You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1206 lines
35 KiB

3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
  1. <template>
  2. <view class="service-new container">
  3. <view class="service-new-address">
  4. <uni-card padding=0 :is-shadow="false">
  5. <view class="service-new-title" slot="title">
  6. <view class="service-new-title-left">
  7. <view class="service-new-flag"></view>
  8. <view>服务地址</view>
  9. </view>
  10. </view>
  11. <view class="split-line"></view>
  12. <view class="service-new-address-content">
  13. <view class="service-new-address-selected">
  14. <view class="personal-address-info">
  15. <view class="personal-address-text">
  16. {{currentAddress.province}} {{currentAddress.city}} {{currentAddress.detailAddress}}
  17. </view>
  18. <view class="personal-address-people">
  19. <view>
  20. {{currentAddress.name}}
  21. </view>
  22. <view style="border: solid #7D8196 1px; margin: 0 10px; height: 12px;"> </view>
  23. <view>
  24. {{currentAddress.phone}}
  25. </view>
  26. </view>
  27. </view>
  28. </view>
  29. </view>
  30. </uni-card>
  31. </view>
  32. <view class="service-new-pet">
  33. <uni-card padding=0 :is-shadow="false">
  34. <view class="service-new-title" slot="title">
  35. <view class="service-new-title-left">
  36. <view class="service-new-flag">
  37. </view>
  38. <view class="service-new-title-text">
  39. 服务宠物
  40. </view>
  41. </view>
  42. </view>
  43. <view class="split-line"></view>
  44. <view class="service-new-pet-content">
  45. <view class="personal-pet-list">
  46. <view v-for="(item,index) in showPets" :key="index">
  47. <view class="personal-pet-list-item">
  48. <view class="personal-pet-info">
  49. <!-- 左侧头像 -->
  50. <view class="pet-avatar">
  51. <u-avatar :src="item.photo?item.photo:defaultPhoto" size="60"
  52. shape="circle"></u-avatar>
  53. </view>
  54. <!-- 中间内容 -->
  55. <view class="pet-info" style="flex: 1; margin: 0 20rpx; max-width: 50%;">
  56. <view class="pet-name-gender" style="display: flex; align-items: center;">
  57. <view>{{item.name}}</view>
  58. <view class="pet-gender"
  59. style="margin-left: 10rpx; display: flex;align-items: center;">
  60. <img :src="item.gender=='男生'?'../../static/images/details/boy.svg':'../../static/images/details/girl.svg'"
  61. alt="sex" style="width: 16px;height: 16px;" />
  62. </view>
  63. </view>
  64. <view class="pet-dates ellipsis">
  65. {{ item.pets.map(e=>e.serviceDate).join(';') }}
  66. </view>
  67. </view>
  68. <!-- 右侧天数统计 -->
  69. <view class="date-total" style="margin-left: auto;width: 140rpx;text-align: end;">
  70. {{item.pets.length}}
  71. </view>
  72. </view>
  73. </view>
  74. </view>
  75. </view>
  76. </view>
  77. </uni-card>
  78. </view>
  79. <view class="service-new-pet">
  80. <uni-card padding=0 :is-shadow="false">
  81. <view class="service-new-title" slot="title">
  82. <view class="service-new-title-left">
  83. <view class="service-new-flag">
  84. </view>
  85. <view class="service-new-title-text">
  86. 服务信息及费用
  87. </view>
  88. </view>
  89. </view>
  90. <view class="split-line"></view>
  91. <view class="service-new-pet-content">
  92. <view class="personal-pet-list">
  93. <view v-for="item in dailyShowData" :key="item.date" class="service-new-address">
  94. <view class="service-summary">
  95. <view style="display: flex; align-items: center;">
  96. <view style="padding-right: 10rpx;color: #7D8196;">{{ getDateText(item.date) }}</view>
  97. <!-- <view style="padding-right: 10rpx;color: #7D8196;">{{ item.date }}</view> -->
  98. <view
  99. style="margin: 0 10rpx; width: 2px; height: 20rpx; background-color: #7D8196;">
  100. </view>
  101. <view style="color: #333333; margin-left: 10rpx;">{{baseProduct}}
  102. {{item.customServicesTotalCnt? ' + 定制服务' + item.customServicesTotalCnt + '项' : '' }}
  103. </view>
  104. </view>
  105. <view style="display: flex; align-items: center;">
  106. <view style="color: #333333;margin-right: 10rpx;">¥{{ item.totalCost }}.00</view>
  107. <view>
  108. <u-icon @click="toggleExpand( item.date )"
  109. :name="expandedIndexs.includes( item.date ) ? 'arrow-up' : 'arrow-down'"
  110. color="#FFBF60" size="14"></u-icon>
  111. </view>
  112. </view>
  113. </view>
  114. <view v-if="!expandedIndexs.includes(item.date)" class="split-line"></view>
  115. <view v-show="expandedIndexs.includes(item.date)" class="service-details">
  116. <!-- 基础服务 上门次数 额外费用 -->
  117. <view>基础服务</view>
  118. <view v-for="(priceItem, priceIndex) in item.priceDetails" :key="priceIndex"
  119. class="price-details-item">
  120. <view style="display: flex;" v-for="(item2, index) in priceItem.item" :key="index">
  121. <view v-if="item2.quantity" class="service-item">
  122. <view v-if="item2.itemName != item.name"
  123. class="price-details-item-price-total-item">- {{ item2.itemName }}
  124. </view>
  125. <view class="price-details-item-price-total-item">¥{{ item2.price }} ×
  126. {{ item2.quantity }} {{ item2.unit }}</view>
  127. </view>
  128. </view>
  129. </view>
  130. <view v-if="item.customServicesTotalCnt>0" style="margin-top: 20rpx;">定制服务</view>
  131. <view v-for="(pet, petIndex) in item.pets" :key="petIndex">
  132. <view
  133. v-if="pet.customServices &&pet.customServices.filter(e=>e.quantity>0).length>0"
  134. style="display: flex; align-items: center; justify-content: flex-start; margin-top: 20rpx;">
  135. <view class="pet-avatar">
  136. <u-avatar :src="pet.photo?pet.photo:defaultPhoto" size="34"
  137. shape="circle"></u-avatar>
  138. </view>
  139. <view style="margin-left: 20rpx;">{{ pet.name}}</view>
  140. </view>
  141. <!-- 定制服务 -->
  142. <view v-for="(customItem, customIndex) in pet.customServices" :key="customIndex">
  143. <view v-if="customItem.quantity" class="service-item">
  144. <view>- {{ customItem.name }}</view>
  145. <view>¥{{ customItem.price }} × {{ customItem.quantity }} </view>
  146. </view>
  147. </view>
  148. </view>
  149. </view>
  150. </view>
  151. <view class="service-new-address">
  152. <view v-if="needPreFamiliarize.length>0" class="total-cost">
  153. <view>提前熟悉 </view>
  154. <view>¥40</view>
  155. </view>
  156. <view class="total-cost">
  157. <view>费用总计 </view>
  158. <view>¥{{ originalTotalPrice }}</view>
  159. </view>
  160. <view class="total-cost" @click="selectCoupon">
  161. <view>平台优惠</view>
  162. <view style="color: #FF530A;">
  163. {{ selectedCoupon ? selectedCoupon.stockName : '未使用优惠券' }}
  164. <uni-icons type="right" size="28rpx" color="#AAA"></uni-icons>
  165. </view>
  166. </view>
  167. <view class="total-cost">
  168. <view>会员折扣</view>
  169. <view style="display: flex; align-items: center;">
  170. <view style="color: #999999;">{{ currentMember.itemType }}</view>
  171. <view style="border: solid #7D8196 1px; margin: 0 10rpx; height: 12px;"> </view>
  172. <view style="color: #FF530A;">-¥{{ memberDiscount }}</view>
  173. </view>
  174. </view>
  175. <view class="total-cost">
  176. <view>应付费用</view>
  177. <view style="font-weight: 500;font-size: 32rpx;">¥{{ finalPrice }}</view>
  178. </view>
  179. </view>
  180. </view>
  181. </view>
  182. </uni-card>
  183. </view>
  184. <view class="service-new-pet">
  185. <uni-card padding=0 :is-shadow="false">
  186. <view class="service-new-title" slot="title">
  187. <view class="service-new-title-left">
  188. <view class="service-new-flag">
  189. </view>
  190. <view class="service-new-title-text">
  191. 服务细则
  192. </view>
  193. </view>
  194. </view>
  195. <view class="split-line"></view>
  196. <view class="service-new-details-content">
  197. <view style="margin: 30rpx 0;">
  198. <u-checkbox-group @change="changePreFamiliarize" :value="needPreFamiliarize"
  199. iconPlacement="right" placement="column">
  200. <u-checkbox activeColor="#FFBF60" label="是否提前熟悉" name="是否提前熟悉" shape="circle"></u-checkbox>
  201. </u-checkbox-group>
  202. </view>
  203. <view class="split-line"></view>
  204. <view class="service-new-details-desc">
  205. <view style="display: flex;">
  206. <text style="width: 20rpx;">*</text>
  207. <text style="flex: 1;">价格40元/</text>
  208. </view>
  209. <view style="display: flex; margin: 20rpx 0;">
  210. <text style="width: 20rpx;">*</text>
  211. <text style="flex: 1;">服务内容: 购买此服务后伴宠师将在您离家前按照约定日期提前上门沟通熟悉喂养要求及宠物</text>
  212. </view>
  213. <view style="display: flex;">
  214. <text style="width: 20rpx;">*</text>
  215. <text style="flex: 1;">服务保障: 购买此服务后平台支持在提前熟悉后上门服务第一天前无理由免费更换伴宠师1次</text>
  216. </view>
  217. </view>
  218. </view>
  219. </uni-card>
  220. </view>
  221. <view class="details-subscribe">
  222. <view class="details-radio" style="width: 100%;padding-bottom: 20rpx;">
  223. <view>
  224. <u-checkbox-group v-model="isAgree" size="16" labelSize="14" labelColor="#999999">
  225. <u-checkbox activeColor="#FFBF60" label="我同意" name="我同意" labelSize="14" size="14"
  226. shape="circle"></u-checkbox>
  227. </u-checkbox-group>
  228. </view>
  229. <text style="color: #FFBF60;font-size: 28rpx;" @click="checkAgreement">猫妈狗爸用户服务协议和隐私协议</text>
  230. </view>
  231. <view style="display: flex;justify-content: space-between; align-items: center;">
  232. <view style="height: 80rpx; display: flex; align-items: center;">
  233. <text style="color: #333333;">订单总价: </text>
  234. <text style="color: #FF530A; font-size: 40rpx;">¥{{ finalPrice }} <text
  235. style="font-size: 32rpx;"></text></text>
  236. </view>
  237. <view style="display: flex;">
  238. <u-button color="#FFF4E4" customStyle="width: 200rpx; color: #FFAA48; margin-right: 20rpx;"
  239. text="上一步" @click="goBack"></u-button>
  240. <u-button color="#FFBF60" customStyle="width: 200rpx; color: #FFF;" text="支付"
  241. @click="goNext"></u-button>
  242. </view>
  243. </view>
  244. </view>
  245. <view v-if="showCoupon" class="calendar-popup">
  246. <view class="calendar-mask"></view>
  247. <view class="calendar-content">
  248. <view class="price-details">
  249. <view class="price-details-header">
  250. <text
  251. style="text-align: center; width: 100%; font-size: 32rpx; font-weight: 500; color: #333333;">优惠券 (已自动选择最优惠)</text>
  252. <u-icon name="close" @click="togglePriceDetails"></u-icon>
  253. </view>
  254. <view class="split-line"></view>
  255. <scroll-view class="price-details-body" scroll-y v-if="showCouponList">
  256. <view v-for="(item,index) in couponList" style="padding-bottom:14px;" :key="index">
  257. <view class="coupon-card" :class="{
  258. 'auto-selected': item.checked && item.checked.length > 0 && item.isAutoSelected,
  259. 'manual-selected': item.checked && item.checked.length > 0 && !item.isAutoSelected
  260. }">
  261. <view class="card-left"><text
  262. style="font-size: 40rpx; margin-left: 5rpx;">{{ getCouponAmountOrDiscount(item) }}</text>
  263. </view>
  264. <view class="card-right">
  265. <view class="card-content">
  266. <view class="card-info">
  267. {{item.stockName}}
  268. <text v-if="item.checked && item.checked.length > 0 && item.isAutoSelected" style="color: #FF530A; font-size: 24rpx; margin-left: 10rpx;">(自动选择)</text>
  269. <text v-if="item.checked && item.checked.length > 0 && !item.isAutoSelected" style="color: #FF530A; font-size: 24rpx; margin-left: 10rpx;">(手动选择)</text>
  270. </view>
  271. <view class="card-time">有效期限: {{item.expireTime.slice(0, 10)}}截止</view>
  272. </view>
  273. <view style="width: 20%;">
  274. <u-checkbox-group :value="item.checked" @change="changeCoupon">
  275. <u-checkbox :disabled="item.transactionMinimum > originalTotalPrice"
  276. shape="circle" :name="item.id" activeColor="#ffbf60"></u-checkbox>
  277. </u-checkbox-group>
  278. </view>
  279. </view>
  280. </view>
  281. </view>
  282. </scroll-view>
  283. </view>
  284. <u-button color="#FFBF60" type="primary" @click="confirmCoupon">确定</u-button>
  285. </view>
  286. </view>
  287. </view>
  288. </template>
  289. <script>
  290. import {
  291. getPersonalInfo,
  292. getCouponList,
  293. getCouponListForOrder
  294. } from "@/api/system/personal.js"
  295. import {
  296. getToken,
  297. getOpenIdKey
  298. } from '@/utils/auth'
  299. import {
  300. createOrderNew
  301. } from '@/api/system/user.js'
  302. import dayjs from '@/utils/lib/dayjs.min.js'
  303. export default {
  304. data() {
  305. return {
  306. isPaying: false,
  307. currentAddress: {},
  308. currentPetsByDay: [],
  309. showPets: [],
  310. expandedIndexs: [],
  311. isAgree: false,
  312. needPreFamiliarize: [],
  313. dailyShowData: [],
  314. originalTotalPrice: 0,
  315. totalPrice: 0, // 总费用
  316. discount: 0, // 平台优惠
  317. memberDiscount: 0, // 会员折扣
  318. finalPrice: 0, // 应付费用
  319. defaultPhoto: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/catdog.png',
  320. currentMember: {},
  321. memberDiscountList: [{
  322. itemType: "新晋家长9.5折优惠",
  323. discount: 0.05
  324. },
  325. {
  326. itemType: "普卡会员9折优惠",
  327. discount: 0.1
  328. },
  329. {
  330. itemType: "银卡会员8.8折优惠",
  331. discount: 0.12
  332. },
  333. {
  334. itemType: "金卡会员8.5折优惠",
  335. discount: 0.15
  336. }
  337. ],
  338. couponList: [],
  339. showCoupon: false,
  340. selectedCoupon: null,
  341. showCouponList: true,
  342. couponId: null,
  343. basePrice: 0,
  344. baseProduct: '',
  345. }
  346. },
  347. onLoad() {
  348. if (getToken() && getOpenIdKey()) {
  349. this.getPersonalInfo()
  350. }
  351. this.originalTotalPrice = this.$globalData.newOrderData.totalPrice
  352. if (this.originalTotalPrice) {
  353. this.basePrice = this.$globalData.mainSku[0].price
  354. this.baseProduct = this.$globalData.mainSku[0].name
  355. this.currentAddress = this.$globalData.newOrderData.currentAddress
  356. this.currentPetsByDay = this.$globalData.newOrderData.currentPetsByDay
  357. this.needPreFamiliarize = this.$globalData.newOrderData.needPreFamiliarize
  358. } else {
  359. // 返回首页
  360. uni.reLaunch({
  361. url: '/pages/index'
  362. });
  363. }
  364. this.getShowPets()
  365. this.groupPetsByDate()
  366. this.getCouponList()
  367. // this.totalPrice = this.$globalData.newOrderData.totalPrice
  368. },
  369. methods: {
  370. getDateText(date){
  371. return dayjs(date).format('MM-DD')
  372. },
  373. // 将currentPets转换为showPets
  374. getShowPets() {
  375. const showPets = []
  376. // 将 currentPetsByDay 通过petId分组
  377. const groupedPets = this.currentPetsByDay.reduce((acc, pet) => {
  378. pet.index = pet.petId + '-' + pet.serviceDate
  379. if (!acc[pet.petId]) {
  380. acc[pet.petId] = []; // 如果不存在,则初始化一个空数组
  381. }
  382. acc[pet.petId].push(pet); // 将当前宠物添加到对应的 petId 数组中
  383. return acc;
  384. }, {});
  385. console.log(groupedPets)
  386. // 循环将pets转换为showPets
  387. for (let petId in groupedPets) {
  388. const showPet = {
  389. petId: petId,
  390. name: groupedPets[petId][0].name,
  391. gender: groupedPets[petId][0].gender,
  392. petType: groupedPets[petId][0].petType,
  393. bodyType: groupedPets[petId][0].bodyType,
  394. photo: groupedPets[petId][0].photo,
  395. pets: groupedPets[petId]
  396. }
  397. showPets.push(showPet)
  398. }
  399. this.showPets = showPets
  400. console.log(this.showPets)
  401. },
  402. // 将宠物按天分组
  403. groupPetsByDate() {
  404. const dailyShowData = []
  405. const dailyPets = [];
  406. // 按日期分组宠物
  407. this.currentPetsByDay.forEach(pet => {
  408. const serviceDate = pet.serviceDate;
  409. if (!dailyPets[serviceDate]) {
  410. dailyPets[serviceDate] = [];
  411. }
  412. dailyPets[serviceDate].push(pet);
  413. });
  414. // 计算每日费用
  415. for (const date in dailyPets) {
  416. const pets = dailyPets[date];
  417. const priceDetails = []
  418. // 基础服务
  419. const baseServiceCost = this.basePrice
  420. const largeDogCount = pets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes('大型')).length;
  421. let mediumDogCount = pets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes('中型')).length;
  422. let smallDogCount = pets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes('小型')).length;
  423. let catCount = pets.filter(pet => pet.petType === 'cat').length;
  424. const additionalCostItem = []
  425. let additionalCost = 0
  426. // 单天总宠物费用
  427. let totalPetCost = pets.reduce((acc, pet) => {
  428. return acc + this.calculatePetCost(pet);
  429. }, 0);
  430. // 如果总宠物费用>30
  431. if (totalPetCost > 30) {
  432. // 如果猫数量>=3,则免3只猫
  433. if (catCount >= 3) {
  434. additionalCost = totalPetCost - 30
  435. catCount = catCount - 3
  436. } else if (smallDogCount >= 2) { // 如果小型犬数量>=2,则免2只小型犬
  437. additionalCost = totalPetCost - 30
  438. smallDogCount = smallDogCount - 2
  439. } else if (mediumDogCount >= 1) { // 如果中型犬数量>=1,则免1只中型犬
  440. additionalCost = totalPetCost - 30
  441. mediumDogCount = mediumDogCount - 1
  442. } else {
  443. additionalCost = totalPetCost - 25
  444. catCount = catCount - 1
  445. smallDogCount = smallDogCount - 1
  446. }
  447. if (mediumDogCount > 0) {
  448. additionalCostItem.push({
  449. itemName: '中型犬',
  450. price: 30,
  451. quantity: mediumDogCount,
  452. unit: '只'
  453. })
  454. }
  455. if (smallDogCount > 0) {
  456. additionalCostItem.push({
  457. itemName: '小型犬',
  458. price: 15,
  459. quantity: smallDogCount,
  460. unit: '只'
  461. })
  462. }
  463. if (catCount > 0) {
  464. additionalCostItem.push({
  465. itemName: '猫猫',
  466. price: 10,
  467. quantity: catCount,
  468. unit: '只'
  469. })
  470. }
  471. }
  472. // 如果有大型犬,额外费用为40元/只
  473. if (largeDogCount > 0) {
  474. additionalCost += (40 * largeDogCount)
  475. additionalCostItem.push({
  476. itemName: '大型犬',
  477. price: 40,
  478. quantity: largeDogCount,
  479. unit: '只'
  480. })
  481. }
  482. // 当日多次服务次数
  483. let multServicesTotalCost = 0
  484. const maxFeedCount = Math.max(...pets.map(pet => pet.feedCount));
  485. if (maxFeedCount === 2) {
  486. multServicesTotalCost += 45; // 1天2次
  487. } else if (maxFeedCount === 3) {
  488. multServicesTotalCost += 130; // 1天3次
  489. }
  490. priceDetails.push({
  491. name: '专业喂养',
  492. item: [{
  493. itemName: '专业喂养',
  494. price: baseServiceCost,
  495. quantity: 1,
  496. unit: '天'
  497. }, ]
  498. })
  499. priceDetails.push({
  500. name: '上门次数',
  501. item: [{
  502. itemName: '1天2次',
  503. price: 45,
  504. quantity: maxFeedCount === 2 ? 1 : 0,
  505. unit: '天'
  506. },
  507. {
  508. itemName: '1天3次',
  509. price: 130,
  510. quantity: maxFeedCount === 3 ? 1 : 0,
  511. unit: '天'
  512. },
  513. ]
  514. })
  515. if (additionalCostItem.length > 0) {
  516. priceDetails.push({
  517. name: '额外宠物费用',
  518. item: additionalCostItem
  519. })
  520. }
  521. // 所有宠物定制服务费用
  522. const customServiceCost = pets.reduce((acc, pet) => acc + this.calculatePetCustomServiceCost(pet), 0)
  523. const totalCost = baseServiceCost + additionalCost + multServicesTotalCost + customServiceCost
  524. // 所有宠物定制服务总项数,每个类型的服务只算一次
  525. const acc = []
  526. pets.map(pet => {
  527. pet.customServices.forEach(service => {
  528. if (!acc.includes(service.skuId) && service.quantity > 0) {
  529. acc.push(service.skuId)
  530. }
  531. })
  532. })
  533. const customServicesTotalCnt = acc.length
  534. dailyShowData.push({
  535. date,
  536. pets,
  537. priceDetails,
  538. totalCost,
  539. customServicesTotalCnt
  540. })
  541. }
  542. // 将dailyShowData按日期排序
  543. this.dailyShowData = dailyShowData.sort((a, b) => new Date(a.date) - new Date(b.date))
  544. this.getShowTotalPrice()
  545. },
  546. calculatePetCost(pet) {
  547. // 宠物额外费用 不计算大型犬
  548. let petCost = 0;
  549. if (pet.petType === 'cat') {
  550. petCost += 10; // 猫额外费用
  551. } else if (pet.petType === 'dog' && pet.bodyType.includes('小型')) {
  552. petCost += 15; // 小型犬额外费用
  553. } else if (pet.petType === 'dog' && pet.bodyType.includes('中型')) {
  554. petCost += 30; // 中型犬额外费用
  555. }
  556. return petCost;
  557. },
  558. // 计算宠物定制服务费用
  559. calculatePetCustomServiceCost(pet) {
  560. const customServiceCost = pet.customServices.reduce((acc, item) => acc + item.price * item.quantity, 0)
  561. return customServiceCost
  562. },
  563. // 展开或收起服务详情
  564. toggleExpand(index) {
  565. console.log(index)
  566. this.expandedIndexs = this.expandedIndexs.includes(index) ? this.expandedIndexs.filter(i => i !== index) :
  567. [...this.expandedIndexs, index];
  568. // this.expandedIndexs.push(index)
  569. console.log(this.expandedIndexs)
  570. },
  571. goBack() {
  572. let len = getCurrentPages().length;
  573. if (len >= 2) {
  574. uni.navigateBack();
  575. } else {
  576. uni.redirectTo({
  577. url: '/pages/newOrder/serviceNew2'
  578. });
  579. }
  580. },
  581. changeAgree() {
  582. this.isAgree = !this.isAgree
  583. },
  584. checkAgreement() {
  585. uni.navigateTo({
  586. url: '/pages/details/agreement'
  587. });
  588. },
  589. // 节流
  590. throttle(func, delay) {
  591. let lastCall = 0;
  592. return function(...args) {
  593. const now = new Date().getTime();
  594. if (now - lastCall < delay) {
  595. return;
  596. }
  597. lastCall = now;
  598. func(...args);
  599. }
  600. },
  601. getPersonalInfo() {
  602. getPersonalInfo().then(res => {
  603. if (res && (res.id || res.id === 0)) {
  604. let userLevel = res.level
  605. this.currentMember = this.memberDiscountList.find(item => {
  606. if (item.itemType.includes(userLevel)) {
  607. return item
  608. }
  609. })
  610. this.getShowTotalPrice()
  611. // 重新计算最优惠的优惠券
  612. if (this.couponList.length > 0) {
  613. this.autoSelectBestCoupon()
  614. }
  615. }
  616. })
  617. },
  618. // 获取优惠券列表
  619. getCouponList() {
  620. getCouponListForOrder().then(res => {
  621. let rows = res.rows
  622. console.log(rows)
  623. this.couponList = rows.filter(item => item.couponState == "SENDED")
  624. this.couponList.forEach(item => {
  625. item.checked = []
  626. item.couponAmount = 20
  627. item.isAutoSelected = false
  628. })
  629. // 自动选择最优惠的优惠券
  630. this.autoSelectBestCoupon()
  631. })
  632. },
  633. selectCoupon() {
  634. this.showCoupon = true
  635. },
  636. changeCoupon(item) {
  637. this.showCouponList = false
  638. const selectedCouponId = item[0]
  639. const selectedCoupon = this.couponList.find(coupon => coupon.id === selectedCouponId)
  640. // 清空所有优惠券的选中状态和自动选择标记
  641. this.couponList.forEach(coupon => {
  642. coupon.checked = null
  643. coupon.isAutoSelected = false
  644. })
  645. // 如果选择了优惠券,检查是否有相同 couponState 的优惠券已被使用
  646. if (selectedCoupon) {
  647. // 检查是否有相同 couponState 的优惠券已被使用
  648. const hasSameStateUsed = this.couponList.some(coupon =>
  649. coupon.id !== selectedCouponId &&
  650. coupon.couponState === selectedCoupon.couponState &&
  651. coupon.checked &&
  652. coupon.checked.length > 0
  653. )
  654. if (hasSameStateUsed) {
  655. uni.showToast({
  656. title: '相同类型的优惠券不可重复使用',
  657. icon: 'none'
  658. })
  659. this.showCouponList = true
  660. return
  661. }
  662. // 设置选中状态,标记为用户手动选择
  663. selectedCoupon.checked = item
  664. selectedCoupon.isAutoSelected = false
  665. }
  666. this.showCouponList = true
  667. },
  668. confirmCoupon() {
  669. this.selectedCoupon = this.couponList.find(coupon => coupon.checked && coupon.checked.length > 0)
  670. if (!this.selectedCoupon) {
  671. this.couponId = null
  672. this.discount = 0
  673. } else {
  674. this.couponId = this.selectedCoupon?.id
  675. // 如果优惠券是折扣
  676. if (this.selectedCoupon?.stockType == "PDISCOUNT" && this.selectedCoupon?.discountPercent > 0) {
  677. //保留两位小数
  678. this.discount = (this.originalTotalPrice * (1 - this.selectedCoupon.discountPercent / 100))
  679. .toFixed(2)
  680. } else {
  681. this.discount = this.selectedCoupon?.discountAmount
  682. }
  683. }
  684. this.getShowTotalPrice()
  685. this.showCoupon = false
  686. },
  687. togglePriceDetails() {
  688. this.showCoupon = !this.showCoupon
  689. },
  690. // 构造订单
  691. constructOrder() {
  692. const order = {
  693. openId: getOpenIdKey(),
  694. addressId: this.currentAddress.id,
  695. totalPrice: this.finalPrice,
  696. needPreFamiliarize: this.needPreFamiliarize.length > 0,
  697. couponId: this.couponId,
  698. petOrderServices: this.getPetOrderServices(this.currentPetsByDay),
  699. }
  700. if(this.buyInfo.teacher){
  701. order.teacherId = this.buyInfo.teacher.userId
  702. }else{
  703. //打印
  704. console.log(this.$globalData.newOrderData.companionLevel);
  705. order.companionLevel = ['', 'junior', 'senior'].indexOf(this.$globalData.newOrderData.companionLevel)
  706. }
  707. console.log(order)
  708. return order
  709. },
  710. getSkuList(customServices, feedCount) {
  711. const skuList = customServices.filter(service => service.quantity > 0).map(service2 => {
  712. return {
  713. skuId: service2.skuId,
  714. quantity: service2.quantity,
  715. isMainProduct: service2.isMainProduct
  716. }
  717. })
  718. skuList.push({
  719. skuId: this.$globalData.mainSku[0].skuId,
  720. quantity: feedCount,
  721. isMainProduct: true
  722. })
  723. return skuList
  724. },
  725. getPetOrderServices(currentPetsByDay) {
  726. const petOrderServices = currentPetsByDay.map(pet => {
  727. return {
  728. petId: pet.petId,
  729. serviceDate: pet.serviceDate,
  730. feedCount: pet.feedCount,
  731. selectedTimeSlots: pet.selectedTimeSlots.join(','),
  732. skuList: this.getSkuList(pet.customServices, pet.feedCount)
  733. }
  734. })
  735. return petOrderServices
  736. },
  737. pay(params) {
  738. if (this.isPaying) {
  739. return;
  740. }
  741. this.isPaying = true
  742. uni.requestPayment({
  743. provider: 'wxpay',
  744. timeStamp: params.timeStamp,
  745. nonceStr: params.nonceStr,
  746. package: params.package_,
  747. signType: params.signType,
  748. paySign: params.paySign,
  749. success: (res) => {
  750. this.$modal.showToast('支付成功')
  751. this.$globalData.newOrderData = {
  752. currentAddress: {},
  753. currentPets: [],
  754. totalPrice: 0,
  755. needPreFamiliarize: []
  756. }
  757. uni.reLaunch({
  758. url: '/pages_order/order/payOrderSuccessful'
  759. // url: '/pages/details/successful'
  760. });
  761. },
  762. fail: (err) => {
  763. this.loading = false
  764. console.log('支付失败', err)
  765. this.$modal.showToast('支付失败')
  766. },
  767. complete: () => {
  768. this.loading = false
  769. this.isPaying = false
  770. }
  771. })
  772. },
  773. changePreFamiliarize(name) {
  774. if (name && name.length > 0) {
  775. this.needPreFamiliarize = name
  776. this.originalTotalPrice = this.originalTotalPrice + 40
  777. } else {
  778. this.needPreFamiliarize = []
  779. this.originalTotalPrice = this.originalTotalPrice - 40
  780. }
  781. // 重新计算最优惠的优惠券
  782. this.autoSelectBestCoupon()
  783. this.getShowTotalPrice()
  784. },
  785. getShowTotalPrice() {
  786. //保留两位小数
  787. this.memberDiscount = ((this.originalTotalPrice - this.discount) * this.currentMember.discount).toFixed(2)
  788. this.finalPrice = (this.originalTotalPrice - this.memberDiscount - this.discount).toFixed(2)
  789. },
  790. getCouponAmountOrDiscount(item) {
  791. if (item.stockType == "PDISCOUNT") {
  792. return item.discountPercent / 10 + '折'
  793. } else {
  794. return '¥' + item.discountAmount
  795. }
  796. },
  797. // 自动选择最优惠的优惠券
  798. autoSelectBestCoupon() {
  799. // 清空所有优惠券的选中状态和自动选择标记
  800. this.couponList.forEach(coupon => {
  801. coupon.checked = null
  802. coupon.isAutoSelected = false
  803. })
  804. // 过滤出可用的优惠券(满足最低消费要求)
  805. const availableCoupons = this.couponList.filter(coupon =>
  806. this.originalTotalPrice >= coupon.transactionMinimum
  807. )
  808. if (availableCoupons.length === 0) {
  809. this.selectedCoupon = null
  810. this.couponId = null
  811. this.discount = 0
  812. return
  813. }
  814. // 计算每个优惠券的实际优惠金额
  815. const couponsWithDiscount = availableCoupons.map(coupon => {
  816. let discountAmount = 0
  817. if (coupon.stockType === "PDISCOUNT" && coupon.discountPercent > 0) {
  818. // 折扣券:计算折扣后的优惠金额
  819. discountAmount = this.originalTotalPrice * (coupon.discountPercent / 100)
  820. } else {
  821. // 满减券:直接使用优惠金额
  822. discountAmount = coupon.discountAmount
  823. }
  824. return {
  825. ...coupon,
  826. calculatedDiscount: discountAmount
  827. }
  828. })
  829. // 按优惠金额从高到低排序,选择最优惠的
  830. couponsWithDiscount.sort((a, b) => b.calculatedDiscount - a.calculatedDiscount)
  831. // 选择最优惠的优惠券
  832. const bestCoupon = couponsWithDiscount[0]
  833. // 在原始的 couponList 中找到对应的优惠券并设置选中状态
  834. const originalCoupon = this.couponList.find(coupon => coupon.id === bestCoupon.id)
  835. if (originalCoupon) {
  836. originalCoupon.checked = [originalCoupon.id]
  837. originalCoupon.isAutoSelected = true // 标记为自动选择
  838. this.selectedCoupon = originalCoupon
  839. this.couponId = originalCoupon.id
  840. // 计算优惠金额
  841. if (originalCoupon.stockType === "PDISCOUNT" && originalCoupon.discountPercent > 0) {
  842. this.discount = (this.originalTotalPrice * (1 - originalCoupon.discountPercent / 100)).toFixed(2)
  843. } else {
  844. this.discount = originalCoupon.discountAmount
  845. }
  846. console.log('自动选择最优惠优惠券:', originalCoupon.stockName, '优惠金额:', this.discount)
  847. }
  848. // 重新计算总价
  849. this.getShowTotalPrice()
  850. },
  851. goNext() {
  852. if (!this.isAgree) {
  853. uni.showToast({
  854. title: '请先同意用户协议',
  855. icon: 'none'
  856. })
  857. return
  858. }
  859. const order = this.constructOrder()
  860. createOrderNew(order).then(res => {
  861. if (res.code == 200) {
  862. this.pay(res.data)
  863. } else {
  864. this.$modal.showToast('创建订单失败,请重试');
  865. this.loading = false
  866. }
  867. })
  868. }
  869. }
  870. }
  871. </script>
  872. <style scoped lang="scss">
  873. .container {
  874. position: relative;
  875. height: 100%;
  876. padding-bottom: 200rpx;
  877. .details-subscribe {
  878. background-color: #FFFFFF;
  879. padding: 10px;
  880. width: 100%;
  881. height: 200rpx;
  882. position: fixed;
  883. bottom: 0;
  884. z-index: 100;
  885. .details-btn {
  886. width: 100%;
  887. border-radius: 6px;
  888. background: #FFB13F;
  889. font-size: 16px;
  890. color: #FFFFFF;
  891. }
  892. .details-radio {
  893. display: flex;
  894. align-items: center;
  895. justify-content: center;
  896. }
  897. }
  898. }
  899. .service-new {
  900. .service-new-flag {
  901. width: 8rpx;
  902. height: 32rpx;
  903. background: #FFBF60;
  904. border-radius: 30rpx 30rpx 30rpx 30rpx;
  905. margin-right: 10rpx;
  906. }
  907. .split-line {
  908. width: 100%;
  909. height: 1rpx;
  910. background: #EFEFEF;
  911. }
  912. .service-new-title {
  913. display: flex;
  914. font-weight: 500;
  915. font-size: 28rpx;
  916. color: #333333;
  917. line-height: 33rpx;
  918. margin: 42rpx 0 30rpx;
  919. justify-content: space-between;
  920. .service-new-title-left {
  921. display: flex;
  922. align-items: center;
  923. }
  924. }
  925. .service-new-details-desc {
  926. font-weight: 400;
  927. font-size: 24rpx;
  928. color: #A94F20;
  929. line-height: 28rpx;
  930. text-align: left;
  931. padding: 26rpx 0;
  932. }
  933. .personal-address-info {
  934. display: flex;
  935. align-items: center;
  936. justify-content: flex-start;
  937. flex-wrap: wrap;
  938. margin: 32rpx 0;
  939. width: 80%;
  940. .personal-address-text {
  941. color: #333;
  942. font-size: 28rpx;
  943. font-weight: bold;
  944. width: 100%;
  945. }
  946. .personal-address-people {
  947. color: #7D8196;
  948. font-size: 28rpx;
  949. font-weight: 400;
  950. display: flex;
  951. justify-content: flex-start;
  952. align-items: center;
  953. }
  954. }
  955. .service-new-address-selected {
  956. display: flex;
  957. justify-content: space-between;
  958. align-items: center;
  959. }
  960. }
  961. .personal-pet-list {
  962. margin-top: 20rpx;
  963. .personal-pet-list-item {
  964. margin-bottom: 20rpx;
  965. background-color: #F9F9F9;
  966. height: 172rpx;
  967. border-radius: 8rpx;
  968. .personal-pet-info {
  969. height: 100%;
  970. display: flex;
  971. align-items: center;
  972. padding: 0 20rpx;
  973. }
  974. }
  975. }
  976. .service-new-address {
  977. .service-summary {
  978. height: 90rpx;
  979. display: flex;
  980. justify-content: space-between;
  981. align-items: center;
  982. padding: 15rpx;
  983. cursor: pointer;
  984. transition: background-color 0.3s;
  985. }
  986. .service-details {
  987. padding: 10rpx 15rpx;
  988. background-color: #F9F9F9;
  989. margin-bottom: 10rpx;
  990. .pet-info {
  991. display: flex;
  992. justify-content: space-between;
  993. margin: 5rpx 0;
  994. font-size: 24rpx;
  995. color: #333;
  996. }
  997. .service-item {
  998. display: flex;
  999. justify-content: space-between;
  1000. width: 100%;
  1001. color: #999999;
  1002. font-size: 28rpx;
  1003. }
  1004. }
  1005. .total-cost {
  1006. padding: 10rpx 15rpx;
  1007. font-size: 28rpx;
  1008. color: #333;
  1009. display: flex;
  1010. justify-content: space-between;
  1011. }
  1012. }
  1013. .calendar-popup {
  1014. position: fixed;
  1015. bottom: 0;
  1016. left: 0;
  1017. right: 0;
  1018. z-index: 999;
  1019. .calendar-content {
  1020. background: #F5F5F7;
  1021. border-radius: 16rpx 16rpx 0 0;
  1022. padding: 0 20rpx 40rpx;
  1023. }
  1024. }
  1025. .calendar-mask {
  1026. position: fixed;
  1027. top: 0;
  1028. left: 0;
  1029. right: 0;
  1030. bottom: 0;
  1031. background: rgba(0, 0, 0, 0.5);
  1032. /* 半透明黑色 */
  1033. z-index: 998;
  1034. /* 确保在内容下方 */
  1035. pointer-events: none;
  1036. /* 使遮罩层不阻止点击事件 */
  1037. }
  1038. .calendar-content {
  1039. position: relative;
  1040. /* 确保内容在遮罩层之上 */
  1041. z-index: 999;
  1042. /* 确保内容在遮罩层之上 */
  1043. }
  1044. .price-details {
  1045. background: #F5F5F7;
  1046. /* 背景颜色 */
  1047. padding: 10px 10px 0 10px;
  1048. /* 内边距 */
  1049. z-index: 1000;
  1050. /* 确保在其他元素之上 */
  1051. min-height: 600rpx;
  1052. /* 固定高度 */
  1053. overflow: hidden;
  1054. /* 隐藏超出部分 */
  1055. }
  1056. .price-details-header {
  1057. display: flex;
  1058. justify-content: space-between;
  1059. align-items: center;
  1060. padding-bottom: 20rpx;
  1061. }
  1062. .price-details-body {
  1063. margin-top: 20rpx;
  1064. max-height: 450rpx;
  1065. /* 留出头部空间 */
  1066. overflow-y: auto;
  1067. /* 允许上下滚动 */
  1068. }
  1069. .coupon-card {
  1070. display: flex;
  1071. align-items: center;
  1072. width: 100%;
  1073. padding: 10px 0;
  1074. background: #fff;
  1075. border-radius: 8px;
  1076. -webkit-mask-image: radial-gradient(circle at 88px 4px, transparent 4px, #d8d8d8 4.5px), radial-gradient(closest-side circle at 50%, #d8d8d8 99%, transparent 100%);
  1077. -webkit-mask-size: 100%, 2px 4px;
  1078. -webkit-mask-repeat: repeat, repeat-y;
  1079. -webkit-mask-position: 0 -4px, 87px;
  1080. -webkit-mask-composite: source-out;
  1081. mask-composite: subtract;
  1082. // background: linear-gradient(45deg, orange, red);
  1083. // &.auto-selected {
  1084. // border: 2px solid #FF530A;
  1085. // background: linear-gradient(135deg, #fff 0%, #FFF4E4 100%);
  1086. // }
  1087. // &.manual-selected {
  1088. // border: 2px solid #4CAF50;
  1089. // background: linear-gradient(135deg, #fff 0%, #E8F5E8 100%);
  1090. // }
  1091. }
  1092. .card-left {
  1093. width: 88px;
  1094. text-align: center;
  1095. font-size: 28rpx;
  1096. color: #FF530A;
  1097. }
  1098. .card-right {
  1099. padding: 0px 12px;
  1100. display: flex;
  1101. flex: 1;
  1102. /* flex-direction: column; */
  1103. justify-content: space-between;
  1104. align-items: center;
  1105. height: 60px;
  1106. s .card-content {
  1107. width: 80%;
  1108. }
  1109. .card-icon {
  1110. position: relative;
  1111. right: -10px;
  1112. top: -10px;
  1113. }
  1114. }
  1115. .card-info {
  1116. margin: 0;
  1117. font-size: 14px;
  1118. line-height: 20px;
  1119. color: #333333;
  1120. }
  1121. .card-time {
  1122. font-size: 12px;
  1123. line-height: 16px;
  1124. font-weight: normal;
  1125. color: #aaaaaa;
  1126. margin-top: 4px;
  1127. }
  1128. </style>