feat: 重构勘察业务为明细模式并新增定时任务人性化配置

- 重构勘察业务:移除SOP相关表,新增勘察明细表支持建筑/楼层/房间/照片管理
- 新增定时任务人性化频率配置,支持分钟/小时/天/周/月/自定义六种模式
- 新增频率预览接口,支持配置转换为cron表达式与执行时间预览
- 更新系统品牌名称为鑫潼智电,修改全局标题与登录页文案
This commit is contained in:
andyesfly committed 2026-10-17 10:08:42 +08:00
1 parent f72644c24f
commit 22d27a940d
29 files changed
+1908 -576

No files matched your search

+56 -43
View File
@@ -514,29 +514,31 @@
<div v-else class="detail-text">—</div>
</div>
<div v-if="detailRecord.sopResults?.length" class="detail-block">
<div class="detail-label">
勘察回执({{ detailSopDone }} / {{ detailRecord.sopResults.length }} 项已完成)
</div>
<div v-for="row in detailRecord.sopResults" :key="row.id" class="sop-item">
<div class="sop-item-head">
<span class="sop-item-name">{{ row.itemName || row.sopItem?.itemName || '检查项' }}</span>
<span class="chip" :style="sopChipStyle(row.checked)">{{ row.checked ? '已回执' : '未回执' }}</span>
<div v-if="detailRecord.details?.length" class="detail-block">
<div class="detail-label">勘察明细({{ detailRecord.details.length }} 条)</div>
<div class="detail-list">
<div v-for="(row, idx) in detailRecord.details" :key="row.id ?? idx" class="detail-row">
<div class="detail-row-head">
<span class="detail-row-idx">明细 {{ idx + 1 }}</span>
<span class="detail-row-scope">
{{ row.buildingName || '—' }} · {{ floorLabel(row.floorNo) }}
<template v-if="row.roomName"> · {{ row.roomName }}</template>
</span>
</div>
<div v-if="row.photoUrls?.length" class="photo-list">
<a-image-preview-group>
<a-image
v-for="(url, pi) in row.photoUrls"
:key="pi"
:src="url"
:width="110"
:height="80"
style="object-fit: cover; border-radius: 8px"
/>
</a-image-preview-group>
</div>
<div v-if="row.remark" class="detail-row-remark">备注:{{ row.remark }}</div>
</div>
<div v-if="row.receiptUrls?.length" class="photo-list">
<a-image
v-for="(url, idx) in row.receiptUrls"
:key="idx"
:src="url"
:width="110"
:height="80"
style="object-fit: cover; border-radius: 8px"
/>
</div>
<div v-else-if="row.receiptValue?.length" class="detail-text">
{{ row.receiptValue.join('、') }}
</div>
<div v-if="row.remark" class="sop-item-remark">备注:{{ row.remark }}</div>
</div>
</div>
</div>
@@ -1120,9 +1122,6 @@ async function handleCancelSubmit() {
// ==================== 详情 / 编辑 ====================
const detailVisible = ref(false);
const detailRecord = ref<SurveyOrderInfo | null>(null);
const detailSopDone = computed(
() => (detailRecord.value?.sopResults || []).filter((row) => row.checked).length
);
async function openDetail(record: SurveyOrderInfo) {
detailRecord.value = record;
@@ -1152,12 +1151,13 @@ function formatRange(start?: string, end?: string) {
return `${s} ~ ${e}`;
}
const sopChipStyle = (checked?: boolean) =>
chipStyle(
checked
? { color: '#059669', bg: '#d1fae5', border: '#a7f3d0' }
: { color: '#94a3b8', bg: '#f8fafc', border: '#e2e8f0' }
);
// 楼层:正数为地上、负数为地下(value 为字符串,如 '1' / '-1')
function floorLabel(floorNo?: string | null) {
if (floorNo === undefined || floorNo === null || floorNo === '') return '未标注楼层';
const n = Number(floorNo);
if (Number.isNaN(n)) return String(floorNo);
return n < 0 ? `负${Math.abs(n)}层` : `${n}层`;
}
function money(value?: number | string) {
if (value == null || value === '') return '—';
@@ -1926,33 +1926,46 @@ function maskPhone(phone?: string) {
gap: 10px;
}
.sop-item {
padding: 10px 0;
border-bottom: 1px dashed #eef1f6;
.detail-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.sop-item:last-child {
border-bottom: none;
padding-bottom: 0;
.detail-row {
display: flex;
flex-direction: column;
gap: 8px;
padding: 10px 12px;
border: 1px solid #eef1f6;
border-radius: 8px;
background: #f8fafc;
}
.sop-item-head {
.detail-row-head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
gap: 10px;
flex-wrap: wrap;
}
.sop-item-name {
.detail-row-idx {
font-size: 12px;
font-weight: 600;
color: #0054cd;
}
.detail-row-scope {
font-size: 13px;
font-weight: 600;
color: #0d1c2c;
}
.sop-item-remark {
margin-top: 6px;
.detail-row-remark {
font-size: 12px;
line-height: 1.7;
color: #8a97a8;
white-space: pre-wrap;
}
/* AntD 覆盖 */