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:
1 parent
3d4c46b7ee
commit
9d1d6dca93
14 files changed
+191
-81
No files matched your search
@@ -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
@@ -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,
|
||||
|
||||
@@ -346,5 +346,9 @@ export interface AdminCheckResult {
|
||||
IsDeptManager?: boolean
|
||||
IsWorkgroupLead?: boolean
|
||||
IsMember?: boolean
|
||||
/** 所属部门 UUID(多个用逗号拼接) */
|
||||
DeptUUID?: string | null
|
||||
/** 所属工作组 UUID(多个用逗号拼接) */
|
||||
WorkgroupUUID?: string | null
|
||||
} | null
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
|
||||
@@ -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 模块角色管理)区分,两者迁移来源与路由不同
|
||||
*/
|
||||
|
||||
@@ -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 统一处理
|
||||
*/
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
|
||||
? '特权模式:显示该角色在所有公司的关联记录(无公司隔离)'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user