goodsDetails.vue 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
  1. <template>
  2. <view>
  3. <!-- 顶部轮播图 -->
  4. <view>
  5. <Detiles></Detiles>
  6. </view>
  7. <!-- 页面背景整体定位 -->
  8. <view class="service-description-position">
  9. <!-- 价格服务介绍 -->
  10. <view class="service-description-container">
  11. <view class="service-text">{{ listData.businessDescribe }}</view>
  12. <view class="service-price-tag">¥{{ listData.businessPrice }}</view>
  13. </view>
  14. </view>
  15. <view class="service-description-box">
  16. <up-card title="志愿者介绍" :head-style="{ height: '80rpx', padding: '20rpx', fontWeight: 'bold' }">
  17. <template #body>
  18. <view class="volunteer-card">
  19. <!-- 左侧图片 -->
  20. <image class="volunteer-image" :src="listData.volunteerPicture" mode="aspectFill"></image>
  21. <!-- 中间信息(姓名/类别等) -->
  22. <view class="volunteer-info">
  23. <view class="info-row">
  24. <text class="info-label">姓名:</text>
  25. <text class="info-value">{{ listData.name }}</text>
  26. <!-- 顶部轮播图 -->
  27. <view>
  28. <Detiles></Detiles>
  29. </view>
  30. <!-- 页面背景整体定位 -->
  31. <view class="service-description-position">
  32. <!-- 价格服务介绍 -->
  33. <view class="service-description-container">
  34. <view class="service-price-top">
  35. <view class="service-price-tag1">¥</view>
  36. <view class="service-price-tag2">{{ listData.businessPrice }}</view>
  37. <view class="service-price-tag3">/{{ listData.businessUnit }}</view>
  38. </view>
  39. <view class="service-text">{{ listData.businessTierName }}</view>
  40. </view>
  41. <!-- 预约-资质认证 -->
  42. <view class="service-description-bottom">
  43. <view class="service-item">
  44. <image src="/static/img/check-fill@1x.png" class="service-description-bottom-img"></image>
  45. <text class="service-description-bottom-text">提前预约</text>
  46. </view>
  47. <view class="service-item">
  48. <image src="/static/img/check-fill@1x.png" class="service-description-bottom-img"></image>
  49. <text class="service-description-bottom-text">通过专业资质认证</text>
  50. </view>
  51. <view class="service-item">
  52. <image src="/static/img/check-fill@1x.png" class="service-description-bottom-img"></image>
  53. <text class="service-description-bottom-text">技术人员专业培训</text>
  54. </view>
  55. </view>
  56. </view>
  57. <!-- 志愿者介绍 -->
  58. <view class="volunteer-Introduction">
  59. <view class="volunteer-Introduction-top">
  60. <!-- 主体内容 -->
  61. <view class="volunteer-main-content">
  62. <!-- 头像 -->
  63. <image class="volunteer-image" :src="listData.volunteerPicture" mode="aspectFill"></image>
  64. <!-- 个人信息 -->
  65. <view class="volunteer-info">
  66. <view class="name-row">
  67. <text class="name">{{ listData.name || '陈阿姨' }}</text>
  68. <view class="stat-item">
  69. <image src="/static/img/Iconly@1x (1).png"></image>
  70. <text>5星好评</text>
  71. </view>
  72. <view class="stat-item">
  73. <image src="/static/img/Iconly@1x (2).png"></image>
  74. <text>{{ listData.orderCount || 0 }}总单量</text>
  75. </view>
  76. </view>
  77. <view class="contact-row">
  78. <text>{{ listData.age }}岁</text>
  79. <text>{{ listData.phonenumber || '1554****9215' }}</text>
  80. </view>
  81. </view>
  82. </view>
  83. <!-- 查看个人主页 -->
  84. <view class="view-profile">
  85. <text>查看个人主页</text>
  86. <image src="/static/img/Iconly@1x (3).png"></image>
  87. </view>
  88. <!-- 标签 -->
  89. <view class="tags-container">
  90. <view class="tag">{{ listData.projectName }}</view>
  91. <view class="tag">{{ listData.projectTypeName }}</view>
  92. </view>
  93. </view>
  94. <!-- 个人介绍 -->
  95. <view>
  96. <view class="volunteer-H1">个人介绍</view>
  97. <text class="volunteer-H1-text"> {{ listData.skillDescribe }}</text>
  98. </view>
  99. <!-- -资质证书 -->
  100. <view class="certificate-more-container">
  101. <view class="volunteer-H1">
  102. 资质证书
  103. <view class="certificate-more">
  104. <text>查看更多</text>
  105. <image src="/static/img/Iconly@1x (4).png" class="certificate-arrow-icon"></image>
  106. </view>
  107. </view>
  108. <view class="certificate-container">
  109. <up-image v-for="item in certificationPictures" :key="item" :src="item" class="certificate-img"
  110. mode="aspectFill"></up-image>
  111. </view>
  112. </view>
  113. </view>
  114. <!-- 评论 -->
  115. <view class="comment-container">
  116. <view class="comment-top">
  117. <view class="comment-top-H2">评价(19条)</view>
  118. <view class="comment-top-text">
  119. <text>好评率100%</text>
  120. <image src="/static/img/Iconly@1x (4).png" class="certificate-arrow-img"></image>
  121. </view>
  122. </view>
  123. <view class="comment-evaluate">
  124. <view class="comment-user-title">
  125. <img src="/static/img/容器 162@1x.png" alt="" class="comment-user-img1">
  126. <view class="user-rating">
  127. <view class="user-name">宋小峰</view>
  128. <view class="rating-stars">
  129. <img v-for="item in 5" :key="item" src="/static/img/容器@1x (3).png" alt="" class="comment-user-img2">
  130. </view>
  131. </view>
  132. </view>
  133. <view class="comment-evaluate-text">
  134. <text class="comment-evaluate-text-text">
  135. 重庆永川区好评重庆永川区好评重庆永川区好评
  136. 重庆永川区好评重庆永川区
  137. 重庆永川区好评重庆永川
  138. 区好评重庆永川区好评
  139. 好评重庆永川区好评
  140. </text>
  141. <img src="/static/img/矩形 55145@1x.png" alt="" class="comment-evaluate-img3">
  142. </view>
  143. </view>
  144. </view>
  145. <view class="service-description-box">
  146. <!-- 固定底部按钮区 -->
  147. <view class="Wrap-btn">
  148. <uni-goods-nav :fill="true" :options="options" :buttonGroup="buttonGroup" @click="onClick"
  149. @buttonClick="buttonClick" />
  150. </view>
  151. </view>
  152. <!-- 底部第一层弹框 -->
  153. <view>
  154. <up-popup :show="show" @close="close" @open="open"> </up-popup>
  155. <up-popup :show="show" @open="upPopupOpen" :custom-style="popupStyle">
  156. <scroll-view scroll-y class="popup-scroll-content">
  157. <!-- <view>
  158. <view class="Wrapper">
  159. <image src="/static/img/Location.png" class="Wrapper-img" />
  160. <span class="Wrapper-content">李四</span>
  161. <span class="Wrapper-content">重庆永川区</span>
  162. </view>
  163. <span style="margin-left: 15rpx; margin-top: 50rpx">重庆永川区</span>
  164. </view>
  165. <up-divider></up-divider> -->
  166. <view class="Wrap-content1">
  167. <up-avatar :src="src"></up-avatar>
  168. <text class="service-price">¥{{ businessPrice }}</text>
  169. </view>
  170. <view class="service-info-item">
  171. <text class="service-label1">服务类别:{{ listData.businessTierName }}</text>
  172. <!-- <up-text type="info" class="service-value" text="家庭教育"></up-text> -->
  173. </view>
  174. <view class="service-info-item">
  175. <text class="service-label">上门服务次数:</text>
  176. <up-number-box v-model="singleQuantity" :min="listData.minQuantity" :max="minQuantityMax"
  177. class="service-number-box" :disabled="isChanging" :longPress="false" @change="valChange"></up-number-box>
  178. <view class="service-values">{{ listData.businessUnit }}</view>
  179. </view>
  180. <view class="service-period">
  181. <text class="service-label">服务周期:</text>
  182. <view class="date-picker-container">
  183. <uniDatetimePickerMy :modelValue="doorToDoorTime" :start="startDisabled" :end="endDisabled"
  184. type="daterange">
  185. </uniDatetimePickerMy>
  186. </view>
  187. <view>
  188. <!-- :businessDuration="listData.businessDuration" -->
  189. <its-calendar v-if="show" ref="itsCalendarRef" :businessDuration="businessDurationComputed"
  190. :minQuantity="singleQuantity" :timeArr="doorToDoorTimeArr" :timeHostArr="timeHostArr"
  191. :businessTierName="listData.businessTierName" class="calendar-component" @getByDate="getByDate"
  192. @getByTime="getByTime" @changeMinQuantityMax="changeMinQuantityMax"></its-calendar>
  193. </view>
  194. </view>
  195. <view class="remark-container">
  196. <text class="service-label">备注:</text>
  197. <up-input placeholder="请输入内容" border="surround" v-model="remark" class="remark-input"
  198. @change="change"></up-input>
  199. </view>
  200. <!-- 保持内容底部有足够的空白以避免按钮遮挡内容 -->
  201. <view style="height: 120rpx;"></view>
  202. </scroll-view>
  203. <!-- 将按钮移到scroll-view外部,但保持在popup内 -->
  204. <view class="popup-fixed-bottom">
  205. <up-button type="primary" shape="circle" :customStyle="wrapqx" @click="handlCancel">取消</up-button>
  206. <up-button type="error" shape="circle" :customStyle="wrapqx" @click="handleBuy">
  207. 预约¥{{ computeMoney.toFixed(2) }}
  208. </up-button>
  209. </view>
  210. </up-popup>
  211. </view>
  212. <!-- 第二个弹框-->
  213. <up-popup :show="showSecond">
  214. <view>
  215. <up-cell-group>
  216. <view @click="jumpToAddressSelect" style="display: flex; align-items: center; padding: 10px">
  217. <up-icon name="more-dot-fill" size="16" />
  218. <view v-if="selectedAddress" style="margin-left: 8px; flex: 1">
  219. <view class="address-display">
  220. <text class="address-line">
  221. {{ selectedAddress.provinceName }}{{ selectedAddress.cityName
  222. }}{{ selectedAddress.districtName }}
  223. </text>
  224. <view class="contact-info">
  225. <text class="contact-name">{{ selectedAddress.name }}</text>
  226. <text class="contact-phone">{{
  227. selectedAddress.telephone
  228. }}</text>
  229. </view>
  230. </view>
  231. </view>
  232. <text v-else style="margin-left: 8px; font-size: 18px">请选择服务地址</text>
  233. </view>
  234. <scroll-view scroll-y style="height: 460rpx">
  235. <view class="card-container" v-for="(item, index) in selectedTimes" :key="index">
  236. <image class="card-image" :src="listData.volunteerPicture" mode="aspectFill"></image>
  237. <view class="card-content">
  238. <view class="info-item">服务项目:{{ listData.projectName }}</view>
  239. <view class="Telephone">服务时长:{{ listData.businessDuration }}</view>
  240. <view class="date">日期:{{ item.date }}</view>
  241. <view class="time"> 时间:{{ item.time }} </view>
  242. </view>
  243. </view>
  244. </scroll-view>
  245. </up-cell-group>
  246. </view>
  247. <view class="Wrap-Payment">
  248. <!-- 支付金额 -->
  249. <view class="payment-header">
  250. <text class="payment-title">现在支付</text>
  251. <text class="payment-amount">¥{{ computeMoney }}</text>
  252. </view>
  253. <up-line></up-line>
  254. <!-- 钱包支付选项 -->
  255. <view class="payment-option">
  256. <view class="option-left">
  257. <image src="/static/钱包.png" class="payment-icon"></image>
  258. <text class="option-text">钱包</text>
  259. </view>
  260. <up-radio-group v-model="radiovalue1" placement="column" @change="handlePaymentMethodChange">
  261. <up-radio :customStyle="{ marginLeft: 'auto' }" v-for="(item, index) in radiolist1" :key="index"
  262. :label="item.name" :name="item.name"></up-radio>
  263. </up-radio-group>
  264. </view>
  265. <up-line></up-line>
  266. <!-- 微信支付选项 -->
  267. <view class="payment-option">
  268. <view class="option-left">
  269. <image src="/static/微信支付.png" class="payment-icon"></image>
  270. <text class="option-text">微信支付</text>
  271. </view>
  272. <up-radio-group v-model="radiovalue1" placement="column" @change="handlePaymentMethodChange">
  273. <up-radio :customStyle="{ marginLeft: 'auto' }" v-for="(item, index) in radiolist2" :key="index"
  274. :label="item.name" :name="item.name"></up-radio>
  275. </up-radio-group>
  276. </view>
  277. <up-line></up-line>
  278. <!-- 其他支付方式 -->
  279. <view class="other-payment">
  280. <text class="other-payment-text">其他方式支付</text>
  281. <up-icon name="arrow-right" size="14" color="#1890ff"></up-icon>
  282. </view>
  283. <!-- 条款说明 -->
  284. <view class="terms-of-service">
  285. 我同意购买守则,取消政策和退款政策,我也同意支付以下所示的总金额(含服务费)。
  286. </view>
  287. </view>
  288. <view class="Wrap-detils-btn">
  289. <up-button type="primary" shape="circle" :customStyle="wrapqx" @click="closeSecond">取消</up-button>
  290. <up-button type="error" shape="circle" :customStyle="wrapqx" @click="handlConfiRmpurchase">
  291. 购买¥{{ computeMoney }}
  292. </up-button>
  293. </view>
  294. </up-popup>
  295. <view v-if="addressFlag" class="box">
  296. <addressComponent :modelValue="addressFlag" @update:modelValue="(val) => (addressFlag = val)"
  297. :addressInfo="addressInfo" @update:addressInfo="handleAddressUpdate"></addressComponent>
  298. </view>
  299. </view>
  300. </template>
  301. <script setup>
  302. import { onLoad } from '@dcloudio/uni-app'
  303. import { onMounted, ref, reactive, computed, nextTick } from 'vue'
  304. import {
  305. getDetailsvolunteerId,
  306. volunteerwork,
  307. volunteergetTimesByDate,
  308. ordersCreateOrder,
  309. coreUsersOrdersPayCancel,
  310. } from '@/api/volunteerDetailsApi/details.js'
  311. import itsCalendar from '@/components/its-calendar/its-calendar.vue'
  312. import uniDatetimePickerMy from '@/uni_modules/lic-uni-datetime-picker/components/lic-uni-datetime-picker/lic-uni-datetime-picker.vue'
  313. import addressComponent from '@/pages_home/components/volunteerData/adresss.vue'
  314. import Detiles from './detiles.vue'
  315. // const src = ref(
  316. // 'http://pic2.sc.chinaz.com/Files/pic/pic9/202002/hpic2119_s.jpg'
  317. // )
  318. const itsCalendarRef = ref(null)
  319. const businessPrice = ref() //价格
  320. const isChanging = ref(false) // 步进器防抖
  321. const singleQuantity = ref(1) // 购买次数,默认为1,不可为0
  322. const volunteerId = ref('') // 存储志愿者ID
  323. const serviceCategory = ref('') //存储大类别
  324. const businessManagementId = ref('') //具体分类id
  325. const doorToDoorTimeArr = ref([]) // 完整时间范围
  326. const doorToDoorTime = ref([]) // 只具备开始、结束 时间
  327. const timeHostArr = ref([]) // 时间段数组
  328. const currentDate = ref([]) // 点击过的年月日数组数据
  329. const currentTime = ref('') // 最后一次点击时间的时间节点
  330. const selectedTimes = ref([]) // 存储所有选择的时间对象
  331. const totalTimes = ref(0) // 时间点 点击的次数
  332. const startDisabled = ref('')
  333. const endDisabled = ref('')
  334. const listData = ref({})
  335. const show = ref(false) //第一层弹框
  336. const showSecond = ref(false) //第二层弹框
  337. const remark = ref('') //备注
  338. const radiovalue1 = ref(null)
  339. const selectedAddress = ref(null)
  340. const addressFlag = ref(false)
  341. const paymentMethod = ref(null) // Add payment method ref with default value 1 (wallet)
  342. const addressInfo = ref(null)
  343. const value = ref(1) // 将在onLoad中更新为listData.minQuantity
  344. const minQuantityMax = ref(99999) // 步进器最大值控制变量
  345. // Radio 单选框数据
  346. const radiolist1 = reactive([
  347. {
  348. name: '钱包支付',
  349. disabled: false,
  350. },
  351. ])
  352. const radiolist2 = reactive([
  353. {
  354. name: '微信支付',
  355. disabled: false,
  356. },
  357. ])
  358. const businessDurationComputed = computed(() => {
  359. // listData.value.businessDuration
  360. let result = 0
  361. if (listData.value.businessDuration <= 30) {
  362. result = 30
  363. } else {
  364. const num = Math.floor(listData.value.businessDuration / 30)
  365. // result = (num + 1) * 30
  366. const n = listData.value.businessDuration % 30
  367. result = (n ? (num + 1) : num) * 30
  368. }
  369. console.log(result, '>>>>>>>result')
  370. return result
  371. })
  372. // 设置第一个弹框的样式
  373. const popupStyle = {
  374. height: '80vh', // 设置为视窗高度的80%
  375. overflow: 'hidden', // 防止内容溢出
  376. position: 'relative', // 需要这个来支持内部的绝对定位元素
  377. }
  378. // 详情底部立即购买弹框
  379. const buttonClick = (e) => {
  380. show.value = true // 打开弹框
  381. }
  382. // 详情页第一层弹框
  383. function open() {
  384. show.value = true
  385. }
  386. // 第一层弹框取消
  387. function close() {
  388. // 关闭逻辑,设置 show 为 false
  389. show.value = false
  390. }
  391. // 第一个弹框逻辑
  392. const handleBuy = () => {
  393. console.log(selectedTimes.value, '>>>>>>selectedTimes.value')
  394. if (selectedTimes.value.length === 0) {
  395. uni.showToast({
  396. title: '请选择服务时间',
  397. icon: 'none',
  398. })
  399. return
  400. }
  401. show.value = false // 关闭第一个弹框
  402. showSecond.value = true // 打开第二个弹框
  403. }
  404. // 第二层弹框取消
  405. function closeSecond() {
  406. // 关闭逻辑,设置 show 为 false
  407. showSecond.value = false
  408. show.value = true // 打开第一个弹框
  409. }
  410. const handlCancel = () => {
  411. show.value = false // 关闭第一个弹框
  412. selectedTimes.value = [] // 清空已选时间
  413. totalTimes.value = 0 // 重置点击次数
  414. }
  415. const jumpToAddressSelect = () => {
  416. addressFlag.value = true
  417. }
  418. // 新增:处理子组件传回的地址数据
  419. const handleAddressUpdate = (newAddress) => {
  420. selectedAddress.value = newAddress
  421. addressFlag.value = false // 关闭选择器
  422. }
  423. // 详情底部底部数据
  424. const options = ref([
  425. // {
  426. // icon: 'headphones',
  427. // text: '客服',
  428. // },
  429. // {
  430. // icon: 'shop',
  431. // text: '收藏',
  432. // },
  433. ])
  434. // 详情底部立即购买样式
  435. const buttonGroup = ref([
  436. {
  437. text: '立即购买',
  438. backgroundColor: 'red',
  439. color: '#fff',
  440. },
  441. ])
  442. // 底部帮助客服方法
  443. const onClick = (e) => {
  444. // uni.showToast({
  445. // title: `敬请期待`,
  446. // icon: 'none',
  447. // })
  448. uni.navigateTo({
  449. url: `/pages_orderuser/pages/talk/pages/index/index?customerService=true&conversationType=3`
  450. })
  451. }
  452. const wrapqx = {
  453. // height: '70rpx',
  454. width: '240rpx',
  455. }
  456. // 获取传递的参数
  457. onLoad(async (options) => {
  458. const option = JSON.parse(decodeURIComponent(options.params))
  459. const {
  460. volunteerId: id,
  461. serviceCategory: categoy,
  462. businessManagementId: manage,
  463. } = option
  464. volunteerId.value = id
  465. serviceCategory.value = categoy
  466. businessManagementId.value = manage
  467. const res = await getDetailsvolunteerId({
  468. volunteerId: id,
  469. serviceCategory: categoy,
  470. businessManagementId: manage,
  471. })
  472. listData.value = res.data || {}
  473. // 更新步进器的初始值为listData中的minQuantity
  474. value.value = listData.value.minQuantity || 1
  475. businessPrice.value = listData.value.businessPrice || 0
  476. singleQuantity.value = listData.value.minQuantity || 1
  477. })
  478. // 添加步进器值变化处理函数
  479. const valChange = (obj) => {
  480. // const durationMs = listData.value.businessDuration * obj.value * 60
  481. const durationMs = businessDurationComputed.value * obj.value * 60
  482. const a = itsCalendarRef.value.upItem
  483. const b = itsCalendarRef.value.timeHostArr
  484. const c = itsCalendarRef.value.day_index
  485. let selectLength = [] // 所有日期選擇數組匯總
  486. const disFlag = false // 是否多选参数 / 备用禁用步进器
  487. Object.keys(itsCalendarRef.value.selectedTimeSlots).forEach(i => {
  488. selectLength.push(...itsCalendarRef.value.selectedTimeSlots[i])
  489. })
  490. if (selectLength.length > 1) {
  491. setTimeout(() => {
  492. singleQuantity.value = obj.value - 1
  493. }, 100)
  494. uni.showToast({ title: '请选择单个时间进行调整服务次数', icon: 'none' })
  495. return false
  496. }
  497. // const seconds = listData.value.businessDuration * obj.value * 60
  498. const seconds = businessDurationComputed.value * obj.value * 60
  499. const endTimestamp = a.timeStamp + durationMs
  500. // 根据当前时间分割出后续应选数据数组
  501. const filteredSlots = b[c].filter((i) => {
  502. return i.timeStamp > a.timeStamp && i.timeStamp <= endTimestamp
  503. })
  504. const timestampDifferenceValue = filteredSlots.length
  505. ? filteredSlots[filteredSlots.length - 1].timeStamp - a.timeStamp
  506. : 0
  507. // console.log(filteredSlots, '>>>>>filteredSlots')
  508. if (timestampDifferenceValue < durationMs) {
  509. minQuantityMax.value = obj.value - 1
  510. // 所选时间差值 小于 服务时间值 结束执行
  511. uni.showToast({ title: '所选时间的服务时间不充足!', icon: 'none' })
  512. return false
  513. }
  514. singleQuantity.value = obj.value || 1
  515. nextTick(() => {
  516. itsCalendarRef.value.handleTimeClick2()
  517. })
  518. }
  519. // 选择日期
  520. const getListTime = async () => {
  521. const params = {
  522. volunteerId: Number(volunteerId.value),
  523. }
  524. const res = await volunteerwork(params)
  525. if (res.data && res.data.length > 0) {
  526. doorToDoorTimeArr.value = res.data
  527. const newRes = [res.data[0], res.data[res.data.length - 1]]
  528. doorToDoorTime.value = newRes
  529. startDisabled.value = doorToDoorTime.value[0]
  530. endDisabled.value = doorToDoorTime.value[1]
  531. // 默认加载第一个日期的排班时间
  532. getByDate(res.data[0])
  533. } else {
  534. console.error('接口返回的日期范围为空')
  535. doorToDoorTimeArr.value = []
  536. timeHostArr.value = [] // 清空时间段
  537. }
  538. }
  539. // 获取志愿者排班时间
  540. const getByDate = async (date = doorToDoorTimeArr.value[0]) => {
  541. // 检查日期是否为空
  542. if (!date) {
  543. coannsole.error('日期为空,跳过获取排班时间')
  544. return
  545. }
  546. if (!volunteerId.value) {
  547. console.error('volunteerId 为空')
  548. return
  549. }
  550. const params = {
  551. volunteerId: volunteerId.value,
  552. date,
  553. }
  554. try {
  555. const res = await volunteergetTimesByDate(params)
  556. const TArr = res.data.map((item) => {
  557. // 初始化disabled变量
  558. let disabled = false
  559. if (item.hasReservation === 1) disabled = true
  560. let itemTime =
  561. item.timeStamp > 9999999999 ? item.timeStamp : item.timeStamp * 1000
  562. if (new Date().getTime() > itemTime) disabled = true
  563. return {
  564. ...item,
  565. hours: item.reservationTime,
  566. timeStamp: new Date(`${date} ${item.reservationTime}`).getTime() / 1000,
  567. date: date,
  568. // checked: false, // 每次点击请求时间后,延续之前选中状态,不初始化选中
  569. disabled,
  570. }
  571. })
  572. // 如果 doorToDoorTimeArr.value 为空,直接返回
  573. if (!doorToDoorTimeArr.value.length) {
  574. console.error('可选的日期范围为空')
  575. return
  576. }
  577. // 填充时间段数组
  578. timeHostArr.value = Array(doorToDoorTimeArr.value.length).fill(TArr)
  579. } catch (error) {
  580. console.error('获取排班时间失败:', error)
  581. }
  582. }
  583. const getByTime = (timeObj) => {
  584. if (timeObj.clicked) {
  585. return
  586. }
  587. if (timeObj.checked) {
  588. // 选中 添加到已选时间数组
  589. selectedTimes.value.push({
  590. date: timeObj.date,
  591. time: timeObj.hours,
  592. timestamp: timeObj.timeStamp,
  593. })
  594. } else {
  595. // 取消选中
  596. const index = selectedTimes.value.findIndex(
  597. (item) => item.timestamp === timeObj.timeStamp
  598. )
  599. selectedTimes.value.splice(index, 1)
  600. }
  601. console.log(selectedTimes, '.>>>selectedTimes');
  602. console.log(selectedTimes.value.length, '>>>>>>selectedTimes.value.length');
  603. totalTimes.value = selectedTimes.value.length // 更新点击次数
  604. }
  605. // 立即购买显示时执行
  606. const upPopupOpen = () => {
  607. //显示时初始化预存储信息数据
  608. selectedTimes.value = []
  609. totalTimes.value = 0
  610. currentTime.value = ''
  611. currentDate.value = []
  612. // 初始化disabled/checked 数据状态
  613. getByDate()
  614. }
  615. const computeMoney = computed(() => {
  616. return totalTimes.value * businessPrice.value * singleQuantity.value
  617. })
  618. const certificationPictures = computed(() => {
  619. if (listData.value.certificationPicture) {
  620. return listData.value.certificationPicture.split(',')
  621. }
  622. return []
  623. })
  624. // 确认购买
  625. const handlConfiRmpurchase = () => {
  626. if (!selectedAddress.value) {
  627. uni.showToast({
  628. title: '请选择服务地址',
  629. icon: 'none',
  630. duration: 2000,
  631. })
  632. return // 阻止购买
  633. } else if (!paymentMethod.value) {
  634. uni.showToast({
  635. title: '请选择支付方式',
  636. icon: 'none',
  637. duration: 2000,
  638. })
  639. return // 阻止购买
  640. }
  641. // 如果有地址,执行购买逻辑
  642. proceedToPayment()
  643. }
  644. const proceedToPayment = async () => {
  645. try {
  646. // 定义要发送的数据
  647. const orderData = {
  648. orders: {
  649. // serviceOnePrice: businessPrice.value,
  650. // serviceTotalPrice: computeMoney.value,
  651. serviceCategory: serviceCategory.value,
  652. totalTimes: totalTimes.value,
  653. paymentMethod: paymentMethod.value,
  654. volunteerId: volunteerId.value,
  655. remark: remark.value,
  656. businessManagementId: businessManagementId.value,
  657. volunteerInfoId: listData.value.volunteerInfoId,
  658. addressId: selectedAddress.value.addressId,
  659. singleQuantity: singleQuantity.value,
  660. },
  661. workDateList: [],
  662. }
  663. // 转换所有选择的时间
  664. selectedTimes.value.forEach((item) => {
  665. orderData.workDateList.push({
  666. workDate: item.date,
  667. workStartTime: item.time,
  668. })
  669. })
  670. // 创建订单
  671. const res = await ordersCreateOrder(orderData)
  672. if (res.code !== 200) {
  673. throw new Error('创建订单失败')
  674. }
  675. // 保存订单号
  676. const mainOrderId = res.data.mainOrderId
  677. // console.log('创建订单成功,订单号:', mainOrderId)
  678. // 如果是微信支付
  679. if (paymentMethod.value === 2) {
  680. return new Promise((resolve, reject) => {
  681. uni.requestPayment({
  682. ...res.data,
  683. package: res.data.packageValue,
  684. success: (payRes) => {
  685. // console.log('支付成功:', payRes)
  686. if (payRes.errMsg === 'requestPayment:ok') {
  687. uni.showToast({
  688. title: '支付成功',
  689. icon: 'success',
  690. duration: 1500,
  691. mask: true,
  692. })
  693. setTimeout(() => {
  694. uni.reLaunch({
  695. url: '/pages/classify',
  696. })
  697. }, 1500)
  698. resolve(payRes)
  699. }
  700. },
  701. fail: async (payRes) => {
  702. // console.log('支付失败或取消:', payRes)
  703. try {
  704. // 调用取消支付接口
  705. const cancelRes = await coreUsersOrdersPayCancel({ mainOrderId })
  706. // console.log('取消支付接口调用成功:', cancelRes)
  707. if (payRes.errMsg === 'requestPayment:fail cancel') {
  708. uni.showToast({
  709. title: '已取消支付',
  710. icon: 'none',
  711. duration: 1500,
  712. mask: true,
  713. })
  714. } else {
  715. uni.showToast({
  716. title: error.response?.data?.msg,
  717. icon: 'error',
  718. duration: 1500,
  719. mask: true,
  720. })
  721. }
  722. reject(payRes)
  723. } catch (error) {
  724. console.error('取消支付接口调用失败:', error)
  725. uni.showToast({
  726. title: '取消支付失败',
  727. icon: 'error',
  728. duration: 1500,
  729. mask: true,
  730. })
  731. reject(error)
  732. }
  733. },
  734. })
  735. })
  736. }
  737. // 如果是钱包支付
  738. if (paymentMethod.value === 1) {
  739. uni.showToast({
  740. title: '支付成功',
  741. icon: 'success',
  742. duration: 1500,
  743. mask: true,
  744. })
  745. setTimeout(() => {
  746. uni.reLaunch({
  747. url: '/pages/classify',
  748. })
  749. }, 1500)
  750. }
  751. } catch (error) {
  752. console.error('支付失败:', error)
  753. uni.showToast({
  754. title: error.response?.data?.msg,
  755. icon: 'error',
  756. duration: 1500,
  757. mask: true,
  758. })
  759. }
  760. }
  761. // 修改支付方式
  762. const handlePaymentMethodChange = (value) => {
  763. if (value === '钱包支付') {
  764. paymentMethod.value = 1
  765. } else if (value === '微信支付') {
  766. paymentMethod.value = 2
  767. }
  768. }
  769. const changeMinQuantityMax = (val = 99999) => {
  770. minQuantityMax.value = val
  771. }
  772. onMounted(async () => {
  773. await getListTime()
  774. })
  775. </script>
  776. <style scoped lang="scss">
  777. @import '../../styles/goodsDetails.scss';
  778. </style>