index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. <template>
  2. <view class="register-main">
  3. <view class="register-user-info">
  4. <FontTitle title="请完成注册信息填报" />
  5. <CustForm :column="com_column" ref="cust_form_ref" :isCode="isAdd" />
  6. </view>
  7. <view class="register-card">
  8. <view class="info-list">
  9. <view class="font-title">服务类别( {{ serviceOptions.length }} )</view>
  10. <view class="service-list">
  11. <view v-for="item in serviceOptions" :key="item.id" @click="serviceChange(item, 'classKey')"
  12. :class="serviceKeys.classKey === item.id ? 'servicetab classActive' : 'servicetab'">
  13. {{ item.businessName }}
  14. </view>
  15. </view>
  16. <view v-if="serviceKeys.classKey && serviceItems && serviceItems.length > 0">
  17. <view class="font-title">服务项目</view>
  18. <view class="service-list">
  19. <view v-for="item in serviceItems" :key="item.id" @click="serviceChange(item, 'itemKey')"
  20. :class="serviceKeys.itemKey === item.id ? 'servicetab classActive' : 'servicetab'">
  21. {{ item.businessName }}
  22. </view>
  23. </view>
  24. </view>
  25. <view v-if="serviceKeys.classKey && serviceItems2 && serviceItems2.length > 0">
  26. <view class="font-title">服务项</view>
  27. <view class="service-list">
  28. <view v-for="item in serviceItems2" :key="item.id" @click="serviceChange(item, 'threeKey')"
  29. :class="serviceKeys.threeKey === item.id ? 'servicetab classActive' : 'servicetab'">
  30. {{ item.businessName }}
  31. </view>
  32. </view>
  33. </view>
  34. <view v-if="serviceKeys.itemKeyname || serviceKeys.classKeyname">
  35. <view class="font-title">服务时长(<text class="activeColor">{{ serviceKeys.threeKeyname ||
  36. serviceKeys.itemKeyname ||
  37. serviceKeys.classKeyname }}</text>)</view>
  38. <view class="service-list">
  39. <view v-for="item in timeList" :key="item.id" @click="serviceChange(item, 'time')"
  40. :class="serviceKeys.time === item.id ? 'servicetab classActive' : 'servicetab'">
  41. {{ item.lable }}
  42. </view>
  43. </view>
  44. <view class="font-title">服务价格(<text class="activeColor">{{ serviceKeys.threeKeyname ||
  45. serviceKeys.itemKeyname ||
  46. serviceKeys.classKeyname }}</text>)</view>
  47. <view class="service-list price-box">
  48. <up-input v-model="serviceKeys.price"
  49. :placeholder="min_max_price.minPrice && min_max_price.maxPrice ? `${min_max_price.minPrice}-${min_max_price.maxPrice}` : '请设置服务价格'"
  50. @blur="validatePriceInput" class="price-input">
  51. <template #suffix>
  52. <text>元</text>
  53. </template>
  54. </up-input>
  55. </view>
  56. <view class="price-describe" v-if="min_max_price.minPrice && min_max_price.maxPrice">
  57. 价格说明
  58. </view>
  59. </view>
  60. </view>
  61. </view>
  62. <view class="register-card" v-if="details.appStatus === '3'">
  63. <view class="font-title">驳回原因</view>
  64. <view class="info-list">
  65. {{ details.rejectReason }}
  66. </view>
  67. </view>
  68. <view v-for="item in updata_list" :key="item.key" class="updata-imgs">
  69. <UpdataImgs :fileList="file_url[item.key]" :data="item" ref="zsImg" @onSubmit="onChange" />
  70. </view>
  71. <!-- <up-button color="rgba(221, 94, 69, 1)" text="确定" @click="onSubmit" ></up-button> -->
  72. <view class="status-btn" @click="onSubmit">确定</view>
  73. </view>
  74. </template>
  75. <script setup>
  76. import { ref, reactive, onMounted, nextTick } from 'vue';
  77. import { onLoad } from '@dcloudio/uni-app';
  78. import FontTitle from "@/pages_home/components/font-title/index.vue";
  79. import CustForm from "@/pages_home/components/cust-form/index";
  80. import UpdataImgs from "@/pages_home/components/updata-imgs/index.vue";
  81. import { column } from "./data";
  82. import { add, getVolunteerInfo } from "@/api/volunteer";
  83. import { computed } from 'vue';
  84. import { getTreeList } from '@/api/volunteer'
  85. const userImg = ref(null);
  86. const zsImg = ref(null);
  87. const min_max_price = ref({
  88. minPrice: 10,
  89. maxPrice: 100
  90. })
  91. const updata_list = [
  92. {
  93. title: '上传头像',
  94. text: '上传您的头像',
  95. img: '/static/img/updata-user-img.png',
  96. key: 'volunteerPicture',
  97. ref: userImg,
  98. // permission: [1, 2],
  99. required: true
  100. },
  101. {
  102. title: '个人身份证',
  103. text: '上传您的个人身份证',
  104. img: '/static/img/updata-user-img.png',
  105. key: 'idCardPicture',
  106. ref: zsImg,
  107. // permission: [1, 2],
  108. required: true
  109. },
  110. {
  111. title: '职业、资质证书',
  112. text: '上传您的职业、资质证书',
  113. img: '/static/img/updata-user-img.png',
  114. key: 'certificationPicture',
  115. ref: zsImg,
  116. // permission: [1, 2],
  117. required: false
  118. }
  119. ]
  120. const cust_form_ref = ref(null);
  121. const data = ref(null);
  122. const file_url = reactive({});
  123. const isAdd = ref(true);//是否已经注册
  124. const details = ref({});//详情数据
  125. const serviceOptions = ref([]);//服务类目
  126. const serviceItems = ref([]);//服务项目
  127. const serviceItems2 = ref([]);//服务小类目
  128. const serviceKeys = reactive({
  129. classKey: '',//服务类别
  130. classKeyname: '',
  131. itemKey: '',//服务项目
  132. itemKeyname: '',
  133. threeKey: '',//服务项
  134. threeKeyname: '',
  135. time: '',//时间
  136. price: '',//价格
  137. })
  138. const timeList = [
  139. {
  140. lable: '60分钟',
  141. id: 60
  142. },
  143. {
  144. lable: '90分钟',
  145. id: 90
  146. },
  147. {
  148. lable: '120分钟',
  149. id: 120
  150. },
  151. ]
  152. const sex_status = {
  153. '男': 0,
  154. '女': 1
  155. }
  156. const validatePriceInput = (value) => {
  157. console.log("TCL: validatePriceInput -> value", value)
  158. // 确保输入是纯数字
  159. if (!/^\d*$/.test(value)) {
  160. serviceKeys.price = value.replace(/\D/g, ''); // 移除非数字字符
  161. return;
  162. }
  163. const price = parseInt(serviceKeys.price, 10);
  164. // 检查是否在允许的范围之内
  165. if (min_max_price.value.minPrice !== undefined && min_max_price.value.maxPrice !== undefined) {
  166. if (price >= min_max_price.value.minPrice && price <= min_max_price.value.maxPrice) {
  167. serviceKeys.price = serviceKeys.price.toString();
  168. } else {
  169. serviceKeys.price = null;
  170. uni.showToast({
  171. title: '请输入价格区间内的价格',
  172. icon: 'none'
  173. })
  174. console.log("TCL: validatePriceInput -> error", value)
  175. }
  176. }
  177. };
  178. //根据类型获取表单item 值
  179. const com_column = computed(() => {
  180. let column_list = data.value ? column : [];
  181. return column_list
  182. })
  183. function onSubmit() {
  184. try {
  185. // return;
  186. // 校验表单并获取数据
  187. cust_form_ref.value.onSubmit().then(async (res) => {
  188. //文件必传校验
  189. for (let i = 0; i < updata_list.length; i++) {
  190. const element = updata_list[i];
  191. const type = element.required && !file_url[element.key];
  192. if (type) {
  193. uni.showToast({
  194. title: '请上传' + element.title,
  195. icon: 'error'
  196. })
  197. return;
  198. }
  199. }
  200. if (!(serviceKeys.threeKeyname || serviceKeys.itemKeyname || serviceKeys.classKeyname)) {
  201. uni.showToast({
  202. title: '请选择服务',
  203. icon: 'none'
  204. })
  205. return
  206. }
  207. if (!serviceKeys.time) {
  208. uni.showToast({
  209. title: '请选择服务时长',
  210. icon: 'none'
  211. })
  212. return
  213. }
  214. if (!serviceKeys.price) {
  215. uni.showToast({
  216. title: '请输入服务价格',
  217. icon: 'none'
  218. })
  219. return
  220. }
  221. const parmas = {
  222. serviceCategory: data.value.key,
  223. ...file_url,
  224. businessManagementId: serviceKeys.threeKey || serviceKeys.itemKey || serviceKeys.classKey,
  225. businessPrice: serviceKeys.price,
  226. businessDuration: serviceKeys.time
  227. };
  228. for (const key in res) {
  229. parmas[key] = key == 'sex' ? sex_status[res[key]] : res[key];
  230. if (key === 'businessManagementIdkey') {
  231. parmas['businessManagementId'] = res[key]
  232. delete parmas['businessManagementIdkey'];
  233. }
  234. }
  235. console.log('提交', parmas);
  236. // return;
  237. // 提交接口,注册人员
  238. const submit_res = await add(parmas);
  239. if (submit_res.code == 200) {
  240. uni.showToast({
  241. title: '申请成功',
  242. icon: 'success',
  243. success: () => {
  244. setTimeout(() => {
  245. uni.navigateBack();
  246. }, 1000)
  247. }
  248. })
  249. return;
  250. }
  251. uni.showToast({
  252. title: res.msg,
  253. icon: 'none'
  254. })
  255. })
  256. } catch (error) {
  257. console.log('error', error);
  258. } finally {
  259. }
  260. }
  261. function onChange({ key, url }) {
  262. Object.assign(file_url, {
  263. [key]: url
  264. })
  265. }
  266. function handlerList(array, targetId, path = []) {
  267. for (let i = 0; i < array.length; i++) {
  268. const item = array[i];
  269. const currentPath = path.concat(i);
  270. if (item.id === targetId) {
  271. return item
  272. }
  273. if (item.children) {
  274. const result = handlerList(item.children, targetId, currentPath);
  275. if (result) {
  276. return result;
  277. }
  278. }
  279. }
  280. return null; // 如果没有找到对应的项,返回 null
  281. }
  282. /**
  283. * 根据目标id查找所有父级路径
  284. * @param {Array} data 树形数据源(如 serviceOptions.value)
  285. * @param {String|Number} targetId 要查找的目标id
  286. * @param {String} [idKey='id'] id字段名
  287. * @param {String} [childrenKey='children'] 子级字段名
  288. * @returns {Object|null}
  289. */
  290. function findParentPath(data, targetId, idKey = 'id', childrenKey = 'children') {
  291. for (const node of data) {
  292. // 当前节点匹配
  293. if (node[idKey] == targetId) {
  294. return {
  295. parentIdPath: [node[idKey]],
  296. businessTierName: node.businessName || ''
  297. };
  298. }
  299. // 搜索子级
  300. const result = node[childrenKey]?.length > 0
  301. ? findParentPath(node[childrenKey], targetId, idKey, childrenKey)
  302. : null;
  303. if (result) {
  304. return {
  305. parentIdPath: [...result.parentIdPath, node[idKey]],
  306. businessTierName: `${result.businessTierName}-${node.businessName || ''}`
  307. };
  308. }
  309. }
  310. return null;
  311. }
  312. function idToIndexs(targetId) {
  313. const res = findParentPath(serviceOptions.value, targetId);
  314. return res || {
  315. parentIdPath: [],
  316. businessTierName: ''
  317. };
  318. }
  319. const backfill = {
  320. 2: 'threeKey',
  321. 1: 'itemKey',
  322. 0: 'classKey',
  323. }
  324. //重新提交的服务信息回显
  325. function servesInit() {
  326. const indexs = idToIndexs(details.value.businessManagementId + '');
  327. const names = indexs.businessTierName.split('-').reverse();
  328. const ids = indexs.parentIdPath.reverse();
  329. const obj = {};
  330. for (let i = 0; i < names.length; i++) {
  331. obj[backfill[i]] = ids[i];
  332. obj[backfill[i] + 'name'] = names[i];
  333. }
  334. console.log('obj', obj, names, ids);
  335. console.log('indexs', indexs, names, ids);
  336. // const indexs = idToIndexs(serviceOptions.value, details.value.businessManagementId + '')
  337. // const names = indexs.businessTierName.split('-');
  338. // console.log('indexs',indexs,names);
  339. if (obj.itemKey) {
  340. const row = handlerList(serviceOptions.value, obj.classKey)
  341. console.log(1, row);
  342. serviceItems.value = row.children;
  343. }
  344. if (obj.threeKey) {
  345. const row = handlerList(serviceOptions.value, obj.itemKey)
  346. console.log(2, row);
  347. serviceItems2.value = row.children;
  348. }
  349. Object.assign(serviceKeys, {
  350. time: details.value.businessDuration,//时间
  351. price: details.value.businessPrice,//价格
  352. ...obj
  353. })
  354. handlerList(serviceOptions.value, serviceKeys.itemKey)
  355. console.log('serviceKeys', serviceKeys);
  356. }
  357. async function getRegister() {
  358. try {
  359. uni.showLoading({
  360. title: '数据加载中...'
  361. });
  362. const res = await getVolunteerInfo({ serviceCategory: data.value.key });
  363. if (res.data) {
  364. details.value = { ...res.data, age: res.data.age + '' };
  365. cust_form_ref.value.setData(details.value);
  366. Object.assign(file_url, {
  367. volunteerPicture: res.data.volunteerPicture,
  368. idCardPicture: res.data.idCardPicture,
  369. certificationPicture: res.data.certificationPicture
  370. })
  371. servesInit(data)
  372. isAdd.value = false;
  373. }
  374. if (data.value.record) {
  375. Object.assign(serviceKeys, {
  376. classKey: data.value.record.id,//服务类别
  377. classKeyname: data.value.record.businessName,
  378. })
  379. }
  380. } catch (error) {
  381. console.log('error', error);
  382. uni.showToast({
  383. title: error.msg,
  384. icon: 'error',
  385. });
  386. } finally {
  387. uni.hideLoading();
  388. }
  389. }
  390. const serviceChange = (item, key) => {
  391. console.log("TCL: serviceChange -> item", item)
  392. if (item.maxPrice && item.minPrice) {
  393. min_max_price.value = {
  394. minPrice: item.minPrice,
  395. maxPrice: item.maxPrice,
  396. }
  397. }
  398. if (key === 'classKey') {
  399. serviceItems.value = item.children;
  400. serviceKeys['itemKey'] = '';
  401. serviceKeys['itemKeyname'] = '';
  402. serviceKeys['threeKey'] = '';
  403. serviceKeys['threeKeyname'] = '';
  404. serviceItems2.value = [];
  405. }
  406. if (key === 'itemKey') {
  407. serviceItems2.value = item.children;
  408. serviceKeys['threeKey'] = '';
  409. serviceKeys['threeKeyname'] = '';
  410. }
  411. Object.assign(serviceKeys, {
  412. [key]: serviceKeys[key] === item.id ? '' : item.id,
  413. [key + 'name']: serviceKeys[key + 'name'] === item.businessName ? '' : item.businessName,
  414. })
  415. }
  416. const getTreeListInit = () => {
  417. getTreeList({ parentId: data.value.key }).then(res => {
  418. serviceOptions.value = res.data;
  419. })
  420. }
  421. onMounted(() => {
  422. getTreeListInit();
  423. })
  424. onLoad((options) => {
  425. const option = JSON.parse(decodeURIComponent(options.data));
  426. data.value = option;
  427. console.log("option", data.value);
  428. uni.setNavigationBarTitle({
  429. title: option.name // 根据业务逻辑调整
  430. });
  431. setTimeout(() => {
  432. getRegister();
  433. }, 500);
  434. })
  435. </script>
  436. <style lang="scss" scoped>
  437. .register-main {
  438. padding: 12px;
  439. background-color: rgba(245, 245, 245, 1);
  440. // height: 100vh;
  441. .register-user-info {
  442. margin-bottom: 12px;
  443. background-color: #fff;
  444. border-radius: 8px;
  445. padding: 18px 16px;
  446. }
  447. .updata-imgs {
  448. margin-bottom: 12px;
  449. }
  450. }
  451. .register-card {
  452. margin-bottom: 12px;
  453. background-color: #fff;
  454. border-radius: 8px;
  455. padding: 18px 16px;
  456. }
  457. .info-list {
  458. flex: 1;
  459. font-size: 14px;
  460. font-weight: 500;
  461. letter-spacing: 0px;
  462. line-height: 23.27px;
  463. color: rgba(51, 51, 51, 1);
  464. }
  465. .status-btn {
  466. // width: 716rpx;
  467. height: 96rpx;
  468. border-radius: 16rpx;
  469. background: rgba(221, 94, 69, 1);
  470. display: flex;
  471. align-items: center;
  472. justify-content: center;
  473. font-size: 32rpx;
  474. font-weight: 400;
  475. color: rgba(255, 255, 255, 1);
  476. margin-bottom: 88rpx;
  477. }
  478. .font-title {
  479. margin-bottom: 32rpx;
  480. }
  481. .service-tile {
  482. font-size: 32rpx;
  483. font-weight: 400;
  484. line-height: 48rpx;
  485. color: rgba(51, 51, 51, 1);
  486. margin-bottom: 16rpx;
  487. }
  488. .service-list {
  489. display: flex;
  490. align-items: flex-start;
  491. flex-wrap: wrap;
  492. margin-bottom: 16rpx;
  493. }
  494. .servicetab {
  495. background: rgba(249, 250, 251, 1);
  496. color: rgba(51, 51, 51, 1);
  497. border: 1px solid #fff;
  498. padding: 8rpx 16rpx;
  499. border-radius: 16rpx;
  500. margin-right: 16rpx;
  501. margin-bottom: 16rpx;
  502. }
  503. .classActive {
  504. background: rgba(251, 229, 225, 1);
  505. color: rgba(221, 94, 69, 1);
  506. border: 1px solid rgba(221, 94, 69, 1);
  507. }
  508. .activeColor {
  509. color: rgba(221, 94, 69, 1);
  510. }
  511. .price-box {
  512. display: flex;
  513. align-items: center;
  514. }
  515. .price-describe {
  516. font-size: 24rpx;
  517. font-weight: 500;
  518. letter-spacing: 0rpx;
  519. line-height: 34.76rpx;
  520. color: rgba(153, 153, 153, 1);
  521. margin-top: 16rpx;
  522. }
  523. </style>