trade-history.vue 8.2 KB

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