26 lines
3.4 KiB
Markdown
26 lines
3.4 KiB
Markdown
# 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.
|