<template>
|
<view class="delivery-ledger">
|
<!-- 页面头部 -->
|
<PageHeader title="发货台账" @back="goBack" />
|
|
<!-- 搜索区域 -->
|
<view class="search-section">
|
<view class="search-bar">
|
<view class="search-input">
|
<up-input
|
class="search-text"
|
placeholder="请输入销售订单号"
|
v-model="searchForm.salesContractNo"
|
@change="handleQuery"
|
clearable
|
/>
|
</view>
|
<view class="search-input">
|
<up-input
|
class="search-text"
|
placeholder="请输入客户名称"
|
v-model="searchForm.customerName"
|
@change="handleQuery"
|
clearable
|
/>
|
</view>
|
<view class="filter-button" @click="handleQuery">
|
<up-icon name="search" size="24" color="#999"></up-icon>
|
</view>
|
</view>
|
</view>
|
|
<!-- 发货台账列表 -->
|
<view class="ledger-list" v-if="tableData.length > 0">
|
<view v-for="(item, index) in tableData" :key="index">
|
<view class="ledger-item" @click="openDetail(item)">
|
<view class="item-header">
|
<view class="item-left">
|
<view class="document-icon">
|
<up-icon name="file-text" size="16" color="#ffffff"></up-icon>
|
</view>
|
<text class="item-id">{{ item.salesContractNo }}</text>
|
</view>
|
<u-tag
|
size="mini"
|
:type="getApprovalStatusType(item.status)"
|
>{{ getApprovalStatusText(item.status) }}</u-tag>
|
</view>
|
<up-divider></up-divider>
|
<view class="item-details">
|
<view class="detail-row">
|
<text class="detail-label">发货订单号</text>
|
<text class="detail-value">{{ item.shippingNo || '--' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">客户名称</text>
|
<text class="detail-value">{{ item.customerName }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">发货进度</text>
|
<view class="progress-wrapper">
|
<up-line-progress
|
:percentage="getShippingProgress(item)"
|
:active-color="getProgressColor(item)"
|
height="8"
|
></up-line-progress>
|
<text class="progress-text">{{ getShippingProgress(item) }}%</text>
|
</view>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">总发货数量</text>
|
<text class="detail-value">{{ item.shippingTotal || 0 }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">已发货数量</text>
|
<text class="detail-value success">{{ item.shippingSuccessTotal || 0 }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">待发货数量</text>
|
<text class="detail-value warning">{{ item.waitShippingTotal || 0 }}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
<view v-else class="no-data">
|
<up-empty mode="list" text="暂无发货台账数据"></up-empty>
|
</view>
|
|
<!-- 发货详情弹窗 -->
|
<u-popup
|
:show="dialogFormVisible"
|
mode="bottom"
|
:round="16"
|
:closeable="true"
|
@close="closeDia"
|
>
|
<view class="popup-content">
|
<view class="popup-header">
|
<text class="popup-title">发货台账详情</text>
|
</view>
|
<scroll-view scroll-y class="popup-body">
|
<view v-if="currentShippingOrder" class="shipping-container">
|
<!-- 订单信息卡片 -->
|
<view class="info-card">
|
<view class="card-header">
|
<text class="card-title">订单信息</text>
|
<u-tag
|
size="mini"
|
:type="getProgressColor(currentShippingOrder) === '#67C23A' ? 'success' : 'warning'"
|
>{{ getShippingProgress(currentShippingOrder) }}%</u-tag>
|
</view>
|
<view class="card-body">
|
<view class="info-row">
|
<text class="info-label">销售订单</text>
|
<text class="info-value">{{ currentShippingOrder.salesContractNo || '--' }}</text>
|
</view>
|
<view class="info-row">
|
<text class="info-label">客户名称</text>
|
<text class="info-value">{{ currentShippingOrder.customerName || '--' }}</text>
|
</view>
|
<view class="info-row">
|
<text class="info-label">发货订单号</text>
|
<text class="info-value">{{ currentShippingOrder.shippingNo || '--' }}</text>
|
</view>
|
<up-divider></up-divider>
|
<view class="quantity-summary">
|
<view class="summary-item">
|
<text class="summary-label">总发货数量</text>
|
<text class="summary-value total">{{ currentShippingOrder.shippingTotal || 0 }}</text>
|
</view>
|
<view class="summary-item">
|
<text class="summary-label">已发货数量</text>
|
<text class="summary-value shipped">{{ currentShippingOrder.shippingSuccessTotal || 0 }}</text>
|
</view>
|
<view class="summary-item">
|
<text class="summary-label">待发货数量</text>
|
<text class="summary-value waiting">{{ currentShippingOrder.waitShippingTotal || 0 }}</text>
|
</view>
|
</view>
|
<view class="progress-wrapper">
|
<up-line-progress
|
:percentage="getShippingProgress(currentShippingOrder)"
|
:active-color="getProgressColor(currentShippingOrder)"
|
height="12"
|
></up-line-progress>
|
</view>
|
</view>
|
</view>
|
|
<!-- 发货记录卡片 -->
|
<view class="records-card">
|
<view class="card-header">
|
<text class="card-title">发货记录</text>
|
<text class="record-count">共 {{ shippingRecords.length }} 条记录</text>
|
</view>
|
<view class="card-body">
|
<view v-if="shippingRecords.length === 0" class="empty-state">
|
<up-empty mode="list" text="暂无发货记录"></up-empty>
|
</view>
|
<view v-else class="records-list">
|
<view
|
v-for="(record, index) in shippingRecords"
|
:key="record.id || index"
|
class="record-item"
|
>
|
<view class="record-header">
|
<u-tag
|
size="mini"
|
:type="record.type === '货车' ? 'primary' : 'success'"
|
>{{ record.type }}</u-tag>
|
<text class="record-date">{{ record.shippingDate }}</text>
|
</view>
|
<view class="record-body">
|
<view class="record-info-row">
|
<text class="record-info-label">发货数量</text>
|
<text class="record-info-value quantity">{{ record.shippingNum }}</text>
|
</view>
|
<view class="record-info-row" v-if="record.type === '货车'">
|
<text class="record-info-label">车牌号</text>
|
<text class="record-info-value">{{ record.shippingCarNumber || '--' }}</text>
|
</view>
|
<view class="record-info-row" v-else>
|
<text class="record-info-label">快递公司</text>
|
<text class="record-info-value">{{ record.expressCompany || '--' }}</text>
|
</view>
|
<view class="record-info-row" v-if="record.type === '快递'">
|
<text class="record-info-label">快递单号</text>
|
<text class="record-info-value">{{ record.expressNumber || '--' }}</text>
|
</view>
|
<view class="record-info-row" v-if="record.commonFileList && record.commonFileList.length > 0">
|
<text class="record-info-label">发货图片</text>
|
<view class="record-images">
|
<image
|
v-for="(file, imgIndex) in record.commonFileList"
|
:key="imgIndex"
|
:src="normalizeFileUrl(file?.url)"
|
mode="aspectFill"
|
class="record-image"
|
@click="previewImage(record.commonFileList, imgIndex)"
|
/>
|
</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
</scroll-view>
|
<view class="popup-footer">
|
<u-button @click="closeDia">关闭</u-button>
|
</view>
|
</view>
|
</u-popup>
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, reactive } from "vue";
|
import { onShow } from "@dcloudio/uni-app";
|
import PageHeader from "@/components/PageHeader.vue";
|
import {
|
deliveryLedgerListPage,
|
shippingInfoDetailListPage,
|
} from "@/api/salesManagement/deliveryLedger.js";
|
|
// 表格数据
|
const tableData = ref([]);
|
const tableLoading = ref(false);
|
const page = reactive({
|
current: 1,
|
size: 100,
|
});
|
const total = ref(0);
|
const javaApi = import.meta.env.VITE_APP_BASE_API;
|
|
// 搜索表单
|
const searchForm = reactive({
|
salesContractNo: "",
|
customerName: "",
|
});
|
|
// 发货详情弹框
|
const dialogFormVisible = ref(false);
|
const currentShippingOrder = ref(null);
|
const shippingRecords = ref([]);
|
const shippingRecordsLoading = ref(false);
|
|
// 返回上一页
|
const goBack = () => {
|
uni.navigateBack();
|
};
|
|
// 显示加载提示
|
const showLoadingToast = (message) => {
|
uni.showLoading({
|
title: message,
|
mask: true,
|
});
|
};
|
|
// 关闭提示
|
const closeToast = () => {
|
uni.hideLoading();
|
};
|
|
// 查询列表
|
const handleQuery = () => {
|
page.current = 1;
|
getList();
|
};
|
|
const getList = () => {
|
tableLoading.value = true;
|
showLoadingToast("加载中...");
|
deliveryLedgerListPage({ ...searchForm, ...page })
|
.then((res) => {
|
tableData.value = res.data.records || [];
|
total.value = res.data.total || 0;
|
closeToast();
|
tableLoading.value = false;
|
})
|
.catch(() => {
|
closeToast();
|
tableLoading.value = false;
|
});
|
};
|
|
// 打开详情弹框
|
const openDetail = async (row) => {
|
currentShippingOrder.value = row;
|
await loadShippingRecords(row.id);
|
dialogFormVisible.value = true;
|
};
|
|
// 加载发货记录
|
const loadShippingRecords = async (shippingInfoId) => {
|
shippingRecordsLoading.value = true;
|
try {
|
const res = await shippingInfoDetailListPage({ shippingInfoId, current: 1, size: 100 });
|
shippingRecords.value = res.data.records || [];
|
} catch (error) {
|
shippingRecords.value = [];
|
} finally {
|
shippingRecordsLoading.value = false;
|
}
|
};
|
|
// 关闭弹框
|
const closeDia = () => {
|
dialogFormVisible.value = false;
|
currentShippingOrder.value = null;
|
shippingRecords.value = [];
|
};
|
|
// 文件URL处理
|
const normalizeFileUrl = (rawUrl = '') => {
|
let fileUrl = rawUrl || '';
|
if (fileUrl && fileUrl.indexOf('\\') > -1) {
|
const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads');
|
if (uploadsIndex > -1) {
|
const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/');
|
fileUrl = '/' + relativePath;
|
} else {
|
const parts = fileUrl.split('\\');
|
const fileName = parts[parts.length - 1];
|
fileUrl = '/uploads/' + fileName;
|
}
|
}
|
if (fileUrl && !fileUrl.startsWith('http')) {
|
if (!fileUrl.startsWith('/')) fileUrl = '/' + fileUrl;
|
fileUrl = javaApi + fileUrl;
|
}
|
return fileUrl;
|
};
|
|
// 预览图片
|
const previewImage = (fileList, currentIndex) => {
|
const urls = fileList.map((f) => normalizeFileUrl(f?.url));
|
uni.previewImage({
|
urls: urls,
|
current: currentIndex,
|
});
|
};
|
|
// 获取发货进度百分比
|
const getShippingProgress = (row) => {
|
const shipped = row.shippingSuccessTotal || 0;
|
const total = shipped + (row.waitShippingTotal || 0);
|
if (total === 0) return 0;
|
return Math.round((shipped / total) * 100);
|
};
|
|
// 获取进度条颜色
|
const getProgressColor = (row) => {
|
const progress = getShippingProgress(row);
|
if (progress === 100) return '#67C23A';
|
if (progress >= 50) return '#E6A23C';
|
return '#2979ff';
|
};
|
|
// 获取审核状态文本
|
const getApprovalStatusText = (status) => {
|
if (status === null || status === undefined || status === '') {
|
return '待审核';
|
}
|
if (typeof status === 'number') {
|
const statusMap = {
|
0: '待审核',
|
1: '审核中',
|
2: '审核拒绝',
|
3: '审核通过'
|
};
|
return statusMap[status] || '待审核';
|
}
|
const statusStr = String(status).trim();
|
const statusTextMap = {
|
'待审核': '待审核',
|
'审核中': '审核中',
|
'审核拒绝': '审核拒绝',
|
'审核通过': '审核通过',
|
'0': '待审核',
|
'1': '审核中',
|
'2': '审核拒绝',
|
'3': '审核通过'
|
};
|
return statusTextMap[statusStr] || statusStr || '待审核';
|
};
|
|
// 获取审核状态标签类型
|
const getApprovalStatusType = (status) => {
|
if (status === null || status === undefined || status === '') {
|
return 'info';
|
}
|
if (typeof status === 'number') {
|
const typeMap = {
|
0: 'info',
|
1: 'warning',
|
2: 'error',
|
3: 'success'
|
};
|
return typeMap[status] || 'info';
|
}
|
const statusStr = String(status).trim();
|
const typeTextMap = {
|
'待审核': 'info',
|
'审核中': 'warning',
|
'审核拒绝': 'error',
|
'审核通过': 'success',
|
'0': 'info',
|
'1': 'warning',
|
'2': 'error',
|
'3': 'success'
|
};
|
return typeTextMap[statusStr] || 'info';
|
};
|
|
onShow(() => {
|
getList();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.delivery-ledger {
|
min-height: 100vh;
|
background: #f8f9fa;
|
position: relative;
|
}
|
|
// 搜索区域
|
.search-section {
|
padding: 10px 20px;
|
background: #ffffff;
|
}
|
|
.search-bar {
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
flex-wrap: wrap;
|
}
|
|
.search-input {
|
flex: 1;
|
min-width: 140px;
|
background: #f5f5f5;
|
border-radius: 24px;
|
padding: 0 16px;
|
display: flex;
|
align-items: center;
|
}
|
|
.search-text {
|
flex: 1;
|
font-size: 14px;
|
color: #333;
|
background: transparent;
|
border: none;
|
outline: none;
|
|
&::placeholder {
|
color: #999;
|
}
|
}
|
|
.filter-button {
|
width: 40px;
|
height: 40px;
|
border-radius: 8px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
background: #f5f5f5;
|
}
|
|
// 列表区域
|
.ledger-list {
|
padding: 20px;
|
}
|
|
.ledger-item {
|
background: #ffffff;
|
border-radius: 12px;
|
margin-bottom: 16px;
|
overflow: hidden;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
padding: 0 16px;
|
|
&:active {
|
transform: scale(0.98);
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
}
|
}
|
|
.item-header {
|
padding: 16px 0;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
}
|
|
.item-left {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.document-icon {
|
width: 24px;
|
height: 24px;
|
background: #2979ff;
|
border-radius: 4px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.item-id {
|
font-size: 14px;
|
color: #333;
|
font-weight: 500;
|
}
|
|
.item-details {
|
padding: 16px 0;
|
}
|
|
.detail-row {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 8px;
|
|
&:last-child {
|
margin-bottom: 0;
|
}
|
}
|
|
.detail-label {
|
font-size: 12px;
|
color: #777777;
|
min-width: 60px;
|
}
|
|
.detail-value {
|
font-size: 12px;
|
color: #000000;
|
text-align: right;
|
flex: 1;
|
margin-left: 16px;
|
|
&.success {
|
color: #67C23A;
|
font-weight: 500;
|
}
|
|
&.warning {
|
color: #E6A23C;
|
font-weight: 500;
|
}
|
}
|
|
.progress-wrapper {
|
flex: 1;
|
margin-left: 16px;
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
|
.progress-text {
|
font-size: 12px;
|
color: #666;
|
min-width: 36px;
|
text-align: right;
|
}
|
}
|
|
.no-data {
|
padding: 40px 20px;
|
text-align: center;
|
color: #999;
|
}
|
|
// 弹窗样式
|
.popup-content {
|
max-height: 80vh;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.popup-header {
|
padding: 16px;
|
border-bottom: 1px solid #eee;
|
text-align: center;
|
|
.popup-title {
|
font-size: 16px;
|
font-weight: 500;
|
color: #333;
|
}
|
}
|
|
.popup-body {
|
flex: 1;
|
padding: 16px;
|
max-height: 60vh;
|
}
|
|
.popup-footer {
|
padding: 16px;
|
border-top: 1px solid #eee;
|
display: flex;
|
gap: 12px;
|
|
:deep(.u-button) {
|
flex: 1;
|
}
|
}
|
|
// 卡片样式
|
.info-card,
|
.records-card {
|
background: #ffffff;
|
border-radius: 12px;
|
margin-bottom: 16px;
|
overflow: hidden;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
}
|
|
.card-header {
|
padding: 16px;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
border-bottom: 1px solid #f5f5f5;
|
|
.card-title {
|
font-size: 14px;
|
font-weight: 500;
|
color: #333;
|
}
|
|
.record-count {
|
font-size: 12px;
|
color: #999;
|
}
|
}
|
|
.card-body {
|
padding: 16px;
|
}
|
|
// 订单信息
|
.info-row {
|
display: flex;
|
justify-content: space-between;
|
padding: 8px 0;
|
|
.info-label {
|
color: #666;
|
font-size: 14px;
|
}
|
|
.info-value {
|
color: #333;
|
font-weight: 500;
|
font-size: 14px;
|
}
|
}
|
|
.quantity-summary {
|
display: flex;
|
justify-content: space-between;
|
margin: 16px 0;
|
|
.summary-item {
|
text-align: center;
|
flex: 1;
|
|
.summary-label {
|
font-size: 12px;
|
color: #999;
|
margin-bottom: 8px;
|
display: block;
|
}
|
|
.summary-value {
|
font-size: 20px;
|
font-weight: bold;
|
|
&.total {
|
color: #2979ff;
|
}
|
|
&.shipped {
|
color: #67C23A;
|
}
|
|
&.waiting {
|
color: #E6A23C;
|
}
|
}
|
}
|
}
|
|
// 发货记录
|
.empty-state {
|
padding: 40px 0;
|
}
|
|
.records-list {
|
.record-item {
|
border: 1px solid #eee;
|
border-radius: 8px;
|
padding: 12px;
|
margin-bottom: 12px;
|
|
&:last-child {
|
margin-bottom: 0;
|
}
|
}
|
|
.record-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 12px;
|
padding-bottom: 12px;
|
border-bottom: 1px solid #f5f5f5;
|
|
.record-date {
|
font-size: 12px;
|
color: #999;
|
}
|
}
|
|
.record-body {
|
.record-info-row {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 8px;
|
|
&:last-child {
|
margin-bottom: 0;
|
}
|
|
.record-info-label {
|
font-size: 12px;
|
color: #999;
|
}
|
|
.record-info-value {
|
font-size: 14px;
|
color: #333;
|
|
&.quantity {
|
font-weight: bold;
|
color: #2979ff;
|
font-size: 16px;
|
}
|
}
|
}
|
|
.record-images {
|
display: flex;
|
gap: 8px;
|
flex-wrap: wrap;
|
margin-top: 8px;
|
|
.record-image {
|
width: 60px;
|
height: 60px;
|
border-radius: 4px;
|
}
|
}
|
}
|
}
|
|
// 隐藏uview-plus的某些默认样式
|
:deep(.u-divider) {
|
margin: 8px 0 !important;
|
}
|
</style>
|