diff --git a/src/main/java/com/budwk/app/sys/controller/SysUnitController.java b/src/main/java/com/budwk/app/sys/controller/SysUnitController.java index 9a72ff3..f46054c 100644 --- a/src/main/java/com/budwk/app/sys/controller/SysUnitController.java +++ b/src/main/java/com/budwk/app/sys/controller/SysUnitController.java @@ -3,10 +3,6 @@ package com.budwk.app.sys.controller; import cn.dev33.satoken.annotation.SaCheckLogin; import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.stp.StpUtil; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.tree.Tree; -import cn.hutool.core.lang.tree.TreeNode; -import cn.hutool.core.lang.tree.TreeUtil; import cn.hutool.core.util.StrUtil; import com.budwk.app.base.annotation.SLog; import com.budwk.app.base.constant.RoleConstant; @@ -49,7 +45,6 @@ import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import java.util.ArrayList; import java.util.List; -import java.util.Map; ; @@ -60,8 +55,6 @@ import java.util.Map; @At("/platform/sys/unit") public class SysUnitController { private static final Log log = Logs.get(); - /** 浙江交通职业技术学院在 sys_unit 中的单位主键,作为单位树未指定 pid 时的默认根。 */ - private static final String DEFAULT_UNIT_ROOT_ID = "4133012036"; @Inject private SysUnitService sysUnitService; @Inject @@ -78,20 +71,17 @@ public class SysUnitController { } + /** + * @param pageForm 分页参数,pageNumber 为页码,pageSize 为每页条数 + * @param unitName 单位名称模糊查询条件,可为空 + * @param unitId 上级单位ID,可为空;为空时保留原有单位范围 + * @return Result,data.list 为按生日祝福编号排序的单位,data.totalCount 为总条数 + */ @At("/pageData") @Ok("json") @SaCheckLogin public Object pageData(PageForm pageForm, String unitName, String unitId) { - Cnd cnd = Cnd.NEW(); -// cnd.and("parentId", "is not", null).andEX("unitLevel", "=", unitLevel).asc("unitLevel").asc("unitcode"); - cnd.and("parentId", "is not", null); - cnd.andEX("parentId", "=", unitId); -// cnd.and("unitTypeCode", "=", "1"); - cnd.asc("unitcode"); - cnd.and(Cnd.likeEX("name", unitName)); - - Pagination listPage = sysUnitService.listPage(pageForm.getPageNumber(), pageForm.getPageSize(), Sys_unit.class, cnd); - return Result.success(listPage); + return Result.success(sysUnitService.pageUnits(pageForm, unitName, unitId)); } @At("/userPageData") @@ -297,43 +287,34 @@ public class SysUnitController { } } + /** + * @param pid 单位树根ID;为空时使用学校默认根节点 + * @param req 当前请求上下文 + * @return Result,data 为含 id、parentId、name、unitTypeCode、children 的单位树列表 + */ @At("/tree") @Ok("json") @SaCheckLogin public Object tree(@Param("pid") String pid, HttpServletRequest req) { try { - String rootId = StrUtil.blankToDefault(pid, DEFAULT_UNIT_ROOT_ID); - String virtualRootId = "root"; - Cnd cnd = Cnd.NEW(); - cnd.asc("unitcode"); - List list = sysUnitService.query(cnd); - - List> nodeList = CollUtil.newArrayList(); - for (int i = 0; i < list.size(); i++) { - Sys_unit unit = list.get(i); - /* - * 单位根节点存在 id 与 parentId 都为 0 的自引用数据。 - * 构建树时把当前查询根挂到虚拟根下,避免“中国地质大学”和 parentId=0 的学院被构造成同级。 - */ - String parentId = rootId.equals(unit.getId()) ? virtualRootId : unit.getParentId(); - nodeList.add(new TreeNode<>(unit.getId(), parentId, unit.getName(), i) - .setExtra( - Map.of( - "unitTypeCode", unit.getUnitTypeCode() - ) - )); - } - List> treeList = TreeUtil.build(nodeList, virtualRootId); - return Result.success(treeList); + return Result.success(sysUnitService.getUnitTree(pid)); } catch (Exception e) { + log.error("单位树加载失败,路径=/platform/sys/unit/tree,pid=" + pid, e); return Result.error(); } } + /** + * @param unit 单位表单;name/unitcode 为名称和编码,aliasName 为最长100字简称,birthdaySortNo 为可空整数编号 + * @param parentId 上级单位ID,新建时决定所属单位 + * @param req 请求上下文;编辑时 birthdaySortNo 传空表示清空,未传表示保留 + * @return Result,code=0 表示保存成功,msg 为操作提示 + */ @At @Ok("json") @SaCheckPermission("sys.manager.unit.add") @SLog(tag = "新建单位", msg = "单位名称:${args[0].name}") + @Aop(TransAop.READ_COMMITTED) public Object addDo(@Param("..") Sys_unit unit, @Param("parentId") String parentId, HttpServletRequest req) { try { if ("root".equals(parentId)) { @@ -343,6 +324,7 @@ public class SysUnitController { sysUnitService.save(unit, parentId); return Result.success(); } catch (Exception e) { + log.error("单位保存失败,接口=addDo,单位ID=" + unit.getId(), e); return Result.error(); } } @@ -373,16 +355,24 @@ public class SysUnitController { } } + /** + * @param unit 单位表单;name/unitcode 为名称和编码,aliasName 为最长100字简称,birthdaySortNo 为可空整数编号 + * @param parentId 上级单位ID,新建时决定所属单位 + * @param req 请求上下文;编辑时 birthdaySortNo 传空表示清空,未传表示保留 + * @return Result,code=0 表示保存成功,msg 为操作提示 + */ @At @Ok("json") @SaCheckPermission("sys.manager.unit.edit") @SLog(tag = "编辑单位", msg = "单位名称:${args[0].name}") + @Aop(TransAop.READ_COMMITTED) public Object editDo(@Param("..") Sys_unit unit, @Param("parentId") String parentId, HttpServletRequest req) { try { unit.setUpdatedBy(SecurityUtil.getUserId()); - sysUnitService.updateIgnoreNull(unit); + sysUnitService.updateUnit(unit, req.getParameter("birthdaySortNo") != null); return Result.success(); } catch (Exception e) { + log.error("单位保存失败,接口=editDo,单位ID=" + unit.getId(), e); return Result.error(); } } diff --git a/src/main/java/com/budwk/app/sys/services/SysUnitService.java b/src/main/java/com/budwk/app/sys/services/SysUnitService.java index 1214da9..b1befb0 100644 --- a/src/main/java/com/budwk/app/sys/services/SysUnitService.java +++ b/src/main/java/com/budwk/app/sys/services/SysUnitService.java @@ -1,5 +1,7 @@ package com.budwk.app.sys.services; +import cn.hutool.core.lang.tree.Tree; +import java.util.List; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.param.PageForm; import com.budwk.app.base.service.BaseService; @@ -9,6 +11,27 @@ import com.budwk.app.sys.models.Sys_unit; * Created by wizzer on 2016/12/22. */ public interface SysUnitService extends BaseService { + /** + * 按生日祝福编号升序分页;空编号排后,同编号和空编号按单位编码升序。 + * @param pageForm 分页参数,pageNumber 为页码,pageSize 为每页条数 + * @param unitName 单位名称模糊查询条件,可为空 + * @param unitId 上级单位ID,可为空;为空时查询 parentId 非空的单位 + * @return 分页结果,list 为当前页单位,totalCount 为符合条件的单位总数 + */ + Pagination pageUnits(PageForm pageForm, String unitName, String unitId); + + /** + * @param pid 查询根单位ID;为空时使用学校默认根 + * @return 单位树,节点包含 id、parentId、name、unitTypeCode 和 children;类别允许为空 + */ + List> getUnitTree(String pid); + + /** + * @param unit 待编辑单位,id 必填;aliasName 为简称,birthdaySortNo 为可空整数 + * @param birthdaySortNoProvided 请求是否提交排序编号;true 时允许将原编号清空,false 时保留原值 + */ + void updateUnit(Sys_unit unit, boolean birthdaySortNoProvided); + /** * 分页查询指定分工会的组成单位。 * diff --git a/src/main/java/com/budwk/app/sys/services/impl/SysUnitServiceImpl.java b/src/main/java/com/budwk/app/sys/services/impl/SysUnitServiceImpl.java index f805b30..88613e2 100644 --- a/src/main/java/com/budwk/app/sys/services/impl/SysUnitServiceImpl.java +++ b/src/main/java/com/budwk/app/sys/services/impl/SysUnitServiceImpl.java @@ -1,6 +1,13 @@ package com.budwk.app.sys.services.impl; import cn.hutool.core.util.StrUtil; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeNode; +import cn.hutool.core.lang.tree.TreeUtil; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import com.budwk.app.base.exception.BaseException; import com.budwk.app.base.constant.RedisConstant; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.param.PageForm; @@ -25,6 +32,69 @@ import org.nutz.plugins.wkcache.annotation.CacheRemoveAll; @IocBean(args = {"refer:dao"}) @CacheDefaults(cacheName = RedisConstant.PLATFORM_REDIS_WKCACHE_PREFIX + "sys_unit",isHash = true) public class SysUnitServiceImpl extends BaseServiceImpl implements SysUnitService { + /** 学校单位主键,未指定 pid 时作为单位树的默认根。 */ + private static final String DEFAULT_UNIT_ROOT_ID = "4133012036"; + + /** + * 单位列表与树共用数据库排序:有编号的优先(含0),编号升序后以单位编码升序兜底。 + * @return 仅包含排序规则的新条件对象,可继续追加各自的查询条件 + */ + private Cnd unitBirthdayOrder() { + Cnd cnd = Cnd.NEW(); + cnd.asc("CASE WHEN birthdaySortNo IS NULL THEN 1 ELSE 0 END") + .asc("birthdaySortNo").asc("unitcode"); + return cnd; + } + + /** 保留单位名称与上级单位筛选条件,在数据库分页前完成统一排序。 */ + @Override + public Pagination pageUnits(PageForm pageForm, String unitName, String unitId) { + Cnd cnd = unitBirthdayOrder(); + cnd.and("parentId", "is not", null); + cnd.andEX("parentId", "=", unitId); + cnd.and(Cnd.likeEX("name", unitName)); + return listPage(pageForm.getPageNumber(), pageForm.getPageSize(), Sys_unit.class, cnd); + } + + /** 构建单位树时保留类别空值,避免历史数据因 Map.of 禁止 null 而导致整棵树加载失败。 */ + @Override + public List> getUnitTree(String pid) { + String rootId = StrUtil.blankToDefault(pid, DEFAULT_UNIT_ROOT_ID); + String virtualRootId = "root"; + List list = query(unitBirthdayOrder()); + + List> nodeList = new ArrayList<>(); + for (int i = 0; i < list.size(); i++) { + Sys_unit unit = list.get(i); + /* + * 单位根节点存在 id 与 parentId 都为 0 的自引用数据。 + * 构建树时把当前查询根挂到虚拟根下,保证当前根节点与其下属单位的层级关系。 + */ + String parentId = rootId.equals(unit.getId()) ? virtualRootId : unit.getParentId(); + // 使用查询顺序作为权重,树构建后同级节点仍遵循生日祝福编号排序。 + nodeList.add(new TreeNode<>(unit.getId(), parentId, unit.getName(), i) + .setExtra( + new HashMap<>(java.util.Collections.singletonMap("unitTypeCode", unit.getUnitTypeCode())) + )); + } + List> treeList = TreeUtil.build(nodeList, virtualRootId); + return treeList; + } + + /** 编辑单位时保留原有非空更新规则,仅对明确提交的空排序编号执行清空。 */ + @Override + @Aop(TransAop.READ_COMMITTED) + public void updateUnit(Sys_unit unit, boolean birthdaySortNoProvided) { + if (unit == null || StrUtil.isBlank(unit.getId())) { + throw new BaseException("单位ID不能为空"); + } + updateIgnoreNull(unit); + // 未提交字段的其他调用方不受影响;显式空值需要绕过 updateIgnoreNull。 + if (birthdaySortNoProvided && unit.getBirthdaySortNo() == null) { + update(Chain.make("birthdaySortNo", null), Cnd.where("id", "=", unit.getId())); + } + } + public SysUnitServiceImpl(Dao dao) { super(dao); } diff --git a/src/main/resources/views/platform/sys/unit/unitManage.js b/src/main/resources/views/platform/sys/unit/unitManage.js index 956d398..07a5943 100644 --- a/src/main/resources/views/platform/sys/unit/unitManage.js +++ b/src/main/resources/views/platform/sys/unit/unitManage.js @@ -16,6 +16,8 @@ const UNIT_MANAGE_TEMPLATE = { + + @@ -29,16 +31,21 @@ const UNIT_MANAGE_TEMPLATE = { - + - - - + + + + + + + @@ -75,10 +82,11 @@ const UNIT_MANAGE_TEMPLATE = { - 取 消 - 确 定 + 取 消 + 确 定 + `, props: { @@ -111,6 +119,7 @@ const UNIT_MANAGE_TEMPLATE = { }, tableData: [], dialogVisible: false, + formLoading: false, formData: {}, formRules: { name: [{ required: true, message: "必填", trigger: "blur" }], @@ -129,51 +138,71 @@ const UNIT_MANAGE_TEMPLATE = { }, computed: {}, methods: { + // 通过组件方法关闭弹框,确保 $set 使用当前组件实例。 + closeDialog() { + this.$set(this, "dialogVisible", false) + }, doSearch() { this.pageForm.pageNumber = 1 this.pageData() }, openAdd() { this.dialogVisible = true - this.formData = {} //打开新增窗口,表单先清空 + // 新建时清空扩展字段,避免沿用上一次编辑的值。 + this.$set(this, "formData", { aliasName: "", birthdaySortNo: undefined }) this.parentUnit = [] this.options = [] }, doHandle() { + if (this.formLoading) return this.$confirm("确定要提交吗?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }).then(() => { - const self = this - const url = this.formData.id ? "/platform/sys/unit/editDo" : "/platform/sys/unit/addDo" - this.$refs["form"].validate(async function (valid) { - if (valid) { - if (self.currentNode) { - self.formData.parentId = self.currentData.id - } - const resp = await self.$axios.post(url, self.formData) - if (resp.code === 0) { - self.$message.success(resp.msg) - self.doSearch() - self.dialogVisible = false - self.$emit("refresh", null) - } else { - self.$message.warning(resp.msg) - } + this.$refs["form"].validate((valid) => { + if (!valid || this.formLoading) return + const url = this.formData.id ? "/platform/sys/unit/editDo" : "/platform/sys/unit/addDo" + if (this.currentNode) { + this.$set(this.formData, "parentId", this.currentData.id) } + // 明确提交空字符串表示清空;避免 undefined 被请求序列化忽略。 + const params = Object.assign({}, this.formData, { + aliasName: this.formData.aliasName || "", + birthdaySortNo: this.formData.birthdaySortNo == null ? "" : this.formData.birthdaySortNo + }) + this.formLoading = true + this.$axios.post(url, params).then((resp) => { + if (resp.code === 0) { + this.$message.success(resp.msg) + this.doSearch() + this.$set(this, "dialogVisible", false) + this.$emit("refresh", null) + } else { + this.$message.warning(resp.msg) + } + }).finally(() => { + this.formLoading = false + }) }) }) }, - async openEdit(id) { - console.log(this.currentData) - const resp = await this.$axios.post("/platform/sys/unit/edit/" + id) - if (resp.code === 0) { - this.formData = resp.data - this.dialogVisible = true - } else { - this.$message.warning(resp.msg) - } + openEdit(id) { + if (this.formLoading) return + this.formLoading = true + this.$axios.post("/platform/sys/unit/edit/" + id).then((resp) => { + if (resp.code === 0 && resp.data) { + this.$set(this, "formData", resp.data) + // 兼容历史空值,保留编号 0;数字控件使用 undefined 显示空输入。 + this.$set(this.formData, "aliasName", resp.data.aliasName || "") + this.$set(this.formData, "birthdaySortNo", resp.data.birthdaySortNo == null ? undefined : resp.data.birthdaySortNo) + this.$set(this, "dialogVisible", true) + } else { + this.$message.warning(resp.msg) + } + }).finally(() => { + this.formLoading = false + }) }, async doDelete(id) { const confirm = await this.$confirm("此操作将永久删除, 是否继续?", "提示", {