resource-input.vue 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531
  1. <template>
  2. <view class="resource-input-page">
  3. <!-- 顶部导航栏 -->
  4. <view class="header">
  5. <view class="header-left" @click="goBack">
  6. <text class="back-icon">←</text>
  7. </view>
  8. <text class="header-title">{{ isEditMode ? '编辑信息' : '信息录入' }}</text>
  9. <view class="header-right">
  10. <text class="more-icon">⋯</text>
  11. </view>
  12. </view>
  13. <scroll-view scroll-y class="content">
  14. <view class="form-container">
  15. <!-- 基本信息 -->
  16. <view class="form-section" :class="{ 'active': activeSection === 'basic' }">
  17. <view class="section-title">基本信息</view>
  18. <view class="form-item">
  19. <text class="form-label">姓名<text class="required">*</text></text>
  20. <input type="text" class="form-input" placeholder="请输入姓名" v-model="formData.name" />
  21. </view>
  22. <view class="form-item">
  23. <text class="form-label">年龄<text class="required">*</text></text>
  24. <input type="number" class="form-input" placeholder="请输入年龄" v-model="formData.age" />
  25. </view>
  26. <view class="form-item">
  27. <text class="form-label">性别<text class="required">*</text></text>
  28. <picker mode="selector" :range="genderOptions" :value="genderIndex" @change="onGenderChange">
  29. <view class="form-input picker-input">
  30. <text v-if="formData.gender">{{ genderOptions[genderIndex] }}</text>
  31. <text v-else class="placeholder">请选择性别</text>
  32. <text class="picker-arrow">▼</text>
  33. </view>
  34. </picker>
  35. </view>
  36. <view class="form-item">
  37. <text class="form-label">星座</text>
  38. <picker mode="selector" :range="constellationOptions" :value="constellationIndex" @change="onConstellationChange">
  39. <view class="form-input picker-input">
  40. <text v-if="formData.constellation">{{ constellationOptions[constellationIndex] }}</text>
  41. <text v-else class="placeholder">请选择星座</text>
  42. <text class="picker-arrow">▼</text>
  43. </view>
  44. </picker>
  45. </view>
  46. </view>
  47. <!-- 身体状况 -->
  48. <view class="form-section" :class="{ 'active': activeSection === 'physical' }">
  49. <view class="section-title">身体状况</view>
  50. <view class="form-item">
  51. <text class="form-label">身高 (cm)<text class="required">*</text></text>
  52. <input type="number" class="form-input" placeholder="请输入身高" v-model="formData.height" />
  53. </view>
  54. <view class="form-item">
  55. <text class="form-label">体重 (kg)<text class="required">*</text></text>
  56. <input type="number" class="form-input" placeholder="请输入体重" v-model="formData.weight" />
  57. </view>
  58. </view>
  59. <!-- 社会属性 -->
  60. <view class="form-section" :class="{ 'active': activeSection === 'social' }">
  61. <view class="section-title">社会属性</view>
  62. <view class="form-item">
  63. <text class="form-label">婚况<text class="required">*</text></text>
  64. <picker mode="selector" :range="maritalStatusOptions" :value="maritalStatusIndex >= 0 ? maritalStatusIndex : 0" @change="onMaritalStatusChange">
  65. <view class="form-input picker-input">
  66. <text v-if="maritalStatusIndex >= 0 && maritalStatusIndex < maritalStatusOptions.length">{{ maritalStatusOptions[maritalStatusIndex] }}</text>
  67. <text v-else class="placeholder">请选择婚况</text>
  68. <text class="picker-arrow">▼</text>
  69. </view>
  70. </picker>
  71. </view>
  72. <view class="form-item">
  73. <text class="form-label">学历</text>
  74. <picker mode="selector" :range="diplomaOptions" :value="diplomaIndex" @change="onDiplomaChange">
  75. <view class="form-input picker-input">
  76. <text v-if="formData.diploma">{{ diplomaOptions[diplomaIndex] }}</text>
  77. <text v-else class="placeholder">请选择学历</text>
  78. <text class="picker-arrow">▼</text>
  79. </view>
  80. </picker>
  81. </view>
  82. <view class="form-item">
  83. <text class="form-label">年收入 (万元)</text>
  84. <input type="text" class="form-input" placeholder="请输入年收入" v-model="formData.income" />
  85. </view>
  86. <view class="form-item">
  87. <text class="form-label">住址<text class="required">*</text></text>
  88. <!-- 省市区三级联动选择器 -->
  89. <picker mode="multiSelector" :range="addressMultiArray" :value="addressMultiIndex" @columnchange="onAddressColumnChange" @change="onAddressChange">
  90. <view class="form-input picker-input">
  91. <text v-if="addressDisplayText" class="picker-text">{{ addressDisplayText }}</text>
  92. <text v-else class="placeholder">请选择省市区</text>
  93. <text class="picker-arrow">▼</text>
  94. </view>
  95. </picker>
  96. <!-- 详细地址输入框 -->
  97. <input type="text" class="form-input address-detail-input" placeholder="请输入详细地址(街道、门牌号等)" v-model="formData.addressDetail" style="margin-top: 15rpx;" />
  98. </view>
  99. <view class="form-item">
  100. <text class="form-label">户籍地</text>
  101. <!-- 省市区三级联动选择器 -->
  102. <picker mode="multiSelector" :range="domicileMultiArray" :value="domicileMultiIndex" @columnchange="onDomicileColumnChange" @change="onDomicileChange">
  103. <view class="form-input picker-input">
  104. <text v-if="domicileDisplayText" class="picker-text">{{ domicileDisplayText }}</text>
  105. <text v-else class="placeholder">请选择省市区</text>
  106. <text class="picker-arrow">▼</text>
  107. </view>
  108. </picker>
  109. <!-- 详细地址输入框 -->
  110. <input type="text" class="form-input address-detail-input" placeholder="请输入详细地址(街道、门牌号等)" v-model="formData.domicileDetail" style="margin-top: 15rpx;" />
  111. </view>
  112. <view class="form-item">
  113. <text class="form-label">职业</text>
  114. <input type="text" class="form-input" placeholder="请输入职业" v-model="formData.occupation" />
  115. </view>
  116. <view class="form-item">
  117. <text class="form-label">购房</text>
  118. <picker mode="selector" :range="houseOptions" :value="houseIndex" @change="onHouseChange">
  119. <view class="form-input picker-input">
  120. <text v-if="formData.house !== null && formData.house !== undefined">{{ houseOptions[houseIndex] }}</text>
  121. <text v-else class="placeholder">请选择</text>
  122. <text class="picker-arrow">▼</text>
  123. </view>
  124. </picker>
  125. </view>
  126. <view class="form-item">
  127. <text class="form-label">购车</text>
  128. <picker mode="selector" :range="carOptions" :value="carIndex" @change="onCarChange">
  129. <view class="form-input picker-input">
  130. <text v-if="formData.car !== null && formData.car !== undefined">{{ carOptions[carIndex] }}</text>
  131. <text v-else class="placeholder">请选择</text>
  132. <text class="picker-arrow">▼</text>
  133. </view>
  134. </picker>
  135. </view>
  136. </view>
  137. <!-- 择偶标准 -->
  138. <view class="form-section">
  139. <view class="section-title">择偶标准</view>
  140. <view class="form-item">
  141. <text class="form-label">择偶要求</text>
  142. <input type="text" class="form-input" placeholder="请输入择偶要求,如:176+本科" v-model="formData.mateSelectionCriteria" />
  143. </view>
  144. </view>
  145. <!-- 客户标签 -->
  146. <view class="form-section">
  147. <view class="section-title">客户标签</view>
  148. <view class="form-item">
  149. <text class="form-label">选择标签</text>
  150. <!-- 分类按钮 -->
  151. <view class="category-buttons">
  152. <view
  153. class="category-btn"
  154. :class="{ 'active': selectedCategoryId === category.id }"
  155. v-for="category in categoryList"
  156. :key="category.id"
  157. @click="selectCategory(category.id)"
  158. >
  159. <text class="category-btn-text">{{ category.name }}</text>
  160. </view>
  161. </view>
  162. <!-- 标签列表 -->
  163. <view class="tags-container">
  164. <view
  165. class="tag-item"
  166. :class="{ 'selected': selectedTagIds.includes(tag.id) }"
  167. v-for="tag in filteredTagList"
  168. :key="tag.id || tag.tag_id"
  169. @click="toggleTag(tag.id || tag.tag_id)"
  170. >
  171. <text class="tag-text">{{ tag.name || tag.tag_name }}</text>
  172. </view>
  173. <view v-if="filteredTagList.length === 0 && !isLoadingTags" class="tag-empty">
  174. <text>暂无标签</text>
  175. </view>
  176. <view v-if="isLoadingTags" class="tag-loading">
  177. <text>加载中...</text>
  178. </view>
  179. </view>
  180. </view>
  181. </view>
  182. <!-- 联系方式 -->
  183. <view class="form-section">
  184. <view class="section-title">联系方式</view>
  185. <view class="form-item">
  186. <text class="form-label">手机号<text class="required">*</text></text>
  187. <input type="text" class="form-input" placeholder="请输入手机号" v-model="formData.phone" />
  188. </view>
  189. <view class="form-item">
  190. <text class="form-label">备用手机号</text>
  191. <input type="text" class="form-input" placeholder="请输入备用手机号" v-model="formData.backupPhone" />
  192. </view>
  193. </view>
  194. </view>
  195. </scroll-view>
  196. <!-- 底部提交按钮 -->
  197. <view class="submit-bar">
  198. <button class="submit-btn" @click="handleSubmit">{{ isEditMode ? '确认修改' : '提交' }}</button>
  199. </view>
  200. </view>
  201. </template>
  202. <script>
  203. import api from '@/utils/api.js'
  204. export default {
  205. data() {
  206. return {
  207. activeSection: 'basic',
  208. currentUserId: null, // 当前登录用户的用户ID
  209. resourceId: null, // 编辑模式下的资源ID
  210. isEditMode: false, // 是否为编辑模式
  211. formData: {
  212. name: '',
  213. age: null,
  214. gender: null,
  215. constellation: '',
  216. height: null,
  217. weight: null,
  218. marrStatus: null,
  219. diploma: '',
  220. income: '',
  221. address: '', // 住址(省市区+详细地址组合)
  222. addressDetail: '', // 住址详细地址
  223. domicile: '', // 户籍地(省市区+详细地址组合)
  224. domicileDetail: '', // 户籍地详细地址
  225. occupation: '',
  226. house: null,
  227. car: null,
  228. mateSelectionCriteria: '',
  229. phone: '',
  230. backupPhone: ''
  231. },
  232. // 住址省市区数据
  233. addressProvinces: [],
  234. addressCities: [],
  235. addressAreas: [],
  236. addressMultiArray: [[], [], []], // 三级联动数据
  237. addressMultiIndex: [0, 0, 0], // 三级联动索引
  238. addressDisplayText: '', // 住址显示文本
  239. // 户籍地省市区数据
  240. domicileProvinces: [],
  241. domicileCities: [],
  242. domicileAreas: [],
  243. domicileMultiArray: [[], [], []], // 三级联动数据
  244. domicileMultiIndex: [0, 0, 0], // 三级联动索引
  245. domicileDisplayText: '', // 户籍地显示文本
  246. genderOptions: ['男', '女'],
  247. genderIndex: 0,
  248. constellationOptions: ['白羊座', '金牛座', '双子座', '巨蟹座', '狮子座', '处女座', '天秤座', '天蝎座', '射手座', '摩羯座', '水瓶座', '双鱼座'],
  249. constellationIndex: 0,
  250. maritalStatusOptions: ['未婚', '离异', '丧偶'],
  251. maritalStatusIndex: -1, // -1表示未选择,0-未婚,1-离异,2-丧偶
  252. diplomaOptions: ['高中', '专科', '本科', '硕士', '博士', '无'],
  253. diplomaIndex: 0,
  254. houseOptions: ['无', '有'],
  255. houseIndex: 0,
  256. carOptions: ['无', '有'],
  257. carIndex: 0,
  258. tagList: [], // 所有标签列表
  259. categoryList: [], // 标签分类列表
  260. selectedCategoryId: 1, // 当前选中的分类ID,默认显示职业分类(category_id = 1)
  261. selectedTagIds: [], // 选中的标签ID列表
  262. isLoadingTags: false // 是否正在加载标签
  263. }
  264. },
  265. computed: {
  266. // 根据选中的分类筛选标签(只显示当前分类的标签,不显示全部)
  267. filteredTagList() {
  268. // 如果没有选中分类,返回空数组(不显示任何标签)
  269. if (this.selectedCategoryId === null || this.selectedCategoryId === undefined) {
  270. return []
  271. }
  272. // 确保selectedCategoryId是数字类型
  273. const selectedId = parseInt(this.selectedCategoryId)
  274. if (isNaN(selectedId)) {
  275. console.warn('selectedCategoryId不是有效数字:', this.selectedCategoryId)
  276. return []
  277. }
  278. // 筛选标签,只显示当前分类的标签
  279. const filtered = this.tagList.filter(tag => {
  280. // 获取标签的分类ID,优先使用 category_id(后端返回的字段名)
  281. const tagCategoryId = tag.category_id !== null && tag.category_id !== undefined
  282. ? parseInt(tag.category_id)
  283. : (tag.categoryId !== null && tag.categoryId !== undefined
  284. ? parseInt(tag.categoryId)
  285. : null)
  286. // 如果tagCategoryId是NaN或null,说明数据有问题或未分类,不显示
  287. if (isNaN(tagCategoryId) || tagCategoryId === null) {
  288. return false
  289. }
  290. // 只返回匹配当前分类的标签
  291. return tagCategoryId === selectedId
  292. })
  293. return filtered
  294. }
  295. },
  296. onLoad(options) {
  297. // 获取当前登录用户信息
  298. const userInfo = uni.getStorageSync('userInfo') || {}
  299. const userId = uni.getStorageSync('userId')
  300. // 从userInfo中获取用户ID,将作为红娘ID
  301. let rawUserId = userInfo.userId || userId || null
  302. // 验证并转换currentUserId为有效的整数
  303. if (rawUserId !== null && rawUserId !== undefined) {
  304. rawUserId = parseInt(rawUserId)
  305. if (isNaN(rawUserId) || rawUserId <= 0) {
  306. rawUserId = null
  307. }
  308. }
  309. this.currentUserId = rawUserId
  310. console.log('当前登录用户ID:', this.currentUserId)
  311. // 检查是否为编辑模式
  312. if (options && options.resourceId) {
  313. this.resourceId = parseInt(options.resourceId)
  314. this.isEditMode = true
  315. console.log('编辑模式,资源ID:', this.resourceId)
  316. // 加载资源详情
  317. this.loadResourceDetail()
  318. }
  319. // 加载标签分类和标签列表
  320. this.loadCategories()
  321. this.loadTags()
  322. // 加载省市区数据
  323. this.loadAddressProvinces()
  324. this.loadDomicileProvinces()
  325. },
  326. methods: {
  327. goBack() {
  328. uni.navigateBack()
  329. },
  330. // 加载资源详情(编辑模式)
  331. async loadResourceDetail() {
  332. if (!this.resourceId) return
  333. try {
  334. uni.showLoading({ title: '加载中...' })
  335. const baseUrl = process.env.NODE_ENV === 'development'
  336. ? 'http://localhost:8083/api' // 开发环境 - 通过网关
  337. : 'https://your-domain.com/api' // 生产环境
  338. const [error, res] = await uni.request({
  339. url: `${baseUrl}/my-resource/detail/${this.resourceId}`,
  340. method: 'GET'
  341. })
  342. uni.hideLoading()
  343. if (error) {
  344. console.error('加载资源详情失败:', error)
  345. uni.showToast({
  346. title: '加载资源详情失败',
  347. icon: 'none'
  348. })
  349. return
  350. }
  351. if (res.statusCode === 200 && res.data) {
  352. // 检查返回的code
  353. if (res.data.code === 200 && res.data.data) {
  354. const resource = res.data.data
  355. console.log('资源详情:', resource)
  356. // 填充表单数据
  357. this.formData.name = resource.name || ''
  358. this.formData.age = resource.age || null
  359. this.formData.gender = resource.gender || null
  360. if (this.formData.gender) {
  361. this.genderIndex = this.formData.gender - 1 // 1-男,2-女 -> 0,1
  362. }
  363. this.formData.constellation = resource.constellation || ''
  364. if (this.formData.constellation) {
  365. const index = this.constellationOptions.indexOf(this.formData.constellation)
  366. if (index >= 0) {
  367. this.constellationIndex = index
  368. }
  369. }
  370. this.formData.height = resource.height || null
  371. this.formData.weight = resource.weight || null
  372. this.formData.marrStatus = resource.marrStatus !== null && resource.marrStatus !== undefined ? resource.marrStatus : null
  373. if (this.formData.marrStatus !== null && this.formData.marrStatus >= 0) {
  374. this.maritalStatusIndex = this.formData.marrStatus
  375. }
  376. this.formData.diploma = resource.diploma || ''
  377. if (this.formData.diploma) {
  378. const index = this.diplomaOptions.indexOf(this.formData.diploma)
  379. if (index >= 0) {
  380. this.diplomaIndex = index
  381. }
  382. }
  383. this.formData.income = resource.income || ''
  384. // 解析地址和户籍地
  385. this.parseAddress(resource.address || '')
  386. this.parseDomicile(resource.domicile || '')
  387. this.formData.occupation = resource.occupation || ''
  388. this.formData.house = resource.house !== null && resource.house !== undefined ? resource.house : null
  389. if (this.formData.house !== null && this.formData.house !== undefined) {
  390. this.houseIndex = this.formData.house
  391. }
  392. this.formData.car = resource.car !== null && resource.car !== undefined ? resource.car : null
  393. if (this.formData.car !== null && this.formData.car !== undefined) {
  394. this.carIndex = this.formData.car
  395. }
  396. this.formData.mateSelectionCriteria = resource.mateSelectionCriteria || ''
  397. this.formData.phone = resource.phone || ''
  398. this.formData.backupPhone = resource.backupPhone || ''
  399. // 加载标签(需要先加载标签列表)
  400. // 这里假设标签会在 loadTags 之后加载,所以需要延迟处理
  401. this.$nextTick(() => {
  402. // 加载资源关联的标签
  403. this.loadResourceTags()
  404. })
  405. } else {
  406. // 资源不存在或其他错误
  407. const errorMessage = res.data.message || res.data.msg || '请求的资源不存在'
  408. uni.showToast({
  409. title: errorMessage,
  410. icon: 'none',
  411. duration: 2000
  412. })
  413. // 延迟返回上一页
  414. setTimeout(() => {
  415. uni.navigateBack()
  416. }, 2000)
  417. }
  418. } else {
  419. uni.showToast({
  420. title: '加载资源详情失败',
  421. icon: 'none'
  422. })
  423. setTimeout(() => {
  424. uni.navigateBack()
  425. }, 2000)
  426. }
  427. } catch (e) {
  428. uni.hideLoading()
  429. console.error('加载资源详情异常:', e)
  430. uni.showToast({
  431. title: '加载资源详情失败',
  432. icon: 'none'
  433. })
  434. }
  435. },
  436. // 解析地址(分离省市区和详细地址)
  437. parseAddress(fullAddress) {
  438. if (!fullAddress) return
  439. // 简单的地址解析:假设格式为 "省 市 区 详细地址"
  440. // 这里可以根据实际情况调整解析逻辑
  441. const parts = fullAddress.split(' ')
  442. if (parts.length >= 3) {
  443. // 前三个部分可能是省市区
  444. const regionParts = parts.slice(0, 3)
  445. this.addressDisplayText = regionParts.join(' ')
  446. this.formData.addressDetail = parts.slice(3).join(' ') || ''
  447. // 尝试匹配省市区并设置选择器
  448. this.matchAddressRegion(regionParts)
  449. } else {
  450. // 如果格式不符合,将整个地址作为详细地址
  451. this.formData.addressDetail = fullAddress
  452. }
  453. },
  454. // 解析户籍地(分离省市区和详细地址)
  455. parseDomicile(fullDomicile) {
  456. if (!fullDomicile) return
  457. const parts = fullDomicile.split(' ')
  458. if (parts.length >= 3) {
  459. const regionParts = parts.slice(0, 3)
  460. this.domicileDisplayText = regionParts.join(' ')
  461. this.formData.domicileDetail = parts.slice(3).join(' ') || ''
  462. // 尝试匹配省市区并设置选择器
  463. this.matchDomicileRegion(regionParts)
  464. } else {
  465. this.formData.domicileDetail = fullDomicile
  466. }
  467. },
  468. // 匹配地址省市区(简化版,实际可能需要更复杂的匹配逻辑)
  469. async matchAddressRegion(regionParts) {
  470. // 这里简化处理,实际应该根据省市区名称匹配
  471. // 暂时只设置显示文本,不自动选择
  472. },
  473. // 匹配户籍地省市区(简化版)
  474. async matchDomicileRegion(regionParts) {
  475. // 这里简化处理,实际应该根据省市区名称匹配
  476. // 暂时只设置显示文本,不自动选择
  477. },
  478. // 加载资源关联的标签
  479. async loadResourceTags() {
  480. if (!this.resourceId) return
  481. try {
  482. const baseUrl = process.env.NODE_ENV === 'development'
  483. ? 'http://localhost:8083/api'
  484. : 'https://your-domain.com/api'
  485. // 调用获取资源标签ID列表的接口
  486. const [error, res] = await uni.request({
  487. url: `${baseUrl}/my-resource/tag-ids/${this.resourceId}`,
  488. method: 'GET'
  489. })
  490. if (error) {
  491. console.error('加载资源标签失败:', error)
  492. return
  493. }
  494. if (res.statusCode === 200 && res.data && res.data.code === 200) {
  495. // 后端直接返回标签ID列表
  496. const tagIds = res.data.data || []
  497. this.selectedTagIds = tagIds.filter(id => id !== null && id !== undefined)
  498. console.log('资源标签ID列表:', this.selectedTagIds)
  499. }
  500. } catch (e) {
  501. console.error('加载资源标签异常:', e)
  502. }
  503. },
  504. // 加载标签分类列表
  505. async loadCategories() {
  506. try {
  507. const baseUrl = process.env.NODE_ENV === 'development'
  508. ? 'http://localhost:8083/api' // 开发环境 - 通过网关
  509. : 'https://your-domain.com/api' // 生产环境
  510. const [error, res] = await uni.request({
  511. url: `${baseUrl}/tag/categories`,
  512. method: 'GET',
  513. timeout: 10000
  514. })
  515. if (error) {
  516. console.error('加载标签分类失败:', error)
  517. return
  518. }
  519. if (res.statusCode === 200 && res.data && res.data.code === 200) {
  520. // 将后端返回的下划线字段转换为驼峰命名
  521. this.categoryList = (res.data.data || []).map(category => ({
  522. id: category.id || category.category_id,
  523. name: category.name || category.category_name,
  524. code: category.code || category.category_code,
  525. sortOrder: category.sortOrder || category.sort_order,
  526. status: category.status
  527. }))
  528. console.log('标签分类列表加载成功:', this.categoryList)
  529. // 如果还没有选中分类,默认选中职业分类(category_id = 1)
  530. if (this.selectedCategoryId === null || this.selectedCategoryId === undefined) {
  531. // 查找职业分类(code为'occupation'或id为1)
  532. const occupationCategory = this.categoryList.find(cat =>
  533. (cat.code === 'occupation' || cat.id === 1)
  534. )
  535. if (occupationCategory) {
  536. this.selectedCategoryId = occupationCategory.id
  537. console.log('默认选中职业分类,ID:', this.selectedCategoryId)
  538. } else if (this.categoryList.length > 0) {
  539. // 如果找不到职业分类,选中第一个分类
  540. this.selectedCategoryId = this.categoryList[0].id
  541. console.log('默认选中第一个分类,ID:', this.selectedCategoryId)
  542. }
  543. }
  544. } else {
  545. console.error('加载标签分类失败:', res.data.message)
  546. }
  547. } catch (e) {
  548. console.error('加载标签分类异常:', e)
  549. }
  550. },
  551. // 加载标签列表
  552. async loadTags() {
  553. try {
  554. this.isLoadingTags = true
  555. const baseUrl = process.env.NODE_ENV === 'development'
  556. ? 'http://localhost:8083/api' // 开发环境 - 通过网关
  557. : 'https://your-domain.com/api' // 生产环境
  558. const [error, res] = await uni.request({
  559. url: `${baseUrl}/tag/list`,
  560. method: 'GET',
  561. timeout: 10000
  562. })
  563. if (error) {
  564. console.error('加载标签失败:', error)
  565. return
  566. }
  567. if (res.statusCode === 200 && res.data && res.data.code === 200) {
  568. // 直接使用后端返回的数据,不进行映射(因为Vue的响应式系统会保留原始属性)
  569. // 只保存有分类的标签(category_id不为null)
  570. this.tagList = (res.data.data || []).filter(tag => {
  571. const categoryId = tag.category_id !== null && tag.category_id !== undefined
  572. ? tag.category_id
  573. : (tag.categoryId !== null && tag.categoryId !== undefined ? tag.categoryId : null)
  574. return categoryId !== null && categoryId !== undefined
  575. })
  576. console.log('标签列表加载成功,数量:', this.tagList.length)
  577. if (this.tagList.length > 0) {
  578. console.log('第一条标签数据:', this.tagList[0])
  579. console.log('第一条标签的category_id:', this.tagList[0].category_id, '类型:', typeof this.tagList[0].category_id)
  580. }
  581. } else {
  582. console.error('加载标签失败:', res.data.message)
  583. }
  584. } catch (e) {
  585. console.error('加载标签异常:', e)
  586. } finally {
  587. this.isLoadingTags = false
  588. }
  589. },
  590. // 选择分类
  591. selectCategory(categoryId) {
  592. this.selectedCategoryId = categoryId
  593. console.log('=== 选择分类 ===')
  594. console.log('分类ID:', categoryId, '类型:', typeof categoryId)
  595. console.log('标签总数:', this.tagList.length)
  596. // 使用$nextTick确保DOM更新后再输出
  597. this.$nextTick(() => {
  598. console.log('筛选后的标签数量:', this.filteredTagList.length)
  599. if (this.filteredTagList.length > 0) {
  600. console.log('筛选后的标签名称:', this.filteredTagList.map(t => t.name || t.tag_name || t.tagName))
  601. } else {
  602. console.warn('没有找到匹配的标签!')
  603. // 调试:查看前几条标签的category_id
  604. if (this.tagList.length > 0) {
  605. console.log('前3条标签的category_id:',
  606. this.tagList.slice(0, 3).map(t => ({
  607. name: t.name || t.tag_name,
  608. category_id: t.category_id,
  609. categoryId: t.categoryId
  610. }))
  611. )
  612. }
  613. }
  614. })
  615. },
  616. // 切换标签选择状态
  617. toggleTag(tagId) {
  618. const index = this.selectedTagIds.indexOf(tagId)
  619. if (index > -1) {
  620. // 已选中,取消选择
  621. this.selectedTagIds.splice(index, 1)
  622. } else {
  623. // 未选中,添加到选中列表
  624. this.selectedTagIds.push(tagId)
  625. }
  626. console.log('选中的标签ID:', this.selectedTagIds)
  627. },
  628. onGenderChange(e) {
  629. this.genderIndex = parseInt(e.detail.value) || 0
  630. this.formData.gender = this.genderIndex + 1 // 1-男,2-女
  631. console.log('性别选择 - genderIndex:', this.genderIndex, 'formData.gender:', this.formData.gender)
  632. },
  633. onConstellationChange(e) {
  634. this.constellationIndex = e.detail.value
  635. this.formData.constellation = this.constellationOptions[this.constellationIndex]
  636. },
  637. onMaritalStatusChange(e) {
  638. this.maritalStatusIndex = parseInt(e.detail.value)
  639. // 0-未婚,1-离异,2-丧偶
  640. // 直接使用索引值作为marrStatus的值
  641. this.formData.marrStatus = this.maritalStatusIndex
  642. console.log('婚姻状态选择:', this.maritalStatusIndex, '对应值:', this.formData.marrStatus, '选项:', this.maritalStatusOptions[this.maritalStatusIndex])
  643. },
  644. onDiplomaChange(e) {
  645. this.diplomaIndex = e.detail.value
  646. this.formData.diploma = this.diplomaOptions[this.diplomaIndex]
  647. },
  648. onHouseChange(e) {
  649. this.houseIndex = e.detail.value
  650. this.formData.house = this.houseIndex // 0-无,1-有
  651. },
  652. onCarChange(e) {
  653. this.carIndex = e.detail.value
  654. this.formData.car = this.carIndex // 0-无,1-有
  655. },
  656. async handleSubmit() {
  657. // 验证必填项
  658. if (!this.formData.name || !this.formData.name.trim()) {
  659. uni.showToast({
  660. title: '请输入姓名',
  661. icon: 'none'
  662. })
  663. return
  664. }
  665. // 验证年龄
  666. if (this.formData.age === null || this.formData.age === undefined || this.formData.age === '') {
  667. uni.showToast({
  668. title: '请输入年龄',
  669. icon: 'none'
  670. })
  671. return
  672. }
  673. // 验证性别
  674. if (this.formData.gender === null || this.formData.gender === undefined) {
  675. uni.showToast({
  676. title: '请选择性别',
  677. icon: 'none'
  678. })
  679. return
  680. }
  681. // 验证身高
  682. if (this.formData.height === null || this.formData.height === undefined || this.formData.height === '') {
  683. uni.showToast({
  684. title: '请输入身高',
  685. icon: 'none'
  686. })
  687. return
  688. }
  689. // 验证体重
  690. if (this.formData.weight === null || this.formData.weight === undefined || this.formData.weight === '') {
  691. uni.showToast({
  692. title: '请输入体重',
  693. icon: 'none'
  694. })
  695. return
  696. }
  697. // 验证婚况
  698. if ((this.maritalStatusIndex < 0 || this.maritalStatusIndex === null || this.maritalStatusIndex === undefined)
  699. && (this.formData.marrStatus === null || this.formData.marrStatus === undefined)) {
  700. uni.showToast({
  701. title: '请选择婚况',
  702. icon: 'none'
  703. })
  704. return
  705. }
  706. // 验证住址(省市区或详细地址至少有一个)
  707. if (!this.addressDisplayText && (!this.formData.addressDetail || !this.formData.addressDetail.trim())) {
  708. uni.showToast({
  709. title: '请选择省市区或输入详细地址',
  710. icon: 'none'
  711. })
  712. return
  713. }
  714. if (!this.formData.phone || !this.formData.phone.trim()) {
  715. uni.showToast({
  716. title: '请输入手机号',
  717. icon: 'none'
  718. })
  719. return
  720. }
  721. // 验证手机号格式
  722. const phoneReg = /^1[3-9]\d{9}$/
  723. if (!phoneReg.test(this.formData.phone)) {
  724. uni.showToast({
  725. title: '请输入正确的手机号',
  726. icon: 'none'
  727. })
  728. return
  729. }
  730. uni.showLoading({
  731. title: '提交中...'
  732. })
  733. try {
  734. // 确保婚姻状态已选择
  735. if ((this.maritalStatusIndex < 0 || this.maritalStatusIndex === null || this.maritalStatusIndex === undefined)
  736. && (this.formData.marrStatus === null || this.formData.marrStatus === undefined)) {
  737. uni.showToast({
  738. title: '请选择婚况',
  739. icon: 'none'
  740. })
  741. return
  742. }
  743. // 调试:打印原始值
  744. console.log('=== 提交前的原始数据 ===')
  745. console.log('formData.marrStatus:', this.formData.marrStatus, '类型:', typeof this.formData.marrStatus)
  746. console.log('maritalStatusIndex:', this.maritalStatusIndex, '类型:', typeof this.maritalStatusIndex)
  747. console.log('formData.backupPhone:', this.formData.backupPhone, '类型:', typeof this.formData.backupPhone)
  748. console.log('formData.mateSelectionCriteria:', this.formData.mateSelectionCriteria, '类型:', typeof this.formData.mateSelectionCriteria)
  749. // 确定婚姻状态的值:优先使用formData.marrStatus,如果没有则使用maritalStatusIndex
  750. let marrStatusValue = null
  751. if (this.formData.marrStatus !== null && this.formData.marrStatus !== undefined && this.formData.marrStatus !== 'undefined' && this.formData.marrStatus !== 'null' && this.formData.marrStatus !== '') {
  752. const parsed = parseInt(this.formData.marrStatus)
  753. marrStatusValue = isNaN(parsed) ? null : parsed
  754. console.log('婚姻状态处理: formData.marrStatus ->', marrStatusValue)
  755. } else if (this.maritalStatusIndex !== null && this.maritalStatusIndex !== undefined && this.maritalStatusIndex >= 0) {
  756. const parsed = parseInt(this.maritalStatusIndex)
  757. marrStatusValue = isNaN(parsed) ? null : parsed
  758. console.log('婚姻状态处理: maritalStatusIndex ->', marrStatusValue)
  759. } else {
  760. console.log('婚姻状态处理: 未找到有效值')
  761. }
  762. // 处理备用手机号:保留有效值,空字符串转为null
  763. let backupPhoneValue = null
  764. const backupPhoneRaw = this.formData.backupPhone
  765. if (backupPhoneRaw !== null && backupPhoneRaw !== undefined && backupPhoneRaw !== 'undefined' && backupPhoneRaw !== 'null') {
  766. const trimmed = String(backupPhoneRaw).trim()
  767. if (trimmed !== '' && trimmed !== 'undefined' && trimmed !== 'null') {
  768. backupPhoneValue = trimmed
  769. console.log('备用手机号处理: 保留值 ->', backupPhoneValue)
  770. } else {
  771. console.log('备用手机号处理: 空值,转为null')
  772. }
  773. } else {
  774. console.log('备用手机号处理: 原始值为null/undefined')
  775. }
  776. // 处理择偶标准:保留有效值,空字符串转为null
  777. let mateSelectionCriteriaValue = null
  778. const mateSelectionCriteriaRaw = this.formData.mateSelectionCriteria
  779. if (mateSelectionCriteriaRaw !== null && mateSelectionCriteriaRaw !== undefined && mateSelectionCriteriaRaw !== 'undefined' && mateSelectionCriteriaRaw !== 'null') {
  780. const trimmed = String(mateSelectionCriteriaRaw).trim()
  781. if (trimmed !== '' && trimmed !== 'undefined' && trimmed !== 'null') {
  782. mateSelectionCriteriaValue = trimmed
  783. console.log('择偶标准处理: 保留值 ->', mateSelectionCriteriaValue)
  784. } else {
  785. console.log('择偶标准处理: 空值,转为null')
  786. }
  787. } else {
  788. console.log('择偶标准处理: 原始值为null/undefined')
  789. }
  790. // 辅助函数:安全地将值转换为整数,无效值返回null
  791. const safeParseInt = (value) => {
  792. if (value === null || value === undefined || value === '' || value === 'undefined' || value === 'null') {
  793. return null
  794. }
  795. const parsed = parseInt(value)
  796. return isNaN(parsed) ? null : parsed
  797. }
  798. // 辅助函数:安全地获取字符串值,无效值返回null
  799. const safeString = (value) => {
  800. if (value === null || value === undefined || value === '' || value === 'undefined' || value === 'null') {
  801. return null
  802. }
  803. const str = String(value).trim()
  804. return str === '' ? null : str
  805. }
  806. // 转换数据类型,明确构建提交数据对象
  807. const submitData = {
  808. name: safeString(this.formData.name),
  809. age: safeParseInt(this.formData.age),
  810. gender: safeParseInt(this.formData.gender),
  811. constellation: safeString(this.formData.constellation),
  812. height: safeParseInt(this.formData.height),
  813. weight: safeParseInt(this.formData.weight),
  814. // 确保婚姻状态被正确设置(0-未婚,1-离异,2-丧偶)
  815. marrStatus: marrStatusValue, // 直接使用处理后的值,不需要再次转换
  816. diploma: safeString(this.formData.diploma),
  817. income: safeString(this.formData.income),
  818. // 组合住址:省市区 + 详细地址
  819. address: this.buildFullAddress(this.addressDisplayText, this.formData.addressDetail),
  820. // 组合户籍地:省市区 + 详细地址
  821. domicile: this.buildFullAddress(this.domicileDisplayText, this.formData.domicileDetail),
  822. occupation: safeString(this.formData.occupation),
  823. house: safeParseInt(this.formData.house),
  824. car: safeParseInt(this.formData.car),
  825. phone: safeString(this.formData.phone),
  826. // 确保备用手机号被正确传递(保留有效值,null表示空)
  827. backupPhone: backupPhoneValue,
  828. // 确保择偶标准被正确传递(保留有效值,null表示空)
  829. mateSelectionCriteria: mateSelectionCriteriaValue,
  830. // 选中的标签ID列表
  831. tagIds: this.selectedTagIds.length > 0 ? this.selectedTagIds : null
  832. }
  833. console.log('提交数据 - 婚姻状态:', submitData.marrStatus, 'maritalStatusIndex:', this.maritalStatusIndex, 'formData.marrStatus:', this.formData.marrStatus, '选项:', submitData.marrStatus !== null ? this.maritalStatusOptions[submitData.marrStatus] : '未选择')
  834. console.log('提交数据 - 备用手机号:', submitData.backupPhone, '类型:', typeof submitData.backupPhone)
  835. console.log('提交数据 - 择偶标准:', submitData.mateSelectionCriteria, '类型:', typeof submitData.mateSelectionCriteria)
  836. console.log('提交数据 - 标签ID列表:', submitData.tagIds)
  837. console.log('完整提交数据:', JSON.stringify(submitData, null, 2))
  838. // 调用后端接口(通过网关)
  839. const baseUrl = process.env.NODE_ENV === 'development'
  840. ? 'http://localhost:8083/api' // 开发环境 - 通过网关
  841. : 'https://your-domain.com/api' // 生产环境
  842. // 检查手机号是否已存在(检查整个my_resource表,不限制红娘)
  843. const phone = submitData.phone
  844. const backupPhone = submitData.backupPhone
  845. // 检查是否已登录
  846. if (!this.currentUserId) {
  847. uni.hideLoading()
  848. uni.showToast({
  849. title: '请先登录',
  850. icon: 'none'
  851. })
  852. return
  853. }
  854. // 编辑模式下跳过手机号检查(因为可能是自己的手机号)
  855. // 新增模式下检查手机号是否已存在
  856. if (!this.isEditMode) {
  857. // 调用检查接口
  858. const checkUrl = `${baseUrl}/my-resource/check-phone?phone=${encodeURIComponent(phone || '')}${backupPhone ? '&backupPhone=' + encodeURIComponent(backupPhone) : ''}`
  859. const [checkError, checkRes] = await uni.request({
  860. url: checkUrl,
  861. method: 'GET'
  862. })
  863. if (checkError) {
  864. console.error('检查手机号失败:', checkError)
  865. uni.hideLoading()
  866. uni.showToast({
  867. title: '检查手机号失败,请重试',
  868. icon: 'none'
  869. })
  870. return
  871. }
  872. if (checkRes.statusCode === 200 && checkRes.data && checkRes.data.code === 200) {
  873. const checkResult = checkRes.data.data
  874. const existingResource = checkResult.existingResource
  875. // 如果发现重复的手机号,提示用户
  876. if (checkResult.phoneExists || checkResult.backupPhoneExists || checkResult.phoneAsBackupExists || checkResult.backupPhoneAsMainExists) {
  877. let message = '该手机号已经绑定资源啦'
  878. // 根据不同的检查结果,显示具体的手机号类型
  879. if (checkResult.phoneExists) {
  880. message = '该主手机号已经绑定资源啦'
  881. } else if (checkResult.phoneAsBackupExists) {
  882. message = '该主手机号已经作为备用手机号绑定资源啦'
  883. } else if (checkResult.backupPhoneAsMainExists) {
  884. message = '该备用手机号已经作为主手机号绑定资源啦'
  885. } else if (checkResult.backupPhoneExists) {
  886. message = '该备用手机号已经绑定资源啦'
  887. }
  888. uni.hideLoading()
  889. uni.showToast({
  890. title: message,
  891. icon: 'none',
  892. duration: 3000
  893. })
  894. return
  895. }
  896. }
  897. }
  898. // 根据是否为编辑模式选择不同的接口
  899. let url, method, successMessage
  900. if (this.isEditMode && this.resourceId) {
  901. // 编辑模式:调用更新接口
  902. url = `${baseUrl}/my-resource/update/${this.resourceId}`
  903. method = 'PUT'
  904. successMessage = '修改成功'
  905. console.log('编辑模式 - 更新接口URL:', url, 'resourceId:', this.resourceId)
  906. } else {
  907. // 新增模式:调用添加接口
  908. url = `${baseUrl}/my-resource/add`
  909. method = 'POST'
  910. // 确保currentUserId是有效的整数才添加到URL中
  911. if (this.currentUserId !== null && this.currentUserId !== undefined) {
  912. const userId = parseInt(this.currentUserId)
  913. if (!isNaN(userId) && userId > 0) {
  914. url += `?currentUserId=${userId}`
  915. }
  916. }
  917. console.log('新增模式 - 添加接口URL:', url)
  918. }
  919. console.log('提交请求 - URL:', url, 'Method:', method, 'isEditMode:', this.isEditMode, 'resourceId:', this.resourceId)
  920. const res = await uni.request({
  921. url: url,
  922. method: method,
  923. data: submitData,
  924. header: {
  925. 'Content-Type': 'application/json'
  926. }
  927. })
  928. uni.hideLoading()
  929. if (res[1].statusCode === 200 && res[1].data.code === 200) {
  930. if (this.isEditMode) {
  931. // 编辑模式:显示修改成功
  932. uni.showToast({
  933. title: successMessage,
  934. icon: 'success'
  935. })
  936. } else {
  937. // 新增模式:获取返回的资源类型信息
  938. const responseData = res[1].data.data
  939. const resourceType = responseData && responseData.resourceType ? responseData.resourceType : '资源'
  940. const isUser = responseData && responseData.isUser !== undefined ? responseData.isUser : null
  941. // 根据资源类型显示不同的成功提示
  942. if (isUser === 0 || resourceType === '线索') {
  943. successMessage = '提交线索成功'
  944. } else if (isUser === 1 || resourceType === '资源') {
  945. successMessage = '添加资源成功'
  946. }
  947. uni.showToast({
  948. title: successMessage,
  949. icon: 'success'
  950. })
  951. }
  952. // 延迟跳转到我的资源列表页面并刷新
  953. setTimeout(() => {
  954. if (this.isEditMode) {
  955. // 编辑模式:直接返回上一页并刷新
  956. uni.navigateBack({
  957. success: () => {
  958. // 触发刷新事件
  959. uni.$emit('refreshResourceList')
  960. }
  961. })
  962. } else {
  963. // 新增模式:获取页面栈,检查是否已经有我的资源列表页面
  964. const pages = getCurrentPages()
  965. const myResourcesPageIndex = pages.findIndex(page => {
  966. return page.route && page.route.includes('matchmaker-workbench/my-resources')
  967. })
  968. if (myResourcesPageIndex >= 0 && myResourcesPageIndex < pages.length - 1) {
  969. // 如果我的资源列表页面已经在页面栈中(且不是当前页),返回到该页面
  970. const delta = pages.length - myResourcesPageIndex - 1
  971. uni.navigateBack({
  972. delta: delta,
  973. success: () => {
  974. // 触发刷新事件
  975. uni.$emit('refreshResourceList')
  976. }
  977. })
  978. } else {
  979. // 如果我的资源列表页面不在页面栈中,跳转到该页面
  980. uni.redirectTo({
  981. url: '/pages/matchmaker-workbench/my-resources',
  982. success: () => {
  983. // 触发刷新事件
  984. uni.$emit('refreshResourceList')
  985. }
  986. })
  987. }
  988. }
  989. }, 1500)
  990. } else {
  991. uni.showToast({
  992. title: res[1].data.message || '提交失败',
  993. icon: 'none',
  994. duration: 2000
  995. })
  996. }
  997. } catch (error) {
  998. uni.hideLoading()
  999. console.error('提交失败:', error)
  1000. uni.showToast({
  1001. title: '提交失败,请稍后重试',
  1002. icon: 'none'
  1003. })
  1004. }
  1005. },
  1006. // 组合完整地址(省市区 + 详细地址)
  1007. buildFullAddress(regionText, detailText) {
  1008. let fullAddress = ''
  1009. if (regionText && regionText.trim()) {
  1010. fullAddress = regionText.trim()
  1011. }
  1012. if (detailText && detailText.trim()) {
  1013. if (fullAddress) {
  1014. fullAddress += ' ' + detailText.trim()
  1015. } else {
  1016. fullAddress = detailText.trim()
  1017. }
  1018. }
  1019. return fullAddress || null
  1020. },
  1021. // ========== 住址省市区相关方法 ==========
  1022. // 加载住址省份列表
  1023. async loadAddressProvinces() {
  1024. try {
  1025. const provinces = await api.area.getProvinces()
  1026. this.addressProvinces = provinces || []
  1027. this.addressMultiArray[0] = this.addressProvinces.map(p => p.name)
  1028. // 默认加载第一个省份的城市
  1029. if (this.addressProvinces.length > 0) {
  1030. await this.loadAddressCities(0)
  1031. }
  1032. } catch (e) {
  1033. console.error('加载住址省份失败:', e)
  1034. }
  1035. },
  1036. // 加载住址城市列表
  1037. async loadAddressCities(provinceIndex) {
  1038. const province = this.addressProvinces[provinceIndex]
  1039. if (!province || !province.id) return
  1040. try {
  1041. const cities = await api.area.getCities(province.id)
  1042. this.addressCities = cities || []
  1043. this.addressMultiArray[1] = this.addressCities.map(c => c.name)
  1044. // 默认加载第一个城市的区域
  1045. if (this.addressCities.length > 0) {
  1046. await this.loadAddressAreas(0)
  1047. } else {
  1048. this.addressMultiArray[2] = []
  1049. }
  1050. } catch (e) {
  1051. console.error('加载住址城市失败:', e)
  1052. this.addressCities = []
  1053. this.addressMultiArray[1] = []
  1054. this.addressMultiArray[2] = []
  1055. }
  1056. },
  1057. // 加载住址区域列表
  1058. async loadAddressAreas(cityIndex) {
  1059. const city = this.addressCities[cityIndex]
  1060. if (!city || !city.id) return
  1061. try {
  1062. const areas = await api.area.getAreas(city.id)
  1063. this.addressAreas = areas || []
  1064. this.addressMultiArray[2] = this.addressAreas.map(a => a.name)
  1065. } catch (e) {
  1066. console.error('加载住址区域失败:', e)
  1067. this.addressAreas = []
  1068. this.addressMultiArray[2] = []
  1069. }
  1070. },
  1071. // 住址列变化事件
  1072. async onAddressColumnChange(e) {
  1073. const column = e.detail.column
  1074. const row = e.detail.value
  1075. if (column === 0) {
  1076. // 选择了省份
  1077. this.addressMultiIndex[0] = row
  1078. this.addressMultiIndex[1] = 0
  1079. this.addressMultiIndex[2] = 0
  1080. await this.loadAddressCities(row)
  1081. } else if (column === 1) {
  1082. // 选择了城市
  1083. this.addressMultiIndex[1] = row
  1084. this.addressMultiIndex[2] = 0
  1085. await this.loadAddressAreas(row)
  1086. } else if (column === 2) {
  1087. // 选择了区域
  1088. this.addressMultiIndex[2] = row
  1089. }
  1090. },
  1091. // 住址确认选择事件
  1092. onAddressChange(e) {
  1093. const values = e.detail.value
  1094. this.addressMultiIndex = values
  1095. // 更新显示文本
  1096. const provinceName = this.addressMultiArray[0][values[0]] || ''
  1097. const cityName = this.addressMultiArray[1][values[1]] || ''
  1098. const areaName = this.addressMultiArray[2][values[2]] || ''
  1099. let displayText = ''
  1100. if (provinceName) displayText += provinceName
  1101. if (cityName) displayText += (displayText ? ' ' : '') + cityName
  1102. if (areaName) displayText += (displayText ? ' ' : '') + areaName
  1103. this.addressDisplayText = displayText
  1104. },
  1105. // ========== 户籍地省市区相关方法 ==========
  1106. // 加载户籍地省份列表
  1107. async loadDomicileProvinces() {
  1108. try {
  1109. const provinces = await api.area.getProvinces()
  1110. this.domicileProvinces = provinces || []
  1111. this.domicileMultiArray[0] = this.domicileProvinces.map(p => p.name)
  1112. // 默认加载第一个省份的城市
  1113. if (this.domicileProvinces.length > 0) {
  1114. await this.loadDomicileCities(0)
  1115. }
  1116. } catch (e) {
  1117. console.error('加载户籍地省份失败:', e)
  1118. }
  1119. },
  1120. // 加载户籍地城市列表
  1121. async loadDomicileCities(provinceIndex) {
  1122. const province = this.domicileProvinces[provinceIndex]
  1123. if (!province || !province.id) return
  1124. try {
  1125. const cities = await api.area.getCities(province.id)
  1126. this.domicileCities = cities || []
  1127. this.domicileMultiArray[1] = this.domicileCities.map(c => c.name)
  1128. // 默认加载第一个城市的区域
  1129. if (this.domicileCities.length > 0) {
  1130. await this.loadDomicileAreas(0)
  1131. } else {
  1132. this.domicileMultiArray[2] = []
  1133. }
  1134. } catch (e) {
  1135. console.error('加载户籍地城市失败:', e)
  1136. this.domicileCities = []
  1137. this.domicileMultiArray[1] = []
  1138. this.domicileMultiArray[2] = []
  1139. }
  1140. },
  1141. // 加载户籍地区域列表
  1142. async loadDomicileAreas(cityIndex) {
  1143. const city = this.domicileCities[cityIndex]
  1144. if (!city || !city.id) return
  1145. try {
  1146. const areas = await api.area.getAreas(city.id)
  1147. this.domicileAreas = areas || []
  1148. this.domicileMultiArray[2] = this.domicileAreas.map(a => a.name)
  1149. } catch (e) {
  1150. console.error('加载户籍地区域失败:', e)
  1151. this.domicileAreas = []
  1152. this.domicileMultiArray[2] = []
  1153. }
  1154. },
  1155. // 户籍地列变化事件
  1156. async onDomicileColumnChange(e) {
  1157. const column = e.detail.column
  1158. const row = e.detail.value
  1159. if (column === 0) {
  1160. // 选择了省份
  1161. this.domicileMultiIndex[0] = row
  1162. this.domicileMultiIndex[1] = 0
  1163. this.domicileMultiIndex[2] = 0
  1164. await this.loadDomicileCities(row)
  1165. } else if (column === 1) {
  1166. // 选择了城市
  1167. this.domicileMultiIndex[1] = row
  1168. this.domicileMultiIndex[2] = 0
  1169. await this.loadDomicileAreas(row)
  1170. } else if (column === 2) {
  1171. // 选择了区域
  1172. this.domicileMultiIndex[2] = row
  1173. }
  1174. },
  1175. // 户籍地确认选择事件
  1176. onDomicileChange(e) {
  1177. const values = e.detail.value
  1178. this.domicileMultiIndex = values
  1179. // 更新显示文本
  1180. const provinceName = this.domicileMultiArray[0][values[0]] || ''
  1181. const cityName = this.domicileMultiArray[1][values[1]] || ''
  1182. const areaName = this.domicileMultiArray[2][values[2]] || ''
  1183. let displayText = ''
  1184. if (provinceName) displayText += provinceName
  1185. if (cityName) displayText += (displayText ? ' ' : '') + cityName
  1186. if (areaName) displayText += (displayText ? ' ' : '') + areaName
  1187. this.domicileDisplayText = displayText
  1188. }
  1189. }
  1190. }
  1191. </script>
  1192. <style lang="scss" scoped>
  1193. .resource-input-page {
  1194. min-height: 100vh;
  1195. background: #FFF9F9;
  1196. display: flex;
  1197. flex-direction: column;
  1198. }
  1199. .header {
  1200. display: flex;
  1201. align-items: center;
  1202. justify-content: space-between;
  1203. padding: 25rpx 30rpx;
  1204. padding-top: calc(25rpx + env(safe-area-inset-top));
  1205. background: #FFF9F9;
  1206. border-bottom: 1rpx solid #F0F0F0;
  1207. .header-left {
  1208. width: 60rpx;
  1209. }
  1210. .back-icon {
  1211. font-size: 40rpx;
  1212. color: #333;
  1213. }
  1214. .header-title {
  1215. flex: 1;
  1216. text-align: center;
  1217. font-size: 38rpx;
  1218. font-weight: bold;
  1219. color: #9C27B0;
  1220. }
  1221. .header-right {
  1222. width: 60rpx;
  1223. text-align: right;
  1224. }
  1225. .more-icon {
  1226. font-size: 40rpx;
  1227. color: #333;
  1228. }
  1229. }
  1230. .content {
  1231. flex: 1;
  1232. padding-bottom: 120rpx;
  1233. }
  1234. .form-container {
  1235. padding: 20rpx;
  1236. }
  1237. .form-section {
  1238. background: #FFFFFF;
  1239. border-radius: 20rpx;
  1240. padding: 30rpx;
  1241. margin-bottom: 20rpx;
  1242. border: 2rpx solid transparent;
  1243. &.active {
  1244. border-color: #FF4081;
  1245. }
  1246. .section-title {
  1247. font-size: 32rpx;
  1248. font-weight: bold;
  1249. color: #333;
  1250. margin-bottom: 30rpx;
  1251. padding-bottom: 15rpx;
  1252. border-bottom: 2rpx solid #F0F0F0;
  1253. }
  1254. }
  1255. .form-item {
  1256. margin-bottom: 30rpx;
  1257. &:last-child {
  1258. margin-bottom: 0;
  1259. }
  1260. .form-label {
  1261. display: block;
  1262. font-size: 28rpx;
  1263. color: #666;
  1264. margin-bottom: 15rpx;
  1265. .required {
  1266. color: #FF4444;
  1267. margin-left: 5rpx;
  1268. }
  1269. }
  1270. .category-buttons {
  1271. display: flex;
  1272. flex-wrap: wrap;
  1273. gap: 12rpx;
  1274. margin-bottom: 20rpx;
  1275. .category-btn {
  1276. padding: 10rpx 20rpx;
  1277. background: #F8F8F8;
  1278. border: 2rpx solid #E0E0E0;
  1279. border-radius: 20rpx;
  1280. transition: all 0.3s;
  1281. &.active {
  1282. background: linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%);
  1283. border-color: #9C27B0;
  1284. .category-btn-text {
  1285. color: #FFFFFF;
  1286. font-weight: 600;
  1287. }
  1288. }
  1289. .category-btn-text {
  1290. font-size: 24rpx;
  1291. color: #666;
  1292. }
  1293. }
  1294. }
  1295. .tags-container {
  1296. display: flex;
  1297. flex-wrap: wrap;
  1298. gap: 15rpx;
  1299. margin-top: 10rpx;
  1300. min-height: 400rpx; /* 设置最小高度,确保空间不变 */
  1301. align-content: flex-start; /* 标签从顶部开始排列 */
  1302. .tag-item {
  1303. padding: 12rpx 24rpx;
  1304. background: #F8F8F8;
  1305. border: 2rpx solid #E0E0E0;
  1306. border-radius: 24rpx;
  1307. font-size: 26rpx;
  1308. color: #666;
  1309. transition: all 0.3s;
  1310. flex-shrink: 0; /* 防止标签被压缩 */
  1311. &.selected {
  1312. background: linear-gradient(135deg, #F3E5F5 0%, #E1BEE7 100%);
  1313. border-color: #9C27B0;
  1314. color: #7B1FA2;
  1315. font-weight: 600;
  1316. box-shadow: 0 2rpx 8rpx rgba(156, 39, 176, 0.2);
  1317. }
  1318. .tag-text {
  1319. font-size: 26rpx;
  1320. }
  1321. }
  1322. .tag-loading {
  1323. padding: 20rpx;
  1324. color: #999;
  1325. font-size: 26rpx;
  1326. text-align: center;
  1327. width: 100%;
  1328. min-height: 400rpx; /* 加载时也保持固定高度 */
  1329. display: flex;
  1330. align-items: center;
  1331. justify-content: center;
  1332. }
  1333. .tag-empty {
  1334. padding: 20rpx;
  1335. color: #999;
  1336. font-size: 26rpx;
  1337. text-align: center;
  1338. width: 100%;
  1339. min-height: 400rpx; /* 空状态时也保持固定高度 */
  1340. display: flex;
  1341. align-items: center;
  1342. justify-content: center;
  1343. }
  1344. }
  1345. .form-input {
  1346. width: 100%;
  1347. height: 80rpx;
  1348. background: #F8F8F8;
  1349. border: 2rpx solid #E0E0E0;
  1350. border-radius: 10rpx;
  1351. padding: 0 20rpx;
  1352. font-size: 28rpx;
  1353. color: #333;
  1354. box-sizing: border-box;
  1355. &:focus {
  1356. border-color: #9C27B0;
  1357. background: #FFFFFF;
  1358. }
  1359. }
  1360. .picker-input {
  1361. display: flex;
  1362. align-items: center;
  1363. justify-content: space-between;
  1364. .placeholder {
  1365. color: #999;
  1366. }
  1367. .picker-text {
  1368. color: #333;
  1369. flex: 1;
  1370. }
  1371. .picker-arrow {
  1372. font-size: 24rpx;
  1373. color: #999;
  1374. }
  1375. }
  1376. .address-detail-input {
  1377. margin-top: 15rpx;
  1378. }
  1379. }
  1380. .submit-bar {
  1381. position: fixed;
  1382. bottom: 0;
  1383. left: 0;
  1384. right: 0;
  1385. padding: 20rpx 30rpx;
  1386. padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
  1387. background: #FFFFFF;
  1388. border-top: 1rpx solid #F0F0F0;
  1389. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
  1390. .submit-btn {
  1391. width: 100%;
  1392. height: 88rpx;
  1393. background: linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%);
  1394. color: #FFFFFF;
  1395. font-size: 32rpx;
  1396. font-weight: bold;
  1397. border-radius: 44rpx;
  1398. border: none;
  1399. display: flex;
  1400. align-items: center;
  1401. justify-content: center;
  1402. }
  1403. }
  1404. </style>