riku
2025-07-29 056ea576d820729878ffd62cd54cd7598e72d07e
src/views/overlay-clue/report/components/QuestionDetail.vue
@@ -7,7 +7,11 @@
    destroy-on-close
  >
    <template #header>
      <span> 添加问题</span>
      <span>
        {{
          uploaded ? '问题详情' : createMode ? '添加问题' : '修改问题'
        }}</span
      >
    </template>
    <el-form
      label-width="90px"
@@ -18,19 +22,25 @@
    >
      <el-form-item label="问题名称" prop="cqName">
        <el-input
          :disabled="uploaded"
          v-model="formObj.cqName"
          placeholder="请输入问题名称"
        ></el-input>
      </el-form-item>
      <el-form-item label="问题描述" prop="cqDescription">
        <el-input
          :disabled="uploaded"
          v-model="formObj.cqDescription"
          type="textarea"
          placeholder="请输入问题描述"
        ></el-input>
      </el-form-item>
      <el-form-item label="所在街镇" prop="cqStreet">
        <el-select v-model="formObj.cqStreet" placeholder="所在街镇">
        <el-select
          v-model="formObj.cqStreet"
          placeholder="所在街镇"
          :disabled="uploaded"
        >
          <el-option
            v-for="s in streets"
            :key="s.value"
@@ -41,22 +51,29 @@
      </el-form-item>
      <el-form-item label="详细地址" prop="cqAddress">
        <el-input
          :disabled="uploaded"
          v-model="formObj.cqAddress"
          placeholder="请输入地址或者通过“坐标拾取”自动获得"
        ></el-input>
      </el-form-item>
      <el-form-item label="坐标" prop="coordinate">
        <el-input
          :disabled="uploaded"
          style="width: 300px; margin-right: 8px"
          v-model="formObj.coordinate"
          placeholder="经纬度坐标,格式为121.123452,31.231235"
        ></el-input>
        <el-button plain type="primary" @click="openMapDialog"
        <el-button
          :disabled="uploaded"
          plain
          type="primary"
          @click="openMapDialog"
          >坐标拾取</el-button
        >
      </el-form-item>
      <el-form-item label="问题图片" prop="files">
        <el-upload
          :class="uploadableClz"
          ref="uploadRef"
          :file-list="fileList"
          action=""
@@ -64,16 +81,50 @@
          list-type="picture-card"
          name="images"
          accept="image/png, image/jpeg"
          :limit="3"
          :limit="maxImageCount"
          multiple
          :on-preview="handleFilePreview"
          :on-remove="handleFileRemove"
          :on-change="handleFileChange"
        >
          <el-icon><Plus /></el-icon>
          <template #file="{ file }">
            <div>
              <el-image
                class="el-upload-list__item-thumbnail"
                :src="file.url"
                fit="cover"
                alt=""
              />
              <span class="el-upload-list__item-actions">
                <span
                  class="el-upload-list__item-preview"
                  @click="handleFilePreview(file)"
                >
                  <el-icon><zoom-in /></el-icon>
                </span>
                <!-- <span
                  v-if="!disabled"
                  class="el-upload-list__item-delete"
                  @click="handleDownload(file)"
                >
                  <el-icon><Download /></el-icon>
                </span> -->
                <span
                  v-if="!uploaded"
                  class="el-upload-list__item-delete"
                  @click="handleFileRemove(file)"
                >
                  <el-icon><Delete /></el-icon>
                </span>
              </span>
            </div>
          </template>
          <template #tip>
            <div class="el-upload__tip">
              请选择小于500kb的jpg/png图片,最多3张
              {{
                `请选择小于5MB的jpg/png图片,最多${maxImageCount}张`
              }}
            </div>
          </template>
        </el-upload>
@@ -99,37 +150,79 @@
  ></el-image-viewer>
  <MapSearch
    v-model:show="mapDialogShow"
    :defaultCoor="
      formObj.coordinate ? formObj.coordinate.split(',') : undefined
    "
    @on-submit="selectAddress"
  ></MapSearch>
</template>
<script setup>
import { reactive, ref, watch, computed } from 'vue';
import { reactive, ref, watch, computed, inject } from 'vue';
import { ElMessage } from 'element-plus';
import { useFormConfirm } from '@/composables/formConfirm';
import { streets } from '@/constant/street';
import clueQuestionApi from '@/api/clue/clueQuestionApi';
import { $clue } from '@/api/index';
import MapSearch from '@/components/map/MapSearch.vue';
// 图片限定大小(单位:B)
const IMG_MAX_SIZE = 4 * 1024 * 1024;
// 决定当前是否是内部线索相关操作
const isInternal = inject('isInternal', false);
const props = defineProps({
  clueId: Number,
  // 应急线索对象
  clueData: {
    type: Object,
    default: () => {
      return {};
    }
  },
  // 对话框显示控制
  show: Boolean,
  question: Object
  // 线索问题对象
  question: Object,
  // 问题是否已上传
  uploaded: {
    type: Boolean,
    default: false
  },
  maxImageCount: {
    type: Number,
    default: 3
  }
});
const emit = defineEmits(['update:show', 'onSubmit', 'onClose']);
// 创建或是修改模式
const createMode = ref(true);
// 上报弹出框
const dialogShow = ref(false);
const mapDialogShow = ref(false);
const uploadRef = ref();
const fileList = ref([]);
// 更新模式下,记录被删除的原有图片
let deletedFileList = [];
// 决定是否能上传图片
const uploadableClz = computed(() => {
  return props.uploaded ||
    (fileList.value && fileList.value.length >= props.maxImageCount)
    ? 'question-not-upload'
    : '';
});
const previewShow = ref(false);
const initialIndex = ref(0);
const urlList = computed(() =>
  fileList.value.map((value) => {
    return value.url;
  })
  fileList.value
    ? fileList.value.map((value) => {
        return value.url;
      })
    : []
);
function handleFilePreview(file) {
@@ -141,12 +234,33 @@
  previewShow.value = false;
}
function handleFileRemove(file, fileList) {
  formObj.value.files = fileList;
function handleFileRemove(file, files) {
  if (!createMode.value) {
    if (file.url.indexOf($clue.imgUrl) != -1) {
      const originUrl = file.url.replace($clue.imgUrl, '');
      deletedFileList.push(originUrl);
    }
  }
  const index = fileList.value.indexOf(file);
  fileList.value.splice(index, 1);
  // fileList.value = fileList;
  edit.value = true;
}
function handleFileChange(file, fileList) {
  formObj.value.files = fileList;
function handleFileChange(file, files) {
  // console.log('fileSelect', file);
  // 判断
  if (file.size > IMG_MAX_SIZE) {
    const index = files.indexOf(file);
    files.splice(index, 1);
    ElMessage({
      message: '图片大小超过限制',
      type: 'error'
    });
  } else {
    fileList.value = files;
  }
  edit.value = true;
}
const { formObj, formRef, edit, onSubmit, onCancel, clear } =
@@ -195,7 +309,7 @@
      message: '坐标不能为空',
      trigger: 'blur'
    }
  ],
  ]
  // cqLongitude: [
  //   {
  //     required: true,
@@ -210,31 +324,48 @@
  //     trigger: 'blur'
  //   }
  // ],
  files: [
    {
      required: true,
      message: '图片不能为空',
      trigger: 'change'
    }
  ]
  // files: [
  //   {
  //     required: true,
  //     message: '图片不能为空',
  //     trigger: 'change'
  //   }
  // ]
});
function submit() {
  if (!fileList.value || fileList.value.length == 0) {
    ElMessage({
      message: '至少上传一张图片',
      type: 'error'
    });
    return;
  }
  const coor = formObj.value.coordinate.split(',');
  const q = {
    cId: parseInt(props.clueId),
    cqName: formObj.value.cqName,
    cqDescription: formObj.value.cqDescription,
    cqStreet: formObj.value.cqStreet,
    cqAddress: formObj.value.cqAddress,
    ...formObj.value,
    // cqId: formObj.value.cqId,
    cId: parseInt(props.clueData.cid),
    // cqName: formObj.value.cqName,
    // cqDescription: formObj.value.cqDescription,
    // cqStreet: formObj.value.cqStreet,
    // cqAddress: formObj.value.cqAddress,
    cqLongitude: parseFloat(coor[0]),
    cqLatitude: parseFloat(coor[1])
    cqLatitude: parseFloat(coor[1]),
    cqInternal: isInternal
    // cqFilePath: formObj.value.cqFilePath
  };
  const files = [];
  formObj.value.files.forEach((f) => {
    files.push(f.raw);
  });
  return uploadQuestion(q, files);
  if (fileList.value) {
    fileList.value.forEach((f) => {
      if (f.url.indexOf($clue.imgUrl) == -1) {
        files.push(f.raw);
      }
    });
  }
  return createMode.value
    ? uploadQuestion(q, files)
    : updateQuestion(q, files);
}
function cancel() {
@@ -256,12 +387,27 @@
 */
function uploadQuestion(question, files) {
  loading.value = true;
  return clueQuestionApi
    .uploadQuestion(question, files)
    .then(() => {
      dialogShow.value = false;
      clear();
      // clear();
      uploadRef.value.clearFiles();
      emit('onSubmit');
    })
    .finally(() => {
      loading.value = false;
    });
}
function updateQuestion(question, newFiles) {
  loading.value = true;
  const deleteImgUrl = deletedFileList.join(';');
  return clueQuestionApi
    .updateQuestion(question, newFiles, deleteImgUrl)
    .then(() => {
      dialogShow.value = false;
      // clear();
      uploadRef.value.clearFiles();
      emit('onSubmit');
    })
@@ -273,12 +419,11 @@
function parseFormObj(question) {
  question.coordinate =
    question.cqLongitude + ',' + question.cqLatitude;
  fileList.value = [];
  question.cqFilePath.forEach((f, index) => {
    fileList.value.push({
  fileList.value = question.files.map((f, index) => {
    return {
      name: `${index}`,
      url: f
    });
    };
  });
  return { ...question };
}
@@ -289,26 +434,28 @@
    dialogShow.value = val[0];
    if (val[0]) {
      fileList.value = [];
      deletedFileList = [];
      if (val[1]) {
        formObj.value = parseFormObj(val[1]);
        createMode.value = false;
        formObj.value = parseFormObj(val[1]);
      } else {
        createMode.value = true;
        formObj.value = {};
      }
      // edit.value = false
    }
  }
);
// watch(
//   () => props.question,
//   (val) => {
//     fileList.value = [];
//     if (val) {
//       formObj.value = parseFormObj(val);
//     } else {
//       formObj.value = {};
//     }
//   }
// );
watch(dialogShow, (val) => {
  if (!val) {
    clear();
  }
  emit('update:show', val);
});
</script>
<style scoped>
:deep(.question-not-upload .el-upload-list > .el-upload) {
  display: none;
}
</style>