建材商城系统20241014
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.

705 lines
20 KiB

8 months ago
  1. <template>
  2. <view class="hand-firm">
  3. <navbar
  4. title="确定下单"
  5. leftClick
  6. @leftClick="$utils.navigateBack"
  7. />
  8. <!-- 商品信息 -->
  9. <view class="content-wrapper">
  10. <!-- 快捷下单内容展示 -->
  11. <view class="quick-order-content" v-if="quickOrderContent.type">
  12. <view class="section-wrapper">
  13. <view class="section-title">
  14. <view class="title-bar"></view>
  15. <text>{{getOrderTypeName(quickOrderContent.type)}}</text>
  16. </view>
  17. <!-- 拍照订单显示图片 -->
  18. <view class="photo-content" v-if="quickOrderContent.type === '0' && quickOrderContent.imageUrl">
  19. <image :src="quickOrderContent.imageUrl" mode="aspectFill" class="order-image" @click="previewImage"></image>
  20. </view>
  21. <!-- 语音订单显示语音信息 -->
  22. <voice-player
  23. v-if="quickOrderContent.type === '2' && quickOrderContent.voiceUrl"
  24. :voiceUrl="quickOrderContent.voiceUrl"
  25. @playStarted="onVoicePlayStarted"
  26. @playEnded="onVoicePlayEnded"
  27. @playError="onVoicePlayError"
  28. ></voice-player>
  29. </view>
  30. </view>
  31. <uv-checkbox-group
  32. shape="circle"
  33. v-model="checkboxValue">
  34. <view class="section-wrapper">
  35. <view class="section-title">
  36. <view class="title-bar"></view>
  37. <text>商品信息</text>
  38. </view>
  39. <view class="product-card" v-for="(info, index) in productInfo.commonShop"
  40. :key="index">
  41. <view class="checkbox">
  42. <uv-checkbox
  43. :name="info.id"
  44. activeColor="#eb3300"
  45. size="40rpx"
  46. icon-size="35rpx"
  47. ></uv-checkbox>
  48. </view>
  49. <image class="product-image" :src="info.image &&
  50. info.image.split(',')[0]" mode="aspectFill"></image>
  51. <view class="product-details">
  52. <view class="product-name">{{info.name || ''}}</view>
  53. <view class="product-tags">
  54. <text class="tag">快速下单</text>
  55. </view>
  56. <view class="product-price">
  57. <text class="price-value">¥{{info.price || ''}}</text>
  58. <text class="price-unit">/{{info.unit || ''}}</text>
  59. </view>
  60. <view class="selectNum">
  61. <uv-number-box v-model="info.selectNum"
  62. button-size="60rpx"
  63. inputWidth="200rpx"
  64. ></uv-number-box>
  65. </view>
  66. </view>
  67. </view>
  68. </view>
  69. </uv-checkbox-group>
  70. <!-- 个人信息 -->
  71. <view class="section-wrapper">
  72. <view class="section-title">
  73. <view class="title-bar"></view>
  74. <text>填写个人信息</text>
  75. </view>
  76. <view class="address-section">
  77. <view v-if="addressTotal > 0" class="has-address">
  78. <view class="address-info" @click="openAddress">
  79. <view class="address-user">
  80. <text class="name">{{address.name}}</text>
  81. <text class="phone">{{address.phone}}</text>
  82. </view>
  83. <view class="address-detail">
  84. {{address.address}} {{address.addressDetails}}
  85. </view>
  86. <view class="address-action">
  87. <text class="address-tip">选择其他已添加过的地址</text>
  88. <view class="arrow-right"></view>
  89. </view>
  90. </view>
  91. <view class="address-tag">
  92. <text>已添加过的地址</text>
  93. </view>
  94. </view>
  95. <!-- 地址表单 -->
  96. <view v-else class="no-address">
  97. <redact-address-form
  98. ref="addressForm"
  99. @saveOrUpdate="handleAddressSave"
  100. ></redact-address-form>
  101. </view>
  102. </view>
  103. </view>
  104. </view>
  105. <!-- 底部按钮 -->
  106. <view class="order-submit" v-if="productInfo.isPay != 'Y'">
  107. <button class="submit-btn-close" @click="cancelOrderFast">取消</button>
  108. <button class="submit-btn" @click="submitOrder">快捷下单{{ totalPrice }}</button>
  109. </view>
  110. <view class="order-submit" v-else>
  111. <button class="submit-btn" style="background-color: #999999; color: #cccccc;">已下单</button>
  112. </view>
  113. <!-- 地址选择弹窗 -->
  114. <uv-popup ref="addressPopup" :round="30" style="padding-bottom: 90rpx;">
  115. <addressList ref="addressList" height="60vh" @select="selectAddress" />
  116. <view class="add-btn">
  117. <view @click="$utils.navigateTo('/pages_order/mine/address?type=back')" class="button-submit">新增地址</view>
  118. </view>
  119. </uv-popup>
  120. <kefu/>
  121. </view>
  122. </template>
  123. <script>
  124. import redactAddressForm from '../components/address/redactAddressForm.vue';
  125. import addressList from '../components/address/addressList.vue';
  126. import VoicePlayer from '@/components/VoicePlayer.vue';
  127. export default {
  128. components: {
  129. redactAddressForm,
  130. addressList,
  131. VoicePlayer
  132. },
  133. data() {
  134. return {
  135. checkboxValue : [],
  136. productInfo: {
  137. commonShop : [],
  138. }, // 信息
  139. orderId: '', // 订单ID
  140. address: {
  141. name: '请选择地址',
  142. address: '',
  143. phone: ''
  144. },
  145. addressTotal: 0,
  146. orderInfo: [],
  147. isLoading: false, // 加载状态
  148. shouldSubmitOrder: false, // 标记是否应该在获取地址后自动提交订单
  149. quickOrderContent: { // 快捷下单内容
  150. type: '',
  151. imageUrl: '',
  152. voiceUrl: ''
  153. }
  154. };
  155. },
  156. computed: {
  157. totalPrice(){
  158. if (!this.checkboxValue.length || !this.productInfo.commonShop) {
  159. return 0
  160. }
  161. let price = 0
  162. this.productInfo.commonShop.forEach(n => {
  163. if(this.checkboxValue.includes(n.id)){
  164. price += n.price * (n.selectNum || 1)
  165. }
  166. })
  167. return Number(price).toFixed(2)
  168. },
  169. },
  170. onLoad(options) {
  171. // 获取订单ID
  172. if (options.orderId) {
  173. this.orderId = options.orderId;
  174. this.getOrderInfo();
  175. }
  176. },
  177. onShow() {
  178. // 获取地址列表
  179. this.getAddressList();
  180. },
  181. methods: {
  182. // 获取订单信息
  183. getOrderInfo() {
  184. // this.$api('getOrderInfo', res => {
  185. // if (res.code === 200 && res.result[0]) {
  186. // // 如果返回商品信息,则设置商品信息 commonShop
  187. // this.productInfo = res.result[0];
  188. // this.productInfo.commonShop.forEach(n => {
  189. // this.checkboxValue.push(n.id)
  190. // })
  191. // }
  192. // });
  193. this.$api('getAddOrderInfo', {
  194. orderId : this.orderId
  195. }, res => {
  196. if (res.code === 200 && res.result) {
  197. // 如果返回商品信息,则设置商品信息 commonShop
  198. if(res.result.shopRecommends){
  199. res.result.shopRecommends = JSON.parse(res.result.shopRecommends)
  200. }
  201. res.result.commonShop.forEach(n => {
  202. this.checkboxValue.push(n.id)
  203. if(typeof res.result.shopRecommends == 'object'){
  204. n.selectNum = res.result.shopRecommends[n.id]
  205. }
  206. })
  207. console.log(res.result);
  208. this.productInfo = res.result;
  209. // 提取快捷下单内容
  210. this.quickOrderContent = {
  211. type: res.result.type || '',
  212. imageUrl: res.result.imageUrl || '',
  213. voiceUrl: res.result.voiceUrl || ''
  214. };
  215. }
  216. });
  217. },
  218. // 获取地址列表
  219. getAddressList() {
  220. // 调用地址列表组件获取地址
  221. this.$refs.addressList.getAddressList().then(res => {
  222. if (res && res.total) {
  223. this.addressTotal = res.total;
  224. // 查找默认地址
  225. if (res.records && res.records.length > 0) {
  226. const defaultAddress = res.records.find(addr => addr.defaultFlag === '1');
  227. if (defaultAddress) {
  228. this.address = defaultAddress;
  229. } else {
  230. // 如果没有默认地址,使用第一个地址
  231. this.address = res.records[0];
  232. }
  233. // 如果标记为需要自动提交订单
  234. if (this.shouldSubmitOrder) {
  235. this.shouldSubmitOrder = false; // 重置标记
  236. this.submitOrder(true); // 执行提交订单,传入true表示不再验证地址
  237. }
  238. }
  239. } else {
  240. this.addressTotal = 0;
  241. }
  242. // 完成加载
  243. this.isLoading = false;
  244. }).catch(err => {
  245. console.error('获取地址列表失败', err);
  246. this.addressTotal = 0;
  247. this.isLoading = false;
  248. });
  249. },
  250. // 打开地址选择弹窗
  251. openAddress() {
  252. this.$refs.addressPopup.open('bottom');
  253. },
  254. // 选择地址
  255. selectAddress(address) {
  256. this.address = address;
  257. this.$refs.addressPopup.close();
  258. },
  259. // 处理地址保存
  260. handleAddressSave(address) {
  261. // 显示加载状态
  262. this.isLoading = true;
  263. // 保存地址
  264. this.$api('saveOrUpdateAddress', address, res => {
  265. if (res.code === 200) {
  266. uni.showToast({
  267. title: '地址保存成功',
  268. icon: 'success'
  269. });
  270. // 标记需要在获取地址后自动提交订单
  271. this.shouldSubmitOrder = true;
  272. // 重新获取地址列表
  273. this.getAddressList();
  274. } else {
  275. uni.showToast({
  276. title: res.message || '保存失败',
  277. icon: 'none'
  278. });
  279. this.isLoading = false;
  280. }
  281. });
  282. },
  283. // 提交订单
  284. submitOrder(skipAddressCheck = false) {
  285. if (!this.address.id) {
  286. const addressForm = this.$refs.addressForm;
  287. // 验证地址表单
  288. const isValid = addressForm.parameterVerification(addressForm.addressDetail);
  289. if (!isValid.auth) {
  290. uni.showToast({
  291. title: isValid.title,
  292. icon: 'none'
  293. });
  294. return;
  295. }
  296. // 显示加载状态
  297. this.isLoading = true;
  298. // 保存地址并继续
  299. addressForm.onSubmit();
  300. return;
  301. }
  302. // 显示加载中
  303. uni.showLoading({
  304. title: '提交订单中...'
  305. });
  306. let list = []
  307. this.productInfo.commonShop.forEach(n => {
  308. if(this.checkboxValue.includes(n.id)){
  309. list.push({
  310. num: n.selectNum || 1,
  311. shopId: n.id,
  312. })
  313. }
  314. })
  315. let data = {
  316. addressId: this.address.id,
  317. payType: 1, // 默认微信支付
  318. orderId: this.orderId,
  319. list: JSON.stringify(list),
  320. }
  321. /*
  322. {
  323. addressId: this.address.id,
  324. productId: this.productInfo.id,
  325. num : 1,
  326. payType: 1, // 默认微信支付
  327. orderId: this.orderId
  328. }
  329. */
  330. // 创建订单
  331. this.$api('createSumOrder', data, res => {
  332. uni.hideLoading();
  333. if (res.code === 200) {
  334. this.productInfo.isPay = 'Y'
  335. uni.$emit('getQuickOrderInfo')
  336. uni.requestPaymentWxPay(res)
  337. .then(e => {
  338. uni.showToast({
  339. title: '下单成功',
  340. icon: 'none'
  341. })
  342. this.paySuccess(res)
  343. }).catch(n => {
  344. setTimeout(uni.redirectTo, 700, {
  345. url: '/pages/index/order'
  346. })
  347. })
  348. } else {
  349. uni.showToast({
  350. title: res.message || '下单失败',
  351. icon: 'none'
  352. });
  353. }
  354. });
  355. },
  356. paySuccess(res){
  357. setTimeout(uni.redirectTo, 700, {
  358. url: '/pages/index/order'
  359. })
  360. },
  361. cancelOrderFast(){
  362. uni.showModal({
  363. title: '确认取消吗?',
  364. success : res => {
  365. if(res.confirm){
  366. this.$api('cancelOrderFast', {
  367. orderId : this.orderId
  368. }).then(res => {
  369. uni.$emit('getQuickOrderInfo')
  370. uni.showToast({
  371. title: '取消成功',
  372. icon: 'none',
  373. success() {
  374. uni.navigateBack(-1)
  375. }
  376. })
  377. })
  378. }
  379. }
  380. })
  381. },
  382. // 获取订单类型名称
  383. getOrderTypeName(type) {
  384. const typeMap = {
  385. '0': '拍照下单',
  386. '2': '语音下单'
  387. };
  388. return typeMap[type] || '快捷下单';
  389. },
  390. // 预览图片
  391. previewImage() {
  392. if (!this.quickOrderContent.imageUrl) {
  393. uni.showToast({
  394. title: '图片不存在',
  395. icon: 'none'
  396. });
  397. return;
  398. }
  399. uni.previewImage({
  400. current: this.quickOrderContent.imageUrl,
  401. urls: [this.quickOrderContent.imageUrl]
  402. });
  403. },
  404. // 语音播放开始事件
  405. onVoicePlayStarted() {
  406. console.log('语音播放开始');
  407. },
  408. // 语音播放结束事件
  409. onVoicePlayEnded() {
  410. console.log('语音播放结束');
  411. },
  412. // 语音播放错误事件
  413. onVoicePlayError(error) {
  414. console.error('语音播放错误', error);
  415. },
  416. }
  417. }
  418. </script>
  419. <style scoped lang="scss">
  420. .hand-firm {
  421. background-color: #f6f6f6;
  422. padding-bottom: 120rpx;
  423. .content-wrapper {
  424. padding: 20rpx;
  425. }
  426. .section-wrapper {
  427. margin-bottom: 20rpx;
  428. border-radius: 12rpx;
  429. overflow: hidden;
  430. width: 100%;
  431. background-color: #fff;
  432. }
  433. .section-title {
  434. display: flex;
  435. align-items: center;
  436. padding: 30rpx;
  437. border-bottom: 1rpx solid #f0f0f0;
  438. .title-bar {
  439. width: 6rpx;
  440. height: 30rpx;
  441. background-color: #D03F25;
  442. margin-right: 15rpx;
  443. border-radius: 3rpx;
  444. }
  445. text {
  446. font-size: 32rpx;
  447. font-weight: 500;
  448. color: #333;
  449. }
  450. }
  451. .product-card {
  452. display: flex;
  453. padding: 30rpx;
  454. .checkbox{
  455. display: flex;
  456. justify-content: center;
  457. align-items: center;
  458. }
  459. .product-image {
  460. width: 200rpx;
  461. height: 200rpx;
  462. margin-right: 30rpx;
  463. background-color: #f9f9f9;
  464. border-radius: 8rpx;
  465. }
  466. .product-details {
  467. flex: 1;
  468. display: flex;
  469. flex-direction: column;
  470. justify-content: space-between;
  471. gap: 6rpx;
  472. .product-name {
  473. font-size: 28rpx;
  474. font-weight: 500;
  475. color: #333;
  476. }
  477. .product-tags {
  478. display: flex;
  479. .tag {
  480. font-size: 24rpx;
  481. color: #D03F25;
  482. padding: 4rpx 12rpx;
  483. background-color: rgba(208, 63, 37, 0.1);
  484. border-radius: 14rpx;
  485. margin-right: 15rpx;
  486. }
  487. }
  488. .product-price {
  489. font-size: 26rpx;
  490. color: #666;
  491. .price-value {
  492. font-size: 30rpx;
  493. font-weight: 600;
  494. color: #D03F25;
  495. }
  496. }
  497. }
  498. }
  499. .address-section {
  500. .has-address {
  501. position: relative;
  502. .address-info {
  503. padding: 30rpx;
  504. .address-user {
  505. margin-bottom: 15rpx;
  506. .name {
  507. font-size: 32rpx;
  508. font-weight: 500;
  509. color: #333;
  510. margin-right: 30rpx;
  511. }
  512. .phone {
  513. font-size: 28rpx;
  514. color: #666;
  515. }
  516. }
  517. .address-detail {
  518. font-size: 28rpx;
  519. color: #666;
  520. line-height: 1.5;
  521. margin-bottom: 20rpx;
  522. }
  523. .address-action {
  524. display: flex;
  525. justify-content: space-between;
  526. align-items: center;
  527. padding-top: 20rpx;
  528. border-top: 1px solid #eee;
  529. .address-tip {
  530. font-size: 28rpx;
  531. color: #D03F25;
  532. }
  533. .arrow-right {
  534. width: 16rpx;
  535. height: 16rpx;
  536. border-top: 2rpx solid #D03F25;
  537. border-right: 2rpx solid #D03F25;
  538. transform: rotate(45deg);
  539. }
  540. }
  541. }
  542. .address-tag {
  543. position: absolute;
  544. top: 20rpx;
  545. right: 0;
  546. background-color: #D03F25;
  547. color: #fff;
  548. font-size: 24rpx;
  549. padding: 8rpx 20rpx;
  550. border-radius: 30rpx 0 0 30rpx;
  551. }
  552. }
  553. .no-address {
  554. padding: 20rpx;
  555. }
  556. }
  557. .order-submit {
  558. display: flex;
  559. padding: 20rpx 30rpx;
  560. background-color: #fff;
  561. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
  562. gap: 20rpx;
  563. .submit-btn-close{
  564. height: 90rpx;
  565. line-height: 90rpx;
  566. text-align: center;
  567. background-color: rgba($uni-color, 0.2);
  568. border: 1rpx solid $uni-color;
  569. color: $uni-color;
  570. font-size: 32rpx;
  571. border-radius: 45rpx;
  572. border: none;
  573. flex: 1;
  574. }
  575. .submit-btn {
  576. height: 90rpx;
  577. line-height: 90rpx;
  578. text-align: center;
  579. background-color: $uni-color;
  580. color: #fff;
  581. font-size: 32rpx;
  582. border-radius: 45rpx;
  583. border: none;
  584. flex: 4;
  585. }
  586. }
  587. .add-btn {
  588. padding: 30rpx;
  589. .button-submit {
  590. display: flex;
  591. align-items: center;
  592. justify-content: center;
  593. width: 100%;
  594. height: 90rpx;
  595. background-color: #D03F25;
  596. color: #fff;
  597. font-size: 32rpx;
  598. border-radius: 45rpx;
  599. }
  600. }
  601. .quick-order-content {
  602. .photo-content {
  603. padding: 30rpx;
  604. text-align: center;
  605. .order-image {
  606. width: 200rpx;
  607. height: 200rpx;
  608. border-radius: 12rpx;
  609. margin-bottom: 20rpx;
  610. background-color: #f9f9f9;
  611. }
  612. .image-desc {
  613. font-size: 26rpx;
  614. color: #666;
  615. }
  616. }
  617. }
  618. }
  619. </style>