goodsDetails.vue 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416
  1. <template>
  2. <view>
  3. <view>
  4. <Detiles></Detiles>
  5. <up-card
  6. title="服务描述"
  7. :thumb="thumb"
  8. :head-style="{ height: '80rpx', padding: '20rpx', fontWeight: 'bold' }"
  9. >
  10. <template #body>
  11. <view class="service-description-container">
  12. <view class="service-text">{{ listData.businessDescribe }}</view>
  13. <view class="service-price-tag">¥{{ listData.businessPrice }}</view>
  14. </view>
  15. </template>
  16. </up-card>
  17. </view>
  18. <view>
  19. <up-card
  20. title="志愿者介绍"
  21. :head-style="{ height: '80rpx', padding: '20rpx', fontWeight: 'bold' }"
  22. >
  23. <template #body>
  24. <view class="volunteer-card">
  25. <!-- 左侧图片 -->
  26. <image
  27. class="volunteer-image"
  28. :src="listData.volunteerPicture"
  29. mode="aspectFill"
  30. ></image>
  31. <!-- 中间信息(姓名/类别等) -->
  32. <view class="volunteer-info">
  33. <view class="info-row">
  34. <text class="info-label">姓名:</text>
  35. <text class="info-value">{{ listData.name }}</text>
  36. </view>
  37. <view class="info-row">
  38. <text class="info-label">服务项目:</text>
  39. <text class="info-value">{{ listData.businessTierName }}</text>
  40. </view>
  41. <view v-if="serviceSubjectName != null" class="info-row">
  42. <text class="info-label">类别:</text>
  43. <text class="info-value">{{ listData.projectTypeName }}</text>
  44. </view>
  45. <view class="info-row">
  46. <text class="info-label">服务时长:</text>
  47. <text class="info-value">{{ listData.businessDuration }}</text>
  48. </view>
  49. <view class="info-row">
  50. <text class="info-label">电话:</text>
  51. <text class="info-value">{{ listData.phonenumber }}</text>
  52. </view>
  53. </view>
  54. <!-- 右上角评分 -->
  55. <view class="volunteer-rating">
  56. <text class="rating-text">4.5</text>
  57. <text class="rating-label">评分</text>
  58. </view>
  59. </view>
  60. </template>
  61. </up-card>
  62. <up-card
  63. title="技能介绍"
  64. :thumb="thumb"
  65. :head-style="{ height: '80rpx', padding: '20rpx', fontWeight: 'bold' }"
  66. >
  67. <template #body>
  68. <view class="skill-description">
  69. {{ listData.skillDescribe }}
  70. </view>
  71. </template>
  72. </up-card>
  73. <up-card
  74. title="证书"
  75. :thumb="thumb"
  76. :head-style="{ height: '80rpx', padding: '20rpx' }"
  77. >
  78. <template #body>
  79. <view class="certificate">
  80. <image
  81. v-for="item in certificationPictures"
  82. :key="item"
  83. :src="item"
  84. mode=""
  85. style="width: 100%"
  86. class="certificate-img"
  87. ></image>
  88. </view>
  89. </template>
  90. </up-card>
  91. <!-- 固定底部按钮区 -->
  92. <view class="Wrap-btn">
  93. <uni-goods-nav
  94. :fill="true"
  95. :options="options"
  96. :buttonGroup="buttonGroup"
  97. @click="onClick"
  98. @buttonClick="buttonClick"
  99. />
  100. </view>
  101. </view>
  102. <!-- 底部第一层弹框 -->
  103. <view>
  104. <up-popup :show="show" @close="close" @open="open"> </up-popup>
  105. <up-popup :show="show" @open="upPopupOpen" :custom-style="popupStyle">
  106. <scroll-view scroll-y class="popup-scroll-content">
  107. <view>
  108. <view class="Wrapper">
  109. <image src="/static/img/Location.png" class="Wrapper-img" />
  110. <span class="Wrapper-content">李四</span>
  111. <span class="Wrapper-content">重庆永川区</span>
  112. </view>
  113. <span style="margin-left: 15rpx; margin-top: 50rpx">重庆永川区</span>
  114. </view>
  115. <up-divider></up-divider>
  116. <view class="Wrap-content1">
  117. <up-avatar :src="src"></up-avatar>
  118. <text class="service-price">¥{{ businessPrice }}</text>
  119. </view>
  120. <view class="service-info-item">
  121. <text class="service-label">服务类别:</text>
  122. <up-text type="info" class="service-value" text="家庭教育"></up-text>
  123. </view>
  124. <view class="service-info-item">
  125. <text class="service-label">上门服务次数:</text>
  126. <up-number-box
  127. :modelValue="singleQuantity"
  128. :min="listData.minQuantity"
  129. :max="minQuantityMax"
  130. class="service-number-box"
  131. @change="valChange"
  132. ></up-number-box>
  133. <view class="service-values">{{ listData.minQuantity + listData.businessUnit }}</view>
  134. </view>
  135. <view class="service-period">
  136. <text class="service-label">服务周期:</text>
  137. <view class="date-picker-container">
  138. <uniDatetimePickerMy
  139. :modelValue="doorToDoorTime"
  140. :start="startDisabled"
  141. :end="endDisabled"
  142. type="daterange"
  143. ></uniDatetimePickerMy>
  144. </view>
  145. <view>
  146. <its-calendar
  147. v-if="show"
  148. ref="itsCalendarRef"
  149. :businessDuration="listData.businessDuration"
  150. :minQuantity="singleQuantity"
  151. :timeArr="doorToDoorTimeArr"
  152. :timeHostArr="timeHostArr"
  153. :businessTierName="listData.businessTierName"
  154. class="calendar-component"
  155. @getByDate="getByDate"
  156. @getByTime="getByTime"
  157. ></its-calendar>
  158. </view>
  159. </view>
  160. <view class="remark-container">
  161. <text class="service-label">备注:</text>
  162. <up-input
  163. placeholder="请输入内容"
  164. border="surround"
  165. v-model="remark"
  166. class="remark-input"
  167. @change="change"
  168. ></up-input>
  169. </view>
  170. <!-- 保持内容底部有足够的空白以避免按钮遮挡内容 -->
  171. <view style="height: 120rpx;"></view>
  172. </scroll-view>
  173. <!-- 将按钮移到scroll-view外部,但保持在popup内 -->
  174. <view class="popup-fixed-bottom">
  175. <up-button
  176. type="primary"
  177. shape="circle"
  178. :customStyle="wrapqx"
  179. @click="handlCancel"
  180. >取消</up-button
  181. >
  182. <up-button
  183. type="error"
  184. shape="circle"
  185. :customStyle="wrapqx"
  186. @click="handleBuy"
  187. >
  188. 预约¥{{ computeMoney }}
  189. </up-button>
  190. </view>
  191. </up-popup>
  192. </view>
  193. <!-- 第二个弹框-->
  194. <up-popup :show="showSecond">
  195. <view>
  196. <up-cell-group>
  197. <view
  198. @click="jumpToAddressSelect"
  199. style="display: flex; align-items: center; padding: 10px"
  200. >
  201. <up-icon name="more-dot-fill" size="16" />
  202. <view v-if="selectedAddress" style="margin-left: 8px; flex: 1">
  203. <view class="address-display">
  204. <text class="address-line">
  205. {{ selectedAddress.provinceName }}{{ selectedAddress.cityName
  206. }}{{ selectedAddress.districtName }}
  207. </text>
  208. <view class="contact-info">
  209. <text class="contact-name">{{ selectedAddress.name }}</text>
  210. <text class="contact-phone">{{
  211. selectedAddress.telephone
  212. }}</text>
  213. </view>
  214. </view>
  215. </view>
  216. <text v-else style="margin-left: 8px; font-size: 18px"
  217. >请选择服务地址</text
  218. >
  219. </view>
  220. <scroll-view scroll-y style="height: 460rpx">
  221. <view
  222. class="card-container"
  223. v-for="(item, index) in selectedTimes"
  224. :key="index"
  225. >
  226. <image
  227. class="card-image"
  228. :src="listData.volunteerPicture"
  229. mode="aspectFill"
  230. ></image>
  231. <view class="card-content">
  232. <view class="info-item"
  233. >服务项目:{{ listData.projectName }}</view
  234. >
  235. <view class="Telephone"
  236. >服务时长:{{ listData.businessDuration }}</view
  237. >
  238. <view class="date">日期:{{ item.date }}</view>
  239. <view class="time"> 时间:{{ item.time }} </view>
  240. </view>
  241. </view>
  242. </scroll-view>
  243. </up-cell-group>
  244. </view>
  245. <view class="Wrap-Payment">
  246. <!-- 支付金额 -->
  247. <view class="payment-header">
  248. <text class="payment-title">现在支付</text>
  249. <text class="payment-amount">¥{{ computeMoney }}</text>
  250. </view>
  251. <up-line></up-line>
  252. <!-- 钱包支付选项 -->
  253. <view class="payment-option">
  254. <view class="option-left">
  255. <image src="/static/钱包.png" class="payment-icon"></image>
  256. <text class="option-text">钱包</text>
  257. </view>
  258. <up-radio-group
  259. v-model="radiovalue1"
  260. placement="column"
  261. @change="handlePaymentMethodChange"
  262. >
  263. <up-radio
  264. :customStyle="{ marginLeft: 'auto' }"
  265. v-for="(item, index) in radiolist1"
  266. :key="index"
  267. :label="item.name"
  268. :name="item.name"
  269. ></up-radio>
  270. </up-radio-group>
  271. </view>
  272. <up-line></up-line>
  273. <!-- 微信支付选项 -->
  274. <view class="payment-option">
  275. <view class="option-left">
  276. <image src="/static/微信支付.png" class="payment-icon"></image>
  277. <text class="option-text">微信支付</text>
  278. </view>
  279. <up-radio-group
  280. v-model="radiovalue1"
  281. placement="column"
  282. @change="handlePaymentMethodChange"
  283. >
  284. <up-radio
  285. :customStyle="{ marginLeft: 'auto' }"
  286. v-for="(item, index) in radiolist2"
  287. :key="index"
  288. :label="item.name"
  289. :name="item.name"
  290. ></up-radio>
  291. </up-radio-group>
  292. </view>
  293. <up-line></up-line>
  294. <!-- 其他支付方式 -->
  295. <view class="other-payment">
  296. <text class="other-payment-text">其他方式支付</text>
  297. <up-icon name="arrow-right" size="14" color="#1890ff"></up-icon>
  298. </view>
  299. <!-- 条款说明 -->
  300. <view class="terms-of-service">
  301. 我同意购买守则,取消政策和退款政策,我也同意支付以下所示的总金额(含服务费)。
  302. </view>
  303. </view>
  304. <view class="Wrap-detils-btn">
  305. <up-button
  306. type="primary"
  307. shape="circle"
  308. :customStyle="wrapqx"
  309. @click="closeSecond"
  310. >取消</up-button
  311. >
  312. <up-button
  313. type="error"
  314. shape="circle"
  315. :customStyle="wrapqx"
  316. @click="handlConfiRmpurchase"
  317. >
  318. 购买¥{{ computeMoney }}
  319. </up-button>
  320. </view>
  321. </up-popup>
  322. <view v-if="addressFlag" class="box">
  323. <addressComponent
  324. :modelValue="addressFlag"
  325. @update:modelValue="(val) => (addressFlag = val)"
  326. :addressInfo="addressInfo"
  327. @update:addressInfo="handleAddressUpdate"
  328. ></addressComponent>
  329. </view>
  330. </view>
  331. </template>
  332. <script setup>
  333. import { onLoad } from '@dcloudio/uni-app'
  334. import { onMounted, ref, reactive, computed, nextTick } from 'vue'
  335. import {
  336. getDetailsvolunteerId,
  337. volunteerwork,
  338. volunteergetTimesByDate,
  339. ordersCreateOrder,
  340. coreUsersOrdersPayCancel,
  341. } from '@/api/volunteerDetailsApi/details.js'
  342. import itsCalendar from '@/components/its-calendar/its-calendar.vue'
  343. import uniDatetimePickerMy from '@/uni_modules/lic-uni-datetime-picker/components/lic-uni-datetime-picker/lic-uni-datetime-picker.vue'
  344. import addressComponent from '@/pages_home/components/volunteerData/adresss.vue'
  345. import Detiles from './detiles.vue'
  346. // const src = ref(
  347. // 'http://pic2.sc.chinaz.com/Files/pic/pic9/202002/hpic2119_s.jpg'
  348. // )
  349. const itsCalendarRef = ref(null)
  350. const businessPrice = ref() //价格
  351. const singleQuantity = ref(1) // 购买次数,默认为1,不可为0
  352. const volunteerId = ref('') // 存储志愿者ID
  353. const serviceCategory = ref('') //存储大类别
  354. const businessManagementId = ref('') //具体分类id
  355. const doorToDoorTimeArr = ref([]) // 完整时间范围
  356. const doorToDoorTime = ref([]) // 只具备开始、结束 时间
  357. const timeHostArr = ref([]) // 时间段数组
  358. const currentDate = ref([]) // 点击过的年月日数组数据
  359. const currentTime = ref('') // 最后一次点击时间的时间节点
  360. const selectedTimes = ref([]) // 存储所有选择的时间对象
  361. const totalTimes = ref(0) // 时间点 点击的次数
  362. const startDisabled = ref('')
  363. const endDisabled = ref('')
  364. const listData = ref({})
  365. const show = ref(false) //第一层弹框
  366. const showSecond = ref(false) //第二层弹框
  367. const remark = ref('') //备注
  368. const radiovalue1 = ref(null)
  369. const selectedAddress = ref(null)
  370. const addressFlag = ref(false)
  371. const paymentMethod = ref(null) // Add payment method ref with default value 1 (wallet)
  372. const addressInfo = ref(null)
  373. const value = ref(1) // 将在onLoad中更新为listData.minQuantity
  374. const minQuantityMax = ref(99999) // 步进器最大值控制变量
  375. // Radio 单选框数据
  376. const radiolist1 = reactive([
  377. {
  378. name: '钱包支付',
  379. disabled: false,
  380. },
  381. ])
  382. const radiolist2 = reactive([
  383. {
  384. name: '微信支付',
  385. disabled: false,
  386. },
  387. ])
  388. // 设置第一个弹框的样式
  389. const popupStyle = {
  390. height: '80vh', // 设置为视窗高度的80%
  391. overflow: 'hidden', // 防止内容溢出
  392. position: 'relative', // 需要这个来支持内部的绝对定位元素
  393. }
  394. // 详情底部立即购买弹框
  395. const buttonClick = (e) => {
  396. show.value = true // 打开弹框
  397. }
  398. // 详情页第一层弹框
  399. function open() {
  400. show.value = true
  401. }
  402. // 第一层弹框取消
  403. function close() {
  404. // 关闭逻辑,设置 show 为 false
  405. show.value = false
  406. }
  407. // 第一个弹框逻辑
  408. const handleBuy = () => {
  409. show.value = false // 关闭第一个弹框
  410. showSecond.value = true // 打开第二个弹框
  411. }
  412. // 第二层弹框取消
  413. function closeSecond() {
  414. // 关闭逻辑,设置 show 为 false
  415. showSecond.value = false
  416. show.value = true // 打开第一个弹框
  417. }
  418. const handlCancel = () => {
  419. show.value = false // 关闭第一个弹框
  420. selectedTimes.value = [] // 清空已选时间
  421. totalTimes.value = 0 // 重置点击次数
  422. }
  423. const jumpToAddressSelect = () => {
  424. addressFlag.value = true
  425. }
  426. // 新增:处理子组件传回的地址数据
  427. const handleAddressUpdate = (newAddress) => {
  428. selectedAddress.value = newAddress
  429. addressFlag.value = false // 关闭选择器
  430. }
  431. // 详情底部底部数据
  432. const options = ref([
  433. {
  434. icon: 'headphones',
  435. text: '客服',
  436. },
  437. {
  438. icon: 'shop',
  439. text: '收藏',
  440. },
  441. ])
  442. // 详情底部立即购买样式
  443. const buttonGroup = ref([
  444. {
  445. text: '立即购买',
  446. backgroundColor: 'red',
  447. color: '#fff',
  448. },
  449. ])
  450. // 底部帮助客服方法
  451. const onClick = (e) => {
  452. uni.showToast({
  453. title: `点击${e.content.text}`,
  454. icon: 'none',
  455. })
  456. }
  457. // 修改详情页底部样式
  458. const customStyle = {
  459. height: '70rpx',
  460. paddingLeft: '30rpx',
  461. width: '540rpx',
  462. }
  463. const wrapqx = {
  464. // height: '70rpx',
  465. width: '240rpx',
  466. }
  467. // 获取传递的参数
  468. onLoad(async (options) => {
  469. const option = JSON.parse(decodeURIComponent(options.params))
  470. const {
  471. volunteerId: id,
  472. serviceCategory: categoy,
  473. businessManagementId: manage,
  474. } = option
  475. volunteerId.value = id
  476. serviceCategory.value = categoy
  477. businessManagementId.value = manage
  478. const res = await getDetailsvolunteerId({
  479. volunteerId: id,
  480. serviceCategory: categoy,
  481. businessManagementId: manage,
  482. })
  483. listData.value = res.data || {}
  484. // 更新步进器的初始值为listData中的minQuantity
  485. value.value = listData.value.minQuantity || 1
  486. businessPrice.value = listData.value.businessPrice || 0
  487. singleQuantity.value = listData.value.minQuantity || 1
  488. })
  489. // 添加步进器值变化处理函数
  490. const valChange = (obj) => {
  491. const durationMs = listData.value.businessDuration * obj.value * 60
  492. const a = itsCalendarRef.value.upItem
  493. const b = itsCalendarRef.value.timeHostArr
  494. const c = itsCalendarRef.value.day_index
  495. const seconds = listData.value.businessDuration * obj.value * 60
  496. const endTimestamp = a.timeStamp + durationMs
  497. // 根据当前时间分割出后续应选数据数组
  498. const filteredSlots = b[c].filter((i) => {
  499. return i.timeStamp > a.timeStamp && i.timeStamp <= endTimestamp
  500. })
  501. const timestampDifferenceValue = filteredSlots.length
  502. ? filteredSlots[filteredSlots.length - 1].timeStamp - a.timeStamp
  503. : 0
  504. // console.log(filteredSlots, '>>>>>filteredSlots')
  505. if (timestampDifferenceValue < durationMs) {
  506. minQuantityMax.value = obj.value - 1
  507. // 所选时间差值 小于 服务时间值 结束执行
  508. uni.showToast({ title: '所选时间的服务时间不充足!', icon: 'none' })
  509. return false
  510. }
  511. singleQuantity.value = obj.value || 1
  512. nextTick(() => {
  513. itsCalendarRef.value.handleTimeClick2()
  514. })
  515. }
  516. // 选择日期
  517. const getListTime = async () => {
  518. const params = {
  519. volunteerId: Number(volunteerId.value),
  520. }
  521. const res = await volunteerwork(params)
  522. if (res.data && res.data.length > 0) {
  523. doorToDoorTimeArr.value = res.data
  524. const newRes = [res.data[0], res.data[res.data.length - 1]]
  525. doorToDoorTime.value = newRes
  526. startDisabled.value = doorToDoorTime.value[0]
  527. endDisabled.value = doorToDoorTime.value[1]
  528. // 默认加载第一个日期的排班时间
  529. getByDate(res.data[0])
  530. } else {
  531. console.error('接口返回的日期范围为空')
  532. doorToDoorTimeArr.value = []
  533. timeHostArr.value = [] // 清空时间段
  534. }
  535. }
  536. // 获取志愿者排班时间
  537. const getByDate = async (date = doorToDoorTimeArr.value[0]) => {
  538. // 检查日期是否为空
  539. if (!date) {
  540. coannsole.error('日期为空,跳过获取排班时间')
  541. return
  542. }
  543. if (!volunteerId.value) {
  544. console.error('volunteerId 为空')
  545. return
  546. }
  547. const params = {
  548. volunteerId: volunteerId.value,
  549. date,
  550. }
  551. try {
  552. const res = await volunteergetTimesByDate(params)
  553. const TArr = res.data.map((item) => {
  554. // 初始化disabled变量
  555. let disabled = false
  556. if (item.hasReservation === 1) disabled = true
  557. let itemTime =
  558. item.timeStamp > 9999999999 ? item.timeStamp : item.timeStamp * 1000
  559. if (new Date().getTime() > itemTime) disabled = true
  560. return {
  561. ...item,
  562. hours: item.reservationTime,
  563. timeStamp: new Date(`${date} ${item.reservationTime}`).getTime() / 1000,
  564. date: date,
  565. // checked: false, // 每次点击请求时间后,延续之前选中状态,不初始化选中
  566. disabled,
  567. }
  568. })
  569. // 如果 doorToDoorTimeArr.value 为空,直接返回
  570. if (!doorToDoorTimeArr.value.length) {
  571. console.error('可选的日期范围为空')
  572. return
  573. }
  574. // 填充时间段数组
  575. timeHostArr.value = Array(doorToDoorTimeArr.value.length).fill(TArr)
  576. } catch (error) {
  577. console.error('获取排班时间失败:', error)
  578. }
  579. }
  580. const getByTime = (timeObj) => {
  581. if (timeObj.clicked) {
  582. return
  583. }
  584. if (timeObj.checked) {
  585. // 选中 添加到已选时间数组
  586. selectedTimes.value.push({
  587. date: timeObj.date,
  588. time: timeObj.hours,
  589. timestamp: timeObj.timeStamp,
  590. })
  591. } else {
  592. // 取消选中
  593. const index = selectedTimes.value.findIndex(
  594. (item) => item.timestamp === timeObj.timeStamp
  595. )
  596. selectedTimes.value.splice(index, 1)
  597. }
  598. totalTimes.value = selectedTimes.value.length // 更新点击次数
  599. }
  600. // 立即购买显示时执行
  601. const upPopupOpen = () => {
  602. //显示时初始化预存储信息数据
  603. selectedTimes.value = []
  604. totalTimes.value = 0
  605. currentTime.value = ''
  606. currentDate.value = []
  607. // 初始化disabled/checked 数据状态
  608. getByDate()
  609. }
  610. const computeMoney = computed(() => {
  611. // console.log(
  612. // totalTimes.value,
  613. // businessPrice.value,
  614. // singleQuantity.value,
  615. // '>>>>>> console.log(totalTimes.value * businessPrice.value * minQuantity.value);'
  616. // )
  617. return totalTimes.value * businessPrice.value * singleQuantity.value
  618. })
  619. const certificationPictures = computed(() => {
  620. if (listData.value.certificationPicture) {
  621. return listData.value.certificationPicture.split(',')
  622. }
  623. return []
  624. })
  625. // 确认购买
  626. const handlConfiRmpurchase = () => {
  627. if (!selectedAddress.value) {
  628. uni.showToast({
  629. title: '请选择服务地址',
  630. icon: 'none',
  631. duration: 2000,
  632. })
  633. return // 阻止购买
  634. } else if (!paymentMethod.value) {
  635. uni.showToast({
  636. title: '请选择支付方式',
  637. icon: 'none',
  638. duration: 2000,
  639. })
  640. return // 阻止购买
  641. }
  642. // 如果有地址,执行购买逻辑
  643. proceedToPayment()
  644. }
  645. const proceedToPayment = async () => {
  646. try {
  647. // 定义要发送的数据
  648. const orderData = {
  649. orders: {
  650. // serviceOnePrice: businessPrice.value,
  651. // serviceTotalPrice: computeMoney.value,
  652. serviceCategory: serviceCategory.value,
  653. totalTimes: totalTimes.value,
  654. paymentMethod: paymentMethod.value,
  655. volunteerId: volunteerId.value,
  656. remark: remark.value,
  657. businessManagementId: businessManagementId.value,
  658. volunteerInfoId: listData.value.volunteerInfoId,
  659. addressId: selectedAddress.value.addressId,
  660. singleQuantity: singleQuantity.value,
  661. },
  662. workDateList: [],
  663. }
  664. // 转换所有选择的时间
  665. selectedTimes.value.forEach((item) => {
  666. orderData.workDateList.push({
  667. workDate: item.date,
  668. workStartTime: item.time,
  669. })
  670. })
  671. // 创建订单
  672. const res = await ordersCreateOrder(orderData)
  673. if (res.code !== 200) {
  674. throw new Error('创建订单失败')
  675. }
  676. // 保存订单号
  677. const mainOrderId = res.data.mainOrderId
  678. // console.log('创建订单成功,订单号:', mainOrderId)
  679. // 如果是微信支付
  680. if (paymentMethod.value === 2) {
  681. return new Promise((resolve, reject) => {
  682. uni.requestPayment({
  683. ...res.data,
  684. package: res.data.packageValue,
  685. success: (payRes) => {
  686. // console.log('支付成功:', payRes)
  687. if (payRes.errMsg === 'requestPayment:ok') {
  688. uni.showToast({
  689. title: '支付成功',
  690. icon: 'success',
  691. duration: 1500,
  692. mask: true,
  693. })
  694. setTimeout(() => {
  695. uni.reLaunch({
  696. url: '/pages/classify',
  697. })
  698. }, 1500)
  699. resolve(payRes)
  700. }
  701. },
  702. fail: async (payRes) => {
  703. // console.log('支付失败或取消:', payRes)
  704. try {
  705. // 调用取消支付接口
  706. const cancelRes = await coreUsersOrdersPayCancel({ mainOrderId })
  707. // console.log('取消支付接口调用成功:', cancelRes)
  708. if (payRes.errMsg === 'requestPayment:fail cancel') {
  709. uni.showToast({
  710. title: '已取消支付',
  711. icon: 'none',
  712. duration: 1500,
  713. mask: true,
  714. })
  715. } else {
  716. uni.showToast({
  717. title: '支付失败',
  718. icon: 'error',
  719. duration: 1500,
  720. mask: true,
  721. })
  722. }
  723. reject(payRes)
  724. } catch (error) {
  725. console.error('取消支付接口调用失败:', error)
  726. uni.showToast({
  727. title: '取消支付失败',
  728. icon: 'error',
  729. duration: 1500,
  730. mask: true,
  731. })
  732. reject(error)
  733. }
  734. },
  735. })
  736. })
  737. }
  738. // 如果是钱包支付
  739. if (paymentMethod.value === 1) {
  740. uni.showToast({
  741. title: '支付成功',
  742. icon: 'success',
  743. duration: 1500,
  744. mask: true,
  745. })
  746. setTimeout(() => {
  747. uni.reLaunch({
  748. url: '/pages/classify',
  749. })
  750. }, 1500)
  751. }
  752. } catch (error) {
  753. console.error('支付失败:', error)
  754. uni.showToast({
  755. title: error.response?.data?.msg || '支付失败',
  756. icon: 'error',
  757. duration: 1500,
  758. mask: true,
  759. })
  760. }
  761. }
  762. // 修改支付方式
  763. const handlePaymentMethodChange = (value) => {
  764. if (value === '钱包支付') {
  765. paymentMethod.value = 1
  766. } else if (value === '微信支付') {
  767. paymentMethod.value = 2
  768. }
  769. }
  770. onMounted(async () => {
  771. await getListTime()
  772. })
  773. </script>
  774. <style scoped>
  775. /* 添加卡片样式 */
  776. .card-container {
  777. display: flex;
  778. padding: 20rpx;
  779. margin: 10rpx;
  780. background-color: #fff;
  781. border-radius: 10rpx;
  782. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
  783. }
  784. .card-image {
  785. width: 120rpx;
  786. height: 120rpx;
  787. border-radius: 10rpx;
  788. margin-right: 20rpx;
  789. }
  790. .card-content {
  791. flex: 1;
  792. display: flex;
  793. flex-direction: column;
  794. justify-content: space-between;
  795. }
  796. .info-item,
  797. .Telephone,
  798. .date,
  799. .time {
  800. font-size: 26rpx;
  801. margin-bottom: 8rpx;
  802. }
  803. .box {
  804. height: 100vh;
  805. width: 100vw;
  806. position: fixed;
  807. top: 0;
  808. left: 0;
  809. z-index: 999999;
  810. background: red;
  811. }
  812. .card-container {
  813. display: flex;
  814. position: relative;
  815. padding: 20rpx;
  816. align-items: flex-start;
  817. background-color: #fff;
  818. border-radius: 12rpx;
  819. }
  820. /* 左侧图片 */
  821. .card-image {
  822. width: 240rpx;
  823. height: 380rpx;
  824. border-radius: 12rpx;
  825. margin-right: 30rpx;
  826. flex-shrink: 0;
  827. }
  828. /* 中间信息区域 */
  829. .card-info {
  830. flex: 1;
  831. padding-right: 100rpx;
  832. }
  833. .Telephone {
  834. white-space: nowrap;
  835. margin-bottom: 24rpx;
  836. font-size: 28rpx;
  837. line-height: 1.6;
  838. color: #333;
  839. }
  840. .info-item {
  841. margin-bottom: 24rpx;
  842. font-size: 28rpx;
  843. line-height: 1.6;
  844. color: #333;
  845. }
  846. /* 右上角评分 */
  847. .card-rating {
  848. position: absolute;
  849. top: 20rpx;
  850. right: 20rpx;
  851. background-color: #f8f8f8;
  852. padding: 6rpx 16rpx;
  853. border-radius: 20rpx;
  854. font-size: 24rpx;
  855. color: #ff9900;
  856. font-weight: bold;
  857. }
  858. /* 固定在页面底部 */
  859. .Wrap-btn {
  860. /* display: flex; */
  861. /* justify-content: flex-end; */
  862. /* align-items: center; */
  863. position: fixed;
  864. bottom: 0;
  865. width: 100%;
  866. background-color: white;
  867. padding: 40rpx 20rpx;
  868. box-shadow: -2rpx 5rpx rgba(0, 0, 0, 0.1);
  869. }
  870. .Wrapper {
  871. display: flex;
  872. align-items: center;
  873. gap: 10rpx;
  874. margin-top: 10rpx;
  875. }
  876. .Wrapper-img {
  877. width: 50rpx;
  878. height: 50rpx;
  879. margin-left: 10rpx;
  880. }
  881. .Wrapper-content {
  882. color: rgba(16, 16, 16, 1);
  883. font-size: 25rpx;
  884. font-family: PingFangSC-regular;
  885. line-height: 20rpx;
  886. }
  887. .Wrap-content1 {
  888. display: flex;
  889. justify-content: flex-start;
  890. margin-left: 10rpx;
  891. margin-top: 10rpx;
  892. }
  893. .Wrap-info {
  894. display: flex;
  895. justify-content: flex-start;
  896. margin-left: 10rpx;
  897. margin-top: 25rpx;
  898. }
  899. .Wrap-content3 {
  900. margin-top: 25rpx;
  901. }
  902. .Wrap-content4 {
  903. margin-top: 25rpx;
  904. }
  905. .Wrap-content5 {
  906. display: flex;
  907. justify-content: flex-start;
  908. margin-left: 10rpx;
  909. margin-top: 25rpx;
  910. }
  911. .Wrap-content6 {
  912. margin-top: 25rpx;
  913. }
  914. .Wrap-detils-btn {
  915. display: flex;
  916. justify-content: flex-end;
  917. align-items: center;
  918. margin-top: 25rpx;
  919. /* position: fixed;
  920. bottom: 0;
  921. width: 100%;
  922. background-color: white;
  923. padding: 40rpx 20rpx;
  924. box-shadow: -2rpx 5rpx rgba(0, 0, 0, 0.1); */
  925. }
  926. .Wrap-Payment {
  927. padding: 40rpx 30rpx;
  928. background-color: #fff;
  929. border-radius: 16rpx;
  930. margin-bottom: 20rpx;
  931. }
  932. .payment-header {
  933. display: flex;
  934. justify-content: space-between;
  935. align-items: center;
  936. padding: 20rpx 0 30rpx;
  937. }
  938. .payment-title {
  939. font-size: 32rpx;
  940. font-weight: bold;
  941. color: #333;
  942. }
  943. .payment-amount {
  944. font-size: 40rpx;
  945. color: #ff4d4f;
  946. font-weight: bold;
  947. }
  948. .payment-option {
  949. display: flex;
  950. justify-content: space-between;
  951. align-items: center;
  952. padding: 30rpx 0;
  953. }
  954. .option-left {
  955. display: flex;
  956. align-items: center;
  957. }
  958. .payment-icon {
  959. width: 64rpx;
  960. height: 64rpx;
  961. margin-right: 24rpx;
  962. border-radius: 8rpx;
  963. }
  964. .option-text {
  965. font-size: 30rpx;
  966. font-weight: 500;
  967. color: #333;
  968. }
  969. .other-payment {
  970. padding: 30rpx 0;
  971. display: flex;
  972. align-items: center;
  973. justify-content: space-between;
  974. }
  975. .other-payment-text {
  976. color: #1890ff;
  977. font-size: 28rpx;
  978. }
  979. .terms-of-service {
  980. padding: 30rpx 0;
  981. font-size: 24rpx;
  982. color: #999;
  983. line-height: 1.6;
  984. background-color: #f9f9f9;
  985. padding: 20rpx;
  986. border-radius: 8rpx;
  987. }
  988. .Wrap-detils-btn {
  989. display: flex;
  990. justify-content: space-between;
  991. align-items: center;
  992. padding: 30rpx;
  993. background-color: #fff;
  994. border-top: 1px solid #f0f0f0;
  995. }
  996. .certificate {
  997. display: flex;
  998. flex-direction: column;
  999. }
  1000. .certificate-img {
  1001. margin-bottom: 24rpx;
  1002. }
  1003. /* 第二层弹框 */
  1004. .card-container {
  1005. display: flex;
  1006. flex-direction: row;
  1007. align-items: flex-start;
  1008. padding: 10px;
  1009. }
  1010. .card-image {
  1011. width: 100px;
  1012. /* Adjust as needed */
  1013. height: 100px;
  1014. /* Adjust as needed */
  1015. border-radius: 8px;
  1016. margin-right: 15px;
  1017. object-fit: cover;
  1018. }
  1019. .card-content {
  1020. flex: 1;
  1021. display: flex;
  1022. flex-direction: column;
  1023. }
  1024. .info-item,
  1025. .Telephone,
  1026. .date,
  1027. .time {
  1028. margin-bottom: 5px;
  1029. font-size: 14px;
  1030. color: #333;
  1031. }
  1032. .time span {
  1033. margin-right: 8px;
  1034. }
  1035. .address-line {
  1036. font-size: 14px;
  1037. line-height: 20rpx;
  1038. /* 行高20rpx */
  1039. color: #333;
  1040. /* 深色文字 */
  1041. display: block;
  1042. /* 确保独占一行 */
  1043. margin-bottom: 30rpx;
  1044. /* 下边距30rpx */
  1045. }
  1046. /* 联系人信息容器 */
  1047. .contact-info {
  1048. display: flex;
  1049. gap: 20rpx;
  1050. /* 姓名和电话之间的间距 */
  1051. }
  1052. /* 姓名样式 */
  1053. .contact-name {
  1054. font-size: 12px;
  1055. color: #999;
  1056. /* 灰色文字 */
  1057. line-height: 20rpx;
  1058. }
  1059. /* 电话样式 */
  1060. .contact-phone {
  1061. font-size: 12px;
  1062. color: #999;
  1063. /* 灰色文字 */
  1064. line-height: 20rpx;
  1065. }
  1066. /* 服务描述卡片样式 */
  1067. .service-description-container {
  1068. display: flex;
  1069. justify-content: space-between;
  1070. align-items: flex-start;
  1071. }
  1072. .service-text {
  1073. flex: 1;
  1074. margin-right: 20rpx;
  1075. }
  1076. .service-price-tag {
  1077. font-size: 36rpx;
  1078. font-weight: bold;
  1079. color: #ff4d4f;
  1080. white-space: nowrap;
  1081. padding: 8rpx 16rpx;
  1082. background-color: #fff2f0;
  1083. border-radius: 8rpx;
  1084. }
  1085. /* 弹框滚动内容样式 */
  1086. .popup-scroll-content {
  1087. height: calc(80vh - 120rpx); /* 减去底部按钮区域的高度 */
  1088. padding: 24rpx;
  1089. padding-bottom: 30px; /* 增加底部内边距,避免内容被按钮遮挡 */
  1090. box-sizing: border-box;
  1091. }
  1092. /* 固定底部按钮样式 */
  1093. .popup-fixed-bottom {
  1094. position: absolute; /* 相对于popup容器定位 */
  1095. bottom: 0;
  1096. left: 0;
  1097. right: 0;
  1098. width: 100%;
  1099. background-color: white;
  1100. padding: 20rpx;
  1101. box-sizing: border-box;
  1102. display: flex;
  1103. justify-content: flex-end; /* 右对齐按钮 */
  1104. align-items: center;
  1105. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1); /* 顶部阴影,创造分隔感 */
  1106. z-index: 1; /* 确保按钮在内容之上 */
  1107. }
  1108. /* 服务信息项样式 */
  1109. .service-info-item {
  1110. display: flex;
  1111. align-items: center;
  1112. padding: 24rpx 0;
  1113. border-bottom: 1px solid #f5f5f5;
  1114. }
  1115. .service-values{
  1116. margin-left: 20rpx;
  1117. }
  1118. .service-label {
  1119. width: 200rpx;
  1120. font-size: 28rpx;
  1121. color: #666;
  1122. flex-shrink: 0;
  1123. }
  1124. .service-value {
  1125. font-size: 28rpx;
  1126. color: #333;
  1127. font-weight: 500;
  1128. }
  1129. .service-number-box {
  1130. background-color: #f8f8f8;
  1131. border-radius: 8rpx;
  1132. }
  1133. /* 服务周期样式 */
  1134. .service-period {
  1135. padding: 24rpx 0;
  1136. border-bottom: 1px solid #f5f5f5;
  1137. }
  1138. .date-picker-container {
  1139. margin: 20rpx 0;
  1140. padding: 12rpx;
  1141. background-color: #f8f8f8;
  1142. border-radius: 8rpx;
  1143. }
  1144. .calendar-component {
  1145. margin-top: 20rpx;
  1146. border: 1px solid #f0f0f0;
  1147. border-radius: 8rpx;
  1148. }
  1149. /* 备注容器样式 */
  1150. .remark-container {
  1151. padding: 24rpx 0;
  1152. display: flex;
  1153. flex-direction: column;
  1154. }
  1155. .remark-input {
  1156. margin-top: 16rpx;
  1157. background-color: #f8f8f8;
  1158. border-radius: 8rpx;
  1159. }
  1160. /* 修改卡片基础样式 */
  1161. .card-container {
  1162. display: flex;
  1163. padding: 24rpx;
  1164. margin: 16rpx 0;
  1165. background-color: #fff;
  1166. border-radius: 12rpx;
  1167. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
  1168. }
  1169. /* 志愿者卡片样式 */
  1170. .volunteer-card {
  1171. display: flex;
  1172. position: relative;
  1173. padding: 24rpx 0;
  1174. align-items: flex-start;
  1175. }
  1176. .volunteer-image {
  1177. width: 180rpx;
  1178. height: 180rpx;
  1179. border-radius: 12rpx;
  1180. margin-right: 24rpx;
  1181. flex-shrink: 0;
  1182. object-fit: cover;
  1183. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
  1184. }
  1185. .volunteer-info {
  1186. flex: 1;
  1187. display: flex;
  1188. flex-direction: column;
  1189. gap: 16rpx;
  1190. }
  1191. .info-row {
  1192. display: flex;
  1193. align-items: center;
  1194. }
  1195. .info-label {
  1196. color: #666;
  1197. font-size: 26rpx;
  1198. margin-right: 8rpx;
  1199. width: 140rpx;
  1200. flex-shrink: 0;
  1201. }
  1202. .info-value {
  1203. color: #333;
  1204. font-size: 26rpx;
  1205. font-weight: 500;
  1206. }
  1207. .volunteer-rating {
  1208. position: absolute;
  1209. top: 24rpx;
  1210. right: 0;
  1211. background-color: #fff8e6;
  1212. padding: 8rpx 16rpx;
  1213. border-radius: 20rpx;
  1214. display: flex;
  1215. align-items: center;
  1216. border: 1px solid #ffebb7;
  1217. }
  1218. .rating-text {
  1219. font-size: 28rpx;
  1220. color: #ff9900;
  1221. font-weight: bold;
  1222. margin-right: 6rpx;
  1223. }
  1224. .rating-label {
  1225. font-size: 24rpx;
  1226. color: #ff9900;
  1227. }
  1228. /* 技能描述样式 */
  1229. .skill-description {
  1230. padding: 16rpx 0;
  1231. font-size: 28rpx;
  1232. line-height: 1.6;
  1233. color: #333;
  1234. }
  1235. /* 证书样式 */
  1236. .certificate {
  1237. display: flex;
  1238. flex-direction: column;
  1239. gap: 20rpx;
  1240. }
  1241. .certificate-img {
  1242. border-radius: 8rpx;
  1243. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
  1244. margin-bottom: 16rpx;
  1245. }
  1246. /* Address selection styles */
  1247. .address-display {
  1248. padding: 16rpx;
  1249. background-color: #f8f8f8;
  1250. border-radius: 8rpx;
  1251. margin-bottom: 16rpx;
  1252. }
  1253. .address-line {
  1254. font-size: 28rpx;
  1255. line-height: 40rpx;
  1256. color: #333;
  1257. display: block;
  1258. margin-bottom: 12rpx;
  1259. font-weight: 500;
  1260. }
  1261. .contact-info {
  1262. display: flex;
  1263. gap: 20rpx;
  1264. }
  1265. .contact-name, .contact-phone {
  1266. font-size: 26rpx;
  1267. color: #666;
  1268. line-height: 36rpx;
  1269. }
  1270. </style>