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

760 lines
22 KiB

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