Browse Source

项目自定义分组

dev
CODE 1 year ago
parent
commit
033345a58f
  1. 5
      api/app/lib/controllers/control/analysis.js
  2. 6
      api/app/lib/controllers/control/data.js
  3. 110
      api/app/lib/controllers/project/group.js
  4. 57
      api/app/lib/models/project_group.js
  5. 12
      api/app/lib/routes/project/index.js
  6. 2
      api/app/lib/utils/dataRange.js
  7. 2
      api/sequelize-automate.config.js
  8. 4
      web/client/src/app.jsx
  9. 80
      web/client/src/layout/components/header/components/customProjGroupModal.jsx
  10. 163
      web/client/src/layout/components/header/index.jsx
  11. 59
      web/client/src/layout/components/header/index.less
  12. 35
      web/client/src/sections/projectGroup/actions/group.js
  13. 7
      web/client/src/sections/projectGroup/actions/index.js
  14. 4
      web/client/src/sections/projectGroup/containers/index.js
  15. 23
      web/client/src/sections/projectGroup/containers/static.jsx
  16. 15
      web/client/src/sections/projectGroup/index.js
  17. 10
      web/client/src/sections/projectGroup/nav-item.jsx
  18. 5
      web/client/src/sections/projectGroup/reducers/index.js
  19. 11
      web/client/src/sections/projectGroup/routes.js
  20. 0
      web/client/src/sections/projectGroup/style.less
  21. 2
      web/client/src/utils/webapi.js

5
api/app/lib/controllers/control/analysis.js

@ -140,7 +140,7 @@ async function personnelApp (ctx) {
findOptions.where.id = { $in: userInfo.correlationProject }
}
if (pepId) {
findOptions.where.id = pepId
findOptions.where.id = { $in: pepId.split(',') }
}
const proRes = await models.ProjectCorrelation.findAndCountAll(findOptions)
@ -199,7 +199,8 @@ async function personnelApp (ctx) {
let personnel = userRes.rows.filter(r => r.correlationProject.length > 0)
if (pepId) {
personnel = personnel.filter(r => r.dataValues.correlationProject.map(v => v.id).includes(Number(pepId)))
let pepIds = pepId.split(',')
personnel = personnel.filter(r => r.dataValues.correlationProject.map(v => v.id).some(pepId => pepIds.includes(pepId)))
}
ctx.status = 200

6
api/app/lib/controllers/control/data.js

@ -250,7 +250,11 @@ async function getAlarmsHandleStatistics (ctx) {
const models = ctx.fs.dc.models;
const data = await models.AlarmHandleStatistics.findAll({
order: [['time', 'DESC']],
where: projectCorrelationId ? { projectCorrelationId: projectCorrelationId } : {},
where: projectCorrelationId ?
{
projectCorrelationId: { $in: projectCorrelationId.split(',') }
}
: {},
limit: 1
})
ctx.status = 200;

110
api/app/lib/controllers/project/group.js

@ -0,0 +1,110 @@
'use strict';
const moment = require('moment')
async function groupList (ctx) {
try {
const { models } = ctx.fs.dc;
const { userId } = ctx.fs.api
const res = await models.ProjectGroup.findAll({
where: {
pomsUserId: userId
},
order: [['id', 'DESC']]
})
ctx.status = 200;
ctx.body = res
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function editGroup (ctx) {
try {
const { models } = ctx.fs.dc;
const { userId } = ctx.fs.api
const { id, name, pomsProjectIds = [] } = ctx.request.body
if (!name || !pomsProjectIds || !pomsProjectIds.length) {
throw '参数错误!'
}
let repeatNameRes = await models.ProjectGroup.findOne({
where: {
pomsUserId: userId,
name,
}
})
let repeatProjectRes = await models.ProjectGroup.findOne({
where: {
pomsUserId: userId,
pomsProjectIds
}
})
if (repeatNameRes && (!id || (id && repeatNameRes.id != id))) {
throw '已有相同名称的分组信息!'
}
if (repeatProjectRes && (!id || (id && repeatProjectRes.id != id))) {
throw '已有相同项目的分组信息!'
}
if (id) {
await models.ProjectGroup.update({
name,
pomsProjectIds,
}, {
where: {
id
}
})
} else {
await models.ProjectGroup.create({
name,
pomsProjectIds,
pomsUserId: userId,
}, {
where: {
id
}
})
}
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function delGroup (ctx) {
try {
const { models } = ctx.fs.dc;
await models.ProjectGroup.destroy({
where: {
id: ctx.query.groupId
}
})
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
module.exports = {
groupList,
editGroup,
delGroup,
};

57
api/app/lib/models/project_group.js

@ -0,0 +1,57 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const ProjectGroup = sequelize.define("projectGroup", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "project_group_id_uindex"
},
name: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "name",
autoIncrement: false
},
pomsProjectIds: {
type: DataTypes.ARRAY(DataTypes.INTEGER),
allowNull: false,
defaultValue: null,
comment: "运维项目id",
primaryKey: false,
field: "poms_project_ids",
autoIncrement: false
},
pomsUserId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "poms_user_id",
autoIncrement: false,
references: {
key: "id",
model: "user"
}
}
}, {
tableName: "project_group",
comment: "",
indexes: []
});
dc.models.ProjectGroup = ProjectGroup;
return ProjectGroup;
};

12
api/app/lib/routes/project/index.js

@ -2,6 +2,7 @@
const project = require('../../controllers/project');
const projectBind = require('../../controllers/project/bind')
const projectGroup = require('../../controllers/project/group')
module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/project/app_list'] = { content: '获取应用列表', visible: true };
@ -27,4 +28,15 @@ module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/project/structure'] = { content: '获取绑定项目下结构物', visible: true };
router.get('/project/structure', project.strucWithPomsProject);
//
app.fs.api.logAttr['GET/project/group'] = { content: '获取项目分组', visible: true };
router.get('/project/group', projectGroup.groupList);
app.fs.api.logAttr['PUT/project/group'] = { content: '编辑项目分组', visible: true };
router.put('/project/group', projectGroup.editGroup);
app.fs.api.logAttr['DEL/project/group'] = { content: '删除项目分组', visible: true };
router.delete('/project/group', projectGroup.delGroup);
};

2
api/app/lib/utils/dataRange.js

@ -28,7 +28,7 @@ module.exports = function (app, opts) {
}
if (pepProjectId) {
// 有 特定的项目id 就按此查询
findOption.where.id = pepProjectId
findOption.where.id = { $in: String(pepProjectId).split(',') }
} else if (!isSuper) {
// 还不是超管或管理员就按关联的项目id的数据范围查
findOption.where.id = { $in: correlationProject }

2
api/sequelize-automate.config.js

@ -33,7 +33,7 @@ module.exports = {
dir: './app/lib/models', // 指定输出 models 文件的目录
typesDir: 'models', // 指定输出 TypeScript 类型定义的文件目录,只有 TypeScript / Midway 等会有类型定义
emptyDir: false, // !!! 谨慎操作 生成 models 之前是否清空 `dir` 以及 `typesDir`
tables: ['workorder'], // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性
tables: ['project_group'], // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性
skipTables: [], // 指定跳过哪些表的 models,如 ['user'];如果为 null,则忽略改属性
tsNoCheck: false, // 是否添加 `@ts-nocheck` 注释到 models 文件中
ignorePrefix: [], // 生成的模型名称忽略的前缀,因为 项目中有以下表名是以 t_ 开头的,在实际模型中不需要, 可以添加多个 [ 't_data_', 't_',] ,长度较长的 前缀放前面

4
web/client/src/app.jsx

@ -14,6 +14,7 @@ import Service from './sections/service';
import WorkOrder from './sections/workOrder';
import Means from './sections/means';
import Data from './sections/data';
import ProjectGroup from './sections/projectGroup';
const App = props => {
const { projectName } = props
@ -40,7 +41,8 @@ const App = props => {
title={projectName}
sections={[//Example,
Analysis, Install, Data, Facility, Service, Problem, WorkOrder, Means,
Auth, NoMatch, Control
Auth, NoMatch, Control,
ProjectGroup,
]}
/>
)

80
web/client/src/layout/components/header/components/customProjGroupModal.jsx

@ -0,0 +1,80 @@
"use strict";
import React, { useEffect, useState, useRef } from 'react'
import { connect, createStore } from "react-redux";
import Immutable from 'immutable';
import { SplitButtonGroup, Dropdown, Button, Nav, Avatar, Input, useFormApi, Form, Modal } from '@douyinfe/semi-ui';
import { IconTreeTriangleDown, IconSearch, IconPlus } from '@douyinfe/semi-icons';
import "../index.less";
const { Option } = Form.Select;
const CustomProjGroupModal = (props) => {
const { visible, cancel, editData, pomsList, dispatch, actions } = props
const form = useRef();
return (
<Modal
title={(editData ? "修改" : "添加") + "自定义分组"}
visible={visible}
onOk={(e) => {
e.preventDefault()
form.current.validate()
.then(values => {
console.log(values);
let stoData = {
...values,
}
if (editData) {
stoData.id = editData.id
}
dispatch(actions.projectGroupAC.editProjectGroup(stoData)).then((res) => {
if (res.success) {
cancel({ refresh: true })
form.current?.reset()
}
})
})
}}
onCancel={(e) => {
e.preventDefault()
form.current?.reset()
cancel()
}}
closeOnEsc={true}
>
<Form getFormApi={formApi => {
form.current = formApi
if (editData) {
setTimeout(() => {
formApi.setValues(editData)
}, 0);
}
}}>
{({ formState, values, formApi }) => (
<>
<Form.Input field='name' label='名称' rules={[{ required: true, message: '请填写自定义分组名称' }]} maxLength={20} />
<Form.Select multiple field="pomsProjectIds" label={{ text: '运维项目' }} style={{ width: '100%' }} rules={[{ required: true, message: '请选择运维项目' }]} >
{
pomsList.map((item, index) => {
return (
<Option key={index} value={item.pepProjectId}>{item.pepProjectName}</Option>
)
})
}
</Form.Select>
</>
)}
</Form>
</Modal>
)
}
function mapStateToProps (state) {
const { global, auth } = state;
return {
actions: global.actions,
user: auth.user,
};
}
export default connect(mapStateToProps)(CustomProjGroupModal);

163
web/client/src/layout/components/header/index.jsx

@ -3,20 +3,27 @@ import React, { useEffect, useState } from 'react'
import { connect, createStore } from "react-redux";
import Immutable from 'immutable';
import { pepProject } from '../../actions/global';
import { SplitButtonGroup, Dropdown, Button, Nav, Avatar, Input, Tooltip, Tabs, TabPane } from '@douyinfe/semi-ui';
import { IconTreeTriangleDown, IconSearch } from '@douyinfe/semi-icons';
import { SplitButtonGroup, Dropdown, Button, Nav, Avatar, Input, Tooltip, Tabs, TabPane, Space, Popconfirm, Modal } from '@douyinfe/semi-ui';
import { IconTreeTriangleDown, IconSearch, IconEdit, IconDelete } from '@douyinfe/semi-icons';
import CustomProjGroupModal from './components/customProjGroupModal'
import PerfectScrollbar from "perfect-scrollbar";
import "./index.less";
let newScrollbar;
const Header = (props) => {
const { dispatch, history, user, actions, socket, headerItems, tochange } = props;
const { install } = actions
const { dispatch, history, user, actions, socket, headerItems, tochange, projectGroup } = props;
const { install, projectGroup: projectGroupAC } = actions
const [pomsList, setPomsList] = useState([])
const [pomsName, setPomsName] = useState('全局')
const [pepProjectId, setPepProjectId] = useState()
const [keyword, setKeyword] = useState('')
const [Scrollbar, setScrollbar] = useState(false)
const [prjDropdownVis, setPrjDropdownVis] = useState(false)
const [prjDropDownTabKey, setPrjDropDownTabKey] = useState('项目')
const [customProjGroupModalVis, setCustomProjGroupModalVis] = useState(false)
const [customProjGroupDelPopVis, setCustomProjGroupDelPopVis] = useState(false)
const [customProjGroupEditData, setCustomProjGroupEditData] = useState(null)
let userRole = user?.pomsUserInfo?.role
let modalRole = []
if (userRole) {
@ -33,6 +40,10 @@ const Header = (props) => {
if (userRole?.includes('SuperAdmin') || userRole?.includes('admin')) modalRole = headerItems
}
const getProjGroup = () => {
dispatch(projectGroupAC.getProjectGroup())
}
useEffect(() => {
if (JSON.parse(sessionStorage.getItem('pomsUser'))?.token) {
dispatch(install.getProjectPoms({ global: 1 })).then((res) => { //
@ -41,6 +52,7 @@ const Header = (props) => {
setPomsList(data)
}
})
getProjGroup()
}
}, [])
@ -60,7 +72,15 @@ const Header = (props) => {
dispatch(pepProject(pepProjectId))
}, [pepProjectId])
const semiPortalZindex = () => {
const semiPortal = document.getElementsByClassName('semi-portal')
for (let sp of semiPortal) {
if (sp.style.zIndex == 1060) {
sp.style.zIndex = 990
break
}
}
}
return (
<>
<div id="top-slider">
@ -86,7 +106,11 @@ const Header = (props) => {
logo: (
<img
src="/assets/images/install/long_logo.png"
style={{ display: "inline-block", width: 200, height: 40, marginLeft: -24 }}
style={{ display: "inline-block", width: 200, height: 40, marginLeft: -24, cursor: 'pointer' }}
onClick={() => {
// history.push('/projectGroup/static')
window.open('/projectGroup/static', '_blank');
}}
/>
),
text: (
@ -97,10 +121,26 @@ const Header = (props) => {
setScrollbar(!Scrollbar)
setKeyword('')
}}
clickToHide={true}
// trigger="click"
// clickToHide={true}
trigger={'custom'}
visible={prjDropdownVis}
onClickOutSide={(e) => {
if (customProjGroupModalVis || customProjGroupDelPopVis) {
return
}
setPrjDropdownVis(false)
}}
position="rightBottom"
stopPropagation={true}
render={
<Dropdown.Menu style={{ minWidth: 270, maxWidth: 714, padding: 20, fontSize: 12 }}>
<Tabs type="button">
<Dropdown.Menu style={{ minWidth: 270, maxWidth: 714, padding: 20, fontSize: 12, position: 'relative', }}>
<Tabs type="button"
activeKey={prjDropDownTabKey}
onChange={
(v) => setPrjDropDownTabKey(v)
}
>
<TabPane tab="项目" itemKey="项目">
<div style={{ width: '100%', height: 1, background: "#d5cfcf8c", margin: "10px 0" }}></div>
<Input suffix={<IconSearch />} onChange={(v) => setKeyword(v)} showClear onClick={(e) => e.stopPropagation()}></Input>
@ -110,13 +150,13 @@ const Header = (props) => {
return <Dropdown.Item
key={'pomsList' + v.pepProjectId}
style={{ width: 224, overflow: 'hidden', borderBottom: '', display: "inline-block", whiteSpace: 'nowrap', color: 'rgb(0, 90, 189)' }}>
{
v.pepProjectName?.length > 15 ? <Tooltip content={<div>{v.pepProjectName}</div>}>
<div style={{}} >
<div onClick={() => {
setPomsName(v.pepProjectName)
setPepProjectId(v.pepProjectId)
setPrjDropdownVis(false)
}}>
{v.pepProjectName?.length > 15 ? `${v.pepProjectName?.substr(0, 15)}` : v.pepProjectName}
</div>
@ -125,6 +165,7 @@ const Header = (props) => {
: <div onClick={() => {
setPomsName(v.pepProjectName)
setPepProjectId(v.pepProjectId)
setPrjDropdownVis(false)
}}>{v.pepProjectName}</div>
}
</Dropdown.Item>
@ -135,11 +176,91 @@ const Header = (props) => {
<TabPane tab={<div onClick={() => {
setPomsName('全局')
setPepProjectId('')
setPrjDropdownVis(false)
}}>全局</div>} itemKey="全局">
</TabPane>
<TabPane tab={<div onClick={() => {
setPepProjectId('')
}}>自定义分组</div>} itemKey="自定义分组">
<div style={{ width: '100%', height: 1, background: "#d5cfcf8c", margin: "10px 0" }} />
<div style={{ display: 'flex' }}>
<Button type="primary" onClick={() => {
semiPortalZindex()
setCustomProjGroupModalVis(true)
}}>设置新分组</Button>
<Input style={{
flex: 1, marginLeft: 14
}} suffix={<IconSearch />} onChange={(v) => setKeyword(v)} showClear onClick={(e) => e.stopPropagation()}></Input>
</div>
<div id='overall' className='customGroupPop' style={{ width: '100%', height: 260, position: "relative", marginTop: 10 }}>
{
projectGroup.filter(u => u.name?.includes(keyword))?.map(v => {
return (
<Dropdown.Item
className='customGroupItem'
key={'projectGroup' + v.id}
style={{
width: 224, overflow: 'hidden', borderBottom: '', display: "inline-block", whiteSpace: 'nowrap', color: 'rgb(0, 90, 189)'
}}
onClick={(e) => {
e.stopPropagation()
setPomsName(v.name)
setPepProjectId(v.pomsProjectIds.join(','))
setPrjDropdownVis(false)
}}
>
{
v.name?.length > 10 ?
<Tooltip content={<div>{v.name}</div>}>
<span >
{v.name?.substr(0, 10)}...
</span>
</Tooltip>
:
<span >
{v.name}
</span>
}
<Space style={{ float: 'right', position: 'relative', top: 3, }}>
<IconEdit className="edit-icon" onClick={(e) => {
e.stopPropagation()
semiPortalZindex()
setCustomProjGroupEditData(v)
setCustomProjGroupModalVis(true)
}} />
<IconDelete className="del-icon" onClick={(e) => {
e.stopPropagation()
semiPortalZindex()
setCustomProjGroupDelPopVis(v.id)
Modal.warning({
title: '确定删除该自定义分组?', content: '此修改将不可逆',
onCancel: () => {
setCustomProjGroupDelPopVis(null)
},
onOk: () => {
dispatch(projectGroupAC.delProjectGroup(v.id)).then((res) => {
if (res.success) {
setCustomProjGroupDelPopVis(null)
getProjGroup()
}
})
},
style: { zIndex: 1090 }
});
}} />
</Space>
</Dropdown.Item>
)
})
}
</div>
</TabPane>
</Tabs>
</Dropdown.Menu>
} trigger="click" position="bottomRight">
}
>
<Button theme="solid" type="primary" style={{ height: 24, background: '#005ABD' }}>{
pomsName.length > 15 ? <Tooltip content={<div>{pomsName}</div>}>
<div style={{}}>
@ -147,7 +268,7 @@ const Header = (props) => {
</div>
</Tooltip> : pomsName
}</Button>
<Button style={{ width: 16, height: 24, background: '#005ABD' }} theme="solid" type="primary" icon={<IconTreeTriangleDown />}></Button>
<Button style={{ width: 16, height: 24, background: '#005ABD' }} theme="solid" type="primary" icon={<IconTreeTriangleDown />} onClick={() => { setPrjDropdownVis(!prjDropdownVis) }}></Button>
</Dropdown>
</SplitButtonGroup>
</>
@ -176,7 +297,6 @@ const Header = (props) => {
<img src="/assets/images/background/console.png" style={{ width: 24, marginRight: -10 }} />
<Nav.Item key={index + 'a'} itemKey={item.itemKey} text={item.text} onClick={() => { tochange(item) }} />
</div>
)
}
}) : ""}
@ -253,17 +373,32 @@ const Header = (props) => {
</>
}
/>
{
<CustomProjGroupModal
visible={customProjGroupModalVis}
pomsList={pomsList}
editData={customProjGroupEditData}
cancel={({ refresh } = {}) => {
setCustomProjGroupModalVis(false)
setCustomProjGroupEditData(null)
if (refresh) {
getProjGroup()
}
}}
/>
}
</div>
</>
);
};
function mapStateToProps (state) {
const { global, auth, webSocket } = state;
const { global, auth, webSocket, projectGroup } = state;
return {
actions: global.actions,
user: auth.user,
socket: webSocket.socket,
projectGroup: projectGroup.data || []
};
}

59
web/client/src/layout/components/header/index.less

@ -3,16 +3,75 @@
font-size: 13px;
color: #F2F3F5;
}
.semi-navigation-item-icon {
color: #F2F3F5;
}
.semi-navigation-item-selected {
background: none;
}
.semi-navigation-item {
margin: 0px;
}
.semi-navigation-item-text {
overflow: inherit;
}
}
.customGroupPop {
.customGroupItem {
position: relative;
.edit-icon,
.del-icon {
display: none;
cursor: pointer;
}
.del-icon {
color: red;
}
&:hover .edit-icon,
&:hover .del-icon {
display: inline-block;
opacity: 1;
}
&:hover .edit-icon {
animation: fadeIn 0.3s ease-in-out;
}
&:hover .del-icon {
animation: slideIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
display: inline-block;
opacity: 1;
}
}
@keyframes slideIn {
from {
transform: translateX(-10px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
}
}

35
web/client/src/sections/projectGroup/actions/group.js

@ -0,0 +1,35 @@
'use strict';
import { ApiTable, basicAction } from '$utils'
export function getProjectGroup () {
return (dispatch) => basicAction({
type: "get",
dispatch: dispatch,
actionType: "GET_PROJECT_GROPUP",
url: `${ApiTable.projectGroup}`,
msg: { error: "获取项目分组失败" },
reducer: { name: "projectGroup", params: { noClear: true } },
});
}
export function editProjectGroup (data) {
return (dispatch) => basicAction({
type: "put",
data,
dispatch: dispatch,
actionType: "EDIT_PROJECT_GROPUP",
url: `${ApiTable.projectGroup}`,
msg: { option: (data?.id ? '编辑' : '添加') + "项目分组" },
});
}
export function delProjectGroup (id) {
return (dispatch) => basicAction({
type: "del",
dispatch: dispatch,
actionType: "DEL_PROJECT_GROPUP",
url: `${ApiTable.projectGroup}?groupId=${id}`,
msg: { option: "删除项目分组" },
});
}

7
web/client/src/sections/projectGroup/actions/index.js

@ -0,0 +1,7 @@
'use strict';
import * as group from './group'
export default {
...group
}

4
web/client/src/sections/projectGroup/containers/index.js

@ -0,0 +1,4 @@
'use strict';
import Static from './static'
export { Static };

23
web/client/src/sections/projectGroup/containers/static.jsx

@ -0,0 +1,23 @@
import React, { useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux';
import { Skeleton, Button, Pagination, Form, Popconfirm, Table, Toast } from '@douyinfe/semi-ui';
import moment from "moment";
const Static = (props) => {
return (
<div>
</div>
)
}
function mapStateToProps (state) {
const { auth, global, } = state;
return {
user: auth.user,
actions: global.actions,
};
}
export default connect(mapStateToProps)(Static);

15
web/client/src/sections/projectGroup/index.js

@ -0,0 +1,15 @@
'use strict';
import reducers from './reducers';
import routes from './routes';
import actions from './actions';
import { getNavItem } from './nav-item';
export default {
key: 'projectGroup',
name: '项目分组',
reducers: reducers,
routes: routes,
actions: actions,
getNavItem: getNavItem
};

10
web/client/src/sections/projectGroup/nav-item.jsx

@ -0,0 +1,10 @@
import React from 'react';
import { IconCode } from '@douyinfe/semi-icons';
export function getNavItem (user, dispatch) {
return (
[
]
);
}

5
web/client/src/sections/projectGroup/reducers/index.js

@ -0,0 +1,5 @@
'use strict';
export default {
}

11
web/client/src/sections/projectGroup/routes.js

@ -0,0 +1,11 @@
import { Static } from './containers';
export default [{
type: 'outer',
route: {
path: '/projectGroup/static',
key: 'projectGroup',
breadcrumb: '项目集',
component: Static,
}
}];

0
web/client/src/sections/projectGroup/style.less

2
web/client/src/utils/webapi.js

@ -33,6 +33,8 @@ export const ApiTable = {
getProjectAppList: 'project/app_list',//获取应用列表
deleteProjectBind: 'project/bind/{bindId}',//删除安心云、项目管理项目绑定关系
//项目分组
projectGroup: 'project/group',
//告警
getProjectPoms: 'project/poms', //获取已绑定项目

Loading…
Cancel
Save