exchange-transaction.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. <template>
  2. <view>
  3. <view class="d-flex p-md justify-between align-center fn-16 color-light">
  4. <view @click="$emit('symbol')">
  5. <i class="iconfont color-theme-1 fn-18">&#xe655;</i>
  6. {{ symbol }}
  7. </view>
  8. <view class="d-flex fn-20 color-theme-1">
  9. <!-- foucs -->
  10. <view class="m-r-xs" @click="$emit('option')">
  11. <van-icon v-if="isCoolect" name="star" />
  12. <van-icon v-else class="color-default" name="star-o" />
  13. </view>
  14. <v-link
  15. tag="div"
  16. v-if="symbol"
  17. :to="{ path: '/pages/exchange/index', query: { code: symbol } }"
  18. >
  19. <van-icon name="chart-trending-o" />
  20. </v-link>
  21. </view>
  22. </view>
  23. <view class="d-flex p-x-md p-b-md">
  24. <!-- 买卖表单 -->
  25. <view class="w-6/12">
  26. <view
  27. class="d-flex fn-center justify-between rounded-xs overflow-hidden"
  28. >
  29. <view
  30. class="w-6/12 h-34 d-flex justify-center align-center"
  31. :class="{
  32. 'bg-form-panel-4 ': form.direction != 'buy',
  33. 'bg-gradient-blue color-plain': form.direction == 'buy',
  34. }"
  35. @click="
  36. form.direction = 'buy';
  37. getDefaultPrice();
  38. "
  39. >{{ $t("exchange.c3") }}</view
  40. >
  41. <view
  42. class="color-gray w-6/12 h-34 d-flex justify-center align-center"
  43. :class="{
  44. 'bg-form-panel-4': form.direction != 'sell',
  45. 'bg-gradient-blue color-plain': form.direction == 'sell',
  46. }"
  47. @click="
  48. form.direction = 'sell';
  49. getDefaultPrice();
  50. "
  51. >{{ $t("exchange.c4") }}</view
  52. >
  53. </view>
  54. <v-picker :list="typeList" v-model="form.type" class="p-y-xs fn-sm">
  55. {{ activeType.label }}
  56. <i class="iconfont">&#xe6e9;</i>
  57. </v-picker>
  58. <template v-if="form.type == 1">
  59. <view>
  60. <van-stepper
  61. :min="0"
  62. :value="form.entrust_price"
  63. @change="form.entrust_price = $event.detail"
  64. input-width="61%"
  65. step="0.01"
  66. />
  67. <view class="tips fn-xs m-t-xs">{{ targetCoin }}</view>
  68. </view>
  69. <view class="m-t-xs">
  70. <v-input
  71. v-model="form.amount"
  72. class="h-30 p-x-xs rounded-xs color-light bg-form-panel-4"
  73. :placeholder="$t('exchange.c5')"
  74. >
  75. <template #right>
  76. <view class="color-default fn-10">{{
  77. activeCoin.coin_name
  78. }}</view>
  79. </template>
  80. </v-input>
  81. </view>
  82. </template>
  83. <template v-if="form.type == 2">
  84. <view>
  85. <v-input
  86. disabled
  87. class="h-30 p-x-xs rounded-xs color-light bg-panel-4"
  88. :placeholder="$t('exchange.c6')"
  89. ></v-input>
  90. </view>
  91. <view class="tips fn-xs m-t-xs color-panel-4">{{ targetCoin }}</view>
  92. <view class="m-t-xs">
  93. <v-input
  94. v-model="form.amount"
  95. class="h-30 p-x-xs rounded-xs color-light bg-panel-4"
  96. :placeholder="
  97. form.direction == 'buy' ? $t('exchange.c7') : $t('exchange.c5')
  98. "
  99. >
  100. <template #right>
  101. <view class="color-default" v-if="form.direction == 'sell'">{{
  102. currentCoin
  103. }}</view>
  104. <view class="color-default" v-if="form.direction == 'buy'">{{
  105. targetCoin
  106. }}</view>
  107. </template>
  108. </v-input>
  109. </view>
  110. </template>
  111. <view>
  112. <view class="tips fn-10 m-t-xs"
  113. >{{ $t("exchange.c8") }}:{{ usable }}{{ usableUnit }}</view
  114. >
  115. </view>
  116. <view>
  117. <view class="m-t-xs">
  118. <view class="d-flex justify-between fn-sm">
  119. <text>0%</text>
  120. <text>100%</text>
  121. </view>
  122. <view class="d-flex justify-center">
  123. <bing-progress
  124. activeColor="#79D47C"
  125. barBorderRadius="20px"
  126. handleWidth="12px"
  127. handleHeight="12px"
  128. handleColor="#79D47C"
  129. borderRadius="20px"
  130. width="150px"
  131. :showInfo="false"
  132. strokeWidth="2px"
  133. noActiveColor="#484859"
  134. @dragging="sliderChange"
  135. :value="activeStep"
  136. />
  137. </view>
  138. </view>
  139. </view>
  140. <view class="d-flex fn-sm m-b-xs color-light justify-between">
  141. <span>{{ $t("exchange.c9") }}:</span>
  142. <span v-if="form.type == 2 && form.direction == 'sell'"
  143. >{{ totalMoney }} {{ currentCoin }}</span
  144. >
  145. <span v-else>{{ totalMoney }} {{ targetCoin }}</span>
  146. </view>
  147. <template v-if="isLogin">
  148. <v-button
  149. size="small"
  150. :type="form.direction == 'buy' ? 'green' : 'red'"
  151. class="w-max rounded-xs"
  152. block
  153. @click="storeEntrust"
  154. ref="btn"
  155. >{{
  156. form.direction == "buy"
  157. ? this.$t("exchange.c3")
  158. : this.$t("exchange.c4")
  159. }}
  160. {{ activeCoin.coin_name }}</v-button
  161. >
  162. </template>
  163. <template v-else>
  164. <navigator class="d-block" url="/pages/login/index" open-type="reLaunch" hover-class="none">
  165. <v-button
  166. size="small"
  167. block
  168. :type="form.direction == 'buy' ? 'primary' : 'danger'"
  169. class="w-max"
  170. >{{ $t("exchange.d0") }}</v-button
  171. >
  172. </navigator>
  173. </template>
  174. <v-link
  175. :to="{ path: '/pages/exchange/index', query: { code: symbol } }"
  176. class="d-block m-t-md"
  177. >
  178. <view class="fn-10 p-y-xs">{{ symbol }} {{ $t("exchange.d1") }}</view>
  179. <view class="h-100">
  180. <time-sharing
  181. :query="query"
  182. ref="highChart"
  183. :tradeList="tradeList"
  184. ></time-sharing>
  185. </view>
  186. </v-link>
  187. </view>
  188. <!-- 深度列表 -->
  189. <view class="w-6/12 p-l-md d-flex flex-col dep-list">
  190. <sell-and-buy
  191. :buyList="buyList"
  192. :max="symbol == 'ABK/USDT' ? 14 : 7"
  193. :sellList="sellList"
  194. :symbol="symbol"
  195. >
  196. <view
  197. class="fn-md row"
  198. :class="newPrice.increase < 0 ? 'color-sell' : 'color-buy'"
  199. >
  200. {{ newPrice.price }}
  201. </view>
  202. <!-- <view class="fn-xs" v-if="newPrice.price">
  203. ≈{{ omitTo(newPrice.price * price_cny, 2) }}CNY
  204. </view> -->
  205. </sell-and-buy>
  206. </view>
  207. </view>
  208. <view class="h-20"></view>
  209. <view class="p-t-md" style="padding-bottom:60px">
  210. <view class="fn-lg m-b-md">{{ $t("exchange.d3") }}</view>
  211. <table class="w-max">
  212. <thead class="fn-xs">
  213. <tr>
  214. <td class="p-y-xs p-l-md">{{ $t("exchange.d4") }}</td>
  215. <td class="p-y-xs">{{ $t("exchange.d5") }}</td>
  216. <td class="p-y-xs fn-right">{{ $t("exchange.d2") }}</td>
  217. <td class="p-y-xs fn-right p-r-md">{{ $t("exchange.c5") }}</td>
  218. </tr>
  219. </thead>
  220. <tbody class="color-light trade-list">
  221. <!-- ||item.tradeId -->
  222. <tr v-for="(item, idx) in tradeList" :key="idx">
  223. <td class="p-y-xs p-l-md rounded-bl-xs rounded-tl-xs">
  224. {{ parseTime(item.ts, false, "{h}:{i}:{s}") }}
  225. </td>
  226. <td :class="`color-${item.direction}`">
  227. <template v-if="item.direction == 'buy'">{{
  228. $t("exchange.b5")
  229. }}</template>
  230. <template v-else-if="'sell'">{{ $t("exchange.b6") }}</template>
  231. </td>
  232. <td class="fn-right">{{ item.price }}</td>
  233. <td class="p-y-xs fn-right p-r-md rounded-br-xs rounded-tr-xs">
  234. {{ omitTo(item.amount, 8) * 1 }}
  235. </td>
  236. </tr>
  237. </tbody>
  238. </table>
  239. </view>
  240. </view>
  241. </template>
  242. <script>
  243. import timeSharing from "@/pages/exchange/time-sharing";
  244. import Exchange from "@/api/exchange";
  245. import Market from "@/api/market";
  246. import date from "@/utils/class/date";
  247. import math from "@/utils/class/math";
  248. import { mapState } from "vuex";
  249. import sellAndBuy from "@/pages/exchange/sell-and-buy";
  250. import bingProgress from "@/components/bing-progress/bing-progress.vue";
  251. export default {
  252. props: {
  253. collect: {
  254. default() {
  255. return [];
  256. },
  257. type: Array,
  258. required: false,
  259. },
  260. marketList: {
  261. defalut() {
  262. return [];
  263. },
  264. type: Array,
  265. required: false,
  266. },
  267. query: {
  268. default: {},
  269. type: Object,
  270. required: false,
  271. },
  272. isShow: {
  273. default: true,
  274. type: Boolean,
  275. required: false,
  276. },
  277. },
  278. components: {
  279. timeSharing,
  280. sellAndBuy,
  281. bingProgress,
  282. },
  283. name: "exchange-transaction",
  284. data() {
  285. return {
  286. buyList: [],
  287. sellList: [],
  288. tradeList: [],
  289. form: {
  290. direction: "buy",
  291. type: 1,
  292. symbol: this.symbol,
  293. entrust_price: "",
  294. amount: "",
  295. trigger_price: undefined,
  296. total: "",
  297. },
  298. balanceMap: {},
  299. unSymbol: "",
  300. newPrice: {},
  301. price_cny: 6.72,
  302. };
  303. },
  304. computed: {
  305. ...mapState({
  306. ws: "ws",
  307. }),
  308. isLogin() {
  309. return Boolean(uni.getStorageSync("token"));
  310. },
  311. symbol() {
  312. return this.query.symbol;
  313. },
  314. // 是否为自选
  315. isCoolect() {
  316. return this.collect.map((item) => item.pair_name).includes(this.symbol);
  317. },
  318. typeList() {
  319. return [
  320. {
  321. value: 1,
  322. label: this.$t("exchange.d6"),
  323. },
  324. {
  325. value: 2,
  326. label: this.$t("exchange.d7"),
  327. },
  328. ];
  329. },
  330. activeType() {
  331. return this.typeList.find((item) => item.value == this.form.type);
  332. },
  333. // 当前选中的coin
  334. activeCoin() {
  335. if (!this.marketList.length) return {};
  336. let list = [];
  337. this.marketList.forEach((parentItem) => {
  338. parentItem.marketInfoList.forEach((item) => {
  339. list.push(item);
  340. });
  341. });
  342. return list.find((item) => item.pair_name == this.symbol);
  343. },
  344. // 当前币种
  345. currentCoin() {
  346. if (!this.symbol) return "";
  347. return this.symbol.split("/")[0];
  348. },
  349. // 目标币种
  350. targetCoin() {
  351. if (!this.symbol) return "";
  352. return this.symbol.split("/")[1];
  353. },
  354. // 当前余额
  355. currentBalance() {
  356. return this.balanceMap[this.currentCoin] || {};
  357. },
  358. // 目标余额
  359. targetBalance() {
  360. return this.balanceMap[this.targetCoin] || {};
  361. },
  362. // 计算交易额
  363. totalMoney() {
  364. // todo
  365. let totalMoney = 0;
  366. if (this.form.type == 1) {
  367. totalMoney = math.multiple(this.form.amount, this.form.entrust_price);
  368. } else {
  369. totalMoney = this.form.amount;
  370. }
  371. return totalMoney;
  372. },
  373. // 显示百分比
  374. activeStep() {
  375. let num = 0;
  376. if (this.form.direction == "buy") {
  377. if (!this.targetBalance.usable_balance) return 0;
  378. num = this.totalMoney / this.targetBalance.usable_balance;
  379. } else if (this.form.direction == "sell") {
  380. if (!this.currentBalance.usable_balance) return 0;
  381. num = this.form.amount / this.currentBalance.usable_balance;
  382. }
  383. if (!isNaN(num)) {
  384. num = num.toFixed(3);
  385. }
  386. return num * 100;
  387. },
  388. // 可用数量
  389. usable() {
  390. if (this.form.direction == "buy") {
  391. if (!this.targetBalance.usable_balance) return 0;
  392. return this.omitTo(
  393. this.targetBalance.usable_balance,
  394. this.activeCoin.price_decimals
  395. );
  396. } else {
  397. if (!this.currentBalance.usable_balance) return 0;
  398. return this.omitTo(
  399. this.currentBalance.usable_balance,
  400. this.activeCoin.qty_decimals
  401. );
  402. }
  403. },
  404. // 可用单位
  405. usableUnit() {
  406. if (this.form.direction == "buy") {
  407. return this.targetCoin;
  408. } else {
  409. return this.currentCoin;
  410. }
  411. },
  412. },
  413. watch: {
  414. symbol(n, o) {
  415. this.getBooks();
  416. this.getUserBalance();
  417. if (o) {
  418. this.unLink(o.replace("/", "").toLocaleLowerCase());
  419. }
  420. },
  421. isShow(n) {
  422. if (n) {
  423. this.linkSocket(this.activeCoin.symbol);
  424. } else {
  425. this.unLink(this.activeCoin.symbol);
  426. }
  427. },
  428. },
  429. methods: {
  430. parseTime: date.parseTime,
  431. omitTo: math.omitTo,
  432. // 计算滑动出来的金额
  433. sliderChange($ev) {
  434. let num = $ev.value / 100;
  435. if (this.form.direction == "buy") {
  436. if (!this.targetBalance.usable_balance) return;
  437. if (this.form.type == 1) {
  438. this.form.amount = math.multiple(
  439. this.targetBalance.usable_balance / this.form.entrust_price,
  440. num,
  441. 4
  442. );
  443. } else if (this.form.type == 2) {
  444. this.form.amount = this.form.amount = math.multiple(
  445. this.targetBalance.usable_balance,
  446. num,
  447. 4
  448. );
  449. }
  450. } else if (this.form.direction == "sell") {
  451. if (!this.currentBalance.usable_balance) return;
  452. this.form.amount = math.multiple(
  453. this.currentBalance.usable_balance,
  454. num,
  455. 4
  456. );
  457. }
  458. },
  459. // 提交订单
  460. storeEntrust() {
  461. let data = this.form;
  462. // debugger
  463. if (this.form.type == 1) {
  464. //限价
  465. if (!this.form.entrust_price) {
  466. this.$toast(this.$t("exchange.d8"));
  467. return;
  468. }
  469. if (!this.form.amount) {
  470. this.$toast(this.$t("exchange.d9"));
  471. return;
  472. }
  473. } else if (this.form.type == 2) {
  474. //市价
  475. this.form.total = this.form.amount;
  476. if (!this.form.total) {
  477. if (this.form.direction == "buy") {
  478. this.$toast(this.$t("exchange.e0"));
  479. } else {
  480. this.$toast(this.$t("exchange.d9"));
  481. }
  482. return;
  483. }
  484. }
  485. this.form.symbol = this.symbol;
  486. Exchange.storeEntrust(data, {
  487. btn: this.$refs.btn,
  488. })
  489. .then(() => {
  490. this.form.amount = "";
  491. this.form.total = "";
  492. this.$toast.success(this.$t("exchange.e1"));
  493. this.getUserBalance();
  494. })
  495. .catch(() => {});
  496. },
  497. // 获取列表
  498. getBooks() {
  499. console.log(this.symbol)
  500. if (!this.symbol) return;
  501. Market.getBooks({
  502. symbol: this.symbol,
  503. }).then((res) => {
  504. console.log(res)
  505. this.buyList = res.data.buyList;
  506. this.sellList = res.data.sellList;
  507. this.tradeList = res.data.tradeList;
  508. this.linkSocket(this.activeCoin.symbol);
  509. this.setChartData(this.tradeList);
  510. this.getDefaultPrice();
  511. this.newPrice = this.tradeList[0] || {};
  512. });
  513. // this.getCurrencyExCny()
  514. },
  515. // 获取默认价格
  516. getDefaultPrice() {
  517. if (this.form.direction == "buy") {
  518. this.form.entrust_price = Math.min(
  519. ...this.tradeList.map((item) => item.price)
  520. );
  521. } else {
  522. this.form.entrust_price = Math.max(
  523. ...this.buyList.map((item) => item.price)
  524. );
  525. }
  526. },
  527. // 计算深度
  528. getValue(amount) {
  529. const arr = this.buyList.concat(this.sellList).map((item) => item.amount);
  530. let max = Math.max(...arr);
  531. return math.division(amount, max, 2) * 100;
  532. },
  533. selectType() {
  534. this.$picker(this.typeList, { value: this.form.type }).then((res) => {
  535. this.form.type = res;
  536. });
  537. },
  538. // 获取余额
  539. getUserBalance() {
  540. if (!this.symbol || !this.isLogin) return;
  541. Exchange.getUserBalance({
  542. symbol: this.symbol,
  543. }).then((res) => {
  544. this.balanceMap = { ...this.balanceMap, ...res.data };
  545. });
  546. },
  547. // 获取汇率
  548. getCurrencyExCny() {
  549. // console.log(123123)
  550. Exchange.getCurrencyExCny({
  551. coin_name: this.targetCoin,
  552. }).then((res) => {
  553. console.log(res.data)
  554. this.price_cny = res.data.price_cny;
  555. });
  556. },
  557. // 链接socket
  558. linkSocket(symbol) {
  559. console.log(symbol)
  560. this.unSymbol = symbol;
  561. // 订阅买线
  562. this.ws.send({
  563. cmd: "sub",
  564. msg: `buyList_${symbol}`,
  565. });
  566. // 订阅卖线
  567. this.ws.send({
  568. cmd: "sub",
  569. msg: `sellList_${symbol}`,
  570. });
  571. // 订阅成交
  572. this.ws.send({
  573. cmd: "sub",
  574. msg: `tradeList_${symbol}`,
  575. });
  576. },
  577. // 取消订阅
  578. unLink(symbol) {
  579. // 取消买线
  580. this.ws.send({
  581. cmd: "unsub",
  582. msg: `buyList_${symbol}`,
  583. });
  584. // 取消卖线
  585. this.ws.send({
  586. cmd: "unsub",
  587. msg: `sellList_${symbol}`,
  588. });
  589. // 取消成交
  590. this.ws.send({
  591. cmd: "unsub",
  592. msg: `tradeList_${symbol}`,
  593. });
  594. },
  595. socketMessage() {
  596. this.ws.on("message", (res) => {
  597. let symbol = this.activeCoin && this.activeCoin.symbol;
  598. let { data, sub } = res;
  599. switch (sub) {
  600. case `buyList_${symbol}`:
  601. this.buyList = data;
  602. break;
  603. case `sellList_${symbol}`:
  604. this.sellList = data.sort((a, b) => b.price - a.price);
  605. break;
  606. case `tradeList_${symbol}`:
  607. if (this.tradeList.length > 20) {
  608. this.tradeList.unshift(data);
  609. this.tradeList.pop();
  610. } else {
  611. this.tradeList.unshift(data);
  612. }
  613. this.addChartPoint(data);
  614. this.newPrice = data;
  615. break;
  616. }
  617. });
  618. },
  619. // 设置highChart
  620. setChartData(arr) {
  621. if (!this.$refs.highChart) return;
  622. let list = arr.map((item) => {
  623. return [item.ts, item.price];
  624. });
  625. this.$nextTick(() => {
  626. this.$refs.highChart.resetData(list);
  627. });
  628. },
  629. // 添加highChart点
  630. addChartPoint(obj) {
  631. if (!this.$refs.highChart) return;
  632. this.$refs.highChart.addPoint([obj.ts, obj.price]);
  633. },
  634. },
  635. mounted() {
  636. this.getBooks();
  637. this.getUserBalance();
  638. this.socketMessage();
  639. if (this.query.direction) {
  640. this.form.direction = this.query.direction;
  641. }
  642. },
  643. destroyed() {
  644. // this.unLink(this.unSymbol);
  645. },
  646. };
  647. </script>
  648. <style lang="scss" scoped>
  649. .custom-button {
  650. min-width: 26px;
  651. color: #fff;
  652. line-height: 18px;
  653. text-align: center;
  654. border-radius: 100px;
  655. }
  656. /deep/ .van-stepper {
  657. display: flex;
  658. justify-content: space-between;
  659. .minus-class,
  660. .input-class,
  661. .plus-class {
  662. background: $form-panel-4;
  663. }
  664. }
  665. .dep-list {
  666. height: 440px;
  667. .row {
  668. position: relative;
  669. .proagess {
  670. position: absolute;
  671. right: 0;
  672. top: 0;
  673. transition: width 0.3s;
  674. }
  675. }
  676. }
  677. ::v-deep .van-step--horizontal {
  678. .van-step__circle-container {
  679. background-color: transparent;
  680. .van-icon-checked {
  681. width: 14px;
  682. height: 14px;
  683. position: relative;
  684. background: rgba($green, 0.7);
  685. font-size: 0;
  686. display: block;
  687. border-radius: 20px;
  688. bottom: 9px;
  689. &::after {
  690. content: "";
  691. display: block;
  692. font-size: 0;
  693. width: 8px;
  694. height: 8px;
  695. position: absolute;
  696. left: 50%;
  697. top: 50%;
  698. transform: translate(-50%, -50%);
  699. background: $green;
  700. border-radius: 20px;
  701. }
  702. }
  703. }
  704. .van-step__line {
  705. bottom: 8px;
  706. }
  707. }
  708. .trade-list {
  709. tr:nth-of-type(2n-1) {
  710. box-shadow: $shadow;
  711. border-radius: $border-radius-xs;
  712. td {
  713. background: $panel-3;
  714. }
  715. }
  716. }
  717. </style>