|
|
@@ -0,0 +1,464 @@
|
|
|
+<template>
|
|
|
+ <div class="diseases-dictionary-detail">
|
|
|
+ <div class="page-title" @click="goBack">
|
|
|
+ <el-icon class="title-icon" color="rgba(0, 0, 0, 0.8)" size="16"><ArrowLeftBold /></el-icon>
|
|
|
+ 识别结果
|
|
|
+ </div>
|
|
|
+ <div class="detail-content">
|
|
|
+ <div class="detail-img">
|
|
|
+ <Swipe class="card-swipe-wrapper" :show-indicators="imgKey && imgKey.length > 1" :loop="false">
|
|
|
+ <SwipeItem v-for="(img, index) in imgKey" :key="index">
|
|
|
+ <div class="card-item">
|
|
|
+ <div class="ing-wrap" v-if="isRecognize">
|
|
|
+ <div>
|
|
|
+ <el-icon size="20" class="is-loading">
|
|
|
+ <Loading />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ 正在识别,请稍后...
|
|
|
+ </div>
|
|
|
+ <img class="card-bg" :src="displayUrls[index]" />
|
|
|
+ <div class="card-content" v-if="!isRecognize && resultsByIndex[index]?.status === 'ok'">
|
|
|
+ <div class="title-ques">
|
|
|
+ <div class="ques-text">病虫名称:{{ resultsByIndex[index]?.data?.name }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="dialog-famous">管理方法:</div>
|
|
|
+ <div class="dialog-answer">
|
|
|
+ {{ resultsByIndex[index]?.data?.cure }}
|
|
|
+ </div>
|
|
|
+ <div v-if="resultsByIndex[index]?.data?.info" class="advice-wrap">
|
|
|
+ <div class="item-tag">基本信息</div>
|
|
|
+ <div v-html="resultsByIndex[index]?.data?.info"></div>
|
|
|
+ </div>
|
|
|
+ <div class="famous-info" @click="toDetail(index)">
|
|
|
+ <img src="@/assets/img/home/link-icon.png" />
|
|
|
+ <span>点击查看农事详情</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="card-content no-data" v-if="!isRecognize && resultsByIndex[index]?.status === 'nodata'">
|
|
|
+ <img src="@/assets/img/home/good-fill.png" />
|
|
|
+ 长势良好,并未发现病虫害
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </SwipeItem>
|
|
|
+ </Swipe>
|
|
|
+ </div>
|
|
|
+ <div class="btn-wrap" v-if="!isRecognize">
|
|
|
+ <div class="btn primary" @click="goBack">咨询专家</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 农事信息弹窗 -->
|
|
|
+ <detail-dialog ref="detailDialogRef" :show-success-only="true"></detail-dialog>
|
|
|
+ <!-- 上传弹窗组件 -->
|
|
|
+ <active-upload-popup></active-upload-popup>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { onMounted, ref } from "vue";
|
|
|
+import { Swipe, SwipeItem } from "vant";
|
|
|
+import { useRouter, useRoute } from "vue-router";
|
|
|
+import { base_img_url2 } from "@/api/config.js";
|
|
|
+import { ElMessage } from "element-plus";
|
|
|
+import MqttClient from "@/mqtt/MqttClient";
|
|
|
+import detailDialog from "@/components/detailDialog.vue";
|
|
|
+import activeUploadPopup from "@/components/popup/activeUploadPopup.vue";
|
|
|
+
|
|
|
+let resize = "?x-oss-process=image/resize,w_300";
|
|
|
+const route = useRoute();
|
|
|
+const json = JSON.parse(route.query.json || "{}");
|
|
|
+// const json = JSON.parse(
|
|
|
+// `{"imgKey":["birdseye-look-mini/766/1761968109259.png","birdseye-look-mini/766/1761968110225.png"],"farmId":766,"id":"65","imageIds":["772470289337421824","772470289337421825"],"token":"bcc0e12d-bff6-4f1f-8edc-2ab80b19af41"}`)
|
|
|
+const imgKey = json.imgKey;
|
|
|
+const farmId = json.farmId;
|
|
|
+const imageIds = json.imageIds || [];
|
|
|
+
|
|
|
+const isRecognize = ref(true);
|
|
|
+const noData = ref(false);
|
|
|
+const resultsByIndex = ref({});
|
|
|
+
|
|
|
+// 存储每张图片的 farmWorkLibId,用于后续接口请求
|
|
|
+const farmWorkLibIdsByIndex = ref({});
|
|
|
+
|
|
|
+// 预置显示地址:默认使用传入的 cloudFilename
|
|
|
+const displayUrls = ref(imgKey.map((p) => base_img_url2 + p + resize));
|
|
|
+const detailDialogRef = ref(null);
|
|
|
+onMounted(() => {
|
|
|
+ const mqttClient = new MqttClient(["farm/pest_recognition/task/" + farmId], mqttListener);
|
|
|
+ mqttClient.connect();
|
|
|
+});
|
|
|
+
|
|
|
+const mqttListener = (topic, message) => {
|
|
|
+ let resData = JSON.parse(message);
|
|
|
+ if (json.id == resData.taskId) {
|
|
|
+ if (resData.status === 1) {
|
|
|
+ const resultMap = resData.result || {};
|
|
|
+ // 初始化每张图片的结果状态
|
|
|
+ const fetchPromises = [];
|
|
|
+ imageIds.forEach((imgId, index) => {
|
|
|
+ const resultData = resultMap[imgId];
|
|
|
+ // 新数据结构:可能是 null 或数组,数组中的对象包含 farmWorkLibId 和 pestDiseaseId
|
|
|
+ if (Array.isArray(resultData) && resultData.length > 0) {
|
|
|
+ // 提取 pestDiseaseId 和 farmWorkLibId
|
|
|
+ const pestDiseaseIds = [];
|
|
|
+ const farmWorkLibIdList = [];
|
|
|
+
|
|
|
+ resultData.forEach((item) => {
|
|
|
+ if (item && item.pestDiseaseId) {
|
|
|
+ pestDiseaseIds.push(item.pestDiseaseId);
|
|
|
+ }
|
|
|
+ if (item && item.farmWorkLibId) {
|
|
|
+ farmWorkLibIdList.push(item.farmWorkLibId);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 按索引保存 farmWorkLibId 数组
|
|
|
+ if (farmWorkLibIdList.length > 0) {
|
|
|
+ farmWorkLibIdsByIndex.value[index] = farmWorkLibIdList;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用 pestDiseaseId 调用病虫详情接口
|
|
|
+ if (pestDiseaseIds.length > 0) {
|
|
|
+ const p = VE_API.home.fetchDiseaseDetail(pestDiseaseIds).then(({ data, code: respCode }) => {
|
|
|
+ if (respCode === 0) {
|
|
|
+ const detail = Array.isArray(data) ? data[0] : data;
|
|
|
+ resultsByIndex.value = {
|
|
|
+ ...resultsByIndex.value,
|
|
|
+ [index]: { status: 'ok', data: detail }
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ resultsByIndex.value = {
|
|
|
+ ...resultsByIndex.value,
|
|
|
+ [index]: { status: 'nodata' }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ resultsByIndex.value = {
|
|
|
+ ...resultsByIndex.value,
|
|
|
+ [index]: { status: 'nodata' }
|
|
|
+ };
|
|
|
+ });
|
|
|
+ fetchPromises.push(p);
|
|
|
+ } else {
|
|
|
+ // 没有 pestDiseaseId,但有 farmWorkLibId,可能只有农事信息
|
|
|
+ resultsByIndex.value = {
|
|
|
+ ...resultsByIndex.value,
|
|
|
+ [index]: { status: 'nodata' }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 结果为 null 或空数组
|
|
|
+ resultsByIndex.value = {
|
|
|
+ ...resultsByIndex.value,
|
|
|
+ [index]: { status: 'nodata' }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ VE_API.farm.listByIds(resData.imageIds).then(({ data, code: respCode }) => {
|
|
|
+ if (respCode === 0) {
|
|
|
+ // 按 imageIds 顺序,为每张图优先选择 cloudResFilename,否则 cloudFilename
|
|
|
+ const idToRecord = {};
|
|
|
+ (data || []).forEach((item) => {
|
|
|
+ if (item && item.id) idToRecord[item.id] = item;
|
|
|
+ });
|
|
|
+ const urls = [...displayUrls.value];
|
|
|
+ imageIds.forEach((id, idx) => {
|
|
|
+ const rec = idToRecord[id];
|
|
|
+ if (rec) {
|
|
|
+ const path = (rec.cloudResFilename && rec.cloudResFilename.trim() !== '')
|
|
|
+ ? rec.cloudResFilename
|
|
|
+ : (rec.cloudFilename || imgKey[idx]);
|
|
|
+ urls[idx] = base_img_url2 + path + resize;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ displayUrls.value = urls;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (fetchPromises.length > 0) {
|
|
|
+ Promise.allSettled(fetchPromises).finally(() => {
|
|
|
+ isRecognize.value = false;
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 全部无病虫
|
|
|
+ noData.value = true;
|
|
|
+ isRecognize.value = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const router = useRouter();
|
|
|
+const goBack = () => {
|
|
|
+ // router.go(-1);
|
|
|
+ router.push(`/home`);
|
|
|
+};
|
|
|
+
|
|
|
+const toDetail = (index) => {
|
|
|
+ // 根据当前图片的索引获取对应的 farmWorkLibId
|
|
|
+ const farmWorkLibIdList = farmWorkLibIdsByIndex.value[index];
|
|
|
+ const farmWorkLibId = Array.isArray(farmWorkLibIdList) && farmWorkLibIdList.length > 0
|
|
|
+ ? farmWorkLibIdList[0]
|
|
|
+ : null;
|
|
|
+
|
|
|
+ // 调用子组件方法,传递 farmWorkLibId 参数
|
|
|
+ if (farmWorkLibId) {
|
|
|
+ detailDialogRef.value.showDialog(farmWorkLibId);
|
|
|
+ } else {
|
|
|
+ ElMessage.warning('暂无农事详情');
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.diseases-dictionary-detail {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 100vh;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #fff;
|
|
|
+
|
|
|
+ .page-title {
|
|
|
+ height: 44px;
|
|
|
+ line-height: 44px;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 17px;
|
|
|
+ font-weight: bold;
|
|
|
+ // border-bottom: 1px solid #ededed;
|
|
|
+ .title-icon {
|
|
|
+ cursor: pointer;
|
|
|
+ position: absolute;
|
|
|
+ left: 16px;
|
|
|
+ top: 13px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .detail-content {
|
|
|
+ height: calc(100% - 44px - 20px);
|
|
|
+ overflow: auto;
|
|
|
+ .detail-img {
|
|
|
+ height: calc(100% - 100px);
|
|
|
+ position: relative;
|
|
|
+ padding: 4px 16px 30px 16px;
|
|
|
+ .card-swipe-wrapper {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 24px 0 36px 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ :deep(.van-swipe-item) {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+ :deep(.van-swipe__indicators) {
|
|
|
+ bottom: 10px;
|
|
|
+ }
|
|
|
+ :deep(.van-swipe__indicator) {
|
|
|
+ width: 6px;
|
|
|
+ height: 6px;
|
|
|
+ background-color: #cccccc;
|
|
|
+ border-radius: 50%;
|
|
|
+ margin: 0 4px;
|
|
|
+ transition: all 0.3s;
|
|
|
+ }
|
|
|
+ :deep(.van-swipe__indicator--active) {
|
|
|
+ width: 20px;
|
|
|
+ height: 6px;
|
|
|
+ background-color: #2199f8;
|
|
|
+ border-radius: 3px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .card-item {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+ .ing-wrap {
|
|
|
+ color: #fff;
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ margin: 0 auto;
|
|
|
+ background: rgba(0, 0, 0, 0.6);
|
|
|
+ border-radius: 12px;
|
|
|
+ width: 164px;
|
|
|
+ height: 95px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 10;
|
|
|
+ }
|
|
|
+ .card-bg {
|
|
|
+ border-radius: 24px 0 36px 4px;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ }
|
|
|
+ .no-data {
|
|
|
+ color: #fff;
|
|
|
+ text-align: center;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ img {
|
|
|
+ width: 17px;
|
|
|
+ margin-right: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .card-content {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ padding: 12px 12px 26px 12px;
|
|
|
+ border-radius: 24px 0 36px 4px;
|
|
|
+ color: #ffffff;
|
|
|
+ background: rgba(0, 0, 0, 0.6);
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
+ max-height: calc(100% - 38px);
|
|
|
+ overflow: auto;
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+ &.no-data {
|
|
|
+ padding: 16px 0 20px 0;
|
|
|
+ }
|
|
|
+ .ques-text {
|
|
|
+ color: #ffd786;
|
|
|
+ font-size: 18px;
|
|
|
+ position: relative;
|
|
|
+ padding-left: 12px;
|
|
|
+ }
|
|
|
+ .ques-text:after {
|
|
|
+ content: "";
|
|
|
+ position: absolute;
|
|
|
+ width: 4px;
|
|
|
+ height: 15px;
|
|
|
+ top: 5px;
|
|
|
+ left: 0;
|
|
|
+ border-radius: 2px;
|
|
|
+ background: #ffd186;
|
|
|
+ }
|
|
|
+ .dialog-famous {
|
|
|
+ padding: 8px 0 4px 0;
|
|
|
+ color: #ffd786;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+ .dialog-answer {
|
|
|
+ line-height: 24px;
|
|
|
+ }
|
|
|
+ .advice-wrap {
|
|
|
+ padding-top: 12px;
|
|
|
+ }
|
|
|
+ .item-tag {
|
|
|
+ width: fit-content;
|
|
|
+ padding: 4px 10px;
|
|
|
+ border-radius: 20px;
|
|
|
+ color: #ffd786;
|
|
|
+ background: rgba(255, 215, 134, 0.2);
|
|
|
+ margin-bottom: 6px;
|
|
|
+ }
|
|
|
+ .info {
|
|
|
+ padding: 10px 10px 6px 0;
|
|
|
+ font-size: 15px;
|
|
|
+ }
|
|
|
+ .desc {
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 24px;
|
|
|
+ }
|
|
|
+ .famous-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ color: #ffd786;
|
|
|
+ font-size: 16px;
|
|
|
+ padding: 8px 0 0px 0;
|
|
|
+ img {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ margin-right: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .btn-wrap {
|
|
|
+ width: 100%;
|
|
|
+ height: 56px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin: 0 auto;
|
|
|
+ padding: 0 16px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ .btn {
|
|
|
+ height: 40px;
|
|
|
+ line-height: 40px;
|
|
|
+ font-size: 16px;
|
|
|
+ text-align: center;
|
|
|
+ color: #000;
|
|
|
+ border-radius: 4px;
|
|
|
+ &.primary {
|
|
|
+ flex: 1;
|
|
|
+ color: #fff;
|
|
|
+ background: #2199f8;
|
|
|
+ }
|
|
|
+ &.share {
|
|
|
+ background: #fff;
|
|
|
+ width: 20%;
|
|
|
+ min-width: 80px;
|
|
|
+ text-align: center;
|
|
|
+ border: 1px solid #8e8e8e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .btn + .btn {
|
|
|
+ margin-left: 15px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .box-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #000;
|
|
|
+ padding: 16px 2px 16px 0;
|
|
|
+ }
|
|
|
+ .padding-t {
|
|
|
+ padding-top: 26px;
|
|
|
+ }
|
|
|
+ .expert-box {
|
|
|
+ .expert-item {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ }
|
|
|
+ .expert-l {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ .expert-name {
|
|
|
+ padding-left: 10px;
|
|
|
+ }
|
|
|
+ .expert-tag {
|
|
|
+ background: #f7ecc7;
|
|
|
+ padding: 2px 6px;
|
|
|
+ color: #ae7d22;
|
|
|
+ width: fit-content;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+ img {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .line {
|
|
|
+ margin: 16px;
|
|
|
+ width: calc(100% - 32px);
|
|
|
+ height: 1px;
|
|
|
+ background: #f1f1f1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|