This commit is contained in:
xuyuncong 2025-10-23 14:58:19 +08:00
parent aadeb256a7
commit 780f394407
9 changed files with 922 additions and 30 deletions

View File

@ -20,6 +20,15 @@ export function getRepairSoPage(params){
})
}
// 分页
export function getRepairSoPageItems(params){
return request({
url: preUrl + "/page-with-items",
method: "get",
params
})
}
// 统计
export function purchase(params){
return request({
@ -74,7 +83,8 @@ export function inWare(data){
// 导出数据
export function exportData(params){
return request({
url: preUrl + "/export",
// url: preUrl + "/export",
url: preUrl + "/export-with-items",
method: 'get',
params,
responseType: 'blob'

View File

@ -54,10 +54,11 @@ export function setTicketsSettlement(data){
data
})
}
// 结算审核
export function settlementReview(data){
// 反结算
export function setTicketsAntiSettlement(data){
return request({
url: preUrl + "/settlementReview",
url: preUrl + "/antiSettlement",
method: 'post',
data
})
@ -107,6 +108,15 @@ export function getStatistics(params){
})
}
// 结算审核
export function settlementReview(data){
return request({
url: preUrl + "/settlementReview",
method: 'post',
data
})
}
// 判断登录用户的角色,针对维修工单中的四个角色
export function getUserRole(){
return request({
@ -245,6 +255,17 @@ export function exportData(params){
})
}
// 导出合并数据
export function exportMerged(params){
return request({
url: preUrl + "/exportMerged",
method: 'get',
params,
responseType: 'blob',
timeout: 6000000
})
}
// 导出数据 根据工单状态
export function exportByStatus(params){
return request({

View File

@ -35,6 +35,15 @@ export function updateChargeCompany(data) {
})
}
// 修改挂账单位
export function reviewChargeCompany(data) {
return request({
url: '/base-charge-company/review',
method: 'put',
data
})
}
// 删除挂账单位
export function deleteChargeCompany(ids) {
return request({

View File

@ -37,7 +37,7 @@
</el-form>
<!-- 文件预览对话框 -->
<el-dialog :title="'文件预览(' + selectFile.fileName + ''" :visible.sync="isShowFile" width="70%" append-to-body>
<el-dialog :title="'文件预览(' + selectFile.fileName + ''" :visible.sync="isShowFile" width="70%" append-to- >
<!-- 全屏按钮 -->
<el-button
class="fullscreen-btn"

View File

@ -191,7 +191,7 @@
</template>
<script>
import { getChargeCompanyList, getChargeCompany, addChargeCompany, updateChargeCompany, deleteChargeCompany } from './api/chargeCompanyApi'
import { getChargeCompanyList, getChargeCompany, addChargeCompany, updateChargeCompany, deleteChargeCompany ,reviewChargeCompany} from './api/chargeCompanyApi'
import Pagination from '@/components/Pagination'
import { getLastPathSegment } from '@/utils/ruoyi'
import ChargeCompanyForm from './form/ChargeCompanyForm'
@ -344,7 +344,7 @@ export default {
handleStatusChange(row) {
const statusMsg = row.status ? '启用' : '禁用'
this.$modal.confirm(`是否确认${statusMsg}该挂账单位?`).then(() => {
updateChargeCompany(row).then(() => {
reviewChargeCompany(row).then(() => {
this.$modal.msgSuccess(`${statusMsg}成功`)
this.getList()
}).catch(() => {

View File

@ -36,10 +36,10 @@
</el-row>
<!-- 表格 -->
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="序号" align="center" width="55">
<el-table v-loading="loading" :data="expandedList" :stripe="true" :show-overflow-tooltip="true" :row-key="row => row._rowKey" :span-method="mergeCells">
<el-table-column label="序号" align="center" width="55" prop="_index">
<template scope="scope">
<span>{{ scope.$index + 1 }}</span>
<span>{{ scope.row._index + 1 }}</span>
</template>
</el-table-column>
<el-table-column label="单号" align="center" prop="soNo" width="200"/>
@ -49,8 +49,29 @@
<el-table-column label="退货时间" align="center" prop="soTime"
width="150"/>
<el-table-column label="退货人" align="center" prop="userName" />
<el-table-column label="商品" align="center" prop="itemGoodsName" width="180">
<template slot-scope="scope">
{{ scope.row.itemGoodsName || '-' }}
</template>
</el-table-column>
<el-table-column label="商品数量" align="center" prop="itemGoodsCount" width="150">
<template slot-scope="scope">
{{ scope.row.itemGoodsCount || '-' }}
</template>
</el-table-column>
<el-table-column label="商品价格" align="center" prop="itemGoodsPrice" width="150">
<template slot-scope="scope">
{{ scope.row.itemGoodsPrice || '-' }}
</template>
</el-table-column>
<el-table-column label="仓库" align="center" prop="houseName" width="180">
<template slot-scope="scope">
{{ scope.row.houseName || '-' }}
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="180" align="center">
<template v-slot="scope">
<template v-if="scope.row._isFirstItem">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleShow(scope.row)"
>查看
</el-button>
@ -58,6 +79,7 @@
>作废
</el-button>
</template>
</template>
</el-table-column>
</el-table>
@ -183,7 +205,7 @@ import SupplierChoose from "@/views/repair/Components/SupplierChoose.vue";
import StaffChoose from "@/views/repair/Components/StaffChoose.vue";
import CorpChoose from "@/views/repair/Components/CorpChoose.vue";
import SoReturnForm from "@/views/repair/stockOperate/form/SoReturnForm.vue";
import {getRepairSoById, getRepairSoPage, voidSo, exportData} from "@/api/repair/stockOperate/stockOperate";
import {getRepairSoById, getRepairSoPage, voidSo, exportData, getRepairSoPageItems} from "@/api/repair/stockOperate/stockOperate";
import {getRepairSoiByIds} from "@/api/repair/stockOperate/stockOperateItem";
import {getBaseWarehouseList} from "@/api/base/warehouse";
@ -201,6 +223,7 @@ export default {
},
showSearch: true,
list: [],
expandedList: [],
total: 0,
loading: false,
supplier: null,
@ -211,6 +234,10 @@ export default {
dialogVisible: false,
info: {},
warehouseList: [],
//
mergeProps: ['_index', 'soNo', 'itemCount', 'totalPrice', 'supplierName', 'soTime', 'userName'],
//
itemProps: ['itemGoodsName', 'itemGoodsCount', 'itemGoodsPrice', 'houseName'],
//
exportLoading: false,
}
@ -263,12 +290,109 @@ export default {
} catch {
}
},
// items
processDataForMergeCells(data) {
const result = [];
let index = 0;
data.forEach((item, idx) => {
const items = item.items || [];
if (items.length > 0) {
items.forEach((subItem, subIdx) => {
const newItem = {
...item,
_index: idx, //
_rowKey: `${item.id}_${subIdx}`, //
_isFirstItem: subIdx === 0,
itemGoodsName: subItem.wares?.name || '-',
itemGoodsCount: subItem.goodsCount || '-',
itemGoodsPrice: subItem.goodsPrice || '-',
houseName: subItem.houseName || '-'
};
result.push(newItem);
});
} else {
// items
const newItem = {
...item,
_index: idx,
_rowKey: `${item.id}_0`,
_isFirstItem: true,
itemGoodsName: '-',
itemGoodsCount: '-',
itemGoodsPrice: '-',
itemWareId: '-'
};
result.push(newItem);
}
});
return result;
},
//
mergeCells({row, column, rowIndex, columnIndex}) {
//
if (this.itemProps.includes(column.property)) {
return { rowspan: 1, colspan: 1 };
}
//
if (column.property === undefined && column.label === '操作') {
if (row._isFirstItem) {
const mainId = row.id;
let count = 0;
// id
for (let i = rowIndex; i < this.expandedList.length; i++) {
if (this.expandedList[i].id === mainId) {
count++;
} else {
break;
}
}
return { rowspan: count, colspan: 1 };
} else {
return { rowspan: 0, colspan: 0 };
}
}
//
if (this.mergeProps.includes(column.property) || (column.property === undefined && column.label === '序号')) {
const mainId = row.id;
//
if (row._isFirstItem) {
let count = 0;
// id
for (let i = rowIndex; i < this.expandedList.length; i++) {
if (this.expandedList[i].id === mainId) {
count++;
} else {
break;
}
}
return { rowspan: count, colspan: 1 };
} else {
return { rowspan: 0, colspan: 0 };
}
}
return { rowspan: 1, colspan: 1 };
},
async getReturnList() {
try {
this.loading = true
const res = await getRepairSoPage(this.queryParams)
const res = await getRepairSoPageItems(this.queryParams)
this.list = res.data.records
this.total = res.data.total
// items
this.expandedList = this.processDataForMergeCells(this.list)
}finally {
this.loading = false
}

View File

@ -127,7 +127,7 @@ export default {
break
case "ts":
// this.queryParams.ticketsStatus = "04"
this.queryParams.payStatus = "01"
this.queryParams.payStatuses = ["01","04"]
break
default:
break

View File

@ -120,16 +120,20 @@
@click="handlePaid(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="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"
@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 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 v-if="TicketType === 'tp'" size="mini" type="text" icon="el-icon-refresh-right"-->
@ -455,6 +459,17 @@
<el-input v-model="chargeCompanyForm.remark" placeholder="请输入备注" type="textarea" />
</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-input v-model="chargeCompanyForm.status" type="hidden" />
@ -463,6 +478,10 @@
<el-form-item label="" prop="systemCode" style="display: none;">
<el-input v-model="chargeCompanyForm.systemCode" type="hidden" />
</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>
<div slot="footer" class="dialog-footer">
@ -470,6 +489,49 @@
<el-button @click="cancelChargeCompany"> </el-button>
</div>
</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>
</template>
@ -482,17 +544,20 @@ import {
setTicketsSettlement,
settlementReview,
getSettlement,
payConfirm
payConfirm,
setTicketsAntiSettlement
} from '@/api/repair/tickets/Tickets'
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 { 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 {
name: "TicketTable",
components: {EditTickets, TicketsShow},
components: {EditTickets, TicketsShow, ImagePreview},
props: {
TicketType: {
type: String,
@ -543,8 +608,29 @@ export default {
address: '',
status: false, //
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: {
companyName: [{ required: true, message: '单位名称不能为空', trigger: 'blur' }],
contactPerson: [{ required: true, message: '联系人不能为空', trigger: 'blur' }],
@ -610,6 +696,32 @@ 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);
},
// PDF
isPdfType() {
if (!this.selectFile.fileName) return false;
return this.selectFile.fileName.toLowerCase().endsWith('.pdf');
},
// TXT
isTxtType() {
if (!this.selectFile.fileName) return false;
return this.selectFile.fileName.toLowerCase().endsWith('.txt');
}
},
watch: {
//
'formData.isPaid': {
@ -653,6 +765,26 @@ export default {
} 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 {
}
},
/**
* 打印
*/
@ -821,6 +953,7 @@ export default {
try {
await this.$refs['formRefSettlement'].validate()
if (this.settlementType === 'jssh') {
console.log('执行借宿那审核');
await settlementReview(this.settlementFormData)
} else {
await setTicketsSettlement(this.settlementFormData)
@ -828,7 +961,8 @@ export default {
this.$modal.msgSuccess("提交成功")
this.dialogVisibleSettlement = false
this.$emit("setVoid")
} catch {
} catch (error) {
console.error('Error in settlement process:', error);
}
},
getDictDataByCode(code) {
@ -953,8 +1087,11 @@ export default {
address: '',
status: false, //
systemCode: 'repair',
remark: ''
remark: '',
file: ''
};
//
this.uploadFileList = [];
this.chargeCompanyDialogVisible = true;
},
@ -963,6 +1100,10 @@ export default {
this.$refs.chargeCompanyFormRef.validate(async (valid) => {
if (valid) {
try {
// file
const fileField = this.ensureFileField();
console.log('提交时的file字段值:', fileField);
await addChargeCompany(this.chargeCompanyForm);
this.$modal.msgSuccess('挂账单位申请成功,请等待审核');
this.chargeCompanyDialogVisible = false;
@ -982,6 +1123,183 @@ export default {
cancelChargeCompany() {
this.chargeCompanyDialogVisible = false;
this.$refs.chargeCompanyFormRef && this.$refs.chargeCompanyFormRef.resetFields();
//
this.uploadFileList = [];
},
//
beforeUpload(file) {
//
const isLt20M = file.size / 1024 / 1024 < 20 // 20MB
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 // fileName
this.uploadFileList[index].filePath = 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)
})
}
// file
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 => {
// 使使url
const filePath = file.url || file.name || ''
// viewFileUrl
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 || '';
// http
if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
return filePath;
}
// URL
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 || '')
};
// Office使Office Online
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 '未知文件';
// URL
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] || '未知文件';
},
// file
ensureFileField() {
this.updateFilePaths()
return this.chargeCompanyForm.file
}
}
}
@ -1013,4 +1331,28 @@ export default {
color: #409EFF;
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>

View File

@ -0,0 +1,386 @@
<template>
<div class="app-container">
<div class="head-container">
<div class="filter-container">
<el-input v-model="queryParams.ticketNo" placeholder="工单编号" clearable style="width: 200px" />
<el-input v-model="queryParams.carNo" placeholder="车牌号" clearable style="width: 180px" />
<el-input v-model="queryParams.userName" placeholder="客户名称" clearable style="width: 180px" />
<el-select v-model="queryParams.repairType" placeholder="维修类别" clearable style="width: 120px">
<el-option v-for="item in repairTypeList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
style="width: 250px"
/>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
<el-button
:loading="exportLoading"
type="primary"
icon="el-icon-download"
@click="handleExport"
>导出</el-button>
</div>
</div>
<div class="table-container">
<el-table v-loading="loading" :data="expandedTicketList" :stripe="true" :show-overflow-tooltip="true" :row-key="row => row._rowKey" :span-method="mergeCells">
<!-- 合并单元格的表格 -->
<el-table-column label="序号" align="center" prop="_index">
<template slot-scope="scope">
<span v-if="scope.row._isFirstItem">{{ scope.row._index + 1 }}</span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="订单编号" align="center" prop="ticketNo" width="200"/>
<el-table-column label="维修类别" align="center" prop="repairType" width="100">
<template slot-scope="scope">
<dict-tag :type="DICT_TYPE.REPAIR_TYPE" :value="scope.row.repairType"/>
</template>
</el-table-column>
<el-table-column label="客户信息" align="center">
<el-table-column label="客户名称" align="center" prop="userName" width="100"/>
<el-table-column label="车牌号" align="center" prop="carNo" width="100"/>
<el-table-column label="车系" align="center" prop="carBrandName" width="100"/>
<el-table-column label="手机号" align="center" prop="userMobile" width="110"/>
</el-table-column>
<el-table-column label="经办人姓名" align="center" prop="handleName" width="180"/>
<el-table-column label="经办人电话" align="center" prop="handleMobile" width="180"/>
<!-- <el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}
</template>
</el-table-column> -->
<el-table-column label="合计金额" align="center" prop="totalPrice" width="80"/>
<el-table-column label="参考成本" align="center" prop="cost" width="80"/>
<el-table-column label="参考毛利" align="center" prop="profit" width="80"/>
<el-table-column label="服务顾问" align="center" prop="adviserName" width="100"/>
<!-- 项目详情信息 - 不合并 -->
<el-table-column label="项目名称" align="center" width="180" prop="itemName"/>
<el-table-column label="数量" align="center" width="180" prop="itemCount"/>
<el-table-column label="项目金额" align="center" width="100" prop="itemMoney"/>
<el-table-column label="毛利" align="center" width="100" prop="itemProfit"/>
<el-table-column label="毛利率" align="center" width="100" prop="itemProfitRate"/>
<el-table-column label="类别" align="center" width="100" prop="itemType">
<template slot-scope="scope">
{{ scope.row.itemType == '01' ? '公时' : '配件' }}
</template>
</el-table-column>
<el-table-column label="班组" align="center" width="100" prop="workerType">
<template slot-scope="scope">
<DictTag :type="'repair_work_type'" :value="scope.row.workerType"/>
</template>
</el-table-column>
<el-table-column label="维修员工" align="center" width="100" prop="repairNames"/>
<el-table-column label="销售人员" align="center" width="100" prop="saleName"/>
<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="receivablesAccount" width="180"/>
<el-table-column label="确认收款状态" prop="payConfirm" width="100">
<template slot-scope="scope">
<el-tag v-if="scope.row.payConfirm == '1'" type="success">已确认</el-tag>
<el-tag v-else type="danger">未确认</el-tag>
</template>
</el-table-column>
<el-table-column label="确认收款备注" v-if="activeTab === 'tp'" prop="payConfirmRemark" width="100"/>
<el-table-column label="操作" fixed="right" align="center" width="100">
<template slot-scope="scope">
<span v-if="scope.row._isFirstItem">
<el-button size="mini" type="text" icon="el-icon-printer">下载打印</el-button>
</span>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination-container">
<Pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</div>
</template>
<script>
import { getTicketsPage, exportMerged } from '@/api/repair/tickets/Tickets'
import Pagination from '@/components/Pagination'
import { DICT_TYPE } from '@/utils/dict'
import DictTag from '@/components/DictTag'
export default {
name: 'RepairTicketManagement',
components: {
Pagination,
DictTag
},
data() {
return {
//
ticketList: [],
//
expandedTicketList: [],
//
mergeRules: [],
//
total: 0,
//
loading: false,
//
exportLoading: false,
//
activeTab: 'tu',
//
dateRange: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
ticketNo: '',
carNo: '',
userName: '',
repairType: '',
startTime: '',
endTime: ''
},
//
repairTypeList: []
}
},
created() {
this.getList()
this.loadDictData()
},
methods: {
//
async getList() {
this.loading = true
try {
//
if (this.dateRange && this.dateRange.length > 0) {
this.queryParams.startTime = this.dateRange[0]
this.queryParams.endTime = this.dateRange[1]
} else {
this.queryParams.startTime = ''
this.queryParams.endTime = ''
}
const res = await getTicketsPage({
...this.queryParams
})
this.ticketList = res.data.records
this.total = res.data.total
//
this.processDataForMergeCells()
} catch (error) {
console.error('获取工单列表失败:', error)
this.$modal.msgError('获取工单列表失败')
} finally {
this.loading = false
}
},
//
processDataForMergeCells() {
const expandedList = []
const mergeRules = []
let rowIndex = 0
this.ticketList.forEach((ticket, ticketIndex) => {
// itemList
const items = Array.isArray(ticket.itemList) && ticket.itemList.length > 0 ? ticket.itemList : [{
itemName: '-',
itemMoney: '-',
repairNames: '-',
saleName: '-'
}]
const itemCount = items.length
//
mergeRules.push({
startRow: rowIndex,
endRow: rowIndex + itemCount - 1,
ticketIndex: ticketIndex,
index: ticketIndex + 1 //
})
//
items.forEach((item, itemIndex) => {
expandedList.push({
//
...ticket,
//
...item,
//
_rowKey: `${ticket.id || rowIndex}-${itemIndex}`,
//
_isFirstItem: itemIndex === 0,
//
_index: ticketIndex
})
rowIndex++
})
})
this.expandedTicketList = expandedList
this.mergeRules = mergeRules
},
//
mergeCells({ row, column, rowIndex, columnIndex }) {
//
const prop = column.property;
//
const mergeProps = ['_index', 'ticketNo', 'repairType', 'userName', 'carNo',
'carBrandName', 'userMobile', 'handleName', 'handleMobile',
'createTime', 'totalPrice', 'cost', 'profit', 'adviserName',
'remark', 'corpId', 'receivablesAccount', 'payConfirm', 'payConfirmRemark'];
//
const itemProps = ['itemName', 'itemMoney', 'repairNames', 'saleName'];
if (itemProps.includes(prop)) {
return { rowspan: 1, colspan: 1 };
}
//
const rule = this.mergeRules.find(
r => rowIndex >= r.startRow && rowIndex <= r.endRow
);
//
if (rule && (mergeProps.includes(prop) || columnIndex === 0)) {
if (rowIndex === rule.startRow) {
// rowspan
return {
rowspan: rule.endRow - rule.startRow + 1,
colspan: 1
};
} else {
//
return {
rowspan: 0,
colspan: 0
};
}
}
},
//
loadDictData() {
this.repairTypeList = this.getDictDatas(DICT_TYPE.REPAIR_TYPE)
},
//
handleTabChange(tab) {
this.activeTab = tab.name
this.queryParams.pageNum = 1
this.getList()
},
//
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
//
resetQuery() {
this.dateRange = []
this.queryParams = {
pageNum: 1,
pageSize: 10,
ticketNo: '',
carNo: '',
userName: '',
repairType: '',
startTime: '',
endTime: ''
}
this.getList()
},
//
handleRefresh() {
this.getList()
},
//
handleExport() {
this.$modal.confirm('是否确认导出当前查询条件所有数据项?').then(() => {
this.exportLoading = true
//
const exportParams = {...this.queryParams}
exportParams.pageNum = undefined
exportParams.pageSize = undefined
if (this.dateRange && this.dateRange.length > 0) {
exportParams.startTime = this.dateRange[0]
exportParams.endTime = this.dateRange[1]
} else {
exportParams.startTime = ''
exportParams.endTime = ''
}
return exportMerged(exportParams)
}).then(response => {
this.$download.excel(response, '维修工单合并数据.xlsx')
}).finally(() => {
this.exportLoading = false
})
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.head-container {
margin-bottom: 20px;
}
.filter-container {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.tab-container {
border: 1px solid #ebeef5;
border-radius: 4px;
}
.table-container {
margin-bottom: 20px;
}
.pagination-container {
text-align: right;
}
//
@media (max-width: 768px) {
.filter-container {
flex-direction: column;
align-items: stretch;
}
.filter-container > * {
width: 100% !important;
}
}
</style>