无人机管理后台前端(已迁走)
rjg
2025-04-14 fe985982a67406a3a6175cc9d4bcf47d45d1e406
Merge branch 'master' of http://139.196.74.78:10010/r/drone/drone-web-manage
2 files modified
423 ■■■■■ changed files
src/views/device/addDevice.vue 24 ●●●●● patch | view | raw | blame | history
src/views/tickets/ticket.vue 399 ●●●●● patch | view | raw | blame | history
src/views/device/addDevice.vue
@@ -31,6 +31,7 @@
import ConnectWebSocket from '@/websocket';
import { $Clipboard } from "@smallwei/avue";
export default {
    components: {
    },
@@ -228,8 +229,10 @@
    methods: {
        copyInfo(row, index) {
            let params = `
            host: tcp://139.196.74.78:1883
            if(import.meta.env.VITE_APP_ENV==='development'){
                // alert('地址'+import.meta.env.VITE_APP_ENV);
                let params = `
            host: tcp://139.196.74.78:1883
            账号:drone  
            密码:123456  
            组织id:${row.bing_id}  
@@ -243,6 +246,23 @@
            }).catch(() => {
                this.$message.error("复制失败");
            });
        }else{
            // alert('地址'+import.meta.env.VITE_APP_ENV);
            let params = `
            host: tcp://101.132.85.201:1883
            账号:drone
            密码:drone@123
            组织id:${row.bing_id}
            设备码:${row.bind_code}
            机场名称:${row.workspace_name}
            aircrattName:${row.workspace_desc}`;
            $Clipboard({
                text: params,
            }).then(() => {
                this.$message.success("复制成功");
            }).catch(() => {
                this.$message.error("复制失败");
            });}
        },
        init() {
            this.onLoad(this.page);
src/views/tickets/ticket.vue
@@ -57,11 +57,9 @@
    <!-- 新建工单对话框 -->
    <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" class="create-ticket-form">
        <!-- 基本信息 -->
      <el-form :model="form" :rules="rules" ref="form" label-width="90px" class="create-ticket-form">
        <div class="form-section">
          <div class="section-title">基本信息</div>
          <el-row :gutter="12">
          <el-row :gutter="16">
            <el-col :span="12">
              <el-form-item label="工单名称" prop="name">
                <el-input v-model="form.name" placeholder="请输入工单名称"></el-input>
@@ -75,11 +73,11 @@
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="12">
          <el-row :gutter="16">
            <el-col :span="12">
              <el-form-item label="所属部门" prop="department">
                <el-select v-model="form.department" placeholder="请选择所属部门" @change="handleDepartmentChange"
                  class="full-width">
                <el-select v-model="form.department" placeholder="请选择所属部门" @change="handleDepartmentChange" class="full-width">
                  <el-option v-for="dept in departments" :key="dept.value" :label="dept.label" :value="dept.value" />
                </el-select>
              </el-form-item>
@@ -92,7 +90,8 @@
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="12">
          <el-row :gutter="16">
            <el-col :span="12">
              <el-form-item label="关联算法" prop="algorithm">
                <el-select v-model="form.algorithm" placeholder="请选择关联算法" class="full-width">
@@ -100,56 +99,53 @@
                </el-select>
              </el-form-item>
            </el-col>
          </el-row>
        </div>
        <!-- 修改位置信息部分 -->
        <div class="form-section">
          <div class="section-title">位置信息</div>
          <el-row :gutter="12">
            <el-col :span="24">
              <el-form-item label="地图选址" prop="location">
                <div class="map-select">
                  <!-- 替换地图为按钮 -->
                  <avue-input-map v-model="form.location" :params="mapParams" @change="handleLocationChange"
                    type="button">
                    <el-button type="primary" plain>
                      <i class="el-icon-map-location"></i> 选择位置
            <el-col :span="12">
              <el-form-item label="选择位置" prop="location">
                <div class="location-wrapper">
                  <avue-input-map v-model="form.location" :params="mapParams" @change="handleLocationChange" type="button">
                    <el-button type="primary" plain class="map-button">
                      <i class="el-icon-map-location"></i> 地图选点
                    </el-button>
                  </avue-input-map>
                  <!-- 添加位置信息显示 -->
                  <div v-if="form.location && form.location.length >= 2" class="selected-location">
                    <p>已选位置: {{ formatLocation(form.location) }}</p>
                    <p>详细地址: {{ form.address || '获取中...' }}</p>
                  </div>
                  <div v-if="form.location?.length >= 2" class="location-text">{{ form.address || '获取地址中...' }}</div>
                </div>
              </el-form-item>
            </el-col>
          </el-row>
        </div>
        <!-- 工单内容 -->
        <div class="form-section">
          <div class="section-title">工单内容</div>
          <el-form-item label="工单描述" prop="content">
            <el-input type="textarea" v-model="form.content" rows="3" placeholder="请输入工单内容描述" resize="none"></el-input>
          </el-form-item>
          <el-form-item label="附件图片" class="upload-item">
            <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/*">
              <i class="el-icon-plus"></i>
            </el-upload>
            <div class="el-upload__tip">支持jpg/png格式图片,最多1张,单张不超过5MB</div>
          </el-form-item>
          <el-row :gutter="16">
            <el-col :span="12">
              <el-form-item label="工单内容" prop="content">
                <el-input type="textarea" v-model="form.content" :rows="4" placeholder="请输入工单内容描述"></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="附件图片" prop="photos" class="upload-wrapper">
                <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>
                </el-upload>
                <div class="upload-tip">支持jpg/png格式图片,单张不超过5MB</div>
              </el-form-item>
            </el-col>
          </el-row>
        </div>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button size="small" @click="dialogVisible = false">取 消</el-button>
          <el-button size="small" type="info" @click="saveDraft">存草稿</el-button>
          <el-button size="small" type="primary" @click="submitForm">提 交</el-button>
          <el-button type="danger" @click="submitForm">确认提交</el-button>
          <el-button type="primary" plain @click="saveDraft">存草稿</el-button>
          <el-button @click="dialogVisible = false">取 消</el-button>
        </div>
      </template>
    </el-dialog>
@@ -500,6 +496,27 @@
        department: [{ required: true, message: '请选择所属部门', trigger: 'change' }],
        handler: [{ required: true, message: '请选择处理人员', trigger: 'change' }],
        content: [{ required: true, message: '请输入工单内容', trigger: 'blur' }],
        algorithm: [{ required: true, message: '请选择关联算法', trigger: 'change' }],
        location: [{
          validator: (rule, value, callback) => {
            if (!value || value.length < 2) {
              callback(new Error('请选择位置信息'));
            } else {
              callback();
            }
          },
          trigger: 'change'
        }],
        photos: [{
          validator: (rule, value, callback) => {
            if (!this.form.photos || this.form.photos.length === 0) {
              callback(new Error('请上传工单图片'));
            } else {
              callback();
            }
          },
          trigger: 'change'
        }],
      },
      departmentUsers: {},
      loading: false,
@@ -523,6 +540,7 @@
      }, // 新增:派发表单验证规则
      stepInfos: [], // 新增:存储步骤信息
      fixedStatuses: ["2", "0", "3", "4", "5"], // 固定的五个状态
      userNameToIdMap: {}, // 新增用户名到ID的映射
    };
  },
  created() {
@@ -703,6 +721,16 @@
          label: item.dict_value, // 修改为 label
          value: item.dict_key,  // 修改为 value
        }));
        // 构建用户ID和名称的映射关系
        this.userNameToIdMap = {};
        dept_data.forEach(dept => {
          (dept.user_data || []).forEach(user => {
            this.userNameToIdMap[user.name] = user.id;
          });
        });
        console.log('用户名到ID的映射:', this.userNameToIdMap);
      } catch (error) {
        console.error('获取下拉框数据失败:', error);
        this.$message.error('加载下拉框数据失败');
@@ -796,53 +824,63 @@
    },
    async submitForm() {
      this.$refs.form.validate(async (valid) => {
        if (valid) {
          if (!this.form.location || this.form.location.length < 2) {
            this.$message.warning('请在地图上选择位置');
            return;
          }
          try {
            const submitData = {
              eventName: this.form.name,
              content: this.form.content,  // 传递工单内容
              workType: "1",
              longitude: String(this.form.location[0]),
              latitude: String(this.form.location[1]),
              address: this.form.address,
              eventDictKey: this.form.type,
              aiType: this.form.algorithm,
              updateUser: this.form.handler,
              createDept: this.form.department,
              isDraft: 0
            };
            // 如果是编辑状态,添加 id
            if (this.form.id) {
              submitData.id = this.form.id;
            }
            // 获取文件对象
            let file = null;
            if (this.form.photos && this.form.photos.length > 0) {
              file = this.form.photos[0].raw;
            }
            const response = await createTicket(submitData, file);
            if (response.data.code === 0) {
              this.$message.success('工单创建成功');
              this.dialogVisible = false;
              this.fetchTableData();
            } else {
              throw new Error(response.data.msg || '创建失败');
            }
          } catch (error) {
            console.error('提交失败:', error);
            this.$message.error(error.message || '工单创建失败,请稍后重试');
          }
      try {
        // 提交时需要完整验证
        await this.$refs.form.validate();
        // 验证位置信息
        if (!this.form.location || this.form.location.length < 2) {
          this.$message.warning('请在地图上选择位置');
          return;
        }
      });
        // 验证图片上传
        if (!this.form.photos || this.form.photos.length === 0) {
          this.$message.warning('请上传工单图片');
          return;
        }
        const submitData = {
          eventName: this.form.name,
          content: this.form.content,
          workType: "1",
          longitude: String(this.form.location[0]),
          latitude: String(this.form.location[1]),
          address: this.form.address,
          eventDictKey: this.form.type,
          aiType: this.form.algorithm,
          updateUser: this.form.handler,
          createDept: this.form.department,
          isDraft: 0
        };
        if (this.form.id) {
          submitData.id = this.form.id;
        }
        // 获取文件对象
        const file = this.form.photos[0].raw;
        if (!file) {
          this.$message.warning('图片文件无效,请重新上传');
          return;
        }
        const response = await createTicket(submitData, file);
        if (response.data.code === 0) {
          this.$message.success('工单创建成功');
          this.dialogVisible = false;
          this.fetchTableData();
        } else {
          throw new Error(response.data.msg || '创建失败');
        }
      } catch (error) {
        console.error('提交失败:', error);
        if (error.message.includes('验证未通过')) {
          this.$message.warning('请填写完整的工单信息');
        } else {
          this.$message.error(error.message || '工单创建失败,请稍后重试');
        }
      }
    },
    async saveDraft() {
@@ -850,7 +888,7 @@
        const submitData = {
          id: this.form.id,
          eventName: this.form.name || undefined,
          content: this.form.content || undefined,  // 传递工单内容
          content: this.form.content || undefined,
          workType: "1",
          longitude: this.form.location?.[0] ? String(this.form.location[0]) : undefined,
          latitude: this.form.location?.[1] ? String(this.form.location[1]) : undefined,
@@ -862,14 +900,10 @@
          isDraft: 1
        };
        // 确保即使为空字符串也会传入 content
        if (this.form.content !== undefined) {
          submitData.content = this.form.content;
        }
        // 如果是编辑状态,添加 id
        if (this.form.id) {
          submitData.id = this.form.id;
        // 草稿时也至少需要工单名称
        if (!submitData.eventName) {
          this.$message.warning('请至少输入工单名称');
          return;
        }
        // 过滤掉所有 undefined 的字段
@@ -877,13 +911,12 @@
          submitData[key] === undefined && delete submitData[key]
        );
        // 获取文件对象
        let file = null;
        if (this.form.photos && this.form.photos.length > 0) {
          file = this.form.photos[0].raw;
        }
        const response = await createTicket(submitData, file); // 使用 createTicket 而不是 flowEvent
        const response = await createTicket(submitData, file);
        if (response.data.code === 0) {
          this.$message.success('草稿保存成功');
          this.dialogVisible = false;
@@ -1322,6 +1355,7 @@
      if (this.currentDetail.status !== 3) {
        this.$message.warning('只有处理中状态的工单可以提交处理详情');
        return;
      }
      try {
@@ -1451,22 +1485,51 @@
    // 添加编辑方法
    handleEdit(row) {
      // 从原始数据中获取 id 和 dept_id
      const originalDeptId = row.department?.value || row.dept_id;
      console.log('编辑原始数据:', row);
      // 尝试从row.dept_id或通过部门名称查找对应的部门ID
      let deptId = row.dept_id;  // 优先使用原始数据中的dept_id
      // 如果没有dept_id,则通过部门名称查找
      if (!deptId) {
        // 输出所有可用的部门数据用于调试
        console.log('可用部门列表:', this.departments);
        console.log('当前部门名称:', row.department);
        const deptInfo = this.departments.find(dept =>
          dept.label && dept.label.trim() === row.department.trim()
        );
        deptId = deptInfo?.value;
      }
      // 获取工单类型值 - 从types中找到匹配的值
      const typeValue = this.types.find(t => t.label === row.type)?.value || row.event_dict_key;
      // 获取处理人ID - 使用userNameToIdMap映射
      const handlerId = this.userNameToIdMap[row.handler] || row.handler;
      console.log('数据映射:', {
        部门名称: row.department,
        找到的部门ID: deptId,
        原始处理人: row.handler,
        处理人ID: handlerId,
        原始工单类型: row.type,
        映射后类型值: typeValue
      });
      this.form = {
        id: row.id, // 保存原始 id
        id: row.id,
        name: row.orderName,
        type: row.event_dict_key, // 使用原始 key 值
        department: originalDeptId,
        handler: row.handler,
        algorithm: row.content,
        type: typeValue,
        department: deptId,
        handler: handlerId, // 使用映射后的处理人ID
        algorithm: row.aiType,
        location: row.location,
        address: row.address,
        content: row.content,
        photos: [],
        content: row.content,
      };
      // 如果有图片,添加到表单中
      if (row.photo_url) {
        this.form.photos = [{
@@ -1474,9 +1537,9 @@
          url: row.photo_url
        }];
      }
      this.dialogVisible = true;
    },
    // 添加删除方法
    handleDelete(row) {
      this.$confirm('确认删除该工单?', '提示', {
@@ -1588,69 +1651,105 @@
}
.create-ticket-form {
  padding: 10px;
  padding: 20px 10px;
  .form-section {
    margin-bottom: 15px;
    background: #f8f9fa;
    background-color: #fff;
    border-radius: 4px;
    padding: 12px;
    .section-title {
      font-size: 14px;
      font-weight: 500;
      color: #303133;
      margin-bottom: 12px;
      padding-left: 8px;
      border-left: 3px solid #409EFF;
    }
    .el-row {
      margin-bottom: 16px;
    .el-form-item {
      margin-bottom: 12px;
      &:last-child {
        margin-bottom: 0;
      }
    }
  }
  .map-select {
    display: flex;
    align-items: center;
    gap: 10px;
  .location-wrapper {
    .map-button {
      width: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      i {
        margin-right: 4px;
      }
    }
    .location-text {
      margin-top: 8px;
      padding: 8px 12px;
      background-color: #f5f7fa;
      border-radius: 4px;
      color: #606266;
      font-size: 13px;
      line-height: 1.4;
    }
  }
  .upload-item {
    margin-bottom: 0;
  }
  .upload-wrapper {
    .uploader {
      :deep(.el-upload--picture-card) {
        width: 120px;
        height: 120px;
        line-height: 128px;
      }
  .ticket-upload {
    :deep(.el-upload--picture-card) {
      width: 100px;
      height: 100px;
      line-height: 100px;
      margin: 0 8px 8px 0;
      :deep(.el-upload-list__item) {
        width: 120px;
        height: 120px;
      }
    }
    :deep(.el-upload-list--picture-card .el-upload-list__item) {
      width: 100px;
      height: 100px;
      margin: 0 8px 8px 0;
    .upload-tip {
      font-size: 12px;
      color: #909399;
      line-height: 1.4;
      margin-top: 8px;
    }
  }
  .el-upload__tip {
    font-size: 12px;
    color: #909399;
    line-height: 1.2;
    margin-top: 5px;
  .el-form-item {
    margin-bottom: 18px;
    &:last-child {
      margin-bottom: 0;
    }
  }
  :deep(.el-form-item__label) {
    font-weight: 500;
    color: #606266;
  }
  :deep(.el-input__inner) {
    height: 36px;
    line-height: 36px;
  }
  :deep(.el-textarea__inner) {
    padding: 8px 12px;
  }
}
.dialog-footer {
  text-align: right;
  padding-top: 10px;
  padding-top: 16px;
  border-top: 1px solid #ebeef5;
  .el-button + .el-button {
    margin-left: 12px;
  }
  .el-button {
    padding: 9px 20px;
    &:last-child {
      margin-left: 12px;
    }
  }
}
.map-container {