report.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. <template>
  2. <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
  3. <cwg-header :title="t('Home.page_ib.item3')" />
  4. <view class="account-content">
  5. <view class="search-content">
  6. <view class="search-bar">
  7. <cwg-complex-search :fields="filterFields" v-model="searchParams" @search="handleSearch" @reset="handleReset" />
  8. </view>
  9. </view>
  10. <cwg-tabel
  11. ref="tableRef"
  12. :columns="columns"
  13. :mobilePrimaryFields="mobilePrimaryFields"
  14. :queryParams="search"
  15. :api="listApi"
  16. :show-operation="false"
  17. :showPagination="true"
  18. :showSummary="true"
  19. :summaryMethod="getSummaries"
  20. >
  21. </cwg-tabel>
  22. </view>
  23. </cwg-page-wrapper>
  24. </template>
  25. <script setup lang="ts">
  26. // 报告
  27. import { ref, computed, onMounted, watch } from 'vue'
  28. import { useI18n } from 'vue-i18n'
  29. import Config from '@/config/index'
  30. import { ibApi } from '@/service/ib'
  31. import { columnList, mobileList, platformOptions, customTypeList } from '@/pages/ib/const/report'
  32. import { useFilters } from '@/composables/useFilters'
  33. import { isAfterJuly28 } from '@/utils/dateUtils'
  34. import useUserStore from '@/stores/use-user-store'
  35. const { numberFormat } = useFilters()
  36. const { t } = useI18n()
  37. let { Code } = Config
  38. const { userInfo } = useUserStore()
  39. const tableRef = ref(null)
  40. const typeList = ref([
  41. { text: t('Ib.Report.Title1'), value: 1 },
  42. { text: t('Ib.Report.Title2'), value: 2 },
  43. { text: t('Ib.Report.Title3'), value: 3 },
  44. { text: t('Ib.Report.Title6'), value: 4 },
  45. { text: t('Ib.Report.Title5'), value: 5 },
  46. { text: t('news_add_field.IbReport.Title6'), value: 6 },
  47. { text: t('Ib.Report.Title7'), value: 7 },
  48. { text: t('Ib.Report.Title8'), value: 24 },
  49. ])
  50. const detailTypeList = ref([
  51. { text: t('Ib.Report.Tit1'), value: 1 },
  52. { text: t('Ib.Report.Tit2'), value: 2 },
  53. { text: t('Ib.Report.Tit3'), value: 3 },
  54. { text: t('Ib.Report.Tit4'), value: 4 },
  55. ])
  56. const isShortList = ref([
  57. { text: t('State.All'), value: 0 },
  58. { text: t('Ib.Report.item1'), value: 1 },
  59. { text: t('Ib.Report.item2'), value: 2 },
  60. ])
  61. const now = new Date()
  62. const year = now.getFullYear()
  63. const month = String(now.getMonth() + 1).padStart(2, '0')
  64. const day = String(now.getDate()).padStart(2, '0')
  65. const defaultDateRange = [`${year}-${month}-01`, `${year}-${month}-${day}`]
  66. const searchParams = ref<any>({
  67. reportType: 1,
  68. detail_type: null,
  69. customType: 0,
  70. platform: 'MT4',
  71. agentId: '',
  72. login: '',
  73. cId: '',
  74. isShort: 0,
  75. date: defaultDateRange,
  76. })
  77. const search = ref({
  78. reportType: 1,
  79. detail_type: null as number | null,
  80. customType: 0,
  81. platform: 'MT4',
  82. startDate: defaultDateRange[0],
  83. endDate: defaultDateRange[1],
  84. agentId: '',
  85. login: '',
  86. cId: '',
  87. isShort: 0,
  88. })
  89. const country = computed(() => {
  90. return userInfo.customInfo.country
  91. })
  92. const agentIdOpts = ref<any[]>([
  93. { value: 0, label: t('news_add_field.IbReport.ALL'), children: [] },
  94. {
  95. value: -1,
  96. label: t('news_add_field.IbReport.DirectlyUnder'),
  97. children: [],
  98. },
  99. ])
  100. const handleItemChange = (e: any) => {}
  101. const getSummaries = (param: any) => {
  102. const { columns, summaryData } = param
  103. const sums: string[] = []
  104. if (!summaryData || Object.keys(summaryData).length === 0) return sums
  105. columns.forEach((column: any, index: number) => {
  106. if (index === 0) {
  107. sums[index] = t("Label.Total")
  108. return
  109. }
  110. const type = search.value.reportType
  111. const detailType = search.value.detail_type
  112. // 根据不同的列 prop 进行合计数据显示和格式化
  113. const prop = column.prop
  114. if (!prop) {
  115. sums[index] = ""
  116. return
  117. }
  118. if (type == 1) {
  119. if (['deposit', 'withdrawal', 'credit'].includes(prop)) {
  120. sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
  121. } else {
  122. sums[index] = ""
  123. }
  124. } else if (type == 2) {
  125. if (['volume', 'rebateAmount', 'commissionAmount'].includes(prop)) {
  126. sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
  127. } else {
  128. sums[index] = ""
  129. }
  130. } else if (type == 3) {
  131. if (detailType == 4) {
  132. if (['volume', 'storage', 'taxes', 'profit'].includes(prop)) {
  133. sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
  134. } else {
  135. sums[index] = ""
  136. }
  137. } else {
  138. if (['commission', 'volume', 'storage', 'taxes', 'profit', 'totalProfit'].includes(prop)) {
  139. sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
  140. } else {
  141. sums[index] = ""
  142. }
  143. }
  144. } else if (type == 4) {
  145. if (['credit', 'balance', 'equity', 'floating'].includes(prop)) {
  146. sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
  147. } else {
  148. sums[index] = ""
  149. }
  150. } else if (type == 6) {
  151. if (prop === 'amount') {
  152. sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
  153. } else {
  154. sums[index] = ""
  155. }
  156. } else if (type == 7) {
  157. if (['fxVolume', 'cfdVolume', 'indexVolume', 'metalVolume'].includes(prop)) {
  158. sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
  159. } else {
  160. sums[index] = ""
  161. }
  162. } else if (type == 24) {
  163. if (prop === 'volume') {
  164. sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) + " /手" : "0"
  165. } else if (prop === 'rebate') {
  166. sums[index] = summaryData[prop] ? "$" + numberFormat(summaryData[prop]) : "0"
  167. } else {
  168. sums[index] = ""
  169. }
  170. } else {
  171. sums[index] = ""
  172. }
  173. })
  174. return sums
  175. }
  176. const normalizePickerValue = (val: any) => {
  177. if (Array.isArray(val)) return val[val.length - 1]
  178. return val
  179. }
  180. const handleSearch = (params: any) => {
  181. const payload: any = { ...params }
  182. payload.agentId = normalizePickerValue(payload.agentId)
  183. const type = Number(payload.reportType)
  184. if (![1, 3, 4, 7, 24].includes(type)) payload.platform = ''
  185. if (![1, 2, 3, 4, 7].includes(type)) payload.login = ''
  186. if (type !== 7) payload.cId = ''
  187. if (![3, 7].includes(type)) payload.isShort = 0
  188. if (type !== 3) payload.detail_type = null
  189. if (type !== 24) payload.customType = 0
  190. search.value = {
  191. reportType: payload.reportType,
  192. detail_type: payload.detail_type,
  193. customType: payload.customType,
  194. platform: payload.platform,
  195. startDate: payload.startDate,
  196. endDate: payload.endDate,
  197. agentId: payload.agentId,
  198. login: payload.login,
  199. cId: payload.cId,
  200. isShort: payload.isShort,
  201. }
  202. tableRef.value?.refreshTable?.()
  203. }
  204. const handleReset = (emptyParams: any) => {
  205. handleSearch(emptyParams)
  206. }
  207. const initIbTree = async () => {
  208. const res = await ibApi.ibTree({ pid: 0 })
  209. if (res.code === Code.StatusOK) {
  210. if (res.data && res.data.length > 0) {
  211. res.data.forEach((item: any) => {
  212. if (item.ibNo) {
  213. let option = {
  214. value: item.id,
  215. label: item.name ? `${item.ibNo} - ${item.name}` : item.ibNo,
  216. name: item.name,
  217. children: [],
  218. }
  219. agentIdOpts.value.push(option)
  220. }
  221. })
  222. } else {
  223. uni.showToast({
  224. title: res.msg, icon: 'none',
  225. })
  226. }
  227. }
  228. }
  229. const onnodeclick = async (node: any) => {
  230. const val = node.value
  231. if (node.children && node.children.length > 0) {
  232. return
  233. }
  234. try {
  235. // uni.showLoading({ title: t('Msg.Loading') || 'Loading...', mask: true })
  236. const res = await ibApi.ibTree({ pid: val })
  237. if (res.code === Code.StatusOK) {
  238. if (res.data && res.data.length > 0) {
  239. const newChildren: any[] = []
  240. res.data.forEach((item: any) => {
  241. if (item.ibNo) {
  242. newChildren.push({
  243. value: item.id,
  244. label: item.name ? `${item.ibNo} - ${item.name}` : item.ibNo,
  245. name: item.name,
  246. children: [],
  247. })
  248. }
  249. })
  250. node.children = newChildren
  251. } else {
  252. delete node.children
  253. }
  254. } else {
  255. uni.showToast({
  256. title: res.msg || 'Error',
  257. icon: 'none',
  258. })
  259. }
  260. } catch (error) {
  261. uni.showToast({ title: 'Error', icon: 'none' })
  262. } finally {
  263. // uni.hideLoading()
  264. }
  265. }
  266. // 表格列配置 根据types 切换
  267. const columns = computed(() => {
  268. if (search.value.reportType === 3) {
  269. return columnList[`3_${search.value.detail_type}` as keyof typeof columnList] || []
  270. }
  271. return columnList[search.value.reportType as keyof typeof columnList] || []
  272. })
  273. const mobilePrimaryFields = computed(() => {
  274. let list: any[] = []
  275. if (search.value.reportType === 3) {
  276. list = mobileList[`3_${search.value.detail_type}` as keyof typeof mobileList] || []
  277. } else {
  278. list = mobileList[search.value.reportType as keyof typeof mobileList] || []
  279. }
  280. return [
  281. ...list,
  282. {
  283. prop: 'more',
  284. type: 'more',
  285. width: 20,
  286. align: 'right',
  287. },
  288. ]
  289. })
  290. // 接口 根据types 切换(动态代理不同类型报表API)
  291. const listApi = computed(() => {
  292. return async (params: any) => {
  293. let apiFn: any
  294. const type = search.value.reportType
  295. const detailType = search.value.detail_type
  296. if (type == 1) apiFn = ibApi.tradeDw
  297. else if (type == 2) apiFn = ibApi.tradeAgentCommission
  298. else if (type == 3) {
  299. if (detailType == 1) apiFn = ibApi.tradeHistory
  300. else if (detailType == 2) apiFn = ibApi.tradePendingHistory
  301. else if (detailType == 3) apiFn = ibApi.tradePending
  302. else if (detailType == 4) apiFn = ibApi.tradePosition
  303. } else if (type == 4) apiFn = ibApi.tradeAccount
  304. else if (type == 5) apiFn = ibApi.tradeIb
  305. else if (type == 6) apiFn = ibApi.ibReportBalance
  306. else if (type == 7) apiFn = ibApi.tradeSymbolCategory
  307. else if (type == 24) apiFn = ibApi.tradeSalesHidden
  308. if (apiFn) {
  309. if (type == 2) {
  310. const isVietnam = country.value === 'VN' // 这里需要你实际的取国家逻辑
  311. return await apiFn(params, isVietnam)
  312. }
  313. return await apiFn(params)
  314. }
  315. return Promise.reject('No API found')
  316. }
  317. })
  318. const groupCurrency1 = (type: string) => {
  319. if (type == 'GBP') return '£'
  320. if (type == 'USD') return '$'
  321. if (type == 'EUR') return '€'
  322. if (type == 'USC') return '¢'
  323. return '$'
  324. }
  325. const groupTypeName = (type: string | number) => {
  326. if (type == '1') return t('AccountType.ClassicAccount')
  327. if (type == '2') return t('AccountType.SeniorAccount')
  328. if (type == '3') return isAfterJuly28() ? '--' : t('AccountType.AgencyAccount')
  329. if (type == '5') return t('AccountType.SpeedAccount')
  330. if (type == '6') return t('AccountType.SpeedAccount')
  331. if (type == '7') return t('AccountType.StandardAccount')
  332. if (type == '8') return t('AccountType.CentAccount')
  333. return '--'
  334. }
  335. watch(() => searchParams.value.reportType, (val) => {
  336. const type = Number(val)
  337. search.value.reportType = type
  338. if (type === 24) {
  339. searchParams.value.customType = 0
  340. searchParams.value.detail_type = null
  341. search.value.customType = 0
  342. search.value.detail_type = null
  343. } else if (type === 3) {
  344. if (searchParams.value.detail_type == null) searchParams.value.detail_type = 1
  345. if (search.value.detail_type == null) search.value.detail_type = 1
  346. } else {
  347. searchParams.value.detail_type = null
  348. search.value.detail_type = null
  349. searchParams.value.customType = 0
  350. search.value.customType = 0
  351. }
  352. }, { immediate: true })
  353. watch(() => searchParams.value.detail_type, (val) => {
  354. search.value.detail_type = val == null ? null : Number(val)
  355. })
  356. watch(() => searchParams.value.platform, (val) => {
  357. search.value.platform = val
  358. })
  359. watch(() => searchParams.value.customType, (val) => {
  360. search.value.customType = val
  361. })
  362. watch(() => searchParams.value.isShort, (val) => {
  363. search.value.isShort = val
  364. })
  365. onMounted(() => {
  366. initIbTree()
  367. })
  368. const filterFields = computed(() => {
  369. const type = Number(searchParams.value.reportType ?? 1)
  370. const fields: any[] = [
  371. { key: 'reportType', type: 'select', label: t('Home.page_ib.item3'), placeholder: t('placeholder.choose'), options: typeList.value, defaultValue: 1, isSelect: true },
  372. ]
  373. if (type === 3) {
  374. fields.push({ key: 'detail_type', type: 'select', label: t('placeholder.choose'), placeholder: t('placeholder.choose'), options: detailTypeList.value, defaultValue: 1, isSelect: true })
  375. }
  376. if ([1, 3, 4, 7, 24].includes(type)) {
  377. fields.push({ key: 'platform', type: 'select', label: t('Label.Platform'), placeholder: t('placeholder.choose'), options: platformOptions, defaultValue: 'MT4' })
  378. }
  379. if (type === 24) {
  380. fields.push({ key: 'customType', type: 'select', label: t('placeholder.choose'), placeholder: t('placeholder.choose'), options: customTypeList, defaultValue: 0, isSelect: true })
  381. }
  382. fields.push({
  383. key: 'agentId',
  384. type: 'picker',
  385. label: t('State.All'),
  386. options: agentIdOpts.value,
  387. popupTitle: t('State.All'),
  388. map: { value: 'value', text: 'label' },
  389. onChange: handleItemChange,
  390. onNodeClick: onnodeclick,
  391. defaultValue: '',
  392. })
  393. if ([1, 2, 3, 4, 7].includes(type)) {
  394. fields.push({ key: 'login', type: 'input', label: t('Label.TradingAccount'), placeholder: t('Label.TradingAccount'), defaultValue: '' })
  395. }
  396. if (type === 7) {
  397. fields.push({ key: 'cId', type: 'input', label: 'CID', placeholder: 'CID', defaultValue: '' })
  398. }
  399. if ([3, 7].includes(type)) {
  400. fields.push({ key: 'isShort', type: 'select', label: t('placeholder.choose'), placeholder: t('placeholder.choose'), options: isShortList.value, defaultValue: 0, isSelect: true })
  401. }
  402. fields.push({ key: 'date', label: t('placeholder.Start') + ' - ' + t('placeholder.End'), type: 'daterange' })
  403. return fields
  404. })
  405. </script>
  406. <style lang="scss" scoped>
  407. @import "@/uni.scss";
  408. .search-content {
  409. display: flex;
  410. justify-content: space-between;
  411. }
  412. .report-platform {
  413. display: flex;
  414. align-items: center;
  415. .checklist-box {
  416. cursor: pointer;
  417. box-sizing: border-box;
  418. padding: 0 px2rpx(20);
  419. border: 1px solid #DCDFE6;
  420. background-color: #f5f5f5;
  421. height: px2rpx(35);
  422. line-height: px2rpx(35);
  423. border-radius: px2rpx(4) 0 0 px2rpx(4);
  424. &:last-child {
  425. border-radius: 0 px2rpx(4) px2rpx(4) 0;
  426. }
  427. &.active {
  428. color: var(--color-white);
  429. background-color: var(--color-error);
  430. border-color: var(--color-error);
  431. }
  432. }
  433. }
  434. .search-input-box {
  435. .uni-input {
  436. height: px2rpx(35);
  437. border: 1px solid #DCDFE6;
  438. padding: 0 px2rpx(20);
  439. border-radius: px2rpx(4);
  440. background-color: #fff;
  441. }
  442. }
  443. .agent-select {
  444. width: px2rpx(240);
  445. }
  446. .search-btn {
  447. height: px2rpx(36);
  448. line-height: px2rpx(36);
  449. margin: 0;
  450. }
  451. </style>