<template>
|
<basicContainer>
|
<avue-crud :option="option" :table-loading="pageF.loading" :data="tableData" :page="page"
|
:permission="permissionList" :before-open="beforeOpen" v-model="form" v-model:search="queryParams" ref="crudRef"
|
@refresh-change="refreshChange" @search-change="searchChange" @search-reset="searchReset"
|
@selection-change="selectionChange" @current-change="currentChange" @size-change="sizeChange" @on-load="onLoad">
|
<template #remark="{ row }">
|
<el-input v-model="row.remark" readonly placeholder="暂无备注" @click="handleRemarkClick(row)"
|
style="cursor: pointer;" />
|
</template>
|
<template #menu-left>
|
|
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['tms:tmsDriver:export']">导出
|
</el-button>
|
</template>
|
<template #address="{ row }">
|
<div class="hot" @click="handleLogItinerary(row)">
|
<div>起:{{ row.shipperRegionLabel?.split('/').pop() }}({{ row.shipperAddress }})</div>
|
<div>抵:{{ row.receiverRegionLabel?.split('/').pop() }}({{ row.receiverAddress }})</div>
|
</div>
|
|
</template>
|
<template #license="{ row }">
|
<div>{{ row.licensePlate }}</div>
|
<div>{{ row.licenseHk }}</div>
|
<div>{{ row.licenseMo }}</div>
|
</template>
|
<template #shelfCodeAndContainerNo="{ row }">
|
<div>{{ row.shelfCode }}</div>
|
<div>{{ row.containerNo }}</div>
|
</template>
|
<template #pShippingFee="{ row }">
|
<div v-if="row.rshippingFeeRmb">{{ row.rshippingFeeRmb }}RMB</div>
|
<div v-if="row.rshippingFeeHkd">{{ row.rshippingFeeHkd }}HKD</div>
|
</template>
|
<template #pServerFee="{ row }">
|
<div v-if="row.pserverFeeRmb">{{ row.pserverFeeRmb }}RMB</div>
|
<div v-if="row.pserverFeeHkd">{{ row.pserverFeeHkd }}HKD</div>
|
</template>
|
<template #pActualFee="{ row }">
|
<div v-if="row.pactualFeeRmb">{{ row.pactualFeeRmb }}RMB</div>
|
<div v-if="row.pactualFeeHkd">{{ row.pactualFeeHkd }}HKD</div>
|
</template>
|
<template #pTotal="{ row }">
|
<div class="hot" @click="handlePayableFeeView(row)">
|
|
<div v-if="row.ptotalRmb">{{ row.ptotalRmb }}RMB</div>
|
<div v-if="row.ptotalHkd">{{ row.ptotalHkd }}HKD</div>
|
</div>
|
</template>
|
|
<template #rShippingFee="{ row }">
|
<div v-if="row.rshippingFeeRmb">{{ row.rshippingFeeRmb }}RMB</div>
|
<div v-if="row.rshippingFeeHkd">{{ row.rshippingFeeRmb }}HKD</div>
|
</template>
|
<template #rServerFee="{ row }">
|
<div v-if="row.rserverFeeRmb">{{ row.rserverFeeRmb }}RMB</div>
|
<div v-if="row.rserverFeeHkd">{{ row.rserverFeeHkd }}HKD</div>
|
</template>
|
<template #rActualFee="{ row }">
|
<div v-if="row.ractualFeeRmb">{{ row.ractualFeeRmb }}RMB</div>
|
<div v-if="row.ractualFeeHkd">{{ row.ractualFeeHkd }}HKD</div>
|
</template>
|
<template #rTotal="{ row }">
|
<div class="hot" @click="handleReceivableFeeView(row)">
|
<div v-if="row.rtotalRmb">{{ row.rtotalRmb }}RMB</div>
|
<div v-if="row.rtotalHkd">{{ row.rtotalHkd }}HKD</div>
|
</div>
|
</template>
|
|
<template #grossProfit="{ row }">
|
<div v-if="row.grossProfitRmb">{{ row.grossProfitRmb }}RMB</div>
|
<div v-if="row.grossProfitHkd">{{ row.grossProfitHkd }}HKD</div>
|
</template>
|
<template #grossProfitRadio="{ row }">
|
<div v-if="row.grossProfitRadioRmb">RMB:{{ row.grossProfitRadioRmb }}%</div>
|
<div v-if="row.grossProfitRadioHkd">HKD:{{ row.grossProfitRadioHkd }}%</div>
|
</template>
|
|
|
|
</avue-crud>
|
</basicContainer>
|
|
<el-dialog :title="pageF.title" v-model="pageF.open" class="avue-dialog avue-dialog--top" width="80%">
|
<div v-if="optionType == 'receivableFee'">
|
<el-descriptions :column="3" border>
|
<el-descriptions-item label="应收费用编号">{{ form.systemNo }}</el-descriptions-item>
|
<el-descriptions-item label="调度单号">{{ form.dispatchNo }}</el-descriptions-item>
|
<el-descriptions-item label="客户名称">{{ form.customerName }}</el-descriptions-item>
|
<el-descriptions-item label="项目名称">{{ form.projectName }}</el-descriptions-item>
|
<el-descriptions-item label="调度单确定时间">{{ form.dispatchConfirmTime }}</el-descriptions-item>
|
<el-descriptions-item label="费用生成时间">{{ form.costGenerateTime }}</el-descriptions-item>
|
<el-descriptions-item label="关联账单编号">{{ form.billRelationNo }}</el-descriptions-item>
|
<el-descriptions-item label="应收金额人民币">{{ form.receivableRMBAmount }}</el-descriptions-item>
|
<el-descriptions-item label="应收金额港币">{{ form.receivableHKBAmount }}</el-descriptions-item>
|
</el-descriptions>
|
<h3>费用明细</h3>
|
</div>
|
<avue-crud :option="boxTableOption" ref="itemsTableRef" :data="boxTableData">
|
<template #expand="{ row }">
|
<avue-crud :option="boxItemTableOption" ref="itemsTableRef2" :data="row.payableFeeItems">
|
</avue-crud>
|
</template>
|
<template #receivableAmount="{ row }">
|
<div v-if="row.receivableAmountRMB > 0">{{ row.receivableAmountRMB }}人民币</div>
|
<div v-if="row.receivableAmountHKD > 0">{{ row.receivableAmountHKD }}港币</div>
|
|
</template>
|
|
<template #payableAmount="{ row }">
|
<div v-if="row.payableRmbAmount > 0">{{ row.payableRmbAmount }}人民币</div>
|
<div v-if="row.payableHkbAmount > 0">{{ row.payableHkbAmount }}港币</div>
|
</template>
|
</avue-crud>
|
|
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="pageF.open = false">取 消</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
|
<el-dialog :title="remarkBox.title" v-model="remarkBox.open" width="500px">
|
<div style="margin-bottom: 20px;">
|
<el-input v-model="remarkBox.content" type="textarea" :rows="4" placeholder="请输入备注内容" />
|
<div style="margin-top: 15px; text-align: right;">
|
<el-button @click="remarkBox.open = false">取 消</el-button>
|
<el-button type="primary" @click="submitRemark">确 定</el-button>
|
</div>
|
</div>
|
|
<el-table :data="remarkBox.list" border stripe size="small">
|
<el-table-column prop="createTime" label="操作时间" width="160" />
|
<el-table-column prop="createBy" label="操作人" width="100" />
|
<el-table-column prop="notes" label="备注内容" />
|
</el-table>
|
</el-dialog>
|
</template>
|
|
<script setup name="tmsDriver" lang="ts">
|
import {
|
listTmsDispatchFeeSummaryVi, exportTmsDispatchFeeSummaryVi, tmsDispatchFeeSummaryViNote
|
} from "@/api/tms/tmsDispatchFeeSummary";
|
import useCurrentInstance from "@/utils/useCurrentInstance";
|
import { computed, onMounted, reactive, ref, toRefs, watch } from "vue";
|
import { PagesInterface, PageQueryInterface } from "@/utils/globalInterface";
|
import { usePagePlus } from "@/hooks/usePagePlus";
|
import { hasPermission } from "@/utils/permissionUtils";
|
import { getTmsServiceProvider, listTmsServiceProvider } from "@/api/tms/tmsServiceProvider";
|
import { listTmsTrip } from "@/api/tms/tmsTrip";
|
import { getTmsReceivableFeeByDispatchNo } from "@/api/tms/tmsReceivableFee";
|
import { getTmsPayableFeeByDispatchNo } from "@/api/tms/tmsPayableFee";
|
import { listDispatchFeeSummaryViLog } from "@/api/tms/dispatchFeeSummaryViLog";
|
const { proxy } = useCurrentInstance();
|
const crudRef = ref();
|
|
const permissionList = computed(() => {
|
return {
|
addBtn: hasPermission(["tms:tmsDriver:add"]),
|
delBtn: hasPermission(["tms:tmsDriver:remove"]),
|
editBtn: hasPermission(["tms:tmsDriver:edit"]),
|
viewBtn: hasPermission(["tms:tmsDriver:query"]),
|
}
|
})
|
|
const data = reactive({
|
form: <any>{},
|
queryParams: <any & PageQueryInterface>{},
|
page: <PagesInterface>{
|
pageSize: 10,
|
total: 0,
|
currentPage: 1,
|
},
|
selectionList: [],
|
boxTableOption: <any>{},
|
boxTableData: <any>[],
|
optionType: <any>{},
|
boxItemTableOption: {},
|
})
|
const { queryParams, form, page, selectionList, boxTableOption, boxTableData, optionType, boxItemTableOption } = toRefs(data);
|
const option = ref({
|
pageKey: 'TmsDriver',
|
rowKey: 'id',
|
labelWidth: 120,
|
searchLabelWidth: 100,
|
addBtn: false,
|
selection: false,
|
menu: false,
|
column: [
|
{
|
label: '下单信息',
|
children: [
|
{ label: '下单时间', prop: 'orderTime', width: 120 },
|
{ label: '下单时间', prop: 'orderTimeRange', width: 120, search: true, searchRange: true, type: 'date', format: 'YYYY-MM-DD', hide: true, searchSpan: 5, valueFormat: 'YYYY-MM-DD', },
|
{ label: '客户', prop: 'customerName', width: 180, search: true, },
|
{ label: '项目名称', prop: 'projectName', width: 180, search: true },
|
{ label: '运输路线', prop: 'address', width: 300, search: true },
|
{ label: '下单车型', prop: 'requiredVehicleTypes', width: 120, type: 'select', dicUrl: '/system/dict/data/type/vehicle_type', dataType: 'string', search: true },
|
]
|
},
|
{
|
label: '备注',
|
prop: 'remark',
|
width: 120,
|
slot: true, // 开启自定义插槽
|
},
|
{
|
label: '派车信息',
|
children: [
|
{ label: '调度单号', prop: 'dispatchNo', width: 180, search: true, },
|
{
|
label: '状态', prop: 'status', width: 120, type: 'select', dataType: 'string', dicUrl: '/system/dict/data/type/tms_dispatch_order_status', search: true
|
},
|
{ label: '承运商', prop: 'providerName', width: 180, search: true },
|
{ label: '车牌', prop: 'license', width: 120 },
|
{ label: '司机', prop: 'mainDriverName', width: 120 },
|
{ label: '承运车型', prop: 'actualVehicleType', width: 120, type: 'select', dicUrl: '/system/dict/data/type/vehicle_type', dataType: 'string', search: true },
|
]
|
},
|
{
|
label: '承运信息',
|
children: [
|
{ label: '托架/柜号', prop: 'shelfCodeAndContainerNo', width: 120 },
|
{ label: '口岸', prop: 'port', width: 120 },
|
{ label: '无缝供应商', prop: 'seamlessSupplierName', width: 120 },
|
{ label: '无缝号', prop: 'seamlessNumber', width: 120 },
|
{ label: '查验', prop: 'iscc', width: 120, type: 'select', dicUrl: '/system/dict/data/type/sys_number_is', dataType: 'string', },
|
{ label: '报关服务商', prop: 'customsServiceProviderName', width: 120, search: true },
|
{ label: '装卸服务商', prop: 'loadingServiceProviderName', width: 120, search: true },
|
|
]
|
}, {
|
label: '应付',
|
children: [
|
{ label: '运费', prop: 'pShippingFee', width: 120 },
|
{ label: '增值服务', prop: 'pServerFee', width: 120 },
|
{ label: '实报实销', prop: 'pActualFee', width: 120 },
|
{ label: '应付小计', prop: 'pTotal', width: 120 },
|
]
|
}, {
|
label: '应收',
|
children: [
|
{ label: '运费', prop: 'rShippingFee', width: 120 },
|
{ label: '增值服务', prop: 'rServerFee', width: 120 },
|
{ label: '实报实销', prop: 'rActualFee', width: 120 },
|
{ label: '应收小计', prop: 'rTotal', width: 120 },
|
]
|
}, {
|
label: '利润',
|
children: [
|
{ label: '毛利', prop: 'grossProfit', width: 120 },
|
{ label: '毛利率', prop: 'grossProfitRadio', width: 120 },
|
]
|
}, {
|
label: '备注',
|
children: [
|
{ label: '', prop: 'remark', width: 120 },
|
|
]
|
}
|
]
|
})
|
const itineraryTableOption = ref({
|
menu: false,
|
addBtn: false,
|
header: false, selection: false,
|
title: '历史行程',
|
column: {
|
dataSource: {
|
label: '数据来源',
|
display: false,
|
type: 'select', dataType: 'string', dicUrl: '/system/dict/data/type/data_source',
|
|
},
|
driverName: {
|
label: '主驾驶员名称',
|
display: true, disabled: true,
|
},
|
vehicleNumber: {
|
label: '车牌号码',
|
display: true, disabled: true,
|
|
},
|
tripType: {
|
label: '行程类型',
|
display: true,
|
type: 'select', dataType: 'string', dicUrl: '/system/dict/data/type/trip_type',
|
|
},
|
tripTime: {
|
label: '时间',
|
type: 'date', valueFormat: 'YYYY-MM-DD',
|
display: true,
|
},
|
odometer: {
|
label: '仪表里程',
|
display: true,
|
},
|
address: {
|
label: '具体地址',
|
display: true,
|
},
|
voucherUrl: {
|
label: '凭证',
|
display: true,
|
span: 24, dataType: 'string',
|
type: 'img',
|
|
},
|
|
}
|
})
|
|
const YSGenerateItemTableOption = ref({
|
menu: false,
|
add: false,
|
header: false,
|
selection: false,
|
rowKey: 'rowKey',
|
|
column: {
|
feeType: {
|
label: '费用类型',
|
type: 'radio', dataType: 'string', dicUrl: '/system/dict/data/type/receivable_fee_type',
|
|
},
|
feeName: {
|
label: '费用名称',
|
},
|
registerTime: {
|
label: '登记时间',
|
},
|
registerAmount: {
|
label: '登记金额',
|
},
|
currency: {
|
label: '币制',
|
type: 'radio', dataType: 'string', dicUrl: '/system/dict/data/type/sys_currency',
|
|
}
|
}
|
})
|
const YFGenerateTableOption = ref({
|
menu: false,
|
add: false,
|
header: false,
|
selection: false,
|
expand: true,
|
defaultExpandAll: true,
|
rowKey: 'id',
|
column: {
|
dispatchNo: {
|
label: '调度单号',
|
},
|
serviceProviderType: {
|
label: '服务商类型',
|
type: 'radio', dicUrl: '/system/dict/data/type/provider_type', dataType: 'string',
|
},
|
serviceProviderName: {
|
label: '服务商名稱',
|
},
|
payableAmount: {
|
label: '应付费用',
|
}
|
}
|
})
|
const YFGenerateItemTableOption = ref({
|
menu: false,
|
add: false,
|
header: false,
|
selection: false,
|
rowKey: 'rowKey',
|
|
column: {
|
feeType: {
|
label: '费用类型',
|
type: 'radio', dataType: 'string', dicUrl: '/system/dict/data/type/receivable_fee_type',
|
|
},
|
feeName: {
|
label: '费用名称',
|
},
|
registerTime: {
|
label: '登记时间',
|
},
|
registerAmount: {
|
label: '登记金额',
|
},
|
currency: {
|
label: '币制',
|
type: 'radio', dataType: 'string', dicUrl: '/system/dict/data/type/sys_currency',
|
|
}
|
}
|
})
|
const {
|
tableData,
|
pageF,
|
rowSave,
|
rowUpdate,
|
rowDel,
|
beforeOpen,
|
searchChange,
|
searchReset,
|
selectionChange,
|
onLoad,
|
currentChange,
|
sizeChange,
|
handleDelete,
|
handleExport,
|
handleUpdate,
|
refreshChange
|
} = usePagePlus({
|
form: form,
|
option: option,
|
queryParams: queryParams,
|
idKey: 'id',
|
page: page.value,
|
getListApi: listTmsDispatchFeeSummaryVi,
|
exportApi: exportTmsDispatchFeeSummaryVi,
|
handleUpdateFunc: () => {
|
crudRef.value.rowEdit(selectionList.value[0]);
|
},
|
handleSelectionChangeFunc: (selection: any) => {
|
selectionList.value = selection;
|
},
|
getBeginListFunc: (params = {}) => {
|
// 1. 日期转换
|
let newParams = <any>{ ...params };
|
if (newParams) {
|
// 1. 定义日期字段映射 (数组名 : 接口需要的前缀)
|
// 这样写的好处是:如果你有多个日期,直接在这里添加一行即可
|
const dateMap = {
|
orderTimeRange: 'orderTime',
|
};
|
|
// 2. 遍历处理日期
|
Object.keys(dateMap).forEach(arrayKey => {
|
const prefix = dateMap[arrayKey];
|
const range = newParams[arrayKey];
|
|
if (Array.isArray(range) && range.length > 0) {
|
// 赋值 Begin 和 End
|
newParams[`${prefix}Begin`] = range[0];
|
newParams[`${prefix}End`] = range[1];
|
}
|
|
// 【核心改动】:无论是否有值,处理完后都把原始的 Array 字段删掉
|
// 这样请求里就不会出现 confirmTimeRangeArray: [...]
|
delete newParams[arrayKey];
|
});
|
|
// 3. 通用清洗:删除所有空字符串、null 或 undefined 的其他字段
|
Object.keys(newParams).forEach(key => {
|
const val = newParams[key];
|
if (val === '' || val === null || val === undefined) {
|
delete newParams[key];
|
}
|
});
|
|
} else {
|
newParams = {};
|
}
|
|
return newParams;
|
|
},
|
})
|
const handleLogItinerary = (row: any) => {
|
optionType.value = 'view';
|
boxTableOption.value = itineraryTableOption.value;
|
listTmsTrip({ dispatchOrderId: row.dispatchId, pageNum: 1, pageSize: 999 }).then(res => {
|
boxTableData.value = res.rows || [];
|
pageF.open = true;
|
pageF.title = '查看行程';
|
})
|
}
|
|
const handleReceivableFeeView = (row: any) => {
|
optionType.value = 'receivableFee';
|
boxTableOption.value = YSGenerateItemTableOption.value;
|
getTmsReceivableFeeByDispatchNo(row.dispatchNo).then(res => {
|
form.value = res.data || {};
|
boxTableData.value = form.value.items || [];
|
pageF.open = true;
|
pageF.title = '应收费用明细';
|
|
})
|
}
|
const handlePayableFeeView = (row: any) => {
|
optionType.value = 'payableFee';
|
boxTableOption.value = YFGenerateTableOption.value;
|
boxItemTableOption.value = YFGenerateItemTableOption.value;
|
getTmsPayableFeeByDispatchNo(row.dispatchNo).then(res => {
|
boxTableData.value = res.data || [];
|
pageF.open = true;
|
pageF.title = '应付费用明细';
|
|
|
|
})
|
}
|
// 控制备注弹窗显示
|
const remarkBox = reactive({
|
open: false,
|
title: '备注记录',
|
content: '', // 新增备注的内容
|
list: [] // 备注历史列表
|
});
|
|
const activeRow = ref({}); // 当前操作的行
|
|
// 点击备注列触发
|
const handleRemarkClick = (row) => {
|
activeRow.value = row;
|
remarkBox.content = ''; // 清空输入框
|
remarkBox.open = true;
|
remarkBox.list = [
|
];
|
listDispatchFeeSummaryViLog({ headId: row.dispatchId }).then(res => remarkBox.list = res.rows || []);
|
// getRemarkHistory(row.id).then(res => remarkBox.list = res.data);
|
|
};
|
|
// 弹窗点击确定的逻辑
|
const submitRemark = () => {
|
if (!remarkBox.content) {
|
// return ElMessage.warning('请输入备注内容');
|
return proxy.$modal.msgWarning("请输入备注内容");
|
|
}
|
// 更新前端表格显示(实际应调用保存接口后刷新)
|
|
// 调用后端接口示例:
|
// saveRemark({ id: activeRow.value.id, remark: remarkBox.content }).then(...)
|
tmsDispatchFeeSummaryViNote({ dispatchId: activeRow.value.dispatchId, remark: remarkBox.content }).then((res) => {
|
if (res.code == 200) {
|
// remarkBox.open = false;
|
listDispatchFeeSummaryViLog({ headId: activeRow.value.dispatchId }).then((res1) => {
|
if (res1.code == 200) {
|
remarkBox.list = res1.rows || [];
|
remarkBox.content = ''; // 清空输入框
|
proxy.$modal.msgSuccess(res.msg);
|
|
}
|
});
|
// activeRow.value.remark = remarkBox.content; // 更新表格显示
|
}
|
// ElMessage.success('备注已更新');
|
});
|
};
|
|
</script>
|
<style scoped lang="scss">
|
.hot {
|
cursor: pointer;
|
text-align: left;
|
color: #409eff;
|
|
&:hover {
|
color: #f0ad4e;
|
}
|
}
|
</style>
|