初始化
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
<template>
|
||||
<div class="form-page">
|
||||
<el-page-header @back="handleBack" :title="isEdit ? '编辑用户' : '新增用户'" />
|
||||
<el-divider />
|
||||
|
||||
<el-card shadow="never" class="form-card">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
|
||||
<!-- ===== 基本信息 ===== -->
|
||||
<el-divider content-position="left"><span class="section-label">基本信息</span></el-divider>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="form.username" placeholder="请输入用户名" :disabled="isEdit" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="真实姓名" prop="realName">
|
||||
<el-input v-model="form.realName" placeholder="请输入真实姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="form.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="岗位">
|
||||
<el-input v-model="form.post" placeholder="请输入岗位" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item v-if="!isEdit" label="密码" prop="password">
|
||||
<el-input v-model="form.password" type="password" placeholder="请输入密码" show-password />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-switch v-model="form.status" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="禁用" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- ===== 系统 & 角色绑定(仅编辑模式) ===== -->
|
||||
<template v-if="isEdit">
|
||||
<el-divider content-position="left"><span class="section-label">系统绑定</span></el-divider>
|
||||
|
||||
<el-form-item label="绑定系统">
|
||||
<el-checkbox-group v-model="bindSystemIds" @change="onSystemChange">
|
||||
<el-checkbox
|
||||
v-for="sys in allSystems"
|
||||
:key="sys.systemId"
|
||||
:label="sys.systemId"
|
||||
>
|
||||
{{ sys.systemName }}
|
||||
<el-tag size="small" type="info" style="margin-left:4px">{{ sys.systemCode }}</el-tag>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left"><span class="section-label">角色分配</span></el-divider>
|
||||
<p class="hint-text">先选择系统,再为该用户分配该系统下的角色</p>
|
||||
|
||||
<el-form-item label="选择系统">
|
||||
<el-select
|
||||
v-model="roleSystemId"
|
||||
placeholder="请选择系统"
|
||||
clearable
|
||||
style="width:280px"
|
||||
@change="onRoleSystemChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="sys in allSystems"
|
||||
:key="sys.systemId"
|
||||
:label="sys.systemName + ' (' + sys.systemCode + ')'"
|
||||
:value="sys.systemId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="roleSystemId" label="分配角色">
|
||||
<el-checkbox-group v-model="assignRoleIds">
|
||||
<el-checkbox
|
||||
v-for="r in currentRoleList"
|
||||
:key="r.id"
|
||||
:label="r.id"
|
||||
>
|
||||
{{ r.roleName }}
|
||||
<span style="color:var(--text-muted);font-size:12px;margin-left:4px">{{ r.roleCode }}</span>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<el-empty v-if="currentRoleList.length === 0" description="该系统下暂无角色" :image-size="60" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- ===== 按钮 ===== -->
|
||||
<el-divider />
|
||||
<div class="form-footer">
|
||||
<el-button @click="handleBack">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import request from '@/utils/request'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const isEdit = ref(false)
|
||||
const editId = ref<string>()
|
||||
const submitLoading = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
const form = reactive({
|
||||
username: '',
|
||||
realName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
post: '',
|
||||
password: '',
|
||||
status: 1,
|
||||
})
|
||||
|
||||
const rules = computed<FormRules>(() => ({
|
||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
realName: [{ required: true, message: '请输入真实姓名', trigger: 'blur' }],
|
||||
email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }],
|
||||
password: isEdit.value ? [] : [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
|
||||
}))
|
||||
|
||||
// ===== 系统绑定 =====
|
||||
const allSystems = ref<any[]>([])
|
||||
const bindSystemIds = ref<string[]>([])
|
||||
|
||||
async function fetchAllSystems() {
|
||||
try {
|
||||
const res = await request.get('/zgapi/v1/auth/system/list')
|
||||
allSystems.value = res.data ?? []
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function fetchUserSystems() {
|
||||
if (!editId.value) return
|
||||
try {
|
||||
const res = await request.get(`/zgapi/v1/admin/user/${editId.value}/system`)
|
||||
bindSystemIds.value = res.data ?? []
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function onSystemChange(_val: string[]) {
|
||||
// 用户取消勾选系统时,清除该系统下的角色选择
|
||||
await saveSystems()
|
||||
}
|
||||
|
||||
async function saveSystems() {
|
||||
if (!editId.value) return
|
||||
try {
|
||||
await request.post(`/zgapi/v1/admin/user/${editId.value}/system`, bindSystemIds.value)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// ===== 角色分配 =====
|
||||
const roleSystemId = ref<string>()
|
||||
const assignRoleIds = ref<string[]>([])
|
||||
const currentRoleList = ref<any[]>([])
|
||||
|
||||
async function onRoleSystemChange(sysId: string | undefined) {
|
||||
assignRoleIds.value = []
|
||||
if (!sysId) { currentRoleList.value = []; return }
|
||||
|
||||
// 加载该系统下的角色列表
|
||||
try {
|
||||
const res = await request.post('/zgapi/v1/admin/role/page', { page: 1, pageSize: 999, systemId: sysId })
|
||||
currentRoleList.value = res.data?.records ?? []
|
||||
} catch {
|
||||
currentRoleList.value = []
|
||||
}
|
||||
|
||||
// 加载用户在该系统下的已有角色
|
||||
if (!editId.value) return
|
||||
try {
|
||||
const res = await request.get(`/zgapi/v1/admin/user/${editId.value}/role/${sysId}`)
|
||||
assignRoleIds.value = res.data ?? []
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function saveRoles() {
|
||||
if (!editId.value || !roleSystemId.value) return
|
||||
try {
|
||||
await request.post(`/zgapi/v1/admin/user/${editId.value}/role`, assignRoleIds.value, {
|
||||
params: { systemId: roleSystemId.value }
|
||||
})
|
||||
ElMessage.success('角色已保存')
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// ===== 加载与提交 =====
|
||||
async function loadData() {
|
||||
const id = route.params.id as string
|
||||
if (!id) { isEdit.value = false; return }
|
||||
|
||||
isEdit.value = true
|
||||
editId.value = id
|
||||
|
||||
try {
|
||||
const res = await request.get(`/zgapi/v1/admin/user/${editId.value}`)
|
||||
const data = res.data
|
||||
if (data) {
|
||||
Object.assign(form, {
|
||||
username: data.username ?? '',
|
||||
realName: data.realName ?? '',
|
||||
email: data.email ?? '',
|
||||
phone: data.phone ?? '',
|
||||
post: data.post ?? '',
|
||||
password: '',
|
||||
status: data.status ?? 1,
|
||||
})
|
||||
setTimeout(() => formRef.value?.clearValidate(), 0)
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('加载用户信息失败')
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
|
||||
function handleBack() { router.back() }
|
||||
|
||||
async function handleSubmit() {
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const payload: any = {
|
||||
username: form.username,
|
||||
realName: form.realName,
|
||||
email: form.email,
|
||||
phone: form.phone,
|
||||
post: form.post,
|
||||
status: form.status,
|
||||
}
|
||||
if (isEdit.value) {
|
||||
await request.put('/zgapi/v1/admin/user', { ...payload, id: editId.value })
|
||||
// 保存系统绑定
|
||||
await saveSystems()
|
||||
// 如果有选择角色系统,保存角色
|
||||
if (roleSystemId.value) await saveRoles()
|
||||
ElMessage.success('用户信息已保存')
|
||||
} else {
|
||||
await request.post('/zgapi/v1/admin/user', { ...payload, password: form.password })
|
||||
ElMessage.success('新增成功')
|
||||
}
|
||||
router.back()
|
||||
} catch (e: any) {
|
||||
const msg = e?.response?.data?.msg || e?.message
|
||||
if (msg && msg !== '请求失败') ElMessage.error(msg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchAllSystems()
|
||||
await loadData()
|
||||
if (isEdit.value) {
|
||||
await fetchUserSystems()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-page { display:flex; flex-direction:column; }
|
||||
:deep(.el-page-header) { margin-bottom:4px; }
|
||||
:deep(.el-page-header + .el-divider) { margin:0 0 20px; }
|
||||
|
||||
.form-card { max-width:860px; margin:0 auto; width:100%; }
|
||||
|
||||
.section-label { font-size:15px; font-weight:600; color:var(--text-primary); }
|
||||
|
||||
.hint-text { font-size:13px; color:var(--text-muted); margin-bottom:16px; }
|
||||
|
||||
.form-footer { display:flex; justify-content:flex-end; gap:12px; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.form-card { max-width: 100%; padding: 0 8px; }
|
||||
.form-footer { flex-direction: column; }
|
||||
.form-footer .el-button { width: 100%; }
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<div class="page-wrapper">
|
||||
<!-- 搜索区域 -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<template #header>
|
||||
<div class="card-header-title">
|
||||
<el-icon><Search /></el-icon>
|
||||
<span>搜索条件</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="query" inline>
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="query.username" placeholder="请输入用户名" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱">
|
||||
<el-input v-model="query.email" placeholder="请输入邮箱" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="query.status" placeholder="全部" clearable style="width: 120px">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="禁用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><Search /></el-icon>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新增用户
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-card shadow="never">
|
||||
<el-table :data="tableData" border stripe v-loading="loading" style="width: 100%">
|
||||
<el-table-column prop="username" label="用户名" width="120" />
|
||||
<el-table-column prop="realName" label="真实姓名" width="100" />
|
||||
<el-table-column prop="email" label="邮箱" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="phone" label="手机号" width="130" />
|
||||
<el-table-column prop="status" label="状态" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'danger'" effect="dark" size="small">
|
||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="lastLoginTime" label="最后登录时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ row.lastLoginTime ? new Date(row.lastLoginTime).toLocaleString('zh-CN') : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" align="center" class-name="ops-col">
|
||||
<template #default="{ row }">
|
||||
<div class="actions">
|
||||
<el-button type="primary" size="small" plain @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" size="small" plain @click="handleDelete(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<el-empty description="暂无用户数据" :image-size="100" />
|
||||
</template>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination
|
||||
v-model:current-page="query.page"
|
||||
v-model:page-size="query.pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
background
|
||||
@current-change="fetchData"
|
||||
@size-change="fetchData"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search, Plus } from '@element-plus/icons-vue'
|
||||
import request from '@/utils/request'
|
||||
|
||||
/** 用户类型 */
|
||||
interface UserItem {
|
||||
id?: string
|
||||
username: string
|
||||
realName: string
|
||||
email: string
|
||||
phone: string
|
||||
post: string
|
||||
password?: string
|
||||
status: number
|
||||
lastLoginTime?: string
|
||||
}
|
||||
|
||||
interface SystemItem {
|
||||
id: string
|
||||
systemName: string
|
||||
}
|
||||
|
||||
interface RoleItem {
|
||||
id: string
|
||||
roleName: string
|
||||
}
|
||||
|
||||
// --- 查询 ---
|
||||
const query = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
username: '',
|
||||
email: '',
|
||||
status: '' as string | number,
|
||||
})
|
||||
|
||||
const tableData = ref<UserItem[]>([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
|
||||
async function fetchData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: Record<string, unknown> = {
|
||||
page: query.page,
|
||||
pageSize: query.pageSize,
|
||||
}
|
||||
if (query.username) params.username = query.username
|
||||
if (query.email) params.email = query.email
|
||||
if (query.status !== '') params.status = query.status
|
||||
|
||||
const res = await request.post('/zgapi/v1/admin/user/page', params)
|
||||
tableData.value = res.data?.records ?? []
|
||||
total.value = res.data?.total ?? 0
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
query.page = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
query.username = ''
|
||||
query.email = ''
|
||||
query.status = ''
|
||||
query.page = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// --- 新增/编辑(路由跳转到独立表单页)---
|
||||
const router = useRouter()
|
||||
|
||||
function handleAdd() {
|
||||
router.push('/admin/user/form')
|
||||
}
|
||||
|
||||
function handleEdit(row: UserItem) {
|
||||
router.push('/admin/user/form/' + row.id)
|
||||
}
|
||||
|
||||
function handleDelete(row: UserItem) {
|
||||
ElMessageBox.confirm(`确认删除用户"${row.username}"吗?`, '删除确认', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
}).then(async () => {
|
||||
try {
|
||||
await request.delete(`/zgapi/v1/admin/user/${row.id}`)
|
||||
ElMessage.success('删除成功')
|
||||
fetchData()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 初始化
|
||||
fetchData()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.search-card :deep(.el-card__header) {
|
||||
padding: 14px 20px;
|
||||
}
|
||||
|
||||
.card-header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-wrapper { gap: 10px; }
|
||||
.page-toolbar { flex-direction: column; gap: 10px; }
|
||||
.page-toolbar .el-select,
|
||||
.page-toolbar .el-input { width: 100% !important; }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user