This commit is contained in:
oneao committed 2025-12-24 17:31:19 +08:00
1 parent e9ad523fcf
commit f310336188
39 files changed
+411 -303

No files matched your search

@@ -0,0 +1,27 @@
package cn.link56.post.controller;
import cn.link56.common.Result;
import cn.link56.post.model.vo.PostCategoryVO;
import cn.link56.post.model.vo.PostTagVO;
import cn.link56.post.service.PostCategoryService;
import cn.link56.post.service.PostTagService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/post/category")
public class PostCategoryController {
@Resource
private PostCategoryService postCategoryService;
@GetMapping
public Result<List<PostCategoryVO>> listCategories(){
List<PostCategoryVO> list = postCategoryService.listCategories();
return Result.success(list);
}
}
@@ -0,0 +1,9 @@
package cn.link56.post.mapper;
import cn.link56.post.model.po.PostCategory;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PostCategoryMapper extends BaseMapper<PostCategory> {
}
@@ -1,9 +1,9 @@
package cn.link56.post.mapper; package cn.link56.post.mapper;
import cn.link56.post.model.po.PostCommentPO; import cn.link56.post.model.po.PostComment;
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;
@Mapper @Mapper
public interface PostCommentMapper extends BaseMapper<PostCommentPO> { public interface PostCommentMapper extends BaseMapper<PostComment> {
} }
@@ -1,9 +1,9 @@
package cn.link56.post.mapper; package cn.link56.post.mapper;
import cn.link56.post.model.po.PostFavoritePO; import cn.link56.post.model.po.PostFavorite;
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;
@Mapper @Mapper
public interface PostFavoriteMapper extends BaseMapper<PostFavoritePO> { public interface PostFavoriteMapper extends BaseMapper<PostFavorite> {
} }
@@ -1,9 +1,9 @@
package cn.link56.post.mapper; package cn.link56.post.mapper;
import cn.link56.post.model.po.PostLikePO; import cn.link56.post.model.po.PostLike;
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;
@Mapper @Mapper
public interface PostLikeMapper extends BaseMapper<PostLikePO> { public interface PostLikeMapper extends BaseMapper<PostLike> {
} }
@@ -1,6 +1,6 @@
package cn.link56.post.mapper; package cn.link56.post.mapper;
import cn.link56.post.model.po.PostPO; import cn.link56.post.model.po.Post;
import cn.link56.post.model.vo.DashboardVO; 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;
@@ -10,7 +10,7 @@ import org.apache.ibatis.annotations.Param;
import java.util.List; import java.util.List;
@Mapper @Mapper
public interface PostMapper extends BaseMapper<PostPO> { public interface PostMapper extends BaseMapper<Post> {
List<QueryPostVO> selectPostPage( List<QueryPostVO> selectPostPage(
@Param("offset") int offset, @Param("offset") int offset,
@Param("limit") int limit, @Param("limit") int limit,
@@ -1,12 +1,9 @@
package cn.link56.post.mapper; package cn.link56.post.mapper;
import cn.link56.post.model.po.PostTagPO; import cn.link56.post.model.po.PostTag;
import cn.link56.post.model.vo.PostTagVO;
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;
import java.util.List;
@Mapper @Mapper
public interface PostTagMapper extends BaseMapper<PostTagPO> { public interface PostTagMapper extends BaseMapper<PostTag> {
} }
@@ -1,9 +1,9 @@
package cn.link56.post.mapper; package cn.link56.post.mapper;
import cn.link56.post.model.po.PostViewPO; import cn.link56.post.model.po.PostView;
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;
@Mapper @Mapper
public interface PostViewMapper extends BaseMapper<PostViewPO> { public interface PostViewMapper extends BaseMapper<PostView> {
} }
@@ -18,9 +18,7 @@ public class UpdatePostDTO {
private String title; private String title;
@NotBlank(message = "帖子内容不能为空") @NotBlank(message = "帖子内容不能为空")
private String content; private String content;
@NotBlank(message = "帖子摘要不能为空")
private String summary;
private String cover;
private List<String> tags; private List<String> tags;
private Integer status; private Integer status;
@@ -4,11 +4,9 @@ import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import net.sf.jsqlparser.expression.DateTimeLiteralExpression;
import java.util.Date; import java.util.Date;
@@ -16,7 +14,7 @@ import java.util.Date;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@TableName("b_post") @TableName("b_post")
public class PostPO { public class Post {
@TableId(value = "b_id") @TableId(value = "b_id")
private Long id; private Long id;
@@ -29,15 +27,9 @@ public class PostPO {
@TableField("b_content") @TableField("b_content")
private String content; private String content;
@TableField("b_summary")
private String summary;
@TableField("b_tag") @TableField("b_tag")
private String tag; private String tag;
@TableField("b_cover")
private String cover;
@TableField("b_publish_time") @TableField("b_publish_time")
private Date publishTime; private Date publishTime;
@@ -0,0 +1,23 @@
package cn.link56.post.model.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("b_post_category")
public class PostCategory {
@TableId(value = "b_id")
private Long id;
@TableField("b_name")
private String name;
@TableField("b_canuse")
private String canuse;
}
@@ -12,7 +12,7 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@TableName("b_post_comment") @TableName("b_post_comment")
public class PostCommentPO { public class PostComment {
@TableId(value = "b_id") @TableId(value = "b_id")
private Long id; private Long id;
@@ -12,7 +12,7 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@TableName("b_post_favorite") @TableName("b_post_favorite")
public class PostFavoritePO { public class PostFavorite {
@TableId(value = "b_id") @TableId(value = "b_id")
private Long id; private Long id;
@@ -12,7 +12,7 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@TableName("b_post_like") @TableName("b_post_like")
public class PostLikePO { public class PostLike {
@TableId(value = "b_id") @TableId(value = "b_id")
private Long id; private Long id;
@@ -11,7 +11,7 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@TableName("b_post_tag") @TableName("b_post_tag")
public class PostTagPO { public class PostTag {
@TableId(value = "b_id") @TableId(value = "b_id")
private Long id; private Long id;
@@ -12,7 +12,7 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@TableName("b_post_view") @TableName("b_post_view")
public class PostViewPO { public class PostView {
@TableId(value = "b_id") @TableId(value = "b_id")
private Long id; private Long id;
@@ -0,0 +1,13 @@
package cn.link56.post.model.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PostCategoryVO {
private Long id;
private String name;
}
@@ -0,0 +1,33 @@
package cn.link56.post.service;
import cn.link56.post.mapper.PostCategoryMapper;
import cn.link56.post.model.po.PostCategory;
import cn.link56.post.model.vo.PostCategoryVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class PostCategoryService extends ServiceImpl<PostCategoryMapper, PostCategory> {
@Resource
private PostCategoryMapper postCategoryMapper;
public List<PostCategoryVO> listCategories() {
LambdaQueryWrapper<PostCategory> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PostCategory::getCanuse,"1");
List<PostCategory> postCategories = postCategoryMapper.selectList(wrapper);
return postCategories.stream().map(postCategory -> {
PostCategoryVO postCategoryVO = new PostCategoryVO();
BeanUtils.copyProperties(postCategory, postCategoryVO);
return postCategoryVO;
}).collect(Collectors.toList());
}
}
@@ -1,14 +1,14 @@
package cn.link56.post.service; package cn.link56.post.service;
import cn.link56.post.mapper.PostCommentMapper; import cn.link56.post.mapper.PostCommentMapper;
import cn.link56.post.model.po.PostCommentPO; import cn.link56.post.model.po.PostComment;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@Service @Service
public class PostCommentService extends ServiceImpl<PostCommentMapper, PostCommentPO> { public class PostCommentService extends ServiceImpl<PostCommentMapper, PostComment> {
@Resource @Resource
private PostCommentMapper postCommentMapper; private PostCommentMapper postCommentMapper;
} }
@@ -1,14 +1,14 @@
package cn.link56.post.service; package cn.link56.post.service;
import cn.link56.post.mapper.PostFavoriteMapper; import cn.link56.post.mapper.PostFavoriteMapper;
import cn.link56.post.model.po.PostFavoritePO; import cn.link56.post.model.po.PostFavorite;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@Service @Service
public class PostFavoriteService extends ServiceImpl<PostFavoriteMapper, PostFavoritePO> { public class PostFavoriteService extends ServiceImpl<PostFavoriteMapper, PostFavorite> {
@Resource @Resource
private PostFavoriteMapper postFavoriteMapper; private PostFavoriteMapper postFavoriteMapper;
} }
@@ -1,14 +1,14 @@
package cn.link56.post.service; package cn.link56.post.service;
import cn.link56.post.mapper.PostLikeMapper; import cn.link56.post.mapper.PostLikeMapper;
import cn.link56.post.model.po.PostLikePO; import cn.link56.post.model.po.PostLike;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@Service @Service
public class PostLikeService extends ServiceImpl<PostLikeMapper, PostLikePO> { public class PostLikeService extends ServiceImpl<PostLikeMapper, PostLike> {
@Resource @Resource
private PostLikeMapper postLikeMapper; private PostLikeMapper postLikeMapper;
} }
@@ -3,18 +3,15 @@ package cn.link56.post.service;
import cn.link56.common.BasePageVO; import cn.link56.common.BasePageVO;
import cn.link56.common.context.UserContext; 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.post.mapper.PostMapper; import cn.link56.post.mapper.PostMapper;
import cn.link56.post.model.dto.*; import cn.link56.post.model.dto.*;
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.Post;
import cn.link56.post.model.vo.DashboardVO; 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;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; 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;
@@ -27,48 +24,48 @@ import java.util.*;
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, Post> {
@Resource @Resource
private PostMapper postMapper; private PostMapper postMapper;
@Transactional @Transactional
public EditPostVO addPost(AddPostDTO dto) { public EditPostVO addPost(AddPostDTO dto) {
PostPO postPO = new PostPO(); Post post = new Post();
BeanUtils.copyProperties(dto, postPO); BeanUtils.copyProperties(dto, post);
postPO.setId(IdGenerator.nextId()); post.setId(IdGenerator.nextId());
postPO.setCompanyId(UserContext.getCompanyId()); post.setCompanyId(UserContext.getCompanyId());
postPO.setTag(joinTags(dto.getTags())); post.setTag(joinTags(dto.getTags()));
handleUpdatePublishTime(postPO, dto.getStatus()); handleUpdatePublishTime(post, dto.getStatus());
postMapper.insert(postPO); postMapper.insert(post);
return buildEditPostVO(postPO); return buildEditPostVO(post);
} }
public EditPostVO updatePost(UpdatePostDTO dto) { public EditPostVO updatePost(UpdatePostDTO dto) {
PostPO postPO = postMapper.selectById(dto.getId()); Post post = postMapper.selectById(dto.getId());
if (postPO == null) { if (post == null) {
return null; return null;
} }
BeanUtils.copyProperties(dto, postPO); BeanUtils.copyProperties(dto, post);
postPO.setTag(joinTags(dto.getTags())); post.setTag(joinTags(dto.getTags()));
handleUpdatePublishTime(postPO, dto.getStatus()); handleUpdatePublishTime(post, dto.getStatus());
postMapper.updateById(postPO); postMapper.updateById(post);
return buildEditPostVO(postPO); return buildEditPostVO(post);
} }
public EditPostVO getDetail(Long id) { public EditPostVO getDetail(Long id) {
PostPO postPO = postMapper.selectById(id); Post post = postMapper.selectById(id);
if (postPO == null) { if (post == null) {
return null; return null;
} }
return buildEditPostVO(postPO); return buildEditPostVO(post);
} }
public BasePageVO<QueryPostVO> queryPost(QueryPostDTO dto) { public BasePageVO<QueryPostVO> queryPost(QueryPostDTO dto) {
@@ -101,14 +98,14 @@ public class PostService extends ServiceImpl<PostMapper, PostPO> {
/* ================= 私有方法 ================= */ /* ================= 私有方法 ================= */
private EditPostVO buildEditPostVO(PostPO postPO) { private EditPostVO buildEditPostVO(Post post) {
EditPostVO vo = new EditPostVO(); EditPostVO vo = new EditPostVO();
BeanUtils.copyProperties(postPO, vo); BeanUtils.copyProperties(post, vo);
vo.setTags( vo.setTags(
StringUtils.isBlank(postPO.getTag()) StringUtils.isBlank(post.getTag())
? Collections.emptyList() ? Collections.emptyList()
: Arrays.asList(StringUtils.split(postPO.getTag(), ",")) : Arrays.asList(StringUtils.split(post.getTag(), ","))
); );
return vo; return vo;
} }
@@ -119,10 +116,10 @@ public class PostService extends ServiceImpl<PostMapper, PostPO> {
: StringUtils.join(tags, ","); : StringUtils.join(tags, ",");
} }
private void handleUpdatePublishTime(PostPO postPO, Integer status) { private void handleUpdatePublishTime(Post post, Integer status) {
if (PostStatusEnum.PUBLISHED.getCode() == status if (PostStatusEnum.PUBLISHED.getCode() == status
&& postPO.getPublishTime() == null) { && post.getPublishTime() == null) {
postPO.setPublishTime(new Date()); post.setPublishTime(new Date());
} }
} }
@@ -136,13 +133,13 @@ public class PostService extends ServiceImpl<PostMapper, PostPO> {
Long id = updatePostStatusDTO.getId(); Long id = updatePostStatusDTO.getId();
Integer status = updatePostStatusDTO.getStatus(); Integer status = updatePostStatusDTO.getStatus();
PostPO postPO = postMapper.selectById(id); Post post = postMapper.selectById(id);
if (postPO == null) { if (post == null) {
return; return;
} }
postPO.setStatus(status); post.setStatus(status);
postMapper.updateById(postPO); postMapper.updateById(post);
} }
public DashboardVO getDashboardData() { public DashboardVO getDashboardData() {
@@ -184,21 +181,21 @@ public class PostService extends ServiceImpl<PostMapper, PostPO> {
} }
private List<DashboardVO.Chart> getDashboardTypeStats(Long userId) { private List<DashboardVO.Chart> getDashboardTypeStats(Long userId) {
LambdaQueryWrapper<PostPO> queryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Post> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper queryWrapper
.select(PostPO::getTag) .select(Post::getTag)
.eq(PostPO::getStatus, PostStatusEnum.PUBLISHED.getCode()) .eq(Post::getStatus, PostStatusEnum.PUBLISHED.getCode())
.eq(PostPO::getCreateBy, userId); .eq(Post::getCreateBy, userId);
List<PostPO> postPOS = postMapper.selectList(queryWrapper); List<Post> posts = postMapper.selectList(queryWrapper);
// 用 map 统计每个 tag 出现次数 // 用 map 统计每个 tag 出现次数
Map<String, Integer> tagCountMap = new HashMap<>(); Map<String, Integer> tagCountMap = new HashMap<>();
for (PostPO postPO : postPOS) { for (Post post : posts) {
if (postPO == null) continue; if (post == null) continue;
String tagsStr = postPO.getTag(); String tagsStr = post.getTag();
if (tagsStr == null || tagsStr.isEmpty()) continue; if (tagsStr == null || tagsStr.isEmpty()) continue;
// 分割逗号,并去掉空格 // 分割逗号,并去掉空格
@@ -1,7 +1,7 @@
package cn.link56.post.service; package cn.link56.post.service;
import cn.link56.post.mapper.PostTagMapper; import cn.link56.post.mapper.PostTagMapper;
import cn.link56.post.model.po.PostTagPO; import cn.link56.post.model.po.PostTag;
import cn.link56.post.model.vo.PostTagVO; import cn.link56.post.model.vo.PostTagVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -13,19 +13,19 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
public class PostTagService extends ServiceImpl<PostTagMapper, PostTagPO> { public class PostTagService extends ServiceImpl<PostTagMapper, PostTag> {
@Resource @Resource
private PostTagMapper postTagMapper; private PostTagMapper postTagMapper;
public List<PostTagVO> listTags() { public List<PostTagVO> listTags() {
LambdaQueryWrapper<PostTagPO> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<PostTag> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PostTagPO::getCanuse,"1"); wrapper.eq(PostTag::getCanuse,"1");
List<PostTagPO> postTagPOS = postTagMapper.selectList(wrapper); List<PostTag> postTags = postTagMapper.selectList(wrapper);
return postTagPOS.stream().map(postTagPO -> { return postTags.stream().map(postTag -> {
PostTagVO postTagVO = new PostTagVO(); PostTagVO postTagVO = new PostTagVO();
BeanUtils.copyProperties(postTagPO, postTagVO); BeanUtils.copyProperties(postTag, postTagVO);
return postTagVO; return postTagVO;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
@@ -1,14 +1,14 @@
package cn.link56.post.service; package cn.link56.post.service;
import cn.link56.post.mapper.PostViewMapper; import cn.link56.post.mapper.PostViewMapper;
import cn.link56.post.model.po.PostViewPO; import cn.link56.post.model.po.PostView;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@Service @Service
public class PostViewService extends ServiceImpl<PostViewMapper, PostViewPO> { public class PostViewService extends ServiceImpl<PostViewMapper, PostView> {
@Resource @Resource
private PostViewMapper postViewMapper; private PostViewMapper postViewMapper;
} }
@@ -8,7 +8,6 @@
type="cn.link56.post.model.vo.QueryPostVO"> type="cn.link56.post.model.vo.QueryPostVO">
<id property="id" column="id"/> <id property="id" column="id"/>
<result property="title" column="title"/> <result property="title" column="title"/>
<result property="cover" column="cover"/>
<!-- tags 字符串 → List<String> --> <!-- tags 字符串 → List<String> -->
<result <result
@@ -32,7 +31,6 @@
SELECT SELECT
p.b_id AS id, p.b_id AS id,
p.b_title AS title, p.b_title AS title,
p.b_cover AS cover,
p.b_tag AS tagsString, p.b_tag AS tagsString,
p.b_update_time AS updateTime, p.b_update_time AS updateTime,
p.b_publish_time AS publishTime, p.b_publish_time AS publishTime,
@@ -51,7 +51,7 @@ function onSearch() {
border-radius: 4px; border-radius: 4px;
border: 0; border: 0;
color: #000; color: #000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); // box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
background-color: #f4f4f4; background-color: #f4f4f4;
.query-item { .query-item {
flex: 1; flex: 1;
@@ -99,11 +99,9 @@ function isActive(path: string) {
<style lang="scss" scoped> <style lang="scss" scoped>
.header { .header {
position: fixed; position: sticky;
top: 0; top: 0;
right: 0; z-index: 1000;
left: 0;
z-index: 99;
background-color: #fff; background-color: #fff;
.container { .container {
@@ -112,16 +110,24 @@ function isActive(path: string) {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: 60px; height: var(--header-height);
box-shadow: 0 5px 16px rgba(0, 0, 0, 0.1); border-bottom: 1px solid rgb(232.9, 232.9, 232.9);
flex-shrink: 0;
min-width: 0;
.logo { .logo {
width: 160px; width: 160px;
height: 50px; height: 50px;
flex-shrink: 0;
min-width: 0;
} }
.menu { .menu {
display: flex; display: flex;
align-items: center; align-items: center;
flex-shrink: 0;
min-width: 0;
.menu-item { .menu-item {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -146,7 +152,8 @@ function isActive(path: string) {
&::after { &::after {
content: ''; content: '';
position: absolute; position: absolute;
bottom: -4px; bottom: 0px;
bottom: -8px;
left: 50%; left: 50%;
width: 0; width: 0;
height: 2px; height: 2px;
@@ -173,6 +180,9 @@ function isActive(path: string) {
.action { .action {
display: flex; display: flex;
align-items: center; align-items: center;
flex-shrink: 0;
min-width: 0;
.action-item { .action-item {
font-size: 20px; font-size: 20px;
margin-right: 24px; margin-right: 24px;
@@ -3,7 +3,7 @@ import Header from './components/header.vue'
</script> </script>
<template> <template>
<div class="app"> <div class="layout">
<Header /> <Header />
<div class="content"> <div class="content">
<RouterView /> <RouterView />
@@ -15,18 +15,16 @@ import Header from './components/header.vue'
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.app { .layout {
position: relative; position: relative;
} }
.content { .content {
padding-top: 60px;
} }
.footer { .footer {
margin-top: 32px; margin-top: 32px;
height: 100px; height: 100px;
background-color: #fff;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -1,32 +1,14 @@
:root {
--header-height: 60px;
}
html, html,
body { body {
background-color: #f7f7f7; background-color: #f7f7f7;
} }
body::-webkit-scrollbar {
display: none;
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
} }
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
border: 2px solid #f1f1f1;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
@@ -7,8 +7,6 @@ export interface PostEditorForm {
id: number id: number
title: string title: string
content: string content: string
summary: string
cover: string
tags: string[] tags: string[]
status: number status: number
publishTime: string publishTime: string
@@ -18,8 +16,6 @@ export interface PostEditorForm {
export interface AddPostParams { export interface AddPostParams {
title: string title: string
content: string content: string
summary: string
cover: string
status: number status: number
tags: string[] tags: string[]
} }
@@ -28,8 +24,6 @@ export interface UpdatePostParams {
id: number id: number
title: string title: string
content: string content: string
summary: string
cover: string
status: number status: number
tags: string[] tags: string[]
} }
@@ -57,7 +51,6 @@ export interface UpdatePostStatusParams {
export interface Post { export interface Post {
id: number id: number
title: string title: string
cover: string
tags: string[] tags: string[]
status: number status: number
@@ -44,8 +44,6 @@ const oldForm = ref<PostEditorForm>({
id: 0, id: 0,
title: '', title: '',
content: '', content: '',
summary: '',
cover: '',
tags: [], tags: [],
publishTime: '', publishTime: '',
updateTime: '', updateTime: '',
@@ -56,8 +54,6 @@ const form = ref<PostEditorForm>({
id: 0, id: 0,
title: '', title: '',
content: '', content: '',
summary: '',
cover: '',
tags: [], tags: [],
publishTime: '', publishTime: '',
updateTime: '', updateTime: '',
@@ -108,10 +104,6 @@ async function handleShowPushDrawer() {
loading.init = true loading.init = true
showPushDrawer.value = true showPushDrawer.value = true
if (!isUpdate.value) {
form.value.summary = (wangEditorRef.value.getText() || '').slice(0, 100)
}
await getPostTagApi().then((res) => { await getPostTagApi().then((res) => {
if (res.isSuccess) { if (res.isSuccess) {
tagOptions.value = res.data tagOptions.value = res.data
@@ -127,18 +119,6 @@ async function handleShowPushDrawer() {
loading.init = false loading.init = false
} }
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) { async function handleSave(formEl?: FormInstance) {
if (!formEl) if (!formEl)
return return
@@ -158,14 +138,10 @@ async function handleSave(formEl?: FormInstance) {
} }
try { try {
await handleCoverUploadIfNeeded()
const payload = { const payload = {
title: form.value.title, title: form.value.title,
content: form.value.content, content: form.value.content,
summary: form.value.summary,
status: form.value.status, status: form.value.status,
cover: form.value.cover,
tags: form.value.tags, tags: form.value.tags,
} }
@@ -244,7 +220,7 @@ async function handleSave(formEl?: FormInstance) {
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="文章封面 " prop="cover"> <!-- <el-form-item label="文章封面 " prop="cover">
<ImageUploadCard <ImageUploadCard
v-model="form.cover" v-model="form.cover"
@change="(data) => { @change="(data) => {
@@ -262,7 +238,7 @@ async function handleSave(formEl?: FormInstance) {
maxlength="100" maxlength="100"
type="textarea" type="textarea"
/> />
</el-form-item> </el-form-item> -->
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-select v-model="form.status" placeholder="请选择状态"> <el-select v-model="form.status" placeholder="请选择状态">
@@ -291,7 +267,6 @@ async function handleSave(formEl?: FormInstance) {
<style lang="scss" scoped> <style lang="scss" scoped>
.editor-container { .editor-container {
padding: 16px 32px;
.toolbar { .toolbar {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -1,7 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from 'vue' import type { PropType } from 'vue'
import type { DashboradChart } from '@/types/post'
import { watch } from 'vue'
import { useEcharts } from '@/hooks/echarts' import { useEcharts } from '@/hooks/echarts'
const props = defineProps({
data: {
type: Array as PropType<DashboradChart[]>,
},
})
const { domRef, updateOptions } = useEcharts(() => ({ const { domRef, updateOptions } = useEcharts(() => ({
tooltip: { tooltip: {
trigger: 'item', // 鼠标悬停时触发提示框,显示单个数据点 trigger: 'item', // 鼠标悬停时触发提示框,显示单个数据点
@@ -25,36 +33,30 @@ const { domRef, updateOptions } = useEcharts(() => ({
}, },
], ],
})) }))
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) => { watch(() => props.data, (newVal) => {
opts.xAxis.data = labels if (!newVal) {
opts.series = [ return
{ }
data: values, const labels: string[] = newVal.map(item => item.label)
type: 'bar', const values: number[] = newVal.map(item => item.value)
barWidth: '40%', // 调整柱子的宽度
}, updateOptions((opts) => {
] opts.xAxis.data = labels
return opts opts.series = [
}) {
data: values,
type: 'bar',
barWidth: '40%', // 调整柱子的宽度
},
]
return opts
}) })
} })
</script> </script>
<template> <template>
<el-card class="card-demo" title="文章发布数量" hoverable> <div ref="domRef" style="height: 360px;" />
<template #extra>
<a-link>刷新</a-link>
</template>
<div ref="domRef" class="h-360px" />
</el-card>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>
@@ -1,63 +1,66 @@
<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"> <script setup lang="ts">
import { useEcharts } from "@/hooks/echarts"; import type { PropType } from 'vue'
import { getForumPostChartTypeApi } from "@/service/forum/post"; import type { DashboradChart } from '@/types/post'
import { onMounted } from "vue"; import { watch } from 'vue'
import { useEcharts } from '@/hooks/echarts'
const props = defineProps({
data: {
type: Array as PropType<DashboradChart[]>,
default: () => [],
},
})
const { domRef, updateOptions } = useEcharts(() => ({ const { domRef, updateOptions } = useEcharts(() => ({
title: { title: {
text: "文章分类", text: '文章分类',
left: "center" left: 'center',
}, },
tooltip: { tooltip: {
trigger: "item" trigger: 'item',
}, },
legend: { legend: {
orient: "vertical", orient: 'vertical',
left: "left" left: 'left',
}, },
series: [ series: [
{ {
name: "文章分类", name: '文章分类',
type: "pie", type: 'pie',
radius: "50%", radius: '50%',
data: [] as any[], // Initial empty data data: [] as any[], // Initial empty data
emphasis: { emphasis: {
itemStyle: { itemStyle: {
shadowBlur: 10, shadowBlur: 10,
shadowOffsetX: 0, shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)" shadowColor: 'rgba(0, 0, 0, 0.5)',
} },
} },
} },
] ],
})); }))
// 数据请求并更新图表 watch(() => props.data, (newVal) => {
function getData() { if (!newVal) {
getForumPostChartTypeApi().then((res) => { return
// 更新饼图的数据 }
updateOptions((opts) => {
opts.series[0].data = res.data.map((item) => ({
value: item.value,
name: item.label
}));
return opts;
});
});
}
onMounted(() => { console.log('===type===')
getData(); // 在组件加载时获取数据 console.log(newVal)
});
// 更新饼图的数据
updateOptions((opts) => {
opts.series[0].data = newVal.map(item => ({
value: item.value,
name: item.label,
}))
return opts
})
}, { deep: true })
</script> </script>
<template>
<div ref="domRef" style="height: 360px;" />
</template>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>
@@ -1,58 +1,59 @@
<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"> <script setup lang="ts">
import { useEcharts } from "@/hooks/echarts"; import type { PropType } from 'vue'
import { getForumPostChartViewApi } from "@/service/forum/post"; import type { DashboradChart } from '@/types/post'
import { onMounted } from "vue"; import { onMounted, watch } from 'vue'
import { useEcharts } from '@/hooks/echarts'
const props = defineProps({
data: {
type: Array as PropType<DashboradChart[]>,
},
})
const { domRef, updateOptions } = useEcharts(() => ({ const { domRef, updateOptions } = useEcharts(() => ({
tooltip: { tooltip: {
trigger: "item", // 鼠标悬停时触发提示框,显示单个数据点 trigger: 'item', // 鼠标悬停时触发提示框,显示单个数据点
formatter: "{b}:{c}" // 自定义提示框显示格式:{b} 是 xAxis 的标签,{c} 是 series 的值 formatter: '{b}:{c}', // 自定义提示框显示格式:{b} 是 xAxis 的标签,{c} 是 series 的值
}, },
xAxis: { xAxis: {
type: "category", type: 'category',
data: [] as string[] data: [] as string[],
}, },
yAxis: { yAxis: {
type: "value" type: 'value',
}, },
series: [ series: [
{ {
data: [] as number[], data: [] as number[],
type: "line" type: 'line',
} },
] ],
})); }))
onMounted(() => { watch(() => props.data, (newVal) => {
getData(); if (!newVal) {
}); return
}
const labels: string[] = newVal.map(item => item.label)
const values: number[] = newVal.map(item => item.value)
function getData() { updateOptions((opts) => {
getForumPostChartViewApi().then((res) => { opts.xAxis.data = labels
const labels: string[] = res.data.map((item) => item.label); opts.series = [
const values: number[] = res.data.map((item) => item.value); {
data: values,
updateOptions((opts) => { type: 'line',
opts.xAxis.data = labels; },
opts.series = [ ]
{ return opts
data: values, })
type: "line" })
}
];
return opts;
});
});
}
</script> </script>
<template>
<div ref="domRef" style="height: 360px;" />
</template>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>
@@ -1,13 +1,44 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { DashboardData } from '@/types/post'
import { ElMessage } from 'element-plus'
import { onMounted, 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' import { getDashboardDataApi } from '@/request/api/post'
import PostNumberChart from './components/post-number-chart.vue'
import PostTypeChart from './components/post-type-chart.vue'
import PostViewChart from './components/post-view-chart.vue'
const loading = ref<boolean>(false) const loading = ref<boolean>(false)
const dashboradData = ref<DashboardData>({
allCount: {
postCount: 0,
viewCount: 0,
likeCount: 0,
favoriteCount: 0,
},
numberChart: [],
viewChart: [],
typeChart: [],
})
onMounted(() => { onMounted(() => {
loading.value = true
getDashboardDataApi().then((res) => { getDashboardDataApi().then((res) => {
console.log(res) if (res.isSuccess) {
// dashboradData.value = res.data
dashboradData.value.typeChart = [...res.data.typeChart]
console.log(dashboradData.value)
}
else {
ElMessage.error(res.message || '加载失败,请稍后再试')
}
}).catch((err) => {
console.log(err)
ElMessage.error('加载失败,请稍后再试')
}).finally(() => {
loading.value = false
}) })
}) })
</script> </script>
@@ -30,7 +61,7 @@ onMounted(() => {
文章数 文章数
</div> </div>
<div class="stat-value"> <div class="stat-value">
<el-statistic :value="98500" /> <el-statistic :value="dashboradData.allCount.postCount" />
</div> </div>
</div> </div>
</el-col> </el-col>
@@ -40,7 +71,7 @@ onMounted(() => {
浏览量 浏览量
</div> </div>
<div class="stat-value"> <div class="stat-value">
<el-statistic :value="98500" /> <el-statistic :value="dashboradData.allCount.viewCount" />
</div> </div>
</div> </div>
</el-col> </el-col>
@@ -50,7 +81,7 @@ onMounted(() => {
点赞数 点赞数
</div> </div>
<div class="stat-value"> <div class="stat-value">
<el-statistic :value="98500" /> <el-statistic :value="dashboradData.allCount.likeCount" />
</div> </div>
</div> </div>
</el-col> </el-col>
@@ -60,7 +91,7 @@ onMounted(() => {
收藏数量 收藏数量
</div> </div>
<div class="stat-value"> <div class="stat-value">
<el-statistic :value="98500" /> <el-statistic :value="dashboradData.allCount.favoriteCount" />
</div> </div>
</div> </div>
</el-col> </el-col>
@@ -73,7 +104,11 @@ onMounted(() => {
数据报表 数据报表
</div> </div>
</div> </div>
<div class="card-content" /> <div class="card-content">
<PostViewChart :data="dashboradData.viewChart" />
<PostNumberChart :data="dashboradData.numberChart" />
<PostTypeChart :data="dashboradData.typeChart" />
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -280,9 +280,6 @@ function handleToEdit(row?: Post) {
class="post-item" class="post-item"
@click="handleToEdit(item)" @click="handleToEdit(item)"
> >
<div v-if="item.cover" class="post-cover">
<img :src="item.cover">
</div>
<div class="post-content"> <div class="post-content">
<div class="post-main"> <div class="post-main">
<div class="post-title-area"> <div class="post-title-area">
@@ -112,13 +112,15 @@ watch(searchKey, async (val) => {
<template> <template>
<div class="settings-wrapper"> <div class="settings-wrapper">
<div class="left-menu"> <div class="left-menu">
<el-input <div class="menu-search">
v-model="searchKey" <el-input
placeholder="搜索菜单" v-model="searchKey"
size="large" placeholder="搜索菜单"
:prefix-icon="Search" size="large"
clearable :prefix-icon="Search"
/> clearable
/>
</div>
<el-menu <el-menu
ref="menuRef" ref="menuRef"
@@ -128,7 +130,6 @@ watch(searchKey, async (val) => {
<NavMenu :menu="filteredMenu" /> <NavMenu :menu="filteredMenu" />
</el-menu> </el-menu>
</div> </div>
<div class="right-view"> <div class="right-view">
<router-view /> <router-view />
</div> </div>
@@ -138,33 +139,51 @@ watch(searchKey, async (val) => {
<style lang="scss" scoped> <style lang="scss" scoped>
.settings-wrapper { .settings-wrapper {
display: flex; display: flex;
gap: 40px; background-color: #fff;
margin-top: 30px;
padding: 0px 240px; padding: 0px 240px;
height: 800px;
.left-menu { .left-menu {
height: calc(100vh - var(--header-height));
font-size: 16px; font-size: 16px;
overflow-y: auto;
width: 240px;
height: 100%;
background-color: #fff;
flex-shrink: 0; flex-shrink: 0;
:deep(.el-input__wrapper) { border-right: 1px solid rgb(232.9, 232.9, 232.9);
margin: 12px 12px 4px 12px; position: sticky;
top: var(--header-height);
align-self: flex-start;
padding: 12px 16px;
.menu-search {
margin-bottom: 8px;
} }
:deep(.el-menu) { :deep(.el-menu) {
border-right: none; border-right: none;
} }
:deep(.el-menu-item) {
border-radius: 8px;
margin: 4px 0px;
}
:deep(.el-menu-item.is-active) {
background-color: #d1f0ff;
color: #303133;
font-weight: 600;
}
:deep(.el-sub-menu) {
margin: 4px 0px;
}
:deep(.el-sub-menu__title) {
border-radius: 8px;
}
} }
.right-view { .right-view {
width: 1200px; flex: 1;
flex-shrink: 0; flex-shrink: 0;
overflow-y: auto;
background-color: #fff;
border-radius: 0px; border-radius: 0px;
width: 100%;
} }
} }
</style> </style>
@@ -239,8 +239,7 @@ const activeCollapseNames = ref(['1', '2', '3'])
<style lang="scss" scoped> <style lang="scss" scoped>
.company-wrapper { .company-wrapper {
padding: 24px 32px; padding-bottom: 16px;
:deep(.el-collapse) { :deep(.el-collapse) {
border: none; border: none;
} }
@@ -251,8 +250,12 @@ const activeCollapseNames = ref(['1', '2', '3'])
:deep(.el-collapse-item__header) { :deep(.el-collapse-item__header) {
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
padding: 16px;
border-bottom: 1px solid #ddd;
line-height: 0;
} }
:deep(.el-collapse-item__content) { :deep(.el-collapse-item__content) {
padding: 20px 20px 0px 20px;
padding-bottom: 0px; padding-bottom: 0px;
} }
@@ -299,7 +299,6 @@ function handleDelteRows(val?: CompanyUser) {
:total="page.total" :total="page.total"
layout="prev, pager, next" layout="prev, pager, next"
background background
size="large"
@change="getCompanyUser()" @change="getCompanyUser()"
/> />
</div> </div>
@@ -320,7 +319,6 @@ function handleDelteRows(val?: CompanyUser) {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
padding: 8px 24px 0px 24px;
:deep(.el-collapse) { :deep(.el-collapse) {
border: none; border: none;
@@ -331,10 +329,14 @@ function handleDelteRows(val?: CompanyUser) {
:deep(.el-collapse-item__header) { :deep(.el-collapse-item__header) {
font-size: 18px; font-size: 18px;
font-weight: 500; font-weight: 600;
padding: 16px;
border-bottom: 1px solid #ddd;
line-height: 0;
} }
:deep(.el-collapse-item__content) { :deep(.el-collapse-item__content) {
padding-bottom: 0px; padding-bottom: 0px;
padding: 20px 20px 0px 20px;
} }
.table-container { .table-container {
@@ -342,6 +344,7 @@ function handleDelteRows(val?: CompanyUser) {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; min-height: 0;
padding: 0px 20px;
.table-body-wrapper { .table-body-wrapper {
flex: 1; flex: 1;
@@ -357,7 +360,7 @@ function handleDelteRows(val?: CompanyUser) {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
height: 56px; margin: 20px 0px;
} }
:deep(.el-table__header) { :deep(.el-table__header) {
.el-table__cell { .el-table__cell {