优化
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { isLocalFullAccessHost, useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { useAuthStore } from '@/core/auth/auth'
|
||||
|
||||
describe('server access store', () => {
|
||||
beforeEach(() => setActivePinia(createPinia()))
|
||||
beforeEach(() => {
|
||||
window.localStorage.clear()
|
||||
setActivePinia(createPinia())
|
||||
})
|
||||
|
||||
it('uses only the authenticated permission snapshot', () => {
|
||||
const auth = useAuthStore()
|
||||
@@ -24,4 +27,29 @@ describe('server access store', () => {
|
||||
expect(access.filteredNavigation).toHaveLength(1)
|
||||
expect(access.firstAccessiblePath).toBe('/dashboard')
|
||||
})
|
||||
|
||||
it('allows every frontend permission only while the localhost control is enabled', () => {
|
||||
const access = useAccessPreviewStore()
|
||||
|
||||
expect(access.localFullAccessAvailable).toBe(true)
|
||||
expect(access.can('platform:anything:create')).toBe(false)
|
||||
|
||||
access.setLocalFullAccessEnabled(true)
|
||||
|
||||
expect(access.can('platform:anything:create')).toBe(true)
|
||||
expect(access.permissionCodes.has('platform:anything:edit')).toBe(true)
|
||||
expect(access.filteredNavigation.length).toBeGreaterThan(1)
|
||||
expect(window.localStorage.getItem('baiyue-admin-local-full-access')).toBe('true')
|
||||
|
||||
access.setLocalFullAccessEnabled(false)
|
||||
expect(access.can('platform:anything:create')).toBe(false)
|
||||
expect(window.localStorage.getItem('baiyue-admin-local-full-access')).toBeNull()
|
||||
})
|
||||
|
||||
it('recognizes only local preview hosts', () => {
|
||||
expect(isLocalFullAccessHost('localhost')).toBe(true)
|
||||
expect(isLocalFullAccessHost('127.0.0.1')).toBe(true)
|
||||
expect(isLocalFullAccessHost('::1')).toBe(true)
|
||||
expect(isLocalFullAccessHost('admin.example.com')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,11 +7,38 @@ import {
|
||||
} from '@/core/navigation/adminNavigation'
|
||||
import { useAuthStore } from '@/core/auth/auth'
|
||||
|
||||
const LOCAL_FULL_ACCESS_STORAGE_KEY = 'baiyue-admin-local-full-access'
|
||||
|
||||
class LocalFullAccessPermissionSet extends Set<string> {
|
||||
override has(_permissionCode: string): boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export function isLocalFullAccessHost(hostname: string): boolean {
|
||||
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1'
|
||||
}
|
||||
|
||||
export const useAccessPreviewStore = defineStore('access-preview', () => {
|
||||
const auth = useAuthStore()
|
||||
const initialized = ref(false)
|
||||
const permissionCodes = computed<ReadonlySet<string>>(() => auth.permissionCodes)
|
||||
const permissionSignature = computed(() => [...permissionCodes.value].sort().join('|'))
|
||||
const localFullAccessAvailable = import.meta.env.DEV
|
||||
&& typeof window !== 'undefined'
|
||||
&& isLocalFullAccessHost(window.location.hostname)
|
||||
const localFullAccessEnabled = ref(
|
||||
localFullAccessAvailable
|
||||
&& window.localStorage.getItem(LOCAL_FULL_ACCESS_STORAGE_KEY) === 'true',
|
||||
)
|
||||
const permissionCodes = computed<ReadonlySet<string>>(() => (
|
||||
localFullAccessEnabled.value
|
||||
? new LocalFullAccessPermissionSet(auth.permissionCodes)
|
||||
: auth.permissionCodes
|
||||
))
|
||||
const permissionSignature = computed(() => (
|
||||
localFullAccessEnabled.value
|
||||
? '__LOCAL_FULL_ACCESS__'
|
||||
: [...permissionCodes.value].sort().join('|')
|
||||
))
|
||||
const filteredNavigation = computed(() => filterAdminNavigation(adminNavigation, permissionCodes.value))
|
||||
const firstAccessiblePath = computed(() => getFirstNavigationLeaf(filteredNavigation.value)?.path ?? null)
|
||||
|
||||
@@ -23,18 +50,33 @@ export const useAccessPreviewStore = defineStore('access-preview', () => {
|
||||
return permissionCodes.value.has(permissionCode)
|
||||
}
|
||||
|
||||
function setLocalFullAccessEnabled(enabled: boolean) {
|
||||
if (!localFullAccessAvailable) return
|
||||
localFullAccessEnabled.value = enabled
|
||||
if (enabled) window.localStorage.setItem(LOCAL_FULL_ACCESS_STORAGE_KEY, 'true')
|
||||
else window.localStorage.removeItem(LOCAL_FULL_ACCESS_STORAGE_KEY)
|
||||
}
|
||||
|
||||
function toggleLocalFullAccess() {
|
||||
setLocalFullAccessEnabled(!localFullAccessEnabled.value)
|
||||
}
|
||||
|
||||
function reset() {
|
||||
initialized.value = false
|
||||
}
|
||||
|
||||
return {
|
||||
initialized,
|
||||
localFullAccessAvailable,
|
||||
localFullAccessEnabled,
|
||||
permissionCodes,
|
||||
permissionSignature,
|
||||
filteredNavigation,
|
||||
firstAccessiblePath,
|
||||
initializeAfterLogin,
|
||||
can,
|
||||
setLocalFullAccessEnabled,
|
||||
toggleLocalFullAccess,
|
||||
reset,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('adminNavigation', () => {
|
||||
'供应商中心',
|
||||
'货柜管理',
|
||||
'海外业务',
|
||||
'海外仓库存',
|
||||
'海外仓',
|
||||
'财务管理',
|
||||
'销售管理',
|
||||
'通用',
|
||||
@@ -49,19 +49,20 @@ describe('adminNavigation', () => {
|
||||
'装柜清单',
|
||||
])
|
||||
expect(adminNavigation.find((item) => item.key === 'overseas-inventory')?.children?.map((item) => item.title)).toEqual([
|
||||
'尾程订单',
|
||||
'入库单',
|
||||
'换标单',
|
||||
'出库单',
|
||||
'托盘管理',
|
||||
'箱号管理',
|
||||
'产品库',
|
||||
'退货库存',
|
||||
'暂扣库存',
|
||||
'库存管理',
|
||||
])
|
||||
expect(adminNavigation.find((item) => item.key === 'general')?.children?.map((item) => item.title)).toEqual([
|
||||
'产品信息',
|
||||
'费用项目',
|
||||
'汇率管理',
|
||||
'仓库管理',
|
||||
'海外仓报价',
|
||||
'船期价格表',
|
||||
'车辆列表',
|
||||
'下载中心',
|
||||
@@ -137,6 +138,42 @@ describe('adminNavigation', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('shows only the confirmed phase-four pages with their list permissions', () => {
|
||||
const filtered = filterAdminNavigation(adminNavigation, new Set([
|
||||
'platform:overseas-warehouse:last-mile-order:list',
|
||||
'platform:overseas-warehouse:inbound-order:list',
|
||||
'platform:overseas-warehouse:relabel-order:list',
|
||||
'platform:overseas-warehouse:pallet:list',
|
||||
'platform:overseas-warehouse:carton:list',
|
||||
'platform:overseas-warehouse:product:list',
|
||||
'platform:overseas-warehouse:inventory:list',
|
||||
'platform:overseas-business:purchase:list',
|
||||
'platform:overseas-business:truck:list',
|
||||
'platform:overseas-business:looseboard:list',
|
||||
'platform:overseas-master-data:exchange-rate:list',
|
||||
'platform:overseas-master-data:quote:list',
|
||||
'platform:overseas-master-data:vehicle:list',
|
||||
]))
|
||||
const paths = getNavigationLeaves(filtered).map((item) => item.path)
|
||||
|
||||
expect(paths).toEqual(expect.arrayContaining([
|
||||
'/overseas-warehouse/last-mile-orders',
|
||||
'/overseas-warehouse/inbound-orders',
|
||||
'/overseas-warehouse/relabel-orders',
|
||||
'/overseas-warehouse/pallets',
|
||||
'/overseas-warehouse/cartons',
|
||||
'/overseas-warehouse/products',
|
||||
'/overseas-warehouse/inventory',
|
||||
'/overseas-business/purchases',
|
||||
'/overseas-business/trucks',
|
||||
'/overseas-business/looseboards',
|
||||
'/overseas-master-data/exchange-rates',
|
||||
'/overseas-master-data/quotes',
|
||||
'/overseas-master-data/vehicles',
|
||||
]))
|
||||
expect(paths.some((path) => path.includes('outbound') || path.includes('delivery'))).toBe(false)
|
||||
})
|
||||
|
||||
it('filters protected pages without mutating the source tree', () => {
|
||||
const sourceSnapshot = JSON.stringify(adminNavigation, (key, value) => key === 'icon' ? '[icon]' : value)
|
||||
const filtered = filterAdminNavigation(adminNavigation, new Set(['platform:permission:list']))
|
||||
|
||||
@@ -110,27 +110,24 @@ export const adminNavigation: AdminNavigationNode[] = [
|
||||
key: 'overseas-business',
|
||||
title: '海外业务',
|
||||
icon: PrototypeOverseasBusinessIcon,
|
||||
hidden: true,
|
||||
children: [
|
||||
{ key: 'overseas-purchase', title: '采购订单', icon: ClipboardList, path: '/prototype/overseas-business/purchase-orders' },
|
||||
{ key: 'overseas-truck', title: '本土卡车', icon: Truck, path: '/prototype/overseas-business/local-truck' },
|
||||
{ key: 'overseas-looseboard', title: '本土散板', icon: ClipboardList, path: '/prototype/overseas-business/local-looseboard' },
|
||||
{ key: 'overseas-purchase', title: '采购订单', icon: ClipboardList, path: '/overseas-business/purchases', permission: 'platform:overseas-business:purchase:list' },
|
||||
{ key: 'overseas-truck', title: '本土卡车', icon: Truck, path: '/overseas-business/trucks', permission: 'platform:overseas-business:truck:list' },
|
||||
{ key: 'overseas-looseboard', title: '本土散板', icon: ClipboardList, path: '/overseas-business/looseboards', permission: 'platform:overseas-business:looseboard:list' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'overseas-inventory',
|
||||
title: '海外仓库存',
|
||||
title: '海外仓',
|
||||
icon: PrototypeOverseasWarehouseIcon,
|
||||
hidden: true,
|
||||
children: [
|
||||
{ key: 'fba-inbound-order', title: '入库单', icon: ClipboardList, path: '/prototype/fba-returns/inbound-orders' },
|
||||
{ key: 'fba-relabel-order', title: '换标单', icon: ClipboardList, path: '/prototype/fba-returns/relabel-orders' },
|
||||
{ key: 'fba-outbound-order', title: '出库单', icon: ClipboardList, path: '/prototype/fba-returns/outbound-orders' },
|
||||
{ key: 'fba-pallet', title: '托盘管理', icon: WarehouseIcon, path: '/prototype/fba-returns/pallet-management' },
|
||||
{ key: 'fba-carton', title: '箱号管理', icon: ClipboardList, path: '/prototype/fba-returns/carton-management' },
|
||||
{ key: 'fba-product', title: '产品库', icon: Database, path: '/prototype/fba-returns/product-catalog' },
|
||||
{ key: 'fba-return-stock', title: '退货库存', icon: WarehouseIcon, path: '/prototype/fba-returns/return-stock' },
|
||||
{ key: 'overseas-held-stock', title: '暂扣库存', icon: WarehouseIcon, path: '/prototype/overseas-inventory/stock-list' },
|
||||
{ key: 'last-mile-orders', title: '尾程订单', icon: ClipboardList, path: '/overseas-warehouse/last-mile-orders', permission: 'platform:overseas-warehouse:last-mile-order:list' },
|
||||
{ key: 'fba-inbound-order', title: '入库单', icon: ClipboardList, path: '/overseas-warehouse/inbound-orders', permission: 'platform:overseas-warehouse:inbound-order:list' },
|
||||
{ key: 'fba-relabel-order', title: '换标单', icon: ClipboardList, path: '/overseas-warehouse/relabel-orders', permission: 'platform:overseas-warehouse:relabel-order:list' },
|
||||
{ key: 'fba-pallet', title: '托盘管理', icon: WarehouseIcon, path: '/overseas-warehouse/pallets', permission: 'platform:overseas-warehouse:pallet:list' },
|
||||
{ key: 'fba-carton', title: '箱号管理', icon: ClipboardList, path: '/overseas-warehouse/cartons', permission: 'platform:overseas-warehouse:carton:list' },
|
||||
{ key: 'fba-product', title: '产品库', icon: Database, path: '/overseas-warehouse/products', permission: 'platform:overseas-warehouse:product:list' },
|
||||
{ key: 'overseas-inventory-list', title: '库存管理', icon: WarehouseIcon, path: '/overseas-warehouse/inventory', permission: 'platform:overseas-warehouse:inventory:list' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -163,9 +160,11 @@ export const adminNavigation: AdminNavigationNode[] = [
|
||||
children: [
|
||||
{ key: 'general-product', title: '产品信息', icon: Database, path: '/prototype/general/product-information', hidden: true },
|
||||
{ key: 'general-fee', title: '费用项目', icon: Database, path: '/master-data/fee-items', permission: 'firstleg:fee-item:view' },
|
||||
{ key: 'exchange-rates', title: '汇率管理', icon: CircleDollarSign, path: '/overseas-master-data/exchange-rates', permission: 'platform:overseas-master-data:exchange-rate:list' },
|
||||
{ key: 'warehouses', title: '仓库管理', icon: WarehouseIcon, path: '/warehouses', permission: 'platform:warehouse:list' },
|
||||
{ key: 'overseas-quotes', title: '海外仓报价', icon: CircleDollarSign, path: '/overseas-master-data/quotes', permission: 'platform:overseas-master-data:quote:list' },
|
||||
{ key: 'fba-carrier-rates', title: '船期价格表', icon: CircleDollarSign, path: '/first-leg-pricing/carrier-rates', permission: 'firstleg:fba-pricing:rate:view' },
|
||||
{ key: 'vehicle-list', title: '车辆列表', icon: Truck, path: '/prototype/vehicle-management/vehicle-list', hidden: true },
|
||||
{ key: 'vehicle-list', title: '车辆列表', icon: Truck, path: '/overseas-master-data/vehicles', permission: 'platform:overseas-master-data:vehicle:list' },
|
||||
{ key: 'download-center', title: '下载中心', icon: Download, path: '/downloads', permission: 'platform:download:list' },
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user