mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-08-06 10:57:26 +08:00
feat (router): 新增采购详情路由,优化预警数量管理功能
在管理员模块与商家模块中新增采购详情页面路由。 升级预警数量管理组件,优化交互体验与功能逻辑。
This commit is contained in:
51
seller/src/api/procurement.js
Normal file
51
seller/src/api/procurement.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
getRequest,
|
||||
postRequest,
|
||||
putRequest,
|
||||
} from "@/libs/axios";
|
||||
|
||||
// ========== 采购单 ==========
|
||||
export const getProcurementOrderPage = params => getRequest("/procurement/order", params);
|
||||
export const getProcurementOrderStatusCount = params => getRequest("/procurement/order/status-count", params);
|
||||
export const getProcurementOrderDetail = id => getRequest(`/procurement/order/${id}`);
|
||||
export const createProcurementOrder = params =>
|
||||
postRequest("/procurement/order", params, { "Content-Type": "application/json" });
|
||||
export const submitProcurementOrder = id => putRequest(`/procurement/order/submit/${id}`);
|
||||
export const auditProcurementOrder = (id, params) =>
|
||||
putRequest(`/procurement/order/audit/${id}`, params, { "Content-Type": "application/json" });
|
||||
export const closeProcurementOrder = id => putRequest(`/procurement/order/close/${id}`);
|
||||
|
||||
// ========== 采购入库单 ==========
|
||||
export const getProcurementInboundPage = params => getRequest("/procurement/inbound", params);
|
||||
export const getProcurementInboundDetail = id => getRequest(`/procurement/inbound/${id}`);
|
||||
export const getProcurementInboundItems = id => getRequest(`/procurement/inbound/${id}/items`);
|
||||
export const createProcurementInbound = params =>
|
||||
postRequest("/procurement/inbound", params, { "Content-Type": "application/json" });
|
||||
|
||||
// ========== 盘点单 ==========
|
||||
export const createInventoryCount = () => postRequest("/procurement/inventory-count");
|
||||
export const getInventoryCountPage = params => getRequest("/procurement/inventory-count/page", params);
|
||||
export const getInventoryCountDetail = id => getRequest(`/procurement/inventory-count/${id}`);
|
||||
export const getInventoryCountItemsPage = (id, params) =>
|
||||
getRequest(`/procurement/inventory-count/${id}/items/page`, params);
|
||||
export const downloadInventoryCountItems = id =>
|
||||
getRequest(`/procurement/inventory-count/${id}/download`);
|
||||
export const exportInventoryCountItems = id =>
|
||||
getRequest(`/procurement/inventory-count/${id}/export`, {}, "blob");
|
||||
|
||||
// ========== 报损单 ==========
|
||||
export const getDamageReportPage = params => getRequest("/procurement/damage-report/page", params);
|
||||
export const getDamageReportStatusCount = params => getRequest("/procurement/damage-report/status-count", params);
|
||||
export const getDamageReportDetail = id => getRequest(`/procurement/damage-report/${id}`);
|
||||
export const getDamageReportItems = id => getRequest(`/procurement/damage-report/${id}/items`);
|
||||
export const createDamageReport = params =>
|
||||
postRequest("/procurement/damage-report", params, { "Content-Type": "application/json" });
|
||||
export const submitDamageReport = id => putRequest(`/procurement/damage-report/${id}/submit`);
|
||||
export const approveDamageReport = id => putRequest(`/procurement/damage-report/${id}/approve`);
|
||||
export const rejectDamageReport = (id, remark) =>
|
||||
putRequest(`/procurement/damage-report/${id}/reject?remark=${encodeURIComponent(remark || "")}`);
|
||||
export const cancelDamageReport = id => putRequest(`/procurement/damage-report/${id}/cancel`);
|
||||
export const completeDamageReport = id => putRequest(`/procurement/damage-report/${id}/complete`);
|
||||
|
||||
// ========== 出入库原因 ==========
|
||||
export const getStockReasonList = params => getRequest("/procurement/reason", params);
|
||||
@@ -197,6 +197,42 @@ export const otherRouter = {
|
||||
name: "goodsSalesSummaryReport",
|
||||
meta: { title: "商品销售汇总报表" },
|
||||
component: () => import("@/views/statistics/goods-sales-summary.vue")
|
||||
},
|
||||
{
|
||||
path: "procurement/purchase-order/operation",
|
||||
title: "采购单",
|
||||
name: "procurement-purchase-order-operation",
|
||||
component: () => import("@/views/procurement/purchase-order/operation.vue")
|
||||
},
|
||||
{
|
||||
path: "procurement/inbound/operation",
|
||||
title: "采购入库",
|
||||
name: "procurement-inbound-operation",
|
||||
component: () => import("@/views/procurement/inbound/operation.vue")
|
||||
},
|
||||
{
|
||||
path: "procurement/inventory-count/detail",
|
||||
title: "盘点明细",
|
||||
name: "procurement-inventory-count-detail",
|
||||
component: () => import("@/views/procurement/inventory-count/detail.vue")
|
||||
},
|
||||
{
|
||||
path: "procurement/damage-report/operation",
|
||||
title: "报损单",
|
||||
name: "procurement-damage-report-operation",
|
||||
component: () => import("@/views/procurement/damage-report/operation.vue")
|
||||
},
|
||||
{
|
||||
path: "alertQuantityWarnList",
|
||||
title: "预警商品",
|
||||
name: "alertQuantityWarnList",
|
||||
component: () => import("@/views/goods/goods-seller/alertQuantityWarn.vue")
|
||||
},
|
||||
{
|
||||
path: "alertQuantityWarnSetting",
|
||||
title: "设置预警",
|
||||
name: "alertQuantityWarnSetting",
|
||||
component: () => import("@/views/goods/goods-seller/alertQuantityWarn.vue")
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -34,96 +34,56 @@
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="stockType" @tab-click="switchTabs">
|
||||
<el-tab-pane label="商品库存管理" name="stockManage">
|
||||
<div class="operation padding-row">
|
||||
<el-button type="primary" class="export" @click="exportStock">批量导出</el-button>
|
||||
<el-button class="export" @click="openImportStock">批量导入</el-button>
|
||||
</div>
|
||||
<el-table ref="table" v-loading="loading" border :data="stockAllData" class="mt_10" style="width: 100%">
|
||||
<el-table-column label="商品信息" min-width="400">
|
||||
<template #default="{ row }">
|
||||
<div style="margin-top: 5px; height: 90px; display: flex">
|
||||
<img :src="row.thumbnail" style="height: 80px; margin-top: 3px; width: 70px" alt="" />
|
||||
<div style="margin-left: 13px; margin-top: 5px">
|
||||
<div class="div-zoom" style="color: black">{{ row.goodsName }}</div>
|
||||
<div class="div-zoom" style="margin-top: 5px">ID: {{ row.goodsId }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="operation padding-row">
|
||||
<el-button type="primary" class="export" @click="exportStock">批量导出</el-button>
|
||||
<el-button class="export" @click="openImportStock">批量导入</el-button>
|
||||
</div>
|
||||
<el-table ref="table" v-loading="loading" border :data="stockAllData" class="mt_10" style="width: 100%">
|
||||
<el-table-column label="商品信息" min-width="400">
|
||||
<template #default="{ row }">
|
||||
<div style="margin-top: 5px; height: 90px; display: flex">
|
||||
<img :src="row.thumbnail" style="height: 80px; margin-top: 3px; width: 70px" alt="" />
|
||||
<div style="margin-left: 13px; margin-top: 5px">
|
||||
<div class="div-zoom" style="color: black">{{ row.goodsName }}</div>
|
||||
<div class="div-zoom" style="margin-top: 5px">ID: {{ row.goodsId }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="SKU信息" min-width="400">
|
||||
<template #default="{ row }">
|
||||
<div style="margin-top: 5px">
|
||||
<div class="div-zoom" style="color: black">{{ row.simpleSpecs }}</div>
|
||||
<div class="div-zoom" style="margin-top: 5px">ID: {{ row.id }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上架状态" width="130">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.marketEnable === 'DOWN' ? 'danger' : 'success'">
|
||||
{{ row.marketEnable === "DOWN" ? "下架" : "上架" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审核状态" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="
|
||||
row.authFlag === 'PASS' ? 'success' : row.authFlag === 'TOBEAUDITED' ? 'warning' : 'danger'
|
||||
"
|
||||
>
|
||||
{{
|
||||
row.authFlag === "PASS" ? "通过" : row.authFlag === "TOBEAUDITED" ? "待审核" : "审核拒绝"
|
||||
}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存" min-width="120">
|
||||
<template #default="{ row }">{{ row.quantity || 0 }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="预警商品" name="warnList">
|
||||
<el-table v-loading="loading" border :data="warnData" class="mt_10" style="width: 100%">
|
||||
<el-table-column prop="goodsName" label="商品名称" min-width="400" show-overflow-tooltip />
|
||||
<el-table-column label="库存" min-width="120">
|
||||
<template #default="{ row }">{{ row.quantity || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预警值" min-width="120">
|
||||
<template #default="{ row }">{{ row.alertQuantity || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<a class="link-text" @click="openUpdataStockModal(row)">库存</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="设置预警" name="warnSetting">
|
||||
<el-table v-loading="loading" border :data="skuAllData" class="mt_10" style="width: 100%">
|
||||
<el-table-column prop="goodsName" label="商品名称" />
|
||||
<el-table-column label="库存" width="200">
|
||||
<template #default="{ row }">{{ row.quantity || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预警值" width="200">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.alertQuantity"
|
||||
type="number"
|
||||
clearable
|
||||
placeholder="请输入预警库存"
|
||||
@blur="updateWarnStock(row)"
|
||||
@change="checkVal(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="SKU信息" min-width="400">
|
||||
<template #default="{ row }">
|
||||
<div style="margin-top: 5px">
|
||||
<div class="div-zoom" style="color: black">{{ row.simpleSpecs }}</div>
|
||||
<div class="div-zoom" style="margin-top: 5px">ID: {{ row.id }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上架状态" width="130">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.marketEnable === 'DOWN' ? 'danger' : 'success'">
|
||||
{{ row.marketEnable === "DOWN" ? "下架" : "上架" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审核状态" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="
|
||||
row.authFlag === 'PASS' ? 'success' : row.authFlag === 'TOBEAUDITED' ? 'warning' : 'danger'
|
||||
"
|
||||
>
|
||||
{{
|
||||
row.authFlag === "PASS" ? "通过" : row.authFlag === "TOBEAUDITED" ? "待审核" : "审核拒绝"
|
||||
}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存" min-width="120">
|
||||
<template #default="{ row }">{{ row.quantity || 0 }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="mt_10" style="display: flex; justify-content: flex-end">
|
||||
<el-pagination
|
||||
@@ -139,36 +99,6 @@
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="updateStockModalVisible" title="更新库存" width="610px" :close-on-click-modal="false">
|
||||
<el-table :data="stockList" border class="mt_10" style="width: 100%">
|
||||
<el-table-column label="sku规格" min-width="120">
|
||||
<template #default="{ row }">{{ row.simpleSpecs }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审核状态" width="130">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="
|
||||
row.authFlag === 'PASS' ? 'success' : row.authFlag === 'TOBEAUDITED' ? 'primary' : 'danger'
|
||||
"
|
||||
>
|
||||
{{
|
||||
row.authFlag === "TOBEAUDITED" ? "待审核" : row.authFlag === "PASS" ? "通过" : "审核拒绝"
|
||||
}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.quantity" :min="0" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="updateStockModalVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="updateStock">更新</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="importModal" title="导入商品信息" :close-on-click-modal="false">
|
||||
<div v-loading="spinShow" style="text-align: center">
|
||||
<el-upload drag :before-upload="handleUpload" :show-file-list="false" accept=".xlsx,.xls">
|
||||
@@ -188,38 +118,28 @@
|
||||
<script>
|
||||
import {
|
||||
getGoodsCategoryAll,
|
||||
getGoodsListDataByStockSeller,
|
||||
getGoodsSkuListDataSeller,
|
||||
importStockExcel,
|
||||
queryExportStock,
|
||||
updateGoodsAlertStocks,
|
||||
updateGoodsSkuStocks,
|
||||
} from "@/api/goods";
|
||||
|
||||
export default {
|
||||
name: "goods",
|
||||
name: "alertQuantity",
|
||||
data() {
|
||||
return {
|
||||
importModal: false,
|
||||
spinShow: false,
|
||||
loading: true,
|
||||
updateStockModalVisible: false,
|
||||
stockType: "stockManage",
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
},
|
||||
checkFlag: false,
|
||||
stockList: [],
|
||||
warnData: [],
|
||||
skuAllData: [],
|
||||
stockAllData: [],
|
||||
total: 0,
|
||||
categoryList: [],
|
||||
category: [],
|
||||
selectedSku: {},
|
||||
file: null,
|
||||
};
|
||||
},
|
||||
@@ -228,51 +148,6 @@ export default {
|
||||
this.getDataList();
|
||||
this.deepGroup();
|
||||
},
|
||||
openUpdataStockModal(row) {
|
||||
this.stockList = [];
|
||||
this.selectedSku = JSON.parse(JSON.stringify(row));
|
||||
this.stockList.push(this.selectedSku);
|
||||
this.updateStockModalVisible = true;
|
||||
},
|
||||
updateStock() {
|
||||
const updateStockList = this.stockList.map((i) => ({
|
||||
skuId: i.id,
|
||||
quantity: i.quantity,
|
||||
}));
|
||||
updateGoodsSkuStocks(updateStockList).then((res) => {
|
||||
if (res.success) {
|
||||
this.updateStockModalVisible = false;
|
||||
this.$Message.success("更新库存成功");
|
||||
this.getDataList();
|
||||
}
|
||||
});
|
||||
},
|
||||
updateWarnStock(row) {
|
||||
if (this.checkFlag) {
|
||||
updateGoodsAlertStocks({ skuId: row.id, alertQuantity: row.alertQuantity }).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("更新成功");
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
checkVal(row) {
|
||||
if (
|
||||
!/^[+]{0,1}(\d+)$|^[+]{0,1}(\d+\.\d+)$/.test(row.alertQuantity) ||
|
||||
parseInt(row.alertQuantity) < 0 ||
|
||||
parseInt(row.alertQuantity) > 99999999
|
||||
) {
|
||||
this.$Message.error("请输入0~99999999之间的数字值");
|
||||
row.alertQuantity = 0;
|
||||
this.checkFlag = false;
|
||||
return;
|
||||
}
|
||||
this.checkFlag = true;
|
||||
},
|
||||
switchTabs() {
|
||||
this.handleReset();
|
||||
this.getDataList();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.searchForm.pageSize = v;
|
||||
this.getDataList();
|
||||
@@ -298,31 +173,13 @@ export default {
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
if (this.stockType === "warnList") {
|
||||
getGoodsListDataByStockSeller(this.searchForm).then((res) => {
|
||||
if (res.success) {
|
||||
this.warnData = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
} else if (this.stockType === "warnSetting") {
|
||||
getGoodsSkuListDataSeller(this.searchForm).then((res) => {
|
||||
if (res.success) {
|
||||
this.skuAllData = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
} else {
|
||||
getGoodsSkuListDataSeller(this.searchForm).then((res) => {
|
||||
if (res.success) {
|
||||
this.stockAllData = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
getGoodsSkuListDataSeller(this.searchForm).then((res) => {
|
||||
if (res.success) {
|
||||
this.stockAllData = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
deepGroup() {
|
||||
getGoodsCategoryAll().then((res) => {
|
||||
@@ -394,9 +251,4 @@ export default {
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/table-common.scss";
|
||||
.link-text {
|
||||
color: #2d8cf0;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
295
seller/src/views/goods/goods-seller/alertQuantityWarn.vue
Normal file
295
seller/src/views/goods/goods-seller/alertQuantityWarn.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<div @keyup.enter="handleSearch">
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="70px" class="search-form">
|
||||
<el-form-item label="商品名称" prop="goodsName">
|
||||
<el-input v-model="searchForm.goodsName" placeholder="请输入商品名称" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品编号" prop="goodsId">
|
||||
<el-input v-model="searchForm.goodsId" placeholder="请输入商品编号" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="searchForm.marketEnable" placeholder="请选择" clearable style="width: 200px">
|
||||
<el-option label="下架" value="DOWN" />
|
||||
<el-option label="上架" value="UPPER" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品分类" prop="category">
|
||||
<el-cascader
|
||||
v-model="category"
|
||||
:options="categoryList"
|
||||
placeholder="请选择商品分类"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="货号" prop="sn">
|
||||
<el-input v-model="searchForm.sn" placeholder="请输入货号" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
|
||||
<el-button class="search-btn" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab" @tab-click="switchTabs">
|
||||
<el-tab-pane label="预警商品" name="warnList">
|
||||
<el-table v-loading="loading" border :data="warnData" class="mt_10" style="width: 100%">
|
||||
<el-table-column prop="goodsName" label="商品名称" min-width="400" show-overflow-tooltip />
|
||||
<el-table-column label="库存" min-width="120">
|
||||
<template #default="{ row }">{{ row.quantity || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预警值" min-width="120">
|
||||
<template #default="{ row }">{{ row.alertQuantity || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<a class="link-text" @click="openUpdataStockModal(row)">库存</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="设置预警" name="warnSetting">
|
||||
<el-table v-loading="loading" border :data="skuAllData" class="mt_10" style="width: 100%">
|
||||
<el-table-column prop="goodsName" label="商品名称" />
|
||||
<el-table-column label="库存" width="200">
|
||||
<template #default="{ row }">{{ row.quantity || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预警值" width="200">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.alertQuantity"
|
||||
type="number"
|
||||
clearable
|
||||
placeholder="请输入预警库存"
|
||||
@blur="updateWarnStock(row)"
|
||||
@change="checkVal(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="mt_10" style="display: flex; justify-content: flex-end">
|
||||
<el-pagination
|
||||
v-model:current-page="searchForm.pageNumber"
|
||||
v-model:page-size="searchForm.pageSize"
|
||||
:page-sizes="[20, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
size="small"
|
||||
@current-change="changePage"
|
||||
@size-change="changePageSize"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="updateStockModalVisible" title="更新库存" width="610px" :close-on-click-modal="false">
|
||||
<el-table :data="stockList" border class="mt_10" style="width: 100%">
|
||||
<el-table-column label="sku规格" min-width="120">
|
||||
<template #default="{ row }">{{ row.simpleSpecs }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审核状态" width="130">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="
|
||||
row.authFlag === 'PASS' ? 'success' : row.authFlag === 'TOBEAUDITED' ? 'primary' : 'danger'
|
||||
"
|
||||
>
|
||||
{{
|
||||
row.authFlag === "TOBEAUDITED" ? "待审核" : row.authFlag === "PASS" ? "通过" : "审核拒绝"
|
||||
}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.quantity" :min="0" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="updateStockModalVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="updateStock">更新</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getGoodsCategoryAll,
|
||||
getGoodsListDataByStockSeller,
|
||||
getGoodsSkuListDataSeller,
|
||||
updateGoodsAlertStocks,
|
||||
updateGoodsSkuStocks,
|
||||
} from "@/api/goods";
|
||||
|
||||
const ROUTE_TAB_MAP = {
|
||||
alertQuantityWarn: "warnList",
|
||||
alertQuantityWarnList: "warnList",
|
||||
alertQuantityWarnSetting: "warnSetting",
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "alertQuantityWarn",
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
updateStockModalVisible: false,
|
||||
activeTab: "warnList",
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
},
|
||||
checkFlag: false,
|
||||
stockList: [],
|
||||
warnData: [],
|
||||
skuAllData: [],
|
||||
total: 0,
|
||||
categoryList: [],
|
||||
category: [],
|
||||
selectedSku: {},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
"$route.name"(name) {
|
||||
this.applyRouteTab(name);
|
||||
this.handleReset();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.applyRouteTab();
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
applyRouteTab(name = this.$route.name) {
|
||||
this.activeTab = ROUTE_TAB_MAP[name] || "warnList";
|
||||
},
|
||||
init() {
|
||||
this.getDataList();
|
||||
this.deepGroup();
|
||||
},
|
||||
openUpdataStockModal(row) {
|
||||
this.stockList = [];
|
||||
this.selectedSku = JSON.parse(JSON.stringify(row));
|
||||
this.stockList.push(this.selectedSku);
|
||||
this.updateStockModalVisible = true;
|
||||
},
|
||||
updateStock() {
|
||||
const updateStockList = this.stockList.map((i) => ({
|
||||
skuId: i.id,
|
||||
quantity: i.quantity,
|
||||
}));
|
||||
updateGoodsSkuStocks(updateStockList).then((res) => {
|
||||
if (res.success) {
|
||||
this.updateStockModalVisible = false;
|
||||
this.$Message.success("更新库存成功");
|
||||
this.getDataList();
|
||||
}
|
||||
});
|
||||
},
|
||||
updateWarnStock(row) {
|
||||
if (this.checkFlag) {
|
||||
updateGoodsAlertStocks({ skuId: row.id, alertQuantity: row.alertQuantity }).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("更新成功");
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
checkVal(row) {
|
||||
if (
|
||||
!/^[+]{0,1}(\d+)$|^[+]{0,1}(\d+\.\d+)$/.test(row.alertQuantity) ||
|
||||
parseInt(row.alertQuantity) < 0 ||
|
||||
parseInt(row.alertQuantity) > 99999999
|
||||
) {
|
||||
this.$Message.error("请输入0~99999999之间的数字值");
|
||||
row.alertQuantity = 0;
|
||||
this.checkFlag = false;
|
||||
return;
|
||||
}
|
||||
this.checkFlag = true;
|
||||
},
|
||||
switchTabs() {
|
||||
this.handleReset();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.searchForm.pageSize = v;
|
||||
this.getDataList();
|
||||
},
|
||||
changePage(v) {
|
||||
this.searchForm.pageNumber = v;
|
||||
this.getDataList();
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.categoryPath = this.category?.length ? this.category.join(",") : null;
|
||||
this.getDataList();
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "create_time",
|
||||
order: "desc",
|
||||
};
|
||||
this.category = [];
|
||||
this.getDataList();
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
if (this.activeTab === "warnList") {
|
||||
getGoodsListDataByStockSeller(this.searchForm).then((res) => {
|
||||
if (res.success) {
|
||||
this.warnData = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
} else {
|
||||
getGoodsSkuListDataSeller(this.searchForm).then((res) => {
|
||||
if (res.success) {
|
||||
this.skuAllData = res.result.records;
|
||||
this.total = res.result.total;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
deepGroup() {
|
||||
getGoodsCategoryAll().then((res) => {
|
||||
if (res.success) {
|
||||
this.categoryList = res.result.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
children: (item.children || []).map((child) => ({
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
children: (child.children || []).map((grandson) => ({
|
||||
value: grandson.id,
|
||||
label: grandson.name,
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/table-common.scss";
|
||||
.link-text {
|
||||
color: #2d8cf0;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
@@ -226,7 +226,14 @@ export default {
|
||||
setData(v, init) {
|
||||
if (typeof v == "string") {
|
||||
if (this.multiple) {
|
||||
this.$Message.warning("多张上传仅支持数组数据类型");
|
||||
if (!v) {
|
||||
this.uploadList = [];
|
||||
return;
|
||||
}
|
||||
this.setData(
|
||||
v.split(",").map((s) => s.trim()).filter(Boolean),
|
||||
init
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!v) {
|
||||
|
||||
81
seller/src/views/procurement/constants.js
Normal file
81
seller/src/views/procurement/constants.js
Normal file
@@ -0,0 +1,81 @@
|
||||
export const PROCUREMENT_STATUS = {
|
||||
DRAFT: "待提交",
|
||||
SUBMITTED: "已提交",
|
||||
PENDING_INBOUND: "待入库",
|
||||
PARTIAL_INBOUND: "部分入库",
|
||||
CLOSED: "已关闭",
|
||||
COMPLETED: "已完成",
|
||||
REJECTED: "已拒绝",
|
||||
};
|
||||
|
||||
export const PROCUREMENT_STATUS_TAG = {
|
||||
DRAFT: "info",
|
||||
SUBMITTED: "warning",
|
||||
PENDING_INBOUND: "primary",
|
||||
PARTIAL_INBOUND: "warning",
|
||||
CLOSED: "info",
|
||||
COMPLETED: "success",
|
||||
REJECTED: "danger",
|
||||
};
|
||||
|
||||
export const DAMAGE_STATUS = {
|
||||
DRAFT: "待提交",
|
||||
SUBMITTED: "待审核",
|
||||
APPROVED: "已通过",
|
||||
REJECTED: "已驳回",
|
||||
CANCELLED: "已作废",
|
||||
COMPLETED: "已完成",
|
||||
};
|
||||
|
||||
export const DAMAGE_STATUS_TAG = {
|
||||
DRAFT: "info",
|
||||
SUBMITTED: "warning",
|
||||
APPROVED: "primary",
|
||||
REJECTED: "danger",
|
||||
CANCELLED: "info",
|
||||
COMPLETED: "success",
|
||||
};
|
||||
|
||||
export const MARKET_ENABLE = {
|
||||
UPPER: "上架",
|
||||
DOWN: "下架",
|
||||
};
|
||||
|
||||
export function procurementStatusText(status) {
|
||||
return PROCUREMENT_STATUS[status] || status || "-";
|
||||
}
|
||||
|
||||
export function procurementStatusTag(status) {
|
||||
return PROCUREMENT_STATUS_TAG[status] || "info";
|
||||
}
|
||||
|
||||
export function damageStatusText(status) {
|
||||
return DAMAGE_STATUS[status] || status || "-";
|
||||
}
|
||||
|
||||
export function damageStatusTag(status) {
|
||||
return DAMAGE_STATUS_TAG[status] || "info";
|
||||
}
|
||||
|
||||
export function formatMoney(val) {
|
||||
if (val === null || val === undefined || val === "") return "0.00";
|
||||
return Number(val).toFixed(2);
|
||||
}
|
||||
|
||||
export function exportCsv(filename, headers, rows) {
|
||||
const escape = (v) => {
|
||||
const s = v == null ? "" : String(v);
|
||||
if (s.includes(",") || s.includes('"') || s.includes("\n")) {
|
||||
return `"${s.replace(/"/g, '""')}"`;
|
||||
}
|
||||
return s;
|
||||
};
|
||||
const lines = [headers.map(escape).join(",")];
|
||||
rows.forEach((row) => lines.push(row.map(escape).join(",")));
|
||||
const blob = new Blob(["\uFEFF" + lines.join("\n")], { type: "text/csv;charset=utf-8;" });
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = filename;
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
}
|
||||
303
seller/src/views/procurement/damage-report/list.vue
Normal file
303
seller/src/views/procurement/damage-report/list.vue
Normal file
@@ -0,0 +1,303 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="80px">
|
||||
<el-form-item label="创建时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
start-placeholder="开始"
|
||||
end-placeholder="结束"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt_10">
|
||||
<div class="order-tab">
|
||||
<el-tabs v-model="currentStatus" @tab-click="onStatusTabClick">
|
||||
<el-tab-pane
|
||||
v-for="item in statusTabs"
|
||||
:key="item.value"
|
||||
:label="item.title"
|
||||
:name="item.value"
|
||||
/>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<div class="operation padding-row">
|
||||
<el-button type="primary" @click="goCreate">新建报损单</el-button>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="data" border class="mt_10" style="width: 100%">
|
||||
<el-table-column prop="sn" label="报损单号" width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="totalQuantity" label="报损数量" width="90" align="center" />
|
||||
<el-table-column label="报损金额" width="110" align="right">
|
||||
<template #default="{ row }">¥{{ formatMoney(row.totalAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="damageDate" label="报损日期" width="120" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="damageStatusTag(row.status)">{{ damageStatusText(row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="260" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<a class="link-text" @click="goDetail(row)">查看</a>
|
||||
<template v-if="row.status === 'DRAFT'">
|
||||
<span class="op-split">|</span>
|
||||
<a class="link-text" @click="handleSubmit(row)">提交</a>
|
||||
</template>
|
||||
<template v-if="row.status === 'SUBMITTED'">
|
||||
<span class="op-split">|</span>
|
||||
<a class="link-text" @click="handleApprove(row)">通过</a>
|
||||
<span class="op-split">|</span>
|
||||
<a class="link-text" @click="handleReject(row)">驳回</a>
|
||||
</template>
|
||||
<template v-if="row.status === 'APPROVED'">
|
||||
<span class="op-split">|</span>
|
||||
<a class="link-text" @click="handleComplete(row)">完成报损</a>
|
||||
</template>
|
||||
<template v-if="['DRAFT', 'SUBMITTED', 'APPROVED', 'REJECTED'].includes(row.status)">
|
||||
<span class="op-split">|</span>
|
||||
<a class="link-text" @click="handleCancel(row)">作废</a>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="mt_10" style="display: flex; justify-content: flex-end">
|
||||
<el-pagination
|
||||
v-model:current-page="searchForm.pageNumber"
|
||||
v-model:page-size="searchForm.pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
size="small"
|
||||
@current-change="getDataList"
|
||||
@size-change="getDataList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import {
|
||||
getDamageReportPage,
|
||||
getDamageReportStatusCount,
|
||||
submitDamageReport,
|
||||
approveDamageReport,
|
||||
rejectDamageReport,
|
||||
cancelDamageReport,
|
||||
completeDamageReport,
|
||||
} from "@/api/procurement";
|
||||
import {
|
||||
DAMAGE_STATUS,
|
||||
damageStatusText,
|
||||
damageStatusTag,
|
||||
formatMoney,
|
||||
} from "../constants";
|
||||
|
||||
export default {
|
||||
name: "procurement-damage-report-list",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
dateRange: [],
|
||||
currentStatus: "ALL",
|
||||
statusCountData: {},
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
status: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
statusTabs() {
|
||||
const tabs = [{ title: "全部", value: "ALL" }];
|
||||
Object.keys(DAMAGE_STATUS).forEach((key) => {
|
||||
const count = this.statusCountData[key];
|
||||
const label = DAMAGE_STATUS[key];
|
||||
tabs.push({
|
||||
title: count ? `${label}(${count})` : label,
|
||||
value: key,
|
||||
});
|
||||
});
|
||||
return tabs;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
damageStatusText,
|
||||
damageStatusTag,
|
||||
formatMoney,
|
||||
init() {
|
||||
this.getStatusCount();
|
||||
this.getDataList();
|
||||
},
|
||||
buildSearchParams(includeStatus = true) {
|
||||
const params = { ...this.searchForm };
|
||||
if (!includeStatus) {
|
||||
delete params.status;
|
||||
}
|
||||
if (this.dateRange && this.dateRange.length === 2) {
|
||||
params.startDate = this.dateRange[0];
|
||||
params.endDate = this.dateRange[1];
|
||||
}
|
||||
return params;
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getStatusCount();
|
||||
this.getDataList();
|
||||
},
|
||||
resetSearch() {
|
||||
this.searchForm = { pageNumber: 1, pageSize: 10, status: "" };
|
||||
this.dateRange = [];
|
||||
this.currentStatus = "ALL";
|
||||
this.getStatusCount();
|
||||
this.getDataList();
|
||||
},
|
||||
onStatusTabClick(tab) {
|
||||
this.statusClick(tab.paneName);
|
||||
},
|
||||
statusClick(name) {
|
||||
if (name === "ALL" || name === "" || name === undefined) {
|
||||
this.searchForm.status = "";
|
||||
this.currentStatus = "ALL";
|
||||
} else {
|
||||
this.searchForm.status = name;
|
||||
this.currentStatus = name;
|
||||
}
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
getStatusCount() {
|
||||
getDamageReportStatusCount(this.buildSearchParams(false)).then((res) => {
|
||||
if (res.success) {
|
||||
this.statusCountData = res.result || {};
|
||||
}
|
||||
});
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
getDamageReportPage(this.buildSearchParams(true))
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result.records || [];
|
||||
this.total = res.result.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
refreshList() {
|
||||
this.getStatusCount();
|
||||
this.getDataList();
|
||||
},
|
||||
goCreate() {
|
||||
this.$filters.customRouterPush({ name: "procurement-damage-report-operation" });
|
||||
},
|
||||
goDetail(row) {
|
||||
this.$filters.customRouterPush({
|
||||
name: "procurement-damage-report-operation",
|
||||
query: { id: row.id, readonly: 1 },
|
||||
});
|
||||
},
|
||||
handleSubmit(row) {
|
||||
this.$Modal.confirm({
|
||||
title: "提交报损单",
|
||||
content: "确认提交该报损单?",
|
||||
onOk: () => {
|
||||
submitDamageReport(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("提交成功");
|
||||
this.refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
handleApprove(row) {
|
||||
this.$Modal.confirm({
|
||||
title: "审核通过",
|
||||
content: "确认通过该报损单?",
|
||||
onOk: () => {
|
||||
approveDamageReport(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("审核通过");
|
||||
this.refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
handleReject(row) {
|
||||
ElMessageBox.prompt("请输入驳回原因", "驳回报损单", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
inputPlaceholder: "驳回原因",
|
||||
})
|
||||
.then(({ value }) => {
|
||||
rejectDamageReport(row.id, value || "驳回").then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("已驳回");
|
||||
this.refreshList();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
handleCancel(row) {
|
||||
this.$Modal.confirm({
|
||||
title: "作废报损单",
|
||||
content: "确认作废该报损单?",
|
||||
onOk: () => {
|
||||
cancelDamageReport(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("已作废");
|
||||
this.refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
handleComplete(row) {
|
||||
this.$Modal.confirm({
|
||||
title: "完成报损",
|
||||
content: "完成后将扣减库存,是否继续?",
|
||||
onOk: () => {
|
||||
completeDamageReport(row.id).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("报损完成,库存已扣减");
|
||||
this.refreshList();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.op-split {
|
||||
margin: 0 6px;
|
||||
color: #dcdfe6;
|
||||
}
|
||||
.order-tab :deep(.el-tabs__item) {
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
378
seller/src/views/procurement/damage-report/operation.vue
Normal file
378
seller/src/views/procurement/damage-report/operation.vue
Normal file
@@ -0,0 +1,378 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ pageTitle }}</span>
|
||||
<el-button @click="goBack">返回列表</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="readonly">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="报损单号">{{ detail.sn || "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="damageStatusTag(detail.status)">{{ damageStatusText(detail.status) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">{{ detail.makerName || detail.createBy || "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detail.createTime || "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审核人">{{ detail.auditorName || "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审核时间">{{ detail.auditTime || "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报损日期">{{ detail.damageDate || "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报损原因">{{ reasonText }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报损数量">{{ detail.totalQuantity ?? "-" }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报损金额">¥{{ formatMoney(detail.totalAmount) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ detail.remark || "-" }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-form label-width="100px" class="mt_10">
|
||||
<el-form-item label="凭证图片">
|
||||
<upload-pic-thumb
|
||||
v-if="evidenceImageList.length"
|
||||
v-model="evidenceImageList"
|
||||
:limit="5"
|
||||
:disable="true"
|
||||
:remove="false"
|
||||
:is-view="true"
|
||||
/>
|
||||
<span v-else class="empty-text">暂无凭证</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="items" border class="mt_10 item-table" style="width: 100%">
|
||||
<el-table-column prop="goodsName" label="商品名称" class-name="goods-name-col" width="200">
|
||||
<template #default="{ row }">{{ row.goodsName }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="skuId" label="SKU ID" class-name="sku-id-col" width="200">
|
||||
<template #default="{ row }">{{ row.skuId }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="quantity" label="报损数量" width="100" align="center" />
|
||||
<el-table-column label="单价" width="110" align="right">
|
||||
<template #default="{ row }">¥{{ formatMoney(row.unitPrice) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" width="100" align="right">
|
||||
<template #default="{ row }">¥{{ formatMoney(row.amount) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item label="报损日期">
|
||||
<el-date-picker
|
||||
v-model="form.damageDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
:disabled="readonly"
|
||||
placeholder="报损日期"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="报损原因">
|
||||
<el-select
|
||||
v-model="form.damageReasonId"
|
||||
:disabled="readonly"
|
||||
placeholder="请选择报损原因"
|
||||
style="width: 300px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in reasonOptions"
|
||||
:key="item.id"
|
||||
:label="item.reason"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:disabled="readonly"
|
||||
style="max-width: 600px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="凭证图片">
|
||||
<upload-pic-thumb v-model="form.evidence" :limit="5" :disable="readonly" :is-view="readonly" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div v-if="!readonly" class="operation padding-row">
|
||||
<el-button type="primary" @click="openSkuPicker">添加商品</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="items" border class="mt_10 item-table" style="width: 100%">
|
||||
<el-table-column prop="goodsName" label="商品名称" class-name="goods-name-col" width="200">
|
||||
<template #default="{ row }">{{ row.goodsName }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="skuId" label="SKU ID" class-name="sku-id-col" width="200">
|
||||
<template #default="{ row }">{{ row.skuId }}</template>
|
||||
</el-table-column> <el-table-column label="报损数量" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-if="!readonly"
|
||||
v-model="row.quantity"
|
||||
class="cell-input"
|
||||
@blur="onQuantityBlur(row)"
|
||||
/>
|
||||
<span v-else>{{ row.quantity }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-if="!readonly"
|
||||
v-model="row.unitPrice"
|
||||
class="cell-input"
|
||||
@blur="onUnitPriceBlur(row)"
|
||||
/>
|
||||
<span v-else>¥{{ formatMoney(row.unitPrice) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" width="100" align="right">
|
||||
<template #default="{ row }">¥{{ formatMoney(row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="!readonly" label="操作" width="70" align="center" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<a class="link-text" @click="removeItem($index)">删除</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="summary mt_10">
|
||||
<span>合计数量:<b>{{ totalQuantity }}</b></span>
|
||||
<span class="ml_20">合计金额:<b>¥{{ formatMoney(totalAmount) }}</b></span>
|
||||
</div>
|
||||
|
||||
<div class="footer-actions mt_20">
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSave(false)">保存草稿</el-button>
|
||||
<el-button type="success" :loading="submitLoading" @click="handleSave(true)">保存并提交</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
|
||||
<liliDialog ref="liliDialog" @selectedGoodsData="onGoodsSelected" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import liliDialog from "@/views/lili-dialog";
|
||||
import uploadPicThumb from "@/views/my-components/lili/upload-pic-thumb";
|
||||
import {
|
||||
createDamageReport,
|
||||
getDamageReportDetail,
|
||||
getDamageReportItems,
|
||||
getStockReasonList,
|
||||
submitDamageReport,
|
||||
} from "@/api/procurement";
|
||||
import { damageStatusText, damageStatusTag, formatMoney } from "../constants";
|
||||
|
||||
export default {
|
||||
name: "procurement-damage-report-operation",
|
||||
components: { liliDialog, uploadPicThumb },
|
||||
data() {
|
||||
return {
|
||||
readonly: false,
|
||||
submitLoading: false,
|
||||
form: {
|
||||
damageDate: "",
|
||||
damageReasonId: "",
|
||||
remark: "",
|
||||
evidence: [],
|
||||
},
|
||||
items: [],
|
||||
detail: {},
|
||||
reasonOptions: [],
|
||||
evidenceImageList: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
return this.readonly ? "报损单详情" : "新建报损单";
|
||||
},
|
||||
reasonText() {
|
||||
const reason = this.reasonOptions.find((item) => item.id === this.detail.damageReasonId);
|
||||
return reason ? reason.reason : "-";
|
||||
},
|
||||
totalQuantity() {
|
||||
return this.items.reduce((sum, item) => sum + (Number(item.quantity) || 0), 0);
|
||||
},
|
||||
totalAmount() {
|
||||
return this.items.reduce((sum, item) => sum + (Number(item.amount) || 0), 0);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.readonly = this.$route.query.readonly === "1" || !!this.$route.query.id;
|
||||
this.loadReasons();
|
||||
if (this.$route.query.id) {
|
||||
this.loadDetail(this.$route.query.id);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
damageStatusText,
|
||||
damageStatusTag,
|
||||
formatMoney,
|
||||
loadReasons() {
|
||||
getStockReasonList({ category: "DAMAGE", pageNumber: 1, pageSize: 100 }).then((res) => {
|
||||
if (res.success) {
|
||||
this.reasonOptions = res.result.records || [];
|
||||
}
|
||||
});
|
||||
},
|
||||
recalcRow(row) {
|
||||
row.amount = (Number(row.unitPrice) || 0) * (Number(row.quantity) || 0);
|
||||
},
|
||||
onQuantityBlur(row) {
|
||||
const val = parseInt(row.quantity, 10);
|
||||
row.quantity = Number.isNaN(val) || val < 1 ? 1 : val;
|
||||
this.recalcRow(row);
|
||||
},
|
||||
onUnitPriceBlur(row) {
|
||||
const val = parseFloat(row.unitPrice);
|
||||
row.unitPrice = Number.isNaN(val) || val < 0 ? 0 : Math.round(val * 100) / 100;
|
||||
this.recalcRow(row);
|
||||
},
|
||||
loadDetail(id) {
|
||||
getDamageReportDetail(id).then((res) => {
|
||||
if (res.success && res.result) {
|
||||
this.detail = res.result;
|
||||
this.form.damageDate = res.result.damageDate;
|
||||
this.form.damageReasonId = res.result.damageReasonId;
|
||||
this.form.remark = res.result.remark;
|
||||
this.form.evidence = res.result.evidence;
|
||||
this.evidenceImageList = this.parseEvidenceImages(res.result.evidence);
|
||||
}
|
||||
});
|
||||
getDamageReportItems(id).then((res) => {
|
||||
if (res.success) {
|
||||
this.items = res.result || [];
|
||||
}
|
||||
});
|
||||
},
|
||||
parseEvidenceImages(val) {
|
||||
if (!val) return [];
|
||||
if (Array.isArray(val)) return val.filter(Boolean);
|
||||
return String(val)
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
},
|
||||
openSkuPicker() {
|
||||
this.$refs.liliDialog.goodsData = this.items.map((item) => ({
|
||||
id: item.skuId,
|
||||
goodsId: item.goodsId,
|
||||
goodsName: item.goodsName,
|
||||
price: item.unitPrice,
|
||||
cost: item.unitPrice,
|
||||
}));
|
||||
this.$refs.liliDialog.open("goods");
|
||||
},
|
||||
onGoodsSelected(selected) {
|
||||
const map = new Map(this.items.map((item) => [item.skuId, item]));
|
||||
this.items = selected.map((sku) => {
|
||||
const existed = map.get(sku.id);
|
||||
if (existed) return existed;
|
||||
const unitPrice = sku.cost || sku.price || 0;
|
||||
return {
|
||||
goodsId: sku.goodsId,
|
||||
skuId: sku.id,
|
||||
goodsName: sku.goodsName,
|
||||
quantity: 1,
|
||||
unitPrice,
|
||||
amount: unitPrice,
|
||||
};
|
||||
});
|
||||
},
|
||||
removeItem(index) {
|
||||
this.items.splice(index, 1);
|
||||
},
|
||||
validateForm() {
|
||||
if (!this.form.damageReasonId) {
|
||||
this.$Message.warning("请选择报损原因");
|
||||
return false;
|
||||
}
|
||||
if (!this.items.length) {
|
||||
this.$Message.warning("请添加报损商品");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
handleSave(andSubmit) {
|
||||
if (!this.validateForm()) return;
|
||||
this.submitLoading = true;
|
||||
const payload = {
|
||||
damageDate: this.form.damageDate || undefined,
|
||||
damageReasonId: this.form.damageReasonId,
|
||||
remark: this.form.remark,
|
||||
evidence: Array.isArray(this.form.evidence)
|
||||
? this.form.evidence.join(",")
|
||||
: this.form.evidence || "",
|
||||
items: this.items.map((item) => ({
|
||||
goodsId: item.goodsId,
|
||||
skuId: item.skuId,
|
||||
goodsName: item.goodsName,
|
||||
quantity: item.quantity,
|
||||
unitPrice: item.unitPrice,
|
||||
})),
|
||||
};
|
||||
createDamageReport(payload)
|
||||
.then((res) => {
|
||||
if (!res.success) return;
|
||||
const reportId = res.result.id;
|
||||
if (andSubmit) {
|
||||
return submitDamageReport(reportId).then((subRes) => {
|
||||
if (subRes.success) {
|
||||
this.$Message.success("保存并提交成功");
|
||||
this.goBack();
|
||||
}
|
||||
});
|
||||
}
|
||||
this.$Message.success("保存成功");
|
||||
this.goBack();
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
},
|
||||
goBack() {
|
||||
this.$filters.customRouterPush({ name: "procurementDamageReportList" });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.summary {
|
||||
padding: 12px 0;
|
||||
}
|
||||
.ml_20 {
|
||||
margin-left: 20px;
|
||||
}
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
.item-table :deep(.cell-input) {
|
||||
width: 100%;
|
||||
}
|
||||
.item-table :deep(.cell-input .el-input__wrapper) {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
.item-table :deep(.sku-id-col .cell) {
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.item-table :deep(.goods-name-col .cell) {
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.empty-text {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
109
seller/src/views/procurement/inbound/list.vue
Normal file
109
seller/src/views/procurement/inbound/list.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="80px">
|
||||
<el-form-item label="入库单号">
|
||||
<el-input v-model="searchForm.inboundSn" placeholder="入库单号" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt_10">
|
||||
<div class="operation padding-row">
|
||||
<el-button type="primary" @click="goCreate">新建入库单</el-button>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="data" border class="mt_10" style="width: 100%">
|
||||
<el-table-column prop="inboundSn" label="入库单号" width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="procurementOrderId" label="采购单ID" width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="confirmedQuantity" label="已入库量" width="90" align="center" />
|
||||
<el-table-column prop="pendingQuantity" label="待入库量" width="90" align="center" />
|
||||
<el-table-column label="入库成本" width="110" align="right">
|
||||
<template #default="{ row }">¥{{ formatMoney(row.totalCost) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operatorName" label="操作人" width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="inboundTime" label="入库时间" width="170" />
|
||||
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<a class="link-text" @click="goDetail(row)">查看</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="mt_10" style="display: flex; justify-content: flex-end">
|
||||
<el-pagination
|
||||
v-model:current-page="searchForm.pageNumber"
|
||||
v-model:page-size="searchForm.pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
size="small"
|
||||
@current-change="getDataList"
|
||||
@size-change="getDataList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getProcurementInboundPage } from "@/api/procurement";
|
||||
import { formatMoney } from "../constants";
|
||||
|
||||
export default {
|
||||
name: "procurement-inbound-list",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
inboundSn: "",
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getDataList();
|
||||
},
|
||||
methods: {
|
||||
formatMoney,
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
resetSearch() {
|
||||
this.searchForm.inboundSn = "";
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
getProcurementInboundPage(this.searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result.records || [];
|
||||
this.total = res.result.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
goCreate() {
|
||||
this.$filters.customRouterPush({ name: "procurement-inbound-operation" });
|
||||
},
|
||||
goDetail(row) {
|
||||
this.$filters.customRouterPush({
|
||||
name: "procurement-inbound-operation",
|
||||
query: { id: row.id, readonly: 1 },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
294
seller/src/views/procurement/inbound/operation.vue
Normal file
294
seller/src/views/procurement/inbound/operation.vue
Normal file
@@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ pageTitle }}</span>
|
||||
<el-button @click="goBack">返回列表</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="!readonly">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="采购单" required>
|
||||
<el-select
|
||||
v-model="form.procurementOrderId"
|
||||
filterable
|
||||
placeholder="请选择待入库采购单"
|
||||
style="width: 400px"
|
||||
@change="onOrderChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in orderOptions"
|
||||
:key="item.id"
|
||||
:label="`${item.orderSn}(${procurementStatusText(item.status)})`"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button class="ml_10" @click="loadPendingOrders">刷新</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="入库凭证">
|
||||
<upload-pic-thumb v-model="form.certificateImages" :limit="5" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="items" border class="item-table" style="width: 100%">
|
||||
<el-table-column prop="goodsName" label="商品名称" class-name="goods-name-col" width="200">
|
||||
<template #default="{ row }">{{ row.goodsName }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="skuId" label="SKU ID" class-name="sku-id-col" width="200">
|
||||
<template #default="{ row }">{{ row.skuId }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="采购数量" width="90" align="center">
|
||||
<template #default="{ row }">{{ row.quantity }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已入库" width="80" align="center">
|
||||
<template #default="{ row }">{{ row.receivedQuantity || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="剩余可入" width="90" align="center">
|
||||
<template #default="{ row }">{{ remainQty(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="本次入库" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.inboundQuantity"
|
||||
class="cell-input"
|
||||
@blur="onInboundQtyBlur(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="footer-actions mt_20">
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确认入库</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="入库单号">{{ detail.inboundSn }}</el-descriptions-item>
|
||||
<el-descriptions-item label="采购单ID">{{ detail.procurementOrderId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="本次入库数量">{{ totalInboundQuantity }}</el-descriptions-item>
|
||||
<el-descriptions-item label="入库成本">¥{{ formatMoney(detail.totalCost) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人">{{ detail.operatorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="入库时间">{{ detail.inboundTime }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-form label-width="100px" class="mt_10">
|
||||
<el-form-item label="入库凭证">
|
||||
<upload-pic-thumb
|
||||
v-if="certificateImageList.length"
|
||||
v-model="certificateImageList"
|
||||
:limit="5"
|
||||
:disable="true"
|
||||
:remove="false"
|
||||
:is-view="true"
|
||||
/>
|
||||
<span v-else class="empty-text">暂无凭证</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="inboundItems" border class="mt_10 item-table" style="width: 100%">
|
||||
<el-table-column prop="goodsName" label="商品名称" class-name="goods-name-col" width="200">
|
||||
<template #default="{ row }">{{ row.goodsName }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="skuId" label="SKU ID" class-name="sku-id-col" width="200">
|
||||
<template #default="{ row }">{{ row.skuId }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="inboundQuantity" label="本次入库" width="90" align="center" />
|
||||
</el-table>
|
||||
</template>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uploadPicThumb from "@/views/my-components/lili/upload-pic-thumb";
|
||||
import {
|
||||
getProcurementOrderPage,
|
||||
getProcurementOrderDetail,
|
||||
createProcurementInbound,
|
||||
getProcurementInboundDetail,
|
||||
getProcurementInboundItems,
|
||||
} from "@/api/procurement";
|
||||
import { procurementStatusText, formatMoney } from "../constants";
|
||||
|
||||
export default {
|
||||
name: "procurement-inbound-operation",
|
||||
components: { uploadPicThumb },
|
||||
data() {
|
||||
return {
|
||||
readonly: false,
|
||||
submitLoading: false,
|
||||
form: {
|
||||
procurementOrderId: "",
|
||||
certificateImages: [],
|
||||
},
|
||||
orderOptions: [],
|
||||
items: [],
|
||||
detail: {},
|
||||
inboundItems: [],
|
||||
certificateImageList: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
return this.readonly ? "入库单详情" : "新建入库单";
|
||||
},
|
||||
totalInboundQuantity() {
|
||||
return (this.inboundItems || []).reduce(
|
||||
(sum, item) => sum + (Number(item.inboundQuantity) || 0),
|
||||
0
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.readonly = this.$route.query.readonly === "1";
|
||||
if (this.$route.query.id) {
|
||||
this.loadInboundDetail(this.$route.query.id);
|
||||
} else {
|
||||
this.loadPendingOrders();
|
||||
if (this.$route.query.procurementOrderId) {
|
||||
this.form.procurementOrderId = this.$route.query.procurementOrderId;
|
||||
this.onOrderChange(this.$route.query.procurementOrderId);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
procurementStatusText,
|
||||
formatMoney,
|
||||
remainQty(row) {
|
||||
return (row.quantity || 0) - (row.receivedQuantity || 0);
|
||||
},
|
||||
onInboundQtyBlur(row) {
|
||||
const max = this.remainQty(row);
|
||||
const val = parseInt(row.inboundQuantity, 10);
|
||||
if (Number.isNaN(val) || val < 0) {
|
||||
row.inboundQuantity = 0;
|
||||
} else if (val > max) {
|
||||
row.inboundQuantity = max;
|
||||
} else {
|
||||
row.inboundQuantity = val;
|
||||
}
|
||||
},
|
||||
loadPendingOrders() {
|
||||
getProcurementOrderPage({
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
status: "",
|
||||
}).then((res) => {
|
||||
if (res.success) {
|
||||
this.orderOptions = (res.result.records || []).filter((o) =>
|
||||
["PENDING_INBOUND", "PARTIAL_INBOUND"].includes(o.status)
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
onOrderChange(orderId) {
|
||||
if (!orderId) {
|
||||
this.items = [];
|
||||
return;
|
||||
}
|
||||
getProcurementOrderDetail(orderId).then((res) => {
|
||||
if (res.success && res.result) {
|
||||
this.items = (res.result.items || [])
|
||||
.filter((item) => this.remainQty(item) > 0)
|
||||
.map((item) => ({
|
||||
...item,
|
||||
procurementOrderItemId: item.id,
|
||||
inboundQuantity: this.remainQty(item),
|
||||
}));
|
||||
}
|
||||
});
|
||||
},
|
||||
loadInboundDetail(id) {
|
||||
getProcurementInboundDetail(id).then((res) => {
|
||||
if (res.success) {
|
||||
this.detail = res.result || {};
|
||||
this.certificateImageList = this.parseCertificateImages(this.detail.certificateImages);
|
||||
}
|
||||
});
|
||||
getProcurementInboundItems(id).then((res) => {
|
||||
if (res.success) this.inboundItems = res.result || [];
|
||||
});
|
||||
},
|
||||
parseCertificateImages(val) {
|
||||
if (!val) return [];
|
||||
if (Array.isArray(val)) return val.filter(Boolean);
|
||||
return String(val)
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
},
|
||||
handleSubmit() {
|
||||
if (!this.form.procurementOrderId) {
|
||||
this.$Message.warning("请选择采购单");
|
||||
return;
|
||||
}
|
||||
const validItems = this.items.filter((item) => item.inboundQuantity > 0);
|
||||
if (!validItems.length) {
|
||||
this.$Message.warning("请填写本次入库数量");
|
||||
return;
|
||||
}
|
||||
this.submitLoading = true;
|
||||
createProcurementInbound({
|
||||
procurementOrderId: this.form.procurementOrderId,
|
||||
certificateImages: Array.isArray(this.form.certificateImages)
|
||||
? this.form.certificateImages.join(",")
|
||||
: this.form.certificateImages || "",
|
||||
items: validItems.map((item) => ({
|
||||
procurementOrderItemId: item.procurementOrderItemId || item.id,
|
||||
goodsId: item.goodsId,
|
||||
skuId: item.skuId,
|
||||
goodsName: item.goodsName,
|
||||
inboundQuantity: item.inboundQuantity,
|
||||
})),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("入库成功");
|
||||
this.goBack();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
},
|
||||
goBack() {
|
||||
this.$filters.customRouterPush({ name: "procurementInboundList" });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.ml_10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.footer-actions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.item-table :deep(.cell-input) {
|
||||
width: 100%;
|
||||
}
|
||||
.item-table :deep(.cell-input .el-input__wrapper) {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
.item-table :deep(.sku-id-col .cell) {
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.item-table :deep(.goods-name-col .cell) {
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.empty-text {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
180
seller/src/views/procurement/inventory-count/detail.vue
Normal file
180
seller/src/views/procurement/inventory-count/detail.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card class="info-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="section-title">盘点单信息</span>
|
||||
<el-button @click="goBack">返回列表</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-descriptions v-if="detail.id" :column="2" border>
|
||||
<el-descriptions-item label="盘点单号">{{ detail.sn }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商品总数">{{ detail.itemTotal }}</el-descriptions-item>
|
||||
<el-descriptions-item label="盘点时间">{{ detail.countTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detail.createTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="制单人">{{ detail.makerName }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<el-card class="goods-card mt_10" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="section-title">盘点商品明细</span>
|
||||
<el-button type="primary" :loading="exporting" @click="handleExport">导出 Excel</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div @keyup.enter="handleSearch">
|
||||
<el-form ref="searchFormRef" :model="searchForm" inline label-width="80px" class="search-form">
|
||||
<el-form-item label="商品名称" prop="goodsName">
|
||||
<el-input
|
||||
v-model="searchForm.goodsName"
|
||||
placeholder="请输入商品名称"
|
||||
clearable
|
||||
style="width: 220px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
|
||||
<el-button class="search-btn" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="data" border style="width: 100%">
|
||||
<el-table-column prop="goodsName" label="商品名称" width="280" show-overflow-tooltip />
|
||||
<el-table-column prop="skuName" label="规格" width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="skuId" label="SKU ID" width="180" show-overflow-tooltip />
|
||||
<el-table-column label="上架状态" width="90" align="center">
|
||||
<template #default="{ row }">{{ MARKET_ENABLE[row.marketEnable] || row.marketEnable }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="quantity" label="库存数量" width="100" align="center" />
|
||||
</el-table>
|
||||
|
||||
<div class="mt_10" style="display: flex; justify-content: flex-end">
|
||||
<el-pagination
|
||||
v-model:current-page="searchForm.pageNumber"
|
||||
v-model:page-size="searchForm.pageSize"
|
||||
:page-sizes="[20, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
size="small"
|
||||
@current-change="getDataList"
|
||||
@size-change="getDataList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getInventoryCountDetail,
|
||||
getInventoryCountItemsPage,
|
||||
exportInventoryCountItems,
|
||||
} from "@/api/procurement";
|
||||
import { MARKET_ENABLE } from "../constants";
|
||||
import { downloadBlob } from "@/utils/downloadBlob";
|
||||
|
||||
export default {
|
||||
name: "procurement-inventory-count-detail",
|
||||
data() {
|
||||
return {
|
||||
MARKET_ENABLE,
|
||||
loading: false,
|
||||
exporting: false,
|
||||
detail: {},
|
||||
data: [],
|
||||
total: 0,
|
||||
sn: "",
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
goodsName: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.sn = this.$route.query.sn || "";
|
||||
const id = this.$route.query.id;
|
||||
if (id) {
|
||||
getInventoryCountDetail(id).then((res) => {
|
||||
if (res.success) {
|
||||
this.detail = res.result || {};
|
||||
this.sn = this.detail.sn || this.sn;
|
||||
}
|
||||
});
|
||||
this.getDataList();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDataList() {
|
||||
const id = this.$route.query.id;
|
||||
if (!id) return;
|
||||
this.loading = true;
|
||||
getInventoryCountItemsPage(id, this.searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result.records || [];
|
||||
this.total = res.result.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getDataList();
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
goodsName: "",
|
||||
};
|
||||
this.getDataList();
|
||||
},
|
||||
handleExport() {
|
||||
const id = this.$route.query.id;
|
||||
if (!id) return;
|
||||
this.exporting = true;
|
||||
exportInventoryCountItems(id)
|
||||
.then((blob) => {
|
||||
downloadBlob(blob, `盘点单_${this.sn || id}.xlsx`);
|
||||
})
|
||||
.finally(() => {
|
||||
this.exporting = false;
|
||||
});
|
||||
},
|
||||
goBack() {
|
||||
this.$filters.customRouterPush({ name: "procurementInventoryCountList" });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
.info-card :deep(.el-card__header) {
|
||||
background: #f5f7fa;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
.goods-card :deep(.el-card__header) {
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
.mt_10 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
105
seller/src/views/procurement/inventory-count/list.vue
Normal file
105
seller/src/views/procurement/inventory-count/list.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<div class="operation padding-row">
|
||||
<el-button type="primary" :loading="creating" @click="handleCreate">生成盘点单</el-button>
|
||||
<span class="tip">将快照当前店铺全部 SKU 库存,不调整实际库存</span>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="data" border class="mt_10" style="width: 100%">
|
||||
<el-table-column prop="sn" label="盘点单号" width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="itemTotal" label="商品数" width="90" align="center" />
|
||||
<el-table-column prop="makerName" label="制单人" width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="countTime" label="盘点时间" width="170" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<a class="link-text" @click="goDetail(row)">查看明细</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="mt_10" style="display: flex; justify-content: flex-end">
|
||||
<el-pagination
|
||||
v-model:current-page="searchForm.pageNumber"
|
||||
v-model:page-size="searchForm.pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
size="small"
|
||||
@current-change="getDataList"
|
||||
@size-change="getDataList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createInventoryCount, getInventoryCountPage } from "@/api/procurement";
|
||||
|
||||
export default {
|
||||
name: "procurement-inventory-count-list",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
creating: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getDataList();
|
||||
},
|
||||
methods: {
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
getInventoryCountPage(this.searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result.records || [];
|
||||
this.total = res.result.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleCreate() {
|
||||
this.$Modal.confirm({
|
||||
title: "生成盘点单",
|
||||
content: "将快照当前店铺全部 SKU 库存,是否继续?",
|
||||
onOk: () => {
|
||||
this.creating = true;
|
||||
createInventoryCount()
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("盘点单已生成");
|
||||
this.getDataList();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.creating = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
goDetail(row) {
|
||||
this.$filters.customRouterPush({
|
||||
name: "procurement-inventory-count-detail",
|
||||
query: { id: row.id, sn: row.sn },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tip {
|
||||
margin-left: 12px;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
297
seller/src/views/procurement/purchase-order/list.vue
Normal file
297
seller/src/views/procurement/purchase-order/list.vue
Normal file
@@ -0,0 +1,297 @@
|
||||
<template>
|
||||
|
||||
<div class="search">
|
||||
|
||||
<el-card>
|
||||
|
||||
<el-form ref="searchForm" :model="searchForm" inline label-width="80px" class="search-form">
|
||||
|
||||
<el-form-item label="单据编号">
|
||||
|
||||
<el-input v-model="searchForm.orderSn" placeholder="采购单号" clearable style="width: 200px" />
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="商品名称">
|
||||
|
||||
<el-input v-model="searchForm.goodsName" placeholder="商品名称" clearable style="width: 200px" />
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="创建时间">
|
||||
|
||||
<el-date-picker
|
||||
|
||||
v-model="createRange"
|
||||
|
||||
type="datetimerange"
|
||||
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
|
||||
start-placeholder="开始"
|
||||
|
||||
end-placeholder="结束"
|
||||
|
||||
style="width: 360px"
|
||||
|
||||
/>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
|
||||
</el-card>
|
||||
|
||||
|
||||
|
||||
<el-card class="mt_10">
|
||||
|
||||
<div class="order-tab">
|
||||
|
||||
<el-tabs v-model="currentStatus" @tab-click="onStatusTabClick">
|
||||
|
||||
<el-tab-pane
|
||||
|
||||
v-for="item in statusTabs"
|
||||
|
||||
:key="item.value"
|
||||
|
||||
:label="item.title"
|
||||
|
||||
:name="item.value"
|
||||
|
||||
/>
|
||||
|
||||
</el-tabs>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="operation padding-row">
|
||||
|
||||
<el-button type="primary" @click="goCreate">新建采购单</el-button>
|
||||
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="data" border class="mt_10" style="width: 100%">
|
||||
|
||||
<el-table-column prop="orderSn" label="采购单号" width="220" show-overflow-tooltip />
|
||||
|
||||
<el-table-column prop="totalQuantity" label="采购数量" width="90" align="center" />
|
||||
|
||||
<el-table-column label="采购金额" width="110" align="right">
|
||||
|
||||
<template #default="{ row }">¥{{ formatMoney(row.totalAmount) }}</template>
|
||||
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="makerName" label="制单人" width="120" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
|
||||
<template #default="{ row }">
|
||||
|
||||
<el-tag :type="procurementStatusTag(row.status)">{{ procurementStatusText(row.status) }}</el-tag>
|
||||
|
||||
</template>
|
||||
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
|
||||
<el-table-column prop="auditTime" label="审核时间" width="170" />
|
||||
|
||||
<el-table-column prop="auditorName" label="审核人" width="120" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
||||
|
||||
<template #default="{ row }">
|
||||
|
||||
<a class="link-text" @click="goDetail(row)">查看</a>
|
||||
|
||||
<template v-if="row.status === 'DRAFT'">
|
||||
|
||||
<span class="op-split">|</span>
|
||||
|
||||
<a class="link-text" @click="handleSubmit(row)">提交</a>
|
||||
|
||||
</template>
|
||||
|
||||
<template v-if="row.status === 'SUBMITTED'">
|
||||
|
||||
<span class="op-split">|</span>
|
||||
|
||||
<a class="link-text" @click="handleAudit(row, true)">通过</a>
|
||||
|
||||
<span class="op-split">|</span>
|
||||
|
||||
<a class="link-text" @click="handleAudit(row, false)">驳回</a>
|
||||
|
||||
</template>
|
||||
|
||||
<template v-if="['DRAFT', 'SUBMITTED', 'PENDING_INBOUND', 'PARTIAL_INBOUND', 'REJECTED'].includes(row.status)">
|
||||
|
||||
<span class="op-split">|</span>
|
||||
|
||||
<a class="link-text" @click="handleClose(row)">关闭</a>
|
||||
|
||||
</template>
|
||||
|
||||
<template v-if="['PENDING_INBOUND', 'PARTIAL_INBOUND'].includes(row.status)">
|
||||
|
||||
<span class="op-split">|</span>
|
||||
|
||||
<a class="link-text" @click="goInbound(row)">入库</a>
|
||||
|
||||
</template>
|
||||
|
||||
</template>
|
||||
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
|
||||
<div class="mt_10" style="display: flex; justify-content: flex-end">
|
||||
|
||||
<el-pagination
|
||||
|
||||
v-model:current-page="searchForm.pageNumber"
|
||||
|
||||
v-model:page-size="searchForm.pageSize"
|
||||
|
||||
:page-sizes="[10, 20, 50]"
|
||||
|
||||
:total="total"
|
||||
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
|
||||
size="small"
|
||||
|
||||
@current-change="getDataList"
|
||||
|
||||
@size-change="getDataList"
|
||||
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
</el-card>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
import {
|
||||
|
||||
getProcurementOrderPage,
|
||||
|
||||
getProcurementOrderStatusCount,
|
||||
|
||||
submitProcurementOrder,
|
||||
|
||||
auditProcurementOrder,
|
||||
|
||||
closeProcurementOrder,
|
||||
|
||||
} from "@/api/procurement";
|
||||
|
||||
import {
|
||||
|
||||
PROCUREMENT_STATUS,
|
||||
|
||||
procurementStatusText,
|
||||
|
||||
procurementStatusTag,
|
||||
|
||||
formatMoney,
|
||||
|
||||
} from "../constants";
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
name: "procurement-purchase-order-list",
|
||||
|
||||
data() {
|
||||
|
||||
return {
|
||||
|
||||
loading: false,
|
||||
|
||||
data: [],
|
||||
|
||||
total: 0,
|
||||
|
||||
createRange: [],
|
||||
|
||||
currentStatus: "ALL",
|
||||
|
||||
statusCountData: {},
|
||||
|
||||
searchForm: {
|
||||
|
||||
pageNumber: 1,
|
||||
|
||||
pageSize: 10,
|
||||
|
||||
orderSn: "",
|
||||
|
||||
goodsName: "",
|
||||
|
||||
status: "",
|
||||
|
||||
sort: "createTime",
|
||||
|
||||
order: "desc",
|
||||
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
statusTabs() {
|
||||
|
||||
const tabs = [{ title: "全部", value: "ALL" }];
|
||||
|
||||
Object.keys(PROCUREMENT_STATUS).forEach((key) => {
|
||||
|
||||
const count = this.statusCountData[key];
|
||||
|
||||
const label = PROCUREMENT_STATUS[key];
|
||||
|
||||
tabs.push({
|
||||
|
||||
title: count ? `${label}(${count})` : label,
|
||||
|
||||
value: key,
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return tabs;
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
||||
this.init();
|
||||
|
||||
},
|
||||
350
seller/src/views/procurement/purchase-order/operation.vue
Normal file
350
seller/src/views/procurement/purchase-order/operation.vue
Normal file
@@ -0,0 +1,350 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ pageTitle }}</span>
|
||||
<el-button @click="goBack">返回列表</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item label="出入库原因" required>
|
||||
<el-select
|
||||
v-model="form.stockReasonId"
|
||||
:disabled="readonly"
|
||||
placeholder="请选择出入库原因"
|
||||
style="width: 300px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in reasonOptions"
|
||||
:key="item.id"
|
||||
:label="item.reason"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:disabled="readonly"
|
||||
placeholder="备注信息"
|
||||
style="max-width: 600px"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div v-if="!readonly" class="operation padding-row">
|
||||
<el-button type="primary" @click="openSkuPicker">添加商品</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="items" border class="mt_10 item-table" style="width: 100%">
|
||||
<el-table-column prop="goodsName" label="商品名称" width="240" show-overflow-tooltip />
|
||||
<el-table-column prop="skuId" label="SKU ID" class-name="sku-id-col" :width="readonly ? 200 : 170">
|
||||
<template #default="{ row }">{{ row.skuId }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="零售价" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-if="!readonly"
|
||||
v-model="row.retailPrice"
|
||||
class="cell-input"
|
||||
@blur="onDecimalBlur(row, 'retailPrice')"
|
||||
/>
|
||||
<span v-else>¥{{ formatMoney(row.retailPrice) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="采购数量" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-if="!readonly"
|
||||
v-model="row.quantity"
|
||||
class="cell-input"
|
||||
@blur="onQuantityBlur(row)"
|
||||
/>
|
||||
<span v-else>{{ row.quantity }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="税率(%)" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-if="!readonly"
|
||||
v-model="row.taxRate"
|
||||
class="cell-input"
|
||||
@blur="onTaxRateBlur(row)"
|
||||
/>
|
||||
<span v-else>{{ row.taxRate }}%</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="含税单价" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-if="!readonly"
|
||||
v-model="row.unitPriceWithTax"
|
||||
class="cell-input"
|
||||
@blur="onDecimalBlur(row, 'unitPriceWithTax')"
|
||||
/>
|
||||
<span v-else>¥{{ formatMoney(row.unitPriceWithTax) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="含税小计" width="100" align="right">
|
||||
<template #default="{ row }">¥{{ formatMoney(calcSubtotal(row)) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="orderDetail.id" label="已入库" width="80" align="center">
|
||||
<template #default="{ row }">{{ row.receivedQuantity || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="!readonly" label="操作" width="70" align="center" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<a class="link-text" @click="removeItem($index)">删除</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="summary mt_10">
|
||||
<span>合计数量:<b>{{ totalQuantity }}</b></span>
|
||||
<span class="ml_20">合计金额:<b>¥{{ formatMoney(totalAmount) }}</b></span>
|
||||
<span v-if="orderDetail.status" class="ml_20">
|
||||
状态:<el-tag :type="procurementStatusTag(orderDetail.status)">{{ procurementStatusText(orderDetail.status) }}</el-tag>
|
||||
</span>
|
||||
<span v-if="orderDetail.auditorName" class="ml_20">审核人:<b>{{ orderDetail.auditorName }}</b></span>
|
||||
</div>
|
||||
|
||||
<div v-if="!readonly" class="footer-actions mt_20">
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSave(false)">保存草稿</el-button>
|
||||
<el-button type="success" :loading="submitLoading" @click="handleSave(true)">保存并提交</el-button>
|
||||
</div>
|
||||
|
||||
<div v-if="readonly && orderDetail.status === 'SUBMITTED'" class="footer-actions mt_20">
|
||||
<el-button type="primary" @click="handleAudit(true)">审核通过</el-button>
|
||||
<el-button type="danger" @click="handleAudit(false)">审核驳回</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<liliDialog ref="liliDialog" @selectedGoodsData="onGoodsSelected" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import liliDialog from "@/views/lili-dialog";
|
||||
import {
|
||||
createProcurementOrder,
|
||||
getProcurementOrderDetail,
|
||||
submitProcurementOrder,
|
||||
auditProcurementOrder,
|
||||
getStockReasonList,
|
||||
} from "@/api/procurement";
|
||||
import {
|
||||
procurementStatusText,
|
||||
procurementStatusTag,
|
||||
formatMoney,
|
||||
} from "../constants";
|
||||
|
||||
export default {
|
||||
name: "procurement-purchase-order-operation",
|
||||
components: { liliDialog },
|
||||
data() {
|
||||
return {
|
||||
form: { remark: "", stockReasonId: "" },
|
||||
items: [],
|
||||
orderDetail: {},
|
||||
reasonOptions: [],
|
||||
submitLoading: false,
|
||||
readonly: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
if (this.readonly) return "采购单详情";
|
||||
return this.$route.query.id ? "编辑采购单" : "新建采购单";
|
||||
},
|
||||
totalQuantity() {
|
||||
return this.items.reduce((sum, item) => sum + (Number(item.quantity) || 0), 0);
|
||||
},
|
||||
totalAmount() {
|
||||
return this.items.reduce((sum, item) => sum + this.calcSubtotal(item), 0);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadReasons();
|
||||
if (this.$route.query.id) {
|
||||
this.readonly = true;
|
||||
this.loadDetail(this.$route.query.id);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
procurementStatusText,
|
||||
procurementStatusTag,
|
||||
formatMoney,
|
||||
loadReasons() {
|
||||
getStockReasonList({ category: "INBOUND", pageNumber: 1, pageSize: 100 }).then((res) => {
|
||||
if (res.success) {
|
||||
this.reasonOptions = res.result.records || [];
|
||||
}
|
||||
});
|
||||
},
|
||||
calcSubtotal(row) {
|
||||
return (Number(row.unitPriceWithTax) || 0) * (Number(row.quantity) || 0);
|
||||
},
|
||||
recalcRow() {},
|
||||
onQuantityBlur(row) {
|
||||
const val = parseInt(row.quantity, 10);
|
||||
row.quantity = Number.isNaN(val) || val < 1 ? 1 : val;
|
||||
this.recalcRow(row);
|
||||
},
|
||||
onTaxRateBlur(row) {
|
||||
const val = parseInt(row.taxRate, 10);
|
||||
if (Number.isNaN(val) || val < 0) {
|
||||
row.taxRate = 0;
|
||||
} else if (val > 100) {
|
||||
row.taxRate = 100;
|
||||
} else {
|
||||
row.taxRate = val;
|
||||
}
|
||||
this.recalcRow(row);
|
||||
},
|
||||
onDecimalBlur(row, field) {
|
||||
const val = parseFloat(row[field]);
|
||||
row[field] = Number.isNaN(val) || val < 0 ? 0 : Math.round(val * 100) / 100;
|
||||
this.recalcRow(row);
|
||||
},
|
||||
loadDetail(id) {
|
||||
getProcurementOrderDetail(id).then((res) => {
|
||||
if (res.success && res.result) {
|
||||
this.orderDetail = res.result;
|
||||
this.form.remark = res.result.remark || "";
|
||||
this.form.stockReasonId = res.result.stockReasonId || "";
|
||||
this.items = (res.result.items || []).map((item) => ({ ...item }));
|
||||
}
|
||||
});
|
||||
},
|
||||
openSkuPicker() {
|
||||
this.$refs.liliDialog.goodsData = this.items.map((item) => ({
|
||||
id: item.skuId,
|
||||
goodsId: item.goodsId,
|
||||
goodsName: item.goodsName,
|
||||
price: item.retailPrice,
|
||||
}));
|
||||
this.$refs.liliDialog.open("goods");
|
||||
},
|
||||
onGoodsSelected(selected) {
|
||||
const map = new Map(this.items.map((item) => [item.skuId, item]));
|
||||
this.items = selected.map((sku) => {
|
||||
const existed = map.get(sku.id);
|
||||
if (existed) return existed;
|
||||
return {
|
||||
goodsId: sku.goodsId,
|
||||
skuId: sku.id,
|
||||
goodsName: sku.goodsName,
|
||||
retailPrice: sku.price || 0,
|
||||
quantity: 1,
|
||||
taxRate: 0,
|
||||
unitPriceWithTax: sku.cost || sku.price || 0,
|
||||
};
|
||||
});
|
||||
},
|
||||
removeItem(index) {
|
||||
this.items.splice(index, 1);
|
||||
},
|
||||
validateItems() {
|
||||
if (!this.form.stockReasonId) {
|
||||
this.$Message.warning("请选择出入库原因");
|
||||
return false;
|
||||
}
|
||||
if (!this.items.length) {
|
||||
this.$Message.warning("请至少添加一个商品");
|
||||
return false;
|
||||
}
|
||||
for (const item of this.items) {
|
||||
if (!item.quantity || item.quantity < 1) {
|
||||
this.$Message.warning("采购数量必须大于0");
|
||||
return false;
|
||||
}
|
||||
if (item.unitPriceWithTax == null || item.unitPriceWithTax < 0) {
|
||||
this.$Message.warning("请填写含税单价");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
handleSave(andSubmit) {
|
||||
if (!this.validateItems()) return;
|
||||
this.submitLoading = true;
|
||||
const payload = {
|
||||
remark: this.form.remark,
|
||||
stockReasonId: this.form.stockReasonId,
|
||||
items: this.items.map((item) => ({
|
||||
goodsId: item.goodsId,
|
||||
skuId: item.skuId,
|
||||
goodsName: item.goodsName,
|
||||
retailPrice: item.retailPrice,
|
||||
quantity: item.quantity,
|
||||
taxRate: item.taxRate || 0,
|
||||
unitPriceWithTax: item.unitPriceWithTax,
|
||||
})),
|
||||
};
|
||||
createProcurementOrder(payload)
|
||||
.then((res) => {
|
||||
if (!res.success) return;
|
||||
const orderId = res.result.id;
|
||||
if (andSubmit) {
|
||||
return submitProcurementOrder(orderId).then((subRes) => {
|
||||
if (subRes.success) {
|
||||
this.$Message.success("保存并提交成功");
|
||||
this.goBack();
|
||||
}
|
||||
});
|
||||
}
|
||||
this.$Message.success("保存成功");
|
||||
this.goBack();
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
},
|
||||
handleAudit(pass) {
|
||||
auditProcurementOrder(this.orderDetail.id, { pass, remark: "" }).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("操作成功");
|
||||
this.goBack();
|
||||
}
|
||||
});
|
||||
},
|
||||
goBack() {
|
||||
this.$filters.customRouterPush({ name: "procurementPurchaseOrderList" });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.summary {
|
||||
padding: 12px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.ml_20 {
|
||||
margin-left: 20px;
|
||||
}
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
.item-table :deep(.cell-input) {
|
||||
width: 100%;
|
||||
}
|
||||
.item-table :deep(.cell-input .el-input__wrapper) {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
.item-table :deep(.sku-id-col .cell) {
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user