Files
2026-07-15 16:13:27 +08:00

310 lines
9.8 KiB
Vue
Raw Permalink 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="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>