index.vue 17 KB

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