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