feat(M09-D4): 完成商品选购与运营工作台

This commit is contained in:
Codex
2026-08-11 08:06:13 +08:00
parent 60264cbd11
commit e91b979128
32 changed files with 1065 additions and 60 deletions
+3 -3
View File
@@ -24,11 +24,11 @@ if (!distOnly) {
assert.doesNotMatch(app, /import\s+(?!AdminLoginPanel\b)\w+Panel\s+from/);
const router = read('admin/src/router.ts');
for (const path of ['/overview', '/stores', '/orders', '/payments', '/third-party',
for (const path of ['/overview', '/stores', '/orders', '/products', '/payments', '/third-party',
'/people', '/cleaning', '/devices', '/apps', '/content', '/franchise', '/system']) {
assert.match(read('admin/src/navigation.mjs'), new RegExp(`path: '${path.replace('/', '\\/')}'`));
}
assert.equal((router.match(/\(\) => import\('/g) || []).length, 12);
assert.equal((router.match(/\(\) => import\('/g) || []).length, 13);
assert.match(router, /router\.beforeEach/);
assert.match(router, /isNavigationAllowed/);
@@ -60,7 +60,7 @@ if (!sourceOnly) {
.sort((left, right) => right.bytes - left.bytes)[0];
assert.ok(largest.bytes < 500 * 1024, `chunk ${largest.name} exceeds 500 KiB`);
for (const chunk of ['OperationsOverviewPanel', 'StoresRoomsPanel', 'OrdersPanel',
'PaymentsPanel', 'ThirdPartyPanel', 'MembersStaffPanel', 'CleaningWorkspace',
'ProductOperationsPanel', 'PaymentsPanel', 'ThirdPartyPanel', 'MembersStaffPanel', 'CleaningWorkspace',
'DevicesPanel', 'PlatformAppsPanel', 'ContentManagementPanel', 'FranchisePanel',
'LogsSystemPanel']) assert.ok(javascript.some((name) => name.startsWith(`${chunk}-`)), `${chunk} is not lazy`);
const entry = javascript.find((name) => name.startsWith('index-'));
+2 -2
View File
@@ -24,10 +24,10 @@ for (const pattern of [
assert.doesNotMatch(app, /import\s+(?!AdminLoginPanel\b)\w+Panel\s+from/);
const router = read('admin/src/router.ts');
assert.equal((router.match(/\(\) => import\('/g) || []).length, 12);
assert.equal((router.match(/\(\) => import\('/g) || []).length, 13);
assert.match(router, /router\.beforeEach/);
const navigation = read('admin/src/navigation.mjs');
for (const path of ['/overview', '/stores', '/orders', '/payments', '/third-party', '/people',
for (const path of ['/overview', '/stores', '/orders', '/products', '/payments', '/third-party', '/people',
'/cleaning', '/devices', '/apps', '/content', '/franchise', '/system']) assert.ok(navigation.includes(`path: '${path}'`));
const cleaningWorkspace = read('admin/src/components/CleaningWorkspace.vue');
@@ -0,0 +1,82 @@
import assert from 'node:assert/strict';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
const root = process.cwd();
const read = (path) => readFileSync(join(root, path), 'utf8');
const routes = read('backend/src/routes/products.ts');
for (const pattern of [
'/app-api/stores/:storeId/product-catalog',
'authenticateAccessToken',
'listStoreCatalog',
'tenantId: auth.session.tenantId'
]) assert.ok(routes.includes(pattern), `customer catalog route is missing ${pattern}`);
const routeTest = read('backend/tests/product-route.test.mjs');
assert.match(routeTest, /app-api\/stores\/11\/product-catalog/);
assert.match(routeTest, /customerCatalog\.statusCode, 200/);
assert.match(routeTest, /statusCode, 401/);
const navigation = read('admin/src/navigation.mjs');
const router = read('admin/src/router.ts');
const app = read('admin/src/App.vue');
assert.match(navigation, /menuKey: 'products'/);
assert.match(navigation, /path: '\/products'/);
for (const capability of [
'product.catalog.read', 'inventory.read', 'inventory.adjust',
'goods.order.read', 'goods.order.manage', 'goods.storage.read', 'goods.storage.manage'
]) assert.ok(navigation.includes(capability), `product navigation is missing ${capability}`);
assert.match(router, /ProductOperationsPanel\.vue/);
assert.match(app, /ShoppingBasket/);
const api = read('admin/src/api.ts');
for (const pattern of [
'listProductListings', 'listInventoryStocks', 'mutateInventory',
'listProductOrders', 'getProductOrder', 'runProductOrderAction',
'listProductStorages', 'getProductStorage', 'createOrderProductStorage',
'runProductStorageAction'
]) assert.ok(api.includes(pattern), `admin product API is missing ${pattern}`);
const panel = read('admin/src/components/ProductOperationsPanel.vue');
for (const pattern of [
'canCatalogRead', 'canInventoryRead', 'canInventoryWrite',
'canOrderRead', 'canOrderWrite', 'canStorageRead', 'canStorageWrite',
'inventoryQuantity', 'stocktake', 'orderActions', 'submitRetrieve',
'rotateStorage', 'submitCreateStorage', '一次性领取码'
]) assert.ok(panel.includes(pattern), `product operations panel is missing ${pattern}`);
assert.match(panel, /Promise\.all/);
assert.match(panel, /:disabled="!canCatalogRead"/);
const appJson = JSON.parse(read('miniapp/app.json'));
assert.ok(appJson.pages.includes('pages/goods/index'));
for (const path of [
'miniapp/pages/goods/index.js', 'miniapp/pages/goods/index.json',
'miniapp/pages/goods/index.wxml', 'miniapp/pages/goods/index.wxss'
]) assert.ok(existsSync(join(root, path)), `${path} is missing`);
const goodsScript = read('miniapp/pages/goods/index.js');
for (const pattern of [
'/product-orders', '/product-storages', '/product-catalog',
'createOrder', 'cancelOrder', 'storeOrder', 'retrieveItem',
'rotateCredential', 'cancelStorage', 'showCredential', 'clearCredential'
]) assert.ok(goodsScript.includes(pattern), `miniapp goods flow is missing ${pattern}`);
assert.doesNotMatch(goodsScript, /(?:wx\.)?setStorage(?:Sync)?\s*\(/,
'one-time storage credential must not be persisted locally');
const goodsView = read('miniapp/pages/goods/index.wxml');
for (const pattern of [
'选购', '商品订单', '我的寄存', '领取码仅展示本次',
'提交订单', '整单寄存', '更换领取码', '取出流水'
]) assert.ok(goodsView.includes(pattern), `miniapp goods view is missing ${pattern}`);
const entryPoints = read('miniapp/pages/store/detail.js')
+ read('miniapp/pages/store/detail.wxml')
+ read('miniapp/pages/profile/index.js')
+ read('miniapp/pages/profile/index.wxml');
assert.match(entryPoints, /pages\/goods\/index/);
assert.match(entryPoints, /选购商品/);
console.log(
'PASS: M09-D4 authenticated catalog, capability-scoped admin workspace, customer order/storage flow and one-time credential gates are present.'
);
+2
View File
@@ -44,6 +44,8 @@ node scripts/check-m09-d2-product-orders.mjs
Assert-NativeSuccess "check-m09-d2-product-orders"
node scripts/check-m09-d3-product-storage.mjs
Assert-NativeSuccess "check-m09-d3-product-storage"
node scripts/check-m09-d4-product-experience.mjs
Assert-NativeSuccess "check-m09-d4-product-experience"
if (Test-Path "admin/package.json") {
Push-Location admin