mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 10:57:13 +08:00 
			
		
		
		
	新增:新增系统管理/部门管理/新增功能
This commit is contained in:
		| @@ -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); | ||||
|     } | ||||
| } | ||||
| @@ -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; | ||||
| } | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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()); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										13
									
								
								continew-admin-ui/src/api/common/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								continew-admin-ui/src/api/common/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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); | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
| @@ -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) { | ||||
| @@ -28,3 +26,13 @@ export function getDeptList(params: DeptParams) { | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export interface CreateDeptReq { | ||||
|   parentId: number; | ||||
|   deptName: string; | ||||
|   deptSort: number; | ||||
|   description: string; | ||||
| } | ||||
| export function createDept(req: CreateDeptReq) { | ||||
|   return axios.post('/system/dept', req); | ||||
| } | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
| @@ -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)); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user