From e8ed1d93f98e9de7279a697f124290aad8d97ab5 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 29 Jul 2026 13:44:45 +0800 Subject: [PATCH 1/7] commit --- .../resources/views/layouts/v4/home/act.js | 108 +++++++++++------- .../views/layouts/v4/home/index.html | 11 +- 2 files changed, 78 insertions(+), 41 deletions(-) diff --git a/src/main/resources/views/layouts/v4/home/act.js b/src/main/resources/views/layouts/v4/home/act.js index c10e4647..2f3ef6f2 100644 --- a/src/main/resources/views/layouts/v4/home/act.js +++ b/src/main/resources/views/layouts/v4/home/act.js @@ -6,41 +6,39 @@ const act = {
-
- -
-
-
-
-
- - - {{getStatusText(activity)}} - -
-

- {{ activity.name}} -

- -
-

- - 时间:{{ activity.startDate }}至{{ activity.endDate }} -

-
+ +
+
+
+
+
+ + + {{getStatusText(activity)}} + +
+

+ {{ activity.name}} +

+ +
+

+ + 时间:{{ activity.startDate }}至{{ activity.endDate }} +

- - -
-
- - -
+ + +
+
+ + +
@@ -80,17 +78,33 @@ const act = { this.swiper.destroy(true, true) this.swiper = null } - this.swiper = new Swiper('.activity-swiper', { + const swiperElement = this.$refs.activitySwiper + if (!swiperElement) { + return + } + // 仅操作当前组件的轮播节点,避免页面其他轮播或尺寸观察器影响卡片宽度。 + this.swiper = new Swiper(swiperElement, { slidesPerView: 1, spaceBetween: 18, centeredSlides: false, loop: false, + watchOverflow: true, + observer: false, + observeParents: false, + resizeObserver: false, + // 鼠标停留在活动区域时可横向浏览,到首尾后释放页面滚动。 + mousewheel: { + enabled: true, + forceToAxis: false, + releaseOnEdges: true, + sensitivity: 0.8, + }, navigation: { - nextEl: '.swiper-button-next', - prevEl: '.swiper-button-prev', + nextEl: this.$refs.nextButton, + prevEl: this.$refs.prevButton, }, pagination: { - el: '.swiper-pagination', + el: this.$refs.pagination, type: 'progressbar', }, breakpoints: { @@ -134,10 +148,25 @@ const act = { mounted() { this.listAct() }, + beforeDestroy() { + if (this.swiper) { + this.swiper.destroy(true, true) + this.swiper = null + } + }, style: /*language=CSS*/ ` .activity-section-wrapper { width: 100%; min-width: 0; + max-width: 100%; + box-sizing: border-box; + overflow: hidden; + } + + .section-act { + width: 100%; + min-width: 0; + max-width: 100%; } .section-act .section-act-title span { @@ -182,8 +211,9 @@ const act = { position: relative; width: 100%; min-width: 0; + max-width: 100%; box-sizing: border-box; - padding: 9px 14px; + padding: 9px 54px 12px; /*margin-top: 30px;*/ overflow: hidden; } @@ -207,6 +237,8 @@ const act = { background: white; width: 40px; height: 40px; + top: 72px; + margin-top: 0; border-radius: 50%; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; @@ -225,11 +257,9 @@ const act = { font-weight: bold; } - /* 进度条样式 */ + /* 进度条不展示,活动通过左右箭头或区域内滚轮浏览。 */ .activity-swiper-container .swiper-pagination { - position: relative; - margin-top: 12px; - height: 4px; + display: none; } .activity-swiper-container .swiper-pagination-progressbar { diff --git a/src/main/resources/views/layouts/v4/home/index.html b/src/main/resources/views/layouts/v4/home/index.html index 2bf702d2..347e226d 100644 --- a/src/main/resources/views/layouts/v4/home/index.html +++ b/src/main/resources/views/layouts/v4/home/index.html @@ -53,16 +53,23 @@ layout("/layouts/v4/baseLayout.html"){ .act-card { display: flex; align-items: center; - min-width: 320px; + width: 100%; + min-width: 0; + max-width: 100%; background: white; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.06); - padding: 11px; + padding: 22px 11px; + box-sizing: border-box; + overflow: hidden; transition: transform 0.2s, box-shadow 0.2s; } .home-section { width: 100%; + min-width: 0; + max-width: 100%; + box-sizing: border-box; } .home-section-header { From ff3e8075b5dcd2119026c573ad7331e2bfbb21e4 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 29 Jul 2026 16:50:05 +0800 Subject: [PATCH 2/7] commit --- .../ProposalDashboardController.java | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/dashboard/ProposalDashboardController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/dashboard/ProposalDashboardController.java index f4b5b30b..8dfce38c 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/dashboard/ProposalDashboardController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/dashboard/ProposalDashboardController.java @@ -79,14 +79,19 @@ public class ProposalDashboardController { // 节点 ProcessDefine define = processDefineService.getLastByName("JDHTA_NC"); ProcessModel processModel = processDefineService.processDefineToModel(define); - List taskModels = processModel.getTasks(); + // 按流程图的连线关系遍历任务节点,保证看板展示顺序与流程图一致。 + List taskModels = processModel.getModels(TaskModel.class); List excludeNodes = new ArrayList<>(3); excludeNodes.add("撰写提案"); excludeNodes.add("邀请附议人"); excludeNodes.add("提案附议"); - List taskNodes = taskModels.stream().filter(taskModel -> !excludeNodes.contains(taskModel.getDisplayName())) + // opinion_unit_reply、two_unit_reply 为临时屏蔽的承办单位答复节点,不在看板展示。 + List hiddenNodeIds = List.of("opinion_unit_reply", "two_unit_reply"); + List taskNodes = new ArrayList<>(taskModels.stream() + .filter(taskModel -> !excludeNodes.contains(taskModel.getDisplayName())) + .filter(taskModel -> !hiddenNodeIds.contains(taskModel.getName())) .map(taskModel -> NutMap.NEW() .addv("name", taskModel.getDisplayName()) .addv("id", taskModel.getName()) @@ -94,19 +99,35 @@ public class ProposalDashboardController { .addv("count", 0) .addv("todoCount", 0) .addv("doneCount", 0) - ).toList(); - nodes.addAll(taskNodes); + ).toList()); // 立案结果 List caseFilingResult = sysDictService.getSubListByCode("PROPOSAL_CASE_FILING_RESULT"); + List caseFilingResultNodes = new ArrayList<>(); for (Sys_dict dict : caseFilingResult) { - nodes.add(NutMap.NEW() + caseFilingResultNodes.add(NutMap.NEW() .addv("name", dict.getName()) .addv("id", dict.getCode()) .addv("type", "caseFilingResult") .addv("count", 0)); } + // 立案结果需展示在委员会确认承办单位之前,反馈评价统一固定到看板末尾。 + List feedbackNodes = taskNodes.stream() + .filter(taskNode -> "feedback".equals(taskNode.getString("id"))) + .toList(); + taskNodes.removeIf(taskNode -> "feedback".equals(taskNode.getString("id"))); + int committeeFilingUnitIndex = taskNodes.size(); + for (int i = 0; i < taskNodes.size(); i++) { + if ("committeeFilingUnit".equals(taskNodes.get(i).getString("id"))) { + committeeFilingUnitIndex = i; + break; + } + } + taskNodes.addAll(committeeFilingUnitIndex, caseFilingResultNodes); + nodes.addAll(taskNodes); + nodes.addAll(feedbackNodes); + // 查询待办任务 Sql todoSql = Sqls.create(""" SELECT From 5043cee74765f9489f030081d2a33931216f2d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=9C=E5=AD=A6=E6=88=90?= <1274337408@qq.com> Date: Thu, 30 Jul 2026 15:42:06 +0800 Subject: [PATCH 3/7] =?UTF-8?q?web=E7=AB=AF-=E6=96=B0=E5=A2=9E=E9=A3=8E?= =?UTF-8?q?=E9=87=87=E5=A2=99=E6=A8=A1=E5=9D=97(=E5=B7=A5=E4=BC=9A?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E4=BC=A0=E6=92=AD=E4=B8=8E=E6=96=87=E5=8C=96?= =?UTF-8?q?=E5=B1=95=E7=A4=BA);?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SpreadCategoryController.java | 101 ++++++++ .../controller/SpreadCreationController.java | 81 ++++++ .../controller/SpreadDisplayController.java | 76 ++++++ .../controller/SpreadManageController.java | 78 ++++++ .../SpreadStatisticsController.java | 60 +++++ .../zhgh/spread/models/SpreadCategory.java | 55 ++++ .../app/zhgh/spread/models/SpreadContent.java | 99 ++++++++ .../zhgh/spread/param/SpreadContentQuery.java | 12 + .../spread/param/SpreadStatisticsQuery.java | 11 + .../spread/service/SpreadCategoryService.java | 22 ++ .../spread/service/SpreadContentService.java | 31 +++ .../service/SpreadStatisticsService.java | 15 ++ .../impl/SpreadCategoryServiceImpl.java | 180 ++++++++++++++ .../impl/SpreadContentServiceImpl.java | 234 ++++++++++++++++++ .../impl/SpreadStatisticsServiceImpl.java | 113 +++++++++ .../resources/db/menu/init_spread_menu.sql | 79 ++++++ src/main/resources/db/spread_create.sql | 76 ++++++ .../platform/zhgh/spread/category/index.html | 176 +++++++++++++ .../platform/zhgh/spread/creation/index.html | 198 +++++++++++++++ .../platform/zhgh/spread/display/index.html | 215 ++++++++++++++++ .../platform/zhgh/spread/manage/index.html | 180 ++++++++++++++ .../zhgh/spread/statistics/index.html | 178 +++++++++++++ 22 files changed, 2270 insertions(+) create mode 100644 src/main/java/com/budwk/app/zhgh/spread/controller/SpreadCategoryController.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/controller/SpreadCreationController.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/controller/SpreadDisplayController.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/controller/SpreadManageController.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/controller/SpreadStatisticsController.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/models/SpreadCategory.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/models/SpreadContent.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/param/SpreadContentQuery.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/param/SpreadStatisticsQuery.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/service/SpreadCategoryService.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/service/SpreadContentService.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/service/SpreadStatisticsService.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadCategoryServiceImpl.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadContentServiceImpl.java create mode 100644 src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadStatisticsServiceImpl.java create mode 100644 src/main/resources/db/menu/init_spread_menu.sql create mode 100644 src/main/resources/db/spread_create.sql create mode 100644 src/main/resources/views/platform/zhgh/spread/category/index.html create mode 100644 src/main/resources/views/platform/zhgh/spread/creation/index.html create mode 100644 src/main/resources/views/platform/zhgh/spread/display/index.html create mode 100644 src/main/resources/views/platform/zhgh/spread/manage/index.html create mode 100644 src/main/resources/views/platform/zhgh/spread/statistics/index.html diff --git a/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadCategoryController.java b/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadCategoryController.java new file mode 100644 index 00000000..073b686a --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadCategoryController.java @@ -0,0 +1,101 @@ +package com.budwk.app.zhgh.spread.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.annotation.SLog; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.spread.models.SpreadCategory; +import com.budwk.app.zhgh.spread.service.SpreadCategoryService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +@IocBean +@Ok("json:full") +@Api("风采墙-栏目管理") +@At("/platform/spread/category") +public class SpreadCategoryController { + + @Inject + private SpreadCategoryService categoryService; + + @At("") + @Ok("beetl:/platform/zhgh/spread/category/index.html") + @SaCheckPermission("spread.category") + public void index() { + } + + @At + @ApiOperation("栏目列表") + @SaCheckPermission("spread.category") + public Result listData() { + return Result.success(categoryService.listCategories(false)); + } + + @At + @ApiOperation("栏目树") + @SaCheckPermission("spread.category") + public Result treeData() { + return Result.success(categoryService.categoryTree(false)); + } + + @At + @ApiOperation("新增栏目") + @SaCheckPermission("spread.category") + @SLog(tag = "风采墙-栏目管理", msg = "新增栏目:${args[0].name}") + @Aop(TransAop.READ_COMMITTED) + public Result doAdd(SpreadCategory category) { + try { + categoryService.saveCategory(category); + return Result.success(); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } + + @At + @ApiOperation("编辑栏目") + @SaCheckPermission("spread.category") + @SLog(tag = "风采墙-栏目管理", msg = "编辑栏目:${args[0].id}") + @Aop(TransAop.READ_COMMITTED) + public Result doEdit(SpreadCategory category) { + try { + categoryService.updateCategory(category); + return Result.success(); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } + + @At + @ApiOperation("删除栏目") + @SaCheckPermission("spread.category") + @SLog(tag = "风采墙-栏目管理", msg = "删除栏目:${args[0]}") + @Aop(TransAop.READ_COMMITTED) + public Result doDelete(String id) { + try { + categoryService.deleteCategory(id); + return Result.success(); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } + + @At + @ApiOperation("设置栏目显示状态") + @SaCheckPermission("spread.category") + @SLog(tag = "风采墙-栏目管理", msg = "栏目:${args[0]},显示状态:${args[1]}") + @Aop(TransAop.READ_COMMITTED) + public Result doVisible(String id, Integer visible) { + try { + categoryService.setVisible(id, visible); + return Result.success(); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadCreationController.java b/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadCreationController.java new file mode 100644 index 00000000..181ca5e6 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadCreationController.java @@ -0,0 +1,81 @@ +package com.budwk.app.zhgh.spread.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.annotation.SLog; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.spread.models.SpreadContent; +import com.budwk.app.zhgh.spread.service.SpreadCategoryService; +import com.budwk.app.zhgh.spread.service.SpreadContentService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +@IocBean +@Ok("json:full") +@Api("风采墙-内容发布") +@At("/platform/spread/creation") +public class SpreadCreationController { + + @Inject + private SpreadCategoryService categoryService; + + @Inject + private SpreadContentService contentService; + + @At("") + @Ok("beetl:/platform/zhgh/spread/creation/index.html") + @SaCheckPermission("spread.creation") + public void index() { + } + + @At + @ApiOperation("可选栏目树") + @SaCheckPermission("spread.creation") + public Result categoryTree() { + return Result.success(categoryService.categoryTree(true)); + } + + @At + @ApiOperation("内容详情") + @SaCheckPermission("spread.creation") + public Result detailData(String id) { + try { + return Result.success(contentService.getContent(id)); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } + + @At + @ApiOperation("新增内容") + @SaCheckPermission("spread.creation") + @SLog(tag = "风采墙-内容发布", msg = "新增内容:${args[0].title}") + @Aop(TransAop.READ_COMMITTED) + public Result doAdd(SpreadContent content) { + try { + contentService.saveContent(content); + return Result.success(); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } + + @At + @ApiOperation("编辑内容") + @SaCheckPermission("spread.creation") + @SLog(tag = "风采墙-内容发布", msg = "编辑内容:${args[0].id}") + @Aop(TransAop.READ_COMMITTED) + public Result doEdit(SpreadContent content) { + try { + contentService.updateContent(content); + return Result.success(); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadDisplayController.java b/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadDisplayController.java new file mode 100644 index 00000000..3ec323f3 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadDisplayController.java @@ -0,0 +1,76 @@ +package com.budwk.app.zhgh.spread.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.spread.service.SpreadCategoryService; +import com.budwk.app.zhgh.spread.service.SpreadContentService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +@IocBean +@Ok("json:full") +@Api("风采墙-展示") +@At("/platform/spread/display") +public class SpreadDisplayController { + + @Inject + private SpreadCategoryService categoryService; + + @Inject + private SpreadContentService contentService; + + @At("") + @Ok("beetl:/platform/zhgh/spread/display/index.html") + @SaCheckPermission("spread.display") + public void index() { + } + + @At + @ApiOperation("可见栏目树") + @SaCheckPermission("spread.display") + public Result categoryTree() { + return Result.success(categoryService.categoryTree(true)); + } + + @At + @ApiOperation("已发布内容分页") + @SaCheckPermission("spread.display") + public Result pageData(PageForm pageForm, String categoryId) { + return Result.success(contentService.pagePublished(pageForm, categoryId)); + } + + @At + @ApiOperation("内容详情") + @SaCheckPermission("spread.display") + public Result detailData(String id) { + try { + return Result.success(contentService.getContent(id)); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } + + @At + @ApiOperation("轮播内容") + @SaCheckPermission("spread.display") + public Result sliderData() { + return Result.success(contentService.sliderContent()); + } + + @At + @ApiOperation("增加浏览量") + @SaCheckPermission("spread.display") + public Result addView(String id) { + try { + contentService.addViewCount(id); + return Result.success(); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadManageController.java b/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadManageController.java new file mode 100644 index 00000000..ac1ccd13 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadManageController.java @@ -0,0 +1,78 @@ +package com.budwk.app.zhgh.spread.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.annotation.SLog; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.spread.param.SpreadContentQuery; +import com.budwk.app.zhgh.spread.service.SpreadCategoryService; +import com.budwk.app.zhgh.spread.service.SpreadContentService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +@IocBean +@Ok("json:full") +@Api("风采墙-内容管理") +@At("/platform/spread/manage") +public class SpreadManageController { + + @Inject + private SpreadCategoryService categoryService; + + @Inject + private SpreadContentService contentService; + + @At("") + @Ok("beetl:/platform/zhgh/spread/manage/index.html") + @SaCheckPermission("spread.manage") + public void index() { + } + + @At + @ApiOperation("内容分页") + @SaCheckPermission("spread.manage") + public Result pageData(PageForm pageForm, SpreadContentQuery query) { + return Result.success(contentService.pageContent(pageForm, query)); + } + + @At + @ApiOperation("栏目树") + @SaCheckPermission("spread.manage") + public Result categoryTree() { + return Result.success(categoryService.categoryTree(false)); + } + + @At + @ApiOperation("删除内容") + @SaCheckPermission("spread.manage") + @SLog(tag = "风采墙-内容管理", msg = "删除内容:${args[0]}") + @Aop(TransAop.READ_COMMITTED) + public Result doDelete(String id) { + try { + contentService.deleteContent(id); + return Result.success(); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } + + @At + @ApiOperation("发布或撤回内容") + @SaCheckPermission("spread.manage") + @SLog(tag = "风采墙-内容管理", msg = "内容:${args[0]},发布状态:${args[1]}") + @Aop(TransAop.READ_COMMITTED) + public Result doPublish(String id, Integer status) { + try { + contentService.publishContent(id, status); + return Result.success(); + } catch (RuntimeException e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadStatisticsController.java b/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadStatisticsController.java new file mode 100644 index 00000000..e81056d3 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/controller/SpreadStatisticsController.java @@ -0,0 +1,60 @@ +package com.budwk.app.zhgh.spread.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.spread.param.SpreadStatisticsQuery; +import com.budwk.app.zhgh.spread.service.SpreadCategoryService; +import com.budwk.app.zhgh.spread.service.SpreadStatisticsService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +@IocBean +@Ok("json:full") +@Api("风采墙-统计分析") +@At("/platform/spread/statistics") +public class SpreadStatisticsController { + + @Inject + private SpreadCategoryService categoryService; + + @Inject + private SpreadStatisticsService statisticsService; + + @At("") + @Ok("beetl:/platform/zhgh/spread/statistics/index.html") + @SaCheckPermission("spread.statistics") + public void index() { + } + + @At + @ApiOperation("栏目树") + @SaCheckPermission("spread.statistics") + public Result categoryTree() { + return Result.success(categoryService.categoryTree(false)); + } + + @At + @ApiOperation("统计卡片") + @SaCheckPermission("spread.statistics") + public Result summary(SpreadStatisticsQuery query) { + return Result.success(statisticsService.summary(query)); + } + + @At + @ApiOperation("浏览量前十") + @SaCheckPermission("spread.statistics") + public Result top10(SpreadStatisticsQuery query) { + return Result.success(statisticsService.top10(query)); + } + + @At + @ApiOperation("栏目统计") + @SaCheckPermission("spread.statistics") + public Result categoryData(SpreadStatisticsQuery query) { + return Result.success(statisticsService.categoryStatistics(query)); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/models/SpreadCategory.java b/src/main/java/com/budwk/app/zhgh/spread/models/SpreadCategory.java new file mode 100644 index 00000000..371346c3 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/models/SpreadCategory.java @@ -0,0 +1,55 @@ +package com.budwk.app.zhgh.spread.models; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Comment; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.interceptor.annotation.PrevInsert; + +@Data +@Table("spread_category") +@EqualsAndHashCode(callSuper = true) +public class SpreadCategory extends BaseModel { + + @Column + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(uu32 = true) + private String id; + + @Column + @Comment("父栏目ID,0表示顶级栏目") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String parentId; + + @Column + @Comment("栏目名称") + @ColDefine(type = ColType.VARCHAR, width = 100) + private String name; + + @Column + @Comment("栏目编码") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String code; + + @Column + @Comment("排序") + @ColDefine(type = ColType.INT) + private Integer sortOrder; + + @Column + @Comment("是否可见") + @ColDefine(type = ColType.INT) + private Integer visible; + + @Column + @Comment("是否参与轮播") + @ColDefine(type = ColType.INT) + private Integer slider; +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/models/SpreadContent.java b/src/main/java/com/budwk/app/zhgh/spread/models/SpreadContent.java new file mode 100644 index 00000000..2d7eca2a --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/models/SpreadContent.java @@ -0,0 +1,99 @@ +package com.budwk.app.zhgh.spread.models; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Comment; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.interceptor.annotation.PrevInsert; + +@Data +@Table("spread_content") +@EqualsAndHashCode(callSuper = true) +public class SpreadContent extends BaseModel { + + @Column + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(uu32 = true) + private String id; + + @Column + @Comment("栏目ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String categoryId; + + @Column + @Comment("栏目名称") + @ColDefine(type = ColType.VARCHAR, width = 100) + private String categoryName; + + @Column + @Comment("标题") + @ColDefine(type = ColType.VARCHAR, width = 300) + private String title; + + @Column + @Comment("副标题") + @ColDefine(type = ColType.VARCHAR, width = 255) + private String subTitle; + + @Column + @Comment("摘要") + @ColDefine(type = ColType.VARCHAR, width = 1000) + private String summary; + + @Column + @Comment("正文") + @ColDefine(customType = "longtext") + private String contentBody; + + @Column + @Comment("封面图") + @ColDefine(type = ColType.VARCHAR, width = 1000) + private String thumbUrl; + + @Column + @Comment("轮播图片") + @ColDefine(customType = "longtext") + private String sliderImages; + + @Column + @Comment("是否置顶") + @ColDefine(type = ColType.INT) + private Integer isTop; + + @Column + @Comment("是否推荐") + @ColDefine(type = ColType.INT) + private Integer isRecommend; + + @Column + @Comment("是否头条") + @ColDefine(type = ColType.INT) + private Integer isHeadline; + + @Column + @Comment("浏览量") + @ColDefine(type = ColType.INT) + private Integer viewCount; + + @Column + @Comment("排序") + @ColDefine(type = ColType.INT) + private Integer sortOrder; + + @Column + @Comment("是否发布") + @ColDefine(type = ColType.INT) + private Integer isPublished; + + @Column + @Comment("发布时间") + private Long publishedAt; +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/param/SpreadContentQuery.java b/src/main/java/com/budwk/app/zhgh/spread/param/SpreadContentQuery.java new file mode 100644 index 00000000..0b744e57 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/param/SpreadContentQuery.java @@ -0,0 +1,12 @@ +package com.budwk.app.zhgh.spread.param; + +import lombok.Data; + +@Data +public class SpreadContentQuery { + private String categoryId; + private String title; + private Integer isPublished; + private String beginTime; + private String endTime; +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/param/SpreadStatisticsQuery.java b/src/main/java/com/budwk/app/zhgh/spread/param/SpreadStatisticsQuery.java new file mode 100644 index 00000000..b404da49 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/param/SpreadStatisticsQuery.java @@ -0,0 +1,11 @@ +package com.budwk.app.zhgh.spread.param; + +import lombok.Data; + +@Data +public class SpreadStatisticsQuery { + private String categoryId; + private Integer isPublished; + private String beginTime; + private String endTime; +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/service/SpreadCategoryService.java b/src/main/java/com/budwk/app/zhgh/spread/service/SpreadCategoryService.java new file mode 100644 index 00000000..6b2a714b --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/service/SpreadCategoryService.java @@ -0,0 +1,22 @@ +package com.budwk.app.zhgh.spread.service; + +import com.budwk.app.base.service.BaseService; +import com.budwk.app.zhgh.spread.models.SpreadCategory; +import org.nutz.lang.util.NutMap; + +import java.util.List; + +public interface SpreadCategoryService extends BaseService { + + List listCategories(boolean visibleOnly); + + List categoryTree(boolean visibleOnly); + + void saveCategory(SpreadCategory category); + + void updateCategory(SpreadCategory category); + + void deleteCategory(String id); + + void setVisible(String id, Integer visible); +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/service/SpreadContentService.java b/src/main/java/com/budwk/app/zhgh/spread/service/SpreadContentService.java new file mode 100644 index 00000000..07967870 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/service/SpreadContentService.java @@ -0,0 +1,31 @@ +package com.budwk.app.zhgh.spread.service; + +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.service.BaseService; +import com.budwk.app.zhgh.spread.models.SpreadContent; +import com.budwk.app.zhgh.spread.param.SpreadContentQuery; +import org.nutz.lang.util.NutMap; + +import java.util.List; + +public interface SpreadContentService extends BaseService { + + Pagination pageContent(PageForm pageForm, SpreadContentQuery query); + + Pagination pagePublished(PageForm pageForm, String categoryId); + + SpreadContent getContent(String id); + + void saveContent(SpreadContent content); + + void updateContent(SpreadContent content); + + void deleteContent(String id); + + void publishContent(String id, Integer status); + + void addViewCount(String id); + + List sliderContent(); +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/service/SpreadStatisticsService.java b/src/main/java/com/budwk/app/zhgh/spread/service/SpreadStatisticsService.java new file mode 100644 index 00000000..08b9002f --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/service/SpreadStatisticsService.java @@ -0,0 +1,15 @@ +package com.budwk.app.zhgh.spread.service; + +import com.budwk.app.zhgh.spread.param.SpreadStatisticsQuery; +import org.nutz.lang.util.NutMap; + +import java.util.List; + +public interface SpreadStatisticsService { + + NutMap summary(SpreadStatisticsQuery query); + + List top10(SpreadStatisticsQuery query); + + List categoryStatistics(SpreadStatisticsQuery query); +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadCategoryServiceImpl.java b/src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadCategoryServiceImpl.java new file mode 100644 index 00000000..3cf1d81c --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadCategoryServiceImpl.java @@ -0,0 +1,180 @@ +package com.budwk.app.zhgh.spread.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.zhgh.spread.models.SpreadCategory; +import com.budwk.app.zhgh.spread.models.SpreadContent; +import com.budwk.app.zhgh.spread.service.SpreadCategoryService; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@IocBean(args = {"refer:dao"}) +public class SpreadCategoryServiceImpl extends BaseServiceImpl implements SpreadCategoryService { + + public SpreadCategoryServiceImpl(Dao dao) { + super(dao); + } + + @Override + public List listCategories(boolean visibleOnly) { + Cnd cnd = Cnd.where("delFlag", "=", false); + if (visibleOnly) { + cnd.and("visible", "=", 1); + } + cnd.asc("sortOrder").asc("createdAt"); + return query(cnd); + } + + @Override + public List categoryTree(boolean visibleOnly) { + List categories = listCategories(visibleOnly); + Map nodeMap = new LinkedHashMap<>(); + for (SpreadCategory category : categories) { + nodeMap.put(category.getId(), NutMap.NEW() + .addv("id", category.getId()) + .addv("parentId", category.getParentId()) + .addv("name", category.getName()) + .addv("code", category.getCode()) + .addv("sortOrder", category.getSortOrder()) + .addv("visible", category.getVisible()) + .addv("slider", category.getSlider()) + .addv("children", new ArrayList<>())); + } + List roots = new ArrayList<>(); + for (NutMap node : nodeMap.values()) { + String parentId = node.getString("parentId"); + if ("0".equals(parentId) || !nodeMap.containsKey(parentId)) { + roots.add(node); + } else { + nodeMap.get(parentId).getList("children", NutMap.class).add(node); + } + } + return roots; + } + + @Override + public void saveCategory(SpreadCategory category) { + validateCategory(category, null); + applyDefaults(category); + insert(category); + syncParentSlider(category); + } + + @Override + public void updateCategory(SpreadCategory category) { + if (category == null || StrUtil.isBlank(category.getId())) { + throw new RuntimeException("请选择要编辑的栏目"); + } + SpreadCategory dbCategory = fetch(category.getId()); + if (dbCategory == null || Boolean.TRUE.equals(dbCategory.getDelFlag())) { + throw new RuntimeException("栏目不存在"); + } + validateCategory(category, category.getId()); + if (!dbCategory.getParentId().equals(category.getParentId()) + && count(Cnd.where("parentId", "=", category.getId()).and("delFlag", "=", false)) > 0) { + throw new RuntimeException("该栏目下存在子栏目,不能移动"); + } + applyDefaults(category); + updateIgnoreNull(category); + syncParentSlider(category); + if ("0".equals(category.getParentId()) && Integer.valueOf(0).equals(category.getSlider())) { + SpreadCategory child = new SpreadCategory(); + child.setSlider(0); + dao().update(SpreadCategory.class, org.nutz.dao.Chain.make("slider", 0), + Cnd.where("parentId", "=", category.getId()).and("delFlag", "=", false)); + } + } + + @Override + public void deleteCategory(String id) { + if (StrUtil.isBlank(id)) { + throw new RuntimeException("请选择要删除的栏目"); + } + List children = query(Cnd.where("parentId", "=", id).and("delFlag", "=", false)); + List ids = new ArrayList<>(); + ids.add(id); + children.forEach(item -> ids.add(item.getId())); + if (dao().count(SpreadContent.class, + Cnd.where("categoryId", "in", ids).and("delFlag", "=", false)) > 0) { + throw new RuntimeException("栏目下存在内容,请先删除相关内容"); + } + dao().clear(SpreadCategory.class, Cnd.where("id", "in", ids)); + } + + @Override + public void setVisible(String id, Integer visible) { + if (StrUtil.isBlank(id) || (visible == null || (visible != 0 && visible != 1))) { + throw new RuntimeException("栏目或显示状态不正确"); + } + dao().update(SpreadCategory.class, org.nutz.dao.Chain.make("visible", visible), + Cnd.where("id", "=", id).and("delFlag", "=", false)); + if (visible == 0) { + dao().update(SpreadCategory.class, org.nutz.dao.Chain.make("visible", 0), + Cnd.where("parentId", "=", id).and("delFlag", "=", false)); + } + } + + private void validateCategory(SpreadCategory category, String excludeId) { + if (category == null || StrUtil.isBlank(category.getName())) { + throw new RuntimeException("栏目名称不能为空"); + } + if (StrUtil.isBlank(category.getCode())) { + throw new RuntimeException("栏目编码不能为空"); + } + category.setName(category.getName().trim()); + category.setCode(category.getCode().trim()); + category.setParentId(StrUtil.blankToDefault(category.getParentId(), "0")); + if (category.getId() != null && category.getId().equals(category.getParentId())) { + throw new RuntimeException("上级栏目不能选择自身"); + } + if (!"0".equals(category.getParentId())) { + SpreadCategory parent = fetch(category.getParentId()); + if (parent == null || Boolean.TRUE.equals(parent.getDelFlag())) { + throw new RuntimeException("上级栏目不存在"); + } + if (!"0".equals(parent.getParentId())) { + throw new RuntimeException("风采墙栏目最多支持两级"); + } + } + Cnd nameCnd = Cnd.where("name", "=", category.getName()) + .and("parentId", "=", category.getParentId()) + .and("delFlag", "=", false); + Cnd codeCnd = Cnd.where("code", "=", category.getCode()).and("delFlag", "=", false); + if (StrUtil.isNotBlank(excludeId)) { + nameCnd.and("id", "!=", excludeId); + codeCnd.and("id", "!=", excludeId); + } + if (count(nameCnd) > 0) { + throw new RuntimeException("同级栏目名称已存在"); + } + if (count(codeCnd) > 0) { + throw new RuntimeException("栏目编码已存在"); + } + } + + private void applyDefaults(SpreadCategory category) { + if (category.getSortOrder() == null) { + category.setSortOrder(255); + } + if (category.getVisible() == null) { + category.setVisible(1); + } + if (category.getSlider() == null) { + category.setSlider(0); + } + } + + private void syncParentSlider(SpreadCategory category) { + if (!"0".equals(category.getParentId()) && Integer.valueOf(1).equals(category.getSlider())) { + dao().update(SpreadCategory.class, org.nutz.dao.Chain.make("slider", 1), + Cnd.where("id", "=", category.getParentId()).and("delFlag", "=", false)); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadContentServiceImpl.java b/src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadContentServiceImpl.java new file mode 100644 index 00000000..d58ce44a --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadContentServiceImpl.java @@ -0,0 +1,234 @@ +package com.budwk.app.zhgh.spread.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.zhgh.spread.models.SpreadCategory; +import com.budwk.app.zhgh.spread.models.SpreadContent; +import com.budwk.app.zhgh.spread.param.SpreadContentQuery; +import com.budwk.app.zhgh.spread.service.SpreadContentService; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; + +import java.util.ArrayList; +import java.util.List; + +@IocBean(args = {"refer:dao"}) +public class SpreadContentServiceImpl extends BaseServiceImpl implements SpreadContentService { + + public SpreadContentServiceImpl(Dao dao) { + super(dao); + } + + @Override + public Pagination pageContent(PageForm pageForm, SpreadContentQuery query) { + Cnd cnd = buildCondition(query); + Sql countSql = Sqls.create(""" + SELECT COUNT(1) + FROM spread_content c + $condition + """); + countSql.setCondition(cnd); + countSql.setCallback(Sqls.callback.integer()); + dao().execute(countSql); + + Sql listSql = Sqls.create(""" + SELECT c.* + FROM spread_content c + $condition + ORDER BY c.isTop DESC, c.sortOrder ASC, c.createdAt DESC + """); + listSql.setCondition(cnd); + listSql.setPager(dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize())); + listSql.setCallback(Sqls.callback.maps()); + dao().execute(listSql); + return new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), + countSql.getInt(), listSql.getList(NutMap.class)); + } + + @Override + public Pagination pagePublished(PageForm pageForm, String categoryId) { + SpreadContentQuery query = new SpreadContentQuery(); + query.setCategoryId(categoryId); + query.setIsPublished(1); + Cnd cnd = buildCondition(query); + Sql countSql = Sqls.create("SELECT COUNT(1) FROM spread_content c $condition"); + countSql.setCondition(cnd); + countSql.setCallback(Sqls.callback.integer()); + dao().execute(countSql); + + Sql listSql = Sqls.create(""" + SELECT c.* + FROM spread_content c + $condition + ORDER BY c.isTop DESC, c.publishedAt DESC, c.sortOrder ASC, c.createdAt DESC + """); + listSql.setCondition(cnd); + listSql.setPager(dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize())); + listSql.setCallback(Sqls.callback.maps()); + dao().execute(listSql); + return new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), + countSql.getInt(), listSql.getList(NutMap.class)); + } + + @Override + public SpreadContent getContent(String id) { + if (StrUtil.isBlank(id)) { + throw new RuntimeException("请选择内容"); + } + SpreadContent content = fetch(Cnd.where("id", "=", id).and("delFlag", "=", false)); + if (content == null) { + throw new RuntimeException("内容不存在"); + } + return content; + } + + @Override + public void saveContent(SpreadContent content) { + validateContent(content); + applyDefaults(content); + if (Integer.valueOf(1).equals(content.getIsPublished())) { + content.setPublishedAt(System.currentTimeMillis()); + } + insert(content); + } + + @Override + public void updateContent(SpreadContent content) { + if (content == null || StrUtil.isBlank(content.getId())) { + throw new RuntimeException("请选择要编辑的内容"); + } + SpreadContent dbContent = getContent(content.getId()); + validateContent(content); + applyDefaults(content); + if (Integer.valueOf(1).equals(content.getIsPublished()) && !Integer.valueOf(1).equals(dbContent.getIsPublished())) { + content.setPublishedAt(System.currentTimeMillis()); + } + updateIgnoreNull(content); + } + + @Override + public void deleteContent(String id) { + getContent(id); + dao().clear(SpreadContent.class, Cnd.where("id", "=", id)); + } + + @Override + public void publishContent(String id, Integer status) { + getContent(id); + if (status == null || (status != 0 && status != 1)) { + throw new RuntimeException("发布状态不正确"); + } + org.nutz.dao.Chain chain = org.nutz.dao.Chain.make("isPublished", status); + if (status == 1) { + chain.add("publishedAt", System.currentTimeMillis()); + } + dao().update(SpreadContent.class, chain, Cnd.where("id", "=", id).and("delFlag", "=", false)); + } + + @Override + public void addViewCount(String id) { + getContent(id); + Sql sql = Sqls.create(""" + UPDATE spread_content + SET viewCount = IFNULL(viewCount, 0) + 1, + updatedAt = @updatedAt + WHERE id = @id AND delFlag = 0 + """); + sql.params().set("id", id); + sql.params().set("updatedAt", System.currentTimeMillis()); + dao().execute(sql); + } + + @Override + public List sliderContent() { + Sql sql = Sqls.create(""" + SELECT c.id, c.title, c.summary, c.thumbUrl, c.sliderImages, c.categoryId, c.categoryName + FROM spread_content c + INNER JOIN spread_category child ON child.id = c.categoryId + LEFT JOIN spread_category parent ON parent.id = child.parentId + WHERE c.delFlag = 0 + AND c.isPublished = 1 + AND child.delFlag = 0 + AND child.visible = 1 + AND child.slider = 1 + AND (child.parentId = '0' OR (parent.delFlag = 0 AND parent.visible = 1 AND parent.slider = 1)) + ORDER BY c.isTop DESC, c.publishedAt DESC, c.createdAt DESC + LIMIT 10 + """); + sql.setCallback(Sqls.callback.maps()); + dao().execute(sql); + return sql.getList(NutMap.class); + } + + private Cnd buildCondition(SpreadContentQuery query) { + Cnd cnd = Cnd.where("c.delFlag", "=", false); + if (query == null) { + return cnd; + } + if (StrUtil.isNotBlank(query.getCategoryId())) { + List categoryIds = new ArrayList<>(); + categoryIds.add(query.getCategoryId()); + List children = dao().query(SpreadCategory.class, + Cnd.where("parentId", "=", query.getCategoryId()).and("delFlag", "=", false)); + children.forEach(item -> categoryIds.add(item.getId())); + cnd.and("c.categoryId", "in", categoryIds); + } + cnd.and(Cnd.likeEX("c.title", query.getTitle())); + cnd.andEX("c.isPublished", "=", query.getIsPublished()); + if (StrUtil.isNotBlank(query.getBeginTime())) { + cnd.and("c.createdAt", ">=", DateUtil.parse(query.getBeginTime()).getTime()); + } + if (StrUtil.isNotBlank(query.getEndTime())) { + cnd.and("c.createdAt", "<=", DateUtil.parse(query.getEndTime()).getTime()); + } + return cnd; + } + + private void validateContent(SpreadContent content) { + if (content == null || StrUtil.isBlank(content.getCategoryId())) { + throw new RuntimeException("请选择栏目"); + } + SpreadCategory category = dao().fetch(SpreadCategory.class, + Cnd.where("id", "=", content.getCategoryId()).and("delFlag", "=", false)); + if (category == null) { + throw new RuntimeException("栏目不存在"); + } + if (dao().count(SpreadCategory.class, + Cnd.where("parentId", "=", category.getId()).and("delFlag", "=", false)) > 0) { + throw new RuntimeException("内容只能发布到末级栏目"); + } + if (StrUtil.isBlank(content.getTitle())) { + throw new RuntimeException("标题不能为空"); + } + content.setTitle(content.getTitle().trim()); + content.setCategoryName(category.getName()); + } + + private void applyDefaults(SpreadContent content) { + if (content.getIsTop() == null) { + content.setIsTop(0); + } + if (content.getIsRecommend() == null) { + content.setIsRecommend(0); + } + if (content.getIsHeadline() == null) { + content.setIsHeadline(0); + } + if (content.getViewCount() == null) { + content.setViewCount(0); + } + if (content.getSortOrder() == null) { + content.setSortOrder(255); + } + if (content.getIsPublished() == null) { + content.setIsPublished(0); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadStatisticsServiceImpl.java b/src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadStatisticsServiceImpl.java new file mode 100644 index 00000000..9e8d2c84 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadStatisticsServiceImpl.java @@ -0,0 +1,113 @@ +package com.budwk.app.zhgh.spread.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.zhgh.spread.param.SpreadStatisticsQuery; +import com.budwk.app.zhgh.spread.service.SpreadStatisticsService; +import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; + +import java.util.List; + +@IocBean +public class SpreadStatisticsServiceImpl implements SpreadStatisticsService { + + @Inject + private Dao dao; + + @Override + public NutMap summary(SpreadStatisticsQuery query) { + String condition = buildContentCondition(query, "c"); + Sql sql = Sqls.create(""" + SELECT + (SELECT COUNT(1) FROM spread_category cg WHERE cg.delFlag = 0) AS categoryCount, + COUNT(1) AS contentCount, + IFNULL(SUM(CASE WHEN c.isPublished = 1 THEN 1 ELSE 0 END), 0) AS publishedCount, + IFNULL(SUM(c.viewCount), 0) AS viewCount, + IFNULL(ROUND(AVG(c.viewCount)), 0) AS avgViewCount + FROM spread_content c + WHERE c.delFlag = 0 + $condition + """); + sql.setVar("condition", condition); + setParams(sql, query); + sql.setCallback(Sqls.callback.map()); + dao.execute(sql); + NutMap result = sql.getObject(NutMap.class); + return result == null ? NutMap.NEW() : result; + } + + @Override + public List top10(SpreadStatisticsQuery query) { + Sql sql = Sqls.create(""" + SELECT c.id, c.title, c.categoryName, c.viewCount, c.isPublished, c.publishedAt + FROM spread_content c + WHERE c.delFlag = 0 + $condition + ORDER BY c.viewCount DESC, c.updatedAt DESC + LIMIT 10 + """); + sql.setVar("condition", buildContentCondition(query, "c")); + setParams(sql, query); + sql.setCallback(Sqls.callback.maps()); + dao.execute(sql); + return sql.getList(NutMap.class); + } + + @Override + public List categoryStatistics(SpreadStatisticsQuery query) { + Sql sql = Sqls.create(""" + SELECT c.categoryId, c.categoryName, COUNT(1) AS contentCount, + IFNULL(SUM(c.viewCount), 0) AS viewCount + FROM spread_content c + WHERE c.delFlag = 0 + $condition + GROUP BY c.categoryId, c.categoryName + ORDER BY viewCount DESC, contentCount DESC + """); + sql.setVar("condition", buildContentCondition(query, "c")); + setParams(sql, query); + sql.setCallback(Sqls.callback.maps()); + dao.execute(sql); + return sql.getList(NutMap.class); + } + + private String buildContentCondition(SpreadStatisticsQuery query, String alias) { + if (query == null) { + return ""; + } + StringBuilder condition = new StringBuilder(); + if (StrUtil.isNotBlank(query.getCategoryId())) { + condition.append(" AND ").append(alias) + .append(".categoryId IN (SELECT id FROM spread_category WHERE id = @categoryId OR parentId = @categoryId)"); + } + if (query.getIsPublished() != null) { + condition.append(" AND ").append(alias).append(".isPublished = @isPublished"); + } + if (StrUtil.isNotBlank(query.getBeginTime())) { + condition.append(" AND ").append(alias).append(".createdAt >= @beginAt"); + } + if (StrUtil.isNotBlank(query.getEndTime())) { + condition.append(" AND ").append(alias).append(".createdAt <= @endAt"); + } + return condition.toString(); + } + + private void setParams(Sql sql, SpreadStatisticsQuery query) { + if (query == null) { + return; + } + sql.params().set("categoryId", query.getCategoryId()); + sql.params().set("isPublished", query.getIsPublished()); + if (StrUtil.isNotBlank(query.getBeginTime())) { + sql.params().set("beginAt", DateUtil.parse(query.getBeginTime()).getTime()); + } + if (StrUtil.isNotBlank(query.getEndTime())) { + sql.params().set("endAt", DateUtil.parse(query.getEndTime()).getTime()); + } + } +} diff --git a/src/main/resources/db/menu/init_spread_menu.sql b/src/main/resources/db/menu/init_spread_menu.sql new file mode 100644 index 00000000..26b52596 --- /dev/null +++ b/src/main/resources/db/menu/init_spread_menu.sql @@ -0,0 +1,79 @@ +-- 风采墙电脑端菜单。使用 permission 做幂等判断,脚本可重复执行。 +INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService) +SELECT + '9f5c0200000000000000000000000001', + '', + LPAD(IFNULL(MAX(CAST(path AS UNSIGNED)), 0) + 1, 4, '0'), + '风采墙', + 'Style Wall', + 'menu', + '', + '', + 'ti-medall', + 1, + 0, + 'spread', + NULL, + 992, + 1, + '', + UNIX_TIMESTAMP(NOW()) * 1000, + '', + UNIX_TIMESTAMP(NOW()) * 1000, + 0, + 'PC', + NULL, + NULL, + 'f', + 0, + 0 +FROM sys_menu +WHERE (parentId = '' OR parentId IS NULL) + AND CHAR_LENGTH(path) = 4 +HAVING NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'spread') t); + +INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService) +SELECT '9f5c0200000000000000000000000002', p.id, CONCAT(p.path, '0001'), '风采展示', 'Style Display', 'menu', '/platform/spread/display', 'data-pjax', '', 1, 0, 'spread.display', NULL, 1, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'f', 0, 0 +FROM sys_menu p +WHERE p.permission = 'spread' + AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'spread.display') t); + +INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService) +SELECT '9f5c0200000000000000000000000003', p.id, CONCAT(p.path, '0002'), '内容发布', 'Content Creation', 'menu', '/platform/spread/creation', 'data-pjax', '', 1, 0, 'spread.creation', NULL, 2, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'n', 0, 0 +FROM sys_menu p +WHERE p.permission = 'spread' + AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'spread.creation') t); + +INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService) +SELECT '9f5c0200000000000000000000000004', p.id, CONCAT(p.path, '0003'), '内容管理', 'Content Manage', 'menu', '/platform/spread/manage', 'data-pjax', '', 1, 0, 'spread.manage', NULL, 3, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'n', 0, 0 +FROM sys_menu p +WHERE p.permission = 'spread' + AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'spread.manage') t); + +INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService) +SELECT '9f5c0200000000000000000000000005', p.id, CONCAT(p.path, '0004'), '栏目管理', 'Category Manage', 'menu', '/platform/spread/category', 'data-pjax', '', 1, 0, 'spread.category', NULL, 4, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0 +FROM sys_menu p +WHERE p.permission = 'spread' + AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'spread.category') t); + +INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService) +SELECT '9f5c0200000000000000000000000006', p.id, CONCAT(p.path, '0005'), '统计分析', 'Statistics', 'menu', '/platform/spread/statistics', 'data-pjax', '', 1, 0, 'spread.statistics', NULL, 5, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 't', 0, 0 +FROM sys_menu p +WHERE p.permission = 'spread' + AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'spread.statistics') t); + +-- 系统管理员默认拥有风采墙全部权限,其他角色可在角色管理中按需分配。 +INSERT INTO sys_role_menu (roleId, menuId) +SELECT r.id, m.id +FROM sys_role r +JOIN sys_menu m ON m.permission IN ( + 'spread', + 'spread.display', + 'spread.creation', + 'spread.manage', + 'spread.category', + 'spread.statistics' +) +LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id +WHERE r.code = 'SYSADMIN' + AND rm.roleId IS NULL; diff --git a/src/main/resources/db/spread_create.sql b/src/main/resources/db/spread_create.sql new file mode 100644 index 00000000..44912c29 --- /dev/null +++ b/src/main/resources/db/spread_create.sql @@ -0,0 +1,76 @@ +-- 风采墙栏目表,适用于 MySQL 8。 +CREATE TABLE IF NOT EXISTS `spread_category` ( + `id` varchar(32) NOT NULL COMMENT 'ID', + `parentId` varchar(32) NOT NULL DEFAULT '0' COMMENT '父栏目ID,0表示顶级栏目', + `name` varchar(100) NOT NULL COMMENT '栏目名称', + `code` varchar(50) NOT NULL COMMENT '栏目编码', + `sortOrder` int NOT NULL DEFAULT 255 COMMENT '排序,数值越小越靠前', + `visible` tinyint NOT NULL DEFAULT 1 COMMENT '是否可见:0否,1是', + `slider` tinyint NOT NULL DEFAULT 0 COMMENT '是否参与轮播:0否,1是', + `createdBy` varchar(32) DEFAULT NULL COMMENT '创建人', + `createdAt` bigint DEFAULT NULL COMMENT '创建时间', + `updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人', + `updatedAt` bigint DEFAULT NULL COMMENT '修改时间', + `delFlag` tinyint NOT NULL DEFAULT 0 COMMENT '删除标记', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_spread_category_code` (`code`), + KEY `idx_spread_category_parent` (`parentId`), + KEY `idx_spread_category_visible_sort` (`visible`, `sortOrder`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='风采墙栏目'; + +-- 风采墙内容表。 +CREATE TABLE IF NOT EXISTS `spread_content` ( + `id` varchar(32) NOT NULL COMMENT 'ID', + `categoryId` varchar(32) NOT NULL COMMENT '栏目ID', + `categoryName` varchar(100) NOT NULL COMMENT '栏目名称', + `title` varchar(300) NOT NULL COMMENT '标题', + `subTitle` varchar(255) DEFAULT NULL COMMENT '副标题', + `summary` varchar(1000) DEFAULT NULL COMMENT '摘要', + `contentBody` longtext COMMENT 'HTML正文', + `thumbUrl` varchar(1000) DEFAULT NULL COMMENT '封面图', + `sliderImages` longtext COMMENT '轮播图片JSON', + `isTop` tinyint NOT NULL DEFAULT 0 COMMENT '是否置顶', + `isRecommend` tinyint NOT NULL DEFAULT 0 COMMENT '是否推荐', + `isHeadline` tinyint NOT NULL DEFAULT 0 COMMENT '是否头条', + `viewCount` int NOT NULL DEFAULT 0 COMMENT '浏览量', + `sortOrder` int NOT NULL DEFAULT 255 COMMENT '排序', + `isPublished` tinyint NOT NULL DEFAULT 0 COMMENT '是否发布', + `publishedAt` bigint DEFAULT NULL COMMENT '发布时间', + `createdBy` varchar(32) DEFAULT NULL COMMENT '创建人', + `createdAt` bigint DEFAULT NULL COMMENT '创建时间', + `updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人', + `updatedAt` bigint DEFAULT NULL COMMENT '修改时间', + `delFlag` tinyint NOT NULL DEFAULT 0 COMMENT '删除标记', + PRIMARY KEY (`id`), + KEY `idx_spread_content_category` (`categoryId`), + KEY `idx_spread_content_publish` (`isPublished`, `publishedAt`), + KEY `idx_spread_content_top_sort` (`isTop`, `sortOrder`), + KEY `idx_spread_content_created` (`createdAt`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='风采墙内容'; + +-- 初始化栏目,仅在对应编码不存在时插入,脚本可重复执行。 +INSERT INTO `spread_category` +(`id`, `parentId`, `name`, `code`, `sortOrder`, `visible`, `slider`, `createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`) +SELECT '9f5c0100000000000000000000000001', '0', '风采展示', 'style_wall', 1, 1, 1, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0 +WHERE NOT EXISTS (SELECT 1 FROM `spread_category` WHERE `code` = 'style_wall'); + +INSERT INTO `spread_category` +(`id`, `parentId`, `name`, `code`, `sortOrder`, `visible`, `slider`, `createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`) +SELECT '9f5c0100000000000000000000000002', p.id, '劳模风采', 'model_worker_style', 1, 1, 1, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0 +FROM `spread_category` p +WHERE p.code = 'style_wall' + AND NOT EXISTS (SELECT 1 FROM `spread_category` WHERE `code` = 'model_worker_style'); + +INSERT INTO `spread_category` +(`id`, `parentId`, `name`, `code`, `sortOrder`, `visible`, `slider`, `createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`) +SELECT '9f5c0100000000000000000000000003', p.id, '巾帼风采', 'women_style', 2, 1, 1, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0 +FROM `spread_category` p +WHERE p.code = 'style_wall' + AND NOT EXISTS (SELECT 1 FROM `spread_category` WHERE `code` = 'women_style'); + +INSERT INTO `spread_category` +(`id`, `parentId`, `name`, `code`, `sortOrder`, `visible`, `slider`, `createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`) +SELECT '9f5c0100000000000000000000000004', p.id, '教工风采', 'faculty_style', 3, 1, 1, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0 +FROM `spread_category` p +WHERE p.code = 'style_wall' + AND NOT EXISTS (SELECT 1 FROM `spread_category` WHERE `code` = 'faculty_style'); diff --git a/src/main/resources/views/platform/zhgh/spread/category/index.html b/src/main/resources/views/platform/zhgh/spread/category/index.html new file mode 100644 index 00000000..a1922d9a --- /dev/null +++ b/src/main/resources/views/platform/zhgh/spread/category/index.html @@ -0,0 +1,176 @@ + +
+ + + + 新增栏目 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 显示 + 隐藏 + + + + + 开启 + 关闭 + +
子栏目开启轮播后,其上级栏目会自动开启。
+
+
+ + 取消 + 确定 + +
+
+ + + diff --git a/src/main/resources/views/platform/zhgh/spread/creation/index.html b/src/main/resources/views/platform/zhgh/spread/creation/index.html new file mode 100644 index 00000000..5e711604 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/spread/creation/index.html @@ -0,0 +1,198 @@ + + +
+ +
+ {{formData.id ? '编辑风采内容' : '发布风采内容'}} +
+ + + + + + + + + + + + + + + + + + + + + + + + +
建议使用横版图片,单张不超过 10MB。
+
+ + +
仅栏目开启“参与轮播”后生效,最多上传 10 张。
+
+ + + + + + + + + + + + + + + + + + + + + + + 保存草稿 + 立即发布 + + + +
+
+
+ + + diff --git a/src/main/resources/views/platform/zhgh/spread/display/index.html b/src/main/resources/views/platform/zhgh/spread/display/index.html new file mode 100644 index 00000000..43dc8813 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/spread/display/index.html @@ -0,0 +1,215 @@ + + + +
+
+

风采墙

+
记录奋斗身影 · 展示教工风采 · 凝聚榜样力量
+
+ + +
+ +
{{item.title}}
+
+
+
+
榜样引领 · 风采绽放
+
+
+ +
+ + +
+ + +
+
+
+ + + diff --git a/src/main/resources/views/platform/zhgh/spread/manage/index.html b/src/main/resources/views/platform/zhgh/spread/manage/index.html new file mode 100644 index 00000000..ee07dfc1 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/spread/manage/index.html @@ -0,0 +1,180 @@ + +
+ + + + + + + + + + + + + + + + + + +
+ 搜索 + 重置 +
+
+
+ + + + 发布内容 + + + + + + + + + + + + + + + + + + + + + +
+
+ + + diff --git a/src/main/resources/views/platform/zhgh/spread/statistics/index.html b/src/main/resources/views/platform/zhgh/spread/statistics/index.html new file mode 100644 index 00000000..ebee0c79 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/spread/statistics/index.html @@ -0,0 +1,178 @@ + + + +
+ + + + + + + + + + + + + + + +
+ 查询 + 重置 +
+
+
+ +
+
栏目数量
{{summary.categoryCount || 0}}
+
内容总数
{{summary.contentCount || 0}}
+
已发布
{{summary.publishedCount || 0}}
+
累计浏览
{{summary.viewCount || 0}}
+
平均浏览
{{summary.avgViewCount || 0}}
+
+ +
+ +
浏览量 TOP 10
+
+
+ +
栏目浏览量占比
+
+
+
+ + +
栏目数据明细
+ + + + + + +
+
+
+ + + From b3406f8b005ee551c0aca67c4f2713e01de2e0dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=9C=E5=AD=A6=E6=88=90?= <1274337408@qq.com> Date: Thu, 30 Jul 2026 16:59:50 +0800 Subject: [PATCH 4/7] =?UTF-8?q?web=E7=AB=AF-=E6=96=B0=E5=A2=9E=E9=A3=8E?= =?UTF-8?q?=E9=87=87=E5=A2=99=E6=A8=A1=E5=9D=97(=E5=B7=A5=E4=BC=9A?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E4=BC=A0=E6=92=AD=E4=B8=8E=E6=96=87=E5=8C=96?= =?UTF-8?q?=E5=B1=95=E7=A4=BA):=20=E4=BF=AE=E5=A4=8D=E6=A0=8F=E7=9B=AE?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E4=B8=8B=E7=BA=A7=E6=A0=8F=E7=9B=AE=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=89=93=E5=BC=80=E7=AD=89=E9=97=AE=E9=A2=98;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/SpreadCategoryServiceImpl.java | 25 ++++++------ src/main/resources/db/spread_create.sql | 2 +- .../platform/zhgh/spread/category/index.html | 38 ++++++++++++++----- 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadCategoryServiceImpl.java b/src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadCategoryServiceImpl.java index 3cf1d81c..b4670347 100644 --- a/src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadCategoryServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/spread/service/impl/SpreadCategoryServiceImpl.java @@ -36,7 +36,9 @@ public class SpreadCategoryServiceImpl extends BaseServiceImpl i public List categoryTree(boolean visibleOnly) { List categories = listCategories(visibleOnly); Map nodeMap = new LinkedHashMap<>(); + Map> childrenMap = new LinkedHashMap<>(); for (SpreadCategory category : categories) { + List children = new ArrayList<>(); nodeMap.put(category.getId(), NutMap.NEW() .addv("id", category.getId()) .addv("parentId", category.getParentId()) @@ -45,7 +47,8 @@ public class SpreadCategoryServiceImpl extends BaseServiceImpl i .addv("sortOrder", category.getSortOrder()) .addv("visible", category.getVisible()) .addv("slider", category.getSlider()) - .addv("children", new ArrayList<>())); + .addv("children", children)); + childrenMap.put(category.getId(), children); } List roots = new ArrayList<>(); for (NutMap node : nodeMap.values()) { @@ -53,9 +56,16 @@ public class SpreadCategoryServiceImpl extends BaseServiceImpl i if ("0".equals(parentId) || !nodeMap.containsKey(parentId)) { roots.add(node); } else { - nodeMap.get(parentId).getList("children", NutMap.class).add(node); + // 直接向节点持有的原始集合追加,避免 NutMap 类型转换后子节点没有写回。 + childrenMap.get(parentId).add(node); } } + // 叶子栏目不返回空 children,避免级联选择器误判为仍有下一级。 + nodeMap.forEach((id, node) -> { + if (childrenMap.get(id).isEmpty()) { + node.remove("children"); + } + }); return roots; } @@ -125,11 +135,9 @@ public class SpreadCategoryServiceImpl extends BaseServiceImpl i if (category == null || StrUtil.isBlank(category.getName())) { throw new RuntimeException("栏目名称不能为空"); } - if (StrUtil.isBlank(category.getCode())) { - throw new RuntimeException("栏目编码不能为空"); - } category.setName(category.getName().trim()); - category.setCode(category.getCode().trim()); + // 参考模块不要求用户维护栏目编码,空值统一保存为 NULL。 + category.setCode(StrUtil.isBlank(category.getCode()) ? null : category.getCode().trim()); category.setParentId(StrUtil.blankToDefault(category.getParentId(), "0")); if (category.getId() != null && category.getId().equals(category.getParentId())) { throw new RuntimeException("上级栏目不能选择自身"); @@ -146,17 +154,12 @@ public class SpreadCategoryServiceImpl extends BaseServiceImpl i Cnd nameCnd = Cnd.where("name", "=", category.getName()) .and("parentId", "=", category.getParentId()) .and("delFlag", "=", false); - Cnd codeCnd = Cnd.where("code", "=", category.getCode()).and("delFlag", "=", false); if (StrUtil.isNotBlank(excludeId)) { nameCnd.and("id", "!=", excludeId); - codeCnd.and("id", "!=", excludeId); } if (count(nameCnd) > 0) { throw new RuntimeException("同级栏目名称已存在"); } - if (count(codeCnd) > 0) { - throw new RuntimeException("栏目编码已存在"); - } } private void applyDefaults(SpreadCategory category) { diff --git a/src/main/resources/db/spread_create.sql b/src/main/resources/db/spread_create.sql index 44912c29..4bfda893 100644 --- a/src/main/resources/db/spread_create.sql +++ b/src/main/resources/db/spread_create.sql @@ -3,7 +3,7 @@ CREATE TABLE IF NOT EXISTS `spread_category` ( `id` varchar(32) NOT NULL COMMENT 'ID', `parentId` varchar(32) NOT NULL DEFAULT '0' COMMENT '父栏目ID,0表示顶级栏目', `name` varchar(100) NOT NULL COMMENT '栏目名称', - `code` varchar(50) NOT NULL COMMENT '栏目编码', + `code` varchar(50) DEFAULT NULL COMMENT '栏目编码(兼容预置数据,页面无需维护)', `sortOrder` int NOT NULL DEFAULT 255 COMMENT '排序,数值越小越靠前', `visible` tinyint NOT NULL DEFAULT 1 COMMENT '是否可见:0否,1是', `slider` tinyint NOT NULL DEFAULT 0 COMMENT '是否参与轮播:0否,1是', diff --git a/src/main/resources/views/platform/zhgh/spread/category/index.html b/src/main/resources/views/platform/zhgh/spread/category/index.html index a1922d9a..8582a479 100644 --- a/src/main/resources/views/platform/zhgh/spread/category/index.html +++ b/src/main/resources/views/platform/zhgh/spread/category/index.html @@ -6,11 +6,12 @@ layout("/layouts/platform.html"){ 新增栏目 + 展开全部 + 折叠全部 - -