index.vue 32 KB

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