1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
| <script setup lang="ts">
| import { computed, reactive, ref, watch } from 'vue'
| import { ElMessage } from 'element-plus'
| import type { FormInstance, FormRules } from 'element-plus'
| import { useRoute, useRouter } from 'vue-router'
| import { createAdminRole, getAdminRoles, updateAdminRole } from '../api/admin'
| import type { AdminRole } from '../types/admin'
|
| type Page = { code: string; name: string; actions: string[] }
| type Module = { name: string; pages: Page[] }
| const modules: Module[] = [
| { name: '首页', pages: [{ code: 'dashboard', name: '首页', actions: ['查看'] }] },
| {
| name: '持卡管理',
| pages: [
| { code: 'talents', name: '持卡人管理', actions: ['查看', '新增', '编辑', '启用/停用', '批量导入', '下载'] },
| { code: 'family-card-applications', name: '家属卡申请', actions: ['查看', '审核', '下载'] },
| { code: 'upgrade-applications', name: '升级申请', actions: ['查看', '审核', '下载'] },
| { code: 'family-cards', name: '家属卡管理', actions: ['查看', '启用/停用', '下载'] },
| { code: 'child-cards', name: '子女管理', actions: ['查看', '审核', '下载'] },
| ],
| },
| {
| name: '商家管理',
| pages: [
| { code: 'merchants', name: '商家查询', actions: ['查看', '新增', '编辑', '启用/停用'] },
| { code: 'products', name: '团单审核', actions: ['查看', '审核'] },
| ],
| },
| {
| name: '订单管理',
| pages: [
| { code: 'orders', name: '订单列表', actions: ['查看', '下载', '刷新'] },
| { code: 'refunds', name: '退款记录', actions: ['查看', '下载', '刷新'] },
| { code: 'hotel-free-verifications', name: '酒店免费核销记录', actions: ['查看', '下载'] },
| ],
| },
| {
| name: '预约管理',
| pages: [
| { code: 'medical-reservations', name: '体检预约管理', actions: ['查看', '审核', '下载'] },
| { code: 'parking-plates', name: '停车车牌管理', actions: ['查看', '审核', '下载'] },
| { code: 'fuel-services', name: '加油服务管理', actions: ['查看', '审核', '下载'] },
| { code: 'spouse-employment', name: '配偶就业管理', actions: ['查看', '审核', '下载'] },
| { code: 'hotel-reservations', name: '酒店预约管理', actions: ['查看', '审核', '下载'] },
| ],
| },
| // 结算记录只读:数据直接来自斗拱,后台没有人工操作。
| { name: '财务管理', pages: [{ code: 'merchant-settlement', name: '结算记录', actions: ['查看'] }] },
| {
| name: '运营管理',
| pages: [
| { code: 'equity-images', name: '权益图片管理', actions: ['查看', '编辑'] },
| { code: 'news', name: '人才资讯管理', actions: ['查看', '新增', '编辑', '删除'] },
| { code: 'policies', name: '人才政策管理', actions: ['查看', '新增', '编辑', '删除'] },
| { code: 'movie-tickets', name: '电影票管理', actions: ['查看', '新增', '编辑', '审核', '批量导入', '下载'] },
| ],
| },
| {
| name: '系统管理',
| pages: [
| { code: 'settings', name: '平台设置', actions: ['查看', '编辑'] },
| { code: 'protocol-settings', name: '协议设置', actions: ['查看', '编辑'] },
| { code: 'roles', name: '角色管理', actions: ['查看', '新增', '编辑', '启用/停用', '删除', '下载'] },
| { code: 'staff', name: '员工管理', actions: ['查看', '新增', '编辑', '启用/停用', '删除', '重置密码', '下载'] },
| ],
| },
| ]
| const route = useRoute(), router = useRouter(), saving = ref(false)
| const id = computed(() => route.params.id ? String(route.params.id) : '')
| const isViewMode = computed(() => route.name === 'role-detail')
| const form = reactive({ roleName: '', remark: '', status: 1, permissionCodes: [] as string[] })
| const formRef = ref<FormInstance>()
| const rules: FormRules = { roleName: [{ required: true, message: '请输入角色名称', trigger: 'blur' }] }
| const code = (page: string, action: string) => `admin.${page}.${({
| '查看': 'view',
| '新增': 'create',
| '编辑': 'edit',
| '启用/停用': 'disable',
| '删除': 'delete',
| '重置密码': 'reset',
| '审核': 'audit',
| '批量导入': 'import',
| '下载': 'export',
| '刷新': 'edit',
| } as Record<string, string>)[action] || 'view'}`
| const pageCodes = (page: Page) => page.actions.map((action) => code(page.code, action))
| const moduleCodes = (module: Module) => module.pages.flatMap(pageCodes)
| const allPermissionCodes = () => modules.flatMap(moduleCodes)
| const checked = (values: string[]) => values.every((value) => form.permissionCodes.includes(value))
| const partial = (values: string[]) => values.some((value) => form.permissionCodes.includes(value)) && !checked(values)
| const toggle = (values: string[], enabled: boolean) => { const selected = new Set(form.permissionCodes); values.forEach((value) => enabled ? selected.add(value) : selected.delete(value)); form.permissionCodes = [...selected] }
| const toggleModule = (module: Module, enabled: boolean) => toggle(moduleCodes(module), enabled)
| const togglePage = (page: Page, enabled: boolean) => toggle(pageCodes(page), enabled)
| const parse = (value: AdminRole['permissionCodes']) => { if (Array.isArray(value)) return value; try { const result = JSON.parse(value || '[]'); return Array.isArray(result) ? result : [] } catch { return [] } }
| async function load() { if (!id.value) return; const role = (await getAdminRoles()).find((item) => String(item.id) === id.value); if (!role) { ElMessage.error('角色不存在'); router.replace('/roles'); return }; Object.assign(form, { roleName: role.roleName, remark: role.remark || '', status: role.status, permissionCodes: role.roleCode === 'platform_admin' ? allPermissionCodes() : parse(role.permissionCodes) }) }
| async function save() { if (isViewMode.value) return; const valid = await formRef.value?.validate().catch(() => false); if (!valid) return; saving.value = true; try { const payload = { roleName: form.roleName.trim(), roleCode: '', remark: form.remark.trim(), status: form.status, permissionCodes: form.permissionCodes }; if (id.value) await updateAdminRole(id.value, payload); else await createAdminRole(payload); ElMessage.success('保存成功'); router.replace('/roles') } finally { saving.value = false } }
| watch(() => route.fullPath, load, { immediate: true })
| </script>
| <template><div class="page-stack role-form"><section class="panel"><div class="panel-header"><h3>{{ isViewMode ? '查看角色' : id ? '编辑角色' : '新增角色' }}</h3></div><el-form ref="formRef" class="base" :model="form" :rules="rules" label-position="top"><el-form-item label="角色名称" prop="roleName"><el-input v-model="form.roleName" :readonly="isViewMode" placeholder="请输入" maxlength="64" /></el-form-item><el-form-item label="备注"><el-input v-model="form.remark" :readonly="isViewMode" placeholder="请输入" maxlength="255" /></el-form-item></el-form><div class="permission-title">操作权限</div><div class="matrix"><div class="line header"><span>板块</span><span>页面</span><span>权限</span></div><template v-for="module in modules" :key="module.name"><div class="module-group"><div v-for="(page, index) in module.pages" :key="page.code" class="line"><span v-if="index === 0" class="module" :style="{ gridRow: `span ${module.pages.length}` }"><el-checkbox :disabled="isViewMode" :model-value="checked(moduleCodes(module))" :indeterminate="partial(moduleCodes(module))" @click.stop @change="toggleModule(module, Boolean($event))">{{ module.name }}</el-checkbox></span><span><el-checkbox :disabled="isViewMode" :model-value="checked(pageCodes(page))" :indeterminate="partial(pageCodes(page))" @click.stop @change="togglePage(page, Boolean($event))">{{ page.name }}</el-checkbox></span><span class="actions"><el-checkbox v-for="action in page.actions" :key="action" :disabled="isViewMode" :model-value="form.permissionCodes.includes(code(page.code, action))" @click.stop @change="toggle([code(page.code, action)], Boolean($event))">{{ action }}</el-checkbox></span></div></div></template></div><div class="buttons"><el-button v-if="!isViewMode" type="primary" :loading="saving" @click="save">保存</el-button><el-button @click="router.back()">返回</el-button></div></section></div></template>
| <style scoped>.base{display:grid;grid-template-columns:360px minmax(400px,1fr);gap:28px;padding:16px 20px}.permission-title{padding:14px 20px;border-top:8px solid #f4f6f8;font-size:16px;font-weight:600}.matrix{display:grid;grid-template-columns:180px 220px minmax(0,1fr);margin:0 20px;border:1px solid #dfe4ea}.module-group{display:contents}.line{display:contents}.line>span{display:flex;align-items:center;min-height:44px;padding:7px 18px;border-right:1px solid #e7ebf0;border-bottom:1px solid #e7ebf0}.line>span:last-child{border-right:0}.module{align-self:stretch}.header{display:contents}.header span{display:flex;align-items:center;justify-content:center;min-height:36px;background:#fafbfd;font-weight:600;border-right:1px solid #e7ebf0;border-bottom:1px solid #e7ebf0}.header span:last-child{border-right:0}.actions{display:flex;align-items:center;gap:26px;flex-wrap:nowrap;white-space:nowrap;overflow:auto}.buttons{padding:20px;display:flex;gap:14px}.buttons .el-button{min-width:78px}@media(max-width:850px){.base{grid-template-columns:1fr}.matrix{grid-template-columns:130px 170px minmax(360px,1fr)}.actions{gap:12px}}</style>
|
|