Commit 7da61cdb67f64bdcbe21d4f6b77bedf856c2b6ed
1 parent
25904bec
养护计划
Showing
8 changed files
with
932 additions
and
560 deletions
api/maintain-manage/maintain-manage.js
| 1 | 1 | ||
| 2 | -import { post, get } from '@/common/utils/request'; | 2 | +import { post, get, put } from '@/common/utils/request'; |
| 3 | 3 | ||
| 4 | /** | 4 | /** |
| 5 | * 道路汇总 | 5 | * 道路汇总 |
| @@ -34,6 +34,15 @@ export const maintainCreate = (data) => { | @@ -34,6 +34,15 @@ export const maintainCreate = (data) => { | ||
| 34 | }; | 34 | }; |
| 35 | 35 | ||
| 36 | /** | 36 | /** |
| 37 | + * 新增养护计划并完成 | ||
| 38 | + * @returns {Promise} | ||
| 39 | + */ | ||
| 40 | +export const maintainCreateFinish = (data) => { | ||
| 41 | + return post('/app-api/app/garden/maintain-plan-commit/create-and-finish',data); | ||
| 42 | +}; | ||
| 43 | + | ||
| 44 | + | ||
| 45 | +/** | ||
| 37 | * 未完成养护计划 | 46 | * 未完成养护计划 |
| 38 | * @returns {Promise} | 47 | * @returns {Promise} |
| 39 | */ | 48 | */ |
| @@ -51,3 +60,59 @@ export const detailList = (params) => { | @@ -51,3 +60,59 @@ export const detailList = (params) => { | ||
| 51 | }; | 60 | }; |
| 52 | 61 | ||
| 53 | 62 | ||
| 63 | +/** | ||
| 64 | + * 查询单条上报记录 | ||
| 65 | + * @returns {Promise} | ||
| 66 | + */ | ||
| 67 | +export const getDetailList = (params) => { | ||
| 68 | + return get('/admin-api/garden/maintain-plan-commit/get',params); | ||
| 69 | +}; | ||
| 70 | + | ||
| 71 | + | ||
| 72 | + | ||
| 73 | + | ||
| 74 | +/** | ||
| 75 | + * 养护道路 | ||
| 76 | + * @returns {Promise} | ||
| 77 | + */ | ||
| 78 | +export const maintainPlan= (params) => { | ||
| 79 | + return get('/admin-api/garden/maintain-plan/page',params); | ||
| 80 | +}; | ||
| 81 | +/** | ||
| 82 | + * 养护类型次数 | ||
| 83 | + * @returns {Promise} | ||
| 84 | + */ | ||
| 85 | +export const maintainPlanNum= (params) => { | ||
| 86 | + return get('/admin-api/garden/maintain-plan-detail/page',params); | ||
| 87 | +}; | ||
| 88 | + | ||
| 89 | + | ||
| 90 | +/** | ||
| 91 | + * 查询上报记录列表 | ||
| 92 | + * @returns {Promise} | ||
| 93 | + */ | ||
| 94 | +export const maintainPlanList= (params) => { | ||
| 95 | + return get('/app-api/app/garden/maintain-plan-commit/page',params); | ||
| 96 | +}; | ||
| 97 | + | ||
| 98 | + | ||
| 99 | +/** | ||
| 100 | + * 直接结束 | ||
| 101 | + * @returns {Promise} | ||
| 102 | + */ | ||
| 103 | +export const finishPlan= (params) => { | ||
| 104 | + return post('/app-api/app/garden/maintain-plan-commit/finish',params); | ||
| 105 | +}; | ||
| 106 | + | ||
| 107 | +/** | ||
| 108 | + * 设为失效 | ||
| 109 | + * @returns {Promise} | ||
| 110 | + */ | ||
| 111 | +export const invalidatePlan= (params) => { | ||
| 112 | + return put('/admin-api/garden/maintain-plan-detail/invalidate',params); | ||
| 113 | +}; | ||
| 114 | + | ||
| 115 | + | ||
| 116 | + | ||
| 117 | + | ||
| 118 | + |
pages-sub/daily/maintain-manage/add-record.vue
| @@ -4,44 +4,40 @@ | @@ -4,44 +4,40 @@ | ||
| 4 | <up-form label-position="left" :model="inspectForm" ref="inspectFormRef" label-width="140rpx"> | 4 | <up-form label-position="left" :model="inspectForm" ref="inspectFormRef" label-width="140rpx"> |
| 5 | <!-- ========== 动态表单区域 ========== --> | 5 | <!-- ========== 动态表单区域 ========== --> |
| 6 | <template v-for="item in dynamicFields" :key="item.fieldCode"> | 6 | <template v-for="item in dynamicFields" :key="item.fieldCode"> |
| 7 | - <up-form-item :label="item.fieldName" :prop="item.fieldCode" class="form-item" @click="openPickerIfNeeded(item)"> | 7 | + <up-form-item :label="item.fieldName" :prop="item.fieldCode" class="form-item" |
| 8 | + @click="openPickerIfNeeded(item)"> | ||
| 8 | 9 | ||
| 9 | <!-- type 1 数值:u-number-box,最多2位小数,min=0,max取item.length --> | 10 | <!-- type 1 数值:u-number-box,最多2位小数,min=0,max取item.length --> |
| 10 | <view v-if="item.fieldType === 1" class="number-box-wrap"> | 11 | <view v-if="item.fieldType === 1" class="number-box-wrap"> |
| 11 | - <u-number-box | ||
| 12 | - v-model="inspectForm[item.fieldCode]" | ||
| 13 | - min="1" | ||
| 14 | - :step="1" | ||
| 15 | - :max="item.length ? 10 ** Number(item.length) - 1 : null" | ||
| 16 | - :placeholder="`请输入${item.fieldName}${item.unit} `" | ||
| 17 | - @change="valChange(item)" | ||
| 18 | - @blur="valChange(item)" | ||
| 19 | - class="large-number-box"> | ||
| 20 | -</u-number-box> | 12 | + <u-number-box v-model="inspectForm[item.fieldCode]" min="1" :step="1" |
| 13 | + :max="item.length ? 10 ** Number(item.length) - 1 : null" | ||
| 14 | + :placeholder="`请输入${item.fieldName}${item.unit} `" @change="valChange(item)" @blur="valChange(item)" | ||
| 15 | + class="large-number-box"> | ||
| 16 | + </u-number-box> | ||
| 21 | 17 | ||
| 22 | <text class="unit-text" v-if="item.unit">{{ item.unit }}</text> | 18 | <text class="unit-text" v-if="item.unit">{{ item.unit }}</text> |
| 23 | </view> | 19 | </view> |
| 24 | <!-- type 2 文本 --> | 20 | <!-- type 2 文本 --> |
| 25 | <up-input v-else-if="item.fieldType === 2" v-model="inspectForm[item.fieldCode]" | 21 | <up-input v-else-if="item.fieldType === 2" v-model="inspectForm[item.fieldCode]" |
| 26 | - :maxlength="item.length || 50" :placeholder="`请输入${item.fieldName}`"></up-input> | 22 | + :maxlength="item.length || 50" :placeholder="`请输入${item.fieldName}`"></up-input> |
| 27 | <!-- 分层选项(3枚举/6多选,且选项带 path):统一只读入口,点开分组弹层选择。 | 23 | <!-- 分层选项(3枚举/6多选,且选项带 path):统一只读入口,点开分组弹层选择。 |
| 28 | 必须放在下面的 3/6 分支之前——v-else-if 短路,放后面就永远进不来 --> | 24 | 必须放在下面的 3/6 分支之前——v-else-if 短路,放后面就永远进不来 --> |
| 29 | <up-input v-else-if="isGroupedField(item)" v-model="labelMap[item.fieldCode]" readonly | 25 | <up-input v-else-if="isGroupedField(item)" v-model="labelMap[item.fieldCode]" readonly |
| 30 | - disabled-color="#ffffff" :placeholder="`请选择${item.fieldName}`" border="none"> | 26 | + disabled-color="#ffffff" :placeholder="`请选择${item.fieldName}`" border="none"> |
| 31 | <template #right> | 27 | <template #right> |
| 32 | <up-icon name="arrow-right" size="16"></up-icon> | 28 | <up-icon name="arrow-right" size="16"></up-icon> |
| 33 | </template> | 29 | </template> |
| 34 | </up-input> | 30 | </up-input> |
| 35 | <!-- type3 枚举:多选 multiple=1 使用复选框组(name 必须是 value,提交按逗号拼接 value,与值格式契约一致) --> | 31 | <!-- type3 枚举:多选 multiple=1 使用复选框组(name 必须是 value,提交按逗号拼接 value,与值格式契约一致) --> |
| 36 | <u-checkbox-group v-else-if="item.fieldType === 3 && item.multiple === 1" | 32 | <u-checkbox-group v-else-if="item.fieldType === 3 && item.multiple === 1" |
| 37 | - v-model="inspectForm[item.fieldCode]"> | 33 | + v-model="inspectForm[item.fieldCode]"> |
| 38 | <u-checkbox v-for="opt in item.selectOptions" :key="opt.value" :label="opt.label" :name="opt.value"> | 34 | <u-checkbox v-for="opt in item.selectOptions" :key="opt.value" :label="opt.label" :name="opt.value"> |
| 39 | 35 | ||
| 40 | </u-checkbox> | 36 | </u-checkbox> |
| 41 | </u-checkbox-group> | 37 | </u-checkbox-group> |
| 42 | <!-- type3 枚举:单选 用up-action-sheet 底部选择 --> | 38 | <!-- type3 枚举:单选 用up-action-sheet 底部选择 --> |
| 43 | <up-input v-else-if="item.fieldType === 3" v-model="labelMap[item.fieldCode]" readonly | 39 | <up-input v-else-if="item.fieldType === 3" v-model="labelMap[item.fieldCode]" readonly |
| 44 | - disabled-color="#ffffff" :placeholder="`请选择${item.fieldName}`" border="none"> | 40 | + disabled-color="#ffffff" :placeholder="`请选择${item.fieldName}`" border="none"> |
| 45 | <template #right> | 41 | <template #right> |
| 46 | <up-icon name="arrow-right" size="16"></up-icon> | 42 | <up-icon name="arrow-right" size="16"></up-icon> |
| 47 | </template> | 43 | </template> |
| @@ -59,7 +55,7 @@ | @@ -59,7 +55,7 @@ | ||
| 59 | <view v-else-if="item.fieldType === 6" class="multi-wrap"> | 55 | <view v-else-if="item.fieldType === 6" class="multi-wrap"> |
| 60 | <u-checkbox-group v-model="inspectForm[item.fieldCode]" @change="onMultiChange(item)"> | 56 | <u-checkbox-group v-model="inspectForm[item.fieldCode]" @change="onMultiChange(item)"> |
| 61 | <u-checkbox v-for="opt in item.selectOptions" :key="opt.value" :label="opt.label" :name="opt.value" | 57 | <u-checkbox v-for="opt in item.selectOptions" :key="opt.value" :label="opt.label" :name="opt.value" |
| 62 | - :disabled="isOptionLocked(item, opt)"> | 58 | + :disabled="isOptionLocked(item, opt)"> |
| 63 | </u-checkbox> | 59 | </u-checkbox> |
| 64 | </u-checkbox-group> | 60 | </u-checkbox-group> |
| 65 | <!-- 有限上限时提示已选数。上限靠「禁用未选中项」实现:u-checkbox 的勾选态在组件内部,回写 v-model 撤不掉 --> | 61 | <!-- 有限上限时提示已选数。上限靠「禁用未选中项」实现:u-checkbox 的勾选态在组件内部,回写 v-model 撤不掉 --> |
| @@ -73,22 +69,12 @@ | @@ -73,22 +69,12 @@ | ||
| 73 | </view> | 69 | </view> |
| 74 | </view> | 70 | </view> |
| 75 | <!-- type7 图片:多张上传,提交 JSON 数组(URL) --> | 71 | <!-- type7 图片:多张上传,提交 JSON 数组(URL) --> |
| 76 | - <!-- type7 图片:多张上传,提交 JSON 数组(URL) --> | ||
| 77 | -<!-- type7 图片:多张上传,提交 JSON 数组(URL)【对齐工单上传组件】 --> | ||
| 78 | -<up-upload | ||
| 79 | - v-else-if="item.fieldType === 7" | ||
| 80 | - :file-list="fileListMap[item.fieldCode] || []" | ||
| 81 | - @after-read="(event) => uploadFieldFiles(item, event)" | ||
| 82 | - @delete="(event) => deleteFieldFile(item, event)" | ||
| 83 | - multiple | ||
| 84 | - width="70" | ||
| 85 | - height="70" | ||
| 86 | - :sizeType="['compressed']" | ||
| 87 | - :max-count="pickCountOf(item, 5)" | ||
| 88 | - upload-text="选择图片" | ||
| 89 | - del-color="#ff4d4f" | ||
| 90 | - class="upload-wrap" | ||
| 91 | -></up-upload> | 72 | + <!-- type7 图片:多张上传,提交 JSON 数组(URL) --> |
| 73 | + <!-- type7 图片:多张上传,提交 JSON 数组(URL)【对齐工单上传组件】 --> | ||
| 74 | + <up-upload v-else-if="item.fieldType === 7" :file-list="fileListMap[item.fieldCode] || []" | ||
| 75 | + @after-read="(event) => uploadFieldFiles(item, event)" @delete="(event) => deleteFieldFile(item, event)" | ||
| 76 | + multiple width="70" height="70" :sizeType="['compressed']" :max-count="pickCountOf(item, 5)" | ||
| 77 | + upload-text="选择图片" del-color="#ff4d4f" class="upload-wrap"></up-upload> | ||
| 92 | 78 | ||
| 93 | 79 | ||
| 94 | <!-- type8 附件:微信只能从聊天记录选文件(无通用文件选择器),提交 JSON 数组(URL) --> | 80 | <!-- type8 附件:微信只能从聊天记录选文件(无通用文件选择器),提交 JSON 数组(URL) --> |
| @@ -98,21 +84,21 @@ | @@ -98,21 +84,21 @@ | ||
| 98 | <text class="file-del" @click.stop="deleteFieldFile(item, { index: idx })">删除</text> | 84 | <text class="file-del" @click.stop="deleteFieldFile(item, { index: idx })">删除</text> |
| 99 | </view> | 85 | </view> |
| 100 | <up-button size="small" type="primary" plain :text="fileButtonText(item)" | 86 | <up-button size="small" type="primary" plain :text="fileButtonText(item)" |
| 101 | - @click.stop="chooseFieldFiles(item)"></up-button> | 87 | + @click.stop="chooseFieldFiles(item)"></up-button> |
| 102 | </view> | 88 | </view> |
| 103 | <!-- type9 关联记录:底部选择,提交 JSON 数组 [{id,name}] --> | 89 | <!-- type9 关联记录:底部选择,提交 JSON 数组 [{id,name}] --> |
| 104 | <up-input v-else-if="item.fieldType === 9" v-model="labelMap[item.fieldCode]" readonly | 90 | <up-input v-else-if="item.fieldType === 9" v-model="labelMap[item.fieldCode]" readonly |
| 105 | - disabled-color="#ffffff" :placeholder="`请选择${item.fieldName}`" border="none"> | 91 | + disabled-color="#ffffff" :placeholder="`请选择${item.fieldName}`" border="none"> |
| 106 | <template #right> | 92 | <template #right> |
| 107 | <up-icon name="arrow-right" size="16"></up-icon> | 93 | <up-icon name="arrow-right" size="16"></up-icon> |
| 108 | </template> | 94 | </template> |
| 109 | </up-input> | 95 | </up-input> |
| 110 | <!-- type10 链接:URL 输入,提交裸 URL --> | 96 | <!-- type10 链接:URL 输入,提交裸 URL --> |
| 111 | <up-input v-else-if="item.fieldType === 10" v-model.trim="inspectForm[item.fieldCode]" | 97 | <up-input v-else-if="item.fieldType === 10" v-model.trim="inspectForm[item.fieldCode]" |
| 112 | - :placeholder="`请输入${item.fieldName}(http/https 开头)`"></up-input> | 98 | + :placeholder="`请输入${item.fieldName}(http/https 开头)`"></up-input> |
| 113 | <!-- type11 地理位置:uni.chooseLocation 选点,提交 JSON 对象 {lng,lat,address} --> | 99 | <!-- type11 地理位置:uni.chooseLocation 选点,提交 JSON 对象 {lng,lat,address} --> |
| 114 | <up-input v-else-if="item.fieldType === 11" v-model="labelMap[item.fieldCode]" readonly | 100 | <up-input v-else-if="item.fieldType === 11" v-model="labelMap[item.fieldCode]" readonly |
| 115 | - disabled-color="#ffffff" :placeholder="`点击选择${item.fieldName}`" border="none"> | 101 | + disabled-color="#ffffff" :placeholder="`点击选择${item.fieldName}`" border="none"> |
| 116 | <template #right> | 102 | <template #right> |
| 117 | <up-icon name="arrow-right" size="16"></up-icon> | 103 | <up-icon name="arrow-right" size="16"></up-icon> |
| 118 | </template> | 104 | </template> |
| @@ -138,38 +124,40 @@ | @@ -138,38 +124,40 @@ | ||
| 138 | </up-form-item> --> | 124 | </up-form-item> --> |
| 139 | </up-form> | 125 | </up-form> |
| 140 | </view> | 126 | </view> |
| 141 | - <!-- 底部提交按钮 --> | ||
| 142 | - <view v-if="dynamicFields.length === 0" class="empty-field-tip"> | ||
| 143 | - <text>请去平台配置业务类型字段</text> | ||
| 144 | -</view> | ||
| 145 | -<!-- ========== 底部提交按钮:只有存在动态字段才显示 ========== --> | ||
| 146 | -<view class="fixed-bottom-btn-wrap" v-if="dynamicFields.length > 0"> | ||
| 147 | -<up-button type="primary" text="提交" @click="submitInspect" | ||
| 148 | -style="width: 100%; height: 88rpx; font-size:32rpx; border-radius: 0;"></up-button> | ||
| 149 | -</view> | 127 | + <!-- 底部双按钮 --> |
| 128 | + <view v-if="dynamicFields.length === 0" class="empty-field-tip"> | ||
| 129 | + <text>请去平台配置业务类型字段</text> | ||
| 130 | + </view> | ||
| 131 | + <view class="fixed-bottom-btn-wrap" v-if="dynamicFields.length > 0"> | ||
| 132 | + <view class="btn-item btn-left" @click="submitInspect"> | ||
| 133 | + <text>提交</text> | ||
| 134 | + </view> | ||
| 135 | + <view class="btn-item btn-right" @click="submitAndFinish"> | ||
| 136 | + <text>提交并结束本次任务</text> | ||
| 137 | + </view> | ||
| 138 | + </view> | ||
| 150 | <!-- uview-plus 日期时间选择器(全局复用) --> | 139 | <!-- uview-plus 日期时间选择器(全局复用) --> |
| 151 | <up-datetime-picker :show="datePickerShow" v-model="datePickerValue" mode="datetime" close-on-click-overlay | 140 | <up-datetime-picker :show="datePickerShow" v-model="datePickerValue" mode="datetime" close-on-click-overlay |
| 152 | - @confirm="onDateConfirm" @cancel="onDateClose" @close="onDateClose"></up-datetime-picker> | 141 | + @confirm="onDateConfirm" @cancel="onDateClose" @close="onDateClose"></up-datetime-picker> |
| 153 | <!-- ActionSheet 底部选择弹窗 --> | 142 | <!-- ActionSheet 底部选择弹窗 --> |
| 154 | <up-action-sheet :show="actionSheet.show" :actions="actionSheet.options" @select="onActionSheetSelect" | 143 | <up-action-sheet :show="actionSheet.show" :actions="actionSheet.options" @select="onActionSheetSelect" |
| 155 | - @close="actionSheet.show = false" cancel-text="取消"></up-action-sheet> | 144 | + @close="actionSheet.show = false" cancel-text="取消"></up-action-sheet> |
| 156 | <!-- 分层选项选择器(选项带 path 的动态字段) --> | 145 | <!-- 分层选项选择器(选项带 path 的动态字段) --> |
| 157 | <grouped-picker :show="groupPicker.show" :title="groupPicker.title" :options="groupPicker.options" | 146 | <grouped-picker :show="groupPicker.show" :title="groupPicker.title" :options="groupPicker.options" |
| 158 | - :multiple="groupPicker.multiple" :max-count="groupPicker.maxCount" | ||
| 159 | - :need-quantity="groupPicker.needQuantity" :value="groupPicker.value" | ||
| 160 | - :container-width="groupPicker.containerWidth" | ||
| 161 | - :quantities="groupPicker.quantities" @update:show="groupPicker.show = $event" | ||
| 162 | - @confirm="onGroupedConfirm"></grouped-picker> | 147 | + :multiple="groupPicker.multiple" :max-count="groupPicker.maxCount" :need-quantity="groupPicker.needQuantity" |
| 148 | + :value="groupPicker.value" :container-width="groupPicker.containerWidth" :quantities="groupPicker.quantities" | ||
| 149 | + @update:show="groupPicker.show = $event" @confirm="onGroupedConfirm"></grouped-picker> | ||
| 163 | </view> | 150 | </view> |
| 164 | </template> | 151 | </template> |
| 165 | 152 | ||
| 166 | <script lang="ts"> | 153 | <script lang="ts"> |
| 167 | import { uploadImages } from '@/common/utils/upload' | 154 | import { uploadImages } from '@/common/utils/upload' |
| 168 | -import { maintainCreate } from "@/api/maintain-manage/maintain-manage" | 155 | +import { maintainCreate, maintainCreateFinish } from "@/api/maintain-manage/maintain-manage" |
| 169 | import { getByType, getOptionsByCode, getMaterialByCode, workRecordSubmit } from "@/api/common" | 156 | import { getByType, getOptionsByCode, getMaterialByCode, workRecordSubmit } from "@/api/common" |
| 170 | export default { | 157 | export default { |
| 171 | data() { | 158 | data() { |
| 172 | return { | 159 | return { |
| 160 | + submitting: false, | ||
| 173 | imagesList: [], | 161 | imagesList: [], |
| 174 | initProgress: 0, | 162 | initProgress: 0, |
| 175 | inspectForm: { | 163 | inspectForm: { |
| @@ -234,7 +222,7 @@ export default { | @@ -234,7 +222,7 @@ export default { | ||
| 234 | // 数量单位不再传进弹层(原型列表行右侧只有步进器,没有单位),页面行内仍显示 item.unit | 222 | // 数量单位不再传进弹层(原型列表行右侧只有步进器,没有单位),页面行内仍显示 item.unit |
| 235 | unit: '', | 223 | unit: '', |
| 236 | // 新增:由父页面点击时赋值 | 224 | // 新增:由父页面点击时赋值 |
| 237 | - containerWidth: 0 | 225 | + containerWidth: 0 |
| 238 | } | 226 | } |
| 239 | } | 227 | } |
| 240 | }, | 228 | }, |
| @@ -328,15 +316,15 @@ export default { | @@ -328,15 +316,15 @@ export default { | ||
| 328 | this.groupPicker.needQuantity = item.fieldType === 6 && this.needQty(item) | 316 | this.groupPicker.needQuantity = item.fieldType === 6 && this.needQty(item) |
| 329 | this.groupPicker.value = multiple ? (this.inspectForm[code] || []).slice() : (this.inspectForm[code] || '') | 317 | this.groupPicker.value = multiple ? (this.inspectForm[code] || []).slice() : (this.inspectForm[code] || '') |
| 330 | this.groupPicker.quantities = Object.assign({}, this.qtyMap[code] || {}) | 318 | this.groupPicker.quantities = Object.assign({}, this.qtyMap[code] || {}) |
| 331 | - // ========== 新增:点击打开弹窗瞬间,获取父表单容器宽度 ========== | ||
| 332 | - const query = uni.createSelectorQuery().in(this) | ||
| 333 | - query.select('.inspect-form-content').boundingClientRect((rect) => { | ||
| 334 | - if (rect && rect.width) { | ||
| 335 | - // rect.width 单位 px,直接传给子组件 | ||
| 336 | - this.groupPicker.containerWidth = rect.width | ||
| 337 | - } | ||
| 338 | - this.groupPicker.show = true | ||
| 339 | - }).exec() | 319 | + // ========== 新增:点击打开弹窗瞬间,获取父表单容器宽度 ========== |
| 320 | + const query = uni.createSelectorQuery().in(this) | ||
| 321 | + query.select('.inspect-form-content').boundingClientRect((rect) => { | ||
| 322 | + if (rect && rect.width) { | ||
| 323 | + // rect.width 单位 px,直接传给子组件 | ||
| 324 | + this.groupPicker.containerWidth = rect.width | ||
| 325 | + } | ||
| 326 | + this.groupPicker.show = true | ||
| 327 | + }).exec() | ||
| 340 | }, | 328 | }, |
| 341 | /** | 329 | /** |
| 342 | * 分层弹层确定回调。写入位置与原有两条路径完全一致(inspectForm/labelMap/qtyMap), | 330 | * 分层弹层确定回调。写入位置与原有两条路径完全一致(inspectForm/labelMap/qtyMap), |
| @@ -363,8 +351,8 @@ export default { | @@ -363,8 +351,8 @@ export default { | ||
| 363 | // 物料带 currentNum 时在展示名后附库存余量(只影响展示名,提交的 materialName 仍取干净的 opt.label) | 351 | // 物料带 currentNum 时在展示名后附库存余量(只影响展示名,提交的 materialName 仍取干净的 opt.label) |
| 364 | this.actionSheet.options = (item.selectOptions || []).map(opt => { | 352 | this.actionSheet.options = (item.selectOptions || []).map(opt => { |
| 365 | const stock = opt.currentNum === null || opt.currentNum === undefined || opt.currentNum === '' | 353 | const stock = opt.currentNum === null || opt.currentNum === undefined || opt.currentNum === '' |
| 366 | - ? NaN | ||
| 367 | - : Number(opt.currentNum) | 354 | + ? NaN |
| 355 | + : Number(opt.currentNum) | ||
| 368 | return { | 356 | return { |
| 369 | name: isNaN(stock) ? opt.label : `${opt.label}(库存${stock}${opt.unit || ''})`, | 357 | name: isNaN(stock) ? opt.label : `${opt.label}(库存${stock}${opt.unit || ''})`, |
| 370 | value: opt.value | 358 | value: opt.value |
| @@ -487,9 +475,9 @@ export default { | @@ -487,9 +475,9 @@ export default { | ||
| 487 | // 3物料为标量(领用数量),默认 0 = 本次不领用、不扣库存 | 475 | // 3物料为标量(领用数量),默认 0 = 本次不领用、不扣库存 |
| 488 | if (field.fieldType === 6 && this.needQty(field)) { | 476 | if (field.fieldType === 6 && this.needQty(field)) { |
| 489 | const qty = {} | 477 | const qty = {} |
| 490 | - ;(field.selectOptions || []).forEach(opt => { | ||
| 491 | - qty[opt.value] = '' | ||
| 492 | - }) | 478 | + ; (field.selectOptions || []).forEach(opt => { |
| 479 | + qty[opt.value] = '' | ||
| 480 | + }) | ||
| 493 | this.qtyMap[field.fieldCode] = qty | 481 | this.qtyMap[field.fieldCode] = qty |
| 494 | } | 482 | } |
| 495 | if (field.fieldType === 3 && field.optionSource === 3) { | 483 | if (field.fieldType === 3 && field.optionSource === 3) { |
| @@ -657,50 +645,50 @@ export default { | @@ -657,50 +645,50 @@ export default { | ||
| 657 | * 7/8/9/11 的值是数组或对象,async-validator 的 required 按默认 string 类型判定, | 645 | * 7/8/9/11 的值是数组或对象,async-validator 的 required 按默认 string 类型判定, |
| 658 | * 会放过「空数组」「null」⇒ 必须用 validator 显式判空,否则未选也能提交 | 646 | * 会放过「空数组」「null」⇒ 必须用 validator 显式判空,否则未选也能提交 |
| 659 | */ | 647 | */ |
| 660 | -buildRequiredRule(field: any) { | ||
| 661 | - const type = field.fieldType | ||
| 662 | - if (type === 6 || type === 7 || type === 8) { | ||
| 663 | - const action = type === 6 ? '请选择' : '请上传' | ||
| 664 | - return { | ||
| 665 | - required: true, | ||
| 666 | - trigger: ['change','blur'], | ||
| 667 | - validator: (rule, value, callback) => { | ||
| 668 | - Array.isArray(value) && value.length > 0 ? callback() : callback(new Error(`${action}${field.fieldName}`)) | 648 | + buildRequiredRule(field: any) { |
| 649 | + const type = field.fieldType | ||
| 650 | + if (type === 6 || type === 7 || type === 8) { | ||
| 651 | + const action = type === 6 ? '请选择' : '请上传' | ||
| 652 | + return { | ||
| 653 | + required: true, | ||
| 654 | + trigger: ['change', 'blur'], | ||
| 655 | + validator: (rule, value, callback) => { | ||
| 656 | + Array.isArray(value) && value.length > 0 ? callback() : callback(new Error(`${action}${field.fieldName}`)) | ||
| 657 | + } | ||
| 658 | + } | ||
| 669 | } | 659 | } |
| 670 | - } | ||
| 671 | - } | ||
| 672 | - if (type === 9 || type === 11) { | ||
| 673 | - return { | ||
| 674 | - required: true, | ||
| 675 | - trigger: ['change','blur'], | ||
| 676 | - validator: (rule, value, callback) => { | ||
| 677 | - value ? callback() : callback(new Error(type === 9 ? `请选择${field.fieldName}` : `请选择${field.fieldName}的位置`)) | 660 | + if (type === 9 || type === 11) { |
| 661 | + return { | ||
| 662 | + required: true, | ||
| 663 | + trigger: ['change', 'blur'], | ||
| 664 | + validator: (rule, value, callback) => { | ||
| 665 | + value ? callback() : callback(new Error(type === 9 ? `请选择${field.fieldName}` : `请选择${field.fieldName}的位置`)) | ||
| 666 | + } | ||
| 667 | + } | ||
| 678 | } | 668 | } |
| 679 | - } | ||
| 680 | - } | ||
| 681 | - // ========= 新增:数值类型单独校验 ========= | ||
| 682 | - if (type === 1) { | ||
| 683 | - return { | ||
| 684 | - required: true, | ||
| 685 | - trigger: ['change','blur'], | ||
| 686 | - validator: (rule, value, callback) => { | ||
| 687 | - // 空字符串、null、undefined 才是空;数字 1,7,100 全部合法 | ||
| 688 | - if (value === '' || value === null || value === undefined) { | ||
| 689 | - callback(new Error(`请填写${field.fieldName}`)) | ||
| 690 | - } else { | ||
| 691 | - callback() | 669 | + // ========= 新增:数值类型单独校验 ========= |
| 670 | + if (type === 1) { | ||
| 671 | + return { | ||
| 672 | + required: true, | ||
| 673 | + trigger: ['change', 'blur'], | ||
| 674 | + validator: (rule, value, callback) => { | ||
| 675 | + // 空字符串、null、undefined 才是空;数字 1,7,100 全部合法 | ||
| 676 | + if (value === '' || value === null || value === undefined) { | ||
| 677 | + callback(new Error(`请填写${field.fieldName}`)) | ||
| 678 | + } else { | ||
| 679 | + callback() | ||
| 680 | + } | ||
| 681 | + } | ||
| 692 | } | 682 | } |
| 693 | } | 683 | } |
| 694 | - } | ||
| 695 | - } | ||
| 696 | - // 2文本 /4日期 /5布尔 /10链接 | ||
| 697 | - const action = type === 3 || type === 5 ? '请选择' : '请填写' | ||
| 698 | - return { | ||
| 699 | - required: true, | ||
| 700 | - message: `${action}${field.fieldName}`, | ||
| 701 | - trigger: ['change','blur'] | ||
| 702 | - } | ||
| 703 | -}, | 684 | + // 2文本 /4日期 /5布尔 /10链接 |
| 685 | + const action = type === 3 || type === 5 ? '请选择' : '请填写' | ||
| 686 | + return { | ||
| 687 | + required: true, | ||
| 688 | + message: `${action}${field.fieldName}`, | ||
| 689 | + trigger: ['change', 'blur'] | ||
| 690 | + } | ||
| 691 | + }, | ||
| 704 | 692 | ||
| 705 | /** 幂等流水号(后端按 userId+clientSeq 去重,命中则返回已存在的 recordId) */ | 693 | /** 幂等流水号(后端按 userId+clientSeq 去重,命中则返回已存在的 recordId) */ |
| 706 | genClientSeq() { | 694 | genClientSeq() { |
| @@ -727,26 +715,26 @@ buildRequiredRule(field: any) { | @@ -727,26 +715,26 @@ buildRequiredRule(field: any) { | ||
| 727 | /** | 715 | /** |
| 728 | * 7图片 上传(u-upload 的 after-read 回调)【对齐工单上传逻辑】 | 716 | * 7图片 上传(u-upload 的 after-read 回调)【对齐工单上传逻辑】 |
| 729 | */ | 717 | */ |
| 730 | -uploadFieldFiles(item: any, event: any) { | ||
| 731 | - console.log('动态图片上传', item.fieldName, event) | ||
| 732 | - const rawFile = event && event.file | ||
| 733 | - const fileList = Array.isArray(rawFile) ? rawFile : [rawFile] | ||
| 734 | - const targetImgList = this.fileListMap[item.fieldCode] || (this.fileListMap[item.fieldCode] = []) | ||
| 735 | - const filePaths = fileList.map(file => file.url) | ||
| 736 | - // const tempItems = fileList.map(item => ({ | ||
| 737 | - // ...item, | ||
| 738 | - // status: 'uploading', | ||
| 739 | - // message: '上传中' | ||
| 740 | - // })) | ||
| 741 | - // const startIndex = targetImgList.length | ||
| 742 | - // targetImgList.push(...tempItems) | 718 | + uploadFieldFiles(item: any, event: any) { |
| 719 | + console.log('动态图片上传', item.fieldName, event) | ||
| 720 | + const rawFile = event && event.file | ||
| 721 | + const fileList = Array.isArray(rawFile) ? rawFile : [rawFile] | ||
| 722 | + const targetImgList = this.fileListMap[item.fieldCode] || (this.fileListMap[item.fieldCode] = []) | ||
| 723 | + const filePaths = fileList.map(file => file.url) | ||
| 724 | + // const tempItems = fileList.map(item => ({ | ||
| 725 | + // ...item, | ||
| 726 | + // status: 'uploading', | ||
| 727 | + // message: '上传中' | ||
| 728 | + // })) | ||
| 729 | + // const startIndex = targetImgList.length | ||
| 730 | + // targetImgList.push(...tempItems) | ||
| 743 | 731 | ||
| 744 | - this.uploadToField(item, fileList.filter(file => file && file.url).map(file => ({ | ||
| 745 | - url: file.url, | ||
| 746 | - name: file.name || '', | ||
| 747 | - size: file.size | ||
| 748 | - }))) | ||
| 749 | -}, | 732 | + this.uploadToField(item, fileList.filter(file => file && file.url).map(file => ({ |
| 733 | + url: file.url, | ||
| 734 | + name: file.name || '', | ||
| 735 | + size: file.size | ||
| 736 | + }))) | ||
| 737 | + }, | ||
| 750 | 738 | ||
| 751 | /** | 739 | /** |
| 752 | * 8附件 选择并上传。 | 740 | * 8附件 选择并上传。 |
| @@ -783,65 +771,65 @@ uploadFieldFiles(item: any, event: any) { | @@ -783,65 +771,65 @@ uploadFieldFiles(item: any, event: any) { | ||
| 783 | * 把选中的本地文件逐个上传并把 http 地址回填到 fileListMap / inspectForm。 | 771 | * 把选中的本地文件逐个上传并把 http 地址回填到 fileListMap / inspectForm。 |
| 784 | * 7图片与8附件共用(uploadImages 是通用 uni.uploadFile,不限图片类型) | 772 | * 7图片与8附件共用(uploadImages 是通用 uni.uploadFile,不限图片类型) |
| 785 | */ | 773 | */ |
| 786 | - async uploadToField(item: any, files: any[]) { | ||
| 787 | -const code = item.fieldCode | ||
| 788 | -const maxCount = this.maxCountOf(item, 5) | ||
| 789 | -const maxSizeMb = this.maxSizeMbOf(item, 5) | ||
| 790 | -const validFiles = (files || []).filter(file => file && file.url && !this.isOversize(file, maxSizeMb)) | ||
| 791 | -if (!validFiles.length) { | ||
| 792 | -return | ||
| 793 | -} | ||
| 794 | -const targetList = this.fileListMap[code] || (this.fileListMap[code] = []) | ||
| 795 | -if (targetList.length + validFiles.length > maxCount) { | ||
| 796 | -uni.showToast({ title: `最多只能上传${maxCount}个`, icon: 'none' }) | ||
| 797 | -return | ||
| 798 | -} | ||
| 799 | -// 保存起始下标 | ||
| 800 | -const startIndex = targetList.length | ||
| 801 | -// 只在这里push一次 | ||
| 802 | -targetList.push(...validFiles.map(file => ({ | ||
| 803 | -url: file.url, status: 'uploading', message: '上传中', name: file.name || '' | ||
| 804 | -}))) | ||
| 805 | -try { | ||
| 806 | -const uploadResultUrls = await uploadImages({ | ||
| 807 | -filePaths: validFiles.map(file => file.url), | ||
| 808 | -ignoreError: true | 774 | + async uploadToField(item: any, files: any[]) { |
| 775 | + const code = item.fieldCode | ||
| 776 | + const maxCount = this.maxCountOf(item, 5) | ||
| 777 | + const maxSizeMb = this.maxSizeMbOf(item, 5) | ||
| 778 | + const validFiles = (files || []).filter(file => file && file.url && !this.isOversize(file, maxSizeMb)) | ||
| 779 | + if (!validFiles.length) { | ||
| 780 | + return | ||
| 781 | + } | ||
| 782 | + const targetList = this.fileListMap[code] || (this.fileListMap[code] = []) | ||
| 783 | + if (targetList.length + validFiles.length > maxCount) { | ||
| 784 | + uni.showToast({ title: `最多只能上传${maxCount}个`, icon: 'none' }) | ||
| 785 | + return | ||
| 786 | + } | ||
| 787 | + // 保存起始下标 | ||
| 788 | + const startIndex = targetList.length | ||
| 789 | + // 只在这里push一次 | ||
| 790 | + targetList.push(...validFiles.map(file => ({ | ||
| 791 | + url: file.url, status: 'uploading', message: '上传中', name: file.name || '' | ||
| 792 | + }))) | ||
| 793 | + try { | ||
| 794 | + const uploadResultUrls = await uploadImages({ | ||
| 795 | + filePaths: validFiles.map(file => file.url), | ||
| 796 | + ignoreError: true | ||
| 809 | }) | 797 | }) |
| 810 | -uploadResultUrls.forEach((url, index) => { | ||
| 811 | -// 用startIndex + index 替换对应条目 | ||
| 812 | -if (targetList[startIndex + index]) { | ||
| 813 | -targetList.splice(startIndex + index, 1, { | ||
| 814 | -url, status: 'success', message: '', name: validFiles[index].name || '' | 798 | + uploadResultUrls.forEach((url, index) => { |
| 799 | + // 用startIndex + index 替换对应条目 | ||
| 800 | + if (targetList[startIndex + index]) { | ||
| 801 | + targetList.splice(startIndex + index, 1, { | ||
| 802 | + url, status: 'success', message: '', name: validFiles[index].name || '' | ||
| 815 | }) | 803 | }) |
| 816 | } | 804 | } |
| 817 | }) | 805 | }) |
| 818 | -for (let i = uploadResultUrls.length; i < validFiles.length; i++) { | ||
| 819 | -if (targetList[startIndex + i]) { | ||
| 820 | -targetList.splice(startIndex + i, 1, { | ||
| 821 | -url: validFiles[i].url, status: 'failed', message: '上传失败', name: validFiles[i].name || '' | 806 | + for (let i = uploadResultUrls.length; i < validFiles.length; i++) { |
| 807 | + if (targetList[startIndex + i]) { | ||
| 808 | + targetList.splice(startIndex + i, 1, { | ||
| 809 | + url: validFiles[i].url, status: 'failed', message: '上传失败', name: validFiles[i].name || '' | ||
| 822 | }) | 810 | }) |
| 823 | } | 811 | } |
| 824 | } | 812 | } |
| 825 | -if (uploadResultUrls.length < validFiles.length) { | ||
| 826 | -uni.showToast({ | ||
| 827 | -title: `成功${uploadResultUrls.length}个,失败${validFiles.length - uploadResultUrls.length}个`, | ||
| 828 | -icon: 'none' | 813 | + if (uploadResultUrls.length < validFiles.length) { |
| 814 | + uni.showToast({ | ||
| 815 | + title: `成功${uploadResultUrls.length}个,失败${validFiles.length - uploadResultUrls.length}个`, | ||
| 816 | + icon: 'none' | ||
| 829 | }) | 817 | }) |
| 830 | } | 818 | } |
| 831 | } catch (err) { | 819 | } catch (err) { |
| 832 | -console.error(`【${code}】文件上传失败:`, err) | ||
| 833 | -for (let i = 0; i < validFiles.length; i++) { | ||
| 834 | -if (targetList[startIndex + i]) { | ||
| 835 | -targetList.splice(startIndex + i, 1, { | ||
| 836 | -url: validFiles[i].url, status: 'failed', message: '上传失败', name: validFiles[i].name || '' | 820 | + console.error(`【${code}】文件上传失败:`, err) |
| 821 | + for (let i = 0; i < validFiles.length; i++) { | ||
| 822 | + if (targetList[startIndex + i]) { | ||
| 823 | + targetList.splice(startIndex + i, 1, { | ||
| 824 | + url: validFiles[i].url, status: 'failed', message: '上传失败', name: validFiles[i].name || '' | ||
| 837 | }) | 825 | }) |
| 838 | } | 826 | } |
| 839 | } | 827 | } |
| 840 | -uni.showToast({ title: '上传失败,请重试', icon: 'none' }) | 828 | + uni.showToast({ title: '上传失败,请重试', icon: 'none' }) |
| 841 | } | 829 | } |
| 842 | -this.syncFileValue(item) | ||
| 843 | -this.$refs.inspectFormRef.validateField(code) | ||
| 844 | -}, | 830 | + this.syncFileValue(item) |
| 831 | + this.$refs.inspectFormRef.validateField(code) | ||
| 832 | + }, | ||
| 845 | 833 | ||
| 846 | /** | 834 | /** |
| 847 | * 删除 7图片/8附件 的某一个已选文件 | 835 | * 删除 7图片/8附件 的某一个已选文件 |
| @@ -893,12 +881,12 @@ this.$refs.inspectFormRef.validateField(code) | @@ -893,12 +881,12 @@ this.$refs.inspectFormRef.validateField(code) | ||
| 893 | fields.push(row) | 881 | fields.push(row) |
| 894 | } else if (type === 3) { | 882 | } else if (type === 3) { |
| 895 | // 3枚举:单选取裸 value;历史「枚举+是否多选」存逗号分隔 | 883 | // 3枚举:单选取裸 value;历史「枚举+是否多选」存逗号分隔 |
| 896 | - fields.push({ | ||
| 897 | - fieldCode: code, | ||
| 898 | - valueText: item.multiple === 1 | ||
| 899 | - ? (Array.isArray(value) ? value.join(',') : String(value || '')) | ||
| 900 | - : String(value) | ||
| 901 | -}) | 884 | + fields.push({ |
| 885 | + fieldCode: code, | ||
| 886 | + valueText: item.multiple === 1 | ||
| 887 | + ? (Array.isArray(value) ? value.join(',') : String(value || '')) | ||
| 888 | + : String(value) | ||
| 889 | + }) | ||
| 902 | } else if (type === 6) { | 890 | } else if (type === 6) { |
| 903 | const selected = (item.selectOptions || []).filter(opt => (value || []).indexOf(opt.value) > -1) | 891 | const selected = (item.selectOptions || []).filter(opt => (value || []).indexOf(opt.value) > -1) |
| 904 | if (this.needQty(item)) { | 892 | if (this.needQty(item)) { |
| @@ -934,6 +922,70 @@ this.$refs.inspectFormRef.validateField(code) | @@ -934,6 +922,70 @@ this.$refs.inspectFormRef.validateField(code) | ||
| 934 | } | 922 | } |
| 935 | return fields | 923 | return fields |
| 936 | }, | 924 | }, |
| 925 | + | ||
| 926 | + async submitAndFinish() { | ||
| 927 | + | ||
| 928 | + try { | ||
| 929 | + await this.$refs.inspectFormRef.validate() | ||
| 930 | + const dynamicValues = this.buildDynamicFields() | ||
| 931 | + if (!this.clientSeq) { | ||
| 932 | + this.clientSeq = this.genClientSeq() | ||
| 933 | + } | ||
| 934 | + uni.showLoading({ title: '提交中...' }) | ||
| 935 | + console.log('dsahdjashj') | ||
| 936 | + // this.submitting = true | ||
| 937 | + | ||
| 938 | + // if (!this.commitId) { | ||
| 939 | + // const submitData = { | ||
| 940 | + // planNo: this.paramsOptins.planNo, | ||
| 941 | + // imgHost: this.getImgUrlList(this.imagesList), | ||
| 942 | + // commonUserList: [], | ||
| 943 | + // remark: this.inspectForm.remark.trim(), | ||
| 944 | + // } | ||
| 945 | + // const commitRes: any = await maintainCreate(submitData) | ||
| 946 | + // this.commitId = commitRes && typeof commitRes === 'object' ? commitRes.id : commitRes | ||
| 947 | + // } | ||
| 948 | + | ||
| 949 | + // if (dynamicValues.length > 0 && this.commitId) { | ||
| 950 | + // await workRecordSubmit({ | ||
| 951 | + // businessType: this.paramsOptins.businessType, | ||
| 952 | + // bizType: 3, | ||
| 953 | + // bizId: this.commitId, | ||
| 954 | + // workTypeId: Number(this.paramsOptins.planTypeId), | ||
| 955 | + // clientSeq: this.clientSeq, | ||
| 956 | + // remark: this.inspectForm.remark.trim(), | ||
| 957 | + // fields: dynamicValues | ||
| 958 | + // }) | ||
| 959 | + // } | ||
| 960 | + | ||
| 961 | + // 新增:调用【提交并结束】接口 | ||
| 962 | + await maintainCreateFinish({ | ||
| 963 | + submitData | ||
| 964 | + }) | ||
| 965 | + | ||
| 966 | + uni.hideLoading() | ||
| 967 | + uni.showToast({ title: '提交并结束任务成功', icon: 'success', duration: 1000 }) | ||
| 968 | + setTimeout(() => { | ||
| 969 | + uni.reLaunch({ url: '/pages-sub/daily/maintain-manage/index' }) | ||
| 970 | + }, 1000) | ||
| 971 | + } catch (error) { | ||
| 972 | + uni.hideLoading() | ||
| 973 | + // if (Array.isArray(error)) return | ||
| 974 | + // console.error('提交并结束失败', error) | ||
| 975 | + // const backendMsg = error && error.msg | ||
| 976 | + // if (this.commitId) { | ||
| 977 | + // uni.showToast({ | ||
| 978 | + // title: backendMsg ? `养护记录已提交:${backendMsg}` : '养护记录已提交,结束任务接口失败', | ||
| 979 | + // icon: 'none', duration: 2500 | ||
| 980 | + // }) | ||
| 981 | + // } else if (!backendMsg) { | ||
| 982 | + // uni.showToast({ title: '提交失败,请重试', icon: 'none', duration: 2000 }) | ||
| 983 | + // } | ||
| 984 | + } finally { | ||
| 985 | + // this.submitting = false | ||
| 986 | + } | ||
| 987 | + }, | ||
| 988 | + | ||
| 937 | /** | 989 | /** |
| 938 | * 提交养护记录 —— 双写两条链路: | 990 | * 提交养护记录 —— 双写两条链路: |
| 939 | * ① maintain-plan-commit/create:养护计划本身(描述 + 图片 + 人员),返回 commit.id | 991 | * ① maintain-plan-commit/create:养护计划本身(描述 + 图片 + 人员),返回 commit.id |
| @@ -942,11 +994,12 @@ this.$refs.inspectFormRef.validateField(code) | @@ -942,11 +994,12 @@ this.$refs.inspectFormRef.validateField(code) | ||
| 942 | */ | 994 | */ |
| 943 | async submitInspect() { | 995 | async submitInspect() { |
| 944 | // 调试打印,打开小程序控制台查看 | 996 | // 调试打印,打开小程序控制台查看 |
| 945 | - console.log('=====调试=====') | ||
| 946 | - console.log('dynamicFields', this.dynamicFields) | ||
| 947 | - this.dynamicFields.forEach(item=>{ | ||
| 948 | - console.log(item.fieldCode, this.inspectForm[item.fieldCode], typeof this.inspectForm[item.fieldCode]) | ||
| 949 | - }) | 997 | + console.log('=====调试=====') |
| 998 | + console.log('dynamicFields', this.dynamicFields) | ||
| 999 | + if (this.submitting) return | ||
| 1000 | + this.dynamicFields.forEach(item => { | ||
| 1001 | + console.log(item.fieldCode, this.inspectForm[item.fieldCode], typeof this.inspectForm[item.fieldCode]) | ||
| 1002 | + }) | ||
| 950 | try { | 1003 | try { |
| 951 | await this.$refs.inspectFormRef.validate() | 1004 | await this.$refs.inspectFormRef.validate() |
| 952 | const dynamicValues = this.buildDynamicFields() | 1005 | const dynamicValues = this.buildDynamicFields() |
| @@ -954,6 +1007,7 @@ this.$refs.inspectFormRef.validateField(code) | @@ -954,6 +1007,7 @@ this.$refs.inspectFormRef.validateField(code) | ||
| 954 | this.clientSeq = this.genClientSeq() | 1007 | this.clientSeq = this.genClientSeq() |
| 955 | } | 1008 | } |
| 956 | uni.showLoading({ title: '提交中...' }) | 1009 | uni.showLoading({ title: '提交中...' }) |
| 1010 | + this.submitting = true | ||
| 957 | // ① 养护计划本体:仅首次提交时写入 | 1011 | // ① 养护计划本体:仅首次提交时写入 |
| 958 | if (!this.commitId) { | 1012 | if (!this.commitId) { |
| 959 | const submitData = { | 1013 | const submitData = { |
| @@ -1017,11 +1071,13 @@ this.$refs.inspectFormRef.validateField(code) | @@ -1017,11 +1071,13 @@ this.$refs.inspectFormRef.validateField(code) | ||
| 1017 | .page-container { | 1071 | .page-container { |
| 1018 | padding-bottom: 120rpx; | 1072 | padding-bottom: 120rpx; |
| 1019 | } | 1073 | } |
| 1074 | + | ||
| 1020 | .inspect-form-content { | 1075 | .inspect-form-content { |
| 1021 | background: #fff; | 1076 | background: #fff; |
| 1022 | padding: 20rpx; | 1077 | padding: 20rpx; |
| 1023 | margin-bottom: 20rpx; | 1078 | margin-bottom: 20rpx; |
| 1024 | } | 1079 | } |
| 1080 | + | ||
| 1025 | .date-input-wrap { | 1081 | .date-input-wrap { |
| 1026 | width: 100%; | 1082 | width: 100%; |
| 1027 | } | 1083 | } |
| @@ -1033,35 +1089,42 @@ this.$refs.inspectFormRef.validateField(code) | @@ -1033,35 +1089,42 @@ this.$refs.inspectFormRef.validateField(code) | ||
| 1033 | height: 72rpx; | 1089 | height: 72rpx; |
| 1034 | font-size: 32rpx; | 1090 | font-size: 32rpx; |
| 1035 | } | 1091 | } |
| 1092 | + | ||
| 1036 | :deep(.u-number-box__btn) { | 1093 | :deep(.u-number-box__btn) { |
| 1037 | width: 64rpx; | 1094 | width: 64rpx; |
| 1038 | height: 72rpx; | 1095 | height: 72rpx; |
| 1039 | } | 1096 | } |
| 1040 | } | 1097 | } |
| 1098 | + | ||
| 1041 | .number-box-wrap { | 1099 | .number-box-wrap { |
| 1042 | display: flex; | 1100 | display: flex; |
| 1043 | align-items: center; | 1101 | align-items: center; |
| 1044 | gap: 16rpx; | 1102 | gap: 16rpx; |
| 1103 | + | ||
| 1045 | .unit-text { | 1104 | .unit-text { |
| 1046 | font-size: 28rpx; | 1105 | font-size: 28rpx; |
| 1047 | color: #333; | 1106 | color: #333; |
| 1048 | } | 1107 | } |
| 1049 | } | 1108 | } |
| 1109 | + | ||
| 1050 | // 6多选:复选框组 + needQuantity 时每个已选项一行数量输入 | 1110 | // 6多选:复选框组 + needQuantity 时每个已选项一行数量输入 |
| 1051 | .multi-wrap { | 1111 | .multi-wrap { |
| 1052 | width: 100%; | 1112 | width: 100%; |
| 1113 | + | ||
| 1053 | .multi-hint { | 1114 | .multi-hint { |
| 1054 | display: block; | 1115 | display: block; |
| 1055 | margin-top: 8rpx; | 1116 | margin-top: 8rpx; |
| 1056 | font-size: 24rpx; | 1117 | font-size: 24rpx; |
| 1057 | color: #909399; | 1118 | color: #909399; |
| 1058 | } | 1119 | } |
| 1120 | + | ||
| 1059 | .qty-list { | 1121 | .qty-list { |
| 1060 | margin-top: 16rpx; | 1122 | margin-top: 16rpx; |
| 1061 | padding-left: 20rpx; | 1123 | padding-left: 20rpx; |
| 1062 | border-left: 4rpx solid #f0f0f0; | 1124 | border-left: 4rpx solid #f0f0f0; |
| 1063 | } | 1125 | } |
| 1064 | } | 1126 | } |
| 1127 | + | ||
| 1065 | // 数量行(6多选的每个已选项 / 3物料的领用数量共用) | 1128 | // 数量行(6多选的每个已选项 / 3物料的领用数量共用) |
| 1066 | .qty-row { | 1129 | .qty-row { |
| 1067 | display: flex; | 1130 | display: flex; |
| @@ -1069,6 +1132,7 @@ this.$refs.inspectFormRef.validateField(code) | @@ -1069,6 +1132,7 @@ this.$refs.inspectFormRef.validateField(code) | ||
| 1069 | justify-content: space-between; | 1132 | justify-content: space-between; |
| 1070 | gap: 16rpx; | 1133 | gap: 16rpx; |
| 1071 | padding: 8rpx 0; | 1134 | padding: 8rpx 0; |
| 1135 | + | ||
| 1072 | .qty-label { | 1136 | .qty-label { |
| 1073 | flex: 1; | 1137 | flex: 1; |
| 1074 | min-width: 0; | 1138 | min-width: 0; |
| @@ -1076,20 +1140,24 @@ this.$refs.inspectFormRef.validateField(code) | @@ -1076,20 +1140,24 @@ this.$refs.inspectFormRef.validateField(code) | ||
| 1076 | color: #333; | 1140 | color: #333; |
| 1077 | } | 1141 | } |
| 1078 | } | 1142 | } |
| 1143 | + | ||
| 1079 | .qty-row-material { | 1144 | .qty-row-material { |
| 1080 | margin-top: 12rpx; | 1145 | margin-top: 12rpx; |
| 1081 | padding-top: 12rpx; | 1146 | padding-top: 12rpx; |
| 1082 | border-top: 1rpx solid #f0f0f0; | 1147 | border-top: 1rpx solid #f0f0f0; |
| 1083 | } | 1148 | } |
| 1149 | + | ||
| 1084 | // 8附件:自绘文件列表 + 选择按钮(微信无通用文件选择器,走 chooseMessageFile) | 1150 | // 8附件:自绘文件列表 + 选择按钮(微信无通用文件选择器,走 chooseMessageFile) |
| 1085 | .file-wrap { | 1151 | .file-wrap { |
| 1086 | width: 100%; | 1152 | width: 100%; |
| 1153 | + | ||
| 1087 | .file-row { | 1154 | .file-row { |
| 1088 | display: flex; | 1155 | display: flex; |
| 1089 | align-items: center; | 1156 | align-items: center; |
| 1090 | justify-content: space-between; | 1157 | justify-content: space-between; |
| 1091 | gap: 16rpx; | 1158 | gap: 16rpx; |
| 1092 | padding: 8rpx 0; | 1159 | padding: 8rpx 0; |
| 1160 | + | ||
| 1093 | .file-name { | 1161 | .file-name { |
| 1094 | flex: 1; | 1162 | flex: 1; |
| 1095 | min-width: 0; | 1163 | min-width: 0; |
| @@ -1099,6 +1167,7 @@ this.$refs.inspectFormRef.validateField(code) | @@ -1099,6 +1167,7 @@ this.$refs.inspectFormRef.validateField(code) | ||
| 1099 | white-space: nowrap; | 1167 | white-space: nowrap; |
| 1100 | text-overflow: ellipsis; | 1168 | text-overflow: ellipsis; |
| 1101 | } | 1169 | } |
| 1170 | + | ||
| 1102 | .file-del { | 1171 | .file-del { |
| 1103 | font-size: 26rpx; | 1172 | font-size: 26rpx; |
| 1104 | color: #ff4d4f; | 1173 | color: #ff4d4f; |
| @@ -1113,5 +1182,38 @@ this.$refs.inspectFormRef.validateField(code) | @@ -1113,5 +1182,38 @@ this.$refs.inspectFormRef.validateField(code) | ||
| 1113 | color: #999; | 1182 | color: #999; |
| 1114 | } | 1183 | } |
| 1115 | 1184 | ||
| 1185 | +.fixed-bottom-btn-wrap { | ||
| 1186 | + background-color: #ffffff; | ||
| 1187 | + display: flex; | ||
| 1188 | + .btn-item { | ||
| 1189 | + flex: 1; | ||
| 1190 | + height: 88rpx; | ||
| 1191 | + display: flex; | ||
| 1192 | + align-items: center; | ||
| 1193 | + justify-content: center; | ||
| 1194 | + font-size: 32rpx; | ||
| 1195 | + color: #fff; | ||
| 1196 | + } | ||
| 1197 | + | ||
| 1198 | + .btn-left { | ||
| 1199 | + background: #1989fa; | ||
| 1200 | + } | ||
| 1201 | + | ||
| 1202 | + .btn-right { | ||
| 1203 | + background: #8b572a; | ||
| 1204 | + /* 褐色 */ | ||
| 1205 | + } | ||
| 1206 | +} | ||
| 1207 | + | ||
| 1208 | +// 页面底部预留空白,防止表单内容被固定按钮遮挡 | ||
| 1209 | +.page-container { | ||
| 1210 | + padding-bottom: calc(120rpx + env(safe-area-inset-bottom)); | ||
| 1211 | +} | ||
| 1116 | 1212 | ||
| 1213 | +.empty-field-tip { | ||
| 1214 | + text-align: center; | ||
| 1215 | + padding: 80rpx 20rpx; | ||
| 1216 | + font-size: 30rpx; | ||
| 1217 | + color: #999; | ||
| 1218 | +} | ||
| 1117 | </style> | 1219 | </style> |
pages-sub/daily/maintain-manage/finish-plan-detail.vue
| @@ -154,7 +154,7 @@ | @@ -154,7 +154,7 @@ | ||
| 154 | 154 | ||
| 155 | <script setup lang="ts"> | 155 | <script setup lang="ts"> |
| 156 | import {ref} from 'vue'; | 156 | import {ref} from 'vue'; |
| 157 | -import {detailList} from "@/api/maintain-manage/maintain-manage"; | 157 | +import {detailList,getDetailList} from "@/api/maintain-manage/maintain-manage"; |
| 158 | import {onLoad} from '@dcloudio/uni-app'; | 158 | import {onLoad} from '@dcloudio/uni-app'; |
| 159 | import {timeFormat} from '@/uni_modules/uview-plus'; | 159 | import {timeFormat} from '@/uni_modules/uview-plus'; |
| 160 | 160 | ||
| @@ -199,15 +199,15 @@ const previewImage = (currentUrl, allUrls) => { | @@ -199,15 +199,15 @@ const previewImage = (currentUrl, allUrls) => { | ||
| 199 | /** | 199 | /** |
| 200 | * 获取工单详情 | 200 | * 获取工单详情 |
| 201 | */ | 201 | */ |
| 202 | -const getOrderDetail = async (planNo: string) => { | 202 | +const getOrderDetail = async (id: string) => { |
| 203 | try { | 203 | try { |
| 204 | loading.value = true; | 204 | loading.value = true; |
| 205 | let queryData = { | 205 | let queryData = { |
| 206 | - planNo: planNo, | ||
| 207 | - pageSize: 100, | ||
| 208 | - pageNo: 1, | 206 | + id: id, |
| 207 | + // pageSize: 100, | ||
| 208 | + // pageNo: 1, | ||
| 209 | }; | 209 | }; |
| 210 | - const res = await detailList(queryData); | 210 | + const res = await getDetailList(queryData); |
| 211 | console.log('接口返回:', res); | 211 | console.log('接口返回:', res); |
| 212 | 212 | ||
| 213 | // 修复2:正确解析接口数据(res.data.list 而非 res.list) | 213 | // 修复2:正确解析接口数据(res.data.list 而非 res.list) |
| @@ -527,9 +527,9 @@ const openGeo = (geo) => { | @@ -527,9 +527,9 @@ const openGeo = (geo) => { | ||
| 527 | 527 | ||
| 528 | // 页面加载 | 528 | // 页面加载 |
| 529 | onLoad((options) => { | 529 | onLoad((options) => { |
| 530 | - const {planNo} = options; | ||
| 531 | - if (planNo) { | ||
| 532 | - getOrderDetail(planNo); | 530 | + const {id} = options; |
| 531 | + if (id) { | ||
| 532 | + getOrderDetail(id); | ||
| 533 | } else { | 533 | } else { |
| 534 | loading.value = false; | 534 | loading.value = false; |
| 535 | uni.showToast({title: '缺少工单ID', icon: 'none'}); | 535 | uni.showToast({title: '缺少工单ID', icon: 'none'}); |
pages-sub/daily/maintain-manage/index.vue
| @@ -185,7 +185,7 @@ | @@ -185,7 +185,7 @@ | ||
| 185 | <script setup> | 185 | <script setup> |
| 186 | import { ref, computed } from 'vue' | 186 | import { ref, computed } from 'vue' |
| 187 | import { onLoad, onShow } from '@dcloudio/uni-app' | 187 | import { onLoad, onShow } from '@dcloudio/uni-app' |
| 188 | -import { roadPage, planTypePage } from "@/api/maintain-manage/maintain-manage"; | 188 | +import { maintainPlan } from "@/api/maintain-manage/maintain-manage"; |
| 189 | import { timeFormat } from '@/uni_modules/uview-plus'; | 189 | import { timeFormat } from '@/uni_modules/uview-plus'; |
| 190 | 190 | ||
| 191 | // 初始化响应式数据 | 191 | // 初始化响应式数据 |
| @@ -274,26 +274,14 @@ const handleSearch = () => { | @@ -274,26 +274,14 @@ const handleSearch = () => { | ||
| 274 | const fetchData = async (pageNo, pageSize) => { | 274 | const fetchData = async (pageNo, pageSize) => { |
| 275 | try { | 275 | try { |
| 276 | let res = null | 276 | let res = null |
| 277 | - // 道路类型请求 | ||
| 278 | - if (activeType.value === '道路') { | ||
| 279 | - res = await roadPage({ | 277 | + const currentType = typeTabs.value.find(item => item.name === activeType.value) |
| 278 | + res = await maintainPlan({ | ||
| 279 | + // planTypeId: currentType?.planTypeId || '', | ||
| 280 | roadName: searchValue.value.trim(), | 280 | roadName: searchValue.value.trim(), |
| 281 | pageNo, | 281 | pageNo, |
| 282 | pageSize, | 282 | pageSize, |
| 283 | - finishState: activeStatus.value | 283 | + // finishState: activeStatus.value |
| 284 | }) | 284 | }) |
| 285 | - } else { | ||
| 286 | - // 其他养护类型请求 | ||
| 287 | - // 找到当前类型对应的planTypeId | ||
| 288 | - const currentType = typeTabs.value.find(item => item.name === activeType.value) | ||
| 289 | - res = await planTypePage({ | ||
| 290 | - planTypeId: currentType?.planTypeId || '', | ||
| 291 | - planName: searchValue.value.trim(), | ||
| 292 | - pageNo, | ||
| 293 | - pageSize, | ||
| 294 | - finishState: activeStatus.value | ||
| 295 | - }) | ||
| 296 | - } | ||
| 297 | 285 | ||
| 298 | // 告诉z-paging数据加载完成 | 286 | // 告诉z-paging数据加载完成 |
| 299 | // res.list为当前页数据,res.total为总条数(如果接口返回) | 287 | // res.list为当前页数据,res.total为总条数(如果接口返回) |
| @@ -313,8 +301,9 @@ const goToDetail = (item) => { | @@ -313,8 +301,9 @@ const goToDetail = (item) => { | ||
| 313 | // }) | 301 | // }) |
| 314 | 302 | ||
| 315 | uni.navigateTo({ | 303 | uni.navigateTo({ |
| 316 | - url: `/pages-sub/daily/maintain-manage/type-list?roadId=${item.roadId}&finish_state=${item.finishState || ''} &businessType=${item.busiLine}` | ||
| 317 | - }) | 304 | + url: `/pages-sub/daily/maintain-manage/type-list?roadName=${encodeURIComponent(item.roadName)}&levelId=${item.levelId}` |
| 305 | +}) | ||
| 306 | + | ||
| 318 | } | 307 | } |
| 319 | 308 | ||
| 320 | // 提交记录 | 309 | // 提交记录 |
pages-sub/daily/maintain-manage/now-list-page.vue
| 1 | <template> | 1 | <template> |
| 2 | - <view class="page-wrap"> | ||
| 3 | - <!-- 顶部提示栏 --> | ||
| 4 | - <view class="tip-bar"> | ||
| 5 | - 一个任务可提交多次上报记录,每一次作业需如实上报。 | ||
| 6 | - </view> | ||
| 7 | - | ||
| 8 | - <!-- 上报记录列表 --> | ||
| 9 | - <view class="list-wrap" v-if="reportList.length > 0"> | ||
| 10 | - <view class="report-item" v-for="(item, index) in reportList" :key="index"> | ||
| 11 | - <view class="item-title">【第{{ item.no }}次上报】{{ item.taskName }}</view> | ||
| 12 | - | ||
| 13 | - <view class="item-row item-row-bottom"> | ||
| 14 | - <view class="item-time">提交时间:{{ item.submitTime }}</view> | ||
| 15 | - <view class="look-btn" @click="handleView(item)">查看</view> | 2 | + <view class="page-wrap"> |
| 3 | + <!-- 顶部提示栏 --> | ||
| 4 | + <view class="tip-bar"> | ||
| 5 | + 一个任务可提交多次上报记录,每一次作业需如实上报。 | ||
| 6 | + </view> | ||
| 7 | + <!-- 上报记录列表 --> | ||
| 8 | + <view class="list-wrap" v-if="reportList.length > 0"> | ||
| 9 | + <view class="report-item" v-for="(item, index) in reportList" :key="index"> | ||
| 10 | + <view class="item-title">【第{{ index+1 }}次上报】{{ item.planName }}{{ item.planName }}</view> | ||
| 11 | + <view class="item-row item-row-bottom"> | ||
| 12 | + <view class="item-time">提交时间:{{ item.submitTime }}</view> | ||
| 13 | + <view class="look-btn" @click="handleView(item)">查看</view> | ||
| 14 | + </view> | ||
| 15 | + </view> | ||
| 16 | + </view> | ||
| 17 | + <!-- 暂无数据 --> | ||
| 18 | + <view class="empty-wrap" v-else> | ||
| 19 | + <empty-view text="暂无数据"></empty-view> | ||
| 20 | + </view> | ||
| 21 | + <!-- 底部固定按钮 --> | ||
| 22 | + <view class="fixed-bottom-btn-wrap"> | ||
| 23 | + <up-button type="primary" size="large" block @click="handleAddReport">上报本次记录</up-button> | ||
| 16 | </view> | 24 | </view> |
| 17 | - </view> | ||
| 18 | - </view> | ||
| 19 | - | ||
| 20 | - <!-- 暂无数据 --> | ||
| 21 | - <view class="empty-wrap" v-else> | ||
| 22 | - <empty-view text="暂无数据"></empty-view> | ||
| 23 | - </view> | ||
| 24 | - | ||
| 25 | - <!-- 底部固定按钮 --> | ||
| 26 | - <view class="fixed-bottom-btn-wrap"> | ||
| 27 | - <up-button type="primary" size="large" block @click="handleAddReport">上报本次记录</up-button> | ||
| 28 | </view> | 25 | </view> |
| 29 | - </view> | ||
| 30 | </template> | 26 | </template> |
| 31 | - | ||
| 32 | <script setup lang="ts"> | 27 | <script setup lang="ts"> |
| 33 | import { ref } from 'vue' | 28 | import { ref } from 'vue' |
| 29 | +import { onShow, onLoad } from '@dcloudio/uni-app' | ||
| 30 | +import { maintainPlanList } from "@/api/maintain-manage/maintain-manage"; | ||
| 34 | 31 | ||
| 35 | // 上报记录数据 | 32 | // 上报记录数据 |
| 36 | -const reportList = ref([ | ||
| 37 | - { | ||
| 38 | - no:3, | ||
| 39 | - taskName:'德胜门东大街月季补植', | ||
| 40 | - varityName:'果汁阳台月季', | ||
| 41 | - submitTime:'2026-06-22 14:34:33' | ||
| 42 | - }, | ||
| 43 | - { | ||
| 44 | - no:2, | ||
| 45 | - taskName:'德胜门东大街月季补植', | ||
| 46 | - varityName:'蓝色风暴月季', | ||
| 47 | - submitTime:'2026-06-21 10:34:33' | ||
| 48 | - }, | ||
| 49 | - { | ||
| 50 | - no:1, | ||
| 51 | - taskName:'德胜门东大街月季补植', | ||
| 52 | - varityName:'金丝雀月季', | ||
| 53 | - submitTime:'2026-06-20 10:34:33' | ||
| 54 | - } | ||
| 55 | -]) | 33 | +const reportList = ref<any[]>([]) |
| 34 | +let planNo = ref('') | ||
| 35 | +let planTypeId = ref('') | ||
| 36 | +let businessType = ref('') | ||
| 37 | +// onLoad接收url传过来的planNo | ||
| 38 | +onLoad((options) => { | ||
| 39 | + if (options.planNo) { | ||
| 40 | + planNo.value = options.planNo | ||
| 41 | + planTypeId.value = options.planTypeId | ||
| 42 | + businessType.value = options.businessType | ||
| 43 | + // planTypeId=${item.planTypeId}&businessType=${item.busiLine} | ||
| 44 | + getReportList() | ||
| 45 | + } else { | ||
| 46 | + uni.showToast({ title: 'planNo参数缺失', icon: 'none' }) | ||
| 47 | + } | ||
| 48 | +}) | ||
| 49 | + | ||
| 50 | +// 请求上报记录接口 | ||
| 51 | +const getReportList = async () => { | ||
| 52 | + console.log('3123') | ||
| 53 | + uni.showLoading({ title: '加载中...', mask: true }) | ||
| 54 | + try { | ||
| 55 | + // 传入planNo调取接口 | ||
| 56 | + let data = { | ||
| 57 | + planNo: planNo.value, | ||
| 58 | + pageNo: 1, | ||
| 59 | + pageSize: 10 | ||
| 60 | + } | ||
| 61 | + const res = await maintainPlanList(data) | ||
| 62 | + // 接口返回list,无数据则reportList为空,页面自动显示empty | ||
| 63 | + reportList.value = res?.list || [] | ||
| 64 | + } catch (err) { | ||
| 65 | + console.error('获取上报记录失败', err) | ||
| 66 | + uni.showToast({ title: '加载失败', icon: 'none' }) | ||
| 67 | + } finally { | ||
| 68 | + uni.hideLoading() | ||
| 69 | + } | ||
| 70 | +} | ||
| 56 | 71 | ||
| 57 | // 查看上报详情 | 72 | // 查看上报详情 |
| 58 | const handleView = (item) => { | 73 | const handleView = (item) => { |
| 59 | - console.log('查看详情', item) | ||
| 60 | - uni.navigateTo({ | ||
| 61 | - url: `/pages-sub/daily/maintain-manage/finish-plan-detail` | ||
| 62 | - }) | 74 | + console.log('查看详情', item) |
| 75 | + uni.navigateTo({ | ||
| 76 | + url: `/pages-sub/daily/maintain-manage/finish-plan-detail?id=${item.commitId}` | ||
| 77 | + }) | ||
| 63 | } | 78 | } |
| 64 | - | ||
| 65 | -// 新增上报记录 | 79 | +// 新增上报记录,把planNo带到新增页面 |
| 66 | const handleAddReport = () => { | 80 | const handleAddReport = () => { |
| 67 | - console.log('上报本次记录') | ||
| 68 | - uni.navigateTo({ | ||
| 69 | - url: `/pages-sub/daily/maintain-manage/add-record` | ||
| 70 | - }) | 81 | + console.log('上报本次记录') |
| 82 | + uni.navigateTo({ | ||
| 83 | + url: `/pages-sub/daily/maintain-manage/add-record?planNo=${planNo.value}&planTypeId=${planTypeId.value}&businessType=${businessType.value}` | ||
| 84 | + }) | ||
| 71 | } | 85 | } |
| 72 | </script> | 86 | </script> |
| 73 | - | ||
| 74 | <style scoped lang="scss"> | 87 | <style scoped lang="scss"> |
| 75 | .page-wrap { | 88 | .page-wrap { |
| 76 | - min-height: 100vh; | ||
| 77 | - padding-bottom: 120rpx; // 给底部按钮预留空间 | ||
| 78 | - background: #ffffff; | 89 | + min-height: 100vh; |
| 90 | + padding-bottom: 120rpx; // 给底部按钮预留空间 | ||
| 91 | + background: #ffffff; | ||
| 79 | } | 92 | } |
| 93 | + | ||
| 80 | // 顶部黄色提示条 | 94 | // 顶部黄色提示条 |
| 81 | .tip-bar { | 95 | .tip-bar { |
| 82 | - background-color: #f9e0a2; | ||
| 83 | - color: #6b4b00; | ||
| 84 | - font-size: 28rpx; | ||
| 85 | - padding:24rpx 20rpx; | ||
| 86 | - line-height:1.5; | 96 | + background-color: #f9e0a2; |
| 97 | + color: #6b4b00; | ||
| 98 | + font-size: 28rpx; | ||
| 99 | + padding: 24rpx 20rpx; | ||
| 100 | + line-height: 1.5; | ||
| 87 | } | 101 | } |
| 88 | 102 | ||
| 89 | .list-wrap { | 103 | .list-wrap { |
| 90 | - padding: 0; | 104 | + padding: 0; |
| 91 | } | 105 | } |
| 106 | + | ||
| 92 | .report-item { | 107 | .report-item { |
| 93 | - padding:30rpx 24rpx; | ||
| 94 | - border-bottom:1rpx solid #eee; | ||
| 95 | - .item-title { | ||
| 96 | - font-size:32rpx; | ||
| 97 | - color:#222; | ||
| 98 | - font-weight:500; | ||
| 99 | - margin-bottom:20rpx; | ||
| 100 | - } | ||
| 101 | - .item-row { | ||
| 102 | - font-size:28rpx; | ||
| 103 | - color:#555; | ||
| 104 | - margin-bottom:12rpx; | ||
| 105 | - } | ||
| 106 | - .item-row-bottom { | ||
| 107 | - display:flex; | ||
| 108 | - justify-content:space-between; | ||
| 109 | - align-items:center; | ||
| 110 | - margin-bottom:0; | ||
| 111 | - } | ||
| 112 | - .look-btn { | ||
| 113 | - font-size:28rpx; | ||
| 114 | - color:#1989fa; | ||
| 115 | - } | 108 | + padding: 30rpx 24rpx; |
| 109 | + border-bottom: 1rpx solid #eee; | ||
| 110 | + | ||
| 111 | + .item-title { | ||
| 112 | + font-size: 32rpx; | ||
| 113 | + color: #222; | ||
| 114 | + font-weight: 500; | ||
| 115 | + margin-bottom: 20rpx; | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + .item-row { | ||
| 119 | + font-size: 28rpx; | ||
| 120 | + color: #555; | ||
| 121 | + margin-bottom: 12rpx; | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + .item-row-bottom { | ||
| 125 | + display: flex; | ||
| 126 | + justify-content: space-between; | ||
| 127 | + align-items: center; | ||
| 128 | + margin-bottom: 0; | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + .look-btn { | ||
| 132 | + font-size: 28rpx; | ||
| 133 | + color: #1989fa; | ||
| 134 | + } | ||
| 116 | } | 135 | } |
| 136 | + | ||
| 117 | .empty-wrap { | 137 | .empty-wrap { |
| 118 | - padding-top: 120rpx; | 138 | + padding-top: 120rpx; |
| 119 | } | 139 | } |
| 120 | 140 | ||
| 141 | +.fixed-bottom-btn-wrap { | ||
| 142 | + // position: fixed; | ||
| 143 | + // bottom: 0; | ||
| 144 | + // left:0; | ||
| 145 | + // width:100%; | ||
| 146 | + // padding:20rpx; | ||
| 147 | + // background:#fff; | ||
| 148 | +} | ||
| 121 | </style> | 149 | </style> |
pages-sub/daily/maintain-manage/type-list-page.vue
| 1 | <template> | 1 | <template> |
| 2 | - <view class="page-container"> | ||
| 3 | - <up-sticky> | ||
| 4 | - <view class="sticky-wrap"> | ||
| 5 | - <up-tabs | ||
| 6 | - v-model="activeStatus" | ||
| 7 | - :list="statusTabs" | ||
| 8 | - line-width="40rpx" | ||
| 9 | - active-color="#1989fa" | ||
| 10 | - inactive-color="#666" | ||
| 11 | - :scrollable="false" | ||
| 12 | - @change="handleStatusChange" | ||
| 13 | - ></up-tabs> | ||
| 14 | - </view> | ||
| 15 | - </up-sticky> | ||
| 16 | - <z-paging | ||
| 17 | - ref="pagingRef" | ||
| 18 | - v-model="currentList" | ||
| 19 | - @query="fetchData" | ||
| 20 | - :auto-show-system-loading="true" | ||
| 21 | - > | ||
| 22 | - <template #empty> | ||
| 23 | - <empty-view/> | ||
| 24 | - </template> | ||
| 25 | - <view class="card-list"> | ||
| 26 | - <view | ||
| 27 | - class="task-card" | ||
| 28 | - v-for="(item,index) in currentList" | ||
| 29 | - :key="`${item.planNo}_${index}`" | ||
| 30 | - > | ||
| 31 | - <!-- 第一行:任务名称 + 置为失效 --> | ||
| 32 | - <view class="row row-1"> | ||
| 33 | - <view class="plan-name u-line-1">{{ item.planName || '无计划名称' }}</view> | ||
| 34 | - <view | ||
| 35 | - v-if="isMaintainLeader" | ||
| 36 | - class="btn-set-invalid" | ||
| 37 | - @click.stop="handleSetInvalid(item)" | ||
| 38 | - > | ||
| 39 | - 置为失效 | 2 | + <view class="page-container"> |
| 3 | + <up-sticky> | ||
| 4 | + <view class="sticky-wrap"> | ||
| 5 | + <up-tabs v-model="activeStatus" :list="statusTabs" line-width="40rpx" active-color="#1989fa" | ||
| 6 | + inactive-color="#666" :scrollable="false" @change="handleStatusChange"></up-tabs> | ||
| 40 | </view> | 7 | </view> |
| 41 | - </view> | ||
| 42 | - <!-- 第二行:任务次数 + 去作业按钮 --> | ||
| 43 | - <view class="row row-2"> | ||
| 44 | - <view class="info-text">任务次数:第{{ item.taskTimes || 1 }}次</view> | ||
| 45 | - <view class="mini-btn" @click.stop="handleGoWork(item)">去作业</view> | ||
| 46 | - </view> | ||
| 47 | - <!-- 第三行:有效期 + 结束任务按钮 --> | ||
| 48 | - <view class="row row-3"> | ||
| 49 | - <view class="info-text u-line-1">有效期:{{ item.beginTime || '-' }}—{{ item.endTime || '-' }}</view> | ||
| 50 | - <view class="mini-btn" @click.stop="handleEndTask(item)">结束任务</view> | ||
| 51 | - </view> | ||
| 52 | - </view> | ||
| 53 | - </view> | ||
| 54 | - </z-paging> | ||
| 55 | - | ||
| 56 | - <!-- ========== 置为失效弹窗(参考回退弹窗结构) ========== --> | ||
| 57 | - <up-modal | ||
| 58 | - :show="invalidModalShow" | ||
| 59 | - title="失效原因" | ||
| 60 | - :closeOnClickOverlay="false" | ||
| 61 | - :showConfirmButton="true" | ||
| 62 | - :showCancelButton="true" | ||
| 63 | - @cancel="handleInvalidModalCancel" | ||
| 64 | - @confirm="confirmSetInvalid" | ||
| 65 | - > | ||
| 66 | - <view class="invalid-modal-content"> | ||
| 67 | - <up-textarea | ||
| 68 | - v-model.trim="invalidReason" | ||
| 69 | - placeholder="请输入失效原因(必填)" | ||
| 70 | - rows="6" | ||
| 71 | - :count="200" | ||
| 72 | - maxlength="200" | ||
| 73 | - class="invalid-textarea" | ||
| 74 | - /> | ||
| 75 | - <view class="upload-wrap mt-20"> | ||
| 76 | - <view class="upload-title">上传图片(选填)</view> | ||
| 77 | - <up-upload | ||
| 78 | - :file-list="invalidImgs.rawImgList.value || []" | ||
| 79 | - @after-read="invalidImgs.uploadImgs" | ||
| 80 | - @delete="invalidImgs.deleteImg" | ||
| 81 | - multiple | ||
| 82 | - width="70" | ||
| 83 | - height="70" | ||
| 84 | - :max-count="invalidImgs.uploadConfig.maxCount" | ||
| 85 | - :upload-text="invalidImgs.uploadConfig.uploadText" | ||
| 86 | - :size-type="invalidImgs.uploadConfig.sizeType" | ||
| 87 | - /> | ||
| 88 | - </view> | ||
| 89 | - </view> | ||
| 90 | - </up-modal> | ||
| 91 | - </view> | 8 | + </up-sticky> |
| 9 | + <z-paging ref="pagingRef" v-model="currentList" @query="fetchData" :auto-show-system-loading="true"> | ||
| 10 | + <template #empty> | ||
| 11 | + <empty-view /> | ||
| 12 | + </template> | ||
| 13 | + <view class="card-list"> | ||
| 14 | + <view class="task-card" v-for="(item, index) in currentList" :key="`${item.planNo}_${index}`"> | ||
| 15 | + <!-- 第一行:任务名称 + 置为失效 --> | ||
| 16 | + <view class="row row-1"> | ||
| 17 | + <view class="plan-name u-line-1">{{ item.planName || '无计划名称' }}</view> | ||
| 18 | + <!-- 权限:包含team_leader_yl / team_leader_wy / team_leader_hjws 并且当前是待完成tab,才显示置为失效 --> | ||
| 19 | + <view v-if="hasInvalidPerm && activeStatus === '1'" class="btn-set-invalid" | ||
| 20 | + @click.stop="handleSetInvalid(item)"> | ||
| 21 | + 置为失效 | ||
| 22 | + </view> | ||
| 23 | + </view> | ||
| 24 | + <!-- 第二行:任务次数 + 去作业按钮 --> | ||
| 25 | + <view class="row row-2"> | ||
| 26 | + <view class="info-text">任务次数:第{{ item.plannum || 1 }}次</view> | ||
| 27 | + <!-- 仅待完成才显示去作业 --> | ||
| 28 | + <view v-if="activeStatus === '1'" class="mini-btn" @click.stop="handleGoWork(item)">去作业</view> | ||
| 29 | + </view> | ||
| 30 | + <!-- 第三行:有效期 + 结束任务按钮 --> | ||
| 31 | + <view class="row row-3"> | ||
| 32 | + <!-- timeFormat(start, 'yyyy-mm-dd'), --> | ||
| 33 | + <view class="info-text u-line-1">有效期:{{ timeFormat(item.beginTime, 'yyyy-mm-dd') }}—{{ timeFormat(item.endTime, 'yyyy-mm-dd') }} | ||
| 34 | + </view> | ||
| 35 | + <!-- 仅待完成才显示结束任务 --> | ||
| 36 | + <view v-if="activeStatus === '1'" class="mini-btn" @click.stop="handleEndTask(item)">结束任务</view> | ||
| 37 | + </view> | ||
| 38 | + </view> | ||
| 39 | + </view> | ||
| 40 | + </z-paging> | ||
| 41 | + <!-- ========== 置为失效弹窗(参考回退弹窗结构) ========== --> | ||
| 42 | + <up-modal :show="invalidModalShow" title="失效原因" :closeOnClickOverlay="false" :showConfirmButton="true" | ||
| 43 | + :showCancelButton="true" @cancel="handleInvalidModalCancel" @confirm="confirmSetInvalid"> | ||
| 44 | + <view class="invalid-modal-content"> | ||
| 45 | + <up-textarea v-model.trim="invalidReason" placeholder="请输入失效原因(必填)" rows="6" :count="200" | ||
| 46 | + maxlength="200" class="invalid-textarea" /> | ||
| 47 | + <view class="upload-wrap mt-20"> | ||
| 48 | + <view class="upload-title">上传图片(选填)</view> | ||
| 49 | + <up-upload :file-list="invalidImgs.rawImgList.value || []" @after-read="invalidImgs.uploadImgs" | ||
| 50 | + @delete="invalidImgs.deleteImg" multiple width="70" height="70" | ||
| 51 | + :max-count="invalidImgs.uploadConfig.maxCount" | ||
| 52 | + :upload-text="invalidImgs.uploadConfig.uploadText" | ||
| 53 | + :size-type="invalidImgs.uploadConfig.sizeType" /> | ||
| 54 | + </view> | ||
| 55 | + </view> | ||
| 56 | + </up-modal> | ||
| 57 | + </view> | ||
| 92 | </template> | 58 | </template> |
| 93 | - | ||
| 94 | <script setup lang="ts"> | 59 | <script setup lang="ts"> |
| 95 | import { ref, computed } from 'vue' | 60 | import { ref, computed } from 'vue' |
| 96 | -import { onShow } from '@dcloudio/uni-app' | 61 | +import { onShow, onUnload,onLoad } from '@dcloudio/uni-app' |
| 97 | import { useUserStore } from '@/pinia/user'; | 62 | import { useUserStore } from '@/pinia/user'; |
| 98 | -// 引入图片上传hook(和你回退弹窗同一个工具) | 63 | +import { maintainPlanNum, finishPlan, invalidatePlan } from "@/api/maintain-manage/maintain-manage"; |
| 64 | +import { timeFormat } from '@/uni_modules/uview-plus'; | ||
| 99 | import { useUploadImgs } from '@/common/utils/useUploadImgs' | 65 | import { useUploadImgs } from '@/common/utils/useUploadImgs' |
| 100 | - | ||
| 101 | const userStore = useUserStore(); | 66 | const userStore = useUserStore(); |
| 102 | const userRoles = computed<string[]>(() => userStore.userInfo?.roles || []); | 67 | const userRoles = computed<string[]>(() => userStore.userInfo?.roles || []); |
| 103 | -const isMaintainLeader = computed(() => userRoles.value.includes('yl_worker')) | 68 | + |
| 69 | +// 判断是否拥有置为失效权限:任意一个组长角色就可以 | ||
| 70 | +const hasInvalidPerm = computed(() => { | ||
| 71 | + const roles = userRoles.value | ||
| 72 | + const leaderRoles = ['team_leader_yl', 'team_leader_wy', 'team_leader_hjws'] | ||
| 73 | + return leaderRoles.some(key => roles.includes(key)) | ||
| 74 | +}) | ||
| 104 | 75 | ||
| 105 | const statusTabs = ref([ | 76 | const statusTabs = ref([ |
| 106 | - { name: '待完成', id: '1' }, | ||
| 107 | - { name: '已失效', id: '3' }, | ||
| 108 | - { name: '已完成', id: '2' } | 77 | + { name: '待完成', id: '1' }, |
| 78 | + { name: '已失效', id: '3' }, | ||
| 79 | + { name: '已完成', id: '2' } | ||
| 109 | ]) | 80 | ]) |
| 110 | const activeStatus = ref('1') | 81 | const activeStatus = ref('1') |
| 111 | const pagingRef = ref(null) | 82 | const pagingRef = ref(null) |
| 112 | const currentList = ref([]) | 83 | const currentList = ref([]) |
| 113 | - | 84 | +// 保存上一页传过来的原始数组 |
| 85 | +let originRawList: any[] = [] | ||
| 86 | +// 合并之后全部接口返回的总数据 | ||
| 87 | +const allTotalData = ref<any[]>([]) | ||
| 114 | // ===================== 失效弹窗状态 ===================== | 88 | // ===================== 失效弹窗状态 ===================== |
| 115 | const invalidModalShow = ref(false) | 89 | const invalidModalShow = ref(false) |
| 116 | const invalidReason = ref('') | 90 | const invalidReason = ref('') |
| 117 | const currentInvalidItem = ref(null) | 91 | const currentInvalidItem = ref(null) |
| 118 | // 图片上传实例,和回退保持一致,最多3张 | 92 | // 图片上传实例,和回退保持一致,最多3张 |
| 119 | const invalidImgs = useUploadImgs({ | 93 | const invalidImgs = useUploadImgs({ |
| 120 | - maxCount: 3, | ||
| 121 | - uploadText: '选择失效图片', | ||
| 122 | - sizeType: ['compressed'], | ||
| 123 | - formRef: null, | ||
| 124 | - fieldName: 'invalidImgs' | 94 | + maxCount: 3, |
| 95 | + uploadText: '选择失效图片', | ||
| 96 | + sizeType: ['compressed'], | ||
| 97 | + formRef: null, | ||
| 98 | + fieldName: 'invalidImgs' | ||
| 125 | }) | 99 | }) |
| 126 | - | ||
| 127 | -const mockAllData = [ | ||
| 128 | - { planNo: 'MH20260616001', planName: '德胜门东大街大街月季补植', taskTimes: 1, beginTime: '2026/06/16', endTime: '2026/12/31', finishState: '1' }, | ||
| 129 | - { planNo: 'MH20260616002', planName: '西长安街行道树修剪养护作业任务', taskTimes: 2, beginTime: '2026/05/01', endTime: '2026/11/30', finishState: '1' }, | ||
| 130 | - { planNo: 'MH20260616003', planName: '北三环绿地浇水、防寒养护专项任务超长名称测试文字溢出', taskTimes: 1, beginTime: '2026/04/10', endTime: '2026/12/20', finishState: '1' }, | ||
| 131 | - { planNo: 'MH20260616004', planName: '奥森园区绿地保洁日常养护', taskTimes: 3, beginTime: '2026/03/01', endTime: '2026/10/15', finishState: '3' }, | ||
| 132 | - { planNo: 'MH20260616005', planName: '护城河沿岸病虫害防治专项作业', taskTimes: 2, beginTime: '2026/02/15', endTime: '2026/09/30', finishState: '2' }, | ||
| 133 | - { planNo: 'MH20260616006', planName: '朝阳公园施肥、除草春季养护任务', taskTimes: 1, beginTime: '2026/04/05', endTime: '2026/11/10', finishState: '1' }, | ||
| 134 | - { planNo: 'MH20260616007', planName: '中关村大街灌木修剪与枯枝清理', taskTimes: 4, beginTime: '2026/01/20', endTime: '2026/12/25', finishState: '1' }, | ||
| 135 | - { planNo: 'MH20260616008', planName: '永定河滨河绿地冬季防寒浇水作业', taskTimes: 1, beginTime: '2026/07/01', endTime: '2026/12/31', finishState: '3' }, | ||
| 136 | - { planNo: 'MH20260616009', planName: '颐和园外围绿带病虫害防治', taskTimes: 2, beginTime: '2026/05/20', endTime: '2026/10/20', finishState: '2' }, | ||
| 137 | - { planNo: 'MH20260616010', planName: '上地街道行道树补植更换任务', taskTimes: 1, beginTime: '2026/06/01', endTime: '2026/12/20', finishState: '1' } | ||
| 138 | -] | ||
| 139 | - | 100 | +// 加载数据,根据当前activeStatus动态调用接口 |
| 101 | +const loadData = async () => { | ||
| 102 | + // originRawList = uni.getStorageSync('temp_maintain_rawList') | ||
| 103 | + console.log('123123') | ||
| 104 | + if (!originRawList || originRawList.length === 0) { | ||
| 105 | + return | ||
| 106 | + } | ||
| 107 | + uni.showLoading({ title: '加载数据...', mask: true }) | ||
| 108 | + try { | ||
| 109 | + const tempResult: any[] = [] | ||
| 110 | + // 获取需要请求的finishState数组 | ||
| 111 | + let stateArr: string[] = [] | ||
| 112 | + if (activeStatus.value === '1') { | ||
| 113 | + // 待完成,只查1 | ||
| 114 | + stateArr = ['1'] | ||
| 115 | + } else if (activeStatus.value === '3') { | ||
| 116 | + // 已失效,同时查3、4 | ||
| 117 | + stateArr = ['3', '4'] | ||
| 118 | + } else if (activeStatus.value === '2') { | ||
| 119 | + // 已完成,只查2 | ||
| 120 | + stateArr = ['2'] | ||
| 121 | + } | ||
| 122 | + // 遍历state数组,每个状态,循环rawList里面所有batchNo请求 | ||
| 123 | + for (const finishState of stateArr) { | ||
| 124 | + for (const item of originRawList) { | ||
| 125 | + const res = await maintainPlanNum({ | ||
| 126 | + pageNo: 1, | ||
| 127 | + pageSize: 10, | ||
| 128 | + planNo: item.planNo || '', | ||
| 129 | + planAttr: '', | ||
| 130 | + finishState: finishState, | ||
| 131 | + batchNo: item.batchNo | ||
| 132 | + }) | ||
| 133 | + if (Array.isArray(res.list)) { | ||
| 134 | + tempResult.push(...res.list) | ||
| 135 | + } | ||
| 136 | + } | ||
| 137 | + } | ||
| 138 | + allTotalData.value = tempResult | ||
| 139 | + } catch (err) { | ||
| 140 | + console.error('批量请求接口异常', err) | ||
| 141 | + uni.showToast({ title: '数据加载失败', icon: 'none' }) | ||
| 142 | + } finally { | ||
| 143 | + uni.hideLoading() | ||
| 144 | + pagingRef.value?.reload() | ||
| 145 | + } | ||
| 146 | +} | ||
| 147 | +const planTypeId = ref('') | ||
| 148 | +onLoad((options) => { | ||
| 149 | + planTypeId.value = options.planTypeId | ||
| 150 | + | ||
| 151 | +}); | ||
| 152 | +// onShow 只保留一处,读取storage,只第一次读取storage | ||
| 153 | +onShow(async () => { | ||
| 154 | + originRawList = uni.getStorageSync('temp_maintain_rawList') || [] | ||
| 155 | + // ✅ 读取完立刻删除,防止脏数据 | ||
| 156 | + // uni.removeStorageSync('temp_maintain_rawList') | ||
| 157 | + await loadData() | ||
| 158 | +}) | ||
| 159 | +// z-paging分页查询,直接使用allTotalData | ||
| 140 | const fetchData = async (pageNo, pageSize) => { | 160 | const fetchData = async (pageNo, pageSize) => { |
| 141 | - await new Promise(resolve => setTimeout(resolve, 400)) | ||
| 142 | - const filterList = mockAllData.filter(item => item.finishState === activeStatus.value) | ||
| 143 | - const start = (pageNo - 1) * pageSize | ||
| 144 | - const pageData = filterList.slice(start, start + pageSize) | ||
| 145 | - pagingRef.value?.complete(pageData, filterList.length) | 161 | + await new Promise(resolve => setTimeout(resolve, 300)) |
| 162 | + const filterList = allTotalData.value | ||
| 163 | + const start = (pageNo - 1) * pageSize | ||
| 164 | + const pageData = filterList.slice(start, start + pageSize) | ||
| 165 | + pagingRef.value?.complete(pageData, filterList.length) | ||
| 146 | } | 166 | } |
| 147 | - | ||
| 148 | -const handleStatusChange = (item) => { | ||
| 149 | - activeStatus.value = item.id | ||
| 150 | - pagingRef.value?.reload() | 167 | +const handleStatusChange = async (tabItem) => { |
| 168 | + activeStatus.value = tabItem.id | ||
| 169 | + // 切换tab,重新拉接口 | ||
| 170 | + await loadData() | ||
| 151 | } | 171 | } |
| 152 | - | ||
| 153 | // 打开失效弹窗 | 172 | // 打开失效弹窗 |
| 154 | const handleSetInvalid = (item) => { | 173 | const handleSetInvalid = (item) => { |
| 155 | - currentInvalidItem.value = item | ||
| 156 | - invalidReason.value = '' | ||
| 157 | - invalidImgs.clearImgs() | ||
| 158 | - invalidModalShow.value = true | 174 | + currentInvalidItem.value = item |
| 175 | + invalidReason.value = '' | ||
| 176 | + invalidImgs.clearImgs() | ||
| 177 | + invalidModalShow.value = true | ||
| 159 | } | 178 | } |
| 160 | - | ||
| 161 | // 关闭弹窗,重置数据 | 179 | // 关闭弹窗,重置数据 |
| 162 | const handleInvalidModalCancel = () => { | 180 | const handleInvalidModalCancel = () => { |
| 163 | - invalidModalShow.value = false | ||
| 164 | - invalidReason.value = '' | ||
| 165 | - invalidImgs.clearImgs() | 181 | + invalidModalShow.value = false |
| 182 | + invalidReason.value = '' | ||
| 183 | + invalidImgs.clearImgs() | ||
| 166 | } | 184 | } |
| 167 | 185 | ||
| 168 | // 确认提交置为失效 | 186 | // 确认提交置为失效 |
| @@ -173,23 +191,27 @@ const confirmSetInvalid = async () => { | @@ -173,23 +191,27 @@ const confirmSetInvalid = async () => { | ||
| 173 | return | 191 | return |
| 174 | } | 192 | } |
| 175 | const item = currentInvalidItem.value | 193 | const item = currentInvalidItem.value |
| 176 | - if (!item) return | 194 | + if (!item || !item.id) return |
| 177 | 195 | ||
| 178 | uni.showLoading({ title: '提交中...', mask: true }) | 196 | uni.showLoading({ title: '提交中...', mask: true }) |
| 179 | try { | 197 | try { |
| 180 | - // 这里后续替换真实接口,获取上传图片url: invalidImgs.getSuccessImgUrls() | ||
| 181 | - const imgList = invalidImgs.getSuccessImgUrls() | ||
| 182 | - console.log('置为失效提交参数:', { | ||
| 183 | - planNo: item.planNo, | ||
| 184 | - reason, | ||
| 185 | - imgList | ||
| 186 | - }) | 198 | + // 获取上传成功图片url数组,拼接逗号 |
| 199 | + const imgUrlArr = invalidImgs.getSuccessImgUrls() | ||
| 200 | + const invalidFiles = imgUrlArr.join(',') | ||
| 187 | 201 | ||
| 188 | - // ============ 此处替换后端接口调用 ============ | ||
| 189 | - // await api.setTaskInvalid({ ... }) | 202 | + const params = { |
| 203 | + id: item.id, | ||
| 204 | + invalidReason: reason, | ||
| 205 | + invalidFiles: invalidFiles | ||
| 206 | + } | ||
| 207 | + console.log('置为失效提交参数:', params) | ||
| 208 | + // 调用接口 invalidatePlan | ||
| 209 | + await invalidatePlan(params) | ||
| 190 | 210 | ||
| 191 | uni.showToast({ title: '操作成功', icon: 'success' }) | 211 | uni.showToast({ title: '操作成功', icon: 'success' }) |
| 192 | handleInvalidModalCancel() | 212 | handleInvalidModalCancel() |
| 213 | + // 重新加载数据刷新列表 | ||
| 214 | + await loadData() | ||
| 193 | pagingRef.value?.reload() | 215 | pagingRef.value?.reload() |
| 194 | } catch (err) { | 216 | } catch (err) { |
| 195 | console.error(err) | 217 | console.error(err) |
| @@ -200,84 +222,117 @@ const confirmSetInvalid = async () => { | @@ -200,84 +222,117 @@ const confirmSetInvalid = async () => { | ||
| 200 | } | 222 | } |
| 201 | 223 | ||
| 202 | const handleGoWork = (item) => { | 224 | const handleGoWork = (item) => { |
| 203 | - | ||
| 204 | - uni.navigateTo({ | ||
| 205 | - url: `/pages-sub/daily/maintain-manage/now-list-page?roadId=${item.roadId}&finish_state=${item.finishState || ''} &businessType=${item.busiLine}` | ||
| 206 | - }) | 225 | + uni.navigateTo({ |
| 226 | + // 重点:把planNo传过去 | ||
| 227 | + url: `/pages-sub/daily/maintain-manage/now-list-page?planNo=${item.planNo}&planTypeId=${planTypeId.value}&businessType=${item.busiLine}` | ||
| 228 | + }) | ||
| 229 | +} | ||
| 230 | +const handleEndTask = async (item) => { | ||
| 231 | + console.log(item) | ||
| 232 | + uni.showLoading({ title: '提交中...', mask: true }) | ||
| 233 | + try { | ||
| 234 | + // 接口 finishPlan,传 planNo | ||
| 235 | + await finishPlan({ | ||
| 236 | + planNo: item.planNo | ||
| 237 | + }) | ||
| 238 | + uni.showToast({ title: '结束任务成功', icon: 'success' }) | ||
| 239 | + // 操作成功,重新加载数据,刷新列表 | ||
| 240 | + await loadData() | ||
| 241 | + pagingRef.value?.reload() | ||
| 242 | + } catch (err) { | ||
| 243 | + console.error('结束任务失败', err) | ||
| 244 | + uni.showToast({ title: '结束任务失败,请重试', icon: 'none' }) | ||
| 245 | + } finally { | ||
| 246 | + uni.hideLoading() | ||
| 247 | + } | ||
| 207 | } | 248 | } |
| 208 | -const handleEndTask = (item) => console.log('结束任务', item) | ||
| 209 | 249 | ||
| 210 | -onShow(() => pagingRef.value?.reload()) | 250 | +// 页面卸载清空,防止内存残留 |
| 251 | +onUnload(() => { | ||
| 252 | + allTotalData.value = [] | ||
| 253 | + originRawList = [] | ||
| 254 | +}) | ||
| 211 | </script> | 255 | </script> |
| 212 | - | ||
| 213 | <style scoped lang="scss"> | 256 | <style scoped lang="scss"> |
| 214 | .page-container { | 257 | .page-container { |
| 215 | - height: 100vh; | ||
| 216 | - background-color: #f5f5f5; | 258 | + height: 100vh; |
| 259 | + background-color: #f5f5f5; | ||
| 217 | } | 260 | } |
| 261 | + | ||
| 218 | .sticky-wrap { | 262 | .sticky-wrap { |
| 219 | - background: #fff; | ||
| 220 | - border-bottom: 1rpx solid #eee; | 263 | + background: #fff; |
| 264 | + border-bottom: 1rpx solid #eee; | ||
| 221 | } | 265 | } |
| 266 | + | ||
| 222 | .card-list { | 267 | .card-list { |
| 223 | - padding:100rpx 20rpx 20rpx; | ||
| 224 | - display: flex; | ||
| 225 | - flex-direction: column; | ||
| 226 | - gap: 20rpx; | 268 | + padding: 100rpx 20rpx 20rpx; |
| 269 | + display: flex; | ||
| 270 | + flex-direction: column; | ||
| 271 | + gap: 20rpx; | ||
| 227 | } | 272 | } |
| 273 | + | ||
| 228 | .task-card { | 274 | .task-card { |
| 229 | - background: #fff; | ||
| 230 | - border-radius: 12rpx; | ||
| 231 | - padding: 24rpx; | ||
| 232 | - display: flex; | ||
| 233 | - flex-direction: column; | ||
| 234 | - gap: 20rpx; | 275 | + background: #fff; |
| 276 | + border-radius: 12rpx; | ||
| 277 | + padding: 24rpx; | ||
| 278 | + display: flex; | ||
| 279 | + flex-direction: column; | ||
| 280 | + gap: 20rpx; | ||
| 235 | } | 281 | } |
| 282 | + | ||
| 236 | .row { | 283 | .row { |
| 237 | - display: flex; | ||
| 238 | - align-items: center; | ||
| 239 | - justify-content: space-between; | ||
| 240 | - gap: 16rpx; | 284 | + display: flex; |
| 285 | + align-items: center; | ||
| 286 | + justify-content: space-between; | ||
| 287 | + gap: 16rpx; | ||
| 241 | } | 288 | } |
| 289 | + | ||
| 242 | .plan-name { | 290 | .plan-name { |
| 243 | - font-size: 30rpx; | ||
| 244 | - color: #333; | ||
| 245 | - font-weight: 500; | ||
| 246 | - flex: 1; | 291 | + font-size: 30rpx; |
| 292 | + color: #333; | ||
| 293 | + font-weight: 500; | ||
| 294 | + flex: 1; | ||
| 247 | } | 295 | } |
| 296 | + | ||
| 248 | .btn-set-invalid { | 297 | .btn-set-invalid { |
| 249 | - font-size: 26rpx; | ||
| 250 | - color: #1677ff; | ||
| 251 | - flex-shrink: 0; | ||
| 252 | - padding-left: 16rpx; | 298 | + font-size: 26rpx; |
| 299 | + color: #1677ff; | ||
| 300 | + flex-shrink: 0; | ||
| 301 | + padding-left: 16rpx; | ||
| 253 | } | 302 | } |
| 303 | + | ||
| 254 | .info-text { | 304 | .info-text { |
| 255 | - font-size: 26rpx; | ||
| 256 | - color: #666; | ||
| 257 | - flex: 1; | 305 | + font-size: 26rpx; |
| 306 | + color: #666; | ||
| 307 | + flex: 1; | ||
| 258 | } | 308 | } |
| 309 | + | ||
| 259 | .mini-btn { | 310 | .mini-btn { |
| 260 | - font-size: 24rpx; | ||
| 261 | - color: #333; | ||
| 262 | - background: #f5f5f5; | ||
| 263 | - border: 1rpx solid #ddd; | ||
| 264 | - border-radius: 6rpx; | ||
| 265 | - padding: 8rpx 20rpx; | ||
| 266 | - flex-shrink: 0; | 311 | + font-size: 24rpx; |
| 312 | + color: #333; | ||
| 313 | + background: #f5f5f5; | ||
| 314 | + border: 1rpx solid #ddd; | ||
| 315 | + border-radius: 6rpx; | ||
| 316 | + padding: 8rpx 20rpx; | ||
| 317 | + flex-shrink: 0; | ||
| 267 | } | 318 | } |
| 268 | 319 | ||
| 269 | // 弹窗样式,和回退弹窗保持一致 | 320 | // 弹窗样式,和回退弹窗保持一致 |
| 270 | .invalid-modal-content { | 321 | .invalid-modal-content { |
| 271 | - width: 100%; | ||
| 272 | - box-sizing: border-box; | ||
| 273 | - padding: 10rpx 0; | 322 | + width: 100%; |
| 323 | + box-sizing: border-box; | ||
| 324 | + padding: 10rpx 0; | ||
| 274 | } | 325 | } |
| 326 | + | ||
| 275 | .upload-wrap { | 327 | .upload-wrap { |
| 276 | - .upload-title { | ||
| 277 | - font-size: 28rpx; | ||
| 278 | - color: #333; | ||
| 279 | - margin-bottom: 10rpx; | ||
| 280 | - } | 328 | + .upload-title { |
| 329 | + font-size: 28rpx; | ||
| 330 | + color: #333; | ||
| 331 | + margin-bottom: 10rpx; | ||
| 332 | + } | ||
| 333 | +} | ||
| 334 | + | ||
| 335 | +.mt-20 { | ||
| 336 | + margin-top: 20rpx; | ||
| 281 | } | 337 | } |
| 282 | -.mt-20 { margin-top: 20rpx; } | ||
| 283 | </style> | 338 | </style> |
pages-sub/daily/maintain-manage/type-list.vue
| 1 | <template> | 1 | <template> |
| 2 | - <view class="tag-wrap"> | ||
| 3 | - <!-- 动态渲染标签列表 --> | ||
| 4 | - <view class="tag-item" v-for="(item, idx) in tagList" :key="idx" hover-class="tag-item--hover" @click="toNextPage"> | ||
| 5 | - <view class="tag-text"> | ||
| 6 | - <!-- 文字区域:超长省略号,数字括号单独放后面,永远可见 --> | ||
| 7 | - <text class="tag-name">{{ item.label }}</text> | ||
| 8 | - <text class="tag-count">({{ item.count }})</text> | ||
| 9 | - </view> | ||
| 10 | - | 2 | + <view class="tag-wrap"> |
| 3 | + <!-- 动态渲染标签列表 --> | ||
| 4 | + <template v-if="tagList.length > 0"> | ||
| 5 | + <view class="tag-item" v-for="(item, idx) in tagList" :key="idx" hover-class="tag-item--hover" | ||
| 6 | + @click="toNextPage(item)"> | ||
| 7 | + <view class="tag-text"> | ||
| 8 | + <!-- 文字区域:超长省略号,数字括号单独放后面,永远可见 --> | ||
| 9 | + <text class="tag-name">{{ item.label }}</text> | ||
| 10 | + <text class="tag-count">({{ item.count }})</text> | ||
| 11 | + </view> | ||
| 12 | + </view> | ||
| 13 | + </template> | ||
| 14 | + <template v-else> | ||
| 15 | + <view style="padding-top: 30px;text-align: center;width: 100%;">暂无养护类型,请去平台配置</view> | ||
| 16 | + </template> | ||
| 11 | </view> | 17 | </view> |
| 12 | - </view> | ||
| 13 | </template> | 18 | </template> |
| 14 | - | ||
| 15 | <script setup lang="ts"> | 19 | <script setup lang="ts"> |
| 16 | import { ref } from 'vue' | 20 | import { ref } from 'vue' |
| 17 | - | ||
| 18 | -// 数据源,完全动态,你可以接口返回覆盖 | ||
| 19 | -const tagList = ref([ | ||
| 20 | - { label: '补植', count: 5 }, | ||
| 21 | - { label: '修剪', count: 2 }, | ||
| 22 | - { label: '浇水、防寒', count: 3 }, | ||
| 23 | - { label: '绿地保洁', count: 3 }, | ||
| 24 | - { label: '病虫害防治', count: 3 }, | ||
| 25 | - { label: '施肥、除草', count: 4 }, | ||
| 26 | - // 测试超长文本 | ||
| 27 | - { label: '非常长的养护任务名称测试文字溢出效果', count: 9 }, | ||
| 28 | -]) | ||
| 29 | - | ||
| 30 | -const toNextPage = (item)=> { | 21 | +import { maintainPlan, maintainPlanNum } from "@/api/maintain-manage/maintain-manage"; |
| 22 | +import { getDictLabel } from '@/common/utils/dict'; | ||
| 23 | +import { useUserStore } from '@/pinia/user'; | ||
| 24 | +// 新增维护仓库 | ||
| 25 | +// import { useMaintainStore } from '@/pinia/maintain'; | ||
| 26 | +import { onLoad, onShow } from '@dcloudio/uni-app'; | ||
| 27 | +const userStore = useUserStore(); | ||
| 28 | +// const maintainStore = useMaintainStore(); | ||
| 29 | +// 标签列表,合并之后的数据结构 | ||
| 30 | +// rawList:保存该分类下全部原始数据,用于点击跳转 | ||
| 31 | +const tagList = ref<Array<{ | ||
| 32 | + label: string, | ||
| 33 | + count: number, | ||
| 34 | + rawList: any[], | ||
| 35 | + planTypeId: string | number | ||
| 36 | +}>>([]) | ||
| 37 | +const roadName = ref<string | null>(null) | ||
| 38 | +const levelId = ref<string | null>(null) | ||
| 39 | +onLoad((options) => { | ||
| 40 | + roadName.value = decodeURIComponent(options.roadName) | ||
| 41 | + levelId.value = decodeURIComponent(options.levelId); | ||
| 42 | +}); | ||
| 43 | +onShow(() => { | ||
| 44 | + if (!roadName.value) { | ||
| 45 | + uni.showToast({ | ||
| 46 | + title: '道路不存在', | ||
| 47 | + icon: 'none' | ||
| 48 | + }); | ||
| 49 | + return; | ||
| 50 | + } | ||
| 51 | + fetchData(); | ||
| 52 | +}); | ||
| 53 | +// 第一步:获取plan列表 | ||
| 54 | +const fetchData = async () => { | ||
| 55 | + try { | ||
| 56 | + const res = await maintainPlan({ | ||
| 57 | + roadName: roadName.value, | ||
| 58 | + levelId: levelId.value | ||
| 59 | + }) | ||
| 60 | + console.log('maintainPlan返回', res) | ||
| 61 | + if (!Array.isArray(res.list) || res.list.length === 0) { | ||
| 62 | + tagList.value = [] | ||
| 63 | + uni.showToast({ | ||
| 64 | + title: '暂无养护类型', | ||
| 65 | + icon: 'none' | ||
| 66 | + }); | ||
| 67 | + return | ||
| 68 | + } | ||
| 69 | + await requestBatchList(res.list) | ||
| 70 | + } catch (error) { | ||
| 71 | + console.error('请求失败:', error) | ||
| 72 | + uni.showToast({ title: '网络异常,请重试', icon: 'none' }) | ||
| 73 | + } | ||
| 74 | +} | ||
| 75 | +// 串行遍历所有batchNo,逐个调用第二个接口 | ||
| 76 | +const requestBatchList = async (list: any[]) => { | ||
| 77 | + const resultTags: any[] = [] | ||
| 78 | + for (const item of list) { | ||
| 79 | + try { | ||
| 80 | + const numRes = await maintainPlanNum({ | ||
| 81 | + pageNo: 1, | ||
| 82 | + pageSize: 10, | ||
| 83 | + planNo: '', | ||
| 84 | + planAttr: item.planAttr ?? '', | ||
| 85 | + finishState: 1, | ||
| 86 | + batchNo: item.batchNo | ||
| 87 | + }) | ||
| 88 | + const total = numRes?.total || 0 | ||
| 89 | + // 字典转换,把planTypeId转为养护类型名称 | ||
| 90 | + const typeLabel = getDictLabel('maintain_type', item.planTypeId) | ||
| 91 | + resultTags.push({ | ||
| 92 | + label: typeLabel, | ||
| 93 | + planTypeId: item.planTypeId, | ||
| 94 | + count: total, | ||
| 95 | + // 保留原始整条数据 | ||
| 96 | + originItem: item | ||
| 97 | + }) | ||
| 98 | + } catch (err) { | ||
| 99 | + console.error('batch请求异常', item.batchNo, err) | ||
| 100 | + const typeLabel = getDictLabel('maintain_type', item.planTypeId) | ||
| 101 | + resultTags.push({ | ||
| 102 | + label: typeLabel, | ||
| 103 | + planTypeId: item.planTypeId, | ||
| 104 | + count: 0, | ||
| 105 | + originItem: item | ||
| 106 | + }) | ||
| 107 | + } | ||
| 108 | + } | ||
| 109 | + console.log(resultTags) | ||
| 110 | + // ========== 分组合并相同养护类型,count累加,保存原始数组 ========== | ||
| 111 | + const groupMap = new Map<string, { | ||
| 112 | + count: number, | ||
| 113 | + rawList: any[], | ||
| 114 | + planTypeId: string | number | ||
| 115 | + }>() | ||
| 116 | + for (const item of resultTags) { | ||
| 117 | + console.log(item); | ||
| 118 | + const name = item.label | ||
| 119 | + if (!name) continue // 过滤字典为空的脏数据 | ||
| 120 | + if (groupMap.has(name)) { | ||
| 121 | + const groupData = groupMap.get(name)! | ||
| 122 | + groupData.count += item.count | ||
| 123 | + groupData.rawList.push(item.originItem) | ||
| 124 | + } else { | ||
| 125 | + groupMap.set(name, { | ||
| 126 | + count: item.count, | ||
| 127 | + rawList: [item.originItem], | ||
| 128 | + planTypeId: item.planTypeId | ||
| 129 | + }) | ||
| 130 | + } | ||
| 131 | + } | ||
| 132 | + // map转为数组给页面渲染 | ||
| 133 | + const groupList: any[] = [] | ||
| 134 | + groupMap.forEach((group, label) => { | ||
| 135 | + groupList.push({ | ||
| 136 | + label, | ||
| 137 | + count: group.count, | ||
| 138 | + rawList: group.rawList, | ||
| 139 | + planTypeId: group.planTypeId | ||
| 140 | + }) | ||
| 141 | + }) | ||
| 142 | + tagList.value = groupList | ||
| 143 | + console.log(tagList.value) | ||
| 144 | +} | ||
| 145 | +// 标签点击跳转,rawList存入storage,并且把planTypeId拼到url传给下一页 | ||
| 146 | +const toNextPage = (item: any) => { | ||
| 147 | + // maintainStore.setRawList(item.rawList) | ||
| 148 | + uni.setStorageSync('temp_maintain_rawList', item.rawList) | ||
| 31 | uni.navigateTo({ | 149 | uni.navigateTo({ |
| 32 | - url: `/pages-sub/daily/maintain-manage/type-list-page?roadId=${item.roadId}&finish_state=${item.finishState || ''} &businessType=${item.busiLine}` | ||
| 33 | - }) | 150 | + url: `/pages-sub/daily/maintain-manage/type-list-page?planTypeId=${item.planTypeId}` |
| 151 | + }) | ||
| 34 | } | 152 | } |
| 35 | </script> | 153 | </script> |
| 36 | - | ||
| 37 | <style scoped lang="scss"> | 154 | <style scoped lang="scss"> |
| 38 | .tag-wrap { | 155 | .tag-wrap { |
| 39 | - display: flex; | ||
| 40 | - flex-wrap: wrap; | ||
| 41 | - gap: 24rpx; | ||
| 42 | - padding: 30rpx; | 156 | + display: flex; |
| 157 | + flex-wrap: wrap; | ||
| 158 | + gap: 24rpx; | ||
| 159 | + padding: 30rpx; | ||
| 43 | } | 160 | } |
| 44 | 161 | ||
| 45 | .tag-item { | 162 | .tag-item { |
| 46 | - display: inline-flex; | ||
| 47 | - align-items: center; | ||
| 48 | - background: #e8f3ff; | ||
| 49 | - border: 1rpx solid #94c5ff; | ||
| 50 | - border-radius: 8rpx; | ||
| 51 | - padding: 16rpx 24rpx; | ||
| 52 | - max-width: 320rpx; // 限制标签最大宽度,超出自动省略 | ||
| 53 | - transition: all 0.2s; | ||
| 54 | - &--hover { | ||
| 55 | - background: #d8eaff; | ||
| 56 | - } | 163 | + display: inline-flex; |
| 164 | + align-items: center; | ||
| 165 | + background: #e8f3ff; | ||
| 166 | + border: 1rpx solid #94c5ff; | ||
| 167 | + border-radius: 8rpx; | ||
| 168 | + padding: 16rpx 24rpx; | ||
| 169 | + max-width: 320rpx; // 限制标签最大宽度,超出自动省略 | ||
| 170 | + transition: all 0.2s; | ||
| 171 | + | ||
| 172 | + &--hover { | ||
| 173 | + background: #d8eaff; | ||
| 174 | + } | ||
| 57 | } | 175 | } |
| 58 | 176 | ||
| 59 | .tag-text { | 177 | .tag-text { |
| 60 | - display: flex; | ||
| 61 | - align-items: center; | ||
| 62 | - flex: 1; | ||
| 63 | - overflow: hidden; | 178 | + display: flex; |
| 179 | + align-items: center; | ||
| 180 | + flex: 1; | ||
| 181 | + overflow: hidden; | ||
| 64 | } | 182 | } |
| 65 | 183 | ||
| 66 | .tag-name { | 184 | .tag-name { |
| 67 | - // 核心:文字超长省略,只对名称生效 | ||
| 68 | - white-space: nowrap; | ||
| 69 | - overflow: hidden; | ||
| 70 | - text-overflow: ellipsis; | ||
| 71 | - color: #1677ff; | ||
| 72 | - font-size: 28rpx; | 185 | + // 核心:文字超长省略,只对名称生效 |
| 186 | + white-space: nowrap; | ||
| 187 | + overflow: hidden; | ||
| 188 | + text-overflow: ellipsis; | ||
| 189 | + color: #1677ff; | ||
| 190 | + font-size: 28rpx; | ||
| 73 | } | 191 | } |
| 74 | 192 | ||
| 75 | .tag-count { | 193 | .tag-count { |
| 76 | - color: #1677ff; | ||
| 77 | - font-size: 28rpx; | ||
| 78 | - flex-shrink: 0; // 重点!禁止压缩,保证(数字)永远显示 | ||
| 79 | - margin-left: 4rpx; | 194 | + color: #1677ff; |
| 195 | + font-size: 28rpx; | ||
| 196 | + flex-shrink: 0; // 重点!禁止压缩,保证(数字)永远显示 | ||
| 197 | + margin-left: 4rpx; | ||
| 80 | } | 198 | } |
| 81 | 199 | ||
| 82 | .tag-icon { | 200 | .tag-icon { |
| 83 | - flex-shrink: 0; | ||
| 84 | - margin-left: 12rpx; | 201 | + flex-shrink: 0; |
| 202 | + margin-left: 12rpx; | ||
| 85 | } | 203 | } |
| 86 | </style> | 204 | </style> |
| 87 | - |
pinia/maintain.js
0 → 100644
| 1 | +import { defineStore } from 'pinia' | ||
| 2 | +export const useMaintainStore = defineStore('maintain', { | ||
| 3 | + state() { | ||
| 4 | + return { | ||
| 5 | + planRawList: [] | ||
| 6 | + } | ||
| 7 | + }, | ||
| 8 | + actions: { | ||
| 9 | + setRawList(list) { | ||
| 10 | + this.planRawList = list | ||
| 11 | + }, | ||
| 12 | + clearRawList() { | ||
| 13 | + this.planRawList = [] | ||
| 14 | + } | ||
| 15 | + } | ||
| 16 | +}) |