Files
threeonecheck_web/pages/Inspectionresult/Inspectionresult.vue
2026-06-03 10:16:37 +08:00

1513 lines
40 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page padding">
<!-- 进度显示 -->
<view class="progress-bar" v-if="checkData">
<view class="progress-text">
<text class="current-index"> {{ checkData.currentIndex || 1 }} 个问题</text>
<text class="total-count"> / {{ checkData.totalCount || 1 }} </text>
</view>
<view class="progress-line">
<view class="progress-inner" :style="{ width: progressPercent + '%' }"></view>
</view>
</view>
<view class="padding bg-white radius">
<view class="text-bold">{{ checkData?.name || '加载中...' }}</view>
<view class="margin-top">
<rich-text :nodes="checkData?.point || ''"></rich-text>
</view>
<view class="margin-top">
<u-radio-group
v-model="radiovalue1"
placement="row"
@change="groupChange"
>
<u-radio
:customStyle="{marginBottom: '8px'}"
v-for="(item, index) in radiolist1"
:key="index"
:label="item.name"
:name="item.name"
@change="radioChange"
>
</u-radio>
</u-radio-group>
</view>
<!-- 异常时显示隐患信息区域 -->
<view v-if="radiovalue1 === '异常'" class="hazard-section margin-top">
<view class="hazard-tip">
<text class="cuIcon-warn text-yellow margin-right-xs"></text>
<text class="text-orange">检查结果为异常需填写隐患信息</text>
</view>
<!-- 未填写时显示按钮 -->
<view v-if="!hasHazardData" class="hazard-btn" @click="openHazardPopup">
<text class="text-blue">填写隐患信息</text>
</view>
<!-- 已填写时显示卡片 -->
<view v-else class="hazard-card">
<view class="card-header">
<view class="text-bold text-black">{{ hazardFormData.title }}</view>
<view class="level-tag" :class="{
'level-minor': hazardFormData.level === 0,
'level-normal': hazardFormData.level === 1,
'level-major': hazardFormData.level === 2
}">{{ levelOptions[hazardFormData.level]?.title }}</view>
</view>
<view class="card-body">
<view class="info-row">
<text class="text-gray">隐患来源</text>
<text>{{ sourceOptions[hazardFormData.source]?.title || '-' }}</text>
</view>
<view class="info-row">
<text class="text-gray">隐患位置</text>
<text>{{ hazardAddress || '-' }}</text>
</view>
<view class="info-row">
<text class="text-gray">隐患描述</text>
<text class="description-text">{{ hazardFormData.description || '-' }}</text>
</view>
<view class="info-row" v-if="hazardFileList.length > 0">
<text class="text-gray">附件</text>
<text>{{ hazardFileList.length }}个文件</text>
</view>
</view>
<view class="card-footer">
<button class="btn-edit" @click="editHazard">修改</button>
<button class="btn-clear" @click="clearHazard">清除</button>
</view>
</view>
</view>
<view class="margin-top">
<up-textarea v-model="value1" placeholder="请输入备注信息" ></up-textarea>
</view>
</view>
<button class="bg-blue round margin-top-xl" @click="handleSubmit">提交</button>
<!-- 新增隐患弹窗 -->
<u-popup :show="showHazardPopup" mode="center" round="20" :safeAreaInsetBottom="false" @close="showHazardPopup = false">
<view class="popup-content">
<view class="popup-header">
<view class="popup-title text-bold">填写隐患信息</view>
<view class="popup-close" @click="showHazardPopup = false">×</view>
</view>
<scroll-view class="popup-body" scroll-y :style="{ height: '60vh' }">
<!-- 草稿恢复提示 -->
<view v-if="showRestoreBanner" class="bg-orange-light text-orange padding-sm radius margin-bottom flex justify-between align-center" style="font-size: 24rpx; background-color: #FFF7EB; border: 1rpx solid #FFE4CC; width: 100%; box-sizing: border-box; display: flex; flex-direction: row; justify-content: space-between; align-items: center; margin-bottom: 20rpx;">
<view class="flex align-center" style="display: flex; flex-direction: row; align-items: center;">
<text class="cuIcon-info margin-right-xs" style="margin-right: 10rpx;"></text>
<text>已自动恢复您上次未提交的内容</text>
</view>
<text class="text-blue text-bold" style="cursor: pointer; padding: 0 10rpx; color: #2667E9; font-weight: bold;" @click="clearDraft(true)">清空草稿</text>
</view>
<view class="flex margin-bottom">
<view class="text-gray">隐患图片</view>
<view class="text-red">*</view>
</view>
<up-upload :fileList="hazardFileList" @afterRead="afterRead" @delete="deletePic" name="1" multiple imageMode="aspectFill"
:maxCount="10"></up-upload>
<!-- 隐藏的 Canvas用于渲染防作弊时间戳水印 -->
<canvas canvas-id="watermarkCanvas" :width="canvasWidth" :height="canvasHeight" :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px', position: 'fixed', left: '-9999px', top: '-9999px' }"></canvas>
<view class="text-gray text-sm">必填请上传现场照片作为隐患证据</view>
<view class="ai-btn-wrapper margin-top">
<button class="ai-analyze-btn" :loading="aiAnalyzing" :disabled="aiAnalyzing" @click="handleAiAnalyze">
<text v-if="!aiAnalyzing" class="cuIcon-magic ai-btn-icon"></text>
{{ aiAnalyzing ? 'AI识别中...' : 'AI 识别隐患' }}
</button>
</view>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患标题</view>
<view class="text-red">*</view>
</view>
<up-input placeholder="请输入内容" border="surround" v-model="hazardFormData.title"></up-input>
<view class="text-sm text-gray margin-top-xs">请用简洁的语言概括隐患要点</view>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患等级</view>
<view class="text-red">*</view>
</view>
<up-choose
ref="levelChooseRef"
v-model="hazardFormData.level"
:options="levelOptions"
:wrap="false"
item-width="183rpx"
item-height="72rpx"
></up-choose>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患来源</view>
<view class="text-red">*</view>
</view>
<up-choose v-model="hazardFormData.source" :options="sourceOptions" :wrap="false" item-width="183rpx"
item-height="72rpx"></up-choose>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患位置</view>
<view class="text-red">*</view>
</view>
<view class="address-box">
<up-input class="address-input-wrapper" v-model="hazardAddress" placeholder="请输入地址" border="surround"></up-input>
<button class="btn-address bg-blue" @tap.stop="chooseLocation">选择地址</button>
</view>
<view class="text-gray text-sm margin-top-xs">办公楼3层东侧消防通道生产车间A区设备旁等</view>
<!-- 法律依据 -->
<view class="flex margin-bottom margin-top">
<view class="text-gray">法律依据</view>
</view>
<view class="select-trigger" @click="openLawPopup">
<view class="select-value" :class="{ 'placeholder': !hazardFormData.regulationName }">
{{ hazardFormData.regulationName || '请选择法律依据' }}
</view>
<text class="cuIcon-unfold"></text>
</view>
<!-- 隐患区域选择 -->
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患区域</view>
</view>
<view class="select-trigger" @click="showAreaPicker = true">
<view class="select-value" :class="{ 'placeholder': !selectedAreaName }">
{{ selectedAreaName || '请选择隐患区域' }}
</view>
<text class="cuIcon-unfold"></text>
</view>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患描述</view>
<view class="text-red">*</view>
</view>
<up-textarea v-model="hazardFormData.description" placeholder="请输入内容"></up-textarea>
<view class="text-gray text-sm margin-top-xs">请详细说明隐患现状潜在风险及影响范围</view>
<view class="text-gray margin-bottom margin-top">隐患标签</view>
<up-choose v-model="hazardFormData.tagIndex" :options="tagOptions"></up-choose>
<view class="text-gray text-sm"></view>
</scroll-view>
<view class="popup-footer">
<button class="btn-cancel" @click="showHazardPopup = false">取消</button>
<button class="btn-confirm bg-blue" @click="confirmHazard">确定</button>
</view>
</view>
</u-popup>
<!-- 选择法规弹出框 -->
<u-popup :show="showLawPopup" mode="center" round="20" :safeAreaInsetBottom="false" @close="showLawPopup = false">
<view class="law-popup">
<view class="popup-header">
<view class="popup-title">选择法律依据</view>
<view class="popup-close" @click="showLawPopup = false">×</view>
</view>
<view class="search-box">
<text class="cuIcon-search search-icon"></text>
<input class="search-input" v-model="lawKeyword" placeholder="请输入关键词搜索" @confirm="searchRegulation" />
<text class="search-btn" @click="searchRegulation">搜索</text>
</view>
<scroll-view class="law-list" scroll-y @scrolltolower="loadMoreLaw">
<view v-if="lawLoading && lawList.length === 0" class="loading-tip">加载中...</view>
<view v-else-if="!lawLoading && lawList.length === 0" class="empty-tip">暂无数据</view>
<template v-else>
<view
class="law-item"
:class="{ 'law-item-active': selectedLawId === item.id }"
v-for="item in lawList"
:key="item.id"
@click="selectLaw(item)"
>
<view class="law-title">{{ item.depict }}</view>
<view class="law-basis text-gray">{{ item.legalBasis }}</view>
</view>
<view v-if="lawLoading" class="loading-tip">加载中...</view>
</template>
</scroll-view>
<view class="popup-footer">
<button class="btn-cancel" @click="showLawPopup = false">取消</button>
<button class="btn-confirm bg-blue" @click="confirmLaw">确定</button>
</view>
</view>
</u-popup>
<!-- 区域选择弹窗 -->
<u-popup :show="showAreaPicker" mode="bottom" round="20" @close="showAreaPicker = false">
<view class="picker-popup">
<view class="picker-header">
<view class="picker-cancel" @click="showAreaPicker = false">取消</view>
<view class="picker-title">选择隐患区域</view>
<view class="picker-confirm" @click="confirmAreaSelect">确定</view>
</view>
<scroll-view class="picker-body" scroll-y>
<view
v-for="item in areaList"
:key="item.id"
class="picker-item"
:class="{ 'picker-item-active': tempAreaId === item.id }"
@click="tempAreaId = item.id"
>
<view class="flex align-center">
<view class="area-color-dot" :style="{ backgroundColor: item.color }"></view>
<text>{{ item.name }}</text>
</view>
<text v-if="tempAreaId === item.id" class="cuIcon-check text-blue"></text>
</view>
<view v-if="areaList.length === 0" class="text-gray text-center padding">
暂无区域数据
</view>
</scroll-view>
</view>
</u-popup>
</view>
</template>
<script setup>
import { ref, reactive, computed, nextTick, watch, getCurrentInstance } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { enterCheckPlan, submitCheckResult, addHiddenDanger, getHiddenDangerLabelList, getRegulationList, analyzeHazardImage } from '@/request/api.js';
import { baseUrl, getToken, toImageUrl, imageBaseUrl } from '@/request/request.js';
import { addTimestampWatermark } from '@/utils/watermark.js';
import { getAreaList } from '@/request/three_one_api/area.js';
// 页面参数
const oneTableId = ref('');
// 防作弊时间戳水印 Canvas 大小配置
const canvasWidth = ref(300);
const canvasHeight = ref(300);
// 检查项数据
const checkData = ref(null);
// 单选框 result: 1.正常 2.异常 3.不涉及
const radiolist1 = reactive([
{
name: '正常',
value: 1,
disabled: false,
},
{
name: '异常',
value: 2,
disabled: false,
},
{
name: '不涉及',
value: 3,
disabled: false,
},
]);
// 选中的结果值
const radiovalue1 = ref('');
const groupChange = (n) => {
console.log('groupChange', n);
// 切换到非异常时清除隐患数据
if (n !== '异常') {
clearHazard();
}
};
const radioChange = (n) => {
console.log('radioChange', n);
};
// 备注
const value1 = ref('');
// ==================== 隐患信息相关 ====================
// 弹窗控制
const showHazardPopup = ref(false);
// 隐患表单数据(暂存)
const hazardFormData = reactive({
title: '',
level: 0,
source: 0,
description: '',
tagIndex: 0,
regulationId: null, // 法律依据ID
regulationName: '' // 法律依据名称(显示用)
});
// 隐患地址和经纬度
const hazardAddress = ref('');
const hazardLng = ref(0);
const hazardLat = ref(0);
// 区域选择相关
const showAreaPicker = ref(false);
const areaList = ref([]);
const selectedAreaId = ref('');
const selectedAreaName = ref('');
const tempAreaId = ref('');
// 选择法规(法律依据)相关
const showLawPopup = ref(false);
const lawKeyword = ref('');
const selectedLawId = ref(null);
const selectedLawName = ref('');
const lawList = ref([]);
const lawLoading = ref(false);
const lawPageNum = ref(1);
const lawPageSize = ref(10);
const hasMoreLaw = ref(true);
// 打开法规选择弹窗
const openLawPopup = () => {
showLawPopup.value = true;
if (lawList.value.length === 0) {
fetchRegulationList();
}
};
// 获取法规列表
const fetchRegulationList = async (isLoadMore = false) => {
if (lawLoading.value) return;
lawLoading.value = true;
try {
const params = {
pageNum: lawPageNum.value,
pageSize: lawPageSize.value,
status: 1 // 启用状态
};
if (lawKeyword.value && lawKeyword.value.trim()) {
params.keyword = lawKeyword.value.trim();
}
const res = await getRegulationList(params);
if (res.code === 0) {
const records = res.data.records || res.data || [];
if (isLoadMore) {
lawList.value = [...lawList.value, ...records];
} else {
lawList.value = records;
}
const total = res.data.total || 0;
hasMoreLaw.value = lawList.value.length < total;
}
} catch (error) {
console.error('获取法规列表失败:', error);
} finally {
lawLoading.value = false;
}
};
// 搜索法规
const searchRegulation = () => {
lawPageNum.value = 1;
lawList.value = [];
hasMoreLaw.value = true;
fetchRegulationList();
};
// 加载更多法规
const loadMoreLaw = () => {
if (!hasMoreLaw.value || lawLoading.value) return;
lawPageNum.value++;
fetchRegulationList(true);
};
// 选择法规
const selectLaw = (item) => {
selectedLawId.value = item.id;
selectedLawName.value = item.depict || item.keyword || '';
};
// 确认选择法规
const confirmLaw = () => {
if (selectedLawId.value) {
hazardFormData.regulationId = selectedLawId.value;
hazardFormData.regulationName = selectedLawName.value;
}
showLawPopup.value = false;
};
// 获取区域列表
const fetchAreaList = async () => {
try {
const res = await getAreaList();
if (res.code === 0 && res.data && res.data.records) {
areaList.value = res.data.records;
}
} catch (error) {
console.error('获取区域列表失败:', error);
}
};
// 确认区域选择
const confirmAreaSelect = () => {
if (tempAreaId.value) {
selectedAreaId.value = tempAreaId.value;
const selected = areaList.value.find(item => item.id === tempAreaId.value);
selectedAreaName.value = selected ? selected.name : '';
}
showAreaPicker.value = false;
};
// 隐患附件列表
const hazardFileList = ref([]);
// up-choose 组件的 ref
const levelChooseRef = ref(null);
// AI 识别相关
const aiAnalyzing = ref(false);
// 是否已填写隐患数据
const hasHazardData = computed(() => {
return hazardFormData.title && hazardFileList.value.length > 0;
});
// 进度百分比
const progressPercent = computed(() => {
if (!checkData.value) return 0;
const current = checkData.value.currentIndex || 1;
const total = checkData.value.totalCount || 1;
return Math.round((current / total) * 100);
});
// 隐患等级选项
const levelOptions = ref([
// { id: 1, title: '轻微隐患' },
{ id: 2, title: '一般隐患' },
{ id: 3, title: '重大隐患' }
]);
// 隐患来源选项
const sourceOptions = ref([
{ id: 1, title: '随手拍' },
{ id: 2, title: '企业自查' },
{ id: 3, title: '行业互查' },
{ id: 4, title: '专家诊查' }
]);
// 隐患标签选项
const tagOptions = ref([]);
// 获取隐患标签列表
const fetchTagOptions = async () => {
try {
const res = await getHiddenDangerLabelList();
if (res.code === 0) {
tagOptions.value = res.data.map(item => ({
id: item.id,
title: item.name
}));
}
} catch (error) {
console.error('获取标签列表失败:', error);
}
};
// 打开隐患信息弹窗
const openHazardPopup = () => {
showHazardPopup.value = true;
};
// 编辑隐患信息(回显数据)
const editHazard = () => {
showHazardPopup.value = true;
};
// 清除隐患信息
const clearHazard = () => {
hazardFormData.regulationId = null;
hazardFormData.regulationName = '';
selectedLawId.value = null;
selectedLawName.value = '';
hazardFormData.title = '';
hazardFormData.level = 0;
hazardFormData.source = 0;
hazardFormData.description = '';
hazardFormData.tagIndex = 0;
hazardAddress.value = '';
hazardLng.value = 0;
hazardLat.value = 0;
hazardFileList.value = [];
selectedAreaId.value = '';
selectedAreaName.value = '';
};
// 确认隐患信息(只暂存,不调接口)
const confirmHazard = () => {
// 表单验证
if (hazardFileList.value.length === 0) {
uni.showToast({ title: '请上传隐患图片', icon: 'none' });
return;
}
if (!hazardFormData.title) {
uni.showToast({ title: '请输入隐患标题', icon: 'none' });
return;
}
if (!hazardAddress.value) {
uni.showToast({ title: '请输入隐患位置', icon: 'none' });
return;
}
if (!hazardFormData.description) {
uni.showToast({ title: '请输入隐患描述', icon: 'none' });
return;
}
// 验证通过,关闭弹窗(数据已在 reactive 中暂存)
showHazardPopup.value = false;
uni.showToast({ title: '隐患信息已暂存', icon: 'success' });
};
// 选择地址
const chooseLocation = () => {
showHazardPopup.value = false;
setTimeout(() => {
uni.chooseLocation({
success: (res) => {
hazardAddress.value = res.address + (res.name ? `(${res.name})` : '');
hazardLng.value = res.longitude;
hazardLat.value = res.latitude;
showHazardPopup.value = true;
},
fail: (err) => {
showHazardPopup.value = true;
if (err.errMsg && err.errMsg.indexOf('cancel') === -1) {
uni.showToast({ title: '选择位置失败', icon: 'none' });
}
}
});
}, 300);
};
// 删除图片
const deletePic = (event) => {
hazardFileList.value.splice(event.index, 1);
};
// 新增图片
const afterRead = async (event) => {
let lists = [].concat(event.file);
let fileListLen = hazardFileList.value.length;
lists.map((item) => {
hazardFileList.value.push({
...item,
status: 'uploading',
message: '上传中',
});
});
for (let i = 0; i < lists.length; i++) {
let watermarkedUrl = lists[i].url;
try {
const instance = getCurrentInstance();
watermarkedUrl = await addTimestampWatermark({
tempFilePath: lists[i].url,
canvasId: 'watermarkCanvas',
canvasWidthRef: canvasWidth,
canvasHeightRef: canvasHeight,
instance
});
} catch (e) {
console.error('加水印失败,将使用原图上传:', e);
}
const result = await uploadFilePromise(watermarkedUrl);
let item = hazardFileList.value[fileListLen];
const serverPath = typeof result === 'string' ? result : (result?.url || result?.path || '');
hazardFileList.value.splice(fileListLen, 1, {
...item,
status: 'success',
message: '',
url: toImageUrl(serverPath),
serverPath: serverPath,
});
fileListLen++;
}
};
// 上传文件
const uploadFilePromise = (filePath) => {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: baseUrl + '/frontend/attachment/upload',
filePath: filePath,
name: 'file',
header: {
'Authorization': getToken()
},
success: (res) => {
const data = JSON.parse(res.data);
if (data.code === 0) {
resolve(data.data);
} else {
reject(data.msg || '上传失败');
}
},
fail: (err) => {
console.error('上传失败:', err);
reject(err);
}
});
});
};
// AI 识别隐患
const handleAiAnalyze = async () => {
const imageFiles = hazardFileList.value.filter(f => {
return f.status === 'success' && f.url.toLowerCase().match(/\.(jpg|jpeg|png|gif|bmp|webp)$/);
});
if (imageFiles.length === 0) {
uni.showToast({ title: '请先上传隐患图片', icon: 'none' });
return;
}
const fullImageUrl = imageFiles[0].url;
aiAnalyzing.value = true;
try {
console.log('开始调用AI分析接口图片地址:', fullImageUrl);
const analyzeRes = await analyzeHazardImage({
imageUrl: fullImageUrl,
});
if (analyzeRes.code === 0 && analyzeRes.data) {
const aiData = analyzeRes.data;
console.log('AI分析结果:', aiData);
if (aiData.title) hazardFormData.title = aiData.title;
if (aiData.description) hazardFormData.description = aiData.description;
if (aiData.level) {
const levelMap = { '轻微': 0, '轻微隐患': 0, '一般': 1, '一般隐患': 1, '重大': 2, '重大隐患': 2 };
const levelIndex = levelMap[aiData.level];
if (levelIndex !== undefined) {
hazardFormData.level = levelIndex;
nextTick(() => {
if (levelChooseRef.value && levelChooseRef.value.$data) {
levelChooseRef.value.$data.currentIndex = levelIndex;
}
});
}
}
uni.showToast({ title: 'AI分析完成已自动填充', icon: 'success', duration: 2000 });
} else {
uni.showToast({ title: analyzeRes.msg || 'AI分析失败', icon: 'none' });
}
} catch (error) {
console.error('AI分析接口调用失败:', error);
uni.showToast({ title: 'AI分析失败请重试', icon: 'none' });
} finally {
aiAnalyzing.value = false;
}
};
// ==================== 提交逻辑 ====================
// 提交检查结果
const handleSubmit = async () => {
// 验证是否选择了检查结果
if (!radiovalue1.value) {
uni.showToast({ title: '请选择检查结果', icon: 'none' });
return;
}
// 获取选中项对应的 value 值
const selectedItem = radiolist1.find(item => item.name === radiovalue1.value);
const resultValue = selectedItem ? selectedItem.value : null;
if (!resultValue) {
uni.showToast({ title: '请选择检查结果', icon: 'none' });
return;
}
// 如果选择异常,验证是否填写了隐患信息
if (radiovalue1.value === '异常' && !hasHazardData.value) {
uni.showToast({ title: '请填写隐患信息', icon: 'none' });
return;
}
try {
uni.showLoading({ title: '提交中...' });
// 如果选择异常,先调用新增隐患接口
if (radiovalue1.value === '异常' && hasHazardData.value) {
// 构建附件列表
const attachments = hazardFileList.value.map(file => {
let url = '';
if (typeof file.url === 'string') {
url = file.url;
} else if (file.url && typeof file.url === 'object') {
url = file.url.url || file.url.path || '';
}
if (typeof url === 'string' && url.startsWith('http')) {
url = url.replace(imageBaseUrl, '');
}
const fileName = (typeof url === 'string' && url) ? url.split('/').pop() : (file.name || '');
return {
fileName: fileName || '',
filePath: url || '',
fileType: file.type || 'image/png',
fileSize: file.size || 0
};
});
// 获取隐患标签ID
const selectedTag = tagOptions.value[hazardFormData.tagIndex];
const tagId = selectedTag ? selectedTag.id : null;
// 构建隐患参数
const hazardParams = {
taskId: checkData.value?.taskId,
checkPointId: checkData.value?.checkPointId,
title: hazardFormData.title,
level: hazardFormData.level + 1,
lng: hazardLng.value || 0,
lat: hazardLat.value || 0,
address: hazardAddress.value || '',
areaId: selectedAreaId.value || null, // 隐患区域ID
description: hazardFormData.description || '',
source: sourceOptions.value[hazardFormData.source]?.title || '',
tagId: tagId,
attachments: attachments,
regulationId: hazardFormData.regulationId || null
};
console.log('隐患参数:', hazardParams);
// 调用新增隐患接口
const hazardRes = await addHiddenDanger(hazardParams);
if (hazardRes.code !== 0) {
uni.hideLoading();
uni.showToast({ title: hazardRes.msg || '新增隐患失败', icon: 'none' });
return;
}
console.log('新增隐患成功');
}
// 调用提交巡检结果接口
const submitParams = {
taskId: checkData.value?.taskId,
result: resultValue,
remark: value1.value
};
console.log('提交参数:', submitParams);
const res = await submitCheckResult(submitParams);
uni.hideLoading();
if (res.code === 0) {
clearDraft(false);
// 判断是否全部完成
if (res.data && res.data.allFinished === true) {
// 全部完成,退出页面
uni.showToast({ title: '全部检查已完成', icon: 'success' });
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else if (res.data && res.data.nextTask) {
// 还有下一个问题,更新数据继续
uni.showToast({ title: '提交成功,进入下一题', icon: 'success' });
// 重置表单状态
resetFormState();
// 更新为下一个检查项的数据
checkData.value = res.data.nextTask;
} else {
// 兜底处理:没有 allFinished 也没有 nextTask直接返回
uni.showToast({ title: '提交成功', icon: 'success' });
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
} else {
uni.showToast({ title: res.msg || '提交失败', icon: 'none' });
}
} catch (error) {
uni.hideLoading();
console.error('提交失败:', error);
uni.showToast({ title: '提交失败', icon: 'none' });
}
};
// 重置表单状态(进入下一题时调用)
const resetFormState = () => {
// 重置检查结果选择
radiovalue1.value = '';
// 重置备注
value1.value = '';
// 清除隐患信息
clearHazard();
};
// 获取检查项数据
const getCheckData = async () => {
try {
const res = await enterCheckPlan(oneTableId.value);
console.log('检查项数据:', res);
if (res.code === 0) {
checkData.value = res.data;
// 拿到题目后,安全触发恢复草稿
restoreDraft();
}
} catch (error) {
console.error(error);
}
};
// 页面加载时获取参数并调用接口
// 草稿缓存与恢复逻辑 (已深度适配,排除区域选择器列表缓存,仅缓存文字、单选、备注、图片等输入信息)
const hasDraft = ref(false);
const showRestoreBanner = ref(false); // 独立控制提示 Banner仅在初次确实从本地恢复了内容时才显示
const isRestoring = ref(false); // 正在恢复标志避免触发冗余watch
const isInitialized = ref(false); // 初始化标识,防止初期空值覆盖已有草稿
const getDraftKey = () => `draft_inspection_result_${oneTableId.value || ''}_${checkData.value?.currentIndex || ''}`;
// 保存草稿
const saveDraft = () => {
if (isRestoring.value || !isInitialized.value) {
console.log('【草稿Debug - 巡检】saveDraft 被跳过:', { isRestoring: isRestoring.value, isInitialized: isInitialized.value });
return;
}
const key = getDraftKey();
const hasContent = radiovalue1.value ||
value1.value ||
hazardFormData.title ||
hazardFormData.description ||
hazardFileList.value.length > 0;
console.log('【草稿Debug - 巡检】尝试自动保存草稿. Key:', key, '是否有实质内容:', !!hasContent, '当前数据:', {
radiovalue1: radiovalue1.value,
value1: value1.value,
title: hazardFormData.title,
description: hazardFormData.description,
fileCount: hazardFileList.value.length
});
if (!hasContent) {
uni.removeStorageSync(key);
console.log('【草稿Debug - 巡检】当前表单为空,自动删除本地缓存 Key:', key);
hasDraft.value = false;
return;
}
const data = {
radiovalue1: radiovalue1.value,
value1: value1.value,
hazardFormData: {
title: hazardFormData.title,
level: hazardFormData.level,
source: hazardFormData.source,
description: hazardFormData.description,
tagIndex: hazardFormData.tagIndex
},
hazardFileList: hazardFileList.value,
hazardAddress: hazardAddress.value,
hazardLng: hazardLng.value,
hazardLat: hazardLat.value
};
uni.setStorageSync(key, JSON.stringify(data));
console.log('【草稿Debug - 巡检】成功保存草稿到 LocalStorage, Key:', key);
hasDraft.value = true;
};
// 清空草稿
const clearDraft = (showToast = true) => {
const key = getDraftKey();
console.log('【草稿Debug - 巡检】手动清空草稿, Key:', key);
uni.removeStorageSync(key);
hasDraft.value = false;
showRestoreBanner.value = false;
isRestoring.value = true;
radiovalue1.value = '';
value1.value = '';
hazardFormData.title = '';
hazardFormData.level = 0;
hazardFormData.source = 0;
hazardFormData.description = '';
hazardFormData.tagIndex = 0;
hazardFileList.value = [];
hazardAddress.value = '';
hazardLng.value = 0;
hazardLat.value = 0;
nextTick(() => {
isRestoring.value = false;
});
if (showToast) {
uni.showToast({ title: '草稿已清空', icon: 'none' });
}
};
// 恢复草稿
const restoreDraft = () => {
const key = getDraftKey();
const cached = uni.getStorageSync(key);
console.log('【草稿Debug - 巡检】尝试恢复草稿, Key:', key, '获取本地缓存结果:', !!cached);
if (cached) {
try {
const data = JSON.parse(cached);
const hasContent = data.radiovalue1 ||
data.value1 ||
data.hazardFormData.title ||
data.hazardFormData.description ||
(data.hazardFileList && data.hazardFileList.length > 0);
console.log('【草稿Debug - 巡检】解析本地缓存成功, 是否有实质内容:', !!hasContent, '缓存数据:', data);
if (!hasContent) {
isInitialized.value = true;
return;
}
isRestoring.value = true;
radiovalue1.value = data.radiovalue1 || '';
value1.value = data.value1 || '';
hazardFormData.title = data.hazardFormData.title || '';
hazardFormData.level = data.hazardFormData.level || 0;
hazardFormData.source = data.hazardFormData.source || 0;
hazardFormData.description = data.hazardFormData.description || '';
hazardFormData.tagIndex = data.hazardFormData.tagIndex || 0;
hazardFileList.value = data.hazardFileList || [];
hazardAddress.value = data.hazardAddress || '';
hazardLng.value = data.hazardLng || 0;
hazardLat.value = data.hazardLat || 0;
hasDraft.value = true;
showRestoreBanner.value = true; // 确实存在内容并恢复了,才亮起提示 Banner
nextTick(() => {
isRestoring.value = false;
isInitialized.value = true;
if (levelChooseRef.value && levelChooseRef.value.$data) {
levelChooseRef.value.$data.currentIndex = hazardFormData.level;
}
console.log('【草稿Debug - 巡检】UI与多选组件状态同步重绘完毕');
});
uni.showToast({
title: '已自动恢复您上次未提交的内容',
icon: 'none',
duration: 2500
});
} catch (e) {
console.error('【草稿Debug - 巡检】解析草稿异常:', e);
isRestoring.value = false;
isInitialized.value = true;
}
} else {
console.log('【草稿Debug - 巡检】本地无任何缓存, 页面已安全标记为 initialized');
isInitialized.value = true;
}
};
// 监听变量变化,自动保存草稿
watch(
() => [
radiovalue1.value,
value1.value,
hazardFormData.title,
hazardFormData.level,
hazardFormData.source,
hazardFormData.description,
hazardFormData.tagIndex,
hazardAddress.value,
hazardFileList.value
],
() => {
if (oneTableId.value) {
saveDraft();
}
},
{ deep: true }
);
onLoad((options) => {
console.log('接收到的参数:', options);
if (options.id) {
oneTableId.value = options.id;
getCheckData();
}
// 获取隐患标签列表
fetchTagOptions();
// 获取区域列表
fetchAreaList();
});
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #EBF2FC;
}
// 进度显示
.progress-bar {
background: #fff;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}
.progress-text {
display: flex;
align-items: baseline;
margin-bottom: 16rpx;
.current-index {
font-size: 32rpx;
font-weight: bold;
color: #2667E9;
}
.total-count {
font-size: 28rpx;
color: #999;
}
}
.progress-line {
height: 12rpx;
background: #E5E5E5;
border-radius: 6rpx;
overflow: hidden;
}
.progress-inner {
height: 100%;
background: linear-gradient(90deg, #2667E9, #5B9BFF);
border-radius: 6rpx;
transition: width 0.3s ease;
}
// 隐患信息区域
.hazard-section {
border-top: 1rpx solid #eee;
padding-top: 20rpx;
}
// 提示文字
.hazard-tip {
display: flex;
align-items: center;
padding: 16rpx 20rpx;
background: #FFF7E6;
border: 1rpx solid #FFE7BA;
border-radius: 8rpx;
margin-bottom: 20rpx;
.text-orange {
color: #FA8C16;
font-size: 26rpx;
}
}
// 填写隐患信息按钮
.hazard-btn {
display: flex;
align-items: center;
justify-content: center;
height: 88rpx;
border: 2rpx dashed #2667E9;
border-radius: 12rpx;
background: #F5F9FF;
.text-blue {
color: #2667E9;
font-size: 28rpx;
}
}
// 隐患信息卡片
.hazard-card {
background: #F5F9FF;
border: 1rpx solid #D6E4FF;
border-radius: 12rpx;
overflow: hidden;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1rpx solid #E8E8E8;
background: #fff;
}
.card-body {
padding: 20rpx;
.info-row {
display: flex;
margin-bottom: 12rpx;
font-size: 26rpx;
&:last-child {
margin-bottom: 0;
}
.text-gray {
flex-shrink: 0;
color: #999;
}
.description-text {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.card-footer {
display: flex;
border-top: 1rpx solid #E8E8E8;
background: #fff;
button {
flex: 1;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
border-radius: 0;
&::after {
border: none;
}
}
.btn-edit {
background: #fff;
color: #2667E9;
border-right: 1rpx solid #E8E8E8;
}
.btn-clear {
background: #fff;
color: #F56C6C;
}
}
}
// 隐患等级标签
.level-tag {
padding: 4rpx 16rpx;
border-radius: 8rpx;
font-size: 24rpx;
}
.level-minor {
background: #F6FFED;
border: 2rpx solid #B7EB8F;
color: #52C41A;
}
.level-normal {
background: #FFF7E6;
border: 2rpx solid #FFD591;
color: #FA8C16;
}
.level-major {
background: #FFF1F0;
border: 2rpx solid #FFA39E;
color: #F5222D;
}
// 弹窗样式
.popup-content {
width: 600rpx;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
.popup-title {
font-size: 32rpx;
color: #333;
}
.popup-close {
font-size: 40rpx;
color: #999;
line-height: 1;
}
}
.popup-body {
padding: 30rpx;
}
// AI 识别按钮样式
.ai-btn-wrapper {
display: flex;
justify-content: flex-end;
}
.ai-analyze-btn {
display: flex;
align-items: center;
justify-content: center;
height: 72rpx;
padding: 0 32rpx;
font-size: 28rpx;
color: #fff;
background: linear-gradient(135deg, #4facfe 0%, #2668EA 100%);
border-radius: 36rpx;
border: none;
&::after {
border: none;
}
.ai-btn-icon {
margin-right: 8rpx;
font-size: 30rpx;
}
&[disabled] {
opacity: 0.7;
}
}
.popup-footer {
display: flex;
border-top: 1rpx solid #eee;
button {
flex: 1;
height: 90rpx;
margin: 0 !important;
padding: 0 !important;
line-height: 90rpx;
border-radius: 0;
font-size: 30rpx;
&::after {
border: none;
}
}
.btn-cancel {
background: #fff;
color: #666;
}
.btn-confirm {
color: #fff;
}
}
.address-box {
display: flex;
align-items: center;
gap: 20rpx;
.address-input-wrapper {
flex: 1;
}
.btn-address {
flex-shrink: 0;
height: 70rpx;
line-height: 70rpx;
padding: 0 30rpx;
font-size: 26rpx;
border-radius: 8rpx;
color: #fff;
&::after {
border: none;
}
}
}
// 选择器触发器样式
.select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
padding: 20rpx 24rpx;
.select-value {
flex: 1;
font-size: 28rpx;
color: #333;
&.placeholder {
color: #c0c4cc;
}
}
}
// 选择弹窗样式
.picker-popup {
background: #fff;
.picker-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
.picker-cancel {
font-size: 28rpx;
color: #999;
}
.picker-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.picker-confirm {
font-size: 28rpx;
color: #2667E9;
}
}
.picker-body {
max-height: 600rpx;
padding: 0 30rpx;
}
.picker-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
font-size: 30rpx;
color: #333;
&:last-child {
border-bottom: none;
}
&.picker-item-active {
color: #2667E9;
}
}
}
// 区域颜色圆点
// 选择法规(法律依据)弹出框样式
.law-popup {
width: 600rpx;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
max-height: 80vh;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.popup-close {
font-size: 40rpx;
color: #999;
cursor: pointer;
}
}
}
.search-box {
display: flex;
align-items: center;
background: #F5F5F5;
border-radius: 40rpx;
padding: 16rpx 24rpx;
margin: 20rpx 30rpx;
.search-icon {
font-size: 28rpx;
color: #999;
margin-right: 12rpx;
}
.search-input {
flex: 1;
font-size: 28rpx;
background: transparent;
border: none;
}
.search-btn {
color: #2667E9;
font-size: 26rpx;
margin-left: 16rpx;
cursor: pointer;
}
}
.loading-tip, .empty-tip {
text-align: center;
padding: 40rpx;
color: #999;
font-size: 26rpx;
}
.law-list {
max-height: 500rpx;
padding: 0 30rpx;
}
.law-item {
padding: 24rpx;
border: 2rpx solid #E5E5E5;
border-radius: 12rpx;
margin-bottom: 16rpx;
font-size: 28rpx;
color: #333;
text-align: left;
.law-title {
line-height: 1.5;
margin-bottom: 8rpx;
font-weight: bold;
}
.law-basis {
font-size: 24rpx;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
.law-item-active {
border-color: #2667E9;
background: #F0F6FF;
}
.area-color-dot {
width: 24rpx;
height: 24rpx;
border-radius: 50%;
margin-right: 16rpx;
}
</style>