Browse Source

(+) 用户、权限管理及操作日志

master
liujiangyong 10 months ago
parent
commit
afc6a6dd34
  1. 31
      api/app/lib/controllers/auth/index.js
  2. 10
      api/app/lib/controllers/bigScreen/index .js
  3. 49
      api/app/lib/controllers/operationLogs/index.js
  4. 102
      api/app/lib/controllers/organization/authority.js
  5. 204
      api/app/lib/controllers/organization/user.js
  6. 27
      api/app/lib/index.js
  7. 74
      api/app/lib/models/operation_log.js
  8. 43
      api/app/lib/models/resource.js
  9. 9
      api/app/lib/models/user.js
  10. 51
      api/app/lib/models/user_resource.js
  11. 0
      api/app/lib/routes/bigScreen/index.js
  12. 19
      api/app/lib/routes/operationLogs/index.js
  13. 28
      api/app/lib/routes/organization/authority.js
  14. 22
      api/app/lib/routes/organization/user.js
  15. 16
      scripts/0.4/data/1.add_user.sql
  16. 47
      scripts/0.4/data/2.add_user_resource.sql
  17. 30
      scripts/0.4/schema/1.create_organization_table.sql
  18. 22
      scripts/0.4/schema/2.create_operation_log.sql
  19. 4
      web/client/src/app.js
  20. 13
      web/client/src/layout/components/header/index.js
  21. 21
      web/client/src/sections/auth/containers/login.js
  22. 2
      web/client/src/sections/bigScreen/components/basis/right_2.js
  23. 5
      web/client/src/sections/bigScreen/components/electrity/realTimeStatus.js
  24. 46
      web/client/src/sections/bigScreen/components/header.js
  25. 12
      web/client/src/sections/bigScreen/containers/systemManagement.js
  26. 7
      web/client/src/sections/operationLogs/actions/index.js
  27. 20
      web/client/src/sections/operationLogs/actions/operationLogs.js
  28. 5
      web/client/src/sections/operationLogs/containers/index.js
  29. 93
      web/client/src/sections/operationLogs/containers/operationLogs.js
  30. 15
      web/client/src/sections/operationLogs/index.js
  31. 20
      web/client/src/sections/operationLogs/nav-item.js
  32. 5
      web/client/src/sections/operationLogs/reducers/index.js
  33. 14
      web/client/src/sections/operationLogs/routes.js
  34. 51
      web/client/src/sections/organization/actions/authority.js
  35. 9
      web/client/src/sections/organization/actions/index.js
  36. 66
      web/client/src/sections/organization/actions/user.js
  37. 74
      web/client/src/sections/organization/components/resetPwd.js
  38. 120
      web/client/src/sections/organization/components/resource.js
  39. 93
      web/client/src/sections/organization/components/userModal.js
  40. 109
      web/client/src/sections/organization/containers/authority.js
  41. 6
      web/client/src/sections/organization/containers/index.js
  42. 186
      web/client/src/sections/organization/containers/user.js
  43. 15
      web/client/src/sections/organization/index.js
  44. 28
      web/client/src/sections/organization/nav-item.js
  45. 5
      web/client/src/sections/organization/reducers/index.js
  46. 26
      web/client/src/sections/organization/routes.js
  47. 4
      web/client/src/utils/func.js
  48. 14
      web/client/src/utils/webapi.js

31
api/app/lib/controllers/auth/index.js

@ -16,10 +16,13 @@ async function login (ctx, next) {
where: { where: {
username: params.username, username: params.username,
password: password, password: password,
} },
include: [{
attributes: ["resourceCode"],
model: models.UserResource
}]
}); });
if (userRes) { if (userRes) {
const token = uuid.v4(); const token = uuid.v4();
const userInfo = { const userInfo = {
@ -35,6 +38,15 @@ async function login (ctx, next) {
userInfo: userInfo, userInfo: userInfo,
expired: expired expired: expired
}); });
await models.OperationLog.create({
time: new Date().getTime(),
clientType: ctx.header['user-agent'],
content: '登录',
parameter: null,
userId: userInfo.id,
})
ctx.status = 200; ctx.status = 200;
ctx.body = userInfo ctx.body = userInfo
} else { } else {
@ -109,12 +121,27 @@ async function logout (ctx) {
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
const params = ctx.request.body; const params = ctx.request.body;
const userInfo = await models.UserToken.findOne({
where: {
token: params.token,
},
attributes: ["userInfo"],
raw: true,
});
await models.UserToken.destroy({ await models.UserToken.destroy({
where: { where: {
token: params.token, token: params.token,
} }
}); });
await models.OperationLog.create({
time: new Date().getTime(),
clientType: ctx.header['user-agent'],
content: '登出',
parameter: null,
userId: userInfo.userInfo.id,
})
ctx.status = 204; ctx.status = 204;
} catch (error) { } catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);

10
api/app/lib/controllers/bigScreen/index .js

@ -153,10 +153,20 @@ async function getInstances (ctx, next) {
async function getCapabilitiesInvoke(ctx, next) { async function getCapabilitiesInvoke(ctx, next) {
try { try {
const models = ctx.fs.dc.models;
let data = ctx.query; let data = ctx.query;
const res = await ctx.app.iota.request.post(`/capabilities/invoke`, data) || {} const res = await ctx.app.iota.request.post(`/capabilities/invoke`, data) || {}
const userId = ctx.fs.api.userId;
await models.OperationLog.create({
time: new Date().getTime(),
clientType: ctx.header['user-agent'],
content: `下发泵站控制指令`,
parameter: null,
userId,
})
ctx.status = 200; ctx.status = 200;
ctx.body = res.body; ctx.body = res.body;
} catch (err) { } catch (err) {

49
api/app/lib/controllers/operationLogs/index.js

@ -0,0 +1,49 @@
'use strict';
async function getLog(ctx, next) {
try {
const models = ctx.fs.dc.models;
const { limit, page, startTime, endTime, keyword } = ctx.query
let findOption = {
where: {},
order: [['time', 'DESC']],
include: [{
model: models.User,
attributes: ["name", "username"],
}],
distinct: true
}
if (limit) {
findOption.limit = Number(limit)
}
if (page && limit) {
findOption.offset = page * limit
}
if (startTime && endTime) {
findOption.where.time = { $between: [startTime, endTime] };
}
if (keyword) {
findOption.where['$or'] = {
clientType: { $like: `%${keyword}%` },
content: { $like: `%${keyword}%` },
parameter: { $like: `%${keyword}%` },
}
}
const res = await models.OperationLog.findAndCountAll(findOption)
ctx.status = 200;
ctx.body = res
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "获取操作日志失败"
}
}
}
module.exports = {
getLog,
}

102
api/app/lib/controllers/organization/authority.js

@ -0,0 +1,102 @@
async function getResource(ctx, next) {
try {
const models = ctx.fs.dc.models;
const res = await models.Resource.findAll({
where: { parentCode: null },
include: [{
model: models.Resource,
}]
})
ctx.body = res;
ctx.status = 200;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "查询所有权限数据失败"
}
}
}
async function getUserResource(ctx, next) {
try {
const models = ctx.fs.dc.models;
const { userId } = ctx.query;
const res = await models.UserResource.findAll({
where: { userId: userId },
include: [{
model: models.Resource,
}]
})
ctx.body = res;
ctx.status = 200;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "查询用户权限数据失败"
}
}
}
async function updateUserRes(ctx, next) {
const transaction = await ctx.fs.dc.orm.transaction();
try {
const models = ctx.fs.dc.models;
const { userId, resCode } = ctx.request.body;
const res = await models.UserResource.findAll({
attributes: ["resourceCode"],
raw: true,
where: { userId: userId }
})
const addRes = resCode.filter(r => !res.some(rr => rr.resourceCode == r)).map(r => { return { userId: userId, resourceCode: r } });
const delRes = res.filter(r => !resCode.includes(r.resourceCode)).map(r => r.resourceCode);
addRes.length && await models.UserResource.bulkCreate(addRes, { transaction: transaction });
delRes.length && await models.UserResource.destroy({
where: {
resourceCode: { $in: delRes },
userId: userId
},
transaction: transaction
})
const operationUserId = ctx.fs.api.userId;
const user = await models.User.findOne({
attributes: ["name"],
where: { id: userId },
raw: true,
transaction: transaction,
})
await models.OperationLog.create({
time: new Date().getTime(),
clientType: ctx.header['user-agent'],
content: `修改 (${user.name}) 用户权限`,
parameter: null,
userId: operationUserId,
}, { transaction })
ctx.status = 204;
await transaction.commit();
} catch (error) {
await transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "更新用户权限数据失败"
}
}
}
module.exports = {
getResource,
getUserResource,
updateUserRes
};

204
api/app/lib/controllers/organization/user.js

@ -0,0 +1,204 @@
'use strict';
const Hex = require('crypto-js/enc-hex');
const MD5 = require('crypto-js/md5');
async function getUser(ctx, next) {
try {
const models = ctx.fs.dc.models;
const userRes = await models.User.findAll({
where: {
delete: false
},
attributes: { exclude: ['password'] },
order: [['id', 'asc']],
})
ctx.status = 200;
ctx.body = userRes
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "获取用户信息失败"
}
}
}
async function creatUser(ctx, next) {
let errMsg = "新建用户失败"
try {
const models = ctx.fs.dc.models;
const data = ctx.request.body;
let repeatUserNameCount = await models.User.count({
where: {
username: data.username,
delete: false
}
})
if (repeatUserNameCount) {
errMsg = '已有当前用户名'
throw errMsg
}
await models.User.create({
name: data.name,
username: data.username,
password: Hex.stringify(MD5(data.password)),
delete: false,
})
const userId = ctx.fs.api.userId;
await models.OperationLog.create({
time: new Date().getTime(),
clientType: ctx.header['user-agent'],
content: `新建用户:${data.name}`,
parameter: null,
userId,
})
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": errMsg
}
}
}
async function updateUser(ctx, next) {
let errMsg = "修改用户失败"
try {
const models = ctx.fs.dc.models;
const data = ctx.request.body;
const { id } = ctx.params;
let repeatUserNameCount = await models.User.findOne({
where: {
username: data.username,
delete: false
},
raw: true,
})
if (repeatUserNameCount && repeatUserNameCount.id != id) {
errMsg = '已有当前用户名'
throw errMsg
}
await models.User.update({
name: data.name,
username: data.username,
delete: false,
}, {
where: { id }
});
const userId = ctx.fs.api.userId;
await models.OperationLog.create({
time: new Date().getTime(),
clientType: ctx.header['user-agent'],
content: `修改 (${data.name}) 用户信息`,
parameter: null,
userId,
})
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": errMsg
}
}
}
async function deleteUser(ctx, next) {
try {
const models = ctx.fs.dc.models;
const { ids } = ctx.params;
const userIds = ids.split(',');
const delUser = await models.User.update({
delete: true,
}, {
where: {
id: { $in: userIds },
},
returning: true,
});
const userId = ctx.fs.api.userId;
const userName = delUser[1].map(item => item.name).join()
await models.OperationLog.create({
time: new Date().getTime(),
clientType: ctx.header['user-agent'],
content: `删除用户:${userName}`,
parameter: null,
userId,
})
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "删除用户失败"
}
}
}
async function resetPwd(ctx, next) {
try {
const models = ctx.fs.dc.models;
const { id } = ctx.params;
const { password } = ctx.request.body;
if (!password) {
ctx.status = 400;
ctx.body = { "message": "请输入修改密码" };
return;
}
const userRes = await models.User.findOne({
where: { id },
attributes: ['id']
});
if (userRes) {
const updateUser = await models.User.update(
{ password: Hex.stringify(MD5(password)) },
{
where: { id },
returning: true,
},
);
const userId = ctx.fs.api.userId;
await models.OperationLog.create({
time: new Date().getTime(),
clientType: ctx.header['user-agent'],
content: `修改 (${updateUser[1][0].name}) 用户密码`,
parameter: null,
userId,
})
ctx.status = 204;
} else {
ctx.status = 400;
ctx.body = {
"message": "用户不存在"
}
}
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "修改用户密码失败"
}
}
}
module.exports = {
getUser,
creatUser,
updateUser,
deleteUser,
resetPwd,
}

27
api/app/lib/index.js

@ -53,25 +53,18 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq
require(`./models/${filename}`)(dc) require(`./models/${filename}`)(dc)
}); });
// const { Department, User, UserResource, Resource, Project, Point, PatrolPlan const { User, UserResource, Resource, OperationLog, } = dc.models;
// } = dc.models;
// UserResource.belongsTo(User, { foreignKey: 'userId', targetKey: 'id' }); User.belongsToMany(Resource, { through: UserResource, foreignKey: 'userId', otherKey: 'resourceCode' });
// User.hasMany(UserResource, { foreignKey: 'userId', sourceKey: 'id' }); Resource.belongsToMany(User, { through: UserResource, foreignKey: 'resourceCode', otherKey: 'userId' });
// UserResource.belongsTo(Resource, { foreignKey: 'resourceId', targetKey: 'code' }); UserResource.belongsTo(User, { foreignKey: 'userId', targetKey: 'id' });
// Resource.hasMany(UserResource, { foreignKey: 'resourceId', sourceKey: 'code' }); User.hasMany(UserResource, { foreignKey: 'userId', sourceKey: 'id' });
// Resource.hasMany(Resource, { foreignKey: 'parentResource', sourceKey: 'code' });
// User.belongsTo(Department, { foreignKey: 'departmentId', targetKey: 'id' }); UserResource.belongsTo(Resource, { foreignKey: 'resourceCode', targetKey: 'code' });
// Department.hasMany(User, { foreignKey: 'departmentId', sourceKey: 'id' }); Resource.hasMany(UserResource, { foreignKey: 'resourceCode', sourceKey: 'code' });
Resource.hasMany(Resource, { foreignKey: 'parentCode', sourceKey: 'code' });
// Point.belongsTo(Project, { foreignKey: 'projectId', targetKey: 'id' }); OperationLog.belongsTo(User, { foreignKey: 'userId', targetKey: 'id' });
// Project.hasMany(Point, { foreignKey: 'projectId', sourceKey: 'id' }); User.hasMany(OperationLog, { foreignKey: 'userId', sourceKey: 'id' });
// PatrolPlan.belongsTo(Project, { foreignKey: 'structureId', targetKey: 'id' });
// Project.hasMany(PatrolPlan, { foreignKey: 'structureId', sourceKey: 'id' });
// PatrolPlan.belongsTo(User, { foreignKey: 'userId', targetKey: 'id' });
// User.hasMany(PatrolPlan, { foreignKey: 'userId', sourceKey: 'id' });
}; };

74
api/app/lib/models/operation_log.js

@ -0,0 +1,74 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const OperationLog = sequelize.define("operationLog", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
},
time: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: "操作时间",
primaryKey: false,
field: "time",
autoIncrement: false
},
clientType: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "客户端类型",
primaryKey: false,
field: "client_type",
autoIncrement: false
},
content: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "操作内容",
primaryKey: false,
field: "content",
autoIncrement: false
},
parameter: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "操作参数",
primaryKey: false,
field: "parameter",
autoIncrement: false
},
userId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "操作用户ID",
primaryKey: false,
field: "user_id",
autoIncrement: false,
references: {
key: "id",
model: "t_user"
}
},
}, {
tableName: "t_operation_log",
comment: "",
indexes: []
});
dc.models.OperationLog = OperationLog;
return OperationLog;
};

43
api/app/lib/models/resource.js

@ -0,0 +1,43 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const Resource = sequelize.define("resource", {
code: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "code",
autoIncrement: false,
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "name",
autoIncrement: false
},
parentCode: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "parent_code",
autoIncrement: false,
},
}, {
tableName: "t_resource",
comment: "",
indexes: []
});
dc.models.Resource = Resource;
return Resource;
};

9
api/app/lib/models/user.js

@ -42,6 +42,15 @@ module.exports = dc => {
field: "password", field: "password",
autoIncrement: false autoIncrement: false
}, },
delete: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
comment: null,
primaryKey: false,
field: "delete",
autoIncrement: false
},
}, { }, {
tableName: "t_user", tableName: "t_user",
comment: "", comment: "",

51
api/app/lib/models/user_resource.js

@ -0,0 +1,51 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const UserResource = sequelize.define("userResource", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
},
userId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "user_id",
autoIncrement: false,
references: {
key: "id",
model: "t_user"
}
},
resourceCode: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "resource_code",
autoIncrement: false,
references: {
key: "code",
model: "t_resource"
}
}
}, {
tableName: "t_user_resource",
comment: "",
indexes: []
});
dc.models.UserResource = UserResource;
return UserResource;
};

0
api/app/lib/routes/organization/index.js → api/app/lib/routes/bigScreen/index.js

19
api/app/lib/routes/operationLogs/index.js

@ -0,0 +1,19 @@
'use strict';
const operationLog = require('../../controllers/operationLogs/index');
module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/operationLog'] = { content: '获取操作日志', visible: false };
router.get('/operationLog', operationLog.getLog);
// app.fs.api.logAttr['POST/operationLog'] = { content: '创建操作日志', visible: false };
// router.post('/operationLog', operationLog.creatLog);
// app.fs.api.logAttr['PUT/operationLog/:id'] = { content: '修改操作日志', visible: false };
// router.put('/operationLog/:id', operationLog.updateLog);
// app.fs.api.logAttr['DEL/operationLog/:ids'] = { content: '删除操作日志', visible: false };
// router.del('/operationLog/:ids', operationLog.deleteLog);
};

28
api/app/lib/routes/organization/authority.js

@ -0,0 +1,28 @@
'use strict';
const Authority = require('../../controllers/organization/authority');
module.exports = function (app, router, opts) {
/**
* @api {GET} resource 查询所有权限码.
* @apiVersion 1.0.0
* @apiGroup Org
*/
app.fs.api.logAttr['GET/resource'] = { content: '查询所有权限码', visible: true };
router.get('resource', Authority.getResource);
/**
* @api {GET} user/resource 查询用户权限.
* @apiVersion 1.0.0
* @apiGroup Org
*/
app.fs.api.logAttr['GET/user/resource'] = { content: '查询用户权限', visible: true };
router.get('user/resource', Authority.getUserResource);
/**
* @api {POST} user/resource 更新用户权限.
* @apiVersion 1.0.0
* @apiGroup Org
*/
app.fs.api.logAttr['POST/user/resource'] = { content: '更新用户权限', visible: true };
router.post('user/resource', Authority.updateUserRes);
};

22
api/app/lib/routes/organization/user.js

@ -0,0 +1,22 @@
'use strict';
const user = require('../../controllers/organization/user');
module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/organization/user'] = { content: '获取用户信息', visible: false };
router.get('/organization/user', user.getUser);
app.fs.api.logAttr['POST/organization/user'] = { content: '创建用户信息', visible: false };
router.post('/organization/user', user.creatUser);
app.fs.api.logAttr['PUT/organization/user/:id'] = { content: '修改用户信息', visible: false };
router.put('/organization/user/:id', user.updateUser);
app.fs.api.logAttr['DEL/organization/user/:ids'] = { content: '删除用户信息', visible: false };
router.del('/organization/user/:ids', user.deleteUser);
app.fs.api.logAttr['PUT/organization/user/resetPwd/:id'] = { content: '重置用户密码', visible: false };
router.put('/organization/user/resetPwd/:id', user.resetPwd);
};

16
scripts/0.4/data/1.add_user.sql

@ -0,0 +1,16 @@
INSERT INTO
"public"."t_user" (
"id",
"name",
"username",
"password",
"delete"
)
VALUES
(
default,
'超级管理员',
'SuperAdmin',
'e10adc3949ba59abbe56e057f20f883e',
'f'
);

47
scripts/0.4/data/2.add_user_resource.sql

@ -0,0 +1,47 @@
INSERT INTO "public"."t_resource" VALUES ('ORG_MANAGE', '组织管理', NULL);
INSERT INTO "public"."t_resource" VALUES ('ORG_MEMBER', '用户管理', 'ORG_MANAGE');
INSERT INTO "public"."t_resource" VALUES ('ORG_AUTH', '权限配置', 'ORG_MANAGE');
INSERT INTO "public"."t_resource" VALUES ('OPERATION_LOG', '操作日志', NULL);
INSERT INTO "public"."t_resource" VALUES ('OPERATION_LOG_VIEW', '操作日志查看', 'OPERATION_LOG');
INSERT INTO "public"."t_resource" VALUES ('PUMP_CONTROL', '泵站控制', NULL);
INSERT INTO "public"."t_resource" VALUES ('PUMP_WATER_CONTROL', '泵站水泵控制', 'PUMP_CONTROL');
INSERT INTO "public"."t_resource" VALUES ('MONITORING_SCOPE', '泵站监测', NULL);
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3569', '八月湖二站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3590', '张坊电排站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3592', '东山电排站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3593', '利用站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3594', '杨家湾站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3595', '汇仁大道补水站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3596', '河下电排站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3597', '石岐补水站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3598', '石岐电排站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3599', '三山电排站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3600', '万寿湖电排站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3601', '河外电排站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3642', '沥山电排站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3652', '象湖泵站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_resource" VALUES ('PUMP_STATION_3653', '雄溪泵站', 'MONITORING_SCOPE');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'ORG_MANAGE');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'ORG_MEMBER');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'ORG_AUTH');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'OPERATION_LOG');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'OPERATION_LOG_VIEW');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_CONTROL');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_WATER_CONTROL');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'MONITORING_SCOPE');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3569');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3590');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3592');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3593');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3594');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3595');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3596');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3597');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3598');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3599');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3600');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3601');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3642');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3652');
INSERT INTO "public"."t_user_resource" VALUES (default, 1, 'PUMP_STATION_3653');

30
scripts/0.4/schema/1.create_organization_table.sql

@ -0,0 +1,30 @@
DROP TABLE IF EXISTS "public"."t_user";
CREATE TABLE "public"."t_user" (
"id" serial,
"name" varchar(64) NOT NULL,
"username" varchar(64) NOT NULL,
"password" varchar(255) NOT NULL,
"delete" bool NOT NULL DEFAULT false,
PRIMARY KEY ("id")
);
COMMENT ON COLUMN "public"."t_user"."username" IS '用户名 账号';
DROP TABLE IF EXISTS "public"."t_resource";
CREATE TABLE "public"."t_resource" (
"code" varchar(128) NOT NULL,
"name" varchar(128) NOT NULL,
"parent_code" varchar(128),
PRIMARY KEY ("code")
);
DROP TABLE IF EXISTS "public"."t_user_resource";
CREATE TABLE "public"."t_user_resource" (
"id" serial,
"user_id" int4 NOT NULL,
"resource_code" varchar(128) NOT NULL,
PRIMARY KEY ("id"),
CONSTRAINT "user_resource_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."t_user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT "user_resource_resource_code_fk" FOREIGN KEY ("resource_code") REFERENCES "public"."t_resource" ("code") ON DELETE NO ACTION ON UPDATE NO ACTION
);

22
scripts/0.4/schema/2.create_operation_log.sql

@ -0,0 +1,22 @@
DROP TABLE IF EXISTS "public"."t_operation_log";
CREATE TABLE "public"."t_operation_log" (
"id" serial,
"time" timestamptz NOT NULL,
"client_type" varchar(512) NOT NULL,
"content" varchar(255) NOT NULL,
"parameter" varchar(512),
"user_id" int4 NOT NULL,
PRIMARY KEY ("id"),
CONSTRAINT "operation_log_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."t_user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
);
COMMENT ON COLUMN "public"."t_operation_log"."time" IS '操作时间';
COMMENT ON COLUMN "public"."t_operation_log"."client_type" IS '客户端类型';
COMMENT ON COLUMN "public"."t_operation_log"."content" IS '操作内容';
COMMENT ON COLUMN "public"."t_operation_log"."parameter" IS '操作参数';
COMMENT ON COLUMN "public"."t_operation_log"."user_id" IS '操作用户ID';

4
web/client/src/app.js

@ -4,6 +4,8 @@ import React, { useEffect } from 'react';
import Layout from './layout'; import Layout from './layout';
import Auth from './sections/auth'; import Auth from './sections/auth';
import bigScreen from './sections/bigScreen'; import bigScreen from './sections/bigScreen';
import Organization from './sections/organization';
import OperationLog from './sections/operationLogs';
const App = props => { const App = props => {
const { projectName } = props const { projectName } = props
@ -15,7 +17,7 @@ const App = props => {
return ( return (
<Layout <Layout
title={projectName} title={projectName}
sections={[Auth, bigScreen]} sections={[Auth, bigScreen, Organization, OperationLog]}
/> />
) )

13
web/client/src/layout/components/header/index.js

@ -5,8 +5,9 @@ import { Link } from 'react-router-dom';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import styles from './style.css'; import styles from './style.css';
import { import {
MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined, LogoutOutlined MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Func } from '$utils';
const Header = props => { const Header = props => {
const { dispatch, history, user, pathname, toggleCollapsed, collapsed, actions } = props const { dispatch, history, user, pathname, toggleCollapsed, collapsed, actions } = props
@ -40,6 +41,12 @@ const Header = props => {
{/* <img src='/assets/images/logo.png' style={{ margin: '0 12px 4px 12px', height: 42, borderRadius: 4 }} /> */} {/* <img src='/assets/images/logo.png' style={{ margin: '0 12px 4px 12px', height: 42, borderRadius: 4 }} /> */}
泵站系统 泵站系统
</div> </div>
{Func.isAuthorized('MONITORING_SCOPE') && <a
style={{ color: '#fff', marginLeft: 16 }}
onClick={() => {
history.push(`/systemManagement`);
}}
>返回大屏</a>}
</div> </div>
<div id="nav" className={styles['header-nav']}> <div id="nav" className={styles['header-nav']}>
<Menu <Menu
@ -49,9 +56,9 @@ const Header = props => {
onClick={handelClick} onClick={handelClick}
theme={'light'} theme={'light'}
items={[{ items={[{
label: <span style={{ color: 'aliceblue' }}>{user.displayName}</span>, label: <span style={{ color: 'aliceblue' }}>{user.name}</span>,
key: "user", key: "user",
icon: <img className={styles['header-nav-user-img']} src={`/assets/images/avatar/5.png`} />, icon: <UserOutlined style={{ color: '#fff' }} />,
children: [{ children: [{
label: '退出', key: 'logout' label: '退出', key: 'logout'
}], }],

21
web/client/src/sections/auth/containers/login.js

@ -7,7 +7,6 @@ import { Request } from '@peace/utils'
import { Button, Input, Form, Row, Col, message, Tabs, Tooltip } from 'antd'; import { Button, Input, Form, Row, Col, message, Tabs, Tooltip } from 'antd';
import { login, LOGIN_ERROR } from '../actions/auth'; import { login, LOGIN_ERROR } from '../actions/auth';
import { ExclamationCircleOutlined } from '@ant-design/icons'; import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Uploads } from '$components'
import { LockOutlined, UserOutlined } from '@ant-design/icons'; import { LockOutlined, UserOutlined } from '@ant-design/icons';
import './login.less'; import './login.less';
@ -16,12 +15,6 @@ const FormItem = Form.Item;
let codCountDownInterval = null let codCountDownInterval = null
const Login = props => { const Login = props => {
const { dispatch, user, error, isRequesting } = props const { dispatch, user, error, isRequesting } = props
const [username, setUserName] = useState('')
const [password, setPassword] = useState('')
const [phone, setPhone] = useState('')
const [code, setCode] = useState('')
const [inputChanged, setInputChanged] = useState(false)
const [curTabKey, setCurTabKey] = useState(1)
const [codSending, setCodSending] = useState(false) const [codSending, setCodSending] = useState(false)
const [codCountDown, setCodeCountDown] = useState(60) const [codCountDown, setCodeCountDown] = useState(60)
const codCountDownRef = useRef(0) const codCountDownRef = useRef(0)
@ -39,7 +32,19 @@ const Login = props => {
useEffect(() => { useEffect(() => {
if (user && user.authorized) { if (user && user.authorized) {
const { userResources } = user;
const resources = userResources.map(item => item.resourceCode);
if (resources.includes('MONITORING_SCOPE')) {
dispatch(push('/systemManagement')); dispatch(push('/systemManagement'));
} else if (resources.includes('ORG_MEMBER')) {
dispatch(push('/organization/user'));
} else if (resources.includes('ORG_AUTH')) {
dispatch(push('/organization/authority'));
} else if (resources.includes('OPERATION_LOG')) {
dispatch(push('/operationLog'));
} else {
message.warn('此账号暂无权限,请联系管理员');
}
} }
}, [user]) }, [user])
@ -67,8 +72,6 @@ const Login = props => {
} }
}, [codSending]) }, [codSending])
return ( return (
<div <div
id='loginContainer' id='loginContainer'

2
web/client/src/sections/bigScreen/components/basis/right_2.js

@ -51,7 +51,7 @@ export default function Right_2({ depthWater }) {
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
name: "单位:M", name: "单位:m",
areaStyle: { areaStyle: {
color: '#FFF', color: '#FFF',
}, },

5
web/client/src/sections/bigScreen/components/electrity/realTimeStatus.js

@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import moment from 'moment' import moment from 'moment'
import { Select, Modal, Switch, Input, Button, Form, message } from 'antd'; import { Select, Modal, Switch, Input, Button, Form, message } from 'antd';
import { Func } from '$utils';
import './realTimeStatus.less' import './realTimeStatus.less'
const RealTimeStatus = ({ dispatch, actions, user, pumpId, pumpList, setPumpId, strucId, siteList, pumpOne, setPumpOne, currentSix, isMobile }) => { const RealTimeStatus = ({ dispatch, actions, user, pumpId, pumpList, setPumpId, strucId, siteList, pumpOne, setPumpOne, currentSix, isMobile }) => {
@ -78,6 +79,10 @@ const RealTimeStatus = ({ dispatch, actions, user, pumpId, pumpList, setPumpId,
}, [pumpOne]) }, [pumpOne])
const showModal = () => { const showModal = () => {
if (!Func.isAuthorized("PUMP_WATER_CONTROL")) {
message.warn('无权限');
return;
}
setPwOpen(true); setPwOpen(true);
}; };

46
web/client/src/sections/bigScreen/components/header.js

@ -1,6 +1,9 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Dropdown, message } from 'antd';
import { CaretDownOutlined, AlignLeftOutlined, LogoutOutlined } from '@ant-design/icons';
import Weather from './public/weather'; import Weather from './public/weather';
import { Func } from '$utils';
const Header = ({ dispatch, actions, user, module, setModule, history }) => { const Header = ({ dispatch, actions, user, module, setModule, history }) => {
@ -23,6 +26,35 @@ const Header = ({ dispatch, actions, user, module, setModule, history }) => {
]) ])
}; };
}, []) }, [])
const items = [
{
key: '1',
label: (
<a onClick={() => {
if (Func.isAuthorized("ORG_MEMBER")) {
history.push(`/organization/user`);
} else if (Func.isAuthorized('ORG_AUTH')) {
history.push(`/organization/authority`);
} else if (Func.isAuthorized('OPERATION_LOG')) {
history.push(`/operationLog`);
} else {
message.warn('您没有权限访问该页面')
}
}}>后台配置</a>
),
icon: <AlignLeftOutlined />,
},
{
key: '2',
label: (
<a onClick={() => {
dispatch(actions.auth.logout(user));
history.push(`/signin`);
}}>退出</a>
),
icon: <LogoutOutlined />,
},
];
return <div style={{ width: '100%', height: 160 }}> return <div style={{ width: '100%', height: 160 }}>
<div style={{ <div style={{
@ -32,21 +64,25 @@ const Header = ({ dispatch, actions, user, module, setModule, history }) => {
alignItems: 'center' alignItems: 'center'
}}> }}>
<Weather /> <Weather />
<Dropdown
menu={{
items,
}}
>
<div style={{ width: 200, color: 'white', display: 'flex', alignItems: 'center' }}> <div style={{ width: 200, color: 'white', display: 'flex', alignItems: 'center' }}>
<div style={{ <div style={{
width: 130, height: 52, width: 130, height: 52,
backgroundImage: 'url(/assets/images/monitor/user.png)', backgroundImage: 'url(/assets/images/monitor/user.png)',
backgroundSize: '100% 100%', backgroundSize: '100% 100%',
backgroundPosition: 'center', backgroundPosition: 'center',
textIndent: 45, lineHeight: '52px' textIndent: 45, lineHeight: '52px',
cursor: "pointer"
}}> }}>
{user?.name} {user?.name}
</div> </div>
<div style={{ cursor: "pointer" }} onClick={() => { <CaretDownOutlined />
dispatch(actions.auth.logout(user));
history.push(`/signin`);
}}>退出</div>
</div> </div>
</Dropdown>
</div> </div>
<div style={{ display: 'flex', justifyContent: 'center' }}> <div style={{ display: 'flex', justifyContent: 'center' }}>

12
web/client/src/sections/bigScreen/containers/systemManagement.js

@ -28,7 +28,17 @@ const SystemManagement = ({ dispatch, actions, user, history }) => {
if (tabKey) { setModule(tabKey) }; if (tabKey) { setModule(tabKey) };
dispatch(bigScreen.getPumpStation({ key: 'structure', methodType: 'get' })).then(res => { dispatch(bigScreen.getPumpStation({ key: 'structure', methodType: 'get' })).then(res => {
if (res.success) { if (res.success) {
setSiteList(res.payload.data?.map(v => ({ value: v.id, label: v.name, iotaThingId: v.iotaThingId })) || []) let userStructIds = []
for (const code of user.userResources) {
if (code.resourceCode.includes('PUMP_STATION')) {
userStructIds.push(Number(code.resourceCode.split('PUMP_STATION_')[1]))
}
}
setSiteList(
res.payload.data
?.filter(v => userStructIds.includes(v.id))
.map(v => ({ value: v.id, label: v.name, iotaThingId: v.iotaThingId })) || []
)
setshowData(res.payload.data?.map(v => ({ name: v.name, lat: v.latitude, lng: v.longitude })) || []) setshowData(res.payload.data?.map(v => ({ name: v.name, lat: v.latitude, lng: v.longitude })) || [])
setSitData(res.payload.data) setSitData(res.payload.data)
} }

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

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

20
web/client/src/sections/operationLogs/actions/operationLogs.js

@ -0,0 +1,20 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getOperationLog(query) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
actionType: 'GET_OPERATION_LOG',
url: ApiTable.getOperationLog,
query,
msg: { error: '获取操作日志失败' },
// reducer: { name: 'operationLog' }
});
}
export default {
getOperationLog,
}

5
web/client/src/sections/operationLogs/containers/index.js

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

93
web/client/src/sections/operationLogs/containers/operationLogs.js

@ -0,0 +1,93 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Button } from 'antd';
import ProTable from '@ant-design/pro-table';
import { getOperationLog, } from '../actions/operationLogs';
import moment from 'moment';
const UserManage = (props) => {
const { dispatch, clientHeight, user } = props
const [dataSource, setDataSource] = useState([]);
const [date, setDate] = useState([moment().subtract(1, 'days'), moment()]);
const columns = [
{
title: '关键字',
dataIndex: 'keyword',
hideInTable: true,
}, {
title: '时间',
dataIndex: 'time',
key: 'time',
valueType: 'dateRange',
fieldProps: {
value: date,
onChange: e => { setDate(e) }
},
ellipsis: true,
width: 150,
render: (_, record) => {
console.log(record, 'record')
return <div>{moment(record.time).format('YYYY-MM-DD HH:mm:ss')}</div>
}
}, {
title: '客户端类型 ',
dataIndex: 'clientType',
key: 'clientType',
search: false,
ellipsis: true,
}, {
title: '内容',
dataIndex: 'content',
key: 'content',
search: false,
ellipsis: true,
}, {
title: '操作用户',
dataIndex: 'user',
key: 'user',
search: false,
ellipsis: true,
width: 150,
render: (_, record) => {
return <div>{record.user?.name}</div>
}
},
];
return (
<ProTable
columns={columns}
dataSource={dataSource || []}
pagination={{ pageSize: 20, size: 'default' }}
options={false}
rowKey="id"
request={async (params = {}) => {
const res = await dispatch(getOperationLog({
limit: params?.pageSize,
page: params?.current - 1,
keyword: params?.keyword,
startTime: date ? date[0].format('YYYY-MM-DD') + ' 00:00:00' : '',
endTime: date ? date[1].format('YYYY-MM-DD') + ' 23:59:59' : '',
}));
setDataSource(res?.payload.data?.rows);
return {
...res,
total: res.payload.data.count ? res.payload.data.count : 0,
};
}}
onReset={() => { setDate([moment().subtract(1, 'days'), moment()]) }}
/>
)
}
function mapStateToProps(state) {
const { global, auth } = state;
return {
clientHeight: global.clientHeight,
user: auth.user,
};
}
export default connect(mapStateToProps)(UserManage);

15
web/client/src/sections/operationLogs/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: 'operationLog',
name: '',
reducers: reducers,
routes: routes,
actions: actions,
getNavItem: getNavItem
};

20
web/client/src/sections/operationLogs/nav-item.js

@ -0,0 +1,20 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Menu } from 'antd';
import { UnorderedListOutlined } from '@ant-design/icons';
import { Func } from '$utils';
const SubMenu = Menu.SubMenu;
export function getNavItem(user, dispatch) {
if (!Func.isAuthorized("OPERATION_LOG")) {
return null
}
return (<>
{Func.isAuthorized("OPERATION_LOG") &&
<Menu.Item key="operationLog" icon={<UnorderedListOutlined />}>
<Link to="/operationLog">操作日志</Link>
</Menu.Item>}
</>);
}

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

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

14
web/client/src/sections/operationLogs/routes.js

@ -0,0 +1,14 @@
'use strict';
import { OperationLogs } from './containers';
export default [{
type: 'inner',
route: {
path: '/operationLog',
key: 'operationLog',
breadcrumb: '操作日志',
// menuSelectKeys: ['operationLog'],
menuOpenKeys: ['operationLog'],
component: OperationLogs,
}
}];

51
web/client/src/sections/organization/actions/authority.js

@ -0,0 +1,51 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getAuthority(orgId) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
actionType: 'GET_MEMBERS',
url: `${ApiTable.getEnterprisesMembers?.replace('{enterpriseId}', orgId)}`,
msg: { error: '获取用户列表失败' },
reducer: { name: 'members' }
});
}
export function getResource(userId) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
actionType: 'GET_RESOURCE',
url: `${ApiTable.getResource}`,
msg: { error: '获取权限失败' },
reducer: { name: 'resource' }
});
}
export function getUserResource(userId) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
actionType: 'GET_USER_RESOURCE',
url: `${ApiTable.getUserResource}?userId=${userId}`,
msg: { error: '获取用户权限失败' },
reducer: { name: 'userResource' }
});
}
export function postUserRes(body) {
return dispatch => basicAction({
type: 'post',
dispatch: dispatch,
actionType: 'UPDATE_USER_RESOURCE',
url: `${ApiTable.postUserRes}`,
data: body,
msg: { success: '更新用户权限' }
});
}
export default {
getAuthority,
getResource,
getUserResource,
postUserRes
}

9
web/client/src/sections/organization/actions/index.js

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

66
web/client/src/sections/organization/actions/user.js

@ -0,0 +1,66 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getUser() {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
actionType: 'GET_USER',
url: ApiTable.getUser,
msg: { error: '获取用户信息失败' },
reducer: { name: 'users' }
});
}
export function createUser(data) {
return dispatch => basicAction({
type: 'post',
data,
dispatch: dispatch,
actionType: 'CREATE_USER',
url: ApiTable.createUser,
msg: { option: '新建用户' },
});
}
export function updateUser(id, data) {
return dispatch => basicAction({
type: 'put',
data,
dispatch: dispatch,
actionType: 'UPDATE_USER',
url: ApiTable.updateUser.replace('{id}', id),
msg: { option: '修改用户' },
});
}
export function delUser(ids) {
return dispatch => basicAction({
type: 'del',
dispatch: dispatch,
actionType: 'DEL_USER',
url: ApiTable.delUser.replace('{ids}', ids),
msg: { option: '删除用户' },
});
}
export function resetPwd(id, data) {
return dispatch => basicAction({
type: 'put',
data,
dispatch: dispatch,
actionType: 'RESET_PWD',
url: ApiTable.resetPwd.replace('{id}', id),
msg: { option: '重置用户密码' },
});
}
export default {
getUser,
createUser,
updateUser,
delUser,
resetPwd
}

74
web/client/src/sections/organization/components/resetPwd.js

@ -0,0 +1,74 @@
import React, { useRef, useState } from 'react';
import { connect } from 'react-redux';
import { Spin, Card, Modal, TreeSelect } from 'antd';
import ProForm, { ProFormText, ModalForm, ProFormSwitch, ProFormTreeSelect } from '@ant-design/pro-form';
const ResetPwd = (props) => {
const { visible, onVisibleChange, onConfirm } = props;
const formRef = useRef();
const onFinish = (values) => {
if (onConfirm) {
onConfirm(values);
}
}
return (
<Spin spinning={false}>
<ModalForm
title={'重置密码'}
visible={visible}
onVisibleChange={onVisibleChange}
onFinish={onFinish}
formRef={formRef}
destroyOnClose
>
<ProFormText.Password
name={['password']}
width="md"
label="新密码"
required
placeholder="请输入密码"
fieldProps={{
autocomplete: 'new-password'
}}
rules={[
{ required: true, message: '请填写密码' },
{ min: 6, message: '请填写至少6位密码' },
]}
/>
<ProFormText.Password
name={['checkPassword']}
width="md"
label="确认密码"
required
autocomplete='off'
placeholder="请输入密码"
rules={[
{ required: true, message: '请再次填写密码' },
{ min: 6, message: '请填写至少6位密码' },
{
validator: (rule, value, callback) => {
const pwd = formRef.current.getFieldValue('password');
if (!value) {
callback();
}
if (pwd == value) {
callback();
} else {
callback('两次输入的密码不一致');
}
}
}
]}
/>
</ModalForm>
</Spin>
)
}
function mapStateToProps(state) {
return {};
}
export default connect(mapStateToProps)(ResetPwd);

120
web/client/src/sections/organization/components/resource.js

@ -0,0 +1,120 @@
import React, { useEffect } from 'react';
import { Checkbox, Table } from 'antd';
import { useState } from 'react';
const CheckboxGroup = Checkbox.Group;
const Resource = props => {
const { roleData, userRole, userSelected, setResCode, userType } = props;
const [indeterminate, setIndeterminate] = useState({});
const [roleCheck, setRoleCheck] = useState({});//一级权限码
const [parentRoleCheck, setParentRoleCheck] = useState({}); //二级权限码
useEffect(() => {
const check = {}
const parentCheck = {}
const initInd = {}
roleData && roleData.map && roleData.map(r => {
let currentInd = false;
let sum = 0;
if (r.resources) {
check[r.code] = []
r.resources.map(child => {
if (userRole.find(code => code.resourceCode == child.code)) {
currentInd = true;
sum++;
check[r.code].push(child.code);
}
})
}
parentCheck[r.code] = r.resources.length === sum
initInd[r.code] = parentCheck[r.code] ? false : currentInd
});
setParentRoleCheck(parentCheck)
setRoleCheck(check);
setIndeterminate(initInd);
}, [userRole]);
const setResData = (role) => {
let codes = [];
// Object.keys(partRole).map(r => {
// if (partRole[r]) codes.push(r)
// })
Object.keys(role).map(r => {
if (role[r].length) {
codes.push(r);
}
codes = codes.concat(role[r])
})
setResCode(codes)
}
return (
<Table
bordered
pagination={false}
dataSource={roleData}
columns={[{
title: '功能',
key: 'name',
dataIndex: 'name',
render: (text, record) => {
const parentCode = record.code
return <Checkbox
indeterminate={indeterminate[parentCode]}
onChange={(e) => {
const currentParCheck = JSON.parse(JSON.stringify(parentRoleCheck));
currentParCheck[parentCode] = e.target.checked;
const currentCode = JSON.parse(JSON.stringify(roleCheck));
currentCode[parentCode] = e.target.checked ? roleData.find(r => r.code == parentCode).resources.map(r => r.code) : []
const currentInd = JSON.parse(JSON.stringify(indeterminate));
currentInd[parentCode] = false;
setParentRoleCheck(currentParCheck);
setRoleCheck(currentCode);
setIndeterminate(currentInd);
setResData(currentCode)
}}
checked={parentRoleCheck[parentCode] || false}
disabled={userSelected === "SuperAdmin" || userType === 4}
options={''}
>
{text}
</Checkbox>
}
}, {
title: '列表',
key: 'resources',
dataIndex: 'resources',
render: (text, record) => {
let data = [];
console.log()
text.map(s => { s.name !== "整治汇总编辑" ? data.push({ label: s.name, value: s.code }) : '' })
let parentCode = record.code;
return <CheckboxGroup
disabled={userSelected === "SuperAdmin" || userType === 4}
options={data}
value={roleCheck[parentCode] || []}
onChange={value => {
const checkArr = JSON.parse(JSON.stringify(roleCheck));
const parentCheck = JSON.parse(JSON.stringify(parentRoleCheck));
const ind = JSON.parse(JSON.stringify(indeterminate));
const currentCode = roleData.find(r => r.code == parentCode) || {}
checkArr[parentCode] = value;
ind[parentCode] = !!value.length && value.length < currentCode.resources.length
parentCheck[parentCode] = value.length === currentCode.resources.length
setRoleCheck(checkArr);
setIndeterminate(ind);
setParentRoleCheck(parentCheck);
setResData(checkArr)
}}
/>
}
}]}
></Table >
)
}
export default Resource

93
web/client/src/sections/organization/components/userModal.js

@ -0,0 +1,93 @@
import React from 'react';
import { connect } from 'react-redux';
import { Spin, Card, Modal, TreeSelect, message } from 'antd';
import ProForm, { ProFormText, ModalForm, ProFormSwitch, ProFormTreeSelect } from '@ant-design/pro-form';
const UserModal = (props) => {
const { visible, modalType, onVisibleChange, onConfirm, editData } = props
const onFinish = (values) => {
if (onConfirm) {
onConfirm(values);
}
}
return (
<Spin spinning={false}>
<ModalForm
title={modalType == 'edit' ? '编辑用户' : '新建用户'}
visible={visible}
onVisibleChange={onVisibleChange}
onFinish={onFinish}
destroyOnClose
initialValues={
modalType == 'edit' ?
{
contract: editData
} :
{
contract: {
enable: true
}
}
}
>
<ProForm.Group>
<ProFormText
name={['contract', 'name']}
maxLength={24}
width="md"
label="姓名"
required
placeholder="请输入姓名"
rules={[{ required: true, message: '请输入姓名' }]}
/>
<ProFormText
name={['contract', 'username']}
width="md"
label="用户名"
required
fieldProps={{
maxLength: 11,
}}
getValueFromEvent={(event) => {
return event.target.value.replace(/\D/g, '')
}}
placeholder="请输入用户名"
rules={[
{ required: true, }, { message: "请输入用户名" }
]}
/>
</ProForm.Group>
<ProForm.Group>
{modalType == 'edit' ? null : <ProFormText.Password
name={['contract', 'password']}
width="md"
label="密码"
required
placeholder="请输入密码"
fieldProps={{
autocomplete: 'new-password'
}}
rules={[
{ required: true, message: '请填写密码' },
{ min: 6, message: '请填写至少6位密码' },
]}
/>}
</ProForm.Group>
</ModalForm>
</Spin>
)
}
function mapStateToProps(state) {
const { } = state;
return {
};
}
export default connect(mapStateToProps)(UserModal);

109
web/client/src/sections/organization/containers/authority.js

@ -0,0 +1,109 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Spin, Row, Col, Card, Button, Tree, Empty } from 'antd';
import { getUser } from '../actions/user';
import { getResource, getUserResource, postUserRes } from '../actions/authority';
import Resource from '../components/resource';
const Authority = (props) => {
const { dispatch, loading, users, resource, userResource, clientHeight } = props
const [userSelectedKeys, setUserSelectedKeys] = useState([])
const [userSelected, setUserSelected] = useState()
const [resCode, setResCode] = useState({})
const [useName, setUseName] = useState()// 选中名字
const [userType, setUserType] = useState()
useEffect(() => {
if (!users.length) dispatch(getUser())
dispatch(getResource())
}, [])
useEffect(() => {
if (users.length) {
setUserSelectedKeys([users[0].id])
setUserSelected(users[0].username)
dispatch(getUserResource(users[0].id))
setUseName(users[0].name)
}
}, [users])
const handleSave = () => {
dispatch(postUserRes({ userId: userSelectedKeys[0], resCode: resCode })).then(res => {
if (res.success) {
dispatch(getUserResource(userSelectedKeys[0]))
}
})
}
return (
<Spin spinning={loading}>
<Row gutter={16}>
<Col span={4} style={{ height: '100%', }}>
<Card title={`用户列表`} bordered={false} bodyStyle={{ padding: 8, paddingTop: 24 }}>
{
users.length ?
<Tree
height={clientHeight - 100}
defaultSelectedKeys={[users[0].id]}
selectedKeys={userSelectedKeys}
onSelect={(selectedKeys, { selected, selectedNodes, node, event }) => {
const name = node.name
setUseName(name)
if (selected) {
setUserSelectedKeys(selectedKeys)
setUserSelected(selectedNodes[0].username || '')
dispatch(getUserResource(selectedKeys[0]))
}
}}
treeData={users}
fieldNames={{
title: 'name',
key: 'id'
}}
/> : <Empty />
}
</Card>
</Col>
<Col span={16} style={{ height: '100%', }}>
{users.length ?
<Card title={`[${useName ? useName : '管理员'}] 功能范围`} bordered={false} bodyStyle={{ padding: 8, paddingTop: 24 }}>
<Resource
userSelected={userSelected}
roleData={resource}
userRole={userResource}
setResCode={setResCode}
userType={userType}
/>
<Row type="flex" justify="center" style={{ marginBottom: 16, marginTop: 16, textAlign: 'center' }}>
<Col span="24">
<Button
disabled={userSelected === "SuperAdmin" || userType === 4}
onClick={handleSave}
style={{ width: '60%' }}
type='primary'>保存修改</Button>
</Col></Row>
</Card>
: <Card title={`[]功能范围`} bordered={false} bodyStyle={{ padding: 8, paddingTop: 24 }}>
<Empty />
</Card>
}
</Col>
</Row>
</Spin >
)
}
function mapStateToProps(state) {
const { userResource, resource, users, global } = state;
return {
clientHeight: global.clientHeight,
loading: users.isRequesting || resource.isRequesting,
userResource: userResource.data || [],
resource: resource.data || [],
users: users.data || []
};
}
export default connect(mapStateToProps)(Authority);

6
web/client/src/sections/organization/containers/index.js

@ -0,0 +1,6 @@
'use strict';
import Authority from './authority';
import UserManage from './user';
export { Authority, UserManage };

186
web/client/src/sections/organization/containers/user.js

@ -0,0 +1,186 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { EllipsisOutlined } from '@ant-design/icons';
import { Spin, Space, Button, Popconfirm, Row, Col, Tree, Table, Card, Switch } from 'antd';
import ProTable from '@ant-design/pro-table';
import { getUser, createUser, updateUser, delUser, resetPwd } from '../actions/user';
import UserModal from '../components/userModal';
import ResetPwd from '../components/resetPwd';
const TreeNode = Tree.TreeNode;
const UserManage = (props) => {
const { dispatch, loading, users, clientHeight, user } = props
const [modalVisible, setModalVisible] = useState(false);
const [modalType, setModalType] = useState();
const [modalRecord, setModalRecord] = useState();
const [pwdModalVisible, setPwdModalVisible] = useState(false);
const [rowSelected, setRowSelected] = useState([])
useEffect(() => {
dispatch(getUser())
}, [])
const columns =
[
{
title: '姓名',
dataIndex: 'name',
}, {
title: '用户名',
dataIndex: 'username',
}, {
title: '操作',
dataIndex: 'action',
render: (dom, record) => {
return record.username == 'SuperAdmin'
? user.username == 'SuperAdmin'
? <Button
type="link"
onClick={() => {
setModalRecord(record);
setPwdModalVisible(true);
}}
>重置密码</Button>
: ''
: [
<Button type="link" onClick={() => { openModal('edit', record) }}>编辑</Button>,
<Popconfirm
title="确认删除?"
onConfirm={() => {
delUsers([record.id])
}}
>
<Button type="link">删除</Button>
</Popconfirm>,
<Button
type="link"
onClick={() => {
setModalRecord(record);
setPwdModalVisible(true);
}}
>重置密码</Button>
]
},
},
];
//弹窗确认
const onConfirm = (values) => {
if (modalType == 'edit') {
dispatch(updateUser(modalRecord.id, values.contract)).then(res => {
if (res.success) {
setModalVisible(false);
dispatch(getUser());
}
});
} else {
dispatch(createUser(values.contract)).then(res => {
if (res.success) {
setModalVisible(false);
dispatch(getUser());
}
});
}
}
//打开弹窗
const openModal = (type, record) => {
setModalVisible(true);
setModalType(type);
if (type == 'edit') {
setModalRecord(record);
} else {
setModalRecord(null);
}
}
//删除用户
const delUsers = (ids, type) => {
dispatch(delUser(ids)).then(res => {
dispatch(getUser());
if (type == 'batch') {
setRowSelected([]);
}
});
}
//重置密码
const onPwdConfirm = (values) => {
dispatch(resetPwd(modalRecord.id, { password: values.password })).then(res => {
if (res.success) {
setPwdModalVisible(false);
dispatch(getUser());
}
});
}
return (
<Spin spinning={loading} /* style={{ height: "calc(100vh - 70px)" }} */>
<ProTable
columns={columns}
dataSource={users}
style={{ width: "100% ", height: clientHeight - 95, overflow: "auto" }}
rowSelection={{
selectedRowKeys: rowSelected,
onChange: (selectedRowKeys) => {
setRowSelected(selectedRowKeys);
},
getCheckboxProps: (record) => {
return {
disabled: record.username === 'SuperAdmin',
}
},
}}
options={false}
search={false}
rowKey="id"
toolBarRender={() => [
<span>
<Button
type="primary"
key="primary"
style={{ marginRight: 10 }}
onClick={() => openModal('create')}
>新建用户</Button>
<Button style={{ marginRight: 10 }} onClick={() => { dispatch(getUser()); }}>刷新</Button>
<Popconfirm title="确认删除?" onConfirm={() => { delUsers(rowSelected, 'batch') }}>
<Button>批量删除</Button>
</Popconfirm>
</span>
]}
/>
{
modalVisible ?
<UserModal
visible={modalVisible}
onVisibleChange={setModalVisible}
modalType={modalType}
onConfirm={onConfirm}
editData={modalRecord}
/> : ''
}
{pwdModalVisible ? <ResetPwd visible={pwdModalVisible}
onVisibleChange={setPwdModalVisible}
onConfirm={onPwdConfirm} /> : ''
}
</Spin>
)
}
function mapStateToProps(state) {
const { users, global, auth } = state;
return {
clientHeight: global.clientHeight,
users: users.data || [],
loading: users.isRequesting,
user: auth.user,
};
}
export default connect(mapStateToProps)(UserManage);

15
web/client/src/sections/organization/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: 'organization',
name: '',
reducers: reducers,
routes: routes,
actions: actions,
getNavItem: getNavItem
};

28
web/client/src/sections/organization/nav-item.js

@ -0,0 +1,28 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Menu } from 'antd';
import { SettingOutlined } from '@ant-design/icons';
import { Func } from '$utils';
const SubMenu = Menu.SubMenu;
export function getNavItem(user, dispatch) {
if (!Func.isAuthorized("ORG_MANAGE")) {
return null
}
return (
<SubMenu key="organization" icon={<SettingOutlined />} title={'组织管理'}>
{Func.isAuthorized("ORG_MEMBER") &&
<Menu.Item key="userManage">
<Link to="/organization/user">用户管理</Link>
</Menu.Item>
}
{Func.isAuthorized("ORG_AUTH") &&
<Menu.Item key="authority">
<Link to="/organization/authority">权限配置</Link>
</Menu.Item>
}
</SubMenu>
);
}

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

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

26
web/client/src/sections/organization/routes.js

@ -0,0 +1,26 @@
'use strict';
import { UserManage, Authority } from './containers';
export default [{
type: 'inner',
route: {
path: '/organization',
key: 'organization',
breadcrumb: '组织管理',
menuSelectKeys: ['userManage'],
menuOpenKeys: ['organization'],
childRoutes: [{
path: '/user',
key: 'userManage',
menuSelectKeys: ['userManage'],
component: UserManage,
breadcrumb: '用户管理',
}, {
path: '/authority',
key: 'authority',
component: Authority,
menuSelectKeys: ['authority'],
breadcrumb: '权限配置',
}]
}
}];

4
web/client/src/utils/func.js

@ -3,11 +3,11 @@
export default class Func { export default class Func {
static isAuthorized(authcode) { static isAuthorized(authcode) {
if (JSON.parse(sessionStorage.getItem('user'))) { if (JSON.parse(sessionStorage.getItem('user'))) {
const { resources } = JSON.parse(sessionStorage.getItem('user')); const { userResources } = JSON.parse(sessionStorage.getItem('user'));
const resources = userResources.map(item => item.resourceCode);
return resources.includes(authcode); return resources.includes(authcode);
} else { } else {
return false; return false;
} }
} }
} }

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

@ -24,6 +24,20 @@ export const ApiTable = {
pumpInformation: 'pumpInformation', pumpInformation: 'pumpInformation',
videoUrl: "videoUrl", videoUrl: "videoUrl",
// 组织管理-用户管理
getUser: 'organization/user',
createUser: 'organization/user',
updateUser: 'organization/user/{id}',
delUser: 'organization/user/{ids}',
resetPwd: 'organization/user/resetPwd/{id}',
// 用户权限
getResource: 'resource',
getUserResource: 'user/resource',
postUserRes: 'user/resource',
// 操作日志
getOperationLog: 'operationLog',
}; };

Loading…
Cancel
Save