open-position.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. <template>
  2. <view>
  3. <view class="d-flex p-md justify-between align-center fn-18 color-light">
  4. <view @click="$emit('symbol')">
  5. <i class="iconfont color-theme-1">&#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-light" name="star-o" />
  13. </view>
  14. <v-link v-if="symbol" tag="div" class="m-r-sm" :to="{
  15. path: '/pages/exchange/index',
  16. query: { code: symbol, contract: 1 },
  17. }">
  18. <van-icon name="chart-trending-o" />
  19. </v-link>
  20. <!-- <van-icon name="weapp-nav" @click="show = true" /> -->
  21. </view>
  22. </view>
  23. <view class="m-b-md p-md bg-form-panel-3 fn-sm m-x-md rounded-sm d-flex justify-around align-center">
  24. <view class="item" style="flex:1;overflow: auto;word-break:break-word;padding-right:20rpx">
  25. <view class="label">{{ $t("contract.d4") }}(USDT)</view>
  26. <view class="num fn-md color-light m-t-md">{{
  27. accountInfo.account_equity || "--"
  28. }}</view>
  29. </view>
  30. <view class="item" style="flex:1;overflow: auto;word-break:break-word;padding-right:20rpx">
  31. <view class="label">{{ $t("contract.c7") }}(USDT)</view>
  32. <view class="num fn-md color-light m-t-md">{{
  33. accountInfo.totalUnrealProfit
  34. }}</view>
  35. </view>
  36. <view class="item fn-right" style="overflow: auto;word-break:break-word">
  37. <view class="label">
  38. {{ $t("contract.d6") }}
  39. <van-icon class="fn-xs color-theme-1 m-l-xs" name="info-o" />
  40. </view>
  41. <view class="num fn-md color-light m-t-md">{{
  42. accountInfo.riskRate || "0%"
  43. }}</view>
  44. </view>
  45. </view>
  46. <view class="d-flex">
  47. <view class="w-6/12 p-l-md box-size">
  48. <view class="d-flex fn-center bg-form-panel-4 rounded-xs m-b-xs h-30">
  49. <v-picker :list="commissionTypes" v-model="form.type"
  50. class="flex-fill d-flex justify-center align-center border-r">
  51. {{ activeCommission.label }}
  52. <i class="iconfont">&#xe6e9;</i>
  53. </v-picker>
  54. <v-picker :list="lever_rage" v-model="form.lever_rate" @change="openNum"
  55. class="flex-fill d-flex justify-center align-center">
  56. {{ form.lever_rate }} X
  57. <i class="iconfont">&#xe6e9;</i>
  58. </v-picker>
  59. </view>
  60. <view class="m-b-xs">
  61. <view class="label fn-sm m-b-xs">{{ $t("contract.d1") }}</view>
  62. <!-- 限价 -->
  63. <van-stepper class="d-flex justify-between" :min="0" input-width="61%" step="0.01"
  64. :value="form.entrust_price" @change="form.entrust_price=$event.detail" v-if="form.type == 1" />
  65. <!-- 市价 -->
  66. <v-input v-if="form.type == 2" disabled :value="$t('contract.d7')"
  67. class="h-30 rounded-xs fn-center color-buy bg-form-panel-4"></v-input>
  68. </view>
  69. <view>
  70. <v-input :placeholder="$t('contract.d2')" v-model="form.amount"
  71. class="h-30 p-x-xs rounded-xs bg-form-panel-4">
  72. <template #right>
  73. <span> {{ $t("contract.d8") }} </span>
  74. </template>
  75. </v-input>
  76. </view>
  77. <view class="m-b-xs">
  78. <view class="m-t-xs">
  79. <view class="d-flex justify-between fn-sm">
  80. <text>0%</text>
  81. <text>100%</text>
  82. </view>
  83. <view class="d-flex justify-center">
  84. <bing-progress activeColor="#79D47C" barBorderRadius="20px" handleWidth="12px"
  85. handleHeight="12px" handleColor="#79D47C" borderRadius="20px" width="150px"
  86. :showInfo="false" strokeWidth="2px" noActiveColor="#484859" @dragging="sliderChange"
  87. :value="activeStep" />
  88. </view>
  89. </view>
  90. </view>
  91. <view class="d-flex fn-sm m-b-xs color-light justify-between">
  92. <span class="color-default">{{ $t("contract.d9") }}</span>
  93. <span>{{ margin }}</span>
  94. </view>
  95. <view class="m-b-xs">
  96. <v-button type="green" ref="btn1" @click="ifOpenPosition(1)" size="small" block
  97. class="w-max rounded-xs">
  98. {{ $t("contract.e0") }}
  99. </v-button>
  100. <view class="fn-sm m-t-xs">
  101. {{ $t("contract.e1") }} {{ maxNum }} {{ $t("contract.d8") }}
  102. </view>
  103. </view>
  104. <view class="m-b-xs">
  105. <v-button type="red" ref="btn2" @click="ifOpenPosition(2)" size="small" block
  106. class="w-max rounded-xs">
  107. {{ $t("contract.e2") }}
  108. </v-button>
  109. <view class="fn-sm m-t-xs">
  110. {{ $t("contract.e3") }} {{ maxNum }} {{ $t("contract.d8") }}
  111. </view>
  112. </view>
  113. <view class="m-b-xs border-t border-b p-y-xs d-flex justify-between align-center">
  114. <span class="fn-sm">{{ $t("contract.e4") }} {{ accountInfo.usable_balance }} USDT</span>
  115. <v-button size="mini" plain type="green" class="rounded-xs"
  116. @click="_router.push('/pages/transfer/index')">{{ $t("contract.e5") }}</v-button>
  117. </view>
  118. </view>
  119. <view class="w-6/12 p-r-md p-l-md box-size">
  120. <sell-and-buy @price="form.entrust_price = $event" :sellList="sellList" :buyList="buyList">
  121. <view class="fn-lg color-buy">{{ newPrice.price }}</view>
  122. </sell-and-buy>
  123. </view>
  124. </view>
  125. <view class="h-20"></view>
  126. <trade-list :tradeList="tradeList" />
  127. </view>
  128. </template>
  129. <script>
  130. import sellAndBuy from "@/pages/exchange/sell-and-buy";
  131. import tradeList from "@/pages/exchange/trade-list";
  132. import Contract from "@/api/contract";
  133. import math from "@/utils/class/math";
  134. import {
  135. mapState
  136. } from "vuex";
  137. import lodash from "lodash";
  138. import bingProgress from "@/components/bing-progress/bing-progress.vue";
  139. export default {
  140. name: "open-position",
  141. components: {
  142. sellAndBuy,
  143. tradeList,
  144. bingProgress,
  145. },
  146. props: {
  147. query: {
  148. default: () => {},
  149. type: Object,
  150. required: false,
  151. },
  152. collect: {
  153. default: () => [],
  154. type: Array,
  155. required: false,
  156. },
  157. isShow: {
  158. default: false,
  159. type: Boolean,
  160. required: false,
  161. },
  162. },
  163. data() {
  164. return {
  165. sellList: [],
  166. buyList: [],
  167. tradeList: [],
  168. show: false,
  169. accountInfo: {},
  170. // 杠杆倍数
  171. lever_rage: [],
  172. // 最多可开张数
  173. maxNum: 0,
  174. form: {
  175. type: 2,
  176. entrust_price: "",
  177. amount: "",
  178. lever_rate: "",
  179. },
  180. unSymbol: "",
  181. newPrice: {},
  182. dtime: null,
  183. unit_amount: 1,
  184. };
  185. },
  186. computed: {
  187. commissionTypes() {
  188. return [{
  189. label: this.$t("contract.f5"),
  190. value: 1
  191. },
  192. {
  193. label: this.$t("contract.f6"),
  194. value: 2
  195. },
  196. ];
  197. },
  198. ...mapState({
  199. ws: "ws1",
  200. }),
  201. symbol() {
  202. return this.query.symbol;
  203. },
  204. symbolLeft() {
  205. if (!this.symbol) return "";
  206. return this.symbol.split("/")[0];
  207. },
  208. // 是否为自选
  209. isCoolect() {
  210. return this.collect.map((item) => item.pair_name).includes(this.symbol);
  211. },
  212. // 选中的委托类型
  213. activeCommission() {
  214. return this.commissionTypes.find((item) => this.form.type == item.value);
  215. },
  216. // 保证金
  217. margin() {
  218. //市价委托
  219. return math.omitTo(
  220. (this.form.amount * this.newPrice.price) / this.form.lever_rate,
  221. 4
  222. );
  223. },
  224. // 所占百分比
  225. activeStep() {
  226. return (this.form.amount / this.maxNum) * 100;
  227. },
  228. isLogin() {
  229. return Boolean(uni.getStorageSync("token"));
  230. },
  231. },
  232. watch: {
  233. 'newPrice.price'(newValue, oldValue) {
  234. this.openNum()
  235. },
  236. symbol() {
  237. if (this.unSymbol) {
  238. this.unLink(this.unSymbol);
  239. }
  240. this.getMarketInfo();
  241. this.contractAccount();
  242. this.getSymbolDetail();
  243. },
  244. // 当前页面显示就订阅
  245. isShow(n) {
  246. if (n) {
  247. this.getMarketInfo();
  248. this.openNum();
  249. } else {
  250. if (this.unSymbol) {
  251. this.unLink(this.unSymbol);
  252. }
  253. }
  254. },
  255. },
  256. methods: {
  257. // 获取买卖盘
  258. getMarketInfo() {
  259. let data = {
  260. symbol: this.symbolLeft,
  261. };
  262. Contract.getMarketInfo(data, {
  263. loading: true
  264. }).then((res) => {
  265. this.sellList = res.data.swapSellList;
  266. this.buyList = res.data.swapBuyList;
  267. this.tradeList = res.data.swapTradeList.reverse();
  268. console.log(this.tradeList,'this.tradeList');
  269. console.log(this.tradeList[0].price,'this.tradeList')
  270. this.newPrice = this.tradeList[0] || {};
  271. console.log(this.newPrice,new Date(this.newPrice.ts))
  272. this.form.entrust_price = this.newPrice.price;
  273. this.linkSocket(data.symbol);
  274. });
  275. },
  276. // 获取合约账户信息
  277. contractAccount(boo) {
  278. if (!this.isLogin) return;
  279. if (!this.isShow) return;
  280. let data = {
  281. symbol: this.symbolLeft,
  282. };
  283. Contract.contractAccount(data, {
  284. loading: !boo,
  285. toast: false
  286. }).then((res) => {
  287. this.accountInfo = res.data;
  288. // this.newPrice.price = res.data.realtime_price
  289. console.log(res.data.realtime_price,'ddd')
  290. if (!boo) {
  291. this.form.lever_rate = res.data.lever_rate;
  292. if (this.form.lever_rate) {
  293. this.openNum();
  294. }
  295. }
  296. });
  297. },
  298. // 获取合约详情
  299. getSymbolDetail() {
  300. if (!this.symbolLeft) return;
  301. let data = {
  302. symbol: this.symbolLeft,
  303. };
  304. Contract.getSymbolDetail(data).then((res) => {
  305. this.unit_amount = res.data.unit_amount;
  306. // console.log(res.data)
  307. if (!this.lever_rage.length) {
  308. this.lever_rage = res.data.lever_rage.map((item) => ({
  309. label: item,
  310. value: item,
  311. }));
  312. }
  313. this.$emit("getSymbolDetail", res.data);
  314. if (!this.form.lever_rate) {
  315. this.form.lever_rate = this.lever_rage[0].value;
  316. this.openNum();
  317. }
  318. });
  319. },
  320. // 获取可开启张数
  321. openNum() {
  322. if (!this.isLogin) return;
  323. if (!this.newPrice.price) return
  324. let data = {
  325. symbol: this.symbolLeft, //ETH
  326. lever_rate: this.form.lever_rate, //10
  327. unit_price: this.newPrice.price
  328. };
  329. Contract.openNum(data, {
  330. toast: false
  331. }).then((res) => {
  332. this.maxNum = res.data;
  333. });
  334. },
  335. // 开仓提示
  336. ifOpenPosition(side) {
  337. // this.$dialog
  338. // .confirm({
  339. // title: this.$t("contract.c4"),
  340. // message: `${this.$t("contract.f7")}${
  341. // this.form.type == 1
  342. // ? this.form.entrust_price
  343. // : this.$t("contract.d7")
  344. // }${this.$t("contract.f8")}${this.form.lever_rate}${this.$t(
  345. // "contract.f9"
  346. // )}${this.form.amount}${this.$t("contract.d8")}${
  347. // side == 1 ? this.$t("contract.g0") : this.$t("contract.g1")
  348. // }?`,
  349. // // leftText:"a",
  350. // // rightText:"b",
  351. // // cancelText:"s", // 取消按钮的文字
  352. // // confirmText:"b", // 确认按钮文字
  353. // // okText:"a",
  354. // // cancelText:"b"
  355. // })
  356. // .then(() => {
  357. this.openPosition(side);
  358. // });
  359. },
  360. // 开仓
  361. openPosition(side) {
  362. if (!this.form.amount) {
  363. return this.$toast(this.$t('exchange.d9'))
  364. }
  365. let data = {
  366. //委托价格
  367. side,
  368. symbol: this.symbolLeft,
  369. ...this.form,
  370. };
  371. let btn;
  372. if (side == 1) {
  373. //开多
  374. btn = this.$refs.btn1;
  375. } else {
  376. // 开空
  377. btn = this.$refs.btn2;
  378. }
  379. Contract.openPosition(data, {
  380. btn
  381. }).then(() => {
  382. this.contractAccount();
  383. this.$toast(this.$t("contract.g2"));
  384. });
  385. },
  386. sliderChange($ev) {
  387. this.form.amount = Math.round((this.maxNum * $ev.value) / 100);
  388. },
  389. // 链接socket
  390. linkSocket(symbol) {
  391. this.unSymbol = symbol;
  392. // 订阅买线
  393. this.ws.send({
  394. cmd: "sub",
  395. msg: `swapBuyList_${symbol}`,
  396. });
  397. // 订阅卖线
  398. this.ws.send({
  399. cmd: "sub",
  400. msg: `swapSellList_${symbol}`,
  401. });
  402. // 订阅成交
  403. this.ws.send({
  404. cmd: "sub",
  405. msg: `swapTradeList_${symbol}`,
  406. });
  407. },
  408. // 取消订阅
  409. unLink(symbol) {
  410. // 取消买线
  411. this.ws.send({
  412. cmd: "unsub",
  413. msg: `swapBuyList_${symbol}`,
  414. });
  415. // 取消卖线
  416. this.ws.send({
  417. cmd: "unsub",
  418. msg: `swapSellList_${symbol}`,
  419. });
  420. // 取消成交
  421. this.ws.send({
  422. cmd: "unsub",
  423. msg: `swapTradeList_${symbol}`,
  424. });
  425. },
  426. socketMessage() {
  427. // 节流防抖
  428. let buyFun = lodash.throttle((data) => {
  429. this.buyList = data;
  430. }, 500);
  431. let sellFun = lodash.throttle((data) => {
  432. this.sellList = data.sort((a, b) => b.price - a.price);
  433. }, 500);
  434. this.ws.on("message", (res) => {
  435. if (!this.isShow) return;
  436. let symbol = this.symbolLeft;
  437. let {
  438. data,
  439. sub
  440. } = res;
  441. switch (sub) {
  442. case `swapBuyList_${symbol}`:
  443. buyFun(data);
  444. break;
  445. case `swapSellList_${symbol}`:
  446. sellFun(data);
  447. break;
  448. case `swapTradeList_${symbol}`:
  449. this.tradeList.unshift(data);
  450. this.tradeList.pop();
  451. this.newPrice = data;
  452. console.log(data,'data')
  453. break;
  454. }
  455. });
  456. },
  457. },
  458. mounted() {
  459. if (this.symbol) {
  460. this.getMarketInfo();
  461. this.contractAccount();
  462. this.getSymbolDetail();
  463. }
  464. this.socketMessage();
  465. this.dtime = setInterval(() => {
  466. this.contractAccount(true);
  467. // this.getMarketInfo();
  468. }, 3000);
  469. },
  470. destroyed() {
  471. clearInterval(this.dtime);
  472. this.unLink(this.unSymbol);
  473. },
  474. };
  475. </script>
  476. <style lang="scss" scoped>
  477. ::v-deep .van-step--horizontal {
  478. .van-step__circle-container {
  479. background-color: transparent;
  480. .van-icon-checked {
  481. width: 14px;
  482. height: 14px;
  483. position: relative;
  484. background: rgba($green, 0.7);
  485. font-size: 0;
  486. display: block;
  487. border-radius: 20px;
  488. bottom: 9px;
  489. &::after {
  490. content: "";
  491. display: block;
  492. font-size: 0;
  493. width: 8px;
  494. height: 8px;
  495. position: absolute;
  496. left: 50%;
  497. top: 50%;
  498. transform: translate(-50%, -50%);
  499. background: $green;
  500. border-radius: 20px;
  501. }
  502. }
  503. }
  504. .van-step__line {
  505. bottom: 8px;
  506. }
  507. }
  508. /deep/ .van-stepper {
  509. display: flex;
  510. justify-content: space-between;
  511. .minus-class,
  512. .input-class,
  513. .plus-class {
  514. background: $form-panel-4;
  515. }
  516. }
  517. </style>