order_fill.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <template>
  2. <view class="sell-postend">
  3. <view class="sell-postend-banner"></view>
  4. <view class="sell-postend-content">
  5. <view class="withdraw-express">
  6. <view class="header">
  7. <view class="header-left">
  8. <view class="line"></view>
  9. <view class="title">自主邮寄</view>
  10. </view>
  11. <view class="header-right">
  12. <uni-countdown
  13. :minute="countdown"
  14. color="#f8c007"
  15. @timeup="timeupHandle"
  16. ></uni-countdown>
  17. </view>
  18. </view>
  19. <view class="item">
  20. <view class="targe">收</view>
  21. <view class="address">
  22. <view
  23. >{{ appStore.$wxConfig.mailerName }} :{{
  24. appStore.$wxConfig.mailerPhone
  25. }}</view
  26. >
  27. <view class="address-detail" style="margin-top: 4px">
  28. <text class="receive-address">地址:</text>
  29. {{ appStore.$wxConfig.mailingAddress }}
  30. </view>
  31. </view>
  32. <view class="end">
  33. <view class="copy" @click="copy">复制</view>
  34. </view>
  35. </view>
  36. </view>
  37. <view class="upload-box">
  38. <view class="header">
  39. <view class="header-left">
  40. <view class="line"></view>
  41. <view class="title">上传实物图</view>
  42. </view>
  43. </view>
  44. <view class="upload-tips"
  45. >上传实物图、包裹图,如有购买凭证(发票、单据) 可以一起上传</view
  46. >
  47. <view class="upload-img">
  48. <view class="upload-box-contanier">
  49. <up-upload
  50. :fileList="imageList"
  51. uploadIcon="plus"
  52. @afterRead="afterRead"
  53. @delete="deletePic"
  54. name="1"
  55. multiple
  56. :maxCount="4"
  57. >
  58. <template #trigger>
  59. <view class="upload-block">
  60. <uni-icons
  61. size="38"
  62. color="#ccc"
  63. type="plusempty"
  64. ></uni-icons>
  65. </view>
  66. </template>
  67. </up-upload>
  68. </view>
  69. </view>
  70. <view class="pz-tips">示例图(产品实物,凭证图片)</view>
  71. <view class="pz-img">
  72. <image
  73. v-for="(item, index) in filelist"
  74. :key="index"
  75. :src="item"
  76. ></image>
  77. </view>
  78. </view>
  79. <view class="postend-info-box">
  80. <view class="header">
  81. <view class="header-left">
  82. <view class="line"></view>
  83. <view class="title">您的信息</view>
  84. </view>
  85. </view>
  86. <view class="postend-info">
  87. <view class="info-item">
  88. <view class="info-item-left">快递单号</view>
  89. <view class="info-item-right">
  90. <input
  91. type="text"
  92. v-model="expressNum"
  93. placeholder="请输入快递单号"
  94. />
  95. <text class="iconfont icon-iconfontscan" @click="scanCode"></text>
  96. </view>
  97. </view>
  98. <view class="info-agree" @click="showAggre">
  99. <up-checkbox-group @change="checkboxChange" v-model="isCheck">
  100. <up-checkbox activeColor="#e9c279" label="确认协议"></up-checkbox>
  101. </up-checkbox-group>
  102. <span style="color: #b5aa90; font-size: 16px">《回收协议》</span>
  103. </view>
  104. </view>
  105. </view>
  106. <view class="recyle-price">
  107. <view class="recyle-desc">预估回收价</view>
  108. <view class="recyle-price-num">¥{{ orderInfo.totalMoney }}</view>
  109. </view>
  110. <view class="btn-box">
  111. <!-- <view class="postend-cbtn" @click="cancelOrder">取消订单</view> -->
  112. <view class="postend-btn" @click="submitOrder">
  113. <image class="btn" src="/static/images/btn-button.png"></image>
  114. <text class="btn-text">立即回收</text>
  115. </view>
  116. </view>
  117. <view class="white"></view>
  118. </view>
  119. <uni-popup
  120. ref="singPopup"
  121. type="bottom"
  122. borderRadius="10px 10px 0 0"
  123. maskBackgroundColor="rgba(0,0,0,0)"
  124. >
  125. <view class="signContent">
  126. <scroll-view scrollY class="scroll">
  127. <up-parse :content="content"></up-parse>
  128. </scroll-view>
  129. <view
  130. class="comfireBtn footer"
  131. @click="
  132. isCheck = true;
  133. $refs.singPopup.close();
  134. "
  135. >
  136. 我已详细知悉
  137. </view>
  138. </view>
  139. </uni-popup>
  140. </view>
  141. </template>
  142. <script setup>
  143. import { ref, onMounted, computed, onUnmounted } from "vue";
  144. import { useImageUpload } from "@/hooks/useImageUpload";
  145. import { recycleCancelAPI, recyclUpdateAPI } from "@/api/functions";
  146. import { onLoad } from "@dcloudio/uni-app";
  147. import { agreementGetoneApi } from "@/api/user";
  148. import { useAppStore } from "@/stores/app";
  149. const appStore = useAppStore();
  150. // 1. 初始化图片上传钩子
  151. const { imageList, afterRead, deletePic, uploadLoading } = useImageUpload({
  152. pid: 14,
  153. model: "recyle",
  154. });
  155. console.log(imageList.value);
  156. // 2. 响应式数据
  157. const pic = ref([]);
  158. const expressNum = ref("");
  159. const singPopup = ref(null);
  160. const isCheck = ref(false);
  161. const content = ref("");
  162. const filelist = ref([
  163. "https://my-go-easy-im.oss-cn-shenzhen.aliyuncs.com/goeasy-im-%E6%B0%B4%E8%B4%9D%E5%95%86%E5%9F%8E/example/example1.png",
  164. "https://my-go-easy-im.oss-cn-shenzhen.aliyuncs.com/goeasy-im-%E6%B0%B4%E8%B4%9D%E5%95%86%E5%9F%8E/example/example2.png",
  165. "https://my-go-easy-im.oss-cn-shenzhen.aliyuncs.com/goeasy-im-%E6%B0%B4%E8%B4%9D%E5%95%86%E5%9F%8E/example/example3.png",
  166. "https://my-go-easy-im.oss-cn-shenzhen.aliyuncs.com/goeasy-im-%E6%B0%B4%E8%B4%9D%E5%95%86%E5%9F%8E/example/example4.png",
  167. ]);
  168. const reqData = ref({}); // 假设从路由参数或其他地方获取
  169. const orderInfo = ref({});
  170. const countdown = ref(999);
  171. // 3. 生命周期
  172. onLoad((options) => {
  173. if (options.order) {
  174. countdown.value = getTimeDiff(JSON.parse(options.order).expirationTime);
  175. orderInfo.value = JSON.parse(options.order);
  176. }
  177. });
  178. function agreementGetoneFn() {
  179. // 资产说明
  180. agreementGetoneApi({ name: "recyle" }).then((res) => {
  181. content.value = res.data?.content;
  182. });
  183. }
  184. const checkboxChange = (n) => {
  185. isCheck.value = true;
  186. };
  187. const showAggre = () => {
  188. singPopup.value?.open();
  189. agreementGetoneFn();
  190. };
  191. // 4. 方法
  192. // 扫描快递单号
  193. const scanCode = () => {
  194. uni.scanCode({
  195. success: (res) => {
  196. console.log(res);
  197. expressNum.value = res.result;
  198. },
  199. fail: (err) => {
  200. uni.showToast({ title: "扫码失败,请重试", icon: "none" });
  201. },
  202. });
  203. };
  204. // 复制收件人信息
  205. const copy = () => {
  206. uni.setClipboardData({
  207. data: `${appStore.$wxConfig.mailerName}:${appStore.$wxConfig.mailerPhone} 地址: ${appStore.$wxConfig.mailingAddress}`,
  208. success() {
  209. uni.showToast({ title: "复制成功", icon: "success" });
  210. },
  211. fail: (err) => {
  212. uni.showToast({
  213. title: "复制失败",
  214. icon: "none",
  215. });
  216. console.error("复制失败:", err);
  217. },
  218. });
  219. };
  220. // 处理图片上传成功
  221. const success = (result) => {
  222. pic.value.push(result.url);
  223. };
  224. // 提交订单
  225. const submitOrder = async () => {
  226. if (!isCheck.value) {
  227. uni.showToast({ title: "请阅读并同意回收协议", icon: "none" });
  228. return;
  229. }
  230. if (imageList.value.length === 0) {
  231. uni.showToast({ title: "请上传实物图", icon: "none" });
  232. return;
  233. }
  234. if (!expressNum.value.trim()) {
  235. uni.showToast({ title: "请填写快递单号", icon: "none" });
  236. return;
  237. }
  238. try {
  239. uni.showLoading({
  240. title: "下单中",
  241. });
  242. // `实际项目中调用接口`
  243. const res = await recyclUpdateAPI({
  244. expressNo: expressNum.value,
  245. images: imageList.value.map((v) => v.info.url),
  246. orderNo: orderInfo.value.orderNo,
  247. });
  248. setTimeout(() => {
  249. uni.redirectTo({
  250. url: "/pages/users/vault/recycle/recyle_order",
  251. });
  252. }, 0);
  253. uni.hideLoading();
  254. } catch (err) {
  255. uni.showToast({
  256. title: err?.msg || "下单失败,请稍后重试",
  257. icon: "none",
  258. });
  259. setTimeout(() => {
  260. uni.redirectTo({
  261. url: "/pages/users/vault/recycle/recyle_order",
  262. });
  263. }, 1000);
  264. }
  265. };
  266. // 取消订单
  267. const cancelOrder = async () => {
  268. try {
  269. const res = await recycleCancelAPI({ orderNo: orderInfo.value.orderNo });
  270. } catch (err) {
  271. console.error("取消订单失败:", err);
  272. }
  273. };
  274. // 订单过期处理
  275. const timeupHandle = () => {
  276. uni.showToast({
  277. title: "订单已过期",
  278. duration: 1000,
  279. icon: "none",
  280. });
  281. // 取消订单
  282. cancelOrder();
  283. setTimeout(() => {
  284. uni.reLaunch({
  285. url: "/pages/users/vault/recycle/recyle_order",
  286. });
  287. }, 1000);
  288. };
  289. // 计算时间差(分钟)
  290. const getTimeDiff = (targetTimeStr) => {
  291. if (!targetTimeStr) return 0;
  292. const targetTime = new Date(targetTimeStr).getTime();
  293. const currentTime = Date.now();
  294. const diffMs = targetTime - currentTime;
  295. const diffMinutes = diffMs / 60000;
  296. return diffMinutes > 0 ? diffMinutes : 0;
  297. };
  298. </script>
  299. <style scoped lang="scss">
  300. .signContent {
  301. background-color: #f8f8f8;
  302. padding: 20px;
  303. box-sizing: border-box;
  304. display: flex;
  305. justify-content: center;
  306. align-items: center;
  307. flex-direction: column;
  308. border-radius: 20px 20px 0 0;
  309. .scroll {
  310. background-color: #fff;
  311. padding: 4px;
  312. height: 300px;
  313. overflow-y: hidden;
  314. border: 1px solid #dfdfdf;
  315. }
  316. .footer {
  317. margin-top: 10px;
  318. color: #fff;
  319. padding: 4px 20px;
  320. border-radius: 20px;
  321. background: linear-gradient(to right, #8ed187, #5dd665);
  322. }
  323. }
  324. .icon-iconfontscan {
  325. font-size: 20px;
  326. color: #f8c007;
  327. }
  328. .upload-box-contanier {
  329. margin-top: 40rpx;
  330. .upload-block {
  331. width: 160rpx;
  332. height: 160rpx;
  333. border: 1px solid #ccc;
  334. border-radius: 10rpx;
  335. display: flex;
  336. flex-direction: column;
  337. justify-content: center;
  338. align-items: center;
  339. color: #ccc;
  340. font-weight: 700;
  341. font-size: 26rpx;
  342. }
  343. }
  344. .sell-postend {
  345. height: auto;
  346. padding: 10rpx 30rpx;
  347. position: relative;
  348. .sell-postend-banner {
  349. position: absolute;
  350. top: 0;
  351. left: 0;
  352. width: 100%;
  353. height: 50vh;
  354. z-index: -1;
  355. // background-image: linear-gradient(360deg, #ffffff 0%, #e8c279 100%);
  356. background: $uni-bg-primary;
  357. }
  358. .sell-postend-content {
  359. display: flex;
  360. flex-direction: column;
  361. align-items: center;
  362. .withdraw-express {
  363. width: 682rpx;
  364. height: 306rpx;
  365. background-color: #ffffff;
  366. box-shadow: 0rpx 3rpx 13rpx 0rpx rgba(0, 0, 0, 0.13);
  367. border-radius: 20rpx;
  368. display: flex;
  369. flex-direction: column;
  370. align-items: flex-start;
  371. justify-content: center;
  372. box-sizing: border-box;
  373. padding: 28rpx;
  374. margin-top: 30rpx;
  375. .item {
  376. display: flex;
  377. align-items: center;
  378. margin-top: 15px;
  379. .targe {
  380. display: flex;
  381. justify-content: center;
  382. align-items: center;
  383. color: #fff;
  384. width: 35px;
  385. height: 35px;
  386. border-radius: 50%;
  387. background-color: #f8c007;
  388. }
  389. .address {
  390. width: 425rpx;
  391. margin: 0 10px;
  392. font-size: 28rpx;
  393. .receive-address {
  394. letter-spacing: 9px;
  395. line-height: 2;
  396. }
  397. }
  398. .end {
  399. display: flex;
  400. justify-content: center;
  401. align-items: center;
  402. .copy {
  403. color: #888888;
  404. border-radius: 3px;
  405. border: 1px solid #888888;
  406. font-size: 24rpx;
  407. padding: 0 23rpx;
  408. }
  409. }
  410. }
  411. }
  412. .upload-box {
  413. width: 682rpx;
  414. height: auto;
  415. background-color: #ffffff;
  416. box-shadow: 0rpx 3rpx 13rpx 0rpx rgba(0, 0, 0, 0.13);
  417. border-radius: 20rpx;
  418. box-sizing: border-box;
  419. margin-top: 25rpx;
  420. padding: 40rpx 40rpx;
  421. .upload-tips {
  422. font-size: 26rpx;
  423. color: #000000;
  424. margin: 30rpx 0;
  425. }
  426. .upload-img {
  427. margin: 30rpx 0;
  428. }
  429. .pz-tips {
  430. text-align: center;
  431. font-size: 24rpx;
  432. color: #7c7c7c;
  433. margin: 40rpx 0;
  434. }
  435. .pz-img {
  436. display: flex;
  437. justify-content: space-between;
  438. flex-wrap: wrap;
  439. margin: 30rpx 0;
  440. image {
  441. width: 138rpx;
  442. height: 138rpx;
  443. background-color: #f3f3f3;
  444. border-radius: 10rpx;
  445. margin: 15rpx 0;
  446. }
  447. }
  448. }
  449. .postend-info-box {
  450. padding: 40rpx 40rpx;
  451. margin-top: 25rpx;
  452. width: 682rpx;
  453. // height: 527rpx;
  454. background-color: #ffffff;
  455. box-shadow: 0rpx 3rpx 13rpx 0rpx rgba(0, 0, 0, 0.13);
  456. border-radius: 20rpx;
  457. box-sizing: border-box;
  458. .postend-info {
  459. margin-top: 30rpx;
  460. .info-item {
  461. display: flex;
  462. align-items: center;
  463. margin: 25rpx 0;
  464. .info-item-left {
  465. font-size: 26rpx;
  466. color: #000000;
  467. }
  468. .info-item-right {
  469. margin-left: 15rpx;
  470. width: 470rpx;
  471. height: 77rpx;
  472. background-color: #f3f3f3;
  473. border-radius: 10rpx;
  474. display: flex;
  475. // justify-content: space-around;
  476. align-items: center;
  477. input {
  478. width: 86%;
  479. font-size: 26rpx;
  480. color: #000;
  481. padding-left: 15rpx;
  482. }
  483. }
  484. }
  485. .info-agree {
  486. display: flex;
  487. justify-content: center;
  488. margin-top: 25rpx;
  489. align-items: center;
  490. }
  491. }
  492. }
  493. .recyle-price {
  494. display: flex;
  495. justify-content: center;
  496. font-size: 26rpx;
  497. color: #000000;
  498. margin: 30rpx 0;
  499. .recyle-price-num {
  500. // color: #7c7c7c;
  501. color: $txt-color;
  502. margin-left: 15rpx;
  503. }
  504. }
  505. .btn-box {
  506. display: flex;
  507. justify-content: center;
  508. .postend-btn {
  509. // width: 330rpx;
  510. width: 267rpx;
  511. height: 71rpx;
  512. position: relative;
  513. margin-top: 30rpx;
  514. .btn {
  515. width: 100%;
  516. height: 100%;
  517. }
  518. .btn-text {
  519. position: absolute;
  520. top: 50%;
  521. left: 50%;
  522. transform: translate(-50%, -50%);
  523. color: #000;
  524. font-size: 26rpx;
  525. }
  526. }
  527. .postend-cbtn {
  528. margin-right: 30rpx;
  529. // width: 330rpx;
  530. padding: 0rpx 60rpx;
  531. height: 71rpx;
  532. background: #cecece;
  533. display: flex;
  534. justify-content: center;
  535. align-items: center;
  536. color: #636363;
  537. border-radius: 30rpx;
  538. margin-top: 30rpx;
  539. }
  540. }
  541. .white {
  542. height: 100rpx;
  543. }
  544. .header {
  545. width: 100%;
  546. display: flex;
  547. justify-content: space-between;
  548. align-items: center;
  549. .header-left {
  550. display: flex;
  551. align-items: center;
  552. .line {
  553. width: 4rpx;
  554. height: 28rpx;
  555. background-color: #cc9933;
  556. }
  557. .title {
  558. margin-left: 5rpx;
  559. font-weight: bold;
  560. }
  561. .tips {
  562. font-size: 26rpx;
  563. color: #7c7c7c;
  564. }
  565. }
  566. .header-right {
  567. font-size: 26rpx;
  568. color: #7c7c7c;
  569. }
  570. }
  571. }
  572. }
  573. </style>