Browse Source

feat: 新增品种和性格选择页面,优化订单和用户相关功能

refactor: 重构用户头像上传逻辑,改用微信原生选择方式

fix: 修复订单列表和详情页的状态显示问题

perf: 优化日历组件显示节假日价格

docs: 更新价格配置和功能说明文档

style: 调整优惠券组件样式和布局

chore: 更新uni-calendar和ksp-cropper组件版本

build: 添加价格配置JSON文件

ci: 更新pages.json路由配置
master
前端-胡立永 2 months ago
parent
commit
fe5325a332
58 changed files with 4966 additions and 1473 deletions
  1. +12
    -0
      api/order/order.js
  2. +12
    -0
      api/system/user.js
  3. +299
    -0
      components/CouponItem/index.vue
  4. +198
    -0
      components/CouponRulePopup/index.vue
  5. BIN
      doc/Group 1000001818.png
  6. BIN
      doc/Group 1000001818@2x.png
  7. BIN
      doc/Group 1000001818@3x.png
  8. BIN
      doc/Rectangle 9031.png
  9. BIN
      doc/Rectangle 9031@2x.png
  10. BIN
      doc/Rectangle 9031@3x.png
  11. +0
    -0
      doc/logo.png
  12. +0
    -0
      doc/logo200.png
  13. +112
    -0
      docs/品种选择功能说明.md
  14. +71
    -0
      docs/性格选择功能说明.md
  15. +79
    -39
      main.js
  16. +1
    -1
      mixins/configList.js
  17. +9
    -3
      mixins/position.js
  18. +57
    -30
      pages.json
  19. +0
    -323
      pages/companionPetList/couponList.vue
  20. +1
    -1
      pages/components/NewUserCoupon.vue
  21. +69
    -251
      pages/index.vue
  22. +210
    -84
      pages/newOrder/confirmOrder.vue
  23. +24
    -0
      pages/newOrder/petList.vue
  24. +109
    -0
      pages/newOrder/price_config.json
  25. +69
    -30
      pages/newOrder/serviceNew.vue
  26. +264
    -95
      pages/newOrder/serviceNew2.vue
  27. +1
    -1
      pages/newOrder备份/confirmOrder.vue
  28. +2
    -2
      pages/newOrder备份/petList.vue
  29. +6
    -5
      pages/newOrder备份/serviceNew2.vue
  30. +405
    -0
      pages/personalCenter/breedSelect.vue
  31. +26
    -123
      pages/personalCenter/components/petBaseInfo.vue
  32. +52
    -14
      pages/personalCenter/index.vue
  33. +304
    -0
      pages/personalCenter/personalitySelect.vue
  34. +1
    -1
      pages/personalCenter/pet.vue
  35. +506
    -373
      pages/personalCenter/petInfo.vue
  36. +107
    -44
      pages/personalCenter/userInfo.vue
  37. +225
    -0
      pages_order/auth/login.vue
  38. +10
    -0
      pages_order/companionPetList/companionPetInfo.vue
  39. +2
    -2
      pages_order/companionPetList/companionPetList.vue
  40. +76
    -0
      pages_order/companionPetList/couponList.vue
  41. +1
    -1
      pages_order/components/order/CompanionSelectPopup.vue
  42. +14
    -2
      pages_order/components/order/ServiceItems.vue
  43. +2
    -2
      pages_order/components/order/ServiceRemarks.vue
  44. +1
    -1
      pages_order/order/companionSelect.vue
  45. +15
    -9
      pages_order/order/orderDetail.vue
  46. +18
    -7
      pages_order/order/orderList.vue
  47. +102
    -7
      pages_order/order/orderModify.vue
  48. +4
    -6
      pages_order/order/orderReview.vue
  49. +185
    -0
      pages_order/order/originalOrderData.json
  50. +3
    -1
      pages_order/order/payOrderSuccessful.vue
  51. +54
    -13
      store/index.js
  52. +36
    -0
      uni_modules/ksp-cropper/changelog.md
  53. +1004
    -0
      uni_modules/ksp-cropper/components/ksp-cropper/ksp-cropper.vue
  54. +81
    -0
      uni_modules/ksp-cropper/package.json
  55. +78
    -0
      uni_modules/ksp-cropper/readme.md
  56. +33
    -0
      uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue
  57. +15
    -1
      uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue
  58. +1
    -1
      utils/getUrl.js

+ 12
- 0
api/order/order.js View File

@ -178,4 +178,16 @@ export const orderPay = (data) => {
method: "POST",
data
})
}
// 再次支付订单
export const updateBaseOrder = (params) => {
return request({
url: '/applet/mall/order/updateBaseOrder',
headers: {
isToken: true
},
method: "POST",
params
})
}

+ 12
- 0
api/system/user.js View File

@ -153,4 +153,16 @@ export function getPhoneNumber(data) {
method: 'post',
data
})
}
//获取手机号
export function updateInfo(params) {
return request({
url: `/h5/member/updateInfoH5`,
headers: {
"isToken": true
},
method: 'post',
params
})
}

+ 299
- 0
components/CouponItem/index.vue View File

@ -0,0 +1,299 @@
<template>
<view style="padding:20rpx;">
<view>
<view class="card">
<view class="card-left">
<view class="">
{{switchType(couponData.stockType)}}
</view>
</view>
<view class="card-center">
<view class="card-center-top"></view>
<view class="card-center-bottom"></view>
</view>
<view class="card-right">
<view class="card-content">
<view class="card-info">{{couponData.stockName}}</view>
<view class="card-type">可用于
<text class="card-type-text">专业喂养</text>
<text class="card-type-text">专业遛狗</text>
<!-- <text class="card-type-text">{{ couponData.goodsName }}</text> -->
</view>
<view class="card-time">有效期至: {{couponData.availableEndTime ? couponData.availableEndTime.slice(0, 16) : ''}}</view>
</view>
<view :class="['coupon-btn', { 'coupon-btn-disabled': !canReceiveCoupon }]" @click="handleReceiveCoupon">
<text class="coupon-btn-text">{{ getCouponButtonText }}</text>
</view>
</view>
</view>
<view class="card-bottom">
<view class="card-bottom-text">
优惠券不可兑换现金
</view>
<view class="card-bottom-text" @click="showRulePopup">
查看详细规则>
</view>
</view>
</view>
</view>
</template>
<script>
import { receiveCoupon } from "@/api/system/user"
export default {
name: 'CouponItem',
props: {
//
couponData: {
type: Object,
default: () => ({})
}
},
computed: {
//
canReceiveCoupon() {
//
if (this.couponData.alreadyReceived && this.couponData.alreadyReceived >= this.couponData.maxCouponsPerUser) {
return false;
}
return true;
},
//
getCouponButtonText() {
if (!this.canReceiveCoupon) {
return '已领取';
}
return '立即领取';
}
},
methods: {
//
switchType(type) {
if (type == 'PNORMAL') {
return '满减券'
}
if (type == 'PDISCOUNT') {
return '折扣券'
}
if (type == 'PTRAIL') {
return '体验券'
}
return '优惠券'
},
//
handleReceiveCoupon() {
if (!this.canReceiveCoupon) {
return; //
}
// API
this.receiveCouponApi(this.couponData.id);
},
// API
receiveCouponApi(id) {
let data = {
stockId: id
}
receiveCoupon(data).then(res => {
console.log("receiveCoupon response:", res)
if (res.code == 200) {
//
if (this.$modal && this.$modal.showToast) {
this.$modal.showToast('优惠券领取成功')
} else {
uni.showToast({
title: '优惠券领取成功',
icon: 'success'
})
}
//
this.updateLocalCouponStatus();
//
this.$emit('coupon-received', this.couponData);
} else {
//
if (this.$modal && this.$modal.showToast) {
this.$modal.showToast('领取优惠券失败')
} else {
uni.showToast({
title: '领取优惠券失败',
icon: 'none'
})
}
}
}).catch(err => {
console.error('领取优惠券失败:', err)
//
if (this.$modal && this.$modal.showToast) {
this.$modal.showToast('领取优惠券失败')
} else {
uni.showToast({
title: '领取优惠券失败',
icon: 'none'
})
}
})
},
//
updateLocalCouponStatus() {
//
const updatedCoupon = { ...this.couponData };
// alreadyReceived0
if (!updatedCoupon.alreadyReceived) {
updatedCoupon.alreadyReceived = 0;
}
//
updatedCoupon.alreadyReceived += 1;
//
this.$emit('update-coupon', updatedCoupon);
},
//
showRulePopup() {
//
this.$emit('show-rule', this.couponData);
}
}
}
</script>
<style lang="scss" scoped>
.card {
display: flex;
align-items: center;
width: 100%;
padding: 10px 0;
background: #fff;
border-radius: 8px 8px 0 0;
}
.card-bottom {
display: flex;
background-color: #FFF1E0;
height: 50rpx;
align-items: center;
justify-content: space-between;
padding: 0 20rpx 0 20rpx;
border-radius: 0 0 8px 8px;
.card-bottom-text {
color: #AAAAAA;
font-size: 24rpx;
font-weight: 400;
}
}
.card-left {
width: 88px;
text-align: center;
color: #FF530A;
font-size: 28rpx;
font-weight: 900;
}
.card-center {
display: flex;
flex-direction: column;
.card-center-top {
width: 40rpx;
height: 20rpx;
border-radius: 0 0 20rpx 20rpx;
background-color: #F5F5F7;
line-height: 20rpx;
margin-top: -22rpx;
margin-bottom: 20rpx;
margin-left: -19rpx;
}
.card-center-bottom {
border-right: 1px dashed #AAAAAA;
width: 1px;
height: 120rpx;
}
}
.card-right {
padding: 0px 12px;
display: flex;
flex: 1;
justify-content: space-between;
align-items: center;
height: 60px;
.card-content {
width: 77%;
}
.card-icon {
position: relative;
right: -10px;
top: -10px;
}
}
.card-info {
margin: 0;
font-size: 28rpx;
line-height: 28rpx;
color: #333333;
font-weight: 500;
}
.card-type {
font-size: 24rpx;
font-weight: 400;
line-height: 24rpx;
font-weight: 400;
color: #AAAAAA;
margin-top: 10rpx;
.card-type-text {
color: #FFAA48;
font-size: 24rpx;
font-weight: 400;
line-height: 24rpx;
border: #FFAA48 1px solid;
border-radius: 7rpx;
margin-left: 8rpx;
}
}
.card-time {
font-size: 24rpx;
font-weight: 400;
line-height: 24rpx;
font-weight: 400;
color: #AAAAAA;
margin-top: 10rpx;
}
//
.coupon-btn {
width: 132rpx;
height: 52rpx;
background-color: #FFAA48;
display: flex;
align-items: center;
justify-content: center;
border-radius: 56rpx;
cursor: pointer;
transition: all 0.3s ease;
&.coupon-btn-disabled {
background-color: #CCCCCC;
cursor: not-allowed;
opacity: 0.6;
}
}
.coupon-btn-text {
font-size: 24rpx;
font-weight: 500;
color: #FFFFFF;
}
</style>

+ 198
- 0
components/CouponRulePopup/index.vue View File

@ -0,0 +1,198 @@
<template>
<uni-popup ref="popup" type="center">
<view class="rule-popup">
<view class="rule-popup-title">优惠券详细规则</view>
<view class="rule-popup-content">
<view class="rule-item">
<view class="rule-label">名称</view>
<view class="rule-value">{{couponData.stockName || ''}}</view>
</view>
<!-- <view class="rule-item" v-if="couponData.comment">
<view class="rule-label">备注</view>
<view class="rule-value">{{couponData.comment}}</view>
</view> -->
<view class="rule-item">
<view class="rule-label">类型</view>
<view class="rule-value">{{getStockTypeText(couponData)}}</view>
</view>
<view class="rule-item">
<view class="rule-label">优惠内容</view>
<view class="rule-value">{{getDiscountText(couponData)}}</view>
</view>
<view class="rule-item" v-if="couponData.transactionMinimum">
<view class="rule-label">消费门槛</view>
<view class="rule-value">{{couponData.transactionMinimum}}元可用</view>
</view>
<view class="rule-item" v-if="couponData.goodsName">
<view class="rule-label">适用范围</view>
<view class="rule-value">{{couponData.goodsName}}</view>
</view>
<view class="rule-item">
<view class="rule-label">有效期</view>
<view class="rule-value">{{getValidTimeText(couponData)}}</view>
</view>
<!-- <view class="rule-item" v-if="couponData.maxCouponsPerUser">
<view class="rule-label">领取限制</view>
<view class="rule-value">每人最多可领{{couponData.maxCouponsPerUser}}</view>
</view> -->
<view class="rule-item">
<view class="rule-label">特别说明</view>
<view class="rule-value">单笔订单仅限使用1张优惠券优惠券仅限用户本人使用不可赠送不可提现不得找零</view>
</view>
</view>
<view class="rule-popup-close" @click="close">关闭</view>
</view>
</uni-popup>
</template>
<script>
export default {
name: 'CouponRulePopup',
data() {
return {
couponData: {}
}
},
methods: {
//
open(couponData = {}) {
this.couponData = couponData;
this.$refs.popup.open();
},
//
close() {
this.$refs.popup.close();
},
//
getStockTypeText(coupon) {
if (!coupon || !coupon.stockType) return '';
const typeMap = {
'PNORMAL': '平台满减券',
'PDISCOUNT': '平台折扣券',
'PTRAIL': '平台体验券',
'NORMAL': '微信满减券',
'DISCOUNT': '微信折扣券',
'EXCHANGE': '微信换购券'
};
return typeMap[coupon.stockType] || coupon.stockType;
},
//
getDiscountText(coupon) {
if (!coupon || !coupon.stockType) return '';
if (coupon.stockType === 'PNORMAL') {
//
if (coupon.discountAmount && coupon.transactionMinimum) {
return `${coupon.transactionMinimum}元减${coupon.discountAmount}`;
} else if (coupon.discountAmount) {
return `${coupon.discountAmount}`;
}
return '满减优惠';
} else if (coupon.stockType === 'PDISCOUNT') {
//
if (coupon.discountPercent) {
return `${coupon.discountPercent / 10}折优惠`;
}
return '折扣优惠';
} else if (coupon.stockType === 'PTRAIL') {
//
return '免费体验';
} else if (coupon.stockType === 'NORMAL') {
//
if (coupon.discountAmount && coupon.transactionMinimum) {
return `${coupon.transactionMinimum}元减${coupon.discountAmount}`;
}
return '满减优惠';
} else if (coupon.stockType === 'DISCOUNT') {
//
if (coupon.discountPercent) {
return `${coupon.discountPercent}折优惠`;
}
return '折扣优惠';
} else if (coupon.stockType === 'EXCHANGE') {
//
return '换购优惠';
}
return '';
},
//
getValidTimeText(coupon) {
if (!coupon) return '';
const beginTime = coupon.availableBeginTime ? coupon.availableBeginTime.slice(0, 16).replace('T', ' ') : '';
const endTime = coupon.availableEndTime ? coupon.availableEndTime.slice(0, 16).replace('T', ' ') : '';
if (beginTime && endTime) {
return `${beginTime}${endTime}`;
} else if (endTime) {
return `截止至 ${endTime}`;
} else if (beginTime) {
return `${beginTime} 开始生效`;
}
//
if (coupon.availableDayAfterReceive) {
return `领取后${coupon.availableDayAfterReceive}天开始生效`;
}
return '长期有效';
}
}
}
</script>
<style lang="scss" scoped>
.rule-popup {
width: 600rpx;
background-color: #FFFFFF;
border-radius: 16rpx;
overflow: hidden;
}
.rule-popup-title {
height: 100rpx;
line-height: 100rpx;
text-align: center;
font-size: 32rpx;
font-weight: 600;
color: #FFFFFF;
background-color: #FFAA48;
}
.rule-popup-content {
padding: 30rpx;
}
.rule-item {
display: flex;
margin-bottom: 20rpx;
}
.rule-label {
width: 140rpx;
font-size: 28rpx;
color: #666666;
flex-shrink: 0;
}
.rule-value {
flex: 1;
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
}
.rule-popup-close {
height: 90rpx;
line-height: 90rpx;
text-align: center;
font-size: 30rpx;
color: #FFAA48;
border-top: 1px solid #EEEEEE;
}
</style>

BIN
doc/Group 1000001818.png View File

Before After
Width: 308  |  Height: 431  |  Size: 59 KiB

BIN
doc/Group 1000001818@2x.png View File

Before After
Width: 615  |  Height: 861  |  Size: 139 KiB

BIN
doc/Group 1000001818@3x.png View File

Before After
Width: 923  |  Height: 1292  |  Size: 292 KiB

BIN
doc/Rectangle 9031.png View File

Before After
Width: 375  |  Height: 632  |  Size: 88 KiB

BIN
doc/Rectangle 9031@2x.png View File

Before After
Width: 750  |  Height: 1264  |  Size: 612 KiB

BIN
doc/Rectangle 9031@3x.png View File

Before After
Width: 1125  |  Height: 1896  |  Size: 1.2 MiB

static/logo.png → doc/logo.png View File


static/logo200.png → doc/logo200.png View File


+ 112
- 0
docs/品种选择功能说明.md View File

@ -0,0 +1,112 @@
# 品种选择功能说明
## 功能概述
根据设计图要求,新建了一个专门的品种选择页面,替换了原有的弹窗式品种选择功能。
## 主要特性
### 1. 页面布局
- **系统导航栏**:橙色背景,显示"选择品种"标题
- **搜索栏**:支持实时搜索品种名称
- **品种列表**:按字母分组显示,支持滚动
- **字母索引**:右侧固定位置,支持快速跳转到指定字母
### 2. 功能实现
#### 品种数据获取
- 根据宠物类型(猫/狗)获取对应的品种数据
- 支持从API动态获取品种列表
#### 搜索功能
- 实时搜索,支持中文和英文搜索
- 搜索结果实时更新显示
#### 字母分组
- 自动按品种名称首字母分组
- 支持中文字符的拼音首字母映射
- 按字母顺序排列显示
- 每个字母组内的品种按中文拼音排序
#### 快速导航
- 右侧字母索引支持点击跳转
- 滚动动画效果
- 当前字母高亮显示
- 滚动时自动更新激活的字母索引
### 3. 页面路由
**新增页面路径**:`/pages/personalCenter/breedSelect`
**页面配置**:
```json
{
"path": "pages/personalCenter/breedSelect",
"style": {
"navigationBarTitleText": "选择品种",
"navigationBarBackgroundColor": "#FF6B35",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
}
```
### 4. 使用方式
在需要选择品种的地方,调用以下代码跳转到品种选择页面:
```javascript
uni.navigateTo({
url: `/pages/personalCenter/breedSelect?petType=${petType}&selectedBreed=${encodeURIComponent(selectedBreed || '')}`
});
```
### 5. 数据传递
- **输入参数**
- `petType`:宠物类型('cat' 或 'dog')
- `selectedBreed`:当前已选择的品种(可选)
- **输出结果**
- 选择品种后自动返回上一页
- 自动更新上一页的品种数据
### 6. 样式特点
- 使用系统导航栏,符合设计图要求
- 简洁的白色背景设计
- 流畅的动画效果
- 响应式布局
- 字母索引激活状态有橙色背景高亮
## 技术实现
### 核心组件
- `breedSelect.vue`:品种选择页面主组件
- 修改了 `petBaseInfo.vue`:移除原有弹窗,改为页面跳转
### 主要方法
- `getPetBreeds()`:获取品种数据
- `groupBreedsByLetter()`:按字母分组
- `onSearchChange()`:搜索处理
- `selectBreed()`:选择品种
- `scrollToLetter()`:字母跳转
- `onScroll()`:滚动事件处理
- `updateCurrentLetter()`:更新当前激活字母
### 数据映射
- 中文字符到拼音首字母的映射表
- 支持常见的中文品种名称
## 兼容性
- 完全兼容原有的品种选择逻辑
- 保持数据格式不变
- 支持现有API接口
## 注意事项
1. 确保API接口 `getDictList` 正常工作
2. 品种数据需要包含 `dictLabel` 字段
3. 页面需要正确配置在 `pages.json`
4. 建议在真机上测试滚动和点击效果

+ 71
- 0
docs/性格选择功能说明.md View File

@ -0,0 +1,71 @@
# 性格选择功能说明
## 功能概述
新增了独立的性格选择页面,用户可以在该页面中:
1. 输入宠物的性格描述
2. 选择预设的性格标签
3. 保存后回显到宠物信息页面
## 文件结构
### 新增文件
- `pages/personalCenter/personalitySelect.vue` - 性格选择页面
### 修改文件
- `pages/personalCenter/components/petBaseInfo.vue` - 修改性格选择逻辑,跳转到新页面
- `pages.json` - 添加新页面路由配置
## 功能特点
### 1. 性格描述输入
- 提供文本输入框,用户可以自由描述宠物性格
- 支持最多200字符输入
- 灰色背景,符合设计规范
### 2. 快捷选择
- 提供8个预设性格标签
- 支持多选功能
- 选中状态为橙色背景,白色文字
- 2行4列网格布局
### 3. 数据回显
- 从API获取性格选项列表
- 支持解析已有的性格数据
- 保存后自动更新父页面数据
### 4. 页面导航
- 从宠物信息页面点击"性格"字段跳转
- 保存后自动返回上一页
- 显示保存成功提示
## 使用流程
1. 在宠物信息页面点击"性格"字段
2. 跳转到性格选择页面
3. 输入性格描述(可选)
4. 选择性格标签(可多选)
5. 点击"保存"按钮
6. 自动返回宠物信息页面,数据已更新
## 技术实现
### 数据传递
- 使用URL参数传递现有性格数据
- 使用页面栈更新父页面数据
### 样式设计
- 响应式网格布局
- 符合设计图的颜色方案
- 流畅的交互动画
### API集成
- 从`pet_personality`字典获取性格选项
- 支持动态加载和解析
## 注意事项
1. 确保至少选择一项性格特征才能保存
2. 性格描述和快捷选择可以组合使用
3. 数据格式为:描述 + "," + 快捷选择标签
4. 支持编辑现有性格数据

+ 79
- 39
main.js View File

@ -23,47 +23,87 @@ Vue.mixin(share)
// 创建一个空的Vue实例作为事件中心
Vue.prototype.$eventHub = new Vue()
// 创建全局变量
Vue.prototype.$globalData = {
isAgree:false,
submitData: {
"phone": "",
"wechatId": "",
"note": "",
"totalPrice": "",
"address": {
"province": "",
"city": "",
"district": "",
"detailAddress": ""
},
"skuList": [],
"service": {
"serviceFrequency": "once_a_day",
"serviceDate": [],
"serviceTimeFirst": "",
"serviceTimeSecond": "",
"pet": []
}
},
mainSku:[],
augmentedSku:[],
itemPrices:[],
servicePrices:[],
openIdStr:'',
confirmData: {
phone: "",
wechatId: "",
note: "",
},
newOrderData:{
currentAddress:{},
currentPets:[],
totalPrice:0,
needPreFamiliarize:[]
}
Vue.prototype.initGlobalData = function() {
Vue.prototype.$globalData = {
isAgree:false,
submitData: {
"phone": "",
"wechatId": "",
"note": "",
"totalPrice": "",
"address": {
"province": "",
"city": "",
"district": "",
"detailAddress": ""
}
},
mainSku:[],
augmentedSku:[],
itemPrices:[],
servicePrices:[],
openIdStr:'',
confirmData: {
phone: "",
wechatId: "",
note: "",
},
newOrderData:{
orderId : '',//需要取消的订单id
currentAddress:{},
currentPets:[],
totalPrice:0,
needPreFamiliarize:[],
distancePrice : 0, // 距离加价
}
}
}
Vue.prototype.initGlobalData()
// 创建全局变量
// Vue.prototype.$globalData = {
// isAgree:false,
// submitData: {
// "phone": "",
// "wechatId": "",
// "note": "",
// "totalPrice": "",
// "address": {
// "province": "",
// "city": "",
// "district": "",
// "detailAddress": ""
// },
// "skuList": [],
// "service": {
// "serviceFrequency": "once_a_day",
// "serviceDate": [],
// "serviceTimeFirst": "",
// "serviceTimeSecond": "",
// "pet": []
// }
// },
// mainSku:[],
// augmentedSku:[],
// itemPrices:[],
// servicePrices:[],
// openIdStr:'',
// confirmData: {
// phone: "",
// wechatId: "",
// note: "",
// },
// newOrderData:{
// orderId : '',//需要取消的订单id
// currentAddress:{},
// currentPets:[],
// totalPrice:0,
// needPreFamiliarize:[]
// }
// }
const app = new Vue({
...App
})


+ 1
- 1
mixins/configList.js View File

@ -13,7 +13,7 @@ export default {
}
},
computed: {
...mapState(['configList', 'userInfo', 'buyInfo']),
...mapState(['configList', 'userInfo', 'buyInfo', 'price_config']),
// currentPagePath() {
// const pages = getCurrentPages();
// const currentPage = pages[pages.length - 1];


+ 9
- 3
mixins/position.js View File

@ -38,16 +38,22 @@ export default {
return minDistance
},
calculateDistanceAddressList(teacherAddress){
calculateDistanceAddressList(teacherAddress, position){
if(!position){
position = this.position
}
if (!teacherAddress ||
teacherAddress.length == 0 ||
!this.position ||
!this.position.latitude ||
!this.position.longitude) {
return 0
return [{
distance : 0
}]
}
teacherAddress.forEach((item, index) => {
item.distance = calculateDistance(
this.position.latitude,


+ 57
- 30
pages.json View File

@ -183,6 +183,24 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/personalCenter/personalitySelect",
"style": {
"navigationBarTitleText": "性格选择",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/personalCenter/breedSelect",
"style": {
"navigationBarTitleText": "选择品种",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/personalCenter/address",
"style": {
@ -282,33 +300,6 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/companionPetList/companionPetList",
"style": {
"navigationBarTitleText": "周边伴宠师",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/companionPetList/companionPetInfo",
"style": {
"navigationBarTitleText": "伴宠师主页",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/companionPetList/couponList",
"style": {
"navigationBarTitleText": "领券中心",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/personalCenter/userInfo",
"style": {
@ -326,9 +317,36 @@
}
},
"subPackages": [
{
"root": "pages_order",
"pages": [
{
"root": "pages_order",
"pages": [
{
"path": "companionPetList/companionPetList",
"style": {
"navigationBarTitleText": "周边伴宠师",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "companionPetList/companionPetInfo",
"style": {
"navigationBarTitleText": "伴宠师主页",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "companionPetList/couponList",
"style": {
"navigationBarTitleText": "领券中心",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "task/taskList",
"style": {
@ -427,6 +445,15 @@
"enablePullDownRefresh": true,
"navigationBarTextStyle": "white"
}
},
{
"path": "auth/login",
"style": {
"navigationBarTitleText": "授权登录",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
}
]
}


+ 0
- 323
pages/companionPetList/couponList.vue View File

@ -1,323 +0,0 @@
<template>
<view class="coupon-list">
<view v-for="(item,index) in couponData" style="padding:20rpx;" :key="index">
<view>
<view class="card">
<view class="card-left">
<view class="">
{{switchType(item.stockType)}}
</view>
</view>
<view class="card-center">
<view class="card-center-top"></view>
<view class="card-center-bottom"></view>
</view>
<view class="card-right">
<view class="card-content">
<view class="card-info">{{item.stockName}}</view>
<view class="card-type">可用于
<text class="card-type-text">专业喂养</text>
<text class="card-type-text">专业遛狗</text>
<!-- <text class="card-type-text">{{ item.goodsName }}</text> -->
</view>
<view class="card-time">有效期至: {{item.availableEndTime.slice(0, 16)}}</view>
</view>
<!-- <view style="width: 22%;">
<u-button @click="receiveCoupon(item.id)" shape="circle" size="mini" color="#ffaa48" text="立即领取"></u-button>
</view> -->
<view style="width: 132rpx;height: 52rpx;background-color: #FFAA48; display: flex;align-items: center;justify-content: center;border-radius: 56rpx;">
<text @click="receiveCoupon(item.id)" style="font-size: 24rpx; font-weight: 500; color: #FFFFFF;">立即领取</text>
</view>
</view>
</view>
<view class="card-bottom">
<view class="card-bottom-text">
优惠券不可兑换现金
</view>
<view class="card-bottom-text" @click="showRulePopup(item)">
查看详细规则>
</view>
</view>
</view>
</view>
<!-- 优惠券详细规则弹窗 -->
<uni-popup ref="rulePopup" type="center">
<view class="rule-popup">
<view class="rule-popup-title">优惠券详细规则</view>
<view class="rule-popup-content">
<view class="rule-item">
<view class="rule-label">名称</view>
<view class="rule-value">{{currentCoupon.stockName}}</view>
</view>
<view class="rule-item">
<view class="rule-label">折扣</view>
<view class="rule-value">{{getDiscountText(currentCoupon)}}</view>
</view>
<view class="rule-item">
<view class="rule-label">使用规则</view>
<view class="rule-value">可用于专业喂养和专业遛狗服务</view>
</view>
<view class="rule-item">
<view class="rule-label">有效日期</view>
<view class="rule-value">{{currentCoupon.availableEndTime ? currentCoupon.availableEndTime.slice(0, 16) : ''}}</view>
</view>
<view class="rule-item">
<view class="rule-label">特别说明</view>
<view class="rule-value">单笔订单仅限使用1张优惠券优惠券仅限用户本人使用不可赠送不可提现不得找零</view>
</view>
</view>
<view class="rule-popup-close" @click="closeRulePopup">关闭</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
getCouponList,
receiveCoupon,
} from "@/api/system/user"
export default {
data() {
return {
couponData: [],
currentCoupon: {}, //
}
},
onLoad() {
// this.openIdStr = this.$globalData.openIdStr;
this.getCouponListAuth()
},
methods: {
getCouponListAuth() {
getCouponList().then(res => {
if (res.code == 200) {
this.couponData = res.data
console.log("this.couponData", this.couponData)
} else {
this.$modal.showToast('获取优惠券失败')
}
})
},
switchType(type) {
if (type == 'PNORMAL') {
return '满减券'
}
if (type == 'PDISCOUNT') {
return '折扣券'
}
if (type == 'PTRAIL') {
return '体验券'
}
return '优惠券'
},
receiveCoupon (id) {
let data = {
stockId: id
}
receiveCoupon(data).then(res => {
console.log("this.receiveCoupon", res)
if (res.code == 200) {
this.$modal.showToast('优惠券领取成功')
} else {
this.$modal.showToast('领取优惠券失败')
}
})
},
//
showRulePopup(item) {
this.currentCoupon = item || {};
this.$refs.rulePopup.open();
},
//
closeRulePopup() {
this.$refs.rulePopup.close();
},
//
getDiscountText(coupon) {
if (!coupon || !coupon.stockType) return '';
if (coupon.stockType === 'PNORMAL') {
return '满100可减10元';
} else if (coupon.stockType === 'PDISCOUNT') {
return '打8折';
} else if (coupon.stockType === 'PTRAIL') {
return '免费体验一次';
}
return '';
}
}
}
</script>
<style lang="scss">
.coupon-list {
/* 优惠券规则弹窗样式 */
.rule-popup {
width: 600rpx;
background-color: #FFFFFF;
border-radius: 16rpx;
overflow: hidden;
}
.rule-popup-title {
height: 100rpx;
line-height: 100rpx;
text-align: center;
font-size: 32rpx;
font-weight: 600;
color: #FFFFFF;
background-color: #FFAA48;
}
.rule-popup-content {
padding: 30rpx;
}
.rule-item {
display: flex;
margin-bottom: 20rpx;
}
.rule-label {
width: 140rpx;
font-size: 28rpx;
color: #666666;
flex-shrink: 0;
}
.rule-value {
flex: 1;
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
}
.rule-popup-close {
height: 90rpx;
line-height: 90rpx;
text-align: center;
font-size: 30rpx;
color: #FFAA48;
border-top: 1px solid #EEEEEE;
}
.card {
display: flex;
align-items: center;
width: 100%;
padding: 10px 0;
background: #fff;
border-radius: 8px 8px 0 0;
}
.card-bottom {
display: flex;
background-color: #FFF1E0;
height: 50rpx;
align-items: center;
justify-content: space-between;
padding: 0 20rpx 0 20rpx;
border-radius: 0 0 8px 8px;
.card-bottom-text {
color: #AAAAAA;
font-size: 24rpx;
font-weight: 400;
}
}
.card-left {
width: 88px;
text-align: center;
color: #FF530A;
font-size: 28rpx;
font-weight: 900;
}
.card-center {
display: flex;
flex-direction: column;
// align-items: center;
.card-center-top {
width: 40rpx;
height: 20rpx;
border-radius: 0 0 20rpx 20rpx;
background-color: #F5F5F7;
line-height: 20rpx;
// border-bottom: 1px solid #FDA714;
// border-left: 1px solid #FDA714;
// border-right: 1px solid #FDA714;
margin-top: -22rpx;
margin-bottom: 20rpx;
margin-left: -19rpx;
}
.card-center-bottom {
border-right: 1px dashed #AAAAAA;
width: 1px;
height: 120rpx;
}
}
.card-right {
padding: 0px 12px;
display: flex;
flex: 1;
/* flex-direction: column; */
justify-content: space-between;
align-items: center;
height: 60px;
.card-content {
width: 77%;
}
.card-icon {
position: relative;
right: -10px;
top: -10px;
}
}
.card-info {
margin: 0;
font-size: 28rpx;
line-height: 28rpx;
color: #333333;
font-weight: 500;
}
.card-type {
font-size: 24rpx;
font-weight: 400;
line-height: 24rpx;
font-weight: 400;
color: #AAAAAA;
margin-top: 10rpx;
.card-type-text {
color: #FFAA48;
font-size: 24rpx;
font-weight: 400;
line-height: 24rpx;
border: #FFAA48 1px solid;
border-radius: 7rpx;
margin-left: 8rpx;
}
}
.card-time {
font-size: 24rpx;
font-weight: 400;
line-height: 24rpx;
font-weight: 400;
color: #AAAAAA;
margin-top: 10rpx;
}
}
</style>

+ 1
- 1
pages/components/NewUserCoupon.vue View File

@ -33,7 +33,7 @@ export default {
if (!getToken()) {
//
uni.navigateTo({
url: '/pages/login/index'
url: '/pages_order/auth/login'
})
} else {
//


+ 69
- 251
pages/index.vue View File

@ -1,7 +1,7 @@
<template>
<view class="home-content">
<NewUserCoupon v-if="isNewUser" @close="closeNewUserPopup" @getCoupon="handleGetCoupon"></NewUserCoupon>
<NewUserCoupon @close="closeNewUserPopup" @getCoupon="handleGetCoupon"></NewUserCoupon>
<view class="banner">
<uni-swiper-dot class="uni-swiper-dot-box" field="content">
@ -14,7 +14,8 @@
</uni-swiper-dot>
</view>
<view style="margin: -250px 20rpx 0 20rpx;">
<view style="margin: -250px 20rpx 0 20rpx;"
@click="toProductDetail">
<image style="width: 710rpx; height: 176rpx;" slot='cover'
src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/petServiceOrder/TopDetail.png" />
</view>
@ -97,40 +98,14 @@
</view>
<view class="split-line"></view>
<view class="coupon-content">
<view v-for="(item,index) in couponData" style="padding:20rpx;" :key="index">
<view style="border: 1px solid #FFBF60; border-radius: 8px;">
<view class="card">
<view class="card-left">{{switchType(item.stockType)}}</view>
<view class="card-center">
<view class="card-center-top"></view>
<view class="card-center-bottom"></view>
</view>
<view class="card-right">
<view class="card-content">
<view class="card-info">{{item.stockName}}</view>
<view class="card-type">可用于
<text class="card-type-text">专业喂养</text>
<text class="card-type-text">专业遛狗</text>
</view>
<view class="card-time">有效期至: {{item && item.availableEndTime ? item.availableEndTime.slice(0, 16) : ''}}</view>
</view>
<view>
<view style="width: 132rpx;height: 52rpx;background-color: #FFAA48; display: flex;align-items: center;justify-content: center;border-radius: 56rpx;">
<text @click="receiveCoupon(item.id)" style="font-size: 24rpx; font-weight: 500; color: #FFFFFF;">立即领取</text>
</view>
</view>
</view>
</view>
<view class="card-bottom">
<view class="card-bottom-text">
优惠券不可兑换现金
</view>
<view class="card-bottom-text" @click="showRulePopup(item)">
查看详细规则>
</view>
</view>
</view>
</view>
<CouponItem
v-for="(item,index) in couponData"
:key="index"
:couponData="item"
@show-rule="showRulePopup"
@coupon-received="handleCouponReceived"
@update-coupon="updateCouponData"
/>
</view>
</uni-card>
</view>
@ -212,11 +187,11 @@
<view class="history-record-flag"></view>
<view>历史记录</view>
</view>
<view class="history-record-title-right">
<!-- <view class="history-record-title-right">
<view @click="getAllRecord">
查看服务记录和评价详情 <uni-icons type="right" size="14px" color="#AAAAAA"></uni-icons>
</view>
</view>
</view> -->
</view>
<view class="split-line"></view>
<view class="history-record-content">
@ -241,7 +216,7 @@
</view>
</view>
<view class="history-record-content-text">
<text>*数据来源于布丁猫妈狗爸平台</text>
<text>*数据来源于猫妈狗爸平台</text>
</view>
</view>
</uni-card>
@ -275,11 +250,12 @@
<view>
<image style="height: 132rpx; width: 710rpx; margin: 0 20rpx 20rpx 20rpx;"
slot='cover'
@click="toPetFront"
src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/petServiceOrder/joinus.png" />
</view>
<Kefu></Kefu>
<view class="coupon_wrap" v-if="envVersion == 'dev'">
<!-- <view class="coupon_wrap" v-if="envVersion.includes('dev')">
<view class="details-subscribe">
<view @click="getCoupon" class="details-btn">点击领取到卡包</view>
</view>
@ -304,37 +280,12 @@
</view>
</view>
</view>
</view>
</view> -->
<!-- 优惠券详细规则弹窗 -->
<uni-popup ref="rulePopup" type="center">
<view class="rule-popup">
<view class="rule-popup-title">优惠券详细规则</view>
<view class="rule-popup-content">
<view class="rule-item">
<view class="rule-label">名称</view>
<view class="rule-value">{{currentCoupon && currentCoupon.stockName || ''}}</view>
</view>
<view class="rule-item">
<view class="rule-label">折扣</view>
<view class="rule-value">{{getDiscountText(currentCoupon)}}</view>
</view>
<view class="rule-item">
<view class="rule-label">使用规则</view>
<view class="rule-value">可用于专业喂养和专业遛狗服务</view>
</view>
<view class="rule-item">
<view class="rule-label">有效日期</view>
<view class="rule-value">{{currentCoupon && currentCoupon.availableEndTime ? currentCoupon.availableEndTime.slice(0, 16) : ''}}</view>
</view>
<view class="rule-item">
<view class="rule-label">特别说明</view>
<view class="rule-value">单笔订单仅限使用1张优惠券优惠券仅限用户本人使用不可赠送不可提现不得找零</view>
</view>
</view>
<view class="rule-popup-close" @click="closeRulePopup">关闭</view>
</view>
</uni-popup>
<CouponRulePopup ref="rulePopup" :coupon="currentCoupon" />
</view>
</template>
@ -347,7 +298,6 @@
getCouponList,
getCouponListNoAuth,
getOpenId,
receiveCoupon,
} from "@/api/system/user"
import {
setToken,
@ -362,8 +312,10 @@
import Kefu from './common/kefu.vue'
import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue';
import NewUserCoupon from './components/NewUserCoupon.vue';
import CouponItem from '@/components/CouponItem/index.vue';
import CouponRulePopup from '@/components/CouponRulePopup/index.vue';
import positionMixin from '@/mixins/position.js';
import {getPhoneNumber} from "@/api/system/user.js"
export default {
mixins: [positionMixin],
data() {
@ -425,12 +377,15 @@
},
currentCoupon: null,
companionList: [],
isMember: false,
}
},
components: {
Kefu,
uniPopup,
NewUserCoupon
NewUserCoupon,
CouponItem,
CouponRulePopup
},
mounted() {
this.getCalendarDate();
@ -449,7 +404,7 @@
},
handleGetCoupon() {
if (getToken()) {
this.getCoupon();
} else {
uni.navigateTo({
url: '/pages/personalCenter/index'
@ -458,7 +413,7 @@
},
getAllCoupon() {
uni.navigateTo({
url: '/pages/companionPetList/couponList'
url: '/pages_order/companionPetList/couponList'
});
},
getAllCompanion() {
@ -470,11 +425,16 @@
})
} else {
uni.navigateTo({
url: `/pages/companionPetList/companionPetList?info=` + encodeURIComponent(JSON
url: `/pages_order/companionPetList/companionPetList?info=` + encodeURIComponent(JSON
.stringify(this.allInfo))
});
}
},
toProductDetail(){
uni.switchTab({
url: 'details/feed'
})
},
getAllRecord () {
},
@ -651,6 +611,21 @@
console.log(response);
})
},
//
toPetFront(){
uni.navigateToMiniProgram({
appId: 'wx01f0f43759922fda',
// path: '',
envVersion: "release",
success: res => {
//
console.log("打开成功", res);
},
fail: err => {
console.log(err);
}
})
},
calculateTeacherListDistance(){
this.companionList.forEach(item => {
item.distanceText = this.calculateDistanceAddress(item.appletAddresseList)
@ -676,12 +651,13 @@
})
uni.navigateTo({
url: `/pages/companionPetList/companionPetList?info=`
url: `/pages_order/companionPetList/companionPetList?info=`
+ encodeURIComponent(JSON
.stringify(this.allInfo))
});
}
} else {
this.initGlobalData()
this.$store.commit('setPosition', {})
this.buyInfo.teacher = null
uni.navigateTo({
@ -689,18 +665,6 @@
});
}
},
getCoupon() {
// getCouponList().then(res => {
// if (res.code == 200) {
// this.couponList = res.rows
// this.showMask = true
// } else {
// this.$modal.showToast('')
// }
// })
},
getCouponListAuth() {
getCouponList().then(res => {
if (res.code == 200) {
@ -711,31 +675,6 @@
}
})
},
switchType(type) {
if (type == 'PNORMAL') {
return '满减券'
}
if (type == 'PDISCOUNT') {
return '折扣券'
}
if (type == 'PTRAIL') {
return '体验券'
}
return '优惠券'
},
receiveCoupon(id) {
let data = {
stockId: id
}
receiveCoupon(data).then(res => {
console.log("this.receiveCoupon", res)
if (res.code == 200) {
this.$modal.showToast('优惠券领取成功')
} else {
this.$modal.showToast('领取优惠券失败')
}
})
},
getCouponListNoAuth() {
// console.log(' getCouponListNoAuth');
// getCouponListNoAuth().then(res => {
@ -888,23 +827,25 @@
return;
}
this.currentCoupon = coupon;
this.$refs.rulePopup.open();
this.$refs.rulePopup.show();
},
closeRulePopup() {
this.$refs.rulePopup.close();
//
handleCouponReceived(couponData) {
console.log('首页优惠券领取成功:', couponData);
//
},
getDiscountText(coupon) {
if (!coupon || !coupon.stockType) return '';
if (coupon.stockType === 'PNORMAL') {
return '满100可减10元';
} else if (coupon.stockType === 'PDISCOUNT') {
return '打8折';
} else if (coupon.stockType === 'PTRAIL') {
return '免费体验一次';
//
updateCouponData(updatedCoupon) {
//
const couponIndex = this.couponData.findIndex(item => item.id === updatedCoupon.id);
if (couponIndex !== -1) {
// 使Vue.set$set
this.$set(this.couponData, couponIndex, updatedCoupon);
}
return '';
},
}
},
@ -1048,82 +989,7 @@
}
.coupon-content {
.card {
display: flex;
align-items: center;
width: 100%;
padding: 10px 0;
border-radius: 8px 8px 0 0;
}
.card-bottom {
display: flex;
background-color: #FFF1E0;
height: 50rpx;
align-items: center;
justify-content: space-between;
padding: 0 20rpx 0 20rpx;
border-radius: 0 0 8px 8px;
.card-bottom-text {
color: #AAAAAA;
font-size: 24rpx;
font-weight: 400;
}
}
.card-left {
width: 88px;
text-align: center;
color: #FF530A;
font-size: 28rpx;
font-weight: 900;
}
.card-center {
display: flex;
flex-direction: column;
.card-center-top {
width: 40rpx;
height: 20rpx;
border-radius: 0 0 20rpx 20rpx;
background-color: #fff;
line-height: 20rpx;
border-bottom: 1px solid #FDA714;
border-left: 1px solid #FDA714;
border-right: 1px solid #FDA714;
margin-top: -22rpx;
margin-bottom: 20rpx;
margin-left: -19rpx;
}
.card-center-bottom {
border-right: 1px dashed #AAAAAA;
width: 1px;
height: 120rpx;
}
}
.card-right {
padding: 0 16rpx 0 0;
display: flex;
flex: 1;
justify-content: space-between;
align-items: center;
height: 60px;
font-size: 24rpx;
.card-content {
width: 77%;
}
.card-icon {
position: relative;
right: -10px;
top: -10px;
}
}
// CouponItem
}
}
@ -1692,53 +1558,5 @@
}
}
/* 优惠券规则弹窗样式 */
.rule-popup {
width: 600rpx;
background-color: #FFFFFF;
border-radius: 16rpx;
overflow: hidden;
}
.rule-popup-title {
height: 100rpx;
line-height: 100rpx;
text-align: center;
font-size: 32rpx;
font-weight: 600;
color: #FFFFFF;
background-color: #FFAA48;
}
.rule-popup-content {
padding: 30rpx;
}
.rule-item {
display: flex;
margin-bottom: 20rpx;
}
.rule-label {
width: 140rpx;
font-size: 28rpx;
color: #666666;
flex-shrink: 0;
}
.rule-value {
flex: 1;
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
}
.rule-popup-close {
height: 90rpx;
line-height: 90rpx;
text-align: center;
font-size: 30rpx;
color: #FFAA48;
border-top: 1px solid #EEEEEE;
}
</style>

+ 210
- 84
pages/newOrder/confirmOrder.vue View File

@ -106,7 +106,7 @@
</view>
</view>
<view style="display: flex; align-items: center;">
<view style="color: #333333;margin-right: 10rpx;">¥{{ item.totalCost }}.00</view>
<view style="color: #333333;margin-right: 10rpx;">¥{{ item.totalCost }}</view>
<view>
<u-icon @click="toggleExpand( item.date )"
:name="expandedIndexs.includes( item.date ) ? 'arrow-up' : 'arrow-down'"
@ -126,7 +126,7 @@
<view v-if="item2.itemName != item.name"
class="price-details-item-price-total-item">- {{ item2.itemName }}
</view>
<view class="price-details-item-price-total-item">¥{{ item2.price }} ×
<view class="price-details-item-price-total-item">¥{{ parseFloat(item2.price) }} ×
{{ item2.quantity }} {{ item2.unit }}</view>
</view>
</view>
@ -145,9 +145,9 @@
<!-- 定制服务 -->
<view v-for="(customItem, customIndex) in pet.customServices" :key="customIndex">
<view v-if="customItem.quantity" class="service-item">
<view>- {{ customItem.name }}</view>
<view>¥{{ customItem.price }} × {{ customItem.quantity }} </view>
</view>
<view>- {{ customItem.name }}</view>
<view>¥{{ parseFloat(customItem.price).toFixed(2) }} × {{ customItem.quantity }} </view>
</view>
</view>
</view>
</view>
@ -156,12 +156,16 @@
<view class="service-new-address">
<view v-if="needPreFamiliarize.length>0" class="total-cost">
<view>提前熟悉 </view>
<view>¥40</view>
<view>¥{{ price_config.preFamiliarize.price }}</view>
</view>
<view class="total-cost">
<view>费用总计 </view>
<view>¥{{ originalTotalPrice }}</view>
<view v-if="companionLevelPrice()>0" class="total-cost">
<view> {{ buyInfo.teacher ? buyInfo.teacher.userName : companionLevelTitle }} </view>
<view>¥{{ companionLevelPrice() }}</view>
</view>
<view class="total-cost">
<view>费用总计 </view>
<view>¥{{ parseFloat(originalTotalPrice).toFixed(2) }}</view>
</view>
<view class="total-cost" @click="selectCoupon">
<view>平台优惠</view>
<view style="color: #FF530A;">
@ -169,18 +173,18 @@
<uni-icons type="right" size="28rpx" color="#AAA"></uni-icons>
</view>
</view>
<view class="total-cost">
<view class="total-cost" v-if="discountMemberText">
<view>会员折扣</view>
<view style="display: flex; align-items: center;">
<view style="color: #999999;">{{ currentMember.itemType }}</view>
<view style="border: solid #7D8196 1px; margin: 0 10rpx; height: 12px;"> </view>
<view style="color: #FF530A;">-¥{{ memberDiscount }}</view>
</view>
<view style="color: #999999;">{{ currentMember.itemType }}{{ discountMemberText }}折优惠</view>
<view style="border: solid #7D8196 1px; margin: 0 10rpx; height: 12px;"> </view>
<view style="color: #FF530A;">-¥{{ parseFloat(memberDiscount).toFixed(2) }}</view>
</view>
<view class="total-cost">
<view>应付费用</view>
<view style="font-weight: 500;font-size: 32rpx;">¥{{ finalPrice }}</view>
</view>
<view class="total-cost">
<view>应付费用</view>
<view style="font-weight: 500;font-size: 32rpx;">¥{{ parseFloat(finalPrice).toFixed(2) }}</view>
</view>
</view>
</view>
@ -211,7 +215,7 @@
<view class="service-new-details-desc">
<view style="display: flex;">
<text style="width: 20rpx;">*</text>
<text style="flex: 1;">价格40/</text>
<text style="flex: 1;">价格{{ price_config.preFamiliarize.price }}/</text>
</view>
<view style="display: flex; margin: 20rpx 0;">
<text style="width: 20rpx;">*</text>
@ -238,7 +242,7 @@
<view style="display: flex;justify-content: space-between; align-items: center;">
<view style="height: 80rpx; display: flex; align-items: center;">
<text style="color: #333333;">订单总价: </text>
<text style="color: #FF530A; font-size: 40rpx;">¥{{ finalPrice }} <text
<text style="color: #FF530A; font-size: 40rpx;">¥{{ parseFloat(finalPrice).toFixed(2) }} <text
style="font-size: 32rpx;"></text></text>
</view>
<view style="display: flex;">
@ -255,7 +259,7 @@
<view class="price-details">
<view class="price-details-header">
<text
style="text-align: center; width: 100%; font-size: 32rpx; font-weight: 500; color: #333333;">优惠券 (已自动选择最优惠)</text>
style="text-align: center; width: 100%; font-size: 32rpx; font-weight: 500; color: #333333;">优惠券</text>
<u-icon name="close" @click="togglePriceDetails"></u-icon>
</view>
<view class="split-line"></view>
@ -327,20 +331,16 @@
defaultPhoto: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/catdog.png',
currentMember: {},
memberDiscountList: [{
itemType: "新晋家长9.5折优惠",
discount: 0.05
itemType: "新晋家长",
},
{
itemType: "普卡会员9折优惠",
discount: 0.1
itemType: "普卡会员",
},
{
itemType: "银卡会员8.8折优惠",
discount: 0.12
itemType: "银卡会员",
},
{
itemType: "金卡会员8.5折优惠",
discount: 0.15
itemType: "金卡会员",
}
],
couponList: [],
@ -350,7 +350,13 @@
couponId: null,
basePrice: 0,
baseProduct: '',
//
holidayPrice: 75,
normalPrice: 75,
holidayDate: [],
//
discountMemberText : '',
companionLevelTitle : '',
}
},
onLoad() {
@ -371,13 +377,46 @@
url: '/pages/index'
});
}
this.initPriceConfig()
this.getShowPets()
this.groupPetsByDate()
this.getCouponList()
// this.totalPrice = this.$globalData.newOrderData.totalPrice
this.companionLevelTitle = this.$globalData.newOrderData.companionLevel.paramValue
},
methods: {
companionLevelPrice(){
let companionLevel = this.$globalData.newOrderData.companionLevel
let price = Number(companionLevel.paramValueText) * this.isAddPrice()
return price * this.$store.state.memberRate
},
//
isAddPrice(){
let currentAddress = this.$globalData.newOrderData.currentAddress || {}
let defaultPrice = 1
try{
defaultPrice = this.price_config.cityConfig.priceRates.default
}catch(e){
defaultPrice = 1
}
if(!this.price_config.cityConfig || !currentAddress.province || !currentAddress.city){
return defaultPrice
}
let addressList = this.price_config.cityConfig.priceRates || []
for(let key in addressList){
if((currentAddress.province + currentAddress.city).includes(key)){
return addressList[key]
}
}
return defaultPrice
},
getDateText(date){
return dayjs(date).format('MM-DD')
},
@ -427,8 +466,8 @@
for (const date in dailyPets) {
const pets = dailyPets[date];
const priceDetails = []
//
const baseServiceCost = this.basePrice
// -
const baseServiceCost = Number(this.isHoliday(date) ? this.holidayPrice : this.normalPrice)
const largeDogCount = pets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes('大型')).length;
let mediumDogCount = pets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes('中型')).length;
@ -442,29 +481,35 @@
let totalPetCost = pets.reduce((acc, pet) => {
return acc + this.calculatePetCost(pet);
}, 0);
// >30
if (totalPetCost > 30) {
// >=3,3
if (catCount >= 3) {
additionalCost = totalPetCost - 30
catCount = catCount - 3
} else if (smallDogCount >= 2) { // >=2,2
additionalCost = totalPetCost - 30
smallDogCount = smallDogCount - 2
} else if (mediumDogCount >= 1) { // >=1,1
additionalCost = totalPetCost - 30
mediumDogCount = mediumDogCount - 1
} else {
additionalCost = totalPetCost - 25
catCount = catCount - 1
smallDogCount = smallDogCount - 1
}
//
// >30
const freeQuota = this.price_config.freeQuota
if (totalPetCost > Number(freeQuota.threshold)) {
freeQuota.rules.forEach(rule => {
if (rule.type === 'cat' && catCount >= rule.count) {
additionalCost = totalPetCost - Number(rule.freeAmount)
catCount = catCount - rule.count
}else if(rule.type === 'smallDog' && smallDogCount >= rule.count){
additionalCost = totalPetCost - Number(rule.freeAmount)
smallDogCount = smallDogCount - rule.count
}else if(rule.type === 'mediumDog' && mediumDogCount >= rule.count){
additionalCost = totalPetCost - Number(rule.freeAmount)
mediumDogCount = mediumDogCount - rule.count
}else{
additionalCost = totalPetCost - Number(rule.freeAmount)
}
})
if (mediumDogCount > 0) {
additionalCostItem.push({
itemName: '中型犬',
price: 30,
price: this.calculatePetCost({
petType: 'dog',
bodyType: '中型',
}),
quantity: mediumDogCount,
unit: '只'
})
@ -472,7 +517,10 @@
if (smallDogCount > 0) {
additionalCostItem.push({
itemName: '小型犬',
price: 15,
price: this.calculatePetCost({
petType: 'dog',
bodyType: '小型',
}),
quantity: smallDogCount,
unit: '只'
})
@ -480,7 +528,9 @@
if (catCount > 0) {
additionalCostItem.push({
itemName: '猫猫',
price: 10,
price: this.calculatePetCost({
petType: 'cat',
}),
quantity: catCount,
unit: '只'
})
@ -489,10 +539,16 @@
}
// 40/
if (largeDogCount > 0) {
additionalCost += (40 * largeDogCount)
additionalCost += (this.calculatePetCost({
petType: 'dog',
bodyType: '大型',
}) * largeDogCount)
additionalCostItem.push({
itemName: '大型犬',
price: 40,
price: this.calculatePetCost({
petType: 'dog',
bodyType: '大型',
}),
quantity: largeDogCount,
unit: '只'
})
@ -501,14 +557,14 @@
let multServicesTotalCost = 0
const maxFeedCount = Math.max(...pets.map(pet => pet.feedCount));
if (maxFeedCount === 2) {
multServicesTotalCost += 45; // 12
multServicesTotalCost += this.price_config.multiService.two.price; // 12
} else if (maxFeedCount === 3) {
multServicesTotalCost += 130; // 13
multServicesTotalCost += this.price_config.multiService.three.price; // 13
}
priceDetails.push({
name: '专业喂养',
item: [{
itemName: '专业喂养',
itemName: this.isHoliday(date) ? '节假日' : '非节假日',
price: baseServiceCost,
quantity: 1,
unit: '天'
@ -518,13 +574,13 @@
name: '上门次数',
item: [{
itemName: '1天2次',
price: 45,
price: this.price_config.multiService.two.price,
quantity: maxFeedCount === 2 ? 1 : 0,
unit: '天'
},
{
itemName: '1天3次',
price: 130,
price: this.price_config.multiService.three.price,
quantity: maxFeedCount === 3 ? 1 : 0,
unit: '天'
},
@ -540,7 +596,10 @@
//
const customServiceCost = pets.reduce((acc, pet) => acc + this.calculatePetCustomServiceCost(pet), 0)
const totalCost = baseServiceCost + additionalCost + multServicesTotalCost + customServiceCost
console.log(baseServiceCost + additionalCost + multServicesTotalCost + customServiceCost);
console.log(baseServiceCost , additionalCost , multServicesTotalCost , customServiceCost);
const totalCost = parseFloat((baseServiceCost + additionalCost + multServicesTotalCost + Number(customServiceCost))).toFixed(2)
//
const acc = []
pets.map(pet => {
@ -565,21 +624,24 @@
this.getShowTotalPrice()
},
calculatePetCost(pet) {
//
let petCost = 0;
if (pet.petType === 'cat') {
petCost += 10; //
} else if (pet.petType === 'dog' && pet.bodyType.includes('小型')) {
petCost += 15; //
} else if (pet.petType === 'dog' && pet.bodyType.includes('中型')) {
petCost += 30; //
}
return petCost;
},
//
let petExtra = this.price_config.petExtra
let petCost = 0;
if (pet.petType === 'cat') {
petCost += Number(petExtra.cat); //
} else if (pet.petType === 'dog' && pet.bodyType.includes('小型')) {
petCost += Number(petExtra.smallDog); //
} else if (pet.petType === 'dog' && pet.bodyType.includes('中型')) {
petCost += Number(petExtra.mediumDog); //
}
return petCost;
},
//
calculatePetCustomServiceCost(pet) {
const customServiceCost = pet.customServices.reduce((acc, item) => acc + item.price * item.quantity, 0)
return customServiceCost
console.log('pet.customServices',pet.customServices)
const customServiceCost = pet.customServices.reduce((acc, item) => acc + Number(item.price) * item.quantity, 0)
console.log('customServiceCost',customServiceCost)
return parseFloat(customServiceCost).toFixed(2)
},
//
toggleExpand(index) {
@ -607,6 +669,27 @@
url: '/pages/details/agreement'
});
},
//
initPriceConfig() {
let priceConfig = this.$store.state.price_config
console.log('价格配置:', priceConfig)
if(priceConfig.basePrice && priceConfig.basePrice.holiday){
this.holidayPrice = Number(priceConfig.basePrice.holiday * this.$store.state.memberRate).toFixed(2)
}
if(priceConfig.basePrice && priceConfig.basePrice.normal){
this.normalPrice = Number(priceConfig.basePrice.normal * this.$store.state.memberRate).toFixed(2)
}
if(priceConfig.holidays && priceConfig.holidays.length > 0){
this.holidayDate = priceConfig.holidays
}else{
this.holidayDate = []
}
this.discountMemberText = (this.$store.state.memberRate * 10)
},
//
isHoliday(date) {
return this.holidayDate.includes(date)
},
//
throttle(func, delay) {
let lastCall = 0;
@ -719,46 +802,81 @@
const order = {
openId: getOpenIdKey(),
addressId: this.currentAddress.id,
totalPrice: this.finalPrice,
totalPrice: this.finalPrice,//
needPreFamiliarize: this.needPreFamiliarize.length > 0,
couponId: this.couponId,
petOrderServices: this.getPetOrderServices(this.currentPetsByDay),
//
//dailyShowData : JSON.stringify(this.dailyShowData),
//
couponDiscount: this.discount,
//
memberDiscount: this.memberDiscount,
//
oldPrice : this.originalTotalPrice,
//
preFamiliarizePrice: this.price_config.preFamiliarize.price,
//
companionLevelTitle : this.companionLevelTitle,
//
companionLevelPrice: this.companionLevelPrice(),
}
if(this.buyInfo.teacher){
order.teacherId = this.buyInfo.teacher.userId
}else{
}
if(this.$globalData.newOrderData.companionLevel){
//
console.log(this.$globalData.newOrderData.companionLevel);
order.companionLevel = ['', 'junior', 'senior'].indexOf(this.$globalData.newOrderData.companionLevel)
order.companionLevel = this.$globalData.newOrderData.companionLevel.paramValueNum
// order.companionLevel = ['', 'junior', 'senior'].indexOf(this.$globalData.newOrderData.companionLevel)
}
if(this.$globalData.newOrderData.orderId){
order.orderId = this.$globalData.newOrderData.orderId
}
console.log(order)
return order
},
getSkuList(customServices, feedCount) {
getSkuList(customServices, feedCount, price) {
console.log('customServices', JSON.parse(JSON.stringify(customServices)));
const skuList = customServices.filter(service => service.quantity > 0).map(service2 => {
return {
skuId: service2.skuId,
quantity: service2.quantity,
isMainProduct: service2.isMainProduct
isMainProduct: service2.isMainProduct,
price: service2.price
}
})
skuList.push({
skuId: this.$globalData.mainSku[0].skuId,
quantity: feedCount,
isMainProduct: true
isMainProduct: true,
price
})
return skuList
},
getPetOrderServices(currentPetsByDay) {
console.log('currentPetsByDay', JSON.parse(JSON.stringify(currentPetsByDay)));
const petOrderServices = currentPetsByDay.map(pet => {
let price = this.isHoliday(pet.serviceDate) ? this.holidayPrice : this.normalPrice
//
let feedCountPrice = 0;
if (pet.feedCount == 2) {
feedCountPrice += this.price_config.multiService.two.price; // 12
} else if (pet.feedCount == 3) {
feedCountPrice += this.price_config.multiService.three.price; // 13
}
return {
petId: pet.petId,
serviceDate: pet.serviceDate,
feedCount: pet.feedCount,
selectedTimeSlots: pet.selectedTimeSlots.join(','),
skuList: this.getSkuList(pet.customServices, pet.feedCount)
skuList: this.getSkuList(pet.customServices, pet.feedCount, price),
feedCountPrice,//
}
})
return petOrderServices
@ -802,19 +920,27 @@
changePreFamiliarize(name) {
if (name && name.length > 0) {
this.needPreFamiliarize = name
this.originalTotalPrice = this.originalTotalPrice + 40
this.originalTotalPrice = this.originalTotalPrice + this.price_config.preFamiliarize.price
} else {
this.needPreFamiliarize = []
this.originalTotalPrice = this.originalTotalPrice - 40
this.originalTotalPrice = this.originalTotalPrice - this.price_config.preFamiliarize.price
}
//
this.autoSelectBestCoupon()
this.getShowTotalPrice()
},
getShowTotalPrice() {
//
this.memberDiscount = ((this.originalTotalPrice - this.discount) * this.currentMember.discount).toFixed(2)
this.finalPrice = (this.originalTotalPrice - this.memberDiscount - this.discount).toFixed(2)
// originalTotalPrice
// originalTotalPrice:
// memberRate: 0.99
// originalTotalPrice / memberRate
// - originalTotalPrice
const originalPriceBeforeMemberDiscount = this.originalTotalPrice / this.$store.state.memberRate
this.memberDiscount = (originalPriceBeforeMemberDiscount - this.originalTotalPrice).toFixed(2)
//
// finalPrice: = -
this.finalPrice = (this.originalTotalPrice - this.discount).toFixed(2)
},
getCouponAmountOrDiscount(item) {
if (item.stockType == "PDISCOUNT") {


+ 24
- 0
pages/newOrder/petList.vue View File

@ -97,6 +97,7 @@
</view>
</view>
<view class="">
<u-picker :showToolbar='false' :show="show" :columns="petTypes" @change="petTypeChange" @cancel="cancel"
@confirm="confirmPetType"></u-picker>
@ -105,10 +106,16 @@
:asyncClose="true" :content='delContent'>
</u-modal>
<view v-if="showCalendar" class="calendar-popup">
<view class="calendar-mask"></view>
<view class="calendar-content">
<uni-calendar class="uni-calendar--hook"
:disabledDay="outDateList"
:holidayDate="holidayDate"
:holidayPrice="holidayPrice"
:normalPrice="normalPrice"
:selected="selectedDate" :startDate="startDate"
:endDate="endDate" @change="change" :showMonth="false" />
<u-button color="#FFBF60" type="primary" @click="confirmCanlendar">确定</u-button>
@ -145,11 +152,28 @@ export default {
currentPets: [],
//
outDateList : ['2025-6-20'],
holidayDate: [],
holidayPrice: 75,
normalPrice : 75,
}
},
onShow() {
this.getPetList();
this.getOutDateList()
let priceConfig = this.$store.state.price_config
console.log(priceConfig)
if(priceConfig.basePrice && priceConfig.basePrice.holiday){
this.holidayPrice = (priceConfig.basePrice.holiday * this.$store.state.memberRate).toFixed(2)
}
if(priceConfig.basePrice && priceConfig.basePrice.normal){
this.normalPrice = (priceConfig.basePrice.normal * this.$store.state.memberRate).toFixed(2)
}
if(priceConfig.holidays && priceConfig.holidays.length > 0){
this.holidayDate = priceConfig.holidays
}else{
this.holidayDate = []
}
this.$store.commit('getUserInfo')
},
onPullDownRefresh() {
this.getPetList();


+ 109
- 0
pages/newOrder/price_config.json View File

@ -0,0 +1,109 @@
{
"basePrice": {
"normal": 75,
"holiday": 85,
"weekend": 80,
"perKm": 3
},
"memberDiscount": {
"new": 0.95,
"regular": 0.9,
"silver": 0.88,
"gold": 0.85
},
"preFamiliarize": {
"price": 38,
"holidayRate": 1.2
},
"multiService": {
"two": {
"price": 46,
"holidayRate": 1.1
},
"three": {
"price": 131,
"holidayRate": 1.1
}
},
"petExtra": {
"largeDog": {
"price": 40,
"holidayRate": 1.1
},
"mediumDog": {
"price": 30,
"holidayRate": 1.1
},
"smallDog": {
"price": 15,
"holidayRate": 1.1
},
"cat": {
"price": 10,
"holidayRate": 1.1
}
},
"freeQuota": {
"threshold": 30,
"rules": [
{
"type": "cat",
"count": 3,
"freeAmount": 30,
"description": "3只及以上猫免费30元"
},
{
"type": "smallDog",
"count": 2,
"freeAmount": 30,
"description": "2只及以上小型犬免费30元"
},
{
"type": "mediumDog",
"count": 1,
"freeAmount": 30,
"description": "1只及以上中型犬免费30元"
},
{
"type": "mixed",
"count": 0,
"freeAmount": 29,
"description": "以上都不满足则免费25元"
}
]
},
"holidays": [
"2025-10-01",
"2025-10-02",
"2025-10-03",
"2025-10-04",
"2025-10-05",
"2025-10-06",
"2025-10-07",
"2025-07-15",
"2025-07-22",
"2025-07-16",
"2025-07-23",
"2025-07-17",
"2025-07-11",
"2025-07-18",
"2025-07-25",
"2025-07-24",
"2025-09-11"
],
"weekends": [
6,
0
],
"customServices": {
"priceConfig": {},
"holidayRate": 1.1
},
"cityConfig": {
"currentCity": "shenzhen",
"priceRates": {
"default": 1,
"长沙": 1.1
}
}
}

+ 69
- 30
pages/newOrder/serviceNew.vue View File

@ -4,7 +4,8 @@
<view class="order-type-select" v-if="!buyInfo.teacher">
<view class="order-type-title">
<image style="width: 40rpx; height: 40rpx;margin-right: 10rpx;" slot='cover' src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/petServiceOrder/OrderIcon.png">
<image style="width: 40rpx; height: 40rpx;margin-right: 10rpx;" slot='cover'
src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/petServiceOrder/OrderIcon.png">
</image>
<!-- <image style="width: 32rpx; height: 32rpx; margin-right: 10rpx;" src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/petServiceOrder/SystemOrderCircle.png"></image> -->
@ -12,17 +13,15 @@
</view>
<view class="order-type-options">
<view class="order-type-option" :class="{active: companionLevel === 'junior'}" @click="selectCompanionLevel('junior')">
<image style="width: 40rpx; height: 40rpx; margin-right: 10rpx;" src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/catdog.png"></image>
初级伴宠师
<view class="info-icon" @click.stop="showCompanionInfo('junior')">
<uni-icons type="info" size="20" color="#FFB13F"></uni-icons>
</view>
</view>
<view class="order-type-option" :class="{active: companionLevel === 'senior'}" @click="selectCompanionLevel('senior')">
<image style="width: 40rpx; height: 40rpx; margin-right: 10rpx;" src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/catdog.png"></image>
高级伴宠师
<view class="info-icon" @click.stop="showCompanionInfo('senior')">
<view class="order-type-option"
v-for="item in teacherLevelList"
:key="item.id"
:class="{active: item.id == companionLevel.id}"
@click="selectCompanionLevel(item)">
<image style="width: 40rpx; height: 40rpx; margin-right: 10rpx;"
:src="item.paramValueImage || 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/catdog.png'"></image>
{{ item.paramValue }}
<view class="info-icon" @click.stop="showCompanionInfo(item)">
<uni-icons type="info" size="20" color="#FFB13F"></uni-icons>
</view>
</view>
@ -50,20 +49,23 @@
<uni-popup ref="companionInfoPopup" type="center">
<view class="companion-info-popup">
<view class="companion-info-title">
<text>{{ popupTitle }}</text>
<text>{{ showLevelInfo.paramValue }}</text>
</view>
<view class="companion-info-content">
<view class="companion-info-item">
<text class="companion-info-label">等级:</text>
<text class="companion-info-value">{{ popupLevel }}</text>
<text class="companion-info-value">{{ showLevelInfo.paramValue }}</text>
</view>
<view class="companion-info-item">
<text class="companion-info-label">价格:</text>
<text class="companion-info-value">要根据规城市不同展示不同的价格</text>
<text class="companion-info-value">{{ companionLevelPrice() }}</text>
</view>
<view class="companion-info-item">
<text class="companion-info-label">分类标准:</text>
<text class="companion-info-value">文字描述初级是什么类型的高级是什么类型的 :)</text>
<text class="companion-info-value"
v-html="showLevelInfo.paramValueArea"
>
</text>
</view>
</view>
<view class="companion-info-close" @click="closeCompanionInfo">
@ -197,7 +199,7 @@
<view class="service-new-details-desc">
<view style="display: flex;">
<text style="width: 20rpx;">*</text>
<text style="flex: 1;">价格40/</text>
<text style="flex: 1;">价格{{ price_config.preFamiliarize.price }}/</text>
</view>
<view style="display: flex; margin: 20rpx 0;">
<text style="width: 20rpx;">*</text>
@ -224,6 +226,7 @@
getOpenId
} from "@/api/system/user"
import { setToken,getToken,getOpenIdKey,setOpenIdKey } from '@/utils/auth'
import { mapState } from 'vuex'
export default {
data()
{
@ -234,12 +237,13 @@
currentPets:[],
needPreFamiliarize:[],
defaultPhoto:'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/catdog.png',
companionLevel: 'junior', //
companionLevelList: ['', 'junior', 'senior'], //
popupTitle: '',
popupLevel: '',
companionLevel: {}, //
showLevelInfo : {},
}
},
computed: {
...mapState(['teacherLevelList'])
},
onLoad: function(option) {
},
onShow() {
@ -259,14 +263,51 @@
this.isPetSelected=true
}
this.needPreFamiliarize = this.$globalData.newOrderData.needPreFamiliarize
//
if(this.$globalData.newOrderData.companionLevel) {
this.companionLevel = this.$globalData.newOrderData.companionLevel
}else{
this.companionLevel = this.teacherLevelList[0]
this.$globalData.newOrderData.companionLevel = this.companionLevel
}
this.$store.commit('getUserInfo')
},
methods:{
companionLevelPrice(){
let price = Number(this.showLevelInfo.paramValueText) * this.isAddPrice()
return price * this.$store.state.memberRate
},
//
isAddPrice(){
let currentAddress = this.$globalData.newOrderData.currentAddress || {}
let defaultPrice = 1
try{
defaultPrice = this.price_config.cityConfig.priceRates.default
}catch(e){
defaultPrice = 1
}
if(!this.price_config.cityConfig || !currentAddress.province || !currentAddress.city){
return defaultPrice
}
let addressList = this.price_config.cityConfig.priceRates || []
for(let key in addressList){
if((currentAddress.province + currentAddress.city).includes(key)){
return addressList[key]
}
}
return defaultPrice
},
selectAddress(){
uni.navigateTo({
url: "/pages/newOrder/addressList"
@ -283,13 +324,7 @@
this.$globalData.newOrderData.companionLevel = level;
},
showCompanionInfo(level) {
if(level === 'junior') {
this.popupTitle = '初级伴宠师';
this.popupLevel = '初级伴宠师';
} else {
this.popupTitle = '高级伴宠师';
this.popupLevel = '高级伴宠师';
}
this.showLevelInfo = level
this.$refs.companionInfoPopup.open();
},
closeCompanionInfo() {
@ -304,9 +339,11 @@
if (response && response.content && response.content.length > 0) {
const skus = response && response.content[0].skus
if(skus && skus.length>0){
let price = this.price_config.basePrice.normal || skus[0].price
let productSku = {
"skuId": skus[0].id,
"price":skus[0].price,
// "price":(price * this.$store.state.memberRate).toFixed(2),
"price":(skus[0].price).toFixed(2),
"name":response.content[0].name,
"quantity": 1,
"isMainProduct":true
@ -345,7 +382,7 @@
})
},
goNext(){
console.log('this.needPreFamiliarize', this.needPreFamiliarize)
console.log('this.$globalData', this.$globalData)
if(!this.isAddressSelected) {
this.$modal.showToast('请选择服务地址');
return;
@ -398,6 +435,8 @@
display: flex;
justify-content: space-between;
margin-top: 20rpx;
flex-wrap: wrap;
gap: 20rpx;
.order-type-option {
display: flex;


+ 264
- 95
pages/newOrder/serviceNew2.vue View File

@ -153,7 +153,7 @@
<view class="service-row-content">
<text class="service-name" style="color: #A94F20;">额外服务费</text>
<view class="service-price" style="color:#A94F20">
¥{{ feedCount > 2 ? 130 : 45 }}
¥{{ feedCount > 2 ? price_config.multiService.three.price : price_config.multiService.two.price }}
</view>
</view>
</view>
@ -200,12 +200,12 @@
<view style="height: 80rpx;">
<view>
<text style="color: #333333;">订单总价 </text>
<text style="color: #FF530A; font-size: 40rpx;">¥{{ totalPrice }} <text
style="font-size: 32rpx;">.00</text></text>
<text style="color: #FF530A; font-size: 40rpx;">¥{{ Number(totalPrice).toFixed(2) }} <text
style="font-size: 32rpx;"></text></text>
</view>
<view style="display: flex; justify-content: flex-end;" @click="togglePriceDetails">
<text v-if="currentDayPrice" style="color:#FF530A; font-size: 22rpx;"> 当日价格¥{{ currentDayPrice
}}.00 |</text>
<text v-if="currentDayPrice" style="color:#FF530A; font-size: 22rpx;"> 当日价格¥{{ Number(currentDayPrice).toFixed(2)
}}|</text>
<text style="color:#FF530A; font-size: 22rpx;margin-left: 10rpx;">明细</text>
<u-icon name="arrow-up" color="#FF530A" size="10"></u-icon>
</view>
@ -256,7 +256,7 @@
<view class="split-line"></view>
<view class="price-details-item" style="margin: 20rpx 0;">
<view style="font-size: 32rpx; color: #333333; font-weight: 500;">合计</view>
<view style="font-size: 32rpx; color: #333333; font-weight: 500;">¥{{ totalPrice }}</view>
<view style="font-size: 32rpx; color: #333333; font-weight: 500;">¥{{ Number(totalPrice).toFixed(2) }}</view>
</view>
<view class="split-line"></view>
</view>
@ -267,12 +267,12 @@
<view style="height: 80rpx;">
<view>
<text style="color: #333333;">订单总价 </text>
<text style="color: #FF530A; font-size: 40rpx;">¥{{ totalPrice }} <text
style="font-size: 32rpx;">.00</text></text>
<text style="color: #FF530A; font-size: 40rpx;">¥{{ Number(totalPrice).toFixed(2) }} <text
style="font-size: 32rpx;"></text></text>
</view>
<view style="display: flex; justify-content: flex-end;" @click="togglePriceDetails">
<text v-if="currentDayPrice" style="color:#FF530A; font-size: 22rpx;"> 当日价格¥{{
currentDayPrice }}.00 |</text>
Number(currentDayPrice).toFixed(2) }}|</text>
<text style="color:#FF530A; font-size: 22rpx;margin-left: 10rpx;">明细</text>
<u-icon name="arrow-up" color="#FF530A" size="10"></u-icon>
</view>
@ -295,11 +295,18 @@ import {
getOpenId
} from "@/api/system/user"
import { setToken, setOpenIdKey } from '@/utils/auth'
import positionMixin from '../../mixins/position';
export default {
mixins: [positionMixin],
data() {
return {
basePrice: 75,
baseProduct: '专业喂养',
//
holidayPrice: 75,
normalPrice: 75,
distancePrice: 1,
holidayDate: [],
currentPets: [],
currentPetId: '',
currentMonthDay: {},
@ -335,14 +342,14 @@ export default {
// customServices
customServicesStr: '',
customServices: [
{ name: '陪玩(20分钟)', price: 20, quantity: 0 },
{ name: '活动区吸毛', price: 20, quantity: 0 },
{ name: '毛发梳理', price: 15, quantity: 0 },
{ name: '身体清洁(如眼、耳、鼻等)', price: 5, quantity: 0 },
{ name: '指甲修剪', price: 10, quantity: 0 },
{ name: '会员深度清洁', price: 20, quantity: 0 },
{ name: '喂药', price: 15, quantity: 0 },
{ name: '上药', price: 25, quantity: 0 }
// { name: '(20)', price: 20, quantity: 0 },
// { name: '', price: 20, quantity: 0 },
// { name: '', price: 15, quantity: 0 },
// { name: '()', price: 5, quantity: 0 },
// { name: '', price: 10, quantity: 0 },
// { name: '', price: 20, quantity: 0 },
// { name: '', price: 15, quantity: 0 },
// { name: '', price: 25, quantity: 0 }
],
showPriceDetails: false, //
priceDetails: [],
@ -374,8 +381,10 @@ export default {
},
onLoad: function (option) {
this.$globalData.newOrderData.distancePrice = this.calculateDistancePrice()
},
mounted() {
console.log('this.$globalData', this.$globalData)
this.currentPets = this.$globalData.newOrderData.currentPets;
if (this.currentPets.length < 1) {
uni.reLaunch({
@ -388,6 +397,8 @@ export default {
this.baseProduct = this.$globalData.mainSku[0].name
}
//
this.initPriceConfig()
this.getProductList()
},
watch: {
@ -401,6 +412,51 @@ export default {
}
},
methods: {
companionLevelPrice(){
let companionLevel = this.$globalData.newOrderData.companionLevel
let price = Number(companionLevel.paramValueText) * this.isAddPrice()
return price * this.$store.state.memberRate
},
//
isAddPrice(){
let currentAddress = this.$globalData.newOrderData.currentAddress || {}
let defaultPrice = 1
try{
defaultPrice = this.price_config.cityConfig.priceRates.default
}catch(e){
defaultPrice = 1
}
if(!this.price_config.cityConfig || !currentAddress.province || !currentAddress.city){
return defaultPrice
}
let addressList = this.price_config.cityConfig.priceRates || []
for(let key in addressList){
if((currentAddress.province + currentAddress.city).includes(key)){
return addressList[key]
}
}
return defaultPrice
},
//
calculateDistancePrice() {
if(!this.buyInfo.teacher || !this.buyInfo.teacher.appletAddresseList){
return
}
let addressList = this.buyInfo.teacher.appletAddresseList
let d = this.calculateDistanceAddressList(addressList, {
latitude: this.$globalData.newOrderData.latitude,
longitude: this.$globalData.newOrderData.longitude,
})[0]?.distance || 0
return d * this.distancePrice
},
initNewOrderData() {
const needPreFamiliarize = this.$globalData.newOrderData.needPreFamiliarize.length > 0
const pets = []
@ -434,7 +490,7 @@ export default {
needPreFamiliarize: needPreFamiliarize, // 40/
pets: pets
}
//
//
this.selectPet(this.currentPets[0] || {})
this.calculateTotalPrice()
},
@ -613,31 +669,39 @@ export default {
calculateTotalPrice() {
// 1.
if (this.$globalData.newOrderData.needPreFamiliarize && this.$globalData.newOrderData.needPreFamiliarize.length > 0) {
this.needPreFamiliarizeCost = 40
this.needPreFamiliarizeCost = this.price_config.preFamiliarize.price
} else {
this.needPreFamiliarizeCost = 0
}
// 2.
// 2.1
// 2.1 -
const uniqueDates = new Set(this.newOrderData.pets.map(item => item.serviceDate)); //
this.baseServiceTotalCost = uniqueDates.size * this.basePrice; // 75
let baseServiceTotalCost = 0;
uniqueDates.forEach(date => {
const isHolidayDate = this.isHoliday(date);
const dayPrice = isHolidayDate ? parseFloat(this.holidayPrice) : parseFloat(this.normalPrice);
baseServiceTotalCost += dayPrice;
});
this.baseServiceTotalCost = baseServiceTotalCost;
// 2.2
// petTypedog bodyType '' ,40/
// petTypedog bodyType '' ,30/
// petTypedog bodyType '' ,15/
// petTypecat ,10/
this.newOrderData.pets.forEach(pet => {
if (pet.petType === 'dog' && pet.bodyType.includes('大型')) {
pet.additionalCost = 40
} else if (pet.petType === 'dog' && pet.bodyType.includes('中型')) {
pet.additionalCost = 30
} else if (pet.petType === 'dog' && pet.bodyType.includes('小型')) {
pet.additionalCost = 15
} else if (pet.petType === 'cat') {
pet.additionalCost = 10
}
pet.additionalCost = this.calculatePetCost(pet)
// if (pet.petType === 'dog' && pet.bodyType.includes('')) {
// pet.additionalCost = 40
// } else if (pet.petType === 'dog' && pet.bodyType.includes('')) {
// pet.additionalCost = 30
// } else if (pet.petType === 'dog' && pet.bodyType.includes('')) {
// pet.additionalCost = 15
// } else if (pet.petType === 'cat') {
// pet.additionalCost = 10
// }
})
// const largeDogArray = this.newOrderData.pets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes(''));
@ -670,23 +734,39 @@ export default {
let totalPetCost = pets.reduce((acc, pet) => {
return acc + this.calculatePetCost(pet);
}, 0);
// >30
if (totalPetCost > 30) {
// >=3
if (catCount >= 3) {
additionalCost = totalPetCost - 30
catCount = catCount - 3
} else if (smallDogCount >= 2) {
additionalCost = totalPetCost - 30
smallDogCount = smallDogCount - 2
} else if (mediumDogCount >= 1) {
additionalCost = totalPetCost - 30
mediumDogCount = mediumDogCount - 1
} else {
additionalCost = totalPetCost - 25
catCount = catCount - 1
smallDogCount = smallDogCount - 1
}
// >30
const freeQuota = this.price_config.freeQuota
if (totalPetCost > Number(freeQuota.threshold)) {
freeQuota.rules.forEach(rule => {
if (rule.type === 'cat' && catCount >= rule.count) {
additionalCost = totalPetCost - Number(rule.freeAmount)
catCount = catCount - rule.count
}else if(rule.type === 'smallDog' && smallDogCount >= rule.count){
additionalCost = totalPetCost - Number(rule.freeAmount)
smallDogCount = smallDogCount - rule.count
}else if(rule.type === 'mediumDog' && mediumDogCount >= rule.count){
additionalCost = totalPetCost - Number(rule.freeAmount)
mediumDogCount = mediumDogCount - rule.count
}else{
additionalCost = totalPetCost - Number(rule.freeAmount)
}
})
// if (totalPetCost > 30) {
// // >=3
// if (catCount >= 3) {
// additionalCost = totalPetCost - 30
// catCount = catCount - 3
// } else if (smallDogCount >= 2) {
// additionalCost = totalPetCost - 30
// smallDogCount = smallDogCount - 2
// } else if (mediumDogCount >= 1) {
// additionalCost = totalPetCost - 30
// mediumDogCount = mediumDogCount - 1
// } else {
// additionalCost = totalPetCost - 25
// catCount = catCount - 1
// smallDogCount = smallDogCount - 1
// }
petTypeCounts.push({
date,
largeDogCount,
@ -717,10 +797,10 @@ export default {
for (const date in dailyPets) {
const maxFeedCount = Math.max(...dailyPets[date].map(pet => pet.feedCount));
if (maxFeedCount === 2) {
multServicesTotalCost += 45; // 12
multServicesTotalCost += this.price_config.multiService.two.price; // 12
oneDayTwoTimesDates.push(date)
} else if (maxFeedCount === 3) {
multServicesTotalCost += 130; // 13
multServicesTotalCost += this.price_config.multiService.three.price; // 13
oneDayThreeTimesDates.push(date)
}
}
@ -745,24 +825,26 @@ export default {
this.customServiceItemCount = customServiceItemCount
this.customServicesTotalCost = customServicesTotalCost
this.totalPrice = this.needPreFamiliarizeCost
+ this.baseServiceTotalCost
+ this.additionalTotalCost
+ this.multServicesTotalCost
+ this.customServicesTotalCost; //
this.totalPrice = Number(this.needPreFamiliarizeCost)
+ Number(this.baseServiceTotalCost)
+ Number(this.additionalTotalCost)
+ Number(this.multServicesTotalCost)
+ Number(this.companionLevelPrice())
+ Number(this.customServicesTotalCost); //
this.getCurrentDayPrice(this.currentMonthDay.fullDate)
},
calculatePetCost(pet) {
//
let petExtra = this.price_config.petExtra
let petCost = 0;
if (pet.petType === 'cat') {
petCost += 10; //
petCost += Number(petExtra.cat); //
} else if (pet.petType === 'dog' && pet.bodyType.includes('小型')) {
petCost += 15; //
petCost += Number(petExtra.smallDog); //
} else if (pet.petType === 'dog' && pet.bodyType.includes('中型')) {
petCost += 30; //
petCost += Number(petExtra.mediumDog); //
}
return petCost;
},
@ -797,32 +879,73 @@ export default {
name: '提前熟悉',
item: [{
itemName: '提前熟悉',
price: 40,
price: this.price_config.preFamiliarize.price,
quantity: this.newOrderData.needPreFamiliarize ? 1 : 0,
unit: '次'
},]
})
}
const uniqueDateCount = new Set(this.newOrderData.pets.map(item => item.serviceDate)).size; //
if (uniqueDateCount > 1) {
let companionLevel = this.$globalData.newOrderData.companionLevel
if (companionLevel) {
priceDetails.push({
name: this.baseProduct, item: [
{ itemName: this.baseProduct, price: this.basePrice, quantity: uniqueDateCount, unit: '天' },
]
name: '伴宠师等级',
item: [{
itemName: companionLevel.paramValue,
price: this.companionLevelPrice(),
quantity: 1,
unit: '位'
},]
})
}
//
const uniqueDates = new Set(this.newOrderData.pets.map(item => item.serviceDate));
let holidayCount = 0;
let normalCount = 0;
uniqueDates.forEach(date => {
if (this.isHoliday(date)) {
holidayCount++;
} else {
normalCount++;
}
});
const baseServiceItems = [];
if (normalCount > 0) {
baseServiceItems.push({
itemName: '非节假日 | ',
price: parseFloat(this.normalPrice),
quantity: normalCount,
unit: '天'
});
}
if (holidayCount > 0) {
baseServiceItems.push({
itemName: '节假日 | ',
price: parseFloat(this.holidayPrice),
quantity: holidayCount,
unit: '天'
});
}
if (baseServiceItems.length > 0) {
priceDetails.push({
name: this.baseProduct,
item: baseServiceItems
});
}
if (this.oneDayTwoTimesDates.length > 0 || this.oneDayThreeTimesDates.length > 0) {
priceDetails.push({
name: '上门次数',
item: [{
itemName: '1天2次',
price: 45,
price: this.price_config.multiService.two.price,
quantity: this.oneDayTwoTimesDates.length,
unit: '天'
},
{
itemName: '1天3次',
price: 130,
price: this.price_config.multiService.three.price,
quantity: this.oneDayThreeTimesDates.length,
unit: '天'
},
@ -838,7 +961,10 @@ export default {
if (item.largeDogCount > 0) {
additionalCostItem.push({
itemName: '大型犬',
price: 40,
price: this.calculatePetCost({
petType: 'dog',
bodyType: '大型',
}),
quantity: item.largeDogCount,
unit: '只',
date: item.date
@ -847,7 +973,10 @@ export default {
if (item.mediumDogCount > 0) {
additionalCostItem.push({
itemName: '中型犬',
price: 30,
price: this.calculatePetCost({
petType: 'dog',
bodyType: '中型',
}),
quantity: item.mediumDogCount,
unit: '只',
date: item.date
@ -856,7 +985,10 @@ export default {
if (item.smallDogCount > 0) {
additionalCostItem.push({
itemName: '小型犬',
price: 15,
price: this.calculatePetCost({
petType: 'dog',
bodyType: '小型',
}),
quantity: item.smallDogCount,
unit: '只',
date: item.date
@ -865,7 +997,9 @@ export default {
if (item.catCount > 0) {
additionalCostItem.push({
itemName: '猫猫',
price: 10,
price: this.calculatePetCost({
petType: 'cat',
}),
quantity: item.catCount,
unit: '只',
date: item.date
@ -914,6 +1048,26 @@ export default {
}
},
//
initPriceConfig() {
let priceConfig = this.$store.state.price_config
console.log('价格配置:', priceConfig)
if(priceConfig.basePrice && priceConfig.basePrice.holiday){
this.holidayPrice = (priceConfig.basePrice.holiday * this.$store.state.memberRate).toFixed(2)
}
if(priceConfig.basePrice && priceConfig.basePrice.normal){
this.normalPrice = (priceConfig.basePrice.normal * this.$store.state.memberRate).toFixed(2)
}
if(priceConfig.holidays && priceConfig.holidays.length > 0){
this.holidayDate = priceConfig.holidays
}else{
this.holidayDate = []
}
},
//
isHoliday(date) {
return this.holidayDate.some(holiday => holiday === date)
},
getProductList() {
getProductList({
"publishStatus": 1,
@ -926,7 +1080,7 @@ export default {
if (skus && skus.length > 0) {
let productSku = {
"skuId": skus[0].id,
"price": skus[0].price,
"price": (skus[0].price * this.$store.state.memberRate).toFixed(2),
"name": item.name,
"quantity": 0,
"isMainProduct": false
@ -972,8 +1126,9 @@ export default {
},
getCurrentDayPrice(currentDay) {
const currentDayPets = this.newOrderData.pets.filter(pet => pet.serviceDate === currentDay)
//
const baseServiceCost = this.basePrice
//
const isHolidayDate = this.isHoliday(currentDay)
const baseServiceCost = isHolidayDate ? parseFloat(this.holidayPrice) : parseFloat(this.normalPrice)
const largeDogCount = currentDayPets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes('大型')).length;
let mediumDogCount = currentDayPets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes('中型')).length;
@ -988,48 +1143,62 @@ export default {
return acc + this.calculatePetCost(pet);
}, 0);
// >30
if (totalPetCost > 30) {
// >=3,3
if (catCount >= 3) {
additionalCost = totalPetCost - 30
catCount = catCount - 3
} else if (smallDogCount >= 2) { // >=2,2
additionalCost = totalPetCost - 30
smallDogCount = smallDogCount - 2
} else if (mediumDogCount >= 1) { // >=1,1
additionalCost = totalPetCost - 30
mediumDogCount = mediumDogCount - 1
} else {
additionalCost = totalPetCost - 25
catCount = catCount - 1
smallDogCount = smallDogCount - 1
}
const freeQuota = this.price_config.freeQuota
if (totalPetCost > Number(freeQuota.threshold)) {
freeQuota.rules.forEach(rule => {
if (rule.type === 'cat' && catCount >= rule.count) {
additionalCost = totalPetCost - Number(rule.freeAmount)
catCount = catCount - rule.count
}else if(rule.type === 'smallDog' && smallDogCount >= rule.count){
additionalCost = totalPetCost - Number(rule.freeAmount)
smallDogCount = smallDogCount - rule.count
}else if(rule.type === 'mediumDog' && mediumDogCount >= rule.count){
additionalCost = totalPetCost - Number(rule.freeAmount)
mediumDogCount = mediumDogCount - rule.count
}else{
additionalCost = totalPetCost - Number(rule.freeAmount)
}
})
if (largeDogCount > 0) {
additionalCostItem.push({ itemName: '大型犬', price: 40, quantity: largeDogCount, unit: '只' })
additionalCostItem.push({ itemName: '大型犬', price: this.calculatePetCost({
petType: 'dog',
bodyType: '大型',
}), quantity: largeDogCount, unit: '只' })
}
if (mediumDogCount > 0) {
additionalCostItem.push({ itemName: '中型犬', price: 30, quantity: mediumDogCount, unit: '只' })
additionalCostItem.push({ itemName: '中型犬', price: this.calculatePetCost({
petType: 'dog',
bodyType: '中型',
}), quantity: mediumDogCount, unit: '只' })
}
if (smallDogCount > 0) {
additionalCostItem.push({ itemName: '小型犬', price: 15, quantity: smallDogCount, unit: '只' })
additionalCostItem.push({ itemName: '小型犬', price: this.calculatePetCost({
petType: 'dog',
bodyType: '小型',
}), quantity: smallDogCount, unit: '只' })
}
if (catCount > 0) {
additionalCostItem.push({ itemName: '猫猫', price: 10, quantity: catCount, unit: '只' })
additionalCostItem.push({ itemName: '猫猫', price: this.calculatePetCost({
petType: 'cat',
}), quantity: catCount, unit: '只' })
}
}
// 40/
if (largeDogCount > 0) {
additionalCost += (40 * largeDogCount)
additionalCost += (this.calculatePetCost({
petType: 'dog',
bodyType: '大型',
}) * largeDogCount)
}
//
let multServicesTotalCost = 0
const maxFeedCount = Math.max(...currentDayPets.map(pet => pet.feedCount));
if (maxFeedCount === 2) {
multServicesTotalCost += 45; // 12
multServicesTotalCost += this.price_config.multiService.two.price; // 12
} else if (maxFeedCount === 3) {
multServicesTotalCost += 130; // 13
multServicesTotalCost += this.price_config.multiService.three.price; // 13
}
//
const customServiceCost = currentDayPets.reduce((acc, pet) => acc + this.calculatePetCustomServiceCost(pet), 0)


+ 1
- 1
pages/newOrder备份/confirmOrder.vue View File

@ -237,7 +237,7 @@
<view style="display: flex;justify-content: space-between; align-items: center;">
<view style="height: 80rpx; display: flex; align-items: center;">
<text style="color: #333333;">订单总价: </text>
<text style="color: #FF530A; font-size: 40rpx;">¥{{ finalPrice }} <text
<text style="color: #FF530A; font-size: 40rpx;">¥{{ Number(finalPrice).toFixed(2) }} <text
style="font-size: 32rpx;"></text></text>
</view>
<view style="display: flex;">


+ 2
- 2
pages/newOrder备份/petList.vue View File

@ -32,7 +32,7 @@
</view>
<view style="margin-left: auto; width: 20px;">
<u-checkbox-group v-model="item.checked">
<u-checkbox shape="circle" activeColor="#ffbf60"></u-checkbox>
<u-checkbox shape="circle" name="checked" activeColor="#ffbf60"></u-checkbox>
</u-checkbox-group>
</view>
</view>
@ -144,7 +144,7 @@ export default {
selectedDate: [],
currentPets: [],
//
outDateList : ['2025-6-20'],
outDateList : [],
}
},
onShow() {


+ 6
- 5
pages/newOrder备份/serviceNew2.vue View File

@ -214,10 +214,10 @@
<view style="height: 80rpx;">
<view>
<text style="color: #333333;">订单总价 </text>
<text style="color: #FF530A; font-size: 40rpx;">¥{{ totalPrice }} <text style="font-size: 32rpx;">.00</text></text>
</view>
<text style="color: #FF530A; font-size: 40rpx;">¥{{ Number(totalPrice).toFixed(2) }} <text style="font-size: 32rpx;">.00</text></text>
</view>Number(finalPrice).toFixed(2)
<view style="display: flex; justify-content: flex-end;" @click="togglePriceDetails">
<text v-if="currentDayPrice" style="color:#FF530A; font-size: 22rpx;"> 当日价格¥{{ currentDayPrice }}.00 |</text>
<text v-if="currentDayPrice" style="color:#FF530A; font-size: 22rpx;"> 当日价格¥{{ Number(currentDayPrice).toFixed(2) }} |</text>
<text style="color:#FF530A; font-size: 22rpx;margin-left: 10rpx;">明细</text>
<u-icon name="arrow-up" color="#FF530A" size="10"></u-icon>
</view>
@ -835,8 +835,9 @@
}
// >0
const additionalCostItem = []
const petTypeArr = this.petTypeCounts.filter(item => item.largeDogCount > 0 || item.mediumDogCount > 0 ||
item.smallDogCount > 0 || item.catCount > 0)
const petTypeArr = this.petTypeCounts.filter(item => item.largeDogCount > 0 ||
item.mediumDogCount > 0 ||
item.smallDogCount > 0 || item.catCount > 0)
// largeDogCount=1 mediumDogCount=2 smallDogCount=3 catCount=4 4
petTypeArr.forEach(item => {
if (item.largeDogCount > 0) {


+ 405
- 0
pages/personalCenter/breedSelect.vue View File

@ -0,0 +1,405 @@
<template>
<view class="breed-select-container">
<!-- 搜索栏 -->
<view class="search-container">
<u-search
v-model="searchValue"
placeholder="搜索宠物品种"
:show-action="false"
@change="onSearchChange"
shape="round"
bg-color="#fff"
></u-search>
</view>
<!-- 品种列表 -->
<view class="breed-list-container">
<scroll-view
scroll-y="true"
class="breed-list"
:scroll-into-view="scrollIntoView"
@scroll="onScroll"
scroll-with-animation="true"
>
<view
v-for="letter in alphabetList"
:key="letter"
v-if="groupedBreeds && groupedBreeds[letter] && Array.isArray(groupedBreeds[letter]) && groupedBreeds[letter].length > 0"
:id="`section-${letter}`"
class="breed-section"
>
<view class="section-header">{{ letter }}</view>
<view
v-for="(breed, index) in groupedBreeds[letter]"
:key="`${letter}-${index}`"
class="breed-item"
@click="() => selectBreed(breed)"
>
<text class="breed-name">{{ breed }}</text>
<view class="breed-divider" v-if="index < groupedBreeds[letter].length - 1"></view>
</view>
</view>
</scroll-view>
</view>
<!-- 字母索引 -->
<view class="letter-index">
<view
v-for="letter in alphabetList"
:key="letter"
class="letter-item"
:class="{ 'active': currentLetter === letter, 'has-content': groupedBreeds && groupedBreeds[letter] && Array.isArray(groupedBreeds[letter]) && groupedBreeds[letter].length > 0 }"
@click="scrollToLetter(letter)"
>
{{ letter }}
</view>
</view>
</view>
</template>
<script>
import { getDictList } from "@/api/system/user"
export default {
data() {
return {
petType: 'dog',
searchValue: '',
breedData: [],
filteredBreeds: [],
groupedBreeds: {},
alphabetList: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
currentLetter: 'A',
scrollIntoView: '',
selectedBreed: '',
scrollTimer: null
}
},
onLoad(options) {
this.petType = options.petType || 'dog'
this.selectedBreed = options.selectedBreed || ''
this.getPetBreeds()
},
methods: {
//
async getPetBreeds() {
try {
const petBreedType = this.petType === 'cat' ? 'pet_brand_cat' : 'pet_brand_dog'
const res = await getDictList(petBreedType)
if (res && res.code === 200 && res.data && Array.isArray(res.data)) {
//
const validBreeds = res.data
.filter(e => e && e.dictLabel && typeof e.dictLabel === 'string')
.map(e => e.dictLabel.trim())
.filter(label => label.length > 0)
this.breedData = Array.from(new Set(validBreeds)).sort((a, b) => a.localeCompare(b, 'zh-CN'))
this.filteredBreeds = [...this.breedData]
this.groupBreedsByLetter()
console.log('品种数据加载成功,共', this.breedData.length, '个品种')
} else {
console.error('API返回数据格式异常:', res)
uni.showToast({
title: '获取品种数据失败',
icon: 'none'
})
}
} catch (error) {
console.error('获取品种数据失败:', error)
uni.showToast({
title: '获取品种数据失败',
icon: 'none'
})
}
},
//
groupBreedsByLetter() {
//
this.groupedBreeds = {}
this.alphabetList.forEach(letter => {
this.groupedBreeds[letter] = []
})
// filteredBreeds
if (!this.filteredBreeds || !Array.isArray(this.filteredBreeds)) {
console.warn('filteredBreeds不是有效数组')
this.filteredBreeds = []
return
}
//
this.filteredBreeds.forEach(breed => {
if (breed && typeof breed === 'string' && breed.trim()) {
const firstChar = this.getFirstChar(breed)
if (this.groupedBreeds && this.groupedBreeds[firstChar] && Array.isArray(this.groupedBreeds[firstChar])) {
this.groupedBreeds[firstChar].push(breed)
}
}
})
//
if (this.groupedBreeds) {
Object.keys(this.groupedBreeds).forEach(letter => {
if (this.groupedBreeds[letter] && Array.isArray(this.groupedBreeds[letter])) {
this.groupedBreeds[letter].sort((a, b) => a.localeCompare(b, 'zh-CN'))
}
})
}
},
//
getFirstChar(breed) {
if (!breed || typeof breed !== 'string' || breed.trim() === '') {
return 'A'
}
const firstChar = breed.charAt(0)
//
if (/[\u4e00-\u9fa5]/.test(firstChar)) {
return this.getPinyinFirstChar(firstChar)
}
return firstChar.toUpperCase()
},
//
getPinyinFirstChar(char) {
const pinyinMap = {
'阿': 'A', '艾': 'A', '澳': 'A', '爱': 'A', '安': 'A', '奥': 'A',
'巴': 'B', '比': 'B', '博': 'B', '边': 'B', '布': 'B', '伯': 'B',
'藏': 'C', '柴': 'C', '长': 'C', '成': 'C', '查': 'C', '春': 'C',
'大': 'D', '德': 'D', '杜': 'D', '斗': 'D', '丹': 'D', '道': 'D',
'俄': 'E', '恩': 'E', '尔': 'E',
'法': 'F', '芬': 'F', '佛': 'F', '费': 'F',
'高': 'G', '贵': 'G', '古': 'G', '格': 'G', '哥': 'G', '国': 'G',
'哈': 'H', '惠': 'H', '黑': 'H', '红': 'H', '华': 'H', '虎': 'H',
'吉': 'J', '金': 'J', '加': 'J', '杰': 'J', '京': 'J', '基': 'J',
'可': 'K', '卡': 'K', '克': 'K', '科': 'K',
'拉': 'L', '罗': 'L', '兰': 'L', '莱': 'L', '利': 'L', '路': 'L',
'马': 'M', '美': 'M', '牧': 'M', '摩': 'M', '曼': 'M', '米': 'M', '缅': 'M',
'牛': 'N', '纽': 'N', '南': 'N', '尼': 'N', '纳': 'N',
'平': 'P', '帕': 'P', '普': 'P', '皮': 'P',
'秋': 'Q', '奇': 'Q', '丘': 'Q',
'日': 'R', '瑞': 'R', '若': 'R', '热': 'R',
'松': 'S', '萨': 'S', '圣': 'S', '苏': 'S', '斯': 'S', '山': 'S',
'泰': 'T', '土': 'T', '特': 'T', '托': 'T',
'威': 'W', '魏': 'W', '温': 'W', '沃': 'W',
'西': 'X', '喜': 'X', '雪': 'X', '新': 'X',
'约': 'Y', '英': 'Y', '意': 'Y', '伊': 'Y',
'中': 'Z', '藏': 'Z', '芝': 'Z', '泽': 'Z'
}
return pinyinMap[char] || 'A'
},
//
onSearchChange(value) {
this.searchValue = value || ''
if (!this.breedData || !Array.isArray(this.breedData)) {
console.warn('breedData不是有效数组')
this.filteredBreeds = []
this.groupBreedsByLetter()
return
}
if (value && value.trim()) {
this.filteredBreeds = this.breedData.filter(breed =>
breed && typeof breed === 'string' && breed.toLowerCase().includes(value.toLowerCase())
)
} else {
this.filteredBreeds = [...this.breedData]
}
this.groupBreedsByLetter()
},
//
selectBreed(breed) {
try {
console.log('选择品种:', breed)
// 使线
uni.$emit('breedSelected', {
breed: breed,
petType: this.petType
})
console.log('触发全局事件 breedSelected:', breed)
//
uni.navigateBack()
} catch (error) {
console.error('选择品种时出错:', error)
uni.showToast({
title: '选择失败,请重试',
icon: 'none'
})
// 使
uni.navigateBack()
}
},
//
scrollToLetter(letter) {
this.currentLetter = letter
this.scrollIntoView = `section-${letter}`
},
//
onScroll(e) {
//
// const scrollTop = e.detail.scrollTop
// 使
// if (this.scrollTimer) {
// clearTimeout(this.scrollTimer)
// }
// this.scrollTimer = setTimeout(() => {
// this.updateCurrentLetter(scrollTop)
// }, 100)
},
//
updateCurrentLetter(scrollTop) {
//
const query = uni.createSelectorQuery().in(this)
query.selectAll('.breed-section').boundingClientRect()
query.exec((res) => {
if (res && res[0] && Array.isArray(res[0])) {
const sections = res[0]
let currentLetter = 'A'
//
for (let i = 0; i < sections.length; i++) {
const section = sections[i]
if (section && typeof section.top === 'number' && typeof section.height === 'number') {
const sectionTop = section.top
const sectionHeight = section.height
//
//
//
const searchBarHeight = 120 // 120rpx
const stickyTop = searchBarHeight
// sectionTop <= stickyTop
// sectionTop > stickyTop - sectionHeight
if (sectionTop <= stickyTop && sectionTop > stickyTop - sectionHeight) {
currentLetter = section.id.replace('section-', '')
break
}
}
}
if (this.currentLetter !== currentLetter) {
this.currentLetter = currentLetter
console.log('当前激活字母:', currentLetter, '滚动位置:', scrollTop)
}
}
})
},
}
}
</script>
<style lang="scss" scoped>
.breed-select-container {
height: 100vh;
background-color: #f5f5f5;
position: relative;
}
.search-container {
background-color: #fff;
padding: 20rpx 30rpx;
position: relative;
z-index: 99;
}
.breed-list-container {
flex: 1;
position: relative;
height: calc(100vh - 120rpx);
background-color: #fff;
}
.breed-list {
height: 100%;
background-color: #fff;
padding: 0 30rpx;
}
.breed-section {
.section-header {
font-size: 36rpx;
font-weight: bold;
color: #333;
padding: 20rpx 0 10rpx 0;
background-color: #fff;
position: sticky;
top: 0;
z-index: 10;
}
.breed-item {
padding: 20rpx 0;
cursor: pointer;
transition: background-color 0.2s;
.breed-name {
font-size: 28rpx;
color: #333;
line-height: 1.5;
font-weight: 400;
}
.breed-divider {
height: 1rpx;
background-color: #f0f0f0;
margin-top: 20rpx;
}
}
}
.letter-index {
position: fixed;
right: 10rpx;
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
align-items: center;
z-index: 1000;
.letter-item {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
color: #666;
margin: 2rpx 0;
transition: all 0.3s;
font-weight: 400;
border-radius: 50%;
&.active {
color: #fff;
font-weight: bold;
background-color: #FFBF60;
}
&.has-content {
color: #333;
}
}
}
</style>

+ 26
- 123
pages/personalCenter/components/petBaseInfo.vue View File

@ -38,56 +38,13 @@
</u-form-item>
</u--form>
</view>
<view style="background-color: #fffcf2;padding: 10px 20px;" v-show="showDisposition">
<view style="height: 85%;">
<u-checkbox-group
v-model="petBaseInfo.personality"
@change="checkboxChange"
iconPlacement="left"
placement="row"
style="flex-wrap: wrap;"
activeColor="#ffbf60">
<u-checkbox :customStyle="{margin: '8px'}" v-for="(item, index) in dispositionActions"
:key="item" :label="item" :name="item">
</u-checkbox>
</u-checkbox-group>
</view>
</view>
<u-picker :show="showSex" :showToolbar='false' :columns="sexActions" @cancel="showSex = false" :immediateChange="true"
@confirm="sexSelect"></u-picker>
<u-picker :show="showWeight" :showToolbar='false' :columns="weightActions" @cancel="showWeight = false" :immediateChange="true"
@confirm="weightSelect"></u-picker>
<u-overlay :show="showBreed">
<view class="breed-select" style="height: 60%;">
<u-search shape="round" :show-action="false" v-model="searchValue" @change="searchBreed"></u-search>
<view style="padding: 10px;height: 80%;overflow: auto;">
<u-radio-group v-model="tempBreed" placement="column">
<u-radio v-for="(item,index) in searchBreedData" :customStyle="{marginBottom: '8px'}"
activeColor="#ffbf60" :label="item" :name="item" :key="item"></u-radio>
</u-radio-group>
</view>
<view class="personal-pet-breed-btns">
<view class="personal-pet-breed-btn">
<u-button color="#FFF4E4" @click="breedSelectClose">
<view style="color: #FFAA48;">
取消
</view>
</u-button>
</view>
<view class="personal-pet-breed-btn" @click="breedSelectConfirm">
<u-button color="#FFBF60">
<view style="color: #fff;">
确定
</view>
</u-button>
</view>
</view>
</view>
</u-overlay>
<u-datetime-picker :maxDate='getMaxDate()' :minDate="getMinDate()" :show="showBirthday" v-model="tempBirthday" mode="year-month" @confirm="yearMonthConfirm"
@cancel="yearMonthClose"></u-datetime-picker>
@ -102,20 +59,21 @@
data() {
return {
showSex: false,
showBreed: false,
showWeight: false,
showBirthday: false,
showDisposition: false,
sexActions: [['男生','女生']],
weightActions: [['小型(<10 KG)','中型(10~20KG)', '大型(20KG以上)']],
dispositionActions: [],
breedData: [],
searchBreedData:[],
tempBreed: '',
searchValue: '',
tempBirthday: '',
tempDisposition: ''
tempBirthday: ''
}
},
watch: {
'petBaseInfo.breed': {
handler(newVal) {
console.log('品种数据更新:', newVal);
this.updatePetBaseInfo();
},
immediate: true
}
},
props:{
@ -135,10 +93,7 @@
})
}
},
mounted() {
this.getPersonalityDataList()
this.getPetBreed()
},
methods: {
nameChange(){
this.updatePetBaseInfo()
@ -154,28 +109,12 @@
this.updatePetBaseInfo()
},
breedSelectOpen() {
this.searchBreedData=this.breedData;
this.searchValue='';
this.tempBreed = this.petBaseInfo.breed;
this.showBreed = true;
},
searchBreed(){
if(this.searchValue&&this.searchValue!=''){
this.searchBreedData=this.breedData.filter(e=>e.includes(this.searchValue));
}else{
this.searchBreedData=this.breedData
}
},
breedSelectClose() {
this.tempBreed = '';
this.showBreed = false;
},
breedSelectConfirm() {
this.petBaseInfo.breed = this.tempBreed;
this.showBreed = false;
this.updatePetBaseInfo()
//
uni.navigateTo({
url: `/pages/personalCenter/breedSelect?petType=${this.petType}&selectedBreed=${encodeURIComponent(this.petBaseInfo.breed || '')}`
});
},
yearMonthOpen() {
if (this.petBaseInfo.birthDate) {
this.tempBirthday = Number(new Date(this.petBaseInfo.birthDate))
@ -194,45 +133,18 @@
this.updatePetBaseInfo()
},
dispositionSelectOpen() {
this.tempDisposition = this.petBaseInfo.personality;
this.showDisposition = true;
},
dispositionSelectClose() {
this.tempDisposition = '';
this.showDisposition = false;
},
dispositionSelectConfirm() {
this.petBaseInfo.personality = this.tempDisposition;
this.showDisposition = false;
this.updatePetBaseInfo()
},
checkboxChange(n) {
console.log('change', n);
this.updatePetBaseInfo();
//
uni.navigateTo({
url: `/pages/personalCenter/personalitySelect?personality=${encodeURIComponent(this.petBaseInfo.personality || '')}`
});
},
updatePetBaseInfo() {
// update petBaseInfo
this.$emit('update:petBaseInfo', this.petBaseInfo);
},
getPersonalityDataList(){
getDictList('pet_personality').then(res=>{
if (res.code == 200) {
this.dispositionActions = Array.from(new Set(res.data.map(e=>e.dictLabel)))
} else {
this.$modal.showToast('获取性格失败')
}
})
},
getPetBreed(){
let petBreedType = this.petType=='cat'?'pet_brand_cat':'pet_brand_dog'
getDictList(petBreedType).then(res=>{
if (res.code == 200) {
this.breedData =Array.from(new Set(res.data.map(e=>e.dictLabel)))
} else {
this.$modal.showToast('获取种类失败')
}
})
},
getMaxDate(){
return Date.now()
},
@ -251,14 +163,5 @@
margin-top: 10px;
padding: 10px 20px;
}
.personal-pet-breed-btns{
display: flex;
justify-content: space-around;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
}
.personal-pet-breed-btn{
width: 40%;
}
</style>

+ 52
- 14
pages/personalCenter/index.vue View File

@ -3,8 +3,8 @@
<view class="personal-base-info">
<view style="width: 100%;height: 140px; margin-top: 10px;">
<view style="width: 100%;text-align: center;" @click="openOtherPage('userInfo')">
<img src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/avatar_1.png"
style="width: 66px; height: 66px;" mode="widthFix"></img>
<img :src="userAvatar"
class="user-avatar-circle"></img>
</view>
<!-- <view style="width: 100%;text-align: center;">
<img src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/cmdf_text.png"style="width:55px;height: 16px;" mode="widthFix"></img>
@ -165,11 +165,13 @@
import {getOpenId,getPhoneNumber} from "@/api/system/user.js"
import { setToken,getToken,getOpenIdKey,setOpenIdKey } from '@/utils/auth'
import {getPersonalInfo} from "@/api/system/personal.js"
import { appletOrderDateFrequencyList } from '@/api/order/order.js'
export default{
data(){
return{
userName:'',
userLevel:'',
userAvatar:'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/avatar_1.png',
process:0,
spent:0,
cost:0,
@ -234,9 +236,10 @@
label: '邀请绑定',
path:'InvitationCode'
},
]
],
orderDateFrequencyList:[],
orderDateFrequencyId : '',
}
},
onShow() {
if (!getToken() || !getOpenIdKey()) {
@ -307,11 +310,14 @@
})
},
getPersonalInfo(){
this.$store.commit('getUserInfo')
this.isMember=true
getPersonalInfo().then(res=>{
if(res&&(res.id || res.id === 0)){
this.userLevel=res.level
this.userName=res.nickname
// 使使
this.userAvatar = res.avatar || 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/avatar_1.png'
this.consumption=res.consumption
this.promotion=res.promotion
this.process=res.process*100
@ -326,15 +332,39 @@
if(orderInfo.status) {
if(orderInfo.status==1){
this.active = 0
}else if(orderInfo.status==2){
let date = new Date();
let currentDate = `${date.getFullYear()}-${("0" + (date.getMonth() + 1)).slice(-2)}-${("0" + date.getDate()).slice(-2)}`;
let serviceDate= orderInfo.orderServiceList[0]?.serviceDate
if(serviceDate.indexOf(currentDate)>-1){
this.active=2
}else{
this.active=1
}
}else if(orderInfo.status==11){
// let date = new Date();
// let currentDate = `${date.getFullYear()}-${("0" + (date.getMonth() + 1)).slice(-2)}-${("0" + date.getDate()).slice(-2)}`;
// let serviceDate= orderInfo.orderServiceList[0]?.serviceDate
// if(serviceDate.indexOf(currentDate)>-1){
// this.active=2
// }else{
// this.active=1
// }
const params = {
orderId: orderInfo.orderId,
pageNumber: this.page,
pageSize: 9999999,
status : 2
};
appletOrderDateFrequencyList(params).then(res => {
if (res && res.data) {
this.active=2
this.orderDateFrequencyList = res.data
for (let index = 0; index < res.data.length; index++) {
const element = res.data[index];
element.list.forEach(n => {
if (n.status == 2) {
this.orderDateFrequencyId = n.id
}
})
}
}
}).catch(err => {
});
}else if(orderInfo.status==3){
this.active = 3
}
@ -354,6 +384,7 @@
let currentYear = new Date().getFullYear()
this.userLevel='游客'
this.userName='游客'
this.userAvatar='https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/avatar_1.png'
this.consumption=`累计消费 0 元( ${currentYear}-01-01起至今)`
this.promotion='注册后即可享受会员权益'
this.process=0
@ -384,7 +415,7 @@
// if (e.index === 2 && this.active === 2) {
//
uni.navigateTo({
url: `/pages/personalCenter/orderDetailImage?orderId=${orderInfo.id}`
url: `/pages/personalCenter/orderDetailImage?id=${this.orderDateFrequencyId}`
});
// }
},
@ -711,4 +742,11 @@
}
}
}
.user-avatar-circle {
width: 66px;
height: 66px;
border-radius: 50%;
object-fit: cover;
}
</style>

+ 304
- 0
pages/personalCenter/personalitySelect.vue View File

@ -0,0 +1,304 @@
<template>
<view class="personality-select-container">
<!-- 性格描述 -->
<view class="personality-description">
<view class="section-title">性格描述</view>
<view class="description-input">
<textarea
v-model="personalityDescription"
placeholder="请描述您的宠物性格"
class="description-textarea"
maxlength="200"
></textarea>
</view>
</view>
<!-- 快捷选择 -->
<view class="quick-selection">
<view class="section-title">快捷选择</view>
<view class="selection-grid">
<view
v-for="(item, index) in personalityOptions"
:key="index"
:class="['selection-item', { 'selected': selectedPersonalities.includes(item) }]"
@click="togglePersonality(item)"
>
{{ item }}
</view>
</view>
</view>
<!-- 保存按钮 -->
<view class="save-button-container">
<u-button
color="#FFBF60"
@click="savePersonality"
:loading="loading"
class="save-button"
>
<view style="color: #fff;">保存</view>
</u-button>
</view>
</view>
</template>
<script>
import { getDictList } from "@/api/system/user"
export default {
data() {
return {
loading: false,
personalityDescription: '',
selectedPersonalities: [],
personalityOptions: [],
originalPersonality: ''
}
},
onLoad(options) {
//
if (options.personality) {
this.originalPersonality = decodeURIComponent(options.personality);
this.parsePersonality(this.originalPersonality);
}
},
mounted() {
this.getPersonalityDataList();
},
methods: {
//
parsePersonality(personality) {
if (!personality) return;
// API
if (this.personalityOptions.length === 0) {
setTimeout(() => {
this.parsePersonality(personality);
}, 100);
return;
}
//
if (Array.isArray(personality)) {
//
this.selectedPersonalities = personality.filter(item =>
this.personalityOptions.includes(item)
);
//
const descriptions = personality.filter(item =>
!this.personalityOptions.includes(item)
);
this.personalityDescription = descriptions.join(',');
} else {
//
const parts = personality.split(',');
if (parts.length > 1) {
//
const quickSelections = parts[parts.length - 1];
this.selectedPersonalities = this.personalityOptions.filter(item =>
quickSelections.includes(item)
);
//
this.personalityDescription = parts.slice(0, -1).join(',');
} else {
//
this.selectedPersonalities = this.personalityOptions.filter(item =>
personality.includes(item)
);
}
}
},
//
togglePersonality(personality) {
const index = this.selectedPersonalities.indexOf(personality);
if (index > -1) {
this.selectedPersonalities.splice(index, 1);
} else {
this.selectedPersonalities.push(personality);
}
},
//
getPersonalityDataList() {
getDictList('pet_personality').then(res => {
if (res.code == 200) {
this.personalityOptions = Array.from(new Set(res.data.map(e => e.dictLabel)));
} else {
this.$modal.showToast('获取性格失败');
}
}).catch(err => {
console.error('获取性格数据失败:', err);
this.$modal.showToast('获取性格数据失败');
});
},
//
savePersonality() {
this.loading = true;
//
let finalPersonality = [];
if (this.personalityDescription.trim()) {
finalPersonality.push(this.personalityDescription.trim());
}
if (this.selectedPersonalities.length > 0) {
finalPersonality = finalPersonality.concat(this.selectedPersonalities);
}
//
if (finalPersonality.length === 0) {
this.$modal.showToast('请至少选择一项性格特征');
this.loading = false;
return;
}
//
setTimeout(() => {
this.loading = false;
//
const pages = getCurrentPages();
//
if (!pages || pages.length < 2) {
console.warn('页面栈不足,无法获取上一页')
uni.navigateBack();
return;
}
const prevPage = pages[pages.length - 2];
if (prevPage && prevPage.$vm) {
//
if (prevPage.$vm.petBaseInfo) {
prevPage.$vm.petBaseInfo.personality = finalPersonality;
//
prevPage.$vm.$emit('update:petBaseInfo', prevPage.$vm.petBaseInfo);
} else {
console.warn('上一页没有petBaseInfo')
}
} else {
console.warn('无法找到上一页')
}
// uni.showToast({
// title: '',
// icon: 'success',
// duration: 1500
// });
//
setTimeout(() => {
uni.navigateBack();
}, 0);
}, 0);
}
}
}
</script>
<style lang="scss">
.personality-select-container {
min-height: 100vh;
background-color: #f5f5f5;
padding: 20px;
box-sizing: border-box;
padding-bottom: 120px;
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 15px;
}
.personality-description {
background-color: #fff;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
.description-input {
.description-textarea {
width: 100%;
min-height: 100px;
padding: 15px;
border: 1px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
line-height: 1.5;
background-color: #fafafa;
box-sizing: border-box;
resize: none;
&::placeholder {
color: #999;
}
}
}
}
.quick-selection {
background-color: #fff;
border-radius: 8px;
padding: 20px;
margin-bottom: 100px;
.selection-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 8px;
.selection-item {
padding: 10px 4px;
background-color: #f5f5f5;
border-radius: 6px;
text-align: center;
font-size: 12px;
color: #666;
border: 1px solid transparent;
transition: all 0.3s ease;
line-height: 1.1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
&.selected {
background-color: #FFBF60;
color: #fff;
border-color: #FFBF60;
}
&:active {
transform: scale(0.98);
}
}
}
}
.save-button-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 20px;
background-color: #fff;
border-top: 1px solid #e0e0e0;
.save-button {
width: 100%;
height: 50px;
border-radius: 8px;
}
}
</style>

+ 1
- 1
pages/personalCenter/pet.vue View File

@ -14,7 +14,7 @@
<view :class="['personal-pet-list-item',item.gender=='男生'?'.personal-pet-list-item_backgroud_m':'.personal-pet-list-item_backgroud_f' ]">
<view class="personal-pet-info">
<view>
<u-avatar :src="item.photo?item.photo:defaultPhoto" size="60" shape="circle"></u-avatar>
<u-avatar :src="item.photo || defaultPhoto" size="60" shape="circle"></u-avatar>
</view>
<view class="personal-pet-info-1">
<view class="personal-pet-info-2">


+ 506
- 373
pages/personalCenter/petInfo.vue View File

@ -1,382 +1,515 @@
<template>
<view class="personal-pet-cat container">
<view class="personal-pet-img">
<view class="personal-pet-info-title">
宠物头像
</view>
<view style="display: flex;justify-content: center;">
<u-upload
accept="image"
:capture="['album','camera']"
:fileList="fileList"
@afterRead="afterRead"
@delete="deletePic"
:max-count="1"
name="pet"
width="60"
height="60"
:custom-style="{flex:0}"
>
<image src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/cat_new.png" style="width: 60px;height: 60px;"></image>
</u-upload>
</view>
</view>
<PetBaseInfo :petType="petType" :petBaseInfo.sync="petBaseInfo" />
<PetHealthInfo :petType="petType" :petHealthInfo.sync="petHealthInfo"/>
<view class="personal-pet-info-btns">
<view class="personal-pet-btns">
<view class="personal-pet-btn">
<u-button :disabled="optionType !='edit'" color="#FFF4E4" @click="deletePet">
<view style="color: #FFAA48;">
删除
</view>
</u-button>
</view>
<view class="personal-pet-btn" @click="save">
<u-button color="#FFBF60" :loading="loading">
<view style="color: #fff;">
保存
</view>
</u-button>
</view>
</view>
</view>
<u-modal :show="showDel"
@confirm="confirmDel"
@cancel="showDel = false;"
ref="uModal"
showCancelButton
:asyncClose="true"
:content='delContent'>
</u-modal>
</view>
<view class="personal-pet-cat container">
<view class="personal-pet-img">
<view class="personal-pet-info-title">
宠物头像
</view>
<view style="display: flex;justify-content: center;">
<view class="avatar-container" @click="selectImage">
<image v-if="avatarPath" :src="avatarPath" class="avatar-image" mode="aspectFill"></image>
<image v-else src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/cat_new.png"
class="avatar-image" mode="aspectFill"></image>
<view class="avatar-overlay">
<text class="avatar-text">点击上传</text>
</view>
</view>
</view>
</view>
<PetBaseInfo :petType="petType" :petBaseInfo.sync="petBaseInfo" />
<PetHealthInfo :petType="petType" :petHealthInfo.sync="petHealthInfo" />
<view class="personal-pet-info-btns">
<view class="personal-pet-btns">
<view class="personal-pet-btn">
<u-button :disabled="optionType != 'edit'" color="#FFF4E4" @click="deletePet">
<view style="color: #FFAA48;">
删除
</view>
</u-button>
</view>
<view class="personal-pet-btn" @click="save">
<u-button color="#FFBF60" :loading="loading">
<view style="color: #fff;">
保存
</view>
</u-button>
</view>
</view>
</view>
<u-modal :show="showDel" @confirm="confirmDel" @cancel="showDel = false;" ref="uModal" showCancelButton
:asyncClose="true" :content='delContent'>
</u-modal>
<!-- 裁剪组件 -->
<ksp-cropper
mode="free"
:width="200"
:height="200"
:maxWidth="1024"
:maxHeight="1024"
:url="cropperUrl"
@cancel="onCropperCancel"
@ok="onCropperOk">
</ksp-cropper>
</view>
</template>
<script>
import {addPet,getPetDetails,updatePet,delPet} from '@/api/system/pet'
import PetBaseInfo from './components/petBaseInfo.vue'
import PetHealthInfo from './components/petHealthInfo.vue'
export default{
data(){
return {
loading:false,
fileList:[],
petId:'',
petType:'dog',
optionType:'add',
isNewOrder:false,
delContent:'',
photo:'',
petBaseInfo:{
name: '',
gender: '',
breed: '',
bodyType: '',
birthDate: '',
personality: ''
},
petHealthInfo: {
vaccineStatus: '',
dewormingStatus: '',
sterilization: '',
doglicenseStatus:'',
healthStatus: [],
remark:''
},
showDel:false,
}
},
components:{
PetBaseInfo,
PetHealthInfo
},
onLoad: function(option) {
console.log(option.petType); //
this.petType = option.petType;
this.optionType=option.optionType;
if(this.optionType=='edit'){
this.petId=option.petId;
this.getPetDetails(option.petId);
}
if(option.isNewOrder){
this.isNewOrder=true;
}
},
methods:{
//
deletePic(event) {
this.fileList.splice(event.index, 1)
},
//
async afterRead(event) {
// multiple true , file
let lists = [].concat(event.file)
let fileListLen = this.fileList.length
lists.map((item) => {
this.fileList.push({
...item,
status: 'uploading',
message: '上传中'
})
})
for (let i = 0; i < lists.length; i++) {
const result = await this.uploadFilePromise(lists[i].url)
let item = this.fileList[fileListLen]
this.fileList.splice(fileListLen, 1, Object.assign(item, {
status: 'success',
message: '',
url: result
}))
fileListLen++
}
},
uploadFilePromise(url) {
return new Promise((resolve, reject) => {
let a = uni.uploadFile({
url: 'https://store-test.catmdogd.com/test-api/h5/oss/upload',
filePath: url,
name: 'file',
formData: {
user: 'test'
},
success: (res) => {
setTimeout(() => {
if(res&&res.data){
let resData = JSON.parse(res.data);
resolve(resData.url);
}
reject("上传失败");
}, 1000)
}
});
})
},
getPetDetails(petId){
getPetDetails(petId).then(res=>{
if(res&& res.id){
const { photo, name, gender,breed, bodyType, birthDate, personality, vaccineStatus, dewormingStatus, sterilization, doglicenseStatus, healthStatus,remark } = res;
this.petBaseInfo = {
name,
gender,
breed,
bodyType,
birthDate,
personality
};
this.petHealthInfo = {
vaccineStatus,
dewormingStatus,
sterilization,
doglicenseStatus,
healthStatus,
remark
}
this.fileList=[{url:photo}]
}else{
this.$modal.showToast('获取pet失败')
}
})
},
save(){
console.log(this.petBaseInfo)
console.log(this.petHealthInfo)
if(!(this.fileList.length>0&&this.fileList[0].url)){
this.$modal.showToast('请上传宠物照片!')
return;
}
this.photo = this.fileList[0].url;
let params = { ...{petType:this.petType,photo:this.photo}, ...this.petBaseInfo,...this.petHealthInfo}
if(!params.name){
this.$modal.showToast('请填写宠物昵称!')
return;
}
if(!params.breed){s
this.$modal.showToast('请选择宠物品种!')
return;
}
if(!params.bodyType){
this.$modal.showToast('请选择宠物体重!')
return;
}
if(!params.personality){
this.$modal.showToast('请选择宠物性格,可多选!')
return;
}
if(!params.vaccineStatus){
this.$modal.showToast('请选择宠物疫苗情况!')
return;
}
if(!params.dewormingStatus){
this.$modal.showToast('请选择宠物驱虫情况!')
return;
}
if(params.healthStatus==null || !params.healthStatus.length){
this.$modal.showToast('请选择宠物健康情况,可多选!')
return;
}
this.loading=true
if(this.optionType=='edit'){
params.id = this.petId;
updatePet(params).then(res=>{
if(res&&res.code==200){
uni.showToast({
title: '保存成功',
duration: 3000,
icon:"none"
})
setTimeout(() => {
this.loading = false;
if(this.isNewOrder){
uni.redirectTo({url:'/pages/newOrder/petList'});
}else{
let len = getCurrentPages().length;
if(len >= 2) {
uni.navigateBack();
}else{
uni.redirectTo({url:'/pages/personalCenter/pet'});
}
}
}, 1000);
}else {
this.loading=false
uni.showToast({
title: '更新pet失败',
duration: 3000,
icon:"none"
})
}
})
} else if(this.optionType=='add'){
addPet(params).then(res=>{
if(res&&res==1){
uni.showToast({
title: '保存成功',
duration: 3000,
icon:"none"
})
setTimeout(() => {
this.loading = false;
if(this.isNewOrder){
uni.redirectTo({url:'/pages/newOrder/petList'});
}else{
let len = getCurrentPages().length;
if(len >= 2) {
uni.navigateBack();
}else {
uni.redirectTo({url:'/pages/personalCenter/pet'});
}
}
}, 1000);
}else {
this.loading=false
uni.showToast({
title: '新增pet失败',
duration: 3000,
icon:"none"
})
}
})
}
},
deletePet(){
this.delContent = `确定要删除${this.petBaseInfo.name}?`;
this.showDel = true;
},
confirmDel(){
delPet(this.petId).then(res=>{
console.log(res);
uni.showToast({
title: '删除成功',
duration: 3000,
icon:"none"
})
this.showDel=false;
setTimeout(() => {
let len = getCurrentPages().length;
this.loading=false
if(len >= 2) {
uni.navigateBack();
}else {
uni.redirectTo({url:'/pages/personalCenter/pet'});
}
}, 2000);
})
},
}
}
import { addPet, getPetDetails, updatePet, delPet } from '@/api/system/pet'
import PetBaseInfo from './components/petBaseInfo.vue'
import PetHealthInfo from './components/petHealthInfo.vue'
export default {
data() {
return {
loading: false,
fileList: [],
petId: '',
petType: 'dog',
optionType: 'add',
isNewOrder: false,
delContent: '',
photo: '',
//
cropperUrl: '',
avatarPath: '',
petBaseInfo: {
name: '',
gender: '',
breed: '',
bodyType: '',
birthDate: '',
personality: ''
},
petHealthInfo: {
vaccineStatus: '',
dewormingStatus: '',
sterilization: '',
doglicenseStatus: '',
healthStatus: [],
remark: ''
},
showDel: false,
}
},
components: {
PetBaseInfo,
PetHealthInfo
},
onLoad: function (option) {
console.log(option.petType); //
this.petType = option.petType;
this.optionType = option.optionType;
if (this.optionType == 'edit') {
this.petId = option.petId;
this.getPetDetails(option.petId);
}
if (option.isNewOrder) {
this.isNewOrder = true;
}
//
uni.$on('breedSelected', this.handleBreedSelected);
},
methods: {
//
selectImage() {
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
// url
this.cropperUrl = res.tempFilePaths[0];
},
fail: (err) => {
console.log('选择图片失败:', err);
uni.showToast({
title: '选择图片失败',
icon: 'none'
});
}
});
},
//
onCropperCancel() {
this.cropperUrl = '';
},
//
async onCropperOk(ev) {
try {
this.cropperUrl = '';
this.avatarPath = ev.path;
//
const uploadResult = await this.uploadFilePromise(ev.path);
this.photo = uploadResult;
// fileList
this.fileList = [{
url: uploadResult,
status: 'success',
message: ''
}];
uni.showToast({
title: '头像上传成功',
icon: 'success'
});
} catch (error) {
console.log('上传失败:', error);
uni.showToast({
title: '上传失败,请重试',
icon: 'none'
});
}
},
//
deletePic(event) {
this.fileList.splice(event.index, 1)
this.avatarPath = '';
this.photo = '';
},
// -
async afterRead(event) {
// multiple true , file
let lists = [].concat(event.file)
let fileListLen = this.fileList.length
lists.map((item) => {
this.fileList.push({
...item,
status: 'uploading',
message: '上传中'
})
})
for (let i = 0; i < lists.length; i++) {
const result = await this.uploadFilePromise(lists[i].url)
let item = this.fileList[fileListLen]
this.fileList.splice(fileListLen, 1, Object.assign(item, {
status: 'success',
message: '',
url: result
}))
fileListLen++
}
},
uploadFilePromise(url) {
return new Promise((resolve, reject) => {
let a = uni.uploadFile({
url: 'https://store-test.catmdogd.com/test-api/h5/oss/upload',
filePath: url,
name: 'file',
formData: {
user: 'test'
},
success: (res) => {
setTimeout(() => {
if (res && res.data) {
let resData = JSON.parse(res.data);
resolve(resData.url);
}
reject("上传失败");
}, 1000)
}
});
})
},
getPetDetails(petId) {
getPetDetails(petId).then(res => {
if (res && res.id) {
const { photo, name, gender, breed, bodyType, birthDate, personality, vaccineStatus, dewormingStatus, sterilization, doglicenseStatus, healthStatus, remark } = res;
this.petBaseInfo = {
name,
gender,
breed,
bodyType,
birthDate,
personality
};
this.petHealthInfo = {
vaccineStatus,
dewormingStatus,
sterilization,
doglicenseStatus,
healthStatus,
remark
}
this.fileList = [{ url: photo }]
this.avatarPath = photo;
this.photo = photo; // photo
} else {
this.$modal.showToast('获取pet失败')
}
})
},
save() {
console.log(this.petBaseInfo)
console.log(this.petHealthInfo)
if (!(this.fileList.length > 0 && this.fileList[0].url)) {
this.$modal.showToast('请上传宠物照片!')
return;
}
// 使URL
this.photo = this.fileList[0].url;
let params = {
petType: this.petType,
photo: this.photo,
...this.petBaseInfo,
...this.petHealthInfo
}
console.log('保存参数:', params); //
if (!params.name) {
this.$modal.showToast('请填写宠物昵称!')
return;
}
if (!params.breed) {
this.$modal.showToast('请选择宠物品种!')
return;
}
if (!params.bodyType) {
this.$modal.showToast('请选择宠物体重!')
return;
}
if (!params.personality) {
this.$modal.showToast('请选择宠物性格,可多选!')
return;
}
if (!params.vaccineStatus) {
this.$modal.showToast('请选择宠物疫苗情况!')
return;
}
if (!params.dewormingStatus) {
this.$modal.showToast('请选择宠物驱虫情况!')
return;
}
if (params.healthStatus == null || !params.healthStatus.length) {
this.$modal.showToast('请选择宠物健康情况,可多选!')
return;
}
this.loading = true
if (this.optionType == 'edit') {
params.id = this.petId;
updatePet(params).then(res => {
if (res && res.code == 200) {
uni.showToast({
title: '保存成功',
duration: 3000,
icon: "none"
})
setTimeout(() => {
this.loading = false;
if (this.isNewOrder) {
uni.redirectTo({ url: '/pages/newOrder/petList' });
} else {
let len = getCurrentPages().length;
if (len >= 2) {
uni.navigateBack();
} else {
uni.redirectTo({ url: '/pages/personalCenter/pet' });
}
}
}, 1000);
} else {
this.loading = false
uni.showToast({
title: '更新pet失败',
duration: 3000,
icon: "none"
})
}
})
} else if (this.optionType == 'add') {
addPet(params).then(res => {
if (res && res == 1) {
uni.showToast({
title: '保存成功',
duration: 3000,
icon: "none"
})
setTimeout(() => {
this.loading = false;
if (this.isNewOrder) {
uni.redirectTo({ url: '/pages/newOrder/petList' });
} else {
let len = getCurrentPages().length;
if (len >= 2) {
uni.navigateBack();
} else {
uni.redirectTo({ url: '/pages/personalCenter/pet' });
}
}
}, 1000);
} else {
this.loading = false
uni.showToast({
title: '新增pet失败',
duration: 3000,
icon: "none"
})
}
})
}
},
deletePet() {
this.delContent = `确定要删除${this.petBaseInfo.name}?`;
this.showDel = true;
},
confirmDel() {
delPet(this.petId).then(res => {
console.log(res);
uni.showToast({
title: '删除成功',
duration: 3000,
icon: "none"
})
this.showDel = false;
setTimeout(() => {
let len = getCurrentPages().length;
this.loading = false
if (len >= 2) {
uni.navigateBack();
} else {
uni.redirectTo({ url: '/pages/personalCenter/pet' });
}
}, 2000);
})
},
//
handleBreedSelected(data) {
console.log('接收到品种选择结果:', data);
if (data && data.breed && data.petType === this.petType) {
this.petBaseInfo.breed = data.breed;
console.log('更新品种信息:', data.breed);
}
},
},
onUnload() {
//
uni.$off('breedSelected', this.handleBreedSelected);
}
}
</script>
<style lang="scss">
.personal-pet-cat{
.breed-select{
background-color: #fff;
width: 100%;
padding: 20px;
border-radius: 8px 8px 0 0;
position: absolute;
bottom: 0;
.breed-select-btn{
display: flex;
justify-content: space-around;
align-items: center;
}
}
.personal-pet-info-title{
font-size: 14px;
color: #333;
font-weight: bold;
padding-bottom: 10px;
}
.border-bottom{
border-bottom: 1px solid #efefef;
}
.personal-pet-img{
width: 100%;
height: 118px;
background-color: #fff;
padding: 10px 20px;
}
.personal-pet-basic-info{
background-color: #fff;
margin-top: 10px;
padding: 10px 20px;
}
}
.personal-pet-cat {
.breed-select {
background-color: #fff;
width: 100%;
padding: 20px;
border-radius: 8px 8px 0 0;
position: absolute;
bottom: 0;
.breed-select-btn {
display: flex;
justify-content: space-around;
align-items: center;
}
}
.personal-pet-info-title {
font-size: 14px;
color: #333;
font-weight: bold;
padding-bottom: 10px;
}
.border-bottom {
border-bottom: 1px solid #efefef;
}
.personal-pet-img {
width: 100%;
height: 118px;
background-color: #fff;
padding: 10px 20px;
}
.personal-pet-basic-info {
background-color: #fff;
margin-top: 10px;
padding: 10px 20px;
}
}
.container {
position: relative;
height: 100%;
padding-bottom: 90px;
.personal-pet-info-btns {
background-color: #FFFFFF;
padding: 10px 20px 40px;
width: 100%;
height: 90px;
position: fixed;
bottom: 0;
z-index: 100;
text-align: center;
.personal-pet-btns {
display: flex;
justify-content: space-around;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
.personal-pet-btn {
width: 40%;
}
}
}
}
.container {
position: relative;
height: 100%;
padding-bottom: 90px;
.personal-pet-info-btns {
background-color: #FFFFFF;
padding: 10px 20px 40px;
width: 100%;
height: 90px;
position: fixed;
bottom: 0;
z-index: 100;
text-align: center;
.personal-pet-btns{
display: flex;
justify-content: space-around;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
.personal-pet-btn{
width: 40%;
}
}
}
}
//
.avatar-container {
position: relative;
width: 60px;
height: 60px;
border-radius: 50%;
overflow: hidden;
border: 2px solid #f0f0f0;
.avatar-image {
width: 100%;
height: 100%;
}
.avatar-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s;
.avatar-text {
color: #fff;
font-size: 10px;
text-align: center;
}
}
&:active .avatar-overlay {
opacity: 1;
}
}
</style>

+ 107
- 44
pages/personalCenter/userInfo.vue View File

@ -4,21 +4,16 @@
<view class="user-info-title">
用户头像
</view>
<view style="display: flex;justify-content: center;">
<u-upload
accept="image"
:capture="['album','camera']"
:fileList="fileList"
@afterRead="afterRead"
@delete="deletePic"
:max-count="1"
name="avatar"
width="80"
height="80"
:custom-style="{flex:0}"
>
<image :src="avatarUrl" style="width: 80px;height: 80px;border-radius: 50%;"></image>
</u-upload>
<view class="avatar-container">
<button class="choose-avatar-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
<view class="avatar-wrapper">
<image
:src="avatarUrl"
class="avatar-image"
mode="aspectFill"
></image>
</view>
</button>
</view>
</view>
@ -55,7 +50,7 @@
</template>
<script>
import {updateUserProfile, uploadAvatar} from '@/api/system/user'
import { updateInfo } from '@/api/system/user'
import {getPersonalInfo} from "@/api/system/personal.js"
export default {
@ -65,7 +60,10 @@
fileList: [],
avatarUrl: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/avatar_1.png',
nickname: '',
userLevel: ''
userLevel: '',
userInfoForm: {
headImage: ''
}
}
},
onLoad() {
@ -80,38 +78,29 @@
this.userLevel = res.level || ''
if (res.avatar) {
this.avatarUrl = res.avatar
this.userInfoForm.headImage = res.avatar
this.fileList = [{url: res.avatar}]
}
}
})
},
//
deletePic(event) {
this.fileList.splice(event.index, 1)
this.avatarUrl = 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/avatar_1.png'
},
//
async afterRead(event) {
// multiple true , file
let lists = [].concat(event.file)
let fileListLen = this.fileList.length
lists.map((item) => {
this.fileList.push({
...item,
status: 'uploading',
message: '上传中'
})
})
for (let i = 0; i < lists.length; i++) {
const result = await this.uploadFilePromise(lists[i].url)
let item = this.fileList[fileListLen]
this.fileList.splice(fileListLen, 1, Object.assign(item, {
status: 'success',
message: '',
url: result
}))
//
async onChooseAvatar(e) {
const { avatarUrl } = e.detail
try {
//
const result = await this.uploadFilePromise(avatarUrl)
this.avatarUrl = result
fileListLen++
this.userInfoForm.headImage = result
uni.showToast({
title: '头像更新成功',
icon: 'success'
})
} catch (error) {
uni.showToast({
title: '头像上传失败',
icon: 'none'
})
}
},
uploadFilePromise(url) {
@ -148,7 +137,7 @@
avatar: this.avatarUrl
}
updateUserProfile(params).then(res => {
updateInfo(params).then(res => {
if (res && res.code == 200) {
uni.showToast({
title: '保存成功',
@ -258,4 +247,78 @@
}
}
}
.user-avatar-circle-edit {
width: 80px !important;
height: 80px !important;
border-radius: 50% !important;
object-fit: cover !important;
display: block !important;
}
//
.avatar-container {
display: flex;
justify-content: center;
align-items: center;
padding: 20px 0;
}
.choose-avatar-btn {
background: none;
border: none;
padding: 0;
margin: 0;
position: relative;
border-radius: 50%;
overflow: visible;
&::after {
border: none;
}
}
.avatar-wrapper {
position: relative;
width: 100px;
height: 100px;
border-radius: 50%;
overflow: hidden;
border: 3px solid #f0f0f0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
}
}
.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.avatar-edit-icon {
position: absolute;
bottom: -5px;
right: -5px;
width: 30px;
height: 30px;
background: #FFBF60;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #fff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
}
.edit-text {
font-size: 10px;
color: #fff;
font-weight: bold;
}
</style>

+ 225
- 0
pages_order/auth/login.vue View File

@ -0,0 +1,225 @@
<template>
<view class="login">
<view class="login-logo">
<image class="logo" :src="configList && configList.applet_info ? configList.applet_info.paramValueImage : ''" mode="aspectFill"></image>
<image class="d" :src="configList && configList.logo_icon ? configList.logo_icon.paramValueImage : ''" mode="aspectFill"></image>
</view>
<view class="login-submit">
<up-button class="mt24" type="primary" text="手机号登录"
open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber"
shape="circle" color="#FFBF60" style="margin-top: 20rpx;"></up-button>
</view>
<view class="flex-rowc">
<view>
<up-checkbox v-model="isAgree" size="29rpx" icon-size="29rpx" name="agree" usedAlone
shape="circle" activeColor="#FFBF60">
</up-checkbox>
</view>
<view class="size-26 agreement">
<text>我已阅读并同意猫爸狗妈</text>
<text @click="privacyPolicy('login_xy')" class="main-color">注册协议</text>
<text></text>
<text @click="privacyPolicy('login_ys')" class="main-color">隐私政策</text>
</view>
</view>
<view class="login-footer flex-rowc">
<button @click="cancelLogin" class="btn-cancel" plain>暂不登录</button>
</view>
</view>
<up-popup :show="show" @close="close" :round="10">
<view style="padding: 10rpx 20rpx;height: 50vh;overflow-y: scroll;" v-html="content"></view>
</up-popup>
</template>
<script>
import {
getOpenIdKey,
getToken,
setIsLogin,
setOpenIdKey,
setStorage,
setToken
} from "../../utils/auth";
import {
getOpenId,
getPersonalInfo,
getPhoneNumber
} from "../../api/system/user";
import { mapGetters } from 'vuex';
export default {
name: 'AuthIndex',
data() {
return {
value: false,
show: false,
content: '',
isAgree: false
}
},
computed: {
...mapGetters(['configList'])
},
mounted() {
//
},
methods: {
//
loginWx() {
if (!this.isAgree) {
return uni.showToast({
title: '请先同意隐私协议',
icon: 'none'
})
}
this.login()
},
login() {
uni.login({
provider: 'weixin',
success: (loginRes) => {
this.getOpenId(loginRes.code)
},
fail: function(error) {
//
uni.showToast('授权失败,请授权后再试')
}
});
},
getOpenId(code) {
getOpenId(code).then(res => {
if (res.code == 200 && res.data) {
let resData = JSON.parse(res.data)
let token = resData.token;
let openId = resData.openId;
setOpenIdKey(openId)
if(token){
setToken(token)
setIsLogin(true)
this.getUserInfo()
}else{
// token
uni.showToast({
title: '请授权手机号登录',
icon: 'none'
})
}
}
})
},
//
async getUserInfo() {
try {
const res = await getPersonalInfo()
if(res && (res.id || res.id === 0)){
setStorage('userInfo', res)
uni.navigateBack()
}
} catch (error) {
console.error('获取用户信息失败:', error)
}
},
// token
onGetPhoneNumber(e) {
if(e.detail.errMsg=="getPhoneNumber:fail user deny"){ //
//
}else{ //
if(getOpenIdKey()){
this.getPhoneNumber({"openId":getOpenIdKey(),"code":e.detail.code})
}
}
},
getPhoneNumber(data){
getPhoneNumber(data).then(res=>{
if(res&&res.code==200){
let token = res.data.token
setToken(token)
setIsLogin(true)
this.getUserInfo()
} else{
uni.showToast({
icon:'error',
title:'获取手机号失败'
})
}
})
},
cancelLogin() {
uni.switchTab({
url: "/pages/workbenchManage/index"
})
},
close() {
this.show = false
},
privacyPolicy(key) {
this.content = this.configList && this.configList[key] ? this.configList[key].paramValueArea || "" : "";
this.show = true;
}
}
}
</script>
<style scoped lang="scss">
.login {
height: 100vh;
background: #ffffff;
&-logo {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 504rpx;
background: linear-gradient(360deg, #FFFFFF 0%, #FFBF60 99%);
.logo {
width: 180rpx;
height: 180rpx;
}
.d {
width: 200rpx;
height: 80rpx;
margin-top: 20rpx;
}
}
&-submit {
padding: 0 76rpx;
margin: 150rpx 0 40rpx 0;
}
}
.agreement {
color: #C7C7C7;
font-size: 22rpx;
line-height: 29rpx;
}
.login-footer {
width: 100vw;
position: fixed;
bottom: 127rpx;
.btn-cancel {
color: #C7C7C7;
font-size: 30rpx;
line-height: 40rpx;
border: none;
display: inline-block;
padding: 0;
}
}
</style>

pages/companionPetList/companionPetInfo.vue → pages_order/companionPetList/companionPetInfo.vue View File

@ -363,6 +363,7 @@
import uniRate from '@/uni_modules/uni-rate/components/uni-rate/uni-rate.vue';
import positionMixin from '../../mixins/position';
import addressMap from '@/components/addressMap.vue'
import { mapState } from 'vuex'
export default {
mixins: [positionMixin],
components : {
@ -476,6 +477,9 @@
]
}
},
computed: {
...mapState(['teacherLevelList'])
},
components: {
uniRate,
},
@ -494,6 +498,12 @@
},
toBuy(){
this.buyInfo.teacher = this.companionInfo
let companionLevel = this.teacherLevelList.find(n => n.paramValueNum == this.companionInfo.userBcsRole)
if(companionLevel){
this.$globalData.newOrderData.companionLevel = companionLevel
}
uni.navigateTo({
url: '/pages/newOrder/serviceNew?type=teacher'
})

pages/companionPetList/companionPetList.vue → pages_order/companionPetList/companionPetList.vue View File

@ -272,8 +272,8 @@
.stringify(this.allInfo))
uni.navigateTo({
// url: '/pages/companionPetList/companionPetInfo',
url: `/pages/companionPetList/companionPetInfo?id=${id}`
// url: '/pages_order/companionPetList/companionPetInfo',
url: `/pages_order/companionPetList/companionPetInfo?id=${id}`
});
}
},

+ 76
- 0
pages_order/companionPetList/couponList.vue View File

@ -0,0 +1,76 @@
<template>
<view class="coupon-list">
<CouponItem
v-for="(item,index) in couponData"
:key="index"
:couponData="item"
@coupon-received="handleCouponReceived"
@update-coupon="updateCouponData"
@show-rule="showRulePopup"
/>
<!-- 优惠券详细规则弹窗组件 -->
<CouponRulePopup ref="couponRulePopup" />
</view>
</template>
<script>
import {
getCouponList,
} from "@/api/system/user"
import CouponRulePopup from '@/components/CouponRulePopup/index.vue'
import CouponItem from '@/components/CouponItem/index.vue'
export default {
components: {
CouponRulePopup,
CouponItem
},
data() {
return {
couponData: []
}
},
onLoad() {
// this.openIdStr = this.$globalData.openIdStr;
this.getCouponListAuth()
},
methods: {
getCouponListAuth() {
getCouponList().then(res => {
if (res.code == 200) {
this.couponData = res.data
console.log("this.couponData", this.couponData)
} else {
this.$modal.showToast('获取优惠券失败')
}
})
},
//
showRulePopup(item) {
this.$refs.couponRulePopup.open(item || {});
},
//
handleCouponReceived(couponData) {
console.log('优惠券领取成功:', couponData);
//
},
//
updateCouponData(updatedCoupon) {
//
const couponIndex = this.couponData.findIndex(item => item.id === updatedCoupon.id);
if (couponIndex !== -1) {
// 使Vue.set$set
this.$set(this.couponData, couponIndex, updatedCoupon);
}
}
}
}
</script>
<style lang="scss">
.coupon-list {
//
}
</style>

+ 1
- 1
pages_order/components/order/CompanionSelectPopup.vue View File

@ -83,7 +83,7 @@ export default {
this.close();
setTimeout(() => {
uni.navigateTo({
url: '/pages/companionPetList/companionPetList'
url: '/pages_order/companionPetList/companionPetList'
});
}, 300);
}


+ 14
- 2
pages_order/components/order/ServiceItems.vue View File

@ -54,9 +54,17 @@
<!-- 费用合计 -->
<view class="cost-summary">
<view class="cost-item">
<text class="cost-label">费用合计</text>
<text class="cost-label">原价</text>
<text class="cost-value">¥{{totalAmount.toFixed(2)}}</text>
</view>
<view class="cost-item" v-if="orderDetail.needPreFamiliarize && orderDetail.preFamiliarizePrice > 0">
<text class="cost-label">提前熟悉的费用</text>
<text class="cost-value">¥{{orderDetail.preFamiliarizePrice.toFixed(2)}}</text>
</view>
<view class="cost-item" v-if="orderDetail.companionLevelPrice">
<text class="cost-label">伴宠师</text>
<text class="cost-value">¥{{orderDetail.companionLevelPrice.toFixed(2)}}</text>
</view>
<view class="cost-item discount" v-if="discount > 0">
<text class="cost-label">平台优惠</text>
<text class="cost-value">- ¥{{discount.toFixed(2)}}</text>
@ -95,7 +103,11 @@
finalAmount: {
type: Number,
default: 0
}
},
orderDetail : {
type : Object,
default : () => {}
}
},
data() {
return {


+ 2
- 2
pages_order/components/order/ServiceRemarks.vue View File

@ -14,9 +14,9 @@
<!-- 是否提前熟悉 -->
<view class="remarks-item advance-familiar">
<text class="item-label">是否提前熟悉</text>
<!-- <view class="checkbox-container" v-if="remarks.isAdvanceFamiliar">
<view class="checkbox-container" v-if="remarks.isAdvanceFamiliar">
<uv-icon name="checkmark" color="#FFAA48" size="36"></uv-icon>
</view> -->
</view>
</view>
<!-- 价格说明 -->


+ 1
- 1
pages_order/order/companionSelect.vue View File

@ -186,7 +186,7 @@
viewCompanionDetail(companionId) {
//
uni.navigateTo({
url: `/pages/companionPetList/companionPetInfo?id=${companionId}`
url: `/pages_order/companionPetList/companionPetInfo?id=${companionId}`
});
}
}


+ 15
- 9
pages_order/order/orderDetail.vue View File

@ -11,7 +11,10 @@
<!-- 服务项目及费用组件 -->
<service-items :items="orderDetail.items" :totalAmount="orderDetail.totalAmount"
:discount="orderDetail.discount" :finalAmount="orderDetail.finalAmount"></service-items>
:discount="orderDetail.discount" :finalAmount="orderDetail.finalAmount"
:memberDiscount="orderDetail.memberDiscount"
:orderDetail="orderDetail"
></service-items>
<!-- 服务备注组件 -->
<service-remarks :remarks="orderDetail.remarks"></service-remarks>
@ -22,22 +25,22 @@
<!-- 底部按钮区域 -->
<view class="order-detail-footer">
<view class="footer-btn cancel-btn" v-if="orderDetail.status == '0'" @click="$refs.cancelOrderPopup.open()">
<view class="footer-btn cancel-btn" v-if="[0, 1].includes(orderDetail.status)" @click="$refs.cancelOrderPopup.open()">
<text>取消订单</text>
</view>
<view class="footer-btn pay-btn" v-if="orderDetail.status == '0'" @click="goToPay">
<text>去付款</text>
</view>
<!-- <view class="footer-btn modify-btn" v-if="orderDetail.status == '1' || orderDetail.status == '2'" @click="modifyOrder">
<view class="footer-btn modify-btn" v-if="[0, 1].includes(orderDetail.status)" @click="modifyOrder">
<text>修改订单</text>
</view> -->
<view class="footer-btn pay-btn" v-if="orderDetail.status == '4'" @click="goToReview">
</view>
<view class="footer-btn pay-btn" v-if="[4].includes(orderDetail.status) && !order.evaluation" @click="goToReview">
<text>去评价</text>
</view>
<view class="footer-btn pay-btn" v-if="orderDetail.status == '4'" @click="handleReorder">
<text>再来一单</text>
</view>
<view class="footer-btn pay-btn" v-if="orderDetail.status == '2'" @click="viewServiceRecord">
<view class="footer-btn pay-btn" v-if="[2,4,11].includes(orderDetail.status)" @click="viewServiceRecord">
<text>查看服务记录</text>
</view>
<view class="footer-btn contact-btn">
@ -216,8 +219,11 @@
//
totalAmount: data.totalAmount,
discount: data.totalAmount - data.payAmount,
discount: data.couponDiscount,
memberDiscount: data.memberDiscount,
finalAmount: data.payAmount,
needPreFamiliarize : data.needPreFamiliarize,
companionLevelPrice : data.companionLevelPrice,
//
status: data.status.toString(),
@ -225,8 +231,8 @@
//
remarks: {
keyHandoverMethod: '存于快递柜',
isAdvanceFamiliar: true,
priceInfo: `价格${data.payAmount}`,
isAdvanceFamiliar: data.needPreFamiliarize,
priceInfo: `价格${data.preFamiliarizePrice}`,
serviceContent: '服务内容: 伴宠师将按照约定时间上门照顾宠物',
serviceCondition: '服务保障: 购买此服务后,平台将安排伴宠师与您确认服务细节',
notes: data.note || ''


+ 18
- 7
pages_order/order/orderList.vue View File

@ -60,7 +60,7 @@
<!-- 订单操作 -->
<view class="order-actions">
<view class="action-btn details-btn" v-if="order.status == 0"
<view class="action-btn details-btn" v-if="[0, 1].includes(order.status)"
@click="$refs.cancelOrderPopup.open(order)">
<text>取消订单</text>
</view>
@ -70,16 +70,18 @@
<view class="action-btn pay-btn" v-if="order.status == 0" @click="goToPay(order.orderId)">
<text>去付款</text>
</view>
<view class="action-btn pay-btn" v-if="[0, 1, 2].includes(order.status)" @click="modifyOrder(order)">
<view class="action-btn pay-btn" v-if="[0, 1].includes(order.status)" @click="modifyOrder(order)">
<text>修改订单</text>
</view>
<view class="action-btn pay-btn" v-if="order.status == 4" @click="goToReview(order)">
<view class="action-btn pay-btn"
v-if="[4].includes(order.status) && !order.evaluation"
@click="goToReview(order)">
<text>去评价</text>
</view>
<view class="action-btn pay-btn" v-if="order.status == 4" @click="handleReorder(order)">
<text>再来一单</text>
</view>
<view class="action-btn pay-btn" v-if="[2,3,4].includes(order.status)" @click="viewServiceRecord(order.orderId)">
<view class="action-btn pay-btn" v-if="[2,4,11].includes(order.status)" @click="viewServiceRecord(order.orderId)">
<text>查看服务记录</text>
</view>
</view>
@ -206,6 +208,9 @@
onPullDownRefresh() {
this.refreshing()
},
onShow() {
this.refreshing()
},
onReachBottom() {
this.loadMore()
},
@ -630,12 +635,18 @@
display: flex;
justify-content: flex-end;
align-items: center;
flex-wrap: wrap;
gap: 20rpx;
.action-btn {
padding: 16rpx 30rpx;
padding: 16rpx 24rpx;
border-radius: 30rpx;
font-size: 26rpx;
margin-left: 20rpx;
font-size: 24rpx;
margin-left: 0;
white-space: nowrap;
flex-shrink: 0;
min-width: 120rpx;
text-align: center;
}
.details-btn {


+ 102
- 7
pages_order/order/orderModify.vue View File

@ -71,9 +71,19 @@
</template>
<script>
import { getOrderDetail, updateOrder } from '@/api/order/order.js';
import { getOrderDetail, updateBaseOrder } from '@/api/order/order.js';
import { mapState } from 'vuex';
import { getOpenIdKey } from '@/utils/auth'
import {
getTeacherDetail,
} from "@/api/order/order"
import positionMixin from '@/mixins/position';
import {
getAddressDetails,addAddress,updateAddress
} from '@/api/system/address.js'
export default {
mixins: [positionMixin],
components: {
},
data() {
@ -90,6 +100,9 @@ export default {
originalOrderData: null
}
},
computed: {
...mapState(['teacherLevelList'])
},
onLoad(options) {
if (options.orderId) {
this.orderId = options.orderId;
@ -109,9 +122,14 @@ export default {
async loadOrderData() {
if (!this.orderId) return;
const params = {
openId: getOpenIdKey(),
orderId: this.orderId
};
this.loading = true;
try {
const res = await getOrderDetail({ orderId: this.orderId });
const res = await getOrderDetail(params);
if (res) {
const orderData = res;
@ -194,7 +212,7 @@ export default {
paymentMethod: this.modifyInfo.paymentMethod,
};
const res = await updateOrder(updateData);
const res = await updateBaseOrder(updateData);
if (res && res.code === 200) {
uni.showToast({
title: '订单修改成功',
@ -222,11 +240,88 @@ export default {
},
//
modifyOrder() {
async modifyOrder() {
this.$globalData.newOrderData.orderId = this.orderId;
let order = this.originalOrderData
//
if(order.addressId) {
try {
const addressRes = await getAddressDetails(order.addressId);
if(addressRes && addressRes.id) {
//
this.$globalData.newOrderData.currentAddress = {
id: order.addressId,
name: order.receiverName,
phone: order.receiverPhone,
province: order.receiverProvince,
city: order.receiverCity,
district: order.receiverDistrict,
detailAddress: order.receiverDetailAddress,
latitude: order.latitude,
longitude: order.longitude,
}
} else {
//
console.log('地址不存在,addressId:', order.addressId);
this.$globalData.newOrderData.currentAddress = {};
}
} catch (error) {
console.error('验证地址失败:', error);
//
this.$globalData.newOrderData.currentAddress = {};
}
} else {
// ID
this.$globalData.newOrderData.currentAddress = {};
}
if(order.teacherId){
getTeacherDetail({
userId : order.teacherId
}).then(response => {
if (response) {
let companionInfo = response
companionInfo.distanceText = this.calculateDistanceAddress(response.appletAddresseList)
this.buyInfo.teacher = companionInfo
}
})
}
if(order.companionLevel){
this.$globalData.newOrderData.companionLevel =
this.teacherLevelList.find(item => item.paramValueNum == order.companionLevel);
}
//
if(order.needPreFamiliarize) {
this.$globalData.newOrderData.needPreFamiliarize = ['是否提前熟悉']
}
//
if(order.petVOList && order.petVOList.length > 0) {
this.$globalData.newOrderData.currentPets = order.petVOList.map(pet => {
//
const petServices = order.orderServiceList.filter(service => service.petId === pet.id);
const selectedDate = petServices.map(service => ({
date: service.serviceDate,
info: "预定"
}));
return {
...pet,
checked: ['checked'], //
selectedDate,
};
});
}
uni.navigateTo({
url: `/pages/newOrder/serviceNew?orderId=${this.orderId}&isModify=true`
url: `/pages/newOrder/serviceNew`
});
},
},
}
}


+ 4
- 6
pages_order/order/orderReview.vue View File

@ -150,14 +150,14 @@
const params = {
orderId : this.orderId,
openId: getOpenIdKey(),
comment: this.reviewContent, //
satisfaction: this.rating, //
user2Id: this.teacherId // IDID
content: this.reviewContent, //
num: this.rating, //
technicianId: this.teacherId // IDID
};
//
orderEvaluate(params).then(res => {
if (res && res.code === 200) {
if (res && res.code == 200) {
this.showSuccessAndBack();
} else {
uni.showToast({
@ -167,8 +167,6 @@
}
}).catch(err => {
console.error('提交评价失败', err);
// 便
this.showSuccessAndBack();
});
},


+ 185
- 0
pages_order/order/originalOrderData.json View File

@ -0,0 +1,185 @@
{
"orderId": 6429631920572416,
"payId": 6429631920572417,
"orderSn": "250803-6429631920572416",
"memberId": 214,
"totalAmount": 171.76,
"payAmount": 146.00,
"status": 0,
"aftersaleStatus": 1,
"orderItemList": [
{
"createBy": 214,
"createTime": "2025-08-03 10:15:30",
"updateBy": null,
"updateTime": null,
"id": 6429631924668416,
"orderId": 6429631920572416,
"orderServiceId": 6429631921915904,
"productId": 63,
"outProductId": "P001",
"skuId": 404,
"outSkuId": null,
"productSnapshotId": null,
"skuSnapshotId": null,
"pic": "https://catmdogf.oss-cn-shanghai.aliyuncs.com/2024/01/08ba3980c107c2472397b13ef9769257e1陪玩.png",
"productName": "专业遛狗",
"salePrice": 63.75,
"purchasePrice": null,
"quantity": 1,
"productCategoryId": 76,
"spData": "{\"时间\":\"60\"}",
"isMainProduct": 1
},
{
"createBy": 214,
"createTime": "2025-08-03 10:15:31",
"updateBy": null,
"updateTime": null,
"id": 6429631929452544,
"orderId": 6429631920572416,
"orderServiceId": 6429631926798336,
"productId": 63,
"outProductId": "P001",
"skuId": 404,
"outSkuId": null,
"productSnapshotId": null,
"skuSnapshotId": null,
"pic": "https://catmdogf.oss-cn-shanghai.aliyuncs.com/2024/01/08ba3980c107c2472397b13ef9769257e1陪玩.png",
"productName": "专业遛狗",
"salePrice": 72.25,
"purchasePrice": null,
"quantity": 1,
"productCategoryId": 76,
"spData": "{\"时间\":\"60\"}",
"isMainProduct": 1
}
],
"orderServiceList": [
{
"createBy": 214,
"createTime": "2025-08-03 10:15:31",
"updateBy": null,
"updateTime": null,
"id": 6429631926798336,
"orderId": 6429631920572416,
"pet": null,
"serviceFrequency": "1",
"serviceDate": "2025-09-11",
"serviceTimeFirst": null,
"serviceTimeSecond": null,
"delFlag": 0,
"petId": 188,
"expectServiceTime": "",
"feedCount": null,
"feedCountPrice": 0.00,
"petVo": {
"createBy": null,
"createTime": null,
"updateBy": null,
"updateTime": "2025-07-28 17:09:47",
"id": 188,
"name": "xx",
"petType": "cat",
"gender": "男生",
"birthDate": "2025-05",
"remark": "",
"photo": "https://cdn.catmdogd.com/2025/07/255102d859a38c4a3ca19a991e7e21aa6bvAZhN0q32GSUf5b3c2ccb949fbdcda8579544239b665.png",
"breed": "蓝白矮脚",
"bodyType": "小型(<10 KG)",
"personality": [
"653",
"害羞腼腆",
"内向胆小",
"有攻击性"
],
"vaccineStatus": "每年都免疫",
"sterilization": "已绝育",
"dewormingStatus": "未驱虫",
"doglicenseStatus": "",
"healthStatus": [
"身体健康,无异常"
],
"owner": "214"
},
"pets": null,
"orderItems": null
},
{
"createBy": 214,
"createTime": "2025-08-03 10:15:30",
"updateBy": null,
"updateTime": null,
"id": 6429631921915904,
"orderId": 6429631920572416,
"pet": null,
"serviceFrequency": "1",
"serviceDate": "2025-09-10",
"serviceTimeFirst": null,
"serviceTimeSecond": null,
"delFlag": 0,
"petId": 188,
"expectServiceTime": "",
"feedCount": null,
"feedCountPrice": 0.00,
"petVo": null,
"pets": null,
"orderItems": null
}
],
"service": null,
"note": null,
"deliverySn": null,
"createTime": "2025-08-03 10:15:30",
"paymentTime": null,
"receiverName": "hly",
"receiverPhone": "19330214982",
"receiverProvince": "湖南省",
"receiverCity": "郴州市",
"receiverDistrict": "永兴县",
"receiverDetailAddress": "大桥路246号永兴县人民政府(大桥路北)12312",
"timeToPay": null,
"staffId": null,
"petVOList": [
{
"createBy": null,
"createTime": null,
"updateBy": null,
"updateTime": "2025-07-28 17:09:47",
"id": 188,
"name": "xx",
"petType": "cat",
"gender": "男生",
"birthDate": "2025-05",
"remark": "",
"photo": "https://cdn.catmdogd.com/2025/07/255102d859a38c4a3ca19a991e7e21aa6bvAZhN0q32GSUf5b3c2ccb949fbdcda8579544239b665.png",
"breed": "蓝白矮脚",
"bodyType": "小型(<10 KG)",
"personality": [
"653",
"害羞腼腆",
"内向胆小",
"有攻击性"
],
"vaccineStatus": "每年都免疫",
"sterilization": "已绝育",
"dewormingStatus": "未驱虫",
"doglicenseStatus": "",
"healthStatus": [
"身体健康,无异常"
],
"owner": "214"
}
],
"teacherId": null,
"companionLevel": 1,
"addressId": 261,
"longitude": "113.11659",
"latitude": "26.1272",
"memberDiscount": 25.76,
"couponDiscount": 0.00,
"oldPrice": 146.00,
"preFamiliarizePrice": 38.00,
"companionLevelPrice": 10.00,
"needPreFamiliarize": false
}

+ 3
- 1
pages_order/order/payOrderSuccessful.vue View File

@ -7,7 +7,7 @@
<view class="content-card">
<view class="card-item">
<view class="item-icon">
<image src="/pages_order/static/order/f.png" mode="aspectFit" :show-menu-by-longpress="true"></image>
<image src="/pages_order/static/order/f.png" mode="aspectFit"></image>
</view>
<view class="item-text">
<text class="item-title">请及时添加服务顾问的企业微信</text>
@ -15,6 +15,7 @@
<view class="code">
<image src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/details/QR_Code.png"
mode="aspectFit"
:show-menu-by-longpress="true"
style="width: 260rpx; height:260rpx"/>
</view>
@ -68,6 +69,7 @@
},
methods: {
goHome() {
this.initGlobalData()
this.$globalData.itemPrice=[]
this.$globalData.submitData = {}
this.$globalData.newOrderData={


+ 54
- 13
store/index.js View File

@ -4,6 +4,7 @@ import user from '@/store/modules/user'
import getters from './getters'
import { getConfigList } from '@/api/system/configList'
import { getPersonalInfo } from "@/api/system/personal.js"
Vue.use(Vuex)
@ -15,26 +16,48 @@ const store = new Vuex.Store({
buyInfo : {
teacher : null,//用户选择的购买老师
},//用户选择的购买信息
userInfo : {},//用户信息
//会员泽口
memberRate : 1,
//技师等级规则列表
teacherLevelList : [],
},
mutations : {
// 初始化配置
initConfig(state) {
getConfigList({
paramCode : 'price_config'
}).then(res => {
const price_config = uni.getStorageSync('price_config')
if(price_config){
state.price_config = price_config
}
const teacherLevelList = uni.getStorageSync('teacherLevelList')
if(teacherLevelList){
state.teacherLevelList = teacherLevelList
}
getConfigList().then(res => {
const configList = {
...state.configList,
}
if (res.code == 200) {
res.result.forEach(n => {
configList[n.paramCode] = n.paramValueText;
configList[n.paramCode + '_keyValue'] = n.keyValue;
if(n.paramCode == 'price_config'){
state.price_config = JSON.parse(n.paramValueText)
}
});
}
state.teacherLevelList = []
res.forEach(n => {
configList[n.paramCode] = n.paramValueText;
configList[n.paramCode + '_keyValue'] = n.keyValue;
if(n.paramCode == 'price_config'){
state.price_config = JSON.parse(n.paramValueText)
uni.setStorageSync('price_config', state.price_config)
}
if(n.paramClass == 'order_mall'){
state.teacherLevelList.push(n)
}
});
uni.setStorageSync('teacherLevelList', state.teacherLevelList)
console.log('state.teacherLevelList', state.teacherLevelList)
state.configList = configList
uni.$emit('initConfig', state.configList)
})
@ -42,6 +65,24 @@ const store = new Vuex.Store({
setPosition(state, position){
state.position = position;
},
setUserInfo(state, userInfo){
state.userInfo = userInfo;
},
getUserInfo(state){
getPersonalInfo().then(res=>{
if(res&&(res.id || res.id === 0)){
state.userInfo = res;
let memberDiscountKey = ['', 'new', 'regular', 'silver', 'gold']
let key = memberDiscountKey[res.levelKey]
if (state.price_config && key) {
state.memberRate = state.price_config.memberDiscount[key] || 1
}
}
})
},
},
modules: {
user


+ 36
- 0
uni_modules/ksp-cropper/changelog.md View File

@ -0,0 +1,36 @@
## 1.1.13(2023-10-26)
优化图片初始化逻辑
修护已知bug
## 1.1.12(2023-06-27)
修护vue3小程序下报错的bug
## 1.1.11(2023-05-29)
修护了在vue3下报错的bug
## 1.1.10(2023-05-26)
修改了已知bug
暂时取消了vue3的支持
## 1.1.9(2023-03-24)
修护bug
## 1.1.8(2023-03-24)
修护bug
## 1.1.7(2022-12-08)
修护bug
## 1.1.6(2022-11-18)
修好app无法拖动问题
## 1.1.5(2022-06-14)
填新版HBuilderX的坑,简单测试是没问题了。
## 1.1.4(2022-02-15)
修护ios下微信小程序第一次裁剪的bug
## 1.1.3(2022-02-10)
修护APP点击无效的bug
## 1.1.2(2022-01-24)
优化一些细节
## 1.1.1(2022-01-19)
更新示例项目
## 1.1.0(2022-01-18)
新增旋转功能
## 1.0.2(2022-01-13)
修护mode="fixed"模式无效的bug
## 1.0.1(2021-12-20)
修护IOS下,小程序点击没反应的bug
## 1.0.0(2021-12-06)
图片裁剪工具

+ 1004
- 0
uni_modules/ksp-cropper/components/ksp-cropper/ksp-cropper.vue
File diff suppressed because it is too large
View File


+ 81
- 0
uni_modules/ksp-cropper/package.json View File

@ -0,0 +1,81 @@
{
"id": "ksp-cropper",
"displayName": "ksp-cropper",
"version": "1.1.13",
"description": "高性能图片裁剪工具",
"keywords": [
"头像",
"图片",
"裁剪"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": "",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": {
"minVersion": "2.9.0"
},
"阿里": "n",
"百度": "n",
"字节跳动": "n",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

+ 78
- 0
uni_modules/ksp-cropper/readme.md View File

@ -0,0 +1,78 @@
# ksp-cropper
## 高性能图片裁剪工具
### 属性说明
|属性 |类型 |默认 |备注 |
| :--------: | :-----: | :----: | :----: |
| url |String | "" | 需要裁剪的图片路径,为空时控件隐藏,不为空时控件显示|
| mode |String | "free" | 裁剪模式|
| width |Number | 200 | 图片裁剪后的宽度,固定大小时有效|
| height |Number | 200 | 图片裁剪后的高度,固定大小时有效|
| maxWidth |Number | 1024 | 图片裁剪后的最大宽度 |
| maxHeight |Number | 1024 | 图片裁剪后的最大高度 |
### mode有效值
| 模式 |值 |说明 |
| :-----: | :-----: | :----: |
| 固定模式 |fixed | 裁剪出指定大小的图片,一般用于头像上传 |
| 等比缩放 |ratio | 限定宽高比,裁剪大小不固定 |
| 自由模式 |free | 不限定宽高比,裁剪大小不固定 |
### 事件说明
|事件名称 |说明 |返回 |
| :--------: | :-----: | :----: |
| ok |点击确定按钮 | e:{path} |
| cancel |点击取消按钮 | - |
### 示例
```html
<template>
<view>
<button @click="select">选择图片</button>
<image mode="widthFix" :src="path"/>
<ksp-cropper mode="free" :width="200" :height="140" :maxWidth="1024" :maxHeight="1024" :url="url" @cancel="oncancel" @ok="onok"></ksp-cropper>
</view>
</template>
<script>
export default {
data() {
return {
url: "",
path: ""
}
},
onLoad() {
},
methods: {
select() {
uni.chooseImage({
count: 1,
success: (rst) => {
// 设置url的值,显示控件
this.url = rst.tempFilePaths[0];
}
});
},
onok(ev) {
this.url = "";
this.path = ev.path;
},
oncancel() {
// url设置为空,隐藏控件
this.url = "";
}
}
}
</script>
```
### 注意
1.微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。<br/>
2.微信小程序真机调试会报错,但正常运行是不会有问题的。<br/>
3.uni-app版本不断更新,插件有时无法适应新版本,感谢大家及时提交bug,但希望大家手下留情,不要轻易给差评。

+ 33
- 0
uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue View File

@ -47,6 +47,8 @@
<text style="font-size: 20rpx;"
v-if="isNotOrder"
>不接单</text>
<text v-else-if="isHoliday" class="uni-calendar-item__holiday-price">¥{{holidayPrice}}</text>
<text v-else-if="normalPrice && normalPrice > 0" class="uni-calendar-item__normal-price">¥{{normalPrice}}</text>
</view>
</view>
</template>
@ -86,6 +88,20 @@
lunar: {
type: Boolean,
default: false
},
holidayDate: {
type: Array,
default () {
return []
}
},
holidayPrice: {
type: [Number, String],
default: 0
},
normalPrice: {
type: [Number, String],
default: 0
}
},
computed: {
@ -95,6 +111,9 @@
isNotOrder(){
return this.disabledDay.includes(this.weeks.fullDate)
},
isHoliday(){
return this.holidayDate.includes(this.weeks.fullDate)
},
},
methods: {
choiceDate(weeks) {
@ -202,4 +221,18 @@
background-color: #ff5a5f;
color: #fff;
}
.uni-calendar-item__holiday-price {
font-size: 20rpx;
color: #ff6b6b;
font-weight: bold;
margin-top: 4rpx;
}
.uni-calendar-item__normal-price {
font-size: 20rpx;
color: #666;
font-weight: bold;
margin-top: 4rpx;
}
</style>

+ 15
- 1
uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue View File

@ -53,7 +53,7 @@
<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" :selected="selected" :lunar="lunar" @change="choiceDate"
:disabledDay="disabledDay"></calendar-item>
:disabledDay="disabledDay" :holidayDate="holidayDate" :holidayPrice="holidayPrice" :normalPrice="normalPrice"></calendar-item>
</view>
</view>
</view>
@ -138,6 +138,20 @@
clearDate: {
type: Boolean,
default: true
},
holidayDate: {
type: Array,
default () {
return []
}
},
holidayPrice: {
type: [Number, String],
default: 0
},
normalPrice: {
type: [Number, String],
default: 0
}
},
data() {


+ 1
- 1
utils/getUrl.js View File

@ -5,7 +5,7 @@ const accountInfo = wx.getAccountInfoSync();
const api={
develop:"http://127.0.0.1:8080",
develop:"http://127.0.0.1:8002",
// develop:"http://h5.xzaiyp.top",
// develop:"https://api.catmdogd.com/prod-api",
// develop:"https://api-test.catmdogd.com/test-api",


Loading…
Cancel
Save