wjx 8 bulan lalu
induk
melakukan
e0f14e4cce

+ 12 - 0
config/routerConfig.ts

@@ -438,12 +438,24 @@ const gsData = {
             access: 'rolePayRetained',
             component: './gsData/rolePayRetained',
         },
+        {
+            path: '/gsData/serverPayRetained',
+            name: 'GS区服付费留存',
+            access: 'serverPayRetained',
+            component: './gsData/serverPayRetained',
+        },
         {
             path: '/gsData/roleRemoveMonitor',
             name: '角色流失监控',
             access: 'roleRemoveMonitor',
             component: './gsData/roleRemoveMonitor',
         },
+        {
+            path: '/gsData/roleManage',
+            name: '游戏角色管理',
+            access: 'roleManage',
+            component: './gsData/roleManage',
+        }
     ]
 }
 

+ 18 - 1
src/components/QueryForm/indexGs.tsx

@@ -163,6 +163,8 @@ interface Props {
     isGsId?: boolean
     /** 是否开启 服务状态 搜索 */
     isServeStatus?: boolean
+    /** 是否开启 GS运营状态:1:独立运营;2:联合运营 搜索 */
+    isGsStatus?: boolean
 }
 /**
  * 游戏数据系统 请求参数
@@ -176,7 +178,7 @@ const QueryFormGS: React.FC<Props> = (props) => {
         isGameRoleName, isFirstRecharge, isPayStatus, isPayWay, isProductName, isRegAgent, isAgentId, isRegDay, isOs, isParentId, isParentIds, isServeDay,
         isRechargeDate, isBGGameClassify, isGameUserId, isSysUserId, isSysUserIds, isUserName, isUserId, isSelectRanking, isGameType, rechargeDay, isUserEnterType, isServerName, isServerId,
         payTimeDay, placeAnOrderDay, isPayIntervalTime, isActiveTypes, isNickname, isMobile, isIsRecharge, isUserStatus, isVipLevel, isRoleLevel, isCreateRoleDay, isLastActiveTime, isIsChange, isWeChatCompany, isWeChat,
-        isCustomerServerId, isOperatorId, isServerIds, isServeStatus, isRankingNum, isIsMergeServer, isIsParticipateMerge, isSuperParentGameId, isGameServerName, isServerIdUn, isSourceServerName, isRemoveGame, isRemoveGameForSystem,
+        isCustomerServerId, isOperatorId, isServerIds, isGsStatus, isServeStatus, isRankingNum, isIsMergeServer, isIsParticipateMerge, isSuperParentGameId, isGameServerName, isServerIdUn, isSourceServerName, isRemoveGame, isRemoveGameForSystem,
     } = props
     const [form] = Form.useForm()
     const parentId = Form.useWatch('parentId', form)
@@ -792,6 +794,21 @@ const QueryFormGS: React.FC<Props> = (props) => {
                 </Select>
             </Form.Item></Col>}
 
+            {isGsStatus && <Col><Form.Item name='gsStatus'>
+                <Select
+                    showSearch
+                    style={{ width: 120 }}
+                    allowClear
+                    placeholder={'GS运营状态'}
+                    filterOption={(input, option) =>
+                        (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    }
+                >
+                    <Select.Option value={1}>独立运营</Select.Option>
+                    <Select.Option value={2}>联合运营</Select.Option>
+                </Select>
+            </Form.Item></Col>}
+
             {isServeStatus && <Col><Form.Item name='serveStatus'>
                 <Select
                     showSearch

+ 98 - 0
src/pages/gsData/roleManage/index.tsx

@@ -0,0 +1,98 @@
+import { useAjax } from "@/Hook/useAjax"
+import React, { useEffect, useState } from "react"
+import columns12 from "./tableConfig"
+import TablePro from "@/pages/gameDataStatistics/components/TablePro"
+import QueryFormGS from "@/components/QueryForm/indexGs"
+import moment from "moment"
+import { getRoleManageListApi, getRoleManageProps } from "@/services/gsData"
+
+/**
+ * 游戏角色管理
+ * @returns 
+ */
+const RoleManage: React.FC = () => {
+
+    /***************************************/
+    const [queryForm, setQueryForm] = useState<getRoleManageProps>({
+        pageNum: 1,
+        pageSize: 30,
+        sourceSystem: 'ZX_ONE'
+    })
+
+    const getRoleManageList = useAjax((params) => getRoleManageListApi(params))
+    /***************************************/
+
+    useEffect(() => {
+        getRoleManageList.run(queryForm)
+    }, [queryForm])
+
+    return <div>
+        <TablePro
+            leftChild={<QueryFormGS
+                initialValues={{ sourceSystem: 'ZX_ONE', roleSource: 2 }}
+                isSource
+                isGsId
+                isServeDay={{}}
+                rechargeDay={{ placeholder: ['角色创建开始日期', '角色创建结束日期'] }}
+                isSuperParentGameId
+                isParentId
+                isServerIds
+                isServeStatus
+                isGsStatus
+                isGameRoleName
+                onChange={(data: any) => {
+                    console.log(data)
+                    const { rechargeDay, gsId, parentId, superParentGameId, serverIds, serveDay, ...params } = data
+                    let newQueryForm = JSON.parse(JSON.stringify(queryForm))
+                    newQueryForm.pageNum = 1
+                    newQueryForm.gsIdList = gsId
+                    newQueryForm.parentGameId = parentId
+                    newQueryForm.superGameId = superParentGameId
+                    newQueryForm.serverIdList = serverIds
+                    if (serveDay && serveDay?.length === 2) {
+                        newQueryForm['serveDayBegin'] = moment(serveDay[0]).format('YYYY-MM-DD')
+                        newQueryForm['serveDayEnd'] = moment(serveDay[1]).format('YYYY-MM-DD')
+                    } else {
+                        delete newQueryForm['serveDayBegin']
+                        delete newQueryForm['serveDayEnd']
+                    }
+                    if (rechargeDay && rechargeDay?.length === 2) {
+                        newQueryForm['roleCreateDayBegin'] = moment(rechargeDay[0]).format('YYYY-MM-DD')
+                        newQueryForm['roleCreateDayEnd'] = moment(rechargeDay[1]).format('YYYY-MM-DD')
+                    } else {
+                        delete newQueryForm['roleCreateDayBegin']
+                        delete newQueryForm['roleCreateDayEnd']
+                    }
+                    setQueryForm({ ...newQueryForm, ...params })
+                }}
+            />}
+            config={columns12()}
+            configName={'游戏角色管理'}
+            fixed={{ left: 4, right: 0 }}
+            scroll={{ x: 1000, y: 620 }}
+            title='游戏角色管理'
+            loading={getRoleManageList.loading}
+            ajax={getRoleManageList}
+            page={getRoleManageList?.data?.current || 1}
+            pageSize={getRoleManageList?.data?.size || 20}
+            total={getRoleManageList?.data?.total || 0}
+            dataSource={getRoleManageList?.data?.records?.map((item: any, index: number) => ({ ...item, id: Number(queryForm.pageNum.toString() + (index + '')) }))}
+            onChange={(pagination: any, _: any, sortData: any) => {
+                let { current, pageSize } = pagination
+                let newQueryForm = JSON.parse(JSON.stringify(queryForm))
+                if (sortData && sortData?.order) {
+                    newQueryForm['sortAsc'] = sortData?.order === 'ascend' ? true : false
+                    newQueryForm['sortFiled'] = sortData?.field
+                } else {
+                    delete newQueryForm['sortAsc']
+                    delete newQueryForm['sortFiled']
+                }
+                newQueryForm.pageNum = current || newQueryForm.pageNum
+                newQueryForm.pageSize = pageSize || newQueryForm.pageSize
+                setQueryForm({ ...newQueryForm })
+            }}
+        />
+    </div>
+}
+
+export default RoleManage

+ 111 - 0
src/pages/gsData/roleManage/tableConfig.tsx

@@ -0,0 +1,111 @@
+import WidthEllipsis from "@/components/widthEllipsis"
+import { Statistic, Tag } from "antd"
+import React from "react"
+
+function columns12(): { label: string, fieldSHow?: { label: string, saveField: string, defaultValue: any[], data: any[] }, data: any[] }[] {
+
+
+    return [
+        {
+            label: '基本信息',
+            data: [
+                {
+                    title: '父游戏名称', dataIndex: 'parentGameName', label: '基本信息', align: 'center', width: 85, default: 1,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '角色名称', dataIndex: 'roleName', label: '基本信息', align: 'center', width: 85, default: 2,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '角色创建时间', dataIndex: 'roleTime', label: '基本信息', align: 'center', width: 125, default: 3,
+                    render: (a: string, b: any) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '角色创角天数', dataIndex: 'roleCreateDayDiff', label: '基本信息', align: 'center', width: 60, default: 4,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: 'GS名称', dataIndex: 'gsName', label: '基本信息', align: 'center', width: 75, default: 5,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: 'GS运营状态', dataIndex: 'gsStatus', label: '基本信息', align: 'center', width: 75, default: 6,
+                    render: (a: 1 | 2) => {
+                        return a === 1 ? <Tag color="#f50" style={{ marginRight: 0 }}>独立运营</Tag> : a === 2 ? <Tag color="#2db7f5" style={{ marginRight: 0 }}>联合运营</Tag> : <Tag style={{ marginRight: 0 }}>无GS</Tag>
+                    }
+                },
+                {
+                    title: '角色所在区服名称', dataIndex: 'endServerName', label: '基本信息', align: 'center', width: 80, default: 7,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '服务时间', dataIndex: 'gsStartTime', label: '基本信息', align: 'center', width: 160, default: 8,
+                    render: (a: string, b: any) => (<WidthEllipsis value={`${a}-${b?.gsEndTime}`} />)
+                },
+                {
+                    title: '服务天数', dataIndex: 'gsServeDays', label: '基本信息', align: 'center', width: 45, default: 9,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '服务状态', dataIndex: 'serveStatus', label: '基本信息', align: 'center', width: 75, default: 10,
+                    render: (a: boolean) => {
+                        return a ? <Tag color="success" style={{ marginRight: 0 }}>服务完成</Tag> : <Tag color="processing" style={{ marginRight: 0 }}>服务中</Tag>
+                    }
+                }
+            ]
+        },
+        {
+            label: '角色数据信息',
+            data: [
+                {
+                    title: '角色VIP等级', dataIndex: 'roleVip', label: '角色数据信息', align: 'center', width: 55, sorter: true, default: 11,
+                },
+                {
+                    title: '角色游戏等级', dataIndex: 'roleLevel', label: '角色数据信息', align: 'center', width: 60, sorter: true, default: 12,
+                },
+                {
+                    title: '角色攻击力', dataIndex: 'combatNum', label: '角色数据信息', align: 'right', width: 80, sorter: true, default: 13,
+                },
+                {
+                    title: '国家', dataIndex: 'country', label: '角色数据信息', align: 'center', width: 60, sorter: true, default: 14,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '角色最近充值时间', dataIndex: 'lastOrderTime', label: '角色数据信息', align: 'center', width: 120, default: 15,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '角色最近活跃时间', dataIndex: 'activeTime', label: '角色数据信息', align: 'center', width: 120, default: 16,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '角色创角充值时间差', dataIndex: 'roleCreatePayDayDiff', label: '角色数据信息', align: 'center', width: 100, default: 17,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '角色最近充值时间距今', dataIndex: 'lastOrderDayDiff', label: '角色数据信息', align: 'center', width: 65, default: 18,
+                    render: (a: number) => (<span style={{ fontSize: 12, color: a > 5 ? 'red' : '#000' }}>{a || '--'}</span>)
+                },
+                {
+                    title: '角色最近活跃时间距今', dataIndex: 'roleActiveDayDiff', label: '角色数据信息', align: 'center', width: 65, default: 19,
+                    render: (a: number) => (<span style={{ fontSize: 12, color: a > 5 ? 'red' : '#000' }}>{a}</span>)
+                },
+                {
+                    title: '角色累计充值金额', dataIndex: 'totalAmount', label: '角色数据信息', align: 'right', width: 70, sorter: true, default: 20,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '付费次数', dataIndex: 'gsCount', label: '角色数据信息', align: 'center', width: 70, sorter: true, default: 21,
+                    render: (a: number) => a || 0
+                },
+                {
+                    title: '付费金额', dataIndex: 'gsAmount', label: '角色数据信息', align: 'right', width: 70, sorter: true, default: 22,
+                    render: (a: string) => <Statistic value={a || 0} />
+                }
+            ]
+        }
+    ]
+}
+
+export default columns12

+ 108 - 0
src/pages/gsData/serverPayRetained/index.tsx

@@ -0,0 +1,108 @@
+import { useAjax } from "@/Hook/useAjax"
+import { getServerPayRetainedApi, GetServerPayRetainedProps, getServerPayRetainedTotalApi } from "@/services/gsData"
+import React, { useEffect, useState } from "react"
+import moment from "moment"
+import TablePro from "@/pages/gameDataStatistics/components/TablePro"
+import QueryFormGS from "@/components/QueryForm/indexGs"
+import columns12 from "./tableConfig"
+
+
+/**
+ * GS区服付费留存
+ * @returns 
+ */
+const ServerPayRetained: React.FC = () => {
+
+    /***************************************/
+    const [queryForm, setQueryForm] = useState<GetServerPayRetainedProps>({
+        pageNum: 1,
+        pageSize: 30,
+        sourceSystem: 'ZX_ONE'
+    })
+    const [totalData, setTotalData] = useState<any[]>([])
+
+    const getServerPayRetainedTotal = useAjax((params) => getServerPayRetainedTotalApi(params))
+    const getServerPayRetained = useAjax((params) => getServerPayRetainedApi(params))
+    /***************************************/
+
+    useEffect(() => {
+        getServerPayRetained.run(queryForm)
+        getServerPayRetainedTotal.run(queryForm).then((res: { id: number; gsName: string, beginDay?: string }) => {
+            res.id = 1
+            res.gsName = '总计'
+            res.beginDay = queryForm.serveDayBegin || moment().subtract(366, 'days').format('YYYY-MM-DD')
+            setTotalData([res])
+        })
+    }, [queryForm])
+
+    return <div>
+        <TablePro
+            leftChild={<QueryFormGS
+                initialValues={{ sourceSystem: 'ZX_ONE', roleSource: 2 }}
+                isSource
+                isGsId
+                isServeDay={{}}
+                rechargeDay={{ placeholder: ['开服日期开始', '开服日期结束'] }}
+                isSuperParentGameId
+                isParentId
+                isServerIds
+                isServeStatus
+                isGsStatus
+                onChange={(data: any) => {
+                    console.log(data)
+                    const { rechargeDay, gsId, parentId, superParentGameId, serverIds, serveDay, ...params } = data
+                    let newQueryForm = JSON.parse(JSON.stringify(queryForm))
+                    newQueryForm.pageNum = 1
+                    newQueryForm.gsIdList = gsId
+                    newQueryForm.parentGameId = parentId
+                    newQueryForm.superGameId = superParentGameId
+                    newQueryForm.serverIdList = serverIds
+                    if (serveDay && serveDay?.length === 2) {
+                        newQueryForm['serveDayBegin'] = moment(serveDay[0]).format('YYYY-MM-DD')
+                        newQueryForm['serveDayEnd'] = moment(serveDay[1]).format('YYYY-MM-DD')
+                    } else {
+                        delete newQueryForm['serveDayBegin']
+                        delete newQueryForm['serveDayEnd']
+                    }
+                    if (rechargeDay && rechargeDay?.length === 2) {
+                        newQueryForm['serverStartBegin'] = moment(rechargeDay[0]).format('YYYY-MM-DD')
+                        newQueryForm['serverStartEnd'] = moment(rechargeDay[1]).format('YYYY-MM-DD')
+                    } else {
+                        delete newQueryForm['serverStartBegin']
+                        delete newQueryForm['serverStartEnd']
+                    }
+                    setQueryForm({ ...newQueryForm, ...params })
+                }}
+            />}
+            isZj
+            totalData={totalData}
+            config={columns12()}
+            configName={'GS区服付费留存'}
+            fixed={{ left: 4, right: 0 }}
+            scroll={{ x: 1000, y: 620 }}
+            title='GS区服付费留存'
+            loading={getServerPayRetained.loading}
+            ajax={getServerPayRetained}
+            page={getServerPayRetained?.data?.current || 1}
+            pageSize={getServerPayRetained?.data?.size || 20}
+            total={getServerPayRetained?.data?.total || 0}
+            dataSource={getServerPayRetained?.data?.records?.map((item: any, index: number) => ({ ...item, id: Number(queryForm.pageNum.toString() + (index + '')) }))}
+            onChange={(pagination: any, _: any, sortData: any) => {
+                let { current, pageSize } = pagination
+                let newQueryForm = JSON.parse(JSON.stringify(queryForm))
+                if (sortData && sortData?.order) {
+                    newQueryForm['sortAsc'] = sortData?.order === 'ascend' ? true : false
+                    newQueryForm['sortFiled'] = sortData?.field
+                } else {
+                    delete newQueryForm['sortAsc']
+                    delete newQueryForm['sortFiled']
+                }
+                newQueryForm.pageNum = current || newQueryForm.pageNum
+                newQueryForm.pageSize = pageSize || newQueryForm.pageSize
+                setQueryForm({ ...newQueryForm })
+            }}
+        />
+    </div>
+}
+
+export default ServerPayRetained

+ 227 - 0
src/pages/gsData/serverPayRetained/tableConfig.tsx

@@ -0,0 +1,227 @@
+import WidthEllipsis from "@/components/widthEllipsis"
+import { Statistic, Tag } from "antd"
+import React from "react"
+import moment from "moment"
+import style from '../../gameDataStatistics/extensionData/everyday/index.less'
+
+
+function columns12(): { label: string, fieldSHow?: { label: string, saveField: string, defaultValue: any[], data: any[] }, data: any[] }[] {
+
+
+    let defaultStart = 26
+    const Dn = Array(90).fill('').map((_item: string, index: number) => {
+        let field = `da${index + 1}`
+        let data: any = {
+            title: `D${index + 1}`,
+            dataIndex: `D${index + 1}`,
+            label: "游戏区服数据",
+            width: 140,
+            render: (a: any, b: any) => {
+                let date1 = moment()
+                if (b?.gsName === '总计') {
+                    if (b?.beginDay) {
+                        date1 = moment(b.beginDay)
+                    } else {
+                        date1 = moment()
+                    }
+                } else {
+                    date1 = moment(b.serveDayBegin)
+                }
+                let dt = moment()
+                let day = dt.diff(date1, 'day');
+                if (index <= day && b?.[field]) {
+                    let [count1, count2, count3, count4, count5, count6, count7] = b?.[field]?.split('/')
+                    return <div className={style.dbox}>
+                        <span style={{ color: '#52c41a', fontWeight: 600 }}>活跃人数:<span>{count1}</span></span>
+                        <span style={{ color: '#a0d911', fontWeight: 600 }}>付费人数:<span>{count2}</span></span>
+                        <span style={{ color: '#1677ff', fontWeight: 600 }}>付费累计人数:<span>{count3}</span></span>
+                        <span style={{ color: '#0f538a', fontWeight: 600 }}>付费金额:<span><Statistic value={count4 || 0} valueStyle={{ color: '#0f538a', fontWeight: 600 }} /></span></span>
+                        <span style={{ color: 'rgb(12,130,16)', fontWeight: 600 }}>付费率:<span>{(count5 * 100)?.toFixed(2)}%</span></span>
+                        <span style={{ color: '#ff5722', fontWeight: 600 }}>活跃留存率:<span>{(count6 * 100)?.toFixed(2)}%</span></span>
+                        <span style={{ color: '#d81b60', fontWeight: 600 }}>付费留存率:<span>{(count7 * 100)?.toFixed(2)}%</span></span>
+                    </div>
+                }
+                return '--'
+            },
+        }
+        data['default'] = defaultStart + index
+        return data
+    })
+
+    let defaultStartM = 116
+    const Mn = Array(9).fill('').map((_item: string, index: number) => {
+        let field = `m${index + 4}`
+        let data: any = {
+            title: `M${index + 4}`,
+            dataIndex: `M${index + 4}`,
+            label: "游戏区服数据",
+            width: 140,
+            render: (a: any, b: any) => {
+                let date1 = moment()
+                if (b?.gsName === '总计') {
+                    if (b?.beginDay) {
+                        date1 = moment(b.beginDay)
+                    } else {
+                        date1 = moment()
+                    }
+                } else {
+                    date1 = moment(b.serveDayBegin)
+                }
+                let dt = moment()
+                let day = dt.diff(date1, 'day');
+                if (index <= day && b?.[field]) {
+                    let [count1, count2, count3, count4, count5, count6, count7] = b?.[field]?.split('/')
+                    return <div className={style.dbox}>
+                        <span style={{ color: '#52c41a', fontWeight: 600 }}>活跃人数:<span>{count1}</span></span>
+                        <span style={{ color: '#a0d911', fontWeight: 600 }}>付费人数:<span>{count2}</span></span>
+                        <span style={{ color: '#1677ff', fontWeight: 600 }}>付费累计人数:<span>{count3}</span></span>
+                        <span style={{ color: '#0f538a', fontWeight: 600 }}>付费金额:<span><Statistic value={count4 || 0} valueStyle={{ color: '#0f538a', fontWeight: 600 }} /></span></span>
+                        <span style={{ color: 'rgb(12,130,16)', fontWeight: 600 }}>付费率:<span>{(count5 * 100)?.toFixed(2)}%</span></span>
+                        <span style={{ color: '#ff5722', fontWeight: 600 }}>活跃留存率:<span>{(count6 * 100)?.toFixed(2)}%</span></span>
+                        <span style={{ color: '#d81b60', fontWeight: 600 }}>付费留存率:<span>{(count7 * 100)?.toFixed(2)}%</span></span>
+                    </div>
+                }
+                return '--'
+            },
+        }
+        data['default'] = defaultStartM + index
+        return data
+    })
+
+    return [
+        {
+            label: '基本信息',
+            data: [
+                {
+                    title: 'GS名称', dataIndex: 'gsName', label: '基本信息', align: 'center', width: 75, default: 1,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '父游戏名称', dataIndex: 'parentGameName', label: '基本信息', align: 'center', width: 85, default: 2,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: 'GS运营状态', dataIndex: 'gsStatus', label: '基本信息', align: 'center', width: 75, default: 3,
+                    render: (a: 1 | 2, b: any) => {
+                        if (b?.gsName === '总计') {
+                            return '--'
+                        }
+                        return a === 1 ? <Tag color="#f50" style={{ marginRight: 0 }}>独立运营</Tag> : a === 2 ? <Tag color="#2db7f5" style={{ marginRight: 0 }}>联合运营</Tag> : <Tag style={{ marginRight: 0 }}>无GS</Tag>
+                    }
+                },
+                {
+                    title: '区服名称', dataIndex: 'serverName', label: '基本信息', align: 'center', width: 85, default: 4,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '区服ID', dataIndex: 'serverId', label: '基本信息', align: 'center', width: 85, default: 5,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '是否原始区服', dataIndex: 'originServer', label: '基本信息', align: 'center', width: 90, default: 6,
+                    render: (a: boolean, b: any) => {
+                        if (b?.gsName === '总计') {
+                            return '--'
+                        }
+                        return a ? <Tag color="success" style={{ marginRight: 0 }}>是</Tag> : <Tag color="error" style={{ marginRight: 0 }}>否</Tag>
+                    }
+                }
+            ]
+        },
+        {
+            label: '游戏区服信息',
+            data: [
+                {
+                    title: '开服时间', dataIndex: 'startTime', label: '游戏区服信息', align: 'center', width: 90, default: 7,
+                    render: (a: string) => (<WidthEllipsis value={`${a || '--'}`} />)
+                },
+                {
+                    title: '开服天数', dataIndex: 'startDiff', label: '游戏区服信息', align: 'center', width: 45, default: 8,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '服务时间', dataIndex: 'dt', label: '游戏区服信息', align: 'center', width: 90, default: 9,
+                    render: (a: string, b: any) => {
+                        if (b?.gsName === '总计') {
+                            return '--'
+                        }
+                        return b.startTime ? `${b.startTime}~${b?.endTime ? b?.endTime : '长期服务'}` : '--'
+                    }
+                },
+                {
+                    title: '服务天数', dataIndex: 'serveDiff', label: '游戏区服信息', align: 'center', width: 45, default: 10,
+                    render: (a: string) => (<WidthEllipsis value={a} />)
+                },
+                {
+                    title: '服务状态', dataIndex: 'serveStatus', label: '游戏区服信息', align: 'center', width: 75, default: 11,
+                    render: (a: boolean, b: any) => {
+                        if (b?.gsName === '总计' || (typeof a !== 'boolean')) {
+                            return '--'
+                        }
+                        return a ? <Tag color="success" style={{ marginRight: 0 }}>服务完成</Tag> : <Tag color="processing" style={{ marginRight: 0 }}>服务中</Tag>
+                    }
+                },
+            ]
+        },
+        {
+            label: '游戏区服数据',
+            data: [
+                {
+                    title: '总创角人数', dataIndex: 'totalRoleNum', label: '游戏区服数据', align: 'center', width: 70, sorter: true, default: 12,
+                },
+                {
+                    title: '总付费人数', dataIndex: 'totalAmountNum', label: '游戏区服数据', align: 'center', width: 70, sorter: true, default: 13,
+                },
+                {
+                    title: '总付费次数', dataIndex: 'totalAmountCount', label: '游戏区服数据', align: 'center', width: 70, sorter: true, default: 14,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '总付费金额', dataIndex: 'totalAmount', label: '游戏区服数据', align: 'right', width: 110, sorter: true, default: 15,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '总ARPPU', tips: 'ARPPU(人均付费金额)=付费金额/付费角色数量', dataIndex: 'arppu', label: '游戏区服数据', align: 'right', width: 70, sorter: true, default: 16,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '总客单价', tips: '客单价(平均每次付费金额)=付费金额/付费次数', dataIndex: 'avgAmount', label: '游戏区服数据', align: 'right', width: 75, sorter: true, default: 17,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '总付费率', tips: '付费率=付费角色数量/指派角色数量', dataIndex: 'amountRate', label: '游戏区服数据', align: 'center', width: 80, sorter: true, default: 18,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={!a ? {} : a >= 50 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '创角人数', dataIndex: 'sonRoleNum', label: '游戏区服数据', align: 'center', width: 70, sorter: true, default: 19
+                },
+                {
+                    title: '付费人数', dataIndex: 'sonServerAmountNum', label: '游戏区服数据', align: 'center', width: 65, sorter: true, default: 20,
+                },
+                {
+                    title: '付费次数', dataIndex: 'sonServerAmountCount', label: '游戏区服数据', align: 'center', width: 70, sorter: true, default: 21,
+                },
+                {
+                    title: '付费金额', dataIndex: 'sonServerAmount', label: '游戏区服数据', align: 'right', width: 100, sorter: true, default: 22,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: 'ARPPU', tips: 'ARPPU(人均付费金额)=付费金额/付费角色数量', dataIndex: 'sonArppu', label: '游戏区服数据', align: 'right', width: 70, sorter: true, default: 23,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '客单价', tips: '客单价(平均每次付费金额)=付费金额/付费次数', dataIndex: 'sonAvgAmount', label: '游戏区服数据', align: 'right', width: 75, sorter: true, default: 24,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '付费率', tips: '付费率=付费角色数量/指派角色数量', dataIndex: 'sonAmountRate', label: '游戏区服数据', align: 'center', width: 80, sorter: true, default: 25,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={!a ? {} : a >= 50 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                ...Dn,
+                ...Mn
+            ]
+        }
+    ]
+}
+
+export default columns12

+ 63 - 0
src/services/gsData/index.ts

@@ -91,4 +91,67 @@ export async function getRoleRemoveListApi(data: GetRoleRemoveProps) {
         method: 'POST',
         data
     });
+}
+
+
+export interface GetServerPayRetainedProps extends Paging, SortProps {
+    serveDayBegin?: string,  // 服务开始日期
+    serveDayEnd?: string,    // 服务结束日期
+    gsIdList?: number[],
+    gsStatus?: 1 | 2,        // GS运营状态:1:独立运营;2:联合运营
+    parentGameId?: number,
+    superGameId?: number,
+    serveStatus?: 1 | 2,     // 服务状态:1:服务中;2:服务完成
+    serverIdList?: number[]  // 角色当前所在区服id列表
+    serverStartBegin?: string, // 开服日期
+    serverStartEnd?: string, 
+}
+
+/**
+ * GS角色付费留存
+ * @param data 
+ * @returns 
+ */
+export async function getServerPayRetainedApi(data: GetServerPayRetainedProps) {
+    return request(wapi + `/game/parent/server/pay/retained/listOfPage`, {
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 总计
+ * @param data 
+ * @returns 
+ */
+export async function getServerPayRetainedTotalApi(data: GetServerPayRetainedProps) {
+    return request(wapi + `/game/parent/server/pay/retained/total`, {
+        method: 'POST',
+        data
+    });
+}
+
+export interface getRoleManageProps extends Paging, SortProps {
+    gsIdList?: number[],
+    parentGameId?: number,
+    superGameId?: number,
+    roleCreateDayBegin?: string, // 角色创建日期开始
+    roleCreateDayEnd?: string, 
+    roleName?: string,
+    serveDayBegin?: string,  // 服务开始日期
+    serveDayEnd?: string,    // 服务结束日期
+    serveStatus?: 1 | 2,     // 服务状态:1:服务中;2:服务完成
+    serverIdList?: number[]  // 角色当前所在区服id列表
+    gsStatus?: 1 | 2,        // GS运营状态:1:独立运营;2:联合运营
+}
+/**
+ * 游戏角色管理
+ * @param data 
+ * @returns 
+ */
+export async function getRoleManageListApi(data: getRoleManageProps) {
+    return request(wapi + `/game/parent/role/manage`, {
+        method: 'POST',
+        data
+    });
 }