<script setup>
import { ref, onMounted, watch } from 'vue';
import { useRoute } from 'vue-router';
import { FilterMatchMode } from '@primevue/core/api';
import { useToast } from 'primevue/usetoast';
import {
GetProduct,
SyncShopGoods,
StartOcrService,
OneKeyUploadProduct,
OneKeyDeleteProducts,
DeleteProduct
} from '@wails/go/main/App';
import { EventsOn } from '/wailsjs/runtime/runtime.js';
const route = useRoute();
const toast = useToast();
// 店铺ID
const shopid = ref(route.query.shopid || '');
// 状态映射
const statusMap = {
0: { label: '在售', severity: 'success' },
1: { label: '已删除', severity: 'danger' }
};
// UI状态
const isLoading = ref(false);
const page = ref(1);
const pageSize = ref(20);
const totalGoods = ref(0);
const syncValue = ref(0); // 同步商品进度
const syncOcrValue = ref(0); // OCR识别进度
const switchValue = ref(false);
const isSyncGoodsVisible = ref(false);
const isSyncOcrVisible = ref(false);
// 数据
const products = ref([]);
const selectedProducts = ref();
// 对话框状态
const oneKeyUploadProductDialog = ref(false);
const delSingleProductDialog = ref(false);
const deleteProductsDialog = ref(false);
const product = ref({});
// 过滤器
const filters = ref({
global: { value: null, matchMode: FilterMatchMode.CONTAINS }
});
// 计算总记录数
function totalRecord() {
return totalGoods.value;
}
// 格式化货币
function formatCurrency(value) {
return value ? `¥${(value / 100).toFixed(2)}` : '';
}
// 加载商品数据
const loadProducts = async () => {
if (!shopid.value) return;
try {
isLoading.value = true;
const response = await GetProduct(shopid.value, pageSize.value, page.value);
// 更新分页信息
totalGoods.value = response.total;
// 更新商品数据
products.value = response.data.map(item => ({
id: item.product_id,
code: item.product_id,
name: item.name,
image: item.img || 'product-placeholder.svg',
price: item.market_price,
inventoryStatus: statusMap[item.status_del]?.label || '未知',
statusValue: item.status_del
}));
} catch (error) {
toast.add({
severity: 'error',
summary: '加载失败',
detail: '无法获取商品数据',
life: 3000
});
console.error("商品加载失败:", error);
} finally {
isLoading.value = false;
}
};
// 监听路由参数变化
watch(() => route.query, (newQuery) => {
if (newQuery.shopid !== shopid.value) {
shopid.value = newQuery.shopid || '';
page.value = 1;
loadProducts();
}
}, { immediate: true });
// 分页事件处理
const onPageChange = (event) => {
page.value = event.page + 1;
pageSize.value = event.rows;
loadProducts();
};
onMounted(() => {
if (shopid.value) {
loadProducts();
}
// 监听同步进度事件
EventsOn("notifySyncGoods", (result) => {
syncValue.value = parseInt(result);
console.log("商品同步进度:", result);
// 进度达到100%后隐藏进度条
if (syncValue.value >= 100) {
setTimeout(() => {
isSyncGoodsVisible.value = false;
toast.add({
severity: 'success',
summary: '同步完成',
detail: '商品同步成功',
life: 3000
});
}, 1000);
}
});
// 监听OCR识别进度事件
EventsOn("notifySyncOcr", (result) => {
syncOcrValue.value = parseInt(result);
console.log("OCR识别进度:", result);
// 进度达到100%后隐藏进度条
if (syncOcrValue.value >= 100) {
setTimeout(() => {
isSyncOcrVisible.value = false;
toast.add({
severity: 'success',
summary: '识别完成',
detail: '图片识别成功',
life: 3000
});
}, 1000);
}
});
});
// 1. 同步商品
async function startSyncGoods() {
if (!shopid.value) {
toast.add({
severity: 'warn',
summary: '未选择店铺',
detail: '请先选择店铺',
life: 3000
});
return;
}
try {
isSyncGoodsVisible.value = true;
syncValue.value = 0; // 重置进度
const result = await SyncShopGoods(shopid.value);
if (!result) {
throw new Error('同步失败,无返回结果');
}
// 刷新商品列表
setTimeout(() => {
loadProducts();
}, 1500);
} catch (error) {
isSyncGoodsVisible.value = false;
toast.add({
severity: 'error',
summary: '同步失败',
detail: error.message || '商品同步失败',
life: 3000
});
console.error("商品同步失败:", error);
}
}
// 2. OCR识别
async function startOcrService() {
if (!shopid.value) {
toast.add({
severity: 'warn',
summary: '未选择店铺',
detail: '请先选择店铺',
life: 3000
});
return;
}
try {
isSyncOcrVisible.value = true;
syncOcrValue.value = 0; // 重置进度
const result = await StartOcrService(shopid.value);
if (!result) {
throw new Error('识别失败,无返回结果');
}
} catch (error) {
isSyncOcrVisible.value = false;
toast.add({
severity: 'error',
summary: '识别失败',
detail: error.message || '图片识别失败',
life: 3000
});
console.error("OCR识别失败:", error);
}
}
// 3. 一键上架
async function oneKeyUploadProduct() {
if (!shopid.value) {
toast.add({
severity: 'warn',
summary: '未选择店铺',
detail: '请先选择店铺',
life: 3000
});
return;
}
try {
const success = await OneKeyUploadProduct(shopid.value);
if (success) {
toast.add({
severity: 'success',
summary: '上架成功',
detail: '商品已成功上架',
life: 3000
});
// 刷新列表
loadProducts();
} else {
throw new Error('上架失败');
}
} catch (error) {
toast.add({
severity: 'error',
summary: '上架失败',
detail: error.message || '商品上架失败',
life: 3000
});
console.error("一键上架失败:", error);
} finally {
oneKeyUploadProductDialog.value = false;
}
}
// 4. 删除单个商品
async function deleteProduct() {
if (!product.value.id) return;
try {
const success = await DeleteProduct(shopid.value, product.value.id);
if (success) {
toast.add({
severity: 'success',
summary: '删除成功',
detail: `商品 "${product.value.name}" 已删除`,
life: 3000
});
// 刷新列表
loadProducts();
} else {
throw new Error('删除失败');
}
} catch (error) {
toast.add({
severity: 'error',
summary: '删除失败',
detail: error.message || `删除商品 "${product.value.name}" 失败`,
life: 3000
});
console.error("删除商品失败:", error);
} finally {
delSingleProductDialog.value = false;
}
}
// 5. 一键删除商品
async function oneKeyDeleteProducts() {
if (!shopid.value) {
toast.add({
severity: 'warn',
summary: '未选择店铺',
detail: '请先选择店铺',
life: 3000
});
return;
}
try {
const success = await OneKeyDeleteProducts(shopid.value);
if (success) {
toast.add({
severity: 'success',
summary: '删除成功',
detail: '所有商品已成功删除',
life: 3000
});
// 刷新列表
loadProducts();
} else {
throw new Error('删除失败');
}
} catch (error) {
toast.add({
severity: 'error',
summary: '删除失败',
detail: error.message || '删除所有商品失败',
life: 3000
});
console.error("一键删除失败:", error);
} finally {
deleteProductsDialog.value = false;
}
}
// 询问删除商品
function askDeleteProduct(prod) {
product.value = prod;
delSingleProductDialog.value = true;
}
// 询问一键上架
function askOneKeyUpload() {
oneKeyUploadProductDialog.value = true;
}
// 询问一键删除
function askOneKeyDeleteProducts() {
deleteProductsDialog.value = true;
}
</script>
<template>
<!-- 加载指示器 -->
<div v-if="isLoading" class="loading-overlay">
<ProgressSpinner />
<p>正在加载商品数据...</p>
</div>
<div>
<div class="card">
<Toolbar class="mb-6">
<template #start>
<Button label="同步商品" icon="pi pi-refresh" severity="secondary" class="mr-2" @click="startSyncGoods" />
<Button label="图片识别" icon="pi pi-check" severity="secondary" @click="startOcrService" />
</template>
<template #end>
<Button label="一键上架" icon="pi pi-upload" severity="secondary" @click="askOneKeyUpload" />
<Button label="一键删除" icon="pi pi-trash" severity="secondary" @click="askOneKeyDeleteProducts" />
</template>
</Toolbar>
<!-- DataTable组件 -->
<DataTable
v-model:selection="selectedProducts"
:value="products"
:lazy="true"
dataKey="id"
:rowHover="true"
:paginator="true"
:rows="pageSize"
:totalRecords="totalRecord()"
:filters="filters"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
:rowsPerPageOptions="[10, 20, 50]"
currentPageReportTemplate="显示第 {first} 到 {last} 条,共 {totalRecords} 条商品"
@page="onPageChange"
>
<template #header>
<div class="flex flex-wrap gap-2 items-center justify-between">
<h4 class="m-0">商品列表 - {{ shopid }} - 商品总数 {{ totalGoods }}</h4>
<IconField>
<InputIcon>
<i class="pi pi-search" />
</InputIcon>
<InputText v-model="filters['global'].value" placeholder="搜索商品..." />
</IconField>
</div>
</template>
<!-- 列定义 -->
<Column selectionMode="multiple" headerStyle="width: 3.5rem" :exportable="false"></Column>
<Column header="商品图片" bodyStyle="text-align: center">
<template #body="slotProps">
<div class="fixed-image-container">
<img
:src="slotProps.data.image"
:alt="slotProps.data.name"
class="fixed-image"
/>
</div>
</template>
</Column>
<Column field="code" header="商品ID" sortable style="min-width: 12rem"></Column>
<Column field="name" header="商品名称" sortable style="min-width: 16rem"></Column>
<Column field="price" header="价格" sortable style="min-width: 8rem">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="inventoryStatus" header="状态" sortable style="min-width: 12rem">
<template #body="slotProps">
<Tag
:value="slotProps.data.inventoryStatus"
:severity="statusMap[slotProps.data.statusValue]?.severity || 'info'"
/>
</template>
</Column>
<Column header="操作" style="min-width: 9rem">
<template #body="slotProps">
<div class="action-buttons">
<Button
icon="pi pi-pencil"
outlined
rounded
severity="secondary"
class="action-btn"
size="small"
/>
<Button
icon="pi pi-trash"
outlined
rounded
severity="danger"
class="action-btn"
size="small"
@click="askDeleteProduct(slotProps.data)"
/>
<ToggleSwitch v-model="switchValue" />
</div>
</template>
</Column>
</DataTable>
</div>
<!-- 进度指示器 -->
<div class="card" v-show="isSyncGoodsVisible" >
<div class="font-semibold text-xl mb-4">当前商品同步进度</div>
<ProgressBar :value="syncValue"></ProgressBar>
<div class="mt-2 text-right">已完成 {{ syncValue }}%</div>
</div>
<div class="card" v-show="isSyncOcrVisible" >
<div class="font-semibold text-xl mb-4">当前图片识别进度</div>
<ProgressBar :value="syncOcrValue"></ProgressBar>
<div class="mt-2 text-right">已完成 {{ syncOcrValue }}%</div>
</div>
<!-- 对话框 -->
<Dialog v-model:visible="oneKeyUploadProductDialog" :style="{ width: '450px' }" header="确认操作" :modal="true">
<div class="flex items-center gap-4">
<i class="pi pi-exclamation-triangle text-yellow-500 text-3xl" />
<span>是否确认执行一键上架商品任务?</span>
</div>
<template #footer>
<Button label="取消" icon="pi pi-times" text @click="oneKeyUploadProductDialog = false" />
<Button label="确认上架" icon="pi pi-check" @click="oneKeyUploadProduct" />
</template>
</Dialog>
<Dialog v-model:visible="delSingleProductDialog" :style="{ width: '450px' }" header="确认删除" :modal="true">
<div class="flex items-center gap-4">
<i class="pi pi-exclamation-triangle text-yellow-500 text-3xl" />
<span v-if="product">
是否确认删除商品 <b>{{ product.name }}</b>?
</span>
</div>
<template #footer>
<Button label="取消" icon="pi pi-times" text @click="delSingleProductDialog = false" />
<Button label="确认删除" icon="pi pi-trash" severity="danger" @click="deleteProduct" />
</template>
</Dialog>
<Dialog v-model:visible="deleteProductsDialog" :style="{ width: '450px' }" header="确认操作" :modal="true">
<div class="flex items-center gap-4">
<i class="pi pi-exclamation-triangle text-yellow-500 text-3xl" />
<span>是否确认删除所有商品?</span>
</div>
<template #footer>
<Button label="取消" icon="pi pi-times" text @click="deleteProductsDialog = false" />
<Button label="确认删除" icon="pi pi-trash" severity="danger" @click="oneKeyDeleteProducts" />
</template>
</Dialog>
</div>
</template>
<style scoped>
/* 图片样式 */
.fixed-image-container {
display: flex;
justify-content: center;
align-items: center;
width: 70px;
height: 70px;
padding: 4px;
background-color: #f8f9fa;
border-radius: 4px;
}
.fixed-image {
width: 64px;
height: 64px;
object-fit: contain;
border-radius: 3px;
}
/* 操作按钮 */
.action-buttons {
display: flex;
justify-content: center;
gap: 0.5rem;
}
.action-btn {
width: 2.2rem;
height: 2.2rem;
display: flex;
align-items: center;
justify-content: center;
}
/* 表头样式优化 */
:deep(.p-datatable-thead th) {
background-color: #f8f9fa;
font-weight: 500;
}
:deep(.p-datatable-tbody td) {
padding: 0.5rem 1rem;
}
/* 响应式调整 */
@media (max-width: 1200px) {
:deep(.p-datatable) {
font-size: 0.9rem;
}
.fixed-image-container {
width: 64px;
height: 64px;
}
.fixed-image {
width: 58px;
height: 58px;
}
}
@media (max-width: 992px) {
:deep(.p-datatable) {
font-size: 0.85rem;
}
.card {
overflow-x: auto;
}
}
/* 加载指示器 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
}
.loading-overlay p {
margin-top: 1rem;
color: var(--primary-color);
}
/* 进度卡片样式增强 */
.card {
margin-top: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
</style>
这段代码是VUE的前端代码,请帮我修改notifySyncGoods的代码,和go后端的代码配套