trade-position.vue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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" :mobilePrimaryFields="mobilePrimaryFields"
  8. :queryParams="search" :api="listApi" :show-operation="false" :showPagination="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 } from 'vue';
  30. import { useI18n } from 'vue-i18n';
  31. const { t, locale } = useI18n();
  32. import { customApi } from '@/service/custom';
  33. const search = ref(
  34. {
  35. "reportType": 3,
  36. "agentId": null,
  37. "groupType": null,
  38. "login": "",
  39. "cId": "",
  40. "customName": "",
  41. "ibNo": "",
  42. "symbol": "",
  43. "cmdName": "",
  44. "platform": "MT4",
  45. "salesNo": "",
  46. "salesName": "",
  47. "dwType": null,
  48. "customType": 0,
  49. "rankingType": 1,
  50. "loginTypes": [],
  51. "isShort": 0,
  52. "detail_type": 4,
  53. "date": [
  54. "1970-01-01",
  55. "2026-03-27"
  56. ],
  57. "startDate": "1970-01-01",
  58. "endDate": "2026-03-27",
  59. "orderColumn": null,
  60. "orderType": null,
  61. "page": {
  62. "current": 1,
  63. "row": 10
  64. }
  65. }
  66. )
  67. const typeMap = computed(() => ([
  68. { value: null, text: t('Custom.PaymentHistory.All') },
  69. { value: 1, text: t('Custom.PaymentHistory.Deposit') },
  70. { value: 2, text: t('Custom.PaymentHistory.Withdrawals') }
  71. ]));
  72. const orderStatusMap = computed(() => ([
  73. { value: null, text: t('Custom.PaymentHistory.All') },
  74. { value: 1, text: t('State.ToBeProcessed') },
  75. { value: 2, text: t('State.Completed') },
  76. { value: 3, text: t('State.InTheProcessing') },
  77. { value: 4, text: t('State.Refused') },
  78. { value: 5, text: t('State.expireTime') },
  79. { value: 6, text: t('State.Cancelled') },
  80. ]));
  81. // 表格列配置
  82. const columns = computed(() => [
  83. {
  84. prop: 'symbol',
  85. label: t('Label.Varieties'), // 交易品种
  86. align: 'left'
  87. },
  88. {
  89. prop: 'cmdName',
  90. label: t('Label.Type'), // 类型
  91. align: 'left'
  92. },
  93. {
  94. prop: 'openTime',
  95. label: t('Label.OpenTime'), // 开仓时间(协调世界时)
  96. align: 'left'
  97. },
  98. {
  99. prop: 'volume',
  100. label: t('Label.Volume'), // 手
  101. formatter: ({ row }) => `${row.volume || 0} ${t('Label.Lot')}`,
  102. align: 'right'
  103. },
  104. {
  105. prop: 'openPrice',
  106. label: t('Label.OpenPrice'), // 开仓价
  107. align: 'right'
  108. },
  109. {
  110. prop: 'tp',
  111. label: t('Label.EP'), // 止盈
  112. align: 'right'
  113. },
  114. {
  115. prop: 'sl',
  116. label: t('Label.EL'), // 止损
  117. align: 'right'
  118. },
  119. {
  120. prop: 'profit',
  121. label: t('Label.ProfitLoss') + '(USD)', // 利润, USD
  122. slot: 'profit',
  123. align: 'right'
  124. },
  125. {
  126. prop: 'comment',
  127. label: t('Label.Note'),
  128. align: 'right',
  129. isTabel: false
  130. },
  131. {
  132. prop: 'more',
  133. type: 'more',
  134. width: 20,
  135. align: 'right'
  136. },
  137. ])
  138. const mobilePrimaryFields = computed(() => [
  139. {
  140. prop: 'symbol',
  141. label: t('Label.Varieties'), // 交易品种
  142. align: 'left',
  143. slot: 'symbol'
  144. },
  145. {
  146. prop: 'profit',
  147. label: t('Label.ProfitLoss') + '(USD)', // 利润, USD
  148. slot: 'profit',
  149. align: 'right'
  150. },
  151. {
  152. prop: 'more',
  153. type: 'more',
  154. width: 20,
  155. align: 'right'
  156. },
  157. ])
  158. // 动态传入筛选字段配置
  159. const filterFields = computed(() => [
  160. { key: 'type', type: 'select', label: t('Custom.PaymentHistory.payType'), placeholder: t('placeholder.choose'), options: typeMap.value, defaultValue: 1 },
  161. { key: 'login', type: 'input', label: t('Custom.PaymentHistory.TradingAccount'), placeholder: t('placeholder.login'), defaultValue: '' },
  162. {
  163. key: 'orderStatus', type: 'select', label: t('Custom.PaymentHistory.Status'), placeholder: t('placeholder.choose'), options: orderStatusMap.value, defaultValue: null
  164. },
  165. { key: 'date', label: t('placeholder.Start') + ' - ' + t('placeholder.End'), type: 'daterange' }
  166. ])
  167. const searchParams = ref({})
  168. const tableRef = ref(null)
  169. const handleSearch = (params) => {
  170. search.value = params
  171. nextTick(() => {
  172. tableRef.value.refreshTable()
  173. })
  174. }
  175. const handleReset = (emptyParams) => {
  176. search.value = emptyParams
  177. nextTick(() => {
  178. tableRef.value.refreshTable()
  179. })
  180. }
  181. const listApi = ref(null)
  182. listApi.value = customApi.tradePosition
  183. const getSymbolParts = (sym: string) => {
  184. if (!sym) return ['', '']
  185. const s = String(sym).toUpperCase()
  186. if (s.includes('/')) {
  187. const [base, quote] = s.split('/')
  188. return [base, quote]
  189. }
  190. const base = s.slice(0, 3)
  191. const quote = s.slice(3)
  192. return [base, quote]
  193. }
  194. const formatCmdName = (cmd: string) => {
  195. const v = String(cmd || '').toLowerCase()
  196. if (v.includes('sell')) return '卖出'
  197. if (v.includes('buy')) return '买入'
  198. return cmd || ''
  199. }
  200. const getCmdColorClass = (cmd: string) => {
  201. const v = String(cmd || '').toLowerCase()
  202. if (v.includes('sell')) return 'is-sell'
  203. if (v.includes('buy')) return 'is-buy'
  204. return ''
  205. }
  206. const getProfitColorClass = (profit: any) => {
  207. const n = Number(profit)
  208. if (!Number.isFinite(n) || n === 0) return ''
  209. return n > 0 ? 'is-profit' : 'is-loss'
  210. }
  211. </script>
  212. <style scoped lang="scss">
  213. @import "@/uni.scss";
  214. .avatar {
  215. width: px2rpx(60);
  216. height: px2rpx(60);
  217. border-radius: 4px;
  218. }
  219. .content-title {
  220. display: flex;
  221. justify-content: space-between;
  222. align-items: center;
  223. font-size: px2rpx(20);
  224. font-weight: 500;
  225. .content-title-btns {
  226. margin: px2rpx(8) 0;
  227. display: flex;
  228. align-items: center;
  229. justify-content: center;
  230. gap: px2rpx(12);
  231. .btn-primary {
  232. min-width: px2rpx(120);
  233. background-color: var(--color-error);
  234. color: white;
  235. padding: 0 px2rpx(12);
  236. border: none;
  237. font-size: px2rpx(14);
  238. text-align: center;
  239. cursor: pointer;
  240. display: flex;
  241. align-items: center;
  242. justify-content: center;
  243. gap: px2rpx(8);
  244. }
  245. .btn-primary:active {
  246. background-color: var(--color-navy-700);
  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>