销售发货,产品调整
This commit is contained in:
@@ -66,7 +66,11 @@ export function importProductData(data, updateSupport) {
|
||||
return request({
|
||||
url: '/mat/product/importData?updateSupport=' + (updateSupport ? 1 : 0),
|
||||
method: 'post',
|
||||
data: data
|
||||
data: data,
|
||||
timeout: 1800000,
|
||||
headers: {
|
||||
repeatSubmit: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,48 @@ export function delShippingOrder(shippingId) {
|
||||
})
|
||||
}
|
||||
|
||||
// ================================
|
||||
// 发货单据明细(gear_shipping_order_detail)
|
||||
// ================================
|
||||
|
||||
export function listShippingOrderDetail(query) {
|
||||
return request({
|
||||
url: '/oa/shippingOrderDetail/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getShippingOrderDetail(detailId) {
|
||||
return request({
|
||||
url: '/oa/shippingOrderDetail/' + detailId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function addShippingOrderDetail(data) {
|
||||
return request({
|
||||
url: '/oa/shippingOrderDetail',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateShippingOrderDetail(data) {
|
||||
return request({
|
||||
url: '/oa/shippingOrderDetail',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function delShippingOrderDetail(detailId) {
|
||||
return request({
|
||||
url: '/oa/shippingOrderDetail/' + detailId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// ================================
|
||||
// 发货计划(独立表 gear_shipping_plan)
|
||||
// ================================
|
||||
|
||||
@@ -36,6 +36,10 @@ service.interceptors.request.use(config => {
|
||||
config.params = {}
|
||||
config.url = url
|
||||
}
|
||||
const isFormData = typeof FormData !== 'undefined' && config.data instanceof FormData
|
||||
if (isFormData) {
|
||||
delete config.headers['Content-Type']
|
||||
}
|
||||
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
|
||||
const requestObj = {
|
||||
url: config.url,
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
<el-form-item label="辅料名称" prop="materialName">
|
||||
<el-input v-model="queryParams.materialName" placeholder="请输入辅料名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格" prop="spec">
|
||||
<el-input v-model="queryParams.spec" placeholder="请输入规格" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="型号" prop="model">
|
||||
<el-input v-model="queryParams.model" placeholder="请输入型号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="厂家" prop="factory">
|
||||
<el-input v-model="queryParams.factory" placeholder="请输入厂家" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
@@ -40,6 +46,8 @@
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="辅料ID 主键" align="center" prop="materialId" v-if="true" /> -->
|
||||
<el-table-column label="辅料名称" align="center" prop="materialName" />
|
||||
<el-table-column label="规格" align="center" prop="spec" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="型号" align="center" prop="model" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="物料类型" align="center" prop="materialType">
|
||||
<template #default="scope">
|
||||
{{ scope.row.materialType === 1 ? '辅料' : '原料' }}
|
||||
@@ -75,6 +83,12 @@
|
||||
<el-form-item label="辅料名称" prop="materialName">
|
||||
<el-input v-model="form.materialName" placeholder="请输入辅料名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格" prop="spec">
|
||||
<el-input v-model="form.spec" placeholder="请输入规格" />
|
||||
</el-form-item>
|
||||
<el-form-item label="型号" prop="model">
|
||||
<el-input v-model="form.model" placeholder="请输入型号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="物料类型" prop="materialType">
|
||||
<el-select v-model="form.materialType" placeholder="请选择物料类型" disabled>
|
||||
<el-option label="辅料" value="1" />
|
||||
|
||||
@@ -2,6 +2,30 @@
|
||||
<div class="stock-dashboard-container" style="padding: 20px;">
|
||||
<!-- 加载状态 -->
|
||||
<el-loading v-loading="loading" text="数据加载中...">
|
||||
<el-card shadow="never" style="margin-bottom: 12px;">
|
||||
<el-form :inline="true" size="small" label-width="70px">
|
||||
<el-form-item label="厂家">
|
||||
<el-input v-model="statQuery.factory" placeholder="请输入厂家" clearable style="width: 220px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时间段">
|
||||
<el-date-picker
|
||||
v-model="statQuery.timeRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 360px"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onStatSearch">查询</el-button>
|
||||
<el-button @click="onStatReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<!-- 第一行:4个指标卡 -->
|
||||
<el-row :gutter="20" mb="20">
|
||||
<el-col :span="6" v-for="item in statCards" :key="item.key">
|
||||
@@ -114,6 +138,11 @@ const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
const statQuery = reactive({
|
||||
factory: undefined,
|
||||
timeRange: undefined
|
||||
})
|
||||
const total = ref(0) // 材料总条数
|
||||
|
||||
// 格式化数值:保留2位小数,处理字符串转数字
|
||||
@@ -144,7 +173,10 @@ const inOutCompareData = ref([]) // 出入库对比折线图数据
|
||||
// 1. 获取材料列表(带分页)
|
||||
const getMaterialList = async () => {
|
||||
try {
|
||||
const res = await listMaterial(queryParams) // 传入分页参数
|
||||
const res = await listMaterial({
|
||||
...queryParams,
|
||||
factory: statQuery.factory || undefined
|
||||
})
|
||||
materialList.value = res.rows || []
|
||||
total.value = res.total || 0 // 若依接口返回total总条数
|
||||
} catch (err) {
|
||||
@@ -153,13 +185,32 @@ const getMaterialList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const buildStatParams = () => {
|
||||
const beginTime = statQuery.timeRange && statQuery.timeRange[0] ? statQuery.timeRange[0] : undefined
|
||||
const endTime = statQuery.timeRange && statQuery.timeRange[1] ? statQuery.timeRange[1] : undefined
|
||||
return {
|
||||
beginTime,
|
||||
endTime,
|
||||
factory: statQuery.factory || undefined
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 获取入库+出库列表(无需分页,取全部数据做统计)
|
||||
const getInOutList = async () => {
|
||||
try {
|
||||
const params = buildStatParams()
|
||||
// 并行请求,提高效率
|
||||
const [inRes, outRes] = await Promise.all([
|
||||
listPurchaseInDetail(),
|
||||
listMaterialOut()
|
||||
listPurchaseInDetail({
|
||||
pageNum: 1,
|
||||
pageSize: 10000,
|
||||
...params
|
||||
}),
|
||||
listMaterialOut({
|
||||
pageNum: 1,
|
||||
pageSize: 10000,
|
||||
...params
|
||||
})
|
||||
])
|
||||
purchaseInDetailList.value = inRes.rows || []
|
||||
materialOutList.value = outRes.rows || []
|
||||
@@ -301,6 +352,18 @@ const loadAllData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const onStatSearch = () => {
|
||||
queryParams.pageNum = 1
|
||||
loadAllData()
|
||||
}
|
||||
|
||||
const onStatReset = () => {
|
||||
statQuery.factory = undefined
|
||||
statQuery.timeRange = undefined
|
||||
queryParams.pageNum = 1
|
||||
loadAllData()
|
||||
}
|
||||
|
||||
// 页面挂载时加载数据
|
||||
onMounted(() => {
|
||||
loadAllData()
|
||||
@@ -318,4 +381,4 @@ onMounted(() => {
|
||||
.el-card {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -196,7 +196,7 @@ import { Document } from '@element-plus/icons-vue';
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const productTypeLabel = (val) => (val === 'semi' ? '半成品' : '产品');
|
||||
const productTypeLabel = (val) => (val === 'semi' ? '半成品' : '成品');
|
||||
const productTypeTagType = (val) => (val === 'semi' ? 'warning' : 'success');
|
||||
|
||||
const productDetail = ref({});
|
||||
|
||||
@@ -86,16 +86,20 @@
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="产品图片" align="center" prop="productImages">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.productImages && scope.row.productImages.trim()" class="image-preview">
|
||||
<div v-if="imageUrls(scope.row).length" class="image-preview">
|
||||
<el-image
|
||||
v-for="(image, index) in scope.row.productImages.split(',')"
|
||||
:key="index"
|
||||
v-for="(image, index) in imageUrls(scope.row)"
|
||||
:key="image + '_' + index"
|
||||
:src="image"
|
||||
:preview-src-list="scope.row.productImages.split(',')"
|
||||
:preview-src-list="imageUrls(scope.row)"
|
||||
:z-index="9999"
|
||||
:preview-teleported="true"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
>
|
||||
<template #error>
|
||||
<div class="image-error">—</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
<span v-else>无</span>
|
||||
</template>
|
||||
@@ -146,7 +150,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="产品类型" prop="productType">
|
||||
<el-select v-model="form.productType" placeholder="请选择产品类型" style="width: 100%">
|
||||
<el-option label="产品" value="product" />
|
||||
<el-option label="成品" value="product" />
|
||||
<el-option label="半成品" value="semi" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -405,6 +409,7 @@ const updateSupport = ref(false);
|
||||
const importFile = ref(null);
|
||||
const additionPreviewMap = ref({});
|
||||
const additionPreviewLoading = ref({});
|
||||
const imagePreviewMap = ref({});
|
||||
const manualOpen = ref(false);
|
||||
const manualLoading = ref(false);
|
||||
const manualFiles = ref([]);
|
||||
@@ -430,7 +435,7 @@ const formatterTime = (time) => {
|
||||
return proxy.parseTime(time, '{y}-{m}-{d}')
|
||||
}
|
||||
|
||||
const productTypeLabel = (val) => (val === 'semi' ? '半成品' : '产品');
|
||||
const productTypeLabel = (val) => (val === 'semi' ? '半成品' : '成品');
|
||||
const productTypeTagType = (val) => (val === 'semi' ? 'warning' : 'success');
|
||||
|
||||
const data = reactive({
|
||||
@@ -471,6 +476,7 @@ function getList() {
|
||||
productList.value = response.rows;
|
||||
total.value = response.total;
|
||||
prefetchAdditions(productList.value);
|
||||
prefetchImages(productList.value);
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
@@ -549,11 +555,28 @@ function handleUpdate(row) {
|
||||
|
||||
// 处理图片文件列表
|
||||
if (form.value.productImages) {
|
||||
imageFileList.value = form.value.productImages.split(',').map((url, index) => ({
|
||||
name: url.substring(url.lastIndexOf('/') + 1),
|
||||
url: url,
|
||||
uid: Date.now() + index
|
||||
}));
|
||||
const raw = String(form.value.productImages).trim();
|
||||
if (isOssIdList(raw)) {
|
||||
listByIds(raw).then(res => {
|
||||
const list = res?.data || [];
|
||||
const urls = list.map(oss => oss?.url).filter(Boolean);
|
||||
form.value.productImages = urls.join(',');
|
||||
imageFileList.value = urls.map((url, index) => ({
|
||||
name: getFileNameFromUrl(url),
|
||||
url,
|
||||
uid: Date.now() + index
|
||||
}));
|
||||
}).catch(() => {
|
||||
imageFileList.value = [];
|
||||
});
|
||||
} else {
|
||||
const urls = raw.split(',').map(s => String(s).trim()).filter(Boolean);
|
||||
imageFileList.value = urls.map((url, index) => ({
|
||||
name: getFileNameFromUrl(url),
|
||||
url,
|
||||
uid: Date.now() + index
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
imageFileList.value = [];
|
||||
}
|
||||
@@ -884,6 +907,59 @@ function prefetchAdditions(list) {
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
function normalizeImageUrlList(raw) {
|
||||
const str = raw == null ? '' : String(raw).trim();
|
||||
if (!str) return [];
|
||||
if (isOssIdList(str)) return [];
|
||||
return str
|
||||
.split(',')
|
||||
.map(s => String(s).trim())
|
||||
.filter(Boolean)
|
||||
.filter(u => /^https?:\/\//i.test(u) || u.startsWith('/') || u.startsWith('data:'));
|
||||
}
|
||||
|
||||
function imageUrls(row) {
|
||||
const key = row?.productId != null ? String(row.productId) : '';
|
||||
const cached = key ? imagePreviewMap.value[key] : null;
|
||||
if (Array.isArray(cached)) return cached;
|
||||
return normalizeImageUrlList(row?.productImages);
|
||||
}
|
||||
|
||||
function prefetchImages(list) {
|
||||
const rows = Array.isArray(list) ? list : [];
|
||||
const targets = rows
|
||||
.map(r => ({ productId: r?.productId, raw: String(r?.productImages || '').trim() }))
|
||||
.filter(x => x.productId != null && x.raw && isOssIdList(x.raw));
|
||||
if (!targets.length) return;
|
||||
const idSet = new Set();
|
||||
for (const it of targets) {
|
||||
const ids = it.raw.split(',').map(s => String(s).trim()).filter(Boolean);
|
||||
for (const id of ids) idSet.add(id);
|
||||
}
|
||||
const all = Array.from(idSet);
|
||||
if (!all.length) return;
|
||||
listByIds(all.join(',')).then(res => {
|
||||
const list = res?.data || [];
|
||||
const map = {};
|
||||
for (const oss of list) {
|
||||
if (oss?.ossId != null && oss?.url) {
|
||||
map[String(oss.ossId)] = oss.url;
|
||||
}
|
||||
}
|
||||
const next = { ...imagePreviewMap.value };
|
||||
for (const it of targets) {
|
||||
const urls = it.raw
|
||||
.split(',')
|
||||
.map(s => String(s).trim())
|
||||
.filter(Boolean)
|
||||
.map(id => map[String(id)])
|
||||
.filter(Boolean);
|
||||
next[String(it.productId)] = urls;
|
||||
}
|
||||
imagePreviewMap.value = next;
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
function pdfCount(row) {
|
||||
const raw = String(row?.productPdfs || '').trim();
|
||||
if (!raw) return 0;
|
||||
@@ -1092,6 +1168,18 @@ getList();
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.image-error {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f5f7fa;
|
||||
color: #c0c4cc;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-table .el-table__cell) {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
|
||||
@@ -74,13 +74,13 @@
|
||||
{{ formatDecimal(scope.row.receivedQty) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收回状态" align="center" width="110">
|
||||
<!-- <el-table-column label="收回状态" align="center" width="110">
|
||||
<template #default="scope">
|
||||
<el-tag :type="receiveStatusTagType(scope.row)" effect="dark">
|
||||
{{ receiveStatusText(scope.row) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column> -->
|
||||
<el-table-column label="委外时间" align="center" prop="outTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ formatterTime(scope.row.outTime) }}</span>
|
||||
@@ -90,7 +90,7 @@
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="Check" :disabled="isFullyReceived(scope.row)" @click="openReceive(scope.row)">收回</el-button>
|
||||
<!-- <el-button link type="primary" icon="Check" :disabled="isFullyReceived(scope.row)" @click="openReceive(scope.row)">收回</el-button> -->
|
||||
<el-button link type="primary" icon="Delete" :disabled="hasReceived(scope.row)" @click="handleDelete(scope.row)">撤回</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -149,7 +149,7 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="收回" v-model="receiveOpen" width="460px" append-to-body>
|
||||
<!-- <el-dialog title="收回" v-model="receiveOpen" width="460px" append-to-body>
|
||||
<el-form ref="receiveRef" :model="receiveForm" label-width="90px">
|
||||
<el-form-item label="收回数量">
|
||||
<el-input-number v-model="receiveForm.receiveQty" style="width: 100%" :controls="false" :min="0" :precision="4" />
|
||||
@@ -167,12 +167,12 @@
|
||||
<el-button @click="receiveOpen = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-dialog> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="ProductOutsourceOrder">
|
||||
import { listProductOutsourceOrder, addProductOutsourceOrder, delProductOutsourceOrder, receiveProductOutsourceOrder } from "@/api/mat/productOutsourceOrder";
|
||||
import { listProductOutsourceOrder, addProductOutsourceOrder, delProductOutsourceOrder } from "@/api/mat/productOutsourceOrder";
|
||||
import { listProductBase } from "@/api/mat/product";
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { formatDecimal } from '@/utils/gear'
|
||||
@@ -194,9 +194,9 @@ const title = ref("");
|
||||
const productOptions = ref([]);
|
||||
const productLoading = ref(false);
|
||||
|
||||
const receiveOpen = ref(false);
|
||||
const receiveLoading = ref(false);
|
||||
const receiveForm = ref({ orderId: null, receiveQty: null, receiveTime: null, receiveBy: null });
|
||||
// const receiveOpen = ref(false);
|
||||
// const receiveLoading = ref(false);
|
||||
// const receiveForm = ref({ orderId: null, receiveQty: null, receiveTime: null, receiveBy: null });
|
||||
|
||||
const formatterTime = (time) => proxy.parseTime(time, '{y}-{m}-{d}')
|
||||
const productTypeLabel = (val) => (val === 'semi' ? '半成品' : '成品');
|
||||
@@ -204,20 +204,24 @@ const productTypeTagType = (val) => (val === 'semi' ? 'warning' : 'success');
|
||||
|
||||
const rowQty = (v) => (v === null || v === undefined ? 0 : Number(v) || 0);
|
||||
const hasReceived = (row) => rowQty(row?.receivedQty) > 0;
|
||||
const isFullyReceived = (row) => rowQty(row?.receivedQty) >= rowQty(row?.quantity);
|
||||
const receiveStatusText = (row) => {
|
||||
const totalQty = rowQty(row?.quantity);
|
||||
const receivedQty = rowQty(row?.receivedQty);
|
||||
if (receivedQty <= 0) return '未收回';
|
||||
if (receivedQty >= totalQty) return '已收回';
|
||||
return '部分收回';
|
||||
};
|
||||
const receiveStatusTagType = (row) => {
|
||||
const t = receiveStatusText(row);
|
||||
if (t === '已收回') return 'success';
|
||||
if (t === '部分收回') return 'warning';
|
||||
return 'info';
|
||||
const isFullyReceived = (row) => {
|
||||
const total = rowQty(row?.quantity);
|
||||
if (total <= 0) return false;
|
||||
return rowQty(row?.receivedQty) >= total;
|
||||
};
|
||||
// const receiveStatusText = (row) => {
|
||||
// const totalQty = rowQty(row?.quantity);
|
||||
// const receivedQty = rowQty(row?.receivedQty);
|
||||
// if (receivedQty <= 0) return '未收回';
|
||||
// if (receivedQty >= totalQty) return '已收回';
|
||||
// return '部分收回';
|
||||
// };
|
||||
// const receiveStatusTagType = (row) => {
|
||||
// const t = receiveStatusText(row);
|
||||
// if (t === '已收回') return 'success';
|
||||
// if (t === '部分收回') return 'warning';
|
||||
// return 'info';
|
||||
// };
|
||||
|
||||
const productOptionLabel = (p) => {
|
||||
const name = p?.productName || '-';
|
||||
@@ -243,7 +247,15 @@ const { queryParams, form, rules } = toRefs(data);
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
listProductOutsourceOrder(queryParams.value).then(res => {
|
||||
list.value = res.rows;
|
||||
const rows = (res && res.rows) ? res.rows : [];
|
||||
const filtered = rows.filter(r => !isFullyReceived(r));
|
||||
if (filtered.length === 0 && queryParams.value.pageNum > 1) {
|
||||
queryParams.value.pageNum = queryParams.value.pageNum - 1;
|
||||
loading.value = false;
|
||||
getList();
|
||||
return;
|
||||
}
|
||||
list.value = filtered;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
});
|
||||
@@ -325,36 +337,36 @@ function handleDelete(row) {
|
||||
});
|
||||
}
|
||||
|
||||
function openReceive(row) {
|
||||
const outTime = proxy.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
|
||||
const totalQty = rowQty(row?.quantity);
|
||||
const receivedQty = rowQty(row?.receivedQty);
|
||||
const remaining = totalQty - receivedQty;
|
||||
receiveForm.value = {
|
||||
orderId: row?.orderId,
|
||||
receiveQty: remaining > 0 ? remaining : 0,
|
||||
receiveTime: outTime,
|
||||
receiveBy: nickName
|
||||
};
|
||||
receiveOpen.value = true;
|
||||
}
|
||||
// function openReceive(row) {
|
||||
// const outTime = proxy.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
|
||||
// const totalQty = rowQty(row?.quantity);
|
||||
// const receivedQty = rowQty(row?.receivedQty);
|
||||
// const remaining = totalQty - receivedQty;
|
||||
// receiveForm.value = {
|
||||
// orderId: row?.orderId,
|
||||
// receiveQty: remaining > 0 ? remaining : 0,
|
||||
// receiveTime: outTime,
|
||||
// receiveBy: nickName
|
||||
// };
|
||||
// receiveOpen.value = true;
|
||||
// }
|
||||
|
||||
function submitReceive() {
|
||||
if (receiveLoading.value) return;
|
||||
const orderId = receiveForm.value.orderId;
|
||||
receiveLoading.value = true;
|
||||
receiveProductOutsourceOrder(orderId, {
|
||||
receiveQty: receiveForm.value.receiveQty,
|
||||
receiveTime: receiveForm.value.receiveTime,
|
||||
receiveBy: receiveForm.value.receiveBy
|
||||
}).then(() => {
|
||||
proxy.$modal.msgSuccess("收回成功");
|
||||
receiveOpen.value = false;
|
||||
getList();
|
||||
}).finally(() => {
|
||||
receiveLoading.value = false;
|
||||
});
|
||||
}
|
||||
// function submitReceive() {
|
||||
// if (receiveLoading.value) return;
|
||||
// const orderId = receiveForm.value.orderId;
|
||||
// receiveLoading.value = true;
|
||||
// receiveProductOutsourceOrder(orderId, {
|
||||
// receiveQty: receiveForm.value.receiveQty,
|
||||
// receiveTime: receiveForm.value.receiveTime,
|
||||
// receiveBy: receiveForm.value.receiveBy
|
||||
// }).then(() => {
|
||||
// proxy.$modal.msgSuccess("收回成功");
|
||||
// receiveOpen.value = false;
|
||||
// getList();
|
||||
// }).finally(() => {
|
||||
// receiveLoading.value = false;
|
||||
// });
|
||||
// }
|
||||
|
||||
function handleExport() {
|
||||
proxy.download('mat/productOutsourceOrder/export', {
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
<el-form-item label="配料名称" prop="materialName">
|
||||
<el-input v-model="queryParams.materialName" placeholder="请输入配料名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格" prop="spec">
|
||||
<el-input v-model="queryParams.spec" placeholder="请输入规格" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="型号" prop="model">
|
||||
<el-input v-model="queryParams.model" placeholder="请输入型号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="厂家" prop="factory">
|
||||
<el-input v-model="queryParams.factory" placeholder="请输入厂家" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
@@ -44,6 +50,8 @@
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="配料ID 主键" align="center" prop="materialId" v-if="true" /> -->
|
||||
<el-table-column label="配料名称" align="center" prop="materialName" />
|
||||
<el-table-column label="规格" align="center" prop="spec" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="型号" align="center" prop="model" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="物料类型" align="center" prop="materialType">
|
||||
<template #default="scope">
|
||||
{{ scope.row.materialType === 1 ? '辅料' : '主材' }}
|
||||
@@ -79,6 +87,12 @@
|
||||
<el-form-item label="配料名称" prop="materialName">
|
||||
<el-input v-model="form.materialName" placeholder="请输入配料名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格" prop="spec">
|
||||
<el-input v-model="form.spec" placeholder="请输入规格" />
|
||||
</el-form-item>
|
||||
<el-form-item label="型号" prop="model">
|
||||
<el-input v-model="form.model" placeholder="请输入型号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="物料类型" prop="materialType">
|
||||
<el-select v-model="form.materialType" placeholder="请选择物料类型" disabled>
|
||||
<el-option label="主材" value="2" />
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<span class="order-left-card__value">{{ item.customerName || "—" }}</span>
|
||||
</div>
|
||||
<div class="order-left-card__header-right">
|
||||
<dict-tag class="order-left-card__status" :options="order_status" :value="item.orderStatus" />
|
||||
<div class="order-left-card__status-spacer" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -86,13 +86,18 @@
|
||||
<div class="top-attachment-title">
|
||||
<span>订单附件</span>
|
||||
<div class="top-attachment-actions">
|
||||
<!-- 预览按钮:打开弹窗预览(避免在Tab上侧区域占用过多空间) -->
|
||||
<el-button plain size="small" :disabled="!form.contractExcelOssIds" @click="openAttachmentPreview">
|
||||
预览
|
||||
<el-button
|
||||
v-if="form.contractExcelOssIds"
|
||||
plain
|
||||
size="small"
|
||||
style="margin-right: 8px;"
|
||||
@click="clearAttachment"
|
||||
>
|
||||
删除附件
|
||||
</el-button>
|
||||
<FileUpload
|
||||
v-model="form.contractExcelOssIds"
|
||||
:limit="5"
|
||||
:limit="1"
|
||||
:file-size="20"
|
||||
:file-type="['xls', 'xlsx', 'pdf']"
|
||||
:is-show-tip="false"
|
||||
@@ -102,50 +107,9 @@
|
||||
</div>
|
||||
|
||||
<div class="top-attachment-body">
|
||||
<!-- 附件概览:只展示数量与文件名(不在这里做预览) -->
|
||||
<div class="top-attachment-summary">
|
||||
<div class="top-attachment-summary__left">
|
||||
<span class="top-attachment-summary__label">数量:</span>
|
||||
<span class="top-attachment-summary__value">{{ attachmentFiles.length }}</span>
|
||||
</div>
|
||||
<div class="top-attachment-summary__right">
|
||||
<el-empty v-if="!attachmentFiles.length" description="暂无附件" />
|
||||
<el-scrollbar v-else max-height="120px">
|
||||
<div
|
||||
v-for="(item, index) in attachmentFiles"
|
||||
:key="item.ossId || item.url || index"
|
||||
class="top-attachment-file"
|
||||
>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 订单附件预览弹窗:左侧列表 + 右侧预览(对齐产品说明书预览的形式) -->
|
||||
<el-dialog title="订单附件预览" v-model="attachmentPreviewOpen" width="1200px" append-to-body>
|
||||
<div class="attachment-preview-wrap" v-loading="attachmentPreviewLoading">
|
||||
<div class="attachment-list">
|
||||
<div class="attachment-list-title">附件列表({{ attachmentFiles.length }})</div>
|
||||
<el-empty v-if="!attachmentFiles.length" description="暂无附件" />
|
||||
<el-scrollbar v-else max-height="520px">
|
||||
<div
|
||||
v-for="(item, index) in attachmentFiles"
|
||||
:key="item.ossId || item.url || index"
|
||||
class="attachment-item"
|
||||
:class="{ active: item.url === currentAttachmentUrl }"
|
||||
@click="selectAttachment(item)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<div class="attachment-viewer">
|
||||
<el-empty v-if="!currentAttachmentUrl" description="请选择附件" />
|
||||
<div class="attachment-viewer top-attachment-single-viewer">
|
||||
<el-empty v-if="!currentAttachmentUrl" description="暂无附件" />
|
||||
<iframe v-else-if="canIframePreview" :src="iframePreviewUrl" class="attachment-iframe" />
|
||||
<!-- Excel 预览:不依赖第三方在线预览,直接在前端解析展示(不保证样式100%一致,但可看数据) -->
|
||||
<div v-else-if="isExcelPreview" class="excel-preview-wrap" v-loading="excelPreviewLoading">
|
||||
<component
|
||||
v-if="vueOfficeExcelComp && officeExcelUrl"
|
||||
@@ -163,7 +127,7 @@
|
||||
<div class="excel-table-tip" v-if="excelTruncated">
|
||||
预览已截断,仅展示前 {{ excelMaxRows }} 行、前 {{ excelMaxCols }} 列
|
||||
</div>
|
||||
<el-scrollbar max-height="520px">
|
||||
<el-scrollbar max-height="420px">
|
||||
<table class="excel-preview-table">
|
||||
<tbody>
|
||||
<tr v-for="(row, rIdx) in excelPreviewRows" :key="rIdx">
|
||||
@@ -183,12 +147,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="attachmentPreviewOpen = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<!-- 发货单据新增弹窗:写入新表 gear_shipping_order(与出入库单据无关) -->
|
||||
<el-dialog title="新增发货单据" v-model="shippingAddOpen" width="900px" append-to-body>
|
||||
@@ -304,12 +263,86 @@
|
||||
<el-table :data="orderDetailList" size="small" border>
|
||||
<el-table-column label="产品编号" prop="productCode" min-width="120" />
|
||||
<el-table-column label="产品名称" prop="productName" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="产品规格" prop="spec" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="产品型号" prop="model" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="预计发出数量" prop="quantity" width="120" align="center" />
|
||||
<el-table-column label="单位" prop="unit" width="80" align="center" />
|
||||
<el-table-column label="产品单价" prop="unitPrice" width="100" align="center" />
|
||||
<el-table-column label="备注" prop="remark" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="产品规格" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row._editSpec"
|
||||
size="small"
|
||||
placeholder="规格"
|
||||
:disabled="!orderDetailEditable || scope.row._saving"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产品型号" min-width="140">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row._editModel"
|
||||
size="small"
|
||||
placeholder="型号"
|
||||
:disabled="!orderDetailEditable || scope.row._saving"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预计发出数量" width="140" align="center">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row._editQuantity"
|
||||
size="small"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
style="width: 120px;"
|
||||
:disabled="!orderDetailEditable || scope.row._saving"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" width="110" align="center">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row._editUnit"
|
||||
size="small"
|
||||
placeholder="单位"
|
||||
:disabled="!orderDetailEditable || scope.row._saving"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产品单价" width="140" align="center">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row._editUnitPrice"
|
||||
size="small"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="4"
|
||||
style="width: 120px;"
|
||||
:disabled="!orderDetailEditable || scope.row._saving"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="160">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row._editRemark"
|
||||
size="small"
|
||||
placeholder="备注"
|
||||
:disabled="!orderDetailEditable || scope.row._saving"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="scope.row._saving"
|
||||
:disabled="!orderDetailEditable || !isOrderDetailRowChanged(scope.row)"
|
||||
@click="saveOrderDetailRow(scope.row)"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
@@ -394,6 +427,7 @@
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!--
|
||||
<el-tab-pane label="生产成果" name="production">
|
||||
<div class="mt-4" v-loading="productionLoading">
|
||||
<div class="production-toolbar">
|
||||
@@ -458,6 +492,7 @@
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
-->
|
||||
|
||||
<el-tab-pane label="发货" name="shippingAlloc">
|
||||
<div class="mt-4">
|
||||
@@ -650,7 +685,7 @@ import ReturnExchange from './return.vue';
|
||||
import Receive from './receive.vue';
|
||||
import { listSalesman } from "@/api/oms/salesman";
|
||||
import { listShippingOrder, addShippingOrder, delShippingOrder } from "@/api/oms/shippingOrder";
|
||||
import { listOrderDetail, addOrderDetail } from "@/api/oms/orderDetail";
|
||||
import { listOrderDetail, addOrderDetail, updateOrderDetail } from "@/api/oms/orderDetail";
|
||||
import { listProductBase } from "@/api/mat/product";
|
||||
import { sumStockQuantityByItemIds } from "@/api/wms/stock";
|
||||
import { listReceivable } from "@/api/finance/receivable";
|
||||
@@ -709,6 +744,11 @@ export default {
|
||||
const list = Array.isArray(this.productionList) ? this.productionList : [];
|
||||
if (!this.productionOnlyProduct) return list;
|
||||
return list.filter(r => String(r && r.productType || "") === "product");
|
||||
},
|
||||
orderDetailEditable() {
|
||||
const s = this.form && this.form.orderStatus != null ? Number(this.form.orderStatus) : NaN;
|
||||
if (Number.isNaN(s)) return true;
|
||||
return s !== EOrderStatus.CANCEL;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -748,8 +788,6 @@ export default {
|
||||
receivedAmount: 0,
|
||||
unreceivedAmount: 0
|
||||
},
|
||||
// 附件预览弹窗:点击“预览”按钮后打开
|
||||
attachmentPreviewOpen: false,
|
||||
attachmentPreviewLoading: false,
|
||||
attachmentFiles: [],
|
||||
currentAttachmentOssId: null,
|
||||
@@ -788,6 +826,8 @@ export default {
|
||||
quantity: 1,
|
||||
unit: "",
|
||||
unitPrice: "",
|
||||
spec: "",
|
||||
model: "",
|
||||
remark: ""
|
||||
},
|
||||
productionLoading: false,
|
||||
@@ -985,9 +1025,9 @@ export default {
|
||||
if (this.activeTab === "logs") {
|
||||
this.loadOperLogs();
|
||||
}
|
||||
if (this.activeTab === "production") {
|
||||
this.loadProduction(item.orderId);
|
||||
}
|
||||
// if (this.activeTab === "production") {
|
||||
// this.loadProduction(item.orderId);
|
||||
// }
|
||||
},
|
||||
|
||||
/** 加载订单详情 */
|
||||
@@ -1018,9 +1058,9 @@ export default {
|
||||
if (this.activeTab === "logs") {
|
||||
this.loadOperLogs();
|
||||
}
|
||||
if (this.activeTab === "production") {
|
||||
this.loadProduction(orderId);
|
||||
}
|
||||
// if (this.activeTab === "production") {
|
||||
// this.loadProduction(orderId);
|
||||
// }
|
||||
}).catch(() => {
|
||||
this.$modal.msgError("加载订单详情失败");
|
||||
}).finally(() => {
|
||||
@@ -1042,12 +1082,18 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
/** 点击“预览”按钮:打开附件预览弹窗 */
|
||||
openAttachmentPreview() {
|
||||
this.attachmentPreviewOpen = true;
|
||||
if (!this.attachmentFiles.length) {
|
||||
clearAttachment() {
|
||||
if (!this.form || !this.form.orderId) return;
|
||||
updateOrder({
|
||||
orderId: this.form.orderId,
|
||||
contractExcelOssIds: ""
|
||||
}).then(() => {
|
||||
this.form.contractExcelOssIds = "";
|
||||
this.$modal.msgSuccess("附件已删除");
|
||||
this.loadAttachmentFiles();
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$modal.msgError("附件删除失败");
|
||||
});
|
||||
},
|
||||
|
||||
/** 加载附件列表(ossId串 -> 文件列表) */
|
||||
@@ -1060,18 +1106,28 @@ export default {
|
||||
this.currentAttachmentExt = "";
|
||||
return;
|
||||
}
|
||||
const firstId = raw.split(",").map(s => String(s).trim()).filter(Boolean)[0] || "";
|
||||
if (firstId && firstId !== raw) {
|
||||
this.form.contractExcelOssIds = firstId;
|
||||
}
|
||||
this.attachmentPreviewLoading = true;
|
||||
listByIds(raw).then(res => {
|
||||
listByIds(firstId || raw).then(res => {
|
||||
const list = (res && res.data) ? res.data : [];
|
||||
this.attachmentFiles = list.map((oss, idx) => {
|
||||
const name = oss.originalName || oss.fileName || String(oss.ossId);
|
||||
const url = oss.url;
|
||||
return { name, url, ossId: oss.ossId, uid: oss.ossId || (Date.now() + idx) };
|
||||
});
|
||||
// 如果当前没有选中附件,或者选中的附件已经不在列表里(例如切换订单/删除/重新上传),默认选中第一条
|
||||
// 如果当前没有选中附件,或者选中的附件已经不在列表里(例如切换订单/删除/重新上传),默认选中一条(优先 Excel)
|
||||
const hasCurrent = this.attachmentFiles.some(f => f.url === this.currentAttachmentUrl);
|
||||
if (this.attachmentFiles.length > 0 && (!this.currentAttachmentUrl || !hasCurrent)) {
|
||||
this.selectAttachment(this.attachmentFiles[0]);
|
||||
const prefer = this.attachmentFiles.find(f => {
|
||||
const name = String(f && f.name ? f.name : "");
|
||||
const ext = name.split(".").pop();
|
||||
const e = ext ? String(ext).toLowerCase() : "";
|
||||
return e === "xls" || e === "xlsx";
|
||||
});
|
||||
this.selectAttachment(prefer || this.attachmentFiles[0]);
|
||||
}
|
||||
}).catch(() => {
|
||||
this.attachmentFiles = [];
|
||||
@@ -1282,9 +1338,9 @@ export default {
|
||||
if (name === "logs") {
|
||||
this.loadOperLogs();
|
||||
}
|
||||
if (name === "production") {
|
||||
this.loadProduction(this.selectedOrderId);
|
||||
}
|
||||
// if (name === "production") {
|
||||
// this.loadProduction(this.selectedOrderId);
|
||||
// }
|
||||
},
|
||||
|
||||
loadProduction(orderId) {
|
||||
@@ -1468,7 +1524,22 @@ export default {
|
||||
return listOrderDetail({ orderId: orderId, pageNum: 1, pageSize: 9999 }).then(res => {
|
||||
const rows = res.rows || [];
|
||||
// 订单管理页的“发货/订单明细”口径:只展示成品(product),避免半成品/原料进入发货视图
|
||||
this.orderDetailList = rows.filter(r => String(r.productType || "").toLowerCase() === "product");
|
||||
this.orderDetailList = rows
|
||||
.filter(r => String(r.productType || "").toLowerCase() === "product")
|
||||
.map(r => {
|
||||
const q = r && r.quantity != null ? Number(r.quantity) : 0;
|
||||
const unitPrice = r && r.taxPrice != null ? Number(r.taxPrice) : (r && r.unitPrice != null ? Number(r.unitPrice) : 0);
|
||||
return {
|
||||
...r,
|
||||
_editSpec: r && r.spec != null ? String(r.spec) : "",
|
||||
_editModel: r && r.model != null ? String(r.model) : "",
|
||||
_editQuantity: Number.isFinite(q) ? q : 0,
|
||||
_editUnit: r && r.unit != null ? String(r.unit) : "",
|
||||
_editUnitPrice: Number.isFinite(unitPrice) ? unitPrice : 0,
|
||||
_editRemark: r && r.remark != null ? String(r.remark) : "",
|
||||
_saving: false
|
||||
};
|
||||
});
|
||||
return this.orderDetailList;
|
||||
}).catch(() => {
|
||||
this.orderDetailList = [];
|
||||
@@ -1608,6 +1679,8 @@ export default {
|
||||
quantity: 1,
|
||||
unit: "",
|
||||
unitPrice: "",
|
||||
spec: "",
|
||||
model: "",
|
||||
remark: ""
|
||||
};
|
||||
this.orderDetailAddOpen = true;
|
||||
@@ -1630,6 +1703,8 @@ export default {
|
||||
if (!hit) return;
|
||||
this.orderDetailAddForm.unit = hit.unit || this.orderDetailAddForm.unit;
|
||||
this.orderDetailAddForm.unitPrice = hit.unitPrice != null ? String(hit.unitPrice) : this.orderDetailAddForm.unitPrice;
|
||||
this.orderDetailAddForm.spec = hit.spec != null ? String(hit.spec) : this.orderDetailAddForm.spec;
|
||||
this.orderDetailAddForm.model = hit.model != null ? String(hit.model) : this.orderDetailAddForm.model;
|
||||
},
|
||||
|
||||
/** 订单明细:提交新增(写入 gear_order_detail) */
|
||||
@@ -1644,12 +1719,18 @@ export default {
|
||||
return;
|
||||
}
|
||||
this.orderDetailAddLoading = true;
|
||||
const priceNum = this.orderDetailAddForm.unitPrice != null && String(this.orderDetailAddForm.unitPrice).trim() !== ""
|
||||
? Number(this.orderDetailAddForm.unitPrice)
|
||||
: null;
|
||||
addOrderDetail({
|
||||
orderId: this.selectedOrderId,
|
||||
productId: this.orderDetailAddForm.productId,
|
||||
quantity: this.orderDetailAddForm.quantity,
|
||||
unit: this.orderDetailAddForm.unit,
|
||||
remark: this.orderDetailAddForm.remark
|
||||
remark: this.orderDetailAddForm.remark,
|
||||
spec: this.orderDetailAddForm.spec,
|
||||
model: this.orderDetailAddForm.model,
|
||||
taxPrice: priceNum != null && Number.isFinite(priceNum) ? priceNum : undefined
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.orderDetailAddOpen = false;
|
||||
@@ -1659,6 +1740,46 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
isOrderDetailRowChanged(row) {
|
||||
if (!row) return false;
|
||||
const q0 = row.quantity != null ? Number(row.quantity) : 0;
|
||||
const q1 = row._editQuantity != null ? Number(row._editQuantity) : 0;
|
||||
const u0 = row.unit != null ? String(row.unit) : "";
|
||||
const u1 = row._editUnit != null ? String(row._editUnit) : "";
|
||||
const r0 = row.remark != null ? String(row.remark) : "";
|
||||
const r1 = row._editRemark != null ? String(row._editRemark) : "";
|
||||
const s0 = row.spec != null ? String(row.spec) : "";
|
||||
const s1 = row._editSpec != null ? String(row._editSpec) : "";
|
||||
const m0 = row.model != null ? String(row.model) : "";
|
||||
const m1 = row._editModel != null ? String(row._editModel) : "";
|
||||
const p0 = row.taxPrice != null ? Number(row.taxPrice) : (row.unitPrice != null ? Number(row.unitPrice) : 0);
|
||||
const p1 = row._editUnitPrice != null ? Number(row._editUnitPrice) : 0;
|
||||
const priceChanged = (Number.isFinite(p0) ? p0 : 0) !== (Number.isFinite(p1) ? p1 : 0);
|
||||
return q0 !== q1 || u0 !== u1 || r0 !== r1 || s0 !== s1 || m0 !== m1 || priceChanged;
|
||||
},
|
||||
|
||||
saveOrderDetailRow(row) {
|
||||
if (!row || !row.detailId) return;
|
||||
if (!this.isOrderDetailRowChanged(row)) return;
|
||||
row._saving = true;
|
||||
const payload = {
|
||||
detailId: row.detailId,
|
||||
orderId: row.orderId,
|
||||
quantity: row._editQuantity != null ? Number(row._editQuantity) : 0,
|
||||
unit: row._editUnit != null ? String(row._editUnit) : "",
|
||||
remark: row._editRemark != null ? String(row._editRemark) : "",
|
||||
spec: row._editSpec != null ? String(row._editSpec) : "",
|
||||
model: row._editModel != null ? String(row._editModel) : "",
|
||||
taxPrice: row._editUnitPrice != null ? Number(row._editUnitPrice) : 0
|
||||
};
|
||||
updateOrderDetail(payload).then(() => {
|
||||
this.$modal.msgSuccess("已保存");
|
||||
this.loadShippingAllocData(this.selectedOrderId);
|
||||
}).finally(() => {
|
||||
row._saving = false;
|
||||
});
|
||||
},
|
||||
|
||||
// 发货单据:打开新增弹窗(默认带入订单信息,由后端生成发货单号)
|
||||
openShippingAdd() {
|
||||
if (!this.selectedOrderId) return;
|
||||
@@ -1919,6 +2040,10 @@ export default {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.order-left-card__status-spacer {
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.order-left-card__status :deep(.el-tag) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
@@ -2090,8 +2215,7 @@ export default {
|
||||
}
|
||||
|
||||
.top-attachment-actions :deep(.upload-file-list) {
|
||||
/* 显示上传后的文件列表(用户能直观看到“已上传了哪些文件”) */
|
||||
display: block;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.top-attachment-actions :deep(.upload-file-list .el-upload-list__item) {
|
||||
@@ -2102,6 +2226,10 @@ export default {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.top-attachment-single-viewer {
|
||||
height: 420px;
|
||||
}
|
||||
|
||||
.top-attachment-summary {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
@@ -65,9 +65,12 @@
|
||||
<el-tabs v-model="activeTab" class="right-tabs" @tab-change="handleTabChange">
|
||||
<el-tab-pane label="跟进客户" name="followCustomer" />
|
||||
<el-tab-pane label="跟进合同" name="followContract" />
|
||||
<el-tab-pane label="历史订单" name="historyOrders" />
|
||||
<el-tab-pane label="订单异议" name="dispute" />
|
||||
<el-tab-pane label="财务状态" name="finance" />
|
||||
<el-tab-pane label="发货单据" name="shippingDocs" />
|
||||
<el-tab-pane label="生产成果" name="production" />
|
||||
<el-tab-pane label="计划发货" name="planShipping" />
|
||||
<el-tab-pane label="销售信息编辑" name="salesmanInfo" />
|
||||
</el-tabs>
|
||||
|
||||
<div class="right-body">
|
||||
@@ -120,6 +123,115 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'historyOrders'">
|
||||
<div class="orders-section">
|
||||
<div class="finance-title-row">
|
||||
<div class="finance-title">历史订单</div>
|
||||
<div class="history-actions">
|
||||
<el-input
|
||||
v-model="historyQuery.orderCode"
|
||||
size="small"
|
||||
clearable
|
||||
placeholder="订单编号"
|
||||
style="width: 180px"
|
||||
@keyup.enter="loadHistoryOrders"
|
||||
/>
|
||||
<el-button size="small" plain icon="Refresh" :loading="historyLoading" @click="loadHistoryOrders">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table v-loading="historyLoading" :data="historyOrderList" border stripe :header-cell-style="{ background: '#f5f7fa' }">
|
||||
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
|
||||
<el-table-column label="客户" prop="customerName" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="状态" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="orderStatusTagType(scope.row.orderStatus)" size="small">
|
||||
{{ orderStatusLabel(scope.row.orderStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="createTime" width="180" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'dispute'">
|
||||
<div class="orders-section">
|
||||
<div class="finance-title-row">
|
||||
<div class="finance-title">订单异议</div>
|
||||
<el-button size="small" plain icon="Refresh" :loading="disputeLoading" @click="loadDisputeOrders">刷新</el-button>
|
||||
</div>
|
||||
<el-table v-loading="disputeLoading" :data="disputeOrderList" border stripe :header-cell-style="{ background: '#f5f7fa' }">
|
||||
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
|
||||
<el-table-column label="客户" prop="customerName" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="状态" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="orderStatusTagType(scope.row.orderStatus)" size="small">
|
||||
{{ orderStatusLabel(scope.row.orderStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="createTime" width="180" />
|
||||
<el-table-column label="操作" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="View" @click="openDispute(scope.row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog title="订单异议" v-model="disputeOpen" width="1200px" append-to-body>
|
||||
<el-empty v-if="!disputeOrderId" description="请选择订单" />
|
||||
<ReturnExchange v-else :orderId="disputeOrderId" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="disputeOpen = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'finance'">
|
||||
<div class="orders-section">
|
||||
<div class="finance-title-row">
|
||||
<div class="finance-title">财务状态</div>
|
||||
<el-button size="small" plain icon="Refresh" :loading="financeLoading" @click="loadFinance">刷新</el-button>
|
||||
</div>
|
||||
|
||||
<div class="finance-strip" v-loading="financeLoading">
|
||||
<div class="finance-item">
|
||||
<div class="finance-item__label">应收总金额</div>
|
||||
<div class="finance-item__value">{{ formatMoney(financeSummary.receivableAmount) }}</div>
|
||||
</div>
|
||||
<div class="finance-item">
|
||||
<div class="finance-item__label">已收款金额</div>
|
||||
<div class="finance-item__value">{{ formatMoney(financeSummary.receivedAmount) }}</div>
|
||||
</div>
|
||||
<div class="finance-item">
|
||||
<div class="finance-item__label">未收款金额</div>
|
||||
<div class="finance-item__value">{{ formatMoney(financeSummary.unreceivedAmount) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="finance-subtitle">收款明细</div>
|
||||
<el-table v-loading="financeLoading" :data="financeList" border stripe :header-cell-style="{ background: '#f5f7fa' }">
|
||||
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
|
||||
<el-table-column label="客户" prop="customerName" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="到期日" prop="dueDate" width="180" />
|
||||
<el-table-column label="应收" prop="amount" width="140" align="right">
|
||||
<template #default="scope">{{ formatMoney(scope.row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已收" prop="paidAmount" width="140" align="right">
|
||||
<template #default="scope">{{ formatMoney(scope.row.paidAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="未收" prop="balanceAmount" width="140" align="right">
|
||||
<template #default="scope">{{ formatMoney(scope.row.balanceAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="status" width="120" />
|
||||
<el-table-column label="备注" prop="remark" min-width="180" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'shippingDocs'">
|
||||
<el-table v-loading="shippingLoading" :data="shippingList">
|
||||
<el-table-column label="发货单号" prop="shippingNo" min-width="180" />
|
||||
@@ -132,29 +244,6 @@
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'production'">
|
||||
<div class="production-wrap">
|
||||
<div class="production-summary">
|
||||
<div class="production-summary__item">订单数:{{ productionSummary.orderCount }}</div>
|
||||
<div class="production-summary__item">计划总量:{{ formatQty(productionSummary.planQty) }}</div>
|
||||
<div class="production-summary__item">已完成总量:{{ formatQty(productionSummary.finishedQty) }}</div>
|
||||
<div class="production-summary__item">完成率:{{ productionSummary.rateText }}</div>
|
||||
</div>
|
||||
<el-table v-loading="productionLoading" :data="productionOrderSummaryList" size="small" border>
|
||||
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
|
||||
<el-table-column label="客户" prop="customerName" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="计划" prop="planQty" width="120" align="center" />
|
||||
<el-table-column label="完成" prop="finishedQty" width="120" align="center" />
|
||||
<el-table-column label="完成率" width="120" align="center">
|
||||
<template #default="scope">
|
||||
{{ formatRate(scope.row.finishedQty, scope.row.planQty) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最近更新" prop="lastUpdateTime" min-width="160" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'planShipping'">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
@@ -212,6 +301,30 @@
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'salesmanInfo'">
|
||||
<el-form ref="salesmanInfoRef" :model="salesmanInfoForm" label-width="90px" class="salesman-info-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="salesmanInfoForm.name" placeholder="请输入销售员姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机">
|
||||
<el-input v-model="salesmanInfoForm.mobile" placeholder="请输入联系电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="salesmanInfoForm.status">
|
||||
<el-radio :value="0">正常</el-radio>
|
||||
<el-radio :value="1">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="salesmanInfoForm.remark" type="textarea" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="salesmanInfoSaving" @click="saveSalesmanInfo">保存</el-button>
|
||||
<el-button @click="resetSalesmanInfo">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -252,16 +365,19 @@ import { listSalesman, addSalesman, updateSalesman, delSalesman, listSalesmanCus
|
||||
import { listOrder } from "@/api/oms/order";
|
||||
import { listShippingOrder } from "@/api/oms/shippingOrder";
|
||||
import { listOrderDetail } from "@/api/oms/orderDetail";
|
||||
import { listOrderProduction } from "@/api/oms/orderProduction";
|
||||
import { listReceivable } from "@/api/finance/receivable";
|
||||
import ReturnExchange from "@/views/oms/order/panels/return.vue";
|
||||
|
||||
export default {
|
||||
name: "Salesman",
|
||||
components: { ReturnExchange },
|
||||
data() {
|
||||
return {
|
||||
leftLoading: false,
|
||||
salesmanList: [],
|
||||
selectedSalesmanId: undefined,
|
||||
selectedSalesmanName: "",
|
||||
selectedSalesman: {},
|
||||
activeTab: "followCustomer",
|
||||
|
||||
// 左侧查询条件(用于筛选销售员列表)
|
||||
@@ -290,18 +406,38 @@ export default {
|
||||
pageSize: 20
|
||||
},
|
||||
|
||||
historyLoading: false,
|
||||
historyOrderList: [],
|
||||
historyQuery: {
|
||||
orderCode: ""
|
||||
},
|
||||
|
||||
// 发货单据
|
||||
shippingLoading: false,
|
||||
shippingList: [],
|
||||
|
||||
// 生产成果:按订单汇总(只读)
|
||||
productionLoading: false,
|
||||
productionOrderSummaryList: [],
|
||||
financeLoading: false,
|
||||
financeList: [],
|
||||
financeSummary: {
|
||||
receivableAmount: 0,
|
||||
receivedAmount: 0,
|
||||
unreceivedAmount: 0
|
||||
},
|
||||
financeOrderCodeMap: {},
|
||||
|
||||
disputeLoading: false,
|
||||
disputeOrderList: [],
|
||||
disputeOpen: false,
|
||||
disputeOrderId: null,
|
||||
|
||||
// 计划发货:产品明细(按销售员全部订单汇总)
|
||||
planDetailLoading: false,
|
||||
planOrderDetailList: [],
|
||||
|
||||
salesmanInfoForm: {},
|
||||
salesmanInfoFormOrigin: {},
|
||||
salesmanInfoSaving: false,
|
||||
|
||||
// 新增/修改弹窗
|
||||
dialogOpen: false,
|
||||
dialogTitle: "",
|
||||
@@ -326,14 +462,6 @@ export default {
|
||||
shippedOrders
|
||||
};
|
||||
},
|
||||
productionSummary() {
|
||||
const list = Array.isArray(this.productionOrderSummaryList) ? this.productionOrderSummaryList : [];
|
||||
const orderCount = list.length;
|
||||
const planQty = list.reduce((sum, r) => sum + Number(r && r.planQty != null ? r.planQty : 0), 0);
|
||||
const finishedQty = list.reduce((sum, r) => sum + Number(r && r.finishedQty != null ? r.finishedQty : 0), 0);
|
||||
const rateText = planQty > 0 ? `${((finishedQty / planQty) * 100).toFixed(1)}%` : "0%";
|
||||
return { orderCount, planQty, finishedQty, rateText };
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getSalesmanList();
|
||||
@@ -373,18 +501,26 @@ export default {
|
||||
if (!item || !item.salesmanId) return;
|
||||
this.selectedSalesmanId = item.salesmanId;
|
||||
this.selectedSalesmanName = item.name;
|
||||
this.selectedSalesman = { ...item };
|
||||
this.initSalesmanInfoForm(item);
|
||||
this.customerQuery.pageNum = 1;
|
||||
this.orderQuery.pageNum = 1;
|
||||
this.loadCustomers();
|
||||
if (this.activeTab === "followContract") {
|
||||
this.loadOrders();
|
||||
}
|
||||
if (this.activeTab === "historyOrders") {
|
||||
this.loadHistoryOrders();
|
||||
}
|
||||
if (this.activeTab === "dispute") {
|
||||
this.loadDisputeOrders();
|
||||
}
|
||||
if (this.activeTab === "finance") {
|
||||
this.loadFinance();
|
||||
}
|
||||
if (this.activeTab === "shippingDocs") {
|
||||
this.loadShippingOrders();
|
||||
}
|
||||
if (this.activeTab === "production") {
|
||||
this.loadProductionSummary();
|
||||
}
|
||||
if (this.activeTab === "planShipping") {
|
||||
this.loadPlanShipping();
|
||||
}
|
||||
@@ -399,15 +535,24 @@ export default {
|
||||
if (this.activeTab === "followContract") {
|
||||
this.loadOrders();
|
||||
}
|
||||
if (this.activeTab === "historyOrders") {
|
||||
this.loadHistoryOrders();
|
||||
}
|
||||
if (this.activeTab === "dispute") {
|
||||
this.loadDisputeOrders();
|
||||
}
|
||||
if (this.activeTab === "finance") {
|
||||
this.loadFinance();
|
||||
}
|
||||
if (this.activeTab === "shippingDocs") {
|
||||
this.loadShippingOrders();
|
||||
}
|
||||
if (this.activeTab === "production") {
|
||||
this.loadProductionSummary();
|
||||
}
|
||||
if (this.activeTab === "planShipping") {
|
||||
this.loadPlanShipping();
|
||||
}
|
||||
if (this.activeTab === "salesmanInfo") {
|
||||
this.initSalesmanInfoForm(this.selectedSalesman);
|
||||
}
|
||||
},
|
||||
|
||||
// 跟进客户:后端反查客户
|
||||
@@ -441,6 +586,53 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
loadHistoryOrders() {
|
||||
if (!this.selectedSalesmanId) return;
|
||||
this.historyLoading = true;
|
||||
listOrder({
|
||||
salesmanId: this.selectedSalesmanId,
|
||||
orderCode: this.historyQuery.orderCode || undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 9999
|
||||
})
|
||||
.then(res => {
|
||||
const list = (res && res.rows) ? res.rows : [];
|
||||
this.historyOrderList = list.filter(o => this.isHistoryOrder(o));
|
||||
})
|
||||
.finally(() => {
|
||||
this.historyLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
isHistoryOrder(order) {
|
||||
const s = order && order.orderStatus != null ? order.orderStatus : null;
|
||||
const n = Number(s);
|
||||
if (Number.isFinite(n)) return n === 2 || n === 3;
|
||||
const t = String(s || "");
|
||||
return t.includes("完成") || t.includes("取消");
|
||||
},
|
||||
|
||||
orderStatusLabel(status) {
|
||||
const n = Number(status);
|
||||
if (Number.isFinite(n)) {
|
||||
if (n === 0) return "预订单";
|
||||
if (n === 1) return "进行中";
|
||||
if (n === 2) return "已完成";
|
||||
if (n === 3) return "已取消";
|
||||
}
|
||||
return String(status || "-");
|
||||
},
|
||||
|
||||
orderStatusTagType(status) {
|
||||
const n = Number(status);
|
||||
if (Number.isFinite(n)) {
|
||||
if (n === 2) return "success";
|
||||
if (n === 3) return "info";
|
||||
if (n === 1) return "warning";
|
||||
}
|
||||
return "info";
|
||||
},
|
||||
|
||||
loadShippingOrders() {
|
||||
if (!this.selectedSalesmanId) return;
|
||||
this.shippingLoading = true;
|
||||
@@ -453,40 +645,71 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
async loadProductionSummary() {
|
||||
loadDisputeOrders() {
|
||||
if (!this.selectedSalesmanId) return;
|
||||
this.productionLoading = true;
|
||||
try {
|
||||
const orderRes = await listOrder({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 });
|
||||
const orders = (orderRes && orderRes.rows) ? orderRes.rows : [];
|
||||
const promises = orders
|
||||
.filter(o => o && o.orderId != null)
|
||||
.map(async (o) => {
|
||||
const prodRes = await listOrderProduction({ orderId: o.orderId, pageNum: 1, pageSize: 9999 });
|
||||
const rows = (prodRes && prodRes.rows) ? prodRes.rows : [];
|
||||
const planQty = rows.reduce((sum, r) => sum + Number(r && r.planQty != null ? r.planQty : 0), 0);
|
||||
const finishedQty = rows.reduce((sum, r) => sum + Number(r && r.finishedQty != null ? r.finishedQty : 0), 0);
|
||||
let lastUpdateTime = "";
|
||||
rows.forEach(r => {
|
||||
const t = r && r.updateTime ? String(r.updateTime) : "";
|
||||
if (t && (!lastUpdateTime || new Date(t).getTime() > new Date(lastUpdateTime).getTime())) {
|
||||
lastUpdateTime = t;
|
||||
}
|
||||
});
|
||||
this.disputeLoading = true;
|
||||
listOrder({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 })
|
||||
.then(res => {
|
||||
this.disputeOrderList = (res && res.rows) ? res.rows : [];
|
||||
})
|
||||
.finally(() => {
|
||||
this.disputeLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
openDispute(row) {
|
||||
if (!row || !row.orderId) return;
|
||||
this.disputeOrderId = row.orderId;
|
||||
this.disputeOpen = true;
|
||||
},
|
||||
|
||||
formatMoney(v) {
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) ? n.toFixed(2) : "0.00";
|
||||
},
|
||||
|
||||
loadFinance() {
|
||||
if (!this.selectedSalesmanId) return;
|
||||
this.financeLoading = true;
|
||||
Promise.all([
|
||||
listReceivable({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 }),
|
||||
listOrder({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 })
|
||||
])
|
||||
.then(([recRes, orderRes]) => {
|
||||
const receivables = (recRes && recRes.rows) ? recRes.rows : [];
|
||||
const orders = (orderRes && orderRes.rows) ? orderRes.rows : [];
|
||||
|
||||
const codeMap = {};
|
||||
orders.forEach(o => {
|
||||
if (!o || o.orderId == null) return;
|
||||
codeMap[o.orderId] = o.orderCode || String(o.orderId);
|
||||
});
|
||||
this.financeOrderCodeMap = codeMap;
|
||||
|
||||
const rows = receivables.map(r => {
|
||||
const orderId = r && r.orderId != null ? r.orderId : null;
|
||||
return {
|
||||
orderId: o.orderId,
|
||||
orderCode: o.orderCode,
|
||||
customerName: o.customerName,
|
||||
planQty,
|
||||
finishedQty,
|
||||
lastUpdateTime
|
||||
...r,
|
||||
orderCode: orderId != null ? (codeMap[orderId] || String(orderId)) : "-"
|
||||
};
|
||||
});
|
||||
this.financeList = rows;
|
||||
|
||||
this.productionOrderSummaryList = await Promise.all(promises);
|
||||
} finally {
|
||||
this.productionLoading = false;
|
||||
}
|
||||
const receivableAmount = rows.reduce((sum, r) => sum + (Number(r && r.amount != null ? r.amount : 0) || 0), 0);
|
||||
const receivedAmount = rows.reduce((sum, r) => sum + (Number(r && r.paidAmount != null ? r.paidAmount : 0) || 0), 0);
|
||||
const unreceivedAmount = rows.reduce((sum, r) => {
|
||||
const bal = r && r.balanceAmount != null ? Number(r.balanceAmount) : NaN;
|
||||
if (Number.isFinite(bal)) return sum + bal;
|
||||
const a = Number(r && r.amount != null ? r.amount : 0) || 0;
|
||||
const p = Number(r && r.paidAmount != null ? r.paidAmount : 0) || 0;
|
||||
return sum + Math.max(0, a - p);
|
||||
}, 0);
|
||||
|
||||
this.financeSummary = { receivableAmount, receivedAmount, unreceivedAmount };
|
||||
})
|
||||
.finally(() => {
|
||||
this.financeLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
loadPlanShipping() {
|
||||
@@ -564,6 +787,42 @@ export default {
|
||||
return `${((fa / fb) * 100).toFixed(1)}%`;
|
||||
},
|
||||
|
||||
initSalesmanInfoForm(item) {
|
||||
const row = item && item.salesmanId ? item : null;
|
||||
if (!row) {
|
||||
this.salesmanInfoForm = {};
|
||||
this.salesmanInfoFormOrigin = {};
|
||||
return;
|
||||
}
|
||||
const form = {
|
||||
salesmanId: row.salesmanId,
|
||||
name: row.name || "",
|
||||
mobile: row.mobile || "",
|
||||
status: row.status != null ? row.status : 0,
|
||||
remark: row.remark || ""
|
||||
};
|
||||
this.salesmanInfoForm = { ...form };
|
||||
this.salesmanInfoFormOrigin = { ...form };
|
||||
},
|
||||
|
||||
resetSalesmanInfo() {
|
||||
this.salesmanInfoForm = { ...this.salesmanInfoFormOrigin };
|
||||
},
|
||||
|
||||
saveSalesmanInfo() {
|
||||
if (!this.salesmanInfoForm || !this.salesmanInfoForm.salesmanId) return;
|
||||
this.salesmanInfoSaving = true;
|
||||
updateSalesman(this.salesmanInfoForm)
|
||||
.then(() => {
|
||||
this.$modal.msgSuccess("保存成功");
|
||||
this.salesmanInfoFormOrigin = { ...this.salesmanInfoForm };
|
||||
this.getSalesmanList();
|
||||
})
|
||||
.finally(() => {
|
||||
this.salesmanInfoSaving = false;
|
||||
});
|
||||
},
|
||||
|
||||
// 新增
|
||||
handleAdd() {
|
||||
this.dialogTitle = "新增销售员";
|
||||
@@ -758,4 +1017,63 @@ export default {
|
||||
.production-summary__item {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.orders-section {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.finance-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.history-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.salesman-info-form {
|
||||
max-width: 680px;
|
||||
}
|
||||
|
||||
.finance-title {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.finance-strip {
|
||||
display: flex;
|
||||
gap: 18px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 10px;
|
||||
background: #f5f7fa;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.finance-item {
|
||||
flex: 1;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.finance-item__label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.finance-item__value {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.finance-subtitle {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -141,8 +141,13 @@
|
||||
</el-descriptions>
|
||||
|
||||
<div class="shipping-detail-products" v-loading="detailProductsLoading">
|
||||
<!-- 发货单绑定订单后:展示该订单的订单明细(仅成品),用于发货与打印 -->
|
||||
<div class="shipping-detail-products__title">订单明细(成品)</div>
|
||||
<div class="shipping-detail-products__title-row">
|
||||
<div class="shipping-detail-products__title">发货单据明细</div>
|
||||
<div class="shipping-detail-products__ops">
|
||||
<el-button size="small" type="primary" plain icon="Plus" @click="handleDetailAdd">新增产品</el-button>
|
||||
<el-button size="small" plain icon="Refresh" @click="reloadCurrentDetail">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="detailProducts" size="small" border>
|
||||
<el-table-column label="产品编号" prop="productCode" min-width="120" />
|
||||
<el-table-column label="产品名称" prop="productName" min-width="160" show-overflow-tooltip />
|
||||
@@ -151,6 +156,12 @@
|
||||
<el-table-column label="数量" prop="quantity" width="100" align="center" />
|
||||
<el-table-column label="单位" prop="unit" width="90" align="center" />
|
||||
<el-table-column label="备注" prop="remark" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="140" align="center">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" size="small" @click="handleDetailEdit(scope.row)">修改</el-button>
|
||||
<el-button link type="danger" size="small" @click="handleDetailDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="shipping-detail-products__extra" v-if="detailProducts && detailProducts.length">
|
||||
@@ -265,9 +276,15 @@
|
||||
</el-form>
|
||||
<el-table v-loading="orderSelectLoading" :data="orderSelectList" highlight-current-row @row-dblclick="confirmOrderBind">
|
||||
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
|
||||
<el-table-column label="订单ID" prop="orderId" min-width="160" />
|
||||
<el-table-column label="客户ID" prop="customerId" min-width="120" />
|
||||
<el-table-column label="状态" prop="orderStatus" width="100" />
|
||||
<el-table-column label="客户" prop="customerName" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="销售员" prop="salesManager" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="订单状态" width="110" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="orderStatusTagType(scope.row.orderStatus)" effect="dark">
|
||||
{{ orderStatusLabel(scope.row.orderStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="createTime" min-width="160" />
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<template #default="scope">
|
||||
@@ -287,6 +304,50 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :title="detailEditTitle" v-model="detailEditOpen" width="520px" append-to-body>
|
||||
<el-form ref="detailEditFormRef" :model="detailEditForm" :rules="detailEditRules" label-width="90px" v-loading="detailEditLoading">
|
||||
<el-form-item label="产品" prop="productId">
|
||||
<el-select
|
||||
v-model="detailEditForm.productId"
|
||||
placeholder="请选择产品"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="remoteSearchDetailProducts"
|
||||
:loading="detailProductLoading"
|
||||
style="width: 100%"
|
||||
@change="handleDetailProductPicked"
|
||||
>
|
||||
<el-option
|
||||
v-for="p in detailProductOptions"
|
||||
:key="p.productId"
|
||||
:label="detailProductOptionLabel(p)"
|
||||
:value="p.productId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="规格">
|
||||
<el-input v-model="detailEditForm.spec" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="型号">
|
||||
<el-input v-model="detailEditForm.model" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="数量" prop="quantity">
|
||||
<el-input-number v-model="detailEditForm.quantity" style="width: 100%" :controls="false" :min="0" :precision="4" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单位" prop="unit">
|
||||
<el-input v-model="detailEditForm.unit" placeholder="单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="detailEditForm.remark" type="textarea" :rows="2" placeholder="备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="detailEditOpen = false">取消</el-button>
|
||||
<el-button type="primary" :loading="detailEditLoading" @click="submitDetailEdit">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 发货计划 新增/编辑弹窗 -->
|
||||
<el-dialog :title="planTitle" v-model="planOpen" width="520px" append-to-body>
|
||||
<el-form ref="planFormRef" :model="planForm" label-width="90px" v-loading="planSubmitLoading">
|
||||
@@ -403,6 +464,10 @@ import {
|
||||
addShippingOrder,
|
||||
updateShippingOrder,
|
||||
delShippingOrder,
|
||||
listShippingOrderDetail,
|
||||
addShippingOrderDetail,
|
||||
updateShippingOrderDetail,
|
||||
delShippingOrderDetail,
|
||||
listShippingPlanWithCount,
|
||||
addShippingPlan,
|
||||
updateShippingPlan,
|
||||
@@ -412,9 +477,10 @@ import {
|
||||
import { listOrder, getOrder } from '@/api/oms/order'
|
||||
import { getCustomer } from '@/api/oms/customer'
|
||||
import { listSalesman } from '@/api/oms/salesman'
|
||||
import { listOrderDetail } from '@/api/oms/orderDetail'
|
||||
import { getDicts } from '@/api/system/dict/data'
|
||||
import { listProductAdditionByProductIds } from '@/api/mat/product'
|
||||
import { listProductBase } from '@/api/mat/product'
|
||||
import { getProduct as getOaProduct } from '@/api/oa/product'
|
||||
import * as XLSX from 'xlsx'
|
||||
import CustomerSelect from '@/components/CustomerSelect/index.vue'
|
||||
|
||||
@@ -442,10 +508,21 @@ export default {
|
||||
// 右侧当前选中行(下方明细区展示)
|
||||
currentRow: null,
|
||||
currentOrderInfo: null,
|
||||
// 当前发货单绑定订单的订单明细(用于页面展示与打印)
|
||||
// 当前发货单据的明细(用于页面展示与打印)
|
||||
detailProductsLoading: false,
|
||||
detailProducts: [],
|
||||
detailAdditionMap: {},
|
||||
detailEditOpen: false,
|
||||
detailEditTitle: '',
|
||||
detailEditLoading: false,
|
||||
detailEditForm: {},
|
||||
detailEditRules: {
|
||||
productId: [{ required: true, message: '请选择产品', trigger: 'change' }],
|
||||
quantity: [{ required: true, message: '请输入数量', trigger: 'blur' }],
|
||||
unit: [{ required: true, message: '请输入单位', trigger: 'blur' }]
|
||||
},
|
||||
detailProductOptions: [],
|
||||
detailProductLoading: false,
|
||||
salesmanOptions: [],
|
||||
orderCompanyOptions: [],
|
||||
// 完成状态选项(对应 gear_shipping_order.status)
|
||||
@@ -685,24 +762,24 @@ export default {
|
||||
},
|
||||
handleCurrentChange(row) {
|
||||
this.currentRow = row || null
|
||||
// 选择发货单时,加载其绑定订单的订单明细(仅成品)
|
||||
if (this.currentRow && this.currentRow.orderId) {
|
||||
this.loadOrderDetailProducts(this.currentRow.orderId)
|
||||
this.loadOrderInfo(this.currentRow.orderId)
|
||||
if (this.currentRow && this.currentRow.shippingId) {
|
||||
this.loadShippingOrderDetailProducts(this.currentRow.shippingId)
|
||||
} else {
|
||||
this.detailProducts = []
|
||||
this.detailAdditionMap = {}
|
||||
}
|
||||
if (this.currentRow && this.currentRow.orderId) {
|
||||
this.loadOrderInfo(this.currentRow.orderId)
|
||||
} else {
|
||||
this.currentOrderInfo = null
|
||||
}
|
||||
},
|
||||
/** 发货单绑定订单后:获取对应订单的订单明细产品数据(不包含库存,只取订单明细) */
|
||||
loadOrderDetailProducts(orderId) {
|
||||
loadShippingOrderDetailProducts(shippingId) {
|
||||
this.detailProductsLoading = true
|
||||
listOrderDetail({ orderId: orderId, pageNum: 1, pageSize: 9999 })
|
||||
listShippingOrderDetail({ shippingId: shippingId, pageNum: 1, pageSize: 9999 })
|
||||
.then(res => {
|
||||
const rows = (res && res.rows) ? res.rows : []
|
||||
// 仅展示成品(product),符合“只有成品类型才可以进入发货”
|
||||
this.detailProducts = rows.filter(r => String(r.productType || '').toLowerCase() === 'product')
|
||||
this.detailProducts = rows
|
||||
const ids = this.detailProducts.map(r => r.productId).filter(Boolean)
|
||||
this.loadProductAdditions(ids).then(map => {
|
||||
this.detailAdditionMap = map
|
||||
@@ -747,7 +824,118 @@ export default {
|
||||
this.$modal.msgSuccess('状态已更新')
|
||||
})
|
||||
},
|
||||
/** 打印预览:打开弹窗并加载绑定订单的订单明细产品数据 */
|
||||
reloadCurrentDetail() {
|
||||
if (!this.currentRow || !this.currentRow.shippingId) return
|
||||
this.loadShippingOrderDetailProducts(this.currentRow.shippingId)
|
||||
},
|
||||
resetDetailEdit() {
|
||||
const shippingId = this.currentRow && this.currentRow.shippingId ? this.currentRow.shippingId : undefined
|
||||
this.detailEditForm = {
|
||||
detailId: undefined,
|
||||
shippingId,
|
||||
productId: undefined,
|
||||
productCode: '',
|
||||
productName: '',
|
||||
spec: '',
|
||||
model: '',
|
||||
quantity: 0,
|
||||
unit: '',
|
||||
remark: '',
|
||||
sort: 0
|
||||
}
|
||||
this.detailProductOptions = []
|
||||
this.resetForm('detailEditFormRef')
|
||||
},
|
||||
handleDetailAdd() {
|
||||
if (!this.currentRow || !this.currentRow.shippingId) {
|
||||
this.$modal.msgError('请先选择发货单')
|
||||
return
|
||||
}
|
||||
this.resetDetailEdit()
|
||||
this.detailEditTitle = '新增产品'
|
||||
this.detailEditOpen = true
|
||||
},
|
||||
handleDetailEdit(row) {
|
||||
if (!row || !row.detailId) return
|
||||
this.resetDetailEdit()
|
||||
this.detailEditForm = Object.assign(this.detailEditForm, row)
|
||||
this.detailEditTitle = '修改产品'
|
||||
this.detailEditOpen = true
|
||||
if (row.productId) {
|
||||
this.detailProductOptions = [{
|
||||
productId: row.productId,
|
||||
productName: row.productName,
|
||||
spec: row.spec,
|
||||
model: row.model,
|
||||
productType: 'product'
|
||||
}]
|
||||
}
|
||||
},
|
||||
handleDetailDelete(row) {
|
||||
if (!row || !row.detailId) return
|
||||
this.$modal.confirm('是否确认删除该产品行?').then(() => {
|
||||
return delShippingOrderDetail(String(row.detailId))
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
this.reloadCurrentDetail()
|
||||
})
|
||||
},
|
||||
remoteSearchDetailProducts(keyword) {
|
||||
const q = String(keyword || '').trim()
|
||||
this.detailProductLoading = true
|
||||
listProductBase({
|
||||
pageNum: 1,
|
||||
pageSize: 50,
|
||||
productType: 'product',
|
||||
productName: q
|
||||
}).then(res => {
|
||||
this.detailProductOptions = (res && res.rows) ? res.rows : []
|
||||
}).finally(() => {
|
||||
this.detailProductLoading = false
|
||||
})
|
||||
},
|
||||
detailProductOptionLabel(p) {
|
||||
const name = p && p.productName ? p.productName : '-'
|
||||
const parts = [p && p.spec, p && p.model].filter(Boolean)
|
||||
return parts.length ? `${name}(${parts.join(' / ')})` : name
|
||||
},
|
||||
handleDetailProductPicked(productId) {
|
||||
const id = productId
|
||||
const hit = (this.detailProductOptions || []).find(p => String(p.productId) === String(id))
|
||||
if (hit) {
|
||||
this.detailEditForm.productName = hit.productName || this.detailEditForm.productName
|
||||
this.detailEditForm.spec = hit.spec || ''
|
||||
this.detailEditForm.model = hit.model || ''
|
||||
}
|
||||
if (id != null && String(id) !== '') {
|
||||
getOaProduct(id).then(res => {
|
||||
const d = res && res.data ? res.data : null
|
||||
if (!d) return
|
||||
if (d.productCode) this.detailEditForm.productCode = d.productCode
|
||||
if (!this.detailEditForm.unit && d.unit) this.detailEditForm.unit = d.unit
|
||||
}).catch(() => {})
|
||||
}
|
||||
},
|
||||
submitDetailEdit() {
|
||||
if (!this.detailEditForm || !this.detailEditForm.shippingId) {
|
||||
this.$modal.msgError('请先选择发货单')
|
||||
return
|
||||
}
|
||||
this.$refs.detailEditFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.detailEditLoading = true
|
||||
const payload = Object.assign({}, this.detailEditForm)
|
||||
const req = payload.detailId ? updateShippingOrderDetail(payload) : addShippingOrderDetail(payload)
|
||||
req.then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.detailEditOpen = false
|
||||
this.reloadCurrentDetail()
|
||||
}).finally(() => {
|
||||
this.detailEditLoading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
/** 打印预览:打开弹窗并加载发货单据明细产品数据 */
|
||||
printPreview(row) {
|
||||
const target = row || this.currentRow
|
||||
if (!target || !target.orderId) {
|
||||
@@ -759,10 +947,10 @@ export default {
|
||||
this.printLoading = true
|
||||
Promise.all([
|
||||
getOrder(target.orderId).then(res => (res && res.data) ? res.data : null).catch(() => null),
|
||||
listOrderDetail({ orderId: target.orderId, pageNum: 1, pageSize: 9999 }).then(res => (res && res.rows) ? res.rows : []).catch(() => []),
|
||||
listShippingOrderDetail({ shippingId: target.shippingId, pageNum: 1, pageSize: 9999 }).then(res => (res && res.rows) ? res.rows : []).catch(() => []),
|
||||
]).then(([orderInfo, rows]) => {
|
||||
this.printOrderInfo = orderInfo
|
||||
this.printProducts = rows.filter(r => String(r.productType || '').toLowerCase() === 'product')
|
||||
this.printProducts = rows
|
||||
const ids = this.printProducts.map(r => r.productId).filter(Boolean)
|
||||
return this.loadProductAdditions(ids)
|
||||
}).then(map => {
|
||||
@@ -1086,9 +1274,6 @@ export default {
|
||||
}).then(res => {
|
||||
this.orderSelectList = (res && res.rows) ? res.rows : []
|
||||
this.orderSelectTotal = res && res.total ? res.total : 0
|
||||
if (this.orderSelectList.length === 1) {
|
||||
this.confirmOrderBind(this.orderSelectList[0])
|
||||
}
|
||||
}).finally(() => {
|
||||
this.orderSelectLoading = false
|
||||
})
|
||||
@@ -1098,6 +1283,7 @@ export default {
|
||||
this.editForm.orderId = row.orderId
|
||||
this.editForm.orderCode = row.orderCode
|
||||
this.orderSelectOpen = false
|
||||
this.$modal.msgSuccess('订单已绑定')
|
||||
getOrder(row.orderId).then(res => {
|
||||
const order = res && res.data ? res.data : null
|
||||
if (!order) return
|
||||
@@ -1107,9 +1293,23 @@ export default {
|
||||
} else if (order.customerName) {
|
||||
this.editForm.receiverCompany = String(order.customerName)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.$modal.msgSuccess('订单已绑定')
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
orderStatusLabel(val) {
|
||||
const n = val == null || val === '' ? NaN : Number(val)
|
||||
if (n === 0) return '预订单'
|
||||
if (n === 1) return '进行中'
|
||||
if (n === 2) return '已完成'
|
||||
if (n === 3) return '已取消'
|
||||
return '—'
|
||||
},
|
||||
orderStatusTagType(val) {
|
||||
const n = val == null || val === '' ? NaN : Number(val)
|
||||
if (n === 0) return 'info'
|
||||
if (n === 1) return 'warning'
|
||||
if (n === 2) return 'success'
|
||||
if (n === 3) return 'danger'
|
||||
return 'info'
|
||||
},
|
||||
handleReceiverCustomerPicked(customerId) {
|
||||
if (customerId == null || String(customerId) === '') {
|
||||
|
||||
@@ -218,25 +218,30 @@
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="物料出入库统计" v-model="flowOpen" width="980px" top="6vh" append-to-body>
|
||||
<el-form :inline="true" size="small" label-width="90px">
|
||||
<el-form-item label="物料">
|
||||
<RawSelector v-model="flowForm.itemId" :allowAdd="false" @change="onFlowMaterialChange" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时间范围">
|
||||
<el-date-picker
|
||||
v-model="flowForm.timeRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="flowLoading" @click="fetchFlow">查询</el-button>
|
||||
</el-form-item>
|
||||
<el-form :inline="true" size="small" label-width="70px">
|
||||
<div style="display:flex; flex-wrap:wrap; align-items:flex-end; gap:10px 16px;">
|
||||
<el-form-item label="物料" style="margin-bottom: 0;">
|
||||
<RawSelector v-model="flowForm.itemId" :allowAdd="false" @change="onFlowMaterialChange" />
|
||||
</el-form-item>
|
||||
<el-form-item label="厂家" style="margin-bottom: 0;">
|
||||
<el-input v-model="flowForm.factory" placeholder="请输入厂家" clearable :disabled="!!flowForm.itemId" style="width: 220px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时间" style="margin-bottom: 0;">
|
||||
<el-date-picker
|
||||
v-model="flowForm.timeRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-bottom: 0; margin-left: auto;">
|
||||
<el-button type="primary" :loading="flowLoading" @click="fetchFlow">查询</el-button>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<div v-loading="flowLoading" style="margin-top: 10px;">
|
||||
@@ -245,7 +250,8 @@
|
||||
物料出入库统计
|
||||
</div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<span>物料:</span><span>{{ flowItemName || '-' }}</span>
|
||||
<span v-if="flowForm.itemId">物料:</span><span v-if="flowForm.itemId">{{ flowItemName || '-' }}</span>
|
||||
<span v-else>厂家:</span><span v-else>{{ flowForm.factory || '-' }}</span>
|
||||
<span style="margin-left: 18px;">时间:</span>
|
||||
<span>{{ (flowForm.timeRange && flowForm.timeRange[0]) || '-' }}</span>
|
||||
<span> 至 </span>
|
||||
@@ -277,7 +283,7 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" style="margin-bottom: 10px;">
|
||||
<el-row :gutter="10" justify="center" style="margin-bottom: 10px;">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div>净变动</div>
|
||||
@@ -351,6 +357,7 @@ export default {
|
||||
flowLoading: false,
|
||||
flowForm: {
|
||||
itemId: undefined,
|
||||
factory: undefined,
|
||||
timeRange: []
|
||||
},
|
||||
flowItemName: '',
|
||||
@@ -515,23 +522,34 @@ export default {
|
||||
},
|
||||
onFlowMaterialChange(material) {
|
||||
this.flowItemName = material && material.materialName ? material.materialName : ''
|
||||
if (this.flowForm.itemId) {
|
||||
this.flowForm.factory = undefined
|
||||
}
|
||||
},
|
||||
fetchFlow() {
|
||||
if (!this.flowForm.itemId) {
|
||||
this.$modal.msgError('请选择物料')
|
||||
return
|
||||
}
|
||||
if (!this.flowForm.timeRange || this.flowForm.timeRange.length !== 2) {
|
||||
this.$modal.msgError('请选择时间范围')
|
||||
return
|
||||
}
|
||||
const factory = this.flowForm.factory ? String(this.flowForm.factory).trim() : ''
|
||||
const itemId = this.flowForm.itemId
|
||||
if (!itemId && !factory) {
|
||||
this.$modal.msgError('请选择物料或填写厂家')
|
||||
return
|
||||
}
|
||||
this.flowLoading = true
|
||||
this.flowResult = null
|
||||
getMaterialFlow({
|
||||
itemId: this.flowForm.itemId,
|
||||
const params = {
|
||||
startTime: this.flowForm.timeRange[0],
|
||||
endTime: this.flowForm.timeRange[1]
|
||||
})
|
||||
}
|
||||
if (itemId) {
|
||||
params.itemId = itemId
|
||||
} else {
|
||||
params.factory = factory
|
||||
this.flowItemName = ''
|
||||
}
|
||||
getMaterialFlow(params)
|
||||
.then((res) => {
|
||||
this.flowResult = res.data || null
|
||||
})
|
||||
@@ -540,22 +558,29 @@ export default {
|
||||
})
|
||||
},
|
||||
exportFlow() {
|
||||
if (!this.flowForm.itemId) {
|
||||
this.$modal.msgError('请选择物料')
|
||||
return
|
||||
}
|
||||
if (!this.flowForm.timeRange || this.flowForm.timeRange.length !== 2) {
|
||||
this.$modal.msgError('请选择时间范围')
|
||||
return
|
||||
}
|
||||
const factory = this.flowForm.factory ? String(this.flowForm.factory).trim() : ''
|
||||
const itemId = this.flowForm.itemId
|
||||
if (!itemId && !factory) {
|
||||
this.$modal.msgError('请选择物料或填写厂家')
|
||||
return
|
||||
}
|
||||
const params = {
|
||||
startTime: this.flowForm.timeRange[0],
|
||||
endTime: this.flowForm.timeRange[1]
|
||||
}
|
||||
if (itemId) {
|
||||
params.itemId = itemId
|
||||
} else {
|
||||
params.factory = factory
|
||||
}
|
||||
this.download(
|
||||
'/gear/stockIoOrder/materialFlow/export',
|
||||
{
|
||||
itemId: this.flowForm.itemId,
|
||||
startTime: this.flowForm.timeRange[0],
|
||||
endTime: this.flowForm.timeRange[1]
|
||||
},
|
||||
`material_flow_${this.flowForm.itemId}_${new Date().getTime()}.xlsx`
|
||||
params,
|
||||
itemId ? `material_flow_${itemId}_${new Date().getTime()}.xlsx` : `material_flow_factory_${factory}_${new Date().getTime()}.xlsx`
|
||||
)
|
||||
},
|
||||
submitEdit() {
|
||||
|
||||
@@ -199,25 +199,30 @@
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="物料出入库统计" v-model="flowOpen" width="980px" top="6vh" append-to-body>
|
||||
<el-form :inline="true" size="small" label-width="90px">
|
||||
<el-form-item label="物料">
|
||||
<RawSelector v-model="flowForm.itemId" :allowAdd="false" @change="onFlowMaterialChange" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时间范围">
|
||||
<el-date-picker
|
||||
v-model="flowForm.timeRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="flowLoading" @click="fetchFlow">查询</el-button>
|
||||
</el-form-item>
|
||||
<el-form :inline="true" size="small" label-width="70px">
|
||||
<div style="display:flex; flex-wrap:wrap; align-items:flex-end; gap:10px 16px;">
|
||||
<el-form-item label="物料" style="margin-bottom: 0;">
|
||||
<RawSelector v-model="flowForm.itemId" :allowAdd="false" @change="onFlowMaterialChange" />
|
||||
</el-form-item>
|
||||
<el-form-item label="厂家" style="margin-bottom: 0;">
|
||||
<el-input v-model="flowForm.factory" placeholder="请输入厂家" clearable :disabled="!!flowForm.itemId" style="width: 220px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时间" style="margin-bottom: 0;">
|
||||
<el-date-picker
|
||||
v-model="flowForm.timeRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-bottom: 0; margin-left: auto;">
|
||||
<el-button type="primary" :loading="flowLoading" @click="fetchFlow">查询</el-button>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<div v-loading="flowLoading" style="margin-top: 10px;">
|
||||
@@ -226,7 +231,8 @@
|
||||
物料出入库统计
|
||||
</div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<span>物料:</span><span>{{ flowItemName || '-' }}</span>
|
||||
<span v-if="flowForm.itemId">物料:</span><span v-if="flowForm.itemId">{{ flowItemName || '-' }}</span>
|
||||
<span v-else>厂家:</span><span v-else>{{ flowForm.factory || '-' }}</span>
|
||||
<span style="margin-left: 18px;">时间:</span>
|
||||
<span>{{ (flowForm.timeRange && flowForm.timeRange[0]) || '-' }}</span>
|
||||
<span> 至 </span>
|
||||
@@ -258,7 +264,7 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" style="margin-bottom: 10px;">
|
||||
<el-row :gutter="10" justify="center" style="margin-bottom: 10px;">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div>净变动</div>
|
||||
@@ -330,6 +336,7 @@ export default {
|
||||
flowLoading: false,
|
||||
flowForm: {
|
||||
itemId: undefined,
|
||||
factory: undefined,
|
||||
timeRange: []
|
||||
},
|
||||
flowItemName: '',
|
||||
@@ -473,23 +480,34 @@ export default {
|
||||
},
|
||||
onFlowMaterialChange(material) {
|
||||
this.flowItemName = material && material.materialName ? material.materialName : ''
|
||||
if (this.flowForm.itemId) {
|
||||
this.flowForm.factory = undefined
|
||||
}
|
||||
},
|
||||
fetchFlow() {
|
||||
if (!this.flowForm.itemId) {
|
||||
this.$modal.msgError('请选择物料')
|
||||
return
|
||||
}
|
||||
if (!this.flowForm.timeRange || this.flowForm.timeRange.length !== 2) {
|
||||
this.$modal.msgError('请选择时间范围')
|
||||
return
|
||||
}
|
||||
const factory = this.flowForm.factory ? String(this.flowForm.factory).trim() : ''
|
||||
const itemId = this.flowForm.itemId
|
||||
if (!itemId && !factory) {
|
||||
this.$modal.msgError('请选择物料或填写厂家')
|
||||
return
|
||||
}
|
||||
this.flowLoading = true
|
||||
this.flowResult = null
|
||||
getMaterialFlow({
|
||||
itemId: this.flowForm.itemId,
|
||||
const params = {
|
||||
startTime: this.flowForm.timeRange[0],
|
||||
endTime: this.flowForm.timeRange[1]
|
||||
})
|
||||
}
|
||||
if (itemId) {
|
||||
params.itemId = itemId
|
||||
} else {
|
||||
params.factory = factory
|
||||
this.flowItemName = ''
|
||||
}
|
||||
getMaterialFlow(params)
|
||||
.then((res) => {
|
||||
this.flowResult = res.data || null
|
||||
})
|
||||
@@ -498,22 +516,29 @@ export default {
|
||||
})
|
||||
},
|
||||
exportFlow() {
|
||||
if (!this.flowForm.itemId) {
|
||||
this.$modal.msgError('请选择物料')
|
||||
return
|
||||
}
|
||||
if (!this.flowForm.timeRange || this.flowForm.timeRange.length !== 2) {
|
||||
this.$modal.msgError('请选择时间范围')
|
||||
return
|
||||
}
|
||||
const factory = this.flowForm.factory ? String(this.flowForm.factory).trim() : ''
|
||||
const itemId = this.flowForm.itemId
|
||||
if (!itemId && !factory) {
|
||||
this.$modal.msgError('请选择物料或填写厂家')
|
||||
return
|
||||
}
|
||||
const params = {
|
||||
startTime: this.flowForm.timeRange[0],
|
||||
endTime: this.flowForm.timeRange[1]
|
||||
}
|
||||
if (itemId) {
|
||||
params.itemId = itemId
|
||||
} else {
|
||||
params.factory = factory
|
||||
}
|
||||
this.download(
|
||||
'/gear/stockIoOrder/materialFlow/export',
|
||||
{
|
||||
itemId: this.flowForm.itemId,
|
||||
startTime: this.flowForm.timeRange[0],
|
||||
endTime: this.flowForm.timeRange[1]
|
||||
},
|
||||
`material_flow_${this.flowForm.itemId}_${new Date().getTime()}.xlsx`
|
||||
params,
|
||||
itemId ? `material_flow_${itemId}_${new Date().getTime()}.xlsx` : `material_flow_factory_${factory}_${new Date().getTime()}.xlsx`
|
||||
)
|
||||
},
|
||||
submitEdit() {
|
||||
|
||||
@@ -293,25 +293,30 @@
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="物料出入库统计" v-model="flowOpen" width="980px" top="6vh" append-to-body>
|
||||
<el-form :inline="true" size="small" label-width="90px">
|
||||
<el-form-item label="物料">
|
||||
<RawSelector v-model="flowForm.itemId" :allowAdd="false" @change="onFlowMaterialChange" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时间范围">
|
||||
<el-date-picker
|
||||
v-model="flowForm.timeRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="flowLoading" @click="fetchFlow">查询</el-button>
|
||||
</el-form-item>
|
||||
<el-form :inline="true" size="small" label-width="70px">
|
||||
<div style="display:flex; flex-wrap:wrap; align-items:flex-end; gap:10px 16px;">
|
||||
<el-form-item label="物料" style="margin-bottom: 0;">
|
||||
<RawSelector v-model="flowForm.itemId" :allowAdd="false" @change="onFlowMaterialChange" />
|
||||
</el-form-item>
|
||||
<el-form-item label="厂家" style="margin-bottom: 0;">
|
||||
<el-input v-model="flowForm.factory" placeholder="请输入厂家" clearable :disabled="!!flowForm.itemId" style="width: 220px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时间" style="margin-bottom: 0;">
|
||||
<el-date-picker
|
||||
v-model="flowForm.timeRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-bottom: 0; margin-left: auto;">
|
||||
<el-button type="primary" :loading="flowLoading" @click="fetchFlow">查询</el-button>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<div v-loading="flowLoading" style="margin-top: 10px;">
|
||||
@@ -320,7 +325,8 @@
|
||||
物料出入库统计
|
||||
</div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<span>物料:</span><span>{{ flowItemName || '-' }}</span>
|
||||
<span v-if="flowForm.itemId">物料:</span><span v-if="flowForm.itemId">{{ flowItemName || '-' }}</span>
|
||||
<span v-else>厂家:</span><span v-else>{{ flowForm.factory || '-' }}</span>
|
||||
<span style="margin-left: 18px;">时间:</span>
|
||||
<span>{{ (flowForm.timeRange && flowForm.timeRange[0]) || '-' }}</span>
|
||||
<span> 至 </span>
|
||||
@@ -352,7 +358,7 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" style="margin-bottom: 10px;">
|
||||
<el-row :gutter="10" justify="center" style="margin-bottom: 10px;">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div>净变动</div>
|
||||
@@ -456,6 +462,7 @@ export default {
|
||||
flowLoading: false,
|
||||
flowForm: {
|
||||
itemId: undefined,
|
||||
factory: undefined,
|
||||
timeRange: []
|
||||
},
|
||||
flowItemName: '',
|
||||
@@ -670,23 +677,34 @@ export default {
|
||||
},
|
||||
onFlowMaterialChange(material) {
|
||||
this.flowItemName = material && material.materialName ? material.materialName : ''
|
||||
if (this.flowForm.itemId) {
|
||||
this.flowForm.factory = undefined
|
||||
}
|
||||
},
|
||||
fetchFlow() {
|
||||
if (!this.flowForm.itemId) {
|
||||
this.$modal.msgError('请选择物料')
|
||||
return
|
||||
}
|
||||
if (!this.flowForm.timeRange || this.flowForm.timeRange.length !== 2) {
|
||||
this.$modal.msgError('请选择时间范围')
|
||||
return
|
||||
}
|
||||
const factory = this.flowForm.factory ? String(this.flowForm.factory).trim() : ''
|
||||
const itemId = this.flowForm.itemId
|
||||
if (!itemId && !factory) {
|
||||
this.$modal.msgError('请选择物料或填写厂家')
|
||||
return
|
||||
}
|
||||
this.flowLoading = true
|
||||
this.flowResult = null
|
||||
getMaterialFlow({
|
||||
itemId: this.flowForm.itemId,
|
||||
const params = {
|
||||
startTime: this.flowForm.timeRange[0],
|
||||
endTime: this.flowForm.timeRange[1]
|
||||
})
|
||||
}
|
||||
if (itemId) {
|
||||
params.itemId = itemId
|
||||
} else {
|
||||
params.factory = factory
|
||||
this.flowItemName = ''
|
||||
}
|
||||
getMaterialFlow(params)
|
||||
.then((res) => {
|
||||
this.flowResult = res.data || null
|
||||
})
|
||||
@@ -695,22 +713,29 @@ export default {
|
||||
})
|
||||
},
|
||||
exportFlow() {
|
||||
if (!this.flowForm.itemId) {
|
||||
this.$modal.msgError('请选择物料')
|
||||
return
|
||||
}
|
||||
if (!this.flowForm.timeRange || this.flowForm.timeRange.length !== 2) {
|
||||
this.$modal.msgError('请选择时间范围')
|
||||
return
|
||||
}
|
||||
const factory = this.flowForm.factory ? String(this.flowForm.factory).trim() : ''
|
||||
const itemId = this.flowForm.itemId
|
||||
if (!itemId && !factory) {
|
||||
this.$modal.msgError('请选择物料或填写厂家')
|
||||
return
|
||||
}
|
||||
const params = {
|
||||
startTime: this.flowForm.timeRange[0],
|
||||
endTime: this.flowForm.timeRange[1]
|
||||
}
|
||||
if (itemId) {
|
||||
params.itemId = itemId
|
||||
} else {
|
||||
params.factory = factory
|
||||
}
|
||||
this.download(
|
||||
'/gear/stockIoOrder/materialFlow/export',
|
||||
{
|
||||
itemId: this.flowForm.itemId,
|
||||
startTime: this.flowForm.timeRange[0],
|
||||
endTime: this.flowForm.timeRange[1]
|
||||
},
|
||||
`material_flow_${this.flowForm.itemId}_${new Date().getTime()}.xlsx`
|
||||
params,
|
||||
itemId ? `material_flow_${itemId}_${new Date().getTime()}.xlsx` : `material_flow_factory_${factory}_${new Date().getTime()}.xlsx`
|
||||
)
|
||||
},
|
||||
showDetail(row) {
|
||||
|
||||
Reference in New Issue
Block a user