feat: three nuxt frontends, demo seed, rounding + money-exponent + rate-cast fixes, archived specs

This commit is contained in:
Chengdong Zhang
2026-09-17 13:34:38 +08:00
parent dc9fd31c5e
commit 653f13161a
85 changed files with 4327 additions and 106 deletions
@@ -0,0 +1,25 @@
# Task: build apps/shop-admin (merchant console) — READ openspec/changes/frontend-apps/agent-conventions.md FIRST and follow it exactly. Your app = @vmall/shop-admin, port 3001. Allowed roles: shop_owner, shop_staff.
Replace the placeholder pages. app.vue (sidebar layout) exists — keep it.
## Pages to implement (apps/shop-admin/pages/)
1. `login.vue` — per conventions; only shop_owner/shop_staff may proceed.
2. `index.vue` (auth) — dashboard: stat cards (total products, published count, orders by status counts, pending invoice requests) computed from `$api.shop.listMyProducts({ per_page: 100 })`, `$api.shop.listOrders({ per_page: 100 })`, `$api.shop.listInvoices()`. Also show shop profile from `$api.shop.getMyShop()` (localized name, slug, status).
3. `products/index.vue` (auth) — table: localized name, slug, SKU count, status badge, created; status filter select (all/draft/published/unpublished); actions: edit link, publish (`$api.shop.publish`) / unpublish (`$api.shop.unpublish`) buttons with immediate refetch; "New product" button → products/new. Paging if total > per_page.
4. `products/new.vue` + `products/[id].vue` (auth) — product form (shared component `components/ProductForm.vue`): slug, name EN + ZH, description EN + ZH (textareas), category select (`$api.listCategories()`, optional), images textarea (one URL per line → string[]). Save: create → `$api.shop.createProduct`, edit → `$api.shop.updateProduct`; body type ProductUpsertBody.
On the EDIT page additionally: SKU manager — table of existing SKUs (sku_code, price formatted, currency, stock, active) + form to add/update a SKU (`$api.shop.upsertSku(productId, body)` with SkuUpsertBody): sku_code, price in MAJOR units converted to minor ×100 (currencies USD/CNY/EUR all exponent 2; offer a select of those three), stock integer, active checkbox. After upsert, refetch the product via `$api.shop.getProduct(id)` (exists in the client).
Validation: slug required (lowercase alnum + dash), name EN required; show API errors (e.g. publish without priced SKU → 400) via .error-text.
5. `orders/index.vue` (auth) — `$api.shop.listOrders({ status, page })`: status filter, table (order_no, created, items count, total in order.currency formatted, status badge), row → orders/[id].
6. `orders/[id].vue` (auth) — order items, address, status. Create-shipment card (when status paid or fulfilling): carrier + tracking_no inputs + one qty input per order item (default = unshipped remainder = item.qty sum of that item's qty across this order's shipments from `$api.shop.listShipments()` filtered by order_id); submit → `$api.shop.createShipment(orderId, carrier, trackingNo, items)` with only qty>0 lines. Shipments of this order: table with status + "Mark shipped" button when pending (`$api.shop.markShipped`). Refetch after every action.
7. `shipments.vue` (auth) — `$api.shop.listShipments()`: full table (shipment_no, order_no, carrier, tracking_no, status badge, created) + mark-shipped action.
8. `invoices.vue` (auth) — `$api.shop.listInvoices()`: table (invoice_no or —, order_no, title, kind, tax_no, amount formatted, status badge) + "Issue" button when requested (`$api.shop.issueInvoice`).
## Middleware
middleware/auth.ts per conventions; apply to everything except /login.
## Verify
`pnpm --filter @vmall/shop-admin build` MUST pass. Fix type errors properly (no `any`, no @ts-ignore).
## Report back
Pages built, deviations, contract gaps found.