无人机管理后台前端(已迁走)
linwe
2025-04-17 e6dd812f10c48ca71c035445a8ebc34b6f93bb87
Merge remote-tracking branch 'origin/master'
3 files modified
499 ■■■■■ changed files
src/api/tickets/orderLog.js 29 ●●●●● patch | view | raw | blame | history
src/views/tickets/orderLog.vue 232 ●●●●● patch | view | raw | blame | history
src/views/tickets/ticket.vue 238 ●●●● patch | view | raw | blame | history
src/api/tickets/orderLog.js
@@ -50,6 +50,7 @@
  });
};
// 导出
export const orderLogExport = (data) => {
  return request({
@@ -60,3 +61,31 @@
  });
};
export const jobStatusNum = () => {
  return request({
    url: '/drone-device-core/wayline/orderLog/jobStatusNum',
    method: 'get'
  });
};
// 草稿转发布
export const userPublish = (id) => {
  return request({
    url: '/drone-device-core/wayline/orderLog/userPublish',
    method: 'get',
    params: { id },
  });
};
//删除
export const deleteOrderLog = (id) => {
  return request({
    url: '/drone-device-core/wayline/orderLog/deleteOrderLog',
    method: 'get',
    params: { id },
  });
};
src/views/tickets/orderLog.vue
@@ -11,7 +11,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">
@@ -67,28 +67,42 @@
                        :table-loading="loading" @current-change="currentChange" @refresh-change="refreshChange"
                        @on-load="onLoad" @search-change="searchChange" @size-change="sizeChange">
                        <template #menu-left>
                            <el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建工单</el-button>
                            <el-button v-if="hasAddBtnPermission()&&activeTab!='WAIT_AUDIT'" type="primary" icon="el-icon-plus"@click="handleAdd" >新建工单</el-button>
                            <el-button type="success" plain icon="el-icon-download" @click="exportData">导出</el-button>
                        </template>
                        <template #menu="{ row }">
                            <div v-if="row.status == 1 && userInfo.user_id != row.create_user">
                                <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">审核</el-button>
                                <el-button v-if="hasPaddingBtnPermission()" type="text" icon="el-icon-view" @click="handleViewDetail(row)">审核</el-button>
                                <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情</el-button>
                            </div>
                            <div v-if="userInfo.user_id == row.create_user">
                                <!--待审核状态-->
                                <div v-if="row.status == 1">
                                    <el-button type="text" icon="el-icon-view"
                                    <el-button type="text" icon="el-icon-warning-outline"
                                        @click="orderLogRecall(row.id)">撤回</el-button>
                                </div>
                                <!--已驳回-->
                                <div v-if="row.status == 2">
                                    <el-button type="text" icon="el-icon-view"
                                        @click="rejectDetail(row.id)">驳回原因</el-button>
                                        @click="handleViewDetail(row)">详情</el-button>
                                </div>
                            </div>
                            <!--已驳回-->
                            <div v-if="row.status == 2">
                                <el-button type="text" icon="el-icon-warning-outline"
                                    @click="rejectDetail(row.id)">驳回原因</el-button>
                                <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情</el-button>
                            </div>
                            <!--草稿-->
                            <div v-if="row.status == 0 && userInfo.user_id == row.create_user">
                                <el-button type="text" icon="el-icon-edit-outline"
                                    @click="handleViewDetail(row)">编辑</el-button>
                                <el-button type="text" icon="el-icon-position"
                                    @click="userPublishPush(row.id)">发布</el-button>
                                <el-button type="text" icon="el-icon-delete"
                                    @click="deleteOrderLog(row.id)">删除</el-button>
                            </div>
                        </template>
                        <template #status="{ row }">
                            <el-tag :type="getStatusTagType(row.status)">{{ mapStatus(row.status) }}</el-tag>
@@ -104,8 +118,10 @@
        </el-tabs>
        <!-- 新建工单对话框 -->
        <el-dialog v-model="dialogVisible" title="新建工单" width="70%" :close-on-click-modal="false" @close="resetForm">
            <el-form :model="form" :rules="rules" ref="form" label-width="100px">
        <el-dialog  v-model="dialogVisible" title="新建工单" width="70%" :close-on-click-modal="false" @close="resetForm">
            <el-form :model="form" :rules="rules" ref="testform" label-width="100px">
                <el-row :gutter="20">
                    <el-col :span="12">
                        <el-form-item label="工单名称" prop="name">
@@ -271,6 +287,9 @@
                        <el-button type="danger"
                            v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
                            @click="submitForm(1)">发布</el-button>
                        <el-button type="primary" v-if="form.status == 0 || userInfo.user_id == form.create_user"
                            @click="submitForm(0)">保存</el-button>
                        <el-button type="primary" v-if="form.status == 1 && userInfo.user_id != form.create_user"
                            @click="orderLogPass(form.id)">通过</el-button>
                        <el-button type="danger" v-if="form.status == 1 && userInfo.user_id != form.create_user"
@@ -311,6 +330,9 @@
    orderLogReject,
    orderLogPass,
    orderLogExport,
    jobStatusNum,
    userPublish,
    deleteOrderLog
} from '@/api/tickets/orderLog'
import { getTicketInfo } from '@/api/tickets/ticket'
import { getDictionary } from '@/api/system/dictbiz'
@@ -325,16 +347,16 @@
export default {
    name: 'TicketPage',
    data () {
    data() {
        return {
            activeTab: 'all',
            tabs: [
                { label: '全部工单', name: 'all', value: null, count: 0 },
                { label: '待审核', name: 'pending', value: 1, count: 0 },
                { label: '已驳回', name: 'processing', value: 2, count: 0 },
                { label: '已通过', name: 'inProgress', value: 3, count: 0 },
                { label: '草稿', name: 'completed', value: 0, count: 0 },
                { label: '待审核', name: 'WAIT_AUDIT', value: 1, count: 0 },
                { label: '已驳回', name: 'REJECTED', value: 2, count: 0 },
                { label: '已通过', name: 'PASS', value: 3, count: 0 },
                { label: '草稿', name: 'DRAFT', value: 0, count: 0 },
            ],
            filters: {
                key_word: '',
@@ -391,7 +413,7 @@
                    { label: '创建人', prop: 'creator_name', width: 50, ellipsis: true, overHidden: true },
                    { label: '关联机巢', prop: 'device_names', width: 80, ellipsis: true, overHidden: true },
                    {
                        label: '工单周期凭次',
                        label: '工单周期频次',
                        prop: '',
                        width: 108,
                        formatter: row => this.formatCycleTime(row),
@@ -429,7 +451,7 @@
            mapLoaded: false,
        }
    },
    async created () {
    async created() {
        var response = await getDictionary({ code: 'SF' })
        var word_order_typeResponse = await getDictionary({ code: 'WORK_ORDER_TYPE' })
        this.ai_types = response.data.data
@@ -443,15 +465,28 @@
            value: item.id,
        }))
    },
    mounted () {
    mounted() {
        this.fetchTableData()
    },
    computed: {
        ...mapGetters(['userInfo']),
        ...mapGetters(['userInfo', 'permission']),
        filteredTabs() {
            // rejection_and_draft 权限控制“已驳回”和“草稿”tab
            const canShowRejectAndDraft = this.permission?.rejection_and_draft === true;
            return this.tabs.map(tab => {
                if (tab.name === 'DRAFT') {
                    return { ...tab, isShow: canShowRejectAndDraft };
                }
                if (tab.name === 'REJECTED') {
                    return { ...tab, isShow: canShowRejectAndDraft };
                }
                return { ...tab, isShow: true };
            }).filter(tab => tab.isShow);
        },
    },
    methods: {
        searchChange (params, done) {
        searchChange(params, done) {
            console.log('searchChange')
            this.query = params
            this.parentId = ''
@@ -459,7 +494,7 @@
            this.onLoad(this.page, params)
            done()
        },
        async onLoad (page, params = {}) {
        async onLoad(page, params = {}) {
            this.loading = true
            getList(
                null,
@@ -474,11 +509,11 @@
        },
        selectionClear () {
        selectionClear() {
            this.selectionList = []
            this.$refs.crud.toggleSelection()
        },
        async loadAMapScripts () {
        async loadAMapScripts() {
            try {
                // await loadAMap();
                // await loadAMapUI();
@@ -488,12 +523,12 @@
                this.$message.error('地图加载失败,请检查网络或API Key配置')
            }
        },
        formatCycleTime (row) {
        formatCycleTime(row) {
            return row.begin_time + '-' + row.end_time + '<br/>'
                + row.rep_rule_type + row.rep_rule_val
                 + row.rep_rule_val
        },
        async fetchTableData () {
        async fetchTableData() {
            this.loading = true
            try {
                let params = this.getQueryParam()
@@ -508,12 +543,10 @@
                    return item
                })
                if (this.activeTab === 'all') {
                    this.updateGlobalCounts(records, total)
                }
                console.log('权限检查:', this.permission);
                this.page.total = total || 0
                this.updateTabCounts()
                this.updateGlobalCounts()
            } catch (error) {
                console.error('获取数据失败:', error)
                this.$message.error(error.message || '获取数据失败')
@@ -524,7 +557,7 @@
            }
        },
        getQueryParam () {
        getQueryParam() {
            const currentTab = this.tabs.find(tab => tab.name === this.activeTab)
            if (this.filters.dateRange) {
                console.log(
@@ -563,11 +596,13 @@
            }
            return params
        },
        sizeChange (pageSize) {
        sizeChange(pageSize) {
            this.page.pageSize = pageSize
        },
        async submitForm (status) {
            this.$refs.form.validate(async valid => {
        async submitForm(status) {
            console.log(this.$refs.testform, '111111')
            this.$refs.testform.validate(async valid => {
                if (valid) {
                    let dateRange = this.form.date_range
                    console.log('dateRange' + dateRange)
@@ -594,7 +629,7 @@
            })
        },
        //驳回原因显示
        async rejectDetail (id) {
        async rejectDetail(id) {
            const response = await orderLogDetails(id)
            let data = response.data.data
            this.$confirm(data.remark, '驳回原因', {
@@ -608,7 +643,7 @@
                this.detailVisible = true
            })
        },
        formatDate (date) {
        formatDate(date) {
            if (!date) return undefined
            const d = new Date(date)
            return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(
@@ -616,7 +651,7 @@
            ).padStart(2, '0')} 00:00:00`
        },
        mapStatus (status) {
        mapStatus(status) {
            const statusTextMap = {
                0: '草稿',
                1: '待审核',
@@ -626,7 +661,7 @@
            return statusTextMap[status] || '未知状态'
        },
        getStatusTagType (status) {
        getStatusTagType(status) {
            const statusMap = {
                1: 'warning',
                2: 'info',
@@ -637,19 +672,19 @@
            return statusMap[status] || 'info'
        },
        handleTabChange (tab) {
        handleTabChange(tab) {
            this.activeTab = tab.props?.name || tab.name
            this.filters.status = ''
            this.page.currentPage = 1
            this.fetchTableData()
        },
        handleSearch () {
        handleSearch() {
            this.page.currentPage = 1
            this.fetchTableData()
        },
        handleReset () {
        handleReset() {
            this.filters = {
                keyword: '',
                department: '',
@@ -661,55 +696,39 @@
            this.fetchTableData()
        },
        currentChange (currentPage) {
        currentChange(currentPage) {
            this.page.currentPage = currentPage
        },
        updateGlobalCounts (records, total) {
        async updateGlobalCounts() {
            const counts = {
                all: total,
                pending: 0,
                processing: 0,
                inProgress: 0,
                completed: 0,
                closed: 0,
                myTickets: 0,
            }
                all: 0,
                DRAFT: 0,
                WAIT_AUDIT: 0,
                REJECTED: 0,
                PASS: 0,
            records.forEach(item => {
                const tab = this.tabs.find(t => t.value === Number(item.status))
            }
            var reponse = await jobStatusNum();
            console.log("统计" + reponse.data.data);
            reponse.data.data.forEach(item => {
                const tab = this.tabs.find(t => t.name === item.dict_key);
                if (tab) {
                    counts[tab.name] = (counts[tab.name] || 0) + 1
                    tab.count = item.num;
                }
            })
            });
            this.globalCounts = counts
        },
        updateTabCounts () {
            if (this.activeTab === 'all') {
                this.tabs.forEach(tab => {
                    tab.count = this.globalCounts[tab.name] || 0
                })
            } else {
                this.tabs.forEach(tab => {
                    if (tab.name === this.activeTab) {
                        tab.count = this.tableData.length
                    } else {
                        tab.count = this.globalCounts[tab.name] || 0
                    }
                })
            }
        },
        handleAdd () {
        handleAdd() {
            this.form = {}
            this.dialogVisible = true
            //航线列表
            this.asyncgetWaylineFileListByArea()
        },
        resetForm () {
        resetForm() {
            this.form = {
                name: '',
                type: '',
@@ -720,19 +739,18 @@
                content: '',
                photos: [],
            }
            if (this.$refs.form) {
                this.$refs.form.resetFields()
            if (this.$refs.testform) {
                this.$refs.testform.resetFields()
            }
        },
        formatLocation (location) {
        formatLocation(location) {
            if (!Array.isArray(location)) {
                return '未知位置'
            }
            return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}`
        },
        async handleViewDetail (row) {
        async handleViewDetail(row) {
            const response = await orderLogDetails(row.id)
            this.form = {
                ...response.data.data,
@@ -744,7 +762,7 @@
            this.device_sns = response.data.data.deviceList
        },
        //导出
        async exportData () {
        async exportData() {
            this.$confirm('是否智飞工单数据?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
@@ -758,19 +776,55 @@
                })
            })
        },
        hasAddBtnPermission() {
            // undefined 或 false 都返回 false,只有 true 返回 true
            console.log('this.permission.order_log_add :', this.permission.order_log_add );
            return this.permission && this.permission.order_log_add === true;
        },
        hasPaddingBtnPermission() {
            // undefined 或 false 都返回 false,只有 true 返回 true
            console.log('权限检查:', this.permission);
            return this.permission && this.permission.order_log_review === true;
        },
        //自己点发布
        userPublishPush(id) {
            this.$confirm('确定发布吗?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning',
            }).then(() => {
                let response = userPublish(id);
                this.$message.success('发布成功');
                this.fetchTableData();
        refreshChange () {
            })
        },
        //删除
        deleteOrderLog(id) {
            this.$confirm('确定删除吗?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning',
            }).then(() => {
                let response = deleteOrderLog(id);
                this.$message.success('删除');
                this.fetchTableData();
            })
        },
        refreshChange() {
            this.fetchTableData()
        },
        //获取航线列表
        async asyncgetWaylineFileListByArea (name) {
        async asyncgetWaylineFileListByArea(name) {
            var wayLineListResponse = await getWaylineFileListByArea(this.userInfo.detail.areaCode)
            this.wayLineList = wayLineListResponse.data.data
            this.initMapLine()
        },
        initMapLine () {
        initMapLine() {
            let currentLine = this.wayLineList.find(item => item.wayline_id == this.form.file_id)
            if (!currentLine) return
@@ -812,7 +866,7 @@
        //可飞行机巢列表
        async getFlyingNestBy (waylineId) {
        async getFlyingNestBy(waylineId) {
            this.initMapLine()
            //按照航线来
@@ -825,7 +879,7 @@
        },
        //撤回
        async orderLogRecall (id) {
        async orderLogRecall(id) {
            this.$confirm('确定撤回则到草稿箱。', '是否撤回?', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
@@ -835,13 +889,13 @@
                this.handleSearch()
            })
        },
        onLoad () {
        onLoad() {
            this.fetchTableData()
        },
        /**
         * 通过
         */
        async orderLogPass (id) {
        async orderLogPass(id) {
            let response = await orderLogPass(id)
            let data = response.data.data
            this.$message.success('审核通过')
@@ -850,7 +904,7 @@
        /**
         * 驳回
         */
        async orderLogReject (id) {
        async orderLogReject(id) {
            this.$prompt('', '驳回原因', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
@@ -865,8 +919,8 @@
    watch: {
        tableData: {
            handler () {
                this.updateTabCounts()
            handler() {
                // this.updateTabCounts()
            },
            deep: true,
        },
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;