diff --git a/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/TreeUtils.java b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/TreeUtils.java
new file mode 100644
index 00000000..225f6250
--- /dev/null
+++ b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/TreeUtils.java
@@ -0,0 +1,65 @@
+/*
+ * 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.common.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.lang.tree.TreeNodeConfig;
+import cn.hutool.core.lang.tree.TreeUtil;
+import cn.hutool.core.lang.tree.parser.NodeParser;
+import cn.hutool.core.util.ReflectUtil;
+
+/**
+ * 树工具类
+ *
+ * @author Charles7c
+ * @since 2023/1/22 22:11
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class TreeUtils {
+
+    /** 默认属性配置对象(根据前端树结构灵活调整名称) */
+    private static final TreeNodeConfig DEFAULT_CONFIG =
+        TreeNodeConfig.DEFAULT_CONFIG.setNameKey("title").setIdKey("key").setWeightKey("sort");
+
+    /**
+     * 树构建
+     *
+     * @param <T>
+     *            转换的实体 为数据源里的对象类型
+     * @param <E>
+     *            ID类型
+     * @param list
+     *            源数据集合
+     * @param nodeParser
+     *            转换器
+     * @return List
+     */
+    public static <T, E> List<Tree<E>> build(List<T> list, NodeParser<T, E> nodeParser) {
+        if (CollUtil.isEmpty(list)) {
+            return new ArrayList<>();
+        }
+        E parentId = (E)ReflectUtil.getFieldValue(list.get(0), DEFAULT_CONFIG.getParentIdKey());
+        return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser);
+    }
+}
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/CreateDeptRequest.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/CreateDeptRequest.java
new file mode 100644
index 00000000..a055ab02
--- /dev/null
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/model/request/CreateDeptRequest.java
@@ -0,0 +1,65 @@
+/*
+ * 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 java.io.Serializable;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+
+import lombok.Data;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+/**
+ * 创建部门信息
+ *
+ * @author Charles7c
+ * @since 2023/1/24 00:21
+ */
+@Data
+@Schema(description = "创建部门信息")
+public class CreateDeptRequest implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 上级部门 ID
+     */
+    @Schema(description = "上级部门 ID", defaultValue = "0")
+    private Long parentId = 0L;
+
+    /**
+     * 部门名称
+     */
+    @Schema(description = "部门名称")
+    @NotBlank(message = "部门名称不能为空")
+    private String deptName;
+
+    /**
+     * 部门排序
+     */
+    @Schema(description = "部门排序", defaultValue = "999")
+    private Integer deptSort = 999;
+
+    /**
+     * 描述
+     */
+    @Schema(description = "描述")
+    @Size(max = 200, message = "描述长度不能超过 200 个字符")
+    private String description;
+}
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/DeptService.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/DeptService.java
index 6871b13e..9116ce94 100644
--- a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/DeptService.java
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/DeptService.java
@@ -18,7 +18,10 @@ package top.charles7c.cnadmin.system.service;
 
 import java.util.List;
 
+import cn.hutool.core.lang.tree.Tree;
+
 import top.charles7c.cnadmin.system.model.query.DeptQuery;
+import top.charles7c.cnadmin.system.model.request.CreateDeptRequest;
 import top.charles7c.cnadmin.system.model.vo.DeptVO;
 
 /**
@@ -37,4 +40,44 @@ public interface DeptService {
      * @return 列表数据
      */
     List<DeptVO> list(DeptQuery query);
+
+    /**
+     * 构建树
+     *
+     * @param list
+     *            原始列表数据
+     * @return 树列表
+     */
+    List<DeptVO> buildListTree(List<DeptVO> list);
+
+    /**
+     * 构建树
+     *
+     * @param list
+     *            原始列表数据
+     * @return 树列表
+     */
+    List<Tree<Long>> buildTree(List<DeptVO> list);
+
+    /**
+     * 新增
+     *
+     * @param request
+     *            创建信息
+     * @return 新增记录 ID
+     */
+    Long create(CreateDeptRequest request);
+
+    /**
+     * 检查部门名称是否存在
+     *
+     * @param deptName
+     *            部门名称
+     * @param parentId
+     *            上级部门 ID
+     * @param deptId
+     *            部门 ID
+     * @return 是否存在
+     */
+    boolean checkDeptNameExist(String deptName, Long parentId, Long deptId);
 }
diff --git a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/DeptServiceImpl.java b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/DeptServiceImpl.java
index 2a0e0397..793af09e 100644
--- a/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/DeptServiceImpl.java
+++ b/continew-admin-system/src/main/java/top/charles7c/cnadmin/system/service/impl/DeptServiceImpl.java
@@ -24,17 +24,23 @@ import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.tree.Tree;
 
+import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
 import top.charles7c.cnadmin.common.util.ExceptionUtils;
+import top.charles7c.cnadmin.common.util.TreeUtils;
 import top.charles7c.cnadmin.common.util.helper.QueryHelper;
 import top.charles7c.cnadmin.system.mapper.DeptMapper;
 import top.charles7c.cnadmin.system.model.entity.SysDept;
 import top.charles7c.cnadmin.system.model.query.DeptQuery;
+import top.charles7c.cnadmin.system.model.request.CreateDeptRequest;
 import top.charles7c.cnadmin.system.model.vo.DeptVO;
 import top.charles7c.cnadmin.system.service.DeptService;
 import top.charles7c.cnadmin.system.service.UserService;
@@ -60,31 +66,11 @@ public class DeptServiceImpl implements DeptService {
         List<SysDept> list = deptMapper.selectList(queryWrapper);
         List<DeptVO> voList = BeanUtil.copyToList(list, DeptVO.class);
         voList.forEach(this::fill);
-        return buildTree(voList);
+        return voList;
     }
 
-    /**
-     * 填充数据
-     *
-     * @param vo
-     *            VO
-     */
-    private void fill(DeptVO vo) {
-        Long updateUser = vo.getUpdateUser();
-        if (updateUser == null) {
-            return;
-        }
-        vo.setUpdateUserString(ExceptionUtils.exToNull(() -> userService.getById(vo.getUpdateUser())).getNickname());
-    }
-
-    /**
-     * 构建树
-     *
-     * @param list
-     *            原始列表数据
-     * @return 树列表
-     */
-    private List<DeptVO> buildTree(List<DeptVO> list) {
+    @Override
+    public List<DeptVO> buildListTree(List<DeptVO> list) {
         if (CollUtil.isEmpty(list)) {
             return new ArrayList<>();
         }
@@ -134,4 +120,43 @@ public class DeptServiceImpl implements DeptService {
         return list.stream().filter(d -> Objects.equals(d.getParentId(), dept.getDeptId()))
             .map(d -> d.setChildren(this.getChildren(d, list))).collect(Collectors.toList());
     }
+
+    @Override
+    public List<Tree<Long>> buildTree(List<DeptVO> list) {
+        return TreeUtils.build(list, (dept, tree) -> {
+            tree.setId(dept.getDeptId());
+            tree.setName(dept.getDeptName());
+            tree.setParentId(dept.getParentId());
+            tree.setWeight(dept.getDeptSort());
+        });
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long create(CreateDeptRequest request) {
+        SysDept sysDept = BeanUtil.copyProperties(request, SysDept.class);
+        sysDept.setStatus(DisEnableStatusEnum.ENABLE);
+        deptMapper.insert(sysDept);
+        return sysDept.getDeptId();
+    }
+
+    @Override
+    public boolean checkDeptNameExist(String deptName, Long parentId, Long deptId) {
+        return deptMapper.exists(Wrappers.<SysDept>lambdaQuery().eq(SysDept::getDeptName, deptName)
+            .eq(SysDept::getParentId, parentId).ne(deptId != null, SysDept::getDeptId, deptId));
+    }
+
+    /**
+     * 填充数据
+     *
+     * @param vo
+     *            VO
+     */
+    private void fill(DeptVO vo) {
+        Long updateUser = vo.getUpdateUser();
+        if (updateUser == null) {
+            return;
+        }
+        vo.setUpdateUserString(ExceptionUtils.exToNull(() -> userService.getById(vo.getUpdateUser())).getNickname());
+    }
 }
diff --git a/continew-admin-ui/src/api/common/index.ts b/continew-admin-ui/src/api/common/index.ts
new file mode 100644
index 00000000..b0365b6e
--- /dev/null
+++ b/continew-admin-ui/src/api/common/index.ts
@@ -0,0 +1,13 @@
+import axios from 'axios';
+import qs from 'query-string';
+import { DeptParams } from '@/api/system/dept';
+import { TreeNodeData } from '@arco-design/web-vue';
+
+export default function getDeptTree(params: DeptParams) {
+  return axios.get<TreeNodeData[]>('/common/tree/dept', {
+    params,
+    paramsSerializer: (obj) => {
+      return qs.stringify(obj);
+    },
+  });
+}
\ No newline at end of file
diff --git a/continew-admin-ui/src/api/system/dept.ts b/continew-admin-ui/src/api/system/dept.ts
index 45d9e659..0d21ef6d 100644
--- a/continew-admin-ui/src/api/system/dept.ts
+++ b/continew-admin-ui/src/api/system/dept.ts
@@ -1,11 +1,10 @@
 import axios from 'axios';
-
 import qs from 'query-string';
 
 export interface DeptRecord {
   deptId: string;
   deptName: string;
-  parentId: string;
+  parentId: number;
   deptSort: number;
   description: string;
   status: number;
@@ -14,10 +13,9 @@ export interface DeptRecord {
   children: Array<DeptRecord>,
 }
 
-export interface DeptParams extends Partial<DeptRecord> {
-  page: number;
-  size: number;
-  sort: Array<string>;
+export interface DeptParams {
+  deptName?: string;
+  status?: number;
 }
 
 export function getDeptList(params: DeptParams) {
@@ -27,4 +25,14 @@ export function getDeptList(params: DeptParams) {
       return qs.stringify(obj);
     },
   });
+}
+
+export interface CreateDeptReq {
+  parentId: number;
+  deptName: string;
+  deptSort: number;
+  description: string;
+}
+export function createDept(req: CreateDeptReq) {
+  return axios.post('/system/dept', req);
 }
\ No newline at end of file
diff --git a/continew-admin-ui/src/views/list/search-table/locale/en-US.ts b/continew-admin-ui/src/views/list/search-table/locale/en-US.ts
index c5ac3326..666bc8b7 100644
--- a/continew-admin-ui/src/views/list/search-table/locale/en-US.ts
+++ b/continew-admin-ui/src/views/list/search-table/locale/en-US.ts
@@ -19,6 +19,9 @@ export default {
   'searchTable.form.reset': 'Reset',
   'searchTable.form.selectDefault': 'All',
   'searchTable.operation.create': 'Create',
+  'searchTable.operation.update': 'Update',
+  'searchTable.operation.delete': 'Delete',
+  'searchTable.operation.export': 'Export',
   'searchTable.operation.import': 'Import',
   'searchTable.operation.download': 'Download',
   // columns
diff --git a/continew-admin-ui/src/views/list/search-table/locale/zh-CN.ts b/continew-admin-ui/src/views/list/search-table/locale/zh-CN.ts
index ca4cd442..ef4e8533 100644
--- a/continew-admin-ui/src/views/list/search-table/locale/zh-CN.ts
+++ b/continew-admin-ui/src/views/list/search-table/locale/zh-CN.ts
@@ -18,7 +18,10 @@ export default {
   'searchTable.form.search': '查询',
   'searchTable.form.reset': '重置',
   'searchTable.form.selectDefault': '全部',
-  'searchTable.operation.create': '新建',
+  'searchTable.operation.create': '新增',
+  'searchTable.operation.update': '修改',
+  'searchTable.operation.delete': '删除',
+  'searchTable.operation.export': '导出',
   'searchTable.operation.import': '批量导入',
   'searchTable.operation.download': '下载',
   // columns
diff --git a/continew-admin-ui/src/views/system/dept/index.vue b/continew-admin-ui/src/views/system/dept/index.vue
index d9dafc09..81f3139a 100644
--- a/continew-admin-ui/src/views/system/dept/index.vue
+++ b/continew-admin-ui/src/views/system/dept/index.vue
@@ -12,17 +12,17 @@
                   v-model="queryFormData.deptName"
                   placeholder="输入部门名称搜索"
                   allow-clear
-                  style="width: 150px;"
+                  style="width: 150px"
                   @press-enter="toQuery"
                 />
               </a-form-item>
               <a-form-item field="status" hide-label>
                 <a-select
                   v-model="queryFormData.status"
-                  :options="statusOptions"
+                  :options="treeData"
                   placeholder="状态搜索"
                   allow-clear
-                  style="width: 150px;"
+                  style="width: 150px"
                 />
               </a-form-item>
               <a-button type="primary" @click="toQuery">
@@ -40,24 +40,230 @@
             </a-form>
           </div>
 
+          <!-- 工具栏 -->
+          <a-row style="margin-bottom: 16px">
+            <a-col :span="12">
+              <a-space>
+                <a-button type="primary" @click="toCreate">
+                  <template #icon>
+                    <icon-plus />
+                  </template>
+                  {{ $t('searchTable.operation.create') }}
+                </a-button>
+                <a-button
+                  type="primary"
+                  status="success"
+                  disabled
+                  title="尚未开发"
+                >
+                  <template #icon>
+                    <icon-edit />
+                  </template>
+                  {{ $t('searchTable.operation.update') }}
+                </a-button>
+                <a-button
+                  type="primary"
+                  status="danger"
+                  disabled
+                  title="尚未开发"
+                >
+                  <template #icon>
+                    <icon-delete />
+                  </template>
+                  {{ $t('searchTable.operation.delete') }}
+                </a-button>
+              </a-space>
+            </a-col>
+            <a-col
+              :span="12"
+              style="display: flex; align-items: center; justify-content: end"
+            >
+              <a-button
+                type="primary"
+                status="warning"
+                disabled
+                title="尚未开发"
+              >
+                <template #icon>
+                  <icon-download />
+                </template>
+                {{ $t('searchTable.operation.export') }}
+              </a-button>
+              <a-tooltip :content="$t('searchTable.actions.refresh')">
+                <div class="action-icon" @click="toQuery">
+                  <icon-refresh size="18" />
+                </div>
+              </a-tooltip>
+              <a-dropdown @select="handleSelectDensity">
+                <a-tooltip :content="$t('searchTable.actions.density')">
+                  <div class="action-icon"><icon-line-height size="18" /></div>
+                </a-tooltip>
+                <template #content>
+                  <a-doption
+                    v-for="item in densityList"
+                    :key="item.value"
+                    :value="item.value"
+                    :class="{ active: item.value === size }"
+                  >
+                    <span>{{ item.name }}</span>
+                  </a-doption>
+                </template>
+              </a-dropdown>
+              <a-tooltip :content="$t('searchTable.actions.columnSetting')">
+                <a-popover
+                  trigger="click"
+                  position="bl"
+                  @popup-visible-change="popupVisibleChange"
+                >
+                  <div class="action-icon"><icon-settings size="18" /></div>
+                  <template #content>
+                    <div id="tableSetting">
+                      <div
+                        v-for="(item, index) in showColumns"
+                        :key="item.dataIndex"
+                        class="setting"
+                      >
+                        <div style="margin-right: 4px; cursor: move">
+                          <icon-drag-arrow />
+                        </div>
+                        <div>
+                          <a-checkbox
+                            v-model="item.checked"
+                            @change="handleChange($event, item as TableColumnData, index)"
+                          >
+                          </a-checkbox>
+                        </div>
+                        <div class="title">
+                          {{ item.title === '#' ? '序列号' : item.title }}
+                        </div>
+                      </div>
+                    </div>
+                  </template>
+                </a-popover>
+              </a-tooltip>
+            </a-col>
+          </a-row>
+
           <!-- 表格渲染 -->
           <a-table
-            :columns="columns"
+            ref="tableRef"
+            :columns="cloneColumns as TableColumnData[]"
             :data="renderData"
             :pagination="false"
             :default-expand-all-rows="true"
             :hide-expand-button-on-empty="true"
-            ref="tableRef"
             row-key="deptId"
             :bordered="false"
             :stripe="true"
             :loading="loading"
-            size="large"
+            :size="size"
           >
             <template #status="{ record }">
-              <a-switch v-model="record.status" :checked-value="1" :unchecked-value="2" />
+              <a-switch
+                v-model="record.status"
+                :checked-value="1"
+                :unchecked-value="2"
+              />
+            </template>
+            <template #operations>
+              <a-button
+                v-permission="['admin']"
+                type="text"
+                size="small"
+                disabled
+                title="尚未开发"
+              >
+                <template #icon>
+                  <icon-edit />
+                </template>
+                修改
+              </a-button>
+              <a-button
+                v-permission="['admin']"
+                type="text"
+                size="small"
+                disabled
+                title="尚未开发"
+              >
+                <template #icon>
+                  <icon-delete />
+                </template>
+                删除
+              </a-button>
             </template>
           </a-table>
+
+          <!-- 窗口 -->
+          <a-modal
+            title="新增部门"
+            :width="570"
+            :visible="visible"
+            :mask-closable="false"
+            unmount-on-close
+            @ok="handleOk"
+            @cancel="handleCancel"
+          >
+            <a-form ref="formRef" :model="formData" :rules="rules">
+              <a-form-item
+                field="parentId"
+                :validate-trigger="['change', 'blur']"
+                label="上级部门"
+              >
+                <a-tree-select
+                  v-model="formData.parentId"
+                  :data="treeData"
+                  :allow-search="true"
+                  :allow-clear="true"
+                  :filter-tree-node="filterDept"
+                  placeholder="请选择上级部门"
+                />
+              </a-form-item>
+              <a-form-item
+                field="deptName"
+                :validate-trigger="['change', 'blur']"
+                label="部门名称"
+              >
+                <a-input
+                  v-model="formData.deptName"
+                  placeholder="请输入部门名称"
+                  size="large"
+                  allow-clear
+                >
+                </a-input>
+              </a-form-item>
+              <a-form-item
+                field="deptSort"
+                :validate-trigger="['change', 'blur']"
+                label="部门排序"
+              >
+                <a-input-number
+                  v-model="formData.deptSort"
+                  :min="1"
+                  placeholder="请输入部门排序"
+                  mode="button"
+                  size="large"
+                >
+                </a-input-number>
+              </a-form-item>
+              <a-form-item
+                field="description"
+                :validate-trigger="['change', 'blur']"
+                label="描述"
+              >
+                <a-textarea
+                  v-model="formData.description"
+                  placeholder="请输入描述"
+                  size="large"
+                  :max-length="200"
+                  show-word-limit
+                  :auto-size="{
+                    minRows:3,
+                  }"
+                >
+                </a-textarea >
+              </a-form-item>
+            </a-form>
+          </a-modal>
         </a-col>
       </a-row>
     </a-card>
@@ -65,19 +271,47 @@
 </template>
 
 <script lang="ts" setup>
-  import { computed, ref } from 'vue';
+  import { computed, nextTick, reactive, ref, watch } from 'vue';
   import useLoading from '@/hooks/loading';
-  import { Message, TableInstance } from '@arco-design/web-vue';
-  import { getDeptList, DeptRecord, DeptParams } from '@/api/system/dept';
+  import { FieldRule, Message, TableInstance, TreeNodeData } from '@arco-design/web-vue';
+  import { getDeptList, DeptRecord, DeptParams, createDept } from '@/api/system/dept';
+  import getDeptTree from '@/api/common';
   import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
   import { FormInstance } from '@arco-design/web-vue/es/form';
   import { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
+  import cloneDeep from 'lodash/cloneDeep';
+  import Sortable from 'sortablejs';
+  import { useI18n } from 'vue-i18n';
 
+  type SizeProps = 'mini' | 'small' | 'medium' | 'large';
+  type Column = TableColumnData & { checked?: true };
+  const cloneColumns = ref<Column[]>([]);
+  const showColumns = ref<Column[]>([]);
+  const size = ref<SizeProps>('large');
+  const { t } = useI18n();
+  const densityList = computed(() => [
+    {
+      name: t('searchTable.size.mini'),
+      value: 'mini',
+    },
+    {
+      name: t('searchTable.size.small'),
+      value: 'small',
+    },
+    {
+      name: t('searchTable.size.medium'),
+      value: 'medium',
+    },
+    {
+      name: t('searchTable.size.large'),
+      value: 'large',
+    },
+  ]);
   const { loading, setLoading } = useLoading(true);
   const tableRef = ref<TableInstance>();
   const queryFormRef = ref<FormInstance>();
   const queryFormData = ref({
-    deptName: '',
+    deptName: undefined,
     status: undefined,
   });
   const statusOptions = computed<SelectOptionData[]>(() => [
@@ -133,11 +367,18 @@
       title: '修改时间',
       dataIndex: 'updateTime',
     },
+    {
+      title: '操作',
+      slotName: 'operations',
+      align: 'center',
+    },
   ]);
 
   // 分页查询列表
   const fetchData = async (
-    params: DeptParams = { page: 1, size: 10, sort: ['parentId,asc', 'deptSort,asc', 'createTime,desc'] }
+    params: DeptParams = {
+      ...queryFormData.value
+    }
   ) => {
     setLoading(true);
     try {
@@ -146,11 +387,128 @@
     } finally {
       setLoading(false);
     }
-    setTimeout(function() {
+    setTimeout(() => {
       tableRef.value?.expandAll();
     }, 0);
   };
   fetchData();
+
+  const handleSelectDensity = (
+    val: string | number | Record<string, any> | undefined,
+    e: Event
+  ) => {
+    size.value = val as SizeProps;
+  };
+
+  const handleChange = (
+    checked: boolean | (string | boolean | number)[],
+    column: Column,
+    index: number
+  ) => {
+    if (!checked) {
+      cloneColumns.value = showColumns.value.filter(
+        (item) => item.dataIndex !== column.dataIndex
+      );
+    } else {
+      cloneColumns.value.splice(index, 0, column);
+    }
+  };
+
+  const exchangeArray = <T extends Array<any>>(
+    array: T,
+    beforeIdx: number,
+    newIdx: number,
+    isDeep = false
+  ): T => {
+    const newArray = isDeep ? cloneDeep(array) : array;
+    if (beforeIdx > -1 && newIdx > -1) {
+      // 先替换后面的,然后拿到替换的结果替换前面的
+      newArray.splice(
+        beforeIdx,
+        1,
+        newArray.splice(newIdx, 1, newArray[beforeIdx]).pop()
+      );
+    }
+    return newArray;
+  };
+
+  const popupVisibleChange = (val: boolean) => {
+    if (val) {
+      nextTick(() => {
+        const el = document.getElementById('tableSetting') as HTMLElement;
+        const sortable = new Sortable(el, {
+          onEnd(e: any) {
+            const { oldIndex, newIndex } = e;
+            exchangeArray(cloneColumns.value, oldIndex, newIndex);
+            exchangeArray(showColumns.value, oldIndex, newIndex);
+          },
+        });
+      });
+    }
+  };
+
+  watch(
+    () => columns.value,
+    (val) => {
+      cloneColumns.value = cloneDeep(val);
+      cloneColumns.value.forEach((item, index) => {
+        item.checked = true;
+      });
+      showColumns.value = cloneDeep(cloneColumns.value);
+    },
+    { deep: true, immediate: true }
+  );
+
+  const visible = ref(false);
+  const treeData = ref<TreeNodeData[]>();
+  const formRef = ref<FormInstance>();
+  const formData = reactive({
+    parentId: undefined,
+    deptName: '',
+    deptSort: 999,
+    description: '',
+  });
+  const rules = computed((): Record<string, FieldRule[]> => {
+    return {
+      deptName: [
+        { required: true, message: '请输入部门名称' }
+      ],
+      deptSort: [
+        { required: true, message: '请输入部门排序' }
+      ],
+    };
+  });
+  // 创建
+  const toCreate = async () => {
+    visible.value = true;
+    const { data } = await getDeptTree({});
+    treeData.value = data;
+  };
+  const filterDept = (searchValue: string, nodeData: TreeNodeData) => {
+    if (nodeData.title) {
+      return nodeData.title.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
+    }
+    return false;
+  };
+  const handleOk = async () => {
+    const errors = await formRef.value?.validate();
+    if (errors) return false;
+    const res = await createDept({
+      parentId: formData.parentId || 0,
+      deptName: formData.deptName,
+      deptSort: formData.deptSort,
+      description: formData.description,
+    });
+    if (!res.success) return false;
+    Message.success(res.msg);
+    handleCancel();
+    fetchData();
+    return true;
+  };
+  const handleCancel = () => {
+    visible.value = false;
+    formRef.value?.resetFields()
+  };
 </script>
 
 <script lang="ts">
@@ -179,4 +537,22 @@
       }
     }
   }
+
+  .action-icon {
+    margin-left: 12px;
+    cursor: pointer;
+  }
+  .active {
+    color: #0960bd;
+    background-color: #e3f4fc;
+  }
+  .setting {
+    display: flex;
+    align-items: center;
+    width: 200px;
+    .title {
+      margin-left: 12px;
+      cursor: pointer;
+    }
+  }
 </style>
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
new file mode 100644
index 00000000..f2f5e8dd
--- /dev/null
+++ b/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/common/CommonController.java
@@ -0,0 +1,61 @@
+/*
+ * 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.common;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.hutool.core.lang.tree.Tree;
+
+import top.charles7c.cnadmin.common.model.vo.R;
+import top.charles7c.cnadmin.system.model.query.DeptQuery;
+import top.charles7c.cnadmin.system.model.vo.DeptVO;
+import top.charles7c.cnadmin.system.service.DeptService;
+
+/**
+ * 公共 API
+ *
+ * @author Charles7c
+ * @since 2023/1/22 21:48
+ */
+@Tag(name = "公共 API")
+@Validated
+@RestController
+@RequiredArgsConstructor
+@RequestMapping(value = "/common", produces = MediaType.APPLICATION_JSON_VALUE)
+public class CommonController {
+
+    private final DeptService deptService;
+
+    @Operation(summary = "查询部门树")
+    @GetMapping("/tree/dept")
+    public R<List<Tree<Long>>> deptTree(@Validated DeptQuery query) {
+        List<DeptVO> list = deptService.list(query);
+        List<Tree<Long>> deptTree = deptService.buildTree(list);
+        return R.ok(deptTree);
+    }
+}
diff --git a/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/system/DeptController.java b/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/system/DeptController.java
index 6bcc5c86..a5c5190c 100644
--- a/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/system/DeptController.java
+++ b/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/system/DeptController.java
@@ -25,12 +25,11 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 
 import org.springframework.http.MediaType;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import top.charles7c.cnadmin.common.model.vo.R;
 import top.charles7c.cnadmin.system.model.query.DeptQuery;
+import top.charles7c.cnadmin.system.model.request.CreateDeptRequest;
 import top.charles7c.cnadmin.system.model.vo.DeptVO;
 import top.charles7c.cnadmin.system.service.DeptService;
 
@@ -53,6 +52,19 @@ public class DeptController {
     @GetMapping
     public R<List<DeptVO>> list(@Validated DeptQuery query) {
         List<DeptVO> list = deptService.list(query);
-        return R.ok(list);
+        return R.ok(deptService.buildListTree(list));
+    }
+
+    @Operation(summary = "新增部门")
+    @PostMapping
+    public R<Long> create(@Validated @RequestBody CreateDeptRequest request) {
+        // 校验
+        String deptName = request.getDeptName();
+        boolean isExist = deptService.checkDeptNameExist(deptName, request.getParentId(), null);
+        if (isExist) {
+            return R.fail(String.format("新增失败,'%s'已存在", deptName));
+        }
+
+        return R.ok("新增成功", deptService.create(request));
     }
 }