refactor: 优化订单详情和宠物信息展示逻辑 fix: 修复订单状态判断和按钮显示条件 docs: 添加数据库事务查询SQL文件 style: 统一时间线组件样式和布局 test: 更新测试用例以适应新功能master
| @ -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命令 | |||||
| - 优先尝试让应用程序正常提交或回滚事务 | |||||
| - 如果必须强制终止,确保了解可能的数据影响 | |||||
| */ | |||||
| @ -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> | |||||
| @ -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: { | |||||
| // 判断list中是否所有项的status都等于2 | |||||
| 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> | |||||
| @ -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> | |||||