From e6dd812f10c48ca71c035445a8ebc34b6f93bb87 Mon Sep 17 00:00:00 2001
From: linwe <872216996@qq.com>
Date: Thu, 17 Apr 2025 21:38:14 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'
---
src/views/tickets/ticket.vue | 238 ++++++++++++++++++++++++++++++++++++++++++-----------------
1 files changed, 169 insertions(+), 69 deletions(-)
diff --git a/src/views/tickets/ticket.vue b/src/views/tickets/ticket.vue
index 673180f..399a94f 100644
--- a/src/views/tickets/ticket.vue
+++ b/src/views/tickets/ticket.vue
@@ -1,7 +1,7 @@
<template>
<basic-container>
<el-tabs v-model="activeTab" @tab-click="handleTabChange">
- <el-tab-pane v-for="tab in tabs" :key="tab.name" :label="`${tab.label} (${tab.count})`" :name="tab.name">
+ <el-tab-pane v-for="tab in filteredTabs" :key="tab.name" :label="`${tab.label} (${tab.count})`" :name="tab.name">
<div class="tab-content">
<!-- 查询条件筛选栏 -->
<div class="filter-bar">
@@ -30,13 +30,14 @@
<!-- 表格部分 -->
<avue-crud ref="avueCrud" v-model="tableData" :option="option" :data="tableData" v-model:page="page"
@size-change="sizeChange" @current-change="handleCurrentChange" :table-loading="loading"
- @selection-change="handleSelectionChange">
+ @selection-change="handleSelectionChange" :permission="permissionList">
<template #menu-left>
- <el-button v-if="activeTab === 'all' || activeTab === 'myTickets'" type="primary" icon="el-icon-plus"
- @click="handleAdd">新建工单</el-button>
- <el-button v-if="activeTab === 'pending'" type="success" icon="el-icon-check"
+ <el-button v-if="(activeTab === 'all' || activeTab === 'myTickets') && permissionList.addBtn"
+ type="primary" icon="el-icon-plus" @click="handleAdd">新建工单</el-button>
+ <el-button v-if="activeTab === 'pending' && permissionList.reviewBtn" type="success" icon="el-icon-check"
@click="openReviewDialog">批量审核</el-button>
- <el-button type="success" plain icon="el-icon-download" @click="exportData">导出</el-button>
+ <el-button v-if="permissionList.exportBtn" type="success" plain icon="el-icon-download"
+ @click="exportData">导出</el-button>
</template>
<template #menu="{ row }">
<template v-if="row.status === -1">
@@ -100,7 +101,7 @@
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="关联算法" prop="algorithm">
- <el-select v-model="form.algorithm" placeholder="请选择关联算法" class="full-width">
+ <el-select v-model="form.algorithm" multiple placeholder="请选择关联算法" class="full-width">
<el-option v-for="item in algorithms" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
@@ -114,7 +115,9 @@
<i class="el-icon-map-location"></i> 地图选点
</el-button>
</avue-input-map>
- <div v-if="form.location?.length >= 2" class="location-text">{{ form.address || '获取地址中...' }}</div>
+ <!-- <div v-if="form.location?.length >= 3">
+ {{ form.location[2] || '获取地址中...' }}
+</div> -->
</div>
</el-form-item>
</el-col>
@@ -131,7 +134,8 @@
<el-upload ref="upload" :action="'#'" :auto-upload="false" list-type="picture-card"
:on-change="handleFileChange" :on-remove="handleUploadRemove" :before-upload="beforeUpload"
:file-list="form.photos" :limit="1" accept="image/*" class="uploader">
- <i class="el-icon-plus"></i>
+ <span
+ style="font-size: 32px; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;">+</span>
</el-upload>
<div class="upload-tip">支持jpg/png格式图片,单张不超过5MB</div>
</el-form-item>
@@ -151,6 +155,7 @@
<!-- 工单详情对话框 -->
<el-dialog v-model="detailVisible" title="工单详情" width="80%" append-to-body>
<div class="detail-container">
+ <div class="event-title-center">{{ currentDetail.orderName || '事件名称' }}</div>
<!-- 工单状态流程 -->
<div class="custom-steps-container">
<!-- 标题行 -->
@@ -200,16 +205,16 @@
</div>
<!-- 基本信息表格 -->
- <el-table :data="formattedDetailFields" border style="width: 100%; margin-bottom: 20px;">
+ <el-table :show-header="false" :data="formattedDetailFields" border style="width: 100%; margin-bottom: 20px;">
<el-table-column prop="label1" label="基本信息" width="150" />
<el-table-column>
<template #default="{ row }">
<!-- 修复工单名称可编辑 -->
- <template v-if="currentDetail.status === 0 && row.label1 === '工单名称'">
+ <template v-if="currentDetail.status === 0 && row.label1 === '工单名称'&&hasProcessingBtnPermission()">
<el-input v-model="currentDetail.orderName" placeholder="请输入工单名称" />
</template>
<!-- 修复任务接收单位为下拉框 -->
- <template v-else-if="currentDetail.status === 0 && row.label1 === '任务接收单位'">
+ <template v-else-if="currentDetail.status === 0 && row.label1 === '发起单位'">
<el-select v-model="currentDetail.department" placeholder="请选择任务接收单位" @change="handleDepartmentChange">
<el-option v-for="item in departments" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
@@ -221,11 +226,11 @@
<el-table-column>
<template #default="{ row }">
<!-- 修复工单内容可编辑 -->
- <template v-if="currentDetail.status === 0 && row.label2 === '工单内容'">
+ <template v-if="currentDetail.status === 0 && row.label2 === '工单内容'&&hasProcessingBtnPermission()">
<el-input type="textarea" v-model="currentDetail.content" placeholder="请输入工单内容" />
</template>
<!-- 修复工单类型为下拉框 -->
- <template v-else-if="currentDetail.status === 0 && row.label2 === '工单类型'">
+ <template v-else-if="currentDetail.status === 0 && row.label2 === '工单类型' && hasProcessingBtnPermission()">
<el-select v-model="currentDetail.type" placeholder="请选择工单类型">
<el-option v-for="item in types" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
@@ -246,7 +251,7 @@
<div v-if="[3, 4, 5].includes(currentDetail.status)" class="form-section">
<div class="section-title">事件处理详情</div>
<!-- 处理中状态显示输入框 -->
- <template v-if="currentDetail.status === 3">
+ <template v-if="currentDetail.status === 3 && hasProcessedAndOverBtnPermission()">
<el-input type="textarea" v-model="currentDetail.processingDetail" placeholder="请输入事件处理详情" :rows="4"
style="width: 100%; margin-bottom: 10px;" />
</template>
@@ -259,22 +264,24 @@
</div>
<!-- 上传图片 -->
- <div v-if="[3, 4].includes(currentDetail.status)" class="form-section">
- <div class="section-title">上传图片</div>
- <el-upload ref="upload" :action="'#'" :auto-upload="false" list-type="picture-card"
- :on-change="handleFileChange" :on-remove="handleUploadRemove" :before-upload="beforeUpload" :file-list="[]"
- accept="image/*">
- <i class="el-icon-plus"></i>
+ <div v-if="[3, 4].includes(currentDetail.status)" class="form-section" style="margin-bottom: 32px;">
+ <div class="section-title" v-if="hasProcessedAndOverBtnPermission()">上传图片</div>
+ <el-upload v-if="hasProcessedAndOverBtnPermission()" ref="upload" :action="'#'" :auto-upload="false"
+ list-type="picture-card" :on-change="handleFileChange" :on-remove="handleUploadRemove"
+ :before-upload="beforeUpload" :file-list="[]" :limit="1" accept="image/*">
+ <span
+ style="font-size: 32px; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;">+</span>
</el-upload>
- <div class="el-upload__tip">支持 jpg/png 格式图片,最多 5 张,单张不超过 5MB</div>
+ <div class="el-upload__tip" style="margin-top: 12px;" v-if="hasProcessedAndOverBtnPermission()">
+ 支持 jpg/png 格式图片,单张不超过 5MB
+ </div>
</div>
-
<!-- 图片和地图部分 -->
<div class="media-section">
<el-row :gutter="20">
<el-col :span="12">
<div class="media-box">
- <div class="media-title">事件图片/事件视频</div>
+ <div class="media-title">事件图片</div>
<div class="media-content">
<el-image v-if="currentDetail.mediaUrl" :src="currentDetail.mediaUrl"
:preview-src-list="[currentDetail.mediaUrl]" fit="cover" style="width: 100%; height: 300px;">
@@ -337,24 +344,23 @@
<div class="dialog-footer">
<template v-if="currentDetail.status === 2">
<!-- 待审核 -->
- <el-button type="primary" @click="approveTicket">通过</el-button>
- <el-button type="danger" @click="rejectTicket">不通过</el-button>
+ <el-button v-if="hasReviewBtnPermission()" type="primary" @click="approveTicket">通过</el-button>
+ <el-button v-if="hasReviewBtnPermission()" type="danger" @click="rejectTicket">不通过</el-button>
<el-button @click="detailVisible = false">取消</el-button>
</template>
<template v-else-if="currentDetail.status === 0">
-
- <el-button type="primary" @click="approveAndDispatch">通过并派发</el-button>
- <el-button type="danger" @click="rejectTicket">不通过</el-button>
+ <el-button v-if="hasProcessingBtnPermission()" type="primary" @click="approveAndDispatch">通过并派发</el-button>
+ <el-button v-if="hasProcessingBtnPermission()" type="danger" @click="rejectTicket">不通过</el-button>
<el-button @click="detailVisible = false">取消</el-button>
</template>
- <template v-else-if="currentDetail.status === 3">
+ <template v-if="currentDetail.status === 3">
<!-- 处理中 -->
- <el-button type="primary" @click="completeTicket">完成工单</el-button>
+ <el-button v-if="hasProcessedAndOverBtnPermission()" type="primary" @click="completeTicket">完成工单</el-button>
<el-button @click="detailVisible = false">取消</el-button>
</template>
<template v-else-if="currentDetail.status === 4">
<!-- 已完成 -->
- <el-button type="primary" @click="finalizeTicket">完结工单</el-button>
+ <el-button v-if="hasProcessedAndOverBtnPermission()" type="primary" @click="finalizeTicket">完结工单</el-button>
<el-button @click="detailVisible = false">取消</el-button>
</template>
<template v-else-if="currentDetail.status === 5">
@@ -443,7 +449,9 @@
name: "TicketPage",
data() {
return {
+ submitLoading: false, // 新增loading状态
activeTab: "all",
+ // tabs 只保留静态结构,不做权限判断
tabs: [
{ label: "全部工单", name: "all", value: null, count: 0 },
{ label: "待审核", name: "pending", value: 2, count: 0 },
@@ -451,7 +459,7 @@
{ label: "处理中", name: "inProgress", value: 3, count: 0 },
{ label: "已完成", name: "completed", value: 4, count: 0 },
{ label: "已完结", name: "closed", value: 5, count: 0 },
- { label: "我发起的", name: "myTickets", value: null, count: 0 },
+ { label: "我发起的工单", name: "myTickets", value: null, count: 0 },
],
filters: {
keyword: "",
@@ -525,7 +533,7 @@
type: '',
department: '',
handler: '',
- algorithm: '',
+ algorithm: [], // 关联算法改为数组
location: [], // 将存储为[经度, 纬度, 地址]格式
address: '',
content: '',
@@ -594,6 +602,8 @@
this.loadAMapScripts();
this.fetchDropdownData();
this.fetchTabCounts(); // 新增:初始化时获取 tab 数据
+ console.log('permission.tickets_processing_btn', this.permission.tickets_processing_btn);
+ console.log('permission', this.permission.tickets_tab_pending);
},
mounted() {
this.fetchTableData();
@@ -646,7 +656,7 @@
editable: false,
},
{
- label: "任务接收单位",
+ label: "发起单位",
value: this.currentDetail.department,
editable: false,
},
@@ -672,7 +682,7 @@
{ label: "事件地址", value: this.currentDetail.address, editable: false },
{ label: "工单类型", value: this.currentDetail.type, editable: this.currentDetail.status === 0, type: "select", options: this.types },
{ label: "关联算法", value: this.currentDetail.aiType, editable: false },
- { label: "任务接收单位", value: this.currentDetail.department, editable: false },
+ { label: "发起单位", value: this.currentDetail.department, editable: false },
{ label: "发起任务时间", value: this.currentDetail.startTime, editable: false },
{ label: "工单内容", value: this.currentDetail.content, editable: this.currentDetail.status === 0, type: "textarea" },
];
@@ -681,12 +691,12 @@
const fields = [
{ label: "工单名称", value: this.currentDetail.orderName },
{ label: "工单类型", value: this.currentDetail.type },
- { label: "任务处理人", value: this.currentDetail.handler || '未分配' }, // 显示处理人
+ { label: "关联任务", value: this.currentDetail.job_name || '暂无关联航线' },
{ label: "任务发起人", value: this.currentDetail.creator },
{ label: "当前状态", value: this.mapStatus(this.currentDetail.status) },
{ label: "事件地址", value: this.currentDetail.address }, // 包含经纬度信息
{ label: "关联算法", value: this.currentDetail.aiType },
- { label: "任务接收单位", value: this.currentDetail.department },
+ { label: "发起单位", value: this.currentDetail.department },
{ label: "发起任务时间", value: this.currentDetail.startTime },
{ label: "工单内容", value: this.currentDetail.content },
];
@@ -710,7 +720,38 @@
}
return this.fixedStatuses; // 默认流程
},
- ...mapGetters(['userInfo']),
+ ...mapGetters(['userInfo', 'permission']),
+ // 动态过滤tabs,保证isShow为true/false
+ filteredTabs() {
+ // 统一处理权限,undefined视为false
+ const tabStatus = this.permission?.tickets_tab_status === true;
+ const tabPending = this.permission?.tickets_tab_pending === true;
+ const tabMyTickets = this.permission?.tickets_tab_mytickets === true;
+ return this.tabs.map(tab => {
+ if (tab.name === 'all') {
+ return { ...tab, isShow: true };
+ }
+ if (tab.name === 'pending') {
+ return { ...tab, isShow: tabPending };
+ }
+ if (['processing', 'inProgress', 'completed', 'closed'].includes(tab.name)) {
+ return { ...tab, isShow: tabStatus };
+ }
+ if (tab.name === 'myTickets') {
+ return { ...tab, isShow: tabMyTickets };
+ }
+ return { ...tab, isShow: false };
+ }).filter(tab => tab.isShow);
+ },
+ permissionList() {
+ // 可根据实际后端权限key调整
+ return {
+ addBtn: this.validData(this.permission.tickets_add, false),
+ delBtn: this.validData(this.permission.tickets_delete, false),
+ exportBtn: this.validData(this.permission.tickets_export, false,),
+ reviewBtn: this.validData(this.permission.tickets_review, false),
+ };
+ },
},
methods: {
async loadAMapScripts() {
@@ -823,8 +864,11 @@
// 确保算法数据的映射一致
this.algorithms = ai_type.map(item => ({
- label: item.dict_value, // 修改为 label
- value: item.dict_key, // 修改为 value
+ dict_key: item.dict_key,
+ dict_value: item.dict_value,
+ // 同时添加 label 和 value 以兼容两处使用
+ label: item.dict_value,
+ value: item.dict_key
}));
// 构建用户ID和名称的映射关系
@@ -858,7 +902,8 @@
size: Number(this.page.pageSize), // 使用每页条数
ai_type: this.filters.algorithm || undefined, // 添加算法参数
// 添加 is_draft 参数,仅在"我发起的"标签页时设置为1
- is_draft: currentTab?.name === 'myTickets' ? 1 : undefined
+ is_draft: currentTab?.name === 'myTickets' ? 1 : undefined,
+ user_id: currentTab?.name === 'myTickets' ? this.userInfo.user_id : undefined,
};
const response = await getList(params);
@@ -870,11 +915,11 @@
let filteredRecords = records;
// 如果是"我发起的"tab,过滤数据
- if (currentTab?.name === 'myTickets') {
- filteredRecords = records.filter(item =>
- String(item.create_user_id) === String(item.user_id)
- );
- }
+ // if (currentTab?.name === 'myTickets') {
+ // filteredRecords = records.filter(item =>
+ // String(item.create_user_id) === String(item.user_id)
+ // );
+ // }
this.tableData = filteredRecords.map(item => {
const longitude = Number(item.longitude) || 0;
@@ -902,15 +947,12 @@
processing_details: item.processing_details || '', // 添加处理详情字段
update_photo_url: item.update_photo_url || '', // 添加处理图片字段
work_type: item.work_type !== undefined ? Number(item.work_type) : 0, // 保留work_type字段并转为数字
+ job_name: item.job_name || '',
};
});
// 更新总数显示
- if (currentTab?.name === 'myTickets') {
- this.page.total = filteredRecords.length;
- } else {
- this.page.total = total || 0;
- }
+ this.page.total = total || 0;
await this.fetchTabCounts();
} catch (error) {
@@ -924,6 +966,8 @@
},
async submitForm() {
+ if (this.submitLoading) return; // 防止重复提交
+ this.submitLoading = true;
try {
// 提交时需要完整验证
await this.$refs.form.validate();
@@ -948,7 +992,7 @@
latitude: String(this.form.location[1]),
address: this.form.address,
eventDictKey: this.form.type,
- aiType: this.form.algorithm,
+ aiType: Array.isArray(this.form.algorithm) ? this.form.algorithm : [this.form.algorithm], // 传数组
updateUser: this.form.handler,
createDept: this.form.department,
isDraft: 0
@@ -980,10 +1024,19 @@
this.$message.error(error.message || '工单创建失败,请稍后重试');
}
}
+ finally {
+ this.submitLoading = false;
+ }
},
async saveDraft() {
+ if (this.submitLoading) return; // 防止重复提交
+ this.submitLoading = true;
try {
+ let handlerValue = this.form.handler;
+ if (!handlerValue || handlerValue === '未分配') {
+ handlerValue = undefined;
+ }
const submitData = {
id: this.form.id,
eventName: this.form.name || undefined,
@@ -993,8 +1046,8 @@
latitude: this.form.location?.[1] ? String(this.form.location[1]) : undefined,
address: this.form.address || undefined,
eventDictKey: this.form.type || undefined,
- aiType: this.form.algorithm || undefined,
- updateUser: this.form.handler || undefined,
+ aiType: Array.isArray(this.form.algorithm) ? this.form.algorithm : [this.form.algorithm], // 传数组
+ updateUser: handlerValue,
createDept: this.form.department || undefined,
isDraft: 1
};
@@ -1026,16 +1079,20 @@
} catch (error) {
this.$message.error(error.message || '保存草稿失败,请稍后重试');
}
+ finally {
+ this.submitLoading = false;
+ }
},
- async handleLocationChange(val) {
-
-
- // 处理 Proxy 对象的值
+ handleLocationChange(val) {
let locationValue = val.value;
- if (locationValue && locationValue.length >= 3) {
- // 确保我们获取到实际的数组值
- this.form.location = [locationValue[0], locationValue[1]];
+ if (locationValue && locationValue.length >= 2) {
+ // 兼容第三项为地址
+ this.form.location = [
+ Number(locationValue[0]),
+ Number(locationValue[1]),
+ locationValue[2] || ''
+ ];
this.form.address = locationValue[2] || '';
} else {
this.form.location = [];
@@ -1142,7 +1199,7 @@
type: '',
department: '',
handler: '',
- algorithm: '',
+ algorithm: [], // 关联算法改为数组
location: [], // 将存储为[经度, 纬度, 地址]格式
address: '',
content: '',
@@ -1178,6 +1235,7 @@
mediaUrl: row.photo_url || row.video_url || '',
updatePhotoUrl: row.update_photo_url || '',
photos: [],
+ job_name: row.job_name || '', // 新增
};
try {
@@ -1517,6 +1575,21 @@
async approveAndDispatch() {
this.dispatchDialogVisible = true; // 打开派发对话框
},
+ hasProcessingBtnPermission() {
+ // undefined 或 false 都返回 false,只有 true 返回 true
+ console.log('权限检查:', this.permission);
+ return this.permission && this.permission.tickets_processing_btn === true;
+ },
+ hasProcessedAndOverBtnPermission() {
+ // undefined 或 false 都返回 false,只有 true 返回 true
+ console.log('权限检查:', this.permission);
+ return this.permission && this.permission.tickets_view_processedAndOver === true;
+ },
+ hasReviewBtnPermission() {
+ // undefined 或 false 都返回 false,只有 true 返回 true
+ console.log('权限检查:', this.permission);
+ return this.permission && this.permission.tickets_review_btn === true;
+ },
async submitDispatch() {
this.$refs.dispatchForm.validate(async (valid) => {
if (valid) {
@@ -1616,19 +1689,40 @@
映射后类型值: typeValue
});
+ let algorithmArr = [];
+ if (Array.isArray(row.aiType)) {
+ algorithmArr = row.aiType;
+ } else if (typeof row.aiType === 'string' && row.aiType) {
+ algorithmArr = row.aiType.split(',').map(i => i.trim());
+ }
this.form = {
id: row.id,
name: row.orderName,
type: typeValue,
department: deptId,
handler: handlerId, // 使用映射后的处理人ID
- algorithm: row.aiType,
- location: row.location,
- address: row.address,
+ algorithm: algorithmArr, // 回显为数组
+ location: Array.isArray(row.location) && row.location.length >= 2
+ ? [Number(row.location[0]), Number(row.location[1])]
+ : [],
+ address: row.address || '',
content: row.content,
photos: [],
};
-
+ this.form.location = Array.isArray(row.location) && row.location.length >= 2
+ ? [
+ Number(row.location[0]),
+ Number(row.location[1]),
+ row.location[2] || row.address || ''
+ ]
+ : [];
+ this.form.address = this.form.location[2] || '';
+ // 设置地图中心点
+ if (Array.isArray(this.form.location) && this.form.location.length >= 2) {
+ this.mapParams.center = [Number(this.form.location[0]), Number(this.form.location[1])];
+ } else {
+ this.mapParams.center = null; // 没有经纬度时用默认
+ }
// 如果有图片,添加到表单中
if (row.photo_url) {
this.form.photos = [{
@@ -1802,7 +1896,7 @@
}
},
- cancleBatchReject(){
+ cancleBatchReject() {
reviewDialogVisible = false;
this.fetchTableData();
},
@@ -2346,7 +2440,13 @@
color: #409eff;
font-weight: bold;
}
-
+.event-title-center {
+ text-align: center;
+ font-size: 20px;
+ font-weight: bold;
+ margin-bottom: 12px;
+ color: #333;
+}
.custom-steps {
margin-top: -20px;
--
Gitblit v1.9.3