From 192c6f1d2612997f18bfdd4e172bfe368cf02776 Mon Sep 17 00:00:00 2001 From: sexygoat <1538832180@qq.com> Date: Tue, 3 Feb 2026 17:20:50 +0800 Subject: [PATCH] =?UTF-8?q?fetch(modify):=E5=AE=8C=E5=96=84=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../add-button-permissions/proposal.md | 162 +++++++++++++++ .../changes/add-button-permissions/tasks.md | 190 ++++++++++++++++++ .../account-management/account/index.vue | 49 +++-- .../enterprise-customer/index.vue | 70 ++++--- .../platform-account/index.vue | 62 ++++-- .../account-management/shop-account/index.vue | 36 +++- .../authorization-records/index.vue | 23 ++- .../asset-management/device-list/index.vue | 106 ++++++---- .../asset-management/device-task/index.vue | 9 +- .../iot-card-management/index.vue | 67 ++++-- .../asset-management/iot-card-task/index.vue | 9 +- .../finance/carrier-management/index.vue | 36 +++- .../commission/agent-commission/index.vue | 17 +- .../commission/my-commission/index.vue | 5 +- .../commission/withdrawal-settings/index.vue | 5 +- .../order-management/order-list/index.vue | 4 +- .../package-assign/index.vue | 50 +++-- .../package-management/package-list/index.vue | 38 +++- .../package-series/index.vue | 37 +++- .../series-assign/index.vue | 37 +++- src/views/product/shop/index.vue | 69 +++++-- src/views/system/permission/index.vue | 36 +++- 22 files changed, 885 insertions(+), 232 deletions(-) create mode 100644 openspec/changes/add-button-permissions/proposal.md create mode 100644 openspec/changes/add-button-permissions/tasks.md diff --git a/openspec/changes/add-button-permissions/proposal.md b/openspec/changes/add-button-permissions/proposal.md new file mode 100644 index 0000000..0d4fbab --- /dev/null +++ b/openspec/changes/add-button-permissions/proposal.md @@ -0,0 +1,162 @@ +# Proposal: Add Button Permissions + +## Summary + +为系统中的所有页面添加按钮级权限控制,根据用户权限动态显示或隐藏按钮和操作。权限系统已实现(`hasAuth` 和 `v-permission` 指令),本提案专注于将权限控制应用到所有相关页面。 + +## Motivation + +当前系统已经实现了基于角色的权限控制(RBAC),但大多数页面的按钮和操作缺少权限控制。这意味着所有用户都能看到所有按钮,即使他们没有权限执行相应操作。需要在UI层面根据用户权限隐藏无权限的按钮,提升用户体验和系统安全性。 + +### Problems Solved + +1. **安全性提升**: 防止用户看到或尝试执行无权限操作 +2. **用户体验优化**: 只显示用户有权限的功能,避免混淆 +3. **权限一致性**: 统一所有页面的权限控制实现方式 + +### Related Work + +- 权限系统基础设施已实现 (`useAuth.ts`, `permission.ts`) +- 系统/角色页面 (`/system/role`) 已实现权限控制,作为最佳实践参考 +- 权限配置已由后端API提供(见文档中的权限JSON结构) + +## Proposed Solution + +### Approach + +按业务模块逐步为所有页面添加按钮权限控制,使用已有的权限基础设施: + +1. **表格操作按钮**: 使用 `v-permission` 指令 +2. **表格内操作**: 使用 `hasAuth()` 函数动态渲染 +3. **状态切换控件**: 使用 `disabled: !hasAuth()` 控制禁用状态 + +### Affected Modules + +根据提供的权限JSON,需要添加权限控制的模块包括: + +1. **套餐管理** (`/package-management`) + - 套餐系列 (`package-series`) + - 套餐列表 (`package-list`) + - 单套餐分配 (`package-assign`) + - 套餐系列分配 (`series-assign`) + +2. **店铺管理** (`/shop-management`) + - 店铺列表 (`list`) + +3. **账号管理** (`/account-management`) + - 账号列表 (`account`) + - 平台账号 (`platform-account`) + - 代理账号 (`shop-account`) + +4. **资产管理** (`/asset-management`) + - IoT卡管理 (`iot-card-management`) + - IoT卡任务 (`iot-card-task`) + - 设备任务 (`device-task`) + - 设备管理 (`devices`) + - 授权记录 (`authorization-records`) + +5. **账户管理** (`/account`) + - 企业客户 (`enterprise-customer`) + - 运营商管理 (`carrier-management`) + - 订单管理 (`orders`) + - 佣金管理 (`commission/*`) + +### Implementation Pattern + +基于 `/system/role` 页面的实现,统一使用以下模式: + +```vue + + + +``` + +### Permission Mapping + +权限代码 (`perm_code`) 与操作的映射关系: + +- `module:add` -> 新增按钮 +- `module:edit` -> 编辑按钮 +- `module:delete` -> 删除按钮 +- `module:update_status` -> 状态切换 +- `module:update_xxx` -> 特定更新操作(如修改密码、修改成本价等) +- `module:xxx` -> 特殊操作(如分配权限、查看客户、卡授权等) + +## Impact Analysis + +### Benefits + +1. **提升安全性**: UI层面防止无权限操作 +2. **改善UX**: 用户只看到有权限的功能 +3. **统一体验**: 所有页面使用相同的权限控制模式 + +### Risks + +1. **向后兼容性**: 现有页面需要更新,可能影响用户使用习惯 +2. **测试覆盖**: 需要测试各种权限组合的显示效果 + +### Mitigations + +1. 分模块逐步rollout,优先完成核心模块 +2. 权限配置由后端控制,前端只负责显示/隐藏 +3. 保持API层面的权限检查,前端权限控制仅为UI优化 + +## Success Criteria + +- [ ] 所有模块的按钮根据权限正确显示/隐藏 +- [ ] 状态切换控件根据权限正确启用/禁用 +- [ ] 表格操作列根据权限动态渲染按钮 +- [ ] 权限变更后UI立即响应 +- [ ] 无权限用户看不到任何无权限操作 + +## Dependencies + +- 依赖后端 API 返回正确的权限数据 +- 依赖 `useUserStore` 中的权限数据管理 +- 依赖已实现的 `useAuth` 和 `v-permission` 指令 + +## Timeline + +预计需要 5-7 个工作日完成所有模块的权限控制集成。 + +## References + +- 权限文档: `docs/在页面上新增按钮权限.md` +- 参考实现: `src/views/system/role/index.vue` +- 权限指令: `src/directives/permission.ts` +- 权限组合式函数: `src/composables/useAuth.ts` diff --git a/openspec/changes/add-button-permissions/tasks.md b/openspec/changes/add-button-permissions/tasks.md new file mode 100644 index 0000000..c6ad50d --- /dev/null +++ b/openspec/changes/add-button-permissions/tasks.md @@ -0,0 +1,190 @@ +# Tasks: Add Button Permissions + +## Overview + +将按钮权限控制系统地应用到所有相关页面。每个任务包含引入 `useAuth`、添加 `v-permission` 指令,以及在表格操作列中使用 `hasAuth()` 进行权限判断。 + +## Tasks + +### 1. 套餐管理模块 (Package Management) ✅ + +- [x] **套餐系列页面** (`/package-management/package-series`) + - ✅ 引入 `useAuth` composable + - ✅ 为"新增套餐系列"按钮添加 `v-permission="'package_series:add'"` + - ✅ 为状态切换添加 `disabled: !hasAuth('package_series:update_status')` + - ✅ 在操作列中使用 `hasAuth()` 判断编辑权限 (`package_series:edit`)和删除权限 (`package_series:delete`) + +- [x] **套餐列表页面** (`/package-management/package-list`) + - ✅ 引入 `useAuth` composable + - ✅ 为"新增套餐"按钮添加 `v-permission="'package:add'"` + - ✅ 为上/下架切换添加 `disabled: !hasAuth('package:update_away')` + - ✅ 为状态切换添加 `disabled: !hasAuth('package:update_status')` + - ✅ 在操作列中使用 `hasAuth()` 判断编辑 (`package:edit`) 和删除 (`package:delete`) 权限 + +- [x] **单套餐分配页面** (`/package-management/package-assign`) + - ✅ 引入 `useAuth` composable + - ✅ 为"新增分配"按钮添加 `v-permission="'package_assign:add'"` + - ✅ 为状态切换添加 `disabled: !hasAuth('package_assign:update_status')` + - ✅ 在操作列中使用 `hasAuth()` 判断以下权限: + - 修改成本价: `package_assign:update_cost` + - 编辑: `package_assign:edit` + - 删除: `package_assign:delete` + +- [x] **套餐系列分配页面** (`/package-management/series-assign`) + - ✅ 引入 `useAuth` composable + - ✅ 为"新增系列分配"按钮添加 `v-permission="'series_assign:add'"` + - ✅ 为状态切换添加 `disabled: !hasAuth('series_assign:update_status')` + - ✅ 在操作列中使用 `hasAuth()` 判断编辑 (`series_assign:edit`) 和删除 (`series_assign:delete`) 权限 + +### 2. 店铺管理模块 (Shop Management) ✅ + +- [x] **店铺列表页面** (`/product/shop`) + - ✅ 引入 `useAuth` composable + - ✅ 为"新增店铺"按钮添加 `v-permission="'shop:add'"` + - ✅ 在操作列中使用 `hasAuth()` 判断以下权限: + - 查看客户: `shop:look_customer` + - 编辑: `shop:edit` + - 删除: `shop:delete` + - ✅ 在右键菜单中使用 `hasAuth()` 动态生成菜单项 + +### 3. 账号管理模块 (Account Management) ✅ + +- [x] **账号列表页面** (`/account-management/account`) + - ✅ 引入 `useAuth` composable + - ✅ 为"新增账号"按钮添加 `v-permission="'account:add'"` + - ✅ 在操作列中使用 `hasAuth()` 判断以下权限: + - 分配角色: `account:patch_role` + - 编辑: `account:edit` + - 删除: `account:delete` + +- [x] **平台账号页面** (`/account-management/platform-account`) + - ✅ 引入 `useAuth` composable + - ✅ 为"新增平台账号"按钮添加 `v-permission="'platform_account:add'"` + - ✅ 在操作列中使用 `hasAuth()` 判断以下权限: + - 分配角色: `platform_account:patch_role` + - 修改密码: `platform_account:update_psd` + - 编辑: `platform_account:edit` + - 删除: `platform_account:delete` + +- [x] **代理账号页面** (`/account-management/shop-account`) + - ✅ 引入 `useAuth` composable + - ✅ 为"新增代理账号"按钮添加 `v-permission="'shop_account:add'"` + - ✅ 在操作列中使用 `hasAuth()` 判断以下权限: + - 修改密码: `shop_account:update_psd` + - 编辑: `shop_account:edit` + +### 4. 资产管理模块 (Asset Management) ✅ + +- [x] **IoT卡管理页面** (`/asset-management/iot-card-management`) + - ✅ 引入 `useAuth` composable + - ✅ 为表格头部操作按钮添加权限控制: + - 批量分配: `v-permission="'iot_card:batch_allocation'"` + - 批量回收: `v-permission="'iot_card:batch_recycle'"` + - 批量设置套餐系列: `v-permission="'iot_card:batch_setting'"` + - 批量充值: `v-permission="'iot_card:batch_recharge'"` + - ✅ 在操作列中使用 `hasAuth()` 判断以下权限: + - 网卡分销: `iot_card:network_distribution` + - 网卡回收: `iot_card:network_recycle` + - 变更套餐: `iot_card:change_package` + - ✅ 验证权限控制正确工作 + +- [x] **IoT卡任务页面** (`/asset-management/iot-card-task`) + - ✅ 引入 `useAuth` composable + - ✅ 为"批量导入IoT卡"按钮添加 `v-permission="'lot_task:bulk_import'"` + - ✅ 验证权限控制正确工作 + +- [x] **设备任务页面** (`/asset-management/device-task`) + - ✅ 引入 `useAuth` composable + - ✅ 为"批量导入设备"按钮添加 `v-permission="'device_task:bulk_import'"` + - ✅ 验证权限控制正确工作 + +- [x] **设备管理页面** (`/asset-management/device-list`) + - ✅ 引入 `useAuth` composable + - ✅ 为表格头部操作按钮添加权限控制: + - 批量分配: `v-permission="'devices:batch_allocation'"` + - 批量回收: `v-permission="'devices:batch_recycle'"` + - 批量设置套餐系列: `v-permission="'devices:batch_setting'"` + - ✅ 在操作列中使用 `hasAuth()` 判断以下权限: + - 查看绑定的卡: `devices:look_binding` + - 删除设备: `devices:delete` + - ✅ 验证权限控制正确工作 + +- [x] **授权记录页面** (`/asset-management/authorization-records`) + - ✅ 引入 `useAuth` composable + - ✅ 在操作列中使用 `hasAuth()` 判断修改备注权限 (`authorization_records:update_remark`) + - ✅ 验证权限控制正确工作 + +### 5. 账户管理模块 (Account Module) ✅ + +- [x] **企业客户页面** (`/account/enterprise-customer`) + - ✅ 引入 `useAuth` composable + - ✅ 为"新增企业客户"按钮添加 `v-permission="'enterprise_customer:add'"` + - ✅ 在操作列中使用 `hasAuth()` 判断以下权限: + - 编辑: `enterprise_customer:edit` + - 查看客户: `enterprise_customer:look_customer` + - 卡授权: `enterprise_customer:card_authorization` + - 修改密码: `enterprise_customer:update_pwd` + - ✅ 验证权限控制正确工作 + +- [x] **运营商管理页面** (`/account/carrier-management`) + - ✅ 引入 `useAuth` composable + - ✅ 为"新增运营商"按钮添加 `v-permission="'carrier:add'"` + - ✅ 为状态切换添加 `disabled: !hasAuth('carrier:update_status')` + - ✅ 在操作列中使用 `hasAuth()` 判断编辑 (`carrier:edit`) 和删除 (`carrier:delete`) 权限 + - ✅ 验证权限控制正确工作 + +- [x] **订单管理页面** (`/account/orders`) + - ✅ 引入 `useAuth` composable + - ✅ 为"创建订单"按钮添加 `v-permission="'orders:add'"` + - ✅ 验证权限控制正确工作 + +- [x] **提现配置页面** (`/account/commission/withdrawal-settings`) + - ✅ 引入 `useAuth` composable + - ✅ 为"新增配置"按钮添加 `v-permission="'withdrawal_settings:add'"` + - ✅ 验证权限控制正确工作 + +- [x] **我的佣金页面** (`/account/commission/my-commission`) + - ✅ 引入 `useAuth` composable + - ✅ 为"发起提现"按钮添加 `v-permission="'my_commission:add'"` + - ✅ 验证权限控制正确工作 + +- [x] **代理商佣金管理页面** (`/account/commission/agent-commission`) + - ✅ 引入 `useAuth` composable + - ✅ 在操作列中使用 `hasAuth()` 判断查看详情权限 (`agent_commission:detail`) + - ✅ 验证权限控制正确工作 + +### 6. 系统管理模块 (System Management) ✅ + +- [x] **权限管理页面** (`/system/permission`) + - ✅ 引入 `useAuth` composable + - ✅ 为"新增权限"按钮添加 `v-permission="'permission:add'"` + - ✅ 在操作列中使用 `hasAuth()` 判断编辑 (`permission:edit`) 和删除 (`permission:delete`) 权限 + - ✅ 验证权限控制正确工作 + +### 7. 测试与文档 + +- [ ] **权限控制测试** + - 创建不同权限组合的测试用户 + - 验证每个页面的按钮显示/隐藏符合预期 + - 验证状态切换的启用/禁用符合预期 + - 测试权限动态变更后UI的响应 + +- [ ] **文档更新** + - 更新开发文档,说明权限控制的实现方式 + - 添加权限代码与功能的映射表 + - 提供新页面添加权限控制的指南 + +## Validation + +每个任务完成后需验证: + +1. **功能正确性**: 有权限时按钮/操作可见且可用 +2. **权限隔离**: 无权限时按钮/操作不可见或禁用 +3. **用户体验**: 权限控制不影响正常操作流程 +4. **性能**: 权限检查不影响页面渲染性能 + +## Dependencies + +- 所有任务依赖权限基础设施 (`useAuth`, `v-permission`) +- 任务可并行执行,建议按模块分批进行 +- 优先级: 核心模块 (账号、套餐、资产) > 其他模块 diff --git a/src/views/account-management/account/index.vue b/src/views/account-management/account/index.vue index fcf4dc7..d746079 100644 --- a/src/views/account-management/account/index.vue +++ b/src/views/account-management/account/index.vue @@ -17,7 +17,7 @@ @refresh="handleRefresh" > @@ -117,6 +117,7 @@ import { ElMessageBox, ElMessage } from 'element-plus' import type { FormRules } from 'element-plus' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import { AccountService } from '@/api/modules/account' import { RoleService } from '@/api/modules/role' @@ -128,6 +129,8 @@ defineOptions({ name: 'Account' }) // 定义组件名称,用于 KeepAlive 缓存控制 + const { hasAuth } = useAuth() + const dialogType = ref('add') const dialogVisible = ref(false) const roleDialogVisible = ref(false) @@ -375,20 +378,36 @@ width: 200, fixed: 'right', formatter: (row: any) => { - return h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ArtButtonTable, { - icon: '', - onClick: () => showRoleDialog(row) - }), - h(ArtButtonTable, { - type: 'edit', - onClick: () => showDialog('edit', row) - }), - h(ArtButtonTable, { - type: 'delete', - onClick: () => deleteAccount(row) - }) - ]) + const buttons = [] + + if (hasAuth('account:patch_role')) { + buttons.push( + h(ArtButtonTable, { + icon: '', + onClick: () => showRoleDialog(row) + }) + ) + } + + if (hasAuth('account:edit')) { + buttons.push( + h(ArtButtonTable, { + type: 'edit', + onClick: () => showDialog('edit', row) + }) + ) + } + + if (hasAuth('account:delete')) { + buttons.push( + h(ArtButtonTable, { + type: 'delete', + onClick: () => deleteAccount(row) + }) + ) + } + + return h('div', { style: 'display: flex; gap: 8px;' }, buttons) } } ]) diff --git a/src/views/account-management/enterprise-customer/index.vue b/src/views/account-management/enterprise-customer/index.vue index 19e2bfa..5abeb8e 100644 --- a/src/views/account-management/enterprise-customer/index.vue +++ b/src/views/account-management/enterprise-customer/index.vue @@ -19,7 +19,7 @@ @refresh="handleRefresh" > @@ -213,6 +213,7 @@ import type { EnterpriseItem, ShopResponse } from '@/types/api' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import CustomerAccountDialog from '@/components/business/CustomerAccountDialog.vue' import { formatDateTime } from '@/utils/business/format' @@ -220,6 +221,8 @@ defineOptions({ name: 'EnterpriseCustomer' }) + const { hasAuth } = useAuth() + const router = useRouter() const dialogVisible = ref(false) @@ -450,28 +453,49 @@ width: 340, fixed: 'right', formatter: (row: EnterpriseItem) => { - return h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ArtButtonTable, { - text: '编辑', - iconClass: BgColorEnum.SECONDARY, - onClick: () => showDialog('edit', row) - }), - h(ArtButtonTable, { - text: '查看客户', - iconClass: BgColorEnum.PRIMARY, - onClick: () => viewCustomerAccounts(row) - }), - h(ArtButtonTable, { - text: '卡授权', - iconClass: BgColorEnum.PRIMARY, - onClick: () => manageCards(row) - }), - h(ArtButtonTable, { - text: '修改密码', - iconClass: BgColorEnum.WARNING, - onClick: () => showPasswordDialog(row) - }) - ]) + const buttons = [] + + if (hasAuth('enterprise_customer:edit')) { + buttons.push( + h(ArtButtonTable, { + text: '编辑', + iconClass: BgColorEnum.SECONDARY, + onClick: () => showDialog('edit', row) + }) + ) + } + + if (hasAuth('enterprise_customer:look_customer')) { + buttons.push( + h(ArtButtonTable, { + text: '查看客户', + iconClass: BgColorEnum.PRIMARY, + onClick: () => viewCustomerAccounts(row) + }) + ) + } + + if (hasAuth('enterprise_customer:card_authorization')) { + buttons.push( + h(ArtButtonTable, { + text: '卡授权', + iconClass: BgColorEnum.PRIMARY, + onClick: () => manageCards(row) + }) + ) + } + + if (hasAuth('enterprise_customer:update_pwd')) { + buttons.push( + h(ArtButtonTable, { + text: '修改密码', + iconClass: BgColorEnum.WARNING, + onClick: () => showPasswordDialog(row) + }) + ) + } + + return h('div', { style: 'display: flex; gap: 8px;' }, buttons) } } ]) diff --git a/src/views/account-management/platform-account/index.vue b/src/views/account-management/platform-account/index.vue index 03a6c36..a160f11 100644 --- a/src/views/account-management/platform-account/index.vue +++ b/src/views/account-management/platform-account/index.vue @@ -18,7 +18,7 @@ @refresh="handleRefresh" > @@ -164,6 +164,7 @@ import { FormInstance, ElMessage, ElMessageBox, ElTag, ElSwitch } from 'element-plus' import type { FormRules } from 'element-plus' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import { AccountService, RoleService, ShopService } from '@/api/modules' import type { SearchFormItem } from '@/types' @@ -173,6 +174,8 @@ defineOptions({ name: 'PlatformAccount' }) // 定义组件名称,用于 KeepAlive 缓存控制 + const { hasAuth } = useAuth() + const dialogType = ref('add') const dialogVisible = ref(false) const roleDialogVisible = ref(false) @@ -443,24 +446,45 @@ width: 240, fixed: 'right', formatter: (row: PlatformAccount) => { - return h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ArtButtonTable, { - icon: '', - onClick: () => showRoleDialog(row) - }), - h(ArtButtonTable, { - icon: '', - onClick: () => showPasswordDialog(row) - }), - h(ArtButtonTable, { - type: 'edit', - onClick: () => showDialog('edit', row) - }), - h(ArtButtonTable, { - type: 'delete', - onClick: () => deleteAccount(row) - }) - ]) + const buttons = [] + + if (hasAuth('platform_account:patch_role')) { + buttons.push( + h(ArtButtonTable, { + icon: '', + onClick: () => showRoleDialog(row) + }) + ) + } + + if (hasAuth('platform_account:update_psd')) { + buttons.push( + h(ArtButtonTable, { + icon: '', + onClick: () => showPasswordDialog(row) + }) + ) + } + + if (hasAuth('platform_account:edit')) { + buttons.push( + h(ArtButtonTable, { + type: 'edit', + onClick: () => showDialog('edit', row) + }) + ) + } + + if (hasAuth('platform_account:delete')) { + buttons.push( + h(ArtButtonTable, { + type: 'delete', + onClick: () => deleteAccount(row) + }) + ) + } + + return h('div', { style: 'display: flex; gap: 8px;' }, buttons) } } ]) diff --git a/src/views/account-management/shop-account/index.vue b/src/views/account-management/shop-account/index.vue index e20db7d..1d245b2 100644 --- a/src/views/account-management/shop-account/index.vue +++ b/src/views/account-management/shop-account/index.vue @@ -17,7 +17,7 @@ @refresh="handleRefresh" > @@ -125,6 +125,7 @@ import { FormInstance, ElMessage, ElMessageBox, ElSwitch, ElSelect, ElOption } from 'element-plus' import type { FormRules } from 'element-plus' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import { AccountService, ShopService } from '@/api/modules' import type { SearchFormItem } from '@/types' @@ -134,6 +135,8 @@ defineOptions({ name: 'ShopAccount' }) // 定义组件名称,用于 KeepAlive 缓存控制 + const { hasAuth } = useAuth() + const dialogType = ref('add') const dialogVisible = ref(false) const passwordDialogVisible = ref(false) @@ -342,16 +345,27 @@ width: 120, fixed: 'right', formatter: (row: PlatformAccount) => { - return h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ArtButtonTable, { - icon: '', - onClick: () => showPasswordDialog(row) - }), - h(ArtButtonTable, { - type: 'edit', - onClick: () => showDialog('edit', row) - }) - ]) + const buttons = [] + + if (hasAuth('shop_account:update_psd')) { + buttons.push( + h(ArtButtonTable, { + icon: '', + onClick: () => showPasswordDialog(row) + }) + ) + } + + if (hasAuth('shop_account:edit')) { + buttons.push( + h(ArtButtonTable, { + type: 'edit', + onClick: () => showDialog('edit', row) + }) + ) + } + + return h('div', { style: 'display: flex; gap: 8px;' }, buttons) } } ]) diff --git a/src/views/asset-management/authorization-records/index.vue b/src/views/asset-management/authorization-records/index.vue index 8cc7fac..5c822f2 100644 --- a/src/views/asset-management/authorization-records/index.vue +++ b/src/views/asset-management/authorization-records/index.vue @@ -102,6 +102,7 @@ import type { FormInstance, FormRules } from 'element-plus' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import { formatDateTime } from '@/utils/business/format' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import type { @@ -113,6 +114,7 @@ defineOptions({ name: 'AuthorizationRecords' }) + const { hasAuth } = useAuth() const router = useRouter() const loading = ref(false) const detailDialogVisible = ref(false) @@ -297,16 +299,25 @@ width: 150, fixed: 'right', formatter: (row: AuthorizationItem) => { - return h('div', { style: 'display: flex; gap: 8px;' }, [ + const buttons = [] + + buttons.push( h(ArtButtonTable, { text: '详情', onClick: () => viewDetail(row) - }), - h(ArtButtonTable, { - type: 'edit', - onClick: () => showRemarkDialog(row) }) - ]) + ) + + if (hasAuth('authorization_records:update_remark')) { + buttons.push( + h(ArtButtonTable, { + type: 'edit', + onClick: () => showRemarkDialog(row) + }) + ) + } + + return h('div', { style: 'display: flex; gap: 8px;' }, buttons) } } ]) diff --git a/src/views/asset-management/device-list/index.vue b/src/views/asset-management/device-list/index.vue index edb7b9a..f67bfaa 100644 --- a/src/views/asset-management/device-list/index.vue +++ b/src/views/asset-management/device-list/index.vue @@ -21,13 +21,23 @@ type="primary" @click="handleBatchAllocate" :disabled="!selectedDevices.length" + v-permission="'devices:batch_allocation'" > 批量分配 - + 批量回收 - + 批量设置套餐系列 @@ -546,6 +556,7 @@ } from '@/types/api' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue' import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue' @@ -555,6 +566,7 @@ defineOptions({ name: 'DeviceList' }) + const { hasAuth } = useAuth() const router = useRouter() const loading = ref(false) const allocateLoading = ref(false) @@ -1064,16 +1076,29 @@ width: 200, fixed: 'right', formatter: (row: Device) => { - return h('div', { style: 'display: flex; gap: 0; align-items: center;' }, [ - h(ArtButtonTable, { - text: '查看卡片', - onClick: () => handleViewCards(row) - }), - h(ArtButtonTable, { - text: '更多操作', - onContextmenu: (e: MouseEvent) => showDeviceOperationMenu(e, row.device_no) - }) - ]) + const buttons = [] + + if (hasAuth('devices:look_binding')) { + buttons.push( + h(ArtButtonTable, { + text: '查看卡片', + onClick: () => handleViewCards(row) + }) + ) + } + + // Show "更多操作" only if user has at least one operation permission + const hasAnyOperationPermission = hasAuth('devices:delete') + if (hasAnyOperationPermission) { + buttons.push( + h(ArtButtonTable, { + text: '更多操作', + onContextmenu: (e: MouseEvent) => showDeviceOperationMenu(e, row.device_no) + }) + ) + } + + return h('div', { style: 'display: flex; gap: 0; align-items: center;' }, buttons) } } ]) @@ -1602,32 +1627,39 @@ } // 设备操作菜单项配置 - const deviceOperationMenuItems = computed((): MenuItemType[] => [ - { - key: 'reboot', - label: '重启设备' - }, - { - key: 'reset', - label: '恢复出厂' - }, - { - key: 'speed-limit', - label: '设置限速' - }, - { - key: 'switch-card', - label: '切换SIM卡' - }, - { - key: 'set-wifi', - label: '设置WiFi' - }, - { - key: 'delete', - label: '删除设备' + const deviceOperationMenuItems = computed((): MenuItemType[] => { + const items: MenuItemType[] = [ + { + key: 'reboot', + label: '重启设备' + }, + { + key: 'reset', + label: '恢复出厂' + }, + { + key: 'speed-limit', + label: '设置限速' + }, + { + key: 'switch-card', + label: '切换SIM卡' + }, + { + key: 'set-wifi', + label: '设置WiFi' + } + ] + + if (hasAuth('devices:delete')) { + items.push({ + key: 'delete', + label: '删除设备' + }) } - ]) + + return items + }) // 显示设备操作菜单 const showDeviceOperationMenu = (e: MouseEvent, deviceNo: string) => { diff --git a/src/views/asset-management/device-task/index.vue b/src/views/asset-management/device-task/index.vue index de329aa..0cb9997 100644 --- a/src/views/asset-management/device-task/index.vue +++ b/src/views/asset-management/device-task/index.vue @@ -18,7 +18,12 @@ @refresh="handleRefresh" > @@ -215,6 +220,7 @@ import type { UploadInstance } from 'element-plus' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import { formatDateTime } from '@/utils/business/format' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import { StorageService } from '@/api/modules/storage' @@ -222,6 +228,7 @@ defineOptions({ name: 'DeviceTask' }) + const { hasAuth } = useAuth() const loading = ref(false) const tableRef = ref() const uploadRef = ref() diff --git a/src/views/asset-management/iot-card-management/index.vue b/src/views/asset-management/iot-card-management/index.vue index c87c137..252b545 100644 --- a/src/views/asset-management/iot-card-management/index.vue +++ b/src/views/asset-management/iot-card-management/index.vue @@ -22,6 +22,7 @@ type="success" :disabled="selectedCards.length === 0" @click="showAllocateDialog" + v-permission="'iot_card:batch_allocation'" > 批量分配 @@ -29,6 +30,7 @@ type="warning" :disabled="selectedCards.length === 0" @click="showRecallDialog" + v-permission="'iot_card:batch_recycle'" > 批量回收 @@ -36,10 +38,17 @@ type="primary" :disabled="selectedCards.length === 0" @click="showSeriesBindingDialog" + v-permission="'iot_card:batch_setting'" > 批量设置套餐系列 - 更多操作 + + 更多操作 + @@ -595,6 +604,7 @@ import QRCode from 'qrcode' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import { formatDateTime } from '@/utils/business/format' import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue' import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue' @@ -611,6 +621,7 @@ defineOptions({ name: 'StandaloneCardList' }) + const { hasAuth } = useAuth() const router = useRouter() const loading = ref(false) const allocateDialogVisible = ref(false) @@ -1513,28 +1524,44 @@ } // 更多操作菜单项配置 - const moreMenuItems = computed((): MenuItemType[] => [ - { - key: 'distribution', - label: '网卡分销' - }, - { - key: 'recharge', - label: '批量充值' - }, - { - key: 'recycle', - label: '网卡回收' - }, - { + const moreMenuItems = computed((): MenuItemType[] => { + const items: MenuItemType[] = [] + + if (hasAuth('iot_card:network_distribution')) { + items.push({ + key: 'distribution', + label: '网卡分销' + }) + } + + if (hasAuth('iot_card:batch_recharge')) { + items.push({ + key: 'recharge', + label: '批量充值' + }) + } + + if (hasAuth('iot_card:network_recycle')) { + items.push({ + key: 'recycle', + label: '网卡回收' + }) + } + + items.push({ key: 'download', label: '批量下载' - }, - { - key: 'changePackage', - label: '变更套餐' + }) + + if (hasAuth('iot_card:change_package')) { + items.push({ + key: 'changePackage', + label: '变更套餐' + }) } - ]) + + return items + }) // 卡操作菜单项配置 const cardOperationMenuItems = computed((): MenuItemType[] => [ diff --git a/src/views/asset-management/iot-card-task/index.vue b/src/views/asset-management/iot-card-task/index.vue index d4a5cd9..f6a835d 100644 --- a/src/views/asset-management/iot-card-task/index.vue +++ b/src/views/asset-management/iot-card-task/index.vue @@ -18,7 +18,12 @@ @refresh="handleRefresh" > @@ -212,6 +217,7 @@ import type { UploadInstance } from 'element-plus' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import { formatDateTime } from '@/utils/business/format' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import { StorageService } from '@/api/modules/storage' @@ -220,6 +226,7 @@ defineOptions({ name: 'IotCardTask' }) + const { hasAuth } = useAuth() const loading = ref(false) const tableRef = ref() const uploadRef = ref() diff --git a/src/views/finance/carrier-management/index.vue b/src/views/finance/carrier-management/index.vue index a9cba39..e9d202b 100644 --- a/src/views/finance/carrier-management/index.vue +++ b/src/views/finance/carrier-management/index.vue @@ -19,7 +19,7 @@ @refresh="handleRefresh" > @@ -104,6 +104,7 @@ import type { Carrier, CarrierType } from '@/types/api' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import { formatDateTime } from '@/utils/business/format' import { @@ -116,6 +117,7 @@ defineOptions({ name: 'CarrierManagement' }) + const { hasAuth } = useAuth() const dialogVisible = ref(false) const loading = ref(false) const submitLoading = ref(false) @@ -258,6 +260,7 @@ activeText: getStatusText(CommonStatus.ENABLED), inactiveText: getStatusText(CommonStatus.DISABLED), inlinePrompt: true, + disabled: !hasAuth('carrier:update_status'), 'onUpdate:modelValue': (val: string | number | boolean) => handleStatusChange(row, val as number) }) @@ -275,16 +278,27 @@ width: 150, fixed: 'right', formatter: (row: any) => { - return h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ArtButtonTable, { - type: 'edit', - onClick: () => showDialog('edit', row) - }), - h(ArtButtonTable, { - type: 'delete', - onClick: () => deleteCarrier(row) - }) - ]) + const buttons = [] + + if (hasAuth('carrier:edit')) { + buttons.push( + h(ArtButtonTable, { + type: 'edit', + onClick: () => showDialog('edit', row) + }) + ) + } + + if (hasAuth('carrier:delete')) { + buttons.push( + h(ArtButtonTable, { + type: 'delete', + onClick: () => deleteCarrier(row) + }) + ) + } + + return h('div', { style: 'display: flex; gap: 8px;' }, buttons) } } ]) diff --git a/src/views/finance/commission/agent-commission/index.vue b/src/views/finance/commission/agent-commission/index.vue index aa68d79..0979dc0 100644 --- a/src/views/finance/commission/agent-commission/index.vue +++ b/src/views/finance/commission/agent-commission/index.vue @@ -232,6 +232,7 @@ WithdrawalRequestItem } from '@/types/api/commission' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import { formatDateTime, formatMoney } from '@/utils/business/format' import { @@ -243,6 +244,8 @@ defineOptions({ name: 'AgentCommission' }) + const { hasAuth } = useAuth() + const route = useRoute() // 主表格状态 @@ -380,10 +383,16 @@ width: 120, fixed: 'right', formatter: (row: ShopCommissionSummaryItem) => { - return h(ArtButtonTable, { - icon: '', - onClick: () => showDetail(row) - }) + const buttons = [] + if (hasAuth('agent_commission:detail')) { + buttons.push( + h(ArtButtonTable, { + icon: '', + onClick: () => showDetail(row) + }) + ) + } + return h('div', { style: 'display: flex; gap: 8px;' }, buttons) } } ]) diff --git a/src/views/finance/commission/my-commission/index.vue b/src/views/finance/commission/my-commission/index.vue index bcf01f9..df726d5 100644 --- a/src/views/finance/commission/my-commission/index.vue +++ b/src/views/finance/commission/my-commission/index.vue @@ -106,7 +106,7 @@ style="margin-top: 20px" > @@ -296,10 +296,13 @@ } from '@/config/constants/commission' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import { formatDateTime, formatMoney } from '@/utils/business/format' defineOptions({ name: 'MyCommission' }) + const { hasAuth } = useAuth() + // 标签页 const activeTab = ref('commission') diff --git a/src/views/finance/commission/withdrawal-settings/index.vue b/src/views/finance/commission/withdrawal-settings/index.vue index f4fab5f..ad7d505 100644 --- a/src/views/finance/commission/withdrawal-settings/index.vue +++ b/src/views/finance/commission/withdrawal-settings/index.vue @@ -80,7 +80,7 @@ @refresh="handleRefresh" > @@ -155,10 +155,13 @@ import type { FormInstance, FormRules } from 'element-plus' import type { WithdrawalSettingItem } from '@/types/api/commission' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import { formatDateTime, formatMoney, formatFeeRate } from '@/utils/business/format' defineOptions({ name: 'CommissionWithdrawalSettings' }) + const { hasAuth } = useAuth() + const dialogVisible = ref(false) const loading = ref(false) const submitLoading = ref(false) diff --git a/src/views/order-management/order-list/index.vue b/src/views/order-management/order-list/index.vue index 19efcb5..43bc3a1 100644 --- a/src/views/order-management/order-list/index.vue +++ b/src/views/order-management/order-list/index.vue @@ -18,7 +18,7 @@ @refresh="handleRefresh" > @@ -275,12 +275,14 @@ } from '@/types/api' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import { formatDateTime } from '@/utils/business/format' defineOptions({ name: 'OrderList' }) const { t } = useI18n() + const { hasAuth } = useAuth() const loading = ref(false) const createLoading = ref(false) diff --git a/src/views/package-management/package-assign/index.vue b/src/views/package-management/package-assign/index.vue index 94296d4..ad7cb9f 100644 --- a/src/views/package-management/package-assign/index.vue +++ b/src/views/package-management/package-assign/index.vue @@ -18,7 +18,7 @@ @refresh="handleRefresh" > @@ -174,6 +174,7 @@ import type { ShopPackageAllocationResponse, PackageResponse, ShopResponse } from '@/types/api' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import { formatDateTime } from '@/utils/business/format' import { @@ -185,6 +186,8 @@ defineOptions({ name: 'PackageAssign' }) + const { hasAuth } = useAuth() + const dialogVisible = ref(false) const costPriceDialogVisible = ref(false) const loading = ref(false) @@ -386,6 +389,7 @@ activeText: getStatusText(CommonStatus.ENABLED), inactiveText: getStatusText(CommonStatus.DISABLED), inlinePrompt: true, + disabled: !hasAuth('package_assign:update_status'), 'onUpdate:modelValue': (val: string | number | boolean) => handleStatusChange(row, val as number) }) @@ -403,20 +407,36 @@ width: 230, fixed: 'right', formatter: (row: ShopPackageAllocationResponse) => { - return h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ArtButtonTable, { - text: '修改成本价', - onClick: () => showCostPriceDialog(row) - }), - h(ArtButtonTable, { - type: 'edit', - onClick: () => showDialog('edit', row) - }), - h(ArtButtonTable, { - type: 'delete', - onClick: () => deleteAllocation(row) - }) - ]) + const buttons = [] + + if (hasAuth('package_assign:update_cost')) { + buttons.push( + h(ArtButtonTable, { + text: '修改成本价', + onClick: () => showCostPriceDialog(row) + }) + ) + } + + if (hasAuth('package_assign:edit')) { + buttons.push( + h(ArtButtonTable, { + type: 'edit', + onClick: () => showDialog('edit', row) + }) + ) + } + + if (hasAuth('package_assign:delete')) { + buttons.push( + h(ArtButtonTable, { + type: 'delete', + onClick: () => deleteAllocation(row) + }) + ) + } + + return h('div', { style: 'display: flex; gap: 8px;' }, buttons) } } ]) diff --git a/src/views/package-management/package-list/index.vue b/src/views/package-management/package-list/index.vue index 3935bfb..813d358 100644 --- a/src/views/package-management/package-list/index.vue +++ b/src/views/package-management/package-list/index.vue @@ -18,7 +18,7 @@ @refresh="handleRefresh" > @@ -176,6 +176,7 @@ import type { PackageResponse, SeriesSelectOption } from '@/types/api' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import { formatDateTime } from '@/utils/business/format' import { @@ -196,6 +197,8 @@ defineOptions({ name: 'PackageList' }) + const { hasAuth } = useAuth() + const dialogVisible = ref(false) const loading = ref(false) const submitLoading = ref(false) @@ -438,6 +441,7 @@ activeText: '上架', inactiveText: '下架', inlinePrompt: true, + disabled: !hasAuth('package:update_away'), 'onUpdate:modelValue': (val: string | number | boolean) => handleShelfStatusChange(row, val ? 1 : 2) }) @@ -456,6 +460,7 @@ activeText: getStatusText(CommonStatus.ENABLED), inactiveText: getStatusText(CommonStatus.DISABLED), inlinePrompt: true, + disabled: !hasAuth('package:update_status'), 'onUpdate:modelValue': (val: string | number | boolean) => handleStatusChange(row, val as number) }) @@ -473,16 +478,27 @@ width: 150, fixed: 'right', formatter: (row: PackageResponse) => { - return h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ArtButtonTable, { - type: 'edit', - onClick: () => showDialog('edit', row) - }), - h(ArtButtonTable, { - type: 'delete', - onClick: () => deletePackage(row) - }) - ]) + const buttons = [] + + if (hasAuth('package:edit')) { + buttons.push( + h(ArtButtonTable, { + type: 'edit', + onClick: () => showDialog('edit', row) + }) + ) + } + + if (hasAuth('package:delete')) { + buttons.push( + h(ArtButtonTable, { + type: 'delete', + onClick: () => deletePackage(row) + }) + ) + } + + return h('div', { style: 'display: flex; gap: 8px;' }, buttons) } } ]) diff --git a/src/views/package-management/package-series/index.vue b/src/views/package-management/package-series/index.vue index 42b5758..72421a3 100644 --- a/src/views/package-management/package-series/index.vue +++ b/src/views/package-management/package-series/index.vue @@ -18,7 +18,7 @@ @refresh="handleRefresh" > @@ -98,6 +98,7 @@ import type { PackageSeriesResponse } from '@/types/api' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import { formatDateTime } from '@/utils/business/format' import { @@ -110,6 +111,8 @@ defineOptions({ name: 'PackageSeries' }) + const { hasAuth } = useAuth() + const dialogVisible = ref(false) const loading = ref(false) const submitLoading = ref(false) @@ -229,6 +232,7 @@ activeText: getStatusText(CommonStatus.ENABLED), inactiveText: getStatusText(CommonStatus.DISABLED), inlinePrompt: true, + disabled: !hasAuth('package_series:update_status'), 'onUpdate:modelValue': (val: string | number | boolean) => handleStatusChange(row, val as number) }) @@ -252,16 +256,27 @@ width: 150, fixed: 'right', formatter: (row: PackageSeriesResponse) => { - return h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ArtButtonTable, { - type: 'edit', - onClick: () => showDialog('edit', row) - }), - h(ArtButtonTable, { - type: 'delete', - onClick: () => deleteSeries(row) - }) - ]) + const buttons = [] + + if (hasAuth('package_series:edit')) { + buttons.push( + h(ArtButtonTable, { + type: 'edit', + onClick: () => showDialog('edit', row) + }) + ) + } + + if (hasAuth('package_series:delete')) { + buttons.push( + h(ArtButtonTable, { + type: 'delete', + onClick: () => deleteSeries(row) + }) + ) + } + + return h('div', { style: 'display: flex; gap: 8px;' }, buttons) } } ]) diff --git a/src/views/package-management/series-assign/index.vue b/src/views/package-management/series-assign/index.vue index b0f2a80..ad6af27 100644 --- a/src/views/package-management/series-assign/index.vue +++ b/src/views/package-management/series-assign/index.vue @@ -18,7 +18,7 @@ @refresh="handleRefresh" > @@ -266,6 +266,7 @@ } from '@/types/api' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import { formatDateTime } from '@/utils/business/format' import { @@ -277,6 +278,8 @@ defineOptions({ name: 'SeriesAssign' }) + const { hasAuth } = useAuth() + const dialogVisible = ref(false) const loading = ref(false) const submitLoading = ref(false) @@ -511,6 +514,7 @@ activeText: getStatusText(CommonStatus.ENABLED), inactiveText: getStatusText(CommonStatus.DISABLED), inlinePrompt: true, + disabled: !hasAuth('series_assign:update_status'), 'onUpdate:modelValue': (val: string | number | boolean) => handleStatusChange(row, val as number) }) @@ -528,16 +532,27 @@ width: 150, fixed: 'right', formatter: (row: ShopSeriesAllocationResponse) => { - return h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ArtButtonTable, { - type: 'edit', - onClick: () => showDialog('edit', row) - }), - h(ArtButtonTable, { - type: 'delete', - onClick: () => deleteAllocation(row) - }) - ]) + const buttons = [] + + if (hasAuth('series_assign:edit')) { + buttons.push( + h(ArtButtonTable, { + type: 'edit', + onClick: () => showDialog('edit', row) + }) + ) + } + + if (hasAuth('series_assign:delete')) { + buttons.push( + h(ArtButtonTable, { + type: 'delete', + onClick: () => deleteAllocation(row) + }) + ) + } + + return h('div', { style: 'display: flex; gap: 8px;' }, buttons) } } ]) diff --git a/src/views/product/shop/index.vue b/src/views/product/shop/index.vue index de85867..b55fb36 100644 --- a/src/views/product/shop/index.vue +++ b/src/views/product/shop/index.vue @@ -17,7 +17,7 @@ @refresh="handleRefresh" > @@ -310,6 +310,7 @@ } from 'element-plus' import type { FormRules } from 'element-plus' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue' import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue' @@ -323,6 +324,8 @@ defineOptions({ name: 'Shop' }) + const { hasAuth } = useAuth() + const dialogType = ref('add') const dialogVisible = ref(false) const customerAccountDialogVisible = ref(false) @@ -606,16 +609,28 @@ width: 200, fixed: 'right', formatter: (row: ShopResponse) => { - return h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ArtButtonTable, { - text: '查看客户', - onClick: () => viewCustomerAccounts(row) - }), - h(ArtButtonTable, { - text: '更多操作', - onContextmenu: (e: MouseEvent) => showShopOperationMenu(e, row) - }) - ]) + const buttons = [] + + if (hasAuth('shop:look_customer')) { + buttons.push( + h(ArtButtonTable, { + text: '查看客户', + onClick: () => viewCustomerAccounts(row) + }) + ) + } + + // 只要有编辑或删除权限之一,就显示更多操作按钮 + if (hasAuth('shop:edit') || hasAuth('shop:delete')) { + buttons.push( + h(ArtButtonTable, { + text: '更多操作', + onContextmenu: (e: MouseEvent) => showShopOperationMenu(e, row) + }) + ) + } + + return h('div', { style: 'display: flex; gap: 8px;' }, buttons) } } ]) @@ -874,20 +889,30 @@ } // 店铺操作菜单项配置 - const shopOperationMenuItems = computed((): MenuItemType[] => [ - { + const shopOperationMenuItems = computed((): MenuItemType[] => { + const items: MenuItemType[] = [] + + items.push({ key: 'defaultRoles', label: '默认角色' - }, - { - key: 'edit', - label: '编辑' - }, - { - key: 'delete', - label: '删除' + }) + + if (hasAuth('shop:edit')) { + items.push({ + key: 'edit', + label: '编辑' + }) } - ]) + + if (hasAuth('shop:delete')) { + items.push({ + key: 'delete', + label: '删除' + }) + } + + return items + }) // 显示店铺操作右键菜单 const showShopOperationMenu = (e: MouseEvent, row: ShopResponse) => { diff --git a/src/views/system/permission/index.vue b/src/views/system/permission/index.vue index 6ad647a..c6e30d1 100644 --- a/src/views/system/permission/index.vue +++ b/src/views/system/permission/index.vue @@ -17,7 +17,7 @@ @refresh="handleRefresh" > @@ -125,6 +125,7 @@ import type { CreatePermissionParams, PermissionTreeNode } from '@/types/api' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' + import { useAuth } from '@/composables/useAuth' import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue' import { PermissionType, @@ -138,6 +139,8 @@ defineOptions({ name: 'Permission' }) + const { hasAuth } = useAuth() + // 搜索表单初始值 const initialSearchState = { perm_name: '', @@ -299,16 +302,27 @@ width: 120, fixed: 'right', formatter: (row: PermissionTreeNode) => { - return h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ArtButtonTable, { - type: 'edit', - onClick: () => showDialog('edit', row) - }), - h(ArtButtonTable, { - type: 'delete', - onClick: () => deletePermission(row) - }) - ]) + const buttons = [] + + if (hasAuth('permission:edit')) { + buttons.push( + h(ArtButtonTable, { + type: 'edit', + onClick: () => showDialog('edit', row) + }) + ) + } + + if (hasAuth('permission:delete')) { + buttons.push( + h(ArtButtonTable, { + type: 'delete', + onClick: () => deletePermission(row) + }) + ) + } + + return h('div', { style: 'display: flex; gap: 8px;' }, buttons) } } ])