Browse Source

Merge branch 'main' of ssh://gitea.free-sun.vip:2022/free-sun/FS-IOT into main

main
yinweiwen 3 years ago
parent
commit
d1eab76dd0
  1. 3
      .gitignore
  2. 41
      code/VideoAccess-VCMP/api/.vscode/launch.json
  3. 14
      code/VideoAccess-VCMP/api/Dockerfile
  4. 3
      code/VideoAccess-VCMP/api/app/index.js
  5. 189
      code/VideoAccess-VCMP/api/app/lib/controllers/auth/index.js
  6. 36
      code/VideoAccess-VCMP/api/app/lib/index.js
  7. 83
      code/VideoAccess-VCMP/api/app/lib/middlewares/api-log.js
  8. 150
      code/VideoAccess-VCMP/api/app/lib/middlewares/authenticator.js
  9. 50
      code/VideoAccess-VCMP/api/app/lib/middlewares/business-rest.js
  10. 108
      code/VideoAccess-VCMP/api/app/lib/models/user.js
  11. 32
      code/VideoAccess-VCMP/api/app/lib/routes/auth/index.js
  12. 17
      code/VideoAccess-VCMP/api/app/lib/routes/index.js
  13. 103
      code/VideoAccess-VCMP/api/config.js
  14. 36
      code/VideoAccess-VCMP/api/package.json
  15. 35
      code/VideoAccess-VCMP/api/sequelize-automate.config.js
  16. 12
      code/VideoAccess-VCMP/api/server.js
  17. 15
      code/VideoAccess-VCMP/api/utils/forward-api.js
  18. 17
      code/VideoAccess-VCMP/web/.babelrc
  19. 18
      code/VideoAccess-VCMP/web/.vscode/launch.json
  20. 4
      code/VideoAccess-VCMP/web/.vscode/settings.json
  21. 12
      code/VideoAccess-VCMP/web/Dockerfile
  22. BIN
      code/VideoAccess-VCMP/web/client/assets/images/avatar/1.png
  23. BIN
      code/VideoAccess-VCMP/web/client/assets/images/avatar/10.png
  24. BIN
      code/VideoAccess-VCMP/web/client/assets/images/avatar/11.png
  25. BIN
      code/VideoAccess-VCMP/web/client/assets/images/avatar/12.png
  26. BIN
      code/VideoAccess-VCMP/web/client/assets/images/avatar/2.png
  27. BIN
      code/VideoAccess-VCMP/web/client/assets/images/avatar/3.png
  28. BIN
      code/VideoAccess-VCMP/web/client/assets/images/avatar/4.png
  29. BIN
      code/VideoAccess-VCMP/web/client/assets/images/avatar/5.png
  30. BIN
      code/VideoAccess-VCMP/web/client/assets/images/avatar/6.png
  31. BIN
      code/VideoAccess-VCMP/web/client/assets/images/avatar/7.png
  32. BIN
      code/VideoAccess-VCMP/web/client/assets/images/avatar/8.png
  33. BIN
      code/VideoAccess-VCMP/web/client/assets/images/avatar/9.png
  34. BIN
      code/VideoAccess-VCMP/web/client/assets/images/avatar/avatar.jpg
  35. 14
      code/VideoAccess-VCMP/web/client/index.ejs
  36. 44
      code/VideoAccess-VCMP/web/client/index.html
  37. 20
      code/VideoAccess-VCMP/web/client/index.js
  38. 23
      code/VideoAccess-VCMP/web/client/src/app.jsx
  39. 5
      code/VideoAccess-VCMP/web/client/src/components/index.js
  40. 8
      code/VideoAccess-VCMP/web/client/src/index.jsx
  41. 38
      code/VideoAccess-VCMP/web/client/src/index.less
  42. 44
      code/VideoAccess-VCMP/web/client/src/layout/actions/global.js
  43. 15
      code/VideoAccess-VCMP/web/client/src/layout/components/footer/index.jsx
  44. 43
      code/VideoAccess-VCMP/web/client/src/layout/components/header/index.jsx
  45. 59
      code/VideoAccess-VCMP/web/client/src/layout/components/sider/index.jsx
  46. 6
      code/VideoAccess-VCMP/web/client/src/layout/containers/index.js
  47. 135
      code/VideoAccess-VCMP/web/client/src/layout/containers/layout/index.jsx
  48. 18
      code/VideoAccess-VCMP/web/client/src/layout/containers/no-match/index.jsx
  49. 177
      code/VideoAccess-VCMP/web/client/src/layout/index.jsx
  50. 28
      code/VideoAccess-VCMP/web/client/src/layout/reducers/ajaxResponse.js
  51. 36
      code/VideoAccess-VCMP/web/client/src/layout/reducers/global.js
  52. 15
      code/VideoAccess-VCMP/web/client/src/layout/reducers/index.js
  53. 13
      code/VideoAccess-VCMP/web/client/src/layout/store/index.js
  54. 30
      code/VideoAccess-VCMP/web/client/src/layout/store/store.dev.js
  55. 20
      code/VideoAccess-VCMP/web/client/src/layout/store/store.prod.js
  56. 79
      code/VideoAccess-VCMP/web/client/src/sections/auth/actions/auth.js
  57. 10
      code/VideoAccess-VCMP/web/client/src/sections/auth/actions/index.js
  58. 4
      code/VideoAccess-VCMP/web/client/src/sections/auth/containers/index.js
  59. 62
      code/VideoAccess-VCMP/web/client/src/sections/auth/containers/login.jsx
  60. 12
      code/VideoAccess-VCMP/web/client/src/sections/auth/index.js
  61. 40
      code/VideoAccess-VCMP/web/client/src/sections/auth/reducers/auth.js
  62. 6
      code/VideoAccess-VCMP/web/client/src/sections/auth/reducers/index.js
  63. 12
      code/VideoAccess-VCMP/web/client/src/sections/auth/routes.js
  64. 15
      code/VideoAccess-VCMP/web/client/src/sections/example/actions/example.js
  65. 7
      code/VideoAccess-VCMP/web/client/src/sections/example/actions/index.js
  66. 45
      code/VideoAccess-VCMP/web/client/src/sections/example/containers/example.jsx
  67. 5
      code/VideoAccess-VCMP/web/client/src/sections/example/containers/index.js
  68. 15
      code/VideoAccess-VCMP/web/client/src/sections/example/index.js
  69. 15
      code/VideoAccess-VCMP/web/client/src/sections/example/nav-item.jsx
  70. 5
      code/VideoAccess-VCMP/web/client/src/sections/example/reducers/index.js
  71. 18
      code/VideoAccess-VCMP/web/client/src/sections/example/routes.js
  72. 7
      code/VideoAccess-VCMP/web/client/src/sections/example/style.less
  73. 5
      code/VideoAccess-VCMP/web/client/src/utils/authCode.js
  74. 14
      code/VideoAccess-VCMP/web/client/src/utils/func.js
  75. 13
      code/VideoAccess-VCMP/web/client/src/utils/index.js
  76. 14
      code/VideoAccess-VCMP/web/client/src/utils/webapi.js
  77. 91
      code/VideoAccess-VCMP/web/config.js
  78. 15
      code/VideoAccess-VCMP/web/jsconfig.json
  79. 16
      code/VideoAccess-VCMP/web/middlewares/proxy.js
  80. 25
      code/VideoAccess-VCMP/web/middlewares/vite-dev.js
  81. 42
      code/VideoAccess-VCMP/web/middlewares/webpack-dev.js
  82. 73
      code/VideoAccess-VCMP/web/package.json
  83. 214
      code/VideoAccess-VCMP/web/readme.md
  84. 92
      code/VideoAccess-VCMP/web/routes/attachment/index.js
  85. 20
      code/VideoAccess-VCMP/web/routes/index.js
  86. 8
      code/VideoAccess-VCMP/web/server.js
  87. 2599
      code/VideoAccess-VCMP/web/typings/node/node.d.ts
  88. 2517
      code/VideoAccess-VCMP/web/typings/react/react.d.ts
  89. 30
      code/VideoAccess-VCMP/web/vite.config.js
  90. 67
      code/VideoAccess-VCMP/web/webpack.config.js
  91. 76
      code/VideoAccess-VCMP/web/webpack.config.prod.js

3
.gitignore

@ -135,3 +135,6 @@ dist
.yarn/install-state.gz
.pnp.*
*yarn.lock
*package-lock.json
*log/

41
code/VideoAccess-VCMP/api/.vscode/launch.json

@ -0,0 +1,41 @@
{
// 使 IntelliSense
//
// 访: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "启动API",
"program": "${workspaceRoot}/server.js",
"env": {
"NODE_ENV": "development"
},
"args": [
"-p 14000",
"-f http://localhost:14000",
// "-g postgres://postgres:123@10.8.30.32:5432/yinjiguanli",
// "-g postgres://postgres:123456@221.230.55.27:5432/yinjiguanli",
// "-g postgres://FashionAdmin:123456@10.8.30.156:5432/SmartEmergency",
"-g postgres://postgres:Mantis1921@116.63.50.139:54327/smartYingji"
]
},
{
"type": "node",
"request": "launch",
"name": "run mocha",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"stopOnEntry": false,
"args": [
"app/test/*.test.js",
"--no-timeouts"
],
"cwd": "${workspaceRoot}",
"runtimeExecutable": null,
"env": {
"NODE_ENV": "development"
}
}
]
}

14
code/VideoAccess-VCMP/api/Dockerfile

@ -0,0 +1,14 @@
FROM repository.anxinyun.cn/base-images/nodejs12:20.10.12.2
MAINTAINER liuxinyi "liu.xinyi@free-sun.com.cn"
COPY . /var/app
WORKDIR /var/app
EXPOSE 8080
CMD ["-g", "postgres://FashionAdmin:123456@iota-m1:5433/SmartRiver", "--qnak", "5XrM4wEB9YU6RQwT64sPzzE6cYFKZgssdP5Kj3uu", "--qnsk", "w6j2ixR_i-aelc6I7S3HotKIX-ukMzcKmDfH6-M5", "--qnbkt", "anxinyun-test", "--qndmn", "http://test.resources.anxinyun.cn"]
ENTRYPOINT [ "node", "server.js" ]

3
code/VideoAccess-VCMP/api/app/index.js

@ -0,0 +1,3 @@
'use strict';
module.exports = require('./lib');

189
code/VideoAccess-VCMP/api/app/lib/controllers/auth/index.js

@ -0,0 +1,189 @@
'use strict';
const Hex = require('crypto-js/enc-hex');
const MD5 = require('crypto-js/md5');
const moment = require('moment');
const uuid = require('uuid');
async function login(ctx, next) {
const transaction = await ctx.fs.dc.orm.transaction();
try {
const models = ctx.fs.dc.models;
const params = ctx.request.body;
let password = Hex.stringify(MD5(params.password));
const userRes = await models.User.findOne({
where: {
username: params.username,
password: password,
delete: false,
},
attributes: { exclude: ['password'] },
include: [{
attributes: ["resourceId"],
model: models.UserResource
}]
});
if (!userRes) {
ctx.status = 400;
ctx.body = {
"message": "账号或密码错误"
}
} else if (!userRes.enable) {
ctx.status = 400;
ctx.body = { message: "该用户已被禁用" }
} else {
const token = uuid.v4();
let userRslt = Object.assign(userRes.dataValues, {
authorized: true,
token: token,
userResources: userRes.userResources.map(r => r.resourceId),
});
await models.UserToken.create({
token: token,
userInfo: userRslt,
expired: moment().add(30, 'days').format()
});
ctx.status = 200;
ctx.body = userRslt;
}
await transaction.commit();
} catch (error) {
await transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "登录失败"
}
}
}
/**
* 微信小程序登录
* @@requires.body {phone-手机号, password-密码} ctx
*/
async function wxLogin(ctx, next) {
const transaction = await ctx.fs.dc.orm.transaction();
try {
const models = ctx.fs.dc.models;
const params = ctx.request.body;
let password = Hex.stringify(MD5(params.password));
const userRes = await models.User.findOne({
where: {
phone: params.phone,
password: password,
delete: false,
},
attributes: { exclude: ['password'] }
});
if (!userRes) {
ctx.status = 400;
ctx.body = { message: "手机号或密码错误" }
} else if (!userRes.enable) {
ctx.status = 400;
ctx.body = { message: "该用户已被禁用" }
} else {
const token = uuid.v4();
//获取用户关注区域信息
const departmentRes = await models.Department.findOne({ where: { id: userRes.departmentId } });
let attentionRegion = departmentRes;
while (attentionRegion.dependence && attentionRegion.type != 1) {
const departmentParent = await models.Department.findOne({ where: { id: attentionRegion.dependence } });
attentionRegion = {
...departmentParent.dataValues,
nextRegin: attentionRegion
}
}
//获取用户权限信息
const resourceRes = await models.UserResource.findAll({
where: {
userId: userRes.id
},
include: [{
model: models.Resource,
attributes: ['code', 'name'],
}],
attributes: []
});
let userRslt = Object.assign({
authorized: true,
token: token,
...userRes.dataValues
});
await models.UserToken.create({
token: token,
userInfo: userRslt,
expired: moment().add(30, 'day').format('YYYY-MM-DD HH:mm:ss')
}, { transaction: transaction });
ctx.status = 200;
ctx.body = Object.assign({
...userRslt,
userRegionType: departmentRes.type,//1-市级,2-区县级,3-乡镇级,4-村级
attentionRegion: attentionRegion,
resources: resourceRes.map(r => r.resource)
});
}
await transaction.commit();
} catch (error) {
await transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "登录失败"
}
}
}
async function logout(ctx) {
try {
const { token, code } = ctx.request.body;
const models = ctx.fs.dc.models;
await models.UserToken.destroy({
where: {
token: token,
},
});
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "登出失败"
}
}
}
/**
* 微信小程序登出
* @request.body {token-用户登录Token} ctx
*/
async function wxLogout(ctx) {
try {
const { token } = ctx.request.body;
const models = ctx.fs.dc.models;
await models.UserToken.destroy({
where: {
token: token,
},
});
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "登出失败"
}
}
}
module.exports = {
login,
wxLogin,
logout,
wxLogout
};

36
code/VideoAccess-VCMP/api/app/lib/index.js

@ -0,0 +1,36 @@
'use strict';
const routes = require('./routes');
const authenticator = require('./middlewares/authenticator');
// const apiLog = require('./middlewares/api-log');
const businessRest = require('./middlewares/business-rest');
module.exports.entry = function (app, router, opts) {
app.fs.logger.log('info', '[FS-AUTH]', 'Inject auth and api mv into router.');
app.fs.api = app.fs.api || {};
app.fs.api.authAttr = app.fs.api.authAttr || {};
app.fs.api.logAttr = app.fs.api.logAttr || {};
router.use(authenticator(app, opts));
router.use(businessRest(app, router, opts));
// router.use(apiLog(app, opts));
router = routes(app, router, opts);
};
module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Sequelize, models: {} }
require('./models/user')(dc);
require('./models/user_token')(dc);
require('./models/department')(dc);
require('./models/resource')(dc);
require('./models/user_resource')(dc);
require('./models/places')(dc);
require('./models/user_placeSecurityRecord')(dc);
require('./models/report_type')(dc);
require('./models/report_downManage')(dc);
require('./models/department')(dc);
require('./models/report_configition')(dc);
require('./models/report_collection')(dc);
require('./models/report_rectify')(dc);
};

83
code/VideoAccess-VCMP/api/app/lib/middlewares/api-log.js

@ -0,0 +1,83 @@
/**
* Created by PengPeng on 2017/4/26.
*/
'use strict';
const moment = require('moment');
const pathToRegexp = require('path-to-regexp');
function factory(app, opts) {
async function sendToEsAsync(producer, payloads) {
return new Promise((resolve, reject) => {
producer.send(payloads, function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
})
}
async function logger(ctx, next) {
const { path, method } = ctx;
const start = Date.now();
// 等待路由处理
await next();
try {
let logAttr = null;
for (let prop in app.fs.api.logAttr) {
let keys = [];
let re = pathToRegexp(prop.replace(/\:[A-Za-z_\-]+\b/g, '(\\d+)'), keys);
if (re.test(`${method}${path}`)) {
logAttr = app.fs.api.logAttr[prop];
break;
}
}
let parameter = null, parameterShow = null, user_id, _token, app_key;
if (ctx.fs.api) {
const { actionParameter, actionParameterShow, userId, token, appKey } = ctx.fs.api;
parameter = actionParameter;
parameterShow = actionParameterShow;
user_id = userId;
_token = token;
app_key = appKey;
}
const producer = ctx.fs.kafka.producer;
const message = {
log_time: moment().toISOString(),
method: method,
content: logAttr ? logAttr.content : '',
parameter: JSON.stringify(parameter) || JSON.stringify(ctx.request.body),
parameter_show: parameterShow,
visible: logAttr ? logAttr.visible : true,
cost: Date.now() - start,
status_code: ctx.status,
url: ctx.request.url,
user_agent: ctx.request.headers["user-agent"],
user_id: user_id,
session: _token,
app_key: app_key,
header: JSON.stringify(ctx.request.headers),
ip: ctx.request.headers["x-real-ip"] || ctx.ip
};
const payloads = [{
topic: `${opts.kafka.topicPrefix}`,
messages: [JSON.stringify(message)],
partition: 0
}];
await sendToEsAsync(producer, payloads);
} catch (e) {
ctx.fs.logger.error(`日志记录失败: ${e}`);
}
}
return logger;
}
module.exports = factory;

150
code/VideoAccess-VCMP/api/app/lib/middlewares/authenticator.js

@ -0,0 +1,150 @@
/**
* Created by PengLing on 2017/3/27.
*/
'use strict';
const pathToRegexp = require('path-to-regexp');
const util = require('util');
const moment = require('moment');
class ExcludesUrls {
constructor(opts) {
this.allUrls = undefined;
this.reload(opts);
}
sanitizePath(path) {
if (!path) return '/';
const p = '/' + path.replace(/^\/+/i, '').replace(/\/+$/, '').replace(/\/{2,}/, '/');
return p;
}
reload(opts) {
// load all url
if (!this.allUrls) {
this.allUrls = opts;
let that = this;
this.allUrls.forEach(function (url, i, arr) {
if (typeof url === "string") {
url = { p: url, o: '*' };
arr[i] = url;
}
const keys = [];
let eachPath = url.p;
url.p = (!eachPath || eachPath === '(.*)' || util.isRegExp(eachPath)) ? eachPath : that.sanitizePath(eachPath);
url.pregexp = pathToRegexp(eachPath, keys);
});
}
}
isExcluded(path, method) {
return this.allUrls.some(function (url) {
return !url.auth
&& url.pregexp.test(path)
&& (url.o === '*' || url.o.indexOf(method) !== -1);
});
}
}
/**
* 判断Url是否不鉴权
* @param {*} opts {exclude: [*] or []}'*'['*']:跳过所有路由; []:所有路由都要验证
* @param {*} path 当前request的path
* @param {*} method 当前request的method
*/
let isPathExcluded = function (opts, path, method) {
let excludeAll = Boolean(opts.exclude && opts.exclude.length && opts.exclude[0] == '*');
let excludes = null;
if (!excludeAll) {
let excludeOpts = opts.exclude || [];
excludeOpts.push({ p: '/login', o: 'POST' });
excludeOpts.push({ p: '/wxLogin', o: 'POST' });
excludeOpts.push({ p: '/logout', o: 'PUT' });
excludeOpts.push({ p: '/wxLogout', o: 'PUT' });
excludes = new ExcludesUrls(excludeOpts);
}
let excluded = excludeAll || excludes.isExcluded(path, method);
return excluded;
};
let authorizeToken = async function (ctx, token) {
let rslt = null;
const tokenFormatRegexp = /^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$/g;
if (token && tokenFormatRegexp.test(token)) {
try {
const axyRes = await ctx.fs.dc.models.UserToken.findOne({
where: {
token: token,
expired: { $gte: moment().format('YYYY-MM-DD HH:mm:ss') }
}
});
const { userInfo, expired } = axyRes;
if (!expired || moment().valueOf() <= moment(expired).valueOf()) {
rslt = {
'authorized': userInfo.authorized,
'resources': (userInfo || {}).resources || [],
};
ctx.fs.api.userId = userInfo.id;
ctx.fs.api.userInfo = userInfo;
ctx.fs.api.token = token;
}
} catch (err) {
const { error } = err.response || {};
ctx.fs.logger.log('[anxinyun]', '[AUTH] failed', (error || {}).message || `cannot GET /users/${token}`);
}
}
return rslt;
};
let isResourceAvailable = function (resources, options) {
let authCode = null;
// authorize user by authorization attribute
const { authAttr, method, path } = options;
console.log(resources, options)
for (let prop in authAttr) {
let keys = [];
let re = pathToRegexp(prop.replace(/\:[A-Za-z_\-]+\b/g, '(\\d+)'), keys);
if (re.test(`${method}${path}`)) {
authCode = authAttr[prop];
break;
}
}
return !authCode || (resources || []).some(code => code === authCode);
};
function factory(app, opts) {
return async function auth(ctx, next) {
const { path, method, header, query } = ctx;
ctx.fs.logger.log('[AUTH] start', path, method);
ctx.fs.api = ctx.fs.api || {};
ctx.fs.port = opts.port;
ctx.redis = app.redis;
let error = null;
if (path) {
if (!isPathExcluded(opts, path, method)) {
const user = await authorizeToken(ctx, header.token || query.token);
if (user && user.authorized) {
// if (!isResourceAvailable(user.resources, { authAttr: app.fs.auth.authAttr, path, method })) {
// error = { status: 403, name: 'Forbidden' }
// } else {
// error = { status: 401, name: 'Unauthorized' }
// }
} else {
error = { status: 401, name: 'Unauthorized' }
}
}
} else {
error = { status: 401, name: 'Unauthorized' };
}
if (error) {
ctx.fs.logger.log('[AUTH] failed', path, method);
ctx.status = error.status;
ctx.body = error.name;
} else {
ctx.fs.logger.log('[AUTH] passed', path, method);
await next();
}
}
}
module.exports = factory;

50
code/VideoAccess-VCMP/api/app/lib/middlewares/business-rest.js

@ -0,0 +1,50 @@
'use strict';
const request = require('superagent');
const buildUrl = (url,token) => {
let connector = url.indexOf('?') === -1 ? '?' : '&';
return `${url}${connector}token=${token}`;
};
function factory(app, router, opts) {
return async function (ctx, next) {
const token = ctx.fs.api.token;
//console.log(username,password)
const req = {
get: (url, query) => {
return request
.get(buildUrl(url,token))
.query(query)
},
post: (url, data, query) => {
return request
.post(buildUrl(url,token))
.query(query)
//.set('Content-Type', 'application/json')
.send(data);
},
put: (url, data) => {
return request
.put(buildUrl(url,token))
//.set('Content-Type', 'application/json')
.send(data);
},
delete: (url) => {
return request
.del(buildUrl(url,token))
},
};
app.business = app.business || {};
app.business.request = req;
await next();
};
}
module.exports = factory;

108
code/VideoAccess-VCMP/api/app/lib/models/user.js

@ -0,0 +1,108 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const User = sequelize.define("user", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "user_id_uindex"
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "name",
autoIncrement: false
},
username: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "用户名 账号",
primaryKey: false,
field: "username",
autoIncrement: false
},
password: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "password",
autoIncrement: false
},
departmentId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "部门id",
primaryKey: false,
field: "department_id",
autoIncrement: false
},
email: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "email",
autoIncrement: false
},
enable: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: null,
comment: "启用状态",
primaryKey: false,
field: "enable",
autoIncrement: false
},
delete: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "delete",
autoIncrement: false
},
phone: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "手机号(小程序使用手机号登录)",
primaryKey: false,
field: "phone",
autoIncrement: false
},
post: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "职位",
primaryKey: false,
field: "post",
autoIncrement: false
}
}, {
tableName: "user",
comment: "",
indexes: []
});
dc.models.User = User;
return User;
};

32
code/VideoAccess-VCMP/api/app/lib/routes/auth/index.js

@ -0,0 +1,32 @@
'use strict';
const auth = require('../../controllers/auth');
module.exports = function (app, router, opts) {
/**
* @api {Post} login 登录.
* @apiVersion 1.0.0
* @apiGroup Auth
*/
app.fs.api.logAttr['POST/login'] = { content: '登录', visible: true };
router.post('/login', auth.login);
/**
* @api {POST} wxLogin 微信小程序登录.使用手机号密码登录
* @apiVersion 1.0.0
* @apiGroup Auth
*/
app.fs.api.logAttr['POST/wxLogin'] = { content: '微信小程序登录', visible: true };
router.post('/wxLogin', auth.wxLogin);
app.fs.api.logAttr['PUT/logout'] = { content: '登出', visible: false };
router.put('/logout', auth.logout);
/**
* @api {PUT} wxLogout 微信小程序登出
* @apiVersion 1.0.0
* @apiGroup Auth
*/
app.fs.api.logAttr['PUT/wxLogout'] = { content: '登出', visible: false };
router.put('/wxLogout', auth.wxLogout);
};

17
code/VideoAccess-VCMP/api/app/lib/routes/index.js

@ -0,0 +1,17 @@
'use strict';
const path = require('path');
const fs = require('fs');
module.exports = function (app, router, opts) {
fs.readdirSync(__dirname).forEach((filename) => {
if (filename.indexOf('.') !== 0 &&fs.lstatSync(path.join(__dirname, filename)).isDirectory()) {
fs.readdirSync(path.join(__dirname, filename)).forEach((api) => {
if (api.indexOf('.') == 0 || api.indexOf('.js') == -1) return;
require(`./${filename}/${api}`)(app, router, opts);
});
}
});
return router;
};

103
code/VideoAccess-VCMP/api/config.js

@ -0,0 +1,103 @@
'use strict';
/*jslint node:true*/
const path = require('path');
const os = require('os');
const moment = require('moment');
const args = require('args');
const dev = process.env.NODE_ENV == 'development';
// 启动参数
args.option(['p', 'port'], '启动端口');
args.option(['g', 'pg'], 'postgre服务URL');
args.option(['f', 'fileHost'], '文件中心本地化存储: WebApi 服务器地址(必填), 该服务器提供文件上传Web服务');
const flags = args.parse(process.argv);
const IOT_VIDEO_ACCESS_DB = process.env.IOT_VIDEO_ACCESS_DB || flags.pg;
const IOT_VIDEO_ACCESS_LOCAL_SVR_ORIGIN = process.env.IOT_VIDEO_ACCESS_LOCAL_SVR_ORIGIN || flags.fileHost;
if (!IOT_VIDEO_ACCESS_DB) {
console.log('缺少启动参数,异常退出');
args.showHelp();
process.exit(-1);
}
const product = {
port: flags.port || 8080,
staticDirs: ['static'],
mws: [
{
entry: require('@fs/attachment').entry,
opts: {
local: {
origin: IOT_VIDEO_ACCESS_LOCAL_SVR_ORIGIN || `http://localhost:${flags.port || 8080}`,
rootPath: 'static',
childPath: 'upload',
},
maxSize: 104857600, // 100M
}
}, {
entry: require('./app').entry,
opts: {
exclude: [], // 不做认证的路由,也可以使用 exclude: ["*"] 跳过所有路由
}
}
],
dc: {
url: IOT_VIDEO_ACCESS_DB,
opts: {
pool: {
max: 80,
min: 10,
idle: 10000
},
define: {
freezeTableName: true, // 固定表名
timestamps: false // 不含列 "createAt"/"updateAt"/"DeleteAt"
},
timezone: '+08:00',
logging: false
},
models: [require('./app').models]
},
logger: {
level: 'info',
json: false,
filename: path.join(__dirname, 'log', 'runtime.log'),
colorize: false,
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
}
};
const development = {
port: product.port,
staticDirs: product.staticDirs,
mws: product.mws,
dc: product.dc,
logger: product.logger
};
if (dev) {
// mws
for (let mw of development.mws) {
// if (mw.opts.exclude) mw.opts.exclude = ['*']; // 使用 ['*'] 跳过所有路由
}
// logger
development.logger.filename = path.join(__dirname, 'log', 'development.log');
development.logger.level = 'debug';
development.dc.opts.logging = console.log;
}
module.exports = dev ? development : product;

36
code/VideoAccess-VCMP/api/package.json

@ -0,0 +1,36 @@
{
"name": "smart-emergency",
"version": "1.0.0",
"description": "fs smart emergency api",
"main": "server.js",
"scripts": {
"test": "set DEBUG=true&&\"node_modules/.bin/mocha\" --harmony --reporter spec app/test/*.test.js",
"start": "set NODE_ENV=development&&node server -p 14000 -g postgres://postgres:123@10.8.30.32:5432/yinjiguanli -f http://localhost:14000",
"start:linux": "export NODE_ENV=development&&node server -p 4000 -g postgres://FashionAdmin:123456@10.8.30.39:5432/pm1",
"automate": "sequelize-automate -c sequelize-automate.config.js"
},
"author": "",
"license": "MIT",
"repository": {},
"dependencies": {
"@fs/attachment": "^1.0.0",
"args": "^3.0.7",
"crypto-js": "^4.0.0",
"file-saver": "^2.0.2",
"fs-web-server-scaffold": "^2.0.2",
"ioredis": "^4.19.4",
"koa-convert": "^1.2.0",
"koa-proxy": "^0.9.0",
"moment": "^2.24.0",
"path": "^0.12.7",
"path-to-regexp": "^3.0.0",
"pg": "^7.9.0",
"redis": "^3.1.2",
"request": "^2.88.2",
"superagent": "^3.5.2",
"uuid": "^3.3.2"
},
"devDependencies": {
"mocha": "^6.0.2"
}
}

35
code/VideoAccess-VCMP/api/sequelize-automate.config.js

@ -0,0 +1,35 @@
module.exports = {
// 数据库配置 与 sequelize 相同
dbOptions: {
database: 'yinjiguanli',
username: 'postgres',
password: '123',
dialect: 'postgres',
host: '10.8.30.32',
port: 5432,
define: {
underscored: false,
freezeTableName: false,
charset: 'utf8mb4',
timezone: '+00: 00',
dialectOptions: {
collate: 'utf8_general_ci',
},
timestamps: false,
},
},
options: {
type: 'freesun', // 指定 models 代码风格
camelCase: true, // Models 文件中代码是否使用驼峰命名
modalNameSuffix: false, // 模型名称是否带 ‘Model’ 后缀
fileNameCamelCase: false, // Model 文件名是否使用驼峰法命名,默认文件名会使用表名,如 `user_post.js`;如果为 true,则文件名为 `userPost.js`
dir: './app/lib/models', // 指定输出 models 文件的目录
typesDir: 'models', // 指定输出 TypeScript 类型定义的文件目录,只有 TypeScript / Midway 等会有类型定义
emptyDir: false, // !!! 谨慎操作 生成 models 之前是否清空 `dir` 以及 `typesDir`
tables: ['user_placeSecurityRecord', 'places'], // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性
skipTables: ['user'], // 指定跳过哪些表的 models,如 ['user'];如果为 null,则忽略改属性
tsNoCheck: false, // 是否添加 `@ts-nocheck` 注释到 models 文件中
ignorePrefix: [], // 生成的模型名称忽略的前缀,因为 项目中有以下表名是以 t_ 开头的,在实际模型中不需要, 可以添加多个 [ 't_data_', 't_',] ,长度较长的 前缀放前面
attrLength: false, // 在生成模型的字段中 是否生成 如 var(128)这种格式,公司一般使用 String ,则配置为 false
},
}

12
code/VideoAccess-VCMP/api/server.js

@ -0,0 +1,12 @@
/**
* Created by rain on 2016/1/25.
*/
'use strict';
/*jslint node:true*/
//from koa
const scaffold = require('fs-web-server-scaffold');
const config = require('./config');
module.exports = scaffold(config);

15
code/VideoAccess-VCMP/api/utils/forward-api.js

@ -0,0 +1,15 @@
'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, '');
}
})));
}
};

17
code/VideoAccess-VCMP/web/.babelrc

@ -0,0 +1,17 @@
{
"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": {}
}
}

18
code/VideoAccess-VCMP/web/.vscode/launch.json

@ -0,0 +1,18 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Server",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/server.js",
"args": [
"-u http://127.0.0.1:4000"
],
"outputCapture": "std",
"env": {
"NODE_ENV": "development"
}
}
]
}

4
code/VideoAccess-VCMP/web/.vscode/settings.json

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

12
code/VideoAccess-VCMP/web/Dockerfile

@ -0,0 +1,12 @@
FROM repository.anxinyun.cn/base-images/nodejs12:20.10.12.2
COPY . /var/app
WORKDIR /var/app
EXPOSE 8080
CMD ["-u", "http://localhost:8088"]
ENTRYPOINT [ "node", "server.js" ]

BIN
code/VideoAccess-VCMP/web/client/assets/images/avatar/1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/avatar/10.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/avatar/11.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/avatar/12.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/avatar/2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/avatar/3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/avatar/4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/avatar/5.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/avatar/6.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/avatar/7.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/avatar/8.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/avatar/9.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/avatar/avatar.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

14
code/VideoAccess-VCMP/web/client/index.ejs

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
<link rel="shortcut icon" href="/assets/images/favicon.ico">
</head>
<body>
<div id='App'></div>
</body>
</html>

44
code/VideoAccess-VCMP/web/client/index.html

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
<link rel="shortcut icon" href="/assets/images/favicon.ico">
</head>
<body>
<div id='App'></div>
<!-- Webpack -->
<script type="text/javascript" src="http://localhost:5001/client/build/app.js"></script>
<!-- Vite -->
<script type="module">
import RefreshRuntime from "http://localhost:5002/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => { }
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
const global = window
</script>
<script type="module" src="http://localhost:5002/src/index.jsx"></script>
<!-- Vite End -->
<script>
//过滤掉一些无用的警告、没有价值的报错
//代理console.warn方法
const _consoleWarn = (...rest) => {
console.error(...rest)
};
console.error = (...rest) => {
if (
![
'Each child in a list should have a unique "key" prop',
].some((item) => rest[0].indexOf(item) !== -1)
) {
_consoleWarn(...rest);
}
};
</script>
</body>
</html>

20
code/VideoAccess-VCMP/web/client/index.js

@ -0,0 +1,20 @@
/**
* 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'));
});
}
};

23
code/VideoAccess-VCMP/web/client/src/app.jsx

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

5
code/VideoAccess-VCMP/web/client/src/components/index.js

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

8
code/VideoAccess-VCMP/web/client/src/index.jsx

@ -0,0 +1,8 @@
'use strict';
import React from 'react';
import { render } from 'react-dom';
import App from './app';
import './index.less';
render((<App projectName="飞尚物联" />), document.getElementById('App'));

38
code/VideoAccess-VCMP/web/client/src/index.less

@ -0,0 +1,38 @@
// webpack (vite 用 alias 兼容了)
@import '~@douyinfe/semi-ui/dist/css/semi.min.css';
@import '~perfect-scrollbar/css/perfect-scrollbar.css';
@import '~nprogress/nprogress.css';
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
margin: 0;
height: 100%;
width: 100%;
a:link {
text-decoration: none;
color: unset
}
a:visited {
text-decoration: none;
color: unset
}
a:hover {
text-decoration: none;
color: unset
}
a:active {
text-decoration: none;
color: unset
}
}

44
code/VideoAccess-VCMP/web/client/src/layout/actions/global.js

@ -0,0 +1,44 @@
'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) {
const headerHeight = 60
const footerHeight = 0
return {
type: RESIZE,
payload: {
clientHeight: clientHeight - headerHeight - footerHeight,
clientWidth: clientWidth
}
}
}
export const INIT_API_ROOT = 'INIT_API_ROOT';
export function initApiRoot () {
return dispatch => {
RouteRequest.get(RouteTable.apiRoot).then(res => {
localStorage.setItem('apiRoot', res.root);
dispatch({
type: INIT_API_ROOT,
payload: {
apiRoot: res.root
}
})
});
}
}

15
code/VideoAccess-VCMP/web/client/src/layout/components/footer/index.jsx

@ -0,0 +1,15 @@
'use strict';
import React from 'react';
import moment from 'moment'
export default class Footer extends React.Component {
render () {
// const { } = this.props;
return (
<div style={{ textAlign: 'center', lineHeight: '32px' }}>
Copyright © {moment().year()} All Rights Reserved 版权所有· 江西飞尚科技有限公司
</div>
);
}
};

43
code/VideoAccess-VCMP/web/client/src/layout/components/header/index.jsx

@ -0,0 +1,43 @@
'use strict';
import React from 'react';
import { connect } from 'react-redux';
import { Nav } from '@douyinfe/semi-ui';
const Header = props => {
const { dispatch, history, user, actions } = props
return (
<div style={{ position: 'relative', height: 60, minWidth: 520 }}>
<div style={{ float: 'left', paddingLeft: 32, fontSize: 16 }}>
<div style={{
lineHeight: '60px', display: 'inline-block', fontSize: 20, textShadow: '0 4px 3px rgba(0, 0, 0, 0.2)',
userSelect: 'none'
}}>
飞尚物联
</div>
</div>
<div id="nav" style={{ float: 'right' }}>
<Nav mode={'horizontal'} onClick={({ itemKey }) => {
if (itemKey == 'logout') {
dispatch(actions.auth.logout(user));
history.push(`/signin`);
}
}}>
<Nav.Sub itemKey={'user'} text={<div style={{ display: 'inline-block' }}>{user.displayName}</div>}>
<Nav.Item itemKey={'logout'} text={'退出'} />
</Nav.Sub>
</Nav>
</div>
</div>
)
};
function mapStateToProps (state) {
const { global, auth } = state;
return {
actions: global.actions,
user: auth.user
};
}
export default connect(mapStateToProps)(Header);

59
code/VideoAccess-VCMP/web/client/src/layout/components/sider/index.jsx

@ -0,0 +1,59 @@
import React, { useEffect, useState } from 'react';
import PerfectScrollbar from 'perfect-scrollbar';
import { connect } from 'react-redux';
import { Nav } from '@douyinfe/semi-ui';
import { push } from 'react-router-redux';
let scrollbar = null
const Sider = props => {
const { collapsed, clientHeight, dispatch } = props
const [items, setItems] = useState([])
const [selectedKeys, setSelectedKeys] = useState([])
const [openKeys, setOpenKeys] = useState([])
useEffect(() => {
const { sections, dispatch, user } = props;
let nextItems = []
for (let c of sections) {
if (typeof c.getNavItem == 'function') {
let item = c.getNavItem(user, dispatch);
if (item) {
nextItems.push.apply(nextItems, item)
}
}
}
setItems(nextItems)
scrollbar = new PerfectScrollbar('#page-slider', { suppressScrollX: true });
}, [])
useEffect(() => {
if (scrollbar) {
scrollbar.update();
}
})
return (
<div id={'page-slider'} style={{ height: clientHeight, position: 'relative' }}>
<Nav
style={{}}
onSelect={({ selectedItems }) => {
const selectItem = selectedItems[0]
if (selectItem.to) {
dispatch(push(selectItem.to))
}
}}
items={items}
/>
</div>
)
}
function mapStateToProps (state) {
const { global } = state;
return {
clientHeight: global.clientHeight,
};
}
export default connect(mapStateToProps)(Sider);

6
code/VideoAccess-VCMP/web/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 };

135
code/VideoAccess-VCMP/web/client/src/layout/containers/layout/index.jsx

@ -0,0 +1,135 @@
'use strict';
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { Layout, Toast } from '@douyinfe/semi-ui';
import Sider from '../../components/sider';
import Header from '../../components/header';
import Footer from '../../components/footer';
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>
`
});
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_ = () => {
dispatch(resize(
document.body.clientHeight,
document.body.clientWidth - (collapsed ? 120 : 240)
));
}
useEffect(() => {
scrollbar = new PerfectScrollbar('#page-content', { suppressScrollX: true });
window.addEventListener('resize', resize_);
return () => {
window.removeEventListener('resize', resize_);
}
}, [])
useEffect(() => {
NProgress.done();
if (!user || !user.authorized) {
history.push('/signin');
}
if (msg) {
if (msg.done) {
Toast.success(msg.done);
}
if (msg.error) {
Toast.error(msg.error);
}
}
const dom = document.getElementById('page-content');
if (dom) {
scrollbar.update();
dom.scrollTop = 0;
}
})
return (
<Layout id="layout">
<Layout.Header>
<Header
user={user}
pathname={location.pathname}
toggleCollapsed={() => {
setCollapsed(!collapsed);
}}
collapsed={collapsed}
history={history}
/>
</Layout.Header>
<Layout>
<Layout.Sider>
<Sider
sections={sections}
dispatch={dispatch}
user={user}
pathname={location.pathname}
collapsed={collapsed}
/>
</Layout.Sider>
<Layout.Content>
<div style={{
margin: '12px 12px 0px',
}}>
<div id="page-content" style={{
height: clientHeight - 12,
minWidth: 520,
position: 'relative',
}}>
<div style={{
minHeight: clientHeight - 32 - 12,
position: 'relative',
padding: '12px 8px',
}}>
{children}
</div>
<Layout.Footer>
<Footer />
</Layout.Footer>
</div>
</div>
</Layout.Content>
</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);

18
code/VideoAccess-VCMP/web/client/src/layout/containers/no-match/index.jsx

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

177
code/VideoAccess-VCMP/web/client/src/layout/index.jsx

@ -0,0 +1,177 @@
'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 '@douyinfe/semi-ui';
import * as layoutActions from './actions/global';
import zhCN from '@douyinfe/semi-ui/lib/es/locale/source/zh_CN';
import { basicReducer } from '@peace/utils';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
const { initLayout, initApiRoot, resize } = 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) => {
let 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) {
obj.exact = true
}
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(resize(document.body.clientHeight, document.body.clientWidth));
store.dispatch(actions.auth.initAuth());
store.dispatch(initApiRoot())
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={route.hasOwnProperty('exact') ? route.exact : true}
path={route.path}
component={route.component}
/>
)))
}, [])
return (
store ?
<ConfigProvider locale={zhCN}>
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
{outerRoutes}
<Layout
history={history}
routes={innnerRoutes}
>
{combineRoutes}
</Layout>
<Route
path={'*'}
component={NoMatch}
/>
</Switch>
</ConnectedRouter>
</Provider>
</ConfigProvider>
: ''
)
}
export default Root;

28
code/VideoAccess-VCMP/web/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
code/VideoAccess-VCMP/web/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,
detailsComponent: null
};
default:
return state;
}
}
export default global;

15
code/VideoAccess-VCMP/web/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
};

13
code/VideoAccess-VCMP/web/client/src/layout/store/index.js

@ -0,0 +1,13 @@
'use strict';
import storeProd from './store.prod'
import storeDev from './store.dev'
let store = null;
if (process.env.NODE_ENV == 'production') {
store = storeProd;
} else {
store = storeDev;
}
export default store;

30
code/VideoAccess-VCMP/web/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
code/VideoAccess-VCMP/web/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;

79
code/VideoAccess-VCMP/web/client/src/sections/auth/actions/auth.js

@ -0,0 +1,79 @@
'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) {
return dispatch => {
dispatch({ type: REQUEST_LOGIN });
if (!username || !password) {
dispatch({
type: LOGIN_ERROR,
payload: { error: '请输入账号名和密码' }
});
return Promise.resolve();
}
return dispatch({
type: LOGIN_SUCCESS,
payload: {
user: {
authorized: true,
displayName: 'TEST'
}
},
});
const url = ApiTable.login;
return Request.post(url, { username, password, p: '456' })
.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');
Request.put(url, {
token: token
});
return {
type: LOGOUT
};
}
export default {
initAuth,
login,
logout
}

10
code/VideoAccess-VCMP/web/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
code/VideoAccess-VCMP/web/client/src/sections/auth/containers/index.js

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

62
code/VideoAccess-VCMP/web/client/src/sections/auth/containers/login.jsx

@ -0,0 +1,62 @@
'use strict';
import React, { useEffect, useRef } from 'react';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import { Form, Button, Toast } from '@douyinfe/semi-ui';
import { login } from '../actions/auth';
const Login = props => {
const { dispatch, user, error, isRequesting } = props
const form = useRef();
useEffect(() => {
if (error) {
Toast.error(error);
form.current.setValue('password', '')
}
}, [error])
useEffect(() => {
if (user && user.authorized) {
dispatch(push('/example/e1'));
}
}, [user])
return (
<div style={{
height: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
<div style={{
width: 400,
height: 410,
padding: 30,
}}>
<p style={{ fontSize: 21, fontWeight: 'bold', textAlign: 'center' }}>飞尚物联</p>
<Form
onSubmit={values => {
dispatch(login(values.username, values.password))
}}
getFormApi={formApi => form.current = formApi}
>
<Form.Input field='username' label='用户名' />
<Form.Input field='password' mode="password" autoComplete="" label='密码' />
<Button htmlType='submit' block theme="solid" >登录</Button>
</Form>
</div>
</div>
);
}
function mapStateToProps (state) {
const { auth } = state;
return {
user: auth.user,
error: auth.error,
isRequesting: auth.isRequesting
}
}
export default connect(mapStateToProps)(Login);

12
code/VideoAccess-VCMP/web/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
code/VideoAccess-VCMP/web/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
code/VideoAccess-VCMP/web/client/src/sections/auth/reducers/index.js

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

12
code/VideoAccess-VCMP/web/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
}
}];

15
code/VideoAccess-VCMP/web/client/src/sections/example/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
code/VideoAccess-VCMP/web/client/src/sections/example/actions/index.js

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

45
code/VideoAccess-VCMP/web/client/src/sections/example/containers/example.jsx

@ -0,0 +1,45 @@
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { Spin, Card } from '@douyinfe/semi-ui';
import '../style.less'
const { Meta } = Card;
const Example = (props) => {
const { dispatch, actions, user, loading } = props
useEffect(() => {
// ACTION
dispatch(actions.example.getMembers(user.orgId))
}, [])
return (
<Spin tip="biubiubiu~" spinning={loading}>
<div id='example'>
<p>STYLE EXAMPLE</p>
</div>
<Card
style={{ maxWidth: 300 }}
cover={
<img
alt="example"
src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo2.jpeg"
/>
}
>
<Meta title="组件示例" />
</Card>
</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);

5
code/VideoAccess-VCMP/web/client/src/sections/example/containers/index.js

@ -0,0 +1,5 @@
'use strict';
import Example from './example';
export { Example };

15
code/VideoAccess-VCMP/web/client/src/sections/example/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: 'example',
name: '书写示例',
reducers: reducers,
routes: routes,
actions: actions,
getNavItem: getNavItem
};

15
code/VideoAccess-VCMP/web/client/src/sections/example/nav-item.jsx

@ -0,0 +1,15 @@
import React from 'react';
import { IconCode } from '@douyinfe/semi-icons';
export function getNavItem (user, dispatch) {
return (
[
{
itemKey: 'example', text: '举个栗子', icon: <IconCode />,
items: [
{ itemKey: 'e1', to: '/example/e1', text: '举个棒子' },
]
},
]
);
}

5
code/VideoAccess-VCMP/web/client/src/sections/example/reducers/index.js

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

18
code/VideoAccess-VCMP/web/client/src/sections/example/routes.js

@ -0,0 +1,18 @@
'use strict';
import { Example, } from './containers';
export default [{
type: 'inner',
route: {
path: '/example',
key: 'example',
breadcrumb: '栗子',
// 不设置 component 则面包屑禁止跳转
childRoutes: [{
path: '/e1',
key: 'e1',
component: Example,
breadcrumb: '棒子',
}]
}
}];

7
code/VideoAccess-VCMP/web/client/src/sections/example/style.less

@ -0,0 +1,7 @@
#example {
box-shadow: 3px 3px 2px black;
}
#example:hover {
color: yellowgreen;
}

5
code/VideoAccess-VCMP/web/client/src/utils/authCode.js

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

14
code/VideoAccess-VCMP/web/client/src/utils/func.js

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

13
code/VideoAccess-VCMP/web/client/src/utils/index.js

@ -0,0 +1,13 @@
'use strict';
import { isAuthorized } from './func';
import { AuthorizationCode } from './authCode';
import { ApiTable, RouteTable } from './webapi'
export {
isAuthorized,
AuthorizationCode,
ApiTable,
RouteTable,
}

14
code/VideoAccess-VCMP/web/client/src/utils/webapi.js

@ -0,0 +1,14 @@
'use strict';
export const ApiTable = {
login: 'login',
logout: 'logout',
getEnterprisesMembers: 'enterprises/{enterpriseId}/members',
};
export const RouteTable = {
apiRoot: '/api/root',
fileUpload: '/_upload/new',
cleanUpUploadTrash: '/_upload/cleanup',
};

91
code/VideoAccess-VCMP/web/config.js

@ -0,0 +1,91 @@
'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' || process.env.NODE_ENV == 'developmentVite';
const vite = process.env.NODE_ENV == 'developmentVite';
dev && console.log('\x1B[33m%s\x1b[0m', '请遵循并及时更新 readme.md,维护良好的开发环境,媛猿有责');
// // 启动参数
args.option(['p', 'port'], '启动端口');
args.option(['u', 'api-url'], 'webapi的URL');
const flags = args.parse(process.argv);
const API_URL = process.env.API_URL || flags.apiUrl;
if (!API_URL) {
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: API_URL,
match: /^\/_api\//,
}
}, {
entry: require('./routes').entry,
opts: {
apiUrl: API_URL,
staticRoot: './client',
}
}, {
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
}
};
let config;
if (dev) {
config = {
port: product.port,
staticDirs: product.staticDirs,
mws: product.mws
.concat([
vite ?
{
entry: require('./middlewares/vite-dev').entry,
opts: {}
}
:
{
entry: require('./middlewares/webpack-dev').entry,
opts: {}
}
])
,
logger: product.logger
}
config.logger.filename = path.join(__dirname, 'log', 'development.txt');
} else {
config = product;
}
module.exports = config;//区分开发和发布

15
code/VideoAccess-VCMP/web/jsconfig.json

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

16
code/VideoAccess-VCMP/web/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, '');
}
})));
}
};

25
code/VideoAccess-VCMP/web/middlewares/vite-dev.js

@ -0,0 +1,25 @@
'use strict';
const express = require('express')
const { createServer: createViteServer } = require('vite')
module.exports = {
entry: async function (app, router, opts) {
const server = express()
// 以中间件模式创建 Vite 服务器
// 竟然会自动读 /vite.config.js 的配置
const vite = await createViteServer({})
// 将 vite 的 connect 实例作中间件使用
server.use(vite.middlewares)
server.use('*', async (req, res) => {
// 如果 `middlewareMode` 是 `'ssr'`,应在此为 `index.html` 提供服务.
// 如果 `middlewareMode` 是 `'html'`,则此处无需手动服务 `index.html`
// 因为 Vite 自会接管
})
server.listen(5002)
console.info('vite server.listen 5002');
}
};

42
code/VideoAccess-VCMP/web/middlewares/webpack-dev.js

@ -0,0 +1,42 @@
'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');
const compiler = webpack(devConfig);
module.exports = {
entry: function (app, router, opts) {
app.use(proxy('http://localhost:5001', {
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:5001', {
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('5001', function (err) {
if (err) {
console.error(err);
} else {
console.info(`webpack-dev listen 5001`);
}
})
}
};

73
code/VideoAccess-VCMP/web/package.json

@ -0,0 +1,73 @@
{
"name": "fs-anxincloud-4.0",
"version": "1.0.0",
"description": "anxincloud-4.0",
"main": "server.js",
"scripts": {
"test": "mocha",
"start-vite": "cross-env NODE_ENV=developmentVite npm run start-params",
"start": "cross-env NODE_ENV=development npm run start-params",
"start-params": "node server -p 5000 -u http://127.0.0.1:4000",
"deploy": "export NODE_ENV=production&& npm run build && node server",
"build-dev": "export NODE_ENV=development&&webpack --config webpack.config.js",
"build": "export 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",
"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",
"nprogress": "^0.2.0",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"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",
"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": {
"@douyinfe/semi-ui": "^2.8.0",
"@fs/attachment": "^1.0.0",
"@peace/components": "0.0.35",
"@peace/utils": "^0.0.44",
"@vitejs/plugin-react": "^1.3.1",
"@vitejs/plugin-react-refresh": "^1.3.6",
"args": "^5.0.1",
"cross-env": "^7.0.3",
"fs-web-server-scaffold": "^1.0.6",
"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",
"perfect-scrollbar": "^1.5.5",
"superagent": "^6.1.0",
"vite": "^2.9.5",
"webpack-dev-server": "^3.11.2"
}
}

214
code/VideoAccess-VCMP/web/readme.md

@ -0,0 +1,214 @@
创建时间:2021/08/19
## 1. 文档维护:
- 文档相关内容若有更改,请及时更新文档,以备后来者查询;
## 2. 项目开发:
- 请遵循此文档约定的目录结构与约定
```js
|-- .babelrc
|-- 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 // 资源文件
| | |-- images
| | |-- avatar
| |-- src // 项目代码
| |-- app.js // 由此开始并加载模块
| |-- index.js
| |-- components // 公用组件
| | |-- index.js // 由此导出组件
| | |-- Upload
| | |-- index.js
| |-- layout // 项目布局以及初始化等操作
| | |-- index.js
| | |-- actions
| | | |-- global.js
| | |-- components
| | | |-- footer
| | | | |-- index.js
| | | |-- header
| | | | |-- index.js
| | | |-- sider
| | | |-- index.js
| | |-- containers
| | | |-- index.js
| | | |-- layout
| | | | |-- 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
| |-- utils //
| |-- authCode.js
| |-- func.js // 常用函数
| |-- index.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 代替;
但是这样的话就不能在同一条运行的命令中使用 && 切割,因为会把命令切割为两个环境,则最终拿不到我们设置的变量;

92
code/VideoAccess-VCMP/web/routes/attachment/index.js

@ -0,0 +1,92 @@
'use strict';
const request = require('superagent');
const parse = require('async-busboy');
const path = require('path')
const fs = require('fs');
const ext = {
project: [".txt", ".dwg", ".doc", ".docx", ".xls", ".xlsx", ".pdf", ".png", ".jpg", ".svg"],
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) {
const getApiRoot = async function (ctx) {
const { apiUrl } = opts;
ctx.status = 200;
ctx.body = { root: apiUrl };
};
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 = { 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.' };
}
}
router.get('/api/root', getApiRoot);
router.post('/_upload/new', upload);
router.delete('/_upload/cleanup', remove);
}
};

20
code/VideoAccess-VCMP/web/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
code/VideoAccess-VCMP/web/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
code/VideoAccess-VCMP/web/typings/node/node.d.ts

File diff suppressed because it is too large

2517
code/VideoAccess-VCMP/web/typings/react/react.d.ts

File diff suppressed because it is too large

30
code/VideoAccess-VCMP/web/vite.config.js

@ -0,0 +1,30 @@
import path from 'path';
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import reactRefresh from '@vitejs/plugin-react-refresh'
// https://vitejs.dev/config/
export default defineConfig({
root: './client/',
plugins: [react({})],
// plugins: [reactRefresh({})],
resolve: {
alias: [
{
find: '$utils', replacement: path.join('/src/utils'),
},
// 针对以 ~/[包名称]开头的,替换为 node_modules/@[包名称]
{
find: /^(~)(?!\/)(.+)/, replacement: path.join('node_modules/$2'),
},
],
},
cors: true,
server: {
hmr: {
protocol: 'ws',
host: 'localhost'
},
middlewareMode: 'html',
}
})

67
code/VideoAccess-VCMP/web/webpack.config.js

@ -0,0 +1,67 @@
const path = require('path');
const webpack = require('webpack');
const 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: [
new webpack.HotModuleReplacementPlugin(),
new BundleAnalyzerPlugin(),
],
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
code/VideoAccess-VCMP/web/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