ForcePilot/web/src/components/DepartmentManagementComponent.vue

550 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="department-management">
<!-- 头部区域 -->
<div class="header-section">
<div class="header-content">
<div class="section-title">部门管理</div>
<p class="section-description">管理系统部门部门下的用户会被隔离管理</p>
</div>
<a-button type="primary" @click="showAddDepartmentModal" class="add-btn lucide-icon-btn">
<template #icon><Plus :size="16" /></template>
添加部门
</a-button>
</div>
<!-- 主内容区域 -->
<div class="content-section">
<a-spin :spinning="departmentManagement.loading">
<div v-if="departmentManagement.error" class="error-message">
<a-alert type="error" :message="departmentManagement.error" show-icon />
</div>
<template v-if="departmentManagement.departments.length > 0">
<a-table
:dataSource="departmentManagement.departments"
:columns="columns"
:rowKey="(record) => record.id"
:pagination="false"
class="department-table"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
<div class="department-name">
<span class="name-text">{{ record.name }}</span>
</div>
</template>
<template v-if="column.key === 'description'">
<span class="description-text">{{ record.description || '-' }}</span>
</template>
<template v-if="column.key === 'userCount'">
<span>{{ record.user_count ?? 0 }} 人</span>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-tooltip title="编辑部门">
<a-button
type="text"
size="small"
@click="showEditDepartmentModal(record)"
class="action-btn lucide-icon-btn"
>
<Pencil :size="14" />
</a-button>
</a-tooltip>
<a-tooltip title="删除部门">
<a-button
type="text"
size="small"
danger
@click="confirmDeleteDepartment(record)"
:disabled="record.id === 1"
class="action-btn lucide-icon-btn"
>
<Trash2 :size="14" />
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
</a-table>
</template>
<div v-else class="empty-state">
<a-empty description="暂无部门数据" />
</div>
</a-spin>
</div>
<!-- 部门表单模态框 -->
<a-modal
v-model:open="departmentManagement.modalVisible"
:title="departmentManagement.modalTitle"
@ok="handleDepartmentFormSubmit"
:confirmLoading="departmentManagement.loading"
@cancel="departmentManagement.modalVisible = false"
:maskClosable="false"
width="520px"
class="department-modal"
>
<a-form layout="vertical" class="department-form">
<a-form-item label="部门名称" required class="form-item">
<a-input
v-model:value="departmentManagement.form.name"
placeholder="请输入部门名称"
size="large"
:maxlength="50"
/>
</a-form-item>
<a-form-item label="部门描述" class="form-item">
<a-textarea
v-model:value="departmentManagement.form.description"
placeholder="请输入部门描述(可选)"
:rows="3"
:maxlength="255"
show-count
/>
</a-form-item>
<a-divider v-if="!departmentManagement.editMode" />
<template v-if="!departmentManagement.editMode">
<div class="admin-section-title">
<Users :size="16" />
<span>部门管理员</span>
</div>
<p class="admin-section-hint">
创建部门时必须同时创建管理员,该管理员将负责管理本部门用户
</p>
<a-form-item label="管理员用户ID" required class="form-item">
<a-input
v-model:value="departmentManagement.form.adminUserId"
placeholder="请输入管理员用户ID3-20位字母/数字/下划线)"
size="large"
:maxlength="20"
@blur="checkAdminUserId"
/>
<div v-if="departmentManagement.form.userIdError" class="error-text">
{{ departmentManagement.form.userIdError }}
</div>
<div v-else class="help-text">此ID将用于登录</div>
</a-form-item>
<a-form-item label="密码" required class="form-item">
<a-input-password
v-model:value="departmentManagement.form.adminPassword"
placeholder="请输入管理员密码"
size="large"
:maxlength="50"
/>
</a-form-item>
<a-form-item label="确认密码" required class="form-item">
<a-input-password
v-model:value="departmentManagement.form.adminConfirmPassword"
placeholder="请再次输入密码"
size="large"
:maxlength="50"
/>
</a-form-item>
<a-form-item label="手机号(可选)" class="form-item">
<a-input
v-model:value="departmentManagement.form.adminPhone"
placeholder="请输入手机号(可用于登录)"
size="large"
:maxlength="11"
/>
<div v-if="departmentManagement.form.phoneError" class="error-text">
{{ departmentManagement.form.phoneError }}
</div>
</a-form-item>
</template>
</a-form>
</a-modal>
</div>
</template>
<script setup>
import { reactive, onMounted, watch } from 'vue'
import { notification, Modal } from 'ant-design-vue'
import { departmentApi, apiSuperAdminGet } from '@/apis'
import { Delete as Trash2, Edit3 as Pencil, Plus, Users } from 'lucide-vue-next'
// 表格列定义
const columns = [
{
title: '部门名称',
dataIndex: 'name',
key: 'name',
width: 200
},
{
title: '描述',
dataIndex: 'description',
key: 'description',
ellipsis: true
},
{
title: '用户数量',
dataIndex: 'user_count',
key: 'userCount',
width: 100,
align: 'center'
},
{
title: '操作',
key: 'action',
width: 120,
align: 'center'
}
]
// 部门管理状态
const departmentManagement = reactive({
loading: false,
departments: [],
error: null,
modalVisible: false,
modalTitle: '添加部门',
editMode: false,
editDepartmentId: null,
form: {
name: '',
description: '',
adminUserId: '',
adminPassword: '',
adminConfirmPassword: '',
adminPhone: '',
userIdError: '',
phoneError: ''
}
})
// 获取部门列表
const fetchDepartments = async () => {
try {
departmentManagement.loading = true
departmentManagement.error = null
const departments = await departmentApi.getDepartments()
departmentManagement.departments = departments
} catch (error) {
console.error('获取部门列表失败:', error)
departmentManagement.error = '获取部门列表失败'
} finally {
departmentManagement.loading = false
}
}
// 打开添加部门模态框
const showAddDepartmentModal = () => {
departmentManagement.modalTitle = '添加部门'
departmentManagement.editMode = false
departmentManagement.editDepartmentId = null
departmentManagement.form = {
name: '',
description: '',
adminUserId: '',
adminPassword: '',
adminConfirmPassword: '',
adminPhone: '',
userIdError: '',
phoneError: ''
}
departmentManagement.modalVisible = true
}
// 打开编辑部门模态框
const showEditDepartmentModal = (department) => {
departmentManagement.modalTitle = '编辑部门'
departmentManagement.editMode = true
departmentManagement.editDepartmentId = department.id
departmentManagement.form = {
name: department.name,
description: department.description || '',
adminUserId: '',
adminPassword: '',
adminConfirmPassword: '',
adminPhone: '',
userIdError: '',
phoneError: ''
}
departmentManagement.modalVisible = true
}
// 验证手机号格式
const validatePhoneNumber = (phone) => {
if (!phone) {
return true // 手机号可选
}
const phoneRegex = /^1[3-9]\d{9}$/
return phoneRegex.test(phone)
}
// 监听手机号输入变化
watch(
() => departmentManagement.form.adminPhone,
(newPhone) => {
departmentManagement.form.phoneError = ''
if (newPhone && !validatePhoneNumber(newPhone)) {
departmentManagement.form.phoneError = '请输入正确的手机号格式'
}
}
)
// 检查管理员用户ID是否可用
const checkAdminUserId = async () => {
const userId = departmentManagement.form.adminUserId.trim()
departmentManagement.form.userIdError = ''
if (!userId) {
return
}
// 验证格式
if (!/^[a-zA-Z0-9_]+$/.test(userId)) {
departmentManagement.form.userIdError = '用户ID只能包含字母、数字和下划线'
return
}
if (userId.length < 3 || userId.length > 20) {
departmentManagement.form.userIdError = '用户ID长度必须在3-20个字符之间'
return
}
// 检查是否已存在
try {
const result = await apiSuperAdminGet(`/api/auth/check-user-id/${userId}`)
if (!result.is_available) {
departmentManagement.form.userIdError = '该用户ID已被使用'
}
} catch (error) {
console.error('检查用户ID失败:', error)
}
}
// 处理部门表单提交
const handleDepartmentFormSubmit = async () => {
try {
// 验证部门名称
if (!departmentManagement.form.name.trim()) {
notification.error({ message: '部门名称不能为空' })
return
}
if (departmentManagement.form.name.trim().length < 2) {
notification.error({ message: '部门名称至少2个字符' })
return
}
// 验证管理员用户ID
const adminUserId = departmentManagement.form.adminUserId.trim()
if (!adminUserId) {
notification.error({ message: '请输入管理员用户ID' })
return
}
if (!/^[a-zA-Z0-9_]+$/.test(adminUserId)) {
notification.error({ message: '用户ID只能包含字母、数字和下划线' })
return
}
if (adminUserId.length < 3 || adminUserId.length > 20) {
notification.error({ message: '用户ID长度必须在3-20个字符之间' })
return
}
if (departmentManagement.form.userIdError) {
notification.error({ message: '管理员用户ID已存在或格式错误' })
return
}
// 验证密码
if (!departmentManagement.form.adminPassword) {
notification.error({ message: '请输入管理员密码' })
return
}
if (
departmentManagement.form.adminPassword !== departmentManagement.form.adminConfirmPassword
) {
notification.error({ message: '两次输入的密码不一致' })
return
}
// 验证手机号
if (
departmentManagement.form.adminPhone &&
!validatePhoneNumber(departmentManagement.form.adminPhone)
) {
notification.error({ message: '请输入正确的手机号格式' })
return
}
departmentManagement.loading = true
if (departmentManagement.editMode) {
// 更新部门
await departmentApi.updateDepartment(departmentManagement.editDepartmentId, {
name: departmentManagement.form.name.trim(),
description: departmentManagement.form.description.trim() || undefined
})
notification.success({ message: '部门更新成功' })
} else {
// 创建部门,同时创建管理员
await departmentApi.createDepartment({
name: departmentManagement.form.name.trim(),
description: departmentManagement.form.description.trim() || undefined,
admin_user_id: adminUserId,
admin_password: departmentManagement.form.adminPassword,
admin_phone: departmentManagement.form.adminPhone || undefined
})
notification.success({ message: `部门创建成功,管理员 "${adminUserId}" 已创建` })
}
// 重新获取部门列表
await fetchDepartments()
departmentManagement.modalVisible = false
} catch (error) {
console.error('部门操作失败:', error)
notification.error({
message: '操作失败',
description: error.message || '请稍后重试'
})
} finally {
departmentManagement.loading = false
}
}
// 删除部门
const confirmDeleteDepartment = (department) => {
Modal.confirm({
title: '确认删除部门',
content: `确定要删除部门 "${department.name}" 吗?此操作不可撤销。该部门下的用户会被迁移到默认部门,部门级配置和部门 API Key 会一并清理。`,
okText: '删除',
okType: 'danger',
cancelText: '取消',
async onOk() {
try {
departmentManagement.loading = true
await departmentApi.deleteDepartment(department.id)
notification.success({ message: '部门删除成功' })
// 重新获取部门列表
await fetchDepartments()
} catch (error) {
console.error('删除部门失败:', error)
notification.error({
message: '删除失败',
description: error.message || '请稍后重试'
})
} finally {
departmentManagement.loading = false
}
}
})
}
// 在组件挂载时获取部门列表
onMounted(() => {
fetchDepartments()
})
</script>
<style lang="less" scoped>
.department-management {
.content-section {
overflow: hidden;
.error-message {
padding: 16px 24px;
}
.empty-state {
padding: 60px 20px;
text-align: center;
}
.department-table {
:deep(.ant-table-thead > tr > th) {
background: var(--gray-50);
font-weight: 500;
padding: 8px 12px;
}
:deep(.ant-table-tbody > tr > td) {
padding: 8px 12px;
}
.department-name {
.name-text {
font-weight: 500;
color: var(--gray-900);
}
}
.description-text {
color: var(--gray-600);
}
.action-btn {
padding: 4px 8px;
border-radius: 6px;
transition: all 0.2s ease;
&:hover {
background: var(--gray-25);
}
}
}
}
}
.department-modal {
:deep(.ant-modal-header) {
padding: 20px 24px;
border-bottom: 1px solid var(--gray-150);
.ant-modal-title {
font-size: 16px;
font-weight: 600;
color: var(--gray-900);
}
}
:deep(.ant-modal-body) {
padding: 24px;
}
.department-form {
.form-item {
margin-bottom: 20px;
:deep(.ant-form-item-label) {
padding-bottom: 4px;
label {
font-weight: 500;
color: var(--gray-900);
}
}
}
}
.error-text {
color: var(--color-error-500);
font-size: 12px;
margin-top: 4px;
line-height: 1.3;
}
.help-text {
color: var(--gray-600);
font-size: 12px;
margin-top: 4px;
line-height: 1.3;
}
}
</style>