You need to sign in or sign up before continuing.
Commit e05fb9d3 by liangjianmin

feat(classify-price): 添加关务价格审批详情页面

- 新增关务价格审批详情页面,展示申请信息、订单信息、货物明细、附件和审批日志。
- 实现加载状态、审核功能及历史价格对比弹窗。
- 更新审批列表页面,优化列表项的唯一键获取方式。
- 更新 API 接口,添加订单审批详情和历史单价接口。
parent b444b6cf
.classify-price-detail-page {
min-height: 100vh;
background: #f3f6fb;
background: linear-gradient(180deg, #f2f6ff 0, #f7f9fc 220rpx, #f4f6fa 100%);
padding: 16rpx 16rpx 168rpx;
box-sizing: border-box;
.section {
margin-bottom: 12rpx;
background: rgba(255, 255, 255, 0.98);
border-radius: 18rpx;
box-shadow: 0 8rpx 22rpx rgba(15, 23, 42, 0.035);
overflow: hidden;
}
.section-title {
display: flex;
align-items: center;
padding: 20rpx 20rpx 6rpx;
font-size: 28rpx;
font-weight: 700;
color: #1e293b;
letter-spacing: 0.5rpx;
&::before {
content: '';
width: 6rpx;
height: 24rpx;
margin-right: 14rpx;
border-radius: 999rpx;
background: linear-gradient(180deg, #5a7fff 0, #3467ff 100%);
}
}
.section-body {
padding: 8rpx 20rpx 20rpx;
}
.info-list {
display: flex;
flex-direction: column;
}
.info-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14rpx 16rpx;
border-radius: 12rpx;
background: #f7faff;
& + .info-item {
margin-top: 8rpx;
}
}
.info-item-order {
align-items: flex-start;
}
.info-label {
font-size: 24rpx;
color: #5f6f86;
line-height: 32rpx;
flex-shrink: 0;
}
.info-value {
font-size: 25rpx;
color: #0f172a;
line-height: 32rpx;
text-align: right;
word-break: break-all;
}
.align-right {
max-width: 68%;
}
.warning-text {
color: #ec7935;
}
.info-value-box {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.link-text {
margin-top: 2rpx;
font-size: 24rpx;
color: #316cff;
line-height: 30rpx;
}
.metric-list {
display: flex;
margin-top: 8rpx;
}
.metric-card {
flex: 1;
padding: 14rpx 16rpx;
border-radius: 12rpx;
background: #f7faff;
& + .metric-card {
margin-left: 8rpx;
}
}
.metric-label {
display: block;
font-size: 24rpx;
color: #5f6f86;
line-height: 30rpx;
}
.metric-value {
display: block;
margin-top: 4rpx;
font-size: 26rpx;
font-weight: 600;
color: #18263c;
line-height: 32rpx;
}
.reason-box {
margin-top: 8rpx;
padding: 16rpx;
border-radius: 12rpx;
background: #f7faff;
}
.reason-label {
display: block;
font-size: 24rpx;
color: #5f6f86;
line-height: 30rpx;
}
.reason-text {
display: block;
margin-top: 6rpx;
font-size: 25rpx;
color: #18263c;
line-height: 34rpx;
word-break: break-all;
}
.goods-list {
padding: 8rpx 20rpx 20rpx;
}
.goods-card {
padding: 16rpx;
border-radius: 12rpx;
background: #f7faff;
& + .goods-card {
margin-top: 12rpx;
}
}
.goods-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12rpx;
}
.goods-title-box {
display: flex;
align-items: flex-start;
flex: 1;
min-width: 0;
}
.goods-index {
width: 34rpx;
height: 34rpx;
margin-right: 10rpx;
border-radius: 50%;
background: #316cff;
color: #ffffff;
font-size: 22rpx;
line-height: 34rpx;
text-align: center;
flex-shrink: 0;
}
.goods-title {
font-size: 27rpx;
font-weight: 600;
color: #18263c;
line-height: 36rpx;
word-break: break-all;
}
.goods-grid {
display: flex;
flex-wrap: wrap;
}
.goods-cell {
width: 50%;
padding: 8rpx 0;
box-sizing: border-box;
}
.goods-label {
display: block;
font-size: 23rpx;
color: #5f6f86;
line-height: 30rpx;
}
.goods-value {
display: block;
margin-top: 2rpx;
padding-right: 12rpx;
font-size: 24rpx;
color: #18263c;
line-height: 32rpx;
word-break: break-all;
}
.price-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 8rpx;
padding-top: 12rpx;
border-top: 1rpx dashed #d9e2f1;
}
.price-value {
min-width: 170rpx;
padding: 4rpx 10rpx;
border-radius: 4rpx;
font-size: 25rpx;
font-weight: 600;
color: #316cff;
line-height: 34rpx;
text-align: right;
}
.goods-desc {
margin-top: 10rpx;
padding-top: 10rpx;
border-top: 1rpx dashed #d9e2f1;
}
.desc-text {
display: block;
margin-top: 4rpx;
font-size: 24rpx;
color: #18263c;
line-height: 34rpx;
word-break: break-all;
}
.file-title {
font-size: 25rpx;
color: #18263c;
line-height: 34rpx;
}
.file-list {
display: flex;
flex-wrap: wrap;
margin-top: 14rpx;
}
.file-item {
width: 96rpx;
height: 96rpx;
margin: 0 16rpx 16rpx 0;
border-radius: 12rpx;
overflow: hidden;
background: #f7faff;
border: 1rpx solid #e5ebf5;
}
.file-image {
width: 100%;
height: 100%;
}
.pdf-file {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background: #fff5f5;
}
.pdf-text {
padding: 4rpx 8rpx;
border-radius: 6rpx;
background: #dc2626;
font-size: 22rpx;
font-weight: 700;
color: #ffffff;
line-height: 26rpx;
}
.empty-inline {
margin-top: 12rpx;
font-size: 24rpx;
color: #9aa7b8;
line-height: 32rpx;
}
.timeline-wrap {
padding: 8rpx 20rpx 20rpx;
}
.timeline-item {
position: relative;
padding-left: 26rpx;
& + .timeline-item {
margin-top: 10rpx;
}
&::before {
content: '';
position: absolute;
left: 7rpx;
top: 18rpx;
bottom: -14rpx;
width: 1rpx;
background: rgba(191, 219, 254, 0.9);
}
&:last-child::before {
display: none;
}
}
.timeline-dot {
position: absolute;
left: 0;
top: 10rpx;
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: #3d6dff;
}
.timeline-content {
padding: 14rpx 16rpx;
border-radius: 12rpx;
background: #f7faff;
}
.log-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.log-time,
.log-user {
font-size: 24rpx;
color: #5f6f86;
line-height: 32rpx;
}
.log-user {
color: #475569;
}
.log-body {
margin-top: 6rpx;
}
.log-footer {
display: flex;
align-items: center;
margin-top: 6rpx;
}
.log-label {
font-size: 24rpx;
color: #5f6f86;
line-height: 34rpx;
}
.log-text {
display: block;
margin-top: 4rpx;
font-size: 24rpx;
color: #18263c;
line-height: 36rpx;
}
.log-status,
.log-node {
margin-left: 12rpx;
font-size: 24rpx;
font-weight: 600;
line-height: 34rpx;
}
.log-node {
color: #475569;
word-break: break-all;
}
.log-status {
&.approved {
color: #16a34a;
}
&.rejected {
color: #dc2626;
}
&.pending {
color: #316cff;
}
}
.bottom-actions {
position: fixed;
left: 16rpx;
right: 16rpx;
bottom: 0;
display: flex;
align-items: center;
padding: 16rpx;
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
background: rgba(255, 255, 255, 0.96);
border-radius: 18rpx 18rpx 0 0;
box-shadow: 0 -6rpx 20rpx rgba(15, 23, 42, 0.05);
z-index: 100;
}
.approve-modal,
.price-modal {
padding: 40rpx 32rpx;
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
background: #ffffff;
}
.price-modal {
max-height: 86vh;
overflow-y: auto;
}
.modal-title {
margin-bottom: 32rpx;
font-size: 34rpx;
font-weight: 700;
color: #1e293b;
text-align: center;
}
.modal-body,
.price-summary {
margin-bottom: 40rpx;
padding: 24rpx;
border-radius: 18rpx;
background: #f8fafc;
}
.modal-textarea-wrap {
margin-top: 28rpx;
}
.modal-footer {
display: flex;
align-items: center;
}
.price-summary {
margin-bottom: 20rpx;
}
.price-summary-item {
display: flex;
justify-content: space-between;
& + .price-summary-item {
margin-top: 12rpx;
}
}
.summary-label {
font-size: 24rpx;
color: #5f6f86;
line-height: 32rpx;
}
.summary-value {
max-width: 58%;
font-size: 25rpx;
font-weight: 600;
color: #18263c;
line-height: 32rpx;
text-align: right;
word-break: break-all;
}
.history-block {
margin-bottom: 24rpx;
}
.history-title {
margin-bottom: 12rpx;
font-size: 26rpx;
font-weight: 700;
color: #1e293b;
line-height: 34rpx;
}
.history-scroll {
width: 100%;
height: 360rpx;
border: 1rpx solid #e5ebf5;
border-radius: 12rpx;
}
.history-table {
min-width: 980rpx;
background: #ffffff;
}
.history-tr {
display: flex;
min-height: 72rpx;
border-bottom: 1rpx solid #edf1f7;
}
.history-head {
background: #f7faff;
font-weight: 700;
}
.history-td {
width: 160rpx;
padding: 18rpx 14rpx;
border-right: 1rpx solid #edf1f7;
box-sizing: border-box;
font-size: 24rpx;
color: #18263c;
line-height: 34rpx;
word-break: break-all;
}
.history-empty {
padding: 28rpx;
font-size: 24rpx;
color: #9aa7b8;
text-align: center;
}
}
<template>
<view class="classify-price-detail-page"></view>
<view class="classify-price-detail-page">
<view v-if="loading" class="section">
<view class="section-body">
<u-skeleton rows="8" title :animate="true" :loading="true" :rowsWidth="['100%', '92%', '88%', '96%', '80%', '90%', '78%', '86%']" rowsHeight="32"></u-skeleton>
</view>
</view>
<template v-else>
<view class="section">
<view class="section-title">申请信息</view>
<view class="section-body">
<view class="info-list">
<view class="info-item">
<text class="info-label">申请类型</text>
<text class="info-value">关务审批</text>
</view>
<view class="info-item">
<text class="info-label">客户名称</text>
<text class="info-value align-right">{{ detail.customer_name || '-' }}</text>
</view>
<view class="info-item">
<text class="info-label">审批类型</text>
<text class="info-value align-right warning-text">{{ currentNodeName }}</text>
</view>
<view class="info-item">
<text class="info-label">申请时间</text>
<text class="info-value">{{ approveMeta.create_time || formatTime(detail.create_time) }}</text>
</view>
<view class="info-item info-item-order">
<text class="info-label">订单号</text>
<view class="info-value-box">
<text class="info-value">{{ detail.order_sn || '-' }}</text>
<text class="link-text">业务:{{ detail.salesman || '-' }}</text>
</view>
</view>
</view>
</view>
</view>
<view class="section">
<view class="section-title">订单信息</view>
<view class="section-body">
<view class="info-list">
<view class="info-item">
<text class="info-label">订单金额(原币)</text>
<text class="info-value">{{ detail.order_price || '-' }}</text>
</view>
<view class="info-item">
<text class="info-label">订单币别</text>
<text class="info-value">{{ detail.currency_cn || getCurrencyName(detail.currency_id) }}</text>
</view>
<view class="info-item">
<text class="info-label">业务日期</text>
<text class="info-value">{{ detail.business_time_cn || '-' }}</text>
</view>
<view class="info-item">
<text class="info-label">成交方式</text>
<text class="info-value">{{ detail.customs_type || '-' }}</text>
</view>
<view class="info-item">
<text class="info-label">报关类型</text>
<text class="info-value">进口</text>
</view>
<view class="info-item">
<text class="info-label">供应商</text>
<text class="info-value align-right">{{ detail.supplier_name || '-' }}</text>
</view>
</view>
<view class="metric-list">
<view class="metric-card">
<text class="metric-label">汇率</text>
<text class="metric-value">{{ detail.rate || '-' }}</text>
</view>
<view class="metric-card">
<text class="metric-label">发票金额</text>
<text class="metric-value">{{ detail.invoice_price || '-' }}</text>
</view>
</view>
<view class="reason-box" v-if="detail.order_remark">
<text class="reason-label">订单备注</text>
<text class="reason-text">{{ detail.order_remark }}</text>
</view>
</view>
</view>
<view class="section highlight-section">
<view class="section-title">明细</view>
<view class="goods-list">
<view class="goods-card" v-for="(item, index) in goodsList" :key="item.order_goods_id || index">
<view class="goods-header">
<view class="goods-title-box">
<text class="goods-index">{{ index + 1 }}</text>
<text class="goods-title">{{ getGoodsTitle(item) }}</text>
</view>
<text class="link-text" @click.stop="openPriceHistory(item)">历史价格</text>
</view>
<view class="goods-grid">
<view class="goods-cell">
<text class="goods-label">品牌</text>
<text class="goods-value">{{ item.brand || '-' }}</text>
</view>
<view class="goods-cell">
<text class="goods-label">品名</text>
<text class="goods-value">{{ item.goods_title || '-' }}</text>
</view>
<view class="goods-cell">
<text class="goods-label">数量</text>
<text class="goods-value">{{ item.numbers || '-' }}</text>
</view>
<view class="goods-cell">
<text class="goods-label">金额</text>
<text class="goods-value">{{ item.total_price || '-' }}</text>
</view>
<view class="goods-cell">
<text class="goods-label">海关编码</text>
<text class="goods-value">{{ item.customs_code || '-' }}</text>
</view>
<view class="goods-cell">
<text class="goods-label">归类状态</text>
<text class="goods-value">{{ getClassifyStatus(item) }}</text>
</view>
<view class="goods-cell">
<text class="goods-label">COO</text>
<text class="goods-value">{{ item.origin || '-' }}</text>
</view>
<view class="goods-cell">
<text class="goods-label">COD</text>
<text class="goods-value">{{ item.cod || '-' }}</text>
</view>
<view class="goods-cell">
<text class="goods-label">是否商检</text>
<text class="goods-value">{{ getGoodsCheckText(item) }}</text>
</view>
<view class="goods-cell">
<text class="goods-label">成交单位</text>
<text class="goods-value">{{ item.measurement || '-' }}</text>
</view>
</view>
<view class="price-row">
<text class="goods-label">单价</text>
<text class="price-value" :style="getPriceStyle(item)">{{ item.unit_price || '-' }}</text>
</view>
<view class="goods-desc" v-if="item.deckeyele">
<text class="goods-label">申报要素内容</text>
<text class="desc-text">{{ item.deckeyele }}</text>
</view>
<view class="goods-desc" v-if="item.material_sn || item.remark">
<text class="desc-text">{{ getGoodsExtraText(item) }}</text>
</view>
</view>
<u-empty v-if="!goodsList.length" mode="data" text="暂无货物明细" iconSize="120" textSize="24"></u-empty>
</view>
</view>
<view class="section">
<view class="section-title">附件</view>
<view class="section-body">
<view class="file-title">发票Invoice:</view>
<view class="file-list" v-if="invoiceFiles.length">
<view class="file-item" v-for="(file, index) in invoiceFiles" :key="getFileKey(file, index)" @click="openFile(file)">
<image v-if="isImageFile(file)" class="file-image" :src="file" mode="aspectFill"></image>
<view v-else class="pdf-file">
<text class="pdf-text">PDF</text>
</view>
</view>
</view>
<view v-else class="empty-inline">暂无发票附件</view>
</view>
</view>
<view class="section" v-if="approveLogs.length">
<view class="section-title">审批日志</view>
<view class="timeline-wrap">
<view class="timeline-item" v-for="(log, index) in approveLogs" :key="log.approve_item_id || index">
<view class="timeline-dot"></view>
<view class="timeline-content">
<view class="log-header">
<text class="log-time">{{ log.audit_time || '-' }}</text>
<text class="log-user">审批人:{{ log.auditor_name || log.approval_names || '-' }}</text>
</view>
<view class="log-body" v-if="log.remark">
<text class="log-label">审批意见</text>
<text class="log-text">{{ log.remark }}</text>
</view>
<view class="log-footer">
<text class="log-label">审批结果</text>
<text :class="['log-status', getLogStatusClass(log)]">{{ log.approve_status_cn || getLogStatusText(log) }}</text>
</view>
<view class="log-footer" v-if="log.node_name">
<text class="log-label">审批节点</text>
<text class="log-node">{{ log.node_name }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<view class="bottom-actions" v-if="canApprove">
<u-button text="取消" shape="circle" :customStyle="{ flex: 1, marginRight: '24rpx', background: '#ffffff', color: '#606266', border: '1rpx solid #dcdfe6' }" @click="goBack"></u-button>
<u-button text="审核" type="primary" shape="circle" :customStyle="{ flex: 1, background: '#2979ff' }" @click="openApproveModal"></u-button>
</view>
<u-popup :show="showApproveModal" mode="bottom" round="24" @close="closeApproveModal">
<view class="approve-modal">
<view class="modal-title">审核关务订单</view>
<view class="modal-body">
<u-radio-group v-model="modalStatus" placement="row">
<u-radio :name="1" label="同意" :customStyle="{ marginRight: '60rpx' }"></u-radio>
<u-radio :name="-1" label="不同意" activeColor="#2979ff"></u-radio>
</u-radio-group>
<view class="modal-textarea-wrap">
<u-textarea v-model="remark" :placeholder="modalStatus === -1 ? '驳回时必须填写驳回原因' : '请输入审批意见(选填)'" :maxlength="200" height="200"></u-textarea>
</view>
</view>
<view class="modal-footer">
<u-button text="取消" shape="circle" :customStyle="{ flex: 1, marginRight: '20rpx', background: '#ffffff', color: '#606266', border: '1rpx solid #dcdfe6' }" @click="closeApproveModal"></u-button>
<u-button text="确认" type="primary" shape="circle" :loading="submitting" :customStyle="{ flex: 1, background: '#2979ff' }" @click="submitApprove"></u-button>
</view>
</view>
</u-popup>
<u-popup :show="showPriceModal" mode="bottom" round="24" @close="closePriceModal">
<view class="price-modal">
<view class="modal-title">价格对比</view>
<view class="price-summary">
<view class="price-summary-item">
<text class="summary-label">当前订单单价</text>
<text class="summary-value">{{ currentPriceText }}</text>
</view>
<view class="price-summary-item">
<text class="summary-label">近3月平均单价</text>
<text class="summary-value">{{ historyPriceText }}</text>
</view>
<view class="price-summary-item">
<text class="summary-label">差异幅度</text>
<text class="summary-value warning-text">{{ priceHistory.change_percent || '-' }}</text>
</view>
</view>
<view class="history-block">
<view class="history-title">近3月申报数据</view>
<scroll-view class="history-scroll" scroll-x scroll-y>
<view class="history-table">
<view class="history-tr history-head">
<text class="history-td">订单日期</text>
<text class="history-td">订单号</text>
<text class="history-td">型号</text>
<text class="history-td">品牌</text>
<text class="history-td">单价</text>
<text class="history-td">币种</text>
</view>
<view class="history-tr" v-for="(item, index) in recentPriceItems" :key="getHistoryKey('recent', index)">
<text class="history-td">{{ item.business_time_cn || '-' }}</text>
<text class="history-td">{{ item.order_sn || '-' }}</text>
<text class="history-td">{{ item.goods_type || '-' }}</text>
<text class="history-td">{{ item.brand || '-' }}</text>
<text class="history-td">{{ item.unit_price || '-' }}</text>
<text class="history-td">{{ item.currency_cn || '-' }}</text>
</view>
<view class="history-empty" v-if="!recentPriceItems.length">暂无历史数据</view>
</view>
</scroll-view>
</view>
<view class="history-block">
<view class="history-title">历史全部申报数据参考</view>
<scroll-view class="history-scroll" scroll-x scroll-y>
<view class="history-table">
<view class="history-tr history-head">
<text class="history-td">订单日期</text>
<text class="history-td">订单号</text>
<text class="history-td">型号</text>
<text class="history-td">品牌</text>
<text class="history-td">单价</text>
<text class="history-td">币种</text>
</view>
<view class="history-tr" v-for="(item, index) in oldPriceItems" :key="getHistoryKey('old', index)">
<text class="history-td">{{ item.business_time_cn || '-' }}</text>
<text class="history-td">{{ item.order_sn || '-' }}</text>
<text class="history-td">{{ item.goods_type || '-' }}</text>
<text class="history-td">{{ item.brand || '-' }}</text>
<text class="history-td">{{ item.unit_price || '-' }}</text>
<text class="history-td">{{ item.currency_cn || '-' }}</text>
</view>
<view class="history-empty" v-if="!oldPriceItems.length">暂无历史数据</view>
</view>
</scroll-view>
</view>
<view class="modal-footer">
<u-button text="关闭" shape="circle" :customStyle="{ flex: 1, background: '#ffffff', color: '#606266', border: '1rpx solid #dcdfe6' }" @click="closePriceModal"></u-button>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import { API } from '@/util/api';
export default {
data() {
return {};
return {
order_id: '',
bill_id: '',
detail: {},
approveMeta: {},
loading: false,
showApproveModal: false,
modalStatus: 1,
remark: '',
submitting: false,
showPriceModal: false,
priceHistory: {}
};
},
computed: {
/**
* 货物明细
* @return {Array} 货物列表
*/
goodsList() {
return this.detail.items || [];
},
/**
* 发票附件列表
* @return {Array} 附件列表
*/
invoiceFiles() {
return this.getFileList(this.detail.order_invoice_file);
},
/**
* 审批日志
* @return {Array} 审批日志列表
*/
approveLogs() {
return this.approveMeta.items || [];
},
/**
* 当前审批节点名称
* @return {string} 节点名称
*/
currentNodeName() {
var currentItem = this.getCurrentApproveItem();
return this.approveMeta.node_name || currentItem.node_name || '-';
},
/**
* 是否可以审核
* @return {boolean} 是否显示审核按钮
*/
canApprove() {
if (!this.bill_id || !this.approveMeta.bill_id) return false;
var status = this.getApproveStatus();
return status === 0 || status === 1;
},
/**
* 近三月价格列表
* @return {Array} 近三月列表
*/
recentPriceItems() {
var items = this.priceHistory.history_items || [];
return items.filter(item => Number(item.is_recent) === 1);
},
/**
* 历史价格列表
* @return {Array} 历史列表
*/
oldPriceItems() {
var items = this.priceHistory.history_items || [];
return items.filter(item => Number(item.is_recent) === 0);
},
/**
* 当前订单单价文本
* @return {string} 单价文本
*/
currentPriceText() {
var current = this.priceHistory.current || {};
return `${current.price || '0'} ${current.currency_cn || ''}`;
},
/**
* 历史平均价文本
* @return {string} 单价文本
*/
historyPriceText() {
var history = this.priceHistory.history || {};
return `${this.formatHistoryPrice(history.price)} ${history.currency_cn || ''}`;
}
},
onLoad(options) {
this.order_id = options.order_id || options.orderId || options.source_id || options.sourceId || '';
this.bill_id = options.bill_id || options.billId || '';
},
onShow() {
this.getData();
},
methods: {
/**
* 获取关务审批详情
* @return {void}
*/
getData() {
if (!this.order_id) {
uni.showToast({ title: '缺少订单ID', icon: 'none' });
return;
}
this.loading = true;
this.request(API.orderApproveDetail, 'GET', { order_id: this.order_id }, true).then(res => {
this.loading = false;
this.handleDetailResult(res);
}).catch(() => {
this.loading = false;
});
},
/**
* 处理详情接口返回
* @param {Object} res - 接口响应
* @return {void}
*/
handleDetailResult(res) {
if (res.code !== 0) {
uni.showToast({ title: res.msg || '获取详情失败', icon: 'none' });
return;
}
this.detail = res.data || {};
this.setPageTitle();
if (this.detail.order_sn) {
this.getApproveMeta(this.detail.order_sn);
}
},
/**
* 获取审批日志与状态
* @param {string} orderSn - 订单号
* @return {void}
*/
getApproveMeta(orderSn) {
var params = { page: 1, limit: 15, customer_type: 0, order_sn: orderSn };
this.request(API.orderApproveList, 'GET', params).then(res => {
if (res.code === 0) this.approveMeta = this.getApproveMetaItem(res);
});
},
/**
* 从列表响应中取当前审批单
* @param {Object} res - 列表响应
* @return {Object} 审批单
*/
getApproveMetaItem(res) {
var data = res.data || {};
var list = Array.isArray(data) ? data : (data.list || data.data || []);
var matched = list.find(item => `${item.bill_id}` === `${this.bill_id}`);
return matched || list[0] || {};
},
/**
* 设置页面标题
* @return {void}
*/
setPageTitle() {
if (!this.detail.order_sn) return;
uni.setNavigationBarTitle({
title: `${this.detail.order_sn} - 审核详情`
});
},
/**
* 获取当前审批状态
* @return {number} 审批状态
*/
getApproveStatus() {
var status = this.approveMeta.approval_status;
var fallbackStatus = this.detail.approve_status;
return Number(status === undefined ? fallbackStatus : status);
},
/**
* 获取当前审批节点
* @return {Object} 当前节点
*/
getCurrentApproveItem() {
var items = this.approveMeta.items || [];
return items.find(item => Number(item.is_current) === 1) || {};
},
/**
* 返回上一页
* @return {void}
*/
goBack() {
uni.navigateBack();
},
/**
* 打开审核弹窗
* @return {void}
*/
openApproveModal() {
this.modalStatus = 1;
this.remark = '';
this.showApproveModal = true;
},
/**
* 关闭审核弹窗
* @return {void}
*/
closeApproveModal() {
this.showApproveModal = false;
},
/**
* 提交审核
* @return {void}
*/
submitApprove() {
if (!this.validateApprove()) return;
this.submitting = true;
this.request(API.approveOrder, 'POST', this.getApproveParams()).then(res => {
this.submitting = false;
this.handleApproveResult(res);
}).catch(() => {
this.submitting = false;
});
},
/**
* 审核校验
* @return {boolean} 是否通过
*/
validateApprove() {
if (this.modalStatus === -1 && !this.remark.trim()) {
uni.showToast({ title: '驳回时必须填写驳回原因', icon: 'none' });
return false;
}
if (this.modalStatus === 1 && this.hasUnclassifiedGoods()) {
uni.showToast({ title: '存在未归类的商品明细,请先完成归类后再审核通过', icon: 'none' });
return false;
}
return true;
},
/**
* 是否存在未归类商品
* @return {boolean} 是否存在
*/
hasUnclassifiedGoods() {
return this.goodsList.some(item => Number(item.classify_status) !== 1);
},
/**
* 获取审核提交参数
* @return {Object} 参数
*/
getApproveParams() {
var params = { bill_id: this.bill_id, approval_status: this.modalStatus };
if (this.remark.trim()) params.remark = this.remark.trim();
return params;
},
/**
* 处理审核结果
* @param {Object} res - 接口响应
* @return {void}
*/
handleApproveResult(res) {
if (res.code !== 0) {
uni.showToast({ title: res.msg || '操作失败', icon: 'none' });
return;
}
uni.showToast({ title: '操作成功', icon: 'success' });
this.closeApproveModal();
setTimeout(() => {
uni.navigateBack();
}, 1500);
},
/**
* 打开历史价格
* @param {Object} item - 货物明细
* @return {void}
*/
openPriceHistory(item) {
if (!item.order_goods_id) return;
this.priceHistory = {};
this.showPriceModal = true;
this.request(API.goodsHistoryPriceList, 'GET', { order_goods_id: item.order_goods_id }, true).then(res => {
this.handlePriceHistoryResult(res);
});
},
/**
* 处理历史价格接口返回
* @param {Object} res - 接口响应
* @return {void}
*/
handlePriceHistoryResult(res) {
if (res.code === 0) {
this.priceHistory = res.data || {};
return;
}
uni.showToast({ title: res.msg || '获取价格历史失败', icon: 'none' });
},
/**
* 关闭历史价格弹窗
* @return {void}
*/
closePriceModal() {
this.showPriceModal = false;
},
/**
* 获取商品标题
* @param {Object} item - 货物明细
* @return {string} 标题
*/
getGoodsTitle(item) {
var controlText = Number(item.is_control) === 1 ? '【管控】' : '';
return `${controlText}${item.goods_type || '-'}`;
},
/**
* 获取商品归类状态
* @param {Object} item - 货物明细
* @return {string} 状态文本
*/
getClassifyStatus(item) {
var statusMap = { 1: '已归类', 2: '归类中' };
return statusMap[Number(item.classify_status)] || '未归类';
},
/**
* 获取商检文本
* @param {Object} item - 货物明细
* @return {string} 商检文本
*/
getGoodsCheckText(item) {
return Number(item.is_goods_check) === 1 ? '是' : '否';
},
/**
* 获取商品补充信息
* @param {Object} item - 货物明细
* @return {string} 补充信息
*/
getGoodsExtraText(item) {
var rows = [
item.material_sn ? `物料编码:${item.material_sn}` : '',
item.remark ? `备注:${item.remark}` : ''
];
return rows.filter(Boolean).join(' ');
},
/**
* 获取单价样式
* @param {Object} item - 货物明细
* @return {Object} 样式
*/
getPriceStyle(item) {
var color = item.color || item.price_color || '';
return color ? { background: color, color: '#000000' } : {};
},
/**
* 获取币别名称
* @param {number|string} id - 币别ID
* @return {string} 币别名称
*/
getCurrencyName(id) {
var currencyMap = { 1: '人民币', 2: '美元', 3: '港币', 4: '欧元' };
return currencyMap[Number(id)] || `${id || '-'}`;
},
/**
* 格式化历史平均价
* @param {number|string} price - 历史平均价
* @return {string} 格式化结果
*/
formatHistoryPrice(price) {
var number = Number(price || 0);
return isNaN(number) ? `${price || '0'}` : number.toFixed(6);
},
/**
* 格式化时间
* @param {number|string} value - 时间
* @return {string} 时间文本
*/
formatTime(value) {
if (!value) return '-';
if (`${value}`.indexOf('-') > -1) return value;
return this.formatDate(new Date(Number(value) * 1000));
},
/**
* 格式化日期对象
* @param {Date} date - 日期对象
* @return {string} 日期文本
*/
formatDate(date) {
var pad = value => `${value}`.padStart(2, '0');
var day = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
return `${day} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
},
/**
* 拆分文件列表
* @param {string} files - 文件字符串
* @return {Array} 文件列表
*/
getFileList(files) {
return `${files || ''}`.split(',').map(file => file.trim()).filter(Boolean);
},
/**
* 获取附件渲染 key
* @param {string} file - 文件地址
* @param {number} index - 序号
* @return {string} 唯一 key
*/
getFileKey(file, index) {
return `${file}_${index}`;
},
/**
* 获取历史价格渲染 key
* @param {string} type - 历史类型
* @param {number} index - 序号
* @return {string} 唯一 key
*/
getHistoryKey(type, index) {
return `${type}_${index}`;
},
/**
* 判断是否图片文件
* @param {string} url - 文件地址
* @return {boolean} 是否图片
*/
isImageFile(url) {
var imageExts = ['png', 'jpeg', 'jpg', 'gif', 'webp'];
return imageExts.indexOf(this.getFileExt(url)) > -1;
},
/**
* 获取文件扩展名
* @param {string} url - 文件地址
* @return {string} 扩展名
*/
getFileExt(url) {
var text = `${url || ''}`;
var fileTypeMatch = text.match(/[?&]fileType=([^&]+)/);
if (fileTypeMatch) return fileTypeMatch[1].toLowerCase();
var cleanUrl = text.split('?')[0];
var chunks = cleanUrl.split('.');
return chunks.length > 1 ? chunks.pop().toLowerCase() : '';
},
/**
* 打开文件
* @param {string} url - 文件地址
* @return {void}
*/
openFile(url) {
if (this.isImageFile(url)) {
var images = this.invoiceFiles.filter(file => this.isImageFile(file));
uni.previewImage({ urls: images, current: url });
return;
}
this.openDocument(url);
},
/**
* 打开文档
* @param {string} url - 文件地址
* @return {void}
*/
openDocument(url) {
if (typeof window !== 'undefined' && window.open) {
window.open(url, '_blank');
return;
}
uni.downloadFile({
url,
success: res => this.previewDocument(res.tempFilePath)
});
},
/**
* 预览文档
* @param {string} filePath - 本地文件地址
* @return {void}
*/
previewDocument(filePath) {
uni.openDocument({
filePath,
fileType: 'pdf',
showMenu: true
});
},
/**
* 获取日志状态样式
* @param {Object} log - 审批日志
* @return {string} 样式类名
*/
getLogStatusClass(log) {
if (Number(log.approval_status) === 1) return 'approved';
if (Number(log.approval_status) === -1) return 'rejected';
return 'pending';
},
/**
* 获取日志状态文本
* @param {Object} log - 审批日志
* @return {string} 状态文本
*/
getLogStatusText(log) {
var statusMap = { 1: '已通过', '-1': '已驳回', 0: '待审批' };
return statusMap[Number(log.approval_status)] || '-';
}
}
};
</script>
......
......@@ -26,7 +26,7 @@
</view>
<template v-else>
<view class="classify-card" v-for="item in currentList" :key="getItemKey(item)">
<view class="classify-card" v-for="item in currentList" :key="item.approve_id">
<view class="card-header row bothSide verCenter">
<text class="card-title">关务价格审批</text>
<view v-if="currentTab !== 0" :class="['status-badge', getStatusClass(item)]">
......@@ -343,14 +343,6 @@
}
},
/**
* 获取列表唯一键
* @param {Object} item - 审批项
* @return {string|number} 唯一键
*/
getItemKey(item) {
return item.bill_id || item.order_id || item.order_sn;
},
/**
* 获取状态样式
* @param {Object} item - 审批项
* @return {string} 样式名
......@@ -364,8 +356,12 @@
* @return {void}
*/
handleDetail(item) {
if (!item.order_id) {
uni.showToast({ title: '缺少订单ID', icon: 'none' });
return;
}
uni.navigateTo({
url: `/pages/classify-price/detail?order_id=${item.order_id || ''}&bill_id=${item.bill_id || ''}`
url: `/pages/classify-price/detail?order_id=${item.order_id}&bill_id=${item.bill_id || ''}`
});
}
}
......
......@@ -85,7 +85,19 @@ const API = {
/**
* 猎芯供应链SCM审批列表
*/
orderApproveList: API_BASE_SCM + '/approve/orderApproveList'
orderApproveList: API_BASE_SCM + '/approve/orderApproveList',
/**
* 猎芯供应链SCM订单审批详情
*/
orderApproveDetail: API_BASE_SCM + '/approve/orderDetail',
/**
* 猎芯供应链SCM订单审批
*/
approveOrder: API_BASE_SCM + '/approve/approveOrder',
/**
* 猎芯供应链SCM历史单价
*/
goodsHistoryPriceList: API_BASE_SCM + '/approve/goodsHistoryPriceList'
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment