diff --git a/src/views/SellerDashboard/MyListings/EditDetail/agents.md b/src/views/SellerDashboard/MyListings/EditDetail/agents.md new file mode 100644 index 00000000..af80d1a8 --- /dev/null +++ b/src/views/SellerDashboard/MyListings/EditDetail/agents.md @@ -0,0 +1,68 @@ +# EditDetail Page Agent Notes + +This directory owns the seller listing edit/create detail page. + +## Files + +- `index.vue`: route-level container. Keep orchestration here: history-state entry mode, API calls, page switching, validation, save/publish navigation, image crop dialog wiring. +- `api.ts`: API wrappers for listing detail, sketch detail, listing batch save, status update, and file upload. +- `types.ts`: shared page-local TypeScript types. Add new cross-component page types here instead of duplicating interfaces in child components. +- `components/TopImageSection.vue`: presentational block for `sketch`, `mainProductImage`, and `cover`. +- `components/ProductImageList.vue`: presentational product-image selector and selected/main badge display. +- `components/ApparelSketchList.vue`: presentational apparel sketch list and crop trigger. +- `components/ListingForm.vue`: presentational listing form. Emits field updates; does not call APIs. +- `components/Radio.vue`: local radio/multi-select button component. +- `Status.vue`: save/publish result status page. + +## Component Boundaries + +- Keep `index.vue` as the single source of truth for `selectList`, `currentPage`, `currentListing`, per-listing `firstSelectedIndex`, and crop/save behavior. +- Child components should receive props and emit events only. Do not import listing APIs or mutate parent state directly from children. +- If a new visual section is added to this page, prefer a new child component under `components/` plus shared types in `types.ts`. + +## Image Category Mapping + +Detail API images are mapped by `category`: + +- `cover` -> `currentListing.cover` +- `sketch` -> `currentListing.sketch` +- `mainProductImage` -> `currentListing.mainProductImage` +- `main_product` or `product` -> `currentListing.prodImageList` +- `apparel` -> `currentListing.sketchList` + +When saving, preserve the backend's expected image categories. Confirm backend naming before changing `main_product`, `product`, or `mainProductImage`. + +## Product Image Rules + +- The `main` badge represents the first selected product image, not the most recently selected one. +- `firstSelectedIndex` is stored on each `ListingItem` and passed to `ProductImageList.vue`. +- Selecting a product image should only set `mainProductImage` when no main image is currently tracked by that listing's `firstSelectedIndex`. +- Unselecting the current main product image clears `mainProductImage` and resets `firstSelectedIndex`. + +## Crop Flow + +- `TopImageSection.vue` and `ApparelSketchList.vue` emit `crop`. +- `index.vue` handles `handleClickCrop`, opens `ImageClipDialog`, uploads with `uploadFile`, then writes the returned URL into the correct field/list item. +- Keep cover crop ratio at `[4, 5]`; other crop types use `[9, 16]`. + +## Form Flow + +- `ListingForm.vue` accepts scalar form props and emits `update:*` events. +- `index.vue` writes those events back into `currentListing`. +- Category options are derived from current gender and Vuex `UserHabit` state. + +## Validation And Navigation + +- `validatePublishRequired()` validates each listing before publish. +- Draft currently requires `cover` before save. +- After save/publish, routing goes to `Status` with route param `status`. + +## Verification + +- Run `npm run build` after behavior or type changes. +- Build can fail before code bundling completes if the existing `feedbackSurvey.vue` Google Fonts import cannot be fetched. If the failure is only `fonts.googleapis.com` socket/DNS related, retry when network is available. +- Project ESLint currently fails before linting files because `.eslintrc.js` contains invalid env key `se6`; do not treat that as a page-specific regression. + +## Known Caution + +- This page has active local edits. Before broad refactors, inspect both staged and unstaged diffs. diff --git a/src/views/SellerDashboard/MyListings/EditDetail/index.vue b/src/views/SellerDashboard/MyListings/EditDetail/index.vue index b1438021..7311066f 100644 --- a/src/views/SellerDashboard/MyListings/EditDetail/index.vue +++ b/src/views/SellerDashboard/MyListings/EditDetail/index.vue @@ -31,7 +31,7 @@ (null) //显示main标签的图片索引 - const getSortedDetailImages = (images: ListingDetailImage[] = []) => { return [...images].sort((prev, next) => (prev.sortOrder ?? 0) - (next.sortOrder ?? 0)) } @@ -234,6 +233,9 @@ listing.prodImageList.find((item) => item.selected)?.url || "" } + const selectedIndex = listing.prodImageList.findIndex((item) => item.selected) + listing.firstSelectedIndex = selectedIndex === -1 ? null : selectedIndex + listing.productImage = listing.prodImageList.map((item) => item.url) listing.apparelSketch = listing.sketchList .map((item) => item.url) @@ -243,20 +245,21 @@ } const handleSelectProdImg = (index: number) => { + const listing = currentListing.value const target = prodImgList.value[index] const willSelect = !target.selected target.selected = willSelect - if (willSelect && firstSelectedIndex.value === null) { - currentListing.value.mainProductImage = target.url - firstSelectedIndex.value = index + if (willSelect && listing.firstSelectedIndex === null) { + listing.mainProductImage = target.url + listing.firstSelectedIndex = index return } - if (!willSelect && currentListing.value.mainProductImage === target.url) { - firstSelectedIndex.value = null - currentListing.value.mainProductImage = "" + if (!willSelect && listing.mainProductImage === target.url) { + listing.firstSelectedIndex = null + listing.mainProductImage = "" } } @@ -360,7 +363,7 @@ price: item.price, status: type === "draft" ? 0 : 1, images: [], - designFor: item.gender.toLowerCase, + designFor: (item.gender || "FEMALE").toLowerCase(), productCategory: item.category } @@ -394,8 +397,6 @@ }) paramsList.push(params) }) - console.log(paramsList) - debugger await fetchUpdateListing(paramsList) } const handleClickMenu = async (status: StatusType) => { @@ -433,11 +434,9 @@ const handleGetDetailById = () => { fetchListingDetailById(itemId.value).then((res: ListingDetailResponse) => { const listing = createListingItemFromDetail(res) - const selectedIndex = listing.prodImageList.findIndex((item) => item.selected) currentPage.value = 1 selectList.value = [listing] - firstSelectedIndex.value = selectedIndex === -1 ? null : selectedIndex }) } diff --git a/src/views/SellerDashboard/MyListings/EditDetail/types.ts b/src/views/SellerDashboard/MyListings/EditDetail/types.ts index a2414647..dc282606 100644 --- a/src/views/SellerDashboard/MyListings/EditDetail/types.ts +++ b/src/views/SellerDashboard/MyListings/EditDetail/types.ts @@ -20,6 +20,7 @@ export type ListingItem = { desc: string gender: string category: string[] | null + firstSelectedIndex: number | null prodImageList: Array<{ url: string selected?: boolean