index.vue 14 KB

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