wjx 1 rok pred
rodič
commit
6bcfdbabca

+ 0 - 6
config/routerConfig.ts

@@ -164,12 +164,6 @@ const gameDataStatistics = {
                     name: '游戏策略配置',
                     access: 'strategy',
                     component: './gameDataStatistics/roleOperate/strategy',
-                },
-                {
-                    path: '/gameDataStatistics/roleOperate/msgPush',
-                    name: '游戏内消息推送',
-                    access: 'msgPush',
-                    component: './gameDataStatistics/roleOperate/msgPush',
                 }
             ]
         },

+ 1 - 0
src/components/CustomList/index.tsx

@@ -304,6 +304,7 @@ function CustomListModel(props: customProps) {
         width={1100}
         className='customListModel'
         onCancel={cancel}
+        maskClosable={false}
     >
         <div className='content'>
             <div className='left'>

+ 36 - 0
src/pages/gameDataStatistics/components/TablePro/index.less

@@ -81,4 +81,40 @@
         top: 0;
         left: 0;
     }
+}
+
+
+.unfollow {
+    background-color: #fafafa;
+}
+
+.ant-table-row.expanded>.ant-table-cell {
+    padding: 5px 16px !important;
+}
+
+.ant-table-row.error {
+    background-color: #ffe9e9;
+}
+
+.ellipsisText {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    display: block;
+}
+
+.components-table-resizable-column .react-resizable {
+    position: relative;
+    background-clip: padding-box;
+}
+
+.components-table-resizable-column .react-resizable-handle {
+    position: absolute;
+    width: 10px;
+    height: 100%;
+    bottom: 0;
+    right: -9px;
+    cursor: col-resize;
+    z-index: 1;
+    border-left: white 1px solid;
 }

+ 138 - 35
src/pages/gameDataStatistics/components/TablePro/index.tsx

@@ -1,9 +1,11 @@
 import { QuestionCircleOutlined } from "@ant-design/icons";
-import { useFullscreen, useLocalStorageState, useMount, useSetState } from "ahooks";
+import { useDebounceFn, useFullscreen, useLocalStorageState, useMount, useSetState } from "ahooks";
 import { Card, Col, Row, Space, Tooltip } from "antd";
-import React, { useRef, useState } from "react"
+import React, { useCallback, useEffect, useRef, useState } from "react"
 import style from './index.less'
 import Settings from "../Settings";
+import CustomListModel from "@/components/CustomList";
+import NewTable from "./newTable";
 
 const log = (text?: any, key?: string) => {
     console.log(`pro_${key || ''}----->`, text)
@@ -11,6 +13,7 @@ const log = (text?: any, key?: string) => {
 export const DispatchContext = React.createContext<PROAPI.TableContentProps | null>(null);
 export const DispatchHeader = React.createContext<PROAPI.HeaderContentProps | null>(null);
 export const version = '1.0.0'
+const ran = Math.ceil(Math.random() * 100)
 /**
  * 升级版表格
  * @returns 
@@ -26,12 +29,20 @@ const TablePro: React.FC<PROAPI.TableProProps> = ({
     leftChild,
     size = 'small',
     className,
-    total
+    total,
+    page,
+    pageSize,
+    scroll,
+    dataSource,
+    isZj,
+    totalData = [],
+    rowSelection,
+    summary
 }) => {
 
     /*********************************************/
     const [lsDataSource, setLsDataSource] = useLocalStorageState(`myAdMonitorConfig${version}_` + configName);
-    const [lsFixed, setLsFixed] = useLocalStorageState<{ left: 0, right: 0 }>(`myAdMonitorConfigFixed${version}_` + configName);
+    const [lsFixed] = useLocalStorageState<{ left: number, right: number }>(`myAdMonitorConfigFixed${version}_` + configName);
     const [state, setState] = useSetState<PROAPI.State>({
         // 所有配置用key转Object
         configObj: {},
@@ -69,26 +80,33 @@ const TablePro: React.FC<PROAPI.TableProProps> = ({
             })
         })
         setState({ defaultColumns: newColumns, configObj: newConfigObj })
-        handleColumns(newColumns, newConfigObj)
+        handleColumns(newColumns, newConfigObj, lsDataSource, lsFixed)
     }
 
-
     // 处理columns
-    const handleColumns = (defaultColumns: any[], configObj: { [key: string]: string; }) => {
+    const handleColumns = (defaultColumns: any[], configObj: { [key: string]: string; }, lsDataSource?: any, lsFixed?: { left: number, right: number }) => {
         log(defaultColumns, 'defaultColumns')
         log(configObj, 'configObj')
+        log(lsDataSource, 'lsDataSource')
+
         // 使用默认配置
         let newColumns = defaultColumns
         let newFixed = fixed
-        log(lsFixed, 'lsFixed')
         if (lsFixed) {
             newFixed = lsFixed
         }
         if (lsDataSource) { // 找到了设置的配置
-
+            newColumns = lsDataSource
+                .filter((item: { dataIndex: string | number;  }) => !!item && configObj[item.dataIndex])
+                .map((item: { dataIndex: string | number; width: number }) => {
+                    let column = configObj[item.dataIndex]
+                    column['width'] = item.width
+                    return column
+                })
         }
+        log(newFixed, 'newFixed')
         newColumns = newColumns.map(item => ({ ...item, key: item.dataIndex }))
-        if (newFixed.left && newFixed.right) {
+        if (newFixed.left || newFixed.right) {
             for (let i = 0; i < Math.min(newFixed.left, newColumns.length); i++) {
                 newColumns[i] = { ...newColumns[i], fixed: 'left' };
             }
@@ -102,31 +120,116 @@ const TablePro: React.FC<PROAPI.TableProProps> = ({
     }
 
 
-    return <Row gutter={[0, 20]} ref={ref} style={isFull ? { background: '#fff' } : {}}>
-        <Col span={24}>
-            <Card
-                style={{ borderRadius: 8 }}
-                headStyle={{ textAlign: 'left' }}
-                bodyStyle={{ padding: '5px 10px' }}
-            >
-                {title && <div className={style.title}>
-                    <Space><span>{title}</span>{tips && <Tooltip title={tips}><span><QuestionCircleOutlined /></span></Tooltip>}</Space>
-                </div>}
-                <Row gutter={[0, 16]}>
-                    <DispatchHeader.Provider value={{ setIsFullscreen, isFullscreen, isFull, toggleFull, setVisible, ajax, czChild, leftChild }}>
-                        <Settings />
-                    </DispatchHeader.Provider>
-                    <DispatchContext.Provider value={{total}}>
-                        <Col span={24}>
-                            <div className={`${style[size]} ${className ? style[className] : ''} `}>
-                                
-                            </div>
-                        </Col>
-                    </DispatchContext.Provider>
-                </Row>
-            </Card>
-        </Col>
-    </Row>
+    useEffect(() => {
+        const contentBodyScroll = (e: any) => {
+            let el = document.querySelector(`.header_table_body_${ran} .ant-table-body`);
+            if (el) {
+                el.scrollLeft = e.target.scrollLeft
+            }
+        }
+        const headerBodyScroll = (e: any) => {
+            let el = document.querySelector(`.content_table_body_${ran} .ant-table-body`);
+            if (el) {
+                el.scrollLeft = e.target.scrollLeft
+            }
+        }
+        if (isZj) {
+            document.querySelector(`.content_table_body_${ran} .ant-table-body`)?.addEventListener('scroll', contentBodyScroll);
+            document.querySelector(`.header_table_body_${ran} .ant-table-body`)?.addEventListener('scroll', headerBodyScroll);
+        }
+        () => {
+            if (isZj) {
+                document.querySelector(`.content_table_body_${ran} .ant-table-body`)?.removeEventListener('scroll', contentBodyScroll);
+                document.querySelector(`.header_table_body_${ran} .ant-table-body`)?.removeEventListener('scroll', headerBodyScroll);
+            }
+        }
+    }, [isZj, ran])
+
+    const { run: runResize } = useDebounceFn((columns) => {
+        if (configName) {
+            let newSelectData = state.columns?.map((item, index) => {
+                item['width'] = columns[index]['width']
+                return item
+            })
+            setLsDataSource(newSelectData)
+        }
+    }, { wait: 200 });
+
+    //拖动宽度设置设置保存
+    const handelResize = useCallback((columns: any) => {
+        runResize(columns)
+    }, [configName, state.columns])
+
+    return <>
+        {/* 设置展示参数 */}
+        {visible && <CustomListModel
+            columns={state.columns}
+            sysFixed={fixed}
+            version={version}
+            config={config}
+            configName={configName}
+            visible={visible}
+            onClose={() => { setVisible(false) }}
+            onChange={(value) => {
+                if (value) {
+                    handleColumns(state.defaultColumns, state.configObj, value?.selectData, value?.fixed)
+                } else {
+                    handleColumns(state.defaultColumns, state.configObj)
+                }
+            }}
+        />}
+        <Row gutter={[0, 20]} ref={ref} style={isFull ? { background: '#fff' } : {}}>
+            <Col span={24}>
+                <Card
+                    style={{ borderRadius: 8 }}
+                    headStyle={{ textAlign: 'left' }}
+                    bodyStyle={{ padding: '5px 10px' }}
+                >
+                    {title && <div className={style.title}>
+                        <Space><span>{title}</span>{tips && <Tooltip title={tips}><span><QuestionCircleOutlined /></span></Tooltip>}</Space>
+                    </div>}
+                    <Row gutter={[0, 16]}>
+                        <DispatchHeader.Provider value={{ setIsFullscreen, isFullscreen, isFull, toggleFull, setVisible, ajax, czChild, leftChild }}>
+                            <Settings />
+                        </DispatchHeader.Provider>
+                        <DispatchContext.Provider value={{ total, page, pageSize, handelResize }}>
+                            <Col span={24}>
+                                <div className={`${style[size]} ${className ? style[className] : ''} `}>
+                                    {
+                                        isZj && <NewTable
+                                            bordered
+                                            columns={state.columns}
+                                            dataSource={totalData?.length > 0 ? [...totalData] : [{ id: 1 }]}
+                                            scroll={scroll ? isFull ? { ...scroll, y: document.body.clientHeight - 300 } : scroll : {}}
+                                            size={size}
+                                            pagination={false}
+                                            loading={ajax?.loading}
+                                            rowSelection={rowSelection}
+                                            className={`all_table header_table_body header_table_body_${ran} ${className ? className : ''}`}
+                                            sortDirections={['ascend', 'descend', null]}
+                                        // onChange={(pagination: any, filters: any, sorter: any) => onChange && onChange({ pagination, filters, sortData: sorter })}
+                                        />
+                                    }
+                                    <NewTable
+                                        showHeader={!isZj}
+                                        sortDirections={['ascend', 'descend', null]}
+                                        bordered
+                                        className={`all_table content_table_body content_table_body_${ran} ${className ? className : ''}`}
+                                        scroll={scroll ? isFull ? { ...scroll, y: document.body.clientHeight - 300 } : scroll : {}}
+                                        columns={state.columns}
+                                        dataSource={dataSource}
+                                        loading={ajax?.loading}
+                                        rowSelection={rowSelection}
+                                        summary={summary}
+                                    />
+                                </div>
+                            </Col>
+                        </DispatchContext.Provider>
+                    </Row>
+                </Card>
+            </Col>
+        </Row>
+    </>
 }
 
 export default React.memo(TablePro)

+ 68 - 24
src/pages/gameDataStatistics/components/TablePro/newTable.tsx

@@ -1,36 +1,80 @@
 import { Table, TableProps, Tag } from "antd"
-import React, { useContext } from "react"
+import React, { useContext, useEffect, useRef, useState } from "react"
 import { DispatchContext } from ".";
+import { Resizable } from "react-resizable";
+import './index.less'
 
-const NewTable: React.FC<TableProps<any>> = (props) => {
+const ResizableHeader = (props: { [x: string]: any; onResize: any; width: any }) => {
+    const { onResize, width, ...restProps } = props;
+    if (!width) {
+        return <th {...restProps} />
+    }
+    return <Resizable width={width} height={0} onResize={onResize} draggableOpts={{ enableUserSelectHack: false }}>
+        <th {...restProps} />
+    </Resizable>
+}
+
+const NewTable: React.FC<TableProps<any>> = ({ columns, ...props }) => {
 
     /************************************/
-    const { total, page: current, pageSize, pageChange } = useContext(DispatchContext)!;
+    const { total, page: current, pageSize, onPaginationChange, handelResize } = useContext(DispatchContext)!;
     let ww = document.body.clientWidth < 415
+    const colsRef = useRef<any[]>([])
+    const [cols, setCols] = useState<any>(columns)
     /************************************/
 
-    return <Table
-        rowKey={(a: any) => {
-            return (JSON.stringify(a?.id) || JSON.stringify(a?.key))
-        }}
-        {...props}
-        pagination={
-            {
-                total,//总共多少条数据,服务器给,设置后分页自动计算页数
-                current,//当前页数,需state控制配合翻页
-                pageSize,
-                defaultCurrent: 1,//默认初始的当前页数
-                defaultPageSize: 20,//默认初始的每页条数
-                pageSizeOptions: ['10', '20', '30', '40', '50', '60', '70', '80', '90', '100'],
-                showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
-                showSizeChanger: true, //手动开启条数筛选器,默认超过50条开启
-                onChange: pageChange, //点击页码或条数筛选时触发获取当前页数和每页条数
-                simple: ww ? true : false,//开启简单分页
-                hideOnSinglePage: true,//只有一页数据隐藏分页
-                showLessItems: true
-            }
+    const components = {
+        header: {
+            cell: ResizableHeader
+        }
+    }
+
+    useEffect(() => {
+        setCols(columns)
+    }, [columns])
+
+    const handleResize = (index: any) => (e: any, { size }: any) => {
+        const nextColumns = [...cols]
+        nextColumns[index] = {
+            ...nextColumns[index],
+            width: size.width
         }
-    />
+        setCols(nextColumns)
+        handelResize && handelResize(nextColumns)
+    }
+    colsRef.current = (cols || []).map((col: any, index: any) => ({
+        ...col,
+        onHeaderCell: (column: any) => ({
+            width: column.width,
+            onResize: handleResize(index)
+        })
+    }))
+
+    return <div className='components-table-resizable-column'>
+        <Table
+            rowKey={(a: any) => (JSON.stringify(a?.id) || JSON.stringify(a?.key))}
+            pagination={
+                {
+                    size: 'small',
+                    total,//总共多少条数据,服务器给,设置后分页自动计算页数
+                    current,//当前页数,需state控制配合翻页
+                    pageSize,
+                    defaultCurrent: 1,//默认初始的当前页数
+                    defaultPageSize: 20,//默认初始的每页条数
+                    pageSizeOptions: ['10', '20', '30', '40', '50', '60', '70', '80', '90', '100'],
+                    showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                    showSizeChanger: true, //手动开启条数筛选器,默认超过50条开启
+                    onChange: onPaginationChange, //点击页码或条数筛选时触发获取当前页数和每页条数
+                    simple: ww ? true : false,//开启简单分页
+                    hideOnSinglePage: true,//只有一页数据隐藏分页
+                    showLessItems: true
+                }
+            }
+            components={components}
+            columns={colsRef?.current}
+            {...props}
+        />
+    </div>
 }
 
 export default React.memo(NewTable)

+ 10 - 1
src/pages/gameDataStatistics/components/TablePro/typings.d.ts

@@ -14,6 +14,14 @@ declare namespace PROAPI {
         size?: 'small' | 'middle' | 'large',
         className?: string,//自定义class
         total?: number;
+        page?: number,
+        pageSize?: number,
+        scroll?: { x?: number, y?: number },
+        dataSource?: readonly any[]
+        isZj?: boolean,//是否查总计
+        totalData?: any[]
+        rowSelection?: TableRowSelection<any>
+        summary?: ((data: readonly any[]) => React.ReactNode)
     };
     type State = {
         columns: any[];
@@ -34,6 +42,7 @@ declare namespace PROAPI {
         total?: number;
         page?: number;
         pageSize?: number;
-        pageChange?: (page: number, pageSize?: number) => void,
+        onPaginationChange?: (page: number, pageSize?: number) => void,
+        handelResize?: (columns: any) => void
     }
 }

+ 0 - 2
src/pages/gameDataStatistics/order/index.tsx

@@ -7,8 +7,6 @@ import columns12 from "./tableConfig"
 import { getPresets } from "@/components/QueryForm/const"
 import moment from "moment"
 import Details from "./details"
-import modal from "antd/lib/modal"
-import { InputNumber, message } from "antd"
 import Back from "./back"
 
 

+ 46 - 0
src/pages/gameDataStatistics/rankingList/gamer/index.tsx

@@ -6,6 +6,7 @@ import columns12 from "./tableConfig"
 import QueryForm from "@/components/QueryForm"
 import moment from "moment"
 import { getPresetsRanking } from "@/components/QueryForm/const"
+import TablePro from "../../components/TablePro"
 
 const Gamer: React.FC = () => {
 
@@ -79,6 +80,51 @@ const Gamer: React.FC = () => {
             config={columns12()}
             configName={'玩家充值排行榜'}
         />
+
+        {/* <TablePro
+            leftChild={<QueryForm
+                initialValues={{ sourceSystem: 'ZX_ONE', rechargeDay: [moment(), moment()] }}
+                onChange={(data: any) => {
+                    console.log(data)
+                    const { rechargeDay, beginDay, endDay, regPayIntervalTime, ...par } = data
+                    let newQueryForm = JSON.parse(JSON.stringify(queryForm))
+                    newQueryForm.pageNum = 1
+                    if (rechargeDay && rechargeDay?.length === 2) {
+                        newQueryForm['beginDay'] = moment(rechargeDay[0]).format('YYYY-MM-DD')
+                        newQueryForm['endDay'] = moment(rechargeDay[1]).format('YYYY-MM-DD')
+                    } else {
+                        if (beginDay && endDay) {
+                            newQueryForm['beginDay'] = beginDay
+                            newQueryForm['endDay'] = endDay
+                        } else {
+                            delete newQueryForm['beginDay']
+                            delete newQueryForm['endDay']
+                        }
+                    }
+                    if (regPayIntervalTime?.length > 0 && (regPayIntervalTime[0] || regPayIntervalTime[1])) {
+                        newQueryForm.latestAmountUntilNowTimeMin = regPayIntervalTime[0]
+                        newQueryForm.latestAmountUntilNowTimeMax = regPayIntervalTime[1]
+                    } else {
+                        delete newQueryForm.latestAmountUntilNowTimeMin
+                        delete newQueryForm.latestAmountUntilNowTimeMax
+                    }
+                    setQueryForm({ ...newQueryForm, ...par })
+                }}
+                isSource
+                rechargeDay={{ ranges: getPresetsRanking() }}
+                isPayIntervalTime={{ tips: '最近充值时间距今的间隔时间(分)' }}
+            />}
+            config={columns12()}
+            configName={'玩家充值排行榜'}
+            fixed={{ left: 1, right: 0 }}
+            scroll={{ x: 1000, y: 600 }}
+            title='玩家充值排行榜'
+            ajax={getRechargeUserList}
+            page={getRechargeUserList?.data?.current || 1}
+            pageSize={getRechargeUserList?.data?.size || 20}
+            total={getRechargeUserList?.data?.total || 0}
+            dataSource={getRechargeUserList?.data?.records}
+        /> */}
     </div>
 }
 

+ 85 - 0
src/pages/gameDataStatistics/roleOperate/gameServer/assignUser.tsx

@@ -0,0 +1,85 @@
+import { useAjax } from "@/Hook/useAjax"
+import { getRoleUserListApi } from "@/services/gameData"
+import { gameServerAssignApi } from "@/services/gameData/roleOperate"
+import { Form, Modal, Radio, Select, message } from "antd"
+import React, { useEffect, useState } from "react"
+
+interface Props {
+    assignType: 'GAME_SERVER_ASSIGN_CUSTOMER' | 'GAME_SERVER_ASSIGN_GS'
+    initialValues?: any
+    visible?: boolean
+    onChange?: () => void
+    onClose?: () => void
+}
+/**
+ * 批量指派
+ * @returns 
+ */
+const AssignUser: React.FC<Props> = ({ visible, onChange, onClose, assignType, initialValues = {} }) => {
+
+    /***************************/
+    const [list, setList] = useState<any[]>([])
+    const [form] = Form.useForm()
+    const getRoleUserList = useAjax((params) => getRoleUserListApi(params))
+    const gameServerAssign = useAjax((params) => gameServerAssignApi(params))
+    /***************************/
+
+    useEffect(() => {
+        if (assignType) {
+            getRoleUserList.run({ authType: assignType === 'GAME_SERVER_ASSIGN_CUSTOMER' ? 'CUSTOMER' : 'GS' }).then(res => {
+                if (res) {
+                    setList(Object.keys(res)?.map(key => ({ userId: key, nickname: res[key] })))
+                }
+            })
+        } else {
+            setList([])
+        }
+    }, [assignType])
+
+    const handleOk = async () => {
+        let validate = await form.validateFields()
+        gameServerAssign.run({ ...validate, idList: initialValues.idList, assignType }).then(res => {
+            if (res) {
+                message.success('指派成功')
+                onChange?.()
+            }
+        })
+    }
+
+    return <Modal
+        title={<strong>指派{assignType === 'GAME_SERVER_ASSIGN_CUSTOMER' ? '客服' : 'GS'}</strong>}
+        visible={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+    >
+        <Form
+            name="basicAssignUser"
+            form={form}
+            autoComplete="off"
+            initialValues={initialValues}
+            layout="vertical"
+        >
+            <Form.Item
+                label={<strong>{assignType === 'GAME_SERVER_ASSIGN_CUSTOMER' ? '客服' : 'GS'}</strong>}
+                name="assignUserIdList"
+                rules={[{ required: true, message: `请选择${assignType === 'GAME_SERVER_ASSIGN_CUSTOMER' ? '客服' : 'GS'}` }]}
+            >
+                <Select
+                    loading={getRoleUserList.loading}
+                    mode="multiple"
+                    showSearch
+                    style={{ width: '100%' }}
+                    allowClear
+                    placeholder={`请选择${assignType === 'GAME_SERVER_ASSIGN_CUSTOMER' ? '客服' : 'GS'}`}
+                    filterOption={(input, option) =>
+                        (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    }
+                >
+                    {list.map((item: any) => <Select.Option value={item.userId} key={item.userId}>{item.nickname}</Select.Option>)}
+                </Select>
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(AssignUser)

+ 61 - 2
src/pages/gameDataStatistics/roleOperate/gameServer/index.tsx

@@ -11,6 +11,7 @@ import style from '../../components/TableData/index.less'
 import { PlusOutlined } from "@ant-design/icons"
 import EditModal from "./editModal"
 import UploadExcel from "./uploadExcel"
+import AssignUser from "./assignUser"
 
 let hfParmasInit = {
     gameId: "",//超父游戏id
@@ -33,6 +34,10 @@ const GameServer: React.FC = () => {
     const [visible, setVisible] = useState<boolean>(false)
     const [initialValues, setInitialValues] = useState<any>({})
     const [show, set_show] = useState(false)
+    // const [selectedRowKeys, setSelectedRowKeys] = useState<any[]>([])
+    const [assignType, setAssignType] = useState<'GAME_SERVER_ASSIGN_CUSTOMER' | 'GAME_SERVER_ASSIGN_GS'>('GAME_SERVER_ASSIGN_CUSTOMER')
+    const [assignVisible, setAssignVisible] = useState<boolean>(false)
+    const [assignData, setAssignData] = useState<any>({})
     const getGameServerList = useAjax((params) => getGameServerListApi(params))
     const delGameServer = useAjax((params) => delGameServerApi(params))
     const game_supper_list_api = useAjax(() => gameSupperListApi())//获取合服超父游戏列表
@@ -101,6 +106,13 @@ const GameServer: React.FC = () => {
         setQueryForm({ ...oldQueryFrom, ...data, pageNum: 1 })
     }
 
+    /** 指派 */
+    const handleAssign = (data: any, type: 'GAME_SERVER_ASSIGN_CUSTOMER' | 'GAME_SERVER_ASSIGN_GS') => {
+        setAssignType(type)
+        setAssignData({ idList: [data.id], assignUserIdList: type === 'GAME_SERVER_ASSIGN_CUSTOMER' ? data?.customerList?.map((item: { userId: any }) => item.userId) : data?.gsList?.map((item: { userId: any }) => item.userId) })
+        setAssignVisible(true)
+    }
+
     return <Card
         style={{ borderRadius: 8 }}
         headStyle={{ textAlign: 'left' }}
@@ -112,7 +124,7 @@ const GameServer: React.FC = () => {
 
         <Space style={{ width: '100%' }} direction="vertical" size={10}>
             <Form layout="inline" className='queryForm' initialValues={initialValues} name="basicGameServer" form={form} onFinish={onFinish}>
-                <Row gutter={[0, 6]}>
+                <Row gutter={[0, 10]}>
                     <Col><Form.Item name='serverName'>
                         <Input placeholder="区服名称" allowClear style={{ width: 140 }} />
                     </Form.Item></Col>
@@ -159,9 +171,14 @@ const GameServer: React.FC = () => {
                         <Space>
                             <Button type="primary" htmlType="submit">搜索</Button>
                             <Button onClick={() => form.resetFields()}>重置</Button>
+                        </Space>
+                    </Col>
+                    <Col span={24}>
+                        <Space>
                             <Button icon={<PlusOutlined />} type="primary" onClick={() => { setVisible(true); setInitialValues({ startTime: moment().add(90, 'd') }) }}>新增游戏区服</Button>
                             <Button icon={<PlusOutlined />} type="primary" onClick={() => { set_show(true) }}>新增合服</Button>
                             <UploadExcel gameList={game_supper_list_api?.data} onChange={() => getGameServerList.refresh()} />
+                            {/* <Button icon={<PlusOutlined />} disabled={selectedRowKeys?.length === 0} type="primary" onClick={() => {  }}>指派</Button> */}
                         </Space>
                     </Col>
                 </Row>
@@ -174,7 +191,7 @@ const GameServer: React.FC = () => {
                     sortDirections={['ascend', 'descend', null]}
                     current={queryFrom.pageNum}
                     pageSize={queryFrom.pageSize}
-                    columns={columnsPos(editGameServer, del)}
+                    columns={columnsPos(editGameServer, del, handleAssign)}
                     dataSource={getGameServerList?.data?.records}
                     scroll={{ x: 1000, y: 600 }}
                     onChange={(pagination: any, filters: any, sortData: any) => {
@@ -195,6 +212,32 @@ const GameServer: React.FC = () => {
                     total={getGameServerList?.data?.total}
                     loading={getGameServerList?.loading}
                     defaultPageSize={20}
+                // rowSelection={{
+                //     getCheckboxProps: (record: any) => ({
+                //         disabled: selectedRowKeys?.length > 0 && record.gameId !== (selectedRowKeys[0] as any).gameId
+                //     }),
+                //     selectedRowKeys: selectedRowKeys.map((item: any) => item?.id.toString()),
+                //     onSelect: (record: any, selected: boolean, selectedRows: any) => {
+                //         let newSelectedRowKeys: any[] = JSON.parse(JSON.stringify(selectedRowKeys))
+                //         if (selected) {
+                //             newSelectedRowKeys.push(record)
+                //         } else {
+                //             newSelectedRowKeys = newSelectedRowKeys.filter(item => item.id != record.id)
+                //         }
+                //         setSelectedRowKeys(newSelectedRowKeys)
+                //     },
+                //     onSelectAll: (selected: boolean, selectedRows: any, changeRows: any) => {
+                //         let newSelectedRowKeys: any[] = JSON.parse(JSON.stringify(selectedRowKeys))
+                //         let gameId = newSelectedRowKeys?.[0]?.gameId || changeRows?.[0]?.gameId
+                //         if (selected) {
+                //             newSelectedRowKeys = newSelectedRowKeys.concat(changeRows.filter((item: { gameId: any }) => item.gameId === gameId))
+                //         } else {
+                //             let changeRowsIds: any[] = changeRows.map((item: { id: any }) => item.id);
+                //             newSelectedRowKeys = newSelectedRowKeys.filter(item => !changeRowsIds.includes(item.id))
+                //         }
+                //         setSelectedRowKeys(newSelectedRowKeys)
+                //     }
+                // }}
                 />
             </div>
         </Space>
@@ -296,6 +339,22 @@ const GameServer: React.FC = () => {
                 </Col>
             </Row>
         </Modal>}
+
+        {/* 指派 */}
+        {assignVisible && <AssignUser
+            assignType={assignType}
+            initialValues={assignData}
+            visible={assignVisible}
+            onClose={() => {
+                setAssignData({})
+                setAssignVisible(false)
+            }}
+            onChange={() => {
+                setAssignData({})
+                setAssignVisible(false)
+                getGameServerList.refresh()
+            }}
+        />}
     </Card>
 }
 

+ 25 - 3
src/pages/gameDataStatistics/roleOperate/gameServer/tableConfig.tsx

@@ -1,11 +1,11 @@
 
 
 import { Row, Col, Popconfirm } from "antd"
-import { DeleteOutlined, EditOutlined } from "@ant-design/icons"
+import { BranchesOutlined, DeleteOutlined, EditOutlined } from "@ant-design/icons"
 import WidthEllipsis from "@/components/widthEllipsis"
 import React from "react"
 
-function columnsPos(editGameServer: (data: any) => void, del: (id: number) => void) {
+function columnsPos(editGameServer: (data: any) => void, del: (id: number) => void, handleAssign: (data: any, type: 'GAME_SERVER_ASSIGN_CUSTOMER' | 'GAME_SERVER_ASSIGN_GS') => void) {
 
     let newArr: any[] = [
         {
@@ -122,16 +122,38 @@ function columnsPos(editGameServer: (data: any) => void, del: (id: number) => vo
                 return <WidthEllipsis value={arr?.join()} />
             }
         },
+        {
+            title: '客服',
+            dataIndex: 'customerList',
+            key: 'customerList',
+            align: 'center',
+            width: 150,
+            render: (a: any[]) => {
+                return <WidthEllipsis value={a?.map(item => item.userName)?.toString()} />
+            }
+        },
+        {
+            title: 'GS',
+            dataIndex: 'gsList',
+            key: 'gsList',
+            align: 'center',
+            width: 150,
+            render: (a: any[]) => {
+                return <WidthEllipsis value={a?.map(item => item.userName)?.toString()} />
+            }
+        },
         {
             title: '操作',
             dataIndex: 'cz',
             key: 'cz',
             align: 'center',
-            width: 100,
+            width: 140,
             fixed: 'right',
             render: (a: string, b: any) => (
                 <Row justify='center' gutter={[10, 0]}>
                     {b?.isSourceServer ? <>
+                        <Col><a style={{ fontSize: "12px" }} onClick={() => { handleAssign(b, 'GAME_SERVER_ASSIGN_CUSTOMER') }}><BranchesOutlined /> 指派客服</a></Col>
+                        <Col><a style={{ fontSize: "12px" }} onClick={() => { handleAssign(b, 'GAME_SERVER_ASSIGN_GS') }}><BranchesOutlined /> 指派GS</a></Col>
                         <Col><a style={{ fontSize: "12px" }} onClick={() => { editGameServer(b) }}><EditOutlined /> 修改</a></Col>
                         <Col>
                             <Popconfirm

+ 0 - 111
src/pages/gameDataStatistics/roleOperate/msgPush/index.tsx

@@ -1,111 +0,0 @@
-import { Button, Card, Col, Form, Input, Row, Select, Space } from "antd"
-import React, { useState } from "react"
-import style from '../../components/TableData/index.less'
-import { PlusOutlined } from "@ant-design/icons"
-import Tables from "@/components/Tables"
-import columnsPos from "./tableConfig"
-import MsgPushModal from "./msgPushModal"
-
-/**
- * 游戏内消息推送
- * @returns 
- */
-const MsgPush: React.FC = () => {
-
-    /**************************/
-    const [initialValues, setInitialValues] = useState<any>({})
-    const [queryFrom, setQueryForm] = useState<any>({ pageNum: 1, pageSize: 20 })
-    const [visible, setVisible] = useState<boolean>(false)
-    const [form] = Form.useForm()
-    /**************************/
-
-    const onFinish = (data: any) => {
-        let oldQueryFrom = JSON.parse(JSON.stringify(queryFrom))
-        setQueryForm({ ...oldQueryFrom, ...data, pageNum: 1 })
-    }
-
-    return <Card
-        style={{ borderRadius: 8 }}
-        headStyle={{ textAlign: 'left' }}
-        bodyStyle={{ padding: '5px 10px' }}
-    >
-        <div style={{ textAlign: 'center', fontWeight: 'bold', padding: '4px 6px 6px', fontSize: 16, marginBottom: 4, position: 'relative' }}>
-            游戏内消息推送
-        </div>
-
-        <Space style={{ width: '100%' }} direction="vertical" size={10}>
-            <Form layout="inline" className='queryForm' initialValues={initialValues} name="basicGameVip" form={form} onFinish={onFinish}>
-                <Row gutter={[0, 6]}>
-                    <Col><Form.Item name='superGameId'>
-                        <Select
-                            maxTagCount={1}
-                            showSearch
-                            style={{ minWidth: 140 }}
-                            allowClear
-                            placeholder={'请选择超父游戏'}
-                            filterOption={(input, option) =>
-                                (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
-                            }
-                        >
-                            <Select.Option value={'1'}>a</Select.Option>
-                        </Select>
-                    </Form.Item></Col>
-                    <Col><Form.Item name='type'>
-                        <Input placeholder="清输入" />
-                    </Form.Item></Col>
-                    <Col>
-                        <Space>
-                            <Button type="primary" htmlType="submit">搜索</Button>
-                            <Button onClick={() => form.resetFields()}>重置</Button>
-                            <Button icon={<PlusOutlined />} type="primary" onClick={() => { setVisible(true); setInitialValues({}) }}>新增游戏内推送</Button>
-                        </Space>
-                    </Col>
-                </Row>
-            </Form>
-
-            <div className={`${style['small']}`}>
-                <Tables
-                    className={`all_table content_table_body`}
-                    bordered
-                    sortDirections={['ascend', 'descend', null]}
-                    current={queryFrom.pageNum}
-                    pageSize={queryFrom.pageSize}
-                    columns={columnsPos()}
-                    dataSource={[]}
-                    scroll={{ x: 1000, y: 600 }}
-                    onChange={(pagination: any, filters: any, sortData: any) => {
-                        let { current, pageSize } = pagination
-                        let newQueryForm = JSON.parse(JSON.stringify(queryFrom))
-                        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 })
-                    }}
-                    size="small"
-                    total={0}
-                    loading={false}
-                    defaultPageSize={20}
-                />
-            </div>
-        </Space>
-
-        {visible && <MsgPushModal
-            visible={visible}
-            onClose={() => {
-                setVisible(visible)
-
-            }}
-            onChange={() => {
-                
-            }}
-        />}
-    </Card>
-}
-
-export default MsgPush

+ 0 - 111
src/pages/gameDataStatistics/roleOperate/msgPush/tableConfig.tsx

@@ -1,111 +0,0 @@
-
-
-import { Row, Col, Popconfirm, Tag } from "antd"
-import { DeleteOutlined, EditOutlined } from "@ant-design/icons"
-import WidthEllipsis from "@/components/widthEllipsis"
-import React from "react"
-
-function columnsPos(editPack?: (data: any) => void, del?: (id: number) => void) {
-
-    let newArr: any = [
-        {
-            title: '角色',
-            dataIndex: 'superGameId',
-            key: 'superGameId',
-            align: 'center',
-            width: 60
-        },
-        {
-            title: '游戏区服',
-            dataIndex: 'superGameName',
-            key: 'superGameName',
-            align: 'center',
-            width: 90,
-            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
-        },
-        {
-            title: '子游戏',
-            dataIndex: 'type',
-            key: 'type',
-            align: 'center',
-            width: 100,
-            render: (a: number, b: any) => {
-                return { 1: <Tag color="#f50">追踪玩家</Tag>, 2: <Tag color="#2db7f5">玩家流失</Tag>, 3: <Tag color="#87d068">新用户追踪</Tag> }[a]
-            }
-        },
-        {
-            title: '角色相关数据',
-            dataIndex: 'configExplain',
-            key: 'configExplain',
-            width: 150,
-            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
-        },
-        {
-            title: '最新推送消息',
-            dataIndex: 'userNameStr',
-            key: 'userNameStr',
-            align: 'center',
-            width: 110,
-            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
-        },
-        {
-            title: '创建人',
-            dataIndex: 'createName',
-            key: 'createName',
-            align: 'center',
-            width: 80,
-            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
-        },
-        {
-            title: '创建时间',
-            dataIndex: 'createTime',
-            key: 'createTime',
-            align: 'center',
-            width: 135,
-            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
-        },
-        {
-            title: '更新人',
-            dataIndex: 'updateName',
-            key: 'updateName',
-            align: 'center',
-            width: 80,
-            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
-        },
-        {
-            title: '更新时间',
-            dataIndex: 'updateTime',
-            key: 'updateTime',
-            align: 'center',
-            width: 135,
-            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
-        },
-        {
-            title: '操作',
-            dataIndex: 'cz',
-            key: 'cz',
-            align: 'center',
-            width: 100,
-            fixed: 'right',
-            render: (a: string, b: any) => (
-                <Row justify='center' gutter={[10, 0]}>
-                    <Col><a style={{ fontSize: "12px" }} onClick={() => { editPack?.(b) }}><EditOutlined /> 修改</a></Col>
-                    <Col>
-                        <Popconfirm
-                            title="确定删除?"
-                            onConfirm={() => { del?.(b.id) }}
-                            okText="是"
-                            cancelText="否"
-                        >
-                            <a style={{ fontSize: "12px", color: 'red' }}><DeleteOutlined /> 删除</a>
-                        </Popconfirm>
-                    </Col>
-                </Row>
-            )
-        }
-    ]
-    return newArr
-}
-
-
-export default columnsPos

+ 17 - 2
src/pages/gameDataStatistics/roleOperate/roleRechargeRanking/index.tsx

@@ -12,6 +12,7 @@ import { Button, Space } from "antd"
 import RoleCz from "./roleCz"
 import Assign from "./assign"
 import ChangeLog from "./changeLog"
+import MsgPushModal from "./msgPushModal"
 
 let ajax: any = null
 const RoleRechargeRanking: React.FC = () => {
@@ -20,12 +21,13 @@ const RoleRechargeRanking: React.FC = () => {
     /**********************************/
     const [queryForm, setQueryForm] = useState<RoleRechargeRankingProps>({ pageNum: 1, pageSize: 50, sourceSystem: 'ZX_ONE', rechargeBeginDate: moment().format('YYYY-MM-DD'), rechargeEndDate: moment().format('YYYY-MM-DD') })
     const [data, setData] = useState<any[]>([])
-    const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([])
+    const [selectedRowKeys, setSelectedRowKeys] = useState<any[]>([])
     const [sendEmailvisible, setSendEmailVisible] = useState<boolean>(false)
     const [sendPackvisible, setSendPackVisible] = useState<boolean>(false)
     const [assignvisible, setAssignVisible] = useState<boolean>(false)
     const [changeLogVisible, setChangeLogVisible] = useState<boolean>(false)
     const [czvisible, setCzVisible] = useState<boolean>(false)
+    const [msgVisible, setMsgVisible] = useState<boolean>(false)
     const getRoleRechargeRankingList = useAjax((params) => getRoleRechargeRankingListApi(params))
     ajax = getRoleRechargeRankingList
     /**********************************/
@@ -34,7 +36,6 @@ const RoleRechargeRanking: React.FC = () => {
         getRoleRechargeRankingList.run(queryForm)
     }, [queryForm])
 
-
     const sendEmail = (data: any[]) => {
         setData(data)
         setSendEmailVisible(true)
@@ -92,6 +93,7 @@ const RoleRechargeRanking: React.FC = () => {
                 <Button type="primary" size="small" disabled={selectedRowKeys.length === 0} onClick={() => sendEmail(selectedRowKeys)}>批量发送邮件</Button>
                 {/* <Button type="primary" size="small" disabled={selectedRowKeys.length === 0} onClick={() => roleHandle(selectedRowKeys)}>批量角色操作</Button> */}
                 <Button type="primary" size="small" disabled={selectedRowKeys.length === 0} onClick={() => assignHandle(selectedRowKeys)}>批量指派</Button>
+                {/* <Button type="primary" size="small" disabled={selectedRowKeys.length === 0} onClick={() => setMsgVisible(true)}>游戏内消息推送</Button> */}
                 <Button type="primary" danger size="small" disabled={selectedRowKeys.length === 0} onClick={() => setSelectedRowKeys([])}>清空选择</Button>
             </Space>}
             leftChild={<QueryForm
@@ -227,6 +229,19 @@ const RoleRechargeRanking: React.FC = () => {
         {assignvisible && <Assign visible={assignvisible} data={data} onClose={() => setAssignVisible(false)} onChange={() => { setAssignVisible(false); ajax?.refresh(); setSelectedRowKeys([]) }} />}
         {/* 变更记录 */}
         {changeLogVisible && <ChangeLog data={data?.[0]} visible={changeLogVisible} onClose={() => setChangeLogVisible(false)} />}
+        {/* 游戏内消息推送 */}
+        {msgVisible && <MsgPushModal
+            roleRechargeRankingDTO={queryForm}
+            roleIds={selectedRowKeys.map(item => item.role_id)}
+            visible={msgVisible}
+            onClose={() => {
+                setMsgVisible(false)
+            }}
+            onChange={() => {
+                setMsgVisible(false)
+                setSelectedRowKeys([])
+            }}
+        />}
     </div>
 }
 

+ 51 - 28
src/pages/gameDataStatistics/roleOperate/msgPush/msgPushModal.tsx → src/pages/gameDataStatistics/roleOperate/roleRechargeRanking/msgPushModal.tsx

@@ -1,9 +1,13 @@
-import { DatePicker, Form, Input, Modal, Radio, Select, Space, TimePicker } from "antd"
+import { DatePicker, Form, Input, Modal, Radio, Select, Space, TimePicker, message } from "antd"
 import { RangePickerProps } from "antd/lib/date-picker"
 import React from "react"
 import moment from "moment"
+import { sendMsgTaskApi } from "@/services/gameData/roleOperate"
+import { useAjax } from "@/Hook/useAjax"
 
 interface Props {
+    roleRechargeRankingDTO: any
+    roleIds: any[]
     visible?: boolean
     onClose?: () => void
     onChange?: () => void
@@ -14,61 +18,68 @@ interface Props {
  * @param param0 
  * @returns 
  */
-const MsgPushModal: React.FC<Props> = ({ visible, onClose, onChange }) => {
+const MsgPushModal: React.FC<Props> = ({ visible, onClose, onChange, roleRechargeRankingDTO, roleIds }) => {
 
     /*************************/
     const [form] = Form.useForm()
-    const sendWay = Form.useWatch('sendWay', form)
-    const startDate = Form.useWatch('startDate', form);
-    const endDate = Form.useWatch('endDate', form);
+    // const sendWay = Form.useWatch('sendWay', form)
+    // const startDate = Form.useWatch('startDate', form);
+    // const endDate = Form.useWatch('endDate', form);
+    const sendMsgTask = useAjax((params) => sendMsgTaskApi(params))
     /*************************/
 
     const handleOk = async () => {
         let validate = await form.validateFields()
+        let params = { ...validate, roleRechargeRankingDTO, roleIds }
+        console.log(params)
+        sendMsgTask.run(params).then(res => {
+            if (res) {
+                message.success('提交成功')
+                onChange?.()
+            }
+        })
     }
 
-    const disabledDate: RangePickerProps['disabledDate'] = (current) => {
-        return current && current < moment().startOf('days');
-    };
+    // const disabledDate: RangePickerProps['disabledDate'] = (current) => {
+    //     return current && current < moment().startOf('days');
+    // };
 
-    const disabledStartDate: RangePickerProps['disabledDate'] = (current) => {
-        if (endDate) {
-            return current && (current < moment().startOf('days') || current > moment(endDate).endOf('days'));
-        }
-        return current && current < moment().startOf('days');
-    };
+    // const disabledStartDate: RangePickerProps['disabledDate'] = (current) => {
+    //     if (endDate) {
+    //         return current && (current < moment().startOf('days') || current > moment(endDate).endOf('days'));
+    //     }
+    //     return current && current < moment().startOf('days');
+    // };
 
-    const disabledEndDate: RangePickerProps['disabledDate'] = (current) => {
-        if (startDate) {
-            return current && current < moment(startDate).startOf('days');
-        }
-        return current && current < moment().startOf('days');
-    };
+    // const disabledEndDate: RangePickerProps['disabledDate'] = (current) => {
+    //     if (startDate) {
+    //         return current && current < moment(startDate).startOf('days');
+    //     }
+    //     return current && current < moment().startOf('days');
+    // };
 
     return <Modal
-        title='新建游戏内消息推送'
+        title='游戏内消息推送'
         visible={visible}
         onCancel={onClose}
         onOk={handleOk}
+        confirmLoading={sendMsgTask.loading}
     >
         <Form
             name="basicMsgPush"
             form={form}
-            labelCol={{ span: 4 }}
-            wrapperCol={{ span: 20 }}
             autoComplete="off"
             initialValues={{ sendWay: 0 }}
-            colon={false}
-            labelAlign="left"
+            layout="vertical"
         >
             <Form.Item
                 label={<strong>任务标题</strong>}
-                name="name"
+                name="taskName"
                 rules={[{ required: true, message: '请输入任务标题' }]}
             >
                 <Input placeholder="请输入任务标题" />
             </Form.Item>
-            <Form.Item
+            {/* <Form.Item
                 label={<strong>发送类型</strong>}
                 name="sendWay"
                 rules={[{ required: true, message: '请选择发送类型' }]}
@@ -107,7 +118,19 @@ const MsgPushModal: React.FC<Props> = ({ visible, onClose, onChange }) => {
                 >
                     <TimePicker />
                 </Form.Item>
-            </> : null}
+            </> : null} */}
+            <Form.Item
+                label={<strong>群发内容</strong>}
+                name='sendContent'
+                rules={[{ required: true, message: '请输入文本内容' }]}
+            >
+                <Input.TextArea
+                    placeholder="请输入文本内容"
+                    maxLength={500}
+                    showCount
+                    autoSize={{ minRows: 8 }}
+                />
+            </Form.Item>
         </Form>
     </Modal>
 }

+ 24 - 0
src/services/gameData/roleOperate.ts

@@ -318,6 +318,18 @@ export async function uploadExcelServerApi({ formData, ...params }: any) {
     });
 }
 
+/**
+ * 区服指派
+ * @param data 
+ * @returns 
+ */
+export async function gameServerAssignApi(data: { assignType: string, assignUserIdList: number[], idList: number[] }) {
+    return request(apiManage + '/gameServer/assign', {
+        method: 'POST',
+        data
+    });
+}
+
 /**
  * 获取合服超父游戏列表
  * @param params 
@@ -564,4 +576,16 @@ export async function modalStrategyApi(data: StrategyProps) {
         method: 'POST',
         data
     });
+}
+
+/**
+ * 游戏消息发送
+ * @param data 
+ * @returns 
+ */
+export async function sendMsgTaskApi(data: { taskName: string, sendContent: string, roleIds: any[], roleRechargeRankingDTO: any }) {
+    return request(api + `/manage/role/sendMsgTask`, {
+        method: 'POST',
+        data
+    });
 }