align(品牌数据库): 非超管分组强制锁定(筛选/新增/编辑下拉禁用并锁本人分组、重置保持锁定),超管全量放开(对齐 admin.js getInvalidAsinDataLockedGroupId/refreshShopGroupSelects)

This commit is contained in:
2026-09-05 23:06:28 +08:00
parent 32ef9a2ebb
commit 2f4bb834cf
3 changed files with 80 additions and 7 deletions
@@ -1,15 +1,17 @@
<script setup lang="ts">
import { formatDateTime } from '@/utils/datetime'
/** 不符合 ASIN(品牌数据库):分页 + 手动新增/编辑/删除。 */
import { onMounted, reactive, ref } from 'vue'
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useAdminSessionStore } from '@/stores/admin-session'
import { createInvalidAsin, deleteInvalidAsin, fetchInvalidAsinPage, updateInvalidAsin } from '../invalidasin/invalid-asin-api.ts'
import type { InvalidAsinItem } from '../invalidasin/invalid-asin-model.ts'
import { invalidAsinDeleteText, recordSourceLabel } from '../invalidasin/invalid-asin-model.ts'
import { invalidAsinDeleteText, invalidAsinLockedGroupId, recordSourceLabel } from '../invalidasin/invalid-asin-model.ts'
import { createEmptyInvalidAsinForm, invalidAsinFormFromItem, validateInvalidAsinForm } from '../invalidasin/invalid-asin-form-model.ts'
import { fetchShopManageGroups } from '../shop/shop-manage-api.ts'
import type { ShopGroupOption } from '../shop/shop-dto.ts'
const session = useAdminSessionStore()
const loading = ref(false)
const rows = ref<InvalidAsinItem[]>([])
const total = ref(0)
@@ -26,12 +28,26 @@ const editingSource = ref('')
const saving = ref(false)
const form = reactive(createEmptyInvalidAsinForm())
const isSuperAdmin = computed(() => session.isSuperAdmin)
/** 非超管锁定的分组 id(对齐 admin.js getInvalidAsinDataLockedGroupId);超管为 null。 */
const lockedGroupId = computed(() => invalidAsinLockedGroupId(groups.value, null, session.user?.role || ''))
/** 分组下拉可用选项:超管全量,非超管仅锁定分组。 */
const groupOptions = computed<ShopGroupOption[]>(() => {
if (isSuperAdmin.value) return groups.value
const locked = lockedGroupId.value
return locked == null ? [] : groups.value.filter((group) => group.id === locked)
})
async function loadGroups() {
try {
groups.value = await fetchShopManageGroups()
} catch {
groups.value = []
}
// 非超管:筛选强制锁定到本人分组(与参考 refreshShopGroupSelects 的锁定语义一致)。
if (!isSuperAdmin.value && lockedGroupId.value != null) {
filter.groupId = lockedGroupId.value
}
}
async function load() {
@@ -62,7 +78,8 @@ function apply() {
function reset() {
filter.dataValue = ''
filter.brand = ''
filter.groupId = null
// 非超管重置不解除分组锁定。
filter.groupId = isSuperAdmin.value ? null : lockedGroupId.value
page.value = 1
load()
}
@@ -71,6 +88,10 @@ function openCreate() {
editingId.value = null
editingSource.value = ''
Object.assign(form, createEmptyInvalidAsinForm())
// 非超管新增预置锁定分组(下拉同时被禁用)。
if (!isSuperAdmin.value && lockedGroupId.value != null) {
form.groupId = lockedGroupId.value
}
dialogVisible.value = true
}
@@ -149,8 +170,8 @@ onMounted(() => {
</div>
<div class="f-item">
<label>分组</label>
<el-select v-model="filter.groupId" placeholder="全部分组" clearable>
<el-option v-for="group in groups" :key="group.id" :label="group.groupName" :value="group.id" />
<el-select v-model="filter.groupId" placeholder="全部分组" clearable :disabled="!isSuperAdmin">
<el-option v-for="group in groupOptions" :key="group.id" :label="group.groupName" :value="group.id" />
</el-select>
</div>
<div class="f-item btn-row">
@@ -199,8 +220,8 @@ onMounted(() => {
<el-input v-model="form.brand" placeholder="可选" />
</el-form-item>
<el-form-item v-if="editingId == null || editingSource.toUpperCase() !== 'AUTO'" label="分组" required>
<el-select v-model="form.groupId" placeholder="选择分组" filterable>
<el-option v-for="group in groups" :key="group.id" :label="group.groupName" :value="group.id" />
<el-select v-model="form.groupId" placeholder="选择分组" filterable :disabled="!isSuperAdmin">
<el-option v-for="group in groupOptions" :key="group.id" :label="group.groupName" :value="group.id" />
</el-select>
</el-form-item>
</el-form>
@@ -44,6 +44,20 @@ export function invalidAsinDeleteText(item: Pick<InvalidAsinItem, 'dataValue'> |
return `确定删除不符合 ASIN“${value}”吗?删除后不可恢复。`
}
/**
* 非超管分组锁定(对齐 admin.js getInvalidAsinDataLockedGroupId:超管不锁;
* 非超管锁 lockedGroupId,缺失时退化为第一个分组;无分组返回 null)。
*/
export function invalidAsinLockedGroupId(
groups: Array<{ id: number }>,
lockedGroupId: number | null | undefined,
operatorRole: string,
): number | null {
if ((operatorRole || '').toLowerCase() === 'super_admin') return null
if (lockedGroupId != null && groups.some((group) => group.id === lockedGroupId)) return lockedGroupId
return groups.length ? groups[0].id : null
}
/** 解析单条品牌数据库项;缺 id 视为无效。 */
export function toInvalidAsinItem(raw: unknown): InvalidAsinItem | null {
if (!raw || typeof raw !== 'object') return null
@@ -0,0 +1,38 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { readSource } from './helpers.ts'
import { invalidAsinLockedGroupId } from '../src/pages/invalidasin/invalid-asin-model.ts'
/** 对齐 admin.js:4200-4204,4246-4272:非超管品牌数据库分组强制锁定到本人分组。 */
const GROUPS = [{ id: 3, groupName: 'A组' }, { id: 5, groupName: 'B组' }]
test('align_invalid_asin_lock_super_admin_never_locked', () => {
assert.equal(invalidAsinLockedGroupId(GROUPS, 3, 'super_admin'), null)
})
test('align_invalid_asin_lock_uses_locked_group_id_when_present', () => {
assert.equal(invalidAsinLockedGroupId(GROUPS, 5, 'admin'), 5)
})
test('align_invalid_asin_lock_falls_back_to_first_group', () => {
assert.equal(invalidAsinLockedGroupId(GROUPS, null, 'admin'), 3)
assert.equal(invalidAsinLockedGroupId(GROUPS, 99, 'admin'), 3)
})
test('align_invalid_asin_lock_empty_groups_returns_null', () => {
assert.equal(invalidAsinLockedGroupId([], null, 'admin'), null)
})
test('align_invalid_asin_lock_normal_role_also_locked', () => {
assert.equal(invalidAsinLockedGroupId(GROUPS, null, 'normal'), 3)
})
test('align_invalid_asin_page_lock_wiring', () => {
// UI 接线:筛选/新增/编辑分组下拉非超管禁用并锁定;筛选默认锁组;重置保持锁定。
const page = readSource('src/pages/asin/AsinInvalidPage.vue')
assert.match(page, /invalidAsinLockedGroupId/, '锁定分组走纯模型')
assert.match(page, /isSuperAdmin|session\.isSuperAdmin/, '按当前角色判定是否锁定')
assert.match(page, /:disabled/, '分组下拉支持禁用')
assert.match(page, /lockedGroupId/, '锁定组参与筛选/新增预置')
})