2025-05-18 17:18:29 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="user-management">
|
2025-06-16 23:08:40 +08:00
|
|
|
|
<!-- 头部区域 -->
|
|
|
|
|
|
<div class="header-section">
|
|
|
|
|
|
<div class="header-content">
|
2026-03-24 02:32:35 +08:00
|
|
|
|
<div class="section-title">用户管理</div>
|
2026-03-24 22:37:03 +08:00
|
|
|
|
<p class="section-description">
|
|
|
|
|
|
管理系统用户,请谨慎操作。删除用户后该用户将无法登录系统。
|
|
|
|
|
|
</p>
|
2025-05-18 17:18:29 +08:00
|
|
|
|
</div>
|
2026-03-24 02:32:35 +08:00
|
|
|
|
<a-button type="primary" @click="showAddUserModal" class="add-btn lucide-icon-btn">
|
|
|
|
|
|
<template #icon><Plus :size="16" /></template>
|
2025-06-16 23:08:40 +08:00
|
|
|
|
添加用户
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 主内容区域 -->
|
|
|
|
|
|
<div class="content-section">
|
|
|
|
|
|
<a-spin :spinning="userManagement.loading">
|
|
|
|
|
|
<div v-if="userManagement.error" class="error-message">
|
|
|
|
|
|
<a-alert type="error" :message="userManagement.error" show-icon />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-05 15:43:11 +08:00
|
|
|
|
<div class="cards-container">
|
|
|
|
|
|
<div v-if="userManagement.users.length === 0" class="empty-state">
|
|
|
|
|
|
<a-empty description="暂无用户数据" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else class="user-cards-grid">
|
|
|
|
|
|
<div v-for="user in userManagement.users" :key="user.id" class="user-card">
|
|
|
|
|
|
<div class="card-header">
|
2026-01-18 22:54:43 +08:00
|
|
|
|
<div class="user-info-main">
|
2025-10-05 15:43:11 +08:00
|
|
|
|
<div class="user-avatar">
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<img
|
|
|
|
|
|
v-if="user.avatar"
|
|
|
|
|
|
:src="user.avatar"
|
|
|
|
|
|
:alt="user.username"
|
|
|
|
|
|
class="avatar-img"
|
|
|
|
|
|
/>
|
2025-10-05 15:43:11 +08:00
|
|
|
|
<div v-else class="avatar-placeholder">
|
|
|
|
|
|
{{ user.username.charAt(0).toUpperCase() }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-18 22:54:43 +08:00
|
|
|
|
<div class="user-info-content">
|
|
|
|
|
|
<div class="name-tag-row">
|
|
|
|
|
|
<h4 class="username">{{ user.username }}</h4>
|
2026-01-20 02:14:51 +08:00
|
|
|
|
<div
|
2026-01-22 00:28:43 +08:00
|
|
|
|
v-if="
|
|
|
|
|
|
user.role === 'admin' ||
|
|
|
|
|
|
user.role === 'superadmin' ||
|
|
|
|
|
|
user.department_name
|
|
|
|
|
|
"
|
2026-01-20 02:14:51 +08:00
|
|
|
|
class="role-dept-badge"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="role-icon-wrapper" :class="getRoleClass(user.role)">
|
|
|
|
|
|
<UserLock v-if="user.role === 'superadmin'" :size="14" />
|
|
|
|
|
|
<UserStar v-else-if="user.role === 'admin'" :size="14" />
|
|
|
|
|
|
<User v-else :size="14" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span v-if="user.department_name" class="dept-text">
|
2026-01-18 22:54:43 +08:00
|
|
|
|
{{ user.department_name }}
|
2026-01-20 02:14:51 +08:00
|
|
|
|
</span>
|
2026-01-18 22:54:43 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="user-id-row">ID: {{ user.user_id || '-' }}</div>
|
2025-10-05 15:43:11 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card-content">
|
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
|
<span class="info-label">手机号:</span>
|
|
|
|
|
|
<span class="info-value phone-text">{{ user.phone_number || '-' }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
|
<span class="info-label">创建时间:</span>
|
|
|
|
|
|
<span class="info-value time-text">{{ formatTime(user.created_at) }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
|
<span class="info-label">最后登录:</span>
|
|
|
|
|
|
<span class="info-value time-text">{{ formatTime(user.last_login) }}</span>
|
2025-06-16 23:08:40 +08:00
|
|
|
|
</div>
|
2025-10-05 15:43:11 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card-actions">
|
|
|
|
|
|
<a-tooltip title="编辑用户">
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<a-button
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="showEditUserModal(user)"
|
2026-03-24 02:32:35 +08:00
|
|
|
|
class="action-btn lucide-icon-btn"
|
2026-01-15 06:01:34 +08:00
|
|
|
|
>
|
2026-03-24 02:32:35 +08:00
|
|
|
|
<Pencil :size="14" />
|
2025-10-05 15:43:11 +08:00
|
|
|
|
<span>编辑</span>
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</a-tooltip>
|
|
|
|
|
|
<a-tooltip title="删除用户">
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
danger
|
|
|
|
|
|
@click="confirmDeleteUser(user)"
|
2026-01-15 06:01:34 +08:00
|
|
|
|
:disabled="
|
|
|
|
|
|
user.id === userStore.userId ||
|
|
|
|
|
|
(user.role === 'superadmin' && userStore.userRole !== 'superadmin')
|
|
|
|
|
|
"
|
2026-03-24 02:32:35 +08:00
|
|
|
|
class="action-btn lucide-icon-btn"
|
2025-10-05 15:43:11 +08:00
|
|
|
|
>
|
2026-03-24 02:32:35 +08:00
|
|
|
|
<Trash2 :size="14" />
|
2025-10-05 15:43:11 +08:00
|
|
|
|
<span>删除</span>
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</a-tooltip>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-06-16 23:08:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</a-spin>
|
|
|
|
|
|
</div>
|
2025-05-18 17:18:29 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 用户表单模态框 -->
|
|
|
|
|
|
<a-modal
|
2025-08-24 18:54:54 +08:00
|
|
|
|
v-model:open="userManagement.modalVisible"
|
2025-05-18 17:18:29 +08:00
|
|
|
|
:title="userManagement.modalTitle"
|
|
|
|
|
|
@ok="handleUserFormSubmit"
|
|
|
|
|
|
:confirmLoading="userManagement.loading"
|
|
|
|
|
|
@cancel="userManagement.modalVisible = false"
|
|
|
|
|
|
:maskClosable="false"
|
2025-06-16 23:08:40 +08:00
|
|
|
|
width="480px"
|
|
|
|
|
|
class="user-modal"
|
2025-05-18 17:18:29 +08:00
|
|
|
|
>
|
2025-06-16 23:08:40 +08:00
|
|
|
|
<a-form layout="vertical" class="user-form">
|
|
|
|
|
|
<a-form-item label="用户名" required class="form-item">
|
|
|
|
|
|
<a-input
|
|
|
|
|
|
v-model:value="userManagement.form.username"
|
2025-09-21 13:33:09 +08:00
|
|
|
|
placeholder="请输入用户名(2-20个字符)"
|
|
|
|
|
|
@blur="validateAndGenerateUserId"
|
|
|
|
|
|
:maxlength="20"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div v-if="userManagement.form.usernameError" class="error-text">
|
|
|
|
|
|
{{ userManagement.form.usernameError }}
|
|
|
|
|
|
</div>
|
2026-05-12 13:47:40 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-if="userManagement.form.generatedUserId && !userManagement.editMode"
|
|
|
|
|
|
class="help-text"
|
|
|
|
|
|
>
|
|
|
|
|
|
登录ID:{{ userManagement.form.generatedUserId }},此ID将用于登录,根据用户名自动生成
|
|
|
|
|
|
</div>
|
2025-09-21 13:33:09 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 手机号字段 -->
|
|
|
|
|
|
<a-form-item label="手机号" class="form-item">
|
|
|
|
|
|
<a-input
|
|
|
|
|
|
v-model:value="userManagement.form.phoneNumber"
|
|
|
|
|
|
placeholder="请输入手机号(可选,可用于登录)"
|
|
|
|
|
|
:maxlength="11"
|
2025-06-16 23:08:40 +08:00
|
|
|
|
/>
|
2025-09-21 13:33:09 +08:00
|
|
|
|
<div v-if="userManagement.form.phoneError" class="error-text">
|
|
|
|
|
|
{{ userManagement.form.phoneError }}
|
|
|
|
|
|
</div>
|
2025-05-18 17:18:29 +08:00
|
|
|
|
</a-form-item>
|
2025-06-16 23:08:40 +08:00
|
|
|
|
|
2025-05-18 17:18:29 +08:00
|
|
|
|
<template v-if="userManagement.editMode">
|
|
|
|
|
|
<div class="password-toggle">
|
|
|
|
|
|
<a-checkbox v-model:checked="userManagement.displayPasswordFields">
|
|
|
|
|
|
修改密码
|
|
|
|
|
|
</a-checkbox>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<template v-if="!userManagement.editMode || userManagement.displayPasswordFields">
|
2025-06-16 23:08:40 +08:00
|
|
|
|
<a-form-item label="密码" required class="form-item">
|
|
|
|
|
|
<a-input-password
|
|
|
|
|
|
v-model:value="userManagement.form.password"
|
|
|
|
|
|
placeholder="请输入密码"
|
|
|
|
|
|
/>
|
2025-05-18 17:18:29 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
2025-06-16 23:08:40 +08:00
|
|
|
|
<a-form-item label="确认密码" required class="form-item">
|
|
|
|
|
|
<a-input-password
|
|
|
|
|
|
v-model:value="userManagement.form.confirmPassword"
|
|
|
|
|
|
placeholder="请再次输入密码"
|
|
|
|
|
|
/>
|
2025-05-18 17:18:29 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2026-01-22 00:28:43 +08:00
|
|
|
|
<a-form-item
|
|
|
|
|
|
v-if="userManagement.editMode && userManagement.form.role === 'superadmin'"
|
|
|
|
|
|
label="角色"
|
|
|
|
|
|
class="form-item"
|
|
|
|
|
|
>
|
2026-04-21 00:18:59 +08:00
|
|
|
|
<a-input value="超级管理员" disabled />
|
2026-01-20 02:14:51 +08:00
|
|
|
|
<div class="help-text">超级管理员账户无法修改角色</div>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
<a-form-item v-else label="角色" class="form-item">
|
2026-04-21 00:18:59 +08:00
|
|
|
|
<a-select v-model:value="userManagement.form.role">
|
2025-05-18 17:18:29 +08:00
|
|
|
|
<a-select-option value="user">普通用户</a-select-option>
|
|
|
|
|
|
<a-select-option value="admin" v-if="userStore.isSuperAdmin">管理员</a-select-option>
|
|
|
|
|
|
</a-select>
|
|
|
|
|
|
</a-form-item>
|
2026-01-18 22:54:43 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 部门选择器(仅超级管理员可见) -->
|
|
|
|
|
|
<a-form-item v-if="userStore.isSuperAdmin" label="部门" class="form-item">
|
2026-04-26 18:51:38 +08:00
|
|
|
|
<a-select v-model:value="userManagement.form.departmentId" placeholder="请选择部门">
|
2026-01-18 23:07:21 +08:00
|
|
|
|
<a-select-option
|
|
|
|
|
|
v-for="dept in departmentManagement.departments"
|
|
|
|
|
|
:key="dept.id"
|
|
|
|
|
|
:value="dept.id"
|
|
|
|
|
|
>
|
2026-01-18 22:54:43 +08:00
|
|
|
|
{{ dept.name }}
|
|
|
|
|
|
</a-select-option>
|
|
|
|
|
|
</a-select>
|
|
|
|
|
|
</a-form-item>
|
2025-05-18 17:18:29 +08:00
|
|
|
|
</a-form>
|
|
|
|
|
|
</a-modal>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-01-15 06:01:34 +08:00
|
|
|
|
import { reactive, onMounted, watch } from 'vue'
|
2026-04-21 00:18:59 +08:00
|
|
|
|
import { message, Modal } from 'ant-design-vue'
|
2026-01-15 06:01:34 +08:00
|
|
|
|
import { useUserStore } from '@/stores/user'
|
2026-01-18 22:54:43 +08:00
|
|
|
|
import { departmentApi } from '@/apis'
|
2026-03-24 02:32:35 +08:00
|
|
|
|
import { Plus, Pencil, Trash2, User, UserLock, UserStar } from 'lucide-vue-next'
|
2026-01-15 06:01:34 +08:00
|
|
|
|
import { formatDateTime } from '@/utils/time'
|
|
|
|
|
|
|
|
|
|
|
|
const userStore = useUserStore()
|
2025-05-18 17:18:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 用户管理相关状态
|
|
|
|
|
|
const userManagement = reactive({
|
|
|
|
|
|
loading: false,
|
|
|
|
|
|
users: [],
|
|
|
|
|
|
error: null,
|
|
|
|
|
|
modalVisible: false,
|
|
|
|
|
|
modalTitle: '添加用户',
|
|
|
|
|
|
editMode: false,
|
|
|
|
|
|
editUserId: null,
|
|
|
|
|
|
form: {
|
|
|
|
|
|
username: '',
|
2025-09-21 13:33:09 +08:00
|
|
|
|
generatedUserId: '', // 自动生成的user_id
|
|
|
|
|
|
phoneNumber: '', // 手机号
|
2025-05-18 17:18:29 +08:00
|
|
|
|
password: '',
|
|
|
|
|
|
confirmPassword: '',
|
2025-09-21 13:33:09 +08:00
|
|
|
|
role: 'user', // 默认角色
|
2026-01-18 22:54:43 +08:00
|
|
|
|
departmentId: null, // 部门ID
|
2025-09-21 13:33:09 +08:00
|
|
|
|
usernameError: '', // 用户名错误信息
|
|
|
|
|
|
phoneError: '' // 手机号错误信息
|
2025-05-18 17:18:29 +08:00
|
|
|
|
},
|
2026-01-15 06:01:34 +08:00
|
|
|
|
displayPasswordFields: true // 编辑时是否显示密码字段
|
|
|
|
|
|
})
|
2025-05-18 17:18:29 +08:00
|
|
|
|
|
2026-01-18 22:54:43 +08:00
|
|
|
|
// 部门列表(仅超级管理员使用)
|
|
|
|
|
|
const departmentManagement = reactive({
|
|
|
|
|
|
departments: []
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 获取部门列表
|
|
|
|
|
|
const fetchDepartments = async () => {
|
|
|
|
|
|
if (!userStore.isSuperAdmin) return // 普通管理员不需要获取所有部门列表
|
|
|
|
|
|
try {
|
|
|
|
|
|
const departments = await departmentApi.getDepartments()
|
|
|
|
|
|
departmentManagement.departments = departments
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取部门列表失败:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-21 13:33:09 +08:00
|
|
|
|
// 添加验证用户名并生成user_id的函数
|
|
|
|
|
|
const validateAndGenerateUserId = async () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const username = userManagement.form.username.trim()
|
2025-09-21 13:33:09 +08:00
|
|
|
|
|
|
|
|
|
|
// 清空之前的错误和生成的ID
|
2026-01-15 06:01:34 +08:00
|
|
|
|
userManagement.form.usernameError = ''
|
|
|
|
|
|
userManagement.form.generatedUserId = ''
|
2025-09-21 13:33:09 +08:00
|
|
|
|
|
|
|
|
|
|
if (!username) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return
|
2025-09-21 13:33:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 在编辑模式下,不需要重新生成user_id
|
|
|
|
|
|
if (userManagement.editMode) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return
|
2025-09-21 13:33:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const result = await userStore.validateUsernameAndGenerateUserId(username)
|
|
|
|
|
|
userManagement.form.generatedUserId = result.user_id
|
2025-09-21 13:33:09 +08:00
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
userManagement.form.usernameError = error.message || '用户名验证失败'
|
2025-09-21 13:33:09 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
2025-09-21 13:33:09 +08:00
|
|
|
|
|
|
|
|
|
|
// 验证手机号格式
|
|
|
|
|
|
const validatePhoneNumber = (phone) => {
|
|
|
|
|
|
if (!phone) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return true // 手机号可选
|
2025-09-21 13:33:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 中国大陆手机号格式验证
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const phoneRegex = /^1[3-9]\d{9}$/
|
|
|
|
|
|
return phoneRegex.test(phone)
|
|
|
|
|
|
}
|
2025-09-21 13:33:09 +08:00
|
|
|
|
|
2025-05-18 17:18:29 +08:00
|
|
|
|
// 监听密码字段显示状态变化
|
2026-01-15 06:01:34 +08:00
|
|
|
|
watch(
|
|
|
|
|
|
() => userManagement.displayPasswordFields,
|
|
|
|
|
|
(newVal) => {
|
|
|
|
|
|
// 当取消显示密码字段时,清空密码输入
|
|
|
|
|
|
if (!newVal) {
|
|
|
|
|
|
userManagement.form.password = ''
|
|
|
|
|
|
userManagement.form.confirmPassword = ''
|
|
|
|
|
|
}
|
2025-05-18 17:18:29 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
)
|
2025-05-18 17:18:29 +08:00
|
|
|
|
|
2025-09-21 13:33:09 +08:00
|
|
|
|
// 监听手机号输入变化
|
2026-01-15 06:01:34 +08:00
|
|
|
|
watch(
|
|
|
|
|
|
() => userManagement.form.phoneNumber,
|
|
|
|
|
|
(newPhone) => {
|
|
|
|
|
|
userManagement.form.phoneError = ''
|
2025-09-21 13:33:09 +08:00
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
if (newPhone && !validatePhoneNumber(newPhone)) {
|
|
|
|
|
|
userManagement.form.phoneError = '请输入正确的手机号格式'
|
|
|
|
|
|
}
|
2025-09-21 13:33:09 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
)
|
2025-09-21 13:33:09 +08:00
|
|
|
|
|
2025-06-16 23:08:40 +08:00
|
|
|
|
// 格式化时间显示
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const formatTime = (timeStr) => formatDateTime(timeStr)
|
2025-06-16 23:08:40 +08:00
|
|
|
|
|
2025-05-18 17:18:29 +08:00
|
|
|
|
// 获取用户列表
|
|
|
|
|
|
const fetchUsers = async () => {
|
|
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
userManagement.loading = true
|
|
|
|
|
|
const users = await userStore.getUsers()
|
|
|
|
|
|
userManagement.users = users
|
|
|
|
|
|
userManagement.error = null
|
2025-05-18 17:18:29 +08:00
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error('获取用户列表失败:', error)
|
|
|
|
|
|
userManagement.error = '获取用户列表失败'
|
2025-05-18 17:18:29 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
userManagement.loading = false
|
2025-05-18 17:18:29 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
2025-05-18 17:18:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 打开添加用户模态框
|
|
|
|
|
|
const showAddUserModal = () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
userManagement.modalTitle = '添加用户'
|
|
|
|
|
|
userManagement.editMode = false
|
|
|
|
|
|
userManagement.editUserId = null
|
2025-05-18 17:18:29 +08:00
|
|
|
|
userManagement.form = {
|
|
|
|
|
|
username: '',
|
2025-09-21 13:33:09 +08:00
|
|
|
|
generatedUserId: '',
|
|
|
|
|
|
phoneNumber: '',
|
2025-05-18 17:18:29 +08:00
|
|
|
|
password: '',
|
|
|
|
|
|
confirmPassword: '',
|
2026-01-15 06:01:34 +08:00
|
|
|
|
role: 'user', // 默认角色为普通用户
|
2026-01-18 22:54:43 +08:00
|
|
|
|
departmentId: null,
|
2025-09-21 13:33:09 +08:00
|
|
|
|
usernameError: '',
|
|
|
|
|
|
phoneError: ''
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
userManagement.displayPasswordFields = true
|
|
|
|
|
|
userManagement.modalVisible = true
|
|
|
|
|
|
}
|
2025-05-18 17:18:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 打开编辑用户模态框
|
|
|
|
|
|
const showEditUserModal = (user) => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
userManagement.modalTitle = '编辑用户'
|
|
|
|
|
|
userManagement.editMode = true
|
|
|
|
|
|
userManagement.editUserId = user.id
|
2025-05-18 17:18:29 +08:00
|
|
|
|
userManagement.form = {
|
|
|
|
|
|
username: user.username,
|
2025-09-21 13:33:09 +08:00
|
|
|
|
generatedUserId: user.user_id || '', // 编辑模式显示现有的user_id
|
|
|
|
|
|
phoneNumber: user.phone_number || '',
|
2025-05-18 17:18:29 +08:00
|
|
|
|
password: '',
|
|
|
|
|
|
confirmPassword: '',
|
2025-09-21 13:33:09 +08:00
|
|
|
|
role: user.role,
|
2026-01-18 22:54:43 +08:00
|
|
|
|
departmentId: user.department_id || null,
|
2025-09-21 13:33:09 +08:00
|
|
|
|
usernameError: '',
|
|
|
|
|
|
phoneError: ''
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
userManagement.displayPasswordFields = false // 默认不显示密码字段
|
|
|
|
|
|
userManagement.modalVisible = true
|
|
|
|
|
|
}
|
2025-05-18 17:18:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理用户表单提交
|
|
|
|
|
|
const handleUserFormSubmit = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 简单验证
|
2025-06-16 23:08:40 +08:00
|
|
|
|
if (!userManagement.form.username.trim()) {
|
2026-04-21 00:18:59 +08:00
|
|
|
|
message.error('用户名不能为空')
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return
|
2025-05-18 17:18:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-21 13:33:09 +08:00
|
|
|
|
// 验证用户名长度
|
2026-01-15 06:01:34 +08:00
|
|
|
|
if (
|
|
|
|
|
|
userManagement.form.username.trim().length < 2 ||
|
|
|
|
|
|
userManagement.form.username.trim().length > 20
|
|
|
|
|
|
) {
|
2026-04-21 00:18:59 +08:00
|
|
|
|
message.error('用户名长度必须在 2-20 个字符之间')
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return
|
2025-09-21 13:33:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证手机号
|
|
|
|
|
|
if (userManagement.form.phoneNumber && !validatePhoneNumber(userManagement.form.phoneNumber)) {
|
2026-04-21 00:18:59 +08:00
|
|
|
|
message.error('请输入正确的手机号格式')
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return
|
2025-09-21 13:33:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-18 17:18:29 +08:00
|
|
|
|
if (userManagement.displayPasswordFields) {
|
|
|
|
|
|
if (!userManagement.form.password) {
|
2026-04-21 00:18:59 +08:00
|
|
|
|
message.error('密码不能为空')
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return
|
2025-05-18 17:18:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (userManagement.form.password !== userManagement.form.confirmPassword) {
|
2026-04-21 00:18:59 +08:00
|
|
|
|
message.error('两次输入的密码不一致')
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return
|
2025-05-18 17:18:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
userManagement.loading = true
|
2025-05-18 17:18:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 根据模式决定创建还是更新用户
|
|
|
|
|
|
if (userManagement.editMode) {
|
|
|
|
|
|
// 创建更新数据对象
|
|
|
|
|
|
const updateData = {
|
2025-06-16 23:08:40 +08:00
|
|
|
|
username: userManagement.form.username.trim(),
|
2025-05-18 17:18:29 +08:00
|
|
|
|
role: userManagement.form.role
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
2025-05-18 17:18:29 +08:00
|
|
|
|
|
2025-09-21 13:33:09 +08:00
|
|
|
|
// 添加手机号字段
|
|
|
|
|
|
if (userManagement.form.phoneNumber) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
updateData.phone_number = userManagement.form.phoneNumber
|
2025-09-21 13:33:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-18 22:54:43 +08:00
|
|
|
|
// 超级管理员可以修改部门
|
|
|
|
|
|
if (userStore.isSuperAdmin && userManagement.form.departmentId) {
|
|
|
|
|
|
updateData.department_id = userManagement.form.departmentId
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-18 17:18:29 +08:00
|
|
|
|
// 如果显示了密码字段并且填写了密码,才更新密码
|
|
|
|
|
|
if (userManagement.displayPasswordFields && userManagement.form.password) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
updateData.password = userManagement.form.password
|
2025-05-18 17:18:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
await userStore.updateUser(userManagement.editUserId, updateData)
|
2026-04-21 00:18:59 +08:00
|
|
|
|
message.success('用户更新成功')
|
2025-05-18 17:18:29 +08:00
|
|
|
|
} else {
|
2025-09-21 13:33:09 +08:00
|
|
|
|
// 创建新用户
|
|
|
|
|
|
const createData = {
|
2025-06-16 23:08:40 +08:00
|
|
|
|
username: userManagement.form.username.trim(),
|
2025-05-18 17:18:29 +08:00
|
|
|
|
password: userManagement.form.password,
|
|
|
|
|
|
role: userManagement.form.role
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
2025-09-21 13:33:09 +08:00
|
|
|
|
|
2026-01-18 22:54:43 +08:00
|
|
|
|
// 超级管理员可以指定部门
|
|
|
|
|
|
if (userStore.isSuperAdmin && userManagement.form.departmentId) {
|
|
|
|
|
|
createData.department_id = userManagement.form.departmentId
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-21 13:33:09 +08:00
|
|
|
|
// 添加手机号字段(如果填写了)
|
|
|
|
|
|
if (userManagement.form.phoneNumber) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
createData.phone_number = userManagement.form.phoneNumber
|
2025-09-21 13:33:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
await userStore.createUser(createData)
|
2026-04-21 00:18:59 +08:00
|
|
|
|
message.success('用户创建成功')
|
2025-05-18 17:18:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新获取用户列表
|
2026-01-15 06:01:34 +08:00
|
|
|
|
await fetchUsers()
|
|
|
|
|
|
userManagement.modalVisible = false
|
2025-05-18 17:18:29 +08:00
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error('用户操作失败:', error)
|
2026-04-21 00:18:59 +08:00
|
|
|
|
message.error(error.message || '操作失败,请稍后重试')
|
2025-05-18 17:18:29 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
userManagement.loading = false
|
2025-05-18 17:18:29 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
2025-05-18 17:18:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 删除用户
|
|
|
|
|
|
const confirmDeleteUser = (user) => {
|
|
|
|
|
|
// 自己不能删除自己
|
|
|
|
|
|
if (user.id === userStore.userId) {
|
2026-04-21 00:18:59 +08:00
|
|
|
|
message.error('不能删除自己的账户')
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return
|
2025-05-18 17:18:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确认对话框
|
2025-09-21 14:31:10 +08:00
|
|
|
|
Modal.confirm({
|
2025-05-18 17:18:29 +08:00
|
|
|
|
title: '确认删除用户',
|
|
|
|
|
|
content: `确定要删除用户 "${user.username}" 吗?此操作不可撤销。`,
|
|
|
|
|
|
okText: '删除',
|
|
|
|
|
|
okType: 'danger',
|
|
|
|
|
|
cancelText: '取消',
|
|
|
|
|
|
async onOk() {
|
|
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
userManagement.loading = true
|
|
|
|
|
|
await userStore.deleteUser(user.id)
|
2026-04-21 00:18:59 +08:00
|
|
|
|
message.success('用户删除成功')
|
2025-05-18 17:18:29 +08:00
|
|
|
|
// 重新获取用户列表
|
2026-01-15 06:01:34 +08:00
|
|
|
|
await fetchUsers()
|
2025-05-18 17:18:29 +08:00
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error('删除用户失败:', error)
|
2026-04-21 00:18:59 +08:00
|
|
|
|
message.error(error.message || '删除失败,请稍后重试')
|
2025-05-18 17:18:29 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
userManagement.loading = false
|
2025-05-18 17:18:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-05-18 17:18:29 +08:00
|
|
|
|
|
2026-01-18 22:54:43 +08:00
|
|
|
|
const getRoleClass = (role) => {
|
|
|
|
|
|
switch (role) {
|
|
|
|
|
|
case 'superadmin':
|
|
|
|
|
|
return 'role-superadmin'
|
|
|
|
|
|
case 'admin':
|
|
|
|
|
|
return 'role-admin'
|
|
|
|
|
|
case 'user':
|
|
|
|
|
|
return 'role-user'
|
|
|
|
|
|
default:
|
|
|
|
|
|
return 'role-default'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-18 17:18:29 +08:00
|
|
|
|
// 在组件挂载时获取用户列表
|
2026-01-18 22:54:43 +08:00
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
await fetchUsers()
|
|
|
|
|
|
await fetchDepartments()
|
2026-01-15 06:01:34 +08:00
|
|
|
|
})
|
2025-05-18 17:18:29 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="less" scoped>
|
|
|
|
|
|
.user-management {
|
2025-06-16 23:08:40 +08:00
|
|
|
|
.content-section {
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
.error-message {
|
|
|
|
|
|
padding: 16px 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 15:43:11 +08:00
|
|
|
|
.cards-container {
|
|
|
|
|
|
.empty-state {
|
|
|
|
|
|
padding: 60px 20px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
2025-06-16 23:08:40 +08:00
|
|
|
|
|
2025-10-05 15:43:11 +08:00
|
|
|
|
.user-cards-grid {
|
|
|
|
|
|
display: grid;
|
2025-12-19 14:41:23 +08:00
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
2025-10-05 15:43:11 +08:00
|
|
|
|
gap: 16px;
|
|
|
|
|
|
// padding: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
.user-card {
|
|
|
|
|
|
background: var(--gray-0);
|
|
|
|
|
|
border: 1px solid var(--gray-150);
|
|
|
|
|
|
border-radius: 8px;
|
2026-01-20 02:14:51 +08:00
|
|
|
|
padding: 12px;
|
2026-01-18 22:54:43 +08:00
|
|
|
|
padding-bottom: 6px;
|
|
|
|
|
|
|
2025-10-05 15:43:11 +08:00
|
|
|
|
transition: all 0.2s ease;
|
2026-03-29 13:47:41 +08:00
|
|
|
|
box-shadow: 0 1px 3px var(--shadow-1);
|
2025-10-05 15:43:11 +08:00
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
|
|
|
|
border-color: var(--gray-200);
|
|
|
|
|
|
}
|
2025-09-21 13:33:09 +08:00
|
|
|
|
|
2025-10-05 15:43:11 +08:00
|
|
|
|
.card-header {
|
2026-01-18 22:54:43 +08:00
|
|
|
|
margin-bottom: 10px;
|
2025-10-05 15:43:11 +08:00
|
|
|
|
|
2026-01-18 22:54:43 +08:00
|
|
|
|
.user-info-main {
|
2025-10-05 15:43:11 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 12px;
|
2026-01-18 22:54:43 +08:00
|
|
|
|
align-items: center;
|
2025-10-05 15:43:11 +08:00
|
|
|
|
|
|
|
|
|
|
.user-avatar {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: var(--gray-50);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
|
|
|
|
|
|
.avatar-img {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.avatar-placeholder {
|
|
|
|
|
|
color: var(--gray-600);
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-18 22:54:43 +08:00
|
|
|
|
.user-info-content {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
|
|
|
|
|
|
.name-tag-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-01-20 02:14:51 +08:00
|
|
|
|
justify-content: space-between;
|
2026-01-18 22:54:43 +08:00
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin-bottom: 2px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
|
|
|
|
|
|
.username {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--gray-900);
|
|
|
|
|
|
line-height: 1.2;
|
2026-01-20 02:14:51 +08:00
|
|
|
|
flex-shrink: 0;
|
2026-01-18 22:54:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 02:14:51 +08:00
|
|
|
|
.role-dept-badge {
|
|
|
|
|
|
display: inline-flex;
|
2026-01-18 22:54:43 +08:00
|
|
|
|
align-items: center;
|
2026-01-20 02:14:51 +08:00
|
|
|
|
gap: 4px;
|
|
|
|
|
|
padding: 2px 8px 2px 4px;
|
|
|
|
|
|
background: var(--gray-50);
|
|
|
|
|
|
border-radius: 4px;
|
2026-01-18 22:54:43 +08:00
|
|
|
|
|
|
|
|
|
|
.role-icon-wrapper {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
2026-01-20 02:14:51 +08:00
|
|
|
|
width: 16px;
|
|
|
|
|
|
height: 16px;
|
2026-01-18 22:54:43 +08:00
|
|
|
|
|
|
|
|
|
|
&.role-superadmin {
|
|
|
|
|
|
color: var(--color-error-700);
|
|
|
|
|
|
}
|
|
|
|
|
|
&.role-admin {
|
|
|
|
|
|
color: var(--color-info-700);
|
|
|
|
|
|
}
|
|
|
|
|
|
&.role-user {
|
|
|
|
|
|
color: var(--color-success-700);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 02:14:51 +08:00
|
|
|
|
.dept-text {
|
2026-01-18 22:54:43 +08:00
|
|
|
|
font-size: 12px;
|
2026-01-20 02:14:51 +08:00
|
|
|
|
color: var(--gray-700);
|
|
|
|
|
|
font-weight: 500;
|
2026-01-18 22:54:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-05 15:43:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-18 22:54:43 +08:00
|
|
|
|
.user-id-row {
|
2025-10-05 15:43:11 +08:00
|
|
|
|
font-size: 12px;
|
2026-01-18 22:54:43 +08:00
|
|
|
|
color: var(--gray-500);
|
2025-10-05 15:43:11 +08:00
|
|
|
|
font-family: 'Monaco', 'Consolas', monospace;
|
2026-01-18 22:54:43 +08:00
|
|
|
|
line-height: 1.2;
|
2025-10-05 15:43:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-21 13:33:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 15:43:11 +08:00
|
|
|
|
.card-content {
|
|
|
|
|
|
.info-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 2px 0;
|
|
|
|
|
|
border-bottom: 1px solid var(--gray-25);
|
|
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-label {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: var(--gray-600);
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
min-width: 70px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-value {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: var(--gray-900);
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
|
|
|
|
|
|
&.time-text {
|
|
|
|
|
|
color: var(--gray-700);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.phone-text {
|
|
|
|
|
|
font-family: 'Monaco', 'Consolas', monospace;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-21 13:33:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 15:43:11 +08:00
|
|
|
|
.card-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
gap: 6px;
|
2026-01-18 22:54:43 +08:00
|
|
|
|
padding-top: 6px;
|
2025-10-05 15:43:11 +08:00
|
|
|
|
border-top: 1px solid var(--gray-25);
|
|
|
|
|
|
|
|
|
|
|
|
.action-btn {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
span {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background: var(--gray-25);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.ant-btn-dangerous:hover {
|
|
|
|
|
|
background: var(--gray-25);
|
2025-11-25 22:34:44 +08:00
|
|
|
|
border-color: var(--color-error-500);
|
|
|
|
|
|
color: var(--color-error-500);
|
2025-10-05 15:43:11 +08:00
|
|
|
|
}
|
2025-09-21 13:33:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-16 23:08:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-18 17:18:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-16 23:08:40 +08:00
|
|
|
|
.time-text {
|
|
|
|
|
|
font-size: 13px;
|
2025-10-05 15:43:11 +08:00
|
|
|
|
color: var(--gray-700);
|
2025-05-18 17:18:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
.phone-text,
|
|
|
|
|
|
.user-id-text {
|
2025-09-21 13:33:09 +08:00
|
|
|
|
font-size: 13px;
|
2025-10-05 15:43:11 +08:00
|
|
|
|
color: var(--gray-900);
|
2025-09-21 13:33:09 +08:00
|
|
|
|
font-family: 'Monaco', 'Consolas', monospace;
|
|
|
|
|
|
}
|
2025-06-16 23:08:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-modal {
|
|
|
|
|
|
:deep(.ant-modal-header) {
|
2026-04-21 00:18:59 +08:00
|
|
|
|
padding: 20px 24px 16px;
|
2025-12-19 14:41:23 +08:00
|
|
|
|
border-bottom: 1px solid var(--gray-150);
|
2025-06-16 23:08:40 +08:00
|
|
|
|
|
|
|
|
|
|
.ant-modal-title {
|
2026-04-21 00:18:59 +08:00
|
|
|
|
font-size: 17px;
|
2025-06-16 23:08:40 +08:00
|
|
|
|
font-weight: 600;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
color: var(--gray-900);
|
2025-06-16 23:08:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-modal-body) {
|
2026-04-21 00:18:59 +08:00
|
|
|
|
padding: 20px 24px 24px;
|
2025-06-16 23:08:40 +08:00
|
|
|
|
}
|
2025-05-18 17:18:29 +08:00
|
|
|
|
|
2025-06-16 23:08:40 +08:00
|
|
|
|
.user-form {
|
|
|
|
|
|
.form-item {
|
2026-04-21 00:18:59 +08:00
|
|
|
|
margin-bottom: 16px;
|
2025-06-16 23:08:40 +08:00
|
|
|
|
|
|
|
|
|
|
:deep(.ant-form-item-label) {
|
2026-04-21 00:18:59 +08:00
|
|
|
|
padding-bottom: 6px;
|
2025-06-16 23:08:40 +08:00
|
|
|
|
|
|
|
|
|
|
label {
|
2026-04-21 00:18:59 +08:00
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: var(--gray-800);
|
2025-06-16 23:08:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-21 13:33:09 +08:00
|
|
|
|
}
|
2025-06-16 23:08:40 +08:00
|
|
|
|
|
2025-09-21 13:33:09 +08:00
|
|
|
|
.error-text {
|
2025-11-25 22:34:44 +08:00
|
|
|
|
color: var(--color-error-500);
|
2025-09-21 13:33:09 +08:00
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
line-height: 1.3;
|
|
|
|
|
|
}
|
2025-06-16 23:08:40 +08:00
|
|
|
|
|
2025-09-21 13:33:09 +08:00
|
|
|
|
.help-text {
|
2025-12-19 14:41:23 +08:00
|
|
|
|
color: var(--gray-600);
|
2025-09-21 13:33:09 +08:00
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
line-height: 1.3;
|
2025-06-16 23:08:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.password-toggle {
|
|
|
|
|
|
margin-bottom: 16px;
|
2026-04-21 00:18:59 +08:00
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
background: var(--gray-25);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
border: 1px solid var(--gray-100);
|
2025-06-16 23:08:40 +08:00
|
|
|
|
|
|
|
|
|
|
:deep(.ant-checkbox-wrapper) {
|
|
|
|
|
|
font-weight: 500;
|
2026-04-21 00:18:59 +08:00
|
|
|
|
color: var(--gray-700);
|
|
|
|
|
|
font-size: 13px;
|
2025-06-16 23:08:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-18 17:18:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-13 15:08:54 +08:00
|
|
|
|
</style>
|