wjx 1 năm trước cách đây
mục cha
commit
370e350652

+ 6 - 0
config/routerConfig.ts

@@ -179,6 +179,12 @@ const gameDataStatistics = {
                     access: 'flowingWater',
                     component: './gameDataStatistics/gameData/flowingWater',
                 },
+                {
+                    path: '/gameDataStatistics/gameData/ltv',
+                    name: 'LTV',
+                    access: 'ltv',
+                    component: './gameDataStatistics/gameData/ltv',
+                },
             ]
         },
         {

+ 1 - 0
src/assets/ltv.svg

@@ -0,0 +1 @@
+<svg viewBox="64 64 896 896" focusable="false" data-icon="fund-view" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#000000" fill-opacity=".17" p-id="3525"></path><path d="M732.65152 326.4512a30.72 30.72 0 0 1 46.40768 39.97696l-2.94912 3.44064-227.00032 227.00032a30.72 30.72 0 0 1-40.01792 2.99008l-3.44064-2.99008-97.73056-97.73056-157.4912 157.45024a30.72 30.72 0 0 1-39.97696 2.99008l-3.44064-2.99008a30.72 30.72 0 0 1-2.99008-39.97696l2.99008-3.44064 179.2-179.2a30.72 30.72 0 0 1 39.97696-2.99008l3.44064 2.99008 97.73056 97.6896 205.29152-205.2096z" fill="#000000" p-id="3526"></path><path d="M754.31936 317.44a30.72 30.72 0 0 1 30.43328 26.54208l0.28672 4.17792V491.52a30.72 30.72 0 0 1-61.15328 4.17792L723.59936 491.52V378.88h-112.64a30.72 30.72 0 0 1-30.43328-26.54208l-0.28672-4.17792a30.72 30.72 0 0 1 26.54208-30.43328l4.17792-0.28672h143.36z" fill="#000000" p-id="3527"></path></svg>

+ 19 - 1
src/components/QueryForm/index.tsx

@@ -119,6 +119,8 @@ interface Props {
     isPromotionName?: boolean
     /** 是否开启 广告状态 搜索 */
     isAdStatus?: boolean
+    /** 是否开启 展示数据类型(买量,自然,总) 搜索 */
+    isUserEnterType?: boolean
 }
 /**
  * 游戏数据系统 请求参数
@@ -130,7 +132,7 @@ const QueryForm: React.FC<Props> = (props) => {
     const {
         onChange, initialValues, isSource, isAccount, isAccountId, isCompanyId, isAgentKey, isAgentName, isCpId, isCpName, isCpOrderId, isCpStatus, isCreateDay, isDevice, isGameName, isRechargeGameName, isGameId, isOrderGameId, isGameRoleId,
         isGameRoleName, isFirstRecharge, isSwitch, isMerchantNo, isOrderId, isMerchantOrderNo, isPayStatus, isPayWay, isProductName, isRegAgent, isAgentId, isPutAgent, isRegDay, isOs, isParentId, isProjectId, isProjectName, isPromotionId, isPromotionName,
-        isSysUserName, isRechargeDate, isBGGameClassify, isGameUserId, isSysUserId, isUserName, isUserId, isSelectRanking, isGameType, isConsumeDay, rechargeDay, isBeginDay, isType, isAdStatus
+        isSysUserName, isRechargeDate, isBGGameClassify, isGameUserId, isSysUserId, isUserName, isUserId, isSelectRanking, isGameType, isConsumeDay, rechargeDay, isBeginDay, isType, isAdStatus, isUserEnterType
     } = props
     const [form] = Form.useForm()
     const [accountList, setAccountList] = useState<any[]>([])
@@ -303,6 +305,22 @@ const QueryForm: React.FC<Props> = (props) => {
                     <Select.Option value="ZX_ONE">赞象</Select.Option>
                 </Select>
             </Form.Item></Col>}
+            {/* 展示数据类型 */}
+            {isUserEnterType && <Col><Form.Item name='tableTypes'>
+                <Select
+                    showSearch
+                    style={{ width: 130 }}
+                    allowClear
+                    placeholder={'展示数据类型'}
+                    filterOption={(input, option) =>
+                        (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    }
+                >
+                    <Select.Option value="buy">买量</Select.Option>
+                    <Select.Option value="nature">自然量</Select.Option>
+                    <Select.Option value="total">总量</Select.Option>
+                </Select>
+            </Form.Item></Col>}
             {/* 不同排行榜选择 */}
             {isSelectRanking && <Col><Form.Item name='dateType'>
                 <Radio.Group>

+ 4 - 4
src/global.less

@@ -89,13 +89,13 @@ select:-webkit-autofill {
 
 ::-webkit-scrollbar {
   // display: none; /* Chrome Safari */
-  width: 2px;
-  height: 2px;
+  width: 6px;
+  height: 6px;
 }
 
 ::-webkit-scrollbar-thumb {
-  border-radius: 1px;
-  -webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0);
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0);
   background-color: #ddd;
   background: rgba(0, 0, 0, 0);
 }

+ 15 - 0
src/pages/gameDataStatistics/gameData/ltv/index.less

@@ -0,0 +1,15 @@
+.dbox{
+    display: flex;
+    flex-flow: column;
+    align-items: center;
+    // padding: 10px;
+    >span{
+        display: flex;
+        justify-content: left;
+        width: 100%;
+    }
+}
+
+.tabsBottom0 .ant-tabs-nav {
+    margin-bottom: 0;
+}

+ 105 - 0
src/pages/gameDataStatistics/gameData/ltv/index.tsx

@@ -0,0 +1,105 @@
+import React, { useEffect, useState } from "react"
+import TableData from "../../components/TableData"
+import QueryForm from "@/components/QueryForm"
+import { getPresets } from "@/components/QueryForm/const"
+import moment from "moment"
+import { LtvListProps, gteLtvListApi, gteLtvTotalApi } from "@/services/gameData/game"
+import { useAjax } from "@/Hook/useAjax"
+import { Tabs } from "antd"
+import './index.less'
+import columnsUser12 from "./tableConfig"
+import columnsRole12 from "./tableConfigRole"
+
+
+const Ltv: React.FC = () => {
+
+    /************************************/
+    const [queryForm, setQueryForm] = useState<LtvListProps>({
+        tableTypes: 'buy',
+        pageNum: 1, pageSize: 50,
+        registeredBeginDate: moment().format('YYYY-MM-DD'),
+        registeredEndDate: moment().format('YYYY-MM-DD'),
+        sourceSystem: 'ZX_ONE'
+    })
+    const [totalData, setTotalData] = useState<any[]>([])
+    const [accessKey, setAccessKey] = useState<string>('1')
+
+    const gteLtvList = useAjax((params) => gteLtvListApi(params))
+    const gteLtvTotal = useAjax((params) => gteLtvTotalApi(params))
+    /************************************/
+
+    useEffect(() => {
+        gteLtvList.run(queryForm)
+        gteLtvTotal.run(queryForm).then((res: { id: number; costDate: string; beginDay: string | undefined }) => {
+            res.id = 1
+            res.costDate = '总计'
+            res.beginDay = queryForm.registeredBeginDate
+            setTotalData([res])
+        })
+    }, [queryForm])
+
+    return <div>
+        <Tabs type="card" className="tabsBottom0" accessKey={accessKey} onChange={(accessKey) => setAccessKey(accessKey)}>
+            <Tabs.TabPane tab="用户" key="1" />
+            <Tabs.TabPane tab="创角" key="2" />
+        </Tabs>
+
+        <TableData
+            leftChild={<QueryForm
+                initialValues={{ regDay: [moment(), moment()], sourceSystem: 'ZX_ONE', tableTypes: 'buy' }}
+                onChange={(data: any) => {
+                    console.log(data)
+                    const { regStartDay, regEndDay, rechargeDay, ...params } = data
+                    let newQueryForm = JSON.parse(JSON.stringify(queryForm))
+                    newQueryForm.pageNum = 1
+                    if (regStartDay && regEndDay) {
+                        newQueryForm.registeredBeginDate = regStartDay
+                        newQueryForm.registeredEndDate = regEndDay
+                    } else {
+                        delete newQueryForm.registeredBeginDate
+                        delete newQueryForm.registeredEndDate
+                    }
+                    setQueryForm({ ...newQueryForm, ...params })
+                }}
+                isSource
+                isRegDay={{ ranges: getPresets() }}
+                isGameId
+                isBGGameClassify
+                isUserEnterType
+            />}
+            isZj
+            totalData={totalData}
+            scroll={{ x: 1000, y: 600 }}
+            ajax={gteLtvList}
+            fixed={{ left: 2, right: 1 }}
+            dataSource={gteLtvList?.data?.records?.map((item: any, index: number) => ({ ...item, id: Number(queryForm.pageNum.toString() + index.toString()) }))}
+            total={gteLtvList?.data?.total}
+            page={queryForm.pageNum}
+            pageSize={queryForm.pageSize}
+            sortData={{
+                field: queryForm?.sortFiled,
+                order: queryForm?.sortType === 'asc' ? 'ascend' : 'descend'
+            }}
+            title={`LTV`}
+            onChange={(props: any) => {
+                let { pagination, sortData } = props
+                let { current, pageSize } = pagination
+                let newQueryForm = JSON.parse(JSON.stringify(queryForm))
+                if (sortData && sortData?.order) {
+                    newQueryForm['sortType'] = sortData?.order === 'ascend' ? 'asc' : 'desc'
+                    newQueryForm['sortFiled'] = sortData?.field
+                } else {
+                    delete newQueryForm['sortType']
+                    delete newQueryForm['sortFiled']
+                }
+                newQueryForm.pageNum = current
+                newQueryForm.pageSize = pageSize
+                setQueryForm({ ...newQueryForm })
+            }}
+            config={accessKey === '1' ? columnsUser12() : columnsRole12()}
+            configName={accessKey === '1' ? `LTV_USER` : 'LTV_ROL'}
+        />
+    </div>
+}
+
+export default Ltv

+ 322 - 0
src/pages/gameDataStatistics/gameData/ltv/tableConfig.tsx

@@ -0,0 +1,322 @@
+import React from "react"
+import { Statistic } from "antd"
+import { gameClassifyEnum } from "@/components/QueryForm/const"
+import WidthEllipsis from "@/components/widthEllipsis"
+
+function columnsUser12(): { label: string, fieldSHow?: { label: string, saveField: string, defaultValue: any[], data: any[] }, data: any[] }[] {
+
+    let defaultStart = 12
+    // 用户LTV
+    const userLtvDay = Array(90).fill('').map((_item: string, index: number) => {
+        let field = `userLtv${index + 1}`
+        let data = {
+            title: `用户LTV_D${index + 1}`,
+            dataIndex: field,
+            label: "用户LTV",
+            align: "center",
+            width: 70,
+            render: (a: string) => <Statistic value={a || 0} />
+        }
+        data['default'] = defaultStart + index
+        return data
+    })
+
+    // 用户LTV月
+    let defaultStartM = 102
+    let userLtvMonth = [4, 5, 6, 7, 8, 9, 10, 11].map((item, index) => {
+        let field = `userLtvM${item}`
+        let data = {
+            title: `用户LTV_M${item}`,
+            dataIndex: field,
+            label: "用户LTV",
+            align: "center",
+            width: 70,
+            render: (a: string) => <Statistic value={a || 0} />
+        }
+        data['default'] = defaultStartM + index
+        return data
+    })
+    userLtvMonth.push({
+        title: `用户LTV_Y1`,
+        dataIndex: 'userLtvY1',
+        label: "用户LTV",
+        align: "center",
+        width: 70,
+        default: 110,
+        render: (a: string) => <Statistic value={a || 0} />
+    } as any)
+    userLtvMonth.push({
+        title: `用户LTV_总`,
+        dataIndex: 'userLtvTotal',
+        label: "用户LTV",
+        align: "center",
+        width: 70,
+        default: 111,
+        render: (a: string) => <Statistic value={a || 0} />
+    } as any)
+
+
+    // 创角LTV
+    const roleLtvDay = Array(90).fill('').map((_item: string, index: number) => {
+        let field = `roleLtv${index + 1}`
+        let data = {
+            title: `创角LTV_D${index + 1}`,
+            dataIndex: field,
+            label: "创角LTV",
+            align: "center",
+            width: 70,
+            render: (a: string) => <Statistic value={a || 0} />
+        }
+        return data
+    })
+    let roleLtvMonth = [4, 5, 6, 7, 8, 9, 10, 11].map((item, index) => {
+        let field = `roleLtvM${item}`
+        let data = {
+            title: `创角LTV_M${item}`,
+            dataIndex: field,
+            label: "创角LTV",
+            align: "center",
+            width: 70,
+            render: (a: string) => <Statistic value={a || 0} />
+        }
+        return data
+    })
+    roleLtvMonth.push({
+        title: `创角LTV_Y1`,
+        dataIndex: 'roleLtvY1',
+        label: "创角LTV",
+        align: "center",
+        width: 70,
+        render: (a: string) => <Statistic value={a || 0} />
+    } as any)
+    roleLtvMonth.push({
+        title: `创角LTV_总`,
+        dataIndex: 'roleLtvTotal',
+        label: "创角LTV",
+        align: "center",
+        width: 70,
+        render: (a: string) => <Statistic value={a || 0} />
+    } as any)
+
+
+    return [
+        {
+            label: '游戏信息',
+            data: [
+                {
+                    title: '推广游戏名称', dataIndex: 'gameName', label: '游戏信息', align: 'center', width: 70,
+                    render: (a: string, b: any) => (<WidthEllipsis isCopy={b?.costDate !== '总计'} value={a} />)
+                },
+                {
+                    title: '推广游戏应用类型', dataIndex: 'gameClassify', label: '游戏信息', align: 'center', width: 80,
+                    render: (a: string) => (<WidthEllipsis value={gameClassifyEnum[a]} />)
+                }
+            ]
+        },
+        {
+            label: '时间',
+            data: [
+                {
+                    title: '日期', dataIndex: 'costDate', label: '时间', align: 'center', width: 90, default: 1,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+            ]
+        },
+        {
+            label: '消耗',
+            data: [
+                {
+                    title: '消耗', dataIndex: 'cost', label: '消耗', align: 'center', width: 90, sorter: true, default: 2,
+                    render: (a: string) => <Statistic value={a || 0} />
+                }
+            ]
+        },
+        {
+            label: '用户数据',
+            data: [
+                {
+                    title: '注册人数', dataIndex: 'regNum', label: '用户数据', align: 'center', width: 70, sorter: true, default: 3,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '注册成本', dataIndex: 'regCost', label: '用户数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                }
+            ]
+        },
+        {
+            label: '付费数据',
+            data: [
+                {
+                    title: '首日新用户充值次数', dataIndex: 'firstNewUserAmountCount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '首日新用户充值人数', dataIndex: 'firstNewUserAmountNum', label: '付费数据', align: 'center', width: 70, sorter: true, default: 4,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '首日新用户充值金额', dataIndex: 'firstNewUserAmount', label: '付费数据', align: 'center', width: 70, sorter: true, default: 5,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '老用户充值次数', dataIndex: 'oldUserCount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '老用户充值人数', dataIndex: 'oldUserNum', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '老用户充值金额', dataIndex: 'oldUserAmount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '账面充值次数', dataIndex: 'amountCount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '账面充值人数', dataIndex: 'amountNum', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '账面充值金额', dataIndex: 'amount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '新用户累计充值次数', dataIndex: 'newUserTotalAmountCount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '新用户累计充值人数', dataIndex: 'newUserTotalAmountNum', label: '付费数据', align: 'center', width: 70, sorter: true, default: 6,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '新用户累计充值金额', dataIndex: 'newUserTotalAmount', label: '付费数据', align: 'center', width: 70, sorter: true, default: 7,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '首日ROI', dataIndex: 'firstRoi', label: '付费数据', align: 'center', width: 70, sorter: true, default: 8,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '总ROI', dataIndex: 'totalRoi', label: '付费数据', align: 'center', width: 70, sorter: true, default: 11,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '首日付费率', dataIndex: 'firstAmountRate', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '至今付费率', tips: '至今付费率(总)=新用户累计充值人数/注册人数', dataIndex: 'todayAmountRate', label: '付费数据', align: 'center', width: 80, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '新用户付费比', dataIndex: 'newUserRate', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '首日客单价', dataIndex: 'firstAvgAmount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '首日充值成本', dataIndex: 'firstNewUserRechargeCost', label: '付费数据', align: 'center', width: 70, sorter: true, default: 9,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '总充值成本', dataIndex: 'totalRechargeCost', label: '付费数据', align: 'center', width: 70, sorter: true, default: 10,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '至今客单价', tips: '至今客单价(总)=新用户累计充值金额/新用户累计充值次数', dataIndex: 'todayAvgAmount', label: '付费数据', align: 'center', width: 80, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '账面客单价', dataIndex: 'avgAmount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '复充率', tips: '复充率(总)=新用户复充人数/新用户累计充值人数(新用户复充人数为累计充值次数n≥2)', dataIndex: 'userAgainRate', label: '付费数据', align: 'center', width: 80, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+
+                {
+                    title: '单日付费100+人数', dataIndex: 'hundredUserNum', label: '付费数据', align: 'center', width: 90, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '单日付费100+成本', dataIndex: 'hundredUserNumCost', label: '付费数据', align: 'center', width: 90, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '首日创角人数', dataIndex: 'firstRoleNum', label: '付费数据', align: 'center', width: 80, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '创角人数', dataIndex: 'roleNum', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '新用户累计创角人数', dataIndex: 'newUserTotalRoleNum', label: '付费数据', align: 'center', width: 85, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '首日创角人数成本', dataIndex: 'firstRoleNumCost', label: '付费数据', align: 'center', width: 80, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '创角人数成本', dataIndex: 'roleNumCost', label: '付费数据', align: 'center', width: 75, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '新用户累计创角人数成本', dataIndex: 'newUserTotalRoleNumCost', label: '付费数据', align: 'center', width: 90, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '首日创角率', dataIndex: 'firstRoleNumRate', label: '付费数据', align: 'center', width: 75, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '创角率', dataIndex: 'roleNumRate', label: '付费数据', align: 'center', width: 75, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '新用户累计创角率', dataIndex: 'newUserTotalRoleNumRate', label: '付费数据', align: 'center', width: 90, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '新增注册ARPPU', dataIndex: 'regUserArpu', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '首日付费ARPPU', dataIndex: 'firstAmountArpu', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '至今付费ARPPU', dataIndex: 'todayAmountArpu', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '账面ARPPU', dataIndex: 'amountArpu', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                }
+            ]
+        },
+        {
+            label: '用户LTV',
+            data: [
+                ...userLtvDay,
+                ...userLtvMonth
+            ]
+        },
+        {
+            label: '创角LTV',
+            data: [
+                ...roleLtvDay,
+                ...roleLtvMonth
+            ]
+        }
+    ]
+}
+
+export default columnsUser12

+ 321 - 0
src/pages/gameDataStatistics/gameData/ltv/tableConfigRole.tsx

@@ -0,0 +1,321 @@
+import React from "react"
+import { Statistic } from "antd"
+import { gameClassifyEnum } from "@/components/QueryForm/const"
+import WidthEllipsis from "@/components/widthEllipsis"
+
+function columnsRole12(): { label: string, fieldSHow?: { label: string, saveField: string, defaultValue: any[], data: any[] }, data: any[] }[] {
+
+    // 用户LTV
+    const userLtvDay = Array(90).fill('').map((_item: string, index: number) => {
+        let field = `userLtv${index + 1}`
+        let data = {
+            title: `用户LTV_D${index + 1}`,
+            dataIndex: field,
+            label: "用户LTV",
+            align: "center",
+            width: 70,
+            render: (a: string) => <Statistic value={a || 0} />
+        }
+        return data
+    })
+
+    // 用户LTV月
+    let userLtvMonth = [4, 5, 6, 7, 8, 9, 10, 11].map((item, index) => {
+        let field = `userLtvM${item}`
+        let data = {
+            title: `用户LTV_M${item}`,
+            dataIndex: field,
+            label: "用户LTV",
+            align: "center",
+            width: 70,
+            render: (a: string) => <Statistic value={a || 0} />
+        }
+        return data
+    })
+    userLtvMonth.push({
+        title: `用户LTV_Y1`,
+        dataIndex: 'userLtvY1',
+        label: "用户LTV",
+        align: "center",
+        width: 70,
+        render: (a: string) => <Statistic value={a || 0} />
+    } as any)
+    userLtvMonth.push({
+        title: `用户LTV_总`,
+        dataIndex: 'userLtvTotal',
+        label: "用户LTV",
+        align: "center",
+        width: 70,
+        render: (a: string) => <Statistic value={a || 0} />
+    } as any)
+
+    let defaultStart = 12
+    // 创角LTV
+    const roleLtvDay = Array(90).fill('').map((_item: string, index: number) => {
+        let field = `roleLtv${index + 1}`
+        let data = {
+            title: `创角LTV_D${index + 1}`,
+            dataIndex: field,
+            label: "创角LTV",
+            align: "center",
+            width: 70,
+            render: (a: string) => <Statistic value={a || 0} />
+        }
+        data['default'] = defaultStart + index
+        return data
+    })
+    let defaultStartM = 102
+    let roleLtvMonth = [4, 5, 6, 7, 8, 9, 10, 11].map((item, index) => {
+        let field = `roleLtvM${item}`
+        let data = {
+            title: `创角LTV_M${item}`,
+            dataIndex: field,
+            label: "创角LTV",
+            align: "center",
+            width: 70,
+            render: (a: string) => <Statistic value={a || 0} />
+        }
+        data['default'] = defaultStartM + index
+        return data
+    })
+    roleLtvMonth.push({
+        title: `创角LTV_Y1`,
+        dataIndex: 'roleLtvY1',
+        label: "创角LTV",
+        align: "center",
+        width: 70,
+        default: 110,
+        render: (a: string) => <Statistic value={a || 0} />
+    } as any)
+    roleLtvMonth.push({
+        title: `创角LTV_总`,
+        dataIndex: 'roleLtvTotal',
+        label: "创角LTV",
+        align: "center",
+        width: 70,
+        default: 111,
+        render: (a: string) => <Statistic value={a || 0} />
+    } as any)
+
+
+    return [
+        {
+            label: '游戏信息',
+            data: [
+                {
+                    title: '推广游戏名称', dataIndex: 'gameName', label: '游戏信息', align: 'center', width: 70,
+                    render: (a: string, b: any) => (<WidthEllipsis isCopy={b?.costDate !== '总计'} value={a} />)
+                },
+                {
+                    title: '推广游戏应用类型', dataIndex: 'gameClassify', label: '游戏信息', align: 'center', width: 80,
+                    render: (a: string) => (<WidthEllipsis value={gameClassifyEnum[a]} />)
+                }
+            ]
+        },
+        {
+            label: '时间',
+            data: [
+                {
+                    title: '日期', dataIndex: 'costDate', label: '时间', align: 'center', width: 90, default: 1,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+            ]
+        },
+        {
+            label: '消耗',
+            data: [
+                {
+                    title: '消耗', dataIndex: 'cost', label: '消耗', align: 'center', width: 90, sorter: true, default: 2,
+                    render: (a: string) => <Statistic value={a || 0} />
+                }
+            ]
+        },
+        {
+            label: '用户数据',
+            data: [
+                {
+                    title: '注册人数', dataIndex: 'regNum', label: '用户数据', align: 'center', width: 70, sorter: true, default: 3,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '注册成本', dataIndex: 'regCost', label: '用户数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                }
+            ]
+        },
+        {
+            label: '付费数据',
+            data: [
+                {
+                    title: '首日新用户充值次数', dataIndex: 'firstNewUserAmountCount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '首日新用户充值人数', dataIndex: 'firstNewUserAmountNum', label: '付费数据', align: 'center', width: 70, sorter: true, default: 4,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '首日新用户充值金额', dataIndex: 'firstNewUserAmount', label: '付费数据', align: 'center', width: 70, sorter: true, default: 5,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '老用户充值次数', dataIndex: 'oldUserCount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '老用户充值人数', dataIndex: 'oldUserNum', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '老用户充值金额', dataIndex: 'oldUserAmount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '账面充值次数', dataIndex: 'amountCount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '账面充值人数', dataIndex: 'amountNum', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '账面充值金额', dataIndex: 'amount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '新用户累计充值次数', dataIndex: 'newUserTotalAmountCount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '新用户累计充值人数', dataIndex: 'newUserTotalAmountNum', label: '付费数据', align: 'center', width: 70, sorter: true, default: 6,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '新用户累计充值金额', dataIndex: 'newUserTotalAmount', label: '付费数据', align: 'center', width: 70, sorter: true, default: 7,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '首日ROI', dataIndex: 'firstRoi', label: '付费数据', align: 'center', width: 70, sorter: true, default: 8,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '总ROI', dataIndex: 'totalRoi', label: '付费数据', align: 'center', width: 70, sorter: true, default: 11,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '首日付费率', dataIndex: 'firstAmountRate', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '至今付费率', tips: '至今付费率(总)=新用户累计充值人数/注册人数', dataIndex: 'todayAmountRate', label: '付费数据', align: 'center', width: 80, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '新用户付费比', dataIndex: 'newUserRate', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '首日客单价', dataIndex: 'firstAvgAmount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '首日充值成本', dataIndex: 'firstNewUserRechargeCost', label: '付费数据', align: 'center', width: 70, sorter: true, default: 9,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '总充值成本', dataIndex: 'totalRechargeCost', label: '付费数据', align: 'center', width: 70, sorter: true, default: 10,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '至今客单价', tips: '至今客单价(总)=新用户累计充值金额/新用户累计充值次数', dataIndex: 'todayAvgAmount', label: '付费数据', align: 'center', width: 80, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '账面客单价', dataIndex: 'avgAmount', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '复充率', tips: '复充率(总)=新用户复充人数/新用户累计充值人数(新用户复充人数为累计充值次数n≥2)', dataIndex: 'userAgainRate', label: '付费数据', align: 'center', width: 80, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+
+                {
+                    title: '单日付费100+人数', dataIndex: 'hundredUserNum', label: '付费数据', align: 'center', width: 90, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '单日付费100+成本', dataIndex: 'hundredUserNumCost', label: '付费数据', align: 'center', width: 90, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '首日创角人数', dataIndex: 'firstRoleNum', label: '付费数据', align: 'center', width: 80, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '创角人数', dataIndex: 'roleNum', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '新用户累计创角人数', dataIndex: 'newUserTotalRoleNum', label: '付费数据', align: 'center', width: 85, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '首日创角人数成本', dataIndex: 'firstRoleNumCost', label: '付费数据', align: 'center', width: 80, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '创角人数成本', dataIndex: 'roleNumCost', label: '付费数据', align: 'center', width: 75, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '新用户累计创角人数成本', dataIndex: 'newUserTotalRoleNumCost', label: '付费数据', align: 'center', width: 90, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '首日创角率', dataIndex: 'firstRoleNumRate', label: '付费数据', align: 'center', width: 75, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '创角率', dataIndex: 'roleNumRate', label: '付费数据', align: 'center', width: 75, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '新用户累计创角率', dataIndex: 'newUserTotalRoleNumRate', label: '付费数据', align: 'center', width: 90, sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={a >= 1 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '新增注册ARPPU', dataIndex: 'regUserArpu', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '首日付费ARPPU', dataIndex: 'firstAmountArpu', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '至今付费ARPPU', dataIndex: 'todayAmountArpu', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '账面ARPPU', dataIndex: 'amountArpu', label: '付费数据', align: 'center', width: 70, sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                }
+            ]
+        },
+        {
+            label: '用户LTV',
+            data: [
+                ...userLtvDay,
+                ...userLtvMonth
+            ]
+        },
+        {
+            label: '创角LTV',
+            data: [
+                ...roleLtvDay,
+                ...roleLtvMonth
+            ]
+        }
+    ]
+}
+
+export default columnsRole12

+ 31 - 0
src/services/gameData/game.ts

@@ -112,4 +112,35 @@ export async function getGameDataAgainListApi(data: GameAgainProps) {
         method: 'POST',
         data
     });
+}
+
+
+export interface LtvListProps extends Paging, SortProps {
+    tableTypes: string,   // buy nature total
+    classify?: number,
+    gameId?: number,
+    registeredBeginDate?: string,
+    registeredEndDate?: string
+}
+/**
+ * LTV数据
+ * @param data 
+ * @returns 
+ */
+export async function gteLtvListApi(data: LtvListProps) {
+    return request(wapi + `/gameData/ltv`, {
+        method: 'POST',
+        data
+    });
+}
+/**
+ * LTV总计栏
+ * @param data 
+ * @returns 
+ */
+export async function gteLtvTotalApi(data: LtvListProps) {
+    return request(wapi + `/gameData/ltv/total`, {
+        method: 'POST',
+        data
+    });
 }