租房小程序前端代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

608 lines
19 KiB

  1. <template>
  2. <view class="container">
  3. <view class="header">
  4. <view class="title">经营性建设用地录入</view>
  5. </view>
  6. <view class="form-container">
  7. <uv-form labelPosition="left" :model="form" :rules="rules" ref="form" labelWidth="80" labelStyle="font-size:28rpx;">
  8. <!-- 必填项 -->
  9. <view class="section-title">基本信息必填</view>
  10. <!-- 1. 出租/出让 -->
  11. <uv-form-item label="类型" prop="type" required>
  12. <uv-radio-group v-model="form.type" placement="row" @change="debounceSaveFormData">
  13. <uv-radio name="rent" label="出租"></uv-radio>
  14. <uv-radio name="transfer" customStyle="margin-left:30rpx;" label="出让"></uv-radio>
  15. </uv-radio-group>
  16. </uv-form-item>
  17. <!-- 2. 地址 -->
  18. <uv-form-item label="地址" prop="address" required @click="handleAddressSelect()">
  19. <uv-input v-model="form.address" @click="handleAddressSelect()" disabled disabledColor="#ffffff" placeholder="请选择地块位置" border="none">
  20. </uv-input>
  21. <template v-slot:right>
  22. <uv-icon name="arrow-right"></uv-icon>
  23. </template>
  24. </uv-form-item>
  25. <!-- 3. 联系人和电话 -->
  26. <uv-form-item label="联系人" prop="contactName" required>
  27. <uv-input v-model="form.contactName" type="text" placeholder="请输入联系人姓名" customStyle="border-radius: 5px;margin-top:5px;background-color: #f5f5f5;padding:5px 10px;" @input="debounceSaveFormData"></uv-input>
  28. </uv-form-item>
  29. <uv-form-item label="联系电话" prop="contactPhone" required>
  30. <uv-input v-model="form.contactPhone" type="number" placeholder="请输入联系电话" customStyle="border-radius: 5px;margin-top:5px;background-color: #f5f5f5;padding:5px 10px;" @input="debounceSaveFormData"></uv-input>
  31. </uv-form-item>
  32. <!-- 4. 产权证照片 -->
  33. <uv-form-item label="产权证照片" labelWidth="250" prop="propertyImages" labelPosition="top" required>
  34. <view class="upload-tip">请上传房产证土地证或不动产权证照片</view>
  35. <uv-upload customStyle="margin-top:20rpx;" :fileList="form.propertyImages" @afterRead="afterPropertyRead" @delete="deletePropertyPic" name="1"
  36. multiple :maxCount="5"></uv-upload>
  37. </uv-form-item>
  38. <!-- 5. 土地面积 -->
  39. <uv-form-item label="土地面积" prop="landArea" required>
  40. <view class="input-with-unit">
  41. <uv-input v-model="form.landArea" type="digit" placeholder="请输入土地面积" customStyle="border-radius: 5px;margin-top:5px;background-color: #f5f5f5;padding:5px 10px;" @input="debounceSaveFormData"></uv-input>
  42. <text class="unit"></text>
  43. </view>
  44. <view class="input-tip">请录入土地使用证或不动产权证面积仅输入数字</view>
  45. </uv-form-item>
  46. <!-- 6. 地块视频 -->
  47. <uv-form-item label="地块视频" labelWidth="250" prop="videos" labelPosition="top" required>
  48. <view class="upload-tip">请上传地块视频时长不超过1分钟最多上传2个视频</view>
  49. <uv-upload customStyle="margin-top:20rpx;" accept="video" :fileList="form.videos" @afterRead="afterVideoRead" @delete="deleteVideoPic" name="1"
  50. multiple :maxCount="2"></uv-upload>
  51. </uv-form-item>
  52. <!-- 7. 价格 -->
  53. <uv-form-item label="价格" prop="price" required>
  54. <view class="price-container">
  55. <uv-input v-model="form.price" type="digit" placeholder="请输入价格" customStyle="border-radius: 5px;margin-top:5px;background-color: #f5f5f5;padding:5px 10px;flex:1;" @input="debounceSaveFormData"></uv-input>
  56. <text class="price-unit" v-if="form.type === 'rent'">//</text>
  57. <text class="price-unit" v-else-if="form.type === 'transfer'">万元/</text>
  58. <text class="price-unit" v-else></text>
  59. </view>
  60. <view class="input-tip">请输入数字支持小数点后两位</view>
  61. </uv-form-item>
  62. <!-- 8. 地块图片 -->
  63. <uv-form-item label="地块图片" labelWidth="250" prop="images" labelPosition="top" required>
  64. <view class="upload-tip">请上传地块现状照片展示地块实际情况</view>
  65. <uv-upload customStyle="margin-top:20rpx;" :fileList="form.images" @afterRead="afterImageRead" @delete="deleteImagePic" name="1"
  66. multiple :maxCount="10"></uv-upload>
  67. </uv-form-item>
  68. <!-- 分类标识 -->
  69. <uv-form-item label="分类标识" prop="classId" @click="handleClass()">
  70. <uv-input v-model="form.className" @click="handleClass()" disabled disabledColor="#ffffff" placeholder="选择分类标识" border="none">
  71. </uv-input>
  72. <template v-slot:right>
  73. <uv-icon name="arrow-right"></uv-icon>
  74. </template>
  75. </uv-form-item>
  76. <!-- 选填项 -->
  77. <view class="section-title">详细信息选填</view>
  78. <!-- 8. 地块介绍 -->
  79. <uv-form-item label="地块介绍" prop="description" labelPosition="top">
  80. <uv-input
  81. v-model="form.description"
  82. type="textarea"
  83. placeholder="请详细介绍地块情况、用途规划、周边环境等"
  84. customStyle="border-radius: 5px;margin-top:5px;background-color: #f5f5f5;padding:10px;"
  85. :autoHeight="true"
  86. :maxlength="500"
  87. @input="debounceSaveFormData"
  88. ></uv-input>
  89. </uv-form-item>
  90. <uv-form-item>
  91. <uv-button type="primary" text="提交信息" customStyle="margin-top: 30px;background-color: #1EC77A;border-radius: 30px;" @click="submit"></uv-button>
  92. </uv-form-item>
  93. </uv-form>
  94. </view>
  95. <!-- 分类选择器 -->
  96. <uv-picker ref="picker" :columns="columns" @confirm="confirm"></uv-picker>
  97. </view>
  98. </template>
  99. <script>
  100. import { saveOrUpdateHouse, houseType } from "@/common/api.js"
  101. import formStorage from "@/utils/formStorage.js"
  102. export default {
  103. data() {
  104. return {
  105. formKey: 'commercial_form', // 表单存储唯一标识
  106. saveTimer: null, // 防抖定时器
  107. commonClass: '', // 所属分类ID
  108. form: {
  109. type: '', // 出租/出让
  110. address: '', // 地址
  111. contactName: '', // 联系人
  112. contactPhone: '', // 联系电话
  113. propertyImages: [], // 产权证照片
  114. landArea: '', // 土地面积
  115. videos: [], // 地块视频
  116. price: '', // 价格
  117. images: [], // 地块图片
  118. description: '', // 地块介绍
  119. longitude: '', // 经度
  120. latitude: '', // 纬度
  121. classId: '', // 分类标识
  122. className: '' // 分类名称
  123. },
  124. columns: [], // 分类选择器数据
  125. houseTypeList: [], // 分类列表
  126. rules: {
  127. type: [
  128. { required: true, message: '请选择出租或出让', trigger: ['blur', 'change'] }
  129. ],
  130. address: [
  131. { required: true, message: '请选择地块地址', trigger: ['blur', 'change'] }
  132. ],
  133. contactName: [
  134. { required: true, message: '请输入联系人姓名', trigger: ['blur', 'change'] }
  135. ],
  136. contactPhone: [
  137. { required: true, message: '请输入联系电话', trigger: ['blur', 'change'] },
  138. { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: ['blur', 'change'] }
  139. ],
  140. landArea: [
  141. { required: true, message: '请输入土地面积', trigger: ['blur', 'change'] },
  142. { pattern: /^\d+(\.\d{1,2})?$/, message: '请输入正确的数字格式', trigger: ['blur', 'change'] }
  143. ],
  144. price: [
  145. { required: true, message: '请输入价格', trigger: ['blur', 'change'] },
  146. { pattern: /^\d+(\.\d{1,2})?$/, message: '请输入正确的价格格式', trigger: ['blur', 'change'] }
  147. ],
  148. images: [
  149. {
  150. required: true,
  151. message: '请上传地块图片',
  152. trigger: ['blur', 'change'],
  153. validator: (rule, value, callback) => {
  154. if (!value || value.length === 0) {
  155. callback(new Error('请上传地块图片'));
  156. } else {
  157. callback();
  158. }
  159. }
  160. }
  161. ],
  162. classId: [
  163. { required: true, message: '请选择分类标识', trigger: ['blur', 'change'] }
  164. ]
  165. }
  166. }
  167. },
  168. onReady() {
  169. this.$refs.form.setRules(this.rules)
  170. },
  171. onLoad(options) {
  172. // 设置页面标题
  173. uni.setNavigationBarTitle({
  174. title: '经营性建设用地录入'
  175. })
  176. // 接收传递的commonClass参数
  177. if(options.commonClass) {
  178. this.commonClass = options.commonClass
  179. }
  180. // 如果是编辑模式,设置classId用于回显
  181. if(options.classId) {
  182. this.form.classId = options.classId
  183. }
  184. // 初始化分类数据
  185. this.onHouseType()
  186. // 恢复表单数据
  187. this.restoreFormData()
  188. },
  189. onUnload() {
  190. // 页面卸载时保存数据
  191. this.saveFormData()
  192. // 清除定时器
  193. if (this.saveTimer) {
  194. clearTimeout(this.saveTimer)
  195. }
  196. },
  197. methods: {
  198. // 恢复表单数据
  199. restoreFormData() {
  200. const savedData = formStorage.getFormData(this.formKey)
  201. if (savedData) {
  202. // 合并保存的数据到表单
  203. this.form = { ...this.form, ...savedData }
  204. }
  205. },
  206. // 保存表单数据
  207. saveFormData() {
  208. formStorage.saveFormData(this.formKey, this.form)
  209. },
  210. // 防抖保存表单数据
  211. debounceSaveFormData() {
  212. clearTimeout(this.saveTimer)
  213. this.saveTimer = setTimeout(() => {
  214. this.saveFormData()
  215. }, 1000)
  216. },
  217. // 获取分类数据
  218. onHouseType(){
  219. let that = this
  220. houseType({}).then(response=>{
  221. let arr=[]
  222. that.houseTypeList = response.result
  223. response.result.forEach(items=>{
  224. arr.push(items.title)
  225. // 数据回显:如果当前classId匹配,设置className
  226. if(items.id == that.form.classId){
  227. that.form.className = items.title
  228. }
  229. })
  230. that.columns[0]=arr
  231. }).catch(error=>{
  232. })
  233. },
  234. // 地址选择
  235. handleAddressSelect() {
  236. const that = this;
  237. wx.chooseLocation({
  238. success: function (res) {
  239. console.log('选择的位置:', res);
  240. that.form.longitude = res.longitude
  241. that.form.latitude = res.latitude
  242. that.form.address = res.address
  243. }
  244. })
  245. },
  246. // 分类选择
  247. handleClass() {
  248. this.$refs.picker.open();
  249. },
  250. confirm(e) {
  251. let that = this
  252. let {indexs,value,values} = e
  253. that.form.classId = that.houseTypeList[indexs[0]].id;
  254. that.form.className = that.houseTypeList[indexs[0]].title;
  255. // 选择后保存表单数据
  256. that.debounceSaveFormData()
  257. },
  258. // 产权证照片上传
  259. async afterPropertyRead(e) {
  260. let self = this
  261. e.file.forEach(file => {
  262. self.$Oss.ossUpload(file.url).then(url => {
  263. self.form.propertyImages.push({
  264. url
  265. })
  266. // 上传完成后保存表单数据
  267. self.debounceSaveFormData()
  268. })
  269. })
  270. },
  271. deletePropertyPic(event) {
  272. this.form.propertyImages.splice(event.index, 1)
  273. // 删除后保存表单数据
  274. this.debounceSaveFormData()
  275. },
  276. // 视频上传
  277. async afterVideoRead(e) {
  278. let self = this
  279. // 检查每个视频文件的时长
  280. for (let file of e.file) {
  281. try {
  282. // 创建视频元素来获取视频时长
  283. // const duration = await this.getVideoDuration(file.url)
  284. // 检查视频时长是否超过60秒
  285. // if (duration > 60) {
  286. // uni.showToast({
  287. // title: '视频时长不能超过1分钟',
  288. // icon: 'none'
  289. // })
  290. // continue // 跳过这个视频
  291. // }
  292. // 上传视频
  293. self.$Oss.ossUpload(file.url).then(url => {
  294. self.form.videos.push({
  295. url
  296. })
  297. // 上传完成后保存表单数据
  298. self.debounceSaveFormData()
  299. })
  300. } catch (error) {
  301. console.error('获取视频时长失败:', error)
  302. uni.showToast({
  303. title: '视频格式不支持',
  304. icon: 'none'
  305. })
  306. }
  307. }
  308. },
  309. // 获取视频时长的辅助方法
  310. getVideoDuration(videoUrl) {
  311. return new Promise((resolve, reject) => {
  312. // #ifdef MP-WEIXIN
  313. uni.getVideoInfo({
  314. src: videoUrl,
  315. success: (res) => {
  316. resolve(res.duration)
  317. },
  318. fail: (err) => {
  319. reject(err)
  320. }
  321. })
  322. // #endif
  323. // #ifdef APP-PLUS
  324. plus.io.getVideoInfo({
  325. filePath: videoUrl,
  326. success: (res) => {
  327. resolve(res.duration)
  328. },
  329. fail: (err) => {
  330. reject(err)
  331. }
  332. })
  333. // #endif
  334. // #ifdef H5
  335. // H5环境暂不支持视频时长检测,直接通过
  336. resolve(0)
  337. // #endif
  338. })
  339. },
  340. deleteVideoPic(event) {
  341. this.form.videos.splice(event.index, 1)
  342. // 删除后保存表单数据
  343. this.debounceSaveFormData()
  344. },
  345. // 地块图片上传
  346. async afterImageRead(e) {
  347. let self = this
  348. e.file.forEach(file => {
  349. self.$Oss.ossUpload(file.url).then(url => {
  350. self.form.images.push({
  351. url
  352. })
  353. // 上传完成后保存表单数据
  354. self.debounceSaveFormData()
  355. // 触发表单验证更新
  356. self.$refs.form.validateField('images')
  357. })
  358. })
  359. },
  360. deleteImagePic(event) {
  361. this.form.images.splice(event.index, 1)
  362. // 删除后保存表单数据
  363. this.debounceSaveFormData()
  364. // 触发表单验证更新
  365. this.$refs.form.validateField('images')
  366. },
  367. // 提交表单
  368. submit() {
  369. this.$refs.form.validate().then(res => {
  370. // 验证必填的图片和视频
  371. if (this.form.propertyImages.length === 0) {
  372. uni.showToast({
  373. title: '请上传产权证照片',
  374. icon: 'none'
  375. });
  376. return;
  377. }
  378. if (this.form.videos.length === 0) {
  379. uni.showToast({
  380. title: '请上传地块视频',
  381. icon: 'none'
  382. });
  383. return;
  384. }
  385. if (this.form.images.length === 0) {
  386. uni.showToast({
  387. title: '请上传地块图片',
  388. icon: 'none'
  389. });
  390. return;
  391. }
  392. // 构建与index.vue兼容的提交参数
  393. const params = {
  394. userId: uni.getStorageSync('userInfo')?.id || "",
  395. id: "", // 新增数据,无ID
  396. classId: this.form.classId, // 使用固定分类标识
  397. commonClass: this.commonClass, // 所属分类
  398. address: this.form.address, // 地址
  399. homeAge: "", // 户主年龄 - 经营性用地无此字段
  400. homeAz: "", // 是否经过安置 - 默认否
  401. homeBian: "", // 房屋周边 - 经营性用地无此字段
  402. homeBjsx: "", // 报建手续 - 经营性用地无此字段
  403. homeCai: "", // 菜地 - 经营性用地无此字段
  404. homeCat: "", // 停车 - 经营性用地无此字段
  405. homeGz: "", // 房屋主体是否改造 - 默认否
  406. homeHb: "", // 房屋朝向及海拔 - 经营性用地无此字段
  407. homeBz: this.form.description, // 备注 - 使用地块介绍
  408. homeJg: "经营性建设用地", // 房屋结构 - 使用固定值
  409. homeJl: "", // 距离场镇距离 - 经营性用地无此字段
  410. homeJt: "", // 交通 - 经营性用地无此字段
  411. homeJtzy: "", // 户主家庭职业 - 经营性用地无此字段
  412. homeMi: this.form.landArea, // 面积 - 使用土地面积
  413. homeMj: this.form.landArea, // 房屋面积 - 使用土地面积
  414. homeMoney: "", // 佣金 - 经营性用地无此字段
  415. homeNo: "", // 房屋编号 - 经营性用地无此字段
  416. homeNum: "", // 房间数量 - 经营性用地无此字段
  417. homePay: "", // 付款方式及押金 - 经营性用地无此字段
  418. homePj: "", // 邻居对房东评价 - 经营性用地无此字段
  419. homeSd: "", // 水电气网 - 经营性用地无此字段
  420. homeShjl: "", // 距离成都西三环 - 经营性用地无此字段
  421. homeSw: "", // 非正常死亡 - 默认无
  422. homeTf: "", // 府市民云房屋信息档案查询 - 经营性用地无此字段
  423. homeTime: "", // 租期 - 经营性用地无此字段
  424. homeType: this.form.type === 'rent' ? '出租' : '出让', // 户型 - 使用类型
  425. homeYs: "", // 钥匙 - 默认无
  426. homeYzmj: this.form.landArea, // 院子总面积 - 使用土地面积
  427. homeZy: "", // 坟包及电塔 工厂噪音 - 经营性用地无此字段
  428. iconName: "经营性建设用地", // 热点名称
  429. iconTitle: "经营性用地", // 标签
  430. num: "0", // 浏览量 - 默认0
  431. price: this.form.price, // 价格
  432. timeGo: "", // 年限 - 经营性用地无此字段
  433. title: `${this.form.type === 'rent' ? '出租' : '出让'}-经营性建设用地-${this.form.address.split('市')[1] || this.form.address}`, // 标题 - 自动生成
  434. unit: this.form.type === 'rent' ? '元/亩/年' : '万元/亩', // 单位
  435. image: this.form.images.map(item => item.url).join(','), // 地块图片
  436. iconImage: "", // 左上角图标
  437. homeImage: this.form.propertyImages.map(item => item.url).join(','), // 产权证照片
  438. homeMp4: this.form.videos.map(item => item.url).join(','), // 视频
  439. latitude: this.form.latitude,
  440. longitude: this.form.longitude,
  441. // 新增字段用于标识经营性用地
  442. category: 'commercial',
  443. contactName: this.form.contactName,
  444. contactPhone: this.form.contactPhone
  445. };
  446. console.log('经营性用地提交参数:', params);
  447. uni.showLoading({
  448. title: '提交中...'
  449. });
  450. // 调用统一API
  451. saveOrUpdateHouse(params).then(response => {
  452. uni.hideLoading();
  453. // 提交成功后清除本地存储的表单数据
  454. formStorage.removeFormData(this.formKey);
  455. uni.showToast({
  456. title: response.message || '提交成功',
  457. icon: 'success'
  458. });
  459. setTimeout(() => {
  460. uni.redirectTo({
  461. url: "/pages_subpack/successful-apply/index"
  462. });
  463. }, 2000);
  464. }).catch(error => {
  465. uni.hideLoading();
  466. uni.showToast({
  467. title: error.message || '提交失败',
  468. icon: 'none'
  469. });
  470. console.error('经营性用地提交失败:', error);
  471. });
  472. }).catch(errors => {
  473. console.log('表单验证失败:', errors);
  474. uni.showToast({
  475. title: '请补全必填项',
  476. icon: 'none'
  477. });
  478. });
  479. }
  480. }
  481. }
  482. </script>
  483. <style scoped>
  484. .container {
  485. min-height: 100vh;
  486. background-color: #f5f5f5;
  487. }
  488. .header {
  489. background: linear-gradient(135deg, #1EC77A 0%, #4CAF50 100%);
  490. padding: 40rpx 40rpx 60rpx;
  491. color: white;
  492. }
  493. .title {
  494. font-size: 36rpx;
  495. font-weight: bold;
  496. text-align: center;
  497. }
  498. .form-container {
  499. background: white;
  500. margin: -30rpx 20rpx 20rpx;
  501. border-radius: 20rpx;
  502. padding: 40rpx;
  503. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.1);
  504. }
  505. .section-title {
  506. font-size: 32rpx;
  507. font-weight: bold;
  508. color: #333;
  509. margin: 40rpx 0 30rpx;
  510. padding-left: 20rpx;
  511. border-left: 6rpx solid #1EC77A;
  512. }
  513. .section-title:first-child {
  514. margin-top: 0;
  515. }
  516. .input-with-unit {
  517. display: flex;
  518. align-items: center;
  519. gap: 10rpx;
  520. }
  521. .unit {
  522. font-size: 26rpx;
  523. color: #666;
  524. margin-left: 10rpx;
  525. }
  526. .input-tip {
  527. font-size: 22rpx;
  528. color: #999;
  529. margin-top: 10rpx;
  530. line-height: 1.4;
  531. }
  532. .upload-tip {
  533. font-size: 24rpx;
  534. color: #666;
  535. margin-bottom: 10rpx;
  536. line-height: 1.5;
  537. }
  538. .price-container {
  539. display: flex;
  540. align-items: center;
  541. gap: 10rpx;
  542. }
  543. .price-unit {
  544. font-size: 26rpx;
  545. color: #FF6B35;
  546. font-weight: bold;
  547. margin-left: 10rpx;
  548. }
  549. </style>