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.

653 lines
14 KiB

  1. <template>
  2. <view class="order-modify-container">
  3. <view class="content">
  4. <!-- 加载状态 -->
  5. <view class="loading-container" v-if="loading">
  6. <view class="loading-circle"></view>
  7. <text class="loading-text">加载中...</text>
  8. </view>
  9. <view v-else>
  10. <view class="info-section modify-instruction-section">
  11. <view class="gradient-header">
  12. <view class="section-title">订单修改说明</view>
  13. </view>
  14. <view class="info-content">
  15. <view class="desc-item">
  16. <view class="desc-icon">🐾</view>
  17. <view class="desc-text">您可以对<text class="highlight">不涉及服务费用变动</text>的订单信息进行修改</view>
  18. </view>
  19. <view class="desc-item">
  20. <view class="desc-icon">🐾</view>
  21. <view class="desc-text">若需修改涉及金额变动的服务信息"增加或删除服务宠物/服务项目"可点击下方按钮<text class="highlight">修改服务</text>来修改或者您也可"取消订单""重新下单"</view>
  22. </view>
  23. </view>
  24. </view>
  25. <view class="info-section service-modify-section">
  26. <view class="section-header">
  27. <view class="accent-bar"></view>
  28. <view class="section-title">服务修改信息</view>
  29. </view>
  30. <view class="info-content">
  31. <view class="info-row editable">
  32. <text class="info-label">联系人</text>
  33. <input class="info-value-input" type="text" v-model="modifyInfo.contactName" placeholder="请输入联系人姓名" />
  34. </view>
  35. <view class="info-row editable">
  36. <text class="info-label">联系方式</text>
  37. <input class="info-value-input" type="text" v-model="modifyInfo.contactPhone" placeholder="请输入联系电话" />
  38. </view>
  39. <view class="info-row clickable">
  40. <text class="info-label">钥匙交接方式</text>
  41. <view class="info-value-container">
  42. <text class="info-value">{{modifyInfo.keyHandoverMethod || '存于快递柜'}}</text>
  43. <text class="arrow-right">></text>
  44. </view>
  45. </view>
  46. </view>
  47. </view>
  48. </view>
  49. </view>
  50. <!-- 底部按钮区域 -->
  51. <view class="order-modify-footer">
  52. <view class="footer-btn modify-service-btn" @click="modifyOrder">
  53. <view class="btn-icon">📝</view>
  54. <text class="btn-text">修改服务</text>
  55. </view>
  56. <view class="footer-btn confirm-modify-btn" @click="confirmModify">
  57. <text class="btn-text">确认修改</text>
  58. </view>
  59. </view>
  60. <!-- 客服组件 -->
  61. <Kefu></Kefu>
  62. </view>
  63. </template>
  64. <script>
  65. import { getOrderDetail, updateBaseOrder } from '@/api/order/order.js';
  66. import { mapState } from 'vuex';
  67. import { getOpenIdKey } from '@/utils/auth'
  68. import {
  69. getTeacherDetail,
  70. } from "@/api/order/order"
  71. import positionMixin from '@/mixins/position';
  72. import {
  73. getAddressDetails,addAddress,updateAddress
  74. } from '@/api/system/address.js'
  75. export default {
  76. mixins: [positionMixin],
  77. components: {
  78. },
  79. data() {
  80. return {
  81. loading: false,
  82. orderId: null,
  83. modifyInfo: {
  84. contactName: '',
  85. contactPhone: '',
  86. paymentMethod: '',
  87. keyHandoverMethod: '',
  88. },
  89. showCancelOrderPopup: false,
  90. originalOrderData: null
  91. }
  92. },
  93. computed: {
  94. ...mapState(['teacherLevelList'])
  95. },
  96. onLoad(options) {
  97. if (options.orderId) {
  98. this.orderId = options.orderId;
  99. this.loadOrderData();
  100. } else {
  101. uni.showToast({
  102. title: '订单ID不存在',
  103. icon: 'none'
  104. });
  105. setTimeout(() => {
  106. uni.navigateBack();
  107. }, 1500);
  108. }
  109. },
  110. methods: {
  111. // 加载订单数据
  112. async loadOrderData() {
  113. if (!this.orderId) return;
  114. const params = {
  115. openId: getOpenIdKey(),
  116. orderId: this.orderId
  117. };
  118. this.loading = true;
  119. try {
  120. const res = await getOrderDetail(params);
  121. if (res) {
  122. const orderData = res;
  123. this.originalOrderData = orderData;
  124. // 初始化修改信息
  125. this.modifyInfo.contactName = orderData.receiverName || '';
  126. this.modifyInfo.contactPhone = orderData.receiverPhone || '';
  127. this.modifyInfo.paymentMethod = orderData.paymentMethod || '';
  128. } else {
  129. uni.showToast({
  130. title: res.msg || '获取订单信息失败',
  131. icon: 'none'
  132. });
  133. }
  134. } catch (error) {
  135. console.error('获取订单数据失败', error);
  136. uni.showToast({
  137. title: '网络异常,请稍后重试',
  138. icon: 'none'
  139. });
  140. } finally {
  141. this.loading = false;
  142. }
  143. },
  144. // 确认修改
  145. async confirmModify() {
  146. // 表单验证
  147. if (!this.modifyInfo.contactName.trim()) {
  148. return uni.showToast({
  149. title: '请输入联系人姓名',
  150. icon: 'none'
  151. });
  152. }
  153. if (!this.modifyInfo.contactPhone.trim()) {
  154. return uni.showToast({
  155. title: '请输入联系方式',
  156. icon: 'none'
  157. });
  158. }
  159. // 验证手机号
  160. const phoneReg = /^1[3-9]\d{9}$/;
  161. if (!phoneReg.test(this.modifyInfo.contactPhone)) {
  162. return uni.showToast({
  163. title: '请输入正确的手机号码',
  164. icon: 'none'
  165. });
  166. }
  167. // 检查是否有修改(如果有原始数据的话)
  168. let hasChanged = false;
  169. if (this.originalOrderData) {
  170. hasChanged =
  171. this.modifyInfo.contactName !== this.originalOrderData.contactName ||
  172. this.modifyInfo.contactPhone !== this.originalOrderData.contactPhone ||
  173. this.modifyInfo.paymentMethod !== this.originalOrderData.paymentMethod;
  174. } else {
  175. // 如果没有原始数据,只要有输入内容就认为有修改
  176. hasChanged =
  177. this.modifyInfo.contactName.trim() !== '' ||
  178. this.modifyInfo.contactPhone.trim() !== '';
  179. }
  180. if (!hasChanged) {
  181. return uni.showToast({
  182. title: '未检测到任何修改',
  183. icon: 'none'
  184. });
  185. }
  186. this.loading = true;
  187. try {
  188. const updateData = {
  189. id: this.orderId,
  190. contactName: this.modifyInfo.contactName,
  191. contactPhone: this.modifyInfo.contactPhone,
  192. paymentMethod: this.modifyInfo.paymentMethod,
  193. };
  194. const res = await updateBaseOrder(updateData);
  195. if (res && res.code === 200) {
  196. uni.showToast({
  197. title: '订单修改成功',
  198. icon: 'success'
  199. });
  200. setTimeout(() => {
  201. uni.navigateBack();
  202. }, 1500);
  203. } else {
  204. uni.showToast({
  205. title: res.msg || '订单修改失败',
  206. icon: 'none'
  207. });
  208. }
  209. } catch (error) {
  210. console.error('订单修改失败', error);
  211. uni.showToast({
  212. title: '网络异常,请稍后重试',
  213. icon: 'none'
  214. });
  215. } finally {
  216. this.loading = false;
  217. }
  218. },
  219. // 修改服务(跳转到下单流程)
  220. async modifyOrder() {
  221. this.$globalData.newOrderData.orderId = this.orderId;
  222. let order = this.originalOrderData
  223. this.$globalData.newOrderData.originalOrderData = order
  224. // 验证地址是否存在
  225. if(order.addressId) {
  226. try {
  227. const addressRes = await getAddressDetails(order.addressId);
  228. if(addressRes && addressRes.id) {
  229. // 地址存在,设置地址信息
  230. this.$globalData.newOrderData.currentAddress = {
  231. id: order.addressId,
  232. name: order.receiverName,
  233. phone: order.receiverPhone,
  234. province: order.receiverProvince,
  235. city: order.receiverCity,
  236. district: order.receiverDistrict,
  237. detailAddress: order.receiverDetailAddress,
  238. latitude: order.latitude,
  239. longitude: order.longitude,
  240. }
  241. } else {
  242. // 地址不存在,不设置地址信息
  243. console.log('地址不存在,addressId:', order.addressId);
  244. this.$globalData.newOrderData.currentAddress = {};
  245. }
  246. } catch (error) {
  247. console.error('验证地址失败:', error);
  248. // 验证失败时也不设置地址信息
  249. this.$globalData.newOrderData.currentAddress = {};
  250. }
  251. } else {
  252. // 没有地址ID,不设置地址信息
  253. this.$globalData.newOrderData.currentAddress = {};
  254. }
  255. if(order.teacherId){
  256. getTeacherDetail({
  257. userId : order.teacherId
  258. }).then(response => {
  259. if (response) {
  260. let companionInfo = response
  261. companionInfo.distanceText = this.calculateDistanceAddress(response.appletAddresseList)
  262. this.buyInfo.teacher = companionInfo
  263. }
  264. })
  265. }
  266. if(order.companionLevel){
  267. this.$globalData.newOrderData.companionLevel =
  268. this.teacherLevelList.find(item => item.paramValueNum == order.companionLevel);
  269. }
  270. // 处理提前熟悉相关数据
  271. if(order.needPreFamiliarize) {
  272. this.$globalData.newOrderData.needPreFamiliarize = ['是否提前熟悉']
  273. }
  274. // 组装宠物数据
  275. if(order.petVOList && order.petVOList.length > 0) {
  276. this.$globalData.newOrderData.currentPets = order.petVOList.map(pet => {
  277. // 获取该宠物的服务日期
  278. const petServices = order.orderServiceList.filter(service => service.petId === pet.id);
  279. const selectedDate = petServices.map(service => ({
  280. date: service.serviceDate,
  281. info: "预定"
  282. }));
  283. return {
  284. ...pet,
  285. checked: ['checked'], // 默认选中
  286. selectedDate,
  287. };
  288. });
  289. }
  290. uni.navigateTo({
  291. url: `/pages/newOrder/serviceNew`
  292. });
  293. },
  294. }
  295. }
  296. </script>
  297. <style lang="scss" scoped>
  298. .order-modify-container {
  299. background-color: #f5f5f5;
  300. min-height: 100vh;
  301. display: flex;
  302. flex-direction: column;
  303. }
  304. .header {
  305. background-color: #FFAA48;
  306. padding: 20rpx 30rpx;
  307. color: #FFFFFF;
  308. .title {
  309. font-size: 36rpx;
  310. font-weight: bold;
  311. }
  312. }
  313. .content {
  314. flex: 1;
  315. padding: 20rpx;
  316. }
  317. .loading-container {
  318. display: flex;
  319. flex-direction: column;
  320. align-items: center;
  321. justify-content: center;
  322. height: 300rpx;
  323. margin-top: 100rpx;
  324. }
  325. .loading-circle {
  326. width: 80rpx;
  327. height: 80rpx;
  328. border: 4rpx solid #FFAA48;
  329. border-top-color: transparent;
  330. border-radius: 50%;
  331. animation: spin 1s linear infinite;
  332. margin-bottom: 20rpx;
  333. }
  334. @keyframes spin {
  335. 0% {
  336. transform: rotate(0deg);
  337. }
  338. 100% {
  339. transform: rotate(360deg);
  340. }
  341. }
  342. .loading-text {
  343. font-size: 28rpx;
  344. color: #666;
  345. }
  346. .info-section {
  347. background-color: #FFFFFF;
  348. border-radius: 20rpx;
  349. margin-bottom: 20rpx;
  350. overflow: hidden;
  351. }
  352. .modify-instruction-section {
  353. background: linear-gradient(135deg, #FFF5E6 0%, #FFFFFF 100%);
  354. border: 1px solid #FFE4B3;
  355. }
  356. .gradient-header {
  357. //background: linear-gradient(135deg, #FFAA48 0%, #FFB366 100%);
  358. padding: 20rpx;
  359. border-radius: 20rpx 20rpx 0 0;
  360. }
  361. .modify-instruction-section .section-title {
  362. //color: #FFFFFF;
  363. border-bottom: none;
  364. padding: 0;
  365. font-size: 32rpx;
  366. font-weight: bold;
  367. }
  368. .service-modify-section {
  369. background-color: #FFFFFF;
  370. }
  371. .section-header {
  372. display: flex;
  373. align-items: center;
  374. padding: 20rpx;
  375. border-bottom: 1px solid #EEEEEE;
  376. }
  377. .accent-bar {
  378. width: 6rpx;
  379. height: 32rpx;
  380. background-color: #FFAA48;
  381. border-radius: 3rpx;
  382. margin-right: 15rpx;
  383. }
  384. .service-modify-section .section-title {
  385. color: #333333;
  386. border-bottom: none;
  387. padding: 0;
  388. font-size: 32rpx;
  389. font-weight: bold;
  390. }
  391. .info-row {
  392. display: flex;
  393. align-items: center;
  394. padding: 20rpx;
  395. border-bottom: 1px solid #F5F5F5;
  396. &:last-child {
  397. border-bottom: none;
  398. }
  399. &.clickable {
  400. cursor: pointer;
  401. }
  402. }
  403. .info-row .info-label {
  404. width: 180rpx;
  405. font-size: 28rpx;
  406. color: #666666;
  407. flex-shrink: 0;
  408. }
  409. .info-row .info-value {
  410. flex: 1;
  411. font-size: 28rpx;
  412. color: #333333;
  413. text-align: right;
  414. }
  415. .info-row.editable {
  416. .info-value-input {
  417. flex: 1;
  418. font-size: 28rpx;
  419. color: #333333;
  420. text-align: right;
  421. border: none;
  422. background: transparent;
  423. padding: 0;
  424. &::placeholder {
  425. color: #CCCCCC;
  426. }
  427. &:focus {
  428. outline: none;
  429. }
  430. }
  431. }
  432. .info-value-container {
  433. display: flex;
  434. align-items: center;
  435. justify-content: flex-end;
  436. flex: 1;
  437. }
  438. .arrow-right {
  439. font-size: 24rpx;
  440. color: #CCCCCC;
  441. margin-left: 10rpx;
  442. }
  443. .section-title {
  444. font-size: 32rpx;
  445. font-weight: bold;
  446. padding: 20rpx;
  447. border-bottom: 1px solid #EEEEEE;
  448. }
  449. .info-content {
  450. padding: 20rpx;
  451. }
  452. .desc-item {
  453. display: flex;
  454. align-items: flex-start;
  455. margin-bottom: 20rpx;
  456. &:last-child {
  457. margin-bottom: 0;
  458. }
  459. }
  460. .desc-icon {
  461. font-size: 24rpx;
  462. margin-right: 15rpx;
  463. margin-top: 4rpx;
  464. color: #FFAA48;
  465. }
  466. .desc-text {
  467. font-size: 28rpx;
  468. color: #666;
  469. line-height: 1.6;
  470. flex: 1;
  471. }
  472. .highlight {
  473. color: #FFAA48;
  474. font-weight: 500;
  475. }
  476. .info-item {
  477. display: flex;
  478. align-items: center;
  479. margin-bottom: 20rpx;
  480. &:last-child {
  481. margin-bottom: 0;
  482. }
  483. .info-label {
  484. width: 180rpx;
  485. font-size: 28rpx;
  486. color: #666;
  487. }
  488. .info-value {
  489. flex: 1;
  490. font-size: 28rpx;
  491. color: #333;
  492. padding: 10rpx;
  493. border: 1px solid #EEEEEE;
  494. border-radius: 8rpx;
  495. }
  496. }
  497. .payment-method {
  498. .payment-value {
  499. display: flex;
  500. align-items: center;
  501. input {
  502. flex: 1;
  503. border: none;
  504. padding: 0;
  505. }
  506. .arrow-right {
  507. font-size: 24rpx;
  508. color: #999;
  509. margin-left: 10rpx;
  510. }
  511. }
  512. }
  513. .reason-input {
  514. width: 100%;
  515. height: 200rpx;
  516. font-size: 28rpx;
  517. padding: 20rpx;
  518. box-sizing: border-box;
  519. border: 1px solid #EEEEEE;
  520. border-radius: 8rpx;
  521. }
  522. .order-modify-footer {
  523. padding: 30rpx 20rpx;
  524. background-color: #FFFFFF;
  525. border-top: 1px solid #EEEEEE;
  526. display: flex;
  527. justify-content: space-between;
  528. align-items: center;
  529. gap: 20rpx;
  530. }
  531. .footer-btn {
  532. display: flex;
  533. flex-direction: column;
  534. align-items: center;
  535. justify-content: center;
  536. padding: 20rpx;
  537. border-radius: 12rpx;
  538. text-align: center;
  539. transition: all 0.3s ease;
  540. &:active {
  541. transform: scale(0.95);
  542. }
  543. }
  544. .modify-service-btn {
  545. background-color: #FFFFFF;
  546. border-radius: 12rpx;
  547. padding: 20rpx 30rpx;
  548. min-width: 120rpx;
  549. .btn-icon {
  550. font-size: 32rpx;
  551. margin-bottom: 8rpx;
  552. }
  553. .btn-text {
  554. font-size: 24rpx;
  555. color: #333333;
  556. font-weight: 500;
  557. }
  558. }
  559. .confirm-modify-btn {
  560. background-color: #FFAA48;
  561. border: none;
  562. border-radius: 50rpx;
  563. padding: 20rpx 60rpx;
  564. flex: 1;
  565. .btn-text {
  566. font-size: 32rpx;
  567. color: #FFFFFF;
  568. font-weight: bold;
  569. }
  570. }
  571. </style>