diff --git a/app/components/export/EditModal.vue b/app/components/export/EditModal.vue new file mode 100644 index 0000000..01df664 --- /dev/null +++ b/app/components/export/EditModal.vue @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/components/item/EditModal.vue b/app/components/item/EditModal.vue index af121e0..ce55b85 100644 --- a/app/components/item/EditModal.vue +++ b/app/components/item/EditModal.vue @@ -100,7 +100,7 @@ const { utils, isNameAvailable, addItem, editItem, items } = useItemsStore(); const baseSchema = z.object({ brand: z.string().optional(), name: z.string().min(1, "名称不能为空"), - imageUrl: z.string().nullable().optional(), + imageUrl: z.string().optional(), description: z.string().nullable().optional(), tags: z.string().nullable().optional(), }); @@ -121,7 +121,7 @@ type ItemSchema = z.output; const itemState = reactive({ brand: undefined, name: "", - imageUrl: null, + imageUrl: "", description: null, tags: null, }); @@ -134,7 +134,7 @@ const openModal = (item?: Item) => { currentId.value = item.id; itemState.brand = item.brand; itemState.name = item.name; - itemState.imageUrl = item.imageUrl ?? null; + itemState.imageUrl = item.imageUrl; itemState.description = item.description ?? null; itemState.tags = item.tags?.join(", ") ?? null; } else { @@ -143,7 +143,7 @@ const openModal = (item?: Item) => { itemState.brand = undefined; currentId.value = null; itemState.name = ""; - itemState.imageUrl = null; + itemState.imageUrl = ""; itemState.description = null; itemState.tags = null; } diff --git a/app/composables/biddingItems.ts b/app/composables/biddingItems.ts new file mode 100644 index 0000000..52c459b --- /dev/null +++ b/app/composables/biddingItems.ts @@ -0,0 +1,50 @@ +import { createSharedComposable } from "@vueuse/core"; + +export type BiddingItem = { + id: number; + name: string; + startPrice: number; + remarks: string; + imageUrl?: string; +}; + +const _useBiddingItems = () => { + const biddingItems = ref([]); + + const biddingItemsLatestId = computed(() => { + const arr = biddingItems.value ?? []; + if (arr.length === 0) return 1; + return Math.max(...arr.map((i) => i.id)) + 1; + }); + + const addBiddingItem = (item: Item, startPrice: number) => { + biddingItems.value.push({ + id: biddingItemsLatestId.value, + name: `${item.brand} ${item.name}`, + startPrice: startPrice, + remarks: "", + imageUrl: item.imageUrl, + }); + }; + + const removeBiddingItem = (id: number) => { + biddingItems.value = (biddingItems.value ?? []).filter((i) => i.id !== id); + }; + + const editBiddingItem = (biddingItem: BiddingItem) => { + const idx = (biddingItems.value ?? []).findIndex( + (i) => i.id === biddingItem.id + ); + if (idx === -1) return; + const updatedBiddingItems = [...(biddingItems.value ?? [])]; + updatedBiddingItems[idx] = { + ...updatedBiddingItems[idx], + ...biddingItem, + }; + biddingItems.value = updatedBiddingItems; + }; + + return { biddingItems, addBiddingItem, removeBiddingItem, editBiddingItem }; +}; + +export const useBiddingItems = createSharedComposable(_useBiddingItems); diff --git a/app/composables/items.ts b/app/composables/items.ts index f2fa5e9..f5326ad 100644 --- a/app/composables/items.ts +++ b/app/composables/items.ts @@ -4,7 +4,7 @@ export type Item = { id: number; brand?: string; name: string; - imageUrl?: string | null; + imageUrl?: string; description?: string | null; tags?: string[] | null; createdAt?: Date; diff --git a/app/pages/export/index.vue b/app/pages/export/index.vue index b9b4da4..ad3e5c9 100644 --- a/app/pages/export/index.vue +++ b/app/pages/export/index.vue @@ -1,13 +1,349 @@ - + + + + + + 物品列表 + + + + + + + + + + + + + + {{ row.original.brand }} + {{ row.original.name }} + + + + + + + + + + + 竞标清单 + + + + + 应用到选中 + + + + + row.toggleSelected(!!value)" + aria-label="Select row" + /> + + + + + + + + {{ row.original.name }} + + + + + + + + {{ formatCurrency(row.original.startPrice) }} + + + + + + + + + + + + + + + + 导出预览 + + {{ exportCsv }} + + + 导出 CSV + 下载图片集 + + + + + + + + \ No newline at end of file +/* 阻止表头换行 */ +.no-wrap-header thead th { + white-space: nowrap !important; + text-overflow: ellipsis; + overflow: hidden; +} + diff --git a/package.json b/package.json index df15f16..fce700f 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@nuxt/ui": "4.0.1", "nuxt": "^4.1.3", + "sortablejs": "^1.15.6", "typescript": "^5.9.3", "vue": "^3.5.22", "vue-router": "^4.5.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4ad11b..39dc586 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,10 +10,13 @@ importers: dependencies: '@nuxt/ui': specifier: 4.0.1 - version: 4.0.1(@babel/parser@7.28.4)(db0@0.3.4)(embla-carousel@8.6.0)(ioredis@5.8.1)(magicast@0.3.5)(typescript@5.9.3)(vite@7.1.9(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-router@4.5.1(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))(zod@4.1.12) + version: 4.0.1(@babel/parser@7.28.4)(db0@0.3.4)(embla-carousel@8.6.0)(ioredis@5.8.1)(magicast@0.3.5)(sortablejs@1.15.6)(typescript@5.9.3)(vite@7.1.9(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-router@4.5.1(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))(zod@4.1.12) nuxt: specifier: ^4.1.3 version: 4.1.3(@parcel/watcher@2.5.1)(@vue/compiler-sfc@3.5.22)(db0@0.3.4)(ioredis@5.8.1)(lightningcss@1.30.1)(magicast@0.3.5)(rollup@4.52.4)(terser@5.44.0)(typescript@5.9.3)(vite@7.1.9(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(yaml@2.8.1) + sortablejs: + specifier: ^1.15.6 + version: 1.15.6 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -3152,6 +3155,9 @@ packages: smob@1.5.0: resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + sortablejs@1.15.6: + resolution: {integrity: sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -4439,7 +4445,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/ui@4.0.1(@babel/parser@7.28.4)(db0@0.3.4)(embla-carousel@8.6.0)(ioredis@5.8.1)(magicast@0.3.5)(typescript@5.9.3)(vite@7.1.9(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-router@4.5.1(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))(zod@4.1.12)': + '@nuxt/ui@4.0.1(@babel/parser@7.28.4)(db0@0.3.4)(embla-carousel@8.6.0)(ioredis@5.8.1)(magicast@0.3.5)(sortablejs@1.15.6)(typescript@5.9.3)(vite@7.1.9(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1))(vue-router@4.5.1(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))(zod@4.1.12)': dependencies: '@ai-sdk/vue': 2.0.68(vue@3.5.22(typescript@5.9.3))(zod@4.1.12) '@iconify/vue': 5.0.0(vue@3.5.22(typescript@5.9.3)) @@ -4456,7 +4462,7 @@ snapshots: '@tanstack/vue-table': 8.21.3(vue@3.5.22(typescript@5.9.3)) '@unhead/vue': 2.0.19(vue@3.5.22(typescript@5.9.3)) '@vueuse/core': 13.9.0(vue@3.5.22(typescript@5.9.3)) - '@vueuse/integrations': 13.9.0(fuse.js@7.1.0)(vue@3.5.22(typescript@5.9.3)) + '@vueuse/integrations': 13.9.0(fuse.js@7.1.0)(sortablejs@1.15.6)(vue@3.5.22(typescript@5.9.3)) colortranslator: 5.0.0 consola: 3.4.2 defu: 6.1.4 @@ -5292,13 +5298,14 @@ snapshots: '@vueuse/shared': 13.9.0(vue@3.5.22(typescript@5.9.3)) vue: 3.5.22(typescript@5.9.3) - '@vueuse/integrations@13.9.0(fuse.js@7.1.0)(vue@3.5.22(typescript@5.9.3))': + '@vueuse/integrations@13.9.0(fuse.js@7.1.0)(sortablejs@1.15.6)(vue@3.5.22(typescript@5.9.3))': dependencies: '@vueuse/core': 13.9.0(vue@3.5.22(typescript@5.9.3)) '@vueuse/shared': 13.9.0(vue@3.5.22(typescript@5.9.3)) vue: 3.5.22(typescript@5.9.3) optionalDependencies: fuse.js: 7.1.0 + sortablejs: 1.15.6 '@vueuse/metadata@10.11.1': {} @@ -7237,6 +7244,8 @@ snapshots: smob@1.5.0: {} + sortablejs@1.15.6: {} + source-map-js@1.2.1: {} source-map-support@0.5.21:
+ {{ row.original.brand }} + {{ row.original.name }} +
+ {{ row.original.name }} +
{{ exportCsv }}