From 2b20145159c4c4eefc73fd449093e639812a64df Mon Sep 17 00:00:00 2001
From: shinnosuke <tantao@1hoper.com>
Date: 星期三, 19 八月 2026 14:35:36 +0800
Subject: [PATCH] 又又更新
---
talent-card-admin/src/views/MedicalReservationView.vue | 2
talent-card-admin/src/views/OrderView.vue | 2
talent-card-admin/src/views/MedicalApplicationView.vue | 2
talent-card-admin/src/views/AppointmentManagementView.vue | 2
talent-card-admin/src/views/BusinessManagementView.vue | 12 +-
talent-card-admin/src/views/StaffManagementView.vue | 10 +-
talent-card-admin/src/router/index.ts | 9 +
talent-card-admin/src/views/HotelVerificationView.vue | 2
talent-card-admin/src/views/ProductAuditDetailView.vue | 4
talent-card-admin/src/main.ts | 13 ++
talent-card-admin/src/views/MovieTicketView.vue | 6
talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementService.java | 2
talent-card-admin/src/views/ChildCardView.vue | 2
talent-card-admin/src/views/MerchantView.vue | 6
talent-card-admin/src/views/ParkingPlateView.vue | 2
talent-card-admin/src/views/RoleFormView.vue | 44 ++++----
talent-card-admin/src/views/RefundRecordView.vue | 2
talent-card-admin/src/views/FamilyCardApplicationDetailView.vue | 4
talent-card-admin/src/views/ProductView.vue | 2
talent-card-admin/src/views/TalentListView.vue | 8
talent-card-admin/src/views/SpouseEmploymentView.vue | 2
talent-card-server/src/main/java/com/talentcard/server/business/admin/auth/AdminAuthInterceptor.java | 57 +++++++++++
talent-card-admin/src/views/UpgradeApplicationDetailView.vue | 4
talent-card-admin/src/views/MerchantCreateView.vue | 5
talent-card-admin/src/views/ChildCardApplicationDetailView.vue | 4
talent-card-admin/src/views/FamilyCardView.vue | 2
talent-card-server/src/main/java/com/talentcard/server/business/admin/auth/AdminAuthService.java | 19 +++
talent-card-admin/src/style.css | 60 ++++++++++--
talent-card-admin/src/views/HotelReservationView.vue | 2
talent-card-admin/src/layouts/AdminLayout.vue | 5
talent-card-admin/src/views/FuelServiceView.vue | 2
31 files changed, 218 insertions(+), 80 deletions(-)
diff --git a/talent-card-admin/src/layouts/AdminLayout.vue b/talent-card-admin/src/layouts/AdminLayout.vue
index ee8b33b..d3246ff 100644
--- a/talent-card-admin/src/layouts/AdminLayout.vue
+++ b/talent-card-admin/src/layouts/AdminLayout.vue
@@ -18,6 +18,7 @@
import { getAdminPendingNotices } from '../api/admin'
import type { AdminPendingNotice } from '../api/admin'
import { useAppStore } from '../stores/app'
+import { hasAdminPermission } from '../utils/permissions'
interface MenuItem {
path?: string
@@ -71,7 +72,7 @@
]
const financeMenus: MenuItem[] = [
- { path: '/merchant-settlement', label: '商户结算审核' },
+ { path: '/merchant-settlement', label: '提现管理' },
]
const orderMenus: MenuItem[] = [
@@ -125,7 +126,7 @@
const canAccess = (path?: string) => {
if (!path || appStore.user.role === '平台管理') return true
const required = routePermissions[path]
- return !required || Boolean(appStore.user.permissionCodes?.includes('*') || appStore.user.permissionCodes?.includes(required))
+ return !required || hasAdminPermission(required, appStore)
}
const visibleMenuGroups = computed(() => menuGroups
.map((group) => ({ ...group, items: group.items.filter((item) => canAccess(item.path)) }))
diff --git a/talent-card-admin/src/main.ts b/talent-card-admin/src/main.ts
index 622e155..8a797fc 100644
--- a/talent-card-admin/src/main.ts
+++ b/talent-card-admin/src/main.ts
@@ -6,5 +6,16 @@
import './style.css'
import App from './App.vue'
import router from './router'
+import { hasAdminPermission } from './utils/permissions'
-createApp(App).use(createPinia()).use(router).use(ElementPlus, { locale: zhCn }).mount('#app')
+const app = createApp(App)
+app.use(createPinia()).use(router).use(ElementPlus, { locale: zhCn })
+app.directive('permission', {
+ mounted(el, binding) {
+ if (!hasAdminPermission(String(binding.value))) el.style.display = 'none'
+ },
+ updated(el, binding) {
+ el.style.display = hasAdminPermission(String(binding.value)) ? '' : 'none'
+ },
+})
+app.mount('#app')
diff --git a/talent-card-admin/src/router/index.ts b/talent-card-admin/src/router/index.ts
index d17b165..41e21d6 100644
--- a/talent-card-admin/src/router/index.ts
+++ b/talent-card-admin/src/router/index.ts
@@ -1,6 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useAppStore } from '../stores/app'
import AdminLayout from '../layouts/AdminLayout.vue'
+import { hasAdminPermission, requiredRoutePermission } from '../utils/permissions'
const familyCardView = () => import('../views/FamilyCardView.vue')
const businessManagementView = () => import('../views/BusinessManagementView.vue')
@@ -110,7 +111,7 @@
path: 'merchant-settlement',
name: 'merchant-settlement',
component: () => import('../views/MerchantSettlementView.vue'),
- meta: { title: '商户结算审核' },
+ meta: { title: '提现管理' },
},
{
path: 'merchant-onboardings/:id',
@@ -348,7 +349,11 @@
router.beforeEach((to) => {
const appStore = useAppStore()
if (!to.meta.public && !appStore.token) {
- return '/login'
+ return { path: '/login', query: { redirect: to.fullPath } }
+ }
+ if (!to.meta.public) {
+ const required = requiredRoutePermission(to.path, to.query as unknown as Record<string, unknown>)
+ if (required && !hasAdminPermission(required, appStore)) return '/dashboard'
}
return true
})
diff --git a/talent-card-admin/src/style.css b/talent-card-admin/src/style.css
index cd2eee5..8186e05 100644
--- a/talent-card-admin/src/style.css
+++ b/talent-card-admin/src/style.css
@@ -2032,15 +2032,39 @@
.coupon-edit-row {
display: grid;
- grid-template-columns: 72px minmax(0, 1fr);
- gap: 14px;
- align-items: center;
+ grid-template-columns: 82px minmax(0, 1fr);
+ gap: 6px;
+ align-items: start;
margin-bottom: 18px;
}
.coupon-edit-row > label {
- line-height: 18px;
+ height: 30px;
+ box-sizing: border-box;
+ padding: 0 0 0 12px;
+ line-height: 30px;
+ justify-content: flex-end;
white-space: nowrap;
+}
+
+.merchant-coupon-edit-form .coupon-edit-row .el-form-item__label {
+ width: 82px !important;
+ max-width: none;
+ height: 30px;
+ box-sizing: border-box;
+ padding: 0 !important;
+ overflow: visible;
+ flex: 0 0 82px;
+ line-height: 30px;
+ white-space: nowrap !important;
+ word-break: keep-all;
+ overflow-wrap: normal;
+}
+
+.coupon-edit-row .el-form-item__content {
+ min-width: 0;
+ min-height: 30px;
+ margin-left: 0 !important;
}
.merchant-coupon-edit-form .el-input {
@@ -2050,16 +2074,23 @@
.coupon-amount-control {
display: flex;
align-items: center;
- gap: 8px;
+ gap: 12px;
}
-.coupon-amount-control .el-input-number {
- width: 200px;
+.coupon-amount-control .el-input {
+ width: 184px;
+}
+
+.merchant-coupon-edit-form .coupon-amount-control .el-input__wrapper {
+ min-height: 30px;
+ padding: 1px 11px;
+ background: #f5f6f8;
+ box-shadow: none;
}
.coupon-amount-control > span {
color: #334155;
- line-height: 28px;
+ line-height: 30px;
white-space: nowrap;
}
@@ -2085,8 +2116,10 @@
.coupon-requirement-row .el-radio-group {
display: flex;
align-items: center;
+ flex-wrap: nowrap;
gap: 0;
height: 28px;
+ white-space: nowrap;
}
.coupon-requirement-row .el-radio {
@@ -2190,12 +2223,19 @@
}
.coupon-switch-row {
- grid-template-columns: 72px 44px;
+ grid-template-columns: 130px 44px;
+ gap: 6px;
margin-bottom: 18px;
}
.coupon-switch-row > label {
- white-space: normal;
+ width: 130px;
+ box-sizing: border-box;
+ padding: 0 0 0 20px;
+ line-height: 30px;
+ justify-content: flex-start;
+ white-space: nowrap;
+ word-break: keep-all;
}
.coupon-switch-row .el-switch {
diff --git a/talent-card-admin/src/views/AppointmentManagementView.vue b/talent-card-admin/src/views/AppointmentManagementView.vue
index ef7eabd..eaf4af6 100644
--- a/talent-card-admin/src/views/AppointmentManagementView.vue
+++ b/talent-card-admin/src/views/AppointmentManagementView.vue
@@ -44,7 +44,7 @@
<el-form-item label="卡片等级"><el-select v-model="filters.cardLevel" clearable placeholder="全部"><el-option v-for="level in cardLevels" :key="level" :label="level" :value="level" /></el-select></el-form-item>
<el-form-item label="状态"><el-select v-model="filters.status" clearable placeholder="全部"><el-option v-for="status in statusOptions" :key="status" :label="status" :value="status" /></el-select></el-form-item>
<el-form-item label="提交时间"><el-date-picker v-model="filters.submittedTime" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" /></el-form-item>
- <div class="toolbar-actions"><el-button type="primary" :icon="Search" @click="search">查询</el-button><el-button :icon="Refresh" @click="resetFilters">重置</el-button><el-button :icon="Download" @click="download">导出</el-button></div>
+ <div class="toolbar-actions"><el-button type="primary" :icon="Search" @click="search">查询</el-button><el-button :icon="Refresh" @click="resetFilters">重置</el-button><el-button v-permission="'admin.appointment-management.export'" :icon="Download" @click="download">导出</el-button></div>
</el-form>
<el-table v-loading="loading" :data="rows" border empty-text="暂无预约申请数据">
<el-table-column prop="applicationNo" label="申请单号" min-width="130" />
diff --git a/talent-card-admin/src/views/BusinessManagementView.vue b/talent-card-admin/src/views/BusinessManagementView.vue
index 3e07bd7..4087e7e 100644
--- a/talent-card-admin/src/views/BusinessManagementView.vue
+++ b/talent-card-admin/src/views/BusinessManagementView.vue
@@ -202,18 +202,18 @@
</div>
</el-form>
<div class="business-fill-tools">
- <el-button v-if="!isUpgrade" type="primary" :icon="Plus" @click="openCreate">新建</el-button>
- <el-button :icon="Download" @click="download">下载</el-button>
+ <el-button v-if="!isUpgrade" v-permission="isRole ? 'admin.roles.create' : 'admin.staff.create'" type="primary" :icon="Plus" @click="openCreate">新建</el-button>
+ <el-button v-permission="isUpgrade ? 'admin.upgrade-applications.export' : isRole ? 'admin.roles.export' : 'admin.staff.export'" :icon="Download" @click="download">下载</el-button>
</div>
<el-table v-loading="loading" :data="pagedRows" border class="business-fill-table" :empty-text="`暂无${title}数据`">
<el-table-column v-for="[key, label] in columns" :key="key" :prop="key" :label="label" min-width="130" show-overflow-tooltip />
<el-table-column label="操作" width="150" fixed="right" align="center">
<template #default="{ row }">
- <el-button link type="primary" :icon="View" @click="view(row)">查看</el-button>
- <el-button v-if="!isUpgrade && !(isRole && isPlatformAdminRole(row))" link type="primary" :icon="Edit" @click="openEdit(row)">编辑</el-button>
+ <el-button v-permission="isUpgrade ? 'admin.upgrade-applications.view' : isRole ? 'admin.roles.view' : 'admin.staff.view'" link type="primary" :icon="View" @click="view(row)">查看</el-button>
+ <el-button v-if="!isUpgrade && !(isRole && isPlatformAdminRole(row))" v-permission="isRole ? 'admin.roles.edit' : 'admin.staff.edit'" link type="primary" :icon="Edit" @click="openEdit(row)">编辑</el-button>
<template v-if="isRole && !isPlatformAdminRole(row)">
- <el-button link type="primary" @click="changeRoleStatus(row)">{{ Number(row.status) === 1 ? '停用' : '启用' }}</el-button>
- <el-button link type="danger" @click="removeRole(row)">删除</el-button>
+ <el-button v-permission="'admin.roles.disable'" link type="primary" @click="changeRoleStatus(row)">{{ Number(row.status) === 1 ? '停用' : '启用' }}</el-button>
+ <el-button v-permission="'admin.roles.delete'" link type="danger" @click="removeRole(row)">删除</el-button>
</template>
</template>
</el-table-column>
diff --git a/talent-card-admin/src/views/ChildCardApplicationDetailView.vue b/talent-card-admin/src/views/ChildCardApplicationDetailView.vue
index a3e9d7d..e8457f0 100644
--- a/talent-card-admin/src/views/ChildCardApplicationDetailView.vue
+++ b/talent-card-admin/src/views/ChildCardApplicationDetailView.vue
@@ -124,8 +124,8 @@
</section>
<div v-if="isPending" style="display: flex; gap: 12px; margin-top: 12px;">
- <el-button type="primary" @click="approveApplication">审核通过</el-button>
- <el-button type="primary" @click="rejectApplication">审核不通过</el-button>
+ <el-button v-permission="'admin.child-cards.audit'" type="primary" @click="approveApplication">审核通过</el-button>
+ <el-button v-permission="'admin.child-cards.audit'" type="primary" @click="rejectApplication">审核不通过</el-button>
</div>
</div>
</template>
diff --git a/talent-card-admin/src/views/ChildCardView.vue b/talent-card-admin/src/views/ChildCardView.vue
index eba18e7..71f986d 100644
--- a/talent-card-admin/src/views/ChildCardView.vue
+++ b/talent-card-admin/src/views/ChildCardView.vue
@@ -163,7 +163,7 @@
</el-form>
<div class="family-card-table-tools">
- <el-button :icon="Download" @click="download">下载</el-button>
+ <el-button v-permission="'admin.child-cards.export'" :icon="Download" @click="download">下载</el-button>
</div>
<el-table v-loading="loading" :data="rows" class="family-card-table" :empty-text="emptyText">
diff --git a/talent-card-admin/src/views/FamilyCardApplicationDetailView.vue b/talent-card-admin/src/views/FamilyCardApplicationDetailView.vue
index 4861c0d..4facc95 100644
--- a/talent-card-admin/src/views/FamilyCardApplicationDetailView.vue
+++ b/talent-card-admin/src/views/FamilyCardApplicationDetailView.vue
@@ -163,8 +163,8 @@
</section>
<div v-if="isPending" style="display: flex; gap: 12px; margin-top: 12px;">
- <el-button type="primary" @click="approveApplication">通过</el-button>
- <el-button type="primary" @click="rejectApplication">拒绝</el-button>
+ <el-button v-permission="'admin.family-card-applications.audit'" type="primary" @click="approveApplication">通过</el-button>
+ <el-button v-permission="'admin.family-card-applications.audit'" type="primary" @click="rejectApplication">拒绝</el-button>
</div>
</div>
</template>
diff --git a/talent-card-admin/src/views/FamilyCardView.vue b/talent-card-admin/src/views/FamilyCardView.vue
index 2bfc305..7517bae 100644
--- a/talent-card-admin/src/views/FamilyCardView.vue
+++ b/talent-card-admin/src/views/FamilyCardView.vue
@@ -212,7 +212,7 @@
</el-form>
<div class="family-card-table-tools">
- <el-button :icon="Download" @click="download">{{ isManagement ? '下载' : '导出' }}</el-button>
+ <el-button v-permission="isManagement ? 'admin.family-cards.export' : 'admin.family-card-applications.export'" :icon="Download" @click="download">{{ isManagement ? '下载' : '导出' }}</el-button>
</div>
<el-table v-if="isManagement" v-loading="loading" :data="rows" class="family-card-table" :empty-text="emptyText">
diff --git a/talent-card-admin/src/views/FuelServiceView.vue b/talent-card-admin/src/views/FuelServiceView.vue
index 503e35a..ddef4d5 100644
--- a/talent-card-admin/src/views/FuelServiceView.vue
+++ b/talent-card-admin/src/views/FuelServiceView.vue
@@ -132,7 +132,7 @@
</el-form>
<div class="fuel-service-table-tools">
- <el-button :icon="Download" @click="download">下载</el-button>
+ <el-button v-permission="'admin.fuel-services.export'" :icon="Download" @click="download">下载</el-button>
</div>
<el-table
diff --git a/talent-card-admin/src/views/HotelReservationView.vue b/talent-card-admin/src/views/HotelReservationView.vue
index 5a923ad..caf5f04 100644
--- a/talent-card-admin/src/views/HotelReservationView.vue
+++ b/talent-card-admin/src/views/HotelReservationView.vue
@@ -133,7 +133,7 @@
</el-form>
<div class="hotel-reservation-table-tools">
- <el-button :icon="Download" @click="download">下载</el-button>
+ <el-button v-permission="'admin.hotel-reservations.export'" :icon="Download" @click="download">下载</el-button>
</div>
<el-table
diff --git a/talent-card-admin/src/views/HotelVerificationView.vue b/talent-card-admin/src/views/HotelVerificationView.vue
index e4e6902..79cac98 100644
--- a/talent-card-admin/src/views/HotelVerificationView.vue
+++ b/talent-card-admin/src/views/HotelVerificationView.vue
@@ -126,7 +126,7 @@
</el-form>
<div class="order-table-tools">
- <el-button :icon="Download" @click="download">下载</el-button>
+ <el-button v-permission="'admin.hotel-free-verifications.export'" :icon="Download" @click="download">下载</el-button>
</div>
<el-table
diff --git a/talent-card-admin/src/views/MedicalApplicationView.vue b/talent-card-admin/src/views/MedicalApplicationView.vue
index 52ca68d..bde8fbc 100644
--- a/talent-card-admin/src/views/MedicalApplicationView.vue
+++ b/talent-card-admin/src/views/MedicalApplicationView.vue
@@ -123,7 +123,7 @@
</el-form>
<div class="medical-reservation-table-tools">
- <el-button :icon="Download" @click="download">下载</el-button>
+ <el-button v-permission="'admin.medical-applications.export'" :icon="Download" @click="download">下载</el-button>
</div>
<el-table
diff --git a/talent-card-admin/src/views/MedicalReservationView.vue b/talent-card-admin/src/views/MedicalReservationView.vue
index a732331..aaeb43a 100644
--- a/talent-card-admin/src/views/MedicalReservationView.vue
+++ b/talent-card-admin/src/views/MedicalReservationView.vue
@@ -127,7 +127,7 @@
</el-form>
<div class="medical-reservation-table-tools">
- <el-button :icon="Download" @click="download">下载</el-button>
+ <el-button v-permission="'admin.medical-reservations.export'" :icon="Download" @click="download">下载</el-button>
</div>
<el-table
diff --git a/talent-card-admin/src/views/MerchantCreateView.vue b/talent-card-admin/src/views/MerchantCreateView.vue
index 378a15d..35e3bf1 100644
--- a/talent-card-admin/src/views/MerchantCreateView.vue
+++ b/talent-card-admin/src/views/MerchantCreateView.vue
@@ -3092,7 +3092,10 @@
</el-form-item>
<el-form-item class="coupon-edit-row coupon-amount-row" label="优惠券金额" prop="couponAmount" required>
- <div class="coupon-amount-control"><el-input-number v-model="couponEditForm.couponAmount" :min="0.01" :precision="2" :step="1" controls-position="right" placeholder="请输入优惠券金额" /><span>元</span></div>
+ <div class="coupon-amount-control">
+ <el-input v-model="couponEditForm.couponAmount" inputmode="decimal" maxlength="8" placeholder="请输入优惠券金额" />
+ <span>元</span>
+ </div>
</el-form-item>
<el-form-item class="coupon-edit-row coupon-requirement-row" label="使用要求" prop="fullAmount" :required="couponEditForm.useRequirement === 'full'">
diff --git a/talent-card-admin/src/views/MerchantView.vue b/talent-card-admin/src/views/MerchantView.vue
index 1feb33d..6a410ca 100644
--- a/talent-card-admin/src/views/MerchantView.vue
+++ b/talent-card-admin/src/views/MerchantView.vue
@@ -291,7 +291,7 @@
</el-form>
<div class="prototype-table-tools">
- <el-button type="primary" :icon="Plus" @click="openCreate">新建</el-button>
+ <el-button v-permission="'admin.merchants.create'" type="primary" :icon="Plus" @click="openCreate">新建</el-button>
</div>
<el-table
@@ -332,11 +332,11 @@
</el-table-column>
<el-table-column label="操作" width="174" fixed="right" align="center">
<template #default="{ row }">
- <el-button link :type="row.status === 0 ? 'success' : 'danger'" @click="toggleStatus(row)">
+ <el-button v-permission="'admin.merchants.disable'" link :type="row.status === 0 ? 'success' : 'danger'" @click="toggleStatus(row)">
{{ row.status === 0 ? '启用' : '禁用' }}
</el-button>
<el-button link type="primary" :icon="Goods" @click="openProducts(row)">团购管理</el-button>
- <el-button link type="primary" :icon="Edit" @click="openEdit(row)">编辑</el-button>
+ <el-button v-permission="'admin.merchants.edit'" link type="primary" :icon="Edit" @click="openEdit(row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
diff --git a/talent-card-admin/src/views/MovieTicketView.vue b/talent-card-admin/src/views/MovieTicketView.vue
index d60bc69..d77eff0 100644
--- a/talent-card-admin/src/views/MovieTicketView.vue
+++ b/talent-card-admin/src/views/MovieTicketView.vue
@@ -437,7 +437,7 @@
<div class="movie-ticket-package-tools">
<el-button type="primary" :icon="Plus" @click="openCreate">新增</el-button>
- <el-button :icon="Download" @click="download">下载</el-button>
+ <el-button v-permission="'admin.movie-tickets.export'" :icon="Download" @click="download">下载</el-button>
</div>
<el-table v-loading="loading" :data="rows" border class="movie-ticket-package-table" empty-text="暂无票包">
@@ -488,7 +488,7 @@
<div class="movie-ticket-record-tools">
<el-button type="primary" @click="openSupplement">补录发放</el-button>
- <el-button :icon="Download" @click="download">下载</el-button>
+ <el-button v-permission="'admin.movie-tickets.export'" :icon="Download" @click="download">下载</el-button>
</div>
<el-table v-loading="loading" :data="rows" border class="movie-ticket-record-table" empty-text="暂无领取记录">
@@ -528,7 +528,7 @@
</div>
</el-form>
<div class="movie-ticket-package-tools">
- <el-button type="primary" @click="openImport(selectedPackage || {})">导入码</el-button>
+ <el-button v-permission="'admin.movie-tickets.import'" type="primary" @click="openImport(selectedPackage || {})">导入码</el-button>
<el-button @click="openGenerate(selectedPackage || {})">批量生成</el-button>
<span />
</div>
diff --git a/talent-card-admin/src/views/OrderView.vue b/talent-card-admin/src/views/OrderView.vue
index d2a8b8b..f2467e2 100644
--- a/talent-card-admin/src/views/OrderView.vue
+++ b/talent-card-admin/src/views/OrderView.vue
@@ -203,7 +203,7 @@
</el-form>
<div class="order-table-tools">
- <el-button :icon="Download" @click="download">下载</el-button>
+ <el-button v-permission="'admin.orders.export'" :icon="Download" @click="download">下载</el-button>
</div>
<el-table v-loading="loading" :data="orders" border class="order-list-table" empty-text="暂无订单数据">
diff --git a/talent-card-admin/src/views/ParkingPlateView.vue b/talent-card-admin/src/views/ParkingPlateView.vue
index eeef99d..8c28f22 100644
--- a/talent-card-admin/src/views/ParkingPlateView.vue
+++ b/talent-card-admin/src/views/ParkingPlateView.vue
@@ -34,7 +34,7 @@
<el-form-item label="提交类型"><el-select v-model="filters.submissionType" clearable placeholder="全部"><el-option v-for="item in submissionTypes" :key="item" :label="item" :value="item" /></el-select></el-form-item>
<el-form-item label="状态"><el-select v-model="filters.status" clearable placeholder="全部"><el-option v-for="status in statusOptions" :key="status" :label="status" :value="status" /></el-select></el-form-item>
<el-form-item label="提交时间"><el-date-picker v-model="filters.submittedTime" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" /></el-form-item>
- <div class="toolbar-actions"><el-button type="primary" :icon="Search" @click="search">查询</el-button><el-button :icon="Refresh" @click="resetFilters">重置</el-button><el-button :icon="Download" @click="download">导出</el-button></div>
+ <div class="toolbar-actions"><el-button type="primary" :icon="Search" @click="search">查询</el-button><el-button :icon="Refresh" @click="resetFilters">重置</el-button><el-button v-permission="'admin.parking-plates.export'" :icon="Download" @click="download">导出</el-button></div>
</el-form>
<el-table v-loading="loading" :data="rows" border empty-text="暂无车牌提交数据">
<el-table-column label="提交单号" min-width="130"><template #default="{ row }">{{ text(row.submissionNo || row.applicationNo) }}</template></el-table-column>
diff --git a/talent-card-admin/src/views/ProductAuditDetailView.vue b/talent-card-admin/src/views/ProductAuditDetailView.vue
index 890965a..137ebbf 100644
--- a/talent-card-admin/src/views/ProductAuditDetailView.vue
+++ b/talent-card-admin/src/views/ProductAuditDetailView.vue
@@ -313,8 +313,8 @@
</section>
<div v-if="isPendingAudit" class="audit-action-row">
- <el-button type="primary" :loading="auditing" @click="submitAudit(2)">审核通过</el-button>
- <el-button type="primary" :loading="auditing" @click="submitAudit(3)">审核不通过</el-button>
+ <el-button v-permission="'admin.products.audit'" type="primary" :loading="auditing" @click="submitAudit(2)">审核通过</el-button>
+ <el-button v-permission="'admin.products.audit'" type="primary" :loading="auditing" @click="submitAudit(3)">审核不通过</el-button>
</div>
<div v-else class="audit-result-row">
<span>审核结果:{{ auditResultText }}</span>
diff --git a/talent-card-admin/src/views/ProductView.vue b/talent-card-admin/src/views/ProductView.vue
index 37ab68d..747590a 100644
--- a/talent-card-admin/src/views/ProductView.vue
+++ b/talent-card-admin/src/views/ProductView.vue
@@ -210,7 +210,7 @@
</el-table-column>
<el-table-column label="操作" width="180" fixed="right" align="center">
<template #default="{ row }">
- <el-button link type="primary" @click="viewDetail(row)">查看</el-button>
+ <el-button v-permission="'admin.products.view'" link type="primary" @click="viewDetail(row)">查看</el-button>
</template>
</el-table-column>
</el-table>
diff --git a/talent-card-admin/src/views/RefundRecordView.vue b/talent-card-admin/src/views/RefundRecordView.vue
index f77665c..492e107 100644
--- a/talent-card-admin/src/views/RefundRecordView.vue
+++ b/talent-card-admin/src/views/RefundRecordView.vue
@@ -174,7 +174,7 @@
</el-form>
<div class="order-table-tools">
- <el-button :icon="Download" @click="download">下载</el-button>
+ <el-button v-permission="'admin.refunds.export'" :icon="Download" @click="download">下载</el-button>
</div>
<el-table v-loading="loading" :data="rows" border class="order-list-table refund-record-table" empty-text="暂无退款记录">
diff --git a/talent-card-admin/src/views/RoleFormView.vue b/talent-card-admin/src/views/RoleFormView.vue
index 098c390..b0c56d2 100644
--- a/talent-card-admin/src/views/RoleFormView.vue
+++ b/talent-card-admin/src/views/RoleFormView.vue
@@ -13,11 +13,11 @@
{
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: ['查看', '审核'] },
+ { 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: ['查看', '审核', '下载'] },
],
},
{
@@ -30,32 +30,32 @@
{
name: '订单管理',
pages: [
- { code: 'orders', name: '订单列表', actions: ['查看'] },
- { code: 'refunds', name: '退款记录', actions: ['查看'] },
- { code: 'hotel-free-verifications', name: '酒店免费核销记录', actions: ['查看'] },
+ { code: 'orders', name: '订单列表', actions: ['查看', '下载'] },
+ { code: 'refunds', name: '退款记录', actions: ['查看', '下载'] },
+ { code: 'hotel-free-verifications', name: '酒店免费核销记录', actions: ['查看', '下载'] },
],
},
{
name: '预约管理',
pages: [
- { code: 'appointment-management', name: '预约服务管理', actions: ['查看', '审核'] },
- { code: 'medical-reservations', name: '体检预约管理', actions: ['查看', '审核'] },
- { code: 'medical-applications', name: '就医申请管理', actions: ['查看', '审核'] },
- { code: 'parking-plates', name: '停车车牌管理', actions: ['查看', '审核'] },
+ { code: 'appointment-management', name: '预约服务管理', actions: ['查看', '审核', '下载'] },
+ { code: 'medical-reservations', name: '体检预约管理', actions: ['查看', '审核', '下载'] },
+ { code: 'medical-applications', name: '就医申请管理', actions: ['查看', '审核', '下载'] },
+ { code: 'parking-plates', name: '停车车牌管理', actions: ['查看', '审核', '下载'] },
{ code: 'car-wash-benefits', name: '洗车权益管理', actions: ['查看', '审核'] },
- { code: 'fuel-services', name: '加油服务管理', actions: ['查看', '审核'] },
- { code: 'spouse-employment', name: '配偶就业管理', actions: ['查看', '审核'] },
- { code: 'hotel-reservations', 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: 'merchant-settlement', name: '提现管理', actions: ['查看', '审核'] }] },
{
name: '运营管理',
pages: [
{ code: 'equity-images', name: '权益图片管理', actions: ['查看', '编辑'] },
{ code: 'news', name: '人才资讯管理', actions: ['查看', '新增', '编辑', '删除'] },
{ code: 'policies', name: '人才政策管理', actions: ['查看', '新增', '编辑', '删除'] },
- { code: 'movie-tickets', name: '电影票管理', actions: ['查看', '新增', '编辑', '审核'] },
+ { code: 'movie-tickets', name: '电影票管理', actions: ['查看', '新增', '编辑', '审核', '批量导入', '下载'] },
],
},
{
@@ -63,8 +63,8 @@
pages: [
{ code: 'settings', name: '平台设置', actions: ['查看', '编辑'] },
{ code: 'protocol-settings', name: '协议设置', actions: ['查看', '编辑'] },
- { code: 'roles', name: '角色管理', actions: ['查看', '新增', '编辑', '删除'] },
- { code: 'staff', name: '员工管理', actions: ['查看', '新增', '编辑', '启用/停用', '删除', '重置密码'] },
+ { code: 'roles', name: '角色管理', actions: ['查看', '新增', '编辑', '启用/停用', '删除', '下载'] },
+ { code: 'staff', name: '员工管理', actions: ['查看', '新增', '编辑', '启用/停用', '删除', '重置密码', '下载'] },
],
},
]
@@ -81,6 +81,8 @@
'删除': 'delete',
'重置密码': 'reset',
'审核': 'audit',
+ '批量导入': 'import',
+ '下载': 'export',
} 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)
@@ -94,5 +96,5 @@
async function save() { 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 } }
onMounted(load)
</script>
-<template><div class="page-stack role-form"><section class="panel"><div class="panel-header"><h3>{{ 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" placeholder="请输入" maxlength="64" /></el-form-item><el-form-item label="备注"><el-input v-model="form.remark" 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 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 :model-value="checked(moduleCodes(module))" :indeterminate="partial(moduleCodes(module))" @click.stop @change="toggleModule(module, Boolean($event))">{{ module.name }}</el-checkbox></span><span><el-checkbox :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" :model-value="form.permissionCodes.includes(code(page.code, action))" @click.stop @change="toggle([code(page.code, action)], Boolean($event))">{{ action }}</el-checkbox></span></div></template></div><div class="buttons"><el-button 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{margin:0 20px;border:1px solid #dfe4ea}.line{display:grid;grid-template-columns:150px 190px 1fr;min-height:44px;border-bottom:1px solid #e7ebf0}.line:last-child{border-bottom:0}.line>span{display:flex;align-items:center;padding:7px 18px;border-right:1px solid #e7ebf0}.line>span:last-child{border-right:0}.header{min-height:36px;background:#fafbfd;font-weight:600}.header span{justify-content:center}.actions{gap:26px;flex-wrap:wrap}.buttons{padding:20px;display:flex;gap:14px}.buttons .el-button{min-width:78px}@media(max-width:850px){.base{grid-template-columns:1fr}.line{grid-template-columns:120px 150px 1fr}.actions{gap:12px}}</style>
+<template><div class="page-stack role-form"><section class="panel"><div class="panel-header"><h3>{{ 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" placeholder="请输入" maxlength="64" /></el-form-item><el-form-item label="备注"><el-input v-model="form.remark" 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 :model-value="checked(moduleCodes(module))" :indeterminate="partial(moduleCodes(module))" @click.stop @change="toggleModule(module, Boolean($event))">{{ module.name }}</el-checkbox></span><span><el-checkbox :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" :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 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>
diff --git a/talent-card-admin/src/views/SpouseEmploymentView.vue b/talent-card-admin/src/views/SpouseEmploymentView.vue
index 30c2842..63d52b3 100644
--- a/talent-card-admin/src/views/SpouseEmploymentView.vue
+++ b/talent-card-admin/src/views/SpouseEmploymentView.vue
@@ -119,7 +119,7 @@
</el-form>
<div class="spouse-employment-table-tools">
- <el-button :icon="Download" @click="download">下载</el-button>
+ <el-button v-permission="'admin.spouse-employment.export'" :icon="Download" @click="download">下载</el-button>
</div>
<el-table
diff --git a/talent-card-admin/src/views/StaffManagementView.vue b/talent-card-admin/src/views/StaffManagementView.vue
index 236067d..a03ab2c 100644
--- a/talent-card-admin/src/views/StaffManagementView.vue
+++ b/talent-card-admin/src/views/StaffManagementView.vue
@@ -248,7 +248,7 @@
></el-form
>
<div class="tools">
- <el-button type="primary" :icon="Plus" @click="create">新增</el-button>
+ <el-button v-permission="'admin.staff.create'" type="primary" :icon="Plus" @click="create">新增</el-button>
</div>
<el-table v-loading="loading" :data="pagedRows" stripe class="table"
><el-table-column
@@ -289,16 +289,16 @@
}}</template></el-table-column
><el-table-column label="操作" width="225" fixed="right"
><template #default="{ row }"
- ><el-button v-if="!isDefaultAdmin(row)" link type="primary" @click="edit(row)">编辑</el-button
- ><el-button
+ ><el-button v-if="!isDefaultAdmin(row)" v-permission="'admin.staff.edit'" link type="primary" @click="edit(row)">编辑</el-button
+ ><el-button v-permission="'admin.staff.disable'"
v-if="!isDefaultAdmin(row)"
link
type="primary"
@click="changeStatus(row)"
>{{ row.status === 1 ? "禁用" : "启用" }}</el-button
- ><el-button link type="primary" @click="password(row)"
+ ><el-button v-permission="'admin.staff.reset'" link type="primary" @click="password(row)"
>重置密码</el-button
- ><el-button
+ ><el-button v-permission="'admin.staff.delete'"
v-if="!isDefaultAdmin(row)"
link
type="danger"
diff --git a/talent-card-admin/src/views/TalentListView.vue b/talent-card-admin/src/views/TalentListView.vue
index cfc4897..6cef01e 100644
--- a/talent-card-admin/src/views/TalentListView.vue
+++ b/talent-card-admin/src/views/TalentListView.vue
@@ -236,10 +236,10 @@
<div class="prototype-table-tools">
<div class="prototype-table-tools-left">
- <el-button type="primary" :icon="Plus" @click="openCreate">新建</el-button>
- <el-button :icon="Upload" @click="openImport">批量导入</el-button>
+ <el-button v-permission="'admin.talents.create'" type="primary" :icon="Plus" @click="openCreate">新建</el-button>
+ <el-button v-permission="'admin.talents.import'" :icon="Upload" @click="openImport">批量导入</el-button>
</div>
- <el-button :icon="Download" :loading="exporting" @click="download">下载</el-button>
+ <el-button v-permission="'admin.talents.export'" :icon="Download" :loading="exporting" @click="download">下载</el-button>
</div>
<el-table v-loading="loading" :data="talents" border class="prototype-table" empty-text="暂无持卡人数据">
@@ -267,7 +267,7 @@
<el-table-column label="操作" width="150" fixed="right" align="center">
<template #default="{ row }">
<el-button link type="primary" @click="openDetail(row)">查看</el-button>
- <el-button link type="primary" @click="changeStatus(row)">{{ row.status === 1 ? '停用' : '启用' }}</el-button>
+ <el-button v-permission="'admin.talents.disable'" link type="primary" @click="changeStatus(row)">{{ row.status === 1 ? '停用' : '启用' }}</el-button>
</template>
</el-table-column>
</el-table>
diff --git a/talent-card-admin/src/views/UpgradeApplicationDetailView.vue b/talent-card-admin/src/views/UpgradeApplicationDetailView.vue
index aedde8c..90148ed 100644
--- a/talent-card-admin/src/views/UpgradeApplicationDetailView.vue
+++ b/talent-card-admin/src/views/UpgradeApplicationDetailView.vue
@@ -109,8 +109,8 @@
</section>
<section v-if="detail" class="upgrade-detail-actions">
<template v-if="pending">
- <el-button type="primary" :loading="processing" @click="approve">处理</el-button>
- <el-button type="danger" :loading="processing" @click="reject">审核不通过</el-button>
+ <el-button v-permission="'admin.upgrade-applications.audit'" type="primary" :loading="processing" @click="approve">处理</el-button>
+ <el-button v-permission="'admin.upgrade-applications.audit'" type="danger" :loading="processing" @click="reject">审核不通过</el-button>
</template>
<span v-else>审核结果:{{ text(detail.status) }}<template v-if="detail.auditRemark">({{ detail.auditRemark }})</template></span>
</section>
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementService.java b/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementService.java
index f523d0e..1203c8e 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementService.java
@@ -24,7 +24,7 @@
public List<Map<String, Object>> roles() { return rows("select id, role_name, role_code, remark, permission_codes, status, created_at from admin_role order by id"); }
@Transactional public Map<String, Object> createRole(AdminRoleRequest request) { String code = roleCode(request.roleCode()); jdbc.update("insert into admin_role(role_name,role_code,remark,permission_codes,status) values (?,?,?,?,?)", request.roleName().trim(), code, request.remark(), permissions(request.permissionCodes()), request.status()); return one("select id, role_name, role_code, remark, permission_codes, status, created_at from admin_role where id=last_insert_id()"); }
- @Transactional public Map<String, Object> updateRole(Long id, AdminRoleRequest request) { if ("platform_admin".equals(existingRoleCode(id))) throw new BusinessException(ErrorCode.CONFLICT, "超级管理员角色不可编辑"); String code = request.roleCode() == null || request.roleCode().isBlank() ? existingRoleCode(id) : request.roleCode().trim(); if (jdbc.update("update admin_role set role_name=?,role_code=?,remark=?,permission_codes=?,status=? where id=?", request.roleName().trim(), code, request.remark(), permissions(request.permissionCodes()), request.status(), id) == 0) throw new BusinessException(ErrorCode.NOT_FOUND, "Role does not exist"); return one("select id, role_name, role_code, remark, permission_codes, status, created_at from admin_role where id=?", id); }
+ @Transactional public Map<String, Object> updateRole(Long id, AdminRoleRequest request) { if ("platform_admin".equals(existingRoleCode(id))) throw new BusinessException(ErrorCode.CONFLICT, "超级管理员角色不可编辑"); String oldCode = existingRoleCode(id); String code = request.roleCode() == null || request.roleCode().isBlank() ? oldCode : request.roleCode().trim(); if (jdbc.update("update admin_role set role_name=?,role_code=?,remark=?,permission_codes=?,status=? where id=?", request.roleName().trim(), code, request.remark(), permissions(request.permissionCodes()), request.status(), id) == 0) throw new BusinessException(ErrorCode.NOT_FOUND, "Role does not exist"); jdbc.update("update admin_session s join admin_operator o on o.id=s.operator_id set s.revoked_at=now() where s.revoked_at is null and o.role_code in (?, ?)", oldCode, code); return one("select id, role_name, role_code, remark, permission_codes, status, created_at from admin_role where id=?", id); }
@Transactional public Map<String, Object> updateRoleStatus(Long id, Byte status) { String code = existingRoleCode(id); if (status == 0 && "platform_admin".equals(code)) throw new BusinessException(ErrorCode.CONFLICT, "平台管理员角色不可禁用"); jdbc.update("update admin_role set status=? where id=?", status, id); return one("select id, role_name, role_code, remark, permission_codes, status, created_at from admin_role where id=?", id); }
@Transactional public Map<String, Object> deleteRole(Long id) { String code = existingRoleCode(id); if ("platform_admin".equals(code)) throw new BusinessException(ErrorCode.CONFLICT, "平台管理员角色不可删除"); if (Boolean.TRUE.equals(jdbc.queryForObject("select exists(select 1 from admin_operator where role_code=? and deleted=0)", Boolean.class, code))) throw new BusinessException(ErrorCode.CONFLICT, "该角色仍有员工使用,不能删除"); jdbc.update("delete from admin_role where id=?", id); return Map.of("id", id, "deleted", true); }
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/admin/auth/AdminAuthInterceptor.java b/talent-card-server/src/main/java/com/talentcard/server/business/admin/auth/AdminAuthInterceptor.java
index edf6827..4a712c7 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/admin/auth/AdminAuthInterceptor.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/admin/auth/AdminAuthInterceptor.java
@@ -5,6 +5,8 @@
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.http.HttpStatus;
@Component
public class AdminAuthInterceptor implements HandlerInterceptor {
@@ -22,6 +24,61 @@
Long operatorId = service.authenticate(request.getHeader(HttpHeaders.AUTHORIZATION));
request.setAttribute(OPERATOR_ID_ATTRIBUTE, operatorId);
request.setAttribute(OPERATOR_NAME_ATTRIBUTE, service.operatorName(operatorId));
+ String permission = permission(request.getMethod(), request.getRequestURI());
+ if (!permission.isBlank() && !service.hasPermission(operatorId, permission)) {
+ throw new ResponseStatusException(HttpStatus.FORBIDDEN, "当前账号没有该操作权限");
+ }
return true;
}
+
+ private String permission(String method, String uri) {
+ if (uri.startsWith("/api/admin/management/roles")) {
+ return managementPermission(method, uri, "roles");
+ }
+ if (uri.startsWith("/api/admin/management/staff")) {
+ return managementPermission(method, uri, "staff");
+ }
+ String page = page(uri);
+ if (page.isBlank()) return "";
+ String action = "GET".equalsIgnoreCase(method) ? "view" : "edit";
+ if ("POST".equalsIgnoreCase(method)) action = "create";
+ if ("DELETE".equalsIgnoreCase(method)) action = "delete";
+ if ("PATCH".equalsIgnoreCase(method)) {
+ action = uri.endsWith("/audit") || uri.endsWith("/status") ? (uri.endsWith("/audit") ? "audit" : "disable") : "edit";
+ }
+ return "admin." + page + "." + action;
+ }
+
+ private String page(String uri) {
+ if (uri.startsWith("/api/admin/business/merchants")) return "merchants";
+ if (uri.startsWith("/api/admin/business/products")) return "products";
+ if (uri.startsWith("/api/admin/business/news")) return "news";
+ if (uri.startsWith("/api/admin/business/policies")) return "policies";
+ if (uri.startsWith("/api/admin/business/coupons")) return "benefit-coupons";
+ if (uri.startsWith("/api/admin/dependent-card-applies")) return "family-card-applications";
+ if (uri.startsWith("/api/admin/cards/child-applications")) return "child-cards";
+ if (uri.startsWith("/api/admin/service-appointment-applications")) return "appointment-management";
+ if (uri.startsWith("/api/admin/medical-reservations")) return "medical-reservations";
+ if (uri.startsWith("/api/admin/parking-plate-submissions")) return "parking-plates";
+ if (uri.startsWith("/api/admin/car-wash-benefits") || uri.startsWith("/api/admin/car-wash-merchant-jump-configs")) return "car-wash-benefits";
+ if (uri.startsWith("/api/admin/fuel-services")) return "fuel-services";
+ if (uri.startsWith("/api/admin/spouse-employment")) return "spouse-employment";
+ if (uri.startsWith("/api/admin/hotel-reservations")) return "hotel-reservations";
+ if (uri.startsWith("/api/admin/merchant-settlement")) return "merchant-settlement";
+ if (uri.startsWith("/api/admin/ticket-packages")) return "movie-tickets";
+ if (uri.startsWith("/api/admin/display-services")) return "display-services";
+ if (uri.startsWith("/api/admin/equity-images")) return "equity-images";
+ return "";
+ }
+
+ private String managementPermission(String method, String uri, String page) {
+ String action;
+ if ("GET".equalsIgnoreCase(method)) action = "view";
+ else if ("POST".equalsIgnoreCase(method)) action = "create";
+ else if ("DELETE".equalsIgnoreCase(method)) action = "delete";
+ else if ("PUT".equalsIgnoreCase(method)) action = "edit";
+ else if ("PATCH".equalsIgnoreCase(method)) action = uri.endsWith("/status") ? "disable" : "reset";
+ else return "";
+ return "admin." + page + "." + action;
+ }
}
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/admin/auth/AdminAuthService.java b/talent-card-server/src/main/java/com/talentcard/server/business/admin/auth/AdminAuthService.java
index 71a3848..e3fe181 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/admin/auth/AdminAuthService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/admin/auth/AdminAuthService.java
@@ -109,6 +109,25 @@
return displayName.isBlank() ? "管理员" : displayName;
}
+ @Transactional(readOnly = true)
+ public boolean hasPermission(Long operatorId, String permission) {
+ List<Map<String, Object>> result = rows("""
+ select o.role_code, r.permission_codes
+ from admin_operator o left join admin_role r on r.role_code = o.role_code
+ where o.id = ? and o.deleted = 0
+ limit 1
+ """, operatorId);
+ if (result.isEmpty()) return false;
+ Map<String, Object> operator = result.get(0);
+ if ("platform_admin".equals(String.valueOf(operator.get("roleCode")))) return true;
+ String raw = String.valueOf(operator.getOrDefault("permissionCodes", ""));
+ try {
+ return JSON.parseArray(raw, String.class).contains(permission);
+ } catch (Exception ignored) {
+ return false;
+ }
+ }
+
private Map<String, Object> issueSession(Map<String, Object> operator) {
String accessToken = token();
LocalDateTime now = LocalDateTime.now();
--
Gitblit v1.7.1