猫妈狗爸伴宠师小程序前端代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

605 lines
14 KiB

fix(订单管理): 修复宠物档案跳转缺少订单ID的问题 修复订单详情页跳转宠物档案页面时未传递orderId参数的问题 ``` ```msg refactor(认证考试): 重构考试答案提交逻辑 将单个题目提交改为批量提交,优化考试流程: 1. 基础考试和培训考试都改为最后统一提交答案 2. 添加加载状态提示 3. 使用Promise.all处理并发请求 ``` ```msg fix(认证考试): 修复考试完成状态判断逻辑 修改answeBaseIsFinish和answeTrainIsFinish接口的返回判断逻辑,从检查code改为检查data字段 ``` ```msg feat(认证考试): 新增重新考试和成为伴宠师接口 1. 添加retakeExam和appletUsersTeacher接口 2. 在错误详情页添加重新考试功能 3. 在考试完成页添加成为伴宠师功能 ``` ```msg style(时间轴组件): 优化操作按钮布局 1. 添加按钮间距(gap) 2. 使用flex:1使按钮等宽 3. 根据状态显示不同按钮文本 4. 添加serviceBtn属性控制档案按钮显示 ``` ```msg refactor(订单弹窗): 重构服务档案弹窗组件 1. 使用timelineService组件替代原有实现 2. 简化数据结构处理 3. 添加状态判断逻辑 4. 优化弹窗标题和样式 ``` ```msg fix(表单验证): 添加认证考试结束页表单验证 1. 添加姓名、电话、地址的必填验证 2. 添加格式验证(电话格式、姓名格式) 3. 添加长度验证 4. 添加错误状态样式 5. 优化错误提示体验 ``` ```msg refactor(工作台): 重构伴宠师申请流程 1. 优化申请条件判断逻辑 2. 添加用户状态检查 3. 完善考试状态跳转逻辑 4. 统一使用store获取用户信息
3 months ago
fix(订单管理): 修复宠物档案跳转缺少订单ID的问题 修复订单详情页跳转宠物档案页面时未传递orderId参数的问题 ``` ```msg refactor(认证考试): 重构考试答案提交逻辑 将单个题目提交改为批量提交,优化考试流程: 1. 基础考试和培训考试都改为最后统一提交答案 2. 添加加载状态提示 3. 使用Promise.all处理并发请求 ``` ```msg fix(认证考试): 修复考试完成状态判断逻辑 修改answeBaseIsFinish和answeTrainIsFinish接口的返回判断逻辑,从检查code改为检查data字段 ``` ```msg feat(认证考试): 新增重新考试和成为伴宠师接口 1. 添加retakeExam和appletUsersTeacher接口 2. 在错误详情页添加重新考试功能 3. 在考试完成页添加成为伴宠师功能 ``` ```msg style(时间轴组件): 优化操作按钮布局 1. 添加按钮间距(gap) 2. 使用flex:1使按钮等宽 3. 根据状态显示不同按钮文本 4. 添加serviceBtn属性控制档案按钮显示 ``` ```msg refactor(订单弹窗): 重构服务档案弹窗组件 1. 使用timelineService组件替代原有实现 2. 简化数据结构处理 3. 添加状态判断逻辑 4. 优化弹窗标题和样式 ``` ```msg fix(表单验证): 添加认证考试结束页表单验证 1. 添加姓名、电话、地址的必填验证 2. 添加格式验证(电话格式、姓名格式) 3. 添加长度验证 4. 添加错误状态样式 5. 优化错误提示体验 ``` ```msg refactor(工作台): 重构伴宠师申请流程 1. 优化申请条件判断逻辑 2. 添加用户状态检查 3. 完善考试状态跳转逻辑 4. 统一使用store获取用户信息
3 months ago
3 weeks ago
3 weeks ago
fix(订单管理): 修复宠物档案跳转缺少订单ID的问题 修复订单详情页跳转宠物档案页面时未传递orderId参数的问题 ``` ```msg refactor(认证考试): 重构考试答案提交逻辑 将单个题目提交改为批量提交,优化考试流程: 1. 基础考试和培训考试都改为最后统一提交答案 2. 添加加载状态提示 3. 使用Promise.all处理并发请求 ``` ```msg fix(认证考试): 修复考试完成状态判断逻辑 修改answeBaseIsFinish和answeTrainIsFinish接口的返回判断逻辑,从检查code改为检查data字段 ``` ```msg feat(认证考试): 新增重新考试和成为伴宠师接口 1. 添加retakeExam和appletUsersTeacher接口 2. 在错误详情页添加重新考试功能 3. 在考试完成页添加成为伴宠师功能 ``` ```msg style(时间轴组件): 优化操作按钮布局 1. 添加按钮间距(gap) 2. 使用flex:1使按钮等宽 3. 根据状态显示不同按钮文本 4. 添加serviceBtn属性控制档案按钮显示 ``` ```msg refactor(订单弹窗): 重构服务档案弹窗组件 1. 使用timelineService组件替代原有实现 2. 简化数据结构处理 3. 添加状态判断逻辑 4. 优化弹窗标题和样式 ``` ```msg fix(表单验证): 添加认证考试结束页表单验证 1. 添加姓名、电话、地址的必填验证 2. 添加格式验证(电话格式、姓名格式) 3. 添加长度验证 4. 添加错误状态样式 5. 优化错误提示体验 ``` ```msg refactor(工作台): 重构伴宠师申请流程 1. 优化申请条件判断逻辑 2. 添加用户状态检查 3. 完善考试状态跳转逻辑 4. 统一使用store获取用户信息
3 months ago
fix(订单管理): 修复宠物档案跳转缺少订单ID的问题 修复订单详情页跳转宠物档案页面时未传递orderId参数的问题 ``` ```msg refactor(认证考试): 重构考试答案提交逻辑 将单个题目提交改为批量提交,优化考试流程: 1. 基础考试和培训考试都改为最后统一提交答案 2. 添加加载状态提示 3. 使用Promise.all处理并发请求 ``` ```msg fix(认证考试): 修复考试完成状态判断逻辑 修改answeBaseIsFinish和answeTrainIsFinish接口的返回判断逻辑,从检查code改为检查data字段 ``` ```msg feat(认证考试): 新增重新考试和成为伴宠师接口 1. 添加retakeExam和appletUsersTeacher接口 2. 在错误详情页添加重新考试功能 3. 在考试完成页添加成为伴宠师功能 ``` ```msg style(时间轴组件): 优化操作按钮布局 1. 添加按钮间距(gap) 2. 使用flex:1使按钮等宽 3. 根据状态显示不同按钮文本 4. 添加serviceBtn属性控制档案按钮显示 ``` ```msg refactor(订单弹窗): 重构服务档案弹窗组件 1. 使用timelineService组件替代原有实现 2. 简化数据结构处理 3. 添加状态判断逻辑 4. 优化弹窗标题和样式 ``` ```msg fix(表单验证): 添加认证考试结束页表单验证 1. 添加姓名、电话、地址的必填验证 2. 添加格式验证(电话格式、姓名格式) 3. 添加长度验证 4. 添加错误状态样式 5. 优化错误提示体验 ``` ```msg refactor(工作台): 重构伴宠师申请流程 1. 优化申请条件判断逻辑 2. 添加用户状态检查 3. 完善考试状态跳转逻辑 4. 统一使用store获取用户信息
3 months ago
  1. <template>
  2. <view class="timeline-container">
  3. <!-- 日期和状态标签 -->
  4. <view class="date-header">
  5. <view class="date-box">
  6. <view class="date-box-color" :style="{'background-color': getTopBgColor()}"></view>
  7. <view class="date-month-day">{{ formatDate(props.date).month }}-{{ formatDate(props.date).day }}</view>
  8. </view>
  9. <view class="status-tag" :class="{'status-tag-pending': props.status}">
  10. <image src="/static/images/ydd/icon1.png"
  11. mode="aspectFit"
  12. v-if="status"
  13. class="status-icon"></image>
  14. <image src="/static/images/order/success.png"
  15. mode="aspectFit"
  16. v-else
  17. class="status-icon"></image>
  18. {{ status ? '待上门' : '已完成' }}
  19. </view>
  20. </view>
  21. <!-- 空状态显示 -->
  22. <view v-if="!props.list || props.list.length === 0" class="empty-state">
  23. <text class="empty-text">暂无订单数据</text>
  24. </view>
  25. <!-- 时间线主体 -->
  26. <view v-else class="timeline-body" v-for="(item, index) in props.list" :key="index">
  27. <view class="timeline-line"></view>
  28. <view class="time-point">
  29. <view class="time-icon">
  30. <image src="/static/images/order/address.png" mode="aspectFit" class="time-image"></image>
  31. </view>
  32. <view class="time-text">{{ item.receiverCity }}</view>
  33. <view class="collapse-icon" @click="toggleServiceCard(index)">
  34. {{ serviceCardCollapsed[index] ? '展开' : '收起' }} <text class="arrow" :class="{'arrow-up': !serviceCardCollapsed[index]}"></text>
  35. </view>
  36. </view>
  37. <!-- 服务内容卡片 -->
  38. <view v-if="!serviceCardCollapsed[index]" class="service-card">
  39. <!-- 服务日期 -->
  40. <view class="service-section">
  41. <view class="section-title">
  42. <view class="title-indicator"></view>
  43. <text>服务日期</text>
  44. <text style="margin-left: auto;font-weight: 500;font-size: 24rpx;">订单编号{{ item.orderId }}</text>
  45. </view>
  46. <view class="section-content date-content" :class="{bgSuccessQ : item.status == '2'}">
  47. {{ dayjs(item.serviceDate).format('YYYY/MM/DD') }}
  48. </view>
  49. </view>
  50. <!-- 陪伴对象 -->
  51. <view class="service-section">
  52. <view class="section-title">
  53. <view class="title-indicator"></view>
  54. <text>陪伴对象</text>
  55. <view class="collapse-icon" @click="togglePetList(index)">
  56. {{ petListCollapsed[index] ? '展开' : '收起' }} <text class="arrow" :class="{'arrow-up': !petListCollapsed[index]}"></text>
  57. </view>
  58. </view>
  59. <view class="section-content pet-list" :class="{bgSuccessQ : item.status == '2'}" v-if="!petListCollapsed[index]">
  60. <view v-for="(pet, i) in item.pets" :key="i" class="pet-item">
  61. <view class="pet-avatar">
  62. <image :src="pet.photo" mode="aspectFill" class="avatar-image"></image>
  63. </view>
  64. <view class="pet-info">
  65. <view class="pet-name">
  66. {{ pet.name }}
  67. <text class="pet-gender" :class="{'pet-gender-male': pet.gender === 'male', 'pet-gender-female': pet.gender === 'female'}">
  68. {{ pet.gender === 'male' ? '♂' : '♀' }}
  69. </text>
  70. </view>
  71. <view class="pet-description">
  72. {{ pet.breed }}{{ pet.bodyType }} | {{ pet.orderItemList.map(n => n.productName).join(',') }}
  73. </view>
  74. </view>
  75. </view>
  76. </view>
  77. </view>
  78. <!-- 上门地址 -->
  79. <view class="service-section">
  80. <view class="section-title">
  81. <view class="title-indicator"></view>
  82. <text>上门地址</text>
  83. </view>
  84. <view class="section-content address-content" :class="{bgSuccessQ : item.status == '2'}">
  85. {{ item.receiverDetailAddress }}
  86. </view>
  87. </view>
  88. <!-- 操作按钮 -->
  89. <view class="action-buttons">
  90. <view class="btn btn-clock" :class="{bgSuccess : item.status == '2'}"
  91. @click="handleClock(item)">{{ item.status == '2' ? '查看记录' : '打卡' }}</view>
  92. <view class="btn btn-clock" :class="{bgSuccess : item.status == '2'}"
  93. v-if="serviceBtn"
  94. @click="handlePetFile(item)">宠物档案</view>
  95. <view class="btn btn-clock" :class="{bgSuccess : item.status == '2'}"
  96. v-if="serviceBtn"
  97. @click="handleServiceFile(item)">服务档案</view>
  98. </view>
  99. </view>
  100. </view>
  101. </view>
  102. </template>
  103. <script setup>
  104. import { ref, computed } from 'vue';
  105. import { getOrderServiceText, getProductNameText } from '@/utils/serviceTime.js';
  106. import dayjs from 'dayjs';
  107. import list from '../../../uni_modules/uview-plus/components/u-list/list';
  108. // 定义组件属性
  109. const props = defineProps({
  110. date: {
  111. type: String,
  112. default: '2024-12-08'
  113. },
  114. orderCount: {
  115. type: Number,
  116. default: 2
  117. },
  118. status : {
  119. type: Boolean,
  120. default: true
  121. },
  122. current: {
  123. type: Number,
  124. default: 0
  125. },
  126. list: {
  127. type: Array,
  128. default: () => []
  129. },
  130. serviceBtn : {
  131. default : true,
  132. type : Boolean,
  133. },
  134. });
  135. // 宠物列表折叠状态 - 使用数组来单独控制每个卡片中的宠物列表
  136. const petListCollapsed = ref([]);
  137. // 服务卡片折叠状态 - 使用数组来单独控制每个卡片
  138. const serviceCardCollapsed = ref([]);
  139. // 切换宠物列表显示状态
  140. const togglePetList = (index) => {
  141. if (petListCollapsed.value[index] === undefined) {
  142. petListCollapsed.value[index] = true;
  143. } else {
  144. petListCollapsed.value[index] = !petListCollapsed.value[index];
  145. }
  146. };
  147. // 切换服务卡片显示状态
  148. const toggleServiceCard = (index) => {
  149. if (serviceCardCollapsed.value[index] === undefined) {
  150. serviceCardCollapsed.value[index] = true;
  151. } else {
  152. serviceCardCollapsed.value[index] = !serviceCardCollapsed.value[index];
  153. }
  154. };
  155. // 格式化日期
  156. const formatDate = (dateString) => {
  157. const date = new Date(dateString);
  158. return {
  159. day: date.getDate().toString().padStart(2, '0'),
  160. month: (date.getMonth() + 1).toString().padStart(2, '0')
  161. };
  162. };
  163. // 按钮事件处理函数
  164. const handleClock = (item) => {
  165. console.log(item);
  166. // 检查服务日期是否已到
  167. const today = new Date();
  168. const serviceDate = new Date(item.serviceDate);
  169. // 将时间设置为当天的开始,避免时分秒的影响
  170. today.setHours(0, 0, 0, 0);
  171. serviceDate.setHours(0, 0, 0, 0);
  172. // if (serviceDate > today) {
  173. // // 服务日期未到,显示提示
  174. // uni.showToast({
  175. // title: '服务日期未到,无法打卡',
  176. // icon: 'none',
  177. // duration: 2000
  178. // });
  179. // return;
  180. // }
  181. // 根据订单状态确定跳转路径
  182. const paths = [
  183. `/otherPages/myOrdersManage/clock/index?id=${item.id}`,
  184. ];
  185. uni.navigateTo({
  186. url: `/otherPages/myOrdersManage/clock/index?id=${item.id}`,
  187. });
  188. };
  189. const handlePetFile = (item) => {
  190. uni.navigateTo({
  191. url: "/otherPages/orderTakingManage/pet/index?id=" + item.orderId
  192. });
  193. };
  194. const handleServiceFile = (item) => {
  195. uni.navigateTo({
  196. url: "/otherPages/myOrdersManage/service/index?id=" + item.orderId
  197. });
  198. };
  199. function getTopBgColor(){
  200. return props.status ? '#FFAA48' : '#4CD964';
  201. }
  202. </script>
  203. <style lang="scss" scoped>
  204. .bgSuccess{
  205. background-color: #4CD964 !important;
  206. }
  207. .bgSuccessQ{
  208. background-color: #4CD96422 !important;
  209. }
  210. .timeline-container {
  211. position: relative;
  212. padding: 20rpx;
  213. margin-bottom: 30rpx;
  214. .empty-state {
  215. display: flex;
  216. flex-direction: column;
  217. align-items: center;
  218. justify-content: center;
  219. padding: 80rpx 40rpx;
  220. .empty-image {
  221. width: 200rpx;
  222. height: 200rpx;
  223. margin-bottom: 20rpx;
  224. }
  225. .empty-text {
  226. color: #999;
  227. font-size: 28rpx;
  228. }
  229. }
  230. .date-header {
  231. display: flex;
  232. align-items: center;
  233. margin-bottom: 20rpx;
  234. .date-box {
  235. width: 80rpx;
  236. background-color: #ffffff;
  237. border: 2px solid #333;
  238. border-radius: 0;
  239. display: flex;
  240. flex-direction: column;
  241. justify-content: center;
  242. align-items: center;
  243. margin-right: 20rpx;
  244. box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
  245. border-radius: 14rpx;
  246. .date-box-color{
  247. height: 20rpx;
  248. width: 100%;
  249. border-top-left-radius: 14rpx;
  250. border-top-right-radius: 14rpx;
  251. position: relative;
  252. &::before{
  253. content: '';
  254. display: block;
  255. background-color: #ddd;
  256. width: 100%;
  257. height: 26rpx;
  258. top: 100%;
  259. left: 0;
  260. position: absolute;
  261. }
  262. }
  263. .date-month-day {
  264. position: relative;
  265. font-size: 26rpx;
  266. font-weight: bold;
  267. color: #333;
  268. height: 50rpx;
  269. display: flex;
  270. flex-direction: column;
  271. justify-content: center;
  272. }
  273. }
  274. .status-tag {
  275. background-color: #4CD96422;
  276. color: #4CD964;
  277. border: 4rpx solid #4CD964;
  278. padding: 16rpx 26rpx;
  279. border-radius: 14rpx;
  280. font-size: 26rpx;
  281. display: flex;
  282. align-items: center;
  283. position: relative;
  284. margin-left: 20rpx;
  285. .status-icon {
  286. width: 32rpx;
  287. height: 32rpx;
  288. margin-right: 8rpx;
  289. }
  290. &::after{
  291. content: '';
  292. display: block;
  293. position: absolute;
  294. width: 0;
  295. height: 0;
  296. top: 50%;
  297. transform: translateY(-50%);
  298. left: -16rpx;
  299. border-top: 16rpx solid transparent;
  300. border-bottom: 16rpx solid transparent;
  301. border-right: 16rpx solid #4CD964;
  302. }
  303. &::before{
  304. content: '';
  305. display: block;
  306. position: absolute;
  307. width: 0;
  308. height: 0;
  309. top: 50%;
  310. transform: translateY(-50%);
  311. left: -12rpx;
  312. border-top: 12rpx solid transparent;
  313. border-bottom: 12rpx solid transparent;
  314. border-right: 12rpx solid #4CD96422;
  315. z-index: 1;
  316. }
  317. }
  318. .status-tag-pending {
  319. background-color: #FFAA4822;
  320. color: #FFAA48;
  321. border-color: #FFAA48;
  322. &::after{
  323. border-right-color: #FFAA48;
  324. }
  325. &::before{
  326. border-right-color: #FFAA4822;
  327. }
  328. }
  329. }
  330. .timeline-body {
  331. position: relative;
  332. padding-left: 40rpx;
  333. padding-bottom: 40rpx;
  334. .timeline-line {
  335. position: absolute;
  336. left: 40rpx;
  337. top: 0;
  338. height: 100%;
  339. width: 0;
  340. border-left: 2rpx dashed #707070;
  341. border-left-style: dashed;
  342. border-image: repeating-linear-gradient(to bottom, #707070 0, #707070 8rpx, transparent 8rpx, transparent 20rpx) 1;
  343. z-index: 0;
  344. &::after{
  345. content: '';
  346. display: block;
  347. position: absolute;
  348. width: 8rpx;
  349. height: 8rpx;
  350. background-color: #000;
  351. border: 2rpx solid #707070;
  352. border-radius: 50%;
  353. left: -7rpx;
  354. top: 30rpx;
  355. }
  356. }
  357. .time-point {
  358. display: flex;
  359. align-items: center;
  360. margin-bottom: 20rpx;
  361. position: relative;
  362. z-index: 1;
  363. .time-icon {
  364. width: 60rpx;
  365. height: 60rpx;
  366. background-color: #fff;
  367. border-radius: 50%;
  368. display: flex;
  369. justify-content: center;
  370. align-items: center;
  371. margin-right: 20rpx;
  372. position: relative;
  373. left: 20rpx;
  374. .time-image {
  375. width: 40rpx;
  376. height: 40rpx;
  377. }
  378. }
  379. .time-text {
  380. font-size: 28rpx;
  381. color: #333;
  382. margin-left: 20rpx;
  383. flex: 1;
  384. }
  385. .collapse-icon {
  386. font-size: 24rpx;
  387. color: #999;
  388. padding: 0 20rpx;
  389. .arrow {
  390. transition: transform 0.3s;
  391. display: inline-block;
  392. }
  393. .arrow-up {
  394. transform: rotate(180deg);
  395. }
  396. }
  397. }
  398. .service-card {
  399. background-color: #fff;
  400. border-radius: 12rpx;
  401. padding: 30rpx;
  402. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  403. margin-left: 20rpx;
  404. .service-section {
  405. margin-bottom: 30rpx;
  406. .section-title {
  407. display: flex;
  408. align-items: center;
  409. margin-bottom: 15rpx;
  410. .title-indicator {
  411. width: 6rpx;
  412. height: 30rpx;
  413. background-color: #FFAA48;
  414. margin-right: 15rpx;
  415. }
  416. text {
  417. font-size: 28rpx;
  418. color: #333;
  419. font-weight: bold;
  420. }
  421. .collapse-icon {
  422. margin-left: auto;
  423. font-size: 24rpx;
  424. color: #999;
  425. .arrow {
  426. transition: transform 0.3s;
  427. display: inline-block;
  428. }
  429. .arrow-up {
  430. transform: rotate(180deg);
  431. }
  432. }
  433. }
  434. .section-content {
  435. padding: 0 15rpx;
  436. background-color: #FFF9F0;
  437. }
  438. .date-content {
  439. background-color: #FFF9F0;
  440. padding: 20rpx;
  441. border-radius: 8rpx;
  442. font-size: 28rpx;
  443. color: #333;
  444. }
  445. .pet-list {
  446. padding: 15rpx;
  447. .pet-item {
  448. display: flex;
  449. margin-bottom: 20rpx;
  450. &:last-child {
  451. margin-bottom: 0;
  452. }
  453. .pet-avatar {
  454. width: 80rpx;
  455. height: 80rpx;
  456. border-radius: 50%;
  457. overflow: hidden;
  458. margin-right: 20rpx;
  459. .avatar-image {
  460. width: 100%;
  461. height: 100%;
  462. }
  463. }
  464. .pet-info {
  465. flex: 1;
  466. .pet-name {
  467. font-size: 28rpx;
  468. color: #333;
  469. margin-bottom: 8rpx;
  470. .pet-gender {
  471. display: inline-block;
  472. width: 32rpx;
  473. height: 32rpx;
  474. line-height: 32rpx;
  475. text-align: center;
  476. border-radius: 50%;
  477. color: #fff;
  478. font-size: 20rpx;
  479. margin-left: 10rpx;
  480. }
  481. .pet-gender-male {
  482. background-color: #4A90E2;
  483. }
  484. .pet-gender-female {
  485. background-color: #FF6B9A;
  486. }
  487. }
  488. .pet-description {
  489. font-size: 24rpx;
  490. color: #7D8196;
  491. }
  492. }
  493. }
  494. }
  495. .address-content {
  496. padding: 20rpx;
  497. border-radius: 8rpx;
  498. font-size: 28rpx;
  499. color: #7D8196;
  500. }
  501. }
  502. .action-buttons {
  503. display: flex;
  504. justify-content: space-between;
  505. gap: 20rpx;
  506. .btn {
  507. height: 80rpx;
  508. line-height: 80rpx;
  509. text-align: center;
  510. border-radius: 40rpx;
  511. font-size: 28rpx;
  512. flex: 1;
  513. }
  514. .btn-clock {
  515. background-color: #FFAA48;
  516. color: #fff;
  517. }
  518. .btn-pet-file, .btn-service-file {
  519. background-color: #F6F7FB;
  520. color: #333;
  521. border: 1px solid #E5E6EB;
  522. }
  523. }
  524. }
  525. }
  526. }
  527. </style>