mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-08-06 02:47:29 +08:00
feat (router): 新增采购详情路由,优化预警数量管理功能
在管理员模块与商家模块中新增采购详情页面路由。 升级预警数量管理组件,优化交互体验与功能逻辑。
This commit is contained in:
38
manager/src/api/procurement.js
Normal file
38
manager/src/api/procurement.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
getRequest,
|
||||||
|
postRequest,
|
||||||
|
putRequest,
|
||||||
|
deleteRequest,
|
||||||
|
} from "@/libs/axios";
|
||||||
|
|
||||||
|
// ========== 采购单(只读) ==========
|
||||||
|
export const getProcurementOrderPage = params => getRequest("/procurement/order", params);
|
||||||
|
export const getProcurementOrderDetail = id => getRequest(`/procurement/order/${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 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 getDamageReportDetail = id => getRequest(`/procurement/damage-report/${id}`);
|
||||||
|
export const getDamageReportItems = id => getRequest(`/procurement/damage-report/${id}/items`);
|
||||||
|
|
||||||
|
// ========== 出入库原因 CRUD ==========
|
||||||
|
export const getStockReasonPage = params => getRequest("/procurement/reason", params);
|
||||||
|
export const addStockReason = params =>
|
||||||
|
postRequest("/procurement/reason", params, { "Content-Type": "application/json" });
|
||||||
|
export const updateStockReason = params =>
|
||||||
|
putRequest("/procurement/reason", params, { "Content-Type": "application/json" });
|
||||||
|
export const deleteStockReason = id => deleteRequest(`/procurement/reason/${id}`);
|
||||||
@@ -454,6 +454,30 @@ export const otherRouter = {
|
|||||||
name: "goodsSalesSummaryReport",
|
name: "goodsSalesSummaryReport",
|
||||||
meta: { title: "商品销售汇总报表" },
|
meta: { title: "商品销售汇总报表" },
|
||||||
component: () => import("@/views/statistics/goods-sales-summary.vue")
|
component: () => import("@/views/statistics/goods-sales-summary.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "procurement/purchase-order/detail",
|
||||||
|
title: "采购单详情",
|
||||||
|
name: "manager-procurement-order-detail",
|
||||||
|
component: () => import("@/views/procurement/purchase-order/detail.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "procurement/inbound/detail",
|
||||||
|
title: "入库单详情",
|
||||||
|
name: "manager-procurement-inbound-detail",
|
||||||
|
component: () => import("@/views/procurement/inbound/detail.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "procurement/inventory-count/detail",
|
||||||
|
title: "盘点单详情",
|
||||||
|
name: "manager-inventory-count-detail",
|
||||||
|
component: () => import("@/views/procurement/inventory-count/detail.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "procurement/damage-report/detail",
|
||||||
|
title: "报损单详情",
|
||||||
|
name: "manager-damage-report-detail",
|
||||||
|
component: () => import("@/views/procurement/damage-report/detail.vue")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
91
manager/src/views/procurement/constants.js
Normal file
91
manager/src/views/procurement/constants.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
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 const STOCK_REASON_CATEGORY = {
|
||||||
|
INBOUND: "入库",
|
||||||
|
OUTBOUND: "出库",
|
||||||
|
DAMAGE: "报损",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function stockReasonCategoryText(category) {
|
||||||
|
return STOCK_REASON_CATEGORY[category] || category || "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
83
manager/src/views/procurement/damage-report/detail.vue
Normal file
83
manager/src/views/procurement/damage-report/detail.vue
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>报损单详情</span>
|
||||||
|
<el-button @click="$router.back()">返回</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="状态">
|
||||||
|
<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="总数量">{{ 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-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="90" 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>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getDamageReportDetail, getDamageReportItems } from "@/api/procurement";
|
||||||
|
import { damageStatusText, damageStatusTag, formatMoney } from "../constants";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "manager-damage-report-detail",
|
||||||
|
data() {
|
||||||
|
return { detail: {}, items: [] };
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const id = this.$route.query.id;
|
||||||
|
if (id) {
|
||||||
|
getDamageReportDetail(id).then((res) => {
|
||||||
|
if (res.success) this.detail = res.result || {};
|
||||||
|
});
|
||||||
|
getDamageReportItems(id).then((res) => {
|
||||||
|
if (res.success) this.items = res.result || [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: { damageStatusText, damageStatusTag, formatMoney },
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
95
manager/src/views/procurement/damage-report/list.vue
Normal file
95
manager/src/views/procurement/damage-report/list.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<el-form :model="searchForm" inline label-width="80px">
|
||||||
|
<el-form-item label="店铺ID">
|
||||||
|
<el-input v-model="searchForm.storeId" clearable style="width: 200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="searchForm.status" clearable style="width: 160px">
|
||||||
|
<el-option v-for="(label, value) in DAMAGE_STATUS" :key="value" :label="label" :value="value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-table v-loading="loading" :data="data" border style="width: 100%">
|
||||||
|
<el-table-column prop="sn" label="报损单号" width="220" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="storeId" label="店铺ID" width="180" 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 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="80" 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"
|
||||||
|
:total="total"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
size="small"
|
||||||
|
@current-change="getDataList"
|
||||||
|
@size-change="getDataList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getDamageReportPage } from "@/api/procurement";
|
||||||
|
import { DAMAGE_STATUS, damageStatusText, damageStatusTag, formatMoney } from "../constants";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "manager-damage-report-list",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
DAMAGE_STATUS,
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
searchForm: { pageNumber: 1, pageSize: 10, storeId: "", status: "" },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getDataList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
damageStatusText,
|
||||||
|
damageStatusTag,
|
||||||
|
formatMoney,
|
||||||
|
handleSearch() {
|
||||||
|
this.searchForm.pageNumber = 1;
|
||||||
|
this.getDataList();
|
||||||
|
},
|
||||||
|
getDataList() {
|
||||||
|
this.loading = true;
|
||||||
|
getDamageReportPage(this.searchForm)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
this.data = res.result.records || [];
|
||||||
|
this.total = res.result.total || 0;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
goDetail(row) {
|
||||||
|
this.$router.push({ name: "manager-damage-report-detail", query: { id: row.id } });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
78
manager/src/views/procurement/inbound/detail.vue
Normal file
78
manager/src/views/procurement/inbound/detail.vue
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>入库单详情</span>
|
||||||
|
<el-button @click="$router.back()">返回</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-descriptions v-if="detail.id" :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>
|
||||||
|
<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="inboundQuantity" label="入库数量" width="90" align="center" />
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getProcurementInboundDetail, getProcurementInboundItems } from "@/api/procurement";
|
||||||
|
import { formatMoney } from "../constants";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "manager-procurement-inbound-detail",
|
||||||
|
data() {
|
||||||
|
return { detail: {}, items: [] };
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
totalInboundQuantity() {
|
||||||
|
return (this.items || []).reduce(
|
||||||
|
(sum, item) => sum + (Number(item.inboundQuantity) || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const id = this.$route.query.id;
|
||||||
|
if (id) {
|
||||||
|
getProcurementInboundDetail(id).then((res) => {
|
||||||
|
if (res.success) this.detail = res.result || {};
|
||||||
|
});
|
||||||
|
getProcurementInboundItems(id).then((res) => {
|
||||||
|
if (res.success) this.items = res.result || [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: { formatMoney },
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
83
manager/src/views/procurement/inbound/list.vue
Normal file
83
manager/src/views/procurement/inbound/list.vue
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<el-form :model="searchForm" inline label-width="80px">
|
||||||
|
<el-form-item label="入库单号">
|
||||||
|
<el-input v-model="searchForm.inboundSn" clearable style="width: 200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-table v-loading="loading" :data="data" border style="width: 100%">
|
||||||
|
<el-table-column prop="inboundSn" label="入库单号" width="220" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="storeId" label="店铺ID" width="180" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="confirmedQuantity" 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="80" 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"
|
||||||
|
:total="total"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
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: "manager-procurement-inbound-list",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
searchForm: { pageNumber: 1, pageSize: 10, inboundSn: "" },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getDataList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formatMoney,
|
||||||
|
handleSearch() {
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
goDetail(row) {
|
||||||
|
this.$router.push({ name: "manager-procurement-inbound-detail", query: { id: row.id } });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
106
manager/src/views/procurement/inventory-count/detail.vue
Normal file
106
manager/src/views/procurement/inventory-count/detail.vue
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>盘点单明细 - {{ sn }}</span>
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" :loading="exporting" @click="handleExport">导出 Excel</el-button>
|
||||||
|
<el-button @click="$router.back()">返回</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-table v-loading="loading" :data="data" 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="skuName" label="规格" width="120" show-overflow-tooltip />
|
||||||
|
<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>
|
||||||
|
<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"
|
||||||
|
:total="total"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
size="small"
|
||||||
|
@current-change="getDataList"
|
||||||
|
@size-change="getDataList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getInventoryCountItemsPage, exportInventoryCountItems } from "@/api/procurement";
|
||||||
|
import { downloadBlob } from "@/utils/downloadBlob";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "manager-inventory-count-detail",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
exporting: false,
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
sn: "",
|
||||||
|
searchForm: { pageNumber: 1, pageSize: 20 },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.sn = this.$route.query.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;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
66
manager/src/views/procurement/inventory-count/list.vue
Normal file
66
manager/src/views/procurement/inventory-count/list.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<el-table v-loading="loading" :data="data" border style="width: 100%">
|
||||||
|
<el-table-column prop="sn" label="盘点单号" width="220" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="storeId" label="店铺ID" width="180" 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 label="操作" width="80" 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"
|
||||||
|
:total="total"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
size="small"
|
||||||
|
@current-change="getDataList"
|
||||||
|
@size-change="getDataList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getInventoryCountPage } from "@/api/procurement";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "manager-inventory-count-list",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: 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;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
goDetail(row) {
|
||||||
|
this.$router.push({ name: "manager-inventory-count-detail", query: { id: row.id, sn: row.sn } });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
92
manager/src/views/procurement/purchase-order/detail.vue
Normal file
92
manager/src/views/procurement/purchase-order/detail.vue
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>采购单详情</span>
|
||||||
|
<el-button @click="$router.back()">返回</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-descriptions v-if="detail.id" :column="2" border>
|
||||||
|
<el-descriptions-item label="采购单号">{{ detail.orderSn }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="店铺">{{ detail.storeName }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="状态">
|
||||||
|
<el-tag :type="procurementStatusTag(detail.status)">{{ procurementStatusText(detail.status) }}</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="制单人">{{ detail.makerName }}</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.totalQuantity }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="总金额">¥{{ formatMoney(detail.totalAmount) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="出入库原因">{{ stockReasonText }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="备注" :span="2">{{ detail.remark || '-' }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<el-table :data="detail.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="90" align="center" />
|
||||||
|
<el-table-column prop="receivedQuantity" label="已入库" width="80" align="center" />
|
||||||
|
<el-table-column label="含税单价" width="110" align="right">
|
||||||
|
<template #default="{ row }">¥{{ formatMoney(row.unitPriceWithTax) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="含税小计" width="100" align="right">
|
||||||
|
<template #default="{ row }">¥{{ formatMoney(row.subtotalWithTax) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getProcurementOrderDetail, getStockReasonPage } from "@/api/procurement";
|
||||||
|
import { procurementStatusText, procurementStatusTag, formatMoney } from "../constants";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "manager-procurement-order-detail",
|
||||||
|
data() {
|
||||||
|
return { detail: {}, reasonOptions: [] };
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
stockReasonText() {
|
||||||
|
const reason = this.reasonOptions.find((item) => item.id === this.detail.stockReasonId);
|
||||||
|
return reason ? reason.reason : "-";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
getStockReasonPage({ category: "INBOUND", pageNumber: 1, pageSize: 100 }).then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
this.reasonOptions = res.result.records || [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const id = this.$route.query.id;
|
||||||
|
if (id) {
|
||||||
|
getProcurementOrderDetail(id).then((res) => {
|
||||||
|
if (res.success) this.detail = res.result || {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: { procurementStatusText, procurementStatusTag, formatMoney },
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
100
manager/src/views/procurement/purchase-order/list.vue
Normal file
100
manager/src/views/procurement/purchase-order/list.vue
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<el-form :model="searchForm" inline label-width="80px">
|
||||||
|
<el-form-item label="单据编号">
|
||||||
|
<el-input v-model="searchForm.orderSn" clearable style="width: 200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品名称">
|
||||||
|
<el-input v-model="searchForm.goodsName" clearable style="width: 200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="searchForm.status" clearable style="width: 160px">
|
||||||
|
<el-option v-for="(label, value) in PROCUREMENT_STATUS" :key="value" :label="label" :value="value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-table v-loading="loading" :data="data" border style="width: 100%">
|
||||||
|
<el-table-column prop="orderSn" label="采购单号" width="220" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="storeName" label="店铺" width="140" 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 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="80" 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"
|
||||||
|
:total="total"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
size="small"
|
||||||
|
@current-change="getDataList"
|
||||||
|
@size-change="getDataList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getProcurementOrderPage } from "@/api/procurement";
|
||||||
|
import { PROCUREMENT_STATUS, procurementStatusText, procurementStatusTag, formatMoney } from "../constants";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "manager-procurement-order-list",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
PROCUREMENT_STATUS,
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
searchForm: { pageNumber: 1, pageSize: 10, orderSn: "", goodsName: "", status: "" },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getDataList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
procurementStatusText,
|
||||||
|
procurementStatusTag,
|
||||||
|
formatMoney,
|
||||||
|
handleSearch() {
|
||||||
|
this.searchForm.pageNumber = 1;
|
||||||
|
this.getDataList();
|
||||||
|
},
|
||||||
|
getDataList() {
|
||||||
|
this.loading = true;
|
||||||
|
getProcurementOrderPage(this.searchForm)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
this.data = res.result.records || [];
|
||||||
|
this.total = res.result.total || 0;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
goDetail(row) {
|
||||||
|
this.$router.push({ name: "manager-procurement-order-detail", query: { id: row.id } });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
169
manager/src/views/procurement/reason/reasonManage.vue
Normal file
169
manager/src/views/procurement/reason/reasonManage.vue
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<el-form :model="searchForm" inline label-width="80px">
|
||||||
|
<el-form-item label="原因">
|
||||||
|
<el-input v-model="searchForm.reason" clearable style="width: 200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="类别">
|
||||||
|
<el-select v-model="searchForm.category" clearable style="width: 160px">
|
||||||
|
<el-option label="入库" value="INBOUND" />
|
||||||
|
<el-option label="出库" value="OUTBOUND" />
|
||||||
|
<el-option label="报损" value="DAMAGE" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div class="operation padding-row">
|
||||||
|
<el-button type="primary" @click="openAdd">添加</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table v-loading="loading" :data="data" border class="mt_10" style="width: 100%">
|
||||||
|
<el-table-column prop="reason" label="原因" width="240" show-overflow-tooltip />
|
||||||
|
<el-table-column label="类别" width="120" align="center">
|
||||||
|
<template #default="{ row }">{{ stockReasonCategoryText(row.category) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||||
|
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<a class="link-text" @click="openEdit(row)">编辑</a>
|
||||||
|
<span class="op-split">|</span>
|
||||||
|
<a class="link-text" @click="handleDelete(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"
|
||||||
|
:total="total"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
size="small"
|
||||||
|
@current-change="getDataList"
|
||||||
|
@size-change="getDataList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-dialog v-model="modalVisible" :title="modalTitle" width="480px" append-to-body>
|
||||||
|
<el-form ref="formRef" :model="form" label-width="80px">
|
||||||
|
<el-form-item label="原因" required>
|
||||||
|
<el-input v-model="form.reason" placeholder="出入库原因" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="类别" required>
|
||||||
|
<el-select v-model="form.category" style="width: 100%">
|
||||||
|
<el-option label="入库" value="INBOUND" />
|
||||||
|
<el-option label="出库" value="OUTBOUND" />
|
||||||
|
<el-option label="报损" value="DAMAGE" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="modalVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
getStockReasonPage,
|
||||||
|
addStockReason,
|
||||||
|
updateStockReason,
|
||||||
|
deleteStockReason,
|
||||||
|
} from "@/api/procurement";
|
||||||
|
import { stockReasonCategoryText } from "../constants";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "manager-stock-reason-manage",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
submitLoading: false,
|
||||||
|
modalVisible: false,
|
||||||
|
modalTitle: "添加原因",
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
searchForm: { pageNumber: 1, pageSize: 10, reason: "", category: "" },
|
||||||
|
form: { id: "", reason: "", category: "INBOUND" },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getDataList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
stockReasonCategoryText,
|
||||||
|
handleSearch() {
|
||||||
|
this.searchForm.pageNumber = 1;
|
||||||
|
this.getDataList();
|
||||||
|
},
|
||||||
|
getDataList() {
|
||||||
|
this.loading = true;
|
||||||
|
getStockReasonPage(this.searchForm)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
this.data = res.result.records || [];
|
||||||
|
this.total = res.result.total || 0;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
openAdd() {
|
||||||
|
this.modalTitle = "添加原因";
|
||||||
|
this.form = { id: "", reason: "", category: "INBOUND" };
|
||||||
|
this.modalVisible = true;
|
||||||
|
},
|
||||||
|
openEdit(row) {
|
||||||
|
this.modalTitle = "编辑原因";
|
||||||
|
this.form = { id: row.id, reason: row.reason, category: row.category };
|
||||||
|
this.modalVisible = true;
|
||||||
|
},
|
||||||
|
handleSubmit() {
|
||||||
|
if (!this.form.reason || !this.form.category) {
|
||||||
|
this.$Message.warning("请填写完整信息");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.submitLoading = true;
|
||||||
|
const req = this.form.id
|
||||||
|
? updateStockReason(this.form)
|
||||||
|
: addStockReason({ reason: this.form.reason, category: this.form.category });
|
||||||
|
req
|
||||||
|
.then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
this.$Message.success("保存成功");
|
||||||
|
this.modalVisible = false;
|
||||||
|
this.getDataList();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.submitLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleDelete(row) {
|
||||||
|
this.$Modal.confirm({
|
||||||
|
title: "删除确认",
|
||||||
|
content: `确认删除原因「${row.reason}」?`,
|
||||||
|
onOk: () => {
|
||||||
|
deleteStockReason(row.id).then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
this.$Message.success("删除成功");
|
||||||
|
this.getDataList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.op-split {
|
||||||
|
margin: 0 6px;
|
||||||
|
color: #dcdfe6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
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",
|
name: "goodsSalesSummaryReport",
|
||||||
meta: { title: "商品销售汇总报表" },
|
meta: { title: "商品销售汇总报表" },
|
||||||
component: () => import("@/views/statistics/goods-sales-summary.vue")
|
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>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-tabs v-model="stockType" @tab-click="switchTabs">
|
<div>
|
||||||
<el-tab-pane label="商品库存管理" name="stockManage">
|
<div class="operation padding-row">
|
||||||
<div class="operation padding-row">
|
<el-button type="primary" class="export" @click="exportStock">批量导出</el-button>
|
||||||
<el-button type="primary" class="export" @click="exportStock">批量导出</el-button>
|
<el-button class="export" @click="openImportStock">批量导入</el-button>
|
||||||
<el-button class="export" @click="openImportStock">批量导入</el-button>
|
</div>
|
||||||
</div>
|
<el-table ref="table" v-loading="loading" border :data="stockAllData" class="mt_10" style="width: 100%">
|
||||||
<el-table ref="table" v-loading="loading" border :data="stockAllData" class="mt_10" style="width: 100%">
|
<el-table-column label="商品信息" min-width="400">
|
||||||
<el-table-column label="商品信息" min-width="400">
|
<template #default="{ row }">
|
||||||
<template #default="{ row }">
|
<div style="margin-top: 5px; height: 90px; display: flex">
|
||||||
<div style="margin-top: 5px; height: 90px; display: flex">
|
<img :src="row.thumbnail" style="height: 80px; margin-top: 3px; width: 70px" alt="" />
|
||||||
<img :src="row.thumbnail" style="height: 80px; margin-top: 3px; width: 70px" alt="" />
|
<div style="margin-left: 13px; margin-top: 5px">
|
||||||
<div style="margin-left: 13px; margin-top: 5px">
|
<div class="div-zoom" style="color: black">{{ row.goodsName }}</div>
|
||||||
<div class="div-zoom" style="color: black">{{ row.goodsName }}</div>
|
<div class="div-zoom" style="margin-top: 5px">ID: {{ row.goodsId }}</div>
|
||||||
<div class="div-zoom" style="margin-top: 5px">ID: {{ row.goodsId }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</el-table-column>
|
</template>
|
||||||
<el-table-column label="SKU信息" min-width="400">
|
</el-table-column>
|
||||||
<template #default="{ row }">
|
<el-table-column label="SKU信息" min-width="400">
|
||||||
<div style="margin-top: 5px">
|
<template #default="{ row }">
|
||||||
<div class="div-zoom" style="color: black">{{ row.simpleSpecs }}</div>
|
<div style="margin-top: 5px">
|
||||||
<div class="div-zoom" style="margin-top: 5px">ID: {{ row.id }}</div>
|
<div class="div-zoom" style="color: black">{{ row.simpleSpecs }}</div>
|
||||||
</div>
|
<div class="div-zoom" style="margin-top: 5px">ID: {{ row.id }}</div>
|
||||||
</template>
|
</div>
|
||||||
</el-table-column>
|
</template>
|
||||||
<el-table-column label="上架状态" width="130">
|
</el-table-column>
|
||||||
<template #default="{ row }">
|
<el-table-column label="上架状态" width="130">
|
||||||
<el-tag :type="row.marketEnable === 'DOWN' ? 'danger' : 'success'">
|
<template #default="{ row }">
|
||||||
{{ row.marketEnable === "DOWN" ? "下架" : "上架" }}
|
<el-tag :type="row.marketEnable === 'DOWN' ? 'danger' : 'success'">
|
||||||
</el-tag>
|
{{ row.marketEnable === "DOWN" ? "下架" : "上架" }}
|
||||||
</template>
|
</el-tag>
|
||||||
</el-table-column>
|
</template>
|
||||||
<el-table-column label="审核状态" width="120">
|
</el-table-column>
|
||||||
<template #default="{ row }">
|
<el-table-column label="审核状态" width="120">
|
||||||
<el-tag
|
<template #default="{ row }">
|
||||||
:type="
|
<el-tag
|
||||||
row.authFlag === 'PASS' ? 'success' : row.authFlag === 'TOBEAUDITED' ? 'warning' : 'danger'
|
:type="
|
||||||
"
|
row.authFlag === 'PASS' ? 'success' : row.authFlag === 'TOBEAUDITED' ? 'warning' : 'danger'
|
||||||
>
|
"
|
||||||
{{
|
>
|
||||||
row.authFlag === "PASS" ? "通过" : row.authFlag === "TOBEAUDITED" ? "待审核" : "审核拒绝"
|
{{
|
||||||
}}
|
row.authFlag === "PASS" ? "通过" : row.authFlag === "TOBEAUDITED" ? "待审核" : "审核拒绝"
|
||||||
</el-tag>
|
}}
|
||||||
</template>
|
</el-tag>
|
||||||
</el-table-column>
|
</template>
|
||||||
<el-table-column label="库存" min-width="120">
|
</el-table-column>
|
||||||
<template #default="{ row }">{{ row.quantity || 0 }}</template>
|
<el-table-column label="库存" min-width="120">
|
||||||
</el-table-column>
|
<template #default="{ row }">{{ row.quantity || 0 }}</template>
|
||||||
</el-table>
|
</el-table-column>
|
||||||
</el-tab-pane>
|
</el-table>
|
||||||
|
</div>
|
||||||
<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">
|
<div class="mt_10" style="display: flex; justify-content: flex-end">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
@@ -139,36 +99,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-card>
|
</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">
|
<el-dialog v-model="importModal" title="导入商品信息" :close-on-click-modal="false">
|
||||||
<div v-loading="spinShow" style="text-align: center">
|
<div v-loading="spinShow" style="text-align: center">
|
||||||
<el-upload drag :before-upload="handleUpload" :show-file-list="false" accept=".xlsx,.xls">
|
<el-upload drag :before-upload="handleUpload" :show-file-list="false" accept=".xlsx,.xls">
|
||||||
@@ -188,38 +118,28 @@
|
|||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
getGoodsCategoryAll,
|
getGoodsCategoryAll,
|
||||||
getGoodsListDataByStockSeller,
|
|
||||||
getGoodsSkuListDataSeller,
|
getGoodsSkuListDataSeller,
|
||||||
importStockExcel,
|
importStockExcel,
|
||||||
queryExportStock,
|
queryExportStock,
|
||||||
updateGoodsAlertStocks,
|
|
||||||
updateGoodsSkuStocks,
|
|
||||||
} from "@/api/goods";
|
} from "@/api/goods";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "goods",
|
name: "alertQuantity",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
importModal: false,
|
importModal: false,
|
||||||
spinShow: false,
|
spinShow: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
updateStockModalVisible: false,
|
|
||||||
stockType: "stockManage",
|
|
||||||
searchForm: {
|
searchForm: {
|
||||||
pageNumber: 1,
|
pageNumber: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
sort: "create_time",
|
sort: "create_time",
|
||||||
order: "desc",
|
order: "desc",
|
||||||
},
|
},
|
||||||
checkFlag: false,
|
|
||||||
stockList: [],
|
|
||||||
warnData: [],
|
|
||||||
skuAllData: [],
|
|
||||||
stockAllData: [],
|
stockAllData: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
categoryList: [],
|
categoryList: [],
|
||||||
category: [],
|
category: [],
|
||||||
selectedSku: {},
|
|
||||||
file: null,
|
file: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -228,51 +148,6 @@ export default {
|
|||||||
this.getDataList();
|
this.getDataList();
|
||||||
this.deepGroup();
|
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) {
|
changePageSize(v) {
|
||||||
this.searchForm.pageSize = v;
|
this.searchForm.pageSize = v;
|
||||||
this.getDataList();
|
this.getDataList();
|
||||||
@@ -298,31 +173,13 @@ export default {
|
|||||||
},
|
},
|
||||||
getDataList() {
|
getDataList() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
if (this.stockType === "warnList") {
|
getGoodsSkuListDataSeller(this.searchForm).then((res) => {
|
||||||
getGoodsListDataByStockSeller(this.searchForm).then((res) => {
|
if (res.success) {
|
||||||
if (res.success) {
|
this.stockAllData = res.result.records;
|
||||||
this.warnData = res.result.records;
|
this.total = res.result.total;
|
||||||
this.total = res.result.total;
|
}
|
||||||
}
|
this.loading = false;
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
deepGroup() {
|
deepGroup() {
|
||||||
getGoodsCategoryAll().then((res) => {
|
getGoodsCategoryAll().then((res) => {
|
||||||
@@ -394,9 +251,4 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "@/styles/table-common.scss";
|
@import "@/styles/table-common.scss";
|
||||||
.link-text {
|
|
||||||
color: #2d8cf0;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
</style>
|
</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) {
|
setData(v, init) {
|
||||||
if (typeof v == "string") {
|
if (typeof v == "string") {
|
||||||
if (this.multiple) {
|
if (this.multiple) {
|
||||||
this.$Message.warning("多张上传仅支持数组数据类型");
|
if (!v) {
|
||||||
|
this.uploadList = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setData(
|
||||||
|
v.split(",").map((s) => s.trim()).filter(Boolean),
|
||||||
|
init
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!v) {
|
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