Browse Source

大屏组件

dev
CODE 1 year ago
parent
commit
20b2fa56fe
  1. 2
      api/app/lib/controllers/equipment/index.js
  2. 1
      api/app/lib/controllers/operationData/index.js
  3. 42
      api/app/lib/controllers/project/group.js
  4. 30
      api/app/lib/controllers/weather/index.js
  5. 2
      api/app/lib/index.js
  6. 8
      api/app/lib/routes/weather/index.js
  7. 7
      api/app/lib/service/paasRequest.js
  8. 13
      api/config.js
  9. BIN
      web/client/assets/fonts/PangMenZhengDaoBiaoTiTi-1.ttf
  10. BIN
      web/client/assets/images/projectGroup/body.png
  11. BIN
      web/client/assets/images/projectGroup/cardTitle.png
  12. BIN
      web/client/assets/images/projectGroup/cardTitlePoint.png
  13. BIN
      web/client/assets/images/projectGroup/header.png
  14. 11
      web/client/index.html
  15. 3
      web/client/src/layout/components/header/index.jsx
  16. 3
      web/client/src/layout/containers/layout/index.jsx
  17. 28
      web/client/src/layout/index.jsx
  18. 15
      web/client/src/sections/auth/actions/index.js
  19. 27
      web/client/src/sections/projectGroup/components/body.jsx
  20. 45
      web/client/src/sections/projectGroup/components/card.jsx
  21. 127
      web/client/src/sections/projectGroup/components/header.jsx
  22. 30
      web/client/src/sections/projectGroup/containers/bigscreen.jsx
  23. 4
      web/client/src/sections/projectGroup/containers/index.js
  24. 23
      web/client/src/sections/projectGroup/containers/static.jsx
  25. 31
      web/client/src/sections/projectGroup/containers/statistic.jsx
  26. 6
      web/client/src/sections/projectGroup/routes.js
  27. 3
      web/client/src/sections/projectGroup/style.less
  28. 3
      web/client/src/utils/webapi.js

2
api/app/lib/controllers/equipment/index.js

@ -88,8 +88,6 @@ async function getEquipment (ctx) {
equipmentMaintenanceRecordProjects: item.equipmentMaintenanceRecordProjects
}
})
//console.log('res111', lastRes, resCount)
// console.log('res11', arrayUserIdCopy)
ctx.body = { result: lastRes, resCount }
ctx.status = 200
} catch (error) {

1
api/app/lib/controllers/operationData/index.js

@ -113,7 +113,6 @@ async function getOperationsPersonnel (ctx) {
//查询用户id
const res = await sequelize.query(`SELECT t.pep_user_id userId,count(1) FROM maintenance_record_execute_user t GROUP BY pep_user_id`)
let useList = new Set()
console.log('rss', res)
if (res.length > 0) {
res[0].forEach((item) => {
useList.add(item.userid)

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

@ -110,13 +110,43 @@ async function groupStatic (ctx) {
const { clickHouse } = ctx.app.fs
const sequelize = ctx.fs.dc.orm
const groupProjectRes = await sequelize.query(`
SELECT * FROM project_correlation
LEFT JOIN project_group
ON project_correlation.id = ANY(project_group.poms_project_ids)
WHERE project_group.poms_user_id = ${userId}
`)
const progectGroupList = await models.ProjectGroup.findAll({
where: {
pomsUserId: userId
}
})
// 获取全部的 poms 项目id 并构建关系
let pomsProjectIds = new Set()
for (let group of progectGroupList) {
for (let projectId of group.pomsProjectIds) {
pomsProjectIds.add(projectId)
}
}
let pomsProjectIdArr = Array.from(pomsProjectIds)
const groupProjectRes = await models.ProjectCorrelation.findAll({
where: {
id: { $in: pomsProjectIdArr }
}
})
// 获取所有的 安心云项目id
let anxinProjectIds = new Set()
for (let project of groupProjectRes) {
for (let projectId of project.anxinProjectId) {
anxinProjectIds.add(projectId)
}
}
let anxinProjectIdArr = Array.from(anxinProjectIds)
// // 统计安心云项目下的结构物个数
// let strucRes = await clickHouse.anxinyun.query(
// `
// SELECT
// `
// )
ctx.status = 200;
ctx.body = []

30
api/app/lib/controllers/weather/index.js

@ -0,0 +1,30 @@
'use strict';
function realtime (opts) {
return async (ctx, next) => {
try {
const { models } = ctx.fs.dc;
const { location } = ctx.query
let location_ = location || '114.298156,27.717683';//经纬度字符串
const weatherRes = await ctx.app.fs.caiyunRequest.get(`${opts.caiyun.key}/${location_}/realtime`)
let rslt = {}
if (weatherRes.status == 'ok') {
rslt = weatherRes.result
}
ctx.status = 200;
ctx.body = rslt
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
}
module.exports = {
realtime
}

2
api/app/lib/index.js

@ -30,7 +30,7 @@ module.exports.entry = function (app, router, opts) {
// 实例其他平台请求方法
paasRequest(app, opts)
kafka(app, opts)
// kafka(app, opts)
// clickHouse 数据库 client
clickHouseClient(app, opts)

8
api/app/lib/routes/weather/index.js

@ -0,0 +1,8 @@
'use strict';
const weather = require('../../controllers/weather');
module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/weather/realtime'] = { content: '获取实时天气', visible: true };
router.get('/weather/realtime', weather.realtime(opts));
};

7
api/app/lib/service/paasRequest.js

@ -24,7 +24,11 @@ class paasRequest {
get = (url, { query = {}, header = {} } = {}) => {
return new Promise((resolve, reject) => {
request.get(this.#buildUrl(url)).set(header).query(Object.assign(query, this.query)).end(this.#resultHandler(resolve, reject));
request.get(
this.#buildUrl(url))
.set(header)
.query(Object.assign(query, this.query))
.end(this.#resultHandler(resolve, reject));
})
}
@ -52,6 +56,7 @@ function factory (app, opts) {
try {
for (let r of opts.pssaRequest) {
if (r.name && r.root) {
console.log(`跨平台请求工具 ${r.name}`);
app.fs[r.name] = new paasRequest(r.root, { ...(r.params || {}) }, { dataWord: r.dataWord || 'body' })
} else {
throw 'opts.pssaRequest 参数错误!'

13
api/config.js

@ -50,6 +50,9 @@ args.option('clickHouseCamworkflow', 'clickHouse 工作流数据库名称');
args.option('confirmAlarmAnxinUserId', '确认告警时保存到 ES 的安心云的用户的 id');
args.option('caiyunApi', '彩云天气 api');
args.option('caiyunKey', '彩云天气 apiKey');
// 视频应用秘钥
args.option('vcmpAppId', '视频平台 应用 id')
args.option('vcmpAppSecret', '视频平台 应用秘钥')
@ -115,6 +118,10 @@ const PLATFORM_NAME = process.env.PLATFORM_NAME || flags.platformName || 'anxiny
const VCMP_APP_ID = process.env.VCMP_APP_ID || flags.vcmpAppId
const VCMP_APP_SECRET = process.env.VCMP_APP_SECRET || flags.vcmpAppSecret
// 彩云天气
const CAIYUN_API = process.env.CAIYUN_API || flags.caiyunApi || 'https://api.caiyunapp.com/v2';
const CAIYUN_KEY = process.env.CAIYUN_KEY || flags.caiyunKey || '1l0eNveMANMXEIJI';
if (
!POMS_DB
|| !IOTA_REDIS_SERVER_HOST || !IOTA_REDIS_SERVER_PORT
@ -203,6 +210,9 @@ const product = {
password: 'Fs2689'
}
},
caiyun: {
key: CAIYUN_KEY,
},
pssaRequest: [{// name 会作为一个 request 出现在 ctx.app.fs
name: 'axyRequest',
root: API_ANXINYUN_URL
@ -226,6 +236,9 @@ const product = {
key: GOD_KEY
}
}
}, {
name: 'caiyunRequest',
root: CAIYUN_API
},],
clickHouse: {
url: CLICKHOUST_URL,

BIN
web/client/assets/fonts/PangMenZhengDaoBiaoTiTi-1.ttf

Binary file not shown.

BIN
web/client/assets/images/projectGroup/body.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
web/client/assets/images/projectGroup/cardTitle.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

BIN
web/client/assets/images/projectGroup/cardTitlePoint.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

BIN
web/client/assets/images/projectGroup/header.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

11
web/client/index.html

@ -10,8 +10,15 @@
<script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script>
<script>LA.init({ id: "Jo4eTlZVqgx3uwqm", ck: "Jo4eTlZVqgx3uwqm" })</script>
<script
src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_19077_11.559b91c217b8ddc76c0c4b1397d84d48.es5.js" async></script>
<script src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_19077_11.559b91c217b8ddc76c0c4b1397d84d48.es5.js"
async></script>
<style>
@font-face {
font-family: PangMenZhengDaoBiaoTiTi;
src: url('/assets/fonts/PangMenZhengDaoBiaoTiTi-1.ttf') format('truetype');
}
</style>
</head>
<body>

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

@ -108,8 +108,7 @@ const Header = (props) => {
src="/assets/images/install/long_logo.png"
style={{ display: "inline-block", width: 200, height: 40, marginLeft: -24, cursor: 'pointer' }}
onClick={() => {
// history.push('/projectGroup/static')
window.open('/projectGroup/static', '_blank');
window.open('/projectGroup/statistic', '_blank');
}}
/>
),

3
web/client/src/layout/containers/layout/index.jsx

@ -188,8 +188,9 @@ const LayoutContainer = props => {
})
}
}
const dom = document.getElementById('page-content');
setTimeout(() => {
const dom = document.getElementById('page-content');
if (dom) {
if (!scrollbar) {
scrollbar = new PerfectScrollbar('#page-content', { suppressScrollX: true });

28
web/client/src/layout/index.jsx

@ -105,18 +105,21 @@ const Root = props => {
}
if (s.actions) {
actions = { ...actions, [s.key]: s.actions }
if (s.key != 'auth') {
for (let ak in s.actions) {
let actions = s.actions[ak]
if (actions && typeof actions == 'object') {
for (let actionName in actions) {
initReducer(reducers, actionName, actions[actionName])
}
} else if (typeof actions == 'function') {
initReducer(reducers, ak, actions)
// if (s.key != 'auth') {
for (let ak in s.actions) {
if (['initAuth', 'login', 'logout'].includes(ak)) {
continue
}
let actions = s.actions[ak]
if (actions && typeof actions == 'object') {
for (let actionName in actions) {
initReducer(reducers, actionName, actions[actionName])
}
} else if (typeof actions == 'function') {
initReducer(reducers, ak, actions)
}
}
// }
}
}
@ -124,7 +127,12 @@ const Root = props => {
let store = configStore(reducers, history);
store.dispatch(initLayout(title, copyright, sections, actions));
// store.dispatch(resize(document.body.clientHeight, document.body.clientWidth));
store.dispatch(resize(document.getElementById('PomsApp').clientHeight, document.getElementById('PomsApp').clientWidth));
store.dispatch(
resize(
document.getElementById('PomsApp').clientHeight,
document.getElementById('PomsApp').clientWidth
)
);
store.dispatch(actions.auth.initAuth());
const resourceRoot = await store.dispatch(initApiRoot())
store.dispatch(initWebSocket({}))

15
web/client/src/sections/auth/actions/index.js

@ -1,7 +1,20 @@
'use strict';
import auth from './auth';
import { ApiTable, basicAction } from '$utils'
export function getWeatherRealtime () {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
actionType: 'GET_WEATHER_REALTIME',
url: `${ApiTable.weatherRealtime}`,
msg: { error: '获取实时天气失败' },
reducer: { name: 'weatherRealtime' }
});
}
export default {
...auth
...auth,
getWeatherRealtime,
};

27
web/client/src/sections/projectGroup/components/body.jsx

@ -0,0 +1,27 @@
import React, { useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux';
const Body = (props) => {
return (
<div style={{
backgroundImage: "url('/assets/images/projectGroup/body.png')",
backgroundRepeat: "no-repeat",
backgroundSize: "100% 100%",
height: 'calc(100vh - 64px)',
padding: '24px 16px'
}}>
{props?.children}
</div>
)
}
function mapStateToProps (state) {
const { auth, global, } = state;
return {
user: auth.user,
actions: global.actions,
};
}
export default connect(mapStateToProps)(Body);

45
web/client/src/sections/projectGroup/components/card.jsx

@ -0,0 +1,45 @@
import React, { useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux';
const Card = (props) => {
const { title } = props
return (
<div style={{
background: ' #FFFFFF',
boxShadow: '0 2px 4px 2px #CBD5EE',
borderRadius: 4,
// filter: 'blur(5px)',
padding: '6px 4px',
}}>
<div style={{
height: 34, lineHeight: '34px',
backgroundImage: "url('/assets/images/projectGroup/cardTitle.png')",
backgroundRepeat: "no-repeat",
padding: '0 10px',
display: 'flex', alignItems: 'center', justifyContent: 'space-between'
}}>
<span style={{ fontSize: 'x-large', letterSpacing: 1, textShadow: '0 0 8px #2a62fc05' }}>{title}</span>
<img src='/assets/images/projectGroup/cardTitlePoint.png' style={{
width: 4,
height: 3,
background: '#2C66F3',
boxShadow: '0 0 4px 1px #2C66F3'
}} />
</div>
<div style={{ minHeight: 24, padding: '12px 18px' }}>
{props?.children}
</div>
</div>
)
}
function mapStateToProps (state) {
const { auth, global, } = state;
return {
user: auth.user,
actions: global.actions,
};
}
export default connect(mapStateToProps)(Card);

127
web/client/src/sections/projectGroup/components/header.jsx

@ -0,0 +1,127 @@
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 Header = (props) => {
const { dispatch, actions, weatherRealtime } = props
const [date, setDate] = useState(moment());
const dayMap = { 0: '日', 1: '一', 2: '二', 3: '三', 4: '四', 5: '五', 6: '六' }
const weatherMap = {
CLEAR_DAY: '晴(白天)',
CLEAR_NIGHT: '晴(夜间)',
PARTLY_CLOUDY_DAY: '多云(白天)',
PARTLY_CLOUDY_NIGHT: '多云(夜间)',
CLOUDY: '阴',
WIND: '大风',
HAZE: '雾霾',
RAIN: '雨',
SNOW: '雪'
}
const [weatherTemperature, setWeatherTemperature] = useState('');
const getWeather = () => {
dispatch(actions.auth.getWeatherRealtime())
}
useEffect(() => {
const setTime = () => {
setDate(moment());
setTimeout(() => {
setTime()
}, 1000);
}
const refreshWeather = () => {
getWeather();
setTimeout(() => {
refreshWeather()
}, 1000 * 60 * 15);
}
setTimeout(() => {
setTime()
refreshWeather()
}, 0);
}, [])
useEffect(() => {
setWeatherTemperature(
weatherRealtime?.temperature ?
(weatherRealtime?.temperature - Math.ceil(Math.random() * 5))
+ '~' +
(weatherRealtime?.temperature + Math.ceil(Math.random() * 3))
+ '℃'
: ''
)
}, [weatherRealtime])
const lineBetweenStyle = {
width: 1,
height: 15,
border: '1px solid #B3C9FF',
margin: '0 4px'
}
return (
<div style={{
backgroundImage: "url('/assets/images/projectGroup/header.png')",
backgroundRepeat: "no-repeat",
backgroundSize: "100% 100%",
height: 64,
lineHeight: '64px',
padding: '0 24px',
display: 'flex', alignItems: 'center', justifyContent: 'space-between'
}}>
<span style={{ fontSize: 'xx-large', fontWeight: 'bolder' }}>运维中台大屏</span>
<span style={{
display: 'flex', alignItems: 'center', flexDirection: 'row'
}}>
<div>
<span style={{ fontSize: 'large', fontWeight: 'bolder' }}>
{date.format('HH:mm:ss')}
</span>
</div>
<div style={lineBetweenStyle} />
<div style={{
display: 'flex', flexDirection: 'column',
lineHeight: '12px',
fontSize: 12,
color: '#5A6685',
letterSpacing: 0.6
}}>
<span style={{ lineHeight: '14px', }}>
星期{dayMap[date.day()]}
</span>
<span style={{ display: 'inline-block', minWidth: 74 }}>
{date.format('YYYY-MM-DD')}
</span>
</div>
<div style={lineBetweenStyle} />
<div style={{
display: 'flex', flexDirection: 'column',
lineHeight: '12px',
fontSize: 12,
color: '#5A6685',
letterSpacing: 0.6
}}>
<span style={{ lineHeight: '14px', }}>
{weatherTemperature}
</span>
<span style={{}}>
{weatherMap[weatherRealtime?.skycon]}
</span>
</div>
</span>
</div>
)
}
function mapStateToProps (state) {
const { auth, global, weatherRealtime } = state;
return {
user: auth.user,
actions: global.actions,
weatherRealtime: weatherRealtime.data || {}
};
}
export default connect(mapStateToProps)(Header);

30
web/client/src/sections/projectGroup/containers/bigscreen.jsx

@ -0,0 +1,30 @@
import React, { useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux';
import Header from '../components/header';
import Body from '../components/body'
import Card from '../components/card'
import '../style.less'
const Bigscreen = (props) => {
return (
<div className='project-group'>
<Header />
<Body>
<Card>
123
</Card>
</Body>
</div>
)
}
function mapStateToProps (state) {
const { auth, global, } = state;
return {
user: auth.user,
actions: global.actions,
};
}
export default connect(mapStateToProps)(Bigscreen);

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

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

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

@ -1,23 +0,0 @@
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);

31
web/client/src/sections/projectGroup/containers/statistic.jsx

@ -0,0 +1,31 @@
import React, { useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux';
import Header from '../components/header';
import Body from '../components/body'
import Card from '../components/card'
import '../style.less'
const Statistic = (props) => {
return (
<div className='project-group'>
<Header />
<Body>
<Card title="12312312">
123
</Card>
</Body>
</div>
)
}
function mapStateToProps (state) {
const { auth, global, } = state;
return {
user: auth.user,
actions: global.actions,
};
}
export default connect(mapStateToProps)(Statistic);

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

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

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

@ -0,0 +1,3 @@
.project-group {
font-family: PangMenZhengDaoBiaoTiTi;
}

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

@ -18,6 +18,9 @@ export const ApiTable = {
login: "login",
logout: "logout",
// 天气
weatherRealtime: 'weather/realtime',
//设置-鉴权管理
getOrganizationDeps: 'organization/deps',//获取项企(PEP)全部部门及其下用户
getOrganizationUser: 'organization/user',//获取成员列表

Loading…
Cancel
Save