trade-position.vue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <template>
  2. <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
  3. <cwg-header :title="t('Ib.Report.Tit4')" />
  4. <view class="info-card">
  5. <cwg-complex-search :fields="filterFields" v-model="searchParams" @search="handleSearch"
  6. @reset="handleReset" />
  7. <cwg-tabel ref="tableRef" :columns="columns" :immediate="false" :mobilePrimaryFields="mobilePrimaryFields"
  8. :queryParams="search" :api="listApi" :show-operation="false">
  9. <template #symbol="{ row }">
  10. <view class="symbol-cell">
  11. <view class="pair">{{ getSymbolParts(row.symbol)[0] }}/{{ getSymbolParts(row.symbol)[1]
  12. }}</view>
  13. <view class="desc">{{ row.openPrice }}
  14. <text :class="getCmdColorClass(row.cmdName)">{{ formatCmdName(row.cmdName) }}{{ row.volume
  15. }}{{ t('Label.Lot') }}</text>
  16. </view>
  17. </view>
  18. </template>
  19. <template #profit="{ row }">
  20. <view class="symbol-cell">
  21. <text :class="getProfitColorClass(row.profit)">{{ row.profit || 0 }}</text>
  22. </view>
  23. </template>
  24. </cwg-tabel>
  25. </view>
  26. </cwg-page-wrapper>
  27. </template>
  28. <script setup lang="ts">
  29. import { computed, ref, nextTick, reactive, watch } from 'vue';
  30. import { useI18n } from 'vue-i18n';
  31. const { t, locale } = useI18n();
  32. import { onLoad } from '@dcloudio/uni-app'
  33. import { customApi } from '@/service/custom';
  34. import { useAccountOptions } from '@/composables/useAccountOptions'
  35. const { loginOptions, isLoaded, isSuccess } = useAccountOptions()
  36. const search = reactive({
  37. login: null
  38. })
  39. const typeMap = computed(() => ([
  40. { value: null, text: t('Custom.PaymentHistory.All') },
  41. { value: 1, text: t('Custom.PaymentHistory.Deposit') },
  42. { value: 2, text: t('Custom.PaymentHistory.Withdrawals') }
  43. ]));
  44. const orderStatusMap = computed(() => ([
  45. { value: null, text: t('Custom.PaymentHistory.All') },
  46. { value: 1, text: t('State.ToBeProcessed') },
  47. { value: 2, text: t('State.Completed') },
  48. { value: 3, text: t('State.InTheProcessing') },
  49. { value: 4, text: t('State.Refused') },
  50. { value: 5, text: t('State.expireTime') },
  51. { value: 6, text: t('State.Cancelled') },
  52. ]));
  53. // 表格列配置
  54. const columns = computed(() => [
  55. {
  56. prop: 'symbol',
  57. label: t('Label.Varieties'), // 交易品种
  58. align: 'left'
  59. },
  60. {
  61. prop: 'cmdName',
  62. label: t('Label.Type'), // 类型
  63. align: 'left'
  64. },
  65. {
  66. prop: 'openTime',
  67. label: t('Label.OpenTime'), // 开仓时间(协调世界时)
  68. align: 'left'
  69. },
  70. {
  71. prop: 'volume',
  72. label: t('Label.Volume'), // 手
  73. formatter: ({ row }) => `${row.volume || 0} ${t('Label.Lot')}`,
  74. align: 'right'
  75. },
  76. {
  77. prop: 'openPrice',
  78. label: t('Label.OpenPrice'), // 开仓价
  79. align: 'right'
  80. },
  81. {
  82. prop: 'tp',
  83. label: t('Label.EP'), // 止盈
  84. align: 'right'
  85. },
  86. {
  87. prop: 'sl',
  88. label: t('Label.EL'), // 止损
  89. align: 'right'
  90. },
  91. {
  92. prop: 'profit',
  93. label: t('Label.ProfitLoss') + '(USD)', // 利润, USD
  94. slot: 'profit',
  95. align: 'right'
  96. },
  97. {
  98. prop: 'comment',
  99. label: t('Label.Note'),
  100. align: 'right',
  101. isTabel: false
  102. },
  103. {
  104. prop: 'more',
  105. type: 'more',
  106. width: 20,
  107. align: 'right'
  108. },
  109. ])
  110. const mobilePrimaryFields = computed(() => [
  111. {
  112. prop: 'symbol',
  113. label: t('Label.Varieties'), // 交易品种
  114. align: 'left',
  115. slot: 'symbol'
  116. },
  117. {
  118. prop: 'profit',
  119. label: t('Label.ProfitLoss') + '(USD)', // 利润, USD
  120. slot: 'profit',
  121. align: 'right'
  122. },
  123. {
  124. prop: 'more',
  125. type: 'more',
  126. width: 20,
  127. align: 'right'
  128. },
  129. ])
  130. // 动态传入筛选字段配置
  131. const filterFields = computed(() => [
  132. isLoaded.value && isSuccess.value && { key: 'login', type: 'select', label: t('Custom.PaymentHistory.TradingAccount'), placeholder: t('placeholder.login'), options: loginOptions || [], defaultValue: undefined },
  133. { key: 'date', label: t('placeholder.Start') + ' - ' + t('placeholder.End'), type: 'daterange' }
  134. ])
  135. const searchParams = ref({})
  136. const tableRef = ref(null)
  137. const handleSearch = (params) => {
  138. Object.assign(search, params)
  139. search.login = params.login && Number(params.login)
  140. search.platform = loginOptions.find(item => item.value == params.login)?.platform || ''
  141. if (!search.platform) return
  142. nextTick(() => {
  143. tableRef.value.refreshTable()
  144. })
  145. }
  146. const handleReset = (params) => {
  147. Object.assign(search, params)
  148. search.platform = loginOptions.find(item => item.value == params.login)?.platform || ''
  149. if (!search.platform) return
  150. nextTick(() => {
  151. tableRef.value.refreshTable()
  152. })
  153. }
  154. const listApi = ref(null)
  155. listApi.value = customApi.tradePosition
  156. const getSymbolParts = (sym: string) => {
  157. if (!sym) return ['', '']
  158. const s = String(sym).toUpperCase()
  159. if (s.includes('/')) {
  160. const [base, quote] = s.split('/')
  161. return [base, quote]
  162. }
  163. const base = s.slice(0, 3)
  164. const quote = s.slice(3)
  165. return [base, quote]
  166. }
  167. const formatCmdName = (cmd: string) => {
  168. const v = String(cmd || '').toLowerCase()
  169. if (v.includes('sell')) return '卖出'
  170. if (v.includes('buy')) return '买入'
  171. return cmd || ''
  172. }
  173. const getCmdColorClass = (cmd: string) => {
  174. const v = String(cmd || '').toLowerCase()
  175. if (v.includes('sell')) return 'is-sell'
  176. if (v.includes('buy')) return 'is-buy'
  177. return ''
  178. }
  179. const getProfitColorClass = (profit: any) => {
  180. const n = Number(profit)
  181. if (!Number.isFinite(n) || n === 0) return ''
  182. return n > 0 ? 'is-profit' : 'is-loss'
  183. }
  184. onLoad(async (e) => {
  185. let targetLogin: number | null = null
  186. if (e.login) {
  187. targetLogin = Number(e.login)
  188. }
  189. await new Promise<void>(resolve => {
  190. const stopWatch = watch([isLoaded, isSuccess], ([loaded, success]) => {
  191. if (loaded && success) {
  192. stopWatch()
  193. resolve()
  194. }
  195. })
  196. })
  197. nextTick(() => {
  198. searchParams.value.login = targetLogin ?? loginOptions?.value?.[0]?.value ?? null
  199. handleSearch(searchParams.value)
  200. })
  201. })
  202. </script>
  203. <style scoped lang="scss">
  204. @import "@/uni.scss";
  205. .avatar {
  206. width: px2rpx(60);
  207. height: px2rpx(60);
  208. border-radius: 4px;
  209. }
  210. .content-title {
  211. display: flex;
  212. justify-content: space-between;
  213. align-items: center;
  214. font-size: px2rpx(20);
  215. font-weight: 500;
  216. .content-title-btns {
  217. margin: px2rpx(8) 0;
  218. display: flex;
  219. align-items: center;
  220. justify-content: center;
  221. gap: px2rpx(12);
  222. .btn-primary {
  223. min-width: px2rpx(120);
  224. background-color: var(--color-error);
  225. color: white;
  226. padding: 0 px2rpx(12);
  227. border: none;
  228. font-size: px2rpx(14);
  229. text-align: center;
  230. cursor: pointer;
  231. display: flex;
  232. align-items: center;
  233. justify-content: center;
  234. gap: px2rpx(8);
  235. }
  236. .btn-primary:active {
  237. background-color: #cf1322;
  238. ;
  239. }
  240. }
  241. }
  242. .operation-btn {
  243. :deep(span) {
  244. display: flex;
  245. align-items: center;
  246. justify-content: center;
  247. gap: px2rpx(4);
  248. cursor: pointer;
  249. background-color: var(--color-slate-150);
  250. padding: px2rpx(8) 0;
  251. }
  252. }
  253. .operation-btn.disabled {
  254. cursor: not-allowed;
  255. opacity: 0.5;
  256. }
  257. .symbol-cell {
  258. display: inline-flex;
  259. align-items: flex-start;
  260. gap: 0.25rem;
  261. flex-direction: column;
  262. .pair {
  263. font-weight: 600;
  264. color: var(--color-slate-900);
  265. }
  266. .desc {
  267. color: var(--color-slate-600);
  268. }
  269. }
  270. .is-sell,
  271. .is-loss {
  272. color: #eb483f;
  273. }
  274. .is-buy,
  275. .is-profit {
  276. color: #46cd7c;
  277. }
  278. .search-bar {
  279. display: flex;
  280. align-items: center;
  281. justify-content: flex-start;
  282. flex-wrap: wrap;
  283. gap: px2rpx(16);
  284. margin: px2rpx(16) 0;
  285. .cwg-combox,
  286. .uni-easyinput,
  287. .uni-date {
  288. width: px2rpx(240) !important;
  289. flex: none;
  290. }
  291. }
  292. </style>