index.vue 17 KB

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