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.

1292 lines
38 KiB

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