租房小程序前端代码
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.

603 lines
18 KiB

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