index.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104
  1. <template>
  2. <view class="sell-materials-page">
  3. <!-- 导航栏(若需添加导航可在此处补充,如uni-nav-bar) -->
  4. <!-- <view class="sell-postend-banner"></view> -->
  5. <view class="page-content">
  6. <!-- 标签栏 -->
  7. <view class="tabs-container">
  8. <view
  9. class="tabs-item"
  10. :class="{ active: activeTabId === item.id }"
  11. v-for="item in tabList"
  12. :key="item.id"
  13. @click="handleTabChange(item)"
  14. >
  15. {{ item.name }}
  16. </view>
  17. </view>
  18. <!-- 约价回收(activeTabId=1) -->
  19. <view class="recycle-container" v-show="activeTabId === 1">
  20. <!-- 金料类型选择 -->
  21. <view class="gold-category-list">
  22. <view
  23. class="gold-category-item"
  24. v-for="item in goldCategoryList"
  25. :key="item.key"
  26. :class="{ active: activeGoldCategoryKey === item.key }"
  27. @click="handleGoldCategoryChange(item)"
  28. >
  29. {{ item.title }}
  30. </view>
  31. </view>
  32. <!-- 卖料信息表单 -->
  33. <view class="recycle-info-card">
  34. <view class="price-info">
  35. {{ goldName
  36. }}{{ `${!appStore.$wxConfig?.auditModeEnabled ? "回收" : ""}` }}价格
  37. <text class="current-price">{{ realprice?.toFixed(2) || 0 }}</text>
  38. 元/g
  39. <view class="equity-tag"
  40. >权益 +{{ benefitPrice?.toFixed(2) || 0 }}</view
  41. >
  42. </view>
  43. <!-- 克重输入(动态校验提示) -->
  44. <view class="weight-input-container">
  45. <view class="weight-input">
  46. <input
  47. type="text"
  48. placeholder="请输入克重"
  49. v-model="weight"
  50. @input="handleWeightInput"
  51. />
  52. </view>
  53. <!-- 动态错误提示 -->
  54. <view class="weight-tip" v-if="!isWeightValid">
  55. {{ weightTipText }}
  56. </view>
  57. </view>
  58. <!-- 金额信息(动态计算) -->
  59. <view class="amount-info-group">
  60. <view class="amount-info-item">
  61. <view class="info-label">{{
  62. `预计${!appStore.$wxConfig?.auditModeEnabled ? "回收" : ""}金额`
  63. }}</view>
  64. <view class="info-value"
  65. >¥{{ recylePrice?.toFixed(2) || 0 }}</view
  66. >
  67. </view>
  68. <view class="amount-info-item">
  69. <view class="info-label">定金比例</view>
  70. <view class="info-value">{{ currentDepositPerGram }}元/g</view>
  71. </view>
  72. <view class="amount-info-item">
  73. <view class="info-label">预付定金</view>
  74. <view class="info-value"
  75. >¥{{ advanceDeposit?.toFixed(2) || 0 }}</view
  76. >
  77. <!-- 余额不足警告:动态判断 -->
  78. <view
  79. class="balance-warning small"
  80. v-if="appStore?.isLogin && !isBalanceEnough"
  81. >
  82. 余额不足,需充值{{ isNeedchargePrice?.toFixed(2) || 0 }}元
  83. </view>
  84. </view>
  85. </view>
  86. </view>
  87. <!-- 余额支付信息(动态判断余额不足) -->
  88. <view class="balance-pay-section">
  89. <view class="balance-info">
  90. <image
  91. class="balance-icon"
  92. src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/static/Wallet.png"
  93. mode="widthFix"
  94. ></image>
  95. <view class="balance-detail">
  96. <view class="balance-title">余额支付</view>
  97. <view class="balance-content">
  98. <view class="balance-amount">
  99. 当前余额:
  100. <text class="amount-num">{{
  101. appStore.userInfo?.nowMoney || 0
  102. }}</text>
  103. </view>
  104. <view
  105. class="balance-awrning"
  106. v-if="appStore?.isLogin && !isBalanceEnough"
  107. >
  108. 余额不足
  109. </view>
  110. </view>
  111. </view>
  112. </view>
  113. <!-- <view
  114. class="recharge-btn"
  115. @click="jumpToSBei"
  116. v-if="!isBalanceEnough && !wxConfig?.auditModeEnabled"
  117. >
  118. 前往充值
  119. </view> -->
  120. </view>
  121. <!-- 确认按钮(禁用逻辑:克重不合法或余额不足) -->
  122. <view class="btn-box">
  123. <view
  124. class="confirm-btn"
  125. @click="handleConfirmSell"
  126. :class="{ disabled: !isWeightValid || !isBalanceEnough }"
  127. :disabled="!isWeightValid || !isBalanceEnough"
  128. >
  129. 确认卖料
  130. </view>
  131. </view>
  132. <!-- 规则说明(若有配置则显示) -->
  133. <view class="rule-desc" v-if="setting?.recycleText">
  134. <rich-text :nodes="setting?.recycleText"></rich-text>
  135. </view>
  136. </view>
  137. <!-- 余料出售(activeTabId=2) -->
  138. <view class="gold-container" v-show="activeTabId === 2">
  139. <!-- 金料类型选择 -->
  140. <view class="gold-category-list">
  141. <view
  142. class="gold-category-item"
  143. v-for="item in goldRecyleCategoryList"
  144. :key="item.key"
  145. :class="{ active: activeGoldCategoryKey === item.key }"
  146. @click="handleGoldCategoryChange(item, true)"
  147. >
  148. {{ item.title }}
  149. </view>
  150. </view>
  151. <view class="recycle-info-card">
  152. <view class="price-info">
  153. {{ goldName }}
  154. <text class="current-price">{{ realprice?.toFixed(2) || 0 }}</text>
  155. 元/g
  156. <view class="equity-tag"
  157. >权益 +{{ benefitPrice?.toFixed(2) || 0 }}</view
  158. >
  159. </view>
  160. <!-- 克重输入(余料专属校验) -->
  161. <view class="weight-input-container">
  162. <view class="weight-input">
  163. <input
  164. type="text"
  165. placeholder="请输入克重"
  166. v-model="rmbExtract"
  167. @input="rmbOnKeyInput"
  168. @keypress="(e) => !/[0-9.]/.test(e.key) && e.preventDefault()"
  169. />
  170. </view>
  171. <!-- 动态提示 -->
  172. <view class="weight-tip" v-if="rmbIsLowest">
  173. {{ goldName }}最低{{ currentMinWeight }}g起出售,且最多两位小数
  174. </view>
  175. <view class="weight-tip" v-if="rmbIsOut">
  176. 输入克重超过可出售克重,当前可售{{
  177. currentAvailableWeight?.toFixed(2) || 0
  178. }}g
  179. </view>
  180. </view>
  181. <!-- 金额信息(动态计算) -->
  182. <view class="amount-info-group">
  183. <view class="amount-info-item">
  184. <view class="info-label">预计金额</view>
  185. <view class="info-value"
  186. >¥{{ rmbNeedPrice?.toFixed(2) || 0 }}</view
  187. >
  188. </view>
  189. <view class="amount-info-item">
  190. <view class="info-label">当前可售克重</view>
  191. <view class="info-value"
  192. >{{ currentAvailableWeight?.toFixed(2) || 0 }}g</view
  193. >
  194. </view>
  195. </view>
  196. </view>
  197. <!-- 余料信息(动态显示) -->
  198. <view class="balance-pay-section">
  199. <view class="balance-info">
  200. <image
  201. class="balance-icon"
  202. src="https://mp-ad17e5cd-05c1-4df9-b060-556e25dac130.cdn.bspapp.com/mini/static/Wallet.png"
  203. mode="widthFix"
  204. ></image>
  205. <view class="balance-detail">
  206. <view class="balance-title">余料</view>
  207. <view class="balance-content">
  208. <view class="balance-amount">
  209. 当前余料:
  210. <text class="amount-num"
  211. >{{ currentAvailableWeight?.toFixed(2) || 0 }}g</text
  212. >
  213. </view>
  214. <view class="balance-warning" v-if="rmbIsOut">余料不足</view>
  215. </view>
  216. </view>
  217. </view>
  218. </view>
  219. <!-- 协议确认 -->
  220. <view class="agreement-section">
  221. <view class="aggregate" @click="rmbAggregate = !rmbAggregate">
  222. <image
  223. class="choose"
  224. :src="
  225. rmbAggregate
  226. ? '/static/recycle/choose.png'
  227. : '/static/recycle/nochoose.png'
  228. "
  229. mode="scaleToFill"
  230. style="width: 30rpx; height: 30rpx"
  231. ></image>
  232. <view class="aggre">
  233. 阅读并同意
  234. <span class="aggre-text" @click="rmbShowAggre">《余料出售协议》</span>
  235. </view>
  236. </view>
  237. </view>
  238. <!-- 确认按钮(禁用逻辑:未同意协议/克重不合法) -->
  239. <view class="btn-box">
  240. <view
  241. class="confirm-btn"
  242. @click="rmbHandleConfirmSell()"
  243. :class="{ disabled: !rmbAggregate || !rmbIsPost }"
  244. :disabled="!rmbAggregate || !rmbIsPost"
  245. >
  246. 确认出售
  247. </view>
  248. </view>
  249. <!-- 出售协议弹窗 -->
  250. <uni-popup
  251. ref="rmbSingPopupRef"
  252. type="bottom"
  253. borderRadius="10px 10px 0 0"
  254. maskBackgroundColor="rgba(0,0,0,0.5)"
  255. >
  256. <view class="signContent">
  257. <scroll-view scrollY class="scroll" style="height: 500rpx">
  258. <up-parse :content="rmbAgreement"></up-parse>
  259. </scroll-view>
  260. <view
  261. class="comfireBtn footer"
  262. @click="
  263. rmbAggregate = true;
  264. rmbSingPopupRef.close();
  265. "
  266. >
  267. 我已详细知悉
  268. </view>
  269. </view>
  270. </uni-popup>
  271. </view>
  272. </view>
  273. <!-- 约价回收规则 -->
  274. <view class="up-content" v-if="activeTabId === 1">
  275. <up-parse :content="recyleContent"></up-parse>
  276. </view>
  277. <!-- 页面内 容结束 -->
  278. <PayPop ref="paypopRef" :pwdlength="6" @pwd_e="handlerPwd"></PayPop>
  279. </view>
  280. </template>
  281. <script setup>
  282. import { ref, computed, watch } from "vue";
  283. import PayPop from "../../../../components/pay-pop/pay-pop.vue";
  284. import { onShow, onLoad } from "@dcloudio/uni-app";
  285. import { getUserInfo, userPayPasswordConfirmAPI } from "@/api/user";
  286. import { useAppStore } from "@/stores/app";
  287. import useRealGoldPrice from "@/hooks/useRealGoldPrice";
  288. import { useStoreRights } from "@/stores/rights";
  289. import { exchangeGoldAPI, recycleCreateAPI } from "@/api/functions";
  290. import { agreementGetoneApi } from "@/api/user";
  291. // 1. 全局状态与工具
  292. const appStore = useAppStore();
  293. const rightsStore = useStoreRights();
  294. const paypopRef = ref(null);
  295. const {
  296. realGoldRecyclePrice,
  297. realKGoldRecyclePrice,
  298. realPtRecyclePrice,
  299. realAgRecyclePrice,
  300. } = useRealGoldPrice({});
  301. // 2. 页面基础响应式数据
  302. const activeTabId = ref(1);
  303. const tabList = ref([]);
  304. // 约价回收金料分类(含K金)
  305. const goldCategoryList = ref([
  306. { key: "au", title: "黄金", metalType: 1 },
  307. { key: "kau", title: "K金", metalType: 4 },
  308. { key: "pt", title: "铂金", metalType: 2 },
  309. { key: "ag", title: "白银", metalType: 3 },
  310. ]);
  311. // 余料出售金料分类(无K金)
  312. const goldRecyleCategoryList = ref([
  313. { key: "au", title: "黄金", metalType: 1 },
  314. { key: "pt", title: "铂金", metalType: 2 },
  315. { key: "ag", title: "白银", metalType: 3 },
  316. ]);
  317. const activeGoldCategoryKey = ref("au");
  318. const goldName = ref("黄金");
  319. const setting = ref(appStore.setting || {}); // 全局配置(如回收规则)
  320. const wxConfig = appStore.$wxConfig;
  321. // 3. 约价回收相关响应式数据
  322. const weight = ref("");
  323. const isWeightValid = ref(true);
  324. const weightTipText = ref("");
  325. const metalRecyleType = ref(1); // 金料类型(提交订单用)
  326. // 4. 余料出售相关响应式数据
  327. const rmbExtract = ref("");
  328. const rmbIsOut = ref(false); // 超过可售克重
  329. const rmbIsLowest = ref(false); // 低于最小克重
  330. const rmbIsPost = ref(false); // 输入合法
  331. const rmbNeedPrice = ref(0); // 预计出售金额
  332. const rmbAggregate = ref(false); // 同意协议
  333. const recyleContent = ref(""); // 约价回收规则
  334. const rmbAgreement = ref(""); // 余料出售协议
  335. const rmbSingPopupRef = ref(null); // 协议弹窗ref
  336. // 5. 金料定金映射(核心规则)
  337. const GOLD_DEPOSIT_MAP = ref({
  338. au: {
  339. depositPerGram: 40,
  340. minimumWeight: 1,
  341. minirecycleWeight: 1,
  342. svipDiscountEnabled: false,
  343. },
  344. kau: {
  345. depositPerGram: 40,
  346. minimumWeight: 1,
  347. minirecycleWeight: 1,
  348. svipDiscountEnabled: false,
  349. },
  350. pt: {
  351. depositPerGram: 40,
  352. minimumWeight: 1,
  353. minirecycleWeight: 1,
  354. svipDiscountEnabled: false,
  355. },
  356. ag: {
  357. depositPerGram: 40,
  358. minimumWeight: 1,
  359. minirecycleWeight: 1,
  360. svipDiscountEnabled: false,
  361. },
  362. });
  363. // 17. 生命周期:页面显示时同步数据
  364. onShow(() => {
  365. getAgreementContent();
  366. appStore.isLogin && updateUserInfo();
  367. });
  368. onLoad(() => {
  369. setGoldDeposit();
  370. if (!appStore.$wxConfig?.auditModeEnabled) {
  371. tabList.value = [
  372. { id: 1, name: "约价回收" },
  373. { id: 2, name: "余料出售" },
  374. ];
  375. } else {
  376. tabList.value = [
  377. // { id: 1, name: "金价咨询" },
  378. // { id: 2, name: "余料出售" },
  379. ];
  380. }
  381. });
  382. // 6. 计算属性(精简冗余逻辑)
  383. // 每克定金金额
  384. const currentDepositPerGram = computed(
  385. () =>
  386. GOLD_DEPOSIT_MAP.value[activeGoldCategoryKey.value]?.depositPerGram || 40
  387. );
  388. // 最小起收克重(统一1g)
  389. const currentMinWeight = computed(() => {
  390. const glodInfo = GOLD_DEPOSIT_MAP.value[activeGoldCategoryKey.value];
  391. console.log("glodInfo");
  392. if (activeTabId.value === 1) return glodInfo.minimumWeight;
  393. else return glodInfo.minirecycleWeight;
  394. });
  395. // 当前金料回收价(约价/余料通用)
  396. const realprice = computed(() => {
  397. switch (activeGoldCategoryKey.value) {
  398. case "au":
  399. return vipRealGoldRecyclePrice.value;
  400. case "kau":
  401. return viprealKGoldRecyclePrice.value;
  402. case "pt":
  403. return viprealPtRecyclePrice.value;
  404. case "ag":
  405. return viprealAgRecyclePrice.value;
  406. default:
  407. return 0;
  408. }
  409. });
  410. // 权益金额(会员额外加成)
  411. const benefitPrice = computed(() => {
  412. const soldBenefit = Number(rightsStore.userBenefits?.sold || 0);
  413. return appStore.userInfo?.svip ? soldBenefit + 0.3 : soldBenefit;
  414. });
  415. // 约价回收:预计回收金额
  416. const recylePrice = computed(() => Number(weight.value) * realprice.value || 0);
  417. // 约价回收:预付定金
  418. const advanceDeposit = computed(
  419. () => Number(weight.value) * currentDepositPerGram.value || 0
  420. );
  421. // 约价回收:余额是否充足
  422. const isBalanceEnough = computed(() => {
  423. const userBalance = Number(appStore.userInfo.nowMoney) || 0;
  424. return userBalance >= advanceDeposit.value;
  425. });
  426. // 约价回收:需充值金额
  427. const isNeedchargePrice = computed(() => {
  428. return advanceDeposit.value - Number(appStore.userInfo.nowMoney) || 0;
  429. });
  430. // 余料出售:当前可售克重(直接从用户信息读取,移除冗余的preciousMetal)
  431. const currentAvailableWeight = computed(() => {
  432. const balanceKeyMap = { au: "goldBalance", pt: "ptBalance", ag: "agBalance" };
  433. return (
  434. Number(appStore.userInfo[balanceKeyMap[activeGoldCategoryKey.value]]) || 0
  435. );
  436. });
  437. // 黄金调整价
  438. const adjustGoldprice = computed(() => {
  439. const res = rightsStore.userBenefits.nobleMeta?.find(
  440. (gold) => gold.name == "黄金"
  441. );
  442. return res;
  443. });
  444. // K金调整价
  445. const adjustKprice = computed(() => {
  446. const res = rightsStore.userBenefits.nobleMeta?.find(
  447. (gold) => gold.name == "K金"
  448. );
  449. return res;
  450. });
  451. // 铂金调整价
  452. const adjustPtprice = computed(() => {
  453. const res = rightsStore.userBenefits.nobleMeta?.find(
  454. (gold) => gold.name == "铂金"
  455. );
  456. return res;
  457. });
  458. // 白银调整价
  459. const adjustAgprice = computed(() => {
  460. const res = rightsStore.userBenefits.nobleMeta?.find(
  461. (gold) => gold.name == "白银"
  462. );
  463. return res;
  464. });
  465. // 7. 会员权益价计算(复用逻辑)
  466. const vipRealGoldRecyclePrice = computed(() => {
  467. const base = Number(realGoldRecyclePrice.value || 0);
  468. const benefit = Number(rightsStore.userBenefits?.sold || 0);
  469. const adjustBuy = Number(adjustGoldprice.value?.buyPriceAdjust || 0);
  470. return appStore.userInfo?.svip
  471. ? base - adjustBuy + benefit + 0.3
  472. : base + benefit - adjustBuy;
  473. });
  474. const viprealKGoldRecyclePrice = computed(() => {
  475. const base = Number(realKGoldRecyclePrice.value || 0);
  476. const benefit = Number(rightsStore.userBenefits?.sold || 0);
  477. const adjustBuy = Number(adjustKprice.value?.buyPriceAdjust || 0);
  478. return appStore.userInfo?.svip
  479. ? base - adjustBuy + benefit + 0.3
  480. : base + benefit - adjustBuy;
  481. });
  482. const viprealPtRecyclePrice = computed(() => {
  483. const base = Number(realPtRecyclePrice.value || 0);
  484. const benefit = Number(rightsStore.userBenefits?.sold || 0);
  485. const adjustBuy = Number(adjustPtprice.value?.buyPriceAdjust || 0);
  486. return appStore.userInfo?.svip
  487. ? base - adjustBuy + benefit + 0.3
  488. : base + benefit - adjustBuy;
  489. });
  490. const viprealAgRecyclePrice = computed(() => {
  491. const base = Number(realAgRecyclePrice.value || 0);
  492. const benefit = Number(rightsStore.userBenefits?.sold || 0);
  493. const adjustBuy = Number(adjustAgprice.value?.buyPriceAdjust || 0);
  494. return appStore.userInfo?.svip
  495. ? base - adjustBuy + benefit + 0.3
  496. : base + benefit - adjustBuy;
  497. });
  498. // 8. 约价回收克重校验
  499. const handleWeightInput = () => {
  500. const input = weight.value.trim();
  501. const weightNum = Number(input);
  502. const min = currentMinWeight.value;
  503. // 重置状态
  504. isWeightValid.value = true;
  505. weightTipText.value = "";
  506. if (!input) return;
  507. if (isNaN(weightNum)) {
  508. isWeightValid.value = false;
  509. weightTipText.value = "请输入有效数字";
  510. } else if (weightNum < min) {
  511. isWeightValid.value = false;
  512. weightTipText.value = `${goldName.value}起收克重为${min}g`;
  513. } else if ((input.split(".")[1] || "").length > 2) {
  514. isWeightValid.value = false;
  515. weightTipText.value = `${goldName.value}最多两位小数`;
  516. }
  517. };
  518. // 9. 余料出售克重校验(移除冗余的rmbRecyclePrice,直接用realprice)
  519. const rmbOnKeyInput = () => {
  520. const input = rmbExtract.value.trim();
  521. const extractNum = Number(input);
  522. const min = currentMinWeight.value;
  523. const max = currentAvailableWeight.value;
  524. // 重置状态
  525. rmbIsOut.value = false;
  526. rmbIsLowest.value = false;
  527. rmbIsPost.value = false;
  528. rmbNeedPrice.value = 0;
  529. if (!input || isNaN(extractNum)) return;
  530. if (extractNum < min) {
  531. rmbIsLowest.value = true;
  532. } else if (extractNum > max) {
  533. rmbIsOut.value = true;
  534. } else if ((input.split(".")[1] || "").length > 2) {
  535. return;
  536. } else {
  537. rmbIsPost.value = true;
  538. rmbNeedPrice.value = extractNum * realprice.value; // 直接用realprice
  539. }
  540. };
  541. // 10. 切换金料类型(移除调试log和无用注释)
  542. const handleGoldCategoryChange = (item, isRemain = false) => {
  543. metalRecyleType.value = item.metalType;
  544. activeGoldCategoryKey.value = item.key;
  545. goldName.value = item.title;
  546. // 重置约价回收状态
  547. weight.value = "";
  548. isWeightValid.value = true;
  549. weightTipText.value = "";
  550. // 重置余料出售状态
  551. if (isRemain) {
  552. rmbExtract.value = "";
  553. rmbIsOut.value = false;
  554. rmbIsLowest.value = false;
  555. rmbIsPost.value = false;
  556. rmbNeedPrice.value = 0;
  557. }
  558. };
  559. // 11. 切换标签页
  560. const handleTabChange = (item) => {
  561. activeTabId.value = item.id;
  562. // 重置金料类型为黄金
  563. handleGoldCategoryChange(
  564. { key: "au", title: "黄金", metalType: 1 },
  565. item.id === 2
  566. );
  567. };
  568. // 12. 约价回收提交
  569. const handleConfirmSell = async () => {
  570. if (!weight.value.trim()) {
  571. uni.showToast({ title: "请输入克重", icon: "none" });
  572. return;
  573. }
  574. if (!isWeightValid.value) {
  575. uni.showToast({ title: weightTipText.value, icon: "none" });
  576. return;
  577. }
  578. if (!isBalanceEnough.value) {
  579. uni.showToast({ title: "余额不足,请先充值", icon: "none" });
  580. return;
  581. }
  582. paypopRef.value.Open();
  583. };
  584. // 判断密码是否正确
  585. const handlerPwd = async (e) => {
  586. const res = await userPayPasswordConfirmAPI({ payPassword: e });
  587. if (!res.data) {
  588. return uni.showToast({
  589. title: "支付密码错误",
  590. duration: 2000,
  591. icon: "error",
  592. });
  593. }
  594. // if()
  595. try {
  596. uni.showLoading({
  597. title: "下单中",
  598. });
  599. const res = await recycleCreateAPI({
  600. deposit:
  601. GOLD_DEPOSIT_MAP.value[activeGoldCategoryKey.value].depositPerGram,
  602. svipDiscountEnabled:
  603. GOLD_DEPOSIT_MAP.value[activeGoldCategoryKey.value].svipDiscountEnabled,
  604. estimatedWeight: weight.value,
  605. metalType: metalRecyleType.value,
  606. });
  607. if (!res.data.flag) {
  608. uni.showToast({
  609. title: "您还有暂未寄出的订单,请寄出后再下单",
  610. icon: "none",
  611. });
  612. }
  613. uni.hideLoading();
  614. // 延迟跳转确保提示可见
  615. setTimeout(() => {
  616. uni.navigateTo({
  617. url: `/pages/users/vault/recycle/order_fill?order=${JSON.stringify(
  618. res.data
  619. )}`,
  620. });
  621. }, 500);
  622. } finally {
  623. }
  624. };
  625. // 13. 余料出售提交
  626. const rmbHandleConfirmSell = async () => {
  627. if (!rmbAggregate.value) {
  628. uni.showToast({ title: "请阅读并同意出售协议", icon: "none" });
  629. return;
  630. }
  631. if (currentAvailableWeight.value < rmbExtract.value) {
  632. uni.showToast({ title: "余料不足", icon: "none" });
  633. return;
  634. }
  635. if (!rmbIsPost.value) {
  636. uni.showToast({ title: "请输入克重", icon: "none" });
  637. return;
  638. }
  639. uni.showLoading({
  640. title: "出售中",
  641. });
  642. const res = await exchangeGoldAPI({
  643. metalType: metalRecyleType.value,
  644. weight: rmbExtract.value,
  645. });
  646. if (res.code === 200) {
  647. uni.showToast({ title: "出售成功,余额已更新", icon: "none" });
  648. setTimeout(() => {
  649. uni.navigateTo({ url: "/pages/users/vault/index" });
  650. }, 500);
  651. // 重置输入
  652. rmbExtract.value = "";
  653. }
  654. uni.hideLoading();
  655. };
  656. // 14. 打开协议弹窗
  657. const rmbShowAggre = () => {
  658. rmbSingPopupRef.value?.open();
  659. };
  660. // 16. 数据同步方法
  661. async function updateUserInfo() {
  662. const { data } = await getUserInfo();
  663. appStore.UPDATE_USERINFO(data);
  664. }
  665. // 获取协议内容
  666. function getAgreementContent() {
  667. // 约价回收规则
  668. agreementGetoneApi({ name: "recyle" }).then((res) => {
  669. recyleContent.value = res.data?.content || "";
  670. });
  671. // 余料出售协议
  672. agreementGetoneApi({ name: "soldGold" }).then((res) => {
  673. rmbAgreement.value = res.data?.content || "";
  674. });
  675. }
  676. const setGoldDeposit = () => {
  677. if (!wxConfig?.metalConfigs) return;
  678. const initDate = {};
  679. wxConfig.metalConfigs.map((item) => {
  680. initDate[item.metalType] = item;
  681. });
  682. GOLD_DEPOSIT_MAP.value = initDate;
  683. };
  684. // 18. 监听价格/克重变化,实时更新余料出售金额
  685. watch([realprice, rmbExtract], ([newPrice, newWeight]) => {
  686. const priceNum = Number(newPrice) || 0;
  687. const weightNum = Number(newWeight) || 0;
  688. const min = currentMinWeight.value;
  689. const max = currentAvailableWeight.value;
  690. // 仅在输入合法时更新(避免重复计算)
  691. if (weightNum >= min && weightNum <= max && !isNaN(weightNum)) {
  692. rmbNeedPrice.value = Number((priceNum * weightNum)?.toFixed(2)) || 0;
  693. }
  694. });
  695. </script>
  696. <style scoped lang="scss">
  697. /* 样式保持不变,此处省略(与原代码一致) */
  698. .sell-materials-page {
  699. min-height: 100vh;
  700. padding: 10rpx 30rpx;
  701. position: relative;
  702. background-image: linear-gradient(360deg, #ffffff 50%, #e8c279 100%);
  703. .sell-postend-banner {
  704. position: absolute;
  705. top: 0;
  706. left: 0;
  707. width: 100%;
  708. height: 50vh;
  709. // z-index: 0;
  710. // background-image: linear-gradient(360deg, #ffffff 0%, #e8c279 100%);
  711. }
  712. .page-content {
  713. .tabs-container {
  714. width: 100%;
  715. display: flex;
  716. justify-content: space-around;
  717. margin: 45rpx 0;
  718. font-size: 30rpx;
  719. color: #000000;
  720. font-weight: bold;
  721. .tabs-item {
  722. padding: 10rpx 0;
  723. &.active {
  724. border-bottom: 4rpx solid #cc9933;
  725. }
  726. }
  727. }
  728. .gold-category-list {
  729. display: flex;
  730. width: 100%;
  731. .gold-category-item {
  732. flex: 1;
  733. display: flex;
  734. justify-content: center;
  735. align-items: center;
  736. position: relative;
  737. font-size: 30rpx;
  738. color: #000000;
  739. &.active::after {
  740. position: absolute;
  741. bottom: -36rpx;
  742. left: 50%;
  743. transform: translateX(-50%);
  744. content: "";
  745. z-index: 100;
  746. border-style: solid;
  747. border-width: 0 23rpx 23rpx 23rpx;
  748. border-color: transparent transparent #ffffff transparent;
  749. width: 0;
  750. height: 0;
  751. transform-origin: center top;
  752. animation: showTriangle 0.3s ease-out forwards;
  753. }
  754. }
  755. }
  756. .recycle-info-card {
  757. width: 682rpx;
  758. background-color: #ffffff;
  759. box-shadow: 0rpx 3rpx 13rpx 0rpx rgba(0, 0, 0, 0.13);
  760. border-radius: 20rpx;
  761. display: flex;
  762. flex-direction: column;
  763. align-items: center;
  764. box-sizing: border-box;
  765. padding: 35rpx 40rpx;
  766. margin-top: 36rpx;
  767. .price-info {
  768. font-size: 28rpx;
  769. color: #000000;
  770. position: relative;
  771. margin-top: 20rpx;
  772. .equity-tag {
  773. position: absolute;
  774. top: -35rpx;
  775. right: 0;
  776. padding: 2rpx 20rpx;
  777. font-size: 20rpx;
  778. color: #fff;
  779. background: #cd9933;
  780. border-radius: 10rpx;
  781. border-top-left-radius: 60rpx;
  782. border-bottom-right-radius: 60rpx;
  783. }
  784. .current-price {
  785. font-size: 37rpx;
  786. color: #cc9933;
  787. margin: 0 10rpx;
  788. }
  789. }
  790. .weight-input-container {
  791. margin: 25rpx 0;
  792. .weight-input {
  793. width: 572rpx;
  794. height: 80rpx;
  795. background-color: #f3f3f3;
  796. border-radius: 10rpx;
  797. display: flex;
  798. justify-content: center;
  799. align-items: center;
  800. input {
  801. text-align: center;
  802. width: 100%;
  803. font-size: 26rpx;
  804. color: #000;
  805. }
  806. }
  807. .weight-tip {
  808. margin-top: 5rpx;
  809. font-size: 21rpx;
  810. color: red;
  811. }
  812. }
  813. .amount-info-group {
  814. width: 100%;
  815. margin-top: 10rpx;
  816. .amount-info-item {
  817. display: flex;
  818. justify-content: space-between;
  819. font-size: 26rpx;
  820. color: #000000;
  821. margin-bottom: 15rpx;
  822. .info-label {
  823. color: #666;
  824. }
  825. .info-value {
  826. color: #000;
  827. }
  828. }
  829. }
  830. }
  831. .balance-pay-section {
  832. margin-top: 25rpx;
  833. width: 682rpx;
  834. height: 165rpx;
  835. background-color: #ffffff;
  836. box-shadow: 0rpx 3rpx 13rpx 0rpx rgba(0, 0, 0, 0.13);
  837. border-radius: 20rpx;
  838. display: flex;
  839. justify-content: space-between;
  840. align-items: center;
  841. box-sizing: border-box;
  842. padding: 20rpx 26rpx;
  843. .balance-info {
  844. display: flex;
  845. align-items: center;
  846. .balance-icon {
  847. width: 53rpx;
  848. }
  849. .balance-detail {
  850. display: flex;
  851. flex-direction: column;
  852. margin-left: 20rpx;
  853. .balance-title {
  854. font-size: 26rpx;
  855. color: #000000;
  856. }
  857. .balance-content {
  858. display: flex;
  859. margin-top: 6rpx;
  860. .balance-amount {
  861. font-size: 26rpx;
  862. color: #7c7c7c;
  863. text {
  864. margin-left: 10rpx;
  865. }
  866. }
  867. .balance-warning {
  868. font-size: 24rpx;
  869. color: #7c7c7c;
  870. margin-left: 10rpx;
  871. }
  872. }
  873. }
  874. }
  875. .recharge-btn {
  876. width: 177rpx;
  877. height: 59rpx;
  878. background-color: #e8c279;
  879. font-size: 26rpx;
  880. border-radius: 30rpx;
  881. display: flex;
  882. justify-content: center;
  883. align-items: center;
  884. color: #ffffff;
  885. }
  886. }
  887. .btn-box {
  888. width: 100%;
  889. display: flex;
  890. justify-content: center;
  891. align-items: center;
  892. .confirm-btn {
  893. width: 269rpx;
  894. height: 66rpx;
  895. background-color: #e8c279;
  896. border-radius: 34rpx;
  897. display: flex;
  898. justify-content: center;
  899. align-items: center;
  900. font-size: 28rpx;
  901. color: #ffffff;
  902. margin: 70rpx 0;
  903. }
  904. }
  905. .recycle-container {
  906. .rule-desc {
  907. width: 100%;
  908. padding: 20rpx;
  909. box-sizing: border-box;
  910. }
  911. }
  912. .gold-container {
  913. .quick-select-section {
  914. width: 682rpx;
  915. margin-top: 25rpx;
  916. background-color: #ffffff;
  917. box-shadow: 0rpx 3rpx 13rpx 0rpx rgba(0, 0, 0, 0.13);
  918. border-radius: 20rpx;
  919. padding: 20rpx;
  920. box-sizing: border-box;
  921. .quick-select-title {
  922. font-size: 26rpx;
  923. color: #000000;
  924. margin-bottom: 20rpx;
  925. }
  926. .quick-select-grid {
  927. display: flex;
  928. flex-wrap: wrap;
  929. justify-content: center;
  930. gap: 20rpx;
  931. .quick-select-item {
  932. border: 2rpx solid #c9c9c9;
  933. width: calc(33% - 20rpx);
  934. height: 120rpx;
  935. background-color: #efefef;
  936. border-radius: 10rpx;
  937. display: flex;
  938. justify-content: center;
  939. align-items: center;
  940. font-size: 32rpx;
  941. color: #bbbbbb;
  942. transition: all 0.2s ease;
  943. box-sizing: border-box;
  944. &.active {
  945. color: #cc9933;
  946. border: 2rpx solid #cf9a31;
  947. background: #f8f4e9;
  948. }
  949. }
  950. }
  951. }
  952. .agreement-section {
  953. width: 682rpx;
  954. margin-top: 25rpx;
  955. display: flex;
  956. align-items: center;
  957. justify-content: center;
  958. .aggregate {
  959. display: flex;
  960. align-items: center;
  961. .choose {
  962. width: 30rpx;
  963. height: 30rpx;
  964. margin-right: 10rpx;
  965. }
  966. .aggre {
  967. font-size: 24rpx;
  968. color: #666666;
  969. .aggre-text {
  970. color: #cc9933;
  971. }
  972. }
  973. }
  974. }
  975. }
  976. }
  977. }
  978. .signContent {
  979. background-color: #f8f8f8;
  980. padding: 20px;
  981. box-sizing: border-box;
  982. display: flex;
  983. justify-content: center;
  984. align-items: center;
  985. flex-direction: column;
  986. border-radius: 20px 20px 0 0;
  987. .scroll {
  988. background-color: #fff;
  989. padding: 4px;
  990. height: 300px;
  991. overflow-y: hidden;
  992. border: 1px solid #dfdfdf;
  993. }
  994. .footer {
  995. margin-top: 10px;
  996. color: #fff;
  997. padding: 4px 20px;
  998. border-radius: 20px;
  999. background: linear-gradient(to right, #8ed187, #5dd665);
  1000. }
  1001. }
  1002. @keyframes showTriangle {
  1003. 0% {
  1004. transform: translateX(-50%) scale(0);
  1005. }
  1006. 100% {
  1007. transform: translateX(-50%) scale(1);
  1008. }
  1009. }
  1010. </style>