Commit 1d3624b054a22e7c4b0ca549d9c5d880e1ee4d5e

Authored by 刘淇
1 parent ecbfbe06

首页。养护任务

api/index/index.js
... ... @@ -30,5 +30,23 @@ export const iWorkOrderSummary = (params) => {
30 30 return post('/app-api/homePage/summary/iWorkOrderSummary', params);
31 31 };
32 32  
  33 +/**
  34 + * 距离待办已办
  35 + * @param {Object} params {mobile, password, code}
  36 + * @returns {Promise}
  37 + */
  38 +export const taskDetailsWithDistance = (params) => {
  39 + return post('/app-api/homePage/summary/taskDetailsWithDistance', params);
  40 +};
  41 +
  42 +/**
  43 + * 效率待办已办
  44 + * @param {Object} params {mobile, password, code}
  45 + * @returns {Promise}
  46 + */
  47 +export const taskDetailsWithSmartSort = (params) => {
  48 + return post('/app-api/homePage/summary/taskDetailsWithSmartSort', params);
  49 +};
  50 +
33 51  
34 52  
... ...
common/config/env.js
... ... @@ -4,7 +4,8 @@ const env = {
4 4 development: {
5 5 baseUrl: 'https://test.jichengshanshui.com.cn:28302',
6 6 // https://test.jichengshanshui.com.cn:28302
7   - // http://h95ce3aa.natappfree.cc
  7 +
  8 + // baseUrl: 'http://192.168.1.186:48081',
8 9  
9 10 uploadUrl: 'https://test.jichengshanshui.com.cn:28302',
10 11 // baseApi:'admin-api',
... ...
pages-sub/data/tree-archive/addTree.vue
... ... @@ -5,11 +5,22 @@
5 5 <up-input v-model.trim="formData.treetype" placeholder="请输入名称" maxlength="30" border="none"/>
6 6 </up-form-item>
7 7  
  8 + <!-- 树种类型 底部弹窗选择 -->
  9 + <!-- <up-form-item label="树种类型" prop="treeSpeciesType" required arrow @click="openTreeSpeciesType">
  10 + <up-input
  11 + v-model="formData.treeSpeciesTypeName"
  12 + placeholder="请选择树种类型"
  13 + readonly
  14 + border="none"
  15 + bg-color="transparent"
  16 + />
  17 + </up-form-item> -->
  18 +
8 19 <view class="form-row-wrap">
9 20 <up-row gutter="10">
10 21 <up-col span="6">
11 22 <up-form-item label="胸径" prop="dbh" required>
12   - <up-input v-model.trim="formData.dbh" placeholder="请输入" maxlength="10" border="none" input-align="left"/>
  23 + <up-input v-model.trim="formData.dbh" placeholder="请输入" maxlength="10" border="none" input-align="left"/>
13 24 <template #right>
14 25 <text style="padding-left: 12rpx;color:#ccc;font-size:14px">厘米</text>
15 26 </template>
... ... @@ -17,7 +28,7 @@
17 28 </up-col>
18 29 <up-col span="6">
19 30 <up-form-item label="高度" prop="treeheight">
20   - <up-input v-model.trim="formData.treeheight" placeholder="请输入" maxlength="10" border="none"
  31 + <up-input v-model.trim="formData.treeheight" placeholder="请输入" maxlength="10" border="none"
21 32 input-align="left"/>
22 33 <template #right>
23 34 <text style="padding-left: 12rpx;color:#ccc;font-size:14px;">米</text>
... ... @@ -65,7 +76,7 @@
65 76 :width="70"
66 77 :height="70"
67 78 :max-count="treeImgs.uploadConfig.maxCount"
68   - :upload-text="treeImgs.uploadConfig.uploadText"
  79 + :upload-text="treeImgs.uploadText"
69 80 :size-type="treeImgs.uploadConfig.sizeType"
70 81 ></up-upload>
71 82 </up-form-item>
... ... @@ -182,11 +193,13 @@
182 193 import { timeFormat } from '@/uni_modules/uview-plus';
183 194 import { ref, reactive, nextTick } from 'vue'
184 195 import { onReady, onLoad, onShow, onUnload } from '@dcloudio/uni-app';
  196 +
185 197 onUnload(() => {
186 198 uni.reLaunch({
187 199 url: '/pages-sub/data/tree-archive/index'
188 200 })
189 201 })
  202 +
190 203 import { addTree } from "@/api/tree-archive/tree-archive.js";
191 204 import { useUploadImgs } from '@/common/utils/useUploadImgs'
192 205 import { useUserStore } from '@/pinia/user';
... ... @@ -224,10 +237,10 @@ const isShow = ref(false)
224 237 const contentHeight = ref(200)
225 238 const treeOwnershipData = ref([])
226 239 const treeLevelData = ref([])
  240 +const treeSpeciesTypeList = ref([])
227 241 const loadingFlag = ref(false)
228 242 const showActionSheet = ref(false);
229 243 const currentActionSheetData = reactive({ type: '', list: [], title: '' });
230   -// 年月弹窗开关
231 244 const showDatePicker = ref(false);
232 245 const plantingDate = ref(Date.now())
233 246  
... ... @@ -241,9 +254,11 @@ const treeImgs = useUploadImgs({
241 254 })
242 255 if (!Array.isArray(treeImgs.rawImgList.value)) treeImgs.rawImgList.value = [];
243 256  
244   -// 表单数据:estimationtreeage 改为 plantingDate
  257 +// 表单数据:treeSpeciesType 存编码,treeSpeciesTypeName 页面展示文字
245 258 const formData = reactive({
246 259 treetype: '',
  260 + // treeSpeciesType: '',
  261 + treeSpeciesTypeName: '',
247 262 treeheight: '',
248 263 dbh: '',
249 264 treelevel: '',
... ... @@ -252,7 +267,7 @@ const formData = reactive({
252 267 oldtreeownership: '',
253 268 oldtreeownershipText: '',
254 269 latinname: '',
255   - plantingDate: '', //栽种年月 yyyy-MM
  270 + plantingDate: '',
256 271 canopysouthnorth: '',
257 272 canopyeastwest: '',
258 273 weekday: '',
... ... @@ -266,84 +281,107 @@ const formData = reactive({
266 281 maintainunit: ''
267 282 })
268 283  
269   -// 表单校验规则(移除estimationtreeage,plantingDate非必填)
  284 +// 表单校验规则
270 285 const rules = reactive({
271 286 treetype: [
272   - {validator: (rule, val, callback) => {
  287 + {
  288 + validator: (rule, val, callback) => {
273 289 if (val && hasEmoji(val)) return callback(new Error('禁止输入表情符号'));
274 290 callback();
275   - }, trigger: ['blur','change','input'], priority: 10},
276   - {required: true, message: '请输入名称', trigger: ['blur','change','input']},
277   - {max:30, message:'名称最多输入30个字符', trigger: ['blur','change','input']}
  291 + }, trigger: ['blur', 'change', 'input'], priority: 10
  292 + },
  293 + { required: true, message: '请输入名称', trigger: ['blur', 'change', 'input'] },
  294 + { max: 30, message: '名称最多输入30个字符', trigger: ['blur', 'change', 'input'] }
278 295 ],
  296 + // treeSpeciesType: [
  297 + // { required: true, message: '请选择树种类型', trigger: ['change'] }
  298 + // ],
279 299 treeheight: [
280   - {max: 10, message: '树高不能超过10个字符', trigger: ['blur','change','input']},
281   - {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur','change','input']},
282   - {validator: (rule, val, callback) => {
  300 + { max: 10, message: '树高不能超过10个字符', trigger: ['blur', 'change', 'input'] },
  301 + { pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur', 'change', 'input'] },
  302 + {
  303 + validator: (rule, val, callback) => {
283 304 if (isPureZero(val)) return callback(new Error('不能输入纯0无效值'));
284 305 callback();
285   - }, trigger: ['blur','change','input']}
  306 + }, trigger: ['blur', 'change', 'input']
  307 + }
286 308 ],
287 309 dbh: [
288   - {validator: (rule, val, callback) => {
  310 + {
  311 + validator: (rule, val, callback) => {
289 312 if (val && hasEmoji(val)) return callback(new Error('禁止输入表情符号'));
290 313 callback();
291   - }, trigger: ['blur','change','input'], priority:10},
292   - {required: true, message: '请输入胸径',trigger: ['blur','change','input']},
293   - {max: 10, message: '胸径不能超过10个字符', trigger: ['blur','change','input']},
294   - {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur','change','input']},
295   - {validator: (rule, val, callback) => {
  314 + }, trigger: ['blur', 'change', 'input'], priority: 10
  315 + },
  316 + { required: true, message: '请输入胸径', trigger: ['blur', 'change', 'input'] },
  317 + { max: 10, message: '胸径不能超过10个字符', trigger: ['blur', 'change', 'input'] },
  318 + { pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur', 'change', 'input'] },
  319 + {
  320 + validator: (rule, val, callback) => {
296 321 if (isPureZero(val)) return callback(new Error('不能输入纯0无效值'));
297 322 callback();
298   - }, trigger: ['blur','change','input']}
  323 + }, trigger: ['blur', 'change', 'input']
  324 + }
299 325 ],
300 326 weekday: [
301   - {max: 10, message: '干周不能超过10个字符', trigger: ['blur','change','input']},
302   - {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur','change','input']},
303   - {validator: (rule, val, callback) => {
  327 + { max: 10, message: '干周不能超过10个字符', trigger: ['blur', 'change', 'input'] },
  328 + { pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur', 'change', 'input'] },
  329 + {
  330 + validator: (rule, val, callback) => {
304 331 if (isPureZero(val)) return callback(new Error('不能输入纯0无效值'));
305 332 callback();
306   - }, trigger: ['blur','change','input']}
  333 + }, trigger: ['blur', 'change', 'input']
  334 + }
307 335 ],
308 336 canopyeastwest: [
309   - {max: 10, message: '东西冠幅不能超过10个字符',trigger: ['blur','change','input']},
310   - {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur','change','input']},
311   - {validator: (rule, val, callback) => {
  337 + { max: 10, message: '东西冠幅不能超过10个字符', trigger: ['blur', 'change', 'input'] },
  338 + { pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur', 'change', 'input'] },
  339 + {
  340 + validator: (rule, val, callback) => {
312 341 if (isPureZero(val)) return callback(new Error('不能输入纯0无效值'));
313 342 callback();
314   - }, trigger: ['blur','change','input']}
  343 + }, trigger: ['blur', 'change', 'input']
  344 + }
315 345 ],
316 346 canopysouthnorth: [
317   - {max: 10, message: '南北冠幅不能超过10个字符', trigger: ['blur','change','input']},
318   - {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur','change','input']},
319   - {validator: (rule, val, callback) => {
  347 + { max: 10, message: '南北冠幅不能超过10个字符', trigger: ['blur', 'change', 'input'] },
  348 + { pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur', 'change', 'input'] },
  349 + {
  350 + validator: (rule, val, callback) => {
320 351 if (isPureZero(val)) return callback(new Error('不能输入纯0无效值'));
321 352 callback();
322   - }, trigger: ['blur','change','input']}
  353 + }, trigger: ['blur', 'change', 'input']
  354 + }
323 355 ],
324   - growlocation: [{required: true, message: '请选择地图位置', trigger: ['blur','change','manual']}],
  356 + growlocation: [{ required: true, message: '请选择地图位置', trigger: ['blur', 'change', 'manual'] }],
325 357 managedutyunit: [
326   - {validator: (rule, val, callback) => {
  358 + {
  359 + validator: (rule, val, callback) => {
327 360 if (val && hasEmoji(val)) return callback(new Error('禁止输入表情符号'));
328 361 callback();
329   - }, trigger: ['blur','change','input'], priority:10},
330   - {required: true, message: '请输入管护单位', trigger: ['blur','change','input']},
331   - {max:30, message:'管护单位最多输入30个字符', trigger: ['blur','change','input']}
  362 + }, trigger: ['blur', 'change', 'input'], priority: 10
  363 + },
  364 + { required: true, message: '请输入管护单位', trigger: ['blur', 'change', 'input'] },
  365 + { max: 30, message: '管护单位最多输入30个字符', trigger: ['blur', 'change', 'input'] }
332 366 ],
333   - oldtreeownershipText: [{required: true, message: '请选择权属分类', trigger: ['blur','change']}],
  367 + oldtreeownershipText: [{ required: true, message: '请选择权属分类', trigger: ['blur', 'change'] }],
334 368 latinname: [
335   - {validator: (rule, val, callback) => {
  369 + {
  370 + validator: (rule, val, callback) => {
336 371 if (val && hasEmoji(val)) return callback(new Error('禁止输入表情符号'));
337 372 callback();
338   - }, trigger: ['blur','change','input'], priority:10},
339   - {max:30, message:'拉丁文最多输入30个字符', trigger: ['blur','change','input']}
  373 + }, trigger: ['blur', 'change', 'input'], priority: 10
  374 + },
  375 + { max: 30, message: '拉丁文最多输入30个字符', trigger: ['blur', 'change', 'input'] }
340 376 ],
341 377 growthenvironment: [
342   - {validator: (rule, val, callback) => {
  378 + {
  379 + validator: (rule, val, callback) => {
343 380 if (val && hasEmoji(val)) return callback(new Error('禁止输入表情符号'));
344 381 callback();
345   - }, trigger: ['blur','change','input'], priority:10},
346   - {max:50, message:'生长环境最多输入50个字符', trigger: ['blur','change','input']}
  382 + }, trigger: ['blur', 'change', 'input'], priority: 10
  383 + },
  384 + { max: 50, message: '生长环境最多输入50个字符', trigger: ['blur', 'change', 'input'] }
347 385 ],
348 386 treeImgList: [treeImgs.imgValidateRule]
349 387 })
... ... @@ -352,10 +390,13 @@ const rules = reactive({
352 390 onLoad((options) => {
353 391 formData.road = options.roadId
354 392 })
  393 +
355 394 onShow(async () => {
356 395 treeLevelData.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('tree_level'))
357   - treeOwnershipData.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('tree_ownership'))
  396 + treeOwnershipData.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('tree_ownership'))
  397 + treeSpeciesTypeList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('tree_species_type'))
358 398 })
  399 +
359 400 onReady(() => {
360 401 nextTick(() => {
361 402 formRef.value?.setRules(rules);
... ... @@ -363,22 +404,22 @@ onReady(() =&gt; {
363 404 });
364 405  
365 406 // 打开栽种年月选择器
366   -const openPlantDatePicker = ()=>{
  407 +const openPlantDatePicker = () => {
367 408 showDatePicker.value = true
368 409 }
369   -// 选中年月回调,自动格式 yyyy-MM
370   -const onSelectPlantDate = (date)=>{
371   -console.log(plantingDate.value)
372   - console.log(timeFormat(plantingDate.value, 'yyyy-mm'))
373   - // date格式:yyyy-MM
374   - formData.plantingDate = timeFormat(plantingDate.value, 'yyyy-mm')
  410 +
  411 +// 年月选择回调
  412 +const onSelectPlantDate = () => {
  413 + formData.plantingDate = timeFormat(plantingDate.value, 'yyyy-mm')
375 414 showDatePicker.value = false
376 415 }
377 416  
  417 +// 展开/收起更多区域
378 418 const toggleArea = () => {
379 419 isShow.value = !isShow.value
380 420 }
381 421  
  422 +// 打开权属/级别选择弹窗
382 423 const handleActionSheetOpen = (type) => {
383 424 const configMap = {
384 425 ownership: { title: '请选择权属分类', list: treeOwnershipData.value },
... ... @@ -388,11 +429,23 @@ const handleActionSheetOpen = (type) =&gt; {
388 429 showActionSheet.value = true;
389 430 };
390 431  
  432 +// 打开树种类型选择弹窗
  433 +const openTreeSpeciesType = () => {
  434 + Object.assign(currentActionSheetData, {
  435 + title: '请选择树种类型',
  436 + list: treeSpeciesTypeList.value,
  437 + type: 'treeSpecies'
  438 + })
  439 + showActionSheet.value = true
  440 +}
  441 +
  442 +// 关闭弹窗
391 443 const handleActionSheetClose = () => {
392 444 showActionSheet.value = false;
393 445 Object.assign(currentActionSheetData, { type: '', list: [], title: '' });
394 446 };
395 447  
  448 +// 弹窗选中回调
396 449 const handleActionSheetSelect = (e) => {
397 450 const { type } = currentActionSheetData;
398 451 if (type === 'ownership') {
... ... @@ -402,6 +455,10 @@ const handleActionSheetSelect = (e) =&gt; {
402 455 } else if (type === 'level') {
403 456 formData.treelevel = e.value
404 457 formData.treeleveltext = e.name
  458 + } else if (type === 'treeSpecies') {
  459 + formData.treeSpeciesType = e.value
  460 + formData.treeSpeciesTypeName = e.name
  461 + formRef.value?.validateField('treeSpeciesType');
405 462 }
406 463 handleActionSheetClose();
407 464 };
... ... @@ -419,13 +476,13 @@ const openMap = () =&gt; {
419 476 fail: (err) => {
420 477 console.error('地图选择失败', err);
421 478 if (err.errMsg.includes('auth deny')) {
422   - uni.showToast({title: '请授权位置权限', icon: 'none'});
  479 + uni.showToast({ title: '请授权位置权限', icon: 'none' });
423 480 }
424 481 }
425 482 });
426 483 }
427 484  
428   -// 提交
  485 +// 提交表单
429 486 const submit = async () => {
430 487 if (loadingFlag.value) return
431 488  
... ... @@ -443,11 +500,11 @@ const submit = async () =&gt; {
443 500 formData.treeImgList = uploadImgUrls
444 501 loadingFlag.value = true
445 502 try {
446   - const res = await addTree(formData)
447   - uni.showToast({title: "新增成功", icon: "success"});
448   - uni.redirectTo({url: '/pages-sub/data/tree-archive/index'});
  503 + await addTree(formData)
  504 + uni.showToast({ title: "新增成功", icon: "success" });
  505 + uni.redirectTo({ url: '/pages-sub/data/tree-archive/index' });
449 506 } catch (err) {
450   - uni.showToast({title: "新增失败,请重试", icon: "none"});
  507 + uni.showToast({ title: "新增失败,请重试", icon: "none" });
451 508 console.error(err)
452 509 } finally {
453 510 loadingFlag.value = false
... ... @@ -479,7 +536,7 @@ const submit = async () =&gt; {
479 536 position: relative;
480 537 margin-bottom: 0 !important;
481 538 }
482   - :deep(.u-form-item__body__right__message ) {
  539 + :deep(.u-form-item__body__right__message) {
483 540 position: absolute;
484 541 left: 0;
485 542 bottom: -20rpx;
... ... @@ -490,6 +547,7 @@ const submit = async () =&gt; {
490 547 box-sizing: border-box;
491 548 }
492 549 }
  550 +
493 551 .form-row-wrap + .u-form-item {
494 552 margin-top: 25rpx !important;
495 553 }
... ...
pages-sub/data/tree-archive/editTree.vue
... ... @@ -22,6 +22,17 @@
22 22 <up-input v-model.trim="formData.treetype" placeholder="请输入名称" maxlength="30" border="none"/>
23 23 </up-form-item>
24 24  
  25 + <!-- 新增:树种类型 底部弹窗选择 -->
  26 + <!-- <up-form-item label="树种类型" prop="treeSpeciesType" required arrow @click="openTreeSpeciesType">
  27 + <up-input
  28 + v-model="formData.treeSpeciesTypeName"
  29 + placeholder="请选择树种类型"
  30 + readonly
  31 + border="none"
  32 + bg-color="transparent"
  33 + />
  34 + </up-form-item> -->
  35 +
25 36 <view class="form-row-wrap">
26 37 <up-row gutter="10">
27 38 <up-col span="6">
... ... @@ -277,8 +288,6 @@ import { onReady, onLoad, onShow, onUnload } from &#39;@dcloudio/uni-app&#39;;
277 288  
278 289 import { treeRoadReq, treeLogReq, treeDetailReq, updateTree, inspectionLogReq }
279 290 from "@/api/tree-archive/tree-archive.js";
280   -// 引入巡检列表接口
281   -// import { getInspectListReq } from "@/api/tree-archive/tree-archive.js";
282 291  
283 292 import { timeFormat } from '@/uni_modules/uview-plus';
284 293 import { useUploadImgs } from '@/common/utils/useUploadImgs'
... ... @@ -289,11 +298,6 @@ const CONST = {
289 298 UPLOAD_CONFIG: { maxCount: 3, uploadText: '选择图片', sizeType: ['compressed'] }
290 299 }
291 300  
292   -// ========== 页面关闭统一跳转 ==========
293   -onUnload(() => {
294   - uni.reLaunch({url: '/pages-sub/data/tree-archive/index'})
295   -})
296   -
297 301 // ========== 基础变量 ==========
298 302 const rows = ref([])
299 303 const roadId = ref('')
... ... @@ -321,6 +325,7 @@ const isShow = ref(false)
321 325 const contentHeight = ref(200)
322 326 const treeOwnershipData = ref([])
323 327 const treeLevelData = ref([])
  328 +const treeSpeciesTypeList = ref([]) // 树种类型字典列表
324 329 const showActionSheet = ref(false);
325 330 const currentActionSheetData = reactive({type: '', list: [], title: ''});
326 331  
... ... @@ -333,10 +338,12 @@ const treeImgList = useUploadImgs({
333 338 treeImgList.imgList.value = []
334 339 treeImgList.rawImgList.value = []
335 340  
336   -// ========== 表单数据 移除estimationtreeage,替换为plantingDate ==========
  341 +// ========== 表单数据 ==========
337 342 const formData = reactive({
338 343 id: '',
339 344 treetype: '',
  345 + // treeSpeciesType: '', // 树种类型编码(提交接口)
  346 + treeSpeciesTypeName: '', // 树种类型名称(页面展示)
340 347 treeheight: '',
341 348 dbh: '',
342 349 treelevel: '',
... ... @@ -390,6 +397,10 @@ const rules = reactive({
390 397 {required: true, message: '请输入名称', trigger: ['blur', 'change', 'input']},
391 398 {max: 30, message: '名称最多输入30个字符', trigger: ['blur', 'change', 'input']}
392 399 ],
  400 + // 树种类型 必填校验
  401 + // treeSpeciesType: [
  402 + // { required: true, message: '请选择树种类型', trigger: ['change'] }
  403 + // ],
393 404 treeheight: [
394 405 {max: 10, message: '树高不能超过10个字符', trigger: ['blur', 'change', 'input']},
395 406 {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur', 'change', 'input']},
... ... @@ -478,8 +489,11 @@ onLoad((options) =&gt; {
478 489 });
479 490  
480 491 onShow(async () => {
  492 + // 加载全部字典
481 493 treeLevelData.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('tree_level'))
482 494 treeOwnershipData.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('tree_ownership'))
  495 + treeSpeciesTypeList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('tree_species_type'))
  496 +
483 497 if (activeTab.value === 0 && treeId.value && !isInit.value) {
484 498 await getTreeDetail()
485 499 isInit.value = true
... ... @@ -511,12 +525,14 @@ const treeRoadQuery = async () =&gt; {
511 525 rows.value = res.list
512 526 }
513 527  
514   -// 获取树木详情
  528 +// 获取树木详情(回显树种类型名称+编码)
515 529 const getTreeDetail = async () => {
516 530 const res = await treeDetailReq({id: treeId.value})
517 531 Object.assign(formData, res)
  532 + // 字典回显
518 533 formData.oldtreeownershipText = uni.$dict.getDictLabel('tree_ownership', res.oldtreeownership)
519 534 formData.treeleveltext = uni.$dict.getDictLabel('tree_level', formData.treelevel)
  535 + // formData.treeSpeciesTypeName = uni.$dict.getDictLabel('tree_species_type', formData.treeSpeciesType)
520 536  
521 537 treeImgList.imgList.value = []
522 538 treeImgList.rawImgList.value = []
... ... @@ -545,7 +561,6 @@ const getTreeLogList = async () =&gt; {
545 561  
546 562 // 获取巡检记录列表
547 563 const getInspectList = async () => {
548   - // 正式环境启用下面接口
549 564 const res = await inspectionLogReq({treeId: treeId.value,pageNo:1,pageSize:100})
550 565 console.log(res)
551 566 inspectRows.value = res.list || []
... ... @@ -556,10 +571,6 @@ const openPlantDatePicker = () =&gt; {
556 571 showDatePicker.value = true
557 572 }
558 573 const onSelectPlantDate = (date) => {
559   - // date自动 yyyy-MM
560   - console.log(plantingDate.value)
561   - console.log(timeFormat(plantingDate.value, 'yyyy-mm'))
562   - // date格式:yyyy-MM
563 574 formData.plantingDate = timeFormat(plantingDate.value, 'yyyy-mm')
564 575 showDatePicker.value = false
565 576 }
... ... @@ -567,6 +578,7 @@ const onSelectPlantDate = (date) =&gt; {
567 578 // ========== 表单相关方法 ==========
568 579 const toggleArea = () => isShow.value = !isShow.value
569 580  
  581 +// 打开权属/级别弹窗
570 582 const handleActionSheetOpen = (type) => {
571 583 const configMap = {
572 584 ownership: {title: '请选择权属分类', list: treeOwnershipData.value},
... ... @@ -576,11 +588,22 @@ const handleActionSheetOpen = (type) =&gt; {
576 588 showActionSheet.value = true;
577 589 };
578 590  
  591 +// 打开树种类型弹窗
  592 +const openTreeSpeciesType = () => {
  593 + Object.assign(currentActionSheetData, {
  594 + title: '请选择树种类型',
  595 + list: treeSpeciesTypeList.value,
  596 + type: 'treeSpecies'
  597 + })
  598 + showActionSheet.value = true
  599 +}
  600 +
579 601 const handleActionSheetClose = () => {
580 602 showActionSheet.value = false;
581 603 Object.assign(currentActionSheetData, {type: '', list: [], title: ''});
582 604 };
583 605  
  606 +// 弹窗选中回调(区分三种类型)
584 607 const handleActionSheetSelect = (e) => {
585 608 const {type} = currentActionSheetData;
586 609 if (type === 'ownership') {
... ... @@ -590,6 +613,11 @@ const handleActionSheetSelect = (e) =&gt; {
590 613 } else if (type === 'level') {
591 614 formData.treelevel = e.value
592 615 formData.treeleveltext = e.name
  616 + } else if (type === 'treeSpecies') {
  617 + // 树种类型:编码存接口字段,名称页面展示
  618 + formData.treeSpeciesType = e.value
  619 + formData.treeSpeciesTypeName = e.name
  620 + formRef.value?.validateField('treeSpeciesType');
593 621 }
594 622 handleActionSheetClose();
595 623 };
... ... @@ -658,11 +686,9 @@ const toInspectDetailPage = (item) =&gt; {
658 686 // 跳转到新增巡检页面
659 687 const toAddInspect = () => {
660 688 const firstItem = inspectRows.value.length > 0 ? inspectRows.value[0] : null
661   - // 存入本地缓存
662 689 if (firstItem) {
663 690 uni.setStorageSync('temp_inspect_fill_data', JSON.stringify(firstItem))
664 691 } else {
665   - // 没有数据先清空残留缓存
666 692 uni.removeStorageSync('temp_inspect_fill_data')
667 693 }
668 694 uni.navigateTo({
... ...
pages-sub/data/tree-archive/logDetail.vue
... ... @@ -185,9 +185,18 @@ const formatImgList = (imgList) =&gt; {
185 185  
186 186 // ========== 生命周期 ==========
187 187 onUnload(() => {
188   - uni.reLaunch({
189   - url: '/pages-sub/data/tree-archive/index'
190   - })
  188 + const pages = getCurrentPages()
  189 + // 页面栈长度 >=2 说明有上两页,回退2级
  190 + if (pages.length >= 2) {
  191 + uni.navigateBack({
  192 + delta: 2
  193 + })
  194 + } else {
  195 + // 兜底:页面栈不足时,跳列表页
  196 + uni.reLaunch({
  197 + url: '/pages-sub/data/tree-archive/index'
  198 + })
  199 + }
191 200 })
192 201  
193 202 onLoad((options) => {
... ...
pages-sub/data/tree-archive/treeRecord.vue
... ... @@ -69,12 +69,12 @@
69 69 <script setup>
70 70 import { ref } from 'vue'
71 71 import { onLoad, onShow, onUnload } from '@dcloudio/uni-app';
72   -onUnload(() => {
73   - // 关闭所有页面,直接打开【行道树档案】主页面 【微信小程序完美兼容,无任何报错】
74   - uni.reLaunch({
75   - url: '/pages-sub/data/tree-archive/index'
76   - })
77   -})
  72 +// onUnload(() => {
  73 +// // 关闭所有页面,直接打开【行道树档案】主页面 【微信小程序完美兼容,无任何报错】
  74 +// uni.reLaunch({
  75 +// url: '/pages-sub/data/tree-archive/index'
  76 +// })
  77 +// })
78 78 import { treeRoadReq } from "@/api/tree-archive/tree-archive.js";
79 79 import { timeFormat } from '@/uni_modules/uview-plus';
80 80 const rows = ref([])
... ...
pages.json
... ... @@ -18,7 +18,8 @@
18 18 "path": "pages/index/index",
19 19 "style": {
20 20 "navigationBarTitleText": "首页",
21   - "navigationStyle": "custom"
  21 + "navigationStyle": "custom",
  22 + "enablePullDownRefresh": true
22 23 }
23 24 },
24 25  
... ...
pages/index/index.vue
... ... @@ -9,7 +9,7 @@
9 9 </view>
10 10 </view>
11 11 <view class="msg-icon" @click="handleMsgClick" hover-class="msg-icon--hover">
12   - <up-icon name="chat" color="#fff" size="24"/>
  12 + <up-icon name="chat" color="#fff" size="24" />
13 13 <view class="msg-badge">
14 14 <up-badge type="error" showZero="true" max="999" :value="msgCount"></up-badge>
15 15 </view>
... ... @@ -17,161 +17,192 @@
17 17 </view>
18 18  
19 19 <view class="content-wrap">
20   - <!-- 任务完成情况标题 -->
21   - <view class="module-title">任务完成情况</view>
22   -
23   - <!-- 任务完成情况卡片 -->
24   - <view class="task-chart-card">
25   - <view class="card-header">
26   - <view class="unit-tip">单位: 个</view>
27   - <view class="date-picker-wrap">
28   - <neo-datetime-pro
29   - v-model="dateRange"
30   - type="daterange"
31   - :clearable="false"
32   - placeholder="请选择日期范围"
33   - @confirm="handleDateConfirm"
34   - :max-date="maxDate"
35   -
36   - @popup-show="handlePopupShow"
37   - @popup-hide="handlePopupHide"
38   - />
39   -<!-- :minDate="minDate"-->
  20 + <!-- 图表区域 增加平滑动画容器 -->
  21 + <view class="chart-wrap" v-show="showChartArea">
  22 + <!-- 任务完成情况标题 -->
  23 + <view class="module-title">任务完成情况</view>
  24 +
  25 + <!-- 任务完成情况卡片 -->
  26 + <view class="task-chart-card">
  27 + <view class="card-header">
  28 + <view class="unit-tip">单位: 个</view>
  29 + <view class="date-picker-wrap">
  30 + <neo-datetime-pro v-model="dateRange" type="daterange" :clearable="false" placeholder="请选择日期范围"
  31 + @confirm="handleDateConfirm" :max-date="maxDate" @popup-show="handlePopupShow"
  32 + @popup-hide="handlePopupHide" />
  33 + </view>
40 34 </view>
41   - </view>
42 35  
43   - <!-- 双折线K线图 -->
44   - <view class="chart-container">
45   - <uni-charts
46   - v-if="!isDatePickerOpen"
47   - type="line"
48   - :data="klineChartData"
49   - :categories="klineCategories"
50   - :option="klineOption"
51   - :width="chartWidth"
52   - :height="chartHeight"
53   - />
  36 + <!-- 双折线K线图 -->
  37 + <view class="chart-container">
  38 + <uni-charts v-if="!isDatePickerOpen" type="line" :data="klineChartData" :categories="klineCategories"
  39 + :option="klineOption" :width="chartWidth" :height="chartHeight" />
  40 + </view>
54 41 </view>
55 42 </view>
56 43  
57   - <!-- 使用up-tabs组件实现Tab切换 -->
58   - <up-tabs
59   - :list="tabList"
60   - lineWidth="60"
61   - @change="handleTabChange"
62   - class="task-tab-container"
63   - :scrollable="false"
64   - :activeStyle="{
65   - color: '#000000',
66   - fontSize: '14'
67   - }"
68   - :inactiveStyle="{
69   - color: '#787070',
70   - fontSize: '14'
71   - }"
72   - ></up-tabs>
73   -
74   - <!-- z-paging分页列表 -->
75   - <z-paging
76   - ref="paging"
77   - v-model="currentTaskList"
78   - @query="queryList"
79   - :auto-show-system-loading="true"
80   - :page-size="10"
81   - use-page-scroll
82   - >
83   -
84   - <template #empty>
85   - <up-empty/>
86   - </template>
87   - <view class="task-list-container">
88   - <view
89   - class="task-item"
90   - v-for="(item,index) in currentTaskList"
91   - :key="`${item.taskName}_${index}`"
92   - hover-class="task-item--hover"
93   - @click="handleTaskClick(item)"
94   - >
  44 + <view class="con-wrap">
  45 + <view class="con-title">养护任务 </view>
  46 +
  47 + <!-- 手写Tab切换:胶囊分段样式 -->
  48 + <view class="custom-tab-wrap">
  49 + <view class="tab-capsule" :class="{ active: currentTabValue === 0 }" @click="handleTabClick(0)">
  50 + 待办事项({{ todoTotal }})
  51 + </view>
  52 + <view class="tab-capsule" :class="{ active: currentTabValue === 1 }" @click="handleTabClick(1)">
  53 + 已办事项({{ doneTotal }})
  54 + </view>
  55 + </view>
  56 +
  57 +
  58 + <!-- 动态组信息文案 -->
  59 + <view class="tip-top" style="padding: 8px 0 0 70px;">
  60 + <text>{{ tipText }}</text>
  61 + </view>
  62 +
  63 + <!-- 顶部进度提示栏 -->
  64 + <view v-if="showTipHeader" class="tip-header">
  65 + <up-icon name="checkmark-circle" color="#00b42a" size="20"></up-icon>
  66 + <text style="color: #1677ff;">今日组内:您已经完成{{ finishNum }}项,小组均值{{ avgNum }}项</text>
  67 + </view>
  68 +
  69 +
  70 + <scroll-view
  71 + class="task-list-container"
  72 + scroll-y
  73 + @scroll="onListScroll"
  74 + @scrolltoupper="handleTouchTop"
  75 + enhanced
  76 + >
  77 + <view v-if="taskList.length === 0" class="empty-box">
  78 + <up-empty />
  79 + </view>
  80 + <view class="task-item" v-for="(item, index) in taskList" :key="`${item.taskName}_${index}`"
  81 + hover-class="task-item--hover" @click="handleTaskClick(item)">
95 82 <view class="task-item__content">
96   - <view class="task-item__name u-line-1">
97   - {{ item.taskName || '无' }}
98   - </view>
99   - <view class="task-item__footer u-flex common-item-center common-justify-between"
100   - style="font-size: 13px; margin-top: 5px;">
101   - <view class="urgency-tag">
102   - 紧急程度: {{ uni.$dict.getDictLabel('workorder_pressing_type', item.pressingType) || '--' }}
  83 + <view class="name-fixed-wrap">
  84 + <view class="task-item__name ">
  85 + 事项{{ index + 1 }}
103 86 </view>
104   - <view style="font-size: 13px;color: #333">{{
105   - timeFormat(item.busiDateTime, 'yyyy-mm-dd hh:MM:ss')
106   - }}
  87 + <view class="task-name-text">
  88 + {{ item.taskName || '无' }}
107 89 </view>
108 90 </view>
  91 +
  92 + <view v-if="currentTabValue === 0" class="task-item__footer" style="padding-left: 60px;">
  93 + <image class="loc-icon" src="https://img.jichengshanshui.com.cn:28207/appimg/定位icon@3x.png" mode="aspectFit"></image>
  94 + <template v-if="isWalkTextMode">
  95 + <text>距离当前{{ item.distance }}m ·约{{ item.estimatedWalkingMinutes }}分钟步行</text>
  96 + </template>
  97 + <template v-else>
  98 + <text>{{ item.distance }}m ·</text>
  99 + <up-icon name="star-fill" color="#ffd21d" size="20"></up-icon>
  100 + <text>历史完成率{{ item.efficiencyPercent }}%</text>
  101 + </template>
  102 + </view>
109 103 </view>
110 104 </view>
111   - </view>
112   - </z-paging>
  105 + </scroll-view>
  106 + </view>
113 107 </view>
114 108 </view>
115 109 </template>
116 110  
117 111 <script setup lang="ts">
118   -import {ref, watch, computed, reactive} from 'vue';
119   -import {onShow} from '@dcloudio/uni-app'
120   -import {useUserStore} from '@/pinia/user';
121   -import {timeFormat} from '@/uni_modules/uview-plus';
  112 +import { ref, computed, nextTick } from 'vue';
  113 +import { onShow,onUnload } from '@dcloudio/uni-app'
  114 +import { useUserStore } from '@/pinia/user';
  115 +import { timeFormat } from '@/uni_modules/uview-plus';
122 116 import uniCharts from '@/components/uni-charts/uni-charts.vue';
123   -import {getUnreadCount} from "@/api/user";
124   -import {getTaskCompletionSummary, getTaskDetails} from "@/api/index/index";
  117 +import { getUnreadCount } from "@/api/user";
  118 +import {
  119 + getTaskCompletionSummary,
  120 + taskDetailsWithDistance,
  121 + taskDetailsWithSmartSort
  122 +} from "@/api/index/index";
125 123  
  124 +const userStore = useUserStore();
  125 +const deptId = computed(() => String(userStore.userInfo?.user?.deptId ?? ''));
  126 +
  127 +// ====================== 只需要在这里统一维护分组配置 ======================
  128 +type GroupItem = {
  129 + deptId: string;
  130 + label: string;
  131 + showTipHeader: boolean; // 是否展示顶部进度条
  132 + useWalkText: boolean; // 是否展示步行距离文案
  133 +};
  134 +const groupConfig: GroupItem[] = [
  135 + { deptId: '364', label: '[组2 A1B2 纯效率·有提醒]', showTipHeader: true, useWalkText: true },
  136 + { deptId: '337', label: '[组3 A2B1 个性化·无提醒]', showTipHeader: false, useWalkText: false },
  137 + { deptId: '338', label: '[组4 A2B2 个性化·有提醒]', showTipHeader: true, useWalkText: false },
  138 +];
  139 +// 默认组1配置
  140 +const defaultGroup: GroupItem = { deptId: '', label: '[组1 A1B1 纯效率·无提醒]', showTipHeader: false, useWalkText: true };
  141 +
  142 +// 获取当前部门配置对象
  143 +const currentGroup = computed(() => {
  144 + const target = groupConfig.find(item => item.deptId === deptId.value);
  145 + return target ?? defaultGroup;
  146 +});
  147 +
  148 +// 下面全部自动读取配置,无需写if/switch
  149 +const tipText = computed(() => currentGroup.value.label);
  150 +const showTipHeader = computed(() => currentGroup.value.showTipHeader);
  151 +const isWalkTextMode = computed(() => currentGroup.value.useWalkText);
  152 +const useDistanceApi = computed(() => currentGroup.value.useWalkText);
  153 +// =========================================================================
126 154  
127   -// ========== 2. 响应式数据 ==========
  155 +const finishNum = ref(0);
  156 +const avgNum = ref(0);
128 157 const msgCount = ref(0);
129   -const currentTab = ref('todo'); // todo:待办 done:已办
130   -const dateRange = ref([]);
  158 +const currentTabValue = ref(0);
  159 +
  160 +const dateRange = ref<string[]>([]);
131 161 const yesterday = new Date();
132   -yesterday.setDate(yesterday.getDate() - 1); // 日期减1天 = 昨天
  162 +yesterday.setDate(yesterday.getDate() - 1);
133 163 const maxDate = ref(timeFormat(yesterday, 'yyyy-mm-dd'));
134   -const minDate = '2026-01-06'
  164 +const minDate = '2026-01-06';
135 165  
136   -// 新增响应式变量:标记日期选择器弹窗是否打开
137 166 const isDatePickerOpen = ref(false);
138   -
139   -// 弹窗打开时触发
140 167 const handlePopupShow = () => {
141 168 isDatePickerOpen.value = true;
142 169 };
143   -
144   -// 弹窗关闭时触发
145 170 const handlePopupHide = () => {
146 171 isDatePickerOpen.value = false;
147 172 };
148   -// Tab列表配置
149   -const tabList = ref([]);
150   -const todoTotal = ref(0); // 待办事项总数
151   -const doneTotal = ref(0); // 已办事项总数
152 173  
153   -// 分页相关
154   -const paging = ref(null);
155   -const currentTaskList = ref([]); // 当前显示的任务列表(分页数据)
  174 +const todoTotal = ref(0);
  175 +const doneTotal = ref(0);
  176 +const todoFullList = ref<any[]>([]);
  177 +const doneFullList = ref<any[]>([]);
  178 +const taskList = computed(() => {
  179 + return currentTabValue.value === 0 ? todoFullList.value : doneFullList.value;
  180 +});
  181 +
  182 +const chooseOptions = computed(() => [
  183 + { title: `待办事项(${todoTotal.value})`, id: 'todo' },
  184 + { title: `已办事项(${doneTotal.value})`, id: 'done' }
  185 +]);
156 186  
157   -// 折线图数据配置
  187 +const currentLon = ref<string>('');
  188 +const currentLat = ref<string>('');
158 189 const chartWidth = ref(0);
159 190 const chartHeight = ref(300);
160   -const klineCategories = ref([]); // X轴日期
  191 +
  192 +const klineCategories = ref<string[]>([]);
161 193 const klineChartData = ref([
162 194 {
163 195 name: '已完成任务数',
164   - data: [],
  196 + data: [] as number[],
165 197 color: '#25AF69'
166 198 },
167 199 {
168 200 name: '总任务数',
169   - data: [],
  201 + data: [] as number[],
170 202 color: '#B34C17'
171 203 }
172 204 ]);
173 205  
174   -// 图表配置
175 206 const klineOption = ref({
176 207 grid: {
177 208 top: '25%',
... ... @@ -180,16 +211,16 @@ const klineOption = ref({
180 211 bottom: '16%'
181 212 },
182 213 xAxis: {
183   - axisLabel: {fontSize: 12, color: '#666'}
  214 + axisLabel: { fontSize: 12, color: '#666' }
184 215 },
185 216 yAxis: {
186 217 min: 0,
187   - splitLine: {lineStyle: {color: '#f5f5f7'}},
188   - axisLabel: {fontSize: 12, color: '#666'}
  218 + splitLine: { lineStyle: { color: '#f5f5f7' } },
  219 + axisLabel: { fontSize: 12, color: '#666' }
189 220 },
190 221 tooltip: {
191 222 trigger: 'axis',
192   - formatter: (params) => {
  223 + formatter: (params: any[]) => {
193 224 return `${params[0].name}<br/>
194 225 ${params[0].seriesName}: ${params[0].data}<br/>
195 226 ${params[1].seriesName}: ${params[1].data}`;
... ... @@ -198,34 +229,144 @@ const klineOption = ref({
198 229 legend: {
199 230 show: true,
200 231 top: '5%',
201   - textStyle: {fontSize: 12}
  232 + textStyle: { fontSize: 12 }
202 233 }
203 234 });
204 235  
205   -// ========== 3. 计算属性 ==========
206   -const userStore = useUserStore();
207 236 const userName = computed(() => userStore.userInfo?.user?.nickname || '用户');
208 237  
209   -// ========== 4. 工具函数 ==========
210   -const rpx2px = (rpx) => {
  238 +const rpx2px = (rpx: number) => {
211 239 const systemInfo = uni.getSystemInfoSync();
212   - return Math.floor(rpx * (systemInfo.screenWidth / 750));
  240 + return Math.floor(systemInfo.screenWidth / 750 * rpx);
  241 +};
  242 +
  243 +// ========== 滚动逻辑 终极优化 ==========
  244 +const showChartArea = ref(true);
  245 +// 隐藏阈值:超过80rpx收起
  246 +const scrollHideThreshold = rpx2px(80);
  247 +// 安全显示区间:≤80rpx一律显示,覆盖所有回弹残留(70/80/90)
  248 +const safeShowThreshold = rpx2px(130);
  249 +let scrollTimer: number | null = null;
  250 +
  251 +// 下拉触顶强制展示
  252 +const handleTouchTop = () => {
  253 + showChartArea.value = true;
213 254 };
214 255  
  256 +const onListScroll = (e: any) => {
  257 + const scrollTop = e.detail.scrollTop;
  258 + console.log('scrollTop', scrollTop)
  259 + if (scrollTimer) clearTimeout(scrollTimer);
  260 +
  261 + // 实时:只要在安全区间立刻显示
  262 + if (scrollTop <= safeShowThreshold) {
  263 + showChartArea.value = true;
  264 + } else if (scrollTop > scrollHideThreshold) {
  265 + showChartArea.value = false;
  266 + }
  267 +
  268 + // 滚动停止兜底二次校验
  269 + scrollTimer = setTimeout(() => {
  270 + if (scrollTop <= safeShowThreshold) {
  271 + showChartArea.value = true;
  272 + }
  273 + }, 100);
  274 +};
  275 +
  276 +// 切换tab重置图表显示
  277 +const handleTabClick = (tabIndex: number) => {
  278 + if (currentTabValue.value === tabIndex) return;
  279 + currentTabValue.value = tabIndex;
  280 + // 切换tab强制显示图表
  281 + showChartArea.value = true;
  282 +};
  283 +
  284 +// 页面每次显示强制重置图表为显示状态
  285 +onShow(async () => {
  286 + showChartArea.value = true;
  287 + chartWidth.value = rpx2px(750 - 60);
  288 + chartHeight.value = rpx2px(300);
  289 + initRecent7Days();
  290 + getUnread();
  291 + await loadAllTaskByDept();
  292 +});
  293 +
  294 +// 页面销毁清除定时器
  295 +onUnload(() => {
  296 + if (scrollTimer) clearTimeout(scrollTimer);
  297 +});
  298 +// ======================================
  299 +
  300 +const getCurrentLocation = (): Promise<{ lon: string; lat: string }> => {
  301 + return new Promise((resolve, reject) => {
  302 + uni.getSetting({
  303 + success: (settingRes) => {
  304 + if (!settingRes.authSetting['scope.userLocation']) {
  305 + uni.authorize({
  306 + scope: 'scope.userLocation',
  307 + success: () => {
  308 + uni.getLocation({
  309 + type: 'gcj02',
  310 + success: (loc) => {
  311 + resolve({
  312 + lon: loc.longitude.toFixed(6),
  313 + lat: loc.latitude.toFixed(6)
  314 + })
  315 + },
  316 + fail: (err) => {
  317 + uni.showToast({ title: '获取位置失败,请打开手机定位', icon: 'none' })
  318 + reject(err)
  319 + }
  320 + })
  321 + },
  322 + fail: () => {
  323 + uni.showModal({
  324 + title: '需要定位权限',
  325 + content: '请允许获取位置才能正常使用',
  326 + confirmText: '去设置',
  327 + success: (modal) => {
  328 + if (modal.confirm) {
  329 + uni.openSetting()
  330 + }
  331 + }
  332 + })
  333 + reject('用户拒绝定位权限')
  334 + }
  335 + })
  336 + } else {
  337 + uni.getLocation({
  338 + type: 'gcj02',
  339 + success: (loc) => {
  340 + resolve({
  341 + lon: loc.longitude.toFixed(6),
  342 + lat: loc.latitude.toFixed(6)
  343 + })
  344 + },
  345 + fail: (err) => {
  346 + uni.showToast({ title: '获取位置失败,请打开手机定位', icon: 'none' })
  347 + reject(err)
  348 + }
  349 + })
  350 + }
  351 + },
  352 + fail: (err) => {
  353 + reject('获取权限失败:' + err.errMsg)
  354 + }
  355 + })
  356 + })
  357 +}
215 358  
216 359 const initRecent7Days = async () => {
217 360 const yesterday = new Date();
218   - yesterday.setDate(yesterday.getDate() - 1); // 结束时间 = 昨天
  361 + yesterday.setDate(yesterday.getDate() - 1);
219 362 const sevenDaysAgo = new Date(yesterday);
220   - sevenDaysAgo.setDate(yesterday.getDate() - 6); // 开始时间 = 昨天往前推6天(共7天)
  363 + sevenDaysAgo.setDate(yesterday.getDate() - 6);
221 364  
222   - // 格式化日期为 YYYY-MM-DD 格式
223 365 dateRange.value = [
224 366 timeFormat(sevenDaysAgo, 'yyyy-mm-dd'),
225 367 timeFormat(yesterday, 'yyyy-mm-dd')
226 368 ];
227   -
228   - initChartData(timeFormat(sevenDaysAgo, 'yyyy-mm-dd'), timeFormat(yesterday, 'yyyy-mm-dd'));
  369 + initChartData(dateRange.value[0], dateRange.value[1]);
229 370 };
230 371  
231 372 const getUnread = async () => {
... ... @@ -238,145 +379,115 @@ const getUnread = async () =&gt; {
238 379 }
239 380 };
240 381  
241   -/**
242   - * 初始化图表数据
243   - * @param {string} beginTime 开始时间
244   - * @param {string} endTime 结束时间
245   - */
246   -const initChartData = async (beginTime, endTime) => {
  382 +const initChartData = async (beginTime: string, endTime: string) => {
247 383 try {
248   - let postData = {
249   - beginTime: beginTime,
250   - endTime: endTime
251   - };
  384 + const postData = { beginTime, endTime };
252 385 const res = await getTaskCompletionSummary(postData);
253   - console.log('图表数据:', res);
254   - klineCategories.value = res.map(item => item.currentDate);
255   - klineChartData.value[0].data = res.map(item => item.completedNum);
256   - klineChartData.value[1].data = res.map(item => item.countpendingNum);
  386 + klineCategories.value = res.map((item: any) => item.currentDate);
  387 + klineChartData.value[0].data = res.map((item: any) => item.completedNum);
  388 + klineChartData.value[1].data = res.map((item: any) => item.countpendingNum);
257 389 } catch (error) {
258 390 console.error('获取图表数据失败:', error);
259 391 }
260 392 };
261 393  
262   -/**
263   - * 获取所有Tab的总数(初始化时调用)
264   - */
265   -const getAllTabTotal = async () => {
  394 +const loadDistanceApiTask = async (lon: string, lat: string) => {
266 395 try {
267   - // 同时请求待办和已办的总数(只请求第1页,每页1条,仅获取total)
  396 + const paramsTodo = { queryType: 1, currentLon: lon, currentLat: lat };
  397 + const paramsDone = { queryType: 2, currentLon: lon, currentLat: lat };
268 398 const [todoRes, doneRes] = await Promise.all([
269   - getTaskDetails({queryType: 1, pageNo: 1, pageSize: 1}),
270   - getTaskDetails({queryType: 2, pageNo: 1, pageSize: 1})
  399 + taskDetailsWithDistance(paramsTodo),
  400 + taskDetailsWithDistance(paramsDone)
271 401 ]);
272   -
273   - // 更新总数
274   - todoTotal.value = todoRes?.total || 0;
275   - doneTotal.value = doneRes?.total || 0;
276   - console.log('已办:')
277   - console.log(doneRes)
278   - tabList.value = [
279   - {name: `待办事项(${todoTotal.value})`},
280   - {name: `已办事项(${doneTotal.value})`},
281   - ]
282   - console.log('初始化总数:', {todoTotal: todoTotal.value, doneTotal: doneTotal.value});
283   - } catch (error) {
284   - console.error('获取Tab总数失败:', error);
285   - // 保底:如果已办请求失败,至少保证待办数据正常
  402 + todoFullList.value = todoRes?.sortedTasks || [];
  403 + doneFullList.value = doneRes?.sortedTasks || [];
  404 + todoTotal.value = Number(todoRes?.totalPendingTasks) || 0;
  405 + doneTotal.value = Number(doneRes?.totalPendingTasks) || 0;
  406 + finishNum.value = todoRes?.teamProgress?.userCompletedCount ?? 0;
  407 + avgNum.value = doneRes?.teamProgress?.teamAverage ?? 0;
  408 + await nextTick();
  409 + } catch (err) {
  410 + uni.showToast({ title: '加载任务失败', icon: 'none' });
  411 + console.error('加载distance接口任务报错', err);
  412 + todoFullList.value = [];
  413 + doneFullList.value = [];
  414 + todoTotal.value = 0;
  415 + doneTotal.value = 0;
286 416 }
287 417 };
288 418  
289   -// ========== 5. 业务逻辑 ==========
290   -/**
291   - * up-tabs切换事件处理
292   - * @param {object} item 选中的Tab项
293   - */
294   -const handleTabChange = (item) => {
295   - // 解析Tab类型(todo/done)
296   - console.log(item)
297   - const newTab = item.name.includes('待办') ? 'todo' : 'done';
298   - if (currentTab.value === newTab) return;
299   -
300   - currentTab.value = newTab;
301   - // 切换标签后刷新分页数据
302   - paging.value?.reload();
303   -};
304   -
305   -/**
306   - * 分页查询列表数据
307   - * @param {number} pageNo 页码
308   - * @param {number} pageSize 每页条数
309   - */
310   -const queryList = async (pageNo, pageSize) => {
  419 +const loadSmartSortTask = async (lon: string, lat: string) => {
311 420 try {
312   - // 1:待完成 2:已完成
313   - const queryType = currentTab.value === 'todo' ? 1 : 2;
314   - const postData = {
315   - queryType,
316   - pageNo,
317   - pageSize,
318   - };
319   -
320   - const res = await getTaskDetails(postData);
321   - console.log(`${currentTab.value}列表数据:`, res);
322   -
323   - // 更新当前Tab的总数
324   - if (currentTab.value === 'todo') {
325   - todoTotal.value = res?.total || 0;
326   - } else {
327   - doneTotal.value = res?.total || 0;
328   - }
329   -
330   - // 适配z-paging分页(注意接口返回结构是res.data.list)
331   - paging.value.complete(res?.list || [], res?.total || 0);
332   - } catch (error) {
333   - console.error(`加载${currentTab.value}列表失败:`, error);
334   - paging.value?.complete(false);
335   - uni.showToast({title: '加载失败,请重试', icon: 'none'});
  421 + const [todoRes, doneRes] = await Promise.all([
  422 + taskDetailsWithSmartSort({
  423 + queryType: 1,
  424 + currentLon: lon,
  425 + currentLat: lat
  426 + }),
  427 + taskDetailsWithSmartSort({
  428 + queryType: 2,
  429 + currentLon: lon,
  430 + currentLat: lat
  431 + })
  432 + ]);
  433 + todoFullList.value = todoRes?.sortedTasks || [];
  434 + doneFullList.value = doneRes?.sortedTasks || [];
  435 + todoTotal.value = Number(todoRes?.totalPendingTasks) || 0;
  436 + doneTotal.value = Number(doneRes?.totalPendingTasks) || 0;
  437 + finishNum.value = todoRes?.teamProgress?.userCompletedCount ?? 0;
  438 + avgNum.value = doneRes?.teamProgress?.teamAverage ?? 0;
  439 + await nextTick();
  440 + } catch (err) {
  441 + uni.showToast({ title: '加载任务失败', icon: 'none' });
  442 + console.error('加载smartSort接口任务报错', err);
  443 + todoFullList.value = [];
  444 + doneFullList.value = [];
  445 + todoTotal.value = 0;
  446 + doneTotal.value = 0;
336 447 }
337 448 };
338 449  
339   -// 日期选择确认事件
340   -const handleDateConfirm = (e) => {
341   - console.log('日期选择确认:', e);
  450 +const loadAllTaskByDept = async () => {
  451 + const loc = await getCurrentLocation();
  452 + currentLon.value = loc.lon;
  453 + currentLat.value = loc.lat;
  454 + if (useDistanceApi.value) {
  455 + await loadDistanceApiTask(loc.lon, loc.lat);
  456 + } else {
  457 + await loadSmartSortTask(loc.lon, loc.lat);
  458 + }
  459 +};
342 460  
343   - // 更新选中的日期范围
  461 +const handleDateConfirm = (e: string[]) => {
344 462 dateRange.value = e;
345   -
346   - // 加载选择日期后的图表数据
347 463 initChartData(e[0], e[1]);
348 464 };
349 465  
350 466 const handleMsgClick = () => {
351   - uni.navigateTo({url: '/pages-sub/msg/index'});
352   -};
353   -
354   -const handleTaskClick = (item) => {
355   - // uni.navigateTo({url: `/pages-sub/task/detail?id=${item.id || ''}`});
  467 + uni.navigateTo({ url: '/pages-sub/msg/index' });
356 468 };
357 469  
358   -// ========== 6. 生命周期 ==========
359   -onShow(async () => {
360   - // 初始化图表尺寸
361   - chartWidth.value = rpx2px(750 - 60);
362   - chartHeight.value = rpx2px(300);
363   -
364   - // 初始化默认日期范围(最近7天)
365   - initRecent7Days();
366   -
367   - // 获取未读消息数
368   - getUnread();
369   - await getAllTabTotal();
370   -
371   - // 初始化分页数据(待办列表)
372   - if (paging.value) {
373   - paging.value.reload();
  470 +const handleTaskClick = (item: any) => {
  471 + if (item.taskType == "maintain") {
  472 + if (currentTabValue.value === 0) {
  473 + uni.navigateTo({ url: `/pages-sub/daily/maintain-manage/add-record?planNo=${item.planNo || ''}` });
  474 + }
  475 + if (currentTabValue.value === 1) {
  476 + uni.navigateTo({ url: `/pages-sub/daily/maintain-manage/finish-plan-detail?planNo=${item.planNo || ''}` });
  477 + }
374 478 }
375   -});
  479 + if (item.taskType == "inspection") {
  480 + if (currentTabValue.value === 0) {
  481 + uni.navigateTo({ url: `/pages-sub/daily/patrol-manage/add-patrol-record?planNo=${item.planNo}&batchNo=${item.batchNo}` });
  482 + }
  483 + if (currentTabValue.value === 1) {
  484 + uni.navigateTo({ url: `/pages-sub/daily/patrol-manage/finish-plan-detail?planNo=${item.planNo || ''}` });
  485 + }
  486 + }
  487 +};
376 488 </script>
377 489  
378 490 <style scoped lang="scss">
379   -/* 样式变量 */
380 491 $primary-color: #1677ff;
381 492 $danger-color: #E53935;
382 493 $text-color: #333;
... ... @@ -389,14 +500,18 @@ $card-radius: 5px;
389 500 $spacing-sm: 5px;
390 501 $spacing-md: 10px;
391 502 $spacing-lg: 15px;
392   -$border-color: #e5e5e5; // 新增边框颜色变量
  503 +$border-color: #e5e5e5;
393 504  
394 505 .page-container {
395   - min-height: 100vh;
  506 + height: 100vh;
396 507 background-color: $bg-color;
  508 + overflow: hidden;
  509 + display: flex;
  510 + flex-direction: column;
397 511 }
398 512  
399 513 .user-info-bar {
  514 + flex-shrink: 0;
400 515 background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat;
401 516 background-size: 100% 100%;
402 517 padding: 80px $spacing-lg 135px;
... ... @@ -404,28 +519,22 @@ $border-color: #e5e5e5; // 新增边框颜色变量
404 519 justify-content: space-between;
405 520 align-items: center;
406 521 color: #fff;
407   - //position: relative;
408   - //z-index: 1;
409 522  
410 523 .username {
411 524 font-size: 16px;
412 525 margin-bottom: 4px;
413 526 }
414   -
415 527 .login-desc {
416 528 font-size: 16px;
417 529 }
418   -
419 530 .msg-icon {
420 531 position: relative;
421 532 padding: 5px;
422 533 top: 10px;
423 534 border-radius: 50%;
424   -
425 535 &--hover {
426 536 background-color: rgba(255, 255, 255, 0.2);
427 537 }
428   -
429 538 .msg-badge {
430 539 position: absolute;
431 540 top: -2px;
... ... @@ -435,13 +544,25 @@ $border-color: #e5e5e5; // 新增边框颜色变量
435 544 }
436 545  
437 546 .content-wrap {
  547 + flex: 1;
438 548 margin-top: -122px;
439 549 border-radius: $border-radius $border-radius 0 0;
440 550 background-color: $bg-color;
441   - //position: relative;
442   - //z-index: 999;
443 551 overflow: hidden;
444   - padding-bottom: $spacing-lg;
  552 + display: flex;
  553 + flex-direction: column;
  554 +}
  555 +
  556 +.chart-wrap {
  557 + flex-shrink: 0;
  558 + transition: all 0.35s ease-out;
  559 + opacity: 1;
  560 + overflow: hidden;
  561 +}
  562 +.chart-wrap[hidden] {
  563 + opacity: 0;
  564 + height: 0;
  565 + margin: 0;
445 566 }
446 567  
447 568 .module-title {
... ... @@ -449,6 +570,7 @@ $border-color: #e5e5e5; // 新增边框颜色变量
449 570 font-size: 14px;
450 571 font-weight: 600;
451 572 color: $text-color-light;
  573 + flex-shrink: 0;
452 574 }
453 575  
454 576 .task-chart-card {
... ... @@ -456,7 +578,7 @@ $border-color: #e5e5e5; // 新增边框颜色变量
456 578 border-radius: $card-radius;
457 579 margin: 0 $spacing-lg $spacing-md;
458 580 padding: $spacing-md;
459   -
  581 + flex-shrink: 0;
460 582 .card-header {
461 583 display: flex;
462 584 justify-content: space-between;
... ... @@ -464,7 +586,6 @@ $border-color: #e5e5e5; // 新增边框颜色变量
464 586 font-size: 12px;
465 587 color: $text-color-placeholder;
466 588 margin-bottom: $spacing-md;
467   -
468 589 .date-picker-wrap {
469 590 padding: 2px 4px;
470 591 border-radius: 3px;
... ... @@ -476,7 +597,6 @@ $border-color: #e5e5e5; // 新增边框颜色变量
476 597 }
477 598 }
478 599 }
479   -
480 600 .chart-container {
481 601 width: 100%;
482 602 height: 150px;
... ... @@ -485,53 +605,124 @@ $border-color: #e5e5e5; // 新增边框颜色变量
485 605 justify-content: center;
486 606 }
487 607 }
  608 +.con-wrap {
  609 + border-radius: 8px;
  610 + background-color: #fff;
  611 + margin: 0 15px;
  612 + flex: 1;
  613 + min-height: 0;
  614 + display: flex;
  615 + flex-direction: column;
  616 + overflow: hidden;
  617 +}
488 618  
489   -.task-tab-container {
490   - position: relative;
491   - z-index: 4;
492   - margin: 0 $spacing-lg $spacing-sm;
  619 +.con-title {
  620 + padding: 10px 15px;
  621 + border-bottom: 1px solid #f0e9e9;
  622 + flex-shrink: 0;
  623 +}
  624 +
  625 +.custom-tab-wrap {
  626 + display: flex;
  627 + align-items: center;
  628 + gap: 20rpx;
  629 + padding: 20rpx 0 20rpx 20rpx;
  630 + background: #fff;
  631 + flex-shrink: 0;
  632 +}
  633 +
  634 +.tab-capsule {
  635 + padding: 10rpx 20rpx;
  636 + display: flex;
  637 + align-items: center;
  638 + justify-content: center;
  639 + font-size: 28rpx;
  640 + color: #999;
  641 + background: #f5f5f5;
  642 + border-radius: 0;
  643 + transition: all 0.2s ease;
  644 + font-weight: 500;
  645 +}
  646 +.tab-capsule.active {
  647 + color: #d4af37;
  648 + background: #fff8dc;
  649 +}
  650 +
  651 +.tip-header {
  652 + display: flex;
  653 + align-items: center;
  654 + gap: 8rpx;
  655 + padding: 12rpx 15rpx;
  656 + background: #e6f7ff;
  657 + font-size: 14px;
  658 + color: #000;
  659 + flex-shrink: 0;
  660 +}
  661 +
  662 +.tip-top {
  663 + padding: 8px 0 0 70px;
  664 + flex-shrink: 0;
493 665 }
494 666  
495 667 .task-list-container {
496   - margin: 0 $spacing-lg;
497   - background-color: $card-bg;
498   - border-radius: $card-radius;
499   - overflow: hidden; // 防止边框溢出圆角
  668 + width: 100%;
  669 + height: 100%;
  670 + padding-bottom: 300rpx;
  671 + box-sizing: border-box;
  672 +}
  673 +
  674 +.empty-box {
  675 + padding: 60rpx 0;
500 676 }
501 677  
502 678 .task-item {
503 679 padding: $spacing-md;
504 680 transition: background-color 0.2s;
505 681 border-bottom: 1px solid $border-color;
506   -
507 682 &:last-child {
508 683 border-bottom: none;
509 684 }
510   -
511 685 &--hover {
512 686 background-color: $bg-color;
513 687 }
514   -
515 688 &__content {
516 689 width: 100%;
517 690 }
518   -
  691 + .name-fixed-wrap {
  692 + display: flex;
  693 + }
  694 + .task-item__name {
  695 + width: 60px;
  696 + }
  697 + .task-name-text {
  698 + flex: 1;
  699 + font-size: 14px;
  700 + color: $text-color;
  701 + overflow: hidden;
  702 + white-space: nowrap;
  703 + text-overflow: ellipsis;
  704 + }
519 705 &__name {
520   - font-size: 13px;
  706 + font-size: 15px;
521 707 color: $text-color;
522   - margin-bottom: $spacing-sm;
  708 + display: inline-block;
523 709 }
524   -
525 710 &__footer {
526 711 display: flex;
  712 + align-items: flex-start;
  713 + gap: 6rpx;
  714 + font-size: 13px;
  715 + color: $text-color-light;
  716 + margin-top: $spacing-sm;
527 717 align-items: center;
528   - justify-content: space-between;
529 718 }
530 719 }
  720 +.loc-icon {
  721 + width: 20px;
  722 + height: 20px;
  723 +}
531 724  
532   -// 紧急程度标签样式
533 725 .urgency-tag {
534 726 color: #7D7D7D;
535   -
536 727 }
537 728 </style>
538 729 \ No newline at end of file
... ...