feat (router): 新增采购详情路由,优化预警数量管理功能

在管理员模块与商家模块中新增采购详情页面路由。
升级预警数量管理组件,优化交互体验与功能逻辑。
This commit is contained in:
pikachu1995@126.com
2026-07-22 15:52:15 +08:00
parent e91d783e22
commit c83d715234
26 changed files with 3569 additions and 206 deletions

View 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}`);

View File

@@ -454,6 +454,30 @@ export const otherRouter = {
name: "goodsSalesSummaryReport",
meta: { title: "商品销售汇总报表" },
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")
}
]
};

View 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);
}

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>