report.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  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. if (payload.date && Array.isArray(payload.date) && payload.date.length === 2) {
  183. payload.startDate = payload.date[0]
  184. payload.endDate = payload.date[1]
  185. } else {
  186. payload.startDate = ''
  187. payload.endDate = ''
  188. }
  189. delete payload.date
  190. payload.agentId = normalizePickerValue(payload.agentId)
  191. const type = Number(payload.reportType)
  192. if (![1, 3, 4, 7, 24].includes(type)) payload.platform = ''
  193. if (![1, 2, 3, 4, 7].includes(type)) payload.login = ''
  194. if (type !== 7) payload.cId = ''
  195. if (![3, 7].includes(type)) payload.isShort = 0
  196. if (type !== 3) payload.detail_type = null
  197. if (type !== 24) payload.customType = 0
  198. search.value = {
  199. reportType: payload.reportType,
  200. detail_type: payload.detail_type,
  201. customType: payload.customType,
  202. platform: payload.platform,
  203. startDate: payload.startDate,
  204. endDate: payload.endDate,
  205. agentId: payload.agentId,
  206. login: payload.login,
  207. cId: payload.cId,
  208. isShort: payload.isShort,
  209. }
  210. tableRef.value?.refreshTable?.()
  211. }
  212. const handleReset = (emptyParams: any) => {
  213. handleSearch(emptyParams)
  214. }
  215. const initIbTree = async () => {
  216. const res = await ibApi.ibTree({ pid: 0 })
  217. if (res.code === Code.StatusOK) {
  218. if (res.data && res.data.length > 0) {
  219. res.data.forEach((item: any) => {
  220. if (item.ibNo) {
  221. let option = {
  222. value: item.id,
  223. label: item.name ? `${item.ibNo} - ${item.name}` : item.ibNo,
  224. name: item.name,
  225. children: [],
  226. }
  227. agentIdOpts.value.push(option)
  228. }
  229. })
  230. } else {
  231. uni.showToast({
  232. title: res.msg, icon: 'none',
  233. })
  234. }
  235. }
  236. }
  237. const onnodeclick = async (node: any) => {
  238. const val = node.value
  239. if (node.children && node.children.length > 0) {
  240. return
  241. }
  242. try {
  243. // uni.showLoading({ title: t('Msg.Loading') || 'Loading...', mask: true })
  244. const res = await ibApi.ibTree({ pid: val })
  245. if (res.code === Code.StatusOK) {
  246. if (res.data && res.data.length > 0) {
  247. const newChildren: any[] = []
  248. res.data.forEach((item: any) => {
  249. if (item.ibNo) {
  250. newChildren.push({
  251. value: item.id,
  252. label: item.name ? `${item.ibNo} - ${item.name}` : item.ibNo,
  253. name: item.name,
  254. children: [],
  255. })
  256. }
  257. })
  258. node.children = newChildren
  259. } else {
  260. delete node.children
  261. }
  262. } else {
  263. uni.showToast({
  264. title: res.msg || 'Error',
  265. icon: 'none',
  266. })
  267. }
  268. } catch (error) {
  269. uni.showToast({ title: 'Error', icon: 'none' })
  270. } finally {
  271. // uni.hideLoading()
  272. }
  273. }
  274. // 表格列配置 根据types 切换
  275. const columns = computed(() => {
  276. if (search.value.reportType === 3) {
  277. return columnList[`3_${search.value.detail_type}` as keyof typeof columnList] || []
  278. }
  279. return columnList[search.value.reportType as keyof typeof columnList] || []
  280. })
  281. const mobilePrimaryFields = computed(() => {
  282. let list: any[] = []
  283. if (search.value.reportType === 3) {
  284. list = mobileList[`3_${search.value.detail_type}` as keyof typeof mobileList] || []
  285. } else {
  286. list = mobileList[search.value.reportType as keyof typeof mobileList] || []
  287. }
  288. return [
  289. ...list,
  290. {
  291. prop: 'more',
  292. type: 'more',
  293. width: 20,
  294. align: 'right',
  295. },
  296. ]
  297. })
  298. // 接口 根据types 切换(动态代理不同类型报表API)
  299. const listApi = computed(() => {
  300. return async (params: any) => {
  301. let apiFn: any
  302. const type = search.value.reportType
  303. const detailType = search.value.detail_type
  304. if (type == 1) apiFn = ibApi.tradeDw
  305. else if (type == 2) apiFn = ibApi.tradeAgentCommission
  306. else if (type == 3) {
  307. if (detailType == 1) apiFn = ibApi.tradeHistory
  308. else if (detailType == 2) apiFn = ibApi.tradePendingHistory
  309. else if (detailType == 3) apiFn = ibApi.tradePending
  310. else if (detailType == 4) apiFn = ibApi.tradePosition
  311. } else if (type == 4) apiFn = ibApi.tradeAccount
  312. else if (type == 5) apiFn = ibApi.tradeIb
  313. else if (type == 6) apiFn = ibApi.ibReportBalance
  314. else if (type == 7) apiFn = ibApi.tradeSymbolCategory
  315. else if (type == 24) apiFn = ibApi.tradeSalesHidden
  316. if (apiFn) {
  317. if (type == 2) {
  318. const isVietnam = country.value === 'VN' // 这里需要你实际的取国家逻辑
  319. return await apiFn(params, isVietnam)
  320. }
  321. return await apiFn(params)
  322. }
  323. return Promise.reject('No API found')
  324. }
  325. })
  326. const groupCurrency1 = (type: string) => {
  327. if (type == 'GBP') return '£'
  328. if (type == 'USD') return '$'
  329. if (type == 'EUR') return '€'
  330. if (type == 'USC') return '¢'
  331. return '$'
  332. }
  333. const groupTypeName = (type: string | number) => {
  334. if (type == '1') return t('AccountType.ClassicAccount')
  335. if (type == '2') return t('AccountType.SeniorAccount')
  336. if (type == '3') return isAfterJuly28() ? '--' : t('AccountType.AgencyAccount')
  337. if (type == '5') return t('AccountType.SpeedAccount')
  338. if (type == '6') return t('AccountType.SpeedAccount')
  339. if (type == '7') return t('AccountType.StandardAccount')
  340. if (type == '8') return t('AccountType.CentAccount')
  341. return '--'
  342. }
  343. watch(() => searchParams.value.reportType, (val) => {
  344. const type = Number(val)
  345. search.value.reportType = type
  346. if (type === 24) {
  347. searchParams.value.customType = 0
  348. searchParams.value.detail_type = null
  349. search.value.customType = 0
  350. search.value.detail_type = null
  351. } else if (type === 3) {
  352. if (searchParams.value.detail_type == null) searchParams.value.detail_type = 1
  353. if (search.value.detail_type == null) search.value.detail_type = 1
  354. } else {
  355. searchParams.value.detail_type = null
  356. search.value.detail_type = null
  357. searchParams.value.customType = 0
  358. search.value.customType = 0
  359. }
  360. }, { immediate: true })
  361. watch(() => searchParams.value.detail_type, (val) => {
  362. search.value.detail_type = val == null ? null : Number(val)
  363. })
  364. watch(() => searchParams.value.platform, (val) => {
  365. search.value.platform = val
  366. })
  367. watch(() => searchParams.value.customType, (val) => {
  368. search.value.customType = val
  369. })
  370. watch(() => searchParams.value.isShort, (val) => {
  371. search.value.isShort = val
  372. })
  373. onMounted(() => {
  374. initIbTree()
  375. })
  376. const filterFields = computed(() => {
  377. const type = Number(searchParams.value.reportType ?? 1)
  378. const fields: any[] = [
  379. { key: 'reportType', type: 'select', label: t('Home.page_ib.item3'), placeholder: t('placeholder.choose'), options: typeList.value, defaultValue: 1, isSelect: true },
  380. ]
  381. if (type === 3) {
  382. fields.push({ key: 'detail_type', type: 'select', label: t('placeholder.choose'), placeholder: t('placeholder.choose'), options: detailTypeList.value, defaultValue: 1, isSelect: true })
  383. }
  384. if ([1, 3, 4, 7, 24].includes(type)) {
  385. fields.push({ key: 'platform', type: 'select', label: t('Label.Platform'), placeholder: t('placeholder.choose'), options: platformOptions, defaultValue: 'MT4' })
  386. }
  387. if (type === 24) {
  388. fields.push({ key: 'customType', type: 'select', label: t('placeholder.choose'), placeholder: t('placeholder.choose'), options: customTypeList, defaultValue: 0, isSelect: true })
  389. }
  390. fields.push({
  391. key: 'agentId',
  392. type: 'picker',
  393. label: t('State.All'),
  394. options: agentIdOpts.value,
  395. popupTitle: t('State.All'),
  396. map: { value: 'value', text: 'label' },
  397. onChange: handleItemChange,
  398. onNodeClick: onnodeclick,
  399. defaultValue: '',
  400. })
  401. if ([1, 2, 3, 4, 7].includes(type)) {
  402. fields.push({ key: 'login', type: 'input', label: t('Label.TradingAccount'), placeholder: t('Label.TradingAccount'), defaultValue: '' })
  403. }
  404. if (type === 7) {
  405. fields.push({ key: 'cId', type: 'input', label: 'CID', placeholder: 'CID', defaultValue: '' })
  406. }
  407. if ([3, 7].includes(type)) {
  408. fields.push({ key: 'isShort', type: 'select', label: t('placeholder.choose'), placeholder: t('placeholder.choose'), options: isShortList.value, defaultValue: 0, isSelect: true })
  409. }
  410. fields.push({ key: 'date', label: t('placeholder.Start') + ' - ' + t('placeholder.End'), type: 'daterange' })
  411. return fields
  412. })
  413. </script>
  414. <style lang="scss" scoped>
  415. @import "@/uni.scss";
  416. .search-content {
  417. display: flex;
  418. justify-content: space-between;
  419. }
  420. .report-platform {
  421. display: flex;
  422. align-items: center;
  423. .checklist-box {
  424. cursor: pointer;
  425. box-sizing: border-box;
  426. padding: 0 px2rpx(20);
  427. border: 1px solid #DCDFE6;
  428. background-color: #f5f5f5;
  429. height: px2rpx(35);
  430. line-height: px2rpx(35);
  431. border-radius: px2rpx(4) 0 0 px2rpx(4);
  432. &:last-child {
  433. border-radius: 0 px2rpx(4) px2rpx(4) 0;
  434. }
  435. &.active {
  436. color: var(--color-white);
  437. background-color: var(--color-error);
  438. border-color: var(--color-error);
  439. }
  440. }
  441. }
  442. .search-input-box {
  443. .uni-input {
  444. height: px2rpx(35);
  445. border: 1px solid #DCDFE6;
  446. padding: 0 px2rpx(20);
  447. border-radius: px2rpx(4);
  448. background-color: #fff;
  449. }
  450. }
  451. .agent-select {
  452. width: px2rpx(240);
  453. }
  454. .search-btn {
  455. height: px2rpx(36);
  456. line-height: px2rpx(36);
  457. margin: 0;
  458. }
  459. </style>