Files
KCGL/inventory-web/src/views/system/UserCreate.vue
dxc 4324e5a688 feat: add field-level data protection for BOM and user management
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 15:16:11 +08:00

427 lines
13 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="app-container">
<el-card>
<template #header>
<div class="card-header">
<span style="font-weight: bold;">员工账号管理</span>
<el-button v-if="userStore.hasPermission('system_user:operation')" type="primary" @click="handleCreate">
+ 新增员工
</el-button>
</div>
</template>
<el-table
v-loading="tableLoading"
:data="tableData"
border
style="width: 100%"
>
<el-table-column v-if="hasColumnPermission('username')" prop="username" label="用户标识" min-width="180" />
<el-table-column v-if="hasColumnPermission('department')" prop="department" label="所属部门" width="150">
<template #default="scope">
<el-tag>{{ scope.row.department }}</el-tag>
</template>
</el-table-column>
<el-table-column v-if="hasColumnPermission('role')" prop="role" label="系统角色" width="180">
<template #default="scope">
{{ formatRole(scope.row.role) }}
</template>
</el-table-column>
<el-table-column v-if="hasColumnPermission('email')" prop="email" label="邮箱" min-width="200" />
<el-table-column v-if="hasColumnPermission('status')" prop="status" label="状态" width="100" align="center">
<template #default="scope">
<el-tag :type="scope.row.status === 'active' ? 'success' : 'danger'">
{{ scope.row.status === 'active' ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="hasColumnPermission('created_at')" prop="created_at" label="创建时间" width="160" />
<el-table-column v-if="userStore.hasPermission('system_user:operation')" label="操作" width="180" fixed="right">
<template #default="scope">
<el-button link type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-popconfirm title="确定要删除该用户吗?" @confirm="handleDelete(scope.row)">
<template #reference>
<el-button link type="danger" size="small">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog
v-model="dialogVisible"
:title="isEdit ? '编辑员工账号' : '新增员工账号'"
width="500px"
@close="resetForm"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<el-form-item label="真实姓名" prop="cn_name" v-if="hasFormFieldPermission('cn_name')">
<el-input
v-model="form.cn_name"
placeholder="请输入中文姓名 (如: 张三)"
:disabled="isEdit || !userStore.hasPermission('system_user:operation')"
@input="handleNameInput"
/>
</el-form-item>
<el-form-item label="登录账号" prop="username" v-if="hasFormFieldPermission('username')">
<el-input
v-model="form.username"
placeholder="自动生成,可修改 (如: zhangsan)"
:disabled="isEdit || !userStore.hasPermission('system_user:operation')"
>
<template #append>
<span v-if="!isEdit" style="font-size: 12px; color: #999;">重复自动+1</span>
</template>
</el-input>
</el-form-item>
<el-form-item label="密码" prop="password" v-if="hasFormFieldPermission('password')">
<el-input
v-model="form.password"
type="password"
show-password
:placeholder="isEdit ? '不修改请留空' : '设置初始密码'"
:disabled="!userStore.hasPermission('system_user:operation')"
/>
</el-form-item>
<el-form-item label="所属部门" prop="department" v-if="hasFormFieldPermission('department')">
<el-select
v-model="form.department"
placeholder="请输入或选择部门"
style="width: 100%"
filterable
allow-create
default-first-option
:disabled="!userStore.hasPermission('system_user:operation')"
>
<el-option v-for="item in departmentOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="系统角色" prop="role" v-if="hasFormFieldPermission('role')">
<el-select v-model="form.role" placeholder="授予权限" style="width: 100%" :disabled="!userStore.hasPermission('system_user:operation')">
<el-option
v-for="option in roleOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
<el-form-item label="邮箱" prop="email" v-if="hasFormFieldPermission('email')">
<el-input v-model="form.email" placeholder="请输入邮箱" :disabled="!userStore.hasPermission('system_user:operation')" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button v-if="userStore.hasPermission('system_user:operation')" type="primary" @click="onSubmit" :loading="submitLoading">
{{ isEdit ? '确认修改' : '确认创建' }}
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, computed } from 'vue'
import { createUser, updateUser, getUserList, deleteUser } from '@/api/auth'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
import { pinyin } from 'pinyin-pro' // ★ 务必安装: npm install pinyin-pro
const userStore = useUserStore()
// 列与权限Code的映射关系数据库中的code
const permissionMap: Record<string, string> = {
username: 'system_user:username',
department: 'system_user:department',
role: 'system_user:role',
email: 'system_user:email',
status: 'system_user:status',
created_at: 'system_user:created_at',
// 表单字段
cn_name: 'system_user:username',
password: 'system_user:password',
}
// 检查列权限
const hasColumnPermission = (prop: string) => {
if (userStore.role === 'SUPER_ADMIN' || userStore.username === 'IRIS') {
return true
}
const code = permissionMap[prop]
return code ? userStore.hasPermission(code) : false
}
// 检查表单字段权限
const hasFormFieldPermission = (fieldName: string) => {
if (userStore.role === 'SUPER_ADMIN' || userStore.username === 'IRIS') {
return true
}
const code = permissionMap[fieldName]
return code ? userStore.hasPermission(code) : false
}
const tableLoading = ref(false)
const submitLoading = ref(false)
const dialogVisible = ref(false)
const tableData = ref<any[]>([])
const departmentOptions = ref<string[]>([])
const formRef = ref()
const isEdit = ref(false)
const currentId = ref<number | null>(null)
const form = reactive({
cn_name: '', // 真实姓名 (前端输入)
username: '', // 登录账号 (拼音)
password: '',
department: '',
role: '',
email: ''
})
// ★ 监听中文输入,自动转拼音
const handleNameInput = (val: string) => {
if (isEdit.value) return // 编辑模式下不联动
if (!val) {
form.username = ''
return
}
try {
// toneType: 'none' 去除声调, type: 'array' 转数组后拼接
const pinyinStr = pinyin(val, { toneType: 'none', type: 'array' }).join('')
// 将拼音转小写
form.username = pinyinStr.toLowerCase()
} catch (e) {
// 如果转换失败(比如输入符号),不做处理
}
}
const roleOptions = computed(() => {
const options = [
{ label: '主管', value: 'SUPERVISOR' },
{ label: '财务', value: 'FINANCE' },
{ label: '库管', value: 'WAREHOUSE_MGR' },
{ label: '入库员', value: 'INBOUND' },
{ label: '出库员', value: 'OUTBOUND' },
{ label: '采购员', value: 'PURCHASER' },
{ label: '销售', value: 'SALES' }
]
let role = userStore.user?.role || userStore.role
let username = userStore.user?.username || userStore.name
if (!role) role = localStorage.getItem('role')
if (!username) username = localStorage.getItem('username')
const safeRole = role ? String(role).toUpperCase() : ''
const safeUsername = username ? String(username).toUpperCase() : ''
const isSuperAdmin = (safeRole === 'SUPER_ADMIN') || (safeUsername === 'IRIS')
if (isSuperAdmin) {
options.unshift({ label: '超级管理员', value: 'SUPER_ADMIN' })
}
return options
})
const rules = computed(() => {
const commonRules: any = {
cn_name: [{ required: true, message: '请输入真实姓名', trigger: 'blur' }],
username: [{ required: true, message: '账号不能为空', trigger: 'blur' }],
role: [{ required: true, message: '请选择角色', trigger: 'change' }],
department: [{ required: true, message: '请输入或选择部门', trigger: ['blur', 'change'] }],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: ['blur', 'change'] }
]
}
if (!isEdit.value) {
return {
...commonRules,
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码至少6位', trigger: 'blur' }
]
}
} else {
return {
...commonRules,
password: [
{ min: 6, message: '密码至少6位', trigger: 'blur' }
]
}
}
})
// --- 逻辑方法 ---
const getList = async () => {
tableLoading.value = true
try {
const res = await getUserList()
tableData.value = res.data || []
extractDepartments(tableData.value)
} catch (error) {
// 错误已由全局拦截器统一处理
console.error('Fetch users failed:', error)
} finally {
tableLoading.value = false
}
}
const extractDepartments = (data: any[]) => {
const deptSet = new Set<string>()
if (data && data.length > 0) {
data.forEach(user => {
if (user.department && user.department.trim() !== '') {
deptSet.add(user.department)
}
})
}
departmentOptions.value = Array.from(deptSet)
}
const handleCreate = () => {
isEdit.value = false
currentId.value = null
// 重置表单
form.cn_name = ''
form.username = ''
form.password = ''
form.department = ''
form.role = ''
form.email = ''
if (formRef.value) formRef.value.clearValidate()
dialogVisible.value = true
}
const handleEdit = (row: any) => {
isEdit.value = true
currentId.value = row.id
// 回显数据
// 注意:后端返回的 row.username 已经是 "张三(zhangsan01)" 格式
const displayStr = row.username || ''
if (displayStr.includes('(') && displayStr.includes(')')) {
const parts = displayStr.split('(')
form.cn_name = parts[0]
form.username = parts[1].replace(')', '')
} else {
// 兼容旧数据
form.cn_name = displayStr
form.username = displayStr
}
form.department = row.department
form.role = row.role
form.email = row.email || ''
form.password = ''
if (formRef.value) formRef.value.clearValidate()
dialogVisible.value = true
}
const onSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (isEdit.value && currentId.value) {
await updateUser(currentId.value, form)
ElMessage.success(`用户 ${form.cn_name} 修改成功!`)
} else {
// 创建时,后端会自动处理重复逻辑
const res: any = await createUser(form)
// 这里的 res.data.account_id 会返回最终生成的账号 (如 zhangsan1)
ElMessage.success(`创建成功!登录账号为: ${res.data.account_id} (已自动防重)`)
}
dialogVisible.value = false
getList()
} catch (error) {
// 错误已由全局拦截器统一处理
} finally {
submitLoading.value = false
}
}
})
}
const resetForm = () => {
if (!formRef.value) return
formRef.value.resetFields()
form.cn_name = ''
form.username = ''
form.password = ''
form.department = ''
form.role = ''
form.email = ''
}
const handleDelete = async (row: any) => {
try {
await deleteUser(row.id)
ElMessage.success('删除成功')
getList()
} catch (error) {
// 错误已由全局拦截器统一处理
}
}
const formatRole = (val: string) => {
const map: Record<string, string> = {
'SUPER_ADMIN': '超级管理员',
'SUPERVISOR': '主管',
'FINANCE': '财务',
'WAREHOUSE_MGR': '库管',
'INBOUND': '入库员',
'OUTBOUND': '出库员',
'PURCHASER': '采购员',
'SALES': '销售'
}
const key = val ? val.toUpperCase() : ''
return map[key] || val
}
onMounted(() => {
getList()
})
</script>
<style scoped>
.app-container {
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.dialog-footer {
text-align: right;
margin-top: 20px;
}
</style>