align(品牌数据库): 非超管分组强制锁定(筛选/新增/编辑下拉禁用并锁本人分组、重置保持锁定),超管全量放开(对齐 admin.js getInvalidAsinDataLockedGroupId/refreshShopGroupSelects)
This commit is contained in:
@@ -1,15 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { formatDateTime } from '@/utils/datetime'
|
import { formatDateTime } from '@/utils/datetime'
|
||||||
/** 不符合 ASIN(品牌数据库):分页 + 手动新增/编辑/删除。 */
|
/** 不符合 ASIN(品牌数据库):分页 + 手动新增/编辑/删除。 */
|
||||||
import { onMounted, reactive, ref } from 'vue'
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { useAdminSessionStore } from '@/stores/admin-session'
|
||||||
import { createInvalidAsin, deleteInvalidAsin, fetchInvalidAsinPage, updateInvalidAsin } from '../invalidasin/invalid-asin-api.ts'
|
import { createInvalidAsin, deleteInvalidAsin, fetchInvalidAsinPage, updateInvalidAsin } from '../invalidasin/invalid-asin-api.ts'
|
||||||
import type { InvalidAsinItem } from '../invalidasin/invalid-asin-model.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 { createEmptyInvalidAsinForm, invalidAsinFormFromItem, validateInvalidAsinForm } from '../invalidasin/invalid-asin-form-model.ts'
|
||||||
import { fetchShopManageGroups } from '../shop/shop-manage-api.ts'
|
import { fetchShopManageGroups } from '../shop/shop-manage-api.ts'
|
||||||
import type { ShopGroupOption } from '../shop/shop-dto.ts'
|
import type { ShopGroupOption } from '../shop/shop-dto.ts'
|
||||||
|
|
||||||
|
const session = useAdminSessionStore()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const rows = ref<InvalidAsinItem[]>([])
|
const rows = ref<InvalidAsinItem[]>([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
@@ -26,12 +28,26 @@ const editingSource = ref('')
|
|||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const form = reactive(createEmptyInvalidAsinForm())
|
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() {
|
async function loadGroups() {
|
||||||
try {
|
try {
|
||||||
groups.value = await fetchShopManageGroups()
|
groups.value = await fetchShopManageGroups()
|
||||||
} catch {
|
} catch {
|
||||||
groups.value = []
|
groups.value = []
|
||||||
}
|
}
|
||||||
|
// 非超管:筛选强制锁定到本人分组(与参考 refreshShopGroupSelects 的锁定语义一致)。
|
||||||
|
if (!isSuperAdmin.value && lockedGroupId.value != null) {
|
||||||
|
filter.groupId = lockedGroupId.value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
@@ -62,7 +78,8 @@ function apply() {
|
|||||||
function reset() {
|
function reset() {
|
||||||
filter.dataValue = ''
|
filter.dataValue = ''
|
||||||
filter.brand = ''
|
filter.brand = ''
|
||||||
filter.groupId = null
|
// 非超管重置不解除分组锁定。
|
||||||
|
filter.groupId = isSuperAdmin.value ? null : lockedGroupId.value
|
||||||
page.value = 1
|
page.value = 1
|
||||||
load()
|
load()
|
||||||
}
|
}
|
||||||
@@ -71,6 +88,10 @@ function openCreate() {
|
|||||||
editingId.value = null
|
editingId.value = null
|
||||||
editingSource.value = ''
|
editingSource.value = ''
|
||||||
Object.assign(form, createEmptyInvalidAsinForm())
|
Object.assign(form, createEmptyInvalidAsinForm())
|
||||||
|
// 非超管新增预置锁定分组(下拉同时被禁用)。
|
||||||
|
if (!isSuperAdmin.value && lockedGroupId.value != null) {
|
||||||
|
form.groupId = lockedGroupId.value
|
||||||
|
}
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,8 +170,8 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="f-item">
|
<div class="f-item">
|
||||||
<label>分组</label>
|
<label>分组</label>
|
||||||
<el-select v-model="filter.groupId" placeholder="全部分组" clearable>
|
<el-select v-model="filter.groupId" placeholder="全部分组" clearable :disabled="!isSuperAdmin">
|
||||||
<el-option v-for="group in groups" :key="group.id" :label="group.groupName" :value="group.id" />
|
<el-option v-for="group in groupOptions" :key="group.id" :label="group.groupName" :value="group.id" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
<div class="f-item btn-row">
|
<div class="f-item btn-row">
|
||||||
@@ -199,8 +220,8 @@ onMounted(() => {
|
|||||||
<el-input v-model="form.brand" placeholder="可选" />
|
<el-input v-model="form.brand" placeholder="可选" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="editingId == null || editingSource.toUpperCase() !== 'AUTO'" label="分组" required>
|
<el-form-item v-if="editingId == null || editingSource.toUpperCase() !== 'AUTO'" label="分组" required>
|
||||||
<el-select v-model="form.groupId" placeholder="选择分组" filterable>
|
<el-select v-model="form.groupId" placeholder="选择分组" filterable :disabled="!isSuperAdmin">
|
||||||
<el-option v-for="group in groups" :key="group.id" :label="group.groupName" :value="group.id" />
|
<el-option v-for="group in groupOptions" :key="group.id" :label="group.groupName" :value="group.id" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|||||||
@@ -44,6 +44,20 @@ export function invalidAsinDeleteText(item: Pick<InvalidAsinItem, 'dataValue'> |
|
|||||||
return `确定删除不符合 ASIN“${value}”吗?删除后不可恢复。`
|
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 视为无效。 */
|
/** 解析单条品牌数据库项;缺 id 视为无效。 */
|
||||||
export function toInvalidAsinItem(raw: unknown): InvalidAsinItem | null {
|
export function toInvalidAsinItem(raw: unknown): InvalidAsinItem | null {
|
||||||
if (!raw || typeof raw !== 'object') return 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/, '锁定组参与筛选/新增预置')
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user