Просмотр исходного кода

feat:ib- 报告,申请历史,table表格添加合计行(测试)

ljc 2 месяцев назад
Родитель
Сommit
6ae4107951
6 измененных файлов с 1023 добавлено и 18 удалено
  1. 75 1
      components/cwg-tabel.vue
  2. 2 1
      locale/cn.json
  3. 2 0
      pages/ib/const/recording.ts
  4. 488 0
      pages/ib/const/report.ts
  5. 1 1
      pages/ib/recording.vue
  6. 455 15
      pages/ib/report.vue

+ 75 - 1
components/cwg-tabel.vue

@@ -52,6 +52,36 @@
                         </uni-td>
                     </uni-tr>
                 </template>
+
+                <!-- 总计行 -->
+                <uni-tr v-if="showSummary" class="summary-row">
+                    <uni-td v-for="(column, index) in displayColumns" :key="'summary-' + column.prop" :align="column.align || 'center'"
+                        class="summary-cell" :style="getCellStyle(column, currentSummaryData)">
+                        <!-- 当有 summaryMethod 时,按照返回的数组直接渲染 -->
+                        <template v-if="props.summaryMethod">
+                            <text>{{ computedSummaryRow[index] !== undefined ? computedSummaryRow[index] : '' }}</text>
+                        </template>
+                        <template v-else>
+                            <!-- 特定列的总计自定义插槽,例如 #summary-volume="{ row }" -->
+                            <template v-if="$slots['summary-' + column.prop]">
+                                <slot :name="'summary-' + column.prop" :row="currentSummaryData" :column="column" :index="index"></slot>
+                            </template>
+                            <!-- 如果没有特定插槽,尝试常规渲染 -->
+                            <template v-else>
+                                <template v-if="currentSummaryData && currentSummaryData[column.prop] !== undefined && currentSummaryData[column.prop] !== null">
+                                    <!-- 如果有常规插槽,并且总计数据里有该字段的值,则复用常规插槽渲染 -->
+                                    <slot v-if="column.slot" :name="column.slot" :row="currentSummaryData" :column="column" :index="index">
+                                        {{ currentSummaryData[column.prop] }}
+                                    </slot>
+                                    <!-- 否则使用格式化函数 -->
+                                    <text v-else>{{ formatCellValue(currentSummaryData[column.prop], column, currentSummaryData) }}</text>
+                                </template>
+                                <!-- 如果总计数据里没有该字段的值,第一列显示总计文本,其他留空 -->
+                                <text v-else>{{ index === 0 ? summaryText : '' }}</text>
+                            </template>
+                        </template>
+                    </uni-td>
+                </uni-tr>
             </uni-table>
         </view>
         <!-- 空状态 -->
@@ -154,6 +184,14 @@ const props = defineProps({
     stickyHeader: { type: Boolean, default: true },
     stickyOffset: { type: [String, Number], default: '0' },
     isPages: { type: Boolean, default: false },
+    // 是否显示总计行
+    showSummary: { type: Boolean, default: false },
+    // 自定义总计数据,若不传则尝试使用 api 响应中的 res.sum
+    summaryData: { type: Object, default: () => null },
+    // 自定义的合计计算方法,如果配置了,则使用该方法计算总计数据
+    summaryMethod: { type: Function, default: null },
+    // 总计行第一列的默认文本
+    summaryText: { type: String, default: '总计' },
 })
 
 const emit = defineEmits([
@@ -179,6 +217,24 @@ const pagination = ref({
 })
 const loading = ref(false)
 const expandedRows = ref({})
+const internalSummaryData = ref(null)
+
+const currentSummaryData = computed(() => {
+    if (props.summaryData && Object.keys(props.summaryData).length > 0) return props.summaryData
+    return internalSummaryData.value
+})
+
+const computedSummaryRow = computed(() => {
+    if (props.showSummary && props.summaryMethod && typeof props.summaryMethod === 'function') {
+        const result = props.summaryMethod({
+            columns: displayColumns.value,
+            data: tableData.value,
+            summaryData: currentSummaryData.value
+        });
+        return Array.isArray(result) ? result : [];
+    }
+    return [];
+});
 
 // 移动端检测
 const isMobile = ref(false)
@@ -434,7 +490,12 @@ const loadData = async () => {
             params.sort = { field: sortState.value.prop, order: sortState.value.order }
         }
         const res = await props.api(params)
-        if (res.code === 200 || res.code === 0) {
+        if (res.code === 200 || res.code === 0 || res.code === 10000) {
+            if (res.sum) {
+                internalSummaryData.value = res.sum
+            } else {
+                internalSummaryData.value = null
+            }
             const data = res.data || res
             const hasPage = applyPaginationFromRes(res)
             if (Array.isArray(data)) {
@@ -614,6 +675,19 @@ defineExpose({
             }
         }
 
+        .summary-row {
+            background-color: #fafafa;
+            font-weight: 600;
+            
+            .uni-table-td {
+                border-top: 1px solid #ebeef5 !important;
+                position: sticky;
+                bottom: 0;
+                z-index: 99;
+                background-color: #fafafa;
+            }
+        }
+
         .uni-table-th,
         .uni-table-td {
             padding: px2rpx(16) px2rpx(5);

+ 2 - 1
locale/cn.json

@@ -1086,7 +1086,8 @@
       "item6": "活动中心",
       "item7": "申请历史",
       "item8": "佣金模板",
-      "item9": "代理内转"
+      "item9": "代理内转",
+      "item10": "账户管理"
     },
     "page_shop": {
       "item1": "商城首页",

+ 2 - 0
pages/ib/const/recording.ts

@@ -1,4 +1,6 @@
 import { t } from '@/utils/i18n'
+import { useFilters } from '../../../composables/useFilters'
+const { numberFormat } = useFilters()
 
 export const columnList  = {
   1: [

+ 488 - 0
pages/ib/const/report.ts

@@ -0,0 +1,488 @@
+import { t } from '@/utils/i18n'
+import { isAfterJuly28 } from '@/utils/dateUtils'
+import { useFilters } from '../../../composables/useFilters'
+
+const { numberFormat } = useFilters()
+
+export const platformOptions = [
+  { text: 'MT4', value: 'MT4' },
+  { text: 'MT5', value: 'MT5' },
+]
+export const customTypeList = [
+  { text: t('AccountType.ClassicAccount'), value: 1 },
+  { text: t('AccountType.ClassicAccount'), value: 2 },
+  !isAfterJuly28 ? { text: t('AccountType.ClassicAccount'), value: 3 } : '',
+  { text: t('AccountType.ClassicAccount'), value: 7 },
+  { text: t('AccountType.ClassicAccount'), value: 8 },
+].filter((v) => !!v)
+
+export const columnList = {
+  '1': [
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    { prop: 'name', label: t('Label.Name') },
+    {
+      prop: 'deposit', label: t('Label.Deposit'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.deposit ?? '0')}`
+      },
+    },
+    {
+      prop: 'withdrawal', label: t('Label.Withdrawals'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.withdrawal ?? '0')}`
+      },
+    },
+    {
+      prop: 'credit', label: t('Label.Credit'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.credit ?? '0')}`
+      },
+    },
+    { prop: 'closeTime', label: t('Label.CloseTime') },
+  ],
+  '2': [
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    {
+      prop: 'groupType', label: t('Label.Type'),
+      type: 'tag',
+      tagMap: {
+        1: t('AccountType.ClassicAccount'),
+        2: t('AccountType.SeniorAccount'),
+        3: !isAfterJuly28() ? t('AccountType.AgencyAccount') : '',
+        4: t('AccountType.ProfessionalAccount'),
+        5: t('AccountType.SpeedAccount'),
+        6: t('AccountType.SpeedAccount'),
+        7: t('AccountType.StandardAccount'),
+        8: t('AccountType.CentAccount'),
+      },
+    },
+    { prop: 'symbol', label: t('Label.Varieties') },
+    {
+      prop: 'volume', label: t('Label.Volume'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.volume ?? '0')}/${t('Label.Lot')}`
+      },
+    },
+    {
+      prop: 'rebateAmount', label: t('Label.rebateAmount'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.rebateAmount ?? '0')}`
+      },
+    },
+    {
+      prop: 'rebatePoint', label: t('Label.rebatePoint'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.rebatePoint ?? '0')}/${t('Label.Lot')}`
+      },
+    },
+    {
+      prop: 'commissionAmount', label: t('Label.commissionAmount'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.commissionAmount ?? '0')}`
+      },
+    },
+    {
+      prop: 'commissionPoint', label: t('Label.commissionPoint'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.commissionPoint ?? '0')}/${t('Label.Lot')}`
+      },
+    },
+    { prop: 'openTime', label: t('Label.OpenTime') },
+    { prop: 'closeTime', label: t('Label.CloseTime') },
+  ],
+  '3_1': [
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    { prop: 'order', label: t('Label.OrderNumber') },
+    { prop: 'platform', label: t('Label.Platform') },
+    { prop: 'cmdName', label: t('Label.Type') },
+    { prop: 'symbol', label: t('Label.Varieties') },
+    {
+      prop: 'openPrice', label: t('Label.OpenPrice'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.openPrice ?? '0')}`
+      },
+    },
+    { prop: 'openTime', label: t('Label.OpenTime') },
+    {
+      prop: 'closePrice', label: t('Label.ClosePrice'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.closePrice ?? '0')}`
+      },
+    },
+    { prop: 'closeTime', label: t('Label.CloseTime') },
+    { prop: 'tp', label: t('Label.EP') },
+    { prop: 'sl', label: t('Label.EL') },
+    {
+      prop: 'commission', label: t('Label.OutsideCommission'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.commission ?? '0')}`
+      },
+    },
+    {
+      prop: 'volume', label: t('Label.Volume'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.volume ?? '0')}/${t('Label.Lot')}`
+      },
+    },
+    {
+      prop: 'storage', label: t('Label.StorageFee'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.storage ?? '0')}`
+      },
+    },
+    {
+      prop: 'profit', label: t('Label.ProfitLoss'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.profit ?? '0')}`
+      },
+    },
+    {
+      prop: 'totalProfit', label: t('Label.TotalProfitLoss'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.totalProfit ?? '0')}`
+      },
+    },
+  ],
+  '3_2': [
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    { prop: 'order', label: t('Label.OrderNumber') },
+    { prop: 'platform', label: t('Label.Platform') },
+    { prop: 'cmdName', label: t('Label.Type') },
+    { prop: 'symbol', label: t('Label.Varieties') },
+    {
+      prop: 'openPrice', label: t('Label.OpenPrice'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.openPrice ?? '0')}`
+      },
+    },
+    { prop: 'openTime', label: t('Label.OpenTime') },
+    {
+      prop: 'closePrice', label: t('Label.ClosePrice'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.closePrice ?? '0')}`
+      },
+    },
+    { prop: 'closeTime', label: t('Label.CloseTime') },
+    { prop: 'tp', label: t('Label.EP') },
+    { prop: 'sl', label: t('Label.EL') },
+    {
+      prop: 'commission', label: t('Label.OutsideCommission'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.commission ?? '0')}`
+      },
+    },
+    {
+      prop: 'volume', label: t('Label.Volume'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.volume ?? '0')}/${t('Label.Lot')}`
+      },
+    },
+    {
+      prop: 'storage', label: t('Label.StorageFee'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.storage ?? '0')}`
+      },
+    },
+    {
+      prop: 'profit', label: t('Label.ProfitLoss'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.profit ?? '0')}`
+      },
+    },
+    {
+      prop: 'totalProfit', label: t('Label.TotalProfitLoss'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.totalProfit ?? '0')}`
+      },
+    },
+  ],
+  '3_3': [
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    { prop: 'order', label: t('Label.OrderNumber') },
+    { prop: 'platform', label: t('Label.Platform') },
+    { prop: 'cmdName', label: t('Label.Type') },
+    { prop: 'symbol', label: t('Label.Varieties') },
+    {
+      prop: 'openPrice', label: t('Label.OpenPrice'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.openPrice ?? '0')}`
+      },
+    },
+    { prop: 'openTime', label: t('Label.OpenTime') },
+    {
+      prop: 'closePrice', label: t('Label.ClosePrice'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.closePrice ?? '0')}`
+      },
+    },
+    { prop: 'closeTime', label: t('Label.CloseTime') },
+    { prop: 'tp', label: t('Label.EP') },
+    { prop: 'sl', label: t('Label.EL') },
+    {
+      prop: 'commission', label: t('Label.OutsideCommission'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.commission ?? '0')}`
+      },
+    },
+    {
+      prop: 'volume', label: t('Label.Volume'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.volume ?? '0')}/${t('Label.Lot')}`
+      },
+    },
+    {
+      prop: 'storage', label: t('Label.StorageFee'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.storage ?? '0')}`
+      },
+    },
+    {
+      prop: 'profit', label: t('Label.ProfitLoss'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.profit ?? '0')}`
+      },
+    },
+    {
+      prop: 'totalProfit', label: t('Label.TotalProfitLoss'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.totalProfit ?? '0')}`
+      },
+    },
+  ],
+  '3_4': [
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    { prop: 'order', label: t('Label.OrderNumber') },
+    { prop: 'platform', label: t('Label.Platform') },
+    { prop: 'cmdName', label: t('Label.Type') },
+    { prop: 'symbol', label: t('Label.Varieties') },
+    {
+      prop: 'openPrice', label: t('Label.OpenPrice'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.openPrice ?? '0')}`
+      },
+    },
+    { prop: 'openTime', label: t('Label.OpenTime') },
+    { prop: 'tp', label: t('Label.EP') },
+    { prop: 'sl', label: t('Label.EL') },
+    {
+      prop: 'volume', label: t('Label.Volume'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.volume ?? '0')}/${t('Label.Lot')}`
+      },
+    },
+    {
+      prop: 'storage', label: t('Label.StorageFee'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.storage ?? '0')}`
+      },
+    },
+    {
+      prop: 'profit', label: t('Label.ProfitLoss'),
+      formatter: ({ row }) => {
+        return `${groupCurrency1(row.currency)}${numberFormat(row.profit ?? '0')}`
+      },
+    },
+  ],
+  '4': [
+    { prop: 'cId', label: t('Label.CidAccount') },
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    {
+      prop: 'groupType', label: t('Label.AccountType'),
+      formatter: ({ row }) => {
+        return groupTypeName(row.groupType)
+      },
+    },
+    { prop: 'name', label: t('Label.Name') },
+    { prop: 'platform', label: t('Label.Platform') },
+    { prop: 'leverage', label: t('Label.Leverage') },
+    { prop: 'margin', label: t('Label.margin') },
+    {
+      prop: 'commission', label: t('Label.OutsideCommission'),
+      formatter: ({ row }) => {
+        return `$${row.commission ?? '0'}`
+      },
+    },
+    {
+      prop: 'credit', label: t('Label.Credit'),
+      formatter: ({ row }) => {
+        return `$${row.credit ?? '0'}`
+      },
+    },
+    {
+      prop: 'balance', label: t('Label.Amount'),
+      formatter: ({ row }) => {
+        return `$${row.balance ?? '0'}`
+      },
+    },
+    {
+      prop: 'equity', label: t('Label.equity'),
+      formatter: ({ row }) => {
+        return `$${row.equity ?? '0'}`
+      },
+    },
+    { prop: 'floating', label: t('Label.FloatingPL') },
+    { prop: 'registration', label: t('Label.RegistrationTime') },
+  ],
+  '5': [
+    { prop: 'ibNo', label: t('Label.IBAccount') },
+    { prop: 'pIbNo', label: t('Label.AttributionNumber') },
+    { prop: 'name', label: t('Label.Name') },
+    { prop: 'addTime', label: t('Label.RegistrationTime') },
+  ],
+  '6': [
+    { prop: 'ibNo', label: t('Label.IBAccount') },
+    { prop: 'typeName', label: t('Label.OperatingMode') },
+    { prop: 'balanceTypeName', label: t('Label.CommissionType') },
+    {
+      prop: 'amount', label: t('Label.AmountMoney'),
+      formatter: ({ row }) => {
+        return numberFormat(row.amount ?? '0')
+      },
+    },
+    { prop: 'addTime', label: t('Label.ProcessingDate') },
+  ],
+  '7': [
+    { prop: 'cId', label: 'CID' },
+    { prop: 'ibNo', label: t('Label.IBAccount') },
+    { prop: 'name', label: t('Label.Name') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    { prop: 'platform', label: t('Label.Platform') },
+    { prop: 'fxVolume', label: t('Label.fxVolume') },
+    { prop: 'cfdVolume', label: t('Label.cfdVolume') },
+    { prop: 'indexVolume', label: t('Label.indexVolume') },
+    { prop: 'metalVolume', label: t('Label.metalVolume') },
+  ],
+  '24': [
+    { prop: 'cId', label: t('Label.CidAccount') },
+    { prop: 'ticket', label: t('Label.OrderNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    { prop: 'symbol', label: t('Label.Varieties') },
+    { prop: 'openTime', label: t('Label.OpenTime') },
+    { prop: 'closeTime', label: t('Label.CloseTime') },
+    { prop: 'customType', label: t('Label.Type'),
+      type: 'tag',
+      tagMap: {
+        1: t('AccountType.ClassicAccount'),
+        2: t('AccountType.SeniorAccount'),
+        5: t('AccountType.SpeedAccount'),
+        6: t('AccountType.NewSpeedAccount'),
+        7: t('AccountType.StandardAccount'),
+        8: t('AccountType.CentAccount'),
+      },
+    },
+    { prop: 'point', label: t('Label.Rebates') },
+    { prop: 'pointValue', label: t('Ib.Report.Title9') },
+    { prop: 'volume', label: t('Label.Volume'),
+      formatter: ({ row }) => {
+        return `${numberFormat(row.volume ?? '0')}/${t('Label.Lot')}`
+      },
+    },
+    { prop: 'rebate', label: t('Label.Commission'),
+      formatter: ({ row }) => {
+        return `$${row.rebate ?? '0'}`
+      },
+    },
+    { prop: 'ibNo', label: t('Label.AgentNumber') },
+    { prop: 'platform', label: t('Label.Platform') },
+  ],
+}
+
+const groupCurrency1 = (type: string) => {
+  if (type == 'GBP') return '£'
+  if (type == 'USD') return '$'
+  if (type == 'EUR') return '€'
+  if (type == 'USC') return '¢'
+  return '$'
+}
+
+const groupTypeName = (type: string | number) => {
+  if (type == '1') return t('AccountType.ClassicAccount')
+  if (type == '2') return t('AccountType.SeniorAccount')
+  if (type == '3') return isAfterJuly28() ? '--' : t('AccountType.AgencyAccount')
+  if (type == '5') return t('AccountType.SpeedAccount')
+  if (type == '6') return t('AccountType.SpeedAccount')
+  if (type == '7') return t('AccountType.StandardAccount')
+  if (type == '8') return t('AccountType.CentAccount')
+  return '--'
+}
+
+
+export const mobileList = {
+  '1': [
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    { prop: 'name', label: t('Label.Name') },
+  ],
+  '2': [
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    {
+      prop: 'groupType', label: t('Label.Type'),
+      type: 'tag',
+      tagMap: {
+        1: t('AccountType.ClassicAccount'),
+        2: t('AccountType.SeniorAccount'),
+        3: !isAfterJuly28() ? t('AccountType.AgencyAccount') : '',
+        4: t('AccountType.ProfessionalAccount'),
+        5: t('AccountType.SpeedAccount'),
+        6: t('AccountType.SpeedAccount'),
+        7: t('AccountType.StandardAccount'),
+        8: t('AccountType.CentAccount'),
+      },
+    },
+  ],
+  '3_1': [
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    { prop: 'order', label: t('Label.OrderNumber') },
+  ],
+  '3_2': [
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    { prop: 'order', label: t('Label.OrderNumber') },
+  ],
+  '3_3': [
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    { prop: 'order', label: t('Label.OrderNumber') },
+
+  ],
+  '3_4': [
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+    { prop: 'order', label: t('Label.OrderNumber') },
+  ],
+  '4': [
+    { prop: 'cId', label: t('Label.CidAccount') },
+    { prop: 'ibNo', label: t('Label.AttributionNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+
+  ],
+  '5': [
+    { prop: 'ibNo', label: t('Label.IBAccount') },
+    { prop: 'pIbNo', label: t('Label.AttributionNumber') },
+    { prop: 'name', label: t('Label.Name') },
+  ],
+  '6': [
+    { prop: 'ibNo', label: t('Label.IBAccount') },
+    { prop: 'typeName', label: t('Label.OperatingMode') },
+    { prop: 'balanceTypeName', label: t('Label.CommissionType') },
+  ],
+  '7': [
+    { prop: 'cId', label: 'CID' },
+    { prop: 'ibNo', label: t('Label.IBAccount') },
+    { prop: 'name', label: t('Label.Name') },
+  ],
+  '24': [
+    { prop: 'cId', label: t('Label.CidAccount') },
+    { prop: 'ticket', label: t('Label.OrderNumber') },
+    { prop: 'login', label: t('Label.TradingAccount') },
+  ],
+}

+ 1 - 1
pages/ib/recording.vue

@@ -70,7 +70,7 @@
 </template>
 
 <script setup lang="ts">
-  // 账户管理
+  // 申请历史
   import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
   import { onLoad } from '@dcloudio/uni-app'
   import { useI18n } from 'vue-i18n' // uni-app 中已集成,但需配置

+ 455 - 15
pages/ib/report.vue

@@ -1,23 +1,463 @@
 <template>
-    <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
-        <cwg-header :title="t('Home.page_ib.item3')" />
-        <view class="account-section">
+  <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
+    <cwg-header :title="t('Home.page_ib.item3')" />
+
+    <view class="account-content">
+      <view class="search-content">
+        <view class="search-bar">
+          <cwg-combox v-model:value="search.reportType" :options="typeList"
+                      :placeholder="t('placeholder.choose')" />
+          <cwg-combox v-if="search.reportType == 3" v-model:value="search.detail_type" :options="detailTypeList"
+                      :placeholder="t('placeholder.choose')" />
+          <view v-if="[1,3,4,7,24].includes(search.reportType)" class="report-platform">
+            <view
+              v-for="(item,index) in platformOptions"
+              :key="index"
+              class="checklist-box"
+              :class="{
+                'active': item.value === search.platform,
+              }"
+              @click="changePlatformType(item.value)"
+            >
+              {{ item.text }}
+            </view>
+          </view>
+          <cwg-combox v-if="search.reportType == 24" v-model:value="search.customType" :options="customTypeList"
+                      :placeholder="t('placeholder.choose')" />
+
+          <uni-data-picker class="agent-select" v-model="search.agentId" :localdata="agentIdOpts"
+                           :popup-title="t('State.All')" @change="handleItemChange" @nodeclick="onnodeclick"
+                           :map="{value:'value',text:'label'}"></uni-data-picker>
+
+          <view class="search-input-box">
+            <uni-easyinput v-if="['1', '2', '3', '4', '7'].includes(String(search.reportType))"
+                           v-model.trim="search.login" :placeholder="t('Label.TradingAccount')" />
+          </view>
+
+          <view class="search-input-box" v-if="search.reportType == 7">
+            <uni-easyinput v-model.trim="search.cId" placeholder="CID" />
+          </view>
+
+          <cwg-combox v-if="search.reportType == 3 || search.reportType == 7" v-model:value="search.isShort"
+                      :options="isShortList" :placeholder="t('placeholder.choose')" />
+
+          <uni-datetime-picker type="daterange" v-model="search.date"
+                               :placeholder="t('placeholder.Start') + ' - ' + t('placeholder.End')"
+                               @change="handleDateChange" />
+          <button class="search-btn" @click="toSearch">{{ t('Ib.Report.Btn') }}</button>
         </view>
-    </cwg-page-wrapper>
+        <view>
+
+        </view>
+      </view>
+
+      <cwg-tabel
+        ref="tableRef"
+        :columns="columns"
+        :mobilePrimaryFields="mobilePrimaryFields"
+        :queryParams="search"
+        :api="listApi"
+        :show-operation="false"
+        :showPagination="true"
+        :showSummary="true"
+        :summaryMethod="getSummaries"
+      >
+      </cwg-tabel>
+    </view>
+  </cwg-page-wrapper>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
-import { onLoad } from '@dcloudio/uni-app'
-import { useI18n } from 'vue-i18n' // uni-app 中已集成,但需配置
-import { customApi } from '@/service/custom'
-import { financialApi } from '@/service/financial'
-import Config from '@/config/index'
-import AddBankDialog from '@/components/AddBankDialog.vue';
-import PaymentMethodsList from './components/PaymentMethodsList.vue'
-const { t, locale } = useI18n()
-const isZh = computed(() => ['cn', 'zhHant'].includes(locale.value))
+  // 报告
+  import { ref, reactive, computed, onMounted, watch } from 'vue'
+  import { onLoad } from '@dcloudio/uni-app'
+  import { useI18n } from 'vue-i18n'
+  import Config from '@/config/index'
+  import { ibApi } from '@/service/ib'
+  import { columnList, mobileList, platformOptions, customTypeList } from '@/pages/ib/const/report'
+  import { useFilters } from '@/composables/useFilters'
+  import { isAfterJuly28 } from '@/utils/dateUtils'
+  import useUserStore from '@/stores/use-user-store'
+
+  const { numberFormat } = useFilters()
+  const { t, locale } = useI18n()
+  const lang = locale.value
+  let { Code } = Config
+  const { userInfo } = useUserStore()
+  const tableRef = ref(null)
+
+  const typeList = ref([
+    { text: t('Ib.Report.Title1'), value: 1 },
+    { text: t('Ib.Report.Title2'), value: 2 },
+    { text: t('Ib.Report.Title3'), value: 3 },
+    { text: t('Ib.Report.Title6'), value: 4 },
+    { text: t('Ib.Report.Title5'), value: 5 },
+    { text: t('news_add_field.IbReport.Title6'), value: 6 },
+    { text: t('Ib.Report.Title7'), value: 7 },
+    { text: t('Ib.Report.Title8'), value: 24 },
+  ])
+
+  const detailTypeList = ref([
+    { text: t('Ib.Report.Tit1'), value: 1 },
+    { text: t('Ib.Report.Tit2'), value: 2 },
+    { text: t('Ib.Report.Tit3'), value: 3 },
+    { text: t('Ib.Report.Tit4'), value: 4 },
+  ])
+
+  const isShortList = ref([
+    { text: t('State.All'), value: 0 },
+    { text: t('Ib.Report.item1'), value: 1 },
+    { text: t('Ib.Report.item2'), value: 2 },
+  ])
+
+  const search = ref({
+    reportType: 1,
+    detail_type: null as number | null,
+    customType: 0,
+    platform: 'MT4',
+    date: [] as string[],
+    startDate: '',
+    endDate: '',
+    agentId: '',
+    login: '',
+    cId: '',
+    isShort: 0,
+  })
+
+  const country = computed(() => {
+    return userInfo.customInfo.country
+  })
+
+  const agentIdOpts = ref<any[]>([
+    { value: 0, label: t('news_add_field.IbReport.ALL'), children: [] },
+    {
+      value: -1,
+      label: t('news_add_field.IbReport.DirectlyUnder'),
+      children: [],
+    },
+  ])
+  const toSearch = () => {
+    tableRef.value?.refreshTable()
+  }
+
+  const handleItemChange = (e: any) => {
+  }
+
+  const getSummaries = (param: any) => {
+    const { columns, summaryData } = param
+    const sums: string[] = []
+    if (!summaryData || Object.keys(summaryData).length === 0) return sums
+
+    columns.forEach((column: any, index: number) => {
+      if (index === 0) {
+        sums[index] = t("Label.Total")
+        return
+      }
+
+      const type = search.value.reportType
+      const detailType = search.value.detail_type
+
+      // 根据不同的列 prop 进行合计数据显示和格式化
+      const prop = column.prop
+      if (!prop) {
+        sums[index] = ""
+        return
+      }
+
+      if (type == 1) {
+        if (['deposit', 'withdrawal', 'credit'].includes(prop)) {
+          sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
+        } else {
+          sums[index] = ""
+        }
+      } else if (type == 2) {
+        if (['volume', 'rebateAmount', 'commissionAmount'].includes(prop)) {
+          sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
+        } else {
+          sums[index] = ""
+        }
+      } else if (type == 3) {
+        if (detailType == 4) {
+          if (['volume', 'storage', 'taxes', 'profit'].includes(prop)) {
+            sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
+          } else {
+            sums[index] = ""
+          }
+        } else {
+          if (['commission', 'volume', 'storage', 'taxes', 'profit', 'totalProfit'].includes(prop)) {
+            sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
+          } else {
+            sums[index] = ""
+          }
+        }
+      } else if (type == 4) {
+        if (['credit', 'balance', 'equity', 'floating'].includes(prop)) {
+          sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
+        } else {
+          sums[index] = ""
+        }
+      } else if (type == 6) {
+        if (prop === 'amount') {
+          sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
+        } else {
+          sums[index] = ""
+        }
+      } else if (type == 7) {
+        if (['fxVolume', 'cfdVolume', 'indexVolume', 'metalVolume'].includes(prop)) {
+          sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) : "0"
+        } else {
+          sums[index] = ""
+        }
+      } else if (type == 24) {
+        if (prop === 'volume') {
+          sums[index] = summaryData[prop] ? numberFormat(summaryData[prop]) + " /手" : "0"
+        } else if (prop === 'rebate') {
+          sums[index] = summaryData[prop] ? "$" + numberFormat(summaryData[prop]) : "0"
+        } else {
+          sums[index] = ""
+        }
+      } else {
+        sums[index] = ""
+      }
+    })
+
+    return sums
+  }
+  const initIbTree = async () => {
+    const res = await ibApi.ibTree({ pid: val })
+
+    if (res.code === Code.StatusOK) {
+      if (res.data && res.data.length > 0) {
+        res.data.forEach((item: any) => {
+          if (item.ibNo) {
+            let option = {
+              value: item.id,
+              label: item.name ? `${item.ibNo} - ${item.name}` : item.ibNo,
+              name: item.name,
+              children: [],
+            }
+            agentIdOpts.value.push(option)
+          }
+        })
+      } else {
+        uni.showToast({
+          title: res.msg, icon: 'none',
+        })
+      }
+
+    }
+  }
+
+  const onnodeclick = async (node: any) => {
+    const val = node.value
+
+    if (node.children && node.children.length > 0) {
+      return
+    }
+
+    try {
+      // uni.showLoading({ title: t('Msg.Loading') || 'Loading...', mask: true })
+      const res = await ibApi.ibTree({ pid: val })
+
+      if (res.code === Code.StatusOK) {
+        if (res.data && res.data.length > 0) {
+          const newChildren: any[] = []
+          res.data.forEach((item: any) => {
+            if (item.ibNo) {
+              newChildren.push({
+                value: item.id,
+                label: item.name ? `${item.ibNo} - ${item.name}` : item.ibNo,
+                name: item.name,
+                children: [],
+              })
+            }
+          })
+          node.children = newChildren
+        } else {
+          delete node.children
+        }
+      } else {
+        uni.showToast({
+          title: res.msg || 'Error',
+          icon: 'none',
+        })
+      }
+    } catch (error) {
+      uni.showToast({ title: 'Error', icon: 'none' })
+    } finally {
+      // uni.hideLoading()
+    }
+  }
+
+  // 表格列配置 根据types 切换
+  const columns = computed(() => {
+    if (search.value.reportType === 3) {
+      return columnList[`3_${search.value.detail_type}` as keyof typeof columnList] || []
+    }
+    return columnList[search.value.reportType as keyof typeof columnList] || []
+  })
+
+  const mobilePrimaryFields = computed(() => {
+    let list: any[] = []
+    if (search.value.reportType === 3) {
+      list = mobileList[`3_${search.value.detail_type}` as keyof typeof mobileList] || []
+    } else {
+      list = mobileList[search.value.reportType as keyof typeof mobileList] || []
+    }
+    return [
+      ...list,
+      {
+        prop: 'more',
+        type: 'more',
+        width: 20,
+        align: 'right',
+      },
+    ]
+  })
+
+  // 接口 根据types 切换(动态代理不同类型报表API)
+  const listApi = computed(() => {
+    return async (params: any) => {
+      let apiFn: any
+      const type = search.value.reportType
+      const detailType = search.value.detail_type
+
+      if (type == 1) apiFn = ibApi.tradeDw
+      else if (type == 2) apiFn = ibApi.tradeAgentCommission
+      else if (type == 3) {
+        if (detailType == 1) apiFn = ibApi.tradeHistory
+        else if (detailType == 2) apiFn = ibApi.tradePendingHistory
+        else if (detailType == 3) apiFn = ibApi.tradePending
+        else if (detailType == 4) apiFn = ibApi.tradePosition
+      } else if (type == 4) apiFn = ibApi.tradeAccount
+      else if (type == 5) apiFn = ibApi.tradeIb
+      else if (type == 6) apiFn = ibApi.ibReportBalance
+      else if (type == 7) apiFn = ibApi.tradeSymbolCategory
+      else if (type == 24) apiFn = ibApi.tradeSalesHidden
+
+      if (apiFn) {
+        if (type == 2) {
+          const isVietnam = country.value === 'VN' // 这里需要你实际的取国家逻辑
+          return await apiFn(params, isVietnam)
+        }
+        return await apiFn(params)
+      }
+      return Promise.reject('No API found')
+    }
+  })
+
+  const handleDateChange = (val: any) => {
+    if (val && val.length === 2) {
+      search.value.startDate = val[0]
+      search.value.endDate = val[1]
+    } else {
+      search.value.startDate = ''
+      search.value.endDate = ''
+    }
+  }
+
+  const changePlatformType = (type: string) => {
+    search.value.platform = type
+  }
+
+  const groupCurrency1 = (type: string) => {
+    if (type == 'GBP') return '£'
+    if (type == 'USD') return '$'
+    if (type == 'EUR') return '€'
+    if (type == 'USC') return '¢'
+    return '$'
+  }
+
+  const groupTypeName = (type: string | number) => {
+    if (type == '1') return t('AccountType.ClassicAccount')
+    if (type == '2') return t('AccountType.SeniorAccount')
+    if (type == '3') return isAfterJuly28() ? '--' : t('AccountType.AgencyAccount')
+    if (type == '5') return t('AccountType.SpeedAccount')
+    if (type == '6') return t('AccountType.SpeedAccount')
+    if (type == '7') return t('AccountType.StandardAccount')
+    if (type == '8') return t('AccountType.CentAccount')
+    return '--'
+  }
+
+  watch(() => search.value.reportType, (val) => {
+    if (val == 24) {
+      search.value.customType = 0
+      search.value.detail_type = null
+    } else if (val == 3) {
+      search.value.detail_type = 1
+    } else {
+      search.value.detail_type = null
+    }
+  })
+
+  onMounted(() => {
+    initIbTree()
+
+    // 默认当月时间
+    const now = new Date()
+    const year = now.getFullYear()
+    let month: string | number = now.getMonth() + 1
+    if (month < 10) month = '0' + month
+    const day = now.getDate()
+    let dayStr = day < 10 ? '0' + day : day.toString()
+    search.value.date = [`${year}-${month}-01`, `${year}-${month}-${dayStr}`]
+    search.value.startDate = search.value.date[0]
+    search.value.endDate = search.value.date[1]
+
+  })
 </script>
+
 <style lang="scss" scoped>
-@import "@/uni.scss";
+  @import "@/uni.scss";
+
+  .search-content {
+    display: flex;
+    justify-content: space-between;
+  }
+
+  .report-platform {
+    display: flex;
+    align-items: center;
+
+    .checklist-box {
+      cursor: pointer;
+      box-sizing: border-box;
+      padding: 0 px2rpx(20);
+      border: 1px solid #DCDFE6;
+      background-color: #f5f5f5;
+      height: px2rpx(35);
+      line-height: px2rpx(35);
+      border-radius: px2rpx(4) 0 0 px2rpx(4);
+
+      &:last-child {
+        border-radius: 0 px2rpx(4) px2rpx(4) 0;
+      }
+
+      &.active {
+        color: var(--color-white);
+        background-color: var(--color-error);
+        border-color: var(--color-error);
+      }
+    }
+  }
+
+  .search-input-box {
+    .uni-input {
+      height: px2rpx(35);
+      border: 1px solid #DCDFE6;
+      padding: 0 px2rpx(20);
+      border-radius: px2rpx(4);
+      background-color: #fff;
+    }
+  }
+
+  .agent-select {
+    width: px2rpx(240);
+  }
+
+  .search-btn {
+    height: px2rpx(36);
+    line-height: px2rpx(36);
+    margin: 0;
+  }
 </style>