Compare commits

...

80 Commits

Author SHA1 Message Date
殷伟文 89d485d276 Merge pull request 'dev_trial' (#3) from dev_trial into release_0.0.1 2 years ago
yuan_yi a033e13a2e 设备筛选 2 years ago
yuan_yi ddc013104d 高德服务 2 years ago
yuan_yi c61d69a720 Merge branch 'dev_trial' of https://gitea.free-sun.vip/free-sun/FS-IOT into dev_trial 2 years ago
yuan_yi 79d4f26ec1 摄像头若干接口 2 years ago
deartibers a1c20552b1 Merge branch 'dev_trial' of https://gitea.free-sun.vip/free-sun/FS-IOT into dev_trial 2 years ago
deartibers 38f9bf91da 设备厂家必填 2 years ago
wenlele 70080015d7 表格设置和查询的修改 2 years ago
yuan_yi 64fa1bc28c AXY_API_URL 2 years ago
yuan_yi 1efb405f19 Merge branch 'dev_trial' of https://gitea.free-sun.vip/free-sun/FS-IOT into dev_trial 2 years ago
yuan_yi 974455267e 多平台请求抽象化 2 years ago
deartibers 20daa99a47 添加摄像头弹框 2 years ago
yuan_yi 54737dba51 Merge branch 'dev_trial' of https://gitea.free-sun.vip/free-sun/FS-IOT into dev_trial 2 years ago
yuan_yi 9dc3554c7d 摄像头获取接口 2 years ago
wenlele 99b8aca186 查询功能样式完成 3 years ago
wenlele dc0136a212 设备信息和头部样式 3 years ago
wenlele 70d75b164a 列表的设置选择问题 3 years ago
wenlele 1d6877ce2d Merge branch 'dev_trial' of ssh://gitea.free-sun.vip:2022/free-sun/FS-IOT into dev_trial 3 years ago
wenlele 068ccda661 摄像头列表设置完成 3 years ago
deartibers 47a80141b0 修改less文件位置 3 years ago
deartibers b4f2dc62e3 首页样式修改 3 years ago
yuan_yi 224bc45174 socket 优化 3 years ago
yuan_yi 9e8de3ff47 socket 优化 3 years ago
yuan_yi caa85094bd socket 优化 3 years ago
yuan_yi d1be1ce22d Merge branch 'dev_trial' of https://gitea.free-sun.vip/free-sun/FS-IOT into dev_trial 3 years ago
yuan_yi 0c1b8f8d99 websocket 3 years ago
deartibers 25c88dea6b nvr新增刷新列表 3 years ago
wenlele f1f3a17be3 解决冲突 3 years ago
wenlele 870afbcd47 解决冲突 3 years ago
deartibers ee8881a661 修改nvr 3 years ago
wenlele b9ac39955c 表格功能完成 3 years ago
wenlele c23c764238 表格功能完成 3 years ago
deartibers 981dc894c4 nvr接口调试以及摄像头页面 3 years ago
yuan_yi 37779c29b9 注释以运行 3 years ago
yuan_yi 255466ef39 api -u 3 years ago
yuan_yi 12be6e0ba6 redis精简 3 years ago
yuan_yi 29de0083a4 Merge branch 'dev_trial' of https://gitea.free-sun.vip/free-sun/FS-IOT into dev_trial 3 years ago
yuan_yi 685f65affd 获取nvr的接口 3 years ago
wenlele 2bee6dc9a3 Merge branch 'dev_trial' of ssh://gitea.free-sun.vip:2022/free-sun/FS-IOT into dev_trial 3 years ago
wenlele 7113aaf672 表格设置完成 3 years ago
deartibers a45a0a9a50 Merge branch 'dev_trial' of https://gitea.free-sun.vip/free-sun/FS-IOT into dev_trial 3 years ago
deartibers c3a82d8979 添加摄像头页面 3 years ago
yuan_yi 4168be60d7 删除 AUTH 3 years ago
yuan_yi 8c4cffa8b5 Merge branch 'dev_trial' of https://gitea.free-sun.vip/free-sun/FS-IOT into dev_trial 3 years ago
yuan_yi 068c0a6e50 NVR 增删接口 3 years ago
deartibers bdbd43ef73 删除px 3 years ago
deartibers b1d1b4bfb5 nvr弹出框交互 3 years ago
wenlele 525d99c736 BUG修改 3 years ago
yuan_yi 90b6c11a3f auth 参数配置 3 years ago
yuan_yi 6f23175395 namePresent 3 years ago
yuan_yi 3e898f7a3e 登录登出 3 years ago
yuan_yi 85b64e95c4 Merge branch 'dev_trial' of https://gitea.free-sun.vip/free-sun/FS-IOT into dev_trial 3 years ago
yuan_yi d18dfa9c30 登录登出 3 years ago
deartibers 27594bada8 弹框修改 3 years ago
wenlele 5744fe52b8 表格修改 3 years ago
wenlele 6c712920c8 Merge branch 'dev_trial' of ssh://gitea.free-sun.vip:2022/free-sun/FS-IOT into dev_trial 3 years ago
wenlele b6ed892275 冲突更改 3 years ago
deartibers 1aad3c3bef 修改问题 3 years ago
wenlele 3f5742570b 表格 3 years ago
deartibers 00610e8c2a Merge branch 'dev_trial' of https://gitea.free-sun.vip/free-sun/FS-IOT into dev_trial 3 years ago
deartibers 1ebc1f15ae 添加nvr弹框 3 years ago
yuan_yi f1c3748571 Merge branch 'dev_trial' of https://gitea.free-sun.vip/free-sun/FS-IOT into dev_trial 3 years ago
yuan_yi c494dce09e recycle_banner.mp4 3 years ago
deartibers 72f2e341b4 nvr管理头部弹框 3 years ago
yuan_yi 07f13a617f video banner 3 years ago
yuan_yi d192b64407 camera_banner 3 years ago
yuan_yi 333330d9c3 nvr banner 3 years ago
yuan_yi b122c1e11c Merge branch 'dev_trial' of https://gitea.free-sun.vip/free-sun/FS-IOT into dev_trial 3 years ago
yuan_yi 06648ff786 DB模型 3 years ago
deartibers 33b42ed889 banner视频 3 years ago
wenlele a8f7b63cac 格式 3 years ago
wenlele 29a904dce8 冲突解决 3 years ago
wenlele 911e502bf4 Merge 3 years ago
wenlele 6e92a71354 测试 3 years ago
deartibers 67c41bd4d9 nvr头部 3 years ago
deartibers 15420f0c96 Merge branch 'dev_trial' of https://gitea.free-sun.vip/free-sun/FS-IOT into dev_trial 3 years ago
deartibers 26390a2a81 创建设备仓库文件 3 years ago
yuan_yi 40a022484b bg-img 3 years ago
yuan_yi 05e60fb524 fix utils 3 years ago
yuan_yi 052d73e231 fix utils 3 years ago
  1. 14
      code/VideoAccess-VCMP/api/.vscode/launch.json
  2. 189
      code/VideoAccess-VCMP/api/app/lib/controllers/auth/index.js
  3. 159
      code/VideoAccess-VCMP/api/app/lib/controllers/camera/index.js
  4. 135
      code/VideoAccess-VCMP/api/app/lib/controllers/nvr/index.js
  5. 23
      code/VideoAccess-VCMP/api/app/lib/controllers/vender/index.js
  6. 47
      code/VideoAccess-VCMP/api/app/lib/index.js
  7. 2
      code/VideoAccess-VCMP/api/app/lib/middlewares/api-log.js
  8. 26
      code/VideoAccess-VCMP/api/app/lib/middlewares/authenticator.js
  9. 50
      code/VideoAccess-VCMP/api/app/lib/middlewares/business-rest.js
  10. 40
      code/VideoAccess-VCMP/api/app/lib/models/ax_project.js
  11. 266
      code/VideoAccess-VCMP/api/app/lib/models/camera.js
  12. 34
      code/VideoAccess-VCMP/api/app/lib/models/camera_ability.js
  13. 34
      code/VideoAccess-VCMP/api/app/lib/models/camera_kind.js
  14. 96
      code/VideoAccess-VCMP/api/app/lib/models/nvr.js
  15. 40
      code/VideoAccess-VCMP/api/app/lib/models/vender.js
  16. 32
      code/VideoAccess-VCMP/api/app/lib/routes/auth/index.js
  17. 17
      code/VideoAccess-VCMP/api/app/lib/routes/camera/index.js
  18. 14
      code/VideoAccess-VCMP/api/app/lib/routes/nvr/index.js
  19. 8
      code/VideoAccess-VCMP/api/app/lib/routes/vender/index.js
  20. 66
      code/VideoAccess-VCMP/api/app/lib/service/paasRequest.js
  21. 28
      code/VideoAccess-VCMP/api/app/lib/service/redis.js
  22. 25
      code/VideoAccess-VCMP/api/app/lib/service/socket.js
  23. 171
      code/VideoAccess-VCMP/api/config.js
  24. 4
      code/VideoAccess-VCMP/api/package.json
  25. 8
      code/VideoAccess-VCMP/api/sequelize-automate.config.js
  26. 5
      code/VideoAccess-VCMP/web/.vscode/extensions.json
  27. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/Reset.png
  28. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/backGround.png
  29. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/camera.png
  30. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/cascade.png
  31. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/copy1.png
  32. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/copy2.png
  33. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/formchoose.png
  34. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/header.png
  35. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/ipc.png
  36. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/location.png
  37. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/loginBackground.png
  38. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/loginbg.png
  39. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/logo.png
  40. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/notice.png
  41. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/nvr.png
  42. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/nvr_banner.png
  43. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/password.png
  44. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/projectIcon0.png
  45. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/projectIcon1.png
  46. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/projectIcon2.png
  47. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/projectIcon3.png
  48. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/setup.png
  49. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/sewage_camera1.png
  50. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/sewage_camera2.png
  51. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/store1.png
  52. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/store2.png
  53. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/straightline.png
  54. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/test.png
  55. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/topchoose.png
  56. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/user_login.png
  57. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/username.png
  58. BIN
      code/VideoAccess-VCMP/web/client/assets/images/background/ysy.png
  59. BIN
      code/VideoAccess-VCMP/web/client/assets/video/camera_banner.mp4
  60. BIN
      code/VideoAccess-VCMP/web/client/assets/video/nvr_banner.mp4
  61. BIN
      code/VideoAccess-VCMP/web/client/assets/video/recycle_banner.mp4
  62. 25
      code/VideoAccess-VCMP/web/client/index.html
  63. 3
      code/VideoAccess-VCMP/web/client/src/app.jsx
  64. 9
      code/VideoAccess-VCMP/web/client/src/layout/actions/index.js
  65. 33
      code/VideoAccess-VCMP/web/client/src/layout/actions/webSocket.js
  66. 116
      code/VideoAccess-VCMP/web/client/src/layout/components/header/index.jsx
  67. 1
      code/VideoAccess-VCMP/web/client/src/layout/containers/layout/index.jsx
  68. 5
      code/VideoAccess-VCMP/web/client/src/layout/index.jsx
  69. 8
      code/VideoAccess-VCMP/web/client/src/layout/reducers/ajaxResponse.js
  70. 15
      code/VideoAccess-VCMP/web/client/src/layout/reducers/global.js
  71. 4
      code/VideoAccess-VCMP/web/client/src/layout/reducers/index.js
  72. 21
      code/VideoAccess-VCMP/web/client/src/layout/reducers/webSocket.js
  73. 34
      code/VideoAccess-VCMP/web/client/src/sections/auth/actions/auth.js
  74. 5
      code/VideoAccess-VCMP/web/client/src/sections/auth/actions/index.js
  75. 63
      code/VideoAccess-VCMP/web/client/src/sections/auth/containers/login.jsx
  76. 4
      code/VideoAccess-VCMP/web/client/src/sections/auth/reducers/auth.js
  77. 7
      code/VideoAccess-VCMP/web/client/src/sections/auth/style.less
  78. 7
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/actions/index.js
  79. 64
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/actions/nvr.js
  80. 262
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/cameraModal.jsx
  81. 9
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/cameraModal.less
  82. 192
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/cascadeCamera.jsx
  83. 262
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/fluoriteCamera.jsx
  84. 224
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/ipcCamera.jsx
  85. 182
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/nvrCamera.jsx
  86. 198
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/nvrModal.jsx
  87. 196
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/setup.jsx
  88. 468
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/sideSheet.jsx
  89. 542
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/containers/camera.jsx
  90. 5
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/containers/index.js
  91. 495
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/containers/nvr.jsx
  92. 15
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/index.js
  93. 16
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/nav-item.jsx
  94. 5
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/reducers/index.js
  95. 23
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/routes.js
  96. 0
      code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/style.less
  97. 19
      code/VideoAccess-VCMP/web/client/src/sections/example/containers/example.jsx
  98. 11
      code/VideoAccess-VCMP/web/client/src/utils/index.js
  99. 8
      code/VideoAccess-VCMP/web/client/src/utils/webapi.js
  100. 8
      code/VideoAccess-VCMP/web/config.js

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

@ -13,12 +13,14 @@
"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"
"-p 4000",
"-f http://localhost:4000",
"-g postgres://postgres:123@10.8.30.32:5432/video_access",
"--redisHost 127.0.0.1",
"--redisPort 6379",
"--axyApiUrl http://127.0.0.1:4100",
"--godUrl https://restapi.amap.com/v3",
"--godKey 21c2d970e1646bb9a795900dd00093ce"
]
},
{

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

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

159
code/VideoAccess-VCMP/api/app/lib/controllers/camera/index.js

@ -0,0 +1,159 @@
'use strict';
async function getCameraProject (ctx, next) {
try {
const models = ctx.fs.dc.models;
const { limit, page, orderBy, orderDirection, keyword, abilityId, type, venderId } = ctx.query
const { userId, token } = ctx.fs.api
let findOption = {
attributes: { exclude: ['delete', 'recycleTime',] },
where: {
createUserId: userId,
recycleTime: null,
delete: false
},
order: [
[orderBy || 'id', orderDirection || 'DESC']
],
include: [{
model: models.CameraAbility
}, {
model: models.CameraKind
}]
}
if (limit) {
findOption.limit = limit
}
if (page && limit) {
findOption.offset = page * limit
}
if (keyword) {
findOption.where.$or = [{
name: { $like: `%${keyword}%` }
}, {
serialNo: { $like: `%${keyword}%` }
}]
}
if (type) {
findOption.where.type = type
}
if (abilityId) {
findOption.where.abilityId = abilityId
}
if (venderId) {
findOption.where.venderId = venderId
}
const cameraRes = await models.Camera.findAll(findOption)
const total = await models.Camera.count({
where: findOption.where
})
// 查在安心云绑定的数据
const cameraIds = cameraRes.map(c => {
return c.dataValues.id
})
const axbindCameraRes = await ctx.app.fs.axyRequest.get('vcmp/camera/project', { query: { token, cameraId: cameraIds.join(',') } })
for (let { dataValues: camera } of cameraRes) {
const corBindCamera = axbindCameraRes.find(b => b.cameraId == camera.id)
if (corBindCamera) {
camera.station = corBindCamera.stations
} else {
camera.station = []
}
}
ctx.status = 200;
ctx.body = {
total: total,
data: cameraRes
}
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {}
}
}
async function getCamera (ctx) {
try {
const { models } = ctx.fs.dc;
const { cameraId } = ctx.query
const cameraRes = await models.Camera.findAll({
attributes: { exclude: ['delete', 'recycleTime',] },
where: {
id: { $in: cameraId.split(',') }
},
include: [{
model: models.CameraAbility
}, {
model: models.CameraKind
}, {
model: models.Vender
}]
})
ctx.status = 200;
ctx.body = cameraRes
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {}
}
}
async function banned (ctx) {
try {
const { models } = ctx.fs.dc;
const data = ctx.request.body;
// 向视频服务发送通知
// 库记录
await models.Camera.update({
forbidden: data.forbidden
}, {
where: {
id: data.cameraId
}
})
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {}
}
}
async function del (ctx) {
try {
const { models } = ctx.fs.dc;
const { cameraId } = ctx.query
const { token } = ctx.fs.api
await models.cameraId.destroy({
where: {
id: cameraId
}
})
await ctx.app.fs.axyRequest.delete('vcmp/camera/project', { query: { token, cameraId: cameraId.join(',') } })
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {}
}
}
module.exports = {
getCameraProject,
getCamera,
banned,
del,
};

135
code/VideoAccess-VCMP/api/app/lib/controllers/nvr/index.js

@ -0,0 +1,135 @@
'use strict';
const moment = require('moment')
async function edit (ctx, next) {
const transaction = await ctx.fs.dc.orm.transaction();
try {
const models = ctx.fs.dc.models;
const { userId } = ctx.fs.api
const data = ctx.request.body;
// 或取其他服务信息
const nvrData = {
channelCount: 8,
port: 8080,
}
if (data.id) {
// 修改
const storageData = Object.assign({}, data, nvrData)
await models.Nvr.update(storageData, {
where: {
id: data.id
},
transaction
})
} else {
// 添加
const storageData = Object.assign({}, data, nvrData, {
createTime: moment().format(),
createUserId: userId,
delete: false,
})
await models.Nvr.create(storageData, { transaction })
}
await transaction.commit();
ctx.status = 204;
} catch (error) {
await transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {}
}
}
async function get (ctx) {
const models = ctx.fs.dc.models;
try {
const { userId, token } = ctx.fs.api
const { limit, page, orderBy, orderDirection, keyword, venderId } = ctx.query
let findOption = {
attributes: { exclude: ['delete'] },
where: {
createUserId: userId,
delete: false,
},
order: [
[orderBy || 'id', orderDirection || 'DESC']
]
}
if (limit) {
findOption.limit = limit
}
if (page && limit) {
findOption.offset = page * limit
}
if (keyword) {
findOption.where.name = { $like: `%${keyword}%` }
}
if (venderId) {
findOption.where.venderId = venderId
}
const res = await models.Nvr.findAll(findOption)
const total = await models.Nvr.count({
where: findOption.where
})
ctx.status = 200;
ctx.body = {
total: total,
data: res
}
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {}
}
}
async function del (ctx, next) {
const transaction = await ctx.fs.dc.orm.transaction();
try {
const models = ctx.fs.dc.models;
const { userId, token } = ctx.fs.api
const { nvrId } = ctx.params
await models.Nvr.destroy({
where: {
id: nvrId
},
transaction
})
const cameraRes = await models.Camera.findAll({
where: {
nvrId
}
})
const cameraIds = cameraRes.map(c => c.id)
await models.Camera.destroy({
where: {
nvrId,
}
})
await ctx.app.fs.axyRequest.delete('vcmp/camera/project', { query: { token, cameraId: cameraIds.join(',') } })
await transaction.commit();
ctx.status = 204;
} catch (error) {
await transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {}
}
}
module.exports = {
edit,
get,
del,
};

23
code/VideoAccess-VCMP/api/app/lib/controllers/vender/index.js

@ -0,0 +1,23 @@
'use strict';
async function get (ctx) {
const models = ctx.fs.dc.models;
try {
const res = await models.Vender.findAll({
order: [
['id', 'ASC']
]
})
ctx.status = 200;
ctx.body = res
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {}
}
}
module.exports = {
get,
};

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

@ -1,36 +1,39 @@
'use strict';
const routes = require('./routes');
const redisConnect = require('./service/redis')
const socketConect = require('./service/socket')
const paasRequest = require('./service/paasRequest');
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.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 || {};
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));
// 顺序固定 ↓
redisConnect(app, opts)
socketConect(app, opts)
router = routes(app, router, opts);
// 实例其他平台请求方法
paasRequest(app, opts)
router.use(authenticator(app, 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);
require('./models/camera_ability')(dc);
require('./models/camera_kind')(dc);
require('./models/camera')(dc);
require('./models/nvr')(dc);
require('./models/vender')(dc);
require('./models/ax_project')(dc);
};

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

@ -71,7 +71,7 @@ function factory(app, opts) {
partition: 0
}];
await sendToEsAsync(producer, payloads);
// await sendToEsAsync(producer, payloads);
} catch (e) {
ctx.fs.logger.error(`日志记录失败: ${e}`);

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

@ -13,13 +13,13 @@ class ExcludesUrls {
this.reload(opts);
}
sanitizePath(path) {
sanitizePath (path) {
if (!path) return '/';
const p = '/' + path.replace(/^\/+/i, '').replace(/\/+$/, '').replace(/\/{2,}/, '/');
return p;
}
reload(opts) {
reload (opts) {
// load all url
if (!this.allUrls) {
this.allUrls = opts;
@ -37,7 +37,7 @@ class ExcludesUrls {
}
}
isExcluded(path, method) {
isExcluded (path, method) {
return this.allUrls.some(function (url) {
return !url.auth
&& url.pregexp.test(path)
@ -58,9 +58,7 @@ let isPathExcluded = function (opts, path, method) {
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);
@ -72,14 +70,10 @@ let authorizeToken = async function (ctx, token) {
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()) {
const expired = await ctx.redis.hget(token, 'expired');
if (expired && moment().valueOf() <= moment(expired).valueOf()) {
const userInfo = JSON.parse(await ctx.redis.hget(token, 'userInfo'));
rslt = {
'authorized': userInfo.authorized,
'resources': (userInfo || {}).resources || [],
@ -100,7 +94,6 @@ 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);
@ -112,13 +105,14 @@ let isResourceAvailable = function (resources, options) {
return !authCode || (resources || []).some(code => code === authCode);
};
function factory(app, opts) {
return async function auth(ctx, next) {
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;
ctx.redisTools = app.redisTools;
let error = null;
if (path) {
if (!isPathExcluded(opts, path, method)) {

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

@ -1,50 +0,0 @@
'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;

40
code/VideoAccess-VCMP/api/app/lib/models/ax_project.js

@ -0,0 +1,40 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const AxProject = sequelize.define("axProject", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: false,
unique: "ax_project_id_uindex"
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "name",
autoIncrement: false
}
}, {
tableName: "ax_project",
comment: "",
indexes: []
});
const Nvr = dc.models.Nvr;
// Nvr.belongsTo(User, { foreignKey: 'userId', targetKey: 'id' });
// User.hasMany(Nvr, { foreignKey: 'userId', sourceKey: 'id' });
dc.models.AxProject = AxProject;
return AxProject;
};

266
code/VideoAccess-VCMP/api/app/lib/models/camera.js

@ -0,0 +1,266 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const Camera = sequelize.define("camera", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "camera_id_uindex"
},
type: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "设备类型:yingshi - 萤石;nvr - NVR摄像头;ipc - IPC 网络摄像头;cascade - 级联摄像头",
primaryKey: false,
field: "type",
autoIncrement: false
},
name: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "设备名称/安装位置",
primaryKey: false,
field: "name",
autoIncrement: false
},
channelName: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "通道名称",
primaryKey: false,
field: "channel_name",
autoIncrement: false
},
externalDomain: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "外域名称",
primaryKey: false,
field: "external_domain",
autoIncrement: false
},
rtmp: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "rtmp",
autoIncrement: false
},
serialNo: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "设备编号",
primaryKey: false,
field: "serial_no",
autoIncrement: false
},
cloudControl: {
type: DataTypes.BOOLEAN,
allowNull: true,
defaultValue: null,
comment: "云台控制",
primaryKey: false,
field: "cloud_control",
autoIncrement: false
},
highDefinition: {
type: DataTypes.BOOLEAN,
allowNull: true,
defaultValue: null,
comment: "高清支持",
primaryKey: false,
field: "high_definition",
autoIncrement: false
},
voice: {
type: DataTypes.BOOLEAN,
allowNull: true,
defaultValue: null,
comment: "语音对讲支持",
primaryKey: false,
field: "voice",
autoIncrement: false
},
memoryCard: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "内存卡容量",
primaryKey: false,
field: "memory_card",
autoIncrement: false
},
venderId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: "设备厂商id",
primaryKey: false,
field: "vender_id",
autoIncrement: false,
references: {
key: "id",
model: "vender"
}
},
cascadeType: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "级联方式:up - 上级联;down - 下级联",
primaryKey: false,
field: "cascade_type",
autoIncrement: false
},
sip: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "sip",
autoIncrement: false
},
longitude: {
type: DataTypes.DOUBLE,
allowNull: true,
defaultValue: null,
comment: "经度",
primaryKey: false,
field: "longitude",
autoIncrement: false
},
latitude: {
type: DataTypes.DOUBLE,
allowNull: true,
defaultValue: null,
comment: "维度",
primaryKey: false,
field: "latitude",
autoIncrement: false
},
forbidden: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: null,
comment: "是否禁用",
primaryKey: false,
field: "forbidden",
autoIncrement: false
},
createTime: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "create_time",
autoIncrement: false
},
recycleTime: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: null,
comment: "放入回收站时间",
primaryKey: false,
field: "recycle_time",
autoIncrement: false
},
delete: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: null,
comment: "是否彻底删除",
primaryKey: false,
field: "delete",
autoIncrement: false
},
createUserId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "create_user_id",
autoIncrement: false
},
nvrId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "nvr_id",
autoIncrement: false,
references: {
key: "id",
model: "nvr"
}
},
model: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "型号",
primaryKey: false,
field: "model",
autoIncrement: false
},
kindId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "kind_id",
autoIncrement: false,
references: {
key: "id",
model: "cameraKind"
}
},
abilityId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "ability_id",
autoIncrement: false,
references: {
key: "id",
model: "cameraAbility"
}
}
}, {
tableName: "camera",
comment: "",
indexes: []
});
dc.models.Camera = Camera;
const CameraKind = dc.models.CameraKind;
Camera.belongsTo(CameraKind, { foreignKey: 'kindId', targetKey: 'id' });
CameraKind.hasMany(Camera, { foreignKey: 'kindId', sourceKey: 'id' });
const CameraAbility = dc.models.CameraAbility;
Camera.belongsTo(CameraAbility, { foreignKey: 'abilityId', targetKey: 'id' });
CameraAbility.hasMany(Camera, { foreignKey: 'abilityId', sourceKey: 'id' });
return Camera;
};

34
code/VideoAccess-VCMP/api/app/lib/models/camera_ability.js

@ -0,0 +1,34 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const CameraAbility = sequelize.define("cameraAbility", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: false,
unique: "camera_ability_id_uindex"
},
ability: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "ability",
autoIncrement: false
}
}, {
tableName: "camera_ability",
comment: "",
indexes: []
});
dc.models.CameraAbility = CameraAbility;
return CameraAbility;
};

34
code/VideoAccess-VCMP/api/app/lib/models/camera_kind.js

@ -0,0 +1,34 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const CameraKind = sequelize.define("cameraKind", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: false,
unique: "camera_kind_id_uindex"
},
kind: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "kind",
autoIncrement: false
}
}, {
tableName: "camera_kind",
comment: "",
indexes: []
});
dc.models.CameraKind = CameraKind;
return CameraKind;
};

96
code/VideoAccess-VCMP/api/app/lib/models/user.js → code/VideoAccess-VCMP/api/app/lib/models/nvr.js

@ -4,7 +4,7 @@
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const User = sequelize.define("user", {
const Nvr = sequelize.define("nvr", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
@ -13,7 +13,7 @@ module.exports = dc => {
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "user_id_uindex"
unique: "nvr_id_uindex"
},
name: {
type: DataTypes.STRING,
@ -24,49 +24,80 @@ module.exports = dc => {
field: "name",
autoIncrement: false
},
username: {
venderId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: "设备厂家id",
primaryKey: false,
field: "vender_id",
autoIncrement: false,
references: {
key: "id",
model: "vender"
}
},
serialNo: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "用户名 账号",
comment: "设备编号",
primaryKey: false,
field: "username",
field: "serial_no",
autoIncrement: false
},
password: {
regionCode: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "行政区码",
primaryKey: false,
field: "region_code",
autoIncrement: false
},
longitude: {
type: DataTypes.DOUBLE,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "password",
field: "longitude",
autoIncrement: false
},
departmentId: {
type: DataTypes.INTEGER,
latitude: {
type: DataTypes.DOUBLE,
allowNull: false,
defaultValue: null,
comment: "部门id",
comment: null,
primaryKey: false,
field: "department_id",
field: "latitude",
autoIncrement: false
},
email: {
type: DataTypes.STRING,
createTime: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: sequelize.fn('now'),
comment: "创建时间",
primaryKey: false,
field: "create_time",
autoIncrement: false
},
channelCount: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: null,
comment: "通道数",
primaryKey: false,
field: "email",
field: "channel_count",
autoIncrement: false
},
enable: {
type: DataTypes.BOOLEAN,
allowNull: false,
port: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "启用状态",
comment: "端口",
primaryKey: false,
field: "enable",
field: "port",
autoIncrement: false
},
delete: {
@ -78,31 +109,20 @@ module.exports = dc => {
field: "delete",
autoIncrement: false
},
phone: {
type: DataTypes.STRING,
createUserId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "手机号(小程序使用手机号登录)",
primaryKey: false,
field: "phone",
autoIncrement: false
},
post: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "职位",
comment: null,
primaryKey: false,
field: "post",
field: "create_user_id",
autoIncrement: false
}
}, {
tableName: "user",
tableName: "nvr",
comment: "",
indexes: []
});
dc.models.User = User;
return User;
dc.models.Nvr = Nvr;
return Nvr;
};

40
code/VideoAccess-VCMP/api/app/lib/models/vender.js

@ -0,0 +1,40 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const Vender = sequelize.define("vender", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "vender_id_uindex"
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "name",
autoIncrement: false
}
}, {
tableName: "vender",
comment: "",
indexes: []
});
const Camera = dc.models.Camera;
Camera.belongsTo(Vender, { foreignKey: 'venderId', targetKey: 'id' });
Vender.hasMany(Camera, { foreignKey: 'venderId', sourceKey: 'id' });
dc.models.Vender = Vender;
return Vender;
};

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

@ -1,32 +0,0 @@
'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/camera/index.js

@ -0,0 +1,17 @@
'use strict';
const camera = require('../../controllers/camera');
module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/camera/project'] = { content: '获取摄像头列表及项目绑定信息', visible: false };
router.get('/camera/project', camera.getCameraProject);
app.fs.api.logAttr['GET/camera'] = { content: '获取摄像头信息', visible: false };
router.get('/camera', camera.getCamera);
app.fs.api.logAttr['PUT/camera/banned'] = { content: '禁用摄像头', visible: false };
router.put('/camera/banned', camera.banned);
app.fs.api.logAttr['DEL/camera'] = { content: '删除摄像头', visible: false };
router.delete('/camera', camera.del);
};

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

@ -0,0 +1,14 @@
'use strict';
const nvr = require('../../controllers/nvr');
module.exports = function (app, router, opts) {
app.fs.api.logAttr['POST/nvr'] = { content: '添加/修改nvr', visible: false };
router.post('/nvr', nvr.edit);
app.fs.api.logAttr['GET/nvr'] = { content: '获取nvr', visible: false };
router.get('/nvr', nvr.get);
app.fs.api.logAttr['DEL/nvr'] = { content: '删除nvr', visible: false };
router.del('/nvr/:nvrId', nvr.del);
};

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

@ -0,0 +1,8 @@
'use strict';
const vender = require('../../controllers/vender');
module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/vender'] = { content: '获取设备厂商', visible: false };
router.get('/vender', vender.get);
};

66
code/VideoAccess-VCMP/api/app/lib/service/paasRequest.js

@ -0,0 +1,66 @@
'use strict';
const request = require('superagent')
class paasRequest {
constructor(root, { query = {} } = {}) {
this.root = root;
this.query = query
}
#buildUrl = (url) => {
return `${this.root}/${url}`;
}
#resultHandler = (resolve, reject) => {
return (err, res) => {
if (err) {
reject(err);
} else {
resolve(res.body);
}
};
}
get = (url, { query = {}, header = {} } = {}) => {
return new Promise((resolve, reject) => {
request.get(this.#buildUrl(url)).set(header).query(Object.assign(query, this.query)).end(this.#resultHandler(resolve, reject));
})
}
post = (url, { data = {}, query = {}, header = {} } = {}) => {
return new Promise((resolve, reject) => {
request.post(this.#buildUrl(url)).set(header).query(Object.assign(query, this.query)).send(data).end(this.#resultHandler(resolve, reject));
})
}
put = (url, { data = {}, header = {}, query = {}, } = {}) => {
return new Promise((resolve, reject) => {
request.put(this.#buildUrl(url)).set(header).query(Object.assign(query, this.query)).send(data).end(this.#resultHandler(resolve, reject));
})
}
delete = (url, { header = {}, query = {} } = {}) => {
return new Promise((resolve, reject) => {
request.delete(this.#buildUrl(url)).set(header).query(Object.assign(query, this.query)).end(this.#resultHandler(resolve, reject));
})
}
}
function factory (app, opts) {
if (opts.pssaRequest) {
try {
for (let r of opts.pssaRequest) {
if (r.name && r.root) {
app.fs[r.name] = new paasRequest(r.root, { ...(r.params || {}) })
} else {
throw 'opts.pssaRequest 参数错误!'
}
}
} catch (error) {
console.error(error)
process.exit(-1);
}
}
}
module.exports = factory;

28
code/VideoAccess-VCMP/api/app/lib/service/redis.js

@ -0,0 +1,28 @@
'use strict';
// https://github.com/luin/ioredis
const redis = require("ioredis")
module.exports = async function factory (app, opts) {
let client = new redis(opts.redis.port, opts.redis.host);
client.on("error", function (err) {
app.fs.logger.error('info', '[FS-AUTH-REDIS]', 'redis connect error.');
console.error("Error :", err);
process.exit(-1);
});
client.on('connect', function () {
console.log(`redis connect success ${opts.redis.host + ':' + opts.redis.port}`);
})
// 自定义方法
async function hdelall (key) {
const obj = await client.hgetall(key);
await client.hdel(key, Object.keys(obj))
}
app.redis = client
app.redisTools = {
hdelall,
}
}

25
code/VideoAccess-VCMP/api/app/lib/service/socket.js

@ -0,0 +1,25 @@
'use strict';
module.exports = async function factory (app, opts) {
app.socket.on('connection', async (socket) => {
console.info('WEB_SOCKET ' + socket.handshake.query.token + ' 已连接:' + socket.id);
socket.on('disconnecting', async (reason) => {
console.info('WEB_SOCKET ' + socket.handshake.query.token + ' 已断开连接:' + reason);
})
})
// 使用测试 保持链接
// setInterval(async () => {
// // const { connected } = app.socket.sockets
// // const roomId = 'ROOM_' + Math.random()
// // if (connected) {
// // for (let c in connected) {
// // connected[c].join(roomId)
// // }
// // app.socket.to(roomId).emit('TEST', { someProperty: `【星域 ROOM:${roomId}】呼叫自然选择号!!!`, })
// // }
// app.socket.emit('TEST', { someProperty: '【广播】呼叫青铜时代号!!!', })
// }, 1000)
}

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

@ -11,93 +11,124 @@ const dev = process.env.NODE_ENV == 'development';
args.option(['p', 'port'], '启动端口');
args.option(['g', 'pg'], 'postgre服务URL');
args.option(['f', 'fileHost'], '文件中心本地化存储: WebApi 服务器地址(必填), 该服务器提供文件上传Web服务');
args.option('redisHost', 'redisHost');
args.option('redisPort', 'redisPort');
args.option('redisPswd', 'redisPassword');
args.option('axyApiUrl', 'axyApiUrl'); // 安心云 api
args.option('godUrl', '高德地图API请求地址');
args.option('godKey', '高德地图API key');
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 IOTA_REDIS_SERVER_HOST = process.env.IOTA_REDIS_SERVER_HOST || flags.redisHost || "localhost";//redis IP
const IOTA_REDIS_SERVER_PORT = process.env.IOTA_REDIS_SERVER_PORT || flags.redisPort || "6379";//redis 端口
const IOTA_REDIS_SERVER_PWD = process.env.IOTA_REDIS_SERVER_PWD || flags.redisPswd || "";//redis 密码
const AXY_API_URL = process.env.AXY_API_URL || flags.axyApiUrl;
const GOD_URL = process.env.GOD_URL || flags.godUrl || 'https://restapi.amap.com/v3';
const GOD_KEY = process.env.GOD_KEY || flags.godKey;
if (!IOT_VIDEO_ACCESS_DB || !IOTA_REDIS_SERVER_HOST || !IOTA_REDIS_SERVER_PORT || !GOD_KEY) {
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
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',
},
define: {
freezeTableName: true, // 固定表名
timestamps: false // 不含列 "createAt"/"updateAt"/"DeleteAt"
maxSize: 104857600, // 100M
}
}, {
entry: require('./app').entry,
opts: {
exclude: [], // 不做认证的路由,也可以使用 exclude: ["*"] 跳过所有路由
redis: {
host: IOTA_REDIS_SERVER_HOST,
port: IOTA_REDIS_SERVER_PORT,
pwd: IOTA_REDIS_SERVER_PWD
},
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
}
pssaRequest: [{ // name 会作为一个 request 出现在 ctx.app.fs
name: 'axyRequest',
root: AXY_API_URL
}, {
name: 'godRequest',
root: GOD_URL,
params: {
query: {
key: GOD_KEY
}
}
},]
}
}
],
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
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;
// 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;

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

@ -5,7 +5,7 @@
"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": "set NODE_ENV=development&&node server -p 4000 -g postgres://postgres:123@10.8.30.32:5432/video_access -f http://localhost:4000",
"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"
},
@ -18,7 +18,7 @@
"crypto-js": "^4.0.0",
"file-saver": "^2.0.2",
"fs-web-server-scaffold": "^2.0.2",
"ioredis": "^4.19.4",
"ioredis": "^5.0.4",
"koa-convert": "^1.2.0",
"koa-proxy": "^0.9.0",
"moment": "^2.24.0",

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

@ -1,7 +1,7 @@
module.exports = {
// 数据库配置 与 sequelize 相同
dbOptions: {
database: 'yinjiguanli',
database: 'video_access',
username: 'postgres',
password: '123',
dialect: 'postgres',
@ -25,9 +25,9 @@ module.exports = {
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,则忽略改属性
emptyDir: true, // !!! 谨慎操作 生成 models 之前是否清空 `dir` 以及 `typesDir`
tables: null, // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性
skipTables: [], // 指定跳过哪些表的 models,如 ['user'];如果为 null,则忽略改属性
tsNoCheck: false, // 是否添加 `@ts-nocheck` 注释到 models 文件中
ignorePrefix: [], // 生成的模型名称忽略的前缀,因为 项目中有以下表名是以 t_ 开头的,在实际模型中不需要, 可以添加多个 [ 't_data_', 't_',] ,长度较长的 前缀放前面
attrLength: false, // 在生成模型的字段中 是否生成 如 var(128)这种格式,公司一般使用 String ,则配置为 false

5
code/VideoAccess-VCMP/web/.vscode/extensions.json

@ -0,0 +1,5 @@
{
"recommendations": [
"formulahendry.code-runner"
]
}

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/Reset.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/backGround.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/camera.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/cascade.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/copy1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/copy2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/formchoose.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/header.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/ipc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/location.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/loginBackground.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/loginbg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/notice.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/nvr.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/nvr_banner.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/password.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/projectIcon0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/projectIcon1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/projectIcon2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/projectIcon3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/setup.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/sewage_camera1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/sewage_camera2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/store1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/store2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/straightline.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/test.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/topchoose.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/user_login.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/username.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 B

BIN
code/VideoAccess-VCMP/web/client/assets/images/background/ysy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
code/VideoAccess-VCMP/web/client/assets/video/camera_banner.mp4

Binary file not shown.

BIN
code/VideoAccess-VCMP/web/client/assets/video/nvr_banner.mp4

Binary file not shown.

BIN
code/VideoAccess-VCMP/web/client/assets/video/recycle_banner.mp4

Binary file not shown.

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

@ -26,18 +26,19 @@
<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);
}
};
// 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>

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

@ -4,6 +4,7 @@ import React, { useEffect } from 'react';
import Layout from './layout';
import Auth from './sections/auth';
import Example from './sections/example';
import EquipmentWarehouse from './sections/equipmentWarehouse';
const App = props => {
const { projectName } = props
@ -15,7 +16,7 @@ const App = props => {
return (
<Layout
title={projectName}
sections={[Auth, Example]}
sections={[Auth, EquipmentWarehouse]}
/>
)
}

9
code/VideoAccess-VCMP/web/client/src/layout/actions/index.js

@ -0,0 +1,9 @@
'use strict';
import * as global from './global'
import * as socket from './webSocket';
export default {
...global,
...socket,
};

33
code/VideoAccess-VCMP/web/client/src/layout/actions/webSocket.js

@ -0,0 +1,33 @@
'use strict';
import io from 'socket.io-client';
export const INIT_WEB_SOCKET = 'INIT_WEB_SOCKET'
export function initWebSocket ({ ioUrl, token }) {
if (!ioUrl) {
ioUrl = localStorage.getItem('apiRoot')
}
if (!token) {
const user = sessionStorage.getItem('user')
if (user) {
token = JSON.parse(user).token
}
}
if (!ioUrl || !token) {
return {
type: '',
}
}
return dispatch => {
const socket = io(ioUrl, {
query: {
token: token
},
});
dispatch({
type: INIT_WEB_SOCKET,
payload: {
socket: socket
}
})
}
}

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

@ -1,43 +1,85 @@
'use strict';
import React from 'react';
import { connect } from 'react-redux';
import { Nav } from '@douyinfe/semi-ui';
"use strict";
import React from "react";
import { connect } from "react-redux";
import { Nav, Avatar, Dropdown } from "@douyinfe/semi-ui";
const Header = props => {
const { dispatch, history, user, actions } = props
const Header = (props) => {
const { dispatch, history, user, actions, socket } = 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>
)
return (
<>
<Nav
mode={"horizontal"}
onClick={({ itemKey }) => {
if (itemKey == "logout") {
dispatch(actions.auth.logout(user));
if (socket) {
socket.disconnect();
}
history.push(`/signin`);
}
}}
style={{
height: 60,
minWidth: 520,
background: "url(/assets/images/background/header.png)",
backgroundSize: "100% 100%",
color: "white",
}}
header={{
logo: (
<img
src="/assets/images/background/logo.png"
style={{ display: "inline-block", width: 280, height: 52}}
/>
),
text: "",
}}
footer={
<Nav.Sub
itemKey={"user"}
text={
<div
style={{
marginLeft: 20,
display: "inline-block",
color: "white",
}}
>
<img
src="/assets/images/background/notice.png"
style={{
display: "inline-block",
width: 18,
height: 18,
position: "relative",
top: 6,
left: -10,
}}
/>
<Avatar size="small" color="light-blue" style={{ margin: 4 }}>
<img src="/assets/images/avatar/6.png" />
</Avatar>
{user && user.namePresent}
</div>
}
>
<Nav.Item itemKey={"logout"} text={"退出"} />
</Nav.Sub>
}
/>
</>
);
};
function mapStateToProps (state) {
const { global, auth } = state;
return {
actions: global.actions,
user: auth.user
};
function mapStateToProps(state) {
const { global, auth, webSocket } = state;
return {
actions: global.actions,
user: auth.user,
socket: webSocket.socket,
};
}
export default connect(mapStateToProps)(Header);
export default connect(mapStateToProps)(Header);

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

@ -94,6 +94,7 @@ const LayoutContainer = props => {
<Layout.Content>
<div style={{
margin: '12px 12px 0px',
background: "#F6FAFF",
}}>
<div id="page-content" style={{
height: clientHeight - 12,

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

@ -8,14 +8,14 @@ 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 layoutActions from './actions';
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 { initLayout, initApiRoot, resize, initWebSocket } = layoutActions;
const Root = props => {
const { sections, title, copyright } = props;
@ -124,6 +124,7 @@ const Root = props => {
store.dispatch(initLayout(title, copyright, sections, actions));
store.dispatch(resize(document.body.clientHeight, document.body.clientWidth));
store.dispatch(actions.auth.initAuth());
store.dispatch(initWebSocket({}))
store.dispatch(initApiRoot())
const combineRoutes = flatRoutes(innerRoutes);

8
code/VideoAccess-VCMP/web/client/src/layout/reducers/ajaxResponse.js

@ -15,14 +15,14 @@ import Immutable from 'immutable';
* 判断action中是否有error字段如果有则修改store中msg.error
* 在layout中根据msg的值呈现提示信息
*/
export default function ajaxResponse(state = initState, action) {
export default function ajaxResponse (state = initState, action) {
if (action.done) {
return Immutable.fromJS(state).set('msg', {done: action.done}).toJS();
return Immutable.fromJS(state).set('msg', { done: action.done }).toJS();
}
if (action.error) {
return Immutable.fromJS(state).set('msg', {error: action.error}).toJS();
return Immutable.fromJS(state).set('msg', { error: action.error }).toJS();
}
return {msg: null};
return { msg: null };
};

15
code/VideoAccess-VCMP/web/client/src/layout/reducers/global.js

@ -1,6 +1,6 @@
'use strict';
import Immutable from 'immutable';
import { INIT_LAYOUT, RESIZE } from '../actions/global';
import { INIT_LAYOUT, RESIZE, INIT_API_ROOT } from '../actions/global';
function global (state = {
title: '',
@ -9,7 +9,8 @@ function global (state = {
actions: {},
plugins: {},
clientHeight: 768,
clientWidth: 1024
clientWidth: 1024,
apiRoot: '',
}, action) {
const payload = action.payload;
switch (action.type) {
@ -19,15 +20,17 @@ function global (state = {
clientWidth: payload.clientWidth
}).toJS();
case INIT_LAYOUT:
return {
return Immutable.fromJS(state).merge({
title: payload.title,
copyright: payload.copyright,
sections: payload.sections,
actions: payload.actions,
plugins: payload.plugins,
clientHeight: state.clientHeight,
detailsComponent: null
};
}).toJS();
case INIT_API_ROOT:
return Immutable.fromJS(state).merge({
apiRoot: payload.apiRoot,
}).toJS();
default:
return state;
}

4
code/VideoAccess-VCMP/web/client/src/layout/reducers/index.js

@ -7,9 +7,11 @@
'use strict';
import global from './global';
import webSocket from './webSocket'
import ajaxResponse from './ajaxResponse';
export default {
global,
ajaxResponse
webSocket,
ajaxResponse,
};

21
code/VideoAccess-VCMP/web/client/src/layout/reducers/webSocket.js

@ -0,0 +1,21 @@
'use strict';
import * as actionTypes from '../actions/webSocket';
import Immutable from 'immutable';
const initState = {
socket: null,
};
function webSocket (state = initState, action) {
const payload = action.payload;
switch (action.type) {
case actionTypes.INIT_WEB_SOCKET:
return Immutable.fromJS(state).merge({
socket: payload.socket,
}).toJS();
default:
return state;
}
}
export default webSocket;

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

@ -1,7 +1,6 @@
'use strict';
import { ApiTable } from '$utils'
import { Request } from '@peace/utils'
import { ApiTable, AuthRequest } from '$utils'
export const INIT_AUTH = 'INIT_AUTH';
export function initAuth () {
@ -29,27 +28,26 @@ export function login (username, password) {
return Promise.resolve();
}
return dispatch({
type: LOGIN_SUCCESS,
payload: {
user: {
authorized: true,
displayName: 'TEST'
}
},
});
// return dispatch({
// type: LOGIN_SUCCESS,
// payload: {
// user: {
// authorized: true,
// namePresent: 'TEST'
// }
// },
// });
const url = ApiTable.login;
return Request.post(url, { username, password, p: '456' })
return AuthRequest.post(ApiTable.login, { username, password })
.then(user => {
sessionStorage.setItem('user', JSON.stringify(user));
dispatch({
return dispatch({
type: LOGIN_SUCCESS,
payload: { user: user },
});
}, error => {
let { body } = error.response;
dispatch({
return dispatch({
type: LOGIN_ERROR,
payload: {
error: body && body.message ? body.message : '登录失败'
@ -61,11 +59,9 @@ export function login (username, password) {
export const LOGOUT = 'LOGOUT';
export function logout (user) {
const token = user.token;
const url = ApiTable.logout;
sessionStorage.removeItem('user');
Request.put(url, {
token: token
AuthRequest.put(ApiTable.logout, {
token: user.token
});
return {
type: LOGOUT

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

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

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

@ -3,10 +3,12 @@ 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';
import { login, LOGIN_SUCCESS } from '../actions/auth';
import { IconLock,IconUser } from '@douyinfe/semi-icons';
import '../style.less'
const Login = props => {
const { dispatch, user, error, isRequesting } = props
const { dispatch, user, error, actions, apiRoot, isRequesting } = props
const form = useRef();
useEffect(() => {
@ -18,32 +20,61 @@ const Login = props => {
useEffect(() => {
if (user && user.authorized) {
dispatch(push('/example/e1'));
dispatch(push('/equipmentWarehouse/nvr'));
}
}, [user])
return (
<div style={{
height: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundImage:"url('/assets/images/background/loginBackground.png')",
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
position: 'relative',
}}>
<div style={{
width: 400,
height: 410,
padding: 30,
width: 446,
height: 348,
padding: '45px 60px',
backgroundImage:"url('/assets/images/background/loginbg.png')",
backgroundSize: '100% 100%',
backgroundRepeat: 'no-repeat',
position: 'absolute',
top: '33.89%',
right: '16.43%',
}}>
<p style={{ fontSize: 21, fontWeight: 'bold', textAlign: 'center' }}>飞尚物联</p>
<div style={{width:113,height:24,marginTop:3,marginLeft:5}}>
<img src="/assets/images/background/user_login.png" alt="" style={{width:'100%',height:'100%'}}/>
</div>
<Form
onSubmit={values => {
dispatch(login(values.username, values.password))
dispatch(login(values.username, values.password)).then(res => {
const data = res.payload.user
dispatch(actions.layout.initWebSocket({ ioUrl: apiRoot, token: data.token }))
})
}}
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.Input
className='inputbgc'
field='username'
noLabel={true}
label='用户名'
placeholder='请输入账号'
prefix={<IconUser style={{color:'#1859C1',marginRight:14,marginLeft:8}}/>}
style={{background:'rgba(24, 89, 193, 0.08)',height:40,marginTop:26}}
/>
<Form.Input
field='password'
noLabel={true}
mode="password"
autoComplete=""
placeholder='请输入密码'
label='密码'
prefix={<IconLock style={{color:'#1859C1',marginRight:14,marginLeft:8}}/>}
style={{background:'rgba(24, 89, 193, 0.08)',height:40}}
/>
<Button htmlType='submit' block theme="solid" style={{marginTop:17,height:40,backgroundColor:'#1859C1'}}>立即登录</Button>
</Form>
</div>
</div>
@ -51,10 +82,12 @@ const Login = props => {
}
function mapStateToProps (state) {
const { auth } = state;
const { auth, global } = state;
return {
user: auth.user,
error: auth.error,
actions: global.actions,
apiRoot: global.apiRoot,
isRequesting: auth.isRequesting
}
}

4
code/VideoAccess-VCMP/web/client/src/sections/auth/reducers/auth.js

@ -8,9 +8,9 @@ const initState = {
error: null
};
function auth(state = initState, action) {
function auth (state = initState, action) {
const payload = action.payload;
switch (action.type){
switch (action.type) {
case actionTypes.INIT_AUTH:
return Immutable.fromJS(state).set('user', payload.user).toJS();
case actionTypes.REQUEST_LOGIN:

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

@ -0,0 +1,7 @@
input:-webkit-autofill{
-webkit-text-fill-color:black !important;
-webkit-box-shadow: 0 0 0px 1000px transparent inset !important;
box-shadow: 0 0 0px 1000px transparent inset !important;
background-color:transparent;
transition: background-color 50000s ease-in-out 0s;
}

7
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/actions/index.js

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

64
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/actions/nvr.js

@ -0,0 +1,64 @@
"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" },
});
}
export function getNvr(query) {
return (dispatch) =>
basicAction({
type: "get",
dispatch: dispatch,
actionType: "GET_NVR",
query: query,
url: `${ApiTable.getNvr}`,
msg: { option: "获取nvr列表信息" },
reducer: { name: "equipmentWarehouseNvr" },
});
}
export function delNvr(orgId) {
return (dispatch) =>
basicAction({
type: "del",
dispatch: dispatch,
actionType: "DEL_NVR",
url: `${ApiTable.delNvr.replace("{nvrId}", orgId)}`,
msg: { option: "删除NVR" },
reducer: { name: "" },
});
}
export function addchangeNvr(data) {
return (dispatch) =>
basicAction({
type: "post",
dispatch: dispatch,
data,
actionType: "ADD_CHANGE_NVR",
msg: { option: "添加/修改" },
url: `${ApiTable.nvr}`,
});
}
export function getVender() {
//获取设备厂商
return (dispatch) =>
basicAction({
type: "get",
dispatch: dispatch,
actionType: "GET_VENDER",
url: `${ApiTable.getVender}`,
msg: { error: "获取设备厂商失败" },
reducer: { name: "vender" },
});
}

262
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/cameraModal.jsx

@ -0,0 +1,262 @@
import React, { useState ,useRef,useEffect} from 'react'
import { connect } from "react-redux";
import { Modal,Spin } from '@douyinfe/semi-ui';
import { IconChevronLeft,IconChevronRight } from '@douyinfe/semi-icons';
import FluoriteCamera from "./fluoriteCamera";
import NvrCamera from './nvrCamera';
import IpcCamera from './ipcCamera';
import CascadeCamera from './cascadeCamera';
import "./cameraModal.less";
function cameraModal(props){
const {modalName,visible,close}=props
const fluoriteRef = useRef();
const ipcRef = useRef();
const cascadeRef = useRef();
const [isloading,setloading] = useState(false);//loading
const [loadingTip,setloadingTip] = useState('获取中...请稍后...');//loading tip
const [okText,setokText] = useState('确定')//oktext
const [cancelText,setcancelText] = useState('取消')//text
const opts ={//
title:'Hi',
content:'添加成功',
duration:3
}
const [clickNum,setclickNum] = useState(1);//
const cameraList=[//
{
id:1,
img:'/assets/images/background/ysy.png',
title:'萤石云平台摄像头',
text:'通过萤石云平台rtmp地址配置完成推流的平台摄像头。'
},{
id:2,
img:'/assets/images/background/nvr.png',
title:'NVR摄像头',
text:'通过连接NVR(网络硬盘录像机)进行视频流推送的摄像头'
},{
id:3,
img:'/assets/images/background/ipc.png',
title:'IPC网络摄像头',
text:'通过网络与监控设备直连完成视频流推送的摄像头设备'
},{
id:4,
img:'/assets/images/background/cascade.png',
title:'级联摄像头',
text:'通过GB/T28181协议级联的平台摄像头,常用于平台对接推送'
},
]
const [showcameraList,setcameraList]=useState(cameraList.slice(0,3));//
function handleOk() {//
if(clickNum==1){
console.log('1111111111111');
}else if(clickNum==2){
console.log('22222222222222');
}else if(clickNum==3){
console.log('33333333333333');
}else if(clickNum==4){
console.log('44444444444444');
}
// Notification.success(opts)
// close();
}
function handleAfterClose(){//
}
function handleCancel() {//
close();
}
function handleChoose(id){//
setclickNum(id);
}
function turnLift(){//
setcameraList(cameraList.slice(0,3))
}
function turnRight(){//
setcameraList(cameraList.slice(1,4))
}
function onReset(){
if(clickNum==1){
fluoriteRef.current.resetFluoriteCamera()
}else if(clickNum==3){
ipcRef.current.resetIpcCamera()
}else if(clickNum==4){
}
}
function toTest(){
if(clickNum==1){
fluoriteRef.current.fluoriteCameraForm().then(values=>{//
console.log('111111111',values);
})
.catch(errors=>{//
console.log('errors',errors);
})
}else if(clickNum==3){
ipcRef.current.ipcCameraForm().then(values=>{//
console.log('111111111',values);
})
.catch(errors=>{//
console.log('errors',errors);
})
}else if(clickNum==4){
cascadeRef.current.cascadeCameraForm()
.then(values=>{//
let chooseList=[]
let nvrCameraList=[{
id:10,
name:'南昌县1',
number:'111111111111111111',
support:false,
change:false,
},{
id:20,
name:'南昌县2',
number:'222222222222222222',
support:false,
change:false,
},{
id:30,
name:'南昌县3',
number:'333333333333333333',
support:false,
change:false,
},{
id:40,
name:'南昌县4',
number:'444444444444444444',
support:false,
change:false,
}]
cascadeRef.current.setNVRcameraList(nvrCameraList)
for (let index = 0; index < nvrCameraList.length; index++) {
chooseList.push(nvrCameraList[index].id)
}
cascadeRef.current.setNvrCheckList(chooseList)
cascadeRef.current.setIsAllChoose(true)
})
.catch(errors=>{//
console.log('errors',errors);
})
}
}
return (
<>
<Modal
title={modalName=='add'?'添加摄像头':'修改摄像头'}
okText={okText}
cancelText={cancelText} //
visible={visible}
onOk={handleOk}
width={921}
afterClose={handleAfterClose}
onCancel={handleCancel}
>
<Spin tip={loadingTip} spinning={isloading}>
<div style={{marginLeft:'-24px',marginRight:'-24px',marginTop:8}}>
<div style={{marginLeft:29,color:'#1859C1',fontSize:14,fontWeight:500}}>接入类型</div>
<div style={{marginTop:5,display:'flex',alignItems:'center',justifyContent:'space-between'}}>
<IconChevronLeft
style={{color:'rgba(0, 0, 0, 0.45)',fontSize:16,marginLeft:29,cursor: "pointer",}}
onClick={turnLift}/>
<div
style={{display:'flex',alignItems:'center',height:146}}>
{showcameraList.map((item,index)=>(
<div
key={item.id}
style={{
width:266,
height:146,
marginRight:12,
border:clickNum===item.id?'1px solid #1859C1':'1px solid #F9F9F9',
borderRadius:3,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
cursor: "pointer",
position: 'relative'}}
onClick={()=>handleChoose(item.id)}>
<div
style={{marginTop:5,
height:65,
width:116,
display: 'flex',
justifyContent: 'center',
alignItems: 'center'}}>
<img
src={item.img}
alt="设置"
/>
</div>
<div style={{marginTop:2,fontSize:14,color:'rgba(0, 0, 0, 0.85)',}}>{item.title}</div>
<div style={{width:210,height:34,marginTop:9,fontSize:12,color:'rgba(0, 0, 0, 0.45)',textAlign:'center'}}>{item.text}</div>
{clickNum===item.id?<div style={{ position: 'absolute', top: '-3px', right: '-5px'}}>
<img src="/assets/images/background/topchoose.png" alt="1" />
</div>:''}
</div>
))}
</div>
<IconChevronRight
style={{color:'rgba(0, 0, 0, 0.45)',fontSize:16,marginRight:18,cursor: "pointer",}}
onClick={turnRight}/>
</div>
</div>
<div style={{height:30,marginLeft:'-24px',marginRight:'-24px',marginTop:48,display:'flex',alignItems: 'center',justifyContent: 'space-between'}}>
<div style={{marginLeft:29,color:'#1859C1',fontSize:14,fontWeight:500}}>配置属性</div>
{clickNum!==2?<div style={{display:'flex',marginRight:43,}}>
<div style={{
height:30,
width:64,
border:'1px solid #D9D9D9',
borderRadius: '3px',
color:'rgba(0, 0, 0, 0.65)',
display:'flex',
justifyContent: 'center',
alignItems: 'center',
cursor: "pointer",
marginRight:16
}} onClick={onReset}>
<img src="/assets/images/background/Reset.png" alt="1" style={{marginRight:4}}/>
重置
</div>
<div style={{
height:30,
width:64,
border:'1px solid #1859C1',
borderRadius: '3px',
color:'#1859C1',
display:'flex',
justifyContent: 'center',
alignItems: 'center',
cursor: "pointer",
}} onClick={toTest}>
<img src="/assets/images/background/test.png" alt="1" style={{marginRight:4}} />
测试
</div>
</div>:''}
</div>
<div>
{clickNum==1?
<FluoriteCamera cRef={fluoriteRef}/>
:clickNum==2?
<NvrCamera/>
:clickNum==3?
<IpcCamera aRef={ipcRef} />
:<CascadeCamera dRef={cascadeRef}/>}
</div>
</Spin>
</Modal>
</>
);
}
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)(cameraModal);

9
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/cameraModal.less

@ -0,0 +1,9 @@
.semi-radio-cardRadioGroup_checked .cloud{
color: #1859C1;
}
.semi-radio-cardRadioGroup_checked .voice{
color: #1859C1;
}
.semi-radio-cardRadioGroup_checked .switching{
color: #1859C1;
}

192
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/cascadeCamera.jsx

@ -0,0 +1,192 @@
import React, { useState ,useRef,useEffect,useImperativeHandle} from 'react'
import { connect } from "react-redux";
import { Form,Row,Col,CheckboxGroup, Checkbox,Radio,Input } from '@douyinfe/semi-ui';
import { IconEdit,IconPlayCircle } from '@douyinfe/semi-icons';
import "./cameraModal.less";
function cascadeCamera({dRef}){
const form = useRef();
const [memoryList,setMemoryList] = useState([
{
id:1,
value:'8g'
},{
id:2,
value:'16g'
},{
id:3,
value:'32g'
},{
id:4,
value:'64g'
},{
id:5,
value:'128g'
},{
id:6,
value:'256g'
},{
id:7,
value:'>256g'
}
])//
const [nvrCheckList, setNvrCheckList] = useState([]);//nvr
const [NVRcameraList,setNVRcameraList]=useState([])//nvr
const [isAllChoose,setIsAllChoose]=useState(false)//
const [equipmentNum,setEquipmentNum]=useState('')//nvr
function NvrChangeName(e,index){//nvr
let NvrchangeList = JSON.parse(JSON.stringify(NVRcameraList))
NvrchangeList[index].change=true
setNVRcameraList(NvrchangeList)
e.stopPropagation()
}
function nvronBlur(index){//nvr
let NvrchangeList = JSON.parse(JSON.stringify(NVRcameraList))
NvrchangeList[index].change=false
setNVRcameraList(NvrchangeList)
}
function inputchange(e,index){//nvr
let NvrchangeList = JSON.parse(JSON.stringify(NVRcameraList))
NvrchangeList[index].name=e
setNVRcameraList(NvrchangeList)
}
function toggle(e,index){//nvr
let NvrchangeList = JSON.parse(JSON.stringify(NVRcameraList))
NvrchangeList[index].support=e.target.checked
setNVRcameraList(NvrchangeList)
e.stopPropagation()
}
function allChoose(e){///
let chooseList=[]
if(NVRcameraList.length==nvrCheckList.length){
setNvrCheckList([])
setIsAllChoose(false)
}
else{
for (let index = 0; index < NVRcameraList.length; index++) {
chooseList.push(NVRcameraList[index].id)
}
setNvrCheckList(chooseList)
setIsAllChoose(true)
}
}
function playVideo(e) {//nvr
console.log('22222222222222222');
e.stopPropagation()
}
useImperativeHandle(dRef,() => ({//
//
cascadeCameraForm : form.current.validate,
resetCascadeCamera : form.current.reset,
setNVRcameraList : setNVRcameraList,
setNvrCheckList : setNvrCheckList,
setIsAllChoose : setIsAllChoose,
}))
return (
<>
<Form
allowEmpty
labelPosition='left'
labelAlign='left'
labelWidth= '115px'
onValueChange={values=>{console.log(values);setEquipmentNum(values.equipmentNum)}}
getFormApi={formApi => form.current = formApi}>
<Row>
<Col span={12}>
<Form.Input field='foreignDomainName' label='外域名称:' initValue={''} placeholder='请输入外域名称' style={{ width:307 }}
rules={[
{ required: true, message: '请输入外域名称' }
]}/>
</Col>
<Col span={12}>
<Form.Select label="级联方式:" field='cascadeMode' placeholder='请选择级联方式' style={{ width:307 }}
rules={[
{ required: true, message: '请选择输入级联方式' }
]}>
{memoryList.map((item,index)=>(
<Form.Select.Option key={index} value={item.id}>{item.value}</Form.Select.Option>
))}
</Form.Select>
</Col>
<Col span={12}>
<Form.Select label="SIP编号:" field='sipNum' placeholder='请选择SIP编号' style={{ width:307 }}
rules={[
{ required: true, message: '请选择SIP编号' }
]}>
{memoryList.map((item,index)=>(
<Form.Select.Option key={index} value={item.id}>{item.value}</Form.Select.Option>
))}
</Form.Select>
</Col>
<Col span={24}>
{NVRcameraList.length>0?<div style={{display: 'flex',alignItems: 'center',justifyContent: 'flex-end',marginRight:19}}>
<Radio
checked={isAllChoose}
mode="advanced"
onChange={e=>allChoose(e)}
aria-label="全选">
全选
</Radio>
</div>:''}
</Col>
</Row>
<Row>
<CheckboxGroup type='pureCard' direction='vertical' aria-label="视频流获取"
value={nvrCheckList}
onChange={(nvrCheck) => {
setNvrCheckList(nvrCheck);
console.log('11111111111',nvrCheck);
if(NVRcameraList.length==nvrCheck.length){
setIsAllChoose(true)
}
else{
setIsAllChoose(false)
}
}}>
{NVRcameraList.length>0?NVRcameraList.map((item,index)=>(
<Col key={index} span={12} style={{display:'flex',justifyContent:'center',marginTop:12}}>
<Checkbox value={item.id}
extra={
<div>
<div style={{display:'flex',alignItems:'center',justifyContent:'space-between',height:30}}>
<div>通道名称{item.change?<Input autofocus style={{width:100}} value={item.name} onChange={e=>inputchange(e,index)} onBlur={()=>nvronBlur(index)}></Input>:item.name}</div>
<div style={{display:'flex',alignItems:'center'}}>
<IconEdit
style={{fontSize:16,marginLeft:18,cursor: "pointer",color:'#1859C1'}}
onClick={e=>NvrChangeName(e,index)}/>
</div>
</div>
<div style={{marginTop:8,width:246}}>设备编号{item.number}</div>
<div style={{marginTop:12,display:'flex',justifyContent:'space-between',alignItems:'center'}}>
<IconPlayCircle size='extra-large' style={{color:'#1859C1',}} onClick={e=>playVideo(e)}/>
<Radio
checked={item.support}
mode="advanced"
onChange={e=>toggle(e,index)}
aria-label="单选"
>
云台支持
</Radio>
</div>
</div>
}
style={{width:280,border:'1px solid #F9F9F9',}}>
</Checkbox>
</Col>
)):''}
</CheckboxGroup>
</Row>
</Form>
</>
);
}
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)(cascadeCamera);

262
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/fluoriteCamera.jsx

@ -0,0 +1,262 @@
import React, { useState ,useRef,useEffect,useImperativeHandle} from 'react'
import { connect } from "react-redux";
import { Form,Row,Col} from '@douyinfe/semi-ui';
import "./cameraModal.less";
function fluoriteCamera({cRef}){
const { TextArea } = Form;
const form = useRef();
const [cloud,setcloud] = useState('')//
const [voice,setvoice] = useState('')//
const [switching,setSwitching] = useState('')//
const [memoryList,setMemoryList] = useState([
{
id:1,
value:'8g'
},{
id:2,
value:'16g'
},{
id:3,
value:'32g'
},{
id:4,
value:'64g'
},{
id:5,
value:'128g'
},{
id:6,
value:'256g'
},{
id:7,
value:'>256g'
}
])//
function handleLocation(){//
window.open('https://lbs.amap.com/tools/picker','_blank')
}
function positionForm(val){//
let zz = /^(-?\d+)(\.\d+)?$/
if(!val){
return '请输入或拾取高德经纬度坐标'
}
else if(val.split(',').length!=2){
return '请输入格式为116.354169,39.835452的经纬度坐标'
}
else if(!zz.test(val.split(',')[0])){
return '只能填写数字'
}
else if(!zz.test(val.split(',')[1])){
return '只能填写数字'
}
else{
return ''
}
}
useImperativeHandle(cRef,() => ({//
//
fluoriteCameraForm : form.current.validate,
resetFluoriteCamera : form.current.reset
}))
return (
<>
<Form
labelPosition='left'
labelAlign='left'
labelWidth= '115px'
onValueChange={values=>console.log(values)}
getFormApi={formApi => form.current = formApi}>
<Row>
<Col span={12}>
{/* 设备名称 */}
<Form.Input field='UserName' label='设备名称:' initValue={''} placeholder='请输入设备名称、常用项目或位置定义' style={{ width:307 }}
rules={[
{ required: true, message: '请输入设备名称' }
]}/>
{/* 高清切换 */}
<Form.RadioGroup
label="高清切换:"
field='hdSwitching'
type='pureCard'
direction='horizontal'
style={{padding:0,paddingTop:1,paddingBottom:1}}
rules={[
{ required: true, message: '请选择高清切换' }
]}
onChange={(checked) => {
console.log(checked.target.value);
if(checked.target.value=='yes'){
setSwitching('yes')
}
else{
setSwitching('no')
}
}}>
<Form.Radio value="yes" style={{width:58,height:30,padding:0,margin:0,background:'#F9F9F9'}}>
<div className='switching' style={{width:58,height:30,textAlign:'center',lineHeight:'30px'}}>
支持
</div>
{switching=='yes'?<div style={{position: 'absolute', top: '-2px', right: '-1px'}}>
<img src="/assets/images/background/formchoose.png" alt="1" />
</div>:''}
</Form.Radio>
<Form.Radio value="no" style={{width:58,height:30,padding:0,margin:0,marginLeft:18,background:'#F9F9F9'}}>
<div className='switching' style={{width:58,height:30,textAlign:'center',lineHeight:'30px'}}>
不支持
</div>
{switching=='no'?<div style={{position: 'absolute', top: '-2px', right: '-1px'}}>
<img src="/assets/images/background/formchoose.png" alt="1" />
</div>:''}
</Form.Radio>
</Form.RadioGroup>
{/* 安装位置 */}
<div style={{display:'flex'}}>
<Form.Input field='Use11rName1312' label='安装位置:' placeholder='请输入或拾取高德经纬度坐标' style={{ width:270 }}
validate={positionForm}
rules={[
{ required: true, message: '请输入或拾取高德经纬度坐标' }
]}/>
<div style={{
width:32,
height:32,
background:"#1859C1",
marginLeft:4,
display:'flex',
justifyContent: 'center',
alignItems: 'center',
cursor: "pointer",
marginTop:12,
borderRadius: 3+'px'}}
onClick={handleLocation}>
<img src="../../../assets/images/background/location.png" width={16} height={20}/>
</div>
</div>
{/* 设备类型 */}
<div style={{display:'flex',}}>
<div>
<Form.Select label="设备类型:" field='business23' placeholder='请选择摄像头类型' style={{ width:160 }}
rules={[
{ required: true, message: '请选择摄像头类型' }
]}>
{memoryList.map((item,index)=>(
<Form.Select.Option key={index} value={item.id}>{item.value}</Form.Select.Option>
))}
</Form.Select>
</div>
<div style={{marginLeft:7}}>
<Form.Select noLabel='true' field='business244' placeholder='请选择能力' style={{ width:140 }}
rules={[
{ required: true, message: '请选择能力' }
]}>
{memoryList.map((item,index)=>(
<Form.Select.Option key={index} value={item.id}>{item.value}</Form.Select.Option>
))}
</Form.Select>
</div>
</div>
</Col>
<Col span={12}>
{/* 云台支持 */}
<Form.RadioGroup
label="云台支持:"
field='role'
type='pureCard'
direction='horizontal'
style={{padding:0,paddingTop:1,paddingBottom:1}}
rules={[
{ required: true, message: '请选择云台支持' }
]}
onChange={(checked) => {
console.log(checked.target.value);
if(checked.target.value=='yes'){
setcloud('yes')
}
else{
setcloud('no')
}
}}>
<Form.Radio value="yes" style={{width:58,height:30,padding:0,margin:0,background:'#F9F9F9'}}>
<div className='cloud' style={{width:58,height:30,textAlign:'center',lineHeight:'30px'}}>
支持
</div>
{cloud=='yes'?<div style={{position: 'absolute', top: '-2px', right: '-1px'}}>
<img src="/assets/images/background/formchoose.png" alt="1" />
</div>:''}
</Form.Radio>
<Form.Radio value="no" style={{width:58,height:30,padding:0,margin:0,marginLeft:18,background:'#F9F9F9'}}>
<div className='cloud' style={{width:58,height:30,textAlign:'center',lineHeight:'30px'}}>
不支持
</div>
{cloud=='no'?<div style={{position: 'absolute', top: '-2px', right: '-1px'}}>
<img src="/assets/images/background/formchoose.png" alt="1" />
</div>:''}
</Form.Radio>
</Form.RadioGroup>
{/* 内存 */}
<div style={{display:'flex'}}>
<Form.Select label="内存:" field='business2' placeholder='未安装' style={{ width:92 }}>
{memoryList.map((item,index)=>(
<Form.Select.Option key={index} value={item.id}>{item.value}</Form.Select.Option>
))}
</Form.Select>
{/* 语音支持 */}
<div style={{marginLeft:18}}>
<Form.RadioGroup
labelWidth= '76px'
label="语音支持:"
field='role2'
type='pureCard'
direction='horizontal'
style={{padding:0,paddingTop:1,paddingBottom:1}}
onChange={(checked) => {
console.log(checked.target.value);
if(checked.target.value=='yes'){
setvoice('yes')
}
else{
setvoice('no')
}
}}>
<Form.Radio value="yes" style={{width:58,height:30,padding:0,margin:0,background:'#F9F9F9'}}>
<div className='voice' style={{width:58,height:30,textAlign:'center',lineHeight:'30px'}}>
支持
</div>
{voice=='yes'?<div style={{position: 'absolute', top: '-2px', right: '-1px'}}>
<img src="/assets/images/background/formchoose.png" alt="1" />
</div>:''}
</Form.Radio>
<Form.Radio value="no" style={{width:58,height:30,padding:0,margin:0,marginLeft:18,background:'#F9F9F9'}}>
<div className='voice' style={{width:58,height:30,textAlign:'center',lineHeight:'30px'}}>
不支持
</div>
{voice=='no'?<div style={{position: 'absolute', top: '-2px', right: '-1px'}}>
<img src="/assets/images/background/formchoose.png" alt="1" />
</div>:''}
</Form.Radio>
</Form.RadioGroup>
</div>
</div>
{/* RTMP地址接入 */}
<TextArea
style={{ width:320, height: 90 }}
field='description'
label='RTMP地址接入:'
placeholder='请输入RTMP地址接入'
/>
</Col>
</Row>
</Form>
</>
);
}
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)(fluoriteCamera);

224
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/ipcCamera.jsx

@ -0,0 +1,224 @@
import React, { useState ,useRef,useEffect,useImperativeHandle} from 'react'
import { connect } from "react-redux";
import { Form,Row,Col } from '@douyinfe/semi-ui';
import "./cameraModal.less";
function ipcCamera({aRef}){
const { TextArea } = Form;
const form = useRef();
const [cloud,setcloud] = useState('')//
const [voice,setvoice] = useState('')//
const [memoryList,setMemoryList] = useState([
{
id:1,
value:'8g'
},{
id:2,
value:'16g'
},{
id:3,
value:'32g'
},{
id:4,
value:'64g'
},{
id:5,
value:'128g'
},{
id:6,
value:'256g'
},{
id:7,
value:'>256g'
}
])//
function handleLocation(){//
window.open('https://lbs.amap.com/tools/picker','_blank')
}
function positionForm(val){//
let zz = /^(-?\d+)(\.\d+)?$/
if(!val){
return '请输入或拾取高德经纬度坐标'
}
else if(val.split(',').length!=2){
return '请输入格式为116.354169,39.835452的经纬度坐标'
}
else if(!zz.test(val.split(',')[0])){
return '只能填写数字'
}
else if(!zz.test(val.split(',')[1])){
return '只能填写数字'
}
else{
return ''
}
}
useImperativeHandle(aRef,() => ({//
//
ipcCameraForm : form.current.validate,
resetIpcCamera : form.current.reset
}))
return (
<>
<Form
labelPosition='left'
labelAlign='left'
labelWidth= '115px'
onValueChange={values=>console.log(values)}
getFormApi={formApi => form.current = formApi}>
<Row>
<Col span={12}>
<Form.Input field='UserName' label='设备名称:' initValue={''} placeholder='请输入设备名称、常用项目或位置定义' style={{ width:307 }}
rules={[
{ required: true, message: '请输入设备名称' }
]}/>
<Form.Input field='Use11rName11' label='设备厂家:' placeholder='请选择设备厂家' style={{ width:307 }}/>
<div style={{display:'flex'}}>
<Form.Input field='Use11rName1312' label='安装位置:' placeholder='请输入或拾取高德经纬度坐标' style={{ width:270 }}
validate={positionForm}
rules={[
{ required: true, message: '请输入或拾取高德经纬度坐标' }
]}/>
<div style={{
width:32,
height:32,
background:"#1859C1",
marginLeft:4,
display:'flex',
justifyContent: 'center',
alignItems: 'center',
cursor: "pointer",
marginTop:12,
borderRadius: 3+'px'}}
onClick={handleLocation}>
<img src="../../../assets/images/background/location.png" width={16} height={20}/>
</div>
</div>
<div>
<Form.Input field='UserName11' label='设备编号接入:' initValue={''} placeholder='请输入设备编号' style={{ width:307 }}/>
</div>
</Col>
<Col span={12}>
<Form.RadioGroup
label="云台支持:"
field='role'
type='pureCard'
direction='horizontal'
style={{padding:0,paddingTop:1,paddingBottom:1}}
rules={[
{ required: true, message: '请选择云台支持' }
]}
onChange={(checked) => {
console.log(checked.target.value);
if(checked.target.value=='yes'){
setcloud('yes')
}
else{
setcloud('no')
}
}}>
<Form.Radio value="yes" style={{width:58,height:30,padding:0,margin:0,background:'#F9F9F9'}}>
<div className='cloud' style={{width:58,height:30,textAlign:'center',lineHeight:'30px'}}>
支持
</div>
{cloud=='yes'?<div style={{position: 'absolute', top: '-2px', right: '-1px'}}>
<img src="/assets/images/background/formchoose.png" alt="1" />
</div>:''}
</Form.Radio>
<Form.Radio value="no" style={{width:58,height:30,padding:0,margin:0,marginLeft:18,background:'#F9F9F9'}}>
<div className='cloud' style={{width:58,height:30,textAlign:'center',lineHeight:'30px'}}>
不支持
</div>
{cloud=='no'?<div style={{position: 'absolute', top: '-2px', right: '-1px'}}>
<img src="/assets/images/background/formchoose.png" alt="1" />
</div>:''}
</Form.Radio>
</Form.RadioGroup>
<div style={{display:'flex'}}>
<Form.Select label="内存:" field='business2' placeholder='未安装' style={{ width:92 }}>
{memoryList.map((item,index)=>(
<Form.Select.Option key={index} value={item.id}>{item.value}</Form.Select.Option>
))}
</Form.Select>
<div style={{marginLeft:18}}>
<Form.RadioGroup
labelWidth= '76px'
label="语音支持:"
field='role2'
type='pureCard'
direction='horizontal'
style={{padding:0}}
onChange={(checked) => {
console.log(checked.target.value);
if(checked.target.value=='yes'){
setvoice('yes')
}
else{
setvoice('no')
}
}}>
<Form.Radio value="yes" style={{width:58,height:30,padding:0,margin:0,background:'#F9F9F9'}}>
<div className='voice' style={{width:58,height:30,textAlign:'center',lineHeight:'30px'}}>
支持
</div>
{voice=='yes'?<div style={{position: 'absolute', top: '-2px', right: '-1px'}}>
<img src="/assets/images/background/formchoose.png" alt="1" />
</div>:''}
</Form.Radio>
<Form.Radio value="no" style={{width:58,height:30,padding:0,margin:0,marginLeft:18,background:'#F9F9F9'}}>
<div className='voice' style={{width:58,height:30,textAlign:'center',lineHeight:'30px'}}>
不支持
</div>
{voice=='no'?<div style={{position: 'absolute', top: '-2px', right: '-1px'}}>
<img src="/assets/images/background/formchoose.png" alt="1" />
</div>:''}
</Form.Radio>
</Form.RadioGroup>
</div>
</div>
<TextArea
style={{ width:320, height: 90 }}
field='description'
label='RTMP地址接入:'
placeholder='请输入RTMP地址接入'
/>
</Col>
<Col span={18}>
<div style={{display:'flex',}}>
<div>
<Form.Select label="设备类型:" field='business23' placeholder='请选择摄像头类型' style={{ width:160 }}
rules={[
{ required: true, message: '请选择摄像头类型' }
]}>
{memoryList.map((item,index)=>(
<Form.Select.Option key={index} value={item.id}>{item.value}</Form.Select.Option>
))}
</Form.Select>
</div>
<div style={{marginLeft:7}}>
<Form.Select noLabel='true' field='business244' placeholder='请选择能力' style={{ width:140 }}
rules={[
{ required: true, message: '请选择能力' }
]}>
{memoryList.map((item,index)=>(
<Form.Select.Option key={index} value={item.id}>{item.value}</Form.Select.Option>
))}
</Form.Select>
</div>
</div>
</Col>
</Row>
</Form>
</>
);
}
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)(ipcCamera);

182
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/nvrCamera.jsx

@ -0,0 +1,182 @@
import React, { useState ,useRef,useEffect,useImperativeHandle} from 'react'
import { connect } from "react-redux";
import { Form,Row,Col,Button,CheckboxGroup, Checkbox,Radio,Input } from '@douyinfe/semi-ui';
import { IconEdit,IconPlayCircle } from '@douyinfe/semi-icons';
import "./cameraModal.less";
function nvrCamera({cRef}){
const form = useRef();
const [nvrCheckList, setNvrCheckList] = useState([]);//nvr
const [NVRcameraList,setNVRcameraList]=useState([])//nvr
const [isAllChoose,setIsAllChoose]=useState(false)//
const [equipmentNum,setEquipmentNum]=useState('')//nvr
function NvrChangeName(e,index){//nvr
let NvrchangeList = JSON.parse(JSON.stringify(NVRcameraList))
NvrchangeList[index].change=true
setNVRcameraList(NvrchangeList)
e.stopPropagation()
}
function nvronBlur(index){//nvr
let NvrchangeList = JSON.parse(JSON.stringify(NVRcameraList))
NvrchangeList[index].change=false
setNVRcameraList(NvrchangeList)
}
function inputchange(e,index){//nvr
let NvrchangeList = JSON.parse(JSON.stringify(NVRcameraList))
NvrchangeList[index].name=e
setNVRcameraList(NvrchangeList)
}
function toggle(e,index){//nvr
let NvrchangeList = JSON.parse(JSON.stringify(NVRcameraList))
NvrchangeList[index].support=e.target.checked
setNVRcameraList(NvrchangeList)
e.stopPropagation()
}
function getVideoList(){
form.current.validate().then(values=>{//
let chooseList=[]
let nvrCameraList=[{
id:10,
name:'南昌县1',
number:'111111111111111111',
support:false,
change:false,
},{
id:20,
name:'南昌县2',
number:'222222222222222222',
support:false,
change:false,
},{
id:30,
name:'南昌县3',
number:'333333333333333333',
support:false,
change:false,
},{
id:40,
name:'南昌县4',
number:'444444444444444444',
support:false,
change:false,
}]
setNVRcameraList(nvrCameraList)
for (let index = 0; index < nvrCameraList.length; index++) {
chooseList.push(nvrCameraList[index].id)
}
setNvrCheckList(chooseList)
setIsAllChoose(true)
})
.catch(errors=>{//
console.log('errors',errors);
})
}
function allChoose(e){///
let chooseList=[]
if(NVRcameraList.length==nvrCheckList.length){
setNvrCheckList([])
setIsAllChoose(false)
}
else{
for (let index = 0; index < NVRcameraList.length; index++) {
chooseList.push(NVRcameraList[index].id)
}
setNvrCheckList(chooseList)
setIsAllChoose(true)
}
}
function playVideo(e) {//nvr
console.log('22222222222222222');
e.stopPropagation()
}
// useImperativeHandle(cRef,() => ({//
// //aa
// getDate : form.current.validate,
// // resetFluoriteCamera : form.current.reset
// }))
return (
<>
<Form
allowEmpty
labelPosition='left'
labelAlign='left'
labelWidth= '115px'
onValueChange={values=>{setEquipmentNum(values.equipmentNum)}}
getFormApi={formApi => form.current = formApi}>
<div style={{display:'flex'}}>
<Form.Input field='equipmentNum' maxLength='39' label='设备编号:' initValue={''} placeholder='请输入设备编号' style={{ width:307 }}
rules={[
{ required: true, message: '请输入设备编号' },
{ pattern: '^[A-Za-z0-9]+$', message: '只能输入数字或者字母' }
]}/>
<Button disabled={!equipmentNum.length>0} theme='solid' type='primary' onClick={()=>{getVideoList()}} style={{ marginLeft: 8,marginTop:12 }}>视频流获取</Button>
{NVRcameraList.length>0?<div style={{display: 'flex',alignItems: 'center',marginLeft: 211}}>
<Radio
checked={isAllChoose}
mode="advanced"
onChange={e=>allChoose(e)}
aria-label="全选">
全选
</Radio>
</div>:''}
</div>
<Row>
<CheckboxGroup type='pureCard' direction='vertical' aria-label="视频流获取"
value={nvrCheckList}
onChange={(nvrCheck) => {
setNvrCheckList(nvrCheck);
// console.log('11111111111',nvrCheck);
if(NVRcameraList.length==nvrCheck.length){
setIsAllChoose(true)
}
else{
setIsAllChoose(false)
}
}}>
{NVRcameraList.length>0?NVRcameraList.map((item,index)=>(
<Col key={index} span={12} style={{display:'flex',justifyContent:'center',marginTop:12}}>
<Checkbox value={item.id}
extra={
<div>
<div style={{display:'flex',alignItems:'center',justifyContent:'space-between',height:30}}>
<div>通道名称{item.change?<Input autofocus style={{width:100}} value={item.name} onChange={e=>inputchange(e,index)} onBlur={()=>nvronBlur(index)}></Input>:item.name}</div>
<div style={{display:'flex',alignItems:'center'}}>
<IconEdit
style={{fontSize:16,marginLeft:18,cursor: "pointer",color:'#1859C1'}}
onClick={e=>NvrChangeName(e,index)}/>
</div>
</div>
<div style={{marginTop:8,width:246}}>设备编号{item.number}</div>
<div style={{marginTop:12,display:'flex',justifyContent:'space-between',alignItems:'center'}}>
<IconPlayCircle size='extra-large' style={{color:'#1859C1',}} onClick={e=>playVideo(e)}/>
<Radio
checked={item.support}
mode="advanced"
onChange={e=>toggle(e,index)}
aria-label="单选"
>
云台支持
</Radio>
</div>
</div>
}
style={{width:280,border:'1px solid #F9F9F9',}}>
</Checkbox>
</Col>
)):''}
</CheckboxGroup>
</Row>
</Form>
</>
);
}
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)(nvrCamera);

198
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/nvrModal.jsx

@ -0,0 +1,198 @@
import React, { useState ,useRef} from 'react'
import { connect } from "react-redux";
import { Modal,Form,Row,Col,Spin,Notification } from '@douyinfe/semi-ui';
import { IconTickCircle } from '@douyinfe/semi-icons';
function nvrModal(props){
const {modalName}=props
const { dispatch, actions, user, loading,vender,close } = props;
const nvrData = props.nvrData||{}//
const form = useRef();
const [visible, setVisible] = useState(false);//
const [isloading,setloading] = useState(false);//loading
const [loadingTip,setloadingTip] = useState('获取中...请稍后...');//loading tip
const [step,setstep] = useState(0)//
const [okText,setokText] = useState('测试校验')//oktext
const [cancelText,setcancelText] = useState('取消')//text
const [formObj,setformObj] = useState()//
const opts ={//
title:'Hi',
content:'添加成功',
duration:3
}
function showDialog() {//
setVisible(true);
}
function positionForm(val){
let zz = /^(-?\d+)(\.\d+)?$/
if(!val){
return '请输入或拾取高德经纬度坐标'
}
else if(val.split(',').length!=2){
return '请输入格式为116.354169,39.835452的经纬度坐标'
}
else if(!zz.test(val.split(',')[0])){
return '只能填写数字'
}
else if(!zz.test(val.split(',')[1])){
return '只能填写数字'
}
else{
return ''
}
}
function handleOk() {//
if(step==0){
form.current.validate()
.then(values=>{//
let valuesObj=JSON.parse(JSON.stringify(values))
valuesObj.longitude=values.position.split(',')[0]
valuesObj.latitude=values.position.split(',')[1]
delete valuesObj.position
if(nvrData.id){
valuesObj.id=nvrData.id
}
setformObj(valuesObj)
setloading(true);
setTimeout(() => {
setloadingTip('...接受成功')
setTimeout(()=>{
setloadingTip('已完成')
setTimeout(() => {
setstep(1);
setokText('确认');
setcancelText('上一步');
setloading(false);
}, 2000);
}, 2000)
}, 2000);
})
.catch(errors=>{//
console.log('errors',errors);
})
}
else{
dispatch(actions.equipmentWarehouse.addchangeNvr(formObj)).then(res => {
Notification.success(opts)
setVisible(false);
close();
})
}
}
function handleAfterClose(){//
setstep(0);
setokText('测试校验');
setcancelText('取消');
}
function handleCancel() {//
if(step==0){
setVisible(false);
}
else{
setstep(0);
setokText('测试校验');
setcancelText('取消');
}
}
function handleLocation(){//
window.open('https://lbs.amap.com/tools/picker','_blank')
}
return (
<>
<div onClick={showDialog}>{modalName=='add'?'添加NVR':'修改'}</div>
<Modal
title={modalName=='add'?'添加NVR':'修改NVR'}
okText={okText}
cancelText={cancelText} //
visible={visible}
onOk={handleOk}
width={607}
afterClose={handleAfterClose}
onCancel={handleCancel}
>
<Spin tip={loadingTip} spinning={isloading}>
{step==0?<div style={{paddingLeft:16}}>
<Form
allowEmpty
labelPosition='left'
labelAlign='left'
labelWidth= '90px'
onValueChange={values=>console.log(values)}
getFormApi={formApi => form.current = formApi}>
<Row>
<Col span={12}>
<Form.Input maxLength='39' field='serialNo' label='设备编号:' initValue={nvrData.serialNo||''} placeholder='请输入设备编号' style={{ width:149 }}
rules={[
{ required: true, message: '请输入设备编号' }
]}/>
</Col>
<Col span={12}>
<Form.Input maxLength='15' field='regionCode' label='行政区区码:' initValue={nvrData.regionCode||''} placeholder='请输入行政区区码' style={{ width:149 }}/>
</Col>
<Col span={24}>
<Form.Input maxLength='36' field='name' label='设备名称:' initValue={nvrData.name||''} placeholder='请输入设备名称、常用项目或位置定义' style={{ width:421 }}
rules={[
{ required: true, message: '请输入设备名称、常用项目或位置定义' }
]}/>
</Col>
<Col span={24}>
<Form.Select label="设备厂家:" field='venderId' initValue={nvrData.venderId||null} placeholder='请选择设备厂家' style={{ width: 421 }}
rules={[
{ required: true, message: '请选择设备厂家' }
]}>
{vender.map((item,index)=>(
<Form.Select.Option key={index} value={item.id}>{item.name}</Form.Select.Option>
))}
</Form.Select>
</Col>
<Col span={24} style={{display:'flex'}}>
<Form.Input maxLength='39' field='position' label='安装位置:' initValue={nvrData.longitude&&nvrData.latitude?nvrData.longitude+','+nvrData.latitude:''} placeholder='请输入或拾取高德经纬度坐标' style={{ width:386 }}
validate={positionForm}
rules={[
{ required: true, message: '请输入或拾取高德经纬度坐标' }
]}/>
<div style={{
width:32,
height:32,
background:"#1859C1",
marginLeft:4,
display:'flex',
justifyContent: 'center',
alignItems: 'center',
cursor: "pointer",
marginTop:12,
borderRadius: 3+'px'}}
onClick={handleLocation}>
<img src="../../../assets/images/background/location.png" width={16} height={20}/>
</div>
</Col>
</Row>
</Form>
</div>
://
<div style={{height:224}}>
<div style={{paddingTop:50,display: 'flex', justifyContent: 'center'}}>
<IconTickCircle style={{color:'#04B234',fontSize:60}}/>
</div>
<div style={{marginTop:20,display: 'flex', justifyContent: 'center'}}>
已完成NVR设备测试和校验是否确认添加
</div>
</div>
}
</Spin>
</Modal>
</>
);
}
function mapStateToProps(state) {
const { auth, global, members,vender } = state;
return {
loading: members.isRequesting,
user: auth.user,
actions: global.actions,
members: members.data,
vender:vender.data||[],//
};
}
export default connect(mapStateToProps)(nvrModal);

196
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/setup.jsx

@ -0,0 +1,196 @@
import React, { useState, useEffect } from "react";
import {
Modal,
CheckboxGroup,
Checkbox,
TabPane,
Tabs,
} from "@douyinfe/semi-ui";
function Setup(props) {
const {
dispatch,
actions,
user,
loading,
visible,
close,
SETUPS,
CAMERAS,
cameraSetup,
} = props;
const [check, setCheck] = useState([]);
const checkboxcss = { width: "25%", height: 16, margin: "0 0 20px 0" };
useEffect(() => {
//
const nvrItem = localStorage.getItem(SETUPS);
const cameraItem = localStorage.getItem(CAMERAS);
if (cameraSetup) {
setCheck(cameraItem ? JSON.parse(cameraItem) : []);
} else {
setCheck(nvrItem ? JSON.parse(nvrItem) : []);
}
ischeck();
}, []);
const equipmentNVR = [
{ name: "设备厂家", value: "a" },
{ name: "添加账号", value: "b" },
{ name: "通道数", value: "c" },
{ name: "端口", value: "d" },
{ name: "设备状态", value: "e" },
{ name: "创建时间", value: "f" },
];
const projectNVR = [
{ name: "项目名称", value: "g" },
{ name: "things名称", value: "h" },
{ name: "things数量", value: "i" },
];
const equipmentCamera = [
{ name: "设备厂家", value: "manufactor" },
{ name: "接入类型", value: "type" },
{ name: "设备状态", value: "state" },
{ name: "云台支持", value: "support" },
{ name: "内存卡信息", value: "memoryCard" },
{ name: "设备创建时间", value: "time" },
{ name: "设备添加账号", value: "account" },
];
const projectCamera = [
{ name: "项目名称", value: "name" },
{ name: "pcode", value: "pcode" },
{ name: "结构物", value: "structure" },
{ name: "测点", value: "measuringPoint" },
{ name: "监测因素", value: "factor" },
];
function ischeck(value) {
if (check.length >= 8) {
if (check.includes(value)) {
return false;
} else {
return true;
}
}
}
return (
<Modal
title={
<div>
表格属性设置
<span
style={{
width: 50,
lineHeight: "19px",
display: "inline-block",
color: "white",
textAlign: "center",
marginLeft: 6,
background:
check.length == 8
? "rgba(40, 123, 255, 1)"
: "rgba(176, 176, 176, 1)",
}}
>
{check.length}/8
</span>
</div>
}
visible={visible}
style={{ width: 600 }}
onOk={() => {
cameraSetup
? localStorage.setItem(CAMERAS, JSON.stringify(check))
: localStorage.setItem(SETUPS, JSON.stringify(check));
close();
}}
onCancel={() => {
close();
}}
>
<CheckboxGroup
style={{ width: "100%", fontSize: 14 }}
key="primary1"
direction="horizontal"
defaultValue={check}
aria-label="表格属性设置"
onChange={(check) => {
setCheck(check);
ischeck();
}}
>
<div
style={{
width: 550,
border: "1px solid #EAEAEA",
padding: "0px 5px",
borderRadius: 4,
marginBottom: "20px",
}}
>
<div
style={{
borderBottom: "1px solid #EAEAEA",
marginLeft: "10px",
padding: "8px 0px",
}}
>
设备信息
</div>
<div style={{ padding: "15px 12px", width: 530 }}>
{(cameraSetup ? equipmentCamera : equipmentNVR).map((item) => {
return (
<Checkbox
key={item.value}
value={item.value}
style={checkboxcss}
disabled={ischeck(item.value)}
>
{item.name}
</Checkbox>
);
})}
</div>
</div>
<div
style={{
width: 550,
border: "1px solid #EAEAEA",
padding: "0px 5px",
borderRadius: 4,
}}
>
<div
style={{
borderBottom: "1px solid #EAEAEA",
marginLeft: "10px",
padding: "8px 0px",
}}
>
项目信息
</div>
<div style={{ padding: "15px 12px", width: 530 }}>
{(cameraSetup ? projectCamera : projectNVR).map((item) => {
return (
<Checkbox
key={item.value}
value={item.value}
style={checkboxcss}
disabled={ischeck(item.value)}
>
{item.name}
</Checkbox>
);
})}
</div>
</div>
</CheckboxGroup>
</Modal>
);
}
export default Setup;

468
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/components/sideSheet.jsx

@ -0,0 +1,468 @@
import React, { useState, useEffect } from "react";
import { SideSheet, Tabs, TabPane, Button } from "@douyinfe/semi-ui";
import copy from "copy-to-clipboard";
function SideSheets(props) {
const {
dispatch,
actions,
user,
loading,
visible,
close,
SETUPS,
cameraSetup,
} = props;
const [clickStyle, setclickStyle] = useState();
const list = [
{
name: "项目名称",
a: "南昌县智慧环保",
b: "南昌县智慧环保",
c: "南昌市市政隧道综合管理 哦哦哦哦哦 哦哦哦哦 哦哦哦哦 哦哦哦哦哦哦哦哦哦 哦",
d: "C",
},
{ name: "关联结构物", a: "a", b: "B", c: "C", d: "C" },
{ name: "关联测点", a: "a", b: "B", c: "C", d: "C" },
{ name: "关联监测因素", a: "a", b: "B", c: "C", d: "C" },
];
const information = {
nvr: [
{
name: "基础信息",
basics: [
{ name: "设备名称:", value: "消火栓呼呼呼呼" },
{ name: "设备编号:", value: "D50F2049010B" },
{ name: "接入方式:", value: "NJBJ858G68H" },
{ name: "厂商:", value: "哇哇哇哇哇" },
{ name: "添加账号:", value: "Superadmin" },
{ name: "添加时间:", value: "2022-09-09" },
{
name: "行政区别:",
value: "江西省-南昌市-南昌县",
difference: "difference",
},
{
name: "设备安装位置:",
value: "江西省南昌县小蓝经开区江西飞尚科技有限公司",
difference: "line",
},
],
},
{
name: "接入信息",
access: [
{ name: "SIP服务编号:", value: "1111111111" },
{ name: "SIP域:", value: "KGU876J87" },
{ name: "SIP端口号:", value: "KGU876J87" },
{ name: "通道数量:", value: "16通道" },
{ name: "心跳周期:", value: "3600s" },
{ name: "最大心跳次数:", value: "3次" },
{ name: "注册密码:", value: "**********" },
{ name: "注册有效期::", value: "3600s" },
{ name: "接入识别模块:", value: "sssss" },
],
},
{
name: "摄像头信息",
CameraInformation: [
{
name: "通道1",
value1: "145641201564-1",
value2: "哦哦哦我我我喔噢喔喔我",
},
{
name: "通道2",
value1: "145641201564-2",
value2: "哦哦哦哦哦噢噢欧欧哦",
},
],
},
],
camera: [
{
name: "基础信息",
basics: [
{ name: "设备名称:", value: "消火栓呼呼呼呼" },
{ name: "SIP编号/设备编号:", value: "D50F2049010B" },
{ name: "接入方式:", value: "NJBJ858G68H" },
{ name: "厂商:", value: "哇哇哇哇哇" },
{ name: "添加账号:", value: "Superadmin" },
{ name: "添加时间:", value: "2022-09-09" },
{
name: "设备安装位置:",
value: "江西省南昌县小蓝经开区江西飞尚科技有限公司",
difference: "line",
},
],
},
{
name: "复制直播地址",
liveBroadcast: [
{ name: "HLS地址", value: "" },
{ name: "FLV地址", value: "" },
{ name: "RTMP地址", value: "" },
{ name: "EZOPEN地址", value: "" },
{ name: "ONVIF地址", value: "" },
],
},
{
name: "复制回收地址",
recovery: [
{ name: "云储存地址", value: "" },
{ name: "本地储存地址", value: "" },
],
},
],
};
const styles = {
width: 180,
height: 64,
textAlign: "center",
background: "url(/assets/images/background/backGround.png)",
backgroundSize: "100% 100%",
padding: "12px 17px 12px 17px",
margin: "30px 0 0 10px",
lineHeight: "24px",
overflow: "hidden",
textOverflow: "ellipsis",
display: "-webkit-box",
WebkitLineClamp: "2",
WebkitBoxOrient: "vertical",
zIndex: 5,
};
useEffect(() => {}, []);
return (
<SideSheet
visible={visible}
title={cameraSetup ? "污水管理出出口" : "智慧设备NVR"}
style={{ background: "#F9FBFF" }}
size="large"
onCancel={() => {
close();
}}
>
<Tabs type="line">
<TabPane tab="项目信息" itemKey="1">
<div style={{ display: "flex", justifyContent: "space-evenly" }}>
{list.map((item, index) => {
return (
<div
key={item.name}
style={{
width: 200,
margin: "12px 8px",
}}
>
<div
style={{
lineHeight: "32px",
background: "#1859C1",
color: "#FFFFFF",
borderRadius: 3,
textAlign: "center",
}}
>
<img
src={`/assets/images/background/projectIcon${index}.png`}
style={{ marginRight: 10 }}
/>
{item.name}
</div>
<div style={styles}>{item.a}</div>
<div style={styles}>{item.b}</div>
<div style={styles}>{item.c}</div>
<div style={styles}>{item.d}</div>
</div>
);
})}
</div>
</TabPane>
<TabPane tab="设备信息" itemKey="2">
{(cameraSetup ? information.camera : information.nvr).map(
(item, index) => {
let str = [];
if (index > 0) {
str.push(
<img
src="/assets/images/background/straightline.png"
key={"img" + index}
alt="无法显示"
style={{ width: 872, display: "block", marginTop: 24 }}
/>
);
}
str.push(
<div
key={"name" + index}
style={{
fontWeight: "600",
color: "#1859C1",
margin: "15px 0 12px 40px",
}}
>
{item.name}
{cameraSetup ? (
index == 1 ? (
<div
style={{
display: "inline-block",
float: "right",
marginRight: 20,
}}
>
<Button style={{ marginRight: 20 }} theme="solid">
高清
</Button>
<Button>标清</Button>
</div>
) : (
""
)
) : (
""
)}
</div>
);
//
if (index == 0) {
item.basics.map((item, index) => {
if (item.difference == "line") {
str.push(
<div key={"basics" + index} style={{ marginTop: 14 }}>
<span
style={{
width: 150,
display: "inline-block",
textAlign: "right",
fontWeight: "600",
color: "rgba(0, 0, 0, 0.45)",
}}
>
{item.name}
</span>
<span style={{ fontWeight: "600", color: " #34383E" }}>
{item.value}
</span>
</div>
);
} else {
str.push(
<div
key={"basics" + index}
style={{
width: "50%",
display: "inline-block",
marginTop: 20,
}}
>
<span
style={{
width: 150,
display: "inline-block",
textAlign: "right",
fontWeight: "600",
color: "rgba(0, 0, 0, 0.45)",
}}
>
{item.name}
</span>
<span style={{ fontWeight: "600", color: " #34383E" }}>
{item.value}
</span>
</div>
);
}
return str;
});
}
///
if (index == 1) {
(cameraSetup ? item.liveBroadcast : item.access).map(
(item, index) => {
if (cameraSetup) {
str.push(
<div
key={"liveBroadcast" + index}
style={{
width: 120,
height: 130,
display: "inline-block",
borderRadius: 4,
border: "1px solid #D9D9D9",
margin: "20px 0 0 40px",
textAlign: "center",
color: clickStyle == item.name ? "white" : "",
background:
clickStyle == item.name ? "#1859C1" : "",
}}
onClick={() => {
copy(item.name);
// alert("");
setclickStyle(item.name);
}}
>
<img
src={`/assets/images/background/${
clickStyle == item.name
? "sewage_camera2"
: "sewage_camera1"
}.png`}
style={{ margin: "20px 0 8px 0" }}
/>
<div>
{item.name}
<img
src={`/assets/images/background/${
clickStyle == item.name ? "copy2" : "copy1"
}.png`}
style={{
paddingBottom: 10,
display: "inline-block",
width: 10,
height: 20,
}}
/>
</div>
</div>
);
} else {
str.push(
<div
key={"access" + index}
style={{
width: "50%",
display: "inline-block",
marginTop: 20,
}}
>
<span
style={{
width: 150,
display: "inline-block",
textAlign: "right",
fontWeight: "600",
color: "rgba(0, 0, 0, 0.45)",
}}
>
{item.name}
</span>
<span
style={{ fontWeight: "600", color: " #34383E" }}
>
{item.value}
</span>
</div>
);
}
return str;
}
);
}
///
if (index == 2) {
(cameraSetup ? item.recovery : item.CameraInformation).map(
(item, index) => {
if (cameraSetup) {
str.push(
<div
key={"liveBroadcast" + index}
style={{
width: 120,
height: 130,
display: "inline-block",
borderRadius: 4,
border: "1px solid #D9D9D9",
margin: "20px 0 0 40px",
textAlign: "center",
color: clickStyle == item.name ? "white" : "",
background:
clickStyle == item.name ? "#1859C1" : "",
}}
onClick={() => {
copy(item.name);
// alert("");
setclickStyle(item.name);
}}
>
<img
src={`/assets/images/background/${
clickStyle == item.name ? "store2" : "store1"
}.png`}
style={{ margin: "20px 0 8px 0" }}
/>
<div>
{item.name}
<img
src={`/assets/images/background/${
clickStyle == item.name ? "copy2" : "copy1"
}.png`}
style={{
paddingBottom: 10,
display: "inline-block",
width: 10,
height: 20,
}}
/>
</div>
</div>
);
} else {
str.push(
<div
key={"access" + index}
style={{
width: "40%",
display: "inline-block",
borderRadius: 4,
border: "1px solid rgba(151, 151, 151, 0.2)",
margin: "16px 0 0 36px",
color: "rgba(0, 0, 0, 0.85)",
fontWeight: "600",
}}
>
<div
style={{
padding: "8px 10px",
borderBottom:
"1px solid rgba(151, 151, 151, 0.2)",
}}
>
<img
src={`/assets/images/background/camera.png`}
style={{ marginRight: 10 }}
/>
<div
style={{
display: "inline-block",
}}
>
{item.name}
</div>
</div>
<p style={{ margin: "16px 0 0 30px " }}>
{item.value1}
</p>
<p style={{ margin: "16px 0 20px 30px " }}>
{item.value2}
</p>
</div>
);
}
return str;
}
);
}
return str;
}
)}
</TabPane>
</Tabs>
</SideSheet>
);
}
export default SideSheets;

542
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/containers/camera.jsx

@ -0,0 +1,542 @@
import React, { useState, useEffect, useRef } from "react";
import { connect } from "react-redux";
import { Button, Form, Input, Row, Table, Pagination } from "@douyinfe/semi-ui";
import "../style.less";
import CameraModal from "../components/cameraModal";
import NvrModal from "../components/nvrModal";
import Setup from "../components/setup";
import SideSheets from "../components/sideSheet";
const CameraHeader = (props) => {
const { dispatch, actions, user, loading, equipmentWarehouseNvr } = props;
const [cameraModal, setCameraModal] = useState(false);
const [modalName, setModalName] = useState("");
const [setup, setSetup] = useState(false);
const [sideSheet, setSideSheet] = useState(false);
const [cameraSetup, setcameraSetup] = useState(false);
const [setupp, setSetupp] = useState([]);
const [venderList, setvenderList] = useState([]); //
const [query, setQuery] = useState({ limit: 10, page: 0 });
const { equipmentWarehouse } = actions;
const api = useRef();
const CAMERAS = "cameras";
useEffect(() => {
dispatch(actions.equipmentWarehouse.getVender()).then((res) => {
setvenderList(res.payload.data);
});
//
localStorage.getItem(CAMERAS) == null
? localStorage.setItem(
CAMERAS,
JSON.stringify(["state", "type", "manufactor"])
)
: "";
attribute();
}, []);
useEffect(() => {
equipmentGetNvr();
}, [query]);
function equipmentGetNvr() {
dispatch(equipmentWarehouse.getNvr(query));
}
const columns = [
{
title: "序号",
render: (text, record, index) => {
return index + 1;
},
},
{
title: "设备名称",
dataIndex: "name",
render: (text, record, index) => {
return (
<div>
<span
style={{
backgroundColor: record.avatarBg,
width: "10px",
height: "10px",
borderRadius: "50%",
display: "inline-block",
marginRight: "10px",
}}
/>
{record.name}
</div>
);
},
},
{
title: "操作",
width: "20%",
dataIndex: "",
render: (_, row) => {
// console.log(row);
return (
<div style={{ display: "flex" }}>
<Button theme="borderless">
<NvrModal
nvrData={row}
modalName="revise"
venderList={venderList}
close={() => {
equipmentGetNvr();
}}
/>
</Button>
<Button
theme="borderless"
onClick={() => {
setSideSheet(true);
setcameraSetup(true);
}}
>
查看
</Button>
<Button theme="borderless">播放</Button>
<Button theme="borderless">禁用</Button>
<Button
theme="borderless"
onClick={() => {
dispatch(equipmentWarehouse.delNvr(row.id));
equipmentGetNvr();
}}
>
删除
</Button>
</div>
);
},
},
];
//
function attribute() {
const arr = localStorage.getItem(CAMERAS)
? JSON.parse(localStorage.getItem(CAMERAS))
: [];
const column = [
{
title: "设备厂家",
dataIndex: "venderId",
key: "manufactor",
},
{
title: "接入类型",
dataIndex: "createUserId",
key: "type",
},
{
title: "设备状态",
dataIndex: "channelCount",
key: "state",
},
{
title: "云台支持",
dataIndex: "port",
key: "support",
},
{
title: "内存卡信息",
dataIndex: "size",
key: "memoryCard",
},
{
title: "设备创建时间",
dataIndex: "createTime",
key: "time",
},
{
title: "设备添加账号",
dataIndex: "size",
key: "account",
},
{
title: "项目名称",
dataIndex: "updateTime",
key: "name",
},
{
title: "pcode",
dataIndex: "updateTime",
key: "pcode",
},
{
title: "结构物",
dataIndex: "updateTime",
key: "structure",
},
{
title: "测点",
dataIndex: "updateTime",
key: "measuringPoint",
},
{
title: "监测因素",
dataIndex: "updateTime",
key: "factor",
},
];
for (let i = 0; i < arr.length; i++) {
let colum = column.filter((item) => {
return item.key === arr[i];
});
columns.splice(i + 2, 0, colum[0]);
}
setSetupp(columns);
}
//
const screen = {
width: 193,
marginRight: 20,
marginBottom: 16,
color: "rgba(0, 0, 0, 0.65)",
};
return (
<>
<div style={{ position: "" }}>
<video
id="cameraBanner"
autoPlay
loop
muted
style={{ width: "100%", objectFit: "cover", height: 171 }}
src="/assets/video/camera_banner.mp4"
type="video/mp4"
/>
<div style={{ position: "absolute", top: 12 }}>
<div
style={{
fontSize: 22,
paddingTop: 15,
marginLeft: 21,
}}
>
摄像头管理
</div>
<div
style={{
fontSize: 14,
paddingTop: 18,
marginLeft: 20,
}}
>
对监控摄像设备设备添加修改删除的硬件管理页面
</div>
<div
style={{
fontSize: 14,
marginTop: 28,
marginLeft: 21,
width: 89,
height: 32,
lineHeight: 32 + "px",
textAlign: "center",
backgroundColor: "#D9EAFF",
color: "#1859C1",
cursor: "pointer",
}}
onClick={() => {
setModalName("add");
setCameraModal(true);
}}
>
添加摄像头
</div>
</div>
</div>
<div
style={{
width: "100%",
background: "#FFFFFF",
borderRadius: 3,
padding: "8px 20px",
marginTop: 20,
}}
>
<div
style={{
height: 22,
fontSize: 16,
fontFamily: "PingFangSC-Medium, PingFang SC",
fontWeight: "bold",
color: " rgba(0, 0, 0, 0.85)",
lineHeight: "22px",
marginBottom: 16,
}}
>
筛选条件
</div>
<div style={{ display: "flex" }}>
<Form
onSubmit={(values) => console.log(values)}
// onValueChange={values=>console.log(values)}
getFormApi={(formApi) => (api.current = formApi)}
layout="horizontal"
style={{ position: "relative", width: "100%", flex: 1 }}
>
<Form.Input
label="设备搜索:"
field="name"
placeholder="请输入设备名称"
labelPosition="left"
style={screen}
/>
<Form.Select
label="接入类型:"
labelPosition="left"
field="type1"
style={screen}
placeholder="全部"
>
<Form.Select.Option value="12">111</Form.Select.Option>
<Form.Select.Option value="11">111111</Form.Select.Option>
</Form.Select>
<Form.Select
label="厂家筛选:"
labelPosition="left"
style={screen}
field="venderId"
placeholder="全部"
>
{venderList.map((item) => {
return (
<Form.Select.Option key={item.id} value={item.id}>
{item.name}
</Form.Select.Option>
);
})}
</Form.Select>
<Form.Select
label="状态查询:"
labelPosition="left"
field="type2"
style={screen}
placeholder="全部"
>
<Form.Select.Option value="yes">在线</Form.Select.Option>
<Form.Select.Option value="no">离线</Form.Select.Option>
</Form.Select>
{/* <Form.Select
label="关联项目:"
labelPosition="left"
field="type3"
style={screen}
placeholder="全部"
>
<Form.Select.Option value="智慧环保">
飞尚科技1
</Form.Select.Option>
<Form.Select.Option value="智慧水务">
飞尚科技2
</Form.Select.Option>
</Form.Select> */}
</Form>
<div
style={{
width: 150,
display: "flex",
justifyContent: "flex-end",
alignItems: "flex-end",
}}
>
<Button
theme="solid"
type="primary"
style={{
width: 65,
height: 30,
borderRadius: 3,
marginBottom: 20,
marginRight: 20,
}}
onClick={() => {
api.current.validate().then((values) => {
console.log(values);
console.log(
equipmentWarehouseNvr.data.filter((item) => {
return (
item.name.indexOf(values.name) > -1 &&
item.venderId === values.venderId
);
// return item.venderId === values.venderId;
})
);
});
console.log(equipmentWarehouseNvr.data);
}}
>
搜素
</Button>
<Button
theme="light"
type="primary"
style={{
width: 65,
height: 30,
backGround: "#FFFFFF",
borderRadius: 3,
border: "1px solid #D9D9D9",
marginBottom: 20,
}}
>
重置
</Button>
</div>
</div>
</div>
<div style={{ background: "#FFFFFF", marginTop: 5 }}>
<div
style={{
width: "100%",
display: "flex",
justifyContent: "space-between",
padding: "13px 20px",
}}
>
<div
style={{
width: 64,
height: 22,
fontSize: 16,
fontfAmily: "PingFangSC-Medium, PingFang SC",
fontWeight: "bold",
color: "rgba(0, 0, 0, 0.85)",
lineHeight: "22px",
}}
>
设备列表
</div>
<div>
<Button
style={{
width: 32,
height: 32,
background: "#D9D9D9",
borderadius: 3,
marginRight: 20,
}}
type="primary"
key="primary"
onClick={() => {
setSetup(true);
setcameraSetup(true);
}}
>
<img
src="/assets/images/background/setup.png"
alt="设置"
style={{ width: 18, height: 18 }}
/>
</Button>
<Button
style={{
width: 65,
height: 32,
background: "#FFFFFF",
borderRadius: 3,
border: "1px solid #1859C1",
}}
>
导出
</Button>
</div>
</div>
<Table
columns={setupp}
dataSource={equipmentWarehouseNvr.data}
bordered={false}
empty="暂无数据"
style={{
padding: "0px 20px",
}}
pagination={false}
/>
<div
style={{
display: "flex",
justifyContent: "flex-end",
padding: "20px 20px",
}}
>
<span style={{ lineHeight: "30px" }}>
{equipmentWarehouseNvr.total}个设备
</span>
<Pagination
className="22"
total={equipmentWarehouseNvr.total}
showSizeChanger
currentPage={query.page + 1}
pageSizeOpts={[10, 20, 30, 40]}
onChange={(currentPage, pageSize) => {
setQuery({ limit: pageSize, page: currentPage - 1 });
}}
/>
</div>
{setup ? (
<Setup
visible={true}
CAMERAS={CAMERAS}
cameraSetup={cameraSetup}
close={() => {
setSetup(false);
attribute();
setcameraSetup(false);
}}
/>
) : (
""
)}
{sideSheet ? (
<SideSheets
visible={true}
cameraSetup={cameraSetup}
close={() => {
setSideSheet(false);
setcameraSetup(false);
}}
/>
) : (
[]
)}
</div>
{cameraModal ? (
<CameraModal
visible={true}
close={() => {
setCameraModal(false);
// setEditData(null)
}}
modalName={modalName}
/>
) : (
""
)}
</>
);
};
function mapStateToProps(state) {
const { auth, global, members, equipmentWarehouseNvr } = state;
return {
loading: members.isRequesting,
user: auth.user,
actions: global.actions,
members: members.data,
equipmentWarehouseNvr: equipmentWarehouseNvr.data || [],
};
}
export default connect(mapStateToProps)(CameraHeader);

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

@ -0,0 +1,5 @@
'use strict';
import Nvr from './nvr';
import Camera from './camera';
export { Nvr,Camera };

495
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/containers/nvr.jsx

@ -0,0 +1,495 @@
import React, { useState, useEffect, useRef } from "react";
import { connect } from "react-redux";
import moment from "moment";
import { Button, Form, Input, Row, Table, Pagination } from "@douyinfe/semi-ui";
import "../style.less";
import NvrModal from "../components/nvrModal";
import Setup from "../components/setup";
import SideSheets from "../components/sideSheet";
const NvrHeader = (props) => {
const { dispatch, actions, user, loading, equipmentWarehouseNvr } = props;
const { equipmentWarehouse } = actions;
const [setup, setSetup] = useState(false);
const [sideSheet, setSideSheet] = useState(false);
const [setupp, setSetupp] = useState([]);
const [venderList, setvenderList] = useState([]); //
const [query, setQuery] = useState({ limit: 10, page: 0 });
const api = useRef();
const SETUPS = "setups";
useEffect(() => {
dispatch(actions.equipmentWarehouse.getVender()).then((res) => {
setvenderList(res.payload.data);
});
//
localStorage.getItem(SETUPS) == null
? localStorage.setItem(SETUPS, JSON.stringify(["a", "c", "d", "e"]))
: "";
// ;
attribute();
}, []);
useEffect(() => {
equipmentGetNvr();
}, [query]);
function equipmentGetNvr() {
dispatch(equipmentWarehouse.getNvr(query));
}
const columns = [
{
title: "序号",
render: (text, record, index) => {
return index + 1;
},
},
{
title: "设备名称",
dataIndex: "name",
render: (text, record, index) => {
return (
<div>
<span
style={{
backgroundColor: record.avatarBg,
width: "10px",
height: "10px",
borderRadius: "50%",
display: "inline-block",
marginRight: "10px",
}}
/>
{record.name}
</div>
);
},
},
{
title: "SIP地址",
dataIndex: "owner",
},
{
title: "操作",
width: "20%",
dataIndex: "",
render: (_, row) => {
// console.log(row);
return (
<div style={{ display: "flex" }}>
<Button theme="borderless">
<NvrModal
nvrData={row}
modalName="revise"
venderList={venderList}
close={() => {
equipmentGetNvr();
}}
/>
</Button>
<Button
theme="borderless"
onClick={() => {
setSideSheet(true);
}}
>
查看
</Button>
<Button
theme="borderless"
onClick={() => {
dispatch(equipmentWarehouse.delNvr(row.id));
equipmentGetNvr();
}}
>
删除
</Button>
</div>
);
},
},
];
//
function attribute() {
const arr = localStorage.getItem(SETUPS)
? JSON.parse(localStorage.getItem(SETUPS))
: [];
const column = [
{
title: "设备厂家",
dataIndex: "venderId",
key: "a",
},
{
title: "添加账号",
dataIndex: "createUserId",
key: "b",
},
{
title: "通道数",
dataIndex: "channelCount",
key: "c",
},
{
title: "端口",
dataIndex: "port",
key: "d",
},
{
title: "设备状态",
dataIndex: "size",
key: "e",
},
{
title: "创建时间",
dataIndex: "createTime",
key: "f",
render: (text, r, index) => {
return moment(r.createTime).format("YYYY-MM-DD HH:MM:SS");
},
},
{
title: "项目名称",
dataIndex: "size",
key: "g",
},
{
title: "things名称",
dataIndex: "updateTime",
key: "h",
},
{
title: "things数量",
dataIndex: "updateTime",
key: "i",
},
];
for (let i = 0; i < arr.length; i++) {
let colum = column.filter((item) => {
return item.key === arr[i];
});
columns.splice(i + 2, 0, colum[0]);
}
setSetupp(columns);
}
//
const screen = {
width: 193,
marginRight: 20,
marginBottom: 16,
color: "rgba(0, 0, 0, 0.65)",
};
return (
<>
<div style={{ position: "" }}>
<video
id="nvrBanner"
autoPlay
loop
muted
style={{ width: "100%", objectFit: "cover", height: 171 }}
src="/assets/video/nvr_banner.mp4"
type="video/mp4"
/>
<div style={{ position: "absolute", top: 12 }}>
<div
style={{
fontSize: 22,
paddingTop: 15,
marginLeft: 21,
}}
>
NVR管理
</div>
<div
style={{
fontSize: 14,
paddingTop: 18,
marginLeft: 20,
}}
>
对NVR网络硬盘录像机设备节点的管理
</div>
<div
style={{
fontSize: 14,
marginTop: 28,
marginLeft: 21,
width: 89,
height: 32,
lineHeight: "32px",
textAlign: "center",
backgroundColor: "#D9EAFF",
color: "#1859C1",
cursor: "pointer",
}}
>
<NvrModal
modalName="add"
venderList={venderList}
close={() => {
equipmentGetNvr();
}}
/>
</div>
</div>
</div>
<div
style={{
width: "100%",
background: "#FFFFFF",
borderRadius: 3,
padding: "8px 20px",
marginTop: 20,
}}
>
<div
style={{
height: 22,
fontSize: 16,
fontFamily: "PingFangSC-Medium, PingFang SC",
fontWeight: "bold",
color: " rgba(0, 0, 0, 0.85)",
lineHeight: "22px",
marginBottom: 16,
}}
>
筛选条件
</div>
<div style={{ display: "flex" }}>
<Form
onSubmit={(values) => console.log(values)}
// onValueChange={values=>console.log(values)}
getFormApi={(formApi) => (api.current = formApi)}
layout="horizontal"
style={{ position: "relative", width: "100%", flex: 1 }}
>
<Form.Input
label="设备搜索:"
field="name"
placeholder="请输入设备名称"
labelPosition="left"
style={screen}
/>
<Form.Select
label="厂家筛选:"
labelPosition="left"
style={screen}
field="venderId"
placeholder="全部"
>
{venderList.map((item) => {
return (
<Form.Select.Option key={item.id} value={item.id}>
{item.name}
</Form.Select.Option>
);
})}
</Form.Select>
<Form.Select
label="状态查询:"
labelPosition="left"
field="type2"
style={screen}
placeholder="全部"
>
<Form.Select.Option value="yes">在线</Form.Select.Option>
<Form.Select.Option value="no">离线</Form.Select.Option>
</Form.Select>
{/* <Form.Select
label="关联项目:"
labelPosition="left"
field="type3"
style={screen}
placeholder="全部"
>
<Form.Select.Option value="智慧环保">
飞尚科技1
</Form.Select.Option>
<Form.Select.Option value="智慧水务">
飞尚科技2
</Form.Select.Option>
</Form.Select> */}
</Form>
<div
style={{
width: 150,
display: "flex",
justifyContent: "flex-end",
alignItems: "flex-end",
}}
>
<Button
theme="solid"
type="primary"
style={{
width: 65,
height: 30,
borderRadius: 3,
marginBottom: 20,
marginRight: 20,
}}
onClick={() => {
api.current.validate().then((values) => {
console.log(values);
console.log(
equipmentWarehouseNvr.data.filter((item) => {
return (
item.name.indexOf(values.name) > -1 &&
item.venderId === values.venderId
);
})
);
});
console.log(equipmentWarehouseNvr.data);
}}
>
搜素
</Button>
<Button
theme="light"
type="primary"
style={{
width: 65,
height: 30,
backGround: "#FFFFFF",
borderRadius: 3,
border: "1px solid #D9D9D9",
marginBottom: 20,
}}
>
重置
</Button>
</div>
</div>
</div>
<div style={{ background: "#FFFFFF", marginTop: 5 }}>
<div
style={{
width: "100%",
display: "flex",
justifyContent: "space-between",
padding: "13px 20px",
}}
>
<div
style={{
width: 64,
height: 22,
fontSize: 16,
fontfAmily: "PingFangSC-Medium, PingFang SC",
fontWeight: "bold",
color: "rgba(0, 0, 0, 0.85)",
lineHeight: "22px",
}}
>
设备列表
</div>
<div>
<Button
style={{
width: 32,
height: 32,
background: "#D9D9D9",
borderadius: 3,
marginRight: 20,
}}
type="primary"
key="primary"
onClick={() => {
setSetup(true);
}}
>
<img
src="/assets/images/background/setup.png"
alt="设置"
style={{ width: 18, height: 18 }}
/>
</Button>
<Button
style={{
width: 65,
height: 32,
background: "#FFFFFF",
borderRadius: 3,
border: "1px solid #1859C1",
}}
>
导出
</Button>
</div>
</div>
<Table
columns={setupp}
dataSource={equipmentWarehouseNvr.data}
bordered={false}
empty="暂无数据"
style={{
padding: "0px 20px",
}}
pagination={false}
/>
<div
style={{
display: "flex",
justifyContent: "flex-end",
padding: "20px 20px",
}}
>
<span style={{ lineHeight: "30px" }}>
{equipmentWarehouseNvr.total}个设备
</span>
<Pagination
total={equipmentWarehouseNvr.total}
showSizeChanger
currentPage={query.page + 1}
pageSizeOpts={[10, 20, 30, 40]}
onChange={(currentPage, pageSize) => {
setQuery({ limit: pageSize, page: currentPage - 1 });
}}
/>
</div>
{setup ? (
<Setup
visible={true}
SETUPS={SETUPS}
close={() => {
setSetup(false);
attribute();
}}
/>
) : (
""
)}
{sideSheet ? (
<SideSheets
visible={true}
close={() => {
setSideSheet(false);
}}
/>
) : (
[]
)}
</div>
</>
);
};
function mapStateToProps(state) {
const { auth, global, members, equipmentWarehouseNvr } = state;
return {
loading: members.isRequesting,
user: auth.user,
actions: global.actions,
members: members.data,
equipmentWarehouseNvr: equipmentWarehouseNvr.data || [],
};
}
export default connect(mapStateToProps)(NvrHeader);

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

16
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/nav-item.jsx

@ -0,0 +1,16 @@
import React from 'react';
import { IconCode } from '@douyinfe/semi-icons';
export function getNavItem (user, dispatch) {
return (
[
{
itemKey: 'equipmentWarehouse', text: '设备仓库', icon: <IconCode />,
items: [
{ itemKey: 'nvr', to: '/equipmentWarehouse/nvr', text: 'NVR管理' },
{ itemKey: 'camera', to: '/equipmentWarehouse/camera', text: '摄像头管理' },
]
},
]
);
}

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

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

23
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/routes.js

@ -0,0 +1,23 @@
'use strict';
import { Nvr,Camera } from './containers';
export default [{
type: 'inner',
route: {
path: '/equipmentWarehouse',
key: 'equipmentWarehouse',
breadcrumb: '设备仓库',
// 不设置 component 则面包屑禁止跳转
childRoutes: [{
path: '/nvr',
key: 'nvr',
component: Nvr,
breadcrumb: 'NVR管理',
},{
path: '/camera',
key: 'camera',
component: Camera,
breadcrumb: '摄像头管理',
}]
}
}];

0
code/VideoAccess-VCMP/web/client/src/sections/equipmentWarehouse/style.less

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

@ -5,13 +5,25 @@ import '../style.less'
const { Meta } = Card;
const Example = (props) => {
const { dispatch, actions, user, loading } = props
const { dispatch, actions, user, loading, socket } = props
useEffect(() => {
// ACTION
dispatch(actions.example.getMembers(user.orgId))
}, [])
// websocket 使
useEffect(() => {
if (socket) {
socket.on('TEST', function (msg) {
console.info(msg);
});
return () => {
socket.off("TEST");
}
}
}, [socket])
return (
<Spin tip="biubiubiu~" spinning={loading}>
<div id='example'>
@ -33,12 +45,13 @@ const Example = (props) => {
}
function mapStateToProps (state) {
const { auth, global, members } = state;
const { auth, global, members, webSocket } = state;
return {
loading: members.isRequesting,
user: auth.user,
actions: global.actions,
members: members.data
members: members.data,
socket: webSocket.socket
};
}

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

@ -1,11 +1,14 @@
'use strict';
import Func from './func';
import { isAuthorized } from './func';
import { AuthorizationCode } from './authCode';
import { ApiTable, RouteTable } from './webapi'
import { ApiTable, RouteTable, AuthRequest } from './webapi'
export {
Func,
isAuthorized,
AuthorizationCode,
ApiTable,
RouteTable,
AuthorizationCode,
AuthRequest,
}

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

@ -1,10 +1,18 @@
'use strict';
import { ProxyRequest } from '@peace/utils'
export const AuthRequest = new ProxyRequest('_auth')
export const ApiTable = {
login: 'login',
logout: 'logout',
getEnterprisesMembers: 'enterprises/{enterpriseId}/members',
getNvr:'nvr',
getVender:'vender',
nvr:'nvr',
delNvr:'nvr/{nvrId}',
};
export const RouteTable = {

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

@ -12,10 +12,12 @@ dev && console.log('\x1B[33m%s\x1b[0m', '请遵循并及时更新 readme.md,
// // 启动参数
args.option(['p', 'port'], '启动端口');
args.option(['u', 'api-url'], 'webapi的URL');
args.option('apiAuthUrl', 'apiAuthUrl');
const flags = args.parse(process.argv);
const API_URL = process.env.API_URL || flags.apiUrl;
const API_AUTH_URL = process.env.API_AUTH_URL || flags.apiAuthUrl;
if (!API_URL) {
console.log('缺少启动参数,异常退出');
@ -32,6 +34,12 @@ const product = {
host: API_URL,
match: /^\/_api\//,
}
}, {
entry: require('./middlewares/proxy').entry,
opts: {
host: API_AUTH_URL,
match: /^\/_auth\//,
}
}, {
entry: require('./routes').entry,
opts: {

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save