From 3575141a978603b590c5d9c082ecab454aef66ab Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Thu, 26 Feb 2026 10:50:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=95=86=E5=93=81?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=BC=96=E8=BE=91=E4=BA=A4=E4=BA=92=E4=B8=8E?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../list/components/ProductFilterToolbar.vue | 2 - .../list/components/ProductListSection.vue | 38 +++++++++++-- .../list/composables/useProductListPage.ts | 53 ------------------- .../web-antd/src/views/product/list/index.vue | 7 ++- .../src/views/product/list/styles/list.less | 14 +++++ 5 files changed, 52 insertions(+), 62 deletions(-) diff --git a/apps/web-antd/src/views/product/list/components/ProductFilterToolbar.vue b/apps/web-antd/src/views/product/list/components/ProductFilterToolbar.vue index 47832cf..93c1806 100644 --- a/apps/web-antd/src/views/product/list/components/ProductFilterToolbar.vue +++ b/apps/web-antd/src/views/product/list/components/ProductFilterToolbar.vue @@ -25,7 +25,6 @@ interface StatusOption { } interface Props { - isLoading: boolean; isStoreLoading: boolean; kind: '' | ProductKind; kindOptions: KindOption[]; @@ -40,7 +39,6 @@ interface Props { const props = defineProps(); const emit = defineEmits<{ - (event: 'reset'): void; (event: 'search'): void; (event: 'update:kind', value: '' | ProductKind): void; (event: 'update:keyword', value: string): void; diff --git a/apps/web-antd/src/views/product/list/components/ProductListSection.vue b/apps/web-antd/src/views/product/list/components/ProductListSection.vue index f7db488..d957191 100644 --- a/apps/web-antd/src/views/product/list/components/ProductListSection.vue +++ b/apps/web-antd/src/views/product/list/components/ProductListSection.vue @@ -51,6 +51,7 @@ const emit = defineEmits<{ (event: 'delete', item: ProductListItemDto): void; (event: 'detail', item: ProductListItemDto): void; (event: 'edit', item: ProductListItemDto): void; + (event: 'quickEdit', item: ProductListItemDto): void; (event: 'pageChange', payload: ProductPaginationChangePayload): void; (event: 'soldout', item: ProductListItemDto): void; ( @@ -67,7 +68,9 @@ function isChecked(productId: string) { /** 解析“更多”动作菜单。 */ function getMoreActionOptions(item: ProductListItemDto) { - const actions: Array<{ key: string; label: string }> = []; + const actions: Array<{ key: string; label: string }> = [ + { key: 'quick_edit', label: '快速编辑' }, + ]; if (item.status === 'on_sale') { actions.push({ key: 'off', label: '下架' }); } else { @@ -83,6 +86,10 @@ function handleMoreAction( payload: { key: number | string }, ) { const key = String(payload.key); + if (key === 'quick_edit') { + emit('quickEdit', item); + return; + } if (key === 'on') { emit('changeStatus', { item, status: 'on_sale' }); return; @@ -96,6 +103,17 @@ function handleMoreAction( } } +/** 解析商品展示图。 */ +function resolveImageUrl(item: ProductListItemDto) { + return String(item.imageUrl || '').trim(); +} + +/** 解析图片占位首字。 */ +function resolveImageFallbackText(item: ProductListItemDto) { + const name = String(item.name || '').trim(); + return name ? name.slice(0, 1) : '?'; +} + /** 处理单项勾选变化。 */ function handleSingleCheck(productId: string, event: unknown) { const checked = Boolean( @@ -152,7 +170,13 @@ function handlePageSizeChange(current: number, size: number) { />
- {{ item.name.slice(0, 1) }} + + {{ resolveImageFallbackText(item) }}
@@ -268,7 +292,15 @@ function handlePageSizeChange(current: number, size: number) { - {{ item.name.slice(0, 1) }} + + + {{ resolveImageFallbackText(item) }} + diff --git a/apps/web-antd/src/views/product/list/composables/useProductListPage.ts b/apps/web-antd/src/views/product/list/composables/useProductListPage.ts index b58cb6c..401af21 100644 --- a/apps/web-antd/src/views/product/list/composables/useProductListPage.ts +++ b/apps/web-antd/src/views/product/list/composables/useProductListPage.ts @@ -21,7 +21,6 @@ import { useRouter } from 'vue-router'; import { message, Modal } from 'ant-design-vue'; -import { uploadTenantFileApi } from '#/api/files'; import { searchProductPickerApi } from '#/api/product'; import { createBatchActions } from './product-list-page/batch-actions'; @@ -55,7 +54,6 @@ export function useProductListPage() { const isCategoryLoading = ref(false); const isListLoading = ref(false); const isEditorSubmitting = ref(false); - const isEditorImageUploading = ref(false); const isQuickEditSubmitting = ref(false); const isSoldoutSubmitting = ref(false); @@ -257,48 +255,6 @@ export function useProductListPage() { editorForm.description = value; } - function removeEditorImage(index: number) { - editorForm.imageUrls = editorForm.imageUrls.filter( - (_, idx) => idx !== index, - ); - } - - function setEditorPrimaryImage(index: number) { - if (index <= 0 || index >= editorForm.imageUrls.length) return; - const next = [...editorForm.imageUrls]; - const [selected] = next.splice(index, 1); - if (!selected) return; - editorForm.imageUrls = [selected, ...next]; - } - - async function uploadEditorImage(file: File) { - if (editorForm.imageUrls.length >= 5) { - message.warning('最多上传 5 张图片'); - return; - } - - isEditorImageUploading.value = true; - try { - const uploaded = await uploadTenantFileApi(file, 'dish_image'); - const url = String(uploaded.url || '').trim(); - if (!url) { - message.error('图片上传失败'); - return; - } - - editorForm.imageUrls = [...editorForm.imageUrls, url] - .map((item) => String(item || '').trim()) - .filter(Boolean) - .filter((item, index, source) => source.indexOf(item) === index) - .slice(0, 5); - message.success('图片上传成功'); - } catch (error) { - console.error(error); - } finally { - isEditorImageUploading.value = false; - } - } - function normalizeComboGroups() { editorForm.comboGroups = editorForm.comboGroups.map( (group, groupIndex) => ({ @@ -511,10 +467,6 @@ export function useProductListPage() { editorForm.stock = Math.max(0, Math.floor(Number(value || 0))); } - function setEditorTagsText(value: string) { - editorForm.tagsText = value; - } - function setEditorShelfMode(value: 'draft' | 'now' | 'scheduled') { editorForm.shelfMode = value; } @@ -679,7 +631,6 @@ export function useProductListPage() { isComboPickerLoading, isComboPickerOpen, isEditorDrawerOpen, - isEditorImageUploading, isEditorSubmitting, isListLoading, isQuickEditDrawerOpen, @@ -724,11 +675,7 @@ export function useProductListPage() { setEditorShelfMode, setEditorStock, setEditorSubtitle, - setEditorTagsText, setEditorTimedOnShelfAt, - removeEditorImage, - setEditorPrimaryImage, - uploadEditorImage, setFilterKind, setFilterKeyword, setFilterStatus, diff --git a/apps/web-antd/src/views/product/list/index.vue b/apps/web-antd/src/views/product/list/index.vue index 88c0dca..77b6c91 100644 --- a/apps/web-antd/src/views/product/list/index.vue +++ b/apps/web-antd/src/views/product/list/index.vue @@ -56,6 +56,7 @@ const { isSoldoutSubmitting, isStoreLoading, openCreateProductDrawer, + openEditDrawer, openProductDetail, openProductDetailById, openQuickEditDrawer, @@ -63,7 +64,6 @@ const { products, quickEditForm, quickEditSummary, - resetSearchFilters, selectedCount, selectedProductIds, selectedStoreId, @@ -165,14 +165,12 @@ function onBatchAction(action: ProductBatchAction) { :status-options="PRODUCT_STATUS_OPTIONS" :kind-options="PRODUCT_KIND_OPTIONS" :view-mode="viewMode" - :is-loading="isListLoading" @update:selected-store-id="setSelectedStoreId" @update:keyword="setFilterKeyword" @update:status="setFilterStatus" @update:kind="setFilterKind" @update:view-mode="setViewMode" @search="applySearchFilters" - @reset="resetSearchFilters" />