feat(role-scope): 接入 checkAdmin 细粒度作用域,CrudTable 支持只读模式

- stores/user: 新增 roleScopeType/Label 与 isScopeAdmin/isDeptManager/isWorkgroupLead/isScopeMember;AdminCheckResult.role_scope 补 DeptUUID/WorkgroupUUID

- CrudTable: 新增 editable prop,false 时隐藏新增/批量删除/编辑/删除(操作列全空则整列隐藏)

- Users: 四态标题/图标/横幅色/文案,仅 member 关闭行选择与编辑

- Products/Orders: 四态标题与文案、两态横幅;editable 取特权模式

- 清理 company 域 5 页与 system 三页的过期 role_scope TODO 注释
This commit is contained in:
TermiNexus committed 2026-09-15 14:24:40 +08:00
1 parent 3d4c46b7ee
commit 9d1d6dca93
14 files changed
+191 -81

No files matched your search

+21 -12
View File
@@ -80,8 +80,17 @@ const props = defineProps<{
defaultPageSize?: number
/** 新增前拦截回调:返回 false 阻止打开新增弹窗(如依赖父级筛选条件的页面需先选条件) */
beforeCreate?: () => boolean | void
/**
* 是否允许新增/编辑/删除(默认 true)。
* 传 false 时隐藏新增、批量删除、行内编辑/删除按钮,实现只读视图
* (用于 role_scope 非管理员场景,如 Products/Orders 仅管理员可改)。
*/
editable?: boolean
}>()
// 是否可写:editable 未传视为可写;传 false 时隐藏新增/编辑/删除入口(只读视图)
const canEdit = computed(() => props.editable !== false)
// 表格实际使用的最大高度:显式 maxHeight 优先;
// stretch(整页列表)场景默认取视口高度 55%,保证表格与卡片一体、
// 不产生页面级滚动栏(行多时表格内部滚动)
@@ -488,11 +497,11 @@ defineExpose({
<div class="table-header">
<span class="table-title">{{ title || table }}</span>
<div class="table-actions">
<el-button type="primary" @click="onHandleCreate">
<el-button v-if="canEdit" type="primary" @click="onHandleCreate">
<el-icon><Plus /></el-icon>新增
</el-button>
<el-button
v-if="selectable"
v-if="canEdit && selectable"
type="danger"
:disabled="selectedRows.length === 0"
@click="handleBatchDelete"
@@ -595,12 +604,12 @@ defineExpose({
</template>
</vxe-column>
<!-- 操作列 -->
<vxe-column title="操作" width="200" fixed="right">
<!-- 操作列(查看/编辑/删除全不可用时整列隐藏) -->
<vxe-column v-if="onView || canEdit" title="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button v-if="onView" link type="primary" @click="handleView(row as T)">查看</el-button>
<el-button link type="primary" @click="handleEdit(row as T)">编辑</el-button>
<el-button link type="danger" @click="handleDelete(row as T)">删除</el-button>
<el-button v-if="canEdit" link type="primary" @click="handleEdit(row as T)">编辑</el-button>
<el-button v-if="canEdit" link type="danger" @click="handleDelete(row as T)">删除</el-button>
</template>
</vxe-column>
</vxe-table>
@@ -695,14 +704,14 @@ defineExpose({
</div>
</div>
<!-- 卡片操作:查看 / 编辑 / 删除 -->
<div class="crud-card-actions">
<div v-if="onView || canEdit" class="crud-card-actions">
<el-button v-if="onView" link type="primary" @dblclick.stop @click.stop="handleView(node.row as T)">
<el-icon><View /></el-icon>查看
</el-button>
<el-button link type="primary" @dblclick.stop @click.stop="handleEdit(node.row as T)">
<el-button v-if="canEdit" link type="primary" @dblclick.stop @click.stop="handleEdit(node.row as T)">
<el-icon><Edit /></el-icon>编辑
</el-button>
<el-button link type="danger" @dblclick.stop @click.stop="handleDelete(node.row as T)">
<el-button v-if="canEdit" link type="danger" @dblclick.stop @click.stop="handleDelete(node.row as T)">
<el-icon><Delete /></el-icon>删除
</el-button>
</div>
@@ -774,14 +783,14 @@ defineExpose({
</div>
</div>
<!-- 卡片操作:查看 / 编辑 / 删除 -->
<div class="crud-card-actions">
<div v-if="onView || canEdit" class="crud-card-actions">
<el-button v-if="onView" link type="primary" @dblclick.stop @click.stop="handleView(row as T)">
<el-icon><View /></el-icon>查看
</el-button>
<el-button link type="primary" @dblclick.stop @click.stop="handleEdit(row as T)">
<el-button v-if="canEdit" link type="primary" @dblclick.stop @click.stop="handleEdit(row as T)">
<el-icon><Edit /></el-icon>编辑
</el-button>
<el-button link type="danger" @dblclick.stop @click.stop="handleDelete(row as T)">
<el-button v-if="canEdit" link type="danger" @dblclick.stop @click.stop="handleDelete(row as T)">
<el-icon><Delete /></el-icon>删除
</el-button>
</div>
+37 -1
View File
@@ -17,15 +17,30 @@ export interface AdminCheckResult {
current_tenant_uuid?: string
has_current_company?: boolean
role_scope?: {
Type?: string
Type?: RoleScopeType
TypeLabel?: string
IsAdmin?: boolean
IsDeptManager?: boolean
IsWorkgroupLead?: boolean
IsMember?: boolean
/** 所属部门 UUID(多个用逗号拼接,负责人可挂多个) */
DeptUUID?: string | null
/** 所属工作组 UUID(多个用逗号拼接,组长可挂多个) */
WorkgroupUUID?: string | null
} | null
}
/** 角色作用域类型(对齐后端 CheckAdminView.role_scope.Type) */
export type RoleScopeType = 'admin' | 'dept_manager' | 'workgroup_lead' | 'member'
/** 作用域默认中文标签(后端 TypeLabel 缺失时回退) */
const ROLE_SCOPE_LABELS: Record<RoleScopeType, string> = {
admin: '管理员',
dept_manager: '部门负责人',
workgroup_lead: '工作组长',
member: '普通组员',
}
/** 用户状态管理:当前登录用户、角色、菜单、权限 */
export const useUserStore = defineStore('user', () => {
// === 状态 ===
@@ -80,6 +95,21 @@ export const useUserStore = defineStore('user', () => {
// 管理员判定优先用 CheckAdmin(后端按当前角色 admin:super/admin:tenant 权限码判定);
// 未加载 CheckAdmin 时回退 userInfo.IsAdmin(旧缓存数据)
const isAdmin = computed(() => adminCheck.value?.is_admin === true || userInfo.value?.IsAdmin === true)
// === 角色作用域(role_scope,CheckAdmin 下发) ===
// 原生 GERP 语义:admin 看全公司 / dept_manager 看本部门 / workgroup_lead 看本工作组 / member 只看自己
// 未加载完成时按 isAdmin 回退(刷新瞬间不闪烁,与后端计算逻辑一致)
const roleScopeType = computed<RoleScopeType>(() => {
const t = adminCheck.value?.role_scope?.Type
if (t === 'admin' || t === 'dept_manager' || t === 'workgroup_lead' || t === 'member') return t
return isAdmin.value ? 'admin' : 'member'
})
const roleScopeLabel = computed(() => adminCheck.value?.role_scope?.TypeLabel || ROLE_SCOPE_LABELS[roleScopeType.value])
const isScopeAdmin = computed(() => roleScopeType.value === 'admin')
const isDeptManager = computed(() => roleScopeType.value === 'dept_manager')
const isWorkgroupLead = computed(() => roleScopeType.value === 'workgroup_lead')
const isScopeMember = computed(() => roleScopeType.value === 'member')
const currentRole = computed(() => userInfo.value?.CurrentRole || '')
const avatar = computed(() => userInfo.value?.Avatar || '')
const displayName = computed(() => userInfo.value?.DisplayName || userInfo.value?.UserName || '未登录')
@@ -262,6 +292,12 @@ export const useUserStore = defineStore('user', () => {
// 计算属性
isLoggedIn,
isAdmin,
roleScopeType,
roleScopeLabel,
isScopeAdmin,
isDeptManager,
isWorkgroupLead,
isScopeMember,
currentRole,
avatar,
displayName,
+4
View File
@@ -346,5 +346,9 @@ export interface AdminCheckResult {
IsDeptManager?: boolean
IsWorkgroupLead?: boolean
IsMember?: boolean
/** 所属部门 UUID(多个用逗号拼接) */
DeptUUID?: string | null
/** 所属工作组 UUID(多个用逗号拼接) */
WorkgroupUUID?: string | null
} | null
}
+1 -1
View File
@@ -21,7 +21,7 @@
* - 列定义优先走后端 /API/FormAPI/Columns/Company(CrudTable 自动处理)
* 非主实例的 getDefaultColumns fallback 暂不实现(TODO)
* - AdminBadge 自动根据 server/admin_verify 显示特权横幅(对齐原生 adminBadge:true)
* - role_scope 细粒度判断暂用 userStore.isAdmin 二分(TODO 待 checkAdmin 接入)
* - 作用域隔离由后端表单服务按公司/租户强制,前端不做 role_scope 细粒度判定
* - onView 跳 Vue 路由 /company/company-detail?CompanyUUID=xxx(替代 GERP.Navigate)
*/
import { ref } from 'vue'
+1 -1
View File
@@ -23,7 +23,7 @@
* - 列定义优先走后端 /API/FormAPI/Columns/Department(CrudTable 自动处理)
* 非主实例的 getDefaultColumns fallback 暂不实现(TODO)
* - 无 adminBadge(原生未启用特权徽章)
* - role_scope 细粒度判断暂用 userStore.isAdmin 二分(TODO 待 checkAdmin 接入)
* - 作用域隔离由后端表单服务按公司/租户强制,前端不做 role_scope 细粒度判定
* - 树形展示暂未实现(TODO:待 vxe-table tree-config 接入)
* - onView 跳 Vue 路由 /company/department-detail?DeptUUID=xxx(替代 GERP.Navigate)
*/
+1 -1
View File
@@ -21,7 +21,7 @@
* - 列定义优先走后端 /API/FormAPI/Columns/Role(CrudTable 自动处理)
* 非主实例的 getDefaultColumns fallback 暂不实现(TODO)
* - AdminBadge 自动根据 server/admin_verify 显示特权横幅(对齐原生 adminBadge:true)
* - role_scope 细粒度判断暂用 userStore.isAdmin 二分(TODO 待 checkAdmin 接入)
* - 作用域隔离由后端表单服务按公司/租户强制,前端不做 role_scope 细粒度判定
* - onView 跳 Vue 路由 /company/role-detail?RoleUUID=xxx(替代 GERP.Navigate)
* - 注意:与 system/Roles.vue(system 模块角色管理)区分,两者迁移来源与路由不同
*/
+1 -1
View File
@@ -16,7 +16,7 @@
* - 列定义优先走后端 /API/FormAPI/Columns/Tenant(CrudTable 自动处理)
* 非主实例的 getDefaultColumns fallback 暂不实现(TODO)
* - AdminBadge 自动根据 server/admin_verify 显示特权横幅(对齐原生 adminBadge:true)
* - role_scope 细粒度判断暂用 userStore.isAdmin 二分(TODO 待 checkAdmin 接入)
* - 作用域隔离由后端表单服务按公司/租户强制,前端不做 role_scope 细粒度判定
* - onView 跳 Vue 路由 /company/tenant-detail?TenantUUID=xxx(替代 GERP.Navigate)
* - 页面标题随 server 变化由 setPageTitle 统一处理
*/
+1 -1
View File
@@ -21,7 +21,7 @@
* - 列定义优先走后端 /API/FormAPI/Columns/Workgroup(CrudTable 自动处理)
* 非主实例的 getDefaultColumns fallback 暂不实现(TODO)
* - 无 adminBadge(原生未启用特权徽章)
* - role_scope 细粒度判断暂用 userStore.isAdmin 二分(TODO 待 checkAdmin 接入)
* - 作用域隔离由后端表单服务按公司/租户强制,前端不做 role_scope 细粒度判定
* - onView 跳 Vue 路由 /company/workgroup-detail?WorkgroupUUID=xxx(替代 GERP.Navigate)
*/
import { ref } from 'vue'
+33 -14
View File
@@ -8,29 +8,47 @@
*
* Vue 版(阶段 1 简化):
* - server / admin_verify 由 request.ts 拦截器统一注入
* - 作用域判断暂用 userStore.isAdmin 二分(admin / normal)
* - 细粒度 role_scope(dept_manager / workgroup_lead)待 checkAdmin 接入(TODO)
* - 管理员开启行选择;普通组员只读(editable 待 CrudTable 暴露 editable prop 后接入,TODO)
* - role_scope 细粒度(admin / dept_manager / workgroup_lead / member)取自 CheckAdmin:
* 标题与提示文案四态;横幅色与图标两态(admin 蓝 / 其余灰)
* - 行选择与编辑仅特权模式开放(对齐原生 showCheckbox/editable = isAdminMode)
* - onView 跳 /inventory/product-detail?TemplateUUID=xxx
*/
import { computed } from 'vue'
import { computed, watch } from 'vue'
import { useRouter } from 'vue-router'
import CrudTable from '@/components/CrudTable.vue'
import AdminBadge from '@/components/AdminBadge.vue'
import { useUserStore } from '@/stores/user'
import { useUserStore, type RoleScopeType } from '@/stores/user'
import { setPageTitle } from '@/utils/serverScope'
const userStore = useUserStore()
const router = useRouter()
setPageTitle('产品管理')
// === role_scope 作用域(对齐原生 pages/inventory/products.html 的 applyScopeUI) ===
/** 页面标题:admin=产品管理 / dept_manager=部门产品 / workgroup_lead=工作组产品 / member=产品列表 */
const titleMap: Record<RoleScopeType, string> = {
admin: '产品管理',
dept_manager: '部门产品',
workgroup_lead: '工作组产品',
member: '产品列表',
}
/** 作用域提示文案 */
const hintMap: Record<RoleScopeType, string> = {
admin: '管理员视图:可查看和管理全公司产品模板,含跨公司特权模式。',
dept_manager: '部门负责人视图:可查看本部门可用产品模板。',
workgroup_lead: '工作组组长视图:可查看本工作组可用产品模板。',
member: '普通组员视图:可查看可用产品列表。',
}
const pageTitle = computed(() => titleMap[userStore.roleScopeType])
const scopeHint = computed(() => hintMap[userStore.roleScopeType])
// 横幅两态色与图标(对齐原生:scopeType === 'admin' 为蓝,其余为灰)
const bannerClass = computed(() => `scope-banner ${userStore.isScopeAdmin ? 'admin' : 'normal'}`)
const scopeIcon = computed(() => (userStore.isScopeAdmin ? 'fa-shield-alt' : 'fa-info-circle'))
// 行选择与编辑:仅特权模式开放(对齐原生 showCheckbox/editable = isAdminMode)
const isAdmin = computed(() => userStore.isAdmin)
const bannerClass = computed(() => `scope-banner ${isAdmin.value ? 'admin' : 'normal'}`)
const scopeHint = computed(() =>
isAdmin.value
? '管理员视图:可查看和管理全公司产品模板,含跨公司特权模式。'
: '普通组员视图:可查看可用产品列表。',
)
// 标题随作用域变化(setPageTitle 自动处理非主实例的 server 前缀)
watch(pageTitle, (t) => setPageTitle(t), { immediate: true })
interface TemplateRow {
TemplateUUID?: string
@@ -50,17 +68,18 @@ function handleView(row: TemplateRow): void {
<div class="product-page">
<AdminBadge />
<div :class="bannerClass">
<i class="fas" :class="isAdmin ? 'fa-shield-alt' : 'fa-info-circle'" />
<i class="fas" :class="scopeIcon" />
<span>{{ scopeHint }}</span>
</div>
<CrudTable
table="OrderTemplate"
title="产品管理"
:title="pageTitle"
pk-field="TemplateUUID"
searchable
pageable
stretch
:selectable="isAdmin"
:editable="isAdmin"
:on-view="handleView"
/>
</div>
+33 -14
View File
@@ -8,29 +8,47 @@
*
* Vue 版(阶段 1 简化):
* - server / admin_verify 由 request.ts 拦截器统一注入
* - 作用域判断暂用 userStore.isAdmin 二分
* - 行选择始终开启(对齐原生 showCheckbox: true)
* - editable 待 CrudTable 暴露 editable prop 后按 isAdmin 控制(TODO)
* - role_scope 细粒度(admin / dept_manager / workgroup_lead / member)取自 CheckAdmin:
* 标题与提示文案四态;横幅色与图标两态(admin 蓝 / 其余灰)
* - 行选择始终开启(对齐原生 showCheckbox: true);编辑仅特权模式开放(editable = isAdminMode)
* - onView 跳 /sales/order-detail?OrderUUID=xxx
*/
import { computed } from 'vue'
import { computed, watch } from 'vue'
import { useRouter } from 'vue-router'
import CrudTable from '@/components/CrudTable.vue'
import AdminBadge from '@/components/AdminBadge.vue'
import { useUserStore } from '@/stores/user'
import { useUserStore, type RoleScopeType } from '@/stores/user'
import { setPageTitle } from '@/utils/serverScope'
const userStore = useUserStore()
const router = useRouter()
setPageTitle('销售订单')
// === role_scope 作用域(对齐原生 pages/sales/orders.html 的 applyScopeUI) ===
/** 页面标题:admin=销售订单 / dept_manager=部门订单 / workgroup_lead=工作组订单 / member=我的订单 */
const titleMap: Record<RoleScopeType, string> = {
admin: '销售订单',
dept_manager: '部门订单',
workgroup_lead: '工作组订单',
member: '我的订单',
}
/** 作用域提示文案 */
const hintMap: Record<RoleScopeType, string> = {
admin: '管理员视图:可查看全公司订单,含跨公司特权模式。',
dept_manager: '部门负责人视图:仅可查看本部门相关订单。',
workgroup_lead: '工作组组长视图:仅可查看本工作组相关订单。',
member: '普通组员视图:仅可查看自己的订单。',
}
const pageTitle = computed(() => titleMap[userStore.roleScopeType])
const scopeHint = computed(() => hintMap[userStore.roleScopeType])
// 横幅两态色与图标(对齐原生:scopeType === 'admin' 为蓝,其余为灰)
const bannerClass = computed(() => `scope-banner ${userStore.isScopeAdmin ? 'admin' : 'normal'}`)
const scopeIcon = computed(() => (userStore.isScopeAdmin ? 'fa-shield-alt' : 'fa-info-circle'))
// 编辑权限:仅特权模式开放(对齐原生 editable = isAdminMode;行选择恒开)
const isAdmin = computed(() => userStore.isAdmin)
const bannerClass = computed(() => `scope-banner ${isAdmin.value ? 'admin' : 'normal'}`)
const scopeHint = computed(() =>
isAdmin.value
? '管理员视图:可查看全公司订单,含跨公司特权模式。'
: '普通组员视图:仅可查看自己的订单。',
)
// 标题随作用域变化(setPageTitle 自动处理非主实例的 server 前缀)
watch(pageTitle, (t) => setPageTitle(t), { immediate: true })
interface OrderRow {
OrderUUID?: string
@@ -50,17 +68,18 @@ function handleView(row: OrderRow): void {
<div class="order-page">
<AdminBadge />
<div :class="bannerClass">
<i class="fas" :class="isAdmin ? 'fa-shield-alt' : 'fa-info-circle'" />
<i class="fas" :class="scopeIcon" />
<span>{{ scopeHint }}</span>
</div>
<CrudTable
table="Order"
title="销售订单"
:title="pageTitle"
pk-field="OrderUUID"
searchable
pageable
stretch
selectable
:editable="isAdmin"
:on-view="handleView"
/>
</div>
+2 -3
View File
@@ -16,7 +16,7 @@
* - server / admin_verify 由 request.ts 拦截器统一注入
* - 主表单条加载:formApi.list('Role', { RoleUUID, pageSize: 1 })
* - 权限树分配:跳 /system/role-permissions?RoleUUID=xxx(RolePermissions 页用 el-tree 实现)
* - role_scope 细粒度判断暂用 userStore.isAdmin 二分(TODO)
* - 隔离提示由特权模式(admin_verify)决定,与原生 isAdminMode 一致
*/
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
@@ -54,8 +54,7 @@ const PK_FIELD = 'RoleUUID'
// 页面标题
const title = computed(() => data.value?.RoleName || '角色详情')
// 隔离提示
// TODO: 接入 checkAdmin → role_scope
// 隔离提示(特权模式显示全公司记录,否则仅当前公司)
const isolationHint = computed(() =>
userStore.isAdmin
? '特权模式:显示该角色在所有公司的关联记录(无公司隔离)'
+3 -4
View File
@@ -13,8 +13,8 @@
* 迁移要点(对齐工程迁移规则):
* - server / admin_verify 由 request.ts 拦截器统一注入
* - 权限树用 el-tree 实现(TODO:待 PermissionTreePicker 组件封装后替换)
* - role_scope 细粒度判断暂用 userStore.isAdmin 二分(TODO)
* - 仅管理员可访问:非管理员显示拒绝页
* - 仅管理员可访问(原生语义如此,不涉及 role_scope 细粒度分支)
* - 非管理员显示拒绝页
* - 离开页面前提示未保存(beforeunload + 路由守卫)
*
* 接口:
@@ -75,8 +75,7 @@ interface PermGroup {
Permissions: PermItem[]
}
// 仅管理员可访问
// TODO: 接入 checkAdmin → role_scope,细化 dept_manager / workgroup_lead 分支
// 仅管理员可访问(原生语义一致)
const isAdmin = computed(() => userStore.isAdmin)
// el-tree 数据:组 → 权限,统一 _nodeKey 作为 node-key
+1 -1
View File
@@ -20,7 +20,7 @@
* - 主表单条加载:formApi.list('User', { UserUUID, pageSize: 1 }) 取首条
* - 子表 CrudTable 通过 query 透传父级 UUID,无需手动建表
* - UUID 解析使用 useResolveUUIDs composable(本页主表暂无 UUID 字段需解析)
* - role_scope 细粒度判断暂用 userStore.isAdmin 二分(TODO:接入 checkAdmin 后补 dept_manager / workgroup_lead)
* - 隔离提示由特权模式(admin_verify)决定,与原生 isAdminMode 一致
* - 2FA 接口:get('/API/SiteUserManagement/GetUser2FAInfo', { UserUUID })(TODO:契约确认)
*/
import { ref, reactive, computed, onMounted, watch } from 'vue'
+52 -27
View File
@@ -8,45 +8,55 @@
*
* Vue 版(阶段 1 简化):
* - server / admin_verify 由 request.ts 拦截器统一注入
* - 作用域判断暂用 userStore.isAdmin 二分(管理员 / 普通组员)
* - 细粒度 role_scope(dept_manager / workgroup_lead)待 checkAdmin 接口确认后接入(TODO)
* - role_scope 细粒度(admin / dept_manager / workgroup_lead / member)取自 CheckAdmin,
* 按作用域差异化标题、横幅色、图标、提示文案;仅 member 关闭行选择与编辑
* - 列 fallback 暂未实现,主实例走后端 Columns 接口(TODO:非主实例 getDefaultColumns)
* - AdminBadge 自动根据 server/admin_verify 显示特权横幅
* - onView 跳 /system/user-detail?UserUUID=xxx
*/
import { computed } from 'vue'
import { computed, watch } from 'vue'
import { useRouter } from 'vue-router'
import CrudTable from '@/components/CrudTable.vue'
import AdminBadge from '@/components/AdminBadge.vue'
import { useUserStore } from '@/stores/user'
import { useUserStore, type RoleScopeType } from '@/stores/user'
import { setPageTitle } from '@/utils/serverScope'
const userStore = useUserStore()
const router = useRouter()
setPageTitle('用户管理')
// 阶段 1 简化:仅区分管理员 / 普通组员
// TODO: 接入 checkAdmin → role_scope,补 dept_manager / workgroup_lead 分支
const scopeType = computed<'admin' | 'member'>(() =>
userStore.isAdmin ? 'admin' : 'member',
)
// === role_scope 作用域(对齐原生 pages/system/users.html 的 applyScopeUI) ===
/** 页面标题:admin=用户管理 / dept_manager=部门成员 / workgroup_lead=工作组成员 / member=我的资料 */
const titleMap: Record<RoleScopeType, string> = {
admin: '用户管理',
dept_manager: '部门成员',
workgroup_lead: '工作组成员',
member: '我的资料',
}
/** 横幅图标 */
const iconMap: Record<RoleScopeType, string> = {
admin: 'fa-shield-alt',
dept_manager: 'fa-sitemap',
workgroup_lead: 'fa-users-cog',
member: 'fa-user',
}
/** 作用域提示文案 */
const hintMap: Record<RoleScopeType, string> = {
admin: '管理员视图:可查看和管理全公司用户,含跨公司特权模式。',
dept_manager: '部门负责人视图:仅可查看和管理本部门成员,新增/编辑/删除受部门隔离约束。',
workgroup_lead: '工作组组长视图:仅可查看和管理本工作组成员,新增/编辑/删除受工作组隔离约束。',
member: '普通组员视图:仅可查看和编辑自己的资料,无法查看他人数据。',
}
// 作用域提示文本(对齐原生 hintMap)
const scopeHint = computed(() => {
switch (scopeType.value) {
case 'admin':
return '管理员视图:可查看和管理全公司用户,含跨公司特权模式。'
case 'member':
default:
return '普通组员视图:仅可查看和编辑自己的资料,无法查看他人数据。'
}
})
const pageTitle = computed(() => titleMap[userStore.roleScopeType])
const scopeIcon = computed(() => iconMap[userStore.roleScopeType])
const scopeHint = computed(() => hintMap[userStore.roleScopeType])
// 作用域横幅样式类(对齐原生 user-scope-banner 四态色)
const scopeBannerClass = computed(() => `user-scope-banner ${userStore.roleScopeType}`)
// 普通组员:只读(对齐原生 showCheckbox/editable = !IsMember)
const isMember = computed(() => userStore.isScopeMember)
// 作用域横幅样式类(对齐原生 user-scope-banner 样式分支)
const scopeBannerClass = computed(() => `user-scope-banner ${scopeType.value}`)
// 普通组员:禁用行选择与编辑
const isMember = computed(() => scopeType.value === 'member')
// 标题随作用域变化(setPageTitle 自动处理非主实例的 server 前缀)
watch(pageTitle, (t) => setPageTitle(t), { immediate: true })
// 用户记录类型
interface UserRow {
@@ -71,18 +81,19 @@ function handleView(row: UserRow): void {
<!-- 作用域提示横幅 -->
<div :class="scopeBannerClass">
<i class="fas" :class="isMember ? 'fa-user' : 'fa-shield-alt'" />
<i class="fas" :class="scopeIcon" />
<span>{{ scopeHint }}</span>
</div>
<CrudTable
table="User"
title="用户管理"
:title="pageTitle"
pk-field="UserUUID"
searchable
pageable
stretch
:selectable="!isMember"
:editable="!isMember"
:on-view="handleView"
/>
</div>
@@ -119,6 +130,20 @@ function handleView(row: UserRow): void {
border: 1px solid rgba(30, 64, 175, 0.2);
}
// 部门负责人:绿色
&.dept_manager {
background: rgba(16, 185, 129, 0.08);
color: #047857;
border: 1px solid rgba(16, 185, 129, 0.2);
}
// 工作组组长:琥珀色
&.workgroup_lead {
background: rgba(245, 158, 11, 0.08);
color: #b45309;
border: 1px solid rgba(245, 158, 11, 0.2);
}
// 普通组员:灰色
&.member {
background: rgba(100, 116, 139, 0.08);