Files
vmall/openspec/changes/archive/2026-09-17-frontend-apps/agent-shop-admin.md
T

3.4 KiB
Raw Blame History

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.