trade-history.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  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. let startTime = search?.date?.[0]?.replace(/-/g, '/')?.split(' ')[0] || ''
  136. let start = new Date(startTime).getTime();
  137. let thresholdDate = new Date("2020/10/01").getTime();
  138. let shouldUseSharding =
  139. (start >= thresholdDate && search.platform == "MT4") ||
  140. search.platform == "MT5";
  141. console.log(search,startTime,shouldUseSharding)
  142. listApi.value = shouldUseSharding? customApi.tradeShardingHistory: customApi.tradeHistory
  143. if (!search.platform) return
  144. nextTick(() => {
  145. tableRef.value.refreshTable()
  146. })
  147. }
  148. const handleReset = (params) => {
  149. Object.assign(search, params)
  150. search.platform = loginOptions.find(item => item.value == params.login)?.platform || ''
  151. if (!search.platform) return
  152. nextTick(() => {
  153. tableRef.value.refreshTable()
  154. })
  155. }
  156. const listApi = ref(null)
  157. listApi.value = customApi.tradeShardingHistory
  158. const getSymbolParts = (sym: string) => {
  159. if (!sym) return ['', '']
  160. const s = String(sym).toUpperCase()
  161. if (s.includes('/')) {
  162. const [base, quote] = s.split('/')
  163. return [base, quote]
  164. }
  165. const base = s.slice(0, 3)
  166. const quote = s.slice(3)
  167. return [base, quote]
  168. }
  169. const formatCmdName = (cmd: string) => {
  170. const v = String(cmd || '').toLowerCase()
  171. if (v.includes('sell')) return '卖出'
  172. if (v.includes('buy')) return '买入'
  173. return cmd || ''
  174. }
  175. const getCmdColorClass = (cmd: string) => {
  176. const v = String(cmd || '').toLowerCase()
  177. if (v.includes('sell')) return 'is-sell'
  178. if (v.includes('buy')) return 'is-buy'
  179. return ''
  180. }
  181. const getProfitColorClass = (profit: any) => {
  182. const n = Number(profit)
  183. if (!Number.isFinite(n) || n === 0) return ''
  184. return n > 0 ? 'is-profit' : 'is-loss'
  185. }
  186. onLoad(async (e) => {
  187. let targetLogin: number | null = null
  188. // 接收跳转参数
  189. if (e.login) {
  190. targetLogin = Number(e.login)
  191. console.log('onLoad 拿到跳转login:', targetLogin)
  192. }
  193. // 等待账号列表请求完成
  194. await new Promise<void>(resolve => {
  195. const stopWatch = watch([isLoaded, isSuccess], ([loaded, success]) => {
  196. if (loaded && success) {
  197. stopWatch()
  198. resolve()
  199. }
  200. })
  201. })
  202. // 此时 loginOptions 一定有值了
  203. nextTick(() => {
  204. // 优先跳转传参,没有就默认第一个
  205. searchParams.value.login = targetLogin ?? loginOptions[0]?.value ?? null
  206. console.log(searchParams.value,222,loginOptions?.value?.[0]?.value,loginOptions[0]?.value);
  207. handleSearch(searchParams.value)
  208. })
  209. })
  210. </script>
  211. <style scoped lang="scss">
  212. @import "@/uni.scss";
  213. .avatar {
  214. width: px2rpx(60);
  215. height: px2rpx(60);
  216. border-radius: 4px;
  217. }
  218. .content-title {
  219. display: flex;
  220. justify-content: space-between;
  221. align-items: center;
  222. font-size: px2rpx(20);
  223. font-weight: 500;
  224. .content-title-btns {
  225. margin: px2rpx(8) 0;
  226. display: flex;
  227. align-items: center;
  228. justify-content: center;
  229. gap: px2rpx(12);
  230. .btn-primary {
  231. min-width: px2rpx(120);
  232. background-color: var(--color-error);
  233. color: white;
  234. padding: 0 px2rpx(12);
  235. border: none;
  236. font-size: px2rpx(14);
  237. text-align: center;
  238. cursor: pointer;
  239. display: flex;
  240. align-items: center;
  241. justify-content: center;
  242. gap: px2rpx(8);
  243. }
  244. .btn-primary:active {
  245. background-color: #cf1322;
  246. ;
  247. }
  248. }
  249. }
  250. .operation-btn {
  251. :deep(span) {
  252. display: flex;
  253. align-items: center;
  254. justify-content: center;
  255. gap: px2rpx(4);
  256. cursor: pointer;
  257. background-color: var(--color-slate-150);
  258. padding: px2rpx(8) 0;
  259. }
  260. }
  261. .operation-btn.disabled {
  262. cursor: not-allowed;
  263. opacity: 0.5;
  264. }
  265. .symbol-cell {
  266. display: inline-flex;
  267. align-items: flex-start;
  268. gap: 0.25rem;
  269. flex-direction: column;
  270. .pair {
  271. font-weight: 600;
  272. color: var(--color-slate-900);
  273. }
  274. .desc {
  275. color: var(--color-slate-600);
  276. }
  277. }
  278. .is-sell,
  279. .is-loss {
  280. color: #eb483f;
  281. }
  282. .is-buy,
  283. .is-profit {
  284. color: #46cd7c;
  285. }
  286. .search-bar {
  287. display: flex;
  288. align-items: center;
  289. justify-content: flex-start;
  290. flex-wrap: wrap;
  291. gap: px2rpx(16);
  292. margin: px2rpx(16) 0;
  293. .cwg-combox,
  294. .uni-easyinput,
  295. .uni-date {
  296. width: px2rpx(240) !important;
  297. flex: none;
  298. }
  299. }
  300. </style>