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)); } }