Compare commits

...

6 Commits

Author SHA1 Message Date
xuyuncong
0cbc9c51c2 Merge branch 'rescue' of http://114.55.243.137:6688/dianliang/lanan-system-vue into rescue 2025-11-03 17:55:39 +08:00
xuyuncong
c23cebebe9 更新 2025-11-03 17:55:32 +08:00
0015298eda 11.03 2025-11-03 15:40:15 +08:00
xuyuncong
77beb80e4e Merge branch 'rescue' of http://114.55.243.137:6688/dianliang/lanan-system-vue into rescue 2025-10-31 10:43:05 +08:00
659ed62893 Merge branch 'master' into rescue 2025-10-31 10:17:24 +08:00
a4103b33d0 10.31 2025-10-31 10:11:53 +08:00
9 changed files with 1658 additions and 424 deletions

BIN
dist.zip Normal file

Binary file not shown.

View File

@ -184,6 +184,14 @@ export function channelList() {
}) })
} }
// 根据渠道ID查询来源列表
export function getSourcesByChannelId(channelId) {
return request({
url: `/rescue-channel-source/sources/${channelId}`,
method: 'get'
})
}
// 获取所有来源 // 获取所有来源
export function sourceList() { export function sourceList() {
return request({ return request({
@ -192,3 +200,13 @@ export function sourceList() {
}) })
} }
// 按角色编码拉员工
export function getStaffByRoleCode(tenantId, roleCode) {
return request({
url: `/company/staff/staffListByRoleCode`,
method: 'get',
params: { tenantId, code: roleCode }
})
}

50
src/api/rescue/passFee.js Normal file
View File

@ -0,0 +1,50 @@
// =============================================================
// 文件src/api/rescue/passFee.js
// 说明:过关费管理接口封装
// =============================================================
import request from '@/utils/request'
// 列表查询
export function listPassFee(query) {
return request({
url: '/system/passFee/list',
method: 'get',
params: query
})
}
// 新增
export function addPassFee(data) {
return request({
url: '/system/passFee',
method: 'post',
data
})
}
// 修改
export function updatePassFee(data) {
return request({
url: '/system/passFee',
method: 'put',
data
})
}
// 删除(支持批量)
export function delPassFee(ids) {
return request({
url: '/system/passFee/' + ids,
method: 'delete'
})
}
// 导出
export function exportPassFee(params) {
return request({
url: '/system/passFee/export',
method: 'get',
params,
responseType: 'blob'
})
}

View File

@ -98,6 +98,7 @@
<el-table-column label="服务顾问" align="center" prop="adviserName" width="100"/> <el-table-column label="服务顾问" align="center" prop="adviserName" width="100"/>
<el-table-column label="备注" align="center" prop="remark" width="180"/> <el-table-column label="备注" align="center" prop="remark" width="180"/>
<el-table-column label="所属门店" align="center" prop="corpId" width="180"/> <el-table-column label="所属门店" align="center" prop="corpId" width="180"/>
<el-table-column label="收款账号" align="center" v-if="TicketType === 'tp'" prop="receivablesAccount" width="180"/>
<el-table-column label="确认收款状态" v-if="TicketType === 'tp'" width="100"> <el-table-column label="确认收款状态" v-if="TicketType === 'tp'" width="100">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag v-if="scope.row.payConfirm == '1'" type="success">已确认</el-tag> <el-tag v-if="scope.row.payConfirm == '1'" type="success">已确认</el-tag>
@ -116,15 +117,23 @@
>查看 >查看
</el-button> </el-button>
<el-button v-hasPermi="['repair:tk:paid']" size="mini" type="text" icon="el-icon-finished" <el-button v-hasPermi="['repair:tk:paid']" size="mini" type="text" icon="el-icon-finished"
@click="handlePaid(scope.row)" v-if="TicketType === 'tu'" @click="handlePaid(scope.row)" v-if="TicketType === 'tu' && scope.row.settlementType != 'gz'"
>收款 >收款
</el-button> </el-button>
<el-button v-hasPermi="['repair:tk:paid']" size="mini" type="text" icon="el-icon-finished"
@click="handleAntiSettlement(scope.row)" v-if="TicketType === 'tu' && scope.row.settlementType != 'gz'"
>反结算
</el-button>
<el-button v-hasPermi="['repair:tk:paid']" size="mini" type="text" icon="el-icon-finished"
@click="handlePaid(scope.row)" v-if="TicketType === 'tu' && scope.row.settlementType == 'gz'"
>销账
</el-button>
<el-button v-hasPermi="['repair:tk:settlement']" size="mini" type="text" icon="el-icon-finished" <el-button v-hasPermi="['repair:tk:settlement']" size="mini" type="text" icon="el-icon-finished"
@click="handleSettlement(scope.row,'js')" v-if="TicketType === 'ts' && !scope.row.settlement" @click="handleSettlement(scope.row,'js')" v-if="TicketType === 'ts' && (!scope.row.settlement || scope.row.payStatus == '04')"
>结算 >结算
</el-button> </el-button>
<el-button v-hasPermi="['repair:tk:settlement:review']" size="mini" type="text" icon="el-icon-finished" <el-button v-hasPermi="['repair:tk:settlement:review']" size="mini" type="text" icon="el-icon-finished"
@click="handleSettlement(scope.row,'jssh')" v-if="TicketType === 'ts' && scope.row.settlement" @click="handleSettlement(scope.row,'jssh')" v-if="TicketType === 'ts' && scope.row.settlement && scope.row.payStatus != '04'"
>结算审核 >结算审核
</el-button> </el-button>
<!-- <el-button v-if="TicketType === 'tp'" size="mini" type="text" icon="el-icon-refresh-right"--> <!-- <el-button v-if="TicketType === 'tp'" size="mini" type="text" icon="el-icon-refresh-right"-->
@ -235,6 +244,24 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="1">
<el-col :span="24">
<el-form-item label="收款账号" prop="receivablesAccount">
<el-select
v-model="formData.receivablesAccount"
placeholder="请选择收款账号"
:loading="accountLoading"
>
<el-option
v-for="item in accountList"
:key="item.id"
:label="item.accountName + ' - ' + item.accountNumber"
:value="item.accountNumber">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="1"> <el-row :gutter="1">
<el-col :span="24"> <el-col :span="24">
<el-form-item label="收款备注" prop="remark"> <el-form-item label="收款备注" prop="remark">
@ -244,10 +271,10 @@
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
<template slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="doPaid"> </el-button> <el-button type="primary" @click="doPaid"> </el-button>
<el-button @click="dialogVisible = false"> </el-button> <el-button @click="dialogVisible = false"> </el-button>
</template> </div>
</el-dialog> </el-dialog>
<el-dialog :title="settlementType === 'jssh' ? '结算审核': '结算信息'" :visible.sync="dialogVisibleSettlement" <el-dialog :title="settlementType === 'jssh' ? '结算审核': '结算信息'" :visible.sync="dialogVisibleSettlement"
@ -288,8 +315,8 @@
<el-row :gutter="1"> <el-row :gutter="1">
<el-col :span="24"> <el-col :span="24">
<el-form-item label="支付方式" prop="payType"> <el-form-item label="结算方式" prop="settlementType">
<el-radio-group v-model="settlementFormData.payType" @change="handlePayTypeChange"> <el-radio-group v-model="settlementFormData.settlementType" @change="handlePayTypeChange">
<el-radio label="xj">现结</el-radio> <el-radio label="xj">现结</el-radio>
<el-radio label="gz">挂账</el-radio> <el-radio label="gz">挂账</el-radio>
</el-radio-group> </el-radio-group>
@ -297,7 +324,7 @@
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="1" v-if="settlementFormData.payType === 'gz'"> <el-row :gutter="1" v-if="settlementFormData.settlementType === 'gz'">
<el-col :span="24"> <el-col :span="24">
<el-form-item label="挂账单位" prop="chargeCompanyId"> <el-form-item label="挂账单位" prop="chargeCompanyId">
<el-select <el-select
@ -315,6 +342,7 @@
:value="item.id"> :value="item.id">
</el-option> </el-option>
</el-select> </el-select>
<el-button type="text" @click="handleAddChargeCompany">申请挂账单位</el-button>
<el-button type="text" @click="handleAddChargeCompany">申请新增</el-button> <el-button type="text" @click="handleAddChargeCompany">申请新增</el-button>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -432,6 +460,17 @@
<el-input v-model="chargeCompanyForm.remark" placeholder="请输入备注" type="textarea" /> <el-input v-model="chargeCompanyForm.remark" placeholder="请输入备注" type="textarea" />
</el-form-item> </el-form-item>
<!-- 资料上传 -->
<el-form-item label="资料上传" prop="file">
<el-upload class="upload-demo" :action="uploadFileUrl" :multiple="true" :before-upload="beforeUpload"
:file-list="uploadFileList" :on-success="handleUploadSuccess" :on-error="handleUploadError"
:on-remove="handleRemove" :headers="headers" name="file" accept=".pdf,.doc,.docx,.txt,.jpg,.jpeg,.png"
:on-preview="previewFile">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">支持多文件上传文件格式pdfdocdocxtxtjpgjpegpng大小不超过20MB</div>
</el-upload>
</el-form-item>
<!-- 隐藏字段 --> <!-- 隐藏字段 -->
<el-form-item label="" prop="status" style="display: none;"> <el-form-item label="" prop="status" style="display: none;">
<el-input v-model="chargeCompanyForm.status" type="hidden" /> <el-input v-model="chargeCompanyForm.status" type="hidden" />
@ -440,6 +479,10 @@
<el-form-item label="" prop="systemCode" style="display: none;"> <el-form-item label="" prop="systemCode" style="display: none;">
<el-input v-model="chargeCompanyForm.systemCode" type="hidden" /> <el-input v-model="chargeCompanyForm.systemCode" type="hidden" />
</el-form-item> </el-form-item>
<el-form-item label="" prop="file" style="display: none;">
<el-input v-model="chargeCompanyForm.file" type="hidden" />
</el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
@ -447,6 +490,49 @@
<el-button @click="cancelChargeCompany"> </el-button> <el-button @click="cancelChargeCompany"> </el-button>
</div> </div>
</el-dialog> </el-dialog>
<!-- 文件预览对话框 -->
<el-dialog :title="'文件预览(' + selectFile.fileName + ''" :visible.sync="isShowFile" width="70%" append-to-body>
<!-- 全屏按钮 -->
<el-button
class="fullscreen-btn"
icon="el-icon-full-screen"
size="mini"
@click="toggleFullScreen"
style="position: absolute; top: 10px; right: 100px; z-index: 10;"
>
全屏
</el-button>
<div class="preview-container" ref="previewContainer">
<!-- 音频预览 -->
<audio v-if="isAudioType" class="preview-iframe" controls>
<source :src="getPreviewFilePath(selectFile)"/>
</audio>
<!-- 图片预览 -->
<img
v-if="isImageType"
:src="getPreviewFilePath(selectFile)"
class="preview-iframe"
style="max-width: 100%; max-height: 80vh; object-fit: contain;"
>
<!-- Office文档预览 -->
<iframe
v-if="!isAudioType && !isImageType && !isPdfType && !isTxtType"
:src="fileUrl"
frameborder="0"
class="preview-iframe"
>
</iframe>
<!-- PDF和TXT预览 -->
<iframe
v-if="isPdfType || isTxtType"
:src="getPreviewFilePath(selectFile)"
frameborder="0"
class="preview-iframe"
>
</iframe>
</div>
</el-dialog>
</div> </div>
</template> </template>
@ -459,16 +545,20 @@ import {
setTicketsSettlement, setTicketsSettlement,
settlementReview, settlementReview,
getSettlement, getSettlement,
payConfirm payConfirm,
setTicketsAntiSettlement
} from '@/api/repair/tickets/Tickets' } from '@/api/repair/tickets/Tickets'
import TicketsShow from "@/views/repair/tickets/Components/TicketsShow.vue"; import TicketsShow from "@/views/repair/tickets/Components/TicketsShow.vue";
import {getByNameAndMobile} from "@/api/base/customer"; import {getByNameAndMobile} from "@/api/base/customer";
import EditTickets from "@/views/repair/tickets/form/EditTickets.vue"; import EditTickets from "@/views/repair/tickets/form/EditTickets.vue";
import { getChargeCompanyList, addChargeCompany } from '@/views/base/chargeCompany/api/chargeCompanyApi' import { getChargeCompanyList, addChargeCompany } from '@/views/base/chargeCompany/api/chargeCompanyApi'
import { getAccounts } from '@/views/company/account/api/accountApi'
import { getAccessToken } from "@/utils/auth"
import ImagePreview from '@/components/ImagePreview';
export default { export default {
name: "TicketTable", name: "TicketTable",
components: {EditTickets, TicketsShow}, components: {EditTickets, TicketsShow, ImagePreview},
props: { props: {
TicketType: { TicketType: {
type: String, type: String,
@ -490,48 +580,66 @@ export default {
data() { data() {
return { return {
loading: false, loading: false,
showLoading: false, // showLoading: false,
formData: { formData: {
id: null, id: null,
ticketsStatus: null, ticketsStatus: null,
billingRemark: null, billingRemark: null,
payType: null, payType: null,
isPaid: '1', isPaid: '1',
remark: '' remark: '',
receivablesAccount: null
}, },
accountList: [],
accountLoading: false,
settlementFormData: { settlementFormData: {
actualMoney: 0, actualMoney: 0,
discountType: this.defaultDiscountType, discountType: this.defaultDiscountType,
discount: 0, discount: 0,
payType: 'xj', // settlementType: 'xj',
chargeCompanyId: null chargeCompanyId: null
}, },
chargeCompanyList: [], // chargeCompanyList: [],
searchLoading: false, // searchLoading: false,
//
chargeCompanyForm: { chargeCompanyForm: {
companyName: '', companyName: '',
contactPerson: '', contactPerson: '',
contactPhone: '', contactPhone: '',
address: '', address: '',
status: false, // status: false,
systemCode: 'repair', systemCode: 'repair',
remark: '' remark: '',
file: ''
}, },
uploadFileList: [],
viewFileUrl: process.env.VUE_APP_FILE_API,
uploadFileUrl: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload",
headers: { Authorization: "Bearer " + getAccessToken() },
uploadingCount: 0,
isShowFile: false,
selectFile: {
fileName: '',
filePath: '',
isImage: false
},
fileUrl: '',
isSyncingFromParent: false,
chargeCompanyRules: { chargeCompanyRules: {
companyName: [{ required: true, message: '单位名称不能为空', trigger: 'blur' }], companyName: [{ required: true, message: '单位名称不能为空', trigger: 'blur' }],
contactPerson: [{ required: true, message: '联系人不能为空', trigger: 'blur' }], contactPerson: [{ required: true, message: '联系人不能为空', trigger: 'blur' }],
contactPhone: [{ required: true, message: '联系电话不能为空', trigger: 'blur' }] contactPhone: [{ required: true, message: '联系电话不能为空', trigger: 'blur' }]
}, },
chargeCompanyDialogVisible: false, // chargeCompanyDialogVisible: false,
formRules: { formRules: {
payType: [{required: true, message: '支付方式不能为空', trigger: 'blur'}], payType: [{required: true, message: '支付方式不能为空', trigger: 'blur'}],
isPaid: [{required: true, message: '请选择是否支付', trigger: 'blur'}], isPaid: [{required: true, message: '请选择是否支付', trigger: 'blur'}],
receivablesAccount: [{required: true, message: '请选择收款账号', trigger: 'blur'}],
remark: [{required: false, message: '收款备注不能为空', trigger: 'blur'}] remark: [{required: false, message: '收款备注不能为空', trigger: 'blur'}]
}, },
settlementFormRules: { settlementFormRules: {
discountType: [{required: true, message: '优惠类型不能为空', trigger: 'blur'}], discountType: [{required: true, message: '优惠类型不能为空', trigger: 'blur'}],
discount: [{required: true, message: '优惠不能为空', trigger: 'blur'}] discount: [{required: true, message: '优惠不能为空', trigger: 'blur'}],
settlementType: [{required: true, message: '结算方式不能为空', trigger: 'blur'}]
}, },
dialogVisible: false, dialogVisible: false,
dialogVisibleSettlement: false, dialogVisibleSettlement: false,
@ -547,7 +655,6 @@ export default {
value: '2' value: '2'
} }
], ],
//
confirmPaymentVisible: false, confirmPaymentVisible: false,
confirmPaymentLoading: false, confirmPaymentLoading: false,
confirmPaymentSubmitLoading: false, confirmPaymentSubmitLoading: false,
@ -561,14 +668,14 @@ export default {
paidTime: '', paidTime: '',
payTypeLabel: '', payTypeLabel: '',
payConfirm: '', payConfirm: '',
payConfirmRemark: '' payConfirmRemark: '',
payTime: ''
}, },
confirmPaymentRules: { confirmPaymentRules: {
payConfirm: [ payConfirm: [
{ required: true, message: '请选择确认状态', trigger: 'change' } { required: true, message: '请选择确认状态', trigger: 'change' }
] ]
}, },
//
confirmStatusOptions: [ confirmStatusOptions: [
{ {
value: '1', value: '1',
@ -581,8 +688,29 @@ export default {
] ]
} }
}, },
computed: {
isImageType() {
if (!this.selectFile.fileName) return false;
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp'];
const extension = this.selectFile.fileName.substring(this.selectFile.fileName.lastIndexOf('.')).toLowerCase();
return imageExtensions.includes(extension);
},
isAudioType() {
if (!this.selectFile.fileName) return false;
const audioExtensions = ['.mp3', '.wav', '.ogg', '.flac', '.aac'];
const extension = this.selectFile.fileName.substring(this.selectFile.fileName.lastIndexOf('.')).toLowerCase();
return audioExtensions.includes(extension);
},
isPdfType() {
if (!this.selectFile.fileName) return false;
return this.selectFile.fileName.toLowerCase().endsWith('.pdf');
},
isTxtType() {
if (!this.selectFile.fileName) return false;
return this.selectFile.fileName.toLowerCase().endsWith('.txt');
}
},
watch: { watch: {
//
'formData.isPaid': { 'formData.isPaid': {
handler(newVal) { handler(newVal) {
this.updateFormRules(newVal); this.updateFormRules(newVal);
@ -593,14 +721,10 @@ export default {
methods: { methods: {
handleShow(row) { handleShow(row) {
//
this.showLoading = true; this.showLoading = true;
// open
this.$refs.ticketsShow.open(row).then(() => { this.$refs.ticketsShow.open(row).then(() => {
//
this.showLoading = false; this.showLoading = false;
}).catch(() => { }).catch(() => {
//
this.showLoading = false; this.showLoading = false;
}); });
}, },
@ -624,9 +748,26 @@ export default {
} catch { } catch {
} }
}, },
/** async handleAntiSettlement(row) {
* 打印 this.$prompt('反结算备注', '提示', {
*/ confirmButtonText: '确定',
cancelButtonText: '取消',
}).then(({value}) => {
this.formData.id = row.id
this.formData['remark'] = value
this.formData.ticketsStatus = "03"
this.doAntiSettlement()
}).catch(() => {
})
},
async doAntiSettlement() {
try {
await setTicketsAntiSettlement(this.formData)
this.$modal.msgSuccess("反结算成功")
this.$emit("setAntiSettlement")
} catch {
}
},
async handlePrint(row) { async handlePrint(row) {
window.open(process.env.VUE_APP_BASE_API + '/admin-api/repair/tickets/print/' + row.id) window.open(process.env.VUE_APP_BASE_API + '/admin-api/repair/tickets/print/' + row.id)
}, },
@ -640,21 +781,17 @@ export default {
} }
}, },
jisuan() { jisuan() {
//
if (this.settlementFormData.discountType === '1') { if (this.settlementFormData.discountType === '1') {
this.settlementFormData.actualMoney = this.settlementFormData.money - this.settlementFormData.discount this.settlementFormData.actualMoney = this.settlementFormData.money - this.settlementFormData.discount
} else if (this.settlementFormData.discountType === '2') { } else if (this.settlementFormData.discountType === '2') {
//
this.settlementFormData.actualMoney = this.settlementFormData.money * (1 - this.settlementFormData.discount / 100) this.settlementFormData.actualMoney = this.settlementFormData.money * (1 - this.settlementFormData.discount / 100)
this.settlementFormData.actualMoney = parseFloat(this.settlementFormData.actualMoney).toFixed(2) this.settlementFormData.actualMoney = parseFloat(this.settlementFormData.actualMoney).toFixed(2)
} }
console.log(this.settlementFormData) console.log(this.settlementFormData)
}, },
//
async handlePayTypeChange(value) { async handlePayTypeChange(value) {
if (value === 'gz') { if (value === 'gz') {
// systemCoderepair
try { try {
const res = await getChargeCompanyList({ const res = await getChargeCompanyList({
status: 1, status: 1,
@ -668,7 +805,6 @@ export default {
} }
}, },
//
async searchChargeCompany(query) { async searchChargeCompany(query) {
if (query !== '') { if (query !== '') {
this.searchLoading = true; this.searchLoading = true;
@ -686,7 +822,6 @@ export default {
this.searchLoading = false; this.searchLoading = false;
} }
} else { } else {
//
try { try {
const res = await getChargeCompanyList({ const res = await getChargeCompanyList({
status: 1, status: 1,
@ -700,7 +835,6 @@ export default {
} }
}, },
//
handlePaid(row) { handlePaid(row) {
this.formData = { this.formData = {
id: null, id: null,
@ -708,16 +842,33 @@ export default {
billingRemark: null, billingRemark: null,
payType: null, payType: null,
isPaid: '1', isPaid: '1',
remark: '' remark: '',
receivablesAccount: null
} }
this.formData['id'] = row.id this.formData['id'] = row.id
this.formData['ticketsStatus'] = '02' this.formData['ticketsStatus'] = '02'
this.dialogVisible = true this.dialogVisible = true
this.checkIsHangAccount(row) this.checkIsHangAccount(row)
this.getSettlement(row) this.getSettlement(row)
this.loadAccountList()
},
async loadAccountList() {
this.accountLoading = true;
try {
const res = await getAccounts({
status: 1,
systemCode: 'repair'
});
this.accountList = res.data.records || [];
} catch (err) {
console.error('获取收款账号列表失败:', err);
this.$modal.msgError('获取收款账号列表失败');
} finally {
this.accountLoading = false;
}
}, },
//
handleSettlement(row, type) { handleSettlement(row, type) {
this.settlementType = type this.settlementType = type
if (type == 'js') { if (type == 'js') {
@ -725,8 +876,8 @@ export default {
ticketId: row.id, ticketId: row.id,
money: row.totalPrice, money: row.totalPrice,
actualMoney: row.totalPrice, actualMoney: row.totalPrice,
discountType: this.defaultDiscountType, // discountType: this.defaultDiscountType,
discount: 0 // discount: 0
} }
} else if (type === 'jssh') { } else if (type === 'jssh') {
this.settlementFormData = row.settlement this.settlementFormData = row.settlement
@ -774,8 +925,8 @@ export default {
try { try {
await this.$refs['formRefSettlement'].validate() await this.$refs['formRefSettlement'].validate()
if (this.settlementType === 'jssh') { if (this.settlementType === 'jssh') {
console.log('执行借宿那审核');
await settlementReview(this.settlementFormData) await settlementReview(this.settlementFormData)
} else { } else {
await setTicketsSettlement(this.settlementFormData) await setTicketsSettlement(this.settlementFormData)
@ -783,7 +934,8 @@ export default {
this.$modal.msgSuccess("提交成功") this.$modal.msgSuccess("提交成功")
this.dialogVisibleSettlement = false this.dialogVisibleSettlement = false
this.$emit("setVoid") this.$emit("setVoid")
} catch { } catch (error) {
console.error('Error in settlement process:', error);
} }
}, },
getDictDataByCode(code) { getDictDataByCode(code) {
@ -813,7 +965,6 @@ export default {
} }
}, },
//
handleConfirmPayment(row) { handleConfirmPayment(row) {
this.resetConfirmPaymentForm() this.resetConfirmPaymentForm()
this.confirmPaymentForm = { this.confirmPaymentForm = {
@ -822,17 +973,16 @@ export default {
userName: row.userName, userName: row.userName,
carNo: row.carNo, carNo: row.carNo,
totalPrice: row.totalPrice, totalPrice: row.totalPrice,
paidAmount: row.paidAmount || row.totalPrice, // paidAmount: row.paidAmount || row.totalPrice,
paidTime: row.paidTime || '', // paidTime: row.paidTime || '',
payTypeLabel: this.getPayTypeLabel(row.payType), // payTypeLabel: this.getPayTypeLabel(row.payType),
payConfirm: row.payConfirm || '', // payConfirm: row.payConfirm || '',
payConfirmRemark: row.payConfirmRemark || '', // payConfirmRemark: row.payConfirmRemark || '',
payTime: row.payTime || '', // payTime: row.payTime || '',
} }
this.confirmPaymentVisible = true this.confirmPaymentVisible = true
}, },
//
getPayTypeLabel(payType) { getPayTypeLabel(payType) {
if (!payType) return '' if (!payType) return ''
const payTypes = this.getDictDataByCode(this.DICT_TYPE.REPAIR_PAY_TYPE) const payTypes = this.getDictDataByCode(this.DICT_TYPE.REPAIR_PAY_TYPE)
@ -840,55 +990,47 @@ export default {
return payTypeItem ? payTypeItem.label : '' return payTypeItem ? payTypeItem.label : ''
}, },
//
async submitConfirmPayment() { async submitConfirmPayment() {
this.$refs['confirmPaymentForm'].validate(async valid => { this.$refs['confirmPaymentForm'].validate(async valid => {
if (valid) { if (valid) {
this.confirmPaymentSubmitLoading = true this.confirmPaymentSubmitLoading = true
//
const data = { const data = {
id: this.confirmPaymentForm.id, id: this.confirmPaymentForm.id,
payConfirm: this.confirmPaymentForm.payConfirm, payConfirm: this.confirmPaymentForm.payConfirm,
payConfirmRemark: this.confirmPaymentForm.payConfirmRemark payConfirmRemark: this.confirmPaymentForm.payConfirmRemark
} }
// API
// API使
await payConfirm(data) await payConfirm(data)
this.$modal.msgSuccess("确认收款成功") this.$modal.msgSuccess("确认收款成功")
this.confirmPaymentSubmitLoading = false this.confirmPaymentSubmitLoading = false
this.confirmPaymentVisible = false this.confirmPaymentVisible = false
this.$emit("setVoid") // this.$emit("setVoid")
} }
}) })
}, },
//
cancelConfirmPayment() { cancelConfirmPayment() {
this.confirmPaymentVisible = false this.confirmPaymentVisible = false
this.$refs['confirmPaymentForm'].resetFields() this.$refs['confirmPaymentForm'].resetFields()
}, },
//
resetConfirmPaymentForm() { resetConfirmPaymentForm() {
this.confirmPaymentSubmitLoading = false this.confirmPaymentSubmitLoading = false
this.$refs['confirmPaymentForm'] && this.$refs['confirmPaymentForm'].resetFields() this.$refs['confirmPaymentForm'] && this.$refs['confirmPaymentForm'].resetFields()
}, },
//
updateFormRules(isPaid) { updateFormRules(isPaid) {
if (isPaid === '0') { if (isPaid === '0') {
//
this.formRules.payType = [{required: false, message: '支付方式不能为空', trigger: 'blur'}]; this.formRules.payType = [{required: false, message: '支付方式不能为空', trigger: 'blur'}];
this.formRules.receivablesAccount = [{required: false, message: '请选择收款账号', trigger: 'blur'}];
this.formRules.remark = [{required: true, message: '收款备注不能为空', trigger: 'blur'}]; this.formRules.remark = [{required: true, message: '收款备注不能为空', trigger: 'blur'}];
} else { } else {
//
this.formRules.payType = [{required: true, message: '支付方式不能为空', trigger: 'blur'}]; this.formRules.payType = [{required: true, message: '支付方式不能为空', trigger: 'blur'}];
this.formRules.receivablesAccount = [{required: true, message: '请选择收款账号', trigger: 'blur'}];
this.formRules.remark = [{required: false, message: '收款备注不能为空', trigger: 'blur'}]; this.formRules.remark = [{required: false, message: '收款备注不能为空', trigger: 'blur'}];
} }
//
this.$nextTick(() => { this.$nextTick(() => {
if (this.$refs.formRef) { if (this.$refs.formRef) {
this.$refs.formRef.clearValidate(); this.$refs.formRef.clearValidate();
@ -896,32 +1038,33 @@ export default {
}); });
}, },
//
handleAddChargeCompany() { handleAddChargeCompany() {
//
this.chargeCompanyForm = { this.chargeCompanyForm = {
companyName: '', companyName: '',
contactPerson: '', contactPerson: '',
contactPhone: '', contactPhone: '',
address: '', address: '',
status: false, // status: false,
systemCode: 'repair', systemCode: 'repair',
remark: '' remark: '',
file: ''
}; };
this.uploadFileList = [];
this.chargeCompanyDialogVisible = true; this.chargeCompanyDialogVisible = true;
}, },
//
async submitChargeCompany() { async submitChargeCompany() {
this.$refs.chargeCompanyFormRef.validate(async (valid) => { this.$refs.chargeCompanyFormRef.validate(async (valid) => {
if (valid) { if (valid) {
try { try {
const fileField = this.ensureFileField();
console.log('提交时的file字段值:', fileField);
await addChargeCompany(this.chargeCompanyForm); await addChargeCompany(this.chargeCompanyForm);
this.$modal.msgSuccess('挂账单位申请成功,请等待审核'); this.$modal.msgSuccess('挂账单位申请成功,请等待审核');
this.chargeCompanyDialogVisible = false; this.chargeCompanyDialogVisible = false;
// if (this.settlementFormData.settlementType === 'gz') {
if (this.settlementFormData.payType === 'gz') { await this.searchChargeCompany('');
await this.searchChargeCompany(''); //
} }
} catch (err) { } catch (err) {
console.error('新增挂账单位失败:', err); console.error('新增挂账单位失败:', err);
@ -931,10 +1074,157 @@ export default {
}); });
}, },
//
cancelChargeCompany() { cancelChargeCompany() {
this.chargeCompanyDialogVisible = false; this.chargeCompanyDialogVisible = false;
this.$refs.chargeCompanyFormRef && this.$refs.chargeCompanyFormRef.resetFields(); this.$refs.chargeCompanyFormRef && this.$refs.chargeCompanyFormRef.resetFields();
this.uploadFileList = [];
},
beforeUpload(file) {
const isLt20M = file.size / 1024 / 1024 < 20
if (!isLt20M) {
this.$message.error('上传文件大小不能超过 20MB!')
return false
}
if (this.uploadingCount === 0) {
this.$modal.loading("正在上传文件,请稍候...")
}
this.uploadingCount++
return true
},
handleUploadSuccess(res, file, fileList) {
this.uploadingCount--
console.log('上传成功响应:', res);
const originalFileName = file.name || '未知文件名';
const index = this.uploadFileList.findIndex(item => item.uid === file.uid)
if (index > -1) {
const filePath = res && res.code === 0 && res.data ? res.data : file.name
this.uploadFileList[index].url = filePath
this.uploadFileList[index].status = 'success'
this.uploadFileList[index].name = originalFileName
this.uploadFileList[index].fileName = originalFileName
this.uploadFileList[index].filePath = filePath
this.uploadFileList[index].isImage = this.isImageExtension(originalFileName)
} else {
const filePath = res && res.code === 0 && res.data ? res.data : file.name
this.uploadFileList.push({
uid: file.uid,
name: originalFileName,
fileName: originalFileName,
url: filePath,
filePath: filePath,
status: 'success',
isImage: this.isImageExtension(originalFileName)
})
}
this.updateFilePaths()
if (this.uploadingCount === 0) {
this.$modal.closeLoading()
}
},
handleUploadError(err, file, fileList) {
this.uploadingCount--
this.$message.error('文件上传失败,请重试')
if (this.uploadingCount === 0) {
this.$modal.closeLoading()
}
},
handleRemove(file, fileList) {
this.uploadFileList = fileList
this.updateFilePaths()
},
updateFilePaths() {
const paths = this.uploadFileList
.filter(file => file && (file.status === 'success' || file.url))
.map(file => {
const filePath = file.url || file.name || ''
if (filePath && this.viewFileUrl && filePath.includes(this.viewFileUrl)) {
return filePath.replace(this.viewFileUrl, '')
}
return filePath
})
const filePathStr = paths.join(',') || ''
this.chargeCompanyForm.file = filePathStr
console.log('更新后的file字段值:', filePathStr)
},
isImageExtension(fileName) {
if (!fileName) return false;
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp'];
const extension = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
return imageExtensions.includes(extension);
},
getPreviewFilePath(file) {
if (!file || !file.filePath && !file.url) return '';
const filePath = file.filePath || file.url || '';
if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
return filePath;
}
return `${this.viewFileUrl || process.env.VUE_APP_BASE_API}${filePath}`;
},
previewFile(file) {
console.log('预览文件:', file);
this.selectFile = {
fileName: file.name || file.fileName || '未知文件',
filePath: file.url || file.filePath || '',
isImage: file.isImage || this.isImageExtension(file.name || file.fileName || '')
};
if (!this.isImageExtension(this.selectFile.fileName) && !this.isPdfType && !this.isTxtType && !this.isAudioType) {
const fileUrl = this.getPreviewFilePath(this.selectFile);
this.fileUrl = `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(fileUrl)}`;
}
this.isShowFile = true;
},
toggleFullScreen() {
const container = this.$refs.previewContainer;
if (!container) return;
if (!document.fullscreenElement) {
container.requestFullscreen().catch(err => {
console.error(`全屏切换失败: ${err.message}`);
});
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
},
extractFileName(path) {
if (!path) return '未知文件';
let cleanPath = path;
if (cleanPath.includes('http://') || cleanPath.includes('https://')) {
const urlObj = new URL(cleanPath);
cleanPath = urlObj.pathname;
}
const parts = cleanPath.split('/');
return parts[parts.length - 1] || '未知文件';
},
ensureFileField() {
this.updateFilePaths()
return this.chargeCompanyForm.file
} }
} }
} }
@ -966,4 +1256,28 @@ export default {
color: #409EFF; color: #409EFF;
margin-bottom: 10px; margin-bottom: 10px;
} }
/* 文件预览样式 */
.preview-container {
position: relative;
width: 100%;
height: 600px;
overflow: auto;
}
.preview-iframe {
width: 100%;
height: 100%;
border: none;
}
.fullscreen-btn {
background: #409EFF;
color: white;
border: none;
}
.fullscreen-btn:hover {
background: #66b1ff;
color: white;
}
</style> </style>

View File

@ -9,6 +9,15 @@ export function listRefuelRecord(query) {
}) })
} }
// 查询加油费用汇总
export function listRefuelSummary(query) {
return request({
url: '/rescue/refuelRecord/summary',
method: 'get',
params: query
})
}
// 查询加油记录详细 // 查询加油记录详细
export function getRefuelRecord(id) { export function getRefuelRecord(id) {
return request({ return request({

View File

@ -0,0 +1,496 @@
<template>
<section class="statistics-pc">
<!-- 顶部返回 + 标题 -->
<el-page-header class="top-page-header" @back="goBack" content="业务管理统计" />
<!-- ========= 工单统计 ========= -->
<el-card class="block-card" shadow="hover">
<div class="card-head">
工单统计
<el-tabs v-model="orderTab" @tab-click="onOrderTabClick" class="inline-tabs" size="small">
<el-tab-pane v-for="(t, i) in timeTabs" :key="i" :label="t" :name="tabMap[i]" />
</el-tabs>
</div>
<!-- 自定义日期选择 -->
<el-date-picker v-if="orderTab === 'more'" v-model="orderRange" type="daterange" range-separator=""
start-placeholder="开始日期" end-placeholder="结束日期" @change="fetchOrderStats" class="full-width-picker" />
<el-row :gutter="20" class="summary-row">
<el-col :span="6" v-for="box in orderBoxes" :key="box.key">
<el-card class="summary-box">
<div class="box-title">
<span>{{ box.label }}</span>
</div>
<div class="box-value">{{ countNum[box.key] || 0 }}</div>
</el-card>
</el-col>
</el-row>
</el-card>
<!-- ========= 业务渠道统计 ========= -->
<el-card class="block-card" shadow="hover">
<div class="card-head">
业务渠道统计
<el-tabs v-model="channelTab" @tab-click="onChannelTabClick" class="inline-tabs" size="small">
<el-tab-pane v-for="(t, i) in timeTabs" :key="i" :label="t" :name="tabMap[i]" />
</el-tabs>
</div>
<!-- 日期选择 -->
<el-date-picker v-if="channelTab === 'more'" v-model="customRange" type="daterange" range-separator=""
start-placeholder="开始日期" end-placeholder="结束日期" @change="fetchChannelStats" class="full-width-picker" />
<!-- el-table row-key & type='expand' -->
<el-table v-loading="channelLoading" :data="channelStats" border stripe row-key="__rowKey" :height="360" class="tight-table">
<!-- 展开列 -->
<el-table-column type="expand">
<!-- slot-scope 拿到 row.sources再渲染内部小表格 -->
<template #default="props">
<el-table :data="props.row.sources" border size="mini" style="margin:6px 0 6px 40px">
<el-table-column prop="source" label="来源名称" />
<el-table-column prop="yjdNum" label="已接单" width="80" />
<el-table-column prop="jyzNum" label="救援中" width="80" />
<el-table-column prop="ywcNum" label="已完成" width="80" />
<el-table-column prop="dqcNum" label="待取车" width="80" />
<el-table-column prop="receivable" label="应收款(元)" width="110" />
<el-table-column prop="receivedMoney" label="已收款(元)" width="110" />
<el-table-column prop="dskNum" label="待收款(元)" width="110" />
<el-table-column label="操作" width="100">
<template #default="sc">
<el-button type="text" size="mini"
@click="viewOrderList({ channel: props.row.channel, source: sc.row.source }, channelTab, customRange)">
查看
</el-button>
</template>
</el-table-column>
</el-table>
</template>
</el-table-column>
<!-- 渠道汇总列 -->
<el-table-column prop="channel" label="渠道名称" fixed />
<el-table-column prop="yjdNum" label="已接单" width="90" />
<el-table-column prop="jyzNum" label="救援中" width="90" />
<el-table-column prop="ywcNum" label="已完成" width="90" />
<el-table-column prop="dqcNum" label="待取车" width="90" />
<el-table-column prop="receivable" label="应收款(元)" width="110" />
<el-table-column prop="receivedMoney" label="已收款(元)" width="110" />
<el-table-column prop="dskNum" label="待收款(元)" width="110" />
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<el-button type="primary" size="mini" @click="viewOrderList({ channel: scope.row.channel }, channelTab, customRange)">
查看
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- ========= 业务管理统计 ========= -->
<el-card class="block-card" shadow="hover">
<div class="card-head">
业务管理统计
<el-tabs v-model="bmTab" @tab-click="onBmTabClick" class="inline-tabs" size="small">
<el-tab-pane v-for="(t, i) in timeTabs" :key="i" :label="t" :name="tabMap[i]" />
</el-tabs>
</div>
<el-date-picker v-if="bmTab === 'more'" v-model="bmRange" type="daterange" range-separator=""
start-placeholder="开始日期" end-placeholder="结束日期" @change="fetchBmStats" class="full-width-picker" />
<el-collapse v-model="bmActiveKeys" accordion>
<el-collapse-item v-for="cat in bmCategories" :key="cat" :name="cat" :title="bmTitleMap[cat] || cat">
<el-row :gutter="10">
<el-col :span="12" v-for="item in bmStats[cat]" :key="item.name">
<div class="bm-item">
<span class="bm-key">{{ item.name }}</span>
<span class="bm-val">{{ item.count }}</span>
</div>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
</el-card>
<!-- ========= 财务统计 ========= -->
<el-card class="block-card" shadow="hover">
<div class="card-head">
财务统计
<el-tabs v-model="financeTab" @tab-click="onFinanceTabClick" class="inline-tabs" size="small">
<el-tab-pane v-for="(t, i) in timeTabs" :key="i" :label="t" :name="tabMap[i]" />
</el-tabs>
</div>
<el-date-picker v-if="financeTab === 'more'" v-model="financeRange" type="daterange" range-separator=""
start-placeholder="开始日期" end-placeholder="结束日期" @change="fetchFinanceStats" class="full-width-picker" />
<el-row :gutter="20" class="summary-row">
<el-col :span="8" v-for="card in financeCards" :key="card.key">
<el-card class="finance-box" shadow="always">
<div class="finance-title">{{ card.title }}</div>
<p class="finance-money">{{ (financeStats[card.key].money / 100).toFixed(2) }}</p>
<p class="finance-count">数量{{ financeStats[card.key].count }}</p>
<el-button type="text" size="mini" @click="viewOrderList(card.queryExtra, financeTab, financeRange)">查看明细</el-button>
</el-card>
</el-col>
</el-row>
</el-card>
</section>
</template>
<script>
import request from '@/utils/request'
import dayjs from 'dayjs'
export default {
name: 'businissStatistics',
data() {
return {
// tab
timeTabs: ['本日', '本月', '本年', '全部', '自定义'],
tabMap: ['day', 'month', 'year', 'all', 'more'],
/* ========== 工单统计 ========== */
orderTab: 'day',
orderRange: [],
countNum: { yjdNum: 0, jyzNum: 0, ywcNum: 0, dqcNum: 0 },
orderBoxes: [
{ key: 'yjdNum', label: '已接单', status: '' },
{ key: 'jyzNum', label: '救援中', status: 1 },
{ key: 'ywcNum', label: '已完成', status: 5 },
{ key: 'dqcNum', label: '待取车', status: 3 },
],
/* ========== 渠道统计 ========== */
channelTab: 'day',
customRange: [],
channelStats: [],
channelOptions: [],
channelLoading: false,
/* ========== 业务管理统计 ========== */
bmTab: 'day',
bmRange: [],
bmStats: {},
bmCategories: [],
bmActiveKeys: [],
bmTitleMap: {
rescueType: '救援类型统计',
faultDistrict: '故障地点统计',
carBrand: '车辆品牌统计',
carType: '车辆类型统计',
downDestination: '下车地统计',
transferReason: '移交事由统计',
newEnergy: '是否新能源统计',
rescueNeeds: '救援需求统计',
feeType: '收费类型统计',
kouChe: '是否扣车统计',
},
/* ========== 财务统计 ========== */
financeTab: 'day',
financeRange: [],
financeStats: {
receivable: { money: 0, count: 0 },
received: { money: 0, count: 0 },
pending: { money: 0, count: 0 },
},
financeCards: [
{ key: 'receivable', title: '应收款统计', queryExtra: {} },
{ key: 'received', title: '已收款统计', queryExtra: { ifConfirmPay: 1 } },
{ key: 'pending', title: '待收款统计', queryExtra: { ifConfirmPay: 0 } },
],
};
},
created() {
this.fetchAll();
this.fetchChannelOptions().then(() => this.fetchChannelStats())
},
methods: {
/* ====== 通用 ====== */
goBack() {
this.$router.back();
},
buildTimeParams(tab, range = []) {
if (tab !== 'more') return { timeType: tab };
if (range.length === 2) {
const fmt = (d) => dayjs(d).format('YYYY-MM-DD')
return {
timeType: 'more',
startTimeStr: `${fmt(range[0])} 00:00:01`,
endTimeStr: `${fmt(range[1])} 23:59:59`,
};
}
return { timeType: 'day' }; // fallback
},
/** 跳转到救援订单列表,并将筛选条件序列化到 query */
/**
* extra 其它查询字段
* tab 当前模块的 timeTypeday / month / year / all / more
* range 当前模块的日期区间数组[start, end]only for tab==='more'
*/
viewOrderList(extra = {}, tab = 'day', range = []) {
const base = this.buildTimeParams(tab, range);
const query = { ...base, ...extra };
this.$router.push({ path: '/rescue/rescue_finance/index', query });
},
/* ====== 工单统计 ====== */
onOrderTabClick() {
if (this.orderTab !== 'more') this.orderRange = [];
this.fetchOrderStats();
},
async fetchOrderStats() {
const params = this.buildTimeParams(this.orderTab, this.orderRange);
const { data } = await request.get('/app/rescueInfo/getRescueStatistics', { params });
this.countNum = {
yjdNum: data.yjdNum || 0,
jyzNum: data.jyzNum || 0,
ywcNum: data.ywcNum || 0,
dqcNum: data.dqcNum || 0,
};
},
/* === 渠道 tab 点击 === */
onChannelTabClick() {
//
if (this.channelTab !== 'more') {
this.customRange = []
}
//
this.fetchChannelStats()
},
/* ====== 渠道统计 ====== */
async fetchChannelStats() {
this.channelLoading = true;
try{
if (!this.channelOptions.length) await this.fetchChannelOptions()
const base = this.buildTimeParams(this.channelTab, this.customRange)
//
const channelTasks = this.channelOptions.map(c =>
request.get('/app/rescueInfo/getRescueStatistics', {
params: { ...base, channel: c.name }
}).then(({ data }) => ({
channel: c.name,
channelId: c.id,
yjdNum: data.yjdNum,
jyzNum: data.jyzNum,
ywcNum: data.ywcNum,
dqcNum: data.dqcNum,
receivable: (data.yingskNum / 100).toFixed(1),
receivedMoney: (data.yiskNum / 100).toFixed(1),
dskNum: (data.dskNum / 100).toFixed(1)
}))
)
// +
const sourceTasks = this.channelOptions.map(c =>
request.get(`/rescue-channel-source/sources/${c.id}`)
.then(res => res.data || [])
.then(list => {
if (!list.length) return []
const detailCalls = list.map(s =>
request.get('/app/rescueInfo/getRescueStatistics', {
params: { ...base, channel: c.name, source: s.name }
}).then(({ data }) => ({
source: s.name,
yjdNum: data.yjdNum,
jyzNum: data.jyzNum,
ywcNum: data.ywcNum,
dqcNum: data.dqcNum,
receivable: (data.yingskNum / 100).toFixed(1),
receivedMoney: (data.yiskNum / 100).toFixed(1),
dskNum: (data.dskNum / 100).toFixed(1)
}))
)
return Promise.all(detailCalls)
})
)
//
const channels = await Promise.all(channelTasks)
const allSources = await Promise.all(sourceTasks)
this.channelStats = channels.map((c, idx) => ({
...c,
sources: allSources[idx], // sources
__rowKey: `c-${c.channelId}` // row-key
}))
}finally {
this.channelLoading = false; // loading
}
},
async fetchChannelOptions() {
const res = await request.get('/rescue-channel-source/channelList');
this.channelOptions = res.data || [];
},
/* ====== 业务管理统计 ====== */
onBmTabClick() {
if (this.bmTab !== 'more') this.bmRange = [];
this.fetchBmStats();
},
async fetchBmStats() {
const params = this.buildTimeParams(this.bmTab, this.bmRange);
const { data } = await request.get('/rescueBusiness/statistics/overview', { params });
const filled = {};
Object.keys(this.bmTitleMap).forEach((k) => {
filled[k] = Array.isArray(data[k]) && data[k].length ? data[k] : [{ name: '—', count: 0 }];
});
this.bmStats = filled;
this.bmCategories = Object.keys(filled);
this.bmActiveKeys = this.bmCategories;
},
/* ====== 财务统计 ====== */
onFinanceTabClick() {
if (this.financeTab !== 'more') this.financeRange = [];
this.fetchFinanceStats();
},
async fetchFinanceStats() {
const params = this.buildTimeParams(this.financeTab, this.financeRange);
const { data } = await request.get('/app/rescueInfo/getRescueStatisticsInfoNum', { params });
this.financeStats = {
receivable: { money: data.yingskNum || 0, count: data.yjdNum || 0 },
received: { money: data.yiskNum || 0, count: data.confirmPayNum || 0 },
pending: {
money: data.dskNum || 0,
count: (data.yjdNum || 0) - (data.confirmPayNum || 0),
},
};
},
/* ====== 初始化 ====== */
async fetchAll() {
await Promise.all([
this.fetchOrderStats(),
this.fetchChannelOptions().then(this.fetchChannelStats),
this.fetchBmStats(),
this.fetchFinanceStats(),
]);
},
},
};
</script>
<style scoped lang="scss">
$primary: #3a8dff;
$bg: #f5f6fa;
.statistics-pc {
padding: 16px 24px;
background: $bg;
.top-page-header {
background: transparent;
margin-bottom: 16px;
}
.block-card {
margin-bottom: 24px;
border-radius: 8px;
}
.card-head {
display: flex;
align-items: center;
font-weight: 600;
font-size: 16px;
margin-bottom: 12px;
.inline-tabs {
margin-left: 24px;
flex: 1;
}
}
.full-width-picker {
width: 100%;
margin: 8px 0 16px;
}
.summary-row {
margin-top: 4px;
}
.summary-box {
text-align: center;
cursor: pointer;
.box-icon {
width: 24px;
height: 24px;
margin-right: 4px;
}
.box-title {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #606266;
}
.box-value {
font-size: 28px;
font-weight: bold;
color: #001529;
margin-top: 6px;
}
&:hover {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
}
.tight-table {
th,
td {
padding: 8px 12px !important;
}
}
.bm-item {
display: flex;
justify-content: space-between;
padding: 6px 0;
.bm-key {
color: #606266;
}
.bm-val {
font-weight: 600;
}
}
.finance-box {
text-align: center;
.finance-title {
font-size: 15px;
margin-bottom: 6px;
}
.finance-money {
font-size: 24px;
font-weight: 600;
color: $primary;
margin: 8px 0 2px;
}
.finance-count {
font-size: 13px;
color: #606266;
}
}
}
</style>

View File

@ -0,0 +1,216 @@
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form
:model="queryParams"
ref="queryForm"
size="small"
:inline="true"
v-show="showSearch"
label-width="100px"
>
<el-form-item label="日期" prop="dateRange">
<el-date-picker
v-model="dateRange"
type="daterange"
value-format="yyyy-MM-dd"
unlink-panels
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="onDateChange"
/>
</el-form-item>
<el-form-item label="救援车车牌" prop="driverCarNum">
<el-input
v-model="queryParams.driverCarNum"
placeholder="请输入救援车车牌"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="被施救车车牌" prop="licenseNum">
<el-input
v-model="queryParams.licenseNum"
placeholder="请输入被施救车车牌"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 工具栏 -->
<!-- <el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
icon="el-icon-download"
:loading="exportLoading"
size="mini"
@click="handleExport"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row> -->
<!-- 数据表格 -->
<el-table v-loading="loading" :data="feeList" border fit style="width: 100%">
<el-table-column label="序号" align="center" width="60">
<template slot-scope="scope"
>{{ (queryParams.pageNo - 1) * queryParams.pageSize + scope.$index + 1 }}</template
>
</el-table-column>
<el-table-column label="日期" align="center" min-width="120">
<template slot-scope="scope">{{ formatDate(scope.row.rescueTime) }}</template>
</el-table-column>
<el-table-column label="救援车车牌" prop="driverCarNum" align="center" min-width="120" />
<el-table-column label="被施救车车牌" prop="licenseNum" align="center" min-width="140" />
<el-table-column label="过关费(元)" align="center" min-width="100">
<template slot-scope="scope">{{ scope.row.checkpointMoney }}</template>
</el-table-column>
<!-- 取消固定列避免右侧空白 -->
<!-- <el-table-column label="操作" align="center" min-width="120">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="openEdit(scope.row)"
>修改金额</el-button
>
</template>
</el-table-column> -->
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNo"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 修改金额弹窗 -->
<el-dialog title="修改金额" :visible.sync="editOpen" width="400px" append-to-body>
<el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="100px">
<el-form-item label="金额(元)" prop="amount">
<el-input-number v-model="editForm.amount" :min="0" :precision="2" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitEdit"> </el-button>
<el-button @click="editOpen = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listInfo, updateInfo, exportManagement } from '@/api/rescue/info'
import dayjs from 'dayjs'
export default {
name: 'PassFeePage',
data() {
return {
loading: false,
exportLoading: false,
showSearch: true,
total: 0,
feeList: [],
dateRange: [],
queryParams: {
pageNo: 1,
pageSize: 10,
rescueStart: null,
rescueEnd: null,
driverCarNum: null,
licenseNum: null
},
editOpen: false,
editForm: {
id: null,
amount: 0
},
editRules: {
amount: [{ required: true, message: '金额不能为空', trigger: 'blur' }]
}
}
},
created() {
this.getList()
},
methods: {
formatDate(val) {
return val ? dayjs(val).format('YYYY-MM-DD') : '-'
},
/* 查询 */
onDateChange(val) {
if (val && val.length === 2) {
this.queryParams.rescueStart = val[0]
this.queryParams.rescueEnd = val[1]
} else {
this.queryParams.rescueStart = null
this.queryParams.rescueEnd = null
}
},
getList() {
this.loading = true
listInfo({ ...this.queryParams, listType: 2 }).then(res => {
this.feeList = res.data.records || []
this.total = res.data.total || 0
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNo = 1
this.getList()
},
resetQuery() {
this.dateRange = []
this.queryParams = {
pageNo: 1,
pageSize: 10,
rescueStart: null,
rescueEnd: null,
driverCarNum: null,
licenseNum: null
}
this.resetForm('queryForm')
this.getList()
},
/* 编辑金额 */
openEdit(row) {
this.editForm.id = row.id
this.editForm.amount = (row.setMoney || 0) / 100
this.editOpen = true
},
submitEdit() {
this.$refs['editFormRef'].validate(valid => {
if (!valid) return
updateInfo({ id: this.editForm.id, setMoney: Math.round(this.editForm.amount * 100) }).then(() => {
this.$modal.msgSuccess('修改成功')
this.editOpen = false
this.getList()
})
})
},
/* 导出 */
async handleExport() {
try {
this.exportLoading = true
const data = await exportManagement(this.queryParams)
this.$download.excel(data, `过关费_${Date.now()}.xls`)
} finally {
this.exportLoading = false
}
}
}
}
</script>
<style scoped>
.mb8 {
margin-bottom: 8px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
clearable clearable
@keyup.enter.native="handleQuery" @keyup.enter.native="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="车牌号" prop="carId"> <el-form-item label="车牌号" prop="carId">
<el-input <el-input
@ -17,6 +18,20 @@
@keyup.enter.native="handleQuery" @keyup.enter.native="handleQuery"
/> />
</el-form-item> </el-form-item>
<!-- 新增记录时间段 -->
<el-form-item label="记录时间" prop="recordTimeRange">
<el-date-picker
v-model="queryParams.recordTimeRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
clearable
@change="handleQuery"
/>
</el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@ -28,6 +43,33 @@
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row> </el-row>
<!-- 统计栏 -->
<el-card class="mb8" shadow="never">
<el-row :gutter="20" type="flex" justify="center">
<el-col :span="6" class="stat-box">
<div class="stat-label">行驶总公里数 (km)</div>
<div class="stat-value">{{ summary.totalKm }}</div>
</el-col>
<el-col :span="6"> <!-- 新增 -->
<div class="stat-label">加油总花费 ()</div>
<div class="stat-value">{{ summary.totalRefuelMoney }}</div>
</el-col>
<el-col :span="6" class="stat-box">
<div class="stat-label">平均油耗 (L/km)</div>
<div class="stat-value">
{{ summary.avgFuelEfficiencyLpkm }}
</div>
</el-col>
<el-col :span="6" class="stat-box">
<div class="stat-label">平均成本 (/km)</div>
<div class="stat-value">
{{ summary.avgCostPerKm }}
</div>
</el-col>
</el-row>
</el-card>
<el-table v-loading="loading" :data="refuelRecordList" @selection-change="handleSelectionChange"> <el-table v-loading="loading" :data="refuelRecordList" @selection-change="handleSelectionChange">
<el-table-column label="序号" align="center"> <el-table-column label="序号" align="center">
<template scope="scope"> <template scope="scope">
@ -113,12 +155,19 @@
</template> </template>
<script> <script>
import { listRefuelRecord, getRefuelRecord, delRefuelRecord, addRefuelRecord, updateRefuelRecord } from "./api/refuelRecord"; import { listRefuelRecord, getRefuelRecord, delRefuelRecord, addRefuelRecord, updateRefuelRecord,listRefuelSummary } from "./api/refuelRecord";
export default { export default {
name: "RefuelRecord", name: "RefuelRecord",
data() { data() {
return { return {
/* 统计栏三项 */
summary: {
totalKm: 0, //
avgFuelEfficiencyLpkm: 0, //
avgCostPerKm: 0, //
totalRefuelMoney: 0 //
},
// //
loading: true, loading: true,
// //
@ -146,6 +195,9 @@ export default {
refuelNum: null, refuelNum: null,
refuelMoney: null, refuelMoney: null,
recordTime: null, recordTime: null,
recordTimeRange: [], // [, ]
beginTime: null, //
endTime: null //
}, },
// //
form: {}, form: {},
@ -156,16 +208,65 @@ export default {
}, },
created() { created() {
this.getList(); this.getList();
this.getSummary(); //
}, },
methods: { methods: {
/** 汇总接口:统计栏四项 */
getSummary() {
// recordTimeRange beginTime / endTime
const { recordTimeRange, ...others } = this.queryParams;
const [beginTime, endTime] = recordTimeRange || []; // undefined
// ** recordTimeRange**
const params = {
...others, // pageNopageSizerealName...
beginTime, // 2025-10-01
endTime, // 2025-10-31
timeType: 'more'
};
listRefuelSummary(params).then(res => {
/* 后端已返回 totalKm / avgFuelEfficiencyLpkm / avgCostPerKm / totalRefuelMoney */
this.summary = res.data || {
totalKm: 0,
avgFuelEfficiencyLpkm: 0,
avgCostPerKm: 0,
totalRefuelMoney: 0
};
});
},
/** 查询加油记录列表 */ /** 查询加油记录列表 */
getList() { getList() {
this.loading = true; this.loading = true;
listRefuelRecord(this.queryParams).then(response => {
this.refuelRecordList = response.data.records; // recordTimeRange beginTime / endTime
this.total = response.data.total; const { recordTimeRange, ...others } = this.queryParams;
const [beginTime, endTime] = recordTimeRange || []; // undefined
// ** recordTimeRange**
const params = {
...others, // pageNopageSizerealName...
beginTime, // 2025-10-01
endTime // 2025-10-31
};
listRefuelRecord(params).then(res => {
this.refuelRecordList = res.data.records;
this.total = res.data.total;
this.loading = false; this.loading = false;
/* ★ 计算统计数字 ↓↓↓ */
this.getSummary();
}); });
// listRefuelRecord(this.queryParams).then(response => {
// this.refuelRecordList = response.data.records;
// this.total = response.data.total;
// this.loading = false;
// });
}, },
// //
cancel() { cancel() {
@ -205,6 +306,11 @@ export default {
realName: null, realName: null,
recordTime: null, recordTime: null,
} }
//
const [begin, end] = this.queryParams.recordTimeRange || [];
this.queryParams.beginTime = begin || null;
this.queryParams.endTime = end || null;
this.resetForm("queryForm"); this.resetForm("queryForm");
this.handleQuery(); this.handleQuery();
}, },