editLand.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. <template>
  2. <view class="page-container bg-[#f5f7fa] min-h-screen pb-[150rpx]">
  3. <!-- 基本信息区域 -->
  4. <view class="form-card bg-white rounded-[16rpx] mx-[20rpx] mb-[20rpx] p-[30rpx]">
  5. <view class="section-title text-[36rpx] font-bold mb-[30rpx]">基本信息</view>
  6. <view class="form-item">
  7. <view class="item-label">变更理由<text class="required text-red-500">*</text></view>
  8. <picker :range="dictData.bgly" :range-key="'label'" :value="bglyIndex" @change="handleBglyChange">
  9. <view class="item-picker">
  10. {{ getDictLabel('bgly', editForm.changeReason) || '请选择' }}
  11. <text class="picker-arrow">▼</text>
  12. </view>
  13. </picker>
  14. </view>
  15. <view class="form-item">
  16. <view class="item-label">地块名称<text class="required text-red-500">*</text></view>
  17. <input class="item-input" v-model="editForm.dkmc" placeholder="请输入" />
  18. </view>
  19. <view class="form-item">
  20. <view class="item-label">确权面积(合同)<text class="required text-red-500">*</text></view>
  21. <input class="item-input" v-model="editForm.htmjm" placeholder="请输入" type="digit" />
  22. <view class="unit text-[32rpx] text-[#333] ml-[10rpx]">亩</view>
  23. </view>
  24. <view class="form-item">
  25. <view class="item-label">所有权性质<text class="required text-red-500">*</text></view>
  26. <picker :range="dictData.syqxz" :range-key="'label'" :value="syqxzIndex" @change="handleSyqxzChange">
  27. <view class="item-picker">
  28. {{ getDictLabel('syqxz', editForm.syqxz) || '请选择' }}
  29. <text class="picker-arrow">▼</text>
  30. </view>
  31. </picker>
  32. </view>
  33. <view class="form-item">
  34. <view class="item-label">地块类别<text class="required text-red-500">*</text></view>
  35. <picker :range="dictData.dklb" :range-key="'label'" :value="dklbIndex" @change="handleDklbChange">
  36. <view class="item-picker">
  37. {{ getDictLabel('dklb', editForm.dklb) || '请选择' }}
  38. <text class="picker-arrow">▼</text>
  39. </view>
  40. </picker>
  41. </view>
  42. <view class="form-item">
  43. <view class="item-label">地力等级<text class="required text-red-500">*</text></view>
  44. <picker :range="dictData.dldj" :range-key="'label'" :value="dldjIndex" @change="handleDldjChange">
  45. <view class="item-picker">
  46. {{ getDictLabel('dldj', editForm.dldj) || '请选择' }}
  47. <text class="picker-arrow">▼</text>
  48. </view>
  49. </picker>
  50. </view>
  51. <view class="form-item">
  52. <view class="item-label">土地用途<text class="required text-red-500">*</text></view>
  53. <picker :range="dictData.tdyt" :range-key="'label'" :value="tdytIndex" @change="handleTdytChange">
  54. <view class="item-picker">
  55. {{ getDictLabel('tdyt', editForm.tdyt) || '请选择' }}
  56. <text class="picker-arrow">▼</text>
  57. </view>
  58. </picker>
  59. </view>
  60. <view class="form-item">
  61. <view class="item-label">基本农田<text class="required text-red-500">*</text></view>
  62. <picker :range="dictData.jbnt" :range-key="'label'" :value="jbntIndex" @change="handleJbntChange">
  63. <view class="item-picker">
  64. {{ getDictLabel('jbnt', editForm.sfjbnt) || '请选择' }}
  65. <text class="picker-arrow">▼</text>
  66. </view>
  67. </picker>
  68. </view>
  69. <view class="form-item">
  70. <view class="item-label">地块东至<text class="required text-red-500">*</text></view>
  71. <input class="item-input" v-model="editForm.dkdz" placeholder="请输入" />
  72. </view>
  73. <view class="form-item">
  74. <view class="item-label">地块西至<text class="required text-red-500">*</text></view>
  75. <input class="item-input" v-model="editForm.dkxz" placeholder="请输入" />
  76. </view>
  77. <view class="form-item">
  78. <view class="item-label">地块南至<text class="required text-red-500">*</text></view>
  79. <input class="item-input" v-model="editForm.dknz" placeholder="请输入" />
  80. </view>
  81. <view class="form-item">
  82. <view class="item-label">地块北至<text class="required text-red-500">*</text></view>
  83. <input class="item-input" v-model="editForm.dkbz" placeholder="请输入" />
  84. </view>
  85. <view class="form-item-textarea mt-[20rpx]">
  86. <view class="item-label">备注信息</view>
  87. <textarea class="item-textarea" v-model="editForm.remark" placeholder="请输入"></textarea>
  88. </view>
  89. </view>
  90. <!-- 现场指界 -->
  91. <view class="form-card bg-white rounded-[16rpx] mx-[20rpx] mb-[20rpx] p-[30rpx]">
  92. <view class="section-title text-[36rpx] font-bold mb-[30rpx]">现场指界</view>
  93. <view class="upload-wrap">
  94. <view class="upload-box" v-for="(img, idx) in imageList.xczj.img" :key="idx">
  95. <image :src="img" class="upload-img" mode="aspectFill" />
  96. <view class="del-btn" @click.stop="delImg('xczj', idx)">×</view>
  97. </view>
  98. <view class="upload-box add-box" @click="handleUploadImage('xczj')">
  99. <view class="upload-plus">+</view>
  100. </view>
  101. </view>
  102. </view>
  103. <!-- 附件资料 -->
  104. <view class="form-card bg-white rounded-[16rpx] mx-[20rpx] mb-[20rpx] p-[30rpx]">
  105. <view class="section-title text-[36rpx] font-bold mb-[30rpx]">附件资料</view>
  106. <view class="upload-wrap">
  107. <view class="upload-box" v-for="(img, idx) in imageList.fjzl.img" :key="idx">
  108. <image :src="img" class="upload-img" mode="aspectFill" />
  109. <view class="del-btn" @click.stop="delImg('fjzl', idx)">×</view>
  110. </view>
  111. <view class="upload-box add-box" @click="handleUploadImage('fjzl')">
  112. <view class="upload-plus">+</view>
  113. </view>
  114. </view>
  115. </view>
  116. <!-- 底部修改按钮 -->
  117. <view class="submit-btn" @click="handleSubmit">保存修改</view>
  118. </view>
  119. </template>
  120. <script setup>
  121. import { ref, computed } from 'vue'
  122. import { onLoad, onShow } from '@dcloudio/uni-app';
  123. import { getDictData, deletefMaterials, updateLandInfo } from '@/api/farmerApi.js';
  124. import { uploadFile } from '@/utils/upload.js';
  125. import { imgBaseURL } from '@/utils/request.js';
  126. // 数据
  127. const rowData = ref({})
  128. const zsData = ref({})
  129. const addCbfbm = ref('')
  130. const fbfbmcd = ref('')
  131. const coordinateData = ref([])
  132. // 字典
  133. const dictData = ref({
  134. bgly: [],
  135. syqxz: [],
  136. dklb: [],
  137. dldj: [],
  138. tdyt: [],
  139. jbnt: []
  140. })
  141. // 表单
  142. const editForm = ref({
  143. id: '',
  144. bsm: '',
  145. changeReason: '',
  146. dkmc: '',
  147. htmjm: '',
  148. syqxz: '',
  149. dklb: '',
  150. dldj: '',
  151. tdyt: '',
  152. sfjbnt: '',
  153. dkdz: '',
  154. dkxz: '',
  155. dknz: '',
  156. dkbz: '',
  157. remark: '',
  158. tdlylx: ''
  159. })
  160. // 图片
  161. const imageList = ref({
  162. xczj: { img: [], number: [] },
  163. fjzl: { img: [], number: [] }
  164. })
  165. // 字典工具
  166. const transformDictFormat = (dict) => {
  167. if (!dict || typeof dict !== 'object') return []
  168. return Object.entries(dict).map(([k, v]) => ({ value: String(k), label: v }))
  169. }
  170. const getDictLabel = (type, val) => {
  171. if (!val || !dictData.value[type]?.length) return ''
  172. const item = dictData.value[type].find(i => String(i.value) === String(val))
  173. return item?.label || ''
  174. }
  175. // 选择器索引
  176. const bglyIndex = computed(() => dictData.value.bgly.findIndex(i => i.value === editForm.value.changeReason) || 0)
  177. const syqxzIndex = computed(() => dictData.value.syqxz.findIndex(i => i.value === editForm.value.syqxz) || 0)
  178. const dklbIndex = computed(() => dictData.value.dklb.findIndex(i => i.value === editForm.value.dklb) || 0)
  179. const dldjIndex = computed(() => dictData.value.dldj.findIndex(i => i.value === editForm.value.dldj) || 0)
  180. const tdytIndex = computed(() => dictData.value.tdyt.findIndex(i => i.value === editForm.value.tdyt) || 0)
  181. const jbntIndex = computed(() => dictData.value.jbnt.findIndex(i => i.value === editForm.value.sfjbnt) || 0)
  182. // 选择器事件
  183. const handleBglyChange = (e) => editForm.value.changeReason = dictData.value.bgly[e.detail.value]?.value
  184. const handleSyqxzChange = (e) => editForm.value.syqxz = dictData.value.syqxz[e.detail.value]?.value
  185. const handleDklbChange = (e) => editForm.value.dklb = dictData.value.dklb[e.detail.value]?.value
  186. const handleDldjChange = (e) => editForm.value.dldj = dictData.value.dldj[e.detail.value]?.value
  187. const handleTdytChange = (e) => editForm.value.tdyt = dictData.value.tdyt[e.detail.value]?.value
  188. const handleJbntChange = (e) => editForm.value.sfjbnt = dictData.value.jbnt[e.detail.value]?.value
  189. // 加载字典
  190. const loadDictData = async () => {
  191. try {
  192. const [bgly, syqxz, dklb, dldj, tdyt, jbnt] = await Promise.all([
  193. getDictData('dic_bgly'),
  194. getDictData('dic_syqxz'),
  195. getDictData('dic_dklb'),
  196. getDictData('dic_dldj'),
  197. getDictData('dic_tdyt'),
  198. getDictData('dic_sf')
  199. ])
  200. dictData.value.bgly = transformDictFormat(bgly.data)
  201. dictData.value.syqxz = transformDictFormat(syqxz.data)
  202. dictData.value.dklb = transformDictFormat(dklb.data)
  203. dictData.value.dldj = transformDictFormat(dldj.data)
  204. dictData.value.tdyt = transformDictFormat(tdyt.data)
  205. dictData.value.jbnt = transformDictFormat(jbnt.data)
  206. } catch (err) {
  207. uni.showToast({ title: '字典加载失败', icon: 'none' })
  208. }
  209. }
  210. // 初始化编辑数据
  211. const initEditData = () => {
  212. if (!rowData.value?.dkDetail) return
  213. editForm.value = { ...editForm.value, ...rowData.value.dkDetail }
  214. editForm.value.htmjm = rowData.value.htmjm || ''
  215. }
  216. // 上传图片
  217. const handleUploadImage = async (type) => {
  218. try {
  219. const { tempFilePaths } = await uni.chooseImage({ count: 1, sizeType: ['compressed'] })
  220. uni.showLoading({ title: '上传中...' })
  221. const res = await uploadFile(tempFilePaths[0])
  222. if (res.code === 200 && res.data?.id) {
  223. imageList.value[type].img.push(tempFilePaths[0])
  224. imageList.value[type].number.push(res.data.id)
  225. uni.showToast({ title: '上传成功', icon: 'success' })
  226. }
  227. } catch (e) {
  228. uni.showToast({ title: '上传失败', icon: 'none' })
  229. } finally {
  230. uni.hideLoading()
  231. }
  232. }
  233. // 删除图片
  234. const delImg = async (type, index) => {
  235. const id = imageList.value[type].number[index]
  236. if (id) await deletefMaterials(id).catch(() => {})
  237. imageList.value[type].img.splice(index, 1)
  238. imageList.value[type].number.splice(index, 1)
  239. uni.showToast({ title: '已删除' })
  240. }
  241. // 地图选择
  242. const handleChooseRange = () => {
  243. uni.navigateTo({ url: `/pages/map/map?memberInfo=${encodeURIComponent(addCbfbm.value)}` })
  244. }
  245. // 表单校验
  246. const validateForm = () => {
  247. const { changeReason, dkmc, syqxz, dklb, dldj, tdyt, sfjbnt, dkdz, dkxz, dknz, dkbz } = editForm.value
  248. if (!changeReason) return uni.showToast({ title: '请选择变更理由', icon: 'none' })
  249. if (!dkmc) return uni.showToast({ title: '请输入地块名称', icon: 'none' })
  250. if (!syqxz) return uni.showToast({ title: '请选择所有权性质', icon: 'none' })
  251. if (!dklb) return uni.showToast({ title: '请选择地块类别', icon: 'none' })
  252. if (!dldj) return uni.showToast({ title: '请选择地力等级', icon: 'none' })
  253. if (!tdyt) return uni.showToast({ title: '请选择土地用途', icon: 'none' })
  254. if (!sfjbnt) return uni.showToast({ title: '请选择基本农田', icon: 'none' })
  255. if (!dkdz) return uni.showToast({ title: '请输入东至', icon: 'none' })
  256. if (!dkxz) return uni.showToast({ title: '请输入西至', icon: 'none' })
  257. if (!dknz) return uni.showToast({ title: '请输入南至', icon: 'none' })
  258. if (!dkbz) return uni.showToast({ title: '请输入北至', icon: 'none' })
  259. return true
  260. }
  261. // 提交
  262. const handleSubmit = async () => {
  263. if (!validateForm()) return
  264. const dkTemp = {
  265. auditOpinion: '',
  266. bizStatus: '',
  267. bsm: editForm.value.bsm || '',
  268. changeReason: editForm.value.changeReason,
  269. dkbm: zsData.value.dkbm || '',
  270. dkbz: editForm.value.dkbz,
  271. remark: editForm.value.remark || '',
  272. dkdz: editForm.value.dkdz,
  273. dklb: editForm.value.dklb,
  274. dkmc: editForm.value.dkmc,
  275. dknz: editForm.value.dknz,
  276. dkxz: editForm.value.dkxz,
  277. dldj: editForm.value.dldj,
  278. kjzb: '',
  279. scmj: '',
  280. sfjbnt: editForm.value.sfjbnt,
  281. status: '',
  282. syqxz: editForm.value.syqxz,
  283. tdlylx: editForm.value.tdlylx || '',
  284. tdyt: editForm.value.tdyt,
  285. ysdm: '',
  286. zjrxm: ''
  287. }
  288. const submitData = {
  289. dkTemp,
  290. attachmentxczjFileIds: imageList.value.xczj.number,
  291. attachmentfjzlFileIds: imageList.value.fjzl.number,
  292. geoJson: coordinateData.value,
  293. cbdkxxTemp: {
  294. cbfbm: addCbfbm.value,
  295. fbfbm: fbfbmcd.value,
  296. htmj: '',
  297. htmjm: editForm.value.htmjm
  298. }
  299. }
  300. uni.showLoading({ title: '提交中...' })
  301. try {
  302. const res = await updateLandInfo(submitData)
  303. if (res.code === 200) {
  304. uni.showToast({ title: '保存成功', icon: 'success' })
  305. setTimeout(() => uni.navigateBack(), 1500)
  306. } else {
  307. uni.showToast({ title: res.msg || '保存失败', icon: 'none' })
  308. }
  309. } catch (e) {
  310. uni.showToast({ title: '保存失败', icon: 'none' })
  311. } finally {
  312. uni.hideLoading()
  313. }
  314. }
  315. // 页面生命周期
  316. onShow(() => {
  317. const pages = getCurrentPages()
  318. const current = pages[pages.length - 1]
  319. if (current.$vm.selectedLandData) {
  320. coordinateData.value = current.$vm.selectedLandData.points
  321. current.$vm.selectedLandData = null
  322. }
  323. })
  324. onLoad(async (options) => {
  325. addCbfbm.value = options.cbfbm || ''
  326. fbfbmcd.value = options.cbfbm?.substring(0, 14) || ''
  327. if (options.info) {
  328. try {
  329. const data = JSON.parse(decodeURIComponent(options.info))
  330. zsData.value = data
  331. rowData.value = data
  332. // 图片回显(修复空对象判断)
  333. const att = data.attachments || {}
  334. if (att?.xczj?.length) {
  335. att.xczj.forEach(item => {
  336. imageList.value.xczj.img.push(imgBaseURL + item.filePath)
  337. imageList.value.xczj.number.push(String(item.id))
  338. })
  339. }
  340. if (att?.fjzl?.length) {
  341. att.fjzl.forEach(item => {
  342. imageList.value.fjzl.img.push(imgBaseURL + item.filePath)
  343. imageList.value.fjzl.number.push(String(item.id))
  344. })
  345. }
  346. } catch (e) {}
  347. }
  348. await loadDictData()
  349. initEditData()
  350. })
  351. </script>
  352. <style lang="scss" scoped>
  353. page {
  354. background-color: #f5f7fa;
  355. }
  356. .form-card {
  357. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
  358. }
  359. .section-title {
  360. color: #333;
  361. }
  362. .form-item {
  363. display: flex;
  364. align-items: center;
  365. justify-content: space-between;
  366. padding: 30rpx 0;
  367. border-bottom: 1rpx solid #f0f0f0;
  368. .item-label {
  369. font-size: 32rpx;
  370. color: #333;
  371. flex-shrink: 0;
  372. }
  373. .item-input {
  374. flex: 1;
  375. text-align: right;
  376. font-size: 32rpx;
  377. color: #333;
  378. height: 40rpx;
  379. line-height: 40rpx;
  380. }
  381. .item-picker {
  382. flex: 1;
  383. display: flex;
  384. align-items: center;
  385. justify-content: flex-end;
  386. font-size: 32rpx;
  387. color: #333;
  388. height: 40rpx;
  389. line-height: 40rpx;
  390. }
  391. .picker-arrow {
  392. margin-left: 10rpx;
  393. font-size: 28rpx;
  394. color: #333;
  395. }
  396. }
  397. .form-item-textarea {
  398. .item-label {
  399. font-size: 32rpx;
  400. color: #333;
  401. margin-bottom: 20rpx;
  402. }
  403. .item-textarea {
  404. width: 100%;
  405. min-height: 200rpx;
  406. border: 1rpx solid #e5e5e5;
  407. border-radius: 12rpx;
  408. padding: 20rpx;
  409. font-size: 32rpx;
  410. color: #333;
  411. box-sizing: border-box;
  412. }
  413. }
  414. .upload-wrap {
  415. display: flex;
  416. flex-wrap: wrap;
  417. gap: 20rpx;
  418. align-items: center;
  419. }
  420. .upload-box {
  421. width: 280rpx;
  422. height: 280rpx;
  423. background-color: #f5f5f5;
  424. border-radius: 8rpx;
  425. display: flex;
  426. align-items: center;
  427. justify-content: center;
  428. overflow: hidden;
  429. position: relative;
  430. }
  431. .upload-plus {
  432. font-size: 80rpx;
  433. color: #333;
  434. font-weight: 300;
  435. }
  436. .upload-img {
  437. width: 100%;
  438. height: 100%;
  439. }
  440. .add-box {
  441. border: 2rpx dashed #ccc;
  442. }
  443. .del-btn {
  444. position: absolute;
  445. top: 0;
  446. right: 0;
  447. width: 50rpx;
  448. height: 50rpx;
  449. background: rgba(255, 0, 0, 0.7);
  450. color: #fff;
  451. font-size: 30rpx;
  452. display: flex;
  453. align-items: center;
  454. justify-content: center;
  455. }
  456. .submit-btn {
  457. width: 80%;
  458. height: 90rpx;
  459. background-color: #007aff;
  460. color: #fff;
  461. font-size: 36rpx;
  462. font-weight: 500;
  463. border-radius: 45rpx;
  464. display: flex;
  465. align-items: center;
  466. justify-content: center;
  467. margin: 0 auto;
  468. box-shadow: 0 4rpx 12rpx rgba(0, 122, 255, 0.3);
  469. }
  470. </style>