Files
threeonecheck_web/pages/Inspectionresult/detail.vue

1285 lines
30 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">
<view v-if="loading" class="empty-tip text-gray text-center padding">加载中...</view>
<view v-else-if="!checkItemList.length" class="empty-tip text-gray text-center padding">
暂无检查项
</view>
<template v-else>
<!-- 吸顶进度导航条紧贴页面内容区顶部滚动时始终可见 -->
<view class="float-nav">
<view class="float-nav-body">
<view class="float-nav-main">
<view class="progress-text">
<text class="current-index"> {{ checkItemList.length }} 项检查</text>
<text class="total-count">已提交 {{ submittedCount }} </text>
<text class="total-count">待提交 {{ pendingSubmitCount }} </text>
<text class="total-count">待检查 {{ uncheckedCount }} </text>
</view>
<view class="progress-line">
<view class="progress-inner" :style="{ width: progressPercent + '%' }"></view>
</view>
</view>
<view class="sheet-nav-btn" @click="openCheckSheet">
<up-icon name="grid" size="22" color="#2667E9"></up-icon>
</view>
</view>
</view>
<view class="page-content padding">
<view
v-if="showRestoreBanner"
class="draft-banner margin-bottom"
>
<view class="draft-banner-left">
<text class="cuIcon-info draft-banner-icon"></text>
<text>已自动恢复您上次未提交的检查内容</text>
</view>
<text class="draft-banner-action" @click="clearDraft(true)">清空草稿</text>
</view>
<!-- <view class="sheet-header bg-white radius padding margin-bottom">
<view class="text-bold">{{ sheetTitle }}</view>
<view class="text-gray text-sm margin-top-xs" v-if="industryName">
行业类型{{ industryName }}
</view>
</view> -->
<view
v-for="(item, index) in checkItemList"
:key="'check-item-' + index"
:id="'check-item-' + index"
class="question-card padding bg-white radius margin-bottom"
:class="{ 'question-card--submitted': isItemSubmitted(item) }"
>
<view class="question-header-row">
<view class="question-index"> {{ index + 1 }} </view>
<view v-if="isItemSubmitted(item)" class="submitted-tag">已提交</view>
<view v-else-if="isItemPendingSubmit(index)" class="pending-tag">待提交</view>
</view>
<view class="text-bold margin-top-xs">{{ item.name }}</view>
<view class="margin-top-sm">
<rich-text :nodes="item.point || ''"></rich-text>
</view>
<!-- 已提交只读展示 -->
<block v-if="isItemSubmitted(item)">
<view class="answer-panel margin-top">
<view class="readonly-row">
<text class="readonly-label">检查结果</text>
<view :class="['result-tag', getResultTagClass(item.result)]">
{{ getResultLabel(item.result) }}
</view>
</view>
<view v-if="item.remark" class="readonly-row">
<text class="readonly-label">备注</text>
<text class="readonly-value">{{ item.remark }}</text>
</view>
<view v-if="item.result === 2" class="hazard-summary">
<view v-if="item.hazardName" class="readonly-row readonly-row--stack">
<text class="readonly-label">隐患标题</text>
<text class="readonly-value">{{ item.hazardName }}</text>
</view>
<view v-if="item.source" class="readonly-row readonly-row--stack">
<text class="readonly-label">检查形式</text>
<text class="readonly-value">{{ item.source }}</text>
</view>
<view v-if="item.hazardAddress" class="readonly-row readonly-row--stack">
<text class="readonly-label">隐患位置</text>
<text class="readonly-value">{{ item.hazardAddress }}</text>
</view>
<view v-if="item.hazardDesc" class="readonly-row readonly-row--stack">
<text class="readonly-label">隐患描述</text>
<text class="readonly-value">{{ item.hazardDesc }}</text>
</view>
</view>
<button
v-if="item.result === 2 && item.hazardId"
class="view-hazard-btn"
@click="viewHazardDetail(item)"
>
查看隐患
</button>
</view>
</block>
<!-- 未提交可编辑 -->
<block v-else>
<view class="margin-top">
<view class="text-gray margin-bottom-xs">检查结果</view>
<u-radio-group
v-model="answerList[index].resultLabel"
placement="row"
@change="(label) => onResultChange(index, label)"
>
<u-radio
v-for="(opt, optIndex) in resultOptions"
:key="optIndex"
:customStyle="{ marginBottom: '8px', marginRight: '24rpx' }"
:label="opt.name"
:name="opt.name"
></u-radio>
</u-radio-group>
</view>
<view v-if="answerList[index].resultLabel === '异常'" 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(index)"
class="hazard-btn"
@click="openHazardPopup(index)"
>
<text class="text-blue">填写隐患信息</text>
</view>
<view v-else class="hazard-card">
<view class="card-header">
<view class="text-bold text-black">{{ answerList[index].hazardPayload.formData.title }}</view>
<view
class="level-tag"
:class="{
'level-minor': answerList[index].hazardPayload.formData.level === 0,
'level-normal': answerList[index].hazardPayload.formData.level === 1,
'level-major': answerList[index].hazardPayload.formData.level === 2
}"
>
{{ LEVEL_OPTIONS[answerList[index].hazardPayload.formData.level]?.title }}
</view>
</view>
<view class="card-body">
<view class="info-row">
<text class="text-gray">检查形式</text>
<text>{{ SOURCE_OPTIONS[answerList[index].hazardPayload.formData.source]?.title || '-' }}</text>
</view>
<view class="info-row">
<text class="text-gray">隐患位置</text>
<text>{{ answerList[index].hazardPayload.address || '-' }}</text>
</view>
<view class="info-row">
<text class="text-gray">隐患描述</text>
<text class="description-text">{{ answerList[index].hazardPayload.formData.description || '-' }}</text>
</view>
<view class="info-row" v-if="answerList[index].hazardPayload.fileList.length > 0">
<text class="text-gray">附件</text>
<text>{{ answerList[index].hazardPayload.fileList.length }}个文件</text>
</view>
</view>
<view class="card-footer">
<button class="btn-edit" @click="openHazardPopup(index)">修改</button>
<button class="btn-clear" @click="clearHazard(index)">清除</button>
</view>
</view>
</view>
<view class="margin-top">
<up-textarea
v-model="answerList[index].remark"
placeholder="请输入备注信息"
></up-textarea>
</view>
</block>
</view>
<button
v-if="pendingSubmitCount > 0"
class="bg-blue round margin-top-xl submit-btn"
@click="handleSubmitAll"
>
提交已填写项{{ pendingSubmitCount }}
</button>
</view>
</template>
<HazardFormPopup
v-if="activeHazardIndex >= 0"
ref="hazardFormRef"
v-model:show="showHazardPopup"
v-model="editingHazardPayload"
title="填写隐患信息"
mode="collect"
body-height="60vh"
:extra-params="hazardFormExtraParams"
@confirm="onHazardConfirm"
/>
<u-popup
:show="showCheckSheet"
mode="bottom"
round="20"
:safe-area-inset-bottom="true"
@close="closeCheckSheet"
>
<view class="check-sheet-popup">
<view class="check-sheet-header">
<view class="check-sheet-title">检查项导航</view>
<view class="check-sheet-subtitle">
{{ checkItemList.length }} 个检查项
</view>
</view>
<scroll-view class="check-sheet-grid-wrap" scroll-y>
<view class="check-sheet-grid">
<view
v-for="(item, index) in checkItemList"
:key="'sheet-nav-' + index"
class="check-sheet-cell"
:class="getSheetCellClass(index)"
@click="scrollToItem(index)"
>
{{ index + 1 }}
</view>
</view>
</scroll-view>
<view class="check-sheet-footer">
<view class="check-sheet-legend">
<view class="legend-item">
<view class="legend-dot legend-dot--submitted"></view>
<text>已提交 {{ submittedCount }}</text>
</view>
<view class="legend-item">
<view class="legend-dot legend-dot--pending"></view>
<text>待提交 {{ pendingSubmitCount }}</text>
</view>
<view class="legend-item">
<view class="legend-dot legend-dot--todo"></view>
<text>待检查 {{ uncheckedCount }}</text>
</view>
</view>
<button
v-if="pendingSubmitCount > 0"
class="check-sheet-submit-btn"
@click="handleSheetSubmit"
>
立即提交
</button>
</view>
</view>
</u-popup>
</view>
</template>
<script setup>
import { ref, computed, nextTick } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { getAllTask, submitAllTask } from '@/request/api.js';
import HazardFormPopup from '@/components/hazard/HazardFormPopup.vue';
import { buildDraftKey, DRAFT_NS, loadDraftWithFallback, saveDraftToStorage, removeDraft } 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 tableId = ref('');
const taskDate = ref('');
const planName = ref('');
const loading = ref(true);
const checkItemList = ref([]);
const answerList = ref([]);
const sessionSubmittedTaskIds = ref(new Set());
const showHazardPopup = ref(false);
const showCheckSheet = ref(false);
const activeHazardIndex = ref(-1);
const hazardFormRef = ref(null);
const editingHazardPayload = ref(createEmptyHazardPayload());
const resultOptions = [
{ name: '正常', value: 1 },
{ name: '异常', value: 2 },
{ name: '不涉及', value: 3 }
];
const TASK_STATUS_SUBMITTED = 2;
const syncSubmittedTaskIds = (list) => {
const next = new Set(sessionSubmittedTaskIds.value);
(list || []).forEach((item) => {
if (item?.status === TASK_STATUS_SUBMITTED && item.taskId != null) {
next.add(item.taskId);
}
});
sessionSubmittedTaskIds.value = next;
};
const isItemSubmitted = (item) => {
if (!item) return false;
if (item.status === TASK_STATUS_SUBMITTED) return true;
if (item.taskId != null && sessionSubmittedTaskIds.value.has(item.taskId)) return true;
return false;
};
const getResultLabel = (result) => {
const opt = resultOptions.find((o) => o.value === result);
return opt?.name || '-';
};
const getResultTagClass = (result) => {
if (result === 1) return 'result-tag--normal';
if (result === 2) return 'result-tag--abnormal';
if (result === 3) return 'result-tag--na';
return '';
};
const viewHazardDetail = (item) => {
if (!item?.hazardId) {
uni.showToast({ title: '暂无隐患信息', icon: 'none' });
return;
}
uni.navigateTo({
url: `/pages/hiddendanger/view?hazardId=${item.hazardId}`
});
};
const sheetTitle = computed(() => {
return planName.value || checkItemList.value[0]?.itemName || '检查表';
});
const industryName = computed(() => checkItemList.value[0]?.industryName || '');
const createEmptyAnswer = () => ({
resultLabel: '',
result: null,
remark: '',
hazardPayload: createEmptyHazardPayload()
});
const normalizeSheetAnswer = (answer) => {
if (!answer) return createEmptyAnswer();
const hazardPayload = answer.hazardPayload || createEmptyHazardPayload();
const empty = createEmptyHazardPayload();
return {
resultLabel: answer.resultLabel || '',
result: answer.result ?? null,
remark: answer.remark || '',
hazardPayload: {
formData: {
...empty.formData,
...(hazardPayload.formData || {})
},
address: hazardPayload.address || '',
lng: hazardPayload.lng || 0,
lat: hazardPayload.lat || 0,
areaId: hazardPayload.areaId || '',
areaName: hazardPayload.areaName || '',
fileList: Array.isArray(hazardPayload.fileList) ? [...hazardPayload.fileList] : [],
tagOptions: Array.isArray(hazardPayload.tagOptions) ? [...hazardPayload.tagOptions] : []
}
};
};
const syncAnswerFields = (answer) => {
const normalized = normalizeSheetAnswer(answer);
if (!normalized.resultLabel && normalized.result != null) {
const opt = resultOptions.find((o) => o.value === normalized.result);
if (opt) normalized.resultLabel = opt.name;
} else if (normalized.resultLabel && normalized.result == null) {
const opt = resultOptions.find((o) => o.name === normalized.resultLabel);
if (opt) normalized.result = opt.value;
}
return normalized;
};
const buildAnswerFromTask = (item) => {
const answer = createEmptyAnswer();
if (!isItemSubmitted(item)) {
return answer;
}
answer.result = item.result;
answer.remark = item.remark || '';
const opt = resultOptions.find((o) => o.value === item.result);
if (opt) {
answer.resultLabel = opt.name;
}
if (item.result === 2 && item.hazardName) {
const sourceIndex = SOURCE_OPTIONS.findIndex((o) => o.title === item.source);
answer.hazardPayload = {
...createEmptyHazardPayload(),
formData: {
...createEmptyHazardPayload().formData,
title: item.hazardName || '',
description: item.hazardDesc || '',
source: sourceIndex >= 0 ? sourceIndex : 0
},
address: item.hazardAddress || ''
};
}
return syncAnswerFields(answer);
};
const isAnswerFilled = (answer) => {
if (!answer) return false;
return !!(answer.resultLabel || answer.result != null);
};
const hasAnswerDraftContent = (answer) => {
if (!answer) return false;
const normalized = normalizeSheetAnswer(answer);
const hazard = normalized.hazardPayload;
return !!(
normalized.resultLabel ||
normalized.result != null ||
normalized.remark ||
hazard.formData?.title ||
hazard.formData?.description ||
(hazard.fileList && hazard.fileList.length > 0)
);
};
const buildDraftAnswers = () => {
return checkItemList.value
.map((item, index) => {
if (isItemSubmitted(item)) return null;
const answer = answerList.value[index] || createEmptyAnswer();
if (!hasAnswerDraftContent(answer)) return null;
return {
taskId: item.taskId,
...JSON.parse(JSON.stringify(answer))
};
})
.filter(Boolean);
};
const pruneDraftSubmitted = (taskIds = []) => {
const key = buildDraftKey(DRAFT_NS.INSPECTION_SHEET, oneTableId.value, taskDate.value);
const { data } = loadDraftWithFallback(key);
if (!data?.answers?.length) return;
const submittedSet = new Set(taskIds);
const remainingAnswers = data.answers.filter(
(answer) => answer?.taskId == null || !submittedSet.has(answer.taskId)
);
if (!remainingAnswers.length) {
removeDraft(key);
return;
}
saveDraftToStorage(key, {
...data,
answers: remainingAnswers
});
};
const isItemPendingSubmit = (index) => {
const item = checkItemList.value[index];
return !isItemSubmitted(item) && isAnswerFilled(answerList.value[index]);
};
const submittedCount = computed(() => {
return checkItemList.value.filter(isItemSubmitted).length;
});
const pendingSubmitCount = computed(() => {
return checkItemList.value.filter((item, index) => isItemPendingSubmit(index)).length;
});
const uncheckedCount = computed(() => {
return Math.max(checkItemList.value.length - submittedCount.value - pendingSubmitCount.value, 0);
});
const getSheetCellClass = (index) => {
const item = checkItemList.value[index];
if (isItemSubmitted(item)) return 'check-sheet-cell--submitted';
if (isItemPendingSubmit(index)) return 'check-sheet-cell--pending';
return '';
};
const progressPercent = computed(() => {
if (!checkItemList.value.length) return 0;
return Math.round((submittedCount.value / checkItemList.value.length) * 100);
});
const sheetDraftHasContent = (data) => {
if (!data?.answers?.length) return false;
return data.answers.some((answer) => hasAnswerDraftContent(answer));
};
const {
showRestoreBanner,
clear: clearDraft,
restore: restoreDraft,
bindAutoSave
} = useDraftCache({
getKey: () => buildDraftKey(DRAFT_NS.INSPECTION_SHEET, oneTableId.value, taskDate.value),
getPayload: () => ({
oneTableId: oneTableId.value,
taskDate: taskDate.value,
planName: planName.value,
answers: buildDraftAnswers()
}),
hasContent: sheetDraftHasContent,
applyPayload: (data) => {
if (!data?.answers?.length || !checkItemList.value.length) return;
// 优先按数组下标恢复,已提交项以接口数据为准
answerList.value = checkItemList.value.map((item, index) => {
if (isItemSubmitted(item)) {
return buildAnswerFromTask(item);
}
const savedByIndex = data.answers[index];
if (savedByIndex && sheetDraftHasContent({ answers: [savedByIndex] })) {
return syncAnswerFields(savedByIndex);
}
if (item.taskId != null && item.taskId !== '') {
const savedByTaskId = data.answers.find((a) => a.taskId === item.taskId);
if (savedByTaskId) {
return syncAnswerFields(savedByTaskId);
}
}
return createEmptyAnswer();
});
if (data.planName && !planName.value) {
planName.value = data.planName;
}
},
clearForm: () => {
answerList.value = checkItemList.value.map((item) => (
isItemSubmitted(item) ? buildAnswerFromTask(item) : createEmptyAnswer()
));
},
canSave: () => !!oneTableId.value && !!taskDate.value && checkItemList.value.length > 0,
requireInitialized: true
});
bindAutoSave(() => answerList.value);
const onResultChange = (index, label) => {
const answer = answerList.value[index];
if (!answer) return;
answer.resultLabel = label;
const selected = resultOptions.find((opt) => opt.name === label);
answer.result = selected ? selected.value : null;
if (label !== '异常') {
answer.hazardPayload = createEmptyHazardPayload();
}
};
const hazardFormExtraParams = computed(() => {
const index = activeHazardIndex.value;
const taskId = index >= 0 ? checkItemList.value[index]?.taskId : '';
return {
locationDraftKey: `hazard_location_${oneTableId.value}_${taskDate.value}_${taskId || index}`
};
});
const hasHazardData = (index) => hasValidHazardPayload(answerList.value[index]?.hazardPayload);
const openHazardPopup = (index) => {
activeHazardIndex.value = index;
editingHazardPayload.value = JSON.parse(JSON.stringify(
answerList.value[index]?.hazardPayload ?? createEmptyHazardPayload()
));
showHazardPopup.value = true;
};
const closeHazardPopup = () => {
showHazardPopup.value = false;
activeHazardIndex.value = -1;
editingHazardPayload.value = createEmptyHazardPayload();
};
const onHazardConfirm = (payload) => {
const index = activeHazardIndex.value;
const data = payload || editingHazardPayload.value;
if (index >= 0 && data && answerList.value[index]) {
answerList.value[index].hazardPayload = JSON.parse(JSON.stringify(data));
}
closeHazardPopup();
};
const clearHazard = (index) => {
if (answerList.value[index]) {
answerList.value[index].hazardPayload = createEmptyHazardPayload();
}
};
const openCheckSheet = () => {
showCheckSheet.value = true;
};
const closeCheckSheet = () => {
showCheckSheet.value = false;
};
const handleSheetSubmit = () => {
closeCheckSheet();
handleSubmitAll();
};
const scrollToItem = (index) => {
closeCheckSheet();
nextTick(() => {
uni.pageScrollTo({
selector: `#check-item-${index}`,
duration: 300,
offsetTop: -80
});
});
};
const getFilledAnswerIndices = () => {
const indices = [];
for (let i = 0; i < checkItemList.value.length; i++) {
if (!isItemSubmitted(checkItemList.value[i]) && isAnswerFilled(answerList.value[i])) {
indices.push(i);
}
}
return indices;
};
const validateFilledAnswers = () => {
const indices = getFilledAnswerIndices();
if (!indices.length) {
uni.showToast({ title: '请先填写待提交的检查项', icon: 'none' });
return null;
}
for (const index of indices) {
const answer = answerList.value[index];
if (answer.resultLabel === '异常' && !hasValidHazardPayload(answer.hazardPayload)) {
uni.showToast({ title: `${index + 1} 项为异常,请填写隐患信息`, icon: 'none' });
return null;
}
}
return indices;
};
const buildSubmitPayload = (filledIndices) => {
return filledIndices
.filter((index) => !isItemSubmitted(checkItemList.value[index]))
.map((index) => {
const item = checkItemList.value[index];
const answer = syncAnswerFields(answerList.value[index]);
const payload = {
taskId: item.taskId,
result: answer.result,
remark: answer.remark || ''
};
if (answer.resultLabel === '异常' && hasValidHazardPayload(answer.hazardPayload)) {
payload.hazard = buildHiddenDangerParams(answer.hazardPayload, {
taskId: item.taskId,
checkPointId: item.checkPointId
});
}
return payload;
});
};
const goToListPage = () => {
uni.navigateBack();
};
const handleSubmitAll = async () => {
const filledIndices = validateFilledAnswers();
if (!filledIndices) return;
const submitList = buildSubmitPayload(filledIndices);
if (!submitList.length) {
uni.showToast({ title: '没有可提交的新检查项', icon: 'none' });
return;
}
try {
uni.showLoading({ title: '提交中...' });
const res = await submitAllTask(submitList);
uni.hideLoading();
if (res.code === 0) {
const nextSubmitted = new Set(sessionSubmittedTaskIds.value);
submitList.forEach((item) => {
if (item.taskId != null) {
nextSubmitted.add(item.taskId);
}
});
sessionSubmittedTaskIds.value = nextSubmitted;
pruneDraftSubmitted(submitList.map((item) => item.taskId));
uni.showToast({
title: `已成功提交 ${submitList.length}`,
icon: 'success'
});
setTimeout(() => {
goToListPage();
}, 1500);
} else {
uni.showToast({ title: res.msg || '提交失败', icon: 'none' });
}
} catch (error) {
uni.hideLoading();
console.error('提交失败:', error);
uni.showToast({ title: '提交失败', icon: 'none' });
}
};
const fetchDetail = async () => {
if (!oneTableId.value || !taskDate.value) {
loading.value = false;
uni.showToast({ title: '缺少任务参数', icon: 'none' });
return;
}
loading.value = true;
try {
const res = await getAllTask(oneTableId.value, taskDate.value);
if (res.code === 0) {
const list = Array.isArray(res.data) ? res.data : (res.data?.records || []);
syncSubmittedTaskIds(list);
checkItemList.value = list;
answerList.value = list.map((item) => buildAnswerFromTask(item));
restoreDraft();
} else {
uni.showToast({ title: res.msg || '获取检查项失败', icon: 'none' });
}
} catch (error) {
console.error('获取检查项失败:', error);
uni.showToast({ title: '获取检查项失败', icon: 'none' });
} finally {
loading.value = false;
}
};
onLoad((options) => {
if (options.oneTableId) {
oneTableId.value = options.oneTableId;
} else if (options.id) {
oneTableId.value = options.id;
}
if (options.taskDate) {
taskDate.value = decodeURIComponent(options.taskDate).split(' ')[0];
}
if (options.name) {
planName.value = decodeURIComponent(options.name);
}
if (options.tableId) {
tableId.value = options.tableId;
}
fetchDetail();
});
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #EBF2FC;
padding-bottom: 40rpx;
}
.page-content {
padding-bottom: 40rpx;
}
.empty-tip {
font-size: 28rpx;
}
.sheet-header {
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}
.float-nav {
position: sticky;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: #fff;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
padding: 20rpx 24rpx;
}
.float-nav-body {
display: flex;
align-items: center;
gap: 20rpx;
}
.float-nav-main {
flex: 1;
min-width: 0;
}
.sheet-nav-btn {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 72rpx;
height: 72rpx;
background: #eef3ff;
border-radius: 16rpx;
}
.question-card {
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}
.question-card--submitted {
background: #f8faf8;
border: 1rpx solid #e8f5e9;
}
.question-header-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
}
.question-index {
display: inline-block;
padding: 4rpx 16rpx;
font-size: 24rpx;
color: #2667E9;
background: #EEF3FF;
border-radius: 8rpx;
}
.submitted-tag {
padding: 4rpx 16rpx;
font-size: 22rpx;
color: #52c41a;
background: #f6ffed;
border: 1rpx solid #b7eb8f;
border-radius: 8rpx;
white-space: nowrap;
}
.pending-tag {
padding: 4rpx 16rpx;
font-size: 22rpx;
color: #2667e9;
background: #eef3ff;
border: 1rpx solid #adc6ff;
border-radius: 8rpx;
white-space: nowrap;
}
.readonly-block,
.answer-panel {
padding: 20rpx;
background: #f7f8fa;
border: 1rpx solid #e8e8e8;
border-radius: 12rpx;
}
.readonly-row {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 12rpx;
font-size: 26rpx;
line-height: 1.6;
&:last-child {
margin-bottom: 0;
}
}
.readonly-row--stack {
align-items: flex-start;
}
.hazard-summary {
margin-top: 16rpx;
padding-top: 16rpx;
border-top: 1rpx dashed #e0e0e0;
}
.readonly-label {
flex-shrink: 0;
color: #999;
min-width: 120rpx;
}
.readonly-value {
flex: 1;
color: #333;
word-break: break-all;
}
.result-tag {
display: inline-flex;
align-items: center;
padding: 4rpx 16rpx;
border-radius: 8rpx;
font-size: 24rpx;
font-weight: 500;
line-height: 1.4;
white-space: nowrap;
}
.result-tag--normal {
background: #f6ffed;
border: 1rpx solid #b7eb8f;
color: #52c41a;
}
.result-tag--abnormal {
background: #fff1f0;
border: 1rpx solid #ffa39e;
color: #f5222d;
}
.result-tag--na {
background: #f5f5f5;
border: 1rpx solid #d9d9d9;
color: #8c8c8c;
}
.view-hazard-btn {
margin-top: 20rpx;
width: 100%;
height: 72rpx;
line-height: 72rpx;
font-size: 28rpx;
color: #2667e9;
background: #fff;
border: 1rpx solid #adc6ff;
border-radius: 12rpx;
&::after {
border: none;
}
}
.progress-text {
display: flex;
align-items: baseline;
margin-bottom: 16rpx;
.current-index {
font-size: 26rpx;
font-weight: bold;
color: #2667E9;
}
.total-count {
font-size: 24rpx;
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;
}
.submit-btn {
margin-bottom: 40rpx;
}
.draft-banner {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 24rpx;
font-size: 24rpx;
color: #fa8c16;
background: #fff7eb;
border: 1rpx solid #ffe4cc;
border-radius: 12rpx;
}
.draft-banner-left {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
.draft-banner-icon {
margin-right: 10rpx;
}
.draft-banner-action {
flex-shrink: 0;
margin-left: 16rpx;
color: #2667e9;
font-weight: bold;
}
.check-sheet-popup {
padding: 32rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
background: #fff;
}
.check-sheet-header {
margin-bottom: 24rpx;
}
.check-sheet-title {
font-size: 34rpx;
font-weight: bold;
color: #333;
}
.check-sheet-subtitle {
margin-top: 8rpx;
font-size: 26rpx;
color: #999;
}
.check-sheet-grid-wrap {
max-height: 52vh;
}
.check-sheet-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
padding-bottom: 8rpx;
}
.check-sheet-cell {
width: calc((100% - 80rpx) / 5);
height: 88rpx;
line-height: 88rpx;
text-align: center;
font-size: 28rpx;
color: #666;
background: #fff;
border: 2rpx solid #e5e5e5;
border-radius: 12rpx;
box-sizing: border-box;
}
.check-sheet-cell--pending {
color: #fff;
background: #2667e9;
border-color: #2667e9;
}
.check-sheet-cell--submitted {
color: #52c41a;
background: #f6ffed;
border-color: #b7eb8f;
}
.check-sheet-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24rpx;
margin-top: 28rpx;
padding-top: 24rpx;
border-top: 1rpx solid #f0f0f0;
}
.check-sheet-legend {
display: flex;
align-items: center;
gap: 32rpx;
flex: 1;
min-width: 0;
font-size: 26rpx;
color: #666;
}
.check-sheet-submit-btn {
flex-shrink: 0;
height: 72rpx;
line-height: 72rpx;
padding: 0 36rpx;
margin: 0;
font-size: 28rpx;
font-weight: 500;
color: #fff;
background: linear-gradient(135deg, #4facfe 0%, #2668ea 100%);
border-radius: 36rpx;
border: none;
box-shadow: 0 4rpx 16rpx rgba(38, 104, 234, 0.3);
&::after {
border: none;
}
}
.legend-item {
display: flex;
align-items: center;
gap: 12rpx;
}
.legend-dot {
width: 28rpx;
height: 28rpx;
border-radius: 8rpx;
box-sizing: border-box;
}
.legend-dot--submitted {
background: #f6ffed;
border: 2rpx solid #b7eb8f;
}
.legend-dot--pending {
background: #2667e9;
border: 2rpx solid #2667e9;
}
.legend-dot--todo {
background: #fff;
border: 2rpx solid #e5e5e5;
}
</style>