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

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