|
|
@@ -20,6 +20,8 @@
|
|
|
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
|
|
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
|
|
<el-button type="success" :disabled="multiple" @click="handleBatchPush">批量推送</el-button>
|
|
|
+ <el-button type="info" icon="Upload" @click="handleImport">导入</el-button>
|
|
|
+ <el-button type="warning" icon="Download" @click="handleImportTemplate">下载模板</el-button>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
</el-card>
|
|
|
@@ -55,13 +57,8 @@
|
|
|
<el-table-column label="平档价" align="center" prop="standardPrice" width="100" />
|
|
|
<el-table-column label="供应价" align="center" prop="purchasePrice" width="100" />
|
|
|
<el-table-column label="第三方售价" align="center" prop="externalPrice" width="100" />
|
|
|
- <el-table-column label="限定库存" align="center" prop="availableStock" width="100" />
|
|
|
- <el-table-column label="可用库存" align="center" prop="availableStock" width="100" />
|
|
|
- <el-table-column label="总订单" align="center" width="100">
|
|
|
- <template #default="scope">
|
|
|
- <span>0</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
+ <el-table-column label="总库存" align="center" prop="totalInventory" width="100" />
|
|
|
+
|
|
|
<el-table-column label="上架状态" align="center" prop="productStatus" width="100">
|
|
|
<template #default="scope">
|
|
|
<el-tag v-if="scope.row.productStatus == '1'" type="success">已上架</el-tag>
|
|
|
@@ -80,6 +77,9 @@
|
|
|
<el-tooltip content="编辑" placement="top">
|
|
|
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" >编辑</el-button>
|
|
|
</el-tooltip>
|
|
|
+ <el-tooltip content="编辑库存" placement="top">
|
|
|
+ <el-button link type="success" icon="Edit" @click="handleEditInventory(scope.row)">库存</el-button>
|
|
|
+ </el-tooltip>
|
|
|
<el-tooltip content="上下架" placement="top">
|
|
|
<el-button link type="primary" @click="handleToggleStatus(scope.row)">{{ scope.row.productStatus == '1' ? '下架' : '上架' }}</el-button>
|
|
|
</el-tooltip>
|
|
|
@@ -132,18 +132,28 @@
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="第三方平台售价:" prop="externalPrice">
|
|
|
- <el-input-number
|
|
|
- v-model="form.externalPrice"
|
|
|
- :precision="2"
|
|
|
- :min="0"
|
|
|
- :controls="false"
|
|
|
+ <el-input-number
|
|
|
+ v-model="form.externalPrice"
|
|
|
+ :precision="2"
|
|
|
+ :min="0"
|
|
|
+ :controls="false"
|
|
|
placeholder="请输入第三方平台售价"
|
|
|
- style="width: 100%"
|
|
|
+ style="width: 100%"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="备注:" prop="remark">
|
|
|
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
|
|
|
</el-form-item>
|
|
|
+ <el-form-item label="总库存:" prop="totalInventory">
|
|
|
+ <el-input-number
|
|
|
+ v-model="form.totalInventory"
|
|
|
+ :precision="0"
|
|
|
+ :min="0"
|
|
|
+ :controls="false"
|
|
|
+ placeholder="请输入总库存"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
</el-form>
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
|
@@ -152,17 +162,81 @@
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
+
|
|
|
+ <!-- 导入对话框 -->
|
|
|
+ <el-dialog v-model="importDialog.visible" :title="importDialog.title" width="400px" append-to-body>
|
|
|
+ <el-upload
|
|
|
+ ref="uploadRef"
|
|
|
+ drag
|
|
|
+ :limit="1"
|
|
|
+ :accept="importDialog.fileType"
|
|
|
+ :before-upload="handleBeforeUpload"
|
|
|
+ :on-exceed="handleExceed"
|
|
|
+ :http-request="handleFileUpload"
|
|
|
+ >
|
|
|
+ <el-icon class="el-icon--upload">
|
|
|
+ <upload-filled />
|
|
|
+ </el-icon>
|
|
|
+ <div class="el-upload__text">
|
|
|
+ 将文件拖到此处,或<em>点击上传</em>
|
|
|
+ </div>
|
|
|
+ <template #tip>
|
|
|
+ <div class="el-upload__tip text-center">
|
|
|
+ <span>仅支持 {{ importDialog.fileType }} 格式文件</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-upload>
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button type="warning" @click="importDialog.visible = false">取 消</el-button>
|
|
|
+ <el-button type="primary" @click="confirmImport">确 定</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 库存编辑对话框 -->
|
|
|
+ <el-dialog v-model="inventoryDialog.visible" :title="inventoryDialog.title" width="500px" append-to-body>
|
|
|
+ <el-form ref="inventoryFormRef" :model="inventoryForm" :rules="inventoryRules" label-width="100px">
|
|
|
+ <el-form-item label="商品编号:">
|
|
|
+ <el-input v-model="inventoryForm.productNo" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="商品名称:">
|
|
|
+ <el-input v-model="inventoryForm.itemName" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="当前库存:" prop="currentInventory">
|
|
|
+ <el-input-number
|
|
|
+ v-model="inventoryForm.currentInventory"
|
|
|
+ :precision="0"
|
|
|
+ :min="0"
|
|
|
+ :controls="true"
|
|
|
+ placeholder="请输入当前库存"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="备注:" prop="remark">
|
|
|
+ <el-input v-model="inventoryForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button type="warning" @click="inventoryDialog.visible = false">取 消</el-button>
|
|
|
+ <el-button type="primary" :loading="inventoryLoading" @click="submitInventoryForm">确 定</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup name="Product" lang="ts">
|
|
|
-import { getThirdProductPage, getProduct, updateProduct, shelfReview, batchPushProduct } from '@/api/external/product';
|
|
|
+import { getThirdProductPage, getProduct, updateProduct, shelfReview, batchPushProduct, importData, importTemplate } from '@/api/external/product';
|
|
|
import { ThirdProductVO, ProductQuery, ProductVO, ProductForm } from '@/api/external/product/types';
|
|
|
import { getProductCategoryTree } from '@/api/external/productCategory';
|
|
|
import { ProductCategoryVO } from '@/api/external/productCategory/types';
|
|
|
import { listPushPoolLog } from '@/api/external/pushPoolLog';
|
|
|
import { PushPoolLogVO } from '@/api/external/pushPoolLog/types';
|
|
|
import cache from '@/plugins/cache';
|
|
|
+import { UploadFilled } from '@element-plus/icons-vue';
|
|
|
+import type { UploadInstance, UploadRawFile } from 'element-plus';
|
|
|
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
|
@@ -180,6 +254,8 @@ const onShelfCount = ref(0);
|
|
|
const queryFormRef = ref<ElFormInstance>();
|
|
|
const productFormRef = ref<ElFormInstance>();
|
|
|
const productTableRef = ref();
|
|
|
+const uploadRef = ref<UploadInstance>();
|
|
|
+const inventoryFormRef = ref<ElFormInstance>();
|
|
|
|
|
|
const dialog = reactive<DialogOption>({
|
|
|
visible: false,
|
|
|
@@ -193,6 +269,20 @@ const logDrawer = reactive({
|
|
|
data: [] as PushPoolLogVO[]
|
|
|
});
|
|
|
|
|
|
+// 导入对话框数据
|
|
|
+const importDialog = reactive({
|
|
|
+ visible: false,
|
|
|
+ title: '导入商品数据',
|
|
|
+ fileType: '.xlsx, .xls',
|
|
|
+ selectedFile: null as File | null
|
|
|
+});
|
|
|
+
|
|
|
+// 库存编辑对话框数据
|
|
|
+const inventoryDialog = reactive({
|
|
|
+ visible: false,
|
|
|
+ title: '编辑库存'
|
|
|
+});
|
|
|
+
|
|
|
// 编辑模式标识
|
|
|
const isEditMode = ref(false);
|
|
|
|
|
|
@@ -205,7 +295,8 @@ const data = reactive<PageData<ProductForm, ProductQuery>>({
|
|
|
externalCategoryId: undefined,
|
|
|
externalPrice: 0,
|
|
|
pushStatus: 0,
|
|
|
- remark: undefined
|
|
|
+ remark: undefined,
|
|
|
+ totalInventory: 0
|
|
|
},
|
|
|
queryParams: {
|
|
|
pageNum: 1,
|
|
|
@@ -317,7 +408,8 @@ const reset = () => {
|
|
|
externalCategoryId: undefined,
|
|
|
externalPrice: 0,
|
|
|
pushStatus: 0,
|
|
|
- remark: undefined
|
|
|
+ remark: undefined,
|
|
|
+ totalInventory: 0
|
|
|
};
|
|
|
productFormRef.value?.resetFields();
|
|
|
}
|
|
|
@@ -353,17 +445,17 @@ const handleUpdate = async (row?: ThirdProductVO) => {
|
|
|
reset();
|
|
|
isEditMode.value = true;
|
|
|
dialog.title = '编辑对接信息';
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
// 确保分类数据已加载
|
|
|
if (externalCategoryList.value.length === 0) {
|
|
|
await initCategoryData();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 通过商品ID获取对接信息
|
|
|
const res = await getProduct(row!.id);
|
|
|
const productInfo = res.data;
|
|
|
-
|
|
|
+
|
|
|
form.value = {
|
|
|
id: productInfo.id,
|
|
|
productId: productInfo.productId,
|
|
|
@@ -372,9 +464,10 @@ const handleUpdate = async (row?: ThirdProductVO) => {
|
|
|
externalCategoryId: productInfo.externalCategoryId,
|
|
|
externalPrice: productInfo.externalPrice,
|
|
|
pushStatus: productInfo.pushStatus,
|
|
|
- remark: productInfo.remark
|
|
|
+ remark: productInfo.remark,
|
|
|
+ totalInventory: productInfo.totalInventory || 0
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
dialog.visible = true;
|
|
|
} catch (error) {
|
|
|
console.error('获取对接信息失败:', error);
|
|
|
@@ -385,7 +478,7 @@ const handleUpdate = async (row?: ThirdProductVO) => {
|
|
|
/** 提交按钮 */
|
|
|
const submitForm = async () => {
|
|
|
if (!productFormRef.value) return;
|
|
|
-
|
|
|
+
|
|
|
await productFormRef.value.validate(async (valid) => {
|
|
|
if (valid) {
|
|
|
buttonLoading.value = true;
|
|
|
@@ -399,7 +492,7 @@ const submitForm = async () => {
|
|
|
proxy?.$modal.msgWarning('暂不支持新增');
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
dialog.visible = false;
|
|
|
await getList();
|
|
|
} catch (error) {
|
|
|
@@ -421,18 +514,18 @@ const handleDelete = async (row?: ThirdProductVO) => {
|
|
|
const handleToggleStatus = async (row: ThirdProductVO) => {
|
|
|
const action = row.productStatus == '1' ? '下架' : '上架';
|
|
|
const newStatus = row.productStatus == '1' ? '0' : '1';
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
await proxy?.$modal.confirm(`确认${action}该商品吗?`);
|
|
|
-
|
|
|
+
|
|
|
// 调用上下架接口
|
|
|
await shelfReview({
|
|
|
id: row.id,
|
|
|
productStatus: newStatus
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
proxy?.$modal.msgSuccess(`${action}成功`);
|
|
|
-
|
|
|
+
|
|
|
// 刷新列表
|
|
|
await getList();
|
|
|
} catch (error) {
|
|
|
@@ -473,7 +566,7 @@ const handleViewLog = async (row: ThirdProductVO) => {
|
|
|
logDrawer.visible = true;
|
|
|
logDrawer.loading = true;
|
|
|
logDrawer.data = [];
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
// 获取项目ID
|
|
|
const itemId = queryParams.value.itemId;
|
|
|
@@ -482,7 +575,7 @@ const handleViewLog = async (row: ThirdProductVO) => {
|
|
|
logDrawer.visible = false;
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 调用列表接口,传入项目id和商品池id
|
|
|
const res = await listPushPoolLog({ itemId, productId: row.productId, pageNum: 1, pageSize: 100 });
|
|
|
logDrawer.data = res.rows || [];
|
|
|
@@ -501,6 +594,145 @@ const handleExport = () => {
|
|
|
}, `product_${new Date().getTime()}.xlsx`)
|
|
|
}
|
|
|
|
|
|
+/** 导入按钮操作 */
|
|
|
+const handleImport = () => {
|
|
|
+ importDialog.visible = true;
|
|
|
+ importDialog.selectedFile = null;
|
|
|
+ uploadRef.value?.clearFiles();
|
|
|
+}
|
|
|
+
|
|
|
+/** 下载导入模板 */
|
|
|
+const handleImportTemplate = async () => {
|
|
|
+ try {
|
|
|
+ const response = await importTemplate();
|
|
|
+ const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
|
|
+ const url = window.URL.createObjectURL(blob);
|
|
|
+ const link = document.createElement('a');
|
|
|
+ link.href = url;
|
|
|
+ link.download = `对外部推送商品模板_${new Date().getTime()}.xlsx`;
|
|
|
+ link.click();
|
|
|
+ window.URL.revokeObjectURL(url);
|
|
|
+ proxy?.$modal.msgSuccess('模板下载成功');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('下载模板失败:', error);
|
|
|
+ proxy?.$modal.msgError('下载模板失败');
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 文件上传前校验 */
|
|
|
+const handleBeforeUpload = (file: UploadRawFile) => {
|
|
|
+ const isExcel = file.type === 'application/vnd.ms-excel' ||
|
|
|
+ file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
|
|
+ if (!isExcel) {
|
|
|
+ proxy?.$modal.msgError('只能上传 Excel 文件格式!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ importDialog.selectedFile = file;
|
|
|
+ return false; // 阻止自动上传
|
|
|
+}
|
|
|
+
|
|
|
+/** 文件超出数量限制 */
|
|
|
+const handleExceed = () => {
|
|
|
+ proxy?.$modal.msgWarning('只能上传一个文件');
|
|
|
+}
|
|
|
+
|
|
|
+/** 自定义文件上传处理 */
|
|
|
+const handleFileUpload = async () => {
|
|
|
+ if (!importDialog.selectedFile) {
|
|
|
+ proxy?.$modal.msgWarning('请选择要导入的文件');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await importData(importDialog.selectedFile);
|
|
|
+ proxy?.$modal.msgSuccess(res.msg || '导入成功');
|
|
|
+ importDialog.visible = false;
|
|
|
+ uploadRef.value?.clearFiles();
|
|
|
+ importDialog.selectedFile = null;
|
|
|
+ await getList();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('导入失败:', error);
|
|
|
+ proxy?.$modal.msgError('导入失败');
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 确认导入 */
|
|
|
+const confirmImport = () => {
|
|
|
+ if (!importDialog.selectedFile) {
|
|
|
+ proxy?.$modal.msgWarning('请先选择要导入的文件');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ handleFileUpload();
|
|
|
+}
|
|
|
+
|
|
|
+/** 编辑库存操作 */
|
|
|
+const handleEditInventory = async (row: ThirdProductVO) => {
|
|
|
+ // 重置表单
|
|
|
+ inventoryForm.id = row.id;
|
|
|
+ inventoryForm.productNo = row.productNo;
|
|
|
+ inventoryForm.itemName = row.itemName;
|
|
|
+ inventoryForm.currentInventory = row.availableStock || 0;
|
|
|
+ inventoryForm.remark = undefined;
|
|
|
+
|
|
|
+ inventoryDialog.title = '编辑库存';
|
|
|
+ inventoryDialog.visible = true;
|
|
|
+}
|
|
|
+
|
|
|
+/** 提交库存表单 */
|
|
|
+const submitInventoryForm = async () => {
|
|
|
+ if (!inventoryFormRef.value) return;
|
|
|
+
|
|
|
+ await inventoryFormRef.value.validate(async (valid) => {
|
|
|
+ if (valid) {
|
|
|
+ inventoryLoading.value = true;
|
|
|
+ try {
|
|
|
+ // 调用编辑接口,更新库存
|
|
|
+ const updateData = {
|
|
|
+ id: inventoryForm.id,
|
|
|
+ totalInventory: inventoryForm.currentInventory,
|
|
|
+ remark: inventoryForm.remark
|
|
|
+ };
|
|
|
+
|
|
|
+ await updateProduct(updateData);
|
|
|
+ proxy?.$modal.msgSuccess('库存修改成功');
|
|
|
+ inventoryDialog.visible = false;
|
|
|
+ await getList();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('修改库存失败:', error);
|
|
|
+ proxy?.$modal.msgError('修改库存失败');
|
|
|
+ } finally {
|
|
|
+ inventoryLoading.value = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+// 库存编辑表单数据
|
|
|
+interface InventoryForm {
|
|
|
+ id?: string | number;
|
|
|
+ productNo?: string;
|
|
|
+ itemName?: string;
|
|
|
+ currentInventory?: number;
|
|
|
+ remark?: string;
|
|
|
+}
|
|
|
+
|
|
|
+const inventoryForm = reactive<InventoryForm>({
|
|
|
+ id: undefined,
|
|
|
+ productNo: undefined,
|
|
|
+ itemName: undefined,
|
|
|
+ currentInventory: 0,
|
|
|
+ remark: undefined
|
|
|
+});
|
|
|
+
|
|
|
+const inventoryRules = reactive({
|
|
|
+ currentInventory: [
|
|
|
+ { required: true, message: '请输入当前库存', trigger: 'blur' }
|
|
|
+ ]
|
|
|
+});
|
|
|
+
|
|
|
+// 库存编辑加载状态
|
|
|
+const inventoryLoading = ref(false);
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
// 从缓存获取项目ID并初始化
|
|
|
initProjectId();
|