猫妈狗爸伴宠师小程序前端代码
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.

635 lines
15 KiB

  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(date).month }}-{{ formatDate(date).day }}</view>
  8. </view>
  9. <view class="status-tag" :class="{'status-tag-pending': 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="!processedList || processedList.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 processedList" :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.address }}</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}">
  47. {{ item.fullDate }}
  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}" v-if="!petListCollapsed[index]">
  60. <view v-for="(pet, i) in item.petList" :key="i" class="pet-item">
  61. <view class="pet-avatar">
  62. <image :src="pet.avatar" 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.services.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}">
  85. {{ item.addressDetail }}
  86. </view>
  87. </view>
  88. <!-- 操作按钮 -->
  89. <view class="action-buttons">
  90. <view class="btn btn-clock" :class="{bgSuccess : item.status}" @click="handleClock(item)">{{ item.status ? '打卡记录' : '打卡' }}</view>
  91. <view class="btn btn-clock" :class="{bgSuccess : item.status}" @click="handlePetFile(item)">宠物档案</view>
  92. <view class="btn btn-clock" :class="{bgSuccess : item.status}" @click="handleServiceFile(item)">服务档案</view>
  93. </view>
  94. </view>
  95. </view>
  96. </view>
  97. </template>
  98. <script setup>
  99. import { ref, computed } from 'vue';
  100. import { getOrderServiceText, getProductNameText } from '@/utils/serviceTime.js';
  101. // 定义组件属性
  102. const props = defineProps({
  103. date: {
  104. type: String,
  105. default: '2024-12-08'
  106. },
  107. orderCount: {
  108. type: Number,
  109. default: 2
  110. },
  111. status : {
  112. type: Boolean,
  113. default: true
  114. },
  115. current: {
  116. type: Number,
  117. default: 0
  118. },
  119. list: {
  120. type: Array,
  121. default: () => []
  122. }
  123. });
  124. // 宠物列表折叠状态 - 使用数组来单独控制每个卡片中的宠物列表
  125. const petListCollapsed = ref([]);
  126. // 服务卡片折叠状态 - 使用数组来单独控制每个卡片
  127. const serviceCardCollapsed = ref([]);
  128. // 切换宠物列表显示状态
  129. const togglePetList = (index) => {
  130. if (petListCollapsed.value[index] === undefined) {
  131. petListCollapsed.value[index] = true;
  132. } else {
  133. petListCollapsed.value[index] = !petListCollapsed.value[index];
  134. }
  135. };
  136. // 切换服务卡片显示状态
  137. const toggleServiceCard = (index) => {
  138. if (serviceCardCollapsed.value[index] === undefined) {
  139. serviceCardCollapsed.value[index] = true;
  140. } else {
  141. serviceCardCollapsed.value[index] = !serviceCardCollapsed.value[index];
  142. }
  143. };
  144. // 格式化日期
  145. const formatDate = (dateString) => {
  146. const date = new Date(dateString);
  147. return {
  148. day: date.getDate().toString().padStart(2, '0'),
  149. month: (date.getMonth() + 1).toString().padStart(2, '0')
  150. };
  151. };
  152. // 处理订单数据,转换为组件所需格式
  153. const processedList = computed(() => {
  154. return props.list.map(order => {
  155. // 获取所有宠物信息
  156. const petList = [];
  157. let orderId = 0
  158. let serviceId = 0
  159. let status = 1
  160. if (order.appletOrderItemDate && order.appletOrderItemDate.length > 0) {
  161. order.appletOrderItemDate.forEach(item => {
  162. if (item.orderServiceList && item.orderServiceList.petVo) {
  163. const pet = item.orderServiceList.petVo;
  164. const services = [];
  165. // 获取服务名称
  166. if (item.orderItemList && item.orderItemList.length > 0) {
  167. item.orderItemList.forEach(orderItem => {
  168. services.push(orderItem.productName);
  169. orderId = orderItem.orderId
  170. });
  171. }
  172. serviceId = item.id
  173. if(item.status == 0){
  174. status = 0
  175. }
  176. petList.push({
  177. name: pet.name,
  178. serviceId : item.id,
  179. gender: pet.gender === '男生' ? 'male' : 'female',
  180. breed: pet.breed,
  181. bodyType: `(${pet.bodyType})`,
  182. services: services,
  183. avatar: pet.photo || (pet.petType === 'dog' ? '/static/images/ydd/dog.png' : '/static/images/ydd/cat.png')
  184. });
  185. }
  186. });
  187. }
  188. return {
  189. id: order.orderId,
  190. orderId,
  191. serviceId,
  192. status,
  193. address: order.cityAddress,
  194. addressDetail: order.address,
  195. fullDate: props.date.replace(/-/g, '/'),
  196. petList: petList
  197. };
  198. });
  199. });
  200. // 按钮事件处理函数
  201. const handleClock = (item) => {
  202. console.log(item);
  203. // 根据订单状态确定跳转路径
  204. const paths = [
  205. `/otherPages/myOrdersManage/clock/index?id=${item.orderId}&itemID=${item.id}&serviceId=${item.serviceId}`,
  206. `/otherPages/myOrdersManage/clock/index?isRead=true&id=${item.orderId}&itemID=${item.id}&serviceId=${item.serviceId}`,
  207. ];
  208. uni.navigateTo({
  209. url: props.status ? paths[0] : paths[1]
  210. });
  211. };
  212. const handlePetFile = (item) => {
  213. uni.navigateTo({
  214. url: "/otherPages/orderTakingManage/pet/index?id=" + item.orderId
  215. });
  216. };
  217. const handleServiceFile = (item) => {
  218. uni.navigateTo({
  219. url: "/otherPages/myOrdersManage/service/index?id=" + item.orderId
  220. });
  221. };
  222. function getTopBgColor(){
  223. return props.status ? '#FFAA48' : '#4CD964';
  224. }
  225. </script>
  226. <style lang="scss" scoped>
  227. .bgSuccess{
  228. background-color: #4CD964 !important;
  229. }
  230. .bgSuccessQ{
  231. background-color: #4CD96422 !important;
  232. }
  233. .timeline-container {
  234. position: relative;
  235. padding: 20rpx;
  236. margin-bottom: 30rpx;
  237. .empty-state {
  238. display: flex;
  239. flex-direction: column;
  240. align-items: center;
  241. justify-content: center;
  242. padding: 80rpx 40rpx;
  243. .empty-image {
  244. width: 200rpx;
  245. height: 200rpx;
  246. margin-bottom: 20rpx;
  247. }
  248. .empty-text {
  249. color: #999;
  250. font-size: 28rpx;
  251. }
  252. }
  253. .date-header {
  254. display: flex;
  255. align-items: center;
  256. margin-bottom: 20rpx;
  257. .date-box {
  258. width: 80rpx;
  259. background-color: #ffffff;
  260. border: 2px solid #333;
  261. border-radius: 0;
  262. display: flex;
  263. flex-direction: column;
  264. justify-content: center;
  265. align-items: center;
  266. margin-right: 20rpx;
  267. box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
  268. border-radius: 14rpx;
  269. .date-box-color{
  270. height: 20rpx;
  271. width: 100%;
  272. border-top-left-radius: 14rpx;
  273. border-top-right-radius: 14rpx;
  274. position: relative;
  275. &::before{
  276. content: '';
  277. display: block;
  278. background-color: #ddd;
  279. width: 100%;
  280. height: 26rpx;
  281. top: 100%;
  282. left: 0;
  283. position: absolute;
  284. }
  285. }
  286. .date-month-day {
  287. position: relative;
  288. font-size: 26rpx;
  289. font-weight: bold;
  290. color: #333;
  291. height: 50rpx;
  292. display: flex;
  293. flex-direction: column;
  294. justify-content: center;
  295. }
  296. }
  297. .status-tag {
  298. background-color: #4CD96422;
  299. color: #4CD964;
  300. border: 4rpx solid #4CD964;
  301. padding: 16rpx 26rpx;
  302. border-radius: 14rpx;
  303. font-size: 26rpx;
  304. display: flex;
  305. align-items: center;
  306. position: relative;
  307. margin-left: 20rpx;
  308. .status-icon {
  309. width: 32rpx;
  310. height: 32rpx;
  311. margin-right: 8rpx;
  312. }
  313. &::after{
  314. content: '';
  315. display: block;
  316. position: absolute;
  317. width: 0;
  318. height: 0;
  319. top: 50%;
  320. transform: translateY(-50%);
  321. left: -16rpx;
  322. border-top: 16rpx solid transparent;
  323. border-bottom: 16rpx solid transparent;
  324. border-right: 16rpx solid #4CD964;
  325. }
  326. &::before{
  327. content: '';
  328. display: block;
  329. position: absolute;
  330. width: 0;
  331. height: 0;
  332. top: 50%;
  333. transform: translateY(-50%);
  334. left: -12rpx;
  335. border-top: 12rpx solid transparent;
  336. border-bottom: 12rpx solid transparent;
  337. border-right: 12rpx solid #4CD96422;
  338. z-index: 1;
  339. }
  340. }
  341. .status-tag-pending {
  342. background-color: #FFAA4822;
  343. color: #FFAA48;
  344. border-color: #FFAA48;
  345. &::after{
  346. border-right-color: #FFAA48;
  347. }
  348. &::before{
  349. border-right-color: #FFAA4822;
  350. }
  351. }
  352. }
  353. .timeline-body {
  354. position: relative;
  355. padding-left: 40rpx;
  356. padding-bottom: 40rpx;
  357. .timeline-line {
  358. position: absolute;
  359. left: 40rpx;
  360. top: 0;
  361. height: 100%;
  362. width: 0;
  363. border-left: 2rpx dashed #707070;
  364. border-left-style: dashed;
  365. border-image: repeating-linear-gradient(to bottom, #707070 0, #707070 8rpx, transparent 8rpx, transparent 20rpx) 1;
  366. z-index: 0;
  367. &::after{
  368. content: '';
  369. display: block;
  370. position: absolute;
  371. width: 8rpx;
  372. height: 8rpx;
  373. background-color: #000;
  374. border: 2rpx solid #707070;
  375. border-radius: 50%;
  376. left: -7rpx;
  377. top: 30rpx;
  378. }
  379. }
  380. .time-point {
  381. display: flex;
  382. align-items: center;
  383. margin-bottom: 20rpx;
  384. position: relative;
  385. z-index: 1;
  386. .time-icon {
  387. width: 60rpx;
  388. height: 60rpx;
  389. background-color: #fff;
  390. border-radius: 50%;
  391. display: flex;
  392. justify-content: center;
  393. align-items: center;
  394. margin-right: 20rpx;
  395. position: relative;
  396. left: 20rpx;
  397. .time-image {
  398. width: 40rpx;
  399. height: 40rpx;
  400. }
  401. }
  402. .time-text {
  403. font-size: 28rpx;
  404. color: #333;
  405. margin-left: 20rpx;
  406. flex: 1;
  407. }
  408. .collapse-icon {
  409. font-size: 24rpx;
  410. color: #999;
  411. padding: 0 20rpx;
  412. .arrow {
  413. transition: transform 0.3s;
  414. display: inline-block;
  415. }
  416. .arrow-up {
  417. transform: rotate(180deg);
  418. }
  419. }
  420. }
  421. .service-card {
  422. background-color: #fff;
  423. border-radius: 12rpx;
  424. padding: 30rpx;
  425. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  426. margin-left: 20rpx;
  427. .service-section {
  428. margin-bottom: 30rpx;
  429. .section-title {
  430. display: flex;
  431. align-items: center;
  432. margin-bottom: 15rpx;
  433. .title-indicator {
  434. width: 6rpx;
  435. height: 30rpx;
  436. background-color: #FFAA48;
  437. margin-right: 15rpx;
  438. }
  439. text {
  440. font-size: 28rpx;
  441. color: #333;
  442. font-weight: bold;
  443. }
  444. .collapse-icon {
  445. margin-left: auto;
  446. font-size: 24rpx;
  447. color: #999;
  448. .arrow {
  449. transition: transform 0.3s;
  450. display: inline-block;
  451. }
  452. .arrow-up {
  453. transform: rotate(180deg);
  454. }
  455. }
  456. }
  457. .section-content {
  458. padding: 0 15rpx;
  459. background-color: #FFF9F0;
  460. }
  461. .date-content {
  462. background-color: #FFF9F0;
  463. padding: 20rpx;
  464. border-radius: 8rpx;
  465. font-size: 28rpx;
  466. color: #333;
  467. }
  468. .pet-list {
  469. padding: 15rpx;
  470. .pet-item {
  471. display: flex;
  472. margin-bottom: 20rpx;
  473. &:last-child {
  474. margin-bottom: 0;
  475. }
  476. .pet-avatar {
  477. width: 80rpx;
  478. height: 80rpx;
  479. border-radius: 50%;
  480. overflow: hidden;
  481. margin-right: 20rpx;
  482. .avatar-image {
  483. width: 100%;
  484. height: 100%;
  485. }
  486. }
  487. .pet-info {
  488. flex: 1;
  489. .pet-name {
  490. font-size: 28rpx;
  491. color: #333;
  492. margin-bottom: 8rpx;
  493. .pet-gender {
  494. display: inline-block;
  495. width: 32rpx;
  496. height: 32rpx;
  497. line-height: 32rpx;
  498. text-align: center;
  499. border-radius: 50%;
  500. color: #fff;
  501. font-size: 20rpx;
  502. margin-left: 10rpx;
  503. }
  504. .pet-gender-male {
  505. background-color: #4A90E2;
  506. }
  507. .pet-gender-female {
  508. background-color: #FF6B9A;
  509. }
  510. }
  511. .pet-description {
  512. font-size: 24rpx;
  513. color: #7D8196;
  514. }
  515. }
  516. }
  517. }
  518. .address-content {
  519. padding: 20rpx;
  520. border-radius: 8rpx;
  521. font-size: 28rpx;
  522. color: #7D8196;
  523. }
  524. }
  525. .action-buttons {
  526. display: flex;
  527. justify-content: space-between;
  528. .btn {
  529. width: 30%;
  530. height: 80rpx;
  531. line-height: 80rpx;
  532. text-align: center;
  533. border-radius: 40rpx;
  534. font-size: 28rpx;
  535. }
  536. .btn-clock {
  537. background-color: #FFAA48;
  538. color: #fff;
  539. }
  540. .btn-pet-file, .btn-service-file {
  541. background-color: #F6F7FB;
  542. color: #333;
  543. border: 1px solid #E5E6EB;
  544. }
  545. }
  546. }
  547. }
  548. }
  549. </style>