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.

651 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. // 验证地址是否存在
  224. if(order.addressId) {
  225. try {
  226. const addressRes = await getAddressDetails(order.addressId);
  227. if(addressRes && addressRes.id) {
  228. // 地址存在,设置地址信息
  229. this.$globalData.newOrderData.currentAddress = {
  230. id: order.addressId,
  231. name: order.receiverName,
  232. phone: order.receiverPhone,
  233. province: order.receiverProvince,
  234. city: order.receiverCity,
  235. district: order.receiverDistrict,
  236. detailAddress: order.receiverDetailAddress,
  237. latitude: order.latitude,
  238. longitude: order.longitude,
  239. }
  240. } else {
  241. // 地址不存在,不设置地址信息
  242. console.log('地址不存在,addressId:', order.addressId);
  243. this.$globalData.newOrderData.currentAddress = {};
  244. }
  245. } catch (error) {
  246. console.error('验证地址失败:', error);
  247. // 验证失败时也不设置地址信息
  248. this.$globalData.newOrderData.currentAddress = {};
  249. }
  250. } else {
  251. // 没有地址ID,不设置地址信息
  252. this.$globalData.newOrderData.currentAddress = {};
  253. }
  254. if(order.teacherId){
  255. getTeacherDetail({
  256. userId : order.teacherId
  257. }).then(response => {
  258. if (response) {
  259. let companionInfo = response
  260. companionInfo.distanceText = this.calculateDistanceAddress(response.appletAddresseList)
  261. this.buyInfo.teacher = companionInfo
  262. }
  263. })
  264. }
  265. if(order.companionLevel){
  266. this.$globalData.newOrderData.companionLevel =
  267. this.teacherLevelList.find(item => item.paramValueNum == order.companionLevel);
  268. }
  269. // 处理提前熟悉相关数据
  270. if(order.needPreFamiliarize) {
  271. this.$globalData.newOrderData.needPreFamiliarize = ['是否提前熟悉']
  272. }
  273. // 组装宠物数据
  274. if(order.petVOList && order.petVOList.length > 0) {
  275. this.$globalData.newOrderData.currentPets = order.petVOList.map(pet => {
  276. // 获取该宠物的服务日期
  277. const petServices = order.orderServiceList.filter(service => service.petId === pet.id);
  278. const selectedDate = petServices.map(service => ({
  279. date: service.serviceDate,
  280. info: "预定"
  281. }));
  282. return {
  283. ...pet,
  284. checked: ['checked'], // 默认选中
  285. selectedDate,
  286. };
  287. });
  288. }
  289. uni.navigateTo({
  290. url: `/pages/newOrder/serviceNew`
  291. });
  292. },
  293. }
  294. }
  295. </script>
  296. <style lang="scss" scoped>
  297. .order-modify-container {
  298. background-color: #f5f5f5;
  299. min-height: 100vh;
  300. display: flex;
  301. flex-direction: column;
  302. }
  303. .header {
  304. background-color: #FFAA48;
  305. padding: 20rpx 30rpx;
  306. color: #FFFFFF;
  307. .title {
  308. font-size: 36rpx;
  309. font-weight: bold;
  310. }
  311. }
  312. .content {
  313. flex: 1;
  314. padding: 20rpx;
  315. }
  316. .loading-container {
  317. display: flex;
  318. flex-direction: column;
  319. align-items: center;
  320. justify-content: center;
  321. height: 300rpx;
  322. margin-top: 100rpx;
  323. }
  324. .loading-circle {
  325. width: 80rpx;
  326. height: 80rpx;
  327. border: 4rpx solid #FFAA48;
  328. border-top-color: transparent;
  329. border-radius: 50%;
  330. animation: spin 1s linear infinite;
  331. margin-bottom: 20rpx;
  332. }
  333. @keyframes spin {
  334. 0% {
  335. transform: rotate(0deg);
  336. }
  337. 100% {
  338. transform: rotate(360deg);
  339. }
  340. }
  341. .loading-text {
  342. font-size: 28rpx;
  343. color: #666;
  344. }
  345. .info-section {
  346. background-color: #FFFFFF;
  347. border-radius: 20rpx;
  348. margin-bottom: 20rpx;
  349. overflow: hidden;
  350. }
  351. .modify-instruction-section {
  352. background: linear-gradient(135deg, #FFF5E6 0%, #FFFFFF 100%);
  353. border: 1px solid #FFE4B3;
  354. }
  355. .gradient-header {
  356. //background: linear-gradient(135deg, #FFAA48 0%, #FFB366 100%);
  357. padding: 20rpx;
  358. border-radius: 20rpx 20rpx 0 0;
  359. }
  360. .modify-instruction-section .section-title {
  361. //color: #FFFFFF;
  362. border-bottom: none;
  363. padding: 0;
  364. font-size: 32rpx;
  365. font-weight: bold;
  366. }
  367. .service-modify-section {
  368. background-color: #FFFFFF;
  369. }
  370. .section-header {
  371. display: flex;
  372. align-items: center;
  373. padding: 20rpx;
  374. border-bottom: 1px solid #EEEEEE;
  375. }
  376. .accent-bar {
  377. width: 6rpx;
  378. height: 32rpx;
  379. background-color: #FFAA48;
  380. border-radius: 3rpx;
  381. margin-right: 15rpx;
  382. }
  383. .service-modify-section .section-title {
  384. color: #333333;
  385. border-bottom: none;
  386. padding: 0;
  387. font-size: 32rpx;
  388. font-weight: bold;
  389. }
  390. .info-row {
  391. display: flex;
  392. align-items: center;
  393. padding: 20rpx;
  394. border-bottom: 1px solid #F5F5F5;
  395. &:last-child {
  396. border-bottom: none;
  397. }
  398. &.clickable {
  399. cursor: pointer;
  400. }
  401. }
  402. .info-row .info-label {
  403. width: 180rpx;
  404. font-size: 28rpx;
  405. color: #666666;
  406. flex-shrink: 0;
  407. }
  408. .info-row .info-value {
  409. flex: 1;
  410. font-size: 28rpx;
  411. color: #333333;
  412. text-align: right;
  413. }
  414. .info-row.editable {
  415. .info-value-input {
  416. flex: 1;
  417. font-size: 28rpx;
  418. color: #333333;
  419. text-align: right;
  420. border: none;
  421. background: transparent;
  422. padding: 0;
  423. &::placeholder {
  424. color: #CCCCCC;
  425. }
  426. &:focus {
  427. outline: none;
  428. }
  429. }
  430. }
  431. .info-value-container {
  432. display: flex;
  433. align-items: center;
  434. justify-content: flex-end;
  435. flex: 1;
  436. }
  437. .arrow-right {
  438. font-size: 24rpx;
  439. color: #CCCCCC;
  440. margin-left: 10rpx;
  441. }
  442. .section-title {
  443. font-size: 32rpx;
  444. font-weight: bold;
  445. padding: 20rpx;
  446. border-bottom: 1px solid #EEEEEE;
  447. }
  448. .info-content {
  449. padding: 20rpx;
  450. }
  451. .desc-item {
  452. display: flex;
  453. align-items: flex-start;
  454. margin-bottom: 20rpx;
  455. &:last-child {
  456. margin-bottom: 0;
  457. }
  458. }
  459. .desc-icon {
  460. font-size: 24rpx;
  461. margin-right: 15rpx;
  462. margin-top: 4rpx;
  463. color: #FFAA48;
  464. }
  465. .desc-text {
  466. font-size: 28rpx;
  467. color: #666;
  468. line-height: 1.6;
  469. flex: 1;
  470. }
  471. .highlight {
  472. color: #FFAA48;
  473. font-weight: 500;
  474. }
  475. .info-item {
  476. display: flex;
  477. align-items: center;
  478. margin-bottom: 20rpx;
  479. &:last-child {
  480. margin-bottom: 0;
  481. }
  482. .info-label {
  483. width: 180rpx;
  484. font-size: 28rpx;
  485. color: #666;
  486. }
  487. .info-value {
  488. flex: 1;
  489. font-size: 28rpx;
  490. color: #333;
  491. padding: 10rpx;
  492. border: 1px solid #EEEEEE;
  493. border-radius: 8rpx;
  494. }
  495. }
  496. .payment-method {
  497. .payment-value {
  498. display: flex;
  499. align-items: center;
  500. input {
  501. flex: 1;
  502. border: none;
  503. padding: 0;
  504. }
  505. .arrow-right {
  506. font-size: 24rpx;
  507. color: #999;
  508. margin-left: 10rpx;
  509. }
  510. }
  511. }
  512. .reason-input {
  513. width: 100%;
  514. height: 200rpx;
  515. font-size: 28rpx;
  516. padding: 20rpx;
  517. box-sizing: border-box;
  518. border: 1px solid #EEEEEE;
  519. border-radius: 8rpx;
  520. }
  521. .order-modify-footer {
  522. padding: 30rpx 20rpx;
  523. background-color: #FFFFFF;
  524. border-top: 1px solid #EEEEEE;
  525. display: flex;
  526. justify-content: space-between;
  527. align-items: center;
  528. gap: 20rpx;
  529. }
  530. .footer-btn {
  531. display: flex;
  532. flex-direction: column;
  533. align-items: center;
  534. justify-content: center;
  535. padding: 20rpx;
  536. border-radius: 12rpx;
  537. text-align: center;
  538. transition: all 0.3s ease;
  539. &:active {
  540. transform: scale(0.95);
  541. }
  542. }
  543. .modify-service-btn {
  544. background-color: #FFFFFF;
  545. border-radius: 12rpx;
  546. padding: 20rpx 30rpx;
  547. min-width: 120rpx;
  548. .btn-icon {
  549. font-size: 32rpx;
  550. margin-bottom: 8rpx;
  551. }
  552. .btn-text {
  553. font-size: 24rpx;
  554. color: #333333;
  555. font-weight: 500;
  556. }
  557. }
  558. .confirm-modify-btn {
  559. background-color: #FFAA48;
  560. border: none;
  561. border-radius: 50rpx;
  562. padding: 20rpx 60rpx;
  563. flex: 1;
  564. .btn-text {
  565. font-size: 32rpx;
  566. color: #FFFFFF;
  567. font-weight: bold;
  568. }
  569. }
  570. </style>