align(撞款): 店铺数据重复按登录角色区分范围——超管看全分组、管理员只看本组

撞款控制台角色收口:前端读会话角色(isSuperAdmin),非超管隐藏筛选「分组」下拉、
「分组撞款」分段与「分组说明」按钮(分组专属交互仅超管需要);普通管理员数据已由
后端 resolveVisibleShopKeys 裁剪到所管分组店铺(超管 null=全量)。前端 1554 测试全绿 + build 通过。
This commit is contained in:
2026-09-06 15:21:26 +08:00
parent 191e5f9102
commit 8034d916bc
2 changed files with 27 additions and 3 deletions
@@ -5,6 +5,7 @@
* 分组归属来自「店铺管理」真实店铺分组,本页只读展示,仅用于分组撞款范围切换。
*/
import { computed, onMounted, reactive, ref } from 'vue'
import { useAdminSessionStore } from '@/stores/admin-session'
import type { DuplicateItem, DuplicateOccurrence, DuplicateMetrics, DuplicateShop } from './duplicate-model.ts'
import { fetchDuplicateConsole, fetchDuplicateLedger, requestDuplicateExportBlob, type LedgerSortKey } from './duplicate-console-api.ts'
import { aggregateDrawerRows, drawerSummaryOf, dupWithinGroup, indexShopGroups, type DrawerAggRow, type DrawerSummary, type ShopGroups } from './duplicate-console-logic.ts'
@@ -21,6 +22,10 @@ const reanalyzing = ref(false)
const filter = reactive({ asin: '', shop: '', group: '', country: '', dateFrom: '', dateTo: '' })
/** 角色范围:超管可看全部分组店铺并可跨分组筛选;普通管理员数据已由后端裁剪到自己分组,无需分组筛选。 */
const session = useAdminSessionStore()
const isSuper = computed(() => session.isSuperAdmin)
const activeTab = ref<Tab>('dup')
const scope = ref<Scope>('global')
const selectedGroup = ref('')
@@ -393,7 +398,7 @@ onMounted(() => {
<option v-for="shop in orderedShops" :key="shop.shopName" :value="shop.shopName">{{ shop.shopName }}</option>
</select>
</div>
<div class="f-group">
<div v-if="isSuper" class="f-group">
<label>分组</label>
<select v-model="filter.group">
<option value="">全部分组</option>
@@ -466,7 +471,7 @@ onMounted(() => {
</div>
<div class="actions">
<span v-if="overview" class="updated">数据更新时间 {{ overview.scannedAt || '' }}</span>
<button class="btn btn-ghost btn-sm" type="button" @click="groupInfoVisible = true">
<button v-if="isSuper" class="btn btn-ghost btn-sm" type="button" @click="groupInfoVisible = true">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M23 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg>
分组说明
</button>
@@ -483,7 +488,7 @@ onMounted(() => {
<!-- ============ 撞款监控 ============ -->
<template v-if="activeTab === 'dup'">
<div class="scope-bar">
<div v-if="isSuper" class="scope-bar">
<div class="seg">
<button class="seg-btn" :class="{ active: scope === 'global' }" type="button" @click="switchScope('global')">全店撞款</button>
<button class="seg-btn" :class="{ active: scope === 'group' }" type="button" @click="switchScope('group')">分组撞款</button>
@@ -56,3 +56,22 @@ test('align_dup_console_matrix_sticky', () => {
assert.match(page, /table\.matrix th\s*\{[^}]*position:\s*sticky/, '矩阵表头吸顶')
assert.match(page, /\.matrix-wrap\s*\{[^}]*max-height/, '矩阵容器限高滚动')
})
test('align_dup_console_role_scope_group_filter_only_super', () => {
// 角色范围:分组筛选下拉仅超管需要;管理员数据由后端裁到本组。
const page = readSource('src/pages/tasks/DuplicateCheckPage.vue')
assert.match(page, /useAdminSessionStore/, '页面读取登录会话取角色')
assert.match(page, /const isSuper = computed\(\(\) => session\.isSuperAdmin\)/, 'isSuper 由会话角色推导')
assert.match(page, /<div v-if="isSuper" class="f-group">\s*<label><\/label>/s, '')
assert.match(page, /group: filter\.group\.trim\(\)/, '分组筛选仍参与查询参数')
})
test('align_dup_console_role_scope_group_controls_only_super', () => {
// 分组撞款/分组说明为超管专属交互;后端超管全量、普通管理员所管分组店铺。
const page = readSource('src/pages/tasks/DuplicateCheckPage.vue')
assert.match(page, /<div v-if="isSuper" class="scope-bar">/, '分组撞款分段仅超管可见')
assert.match(page, /v-if="isSuper" class="btn btn-ghost btn-sm" type="button" @click="groupInfoVisible = true"/, '分组说明按钮仅超管可见')
const service = readSource('../backend-java/src/main/java/com/nanri/aiimage/modules/shopduplicatecheck/service/ShopDataDuplicateCheckQueryService.java')
assert.match(service, /if \(superAdmin\) \{\s*return null;/, '超管可见范围=全量(null)')
assert.match(service, /selectManagedShopNames\(operatorId\)/, '普通管理员可见范围=所管分组店铺')
})