springboot3+react18+ts实现一个点赞功能

前端:vite+react18+ts+antd

后端:springboot3.0.6+mybatisplus

最终效果大致如下:

后端:

引入pom依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.31</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
</dependencies>

运行sql

/*
 Navicat Premium Data Transfer

 Source Server         : MyDemo
 Source Server Type    : MySQL
 Source Server Version : 80027 (8.0.27)
 Source Host           : 192.168.157.134:3306
 Source Schema         : giveALike

 Target Server Type    : MySQL
 Target Server Version : 80027 (8.0.27)
 File Encoding         : 65001

 Date: 01/05/2023 20:20:15
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for article
-- ----------------------------
DROP TABLE IF EXISTS `article`;
CREATE TABLE `article`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '编号',
  `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '内容',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of article
-- ----------------------------
INSERT INTO `article` VALUES (1, '11');
INSERT INTO `article` VALUES (2, '666');
INSERT INTO `article` VALUES (3, '777');
INSERT INTO `article` VALUES (4, '999');

-- ----------------------------
-- Table structure for giveALike
-- ----------------------------
DROP TABLE IF EXISTS `giveALike`;
CREATE TABLE `giveALike`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '编号',
  `user_id` int NULL DEFAULT NULL COMMENT '用户编号',
  `article_id` int NULL DEFAULT NULL COMMENT '文章编号',
  `is_like` int NULL DEFAULT NULL COMMENT '是否点赞(0表示未点赞,1表示点赞)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of giveALike
-- ----------------------------

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '编号',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '11', '11');

SET FOREIGN_KEY_CHECKS = 1;

项目结构

yml配置,数据库改成你自己的数据库

server:
  port: 5000

spring:
  application:
    name: give-a-like

  datasource:
    url: jdbc:mysql://192.168.157.134:3306/giveALike?serverTimezone=GMT%2B8&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

entity下的三个实体类

Article:

@Data
public class Article {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;//编号
    private String content;//内容
    @TableField(exist = false)
    private Integer isLike;
}

GiveALike: 

@Data
@TableName("giveALike")
public class GiveALike {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private Integer articleId;//文章编号
    private Integer userId;//用户编号
    private int isLike;//是否点赞(0表示未点赞,1表示点赞)
}

User: 

@Data
public class User {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;//用户id
    private String username;//用户名
    private String password;//密码
}

mapper

ArticleMapper:

@Mapper
public interface ArticleMapper extends BaseMapper<Article> {
}

GiveALikeMapper:

@Mapper
public interface GiveALikeMapper extends BaseMapper<GiveALike> {
}

UserMapper: 

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

controller

@Slf4j
//跨域
@CrossOrigin
@RestController
@RequestMapping("/giveALike")
public class GiveALikeController {

    @Resource
    private UserMapper userMapper;

    @Resource
    private ArticleMapper articleMapper;

    @Resource
    private GiveALikeMapper giveALikeMapper;

    //登录
    @PostMapping("/login")
    public User login(@RequestBody User user) {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("username", user.getUsername());
        User vo = userMapper.selectOne(userQueryWrapper);
        if (vo != null && Objects.equals(vo.getPassword(), user.getPassword())) {
            return vo;
        } else {
            return null;
        }
    }

    //获取所有文章数据
    //TODO 优化建议:使用分页,减少并发
    @GetMapping("/getList")
    public List<Article> getList() {
        List<Article> articles = articleMapper.selectList(null);
        List<Article> list = new ArrayList<>();
        for (Article item : articles) {
            QueryWrapper<GiveALike> wrapper = new QueryWrapper<>();
            wrapper.eq("article_id", item.getId());
            GiveALike giveALike = giveALikeMapper.selectOne(wrapper);
            if (giveALike == null || giveALike.getIsLike() == 0) {
                item.setIsLike(0);
            } else {
                item.setIsLike(1);
            }
            list.add(item);
        }
        return list;
    }

    //点赞
    @PostMapping("/saveUserLike")
    @Transactional
    public GiveALike saveUserLike(@RequestBody GiveALike giveALike) {
        QueryWrapper<GiveALike> wrapper = new QueryWrapper<>();
        wrapper.eq("article_id", giveALike.getArticleId()).eq("user_id", giveALike.getUserId());
        GiveALike vo = giveALikeMapper.selectOne(wrapper);
        if (vo != null) {
            if (vo.getIsLike() == 0) {
                vo.setIsLike(1);
            } else {
                vo.setIsLike(0);
            }
            giveALikeMapper.updateById(vo);
            return vo;
        } else {
            giveALike.setIsLike(1);
            giveALikeMapper.insert(giveALike);
            return giveALike;
        }
    }
}

前端:

相关依赖

目录结构

 main.tsx页面

import React, {Suspense} from 'react'
import ReactDOM from 'react-dom/client'
import {RouterProvider} from 'react-router-dom'
import router from './router'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
    <React.StrictMode>
        <Suspense>
            <RouterProvider router={router}></RouterProvider>
        </Suspense>
    </React.StrictMode>
)

App.tsx页面本次没有使用到,无影响

路由router.ts

import React, {lazy} from "react";
import {createBrowserRouter} from "react-router-dom";
import type {RouteObject} from "react-router-dom";

const Home = lazy(() => import('../view/home.tsx'))
const Login = lazy(() => import('../view/login.tsx'))


declare module 'react-router' {
    interface IndexRouteObject {
        meta?: {
            menu?: boolean
            title?: string
            auth?: boolean
        }
    }

    interface NonIndexRouteObject {
        meta?: {
            menu?: boolean
            title?: string
            auth?: boolean
        }
    }
}

export const routes: RouteObject[] = [
    {
        path: '/home',
        element: React.createElement(Home),
        meta: {
            menu: true,
            title: '首页',
            auth: true
        }
    },
    {
        path: '/',
        element: React.createElement(Login),
        meta: {
            menu: true,
            title: '登录',
            auth: false
        }
    }
];

const router = createBrowserRouter(routes)

export default router;

 登录页面login.tsx

import React, {useState} from "react";
import {useNavigate} from 'react-router-dom'
import axios from "axios";

const Login = () => {
    const navigate = useNavigate()

    const [username, setUsername] = useState("");
    const [password, setPassword] = useState("");

    //登录
    const handleSubmit = async (event: React.FormEvent) => {
        event.preventDefault();
        try {
            const response = await axios.post("http://localhost:5000/giveALike/login", {
                username,
                password,
            });
            if (response.status == 200) {
                //登录成功,跳转首页
                navigate('/home');
                //将用户信息存到session
                window.sessionStorage.setItem("user", JSON.stringify(response.data))
            } else {
                alert(response.data.message)
            }
        } catch (error) { /* empty */
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label htmlFor="username">Username</label>
                <input
                    type="text"
                    id="username"
                    value={username}
                    onChange={(event) => setUsername(event.target.value)}
                />
            </div>
            <div>
                <label htmlFor="password">Password</label>
                <input
                    type="password"
                    id="password"
                    value={password}
                    onChange={(event) => setPassword(event.target.value)}
                />
            </div>
            <button type="submit">Login</button>
        </form>
    );
};

export default Login;

home.tsx页面

import axios from "axios";
import {useEffect, useState} from "react";
import {message} from "antd";

interface GiveALike {
    articleId: number,
    userId: number
}

interface Article {
    id: number,
    content: string,
    isLike?: number
}

const Home = () => {
    const [articleList, setArticleList] = useState<Article[]>([]);
    //点赞所需的参数
    const [giveALike] = useState<GiveALike>({
        articleId: 0,//文章编号
        userId: 0//用户编号
    });

    //获取所有内容
    const getList = async () => {
        const response = await axios.get("http://localhost:5000/giveALike/getList")
        setArticleList(response.data)
    }

    //点赞
    const handleLike = (row: number) => {
        //从session中获取用户信息
        const user = JSON.parse(window.sessionStorage.getItem("user") || '')
        //设置用户编号
        giveALike.userId = user.id
        //设置文章编号
        giveALike.articleId = row
        axios.post("http://localhost:5000/giveALike/saveUserLike", giveALike).then((res) => {
            getList()
            if (res.data.isLike == 1) {
                message.success('点赞成功');
            } else {
                message.warning('取消点赞');
            }
        })
    }

    //vite方式获取图片
    const getImageUrl = (name: string) => {
        return new URL(`../assets/${name}`, import.meta.url).href
    }

    useEffect(() => {
        getList()
    }, []);

    return (
        <>
            <div>
                {articleList.map((item: Article) => (
                    <div key={item.id} style={
   
   {display: "flex", fontSize: "30px"}}>
                        <div style={
   
   {marginRight: "10px"}}>编号:{item.id}</div>
                        <div style={
   
   {marginRight: "10px"}}>内容:{item.content}</div>
                        {
                            item.isLike == 0
                                ?
                                <img onClick={() => {
                                    handleLike(item.id)
                                }} src={getImageUrl("未点赞.png")}
                                     style={
   
   {width: "50px", height: "50px", cursor: "pointer"}} alt="图片"/>
                                :
                                <img onClick={() => {
                                    handleLike(item.id)
                                }} src={getImageUrl("点赞.png")}
                                     style={
   
   {width: "50px", height: "50px", cursor: "pointer"}} alt="图片"/>
                        }

                    </div>
                ))}
            </div>
        </>
    );
}

export default Home;

 Gitee仓库地址:Gitee

上述代码如有问题,欢迎提出来,博主看到了会第一时间解决

springboot+vue3+ts版

猜你喜欢

转载自blog.csdn.net/crazy1013/article/details/130458465