Browse Source

fix(订单管理): 修复宠物档案跳转缺少订单ID的问题

修复订单详情页跳转宠物档案页面时未传递orderId参数的问题
```

```msg
refactor(认证考试): 重构考试答案提交逻辑

将单个题目提交改为批量提交,优化考试流程:
1. 基础考试和培训考试都改为最后统一提交答案
2. 添加加载状态提示
3. 使用Promise.all处理并发请求
```

```msg
fix(认证考试): 修复考试完成状态判断逻辑

修改answeBaseIsFinish和answeTrainIsFinish接口的返回判断逻辑,从检查code改为检查data字段
```

```msg
feat(认证考试): 新增重新考试和成为伴宠师接口

1. 添加retakeExam和appletUsersTeacher接口
2. 在错误详情页添加重新考试功能
3. 在考试完成页添加成为伴宠师功能
```

```msg
style(时间轴组件): 优化操作按钮布局

1. 添加按钮间距(gap)
2. 使用flex:1使按钮等宽
3. 根据状态显示不同按钮文本
4. 添加serviceBtn属性控制档案按钮显示
```

```msg
refactor(订单弹窗): 重构服务档案弹窗组件

1. 使用timelineService组件替代原有实现
2. 简化数据结构处理
3. 添加状态判断逻辑
4. 优化弹窗标题和样式
```

```msg
fix(表单验证): 添加认证考试结束页表单验证

1. 添加姓名、电话、地址的必填验证
2. 添加格式验证(电话格式、姓名格式)
3. 添加长度验证
4. 添加错误状态样式
5. 优化错误提示体验
```

```msg
refactor(工作台): 重构伴宠师申请流程

1. 优化申请条件判断逻辑
2. 添加用户状态检查
3. 完善考试状态跳转逻辑
4. 统一使用store获取用户信息
master
前端-胡立永 4 months ago
parent
commit
13660d1a8c
12 changed files with 695 additions and 288 deletions
  1. +31
    -4
      api/examination/index.js
  2. +12
    -12
      otherPages/authentication/components/questionCard.vue
  3. +54
    -4
      otherPages/authentication/examination/base.vue
  4. +147
    -7
      otherPages/authentication/examination/end.vue
  5. +17
    -7
      otherPages/authentication/examination/errorDetail.vue
  6. +26
    -1
      otherPages/authentication/examination/train.vue
  7. +1
    -1
      otherPages/orderTakingManage/detail/index.vue
  8. +277
    -0
      pages/myOrdersManage/components/petServicePopup - 副本.vue
  9. +20
    -205
      pages/myOrdersManage/components/petServicePopup.vue
  10. +27
    -10
      pages/myOrdersManage/components/systemOrder.vue
  11. +17
    -5
      pages/myOrdersManage/components/timelineService.vue
  12. +66
    -32
      pages/workbenchManage/index.vue

+ 31
- 4
api/examination/index.js View File

@ -133,14 +133,14 @@ export const answeTrainByQuestionId = (params) => {
// 伴宠师工作台-查询用户基本考核是否完成 // 伴宠师工作台-查询用户基本考核是否完成
export const answeBaseIsFinish = (params) => { export const answeBaseIsFinish = (params) => {
return request({ return request({
url: '/applet/examination/answeBaseIsFinish',
url: '/applet/examination/answeBaseIsFinishV2',
headers: { headers: {
isToken: true isToken: true
}, },
method: "get", method: "get",
params params
}).then(res => { }).then(res => {
return res.code == 200
return res.data == 1
}) })
} }
@ -148,13 +148,40 @@ export const answeBaseIsFinish = (params) => {
// 伴宠师工作台-查询用户培训考核是否完成 // 伴宠师工作台-查询用户培训考核是否完成
export const answeTrainIsFinish = (params) => { export const answeTrainIsFinish = (params) => {
return request({ return request({
url: '/applet/examination/answeTrainIsFinish',
url: '/applet/examination/answeTrainIsFinishV2',
headers: { headers: {
isToken: true isToken: true
}, },
method: "get", method: "get",
params params
}).then(res => { }).then(res => {
return res.code == 200
return res.data == 1
}) })
} }
// 伴宠师工作台-成为伴宠师
export const appletUsersTeacher = (params) => {
return request({
url: '/applet/examination/appletUsersTeacher',
headers: {
isToken: true
},
method: "get",
params
})
}
// 伴宠师工作台-重新考试
export const retakeExam = (params) => {
return request({
url: '/applet/examination/retakeExam',
headers: {
isToken: true
},
method: "get",
params
})
}

+ 12
- 12
otherPages/authentication/components/questionCard.vue View File

@ -103,18 +103,18 @@
const onChange = (val) => { const onChange = (val) => {
value.value = val value.value = val
const data = {
userId: userId.value,
questionId: props.data.id,
}
if (props.type === '基本') {
data.answerId = val
addBaseAnswer(data)
} else if (props.type === '培训') {
data.answer = val
addTrainAnswer(data)
}
// const data = {
// userId: userId.value,
// questionId: props.data.id,
// }
// if (props.type === '') {
// data.answerId = val
// addBaseAnswer(data)
// } else if (props.type === '') {
// data.answer = val
// addTrainAnswer(data)
// }
} }
</script> </script>


+ 54
- 4
otherPages/authentication/examination/base.vue View File

@ -40,6 +40,17 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { onShow } from '@dcloudio/uni-app' import { onShow } from '@dcloudio/uni-app'
import { getQuestionList, getQuestionOptions } from '@/api/examination' import { getQuestionList, getQuestionOptions } from '@/api/examination'
import {
addBaseAnswer,
addTrainAnswer
} from '@/api/examination'
import {
store
} from '@/store'
const userId = computed(() => {
return store.state.user.userInfo.userId
})
import questionCard from '../components/questionCard.vue'; import questionCard from '../components/questionCard.vue';
@ -50,13 +61,21 @@ const total = ref(0)
const initQuestion = async () => { const initQuestion = async () => {
try { try {
let questions = (await getQuestionList({ type: TYPE })).map(item => ({ id: item.id, title: item.title, value: null }))
let questions = (await getQuestionList({ type: TYPE })).map(item => ({
id: item.id,
title: item.title,
value: null,
answerList : item.answerList,
}))
// todo: // todo:
for (let i = 0; i < questions.length; i++) { for (let i = 0; i < questions.length; i++) {
const options = (await getQuestionOptions({ questionId: questions[i].id })).map(item => ({ id: item.id, title: item.title}))
// const options = (await getQuestionOptions({ questionId: questions[i].id })).map(item => ({ id: item.id, title: item.title}))
questions[i].options = options
// questions[i].options = options
questions[i].options = questions[i].answerList
} }
list.value = questions list.value = questions
@ -87,7 +106,38 @@ const progress = computed(() => {
return Math.floor(answered.value / total.value * 100) return Math.floor(answered.value / total.value * 100)
}) })
const toNext = () => {
const toNext = async () => {
const data = []
uni.showLoading({
title: '提交中...'
})
list.value.forEach(n => {
data.push(addBaseAnswer({
userId: userId.value,
questionId: n.id,
answerId : n.value,
}))
})
await Promise.all(data)
uni.hideLoading()
// list.value.forEach(n => {
// data.push({
// userId: userId.value,
// questionId: n.id,
// answerId : n.value,
// })
// })
// await addBaseAnswer({
// list : JSON.stringify(data)
// })
uni.navigateTo({ uni.navigateTo({
url: "/otherPages/authentication/examination/baseCompleted" url: "/otherPages/authentication/examination/baseCompleted"
}) })


+ 147
- 7
otherPages/authentication/examination/end.vue View File

@ -114,19 +114,31 @@
<view class="info color-777 size-22 mt20"> <view class="info color-777 size-22 mt20">
<view class="flex-between"> <view class="flex-between">
<text class="size-22">姓名:</text> <text class="size-22">姓名:</text>
<input v-model="form.name" type="text" placeholder="请输入姓名" />
<input v-model="form.name"
type="text"
placeholder="请输入姓名"
:class="{ 'input-error': formErrors.name }"
@input="clearFieldError('name')" />
</view> </view>
</view> </view>
<view class="info color-777 size-22 mt20"> <view class="info color-777 size-22 mt20">
<view class="flex-between"> <view class="flex-between">
<text class="size-22">电话:</text> <text class="size-22">电话:</text>
<input v-model="form.phone" type="text" placeholder="请输入道路、小区、门牌号等" />
<input v-model="form.phone"
type="text"
placeholder="请输入手机号码"
:class="{ 'input-error': formErrors.phone }"
@input="clearFieldError('phone')" />
</view> </view>
</view> </view>
<view class="info color-777 size-22 mt20"> <view class="info color-777 size-22 mt20">
<view class="flex-between"> <view class="flex-between">
<text class="size-22">地址:</text> <text class="size-22">地址:</text>
<input v-model="form.address" type="text" placeholder="请输入收货地址" />
<input v-model="form.address"
type="text"
placeholder="请输入收货地址"
:class="{ 'input-error': formErrors.address }"
@input="clearFieldError('address')" />
</view> </view>
</view> </view>
</view> </view>
@ -170,6 +182,7 @@
udpateUser, udpateUser,
getUserOne getUserOne
} from '@/api/userTeacher' } from '@/api/userTeacher'
import { appletUsersTeacher } from '@/api/examination'
import stepProgress from '../components/stepProgress.vue'; import stepProgress from '../components/stepProgress.vue';
@ -192,8 +205,20 @@
latitude: null, latitude: null,
longitude: null, longitude: null,
address: null, address: null,
phone: null,
}) })
//
const formErrors = reactive({
name: false,
phone: false,
address: false
})
//
const clearFieldError = (field) => {
formErrors[field] = false
}
const initData = async () => { const initData = async () => {
console.log('--initData') console.log('--initData')
@ -257,8 +282,101 @@
}) })
} }
const validateForm = () => {
const { name, phone, address } = form
let hasError = false
//
Object.keys(formErrors).forEach(key => {
formErrors[key] = false
})
//
if (!name || name.trim() === '') {
formErrors.name = true
hasError = true
uni.showToast({
title: '请输入姓名',
icon: 'none'
})
return false
}
if (!phone || phone.trim() === '') {
formErrors.phone = true
hasError = true
uni.showToast({
title: '请输入电话号码',
icon: 'none'
})
return false
}
if (!address || address.trim() === '') {
formErrors.address = true
hasError = true
uni.showToast({
title: '请输入收货地址',
icon: 'none'
})
return false
}
//
if (name.trim().length < 2 || name.trim().length > 10) {
formErrors.name = true
hasError = true
uni.showToast({
title: '姓名长度应在2-10个字符之间',
icon: 'none'
})
return false
}
//
const phoneRegex = /^1[3-9]\d{9}$/
if (!phoneRegex.test(phone.trim())) {
formErrors.phone = true
hasError = true
uni.showToast({
title: '请输入正确的手机号码',
icon: 'none'
})
return false
}
//
if (address.trim().length < 5 || address.trim().length > 100) {
formErrors.address = true
hasError = true
uni.showToast({
title: '地址长度应在5-100个字符之间',
icon: 'none'
})
return false
}
//
const nameRegex = /^[\u4e00-\u9fa5a-zA-Z\s]+$/
if (!nameRegex.test(name.trim())) {
formErrors.name = true
hasError = true
uni.showToast({
title: '姓名只能包含中文和英文字母',
icon: 'none'
})
return false
}
return true
}
const onSave = async () => { const onSave = async () => {
try { try {
//
if (!validateForm()) {
return
}
const { const {
area, area,
@ -274,13 +392,20 @@
area, area,
latitude, latitude,
longitude, longitude,
address,
name,
phone,
address: address.trim(),
name: name.trim(),
phone: phone.trim(),
} }
await udpateUser(data) await udpateUser(data)
//
await appletUsersTeacher({
userId : userId.value
})
store.dispatch('getUserInfo')
uni.showToast({ uni.showToast({
title: '提交成功!', title: '提交成功!',
icon: "none" icon: "none"
@ -293,7 +418,11 @@
}, 1000) }, 1000)
} catch (err) { } catch (err) {
uni.showToast({
title: '提交失败,请重试',
icon: 'none'
})
console.error('提交失败:', err)
} }
} }
@ -478,4 +607,15 @@
.highlight { .highlight {
color: #FFBF60; color: #FFBF60;
} }
//
.input-error {
border: 1px solid #ff4757 !important;
background-color: #fff5f5 !important;
color: #ff4757 !important;
}
.input-error::placeholder {
color: #ff9999 !important;
}
</style> </style>

+ 17
- 7
otherPages/authentication/examination/errorDetail.vue View File

@ -34,9 +34,9 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'
import { ref, computed } from 'vue'
import { onShow } from '@dcloudio/uni-app' import { onShow } from '@dcloudio/uni-app'
import { getQuestionList, getQuestionOptions, answeBaseByQuestionId, answeTrainByQuestionId } from '@/api/examination'
import { getQuestionList, getQuestionOptions, answeBaseByQuestionId, answeTrainByQuestionId, retakeExam } from '@/api/examination'
import { useStore } from 'vuex' import { useStore } from 'vuex'
import questionCard from '../components/questionCard.vue'; import questionCard from '../components/questionCard.vue';
@ -46,6 +46,9 @@
const configList = computed(() => { const configList = computed(() => {
return store.getters.configList return store.getters.configList
}) })
const userInfo = computed(() => {
return store.state.user.userInfo
})
const list = ref([ const list = ref([
{ {
@ -124,12 +127,19 @@
}) })
const onClick = () => { const onClick = () => {
uni.reLaunch({
url: "/otherPages/authentication/examination/start"
// todo: check
// url: "/otherPages/authentication/list/index"
})
retakeExam({
userId : userInfo.value.userId
}).then(res => {
uni.reLaunch({
url: "/otherPages/authentication/examination/start"
// todo: check
// url: "/otherPages/authentication/list/index"
})
})
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>


+ 26
- 1
otherPages/authentication/examination/train.vue View File

@ -42,6 +42,18 @@
import { import {
getQuestionList getQuestionList
} from '@/api/examination' } from '@/api/examination'
import {
addBaseAnswer,
addTrainAnswer
} from '@/api/examination'
import {
store
} from '@/store'
const userId = computed(() => {
return store.state.user.userInfo.userId
})
import questionCard from '../components/questionCard.vue'; import questionCard from '../components/questionCard.vue';
@ -92,7 +104,20 @@
return Math.floor(answered.value / total.value * 100) return Math.floor(answered.value / total.value * 100)
}) })
const toNext = () => {
const toNext = async () => {
const data = []
list.value.forEach(n => {
data.push(addTrainAnswer({
userId: userId.value,
questionId: n.id,
answer : n.value,
}))
})
await Promise.all(data)
uni.navigateTo({ uni.navigateTo({
url: "/otherPages/authentication/examination/trainCompleted/index" url: "/otherPages/authentication/examination/trainCompleted/index"
}) })


+ 1
- 1
otherPages/orderTakingManage/detail/index.vue View File

@ -326,7 +326,7 @@
// //
const petInfo = () => { const petInfo = () => {
uni.navigateTo({ uni.navigateTo({
url: "/otherPages/orderTakingManage/pet/index"
url: "/otherPages/orderTakingManage/pet/index?id=" + orderId.value
}) })
} }


+ 277
- 0
pages/myOrdersManage/components/petServicePopup - 副本.vue View File

@ -0,0 +1,277 @@
<template>
<up-popup :show="show" mode="bottom" @close="close" :round="10" :closeable="true">
<view class="popup-container">
<view class="popup-title">宠物服务时间列表</view>
<view class="pet-list">
<!-- 宠物列表循环 -->
<view class="pet-item" v-for="(pet, petIndex) in petList" :key="pet.id">
<view class="pet-info">
<up-image class="pet-avatar" width="50px" height="50px" :src="pet.photo" shape="circle"></up-image>
<view class="pet-details">
<view class="pet-name">{{ pet.name }}</view>
<view class="pet-breed">{{ pet.breed }} {{ pet.bodyType }}</view>
</view>
</view>
<!-- 该宠物的服务时间列表 -->
<view class="service-list">
<view class="service-item" v-for="(service, index) in pet.services" :key="index">
<view class="service-header">
<view class="service-date">{{ service.serviceDate }}</view>
<view class="service-time">{{ getServiceTimeText(service.expectServiceTime) }}</view>
</view>
<view class="service-products">
<view class="product-item" v-for="(product, pIndex) in service.products" :key="pIndex">
<view class="product-name">{{ product.productName }}</view>
<!-- <view class="product-price">{{ product.salePrice }}</view> -->
</view>
</view>
<view class="service-footer">
<up-button
type="primary"
text="查看记录"
@click="handleClock(service)"
shape="circle"
size="mini"
style="height: 60rpx;"
color="#FFAA48"></up-button>
</view>
</view>
</view>
</view>
</view>
</view>
</up-popup>
</template>
<script setup>
import { ref, defineProps, defineEmits } from 'vue';
const props = defineProps({
show: {
type: Boolean,
default: false
},
orderData: {
type: Object,
default: () => ({})
}
});
const emit = defineEmits(['close']);
const petList = ref([]);
//
const close = () => {
emit('close');
};
//
const getServiceTimeText = (timeCode) => {
const timeMap = {
'MORNING': '早上',
'AFTERNOON': '下午',
'EVENING': '晚上'
};
return timeMap[timeCode] || '未指定';
};
//
const handleClock = (service) => {
if (!props.orderData || !service) return;
let url = `/otherPages/myOrdersManage/clock/index?id=${props.orderData.orderId}&itemID=${props.orderData.id}&serviceId=${service.id}&isRead=true`
if(service){
}
uni.navigateTo({
url,
});
};
//
const processOrderData = (orderData) => {
if (!orderData || !orderData.h5OrderVO) return [];
const { orderServiceList, orderItemList, petVOList } = orderData.h5OrderVO;
if (!orderServiceList || !orderItemList || !petVOList) return [];
// ID
const petMap = {};
//
petVOList.forEach(pet => {
petMap[pet.id] = {
...pet,
services: []
};
});
// ID
const serviceMap = {};
// ID
orderServiceList.forEach(service => {
const serviceId = service.id;
const petId = service.petId;
const serviceDate = service.serviceDate;
const expectServiceTime = service.expectServiceTime;
if (!serviceMap[serviceId]) {
serviceMap[serviceId] = {
id: serviceId,
petId,
serviceDate,
expectServiceTime,
products: []
};
}
});
//
orderItemList.forEach(item => {
const serviceId = item.orderServiceId;
if (serviceMap[serviceId]) {
serviceMap[serviceId].products.push({
productName: item.productName,
salePrice: item.salePrice,
pic: item.pic,
quantity: item.quantity,
spData: item.spData ? JSON.parse(item.spData) : {}
});
}
});
//
Object.values(serviceMap).forEach(service => {
if (petMap[service.petId]) {
petMap[service.petId].services.push(service);
}
});
//
const result = Object.values(petMap);
result.forEach(pet => {
pet.services.sort((a, b) => new Date(a.serviceDate) - new Date(b.serviceDate));
});
return result;
};
//
const updateServiceList = () => {
if (props.orderData && props.orderData.h5OrderVO) {
petList.value = processOrderData(props.orderData);
}
};
//
defineExpose({
updateServiceList
});
</script>
<style scoped lang="scss">
.popup-container {
padding: 30rpx;
max-height: 70vh;
overflow-y: auto;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
text-align: center;
margin-bottom: 30rpx;
}
.pet-list {
.pet-item {
margin-bottom: 40rpx;
.pet-info {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.pet-avatar {
flex-shrink: 0;
margin-right: 20rpx;
}
.pet-details {
.pet-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.pet-breed {
font-size: 24rpx;
color: #666;
margin-top: 6rpx;
}
}
}
}
}
.service-list {
.service-item {
margin-bottom: 20rpx;
background-color: #f8f8f8;
border-radius: 12rpx;
padding: 20rpx;
.service-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 10rpx;
border-bottom: 1px solid #eee;
.service-date {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.service-time {
font-size: 24rpx;
color: #666;
background-color: #f0f0f0;
padding: 4rpx 12rpx;
border-radius: 20rpx;
}
}
.service-products {
margin-bottom: 20rpx;
.product-item {
display: flex;
justify-content: space-between;
padding: 10rpx 0;
.product-name {
font-size: 26rpx;
color: #666;
}
.product-price {
font-size: 26rpx;
color: #FF530A;
}
}
}
.service-footer {
display: flex;
justify-content: flex-end;
}
}
}
</style>

+ 20
- 205
pages/myOrdersManage/components/petServicePopup.vue View File

@ -1,44 +1,16 @@
<template> <template>
<up-popup :show="show" mode="bottom" @close="close" :round="10" :closeable="true"> <up-popup :show="show" mode="bottom" @close="close" :round="10" :closeable="true">
<view class="popup-container"> <view class="popup-container">
<view class="popup-title">宠物服务时间列表</view>
<view class="popup-title">服务列表</view>
<view class="pet-list"> <view class="pet-list">
<!-- 宠物列表循环 -->
<view class="pet-item" v-for="(pet, petIndex) in petList" :key="pet.id">
<view class="pet-info">
<up-image class="pet-avatar" width="50px" height="50px" :src="pet.photo" shape="circle"></up-image>
<view class="pet-details">
<view class="pet-name">{{ pet.name }}</view>
<view class="pet-breed">{{ pet.breed }} {{ pet.bodyType }}</view>
</view>
</view>
<!-- 该宠物的服务时间列表 -->
<view class="service-list">
<view class="service-item" v-for="(service, index) in pet.services" :key="index">
<view class="service-header">
<view class="service-date">{{ service.serviceDate }}</view>
<view class="service-time">{{ getServiceTimeText(service.expectServiceTime) }}</view>
</view>
<view class="service-products">
<view class="product-item" v-for="(product, pIndex) in service.products" :key="pIndex">
<view class="product-name">{{ product.productName }}</view>
<!-- <view class="product-price">{{ product.salePrice }}</view> -->
</view>
</view>
<view class="service-footer">
<up-button
type="primary"
text="查看记录"
@click="handleClock(service)"
shape="circle"
size="mini"
style="height: 60rpx;"
color="#FFAA48"></up-button>
</view>
</view>
</view>
</view>
<timelineService
v-for="(item,index) in orderData"
:key="index"
:date="item.date"
:status="currentStatus(item.list)"
:list="item.list"
:serviceBtn="false"
/>
</view> </view>
</view> </view>
</up-popup> </up-popup>
@ -46,6 +18,7 @@
<script setup> <script setup>
import { ref, defineProps, defineEmits } from 'vue'; import { ref, defineProps, defineEmits } from 'vue';
import timelineService from "./timelineService.vue";
const props = defineProps({ const props = defineProps({
show: { show: {
@ -76,94 +49,20 @@ const getServiceTimeText = (timeCode) => {
return timeMap[timeCode] || '未指定'; return timeMap[timeCode] || '未指定';
}; };
//
const handleClock = (service) => {
if (!props.orderData || !service) return;
let url = `/otherPages/myOrdersManage/clock/index?id=${props.orderData.orderId}&itemID=${props.orderData.id}&serviceId=${service.id}&isRead=true`
if(service){
}
uni.navigateTo({
url,
});
};
//
const processOrderData = (orderData) => {
if (!orderData || !orderData.h5OrderVO) return [];
const { orderServiceList, orderItemList, petVOList } = orderData.h5OrderVO;
if (!orderServiceList || !orderItemList || !petVOList) return [];
// ID
const petMap = {};
//
petVOList.forEach(pet => {
petMap[pet.id] = {
...pet,
services: []
};
});
// ID
const serviceMap = {};
// ID
orderServiceList.forEach(service => {
const serviceId = service.id;
const petId = service.petId;
const serviceDate = service.serviceDate;
const expectServiceTime = service.expectServiceTime;
if (!serviceMap[serviceId]) {
serviceMap[serviceId] = {
id: serviceId,
petId,
serviceDate,
expectServiceTime,
products: []
};
}
});
//
orderItemList.forEach(item => {
const serviceId = item.orderServiceId;
if (serviceMap[serviceId]) {
serviceMap[serviceId].products.push({
productName: item.productName,
salePrice: item.salePrice,
pic: item.pic,
quantity: item.quantity,
spData: item.spData ? JSON.parse(item.spData) : {}
});
}
});
//
Object.values(serviceMap).forEach(service => {
if (petMap[service.petId]) {
petMap[service.petId].services.push(service);
const currentStatus = list => {
for (var index = 0; index < list.length; index++) {
var element = list[index];
console.log(element);
if(element.status != '2'){//
return true
} }
});
//
const result = Object.values(petMap);
result.forEach(pet => {
pet.services.sort((a, b) => new Date(a.serviceDate) - new Date(b.serviceDate));
});
return result;
};
}
return false
}
// //
const updateServiceList = () => { const updateServiceList = () => {
if (props.orderData && props.orderData.h5OrderVO) { if (props.orderData && props.orderData.h5OrderVO) {
petList.value = processOrderData(props.orderData);
} }
}; };
@ -188,90 +87,6 @@ defineExpose({
} }
.pet-list { .pet-list {
.pet-item {
margin-bottom: 40rpx;
.pet-info {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.pet-avatar {
flex-shrink: 0;
margin-right: 20rpx;
}
.pet-details {
.pet-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.pet-breed {
font-size: 24rpx;
color: #666;
margin-top: 6rpx;
}
}
}
}
}
.service-list {
.service-item {
margin-bottom: 20rpx;
background-color: #f8f8f8;
border-radius: 12rpx;
padding: 20rpx;
.service-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 10rpx;
border-bottom: 1px solid #eee;
.service-date {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.service-time {
font-size: 24rpx;
color: #666;
background-color: #f0f0f0;
padding: 4rpx 12rpx;
border-radius: 20rpx;
}
}
.service-products {
margin-bottom: 20rpx;
.product-item {
display: flex;
justify-content: space-between;
padding: 10rpx 0;
.product-name {
font-size: 26rpx;
color: #666;
}
.product-price {
font-size: 26rpx;
color: #FF530A;
}
}
}
.service-footer {
display: flex;
justify-content: flex-end;
}
}
} }
</style> </style>

+ 27
- 10
pages/myOrdersManage/components/systemOrder.vue View File

@ -44,11 +44,11 @@
<up-button type="primary" text="宠物档案" <up-button type="primary" text="宠物档案"
@click.stop="toPet(item)" shape="circle" class="mr20" @click.stop="toPet(item)" shape="circle" class="mr20"
color="#FFAA48"></up-button>
color="#FFAA48"></up-button>
<up-button type="primary" text="服务档案" <up-button type="primary" text="服务档案"
@click.stop="toService(item)" shape="circle" @click.stop="toService(item)" shape="circle"
color="#FFAA48"></up-button>
color="#FFAA48"></up-button>
</view> </view>
</view> </view>
</view> </view>
@ -63,8 +63,21 @@
<pet-service-popup :show="popupVisible" :orderData="currentOrder" @close="closeServicePopup" ref="servicePopupRef" /> <pet-service-popup :show="popupVisible" :orderData="currentOrder" @close="closeServicePopup" ref="servicePopupRef" />
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from 'vue';
import { onMounted, ref, computed } from 'vue';
import PetServicePopup from './petServicePopup.vue'; import PetServicePopup from './petServicePopup.vue';
import {
appletOrderDateFrequencyList,
} from "@/api/order/frequency.js"
import {
useStore
} from "vuex"
const store = useStore();
const userInfo = computed(() => {
return store.getters.userInfo;
})
const scrolltolower = () => { const scrolltolower = () => {
@ -88,7 +101,7 @@ import PetServicePopup from './petServicePopup.vue';
// //
const popupVisible = ref(false); const popupVisible = ref(false);
const currentOrder = ref(null);
const currentOrder = ref([]);
const servicePopupRef = ref(null); const servicePopupRef = ref(null);
@ -126,16 +139,20 @@ import PetServicePopup from './petServicePopup.vue';
// return toClock(item); // return toClock(item);
// } // }
//
currentOrder.value = item;
popupVisible.value = true;
//
setTimeout(() => {
appletOrderDateFrequencyList({
masterId: userInfo.value.userId,
orderId : item.orderId,
}).then(res => {
//
currentOrder.value = res.data;
popupVisible.value = true;
if (servicePopupRef.value) { if (servicePopupRef.value) {
servicePopupRef.value.updateServiceList(); servicePopupRef.value.updateServiceList();
} }
}, 100);
})
} }
// //


+ 17
- 5
pages/myOrdersManage/components/timelineService.vue View File

@ -97,9 +97,16 @@
<!-- 操作按钮 --> <!-- 操作按钮 -->
<view class="action-buttons"> <view class="action-buttons">
<view class="btn btn-clock" :class="{bgSuccess : item.status == '2'}" @click="handleClock(item)">{{ '打卡' }}</view>
<view class="btn btn-clock" :class="{bgSuccess : item.status == '2'}" @click="handlePetFile(item)">宠物档案</view>
<view class="btn btn-clock" :class="{bgSuccess : item.status == '2'}" @click="handleServiceFile(item)">服务档案</view>
<view class="btn btn-clock" :class="{bgSuccess : item.status == '2'}"
@click="handleClock(item)">{{ item.status == '2' ? '查看记录' : '打卡' }}</view>
<view class="btn btn-clock" :class="{bgSuccess : item.status == '2'}"
v-if="serviceBtn"
@click="handlePetFile(item)">宠物档案</view>
<view class="btn btn-clock" :class="{bgSuccess : item.status == '2'}"
v-if="serviceBtn"
@click="handleServiceFile(item)">服务档案</view>
</view> </view>
</view> </view>
</view> </view>
@ -133,7 +140,11 @@
list: { list: {
type: Array, type: Array,
default: () => [] default: () => []
}
},
serviceBtn : {
default : true,
type : Boolean,
},
}); });
// - 使 // - 使
@ -549,14 +560,15 @@
.action-buttons { .action-buttons {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
gap: 20rpx;
.btn { .btn {
width: 30%;
height: 80rpx; height: 80rpx;
line-height: 80rpx; line-height: 80rpx;
text-align: center; text-align: center;
border-radius: 40rpx; border-radius: 40rpx;
font-size: 28rpx; font-size: 28rpx;
flex: 1;
} }
.btn-clock { .btn-clock {


+ 66
- 32
pages/workbenchManage/index.vue View File

@ -1,15 +1,9 @@
<template> <template>
<view> <view>
<view class="swiper-container"> <view class="swiper-container">
<swiper
:indicator-dots="true"
:autoplay="true"
:interval="3000"
:duration="1000"
indicatorActiveColor="#FFAA48"
:indicatorStyle="{bottom: '150rpx'}"
indicatorInactiveColor="#fff"
style="height: 412rpx;">
<swiper :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000"
indicatorActiveColor="#FFAA48" :indicatorStyle="{bottom: '150rpx'}" indicatorInactiveColor="#fff"
style="height: 412rpx;">
<swiper-item class="w-100 h-100" v-for="item in state.banner" :key="item.id"> <swiper-item class="w-100 h-100" v-for="item in state.banner" :key="item.id">
<image class="w-100 h-100" :src="item.image" mode=""></image> <image class="w-100 h-100" :src="item.image" mode=""></image>
</swiper-item> </swiper-item>
@ -23,9 +17,8 @@
<view class="explain"> <view class="explain">
<view class="explain-text"> <view class="explain-text">
<text>{{ configList?.partner_work_statement?.paramValueText }}</text> <text>{{ configList?.partner_work_statement?.paramValueText }}</text>
<view class="font24 add-but col-white join"
style="background-color: #999;color: #fff;"
@click="">暂未开放 ></view>
<view class="font24 add-but col-white join" style="background-color: #999;color: #fff;"
@click="">暂未开放 ></view>
<!-- <view class="font24 add-but col-white join" @click="handleJoin(1)">申请加入 ></view> --> <!-- <view class="font24 add-but col-white join" @click="handleJoin(1)">申请加入 ></view> -->
</view> </view>
<image class="explain-img" :src="configList?.partner_work_statement?.paramValueImage" <image class="explain-img" :src="configList?.partner_work_statement?.paramValueImage"
@ -49,7 +42,7 @@
<view class="explain"> <view class="explain">
<view class="explain-text"> <view class="explain-text">
<text>{{ configList?.pet_teacher_statement?.paramValueText }}</text> <text>{{ configList?.pet_teacher_statement?.paramValueText }}</text>
<view class="font24 add-but col-white join" @click="handleJoin(2)"
<view class="font24 add-but col-white join" @click="handleBc"
style="background: #FF7935">申请加入 ></view> style="background: #FF7935">申请加入 ></view>
</view> </view>
<image class="explain-img" :src="configList?.pet_teacher_statement?.paramValueImage" <image class="explain-img" :src="configList?.pet_teacher_statement?.paramValueImage"
@ -57,13 +50,13 @@
</view> </view>
</view> </view>
<!-- 申请之后 --> <!-- 申请之后 -->
<view class="flex-rowl flex-wrap" v-if="userInfo.userBcs == 1" >
<view class="flex-rowl flex-wrap" v-if="userInfo.userBcs == 1">
<view class="icon-list" v-for="item in iconState.list2" :key="item.id" @click="handleGoto(item)"> <view class="icon-list" v-for="item in iconState.list2" :key="item.id" @click="handleGoto(item)">
<up-image class="mb20" :show-loading="true" :src="item.image" width="68rpx" <up-image class="mb20" :show-loading="true" :src="item.image" width="68rpx"
height="68rpx"></up-image> height="68rpx"></up-image>
<view>{{ item.name }}</view> <view>{{ item.name }}</view>
</view> </view>
<!-- <view class="icon-list"@click="handleGoto1()"> <!-- <view class="icon-list"@click="handleGoto1()">
<up-image class="mb20" :show-loading="true" width="68rpx" <up-image class="mb20" :show-loading="true" width="68rpx"
height="68rpx"></up-image> height="68rpx"></up-image>
@ -105,8 +98,14 @@
useStore useStore
} from "vuex" } from "vuex"
import configPopup from '@/components/configPopup.vue' import configPopup from '@/components/configPopup.vue'
import { answeBaseIsFinish, answeTrainIsFinish } from '@/api/examination'
import { code } from "../../uni_modules/uview-plus/libs/function/test";
import {
answeBaseIsFinish,
answeTrainIsFinish
} from '@/api/examination'
import { insertUser, udpateUser, getUserOne } from '@/api/userTeacher'
import {
code
} from "../../uni_modules/uview-plus/libs/function/test";
const configPopupRef = ref(null) const configPopupRef = ref(null)
const store = useStore(); const store = useStore();
@ -118,12 +117,12 @@ import { code } from "../../uni_modules/uview-plus/libs/function/test";
getpz() getpz()
}) })
const userInfo = computed(() => { const userInfo = computed(() => {
return store.getters.userInfo
return store.state.user.userInfo
}) })
const configList = computed(() => { const configList = computed(() => {
return store.getters.configList return store.getters.configList
}) })
// //
const getBanner = async () => { const getBanner = async () => {
const res = await banner() const res = await banner()
@ -197,13 +196,13 @@ import { code } from "../../uni_modules/uview-plus/libs/function/test";
const show = ref(true) const show = ref(true)
const hhShow = ref(true) const hhShow = ref(true)
const handleGoto = (item) => { const handleGoto = (item) => {
if(item.name == '平台手册'){
if (item.name == '平台手册') {
// pet_platform_introduction // pet_platform_introduction
return configPopupRef.value.open('pet_platform_introduction') return configPopupRef.value.open('pet_platform_introduction')
} }
if(item.url) {
if (item.url) {
uni.navigateTo({ uni.navigateTo({
url: item.url url: item.url
}) })
@ -230,35 +229,70 @@ import { code } from "../../uni_modules/uview-plus/libs/function/test";
show.value = false show.value = false
} }
const handleBc = async () => { const handleBc = async () => {
if(!userInfo.value || !userInfo.value.userId){
if (!userInfo.value || !userInfo.value.userId) {
uni.navigateTo({ uni.navigateTo({
url: "/otherPages/authentication/list/index" url: "/otherPages/authentication/list/index"
}) })
return return
} }
uni.showLoading({ uni.showLoading({
title: '加载中...' title: '加载中...'
}) })
try {
const data = await getUserOne(userInfo.value.userId)
if(!data || !data.id){
uni.navigateTo({
url: "/otherPages/authentication/list/index"
})
return
}
try{
let code1 = await answeBaseIsFinish({userId : userInfo.value.userId})
let code2 = await answeTrainIsFinish({userId : userInfo.value.userId})
if(code1 && code2){
if ([1, 2].includes(data.status)) { // status: 0- 1- 2-
uni.navigateTo({
url: `/otherPages/authentication/examination/trainCompleted/index?status=${data.status}`
})
return
}
let code1 = await answeBaseIsFinish({
userId: userInfo.value.userId
})
let code2 = await answeTrainIsFinish({
userId: userInfo.value.userId
})
if (code1 && code2) {
uni.navigateTo({ uni.navigateTo({
url: `/otherPages/authentication/examination/trainCompleted/index?status=0` url: `/otherPages/authentication/examination/trainCompleted/index?status=0`
}) })
} else if(code1){
uni.navigateTo({
url: "/otherPages/authentication/examination/train"
})
}else{ }else{
uni.navigateTo({ uni.navigateTo({
url: "/otherPages/authentication/list/index"
url: "/otherPages/authentication/examination/base"
}) })
} }
}catch(e){
} catch (e) {
console.log(e);
uni.navigateTo({ uni.navigateTo({
url: "/otherPages/authentication/list/index" url: "/otherPages/authentication/list/index"
}) })
}finally{
} finally {
uni.hideLoading() uni.hideLoading()
} }
} }


Loading…
Cancel
Save