feat(): 用户行为分析移入

master
李神峰 2025-12-04 18:01:14 +08:00
parent f2adce43ff
commit 421fa904ce
12 changed files with 948 additions and 70 deletions

View File

@ -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':'当前形势分析',
'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]);

View File

@ -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'
}

View File

@ -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',

View File

@ -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 /> },

View File

@ -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)} />)

View File

@ -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),
},
],
}
}

254
src/views/yhxwfx/index.js Normal file
View File

@ -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 || '-' }&nbsp;<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 || '-'}&nbsp;<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;

View File

@ -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;
}

115
src/views/yhxwfx/toolbar.js Normal file
View File

@ -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;

View File

@ -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),
},
],
}
}

View File

@ -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),
},
],
}
}

68
src/views/yhxwrz/index.js Normal file
View File

@ -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;