提交 8a1ff3b3 编写于 作者: yubinCloud's avatar yubinCloud

Merge branch 'study' into main

# 工程简介
# Fairy Wiki —— 知识库系统
# 延伸阅读
![](https://img.shields.io/badge/license-MIT-000000.svg) ![](https://img.shields.io/badge/language-Java-orange.svg) ![](https://img.shields.io/badge/language-TypeScript-green.svg)
![Fairy2_small](https://gitee.com/yubinCloud/my-imgs-repo/raw/main/img/Fairy2_small.png)
可以在云端存储电子书、文档的知识库 Wiki 系统,一个由 **Spring Boot** + **Vue3** 搭建的全栈项目:
+ 前端 Vue CLI & Ant Design Vue 项目搭建
+ 后端 Spring Boot 搭建
## 界面设计
![FairyWikiDemo](https://gitee.com/yubinCloud/my-imgs-repo/raw/main/img/FairyWikiDemo.jpg)
+ 用户管理
+ 电子书管理
+ 文档管理
+ 分类管理
+ 富文本框的集成
+ 图形统计报表展示
+ ....
## 关键技术点
+ **axios** 解决前后端分离架构的通信问题
+ **AOP** 日志记录
+ **RocketMQ、WebSocket** 异步化实现消息通知
+ **ECharts** 用于数据统计展示
+ 定时任务设计
+ **Redis** 存储用户 token 和登陆校验
+ **Ant Design for Vue** 用于构建前端界面
+ 多环境配置文件分别用于开发和生产
+ **统一异常处理**
+ **拦截器****过滤器**
+ ......
## 启动方式
需要分别启动前端和后端
+ 后端启动方式:
+ 使用 IDEA 打开后,安装 **lombok** 插件
+ 启动 Redis
+ 启动 RocketMQ
+ 以 Maven 方式运行该 Spring Boot 项目
+ 前端启动方式:
+ 在 /web 子目录下,运行一下命令:
```bash
$ npm install
...
$ npm start serve-dev
...
```
\ No newline at end of file
......@@ -86,4 +86,18 @@ create table `user` (
unique key `login_name_unique` (`login_name`)
) engine=innodb default charset=utf8mb4 comment='用户';
insert into `user` (id, `login_name`, `name`, `password`) values (1, 'test', '测试', 'e70e2222a9d67c4f2eae107533359aa4');
\ No newline at end of file
insert into `user` (id, `login_name`, `name`, `password`) values (1, 'test', '测试', 'e70e2222a9d67c4f2eae107533359aa4');
-- 电子书快照表
drop table if exists `ebook_snapshot`;
create table `ebook_snapshot` (
`id` bigint auto_increment not null comment 'id',
`ebook_id` bigint not null default 0 comment '电子书id',
`date` date not null comment '快照日期',
`view_count` int not null default 0 comment '阅读数',
`vote_count` int not null default 0 comment '点赞数',
`view_increase` int not null default 0 comment '阅读增长',
`vote_increase` int not null default 0 comment '点赞增长',
primary key (`id`),
unique key `ebook_id_date_unique` (`ebook_id`, `date`)
) engine=innodb default charset=utf8mb4 comment='电子书快照表';
\ No newline at end of file
......@@ -24,7 +24,8 @@ public class SpringMvcConfig implements WebMvcConfigurer {
"/ebook/query",
"/doc/all/**",
"/doc/vote/**",
"/doc/read-content/**"
"/doc/read-content/**",
"/ebook-snapshot/**"
);
}
}
package io.github.yubincloud.fairywiki.controller;
import io.github.yubincloud.fairywiki.dto.resp.ErrorCode;
import io.github.yubincloud.fairywiki.dto.resp.RestfulModel;
import io.github.yubincloud.fairywiki.dto.resp.StatisticRespDto;
import io.github.yubincloud.fairywiki.service.EbookSnapshotService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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("/ebook-snapshot")
@Api("电子书快照管理")
public class EbookSnapshotController {
@Resource
private EbookSnapshotService ebookSnapshotService;
@GetMapping("/get-statistic")
@ApiOperation(value = "从电子书快照中昨天和今天的获取统计数据")
public RestfulModel<List<StatisticRespDto>> getStatistic() {
List<StatisticRespDto> statisticRespDtoList = ebookSnapshotService.getStatistic();
return new RestfulModel<>(ErrorCode.SUCCESS, "", statisticRespDtoList);
}
@GetMapping("/get-30-statistic")
@ApiOperation(value = "从电子书快照中获取近30天的统计数据")
public RestfulModel<List<StatisticRespDto>> get30DayStatistic() {
List<StatisticRespDto> statisticRespDtoList = ebookSnapshotService.get30DayStatistic();
return new RestfulModel<>(ErrorCode.SUCCESS, "", statisticRespDtoList);
}
}
package io.github.yubincloud.fairywiki.domain;
import java.util.Date;
public class EbookSnapshot {
private Long id;
private Long ebookId;
private Date date;
private Integer viewCount;
private Integer voteCount;
private Integer viewIncrease;
private Integer voteIncrease;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getEbookId() {
return ebookId;
}
public void setEbookId(Long ebookId) {
this.ebookId = ebookId;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Integer getViewCount() {
return viewCount;
}
public void setViewCount(Integer viewCount) {
this.viewCount = viewCount;
}
public Integer getVoteCount() {
return voteCount;
}
public void setVoteCount(Integer voteCount) {
this.voteCount = voteCount;
}
public Integer getViewIncrease() {
return viewIncrease;
}
public void setViewIncrease(Integer viewIncrease) {
this.viewIncrease = viewIncrease;
}
public Integer getVoteIncrease() {
return voteIncrease;
}
public void setVoteIncrease(Integer voteIncrease) {
this.voteIncrease = voteIncrease;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", ebookId=").append(ebookId);
sb.append(", date=").append(date);
sb.append(", viewCount=").append(viewCount);
sb.append(", voteCount=").append(voteCount);
sb.append(", viewIncrease=").append(viewIncrease);
sb.append(", voteIncrease=").append(voteIncrease);
sb.append("]");
return sb.toString();
}
}
\ No newline at end of file
package io.github.yubincloud.fairywiki.dto.resp;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
public class StatisticRespDto {
@JsonFormat(pattern="MM-dd", timezone = "GMT+8")
private Date date;
private int viewCount;
private int voteCount;
private int viewIncrease;
private int voteIncrease;
}
package io.github.yubincloud.fairywiki.job;
import io.github.yubincloud.fairywiki.service.EbookSnapshotService;
import io.github.yubincloud.fairywiki.utils.SnowFlake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class EbookSnapshotJob {
private static final Logger LOG = LoggerFactory.getLogger(EbookSnapshotJob.class);
@Resource
private EbookSnapshotService ebookSnapshotService;
@Resource
private SnowFlake snowFlake;
/**
* 自定义cron表达式跑批
* 只有等上一次执行完成,下一次才会在下一个时间点执行,错过就错过
*/
@Scheduled(cron = "0/59 0 0-12 * * ? ")
public void doSnapshot() {
// 增加日志流水号
MDC.put("LOG_ID", String.valueOf(snowFlake.nextId()));
LOG.info("生成今日电子书快照开始");
long start = System.currentTimeMillis();
ebookSnapshotService.genSnapshots();
LOG.info("生成今日电子书快照结束,耗时:{}毫秒", System.currentTimeMillis() - start);
}
}
package io.github.yubincloud.fairywiki.mapper;
import io.github.yubincloud.fairywiki.domain.EbookSnapshot;
import io.github.yubincloud.fairywiki.domain.EbookSnapshotExample;
import java.util.List;
import org.apache.ibatis.annotations.Param;
public interface EbookSnapshotMapper {
long countByExample(EbookSnapshotExample example);
int deleteByExample(EbookSnapshotExample example);
int deleteByPrimaryKey(Long id);
int insert(EbookSnapshot record);
int insertSelective(EbookSnapshot record);
List<EbookSnapshot> selectByExample(EbookSnapshotExample example);
EbookSnapshot selectByPrimaryKey(Long id);
int updateByExampleSelective(@Param("record") EbookSnapshot record, @Param("example") EbookSnapshotExample example);
int updateByExample(@Param("record") EbookSnapshot record, @Param("example") EbookSnapshotExample example);
int updateByPrimaryKeySelective(EbookSnapshot record);
int updateByPrimaryKey(EbookSnapshot record);
}
\ No newline at end of file
package io.github.yubincloud.fairywiki.mapper;
import io.github.yubincloud.fairywiki.dto.resp.StatisticRespDto;
import java.util.List;
public interface EbookSnapshotMapperCustom {
void genSnapshot();
List<StatisticRespDto> getStatistic();
List<StatisticRespDto> get30DayStatistic();
}
package io.github.yubincloud.fairywiki.service;
import io.github.yubincloud.fairywiki.dto.resp.StatisticRespDto;
import io.github.yubincloud.fairywiki.mapper.EbookSnapshotMapperCustom;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class EbookSnapshotService {
@Resource
private EbookSnapshotMapperCustom ebookSnapshotMapperCustom;
public void genSnapshots() {
ebookSnapshotMapperCustom.genSnapshot();
}
/**
* 获取首页数值数据:总阅读数、总点赞数、今日阅读数、今日点赞数、今日预计阅读数、今日预计阅读增长
*/
public List<StatisticRespDto> getStatistic() {
List<StatisticRespDto> statisticDataList = ebookSnapshotMapperCustom.getStatistic();
if (statisticDataList.size() < 2) {
if (statisticDataList.isEmpty()) {
statisticDataList.add(null);
statisticDataList.add(null);
} else {
statisticDataList.add(0, null);
}
}
return statisticDataList;
}
/**
* 30天数值统计
*/
public List<StatisticRespDto> get30DayStatistic() {
return ebookSnapshotMapperCustom.get30DayStatistic();
}
}
......@@ -43,6 +43,6 @@
targetPackage="io.github.yubincloud.fairywiki.mapper"
type="XMLMAPPER"/>
<table tableName="user" domainObjectName="User"/>
<table tableName="ebook_snapshot"/>
</context>
</generatorConfiguration>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.github.yubincloud.fairywiki.mapper.EbookSnapshotMapper">
<resultMap id="BaseResultMap" type="io.github.yubincloud.fairywiki.domain.EbookSnapshot">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="ebook_id" jdbcType="BIGINT" property="ebookId" />
<result column="date" jdbcType="DATE" property="date" />
<result column="view_count" jdbcType="INTEGER" property="viewCount" />
<result column="vote_count" jdbcType="INTEGER" property="voteCount" />
<result column="view_increase" jdbcType="INTEGER" property="viewIncrease" />
<result column="vote_increase" jdbcType="INTEGER" property="voteIncrease" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
<foreach collection="oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Update_By_Example_Where_Clause">
<where>
<foreach collection="example.oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Base_Column_List">
id, ebook_id, `date`, view_count, vote_count, view_increase, vote_increase
</sql>
<select id="selectByExample" parameterType="io.github.yubincloud.fairywiki.domain.EbookSnapshotExample" resultMap="BaseResultMap">
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
from ebook_snapshot
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from ebook_snapshot
where id = #{id,jdbcType=BIGINT}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from ebook_snapshot
where id = #{id,jdbcType=BIGINT}
</delete>
<delete id="deleteByExample" parameterType="io.github.yubincloud.fairywiki.domain.EbookSnapshotExample">
delete from ebook_snapshot
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</delete>
<insert id="insert" parameterType="io.github.yubincloud.fairywiki.domain.EbookSnapshot">
insert into ebook_snapshot (id, ebook_id, `date`,
view_count, vote_count, view_increase,
vote_increase)
values (#{id,jdbcType=BIGINT}, #{ebookId,jdbcType=BIGINT}, #{date,jdbcType=DATE},
#{viewCount,jdbcType=INTEGER}, #{voteCount,jdbcType=INTEGER}, #{viewIncrease,jdbcType=INTEGER},
#{voteIncrease,jdbcType=INTEGER})
</insert>
<insert id="insertSelective" parameterType="io.github.yubincloud.fairywiki.domain.EbookSnapshot">
insert into ebook_snapshot
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="ebookId != null">
ebook_id,
</if>
<if test="date != null">
`date`,
</if>
<if test="viewCount != null">
view_count,
</if>
<if test="voteCount != null">
vote_count,
</if>
<if test="viewIncrease != null">
view_increase,
</if>
<if test="voteIncrease != null">
vote_increase,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=BIGINT},
</if>
<if test="ebookId != null">
#{ebookId,jdbcType=BIGINT},
</if>
<if test="date != null">
#{date,jdbcType=DATE},
</if>
<if test="viewCount != null">
#{viewCount,jdbcType=INTEGER},
</if>
<if test="voteCount != null">
#{voteCount,jdbcType=INTEGER},
</if>
<if test="viewIncrease != null">
#{viewIncrease,jdbcType=INTEGER},
</if>
<if test="voteIncrease != null">
#{voteIncrease,jdbcType=INTEGER},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.github.yubincloud.fairywiki.domain.EbookSnapshotExample" resultType="java.lang.Long">
select count(*) from ebook_snapshot
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</select>
<update id="updateByExampleSelective" parameterType="map">
update ebook_snapshot
<set>
<if test="record.id != null">
id = #{record.id,jdbcType=BIGINT},
</if>
<if test="record.ebookId != null">
ebook_id = #{record.ebookId,jdbcType=BIGINT},
</if>
<if test="record.date != null">
`date` = #{record.date,jdbcType=DATE},
</if>
<if test="record.viewCount != null">
view_count = #{record.viewCount,jdbcType=INTEGER},
</if>
<if test="record.voteCount != null">
vote_count = #{record.voteCount,jdbcType=INTEGER},
</if>
<if test="record.viewIncrease != null">
view_increase = #{record.viewIncrease,jdbcType=INTEGER},
</if>
<if test="record.voteIncrease != null">
vote_increase = #{record.voteIncrease,jdbcType=INTEGER},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByExample" parameterType="map">
update ebook_snapshot
set id = #{record.id,jdbcType=BIGINT},
ebook_id = #{record.ebookId,jdbcType=BIGINT},
`date` = #{record.date,jdbcType=DATE},
view_count = #{record.viewCount,jdbcType=INTEGER},
vote_count = #{record.voteCount,jdbcType=INTEGER},
view_increase = #{record.viewIncrease,jdbcType=INTEGER},
vote_increase = #{record.voteIncrease,jdbcType=INTEGER}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByPrimaryKeySelective" parameterType="io.github.yubincloud.fairywiki.domain.EbookSnapshot">
update ebook_snapshot
<set>
<if test="ebookId != null">
ebook_id = #{ebookId,jdbcType=BIGINT},
</if>
<if test="date != null">
`date` = #{date,jdbcType=DATE},
</if>
<if test="viewCount != null">
view_count = #{viewCount,jdbcType=INTEGER},
</if>
<if test="voteCount != null">
vote_count = #{voteCount,jdbcType=INTEGER},
</if>
<if test="viewIncrease != null">
view_increase = #{viewIncrease,jdbcType=INTEGER},
</if>
<if test="voteIncrease != null">
vote_increase = #{voteIncrease,jdbcType=INTEGER},
</if>
</set>
where id = #{id,jdbcType=BIGINT}
</update>
<update id="updateByPrimaryKey" parameterType="io.github.yubincloud.fairywiki.domain.EbookSnapshot">
update ebook_snapshot
set ebook_id = #{ebookId,jdbcType=BIGINT},
`date` = #{date,jdbcType=DATE},
view_count = #{viewCount,jdbcType=INTEGER},
vote_count = #{voteCount,jdbcType=INTEGER},
view_increase = #{viewIncrease,jdbcType=INTEGER},
vote_increase = #{voteIncrease,jdbcType=INTEGER}
where id = #{id,jdbcType=BIGINT}
</update>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="io.github.yubincloud.fairywiki.mapper.EbookSnapshotMapperCustom" >
<!--
# 方案一(ID不连续):
# 删除今天的数据
# 为所有的电子书生成一条今天的记录
# 更新总阅读数、总点赞数
# 更新今日阅读数、今日点赞数
# 方案二(ID连续):
# 为所有的电子书生成一条今天的记录,如果还没有
# 更新总阅读数、总点赞数
# 更新今日阅读数、今日点赞数
-->
<update id="genSnapshot">
INSERT INTO ebook_snapshot(ebook_id, `date`, view_count, vote_count, view_increase, vote_increase)
SELECT t1.id, curdate(), 0, 0, 0, 0
FROM ebook t1
WHERE NOT EXISTS(SELECT 1
FROM ebook_snapshot t2
WHERE t1.id = t2.ebook_id
AND t2.`date` = curdate());
UPDATE ebook_snapshot t1, ebook t2
SET t1.view_count = t2.view_count,
t1.vote_count = t2.vote_count
WHERE t1.`date` = curdate()
AND t1.ebook_id = t2.id;
UPDATE ebook_snapshot t1 LEFT JOIN (SELECT ebook_id, view_count, vote_count
FROM ebook_snapshot
WHERE `date` = date_sub(curdate(), INTERVAL 1 DAY)) t2
ON t1.ebook_id = t2.ebook_id
SET t1.view_increase = (t1.view_count - ifnull(t2.view_count, 0)),
t1.vote_increase = (t1.vote_count - ifnull(t2.vote_count, 0))
WHERE t1.`date` = curdate();
</update>
<select id="getStatistic" resultType="io.github.yubincloud.fairywiki.dto.resp.StatisticRespDto">
SELECT
t1.`date` AS `date`,
SUM(t1.view_count) AS viewCount,
SUM(t1.vote_count) AS voteCount,
SUM(t1.view_increase) AS viewIncrease,
SUM(t1.vote_increase) AS voteIncrease
FROM
ebook_snapshot t1
WHERE
t1.`date` >= date_sub(curdate(), INTERVAL 1 DAY)
GROUP BY
t1.`date`
ORDER BY
t1.`date` ASC;
</select>
<select id="get30DayStatistic" resultType="io.github.yubincloud.fairywiki.dto.resp.StatisticRespDto">
SELECT
t1.`date` AS `date`,
SUM(t1.view_count) AS viewCount,
SUM(t1.vote_count) AS voteCount
FROM
ebook_snapshot t1
WHERE
t1.`date` >= date_sub(curdate(), INTERVAL 30 DAY)
GROUP BY
t1.`date`
ORDER BY
t1.`date` ASC;
</select>
</mapper>
\ No newline at end of file
......@@ -13,6 +13,7 @@
"@ant-design/icons-vue": "^6.0.1",
"ant-design-vue": "^2.0.0-rc.3",
"axios": "^0.21.0",
"echarts": "^5.1.1",
"vue": "^3.0.0",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0",
......
......@@ -13,7 +13,19 @@
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<div id="app">
<div style="width: 400px;
height: 100px;
position: absolute;
left: 50%;
top: 50%;
margin: -50px 0 0 -200px;font-family: YouYuan;
color: rgba(0, 0, 0, 1) !important;
font-size: 20px !important;
font-weight: 400;">
首次加载会较慢,正在进入,请等待...
</div>
</div>
<!-- built files will be auto injected -->
</body>
</html>
<template>
<a-layout-header class="header">
<div class="logo" />
<div class="logo">
<img alt="Fairy Wiki" src="../assets/fairy-wiki.png">
</div>
<a-menu
theme="dark"
mode="horizontal"
......@@ -131,6 +133,16 @@ export default defineComponent({
</script>
<style>
.logo {
width: 120px;
height: 31px;
/*background: rgba(255, 255, 255, 0.2);*/
/*margin: 16px 28px 16px 0;*/
float: left;
color: white;
font-size: 18px;
}
.login-menu {
float: right;
color: white;
......
<template>
<div>
<a-row>
<a-col :span="24">
<a-card>
<a-row>
<a-col :span="8">
<a-statistic title="总阅读量" :value="statistic.viewCount">
<template #suffix>
<UserOutlined />
</template>
</a-statistic>
</a-col>
<a-col :span="8">
<a-statistic title="总点赞量" :value="statistic.voteCount">
<template #suffix>
<like-outlined />
</template>
</a-statistic>
</a-col>
<a-col :span="8">
<a-statistic title="点赞率" :value="statistic.voteCount / statistic.viewCount * 100"
:precision="2"
suffix="%"
:value-style="{ color: '#cf1322' }">
<template #suffix>
<like-outlined />
</template>
</a-statistic>
</a-col>
</a-row>
</a-card>
</a-col>
</a-row>
<br>
<a-row :gutter="16">
<a-col :span="12">
<a-card>
<a-row>
<a-col :span="12">
<a-statistic title="今日阅读" :value="statistic.todayViewCount" style="margin-right: 50px">
<template #suffix>
<UserOutlined />
</template>
</a-statistic>
</a-col>
<a-col :span="12">
<a-statistic title="今日点赞" :value="statistic.todayVoteCount">
<template #suffix>
<like-outlined />
</template>
</a-statistic>
</a-col>
</a-row>
</a-card>
</a-col>
<a-col :span="12">
<a-card>
<a-row>
<a-col :span="12">
<a-statistic
title="预计今日阅读"
:value="statistic.todayViewIncrease"
:value-style="{ color: '#0000ff' }"
>
<template #suffix>
<UserOutlined />
</template>
</a-statistic>
</a-col>
<a-col :span="12">
<a-statistic
title="预计今日阅读增长"
:value="statistic.todayViewIncreaseRateAbs"
:precision="2"
suffix="%"
class="demo-class"
:value-style="statistic.todayViewIncreaseRate < 0 ? { color: '#3f8600' } : { color: '#cf1322' }"
>
<template #prefix>
<arrow-down-outlined v-if="statistic.todayViewIncreaseRate < 0"/>
<arrow-up-outlined v-if="statistic.todayViewIncreaseRate >= 0"/>
</template>
</a-statistic>
</a-col>
</a-row>
</a-card>
</a-col>
</a-row>
<br>
<a-row>
<a-col :span="24">
<div id="chart" style="width: 100%;height:300px;"></div>
</a-col>
</a-row>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'
import axios from 'axios';
import * as echarts from 'echarts/core';
import {
BarChart,
// 系列类型的定义后缀都为 SeriesOption
BarSeriesOption,
LineChart,
LineSeriesOption
} from 'echarts/charts';
import {
TitleComponent,
// 组件类型的定义后缀都为 ComponentOption
TitleComponentOption,
GridComponent,
GridComponentOption,
TooltipComponent
} from 'echarts/components';
import {
CanvasRenderer
} from 'echarts/renderers';
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
type ECOption = echarts.ComposeOption<
BarSeriesOption | LineSeriesOption | TitleComponentOption | GridComponentOption
>;
// 注册必须的组件
echarts.use(
[TitleComponent, TooltipComponent, GridComponent, BarChart, CanvasRenderer]
);
export default defineComponent({
name: 'the-welcome',
setup () {
const statistic = ref();
statistic.value = {};
/**
* 生成昨天和今天的数据统计表格
*/
const genTwoDayStatisticTable = () => {
axios.get('/ebook-snapshot/get-statistic').then((response) => {
const respData = response.data;
if (respData.code === 0) {
const statisticData = respData.data;
statistic.value.viewCount = statisticData[1].viewCount;
statistic.value.voteCount = statisticData[1].voteCount;
statistic.value.todayViewCount = statisticData[1].viewIncrease;
statistic.value.todayVoteCount = statisticData[1].voteIncrease;
// 按分钟计算当前时间点,占一天的百分比
const now = new Date();
const nowRate = (now.getHours() * 60 + now.getMinutes()) / (60 * 24);
// console.log(nowRate)
statistic.value.todayViewIncrease = parseInt(String(statisticData[1].viewIncrease / nowRate));
// todayViewIncreaseRate:今日预计增长率
statistic.value.todayViewIncreaseRate = (statistic.value.todayViewIncrease - statisticData[0].viewIncrease) / statisticData[0].viewIncrease * 100;
statistic.value.todayViewIncreaseRateAbs = Math.abs(statistic.value.todayViewIncreaseRate);
}
});
};
const init30DayEcharts = (list: any) => {
// 基于准备好的dom,初始化echarts实例
const statisticChart = echarts.init(document.getElementById('chart') as HTMLCanvasElement);
const xAxis = [];
const seriesView = [];
const seriesVote = [];
for (let i = 0; i < list.length; i++) {
const record = list[i];
xAxis.push(record.date);
seriesView.push(record.viewIncrease);
seriesVote.push(record.voteIncrease);
}
// 指定图表的配置项和数据
const option = {
title: {
text: '30天趋势图'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['总阅读量', '总点赞量']
},
grid: {
left: '1%',
right: '3%',
bottom: '3%',
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xAxis
},
yAxis: {
type: 'value'
},
series: [
{
name: '总阅读量',
type: 'line',
// stack: '总量', 不堆叠
data: seriesView,
smooth: true
},
{
name: '总点赞量',
type: 'line',
// stack: '总量', 不堆叠
data: seriesVote,
smooth: true
}
]
};
// 使用刚指定的配置项和数据显示图表。
statisticChart.setOption(option);
};
/**
* 生成近30天的数据统计图
*/
const gen30DayStatisticChart = () => {
axios.get('/ebook-snapshot/get-30-statistic').then((response) => {
const respData = response.data;
if (respData.code === 0) {
const statisticList = respData.data;
init30DayEcharts(statisticList);
}
});
};
onMounted(() => {
genTwoDayStatisticTable();
gen30DayStatisticChart();
});
return {
statistic
}
}
});
</script>
\ No newline at end of file
......@@ -274,7 +274,7 @@ export default defineComponent({
handleQueryContent();
// 不能选择当前节点及其所有子孙节点,作为父节点,会使树断开
treeSelectData.value = Tool.copy(level1.value);
treeSelectData.value = Tool.copy(level1.value) || [];
setDisable(treeSelectData.value, record.id);
// 为选择树添加一个"无"
......@@ -290,7 +290,7 @@ export default defineComponent({
doc.value = {
ebookId: route.query.ebookId
};
treeSelectData.value = Tool.copy(level1.value);
treeSelectData.value = Tool.copy(level1.value) || [];
// 为选择树添加一个"无"
treeSelectData.value.unshift({id: 0, name: ''});
......
......@@ -205,4 +205,10 @@ export default defineComponent({
padding: 15px;
text-align: center;
}
/* 图片自适应 */
.wangeditor img {
max-width: 100%;
height: auto;
}
</style>
\ No newline at end of file
......@@ -24,7 +24,7 @@
:style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"
>
<div class="welcome" v-show="isShowWelcome">
<h1>欢迎进入 Fairy Wiki</h1>
<the-welcome></the-welcome>
</div>
<a-list v-show="!isShowWelcome" item-layout="vertical" size="large"
:grid="{ gutter: 20, column: 3 }" :data-source="ebooks">
......@@ -66,11 +66,13 @@ import { StarOutlined, LikeOutlined, MessageOutlined } from '@ant-design/icons-v
import {Tool} from "@/util/tool";
import {message} from "_ant-design-vue@2.0.0-rc.3@ant-design-vue";
import {Category} from "@/models";
import TheWelcome from "@/components/the-welcome.vue"
export default defineComponent({
name: 'Home',
components: {
TheWelcome,
StarOutlined,
LikeOutlined,
MessageOutlined,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册