report.vue 14 KB

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