Browse Source

(*)社会治理一体化提交

master
peng.peng 1 year ago
parent
commit
49d7f733e4
  1. 19
      super-screen/.babelrc
  2. 23
      super-screen/.vscode/launch.json
  3. 4
      super-screen/.vscode/settings.json
  4. 21
      super-screen/Dockerfile
  5. 25
      super-screen/client/index.ejs
  6. 28
      super-screen/client/index.html
  7. 19
      super-screen/client/index.js
  8. 27
      super-screen/client/src/app.js
  9. 316
      super-screen/client/src/components/Upload/index.js
  10. 339
      super-screen/client/src/components/UploadLocal/index.js
  11. 391
      super-screen/client/src/components/Uploads/index.js
  12. 51
      super-screen/client/src/components/buttonGroup/index.js
  13. 683
      super-screen/client/src/components/export/index.js
  14. 13
      super-screen/client/src/components/export/index.less
  15. 18
      super-screen/client/src/components/index.js
  16. 21
      super-screen/client/src/components/no-resource/index.js
  17. 7
      super-screen/client/src/index.js
  18. 27
      super-screen/client/src/layout/actions/global.js
  19. 15
      super-screen/client/src/layout/components/footer/index.js
  20. 5
      super-screen/client/src/layout/components/footer/style.css
  21. 100
      super-screen/client/src/layout/components/header/index.js
  22. 41
      super-screen/client/src/layout/components/header/style.css
  23. 95
      super-screen/client/src/layout/components/sider/index.js
  24. 6
      super-screen/client/src/layout/containers/index.js
  25. 48
      super-screen/client/src/layout/containers/layout/breadcrumb.js
  26. 132
      super-screen/client/src/layout/containers/layout/index.js
  27. 3
      super-screen/client/src/layout/containers/layout/index.less
  28. 18
      super-screen/client/src/layout/containers/no-match/index.js
  29. 179
      super-screen/client/src/layout/index.js
  30. 28
      super-screen/client/src/layout/reducers/ajaxResponse.js
  31. 36
      super-screen/client/src/layout/reducers/global.js
  32. 15
      super-screen/client/src/layout/reducers/index.js
  33. 16
      super-screen/client/src/layout/store/index.js
  34. 30
      super-screen/client/src/layout/store/store.dev.js
  35. 20
      super-screen/client/src/layout/store/store.prod.js
  36. 62
      super-screen/client/src/sections/auth/actions/auth.js
  37. 10
      super-screen/client/src/sections/auth/actions/index.js
  38. 4
      super-screen/client/src/sections/auth/containers/index.js
  39. 158
      super-screen/client/src/sections/auth/containers/login.js
  40. 12
      super-screen/client/src/sections/auth/index.js
  41. 40
      super-screen/client/src/sections/auth/reducers/auth.js
  42. 6
      super-screen/client/src/sections/auth/reducers/index.js
  43. 12
      super-screen/client/src/sections/auth/routes.js
  44. 112
      super-screen/client/src/sections/auth/style.less
  45. 15
      super-screen/client/src/sections/homePage/actions/example.js
  46. 7
      super-screen/client/src/sections/homePage/actions/index.js
  47. 49
      super-screen/client/src/sections/homePage/components/abnormalMonitoring.js
  48. 36
      super-screen/client/src/sections/homePage/components/accessData.js
  49. 52
      super-screen/client/src/sections/homePage/components/alarmList.js
  50. 19
      super-screen/client/src/sections/homePage/components/centerTop.js
  51. 54
      super-screen/client/src/sections/homePage/components/dataShare.js
  52. 262
      super-screen/client/src/sections/homePage/components/dataTop5.js
  53. 42
      super-screen/client/src/sections/homePage/components/hotspotData.js
  54. 39
      super-screen/client/src/sections/homePage/components/nodeResource.js
  55. 33
      super-screen/client/src/sections/homePage/components/public/carousel-list.js
  56. 80
      super-screen/client/src/sections/homePage/components/public/index.less
  57. 18
      super-screen/client/src/sections/homePage/components/public/noData.js
  58. 469
      super-screen/client/src/sections/homePage/components/public/scrollBoard/index.js
  59. 44
      super-screen/client/src/sections/homePage/components/public/scrollBoard/style.less
  60. 34
      super-screen/client/src/sections/homePage/components/public/table-card.js
  61. 373
      super-screen/client/src/sections/homePage/components/style.less
  62. 3
      super-screen/client/src/sections/homePage/components/util.js
  63. 74
      super-screen/client/src/sections/homePage/containers/homePage.js
  64. 6
      super-screen/client/src/sections/homePage/containers/index.js
  65. 224
      super-screen/client/src/sections/homePage/containers/style.less
  66. 15
      super-screen/client/src/sections/homePage/index.js
  67. 11
      super-screen/client/src/sections/homePage/nav-item.js
  68. 5
      super-screen/client/src/sections/homePage/reducers/index.js
  69. 13
      super-screen/client/src/sections/homePage/routes.js
  70. 0
      super-screen/client/src/sections/homePage/style.less
  71. 10
      super-screen/client/src/styles/antd.less
  72. 16
      super-screen/client/src/styles/theme.less
  73. 5
      super-screen/client/src/utils/authCode.js
  74. 13
      super-screen/client/src/utils/func.js
  75. 30
      super-screen/client/src/utils/hooks.js
  76. 12
      super-screen/client/src/utils/index.js
  77. 131
      super-screen/client/src/utils/pinyin.js
  78. 13961
      super-screen/client/src/utils/region.js
  79. 172
      super-screen/client/src/utils/webapi.js
  80. 145
      super-screen/config.js
  81. 15
      super-screen/jsconfig.json
  82. 23
      super-screen/middlewares/attachment.js
  83. 16
      super-screen/middlewares/proxy.js
  84. 53
      super-screen/middlewares/webpack-dev.js
  85. 101
      super-screen/package.json
  86. 231
      super-screen/readme.md
  87. 240
      super-screen/routes/attachment/index.js
  88. 20
      super-screen/routes/index.js
  89. 8
      super-screen/server.js
  90. 2599
      super-screen/typings/node/node.d.ts
  91. 2517
      super-screen/typings/react/react.d.ts
  92. 72
      super-screen/webpack.config.js
  93. 76
      super-screen/webpack.config.prod.js

19
super-screen/.babelrc

@ -0,0 +1,19 @@
{
"presets": [
"@babel/preset-react",
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
["import", {
"libraryName": "antd",
"libraryDirectory": "es"
}]
],
"env": {
"development": {}
}
}

23
super-screen/.vscode/launch.json

@ -0,0 +1,23 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Server",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/server.js",
"args": [
"-u http://127.0.0.1:4000",
//OSS
"--aliOssAccessKey LTAI5tNDfn7UhStYQcn3JBtw",
"--aliOssSecretKey rnoXtDWQA1djJ5Xqcdn1OSEol0lVyv",
"--aliOssBucket test-c371",
"--aliOssRegion oss-cn-hangzhou",
],
"outputCapture": "std",
"env": {
"NODE_ENV": "development"
}
}
]
}

4
super-screen/.vscode/settings.json

@ -0,0 +1,4 @@
//
{
"editor.fontSize": 16,
}

21
super-screen/Dockerfile

@ -0,0 +1,21 @@
FROM repository.anxinyun.cn/base-images/nodejs12:20.10.12.2
COPY . /var/app
WORKDIR /var/app
EXPOSE 8080
RUN npm cache clean -f
RUN npm install --registry http://10.8.30.22:7000 --legacy-peer-deps
RUN npm run build
RUN rm -rf client/src
RUN rm -rf node_modules
RUN npm install --production --registry http://10.8.30.22:7000
#RUN npm cache clean -f && npm install --production --force --registry http://10.8.30.22:7000
CMD ["-u", "http://localhost:8088"]
ENTRYPOINT [ "node", "server.js" ]

25
super-screen/client/index.ejs

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="shortcut icon" href="/assets/images/favicon.ico">
<link rel="stylesheet" type="text/css" href="/assets/font_sc/iconfont.css">
</head>
<style>
@font-face {
font-family: YouSheBiaoTiHei;
src: url("/assets/font_sc/YouSheBiaoTiHei-2.ttf");
}
@font-face {
font-family: D-DIN-Bold;
src: url("/assets/font_sc/D-DIN-Bold.ttf");
}
</style>
<body style="background: transparent">
<div id='App'></div>
</body>
</html>

28
super-screen/client/index.html

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<link rel="shortcut icon" href="/assets/images/favicon.ico">
<link rel="stylesheet" type="text/css" href="/assets/font_sc/iconfont.css">
</head>
<style>
@font-face {
font-family: YouSheBiaoTiHei;
src: url("/assets/font_sc/YouSheBiaoTiHei-2.ttf");
}
@font-face {
font-family: D-DIN-Bold;
src: url("/assets/font_sc/D-DIN-Bold.ttf");
}
</style>
<body style="background: #0F1C2A;">
<div id='App'></div>
<script type="text/javascript" src="http://localhost:5401/client/build/vendor.js"></script>
<script type="text/javascript" src="http://localhost:5401/client/build/app.js"></script>
</body>
</html>

19
super-screen/client/index.js

@ -0,0 +1,19 @@
/**
* User: liuxinyi/liu.xinyi@free-sun.com.cn
* Date: 2016/2/22
* Time: 15:29
*
*/
'use strict';
const views = require('koa-view');
const path = require('path');
module.exports = {
entry: function (app, router, opt) {
app.use(views(__dirname));
router.get('(.*)', async function (ctx){
await ctx.render(path.join(__dirname, './index'));
});
}
};

27
super-screen/client/src/app.js

@ -0,0 +1,27 @@
'use strict';
import React, { useEffect } from 'react';
import Layout from './layout';
import Auth from './sections/auth';
import homePage from './sections/homePage';
const App = props => {
const { projectName } = props
useEffect(() => {
document.title = projectName;
}, [])
return (
<Layout
title={projectName}
sections={[
homePage,
Auth
]}
/>
)
}
export default App;

316
super-screen/client/src/components/Upload/index.js

@ -0,0 +1,316 @@
'use strict';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Spin, Upload, message, Modal, Card, Button } from 'antd';
import moment from 'moment';
import { PlusOutlined, UploadOutlined, CloseOutlined } from '@ant-design/icons';
class Uploads extends Component {
constructor(props) {
super(props);
this.ApiRoot = FS_API_ROOT
this.state = {
fileUploading: false,
fileList: [],
curPreviewPic: '',
delPicIng: false,
removeFilesList: []
};
}
dealName = (uploaded) => {
let realName = uploaded.split('/')[2]
let x1 = realName.split('.')
let x2 = x1[0].split('_')
let showName = `${x2[0]}.${x1[1]}`
return showName
}
// setFileList = (value) => {
// let defaultFileList = [];
// defaultFileList = value.map((u, index) => {
// let fileUrl = `${this.ApiRoot}/${u.url}`;
// return {
// uid: -index - 1,
// name: this.dealName(u.url),
// status: 'done',
// storageUrl: u.url,
// url: fileUrl
// };
// });
// onChange(defaultFileList)
// this.setState({
// fileList: defaultFileList
// });
// };
componentDidMount () {
const { value } = this.props;
if (value) {
this.setState(value);
}
}
componentWillReceiveProps (np) {
const { dispatch, value: thisEditData, onChange } = this.props;
const { value: nextEditData } = np;
const setFileList = () => {
let defaultFileList = [];
defaultFileList = nextEditData.map((u, index) => {
let fileUrl = `${this.ApiRoot}/${u.storageUrl}`;
return {
uid: -index - 1,
name: this.dealName(u.storageUrl),
status: 'done',
storageUrl: u.storageUrl,
url: fileUrl,
size: u.size || -1
};
});
this.setState({
fileList: defaultFileList
});
};
if (nextEditData && nextEditData.length) {
if (!thisEditData || !this.state.fileList.length) {
setFileList();
} else if (nextEditData.length != thisEditData.length) {
setFileList();
} else {
let repeat = true;
for (let i = 0; i < thisEditData.length; i++) {
if (thisEditData[i] != nextEditData[i]) {
repeat = false;
break;
}
}
if (!repeat) {
setFileList();
}
}
}
// else{
// this.setState({
// fileList:[],
// })
// }
}
render () {
const UploadPath = {
project: ['txt', 'dwg', 'doc', 'docx', 'xls', 'xlsx', 'pdf', 'png', 'jpg', 'rar', 'zip'],
report: ['doc', 'docx', 'xls', 'xlsx', 'pdf'],
data: ['txt', 'xls', 'xlsx'],
image: ['png', 'jpg', 'svg', 'jpeg'],
three: ['js'],
video: ['mp4']
};
/**
* uploadType string 主要区别文件上传路径 以及类型 web/routes/attachment/index.js UploadPath key 值为准;默认 project;
* disabled boolean 上传是否可用
* maxFilesNum number 最大上传数量
* fileTypes array[string] 可允许上传的文件类型;
* maxFileSize number 单个文件最大大小 M
* listType antd upload 组件的属性
* onChange function 文件数量变化时候回调 返回文件
* value array[obj] 编辑数据 [{url:'xxx', [size:999]}]
* onStateChange function 文件状态改变回调函数 上传中 return { uploading:true/false }
*/
const {
uploadType,
disabled,
maxFilesNum,
fileTypes,
maxFileSize,
listType,
onChange,
value,
showUploadList,
onStateChange
} = this.props;
const { fileList, curPreviewPic, delPicIng, removeFilesList } = this.state;
const that = this;
let uploadType_ = uploadType || 'project';
let maxFilesNum_ = maxFilesNum || 1;
let defaultFileTypes = fileTypes || UploadPath[uploadType_];
const uploadProps = {
name: 'checkFile_',
multiple: false,
showUploadList: showUploadList || true,
action: `${this.ApiRoot}/attachments/${uploadType_}`,
listType: listType || 'text',
disabled: disabled,
beforeUpload: (file) => {
if (fileList.length >= maxFilesNum_) {
message.warning(`最多选择${maxFilesNum_}个文件上传`);
return false;
}
if (file.name.length > 60) {
message.warning(`文件名过长(大于60字符),请修改后上传`);
return false;
}
const extNames = file.name.split('.');
var reg = /^[\.\s\u4e00-\u9fa5a-zA-Z0-9_-]{0,}$/;
if (!reg.exec(file.name)) {
message.warning(`文件名包含除字母、汉字、数字、中划线、下划线之外的字符,请修改后上传`);
return false;
}
let isDAE = false;
if (extNames.length > 0) {
let fileType = extNames[extNames.length - 1].toLowerCase();
isDAE = defaultFileTypes.some((f) => f == fileType);
}
if (!isDAE) {
message.error(`只能上传 ${defaultFileTypes.join()} 格式的文件!`);
return false;
}
const isLt = file.size / 1024 / 1024 < (maxFileSize || 3);
if (!isLt) {
message.error(`文件必须小于${maxFileSize || 3}MB!`);
return false;
}
this.setState({
fileUploading: true
});
if (onStateChange) {
onStateChange({ uploading: true });
}
},
onChange (info) {
const status = info.file.status;
if (status === 'uploading') {
that.setState({
fileList: info.fileList
});
}
if (status === 'done') {
let { uploaded, url } = info.file.response;
let size = info.file.size;
let nextFileList = fileList;
nextFileList[nextFileList.length - 1] = {
uid: -moment().unix(),
name: that.dealName(uploaded),
status: 'done',
storageUrl: uploaded,
url: url,
size: size
};
onChange(nextFileList);
that.setState({
fileUploading: false,
fileList: nextFileList
});
if (onStateChange) {
onStateChange({ uploading: false });
}
} else if (status === 'error') {
that.setState({
fileUploading: false
});
message.error(`${info.file.name} 上传失败,请重试`);
if (onStateChange) {
onStateChange({ uploading: false });
}
}
},
onRemove (file) {
let nextFileList = [];
fileList.map((f, i) => {
if (f.uid != file.uid) {
nextFileList.push(f);
}
});
let nextRemoveFiles = removeFilesList.concat([file.storageUrl]);
if (curPreviewPic == file.url) {
that.setState({
curPreviewPic: ''
});
}
onChange(nextFileList);
that.setState({
fileList: nextFileList,
removeFilesList: nextRemoveFiles
});
},
onPreview (file) {
let filePostfix = file.url.split('.').pop();
filePostfix = filePostfix.toLowerCase();
if (UploadPath.image.some((img) => img == filePostfix)) {
that.setState({
curPreviewPic: file.url
});
} else {
message.warn('仅支持图片预览');
}
}
};
let fileList_ = fileList
// .map(f => {
// if (f.storageUrl) {
// let realName = f.storageUrl.split('/').pop()
// if (f.name != realName) {
// f.name = realName
// }
// }
// return f
// })
return (
<div>
<Spin spinning={delPicIng}>
<Upload {...uploadProps} fileList={fileList_}>
{
disabled ? (
''
) :
listType == 'picture-card' ?
(
fileList.length >= maxFilesNum_ ? null : (
<div style={{}}>
<PlusOutlined />
<div>上传图片</div>
</div>
)
) : (
<Button disabled={fileList.length >= maxFilesNum_} icon={<UploadOutlined />}> 文件上传 </Button>
)
}
</Upload>
{
curPreviewPic ? (
<Card
bodyStyle={{
padding: 8
}}
>
<div style={{ marginBottom: 8 }} >
<span>文件预览</span>
<span
style={{ float: 'right' }}
onClick={() => { this.setState({ curPreviewPic: '' }); }}
>
<CloseOutlined style={{ fontSize: 20 }} />
</span>
</div>
<img style={{ width: '100%' }} src={curPreviewPic}></img>
</Card>
) : ''
}
</Spin>
</div>
);
}
}
function mapStateToProps (state) {
const { auth } = state
return {
user: auth.user
};
}
export default connect(mapStateToProps)(Uploads);

339
super-screen/client/src/components/UploadLocal/index.js

@ -0,0 +1,339 @@
'use strict';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Spin, Upload, message, Modal, Card, Button } from 'antd';
import moment from 'moment';
import { PlusOutlined, UploadOutlined, CloseOutlined } from '@ant-design/icons';
import { RouteRequest } from '@peace/utils';
import { RouteTable } from '$utils'
const { confirm } = Modal;
class Uploads extends Component {
constructor(props) {
super(props);
this.state = {
fileUploading: false,
fileList: [],
curPreviewPic: '',
delPicIng: false,
removeFilesList: []
};
}
dealName = (uploaded) => {
let realName = uploaded.split('/')[2]
let x1 = realName.split('.')
let x2 = x1[0].split('_')
let showName = `${x2[0]}.${x1[1]}`
return showName
}
// setFileList = (value) => {
// let defaultFileList = [];
// defaultFileList = value.map((u, index) => {
// let fileUrl = `${this.ApiRoot}/${u.url}`;
// return {
// uid: -index - 1,
// name: this.dealName(u.url),
// status: 'done',
// storageUrl: u.url,
// url: fileUrl
// };
// });
// onChange(defaultFileList)
// this.setState({
// fileList: defaultFileList
// });
// };
componentDidMount() {
const { value } = this.props;
if (value) {
// this.setState(value);
this.setState({ fileList: value })
}
}
UNSAFE_componentWillReceiveProps(np) {
const { dispatch, value: thisEditData, onChange } = this.props;
const { value: nextEditData } = np;
const setFileList = () => {
let defaultFileList = [];
defaultFileList = nextEditData.map((u, index) => {
let fileUrl = u.filename;
return {
uid: -index - 1,
name: u.name,
status: 'done',
storageUrl: u.filename,
url: fileUrl,
size: u.size || -1
};
});
this.setState({
fileList: defaultFileList
});
};
if (nextEditData && nextEditData.length) {
if (!thisEditData || !this.state.fileList.length) {
setFileList();
} else if (nextEditData.length != thisEditData.length) {
setFileList();
} else {
let repeat = true;
for (let i = 0; i < thisEditData.length; i++) {
if (thisEditData[i] != nextEditData[i]) {
repeat = false;
break;
}
}
if (!repeat) {
setFileList();
}
}
}
// else{
// this.setState({
// fileList:[],
// })
// }
}
//删除文件
deleteFile(file) {
if (file.url) {
RouteRequest.delete(RouteTable.cleanUpUploadTrash, { url: file.url });
};
}
handleOk = (that, file, fileList, curPreviewPic, removeFilesList) => {
let nextFileList = [];
fileList.map((f, i) => {
if (f.uid != file.uid) {
nextFileList.push(f);
}
});
that.deleteFile(file);
let nextRemoveFiles = removeFilesList.concat([file.storageUrl]);
if (curPreviewPic == file.url) {
that.setState({
curPreviewPic: ''
});
}
that.props.onChange(nextFileList);
that.setState({
fileList: nextFileList,
removeFilesList: nextRemoveFiles
});
}
render() {
const UploadPath = {
project: ['txt', 'dwg', 'doc', 'docx', 'xls', 'xlsx', 'pdf', 'png', 'jpg', 'rar', 'zip'],
report: ['doc', 'docx', 'xls', 'xlsx', 'pdf'],
data: ['txt', 'xls', 'xlsx'],
image: ['png', 'jpg', 'svg', 'jpeg'],
three: ['js'],
video: ['mp4']
};
/**
* uploadType string 主要区别文件上传路径 以及类型 web/routes/attachment/index.js UploadPath key 值为准;默认 project;
* disabled boolean 上传是否可用
* maxFilesNum number 最大上传数量
* fileTypes array[string] 可允许上传的文件类型;
* maxFileSize number 单个文件最大大小 M
* listType antd upload 组件的属性
* onChange function 文件数量变化时候回调 返回文件
* value array[obj] 编辑数据 [{url:'xxx', [size:999]}]
* onStateChange function 文件状态改变回调函数 上传中 return { uploading:true/false }
*/
const {
uploadType,
disabled,
maxFilesNum,
fileTypes,
maxFileSize,
listType,
onChange,
value,
showUploadList,
onStateChange,
addNew
} = this.props;
const { fileList, curPreviewPic, delPicIng, removeFilesList } = this.state;
const that = this;
let uploadType_ = uploadType || 'project';
let maxFilesNum_ = maxFilesNum || 1;
let defaultFileTypes = fileTypes || UploadPath[uploadType_];
const uploadProps = {
name: 'checkFile_',
multiple: false,
showUploadList: showUploadList || true,
action: "/_upload/new?type=project",
listType: listType || 'text',
disabled: disabled,
beforeUpload: (file) => {
if (fileList.length >= maxFilesNum_) {
message.warning(`最多选择${maxFilesNum_}个文件上传`);
return false;
}
if (file.name.length > 60) {
message.warning(`文件名过长(大于60字符),请修改后上传`);
return false;
}
const extNames = file.name.split('.');
var reg = /^[\.\s\u4e00-\u9fa5a-zA-Z0-9_-]{0,}$/;
// if (!reg.exec(file.name)) {
// message.warning(`文件名包含除字母、汉字、数字、中划线、下划线之外的字符,请修改后上传`);
// return false;
// }
let isDAE = false;
if (extNames.length > 0) {
let fileType = extNames[extNames.length - 1].toLowerCase();
isDAE = defaultFileTypes.some((f) => f == fileType);
}
if (!isDAE) {
message.error(`只能上传 ${defaultFileTypes.join()} 格式的文件!`);
return false;
}
const isLt = file.size / 1024 / 1024 < (maxFileSize || 3);
if (!isLt) {
message.error(`文件必须小于${maxFileSize || 3}MB!`);
return false;
}
this.setState({
fileUploading: true
});
if (onStateChange) {
onStateChange({ uploading: true });
}
},
onChange(info) {
const status = info.file.status;
if (status === 'uploading') {
that.setState({
fileList: info.fileList
});
}
if (status === 'done') {
let { filename, realName } = info.file.response;
let size = info.file.size;
let nextFileList = fileList;
nextFileList[nextFileList.length - 1] = {
uid: -moment().unix(),
name: info.file.name,
status: 'done',
storageUrl: filename,
url: filename,
size: size,
realName: realName
};
onChange(nextFileList);
that.setState({
fileUploading: false,
fileList: nextFileList
});
if (onStateChange) {
onStateChange({ uploading: false });
}
} else if (status === 'error') {
that.setState({
fileUploading: false
});
message.error(`${info.file.name} 上传失败,请重试`);
if (onStateChange) {
onStateChange({ uploading: false });
}
}
},
onRemove(file) {
// if (confirm('请确认是否删除此文件?删除后将不可恢复!') === true) {
if (addNew) {
that.handleOk(that, file, fileList, curPreviewPic, removeFilesList);
} else {
confirm({
title: '请确认是否删除此文件?删除后将不可恢复!',
onOk() {
that.handleOk(that, file, fileList, curPreviewPic, removeFilesList);
},
onCancel() { },
});
}
},
onPreview(file) {
let filePostfix = file.url.split('.').pop();
filePostfix = filePostfix.toLowerCase();
if (UploadPath.image.some((img) => img == filePostfix)) {
that.setState({
curPreviewPic: file.url
});
} else {
message.warn('仅支持图片预览');
}
}
};
let fileList_ = fileList
// .map(f => {
// if (f.storageUrl) {
// let realName = f.storageUrl.split('/').pop()
// if (f.name != realName) {
// f.name = realName
// }
// }
// return f
// })
return (
<div>
<Spin spinning={delPicIng}>
<Upload {...uploadProps} fileList={fileList_}>
{
disabled ? (
''
) :
listType == 'picture-card' ?
(
fileList.length >= maxFilesNum_ ? null : (
<div style={{}}>
<PlusOutlined />
<div>上传图片</div>
</div>
)
) : (
<Button disabled={fileList.length >= maxFilesNum_} icon={<UploadOutlined />}> 文件上传 </Button>
)
}
</Upload>
{
curPreviewPic ? (
<Card
bodyStyle={{
padding: 8
}}
>
<div style={{ marginBottom: 8 }} >
<span>文件预览</span>
<span
style={{ float: 'right' }}
onClick={() => { this.setState({ curPreviewPic: '' }); }}
>
<CloseOutlined style={{ fontSize: 20 }} />
</span>
</div>
<img style={{ width: '100%' }} src={curPreviewPic}></img>
</Card>
) : ''
}
</Spin>
</div>
);
}
}
function mapStateToProps(state) {
const { auth } = state
return {
user: auth.user
};
}
export default connect(mapStateToProps)(Uploads);

391
super-screen/client/src/components/Uploads/index.js

@ -0,0 +1,391 @@
'use strict';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Spin, Upload, message, Modal, Card, Button } from 'antd';
import moment from 'moment';
import { PlusOutlined, UploadOutlined, CloseOutlined } from '@ant-design/icons';
class Uploads extends Component {
constructor(props) {
super(props);
this.ApiRoot = localStorage.getItem('tyApiRoot')
this.qnDomain = localStorage.getItem('qnDomain');
this.aliAdmin = localStorage.getItem('aliAdmin');
this.state = {
fileUploading: false,
fileList: [],
curPreviewPic: '',
curPreviewVideo: '',
delPicIng: false,
removeFilesList: []
};
}
dealName = (uploaded) => {
let realName = uploaded.split('/')[2]
// let x1 = realName.split('.')
// let postfix = x1.pop()
// let allName = x1.join('.')
// let x2 = allName.split('_')
// let showName = `${x2[0]}.${postfix}`
return realName
}
// setFileList = (value) => {
// let defaultFileList = [];
// defaultFileList = value.map((u, index) => {
// let fileUrl = `${this.ApiRoot}/${u.url}`;
// return {
// uid: -index - 1,
// name: this.dealName(u.url),
// status: 'done',
// storageUrl: u.url,
// url: fileUrl
// };
// });
// onChange(defaultFileList)
// this.setState({
// fileList: defaultFileList
// });
// };
setFileList = (nextEditData, isQiniu, isAli) => {
let defaultFileList = [];
if (nextEditData.length) {
defaultFileList = nextEditData.map((u, index) => {
let fileUrl =
isQiniu ? `/_file-server/${u.storageUrl}`
: isAli ? `/_file-ali-server/${u.storageUrl}`
: `${this.ApiRoot}/${u.storageUrl}`;
return {
uid: -index - 1,
name: this.dealName(u.storageUrl),
status: 'done',
storageUrl: u.storageUrl,
url: fileUrl,
size: u.size || -1
};
});
}
this.setState({
fileList: defaultFileList
});
};
componentDidMount() {
const { value, defaultValue, isQiniu, isAli } = this.props;
if (defaultValue) {
this.setFileList(defaultValue, isQiniu, isAli)
}
}
UNSAFE_componentWillReceiveProps(np) {
const { dispatch, value: thisEditData, onChange } = this.props;
const { value: nextEditData, isQiniu, isAli } = np;
// this.setFileList(nextEditData, isQiniu)
// const setFileList = () => {
// let defaultFileList = [];
// defaultFileList = nextEditData.map((u, index) => {
// let fileUrl = isQiniu ? `/_file-server/${u.storageUrl}` : `${this.ApiRoot}/${u.storageUrl}`;
// return {
// uid: -index - 1,
// name: this.dealName(u.storageUrl),
// status: 'done',
// storageUrl: u.storageUrl,
// url: fileUrl,
// size: u.size || -1
// };
// });
// this.setState({
// fileList: defaultFileList
// });
// };
if (nextEditData && nextEditData.length) {
if (!thisEditData || !this.state.fileList.length) {
this.setFileList(nextEditData, isQiniu, isAli);
} else if (nextEditData.length != thisEditData.length) {
this.setFileList(nextEditData, isQiniu, isAli);
} else {
let repeat = true;
for (let i = 0; i < thisEditData.length; i++) {
if (thisEditData[i] != nextEditData[i]) {
repeat = false;
break;
}
}
if (!repeat) {
this.setFileList(nextEditData, isQiniu, isAli);
}
}
}
// else{
// this.setState({
// fileList:[],
// })
// }
}
render() {
const UploadPath = {
project: ['txt', 'dwg', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'pdf', 'pptx', 'png', 'jpg', 'svg', 'jpeg', 'rar', 'zip', 'jpeg', 'mp4'],
report: ['doc', 'docx', 'xls', 'xlsx', 'csv', 'pdf'],
data: ['txt', 'xls', 'xlsx', 'csv'],
image: ['png', 'jpg', 'svg', 'jpeg'],
three: ['js'],
video: ['mp4']
};
/**
* uploadType string 主要区别文件上传路径 以及类型 web/routes/attachment/index.js UploadPath key 值为准默认 project
* disabled boolean 上传是否可用
* maxFilesNum number 最大上传数量
* fileTypes array[string] 可允许上传的文件类型
* maxFileSize number 单个文件最大大小 M
* listType antd upload 组件的属性
* onChange function 文件数量变化时候回调 返回文件
* value array[obj] 编辑数据 [{url:'xxx', [size:999]}]
* onStateChange function 文件状态改变回调函数 上传中 return { uploading:true/false }
*/
const {
uploadType,
disabled,
maxFilesNum,
fileTypes,
maxFileSize,
listType,
onChange = () => { },
value,
showUploadList,
onStateChange,
isQiniu,
isAli,
} = this.props;
const { fileList, curPreviewPic, curPreviewVideo, delPicIng, removeFilesList } = this.state;
const that = this;
let uploadType_ = uploadType || 'project';
let maxFilesNum_ = maxFilesNum || 1;
let defaultFileTypes = fileTypes || UploadPath[uploadType_];
// debugger
const uploadProps = {
name: 'checkFile_',
multiple: false,
showUploadList: showUploadList || true,
action:
isQiniu ? `/_upload/attachments/${uploadType_}`
: isAli ? `/_upload/attachments/ali/${uploadType_}`
: `${this.ApiRoot}/attachments/${uploadType_}`,
listType: listType || 'text',
disabled: disabled,
beforeUpload: (file) => {
if (fileList.length >= maxFilesNum_) {
message.warning(`最多选择${maxFilesNum_}个文件上传`);
return false;
}
if (file.name.length > 60) {
message.warning(`文件名过长(大于60字符),请修改后上传`);
return false;
}
const extNames = file.name.split('.');
// var reg = /^[\.\s\u4e00-\u9fa5a-zA-Z0-9_-]{0,}$/;
// if (!reg.exec(file.name)) {
// message.warning(`文件名包含除字母、汉字、数字、中划线、下划线之外的字符,请修改后上传`);
// return false;
// }
let isDAE = false;
if (extNames.length > 0) {
let fileType = extNames[extNames.length - 1].toLowerCase();
isDAE = defaultFileTypes.some((f) => f == fileType);
}
if (!isDAE) {
message.error(`只能上传 ${defaultFileTypes.join()} 格式的文件!`);
return false;
}
const isLt = file.size / 1024 / 1024 < (maxFileSize || 3);
if (!isLt) {
message.error(`文件必须小于${maxFileSize || 3}MB!`);
return false;
}
this.setState({
fileUploading: true
});
if (onStateChange) {
onStateChange({ uploading: true });
}
},
onChange(info) {
const status = info.file.status;
if (status === 'uploading') {
that.setState({
fileList: info.fileList
});
}
if (status === 'done') {
let { uploaded, url } = info.file.response;
let size = info.file.size;
let nextFileList = fileList;
nextFileList[nextFileList.length - 1] = {
uid: -moment().unix(),
name: that.dealName(uploaded),
status: 'done',
storageUrl: uploaded,
url:
isQiniu ? '/_file-server/' + uploaded :
isAli ? `/_file-ali-server/${uploaded}` :
url,
size: size
};
onChange(nextFileList);
that.setState({
fileUploading: false,
fileList: nextFileList
});
if (onStateChange) {
onStateChange({ uploading: false });
}
} else if (status === 'error') {
that.setState({
fileUploading: false
});
message.error(`${info.file.name} 上传失败,请重试`);
if (onStateChange) {
onStateChange({ uploading: false });
}
}
},
onRemove(file) {
let nextFileList = [];
fileList.map((f, i) => {
if (f.uid != file.uid) {
nextFileList.push(f);
}
});
let nextRemoveFiles = removeFilesList.concat([file.storageUrl]);
if (curPreviewPic == file.url) {
that.setState({
curPreviewPic: ''
});
}
if (curPreviewVideo == file.url) {
that.setState({
curPreviewVideo: ''
});
}
onChange(nextFileList);
that.setState({
fileList: nextFileList,
removeFilesList: nextRemoveFiles
});
},
onPreview(file) {
let filePostfix = file.url.split('.').pop();
filePostfix = filePostfix.toLowerCase();
if (UploadPath.image.some((img) => img == filePostfix)) {
that.setState({
curPreviewPic: file.url
});
} else if (UploadPath.video.some((img) => img == filePostfix)) {
that.setState({
curPreviewVideo: file.url
});
} else {
//message.warn('仅支持图片预览');
preview(file.storageUrl)
}
}
};
const preview = (url) => {
let link = isQiniu ? encodeURI(`${this.qnDomain}/${url}`) :
isAli ? encodeURI(`${this.aliAdmin}/${url}`) : ''
if (link)
if (url.indexOf("pdf") !== -1 || url.indexOf("csv") !== -1) {
window.open(link)
} else {
window.open(`https://view.officeapps.live.com/op/view.aspx?src=${link}`)
}
}
let fileList_ = fileList
// .map(f => {
// if (f.storageUrl) {
// let realName = f.storageUrl.split('/').pop()
// if (f.name != realName) {
// f.name = realName
// }
// }
// return f
// })
//下载文件
const handleDownload = (file) => {
saveAs(file)
};
const saveAs = (file) => {
const link = document.createElement('a');
link.href = file.url;
link.download = file.name;
link.style.display = 'none';
link.click();
}
//自定义下载
return (
<div>
<Spin spinning={delPicIng}>
<Upload {...uploadProps} fileList={fileList_} showUploadList={{ showDownloadIcon: true }} onDownload={handleDownload}>
{
disabled ? (
''
) :
listType == 'picture-card' ?
(
fileList.length >= maxFilesNum_ ? null : (
<div style={{}}>
<PlusOutlined />
<div>添加附件</div>
</div>
)
) : (
<Button disabled={fileList.length >= maxFilesNum_} icon={<UploadOutlined />}> 文件上传 </Button>
)
}
</Upload>
{
curPreviewPic ? (
<Card bodyStyle={{ padding: 8 }}>
<div style={{ marginBottom: 8 }} >
<span>图片预览</span>
<span style={{ float: 'right' }} onClick={() => { this.setState({ curPreviewPic: '' }) }}>
<CloseOutlined style={{ fontSize: 20 }} />
</span>
</div>
<img style={{ width: '100%' }} src={curPreviewPic} />
</Card>
) : ''
}
{
curPreviewVideo ? (<Card bodyStyle={{ padding: 8 }}>
<div style={{ marginBottom: 8 }} >
<span>视频预览</span>
<span style={{ float: 'right' }} onClick={() => { this.setState({ curPreviewVideo: '' }) }}>
<CloseOutlined style={{ fontSize: 20 }} />
</span>
</div>
<video controls style={{ width: '100%' }}>
<source src={curPreviewVideo} type="video/mp4"></source>
</video>
</Card>) : ''
}
</Spin>
</div>
);
}
}
function mapStateToProps(state) {
const { auth } = state
return {
user: auth.user
};
}
export default connect(mapStateToProps)(Uploads);

51
super-screen/client/src/components/buttonGroup/index.js

@ -0,0 +1,51 @@
'use strict';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Button, Popover, Icon } from 'antd';
import { EllipsisOutlined } from '@ant-design/icons';
class ButtonGroup extends Component {
constructor(props) {
super(props);
this.state = {
};
}
content = () => {
<Button></Button>
}
render_ = () => {
const { children } = this.props
return (
<div style={{ cursor: 'pointer' }}>
<Popover placement="bottomRight" content={children} arrowPointAtCenter>
<EllipsisOutlined style={{ fontSize: 20, fontWeight: 'bolder' }} />
</Popover>
</div >
)
}
render() {
const { children } = this.props
if (children) {
if (Array.isArray(children)) {
if (children.some(c => c)) {
return this.render_()
}
} else {
return this.render_()
}
}
return ''
}
}
function mapStateToProps(state) {
return {
}
}
export default connect(mapStateToProps)(ButtonGroup);

683
super-screen/client/src/components/export/index.js

@ -0,0 +1,683 @@
/**
* Created by Xumeng 2020/04/22.
*/
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import {
Row, Col, Space, Button, message, notification, Form, Input, Tooltip,
} from 'antd';
import moment from 'moment';
import XLSX from 'xlsx';
import { fromJS } from 'immutable';
import { Request } from '@peace/utils';
import FileSaver from 'file-saver';
import './index.less';
// 通用前端导入导出组件 使用方法查看底部propTypes
function ExportAndImport (props) {
const [form] = Form.useForm();
const [exportLoading, setExportLoading] = useState(false);
const [importLoading, setImportLoading] = useState(false);
const {
importDataCallback, onImportSucess, handelData, importMethod = 'post',
} = props;
useEffect(() => () => {
// 只有unmount 时调用
notification.close('import-notification');
});
const importExcel = (file, type) => {
setImportLoading(true);
// 获取上传的文件对象
const { files } = file.target;
// 判断xls、xlsx格式
if (files[0].type.indexOf('sheet') > -1 || files[0].type.indexOf('ms-excel') > -1) {
// 通过FileReader对象读取文件
const fileReader = new FileReader();
fileReader.onload = (event) => {
try {
const { importRequest = true, importUrl, importQuery } = props;
if (importRequest && !importUrl) {
message.error('获取导入接口失败!');
form.resetFields();
return;
}
const { result } = event.target;
// 以二进制流方式读取得到整份excel表格对象
const workbook = XLSX.read(result, { type: 'binary', cellDates: true });
let data = []; // 存储获取到的数据
// 遍历每张工作表进行读取(这里默认只读取第一张表)
for (const sheet in workbook.Sheets) {
if (workbook.Sheets.hasOwnProperty(sheet)) {
// 利用 sheet_to_json 方法将 excel 转成 json 数据
data = data.concat(XLSX.utils.sheet_to_json(workbook.Sheets[sheet]));
break; // 如果只取第一张表,就取消注释这行
}
}
if (data.length > 10000) {
message.error('一次最多导入10000条数据,请分批导入!');
form.resetFields();
setImportLoading(false);
return;
}
if (importRequest) {
message.success('获取文件数据成功,开始处理导入...');
const importData = handelData ? handelData(data) : data;
Request[importMethod](importUrl, { data: importData }, importQuery || {}).then((res) => {
message.success('导入数据成功');
form.resetFields();
notification.close('import-notification');
setImportLoading(false);
onImportSucess && onImportSucess();
}, (err) => {
if (err.status === 500) {
message.error('数据导入出错,导入失败');
} else if (err.status === 400) {
message.error(err.body.message || '数据验证出错,请检查数据格式是否正确');
} else {
message.error('导入失败');
}
form.resetFields();
setImportLoading(false);
});
} else {
form.resetFields();
setImportLoading(false);
importDataCallback && importDataCallback(data, type);
notification.close('import-notification');
}
} catch (e) {
console.log(e);
// 这里可以抛出文件类型错误不正确的相关提示
message.error('文件格式不正确!');
setImportLoading(false);
form.resetFields();
}
};
// fileReader.onloadend = (event) => {
// console.log(event)
// }
// 以二进制方式打开文件
fileReader.readAsBinaryString(files[0]);
} else {
message.error('文件格式不正确!');
form.resetFields();
setImportLoading(false);
}
};
const loop = (data, keypath, values) => { // deal with array
const dkey = keypath.slice(0, 1)[0];
console.log(dkey);
if (dkey) {
const dvalue = data[dkey];
const otherKeypath = keypath.slice(1);
if (Array.isArray(dvalue)) {
if (otherKeypath.length) {
const immutableData = fromJS(data);
for (let index = 0; index < dvalue.length; index++) {
const tmp = immutableData.getIn([dkey, index]).toJS();
loop(tmp, otherKeypath, values);
}
}
} else {
values.push(dvalue);
}
}
return values;
};
const getColumnData = (opts) => {
const {
data, keypath, render, spliter, rawdata,
} = opts;
let v = null;
const outer = data[keypath[0]];
if (Array.isArray(outer)) {
const values = loop(data, keypath, []);
v = rawdata ? values : values.join(spliter || ',');
} else {
v = fromJS(data).getIn(keypath);
}
// 处理render
if (render && typeof render === 'function') {
v = render(outer, data);
}
return v;
};
const getDataSource = (attrs, filterData) => {
// let token = JSON.parse(sessionStorage.getItem('user')).token;
const dataSource = filterData.map((item) => {
const record = {};
attrs.forEach((attr) => {
const {
key, dataIndex, render, child,
} = attr;
if (child) {
record[key] = getDataSource(child, item[key]);
} else {
const v = getColumnData({
data: item,
keypath: dataIndex || [key],
render: render || null,
});
record[key] = v;
}
});
return record;
});
return dataSource;
};
// 暂时只处理两层
const getFlatData = (attrs, filterData, dataToAoa, deep = 0) => {
filterData.map((item) => {
let cur = dataToAoa[deep];
if (!cur) {
cur = dataToAoa[deep] = [];
}
attrs.map((attr, index) => {
const { key, child } = attr;
if (child) {
if (Array.isArray(item[key])) {
// getFlatData(child,item[key],dataToAoa,deep)
item[key].map((s, i) => {
if (i == 0) {
child.map((c) => {
cur.push(s[c.key]);
});
} else {
deep++;
const childCur = dataToAoa[deep] = [];
pushNull(childCur, index);
child.map((c) => {
childCur.push(s[c.key]);
});
}
});
}
} else {
cur.push(item[key]);
}
});
deep++;
});
};
const getHeader = (headers, excelHeader, deep, perOffset) => {
let offset = 0;
let cur = excelHeader[deep];
if (!cur) {
cur = excelHeader[deep] = [];
}
pushNull(cur, perOffset - cur.length);
for (let i = 0; i < headers.length; i++) {
const head = headers[i];
cur.push(head.name);
if (head.hasOwnProperty('child') && Array.isArray(head.child) && head.child.length > 0) {
const childOffset = getHeader(head.child, excelHeader, deep + 1, cur.length - 1);
pushNull(cur, childOffset - 1);
offset += childOffset;
} else {
offset++;
}
}
return offset;
};
const pushNull = (arr, count) => {
for (let i = 0; i < count; i++) {
arr.push(null);
}
};
const fillNull = (arr) => {
const max = Math.max(...(arr.map((a) => a.length)));
arr.filter((e) => e.length < max).forEach((e) => pushNull(e, max - e.length));
};
const doMerges = (arr) => {
// 要么横向合并 要么纵向合并
const deep = arr.length;
const merges = [];
for (let y = 0; y < deep; y++) {
// 先处理横向合并
const row = arr[y];
let colSpan = 0;
for (let x = 0; x < row.length; x++) {
if (row[x] === null) {
colSpan++;
if (((x + 1) === row.length) && (colSpan > 0 && x > colSpan)) {
merges.push({ s: { r: y, c: x - colSpan }, e: { r: y, c: x } });
}
} else if (colSpan > 0 && x > colSpan) {
merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x - 1 } });
colSpan = 0;
} else {
colSpan = 0;
}
}
}
// 再处理纵向合并
const colLength = arr[0].length;
for (let x = 0; x < colLength; x++) {
let rowSpan = 0;
for (let y = 0; y < deep; y++) {
if (arr[y][x] != null) {
rowSpan = 0;
} else {
rowSpan++;
}
}
if (rowSpan > 0) {
merges.push({ s: { r: deep - rowSpan - 1, c: x }, e: { r: deep - 1, c: x } });
}
}
return merges;
};
// 内容暂只出了纵向合并
const doContetMerges = (arr, headerLength) => {
const deep = arr.length;
const merges = [];
// 处理纵向合并
const colLength = arr[0].length;
for (let x = 0; x < colLength; x++) {
let rowSpan = 0;
const mergY = 0;
for (let y = 0; y < deep; y++) {
if (rowSpan > 0) {
// 如果还有null 继续加
if (arr[y][x] === null) {
rowSpan += 1;
} else {
// 不为null 增加merge
merges.push({ s: { r: headerLength + (y - rowSpan - 1), c: x }, e: { r: headerLength + y - 1, c: x } });
rowSpan = 0;
}
} else if (arr[y][x] === null) {
rowSpan += 1;
}
}
if (rowSpan > 0) {
merges.push({ s: { r: headerLength + (deep - rowSpan - 1), c: x }, e: { r: headerLength + deep - 1, c: x } });
rowSpan = 0;
}
}
return merges;
};
const exportMergeExcel = async () => {
setExportLoading(true);
const {
column, data, fileName, exportUrl, exportQuery, exportBody, requestType, header, showYearMouth,
} = props || {};
let resultData = [];
if (exportUrl) {
resultData = requestType == 'post' ? await Request.post(exportUrl, exportBody || {}, exportQuery || {}).then((data) => {
// 数据接口返回的结果 如果是对象 必须把返回数组放入rows
if (typeof data === 'object' && data.rows) {
return data.rows;
}
return data;
}, (err) => {
message.error('获取数据失败,导出失败!');
}) : await Request.get(exportUrl, exportQuery || {}).then((data) => {
if (typeof data === 'object' && data.rows) {
return data.rows;
}
return data;
}, (err) => {
message.error('获取数据失败,导出失败!');
});
if (!resultData) {
return;
}
} else {
resultData = data;
}
const excelHeader = [];
getHeader(column, excelHeader, 0, 0);
fillNull(excelHeader);
// console.log(excelHeader);
const loopData = getDataSource(column, resultData);
// console.log(loopData)
const dataToAoa = [];
getFlatData(column, loopData, dataToAoa, 0);
fillNull(dataToAoa);
// console.log(dataToAoa);
const aoa = [].concat(excelHeader, dataToAoa);
// console.log(aoa)
const headerMerges = doMerges(excelHeader);
const contentMerages = doContetMerges(dataToAoa, excelHeader.length);
const merges = [].concat(headerMerges, contentMerages);
// console.log(contentMerages)
// let opts = {
// defaultCellStyle: {
// font: { name: "宋体", sz: 11, color: { auto: 1 } },
// border: {
// color: { auto: 1 }
// },
// alignment: {
// /// 自动换行
// wrapText: 1,
// // 居中
// horizontal: "center",
// vertical: "center",
// indent: 0
// }
// }
// }
const sheet = XLSX.utils.aoa_to_sheet(aoa);
// let newSheet = {};
// for (let [key, value] of Object.entries(sheet)) {
// if(key == '!ref'){
// newSheet[key] = value
// }else if(typeof value === 'object'){
// newSheet[key] = {
// ...value,
// s: opts.defaultCellStyle
// }
// }
// }
const wpx = column.map((c) => ({
wpx: Number.parseInt(c.wpx) ? Number.parseInt(c.wpx) : 100,
}));
sheet['!cols'] = wpx;
sheet['!merges'] = merges;
// 构建 workbook 对象
const workbook = XLSX.utils.book_new();
const time = moment().format('YYYY-MM-DD');
XLSX.utils.book_append_sheet(workbook, sheet, 'mySheet');
// 导出 Excel
XLSX.writeFile(workbook, fileName ? `${fileName}-${time}.xlsx` : '导出数据.xlsx');
setExportLoading(false);
// message.success(`成功导出了 ${loopData.length || 0} 条数据`);
};
const exportProExcel = async () => {
setExportLoading(true);
const {
column, data, fileName, exportUrl, exportQuery, exportBody, requestType, showYearMouth,
} = props || {};
let resultData = [];
if (exportUrl) {
resultData = requestType == 'post' ? await Request.post(exportUrl, exportBody || {}, exportQuery || {}).then((data) => {
// 数据接口返回的结果 如果是对象 必须把返回数组放入rows
if (typeof data === 'object') {
return data.data ? data.data : data.rows;
}
return data;
}, (err) => {
message.error('获取数据失败,导出失败!');
}) : await Request.get(exportUrl, exportQuery || {}).then((data) => {
if (showYearMouth) {
}
if (typeof data === 'object' && data.rows) {
return data.rows;
}
return data;
}, (err) => {
message.error('获取数据失败,导出失败!');
});
if (!resultData) {
return;
}
} else {
resultData = data;
}
const loopData = getDataSource(column, resultData);
let content = '';
let header = '<tr>';
// header += `<th><div>序号</div></th>`;
column.map((colum) => {
header += `<th><div>${colum.name}</div></th>`;
});
header += '</tr>';
loopData.map((data) => {
content += '<tr>';
column.map((c) => {
if (c.style) {
content += `<th style="${c.style}"><div>${data[c.dataIndex || c.key]}</div></th>`;
} else {
content += `<th><div>${data[c.dataIndex || c.key]}</div></th>`;
}
});
content += '</tr>';
});
const exportTable = `\uFEFF
<table style='text-alagin:center' border="1">
${header}
${content}
</table>
`;
const time = moment().format('YYYY-MM-DD');
const tempStrs = new Blob([exportTable], { type: 'text/xls' });
FileSaver.saveAs(tempStrs, fileName ? `${fileName}-${time}.xls` : '导出数据.xls');
setExportLoading(false);
// message.success(`成功导出了 ${loopData.length || 0} 条数据`);
};
const exportExcel = async () => {
setExportLoading(true);
const {
column, data, fileName, exportUrl, exportQuery, exportBody, requestType,
} = props || {};
const _headers = column
.map((item, i) => ({ key: item.key, title: item.name, position: String.fromCharCode(65 + i) + 1 }))
.reduce((prev, next) => ({ ...prev, [next.position]: { key: next.key, v: next.title } }), {});
let resultData = [];
if (exportUrl) {
resultData = requestType == 'post' ? await Request.post(exportUrl, exportBody || {}, exportQuery || {}).then((data) => {
// 数据接口返回的结果 如果是对象 必须把返回数组放入rows
if (typeof data === 'object' && (data.rows || data.data)) {
return data.data ? data.data : data.rows;
}
return data;
}, (err) => {
message.error('获取数据失败,导出失败!');
}) : await Request.get(exportUrl, exportQuery || {}).then((data) => {
if (typeof data === 'object' && data.rows) {
return data.rows;
}
return data;
}, (err) => {
message.error('获取数据失败,导出失败!');
});
if (!resultData) {
return;
}
} else {
resultData = data;
}
const loopDate = getDataSource(column, resultData);
const wpx = column.map((c) => ({
wpx: Number.parseInt(c.wpx) ? Number.parseInt(c.wpx) : 100,
}));
if (!(loopDate.length > 0)) {
setExportLoading(false);
return;
}
const _data = loopDate
.map((item, i) => column.map((key, j) => ({ content: item[key.key], position: String.fromCharCode(65 + j) + (i + 2) })))
// 对刚才的结果进行降维处理(二维数组变成一维数组)
.reduce((prev, next) => prev.concat(next))
// 转换成 worksheet 需要的结构
.reduce((prev, next) => ({ ...prev, [next.position]: { v: next.content } }), {});
// 合并 column 和 data
const output = { ..._headers, ..._data };
// 获取所有单元格的位置
const outputPos = Object.keys(output);
// 计算出范围 ,["A1",..., "H2"]
const ref = `${outputPos[0]}:${outputPos[outputPos.length - 1]}`;
// 构建 workbook 对象
const workbook = {
SheetNames: ['mySheet'],
Sheets: {
mySheet: {
...output,
'!ref': ref,
'!cols': wpx,
},
},
};
const time = moment().format('YYYY-MM-DD');
// 导出 Excel
XLSX.writeFile(workbook, fileName ? `${fileName}-${time}.xlsx` : '导出数据.xlsx');
setExportLoading(false);
// message.success(`成功导出了 ${loopDate.length || 0} 条数据`);
};
const exportTemplete = async () => {
const { importTemColumn, importTemData, fileName } = props || {};
const _headers = importTemColumn
.map((item, i) => {
let group = 0; // 用于处理Z1的时候,重计算AA1
if (parseInt(i / 26) > group) {
group = parseInt(i / 26);
}
if (group > 0) { // AA1 BA1 CA1
const position = String.fromCharCode(65 + (group - 1));
return { key: item.key, title: item.name, position: position + String.fromCharCode(65 + (i % 26)) + 1 };
} return { key: item.key, title: item.name, position: String.fromCharCode(65 + i) + 1 };
})
.reduce((prev, next) => ({ ...prev, [next.position]: { key: next.key, v: next.title } }), {});
const loopDate = getDataSource(importTemColumn, importTemData);
const wpx = importTemColumn.map((c) => ({
wpx: Number.parseInt(c.wpx) ? Number.parseInt(c.wpx) : 100,
}));
const _data = loopDate.length ? loopDate
.map((item, i) => importTemColumn.map((key, j) => ({ content: item[key.key], position: String.fromCharCode(65 + j) + (i + 2) })))
// 对刚才的结果进行降维处理(二维数组变成一维数组)
.reduce((prev, next) => prev.concat(next))
// 转换成 worksheet 需要的结构
.reduce((prev, next) => ({ ...prev, [next.position]: { v: next.content } }), {}) : [];
// 合并 column 和 data
const output = { ..._headers, ..._data };
// 获取所有单元格的位置
const outputPos = Object.keys(output);
// 计算出范围 ,["A1",..., "H2"]
const ref = `${outputPos[0]}:${outputPos[outputPos.length - 1]}`;
// 构建 workbook 对象
const workbook = {
SheetNames: ['mySheet'],
Sheets: {
mySheet: {
...output,
'!ref': ref,
'!cols': wpx,
},
},
};
// 导出 Excel
XLSX.writeFile(workbook, fileName ? `${fileName}-导入模板.xlsx` : '导入模板.xlsx');
};
const tips = (type) => {
const { tips, templeteBth = true } = props;
const description = (
<div className="export-import">
{tips && tips}
<Row gutter={16}>
<Col span={12}>
<Form form={form} initialValues={{}}>
<Form.Item name="import-file">
<Input className="file-uploader" type="file" accept=".xlsx, .xls" onChange={(e) => importExcel(e, type)} />
<Button style={props.btnStyle} className={props.btnClass} loading={importLoading}>
选择文件
</Button>
</Form.Item>
</Form>
</Col>
{templeteBth && (
<Col span={12}>
<Button style={props.btnStyle} className={props.btnClass} onClick={exportTemplete}>
模板下载
</Button>
</Col>
)}
</Row>
</div>
);
notification.info({
message: '支持 .xlsx、.xls 格式的文件',
description,
key: 'import-notification',
duration: null,
});
};
return (
<Space>
{
props.import && (
<Button style={props.btnStyle} className={props.btnClass} loading={importLoading} onClick={tips}>
{props.importBtnName || '导入'}
</Button>
)
}
{
props.export && (
<Tooltip placement="top" title={props.exportBtnTips || '默认导出所有数据'}>
<Button style={props.btnStyle} className={props.btnClass} loading={exportLoading} onClick={props.exportType === 'pro' ? exportProExcel : exportExcel}>
{props.exportBtnName || '导出'}
</Button>
</Tooltip>
)
}
</Space>
);
}
ExportAndImport.propTypes = {
export: PropTypes.bool, // 是否显示导出按钮
exportBtnName: PropTypes.string, // 导出按钮文字
importBtnName: PropTypes.string, // 导入按钮文字
import: PropTypes.bool, // 是否显示导入按钮
variedImport: PropTypes.bool, // 是否显示多样导入
variedImportDisable: PropTypes.bool, // 多样导入禁用
variedImportBtnName: PropTypes.string, // 多样导入文字
column: PropTypes.array, // 导出显示的header数组 兼容antd column 可直接拿table的column使用 注:column每列的属性wpx设置导出的execl每列的宽度值 默认 100
data: PropTypes.array, // 导出的数据 兼容antd table 数组嵌套处理
exportUrl: PropTypes.string, // 导出数据从接口获取的url地址 返回的数据1、数组必须支持column的设置 ,2、如果是对象,数组需放在rows属性上
exportBody: PropTypes.object, // 导出数据接口body参数
exportQuery: PropTypes.object, // 导出数据从接口获取的url地址上的参数
exportBtnTips: PropTypes.string, // 导出按钮tips文字提示
importUrl: PropTypes.string, // 导入接口url
importQuery: PropTypes.object, // 导入接口url地址上的参数
btnClass: PropTypes.string, // 按钮className
btnStyle: PropTypes.object, // 按钮style
tips: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), // 上传文件提示的信息
onImportSucess: PropTypes.func, // 上传成功后 返回处理函数
importTemColumn: PropTypes.array, // 导入模板设置 头部字段数组
importTemData: PropTypes.array, // 导入模板默认数据
requestType: PropTypes.string, // 请求类型
importDataCallback: PropTypes.func, // 上传后数据返回
templeteBth: PropTypes.bool, // 模板按钮
importRequest: PropTypes.bool, // 请求导入接口,false时搭配importDataCallback,
exportType: PropTypes.string, // 导出执行的函数名
};
export default ExportAndImport;

13
super-screen/client/src/components/export/index.less

@ -0,0 +1,13 @@
.export-import {
.file-uploader {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
outline: none;
opacity: 0;
background-color: transparent;
z-index: 10;
}
}

18
super-screen/client/src/components/index.js

@ -0,0 +1,18 @@
'use strict';
import Upload from './Upload';
import Uploads from './Uploads';
import NoResource from './no-resource';
import ExportAndImport from './export';
import ButtonGroup from './buttonGroup';
import UploadLocal from './UploadLocal';
export {
Upload,
Uploads,
NoResource,
ExportAndImport,
ButtonGroup,
UploadLocal
};

21
super-screen/client/src/components/no-resource/index.js

@ -0,0 +1,21 @@
'use strict';
import React from 'react';
import { Result} from 'antd';
import { MehOutlined } from '@ant-design/icons';
class NoResource extends React.Component {
constructor(props) {
super(props);
}
render() {
const title = this.props.title ? this.props.title : "抱歉,没有可访问的资源!"
return (
<Result
icon={<MehOutlined />}
title={title}
/>
);
}
}
export default NoResource;

7
super-screen/client/src/index.js

@ -0,0 +1,7 @@
'use strict';
import React from 'react';
import { render } from 'react-dom';
import App from './app';
render((<App projectName="政务数据资源中心" />), document.getElementById('App'));

27
super-screen/client/src/layout/actions/global.js

@ -0,0 +1,27 @@
'use strict';
import { RouteRequest } from '@peace/utils';
import { RouteTable } from '$utils'
export const INIT_LAYOUT = 'INIT_LAYOUT';
export function initLayout (title, copyright, sections, actions) {
return {
type: INIT_LAYOUT,
payload: {
title,
copyright,
sections,
actions
}
};
}
export const RESIZE = 'RESIZE';
export function resize (clientHeight, clientWidth) {
return {
type: RESIZE,
payload: {
clientHeight,
clientWidth
}
}
}

15
super-screen/client/src/layout/components/footer/index.js

@ -0,0 +1,15 @@
'use strict';
import React from 'react';
import style from './style.css';
export default class Footer extends React.Component {
render() {
const {footerProps} = this.props;
return (
<div className={style.footer} {...footerProps}>
{this.props.children}
</div>
);
}
};

5
super-screen/client/src/layout/components/footer/style.css

@ -0,0 +1,5 @@
.footer {
text-align: center;
font-size: 12px;
color: #999;
}

100
super-screen/client/src/layout/components/header/index.js

@ -0,0 +1,100 @@
'use strict';
import React from 'react';
import { Menu } from 'antd';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import styles from './style.css';
import {
MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined, LogoutOutlined
} from '@ant-design/icons';
import ResetPasswordModal from '../../../sections/memberManagement/components/resetPassword';
const Header = props => {
const { dispatch, history, user, pathname, toggleCollapsed, collapsed, actions } = props
const onFinish = async (values) => {
const dataToSave = { ...values }
return dispatch(
actions.memberManagement.modifyUser(user.id, dataToSave, values?.msg || ''),
).then((res) => {
if (res.success) {
return true;
} else {
return false;
}
});
};
const handelClick = item => {
if (item.key == 'logout') {
dispatch(actions.auth.logout(user));
history.push(`/signin`);
}
}
let current = pathname;
if (pathname == '/' || pathname == '') {
current = 'default';
} else if (pathname.charAt(0) == '/') {
current = pathname.substring(1);
}
if (current.indexOf('/') != -1) {
current = current.substring(0, current.indexOf('/'));
}
return (
<div className={styles.header}>
<div className={styles['header-fold']}>
<span onClick={toggleCollapsed} style={{ marginRight: 20 }}>
{
collapsed ?
<MenuUnfoldOutlined /> : <MenuFoldOutlined />
}
</span>
<div className={styles['header-title']} style={{}}>
<img src='/assets/images/favicon.ico' style={{ margin: '0 12px 4px 12px', height: 42, borderRadius: 4 }} />政务数据资源中心
</div>
</div>
<div id="nav" className={styles['header-nav']}>
<Menu
mode='horizontal'
selectedKeys={[current]}
style={{ border: 0, background: '#1890ff' }}
onClick={handelClick}
theme={'light'}
items={[{
label: <span style={{ color: 'aliceblue' }}>{user.displayName}</span>,
key: "user",
icon: <img className={styles['header-nav-user-img']} src={`/assets/images/avatar/5.png`} />,
children: [
{
icon: <UserOutlined />,
label: <ResetPasswordModal
editData={user}
triggerRender={<a>修改密码</a>}
title="修改密码"
onFinish={onFinish}
key="resetPassword"
/>,
key: 'resetPassword'
},
{
label: '退出', key: 'logout', icon: <LogoutOutlined />
},
],
}]}
/>
</div>
</div>
);
};
function mapStateToProps(state) {
const { global, auth } = state;
return {
actions: global.actions,
user: auth.user
};
}
export default connect(mapStateToProps)(Header);

41
super-screen/client/src/layout/components/header/style.css

@ -0,0 +1,41 @@
.header {
position: relative;
height: 65px;
min-width: 520px;
background-color: #1890ff;
}
.header-fold {
float: left;
padding-left: 32px;
font-size: 16px;
}
.header-title {
line-height: 60px;
display: inline-block;
font-size: 20px;
color: #fff;
text-shadow: 0 4px 3px rgba(54, 77, 108, 0.20);
}
.header-nav {
float: right;
}
.header-nav-notification {
/* color : #666; */
font-size: 16px;
}
.header-nav-user-img-wrapper {
display: inline;
margin: 14px 8px;
}
.header-nav-user-img {
width: 36px;
height: 36px;
position: relative;
bottom: 2px;
}

95
super-screen/client/src/layout/components/sider/index.js

@ -0,0 +1,95 @@
import React, { Component, useEffect, useState } from 'react';
import { Menu } from 'antd';
const JumpUrls = [
{ url: '/risk/hiddenrectification_approval', keys: 'riskHiddenrectification_approval' },
{ url: '/safetymanage/hiddenrectification_approval', keys: 'hiddenrectification_approval' },
{ url: '/metadataManagement/latestMetadata', keys: 'latestMetadata' },
]
const Sider = (props) => {
const [items, setItems] = useState([])
const [selectedKeys, setSelectedKeys] = useState([])
const [openKeys, setOpenKeys] = useState([])
const { pathname } = props;
useEffect(() => {
let jumpurlObj = JumpUrls.find(s => s.url == pathname && selectedKeys != s.keys)
if (jumpurlObj) {
localStorage.setItem('governmentDataResourceCenter_selected_sider', JSON.stringify([jumpurlObj.keys]))
setSelectedKeys(jumpurlObj.keys)
}
if (pathname.indexOf('metadataManagement/latestMetadata') < 0)
sessionStorage.removeItem('jumpSelectedKey');
if (pathname.indexOf('metadataManagement/businessMetadata') < 0)
sessionStorage.removeItem('jumpBusinessSelectedKey');
}, [pathname])
useEffect(() => {
const { sections, dispatch, user } = props;
let items = sections.reduce((p, c) => {
if (typeof c.getNavItem == 'function') {
let item = c.getNavItem(user, dispatch);
if (item != null) {
if (Array.isArray(item)) {
p = p.concat(item);
} else {
p.push(item);
}
}
}
return p;
}, []);
setItems(items)
let selectedKeys = []
let openKeys = []
const lastSelectedKeys = localStorage.getItem('governmentDataResourceCenter_selected_sider')
if (lastSelectedKeys) {
selectedKeys = JSON.parse(lastSelectedKeys)
}
const lastOpenKeys = localStorage.getItem('governmentDataResourceCenter_open_sider')
if (lastOpenKeys) {
openKeys = JSON.parse(lastOpenKeys)
}
if (!selectedKeys.length && !openKeys.length) {
let firstItem = items[0] || null
if (firstItem) {
let children = firstItem.props.children
if (Array.isArray(children)) {
selectedKeys = [children[0].key]
openKeys = [firstItem.key]
} else if (children.key) {
selectedKeys = [children.key]
openKeys = [firstItem.key]
} else {
selectedKeys = [firstItem.key]
}
}
}
localStorage.setItem('governmentDataResourceCenter_selected_sider', JSON.stringify(selectedKeys))
setSelectedKeys(selectedKeys)
localStorage.setItem('governmentDataResourceCenter_open_sider', JSON.stringify(openKeys))
setOpenKeys(openKeys)
}, [])
return (
<Menu id="sider" mode="inline"
theme={'light'}
selectedKeys={selectedKeys}
openKeys={openKeys}
onSelect={(e) => {
const { selectedKeys } = e;
setSelectedKeys(selectedKeys)
localStorage.setItem('governmentDataResourceCenter_selected_sider', JSON.stringify(selectedKeys))
}}
onOpenChange={(openKeys) => {
setOpenKeys(openKeys)
localStorage.setItem('governmentDataResourceCenter_open_sider', JSON.stringify(openKeys))
}}
>
{items}
</Menu>
)
}
export default Sider;

6
super-screen/client/src/layout/containers/index.js

@ -0,0 +1,6 @@
'use strict';
import Layout from './layout';
import NoMatch from './no-match';
export { Layout };
export { NoMatch };

48
super-screen/client/src/layout/containers/layout/breadcrumb.js

@ -0,0 +1,48 @@
import React from 'react';
import { Breadcrumb } from 'antd';
import withBreadcrumbs from 'react-router-breadcrumbs-hoc';
import { Link } from 'react-router-dom';
const extRoutes = [{ path: '/project-monitor/things/struct/:id/configuration/station', breadcrumb: '测点' }];
function Breadcrumbs(props) {
const excludePaths = [
'/',
'/metadataManagement/latestMetadata/detail',
'/metadataAcquisition/adapter/detail',
];
const { routes } = props;
const Bread = withBreadcrumbs(routes.concat(extRoutes), { excludePaths })(({ breadcrumbs }) => (
<Breadcrumb separator="/" style={{ height: 25 }}>
{
breadcrumbs.map((bc, index) => (
<Breadcrumb.Item key={index}>
{
bc.component
? (
<Link
to={{
pathname: bc.match.url,
state: bc.match.params ? bc.match.params : {},
query: bc.location.query ? bc.location.query : {},
}}
>
{bc.breadcrumb}
</Link>
)
: bc.breadcrumb
}
</Breadcrumb.Item>
))
}
</Breadcrumb>
));
return (
<Bread />
);
}
export default Breadcrumbs;

132
super-screen/client/src/layout/containers/layout/index.js

@ -0,0 +1,132 @@
'use strict';
import './index.less';
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { message, Layout } from 'antd';
import Sider from '../../components/sider';
import Header from '../../components/header';
import Footer from '../../components/footer';
import Breadcrumbs from './breadcrumb';
import { resize } from '../../actions/global';
import * as NProgress from 'nprogress';
import PerfectScrollbar from 'perfect-scrollbar';
NProgress.configure({
template: `
<div class="bar" style="height:2px" role="bar">
<div class="peg"></div>
</div>
<div class="spinner" role="spinner">
<div class="spinner-icon"></div>
</div>
`
});
const headerHeight = 64
const footerHeight = 0
let scrollbar
const LayoutContainer = props => {
const { dispatch, msg, user, copyright, children, sections, clientWidth, clientHeight, location, match, routes, history } = props
const [collapsed, setCollapsed] = useState(false)
NProgress.start();
const resize_ = (collapsed) => {
const extraHeight = headerHeight + footerHeight;
dispatch(resize(
document.body.clientHeight - extraHeight - 12,
document.body.clientWidth - (collapsed ? 120 : 220)
));
}
useEffect(() => {
resize_(collapsed)
scrollbar = new PerfectScrollbar('#page-content', { suppressScrollX: true });
}, [])
useEffect(() => {
NProgress.done();
if (!user || !user.authorized) {
history.push('/signin');
}
if (msg) {
message.destroy();
if (msg.done) {
message.success(msg.done);
}
if (msg.error) {
message.error(msg.error);
}
}
const dom = document.getElementById('page-content');
if (dom) {
scrollbar.update();
dom.scrollTop = 0;
}
})
// console.log(FS_API_ROOT);
return (
<Layout id="layout">
<Layout.Header style={{ padding: 0 }}>
<Header
user={user}
pathname={location.pathname}
toggleCollapsed={() => {
setCollapsed(!collapsed);
resize_(!collapsed)
}}
collapsed={collapsed}
history={history}
/>
</Layout.Header>
<Layout>
<Layout.Sider trigger={null} collapsible collapsed={collapsed} theme={'light'}>
<Sider
sections={sections}
dispatch={dispatch}
user={user}
pathname={location.pathname}
collapsed={collapsed}
/>
</Layout.Sider>
<Layout.Content id="page-content" style={{
position: 'relative',
margin: '12px 12px 0px',
padding: '8px',
height: clientHeight,
background: '#fff'
}}>
<div style={{ minWidth: 520 }}>
<div style={{ padding: '0px 16px 4px', borderBottom: '1px solid #e8e8e8' }}>
<Breadcrumbs routes={routes} />
</div>
<div style={{ padding: '12px 12px 0px 12px' }}>
{children}
</div>
</div>
</Layout.Content>
{/* <Layout.Footer {...footerProps}>
{copyright}
</Layout.Footer> */}
</Layout>
</Layout>
)
}
function mapStateToProps(state) {
const { global, auth, ajaxResponse } = state;
return {
title: global.title,
copyright: global.copyright,
sections: global.sections,
actions: global.actions,
clientWidth: global.clientWidth,
clientHeight: global.clientHeight,
msg: ajaxResponse.msg,
user: auth.user,
};
}
export default connect(mapStateToProps)(LayoutContainer);

3
super-screen/client/src/layout/containers/layout/index.less

@ -0,0 +1,3 @@
@import '~perfect-scrollbar/css/perfect-scrollbar.css';
@import '~nprogress/nprogress.css';
@import '~simplebar-react/dist/simplebar.min.css';

18
super-screen/client/src/layout/containers/no-match/index.js

@ -0,0 +1,18 @@
'use strict';
import React from 'react';
import moment from 'moment'
const NoMatch = props => {
return (
<div style={{ textAlign: 'center', padding: 120 }}>
<p style={{ fontSize: 80, lineHeight: 1.5 }}>404</p>
<p style={{ fontSize: 32, lineHeight: 2 }}>PAGE NOT FOUND</p>
<p>很遗憾您暂时无法访问该页面</p>
<p>请检查您访问的链接地址是否正确</p>
<p style={{ marginTop: 80 }}>Copyright © {moment().year()} 飞尚</p>
</div>
)
}
export default NoMatch;

179
super-screen/client/src/layout/index.js

@ -0,0 +1,179 @@
'use strict';
import React, { useEffect, useState } from 'react';
import moment from 'moment';
import configStore from './store';
import { Provider } from 'react-redux';
import { createBrowserHistory } from 'history';
import { ConnectedRouter } from 'connected-react-router'
import { Layout, NoMatch } from './containers';
import { Switch, Route } from "react-router-dom";
import { ConfigProvider } from 'antd';
import * as layoutActions from './actions/global';
import zhCN from 'antd/lib/locale/zh_CN';
import { basicReducer } from '@peace/utils';
import 'moment/locale/zh-cn';
import 'antd/dist/antd.less';
moment.locale('zh-cn');
const { initLayout } = layoutActions;
const Root = props => {
const { sections, title, copyright } = props;
const [history, setHistory] = useState(null)
const [store, setStore] = useState(null)
const [outerRoutes, setOuterRoutes] = useState([])
const [combineRoutes, setCombineRoutes] = useState([])
const [innnerRoutes, setInnerRoutes] = useState([])
const flatRoutes = (routes) => {
const combineRoutes = [];
function flat(routes, parentRoute) {
routes.forEach((route, i) => {
const obj = {
path: route.path,
breadcrumb: route.breadcrumb,
component: route.component || null,
authCode: route.authCode || '',
key: route.key,
};
if (!route.path.startsWith('/')) {
console.error(`路由配置需以 "/" 开始:${route.path}`);
}
if (route.path.length > 1 && route.path[route.path.length] == '/') {
console.error(`除根路由路由配置不可以以 "/" 结束:${route.path}`);
}
if (parentRoute && parentRoute != '/') {
obj.path = parentRoute + route.path;
}
if (route.exact === false) {
obj.exact = false;
}
if (route.hasOwnProperty('childRoutes')) {
combineRoutes.push(obj);
flat(route.childRoutes, obj.path);
} else {
combineRoutes.push(obj);
}
});
}
flat(routes);
return combineRoutes;
};
const initReducer = (reducers, reducerName, action) => {
let reducerParams = {}
const { actionType, initReducer, reducer } = action()()
if (initReducer || reducer) {
if (reducer) {
if (reducer.name) {
reducerName = reducer.name
}
if (reducer.params) {
reducerParams = reducer.params
}
} else {
reducerName = `${reducerName}Rslt`
}
reducers[reducerName] = function (state, action) {
return basicReducer(state, action, Object.assign({ actionType: actionType }, reducerParams));
}
}
}
useEffect(() => {
let innerRoutes = []
let outerRoutes = []
let reducers = {}
let actions = {
layout: layoutActions
}
for (let s of sections) {
if (!s.key) console.warn('请给你的section添加一个key值,section name:' + s.name);
for (let r of s.routes) {
if (r.type == 'inner' || r.type == 'home') {
innerRoutes.push(r.route)
} else if (r.type == 'outer') {
outerRoutes.push(r.route)
}
}
if (s.reducers) {
reducers = { ...reducers, ...s.reducers }
}
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)
}
}
}
}
}
let history = createBrowserHistory();
let store = configStore(reducers, history);
store.dispatch(initLayout(title, copyright, sections, actions));
store.dispatch(actions.auth.initAuth());
const combineRoutes = flatRoutes(innerRoutes);
setInnerRoutes(combineRoutes)
setHistory(history)
setStore(store)
setOuterRoutes(outerRoutes.map(route => (
<Route
key={route.key}
exact
path={route.path}
component={route.component}
/>
)))
setCombineRoutes(combineRoutes.map(route => (
<Route
key={route.key}
exact={Object.prototype.hasOwnProperty.call(route, 'exact') ? route.exact : true}
path={route.path}
component={route.component}
/>
)))
}, [])
return (
store ?
<ConfigProvider locale={zhCN}>
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<Switch>
{outerRoutes}
<Layout
history={history}
routes={innnerRoutes}
>
{combineRoutes}
</Layout>
<Route
path={'*'}
component={NoMatch}
/>
</Switch>
</div>
</ConnectedRouter>
</Provider>
</ConfigProvider>
: ''
)
}
export default Root;

28
super-screen/client/src/layout/reducers/ajaxResponse.js

@ -0,0 +1,28 @@
/**
* Created by liu.xinyi
* on 2016/4/1.
*/
'use strict';
const initState = {
msg: null
};
import Immutable from 'immutable';
/**
* 全局ajax响应处理
* 判断action中是否有done字段如果有则修改store中的msg.done
* 判断action中是否有error字段如果有则修改store中msg.error
* 在layout中根据msg的值呈现提示信息
*/
export default function ajaxResponse(state = initState, action) {
if (action.done) {
return Immutable.fromJS(state).set('msg', {done: action.done}).toJS();
}
if (action.error) {
return Immutable.fromJS(state).set('msg', {error: action.error}).toJS();
}
return {msg: null};
};

36
super-screen/client/src/layout/reducers/global.js

@ -0,0 +1,36 @@
'use strict';
import Immutable from 'immutable';
import { INIT_LAYOUT, RESIZE } from '../actions/global';
function global (state = {
title: '',
copyright: '',
sections: [],
actions: {},
plugins: {},
clientHeight: 768,
clientWidth: 1024,
}, action) {
const payload = action.payload;
switch (action.type) {
case RESIZE:
return Immutable.fromJS(state).merge({
clientHeight: payload.clientHeight,
clientWidth: payload.clientWidth
}).toJS();
case INIT_LAYOUT:
return {
title: payload.title,
copyright: payload.copyright,
sections: payload.sections,
actions: payload.actions,
plugins: payload.plugins,
clientHeight: state.clientHeight,
clientWidth: state.clientWidth,
};
default:
return state;
}
}
export default global;

15
super-screen/client/src/layout/reducers/index.js

@ -0,0 +1,15 @@
/**
* User: liuxinyi/liu.xinyi@free-sun.com.cn
* Date: 2016/1/13
* Time: 17:52
*
*/
'use strict';
import global from './global';
import ajaxResponse from './ajaxResponse';
export default {
global,
ajaxResponse
};

16
super-screen/client/src/layout/store/index.js

@ -0,0 +1,16 @@
/**
* User: liuxinyi/liu.xinyi@free-sun.com.cn
* Date: 2016/1/13
* Time: 17:51
*
*/
'use strict';
let store = null;
if(process.env.NODE_ENV == 'production'){
store = require('./store.prod').default;
}else {
store = require('./store.dev').default;
}
export default store;

30
super-screen/client/src/layout/store/store.dev.js

@ -0,0 +1,30 @@
/**
* Created by liu.xinyi
* on 2016/4/8.
*/
'use strict';
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import reduxThunk from 'redux-thunk';
import { connectRouter, routerMiddleware } from 'connected-react-router';
import innerReducers from '../reducers';
function configStore(reducers, history) {
const reducer = Object.assign({}, innerReducers, reducers, {
router: connectRouter(history)
});
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
}) : compose;
const enhancers = composeEnhancers(
applyMiddleware(routerMiddleware(history), reduxThunk)
);
return createStore(combineReducers(reducer), {}, enhancers);
}
export default configStore;

20
super-screen/client/src/layout/store/store.prod.js

@ -0,0 +1,20 @@
/**
* Created by liu.xinyi
* on 2016/4/8.
*/
'use strict';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';
import { connectRouter, routerMiddleware } from 'connected-react-router';
import innerReducers from '../reducers';
function configStore(reducers, history){
const reducer = Object.assign({}, innerReducers, reducers, {
router: connectRouter(history)
});
return createStore(combineReducers(reducer), {}, applyMiddleware(routerMiddleware(history), reduxThunk));
}
export default configStore;

62
super-screen/client/src/sections/auth/actions/auth.js

@ -0,0 +1,62 @@
'use strict';
import { ApiTable } from '$utils'
import { Request } from '@peace/utils'
export const INIT_AUTH = 'INIT_AUTH';
export function initAuth() {
const user = JSON.parse(sessionStorage.getItem('user')) || {};
return {
type: INIT_AUTH,
payload: {
user: user
}
};
}
export const REQUEST_LOGIN = 'REQUEST_LOGIN';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_ERROR = 'LOGIN_ERROR';
export function login({ username, password, phone, code }) {
return dispatch => {
dispatch({ type: REQUEST_LOGIN });
return Request.post(ApiTable.login, { username, password, phone, code })
.then(user => {
sessionStorage.setItem('user', JSON.stringify(user));
dispatch({
type: LOGIN_SUCCESS,
payload: { user: user },
});
}, error => {
let { body } = error.response;
dispatch({
type: LOGIN_ERROR,
payload: {
error: body && body.message ? body.message : '登录失败'
}
})
});
}
}
export const LOGOUT = 'LOGOUT';
export function logout(user) {
const token = user.token;
const url = ApiTable.logout;
sessionStorage.removeItem('user');
localStorage.removeItem('governmentDataResourceCenter_selected_sider')
localStorage.removeItem('governmentDataResourceCenter_open_sider')
Request.put(url, {
token: token
});
return {
type: LOGOUT
};
}
export default {
initAuth,
login,
logout
}

10
super-screen/client/src/sections/auth/actions/index.js

@ -0,0 +1,10 @@
/**
* Created by liu.xinyi
* on 2016/4/1.
*/
'use strict';
import auth from './auth';
export default {
...auth
};

4
super-screen/client/src/sections/auth/containers/index.js

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

158
super-screen/client/src/sections/auth/containers/login.js

@ -0,0 +1,158 @@
'use strict';
import React, { useState, useEffect, useRef } from 'react';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import SHA1 from 'crypto-js/sha1';
import Hex from 'crypto-js/enc-hex';
import { ApiTable } from '$utils'
import { Request } from '@peace/utils'
import { Button, Input, Form, Row, Col, message, Tabs } from 'antd';
import { login, LOGIN_ERROR } from '../actions/auth';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Uploads } from '$components'
import '../style.less';
const FormItem = Form.Item;
let codCountDownInterval = null
const Login = props => {
const { dispatch, user, error, isRequesting } = props
const [username, setUserName] = useState('')
const [password, setPassword] = useState('')
const [phone, setPhone] = useState('')
const [code, setCode] = useState('')
const [inputChanged, setInputChanged] = useState(false)
const [curTabKey, setCurTabKey] = useState(1)
const [codSending, setCodSending] = useState(false)
const [codCountDown, setCodeCountDown] = useState(60)
const codCountDownRef = useRef(0)
useEffect(() => {
sessionStorage.removeItem('user');
localStorage.removeItem('governmentDataResourceCenter_selected_sider')
localStorage.removeItem('governmentDataResourceCenter_open_sider')
}, [])
useEffect(() => {
if (user && user.authorized) {
user?.role == '数据消费者' ? dispatch(push('/metadataManagement/latestMetadata')) : dispatch(push('/homePage'));
}
}, [user])
useEffect(() => {
if (codSending) {
setCodeCountDown(59)
codCountDownRef.current = 59
codCountDownInterval = setInterval(() => {
codCountDownRef.current -= 1
if (codCountDownRef.current == 0) {
setCodSending(false)
setCodeCountDown(60)
clearInterval(codCountDownInterval)
codCountDownInterval = null
} else {
setCodeCountDown(codCountDownRef.current)
}
}, 1000);
} else {
if (codCountDownInterval) {
clearInterval(codCountDownInterval)
codCountDownInterval = null
setCodeCountDown(60)
}
}
}, [codSending])
const doLogin = () => {
if (curTabKey == 1) {
if (!username || !password)
dispatch({
type: LOGIN_ERROR,
payload: { error: '请输入账号名和密码' }
});
setInputChanged(false)
dispatch(login({ username, password }));
} else {
if (!phone || !code)
dispatch({
type: LOGIN_ERROR,
payload: { error: '请输入手机号和验证码' }
});
dispatch(login({ phone, code }));
}
}
const enterHandler = e => {
if (e.key === 'Enter') {
doLogin()
}
};
return (
<div className='login'>
<div className='left'></div>
<div className='right'>
<div className='loginBox'>
<div className='_title'>欢迎登录系统</div>
<div className='_divider'></div>
<Form onKeyDown={enterHandler}>
<FormItem>
<Input
style={{ marginTop: 30 }}
placeholder='请输入账号'
className='loginInp'
type="text"
value={username}
// maxlength={11}
onChange={e => {
setUserName(e.target.value)
setInputChanged(true)
}}
/>
</FormItem>
<FormItem>
<Input
style={{ marginTop: 30 }}
placeholder='请输入密码'
className='loginInp'
type="password"
value={password}
onChange={e => {
setPassword(e.target.value)
setInputChanged(true)
}}
/>
</FormItem>
</Form>
<Row style={{
paddingLeft: '10%'
}}>
{
inputChanged || !error ?
<span style={{
visibility: 'hidden'
}}>-</span> :
<span>
<ExclamationCircleOutlined style={{ color: 'red' }} />{error}
</span>
}
</Row>
<Button style={{ borderRadius: 28 }} type="primary" className='loginBtn' loading={isRequesting} onClick={doLogin}>登录</Button>
</div>
</div>
</div>
);
}
function mapStateToProps(state) {
const { auth } = state;
return {
user: auth.user,
error: auth.error,
isRequesting: auth.isRequesting
}
}
export default connect(mapStateToProps)(Login);

12
super-screen/client/src/sections/auth/index.js

@ -0,0 +1,12 @@
'use strict';
import routes from './routes';
import reducers from './reducers';
import actions from './actions';
export default {
key: 'auth',
reducers: reducers,
routes: routes,
actions: actions
};

40
super-screen/client/src/sections/auth/reducers/auth.js

@ -0,0 +1,40 @@
'use strict';
import * as actionTypes from '../actions/auth';
import Immutable from 'immutable';
const initState = {
user: {},
isRequesting: false,
error: null
};
function auth(state = initState, action) {
const payload = action.payload;
switch (action.type){
case actionTypes.INIT_AUTH:
return Immutable.fromJS(state).set('user', payload.user).toJS();
case actionTypes.REQUEST_LOGIN:
return Immutable.fromJS(state).merge({
isRequesting: true,
error: null
}).toJS();
case actionTypes.LOGIN_SUCCESS:
return Immutable.fromJS(state).merge({
isRequesting: false,
user: payload.user
}).toJS();
case actionTypes.LOGIN_ERROR:
return Immutable.fromJS(state).merge({
isRequesting: false,
error: payload.error
}).toJS();
case actionTypes.LOGOUT:
return Immutable.fromJS(state).merge({
user: null
}).toJS();
default:
return state;
}
}
export default auth;

6
super-screen/client/src/sections/auth/reducers/index.js

@ -0,0 +1,6 @@
'use strict';
import auth from './auth'
export default {
auth
};

12
super-screen/client/src/sections/auth/routes.js

@ -0,0 +1,12 @@
'use strict';
import { Login } from './containers';
export default [{
type: 'outer',
route: {
key:'signin',
path: "/signin",
component: Login
}
}];

112
super-screen/client/src/sections/auth/style.less

@ -0,0 +1,112 @@
.login {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background-image: url('/assets/images/login_bg.png');
background-size: 100% 100%;
.left {
background-size: 100% 100%;
background-repeat: no-repeat;
width: 55%;
height: 100%;
float: left;
top: 0px;
left: 0px;
}
.right {
width: 45%;
height: 100%;
// background-color: #000066;
float: left;
right: 0px;
bottom: 0px;
}
}
@media screen and (max-height:1440px) {
.loginBox {
top: 25%;
}
}
@media screen and (max-height: 768px) {
.loginBox {
top: 20%;
}
}
@media screen and (max-height: 630px) {
.loginBox {
top: 10%;
}
}
.loginBox {
height: 50%;
width: 25%;
position: absolute;
right: 16.5%;
z-index: 20;
background: #fff;
text-align: center;
padding-top: 30px;
@media screen and (min-height:1080px) {
height: 40%;
}
._title {
font-family: SourceHanSansCN-Medium;
font-weight: 500;
font-size: 24px;
color: rgba(0, 0, 0, 0.75);
letter-spacing: 0;
}
._divider {
width: 58px;
height: 4px;
background: #4263F7;
display: inline-block;
}
h1 {
color: #fff;
font-size: 58px;
}
.loginFormTit {
width: 20%;
font-size: 18px;
color: rgb(255, 255, 255);
// margin-bottom: 10px;
}
.loginInp {
width: 80%;
height: 50px;
background: #ffffff;
border: 1px solid #C2C2C2;
border-radius: 5px;
}
.loginBtn {
width: 80%;
height: 50px;
margin-top: 40px;
border-radius: 5px;
font-size: 16px;
background: #4263F7;
border-color: #4263F7;
}
.loginBtn:hover {
background: #4263F7;
border-color: #4263F7;
}
}

15
super-screen/client/src/sections/homePage/actions/example.js

@ -0,0 +1,15 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
// export function getMembers(orgId) {
// return dispatch => basicAction({
// type: 'get',
// dispatch: dispatch,
// actionType: 'GET_MEMBERS',
// url: `${ApiTable.getEnterprisesMembers.replace('{enterpriseId}', orgId)}`,
// msg: { error: '获取用户列表失败' },
// reducer: { name: 'members' }
// });
// }

7
super-screen/client/src/sections/homePage/actions/index.js

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

49
super-screen/client/src/sections/homePage/components/abnormalMonitoring.js

@ -0,0 +1,49 @@
import React, { useEffect, useState } from 'react'
import CarouselList from './public/carousel-list';
import { Tooltip } from 'antd';
import { ApiTable, useFsRequest } from '$utils';
import moment from 'moment';
function AbnormalMonitoring(props) {
const { data: logs = {} } = useFsRequest({
url: ApiTable.getLogs,
query: {
logState: false,
startTime: moment().subtract(7, 'days').format('YYYY-MM-DD HH:mm:ss'),
endTime: moment().format('YYYY-MM-DD HH:mm:ss')
},
pollingInterval: 1000 * 60
});
const dataSource = logs?.rows ? logs?.rows?.map(s => {
return [
<div style={{ color: '#fff' }}>
<Tooltip placement="top" title={s?.acquisitionTask?.taskName}>
{s?.acquisitionTask?.taskName?.length > 20 ? s?.acquisitionTask?.taskNamesubstring(0, 20) + '...' : s?.acquisitionTask?.taskName}
</Tooltip>
</div>,
moment(s?.startTime).format('YYYY-MM-DD HH:mm:ss'),
moment(s?.endTime).valueOf() - moment(s?.startTime).valueOf() + '毫秒',
<div style={{ color: 'rgba(245, 27, 27, 1)' }}>
<Tooltip placement="top" title={s?.details}>
{s?.details?.length > 20 ? s?.details.substring(0, 20) + '...' : s?.details}
</Tooltip>
</div>
]
}) : []
return <div style={{ height: 149, border: '1px solid #50c9d74d', backgroundImage: 'linear-gradient(180deg, rgba(0, 32, 74, 0) 3%, rgba(80, 201, 247, 0.1) 100%)' }}>
<div className='center-card-title' style={{ marginBottom: 6 }}><div className='_icon_left' />异常监控<div className='_icon_right' /></div>
<CarouselList
header={['任务名称', '采集时间', '耗时', '异常日志']}
data={dataSource}
rowNum={2}
height={100}
multiellipsis
marginTop={-50}
/>
</div>
}
export default AbnormalMonitoring;

36
super-screen/client/src/sections/homePage/components/accessData.js

@ -0,0 +1,36 @@
import React from 'react'
import Box from './public/table-card';
import { useFsRequest } from '$utils';
import { mathRound } from './util'
function AccessData() {
const { data: accessdata = [] } = useFsRequest({
url: 'homepage/accessdata',
pollingInterval: 1000 * 60,
cacheKey: 'accessdata',
});
const renderBody = () => {
return <div className='access_data'>
<div className='_img'></div>
<div className='data_unit'>
数据单位<div className='data_number'>{!accessdata?.projects ? '-' : accessdata?.projects?.split(',')?.length}</div>
</div>
<div className='data_today'>
今日数据<div className='data_number'>{!accessdata?.res?.stat?.today ? '-' : accessdata?.res?.stat?.today > 1000 ? mathRound(accessdata?.res?.stat?.today) : accessdata?.res?.stat?.today}</div>{accessdata?.res?.stat?.today > 1000 ? '' : ''}
</div>
<div className='data_total'>
数据总量<div className='data_number'>{accessdata?.res?.stat?.datas ? Math.round(accessdata?.res?.stat?.datas / 10000) : '-'}</div>
</div>
</div>
}
return <Box title={"接入数据统计"} >
{renderBody()}
</Box>
}
export default AccessData;

52
super-screen/client/src/sections/homePage/components/alarmList.js

@ -0,0 +1,52 @@
import React from 'react'
import Box from './public/table-card';
import CarouselList from './public/carousel-list';
import { Tooltip } from 'antd';
import moment from 'moment';
import NoData from './public/noData';
import { useFsRequest } from '$utils';
function AlarmList(props) {
const { cardContentHeight } = props;
const { data: alarms = [] } = useFsRequest({
url: 'homepage/alarms',
pollingInterval: 1000 * 60,
cacheKey: 'alarms',
});
const data = alarms.map(s => {
return [
s.content,
s.level == 1 ? '一级' : s.level == 2 ? '二级' : s.level == 3 ? '三级' : '四级',
moment(s.time).format('YYYY-MM-DD HH:mm:ss')
]
})
const renderBody = () => {
return <CarouselList
header={['预警内容', '预警等级', '预警时间']}
data={data?.map(s => {
return [
<Tooltip placement="top" title={s[0]}>
{s[0].length > 20 ? s[0]?.substring(0, 20) + '...' : s[0]}
</Tooltip>,
<div style={{ color: s[1] == '一级' ? 'rgba(245, 27, 27, 1)' : s[1] == '二级' ? '#FF7900' : s[1] == '三级' ? '#FFCD00' : '#00DA9F' }}>{s[1]}</div>,
s[2]
]
})}
rowNum={6}
height={cardContentHeight}
multiellipsis
columnWidth={[180, 80, 150]}
/>
}
return <Box title={"预警列表"}>
{alarms?.length > 0 ? renderBody() : <NoData />}
</Box>
}
export default AlarmList;

19
super-screen/client/src/sections/homePage/components/centerTop.js

@ -0,0 +1,19 @@
import React from 'react'
import './style.less'
function CenterTop(props) {
return <div className='_top'>
<div className='center_top_data'>
<div className='_center_card1'>共享交换</div>
<div className='_center_card2'>数据监控</div>
<div className='_center_card3'>数据治理</div>
<div className='_center_card4'>数据采集</div>
</div>
</div>
}
export default CenterTop;

54
super-screen/client/src/sections/homePage/components/dataShare.js

@ -0,0 +1,54 @@
import React from 'react'
import Box from './public/table-card';
import { useFsRequest } from '$utils';
import { mathRound } from './util';
function DataShare(props) {
const { data: dataTotal = {} } = useFsRequest({
url: 'homepage/datatotal/top5',
pollingInterval: 1000 * 60,
cacheKey: 'datatotal',
});
const { data: restfulInfo = {} } = useFsRequest({
url: 'homepage/restful/info',
pollingInterval: 1000 * 60,
cacheKey: 'restfulInfo',
});
const renderItem = (s) => {
return <div className='_item_content'>
<div className={'_item_icon' + s.key} />
<div className='_item_text'>
{s.title}
<div className='number_container'>
<span className='_number'>{s.data}</span>{s.unit}
</div>
</div>
</div>
}
const leftData = [
{ key: '1', data: mathRound(dataTotal?.total), unit: '万条', title: '共享库数据总量' },
{ key: '2', data: restfulInfo?.total, unit: '次', title: '访问接口总次数' },
{ key: '3', data: restfulInfo?.totalUser, unit: '个', title: '访问接口用户总数' }]
const rightData = [
{ key: '2', data: restfulInfo?.todayTotal, unit: '次', title: '接口访问次数' },
{ key: '3', data: restfulInfo?.todayUser, unit: '个', title: '访问接口用户总数' }]
return <Box title={"数据共享"} >
<div className='data_share'>
<div className='_left_content'>
{leftData.map(s => renderItem(s))}
</div>
<div className='_right_content'>
<div className='_today_text'>今日</div>
{rightData.map(s => renderItem(s))}
</div>
</div>
</Box>
}
export default DataShare;

262
super-screen/client/src/sections/homePage/components/dataTop5.js

@ -0,0 +1,262 @@
import React, { useEffect, useState } from 'react'
import Box from './public/table-card';
import ReactEcharts from 'echarts-for-react';
import './style.less';
import { useFsRequest } from '$utils';
import { mathRound } from './util';
import NoData from './public/noData';
function DataTop5(props) {
const { cardContentHeight } = props;
const { data: dataTotal = {} } = useFsRequest({
url: 'homepage/datatotal/top5',
pollingInterval: 1000 * 60,
cacheKey: 'datatotal',
});
const renderBody = () => {
let chartData = dataTotal?.top5?.map(x => {
return {
name: x?.dataSource?.resourceCatalog?.name,
value: mathRound(x.dbRecordCount),
}
}) || []
let options = {
xAxis: {
splitLine: {
show: false,
},
axisLabel: {
show: false,
},
axisTick: {
show: false,
},
splitArea: { show: false },
axisLine: {
show: false,
},
},
tooltip: {
confine: true,
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
backgroundColor: 'rgba(13,30,44, 0.7)',
borderColor: 'rgba(3, 65, 118, 0.8)',
textStyle: {
color: '#fff',
},
formatter: function (params) {
var name = params[0].name
if (name.length > 20) {
name = name.replace(/(.{20})/g, '$1<br>') // 每 30 个字符添加一个换行符
}
var content = name
return content + ' : <b>' + params[0].value + '</b>万条'
}
},
grid: {
top: 13,
bottom: -10,
left: '5%',
},
yAxis: {
inverse: true,
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
textStyle: {
color: '#fff',
padding: [-5, 0, 35, 18],
},
formatter(value, index) {
let str = '', num = 'TOP' + (index + 1)
let valueHandle = value.length > 10 ? value.substring(0, 10) + '...' : value
if (index === 0) {
str = '{a| ' + num + '}{title| ' + valueHandle + '}'
} else if (index === 1) {
str = '{b| ' + num + '}{title| ' + valueHandle + '}'
} else if (index === 2) {
str = '{c| ' + num + '}{title| ' + valueHandle + '}'
} else {
str = '{d| ' + num + '}{title| ' + valueHandle + '}'
}
return str
},
rich: {
a: {
borderColor: '#EE6F7C',
borderWidth: 1,
borderRadius: [0, 10, 10, 0],
padding: [3.5, 10, 1, -13],
backgroundColor: 'rgba(238, 111, 124, 0.8)',
},
b: {
borderColor: '#FFCF5F',
borderWidth: 1,
borderRadius: [0, 10, 10, 0],
padding: [3.5, 10, 1, -13],
backgroundColor: 'rgba(255, 207, 95, 0.7)',
},
c: {
borderColor: '#00E8FF',
borderWidth: 1,
borderRadius: [0, 10, 10, 0],
padding: [3.5, 10, 1, -13],
backgroundColor: 'rgba(0, 232, 255, 0.7)',
},
d: {
borderColor: '#1A90FF',
borderWidth: 1,
borderRadius: [0, 10, 10, 0],
padding: [3.5, 10, 1, -13],
backgroundColor: 'rgba(26, 144, 255, 0.7)',
},
title: {
padding: [0, 0, 0, 3],
},
},
align: 'left',
},
data: chartData.map((item) => item.name),
},
series: [
{
type: 'pictorialBar',
symbol: 'rect',
symbolRotate: 30,
symbolRepeat: 'fixed',
symbolClip: true,
symbolOffset: [0, -1.5],
symbolSize: [2, 12],
symbolMargin: '3',
itemStyle: {
normal: {
color: '#000726',
},
},
label: {
show: true,
color: '#C8F0FF',
fontFamily: 'Bebas',
fontSize: 12,
offset: [-9, 1],
position: 'right',
formatter(params) {
let result = ''
switch (params.dataIndex) {
case 0:
result = '{img|}{index0|' + params.value + '}{unit|}'
break
case 1:
result = '{img|}{index1|' + params.value + '}{unit|}'
break
case 2:
result = '{img|}{index2|' + params.value + '}{unit|}'
break
default:
result = '{img|}{index3|' + params.value + '}{unit|}'
break
}
return result
},
rich: {
img: {
height: 18,
width: 20,
// backgroundColor: { image: arrow },这个图片自己切,这里上传不了(加了一个尾巴的形状)
},
unit: {
color: '#C8F0FF',
fontSize: 11,
},
index0: {
color: '#FFF',
fontFamily: 'Bebas',
padding: [-2, 2, 0, 0],
fontWeight: 'bold',
fontSize: 16,
},
index1: {
color: '#FFF',
fontFamily: 'Bebas',
padding: [-2, 2, 0, 0],
fontWeight: 'bold',
fontSize: 16,
},
index2: {
color: '#FFF',
fontFamily: 'Bebas',
padding: [-2, 2, 0, 0],
fontWeight: 'bold',
fontSize: 16,
},
index3: {
color: '#FFF',
fontFamily: 'Bebas',
padding: [-2, 2, 0, 0],
fontWeight: 'bold',
fontSize: 16,
},
},
},
symbolBoundingData: Math.max(...chartData.map((item) => item.value)) * 1.3,
data: chartData.map((item) => item.value),
z: 2,
},
{
type: 'bar',
barWidth: 10,
data: chartData.map((item) => item.value),
itemStyle: {
normal: {
color: '#54DEFA',
},
},
z: 1,
},
{
type: 'bar',
barGap: '-125%', // 设置外框粗细
data: chartData.map((items) => Math.max(...chartData.map((item) => item.value)) * 1.3),
barWidth: 15,
itemStyle: {
color: 'none',
borderColor: '#979797',
},
z: 0,
},
],
};
return <ReactEcharts
option={options}
notMerge
lazyUpdate
style={{ height: cardContentHeight }}
/>
}
return <Box title={"数据量TOP5单位"} bodyPaddingTop={1} >
{
dataTotal?.top5?.length > 0 ?
<>
<div className='data_top5_unit'>数据量万条</div>
{renderBody()}
</>
: <NoData />
}
</Box>
}
export default DataTop5;

42
super-screen/client/src/sections/homePage/components/hotspotData.js

@ -0,0 +1,42 @@
import React, { useEffect, useState } from 'react'
import Box from './public/table-card';
import NoData from './public/noData';
import './style.less';
import { ApiTable, useFsRequest } from '$utils';
function HotspotData(props) {
const { data: restfulInfo = {} } = useFsRequest({
url: 'homepage/restful/info',
pollingInterval: 1000 * 60,
cacheKey: 'restfulInfo',
});
const top3 = restfulInfo?.top3
return <Box title={"热点数据"} bodyPaddingTop={25} >
{top3?.length > 0 ?
<div className='hotspot_data_container'>
<div className='_img'></div>
<div className='_top1'>
<span className='hotspot_title' title={top3[0].name}>{top3[0].name?.length > 8 ? top3[0].name.substring(0, 8) + '...' : top3[0].name}</span>
<div className='hotspot_data_number'>{top3[0].count}</div>
</div>
<div className='_top2'>
{top3?.length > 2 && <>
<span className='hotspot_title' title={top3[2].name}>{top3[2].name?.length > 8 ? top3[2].name.substring(0, 8) + '...' : top3[2].name}</span>
<div className='hotspot_data_number'>{top3[2].count}</div>
</>}
</div>
<div className='_top3'>
{top3?.length > 1 && <>
<span className='hotspot_title' title={top3[1].name}>{top3[1].name?.length > 8 ? top3[1].name.substring(0, 8) + '...' : top3[1].name}</span>
<div className='hotspot_data_number'>{top3[1].count}</div>
</>}
</div>
</div> : <NoData />
}
</Box>
}
export default HotspotData;

39
super-screen/client/src/sections/homePage/components/nodeResource.js

@ -0,0 +1,39 @@
import React, { useEffect, useState } from 'react'
import Box from './public/table-card';
import { ApiTable, useFsRequest } from '$utils';
import './style.less';
function NodeResource(props) {
const { data: cluters = {} } = useFsRequest({
url: 'homepage/datatotal/cluters',
pollingInterval: 1000 * 20,
});
const renderBody = () => {
return <div className='node-resource-container'>
<div className='_item'>
<div className='_noderesource_data'>{cluters?.disk}<span className='_percent'>%</span></div>
<div className='_noderesource_title'>硬盘</div>
<div className='disk_icon' />
</div>
<div className='_item'>
<div className='_noderesource_data'>{cluters?.memory}<span className='_percent'>%</span></div>
<div className='_noderesource_title'>内存</div>
<div className='memory_icon' />
</div>
<div className='_item'>
<div className='_noderesource_data'>{cluters?.cpu}<span className='_percent'>%</span></div>
<div className='_noderesource_title'>CPU</div>
<div className='cpu_icon' />
</div>
</div>
}
return <Box title={"节点资源"} >
{renderBody()}
</Box>
}
export default NodeResource;

33
super-screen/client/src/sections/homePage/components/public/carousel-list.js

@ -0,0 +1,33 @@
/* 轮播列表组件 */
import React from 'react';
import ScrollBoard from './scrollBoard';
import NoData from './noData';
import './index.less';
function CarouselList(props) {
const {
header = [], data = [], rowNum = 4, height, columnWidth, multiellipsis, waitTime = 2000, marginTop, ...restProps
} = props;
const config = {
header,
rowNum,
headerBGC: 'rgba(81, 200, 247, 0.2)',
oddRowBGC: 'transparent',
evenRowBGC: 'transparent',
headerHeight: 30,
data,
waitTime,
columnWidth: columnWidth || [],
};
return data.length > 0 ? (
<ScrollBoard
config={config}
style={{ height }}
className={multiellipsis ? 'scroll-board-multi' : 'scroll-board'}
{...restProps}
/>
) : <NoData marginTop={marginTop || 0} />;
}
export default CarouselList;

80
super-screen/client/src/sections/homePage/components/public/index.less

@ -0,0 +1,80 @@
.opcityBackground {
background-color: rgba(8, 27, 55, 0.6);
}
.card-title {
// background: linear-gradient(to bottom, #fafafb, #92cbff);
// background-clip: border-box;
// -webkit-background-clip: text;
color: #fff;
font-size: 22px;
font-family: YouSheBiaoTiHei;
padding-left: 20px;
// font-weight: 600;
}
/* 滚动列表 */
.scroll-board {
width: 533px;
height: 220px;
margin-top: 10px;
margin-left: 6px;
.header {
height: 30px;
border-top: 1px solid #0047ba;
border-bottom: 1px solid #0047ba;
.header-item {
// background: rgba(12, 49, 110, 0.3);
margin-right: 10px;
}
}
.rows {
.row-item {
font-size: 16px;
}
.row-item:hover {
background: linear-gradient(270deg, rgba(17, 183, 247, 0) 0%, rgba(17, 183, 247, 0.85) 100%);
color: #9ac8fc;
}
}
}
.scroll-board-multi {
padding: 5px 0px 5px;
color: rgba(204, 228, 255, 1) !important;
.header {
display: flex;
flex-direction: row;
font-size: 12px !important;
color: rgba(204, 228, 255, 1) !important;
// border-bottom: 1px solid #124C79 !important;
}
.rows {
color: rgba(204, 228, 255, 1) !important;
.row-item {
border-bottom: 1px solid #124C79 !important;
}
.row-item:hover {
background: linear-gradient(270deg, rgba(17, 183, 247, 0) 0%, rgba(17, 183, 247, 0.85) 100%);
color: #9ac8fc;
}
}
}
._sorrow {
display: inline-block;
width: 15px;
height: 15px;
background: url('/assets/images/homePage/bigscreen/sorrow.png');
background-repeat: no-repeat;
background-size: 100% 100%;
margin-left: 13px;
}

18
super-screen/client/src/sections/homePage/components/public/noData.js

@ -0,0 +1,18 @@
/* 公共模块暂无数据组件 */
import React from 'react';
import { Empty } from 'antd';
function NoData({ height = 180, marginTop = 0 }) {
return (
<Empty
image="/assets/images/homePage/bigscreen/empty.png"
imageStyle={{
height,
marginTop
}}
description={false}
/>
);
}
export default NoData;

469
super-screen/client/src/sections/homePage/components/public/scrollBoard/index.js

@ -0,0 +1,469 @@
import React, {
useEffect, useState, useRef, useMemo, forwardRef,
} from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { deepMerge } from '@jiaminghi/charts/lib/util/index';
import { deepClone } from '@jiaminghi/c-render/lib/plugin/util';
import { useAutoResize, co } from '@jiaminghi/data-view-react';
import './style.less';
const defaultConfig = {
/**
* @description Board header
* @type {Array<String>}
* @default header = []
* @example header = ['column1', 'column2', 'column3']
*/
header: [],
/**
* @description Board data
* @type {Array<Array>}
* @default data = []
*/
data: [],
/**
* @description Row num
* @type {Number}
* @default rowNum = 5
*/
rowNum: 5,
/**
* @description Header background color
* @type {String}
* @default headerBGC = '#00BAFF'
*/
headerBGC: '#00BAFF',
/**
* @description Odd row background color
* @type {String}
* @default oddRowBGC = '#003B51'
*/
oddRowBGC: '#003B51',
/**
* @description Even row background color
* @type {String}
* @default evenRowBGC = '#003B51'
*/
evenRowBGC: '#0A2732',
/**
* @description Scroll wait time
* @type {Number}
* @default waitTime = 2000
*/
waitTime: 2000,
/**
* @description Header height
* @type {Number}
* @default headerHeight = 35
*/
headerHeight: 35,
/**
* @description Column width
* @type {Array<Number>}
* @default columnWidth = []
*/
columnWidth: [],
/**
* @description Column align
* @type {Array<String>}
* @default align = []
* @example align = ['left', 'center', 'right']
*/
align: [],
/**
* @description Show index
* @type {Boolean}
* @default index = false
*/
index: false,
/**
* @description index Header
* @type {String}
* @default indexHeader = '#'
*/
indexHeader: '#',
/**
* @description Carousel type
* @type {String}
* @default carousel = 'single'
* @example carousel = 'single' | 'page'
*/
carousel: 'single',
/**
* @description Pause scroll when mouse hovered
* @type {Boolean}
* @default hoverPause = true
* @example hoverPause = true | false
*/
hoverPause: true,
};
function calcHeaderData({ header, index, indexHeader }) {
if (!header.length) {
return [];
}
header = [...header];
if (index) header.unshift(indexHeader);
return header;
}
function calcRows({
data, index, headerBGC, rowNum,
}) {
if (index) {
data = data.map((row, i) => {
row = [...row];
const indexTag = `<span class="index" style="background-color: ${headerBGC};">${i
+ 1}</span>`;
row.unshift(indexTag);
return row;
});
}
data = data.map((ceils, i) => ({ ceils, rowIndex: i }));
const rowLength = data.length;
if (rowLength > rowNum && rowLength < 2 * rowNum) {
data = [...data, ...data];
}
return data.map((d, i) => ({ ...d, scroll: i }));
}
function calcAligns(mergedConfig, header) {
const columnNum = header.length;
const aligns = new Array(columnNum).fill('left');
const { align } = mergedConfig;
return deepMerge(aligns, align);
}
const ScrollBoard = forwardRef(({
onClick, config = {}, className, style, onMouseOver,
}, ref) => {
const { width, height, domRef } = useAutoResize(ref);
const [state, setState] = useState({
mergedConfig: null,
header: [],
rows: [],
rowsShow: [],
widths: [],
heights: [],
aligns: [],
});
const {
mergedConfig, header, rows, widths, heights, aligns, rowsShow,
} = state;
const stateRef = useRef({
...state,
rowsData: [],
avgHeight: 0,
animationIndex: 0,
});
Object.assign(stateRef.current, state);
function onResize() {
if (!mergedConfig) return;
const widths = calcWidths(mergedConfig, stateRef.current.rowsData);
const heights = calcHeights(mergedConfig, header);
const data = { widths, heights };
Object.assign(stateRef.current, data);
setState((state) => ({ ...state, ...data }));
}
const [init, setInit] = useState(true);
function calcData() {
// const mergedConfig = deepMerge(
// deepClone(defaultConfig, true),
// config || {},
// );
const mergedConfig = {
...defaultConfig,
...config,
};
const header = calcHeaderData(mergedConfig);
const rows = calcRows(mergedConfig);
const widths = calcWidths(mergedConfig, stateRef.current.rowsData);
const heights = calcHeights(mergedConfig, header);
const aligns = calcAligns(mergedConfig, header);
const data = {
mergedConfig,
header,
rows,
widths,
aligns,
heights: init ? heights : state.heights.concat(heights),
rowsShow: init ? rows : state.rowsShow,
};
setInit(false);
Object.assign(stateRef.current, data, {
rowsData: rows,
animationIndex: stateRef.current.animationIndex,
});
setState((state) => ({ ...state, ...data }));
}
function calcWidths({ columnWidth, header }, rowsData) {
const usedWidth = columnWidth.reduce((all, w) => all + w, 0);
let columnNum = 0;
if (rowsData[0]) {
columnNum = rowsData[0].ceils.length;
} else if (header.length) {
columnNum = header.length;
}
const avgWidth = (width - usedWidth) / (columnNum - columnWidth.length);
const widths = new Array(columnNum).fill(avgWidth);
return deepMerge(widths, columnWidth);
}
function calcHeights({ headerHeight, rowNum, data }, header) {
let allHeight = height;
if (header.length) allHeight -= headerHeight;
const avgHeight = allHeight / rowNum;
Object.assign(stateRef.current, { avgHeight });
return new Array(data.length).fill(avgHeight);
}
function* animation(start = false) {
let {
avgHeight,
animationIndex,
mergedConfig: { waitTime, carousel, rowNum },
rowsData,
} = stateRef.current;
const rowLength = rowsData.length;
if (start) yield new Promise((resolve) => setTimeout(resolve, waitTime));
const animationNum = carousel === 'single' ? 1 : rowNum;
let rows = rowsData.slice(animationIndex);
rows.push(...rowsData.slice(0, animationIndex));
rows = rows.slice(0, carousel === 'page' ? rowNum * 2 : rowNum + 1);
const heights = new Array(rowLength).fill(avgHeight);
setState((state) => ({
...state, rows, heights, rowsShow: rows,
}));
yield new Promise((resolve) => setTimeout(resolve, 300));
animationIndex += animationNum;
const back = animationIndex - rowLength;
if (back >= 0) animationIndex = back;
const newHeights = [...heights];
newHeights.splice(0, animationNum, ...new Array(animationNum).fill(0));
Object.assign(stateRef.current, { animationIndex });
setState((state) => ({ ...state, heights: newHeights }));
}
function emitEvent(handle, ri, ci, row, ceil) {
const { ceils, rowIndex } = row;
handle && handle({
row: ceils, ceil, rowIndex, columnIndex: ci,
});
}
function handleHover(enter, ri, ci, row, ceil) {
if (enter) emitEvent(onMouseOver, ri, ci, row, ceil);
if (!mergedConfig.hoverPause) return;
const { pause, resume } = task.current;
enter && pause && resume ? pause() : resume && resume();
}
// updateRows(rows, animationIndex) {
// const { mergedConfig, animationHandler, animation } = this
// this.mergedConfig = {
// ...mergedConfig,
// data: [...rows]
// }
// this.needCalc = true
// if (typeof animationIndex === 'number') this.animationIndex = animationIndex
// if (!animationHandler) animation(true)
// }
const getBackgroundColor = (rowIndex) => mergedConfig[rowIndex % 2 === 0 ? 'evenRowBGC' : 'oddRowBGC'];
const task = useRef({});
useEffect(() => {
calcData();
let start = true;
function* loop() {
while (true) {
yield* animation(start);
start = false;
const { waitTime } = stateRef.current.mergedConfig;
yield new Promise((resolve) => setTimeout(resolve, waitTime - 300));
}
}
const {
mergedConfig: { rowNum },
rows: rowsData,
} = stateRef.current;
const rowLength = rowsData.length;
if (rowNum >= rowLength) {
setState((prestate) => ({
...prestate, rowsShow: state.rows,
}));
return;
}
task.current = co(loop);
return task.current.end;
}, [config, domRef.current]);
useEffect(onResize, [width, height, domRef.current]);
const classNames = useMemo(() => classnames('dv-scroll-board', className), [
className,
]);
return (
<div className={classNames} style={style} ref={domRef}>
{!!header.length && !!mergedConfig && (
<div
className="header"
style={{ backgroundColor: `${mergedConfig.headerBGC}` }}
>
{header.map((headerItem, i) => (
<div
className="header-item"
key={`${headerItem}-${i}`}
style={{
height: `${mergedConfig.headerHeight}px`,
lineHeight: `${mergedConfig.headerHeight}px`,
width: `${widths[i]}px`,
}}
align={aligns[i]}
dangerouslySetInnerHTML={{ __html: headerItem }}
/>
))}
</div>
)}
{!!mergedConfig && (
<div
className="rows"
style={{
height: `${height
- (header.length ? mergedConfig.headerHeight : 0)}px`,
}}
>
{rowsShow.map((row, ri) => (
<div
className="row-item"
key={`${row.toString()}-${row.scroll}`}
style={{
height: `${heights[ri]}px`,
lineHeight: `${heights[ri]}px`,
backgroundColor: `${getBackgroundColor(row.rowIndex)}`,
}}
>
{row.ceils.map((ceil, ci) => {
if (typeof (ceil) === 'string') {
return (
<div
className="ceil"
key={`${ceil}-${ri}-${ci}`}
style={{ width: `${widths[ci]}px` }}
align={aligns[ci]}
dangerouslySetInnerHTML={{ __html: ceil }}
onClick={() => emitEvent(onClick, ri, ci, row, ceil)}
onMouseEnter={() => handleHover(true, ri, ci, row, ceil)}
onMouseLeave={() => handleHover(false)}
/>
);
}
return (
<div
className="ceil"
style={{ width: `${widths[ci]}px` }}
align={aligns[ci]}
key={`${ri}-${ci}`}
onMouseEnter={() => handleHover(true, ri, ci, row, ceil)}
onMouseLeave={() => handleHover(false)}
>
{ceil}
</div>
);
})}
</div>
))}
</div>
)}
</div>
);
});
ScrollBoard.propTypes = {
config: PropTypes.object,
onClick: PropTypes.func,
onMouseOver: PropTypes.func,
className: PropTypes.string,
style: PropTypes.object,
};
export default ScrollBoard;

44
super-screen/client/src/sections/homePage/components/public/scrollBoard/style.less

@ -0,0 +1,44 @@
.dv-scroll-board {
position: relative;
width: 100%;
height: 100%;
color: #fff;
.text {
padding: 0 10px;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.header {
display: flex;
flex-direction: row;
font-size: 15px;
.header-item {
.text;
transition: all 0.3s;
}
}
.rows {
overflow: hidden;
.row-item {
display: flex;
font-size: 14px;
transition: all 0.3s;
}
.ceil {
.text;
}
.index {
border-radius: 3px;
padding: 0px 3px;
}
}
}

34
super-screen/client/src/sections/homePage/components/public/table-card.js

@ -0,0 +1,34 @@
'use strict'
import React from 'react'
import './index.less'
class Box extends React.Component {
render() {
const { title, height = '100%', children, bodyPaddingTop = 1, titlePaddingTop, margin, overflow } = this.props
const headerbg = {
background: 'url(/assets/images/homePage/bigscreen/headertitlebg.png) no-repeat',
backgroundSize: '100% 100%',
}
return (
<div style={{ height, width: '100%', margin: margin || "0px 0px 28px" }}>
<div style={{
height: height, listStyle: 'none', overflow: overflow || 'hidden',
backgroundImage: 'linear-gradient(180deg, #00204a00 3%, #50c9f71a 100%)',
}}>
<div style={{ height: 42, paddingLeft: 24, paddingTop: '4px', wordBreak: 'keep-all', whiteSpace: 'nowrap', width: '100%', ...headerbg }}>
<span className='card-title'>{title}</span><div className='_sorrow' />
</div>
<div
style={{
width: '100%', height: 2,
marginTop: titlePaddingTop || 10, marginBottom: bodyPaddingTop || 25,
}} />
{children}
</div>
</div>
)
}
}
export default Box

373
super-screen/client/src/sections/homePage/components/style.less

@ -0,0 +1,373 @@
@card-height: calc(100% - 42px - 13px); //左右卡片内容高度定义 目前卡片为等高
//节点资源
.node-resource-container {
display: flex;
height: @card-height;
width: 100%;
align-items: center;
._item {
width: 33%;
display: flex;
flex-direction: column;
align-items: center;
._noderesource_data {
font-family: D-DINExp-Bold;
font-weight: 600;
font-size: 24px;
color: #FFFFFF;
line-height: 43.2px;
display: flex;
align-items: center;
._percent {
opacity: 0.8;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #C8F0FF;
text-align: left;
line-height: 24px;
}
}
._noderesource_title {
font-family: YouSheBiaoTiHei;
font-size: 20px;
color: #D8F0FF;
letter-spacing: 1.54px;
text-align: center;
text-shadow: 0 0 10px rgba(0, 145, 255, 0.5);
margin-bottom: 17px;
}
.disk_icon {
width: 68.73px;
height: 62.77px;
background: url('/assets/images/homePage/bigscreen/disk.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
.cpu_icon {
width: 68.73px;
height: 62.77px;
background: url('/assets/images/homePage/bigscreen/cpu.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
.memory_icon {
width: 68.73px;
height: 62.77px;
background: url('/assets/images/homePage/bigscreen/memory.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
}
}
//接入数据统计
.access_data {
display: flex;
height: @card-height;
width: 100%;
justify-content: center;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 14px;
color: #FFFFFF;
._img {
width: 230px;
height: 95%;
background: url('/assets/images/homePage/bigscreen/accessdata.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
.data_unit {
position: absolute;
top: 27%;
right: 21%;
.data_number {
line-height: 25px;
font-family: D-DINExp-Bold;
font-weight: 700;
font-size: 20px;
color: #3E86FF;
letter-spacing: 0;
}
}
.data_today {
position: absolute;
bottom: 13%;
right: 79%;
text-align: right;
.data_number {
line-height: 25px;
font-family: D-DINExp-Bold;
font-weight: 700;
font-size: 20px;
color: #00F6E4;
letter-spacing: 0;
}
}
.data_total {
position: absolute;
bottom: 13%;
left: 79%;
.data_number {
line-height: 25px;
font-family: D-DINExp-Bold;
font-weight: 700;
font-size: 20px;
color: #FFDC4E;
letter-spacing: 0;
}
}
}
.data_top5_unit {
position: absolute;
right: 4%;
top: 18%;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #C8F0FF;
}
.hotspot_data_container {
display: flex;
height: @card-height;
width: 100%;
justify-content: center;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 14px;
color: #FFFFFF;
._img {
width: 203px;
height: 80%;
background: url('/assets/images/homePage/bigscreen/hotspotdatabg.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
.hotspot_title {
padding: 2px;
padding-left: 6px;
padding-right: 6px;
background: rgba(77, 241, 227, 0.08);
border: 1px solid rgba(77, 241, 227, 0.1);
box-shadow: inset 0 0 20px 0 rgba(28, 185, 196, 0.23);
}
.hotspot_data_number {
font-family: D-DINExp-Bold;
font-weight: 700;
font-size: 18px;
color: #FFFFFF;
}
._top1 {
position: absolute;
top: 25%;
right: 63%;
text-align: right;
}
._top2 {
position: absolute;
bottom: 22%;
right: 67%;
text-align: center;
}
._top3 {
position: absolute;
bottom: 34%;
left: 73%;
}
}
//数据共享
.data_share {
display: flex;
height: @card-height;
._left_content {
width: 50%;
height: 90%;
padding-left: 30px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
._right_content {
._today_text {
font-family: YouSheBiaoTiHei;
font-size: 24px;
color: #FFFFFF;
letter-spacing: 0.5px;
position: absolute;
right: 6%;
top: 21%;
}
padding-top: 23px;
display: flex;
flex-direction: column;
width: 47%;
height: 95%;
background: url(/assets/images/homePage/bigscreen/todaybg.png);
background-repeat: no-repeat;
background-size: 100% 100%;
justify-content: space-evenly;
align-items: center;
}
._item_content {
display: flex;
._item_icon1 {
width: 52px;
height: 52px;
background: url('/assets/images/homePage/bigscreen/1.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
._item_icon2 {
width: 52px;
height: 52px;
background: url('/assets/images/homePage/bigscreen/2.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
._item_icon3 {
width: 52px;
height: 52px;
background: url('/assets/images/homePage/bigscreen/3.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
._item_text {
color: #C8F0FF;
padding-left: 6px;
.number_container {
._number {
font-family: D-DINExp-Bold;
font-weight: 700;
font-size: 22px;
color: #FFFFFF;
}
display: flex;
align-items: center;
justify-content: space-evenly;
width: 112px;
height: 28px;
background-image: linear-gradient(227deg, #3196AB 0%, #2091cd00 100%);
}
}
}
}
//大屏中间上部分
.center_top_data {
._center_card1 {
width: 353px;
height: 74px;
font-family: YouSheBiaoTiHei;
font-size: 22px;
color: #FFFFFF;
letter-spacing: 0.46px;
text-align: center;
position: absolute;
top: -3%;
left: 32%;
background: url(/assets/images/homePage/bigscreen/centerdatabg1.png);
background-repeat: no-repeat;
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
}
._center_card2 {
width: 146px;
height: 35px;
font-family: YouSheBiaoTiHei;
font-size: 16px;
color: #35D0FF;
letter-spacing: 0.46px;
text-align: center;
position: absolute;
top: 26%;
left: 42%;
background: url(/assets/images/homePage/bigscreen/centerdatabg2.png);
background-repeat: no-repeat;
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
}
._center_card3 {
width: 146px;
height: 35px;
font-family: YouSheBiaoTiHei;
font-size: 16px;
color: #35D0FF;
letter-spacing: 0.46px;
text-align: center;
position: absolute;
top: 52%;
left: 42%;
background: url(/assets/images/homePage/bigscreen/centerdatabg2.png);
background-repeat: no-repeat;
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
}
._center_card4 {
width: 146px;
height: 35px;
font-family: YouSheBiaoTiHei;
font-size: 16px;
color: #35D0FF;
letter-spacing: 0.46px;
text-align: center;
position: absolute;
top: 74%;
left: 42%;
background: url(/assets/images/homePage/bigscreen/centerdatabg2.png);
background-repeat: no-repeat;
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
}
}

3
super-screen/client/src/sections/homePage/components/util.js

@ -0,0 +1,3 @@
export const mathRound = (number) => {
return number ? Math.round(number / 1000) / 10 : 0
}

74
super-screen/client/src/sections/homePage/containers/homePage.js

@ -0,0 +1,74 @@
import React, { useEffect, useState } from 'react'
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import AccessData from '../components/accessData'
import AlarmList from '../components/alarmList'
import DataShare from '../components/dataShare'
import DataTop5 from '../components/dataTop5'
import HotspotData from '../components/hotspotData'
import NodeResource from '../components/nodeResource'
import AbnormalMonitoring from '../components/abnormalMonitoring'
import CenterTop from '../components/centerTop'
import './style.less'
function homePage(props) {
const { dispatch } = props;
const childStyle = { height: '32%', color: '#fff', marginBottom: 17 }
const cardHeight = document.body.clientHeight * 0.896 * 0.32
const cardContentHeight = cardHeight - 42 - 13
return <div className='homepage'>
<div className='_title'>
<div onClick={() => { dispatch(push('/metadataManagement/latestMetadata')) }} className='_exit' ><div className='_icon' />进入后台</div>
</div>
<div className='homepage-left homepage-left-left'>
<div className="list">
<div className='child' style={childStyle}>
<AccessData />
</div>
<div className='child' style={childStyle}>
<NodeResource />
</div>
<div className='child' style={childStyle}>
<AlarmList cardContentHeight={cardContentHeight} />
</div>
</div>
</div>
<div className='homepage-center'>
<CenterTop />
<div className="list">
<div className='child-top'>
<AbnormalMonitoring />
</div>
</div>
</div>
<div className='homepage-left homepage-left-right'>
<div className="list">
<div className='child-right' style={childStyle}>
<DataShare />
</div>
<div className='child-right' style={childStyle}>
<DataTop5 cardContentHeight={cardContentHeight} />
</div>
<div className='child-right' style={childStyle}>
<HotspotData />
</div>
</div>
</div>
</div>
}
function mapStateToProps(state) {
const {
auth, global
} = state;
return {
clientHeight: global.clientHeight,
actions: global.actions,
};
}
export default connect(mapStateToProps)(homePage);

6
super-screen/client/src/sections/homePage/containers/index.js

@ -0,0 +1,6 @@
'use strict';
import homePage from './homePage';
export default homePage ;

224
super-screen/client/src/sections/homePage/containers/style.less

@ -0,0 +1,224 @@
.homepage {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: url('/assets/images/homePage/bigscreen/bg.png');
background-repeat: no-repeat;
background-size: 100% 100%;
overflow: hidden;
._title {
width: 100%;
height: 88px;
background: url('/assets/images/homePage/bigscreen/top.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
._exit {
position: absolute;
right: 60px;
top: 38px;
cursor: pointer;
color: #C8F0FF;
display: flex;
._icon {
display: inline-block;
width: 28px;
height: 28px;
background: url('/assets/images/homePage/bigscreen/exit.png');
background-repeat: no-repeat;
background-size: 100% 100%;
margin-right: 3px;
}
}
.homepage-left {
width: 21.8%;
height: 89.6%;
position: absolute;
top: 8.2%;
z-index: 300;
}
.homepage-center {
width: 49.16%;
height: 89.6%;
position: absolute;
bottom: 2.4%;
left: 25.5%;
padding-left: 16px;
padding-right: 16px;
z-index: 400;
._top {
margin-top: 5%;
height: calc(100% - 200px);
background: url('/assets/images/homePage/bigscreen/centerbg.png');
background-repeat: no-repeat;
background-size: 100% 100%;
position: relative;
}
}
.homepage-left-left {
left: 48px;
}
.homepage-left-right {
right: 48px;
}
}
.list {
list-style: none;
height: 100%;
}
.list .child {
box-sizing: border-box;
opacity: 0;
transform: translateX(-300px);
animation: show .5s forwards;
}
.list .child.show {
animation-delay: 0s !important;
}
.list .child.hide {
opacity: 1;
transform: translateX(0);
animation-name: hide;
animation-delay: 0s;
}
/*animation-delay*/
.list .child:not(.hide):nth-child(5n + 1) {
animation-delay: .3s;
}
.list .child:not(.hide):nth-child(5n + 2) {
animation-delay: .6s;
}
.list .child:not(.hide):nth-child(5n + 3) {
animation-delay: .9s;
}
.list .child:not(.hide):nth-child(5n + 4) {
animation-delay: 1.2s;
}
.list .child:not(.hide):nth-child(5n + 5) {
animation-delay: 1.5s;
}
.list .child-right {
box-sizing: border-box;
opacity: 0;
transform: translateX(300px);
animation: show .5s forwards;
}
.list .child-right.show {
animation-delay: 0s !important;
}
.list .child-right.hide {
opacity: 1;
transform: translateX(0);
animation-name: hide;
animation-delay: 0s;
}
/*animation-delay*/
.list .child-right:not(.hide):nth-child(5n + 1) {
animation-delay: .3s;
}
.list .child-right:not(.hide):nth-child(5n + 2) {
animation-delay: .6s;
}
.list .child-right:not(.hide):nth-child(5n + 3) {
animation-delay: .9s;
}
.list .child-right:not(.hide):nth-child(5n + 4) {
animation-delay: 1.2s;
}
.list .child-right:not(.hide):nth-child(5n + 5) {
animation-delay: 1.5s;
}
.list .child-top {
box-sizing: border-box;
opacity: 0;
transform: translateY(300px);
animation: show 1s forwards;
}
.list .child-top.show {
animation-delay: 0s !important;
}
.list .child-top.hide {
opacity: 1;
transform: translateY(0);
animation-name: hide;
animation-delay: 0s;
}
@keyframes show {
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes hide {
to {
opacity: 0;
transform: translateX(100px);
max-height: 0;
margin: 0;
}
}
.center-card-title {
height: 31px;
font-family: YouSheBiaoTiHei;
font-size: 24px;
color: #FFFFFF;
display: flex;
align-items: center;
margin-top: 5px;
._icon_left {
width: 32px;
height: 17px;
background: url('/assets/images/homePage/bigscreen/center-left.png');
background-repeat: no-repeat;
background-size: 100% 100%;
margin-right: 11px;
margin-left: 10px;
}
._icon_right {
width: 32px;
height: 17px;
background: url('/assets/images/homePage/bigscreen/center-right.png');
background-repeat: no-repeat;
background-size: 100% 100%;
margin-right: 11px;
margin-left: 10px;
}
}

15
super-screen/client/src/sections/homePage/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: 'homePage',
name: '首页',
reducers: reducers,
routes: routes,
actions: actions,
getNavItem: getNavItem
};

11
super-screen/client/src/sections/homePage/nav-item.js

@ -0,0 +1,11 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Menu } from 'antd';
import { HomeOutlined } from '@ant-design/icons';
export function getNavItem(user) {
return (
user?.role == '系统管理员' && <Menu.Item key="homePage" icon={<HomeOutlined />}>
<Link to="/homePage">数据监控平台</Link>
</Menu.Item>
);
}

5
super-screen/client/src/sections/homePage/reducers/index.js

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

13
super-screen/client/src/sections/homePage/routes.js

@ -0,0 +1,13 @@
'use strict';
import homePage from './containers/index';
export default [{
type: 'outer',
route: {
path: '/homePage',
key: 'homePage',
breadcrumb: '数据监控平台',
// 不设置 component 则面包屑禁止跳转
component: homePage
}
}];

0
super-screen/client/src/sections/homePage/style.less

10
super-screen/client/src/styles/antd.less

@ -0,0 +1,10 @@
@icon-url: "/assets/fonticon/iconfont";
.tree-transfer .ant-transfer-list-body {
overflow: auto !important;
}
.ant-pro-table-search {
background-color: @component-background !important;
}

16
super-screen/client/src/styles/theme.less

@ -0,0 +1,16 @@
@import "~antd/lib/style/themes/default.less";
@primary-color : @blue-6;
@link-color : @primary-color;
@secondary-color : fade(@primary-color, 20%);
@btn-primary-bg : @primary-color;
@select-item-selected-option-color: @primary-color;
@processing-color : @primary-color;
@select-item-selected-bg : @background-color-base;
@skeleton-color : @primary-color;
@btn-primary-bg : @primary-color;
@component-background: transparent;
:root {
--PC: @primary-color;
}

5
super-screen/client/src/utils/authCode.js

@ -0,0 +1,5 @@
'use strict';
export const AuthorizationCode = {
};

13
super-screen/client/src/utils/func.js

@ -0,0 +1,13 @@
'use strict';
export default class Func {
static isAuthorized(authcode) {
if (JSON.parse(sessionStorage.getItem('user'))) {
const { resources } = JSON.parse(sessionStorage.getItem('user'));
return resources.includes(authcode);
}else{
return false;
}
}
}

30
super-screen/client/src/utils/hooks.js

@ -0,0 +1,30 @@
import React, { useEffect } from 'react';
import { useRequest } from 'ahooks';
import { Request } from '@peace/utils';
export const useFsRequest = ({ ...props }) => {
const {
method = 'get', header = null, body = {}, query = {}, root = null, url, ...rest
} = props;
return useRequest(() => {
if (method === 'post') {
return Request.post(url, body, query, root, header);
}
if (method === 'put') {
return Request.put(url, body, query, root, header);
}
if (method === 'delete') {
return Request.delete(url, query, root, header);
}
return Request.get(url, query, root, header);
}, {
loadingDelay: 500,
...rest,
});
};
export default {
useFsRequest,
};

12
super-screen/client/src/utils/index.js

@ -0,0 +1,12 @@
'use strict';
import { AuthorizationCode } from './authCode';
import { ApiTable, RouteTable, } from './webapi'
import Func from './func';
import { useFsRequest } from './hooks';
export {
AuthorizationCode,
Func,
ApiTable, RouteTable,
useFsRequest,
}

131
super-screen/client/src/utils/pinyin.js

File diff suppressed because one or more lines are too long

13961
super-screen/client/src/utils/region.js

File diff suppressed because it is too large

172
super-screen/client/src/utils/webapi.js

@ -0,0 +1,172 @@
'use strict';
import request from 'superagent';
export const ApiTable = {
login: 'login',
logout: 'logout',
validatePhone: 'validate/phone',
//标签管理
getTags: 'tags',
postTagSets: 'tag-sets',
putTagSets: 'tag-sets/{id}',
delTagSets: 'tag-sets/{id}',
postTags: 'tags',
putTags: 'tags/{id}',
delTags: 'tags/{id}',
//元数据管理-模型管理
getMetaModelList: 'meta/models',
addMetaModel: 'meta/model',
modifyMetaModel: 'meta/model/{id}',
//最新元数据-资源目录
getResourceCatalog: 'resource-catalog',
postResourceCatalog: 'resource-catalog',
putResourceCatalog: 'resource-catalog/{id}',
delResourceCatalog: 'resource-catalog/{id}',
listStructuredData: 'listStructuredData',
publishingServices: 'publishing/services',
//最新元数据-元数据列表查询
getMetadataDatabases: 'metadata/databases',
getMetadataFiles: 'metadata/files',
getMetadataRestapis: 'metadata/restapis',
getMetadataModels: 'metadata/models',
//库表元数据增删改、等配置
postMetadataDatabases: 'metadata/databases',
putMetadataDatabases: 'metadata/databases/{id}',
delMetadataDatabases: 'metadata/databases/{id}',
getMetadataDatabasesById: 'metadata/databases/{id}',
postTagMetadata: 'tag/metadata',
getTagMetadata: 'tag/metadata/{id}',
getMetadataResourceApplications: 'resource-consumption/applications',
postMetadataResourceApplications: 'resource-consumption/applications',
//文件元数据增删改
postMetadataFiles: 'metadata/files',
putMetadataFiles: 'metadata/files/{id}',
delMetadataFiles: 'metadata/files/{id}',
//接口元数据增删改
postMetadataRestapis: 'metadata/restapis',
putMetadataRestapis: 'metadata/restapis/{id}',
delMetadataRestapis: 'metadata/restapis/{id}',
//业务元数据管理
getBusinessMetadataDatabases: 'business/metadata/databases',
postBusinessMetadataDatabases: 'business/metadata/databases',
putBusinessMetadataDatabases: 'business/metadata/databases/{id}',
delBusinessMetadataDatabases: 'business/metadata/databases/{id}',
getBusinessMetadataFiles: 'business/metadata/files',
postBusinessMetadataFiles: 'business/metadata/files',
putBusinessMetadataFiles: 'business/metadata/files/{id}',
delBusinessMetadataFiles: 'business/metadata/files/{id}',
getBusinessMetadataRestapis: 'business/metadata/restapis',
postBusinessMetadataRestapis: 'business/metadata/restapis',
putBusinessMetadataRestapis: 'business/metadata/restapis/{id}',
delBusinessMetadataRestapis: 'business/metadata/restapis/{id}',
//元数据采集-数据源管理
pgCheckConnect: 'adapter/check/connect',
addDataSource: 'meta/acq/dataSource',
getAdapters: 'meta/acq/adapters',
getDataSources: 'meta/acq/dataSources',
modifyDataSource: 'acq/dataSource/{id}',
//元数据采集-采集任务管理
addTask: 'meta/acq/task',
getTasks: 'meta/acq/tasks',
modifyTask: 'acq/task/{id}',
runTask: 'run/acq/task',
//采集日志
getLogs: "meta/acq/logs",
//资源消费
approveList: 'resource/approve',
//用户管理
getUserList: 'meta/members',
addUser: 'meta/member',
modifyUser: 'meta/member/{id}',
//元数据检索
searchMetadata: "meta/data/search",
//数据质量
standardDocFolders: 'standard-doc-folders',
standardDocs: 'standard-docs',
postFolderFile: 'postFolderFile',
businessRules: 'business-rules',
delBusinessRules: 'business-rules/{id}',
regularBasis: 'regular-basis',
fetchFiles: 'fetchFiles',
getQualityInspection: 'quality-inspection',
//数据安全规范上传
specifications: 'data-security/specifications',
delSpecifications: 'data-security/specifications/{fileIds}',
//元数据检索
searchMetadata: "meta/data/search",
//备份恢复
getBackupsList: 'meta/backups',
addBackups: 'meta/backups',
modifyBackups: 'meta/backups/{id}',
restoreBackups: 'backups/restore',
//REST服务
serviceManagement: 'service-management',
delServiceManagement: 'service-management/{id}',
lookField: 'lookField',
};
export const RouteTable = {
fileUpload: '/_upload/new',
cleanUpUploadTrash: '/_upload/cleanup',
};
const resultHandler = (resolve, reject) => (err, res) => {
if (err) {
if (err.status == 401) {
// 退出到登录页
const user = JSON.parse(sessionStorage.getItem('user'));
sessionStorage.clear();
window.document.location.replace('/login');
reject('unauth');
} else {
reject({
status: err.status || 0,
body: err.response ? err.response.body : err.message
});
}
reject({
status: err.status || 0,
body: err.response ? err.response.body : err.message
});
} else {
resolve(res.body);
}
};
export const buildRoute = (url) => {
const user = JSON.parse(sessionStorage.getItem('user'));
if (user == null) {
return url;
}
let connector = url.indexOf('?') === -1 ? '?' : '&';
return `${url}${connector}token=${user.token}`;
};
export class RouteRequest {
static get = (url, query) =>
new Promise((resolve, reject) => {
request.get(buildRoute(url)).query(query).end(resultHandler(resolve, reject));
});
static post = (url, data, query) =>
new Promise((resolve, reject) => {
request.post(buildRoute(url)).query(query).send(data).end(resultHandler(resolve, reject));
});
static delete = (url, data, query) =>
new Promise((resolve, reject) => {
request.delete(buildRoute(url)).query(query).send(data).end(resultHandler(resolve, reject));
});
}

145
super-screen/config.js

@ -0,0 +1,145 @@
/**
* Created by rain on 2016/1/25.
*/
'use strict';
/*jslint node:true*/
const path = require('path');
/*这种以CommonJS的同步形式去引入其它模块的方式代码更加简洁:获取组件*/
const os = require('os');
const moment = require('moment');
const args = require('args');
const dev = process.env.NODE_ENV == 'development';
dev && console.log('\x1B[33m%s\x1b[0m', '请遵循并及时更新 readme.md,维护良好的开发环境,媛猿有责');
// // 启动参数
args.option(['p', 'port'], '启动端口');
args.option(['u', 'api-url'], 'webapi的URL');
args.option('qnak', 'qiniuAccessKey');
args.option('qnsk', 'qiniuSecretKey');
args.option('qnbkt', 'qiniuBucket');
args.option('qndmn', 'qiniuDomain');
const flags = dev ? args.parse(process.argv) : {};
const FS_UNIAPP_API = process.env.FS_UNIAPP_API || flags.apiUrl;
const ANXINCLOUD_QINIU_ACCESSKEY = process.env.ANXINCLOUD_QINIU_ACCESSKEY || flags.qnak;
const ANXINCLOUD_QINIU_SECRETKEY = process.env.ANXINCLOUD_QINIU_SECRETKEY || flags.qnsk;
const ANXINCLOUD_QINIU_BUCKET_RESOURCE = process.env.ANXINCLOUD_QINIU_BUCKET_RESOURCE || flags.qnbkt;
const ANXINCLOUD_QINIU_DOMAIN_QNDMN_RESOURCE = process.env.ANXINCLOUD_QINIU_DOMAIN_QNDMN_RESOURCE || flags.qndmn;
//阿里OSS
const ALI_OSS_ACCESSKEY = process.env.ALI_OSS_ACCESSKEY || flags.aliOssAccessKey;
const ALI_OSS_SECRETKET = process.env.ALI_OSS_SECRETKET || flags.aliOssSecretKey;
const ALI_OSS_BUCKET = process.env.ALI_OSS_BUCKET || flags.aliOssBucket;
const ALI_OSS_REGION = process.env.ALI_OSS_REGION || flags.aliOssRegion;
if (
!FS_UNIAPP_API
// || !ANXINCLOUD_QINIU_ACCESSKEY
// || !ANXINCLOUD_QINIU_SECRETKEY
// || !ANXINCLOUD_QINIU_BUCKET_RESOURCE
// || !ANXINCLOUD_QINIU_DOMAIN_QNDMN_RESOURCE
) {
console.log('缺少启动参数,异常退出');
args.showHelp();
process.exit(-1);
}
const product = {
port: flags.port || 8080,
staticDirs: [path.join(__dirname, './client')],
mws: [
{
entry: require('./middlewares/proxy').entry,
opts: {
host: FS_UNIAPP_API,
match: /^\/_api\//,
}
},
// {
// entry: require('./middlewares/attachment').entry,
// opts: {
// qiniu: {
// accessKey: ANXINCLOUD_QINIU_ACCESSKEY,
// secretKey: ANXINCLOUD_QINIU_SECRETKEY,
// bucket: ANXINCLOUD_QINIU_BUCKET_RESOURCE,
// domain: ANXINCLOUD_QINIU_DOMAIN_QNDMN_RESOURCE
// },
// maxSize: 104857600, // 100M
// uploadPath: 'other'
// }
// },
{
entry: require('./routes').entry,
opts: {
apiUrl: FS_UNIAPP_API,
staticRoot: './client',
qiniu: {
fetchUrl: '/_file-server',
domain: ANXINCLOUD_QINIU_DOMAIN_QNDMN_RESOURCE
},
aliOss: {
fetchUrl: '/_file-ali-server',
accessKey: ALI_OSS_ACCESSKEY,
secretKey: ALI_OSS_SECRETKET,
bucket: ALI_OSS_BUCKET,
region: ALI_OSS_REGION
}
}
},
{
entry: require('./client').entry,// 静态信息
opts: {}
}
],
logger: {
level: 'debug',
json: false,
filename: path.join(__dirname, 'log', 'runtime.txt'),
colorize: true,
maxsize: 1024 * 1024 * 5,
rotationFormat: false,
zippedArchive: true,
maxFiles: 10,
prettyPrint: true,
label: '',
timestamp: () => moment().format('YYYY-MM-DD HH:mm:ss.SSS'),
eol: os.EOL,
tailable: true,
depth: null,
showLevel: true,
maxRetries: 1
}
};
if (product.frontParams) {
const patt = /[^A-Z|_]+/
for (let k in product.frontParams) {
if (!k.startsWith('FS_') || patt.test(k)) {
console.warn(`参数 ${k} 当以大写字母和下划线组成 且以 FS_ 开头`);
process.exit(-1);
}
}
}
let config;
if (dev) {
config = {
port: product.port,
staticDirs: product.staticDirs,
frontParams: product.frontParams,
mws: product.mws.concat([
{
entry: require('./middlewares/webpack-dev').entry,
opts: {
frontParams: product.frontParams,
}
}
]),
logger: product.logger
}
config.logger.filename = path.join(__dirname, 'log', 'development.txt');
} else {
config = product;
}
module.exports = config;//区分开发和发布

15
super-screen/jsconfig.json

@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"allowSyntheticDefaultImports": true
},
"exclude": [
"node_modules",
"bower_components",
"jspm_packages",
"tmp",
"temp"
]
}

23
super-screen/middlewares/attachment.js

@ -0,0 +1,23 @@
/**
* Created by PengLing on 2018/1/2.
*/
'use strict';
const Attachment = require('fs-attachment');
module.exports = {
entry: function (app, router, opts) {
const attachment = new Attachment(opts);
app.fs = app.fs || {};
app.fs.attachment = attachment;
app.fs.logger.log('debug', 'init fs.attachment and inject it into app(app.fs.attachment) and runtime ctx(ctx.fs.attachment)');
return async (ctx, next) => {
ctx.fs = ctx.fs || {};
ctx.fs.attachment = attachment;
await next();
};
}
};

16
super-screen/middlewares/proxy.js

@ -0,0 +1,16 @@
'use strict';
const proxy = require('koa-proxy');
const convert = require('koa-convert');
module.exports = {
entry: function (app, router, opts) {
app.use(convert(proxy({
host: opts.host,
match: opts.match,
map: function (path) {
return path.replace(opts.match, '');
}
})));
}
};

53
super-screen/middlewares/webpack-dev.js

@ -0,0 +1,53 @@
'use strict';
const express = require('express')
const webpack = require('webpack');
const devConfig = require('../webpack.config');
const middleware = require('webpack-dev-middleware');
const proxy = require('koa-better-http-proxy');
const url = require('url');
module.exports = {
entry: function (app, router, opts) {
devConfig.plugins.push(
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),
...(() => {
let nextParams = {}
for (let k in opts.frontParams) {
nextParams[k] = JSON.stringify(opts.frontParams[k])
}
return nextParams
})()
}),
)
const compiler = webpack(devConfig);
app.use(proxy('http://localhost:5501', {
filter: function (ctx) {
return /\/build/.test(url.parse(ctx.url).path);
},
proxyReqPathResolver: function (ctx) {
return 'client' + url.parse(ctx.url).path;
}
}));
app.use(proxy('http://localhost:5501', {
filter: function (ctx) {
return /\/$/.test(url.parse(ctx.url).path);
},
proxyReqPathResolver: function (ctx) {
return 'client/build/index.html';
}
}));
const server = express();
server.use(middleware(compiler));
//server.use(require("webpack-hot-middleware")(compiler));
server.listen('5401', function (err) {
if (err) {
console.log(err);
}
})
}
};

101
super-screen/package.json

@ -0,0 +1,101 @@
{
"name": "fs-anxincloud-4.0",
"version": "1.0.0",
"description": "anxincloud-4.0",
"main": "server.js",
"scripts": {
"test": "mocha",
"start": "cross-env NODE_ENV=development npm run start-params",
"start-params": "node server -p 5500 -u http://localhost:4400",
"deploy": "export NODE_ENV=production && npm run build && node server",
"build-dev": "set NODE_ENV=development&&webpack --config webpack.config.js",
"build": "set NODE_ENV=production&&webpack --config webpack.config.prod.js"
},
"keywords": [
"app"
],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.14.6",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-object-rest-spread": "^7.14.7",
"@babel/plugin-transform-runtime": "^7.14.5",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.14.7",
"@babel/preset-react": "^7.14.5",
"babel-loader": "^8.2.2",
"babel-plugin-import": "^1.13.3",
"babel-polyfill": "^6.26.0",
"connected-react-router": "^6.8.0",
"css-loader": "^3.5.0",
"express": "^4.17.1",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.5.0",
"immutable": "^4.0.0-rc.12",
"less": "^3.12.2",
"less-loader": "^7.0.2",
"natty-fetch": "^2.5.3",
"nprogress": "^0.2.0",
"path-to-regexp": "^2.4.0",
"perfect-scrollbar": "^1.5.5",
"react": "^17.0.0",
"react-copy-to-clipboard": "^5.0.1",
"react-dnd": "^10.0.2",
"react-dnd-html5-backend": "^10.0.2",
"react-dom": "^17.0.0",
"react-if": "^2.2.1",
"react-jsonschema-form": "^1.8.1",
"react-quill": "^1.3.5",
"react-redux": "^7.2.1",
"react-router-dom": "^5.2.0",
"react-router-redux": "^4.0.8",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"redux-undo": "^1.0.1",
"style-loader": "^2.0.0",
"webpack": "^5.3.2",
"webpack-bundle-analyzer": "^4.1.0",
"webpack-cli": "^4.2.0",
"webpack-dev-middleware": "^4.0.2",
"webpack-hot-middleware": "^2.25.0"
},
"dependencies": {
"@ant-design/icons": "^4.6.2",
"@ant-design/pro-form": "^1.34.0",
"@ant-design/pro-table": "^2.48.0",
"@antv/g6": "^4.2.5",
"@douyinfe/semi-ui": "^2.28.1",
"@fs/attachment": "^1.0.0",
"@jiaminghi/data-view-react": "^1.2.5",
"@peace/components": "0.0.35",
"@peace/utils": "0.0.37",
"ahooks": "^3.7.4",
"ali-oss": "^6.17.1",
"antd": "^4.24.5",
"args": "^5.0.1",
"cron-js-parser": "^1.0.17",
"cron-parser": "^4.8.1",
"cross-env": "^7.0.3",
"crypto-js": "^4.1.1",
"echarts": "^5.4.1",
"echarts-for-react": "^3.0.2",
"file-saver": "^2.0.5",
"fs-attachment": "^1.0.0",
"fs-web-server-scaffold": "^1.0.6",
"jszip": "^3.10.1",
"koa-better-http-proxy": "^0.2.5",
"koa-proxy": "^1.0.0-alpha.3",
"koa-view": "^2.1.4",
"moment": "^2.22.0",
"npm": "^7.20.6",
"path": "^0.12.7",
"react-router-breadcrumbs-hoc": "^4.0.1",
"simplebar-react": "^3.2.4",
"superagent": "^6.1.0",
"uuid": "^8.3.1",
"webpack-dev-server": "^3.11.2",
"xlsx": "^0.16.9",
"xmlhttprequest": "^1.8.0"
}
}

231
super-screen/readme.md

@ -0,0 +1,231 @@
创建时间:2021/08/19
项目名称:
## 1. 文档维护:
- 文档相关内容若有更改,请及时更新文档,以备后来者查询;
## 2. 项目开发:
- 请遵循此文档约定的目录结构与约定
```js
|-- .babelrc
|-- color.js
|-- config.js
|-- Dockerfile
|-- jsconfig.json
|-- package.json
|-- readme.md
|-- server.js
|-- webpack.config.js
|-- webpack.config.prod.js
|-- .vscode
| |-- launch.json
| |-- settings.json
|-- client
| |-- index.ejs
| |-- index.html // 当前 html 文件
| |-- index.js
| |-- assets // 资源文件
| | |-- color.less
| | |-- files
| | |-- fonticon
| | |-- font_sc
| | |-- images
| | |-- avatar
| | |-- loginUi
| |-- src // 项目代码
| |-- app.js // 由此开始并加载模块
| |-- index.js
| |-- components // 公用组件
| | |-- index.js // 由此导出组件
| | |-- Upload
| | |-- index.js
| |-- layout // 项目布局以及初始化等操作
| | |-- index.js
| | |-- actions
| | | |-- global.js
| | |-- components
| | | |-- footer
| | | | |-- index.js
| | | | |-- style.css
| | | |-- header
| | | | |-- index.js
| | | | |-- style.css
| | | |-- sider
| | | |-- index.js
| | |-- containers
| | | |-- index.js
| | | |-- layout
| | | | |-- breadcrumb.js // 面包屑组件
| | | | |-- index.js
| | | | |-- index.less
| | | |-- no-match
| | | |-- index.js
| | |-- reducers
| | | |-- ajaxResponse.js
| | | |-- global.js // 全局数据,主要包含屏幕可视宽高、所有的 action 等
| | | |-- index.js
| | |-- store
| | |-- index.js
| | |-- store.dev.js
| | |-- store.prod.js
| |-- sections // 各功能模块
| | |-- auth // 比较特别的 Auth 模块,目前 action、reducer 依然采用原始写法;包含登录、忘记密码等项目基本功能页面
| | | |-- index.js
| | | |-- routes.js
| | | |-- actions
| | | | |-- auth.js
| | | | |-- index.js
| | | |-- components
| | | |-- containers
| | | | |-- index.js
| | | | |-- login.js
| | | |-- reducers
| | | | |-- auth.js
| | | | |-- index.js
| | | |-- __tests__
| | |-- example // 示例模块,一般的功能模块应遵循此结构
| | |-- index.js // 由此导出该模块信息,应包括一个 key 值,actions 等
| | |-- nav-item.js // 用于生成菜单项,此文件内可以进行权限判断
| | |-- routes.js // 路由文件
| | |-- style.less // 样式文件,若样式并不是非常多,每个模块一个样式文件即可
| | |-- actions
| | | |-- example.js // 具体的 action 操作
| | | |-- index.js // 由此导出该项目的 action
| | |-- components // 组件
| | |-- containers // 容器,此文件夹内应只包括该模块第一层级的页面
| | | |-- example.js
| | | |-- index.js
| | |-- reducers // 若采用封装后的 action 写法,则 reducer 可不写
| | |-- index.js
| |-- styles // 待初始化的主题样式
| | |-- antd.less
| | |-- theme.less
| |-- themes // 初始化后的主题样式文件
| | |-- dark.json
| | |-- light.json
| | |-- theme.json
| |-- utils //
| |-- authCode.js
| |-- func.js // 常用函数
| |-- index.js
| |-- region.js
| |-- webapi.js // api 路由
|-- log
|-- middlewares
| |-- proxy.js
| |-- webpack-dev.js
|-- routes
| |-- index.js
| |-- attachment
|-- typings
|-- node
| |-- node.d.ts
|-- react
|-- react.d.ts
```
- 封装后一般 action 写法:
`@peace/utils 的 actionHelp 中有详细注释`
``` js
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getMembers(orgId) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
actionType: 'GET_MEMBERS',
url: `${ApiTable.getEnterprisesMembers.replace('{enterpriseId}', orgId)}`,
msg: { error: '获取用户列表失败' },
reducer: { name: 'members' }
});
}
```
1. 若 type=post,则可以使用 data 属性发送对象格式数据;
2. reducer.name 会作为该 action 对应的 reducer 的名字,从 state 里可以解构此变量,获得该 action 异步或其他操作获得的数据;
3. msg 可以发送 `{ option:'获取用户列表' }` ,则 actionHelp 会自动将其处理为失败和成功两种情况;
若单独写 success 或 error 的 key,则只在成功或失败的时候进行提示;
4. 后续可以优化:type=get 时候,
使用 query 属性将数据传递,在 @peace/utils 的 actionHelp 中将其添加到路由后面;eg. `{ enterpriseId: orgId }`
使用 replace 属性传递对象数据,对象数据中将被替换的值为key,替换的值为 value,然后再 actionHelp 中更改路由;eg. `{ "{enterpriseId}": orgId}`
5. 最终取得的 reducer 中的数据格式一般为:
``` js
{
data: xxx, // 接口返回的数据格式
isRequesting: false, // 请求状态
success: true, // 以此判断请求是否成功,不用再以 payload.type 判断
}
```
- actions 的引用
从 reducer 的 state.global.actions 里引用具体 action
```js
const Example = (props) => {
const { dispatch, actions, user, loading } = props
useEffect(() => {
dispatch(actions.example.getMembers(user.orgId))
}, [])
return (
<Spin tip="biubiubiu~" spinning={loading}>
example
</Spin>
)
}
function mapStateToProps(state) {
const { auth, global, members } = state;
return {
loading: members.isRequesting,
user: auth.user,
actions: global.actions,
members: members.data
};
}
export default connect(mapStateToProps)(Example);
```
- 一般路由配置
```js
'use strict';
import { Example, } from './containers';
export default [{
type: 'inner', // 是否在layout 内,如果为outer,则看不到 header、footer、sider等布局,比如登陆页面
route: {
path: '/example',
key: 'example',
breadcrumb: '栗子',
// 不设置 component 则面包屑禁止跳转
childRoutes: [{
path: '/e1', // 自路由不必复写父路由内容,会自动拼接; 则此处组件的实际路由为 /example/e1
key: 'e1',
component: Example,
breadcrumb: '棒子',
}]
}
}];
```
- cross-env 的使用限制
cross-env 可以统一不同操作系统下环境变量的导出方式,不用再在 windows 下写 set;linux 下写 export; 可以统一以 cross-env NODE_ENV=DEV 代替;
但是这样的话就不能在同一条运行的命令中使用 && 切割,因为会把命令切割为两个环境,则最终拿不到我们设置的变量;

240
super-screen/routes/attachment/index.js

@ -0,0 +1,240 @@
'use strict';
const request = require('superagent');
const parse = require('async-busboy');
const path = require('path')
const fs = require('fs');
const OSS = require('ali-oss');
const uuid = require('uuid');
const UploadPath = {
project: ['.txt', '.dwg', '.doc', '.docx', '.xls', '.xlsx', ".csv", '.pdf', '.pptx', '.png', '.jpg', '.svg', '.rar', '.zip', '.jpeg', '.mp4'],
report: ['.doc', '.docx', '.xls', '.xlsx', ".csv", '.pdf'],
data: ['.txt', '.xls', '.xlsx', ".csv"],
image: ['.png', '.jpg', '.svg'],
three: ['.js'],
video: ['.mp4']
};
const ext = {
project: ['.txt', '.dwg', '.doc', '.docx', '.xls', '.xlsx', ".csv", '.pdf', '.pptx', '.png', '.jpg', '.gif', '.svg', '.rar', '.zip', '.jpeg', '.mp4'],
report: [".doc", ".docx", ".xls", ".xlsx", ".pdf"],
data: [".txt", ".xls", ".xlsx"],
image: [".png", ".jpg", ".svg"],
three: [".js"],
video: [".mp4"],
bpmn: [".bpmn", ".bpmn20.xml", ".zip", ".bar"],
app: [".apk"]
}
module.exports = {
entry: function (app, router, opts) {
let download_ = async function (ctx, next) {
const { fetchUrl } = opts.qiniu;
const { fetchUrl: aliFetchUrl, bucket, region } = opts.aliOss
if (ctx.path && ctx.path.includes(fetchUrl)) {
try {
const { filename } = ctx.request.query;
const fkey = decodeURI(ctx.path.slice(fetchUrl.length + 1)).replace(/\.json$/, '.js');
if (ctx.path) {
const extNames = ctx.path.split('.');
app.fs.logger.log('info', 'extNames', extNames);
if (extNames.length > 0) {
let fileType = extNames[extNames.length - 1].toLowerCase();
if (fileType === 'pdf') {
ctx.type = 'application/pdf';
app.fs.logger.log('info', 'application/pdf', fileType);
}
}
}
const publicDownloadUrl = await app.fs.attachment.download(fkey);
ctx.status = 200;
if (filename) ctx.attachment(filename);
ctx.body = request.get(publicDownloadUrl);
} catch (err) {
ctx.fs.logger.error(err);
ctx.status = 404;
ctx.body = { error: 'file not found.' };
}
} else if (ctx.path && ctx.path.includes(aliFetchUrl)) {
const { filename } = ctx.request.query;
const fkey = decodeURI(ctx.path.slice(aliFetchUrl.length + 1)).replace(/\.json$/, '.js');
if (ctx.path) {
const extNames = ctx.path.split('.');
app.fs.logger.log('info', 'extNames', extNames);
if (extNames.length > 0) {
let fileType = extNames[extNames.length - 1].toLowerCase();
if (fileType === 'pdf') {
ctx.type = 'application/pdf';
app.fs.logger.log('info', 'application/pdf', fileType);
}
}
}
const publicDownloadUrl = `http://${bucket}.${region}.aliyuncs.com/${encodeURIComponent(fkey)}`
ctx.status = 200;
ctx.body = request.get(publicDownloadUrl);
} else {
await next();
}
};
let upload = async function (ctx, next) {
try {
const { files } = await parse(ctx.req);
const file = files[0];
const extname = path.extname(file.filename).toLowerCase();
const fileType = ctx.query.type || "image";
const fileFolder = ctx.query.fileFolder || 'common';
if (ext[fileType].indexOf(extname) < 0) {
ctx.status = 400;
ctx.body = JSON.stringify({ name: 'UploadFailed', message: '文件格式无效' });
return;
}
const date = new Date().toLocaleDateString();
const time = new Date().getTime();
let fileName = time + '_' + file.filename;
let saveFile = path.join(__dirname, '../../', `/client/assets/files/${fileFolder}`, fileName);
const pathUrl = `./client/assets/files/${fileFolder}`;
const res1 = fs.existsSync(`./client/assets/files/${fileFolder}`);
!res1 && fs.mkdirSync(`./client/assets/files/${fileFolder}`);
const res = fs.existsSync(pathUrl);
!res && fs.mkdirSync(pathUrl);
let stream = fs.createWriteStream(saveFile);
fs.createReadStream(file.path).pipe(stream);
stream.on('error', function (err) {
app.fs.logger.log('error', '[Upload Heatmap]', err);
});
ctx.status = 200;
ctx.body = { realName: fileName, filename: path.join(`/assets/files/${fileFolder}`, fileName), name: 'UploadSuccess', message: '上传成功' };
} catch (err) {
ctx.status = 500;
ctx.fs.logger.error(err);
ctx.body = { err: 'upload error.' };
}
}
let remove = async function (ctx, next) {
try {
const fkeys = ctx.request.body;
let removeUrl = path.join(__dirname, '../../', './client', fkeys.url);
const res = fs.existsSync(removeUrl);
if (!res) {
ctx.status = 400;
ctx.body = JSON.stringify({ name: 'DeleteFailed', message: '文件地址不存在' });
return;
}
fs.unlink(removeUrl, function (error) {
if (error) {
console.log(error);
}
})
ctx.status = 200;
ctx.body = { name: 'DeleteSuccess.', message: '删除成功' };
} catch (err) {
ctx.status = 500;
ctx.fs.logger.error(err);
ctx.body = { err: 'upload cleanup error.' };
}
}
let upload_ = async function (ctx, next) {
let fkey = null;
try {
const { p } = ctx.params;
const { files } = await parse(ctx.req);
const file = files[0];
const extname = path.extname(file.filename).toLowerCase();
if (!UploadPath[p]) {
ctx.status = 400;
ctx.body = JSON.stringify({ error: '附件存放的文件夹名称无效' });
return;
} else if (UploadPath[p].indexOf(extname) < 0) {
ctx.status = 400;
ctx.body = JSON.stringify({ error: '文件格式无效' });
return;
} else {
const fileInfo = await ctx.app.fs.attachment.upload(file, { uploadPath: p });
fkey = fileInfo.key;
ctx.body = { uploaded: fkey };
}
} catch (err) {
ctx.status = 500;
ctx.fs.logger.error(err);
ctx.body = { err: 'upload error.' };
}
}
const uploadAliOSS = async (ctx,) => {
// 这个是上传到阿里
try {
const { aliOss } = opts
const { p = 'default' } = ctx.params;
const { files } = await parse(ctx.req);
const file = files[0];
const filename = file.filename || path.basename(file);
const client = new OSS({
// yourRegion填写Bucket所在地域.以华东1(杭州)为例,Region填写为oss-cn-hangzhou.
region: aliOss.region,
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高.强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户.
accessKeyId: aliOss.accessKey,
accessKeySecret: aliOss.secretKey,
// 填写Bucket名称,例如examplebucket.
bucket: aliOss.bucket,
});
let uploadPath = path.posix.join(p, uuid.v4(), filename);
let result = await client.putStream(
uploadPath,
file,
// { contentLength: size }
);
ctx.status = 200;
ctx.body = {
key: result.name,
uploaded: result.name,
url: result.url,
};
} catch (error) {
ctx.status = 400;
ctx.fs.logger.error(error);
ctx.body = { err: 'upload error.' };
}
}
const downloadFromAli = async (ctx) => {
try {
const { aliOss } = opts
const { path, filename } = ctx.query
const client = new OSS({
// yourRegion填写Bucket所在地域.以华东1(杭州)为例,Region填写为oss-cn-hangzhou.
region: aliOss.region,
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高.强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户.
accessKeyId: aliOss.accessKey,
accessKeySecret: aliOss.secretKey,
// 填写Bucket名称,例如examplebucket.
bucket: aliOss.bucket,
});
const filename_ = filename || path.split('/').pop()
const result = await client.get(path);
ctx.status = 200;
ctx.set('Content-Type', 'application/x-xls');
ctx.set('Content-disposition', 'attachment; filename=' + filename_);
ctx.body = result.content;
} catch (error) {
ctx.status = 400;
ctx.fs.logger.error(error);
ctx.body = { err: 'download error.' };
}
}
router.use(download_);
router.post('/_upload/new', upload);
router.delete('/_upload/cleanup', remove);
router.post('/_upload/attachments/ali/:p', uploadAliOSS);
router.get('/_download/attachments/ali', downloadFromAli);
router.post('/_upload/attachments/:p', upload_);
}
};

20
super-screen/routes/index.js

@ -0,0 +1,20 @@
/**
* Created by liu.xinyi
* on 2016/7/7.
*/
'use strict';
const path = require('path');
const fs = require('fs');
module.exports = {
entry: function (app, router, opts) {
fs.readdirSync(__dirname).forEach(function (dir) {
if(fs.lstatSync(path.join(__dirname, dir)).isDirectory()){
fs.readdirSync(path.join(__dirname, dir)).forEach(function (api) {
require(`./${dir}/${api}`).entry(app, router, opts);
app.fs.logger.log('info', '[Router]', 'Inject api:', dir + '/' + path.basename(api, '.js'));
});
}
});
}
};

8
super-screen/server.js

@ -0,0 +1,8 @@
'use strict';
/*jslint node:true*/
//from koa
const scaffold = require('fs-web-server-scaffold');
const config = require('./config.js');
module.exports = scaffold(config);

2599
super-screen/typings/node/node.d.ts

File diff suppressed because it is too large

2517
super-screen/typings/react/react.d.ts

File diff suppressed because it is too large

72
super-screen/webpack.config.js

@ -0,0 +1,72 @@
var path = require('path');
var webpack = require('webpack');
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const PATHS = {
app: path.join(__dirname, 'client/src'),
build: path.join(__dirname, 'client/build')
};
module.exports = {
mode: "development",
devtool: 'source-map',
devServer: {
historyApiFallback: true
},
entry: {
app: ["@babel/polyfill", PATHS.app]
},
output: {
publicPath: '/client/build/',
path: PATHS.build,
filename: '[name].js'
},
resolve: {
modules: [path.resolve(__dirname, 'client/src'), path.resolve(__dirname, 'node_modules')],
extensions: ['.js', '.jsx'],
alias: {
crypto: false,
$utils: path.resolve(__dirname, 'client/src/utils/'),
$components: path.resolve(__dirname, 'client/src/components/'),
}
},
plugins: [
// DefinePlugin 已在 middlewares 的 webpack-dev 中重新加载
// new webpack.DefinePlugin({
// 'process.env.NODE_ENV': JSON.stringify('development'),
// }),
new webpack.HotModuleReplacementPlugin(),
new BundleAnalyzerPlugin({
analyzerPort: 8500,
}),
],
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', {
loader: 'css-loader',
options: {
modules: true
}
}]
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', {
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true
}
}
}]
},
{
test: /\.(js|jsx)$/,
use: 'babel-loader',
include: [PATHS.app, path.resolve(__dirname, 'node_modules', '@peace')],
}, {
test: /\.(eot|woff|woff2|svg|ttf)([\?]?.*)$/,
loader: "file-loader"
}]
}
};

76
super-screen/webpack.config.prod.js

@ -0,0 +1,76 @@
var path = require('path');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
const PATHS = {
app: path.join(__dirname, 'client/src'),
build: path.join(__dirname, 'client/build')
};
module.exports = {
mode: "production",
entry: {
app: ["babel-polyfill", PATHS.app]
},
output: {
path: PATHS.build,
publicPath: '/build',
filename: '[name].[hash:5].js'
},
resolve: {
modules: [path.resolve(__dirname, 'client/src'), path.resolve(__dirname, 'node_modules')],
extensions: ['.js', '.jsx'],
alias: {
crypto: false,
$utils: path.resolve(__dirname, 'client/src/utils/'),
$components: path.resolve(__dirname, 'client/src/components/'),
}
},
plugins: [
new HtmlWebpackPlugin({
filename: '../index.html',
template: './client/index.ejs'
})
],
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all"
}
}
}
},
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', {
loader: 'css-loader',
options: {
modules: true
}
}]
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader',
{
loader: 'less-loader', options: {
lessOptions: {
javascriptEnabled: true
}
}
}]
},
{
test: /\.jsx?$/,
use: 'babel-loader',
include: [PATHS.app, path.resolve(__dirname, 'node_modules', '@peace')],
},{
test: /\.(eot|woff|woff2|svg|ttf)([\?]?.*)$/,
loader: "file-loader"
}]
}
};
Loading…
Cancel
Save