Commit 7b93c3f6794baad3393c7f20acc8ae6450ef3177

Authored by 王彪总
1 parent 3da5bb94

业务线 小程序不用写死,从后端接口获取

pages-sub/daily/maintain-manage/finish-plan-detail.vue
@@ -14,6 +14,10 @@ @@ -14,6 +14,10 @@
14 <!-- 1. 计划名称:独立标题卡(设计稿顶部白卡) --> 14 <!-- 1. 计划名称:独立标题卡(设计稿顶部白卡) -->
15 <view class="record-head"> 15 <view class="record-head">
16 <view class="record-title">{{i.planName || '--'}}</view> 16 <view class="record-title">{{i.planName || '--'}}</view>
  17 + <!-- 按批次查看时一期次一条,用「第X次 + 上报状态」区分多次提交记录 -->
  18 + <view class="record-sub" v-if="i.sortNum">
  19 + 第 {{ i.sortNum }} 次 · {{ commitStateText(i.commitState) }}
  20 + </view>
17 </view> 21 </view>
18 <!-- 工单详情内容 --> 22 <!-- 工单详情内容 -->
19 <view class="form-card"> 23 <view class="form-card">
@@ -95,56 +99,66 @@ @@ -95,56 +99,66 @@
95 <view class="u-body-item-title">提交时间:</view> 99 <view class="u-body-item-title">提交时间:</view>
96 <view class="u-line-1 u-body-value">{{ i.createTime==null?'--':timeFormat(i.createTime,'yyyy-mm-dd hh:MM:ss') }}</view> 100 <view class="u-line-1 u-body-value">{{ i.createTime==null?'--':timeFormat(i.createTime,'yyyy-mm-dd hh:MM:ss') }}</view>
97 </view> 101 </view>
  102 +
  103 + <!-- 本次养护被置为失效时,把原因一并展示 -->
  104 + <view class="u-body-item u-flex" v-if="i.commitState === 3">
  105 + <view class="u-body-item-title">失效原因:</view>
  106 + <view class="u-line-1 u-body-value">{{ i.invalidReason || '--' }}</view>
  107 + </view>
98 </view> 108 </view>
99 109
100 - <!-- 动态配置的养护字段填报值(garden_work_record/garden_work_field_value),未填报过则整段不显示 -->  
101 - <!-- 动态配置的养护字段填报值(garden_work_record/garden_work_field_value),未填报过则整段不显示 -->  
102 -<view  
103 - class="form-card"  
104 - v-if="i.fieldRows && i.fieldRows.length"  
105 ->  
106 - <!-- 区块标题:蓝色竖条 + 标题 -->  
107 - <view class="sec-head">  
108 - <view class="sec-bar"></view>  
109 - <view class="sec-title">养护填报内容</view>  
110 - </view>  
111 - <view class="u-body-item u-flex field-row" v-for="(f, fi) in i.fieldRows" :key="fi">  
112 - <view class="u-body-item-title field-cell-title-multi">{{ f.name }}</view>  
113 - <view class="field-value">  
114 - <!-- 7 图片:复用本页照片的相册组件(预览/尺寸一致) -->  
115 - <up-album  
116 - v-if="f.kind === 'image'"  
117 - :urls="f.images"  
118 - singleSize="60"  
119 - multipleSize="60"  
120 - :preview-full-image="true"  
121 - ></up-album>  
122 - <!-- 6 多选 / 3 历史多选 / 9 关联记录:标签平铺(自绘,避免引入未使用的组件) -->  
123 - <view v-else-if="f.kind === 'multi'" class="field-tags">  
124 - <view v-for="(t, ti) in f.tags" :key="ti" class="field-tag">{{ t.text }}</view>  
125 - </view>  
126 - <!-- 8 附件:点击下载后用系统应用打开 -->  
127 - <view v-else-if="f.kind === 'file'" class="field-tags">  
128 - <text  
129 - v-for="(file, ui) in f.files"  
130 - :key="ui"  
131 - class="field-file"  
132 - @click="openFile(file)"  
133 - >{{ file.name }}</text>  
134 - </view>  
135 - <!-- 10 链接:小程序不能直接打开外部 URL,点击复制 -->  
136 - <text v-else-if="f.kind === 'link'" class="field-link" @click="copyLink(f.text)">{{ f.text }}</text>  
137 - <!-- 11 地理位置:点击调微信原生地图(无需地图 AK) -->  
138 - <text v-else-if="f.kind === 'geo' && f.geo" class="field-link" @click="openGeo(f.geo)">{{ f.text }}</text>  
139 - <!-- 其余(1数值/2文本/3单选枚举/4日期/5布尔等)文本直出 -->  
140 - <text v-else class="common-text-color field-text">{{ f.text }}</text>  
141 - </view>  
142 - </view>  
143 -</view>  
144 -<!-- 当没有填报字段,提示暂无提交记录 -->  
145 -<view v-else class="no-record-tip">  
146 - 直接结束了本次任务  
147 -</view> 110 + <!-- 动态配置的养护字段填报值:每条子上报(garden_work_record)单独一张卡,不把多次提交的数据混在一起 -->
  111 + <template v-if="i.recordRows && i.recordRows.length">
  112 + <view class="form-card" v-for="(r, ri) in i.recordRows" :key="ri">
  113 + <!-- 区块标题:蓝色竖条 + 标题;多条时右侧标注第几条与上报时间 -->
  114 + <view class="sec-head">
  115 + <view class="sec-bar"></view>
  116 + <view class="sec-title">养护填报内容</view>
  117 + <!-- 按批次查看时每个期次都标「第几条 + 上报时间」,单条记录也标,避免和上一期次的卡片混读 -->
  118 + <text class="sec-count" v-if="i.recordRows.length > 1 || i.sortNum">
  119 + 第 {{ ri + 1 }} 条 · {{ r.workTime == null ? '--' : timeFormat(r.workTime, 'yyyy-mm-dd hh:MM:ss') }}
  120 + </text>
  121 + </view>
  122 + <view class="u-body-item u-flex field-row" v-for="(f, fi) in r.fieldRows" :key="fi">
  123 + <view class="u-body-item-title field-cell-title-multi">{{ f.name }}</view>
  124 + <view class="field-value">
  125 + <!-- 7 图片:复用本页照片的相册组件(预览/尺寸一致) -->
  126 + <up-album
  127 + v-if="f.kind === 'image'"
  128 + :urls="f.images"
  129 + singleSize="60"
  130 + multipleSize="60"
  131 + :preview-full-image="true"
  132 + ></up-album>
  133 + <!-- 6 多选 / 3 历史多选 / 9 关联记录:标签平铺(自绘,避免引入未使用的组件) -->
  134 + <view v-else-if="f.kind === 'multi'" class="field-tags">
  135 + <view v-for="(t, ti) in f.tags" :key="ti" class="field-tag">{{ t.text }}</view>
  136 + </view>
  137 + <!-- 8 附件:点击下载后用系统应用打开 -->
  138 + <view v-else-if="f.kind === 'file'" class="field-tags">
  139 + <text
  140 + v-for="(file, ui) in f.files"
  141 + :key="ui"
  142 + class="field-file"
  143 + @click="openFile(file)"
  144 + >{{ file.name }}</text>
  145 + </view>
  146 + <!-- 10 链接:小程序不能直接打开外部 URL,点击复制 -->
  147 + <text v-else-if="f.kind === 'link'" class="field-link" @click="copyLink(f.text)">{{ f.text }}</text>
  148 + <!-- 11 地理位置:点击调微信原生地图(无需地图 AK) -->
  149 + <text v-else-if="f.kind === 'geo' && f.geo" class="field-link" @click="openGeo(f.geo)">{{ f.text }}</text>
  150 + <!-- 其余(1数值/2文本/3单选枚举/4日期/5布尔等)文本直出 -->
  151 + <text v-else class="common-text-color field-text">{{ f.text }}</text>
  152 + </view>
  153 + </view>
  154 + <!-- 该条上报没填字段时给个占位,避免出现一张空白卡 -->
  155 + <view class="no-field-tip" v-if="!r.fieldRows.length">本条未填写养护内容</view>
  156 + </view>
  157 + </template>
  158 + <!-- 当没有填报字段,提示暂无提交记录 -->
  159 + <view v-else class="no-record-tip">
  160 + {{ i.commitState === 3 ? '本次养护已置为失效,未填写养护内容' : '直接结束了本次任务' }}
  161 + </view>
148 162
149 </view> 163 </view>
150 </template> 164 </template>
@@ -195,18 +209,34 @@ const previewImage = (currentUrl, allUrls) =&gt; { @@ -195,18 +209,34 @@ const previewImage = (currentUrl, allUrls) =&gt; {
195 }); 209 });
196 }; 210 };
197 211
  212 +/** 上报状态文案:1进行中/2已完成/3已失效 */
  213 +const commitStateText = (state) => {
  214 + if (state === 1) return '进行中'
  215 + if (state === 2) return '已完成'
  216 + if (state === 3) return '已失效'
  217 + return '--'
  218 +};
  219 +
198 /** 220 /**
199 * 获取工单详情 221 * 获取工单详情
  222 + * 传 batchNo 时返回该批次下所有期次的上报记录(一次一条期次,多次提交要全部列出);
  223 + * 只传 planNo 时仍按计划编码查单条,兼容原入口。
200 */ 224 */
201 -const getOrderDetail = async (planNo: string, commitId?: string) => { 225 +const getOrderDetail = async (planNo: string, commitId?: string, batchNo?: string) => {
202 console.log('getOrderDetail') 226 console.log('getOrderDetail')
203 try { 227 try {
204 loading.value = true; 228 loading.value = true;
205 let queryData: any = { 229 let queryData: any = {
206 - planNo: planNo,  
207 pageSize: commitId ? 1 : 100, 230 pageSize: commitId ? 1 : 100,
208 pageNo: 1, 231 pageNo: 1,
209 }; 232 };
  233 + // 计划编码 + 批次号同时带上:只查当前这一期次(planNo)的提交记录,批次号用于限定范围
  234 + if (planNo) {
  235 + queryData.planNo = planNo;
  236 + }
  237 + if (batchNo) {
  238 + queryData.batchNo = batchNo;
  239 + }
210 if (commitId) { 240 if (commitId) {
211 queryData.commitId = commitId; 241 queryData.commitId = commitId;
212 } 242 }
@@ -214,12 +244,25 @@ const getOrderDetail = async (planNo: string, commitId?: string) =&gt; { @@ -214,12 +244,25 @@ const getOrderDetail = async (planNo: string, commitId?: string) =&gt; {
214 console.log('接口返回:', res); 244 console.log('接口返回:', res);
215 const list = res.data?.list || res.list || []; 245 const list = res.data?.list || res.list || [];
216 246
  247 + // 按批次查看时按「第几次」正序,读起来和 App 列表一致
  248 + if (batchNo) {
  249 + list.sort((a, b) => Number(a.sortNum || 0) - Number(b.sortNum || 0));
  250 + }
  251 +
217 orderDetail.value = list.map(item => { 252 orderDetail.value = list.map(item => {
218 - // ✅直接使用接口返回的 item.fields,不再读取 workRecords 253 + // 一次养护可能有多次子上报(workRecords),每次上报有自己的字段值,逐条展示;
  254 + // 历史数据(双写上线前)没有 workRecords,退回用 item.fields 当唯一一条记录。
219 const useValues = Array.isArray(item.fields) ? item.fields : [] 255 const useValues = Array.isArray(item.fields) ? item.fields : []
  256 + const workRecords = Array.isArray(item.workRecords) && item.workRecords.length
  257 + ? item.workRecords
  258 + : [{ workTime: item.createTime, values: useValues }]
220 return { 259 return {
221 ...item, 260 ...item,
222 - fieldRows: useValues.map(buildFieldRow) 261 + // 每条子上报一行自己的字段渲染结果,模板里一条记录一张卡
  262 + recordRows: workRecords.map(record => ({
  263 + workTime: record?.workTime ?? item.createTime,
  264 + fieldRows: (Array.isArray(record?.values) ? record.values : []).map(buildFieldRow)
  265 + }))
223 } 266 }
224 }) 267 })
225 } catch (error) { 268 } catch (error) {
@@ -532,9 +575,9 @@ const openGeo = (geo) =&gt; { @@ -532,9 +575,9 @@ const openGeo = (geo) =&gt; {
532 // const workRecordIndex = ref(0) 575 // const workRecordIndex = ref(0)
533 // 页面加载 576 // 页面加载
534 onLoad((options) => { 577 onLoad((options) => {
535 - const {planNo, commitId } = options;  
536 - if (planNo) {  
537 - getOrderDetail(planNo, commitId); 578 + const {planNo, commitId, batchNo } = options;
  579 + if (planNo || batchNo) {
  580 + getOrderDetail(planNo, commitId, batchNo);
538 } else { 581 } else {
539 loading.value = false; 582 loading.value = false;
540 uni.showToast({title: '缺少工单ID', icon: 'none'}); 583 uni.showToast({title: '缺少工单ID', icon: 'none'});
@@ -582,7 +625,9 @@ onLoad((options) =&gt; { @@ -582,7 +625,9 @@ onLoad((options) =&gt; {
582 // 计划名称标题卡:白底 / 8px 圆角 / 卡片阴影 / 32rpx 500(设计稿顶部卡) 625 // 计划名称标题卡:白底 / 8px 圆角 / 卡片阴影 / 32rpx 500(设计稿顶部卡)
583 .record-head { 626 .record-head {
584 display: flex; 627 display: flex;
585 - align-items: center; 628 + flex-direction: column;
  629 + align-items: flex-start;
  630 + gap: 8rpx;
586 margin: 0 24rpx 24rpx; 631 margin: 0 24rpx 24rpx;
587 padding: 24rpx; 632 padding: 24rpx;
588 background: #ffffff; 633 background: #ffffff;
@@ -590,6 +635,13 @@ onLoad((options) =&gt; { @@ -590,6 +635,13 @@ onLoad((options) =&gt; {
590 box-shadow: 0 4rpx 16rpx rgba(17, 24, 39, 0.06); 635 box-shadow: 0 4rpx 16rpx rgba(17, 24, 39, 0.06);
591 } 636 }
592 637
  638 +// 标题卡副标题:第X次 + 上报状态
  639 +.record-sub {
  640 + font-size: 26rpx;
  641 + color: #6B7280;
  642 + line-height: 1.4;
  643 +}
  644 +
593 .record-title { 645 .record-title {
594 flex: 1; 646 flex: 1;
595 min-width: 0; 647 min-width: 0;
@@ -625,6 +677,21 @@ onLoad((options) =&gt; { @@ -625,6 +677,21 @@ onLoad((options) =&gt; {
625 line-height: 1.4; 677 line-height: 1.4;
626 } 678 }
627 679
  680 +/* 区块标题右侧:多条上报时标注第几条 + 上报时间 */
  681 +.sec-count {
  682 + margin-left: auto;
  683 + font-size: 24rpx;
  684 + color: #9CA3AF;
  685 + white-space: nowrap;
  686 +}
  687 +
  688 +/* 某条子上报没填字段时的占位文案 */
  689 +.no-field-tip {
  690 + font-size: 26rpx;
  691 + color: #9CA3AF;
  692 + padding: 8rpx 0;
  693 +}
  694 +
628 // 图片容器样式 695 // 图片容器样式
629 .cell-content-wrap { 696 .cell-content-wrap {
630 //padding: 10rpx 0; 697 //padding: 10rpx 0;
pages-sub/daily/maintain-manage/index.vue
@@ -91,9 +91,6 @@ @@ -91,9 +91,6 @@
91 <!-- </up-button>--> 91 <!-- </up-button>-->
92 <!-- </view>--> 92 <!-- </view>-->
93 93
94 - <view class="common-custom-btn" style="--btn-top: 48px;" @click="goToDetail(item)">  
95 - 养护任务  
96 - </view>  
97 <!-- <view class="u-body-item u-flex">--> 94 <!-- <view class="u-body-item u-flex">-->
98 <!-- <view class="u-body-item-title">批次号:</view>--> 95 <!-- <view class="u-body-item-title">批次号:</view>-->
99 <!-- <view class="u-line-1 u-body-value">{{ item.batchNo || '-' }}</view>--> 96 <!-- <view class="u-line-1 u-body-value">{{ item.batchNo || '-' }}</view>-->
pages-sub/daily/maintain-manage/type-list-page.vue
@@ -14,14 +14,11 @@ @@ -14,14 +14,11 @@
14 <view class="task-card" v-for="(item, index) in currentList" :key="`${item.planNo}_${index}`"> 14 <view class="task-card" v-for="(item, index) in currentList" :key="`${item.planNo}_${index}`">
15 <!-- 计划名称 --> 15 <!-- 计划名称 -->
16 <view class="card-head"> 16 <view class="card-head">
17 - <view class="plan-name u-line-1">{{ item.planName || '无计划名称' }}</view>  
18 - </view>  
19 - <!-- 任务次数 -->  
20 - <view class="info-row">  
21 - <view class="info-label">任务次数</view>  
22 - <view class="info-value" v-if="activeStatus === '2'">完成 {{ item.planFinishNum }} 次</view>  
23 - <!-- 一次一条期次(planNum=1)时「第几次」取期次序号,历史多条次数的期次仍按完成次数递增 -->  
24 - <view class="info-value" v-else>第 {{ item.planNum === 1 ? (item.sortNum || 1) : (item.planFinishNum + 1) }} 次</view> 17 + <view class="plan-name-wrap">
  18 + <view class="plan-name u-line-1">{{ item.planName || '无计划名称' }}</view>
  19 + <!-- 同一批次下多个期次的计划名称相同,必须显示计划编码才能区分 -->
  20 + <view class="plan-no u-line-1" v-if="item.planNo">计划编码:{{ item.planNo }}</view>
  21 + </view>
25 </view> 22 </view>
26 <!-- 有效期 --> 23 <!-- 有效期 -->
27 <view class="info-row"> 24 <view class="info-row">
@@ -31,6 +28,16 @@ @@ -31,6 +28,16 @@
31 timeFormat(item.endTime, 'yyyy-mm-dd') }} 28 timeFormat(item.endTime, 'yyyy-mm-dd') }}
32 </view> 29 </view>
33 </view> 30 </view>
  31 + <!-- 任务次数 -->
  32 + <view class="info-row">
  33 + <view class="info-label">任务次数</view>
  34 + <!-- 已完成:一次一条期次时每个期次只完成 1 次,必须带上期次序号才能区分多次提交记录 -->
  35 + <view class="info-value" v-if="activeStatus === '2'">
  36 + 有效期内第 {{ item.sortNum || 1 }} 个任务,共提交 {{ item.submitCount || 0 }} 次
  37 + </view>
  38 + <!-- 一次一条期次(planNum=1)时「第几次」取期次序号,历史多条次数的期次仍按完成次数递增 -->
  39 + <view class="info-value" v-else>第 {{ item.planNum === 1 ? (item.sortNum || 1) : (item.planFinishNum + 1) }} 次</view>
  40 + </view>
34 <!-- 操作区:统一收底,与信息区用分隔线隔开 --> 41 <!-- 操作区:统一收底,与信息区用分隔线隔开 -->
35 <view class="action-row"> 42 <view class="action-row">
36 <!-- 待完成且一次都没上报过才允许置为失效;已有养护记录(进行中/已完成过)不再显示 --> 43 <!-- 待完成且一次都没上报过才允许置为失效;已有养护记录(进行中/已完成过)不再显示 -->
@@ -326,8 +333,11 @@ const handleGoWork = (item) =&gt; { @@ -326,8 +333,11 @@ const handleGoWork = (item) =&gt; {
326 } 333 }
327 const handleGoWorkLook = (item) => { 334 const handleGoWorkLook = (item) => {
328 const isView = activeStatus.value 335 const isView = activeStatus.value
  336 + // 已完成:一次一条期次,传批次号让详情页把该批次所有期次的上报记录一起列出来;
  337 + // 已失效:只看向被置为失效的那一次,仍按计划编码查单条。
  338 + const batchParam = activeStatus.value === '2' && item.batchNo ? `&batchNo=${item.batchNo}` : ''
329 uni.navigateTo({ 339 uni.navigateTo({
330 - url: `/pages-sub/daily/maintain-manage/finish-plan-detail?planNo=${item.planNo}&planTypeId=${planTypeId.value}&businessType=${item.busiLine}&isView=${isView}` 340 + url: `/pages-sub/daily/maintain-manage/finish-plan-detail?planNo=${item.planNo}&planTypeId=${planTypeId.value}&businessType=${item.busiLine}&isView=${isView}${batchParam}`
331 }) 341 })
332 } 342 }
333 const handleEndTask = async (item) => { 343 const handleEndTask = async (item) => {
@@ -408,22 +418,31 @@ onUnload(() =&gt; { @@ -408,22 +418,31 @@ onUnload(() =&gt; {
408 display: flex; 418 display: flex;
409 align-items: center; 419 align-items: center;
410 gap: 16rpx; 420 gap: 16rpx;
411 - margin-bottom: 16rpx; 421 + /* 与 .plan-no / .info-row 的间距保持一致,卡内文字行上下距离统一 */
  422 + margin-bottom: 8rpx;
412 } 423 }
413 -.plan-name { 424 +.plan-name-wrap {
414 flex: 1; 425 flex: 1;
415 min-width: 0; 426 min-width: 0;
  427 +}
  428 +.plan-name {
416 font-size: 32rpx; 429 font-size: 32rpx;
417 color: #1F2937; 430 color: #1F2937;
418 font-weight: 500; 431 font-weight: 500;
419 line-height: 1.4; 432 line-height: 1.4;
420 } 433 }
  434 +.plan-no {
  435 + margin-top: 8rpx;
  436 + font-size: 24rpx;
  437 + color: #9CA3AF;
  438 + line-height: 1.4;
  439 +}
421 .info-row { 440 .info-row {
422 display: flex; 441 display: flex;
423 align-items: center; 442 align-items: center;
424 justify-content: space-between; 443 justify-content: space-between;
425 gap: 16rpx; 444 gap: 16rpx;
426 - margin-bottom: 6rpx; 445 + margin-bottom: 8rpx;
427 } 446 }
428 .info-label { 447 .info-label {
429 font-size: 26rpx; 448 font-size: 26rpx;
pages-sub/daily/maintain-manage/type-list.vue
1 <template> 1 <template>
2 <view class="type-grid"> 2 <view class="type-grid">
3 - <!-- 动态渲染养护类型卡片 -->  
4 - <template v-if="tagList.length > 0">  
5 - <view class="type-card" v-for="(item, idx) in tagList" :key="idx" hover-class="type-card--hover" 3 + <!-- 按养护类型渲染计划卡片:同一类型下多个批次合并成一张卡,批次号/计划名称等每项一行,多批次时逐行列出 -->
  4 + <template v-if="cardList.length > 0">
  5 + <view class="type-card" v-for="item in cardList" :key="item.key" hover-class="type-card--hover"
6 @click="toNextPage(item)"> 6 @click="toNextPage(item)">
7 - <!-- 类型图标:取类型名称首字,底色淡蓝 -->  
8 - <view class="type-icon">{{ (item.planTypeName || '养护').charAt(0) }}</view>  
9 - <view class="type-name">{{ item.planTypeName }}</view>  
10 - <view class="type-foot">  
11 - <text class="type-foot-label">待完成任务</text>  
12 - <text class="type-count">{{ item.todayPendingCount }}</text> 7 + <view class="info-row" v-for="row in item.rows" :key="row.label">
  8 + <text class="info-label">{{ row.label }}:</text>
  9 + <view class="info-value">
  10 + <view class="info-line" v-for="(value, idx) in row.values" :key="idx"
  11 + :class="{ 'info-line--count': row.label === '待完成任务' }">{{ value }}</view>
  12 + </view>
13 </view> 13 </view>
14 </view> 14 </view>
15 </template> 15 </template>
@@ -20,15 +20,32 @@ @@ -20,15 +20,32 @@
20 </template> 20 </template>
21 <script setup lang="ts"> 21 <script setup lang="ts">
22 import { ref } from 'vue' 22 import { ref } from 'vue'
23 -import { onLoad, onShow, onUnload } from '@dcloudio/uni-app'; 23 +import { onShow, onUnload } from '@dcloudio/uni-app';
24 24
25 -const tagList = ref<Array<{ 25 +const cardList = ref<Array<{
  26 + key: string
26 planTypeId: string | number 27 planTypeId: string | number
27 - planTypeName: string  
28 batchNos: string[] 28 batchNos: string[]
29 - todayPendingCount: number 29 + rows: Array<{ label: string; values: string[] }>
30 }>>([]) 30 }>>([])
31 31
  32 +/** 日期字段可能是 'yyyy-MM-dd' 字符串(LocalDate)或时间戳,统一转成 yyyy-MM-dd */
  33 +const formatDate = (value: any) => {
  34 + if (value === null || value === undefined || value === '') return '--'
  35 + if (Array.isArray(value)) {
  36 + return `${value[0]}-${String(value[1]).padStart(2, '0')}-${String(value[2]).padStart(2, '0')}`
  37 + }
  38 + if (typeof value === 'number') {
  39 + const d = new Date(value)
  40 + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
  41 + }
  42 + // 'yyyy-MM-dd HH:mm:ss' 之类的字符串只取日期部分
  43 + return String(value).slice(0, 10)
  44 +}
  45 +
  46 +/** 去重并保持原顺序,合并卡片时避免同一个值重复占一行 */
  47 +const uniq = (list: any[]) => Array.from(new Set(list.filter(v => v !== null && v !== undefined && v !== '')))
  48 +
32 onShow(() => { 49 onShow(() => {
33 // 从storage拿上一页传过来完整对象 50 // 从storage拿上一页传过来完整对象
34 const item = uni.getStorageSync('temp_maintain_item') 51 const item = uni.getStorageSync('temp_maintain_item')
@@ -37,15 +54,43 @@ onShow(() =&gt; { @@ -37,15 +54,43 @@ onShow(() =&gt; {
37 title: '数据异常,请返回重试', 54 title: '数据异常,请返回重试',
38 icon: 'none' 55 icon: 'none'
39 }); 56 });
40 - return; 57 + return
41 } 58 }
42 - // 直接取planTypes  
43 - tagList.value = item.planTypes || [] 59 + const roadName = item.roadName || '--'
  60 + // 接口已按养护类型分组,同一类型下的多个批次在这里合并成一张卡
  61 + cardList.value = (item.planTypes || []).map((type: any) => {
  62 + const batches = Array.isArray(type.batches) && type.batches.length
  63 + ? type.batches
  64 + : (type.batchNos || []).map((batchNo: string) => ({
  65 + batchNo,
  66 + planName: '',
  67 + beginTime: null,
  68 + endTime: null,
  69 + todayPendingCount: type.todayPendingCount
  70 + }))
  71 + const batchNos = batches.map((batch: any) => batch.batchNo).filter(Boolean)
  72 + const pendingCount = batches.reduce((sum: number, batch: any) => sum + Number(batch.todayPendingCount || 0), 0)
  73 + return {
  74 + key: String(type.planTypeId),
  75 + planTypeId: type.planTypeId,
  76 + batchNos,
  77 + rows: [
  78 + { label: '批次号', values: batchNos.length ? batchNos : ['--'] },
  79 + { label: '计划名称', values: uniq(batches.map((batch: any) => batch.planName)).length
  80 + ? uniq(batches.map((batch: any) => batch.planName)) : ['--'] },
  81 + { label: '道路名称', values: [roadName] },
  82 + { label: '计划类型', values: [type.planTypeName || '--'] },
  83 + { label: '执行时间', values: uniq(batches.map((batch: any) =>
  84 + `${formatDate(batch.beginTime)} - ${formatDate(batch.endTime)}`)) },
  85 + { label: '待完成任务', values: [`${pendingCount}次`] }
  86 + ]
  87 + }
  88 + })
44 }); 89 });
45 90
46 -// 标签点击跳转,batchNos存入storage,并且把planTypeId拼到url传给下一页 91 +// 点击卡片跳到期次列表,把该类型下所有批次号一起带过去
47 const toNextPage = (item: any) => { 92 const toNextPage = (item: any) => {
48 - uni.setStorageSync('temp_maintain_rawList', item.batchNos) 93 + uni.setStorageSync('temp_maintain_rawList', item.batchNos || [])
49 uni.navigateTo({ 94 uni.navigateTo({
50 url: `/pages-sub/daily/maintain-manage/type-list-page?planTypeId=${item.planTypeId}` 95 url: `/pages-sub/daily/maintain-manage/type-list-page?planTypeId=${item.planTypeId}`
51 }) 96 })
@@ -57,15 +102,15 @@ onUnload(()=&gt;{ @@ -57,15 +102,15 @@ onUnload(()=&gt;{
57 }) 102 })
58 </script> 103 </script>
59 <style scoped lang="scss"> 104 <style scoped lang="scss">
60 -/* 两列卡片栅格:页面 24rpx 内边距,卡片间距 16rpx(设计稿 .type-grid) */ 105 +/* 卡片列表:页面 24rpx 内边距,卡片间距 16rpx */
61 .type-grid { 106 .type-grid {
62 - display: grid;  
63 - grid-template-columns: 1fr 1fr; 107 + display: flex;
  108 + flex-direction: column;
64 gap: 16rpx; 109 gap: 16rpx;
65 padding: 24rpx; 110 padding: 24rpx;
66 } 111 }
67 112
68 -/* 卡片:12px 内边距 / 8px 圆角 / 8rpx 内部行距(设计稿 .type-card) */ 113 +/* 卡片:一张卡对应一个养护类型(多批次合并),撑满一行 */
69 .type-card { 114 .type-card {
70 display: flex; 115 display: flex;
71 flex-direction: column; 116 flex-direction: column;
@@ -82,69 +127,44 @@ onUnload(()=&gt;{ @@ -82,69 +127,44 @@ onUnload(()=&gt;{
82 } 127 }
83 } 128 }
84 129
85 -/* 类型图标位:68rpx 方块 + 淡蓝底(设计稿 .type-ico) */  
86 -.type-icon {  
87 - display: flex;  
88 - align-items: center;  
89 - justify-content: center;  
90 - width: 68rpx;  
91 - height: 68rpx;  
92 - background: #EBF3FF;  
93 - border-radius: 8rpx;  
94 - font-size: 30rpx;  
95 - font-weight: 600;  
96 - color: #2563EB;  
97 - line-height: 1;  
98 -}  
99 -  
100 -/* 类型名称:14px / 500(设计稿 .type-card 标题) */  
101 -.type-name {  
102 - font-size: 28rpx;  
103 - font-weight: 500;  
104 - color: #1F2937;  
105 - line-height: 1.4;  
106 - white-space: nowrap;  
107 - overflow: hidden;  
108 - text-overflow: ellipsis;  
109 -}  
110 -  
111 -/* 底部统计行:左侧说明 11px 浅灰,右侧数量标签(设计稿 .type-meta) */  
112 -.type-foot { 130 +/* 信息行:左侧字段名浅灰,右侧值右对齐;多个批次时值逐行显示 */
  131 +.info-row {
113 display: flex; 132 display: flex;
114 - align-items: center; 133 + align-items: flex-start;
115 justify-content: space-between; 134 justify-content: space-between;
116 - margin-top: auto;  
117 - font-size: 22rpx;  
118 - color: #9CA3AF; 135 + gap: 16rpx;
119 } 136 }
120 137
121 -.type-foot-label {  
122 - font-size: 22rpx; 138 +.info-label {
  139 + flex-shrink: 0;
  140 + font-size: 26rpx;
123 color: #9CA3AF; 141 color: #9CA3AF;
124 line-height: 1.5; 142 line-height: 1.5;
125 } 143 }
126 144
127 -/* 数量标签:灰底胶囊(设计稿 .tag.gray) */  
128 -.type-count {  
129 - display: inline-flex;  
130 - align-items: center;  
131 - flex-shrink: 0;  
132 - height: 40rpx;  
133 - padding: 0 16rpx;  
134 - background: #F4F6FA;  
135 - border-radius: 8rpx;  
136 - font-size: 22rpx;  
137 - font-weight: 500; 145 +.info-value {
  146 + flex: 1;
  147 + min-width: 0;
  148 + text-align: right;
  149 +}
  150 +
  151 +.info-line {
  152 + font-size: 26rpx;
138 color: #4B5563; 153 color: #4B5563;
139 - line-height: 1; 154 + line-height: 1.5;
  155 + word-break: break-all;
  156 +
  157 + /* 待完成次数加重,便于一眼看到 */
  158 + &--count {
  159 + color: #2563EB;
  160 + font-weight: 500;
  161 + }
140 } 162 }
141 163
142 -/* 空状态 */  
143 .type-empty { 164 .type-empty {
144 - grid-column: 1 / -1;  
145 - padding-top: 60rpx; 165 + padding: 120rpx 0;
  166 + text-align: center;
146 font-size: 26rpx; 167 font-size: 26rpx;
147 color: #9CA3AF; 168 color: #9CA3AF;
148 - text-align: center;  
149 } 169 }
150 </style> 170 </style>