Commit 03b006dc743efe38a64d91581f69b2739e050de3

Authored by 刘淇
1 parent c5fdc60a

树修改

api/tree-archive/tree-archive.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 /**
pages-sub/data/tree-archive/editTree.vue
1 <template> 1 <template>
2 <view class="container"> 2 <view class="container">
3 - <!-- ✅ 顶部固定吸顶Tabs区域 - 核心新增 --> 3 + <!-- 顶部固定吸顶Tabs区域 -->
4 <up-sticky bg-color="#ffffff"> 4 <up-sticky bg-color="#ffffff">
5 <view class="header-wrap"> 5 <view class="header-wrap">
6 <up-tabs 6 <up-tabs
@@ -15,33 +15,170 @@ @@ -15,33 +15,170 @@
15 </view> 15 </view>
16 </up-sticky> 16 </up-sticky>
17 17
18 - <!-- ✅ Tab0 基本信息页面 (树木的详情编辑页,保留你原编辑页核心结构,这里给你做了标准基础版,可直接加表单) --> 18 + <!-- Tab0 基本信息页面 - v-if改v-show 核心修复 -->
19 <view v-show="activeTab === 0" class="base-info-wrap"> 19 <view v-show="activeTab === 0" class="base-info-wrap">
20 - <up-card :border="false" :show-head="false" class="base-card">  
21 - <view class="card-body-inner">  
22 - <view class="record-list-left" :style="`background-image: url(${treeInfo.treephoto});`"></view>  
23 - <view class="record-list-right">  
24 - <view class="record-list-right-title">  
25 - <view class="u-line-1 treetypeName">{{ treeInfo.treetype }}</view>  
26 - </view>  
27 - <view class="fs-mt8 fs-align__center">  
28 - <img src="../../../static/imgs/tree/tree-high.png" style="width: 14px;height: 14px;margin-right: 6px;" alt=""> 高度:{{ treeInfo.treeheight }} 米  
29 - </view>  
30 - <view class="fs-mt8 fs-align__center">  
31 - <img src="../../../static/imgs/tree/treearound.png" style="width: 14px;height: 14px;margin-right: 6px;" alt="">胸径:{{ treeInfo.dbh }} 厘米  
32 - </view>  
33 - <view class="fs-mt8 fs-align__center">  
34 - <text>树木编号:{{ treeInfo.treenumber }}</text>  
35 - </view> 20 + <up-form :model="formData" ref="formRef" label-width="140rpx" border-bottom>
  21 + <up-form-item label="名称" prop="treetype" required>
  22 + <up-input v-model.trim="formData.treetype" placeholder="请输入名称" maxlength="30" border="none"/>
  23 + </up-form-item>
  24 +
  25 + <view class="form-row-wrap">
  26 + <up-row gutter="10">
  27 + <up-col span="6">
  28 + <up-form-item label="胸径" prop="dbh" required>
  29 + <up-input v-model.trim="formData.dbh" placeholder="请输入" maxlength="10" border="none"
  30 + input-align="left"/>
  31 + <template #right>
  32 + <text style="padding-left: 12rpx;color:#ccc;font-size:14px">厘米</text>
  33 + </template>
  34 + </up-form-item>
  35 + </up-col>
  36 + <up-col span="6">
  37 + <up-form-item label="高度" prop="treeheight">
  38 + <up-input v-model.trim="formData.treeheight" placeholder="请输入" maxlength="10" border="none"
  39 + input-align="left"/>
  40 + <template #right>
  41 + <text style="padding-left: 12rpx;color:#ccc;font-size:14px;">米</text>
  42 + </template>
  43 + </up-form-item>
  44 + </up-col>
  45 + </up-row>
  46 + </view>
  47 +
  48 + <up-form-item label="位置" prop="growlocation" required class="location-form-item" @click="openMap">
  49 + <up-input v-model="formData.growlocation" placeholder="请选择" readonly border="none"/>
  50 + <template #right>
  51 + <up-icon name="map" size="22"></up-icon>
  52 + </template>
  53 + </up-form-item>
  54 +
  55 + <up-row gutter="10">
  56 + <up-col span="6">
  57 + <up-form-item label="经度" :borderBottom="false">
  58 + <up-input v-model="formData.longitude" placeholder="" readonly border="none"/>
  59 + </up-form-item>
  60 + </up-col>
  61 + <up-col span="6">
  62 + <up-form-item label="纬度" :borderBottom="false">
  63 + <up-input v-model="formData.latitude" placeholder="" readonly border="none"/>
  64 + </up-form-item>
  65 + </up-col>
  66 + </up-row>
  67 +
  68 + <up-form-item label="管护单位" prop="managedutyunit" required>
  69 + <up-input v-model.trim="formData.managedutyunit" placeholder="请输入" maxlength="30" border="none"/>
  70 + </up-form-item>
  71 +
  72 + <up-form-item label="权属分类" prop="oldtreeownershipText" required arrow
  73 + @click="handleActionSheetOpen('ownership')">
  74 + <up-input v-model.trim="formData.oldtreeownershipText" placeholder="请选择" readonly border="none"
  75 + bg-color="transparent"/>
  76 + </up-form-item>
  77 +
  78 + <!-- ✅ 核心修复:补全name属性 + 所有配置齐全 -->
  79 + <up-form-item label="图片信息" prop="treeImgList" required>
  80 + <up-upload
  81 + name="treeImgList"
  82 + :file-list="treeImgList.imgList.value"
  83 + @after-read="treeImgList.uploadImgs"
  84 + @delete="treeImgList.deleteImg"
  85 + multiple
  86 + :width="70"
  87 + :height="70"
  88 + :max-count="treeImgList.uploadConfig.maxCount"
  89 + :upload-text="treeImgList.uploadConfig.uploadText"
  90 + :size-type="treeImgList.uploadConfig.sizeType"
  91 + ></up-upload>
  92 + </up-form-item>
  93 +
  94 + <view
  95 + class="animated-area"
  96 + :style="{
  97 + height: isShow ? contentHeight + 'px' : '0',
  98 + opacity: isShow ? 1 : 0,
  99 + overflow: 'hidden'
  100 + }"
  101 + >
  102 + <up-row gutter="10">
  103 + <up-col span="6">
  104 + <up-form-item label="拉丁文" prop="latinname">
  105 + <up-input v-model.trim="formData.latinname" placeholder="请输入" maxlength="30" border="none"/>
  106 + </up-form-item>
  107 + </up-col>
  108 + <up-col span="6">
  109 + <up-form-item label="级别" arrow @click="handleActionSheetOpen('level')">
  110 + <up-input v-model.trim="formData.treeleveltext" placeholder="请选择" readonly border="none"
  111 + bg-color="transparent"/>
  112 + </up-form-item>
  113 + </up-col>
  114 + </up-row>
  115 +
  116 + <up-form-item label="生长环境" prop="growthenvironment">
  117 + <up-input v-model.trim="formData.growthenvironment" placeholder="请输入" maxlength="50" border="none"/>
  118 + </up-form-item>
  119 +
  120 + <view class="form-row-wrap">
  121 + <up-row gutter="10">
  122 + <up-col span="6">
  123 + <up-form-item label="预估树龄" prop="estimationtreeage">
  124 + <up-input v-model.trim="formData.estimationtreeage" placeholder="请输入" maxlength="10" border="none"
  125 + input-align="left"/>
  126 + <template #right>
  127 + <text style="padding-left:12rpx;color:#ccc;font-size:14px">年</text>
  128 + </template>
  129 + </up-form-item>
  130 + </up-col>
  131 + <up-col span="6">
  132 + <up-form-item label="干周" prop="weekday">
  133 + <up-input v-model.trim="formData.weekday" placeholder="请输入" maxlength="10" border="none"
  134 + input-align="left"/>
  135 + <template #right>
  136 + <text style="padding-left:12rpx;color:#ccc;font-size:14px">厘米</text>
  137 + </template>
  138 + </up-form-item>
  139 + </up-col>
  140 + </up-row>
  141 + </view>
  142 +
  143 + <view class="form-row-wrap">
  144 + <up-row gutter="10">
  145 + <up-col span="6">
  146 + <up-form-item label="东西冠幅" prop="canopyeastwest">
  147 + <up-input v-model.trim="formData.canopyeastwest" placeholder="请输入" maxlength="10" border="none"
  148 + input-align="left"/>
  149 + <template #right>
  150 + <text style="padding-left:12rpx;color:#ccc;font-size:14px">米</text>
  151 + </template>
  152 + </up-form-item>
  153 + </up-col>
  154 + <up-col span="6">
  155 + <up-form-item label="南北冠幅" prop="canopysouthnorth">
  156 + <up-input v-model.trim="formData.canopysouthnorth" placeholder="请输入" maxlength="10" border="none"
  157 + input-align="left"/>
  158 + <template #right>
  159 + <text style="padding-left:12rpx;color:#ccc;font-size:14px">米</text>
  160 + </template>
  161 + </up-form-item>
  162 + </up-col>
  163 + </up-row>
36 </view> 164 </view>
37 </view> 165 </view>
38 - <!-- 这里可以继续加你的 编辑表单/其他基本信息 -->  
39 - </up-card> 166 +
  167 + <up-button
  168 + @click="toggleArea"
  169 + type="primary"
  170 + plain
  171 + size="large"
  172 + style="margin-top:20rpx"
  173 + >
  174 + {{ isShow ? '- 隐藏区域' : '+ 显示区域' }}
  175 + </up-button>
  176 + </up-form>
40 </view> 177 </view>
41 178
42 - <!-- ✅ Tab1 变更日志页面 - 和列表页布局完全一致 + 无新增按钮 + 点击卡片跳转详情 --> 179 + <!-- Tab1 变更日志页面 - 不变,保留原布局 -->
43 <view v-show="activeTab === 1" class="log-wrap"> 180 <view v-show="activeTab === 1" class="log-wrap">
44 - <up-empty v-if="logRows.length === 0" text="暂无变更日志"></up-empty> 181 + <up-empty v-if="logRows.length === 0" marginTop="100" text="暂无变更日志"></up-empty>
45 <view class="record-wrap" v-else> 182 <view class="record-wrap" v-else>
46 <up-card 183 <up-card
47 v-for="item in logRows" 184 v-for="item in logRows"
@@ -54,7 +191,6 @@ @@ -54,7 +191,6 @@
54 > 191 >
55 <template #body> 192 <template #body>
56 <view class="card-body-inner"> 193 <view class="card-body-inner">
57 - <!-- ✅ 保留你要求的背景图写法 -->  
58 <view class="record-list-left" :style="`background-image: url(${item.treephotoone});`"></view> 194 <view class="record-list-left" :style="`background-image: url(${item.treephotoone});`"></view>
59 <view class="record-list-right"> 195 <view class="record-list-right">
60 <view class="record-list-right-title"> 196 <view class="record-list-right-title">
@@ -62,10 +198,12 @@ @@ -62,10 +198,12 @@
62 <view style="text-align: right">{{ timeFormat(item.updatetime) }}</view> 198 <view style="text-align: right">{{ timeFormat(item.updatetime) }}</view>
63 </view> 199 </view>
64 <view class=" fs-align__center" style="margin: 5px 0"> 200 <view class=" fs-align__center" style="margin: 5px 0">
65 - <img src="../../../static/imgs/tree/tree-high.png" style="width: 14px;height: 14px;margin-right: 6px;" alt=""> 高度:{{ item.treeheight }} 米 201 + <img src="../../../static/imgs/tree/tree-high.png" style="width:14px;height:14px;margin-right:6px;"
  202 + alt=""> 高度:{{ item.treeheight }} 米
66 </view> 203 </view>
67 <view class=" fs-align__center"> 204 <view class=" fs-align__center">
68 - <img src="../../../static/imgs/tree/treearound.png" style="width: 14px;height: 14px;margin-right: 6px;" alt="">胸径:{{ item.dbh }} 厘米 205 + <img src="../../../static/imgs/tree/treearound.png" style="width:14px;height:14px;margin-right:6px;"
  206 + alt="">胸径:{{ item.dbh }} 厘米
69 </view> 207 </view>
70 </view> 208 </view>
71 </view> 209 </view>
@@ -77,130 +215,376 @@ @@ -77,130 +215,376 @@
77 </view> 215 </view>
78 </view> 216 </view>
79 217
80 - <!-- ✅ 新增按钮 只在【基本信息Tab】显示,变更日志Tab自动隐藏,无需手动处理 -->  
81 - <view class="fixed-bottom-btn-wrap">  
82 - <up-button  
83 - type="primary"  
84 - @click="toAddTreePage"  
85 - v-show="count > 0 && count > rows.length && activeTab === 0"  
86 - >  
87 - 新增树木录入  
88 - </up-button> 218 + <!-- 修改:底部按钮 新增改修改,只在基本信息Tab显示 -->
  219 + <view class="fixed-bottom-btn-wrap" v-show="activeTab === 0">
  220 + <up-button type="primary" @click="submit" :loading="loadingFlag" size="large" >修改树木信息</up-button>
89 </view> 221 </view>
90 222
  223 + <!-- 字典选择弹窗 -->
  224 + <up-action-sheet
  225 + :show="showActionSheet"
  226 + :actions="currentActionSheetData.list"
  227 + :title="currentActionSheetData.title"
  228 + @close="handleActionSheetClose"
  229 + @select="handleActionSheetSelect"
  230 + ></up-action-sheet>
  231 +
91 </view> 232 </view>
92 </template> 233 </template>
93 234
94 <script setup> 235 <script setup>
95 -import { ref, reactive } from 'vue' 236 +import { ref, reactive, nextTick } from 'vue'
96 import { onReady, onLoad, onShow, onUnload } from '@dcloudio/uni-app'; 237 import { onReady, onLoad, onShow, onUnload } from '@dcloudio/uni-app';
  238 +// 接口修改:删除addTree,新增 treeDetailReq + updateTree
  239 +import { treeRoadReq, treeLogReq, treeDetailReq, updateTree } from "@/api/tree-archive/tree-archive.js";
  240 +import { timeFormat } from '@/uni_modules/uview-plus';
  241 +import { useUploadImgs } from '@/common/utils/useUploadImgs'
  242 +import { useUserStore } from '@/pinia/user';
  243 +
  244 +// ========== 常量配置区 - 完全对标参考页写法 集中管理 ==========
  245 +const CONST = {
  246 + // 上传配置 统一抽离,和参考页一致
  247 + UPLOAD_CONFIG: { maxCount: 3, uploadText: '选择图片', sizeType: ['compressed'] }
  248 +}
  249 +
  250 +// ========== 页面关闭统一跳转 ==========
97 onUnload(() => { 251 onUnload(() => {
98 - // 关闭所有页面,直接打开【行道树档案】主页面 【微信小程序完美兼容,无任何报错】  
99 - uni.reLaunch({  
100 - url: '/pages-sub/data/tree-archive/index'  
101 - }) 252 + uni.reLaunch({url: '/pages-sub/data/tree-archive/index'})
102 }) 253 })
103 -import { treeRoadReq,treeLogReq } from "@/api/tree-archive/tree-archive.js";  
104 -import { timeFormat } from '@/uni_modules/uview-plus';  
105 254
106 // ========== 基础变量 ========== 255 // ========== 基础变量 ==========
107 const rows = ref([]) 256 const rows = ref([])
108 const roadId = ref('') 257 const roadId = ref('')
109 const count = ref(0) 258 const count = ref(0)
110 -const treeId = ref('') 259 +const treeId = ref('') // 树木主键ID 必传修改接口
  260 +const loadingFlag = ref(false)
  261 +const isInit = ref(false) // ✅ 新增:防止重复加载回显数据的核心标识
111 262
112 // ========== Tab切换核心变量 ========== 263 // ========== Tab切换核心变量 ==========
113 const activeTab = ref(0); // 0=基本信息 1=变更日志 264 const activeTab = ref(0); // 0=基本信息 1=变更日志
114 -const tabList = ref([  
115 - { name: '基本信息' },  
116 - { name: '变更日志' }  
117 -]); 265 +const tabList = ref([{name: '基本信息'}, {name: '变更日志'}]);
118 266
119 // ========== 数据变量 ========== 267 // ========== 数据变量 ==========
120 -const treeInfo = reactive({ // 基本信息-单棵树详情  
121 - treephoto: '', 268 +const logRows = ref([]) // 变更日志列表数据
  269 +const userStore = useUserStore();
  270 +const formRef = ref(null)
  271 +const isShow = ref(false)
  272 +const contentHeight = ref(200)
  273 +const treeOwnershipData = ref([])
  274 +const treeLevelData = ref([])
  275 +const showActionSheet = ref(false);
  276 +const currentActionSheetData = reactive({type: '', list: [], title: ''});
  277 +
  278 +// ========== 核心修复:图片上传配置 - 1:1对标工单页面 无任何冗余 ==========
  279 +const treeImgList = useUploadImgs({
  280 + ...CONST.UPLOAD_CONFIG,
  281 + formRef: formRef,
  282 + fieldName: 'treeImgList'
  283 +})
  284 +// ✅ 初始化图片数组为纯净空数组,杜绝残留数据
  285 +treeImgList.imgList.value = []
  286 +treeImgList.rawImgList.value = []
  287 +
  288 +// ========== 表单数据 ==========
  289 +const formData = reactive({
  290 + id: '', // 新增主键ID,提交修改必传
122 treetype: '', 291 treetype: '',
123 treeheight: '', 292 treeheight: '',
124 dbh: '', 293 dbh: '',
125 - treenumber: '',  
126 - updatetime: '' 294 + treelevel: '',
  295 + treeleveltext: '',
  296 + managedutyunit: '',
  297 + oldtreeownership: '',
  298 + oldtreeownershipText: '',
  299 + latinname: '',
  300 + estimationtreeage: '',
  301 + canopysouthnorth: '',
  302 + canopyeastwest: '',
  303 + weekday: '',
  304 + growlocation: '',
  305 + growthenvironment: '',
  306 + treeImgList: [],
  307 + address: '',
  308 + latitude: '',
  309 + longitude: '',
  310 + road: '',
  311 + maintainunit: '',
  312 + treephotoone: '',
  313 + treephototwo: '',
  314 + treephotothree: '',
  315 + treephotofour: '',
  316 + treephotofive: ''
  317 +})
  318 +
  319 +// ========== 表单校验规则【完整保留,无修改】 ==========
  320 +const isPureZero = (val) => {
  321 + if (!val) return false;
  322 + const pureZeroReg = /^0+$/;
  323 + return pureZeroReg.test(val.trim());
  324 +};
  325 +const trimFormData = (obj) => {
  326 + for (const key in obj) {
  327 + if (Object.prototype.hasOwnProperty.call(obj, key) && typeof obj[key] === 'string') {
  328 + obj[key] = obj[key].trim();
  329 + }
  330 + }
  331 +};
  332 +const emojiReg = /[\uD83C\uD83D\uD83E][\uDC00-\uDFFF]+|[\u2702\u2705\u2708-\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763-\u2767\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299\ufe0f\u200d]+/g;
  333 +const filterEmoji = (val) => typeof val === 'string' ? val.replace(emojiReg, '') : val;
  334 +const hasEmoji = (val) => val && emojiReg.test(val);
  335 +const rules = reactive({
  336 + treetype: [
  337 + {
  338 + validator: (rule, val, callback) => hasEmoji(val) ? callback(new Error('禁止输入表情符号')) : callback(),
  339 + trigger: ['blur', 'change', 'input'],
  340 + priority: 10
  341 + },
  342 + {required: true, message: '请输入名称', trigger: ['blur', 'change', 'input']},
  343 + {max: 30, message: '名称最多输入30个字符', trigger: ['blur', 'change', 'input']}
  344 + ],
  345 + treeheight: [
  346 + {max: 10, message: '树高不能超过10个字符', trigger: ['blur', 'change', 'input']},
  347 + {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur', 'change', 'input']},
  348 + {
  349 + validator: (rule, val, callback) => isPureZero(val) ? callback(new Error('不能输入纯0无效值')) : callback(),
  350 + trigger: ['blur', 'change', 'input']
  351 + }
  352 + ],
  353 + dbh: [
  354 + {
  355 + validator: (rule, val, callback) => hasEmoji(val) ? callback(new Error('禁止输入表情符号')) : callback(),
  356 + trigger: ['blur', 'change', 'input'],
  357 + priority: 10
  358 + },
  359 + {required: true, message: '请输入胸径', trigger: ['blur', 'change', 'input']},
  360 + {max: 10, message: '胸径不能超过10个字符', trigger: ['blur', 'change', 'input']},
  361 + {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur', 'change', 'input']},
  362 + {
  363 + validator: (rule, val, callback) => isPureZero(val) ? callback(new Error('不能输入纯0无效值')) : callback(),
  364 + trigger: ['blur', 'change', 'input']
  365 + }
  366 + ],
  367 + estimationtreeage: [
  368 + {max: 10, message: '预估树龄不能超过10个字符', trigger: ['blur', 'change', 'input']},
  369 + {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur', 'change', 'input']},
  370 + {
  371 + validator: (rule, val, callback) => isPureZero(val) ? callback(new Error('不能输入纯0无效值')) : callback(),
  372 + trigger: ['blur', 'change', 'input']
  373 + }
  374 + ],
  375 + weekday: [
  376 + {max: 10, message: '干周不能超过10个字符', trigger: ['blur', 'change', 'input']},
  377 + {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur', 'change', 'input']},
  378 + {
  379 + validator: (rule, val, callback) => isPureZero(val) ? callback(new Error('不能输入纯0无效值')) : callback(),
  380 + trigger: ['blur', 'change', 'input']
  381 + }
  382 + ],
  383 + canopyeastwest: [
  384 + {max: 10, message: '东西冠幅不能超过10个字符', trigger: ['blur', 'change', 'input']},
  385 + {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur', 'change', 'input']},
  386 + {
  387 + validator: (rule, val, callback) => isPureZero(val) ? callback(new Error('不能输入纯0无效值')) : callback(),
  388 + trigger: ['blur', 'change', 'input']
  389 + }
  390 + ],
  391 + canopysouthnorth: [
  392 + {max: 10, message: '南北冠幅不能超过10个字符', trigger: ['blur', 'change', 'input']},
  393 + {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur', 'change', 'input']},
  394 + {
  395 + validator: (rule, val, callback) => isPureZero(val) ? callback(new Error('不能输入纯0无效值')) : callback(),
  396 + trigger: ['blur', 'change', 'input']
  397 + }
  398 + ],
  399 + growlocation: [{required: true, message: '请选择地图位置', trigger: ['blur', 'change', 'manual']}],
  400 + managedutyunit: [
  401 + {
  402 + validator: (rule, val, callback) => hasEmoji(val) ? callback(new Error('禁止输入表情符号')) : callback(),
  403 + trigger: ['blur', 'change', 'input'],
  404 + priority: 10
  405 + },
  406 + {required: true, message: '请输入管护单位', trigger: ['blur', 'change', 'input']},
  407 + {max: 30, message: '管护单位最多输入30个字符', trigger: ['blur', 'change', 'input']}
  408 + ],
  409 + oldtreeownershipText: [{required: true, message: '请选择权属分类', trigger: ['blur', 'change']}],
  410 + latinname: [
  411 + {
  412 + validator: (rule, val, callback) => hasEmoji(val) ? callback(new Error('禁止输入表情符号')) : callback(),
  413 + trigger: ['blur', 'change', 'input'],
  414 + priority: 10
  415 + },
  416 + {max: 30, message: '拉丁文最多输入30个字符', trigger: ['blur', 'change', 'input']}
  417 + ],
  418 + growthenvironment: [
  419 + {
  420 + validator: (rule, val, callback) => hasEmoji(val) ? callback(new Error('禁止输入表情符号')) : callback(),
  421 + trigger: ['blur', 'change', 'input'],
  422 + priority: 10
  423 + },
  424 + {max: 50, message: '生长环境最多输入50个字符', trigger: ['blur', 'change', 'input']}
  425 + ],
  426 + treeImgList: [treeImgList.imgValidateRule]
127 }) 427 })
128 -const logRows = ref([]) // 变更日志列表数据  
129 428
130 // ========== 生命周期 ========== 429 // ========== 生命周期 ==========
131 onLoad((options) => { 430 onLoad((options) => {
132 console.log('编辑页入参', options) 431 console.log('编辑页入参', options)
133 roadId.value = options.roadId || '' 432 roadId.value = options.roadId || ''
134 count.value = options.count || 0 433 count.value = options.count || 0
135 - treeId.value = options.id || '' // 树木主键ID  
136 -})  
137 -  
138 -onShow(() => {  
139 - treeRoadQuery()  
140 - if(activeTab.value === 0){  
141 - // getTreeDetail() // 进入页面默认加载基本信息 434 + treeId.value = options.id || '' // 获取树木主键ID
  435 + formData.id = treeId.value // 赋值给表单,提交必传
  436 + formData.road = roadId.value // 赋值道路ID
  437 + isInit.value = false // 初始化标识为false
  438 +});
  439 +
  440 +onShow(async () => {
  441 + treeLevelData.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('tree_level'))
  442 + treeOwnershipData.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('tree_ownership'))
  443 + // ✅ 核心修复:只执行一次回显加载,彻底杜绝重复加载
  444 + if (activeTab.value === 0 && treeId.value && !isInit.value) {
  445 + await getTreeDetail()
  446 + isInit.value = true // 加载完成后标记为true,永不重复执行
142 } 447 }
143 }) 448 })
144 449
  450 +onReady(() => {
  451 + nextTick(() => formRef.value?.setRules(rules));
  452 +});
145 453
146 // ========== Tab切换事件 ========== 454 // ========== Tab切换事件 ==========
147 -const handleTabChange = (item) => { 455 +const handleTabChange = async (item) => {
148 activeTab.value = item.index 456 activeTab.value = item.index
149 - // 切换到哪个tab,加载对应的数据  
150 - if(activeTab.value === 0){  
151 - getTreeDetail() // 加载基本信息  
152 - }else if(activeTab.value === 1){  
153 - getTreeLogList() // 加载变更日志列表 457 + if (activeTab.value === 0) {
  458 + getTreeLogList()
  459 + } else if (activeTab.value === 1) {
  460 + getTreeLogList()
154 } 461 }
155 } 462 }
156 463
157 // ========== 接口请求 ========== 464 // ========== 接口请求 ==========
158 -// 树木列表查询  
159 const treeRoadQuery = async () => { 465 const treeRoadQuery = async () => {
160 - const res = await treeRoadReq( {road: roadId.value}) 466 + const res = await treeRoadReq({road: roadId.value})
161 rows.value = res.list 467 rows.value = res.list
162 } 468 }
163 469
164 -// // 获取单棵树的基本信息  
165 -// const getTreeDetail = async () => {  
166 -// const res = await treeDetailReq({ id: treeId.value })  
167 -// Object.assign(treeInfo, res)  
168 -// } 470 +// ========== ✅ 终极修复:图片回显逻辑 - 彻底解决重复渲染4张图的问题 ==========
  471 +const getTreeDetail = async () => {
  472 + const res = await treeDetailReq({id: treeId.value})
  473 + Object.assign(formData, res)
  474 + // 字典文本回显
  475 + formData.oldtreeownershipText = uni.$dict.getDictLabel('tree_ownership', res.oldtreeownership)
  476 + formData.treeleveltext = uni.$dict.getDictLabel('tree_level', formData.treelevel)
  477 +
  478 + // ✅ 第一步:强制清空为纯净空数组,杜绝任何残留
  479 + treeImgList.imgList.value = []
  480 + treeImgList.rawImgList.value = []
  481 + formData.treephotoone = ''
  482 + formData.treephototwo = ''
  483 + formData.treephotoone = ''
  484 + formData.treephotothree = ''
  485 + formData.treephotofour = ''
  486 + formData.treephotofive = ''
  487 +
  488 + // ✅ 第二步:接口返回几张就渲染几张,绝对不会重复
  489 + if (Array.isArray(res.treeImgList) && res.treeImgList.length > 0) {
  490 + const imgList = res.treeImgList.map((url, idx) => ({
  491 + url,
  492 + name: `renew_img_${idx}`,
  493 + status: 'success'
  494 + }));
  495 + // 直接赋值,不再追加,彻底解决重复问题
  496 + treeImgList.imgList.value = imgList
  497 + treeImgList.rawImgList.value = imgList
  498 + }
  499 +}
169 500
170 // 获取树木的变更日志列表 501 // 获取树木的变更日志列表
171 const getTreeLogList = async () => { 502 const getTreeLogList = async () => {
172 - const res = await treeLogReq({ treeid: treeId.value }) 503 + const res = await treeLogReq({treeid: treeId.value})
173 logRows.value = res.list 504 logRows.value = res.list
174 } 505 }
175 506
176 -// ========== 页面跳转 ==========  
177 -// 前往修改页面(原有)  
178 -const toEditPage = (id) => {  
179 - uni.navigateTo({  
180 - url: `/pages-sub/data/tree-archive/editTree?id=${id}&roadId=${roadId.value}`  
181 - }) 507 +// ========== 表单相关方法 ==========
  508 +const toggleArea = () => isShow.value = !isShow.value
  509 +
  510 +const handleActionSheetOpen = (type) => {
  511 + const configMap = {
  512 + ownership: {title: '请选择权属分类', list: treeOwnershipData.value},
  513 + level: {title: '请选择树木级别', list: treeLevelData.value}
  514 + };
  515 + Object.assign(currentActionSheetData, configMap[type], {type});
  516 + showActionSheet.value = true;
  517 +};
  518 +
  519 +const handleActionSheetClose = () => {
  520 + showActionSheet.value = false;
  521 + Object.assign(currentActionSheetData, {type: '', list: [], title: ''});
  522 +};
  523 +
  524 +const handleActionSheetSelect = (e) => {
  525 + const {type} = currentActionSheetData;
  526 + if (type === 'ownership') {
  527 + formData.oldtreeownership = e.value
  528 + formData.oldtreeownershipText = e.name
  529 + formRef.value?.validateField('oldtreeownershipText');
  530 + } else if (type === 'level') {
  531 + formData.treelevel = e.value
  532 + formData.treeleveltext = e.name
  533 + }
  534 + handleActionSheetClose();
  535 +};
  536 +
  537 +// 地图选址
  538 +const openMap = () => {
  539 + uni.chooseLocation({
  540 + success: async (res) => {
  541 + formData.growlocation = res.address
  542 + formData.latitude = res.latitude
  543 + formData.longitude = res.longitude
  544 + await nextTick()
  545 + formRef.value?.validateField('growlocation');
  546 + },
  547 + fail: (err) => {
  548 + console.error('地图选择失败', err);
  549 + err.errMsg.includes('auth deny') && uni.showToast({title: '请授权位置权限', icon: 'none'});
  550 + }
  551 + });
182 } 552 }
183 553
184 -// 前往新增页面(原有)  
185 -const toAddTreePage = () => {  
186 - uni.navigateTo({  
187 - url: `/pages-sub/data/tree-archive/addTree?roadId=${roadId.value}`  
188 - }) 554 +// ========== 核心修改:表单提交 ==========
  555 +const submit = async () => {
  556 + if (loadingFlag.value) return
  557 + // 过滤表情+去空格
  558 + for (const key in formData) {
  559 + if (typeof formData[key] === 'string') {
  560 + formData[key] = filterEmoji(formData[key]).trim();
  561 + }
  562 + }
  563 + // 表单校验
  564 + const valid = await formRef.value.validate()
  565 + if (!valid) return
  566 + // 对标参考页:图片取值 统一调用封装方法
  567 + const uploadImgUrls = treeImgList.getSuccessImgUrls()
  568 + formData.maintainunit = userStore.userInfo.user.companyId
  569 + formData.treeImgList = uploadImgUrls
  570 + loadingFlag.value = true
  571 + try {
  572 + await updateTree(formData)
  573 + uni.showToast({title: "修改成功", icon: "success"});
  574 + uni.redirectTo({url: '/pages-sub/data/tree-archive/index'});
  575 + } catch (err) {
  576 + uni.showToast({title: "修改失败,请重试", icon: "none"});
  577 + console.error(err)
  578 + } finally {
  579 + loadingFlag.value = false
  580 + }
189 } 581 }
190 582
191 -// ✅ 新增:点击变更日志卡片 前往日志详情页面 583 +// ========== 页面跳转 ==========
192 const toLogDetailPage = (i) => { 584 const toLogDetailPage = (i) => {
193 uni.navigateTo({ 585 uni.navigateTo({
194 - // url: `/pages-sub/data/tree-archive/logDetail?id=${logId}&treeId=${treeId.value}`  
195 url: `/pages-sub/data/tree-archive/logDetail`, 586 url: `/pages-sub/data/tree-archive/logDetail`,
196 - events: {  
197 - sendBackData: (data) => {  
198 - console.log('B页面回传的数据:', data);  
199 - }  
200 - },  
201 - // 2. 向B页面传递数据  
202 success: (res) => { 587 success: (res) => {
203 - // 通过 eventChannel 发送数据,key  
204 res.eventChannel.emit('logData', i); 588 res.eventChannel.emit('logData', i);
205 } 589 }
206 }) 590 })
@@ -210,29 +594,23 @@ const toLogDetailPage = (i) =&gt; { @@ -210,29 +594,23 @@ const toLogDetailPage = (i) =&gt; {
210 <style scoped lang="scss"> 594 <style scoped lang="scss">
211 .container { 595 .container {
212 min-height: 100vh; 596 min-height: 100vh;
  597 + padding-bottom: 100rpx;
213 } 598 }
214 599
215 -// ✅ 顶部tabs吸顶样式  
216 .header-wrap { 600 .header-wrap {
217 background-color: #fff; 601 background-color: #fff;
218 padding: 10rpx 0; 602 padding: 10rpx 0;
219 - box-shadow: 0 2rpx 4rpx rgba(0,0,0,0.03); 603 + box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.03);
220 } 604 }
221 605
222 -// 基本信息样式  
223 .base-info-wrap { 606 .base-info-wrap {
224 - padding: 20rpx 10rpx;  
225 -}  
226 -.base-card {  
227 - background: #fff;  
228 - border-radius: 6px;  
229 - padding: 10px; 607 + padding: 20rpx 15rpx;
230 } 608 }
231 609
232 -// 变更日志样式 和列表页完全一致  
233 .log-wrap { 610 .log-wrap {
234 min-height: calc(100vh - 120rpx); 611 min-height: calc(100vh - 120rpx);
235 } 612 }
  613 +
236 .record-wrap { 614 .record-wrap {
237 padding-bottom: 20px; 615 padding-bottom: 20px;
238 } 616 }
@@ -269,30 +647,48 @@ const toLogDetailPage = (i) =&gt; { @@ -269,30 +647,48 @@ const toLogDetailPage = (i) =&gt; {
269 margin-top: 8px; 647 margin-top: 8px;
270 } 648 }
271 649
272 -// up-card样式适配  
273 -.tree-card {  
274 - //margin: 15px 10px 0;  
275 - //padding: 10px;  
276 - //border-radius: 6px;  
277 - //font-size: 14px;  
278 - //box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);  
279 - //background-color: #ffffff;  
280 -}  
281 -.card-body-inner{ 650 +.card-body-inner {
282 display: flex; 651 display: flex;
283 } 652 }
284 653
  654 +.fs-align__center {
  655 + display: flex;
  656 + align-items: center;
  657 +}
285 658
286 -.addTree {  
287 - width: 100%;  
288 - position: fixed;  
289 - bottom: 0; 659 +// 表单样式
  660 +.animated-area {
  661 + transition: all 0.3s ease-out;
  662 + margin-top: 10rpx;
290 } 663 }
291 664
  665 +.location-form-item {
  666 + padding-right: 10rpx;
  667 +}
292 668
293 -.fs-align__center { 669 +.form-row-wrap {
  670 + width: 100%;
294 display: flex; 671 display: flex;
295 - align-items: center; 672 + flex-direction: column;
  673 +
  674 + :deep(.u-form-item) {
  675 + position: relative;
  676 + margin-bottom: 0 !important;
  677 + }
  678 +
  679 + :deep(.u-form-item__body__right__message ) {
  680 + position: absolute;
  681 + left: 0;
  682 + bottom: -20rpx;
  683 + line-height: 20rpx;
  684 + font-size: 22rpx;
  685 + color: #f56c6c;
  686 + width: 100%;
  687 + box-sizing: border-box;
  688 + }
296 } 689 }
297 690
  691 +.form-row-wrap + .u-form-item {
  692 + margin-top: 25rpx !important;
  693 +}
298 </style> 694 </style>
299 \ No newline at end of file 695 \ No newline at end of file
pages-sub/data/tree-archive/index.vue
@@ -57,7 +57,8 @@ @@ -57,7 +57,8 @@
57 <up-col span="9" class="cate-right"> 57 <up-col span="9" class="cate-right">
58 <scroll-view scroll-y class="right-scroll"> 58 <scroll-view scroll-y class="right-scroll">
59 <!-- 暂无数据 --> 59 <!-- 暂无数据 -->
60 - <empty-view v-if="!hasValidRoadId"></empty-view> 60 +<!-- <empty-view v-if="!hasValidRoadId" style="margin-top: 100px"></empty-view>-->
  61 + <up-empty v-if="!hasValidRoadId" marginTop="100" mode="list"></up-empty>
61 <!-- 道路数据列表 --> 62 <!-- 道路数据列表 -->
62 <view v-else class="road-list-wrap"> 63 <view v-else class="road-list-wrap">
63 <view 64 <view
@@ -67,10 +68,10 @@ @@ -67,10 +68,10 @@
67 @click="toNewPage(item.roadId, item.treeCount)" 68 @click="toNewPage(item.roadId, item.treeCount)"
68 > 69 >
69 <p class="fs-flex__between"> 70 <p class="fs-flex__between">
70 - <span class="up-line-1">{{ item.roadName }}</span>  
71 - <span class="treeCount">{{ item.treeCount }}棵</span> 71 + <span class="up-line-1" style="color: #333;font-size: 14px">{{ item.roadName }}</span>
  72 + <span class="treeCount" style="font-size: 14px">{{ item.treeCount }}棵</span>
72 </p> 73 </p>
73 - <p class="up-line-1 fs-my8">已录入行道树:{{ item.recordedCount }}棵</p> 74 + <p class="up-line-1 fs-my8" style="color: #333;font-size: 14px">已录入行道树:{{ item.recordedCount }}棵</p>
74 <p class="up-line-1 fs-my8" style="color: #999;font-size: 12px">起点:{{ item.startRemark }}</p> 75 <p class="up-line-1 fs-my8" style="color: #999;font-size: 12px">起点:{{ item.startRemark }}</p>
75 <p class="up-line-1" style="color: #999;font-size: 12px">终点:{{ item.endRemark }}</p> 76 <p class="up-line-1" style="color: #999;font-size: 12px">终点:{{ item.endRemark }}</p>
76 </view> 77 </view>
pages-sub/data/tree-archive/logDetail.vue
@@ -60,6 +60,7 @@ @@ -60,6 +60,7 @@
60 :height="70" 60 :height="70"
61 :maxCount="formatImgList(formData.treeImgList).length" 61 :maxCount="formatImgList(formData.treeImgList).length"
62 disabled 62 disabled
  63 + :deletable="false"
63 :preview-full-image="true" 64 :preview-full-image="true"
64 ></up-upload> 65 ></up-upload>
65 </up-form-item> 66 </up-form-item>