Browse Source

feat: 新增服务记录功能及相关页面

refactor: 优化订单详情和宠物信息展示逻辑

fix: 修复订单状态判断和按钮显示条件

docs: 添加数据库事务查询SQL文件

style: 统一时间线组件样式和布局

test: 更新测试用例以适应新功能
master
前端-胡立永 1 week ago
parent
commit
e00dddf708
11 changed files with 1839 additions and 105 deletions
  1. +21
    -1
      api/order/order.js
  2. +143
    -0
      database_transaction_queries.sql
  3. +9
    -0
      pages.json
  4. +6
    -2
      pages/companionPetList/companionPetInfo.vue
  5. +551
    -0
      pages/personalCenter/a.txt
  6. +285
    -88
      pages/personalCenter/orderDetailImage.vue
  7. +579
    -0
      pages_order/components/order/timelineService.vue
  8. +12
    -10
      pages_order/order/orderDetail.vue
  9. +11
    -4
      pages_order/order/orderList.vue
  10. +215
    -0
      pages_order/order/serviceRecord.vue
  11. +7
    -0
      uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue

+ 21
- 1
api/order/order.js View File

@ -145,8 +145,28 @@ export const orderCancel = (data) => {
})
}
// 查询服务记录列表
export const appletOrderDateFrequencyList = (params) => {
return request({
url: '/applet/appletOrderDateFrequency/list',
headers: {
isToken: true
},
method: "GET",
params
})
}
// 根据id查询服务记录详情
export const appletOrderDateFrequencyById = (id) => {
return request({
headers: {
"isToken": true
},
url: "/applet/appletOrderDateFrequency/" + id,
method: 'get',
})
}
// 再次支付订单
export const orderPay = (data) => {


+ 143
- 0
database_transaction_queries.sql View File

@ -0,0 +1,143 @@
-- MySQL 数据库事务状态检查和处理 SQL 语句
-- 1. 查看当前所有连接和进程
SHOW PROCESSLIST;
-- 2. 查看当前正在运行的事务
SELECT
trx_id,
trx_state,
trx_started,
trx_requested_lock_id,
trx_wait_started,
trx_weight,
trx_mysql_thread_id,
trx_query
FROM INFORMATION_SCHEMA.INNODB_TRX;
-- 3. 查看锁等待情况
SELECT
waiting_trx_id,
waiting_thread,
waiting_query,
blocking_trx_id,
blocking_thread,
blocking_query
FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
-- 4. 查看当前锁定状态
SELECT
lock_id,
lock_trx_id,
lock_mode,
lock_type,
lock_table,
lock_index,
lock_space,
lock_page,
lock_rec,
lock_data
FROM INFORMATION_SCHEMA.INNODB_LOCKS;
-- 5. 查看详细的锁等待信息(MySQL 8.0+)
SELECT
OBJECT_SCHEMA,
OBJECT_NAME,
LOCK_TYPE,
LOCK_MODE,
LOCK_STATUS,
LOCK_DATA,
ENGINE_TRANSACTION_ID,
THREAD_ID,
EVENT_ID
FROM performance_schema.data_locks;
-- 6. 查看锁等待关系(MySQL 8.0+)
SELECT
requesting_engine_transaction_id,
requesting_thread_id,
requesting_event_id,
blocking_engine_transaction_id,
blocking_thread_id,
blocking_event_id
FROM performance_schema.data_lock_waits;
-- 7. 查看长时间运行的事务(超过30秒)
SELECT
trx_id,
trx_state,
trx_started,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) as duration_seconds,
trx_mysql_thread_id,
trx_query
FROM INFORMATION_SCHEMA.INNODB_TRX
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 30;
-- 8. 查看阻塞其他事务的连接
SELECT
p1.ID as blocking_thread_id,
p1.USER as blocking_user,
p1.HOST as blocking_host,
p1.DB as blocking_db,
p1.COMMAND as blocking_command,
p1.TIME as blocking_time,
p1.STATE as blocking_state,
p1.INFO as blocking_query,
p2.ID as blocked_thread_id,
p2.USER as blocked_user,
p2.HOST as blocked_host,
p2.DB as blocked_db,
p2.COMMAND as blocked_command,
p2.TIME as blocked_time,
p2.STATE as blocked_state,
p2.INFO as blocked_query
FROM INFORMATION_SCHEMA.PROCESSLIST p1
JOIN INFORMATION_SCHEMA.INNODB_LOCK_WAITS w ON p1.ID = w.blocking_thread
JOIN INFORMATION_SCHEMA.PROCESSLIST p2 ON p2.ID = w.waiting_thread;
-- ===== 处理锁定事务的操作 =====
-- 9. 杀死指定的连接(替换 CONNECTION_ID 为实际的连接ID)
-- KILL CONNECTION_ID;
-- 示例:杀死连接ID为123的连接
-- KILL 123;
-- 10. 强制杀死指定连接(即使在事务中)
-- KILL CONNECTION CONNECTION_ID;
-- 11. 回滚指定事务(需要在对应连接中执行)
-- ROLLBACK;
-- 12. 设置锁等待超时(秒)
SET innodb_lock_wait_timeout = 50;
-- 13. 查看当前锁等待超时设置
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
-- ===== 预防措施 =====
-- 14. 查看死锁信息
SHOW ENGINE INNODB STATUS;
-- 15. 开启死锁检测
SET GLOBAL innodb_deadlock_detect = ON;
-- 16. 查看事务隔离级别
SELECT @@tx_isolation;
-- 17. 设置事务隔离级别(如果需要)
-- SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- ===== 使用步骤说明 =====
/*
1. 1-4
2. ID和连接ID
3. 使KILL命令终止问题连接
4.
- 使KILL命令
-
-
*/

+ 9
- 0
pages.json View File

@ -418,6 +418,15 @@
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "order/serviceRecord",
"style": {
"navigationBarTitleText": "服务记录",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": true,
"navigationBarTextStyle": "white"
}
}
]
}


+ 6
- 2
pages/companionPetList/companionPetInfo.vue View File

@ -12,9 +12,13 @@
<view class="companion-info-left">
<text class="companion-info-left-title">{{companionInfo.userName}}</text>
<view>
<img :src="companionInfo.appletUsersTeacher.sex==1?'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/sex_m.png':
<!-- <img :src="companionInfo.appletUsersTeacher.sex==1?'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/sex_m.png':
'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/sex_f.png'" alt="sex"
style="width: 40rpx;height: 40rpx;" />
style="width: 40rpx;height: 40rpx;" /> -->
<img :src="item.appletUsersTeacher.sex == 0?'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/sex_m.png':
'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/sex_f.png'" alt="sex"
style="width: 20px;height: 20px;" />
</view>
</view>
<view class="">


+ 551
- 0
pages/personalCenter/a.txt View File

@ -0,0 +1,551 @@
<template>
<!-- <view>打卡</view> -->
<view class="box box-size">
<view class="top box-size" :style="{borderRadius:'16rpx'}">
<view class="form-title">
个人准备
</view>
<view class="mt32 ml10">
<span :style="{fontSize:'30rpx',fontWeight:'400'}">手套照片</span>
<span :style="{fontSize:'26rpx',color:'#C7C7C7'}">(至少1张)</span>
</view>
<view class="level">
<view class="mt20 ml10">
<up-upload :fileList="fileList.glove" @afterRead="afterRead" @delete="deletePic" name="glove"
multiple :maxCount="2" width="131rpx" height="131rpx" :disabled="isRead">
<image src="/static/images/ydd/add_photo.png" mode="" :style="{width:'131rpx',height:'131rpx'}">
</image>
</up-upload>
</view>
</view>
<view class="mt32 ml10">
<span :style="{fontSize:'30rpx',fontWeight:'400'}">鞋套照片</span>
<span :style="{fontSize:'26rpx',color:'#C7C7C7'}">(至少1张)</span>
</view>
<view class="level">
<view class="mt20 ml10">
<up-upload :fileList="fileList.ShoeCover" @afterRead="afterRead" @delete="deletePic"
name="ShoeCover" multiple :maxCount="2" width="131rpx" height="131rpx" :disabled="isRead">
<image src="/static/images/ydd/add_photo.png" mode="" :style="{width:'131rpx',height:'131rpx'}">
</image>
</up-upload>
</view>
</view>
</view>
<view class="top mt24 box-size" :style="{borderRadius:'16rpx'}">
<view class="form-title">
宠物状态记录
</view>
<view v-for="(pet, index) in petList" :key="index">
<view class="mt32 ml10">
<span :style="{fontSize:'30rpx',fontWeight:'400'}">{{ pet.title }}照片</span>
<span :style="{fontSize:'26rpx',color:'#C7C7C7'}">(至少2张)</span>
</view>
<view class="level">
<view class="mt20 ml10 mr20">
<up-upload :fileList="fileList['pet' + index]" @afterRead="afterRead" @delete="deletePic"
:name="'pet' + index" multiple :maxCount="2" width="131rpx" height="131rpx" :disabled="isRead">
<image src="/static/images/ydd/add_photo.png" mode=""
:style="{width:'131rpx',height:'131rpx'}">
</image>
</up-upload>
</view>
</view>
</view>
</view>
<view class="top mt24 box-size" :style="{borderRadius:'16rpx'}">
<view class="form-title">
基础服务记录
</view>
<view class="mt32 ml10" :style="{fontSize:'30rpx',fontWeight:'400'}">粮碗前后对比 </view>
<view class="level">
<view class="mt20 ml10">
<up-upload :fileList="fileList.foodA" @afterRead="afterRead" @delete="deletePic" name="foodA"
multiple :maxCount="1" width="131rpx" height="131rpx" :disabled="isRead">
<image src="/static/images/ydd/add_photo.png" mode="" :style="{width:'131rpx',height:'131rpx'}">
</image>
</up-upload>
<view class="jus-center level" :style="{color:'#999999',fontSize:'22rpx',width:'131rpx'}">前</view>
</view>
<view class="mt20 ml10 ml28">
<up-upload :fileList="fileList.foodB" @afterRead="afterRead" @delete="deletePic" name="foodB"
multiple :maxCount="1" width="131rpx" height="131rpx" :disabled="isRead">
<image src="/static/images/ydd/add_photo.png" mode="" :style="{width:'131rpx',height:'131rpx'}">
</image>
</up-upload>
<view class="jus-center level" :style="{color:'#999999',fontSize:'22rpx',width:'131rpx'}">后</view>
</view>
</view>
<view class="mt32 ml10" :style="{fontSize:'30rpx',fontWeight:'400'}">水碗前后对比 </view>
<view class="level">
<view class="mt20 ml10">
<up-upload :fileList="fileList.waterA" @afterRead="afterRead" @delete="deletePic" name="waterA"
multiple :maxCount="1" width="131rpx" height="131rpx" :disabled="isRead">
<image src="/static/images/ydd/add_photo.png" mode="" :style="{width:'131rpx',height:'131rpx'}">
</image>
</up-upload>
<view class="jus-center level" :style="{color:'#999999',fontSize:'22rpx',width:'131rpx'}">前</view>
</view>
<view class="mt20 ml10 ml28">
<up-upload :fileList="fileList.waterB" @afterRead="afterRead" @delete="deletePic" name="waterB"
multiple :maxCount="1" width="131rpx" height="131rpx" :disabled="isRead">
<image src="/static/images/ydd/add_photo.png" mode="" :style="{width:'131rpx',height:'131rpx'}">
</image>
</up-upload>
<view class="jus-center level" :style="{color:'#999999',fontSize:'22rpx',width:'131rpx'}">后</view>
</view>
</view>
<view class="mt32 ml10" :style="{fontSize:'30rpx',fontWeight:'400'}">猫砂盆、尿垫前后对比 </view>
<view class="level">
<view class="mt20 ml10">
<up-upload :fileList="fileList.urinalA" @afterRead="afterRead" @delete="deletePic" name="urinalA"
multiple :maxCount="1" width="131rpx" height="131rpx" :disabled="isRead">
<image src="/static/images/ydd/add_photo.png" mode="" :style="{width:'131rpx',height:'131rpx'}">
</image>
</up-upload>
<view class="jus-center level" :style="{color:'#999999',fontSize:'22rpx',width:'131rpx'}">前</view>
</view>
<view class="mt20 ml10 ml28">
<up-upload :fileList="fileList.urinalB" @afterRead="afterRead" @delete="deletePic" name="urinalB"
multiple :maxCount="1" width="131rpx" height="131rpx" :disabled="isRead">
<image src="/static/images/ydd/add_photo.png" mode="" :style="{width:'131rpx',height:'131rpx'}">
</image>
</up-upload>
<view class="jus-center level" :style="{color:'#999999',fontSize:'22rpx',width:'131rpx'}">后</view>
</view>
</view>
</view>
<view class="top mt24 box-size" :style="{borderRadius:'16rpx'}">
<view class="form-title">
定制服务记录
</view>
<view v-for="(product, pindex) in projectList"
:key="pindex">
<view class="mt32 ml10">
<span :style="{fontSize:'30rpx',fontWeight:'400'}">{{ product.title }}</span>
<span :style="{fontSize:'26rpx',color:'#C7C7C7'}">(2-3张)</span>
</view>
<view class="level">
<view class="mt20 ml10 mr20">
<up-upload :fileList="fileList['project' + pindex]" @afterRead="afterRead" @delete="deletePic"
:name="'project' + pindex"
multiple :maxCount="3" width="131rpx" height="131rpx" :disabled="isRead">
<image src="/static/images/ydd/add_photo.png" mode="" :style="{width:'131rpx',height:'131rpx'}">
</image>
</up-upload>
</view>
</view>
</view>
<!-- <view class="mt32 ml10">
<span :style="{fontSize:'30rpx',fontWeight:'400'}">遛狗</span>
<span :style="{fontSize:'26rpx',color:'#C7C7C7'}">(2-3张)</span>
</view>
<view class="level">
<view class="mt20 ml10 mr20">
<up-upload :fileList="fileList.testa" @afterRead="afterRead" @delete="deletePic" name="testa"
multiple :maxCount="3" width="131rpx" height="131rpx" :disabled="isRead">
<image src="/static/images/ydd/add_photo.png" mode="" :style="{width:'131rpx',height:'131rpx'}">
</image>
</up-upload>
</view>
</view> -->
<!-- <view class="mt32 ml10">
<span :style="{fontSize:'30rpx',fontWeight:'400'}">陪玩</span>
<span :style="{fontSize:'26rpx',color:'#C7C7C7'}">(2-3张)</span>
</view>
<view class="level">
<view class="mt20 ml10 mr20">
<up-upload :fileList="fileList.testb" @afterRead="afterRead" @delete="deletePic" name="testb"
multiple :maxCount="3" width="131rpx" height="131rpx" :disabled="isRead">
<image src="/static/images/ydd/add_photo.png" mode="" :style="{width:'131rpx',height:'131rpx'}">
</image>
</up-upload>
</view>
</view> -->
</view>
<view class="top mt24 box-size" :style="{borderRadius:'16rpx'}">
<view class="form-title">
其他补充信息(必填)
</view>
<view class="mt32 ml10" :style="{color:'#999999',fontSize:'30rpx'}">
可记录一下今日趣事、宠物状况、提醒事项等,最少30个字
</view>
<view class="mt24">
<textarea cols="30" rows="10"
placeholder="请输入内容"
v-model="form.notes"
:style="{color:'#999999',fontSize:'30rpx',backgroundColor:'#F5F5F5',borderRadius:'16rpx',width:'681rpx',height:'180rpx'}"
class="pd20 box-size" :disabled="isRead"></textarea>
</view>
</view>
<view class="buttom mt60 box-size"
v-if="!isRead"
style="display: flex;gap: 20rpx;"
>
<view class="buttom-item buttom-item-2 level size-30"
@click="saveDraft(false)"
:style="{borderRadius:'41rpx',color:'#ff842c'}">
保存草稿
</view>
<view class="buttom-item level size-30"
@click="submit"
:style="{borderRadius:'41rpx',color:'#fff'}">
确定提交
</view>
</view>
</view>
</template>
<script setup>
import {
onMounted,
reactive,
ref,
} from "vue";
import {
onLoad
} from '@dcloudio/uni-app'
import {
ossUpload
} from '@/utils/oss-upload/oss/index.js'
import {
appletOrderDateFrequencyById,
appletOrderDateFrequencyCheck,
} from "@/api/order/frequency.js"
onLoad((options) => {
checkId.value = options.id || null;
GetByOrderId()
});
const checkId = ref(0)
const orderId = ref(0)
const itemOrderID = ref(0)
const serviceId = ref(0)
const isRead = ref(false)
const content = ref('')
const fileList = reactive({
// glove: [],//手套
// ShoeCover: [],//鞋套
// food: [],//粮碗
// water: [],//水碗
// urinal: [],//猫砂盆、尿盆
})
const form = ref({})
const petList = ref([
// {
// title: '小汪'
// },
// {
// title: '大黄'
// },
])
//附加项目
const projectList = ref([
// {
// title: '小汪'
// },
// {
// title: '大黄'
// },
])
// 删除图片
const deletePic = (event) => {
if (fileList[event.name]) {
fileList[event.name].splice(event.index, 1);
}
};
// 新增图片
const afterRead = (event) => {
if (!fileList[event.name]) {
fileList[event.name] = []
}
event.file.forEach(n => {
ossUpload(n.url)
.then(url => {
console.log(url);
fileList[event.name].push({
url
})
})
})
};
function submit() {
// 参数效验
if (!fileList.glove || fileList.glove.length == 0) {
return msg('请上传鞋套照片')
}
if (!fileList.ShoeCover || fileList.ShoeCover.length == 0) {
return msg('请上传鞋套照片')
}
for (let i = 0; i < petList.value.length; i++) {
if (!fileList['pet' + i] || fileList['pet' + i].length < 2) {
return msg(`请上传${petList.value[i].title}照片`)
}
}
if (!fileList.foodA || fileList.foodA.length < 1 ||
!fileList.foodB || fileList.foodB.length < 1) {
return msg('请上传粮碗前后照片')
}
if (!fileList.waterA || fileList.waterA.length < 1 ||
!fileList.waterB || fileList.waterB.length < 1) {
return msg('请上传水碗前后照片')
}
if (!fileList.urinalA || fileList.urinalA.length < 1 ||
!fileList.urinalB || fileList.urinalB.length < 1) {
return msg('请上传猫砂盆、尿盆照片')
}
for (let i = 0; i < projectList.value.length; i++) {
if (!fileList['project' + i] || fileList['project' + i].length < 2) {
return msg(`请上传${projectList.value[i].title}照片`)
}
}
if(!form.value.notes){
return msg('请填写补充信息')
}
if(form.value.notes.length < 30){
return msg('补充信息不少于30字')
}
saveDraft(true)
}
function GetByOrderId(){
appletOrderDateFrequencyById(checkId.value)
.then(res => {
const data = res.data.check;
const frequency = res.data.frequency;
isRead.value = frequency.status == 2
if(res.code == 200 && data) {
form.value = data
// 回显手套照片
if(data.glovePhoto) {
fileList.glove = data.glovePhoto.split(',').map(url => ({ url }));
}
// 回显鞋套照片
if(data.shoeCoverPhoto) {
fileList.ShoeCover = data.shoeCoverPhoto.split(',').map(url => ({ url }));
}
// 回显宠物照片
if(data.petPhoto) {
const pets = JSON.parse(data.petPhoto);
petList.value = pets;
pets.forEach((pet, index) => {
if(pet.fileList) {
fileList['pet' + index] = pet.fileList.split(',').map(url => ({ url }));
}
});
}
// 回显项目照片
if(data.workDogImage) {
const pList = JSON.parse(data.workDogImage);
projectList.value = pList;
pList.forEach((pet, index) => {
if(pet.fileList) {
fileList['project' + index] = pet.fileList.split(',').map(url => ({ url }));
}
});
}
// 回显粮碗照片
if(data.grainBowlFront) fileList.foodA = data.grainBowlFront.split(',').map(url => ({ url }));
if(data.grainBowlAfter) fileList.foodB = data.grainBowlAfter.split(',').map(url => ({ url }));
// 回显水碗照片
if(data.waterBowlFront) fileList.waterA = data.waterBowlFront.split(',').map(url => ({ url }));
if(data.waterBowlAfter) fileList.waterB = data.waterBowlAfter.split(',').map(url => ({ url }));
// 回显猫砂盆/尿垫照片
if(data.basinFront) fileList.urinalA = data.basinFront.split(',').map(url => ({ url }));
if(data.basinAfter) fileList.urinalB = data.basinAfter.split(',').map(url => ({ url }));
// 回显定制服务照片
// if(data.workDogImage) fileList.testa = data.workDogImage.split(',').map(url => ({ url }));
// if(data.workPalyImage) fileList.testb = data.workPalyImage.split(',').map(url => ({ url }));
}else{
// getOrderPetByIdFN()
let projectNameList = []
frequency.pets.forEach((n, i) => {
fileList['pet' + i] = []
n.orderItemList.forEach((item, inde) => {
fileList['project' + inde] = []
})
})
frequency.pets.forEach((n, i) => {
petList.value.push({
title : n.name,
id : n.id,
})
n.orderItemList.forEach((item, inde) => {
if(!projectNameList.includes(item.productName)){
projectNameList.push(item.productName)
projectList.value.push({
title : item.productName,
ids : [item.id]
})
}else{
projectList.value[projectNameList.indexOf(item.productName)].ids.push(item.id)
}
})
})
}
})
}
function getOrderPetByIdFN(){
getOrderPetById(orderId.value)
.then(res => {
if(res.code == 200){
res.data.forEach((n, i) => {
fileList['pet' + i] = []
})
res.data.forEach((n, i) => {
petList.value.push({
title : n.name,
id : n.id,
})
})
}
})
}
function saveDraft(flag) {
// 将所有图片数组转换为逗号分隔的字符串
const params = {
// orderId: orderId.value,
// itemOrderId : itemOrderID.value,
itemDateId : checkId.value,
glovePhoto: fileList.glove?.map(item => item.url).join(',') || '',
shoeCoverPhoto: fileList.ShoeCover?.map(item => item.url).join(',') || '',
// 合并所有宠物照片
petPhoto: JSON.stringify(
petList.value.map((pet, index) => {
return {
id : pet.id,
title : pet.title,
fileList: fileList['pet' + index]?.map(item => item.url).join(',') || ''
};
})
),
workDogImage: JSON.stringify(
projectList.value.map((pet, index) => {
return {
id : pet.id,
title : pet.title,
fileList: fileList['project' + index]?.map(item => item.url).join(',') || ''
};
})
),
// 粮碗照片
grainBowlFront: fileList.foodA?.map(item => item.url).join(',') || '',
grainBowlAfter: fileList.foodB?.map(item => item.url).join(',') || '',
// 水碗照片
waterBowlFront: fileList.waterA?.map(item => item.url).join(',') || '',
waterBowlAfter: fileList.waterB?.map(item => item.url).join(',') || '',
// 猫砂盆/尿垫照片
basinFront: fileList.urinalA?.map(item => item.url).join(',') || '',
basinAfter: fileList.urinalB?.map(item => item.url).join(',') || '',
// 定制服务照片
// workDogImage: fileList.testa?.map(item => item.url).join(',') || '',
// workPalyImage: fileList.testb?.map(item => item.url).join(',') || '',
// 备注信息
notes: form.value.notes || '',
submitFlag : 1,//草稿
}
if(form.value.id) {
params.id = form.value.id
}
if(flag) {
params.submitFlag = 2
}
appletOrderDateFrequencyCheck(params)
.then(res => {
if(res.code === 200) {
uni.showToast({
title: '提交成功',
icon: 'success'
})
// 如果是确认提交(非草稿),则返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
uni.showToast({
title: res.msg || '提交失败',
icon: 'none'
})
}
})
.catch(err => {
uni.showToast({
title: '提交失败',
icon: 'none'
})
})
}
function msg(content) {
uni.showToast({
title: content,
icon: 'none'
})
}
</script>
<style scoped lang="scss">
@import"index.scss"
</style>

+ 285
- 88
pages/personalCenter/orderDetailImage.vue View File

@ -8,34 +8,56 @@
<view class="order-date-text">{{ orderDate }}</view>
</view>
<!-- 个人信息区域 -->
<view class="order-section">
<view class="order-section-title">个人信息</view>
<!-- 个人准备 -->
<view class="order-section" v-if="fileList.glove || fileList.ShoeCover">
<view class="order-section-title">个人准备</view>
<view class="order-section-content">
<view class="order-image-item">
<view class="order-image-label">手套照片 (共2张)</view>
<!-- 手套照片 -->
<view class="order-image-item" v-if="fileList.glove && fileList.glove.length > 0">
<view class="order-image-label">手套照片 ({{ fileList.glove.length }})</view>
<view class="order-image-container">
<image class="order-image" src="/static/images/personal/used.png" mode="aspectFill" @click="previewImage('/static/images/personal/used.png')"></image>
<image
class="order-image"
v-for="(photo, index) in fileList.glove"
:key="index"
:src="photo.url"
mode="aspectFill"
@click="previewImage(fileList.glove.map(p => p.url), photo.url)">
</image>
</view>
</view>
<view class="order-image-item">
<view class="order-image-label">鞋套照片 (共2张)</view>
<!-- 鞋套照片 -->
<view class="order-image-item" v-if="fileList.ShoeCover && fileList.ShoeCover.length > 0">
<view class="order-image-label">鞋套照片 ({{ fileList.ShoeCover.length }})</view>
<view class="order-image-container">
<image class="order-image" src="/static/images/personal/invalid.png" mode="aspectFill" @click="previewImage('/static/images/personal/invalid.png')"></image>
<image
class="order-image"
v-for="(photo, index) in fileList.ShoeCover"
:key="index"
:src="photo.url"
mode="aspectFill"
@click="previewImage(fileList.ShoeCover.map(p => p.url), photo.url)">
</image>
</view>
</view>
</view>
</view>
<!-- 宠物状况记录 -->
<view class="order-section">
<view class="order-section" v-if="petList && petList.length > 0">
<view class="order-section-title">宠物状况记录</view>
<view class="order-section-content">
<view class="pet-record" v-for="(pet, index) in pets" :key="index">
<view class="pet-name">{{ pet.name }} ({{ pet.photoCount }})</view>
<view class="pet-images">
<view class="pet-image-container" v-for="(photo, photoIndex) in pet.photos" :key="photoIndex">
<image class="pet-image" :src="photo" mode="aspectFill" @click="previewImage(photo)"></image>
<view class="pet-record" v-for="(pet, index) in petList" :key="index">
<view class="pet-name">{{ pet.title }} ({{ getPetPhotoCount(index) }})</view>
<view class="pet-images" v-if="fileList['pet' + index] && fileList['pet' + index].length > 0">
<view class="pet-image-container" v-for="(photo, photoIndex) in fileList['pet' + index]" :key="photoIndex">
<image
class="pet-image"
:src="photo.url"
mode="aspectFill"
@click="previewImage(fileList['pet' + index].map(p => p.url), photo.url)">
</image>
</view>
</view>
</view>
@ -43,19 +65,84 @@
</view>
<!-- 基础服务记录 -->
<view class="order-section">
<view class="order-section" v-if="hasBasicServices">
<view class="order-section-title">基础服务记录</view>
<view class="order-section-content">
<view class="service-record" v-for="(service, index) in services" :key="index">
<view class="service-name">{{ service.name }}</view>
<!-- 粮碗前后对比 -->
<view class="service-record" v-if="fileList.foodA || fileList.foodB">
<view class="service-name">粮碗前后对比</view>
<view class="service-comparison">
<view class="service-before-after">
<view class="service-image-container" v-if="fileList.foodA && fileList.foodA.length > 0">
<image
class="service-image"
:src="fileList.foodA[0].url"
mode="aspectFill"
@click="previewImage([fileList.foodA[0].url], fileList.foodA[0].url)">
</image>
<view class="service-image-label"></view>
</view>
<view class="service-image-container" v-if="fileList.foodB && fileList.foodB.length > 0">
<image
class="service-image"
:src="fileList.foodB[0].url"
mode="aspectFill"
@click="previewImage([fileList.foodB[0].url], fileList.foodB[0].url)">
</image>
<view class="service-image-label"></view>
</view>
</view>
</view>
</view>
<!-- 水碗前后对比 -->
<view class="service-record" v-if="fileList.waterA || fileList.waterB">
<view class="service-name">水碗前后对比</view>
<view class="service-comparison">
<view class="service-before-after">
<view class="service-image-container">
<image class="service-image" :src="service.beforeImage" mode="aspectFill" @click="previewImage(service.beforeImage)"></image>
<view class="service-image-container" v-if="fileList.waterA && fileList.waterA.length > 0">
<image
class="service-image"
:src="fileList.waterA[0].url"
mode="aspectFill"
@click="previewImage([fileList.waterA[0].url], fileList.waterA[0].url)">
</image>
<view class="service-image-label"></view>
</view>
<view class="service-image-container">
<image class="service-image" :src="service.afterImage" mode="aspectFill" @click="previewImage(service.afterImage)"></image>
<view class="service-image-container" v-if="fileList.waterB && fileList.waterB.length > 0">
<image
class="service-image"
:src="fileList.waterB[0].url"
mode="aspectFill"
@click="previewImage([fileList.waterB[0].url], fileList.waterB[0].url)">
</image>
<view class="service-image-label"></view>
</view>
</view>
</view>
</view>
<!-- 猫砂盆尿垫前后对比 -->
<view class="service-record" v-if="fileList.urinalA || fileList.urinalB">
<view class="service-name">猫砂盆尿垫前后对比</view>
<view class="service-comparison">
<view class="service-before-after">
<view class="service-image-container" v-if="fileList.urinalA && fileList.urinalA.length > 0">
<image
class="service-image"
:src="fileList.urinalA[0].url"
mode="aspectFill"
@click="previewImage([fileList.urinalA[0].url], fileList.urinalA[0].url)">
</image>
<view class="service-image-label"></view>
</view>
<view class="service-image-container" v-if="fileList.urinalB && fileList.urinalB.length > 0">
<image
class="service-image"
:src="fileList.urinalB[0].url"
mode="aspectFill"
@click="previewImage([fileList.urinalB[0].url], fileList.urinalB[0].url)">
</image>
<view class="service-image-label"></view>
</view>
</view>
@ -65,101 +152,199 @@
</view>
<!-- 定制服务记录 -->
<view class="order-section">
<view class="order-section" v-if="projectList && projectList.length > 0">
<view class="order-section-title">定制服务记录</view>
<view class="order-section-content">
<view class="custom-service">
<view class="custom-service-item">
<view class="custom-service-name">遛狗 (5-30分钟)</view>
<view class="custom-service-images">
<image class="custom-service-image" src="/static/images/tabBar/dog.png" mode="aspectFill" @click="previewImage('/static/images/tabBar/dog.png')"></image>
<image class="custom-service-image" src="/static/images/tabBar/dog_.png" mode="aspectFill" @click="previewImage('/static/images/tabBar/dog_.png')"></image>
<image class="custom-service-image" src="/static/images/tabBar/home.png" mode="aspectFill" @click="previewImage('/static/images/tabBar/home.png')"></image>
</view>
</view>
<view class="custom-service-item">
<view class="custom-service-name">梳毛 (5-30分钟)</view>
<view class="custom-service-images">
<image class="custom-service-image" src="/static/images/tabBar/cat.png" mode="aspectFill" @click="previewImage('/static/images/tabBar/cat.png')"></image>
<image class="custom-service-image" src="/static/images/tabBar/cat_.png" mode="aspectFill" @click="previewImage('/static/images/tabBar/cat_.png')"></image>
<view class="custom-service-item" v-for="(project, index) in projectList" :key="index">
<view class="custom-service-name">{{ project.title }} ({{ getProjectPhotoCount(index) }})</view>
<view class="custom-service-images" v-if="fileList['project' + index] && fileList['project' + index].length > 0">
<image
class="custom-service-image"
v-for="(photo, photoIndex) in fileList['project' + index]"
:key="photoIndex"
:src="photo.url"
mode="aspectFill"
@click="previewImage(fileList['project' + index].map(p => p.url), photo.url)">
</image>
</view>
</view>
</view>
</view>
</view>
<!-- 补充信息 -->
<view class="order-section" v-if="form.notes">
<view class="order-section-title">补充信息</view>
<view class="order-section-content">
<view class="notes-content">{{ form.notes }}</view>
</view>
</view>
</view>
</template>
<script>
import { appletOrderDateFrequencyById } from '@/api/order/order.js'
export default {
data() {
return {
orderId: null,
orderDate: '2024年12月08日',
pets: [
{
name: '小汪',
photoCount: 2,
photos: [
'/static/images/tabBar/dog.png',
'/static/images/tabBar/dog_.png'
]
},
{
name: 'Billion',
photoCount: 2,
photos: [
'/static/images/tabBar/cat.png',
'/static/images/tabBar/cat_.png'
]
}
],
services: [
{
name: '猫粮前后对比',
beforeImage: '/static/images/personal/invalid.png',
afterImage: '/static/images/personal/used.png'
},
{
name: '水碗前后对比',
beforeImage: '/static/images/personal/invalid.png',
afterImage: '/static/images/personal/used.png'
},
{
name: '猫砂盆 厚度前后对比',
beforeImage: '/static/images/personal/invalid.png',
afterImage: '/static/images/personal/used.png'
}
]
id: null,
orderDate: '',
form: {},
fileList: {},
petList: [],
projectList: [],
isRead: false
}
},
computed: {
hasBasicServices() {
return (this.fileList.foodA && this.fileList.foodA.length > 0) ||
(this.fileList.foodB && this.fileList.foodB.length > 0) ||
(this.fileList.waterA && this.fileList.waterA.length > 0) ||
(this.fileList.waterB && this.fileList.waterB.length > 0) ||
(this.fileList.urinalA && this.fileList.urinalA.length > 0) ||
(this.fileList.urinalB && this.fileList.urinalB.length > 0);
}
},
onLoad(options) {
if (options.orderId) {
this.orderId = options.orderId;
// orderId
if (options.id) {
this.id = options.id;
this.loadOrderDetail();
}
},
methods: {
loadOrderDetail() {
// API
// API
/*
getOrderDetail(this.orderId).then(res => {
if (res && res.code === 200) {
this.orderDate = res.data.orderDate;
this.pets = res.data.pets;
this.services = res.data.services;
appletOrderDateFrequencyById(this.id)
.then(res => {
const data = res.data.check;
const frequency = res.data.frequency;
this.isRead = frequency.status == 2;
//
if (frequency.serviceDate) {
this.orderDate = this.formatDate(frequency.serviceDate);
}
if(res.code == 200 && data) {
this.form = data;
//
if(data.glovePhoto) {
this.$set(this.fileList, 'glove', data.glovePhoto.split(',').map(url => ({ url })));
}
//
if(data.shoeCoverPhoto) {
this.$set(this.fileList, 'ShoeCover', data.shoeCoverPhoto.split(',').map(url => ({ url })));
}
//
if(data.petPhoto) {
const pets = JSON.parse(data.petPhoto);
this.petList = pets;
pets.forEach((pet, index) => {
if(pet.fileList) {
this.$set(this.fileList, 'pet' + index, pet.fileList.split(',').map(url => ({ url })));
}
});
}
//
if(data.workDogImage) {
const pList = JSON.parse(data.workDogImage);
this.projectList = pList;
pList.forEach((project, index) => {
if(project.fileList) {
this.$set(this.fileList, 'project' + index, project.fileList.split(',').map(url => ({ url })));
}
});
}
//
if(data.grainBowlFront) this.$set(this.fileList, 'foodA', data.grainBowlFront.split(',').map(url => ({ url })));
if(data.grainBowlAfter) this.$set(this.fileList, 'foodB', data.grainBowlAfter.split(',').map(url => ({ url })));
//
if(data.waterBowlFront) this.$set(this.fileList, 'waterA', data.waterBowlFront.split(',').map(url => ({ url })));
if(data.waterBowlAfter) this.$set(this.fileList, 'waterB', data.waterBowlAfter.split(',').map(url => ({ url })));
// /尿
if(data.basinFront) this.$set(this.fileList, 'urinalA', data.basinFront.split(',').map(url => ({ url })));
if(data.basinAfter) this.$set(this.fileList, 'urinalB', data.basinAfter.split(',').map(url => ({ url })));
} else {
// frequency
let projectNameList = [];
frequency.pets.forEach((pet, i) => {
this.$set(this.fileList, 'pet' + i, []);
pet.orderItemList.forEach((item, index) => {
this.$set(this.fileList, 'project' + index, []);
});
});
frequency.pets.forEach((pet, i) => {
this.petList.push({
title: pet.name,
id: pet.id,
});
pet.orderItemList.forEach((item, index) => {
if(!projectNameList.includes(item.productName)){
projectNameList.push(item.productName);
this.projectList.push({
title: item.productName,
ids: [item.id]
});
} else {
this.projectList[projectNameList.indexOf(item.productName)].ids.push(item.id);
}
});
});
}
})
.catch(err => {
console.error('加载订单详情失败:', err);
uni.showToast({
title: '加载失败',
icon: 'none'
});
});
*/
},
//
getPetPhotoCount(index) {
const photos = this.fileList['pet' + index];
return photos ? photos.length : 0;
},
//
getProjectPhotoCount(index) {
const photos = this.fileList['project' + index];
return photos ? photos.length : 0;
},
//
formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}${month}${day}`;
},
//
previewImage(urls, current) {
// uni-appAPI
if (!urls || urls.length === 0) return;
uni.previewImage({
urls,
current,
urls: urls,
current: current || urls[0],
indicator: 'number',
loop: true
});
@ -349,4 +534,16 @@
}
}
}
.notes-content {
color: #333;
font-size: 14px;
line-height: 1.6;
padding: 12px 16px;
background-color: #F5F5F5;
border-radius: 8px;
min-height: 120px;
white-space: pre-wrap;
word-break: break-word;
}
</style>

+ 579
- 0
pages_order/components/order/timelineService.vue View File

@ -0,0 +1,579 @@
<template>
<view class="timeline-container">
<!-- 日期和状态标签 -->
<view class="date-header">
<view class="date-box">
<view class="date-box-color" :style="{'background-color': getTopBgColor()}"></view>
<view class="date-month-day">{{ formatDate(date).month }}-{{ formatDate(date).day }}</view>
</view>
<!-- <view class="status-tag" :class="{'status-tag-pending': status}">
<image src="/static/images/ydd/icon1.png"
mode="aspectFit"
v-if="status"
class="status-icon"></image>
<image src="/static/images/order/success.png"
mode="aspectFit"
v-else
class="status-icon"></image>
{{ status ? '待上门' : '已完成' }}
</view> -->
</view>
<!-- 空状态显示 -->
<view v-if="!list || list.length === 0" class="empty-state">
<text class="empty-text">暂无订单数据</text>
</view>
<!-- 时间线主体 -->
<view v-else class="timeline-body" v-for="(item, index) in list" :key="index">
<view class="timeline-line"></view>
<!-- <view class="time-point">
<view class="time-icon">
<image src="/static/images/order/address.png" mode="aspectFit" class="time-image"></image>
</view>
<view class="time-text">{{ item.receiverCity }}</view>
<view class="collapse-icon" @click="toggleServiceCard(index)">
{{ serviceCardCollapsed[index] ? '展开' : '收起' }} <text class="arrow" :class="{'arrow-up': !serviceCardCollapsed[index]}"></text>
</view>
</view> -->
<!-- 服务内容卡片 -->
<view v-if="!serviceCardCollapsed[index]" class="service-card">
<!-- 服务日期 -->
<view class="service-section">
<view class="section-title">
<view class="title-indicator"></view>
<text>服务日期</text>
<text style="margin-left: auto;font-weight: 500;font-size: 24rpx;">订单编号{{ item.orderId }}</text>
</view>
<view class="section-content date-content" :class="{bgSuccessQ : item.status == '2'}">
{{ dayjs(item.serviceDate).format('YYYY/MM/DD') }}
</view>
</view>
<!-- 陪伴对象 -->
<view class="service-section">
<view class="section-title">
<view class="title-indicator"></view>
<text>陪伴对象</text>
<view class="collapse-icon" @click="togglePetList(index)">
{{ petListCollapsed[index] ? '展开' : '收起' }} <text class="arrow" :class="{'arrow-up': !petListCollapsed[index]}"></text>
</view>
</view>
<view class="section-content pet-list" :class="{bgSuccessQ : item.status == '2'}" v-if="!petListCollapsed[index]">
<view v-for="(pet, i) in item.pets" :key="i" class="pet-item">
<view class="pet-avatar">
<image :src="pet.photo" mode="aspectFill" class="avatar-image"></image>
</view>
<view class="pet-info">
<view class="pet-name">
{{ pet.name }}
<text class="pet-gender" :class="{'pet-gender-male': pet.gender === 'male', 'pet-gender-female': pet.gender === 'female'}">
{{ pet.gender === 'male' ? '♂' : '♀' }}
</text>
</view>
<view class="pet-description">
{{ pet.breed }}{{ pet.bodyType }} | {{ pet.orderItemList.map(n => n.productName).join(',') }}
</view>
</view>
</view>
</view>
</view>
<!-- 上门地址 -->
<!-- <view class="service-section">
<view class="section-title">
<view class="title-indicator"></view>
<text>上门地址</text>
</view>
<view class="section-content address-content" :class="{bgSuccessQ : item.status == '2'}">
{{ item.receiverDetailAddress }}
</view>
</view> -->
<!-- 操作按钮 -->
<view class="action-buttons">
<view class="btn btn-clock"
v-if="item.status == '2'"
:class="{bgSuccess : item.status == '2'}"
@click="handleClock(item)">查看记录</view>
<view class="btn btn-clock"
v-else
style="background-color: #aaa;color: #fff;"
>暂无记录</view>
</view>
</view>
</view>
</view>
</template>
<script>
import dayjs from '@/utils/lib/dayjs.min.js';
export default {
name: 'TimelineService',
//
props: {
date: {
type: String,
default: '2024-12-08'
},
orderCount: {
type: Number,
default: 2
},
status: {
type: Boolean,
default: true
},
current: {
type: Number,
default: 0
},
list: {
type: Array,
default: () => []
},
},
data() {
return {
// - 使
petListCollapsed: [],
// - 使
serviceCardCollapsed: []
};
},
//
computed: {
// liststatus2
orderCompleted() {
if (!this.list || this.list.length === 0) {
return false;
}
return !this.list.every(item => item.status == '2');
}
},
methods: {
dayjs,
//
togglePetList(index) {
if (this.petListCollapsed[index] === undefined) {
this.$set(this.petListCollapsed, index, true);
} else {
this.$set(this.petListCollapsed, index, !this.petListCollapsed[index]);
}
},
//
toggleServiceCard(index) {
if (this.serviceCardCollapsed[index] === undefined) {
this.$set(this.serviceCardCollapsed, index, true);
} else {
this.$set(this.serviceCardCollapsed, index, !this.serviceCardCollapsed[index]);
}
},
//
formatDate(dateString) {
const date = new Date(dateString);
return {
day: date.getDate().toString().padStart(2, '0'),
month: (date.getMonth() + 1).toString().padStart(2, '0')
};
},
//
handleClock(item) {
uni.navigateTo({
url: `/pages/personalCenter/orderDetailImage?id=${item.id}`,
});
},
getTopBgColor() {
return this.orderCompleted ? '#FFAA48' : '#4CD964';
}
}
}
</script>
<style lang="scss" scoped>
.bgSuccess{
background-color: #4CD964 !important;
}
.bgSuccessQ{
background-color: #4CD96422 !important;
}
.timeline-container {
position: relative;
padding: 20rpx;
margin-bottom: 30rpx;
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 40rpx;
.empty-image {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
.empty-text {
color: #999;
font-size: 28rpx;
}
}
.date-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.date-box {
width: 80rpx;
background-color: #ffffff;
border: 2px solid #333;
border-radius: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-right: 20rpx;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
border-radius: 14rpx;
.date-box-color{
height: 20rpx;
width: 100%;
border-top-left-radius: 14rpx;
border-top-right-radius: 14rpx;
position: relative;
&::before{
content: '';
display: block;
background-color: #ddd;
width: 100%;
height: 26rpx;
top: 100%;
left: 0;
position: absolute;
}
}
.date-month-day {
position: relative;
font-size: 26rpx;
font-weight: bold;
color: #333;
height: 50rpx;
display: flex;
flex-direction: column;
justify-content: center;
}
}
.status-tag {
background-color: #4CD96422;
color: #4CD964;
border: 4rpx solid #4CD964;
padding: 16rpx 26rpx;
border-radius: 14rpx;
font-size: 26rpx;
display: flex;
align-items: center;
position: relative;
margin-left: 20rpx;
.status-icon {
width: 32rpx;
height: 32rpx;
margin-right: 8rpx;
}
&::after{
content: '';
display: block;
position: absolute;
width: 0;
height: 0;
top: 50%;
transform: translateY(-50%);
left: -16rpx;
border-top: 16rpx solid transparent;
border-bottom: 16rpx solid transparent;
border-right: 16rpx solid #4CD964;
}
&::before{
content: '';
display: block;
position: absolute;
width: 0;
height: 0;
top: 50%;
transform: translateY(-50%);
left: -12rpx;
border-top: 12rpx solid transparent;
border-bottom: 12rpx solid transparent;
border-right: 12rpx solid #4CD96422;
z-index: 1;
}
}
.status-tag-pending {
background-color: #FFAA4822;
color: #FFAA48;
border-color: #FFAA48;
&::after{
border-right-color: #FFAA48;
}
&::before{
border-right-color: #FFAA4822;
}
}
}
.timeline-body {
position: relative;
padding-left: 40rpx;
padding-bottom: 40rpx;
.timeline-line {
position: absolute;
left: 40rpx;
top: 0;
height: 100%;
width: 0;
border-left: 2rpx dashed #707070;
border-left-style: dashed;
border-image: repeating-linear-gradient(to bottom, #707070 0, #707070 8rpx, transparent 8rpx, transparent 20rpx) 1;
z-index: 0;
&::after{
content: '';
display: block;
position: absolute;
width: 8rpx;
height: 8rpx;
background-color: #000;
border: 2rpx solid #707070;
border-radius: 50%;
left: -7rpx;
top: 30rpx;
}
}
.time-point {
display: flex;
align-items: center;
margin-bottom: 20rpx;
position: relative;
z-index: 1;
.time-icon {
width: 60rpx;
height: 60rpx;
background-color: #fff;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
margin-right: 20rpx;
position: relative;
left: 20rpx;
.time-image {
width: 40rpx;
height: 40rpx;
}
}
.time-text {
font-size: 28rpx;
color: #333;
margin-left: 20rpx;
flex: 1;
}
.collapse-icon {
font-size: 24rpx;
color: #999;
padding: 0 20rpx;
.arrow {
transition: transform 0.3s;
display: inline-block;
}
.arrow-up {
transform: rotate(180deg);
}
}
}
.service-card {
background-color: #fff;
border-radius: 12rpx;
padding: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
margin-left: 20rpx;
.service-section {
margin-bottom: 30rpx;
.section-title {
display: flex;
align-items: center;
margin-bottom: 15rpx;
.title-indicator {
width: 6rpx;
height: 30rpx;
background-color: #FFAA48;
margin-right: 15rpx;
}
text {
font-size: 28rpx;
color: #333;
font-weight: bold;
}
.collapse-icon {
margin-left: auto;
font-size: 24rpx;
color: #999;
.arrow {
transition: transform 0.3s;
display: inline-block;
}
.arrow-up {
transform: rotate(180deg);
}
}
}
.section-content {
padding: 0 15rpx;
background-color: #FFF9F0;
}
.date-content {
background-color: #FFF9F0;
padding: 20rpx;
border-radius: 8rpx;
font-size: 28rpx;
color: #333;
}
.pet-list {
padding: 15rpx;
.pet-item {
display: flex;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.pet-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
overflow: hidden;
margin-right: 20rpx;
.avatar-image {
width: 100%;
height: 100%;
}
}
.pet-info {
flex: 1;
.pet-name {
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
.pet-gender {
display: inline-block;
width: 32rpx;
height: 32rpx;
line-height: 32rpx;
text-align: center;
border-radius: 50%;
color: #fff;
font-size: 20rpx;
margin-left: 10rpx;
}
.pet-gender-male {
background-color: #4A90E2;
}
.pet-gender-female {
background-color: #FF6B9A;
}
}
.pet-description {
font-size: 24rpx;
color: #7D8196;
}
}
}
}
.address-content {
padding: 20rpx;
border-radius: 8rpx;
font-size: 28rpx;
color: #7D8196;
}
}
.action-buttons {
display: flex;
justify-content: space-between;
gap: 20rpx;
.btn {
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 40rpx;
font-size: 28rpx;
flex: 1;
}
.btn-clock {
background-color: #FFAA48;
color: #fff;
}
.btn-pet-file, .btn-service-file {
background-color: #F6F7FB;
color: #333;
border: 1px solid #E5E6EB;
}
}
}
}
}
</style>

+ 12
- 10
pages_order/order/orderDetail.vue View File

@ -31,13 +31,13 @@
<!-- <view class="footer-btn modify-btn" v-if="orderDetail.status == '1' || orderDetail.status == '2'" @click="modifyOrder">
<text>修改订单</text>
</view> -->
<view class="footer-btn pay-btn" v-if="orderDetail.status == '3'" @click="goToReview">
<view class="footer-btn pay-btn" v-if="orderDetail.status == '4'" @click="goToReview">
<text>去评价</text>
</view>
<view class="footer-btn pay-btn" v-if="orderDetail.status == '3'" @click="handleReorder">
<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="handleReorder">
<view class="footer-btn pay-btn" v-if="orderDetail.status == '2'" @click="viewServiceRecord">
<text>查看服务记录</text>
</view>
<view class="footer-btn contact-btn">
@ -311,22 +311,24 @@
this.getOrderDetail();
},
// /
//
handleReorder() {
if (this.orderDetail.status == '3') {
if (this.orderDetail.status == '4') {
// -
uni.showToast({
title: '再来一单功能开发中',
icon: 'none'
});
} else if (this.orderDetail.status == '2') {
//
uni.navigateTo({
url: `/pages_order/order/serviceRecord?orderId=${this.orderId}`
});
}
},
//
viewServiceRecord() {
uni.navigateTo({
url: `/pages_order/order/serviceRecord?orderId=${this.orderId}`
});
},
//
modifyOrder() {
// ID


+ 11
- 4
pages_order/order/orderList.vue View File

@ -73,13 +73,13 @@
<!-- <view class="action-btn pay-btn" v-if="order.status == 1 || order.status == 2" @click="modifyOrder(order)">
<text>修改订单</text>
</view> -->
<view class="action-btn pay-btn" v-if="order.status == 3" @click="goToReview(order)">
<view class="action-btn pay-btn" v-if="order.status == 4" @click="goToReview(order)">
<text>去评价</text>
</view>
<view class="action-btn pay-btn" v-if="order.status == 3" @click="handleReorder(order)">
<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="order.status == 2" @click="handleReorder(order)">
<view class="action-btn pay-btn" v-if="[2,3,4].includes(order.status)" @click="viewServiceRecord(order.orderId)">
<text>查看服务记录</text>
</view>
</view>
@ -181,7 +181,7 @@
},
{
name: '服务中',
value: 3
value: 11
},
{
name: '已完成',
@ -304,6 +304,13 @@
url: `/pages_order/order/orderDetail?id=${orderId}`
});
},
//
viewServiceRecord(orderId) {
uni.navigateTo({
url: `/pages_order/order/serviceRecord?orderId=${orderId}`
});
},
//
goToPay(orderId) {


+ 215
- 0
pages_order/order/serviceRecord.vue View File

@ -0,0 +1,215 @@
<template>
<view class="service-record-page">
<!-- 服务记录列表 -->
<view class="record-content">
<view v-if="loading && recordList.length === 0" class="loading-state">
<view class="loading-spinner"></view>
<text>正在加载服务记录...</text>
</view>
<view v-else-if="recordList.length === 0" class="empty-state">
<image src="/static/images/personal/no-data.png" mode="aspectFit" class="empty-image"></image>
<text class="empty-text">暂无服务记录</text>
</view>
<view v-else>
<timeline-service
v-for="(record, index) in recordList"
:key="index"
:date="record.date"
:list="record.list"
:serviceBtn="false"
/>
</view>
</view>
<!-- 加载更多 -->
<view class="loading-more" v-if="recordList.length > 0 && hasMore && loading">
<view class="loading-spinner"></view>
<text>加载中...</text>
</view>
<!-- 没有更多数据 -->
<view class="no-more" v-if="recordList.length > 0 && !hasMore">
<text>没有更多记录了</text>
</view>
</view>
</template>
<script>
import { appletOrderDateFrequencyList } from '@/api/order/order.js'
import TimelineService from '../components/order/timelineService.vue'
export default {
components: {
TimelineService
},
data() {
return {
orderId: '',
recordList: [],
loading: false,
hasMore: true,
page: 1,
size: 10
}
},
onLoad(options) {
if (options.orderId) {
this.orderId = options.orderId;
this.getServiceRecords();
}
},
onReachBottom() {
this.loadMore();
},
onPullDownRefresh() {
this.refresh();
},
methods: {
//
getServiceRecords() {
if (this.loading) return;
this.loading = true;
const params = {
orderId: this.orderId,
pageNumber: this.page,
pageSize: this.size
};
appletOrderDateFrequencyList(params).then(res => {
if (res && res.data) {
const newList = res.data;
if (this.page === 1) {
this.recordList = newList;
} else {
this.recordList = [...this.recordList, ...newList];
}
this.hasMore = newList.length === this.size;
} else {
this.hasMore = false;
}
console.log(this.recordList);
this.loading = false;
}).catch(err => {
console.error('获取服务记录失败:', err);
this.loading = false;
uni.showToast({
title: '获取服务记录失败',
icon: 'none'
});
});
},
//
loadMore() {
if (this.loading || !this.hasMore) return;
this.page++;
this.getServiceRecords();
},
//
refresh() {
this.page = 1;
this.recordList = [];
this.hasMore = true;
this.getServiceRecords().then(() => {
uni.stopPullDownRefresh();
}).catch(() => {
uni.stopPullDownRefresh();
});
}
}
}
</script>
<style lang="scss" scoped>
.service-record-page {
background-color: #f5f5f5;
min-height: 100vh;
}
.page-header {
background: linear-gradient(135deg, #FFAA48 0%, #FF8A00 100%);
padding: 40rpx 30rpx 30rpx;
color: #FFFFFF;
.header-title {
font-size: 36rpx;
font-weight: bold;
display: block;
margin-bottom: 10rpx;
}
.header-subtitle {
font-size: 26rpx;
opacity: 0.9;
}
}
.record-content {
padding: 20rpx;
}
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 40rpx;
color: #999;
font-size: 28rpx;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 40rpx;
.empty-image {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
.empty-text {
color: #999;
font-size: 28rpx;
}
}
.loading-more,
.no-more {
text-align: center;
padding: 20rpx 0;
color: #999;
font-size: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading-spinner {
width: 40rpx;
height: 40rpx;
border: 4rpx solid #f3f3f3;
border-top: 4rpx solid #FFAA48;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16rpx;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

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

@ -43,6 +43,10 @@
'uni-calendar-item--after-checked':weeks.afterMultiple,
'uni-calendar-item--disable':weeks.disable,
}">{{weeks.extraInfo.info}}</text> -->
<text style="font-size: 20rpx;"
v-if="isNotOrder"
>不接单</text>
</view>
</view>
</template>
@ -88,6 +92,9 @@
todayText() {
return t("uni-calender.today")
},
isNotOrder(){
return this.disabledDay.includes(this.weeks.fullDate)
},
},
methods: {
choiceDate(weeks) {


Loading…
Cancel
Save