| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- <template>
- <div class="app-container">
- <el-card>
- <template #header>
- <div class="card-header">
- <span>用户列表</span>
- <el-button type="primary" @click="handleAdd">新增用户</el-button>
- </div>
- </template>
- <el-form :inline="true" :model="queryParams" class="search-form">
- <el-form-item label="账号">
- <el-input v-model="queryParams.username" placeholder="请输入账号" clearable />
- </el-form-item>
- <el-form-item label="状态">
- <el-select v-model="queryParams.status" placeholder="全部" clearable style="width: auto; min-width: 120px;">
- <el-option label="正常" :value="0" />
- <el-option label="禁用" :value="1" />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleQuery">查询</el-button>
- <el-button @click="resetQuery">重置</el-button>
- </el-form-item>
- </el-form>
- <el-table v-loading="loading" :data="tableData" border>
- <el-table-column type="index" label="序号" width="60" />
- <el-table-column prop="username" label="账号" />
- <el-table-column prop="realName" label="姓名" />
- <el-table-column prop="employeeType" label="用户类型">
- <template #default="{ row }">
- {{ adminEmployeeTypeLabel(row.employeeType) }}
- </template>
- </el-table-column>
- <el-table-column label="关联角色" min-width="150">
- <template #default="{ row }">
- <template v-if="row.roleIds && row.roleIds.length > 0">
- <el-tag v-for="rid in row.roleIds" :key="rid" size="small" style="margin-right: 4px; margin-bottom: 2px;">
- {{ getRoleName(rid) }}
- </el-tag>
- </template>
- <span v-else class="text-gray">未分配</span>
- </template>
- </el-table-column>
- <el-table-column prop="deptName" label="部门" />
- <el-table-column prop="email" label="邮箱" />
- <el-table-column prop="phone" label="手机号" />
- <el-table-column prop="status" label="状态" width="100">
- <template #default="{ row }">
- <el-tag :type="row.status === 0 ? 'success' : 'danger'">
- {{ row.status === 0 ? '正常' : '禁用' }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="createTime" label="创建时间" />
- <el-table-column label="操作" width="260">
- <template #default="{ row }">
- <el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
- <el-button link type="success" @click="handleAssignRole(row)">分配角色</el-button>
- <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- <el-pagination
- v-model:current-page="queryParams.pageNum"
- v-model:page-size="queryParams.pageSize"
- :total="total"
- :page-sizes="[10, 20, 50]"
- layout="total, sizes, prev, pager, next, jumper"
- class="pagination"
- @current-change="handlePageChange"
- @size-change="handleSizeChange"
- />
- </el-card>
- <!-- 分配角色弹窗 -->
- <el-dialog v-model="roleDialogVisible" title="分配角色" width="500px">
- <el-checkbox-group v-model="selectedRoleIds">
- <el-checkbox v-for="role in allRoles" :key="role.id" :label="role.id">
- {{ role.roleName }}({{ role.roleCode }})
- </el-checkbox>
- </el-checkbox-group>
- <el-empty v-if="allRoles.length === 0" description="暂无可分配的角色" :image-size="60" />
- <template #footer>
- <el-button @click="roleDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="submitAssignRole">确认</el-button>
- </template>
- </el-dialog>
- <!-- 管理员弹窗 -->
- <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px">
- <el-form ref="formRef" :model="form" :rules="formRules" label-width="80px">
- <el-form-item label="账号" prop="username">
- <el-input v-model="form.username" :disabled="isEdit" />
- </el-form-item>
- <el-form-item label="密码" prop="password">
- <el-input v-model="form.password" type="password" :placeholder="isEdit ? '不填表示不修改密码' : '请输入密码'" show-password />
- </el-form-item>
- <el-form-item label="姓名" prop="realName">
- <el-input v-model="form.realName" />
- </el-form-item>
- <el-form-item label="邮箱" prop="email">
- <el-input v-model="form.email" />
- </el-form-item>
- <el-form-item label="手机号" prop="phone">
- <el-input v-model="form.phone" />
- </el-form-item>
- <el-form-item label="所属部门" prop="deptId">
- <el-tree-select
- v-model="form.deptId"
- :data="deptTreeForSelect"
- :props="{ label: 'name', value: 'id', children: 'children' }"
- check-strictly
- clearable
- placeholder="请选择所属部门"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="用户类型" prop="employeeType">
- <el-select v-model="form.employeeType" placeholder="请选择用户类型" style="width: 100%">
- <el-option label="普通用户" value="common_user" />
- <el-option label="部门运维" value="dept_manager" />
- <el-option label="流程运维" value="flow_manager" />
- <el-option label="管理员" value="super_admin" />
- </el-select>
- </el-form-item>
- <el-form-item label="状态">
- <el-radio-group v-model="form.status">
- <el-radio :label="0">正常</el-radio>
- <el-radio :label="1">禁用</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="企微账号">
- <el-input v-model="form.wecomUserId" placeholder="企业微信用户ID" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="dialogVisible = false">取消</el-button>
- <el-button type="primary" @click="submitForm">确认</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, shallowRef, reactive, onMounted } from 'vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import type { FormInstance, FormRules } from 'element-plus'
- import { listUser, addUser, updateUser, deleteUser, assignUserRoles } from '@/api/system/user'
- import { listRole } from '@/api/system/role'
- import { listDept } from '@/api/system/dept'
- import { adminEmployeeTypeLabel } from '@/utils/format'
- import type { User, Role, Dept } from '@/types/system'
- const loading = ref(false)
- const tableData = ref<User[]>([])
- const total = ref(0)
- const deptList = ref<Dept[]>([])
- const queryParams = reactive({
- pageNum: 1,
- pageSize: 10,
- username: undefined as string | undefined,
- status: undefined as number | undefined
- })
- const deptTreeForSelect = shallowRef<Dept[]>([])
- // 管理员弹窗
- const dialogVisible = ref(false)
- const dialogTitle = ref('')
- const isEdit = ref(false)
- const formRef = ref<FormInstance>()
- const form = reactive<Partial<User>>({
- id: undefined,
- username: '',
- password: '',
- realName: '',
- email: '',
- phone: '',
- deptId: undefined,
- employeeType: 'common_user',
- status: 0
- })
- const PASSWORD_PLACEHOLDER = '********'
- const formRules: FormRules = {
- username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
- password: [{
- validator: (rule: any, value: any, callback: any) => {
- if (!isEdit.value && (!value || value.trim() === '')) {
- callback(new Error('请输入密码'))
- } else {
- callback()
- }
- },
- trigger: 'blur'
- }],
- employeeType: [{ required: true, message: '请选择用户类型', trigger: 'change' }]
- }
- async function loadDepts() {
- try {
- const res = await listDept()
- deptList.value = res
- deptTreeForSelect.value = [{ id: 0, parentId: 0, name: '根部门', sort: 0, status: 0 }, ...res]
- } catch (error: any) {
- ElMessage.error(error?.message || '加载部门失败')
- }
- }
- async function loadData() {
- loading.value = true
- try {
- const res = await listUser(queryParams)
- tableData.value = res.list
- total.value = res.total
- } finally {
- loading.value = false
- }
- }
- function handleQuery() {
- queryParams.pageNum = 1
- loadData()
- }
- function handlePageChange() {
- loadData()
- }
- function handleSizeChange() {
- queryParams.pageNum = 1
- loadData()
- }
- function resetQuery() {
- queryParams.username = undefined
- queryParams.status = undefined
- queryParams.pageNum = 1
- loadData()
- }
- function handleAdd() {
- isEdit.value = false
- dialogTitle.value = '新增用户'
- Object.assign(form, {
- id: undefined,
- username: '',
- password: '',
- realName: '',
- email: '',
- phone: '',
- deptId: undefined,
- employeeType: 'common_user',
- status: 0
- })
- dialogVisible.value = true
- }
- function handleEdit(row: User) {
- isEdit.value = true
- dialogTitle.value = '编辑用户'
- Object.assign(form, {
- id: row.id,
- username: row.username,
- password: PASSWORD_PLACEHOLDER,
- realName: row.realName,
- email: row.email,
- phone: row.phone,
- deptId: row.deptId,
- employeeType: row.employeeType || 'common_user',
- status: row.status
- })
- dialogVisible.value = true
- }
- async function handleDelete(row: User) {
- try {
- await ElMessageBox.confirm(`确认删除用户 "${row.username}" 吗?`, '提示', { type: 'warning' })
- await deleteUser(row.id)
- ElMessage.success('删除成功')
- loadData()
- } catch {
- // cancel
- }
- }
- async function submitForm() {
- const valid = await formRef.value?.validate().catch(() => false)
- if (!valid) return
- if (isEdit.value) {
- const editData: Partial<User> = {
- id: form.id,
- username: form.username,
- realName: form.realName,
- email: form.email,
- phone: form.phone,
- deptId: form.deptId,
- employeeType: form.employeeType,
- status: form.status,
- wecomUserId: form.wecomUserId
- }
- if (form.password && form.password.trim() !== '' && form.password !== PASSWORD_PLACEHOLDER) {
- editData.password = form.password
- }
- await updateUser(editData)
- ElMessage.success('修改成功')
- } else {
- await addUser(form)
- ElMessage.success('新增成功')
- }
- dialogVisible.value = false
- loadData()
- }
- // 分配角色
- const roleDialogVisible = ref(false)
- const allRoles = ref<Role[]>([])
- const selectedRoleIds = ref<number[]>([])
- const assigningUserId = ref<number | null>(null)
- async function loadAllRoles() {
- try {
- const res = await listRole({ pageNum: 1, pageSize: 200 })
- allRoles.value = res.list
- } catch { /* ignore */ }
- }
- function getRoleName(roleId: number): string {
- const role = allRoles.value.find(r => r.id === roleId)
- return role ? role.roleName : `角色#${roleId}`
- }
- function handleAssignRole(row: User) {
- assigningUserId.value = row.id
- selectedRoleIds.value = row.roleIds ? [...row.roleIds] : []
- roleDialogVisible.value = true
- }
- async function submitAssignRole() {
- if (assigningUserId.value == null) return
- try {
- await assignUserRoles(assigningUserId.value, selectedRoleIds.value)
- ElMessage.success('角色分配成功')
- roleDialogVisible.value = false
- loadData()
- } catch (e: any) {
- ElMessage.error(e?.message || '分配失败')
- }
- }
- onMounted(() => {
- Promise.all([loadDepts(), loadData(), loadAllRoles()])
- })
- </script>
- <style scoped>
- .card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .search-form {
- margin-bottom: 20px;
- }
- .pagination {
- margin-top: 20px;
- justify-content: flex-end;
- }
- .text-gray {
- color: #909399;
- }
- </style>
|