web端-新增风采墙模块(工会信息传播与文化展示);
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<SpreadCategory> {
|
||||
|
||||
List<SpreadCategory> listCategories(boolean visibleOnly);
|
||||
|
||||
List<NutMap> categoryTree(boolean visibleOnly);
|
||||
|
||||
void saveCategory(SpreadCategory category);
|
||||
|
||||
void updateCategory(SpreadCategory category);
|
||||
|
||||
void deleteCategory(String id);
|
||||
|
||||
void setVisible(String id, Integer visible);
|
||||
}
|
||||
@@ -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<SpreadContent> {
|
||||
|
||||
Pagination<NutMap> pageContent(PageForm pageForm, SpreadContentQuery query);
|
||||
|
||||
Pagination<NutMap> 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<NutMap> sliderContent();
|
||||
}
|
||||
@@ -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<NutMap> top10(SpreadStatisticsQuery query);
|
||||
|
||||
List<NutMap> categoryStatistics(SpreadStatisticsQuery query);
|
||||
}
|
||||
@@ -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<SpreadCategory> implements SpreadCategoryService {
|
||||
|
||||
public SpreadCategoryServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SpreadCategory> 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<NutMap> categoryTree(boolean visibleOnly) {
|
||||
List<SpreadCategory> categories = listCategories(visibleOnly);
|
||||
Map<String, NutMap> 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<NutMap> 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<SpreadCategory> children = query(Cnd.where("parentId", "=", id).and("delFlag", "=", false));
|
||||
List<String> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<SpreadContent> implements SpreadContentService {
|
||||
|
||||
public SpreadContentServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pagination<NutMap> 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<NutMap> 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<NutMap> 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<String> categoryIds = new ArrayList<>();
|
||||
categoryIds.add(query.getCategoryId());
|
||||
List<SpreadCategory> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+113
@@ -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<NutMap> 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<NutMap> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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');
|
||||
@@ -0,0 +1,176 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<table-tool :app="this" label="风采墙栏目">
|
||||
<el-button size="medium" type="primary" icon="el-icon-plus" @click="openAdd">新增栏目</el-button>
|
||||
</table-tool>
|
||||
<el-table v-loading="loading" :data="tableData" border row-key="id"
|
||||
:tree-props="{children: 'children'}" default-expand-all>
|
||||
<el-table-column prop="name" label="栏目名称" min-width="220"></el-table-column>
|
||||
<el-table-column prop="code" label="栏目编码" min-width="180"></el-table-column>
|
||||
<el-table-column prop="sortOrder" label="排序" width="90" align="center"></el-table-column>
|
||||
<el-table-column label="显示" width="100" align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-switch :value="row.visible === 1" @change="changeVisible(row, $event)"></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="轮播" width="100" align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag :type="row.slider === 1 ? 'success' : 'info'" size="mini">
|
||||
{{row.slider === 1 ? '开启' : '关闭'}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="190" align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-button type="primary" size="mini" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" size="mini" @click="remove(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="560px" :close-on-click-modal="false">
|
||||
<el-form ref="categoryForm" :model="formData" :rules="rules" label-width="100px">
|
||||
<el-form-item label="上级栏目" prop="parentId">
|
||||
<el-select v-model="formData.parentId" style="width: 100%" placeholder="请选择上级栏目">
|
||||
<el-option label="顶级栏目" value="0"></el-option>
|
||||
<el-option v-for="item in rootOptions" :key="item.id" :label="item.name" :value="item.id"
|
||||
:disabled="item.id === formData.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="栏目名称" prop="name">
|
||||
<el-input v-model.trim="formData.name" maxlength="100" show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="栏目编码" prop="code">
|
||||
<el-input v-model.trim="formData.code" maxlength="50" placeholder="例如:teacher_style"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="formData.sortOrder" :min="0" :max="9999" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="显示状态">
|
||||
<el-radio-group v-model="formData.visible">
|
||||
<el-radio :label="1">显示</el-radio>
|
||||
<el-radio :label="0">隐藏</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="参与轮播">
|
||||
<el-radio-group v-model="formData.slider">
|
||||
<el-radio :label="1">开启</el-radio>
|
||||
<el-radio :label="0">关闭</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="el-form-item__tip">子栏目开启轮播后,其上级栏目会自动开启。</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submit">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
submitting: false,
|
||||
tableData: [],
|
||||
rootOptions: [],
|
||||
dialogVisible: false,
|
||||
dialogTitle: "",
|
||||
formData: {},
|
||||
rules: {
|
||||
parentId: [{required: true, message: "请选择上级栏目", trigger: "change"}],
|
||||
name: [{required: true, message: "请输入栏目名称", trigger: "blur"}],
|
||||
code: [
|
||||
{required: true, message: "请输入栏目编码", trigger: "blur"},
|
||||
{pattern: /^[A-Za-z][A-Za-z0-9_-]*$/, message: "编码须以字母开头,仅支持字母、数字、下划线和短横线", trigger: "blur"}
|
||||
],
|
||||
sortOrder: [{required: true, message: "请输入排序", trigger: "change"}]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 加载栏目树,同时提取顶级栏目作为上级栏目选项。
|
||||
loadData() {
|
||||
this.loading = true
|
||||
this.$axios.post(loc() + "/treeData").then(resp => {
|
||||
if (resp.code === 0) {
|
||||
this.tableData = resp.data || []
|
||||
this.rootOptions = (resp.data || []).slice()
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
}).finally(() => this.loading = false)
|
||||
},
|
||||
// 构造新增栏目默认值。
|
||||
openAdd() {
|
||||
this.dialogTitle = "新增栏目"
|
||||
this.formData = {parentId: "0", name: "", code: "", sortOrder: 255, visible: 1, slider: 0}
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => this.$refs.categoryForm && this.$refs.categoryForm.clearValidate())
|
||||
},
|
||||
// 将当前行复制到表单,避免直接修改表格数据。
|
||||
openEdit(row) {
|
||||
this.dialogTitle = "编辑栏目"
|
||||
this.formData = Object.assign({}, row)
|
||||
delete this.formData.children
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => this.$refs.categoryForm && this.$refs.categoryForm.clearValidate())
|
||||
},
|
||||
// 校验后调用新增或编辑接口。
|
||||
submit() {
|
||||
this.$refs.categoryForm.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const api = this.formData.id ? "/doEdit" : "/doAdd"
|
||||
this.$axios.post(loc() + api, this.formData).then(resp => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg || "保存成功")
|
||||
this.dialogVisible = false
|
||||
this.loadData()
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
}).finally(() => this.submitting = false)
|
||||
})
|
||||
},
|
||||
// 修改栏目显示状态,失败时重新加载以恢复开关。
|
||||
changeVisible(row, checked) {
|
||||
this.$axios.post(loc() + "/doVisible", {id: row.id, visible: checked ? 1 : 0}).then(resp => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success("设置成功")
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
this.loadData()
|
||||
})
|
||||
},
|
||||
// 删除前进行二次确认;存在内容时后端会阻止删除。
|
||||
remove(row) {
|
||||
this.$confirm("确定删除栏目“" + row.name + "”吗?", "提示", {type: "warning"}).then(() => {
|
||||
this.$axios.post(loc() + "/doDelete", {id: row.id}).then(resp => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.loadData()
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,198 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
.spread-editor-page .form-card { max-width: 1180px; margin: 0 auto; }
|
||||
.spread-editor-page .form-tip { color: #909399; font-size: 12px; line-height: 20px; }
|
||||
.spread-editor-page .footer-actions { padding: 18px 0 6px; text-align: center; }
|
||||
</style>
|
||||
<div id="app" class="spread-editor-page" v-cloak>
|
||||
<el-card class="form-card" shadow="never" v-loading="loading">
|
||||
<div slot="header">
|
||||
<span style="font-size: 17px;font-weight: 600">{{formData.id ? '编辑风采内容' : '发布风采内容'}}</span>
|
||||
</div>
|
||||
<el-form ref="contentForm" :model="formData" :rules="rules" label-width="105px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所属栏目" prop="categoryId">
|
||||
<el-cascader v-model="formData.categoryId" :options="categoryOptions"
|
||||
:props="{value:'id', label:'name', children:'children', emitPath:false, checkStrictly:false}"
|
||||
filterable clearable style="width: 100%" placeholder="请选择末级栏目"></el-cascader>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="formData.sortOrder" :min="0" :max="9999" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model.trim="formData.title" maxlength="300" show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="副标题">
|
||||
<el-input v-model.trim="formData.subTitle" maxlength="255" show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="摘要" prop="summary">
|
||||
<el-input v-model="formData.summary" type="textarea" :rows="3" maxlength="1000" show-word-limit
|
||||
placeholder="用于风采墙列表展示"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="封面图" prop="thumbUrl">
|
||||
<file-upload :upload_number="1" :upload_size="10 * 1024 * 1024"
|
||||
:value.sync="formData.thumbUrl" accept=".jpg,.jpeg,.png,.webp"
|
||||
upload_mode="image" upload_result_category="interval"></file-upload>
|
||||
<div class="form-tip">建议使用横版图片,单张不超过 10MB。</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="轮播图片">
|
||||
<file-upload :upload_number="10" :upload_size="10 * 1024 * 1024"
|
||||
:value.sync="sliderFiles" accept=".jpg,.jpeg,.png,.webp"
|
||||
upload_mode="image" upload_result_category="array"></file-upload>
|
||||
<div class="form-tip">仅栏目开启“参与轮播”后生效,最多上传 10 张。</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="正文" prop="contentBody">
|
||||
<text-editor v-model="formData.contentBody" :height="360" placeholder="请输入风采内容"></text-editor>
|
||||
</el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="置顶">
|
||||
<el-switch v-model="formData.isTop" :active-value="1" :inactive-value="0"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="推荐">
|
||||
<el-switch v-model="formData.isRecommend" :active-value="1" :inactive-value="0"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="头条">
|
||||
<el-switch v-model="formData.isHeadline" :active-value="1" :inactive-value="0"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="发布状态">
|
||||
<el-radio-group v-model="formData.isPublished">
|
||||
<el-radio :label="0">保存草稿</el-radio>
|
||||
<el-radio :label="1">立即发布</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div class="footer-actions">
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submit">保存</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
submitting: false,
|
||||
categoryOptions: [],
|
||||
sliderFiles: [],
|
||||
formData: {},
|
||||
rules: {
|
||||
categoryId: [{required: true, message: "请选择所属栏目", trigger: "change"}],
|
||||
title: [{required: true, message: "请输入标题", trigger: "blur"}],
|
||||
summary: [{required: true, message: "请输入摘要", trigger: "blur"}],
|
||||
contentBody: [{required: true, message: "请输入正文", trigger: "change"}]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 生成新增内容时使用的默认表单。
|
||||
defaultForm() {
|
||||
return {
|
||||
categoryId: "", title: "", subTitle: "", summary: "", contentBody: "",
|
||||
thumbUrl: "", sliderImages: "", isTop: 0, isRecommend: 0,
|
||||
isHeadline: 0, viewCount: 0, sortOrder: 255, isPublished: 0
|
||||
}
|
||||
},
|
||||
// 读取地址栏中的编辑内容 ID。
|
||||
getEditId() {
|
||||
return new URLSearchParams(window.location.search).get("id") || ""
|
||||
},
|
||||
// 加载栏目树,内容只能选择末级栏目。
|
||||
loadCategories() {
|
||||
return this.$axios.post(loc() + "/categoryTree").then(resp => {
|
||||
if (resp.code === 0) {
|
||||
this.categoryOptions = resp.data || []
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 编辑状态下加载内容详情并还原轮播图片数组。
|
||||
loadDetail(id) {
|
||||
if (!id) return Promise.resolve()
|
||||
this.loading = true
|
||||
return this.$axios.post(loc() + "/detailData", {id}).then(resp => {
|
||||
if (resp.code === 0) {
|
||||
this.formData = Object.assign(this.defaultForm(), resp.data || {})
|
||||
this.sliderFiles = this.parseSliderFiles(this.formData.sliderImages)
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
}).finally(() => this.loading = false)
|
||||
},
|
||||
// 兼容数据库中的 JSON 数组和历史逗号分隔地址。
|
||||
parseSliderFiles(value) {
|
||||
if (!value) return []
|
||||
if (Array.isArray(value)) return value
|
||||
try {
|
||||
const urls = JSON.parse(value)
|
||||
return Array.isArray(urls) ? urls : []
|
||||
} catch (e) {
|
||||
return String(value).split(",").filter(Boolean)
|
||||
}
|
||||
},
|
||||
// 将上传组件返回的图片数组转换为可持久化 JSON。
|
||||
buildSliderImages() {
|
||||
const urls = (this.sliderFiles || []).map(item => {
|
||||
if (typeof item === "string") return item
|
||||
return item.url || item.data || (item.response && item.response.data) || ""
|
||||
}).filter(Boolean)
|
||||
return JSON.stringify(urls)
|
||||
},
|
||||
// 重置新增表单;编辑状态下恢复数据库内容。
|
||||
resetForm() {
|
||||
const id = this.getEditId()
|
||||
if (id) {
|
||||
this.loadDetail(id)
|
||||
} else {
|
||||
this.formData = this.defaultForm()
|
||||
this.sliderFiles = []
|
||||
}
|
||||
this.$nextTick(() => this.$refs.contentForm && this.$refs.contentForm.clearValidate())
|
||||
},
|
||||
// 校验并提交新增或编辑数据。
|
||||
submit() {
|
||||
this.$refs.contentForm.validate(valid => {
|
||||
if (!valid) return
|
||||
const data = Object.assign({}, this.formData, {sliderImages: this.buildSliderImages()})
|
||||
const api = data.id ? "/doEdit" : "/doAdd"
|
||||
this.submitting = true
|
||||
this.$axios.post(loc() + api, data).then(resp => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg || "保存成功")
|
||||
if (!data.id) {
|
||||
this.resetForm()
|
||||
}
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
}).finally(() => this.submitting = false)
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.formData = this.defaultForm()
|
||||
const id = this.getEditId()
|
||||
Promise.all([this.loadCategories(), this.loadDetail(id)])
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,215 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
#sub-app-container-main-content { padding: 0 !important; background: #f6f2ea; }
|
||||
.spread-wall { min-height: 100%; color: #3c2f2a; background: linear-gradient(180deg, #8f171d 0, #bd352b 330px, #f6f2ea 330px); }
|
||||
.spread-hero { max-width: 1280px; margin: 0 auto; padding: 34px 32px 26px; color: #fff; }
|
||||
.spread-title { margin: 0; font-family: "Microsoft YaHei", serif; font-size: 34px; letter-spacing: 5px; }
|
||||
.spread-subtitle { margin-top: 9px; color: rgba(255,255,255,.78); font-size: 14px; letter-spacing: 2px; }
|
||||
.hero-slider { height: 205px; margin-top: 24px; overflow: hidden; border-radius: 12px; box-shadow: 0 14px 34px rgba(64, 10, 12, .28); background: #7d1b1f; }
|
||||
.hero-slider .el-carousel, .hero-slider .el-carousel__container { height: 205px; }
|
||||
.slider-item { height: 100%; position: relative; cursor: pointer; background: linear-gradient(135deg, #781116, #c44834); }
|
||||
.slider-item img { width: 100%; height: 100%; object-fit: cover; opacity: .78; }
|
||||
.slider-caption { position: absolute; left: 0; right: 0; bottom: 0; padding: 34px 28px 18px; color: #fff; font-size: 21px; font-weight: 600; background: linear-gradient(transparent, rgba(28,4,5,.78)); }
|
||||
.slider-empty { height: 100%; display: flex; align-items: center; justify-content: center; font-size: 25px; letter-spacing: 6px; }
|
||||
.spread-main { max-width: 1280px; margin: 0 auto; padding: 0 32px 38px; display: grid; grid-template-columns: 245px minmax(0, 1fr); gap: 22px; }
|
||||
.category-panel, .content-panel { background: #fff; border-radius: 12px; box-shadow: 0 8px 28px rgba(90,53,34,.09); }
|
||||
.category-panel { align-self: start; overflow: hidden; }
|
||||
.category-header { padding: 17px 20px; color: #fff; font-size: 17px; font-weight: 600; background: #941d22; }
|
||||
.category-parent { padding: 15px 18px 9px; color: #7e2224; font-weight: 700; border-top: 1px solid #f0e7df; }
|
||||
.category-child { padding: 11px 18px 11px 29px; cursor: pointer; color: #655650; border-left: 3px solid transparent; transition: .2s; }
|
||||
.category-child:hover, .category-child.active { color: #a32227; background: #fff3ed; border-left-color: #a32227; }
|
||||
.content-panel { min-height: 570px; padding: 24px 28px; }
|
||||
.section-heading { margin-bottom: 12px; padding-bottom: 14px; display: flex; align-items: flex-end; justify-content: space-between; border-bottom: 1px solid #eee2d9; }
|
||||
.section-heading h2 { margin: 0; color: #7c1b20; font-size: 23px; }
|
||||
.section-heading span { color: #a0938c; font-size: 13px; }
|
||||
.content-card { padding: 17px 4px; display: grid; grid-template-columns: 72px minmax(0,1fr) 80px; gap: 18px; align-items: center; cursor: pointer; border-bottom: 1px solid #f0e9e4; transition: .2s; }
|
||||
.content-card:hover { padding-left: 10px; background: #fffaf7; }
|
||||
.content-date { color: #8f7770; text-align: center; border-right: 1px solid #eadfd7; }
|
||||
.content-date strong { display: block; color: #8f1d22; font-size: 25px; }
|
||||
.content-title { margin-bottom: 7px; color: #392c28; font-size: 17px; font-weight: 700; }
|
||||
.content-summary { overflow: hidden; color: #7c6d67; font-size: 13px; line-height: 21px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.content-view { color: #aa9d96; font-size: 12px; text-align: right; }
|
||||
.wall-pagination { margin-top: 25px; text-align: center; }
|
||||
.detail-title { margin: 8px 0 12px; color: #672025; font-size: 28px; text-align: center; }
|
||||
.detail-meta { padding-bottom: 18px; color: #9c8d86; text-align: center; border-bottom: 1px solid #eee2d9; }
|
||||
.detail-cover { max-width: 100%; max-height: 430px; margin: 24px auto; display: block; object-fit: contain; border-radius: 8px; }
|
||||
.detail-body { color: #493b36; font-size: 16px; line-height: 1.9; word-break: break-word; }
|
||||
.back-link { margin-bottom: 12px; }
|
||||
@media (max-width: 800px) { .spread-main { grid-template-columns: 1fr; padding: 0 12px 24px; } .spread-hero { padding: 24px 12px; } .category-panel { position: static; } }
|
||||
</style>
|
||||
|
||||
<div id="app" class="spread-wall" v-cloak>
|
||||
<header class="spread-hero">
|
||||
<h1 class="spread-title">风采墙</h1>
|
||||
<div class="spread-subtitle">记录奋斗身影 · 展示教工风采 · 凝聚榜样力量</div>
|
||||
<div class="hero-slider">
|
||||
<el-carousel v-if="sliderItems.length" :interval="5000" arrow="hover">
|
||||
<el-carousel-item v-for="item in sliderItems" :key="item.id">
|
||||
<div class="slider-item" @click="openDetail(item)">
|
||||
<img v-if="getSliderImage(item)" :src="getSliderImage(item)" :alt="item.title">
|
||||
<div class="slider-caption">{{item.title}}</div>
|
||||
</div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<div v-else class="slider-empty">榜样引领 · 风采绽放</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="spread-main">
|
||||
<aside class="category-panel">
|
||||
<div class="category-header"><i class="el-icon-collection-tag"></i> 风采栏目</div>
|
||||
<template v-for="parent in categories">
|
||||
<div :key="parent.id + '-p'" class="category-parent">{{parent.name}}</div>
|
||||
<div v-if="!parent.children || !parent.children.length" :key="parent.id"
|
||||
class="category-child" :class="{active: currentCategory.id === parent.id}"
|
||||
@click="selectCategory(parent)">{{parent.name}}</div>
|
||||
<div v-for="child in parent.children" :key="child.id"
|
||||
class="category-child" :class="{active: currentCategory.id === child.id}"
|
||||
@click="selectCategory(child)">{{child.name}}</div>
|
||||
</template>
|
||||
</aside>
|
||||
|
||||
<main class="content-panel" v-loading="loading">
|
||||
<template v-if="!detail">
|
||||
<div class="section-heading">
|
||||
<h2>{{currentCategory.name || '风采展示'}}</h2>
|
||||
<span>展示教职工的先进事迹与精彩瞬间</span>
|
||||
</div>
|
||||
<article v-for="item in pageData.list" :key="item.id" class="content-card" @click="openDetail(item)">
|
||||
<div class="content-date">
|
||||
<strong>{{datePart(item.publishedAt, 'DD')}}</strong>
|
||||
{{datePart(item.publishedAt, 'YYYY.MM')}}
|
||||
</div>
|
||||
<div>
|
||||
<div class="content-title">
|
||||
<el-tag v-if="item.isTop === 1" type="danger" size="mini">置顶</el-tag>
|
||||
{{item.title}}
|
||||
</div>
|
||||
<div class="content-summary">{{item.summary || item.subTitle || '点击查看详细内容'}}</div>
|
||||
</div>
|
||||
<div class="content-view"><i class="el-icon-view"></i> {{item.viewCount || 0}}</div>
|
||||
</article>
|
||||
<el-empty v-if="!pageData.list.length && !loading" description="暂无风采内容"></el-empty>
|
||||
<el-pagination v-if="pageData.totalCount > pageData.pageSize" class="wall-pagination"
|
||||
background layout="prev, pager, next" :current-page.sync="pageData.pageNumber"
|
||||
:page-size="pageData.pageSize" :total="pageData.totalCount"
|
||||
@current-change="loadPage"></el-pagination>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button class="back-link" icon="el-icon-back" plain size="small" @click="detail = null">返回列表</el-button>
|
||||
<h1 class="detail-title">{{detail.title}}</h1>
|
||||
<div class="detail-meta">
|
||||
{{formatTime(detail.publishedAt || detail.createdAt)}}
|
||||
{{detail.categoryName}} <i class="el-icon-view"></i> {{detail.viewCount || 0}}
|
||||
</div>
|
||||
<img v-if="getCoverUrl(detail.thumbUrl)" class="detail-cover" :src="getCoverUrl(detail.thumbUrl)" :alt="detail.title">
|
||||
<div class="detail-body" v-html="detail.contentBody"></div>
|
||||
</template>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
apiBase: "/platform/spread/display",
|
||||
loading: false,
|
||||
categories: [],
|
||||
sliderItems: [],
|
||||
currentCategory: {},
|
||||
detail: null,
|
||||
pageData: {pageNumber: 1, pageSize: 8, totalCount: 0, list: []}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 加载可见栏目并自动选择第一个末级栏目。
|
||||
loadCategories() {
|
||||
return this.$axios.post(this.apiBase + "/categoryTree").then(resp => {
|
||||
if (resp.code !== 0) return
|
||||
this.categories = resp.data || []
|
||||
const firstParent = this.categories[0]
|
||||
this.currentCategory = firstParent && firstParent.children && firstParent.children.length
|
||||
? firstParent.children[0] : (firstParent || {})
|
||||
})
|
||||
},
|
||||
// 加载开启轮播栏目的最新发布内容。
|
||||
loadSlider() {
|
||||
return this.$axios.post(this.apiBase + "/sliderData").then(resp => {
|
||||
if (resp.code === 0) this.sliderItems = resp.data || []
|
||||
})
|
||||
},
|
||||
// 根据当前栏目加载已发布内容。
|
||||
loadPage() {
|
||||
if (!this.currentCategory.id) {
|
||||
this.pageData.list = []
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.$axios.post(this.apiBase + "/pageData", {
|
||||
categoryId: this.currentCategory.id,
|
||||
pageNumber: this.pageData.pageNumber,
|
||||
pageSize: this.pageData.pageSize
|
||||
}).then(resp => {
|
||||
if (resp.code === 0) this.pageData = resp.data
|
||||
}).finally(() => this.loading = false)
|
||||
},
|
||||
// 切换栏目时回到第一页和列表状态。
|
||||
selectCategory(category) {
|
||||
this.currentCategory = category
|
||||
this.detail = null
|
||||
this.pageData.pageNumber = 1
|
||||
this.loadPage()
|
||||
},
|
||||
// 获取完整详情并登记一次浏览。
|
||||
openDetail(item) {
|
||||
this.loading = true
|
||||
Promise.all([
|
||||
this.$axios.post(this.apiBase + "/detailData", {id: item.id}),
|
||||
this.$axios.post(this.apiBase + "/addView", {id: item.id})
|
||||
]).then(values => {
|
||||
if (values[0].code === 0) {
|
||||
this.detail = values[0].data
|
||||
this.detail.viewCount = Number(this.detail.viewCount || 0) + 1
|
||||
}
|
||||
}).finally(() => this.loading = false)
|
||||
},
|
||||
// 从上传字段中取得第一张图片地址。
|
||||
getCoverUrl(value) {
|
||||
if (!value) return ""
|
||||
if (Array.isArray(value)) return value.length ? (value[0].url || value[0].data || value[0]) : ""
|
||||
if (typeof value === "string" && value.trim().startsWith("[")) {
|
||||
try {
|
||||
const files = JSON.parse(value)
|
||||
const first = files[0]
|
||||
return first ? (first.url || first.data || first) : ""
|
||||
} catch (e) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return String(value).split(",")[0]
|
||||
},
|
||||
// 轮播图优先使用轮播图片,未配置时回退到封面。
|
||||
getSliderImage(item) {
|
||||
return this.getCoverUrl(item.sliderImages) || this.getCoverUrl(item.thumbUrl)
|
||||
},
|
||||
// 格式化列表日期的指定部分。
|
||||
datePart(value, format) {
|
||||
return value ? this.$moment(Number(value)).format(format) : "--"
|
||||
},
|
||||
// 格式化详情发布时间。
|
||||
formatTime(value) {
|
||||
return value ? this.$moment(Number(value)).format("YYYY-MM-DD HH:mm") : ""
|
||||
}
|
||||
},
|
||||
created() {
|
||||
Promise.all([this.loadCategories(), this.loadSlider()]).then(() => this.loadPage())
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,180 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="内容标题">
|
||||
<el-input v-model="pageForm.title" clearable placeholder="请输入标题" @keyup.enter.native="doSearch"></el-input>
|
||||
</search-item>
|
||||
<search-item label="所属栏目">
|
||||
<el-cascader v-model="pageForm.categoryId" :options="categoryOptions"
|
||||
:props="{value:'id',label:'name',children:'children',emitPath:false,checkStrictly:true}"
|
||||
clearable filterable style="width: 100%"></el-cascader>
|
||||
</search-item>
|
||||
<search-item label="发布状态">
|
||||
<el-select v-model="pageForm.isPublished" clearable placeholder="请选择">
|
||||
<el-option label="草稿" :value="0"></el-option>
|
||||
<el-option label="已发布" :value="1"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="创建时间">
|
||||
<el-date-picker v-model="pageForm.createTime" type="datetimerange"
|
||||
start-placeholder="开始时间" end-placeholder="结束时间"
|
||||
value-format="yyyy-MM-dd HH:mm:ss" style="width: 100%"></el-date-picker>
|
||||
</search-item>
|
||||
<div class="search-query">
|
||||
<el-button type="primary" size="medium" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="风采内容列表">
|
||||
<el-button type="primary" size="medium" icon="el-icon-plus" @click="goCreate">发布内容</el-button>
|
||||
</table-tool>
|
||||
<el-table v-loading="tableLoading" :data="tableData" :size="tableSize" border>
|
||||
<el-table-column :index="indexMethod" type="index" label="序号" width="70" align="center"></el-table-column>
|
||||
<el-table-column prop="title" label="标题" min-width="240" show-overflow-tooltip>
|
||||
<template slot-scope="{row}">
|
||||
<el-tag v-if="row.isTop === 1" type="danger" size="mini" style="margin-right: 5px">置顶</el-tag>
|
||||
{{row.title}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="categoryName" label="栏目" min-width="130"></el-table-column>
|
||||
<el-table-column prop="viewCount" label="浏览量" width="100" align="center"></el-table-column>
|
||||
<el-table-column prop="sortOrder" label="排序" width="90" align="center"></el-table-column>
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag :type="row.isPublished === 1 ? 'success' : 'info'" size="mini">
|
||||
{{row.isPublished === 1 ? '已发布' : '草稿'}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="发布时间" width="170" align="center">
|
||||
<template slot-scope="{row}">{{formatTime(row.publishedAt)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="270" align="center" fixed="right">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="goEdit(row)">编辑</el-button>
|
||||
<el-button size="mini" :type="row.isPublished === 1 ? 'warning' : 'success'"
|
||||
@click="togglePublish(row)">
|
||||
{{row.isPublished === 1 ? '撤回' : '发布'}}
|
||||
</el-button>
|
||||
<el-button size="mini" type="danger" @click="remove(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
apiBase: "/platform/spread/manage",
|
||||
categoryOptions: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 组装分页查询参数,将时间范围拆成后端字段。
|
||||
buildPageParams() {
|
||||
const params = Object.assign({}, this.pageForm)
|
||||
params.beginTime = params.createTime && params.createTime.length ? params.createTime[0] : ""
|
||||
params.endTime = params.createTime && params.createTime.length ? params.createTime[1] : ""
|
||||
delete params.createTime
|
||||
return params
|
||||
},
|
||||
// 加载内容管理分页数据。
|
||||
pageData() {
|
||||
this.tableLoading = true
|
||||
this.$axios.post(this.apiBase + "/pageData", this.buildPageParams()).then(resp => {
|
||||
if (resp.code === 0) {
|
||||
this.tableData = resp.data.list || []
|
||||
this.pageForm.totalCount = resp.data.totalCount || 0
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
}).finally(() => this.tableLoading = false)
|
||||
},
|
||||
// 加载可筛选的栏目树。
|
||||
loadCategories() {
|
||||
return this.$axios.post(this.apiBase + "/categoryTree").then(resp => {
|
||||
if (resp.code === 0) this.categoryOptions = resp.data || []
|
||||
})
|
||||
},
|
||||
// 清空筛选条件并重新查询。
|
||||
resetSearch() {
|
||||
this.pageForm.title = ""
|
||||
this.pageForm.categoryId = ""
|
||||
this.pageForm.isPublished = ""
|
||||
this.pageForm.createTime = []
|
||||
this.doSearch()
|
||||
},
|
||||
// 跳转到独立发布页面。
|
||||
goCreate() {
|
||||
this.navigate("/platform/spread/creation")
|
||||
},
|
||||
// 跳转到发布页面并携带编辑 ID。
|
||||
goEdit(row) {
|
||||
this.navigate("/platform/spread/creation?id=" + encodeURIComponent(row.id))
|
||||
},
|
||||
// 兼容 pjax 与普通页面跳转。
|
||||
navigate(url) {
|
||||
if (window.commonUtil && commonUtil.pjaxPush) {
|
||||
commonUtil.pjaxPush(url)
|
||||
} else {
|
||||
window.location.href = url
|
||||
}
|
||||
},
|
||||
// 发布已保存的草稿,或撤回已发布内容。
|
||||
togglePublish(row) {
|
||||
const status = row.isPublished === 1 ? 0 : 1
|
||||
const action = status === 1 ? "发布" : "撤回"
|
||||
this.$confirm("确定" + action + "“" + row.title + "”吗?", "提示", {type: "warning"}).then(() => {
|
||||
this.$axios.post(this.apiBase + "/doPublish", {id: row.id, status}).then(resp => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(action + "成功")
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
// 删除内容前进行二次确认。
|
||||
remove(row) {
|
||||
this.$confirm("删除后无法恢复,确定删除该内容吗?", "提示", {type: "warning"}).then(() => {
|
||||
this.$axios.post(this.apiBase + "/doDelete", {id: row.id}).then(resp => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
// 将毫秒时间戳格式化为日期时间。
|
||||
formatTime(value) {
|
||||
return value ? this.$moment(Number(value)).format("YYYY-MM-DD HH:mm:ss") : ""
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$set(this.pageForm, "title", "")
|
||||
this.$set(this.pageForm, "categoryId", "")
|
||||
this.$set(this.pageForm, "isPublished", "")
|
||||
this.$set(this.pageForm, "createTime", [])
|
||||
this.loadCategories().then(() => this.pageData())
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,178 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<script src="${base!}/assets/platform/plugins/echarts/echarts.min.js" nonce="${cspNonce!}"></script>
|
||||
<style>
|
||||
.spread-statistics .stat-grid { margin: 10px 0; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 14px; }
|
||||
.spread-statistics .stat-card { padding: 20px; position: relative; overflow: hidden; border-radius: 9px; color: #fff; box-shadow: 0 8px 22px rgba(30,50,80,.1); }
|
||||
.spread-statistics .stat-card:nth-child(1) { background: linear-gradient(135deg,#3378d1,#58a1ef); }
|
||||
.spread-statistics .stat-card:nth-child(2) { background: linear-gradient(135deg,#7357c8,#9d7ee7); }
|
||||
.spread-statistics .stat-card:nth-child(3) { background: linear-gradient(135deg,#2b9c73,#50c499); }
|
||||
.spread-statistics .stat-card:nth-child(4) { background: linear-gradient(135deg,#d87a2e,#efa55b); }
|
||||
.spread-statistics .stat-card:nth-child(5) { background: linear-gradient(135deg,#bd4552,#e36c78); }
|
||||
.spread-statistics .stat-label { color: rgba(255,255,255,.82); font-size: 13px; }
|
||||
.spread-statistics .stat-value { margin-top: 8px; font-size: 29px; font-weight: 700; }
|
||||
.spread-statistics .chart-grid { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, .75fr); gap: 14px; }
|
||||
.spread-statistics .chart-box { height: 390px; }
|
||||
@media(max-width: 980px) { .spread-statistics .stat-grid { grid-template-columns: repeat(2, 1fr); } .spread-statistics .chart-grid { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
<div id="app" class="spread-statistics" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="所属栏目">
|
||||
<el-cascader v-model="query.categoryId" :options="categoryOptions"
|
||||
:props="{value:'id',label:'name',children:'children',emitPath:false,checkStrictly:true}"
|
||||
clearable filterable style="width: 100%"></el-cascader>
|
||||
</search-item>
|
||||
<search-item label="发布状态">
|
||||
<el-select v-model="query.isPublished" clearable placeholder="全部">
|
||||
<el-option label="草稿" :value="0"></el-option>
|
||||
<el-option label="已发布" :value="1"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="创建时间">
|
||||
<el-date-picker v-model="query.createTime" type="datetimerange"
|
||||
value-format="yyyy-MM-dd HH:mm:ss" start-placeholder="开始时间"
|
||||
end-placeholder="结束时间" style="width: 100%"></el-date-picker>
|
||||
</search-item>
|
||||
<div class="search-query">
|
||||
<el-button type="primary" size="medium" icon="el-icon-search" @click="loadAll">查询</el-button>
|
||||
<el-button size="medium" @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<div class="stat-grid" v-loading="loading">
|
||||
<div class="stat-card"><div class="stat-label">栏目数量</div><div class="stat-value">{{summary.categoryCount || 0}}</div></div>
|
||||
<div class="stat-card"><div class="stat-label">内容总数</div><div class="stat-value">{{summary.contentCount || 0}}</div></div>
|
||||
<div class="stat-card"><div class="stat-label">已发布</div><div class="stat-value">{{summary.publishedCount || 0}}</div></div>
|
||||
<div class="stat-card"><div class="stat-label">累计浏览</div><div class="stat-value">{{summary.viewCount || 0}}</div></div>
|
||||
<div class="stat-card"><div class="stat-label">平均浏览</div><div class="stat-value">{{summary.avgViewCount || 0}}</div></div>
|
||||
</div>
|
||||
|
||||
<div class="chart-grid">
|
||||
<el-card shadow="never">
|
||||
<div slot="header"><b>浏览量 TOP 10</b></div>
|
||||
<div ref="topChart" class="chart-box"></div>
|
||||
</el-card>
|
||||
<el-card shadow="never">
|
||||
<div slot="header"><b>栏目浏览量占比</b></div>
|
||||
<div ref="categoryChart" class="chart-box"></div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<div slot="header"><b>栏目数据明细</b></div>
|
||||
<el-table :data="categoryData" border>
|
||||
<el-table-column type="index" label="序号" width="70" align="center"></el-table-column>
|
||||
<el-table-column prop="categoryName" label="栏目名称" min-width="180"></el-table-column>
|
||||
<el-table-column prop="contentCount" label="内容数量" width="140" align="center"></el-table-column>
|
||||
<el-table-column prop="viewCount" label="浏览量" width="140" align="center"></el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
apiBase: "/platform/spread/statistics",
|
||||
loading: false,
|
||||
query: {categoryId: "", isPublished: "", createTime: []},
|
||||
categoryOptions: [],
|
||||
summary: {},
|
||||
topData: [],
|
||||
categoryData: [],
|
||||
topChart: null,
|
||||
categoryChart: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 将时间范围拆分成统计接口参数。
|
||||
buildParams() {
|
||||
return {
|
||||
categoryId: this.query.categoryId,
|
||||
isPublished: this.query.isPublished,
|
||||
beginTime: this.query.createTime && this.query.createTime.length ? this.query.createTime[0] : "",
|
||||
endTime: this.query.createTime && this.query.createTime.length ? this.query.createTime[1] : ""
|
||||
}
|
||||
},
|
||||
// 加载栏目筛选树。
|
||||
loadCategories() {
|
||||
return this.$axios.post(this.apiBase + "/categoryTree").then(resp => {
|
||||
if (resp.code === 0) this.categoryOptions = resp.data || []
|
||||
})
|
||||
},
|
||||
// 并行加载卡片、排行和栏目统计,减少页面等待时间。
|
||||
loadAll() {
|
||||
this.loading = true
|
||||
const params = this.buildParams()
|
||||
Promise.all([
|
||||
this.$axios.post(this.apiBase + "/summary", params),
|
||||
this.$axios.post(this.apiBase + "/top10", params),
|
||||
this.$axios.post(this.apiBase + "/categoryData", params)
|
||||
]).then(values => {
|
||||
this.summary = values[0].code === 0 ? (values[0].data || {}) : {}
|
||||
this.topData = values[1].code === 0 ? (values[1].data || []) : []
|
||||
this.categoryData = values[2].code === 0 ? (values[2].data || []) : []
|
||||
this.$nextTick(this.renderCharts)
|
||||
}).finally(() => this.loading = false)
|
||||
},
|
||||
// 清空筛选并重新统计。
|
||||
resetQuery() {
|
||||
this.query = {categoryId: "", isPublished: "", createTime: []}
|
||||
this.loadAll()
|
||||
},
|
||||
// 创建或刷新两个统计图表。
|
||||
renderCharts() {
|
||||
if (!window.echarts) return
|
||||
if (!this.topChart) this.topChart = echarts.init(this.$refs.topChart)
|
||||
if (!this.categoryChart) this.categoryChart = echarts.init(this.$refs.categoryChart)
|
||||
this.topChart.setOption({
|
||||
color: ["#b43135"],
|
||||
tooltip: {trigger: "axis"},
|
||||
grid: {left: 45, right: 20, top: 25, bottom: 95},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: this.topData.map(item => item.title),
|
||||
axisLabel: {interval: 0, rotate: 35, formatter: value => value.length > 10 ? value.slice(0, 10) + "…" : value}
|
||||
},
|
||||
yAxis: {type: "value", minInterval: 1},
|
||||
series: [{type: "bar", barMaxWidth: 34, data: this.topData.map(item => item.viewCount || 0)}]
|
||||
}, true)
|
||||
this.categoryChart.setOption({
|
||||
color: ["#9c252a", "#d78643", "#d4ad62", "#547a86", "#7d669e", "#5a9a7c"],
|
||||
tooltip: {trigger: "item"},
|
||||
legend: {bottom: 0, type: "scroll"},
|
||||
series: [{
|
||||
type: "pie", radius: ["38%", "68%"], center: ["50%", "43%"],
|
||||
label: {formatter: "{b}\n{d}%"},
|
||||
data: this.categoryData.map(item => ({name: item.categoryName, value: item.viewCount || 0}))
|
||||
}]
|
||||
}, true)
|
||||
},
|
||||
// 浏览器尺寸变化时重算图表尺寸。
|
||||
resizeCharts() {
|
||||
if (this.topChart) this.topChart.resize()
|
||||
if (this.categoryChart) this.categoryChart.resize()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadCategories().then(() => this.loadAll())
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("resize", this.resizeCharts)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("resize", this.resizeCharts)
|
||||
if (this.topChart) this.topChart.dispose()
|
||||
if (this.categoryChart) this.categoryChart.dispose()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
Reference in New Issue
Block a user