bugfix: 多页编辑
This commit is contained in:
68
src/views/SellerDashboard/MyListings/EditDetail/agents.md
Normal file
68
src/views/SellerDashboard/MyListings/EditDetail/agents.md
Normal file
@@ -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.
|
||||
@@ -31,7 +31,7 @@
|
||||
<TopImageSection :images="previewImageMap" @crop="handleClickCrop" />
|
||||
<ProductImageList
|
||||
:image-list="prodImgList"
|
||||
:first-selected-index="firstSelectedIndex"
|
||||
:first-selected-index="currentListing.firstSelectedIndex"
|
||||
@select="handleSelectProdImg"
|
||||
/>
|
||||
<ApparelSketchList
|
||||
@@ -130,6 +130,7 @@
|
||||
desc: "",
|
||||
gender: "FEMALE",
|
||||
category: null,
|
||||
firstSelectedIndex: null,
|
||||
prodImageList: [],
|
||||
sketchList: []
|
||||
})
|
||||
@@ -162,8 +163,6 @@
|
||||
cover: currentListing.value.cover
|
||||
}))
|
||||
|
||||
const firstSelectedIndex = ref<number | null>(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
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ export type ListingItem = {
|
||||
desc: string
|
||||
gender: string
|
||||
category: string[] | null
|
||||
firstSelectedIndex: number | null
|
||||
prodImageList: Array<{
|
||||
url: string
|
||||
selected?: boolean
|
||||
|
||||
Reference in New Issue
Block a user