Commit aa6764bb authored by 崔佳豪's avatar 崔佳豪

feat: 添加常用菜单功能“

parent 8917bfef
/* eslint-disable */ /* eslint-disable */
// const proxyURL = process.env.NODE_ENV !== 'production' ? 'http://192.168.10.150:8777' : window.location.origin; // const proxyURL = process.env.NODE_ENV !== 'production' ? 'http://192.168.10.150:8777' : window.location.origin;
const proxyURL = 'http://192.168.10.150:8669'; const proxyURL = 'http://127.0.0.1:8666';
module.exports = { module.exports = {
assetsRoot: process.env.NODE_ENV !== 'production' ? proxyURL : './', assetsRoot: process.env.NODE_ENV !== 'production' ? proxyURL : './',
dev: { dev: {
......
import { jsonp } from '@wisdom-utils/utils'; import { jsonp, request } from '@wisdom-utils/utils';
import * as constants from '../../constants'; import * as constants from '../../constants';
export const API = { export const API = {
AUTHORIZATION_TOKEN: '/Publish/Identity/AuthorizationToken', AUTHORIZATION_TOKEN: '/Publish/Identity/AuthorizationToken',
GET_GATEWAY_CONFIG: '/PandaCore/OMS/GateWayConfig', GET_GATEWAY_CONFIG: '/PandaOMS/OMS/GateWayConfig',
GET_CONFIG: '/CityInterface/rest/services.svc/GetConfig', GET_CONFIG: '/CityInterface/rest/services.svc/GetConfig',
GENERATE_TOKEN: '/cityinterface/rest/services.svc/generatetoken', GENERATE_TOKEN: '/cityinterface/rest/services.svc/generatetoken',
GENERATE_IOT_TOKEN: 'cityinterface/rest/services.svc/generateGCKToken', GENERATE_IOT_TOKEN: 'cityinterface/rest/services.svc/generateGCKToken',
...@@ -34,10 +34,15 @@ export const API = { ...@@ -34,10 +34,15 @@ export const API = {
'/cityinterface/rest/services/filedownload.svc/uploadfile/个人信息/{path}/{filename}', '/cityinterface/rest/services/filedownload.svc/uploadfile/个人信息/{path}/{filename}',
AVATAR_FILE_URL: '/cityinterface/rest/services/filedownload.svc/download', AVATAR_FILE_URL: '/cityinterface/rest/services/filedownload.svc/download',
GET_SENSOR_TYPE: '/PandaCore/GCK/Sensor/GetSensorType', // 获取传感器类型 GET_SENSOR_TYPE: '/PandaCore/GCK/Sensor/GetSensorType', // 获取传感器类型
GET_SYSTEM_CONFIGURATION: '/PandaCore/OMS/SysConfiguration', // 获取系统配置 GET_SYSTEM_CONFIGURATION: '/PandaOMS/OMS/SysConfiguration', // 获取系统配置
GET_INFO_QYWX: '/CityInterface/rest/services/OA.svc/WeChatScanQRCode', GET_INFO_QYWX: '/CityInterface/rest/services/OA.svc/WeChatScanQRCode',
PV_LOGS: '/CityInterface/rest/services/portal.svc/OMMonitor/SavePVLogInfo', PV_LOGS: '/CityInterface/rest/services/portal.svc/OMMonitor/SavePVLogInfo',
LOGIN_LOGS: 'CityInterface/rest/Services/IOTPlatform.svc/WisdomOMS/SaveLoginInfo' LOGIN_LOGS:
'CityInterface/rest/Services/IOTPlatform.svc/WisdomOMS/SaveLoginInfo',
GET_PAGE_PART_INFO:
'/CityInterface/rest/services/CountyProduct.svc/AccountManage/GetHomePagePartInfo',
SAVE_Page_PART_INFO:
'/CityInterface/rest/services/CountyProduct.svc/AccountManage/SaveHomePage',
}; };
const services = { const services = {
...@@ -174,7 +179,12 @@ const services = { ...@@ -174,7 +179,12 @@ const services = {
url: API.LOGIN_LOGS, url: API.LOGIN_LOGS,
method: constants.REQUEST_METHOD_POST, method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_HTTP, type: constants.REQUEST_HTTP,
} },
getPagePartInfo: {
url: API.GET_PAGE_PART_INFO,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_HTTP,
},
}; };
export const searchAutoCity = keywords => { export const searchAutoCity = keywords => {
...@@ -198,4 +208,13 @@ export const searchAutoCity = keywords => { ...@@ -198,4 +208,13 @@ export const searchAutoCity = keywords => {
return jsonp(url, params, options); return jsonp(url, params, options);
}; };
// 更新常用页
export const savePagePartInfo = param =>
request({
url: API.SAVE_Page_PART_INFO,
method: constants.REQUEST_METHOD_POST,
data: param.data,
params: param.query,
});
export default services; export default services;
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
/* eslint-disable no-useless-constructor */
import React, { useState, useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
import { Input, Button, Tree, notification, Spin, message } from 'antd';
import classnames from 'classnames';
import { appService } from '@/api';
import { savePagePartInfo } from '@/api/service/base';
import styles from './index.less';
import thumbnail from '../../assets/images/commonMenu/常用菜单.png';
import pageLogo from '../../assets/images/commonMenu/page-logo.png';
// 是否是灰色的图标(灰色图标在白色背景中看不见,添加滤镜变色)
const isNeedFilterIcon = (icon = '') => {
return !icon.includes('一级') && !icon.includes('ios/');
};
const CommonMenu = () => {
const [menus, setMenus] = useState([]); // 常用菜单信息
const [widgets, setWidgets] = useState([]); // 菜单列表信息
const [showMenuInfo, setShowMenuInfo] = useState(false); // 菜单列表显示隐藏
const [loading, setLoading] = useState(true); // loading显示隐藏
const [searchInfo, setSearchInfo] = useState('');
const page = useRef(null);
const defaultSubmitMenu = () => {
let submitData = [];
submitData = menus.map(item => ({
PartID: item.menuID,
PartName: item.menuName,
PartUrl: item.menuUrl,
icon: item.menuIcon,
}));
return submitData;
};
/**
* 添加、删除菜单
* @param {string} opr add/delete,添加/删除
* @param {object} extData { groupName, label, icon, url, product },操作的菜单信息widget
*/
const updateCommonMenu = (opr, extData) => {
const { groupName, label, icon, url, product } = extData;
setLoading(true);
let submitData = defaultSubmitMenu();
if (opr === 'add') {
submitData.push({
PartID: `${groupName}_${label}`,
PartName: label,
PartUrl: url,
icon,
});
} else if (opr === 'delete') {
submitData = submitData.filter(
item => item.PartID !== `${groupName}_${label}`,
);
}
savePagePartInfo({
query: {
UserID: window.globalConfig?.userInfo?.OID ?? '',
},
data: submitData,
}).then(res => {
if (res.statusCode === '0000') {
message.success('修改常用菜单成功!');
fetchMenus();
} else {
message.error('修改常用菜单失败!');
}
});
};
/**
* 渲染自定义树节点
* @param {object} node 树节点信息,至少包含key, title, extData, children
* @returns {ReactDOM} 返回自定义节点DOM
*/
const renderTitle = node => {
const { key, title, extData = {}, children } = node;
const { icon, isMenu, isAdded } = extData;
return (
<div className={styles.customTitle}>
<div
className={classnames(
styles.titleInfo,
isNeedFilterIcon(icon) ? styles.filterIconBox : '',
)}
>
<span className={styles.iconBox}>
<img src={icon} alt="" />
</span>
<span>{title}</span>
</div>
{isMenu && (
<div className={styles.titleControl}>
{isAdded ? (
<div>
<p className={styles.chooseLabel}>已添加</p>
<div
className={styles.chooseBtn}
onClick={e => {
e.stopPropagation();
updateCommonMenu('delete', extData);
}}
>
取消添加
</div>
</div>
) : (
<div>
<div
className={styles.chooseBtn}
onClick={e => {
e.stopPropagation();
updateCommonMenu('add', extData);
}}
>
添加
</div>
</div>
)}
</div>
)}
</div>
);
};
const getWidgets = addedmenus => {
const isAddedMenu = widget => {
const menuTmp = addedmenus.find(
item =>
item.menuGroup === widget.extData.groupName ||
item.menuName === widget.extData.label,
);
return !!menuTmp;
};
const deepWidgets = (allWidgets, newWidgets, deep, groupName) => {
allWidgets &&
allWidgets.forEach((item, index) => {
const newWidget = {
title: item.label,
key:
item.url === undefined
? `${groupName}_${item.label}`
: `${groupName}_${item.url}`,
// icon: <MehOutlined />,
children: [],
extData: {
...item,
icon: `https://panda-water.cn/web4/${item.icon}`,
isMenu: item.url !== undefined,
groupName:
item.url === undefined
? `${groupName}_${item.label}`
: groupName,
},
};
newWidget.extData.isAdded = isAddedMenu(newWidget);
newWidgets.push(newWidget);
deepWidgets(
item.widgets,
newWidget.children,
deep + 1,
item.url === undefined ? `${groupName}_${item.label}` : groupName,
);
});
};
// 所有的可选菜单
const { allWidgets = [] } = window.globalConfig;
const newWidgets = [];
deepWidgets(
allWidgets,
newWidgets,
1,
`widget_${window.globalConfig.client}`,
);
return newWidgets;
};
const fetchMenus = () => {
appService
.getPagePartInfo({
UserID: window?.globalConfig?.userInfo?.OID ?? '',
})
.then(res => {
setLoading(false);
if (res.say.statusCode !== '0000')
return notification.error({
message: '服务错误',
description: res.say.errMsg,
});
const newMenus = [];
// 过滤出当前client的菜单
const data = res.getMe.filter(item => {
const client = item.PartID.split('_')[1] ?? '';
return client === window.globalConfig.client;
});
data.forEach(item => {
const newMenu = {
menuIcon: '',
menuName: '',
menuGroup: '',
menuID: '',
menuUrl: '',
menuPic: '',
};
newMenu.menuIcon = item.icon;
newMenu.menuName = item.PartName;
const PartIDArr = item.PartID.split('_');
newMenu.menuGroup = PartIDArr.splice(2, PartIDArr.length - 3).join(
'_',
);
newMenu.menuID = item.PartID;
newMenu.menuUrl = item.PartUrl;
newMenu.menuPic = item.BgPicUrl;
newMenus.push(newMenu);
});
setMenus(newMenus);
const w = getWidgets(newMenus);
setWidgets(w);
})
.catch(error => {
notification.error({
message: '服务错误',
description: error,
});
});
};
useEffect(() => {
fetchMenus();
}, []);
const loop = data => {
return data.map(item => {
const index = item.title.indexOf(searchInfo);
const beforeStr = item.title.substr(0, index);
const afterStr = item.title.substr(index + searchInfo.length);
const title =
index > -1 ? (
<span>
{beforeStr}
<span className={styles.treeSearchInfo}>{searchInfo}</span>
{afterStr}
</span>
) : (
<span>{item.title}</span>
);
if (item.children) {
return { ...item, title, children: loop(item.children) };
}
return { ...item, title };
});
};
return (
<div
className={styles.commonMenu}
ref={page}
onClick={e => {
e.stopPropagation();
if (e.target === page.current) setShowMenuInfo(false);
}}
>
<Spin spinning={loading}>
<div className={styles.searchWrapper}>
<div className={styles.searchBox}>
<div className={styles.searchTitle}>
<i className="iconfont">&#xe611;</i>
<span>我的常用菜单</span>
<span>{menus.length}</span>
</div>
<div>
<div className={styles.searchInput}>
<Input.Search
placeholder="搜索功能菜单"
// onFocus={() => setShowMenuInfo(true)}
onSearch={info => {
setShowMenuInfo(true);
setSearchInfo(info);
}}
/>
</div>
</div>
<div
className={classnames(
styles.searchInfoBox,
showMenuInfo ? '' : styles.searchInfoBox_hide,
)}
>
<Tree
height={400}
showIcon
defaultExpandAll
defaultSelectedKeys={['0-0-0']}
// switcherIcon={<DownOutlined />}
treeData={loop(widgets)}
titleRender={renderTitle}
selectable={false}
/>
</div>
</div>
{/* <Button className={styles.searchBtn}>添加菜单</Button> */}
</div>
<div className={styles.menuCardWrapper}>
{menus.map(item => (
<MenuCard menu={item} />
))}
</div>
<div className={styles.pageLogo}>
<img src={pageLogo} alt="" />
</div>
</Spin>
</div>
);
};
const MenuCard = ({ menu }) => {
const { menuIcon, menuName, menuGroup, menuID, menuUrl, menuPic } = menu;
return (
<div className={styles.menuCard}>
<Link to={menuUrl} title={menuName}>
<img
className={styles.cardThumbnail}
src={menuPic || thumbnail}
alt=""
/>
<div className={styles.cardLabel}>
<div
className={classnames(
styles.cardTitle,
isNeedFilterIcon(menuIcon) ? styles.filterIconBox : '',
)}
>
<span className={styles.iconBox}>
<img className={styles.cardIcon} src={menuIcon} alt="" />
</span>
<span className={styles.cardName}>{menuName}</span>
</div>
<div className={styles.cardGroup}>{menuGroup}</div>
</div>
</Link>
</div>
);
};
export default CommonMenu;
@import '~antd/es/style/themes/default.less';
.commonMenu {
width: 100%;
height: 100%;
min-height: 400px;
background: url(assets/images/commonMenu/page-background.png);
font-family: Microsoft YaHei;
padding-top: 57px;
background-color: #F5F6FC;
overflow: hidden;
:global {
p {
margin: 0;
}
.ant-spin-nested-loading {
height: 100%;
width: 100%;
}
}
.filterIconBox {
.iconBox {
filter: invert(33%) sepia(100%) saturate(1499%) hue-rotate(199deg) brightness(102%) contrast(104%);
img {
filter: brightness(0%);
}
}
}
.searchWrapper {
width: 100%;
height: 48px;
padding: 0 20%;
display: flex;
align-items: center;
margin-bottom: 50px;
.searchBox {
flex: 1;
height: 48px;
background: #FFFFFF;
box-shadow: 0px 5px 10px 0px #F3F3F3;
border-radius: 2px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 11px;
position: relative;
.searchTitle {
&>i {
margin-right: 15px;
color: #3B7FDF;
}
}
.searchInfoBox {
width: 100%;
height: 400px;
position: absolute;
overflow: hidden;
left: 0;
top: 100%;
z-index: 100;
transition: height 0.5s ease-out;
&.searchInfoBox_hide {
height: 0;
}
:global {
.ant-tree-treenode {
width: 100%;
}
.ant-tree-node-content-wrapper,
.ant-tree-node-content-wrapper-normal
.ant-tree-node-content-wrapper-close {
flex: 1;
overflow: hidden;
}
.ant-tree-switcher {
line-height: 36px;
}
}
.customTitle {
width: 100%;
height: 36px;
display: flex;
align-items: center;
justify-content: space-between;
// padding-right: 20px;
.titleInfo {
img {
width: 18px;
height: 18px;
margin-right: 8px;
}
}
.treeSearchInfo {
color: #ff5500;
}
&:hover {
background-color: #f1faf9;
.titleControl {
.chooseLabel {
display: none;
}
.chooseBtn {
display: block;
}
}
}
.titleControl {
.chooseLabel {
padding-right: 20px;
display: block;
color: #999999;
font-size: 15px;
}
.chooseBtn {
float: right;
cursor: pointer;
height: 36px;
width: 101px;
background: #01c1a4;
color: white;
text-align: center;
display: none;
line-height: 36px;
}
}
}
}
}
.searchBtn {
flex: none;
width: 135px;
height: 48px;
background: linear-gradient(0deg, #1685FF 0%, #49A0FF 100%);
box-shadow: 0px 5px 10px 0px #F5F6FC;
border-radius: 2px;
margin-left: 36px;
font-weight: bold;
color: #FFFFFF;
}
}
.menuCardWrapper {
display: grid;
grid-template-columns: repeat(4, 25%);
height: calc(100% - 185px);
padding: 0 5%;
overflow: auto;
}
.menuCard {
margin: 20px 10px;
width: 377px;
height: 240px;
background-image: url(assets/images/commonMenu/矩形.png);
background-size: 100%;
background-position: center bottom;
position: relative;
padding: 0 30px;
transition: transform 0.5s ease-out;
cursor: pointer;
&:hover {
transform: scale(1.1);
}
.cardThumbnail {
width: 100%;
}
.cardLabel {
// width: 347px;
height: 75px;
position: absolute;
bottom: 0;
left: 10px;
right: 10px;
// left: 50%;
// transform: translateX(-50%);
background: url("assets/images/commonMenu/card-label.png") center/100% no-repeat;
display: flex;
justify-content: space-between;
align-items: center;
padding: 30px 10px 0;
.cardTitle {
color: #323232;
padding-left: 10px;
img {
width: 20px;
height: 20px;
vertical-align: text-top;
margin-right: 0.5em;
}
}
.cardGroup {
font-size: 14px;
color: #999999;
::before {
content: "·";
margin-right: 0.5em;
}
}
}
}
.pageLogo {
width: 197px;
position: absolute;
bottom: 31px;
left: 50%;
transform: translateX(-50%);
img {
width: 100%;
}
}
}
...@@ -5,6 +5,7 @@ import BasicLayout from '../layouts/BasicLayout1'; ...@@ -5,6 +5,7 @@ import BasicLayout from '../layouts/BasicLayout1';
import UserLayout from '../layouts/UserLayout'; import UserLayout from '../layouts/UserLayout';
import BootPage from '../pages/bootpage'; import BootPage from '../pages/bootpage';
import Login from '../pages/user/login'; import Login from '../pages/user/login';
import CommonMenu from '../pages/commonMenu';
export const dyRoutes = routes => { export const dyRoutes = routes => {
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
...@@ -59,6 +60,18 @@ export const dyRoutes = routes => { ...@@ -59,6 +60,18 @@ export const dyRoutes = routes => {
loading: LoadingComponent, loading: LoadingComponent,
}), }),
}, },
{
path: '/commonmenu',
component: CommonMenu,
},
// {
// path: '/commonmenu',
// component: dynamic({
// loader: () =>
// import(/* webpackChunkName: 'p__500' */ '../pages/commonMenu'),
// loading: CommonMenu,
// }),
// },
], ],
}; };
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment