feat: add Shopee product management page

This commit is contained in:
QiuSW
2026-09-12 09:18:21 +08:00
parent 81f6464d6a
commit df3a0067aa
4 changed files with 123 additions and 0 deletions
@@ -0,0 +1,30 @@
package version
import (
"fmt"
"go-admin/app/admin/models"
"go-admin/cmd/migrate/migration"
common "go-admin/common/models"
"gorm.io/gorm"
)
func init() { migration.Migrate.SetVersion("1789257600000", migrateShopeeProductPage) }
func migrateShopeeProductPage(db *gorm.DB, version string) error {
if err := db.AutoMigrate(&models.ShopeeProduct{}); err != nil {
return err
}
return db.Transaction(func(tx *gorm.DB) error {
var parent models.SysMenu
if err := tx.Where("title = ? AND menu_type = ?", "系统管理", "M").First(&parent).Error; err != nil {
return err
}
menu := models.SysMenu{MenuName: "ShopeeProductManagement", Title: "Shopee 商品", Icon: "shopping", Path: "/admin/shopee-products", MenuType: "C", ParentId: parent.MenuId, Component: "/admin/shopee-product/index", Visible: "0", NoCache: true, Sort: 91, IsFrame: "1"}
if err := tx.Where("menu_name = ?", menu.MenuName).FirstOrCreate(&menu).Error; err != nil {
return err
}
if err := tx.Model(&menu).Update("paths", fmt.Sprintf("%s/%d", parent.Paths, menu.MenuId)).Error; err != nil {
return err
}
return tx.Create(&common.Migration{Version: version}).Error
})
}
+28
View File
@@ -0,0 +1,28 @@
# LOCAL-006:Shopee 商品查询页面
- 状态:进行中
- 来源:2026-09-12 用户「建006工单,做」
- 前置:LOCAL-004、LOCAL-005
## 目标
在 Admin 中查询和查看已保存的 Shopee 商品,并提供按 Shopee ID 从货憨憨同步的入口。
## 范围
- Shopee ID 查询;
- 查询结果落库并在列表显示;
- 商品详情和原始 JSON 查看;
- 标题、店铺、状态、价格、主图和更新时间展示;
- 加载、空结果、错误和重复查询反馈。
## 非目标
商品编辑、图片上传、视频上传、批量操作和定时同步。
## 验收
- Admin 菜单可访问;
- 有效 Shopee ID 查询成功并可在列表和详情中查看;
- 重复查询更新已有记录;
- 无效 ID、认证失败和网络错误有明确提示;
- `go test ./app/admin/...` 和前端编译通过。
## 证据
实施中。
+4
View File
@@ -0,0 +1,4 @@
import request from '@/utils/request'
export const listProducts = shopeeId => request({ url: '/api/v1/shopee/products', method: 'get', params: shopeeId ? { shopeeId } : {} })
export const syncProduct = shopeeId => request({ url: `/api/v1/shopee/products/${encodeURIComponent(shopeeId)}`, method: 'get' })
@@ -0,0 +1,61 @@
<template>
<div class="app-container product-page">
<h2>Shopee 商品</h2>
<p class="hint">输入 Shopee 商品 ID,从货憨憨同步并保存商品信息。</p>
<div class="toolbar">
<el-input v-model="searchId" placeholder="Shopee ID" clearable maxlength="128" aria-label="Shopee ID" @keyup.enter="search" />
<el-button type="primary" :loading="syncing" @click="sync">查询并同步</el-button>
<el-button :loading="loading" @click="load">刷新已保存商品</el-button>
</div>
<el-alert v-if="error" :title="error" type="error" show-icon :closable="false" />
<el-table v-loading="loading" :data="rows" border row-key="id" :empty-text="error ? '数据暂不可用' : '暂无已保存商品'">
<el-table-column prop="shopeeId" label="Shopee ID" min-width="150" />
<el-table-column prop="itemName" label="商品标题" min-width="240" show-overflow-tooltip />
<el-table-column prop="shopName" label="店铺" min-width="140" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="120" />
<el-table-column label="更新时间" min-width="180"><template #default="{ row }">{{ formatDate(row.updatedAt) }}</template></el-table-column>
<el-table-column label="操作" width="110" fixed="right"><template #default="{ row }"><el-button size="small" @click="showDetail(row)">详情</el-button></template></el-table-column>
</el-table>
<el-dialog v-model="detailOpen" title="商品详情" width="min(760px, 94vw)" top="5vh">
<template v-if="selected">
<el-descriptions :column="2" border>
<el-descriptions-item label="Shopee ID">{{ selected.shopeeId }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ selected.status || '—' }}</el-descriptions-item>
<el-descriptions-item label="店铺">{{ selected.shopName || '—' }}</el-descriptions-item>
<el-descriptions-item label="标题" :span="2">{{ selected.itemName || '—' }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ formatDate(selected.updatedAt) }}</el-descriptions-item>
</el-descriptions>
<h4>原始 JSON</h4>
<pre class="json">{{ prettyPayload(selected.payload) }}</pre>
</template>
</el-dialog>
</div>
</template>
<script>
import { listProducts, syncProduct } from '@/api/admin/shopee-product'
import { ElMessage } from 'element-plus'
export default {
name: 'ShopeeProductManagement',
data: () => ({ rows: [], searchId: '', loading: false, syncing: false, error: '', detailOpen: false, selected: null }),
mounted() { this.load() },
methods: {
formatDate(value) { if (!value) return '—'; const d = new Date(value); return Number.isNaN(d.getTime()) ? '—' : d.toLocaleString('zh-CN', { hour12: false }) },
prettyPayload(value) { try { return JSON.stringify(JSON.parse(value), null, 2) } catch (_) { return value || '—' } },
async load() { this.loading = true; this.error = ''; try { const r = await listProducts(this.searchId.trim()); this.rows = r.data || [] } catch (_) { this.error = '读取商品失败,请点击刷新重试' } finally { this.loading = false } },
async sync() { const id = this.searchId.trim(); if (!id) { ElMessage.warning('请输入 Shopee ID'); return }; this.syncing = true; this.error = ''; try { const r = await syncProduct(id); ElMessage.success('商品同步成功'); await this.load(); const found = this.rows.find(row => row.shopeeId === id); if (found) this.showDetail(found); else if (r.data) this.selected = r.data } catch (_) { this.error = '商品查询失败,请检查 API Key、登录状态和 Shopee ID' } finally { this.syncing = false } },
showDetail(row) { this.selected = row; this.detailOpen = true }
}
}
</script>
<style scoped>
.product-page { padding: 24px; }
.hint { color: var(--el-text-color-regular); line-height: 1.6; }
.toolbar { display: flex; flex-wrap: wrap; gap: 12px; margin: 20px 0; }
.toolbar .el-input { width: 300px; max-width: 100%; }
.toolbar .el-button { margin-left: 0; }
.el-alert { margin-bottom: 12px; }
.json { max-height: 360px; overflow: auto; padding: 16px; background: var(--el-fill-color-light); border-radius: 4px; white-space: pre-wrap; word-break: break-word; }
@media (max-width: 600px) { .product-page { padding: 12px; } .toolbar .el-input { width: 100%; } }
</style>