Skip to content

Commit 4cb54d2

Browse files
authored
Merge pull request #2 from BluemsunOffice/codex-admin-search-export
管理员增加搜索和导出文件
2 parents c262c18 + ac067d4 commit 4cb54d2

18 files changed

Lines changed: 3613 additions & 38 deletions

.history/src/stores/order-store_20260421121936.ts

Lines changed: 443 additions & 0 deletions
Large diffs are not rendered by default.

.history/src/stores/order-store_20260421125513.ts

Lines changed: 443 additions & 0 deletions
Large diffs are not rendered by default.

.history/src/stores/order-store_20260421125526.ts

Lines changed: 443 additions & 0 deletions
Large diffs are not rendered by default.

.history/src/stores/order-store_20260421164518.ts

Lines changed: 479 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
<template>
2+
<main class="manage-page">
3+
<NavBar />
4+
5+
<section class="manage-container">
6+
<header class="manage-header">
7+
<div>
8+
<h2>商品管理</h2>
9+
<p>支持商品新增、编辑、删除与进货操作</p>
10+
</div>
11+
12+
<div class="header-actions">
13+
<el-popconfirm title="确认删除选中的商品吗?" @confirm="handleBatchDelete">
14+
<template #reference>
15+
<el-button type="danger" :icon="Delete">批量删除</el-button>
16+
</template>
17+
</el-popconfirm>
18+
<el-button type="primary" :icon="Plus" @click="openCreate">新增商品</el-button>
19+
</div>
20+
</header>
21+
22+
<el-table
23+
:data="goodsList"
24+
border
25+
v-loading="loading"
26+
@selection-change="setSelectedRows"
27+
class="manage-table"
28+
>
29+
<el-table-column type="selection" width="48" />
30+
<el-table-column prop="name" label="商品名称" min-width="160" />
31+
<el-table-column label="商品图片" width="120">
32+
<template #default="{ row }">
33+
<el-image class="goods-image" :src="row.imageUrlUrl" fit="cover" />
34+
</template>
35+
</el-table-column>
36+
<el-table-column label="类型" width="110">
37+
<template #default="{ row }">{{ formatGoodsType(row.type) }}</template>
38+
</el-table-column>
39+
<el-table-column label="货币" width="95">
40+
<template #default="{ row }">{{ formatCurrencyType(row.currencyType) }}</template>
41+
</el-table-column>
42+
<el-table-column prop="price" label="价格" width="90" />
43+
<el-table-column prop="amount" label="库存" width="90" />
44+
<el-table-column label="状态" width="100">
45+
<template #default="{ row }">
46+
<el-tag size="small" :type="row.status === '0' ? 'success' : 'info'">
47+
{{ row.status === '0' ? '上架中' : '下架' }}
48+
</el-tag>
49+
</template>
50+
</el-table-column>
51+
<el-table-column label="操作" min-width="260" fixed="right">
52+
<template #default="{ row }">
53+
<el-button link type="primary" :icon="Edit" @click="openEdit(row)">编辑</el-button>
54+
<el-button link type="success" :icon="Plus" @click="openRestock(row)">进货</el-button>
55+
<el-popconfirm title="确认删除该商品吗?" @confirm="() => handleDeleteOne(row.id)">
56+
<template #reference>
57+
<el-button link type="danger" :icon="Delete">删除</el-button>
58+
</template>
59+
</el-popconfirm>
60+
</template>
61+
</el-table-column>
62+
</el-table>
63+
64+
<div class="pagination-wrap">
65+
<el-pagination
66+
:model-value="pager.pageNum"
67+
:page-size="pager.pageSize"
68+
layout="prev,pager,next,jumper,total"
69+
:total="total"
70+
@current-change="changePage"
71+
/>
72+
</div>
73+
</section>
74+
75+
<el-dialog
76+
v-model="formDialogVisible"
77+
:title="isEditing ? '编辑商品' : '新增商品'"
78+
width="560"
79+
align-center
80+
destroy-on-close
81+
>
82+
<el-form label-width="92px" class="goods-form">
83+
<el-form-item label="商品名称">
84+
<el-input v-model="formModel.name" maxlength="40" show-word-limit />
85+
</el-form-item>
86+
<el-form-item label="商品价格">
87+
<el-input-number v-model="formModel.price" :min="0" :precision="2" :step="1" />
88+
</el-form-item>
89+
<el-form-item label="商品类型">
90+
<el-select v-model="formModel.type" class="w-full">
91+
<el-option label="日用品" value="0" />
92+
<el-option label="服装" value="1" />
93+
<el-option label="学习用品" value="2" />
94+
</el-select>
95+
</el-form-item>
96+
<el-form-item label="货币类型">
97+
<el-select v-model="formModel.currencyType" class="w-full">
98+
<el-option label="日用币" value="0" />
99+
<el-option label="服装币" value="1" />
100+
</el-select>
101+
</el-form-item>
102+
<el-form-item label="商品状态">
103+
<el-radio-group v-model="formModel.status">
104+
<el-radio value="0">上架中</el-radio>
105+
<el-radio value="1">下架</el-radio>
106+
</el-radio-group>
107+
</el-form-item>
108+
<el-form-item label="限购数量">
109+
<el-input-number v-model="formModel.limitNum" :min="1" :step="1" />
110+
</el-form-item>
111+
<el-form-item label="限购周期">
112+
<el-radio-group v-model="formModel.limitType">
113+
<el-radio value="0">一个月</el-radio>
114+
<el-radio value="1">一学期</el-radio>
115+
</el-radio-group>
116+
</el-form-item>
117+
<el-form-item v-if="isEditing" label="库存">
118+
<el-input-number v-model="formModel.amount" :min="0" :step="1" />
119+
</el-form-item>
120+
<el-form-item label="商品介绍">
121+
<el-input
122+
v-model="formModel.intro"
123+
type="textarea"
124+
:rows="3"
125+
maxlength="200"
126+
show-word-limit
127+
/>
128+
</el-form-item>
129+
<el-form-item label="商品图片">
130+
<div class="image-uploader">
131+
<input
132+
ref="imageInputRef"
133+
type="file"
134+
accept="image/*"
135+
class="hidden-input"
136+
@change="handleImageChange"
137+
/>
138+
<el-button :icon="Upload" @click="triggerImageInput">上传图片</el-button>
139+
<el-image
140+
v-if="formModel.imageUrlUrl"
141+
class="preview-image"
142+
:src="formModel.imageUrlUrl"
143+
fit="cover"
144+
/>
145+
</div>
146+
</el-form-item>
147+
</el-form>
148+
149+
<template #footer>
150+
<el-button @click="formDialogVisible = false">取消</el-button>
151+
<el-button type="primary" :loading="submitting" @click="submitGoods">
152+
{{ isEditing ? '保存修改' : '确认新增' }}
153+
</el-button>
154+
</template>
155+
</el-dialog>
156+
157+
<el-dialog v-model="restockDialogVisible" title="增加库存" width="420" align-center>
158+
<el-form label-width="90px">
159+
<el-form-item label="商品名称">
160+
<span>{{ restockGoodsName }}</span>
161+
</el-form-item>
162+
<el-form-item label="进货数量">
163+
<el-input-number v-model="restockAmount" :min="1" :step="1" />
164+
</el-form-item>
165+
</el-form>
166+
167+
<template #footer>
168+
<el-button @click="restockDialogVisible = false">取消</el-button>
169+
<el-button type="primary" @click="submitRestock">确认</el-button>
170+
</template>
171+
</el-dialog>
172+
</main>
173+
</template>
174+
175+
<script setup lang="ts">
176+
import { onMounted, ref } from 'vue'
177+
import { storeToRefs } from 'pinia'
178+
import { Delete, Edit, Plus, Upload } from '@element-plus/icons-vue'
179+
import NavBar from '@/components/nav-bar/index.vue'
180+
import { useManageGoodsStore } from '@/stores/manage-goods-store'
181+
import type { GoodsCurrencyType, GoodsType, ManageGoodsItem } from '@/api/manage-goods.api'
182+
183+
const manageGoodsStore = useManageGoodsStore()
184+
185+
const {
186+
goodsList,
187+
loading,
188+
total,
189+
pager,
190+
selectedIds,
191+
formDialogVisible,
192+
restockDialogVisible,
193+
submitting,
194+
formModel,
195+
restockGoodsName,
196+
restockAmount,
197+
isEditing,
198+
} = storeToRefs(manageGoodsStore)
199+
200+
const {
201+
fetchGoods,
202+
changePage,
203+
setSelectedRows,
204+
deleteGoods,
205+
openCreateDialog,
206+
openEditDialog,
207+
uploadImage,
208+
submitGoods,
209+
openRestockDialog,
210+
submitRestock,
211+
} = manageGoodsStore
212+
213+
const imageInputRef = ref<HTMLInputElement | null>(null)
214+
215+
const goodsTypeMap: Record<GoodsType, string> = {
216+
'0': '日用品',
217+
'1': '服装',
218+
'2': '学习用品',
219+
}
220+
221+
const currencyTypeMap: Record<GoodsCurrencyType, string> = {
222+
'0': '日用币',
223+
'1': '服装币',
224+
}
225+
226+
const formatGoodsType = (value: GoodsType) => goodsTypeMap[value] || '未知类型'
227+
const formatCurrencyType = (value: GoodsCurrencyType) => currencyTypeMap[value] || '未知货币'
228+
229+
const openCreate = () => {
230+
openCreateDialog()
231+
}
232+
233+
const openEdit = (item: ManageGoodsItem) => {
234+
openEditDialog(item)
235+
}
236+
237+
const openRestock = (item: ManageGoodsItem) => {
238+
openRestockDialog(item)
239+
}
240+
241+
const handleDeleteOne = async (id: string) => {
242+
await deleteGoods([id])
243+
}
244+
245+
const handleBatchDelete = async () => {
246+
await deleteGoods(selectedIds.value)
247+
}
248+
249+
const triggerImageInput = () => {
250+
imageInputRef.value?.click()
251+
}
252+
253+
const handleImageChange = async (event: Event) => {
254+
const target = event.target as HTMLInputElement
255+
const file = target.files?.[0]
256+
if (!file) {
257+
return
258+
}
259+
260+
await uploadImage(file)
261+
target.value = ''
262+
}
263+
264+
onMounted(() => {
265+
fetchGoods()
266+
})
267+
</script>
268+
269+
<style scoped>
270+
.manage-page {
271+
min-height: 100vh;
272+
}
273+
274+
.manage-container {
275+
max-width: 1320px;
276+
margin: 20px auto;
277+
padding: 16px;
278+
border-radius: 12px;
279+
background: #fff;
280+
border: 1px solid #ebeef5;
281+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
282+
}
283+
284+
.manage-header {
285+
display: flex;
286+
align-items: center;
287+
justify-content: space-between;
288+
gap: 12px;
289+
margin-bottom: 12px;
290+
}
291+
292+
.manage-header h2 {
293+
margin: 0;
294+
font-size: 22px;
295+
color: #303133;
296+
}
297+
298+
.manage-header p {
299+
margin: 6px 0 0;
300+
font-size: 13px;
301+
color: #909399;
302+
}
303+
304+
.header-actions {
305+
display: flex;
306+
gap: 8px;
307+
}
308+
309+
.manage-table :deep(.el-table__cell) {
310+
font-size: 14px;
311+
}
312+
313+
.goods-image {
314+
width: 72px;
315+
height: 72px;
316+
border-radius: 6px;
317+
}
318+
319+
.pagination-wrap {
320+
margin-top: 16px;
321+
display: flex;
322+
justify-content: flex-end;
323+
}
324+
325+
.goods-form {
326+
padding-right: 8px;
327+
}
328+
329+
.w-full {
330+
width: 100%;
331+
}
332+
333+
.image-uploader {
334+
display: flex;
335+
align-items: center;
336+
gap: 10px;
337+
}
338+
339+
.hidden-input {
340+
display: none;
341+
}
342+
343+
.preview-image {
344+
width: 72px;
345+
height: 72px;
346+
border-radius: 8px;
347+
border: 1px solid #ebeef5;
348+
}
349+
350+
@media (max-width: 900px) {
351+
.manage-container {
352+
margin: 12px;
353+
padding: 12px;
354+
}
355+
356+
.manage-header {
357+
flex-direction: column;
358+
align-items: flex-start;
359+
}
360+
361+
.header-actions {
362+
width: 100%;
363+
}
364+
365+
.header-actions .el-button {
366+
flex: 1;
367+
}
368+
369+
.pagination-wrap {
370+
justify-content: center;
371+
}
372+
}
373+
</style>

0 commit comments

Comments
 (0)