[TOC]
blog_wolf - 后端
项目开发文档
- 如果你看见该项目却不知所措,那么可以参照该开发文档一步步走下去,那样就可以完全复现并理解该项目。
新建springboot项目
构建数据库(blog_wolf)
t_blog(博客表)
t_category(分类表)
t_user(用户表)
配置application.yml
# 后台访问端口配置
server:
port: 8081
# 数据库连接配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3307/blog_wolf?serverTimezone=UTC
username: root
password: 1234
# mybatis配置
mybatis:
mapper-locations: classpath:mapper/*.xml # 定位mapper文件的位置,当xml文件和mapper接口路径一致时可以不用配置
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 标准日志输出
map-underscore-to-camel-case: true # 开启下划线转驼峰
type-aliases-package: com.li.entity # 实体类起别名
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.li</groupId>
<artifactId>blog</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>blog</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 引入lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 引入mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.li.BlogApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
新建包
entity包内新建实体类
实体类Blog
package com.li.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Blog {
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Integer getViews() {
return views;
}
public void setViews(Integer views) {
this.views = views;
}
public String getPublished() {
return published;
}
public void setPublished(String published) {
this.published = published;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getTitleImage() {
return titleImage;
}
public void setTitleImage(String titleImage) {
this.titleImage = titleImage;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
private String id;
private String title;
private String description;
private String content;
private Integer views;
private String published;
private Date createTime;
private String titleImage;
// 实体类属性
private Category category;
private User user;
}
实体类Category
package com.li.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Category {
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Blog> getBlogs() {
return blogs;
}
public void setBlogs(List<Blog> blogs) {
this.blogs = blogs;
}
// 基本属性
private String id;
private String name;
// 实体类属性
private List<Blog> blogs = new ArrayList<>();
}
实体类User
package com.li.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
public class User {
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public List<Blog> getBlogs() {
return blogs;
}
public void setBlogs(List<Blog> blogs) {
this.blogs = blogs;
}
private String id;
private String username;
private String password;
private String nickname;
private String avatar;
private String role;
private List<Blog> blogs = new ArrayList<>();
}
mapper包下新建mapper类(接口类_intetface)
@Mapper注解
的作用相当于在mybatis-config.xml中注册mapper.xml文件。@Param注解
用来标识传递给sql语句的参数名称是什么。
BlogMapper
package com.li.mapper;
import com.li.entity.Blog;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface BlogMapper {
List<Blog> selectAll();
List<Blog> selectPage(@Param("currentPage") Integer currentPage,
@Param("pageSize") Integer pageSize,
@Param("title") String title
);
Integer selectCount(@Param("title") String title);
Blog selectById(String id);
Integer insertOne(Blog Blog);
Integer updateOne(Blog Blog);
Integer deleteById(@Param("id") String id);
}
UserMapper
package com.li.mapper;
import com.li.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper {
List<User> selectAll();
List<User> selectPage(@Param("pageNum") Integer pageNum,
@Param("pageSize") Integer pageSize,
@Param("username") String username,
@Param("role") String role
);
Integer selectCount(@Param("username") String username,
@Param("role") String role);
User selectById(@Param("id") String id);
Integer insertOne(User user);
Integer updateOne(User user);
Integer deleteById(@Param("id") String id);
}
CategoryMapper
package com.li.mapper;
import com.li.entity.Category;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface CategoryMapper {
List<Category> selectAll();
List<Category> selectPage(@Param("currPage") Integer currPage,
@Param("pageSize") Integer pageSize,
@Param("categoryName") String categoryName
);
Integer selectCount(@Param("categoryName") String categoryName);
Category selectById(@Param("id") String id);
Integer insertOne(Category Category);
Integer updateOne(Category Category);
Integer deleteById(@Param("id") String id);
}
mybatis自动生成SQL(mybatisX-Generator)
- 这些mapper在resource目录的mapper目录下。
BlogMapper.xml
<?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="com.li.mapper.BlogMapper">
<insert id="insertOne">
insert into t_blog (id, title, description, content, views, published, create_time, title_image, category_id, user_id)
values (#{id}, #{title}, #{description}, #{content}, #{views}, #{published}, #{createTime}, #{titleImage}, #{category.id}, #{user.id});
</insert>
<update id="updateOne">
update t_blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="description != null">
description = #{description},
</if>
<if test="content != null">
content = #{content},
</if>
<if test="views != null">
views = #{views},
</if>
<if test="published != null">
published = #{published},
</if>
<if test="createTime != null">
create_time = #{createTime},
</if>
<if test="titleImage != null">
title_image = #{titleImage},
</if>
<if test="category != null">
category_id = #{category.id},
</if>
<if test="user != null">
user_id = #{user.id}
</if>
</set>
<where>
id = #{id}
</where>
</update>
<delete id="deleteById">
delete from t_blog where id = #{id}
</delete>
<select id="selectAll" resultMap="BlogsResultMap">
select tb.id blog_id,
tb.title blog_title,
tb.description blog_description,
tb.content blog_content,
tb.views blog_views,
tb.published blog_published,
tb.create_time blog_create_time,
tb.title_image blog_title_image,
tc.id category_id,
tc.name category_name,
tu.id user_id,
tu.username user_username
from t_blog tb
left outer join t_category tc on tb.category_id = tc.id
left outer join t_user tu on tb.user_id = tu.id
order by blog_create_time desc
</select>
<resultMap id="BlogsResultMap" type="Blog">
<id property="id" column="blog_id"></id>
<result property="title" column="blog_title"></result>
<result property="description" column="blog_description"></result>
<result property="content" column="blog_content"></result>
<result property="views" column="blog_views"></result>
<result property="published" column="blog_published"></result>
<result property="createTime" column="blog_create_time"></result>
<result property="titleImage" column="blog_title_image"></result>
<association property="category" javaType="Category">
<id property="id" column="category_id"></id>
<result property="name" column="category_name"></result>
</association>
<association property="user" javaType="com.li.entity.User">
<id property="id" column="user_id"></id>
<result property="username" column="user_username"></result>
</association>
</resultMap>
<select id="selectById" resultMap="BlogResultMap">
select tb.id blog_id,
tb.title blog_title,
tb.description blog_description,
tb.content blog_content,
tb.views blog_views,
tb.published blog_published,
tb.create_time blog_create_time,
tc.id category_id,
tc.name category_name,
tu.id user_id,
tu.username user_username,
tu.nickname user_nickname,
tu.avatar user_avatar,
tu.role user_role
from t_blog tb
left outer join t_category tc on tb.category_id = tc.id
left outer join t_user tu on tb.user_id = tu.id
where tb.id = #{id}
</select>
<select id="selectPage" resultMap="BlogsResultMap">
select tb.id blog_id,
tb.title blog_title,
tb.description blog_description,
tb.content blog_content,
tb.views blog_views,
tb.published blog_published,
tb.create_time blog_create_time,
tb.title_image blog_title_image,
tc.id category_id,
tc.name category_name,
tu.id user_id,
tu.username user_username
from t_blog tb
left outer join t_category tc on tb.category_id = tc.id
left outer join t_user tu on tb.user_id = tu.id
<where>
<if test="title != null">
title like "%"#{title}"%"
</if>
</where>
limit #{currentPage}, #{pageSize}
</select>
<select id="selectCount" resultType="int">
select count(*) from t_blog
<where>
<if test="title != null">
title like "%"#{title}"%"
</if>
</where>
</select>
<resultMap id="BlogResultMap" type="Blog">
<id property="id" column="blog_id"/>
<result property="title" column="blog_title"/>
<result property="description" column="blog_description"/>
<result property="content" column="blog_content"/>
<result property="views" column="blog_views"/>
<result property="published" column="blog_published"/>
<result property="createTime" column="blog_create_time"/>
<association property="category" javaType="Category">
<id property="id" column="category_id"/>
<result property="name" column="category_name"/>
</association>
<association property="user" javaType="User">
<id property="id" column="user_id"/>
<result property="username" column="user_username"/>
<result property="nickname" column="user_nickname"/>
<result property="avatar" column="user_avatar"/>
<result property="role" column="user_role"/>
</association>
</resultMap>
</mapper>
UserMapper.xml
<?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">
<!--绑定UserMapper接口-->
<mapper namespace="com.li.mapper.UserMapper">
<!-- 查询所有用户-->
<select id="selectAll" resultType="com.li.entity.User">
select * from t_user
</select>
<!-- 分页查询-->
<select id="selectPage" resultType="com.li.entity.User">
select * from t_user
<where>
<if test="username != ''">
username like "%"#{username}"%"
</if>
<if test="role != ''">
and role like "%"#{role}"%"
</if>
</where>
limit #{pageNum}, #{pageSize}
</select>
<!-- 根据Id查询用户-->
<select id="selectById" resultType="com.li.entity.User">
select * from t_user where id=#{id}
</select>
<!-- 查询该表总数据条数-->
<select id="selectCount" resultType="int">
select count(*) from t_user
<where>
<if test="username != ''">
username like "%"#{username}"%"
</if>
<if test="role != ''">
and role like "%"#{role}"%"
</if>
</where>
</select>
<!-- 插入一条数据-->
<insert id="insertOne" parameterType="com.li.entity.User">
insert into t_user(id, username, password, nickname, avatar, role)
values (#{id}, #{username}, #{password}, #{nickname}, #{avatar}, #{role})
</insert>
<!-- 根据Id更新数据-->
<update id="updateOne" parameterType="com.li.entity.User">
update t_user
<set>
<if test="username != null">
username=#{username},
</if>
<if test="password != null">
password=#{password},
</if>
<if test="nickname != null">
nickname=#{nickname},
</if>
<if test="avatar != null">
avatar=#{avatar},
</if>
<if test="role != null">
role=#{role}
</if>
</set>
<where>
id=#{id}
</where>
</update>
<!-- 根据Id删除数据-->
<delete id="deleteById">
delete from t_user where id=#{id}
</delete>
</mapper>
CategoryMapper.xml
<?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="com.li.mapper.CategoryMapper">
<insert id="insertOne">
insert into t_category(id, name) values (#{id}, #{name})
</insert>
<update id="updateOne">
update t_category
<set>
<if test="name != null">
name = #{name}
</if>
</set>
<where>
id = #{id}
</where>
</update>
<delete id="deleteById">
delete from t_category where id = #{id}
</delete>
<select id="selectAll" resultType="Category">
select * from t_category
</select>
<select id="selectById" resultType="Category" parameterType="string">
select * from t_category where id = #{id}
</select>
<select id="selectPage" resultType="Category">
select * from t_category
<where>
<if test="categoryName != null">
name like "%"#{categoryName}"%"
</if>
</where>
limit #{currPage}, #{pageSize}
</select>
<select id="selectCount" resultType="int">
select count(*) from t_category
<where>
<if test="categoryName != null">
name like "%"#{categoryName}"%"
</if>
</where>
</select>
</mapper>
在service包下新建service类
@Service注解
用于类上,标记当前类是一个service类,加上该注解会将当前类自动注入到spring容器中,不需要再在applicationContext.xml文件定义bean了。@Autowired
是一种注解,可以对成员变量、方法和构造函数进行标注,来完成自动装配的工作。- 查询全部博客 —— selectAll方法
- 进行分页 —— selectPage
- 汇总 —— selectCount
- 根据Id查询博客 —— selectById
- 保存博客 —— save、saveBlog
检查博客中是否存在分类属性,进行新增
- checkBlogWithCategory
- 新增分类并拿到新增之后的category_id,判断是否为空
检查博客中是否存在用户属性,进行新增
- checkBlogWithUser
- 新增用户并拿到新增之后的user_id,判断是否为空
- 删除 —— deleteById
BlogService
package com.li.service;
import com.li.common.IDUtils;
import com.li.common.Result;
import com.li.entity.Blog;
import com.li.entity.Category;
import com.li.entity.User;
import com.li.mapper.BlogMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
@Service
public class BlogService {
@Autowired
private BlogMapper blogMapper;
@Autowired
private CategoryService categoryService;
@Autowired
private UserService userService;
//查询全部博客
public Result selectAll(){
List<Blog> blogs = blogMapper.selectAll();
if (blogs.size() == 0){
return new Result().error();
}else{
return new Result().success().set("blogs",blogs);
}
}
public Result selectPage(Integer currentPage,Integer pageSize, String name){
int startIndex = (currentPage - 1) * pageSize;
List<Blog> blogList = blogMapper.selectPage(startIndex,pageSize,name);
if (blogList != null){
return new Result().success().set("blogList",blogList);
}
return new Result().error();
}
public Result selectCount(String title){
Integer count = blogMapper.selectCount(title);
if (count != null){
return new Result().success().set("count",count);
}
return new Result().error();
}
// 根据Id查询博客
public Result selectById(String id){
Blog blog = blogMapper.selectById(id);
if (blog != null){
return new Result().success().set("blog",blog);
}else{
return new Result().error();
}
}
//保存博客
public Result save(Blog blog){
blog.setCreateTime(new Date());
Category category = (Category) blog.getCategory();
User user = blog.getUser();
if (category != null){
blog = checkBlogWithUser(blog);
}
if (user != null){
blog = checkBlogWithUser(blog);
}
return saveBlog(blog);
}
public Result saveBlog(Blog blog){
int affectRow;
if (blog.getId() == null){
blog.setId(IDUtils.getUUID());
affectRow = blogMapper.insertOne(blog);
}else{
affectRow = blogMapper.updateOne(blog);
}
if (affectRow > 0){
return new Result().success();
}else{
return new Result().error();
}
}
// 检查博客中是否存在分类属性,进行新增
public Blog checkBlogWithCategory(Blog blog){
Category category = (Category) blog.getCategory();
// 新增分类并拿到新增之后的category_id
if (category.getId() == null){
String category_id = (String) categoryService.save(category).getData().get("id");
category.setId(category_id);
}
return blog;
}
// 检查博客中是否存在用户属性,进行新增
public Blog checkBlogWithUser(Blog blog){
User user = blog.getUser();
// 新增用户并拿到新增之后的user_id
if (user.getId() == null){
String user_id = (String) userService.save(user).getData().get("id");
user.setId(user_id);
}
return blog;
}
public Result deleteById(String id){
int affectRow = blogMapper.deleteById(id);
if (affectRow > 0){
return new Result().success();
}else{
return new Result().error();
}
}
}
CategoryService
后端的categoryService有误,导致分类加载不出来。
- categoryList写成首字母大写了,应该是小写。
- 查询全部分类 —— selectAll方法
- 保存分类 —— save
- IDUtils工具类提供方法,生成随机id
- 进行分页 —— selectPage
- 汇总 —— selectCount
- 根据Id查询分类 —— selectById
- 删除 —— deleteById
package com.li.service;
import com.li.common.IDUtils;
import com.li.common.Result;
import com.li.entity.Category;
import com.li.mapper.CategoryMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CategoryService {
@Autowired
private CategoryMapper categoryMapper;
public Result selectAll(){
List<Category> categoryList = categoryMapper.selectAll();
return new Result().success().set("categoryList",categoryList);
}
public Result save(Category category){
int affectRow;
String id = IDUtils.getUUID();
if (category.getId() == null){
category.setId(id);
affectRow = categoryMapper.insertOne(category);
}else{
affectRow = categoryMapper.updateOne(category);
id = category.getId();
}
if (affectRow > 0){
return new Result().success().set("id",id);
}else {
return new Result().error();
}
}
public Result selectPage(Integer currPage,Integer pageSize, String categoryName){
int startIndex = (currPage - 1) * pageSize;
List<Category> categoryList = categoryMapper.selectPage(startIndex,pageSize,categoryName);
if (categoryList != null){
return new Result().success().set("categoryList",categoryList);
}
return new Result().error();
}
public Result selectCount(String name){
Integer count = categoryMapper.selectCount(name);
if (count != null){
return new Result().success().set("count",count);
}
return new Result().error();
}
public Result selectById(String id){
Category category = categoryMapper.selectById(id);
if (category != null){
return new Result().success().set("category",category);
}else{
return new Result().error();
}
}
public Result deleteById(String id){
int affectRow = categoryMapper.deleteById(id);
if (affectRow > 0){
return new Result().success();
}else{
return new Result().error();
}
}
}
UserService
- 保存用户信息 —— save
- IDUtils工具类提供方法,生成随机id
- 查询全部用户 —— selectAll方法
- 汇总 —— selectCount
- 根据Id查询用户 —— selectById
- 进行分页 —— selectPage
- 删除 —— deleteById
package com.li.service;
import com.li.common.IDUtils;
import com.li.common.Result;
import com.li.entity.User;
import com.li.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public Result save(User user){
int affectRow;
String id = IDUtils.getUUID();
if (user.getId() == null){
user.setId(id);
affectRow = userMapper.insertOne(user);
}else{
affectRow = userMapper.updateOne(user);
id = user.getId();
}
if (affectRow > 0){
return new Result().success().set("id",id);
}else {
return new Result().error();
}
}
public List<User> selectAll(){
return userMapper.selectAll();
}
public Integer selectCount(String username, String role){
return userMapper.selectCount(username, role);
}
public List<User> selectPage(Integer pageNum, Integer pageSize, String username, String role){
return userMapper.selectPage(pageNum,pageSize,username, role);
}
public Result deleteById(String id){
int affectRow = userMapper.deleteById(id);
if (affectRow == 0){
return new Result().success();
}else{
return new Result().error();
}
}
public User selectById(String id){
return userMapper.selectById(id);
}
}
在controller包下新建controller类
@PathVariable
注解:用于接收请求路径中占位符的值。@RequestBody
注解:将前台传过来的json对象转换为后台的Java对象@RestController
等于这两个注解@Controller、@ResponseBody
RequestMapping
是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。- 查询全部博客 —— selectAll方法
- 进行分页 —— selectPage
- 根据Id查询博客 —— selectById
- 保存博客信息 —— save
- 删除 —— deleteById
BlogController
package com.li.controller;
import com.li.common.Result;
import com.li.entity.Blog;
import com.li.service.BlogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/blogs")
public class BlogController {
@Autowired
private BlogService blogService;
@GetMapping
public Result selectAll(){
return blogService.selectAll();
}
@GetMapping("/")
public Result selectPage(
@RequestParam(value = "currentPage",defaultValue = "1",required = true) Integer currentPage,
@RequestParam(value = "pageSize",defaultValue = "10",required = true) Integer pageSize,
@RequestParam(value = "title",defaultValue = "",required = false) String title)
{
Result pageResult = blogService.selectPage(currentPage,pageSize,title);
Result countResult = blogService.selectCount(title);
if (pageResult.getState().equals("200") && countResult.getState().equals("200")){
return new Result().success().set("blogList",pageResult.getData().get("blogList"))
.set("count",countResult.getData().get("count"));
}
return new Result().error();
}
@GetMapping("/{id}")
public Result selectById(@PathVariable("id") String id){
return blogService.selectById(id);
}
@PostMapping
public Result save(@RequestBody Blog blog){
return blogService.save(blog);
}
@DeleteMapping("/{id}")
public Result deleteById(@PathVariable("id") String id){
return blogService.deleteById(id);
}
}
CategoryController
- 查询全部分类 —— selectAll方法
- 进行分页 —— selectPage
- 根据Id查询分类 —— selectById
- 保存分类信息 —— save
- 删除 —— deleteById
package com.li.controller;
import com.li.common.Result;
import com.li.entity.Blog;
import com.li.entity.Category;
import com.li.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/categorys")
public class CategoryController {
@Autowired
public CategoryService categoryService;
@GetMapping
public Result selectAll(){
return categoryService.selectAll();
}
@GetMapping("/")
public Result selectPage(
@RequestParam(value = "currPage",defaultValue = "1",required = true) Integer currPage,
@RequestParam(value = "pageSize",defaultValue = "2",required = true) Integer pageSize,
@RequestParam(value = "categoryName",defaultValue = "",required = false) String categoryName)
{
Result pageResult = categoryService.selectPage(currPage,pageSize,categoryName);
Result countResult = categoryService.selectCount(categoryName);
if (pageResult.getState().equals("200") && countResult.getState().equals("200")){
return new Result().success().set("categoryList",pageResult.getData().get("categoryList"))
.set("count",countResult.getData().get("count"));
}
return new Result().error();
}
@GetMapping("/{id}")
public Result selectById(@PathVariable String id){
return categoryService.selectById(id);
}
@PostMapping
public Result save(@RequestBody Category category){
return categoryService.save(category);
}
@DeleteMapping("/{id}")
public Result deleteById(@PathVariable String id){
return categoryService.deleteById(id);
}
}
UserController
- 查询全部用户 —— selectAll方法
- 进行分页 —— selectPage
- 根据Id查询用户 —— selectById
- 保存用户信息 —— save
- 删除 —— deleteById
package com.li.controller;
import com.li.common.Result;
import com.li.entity.User;
import com.li.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
public UserService userService;
@GetMapping
public List<User> selectAll(){
return userService.selectAll();
}
@GetMapping("/")
public Result selectPage(
@RequestParam(value = "pageNum",defaultValue = "1",required = true) Integer pageNum,
@RequestParam(value = "pageSize",defaultValue = "10",required = true) Integer pageSize,
@RequestParam(value = "username",defaultValue = "",required = false) String username,
@RequestParam(value = "role",defaultValue = "",required = false) String role)
{
int startIndex = (pageNum - 1) * pageSize;
int total = userService.selectCount(username,role);
List<User> userList = userService.selectPage(startIndex,pageSize,username,role);
Result result = new Result().success();
result.set("total",total).set("userList",userList);
return result;
}
@GetMapping("/{id}")
public User selectById(@PathVariable("id") String id){
return userService.selectById(id);
}
@PostMapping
public Result save(@RequestBody User user){
return userService.save(user);
}
@DeleteMapping("/{id}")
public Result deleteById(@PathVariable("id") String id){
return userService.deleteById(id);
}
}
在common包下新建Result类
- 通用Result类模板代码
- 返回状态码信息
package com.li.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.Map;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
// 返回状态码,200表示ok,400表示错误
private String state;
// 返回操作信息
private String message;
// 返回的数据
private Map<String, Object> data = new HashMap<>();
public Result success() {
return new Result("200", "操作成功", this.data);
}
public Result set(String key, Object value) {
this.getData().put(key, value);
return this;
}
public Result error() {
return new Result("400", "操作失败", this.data);
}
}
在common包下新建IDUtils类
- IDUtils工具类提供方法,生成随机id
package com.li.common;
import java.util.UUID;
public class IDUtils {
public static String getUUID() {
return UUID.randomUUID().toString().replace("-", "");
}
}
进行跨域配置
在config包下新建CorsConfig类 —— 跨域配置
package com.li.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
//进行跨域配置
public class CorsConfig {
// 当前跨域请求最大有效时长。这里默认1天
private static final long MAX_AGE = 24 * 60 * 60;
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址
corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
corsConfiguration.setMaxAge(MAX_AGE);
source.registerCorsConfiguration("/**", corsConfiguration); // 4 对接口配置跨域设置
return new CorsFilter(source);
}
}
blog_wolf - 前端(管理端)
- axios
- core-js
- element-ui
- mavon-editor
- pubsub-js —— 订阅消息和发布消息(控制台)
vue-router —— Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。
- 跳转页面
- vue2
配置package.json
{
"name": "admin",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --open",
"build": "vue-cli-service build"
},
"dependencies": {
"axios": "^0.26.0",
"core-js": "^3.6.5",
"element-ui": "^2.15.6",
"mavon-editor": "^2.10.4",
"pubsub-js": "^1.9.4",
"vue": "^2.6.11",
"vue-router": "^3.2.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"vue-template-compiler": "^2.6.11"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
在assets文件夹中设置全局样式
all.css
/* 去掉body的边框,让body充满整个屏幕 */
*{
margin: 0;
padding: 0;
}
/* 设置高度100%,撑满整个屏幕*/
html, body {
height: 100%;
}
/* 设置统一class */
.ml-5 {
margin-left: 5px;
}
.mr-5 {
margin-right: 5px;
}
.pd-10 {
padding: 10px;
}
配置main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// 引入element ui
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
// 引入mavon-editor
import mavonEditor from 'mavon-editor';
import 'mavon-editor/dist/css/index.css';
// use _ element ui
//并设置size属性的组件的默认尺寸均为small
Vue.use(ElementUI,{size:small});
// use _ mavon-editor
Vue.use(mavonEditor);
// 全局样式
import './assets/all.css'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')
配置axios
新建utils文件夹和request.js
import axios from "axios";
const request = axios.create({
baseURL:'http://localhost:8081',
timeout:5000
})
export default request
新建router文件夹和index.js
- vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。路由模块的本质 就是建立起url和页面之间的映射关系。
(vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。路由实际上就是可以理解为指向,就是我在页面上点击一个按钮需要跳转到对应的页面,这就是路由跳转)
index.js
import Vue from 'vue'
import Manager from '../views/Manager.vue'
import VueRouter from "vue-router"
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Manager',
component: Manager,
redirect: '/index',
children:[
{
path: 'index',
name: 'Index',
component: () => import('../views/Index')
},
{
path: 'Manager/user',
name: 'User',
component: () => import('../views/User')
},
{
path: 'Manager/blog',
name: 'Blog',
component: () => import('../views/Blog')
},
{
path: 'Manager/category',
name: 'Category',
component: () => import('../views/Category')
},
{
// 定义:id占位符,根据博客id来指定动态路由
path: 'detail/:id',
name: 'BlogDetail',
component: () => import('../views/BlogDetail')
}
]
},
]
//history 模式不仅可以在url里放参数,还可以将数据存放在一个特定的对象中。
const router = new VueRouter({
mode:"history",
base: process.env.BASE_URL,
routes
})
export default router
修改app.vue
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<style>
#app {
height: 100%;
}
</style>
新建views文件夹
在components文件夹下新建通用组件
编写通用组件
Aside.vue
- 采用了element ui的NavMenu组件
- el-menu标签加上collapse属性,绑定变量isCollapse(属性值为boolean类型),通过变量控制收缩展开
<template>
<el-menu :default-openeds="['1', '2']" style="height: 100%; overflow-x: hidden;"
background-color="rgb(48, 65, 86)"
text-color="#fff"
active-text-color="#ffd04b"
:collapse-transition="false"
:collapse="isCollapse"
router>
<!-- logo-->
<div style="height: 60px; line-height: 60px; text-align: center">
<img src="../assets/logo.png" style="width: 20px; position: relative; top: 5px; margin-right: 5px">
<b style="color: white" v-show="logoTextShow">叩丁狼博客后台管理</b>
</div>
<el-menu-item index="/">
<template slot="title">
<i class="el-icon-house"></i>
<span slot="title">首页</span>
</template>
</el-menu-item>
<el-submenu index="2">
<template slot="title">
<i class="el-icon-message"></i>
<span slot="title">系统管理</span>
</template>
<el-menu-item-group>
<el-menu-item index="/manager/user">
<template slot="title">
<i class="el-icon-menu"></i>
<span slot="title">用户管理</span>
</template>
</el-menu-item>
<el-menu-item index="/manager/blog">
<template slot="title">
<i class="el-icon-menu"></i>
<span slot="title">博客管理</span>
</template>
</el-menu-item>
<el-menu-item index="/manager/category">
<template slot="title">
<i class="el-icon-menu"></i>
<span slot="title">分类管理</span>
</template>
</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</template>
<script>
export default {
name:"Aside",
props:{
isCollapse: Boolean, //控制收缩展开
logoTextShow: Boolean,
asideWidth: Number
}
}
</script>
<style scoped></style>
Header.vue
<template>
<div style="font-size: 12px;line-height: 60px;display: flex">
<div style="flex:1;font-size: 18px">
<span :class="collapseBtnClass" style="cursor: pointer" @click="collapse">
</span>
</div>
<el-dropdown style="cursor: pointer;width: 80px">
<span>管理员</span>
<i class="el-icon-arrow-down" style="margin-right: 15px;margin-left: 5px">
</i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人信息</el-dropdown-item>
<el-dropdown-item>退出系统</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
export default {
name: "Header",
props:{
collapseBtnClass: String,
collapse: Function
}
}
</script>
<style scoped>
</style>
编写单页面组件
Index.vue
<template>
<div>
欢迎来到叩丁狼博客后台管理系统
</div>
</template>
<script>
export default {
name: "Index"
}
</script>
<style scoped>
</style>
Manager.vue
<template>
<div style="height: 100%">
<el-container style="height: 100%">
<el-aside :width="asideWidth + `px`" style="background-color: rgb(238, 241, 246); height: 100%">
<Aside :width="asideWidth" :isCollapse="isCollapse" :logoTextShow="logoTextShow">
</Aside>
</el-aside>
<el-container>
<!-- flex布局-->
<el-header style="border-bottom: 1px solid #ccc">
<Header :collapseBtnClass="collapseBtnClass" :collapse="collapse"></Header>
</el-header>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
import Aside from '@/components/Aside';
import Header from '@/components/Header';
export default {
name: "Manager",
components:{Header,Aside},
data(){
return{
// Header 相关属性
collapseBtnClass: 'el-icon-s-fold',
// Aside 相关属性
isCollapse: false,
asideWith: 200,
logoTextShow:true,
}
},
methods:{
collapse(){
// 控制Aside隐藏、展开
this.isCollapse = !this.isCollapse
if (this.isCollapse){
this.asideWidth = 64
this.collapseBtnClass = 'el-icon-s-unfold'
this.logoTextShow = false
}else{
this.asideWidth = 200
this.collapseBtnClass = 'el-icon-s-fold'
this.logoTextShow = true
}
}
}
}
</script>
初步成果
编写单页面组件(2)
:
是指令v-bind
的缩写,是为了动态绑定数据,用于响应式地更新 HTML 特性。v-model
在表单输入元素或组件上创建双向绑定。@
是指令v-on
的缩写,用来监听DOM事件,比如点击、拖拽、键盘事件等等。- v-on的修饰符:修饰符是以半角句号
.
指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。 Vue提供了修饰符来帮助我们方便的处理一些事件:
.stop
调用 event.stopPropagation()。.prevent
调用 event.preventDefault().once
只触发一次回调
User.vue(用户管理)
<template>
<div>
<!-- 引入搜索框-->
<div style="padding: 10px 0">
<el-input
v-model="username"
style="width: 200px"
suffix-icon="el-icon-search"
placeholder="请输入用户名"
class="ml-5">
</el-input>
<el-input
v-model="role"
style="width: 200px"
suffix-icon="el-icon-user"
placeholder="请输入角色"
class="ml-5">
</el-input>
<el-button class="ml-5" type="primary" @click="handleSearch">搜索</el-button>
</div>
<!-- 引入增删改查按钮-->
<div style="margin: 10px">
<el-button type="primary" @click="handleAdd">新增<i class="el-icon-circle-plus-outline el-icon--right"></i></el-button>
</div>
<!-- 表格列表-->
<!-- stripe 是否为斑马纹 table boolean — false-->
<!-- border 是否带有纵向边框-->
<el-table :data="tableData" stripe border>
<el-table-column prop="id" label="ID" width="260"></el-table-column>
<el-table-column prop="username" label="用户名"></el-table-column>
<el-table-column prop="password" label="密码"></el-table-column>
<el-table-column prop="nickname" label="昵称"></el-table-column>
<!-- <el-table-column prop="avatar" label="头像"></el-table-column>-->
<el-table-column prop="role" label="角色"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
size="mini"
type="success"
@click="handleEdit(scope.$index, scope.row)">编辑
<i class="el-icon-edit el-icon--right"></i>
</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除
<i class="el-icon-delete el-icon--right"></i>
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 引入分页组件-->
<div style="padding: 10px 0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[1, 2, 5, 10]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
<el-dialog title="用户信息" :visible.sync="dialogFormVisible" width="30%">
<el-form label-width="80px">
<el-form-item label="用户名" >
<el-input v-model="form.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" >
<el-input v-model="form.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="昵称" >
<el-input v-model="form.nickname" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="角色" >
<el-input v-model="form.role" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel">取 消</el-button>
<el-button type="primary" @click="handleConfirm">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import request from "@/utils/request";
export default {
name: "User",
data() {
return {
// 表格数据及分页 属性
tableData: [],
total: 0,
pageNum: 1,
pageSize: 5,
// 搜索框 相关属性
username: '',
role: '',
// 新增|编辑 按钮 相关属性
dialogFormVisible: false,
form: {
username: "",
password: "",
nickname: "",
role: ""
}
}
},
created() {
this.loadData()
},
methods: {
// 向后端服务器请求数据并加载
loadData() {
request.get("/users/", {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize,
username: this.username,
role: this.role
}
}).then(
response => {
this.tableData = response.data.data.userList
this.total = response.data.data.total
console.log("loadData()被调用了")
},
error => {
console.log(error)
}
)
},
// 分页查询(页大小改变)触发事件
handleSizeChange(pageSize) {
this.pageSize = pageSize
this.loadData()
},
// 分页查询(当前页改变)触发事件
handleCurrentChange(pageNum) {
this.pageNum = pageNum
this.loadData()
},
// 搜索按钮触发事件
handleSearch() {
this.loadData()
},
// 新增按钮触发事件
handleAdd() {
this.dialogFormVisible = true
this.form = {}
},
// 对话框取消按钮触发事件
handleCancel() {
this.dialogFormVisible = false
this.form = {}
},
// 对话框确认按钮触发事件
handleConfirm() {
request.post("/users", this.form).then(
response => {
if (response.data.state === "200") {
this.$message.success("操作成功")
this.loadData()
}
}
)
this.dialogFormVisible = false
},
// 表格中编辑按钮触发事件
handleEdit(index, row) {
this.form = Object.assign({}, row)
this.dialogFormVisible = true
},
// 表格中删除按钮触发事件
handleDelete(index, row) {
let userId = row.id
request.delete("/users/" + userId).then(
response => {
if(response.data.state === "200") {
this.$message.success("删除成功")
this.loadData()
}
},
error => {
console.log(error)
}
)
}
}
}
</script>
<style scoped>
</style>
Blog.vue(博客管理)
<template>
<div>
<!-- 搜索框-->
<div style="margin: 10px">
<el-input
style="width: 300px"
placeholder="博客名称"
suffix-icon="el-icon-search"
v-model="searchTitle">
</el-input>
<el-button class="ml-5" type="primary" @click="handleSearch">搜索</el-button>
</div>
<!-- 新增博客 按钮-->
<div style="margin: 10px">
<el-button type="primary" @click="handleAdd">新增博客<i class="el-icon-circle-plus el-icon--right"></i></el-button>
</div>
<!-- 表格-->
<el-table :data="tableData" border style="width: 100%">
<el-table-column prop="id" label="ID"></el-table-column>
<el-table-column prop="title" label="标题"></el-table-column>
<el-table-column prop="user.username" label="作者" width="100px"></el-table-column>
<el-table-column prop="views" label="浏览次数" width="80px"></el-table-column>
<el-table-column prop="category.name" label="分类" width="100px"></el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160px">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.createTime | formatDate}}</span>
</template>
</el-table-column>
<el-table-column prop="published" label="是否发表" width="80px"></el-table-column>
<el-table-column label="操作" width="200px">
<template slot-scope="scope">
<el-button size="mini" type="success" @click="handleEdit(scope.$index, scope.row)">编辑<i class="el-icon-edit el-icon--right"></i></el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除<i class="el-icon-delete el-icon--right"></i></el-button>
</template>
</el-table-column>
</el-table>
<div style="margin: 10px 0">
<el-pagination @size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 15, 20]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</div>
</template>
<script>
import request from "@/utils/request";
export default {
name: "Blog",
data(){
return{
tableData:[],
currentPage:1,
pageSize:10,
total:0,
searchTitle:''
}
},
created() {
this.loadData()
},
methods:{
loadData(){
request.get("/blogs/", {
params: {
currentPage: this.currentPage,
pageSize: this.pageSize,
title: this.searchTitle
}
}).then(
response => {
this.tableData = response.data.data.blogList
this.total = response.data.data.count
}
)
},
handleAdd() {
this.$router.push({name: "BlogDetail", params: {id: "-1"}})
},
handleEdit(index, row) {
this.$router.push({name: "BlogDetail", params: {id: row.id}})
console.log(row)
},
handleDelete(index, row) {
let blogId = row.id
request.delete("/blogs/" + blogId).then(
response => {
if(response.data.state === "200") {
this.$message.success("删除成功!")
this.loadData()
}
},
error => {
this.$message.error("操作失败!")
}
)
},
handleCurrentChange(val){
this.currentPage = val
this.loadData()
},
handleSizeChange(val){
this.pageSize = val
this.loadData()
},
handleSearch(val){
this.loadData()
},
// 过滤器,用于格式化日期时间
filters:{
formatDate: function (dateTimeStr) {
let dateTime = new Date(dateTimeStr)
let YY = dateTime.getFullYear()
let MM = dateTime.getMonth().toString().padStart(2, '0')
let DD = dateTime.getDay().toString().padStart(2, '0')
let hh = dateTime.getHours().toString().padStart(2, '0')
let mm = dateTime.getMinutes().toString().padStart(2, '0')
let ss = dateTime.getSeconds().toString().padStart(2, '0')
return YY + "-" + MM + "-" + DD + " " + hh + ":" + mm + ":" + ss
}
}
}
}
</script>
<style scoped>
</style>
Category.vue(分类管理)
<template>
<div>
<!-- 搜索框-->
<div style="margin: 10px">
<el-input
style="width: 300px"
placeholder="分类名称"
suffix-icon="el-icon-search"
v-model="searchName">
</el-input>
<el-button class="ml-5" type="primary" @click="handleSearch">搜索</el-button>
</div>
<!-- 新增分类 按钮-->
<div style="margin: 10px">
<el-button type="primary" @click="handleAdd">新增分类<i class="el-icon-circle-plus el-icon--right"></i></el-button>
</div>
<!-- 表格-->
<el-table :data="tableData" border stripe style="width: 100%">
<el-table-column prop="id" label="ID"></el-table-column>
<el-table-column prop="name" label="名称"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="success" @click="handleEdit(scope.$index, scope.row)">编辑<i class="el-icon-edit el-icon--right"></i></el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除<i class="el-icon-delete el-icon--right"></i></el-button>
</template>
</el-table-column>
</el-table>
<div style="padding: 10px 0">
<el-pagination @current-change="handleCurrentChange"
:current-page="currPage"
:page-size="pageSize"
layout="total, prev, pager, next"
:total="total">
</el-pagination>
</div>
<el-dialog title="用户信息" :visible.sync="dialogFormVisible" width="30%">
<el-form label-width="80px">
<el-form-item label="名称" >
<el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel">取 消</el-button>
<el-button type="primary" @click="handleConfirm">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import request from "@/utils/request";
export default {
name: "Category",
data(){
return{
tableData:[],
form:{
name:''
},
dialogFormVisible:false,
currPage:1,
pageSize:5,
total:0,
searchName:''
}
},
created() {
this.loadData()
},
methods: {
// 向后端服务器请求数据并加载
loadData() {
request.get("/categories/", {
params: {
currPage: this.currPage,
pageSize: this.pageSize,
categoryName: this.searchName
}
}).then(
response => {
this.tableData = response.data.data.categoryList
this.total = response.data.data.count
console.log(response)
}
)
},
// 新增按钮触发事件
handleAdd() {
this.dialogFormVisible = true
this.form = {}
},
// 对话框取消按钮触发事件
handleCancel() {
this.dialogFormVisible = false
this.form = {}
},
// 对话框确认按钮触发事件
handleConfirm() {
this.dialogFormVisible = false
request.post("/categories", this.form).then(
response => {
if (response.data.state === '200') {
this.$message.success("操作成功")
this.loadData()
}
}
)
},
// 表格中编辑按钮触发事件
handleEdit(index, row) {
this.form = Object.assign({}, row)
this.dialogFormVisible = true
},
// 表格中删除按钮触发事件
handleDelete(index, row) {
let categoryId = row.id
request.delete("/categories/" + categoryId).then(
response => {
if (response.data.state === '200') {
this.$message.success("删除成功")
this.loadData()
}
},
error => {
this.$message.error("该分类下还有文章哟,不能删除~")
}
)
},
// 分页查询(当前页改变)触发事件
handleCurrentChange(val) {
this.currPage = val
this.loadData()
},
// 搜索按钮触发事件
handleSearch() {
this.loadData()
}
}
}
</script>
<style scoped>
</style>
BlogDetail.vue(编写文章)
<template>
<div>
<div class="title-box">
<el-input placeholder="请输入博客标题" v-model="form.title">
<template slot="prepend">标题</template>
</el-input>
</div>
<div class="title-box">
<el-input placeholder="请输入题图链接" v-model="form.titleImage">
<template slot="prepend">题图<i class="el-icon-link el-icon--right"></i> </template>
</el-input>
</div>
<mavon-editor v-model="form.content" style="min-height: 500px"/>
<div class="bottom-box">
<el-select
class="mr-5"
v-model="categoryId"
filterable
allow-create
default-first-option
placeholder="请选择博客分类">
<el-option
v-for="item in optionsCategory"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
<div style="flex: 1"></div>
<el-button type="primary" @click="handleSave('否')">保存为草稿</el-button>
<el-button type="primary" @click="handleSave('是')">发表</el-button>
</div>
</div>
</template>
<script>
import request from "@/utils/request";
export default {
name: "BlogDetail",
data() {
return {
categoryId: '',
optionsCategory: [],
form: {
title: '',
description: '',
content: '',
views: 0,
published: '',
titleImage: '',
user: {'id': '1', name: '良辰美景'},
category: {},
}
}
},
created() {
this.loadCategory()
this.loadBlogDataByIdFromPath()
// console.log(this.form)
// console.log(this.optionsCategory)
},
methods: {
// 初始化界面,加载分类数据
loadCategory() {
request.get("/categories").then(
response => {
this.optionsCategory = response.data.data.categoryList
}
)
},
loadBlogDataByIdFromPath() {
// 根据参数是否包含blog来判断是新增博客还是编辑博客
let blog_id = this.$route.params.id
if (blog_id !== '-1') {
request.get("/blogs/" + blog_id).then(
response => {
let blog = response.data.data.blog
this.form = blog
this.categoryId = blog.category.id
}
)
}
},
// 保存并发表
handleSave(published) {
let newCategoryFlag = !this.isNewCategory()
if(newCategoryFlag) {
this.form.category = {"name": this.categoryId}
} else {
this.updateCategory()
}
this.form.published = published
this.form.description = this.form.content.slice(0,20) + "..."
console.log(this.form.description)
request.post("/blogs", this.form).then(
response => {
if(response.data.state === '200') {
this.$message.success("保存成功!")
this.$router.push("/manager/blog")
}
},
error => {
this.$message.error("保存失败!")
}
)
},
// 判断当前分类是否为一个新的分类
isNewCategory() {
for(let i=0; i<this.optionsCategory.length; i++) {
if(this.categoryId === this.optionsCategory[i].id) {
return true
}
}
return false
},
// 根据绑定在select选择器中的categoryId更新form中的category
updateCategory() {
for(let i=0; i<this.optionsCategory.length; i++) {
if(this.categoryId === this.optionsCategory[i].id) {
this.form.category = this.optionsCategory[i]
return
}
}
}
}
}
</script>
<style scoped>
.title-box {
width: 500px;
margin: 10px auto;
}
.bottom-box {
display: flex;
margin: 10px 0;
}
</style>
blog_wolf - 前端(用户端)
配置package.json
- axios
- core-js
- element-ui
- mavon-editor
- pubsub-js —— 订阅消息和发布消息(控制台)
vue-router —— Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。
- 跳转页面
- vue2
- echarts-wordcloud —— 词云图
- vue-b2wordcloud —— 词云
- echarts
- markdown-it —— Markdown 解析器
- marked —— Markdown 解析和编译器
{
"name": "blog",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --open",
"build": "vue-cli-service build"
},
"dependencies": {
"axios": "^0.26.0",
"core-js": "^3.6.5",
"echarts": "^5.3.2",
"echarts-wordcloud": "^2.0.0",
"element-ui": "^2.15.6",
"markdown-it": "^13.0.0",
"marked": "^4.0.14",
"vue": "^2.6.11",
"vue-b2wordcloud": "^1.0.5",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"vue-template-compiler": "^2.6.11"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
在assets文件夹中引入iconfont
- 注意:此处iconfont.css如果为相对路径的,要改为绝对路径。
在assets文件夹中新建img文件夹
配置main.js
- 引入element-ui、echarts、iconfont
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import './assets/css/iconfont/iconfont.css'
import './assets/css/iconfont/iconfont'
import echarts from 'echarts';
Vue.use(ElementUI);
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app')
配置axios
新建utils文件夹和request.js
import axios from "axios";
const request = axios.create({
baseURL:'http://localhost:8081',
timeout:5000
})
export default request
新建router文件夹和index.js
- vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。路由模块的本质 就是建立起url和页面之间的映射关系。
(vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。路由实际上就是可以理解为指向,就是我在页面上点击一个按钮需要跳转到对应的页面,这就是路由跳转)
index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from "@/views/home/Home";
import Category from "@/views/category/Category";
import Archive from "@/views/archive/Archive";
import About from "@/views/about/About";
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/category',
name: 'Category',
component: Category
},
{
path: '/archive',
name: 'Archive',
component: Archive
},
{
path: '/about',
name: 'About',
component: About
},
{
path: '/blog/:id',
name: 'Blog',
component: () => import('../views/blog/Blog')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
配置Vuex
- Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
- 可以
方便的实现组件之间的数据共享
新建store文件夹
- 新建index.js
index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
修改app.vue
<template>
<div id="app">
<Header></Header>
<router-view></router-view>
<BackToTop></BackToTop>
<Footer></Footer>
</div>
</template>
<style>
</style>
<script>
import BackToTop from "@/components/layout/BackToTop";
import Header from "@/components/layout/Header";
import Footer from "@/components/layout/Footer";
export default {
components: {Footer, Header, BackToTop}
}
</script>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
background-color: #efeded;
}
#app {
height: 100%;
}
</style>
新建views文件夹
在components文件夹下layout文件夹
编写通用组件
BlogItem.vue
- 每个blog,称为
blog item
<template>
<div class="article" @click="view(blog)">
<div class="article-cover">
<img v-if="blog.titleImage" :src="blog.titleImage" style="width: 100%; height: 100%; border-radius: 5px">
<img v-else src="../assets/img/cover.jpg" style="width: 100%; height: 100%; border-radius: 5px">
</div>
<div class="article-item-right">
<div class="article-title">
{{blog.title}}
</div>
<div class="article-info">
<span>
<i class="el-icon-date"></i> {{blog.createTime | formatDate}}
<span class="separator">| </span>
</span>
<span>
<i class="el-icon-document"></i> {{blog.category.name}}
</span>
</div>
<div class="article-description">
{{blog.description}}
</div>
</div>
</div>
</template>
<script>
export default {
name: "BlogItem",
props:['blog'],
data(){
return{}
},
methods:{
view(blog){
console.log('view方法被调用了')
this.$router.push({name: 'Blog', params: {id: blog.id, blog: blog}})
}
},
// 过滤器,用于格式化日期时间
filters: {
formatDate: function (dateTimeStr) {
let dateTime = new Date(dateTimeStr)
let YY = dateTime.getFullYear()
let MM = dateTime.getMonth().toString().padStart(2, '0')
let DD = dateTime.getDay().toString().padStart(2, '0')
let hh = dateTime.getHours().toString().padStart(2, '0')
let mm = dateTime.getMinutes().toString().padStart(2, '0')
let ss = dateTime.getSeconds().toString().padStart(2, '0')
return YY + "-" + MM + "-" + DD + " " + hh + ":" + mm + ":" + ss
}
}
}
</script>
<style scoped>
.article {
display: flex;
background-color: white;
width: 730px;
height: 200px;
border: 1px solid white;
margin: 10px 0;
border-radius: 5px;
}
.article-cover {
width: 270px;
}
.article-item-right {
width: 440px;
margin-left: 20px;
}
.article-title {
font-size: 20px;
text-align: center;
margin: 20px 0;
}
.article-info {
font-size: 13px;
color: gray;
}
.article-description {
margin-top: 10px;
font-size: 14px;
color: gray;
}
</style>
card目录
AuthorInfo.vue(博主信息)
<template>
<div class="author-card">
<div class="author-img">
<img src="../../assets/img/author.png" style="margin-left:10%;width: 120%;height: 100%">
</div>
<div>
<div style="font-size: 30px">Wolf</div>
<div style="margin: 10px 0">千里稻花应秀色,五更桐叶最佳音</div>
</div>
<div class="blog-info">
<div>
<div class="text-item">文章</div>
<div>{{blogNum}}</div>
</div>
<div>
<div class="text-item">分类</div>
<div>{{categoryNum}}</div>
</div>
<div>
<div class="text-item">标签</div>
<div>10</div>
</div>
</div>
<div class="fellow-me">联系方式</div>
<div class="contact-me">
<a class="iconfont iconfont-GitHub"
style="font-size: 30px"
href="https://github.com/yosoroQ"
TARGET="_blank"
></a>
<a class="iconfont iconfont-bilibili"
style="font-size: 30px"
href="https://space.bilibili.com/6536171"
TARGET="_blank"
></a>
<a
class="iconfont iconfont-QQ"
style="font-size: 30px"
href="https://wpa.qq.com/wpa_jump_page?v=3&uin=1249508470&site=qq&menu=yes"
TARGET="_blank"
></a>
</div>
</div>
</template>
<script>
export default {
name: "AuthorInfo",
props: ['blogNum', 'categoryNum']
}
</script>
<style scoped>
.author-card {
width: 270px;
height: 350px;
background-color: white;
border-radius: 10px;
text-align: center;
}
.author-img {
width: 100px;
height: 100px;
margin: 0 auto;
padding-top: 5px;
}
.blog-info {
display: flex;
justify-content: space-between;
margin: 10px 50px;
}
.text-item {
margin: 10px 0;
}
.fellow-me {
height: 30px;
width: 240px;
background-color: #49b1f5;
line-height: 30px;
color: white;
margin: 0 auto;
}
.contact-me {
margin: 10px 0;
}
.contact-me a {
margin: 0 20px;
text-decoration: none;
color: #49b1f5;
}
</style>
WebsiteInfo.vue(网站运行信息)
<template>
<div class="website-card">
<div class="icon-title">
<i class="el-icon-sunny" style="font-size: 18px"/>有什么好看的呢
</div>
<div class="detail-info">
<div>本站勉强运行:{{ runTime }}</div>
<div style="margin-top: 5px">总浏览量: {{ totalViews }} 次</div>
</div>
</div>
</template>
<script>
import request from "@/utils/request";
export default {
name: "WebsiteInfo",
data(){
return{
runTime:'',
totalViews:0,
startDate: '2022-11-01 00:00:00'
}
},
created() {
this.calcTotalViews()
},
mounted() {
this.timer = setInterval(this.refreshRunTime, 1000)
},
beforeDestroy() {
clearInterval(this.timer)
},
methods:{
refreshRunTime(){
let time = Date.now() - Date.parse(this.startDate)
console.log(time)
let dd = Math.floor(time / (24 * 60 * 60 * 1000))
let hh = Math.floor((time - dd * (24 * 60 * 60 * 1000)) / (60 * 60 * 1000))
let mm = Math.floor((time - dd * (24 * 60 * 60 * 1000) - hh * (60 * 60 * 1000)) / (60 * 1000))
let ss = Math.floor((time - dd * (24 * 60 * 60 * 1000) - hh * (60 * 60 * 1000) - mm * (60 * 1000)) / 1000)
this.runTime = dd + "天" + hh + "时" + mm + "分" + ss + "秒"
},
calcTotalViews(){
request.get("/blogs").then(
response => {
let blogs = response.data.data.blogs
let total = 0
blogs.forEach(function (blog) {
total += blog.views
})
this.totalViews = total
}
)
}
}
}
</script>
<style scoped>
.website-card {
width: 270px;
height: 140px;
margin-top: 20px;
background-color: white;
border-radius: 10px;
}
.icon-title {
font-size: 18px;
padding: 20px;
}
.detail-info {
padding-left: 20px;
font-size: 14px;
color: #000000;
}
</style>
layout目录
BackToTop.vue(回到顶部)
<template>
<transition>
<div @click="toTop" v-show="topShow" class="me-to-top">
<i class="el-icon-caret-top"></i>
</div>
</transition>
</template>
<script>
export default {
name: "BackToTop",
data(){
return{
topShow:false
}
},
methods:{
toTop(){
document.documentElement.scrollTop = 0
this.topShow = false
},
needToTop(){
//获取滚动条位置
let currentHeight = document.documentElement.scrollTop
this.topShow = currentHeight > 400;
}
},
mounted() {
// 等到页面渲染完成之后给window添加一个监听事件(用于监听滚动条的位置,来判断是否需要触发needToTop函数)
this.$nextTick(function (){
window.addEventListener('scroll', this.needToTop)
})
}
}
</script>
<style scoped>
.me-to-top {
background-color: #fff;
position: fixed;
right: 100px;
bottom: 150px;
width: 40px;
height: 40px;
border-radius: 20px;
/*鼠标放到该位置显示为一个手指的形状*/
cursor: pointer;
transition: 0.3s;
/*给div元素添加阴影*/
box-shadow: 0 0 6px rgba(0, 0, 0, 0.12);
z-index: 5;
}
.me-to-top i {
color: skyblue;
display: block;
/*line-height和height相同,控制元素垂直居中*/
line-height: 40px;
/*控制元素水平居中*/
text-align: center;
font-size: 18px;
}
</style>
Footer.vue(版权信息部分,底部)
<template>
<el-footer style="height: 100px">
<div class="footer-wrap">
<div style="margin-bottom: 10px">©2022 - 19信管2班</div>
<a href="https://beian.miit.gov.cn/" target="_blank" style="color: white; text-decoration: none">
粤ICP备20047854号
</a>
</div>
</el-footer>
</template>
<script>
export default {
name: "Footer"
}
</script>
<style scoped>
.el-footer {
background: #795da3;
}
.footer-wrap {
text-align: center;
position: relative;
font-size: 13px;
color: white;
top: 20%;
}
</style>
Header.vue(头部,导航栏)
<template>
<div class="header-container">
<div class="header-left">
叩丁狼博客
</div>
<div style="flex: 1"></div>
<div class="header-right">
<!-- 使用vue-router模式,在激活导航时以index作为path进行路由跳转 -->
<el-menu :default-active="path"
router
mode="horizontal"
background-color="#efeded"
active-text-color="black"
style="height: 50px; line-height: 50px; border-bottom: 1px solid #ccc; color: #ccc"
>
<el-menu-item style="height: 50px; line-height: 50px" index="/">首页</el-menu-item>
<el-menu-item style="height: 50px; line-height: 50px" index="/category">分类</el-menu-item>
<el-menu-item style="height: 50px; line-height: 50px" index="/archive">归档</el-menu-item>
<el-menu-item style="height: 50px; line-height: 50px" index="/about">关于</el-menu-item>
</el-menu>
</div>
</div>
</template>
<script>
export default {
name: "Header",
data(){
return{
path: this.$router.path
}
}
}
</script>
<style scoped>
.header-container {
height: 50px;
line-height: 50px;
border-bottom: 1px solid #cccccc;
display: flex;
}
.header-left {
width: 200px;
padding-left: 50px;
font-weight: bold;
font-size: 25px;
color: #49b1f5;
}
.header-right {
width: 500px;
}
</style>
编写单页面组件
About.vue(关于页面)
<template>
<div class="container">
<div class="content-box">
<div class="subtitle">我是谁</div>
<hr>
<div class="text">
<p>猜猜我是谁?!</p>
<p>About.vue 实训第八组 所做作品</p>
<p>组内成员:</p>
<ul>
<li>About.vue</li>
<li>About.vue</li>
<li>About.vue</li>
<li>About.vue</li>
<li>About.vue</li>
</ul>
</div>
<div class="subtitle">关于本站</div>
<hr/>
<div class="text">
技术栈?很简单
<br>
<p><b>后端</b></p>
<ul>
<li>SpringBoot</li>
<li>Mybatis</li>
<li>Mysql</li>
</ul>
<b>前端</b>
<ul>
<li>Vue</li>
<li>Vue router</li>
<li>Axios</li>
<li>Element Ui</li>
</ul>
</div>
<div class="subtitle">我们在这里等你!</div>
<hr>
<p>学瀚楼</p>
<!-- <baidu-map class="bm-view" ak="rK1hIqy1rMCK5AQdID75hoFe8wVEazue">-->
<!-- </baidu-map>-->
<!-- <baidu-map :center="center" :zoom="zoom" @ready="handler"></baidu-map>-->
<baidu-map id="mapContent" class="bm-view" ak="rK1hIqy1rMCK5AQdID75hoFe8wVEazue" :center="center" :zoom="zoom" @ready="handler">
<bm-marker :position="{lng: 110.34469, lat: 21.2943}" :dragging="true">
<!-- 湛江科技学院坐标-->
<!-- 110.34469,21.2943-->
</bm-marker>
<bm-map-type :map-types="mapType" anchor="BMAP_ANCHOR_TOP_RIGHT"></bm-map-type>
</baidu-map>
<div>
</div>
</div>
</div>
</template>
<script>
import BaiduMap from 'vue-baidu-map/components/map/Map.vue'
import BmMarker from 'vue-baidu-map/components/overlays/Marker.vue'
export default {
name: "About",
data() {
return {
center: {lng: 110.34469,lat: 21.2943},
zoom: 50,
mapType:['BMAP_NORMAL_MAP', 'BMAP_HYBRID_MAP']
}
},
components: {
BaiduMap,
BmMarker
},
methods: {
handler ({BMap, map}) {
console.log(BMap, map)
this.center.lng = 110.34469
this.center.lat = 21.2943
this.zoom = 50,
//这句使得初始为卫星图
map.setMapType(BMAP_HYBRID_MAP);
},
}
}
</script>
<style scoped>
.container {
min-height: calc(100vh - 120px - 50px - 40px);
background: white;
width: 800px;
margin: 20px auto;
border-radius: 10px;
}
.content-box {
padding: 10px 20px;
}
.subtitle {
font-size: 1.3em;
margin: 20px 0 10px 0;
}
.logo {
font-size: 30px;
margin: 20px;
text-decoration: none;
}
.text {
color: dimgray;
font-size: 15px;
line-height: 22px;
}
.bm-view {
width: 100%;
height: 300px;
}
.map {
width: 100%;
height: 400px;
}
</style>
Home.vue(首页)
<template>
<el-container>
<el-main>
<div style="min-height: 80%">
<BlogItem
v-for="blog in blogs"
:key="blog.id"
:blog="blog"
></BlogItem>
</div>
<div>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[2, 5, 10]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</el-main>
<el-aside style="width: 270px">
<div style="position: fixed">
<AuthorInfo
:blog-num="total"
:category-num="categoryNum"
></AuthorInfo>
<WebsiteInfo/>
</div>
</el-aside>
</el-container>
</template>
<script>
import BlogItem from "@/components/BlogItem";
import AuthorInfo from "@/components/card/AuthorInfo";
import request from "@/utils/request";
import WebsiteInfo from "@/components/card/WebsiteInfo";
export default {
name: "Home",
components: {WebsiteInfo, AuthorInfo, BlogItem},
data(){
return{
blogs: [],
currentPage: 1,
pageSize: 5,
total: 0,
categoryNum: 0,
totalViews: 0,
}
},
created() {
this.loadBlogData()
this.loadCategoryData()
},
methods:{
loadBlogData() {
request.get("/blogs/", {
params: {
currentPage: this.currentPage,
pageSize: this.pageSize
}
}).then(
response => {
this.blogs = response.data.data.blogList
this.total = response.data.data.count
}
)
},
loadCategoryData(){
request.get("/categories").then(
response => {
this.categoryNum = response.data.data.categoryList.length
}
)
},
handleSizeChange(val){
this.pageSize = val
this.loadBlogData()
},
handleCurrentChange(val) {
this.currentPage = val
this.loadBlogData()
}
}
}
</script>
<style scoped>
.el-container {
width: 1020px;
min-height: 100%;
margin: 10px auto;
}
.el-main {
padding: 10px 0;
}
.el-aside {
margin-left: 10px;
margin-top: 20px;
}
</style>
Category.vue(分类页面、词云)
<template>
<div class="category-container">
<div class="category-box" ref="chart1"></div>
</div>
</template>
<script>
import request from "@/utils/request";
import * as echarts from 'echarts';
import 'echarts-wordcloud';
export default {
name: "Category",
data(){
return{
categories:[]
}
},
created() {
this.loadCategoryData()
},
watch:{
// 使用watch属性监听categories的变化,mounted获取不到created中的数据
categories:function (val){
this.showEcharts()
}
},
methods:{
loadCategoryData() {
request.get("/categories").then(
response => {
let tmp_data = []
let categories = response.data.data.categoryList
if(categories){
categories.forEach(function (category) {
tmp_data.push({'name': category.name, 'value': Math.floor(Math.random() * (50 - 1 + 1) + 1)})
})
this.categories = tmp_data
}
}
)
},
showEcharts(){
let chart = echarts.init(this.$refs.chart1)
let option = {
series: [{
type: 'wordCloud',
shape: 'cardioid',
left: 'center',
top: 'center',
width: '80%',
height: '80%',
right: null,
bottom: null,
sizeRange: [18, 100],
rotationRange: [-90, 90],
rotationStep: 25,
gridSize: 25,
drawOutOfBound: false,
// Global text style
textStyle: {
fontFamily: 'sans-serif',
fontWeight: 'bold',
// Color can be a callback function or a color string
color: function () {
// Random color
return 'rgb(' + [
Math.round(Math.random() * 255),
Math.round(Math.random() * 255),
Math.round(Math.random() * 255)
].join(',') + ')';
}
},
emphasis: {
focus: 'self',
textStyle: {
textShadowBlur: 10,
textShadowColor: '#333'
}
},
data: this.categories
}]
}
chart.setOption(option)
}
}
}
</script>
<style scoped>
.category-container {
display: flex;
justify-content: center;
align-items: center;
min-height: calc(100vh - 120px - 50px);
text-align: center;
}
.category-box {
height: 500px;
width: 800px;
background-color: white;
border-radius: 10px;
}
</style>
Archive.vue(归档页面)
<template>
<div class="container">
<div class="archive-card">
<div
v-for = "year in years"
>
<h3 style="color: black">{{year}}</h3>
<hr>
<div
v-for = "blog in archive[years]"
style="color: dimgray"
>
{{blog.createTime | formatDate}} {{blog.title}}
</div>
</div>
</div>
</div>
</template>
<script>
import request from "@/utils/request";
export default {
name: "Archive",
data(){
return{
years:[],
archive:{}
}
},
created() {
this.loadAllBlog()
},
methods:{
loadAllBlog(){
request.get("/blogs").then(
response => {
let blogs = response.data.data.blogs
this.archiveByYear(blogs)
}
)
},
archiveByYear(blogs){
let archive_dict = {}
blogs.forEach(function (blog) {
let year = blog.createTime.slice(0, 4)
if(archive_dict.hasOwnProperty(year)) {
archive_dict[year].push(blog)
} else {
archive_dict[year] = [blog]
}
})
let years = Object.keys(archive_dict).sort(function (a, b) {
return a > b ? -1 : a < b ? 1 : 0
})
this.years = years
this.archive = archive_dict
}
},
filters:{
formatDate:function(dateTimeStr){
let dateTime = new Date(dateTimeStr)
let YY = dateTime.getFullYear()
let MM = dateTime.getMonth().toString().padStart(2, '0')
let DD = dateTime.getDay().toString().padStart(2, '0')
return YY + "-" + MM + "-" + DD
}
}
}
</script>
<style scoped>
.container {
display: flex;
justify-content: center;
min-height: calc(100vh - 120px - 50px);
}
.archive-card {
width: 800px;
min-height: 400px;
margin: 20px;
padding: 20px;
background-color: white;
border-radius: 10px;
}
</style>
Blog.vue(博客详情页面)
<template>
<div class="blog-box">
<div class="base-info">
<h1>{{blog.title}}</h1>
<div style="font-size: 15px">
<span>
<i class="el-icon-time">
文章发表于:{{blog.createTime | formatDate}}
</i>
</span>
<span>
<i class="el-icon-document">
{{blog.category.name}} |
</i>
</span>
<span>
<i class="el-icon-view">
阅读量:{{blog.views}}
</i>
</span>
</div>
</div>
<div class="content">
<div
v-html="markdownToHtml()"
></div>
</div>
</div>
</template>
<script>
//highlight.js, 代码高亮库
import request from "@/utils/request";
import MarkdownIt from "markdown-it";
import hljs from "highlight.js";
import 'highlight.js/styles/default.css';
export default {
name: "Blog",
data(){
return{
blog:{}
}
},
created() {
this.loadDataByIdFromPath()
this.markdownToHtml()
},
methods:{
// 根据path中的id向后端请求博客数据
loadDataByIdFromPath(){
let blog_id = this.$route.path.split("/")[2]
request.get("/blogs/" + blog_id).then(
response =>{
this.blog = response.data.data.blog
console.log(this.blog)
this.updateViews()
}
)
},
// markdown文本转html
markdownToHtml(){
let md = new MarkdownIt({
html:true,
linkify:true,
typographer:true,
highlight:function (str,lang){
if (lang && hljs.getLanguage(lang)) {
try {
return '<pre class="hljs"><code>' +
hljs.highlight(str, {language: lang, ignoreIllegals: true}).value +
'</code></pre>'
} catch(_) {}
}
}
})
return md.render(this.blog.content)
},
// 更新浏览量updateViews()
updateViews(){
let form = {id:this.blog.id,views:this.blog.views +1}
request.post("/blogs", form).then(
response => {
console.log(response)
}
)
}
},
filters: {
formatDate: function (dateTimeStr) {
let dateTime = new Date(dateTimeStr)
let YY = dateTime.getFullYear()
let MM = dateTime.getMonth().toString().padStart(2, '0')
let DD = dateTime.getDay().toString().padStart(2, '0')
let hh = dateTime.getHours().toString().padStart(2, '0')
let mm = dateTime.getMinutes().toString().padStart(2, '0')
let ss = dateTime.getSeconds().toString().padStart(2, '0')
return YY + "-" + MM + "-" + DD + " " + hh + ":" + mm + ":" + ss
}
}
}
</script>
<style scoped>
.blog-box {
min-height: calc(100vh - 120px - 50px - 100px);
width: 60%;
margin: 50px auto;
}
.base-info {
text-align: center;
margin: 30px 0 60px 0;
color: palevioletred;
}
.content {
background-color: white;
color: dimgray;
font-family: "Arial", sans-serif;
border: 1px solid white;
border-radius: 10px;
padding: 0 20px;
}
</style>
Comments | NOTHING