u
This commit is contained in:
1 parent
9eb5639d84
commit
e9ad523fcf
19 files changed
+782
-167
No files matched your search
+29
-3
@@ -2,10 +2,9 @@ package cn.link56.post.controller;
|
|||||||
|
|
||||||
import cn.link56.common.BasePageVO;
|
import cn.link56.common.BasePageVO;
|
||||||
import cn.link56.common.Result;
|
import cn.link56.common.Result;
|
||||||
import cn.link56.post.model.dto.AddPostDTO;
|
import cn.link56.post.model.dto.*;
|
||||||
import cn.link56.post.model.dto.QueryPostDTO;
|
|
||||||
import cn.link56.post.model.dto.UpdatePostDTO;
|
|
||||||
import cn.link56.post.model.vo.EditPostVO;
|
import cn.link56.post.model.vo.EditPostVO;
|
||||||
|
import cn.link56.post.model.vo.DashboardVO;
|
||||||
import cn.link56.post.model.vo.QueryPostVO;
|
import cn.link56.post.model.vo.QueryPostVO;
|
||||||
import cn.link56.post.service.PostService;
|
import cn.link56.post.service.PostService;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
@@ -20,6 +19,12 @@ public class PostController {
|
|||||||
@Resource
|
@Resource
|
||||||
private PostService postService;
|
private PostService postService;
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public Result<EditPostVO> getDetail(@PathVariable("id") Long id) {
|
||||||
|
EditPostVO editPostVO = postService.getDetail(id);
|
||||||
|
return Result.success(editPostVO);
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/add")
|
@PostMapping("/add")
|
||||||
public Result<EditPostVO> addPost(@RequestBody @Validated AddPostDTO addPostDTO) {
|
public Result<EditPostVO> addPost(@RequestBody @Validated AddPostDTO addPostDTO) {
|
||||||
EditPostVO editPostVO = postService.addPost(addPostDTO);
|
EditPostVO editPostVO = postService.addPost(addPostDTO);
|
||||||
@@ -32,9 +37,30 @@ public class PostController {
|
|||||||
return Result.success(editPostVO);
|
return Result.success(editPostVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/update/status")
|
||||||
|
public Result<EditPostVO> updatePostStatus(@RequestBody @Validated UpdatePostStatusDTO updatePostStatusDTO){
|
||||||
|
postService.updatePostStatus(updatePostStatusDTO);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/query")
|
@PostMapping("/query")
|
||||||
public Result<BasePageVO<QueryPostVO>> queryPost(@RequestBody QueryPostDTO queryPostDTO){
|
public Result<BasePageVO<QueryPostVO>> queryPost(@RequestBody QueryPostDTO queryPostDTO){
|
||||||
BasePageVO<QueryPostVO> data = postService.queryPost(queryPostDTO);
|
BasePageVO<QueryPostVO> data = postService.queryPost(queryPostDTO);
|
||||||
return Result.success(data);
|
return Result.success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/delete")
|
||||||
|
public Result<Void> deletePost(@RequestBody @Validated DeletePostDTO deletePostDTO){
|
||||||
|
postService.deletePost(deletePostDTO);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/dashboard")
|
||||||
|
public Result<DashboardVO> getDashboardData(){
|
||||||
|
DashboardVO dashboardVO = postService.getDashboardData();
|
||||||
|
|
||||||
|
return Result.success(dashboardVO);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package cn.link56.post.mapper;
|
package cn.link56.post.mapper;
|
||||||
|
|
||||||
import cn.link56.post.model.po.PostPO;
|
import cn.link56.post.model.po.PostPO;
|
||||||
|
import cn.link56.post.model.vo.DashboardVO;
|
||||||
import cn.link56.post.model.vo.QueryPostVO;
|
import cn.link56.post.model.vo.QueryPostVO;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
@@ -10,8 +11,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface PostMapper extends BaseMapper<PostPO> {
|
public interface PostMapper extends BaseMapper<PostPO> {
|
||||||
|
List<QueryPostVO> selectPostPage(
|
||||||
List<QueryPostVO> queryPostPage(
|
|
||||||
@Param("offset") int offset,
|
@Param("offset") int offset,
|
||||||
@Param("limit") int limit,
|
@Param("limit") int limit,
|
||||||
@Param("userId") Long userId,
|
@Param("userId") Long userId,
|
||||||
@@ -21,11 +21,16 @@ public interface PostMapper extends BaseMapper<PostPO> {
|
|||||||
@Param("month") String month
|
@Param("month") String month
|
||||||
);
|
);
|
||||||
|
|
||||||
Long queryPostTotal(
|
Long selectPostTotal(
|
||||||
@Param("userId") Long userId,
|
@Param("userId") Long userId,
|
||||||
@Param("title") String title,
|
@Param("title") String title,
|
||||||
@Param("status") Integer status,
|
@Param("status") Integer status,
|
||||||
@Param("year") String year,
|
@Param("year") String year,
|
||||||
@Param("month") String month
|
@Param("month") String month
|
||||||
);
|
);
|
||||||
|
|
||||||
|
DashboardVO.Count selectDashboardOverview(@Param("userId") Long userId);
|
||||||
|
|
||||||
|
List<DashboardVO.Chart> selectDashboardViewStats(@Param("userId") Long userId);
|
||||||
|
List<DashboardVO.Chart> selectDashboardNumberStats(@Param("userId") Long userId);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package cn.link56.post.model.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class DeletePostDTO {
|
||||||
|
@NotNull
|
||||||
|
private Long id;
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package cn.link56.post.model.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class UpdatePostStatusDTO {
|
||||||
|
@NotNull
|
||||||
|
private Long id;
|
||||||
|
@NotNull
|
||||||
|
private Integer status;
|
||||||
|
}
|
||||||
+1
-2
@@ -8,8 +8,7 @@ import lombok.Getter;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public enum PostStatusEnum {
|
public enum PostStatusEnum {
|
||||||
DRAFT(0, "草稿"),
|
DRAFT(0, "草稿"),
|
||||||
PUBLISHED(1, "已发布"),
|
PUBLISHED(1, "已发布");
|
||||||
OFFLINE(2, "已下架");
|
|
||||||
|
|
||||||
private final int code;
|
private final int code;
|
||||||
private final String desc;
|
private final String desc;
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package cn.link56.post.model.vo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class DashboardVO {
|
||||||
|
private Count allCount;
|
||||||
|
private List<Chart> numberChart;
|
||||||
|
private List<Chart> viewChart;
|
||||||
|
private List<Chart> typeChart;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public static class Count {
|
||||||
|
private Long postCount;
|
||||||
|
private Long viewCount;
|
||||||
|
private Long likeCount;
|
||||||
|
private Long favoriteCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public static class Chart {
|
||||||
|
private String label;
|
||||||
|
private Integer value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,11 +5,10 @@ import cn.link56.common.context.UserContext;
|
|||||||
import cn.link56.common.utils.snowflake.idgen.IdGenerator;
|
import cn.link56.common.utils.snowflake.idgen.IdGenerator;
|
||||||
import cn.link56.company.model.po.CompanyUserPO;
|
import cn.link56.company.model.po.CompanyUserPO;
|
||||||
import cn.link56.post.mapper.PostMapper;
|
import cn.link56.post.mapper.PostMapper;
|
||||||
import cn.link56.post.model.dto.AddPostDTO;
|
import cn.link56.post.model.dto.*;
|
||||||
import cn.link56.post.model.dto.QueryPostDTO;
|
|
||||||
import cn.link56.post.model.dto.UpdatePostDTO;
|
|
||||||
import cn.link56.post.model.enums.PostStatusEnum;
|
import cn.link56.post.model.enums.PostStatusEnum;
|
||||||
import cn.link56.post.model.po.PostPO;
|
import cn.link56.post.model.po.PostPO;
|
||||||
|
import cn.link56.post.model.vo.DashboardVO;
|
||||||
import cn.link56.post.model.vo.EditPostVO;
|
import cn.link56.post.model.vo.EditPostVO;
|
||||||
import cn.link56.post.model.vo.QueryPostVO;
|
import cn.link56.post.model.vo.QueryPostVO;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
@@ -20,81 +19,203 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.Arrays;
|
import java.time.LocalDate;
|
||||||
import java.util.Date;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class PostService extends ServiceImpl<PostMapper, PostPO> {
|
public class PostService extends ServiceImpl<PostMapper, PostPO> {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private PostMapper postMapper;
|
private PostMapper postMapper;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public EditPostVO addPost(AddPostDTO addPostDTO) {
|
public EditPostVO addPost(AddPostDTO dto) {
|
||||||
PostPO postPO = new PostPO();
|
PostPO postPO = new PostPO();
|
||||||
|
BeanUtils.copyProperties(dto, postPO);
|
||||||
BeanUtils.copyProperties(addPostDTO, postPO);
|
|
||||||
|
|
||||||
postPO.setTag(StringUtils.join(addPostDTO.getTags(), ","));
|
|
||||||
postPO.setCompanyId(UserContext.getCompanyId());
|
|
||||||
|
|
||||||
if(addPostDTO.getStatus() == PostStatusEnum.PUBLISHED.getCode()){
|
|
||||||
postPO.setPublishTime(new Date());
|
|
||||||
}
|
|
||||||
|
|
||||||
postPO.setId(IdGenerator.nextId());
|
postPO.setId(IdGenerator.nextId());
|
||||||
|
postPO.setCompanyId(UserContext.getCompanyId());
|
||||||
|
postPO.setTag(joinTags(dto.getTags()));
|
||||||
|
|
||||||
|
handleUpdatePublishTime(postPO, dto.getStatus());
|
||||||
|
|
||||||
postMapper.insert(postPO);
|
postMapper.insert(postPO);
|
||||||
|
return buildEditPostVO(postPO);
|
||||||
EditPostVO editPostVO = new EditPostVO();
|
|
||||||
|
|
||||||
BeanUtils.copyProperties(postPO, editPostVO);
|
|
||||||
editPostVO.setTags(addPostDTO.getTags());
|
|
||||||
|
|
||||||
return editPostVO;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BasePageVO<QueryPostVO> queryPost(QueryPostDTO queryPostDTO) {
|
public EditPostVO updatePost(UpdatePostDTO dto) {
|
||||||
Integer currentPage = queryPostDTO.getCurrentPage();
|
PostPO postPO = postMapper.selectById(dto.getId());
|
||||||
Integer pageSize = queryPostDTO.getPageSize();
|
if (postPO == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
String title = queryPostDTO.getTitle();
|
BeanUtils.copyProperties(dto, postPO);
|
||||||
Integer status = queryPostDTO.getStatus();
|
postPO.setTag(joinTags(dto.getTags()));
|
||||||
String year = queryPostDTO.getYear();
|
|
||||||
String month = queryPostDTO.getMonth();
|
|
||||||
|
|
||||||
int offset = (currentPage - 1) * pageSize + 1;
|
handleUpdatePublishTime(postPO, dto.getStatus());
|
||||||
int limit = currentPage * pageSize;
|
|
||||||
|
|
||||||
List<QueryPostVO> queryPostVOS = postMapper.queryPostPage(offset, limit, UserContext.getUserId(), title, status, year, month);
|
postMapper.updateById(postPO);
|
||||||
Long total = postMapper.queryPostTotal(UserContext.getUserId(), title, status, year, month);
|
return buildEditPostVO(postPO);
|
||||||
|
}
|
||||||
|
|
||||||
BasePageVO<QueryPostVO> result = new BasePageVO<>();
|
public EditPostVO getDetail(Long id) {
|
||||||
result.setRecords(queryPostVOS);
|
PostPO postPO = postMapper.selectById(id);
|
||||||
result.setTotal(total);
|
if (postPO == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildEditPostVO(postPO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasePageVO<QueryPostVO> queryPost(QueryPostDTO dto) {
|
||||||
|
int offset = (dto.getCurrentPage() - 1) * dto.getPageSize() + 1;
|
||||||
|
int limit = dto.getCurrentPage() * dto.getPageSize();
|
||||||
|
|
||||||
|
List<QueryPostVO> records = postMapper.selectPostPage(
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
UserContext.getUserId(),
|
||||||
|
dto.getTitle(),
|
||||||
|
dto.getStatus(),
|
||||||
|
dto.getYear(),
|
||||||
|
dto.getMonth()
|
||||||
|
);
|
||||||
|
|
||||||
|
Long total = postMapper.selectPostTotal(
|
||||||
|
UserContext.getUserId(),
|
||||||
|
dto.getTitle(),
|
||||||
|
dto.getStatus(),
|
||||||
|
dto.getYear(),
|
||||||
|
dto.getMonth()
|
||||||
|
);
|
||||||
|
|
||||||
|
BasePageVO<QueryPostVO> page = new BasePageVO<>();
|
||||||
|
page.setRecords(records);
|
||||||
|
page.setTotal(total);
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= 私有方法 ================= */
|
||||||
|
|
||||||
|
private EditPostVO buildEditPostVO(PostPO postPO) {
|
||||||
|
EditPostVO vo = new EditPostVO();
|
||||||
|
BeanUtils.copyProperties(postPO, vo);
|
||||||
|
|
||||||
|
vo.setTags(
|
||||||
|
StringUtils.isBlank(postPO.getTag())
|
||||||
|
? Collections.emptyList()
|
||||||
|
: Arrays.asList(StringUtils.split(postPO.getTag(), ","))
|
||||||
|
);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String joinTags(List<String> tags) {
|
||||||
|
return CollectionUtils.isEmpty(tags)
|
||||||
|
? null
|
||||||
|
: StringUtils.join(tags, ",");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUpdatePublishTime(PostPO postPO, Integer status) {
|
||||||
|
if (PostStatusEnum.PUBLISHED.getCode() == status
|
||||||
|
&& postPO.getPublishTime() == null) {
|
||||||
|
postPO.setPublishTime(new Date());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void deletePost(DeletePostDTO deletePostDTO) {
|
||||||
|
postMapper.deleteById(deletePostDTO.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void updatePostStatus(UpdatePostStatusDTO updatePostStatusDTO) {
|
||||||
|
Long id = updatePostStatusDTO.getId();
|
||||||
|
Integer status = updatePostStatusDTO.getStatus();
|
||||||
|
|
||||||
|
PostPO postPO = postMapper.selectById(id);
|
||||||
|
if (postPO == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
postPO.setStatus(status);
|
||||||
|
postMapper.updateById(postPO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DashboardVO getDashboardData() {
|
||||||
|
Long userId = UserContext.getUserId();
|
||||||
|
|
||||||
|
DashboardVO dashboardVO = new DashboardVO();
|
||||||
|
|
||||||
|
DashboardVO.Count count = postMapper.selectDashboardOverview(userId);
|
||||||
|
|
||||||
|
// 原始数据
|
||||||
|
List<DashboardVO.Chart> viewCharts = postMapper.selectDashboardViewStats(userId);
|
||||||
|
List<DashboardVO.Chart> numberCharts = postMapper.selectDashboardNumberStats(userId);
|
||||||
|
List<DashboardVO.Chart> typeCharts = getDashboardTypeStats(userId);
|
||||||
|
|
||||||
|
// 统一处理最近 7 天,补齐缺失值
|
||||||
|
dashboardVO.setViewChart(fillLast7Days(viewCharts));
|
||||||
|
dashboardVO.setNumberChart(fillLast7Days(numberCharts));
|
||||||
|
dashboardVO.setTypeChart(typeCharts);
|
||||||
|
|
||||||
|
dashboardVO.setAllCount(count);
|
||||||
|
return dashboardVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充最近7天的 chart 数据,如果 map 里没有,则补 0
|
||||||
|
*/
|
||||||
|
private List<DashboardVO.Chart> fillLast7Days(List<DashboardVO.Chart> originalCharts) {
|
||||||
|
// 转 map,方便查
|
||||||
|
Map<String, Integer> map = originalCharts.stream()
|
||||||
|
.collect(Collectors.toMap(DashboardVO.Chart::getLabel, DashboardVO.Chart::getValue, (a, b) -> b));
|
||||||
|
|
||||||
|
List<DashboardVO.Chart> result = new ArrayList<>();
|
||||||
|
for (int i = 6; i >= 0; i--) {
|
||||||
|
String date = LocalDate.now().minusDays(i).toString();
|
||||||
|
result.add(new DashboardVO.Chart(date, map.getOrDefault(date, 0)));
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EditPostVO updatePost(UpdatePostDTO updatePostDTO) {
|
private List<DashboardVO.Chart> getDashboardTypeStats(Long userId) {
|
||||||
Long id = updatePostDTO.getId();
|
LambdaQueryWrapper<PostPO> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper
|
||||||
|
.select(PostPO::getTag)
|
||||||
|
.eq(PostPO::getStatus, PostStatusEnum.PUBLISHED.getCode())
|
||||||
|
.eq(PostPO::getCreateBy, userId);
|
||||||
|
|
||||||
PostPO postPO = postMapper.selectById(id);
|
List<PostPO> postPOS = postMapper.selectList(queryWrapper);
|
||||||
|
|
||||||
System.out.println(postPO);
|
// 用 map 统计每个 tag 出现次数
|
||||||
|
Map<String, Integer> tagCountMap = new HashMap<>();
|
||||||
|
|
||||||
BeanUtils.copyProperties(updatePostDTO,postPO);
|
for (PostPO postPO : postPOS) {
|
||||||
postPO.setTag(StringUtils.join(updatePostDTO.getTags(), ","));
|
if (postPO == null) continue;
|
||||||
|
|
||||||
postMapper.updateById(postPO);
|
String tagsStr = postPO.getTag();
|
||||||
|
if (tagsStr == null || tagsStr.isEmpty()) continue;
|
||||||
|
|
||||||
EditPostVO editPostVO = new EditPostVO();
|
// 分割逗号,并去掉空格
|
||||||
|
String[] tags = tagsStr.split(",");
|
||||||
|
for (String tag : tags) {
|
||||||
|
tag = tag.trim();
|
||||||
|
if (tag.isEmpty()) continue;
|
||||||
|
|
||||||
BeanUtils.copyProperties(postPO, editPostVO);
|
tagCountMap.put(tag, tagCountMap.getOrDefault(tag, 0) + 1);
|
||||||
editPostVO.setTags(updatePostDTO.getTags());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return editPostVO;
|
// 转成 DashboardVO.Chart 列表
|
||||||
|
List<DashboardVO.Chart> result = new ArrayList<>();
|
||||||
|
tagCountMap.forEach((tag, count) -> result.add(new DashboardVO.Chart(tag, count)));
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<result property="commentCount" column="commentCount"/>
|
<result property="commentCount" column="commentCount"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<select id="queryPostPage" resultMap="QueryPostVOMap">
|
<select id="selectPostPage" resultMap="QueryPostVOMap">
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM (
|
FROM (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -38,10 +38,10 @@
|
|||||||
p.b_publish_time AS publishTime,
|
p.b_publish_time AS publishTime,
|
||||||
p.b_status AS status,
|
p.b_status AS status,
|
||||||
|
|
||||||
(SELECT COUNT(*) FROM b_post_view v WHERE v.b_post_id = p.b_id) AS viewCount,
|
(SELECT COUNT(1) FROM b_post_view v WHERE v.b_post_id = p.b_id) AS viewCount,
|
||||||
(SELECT COUNT(*) FROM b_post_like l WHERE l.b_post_id = p.b_id) AS likeCount,
|
(SELECT COUNT(1) FROM b_post_like l WHERE l.b_post_id = p.b_id) AS likeCount,
|
||||||
(SELECT COUNT(*) FROM b_post_favorite f WHERE f.b_post_id = p.b_id) AS favoriteCount,
|
(SELECT COUNT(1) FROM b_post_favorite f WHERE f.b_post_id = p.b_id) AS favoriteCount,
|
||||||
(SELECT COUNT(*) FROM b_post_comment c WHERE c.b_post_id = p.b_id) AS commentCount,
|
(SELECT COUNT(1) FROM b_post_comment c WHERE c.b_post_id = p.b_id) AS commentCount,
|
||||||
|
|
||||||
ROW_NUMBER() OVER (ORDER BY p.b_update_time DESC) AS rn
|
ROW_NUMBER() OVER (ORDER BY p.b_update_time DESC) AS rn
|
||||||
FROM b_post p
|
FROM b_post p
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
WHERE t.rn BETWEEN #{offset} AND #{limit}
|
WHERE t.rn BETWEEN #{offset} AND #{limit}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="queryPostTotal" resultType="java.lang.Long">
|
<select id="selectPostTotal" resultType="java.lang.Long">
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM b_post p
|
FROM b_post p
|
||||||
<where>
|
<where>
|
||||||
@@ -95,4 +95,38 @@
|
|||||||
</if>
|
</if>
|
||||||
</where>
|
</where>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectDashboardOverview" resultType="cn.link56.post.model.vo.DashboardVO$Count">
|
||||||
|
SELECT
|
||||||
|
(SELECT COUNT(*) FROM b_post WHERE b_create_by = #{userId}) AS postCount,
|
||||||
|
(SELECT COUNT(*) FROM b_post_view WHERE b_post_id IN (SELECT b_id FROM b_post WHERE b_create_by = #{userId})) AS viewCount,
|
||||||
|
(SELECT COUNT(*) FROM b_post_like WHERE b_post_id IN (SELECT b_id FROM b_post WHERE b_create_by = #{userId})) AS likeCount,
|
||||||
|
(SELECT COUNT(*) FROM b_post_favorite WHERE b_post_id IN (SELECT b_id FROM b_post WHERE b_create_by = #{userId})) AS favoriteCount
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectDashboardViewStats" resultType="cn.link56.post.model.vo.DashboardVO$Chart">
|
||||||
|
SELECT
|
||||||
|
CONVERT(VARCHAR(10), v.b_create_time, 120) AS label,
|
||||||
|
COUNT(*) AS value
|
||||||
|
FROM b_post_view v
|
||||||
|
JOIN b_post p ON v.b_post_id = p.b_id
|
||||||
|
WHERE p.b_create_by = #{userId}
|
||||||
|
AND v.b_create_time >= CONVERT(DATE, DATEADD(DAY, -6, GETDATE()))
|
||||||
|
AND v.b_create_time < CONVERT(DATE, DATEADD(DAY, 1, GETDATE()))
|
||||||
|
GROUP BY CONVERT(VARCHAR(10), v.b_create_time, 120)
|
||||||
|
ORDER BY label
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectDashboardNumberStats" resultType="cn.link56.post.model.vo.DashboardVO$Chart">
|
||||||
|
SELECT
|
||||||
|
CONVERT(VARCHAR(10), p.b_create_time, 120) AS label,
|
||||||
|
COUNT(*) AS value
|
||||||
|
FROM b_post p
|
||||||
|
WHERE p.b_create_by = #{userId}
|
||||||
|
AND p.b_create_time >= CONVERT(DATE, DATEADD(DAY, -6, GETDATE()))
|
||||||
|
AND p.b_create_time < CONVERT(DATE, DATEADD(DAY, 1, GETDATE()))
|
||||||
|
GROUP BY CONVERT(VARCHAR(10), p.b_create_time, 120)
|
||||||
|
ORDER BY label
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { AddPostParams, Post, PostEditorForm, QueryPostParams, UpdatePostParams } from '@/types/post/editor'
|
import type { DashboardData } from '@/types/post'
|
||||||
|
import type { AddPostParams, DeletePostParams, Post, PostEditorForm, QueryPostParams, UpdatePostParams, UpdatePostStatusParams } from '@/types/post/editor'
|
||||||
import { httpGet, httpPostJson } from '@/request/utils'
|
import { httpGet, httpPostJson } from '@/request/utils'
|
||||||
|
|
||||||
export function addPostApi(params: AddPostParams) {
|
export function addPostApi(params: AddPostParams) {
|
||||||
@@ -10,6 +10,22 @@ export function updatePostApi(params: UpdatePostParams) {
|
|||||||
return httpPostJson<PostEditorForm>('/post/update', params)
|
return httpPostJson<PostEditorForm>('/post/update', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updatePostStatusApi(params: UpdatePostStatusParams) {
|
||||||
|
return httpPostJson<PostEditorForm>('/post/update/status', params)
|
||||||
|
}
|
||||||
|
|
||||||
export function queryPostApi(params: QueryPostParams) {
|
export function queryPostApi(params: QueryPostParams) {
|
||||||
return httpPostJson<BasePageResult<Post>>('/post/query', params)
|
return httpPostJson<BasePageResult<Post>>('/post/query', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPostApi(id: number) {
|
||||||
|
return httpGet<PostEditorForm>(`/post/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deletePostApi(params: DeletePostParams) {
|
||||||
|
return httpPostJson<void>('/post/delete', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDashboardDataApi() {
|
||||||
|
return httpGet<DashboardData>(`/post/dashboard`)
|
||||||
|
}
|
||||||
@@ -49,10 +49,10 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings/creator/home',
|
path: '/settings/creator/home',
|
||||||
component: () => import('@/views/settings/creator/home.vue'),
|
component: () => import('@/views/settings/creator/home/index.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings/creator/editor/:id?',
|
path: '/settings/creator/editor',
|
||||||
component: () => import('@/views/settings/creator/editor.vue'),
|
component: () => import('@/views/settings/creator/editor.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export enum PostStatusEnum {
|
export enum PostStatusEnum {
|
||||||
DRAFT = 0, // 草稿
|
DRAFT = 0, // 草稿
|
||||||
PUBLISHED = 1, // 已发布
|
PUBLISHED = 1, // 已发布
|
||||||
OFFLINE = 2, // 已下架
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostEditorForm {
|
export interface PostEditorForm {
|
||||||
@@ -46,6 +45,15 @@ export interface QueryPostParams {
|
|||||||
month: string
|
month: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeletePostParams {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePostStatusParams {
|
||||||
|
id: number
|
||||||
|
status: number
|
||||||
|
}
|
||||||
|
|
||||||
export interface Post {
|
export interface Post {
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export interface DashboradCount {
|
||||||
|
postCount: number
|
||||||
|
viewCount: number
|
||||||
|
likeCount: number
|
||||||
|
favoriteCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboradChart {
|
||||||
|
label: string
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardData {
|
||||||
|
allCount: DashboradCount
|
||||||
|
numberChart: DashboradChart[]
|
||||||
|
viewChart: DashboradChart[]
|
||||||
|
typeChart: DashboradChart[]
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { RecommendCompany, RecommendHotCompany } from '@/types/company'
|
import type { RecommendCompany, RecommendHotCompany } from '@/types/company'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import QueryCompany from '@/components/QueryCompany.vue'
|
import QueryCompany from '@/components/QueryCompany.vue'
|
||||||
import { getRecommendCompanyApi, getRecommendHotCompanyApi } from '@/request/api/company'
|
import { getRecommendCompanyApi, getRecommendHotCompanyApi} from '@/request/api/company'
|
||||||
import { useCommonStore } from '@/store/common'
|
import { useCommonStore } from '@/store/common'
|
||||||
import Recommend from './components/recommend.vue'
|
import Recommend from './components/recommend.vue'
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,12 @@ import { useRoute } from 'vue-router'
|
|||||||
import CustomLoading from '@/components/CustomLoading.vue'
|
import CustomLoading from '@/components/CustomLoading.vue'
|
||||||
import ImageUploadCard from '@/components/ImageUploadCard.vue'
|
import ImageUploadCard from '@/components/ImageUploadCard.vue'
|
||||||
import WangEditor from '@/components/wang-editor/index.vue'
|
import WangEditor from '@/components/wang-editor/index.vue'
|
||||||
import { addPostApi, updatePostApi } from '@/request/api/post'
|
import { addPostApi, getPostApi, updatePostApi } from '@/request/api/post'
|
||||||
import { getPostTagApi } from '@/request/api/post/tag'
|
import { getPostTagApi } from '@/request/api/post/tag'
|
||||||
import { PostStatusEnum } from '@/types/post/editor'
|
import { PostStatusEnum } from '@/types/post/editor'
|
||||||
import { uploadToAliyun } from '@/utils/file'
|
import { uploadToAliyun } from '@/utils/file'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const id = route.params.id as string | undefined
|
|
||||||
|
|
||||||
const titleInputRef = ref<HTMLInputElement | null>(null)
|
const titleInputRef = ref<HTMLInputElement | null>(null)
|
||||||
const wangEditorRef = ref<any>(null)
|
const wangEditorRef = ref<any>(null)
|
||||||
@@ -80,9 +79,25 @@ const formRules = {
|
|||||||
const isUpdate = computed(() => form.value.id !== 0)
|
const isUpdate = computed(() => form.value.id !== 0)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loading.init = false
|
const id = Number(route.query.id)
|
||||||
|
if (id) {
|
||||||
|
loading.init = true
|
||||||
|
|
||||||
oldForm.value = JSON.parse(JSON.stringify(form.value))
|
getPostApi(id).then((res) => {
|
||||||
|
if (res.isSuccess) {
|
||||||
|
form.value = res.data
|
||||||
|
oldForm.value = JSON.parse(JSON.stringify(res.data))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ElMessage.error(res.message || '获取该帖子信息失败,请稍后再试')
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
ElMessage.error('获取该帖子信息失败,请稍后再试')
|
||||||
|
}).finally(() => {
|
||||||
|
loading.init = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
titleInputRef.value?.focus()
|
titleInputRef.value?.focus()
|
||||||
@@ -112,76 +127,70 @@ async function handleShowPushDrawer() {
|
|||||||
loading.init = false
|
loading.init = false
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSave(formEl: FormInstance | undefined) {
|
async function handleCoverUploadIfNeeded() {
|
||||||
|
if (form.value.cover === oldForm.value.cover)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (!form.value.cover || !imageUpload.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
const { blob, name, size } = imageUpload.value
|
||||||
|
const res = await uploadToAliyun(blob, name, size, false)
|
||||||
|
form.value.cover = res.url
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSave(formEl?: FormInstance) {
|
||||||
if (!formEl)
|
if (!formEl)
|
||||||
return
|
return
|
||||||
|
|
||||||
await formEl.validate(async (valid) => {
|
loading.confirm = true
|
||||||
if (!valid)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (form.value.content === '') {
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) {
|
||||||
|
loading.confirm = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!form.value.content) {
|
||||||
ElMessage.warning('文章内容不能为空')
|
ElMessage.warning('文章内容不能为空')
|
||||||
|
loading.confirm = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.confirm = true
|
try {
|
||||||
|
await handleCoverUploadIfNeeded()
|
||||||
|
|
||||||
if (form.value.cover !== oldForm.value.cover) {
|
const payload = {
|
||||||
if (form.value.cover && imageUpload.value !== null) {
|
title: form.value.title,
|
||||||
const aliyunRes = await uploadToAliyun(imageUpload.value.blob, imageUpload.value.name, imageUpload.value.size, false)
|
content: form.value.content,
|
||||||
form.value.cover = aliyunRes.url
|
summary: form.value.summary,
|
||||||
|
status: form.value.status,
|
||||||
|
cover: form.value.cover,
|
||||||
|
tags: form.value.tags,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const res = isUpdate.value
|
||||||
|
? await updatePostApi({ id: form.value.id, ...payload })
|
||||||
|
: await addPostApi(payload)
|
||||||
|
|
||||||
|
if (!res.isSuccess) {
|
||||||
|
ElMessage.error(
|
||||||
|
res.message || (isUpdate.value ? '更新失败,请稍后再试' : '保存失败,请稍后再试'),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.success(isUpdate.value ? '更新成功' : '保存成功')
|
||||||
|
|
||||||
|
form.value = res.data
|
||||||
|
showPushDrawer.value = false
|
||||||
}
|
}
|
||||||
|
catch (err) {
|
||||||
if (isUpdate.value) {
|
console.error(err)
|
||||||
updatePostApi({
|
ElMessage.error(isUpdate.value ? '更新失败,请稍后再试' : '保存失败,请稍后再试')
|
||||||
id: form.value.id,
|
|
||||||
title: form.value.title,
|
|
||||||
content: form.value.content,
|
|
||||||
summary: form.value.summary,
|
|
||||||
status: form.value.status,
|
|
||||||
cover: form.value.cover,
|
|
||||||
tags: form.value.tags,
|
|
||||||
}).then((res) => {
|
|
||||||
if (res.isSuccess) {
|
|
||||||
form.value = res.data
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ElMessage.error(res.message || '更新失败,请稍后再试')
|
|
||||||
}
|
|
||||||
}).catch((err) => {
|
|
||||||
console.log(err)
|
|
||||||
ElMessage.error('更新失败,请稍后再试')
|
|
||||||
}).finally(() => {
|
|
||||||
loading.confirm = false
|
|
||||||
|
|
||||||
showPushDrawer.value = false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
else {
|
finally {
|
||||||
addPostApi({
|
loading.confirm = false
|
||||||
title: form.value.title,
|
|
||||||
content: form.value.content,
|
|
||||||
summary: form.value.summary,
|
|
||||||
status: form.value.status,
|
|
||||||
cover: form.value.cover,
|
|
||||||
tags: form.value.tags,
|
|
||||||
}).then((res) => {
|
|
||||||
if (res.isSuccess) {
|
|
||||||
form.value = res.data
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ElMessage.error(res.message || '保存失败,请稍后再试')
|
|
||||||
}
|
|
||||||
}).catch((err) => {
|
|
||||||
console.log(err)
|
|
||||||
ElMessage.error('保存失败,请稍后再试')
|
|
||||||
}).finally(() => {
|
|
||||||
loading.confirm = false
|
|
||||||
|
|
||||||
showPushDrawer.value = false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -225,7 +234,7 @@ async function handleSave(formEl: FormInstance | undefined) {
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="标签" prop="tags">
|
<el-form-item label="标签" prop="tags">
|
||||||
<el-select v-model="form.tags" placeholder="请选择标签">
|
<el-select v-model="form.tags" multiple placeholder="请选择标签">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in tagOptions"
|
v-for="item in tagOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
|
|||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import { useEcharts } from '@/hooks/echarts'
|
||||||
|
|
||||||
|
const { domRef, updateOptions } = useEcharts(() => ({
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item', // 鼠标悬停时触发提示框,显示单个数据点
|
||||||
|
formatter: '{c}',
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: [] as string[],
|
||||||
|
axisLabel: {
|
||||||
|
interval: 0, // 强制显示所有标签
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: [] as number[],
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: '40%', // 调整柱子的宽度
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}))
|
||||||
|
onMounted(() => {
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
function getData() {
|
||||||
|
getForumPostChartNumberApi().then((res) => {
|
||||||
|
const labels: string[] = res.data.map(item => item.label)
|
||||||
|
const values: number[] = res.data.map(item => item.value)
|
||||||
|
|
||||||
|
updateOptions((opts) => {
|
||||||
|
opts.xAxis.data = labels
|
||||||
|
opts.series = [
|
||||||
|
{
|
||||||
|
data: values,
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: '40%', // 调整柱子的宽度
|
||||||
|
},
|
||||||
|
]
|
||||||
|
return opts
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-card class="card-demo" title="文章发布数量" hoverable>
|
||||||
|
<template #extra>
|
||||||
|
<a-link>刷新</a-link>
|
||||||
|
</template>
|
||||||
|
<div ref="domRef" class="h-360px" />
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<a-card class="card-demo" title="文章分类" hoverable>
|
||||||
|
<template #extra>
|
||||||
|
<a-link @click="getData">刷新</a-link>
|
||||||
|
</template>
|
||||||
|
<div ref="domRef" class="h-360px overflow-hidden"></div>
|
||||||
|
</a-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useEcharts } from "@/hooks/echarts";
|
||||||
|
import { getForumPostChartTypeApi } from "@/service/forum/post";
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
|
||||||
|
const { domRef, updateOptions } = useEcharts(() => ({
|
||||||
|
title: {
|
||||||
|
text: "文章分类",
|
||||||
|
left: "center"
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: "item"
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
orient: "vertical",
|
||||||
|
left: "left"
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "文章分类",
|
||||||
|
type: "pie",
|
||||||
|
radius: "50%",
|
||||||
|
data: [] as any[], // Initial empty data
|
||||||
|
emphasis: {
|
||||||
|
itemStyle: {
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowColor: "rgba(0, 0, 0, 0.5)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 数据请求并更新图表
|
||||||
|
function getData() {
|
||||||
|
getForumPostChartTypeApi().then((res) => {
|
||||||
|
// 更新饼图的数据
|
||||||
|
updateOptions((opts) => {
|
||||||
|
opts.series[0].data = res.data.map((item) => ({
|
||||||
|
value: item.value,
|
||||||
|
name: item.label
|
||||||
|
}));
|
||||||
|
return opts;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData(); // 在组件加载时获取数据
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<a-card class="card-demo" title="文章浏览量" hoverable>
|
||||||
|
<template #extra>
|
||||||
|
<a-link @click="getData">刷新</a-link>
|
||||||
|
</template>
|
||||||
|
<div ref="domRef" class="h-360px overflow-hidden"></div>
|
||||||
|
</a-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useEcharts } from "@/hooks/echarts";
|
||||||
|
import { getForumPostChartViewApi } from "@/service/forum/post";
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
const { domRef, updateOptions } = useEcharts(() => ({
|
||||||
|
tooltip: {
|
||||||
|
trigger: "item", // 鼠标悬停时触发提示框,显示单个数据点
|
||||||
|
formatter: "{b}:{c}" // 自定义提示框显示格式:{b} 是 xAxis 的标签,{c} 是 series 的值
|
||||||
|
},
|
||||||
|
|
||||||
|
xAxis: {
|
||||||
|
type: "category",
|
||||||
|
data: [] as string[]
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: "value"
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: [] as number[],
|
||||||
|
type: "line"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData();
|
||||||
|
});
|
||||||
|
|
||||||
|
function getData() {
|
||||||
|
getForumPostChartViewApi().then((res) => {
|
||||||
|
const labels: string[] = res.data.map((item) => item.label);
|
||||||
|
const values: number[] = res.data.map((item) => item.value);
|
||||||
|
|
||||||
|
updateOptions((opts) => {
|
||||||
|
opts.xAxis.data = labels;
|
||||||
|
opts.series = [
|
||||||
|
{
|
||||||
|
data: values,
|
||||||
|
type: "line"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return opts;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
+9
-5
@@ -1,9 +1,15 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
import CustomLoading from '@/components/CustomLoading.vue'
|
import CustomLoading from '@/components/CustomLoading.vue'
|
||||||
|
import { getDashboardDataApi } from '@/request/api/post'
|
||||||
|
|
||||||
const loading = ref<boolean>(false)
|
const loading = ref<boolean>(false)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getDashboardDataApi().then((res) => {
|
||||||
|
console.log(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -67,9 +73,7 @@ const loading = ref<boolean>(false)
|
|||||||
数据报表
|
数据报表
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content" />
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { TabsPaneContext } from 'element-plus'
|
|
||||||
import type { Post, QueryPostParams } from '@/types/post/editor'
|
import type { Post, QueryPostParams } from '@/types/post/editor'
|
||||||
import { Delete, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
import { Search } from '@element-plus/icons-vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { onMounted, reactive, ref } from 'vue'
|
import { onMounted, reactive, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import CustomLoading from '@/components/CustomLoading.vue'
|
import CustomLoading from '@/components/CustomLoading.vue'
|
||||||
import SvgIcon from '@/components/SvgIcon.vue'
|
import SvgIcon from '@/components/SvgIcon.vue'
|
||||||
import { queryPostApi } from '@/request/api/post'
|
import { deletePostApi, queryPostApi, updatePostStatusApi } from '@/request/api/post'
|
||||||
import { PostStatusEnum } from '@/types/post/editor'
|
import { PostStatusEnum } from '@/types/post/editor'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -63,10 +62,6 @@ const postStatusOptions = [
|
|||||||
label: '已发布',
|
label: '已发布',
|
||||||
value: PostStatusEnum.PUBLISHED,
|
value: PostStatusEnum.PUBLISHED,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '已下架',
|
|
||||||
value: PostStatusEnum.OFFLINE,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const page = reactive<Pagination>({
|
const page = reactive<Pagination>({
|
||||||
@@ -131,18 +126,100 @@ function handleTabChange() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleDeleteRow(row: Post) {
|
function handleDeleteRow(row: Post) {
|
||||||
ElMessageBox.confirm('删除内容后不可恢复,确定删除吗?', '删除提示', {
|
ElMessageBox.confirm(
|
||||||
confirmButtonText: '确认',
|
'删除内容后不可恢复,确定删除吗?',
|
||||||
cancelButtonText: '取消',
|
'删除提示',
|
||||||
type: 'warning',
|
{
|
||||||
}).then(() => {
|
confirmButtonText: '确认',
|
||||||
// 确认删除
|
cancelButtonText: '取消',
|
||||||
})
|
type: 'warning',
|
||||||
|
showClose: false,
|
||||||
|
beforeClose: async (action, instance, done) => {
|
||||||
|
if (action !== 'confirm') {
|
||||||
|
done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
instance.confirmButtonLoading = true
|
||||||
|
|
||||||
|
const res = await deletePostApi({ id: row.id })
|
||||||
|
|
||||||
|
if (!res.isSuccess) {
|
||||||
|
ElMessage.error(res.message || '删除失败,请稍后再试')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
queryPost()
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
ElMessage.error('删除失败,请稍后再试')
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
instance.confirmButtonLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleToEdit(row: Post) {
|
function handleUpdateToDraft(row: Post) {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
'确定将该文章移入草稿箱?',
|
||||||
|
'提示',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确认',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
showClose: false,
|
||||||
|
beforeClose: async (action, instance, done) => {
|
||||||
|
if (action !== 'confirm') {
|
||||||
|
done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
instance.confirmButtonLoading = true
|
||||||
|
|
||||||
|
const res = await updatePostStatusApi({
|
||||||
|
id: row.id,
|
||||||
|
status: PostStatusEnum.DRAFT,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!res.isSuccess) {
|
||||||
|
ElMessage.error(res.message || '更新失败,请稍后再试')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.success('更新成功')
|
||||||
|
|
||||||
|
tableData.value = tableData.value.filter(
|
||||||
|
item => item.id !== row.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
ElMessage.error('更新失败,请稍后再试')
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
instance.confirmButtonLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToEdit(row?: Post) {
|
||||||
|
const query = row ? { id: row.id } : undefined
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
path: `/settings/creator/editor/${row.id}`,
|
path: '/settings/creator/editor',
|
||||||
|
query,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -197,14 +274,38 @@ function handleToEdit(row: Post) {
|
|||||||
<CustomLoading v-if="loading" :in-container="true" />
|
<CustomLoading v-if="loading" :in-container="true" />
|
||||||
|
|
||||||
<div v-else-if="tableData.length > 0" class="post-list">
|
<div v-else-if="tableData.length > 0" class="post-list">
|
||||||
<div v-for="item in tableData" :key="item.id" class="post-item">
|
<div
|
||||||
|
v-for="item in tableData"
|
||||||
|
:key="item.id"
|
||||||
|
class="post-item"
|
||||||
|
@click="handleToEdit(item)"
|
||||||
|
>
|
||||||
<div v-if="item.cover" class="post-cover">
|
<div v-if="item.cover" class="post-cover">
|
||||||
<img :src="item.cover">
|
<img :src="item.cover">
|
||||||
</div>
|
</div>
|
||||||
<div class="post-content">
|
<div class="post-content">
|
||||||
<div class="post-main">
|
<div class="post-main">
|
||||||
<div class="post-title">
|
<div class="post-title-area">
|
||||||
{{ item.title }}
|
<span class="post-title">
|
||||||
|
{{ item.title }}
|
||||||
|
</span>
|
||||||
|
<div class="post-status">
|
||||||
|
<el-tag
|
||||||
|
v-if="item.status === PostStatusEnum.DRAFT"
|
||||||
|
type="info"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
草稿
|
||||||
|
</el-tag>
|
||||||
|
|
||||||
|
<el-tag
|
||||||
|
v-else-if="item.status === PostStatusEnum.PUBLISHED"
|
||||||
|
type="success"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
已发布
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="post-actions">
|
<div class="post-actions">
|
||||||
<el-dropdown>
|
<el-dropdown>
|
||||||
@@ -215,7 +316,10 @@ function handleToEdit(row: Post) {
|
|||||||
<el-dropdown-item @click="handleToEdit(item)">
|
<el-dropdown-item @click="handleToEdit(item)">
|
||||||
编辑
|
编辑
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item v-if="item.status !== PostStatusEnum.DRAFT">
|
<el-dropdown-item v-if="item.status !== PostStatusEnum.DRAFT" @click="handleToEdit(item)">
|
||||||
|
详细
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item v-if="item.status !== PostStatusEnum.DRAFT" @click="handleUpdateToDraft(item)">
|
||||||
移入草稿箱
|
移入草稿箱
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item @click="handleDeleteRow(item)">
|
<el-dropdown-item @click="handleDeleteRow(item)">
|
||||||
@@ -247,7 +351,7 @@ function handleToEdit(row: Post) {
|
|||||||
|
|
||||||
<CustomEmpty v-else :in-container="true">
|
<CustomEmpty v-else :in-container="true">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<el-button type="primary">
|
<el-button type="primary" @click="handleToEdit()">
|
||||||
开始创作
|
开始创作
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -269,11 +373,19 @@ function handleToEdit(row: Post) {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.post-wrapper {
|
.post-wrapper {
|
||||||
padding: 0px 16px;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
.post-header {
|
||||||
|
:deep(.el-tabs__nav-scroll) {
|
||||||
|
padding: 0px 16px;
|
||||||
|
}
|
||||||
|
.post-query {
|
||||||
|
padding: 0px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.post-content {
|
.post-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -281,6 +393,7 @@ function handleToEdit(row: Post) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.post-footer {
|
.post-footer {
|
||||||
|
padding: 0px 16px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -291,11 +404,10 @@ function handleToEdit(row: Post) {
|
|||||||
.post-list {
|
.post-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-top: 1px solid #e5e7eb;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-item {
|
.post-item {
|
||||||
padding: 12px 16px;
|
padding: 12px 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -326,20 +438,35 @@ function handleToEdit(row: Post) {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.post-title {
|
.post-title-area {
|
||||||
width: 800px;
|
display: flex;
|
||||||
font-size: 20px;
|
align-items: center;
|
||||||
font-weight: bold;
|
flex: 1;
|
||||||
white-space: nowrap;
|
min-width: 0;
|
||||||
overflow: hidden;
|
.post-title {
|
||||||
text-overflow: ellipsis;
|
margin-right: 12px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-status {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-actions {
|
.post-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 16px;
|
||||||
|
|
||||||
.post-action {
|
.post-action {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
|||||||
Reference in new issue
Block a user