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.

548 lines
15 KiB

  1. <template>
  2. <view class="order-detail-container">
  3. <!-- 日期信息 -->
  4. <view class="order-date">
  5. <view class="order-date-icon">
  6. <u-icon name="calendar" color="#FFAA48" size="22"></u-icon>
  7. </view>
  8. <view class="order-date-text">{{ orderDate }}</view>
  9. </view>
  10. <!-- 个人准备 -->
  11. <view class="order-section" v-if="fileList.glove || fileList.ShoeCover">
  12. <view class="order-section-title">个人准备</view>
  13. <view class="order-section-content">
  14. <!-- 手套照片 -->
  15. <view class="order-image-item" v-if="fileList.glove && fileList.glove.length > 0">
  16. <view class="order-image-label">手套照片 ({{ fileList.glove.length }})</view>
  17. <view class="order-image-container">
  18. <image
  19. class="order-image"
  20. v-for="(photo, index) in fileList.glove"
  21. :key="index"
  22. :src="photo.url"
  23. mode="aspectFill"
  24. @click="previewImage(fileList.glove.map(p => p.url), photo.url)">
  25. </image>
  26. </view>
  27. </view>
  28. <!-- 鞋套照片 -->
  29. <view class="order-image-item" v-if="fileList.ShoeCover && fileList.ShoeCover.length > 0">
  30. <view class="order-image-label">鞋套照片 ({{ fileList.ShoeCover.length }})</view>
  31. <view class="order-image-container">
  32. <image
  33. class="order-image"
  34. v-for="(photo, index) in fileList.ShoeCover"
  35. :key="index"
  36. :src="photo.url"
  37. mode="aspectFill"
  38. @click="previewImage(fileList.ShoeCover.map(p => p.url), photo.url)">
  39. </image>
  40. </view>
  41. </view>
  42. </view>
  43. </view>
  44. <!-- 宠物状况记录 -->
  45. <view class="order-section" v-if="petList && petList.length > 0">
  46. <view class="order-section-title">宠物状况记录</view>
  47. <view class="order-section-content">
  48. <view class="pet-record" v-for="(pet, index) in petList" :key="index">
  49. <view class="pet-name">{{ pet.title }} ({{ getPetPhotoCount(index) }})</view>
  50. <view class="pet-images" v-if="fileList['pet' + index] && fileList['pet' + index].length > 0">
  51. <view class="pet-image-container" v-for="(photo, photoIndex) in fileList['pet' + index]" :key="photoIndex">
  52. <image
  53. class="pet-image"
  54. :src="photo.url"
  55. mode="aspectFill"
  56. @click="previewImage(fileList['pet' + index].map(p => p.url), photo.url)">
  57. </image>
  58. </view>
  59. </view>
  60. </view>
  61. </view>
  62. </view>
  63. <!-- 基础服务记录 -->
  64. <view class="order-section" v-if="hasBasicServices">
  65. <view class="order-section-title">基础服务记录</view>
  66. <view class="order-section-content">
  67. <!-- 粮碗前后对比 -->
  68. <view class="service-record" v-if="fileList.foodA || fileList.foodB">
  69. <view class="service-name">粮碗前后对比</view>
  70. <view class="service-comparison">
  71. <view class="service-before-after">
  72. <view class="service-image-container" v-if="fileList.foodA && fileList.foodA.length > 0">
  73. <image
  74. class="service-image"
  75. :src="fileList.foodA[0].url"
  76. mode="aspectFill"
  77. @click="previewImage([fileList.foodA[0].url], fileList.foodA[0].url)">
  78. </image>
  79. <view class="service-image-label"></view>
  80. </view>
  81. <view class="service-image-container" v-if="fileList.foodB && fileList.foodB.length > 0">
  82. <image
  83. class="service-image"
  84. :src="fileList.foodB[0].url"
  85. mode="aspectFill"
  86. @click="previewImage([fileList.foodB[0].url], fileList.foodB[0].url)">
  87. </image>
  88. <view class="service-image-label"></view>
  89. </view>
  90. </view>
  91. </view>
  92. </view>
  93. <!-- 水碗前后对比 -->
  94. <view class="service-record" v-if="fileList.waterA || fileList.waterB">
  95. <view class="service-name">水碗前后对比</view>
  96. <view class="service-comparison">
  97. <view class="service-before-after">
  98. <view class="service-image-container" v-if="fileList.waterA && fileList.waterA.length > 0">
  99. <image
  100. class="service-image"
  101. :src="fileList.waterA[0].url"
  102. mode="aspectFill"
  103. @click="previewImage([fileList.waterA[0].url], fileList.waterA[0].url)">
  104. </image>
  105. <view class="service-image-label"></view>
  106. </view>
  107. <view class="service-image-container" v-if="fileList.waterB && fileList.waterB.length > 0">
  108. <image
  109. class="service-image"
  110. :src="fileList.waterB[0].url"
  111. mode="aspectFill"
  112. @click="previewImage([fileList.waterB[0].url], fileList.waterB[0].url)">
  113. </image>
  114. <view class="service-image-label"></view>
  115. </view>
  116. </view>
  117. </view>
  118. </view>
  119. <!-- 猫砂盆尿垫前后对比 -->
  120. <view class="service-record" v-if="fileList.urinalA || fileList.urinalB">
  121. <view class="service-name">猫砂盆尿垫前后对比</view>
  122. <view class="service-comparison">
  123. <view class="service-before-after">
  124. <view class="service-image-container" v-if="fileList.urinalA && fileList.urinalA.length > 0">
  125. <image
  126. class="service-image"
  127. :src="fileList.urinalA[0].url"
  128. mode="aspectFill"
  129. @click="previewImage([fileList.urinalA[0].url], fileList.urinalA[0].url)">
  130. </image>
  131. <view class="service-image-label"></view>
  132. </view>
  133. <view class="service-image-container" v-if="fileList.urinalB && fileList.urinalB.length > 0">
  134. <image
  135. class="service-image"
  136. :src="fileList.urinalB[0].url"
  137. mode="aspectFill"
  138. @click="previewImage([fileList.urinalB[0].url], fileList.urinalB[0].url)">
  139. </image>
  140. <view class="service-image-label"></view>
  141. </view>
  142. </view>
  143. </view>
  144. </view>
  145. </view>
  146. </view>
  147. <!-- 定制服务记录 -->
  148. <view class="order-section" v-if="projectList && projectList.length > 0">
  149. <view class="order-section-title">定制服务记录</view>
  150. <view class="order-section-content">
  151. <view class="custom-service">
  152. <view class="custom-service-item" v-for="(project, index) in projectList" :key="index">
  153. <view class="custom-service-name">{{ project.title }} ({{ getProjectPhotoCount(index) }})</view>
  154. <view class="custom-service-images" v-if="fileList['project' + index] && fileList['project' + index].length > 0">
  155. <image
  156. class="custom-service-image"
  157. v-for="(photo, photoIndex) in fileList['project' + index]"
  158. :key="photoIndex"
  159. :src="photo.url"
  160. mode="aspectFill"
  161. @click="previewImage(fileList['project' + index].map(p => p.url), photo.url)">
  162. </image>
  163. </view>
  164. </view>
  165. </view>
  166. </view>
  167. </view>
  168. <!-- 补充信息 -->
  169. <view class="order-section" v-if="form.notes">
  170. <view class="order-section-title">补充信息</view>
  171. <view class="order-section-content">
  172. <view class="notes-content">{{ form.notes }}</view>
  173. </view>
  174. </view>
  175. </view>
  176. </template>
  177. <script>
  178. import { appletOrderDateFrequencyById } from '@/api/order/order.js'
  179. export default {
  180. data() {
  181. return {
  182. id: null,
  183. orderDate: '',
  184. form: {},
  185. fileList: {},
  186. petList: [],
  187. projectList: [],
  188. isRead: false
  189. }
  190. },
  191. computed: {
  192. hasBasicServices() {
  193. return (this.fileList.foodA && this.fileList.foodA.length > 0) ||
  194. (this.fileList.foodB && this.fileList.foodB.length > 0) ||
  195. (this.fileList.waterA && this.fileList.waterA.length > 0) ||
  196. (this.fileList.waterB && this.fileList.waterB.length > 0) ||
  197. (this.fileList.urinalA && this.fileList.urinalA.length > 0) ||
  198. (this.fileList.urinalB && this.fileList.urinalB.length > 0);
  199. }
  200. },
  201. onLoad(options) {
  202. if (options.id) {
  203. this.id = options.id;
  204. this.loadOrderDetail();
  205. }
  206. },
  207. methods: {
  208. loadOrderDetail() {
  209. appletOrderDateFrequencyById(this.id)
  210. .then(res => {
  211. const data = res.data.check;
  212. const frequency = res.data.frequency;
  213. this.isRead = frequency.status == 2;
  214. // 设置订单日期
  215. if (frequency.serviceDate) {
  216. this.orderDate = this.formatDate(frequency.serviceDate);
  217. }
  218. if(res.code == 200 && data) {
  219. this.form = data;
  220. // 回显手套照片
  221. if(data.glovePhoto) {
  222. this.$set(this.fileList, 'glove', data.glovePhoto.split(',').map(url => ({ url })));
  223. }
  224. // 回显鞋套照片
  225. if(data.shoeCoverPhoto) {
  226. this.$set(this.fileList, 'ShoeCover', data.shoeCoverPhoto.split(',').map(url => ({ url })));
  227. }
  228. // 回显宠物照片
  229. if(data.petPhoto) {
  230. const pets = JSON.parse(data.petPhoto);
  231. this.petList = pets;
  232. pets.forEach((pet, index) => {
  233. if(pet.fileList) {
  234. this.$set(this.fileList, 'pet' + index, pet.fileList.split(',').map(url => ({ url })));
  235. }
  236. });
  237. }
  238. // 回显项目照片
  239. if(data.workDogImage) {
  240. const pList = JSON.parse(data.workDogImage);
  241. this.projectList = pList;
  242. pList.forEach((project, index) => {
  243. if(project.fileList) {
  244. this.$set(this.fileList, 'project' + index, project.fileList.split(',').map(url => ({ url })));
  245. }
  246. });
  247. }
  248. // 回显粮碗照片
  249. if(data.grainBowlFront) this.$set(this.fileList, 'foodA', data.grainBowlFront.split(',').map(url => ({ url })));
  250. if(data.grainBowlAfter) this.$set(this.fileList, 'foodB', data.grainBowlAfter.split(',').map(url => ({ url })));
  251. // 回显水碗照片
  252. if(data.waterBowlFront) this.$set(this.fileList, 'waterA', data.waterBowlFront.split(',').map(url => ({ url })));
  253. if(data.waterBowlAfter) this.$set(this.fileList, 'waterB', data.waterBowlAfter.split(',').map(url => ({ url })));
  254. // 回显猫砂盆/尿垫照片
  255. if(data.basinFront) this.$set(this.fileList, 'urinalA', data.basinFront.split(',').map(url => ({ url })));
  256. if(data.basinAfter) this.$set(this.fileList, 'urinalB', data.basinAfter.split(',').map(url => ({ url })));
  257. } else {
  258. // 如果没有数据,从frequency中初始化基础结构
  259. let projectNameList = [];
  260. frequency.pets.forEach((pet, i) => {
  261. this.$set(this.fileList, 'pet' + i, []);
  262. pet.orderItemList.forEach((item, index) => {
  263. this.$set(this.fileList, 'project' + index, []);
  264. });
  265. });
  266. frequency.pets.forEach((pet, i) => {
  267. this.petList.push({
  268. title: pet.name,
  269. id: pet.id,
  270. });
  271. pet.orderItemList.forEach((item, index) => {
  272. if(!projectNameList.includes(item.productName)){
  273. projectNameList.push(item.productName);
  274. this.projectList.push({
  275. title: item.productName,
  276. ids: [item.id]
  277. });
  278. } else {
  279. this.projectList[projectNameList.indexOf(item.productName)].ids.push(item.id);
  280. }
  281. });
  282. });
  283. }
  284. })
  285. .catch(err => {
  286. console.error('加载订单详情失败:', err);
  287. uni.showToast({
  288. title: '加载失败',
  289. icon: 'none'
  290. });
  291. });
  292. },
  293. // 获取宠物照片数量
  294. getPetPhotoCount(index) {
  295. const photos = this.fileList['pet' + index];
  296. return photos ? photos.length : 0;
  297. },
  298. // 获取项目照片数量
  299. getProjectPhotoCount(index) {
  300. const photos = this.fileList['project' + index];
  301. return photos ? photos.length : 0;
  302. },
  303. // 格式化日期
  304. formatDate(dateString) {
  305. if (!dateString) return '';
  306. const date = new Date(dateString);
  307. const year = date.getFullYear();
  308. const month = String(date.getMonth() + 1).padStart(2, '0');
  309. const day = String(date.getDate()).padStart(2, '0');
  310. return `${year}${month}${day}`;
  311. },
  312. // 图片预览功能
  313. previewImage(urls, current) {
  314. if (!urls || urls.length === 0) return;
  315. uni.previewImage({
  316. urls: urls,
  317. current: current || urls[0],
  318. indicator: 'number',
  319. loop: true
  320. });
  321. }
  322. }
  323. }
  324. </script>
  325. <style lang="scss">
  326. .order-detail-container {
  327. padding: 15px;
  328. background-color: #f5f5f7;
  329. min-height: 100vh;
  330. }
  331. .order-date {
  332. display: flex;
  333. align-items: center;
  334. justify-content: center;
  335. padding: 12px 16px;
  336. background-color: #FFF9EF;
  337. border-radius: 10px;
  338. margin-bottom: 15px;
  339. box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
  340. .order-date-icon {
  341. margin-right: 12px;
  342. display: flex;
  343. align-items: center;
  344. justify-content: center;
  345. width: 32px;
  346. height: 32px;
  347. background-color: rgba(255, 170, 72, 0.1);
  348. border-radius: 50%;
  349. }
  350. .order-date-text {
  351. color: #A94F20;
  352. font-size: 16px;
  353. font-weight: 500;
  354. letter-spacing: 0.5px;
  355. }
  356. }
  357. .order-section {
  358. background-color: #fff;
  359. border-radius: 8px;
  360. margin-bottom: 15px;
  361. overflow: hidden;
  362. .order-section-title {
  363. padding: 12px 15px;
  364. border-bottom: 1px solid #f0f0f0;
  365. color: #333;
  366. font-size: 16px;
  367. font-weight: 500;
  368. position: relative;
  369. &::before {
  370. content: '';
  371. position: absolute;
  372. left: 0;
  373. top: 12px;
  374. height: 16px;
  375. width: 4px;
  376. background-color: #ffaa48;
  377. border-radius: 0 2px 2px 0;
  378. }
  379. }
  380. .order-section-content {
  381. padding: 15px;
  382. }
  383. }
  384. .order-image-item {
  385. margin-bottom: 15px;
  386. .order-image-label {
  387. color: #666;
  388. font-size: 14px;
  389. margin-bottom: 8px;
  390. }
  391. .order-image-container {
  392. display: flex;
  393. flex-wrap: wrap;
  394. gap: 10px;
  395. .order-image {
  396. width: 80px;
  397. height: 80px;
  398. border-radius: 4px;
  399. object-fit: cover;
  400. }
  401. }
  402. }
  403. .pet-record {
  404. margin-bottom: 20px;
  405. .pet-name {
  406. color: #333;
  407. font-size: 15px;
  408. font-weight: 500;
  409. margin-bottom: 10px;
  410. }
  411. .pet-images {
  412. display: flex;
  413. flex-wrap: wrap;
  414. gap: 10px;
  415. .pet-image-container {
  416. .pet-image {
  417. width: 80px;
  418. height: 80px;
  419. border-radius: 4px;
  420. object-fit: cover;
  421. }
  422. }
  423. }
  424. }
  425. .service-record {
  426. margin-bottom: 20px;
  427. .service-name {
  428. color: #333;
  429. font-size: 15px;
  430. font-weight: 500;
  431. margin-bottom: 10px;
  432. }
  433. .service-comparison {
  434. .service-before-after {
  435. display: flex;
  436. gap: 15px;
  437. .service-image-container {
  438. position: relative;
  439. .service-image {
  440. width: 80px;
  441. height: 80px;
  442. border-radius: 4px;
  443. object-fit: cover;
  444. }
  445. .service-image-label {
  446. position: absolute;
  447. bottom: 0;
  448. left: 0;
  449. background-color: rgba(0,0,0,0.5);
  450. color: #fff;
  451. padding: 2px 8px;
  452. font-size: 12px;
  453. border-radius: 0 0 0 4px;
  454. }
  455. }
  456. }
  457. }
  458. }
  459. .custom-service {
  460. .custom-service-item {
  461. margin-bottom: 15px;
  462. .custom-service-name {
  463. color: #333;
  464. font-size: 15px;
  465. font-weight: 500;
  466. margin-bottom: 10px;
  467. }
  468. .custom-service-images {
  469. display: flex;
  470. flex-wrap: wrap;
  471. gap: 10px;
  472. .custom-service-image {
  473. width: 80px;
  474. height: 80px;
  475. border-radius: 4px;
  476. object-fit: cover;
  477. }
  478. }
  479. }
  480. }
  481. .notes-content {
  482. color: #333;
  483. font-size: 14px;
  484. line-height: 1.6;
  485. padding: 12px 16px;
  486. background-color: #F5F5F5;
  487. border-radius: 8px;
  488. min-height: 120px;
  489. white-space: pre-wrap;
  490. word-break: break-word;
  491. }
  492. </style>