From 8200ea822f20e90d9d9fede623605cab8e05e391 Mon Sep 17 00:00:00 2001
From: Charles7c <charles7c@126.com>
Date: Sun, 26 Feb 2023 00:19:56 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E6=96=B0=E5=A2=9E?=
 =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=AE=A1=E7=90=86/=E5=B2=97=E4=BD=8D?=
 =?UTF-8?q?=E7=AE=A1=E7=90=86=EF=BC=88=E5=88=97=E8=A1=A8=E3=80=81=E6=9F=A5?=
 =?UTF-8?q?=E7=9C=8B=E8=AF=A6=E6=83=85=E3=80=81=E6=96=B0=E5=A2=9E=E3=80=81?=
 =?UTF-8?q?=E4=BF=AE=E6=94=B9=E3=80=81=E5=88=A0=E9=99=A4=E3=80=81=E5=AF=BC?=
 =?UTF-8?q?=E5=87=BA=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../cnadmin/common/model/dto/LoginUser.java   |   2 +-
 .../cnadmin/auth/model/vo/UserInfoVO.java     |   4 +-
 .../cnadmin/system/mapper/PostMapper.java     |  28 +
 .../cnadmin/system/mapper/UserPostMapper.java |  28 +
 .../cnadmin/system/model/entity/PostDO.java   |  64 ++
 .../cnadmin/system/model/entity/RoleDO.java   |   2 +-
 .../cnadmin/system/model/entity/UserDO.java   |   2 +-
 .../system/model/entity/UserPostDO.java       |  53 ++
 .../cnadmin/system/model/query/PostQuery.java |  55 ++
 .../system/model/request/PostRequest.java     |  78 +++
 .../system/model/request/UserRequest.java     |   6 +
 .../cnadmin/system/model/vo/PostDetailVO.java |  77 +++
 .../cnadmin/system/model/vo/PostVO.java       |  69 +++
 .../cnadmin/system/model/vo/UserDetailVO.java |  40 +-
 .../cnadmin/system/service/PostService.java   |  53 ++
 .../system/service/UserPostService.java       |  56 ++
 .../cnadmin/system/service/UserService.java   |   9 -
 .../system/service/impl/PostServiceImpl.java  | 113 ++++
 .../system/service/impl/RoleServiceImpl.java  |  15 +-
 .../service/impl/UserPostServiceImpl.java     |  74 +++
 .../system/service/impl/UserServiceImpl.java  |  30 +-
 continew-admin-ui/src/api/common/index.ts     |  17 +-
 continew-admin-ui/src/api/system/post.ts      |  54 ++
 continew-admin-ui/src/api/system/user.ts      |   7 +-
 continew-admin-ui/src/locale/en-US.ts         |   2 +
 continew-admin-ui/src/locale/zh-CN.ts         |   2 +
 .../src/router/routes/modules/system.ts       |  10 +
 .../src/views/system/dept/index.vue           |   2 +-
 .../src/views/system/post/index.vue           | 552 ++++++++++++++++++
 .../src/views/system/post/locale/en-US.ts     |   3 +
 .../src/views/system/post/locale/zh-CN.ts     |   3 +
 .../src/views/system/user/index.vue           | 119 ++--
 .../controller/common/CommonController.java   |  16 +-
 .../controller/system/PostController.java     |  40 ++
 .../changelog/v0.0.1/continew-admin_data.sql  |  50 +-
 .../changelog/v0.0.1/continew-admin_table.sql |  23 +-
 36 files changed, 1656 insertions(+), 102 deletions(-)
 create mode 100644 continew-admin-system/src/main/java/top/charles7c/cnadmin/system/mapper/PostMapper.java
 create mode 100644 continew-admin-system/src/main/java/top/charles7c/cnadmin/system/mapper/UserPostMapper.java
 create mode 100644 continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/PostDO.java
 create mode 100644 continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/UserPostDO.java
 create mode 100644 continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/query/PostQuery.java
 create mode 100644 continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/PostRequest.java
 create mode 100644 continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/vo/PostDetailVO.java
 create mode 100644 continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/vo/PostVO.java
 create mode 100644 continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/PostService.java
 create mode 100644 continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/UserPostService.java
 create mode 100644 continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/PostServiceImpl.java
 create mode 100644 continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/UserPostServiceImpl.java
 create mode 100644 continew-admin-ui/src/api/system/post.ts
 create mode 100644 continew-admin-ui/src/views/system/post/index.vue
 create mode 100644 continew-admin-ui/src/views/system/post/locale/en-US.ts
 create mode 100644 continew-admin-ui/src/views/system/post/locale/zh-CN.ts
 create mode 100644 continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/system/PostController.java

diff --git a/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/dto/LoginUser.java b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/dto/LoginUser.java
index ac927b44..7952bb28 100644
--- a/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/dto/LoginUser.java
+++ b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/dto/LoginUser.java
@@ -75,7 +75,7 @@ public class LoginUser implements Serializable {
     private String description;
 
     /**
-     * 最后一次修改密码的时间
+     * 最后一次修改密码时间
      */
     private LocalDateTime pwdResetTime;
 
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/auth/model/vo/UserInfoVO.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/auth/model/vo/UserInfoVO.java
index e60b6412..ce1f745f 100644
--- a/continew-admin-system/src/main/java/top/charles7c/cnadmin/auth/model/vo/UserInfoVO.java
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/auth/model/vo/UserInfoVO.java
@@ -93,9 +93,9 @@ public class UserInfoVO implements Serializable {
     private String description;
 
     /**
-     * 最后一次修改密码的时间
+     * 最后一次修改密码时间
      */
-    @Schema(description = "最后一次修改密码的时间")
+    @Schema(description = "最后一次修改密码时间")
     private LocalDateTime pwdResetTime;
 
     /**
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/mapper/PostMapper.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/mapper/PostMapper.java
new file mode 100644
index 00000000..478d1000
--- /dev/null
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/mapper/PostMapper.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.charles7c.cnadmin.system.mapper;
+
+import top.charles7c.cnadmin.common.base.BaseMapper;
+import top.charles7c.cnadmin.system.model.entity.PostDO;
+
+/**
+ * 岗位 Mapper
+ *
+ * @author Charles7c
+ * @since 2023/2/25 22:26
+ */
+public interface PostMapper extends BaseMapper<PostDO> {}
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/mapper/UserPostMapper.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/mapper/UserPostMapper.java
new file mode 100644
index 00000000..803c7448
--- /dev/null
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/mapper/UserPostMapper.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.charles7c.cnadmin.system.mapper;
+
+import top.charles7c.cnadmin.common.base.BaseMapper;
+import top.charles7c.cnadmin.system.model.entity.UserPostDO;
+
+/**
+ * 用户和岗位 Mapper
+ *
+ * @author Charles7c
+ * @since 2023/2/25 22:28
+ */
+public interface UserPostMapper extends BaseMapper<UserPostDO> {}
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/PostDO.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/PostDO.java
new file mode 100644
index 00000000..a03b4c0f
--- /dev/null
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/PostDO.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.charles7c.cnadmin.system.model.entity;
+
+import lombok.Data;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+import top.charles7c.cnadmin.common.base.BaseDO;
+import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
+
+/**
+ * 岗位实体
+ *
+ * @author Charles7c
+ * @since 2023/2/25 22:25
+ */
+@Data
+@TableName("sys_post")
+public class PostDO extends BaseDO {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 岗位 ID
+     */
+    @TableId
+    private Long postId;
+
+    /**
+     * 岗位名称
+     */
+    private String postName;
+
+    /**
+     * 描述
+     */
+    private String description;
+
+    /**
+     * 岗位排序
+     */
+    private Integer postSort;
+
+    /**
+     * 状态(1启用 2禁用)
+     */
+    private DisEnableStatusEnum status;
+}
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/RoleDO.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/RoleDO.java
index 7428d51e..42237425 100644
--- a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/RoleDO.java
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/RoleDO.java
@@ -32,7 +32,7 @@ import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
  * @since 2023/2/8 22:54
  */
 @Data
-@TableName(value = "sys_role", autoResultMap = true)
+@TableName("sys_role")
 public class RoleDO extends BaseDO {
 
     private static final long serialVersionUID = 1L;
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/UserDO.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/UserDO.java
index 5d490c95..85f17711 100644
--- a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/UserDO.java
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/UserDO.java
@@ -91,7 +91,7 @@ public class UserDO extends BaseDO {
     private DisEnableStatusEnum status;
 
     /**
-     * 最后一次修改密码的时间
+     * 最后一次修改密码时间
      */
     private LocalDateTime pwdResetTime;
 
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/UserPostDO.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/UserPostDO.java
new file mode 100644
index 00000000..1638b0f2
--- /dev/null
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/entity/UserPostDO.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.charles7c.cnadmin.system.model.entity;
+
+import java.io.Serializable;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+
+/**
+ * 用户和岗位实体
+ *
+ * @author Charles7c
+ * @since 2023/2/25 22:28
+ */
+@Data
+@NoArgsConstructor
+@TableName("sys_user_post")
+public class UserPostDO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户 ID
+     */
+    private Long userId;
+
+    /**
+     * 岗位 ID
+     */
+    private Long postId;
+
+    public UserPostDO(Long userId, Long postId) {
+        this.userId = userId;
+        this.postId = postId;
+    }
+}
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/query/PostQuery.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/query/PostQuery.java
new file mode 100644
index 00000000..3e9fb71e
--- /dev/null
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/query/PostQuery.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.charles7c.cnadmin.system.model.query;
+
+import java.io.Serializable;
+
+import lombok.Data;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import org.springdoc.api.annotations.ParameterObject;
+
+import top.charles7c.cnadmin.common.annotation.Query;
+
+/**
+ * 岗位查询条件
+ *
+ * @author Charles7c
+ * @since 2023/2/25 22:30
+ */
+@Data
+@ParameterObject
+@Schema(description = "岗位查询条件")
+public class PostQuery implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 岗位名称
+     */
+    @Schema(description = "岗位名称")
+    @Query(type = Query.Type.INNER_LIKE)
+    private String postName;
+
+    /**
+     * 状态(1启用 2禁用)
+     */
+    @Schema(description = "状态(1启用 2禁用)")
+    @Query
+    private Integer status;
+}
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/PostRequest.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/PostRequest.java
new file mode 100644
index 00000000..dd5722c1
--- /dev/null
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/PostRequest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.charles7c.cnadmin.system.model.request;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Null;
+
+import lombok.Data;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import org.hibernate.validator.constraints.Length;
+
+import top.charles7c.cnadmin.common.base.BaseRequest;
+import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
+
+/**
+ * 创建或修改岗位信息
+ *
+ * @author Charles7c
+ * @since 2023/2/25 22:31
+ */
+@Data
+@Schema(description = "创建或修改岗位信息")
+public class PostRequest extends BaseRequest {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 岗位 ID
+     */
+    @Schema(description = "岗位 ID")
+    @Null(message = "新增时,ID 必须为空", groups = Create.class)
+    @NotNull(message = "修改时,ID 不能为空", groups = Update.class)
+    private Long postId;
+
+    /**
+     * 岗位名称
+     */
+    @Schema(description = "岗位名称")
+    @NotBlank(message = "岗位名称不能为空")
+    private String postName;
+
+    /**
+     * 岗位排序
+     */
+    @Schema(description = "岗位排序")
+    @NotNull(message = "岗位排序不能为空")
+    private Integer postSort;
+
+    /**
+     * 描述
+     */
+    @Schema(description = "描述")
+    @Length(max = 200, message = "描述长度不能超过 {max} 个字符")
+    private String description;
+
+    /**
+     * 状态(1启用 2禁用)
+     */
+    @Schema(description = "状态(1启用 2禁用)", type = "Integer", allowableValues = {"1", "2"})
+    private DisEnableStatusEnum status;
+}
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/UserRequest.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/UserRequest.java
index 7600fa88..884b96e6 100644
--- a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/UserRequest.java
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/UserRequest.java
@@ -107,6 +107,12 @@ public class UserRequest extends BaseRequest {
     @NotNull(message = "所属部门不能为空")
     private Long deptId;
 
+    /**
+     * 岗位 ID 列表
+     */
+    @Schema(description = "所属岗位")
+    private List<Long> postIds;
+
     /**
      * 角色 ID 列表
      */
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/vo/PostDetailVO.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/vo/PostDetailVO.java
new file mode 100644
index 00000000..f0b175a7
--- /dev/null
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/vo/PostDetailVO.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.charles7c.cnadmin.system.model.vo;
+
+import lombok.Data;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+
+import top.charles7c.cnadmin.common.base.BaseDetailVO;
+import top.charles7c.cnadmin.common.config.easyexcel.ExcelBaseEnumConverter;
+import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
+
+/**
+ * 岗位详情信息
+ *
+ * @author Charles7c
+ * @since 2023/2/25 22:35
+ */
+@Data
+@ExcelIgnoreUnannotated
+@Schema(description = "岗位详情信息")
+public class PostDetailVO extends BaseDetailVO {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 岗位 ID
+     */
+    @Schema(description = "岗位 ID")
+    @ExcelProperty(value = "岗位ID")
+    private Long postId;
+
+    /**
+     * 岗位名称
+     */
+    @Schema(description = "岗位名称")
+    @ExcelProperty(value = "岗位名称")
+    private String postName;
+
+    /**
+     * 岗位排序
+     */
+    @Schema(description = "岗位排序")
+    @ExcelProperty(value = "岗位排序")
+    private Integer postSort;
+
+    /**
+     * 状态(1启用 2禁用)
+     */
+    @Schema(description = "状态(1启用 2禁用)")
+    @ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class)
+    private DisEnableStatusEnum status;
+
+    /**
+     * 描述
+     */
+    @Schema(description = "描述")
+    @ExcelProperty(value = "描述")
+    private String description;
+}
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/vo/PostVO.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/vo/PostVO.java
new file mode 100644
index 00000000..9d1506b4
--- /dev/null
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/vo/PostVO.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.charles7c.cnadmin.system.model.vo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import top.charles7c.cnadmin.common.base.BaseVO;
+import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
+
+/**
+ * 岗位信息
+ *
+ * @author Charles7c
+ * @since 2023/2/25 22:34
+ */
+@Data
+@Accessors(chain = true)
+@Schema(description = "岗位信息")
+public class PostVO extends BaseVO {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 岗位 ID
+     */
+    @Schema(description = "岗位 ID")
+    private Long postId;
+
+    /**
+     * 岗位名称
+     */
+    @Schema(description = "岗位名称")
+    private String postName;
+
+    /**
+     * 岗位排序
+     */
+    @Schema(description = "岗位排序")
+    private Integer postSort;
+
+    /**
+     * 状态(1启用 2禁用)
+     */
+    @Schema(description = "状态(1启用 2禁用)")
+    private DisEnableStatusEnum status;
+
+    /**
+     * 描述
+     */
+    @Schema(description = "描述")
+    private String description;
+}
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/vo/UserDetailVO.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/vo/UserDetailVO.java
index 74edf5ed..da52af81 100644
--- a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/vo/UserDetailVO.java
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/vo/UserDetailVO.java
@@ -16,6 +16,7 @@
 
 package top.charles7c.cnadmin.system.model.vo;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 import lombok.Data;
@@ -107,17 +108,10 @@ public class UserDetailVO extends BaseDetailVO {
     private String description;
 
     /**
-     * 角色 ID 列表
+     * 最后一次修改密码时间
      */
-    @Schema(description = "角色 ID 列表")
-    private List<Long> roleIds;
-
-    /**
-     * 所属角色
-     */
-    @Schema(description = "所属角色")
-    @ExcelProperty(value = "所属角色")
-    private String roleNames;
+    @Schema(description = "最后一次修改密码时间")
+    private LocalDateTime pwdResetTime;
 
     /**
      * 部门 ID
@@ -131,4 +125,30 @@ public class UserDetailVO extends BaseDetailVO {
     @Schema(description = "所属部门")
     @ExcelProperty(value = "所属部门")
     private String deptName;
+
+    /**
+     * 岗位 ID 列表
+     */
+    @Schema(description = "岗位 ID 列表")
+    private List<Long> postIds;
+
+    /**
+     * 所属岗位
+     */
+    @Schema(description = "所属岗位")
+    @ExcelProperty(value = "所属岗位")
+    private String postNames;
+
+    /**
+     * 角色 ID 列表
+     */
+    @Schema(description = "角色 ID 列表")
+    private List<Long> roleIds;
+
+    /**
+     * 所属角色
+     */
+    @Schema(description = "所属角色")
+    @ExcelProperty(value = "所属角色")
+    private String roleNames;
 }
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/PostService.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/PostService.java
new file mode 100644
index 00000000..b5e3548e
--- /dev/null
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/PostService.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.charles7c.cnadmin.system.service;
+
+import java.util.List;
+
+import top.charles7c.cnadmin.common.base.BaseService;
+import top.charles7c.cnadmin.common.model.vo.LabelValueVO;
+import top.charles7c.cnadmin.system.model.query.PostQuery;
+import top.charles7c.cnadmin.system.model.request.PostRequest;
+import top.charles7c.cnadmin.system.model.vo.PostDetailVO;
+import top.charles7c.cnadmin.system.model.vo.PostVO;
+
+/**
+ * 岗位业务接口
+ *
+ * @author Charles7c
+ * @since 2023/2/25 22:38
+ */
+public interface PostService extends BaseService<PostVO, PostDetailVO, PostQuery, PostRequest> {
+
+    /**
+     * 构建字典
+     *
+     * @param list
+     *            原始列表数据
+     * @return 字典列表
+     */
+    List<LabelValueVO<Long>> buildDict(List<PostVO> list);
+
+    /**
+     * 根据岗位 ID 列表查询
+     *
+     * @param postIds
+     *            岗位 ID 列表
+     * @return 岗位名称列表
+     */
+    List<String> listPostNamesByPostIds(List<Long> postIds);
+}
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/UserPostService.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/UserPostService.java
new file mode 100644
index 00000000..1d6ce5f2
--- /dev/null
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/UserPostService.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.charles7c.cnadmin.system.service;
+
+import java.util.List;
+
+/**
+ * 用户和岗位业务接口
+ *
+ * @author Charles7c
+ * @since 2023/2/25 22:40
+ */
+public interface UserPostService {
+
+    /**
+     * 保存
+     *
+     * @param postIds
+     *            岗位 ID 列表
+     * @param userId
+     *            用户 ID
+     */
+    void save(List<Long> postIds, Long userId);
+
+    /**
+     * 根据岗位 ID 列表查询
+     *
+     * @param postIds
+     *            岗位 ID 列表
+     * @return 总记录数
+     */
+    Long countByPostIds(List<Long> postIds);
+
+    /**
+     * 根据用户 ID 查询
+     *
+     * @param userId
+     *            用户 ID
+     * @return 岗位 ID 列表
+     */
+    List<Long> listPostIdsByUserId(Long userId);
+}
\ No newline at end of file
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/UserService.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/UserService.java
index 05c4d24b..f44a67bd 100644
--- a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/UserService.java
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/UserService.java
@@ -106,13 +106,4 @@ public interface UserService extends BaseService<UserVO, UserDetailVO, UserQuery
      * @return 用户数量
      */
     Long countByDeptIds(List<Long> deptIds);
-
-    /**
-     * 根据角色 ID 列表查询
-     *
-     * @param roleIds
-     *            角色 ID 列表
-     * @return 用户数量
-     */
-    Long countByRoleIds(List<Long> roleIds);
 }
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/PostServiceImpl.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/PostServiceImpl.java
new file mode 100644
index 00000000..bcf96f7c
--- /dev/null
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/PostServiceImpl.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.charles7c.cnadmin.system.service.impl;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import lombok.RequiredArgsConstructor;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import cn.hutool.core.collection.CollUtil;
+
+import top.charles7c.cnadmin.common.base.BaseServiceImpl;
+import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
+import top.charles7c.cnadmin.common.model.vo.LabelValueVO;
+import top.charles7c.cnadmin.common.util.validate.CheckUtils;
+import top.charles7c.cnadmin.system.mapper.PostMapper;
+import top.charles7c.cnadmin.system.model.entity.PostDO;
+import top.charles7c.cnadmin.system.model.query.PostQuery;
+import top.charles7c.cnadmin.system.model.request.PostRequest;
+import top.charles7c.cnadmin.system.model.vo.*;
+import top.charles7c.cnadmin.system.service.*;
+
+/**
+ * 岗位业务实现类
+ *
+ * @author Charles7c
+ * @since 2023/2/25 22:38
+ */
+@Service
+@RequiredArgsConstructor
+public class PostServiceImpl extends BaseServiceImpl<PostMapper, PostDO, PostVO, PostDetailVO, PostQuery, PostRequest>
+    implements PostService {
+
+    private final UserPostService userPostService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long add(PostRequest request) {
+        String postName = request.getPostName();
+        boolean isExists = this.checkNameExists(postName, request.getPostId());
+        CheckUtils.throwIf(() -> isExists, String.format("新增失败,'%s'已存在", postName));
+
+        // 新增岗位
+        request.setStatus(DisEnableStatusEnum.ENABLE);
+        return super.add(request);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void update(PostRequest request) {
+        String postName = request.getPostName();
+        boolean isExists = this.checkNameExists(postName, request.getPostId());
+        CheckUtils.throwIf(() -> isExists, String.format("修改失败,'%s'已存在", postName));
+
+        // 更新岗位
+        super.update(request);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delete(List<Long> ids) {
+        CheckUtils.throwIf(() -> userPostService.countByPostIds(ids) > 0, "所选岗位存在用户关联,请解除关联后重试");
+        super.delete(ids);
+    }
+
+    /**
+     * 检查名称是否存在
+     *
+     * @param name
+     *            名称
+     * @param id
+     *            ID
+     * @return 是否存在
+     */
+    private boolean checkNameExists(String name, Long id) {
+        return super.lambdaQuery().eq(PostDO::getPostName, name).ne(id != null, PostDO::getPostId, id).exists();
+    }
+
+    @Override
+    public List<LabelValueVO<Long>> buildDict(List<PostVO> list) {
+        if (CollUtil.isEmpty(list)) {
+            return Collections.emptyList();
+        }
+        return list.stream().map(p -> new LabelValueVO<>(p.getPostName(), p.getPostId())).collect(Collectors.toList());
+    }
+
+    @Override
+    public List<String> listPostNamesByPostIds(List<Long> postIds) {
+        List<PostDO> postList = super.lambdaQuery().select(PostDO::getPostName).in(PostDO::getPostId, postIds).list();
+        if (CollUtil.isEmpty(postList)) {
+            return Collections.emptyList();
+        }
+        return postList.stream().map(PostDO::getPostName).collect(Collectors.toList());
+    }
+}
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/RoleServiceImpl.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/RoleServiceImpl.java
index 384f8abf..bb7fd5eb 100644
--- a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/RoleServiceImpl.java
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/RoleServiceImpl.java
@@ -20,8 +20,6 @@ import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
-import javax.annotation.Resource;
-
 import lombok.RequiredArgsConstructor;
 
 import org.springframework.stereotype.Service;
@@ -57,15 +55,14 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleVO,
     private final RoleMenuService roleMenuService;
     private final RoleDeptService roleDeptService;
     private final MenuService menuService;
-    @Resource
-    private UserService userService;
+    private final UserRoleService userRoleService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long add(RoleRequest request) {
         String roleName = request.getRoleName();
-        boolean isExist = this.checkNameExists(roleName, request.getRoleId());
-        CheckUtils.throwIf(() -> isExist, String.format("新增失败,'%s'已存在", roleName));
+        boolean isExists = this.checkNameExists(roleName, request.getRoleId());
+        CheckUtils.throwIf(() -> isExists, String.format("新增失败,'%s'已存在", roleName));
 
         // 新增角色
         request.setStatus(DisEnableStatusEnum.ENABLE);
@@ -81,8 +78,8 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleVO,
     @Transactional(rollbackFor = Exception.class)
     public void update(RoleRequest request) {
         String roleName = request.getRoleName();
-        boolean isExist = this.checkNameExists(roleName, request.getRoleId());
-        CheckUtils.throwIf(() -> isExist, String.format("修改失败,'%s'已存在", roleName));
+        boolean isExists = this.checkNameExists(roleName, request.getRoleId());
+        CheckUtils.throwIf(() -> isExists, String.format("修改失败,'%s'已存在", roleName));
 
         // 更新角色
         super.update(request);
@@ -96,7 +93,7 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleVO,
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void delete(List<Long> ids) {
-        CheckUtils.throwIf(() -> userService.countByRoleIds(ids) > 0, "所选角色存在用户关联,请解除关联后重试");
+        CheckUtils.throwIf(() -> userRoleService.countByRoleIds(ids) > 0, "所选角色存在用户关联,请解除关联后重试");
         super.delete(ids);
     }
 
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/UserPostServiceImpl.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/UserPostServiceImpl.java
new file mode 100644
index 00000000..53d15dd5
--- /dev/null
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/UserPostServiceImpl.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.charles7c.cnadmin.system.service.impl;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import lombok.RequiredArgsConstructor;
+
+import org.springframework.stereotype.Service;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+
+import cn.hutool.core.collection.CollUtil;
+
+import top.charles7c.cnadmin.system.mapper.UserPostMapper;
+import top.charles7c.cnadmin.system.model.entity.UserPostDO;
+import top.charles7c.cnadmin.system.service.UserPostService;
+
+/**
+ * 用户和岗位业务实现类
+ *
+ * @author Charles7c
+ * @since 2023/2/25 22:40
+ */
+@Service
+@RequiredArgsConstructor
+public class UserPostServiceImpl implements UserPostService {
+
+    private final UserPostMapper userPostMapper;
+
+    @Override
+    public void save(List<Long> postIds, Long userId) {
+        if (CollUtil.isEmpty(postIds)) {
+            return;
+        }
+        // 删除原有关联
+        userPostMapper.delete(Wrappers.<UserPostDO>lambdaQuery().eq(UserPostDO::getUserId, userId));
+        // 保存最新关联
+        List<UserPostDO> userPostList =
+            postIds.stream().map(postId -> new UserPostDO(userId, postId)).collect(Collectors.toList());
+        userPostMapper.insertBatch(userPostList);
+    }
+
+    @Override
+    public Long countByPostIds(List<Long> postIds) {
+        return userPostMapper.selectCount(Wrappers.<UserPostDO>lambdaQuery().in(UserPostDO::getPostId, postIds));
+    }
+
+    @Override
+    public List<Long> listPostIdsByUserId(Long userId) {
+        List<UserPostDO> userPostList = userPostMapper.selectList(
+            Wrappers.<UserPostDO>lambdaQuery().select(UserPostDO::getPostId).eq(UserPostDO::getUserId, userId));
+        if (CollUtil.isEmpty(userPostList)) {
+            return Collections.emptyList();
+        }
+        return userPostList.stream().map(UserPostDO::getPostId).collect(Collectors.toList());
+    }
+}
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/UserServiceImpl.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/UserServiceImpl.java
index 79c92c76..71cd2b7c 100644
--- a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/UserServiceImpl.java
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/UserServiceImpl.java
@@ -51,10 +51,7 @@ import top.charles7c.cnadmin.system.model.request.UpdateUserRoleRequest;
 import top.charles7c.cnadmin.system.model.request.UserRequest;
 import top.charles7c.cnadmin.system.model.vo.UserDetailVO;
 import top.charles7c.cnadmin.system.model.vo.UserVO;
-import top.charles7c.cnadmin.system.service.DeptService;
-import top.charles7c.cnadmin.system.service.RoleService;
-import top.charles7c.cnadmin.system.service.UserRoleService;
-import top.charles7c.cnadmin.system.service.UserService;
+import top.charles7c.cnadmin.system.service.*;
 
 /**
  * 用户业务实现类
@@ -67,19 +64,20 @@ import top.charles7c.cnadmin.system.service.UserService;
 public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserVO, UserDetailVO, UserQuery, UserRequest>
     implements UserService, CommonUserService {
 
+    private final UserPostService userPostService;
+    private final PostService postService;
     private final UserRoleService userRoleService;
+    private final RoleService roleService;
     private final LocalStorageProperties localStorageProperties;
     @Resource
-    private RoleService roleService;
-    @Resource
     private DeptService deptService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long add(UserRequest request) {
         String username = request.getUsername();
-        boolean isExist = this.checkNameExists(username, request.getUserId());
-        CheckUtils.throwIf(() -> isExist, String.format("新增失败,'%s'已存在", username));
+        boolean isExists = this.checkNameExists(username, request.getUserId());
+        CheckUtils.throwIf(() -> isExists, String.format("新增失败,'%s'已存在", username));
 
         // 新增用户
         request.setStatus(DisEnableStatusEnum.ENABLE);
@@ -87,6 +85,8 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserVO,
         super.lambdaUpdate()
             .set(UserDO::getPassword, SecureUtils.md5Salt(Constants.DEFAULT_PASSWORD, userId.toString()))
             .set(UserDO::getPwdResetTime, LocalDateTime.now()).eq(UserDO::getUserId, userId).update();
+        // 保存用户和岗位关联
+        userPostService.save(request.getPostIds(), userId);
         // 保存用户和角色关联
         userRoleService.save(request.getRoleIds(), userId);
         return userId;
@@ -96,12 +96,14 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserVO,
     @Transactional(rollbackFor = Exception.class)
     public void update(UserRequest request) {
         String username = request.getUsername();
-        boolean isExist = this.checkNameExists(username, request.getUserId());
-        CheckUtils.throwIf(() -> isExist, String.format("修改失败,'%s'已存在", username));
+        boolean isExists = this.checkNameExists(username, request.getUserId());
+        CheckUtils.throwIf(() -> isExists, String.format("修改失败,'%s'已存在", username));
 
         // 更新用户
         super.update(request);
         Long userId = request.getUserId();
+        // 保存用户和岗位关联
+        userPostService.save(request.getPostIds(), userId);
         // 保存用户和角色关联
         userRoleService.save(request.getRoleIds(), userId);
     }
@@ -128,6 +130,9 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserVO,
             List<Long> roleIds = userRoleService.listRoleIdsByUserId(detailVO.getUserId());
             detailVO.setRoleIds(roleIds);
             detailVO.setRoleNames(String.join(",", roleService.listRoleNamesByRoleIds(roleIds)));
+            List<Long> postIds = userPostService.listPostIdsByUserId(detailVO.getUserId());
+            detailVO.setPostIds(postIds);
+            detailVO.setPostNames(String.join(",", postService.listPostNamesByPostIds(postIds)));
         }
     }
 
@@ -227,11 +232,6 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserVO,
         return super.lambdaQuery().in(UserDO::getDeptId, deptIds).count();
     }
 
-    @Override
-    public Long countByRoleIds(List<Long> roleIds) {
-        return userRoleService.countByRoleIds(roleIds);
-    }
-
     @Override
     public String getNicknameById(Long userId) {
         return super.getById(userId).getNickname();
diff --git a/continew-admin-ui/src/api/common/index.ts b/continew-admin-ui/src/api/common/index.ts
index 80d67b76..71a63b5d 100644
--- a/continew-admin-ui/src/api/common/index.ts
+++ b/continew-admin-ui/src/api/common/index.ts
@@ -3,8 +3,14 @@ import qs from 'query-string';
 import { DeptParam } from '@/api/system/dept';
 import { MenuParam } from '@/api/system/menu';
 import { RoleParam } from '@/api/system/role';
+import { PostParam } from '@/api/system/post';
 import { TreeNodeData } from '@arco-design/web-vue';
 
+export interface LabelValueRecord {
+  label: string;
+  value: any;
+}
+
 export function listDeptTree(params: DeptParam) {
   return axios.get<TreeNodeData[]>('/common/tree/dept', {
     params,
@@ -24,10 +30,19 @@ export function listMenuTree(params: MenuParam) {
 }
 
 export function listRoleDict(params: RoleParam) {
-  return axios.get<TreeNodeData[]>('/common/dict/role', {
+  return axios.get<LabelValueRecord[]>('/common/dict/role', {
     params,
     paramsSerializer: (obj) => {
       return qs.stringify(obj);
     },
   });
 }
+
+export function listPostDict(params: PostParam) {
+  return axios.get<LabelValueRecord[]>('/common/dict/post', {
+    params,
+    paramsSerializer: (obj) => {
+      return qs.stringify(obj);
+    },
+  });
+}
\ No newline at end of file
diff --git a/continew-admin-ui/src/api/system/post.ts b/continew-admin-ui/src/api/system/post.ts
new file mode 100644
index 00000000..ff33ceb4
--- /dev/null
+++ b/continew-admin-ui/src/api/system/post.ts
@@ -0,0 +1,54 @@
+import axios from 'axios';
+import qs from 'query-string';
+
+const BASE_URL = '/system/post';
+
+export interface PostRecord {
+  postId?: number;
+  postName: string;
+  postSort?: number;
+  description?: string;
+  status?: number;
+  createUserString?: string;
+  createTime?: string;
+  updateUserString?: string;
+  updateTime?: string;
+}
+
+export interface PostParam {
+  postName?: string;
+  status?: number;
+  page?: number;
+  size?: number;
+  sort?: Array<string>;
+}
+
+export interface PostListRes {
+  list: PostRecord[];
+  total: number;
+}
+
+export function listPost(params: PostParam) {
+  return axios.get<PostListRes>(`${BASE_URL}`, {
+    params,
+    paramsSerializer: (obj) => {
+      return qs.stringify(obj);
+    },
+  });
+}
+
+export function getPost(id: number) {
+  return axios.get<PostRecord>(`${BASE_URL}/${id}`);
+}
+
+export function addPost(req: PostRecord) {
+  return axios.post(BASE_URL, req);
+}
+
+export function updatePost(req: PostRecord) {
+  return axios.put(BASE_URL, req);
+}
+
+export function deletePost(ids: number | Array<number>) {
+  return axios.delete(`${BASE_URL}/${ids}`);
+}
diff --git a/continew-admin-ui/src/api/system/user.ts b/continew-admin-ui/src/api/system/user.ts
index 0b888c58..a122dec9 100644
--- a/continew-admin-ui/src/api/system/user.ts
+++ b/continew-admin-ui/src/api/system/user.ts
@@ -11,14 +11,17 @@ export interface UserRecord {
   email?: string;
   phone?: string;
   description?: string;
-  roleIds?: Array<number>;
-  deptId?: number;
   status?: number;
+  pwdResetTime?: string;
   createUserString?: string;
   createTime?: string;
   updateUserString?: string;
   updateTime?: string;
+  deptId?: number;
   deptName?: string;
+  postIds?: Array<number>;
+  postNames?: Array<string>;
+  roleIds?: Array<number>;
   roleNames?: Array<string>;
   disabled?: boolean;
 }
diff --git a/continew-admin-ui/src/locale/en-US.ts b/continew-admin-ui/src/locale/en-US.ts
index 10d1262e..3be651ac 100644
--- a/continew-admin-ui/src/locale/en-US.ts
+++ b/continew-admin-ui/src/locale/en-US.ts
@@ -4,6 +4,7 @@ import localeUser from '@/views/system/user/locale/en-US';
 import localeRole from '@/views/system/role/locale/en-US';
 import localeMenu from '@/views/system/menu/locale/en-US';
 import localeDept from '@/views/system/dept/locale/en-US';
+import localePost from '@/views/system/post/locale/en-US';
 
 import localeOnlineUser from '@/views/monitor/online/locale/en-US';
 import localeLoginLog from '@/views/monitor/log/login/locale/en-US';
@@ -55,6 +56,7 @@ export default {
   ...localeRole,
   ...localeMenu,
   ...localeDept,
+  ...localePost,
 
   ...localeOnlineUser,
   ...localeLoginLog,
diff --git a/continew-admin-ui/src/locale/zh-CN.ts b/continew-admin-ui/src/locale/zh-CN.ts
index 88774c06..81f81e5f 100644
--- a/continew-admin-ui/src/locale/zh-CN.ts
+++ b/continew-admin-ui/src/locale/zh-CN.ts
@@ -4,6 +4,7 @@ import localeUser from '@/views/system/user/locale/zh-CN';
 import localeRole from '@/views/system/role/locale/zh-CN';
 import localeMenu from '@/views/system/menu/locale/zh-CN';
 import localeDept from '@/views/system/dept/locale/zh-CN';
+import localePost from '@/views/system/post/locale/zh-CN';
 
 import localeOnlineUser from '@/views/monitor/online/locale/zh-CN';
 import localeLoginLog from '@/views/monitor/log/login/locale/zh-CN';
@@ -55,6 +56,7 @@ export default {
   ...localeRole,
   ...localeMenu,
   ...localeDept,
+  ...localePost,
 
   ...localeOnlineUser,
   ...localeLoginLog,
diff --git a/continew-admin-ui/src/router/routes/modules/system.ts b/continew-admin-ui/src/router/routes/modules/system.ts
index 10405cba..8c82ce25 100644
--- a/continew-admin-ui/src/router/routes/modules/system.ts
+++ b/continew-admin-ui/src/router/routes/modules/system.ts
@@ -52,6 +52,16 @@ const System: AppRouteRecordRaw = {
         roles: ['*'],
       },
     },
+    {
+      path: '/system/post',
+      name: 'Post',
+      component: () => import('@/views/system/post/index.vue'),
+      meta: {
+        locale: 'menu.system.post.list',
+        requiresAuth: true,
+        roles: ['*'],
+      },
+    },
   ],
 };
 
diff --git a/continew-admin-ui/src/views/system/dept/index.vue b/continew-admin-ui/src/views/system/dept/index.vue
index a6b0bed0..34393b8b 100644
--- a/continew-admin-ui/src/views/system/dept/index.vue
+++ b/continew-admin-ui/src/views/system/dept/index.vue
@@ -216,7 +216,7 @@
         render-to-body
         @cancel="handleDetailCancel"
       >
-        <a-descriptions title="基础信息" :column="2" bordered size="large">
+        <a-descriptions :column="2" bordered size="large" layout="vertical">
           <a-descriptions-item label="部门名称">
             <a-skeleton v-if="detailLoading" :animation="true">
               <a-skeleton-line :rows="1" />
diff --git a/continew-admin-ui/src/views/system/post/index.vue b/continew-admin-ui/src/views/system/post/index.vue
new file mode 100644
index 00000000..717640c2
--- /dev/null
+++ b/continew-admin-ui/src/views/system/post/index.vue
@@ -0,0 +1,552 @@
+<template>
+  <div class="app-container">
+    <Breadcrumb :items="['menu.system', 'menu.system.post.list']" />
+    <a-card class="general-card" :title="$t('menu.system.post.list')">
+      <!-- 头部区域 -->
+      <div class="header">
+        <!-- 搜索栏 -->
+        <div v-if="showQuery" class="header-query">
+          <a-form ref="queryRef" :model="queryParams" layout="inline">
+            <a-form-item field="postName" hide-label>
+              <a-input
+                v-model="queryParams.postName"
+                placeholder="输入岗位名称搜索"
+                allow-clear
+                style="width: 150px"
+                @press-enter="handleQuery"
+              />
+            </a-form-item>
+            <a-form-item field="status" hide-label>
+              <a-select
+                v-model="queryParams.status"
+                :options="statusOptions"
+                placeholder="状态搜索"
+                allow-clear
+                style="width: 150px"
+              />
+            </a-form-item>
+            <a-form-item hide-label>
+              <a-space>
+                <a-button type="primary" @click="handleQuery">
+                  <template #icon><icon-search /></template>查询
+                </a-button>
+                <a-button @click="resetQuery">
+                  <template #icon><icon-refresh /></template>重置
+                </a-button>
+              </a-space>
+            </a-form-item>
+          </a-form>
+        </div>
+        <!-- 操作栏 -->
+        <div class="header-operation">
+          <a-row>
+            <a-col :span="12">
+              <a-space>
+                <a-button type="primary" @click="toCreate">
+                  <template #icon><icon-plus /></template>新增
+                </a-button>
+                <a-button
+                  type="primary"
+                  status="success"
+                  :disabled="single"
+                  :title="single ? '请选择一条要修改的数据' : ''"
+                  @click="toUpdate(ids[0])"
+                >
+                  <template #icon><icon-edit /></template>修改
+                </a-button>
+                <a-button
+                  type="primary"
+                  status="danger"
+                  :disabled="multiple"
+                  :title="multiple ? '请选择要删除的数据' : ''"
+                  @click="handleBatchDelete"
+                >
+                  <template #icon><icon-delete /></template>删除
+                </a-button>
+                <a-button
+                  :loading="exportLoading"
+                  type="primary"
+                  status="warning"
+                  @click="handleExport"
+                >
+                  <template #icon><icon-download /></template>导出
+                </a-button>
+              </a-space>
+            </a-col>
+            <a-col :span="12">
+              <right-toolbar
+                v-model:show-query="showQuery"
+                @refresh="getList"
+              />
+            </a-col>
+          </a-row>
+        </div>
+      </div>
+
+      <!-- 列表区域 -->
+      <a-table
+        ref="tableRef"
+        :data="postList"
+        :row-selection="{
+          type: 'checkbox',
+          showCheckedAll: true,
+          onlyCurrent: false,
+        }"
+        :pagination="{
+          showTotal: true,
+          showPageSize: true,
+          total: total,
+          current: queryParams.page,
+        }"
+        row-key="postId"
+        :bordered="false"
+        :stripe="true"
+        :loading="loading"
+        size="large"
+        @page-change="handlePageChange"
+        @page-size-change="handlePageSizeChange"
+        @selection-change="handleSelectionChange"
+      >
+        <template #columns>
+          <a-table-column title="ID" data-index="postId" />
+          <a-table-column title="岗位名称">
+            <template #cell="{ record }">
+              <a-link @click="toDetail(record.postId)">{{
+                record.postName
+              }}</a-link>
+            </template>
+          </a-table-column>
+          <a-table-column
+            title="岗位排序"
+            align="center"
+            data-index="postSort"
+          />
+          <a-table-column title="状态" align="center" data-index="status">
+            <template #cell="{ record }">
+              <a-switch
+                v-model="record.status"
+                :checked-value="1"
+                :unchecked-value="2"
+                :disabled="record.disabled"
+                @change="handleChangeStatus(record)"
+              />
+            </template>
+          </a-table-column>
+          <a-table-column title="描述" data-index="description" />
+          <a-table-column title="创建人" data-index="createUserString" />
+          <a-table-column title="创建时间" data-index="createTime" />
+          <a-table-column title="操作" align="center">
+            <template #cell="{ record }">
+              <a-button
+                v-permission="['admin']"
+                type="text"
+                size="small"
+                title="修改"
+                :disabled="record.disabled"
+                @click="toUpdate(record.postId)"
+              >
+                <template #icon><icon-edit /></template>修改
+              </a-button>
+              <a-popconfirm
+                content="确定要删除当前选中的数据吗?"
+                type="warning"
+                @ok="handleDelete([record.postId])"
+              >
+                <a-button
+                  v-permission="['admin']"
+                  type="text"
+                  size="small"
+                  title="删除"
+                  :disabled="record.disabled"
+                >
+                  <template #icon><icon-delete /></template>删除
+                </a-button>
+              </a-popconfirm>
+            </template>
+          </a-table-column>
+        </template>
+      </a-table>
+
+      <!-- 表单区域 -->
+      <a-modal
+        :title="title"
+        :visible="visible"
+        :mask-closable="false"
+        unmount-on-close
+        render-to-body
+        @ok="handleOk"
+        @cancel="handleCancel"
+      >
+        <a-form ref="formRef" :model="form" :rules="rules" size="large">
+          <a-form-item label="岗位名称" field="postName">
+            <a-input v-model="form.postName" placeholder="请输入岗位名称" />
+          </a-form-item>
+          <a-form-item label="岗位排序" field="postSort">
+            <a-input-number
+              v-model="form.postSort"
+              placeholder="请输入岗位排序"
+              :min="1"
+              mode="button"
+            />
+          </a-form-item>
+          <a-form-item label="描述" field="description">
+            <a-textarea
+              v-model="form.description"
+              :max-length="200"
+              placeholder="请输入描述"
+              :auto-size="{
+                minRows: 3,
+              }"
+              show-word-limit
+            />
+          </a-form-item>
+        </a-form>
+      </a-modal>
+
+      <!-- 详情区域 -->
+      <a-drawer
+        title="岗位详情"
+        :visible="detailVisible"
+        :width="580"
+        :footer="false"
+        unmount-on-close
+        render-to-body
+        @cancel="handleDetailCancel"
+      >
+        <a-descriptions :column="2" bordered size="large" layout="vertical">
+          <a-descriptions-item label="岗位名称">
+            <a-skeleton v-if="detailLoading" :animation="true">
+              <a-skeleton-line :rows="1" />
+            </a-skeleton>
+            <span v-else>{{ post.postName }}</span>
+          </a-descriptions-item>
+          <a-descriptions-item label="状态">
+            <a-skeleton v-if="detailLoading" :animation="true">
+              <a-skeleton-line :rows="1" />
+            </a-skeleton>
+            <span v-else>
+              <a-tag v-if="post.status === 1" color="green">启用</a-tag>
+              <a-tag v-else color="red">禁用</a-tag>
+            </span>
+          </a-descriptions-item>
+          <a-descriptions-item label="创建人">
+            <a-skeleton v-if="detailLoading" :animation="true">
+              <a-skeleton-line :rows="1" />
+            </a-skeleton>
+            <span v-else>{{ post.createUserString }}</span>
+          </a-descriptions-item>
+          <a-descriptions-item label="创建时间">
+            <a-skeleton v-if="detailLoading" :animation="true">
+              <a-skeleton-line :rows="1" />
+            </a-skeleton>
+            <span v-else>{{ post.createTime }}</span>
+          </a-descriptions-item>
+          <a-descriptions-item label="修改人">
+            <a-skeleton v-if="detailLoading" :animation="true">
+              <a-skeleton-line :rows="1" />
+            </a-skeleton>
+            <span v-else>{{ post.updateUserString }}</span>
+          </a-descriptions-item>
+          <a-descriptions-item label="修改时间">
+            <a-skeleton v-if="detailLoading" :animation="true">
+              <a-skeleton-line :rows="1" />
+            </a-skeleton>
+            <span v-else>{{ post.updateTime }}</span>
+          </a-descriptions-item>
+          <a-descriptions-item label="描述">
+            <a-skeleton v-if="detailLoading" :animation="true">
+              <a-skeleton-line :rows="1" />
+            </a-skeleton>
+            <span v-else>{{ post.description }}</span>
+          </a-descriptions-item>
+        </a-descriptions>
+      </a-drawer>
+    </a-card>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { getCurrentInstance, ref, toRefs, reactive } from 'vue';
+  import { SelectOptionData } from '@arco-design/web-vue';
+  import {
+    PostRecord,
+    PostParam,
+    listPost,
+    getPost,
+    addPost,
+    updatePost,
+    deletePost,
+  } from '@/api/system/post';
+
+  const { proxy } = getCurrentInstance() as any;
+
+  const postList = ref<PostRecord[]>([]);
+  const post = ref<PostRecord>({
+    postName: '',
+    status: 1,
+    createUserString: '',
+    createTime: '',
+    updateUserString: '',
+    updateTime: '',
+    description: '',
+  });
+  const total = ref(0);
+  const ids = ref<Array<number>>([]);
+  const title = ref('');
+  const single = ref(true);
+  const multiple = ref(true);
+  const showQuery = ref(true);
+  const loading = ref(false);
+  const detailLoading = ref(false);
+  const exportLoading = ref(false);
+  const visible = ref(false);
+  const detailVisible = ref(false);
+  const statusOptions = ref<SelectOptionData[]>([
+    { label: '启用', value: 1 },
+    { label: '禁用', value: 2 },
+  ]);
+
+  const data = reactive({
+    // 查询参数
+    queryParams: {
+      postName: undefined,
+      status: undefined,
+      page: 1,
+      size: 10,
+      sort: ['createTime,desc'],
+    },
+    // 表单数据
+    form: {} as PostRecord,
+    // 表单验证规则
+    rules: {
+      postName: [{ required: true, message: '请输入岗位名称' }],
+      postSort: [{ required: true, message: '请输入岗位排序' }],
+    },
+  });
+  const { queryParams, form, rules } = toRefs(data);
+
+  /**
+   * 查询列表
+   *
+   * @param params 查询参数
+   */
+  const getList = (params: PostParam = { ...queryParams.value }) => {
+    loading.value = true;
+    listPost(params)
+      .then((res) => {
+        postList.value = res.data.list;
+        total.value = res.data.total;
+      })
+      .finally(() => {
+        loading.value = false;
+      });
+  };
+  getList();
+
+  /**
+   * 打开新增对话框
+   */
+  const toCreate = () => {
+    reset();
+    title.value = '新增岗位';
+    visible.value = true;
+  };
+
+  /**
+   * 打开修改对话框
+   *
+   * @param id ID
+   */
+  const toUpdate = (id: number) => {
+    reset();
+    getPost(id).then((res) => {
+      form.value = res.data;
+      title.value = '修改岗位';
+      visible.value = true;
+    });
+  };
+
+  /**
+   * 重置表单
+   */
+  const reset = () => {
+    form.value = {
+      postId: undefined,
+      postName: '',
+      description: '',
+      postSort: 999,
+      status: 1,
+    };
+    proxy.$refs.formRef?.resetFields();
+  };
+
+  /**
+   * 取消
+   */
+  const handleCancel = () => {
+    visible.value = false;
+    proxy.$refs.formRef.resetFields();
+  };
+
+  /**
+   * 确定
+   */
+  const handleOk = () => {
+    proxy.$refs.formRef.validate((valid: any) => {
+      if (!valid) {
+        if (form.value.postId !== undefined) {
+          updatePost(form.value).then((res) => {
+            handleCancel();
+            getList();
+            proxy.$message.success(res.msg);
+          });
+        } else {
+          addPost(form.value).then((res) => {
+            handleCancel();
+            getList();
+            proxy.$message.success(res.msg);
+          });
+        }
+      }
+    });
+  };
+
+  /**
+   * 查看详情
+   *
+   * @param id ID
+   */
+  const toDetail = async (id: number) => {
+    if (detailLoading.value) return;
+    detailLoading.value = true;
+    detailVisible.value = true;
+    getPost(id)
+      .then((res) => {
+        post.value = res.data;
+      })
+      .finally(() => {
+        detailLoading.value = false;
+      });
+  };
+
+  /**
+   * 关闭详情
+   */
+  const handleDetailCancel = () => {
+    detailVisible.value = false;
+  };
+
+  /**
+   * 批量删除
+   */
+  const handleBatchDelete = () => {
+    if (ids.value.length === 0) {
+      proxy.$message.info('请选择要删除的数据');
+    } else {
+      proxy.$modal.warning({
+        title: '警告',
+        titleAlign: 'start',
+        content: '确定要删除当前选中的数据吗?',
+        hideCancel: false,
+        onOk: () => {
+          handleDelete(ids.value);
+        },
+      });
+    }
+  };
+
+  /**
+   * 删除
+   *
+   * @param ids ID 列表
+   */
+  const handleDelete = (ids: Array<number>) => {
+    deletePost(ids).then((res) => {
+      proxy.$message.success(res.msg);
+      getList();
+    });
+  };
+
+  /**
+   * 已选择的数据行发生改变时触发
+   *
+   * @param rowKeys ID 列表
+   */
+  const handleSelectionChange = (rowKeys: Array<any>) => {
+    ids.value = rowKeys;
+    single.value = rowKeys.length !== 1;
+    multiple.value = !rowKeys.length;
+  };
+
+  /**
+   * 导出
+   */
+  const handleExport = () => {
+    if (exportLoading.value) return;
+    exportLoading.value = true;
+    proxy
+      .download('/system/post/export', { ...queryParams.value }, '岗位数据')
+      .finally(() => {
+        exportLoading.value = false;
+      });
+  };
+
+  /**
+   * 修改状态
+   *
+   * @param record 记录信息
+   */
+  const handleChangeStatus = (record: PostRecord) => {
+    const tip = record.status === 1 ? '启用' : '禁用';
+    updatePost(record)
+      .then(() => {
+        proxy.$message.success(`${tip}成功`);
+      })
+      .catch(() => {
+        record.status = record.status === 1 ? 2 : 1;
+      });
+  };
+
+  /**
+   * 查询
+   */
+  const handleQuery = () => {
+    getList();
+  };
+
+  /**
+   * 重置
+   */
+  const resetQuery = () => {
+    proxy.$refs.queryRef.resetFields();
+    handleQuery();
+  };
+
+  /**
+   * 切换页码
+   *
+   * @param current 页码
+   */
+  const handlePageChange = (current: number) => {
+    queryParams.value.page = current;
+    getList();
+  };
+
+  /**
+   * 切换每页条数
+   *
+   * @param pageSize 每页条数
+   */
+  const handlePageSizeChange = (pageSize: number) => {
+    queryParams.value.size = pageSize;
+    getList();
+  };
+</script>
+
+<script lang="ts">
+  export default {
+    name: 'Post',
+  };
+</script>
+
+<style scoped lang="less"></style>
diff --git a/continew-admin-ui/src/views/system/post/locale/en-US.ts b/continew-admin-ui/src/views/system/post/locale/en-US.ts
new file mode 100644
index 00000000..d984aca1
--- /dev/null
+++ b/continew-admin-ui/src/views/system/post/locale/en-US.ts
@@ -0,0 +1,3 @@
+export default {
+  'menu.system.post.list': 'Post management',
+};
diff --git a/continew-admin-ui/src/views/system/post/locale/zh-CN.ts b/continew-admin-ui/src/views/system/post/locale/zh-CN.ts
new file mode 100644
index 00000000..c5702f99
--- /dev/null
+++ b/continew-admin-ui/src/views/system/post/locale/zh-CN.ts
@@ -0,0 +1,3 @@
+export default {
+  'menu.system.post.list': '岗位管理',
+};
diff --git a/continew-admin-ui/src/views/system/user/index.vue b/continew-admin-ui/src/views/system/user/index.vue
index 0228f2bf..51c72faf 100644
--- a/continew-admin-ui/src/views/system/user/index.vue
+++ b/continew-admin-ui/src/views/system/user/index.vue
@@ -243,7 +243,7 @@
       <a-modal
         :title="title"
         :visible="visible"
-        :width="580"
+        :width="565"
         :mask-closable="false"
         unmount-on-close
         render-to-body
@@ -293,6 +293,29 @@
               <a-radio :value="0" disabled>未知</a-radio>
             </a-radio-group>
           </a-form-item>
+          <a-form-item label="所属部门" field="deptId">
+            <a-tree-select
+              v-model="form.deptId"
+              :data="deptOptions"
+              placeholder="请选择所属部门"
+              allow-clear
+              allow-search
+              :filter-tree-node="filterDeptOptions"
+              style="width: 416px"
+            />
+          </a-form-item>
+          <a-form-item label="所属岗位" field="postIds">
+            <a-select
+              v-model="form.postIds"
+              :options="postOptions"
+              placeholder="请选择所属岗位"
+              :loading="postLoading"
+              multiple
+              allow-clear
+              :allow-search="{ retainInputValue: true }"
+              style="width: 416px"
+            />
+          </a-form-item>
           <a-form-item label="所属角色" field="roleIds">
             <a-select
               v-model="form.roleIds"
@@ -305,17 +328,6 @@
               style="width: 416px"
             />
           </a-form-item>
-          <a-form-item label="所属部门" field="deptId">
-            <a-tree-select
-              v-model="form.deptId"
-              :data="deptOptions"
-              placeholder="请选择所属部门"
-              allow-clear
-              allow-search
-              :filter-tree-node="filterDeptOptions"
-              style="width: 416px"
-            />
-          </a-form-item>
           <a-form-item label="描述" field="description">
             <a-textarea
               v-model="form.description"
@@ -367,7 +379,7 @@
         render-to-body
         @cancel="handleDetailCancel"
       >
-        <a-descriptions title="基础信息" :column="2" bordered size="large">
+        <a-descriptions :column="2" bordered size="large" layout="vertical">
           <a-descriptions-item label="用户名">
             <a-skeleton v-if="detailLoading" :animation="true">
               <a-skeleton-line :rows="1" />
@@ -410,17 +422,29 @@
             </a-skeleton>
             <span v-else>{{ user.phone || '无' }}</span>
           </a-descriptions-item>
+          <a-descriptions-item label="所属部门">
+            <a-skeleton v-if="detailLoading" :animation="true">
+              <a-skeleton-line :rows="1" />
+            </a-skeleton>
+            <span v-else>{{ user.deptName }}</span>
+          </a-descriptions-item>
+          <a-descriptions-item label="所属岗位">
+            <a-skeleton v-if="detailLoading" :animation="true">
+              <a-skeleton-line :rows="1" />
+            </a-skeleton>
+            <span v-else>{{ user.postNames }}</span>
+          </a-descriptions-item>
           <a-descriptions-item label="所属角色">
             <a-skeleton v-if="detailLoading" :animation="true">
               <a-skeleton-line :rows="1" />
             </a-skeleton>
             <span v-else>{{ user.roleNames }}</span>
           </a-descriptions-item>
-          <a-descriptions-item label="所属部门">
+          <a-descriptions-item label="最后一次修改密码时间">
             <a-skeleton v-if="detailLoading" :animation="true">
               <a-skeleton-line :rows="1" />
             </a-skeleton>
-            <span v-else>{{ user.deptName }}</span>
+            <span v-else>{{ user.pwdResetTime }}</span>
           </a-descriptions-item>
           <a-descriptions-item label="创建人">
             <a-skeleton v-if="detailLoading" :animation="true">
@@ -472,7 +496,12 @@
     resetPassword,
     updateUserRole,
   } from '@/api/system/user';
-  import { listRoleDict, listDeptTree } from '@/api/common';
+  import {
+    LabelValueRecord,
+    listDeptTree,
+    listPostDict,
+    listRoleDict,
+  } from '@/api/common';
   import getAvatar from '@/utils/avatar';
 
   const { proxy } = getCurrentInstance() as any;
@@ -485,6 +514,7 @@
     email: undefined,
     phone: undefined,
     status: 1,
+    pwdResetTime: '',
     createUserString: '',
     createTime: '',
     updateUserString: '',
@@ -509,10 +539,12 @@
     { label: '启用', value: 1 },
     { label: '禁用', value: 2 },
   ]);
-  const roleLoading = ref(false);
   const deptLoading = ref(false);
-  const roleOptions = ref<TreeNodeData[]>([]);
+  const postLoading = ref(false);
+  const roleLoading = ref(false);
   const deptOptions = ref<TreeNodeData[]>([]);
+  const postOptions = ref<LabelValueRecord[]>([]);
+  const roleOptions = ref<LabelValueRecord[]>([]);
   const deptTree = ref<TreeNodeData[]>([]);
   const deptName = ref('');
 
@@ -580,8 +612,9 @@
    */
   const toCreate = () => {
     reset();
-    getRoleOptions();
     getDeptOptions();
+    getPostOptions();
+    getRoleOptions();
     title.value = '新增用户';
     visible.value = true;
   };
@@ -593,8 +626,9 @@
    */
   const toUpdate = (id: number) => {
     reset();
-    getRoleOptions();
     getDeptOptions();
+    getPostOptions();
+    getRoleOptions();
     getUser(id).then((res) => {
       form.value = res.data;
       title.value = '修改用户';
@@ -616,20 +650,6 @@
     });
   };
 
-  /**
-   * 查询角色列表
-   */
-  const getRoleOptions = () => {
-    roleLoading.value = true;
-    listRoleDict({})
-      .then((res) => {
-        roleOptions.value = res.data;
-      })
-      .finally(() => {
-        roleLoading.value = false;
-      });
-  };
-
   /**
    * 查询部门列表
    */
@@ -644,6 +664,34 @@
       });
   };
 
+  /**
+   * 查询岗位列表
+   */
+  const getPostOptions = () => {
+    postLoading.value = true;
+    listPostDict({})
+      .then((res) => {
+        postOptions.value = res.data;
+      })
+      .finally(() => {
+        postLoading.value = false;
+      });
+  };
+
+  /**
+   * 查询角色列表
+   */
+  const getRoleOptions = () => {
+    roleLoading.value = true;
+    listRoleDict({})
+      .then((res) => {
+        roleOptions.value = res.data;
+      })
+      .finally(() => {
+        roleLoading.value = false;
+      });
+  };
+
   /**
    * 重置表单
    */
@@ -657,8 +705,9 @@
       phone: undefined,
       description: '',
       status: 1,
-      roleIds: [] as Array<number>,
       deptId: undefined,
+      postIds: [] as Array<number>,
+      roleIds: [] as Array<number>,
     };
     proxy.$refs.formRef?.resetFields();
   };
diff --git a/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/common/CommonController.java b/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/common/CommonController.java
index 9dc26f72..5b255f06 100644
--- a/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/common/CommonController.java
+++ b/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/common/CommonController.java
@@ -36,12 +36,15 @@ import top.charles7c.cnadmin.common.model.vo.R;
 import top.charles7c.cnadmin.monitor.annotation.Log;
 import top.charles7c.cnadmin.system.model.query.DeptQuery;
 import top.charles7c.cnadmin.system.model.query.MenuQuery;
+import top.charles7c.cnadmin.system.model.query.PostQuery;
 import top.charles7c.cnadmin.system.model.query.RoleQuery;
 import top.charles7c.cnadmin.system.model.vo.DeptVO;
 import top.charles7c.cnadmin.system.model.vo.MenuVO;
+import top.charles7c.cnadmin.system.model.vo.PostVO;
 import top.charles7c.cnadmin.system.model.vo.RoleVO;
 import top.charles7c.cnadmin.system.service.DeptService;
 import top.charles7c.cnadmin.system.service.MenuService;
+import top.charles7c.cnadmin.system.service.PostService;
 import top.charles7c.cnadmin.system.service.RoleService;
 
 /**
@@ -51,6 +54,7 @@ import top.charles7c.cnadmin.system.service.RoleService;
  * @since 2023/1/22 21:48
  */
 @Tag(name = "公共 API")
+@Log(ignore = true)
 @RestController
 @RequiredArgsConstructor
 @RequestMapping("/common")
@@ -59,8 +63,8 @@ public class CommonController {
     private final DeptService deptService;
     private final MenuService menuService;
     private final RoleService roleService;
+    private final PostService postService;
 
-    @Log(ignore = true)
     @Operation(summary = "查询部门树", description = "查询树结构的部门列表")
     @GetMapping("/tree/dept")
     public R<List<Tree<Long>>> listDeptTree(@Validated DeptQuery query, @Validated SortQuery sortQuery) {
@@ -69,7 +73,6 @@ public class CommonController {
         return R.ok(treeList);
     }
 
-    @Log(ignore = true)
     @Operation(summary = "查询菜单树", description = "查询树结构的菜单列表")
     @GetMapping("/tree/menu")
     public R<List<Tree<Long>>> listMenuTree(@Validated MenuQuery query, @Validated SortQuery sortQuery) {
@@ -78,7 +81,6 @@ public class CommonController {
         return R.ok(treeList);
     }
 
-    @Log(ignore = true)
     @Operation(summary = "查询角色字典", description = "查询角色字典列表")
     @GetMapping("/dict/role")
     public R<List<LabelValueVO<Long>>> listRoleDict(@Validated RoleQuery query, @Validated SortQuery sortQuery) {
@@ -86,4 +88,12 @@ public class CommonController {
         List<LabelValueVO<Long>> dictList = roleService.buildDict(list);
         return R.ok(dictList);
     }
+
+    @Operation(summary = "查询岗位字典", description = "查询岗位字典列表")
+    @GetMapping("/dict/post")
+    public R<List<LabelValueVO<Long>>> listPostDict(@Validated PostQuery query, @Validated SortQuery sortQuery) {
+        List<PostVO> list = postService.list(query, sortQuery);
+        List<LabelValueVO<Long>> dictList = postService.buildDict(list);
+        return R.ok(dictList);
+    }
 }
diff --git a/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/system/PostController.java b/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/system/PostController.java
new file mode 100644
index 00000000..f984a66c
--- /dev/null
+++ b/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/system/PostController.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.charles7c.cnadmin.webapi.controller.system;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+import org.springframework.web.bind.annotation.RestController;
+
+import top.charles7c.cnadmin.common.annotation.CrudRequestMapping;
+import top.charles7c.cnadmin.common.base.BaseController;
+import top.charles7c.cnadmin.system.model.query.PostQuery;
+import top.charles7c.cnadmin.system.model.request.PostRequest;
+import top.charles7c.cnadmin.system.model.vo.PostDetailVO;
+import top.charles7c.cnadmin.system.model.vo.PostVO;
+import top.charles7c.cnadmin.system.service.PostService;
+
+/**
+ * 岗位管理 API
+ *
+ * @author Charles7c
+ * @since 2023/2/25 22:54
+ */
+@Tag(name = "岗位管理 API")
+@RestController
+@CrudRequestMapping("/system/post")
+public class PostController extends BaseController<PostService, PostVO, PostDetailVO, PostQuery, PostRequest> {}
diff --git a/continew-admin-webapi/src/main/resources/db/changelog/v0.0.1/continew-admin_data.sql b/continew-admin-webapi/src/main/resources/db/changelog/v0.0.1/continew-admin_data.sql
index 5007c27b..9a9decc2 100644
--- a/continew-admin-webapi/src/main/resources/db/changelog/v0.0.1/continew-admin_data.sql
+++ b/continew-admin-webapi/src/main/resources/db/changelog/v0.0.1/continew-admin_data.sql
@@ -3,21 +3,33 @@
 -- changeset Charles7c:1
 -- 初始化默认菜单
 INSERT IGNORE INTO `sys_menu` VALUES (1000, '系统管理', 0, 1, 'system', NULL, NULL, 'settings', b'0', b'0', b'0', NULL, 1, 1, 1, NOW(), 1, NOW());
-INSERT IGNORE INTO `sys_menu` VALUES (1010, '角色管理', 1000, 2, '/system/role', 'Role', 'system/role/index', NULL, b'0', b'0', b'0', 'system:role:list', 2, 1, 1, NOW(), 1, NOW());
-INSERT IGNORE INTO `sys_menu` VALUES (1011, '角色新增', 1010, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:role:create', 1, 1, 1, NOW(), 1, NOW());
-INSERT IGNORE INTO `sys_menu` VALUES (1012, '角色修改', 1010, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:role:update', 2, 1, 1, NOW(), 1, NOW());
-INSERT IGNORE INTO `sys_menu` VALUES (1013, '角色删除', 1010, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:role:delete', 3, 1, 1, NOW(), 1, NOW());
-INSERT IGNORE INTO `sys_menu` VALUES (1014, '角色导出', 1010, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:role:export', 4, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1010, '用户管理', 1000, 2, '/system/user', 'User', '/system/user/index', NULL, b'0', b'0', b'0', 'system:user:list', 1, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1011, '用户新增', 1010, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:user:create', 1, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1012, '用户修改', 1010, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:user:update', 2, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1013, '用户删除', 1010, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:user:delete', 3, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1014, '用户导出', 1010, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:user:export', 4, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1015, '重置密码', 1010, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:user:password:reset', 5, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1016, '分配角色', 1010, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:user:role:update', 6, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1020, '角色管理', 1000, 2, '/system/role', 'Role', 'system/role/index', NULL, b'0', b'0', b'0', 'system:role:list', 2, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1021, '角色新增', 1020, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:role:create', 1, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1022, '角色修改', 1020, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:role:update', 2, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1023, '角色删除', 1020, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:role:delete', 3, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1024, '角色导出', 1020, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:role:export', 4, 1, 1, NOW(), 1, NOW());
 INSERT IGNORE INTO `sys_menu` VALUES (1030, '菜单管理', 1000, 2, '/system/menu', 'Menu', 'system/menu/index', NULL, b'0', b'0', b'0', 'system:menu:list', 3, 1, 1, NOW(), 1, NOW());
 INSERT IGNORE INTO `sys_menu` VALUES (1031, '菜单新增', 1030, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:menu:create', 1, 1, 1, NOW(), 1, NOW());
 INSERT IGNORE INTO `sys_menu` VALUES (1032, '菜单修改', 1030, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:menu:update', 2, 1, 1, NOW(), 1, NOW());
 INSERT IGNORE INTO `sys_menu` VALUES (1033, '菜单删除', 1030, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:menu:delete', 3, 1, 1, NOW(), 1, NOW());
 INSERT IGNORE INTO `sys_menu` VALUES (1034, '菜单导出', 1030, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:menu:export', 4, 1, 1, NOW(), 1, NOW());
-INSERT IGNORE INTO `sys_menu` VALUES (1050, '部门管理', 1000, 2, '/system/dept', 'Dept', 'system/dept/index', NULL, b'0', b'0', b'0', 'system:dept:list', 4, 1, 1, NOW(), 1, NOW());
-INSERT IGNORE INTO `sys_menu` VALUES (1051, '部门新增', 1050, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:dept:create', 1, 1, 1, NOW(), 1, NOW());
-INSERT IGNORE INTO `sys_menu` VALUES (1052, '部门修改', 1050, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:dept:update', 2, 1, 1, NOW(), 1, NOW());
-INSERT IGNORE INTO `sys_menu` VALUES (1053, '部门删除', 1050, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:dept:delete', 3, 1, 1, NOW(), 1, NOW());
-INSERT IGNORE INTO `sys_menu` VALUES (1054, '部门导出', 1050, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:dept:export', 4, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1040, '部门管理', 1000, 2, '/system/dept', 'Dept', 'system/dept/index', NULL, b'0', b'0', b'0', 'system:dept:list', 4, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1041, '部门新增', 1050, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:dept:create', 1, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1042, '部门修改', 1050, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:dept:update', 2, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1043, '部门删除', 1050, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:dept:delete', 3, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1044, '部门导出', 1050, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:dept:export', 4, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1050, '岗位管理', 1000, 2, '/system/post', 'Post', '/system/post/index', NULL, b'0', b'0', b'0', 'system:post:list', 5, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1051, '岗位新增', 1050, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:post:create', 1, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1052, '岗位修改', 1050, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:post:update', 2, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1053, '岗位删除', 1050, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:post:delete', 3, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_menu` VALUES (1054, '岗位导出', 1050, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:post:export', 4, 1, 1, NOW(), 1, NOW());
 INSERT IGNORE INTO `sys_menu` VALUES (2000, '系统监控', 0, 1, 'monitor', NULL, NULL, 'computer', b'0', b'0', b'0', NULL, 2, 1, 1, NOW(), 1, NOW());
 INSERT IGNORE INTO `sys_menu` VALUES (2010, '在线用户', 2000, 2, '/monitor/online', 'OnlineUser', 'monitor/online/index', NULL, b'0', b'0', b'0', 'monitor:online:user:list', 1, 1, 1, NOW(), 1, NOW());
 INSERT IGNORE INTO `sys_menu` VALUES (2011, '强退用户', 2010, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'monitor:online:user:delete', 1, 1, 1, NOW(), 1, NOW());
@@ -37,6 +49,18 @@ INSERT IGNORE INTO `sys_dept` VALUES (6, '运维部', 2, '系统初始部门', 4
 INSERT IGNORE INTO `sys_dept` VALUES (7, '研发一组', 3, '系统初始部门', 1, 1, 1, NOW(), 1, NOW());
 INSERT IGNORE INTO `sys_dept` VALUES (8, '研发二组', 3, '系统初始部门', 2, 2, 1, NOW(), 1, NOW());
 
+-- 初始化默认岗位
+INSERT IGNORE INTO `sys_post` VALUES (1, '项目总监', '系统初始岗位', 1, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_post` VALUES (2, '技术总监', '系统初始岗位', 2, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_post` VALUES (3, '销售经理', '系统初始岗位', 3, 2, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_post` VALUES (4, '项目经理', '系统初始岗位', 4, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_post` VALUES (5, '产品经理', '系统初始岗位', 5, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_post` VALUES (6, '技术经理', '系统初始岗位', 6, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_post` VALUES (7, '测试经理', '系统初始岗位', 7, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_post` VALUES (8, '销售专员', '系统初始岗位', 8, 2, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_post` VALUES (9, '软件开发', '系统初始岗位', 9, 1, 1, NOW(), 1, NOW());
+INSERT IGNORE INTO `sys_post` VALUES (10, '软件测试', '系统初始岗位', 10, 1, 1, NOW(), 1, NOW());
+
 -- 初始化默认角色
 INSERT IGNORE INTO `sys_role` VALUES (1, '超级管理员', 'admin', 1, '系统初始角色', 1, 1, 1, NOW(), 1, NOW());
 INSERT IGNORE INTO `sys_role` VALUES (2, '测试人员', 'test', 5, '系统初始角色', 2, 2, 1, NOW(), 1, NOW());
@@ -68,4 +92,8 @@ INSERT IGNORE INTO `sys_role_dept` VALUES (2, 5);
 
 -- 初始化默认用户和角色关联数据
 INSERT IGNORE INTO `sys_user_role` VALUES (1, 1);
-INSERT IGNORE INTO `sys_user_role` VALUES (2, 2);
\ No newline at end of file
+INSERT IGNORE INTO `sys_user_role` VALUES (2, 2);
+
+-- 初始化默认用户和岗位关联数据
+INSERT IGNORE INTO `sys_user_post` VALUES (1, 1);
+INSERT IGNORE INTO `sys_user_post` VALUES (2, 10);
\ No newline at end of file
diff --git a/continew-admin-webapi/src/main/resources/db/changelog/v0.0.1/continew-admin_table.sql b/continew-admin-webapi/src/main/resources/db/changelog/v0.0.1/continew-admin_table.sql
index 2a987cee..6e091b44 100644
--- a/continew-admin-webapi/src/main/resources/db/changelog/v0.0.1/continew-admin_table.sql
+++ b/continew-admin-webapi/src/main/resources/db/changelog/v0.0.1/continew-admin_table.sql
@@ -72,6 +72,21 @@ CREATE TABLE IF NOT EXISTS `sys_role_dept`  (
     PRIMARY KEY (`role_id`,`dept_id`) USING BTREE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色和部门关联表';
 
+CREATE TABLE IF NOT EXISTS `sys_post`  (
+    `post_id` bigint(20) unsigned AUTO_INCREMENT COMMENT '岗位ID',
+    `post_name` varchar(255) NOT NULL COMMENT '岗位名称',
+    `description` varchar(512) DEFAULT NULL COMMENT '描述',
+    `post_sort` int(11) unsigned DEFAULT 999 COMMENT '岗位排序',
+    `status` tinyint(1) unsigned DEFAULT 1 COMMENT '状态(1启用 2禁用)',
+    `create_user` bigint(20) unsigned NOT NULL COMMENT '创建人',
+    `create_time` datetime NOT NULL COMMENT '创建时间',
+    `update_user` bigint(20) unsigned NOT NULL COMMENT '修改人',
+    `update_time` datetime NOT NULL COMMENT '修改时间',
+    PRIMARY KEY (`post_id`) USING BTREE,
+    INDEX `idx_create_user`(`create_user`) USING BTREE,
+    INDEX `idx_update_user`(`update_user`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='岗位表';
+
 CREATE TABLE IF NOT EXISTS `sys_user`  (
     `user_id` bigint(20) unsigned AUTO_INCREMENT COMMENT '用户ID',
     `username` varchar(255) NOT NULL COMMENT '用户名',
@@ -83,7 +98,7 @@ CREATE TABLE IF NOT EXISTS `sys_user`  (
     `avatar` varchar(255) DEFAULT NULL COMMENT '头像地址',
     `description` varchar(512) DEFAULT NULL COMMENT '描述',
     `status` tinyint(1) unsigned DEFAULT 1 COMMENT '状态(1启用 2禁用)',
-    `pwd_reset_time` datetime DEFAULT NULL COMMENT '最后一次修改密码的时间',
+    `pwd_reset_time` datetime DEFAULT NULL COMMENT '最后一次修改密码时间',
     `dept_id` bigint(20) unsigned DEFAULT NULL COMMENT '部门ID',
     `create_user` bigint(20) unsigned NOT NULL COMMENT '创建人',
     `create_time` datetime NOT NULL COMMENT '创建时间',
@@ -103,6 +118,12 @@ CREATE TABLE IF NOT EXISTS `sys_user_role`  (
     PRIMARY KEY (`user_id`,`role_id`) USING BTREE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户和角色关联表';
 
+CREATE TABLE IF NOT EXISTS `sys_user_post`  (
+    `user_id` bigint(20) unsigned NOT NULL COMMENT '用户ID',
+    `post_id` bigint(20) unsigned NOT NULL COMMENT '岗位ID',
+    PRIMARY KEY (`user_id`,`post_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户和岗位关联表';
+
 CREATE TABLE IF NOT EXISTS `sys_log` (
     `log_id` bigint(20) unsigned AUTO_INCREMENT COMMENT '日志ID',
     `description` varchar(255) NOT NULL COMMENT '日志描述',