feat(): 用户行为分析移入
parent
f2adce43ff
commit
421fa904ce
|
|
@ -13,56 +13,124 @@ import { currentBreadcrumbs } from '../../models/auth/_';
|
|||
import {page} from '../../service/warn';
|
||||
import { qryBasicWatershedDetailApi } from "../../service/preplan";
|
||||
import moment from "moment"
|
||||
import { httppost2 } from '../../utils/request';
|
||||
import { httppostAuth } from '../../utils/request';
|
||||
import apiurl from '../../service/apiurl';
|
||||
|
||||
const { Header, Content, Sider } = Layout;
|
||||
const meunObj:any = {
|
||||
'home':'基本情况',
|
||||
'fxzb':'防汛准备',
|
||||
'sxfd':'思想发动',
|
||||
'fxkhgzh':'防汛抗旱工作会',
|
||||
'fbzrh':'防办主任会',
|
||||
'fxpxb':'防汛培训班',
|
||||
'fxtj':'防汛图件',
|
||||
'zzjg':'组织机构',
|
||||
'zq':'政区',
|
||||
'gc':'工程',
|
||||
'gczx':'工程整修',
|
||||
'home':'水库一张图',
|
||||
'sq':'四全',
|
||||
'qfg':'全覆盖',
|
||||
'zcdjxx':'注册登记信息',
|
||||
'qys':'全要素',
|
||||
'kqys':'库区要素',
|
||||
'gcys':'工程要素',
|
||||
'xyys': '下游要素',
|
||||
'qth':"全天候",
|
||||
'sksq':"水库水情",
|
||||
'hdsq': "河道水情",
|
||||
'ssyq':'实时水情',
|
||||
'trsq':'土壤墒情',
|
||||
'skyh':'水库溢洪',
|
||||
'spjk':'视频监控',
|
||||
'ytygc':'已投运工程',
|
||||
'zjgc':'在建工程',
|
||||
'hdqz':'河道清障',
|
||||
'yaxb':'预案修编',
|
||||
'zqya':'政区预案',
|
||||
'ytygcya':'已投运工程预案',
|
||||
'zjgcya':'在建工程预案',
|
||||
'hsddya':'洪水调度方案',
|
||||
'qxdw':'抢险队伍',
|
||||
'qxwl':'抢险物料',
|
||||
'jczw':'检测站网',
|
||||
'yqz':'雨情站',
|
||||
'sqz':'水情站',
|
||||
'zbb':'值班表',
|
||||
'txl':'通讯录',
|
||||
'ysgzq':'雨水工灾情',
|
||||
'ssyq':'实时雨情',
|
||||
'sssq':'实时水情',
|
||||
'ssgq':'实时工情',
|
||||
'yxqk':'运行情况',
|
||||
'gcxq':'工程险情',
|
||||
'dbaq':'大坝安全监测数据',
|
||||
'sszq':'实时灾情',
|
||||
'fxdd':'防汛调度',
|
||||
'dqxsfx':'当前形势分析',
|
||||
'tqyb':'天气预报',
|
||||
'qzq':'全周期',
|
||||
'gcdsj':'工程大事记',
|
||||
'qzqda':'全周期档案',
|
||||
'sz':'四制',
|
||||
'gltx':'管理体系',
|
||||
'zzjgck':'组织机构查看',
|
||||
'zrrgl': '责任人管理',
|
||||
'pxgl':'培训管理',
|
||||
'pxjhgl':'培训计划管理',
|
||||
'pxjlgl':'培训记录管理',
|
||||
'szzf':'水政执法',
|
||||
'ajdj':'案件登记',
|
||||
'ajtj':'案件统计',
|
||||
'clyj':'处理依据',
|
||||
'jdkh':'监督考核',
|
||||
'khtj':'考核统计',
|
||||
'khrwgl':'考核任务管理',
|
||||
'khwtzg':'考核问题整改',
|
||||
'khzbgl':'考核指标管理',
|
||||
'khmbgl':'考核模版管理',
|
||||
'zdgl':'制度管理',
|
||||
'flfg':'法律法规',
|
||||
'zsk':'知识库',
|
||||
'ddfa':'调度方案库',
|
||||
'ywgz':'业务规则库',
|
||||
'gcaq':'工程安全知识库',
|
||||
'sy':'四预',
|
||||
'fhxzfx': '防洪形式',
|
||||
'tqyb': '天气预报',
|
||||
'hsyb':'洪水预报',
|
||||
'skhs':'水库洪水',
|
||||
'hdhs':'河道洪水',
|
||||
'ddjc':'调度决策',
|
||||
'yjxy':'应急响应',
|
||||
'ddzl':'调度指令',
|
||||
'videoSurveillance':'视频监控',
|
||||
'fxdp':'防汛大屏',
|
||||
'hyybjs':'洪水预报计算',
|
||||
'ybfagl':'预报方案管理',
|
||||
'csgl': '参数管理',
|
||||
'hsyj':'洪水预警',
|
||||
'yjxx':'预警信息',
|
||||
'gzpz': '规则配置',
|
||||
'hsyy':'洪水预演',
|
||||
'fxya':'防汛预案',
|
||||
'ddgc':'调度规程',
|
||||
'qxdw':'抢险队伍',
|
||||
'qxwl': '抢险物料',
|
||||
'sg':"四管",
|
||||
'xcxj':"巡查巡检",
|
||||
'xcrw':"巡查任务",
|
||||
'xjwtcl':"巡查问题处理",
|
||||
'xjxpz':"巡检项配置",
|
||||
'aqgl':"安全管理",
|
||||
'fxgkqd':"风险管控清单",
|
||||
'aqyhpc':"安全隐患排查",
|
||||
'aqjcgl':"安全检查管理",
|
||||
'aqsgdj':"安全事故登记",
|
||||
'aqjdtz':"安全鉴定台帐",
|
||||
'cxjgtz':"除险加固台帐",
|
||||
'byfz':"白蚁防治",
|
||||
'bypc':"白蚁监测",
|
||||
'byxc':"防治宣传",
|
||||
'zmjk':"闸门监控",
|
||||
'wxyh':"维修养护",
|
||||
'zbgl':"值班管理",
|
||||
'zbb':'值班表',
|
||||
'zbrz':'值班日志',
|
||||
'btbb':'报表管理',
|
||||
'sdjyrbb':'时段降雨日报表',
|
||||
'rjylnbb':'日降雨量年报表',
|
||||
'sdswbb':'时段水位日报表',
|
||||
'rjswbb':'日均水位年报表',
|
||||
'dbaq':'告警管理',
|
||||
'aigj':'AI告警',
|
||||
'gbyj':'广播预警',
|
||||
'gcaqjc':'工程安全监测',
|
||||
'bzt':'布置图',
|
||||
'gcaqfx':'工程安全分析',
|
||||
'jrx':'浸润线',
|
||||
'gcaqyj':'工程安全预警',
|
||||
'yhyj':'隐患预警',
|
||||
'yjgzpz':'预警规则配置',
|
||||
'sjtjcx':'数据统计查询',
|
||||
'sjlr':'人工监测数据录入',
|
||||
'czcx':'测值查询',
|
||||
'syjx':'渗压监测',
|
||||
'sljx':'渗流监测',
|
||||
'wyjx':'位移监测',
|
||||
'ndsytjb':'年度渗压统计表',
|
||||
'ndsltjb':'年度渗流统计表',
|
||||
'ndwytjb':'年度位移统计表',
|
||||
'szydd':'水资源调度',
|
||||
'gsnlfx':'供水能力分析',
|
||||
'diaodu':'调度记录',
|
||||
'gstjfx':'供水统计分析',
|
||||
'dxnjyzl':'典型年降雨资料',
|
||||
'skzfzl': '水库蒸发资料',
|
||||
'sys':'系统管理',
|
||||
'user':'用户管理',
|
||||
'department':'部门管理',
|
||||
'role':'角色管理',
|
||||
'menuM':'菜单管理',
|
||||
'loginLog':'登录日志',
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -113,31 +181,25 @@ const DashboardLayout: React.FC = () => {
|
|||
|
||||
|
||||
// 这个方法是统计菜单点击情况的
|
||||
// useEffect(()=>{
|
||||
// (async()=>{
|
||||
// const list = location.pathname.split('/')
|
||||
// let menu1:any = meunObj?.[list[2]]
|
||||
// let menu2:any = meunObj?.[list[3]]
|
||||
// let menu3:any = meunObj?.[list[4]]
|
||||
// if(menu1==='基本情况'){
|
||||
// menu2 = '基本情况'
|
||||
// }
|
||||
// if(menu1==='视频监控'){
|
||||
// menu2 = '视频监控'
|
||||
// }
|
||||
// if(menu1==='防汛大屏'){
|
||||
// menu2 = '防汛大屏'
|
||||
// }
|
||||
useEffect(()=>{
|
||||
(async()=>{
|
||||
const list = location.pathname.split('/')
|
||||
let menu1:any = meunObj?.[list[2]] //一级菜单
|
||||
let menu2:any = meunObj?.[list[3]] //二级菜单
|
||||
let menu3:any = meunObj?.[list[4]] //三级菜单
|
||||
if(menu1==='水库一张图'){
|
||||
menu2 = '水库一张图'
|
||||
}
|
||||
|
||||
// const res = await httppost2(apiurl.setMenu,{
|
||||
// createId:localStorage.getItem('userId'),
|
||||
// loginType:0,
|
||||
// menu1:menu1,
|
||||
// menu2:menu2,
|
||||
// menu3:menu3
|
||||
// })
|
||||
// })()
|
||||
// },[location.pathname])
|
||||
const res = await httppostAuth(apiurl.setMenu,{
|
||||
createId:localStorage.getItem('userId'),
|
||||
loginType:0,
|
||||
menu1:menu1,
|
||||
menu2:menu2,
|
||||
menu3:menu3
|
||||
})
|
||||
})()
|
||||
},[location.pathname])
|
||||
|
||||
const menuIndexes = useMemo(() => findMenu(menu, pathname), [menu, pathname]);
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,15 @@ const apiurl = {
|
|||
view:baseFileView
|
||||
},
|
||||
systemM: {
|
||||
action: {
|
||||
todayData: service_fxdd + "/userLoginLog/todayCount",
|
||||
activeCount: service_fxdd + "/userLoginLog/userCount",
|
||||
userCount: service_fxdd + "/userLoginLog/visitCount",
|
||||
hotData:service_fxdd + "/visitMenuLog/count"
|
||||
},
|
||||
yhxwrz:{
|
||||
page:service_fxdd + "/visitMenuLog/page",
|
||||
},
|
||||
userM: {
|
||||
updatePassword:service_xyt + '/system/user/profile/updatePwd'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ function request(url, options,type) {
|
|||
const opt = { ...options };
|
||||
opt.headers = opt.headers || {};
|
||||
opt.headers.Accept = 'application/json';
|
||||
// opt.headers.Authorization = "Bearer" + ' ' + localStorage.getItem('access_token');
|
||||
// opt.credentials = opt.credentials || 'include';
|
||||
|
||||
return fetch(url, opt)
|
||||
|
|
@ -398,6 +399,21 @@ export function httppost2(url, data = {}) {
|
|||
return send(url, options);
|
||||
}
|
||||
|
||||
export function httppostAuth(url, data = {}) {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'adcd': localStorage.getItem('ADCD6'),
|
||||
"gs-token": localStorage.getItem('access_token'),
|
||||
"Authorization":"Bearer" + ' ' + localStorage.getItem('access_token')
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
};
|
||||
|
||||
return send(url, options);
|
||||
}
|
||||
|
||||
export function httppost5(url, data = {}) {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
|
|
|
|||
|
|
@ -123,6 +123,8 @@ import Ywgz from './sz/ywgz'
|
|||
import Gcaq from './sz/khzbgl'
|
||||
//系统管理
|
||||
import SystemPage from './systemMange'
|
||||
import Yhxwrz from './yhxwrz'
|
||||
import Yhxwfx from './yhxwfx'
|
||||
|
||||
|
||||
const HomePage = lazy(() => import('./Home'))
|
||||
|
|
@ -304,6 +306,8 @@ const AppRouters: React.FC = () => {
|
|||
{ path: 'sys/role', element: <SystemPage src={'/mgr/home/role'}/> },
|
||||
{ path: 'sys/menuM', element: <SystemPage src={'/mgr/home/menuM'}/> },
|
||||
{ path: 'sys/loginLog', element: <SystemPage src={'/mgr/home/loginLog'}/> },
|
||||
{ path: 'sys/yhxwrz', element: <Yhxwrz /> },
|
||||
{ path: 'sys/yhxwfx', element: <Yhxwfx /> },
|
||||
],
|
||||
},
|
||||
{ path: '/login', element: <LoginPage /> },
|
||||
|
|
|
|||
|
|
@ -22,14 +22,20 @@ const Page = () => {
|
|||
2: "橙色",
|
||||
3: "红色",
|
||||
}
|
||||
const statusObj = {
|
||||
0: "未启用",
|
||||
1: "启用",
|
||||
}
|
||||
const refModal = useRef();
|
||||
const [searchVal, setSearchVal] = useState(false)
|
||||
const columns = [
|
||||
{ title: '序号', key: 'inx', dataIndex: 'inx', width: 60, align: "center" },
|
||||
{ title: '预警时间', key: 'createTime', dataIndex: 'createTime', width: 140, align: "center", },
|
||||
{ title: '规则名称', key: 'ruleName', dataIndex: 'ruleName', width: 140, align: "center", },
|
||||
{ title: '预警等级', key: 'warningLevel', dataIndex: 'warningLevel', width: 100, align: "center", render: (v) => <span>{levelObj[v]}</span> },
|
||||
{ title: '预警信息', key: 'ruleDesc', dataIndex: 'ruleDesc', width: 300, align: "center", },
|
||||
{ title: '状态', key: 'status', dataIndex: 'status', width: 100,align:"center",render: (v) => <span>{statusObj[v]}</span> },
|
||||
{ title: '规则描述', key: 'ruleDesc', dataIndex: 'ruleDesc', width: 300, align: "center", },
|
||||
{ title: '创建时间', key: 'createTime', dataIndex: 'createTime', width: 140, align: "center"},
|
||||
{ title: '创建人', key: 'createName', dataIndex: 'createName', width: 140, align: "center"},
|
||||
{
|
||||
title: '操作', key: 'operation', width: 100, fixed: 'right', align: 'center',
|
||||
render: (value, row, index) => (<CrudOpRender_text edit={true} del={true} command={(cmd) => () => command(cmd)(row)} />)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
export default function hotOption(data) {
|
||||
const result = data.map(item => {
|
||||
if (item.menu3 && item.menu3 == '布置图') item.menu2 = '';
|
||||
return{
|
||||
...item,
|
||||
menu: item.menu1 + "-" + item.menu2 + (item?.menu3 ? "-" + item.menu3 : '')
|
||||
}
|
||||
})
|
||||
const maxY = Math.ceil(Math.max(...data.map(item => item.count)))
|
||||
const minY = Math.floor(Math.min(...data.map(item => item.count)))
|
||||
return {
|
||||
grid: {
|
||||
left:"25%",
|
||||
top: "0%",
|
||||
bottom:"0%"
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
},
|
||||
calculable: true,
|
||||
xAxis: {
|
||||
type: "value",
|
||||
min:minY,
|
||||
max: maxY,
|
||||
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: "category",
|
||||
inverse: true,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
// textStyle: {
|
||||
// color: '#999'
|
||||
// },
|
||||
fontSize:14//调整坐标轴字体大小
|
||||
},
|
||||
data: result.map(item => item.menu),
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "",
|
||||
type: "bar",
|
||||
barWidth:"30%",
|
||||
label: {
|
||||
normal: {
|
||||
show: true,
|
||||
// position: "insideRight",
|
||||
},
|
||||
offset:[100,0]
|
||||
},
|
||||
itemStyle: {
|
||||
color:"#1283e3"
|
||||
},
|
||||
data: result.map(item => item.count),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
import React, { Fragment, useRef, useMemo,useEffect,useState } from 'react';
|
||||
import { Divider,Tabs,Dropdown,Space} from 'antd';
|
||||
import ToolBar from './toolbar';
|
||||
import { DownOutlined,UpOutlined } from '@ant-design/icons';
|
||||
import apiurl from '../../service/apiurl';
|
||||
import moment from 'moment';
|
||||
import { httpget2, httppost2 } from '../../utils/request';
|
||||
import userBarOption from "./userBarOption"
|
||||
import userLineOption from "./userLineOption"
|
||||
import hotOption from './hotOption';
|
||||
import ReactEcharts from 'echarts-for-react';
|
||||
import "./index.less"
|
||||
|
||||
const Page = () => {
|
||||
|
||||
const [searchVal, setSearchVal] = useState(false)
|
||||
const [todayData, setTodayData] = useState({})
|
||||
const [activeCount, setActiveCount] = useState()
|
||||
const [userCountData, setUserCountData] = useState()
|
||||
const [hotData, setHotData] = useState()
|
||||
const [tabs, setTabs] = useState({active:0})
|
||||
const [showMore, setShowMore] = useState(false)
|
||||
const [height, setHeight] = useState("100%")
|
||||
const [hotWidth, setHotWidth] = useState("100%")
|
||||
const dtoption = useMemo(() => {
|
||||
if (userCountData) {
|
||||
return userBarOption(userCountData)
|
||||
}
|
||||
}, [userCountData])
|
||||
|
||||
const lineoption = useMemo(() => {
|
||||
if (activeCount) {
|
||||
return userLineOption(activeCount)
|
||||
}
|
||||
}, [activeCount])
|
||||
|
||||
const hotoption = useMemo(() => {
|
||||
if (hotData) {
|
||||
let substrData = [];
|
||||
if (!showMore) {
|
||||
substrData = hotData.slice(0, 10)
|
||||
} else {
|
||||
substrData = hotData
|
||||
}
|
||||
setHeight(((substrData.length) * 10) + "%")
|
||||
try {
|
||||
const labels = substrData.map(item => (
|
||||
(item.menu1 || '') + '-' + (item.menu2 || '') + (item.menu3 ? '-' + item.menu3 : '')
|
||||
));
|
||||
const maxLen = Math.max(...labels.map(l => (l || '').length), 0);
|
||||
const px = Math.min(Math.max(maxLen * 12 + 600, 800), 2400);
|
||||
setHotWidth(px + 'px');
|
||||
} catch (e) {}
|
||||
return hotOption(substrData)
|
||||
}
|
||||
}, [hotData,showMore])
|
||||
|
||||
|
||||
|
||||
// 获取今日数据
|
||||
const getTodayData = async () => {
|
||||
try {
|
||||
const res = await httpget2(apiurl.systemM.action.todayData)
|
||||
setTodayData(res.data)
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 获取日活跃数
|
||||
const getActiveCount = async (params) => {
|
||||
try {
|
||||
const {data} = await httppost2(apiurl.systemM.action.activeCount,params)
|
||||
const {appList,webList} = data
|
||||
if(appList&&webList){
|
||||
setActiveCount({appList:appList,webList:webList})
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 获取前十用户活跃数
|
||||
const getUserActiveCount = async (params) => {
|
||||
try {
|
||||
const { data } = await httppost2(apiurl.systemM.action.userCount, params)
|
||||
const {appList,webList} = data
|
||||
if(appList&&webList){
|
||||
setUserCountData({appList:appList,webList:webList})
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取热点数据
|
||||
const getHotData = async (data) => {
|
||||
try {
|
||||
const res = await httppost2(apiurl.systemM.action.hotData, data)
|
||||
setHotData(res.data)
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
getTodayData()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (searchVal) {
|
||||
getActiveCount(searchVal)
|
||||
getUserActiveCount(searchVal)
|
||||
|
||||
}
|
||||
}, [searchVal])
|
||||
|
||||
useEffect(() => {
|
||||
if (searchVal && tabs) {
|
||||
const params = {
|
||||
...searchVal,
|
||||
loginType:Number(tabs?.active)
|
||||
}
|
||||
getHotData(params)
|
||||
}
|
||||
}, [searchVal,tabs])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='content-root clearFloat' style={{padding:8,paddingBottom:"0"}}>
|
||||
<div className='action-top'>
|
||||
<div className='comomn-title'>
|
||||
<img alt='' src={`${process.env.PUBLIC_URL}/assets/panelTitle.png`} />
|
||||
<span>今日数据总览</span>
|
||||
</div>
|
||||
<div className='data-panel'>
|
||||
<div className='panel-item'>
|
||||
<span className='name'>WEB端访问次数</span>
|
||||
<p className='value'>{todayData?.web1Count || '-' }</p>
|
||||
</div>
|
||||
<Divider type="vertical" style={{fontSize:100,background:"#d7d7d7"}}/>
|
||||
<div className='panel-item'>
|
||||
<span className='name'>WEB端浏览次数</span>
|
||||
<p className='value'>{todayData?.web2Count || '-' }</p>
|
||||
</div>
|
||||
<Divider type="vertical" style={{fontSize:100,background:"#d7d7d7"}} />
|
||||
<div className='panel-item'>
|
||||
<span className='name'>WEB端平均访问时长</span>
|
||||
<p className='value'>{todayData?.web3Count || '-' } <span style={{fontSize:16}}>h</span></p>
|
||||
|
||||
</div>
|
||||
<Divider type="vertical" style={{fontSize:100,background:"#d7d7d7"}} />
|
||||
<div className='panel-item'>
|
||||
<span className='name'>移动端访问次数</span>
|
||||
<p className='value'>{todayData?.app1Count || '-'}</p>
|
||||
</div>
|
||||
<Divider type="vertical" style={{fontSize:100,background:"#d7d7d7"}}/>
|
||||
<div className='panel-item'>
|
||||
<span className='name'>移动端浏览次数</span>
|
||||
<p className='value'>{todayData?.app2Count || '-'}</p>
|
||||
</div>
|
||||
<Divider type="vertical" style={{fontSize:100,background:"#d7d7d7"}}/>
|
||||
<div className='panel-item'>
|
||||
<span className='name'>移动端平均访问时长</span>
|
||||
<p className='value'>{todayData?.app3Count || '-'} <span style={{fontSize:16}}>h</span></p>
|
||||
|
||||
</div>
|
||||
<Divider type="vertical" style={{fontSize:100,background:"#d7d7d7"}} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='action-middle'>
|
||||
<ToolBar
|
||||
setSearchVal={setSearchVal}
|
||||
/>
|
||||
</div>
|
||||
<div className='action-bottom'>
|
||||
<div className='left'>
|
||||
<div className='left-top'>
|
||||
<div className='comomn-title'>
|
||||
<img alt='' src={`${process.env.PUBLIC_URL}/assets/panelTitle.png`} />
|
||||
<span>访问用户前十</span>
|
||||
</div>
|
||||
<div className='left-top-charts'>
|
||||
<ReactEcharts
|
||||
option={dtoption || {}}
|
||||
style={{ width: "100%", height: '100%' }}
|
||||
notMerge={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='left-bottom'>
|
||||
<div className='comomn-title'>
|
||||
<img alt='' src={`${process.env.PUBLIC_URL}/assets/panelTitle.png`} />
|
||||
<span>日活跃用户数</span>
|
||||
</div>
|
||||
<div className='left-bottom-charts'>
|
||||
<ReactEcharts
|
||||
option={lineoption || {}}
|
||||
style={{ width: "100%", height: '100%' }}
|
||||
notMerge={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='right'>
|
||||
<div className='right-title'>
|
||||
<div className='comomn-title'>
|
||||
<img alt='' src={`${process.env.PUBLIC_URL}/assets/panelTitle.png`} />
|
||||
<span>功能热度前十</span>
|
||||
</div>
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
onChange={(e) => { setTabs({ active: e }); setShowMore(false)}}
|
||||
items={[
|
||||
{
|
||||
label: `web端`,
|
||||
key: 0,
|
||||
children: ``,
|
||||
},
|
||||
{
|
||||
label: `移动端`,
|
||||
key: 1,
|
||||
children: ``,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div className='more'>
|
||||
<a onClick={(e) => { e.preventDefault(); setShowMore(!showMore) }}>
|
||||
<Space>
|
||||
更多
|
||||
{!showMore ? <DownOutlined /> :<UpOutlined /> }
|
||||
</Space>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className='right-charts'>
|
||||
<ReactEcharts
|
||||
option={hotoption || {}}
|
||||
style={{ width: hotWidth, height: height }}
|
||||
notMerge={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
.action-top{
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
background-color: #fff;
|
||||
|
||||
.data-panel{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
column-gap: 45px;
|
||||
align-items: center;
|
||||
.panel-item{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.name{
|
||||
color: #999999;
|
||||
font-weight: 400;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
.value{
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.action-middle{
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
margin: 10px 0;
|
||||
background-color: #fff;
|
||||
padding-left: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-bottom{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
column-gap: 10px;
|
||||
.left{
|
||||
width: 50%;
|
||||
.left-top{
|
||||
background-color: #fff;
|
||||
.left-top-charts{
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
}
|
||||
}
|
||||
.left-bottom{
|
||||
background-color: #fff;
|
||||
margin-top: 10px;
|
||||
.left-bottom-charts{
|
||||
width: 100%;
|
||||
height: 248px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right{
|
||||
width: 50%;
|
||||
background-color: #fff;
|
||||
.right-title{
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 20px;
|
||||
.ant-tabs-top > .ant-tabs-nav, .ant-tabs-bottom > .ant-tabs-nav, .ant-tabs-top > div > .ant-tabs-nav, .ant-tabs-bottom > div > .ant-tabs-nav{
|
||||
margin: 0;
|
||||
}
|
||||
.ant-tabs-top > .ant-tabs-nav::before, .ant-tabs-bottom > .ant-tabs-nav::before, .ant-tabs-top > div > .ant-tabs-nav::before, .ant-tabs-bottom > div > .ant-tabs-nav::before{
|
||||
border: none;
|
||||
}
|
||||
.more{
|
||||
position: absolute;
|
||||
right: 6%;
|
||||
top: 30%;
|
||||
|
||||
}
|
||||
}
|
||||
.right-charts{
|
||||
width: 100%;
|
||||
height: 552px;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
.comomn-title{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 10px;
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
import React, { useEffect,useState } from 'react';
|
||||
import { Form, Input, Button, DatePicker } from 'antd';
|
||||
import NormalSelect from '../../components/Form/NormalSelect';
|
||||
import moment from 'moment';
|
||||
const { RangePicker } = DatePicker;
|
||||
const ToolBar = ({ setSearchVal, onSave, storeData }) => {
|
||||
|
||||
const optionsType = [
|
||||
{
|
||||
label: "今日",
|
||||
value:1
|
||||
},
|
||||
{
|
||||
label: "近一周",
|
||||
value:2
|
||||
},
|
||||
{
|
||||
label:"近一月",
|
||||
value:3
|
||||
},
|
||||
{
|
||||
label:"近三月",
|
||||
value:4
|
||||
},
|
||||
{
|
||||
label:"近一年",
|
||||
value:5
|
||||
},
|
||||
]
|
||||
const [form] = Form.useForm();
|
||||
|
||||
|
||||
const onValuesChange = (e) => {
|
||||
switch (e.ranger) {
|
||||
case 1:
|
||||
form.setFieldValue("tm", [moment().startOf("day"), moment()])
|
||||
setSearchVal({
|
||||
stm: moment().startOf("day").format("YYYY-MM-DD 00:00:00"),
|
||||
etm: moment().format("YYYY-MM-DD 23:59:59")
|
||||
})
|
||||
break;
|
||||
case 2:
|
||||
form.setFieldValue("tm",[moment().subtract(7, 'days'),moment()])
|
||||
setSearchVal({
|
||||
stm: moment().subtract(7, 'days').format("YYYY-MM-DD 00:00:00"),
|
||||
etm: moment().format("YYYY-MM-DD 23:59:59")
|
||||
})
|
||||
break;
|
||||
case 3:
|
||||
form.setFieldValue("tm",[moment().subtract(1, 'months'),moment()])
|
||||
setSearchVal({
|
||||
stm: moment().subtract(1, 'months').format("YYYY-MM-DD 00:00:00"),
|
||||
etm: moment().format("YYYY-MM-DD 23:59:59")
|
||||
})
|
||||
break;
|
||||
case 4:
|
||||
form.setFieldValue("tm",[moment().subtract(3, 'months'),moment()])
|
||||
setSearchVal({
|
||||
stm: moment().subtract(3, 'months').format("YYYY-MM-DD 00:00:00"),
|
||||
etm: moment().format("YYYY-MM-DD 23:59:59")
|
||||
})
|
||||
break;
|
||||
case 5:
|
||||
form.setFieldValue("tm",[moment().subtract(1, 'years'),moment()])
|
||||
setSearchVal({
|
||||
stm: moment().subtract(1, 'years').format("YYYY-MM-DD 00:00:00"),
|
||||
etm: moment().format("YYYY-MM-DD 23:59:59")
|
||||
})
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldValue("tm", [moment().subtract(7, 'days'), moment()])
|
||||
setSearchVal({
|
||||
stm: moment().subtract(7, 'days').format("YYYY-MM-DD 00:00:00"),
|
||||
etm: moment().format("YYYY-MM-DD 23:59:59")
|
||||
})
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{display:'flex',justifyContent:'space-between'}}>
|
||||
<Form form={form} className='toolbarBox' layout="inline" onValuesChange={onValuesChange}>
|
||||
<Form.Item label="时间" name="tm">
|
||||
<RangePicker
|
||||
allowClear
|
||||
style={{ width: "350px" }}
|
||||
format="YYYY-MM-DD"
|
||||
onChange={e => {
|
||||
setSearchVal({
|
||||
stm: e[0].format("YYYY-MM-DD 00:00:00"),
|
||||
etm: e[1].format("YYYY-MM-DD 23:59:59")
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="常用时段" name="ranger">
|
||||
<NormalSelect
|
||||
allowClear
|
||||
style={{ width: "150px" }}
|
||||
options={optionsType}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ToolBar;
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import { rotate } from "ol/coordinate";
|
||||
|
||||
|
||||
export default function userBarOption(data) {
|
||||
const appList = data.appList.map(item => ({
|
||||
...item, appCount: item?.count
|
||||
}))
|
||||
const webList = data.webList.map(item => ({
|
||||
...item, webCount: item?.count
|
||||
}))
|
||||
const arr = [...appList,...webList]
|
||||
const compareLength = appList.length - webList.length;
|
||||
// 找出长度大的数组
|
||||
const mapArr1 = compareLength >= 0 ? appList : webList;
|
||||
// 找出长度小的数组
|
||||
const mapArr2 = compareLength < 0 ? appList : webList;
|
||||
|
||||
const maxY = Math.ceil(Math.max(...arr.map(item => item.count)))
|
||||
const minY = Math.floor(Math.min(...arr.map(item => item.count)))
|
||||
const result = mapArr1.map(item => {
|
||||
let filterData = mapArr2.find(o => item.name == o.name)
|
||||
return {
|
||||
...item,
|
||||
...filterData,
|
||||
}
|
||||
})
|
||||
return {
|
||||
grid: {
|
||||
top: "15%",
|
||||
bottom:"20%"
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
},
|
||||
legend: {
|
||||
show:true
|
||||
},
|
||||
|
||||
calculable: true,
|
||||
xAxis: [
|
||||
{
|
||||
type: "category",
|
||||
axisLabel:{
|
||||
interval:0,
|
||||
rotate:15
|
||||
},
|
||||
data:result.map(item => item.name),
|
||||
},
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
name:"访问次数",
|
||||
min:minY - 1,
|
||||
max:maxY + 1,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "WEB端",
|
||||
type: "bar",
|
||||
barWidth:"13%",
|
||||
itemStyle: {
|
||||
color:"#357efe"
|
||||
},
|
||||
data:result.map(item => item?.webCount || 0),
|
||||
},
|
||||
{
|
||||
name: "移动端",
|
||||
type: "bar",
|
||||
barWidth:"13%",
|
||||
|
||||
itemStyle: {
|
||||
color:"#62dffe"
|
||||
},
|
||||
data:result.map(item => item?.appCount || 0),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
export default function userLineOption(data) {
|
||||
const appList = data.appList.map(item => ({
|
||||
...item, appCount: item?.count
|
||||
}))
|
||||
const webList = data.webList.map(item => ({
|
||||
...item, webCount: item?.count
|
||||
}))
|
||||
const arr = [...appList,...webList]
|
||||
const compareLength = appList.length - webList.length;
|
||||
// 找出长度大的数组
|
||||
const mapArr1 = compareLength >= 0 ? appList : webList;
|
||||
// 找出长度小的数组
|
||||
const mapArr2 = compareLength < 0 ? appList : webList;
|
||||
|
||||
const maxY = Math.ceil(Math.max(...arr.map(item => item.count)))
|
||||
const minY = Math.floor(Math.min(...arr.map(item => item.count)))
|
||||
const result = mapArr1.map(item => {
|
||||
let filterData = mapArr2.find(o => item.createDate == o.createDate)
|
||||
return {
|
||||
...item,
|
||||
...filterData,
|
||||
}
|
||||
})
|
||||
return {
|
||||
grid: {
|
||||
top: "15%",
|
||||
bottom:"20%"
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
top:"0%"
|
||||
},
|
||||
|
||||
calculable: true,
|
||||
xAxis: [
|
||||
{
|
||||
type: "category",
|
||||
data: result.map(item => item.createDate),
|
||||
},
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
min:minY - 1,
|
||||
max:maxY + 1,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "WEB端",
|
||||
type: "line",
|
||||
smooth:true,
|
||||
itemStyle: {
|
||||
color:"#357efe"
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
show: true,
|
||||
position: "top",
|
||||
},
|
||||
},
|
||||
|
||||
data: result.map(item => item?.webCount || 0),
|
||||
},
|
||||
{
|
||||
name: "移动端",
|
||||
type: "line",
|
||||
smooth:true,
|
||||
itemStyle: {
|
||||
color:"#62dffe"
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
show: true,
|
||||
position: "top",
|
||||
},
|
||||
},
|
||||
|
||||
data: result.map(item => item?.appCount || 0),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import React, { Fragment, useRef, useMemo,useEffect,useState } from 'react';
|
||||
import { Table, Card, Row, Col, Divider, Empty } from 'antd';
|
||||
import apiurl from '../../service/apiurl';
|
||||
|
||||
import usePageTable from '../../components/crud/usePageTable2';
|
||||
import { paginate_noCode } from '../../components/crud/_';
|
||||
|
||||
// 页面初始默认查询参数
|
||||
const options = {
|
||||
search:{
|
||||
// year:moment().format('YYYY')
|
||||
},
|
||||
};
|
||||
const Page = () => {
|
||||
const refModal = useRef();
|
||||
const isRender = useRef(true)
|
||||
const [searchVal, setSearchVal] = useState({year:options.search.year})
|
||||
|
||||
const columns = [
|
||||
{ title: '序号', key: 'inx', dataIndex: 'inx', width: 100, align:"center" },
|
||||
{ title: '用户', key: 'name', dataIndex: 'name', width: 200 },
|
||||
{ title: '页面', key: 'menu1', dataIndex: 'menu1', width: 300, render:(i,row)=>{
|
||||
return (row.menu1?row.menu1:'')+(row.menu2?('-'+row.menu2):'')+(row.menu3?('-'+row.menu3):'')
|
||||
}},
|
||||
{ title: '时间', key: 'createTime', dataIndex: 'createTime', width: 150},
|
||||
];
|
||||
|
||||
const width = useMemo(() => columns.reduce((total, cur) => total + (cur.width), 0), [columns]);
|
||||
const { tableProps, search, refresh } = usePageTable((params)=>paginate_noCode(apiurl.systemM.yhxwrz.page,params),options);
|
||||
|
||||
useEffect(()=>{
|
||||
// if(isRender.current){
|
||||
// isRender.current = false
|
||||
// return
|
||||
// }
|
||||
|
||||
const params = {
|
||||
search: {
|
||||
...searchVal
|
||||
}
|
||||
};
|
||||
search(params)
|
||||
|
||||
},[searchVal])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='content-root clearFloat xybm' style={{paddingRight:"0",paddingBottom:"0"}}>
|
||||
<div className='AdcdTreeTableBox'>
|
||||
{/* <Card className='nonebox'>
|
||||
<ToolBar setSearchVal={setSearchVal}/>
|
||||
</Card> */}
|
||||
<div className="ant-card-body" style={{padding:"20px 0 0 0"}}>
|
||||
<Table
|
||||
columns={columns}
|
||||
rowKey="inx"
|
||||
{...tableProps}
|
||||
scroll={{ x: width, y: "calc( 100vh - 400px )" }} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
Loading…
Reference in New Issue