diff --git a/api/app/lib/controllers/organization/authority.js b/api/app/lib/controllers/organization/authority.js new file mode 100644 index 0000000..bcb5a68 --- /dev/null +++ b/api/app/lib/controllers/organization/authority.js @@ -0,0 +1,87 @@ +async function getResource(ctx, next) { + try { + const models = ctx.fs.dc.models; + + const res = await models.Resource.findAll({ + where: { parentResource: 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: ["resourceId"], + raw: true, + where: { userId: userId } + }) + + const addRes = resCode.filter(r => !res.some(rr => rr.resourceId == r)).map(r => { return { userId: userId, resourceId: r } }); + const delRes = res.filter(r => !resCode.includes(r.resourceId)).map(r => r.resourceId); + addRes.length && await models.UserResource.bulkCreate(addRes, { transaction: transaction }); + delRes.length && await models.UserResource.destroy({ + where: { + resourceId: { $in: delRes }, + userId: userId + }, + transaction: 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 +}; \ No newline at end of file diff --git a/api/app/lib/controllers/organization/user.js b/api/app/lib/controllers/organization/user.js new file mode 100644 index 0000000..aa7e2e9 --- /dev/null +++ b/api/app/lib/controllers/organization/user.js @@ -0,0 +1,373 @@ +'use strict'; +const Hex = require('crypto-js/enc-hex'); +const MD5 = require('crypto-js/md5'); + +// async function getDepMessage (ctx, next) { +// try { +// const { fs: { api: { userInfo } } } = ctx +// const models = ctx.fs.dc.models; + +// let deptWhere = {} +// if (userInfo.username !== 'SuperAdmin') { +// deptWhere.id = userInfo.departmentId +// } +// let depType1 = await models.Department.findAll({ +// order: [['id', 'asc']], +// // include: [{ +// // model: models.User, +// // required: false, +// // where: { delete: false }, +// // attributes: { exclude: ['password'] }, +// // }], +// where: deptWhere, +// }) + +// let depRslt = [] +// const getDep = async (d) => { +// let subordinate = [] +// let depRes = await models.Department.findAll({ +// order: [['id', 'asc']], +// // include: [{ +// // model: models.User, +// // required: false, +// // where: { delete: false }, +// // attributes: { exclude: ['password'] }, +// // }], +// where: { +// dependence: d.id +// }, +// }) +// if (depRes.length) +// for (let d of depRes) { +// let dep = d.dataValues +// dep.subordinate = await getDep(d.dataValues) +// subordinate.push(dep) +// } +// return subordinate +// } +// for (let d of depType1) { +// let dep_1 = d.dataValues +// dep_1.subordinate = await getDep(d.dataValues) +// depRslt.push(dep_1) +// } + +// ctx.status = 200; +// ctx.body = depRslt +// } catch (error) { +// ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); +// ctx.status = 400; +// ctx.body = { +// "message": "获取部门信息失败" +// } +// } +// } + +async function getDepMessage(ctx, next) { + let error = { name: 'FindError', message: '获取部门列表失败' }; + let rslt = []; + try { + const models = ctx.fs.dc.models; + let list = await models.Department.findAll({}); + + let deptMap = [] + list.filter(l => !l.dependence).map(ld => {//一级 + deptMap.push({ + id: ld.id, + name: ld.name, + dependence: ld.dependence, + subordinate: [] + }) + }) + + list.filter(l => l.dependence).map(ld => {//二级 + let parent = deptMap.find(dm => dm.id == ld.dependence); + if (parent) { + parent.subordinate.push({ + id: ld.id, + name: ld.name, + dependence: ld.dependence, + subordinate: [] + }) + } + }) + rslt = deptMap; + + error = null; + } catch (err) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${err}`); + } + if (error) { + ctx.status = 400; + ctx.body = error; + } else { + ctx.status = 200; + ctx.body = rslt; + } +} + +async function createDept(ctx, next) { + const models = ctx.fs.dc.models; + try { + let rslt = ctx.request.body; + await models.Department.create(Object.assign({}, rslt, { read: 1 })) + ctx.status = 204; + ctx.body = { message: '新建部门成功' } + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { message: '新建部门失败' } + } +} + +async function updateDept(ctx, next) { + try { + const models = ctx.fs.dc.models; + const { id } = ctx.params; + const body = ctx.request.body; + await models.Department.update( + body, + { where: { id: id } } + ) + ctx.status = 204; + ctx.body = { message: '修改部门成功' } + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { message: '修改部门失败' } + } +} + +async function delDept(ctx, next) { + let errMsg = "删除部门失败"; + try { + const models = ctx.fs.dc.models; + const { id } = ctx.params; + let list = await models.Department.findAll({}); + let deptIds = list.map(l => l.id); + const allUsers = await models.User.findAll({ + where: { + departmentId: { $in: deptIds }, + delete: false + } + }) + const childrenDept = await models.Department.findAll({ where: { dependence: id } }) + const childrenUser = allUsers.filter(au => au.departmentId == id); + if (childrenUser.length || childrenDept.length) { + errMsg = '请先删除部门下的用户或子部门'; + throw errMsg; + } + await models.Department.destroy({ where: { id: id } }); + await models.Department.destroy({ where: { dependence: id } }); + ctx.status = 204; + ctx.body = { message: '删除部门成功' } + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { message: error } + } +} + +async function getUser(ctx, next) { + try { + const models = ctx.fs.dc.models; + const { depId } = ctx.params + const userRes = await models.User.findAll({ + where: { + departmentId: parseInt(depId), + 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.phone, + delete: false + } + }) + + if (repeatUserNameCount) { + errMsg = '已有当前用户名' + throw errMsg + } + + await models.User.create({ + name: data.name, + username: data.phone, + password: Hex.stringify(MD5(data.password)), + departmentId: data.departmentId, + email: data.email, + enable: data.enable, + delete: false, + phone: data.phone, + post: data.post, + }) + + 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.count({ + where: { + username: data.username + } + }) + + if (repeatUserNameCount) { + errMsg = '已有当前用户名' + throw errMsg + } + + await models.User.update({ + name: data.name, + username: data.phone, + departmentId: data.departmentId, + email: data.email, + enable: data.enable, + delete: false, + phone: data.phone, + post: data.post, + }, { + where: { + id: id + } + }); + + 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(','); + await models.User.update({ + delete: true, + }, { + where: { + id: { $in: userIds }, + } + }); + 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 data = ctx.request.body; + await models.User.update({ + password: Hex.stringify(MD5(data.password)), + }, { + where: { + id: id, + } + }); + ctx.status = 204; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + "message": "重置用户密码失败" + } + } +} + +/** + * 修改用户密码 + * @params {userId-用户Id} ctx + * @request.body {password-用户新密码} ctx + */ +async function updateUserPassword(ctx, next) { + try { + const models = ctx.fs.dc.models; + const { userId } = ctx.params; + const { password } = ctx.request.body; + if (!password) { + ctx.status = 400; + ctx.body = { "message": "请输入修改密码" }; + return; + } + const userRes = await models.User.findOne({ + where: { + id: userId + }, + attributes: ['id'] + }); + if (userRes) { + await models.User.update({ password: Hex.stringify(MD5(password)) }, { where: { id: 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 = { + getDepMessage, + createDept, + updateDept, + delDept, + getUser, + creatUser, + updateUser, + deleteUser, + resetPwd, + updateUserPassword +} \ No newline at end of file diff --git a/api/app/lib/models/department.js b/api/app/lib/models/department.js index 2657f84..db844db 100644 --- a/api/app/lib/models/department.js +++ b/api/app/lib/models/department.js @@ -35,9 +35,9 @@ module.exports = dc => { }, type: { type: DataTypes.INTEGER, - allowNull: false, + allowNull: true, defaultValue: null, - comment: "市1,区县2,乡镇3,村4", + comment: "", primaryKey: false, field: "type", autoIncrement: false diff --git a/api/app/lib/routes/organization/authority.js b/api/app/lib/routes/organization/authority.js new file mode 100644 index 0000000..99de6df --- /dev/null +++ b/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); +}; \ No newline at end of file diff --git a/api/app/lib/routes/organization/user.js b/api/app/lib/routes/organization/user.js new file mode 100644 index 0000000..5b63e32 --- /dev/null +++ b/api/app/lib/routes/organization/user.js @@ -0,0 +1,41 @@ +'use strict'; + +const user = require('../../controllers/organization/user'); + +module.exports = function (app, router, opts) { + + app.fs.api.logAttr['GET/organization/department'] = { content: '获取部门信息', visible: false }; + router.get('/organization/department', user.getDepMessage); + + app.fs.api.logAttr['POST/organization/dept/add'] = { content: '新增部门', visible: true }; + router.post('/organization/dept/add', user.createDept); + + app.fs.api.logAttr['PUT/organization/dept/:id/modify'] = { content: '修改部门', visible: true }; + router.put('/organization/dept/:id/modify', user.updateDept); + + app.fs.api.logAttr['DELETE/organization/dept/:id'] = { content: '删除部门', visible: true }; + router.del('/organization/dept/:id', user.delDept); + + app.fs.api.logAttr['GET/organization/department/:depId/user'] = { content: '获取部门下用户信息', visible: false }; + router.get('/organization/department/:depId/user', user.getUser); + + app.fs.api.logAttr['POST/organization/department/user'] = { content: '创建部门下用户信息', visible: false }; + router.post('/organization/department/user', user.creatUser); + + app.fs.api.logAttr['PUT/organization/department/user/:id'] = { content: '修改部门下用户信息', visible: false }; + router.put('/organization/department/user/:id', user.updateUser); + + app.fs.api.logAttr['DEL/organization/department/user/:ids'] = { content: '删除部门下用户信息', visible: false }; + router.del('/organization/department/user/:ids', user.deleteUser); + + app.fs.api.logAttr['PUT/organization/department/user/resetPwd/:id'] = { content: '重置用户密码', visible: false }; + router.put('/organization/department/user/resetPwd/:id', user.resetPwd); + + /** + * @api {PUT} user/password/:id 修改用户密码 + * @apiVersion 1.0.0 + * @apiGroup Organization + */ + app.fs.api.logAttr['PUT/user/password/:userId'] = { content: '修改用户密码', visible: false }; + router.put('/user/password/:userId', user.updateUserPassword); +}; \ No newline at end of file diff --git a/script/1.0.0/data/1.admin_user.sql b/script/1.0.0/data/1.admin_user.sql index b779175..c492d71 100644 --- a/script/1.0.0/data/1.admin_user.sql +++ b/script/1.0.0/data/1.admin_user.sql @@ -1 +1 @@ -INSERT INTO "public"."user" VALUES (1, '管理员', 'SuperAdmin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL, 't', 'f', '123456789', NULL); +INSERT INTO "public"."user" VALUES (default, '管理员', 'SuperAdmin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL, 't', 'f', '123456789', NULL); diff --git a/script/1.0.0/data/2.updata_resource_data.sql b/script/1.0.0/data/2.updata_resource_data.sql new file mode 100644 index 0000000..de022a8 --- /dev/null +++ b/script/1.0.0/data/2.updata_resource_data.sql @@ -0,0 +1,11 @@ +INSERT INTO "public"."resource" VALUES ('STRUCTURE_MANAGE', '结构物管理', NULL); +INSERT INTO "public"."resource" VALUES ('STRU_INFO_CONFIG', '基础信息维护', 'STRUCTURE_MANAGE'); +INSERT INTO "public"."resource" VALUES ('QR_CODE_CONFIG', '二维码管理', 'STRUCTURE_MANAGE'); +INSERT INTO "public"."resource" VALUES ('PATROL_MANAGE', '巡检管理', NULL); +INSERT INTO "public"."resource" VALUES ('PATROL_PLAN_CONFIG', '巡检计划制订', 'PATROL_MANAGE'); +INSERT INTO "public"."resource" VALUES ('PATROL_RECORD_VIEW', '巡检记录查看', 'PATROL_MANAGE'); +INSERT INTO "public"."resource" VALUES ('REPAIR_MANAGE', '维修处理', NULL); +INSERT INTO "public"."resource" VALUES ('HANDLE_REPAIR', '维修处理', 'REPAIR_MANAGE'); +INSERT INTO "public"."resource" VALUES ('ORG_MANAGE', '组织管理', NULL); +INSERT INTO "public"."resource" VALUES ('USER_CONFIG', '部门成员', 'ORG_MANAGE'); +INSERT INTO "public"."resource" VALUES ('AUTH_CONFIG', '权限配置', 'ORG_MANAGE'); \ No newline at end of file diff --git a/script/1.0.0/data/3.updata_user_resource_data.sql b/script/1.0.0/data/3.updata_user_resource_data.sql new file mode 100644 index 0000000..cf2c69b --- /dev/null +++ b/script/1.0.0/data/3.updata_user_resource_data.sql @@ -0,0 +1,11 @@ +INSERT INTO "public"."user_resource" VALUES (default, 1, 'STRUCTURE_MANAGE'); +INSERT INTO "public"."user_resource" VALUES (default, 1, 'STRU_INFO_CONFIG'); +INSERT INTO "public"."user_resource" VALUES (default, 1, 'QR_CODE_CONFIG'); +INSERT INTO "public"."user_resource" VALUES (default, 1, 'PATROL_MANAGE'); +INSERT INTO "public"."user_resource" VALUES (default, 1, 'PATROL_PLAN_CONFIG'); +INSERT INTO "public"."user_resource" VALUES (default, 1, 'PATROL_RECORD_VIEW'); +INSERT INTO "public"."user_resource" VALUES (default, 1, 'REPAIR_MANAGE'); +INSERT INTO "public"."user_resource" VALUES (default, 1, 'HANDLE_REPAIR'); +INSERT INTO "public"."user_resource" VALUES (default, 1, 'ORG_MANAGE'); +INSERT INTO "public"."user_resource" VALUES (default, 1, 'USER_CONFIG'); +INSERT INTO "public"."user_resource" VALUES (default, 1, 'AUTH_CONFIG'); \ No newline at end of file diff --git a/script/1.0.0/schema/1.init_inspection.sql b/script/1.0.0/schema/1.init_inspection.sql index b3b0c57..83480a9 100644 --- a/script/1.0.0/schema/1.init_inspection.sql +++ b/script/1.0.0/schema/1.init_inspection.sql @@ -69,7 +69,7 @@ CREATE TABLE "public"."department" ( "id" int4 NOT NULL DEFAULT nextval('department_id_seq'::regclass), "name" varchar(128) COLLATE "pg_catalog"."default" NOT NULL, "dependence" int4, - "type" int4 NOT NULL + "type" int4 ); COMMENT ON COLUMN "public"."department"."dependence" IS '上级部门/从属'; -- COMMENT ON COLUMN "public"."department"."type" IS '市1,区县2,乡镇3,村4'; diff --git a/web/client/src/app.js b/web/client/src/app.js index fd3f9f1..b1073a0 100644 --- a/web/client/src/app.js +++ b/web/client/src/app.js @@ -5,6 +5,7 @@ import Layout from './layout'; import Auth from './sections/auth'; import Safetymanage from './sections/safetymanage'; import ProjectRegime from './sections/projectRegime'; +import Organization from './sections/organization'; const App = props => { const { projectName } = props @@ -16,7 +17,7 @@ const App = props => { return ( ) diff --git a/web/client/src/sections/organization/actions/authority.js b/web/client/src/sections/organization/actions/authority.js new file mode 100644 index 0000000..d5f0719 --- /dev/null +++ b/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 +} \ No newline at end of file diff --git a/web/client/src/sections/organization/actions/index.js b/web/client/src/sections/organization/actions/index.js new file mode 100644 index 0000000..b44cd45 --- /dev/null +++ b/web/client/src/sections/organization/actions/index.js @@ -0,0 +1,11 @@ +'use strict'; + +import * as authority from './authority' +import { getDepMessage, getDepUser, createUser } from './user' + +export default { + ...authority, + getDepMessage, + getDepUser, + createUser, +} \ No newline at end of file diff --git a/web/client/src/sections/organization/actions/user.js b/web/client/src/sections/organization/actions/user.js new file mode 100644 index 0000000..ef2b452 --- /dev/null +++ b/web/client/src/sections/organization/actions/user.js @@ -0,0 +1,113 @@ +'use strict'; + +import { basicAction } from '@peace/utils' +import { ApiTable } from '$utils' + +export function getDepMessage() { + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + actionType: 'GET_DEPARTMENT_MESSAGE', + url: ApiTable.getDepMessage, + msg: { error: '获取部门信息失败' }, + reducer: { name: 'depMessage' } + }); +} + +//新建部门 +export function createDept(data) { + return dispatch => basicAction({ + type: 'post', + data, + dispatch: dispatch, + actionType: 'CREATE_DEPT', + url: ApiTable.createDept, + msg: { option: '新建部门' }, + }); +} + +//修改部门 +export function updateDept(id, data) { + return dispatch => basicAction({ + type: 'put', + data, + dispatch: dispatch, + actionType: 'UPDATE_DEPT', + url: ApiTable.updateDept.replace('{id}', id), + msg: { option: '修改部门' }, + }); +} + +//删除部门 +export function delDept(id) { + return dispatch => basicAction({ + type: 'del', + dispatch: dispatch, + actionType: 'DEL_DEPT', + url: ApiTable.delDept.replace('{id}', id), + msg: { option: '删除部门' }, + }); +} + +export function getDepUser(depId) { + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + actionType: 'GET_DEPARTMENT_USER', + url: ApiTable.getDepUser.replace('{depId}', depId), + msg: { error: '获取部门下用户信息失败' }, + reducer: { name: 'depUser' } + }); +} + +export function createUser(data) { + return dispatch => basicAction({ + type: 'post', + data, + dispatch: dispatch, + actionType: 'CREATE_DEPARTMENT_USER', + url: ApiTable.createUser, + msg: { option: '新建用户' }, + }); +} + +export function updateUser(id, data) { + return dispatch => basicAction({ + type: 'put', + data, + dispatch: dispatch, + actionType: 'UPDATE_DEPARTMENT_USER', + url: ApiTable.updateUser.replace('{id}', id), + msg: { option: '修改用户' }, + }); +} + +export function delUser(ids) { + return dispatch => basicAction({ + type: 'del', + dispatch: dispatch, + actionType: 'DEL_DEPARTMENT_USER', + url: ApiTable.delUser.replace('{ids}', ids), + msg: { option: '删除用户' }, + }); +} + +export function resetPwd(id, data) { + return dispatch => basicAction({ + type: 'put', + data, + dispatch: dispatch, + actionType: 'CREATE_DEPARTMENT_USER', + url: ApiTable.resetPwd.replace('{id}', id), + msg: { option: '重置用户密码' }, + }); +} + +export default { + getDepMessage, + getDepUser, + createUser, + updateUser, + delUser, + resetPwd +} \ No newline at end of file diff --git a/web/client/src/sections/organization/components/deptModal.js b/web/client/src/sections/organization/components/deptModal.js new file mode 100644 index 0000000..9947081 --- /dev/null +++ b/web/client/src/sections/organization/components/deptModal.js @@ -0,0 +1,88 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { ProFormText, ModalForm, ProFormSelect } from '@ant-design/pro-form'; + +const DeptModal = (props) => { + const { visible, modalType, onVisibleChange, onConfirm, editData, depts } = props + let deptOptions = [], sonArr = []; + depts.map(d => { + deptOptions.push({ + value: d.id, + label: d.name + }); + + d.subordinate.map(ds => { + sonArr.push({ + value: ds.id, + label: ds.name + }) + }) + }) + const onFinish = (values) => { + if (onConfirm) { + if (modalType === 'edit') { + values.contract.parentDeptId = values.contract.parentDeptId || null + onConfirm(values) + } else { + onConfirm(values); + } + } + } + + const checkName = (rule, value, callback) => { + const list = modalType == 'edit' ? deptOptions.concat(sonArr).filter(g => g.value != editData.id) : deptOptions.concat(sonArr) + if (list.filter(s => s.label == value).length) { + callback('该名称已存在'); + } else { + callback(); + } + } + + return ( + + + { + let t = modalType === 'edit' ? deptOptions.filter(i => i.value !== editData.id) : deptOptions + return t + }} + disabled={modalType === 'edit' ? editData.subordinate?.length === 0 ? false : true : false} + /> + + ) +} + +function mapStateToProps(state) { + return { + }; +} + +export default connect(mapStateToProps)(DeptModal); \ No newline at end of file diff --git a/web/client/src/sections/organization/components/resetPwd.js b/web/client/src/sections/organization/components/resetPwd.js new file mode 100644 index 0000000..14135e0 --- /dev/null +++ b/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 ( + + + + { + const pwd = formRef.current.getFieldValue('password'); + if (!value) { + callback(); + } + if (pwd == value) { + callback(); + } else { + callback('两次输入的密码不一致'); + } + } + } + ]} + /> + + + ) +} + +function mapStateToProps(state) { + return {}; +} + +export default connect(mapStateToProps)(ResetPwd); \ No newline at end of file diff --git a/web/client/src/sections/organization/components/resource.js b/web/client/src/sections/organization/components/resource.js new file mode 100644 index 0000000..5e336f0 --- /dev/null +++ b/web/client/src/sections/organization/components/resource.js @@ -0,0 +1,121 @@ +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({}); //二级权限码 + + const roleDatas=roleData.slice(0) + 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.resourceId == 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 ( + { + const parentCode = record.code + return { + 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} + + } + }, { + 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 { + 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) + }} + /> + } + }]} + >
+ ) +} +export default Resource \ No newline at end of file diff --git a/web/client/src/sections/organization/components/userModal.js b/web/client/src/sections/organization/components/userModal.js new file mode 100644 index 0000000..73a082a --- /dev/null +++ b/web/client/src/sections/organization/components/userModal.js @@ -0,0 +1,171 @@ +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, depData, onVisibleChange, onConfirm, editData } = props + const reg_tel = /^1([358][0-9]|4[579]|66|7[0135678]|9[89])[0-9]{8}$/; + const onFinish = (values) => { + if (onConfirm) { + onConfirm(values); + } + } + const mobile = (value) => { + if (reg_tel.test(value)) { + return + } + return message('请输入姓名') + } + return ( + + + + + { + return event.target.value.replace(/\D/g, '') + }} + placeholder="请输入用户名(手机号)" + rules={[ + { required: true, valueType: Number, max: 11 }, { pattern: /^1([358][0-9]|4[579]|66|7[0135678]|9[89])[0-9]{8}$/, message: "请输入正确的手机号" } + ]} + /> + + + { + if (selected) { + setDepSelectedKeys(selectedKeys) + setDepSelected(selectedNodes[0].name || "") + dispatch(getDepUser(selectedKeys[0])) + } + }} + fieldProps={{ + fieldNames: { + label: 'title', + }, + treeDefaultExpandAll: false, + }} + rules={[{ required: true, message: '请选择所属部门' }]} + request={async () => { + console.log(depData); + return depData + }} + expandedKeys={["title"]} + /> + < ProFormText + name={['contract', 'post']} + width="md" + label="职位" + // required + placeholder="请输入职位" + /> + + + + {modalType == 'edit' ? null : } + + + + + + + ) +} + +function mapStateToProps(state) { + const { depMessage } = state; + + const pakData = (dep) => { + // console.log(dep); + return dep.map((d) => { + return { + title: d.name, + value: d.id, + // key: d.id, + children: pakData(d.subordinate) + } + }) + } + let depData = pakData(depMessage.data || []) + + return { + loading: depMessage.isRequesting, + depData, + }; +} + +export default connect(mapStateToProps)(UserModal); \ No newline at end of file diff --git a/web/client/src/sections/organization/containers/authority.js b/web/client/src/sections/organization/containers/authority.js new file mode 100644 index 0000000..ad46746 --- /dev/null +++ b/web/client/src/sections/organization/containers/authority.js @@ -0,0 +1,149 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { Spin, Row, Col, Card, Button, Tree, Empty } from 'antd'; +import { getDepMessage, getDepUser } from '../actions/user'; +import { getResource, getUserResource, postUserRes } from '../actions/authority'; +import Resource from '../components/resource'; + +const Authority = (props) => { + const { dispatch, loading, depMessage, depUser, resource, userResource, clientHeight } = props + const [depSelectedKeys, setDepSelectedKeys] = useState([]) + const [userSelectedKeys, setUserSelectedKeys] = useState([]) + const [depSelected, setDepSelected] = useState() + const [userSelected, setUserSelected] = useState() + const [resCode, setResCode] = useState({}) + const [useName, setUseName] = useState()// 选中名字 + const [userType,setUserType]=useState() + console.log(resource) + useEffect(() => { + dispatch(getResource()) + if (!(depMessage && depMessage.length)) { + dispatch(getDepMessage()) + } + + }, []) + useEffect(() => { + if (depMessage.length) { + setDepSelectedKeys([depMessage[0].id]) + setDepSelected([depMessage[0].name]) + dispatch(getDepUser(depMessage[0].id)) + } + + }, [depMessage]) + useEffect(() => { + if (depUser.length) { + setUserSelectedKeys([depUser[0].id]) + setUserSelected(depUser[0].username) + dispatch(getUserResource(depUser[0].id)) + setUseName(depUser[0].name) + } + console.log(depUser, 'depUser') + }, [depUser]) + + const handleSave = () => { + dispatch(postUserRes({ userId: userSelectedKeys[0], resCode: resCode })).then(res => { + if (res.success) { + dispatch(getUserResource(userSelectedKeys[0])) + } + }) + } + return ( + + + + + { + depMessage.length ? + { + setUserType(selectedNodes[0].type) + if (selected) { + setDepSelectedKeys(selectedKeys) + setDepSelected(selectedNodes[0].name || "") + dispatch(getDepUser(selectedKeys[0])) + } + + }} + treeData={depMessage} + fieldNames={{ + title: 'name', + key: 'id', + children: 'subordinate' + }} + /> : '' + } + + + + + { + depUser.length ? + { + const name = node.name + setUseName(name) + + if (selected) { + setUserSelectedKeys(selectedKeys) + setUserSelected(selectedNodes[0].username || '') + dispatch(getUserResource(selectedKeys[0])) + } + + }} + treeData={depUser} + fieldNames={{ + title: 'name', + key: 'id' + }} + /> : + } + + + + {depUser.length ? + + + + + + + + : + + + } + + + + ) +} + +function mapStateToProps(state) { + const { userResource, resource, depMessage, depUser, global } = state; + return { + clientHeight: global.clientHeight, + loading: depMessage.isRequesting || depUser.isRequesting || resource.isRequesting, + userResource: userResource.data || [], + resource: resource.data || [], + depMessage: depMessage.data || [], + depUser: depUser.data || [] + }; +} + +export default connect(mapStateToProps)(Authority); \ No newline at end of file diff --git a/web/client/src/sections/organization/containers/index.js b/web/client/src/sections/organization/containers/index.js new file mode 100644 index 0000000..e1a69b0 --- /dev/null +++ b/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 }; \ No newline at end of file diff --git a/web/client/src/sections/organization/containers/user.js b/web/client/src/sections/organization/containers/user.js new file mode 100644 index 0000000..9c3eeec --- /dev/null +++ b/web/client/src/sections/organization/containers/user.js @@ -0,0 +1,331 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { FormOutlined, DeleteOutlined } from '@ant-design/icons'; +import { Spin, Tooltip, Button, Popconfirm, Row, Col, Tree, Card, Switch } from 'antd'; +import ProTable from '@ant-design/pro-table'; +import { getDepMessage, getDepUser, createUser, updateUser, delUser, resetPwd, createDept, updateDept, delDept } from '../actions/user'; +import UserModal from '../components/userModal'; +import ResetPwd from '../components/resetPwd'; +import DeptModal from '../components/deptModal'; + +const TreeNode = Tree.TreeNode; +const user = JSON.parse(sessionStorage.getItem('user')); + +const UserManage = (props) => { + const { dispatch, loading, depMessage, depUser, clientHeight } = props; + // 部门 + const [deptModalVisible, setDeptModalVisible] = useState(false); + const [deptModalType, setDeptModalType] = useState(); + const [deptModalRecord, setDeptModalRecord] = useState(); + + // 成员 + const [modalVisible, setModalVisible] = useState(false); + const [modalType, setModalType] = useState(); + const [modalRecord, setModalRecord] = useState(); + const [pwdModalVisible, setPwdModalVisible] = useState(false); + const [depSelectedKeys, setDepSelectedKeys] = useState([]) + const [rowSelected, setRowSelected] = useState([]) + + useEffect(() => { + dispatch(getDepMessage()) + }, []) + + useEffect(() => { + if (depMessage.length) { + setDepSelectedKeys([depMessage[0].id]) + dispatch(getDepUser(depMessage[0].id)) + } + }, [depMessage]) + + const columns = [ + { + title: '姓名', + dataIndex: 'name', + }, { + title: '用户名(手机号)', + dataIndex: 'username', + }, + { + title: '职位', + dataIndex: 'post', + }, { + title: '邮箱', + dataIndex: 'email', + }, { + title: '启用状态', + dataIndex: 'enable', + render: (_, r) => { + return + } + }, { + title: '操作', + dataIndex: 'action', + render: (dom, record) => { + + return record.username == 'SuperAdmin' ? '' : [ + , + { + delUsers([record.id]) + }} + > + + , + + ] + }, + }, + ]; + + //弹窗确认 + const onConfirm = (values) => { + if (modalType == 'edit') { + dispatch(updateUser(modalRecord.id, values.contract)).then(res => { + if (res.success) { + setModalVisible(false); + dispatch(getDepUser(depSelectedKeys[0])); + } + }); + } else { + dispatch(createUser(values.contract)).then(res => { + if (res.success) { + setModalVisible(false); + dispatch(getDepUser(depSelectedKeys[0])); + } + }); + } + + } + + //打开弹窗 + 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(getDepUser(depSelectedKeys[0])); + if (type == 'batch') { + setRowSelected([]); + } + }); + } + + //重置密码 + const onPwdConfirm = (values) => { + dispatch(resetPwd(modalRecord.id, { password: values.password })).then(res => { + if (res.success) { + setPwdModalVisible(false); + dispatch(getDepUser(depSelectedKeys[0])); + } + }); + } + + const openDeptModal = (type, record) => { + console.log(type, record, 'type, record') + + setDeptModalVisible(true); + setDeptModalType(type); + if (type === 'edit') { + setDeptModalRecord(record); + } else { + setDeptModalRecord(null); + } + } + const onDeptConfirm = (values) => { + if (deptModalType === 'edit') { + dispatch(updateDept(deptModalRecord.id, values.contract)).then(res => { + if (res.success) { + setDeptModalVisible(false); + dispatch(getDepMessage()) + } + }); + } else { + dispatch(createDept(values.contract)).then(res => { + if (res.success) { + setDeptModalVisible(false); + dispatch(getDepMessage()) + } + }); + } + } + const delDepartment = (id) => { + dispatch(delDept(id)).then(res => { + if (res.success) { + dispatch(getDepMessage()) + } + }); + } + + const renderTree = (item, id) => { + // let cannotDel = item.users.length || item.subordinate?.filter(is => is.users.length).length;//自己下面有成员 或者下级部门下有成员 不能删除 + return
+ +
{item.name}
+
+
+ { + depSelectedKeys == id && user.username === "SuperAdmin" ? + <> + { + setDeptModalRecord(item) + setDeptModalVisible(true) + setDeptModalType('edit') + }} /> + { + { delDepartment(id) }}> + + + } + : null + } +
+
+ } + + return (
+ + + + + { + user.username === "SuperAdmin" && + } + { + depMessage.length ? + { + if (e.selected) { + setDepSelectedKeys(selectedKeys) + dispatch(getDepUser(selectedKeys[0])) + } + }} + // treeData={depMessage} + // fieldNames={{ + // title: 'name', + // key: 'id', + // children: 'subordinate' + // }} + > + { + depMessage.map((s, index) => { + return + { + s.subordinate.map(k => { + return { setIShowIcon(k.id) }} onMouseOut={() => { setIShowIcon(null) }}> + + }) + } + + }) + } + : '' + } + + + + + + { + setRowSelected(selectedRowKeys); + + }, + getCheckboxProps: (record) => { + return { + disabled: record.username === 'SuperAdmin', + } + }, + }} + options={false} + search={false} + rowKey="id" + toolBarRender={() => [ + + + + { delUsers(rowSelected, 'batch') }}> + + + + ]} + /> + + { + deptModalVisible ? + + : '' + } + { + depMessage.length && modalVisible ? + + : '' + } + {pwdModalVisible ? : ''} + + + + +
+ + ) +} + +function mapStateToProps(state) { + const { depMessage, depUser, global } = state; + return { + clientHeight: global.clientHeight, + loading: depMessage.isRequesting, + depMessage: depMessage.data || [], + depUser: depUser.data || [] + }; +} + +export default connect(mapStateToProps)(UserManage); \ No newline at end of file diff --git a/web/client/src/sections/organization/index.js b/web/client/src/sections/organization/index.js new file mode 100644 index 0000000..412ced6 --- /dev/null +++ b/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 +}; \ No newline at end of file diff --git a/web/client/src/sections/organization/nav-item.js b/web/client/src/sections/organization/nav-item.js new file mode 100644 index 0000000..6b25267 --- /dev/null +++ b/web/client/src/sections/organization/nav-item.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { Menu } from 'antd'; +import { SettingOutlined } from '@ant-design/icons'; + +const SubMenu = Menu.SubMenu; + +export function getNavItem(user, dispatch) { + // if (!Func.isAuthorized("ORG_MANAGE")) { + // return null + // } + return ( + } title={'组织管理'}> + + 部门成员 + + + 权限配置 + + + ); +} \ No newline at end of file diff --git a/web/client/src/sections/organization/reducers/index.js b/web/client/src/sections/organization/reducers/index.js new file mode 100644 index 0000000..0203d01 --- /dev/null +++ b/web/client/src/sections/organization/reducers/index.js @@ -0,0 +1,5 @@ +'use strict'; + +export default { + +}; \ No newline at end of file diff --git a/web/client/src/sections/organization/routes.js b/web/client/src/sections/organization/routes.js new file mode 100644 index 0000000..d6675f5 --- /dev/null +++ b/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: '权限配置', + }] + } +}]; \ No newline at end of file diff --git a/web/client/src/utils/webapi.js b/web/client/src/utils/webapi.js index 2d494cc..8d70541 100644 --- a/web/client/src/utils/webapi.js +++ b/web/client/src/utils/webapi.js @@ -7,6 +7,23 @@ export const ApiTable = { validatePhone: 'validate/phone', getUserSiteList: 'user/site/list', + // 组织管理-用户管理 + getDepMessage: 'organization/department', + createDept: '/organization/dept/add', + updateDept: '/organization/dept/{id}/modify', + delDept: '/organization/dept/{id}', + + getDepUser: 'organization/department/{depId}/user', + createUser: 'organization/department/user', + updateUser: 'organization/department/user/{id}', + delUser: 'organization/department/user/{ids}', + resetPwd: '/organization/department/user/resetPwd/{id}', + + // 用户权限 + getResource: 'resource', + getUserResource: 'user/resource', + postUserRes: 'user/resource', + //安全风险预报 getSiteWeekRegiste: 'sites/report', getRiskReportList: 'risk/report',