增加操作员管理功能
diff --git a/backend/build.gradle b/backend/build.gradle
index 631933b..684619c 100644
--- a/backend/build.gradle
+++ b/backend/build.gradle
@@ -74,7 +74,7 @@
     compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.9.1'
     compile group: 'log4j', name: 'log4j', version: '1.2.17'
 
-    compile group: 'com.supwisdom', name: 'payapi-sdk', version: '1.0.26-5-gf114ee1'
+    compile group: 'com.supwisdom', name: 'payapi-sdk', version: '1.0.28-1-g7415df3'
     
     implementation 'org.hamcrest:hamcrest:2.1'
 }
diff --git a/backend/src/main/java/com/supwisdom/dlpay/framework/dao/OperatorDao.java b/backend/src/main/java/com/supwisdom/dlpay/framework/dao/OperatorDao.java
index 28cc18e..ed4e68a 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/framework/dao/OperatorDao.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/framework/dao/OperatorDao.java
@@ -1,12 +1,13 @@
 package com.supwisdom.dlpay.framework.dao;
 
 import com.supwisdom.dlpay.framework.domain.TOperator;
+import com.supwisdom.dlpay.portal.dao.OperatorRepository;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.stereotype.Repository;
 
 @Repository
-public interface OperatorDao extends JpaRepository<TOperator, String>, JpaSpecificationExecutor<TOperator> {
+public interface OperatorDao extends JpaRepository<TOperator, String>, JpaSpecificationExecutor<TOperator>, OperatorRepository {
   TOperator findByOpercode(String opercode);
 
   TOperator findByOperid(String operid);
diff --git a/backend/src/main/java/com/supwisdom/dlpay/framework/domain/TOperator.java b/backend/src/main/java/com/supwisdom/dlpay/framework/domain/TOperator.java
index be38fed..ba7e07e 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/framework/domain/TOperator.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/framework/domain/TOperator.java
@@ -75,6 +75,9 @@
   private String jti;
 
   @Transient
+  private String rolename;
+
+  @Transient
   private Collection<? extends GrantedAuthority> authorities;  //权限
 
   public TOperator() {
@@ -200,6 +203,14 @@
     this.authorities = authorities;
   }
 
+  public String getRolename() {
+    return rolename;
+  }
+
+  public void setRolename(String rolename) {
+    this.rolename = rolename;
+  }
+
   @Override
   public Collection<? extends GrantedAuthority> getAuthorities() {
     return this.authorities;
diff --git a/backend/src/main/java/com/supwisdom/dlpay/framework/service/OperatorDetailService.java b/backend/src/main/java/com/supwisdom/dlpay/framework/service/OperatorDetailService.java
index 4c31b55..eebda6d 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/framework/service/OperatorDetailService.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/framework/service/OperatorDetailService.java
@@ -1,8 +1,12 @@
 package com.supwisdom.dlpay.framework.service;
 
 import com.supwisdom.dlpay.framework.domain.TOperator;
+import com.supwisdom.dlpay.framework.jpa.page.Pagination;
+import com.supwisdom.dlpay.portal.bean.OperatorSearchBean;
 import com.supwisdom.dlpay.portal.domain.TBResource;
+import com.supwisdom.dlpay.portal.domain.TBRole;
 import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
 
@@ -11,5 +15,22 @@
 
   TOperator saveOper(TOperator operator);
 
+  TOperator saveOrUpdateOper(TOperator operator);
+
+  Boolean checkExistOper(String opercode, String operid);
+
+  void resetPassword(String operid);
+
+  String switchStatus(String operid, String status);
+
   List<TBResource> getResByRoleId(String roleId);
+
+  @Transactional(readOnly = true)
+  List<TBRole> getAllRoles();
+
+  @Transactional(readOnly = true)
+  Pagination getOperatorList(OperatorSearchBean bean);
+
+  @Transactional(readOnly = true)
+  TBRole findRoleById(String roleId);
 }
diff --git a/backend/src/main/java/com/supwisdom/dlpay/framework/service/impl/OperatorDetailServiceImpl.java b/backend/src/main/java/com/supwisdom/dlpay/framework/service/impl/OperatorDetailServiceImpl.java
index d2e1153..6df8991 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/framework/service/impl/OperatorDetailServiceImpl.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/framework/service/impl/OperatorDetailServiceImpl.java
@@ -2,18 +2,30 @@
 
 import com.supwisdom.dlpay.framework.dao.OperatorDao;
 import com.supwisdom.dlpay.framework.domain.TOperator;
+import com.supwisdom.dlpay.framework.jpa.page.Pagination;
 import com.supwisdom.dlpay.framework.service.OperatorDetailService;
+import com.supwisdom.dlpay.framework.tenant.TenantContext;
+import com.supwisdom.dlpay.framework.util.DateUtil;
+import com.supwisdom.dlpay.framework.util.StringUtil;
+import com.supwisdom.dlpay.framework.util.TradeDict;
+import com.supwisdom.dlpay.mobile.exception.PortalBusinessException;
+import com.supwisdom.dlpay.portal.bean.OperatorSearchBean;
 import com.supwisdom.dlpay.portal.dao.ResourceDao;
+import com.supwisdom.dlpay.portal.dao.RoleDao;
 import com.supwisdom.dlpay.portal.domain.TBResource;
+import com.supwisdom.dlpay.portal.domain.TBRole;
+import com.supwisdom.dlpay.portal.util.PortalConstant;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Optional;
 
 @Service
 public class OperatorDetailServiceImpl implements OperatorDetailService {
@@ -21,6 +33,8 @@
   private OperatorDao operatorDao;
   @Autowired
   private ResourceDao resourceDao;
+  @Autowired
+  private RoleDao roleDao;
 
   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
@@ -46,6 +60,60 @@
   }
 
   @Override
+  public Boolean checkExistOper(String opercode, String operid) {
+    TOperator oper = operatorDao.findByOpercode(opercode.trim());
+    if (null != oper && StringUtil.isEmpty(operid)) {
+      return true;
+    }
+    return null != oper && !StringUtil.isEmpty(operid) && !operid.trim().equals(oper.getOperid());
+  }
+
+  @Override
+  public void resetPassword(String operid) {
+    TOperator operator = operatorDao.findByOperid(operid);
+    if (null == operator) {
+      throw new PortalBusinessException("该管理员账号不存在");
+    }
+    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
+    operator.setOperpwd(encoder.encode(PortalConstant.OPERPWD_DEFAULT));
+    operatorDao.save(operator);
+  }
+
+  @Override
+  public String switchStatus(String operid, String status) {
+    TOperator operator = operatorDao.findByOperid(operid);
+    if (null == operator) {
+      throw new PortalBusinessException("该管理员账号不存在");
+    }
+    operator.setStatus(status);
+    operatorDao.save(operator);
+    return operator.getStatus();
+  }
+
+  @Override
+  public TOperator saveOrUpdateOper(TOperator operator) {
+    if (StringUtil.isEmpty(operator.getOperid())){
+      operator.setOpendate(DateUtil.getNow("yyyyMMdd"));
+      operator.setStatus(TradeDict.STATUS_NORMAL);
+      operator.setOpertype(PortalConstant.OPERTYPE_DEFAULT);
+      operator.setTenantId(TenantContext.getTenantSchema());
+      BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
+      operator.setOperpwd(encoder.encode(PortalConstant.OPERPWD_DEFAULT));
+      return operatorDao.save(operator);
+    }else {
+      TOperator op = operatorDao.findByOperid(operator.getOperid());
+      if (op == null) {
+        throw new PortalBusinessException("未找到待修改的管理员信息");
+      }
+      op.setOpername(operator.getOpername());
+      op.setRoleid(operator.getRoleid());
+      op.setMobile(operator.getMobile());
+      op.setEmail(operator.getEmail());
+      return operatorDao.save(op);
+    }
+  }
+
+  @Override
   public List<TBResource> getResByRoleId(String roleId) {
     List<TBResource> rootResource = resourceDao.findRootListByRole(roleId);
     for (TBResource resource : rootResource) {
@@ -54,4 +122,19 @@
     }
     return rootResource;
   }
+
+  @Override
+  public List<TBRole> getAllRoles() {
+    return roleDao.findAll();
+  }
+
+  @Override
+  public Pagination getOperatorList(OperatorSearchBean bean) {
+    return operatorDao.getOperatorList(bean);
+  }
+
+  @Override
+  public TBRole findRoleById(String roleId) {
+    return roleDao.findById(roleId).orElse(null);
+  }
 }
diff --git a/backend/src/main/java/com/supwisdom/dlpay/portal/dao/impl/OperatorRepositoryImpl.java b/backend/src/main/java/com/supwisdom/dlpay/portal/dao/impl/OperatorRepositoryImpl.java
new file mode 100644
index 0000000..93ca48a
--- /dev/null
+++ b/backend/src/main/java/com/supwisdom/dlpay/portal/dao/impl/OperatorRepositoryImpl.java
@@ -0,0 +1,44 @@
+package com.supwisdom.dlpay.portal.dao.impl;
+
+import com.supwisdom.dlpay.framework.domain.TOperator;
+import com.supwisdom.dlpay.framework.jpa.BaseRepository;
+import com.supwisdom.dlpay.framework.jpa.Finder;
+import com.supwisdom.dlpay.framework.jpa.page.Pagination;
+import com.supwisdom.dlpay.framework.util.StringUtil;
+import com.supwisdom.dlpay.portal.bean.OperatorSearchBean;
+import com.supwisdom.dlpay.portal.dao.OperatorRepository;
+import org.hibernate.transform.Transformers;
+import org.jetbrains.annotations.NotNull;
+
+public class OperatorRepositoryImpl extends BaseRepository implements OperatorRepository {
+  @NotNull
+  @Override
+  public Pagination getOperatorList(@NotNull OperatorSearchBean bean) {
+    String opername = bean.getOpername();
+    String opercode = bean.getOpercode();
+    String status = bean.getStatus();
+    int pageno = bean.getPageno();
+    int pagesize = bean.getPagesize();
+    StringBuilder sql = new StringBuilder("select o.operid,o.opercode,o.opername,o.email,o.mobile,o.opendate,o.status,o.roleid,r.rolename from tb_operator o left join tb_role r on o.roleid = r.roleid where 1=1");
+    if (!StringUtil.isEmpty(opername)) {
+      sql.append(" and o.opername like :opername");
+    }
+    if (!StringUtil.isEmpty(opercode)) {
+      sql.append(" and o.opercode like :opercode");
+    }
+    if (!StringUtil.isEmpty(status)) {
+      sql.append(" and o.status=:status");
+    }
+    Finder f = Finder.create(sql.toString());
+    if (!StringUtil.isEmpty(opername)) {
+      f.setParameter("opername", "%" + opername.trim() + "%");
+    }
+    if (!StringUtil.isEmpty(opercode)) {
+      f.setParameter("opercode", "%" + opercode.trim() + "%");
+    }
+    if (!StringUtil.isEmpty(status)) {
+      f.setParameter("status", status);
+    }
+    return findNative(f, Transformers.aliasToBean(TOperator.class), pageno, pagesize);
+  }
+}
diff --git a/backend/src/main/java/com/supwisdom/dlpay/portal/util/PortalConstant.java b/backend/src/main/java/com/supwisdom/dlpay/portal/util/PortalConstant.java
index 21fc3a6..d738d2a 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/portal/util/PortalConstant.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/portal/util/PortalConstant.java
@@ -4,6 +4,9 @@
   public static final String YES = "1";
   public static final String NO = "0";
 
+  public static final String OPERPWD_DEFAULT = "123456";
+  public static final String OPERTYPE_DEFAULT = "oper";
+
   public static final String SYSPARA_IMAGESERVER_URL = "imageserver.url.image";
   public static final String SYSPARA_IMAGE_MAXSIZE = "imagemaxsize";
   public static final String SYSPARA_IMAGE_MINISIZE = "minimagesize";
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
index 22b08fc..71aed08 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
@@ -722,7 +722,15 @@
             return JsonResult.error("用户不存在,请注册")
         }
         if (!user.userid.isNullOrEmpty()) {
-            val response = userProxy.signbxy(user.userid, agree, user.phone)
+            val response = userProxy.signbxy(SignBxyParam().apply {
+                this.userid = user.userid
+                this.code = agree
+                this.phone = user.phone
+                this.uid = user.uid
+                this.rsaprivate = user.rsaprivate
+                this.rsapublic = user.rsapublic
+                this.secertkey = user.secertkey
+            })
             if (response.retcode != 0) {
                 logger.error { "用户签约失败:${response.retmsg}" }
                 return JsonResult.error("签约失败,${response.retmsg}")
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/OperLoginHandler.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/OperLoginHandler.kt
index 7d4d4e6..7ac0a95 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/OperLoginHandler.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/OperLoginHandler.kt
@@ -16,6 +16,7 @@
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.http.HttpStatus
 import org.springframework.security.authentication.BadCredentialsException
+import org.springframework.security.authentication.DisabledException
 import org.springframework.security.authentication.LockedException
 import org.springframework.security.core.Authentication
 import org.springframework.security.core.AuthenticationException
@@ -93,6 +94,7 @@
         val errmsg = when (exception) {
             is BadCredentialsException -> "账号或密码错误"
             is LockedException -> "账户被锁定"
+            is DisabledException -> "账户已被注销,请联系管理员"
             else -> exception.message!!
         }
         response.status = HttpStatus.OK.value()
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/PortalApi.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/PortalApi.kt
index 23d7284..35bfe6e 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/PortalApi.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/PortalApi.kt
@@ -101,12 +101,14 @@
         return try {
             val p = SecurityContextHolder.getContext().authentication
             val oper = operatorDetailService.findByOperid(p.name)
+            val role = operatorDetailService.findRoleById(oper.roleid)
+                    ?: return JsonResult.error("操作员的角色不存在")
             val data = HashMap<String, String>()
             val url = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_IMAGE_URLPUSH)
             val mapKey = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_PORTAL_AMAPKEY)
             val mapUrl = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_PORTAL_AMAPURL)
             data["name"] = oper.opername
-            data["roles"] = "admin"
+            data["roles"] = role.rolecode
             data["url"] = url
             data["mapkey"] = mapKey
             data["mapurl"] = mapUrl
@@ -119,7 +121,7 @@
     }
 
     @RequestMapping("/user/updateinfo")
-    fun updateUserInfo(@RequestBody bean:TOperator): JsonResult?{
+    fun updateUserInfo(@RequestBody bean: TOperator): JsonResult? {
         return try {
             val p = SecurityContextHolder.getContext().authentication
             val oper = operatorDetailService.findByOperid(p.name)
@@ -151,6 +153,7 @@
         }
 
     }
+
     @RequestMapping("/user/resource")
     fun getUserResource(): JsonResult? {
         return try {
@@ -555,4 +558,83 @@
             JsonResult.error("切换网点是否开启异常")
         }
     }
+
+    /**
+     * 获取所有角色
+     */
+    @RequestMapping(value = ["/role/all"], method = [RequestMethod.GET])
+    fun getAllRoles(): JsonResult? {
+        return try {
+            val list = operatorDetailService.allRoles
+            return JsonResult.ok().put("list", list)
+        } catch (e: Exception) {
+            logger.error { e.message }
+            JsonResult.error("查询所有角色失败")
+        }
+    }
+
+    @RequestMapping(value = ["/operator/list"], method = [RequestMethod.GET])
+    fun getOperatorList(bean: OperatorSearchBean): JsonResult? {
+        return try {
+            val page = operatorDetailService.getOperatorList(bean)
+            if (page.list == null || page.list.size == 0) {
+                return JsonResult.ok().put("msg", "无数据")
+            }
+            return JsonResult.ok().put("page", page)
+        } catch (e: Exception) {
+            logger.error { e.message }
+            JsonResult.error("查询操作员列表失败")
+        }
+    }
+
+    @RequestMapping(value = ["/operator/save"], method = [RequestMethod.POST])
+    fun saveOutlets(@RequestBody operatorParam: TOperator): JsonResult? {
+        return try {
+            if (operatorDetailService.checkExistOper(operatorParam.opercode, operatorParam.operid)) {
+                return JsonResult.error("管理员账号重复,请更换")
+            }
+            val operator = TOperator()
+            operator.opercode = operatorParam.opercode
+            operator.opername = operatorParam.opername
+            operator.roleid = operatorParam.roleid
+            operator.mobile = operatorParam.mobile
+            operator.email = operatorParam.email
+            operator.operid = operatorParam.operid
+            operator.thirdadmin = if ("yes".equals(operatorParam.thirdadmin, ignoreCase = true)) "yes" else "no"
+            operatorDetailService.saveOrUpdateOper(operator)
+            JsonResult.ok()
+        } catch (e: Exception) {
+            logger.error { e.message }
+            JsonResult.error("保存管理员异常")
+        }
+    }
+
+    /**
+     * 重置管理员密码
+     */
+    @RequestMapping(value = ["/operator/resetpwd/{operid}"], method = [RequestMethod.POST])
+    fun resetPassword(@PathVariable operid:String): JsonResult? {
+        return try {
+            operatorDetailService.resetPassword(operid)
+            JsonResult.ok()
+        } catch (e: Exception) {
+            logger.error { e.message }
+            JsonResult.error("重置管理员密码异常")
+        }
+    }
+
+    /**
+     * 设置管理员状态
+     */
+    @RequestMapping(value = ["/operator/switchstatus/{operid}"], method = [RequestMethod.POST])
+    fun switchStatus(@PathVariable(value = "operid") operid: String,
+                          @RequestParam(value = "status") status: String): JsonResult? {
+        return try {
+            val result = operatorDetailService.switchStatus(operid, status)
+            JsonResult.ok().put("result", result)
+        } catch (e: Exception) {
+            logger.error { e.message }
+            JsonResult.error("设置管理员状态异常")
+        }
+    }
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/bean/OperatorSearchBean.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/bean/OperatorSearchBean.kt
new file mode 100644
index 0000000..cfb75b1
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/bean/OperatorSearchBean.kt
@@ -0,0 +1,9 @@
+package com.supwisdom.dlpay.portal.bean
+
+class OperatorSearchBean {
+    var opercode: String = ""
+    var opername: String = ""
+    var status: String = ""
+    var pageno: Int = 0
+    var pagesize: Int = 10
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/OperatorRepository.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/OperatorRepository.kt
new file mode 100644
index 0000000..78b5369
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/OperatorRepository.kt
@@ -0,0 +1,8 @@
+package com.supwisdom.dlpay.portal.dao
+
+import com.supwisdom.dlpay.framework.jpa.page.Pagination
+import com.supwisdom.dlpay.portal.bean.OperatorSearchBean
+
+interface OperatorRepository {
+    fun getOperatorList(bean:OperatorSearchBean):Pagination
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/RoleDao.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/RoleDao.kt
new file mode 100644
index 0000000..c5b553d
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/RoleDao.kt
@@ -0,0 +1,9 @@
+package com.supwisdom.dlpay.portal.dao
+
+import com.supwisdom.dlpay.portal.domain.TBRole
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface RoleDao : JpaRepository<TBRole, String>{
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/security.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/security.kt
index b993822..0d67838 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/security.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/security.kt
@@ -281,6 +281,8 @@
                         return
                     }
                     TenantContext.setTenantSchema(tenantId)
+                }else{
+                    TenantContext.setTenantSchema(Constants.DEFAULT_TENANTID)
                 }
                 val auth = UsernamePasswordAuthenticationToken(claims[Constants.JWT_CLAIM_UID], null,
                         (claims[Constants.JWT_CLAIM_AUTHORITIES] as ArrayList<*>)
diff --git a/backend/src/main/resources/data-postgresql.sql b/backend/src/main/resources/data-postgresql.sql
index 8b708c6..9880a3d 100644
--- a/backend/src/main/resources/data-postgresql.sql
+++ b/backend/src/main/resources/data-postgresql.sql
@@ -37,6 +37,8 @@
 INSERT INTO "tb_resource"("resid", "isleaf", "ordernum", "parentid", "resname", "respath", "showflag", "icon") VALUES ('4665765bf07d455486f2a5215dd97380', '1', 5, 'f066939ecbf64da3a54fa93d56e4391b', '文章管理', '/article/list', '1', NULL);
 INSERT INTO "tb_resource"("resid", "isleaf", "ordernum", "parentid", "resname", "respath", "showflag", "icon") VALUES ('80e0326446a643069bc8d26c132d45ea', '1', 7, 'f066939ecbf64da3a54fa93d56e4391b', '咨询问答管理', '/advisory/index', '1', NULL);
 INSERT INTO "tb_resource"("resid", "isleaf", "ordernum", "parentid", "resname", "respath", "showflag", "icon") VALUES ('7e60ec8e741441fe9a2f351b5ab8c965', '1', 8, 'f066939ecbf64da3a54fa93d56e4391b', '网点管理', '/outlets/index', '1', NULL);
+INSERT INTO "tb_resource"("resid", "isleaf", "ordernum", "parentid", "resname", "respath", "showflag", "icon") VALUES ('67291cc80fc148ceb939d98554977bc3', '1', 9, 'f066939ecbf64da3a54fa93d56e4391b', '角色管理', '/role/index', '1', NULL);
+
 
 
 INSERT INTO "tb_operator"("operid", "closedate", "email", "mobile", "opendate", "opercode", "opername", "operpwd", "opertype", "sex", "status", "tenantid", "thirdadmin", "jti", "roleid") VALUES ('LOR2IwRkbOjp+sVG9KR2BpHZbwGKepS4', '20500101', NULL, NULL, '20190101', 'system', '系统管理员', '$2a$10$Ex9xp11.vCaD8D0a7ahiUOKqDij1TcCUBwRAmrqXeDvAkmzLibn4.', 'oper', NULL, 'normal', '{tenantid}', 'no', 'QwC1ln7rReYmBOhq57op6Q', '20497f2fa27a44f7841492288ab75d88');
@@ -52,6 +54,8 @@
 INSERT INTO "tb_role_resource"("id", "addtime", "resid", "roleid") VALUES ('460d7f0b57eb4dcfb73fb1b51ad37f4f', '20200827142245', '99604b8d18b34417befe051a3720cbed', '20497f2fa27a44f7841492288ab75d88');
 INSERT INTO "tb_role_resource"("id", "addtime", "resid", "roleid") VALUES ('49cb562956534d7dbf54b762b2b6af0d', '20200921162433', '80e0326446a643069bc8d26c132d45ea', '20497f2fa27a44f7841492288ab75d88');
 INSERT INTO "tb_role_resource"("id", "addtime", "resid", "roleid") VALUES ('5f78e1eea840497c8b991323fab4541d', '20200923170348', '7e60ec8e741441fe9a2f351b5ab8c965', '20497f2fa27a44f7841492288ab75d88');
+INSERT INTO "tb_role_resource"("id", "addtime", "resid", "roleid") VALUES ('bb2f8e1a9aa74732bf1f35828ef8a87b', '20201027172455', '67291cc80fc148ceb939d98554977bc3', '20497f2fa27a44f7841492288ab75d88');
+
 
 
 
diff --git a/frontend/src/api/operator.js b/frontend/src/api/operator.js
new file mode 100644
index 0000000..50aae8d
--- /dev/null
+++ b/frontend/src/api/operator.js
@@ -0,0 +1,33 @@
+import request from '@/utils/request'
+
+export function saveOperator(data) {
+  return request({
+    url: '/operator/save',
+    method: 'post',
+    data
+  })
+}
+
+export function resetPassword(operid) {
+  return request({
+    url: '/operator/resetpwd/' + operid,
+    method: 'post'
+  })
+}
+
+export function getOperatorList(query) {
+  return request({
+    url: '/operator/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function switchStatus(operid, status) {
+  return request({
+    url: '/operator/switchstatus/' + operid,
+    method: 'post',
+    params: status
+  })
+}
+
diff --git a/frontend/src/api/role.js b/frontend/src/api/role.js
index 959bbd2..4d1a002 100644
--- a/frontend/src/api/role.js
+++ b/frontend/src/api/role.js
@@ -1,5 +1,12 @@
 import request from '@/utils/request'
 
+export function getAllRoles() {
+  return request({
+    url: '/role/all',
+    method: 'get'
+  })
+}
+
 export function getRoutes() {
   return request({
     url: '/vue-element-admin/routes',
diff --git a/frontend/src/store/modules/permission.js b/frontend/src/store/modules/permission.js
index a35f884..1782f6a 100644
--- a/frontend/src/store/modules/permission.js
+++ b/frontend/src/store/modules/permission.js
@@ -97,13 +97,12 @@
         const data = response.resource
         Object.assign(loadMenuData, data)
         generaMenu(asyncRoutes, loadMenuData)
-        let accessedRoutes
-        if (roles.includes('admin')) {
-          // alert(JSON.stringify(asyncRoutes))
-          accessedRoutes = asyncRoutes || []
-        } else {
-          accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
-        }
+        const accessedRoutes = asyncRoutes || []
+        // if (roles.includes('admin')) {
+        //   accessedRoutes = asyncRoutes || []
+        // } else {
+        //   accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
+        // }
         commit('SET_ROUTES', accessedRoutes)
         resolve(accessedRoutes)
 
diff --git a/frontend/src/views/article/list.vue b/frontend/src/views/article/list.vue
index 21491c5..87c5858 100644
--- a/frontend/src/views/article/list.vue
+++ b/frontend/src/views/article/list.vue
@@ -20,6 +20,7 @@
         end-placeholder="结束日期"
         value-format="yyyyMMdd"
         :picker-options="pickerOptions"
+        class="filter-item"
       />
     </div>
     <div class="filter-container">
@@ -35,11 +36,13 @@
         end-placeholder="结束日期"
         value-format="yyyyMMdd"
         :picker-options="pickerOptions"
+        class="filter-item"
       />
       <div class="filter-item" style="margin-right:15px">文章状态</div>
       <el-select
         v-model="formData.status"
         style="width:200px;margin-right:120px"
+        class="filter-item"
       >
         <el-option
           v-for="item in statusOptions"
diff --git a/frontend/src/views/feedback/index.vue b/frontend/src/views/feedback/index.vue
index 95b0835..76dd654 100644
--- a/frontend/src/views/feedback/index.vue
+++ b/frontend/src/views/feedback/index.vue
@@ -29,11 +29,13 @@
         end-placeholder="结束日期"
         value-format="yyyyMMdd"
         :picker-options="pickerOptions"
+        class="filter-item"
       />
       <div class="filter-item" style="margin-right:15px">留言状态</div>
       <el-select
         v-model="formData.status"
         style="width:200px;margin-right:100px"
+        class="filter-item"
       >
         <el-option
           v-for="item in statusOptions"
diff --git a/frontend/src/views/login/index.vue b/frontend/src/views/login/index.vue
index 2abcff9..2b38390 100644
--- a/frontend/src/views/login/index.vue
+++ b/frontend/src/views/login/index.vue
@@ -13,7 +13,7 @@
         <el-input
           ref="username"
           v-model="loginForm.username"
-          placeholder="Username"
+          placeholder="用户名"
           name="username"
           type="text"
           tabindex="1"
@@ -21,7 +21,7 @@
         />
       </el-form-item>
 
-      <el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
+      <el-tooltip v-model="capsTooltip" content="大写锁定已开" placement="right" manual>
         <el-form-item prop="password">
           <span class="svg-container">
             <svg-icon icon-class="password" />
@@ -31,7 +31,7 @@
             ref="password"
             v-model="loginForm.password"
             :type="passwordType"
-            placeholder="Password"
+            placeholder="密码"
             name="password"
             tabindex="2"
             autocomplete="on"
@@ -46,54 +46,33 @@
       </el-tooltip>
 
       <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>
-
-      <div style="position:relative">
-        <div class="tips">
-          <span>Username : admin</span>
-          <span>Password : any</span>
-        </div>
-        <div class="tips">
-          <span style="margin-right:18px;">Username : editor</span>
-          <span>Password : any</span>
-        </div>
-
-        <el-button class="thirdparty-button" type="primary" @click="showDialog=true">
-          Or connect with
-        </el-button>
-      </div>
     </el-form>
 
-    <el-dialog title="Or connect with" :visible.sync="showDialog">
-      Can not be simulated on local, so please combine you own business simulation! ! !
-      <br>
-      <br>
-      <br>
-      <social-sign />
-    </el-dialog>
   </div>
 </template>
 
 <script>
-import SocialSign from './components/SocialSignin'
 
 export default {
   name: 'Login',
-  components: { SocialSign },
   data() {
     const validateUsername = (rule, value, callback) => {
+      if (value === '') {
+        callback(new Error('用户名不能为空'))
+      }
       callback()
     }
     const validatePassword = (rule, value, callback) => {
       if (value.length < 6) {
-        callback(new Error('The password can not be less than 6 digits'))
+        callback(new Error('密码不能少于6位'))
       } else {
         callback()
       }
     }
     return {
       loginForm: {
-        username: 'system',
-        password: '123456'
+        username: '',
+        password: ''
       },
       loginRules: {
         username: [{ required: true, trigger: 'blur', validator: validateUsername }],
@@ -102,7 +81,6 @@
       passwordType: 'password',
       capsTooltip: false,
       loading: false,
-      showDialog: false,
       redirect: undefined,
       otherQuery: {}
     }
diff --git a/frontend/src/views/operator/index.vue b/frontend/src/views/operator/index.vue
index 5956c3f..64ddeb5 100644
--- a/frontend/src/views/operator/index.vue
+++ b/frontend/src/views/operator/index.vue
@@ -1,9 +1,437 @@
+/* eslint-disable vue/valid-v-model */
 
 <template>
-  <div class="app-container" /></template>
+  <div class="app-container">
+    <div class="filter-container">
+      <div class="filter-item" style="margin-right:15px">账号</div>
+      <el-input
+        v-model="formData.opercode"
+        placeholder="管理员登录名"
+        style="width: 350px;margin-right:50px"
+        class="filter-item"
+      />
+      <div class="filter-item" style="margin-right:15px">状态</div>
+      <el-select
+        v-model="formData.status"
+        style="width:200px;margin-right:100px"
+        class="filter-item"
+      >
+        <el-option
+          v-for="item in statusOptions"
+          :key="item.value"
+          :label="item.label"
+          :value="item.value"
+        />
+      </el-select>
+      <el-button
+        style=""
+        class="filter-item"
+        type="primary"
+        icon="el-icon-search"
+        @click="handleFilter()"
+      >
+        搜索
+      </el-button>
+    </div>
+    <div class="filter-container">
+      <div class="filter-item" style="margin-right:15px">名称</div>
+      <el-input
+        v-model="formData.opername"
+        placeholder="管理员名称"
+        style="width: 350px;margin-right:50px"
+        class="filter-item"
+      />
+
+    </div>
+    <el-tooltip class="item" effect="dark" content="新用户密码123456" placement="top">
+      <el-button type="primary" icon="el-icon-circle-plus-outline" @click="addOperator()">新增</el-button>
+    </el-tooltip>
+
+    <el-table
+      :key="tableKey"
+      v-loading="listLoading"
+      :data="list"
+      border
+      fit
+      highlight-current-row
+      style="width: 100%;margin-top:10px"
+    >
+      <el-table-column label="管理员账号" width="150">
+        <template slot-scope="{row}">
+          <span>{{ row.opercode }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="管理员名称" align="center">
+        <template slot-scope="{row}">
+          <span>{{ row.opername }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" width="100">
+        <template slot-scope="{row}">
+          <div>
+            <span class="text_switch">
+              <el-switch
+                :value="row.status==='normal'"
+                active-text="正常"
+                inactive-text="注销"
+                active-color="#13ce66"
+                inactive-color="#A7A7A7"
+                disabled
+                @click.native.prevent="switchStatus(row)"
+              />
+            </span>
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column label="手机号" width="120" align="center">
+        <template slot-scope="{row}">
+          <span>{{ row.mobile }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="邮箱" align="center">
+        <template slot-scope="{row}">
+          <span>{{ row.email }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="注册时间" align="center" width="100">
+        <template slot-scope="{row}">
+          <span>{{ timeFormat(row.opendate) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="100">
+        <template slot-scope="{row}">
+          <el-tooltip class="item" effect="dark" content="修改" placement="bottom">
+            <el-button type="primary" icon="el-icon-edit" circle size="mini" @click="updateOperator(row)" />
+          </el-tooltip>
+          <el-tooltip class="item" effect="dark" content="重置密码" placement="bottom">
+            <el-button type="warning" icon="el-icon-refresh" circle size="mini" @click="resetPassword(row)" />
+          </el-tooltip>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="formData.pageno"
+      :limit.sync="formData.pagesize"
+      style="margin-top:0;"
+      @pagination="getOperatorList"
+    />
+    <el-dialog
+      :title="title"
+      :visible.sync="operatorDialogVisible"
+      width="30%"
+    ><div>
+       <el-form ref="operatorForm" :model="operatorForm" :rules="rules" label-width="100px">
+         <el-form-item label="登录账号" prop="opercode" class="form-input-item">
+           <el-input
+             v-model="operatorForm.opercode"
+             maxlength="16"
+             :disabled="opercodeDisable"
+             show-word-limit
+             style="width:80%"
+           />
+         </el-form-item>
+         <el-form-item label="用户名称" prop="opername" class="form-input-item">
+           <el-input
+             v-model="operatorForm.opername"
+             maxlength="30"
+             show-word-limit
+             style="width:80%"
+           />
+         </el-form-item>
+         <el-form-item label="角色" prop="roleid" class="form-input-item">
+           <el-select
+             v-model="operatorForm.roleid"
+             style="width:80%"
+           >
+             <el-option
+               v-for="item in roles"
+               :key="item.roleid"
+               :label="item.rolename"
+               :value="item.roleid"
+             />
+           </el-select>
+         </el-form-item>
+         <el-form-item label="手机号" prop="mobile" class="form-input-item">
+           <el-input
+             v-model="operatorForm.mobile"
+             style="width:80%"
+           />
+         </el-form-item>
+         <el-form-item label="邮箱" prop="email" class="form-input-item">
+           <el-input
+             v-model="operatorForm.email"
+             style="width:80%"
+           />
+         </el-form-item>
+       </el-form>
+     </div>
+      <div style="text-align:center">
+        <el-button
+          type="primary"
+          @click="saveOperator('operatorForm')"
+        >保存
+        </el-button>
+        <el-button @click="operatorDialogVisible = false">取消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
 <script>
+import { getAllRoles } from '@/api/role'
+import { getOperatorList, saveOperator, resetPassword, switchStatus } from '@/api/operator'
+import moment from 'moment'
+import Pagination from '@/components/Pagination'
 export default {
-  name: 'Operator'
+  name: 'Operator',
+  components: {
+    Pagination
+  },
+  data() {
+    var validateMobile = (rule, value, callback) => {
+      if (value !== null && value !== '') {
+        var reg = /^1[3456789]\d{9}$/
+        if (!reg.test(value)) {
+          callback(new Error('请输入有效的手机号码'))
+        }
+      }
+      callback()
+    }
+    var validateEmail = (rule, value, callback) => {
+      if (value !== null && value !== '') {
+        var reg = /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
+        if (!reg.test(value)) {
+          callback(new Error('请输入有效的邮箱'))
+        }
+      }
+      callback()
+    }
+    return {
+      ssss: true,
+      formData: {
+        opercode: '',
+        opername: '',
+        status: '',
+        roleid: '',
+        pageno: 1,
+        pagesize: 10
+      },
+      operatorForm: {
+        operid: '',
+        opercode: '',
+        opername: '',
+        roleid: '',
+        mobile: '',
+        email: ''
+      },
+      roles: [],
+      statusOptions: [{
+        value: '',
+        label: '所有'
+      },
+      {
+        value: 'normal',
+        label: '正常'
+      }, {
+        value: 'closed',
+        label: '注销'
+      }],
+      opercodeDisable: true,
+      listLoading: false,
+      tableKey: 0,
+      list: null,
+      total: 0,
+      title: '',
+      operatorDialogVisible: false,
+      rules: {
+        opercode: [
+          { required: true, message: '请输入用户名称', trigger: 'blur' }
+        ],
+        opername: [
+          { required: true, message: '请输入用户名称', trigger: 'blur' }
+        ],
+        roleid: [
+          { required: true, message: '请选择操作员角色', trigger: 'blur' }
+        ],
+        mobile: [
+          { validator: validateMobile, trigger: 'blur' }
+        ],
+        email: [
+          { validator: validateEmail, trigger: 'blur' }
+        ]
+      }
+    }
+  },
+  created() {
+    this.getAllRoles()
+    this.getOperatorList()
+  },
+  methods: {
+    getAllRoles() {
+      getAllRoles().then(response => {
+        this.roles = response.list
+      }).catch(error => {
+        this.$message({
+          message: error.msg || '请求异常',
+          type: 'error'
+        })
+      })
+    },
+    handleFilter() {
+      this.formData.pageno = 1
+      this.getOperatorList()
+    },
+    getOperatorList() {
+      getOperatorList(this.formData).then(response => {
+        if (response.page) {
+          this.list = response.page.list
+          this.total = response.page.totalCount
+        } else {
+          this.list = null
+          this.total = 0
+        }
+        this.listLoading = false
+      }).catch(error => {
+        this.$message({
+          message: error.msg || '请求异常',
+          type: 'error'
+        })
+        this.listLoading = false
+      })
+    },
+    addOperator() {
+      this.title = '新增管理员'
+      this.opercodeDisable = false
+      this.resetForm('operatorForm')
+      this.operatorDialogVisible = true
+    },
+    updateOperator(row) {
+      this.title = '信息修改'
+      this.opercodeDisable = true
+      this.resetForm('operatorForm')
+      this.operatorForm = Object.assign({}, row)
+      this.operatorDialogVisible = true
+    },
+    resetForm(formName) {
+      this.operatorForm = {
+        operid: '',
+        opercode: '',
+        opername: '',
+        roleid: '',
+        mobile: '',
+        email: ''
+      }
+      this.$nextTick(() => {
+        this.$refs[formName].clearValidate()
+      })
+    },
+    resetPassword(row) {
+      this.$confirm('确认重置此管理员的密码为123456吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        resetPassword(row.operid).then(response => {
+          this.$message({
+            type: 'success',
+            message: '重置密码成功!'
+          })
+        }).catch(error => {
+          this.$message({
+            message: error.msg || '请求异常',
+            type: 'error'
+          })
+        })
+      })
+    },
+    saveOperator(formName) {
+      this.$refs[formName].validate((valid) => {
+        if (valid) {
+          saveOperator(this.operatorForm).then(response => {
+            this.$notify({
+              title: '成功',
+              message: '保存成功!',
+              type: 'success',
+              duration: 2000
+            })
+            this.operatorDialogVisible = false
+            this.getOperatorList()
+          }).catch(error => {
+            this.$message({
+              message: error.msg || '请求异常',
+              type: 'error'
+            })
+          })
+        } else {
+          return false
+        }
+      })
+    },
+    switchStatus(row) {
+      switchStatus(row.operid, { status: row.status === 'normal' ? 'closed' : 'normal' }).then(response => {
+        this.$message({
+          type: 'success',
+          message: '操作成功!'
+        })
+        row.status = response.result
+      }).catch(error => {
+        this.$message({
+          message: error.msg || '请求异常',
+          type: 'error'
+        })
+      })
+    },
+    timeFormat(time) {
+      if (time === null) {
+        return ''
+      }
+      return moment(time, 'YYYYMMDD').format('YYYY-MM-DD')
+    }
+  }
 }
 </script>
+<style>
+.text_switch .el-switch__label--left {
+    position: relative;
+    left: 22px;
+    color: #fff;
+    margin-right: 0;
+    z-index: -1111;
+  }
+
+  .text_switch .el-switch__label--left span {
+    font-size: 10px;
+  }
+
+  .text_switch .el-switch__core {
+    width: 55px !important;
+    position: absolute;
+  }
+
+  .text_switch .el-switch__label--right {
+    position: relative;
+    right: 25px;
+    color: #fff;
+    z-index: -1111;
+  }
+
+  .text_switch .el-switch__label--right span {
+    font-size: 10px;
+  }
+
+  .text_switch .el-switch__label.is-active {
+    z-index: 1111;
+    color: #fff;
+  }
+
+  .text_switch .el-switch.is-disabled {
+    opacity: 1;
+  }
+
+  .text_switch .el-switch.is-disabled .el-switch__core, .el-switch.is-disabled .el-switch__label {
+    cursor: pointer;
+  }
+</style>