From 3f6ad0c6ad5a9cd58bcef918dc9a8af22f9f8307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Sun, 13 Sep 2026 14:00:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=90=8E=E5=8F=B0=E5=88=86=E7=BB=84):=20?= =?UTF-8?q?=E5=88=86=E7=BB=84=E7=AD=9B=E9=80=89=E4=B8=8E=E5=88=86=E7=BB=84?= =?UTF-8?q?=E5=88=97=E4=BB=85=E8=B6=85=E7=AE=A1=E5=8F=AF=E8=A7=81=EF=BC=8C?= =?UTF-8?q?=E9=9D=9E=E8=B6=85=E7=AE=A1=E4=B8=80=E5=BE=8B=E9=9A=90=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 管理员/普通用户数据已由后端裁剪到本人可访问分组,分组维度对其无意义; 账号归属分组是超管职责,故只有超管需要按分组筛选与区分。 - 隐藏分组筛选+分组列:品牌数据库、查询ASIN、最低价ASIN、去重总数据、 店铺管理、用户密钥 - 仅隐藏分组展示:店铺数据记录(筛选框+卡片行)、图片视频任务(所属分组行)、 撞款检测(明细抽屉分组列+卡片分组标签) - 弹窗分组选择保留展示但锁定:非超管仅1个可访问分组时自动选中并置灰 (查询/最低价ASIN 另联动加载该分组店铺),多分组不锁以免限制用户 - 空数据行 colspan 随列显隐动态化 - 新增 tests/align-group-visibility.test.ts 回归守卫(5 条) --- .../src/pages/account/UserSecretsPage.vue | 15 ++- .../src/pages/asin/AsinInvalidPage.vue | 12 +-- .../src/pages/asin/DedupeRegistryPage.vue | 28 ++++-- .../src/pages/asin/QueryAsinPage.vue | 30 ++++-- .../src/pages/asin/SkipPricePage.vue | 30 ++++-- .../src/pages/shop/ShopManagePage.vue | 24 +++-- .../src/pages/tasks/DuplicateCheckPage.vue | 6 +- .../src/pages/tasks/ImageVideoTasksPage.vue | 2 +- .../src/pages/tasks/ShopDataTasksPage.vue | 4 +- .../tests/align-group-visibility.test.ts | 92 +++++++++++++++++++ 10 files changed, 191 insertions(+), 52 deletions(-) create mode 100644 admin-frontend-vue/tests/align-group-visibility.test.ts diff --git a/admin-frontend-vue/src/pages/account/UserSecretsPage.vue b/admin-frontend-vue/src/pages/account/UserSecretsPage.vue index 293725c0..38ce3175 100644 --- a/admin-frontend-vue/src/pages/account/UserSecretsPage.vue +++ b/admin-frontend-vue/src/pages/account/UserSecretsPage.vue @@ -11,6 +11,11 @@ import { type AdminUserSecretRow, type GroupOption, } from '@/api/user-secrets' +import { useAdminSessionStore } from '@/stores/admin-session' + +const session = useAdminSessionStore() +/** 仅超管需要分组维度:非超管数据由后端裁剪到本人分组,分组筛选/列一律不展示。 */ +const isSuperAdmin = computed(() => session.isSuperAdmin) const loading = ref(false) const rows = ref([]) @@ -200,7 +205,7 @@ onMounted(load) -
+
-
+
- +
@@ -199,7 +199,7 @@ onMounted(() => { ID ASIN 品牌 - 分组 + 分组 来源 创建时间 操作 @@ -211,7 +211,7 @@ onMounted(() => { {{ row.id }} {{ row.dataValue }} {{ row.brand || '—' }} - {{ row.groupName || '未分组' }} + {{ row.groupName || '未分组' }} {{ recordSourceLabel(row.recordSource) }} @@ -225,10 +225,10 @@ onMounted(() => { - 加载中... + 加载中... - 暂无数据 + 暂无数据 diff --git a/admin-frontend-vue/src/pages/asin/DedupeRegistryPage.vue b/admin-frontend-vue/src/pages/asin/DedupeRegistryPage.vue index 9041d55b..c1757a4c 100644 --- a/admin-frontend-vue/src/pages/asin/DedupeRegistryPage.vue +++ b/admin-frontend-vue/src/pages/asin/DedupeRegistryPage.vue @@ -24,6 +24,11 @@ import { importOutcomeText, importTaskFinished, isAllowedImportFile } from './de import type { DedupeImportProgress } from './dedupe-import-model.ts' import { toExportUrl } from './dedupe-total-export.ts' import OldPagination from '@/components/OldPagination.vue' +import { useAdminSessionStore } from '@/stores/admin-session' + +const session = useAdminSessionStore() +/** 仅超管需要分组维度:非超管数据由后端裁剪到本人分组,分组筛选/列一律不展示。 */ +const isSuperAdmin = computed(() => session.isSuperAdmin) const loading = ref(false) const rows = ref([]) @@ -32,6 +37,11 @@ const page = ref(1) const pageSize = ref(10) const filter = reactive(createDedupeTotalFilterState()) const groups = ref([]) +/** 非超管仅有一个可访问分组时,编辑/导入弹窗分组选择锁定为该组(保留展示、不可改)。 */ +const lockedGroupId = computed(() => { + if (isSuperAdmin.value) return null + return groups.value.length === 1 ? groups.value[0].id : null +}) const jumpPage = ref('') const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value))) @@ -115,7 +125,7 @@ function changeSize(size: number) { function openEdit(row: DedupeTotalItem): void { editTarget.value = row editValue.value = row.dataValue - editGroupId.value = row.groupId + editGroupId.value = row.groupId ?? lockedGroupId.value editVisible.value = true } @@ -157,7 +167,7 @@ async function removeRow(row: DedupeTotalItem): Promise { } function resetImport(): void { - importGroupId.value = null + importGroupId.value = lockedGroupId.value importFile.value = null importProgress.value = '' importRunning.value = false @@ -306,7 +316,7 @@ onMounted(() => {
-
+
@@ -348,7 +358,7 @@ onMounted(() => { ASIN 国家 用户名 - 分组 + 分组 创建时间 操作 @@ -360,7 +370,7 @@ onMounted(() => { {{ row.dataValue }} {{ asinCountryLabel(row.country || '') }} {{ row.username || '-' }} - {{ row.groupName || '未分组' }} + {{ row.groupName || '未分组' }} {{ formatDateTime(row.createdAt) }} @@ -369,10 +379,10 @@ onMounted(() => { - 加载中... + 加载中... - 暂无总数据 + 暂无总数据 @@ -390,7 +400,7 @@ onMounted(() => {
- @@ -411,7 +421,7 @@ onMounted(() => {
- diff --git a/admin-frontend-vue/src/pages/asin/QueryAsinPage.vue b/admin-frontend-vue/src/pages/asin/QueryAsinPage.vue index 6658bc3c..d51b18bc 100644 --- a/admin-frontend-vue/src/pages/asin/QueryAsinPage.vue +++ b/admin-frontend-vue/src/pages/asin/QueryAsinPage.vue @@ -16,6 +16,11 @@ import { isAllowedExcelImportFile } from './import-progress-model.ts' import { fetchShopManageGroups } from '../shop/shop-manage-api.ts' import type { ShopGroupOption } from '../shop/shop-dto.ts' import OldPagination from '@/components/OldPagination.vue' +import { useAdminSessionStore } from '@/stores/admin-session' + +const session = useAdminSessionStore() +/** 仅超管需要分组维度:非超管数据由后端裁剪到本人分组,分组筛选/列一律不展示。 */ +const isSuperAdmin = computed(() => session.isSuperAdmin) const loading = ref(false) const rows = ref([]) @@ -23,6 +28,11 @@ const total = ref(0) const page = ref(1) const pageSize = ref(10) const groups = ref([]) +/** 非超管仅有一个可访问分组时,弹窗分组选择锁定为该组(保留展示、不可改)。 */ +const lockedGroupId = computed(() => { + if (isSuperAdmin.value) return null + return groups.value.length === 1 ? groups.value[0].id : null +}) const filter = reactive(createQueryAsinFilterState()) const jumpPage = ref('') const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value))) @@ -115,7 +125,7 @@ const creating = ref(false) const createAsinInputRef = ref | null>(null) function openCreate(): void { - createGroupId.value = null + createGroupId.value = lockedGroupId.value createShopName.value = '' createCountry.value = '' createAsin.value = '' @@ -123,6 +133,8 @@ function openCreate(): void { createMsgOk.value = false shopNames.value = [] createVisible.value = true + // 非超管分组已锁定:直接联动加载该分组店铺,省去一次无意义的选择。 + if (createGroupId.value != null) void onCreateGroupChange() } async function onCreateGroupChange(): Promise { @@ -189,7 +201,7 @@ const importRunning = ref(false) const importProgress = ref('') function resetImport(): void { - importGroupId.value = null + importGroupId.value = lockedGroupId.value importFile.value = null importProgress.value = '' importRunning.value = false @@ -348,7 +360,7 @@ onMounted(() => {
-
+
@@ -378,7 +390,7 @@ onMounted(() => { 序号 - 分组 + 分组 店铺名 ASIN 国家 @@ -390,7 +402,7 @@ onMounted(() => { @@ -409,10 +421,10 @@ onMounted(() => { - 加载中... + 加载中... - 暂无数据 + 暂无数据 @@ -423,7 +435,7 @@ onMounted(() => { - + @@ -475,7 +487,7 @@ onMounted(() => { - + diff --git a/admin-frontend-vue/src/pages/asin/SkipPricePage.vue b/admin-frontend-vue/src/pages/asin/SkipPricePage.vue index 48c1c0e1..012c8220 100644 --- a/admin-frontend-vue/src/pages/asin/SkipPricePage.vue +++ b/admin-frontend-vue/src/pages/asin/SkipPricePage.vue @@ -15,6 +15,11 @@ import { fetchShopNamesByGroup } from './query-asin-api.ts' import { fetchShopManageGroups } from '../shop/shop-manage-api.ts' import type { ShopGroupOption } from '../shop/shop-dto.ts' import OldPagination from '@/components/OldPagination.vue' +import { useAdminSessionStore } from '@/stores/admin-session' + +const session = useAdminSessionStore() +/** 仅超管需要分组维度:非超管数据由后端裁剪到本人分组,分组筛选/列一律不展示。 */ +const isSuperAdmin = computed(() => session.isSuperAdmin) const loading = ref(false) const rows = ref([]) @@ -22,6 +27,11 @@ const total = ref(0) const page = ref(1) const pageSize = ref(10) const groups = ref([]) +/** 非超管仅有一个可访问分组时,弹窗分组选择锁定为该组(保留展示、不可改)。 */ +const lockedGroupId = computed(() => { + if (isSuperAdmin.value) return null + return groups.value.length === 1 ? groups.value[0].id : null +}) const filter = reactive(createSkipPriceFilterState()) const jumpPage = ref('') const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value))) @@ -57,7 +67,7 @@ const creating = ref(false) const createAsinInputRef = ref | null>(null) function openCreate(): void { - createGroupId.value = null + createGroupId.value = lockedGroupId.value createShopName.value = '' createCountry.value = '' createAsin.value = '' @@ -66,6 +76,8 @@ function openCreate(): void { createMsgOk.value = false shopNames.value = [] createVisible.value = true + // 非超管分组已锁定:直接联动加载该分组店铺,省去一次无意义的选择。 + if (createGroupId.value != null) void onCreateGroupChange() } async function onCreateGroupChange(): Promise { @@ -238,7 +250,7 @@ const importRunning = ref(false) const importProgress = ref('') function resetImport(): void { - importGroupId.value = null + importGroupId.value = lockedGroupId.value importFile.value = null importProgress.value = '' importRunning.value = false @@ -392,7 +404,7 @@ onMounted(() => {
-
+
@@ -430,7 +442,7 @@ onMounted(() => { 序号 - 分组 + 分组 店铺名 ASIN 国家 @@ -443,7 +455,7 @@ onMounted(() => { @@ -463,10 +475,10 @@ onMounted(() => { - 加载中... + 加载中... - 暂无数据 + 暂无数据 @@ -477,7 +489,7 @@ onMounted(() => { - + @@ -535,7 +547,7 @@ onMounted(() => { - + diff --git a/admin-frontend-vue/src/pages/shop/ShopManagePage.vue b/admin-frontend-vue/src/pages/shop/ShopManagePage.vue index ff098bbc..5faec1ec 100644 --- a/admin-frontend-vue/src/pages/shop/ShopManagePage.vue +++ b/admin-frontend-vue/src/pages/shop/ShopManagePage.vue @@ -18,6 +18,8 @@ import type { ShopCredential, ShopGroupOption, ShopSummary } from './shop-dto.ts import OldPagination from '@/components/OldPagination.vue' const session = useAdminSessionStore() +/** 仅超管需要分组维度:非超管数据由后端裁剪到本人分组,分组筛选/列一律不展示。 */ +const isSuperAdmin = computed(() => session.isSuperAdmin) const loading = ref(false) const rows = ref([]) const total = ref(0) @@ -42,6 +44,11 @@ const revealedPasswordCache = ref>({}) const revealedRowId = ref(null) const groupOptions = computed(() => groups.value) +/** 非超管仅有一个可访问分组时,弹窗分组选择锁定为该组(保留展示、不可改)。 */ +const lockedGroupId = computed(() => { + if (isSuperAdmin.value) return null + return groups.value.length === 1 ? groups.value[0].id : null +}) function shownPassword(row: ShopSummary): string { if (revealedRowId.value !== row.id) return row.passwordMasked || '******' @@ -123,13 +130,14 @@ function goJump() { function openCreate() { editingId.value = null Object.assign(form, emptyShopManageForm()) + if (lockedGroupId.value != null) form.groupId = lockedGroupId.value dialogVisible.value = true } function openEdit(row: ShopSummary) { editingId.value = row.id Object.assign(form, { - groupId: row.groupId, + groupId: row.groupId ?? lockedGroupId.value, shopName: row.shopName, mallName: row.mallName, znUsername: row.znUsername, @@ -214,7 +222,7 @@ onMounted(() => {
-
+
@@ -232,7 +240,7 @@ onMounted(() => { 序号 - 分组 + 分组 店铺名 店铺商城名 自动化账号 @@ -247,7 +255,7 @@ onMounted(() => { - 加载中... + 加载中... - 暂无店铺 + 暂无店铺 @@ -287,7 +295,7 @@ onMounted(() => { - + @@ -316,7 +324,7 @@ onMounted(() => { {{ credential.shopName }} - {{ credential.groupName || '—' }} + {{ credential.groupName || '—' }} {{ credential.mallName || '—' }} {{ credential.account }} diff --git a/admin-frontend-vue/src/pages/tasks/DuplicateCheckPage.vue b/admin-frontend-vue/src/pages/tasks/DuplicateCheckPage.vue index f64e3b94..263fa9b4 100644 --- a/admin-frontend-vue/src/pages/tasks/DuplicateCheckPage.vue +++ b/admin-frontend-vue/src/pages/tasks/DuplicateCheckPage.vue @@ -588,7 +588,7 @@ onMounted(() => {
{{ block.shopName }}{{ block.count }} 条
-
分组:{{ block.groupName }}
+
分组:{{ block.groupName }}