513 lines
12 KiB
Vue
513 lines
12 KiB
Vue
<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">{{ hazardPayload.formData.title }}</view>
|
||
<view class="level-tag" :class="{
|
||
'level-minor': hazardPayload.formData.level === 0,
|
||
'level-normal': hazardPayload.formData.level === 1,
|
||
'level-major': hazardPayload.formData.level === 2
|
||
}">{{ LEVEL_OPTIONS[hazardPayload.formData.level]?.title }}</view>
|
||
</view>
|
||
<view class="card-body">
|
||
<view class="info-row">
|
||
<text class="text-gray">检查形式:</text>
|
||
<text>{{ SOURCE_OPTIONS[hazardPayload.formData.source]?.title || '-' }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="text-gray">隐患位置:</text>
|
||
<text>{{ hazardPayload.address || '-' }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="text-gray">隐患描述:</text>
|
||
<text class="description-text">{{ hazardPayload.formData.description || '-' }}</text>
|
||
</view>
|
||
<view class="info-row" v-if="hazardPayload.fileList.length > 0">
|
||
<text class="text-gray">附件:</text>
|
||
<text>{{ hazardPayload.fileList.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>
|
||
|
||
<HazardFormPopup
|
||
ref="hazardFormRef"
|
||
v-model:show="showHazardPopup"
|
||
v-model="hazardPayload"
|
||
title="填写隐患信息"
|
||
mode="collect"
|
||
body-height="60vh"
|
||
:show-draft-banner="showRestoreBanner"
|
||
@confirm="onHazardConfirm"
|
||
@clear-draft="clearDraft(true)"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, computed, nextTick } from 'vue';
|
||
import { onLoad } from '@dcloudio/uni-app';
|
||
import { enterCheckPlan, submitCheckResult, addHiddenDanger } from '@/request/api.js';
|
||
import HazardFormPopup from '@/components/hazard/HazardFormPopup.vue';
|
||
import { buildDraftKey, DRAFT_NS } from '@/utils/draftCache.js';
|
||
import { useDraftCache } from '@/utils/useDraftCache.js';
|
||
import {
|
||
LEVEL_OPTIONS,
|
||
SOURCE_OPTIONS,
|
||
createEmptyHazardPayload,
|
||
buildHiddenDangerParams,
|
||
hasValidHazardPayload
|
||
} from '@/components/hazard';
|
||
|
||
const oneTableId = ref('');
|
||
const checkData = ref(null);
|
||
const hazardFormRef = ref(null);
|
||
const showHazardPopup = ref(false);
|
||
const hazardPayload = ref(createEmptyHazardPayload());
|
||
|
||
const radiolist1 = reactive([
|
||
{ name: '正常', value: 1, disabled: false },
|
||
{ name: '异常', value: 2, disabled: false },
|
||
{ name: '不涉及', value: 3, disabled: false }
|
||
]);
|
||
const radiovalue1 = ref('');
|
||
const value1 = ref('');
|
||
|
||
const normalizeInspectionDraft = (data) => {
|
||
if (!data) return { radiovalue1: '', value1: '', hazardPayload: createEmptyHazardPayload() };
|
||
|
||
if (!data.hazardPayload && data.hazardFormData) {
|
||
data.hazardPayload = {
|
||
formData: data.hazardFormData,
|
||
address: data.hazardAddress || '',
|
||
lng: data.hazardLng || 0,
|
||
lat: data.hazardLat || 0,
|
||
areaId: '',
|
||
areaName: '',
|
||
fileList: data.hazardFileList || []
|
||
};
|
||
}
|
||
|
||
return {
|
||
radiovalue1: data.radiovalue1 || '',
|
||
value1: data.value1 || '',
|
||
hazardPayload: data.hazardPayload || createEmptyHazardPayload()
|
||
};
|
||
};
|
||
|
||
const inspectionDraftHasContent = (data) => {
|
||
const normalized = normalizeInspectionDraft(data);
|
||
const payload = normalized.hazardPayload;
|
||
return !!(
|
||
normalized.radiovalue1 ||
|
||
normalized.value1 ||
|
||
payload.formData?.title ||
|
||
payload.formData?.description ||
|
||
(payload.fileList && payload.fileList.length > 0)
|
||
);
|
||
};
|
||
|
||
const {
|
||
showRestoreBanner,
|
||
clear: clearDraft,
|
||
restore: restoreDraft,
|
||
bindAutoSave
|
||
} = useDraftCache({
|
||
getKey: () => buildDraftKey(
|
||
DRAFT_NS.INSPECTION_RESULT,
|
||
oneTableId.value,
|
||
checkData.value?.currentIndex
|
||
),
|
||
getPayload: () => ({
|
||
radiovalue1: radiovalue1.value,
|
||
value1: value1.value,
|
||
hazardPayload: JSON.parse(JSON.stringify(hazardPayload.value))
|
||
}),
|
||
hasContent: inspectionDraftHasContent,
|
||
applyPayload: (data) => {
|
||
const normalized = normalizeInspectionDraft(data);
|
||
radiovalue1.value = normalized.radiovalue1;
|
||
value1.value = normalized.value1;
|
||
hazardPayload.value = normalized.hazardPayload;
|
||
nextTick(() => {
|
||
hazardFormRef.value?.applyPayload(normalized.hazardPayload);
|
||
});
|
||
},
|
||
clearForm: () => {
|
||
radiovalue1.value = '';
|
||
value1.value = '';
|
||
clearHazard();
|
||
},
|
||
canSave: () => !!oneTableId.value,
|
||
requireInitialized: true
|
||
});
|
||
|
||
bindAutoSave(() => [radiovalue1.value, value1.value, hazardPayload.value]);
|
||
|
||
const groupChange = (n) => {
|
||
if (n !== '异常') {
|
||
clearHazard();
|
||
}
|
||
};
|
||
|
||
const radioChange = (n) => {
|
||
console.log('radioChange', n);
|
||
};
|
||
|
||
const hasHazardData = computed(() => hasValidHazardPayload(hazardPayload.value));
|
||
|
||
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 openHazardPopup = () => {
|
||
showHazardPopup.value = true;
|
||
};
|
||
|
||
const editHazard = () => {
|
||
showHazardPopup.value = true;
|
||
};
|
||
|
||
const onHazardConfirm = (payload) => {
|
||
if (payload) {
|
||
hazardPayload.value = JSON.parse(JSON.stringify(payload));
|
||
}
|
||
};
|
||
|
||
const clearHazard = () => {
|
||
hazardPayload.value = createEmptyHazardPayload();
|
||
hazardFormRef.value?.resetForm();
|
||
};
|
||
|
||
const handleSubmit = async () => {
|
||
if (!radiovalue1.value) {
|
||
uni.showToast({ title: '请选择检查结果', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
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 payload = hazardFormRef.value?.getPayload() || hazardPayload.value;
|
||
const hazardParams = buildHiddenDangerParams(payload, {
|
||
taskId: checkData.value?.taskId,
|
||
checkPointId: checkData.value?.checkPointId
|
||
});
|
||
|
||
const hazardRes = await addHiddenDanger(hazardParams);
|
||
if (hazardRes.code !== 0) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: hazardRes.msg || '新增隐患失败', icon: 'none' });
|
||
return;
|
||
}
|
||
}
|
||
|
||
const submitParams = {
|
||
taskId: checkData.value?.taskId,
|
||
result: resultValue,
|
||
remark: value1.value
|
||
};
|
||
|
||
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 {
|
||
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);
|
||
if (res.code === 0) {
|
||
checkData.value = res.data;
|
||
restoreDraft();
|
||
}
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
};
|
||
|
||
onLoad((options) => {
|
||
if (options.id) {
|
||
oneTableId.value = options.id;
|
||
getCheckData();
|
||
}
|
||
});
|
||
</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;
|
||
}
|
||
</style>
|