feat(admin,shop-admin): content/brand management + merchant shop profile (add-content-admin-ui)
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
## Why
|
||||
|
||||
The home-content APIs (`GET /api/admin/content`, `PUT /api/admin/content/{kind}` for banners, promos, quick links and floor adverts) and the brand registry APIs (`GET /api/brands`, `PUT /api/admin/brands`) already exist and already feed the mall, but the platform console has no pages for them: content and brands can only be changed with hand-built API calls. A shop profile is likewise writable only through the platform-admin endpoint `PUT /api/admin/shops/{id}/profile`, so a merchant cannot maintain their own store identity, contact and service copy at all.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add a content management page to `apps/admin` with one editor per home-content kind (banners, promos, quick links, floor adverts): ordered rows with add, remove and reorder, per-entry fields and active flags, form validation mirroring the API rules, a storefront preview, and per-kind whole-list replacement saves with the exact semantics of the existing `PUT /api/admin/content/{kind}`.
|
||||
- Add a brand management page to `apps/admin` editing the whole ordered brand list (slug, bilingual name, active flag) and saving it with the exact semantics of the existing `PUT /api/admin/brands`, with validation, preview and save feedback.
|
||||
- Add a shop profile editing page to `apps/shop-admin` where a merchant maintains their own logo, banner, company, region, and bilingual address, notice and after-sale copy through the shared API adapter.
|
||||
- Add one minimal merchant-scoped endpoint, `PUT /api/shop/profile`, plus its `@vmall/shared` contract method, because no merchant-writable profile route exists today; profile scores stay platform-set.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
None.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `frontend-admin`: platform-console pages for storefront home content and the brand registry, with per-row form validation, storefront preview, and explicit whole-list save feedback.
|
||||
- `frontend-shop-admin`: merchant self-editing of the shop profile through the shop-scoped endpoint, with bilingual validation and read-only platform-set scores.
|
||||
- `store-directory`: shop owners may write their own shop profile through `PUT /api/shop/profile` under the `own_shop` scope; the platform-admin write and the public reads keep their existing behavior.
|
||||
|
||||
## Non-goals
|
||||
|
||||
Drag-and-drop storefront decoration, draft/publish states for content entries, multi-page templates, and full shop storefront decoration (the tigshop store-fitting scope) are excluded. Media upload and hosting are excluded (entries keep carrying image URLs), as are a platform-admin UI for shop profiles (the existing admin API stays as-is), mall rendering changes for content and brands, brand-to-product assignment UI, and per-entry scheduling or analytics.
|
||||
|
||||
## Impact
|
||||
|
||||
- `apps/admin`: new content and brands pages, nav entries, and bilingual strings in the existing admin locale sources.
|
||||
- `apps/shop-admin`: new shop profile page, nav entry, and bilingual strings in the existing shop-admin locale sources.
|
||||
- `apps/api`: one new shop-scoped route `PUT /api/shop/profile` reusing the existing profile upsert and bilingual validation (no migration), with integration coverage in `apps/api/tests/`.
|
||||
- `@vmall/shared`: one `shop.updateMyProfile` method; the existing content, brand and store-directory contract methods are consumed unchanged.
|
||||
- No database migration and no change to the public storefront contract.
|
||||
@@ -0,0 +1,31 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Storefront content management
|
||||
The platform console SHALL manage the four home-content kinds — banners, promos, quick links and floor adverts — on a content page reachable from the console navigation, loading every entry including inactive ones through the shared API adapter. Each kind SHALL be edited as its ordered list of rows with add, remove and reorder controls (no drag and drop), the per-entry fields the API accepts, an active flag per entry, and a preview of how the storefront renders each entry. Saving a kind SHALL submit the whole edited list in its displayed order as one replacement with the semantics of `PUT /api/admin/content/{kind}`, so positions follow the submitted order and the other three kinds are untouched. Rows SHALL be validated before any request with the rules the API enforces — non-empty image and destination URLs, non-empty glyph, and non-empty `en` and `zh` quick-link labels in `{ en, zh }` JSONB text — with inline field errors on failing rows. Saves SHALL give clear success feedback; a rejected save SHALL keep the edited rows and show a visible failure message.
|
||||
|
||||
#### Scenario: save replaces one kind only
|
||||
- **WHEN** an admin reorders banners, saves that kind, and the mall reloads its home content
|
||||
- **THEN** banners appear in the new order while promos, quick links and floor adverts are unchanged
|
||||
|
||||
#### Scenario: invalid row is blocked before submit
|
||||
- **WHEN** an admin leaves a banner image URL empty and saves
|
||||
- **THEN** the row shows an inline field error and no replacement request is sent
|
||||
|
||||
#### Scenario: rejected save keeps the editor state
|
||||
- **WHEN** the API rejects a replacement
|
||||
- **THEN** the page shows a failure message and keeps the edited rows for correction
|
||||
|
||||
### Requirement: Brand management
|
||||
Platform admins SHALL manage the ordered brand registry on a brands page reachable from the console navigation, through the shared API adapter. The page SHALL edit the whole list — slug, non-empty `{ en, zh }` names and an active flag per row — with add, remove and reorder controls and a preview of each brand as the public list serves it. Saving SHALL replace the whole list in its displayed order so positions follow the submitted order. Slugs SHALL be validated against the ascii `a-z`, `0-9`, `-` pattern and duplicate slugs and incomplete bilingual names SHALL be rejected with inline errors before any request is sent. Saves SHALL give clear success feedback; a rejected save SHALL keep the edited rows and show a visible failure message.
|
||||
|
||||
#### Scenario: replace round-trips to the public list
|
||||
- **WHEN** an admin reorders brands, renames one, and saves
|
||||
- **THEN** the public brand list returns the same entries in the submitted order
|
||||
|
||||
#### Scenario: duplicate slug is blocked before submit
|
||||
- **WHEN** an admin saves with two rows carrying the same slug
|
||||
- **THEN** the duplicate rows show inline errors and no replacement request is sent
|
||||
|
||||
#### Scenario: save feedback distinguishes success and failure
|
||||
- **WHEN** a save succeeds or the API refuses the list
|
||||
- **THEN** the page shows a success confirmation or keeps the edited rows with a failure message, respectively
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Merchant shop profile editing
|
||||
Shop users SHALL edit their own shop's profile from a shop-admin page reachable from the shop-scoped navigation, through the shared API adapter's shop-scoped profile update. The form SHALL prefill from the shop's current profile and cover the merchant-writable fields only: logo and banner URLs, company, region, and `{ en, zh }` bilingual address, notice and after-sale copy. Bilingual fields SHALL be validated with non-empty `en` and `zh` text before any request is sent, every write SHALL target the caller's own shop, and profile scores SHALL NOT be editable — they remain platform-set and saving never changes them. Saves SHALL give clear success feedback; a refused save SHALL keep the form values and show a visible failure message.
|
||||
|
||||
#### Scenario: merchant updates their own profile
|
||||
- **WHEN** a shop owner edits their notice, saves, and the mall store page reloads
|
||||
- **THEN** only that shop shows the new notice and no other shop's profile changes
|
||||
|
||||
#### Scenario: incomplete bilingual text is blocked before submit
|
||||
- **WHEN** a shop owner leaves the `zh` after-sale text empty and saves
|
||||
- **THEN** the field shows an inline error and no update request is sent
|
||||
|
||||
#### Scenario: scores are not editable
|
||||
- **WHEN** the profile page loads and is saved
|
||||
- **THEN** no score fields are offered and the platform-set scores remain unchanged
|
||||
|
||||
#### Scenario: refused save keeps the form
|
||||
- **WHEN** the API refuses an update
|
||||
- **THEN** the page keeps the entered values and shows a failure message
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Merchant self-service shop profile
|
||||
A shop owner SHALL set their own shop's profile with `PUT /api/shop/profile`, which upserts the profile row of the shop the caller owns and returns the composed shop profile. The write SHALL require an authenticated user with a shop under the `own_shop` scope and SHALL accept logo and banner URLs, company, region, and bilingual `address`, `notice` and `after_sale` text with the same `{ en, zh }` validation as the platform-admin profile write. Profile scores remain platform-set: merchant-supplied `score_rating`, `score_agreement`, `score_service` or `score_speed` values SHALL NOT be stored. A user without a shop SHALL be refused, and the platform-admin write and public reads SHALL keep their existing behavior.
|
||||
|
||||
#### Scenario: merchant upsert round-trips
|
||||
- **WHEN** a shop owner sets their profile and the storefront reads the shop by slug
|
||||
- **THEN** the public read returns those values for that shop
|
||||
|
||||
#### Scenario: incomplete bilingual text is refused
|
||||
- **WHEN** a shop owner submits a notice with only `en` text
|
||||
- **THEN** the request is refused and the stored profile is unchanged
|
||||
|
||||
#### Scenario: merchant cannot set scores
|
||||
- **WHEN** a shop owner submits score values with their profile
|
||||
- **THEN** the stored scores are unchanged
|
||||
|
||||
#### Scenario: a user without a shop is refused
|
||||
- **WHEN** a signed-in customer sends the merchant profile write
|
||||
- **THEN** the API refuses the write
|
||||
@@ -0,0 +1,28 @@
|
||||
## 1. Admin content management page
|
||||
|
||||
- [x] 1.1 Add `apps/admin/pages/content.vue` loading all four kinds through `admin.getContent()` from `@vmall/shared` (inactive entries included) and add a content entry to the `apps/admin` nav beside existing platform operations.
|
||||
- [x] 1.2 Implement one editor section per kind — banners, promos, quick links, floor adverts — with the exact per-entry fields the API accepts: image and destination URL for banners and promos, `{ en, zh }` label, destination URL and inline SVG glyph for quick links, and image for floor adverts, plus an active flag on every row.
|
||||
- [x] 1.3 Support ordered row editing per kind (add, remove, move up/down — no drag and drop) and pre-validate each row with the API rules (non-empty image and destination URLs, non-empty glyph, non-empty `en` and `zh` quick-link labels), showing inline field errors before any request is sent.
|
||||
- [x] 1.4 Save one kind at a time by submitting the whole displayed list through `admin.replaceContent(kind, items)` so the API replaces that kind atomically and reindexes positions, leaving the other three kinds untouched; render a per-entry storefront preview (image, destination, glyph/label) and clear success feedback on save.
|
||||
- [x] 1.5 On a rejected save keep the edited rows and show a visible failure message, and add the page's bilingual strings through the existing admin locale sources.
|
||||
|
||||
## 2. Admin brand management page
|
||||
|
||||
- [x] 2.1 Add `apps/admin/pages/brands.vue` loading the registry through `admin.getBrands()` and add a brands entry to the `apps/admin` nav beside existing platform operations.
|
||||
- [x] 2.2 Edit the whole ordered list with add, remove and move up/down rows carrying slug, non-empty `{ en, zh }` names and an active flag; pre-validate slugs against the API pattern (ascii `a-z`, `0-9`, `-`) and reject duplicate slugs and incomplete bilingual names with inline errors before any request is sent.
|
||||
- [x] 2.3 Save the full list through `admin.replaceBrands(items)` so positions follow the displayed order, and render a preview row per brand as the public list serves it (slug and bilingual name) with clear success feedback on save.
|
||||
- [x] 2.4 On a rejected save keep the edited rows and show a visible failure message, and add the page's bilingual strings through the existing admin locale sources.
|
||||
|
||||
## 3. Shop-admin profile editing
|
||||
|
||||
- [x] 3.1 Add the minimal merchant-scoped endpoint `PUT /api/shop/profile` in `apps/api/src/modules/shop/`: an authenticated write scoped with `own_shop` over the caller's shop, reusing the existing profile upsert and `{ en, zh }` bilingual validation, accepting logo, banner, company, region, address, notice and after_sale, and never storing `score_rating`, `score_agreement`, `score_service` or `score_speed`; the service returns `ApiResult<ShopProfileView>`.
|
||||
- [x] 3.2 Add a `shop.updateMyProfile` method to the `@vmall/shared` contract (own-profile input without score fields, returning `ShopProfile`) with live-client wiring; all frontend calls go through this shared contract only.
|
||||
- [x] 3.3 Add `apps/shop-admin/pages/shop-profile.vue` editing the caller's own shop profile — logo and banner URLs, company, region and bilingual address, notice and after-sale copy — prefilled from `shop.getMyShop()` and the composed `getShop(slug)` read, with non-empty `en` and `zh` validation on bilingual fields and no score fields in the form.
|
||||
- [x] 3.4 Save through `shop.updateMyProfile`, keep the form values and show a visible failure message when the write is refused (success feedback otherwise), and add the shop-profile nav entry to `apps/shop-admin` with bilingual strings through the existing shop-admin locale sources.
|
||||
|
||||
## 4. Verification
|
||||
|
||||
- [x] 4.1 Add integration coverage in `apps/api/tests/` reusing the `tests/common/mod.rs` fixtures for the new endpoint: a merchant upsert round-trips to the public shop read, incomplete bilingual text is refused with the stored profile unchanged, submitted score values leave the stored scores unchanged, a user without a shop is refused, and another shop's profile is untouched; run the focused `cargo test -p vmall-api --test shops`.
|
||||
- [x] 4.2 Build all three frontends because the shared contract changes: `pnpm --filter @vmall/admin build`, `pnpm --filter @vmall/shop-admin build`, and `pnpm --filter @vmall/mall build`.
|
||||
- [x] 4.3 Run the API and browser-smoke the admin content page (edit, reorder, per-kind save reflected on the mall home with the other kinds untouched), the admin brands page (edit and save reflected in the public brand list), and the shop-admin profile page (edit, save, and the mall store page showing the new copy for that shop only).
|
||||
- [x] 4.4 Check every OpenSpec task and run `openspec change validate add-content-admin-ui --strict` plus `openspec validate --all --strict`.
|
||||
Reference in New Issue
Block a user