LimitOrderModal.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. <template>
  2. <Teleport to="body">
  3. <div v-if="visible" class="modal-overlay" @click="close">
  4. <div class="modal-content" @click.stop>
  5. <div class="drag-handle"></div>
  6. <div class="tabs-header">
  7. <div
  8. v-for="(tab, index) in tabs"
  9. :key="index"
  10. class="tab-item"
  11. :class="{ active: currentTab === index }"
  12. @click="currentTab = index"
  13. >
  14. {{ tab }}
  15. <div v-if="currentTab === index" class="active-line"></div>
  16. </div>
  17. </div>
  18. <div class="tab-body">
  19. <div v-if="currentContent.topDesc" class="top-description">
  20. {{ currentContent.topDesc }}
  21. </div>
  22. <div class="control-row">
  23. <span class="label">图示说明</span>
  24. <div v-if="currentTab === 1" class="checkbox-group">
  25. <div class="checkbox-item" @click="setDirection('long')">
  26. <div class="custom-checkbox" :class="{ checked: direction === 'long' }">
  27. <span v-if="direction === 'long'">✓</span>
  28. </div>
  29. <span>做多</span>
  30. </div>
  31. <div class="checkbox-item" @click="setDirection('short')">
  32. <div class="custom-checkbox" :class="{ checked: direction === 'short' }">
  33. <span v-if="direction === 'short'">✓</span>
  34. </div>
  35. <span>做空</span>
  36. </div>
  37. </div>
  38. </div>
  39. <div class="image-container">
  40. <img :src="currentContent.image" alt="" />
  41. </div>
  42. <div class="description-text fs12 fc999999">
  43. <h3 v-if="currentContent.title">{{ currentContent.title }}</h3>
  44. <div class="desc-content" v-html="currentContent.desc"></div>
  45. </div>
  46. </div>
  47. <div class="footer">
  48. <button class="confirm-btn" @click="close">确认</button>
  49. </div>
  50. </div>
  51. </div>
  52. </Teleport>
  53. </template>
  54. <script setup>
  55. import { ref, computed } from 'vue';
  56. const props = defineProps({
  57. visible: { type: Boolean, default: false }
  58. });
  59. const emit = defineEmits(['update:visible']);
  60. // ================= 状态管理 =================
  61. const tabs = ['市价', '限价', '限价止盈止损', '市价止盈止损'];
  62. const currentTab = ref(0); // 默认为 0 (市价)
  63. const direction = ref('short'); // 仅用于 Tab 1 (限价) 的做多/做空
  64. // ================= 图片资源配置 =================
  65. // 请将此处路径替换为你项目中的实际图片路径
  66. const imgMap = {
  67. market: require('../../../assets/icon/bitcoin/shijia.svg'), // 市价
  68. limitLong: require('../../../assets/icon/bitcoin/xianjia.svg'), // 限价-做多
  69. limitShort: require('../../../assets/icon/bitcoin/xianjiak.svg'), // 限价-做空
  70. limitTpSl: require('../../../assets/icon/bitcoin/xianjiazyzs.svg'), // 限价止盈止损
  71. marketTpSl: require('../../../assets/icon/bitcoin/shijiazyzs.svg'), // 市价止盈止损
  72. };
  73. // ================= 核心内容数据 =================
  74. // 使用 computed 动态生成当前 Tab 应显示的内容
  75. const currentContent = computed(() => {
  76. switch (currentTab.value) {
  77. case 0: // === 市价 ===
  78. return {
  79. topDesc: null,
  80. image: imgMap.market,
  81. title: '市价委托是指按照目前市场最优价格,进行快速买卖。',
  82. desc: '当前价格2400,此时下单一笔市价单,它将会根据对手价直接成交,但成交均价可能不等于2400。'
  83. };
  84. case 1: // === 限价 (带做多/做空逻辑) ===
  85. const isLong = direction.value === 'long';
  86. return {
  87. topDesc: null,
  88. image: isLong ? imgMap.limitLong : imgMap.limitShort,
  89. title: '限价委托是指以特定或更优价格进行买卖,限价单不能保证一定成交',
  90. desc: isLong
  91. ? '当前市价(A)下跌至委托限价(C)或以下,委托单将会自动执\n' +
  92. '行:如果买单委托限价(B)高于或等于当前市价,委托单将会立\n' +
  93. '即成交,因此当需要限价买入时,委托价格应低于当前价格。'
  94. : '当价格(A)上涨至订单的限价(B)或以上,订单将会自动执行\n' +
  95. '如果卖单的限价低于或等于当前价格,卖单可能会立即成交。因此,\n' +
  96. '当需要限价卖出时,委托价格应高于当前价格。'
  97. };
  98. case 2: // === 限价止盈止损 ===
  99. return {
  100. topDesc: '限价止盈止损委托需要同时设置一个触发价格和一个委托价格。当市场最新价到达触发价时,按预先设置的委托价格和数量自动下单。',
  101. image: imgMap.limitTpSl,
  102. title: null,
  103. // 使用 HTML 字符串来模拟截图中的红色文字高亮
  104. desc: `
  105. <p>当前价格为2,400 (A)。限价止盈止损单的触发价格可以设置为3,000 (B),高于当前价格;也可以设置为1,500 (C),低于当前价格。一旦价格上涨至3,000 (B),或下跌到1,500 (C),达到触发价,限价委托单将自动激活生效。</p>
  106. <p style="margin-top:8px; font-weight:500;">备注:</p>
  107. <ol style="padding-left: 15px; margin: 5px 0;">
  108. <li>1)买入和卖出订单的限价都可以高于或低于触发价。比如,<span style="color:#dc3545">触发价B</span>可以和价格略低的<span style="color:#dc3545">委托价B1</span>组成限价止盈止损单,也可以和价格略高的<span style="color:#dc3545">委托价B2</span>组成限价止盈止损单;</li>
  109. <li>2)在没有达到触发价时,限价单不会生效,即使价格达到限价的时间早于触发价也是如此。</li>
  110. <li>3)当达到止损价格时,只能表明限价单将会自动激活并提交至市场,并不表示限价单将会立即成交。限价委托单被激活后,只有满足其成交条件才会最终执行。</li>
  111. </ol>
  112. `
  113. };
  114. case 3: // === 市价止盈止损 ===
  115. return {
  116. topDesc: '当触达设定的价格时,止损市价委托会自动触发。交易者需要设定一个价格去触发该类型委托。该类委托可以应用于设置市价止损和市价止盈委托。',
  117. image: imgMap.marketTpSl,
  118. title: null,
  119. desc: '当前价格为2,400 (A)。市价止盈止损订单的触发价格可以设置为3,000 (B),高于当前价格;也可以设置为1,500(C),低于当前价格。一旦价格上涨至3,000(B),或下跌到1,500 (C),达到触发价,市价单将自动触发生效。'
  120. };
  121. default:
  122. return {};
  123. }
  124. });
  125. const setDirection = (val) => {
  126. direction.value = val;
  127. };
  128. const close = () => {
  129. emit('update:visible', false);
  130. };
  131. </script>
  132. <style scoped>
  133. /* 保持之前的遮罩和弹窗基础样式 */
  134. .modal-overlay {
  135. position: fixed;
  136. top: 0; left: 0; width: 100vw; height: 100vh;
  137. background: rgba(0, 0, 0, 0.5);
  138. display: flex; align-items: flex-end; justify-content: center;
  139. z-index: 1000;
  140. }
  141. .modal-content {
  142. background: white;
  143. width: 100%;
  144. max-width: 500px;
  145. border-radius: 16px 16px 0 0;
  146. padding: 10px 20px 30px;
  147. box-sizing: border-box;
  148. max-height: 90vh;
  149. overflow-y: auto; /* 内容过长时允许滚动 */
  150. animation: slideUp 0.3s ease-out;
  151. }
  152. .drag-handle {
  153. width: 40px; height: 4px; background: #e0e0e0;
  154. border-radius: 2px; margin: 5px auto 15px;
  155. }
  156. /* Tabs Header */
  157. .tabs-header {
  158. display: flex;
  159. justify-content: space-between;
  160. border-bottom: 1px solid #f0f0f0;
  161. margin-bottom: 20px;
  162. }
  163. .tab-item {
  164. padding: 10px 0;
  165. font-size: 14px; /* 字体稍微调小以容纳4个Tab */
  166. color: #999;
  167. cursor: pointer;
  168. position: relative;
  169. font-weight: 500;
  170. white-space: nowrap;
  171. }
  172. .tab-item.active {
  173. color: #000;
  174. font-weight: bold;
  175. }
  176. .active-line {
  177. position: absolute; bottom: -1px; left: 50%;
  178. transform: translateX(-50%);
  179. width: 20px; height: 3px; background: #000; border-radius: 2px;
  180. }
  181. /* Top Description (用于止盈止损页面顶部) */
  182. .top-description {
  183. font-size: 13px;
  184. color: #333;
  185. line-height: 1.6;
  186. margin-bottom: 20px;
  187. text-align: justify;
  188. }
  189. /* Controls */
  190. .control-row {
  191. display: flex;
  192. justify-content: space-between;
  193. align-items: center;
  194. margin-bottom: 15px;
  195. }
  196. .label {
  197. font-size: 14px; font-weight: bold; color: #333;
  198. }
  199. .checkbox-group {
  200. display: flex; gap: 20px;
  201. }
  202. .checkbox-item {
  203. display: flex; align-items: center; cursor: pointer;
  204. font-size: 14px; color: #666;
  205. }
  206. .custom-checkbox {
  207. width: 16px; height: 16px; border: 1px solid #ccc;
  208. border-radius: 4px; margin-right: 6px;
  209. display: flex; align-items: center; justify-content: center;
  210. color: white; font-size: 12px;
  211. }
  212. .custom-checkbox.checked {
  213. background-color: #dc3545; border-color: #dc3545;
  214. }
  215. /* Image */
  216. .image-container {
  217. width: 100%;
  218. background: #fff;
  219. display: flex; justify-content: center;
  220. margin-bottom: 20px;
  221. }
  222. .image-container img {
  223. max-width: 100%; height: auto; object-fit: contain;
  224. }
  225. /* Description Text */
  226. .description-text h3 {
  227. font-size: 14px; color: #333; margin-bottom: 10px; font-weight: bold;
  228. }
  229. /* 深度选择器用于控制 v-html 内部样式 */
  230. .desc-content :deep(p) {
  231. font-size: 12px; color: #888; line-height: 1.6; text-align: justify; margin-bottom: 8px;
  232. }
  233. .desc-content :deep(ol) {
  234. font-size: 12px; color: #888; line-height: 1.6;
  235. }
  236. .desc-content :deep(li) {
  237. margin-bottom: 4px;
  238. }
  239. /* Footer */
  240. .footer { margin-top: 20px; }
  241. .confirm-btn {
  242. width: 100%; padding: 12px;
  243. background-color: #dc3545; color: white;
  244. border: none; border-radius: 24px;
  245. font-size: 16px; font-weight: bold; cursor: pointer;
  246. }
  247. @keyframes slideUp {
  248. from { transform: translateY(100%); }
  249. to { transform: translateY(0); }
  250. }
  251. </style>