前后端项目笔记

news2025/2/25 2:58:43

前端项目创建

准备工作   nodejs安装   vue cli安装

vue create frontend

最后一个y的话 它会保存  方便下次创建项目  我这是手快敲错了 随自己

前端项目组件及作用

Element-UI引入

安装

npm i element-ui -S

main.js中引入

清空路口App.vue

清空Home页面

随便写个按钮

原因

全局样式控制

因为Element-UI带有一些默认样式,有时我们需要创建全局的样式控制(清空ElementUI样式)需要自定义一些样式等

一般在assets中创建 global.css

在main.js中引入

对比

Vue管理系统页面布局

容器布局

左侧菜单

点击菜单进行路由跳转

例如 我新建一个视图

配置路由

表格(编辑删除)

路由

视图

搜索 新增 (el-input)

全局配置UI按钮大小

全局表格头居中data居中

表格数据分页

后台搭建

server:
  port: 8085

spring:
  application:
    name: backend
  redis:
    ##redis 单机环境配置
    ##将docker脚本部署的redis服务映射为宿主机ip
    ##生产环境推荐使用阿里云高可用redis服务并设置密码
    host: 127.0.0.1
    port: 6379
    database: 0
    password: 123456
    ssl: false
    ##redis 集群环境配置
    #cluster:
    #  nodes: 127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003
    #  commandTimeout: 5000
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://xxxxx:3306/springboot?useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=GMT%2B8&useCursorFetch=true
    username: xxxxx
    password: xxxxxx

mybatis:
  type-aliases-package: com.example.backend.pojo
  mapper-locations: classpath:mappers/*Mapper
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    com.example.backend.mapper: debug

前端发送axios调用接口

安装axios

npm install axios

封装axios请求

import axios from 'axios'
import router from '../router'; 

//创建axios实例
const request = axios.create({
    baseURL: 'http://localhost:8085',
    timeout: 5000
})
  
  // request 拦截器
  // 可以自动发送请求前进行一些处理
  // 比如统一加token, 对请求参数统一加密
  request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    
    // 从sessionStorage中获取token
    const token = sessionStorage.getItem('token');
    if (token) {
      config.headers['token'] = token; // 如果token存在,让每个请求头携带token
    } else {
      // token不存在,重定向到登录页面
      window.location.href = '/'; // 或使用 Vue Router的 this.$router.push('/login')
      //this.$router.push('/')
    }
    
    return config;
  }, error => {
    return Promise.reject(error);
  });
  
  // response 拦截器
  // 可以在接口响应后统一处理结果
  request.interceptors.response.use(
    response => {
      let res = response.data;
      // 若返回的是字符串则转换成json
      if (typeof res === 'string') {
        res = res ? JSON.parse(res) : res//将JSON字符串转JS对象 方便点出来
      }
      return res;
    },
    error => {
      console.log('err' + error) // for debug
      return Promise.reject(error)
    }
  );
  
  export default request;//导出  其他页面引用
  

对表格页面进行修改

改成动态数据

引入axios请求

按条件查询

添加清空按钮

分页查询

前端代码

后端引入分页依赖pagehelp

实体类

持久层

业务层

控制器

返回结果类

新增和编辑

注意后面用用户登录  没有对name进行唯一约束  编码也没有   这个注意下

Dialog对话框

message消息提示

前端新增和编辑代码 

<template>
    <!-- 需要有一个根div -->
    <div>
        <div style="margin-bottom: 20px;">
            <el-input style="width: 300px;" v-model="params.name" placeholder="请输入姓名"></el-input>
            <el-input style="width: 300px; margin-left: 20px;" v-model="params.phone" placeholder="请输入电话"></el-input>
            <el-button style="margin-left: 20px;" @click="findBySearch(1)">查询</el-button>
            <el-button style="margin-left: 20px;" @click="reset">清空</el-button><br>
            <el-button type="primary" style="margin-top: 20px;" @click="addForm">新增</el-button>
            <hr />
        </div>
        <div>
            <el-table :data="tableData" style="width: 100%">
                <el-table-column prop="name" label="姓名" width="180">
                </el-table-column>
                <el-table-column prop="password" label="密码" width="180">
                </el-table-column>
                <el-table-column prop="age" label="年龄">
                </el-table-column>
                <el-table-column prop="phone" label="电话">
                </el-table-column>

                <el-table-column label="操作">
                    <template slot-scope="aaa">
                        <el-button type="primary" @click="edit(aaa.row)">编辑</el-button>
                        <el-button type="danger">删除</el-button>
                    </template>
                </el-table-column>
            </el-table>
        </div>

        <div>
            <el-pagination style="margin-top: 20px;" @size-change="handleSizeChange"
                @current-change="handleCurrentChange" :current-page="params.pageNum" :page-sizes="[5, 10, 15, 20]"
                :page-size="params.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="totalNum">
            </el-pagination>
        </div>

        <div>
            <el-dialog :title="dialogTitle" :visible.sync="dialogFormVisible" @close="resetForm">
                <el-form :model="form">
                    <el-form-item label="姓名" :label-width="formLabelWidth">
                        <el-input v-model="form.name" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="年龄" :label-width="formLabelWidth">
                        <el-input v-model="form.age" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="密码" :label-width="formLabelWidth">
                        <el-input v-model="form.password" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="电话" :label-width="formLabelWidth">
                        <el-input v-model="form.phone" autocomplete="off"></el-input>
                    </el-form-item>
                </el-form>
                <div slot="footer" class="dialog-footer">
                    <el-button @click="dialogFormVisible = false">取 消</el-button>
                    <el-button type="primary" @click="submit" :disabled="isSubmitting">确 定</el-button>
                </div>
            </el-dialog>
        </div>
    </div>
</template>
<script>
import request from '@/api/HttpRequest'
export default {
    name: "AdminView",
    data() {
        return {
            dialogFormVisible: false,
            isEdit: false, // 标志是编辑状态还是新增状态
            dialogTitle: '', // 对话框标题
            params: {
                name: '',
                phone: '',
                pageNum: 1,
                pageSize: 5,
            },
            totalNum: 0,
            tableData: [],
            form: {

            },
            formLabelWidth: '120px',
            isSubmitting: false  //提交标志
        }
    },
    created() {
        this.findBySearch()
    },
    methods: {
        
        resetForm() {
            this.form = {
                name: '',
                password: '',
                age: '',
                phone: ''
            };
        },
        reset() {
            this.params.name = ''
            this.params.phone = ''
            this.findBySearch()
        },
        findBySearch(val) {
            if (val == 1) {
                this.params.pageNum = 1
                this.params.pageSize = 5
            }
            request.get("/test/search", {
                params: this.params
            }).then(res => {
                if (res.code == 200) {
                    this.tableData = res.data.list
                    this.totalNum = res.data.total
                } else {

                }
            })
        },
        addForm() {
            this.resetForm(); // 重置表单
            this.isEdit = false; // 设置为新增模式
            this.dialogTitle = '新增管理员信息'; // 设置对话框标题
            this.dialogFormVisible = true; // 显示对话框
        },
        edit(row) {
            this.isEdit = true; // 设置为编辑模式
            this.dialogTitle = '更新管理员信息'; // 设置对话框标题
            this.form = Object.assign({}, row); // 深拷贝行数据到表单
            //this.form=row
            this.dialogFormVisible = true; // 显示对话框
        },


        submit() {
            if (this.isSubmitting) return;

            this.isSubmitting = true;
            const api = this.isEdit ? "/test/updateUser" : "/test/adduser";
            request.post(api, this.form).then(res => {
                if (res.code == 200) {
                    this.$message({
                        message: this.isEdit ? '更新成功' : '新增成功',
                        type: 'success'
                    });
                    this.dialogFormVisible = false;
                    this.resetForm()
                    this.findBySearch(1);
                } else {
                    this.$message.error('出错了,请联系管理员');
                }
            }).catch(error => {
                console.error("请求失败", error);
                this.$message.error('网络错误,请稍后再试');
            }).finally(() => {
                this.isSubmitting = false;
            });
        },

        handleSizeChange(val) {
            this.params.pageSize = val
            this.findBySearch()
        },
        handleCurrentChange(val) {
            this.params.pageNum = val
            this.findBySearch()
        }
    },
}
</script>

后端新增和编辑代码  其实新增和编辑可以用一个接口  更具实体类id是否为null既可以判断是新增操作还是更新操作

持久层

package com.example.backend.mapper;

import com.example.backend.pojo.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

/**
 * @author hrui
 * @date 2024/3/13 5:28
 */
public interface UserMapper{


    @Select("select * from t_user")
    List<User> selectAllUser();

    @Select({"<script>" +
            "select * from t_user" +
            "<where>"+
            "<if test='name!=null and name!=\"\" '>" +
//                "and name=#{name}"+
            "and name like concat('%',#{name},'%')"+
            "</if>"+
            "<if test='phone!=null and phone!=\"\" '>" +
                "and phone=#{phone}"+
            "</if>"+
            "</where>"+
            "</script>"})
    List<User> selectUserByCondition(User user);


    @Insert({
            "<script>",
            "insert into t_user",
            "<trim prefix='(' suffix=')' suffixOverrides=','>",
            "id,",
            "<if test='name!=null and name!=\"\"'>",
            "name,",
            "</if>",
            "<if test='password!=null and password!=\"\"'>",
            "password,",
            "</if>",
            "<if test='age!=null and age!=\"\"'>",
            "age,",
            "</if>",
            "<if test='phone!=null and phone!=\"\"'>",
            "phone,",
            "</if>",
            "</trim>",
            "values",
            "<trim prefix='(' suffix=')' suffixOverrides=','>",
            "null,",
            "<if test='name!=null and name!=\"\"'>",
            "#{name},",
            "</if>",
            "<if test='password!=null and password!=\"\"'>",
            "#{password},",
            "</if>",
            "<if test='age!=null and age!=\"\"'>",
            "#{age},",
            "</if>",
            "<if test='phone!=null and phone!=\"\"'>",
            "#{phone},",
            "</if>",
            "</trim>",
            "</script>"
    })
    int insertUser(User user);


    @Update({
            "<script>",
            "UPDATE t_user",
            "<set>",
            "<if test='name!=null and name!=\"\"'>",
            "name = #{name},",
            "</if>",
            "<if test='password!=null and password!=\"\"'>",
            "password = #{password},",
            "</if>",
            "<if test='age!=null and age!=\"\"'>",
            "age = #{age},",
            "</if>",
            "<if test='phone!=null and phone!=\"\"'>",
            "phone = #{phone},",
            "</if>",
            "</set>",
            "WHERE id = #{id}",
            "</script>"
    })
    int updateUser(User user);

}

删除

删除操作一般需要提示用户确认

删除代码

<template>
    <!-- 需要有一个根div -->
    <div>
        <div style="margin-bottom: 20px;">
            <el-input style="width: 300px;" v-model="params.name" placeholder="请输入姓名"></el-input>
            <el-input style="width: 300px; margin-left: 20px;" v-model="params.phone" placeholder="请输入电话"></el-input>
            <el-button style="margin-left: 20px;" @click="findBySearch(1)">查询</el-button>
            <el-button style="margin-left: 20px;" @click="reset">清空</el-button><br>
            <el-button type="primary" style="margin-top: 20px;" @click="addForm">新增</el-button>
            <hr />
        </div>
        <div>
            <el-table :data="tableData" style="width: 100%">
                <el-table-column prop="name" label="姓名" width="180">
                </el-table-column>
                <el-table-column prop="password" label="密码" width="180">
                </el-table-column>
                <el-table-column prop="age" label="年龄">
                </el-table-column>
                <el-table-column prop="phone" label="电话">
                </el-table-column>

                <el-table-column label="操作">
                    <template slot-scope="scope">
                        <el-button type="primary" @click="edit(scope.row)">编辑</el-button>

                        <el-popconfirm title="确定要删除这条记录吗?" @confirm="deleteRow(scope.row.id)" confirm-button-text="确定"
                            cancel-button-text="取消">
                            <template #reference>
                                <el-button type="danger" style="margin-left: 10px;">删除</el-button>
                            </template>
                        </el-popconfirm>
                    </template>
                </el-table-column>
            </el-table>
        </div>

        <div>
            <el-pagination style="margin-top: 20px;" @size-change="handleSizeChange"
                @current-change="handleCurrentChange" :current-page="params.pageNum" :page-sizes="[5, 10, 15, 20]"
                :page-size="params.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="totalNum">
            </el-pagination>
        </div>

        <div>
            <el-dialog :title="dialogTitle" :visible.sync="dialogFormVisible" @close="resetForm">
                <el-form :model="form">
                    <el-form-item label="姓名" :label-width="formLabelWidth">
                        <el-input v-model="form.name" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="年龄" :label-width="formLabelWidth">
                        <el-input v-model="form.age" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="密码" :label-width="formLabelWidth">
                        <el-input v-model="form.password" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="电话" :label-width="formLabelWidth">
                        <el-input v-model="form.phone" autocomplete="off"></el-input>
                    </el-form-item>
                </el-form>
                <div slot="footer" class="dialog-footer">
                    <el-button @click="dialogFormVisible = false">取 消</el-button>
                    <el-button type="primary" @click="submit" :disabled="isSubmitting">确 定</el-button>
                </div>
            </el-dialog>
        </div>
    </div>
</template>
<script>
import request from '@/api/HttpRequest'
export default {
    name: "AdminView",
    data() {
        return {
            dialogFormVisible: false,
            isEdit: false, // 标志是编辑状态还是新增状态
            dialogTitle: '', // 对话框标题
            params: {
                name: '',
                phone: '',
                pageNum: 1,
                pageSize: 5,
            },
            totalNum: 0,
            tableData: [],
            form: {

            },
            formLabelWidth: '120px',
            isSubmitting: false  //提交标志
        }
    },
    created() {
        this.findBySearch()
    },
    methods: {

        resetForm() {
            this.form = {
                name: '',
                password: '',
                age: '',
                phone: ''
            };
        },
        reset() {
            this.params.name = ''
            this.params.phone = ''
            this.findBySearch()
        },
        findBySearch(val) {
            if (val == 1) {
                this.params.pageNum = 1
                this.params.pageSize = 5
            }
            request.get("/test/search", {
                params: this.params
            }).then(res => {
                if (res.code == 200) {
                    this.tableData = res.data.list
                    this.totalNum = res.data.total
                } else {

                }
            })
        },
        addForm() {
            this.resetForm(); // 重置表单
            this.isEdit = false; // 设置为新增模式
            this.dialogTitle = '新增管理员信息'; // 设置对话框标题
            this.dialogFormVisible = true; // 显示对话框
        },
        edit(row) {
            this.isEdit = true; // 设置为编辑模式
            this.dialogTitle = '更新管理员信息'; // 设置对话框标题
            this.form = Object.assign({}, row); // 深拷贝行数据到表单
            //this.form=row
            this.dialogFormVisible = true; // 显示对话框
        },
        deleteRow(id) {
            // 这里应该调用API来删除row代表的数据
            // 例如:
            this.isSubmitting = true;
            request.delete(`/test/deleteUser/${id}`).then(res => {
                if (res.code == 200) {
                    this.$message.success('删除成功');
                    this.findBySearch(); // 重新加载当前页的数据或者根据需要更新视图
                } else {
                    this.$message.error('删除失败,请稍后再试');
                }
            }).catch(error => {
                console.error("请求失败", error);
                this.$message.error('网络错误,请稍后再试');
            }).finally(() => {
                this.isSubmitting = false;
            });
        },

        submit() {
            if (this.isSubmitting) return;

            this.isSubmitting = true;
            const api = this.isEdit ? "/test/updateUser" : "/test/adduser";
            request.post(api, this.form).then(res => {
                if (res.code == 200) {
                    this.$message({
                        message: this.isEdit ? '更新成功' : '新增成功',
                        type: 'success'
                    });
                    this.dialogFormVisible = false;
                    this.resetForm()
                    this.findBySearch(1);
                } else {
                    this.$message.error('出错了,请联系管理员');
                }
            }).catch(error => {
                console.error("请求失败", error);
                this.$message.error('网络错误,请稍后再试');
            }).finally(() => {
                this.isSubmitting = false;
            });
        },

        handleSizeChange(val) {
            this.params.pageSize = val
            this.findBySearch()
        },
        handleCurrentChange(val) {
            this.params.pageNum = val
            this.findBySearch()
        }
    },
}
</script>

后端代码

持久层

关于#号

和历史模式有关

加上使用历史模式  mode:'history'就不会出现了

登录页面

路由级别

以上所有操作都在大布局下  路由也在同一级别

要想将登录页独立的一个页面

那么要将路由分级

首先个Layout.vue用来存放 原来app.vue路由

<template>
    <div>
        <el-container>
            <el-header style="background-color:blanchedalmond; display: flex; align-items: center; padding-left: 0px;">
                <img src="@/assets/logo.jpg" alt="" style="width:250px; margin-left: 0px;">
            </el-header>
        </el-container>
        <el-container>
            <el-aside style="overflow:hidden;min-height: 100vh;background-color:blanchedalmond;width: 250px;">
                <!-- default-active="1"默认是哪个 background-color="blanchedalmond" 背景颜色 text-color="brown" 字体颜色 active-text-color="blue" 选中字体颜色 -->
                <el-menu :default-active="$route.path" router background-color="blanchedalmond" text-color="brown"
                    active-text-color="blue">
                    <el-menu-item index="/">
                        <i class="el-icon-menu"></i>
                        <span slot="title">系统首页</span>
                    </el-menu-item>
                    <el-submenu index="2">
                        <template slot="title">
                            <i class="el-icon-location"></i>
                            <span>用户管理</span>
                        </template>
                        <el-menu-item-group>
                            <el-menu-item index="/admin">管理员信息</el-menu-item>
                            <el-menu-item index="2-2">用户信息</el-menu-item>
                        </el-menu-item-group>
                    </el-submenu>
                    <el-submenu index="3">
                        <template slot="title">
                            <i class="el-icon-location"></i>
                            <span>信息管理</span>
                        </template>
                        <el-menu-item-group>
                            <el-menu-item index="3-1">xxxx信息</el-menu-item>


                            <el-menu-item index="3-2">yyyy信息</el-menu-item>
                        </el-menu-item-group>
                    </el-submenu>
                </el-menu>
            </el-aside>
            <el-main>
                <!-- 我是入口 -->
                <router-view />
            </el-main>
        </el-container>
    </div>
</template>

<script>
export default {
    name: 'Layout'

}
</script>

<style>
.el-menu{
  border-right: none !important;
}
</style>

将app.vue即程序入口还原成原来样子

<template>
  <div id="app">
    <router-view/>
  </div>
</template>



<style>

</style>

代码结构

修改路由 router下的index.js文件

import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import LoginView from '../views/LoginView.vue'
Vue.use(VueRouter)

const routes = [
  {
    path: '/login',
    name: 'login',
    component: LoginView
  },
  {
    path: '/',
    name: 'Layout',
    component: () => import('../views/Layout.vue'),
    children:[
      {
        path: '/',
        name: 'home',
        component: HomeView
      },
      {
        path: '/about',
        name: 'about',
        //懒加载
        component: () => import('../views/AboutView.vue')
      },
     {
        path: '/message',
        name: 'message',
        //懒加载
        component: () => import('../views/MessageView.vue')
      },
    
      {
        path: '/admin',
        name: 'admin',
        //懒加载
        component: () => import('../views/AdminView.vue')
      }
    ]
  }, 
]

const router = new VueRouter({
  mode: 'history',
  routes
})

export default router

编写登录页面 loginView.vue

访问http://localhost:8081/login

全局异常处理

全局异常处理@ControllerAdvice可以指定包名或者类名

package com.example.backend.exception;

import com.example.backend.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

/**
 * @author hrui
 * @date 2024/3/14 11:26
 */
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {


    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result error(HttpServletRequest request,Exception e){
        log.error("异常信息:", e);
        return Result.error("系统异常", null);
    }

    @ExceptionHandler(CustomException.class)
    @ResponseBody
    public Result customError(HttpServletRequest request,CustomException e){
        log.error("CustomException异常信息:", e.getMessage());
        return Result.error(e.getMessage(), null);
    }

}

注册页面

<template>
    <div class="container">
        <div class="register-box">
            <form @submit.prevent="onRegister" class="register-form">
                <h2>注册</h2>
                <div class="form-group">
                    <input v-model="registerForm.name" type="text" placeholder="用户名" @blur="checkUsernameAvailability"
                        required>
                    <span v-if="usernameAvailable" style="color: red;">{{ usernameErrorMessage }}</span>
                </div>
                <div class="form-group">
                    <input v-model="registerForm.password" type="password" placeholder="密码" required>
                </div>
                <div class="form-group">
                    <input v-model="registerForm.phone" type="text" placeholder="手机号" required>
                </div>
                <div class="form-group action">
                    <!-- <button type="button" @click="triggerSlideVerification">获取短信验证码</button> -->
                    <button @click="triggerSlideVerification" :disabled="isSMSButtonDisabled">{{ smsButtonText }}</button>
                </div>
                <div class="form-group action">
                    <button type="submit">注册</button>
                </div>
                <div class="form-group action">
                    <button type="button" @click="goToLogin">已有账号?登录</button>
                </div>
            </form>
        </div>
    </div>
</template>

<script>
import request from '@/api/HttpRequest'

export default {
    name: 'RegisterView',
    data() {
        return {
            registerForm: {
                name: '',
                password: '',
                phone: ''
            },
            usernameAvailable: false,//用户名是否可用标识
            usernameErrorMessage: '',//不可用显示
            // 添加滑动验证的可见性状态
            isSlideVerificationVisible: false, // 控制滑动验证窗口的显示
            isSMSButtonDisabled: false, // 控制获取短信验证码按钮的可用状态
            countdown: 0,
            smsButtonText: '获取短信验证码',
        };
    },
    methods: {
        triggerSlideVerification() {
            // 显示滑动验证(这里仅为示例,具体实现取决于所用的滑动验证库)
            this.isSlideVerificationVisible = true;
            request.get("/api/checkimg").then(res=>{
                if(res.code==200){
                    console.log("data",res.data)
                }
            })
            // 假设滑动验证通过后调用 getSMSCode 方法
            this.getSMSCode();

            //this.startCountdown(); // 假设滑动验证通过
        },
        getSMSCode() {
            // 在这里调用获取短信验证码的API
            console.log("滑动验证通过,现在获取短信验证码");
            // 假设滑动验证通过,隐藏滑动验证弹窗
            this.slideVerifyVisible = false;
        },
        startCountdown() {
            if (this.countdown > 0) return; // 防止重复点击
            this.isSMSButtonDisabled = true; // 禁用获取短信验证码的按钮
            this.countdown = 60;
            let intervalId = setInterval(() => {
                this.countdown--;
                this.smsButtonText = `${this.countdown}秒后可重发`;
                if (this.countdown <= 0) {
                    clearInterval(intervalId);
                    this.smsButtonText = '获取短信验证码';
                    this.isSMSButtonDisabled = false; // 启用按钮
                    this.isSlideVerificationVisible = false; // 隐藏滑动验证窗口
                }
            }, 1000);
        },

        onRegister() {
            // 首先检查用户名是否可用
            if (!this.usernameAvailable) {
                this.$message.error('用户名不可用,请更换用户名');
                return;
            }

            // 发送POST请求到后端的注册API
            request.post('/api/register', this.registerForm)
                .then(response => {
                    // 处理注册成功的情况
                    if (response.code == 200) {
                        this.$message.success('注册成功');
                        // 注册成功后,可以选择跳转到登录页面或其他页面
                        this.$router.push('/login');
                    } else {
                        // 后端返回了错误状态,显示错误信息
                        this.$message.error(response.msg || '注册失败');
                    }
                })
                .catch(error => {
                    console.error('注册请求失败', error);
                    this.$message.error('注册请求失败,请稍后再试');
                });
        },

        checkUsernameAvailability() {
            //alert(111)
            if (this.registerForm.name.trim() === '') {
                this.usernameAvailable = true;
                this.usernameErrorMessage = '用户名不能为空';
                return;
            }
            request.get('/api/usernamecheck', {
                params: {
                    name: this.registerForm.name
                }
            })
                .then(response => {
                    if (response.code === 200) {
                        this.usernameAvailable = false;
                        this.usernameErrorMessage = '';
                    } else {
                        this.usernameAvailable = true;
                        this.usernameErrorMessage = response.msg;
                    }
                })
                .catch(error => {
                    console.error('请求错误', error);
                    this.$message.error('验证用户名时发生错误');

                });
        },
        goToLogin() {
            // 跳转到登录页面
            this.$router.push('/login');
        }
    },
};
</script>

<style scoped>
.container {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
}

.register-box {
    width: 100%;
    max-width: 400px;
    padding: 20px;
    border: 1px solid #ccc;
    border-radius: 5px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, .1);
}

.register-form {
    display: flex;
    flex-direction: column;
}

.form-group {
    margin-bottom: 20px;
}

input[type="text"],
input[type="password"] {
    width: 100%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
}

.action {
    display: flex;
    justify-content: center;
}

button[type="submit"],
button[type="button"] {
    cursor: pointer;
    padding: 10px 20px;
    margin-right: 10px;
    background-color: #007bff;
    border: none;
    border-radius: 4px;
    color: white;
}

h2 {
    text-align: center;
    margin-bottom: 20px;
}
</style>

用户登录之后到home页面 右上角退出功能

路由守卫

以上的页面  其实任何页面都可以进去

路由守卫就是对路由跳转进行身份验证(权限控制)

就是说你的地址符合我定义的规则 就允许访问

在路由配置文件里配置路由守卫

但是这种方式很不安全  因为用户可以自己设置

JWT  Token验证

就是因为上面路由守卫不安全   还是需要后端验证

大概思路是用户在登录页面登录之后  服务端会返回一个Token

客户端每次请求需要在请求头携带该Token,后端验证

通过就是可以访问,不通过返回登录页

给所有接口加上统一的调用前缀/api然后统一拦截该前缀的的请求

这样在前端的baseUrl中也需要添加

配置WebMvcConfigurer

 

JWT工具类

package com.example.backend.common;


import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.example.backend.pojo.User;
import com.example.backend.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * @author hrui
 * @date 2024/3/15 0:54
 */
@Component
@Slf4j
public class JwtTokenUtils {

    //主要是为了静态方法中使用Spring管理的bean  赋值后可以在静态方法这种使用  用起来方便 
    private static UserService staticUserService;

    @Autowired
    private UserService userService;

    @PostConstruct
    public void setUserService(){
        staticUserService=userService;
    }

    /**
     * 生成Token
     */
    public static String genToken(String userId,String sign){
        return JWT.create().withAudience(userId)//将用户id保存到token做为荷载
                .withExpiresAt(DateUtil.offsetMinute(new Date(), 15))//15分钟过期
                .sign(Algorithm.HMAC256(sign));//以password做为Token密钥
    }

    /**
     * 获取当前登录用户信息
     */
    /**
     * 获取当前登录的用户信息
     */
    public static User getCurrentUser() {
        String token = null;
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            token = request.getHeader("token");
            if (StrUtil.isBlank(token)) {
                token = request.getParameter("token");
            }
            if (StrUtil.isBlank(token)) {
                log.error("获取头部token为空,token: {}", token);
                return null;
            }
            // 解析token,获取用户的id
            String userId = JWT.decode(token).getAudience().get(0);
            return staticUserService.findById(Integer.valueOf(userId));
        } catch (Exception e) {
            log.error("获取当前登录的用户信息失败, token={}", token, e);
            return null;
        }
    }

}

将token放到user中

前端处理

那么每次请求过来在拦截器进行校验

文件上传和下载

CREATE TABLE `t_book` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '书名',
  `price` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '价格',
  `author` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '作者',
  `press` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '出版社',
  `image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '封面',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

前端代码
<template>
    <!-- 需要有一个根div -->
    <div>
        <div style="margin-bottom: 20px;">
            <el-input style="width: 300px;" v-model="params.name" placeholder="请输入书名"></el-input>
            <el-input style="width: 300px; margin-left: 20px;" v-model="params.press" placeholder="请输入出版社"></el-input>
            <el-button style="margin-left: 20px;" @click="findBySearch(1)">查询</el-button>
            <el-button style="margin-left: 20px;" @click="reset">清空</el-button><br>
            <el-button type="primary" style="margin-top: 20px;" @click="addForm">新增</el-button>
            <hr />
        </div>
        <div>
            <el-table :data="tableData" style="width: 100%">
                <el-table-column prop="name" label="书名" width="180">
                </el-table-column>
                <el-table-column prop="price" label="价格" width="180">
                </el-table-column>
                <el-table-column prop="author" label="作者">
                </el-table-column>
                <el-table-column prop="press" label="出版社">
                </el-table-column>
                <el-table-column label="图片封面">
                    <template slot-scope="scope">
                        <el-image style="width: 100px; height: 100px;border-radius: 50%;" :src="'http://localhost:8085/api/file/' + scope.row.image"   :preview-src-list="['http://localhost:8085/api/file/' + scope.row.image]"></el-image>
                    </template>
                </el-table-column>
                <el-table-column label="操作">
                    <template slot-scope="scope">
                        <el-button type="primary" @click="edit(scope.row)">编辑</el-button>

                        <el-popconfirm title="确定要删除这条记录吗?" @confirm="deleteRow(scope.row.id)" confirm-button-text="确定"
                            cancel-button-text="取消">
                            <template #reference>
                                <el-button type="danger" style="margin-left: 10px;">删除</el-button>
                            </template>
                        </el-popconfirm>
                    </template>
                </el-table-column>
            </el-table>
        </div>

        <div>
            <el-pagination style="margin-top: 20px;" @size-change="handleSizeChange"
                @current-change="handleCurrentChange" :current-page="params.pageNum" :page-sizes="[5, 10, 15, 20]"
                :page-size="params.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="totalNum">
            </el-pagination>
        </div>

        <div>
            <el-dialog :title="dialogTitle" :visible.sync="dialogFormVisible" @close="resetForm">
                <el-form :model="form">
                    <el-form-item label="书名" :label-width="formLabelWidth">
                        <el-input v-model="form.name" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="价格" :label-width="formLabelWidth">
                        <el-input v-model="form.price" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="作者" :label-width="formLabelWidth">
                        <el-input v-model="form.author" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="出版社" :label-width="formLabelWidth">
                        <el-input v-model="form.press" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="图书封面" :label-width="formLabelWidth">
                        <!-- :headers="uploadHeaders" 可以动态加上请求头token  或者 后端放行 -->
                        <el-upload class="upload-demo" action="http://localhost:8085/api/file/upload"
                            :headers="uploadHeaders" :on-error="errorUpload" :on-success="successUpload">
                            <el-button size="small" type="primary">点击上传</el-button>
                        </el-upload>
                    </el-form-item>
                </el-form>
                <div slot="footer" class="dialog-footer">
                    <el-button @click="dialogFormVisible = false">取 消</el-button>
                    <el-button type="primary" @click="submit" :disabled="isSubmitting">确 定</el-button>
                </div>
            </el-dialog>
        </div>
    </div>
</template>
<script>
import request from '@/api/HttpRequest'
export default {
    name: "BookView",
    data() {
        return {
            dialogFormVisible: false,
            isEdit: false, // 标志是编辑状态还是新增状态
            dialogTitle: '', // 对话框标题
            params: {
                name: '',
                press: '',
                pageNum: 1,
                pageSize: 5,
            },
            uploadHeaders: {
                'token': sessionStorage.getItem('token')
            },
            totalNum: 0,
            tableData: [],
            form: {

            },
            formLabelWidth: '120px',
            isSubmitting: false  //提交标志
        }
    },
    created() {
        this.findBySearch()
    },
    methods: {
        //文件上传成功后的钩子  
        successUpload(res) {
            //console.log(res)
            if (res.code == 200) {
                this.form.image = res.data
            }

        },
        errorUpload(res) {
            console(res)
        },
        resetForm() {
            this.form = {
                name: '',
                price: '',
                author: '',
                press: '',
                image: ''
            };
        },
        reset() {
            this.params.name = ''
            this.params.press = ''
            this.findBySearch()
        },
        findBySearch(val) {
            if (val == 1) {
                this.params.pageNum = 1
                this.params.pageSize = 5
            }
            request.get("/book/findBooks", {
                params: this.params
            }).then(res => {
                if (res.code == 200) {
                    this.tableData = res.data.list
                    this.totalNum = res.data.total
                } else {

                }
            })
        },
        addForm() {
            this.resetForm(); // 重置表单
            this.isEdit = false; // 设置为新增模式
            this.dialogTitle = '新增管理员信息'; // 设置对话框标题
            this.dialogFormVisible = true; // 显示对话框
        },
        edit(row) {
            this.isEdit = true; // 设置为编辑模式
            this.dialogTitle = '更新管理员信息'; // 设置对话框标题
            this.form = Object.assign({}, row); // 深拷贝行数据到表单
            //this.form=row
            this.dialogFormVisible = true; // 显示对话框
        },
        deleteRow(id) {
            // 这里应该调用API来删除row代表的数据
            // 例如:
            this.isSubmitting = true;
            request.delete(`/book/deleteBook/${id}`).then(res => {
                if (res.code == 200) {
                    this.$message.success('删除成功');
                    this.findBySearch(); // 重新加载当前页的数据或者根据需要更新视图
                } else {
                    this.$message.error('删除失败,请稍后再试');
                }
            }).catch(error => {

                this.$message.error('网络错误,请稍后再试');
            }).finally(() => {
                this.isSubmitting = false;
            });
        },

        submit() {
            if (this.isSubmitting) return;

            this.isSubmitting = true;
            const api = this.isEdit ? "/book/save" : "/book/save";
            request.post(api, this.form).then(res => {
                if (res.code == 200) {
                    this.$message({
                        message: this.isEdit ? '更新成功' : '新增成功',
                        type: 'success'
                    });
                    this.dialogFormVisible = false;
                    this.resetForm()
                    this.findBySearch(1);
                } else {
                    this.$message.error('出错了,请联系管理员');
                }
            }).catch(error => {

                this.$message.error('网络错误,请稍后再试');
            }).finally(() => {
                this.isSubmitting = false;
            });
        },

        handleSizeChange(val) {
            this.params.pageSize = val
            this.findBySearch()
        },
        handleCurrentChange(val) {
            this.params.pageNum = val
            this.findBySearch()
        }
    },
}
</script>
后端代码

控制器

package com.example.backend.controller;

import com.example.backend.common.Result;
import com.example.backend.pojo.Book;
import com.example.backend.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author hrui
 * @date 2024/3/15 23:24
 */
@RestController
@RequestMapping("/book")
public class BookController {


    @Autowired
    private BookService bookService;

    @RequestMapping("/save")
    public Result saveBook(@RequestBody Book book){
//        if(book.getId()==null){
//            int i = bookService.insertBook(book);
//            if(i>0){
//                return Result.success("新增成功", null);
//            }else{
//                return Result.error("新增失败", null);
//            }
//        }else{
//            int i=bookService.updateBook(book);
//            if(i>0){
//                return Result.success("更新成功", null);
//            }else{
//                return Result.error("更新失败", null);
//            }
//        }
        boolean success = book.getId() == null ? bookService.insertBook(book) > 0 : bookService.updateBook(book) > 0;
        return success ? Result.success("操作成功", null) : Result.error("操作失败", null);
    }

    @RequestMapping("/findBooks")
    public Result findBooks(Book book){
        return Result.success("查询成功", bookService.findBooks(book));
    }


    @RequestMapping("/deleteBook/{id}")
    public Result deleteBookById(@PathVariable Integer id){
      return bookService.deleteBookById(id)>0?Result.success("删除成功", null):Result.error("删除失败", null);
    }
}

业务层

package com.example.backend.service.impl;

import com.example.backend.mapper.BookMapper;
import com.example.backend.pojo.Book;
import com.example.backend.pojo.User;
import com.example.backend.service.BookService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author hrui
 * @date 2024/3/15 23:25
 */
@Service
public class BookServiceImpl implements BookService {


    @Autowired
    private BookMapper bookMapper;

    @Override
    public int insertBook(Book book) {
        return bookMapper.insertBook(book);
    }

    @Override
    public int updateBook(Book book) {
        return bookMapper.updateBook(book);
    }

    @Override
    public PageInfo<Book> findBooks(Book book) {
        Page<Object> objects = PageHelper.startPage(book.getPageNum(), book.getPageSize());
        return new PageInfo<>(bookMapper.selectBookByCondition(book));
    }

    @Override
    public int deleteBookById(Integer id) {
        return bookMapper.deleteBookById(id);
    }
}

持久层

package com.example.backend.mapper;

import com.example.backend.pojo.Book;
import com.example.backend.pojo.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

/**
 * @author hrui
 * @date 2024/3/15 23:24
 */
public interface BookMapper {


    @Insert({
            "<script>",
            "insert into t_book",
            "<trim prefix='(' suffix=')' suffixOverrides=','>",
            "id,",
            "<if test='name!=null and name!=\"\"'>",
            "name,",
            "</if>",
            "<if test='price!=null and price!=\"\"'>",
            "price,",
            "</if>",
            "<if test='author!=null and author!=\"\"'>",
            "author,",
            "</if>",
            "<if test='press!=null and press!=\"\"'>",
            "press,",
            "</if>",
            "<if test='image!=null and image!=\"\"'>",
            "image,",
            "</if>",
            "</trim>",
            "values",
            "<trim prefix='(' suffix=')' suffixOverrides=','>",
            "null,",
            "<if test='name!=null and name!=\"\"'>",
            "#{name},",
            "</if>",
            "<if test='price!=null and price!=\"\"'>",
            "#{price},",
            "</if>",
            "<if test='author!=null and author!=\"\"'>",
            "#{author},",
            "</if>",
            "<if test='press!=null and press!=\"\"'>",
            "#{press},",
            "</if>",
            "<if test='image!=null and image!=\"\"'>",
            "#{image},",
            "</if>",
            "</trim>",
            "</script>"
    })
    int insertBook(Book book);

    @Update({
            "<script>",
            "UPDATE t_book",
            "<set>",
            "<if test='name!=null and name!=\"\"'>",
            "name = #{name},",
            "</if>",
            "<if test='price!=null and price!=\"\"'>",
            "price = #{price},",
            "</if>",
            "<if test='author!=null and author!=\"\"'>",
            "author = #{author},",
            "</if>",
            "<if test='press!=null and press!=\"\"'>",
            "press = #{press},",
            "</if>",
            "<if test='image!=null and image!=\"\"'>",
            "image = #{image},",
            "</if>",
            "</set>",
            "WHERE id = #{id}",
            "</script>"
    })
    int updateBook(Book book);

//    @Select("select * from t_book")
//    List<Book> selectAllBooks();

    @Select({"<script>" +
            "select * from t_book" +
            "<where>"+
            "<if test='name!=null and name!=\"\" '>" +
//                "and name=#{name}"+
            "and name like concat('%',#{name},'%')"+
            "</if>"+
            "<if test='price!=null and price!=\"\" '>" +
            "and price=#{price}"+
            "</if>"+
            "<if test='author!=null and author!=\"\" '>" +
            "and author=#{author}"+
            "</if>"+
            "<if test='press!=null and press!=\"\" '>" +
//            "and press=#{press}"+
            "and press like concat('%',#{press},'%')"+
            "</if>"+
            "</where>"+
            "</script>"})
    List<Book> selectBookByCondition(Book book);



    @Delete("DELETE FROM t_book WHERE id = #{id}")
    int deleteBookById(Integer id);
}

文件上传

控制器

package com.example.backend.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.example.backend.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;

/**
 * @author hrui
 * @date 2024/3/16 1:47
 */
@RestController
@RequestMapping("/file")
@Slf4j
public class FileController {

    //文件上传存储路径
    private static final String filePath=System.getProperty("user.dir")+"/upload/";
    /**
     * 文件上传
     */
    @PostMapping("/upload")
    public Result upload(MultipartFile file) {
        synchronized (FileController.class) {//加锁 确保文件名不重复
            String flag = System.currentTimeMillis() + "";
            //原始文件名
            String fileName = file.getOriginalFilename();
            try {
                if (!FileUtil.isDirectory(filePath)) {
                    FileUtil.mkdir(filePath);
                }
                // 文件存储位置;原逻辑-文件名
                FileUtil.writeBytes(file.getBytes(), filePath + flag + "-" + fileName);
                System.out.println(fileName + " --上传成功");
                //Thread.sleep(1L);
            } catch (Exception e) {
                System.err.println(fileName + " --文件上传失败");
            }
            return Result.success("上传成功",flag);
        }
    }

    /**
     * 文件下载
     */
    // @GetMapping映射HTTP GET请求到特定的处理方法
// @PathVariable用来接收请求URL中的flag值
    @GetMapping("/{flag}")
    public void avatarPath(@PathVariable String flag, HttpServletResponse response) {
        // 检查filePath是否是目录,如果不是则创建
        if (!FileUtil.isDirectory(filePath)) {
            FileUtil.mkdir(filePath);
        }
        // 用于写入响应流的输出流
        OutputStream os;
        // 获取filePath路径下的所有文件名
        List<String> fileNames = FileUtil.listFileNames(filePath);
        // 从文件名列表中筛选包含flag的文件名,如果没有找到则返回空字符串
        String avatar = fileNames.stream().filter(name -> name.contains(flag)).findAny().orElse("");
        try {
            // 如果avatar不为空,则执行文件下载操作
            if (StrUtil.isNotEmpty(avatar)) {
                // 设置响应的头部信息,告诉浏览器这是一个附件下载操作,并提供文件名
                response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(avatar, "UTF-8"));
                // 设置响应的内容类型为字节流
                response.setContentType("application/octet-stream");
                // 读取filePath和avatar组合后的文件路径的所有字节
                byte[] bytes = FileUtil.readBytes(filePath + avatar);
                // 获取响应的输出流
                os = response.getOutputStream();
                // 写入字节到响应输出流
                os.write(bytes);
                // 刷新输出流
                os.flush();
                // 关闭输出流
                os.close();
            }
        } catch (Exception e) {
            // 如果文件传输失败,打印失败信息
            System.out.println("文件传输失败");
        }
    }

    public static void main(String[] args) {
        List<String> fileNames = FileUtil.listFileNames(filePath);
    }

}

拦截器放行

实现批量删除

这里存在一个问题

实现Excel导入导出

版本不低于4.1.2

后端

package com.example.backend.controller;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.example.backend.common.Result;
import com.example.backend.exception.ExcelEmptyException;
import com.example.backend.pojo.BookType;
import com.example.backend.service.BookTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author hrui
 * @date 2024/3/16 11:04
 */
@RestController
@RequestMapping("/excel")
public class ExcelController {

    @Autowired
    private BookTypeService bookTypeService;

    @RequestMapping("/bookTypeExcelExport")
    public void booTypeExcelExport(HttpServletResponse response) throws IOException {

        // 1. 从数据库中查询出所有数据
        List<BookType> list=bookTypeService.selectAllBookType();
        if(CollectionUtil.isEmpty(list)){
            throw new ExcelEmptyException("图书分类中没有数据");
        }
        // 2. 定义一个 List 和 Map<key,value> 出来,存储处理之后的数据,用于导出 list 集
        // 3. 遍历每一条数据,然后封装到 Map<key,value> key是列名  value是值  里,把这个 map 集到 list 集
        List<Map<String,Object>> list2=new ArrayList<>();

        for(BookType bt:list){
            Map<String,Object> row=new HashMap<>();
            row.put("图书名称", bt.getName());
            row.put("图书描述",bt.getDescription());
            list2.add(row);
        }
        // 4. 创建一个 ExcelWriter,把 list 集合放入到writer写出(生成数据)
        ExcelWriter writer= ExcelUtil.getWriter(true);
        writer.write(list,true);
        // 5. 把这个 excel 下载下来
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment;filename=bookType.xlsx");
        ServletOutputStream out = response.getOutputStream();
        writer.flush(out, true);
        writer.close();
        IoUtil.close(System.out);
    };


    @PostMapping("/upload")
    public Result excelUpload(MultipartFile file) throws IOException {
        List<BookType> types = ExcelUtil.getReader(file.getInputStream()).readAll(BookType.class);
        if(!CollectionUtil.isEmpty(types)){
            for(BookType t:types){
                try{
                    bookTypeService.insertBookType(t);
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
        }
        return Result.success("导入成功", null);
    }
}

前端

<template>
    <!-- 需要有一个根div -->
    <div>
        <div style="margin-bottom: 20px;">
            <el-input style="width: 300px;" v-model="params.name" placeholder="请输入名称"></el-input>
            <el-input style="width: 300px; margin-left: 20px;" v-model="params.description"
                placeholder="请输入描述"></el-input>
            <el-button style="margin-left: 20px;" @click="findBySearch(1)">查询</el-button>
            <el-button style="margin-left: 20px;" @click="reset">清空</el-button><br>
            <el-button type="primary" style="margin-top: 20px;" @click="addForm">新增</el-button>
            <el-popconfirm title="确定要删除这些记录吗?" @confirm="deleteBatch" confirm-button-text="确定" cancel-button-text="取消">
                <template #reference>
                    <el-button type="danger" style="margin-left: 10px;">批量删除</el-button>
                </template>
            </el-popconfirm>
           
            <el-button type="success" style="margin-left: 10px;" @click="excelExport">Excel导出</el-button>
            <el-upload style="display: inline-block;margin-left: 10px;"  action="http://localhost:8085/api/excel/upload"  :headers="uploadHeaders"  :on-success="successUpload" :show-file-list="false">
                <el-button type="success">Excel导入</el-button>
            </el-upload>
             
            <hr />
        </div>
        <div>
            <el-table :data="tableData" style="width: 100%" @selection-change="handleSelectionChange" row-key="id"
                ref="table">
                <el-table-column type="selection" ref="table" :reserve-selection="true" width="55">
                </el-table-column>
                <el-table-column prop="name" label="分类名称" width="180">
                </el-table-column>

                <el-table-column prop="description" label="分类描述">
                </el-table-column>


                <el-table-column label="操作">
                    <template slot-scope="scope">
                        <el-button type="primary" @click="edit(scope.row)">编辑</el-button>

                        <el-popconfirm title="确定要删除这条记录吗?" @confirm="deleteRow(scope.row.id)" confirm-button-text="确定"
                            cancel-button-text="取消">
                            <template #reference>
                                <el-button type="danger" style="margin-left: 10px;">删除</el-button>
                            </template>
                        </el-popconfirm>
                    </template>
                </el-table-column>
            </el-table>
        </div>

        <div>
            <el-pagination style="margin-top: 20px;" @size-change="handleSizeChange"
                @current-change="handleCurrentChange" :current-page="params.pageNum" :page-sizes="[5, 10, 15, 20]"
                :page-size="params.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="totalNum">
            </el-pagination>
        </div>

        <div>
            <el-dialog :title="dialogTitle" :visible.sync="dialogFormVisible" @close="resetForm">
                <el-form :model="form">
                    <el-form-item label="分类名称" :label-width="formLabelWidth">
                        <el-input v-model="form.name" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="分类描述" :label-width="formLabelWidth">
                        <el-input v-model="form.description" autocomplete="off"></el-input>
                    </el-form-item>

                </el-form>
                <div slot="footer" class="dialog-footer">
                    <el-button @click="dialogFormVisible = false">取 消</el-button>
                    <el-button type="primary" @click="submit" :disabled="isSubmitting">确 定</el-button>
                </div>
            </el-dialog>
        </div>
    </div>
</template>
<script>
import request from '@/api/HttpRequest'
export default {
    name: "BookTypeView",
    data() {
        return {
            uploadHeaders: {
                'token': sessionStorage.getItem('token')
            },
            dialogFormVisible: false,
            isEdit: false, // 标志是编辑状态还是新增状态
            dialogTitle: '', // 对话框标题
            params: {
                name: '',
                description: '',
                pageNum: 1,
                pageSize: 5,
            },
            totalNum: 0,
            tableData: [],
            form: {

            },
            formLabelWidth: '120px',
            isSubmitting: false,  //提交标志
            multipleSelection: []
        }
    },
    created() {
        this.findBySearch()

    },
    methods: {
        handleExceed(files, fileList) {
            this.$message.warning('只能上传一张图片');
        },
        //文件上传成功后的钩子  
        successUpload(res) {
            //console.log(res)
            if (res.code == 200) {
                
                this.$message.success(res.msg)
                this.findBySearch()
            }else{
                this.$message.error(res.msg)
            }

        },
        
        excelExport() {
            location.href='http://localhost:8085/api/excel/bookTypeExcelExport?token='+sessionStorage.getItem("token")  
           
        },
        handleSelectionChange(val) {
            //val本身是个数组 使用 map 函数遍历 val 数组,提取每行数据的 id,并保存到 multipleSelection 数组中
            this.multipleSelection = val.map(item => item.id);
            console.log(JSON.stringify(this.multipleSelection))
        },
        deleteBatch() {
            //判断multipleSelection是否为空
            if (this.multipleSelection.length == 0) {
                this.$message.warning("请勾选要删除的数据")
                return
            }
            request.put('/bookType/deleteBatch', this.multipleSelection).then(res => {
                if (res.code == 200) {
                    this.$message.success("批量删除成功")
                    this.findBySearch()
                } else {
                    this.$message.success(res.msg)
                }
            }).catch(error => {

                this.$message.error('网络错误,请稍后再试');
            }).finally(() => {

            });
        },
        resetForm() {
            this.form = {
                name: '',
                description: '',

            };
        },
        reset() {
            this.params.name = ''
            this.params.description = ''
            this.findBySearch()
        },
        findBySearch(val) {
            if (val == 1) {
                this.params.pageNum = 1
                this.params.pageSize = 5
            }
            request.get("/bookType/findBookType", {
                params: this.params
            }).then(res => {
                if (res.code == 200) {
                    this.tableData = res.data.list
                    this.totalNum = res.data.total
                } else {

                }
            })
        },
        addForm() {
            this.resetForm(); // 重置表单
            this.isEdit = false; // 设置为新增模式
            this.dialogTitle = '新增管理员信息'; // 设置对话框标题
            this.dialogFormVisible = true; // 显示对话框
        },
        edit(row) {
            this.isEdit = true; // 设置为编辑模式
            this.dialogTitle = '更新管理员信息'; // 设置对话框标题
            this.form = Object.assign({}, row); // 深拷贝行数据到表单
            //this.form=row
            this.dialogFormVisible = true; // 显示对话框
        },
        // deleteRow(id) {
        //     // 这里应该调用API来删除row代表的数据
        //     // 例如:
        //     this.isSubmitting = true;
        //     request.delete(`/bookType/deleteBookType/${id}`).then(res => {
        //         if (res.code == 200) {
        //             this.$message.success('删除成功');
        //             this.findBySearch(); // 重新加载当前页的数据或者根据需要更新视图
        //         } else {
        //             this.$message.error('删除失败,请稍后再试');
        //         }
        //     }).catch(error => {

        //         this.$message.error('网络错误,请稍后再试');
        //     }).finally(() => {
        //         this.isSubmitting = false;
        //     });
        // },

        submit() {
            if (this.isSubmitting) return;

            this.isSubmitting = true;
            const api = this.isEdit ? "/bookType/save" : "/bookType/save";
            request.post(api, this.form).then(res => {
                if (res.code == 200) {
                    this.$message({
                        message: this.isEdit ? '更新成功' : '新增成功',
                        type: 'success'
                    });
                    this.dialogFormVisible = false;
                    this.resetForm()
                    this.findBySearch(1);
                } else {
                    this.$message.error('出错了,请联系管理员');
                }
            }).catch(error => {

                this.$message.error('网络错误,请稍后再试');
            }).finally(() => {
                this.isSubmitting = false;
            });
        },

        handleSizeChange(val) {
            this.params.pageSize = val
            this.findBySearch()
        },
        handleCurrentChange(val) {
            this.params.pageNum = val
            this.findBySearch()
        }
    },
}
</script>

模块之间关联关系

将图书分类和图书信息做关联  一般不用外键  主要是添加外键存在强耦合关系

图书分类对应多个图书信息

1对多

在多的一方建个字段关联图书分类表

展示时候数据也可以从后端关联查询出来

<template>
    <!-- 需要有一个根div -->
    <div>
        <div style="margin-bottom: 20px;">
            <el-input style="width: 300px;" v-model="params.name" placeholder="请输入书名"></el-input>
            <el-input style="width: 300px; margin-left: 20px;" v-model="params.press" placeholder="请输入出版社"></el-input>
            <el-button style="margin-left: 20px;" @click="findBySearch(1)">查询</el-button>
            <el-button style="margin-left: 20px;" @click="reset">清空</el-button><br>
            <el-button type="primary" style="margin-top: 20px;" @click="addForm">新增</el-button>
            <hr />
        </div>
        <div>
            <el-table :data="tableData" style="width: 100%">
                <el-table-column prop="name" label="书名" width="180">
                </el-table-column>
                <el-table-column prop="price" label="价格" width="180">
                </el-table-column>
                <el-table-column prop="author" label="作者">
                </el-table-column>
                <el-table-column prop="press" label="出版社">
                </el-table-column>
                <el-table-column label="图片封面">
                    <template slot-scope="scope">
                        <el-image style="width: 100px; height: 100px;border-radius: 50%;"
                            :src="'http://localhost:8085/api/file/' + scope.row.image"
                            :preview-src-list="['http://localhost:8085/api/file/' + scope.row.image]"></el-image>
                    </template>
                </el-table-column>
                <el-table-column label="图书类别" width="180">
                    <template v-slot:default="scope">
                        <!-- 这里假设每行的数据中有 typeId 字段,且options是所有类别的数组 -->
                        {{ findTypeName(scope.row.typeId) }}
                    </template>
                </el-table-column>
                <el-table-column label="操作">
                    <template slot-scope="scope">
                        <el-button type="primary" @click="edit(scope.row)">编辑</el-button>
                        <el-button type="primary" @click="down(scope.row.image)">下载</el-button>
                        <el-popconfirm title="确定要删除这条记录吗?" @confirm="deleteRow(scope.row.id)" confirm-button-text="确定"
                            cancel-button-text="取消">
                            <template #reference>
                                <el-button type="danger" style="margin-left: 10px;">删除</el-button>
                            </template>
                        </el-popconfirm>
                    </template>
                </el-table-column>
               
            </el-table>
        </div>

        <div>
            <el-pagination style="margin-top: 20px;" @size-change="handleSizeChange"
                @current-change="handleCurrentChange" :current-page="params.pageNum" :page-sizes="[5, 10, 15, 20]"
                :page-size="params.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="totalNum">
            </el-pagination>
        </div>

        <div>
            <el-dialog :title="dialogTitle" :visible.sync="dialogFormVisible" @close="resetForm">
                <el-form :model="form">
                    <el-form-item label="书名" :label-width="formLabelWidth">
                        <el-input v-model="form.name" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="价格" :label-width="formLabelWidth">
                        <el-input v-model="form.price" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="作者" :label-width="formLabelWidth">
                        <el-input v-model="form.author" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="出版社" :label-width="formLabelWidth">
                        <el-input v-model="form.press" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="图书封面" :label-width="formLabelWidth">
                        <!-- :headers="uploadHeaders" 可以动态加上请求头token  或者 后端放行 -->
                        <el-upload class="upload-demo" action="http://localhost:8085/api/file/upload"
                            :headers="uploadHeaders" :on-error="errorUpload" :on-success="successUpload" :limit="1"
                            :on-exceed="handleExceed">
                            <el-button size="small" type="primary">点击上传</el-button>
                        </el-upload>
                    </el-form-item>
                    <el-form-item label="图书类别" :label-width="formLabelWidth">
                        <el-select v-model="form.typeId" placeholder="请选择图书类别">
                            <el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id">
                            </el-option>
                        </el-select>
                    </el-form-item>
                </el-form>
                <div slot="footer" class="dialog-footer">
                    <el-button @click="dialogFormVisible = false">取 消</el-button>
                    <el-button type="primary" @click="submit" :disabled="isSubmitting">确 定</el-button>
                </div>
            </el-dialog>
        </div>
    </div>
</template>
<script>
import request from '@/api/HttpRequest'
export default {
    name: "BookView",
    data() {
        return {
            dialogFormVisible: false,
            isEdit: false, // 标志是编辑状态还是新增状态
            dialogTitle: '', // 对话框标题
            params: {
                name: '',
                press: '',
                pageNum: 1,
                pageSize: 5,
            },
            uploadHeaders: {
                'token': sessionStorage.getItem('token')
            },
            totalNum: 0,
            tableData: [],
            form: {

            },
            formLabelWidth: '120px',
            isSubmitting: false,  //提交标志
            options: []
        }
    },
    created() {
        this.findBySearch()
        this.selectOptions()
    },
    methods: {
        findTypeName(typeId) {
            const type = this.options.find(option => option.id === typeId);
            return type ? type.name : '';
        },
        selectOptions() {
            request.get("/bookType/findBookTypes").then(res => {
                if (res.code == 200) {
                    this.options = res.data
                    //console.log(res.data)
                }
            })
        },
        down(imageName) {
            location.href = 'http://localhost:8085/api/file/' + imageName
        },
        handleExceed(files, fileList) {
            this.$message.warning('只能上传一张图片');
        },
        //文件上传成功后的钩子  
        successUpload(res) {
            //console.log(res)
            if (res.code == 200) {
                this.form.image = res.data
            }

        },
        errorUpload(res) {
            console(res)
        },
        resetForm() {
            this.form = {
                name: '',
                price: '',
                author: '',
                press: '',
                image: '',
                typeId: ''
            };
        },
        reset() {
            this.params.name = ''
            this.params.press = ''
            this.findBySearch()
        },
        findBySearch(val) {
            if (val == 1) {
                this.params.pageNum = 1
                this.params.pageSize = 5
            }
            request.get("/book/findBooks", {
                params: this.params
            }).then(res => {
                if (res.code == 200) {
                    this.tableData = res.data.list
                    this.totalNum = res.data.total
                } else {

                }
            })
        },
        addForm() {
            this.resetForm(); // 重置表单
            this.isEdit = false; // 设置为新增模式
            this.dialogTitle = '新增管理员信息'; // 设置对话框标题
            this.dialogFormVisible = true; // 显示对话框
        },
        edit(row) {
            this.isEdit = true; // 设置为编辑模式
            this.dialogTitle = '更新管理员信息'; // 设置对话框标题
            this.form = Object.assign({}, row); // 深拷贝行数据到表单
            //this.form=row
            this.dialogFormVisible = true; // 显示对话框
        },
        deleteRow(id) {
            // 这里应该调用API来删除row代表的数据
            // 例如:
            this.isSubmitting = true;
            request.delete(`/book/deleteBook/${id}`).then(res => {
                if (res.code == 200) {
                    this.$message.success('删除成功');
                    this.findBySearch(); // 重新加载当前页的数据或者根据需要更新视图
                } else {
                    this.$message.error('删除失败,请稍后再试');
                }
            }).catch(error => {

                this.$message.error('网络错误,请稍后再试');
            }).finally(() => {
                this.isSubmitting = false;
            });
        },

        submit() {
            if (this.isSubmitting) return;

            this.isSubmitting = true;
            const api = this.isEdit ? "/book/save" : "/book/save";
            request.post(api, this.form).then(res => {
                if (res.code == 200) {
                    this.$message({
                        message: this.isEdit ? '更新成功' : '新增成功',
                        type: 'success'
                    });
                    this.dialogFormVisible = false;
                    this.resetForm()
                    this.findBySearch(1);
                } else {
                    this.$message.error('出错了,请联系管理员');
                }
            }).catch(error => {

                this.$message.error('网络错误,请稍后再试');
            }).finally(() => {
                this.isSubmitting = false;
            });
        },

        handleSizeChange(val) {
            this.params.pageSize = val
            this.findBySearch()
        },
        handleCurrentChange(val) {
            this.params.pageNum = val
            this.findBySearch()
        }
    },
}
</script>

如果用关联表查询也可以

下面是通过id去查name

角色权限控制

例如某些页面的增删改查按钮  管理员角色可以操作   而普通用户只能浏览

某些页面管理员可以访问   但是可能普通用户不给予访问(看不到)

在用户表添加角色字段   这个字段用int  也可以 随自己

现在角色字段添加了  但是 登录admin或者张三 都是可以看到所有页面的

要做的是根据角色不同  所能看到的菜单不同

审核功能

请假申请  审核功能为例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1529011.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【docker】Docker打包SpringBoot镜像

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;中间件 ⛺️稳中求进&#xff0c;晒太阳 前置说明 最为原始的打包方式spring-boot-maven-plugin插件jib-maven-plugin插件dockerfle-maven-plugin插件 最为原始的方式 也就是使用Docker的打…

Ubuntu Desktop - Desktop

Ubuntu Desktop - Desktop 1. Amazon2. Ubuntu Software3. Desktop4. 系统桌面快捷方式5. 用户桌面快捷方式References 1. Amazon Amazon -> Unlock from Launcher 2. Ubuntu Software Installed -> Games -> Remove 3. Desktop /home/strong/Desktop 4. 系统桌面…

爆肝五千字!ECMAScript核心概念与现代JavaScript特性全解析

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

KEY ENERGY欧洲意大利能源光伏储能展

3月1号第18届意大利里米尼国际可再生能源展&#xff08;KEY ENERGY&#xff09;由知名主办方ITALIAN EXHIBITION GROUP S.P.A组织举办&#xff0c;每年一届&#xff0c;是欧洲第二大能源展&#xff0c;也是覆盖范围最全知名度最高的可再生能源展览会。 该展会将于2024扩大规模…

【Mysql数据库基础03】分组函数(聚合函数)、分组查询

分组函数(聚合函数&#xff09;、分组查询 1 分组函数1.1 简单的使用1.2 是否忽略null值1.3 和关键字搭配使用1.4 count函数的详细介绍1.5 练习 2 分组查询Group by2.1 简单的分组查询2.2 练习 3 格式投票:yum: 1 分组函数 1.1 简单的使用 COUNT(expression)&#xff1a;计算符…

腾讯云服务器租用价格多少钱一个月?2024优惠价格表

2024腾讯云服务器多少钱一个月&#xff1f;5元1个月起&#xff0c;腾讯云轻量服务器4核16G12M带宽32元1个月、96元3个月&#xff0c;8核32G22M配置115元一个月、345元3个月&#xff0c;腾讯云轻量应用服务器61元一年折合5元一个月、4核8G12M配置646元15个月、2核4G5M服务器165元…

腾讯云服务器多少钱1个月?2024一个月收费阿济格IE吧

2024腾讯云服务器多少钱一个月&#xff1f;5元1个月起&#xff0c;腾讯云轻量服务器4核16G12M带宽32元1个月、96元3个月&#xff0c;8核32G22M配置115元一个月、345元3个月&#xff0c;腾讯云轻量应用服务器61元一年折合5元一个月、4核8G12M配置646元15个月、2核4G5M服务器165元…

计算机考研|双非一战135上岸,408经验分享+复盘

计算机专业的同学真的别想的太天真&#xff01; 相比于其他专业&#xff0c;计算机专业的同学其实还是很有优势的 但是现在随着计算机专业的同学越来越多&#xff0c;找工作的困难程度以及学历自然而然被卷起来了 以前的算法岗基本要求在本科以上&#xff0c;现在基本都是非92研…

MacBook远程控制工具VNC Viewer_亲测使用

MacBook远程控制工具VNC Viewer_亲测使用 官方下载地址: https://www.realvnc.com/en/connect/download/viewer/ MacBook远程桌面Windows使用Microsoft Remote Desktop for Mac_亲测使用 VNC 介绍 VNC (Virtual Network Console)是虚拟网络控制台的缩写。它是一款优秀的远程…

Flutter开发进阶之使用Socket实现主机服务(二)

Flutter开发进阶之使用Socket实现主机服务(二) Flutter开发进阶之使用Socket实现主机服务(一) 在完成局域网内设备定位后就可以进入微服务的实操了。 I、构建Socket连接池 一、定义Socket 使用socket_io_client socket_io_client: ^2.0.3+1导入头文件 import packag…

iOS图片占内存大小与什么有关?

1. 问&#xff1a;一张图片所占内存大小跟什么有关&#xff1f; 图片所占内存大小&#xff0c;与图片的宽高有关 我们平时看到的png、jpg、webp这些图片格式&#xff0c;其实都是图片压缩格式。通过对应的算法来优化了大小以节省网络传输与本地保存所需的资源。 但是当我们加…

信息学奥赛之C++ cstdlib – 概览

什么是 C cstdlib&#xff1f; C 标准库头文件 (cstdlib in C) 是 C 程序员使用最广泛的库头文件。即&#xff1a;standard librarian。它定义了一系列函数和宏&#xff0c;以实现跨团队、跨平台的高效且具有卓越表现的标准化 C 代码。 C 是一种广受欢迎的程序语言&#xff0c…

Java的图书管理系统,确实有两把斧子 ! ! !

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

php 页面中下载文件|图片

一、需求 页面中点击下载图片 二、实现 protected function pageLoad(){$filePath $_GET[file];$host $_SERVER[HTTP_HOST];$file http://.$host.$filePath;$fileName basename($filePath);$content file_get_contents($file);ob_clean();$suffixArr explode(., $file…

力扣大厂热门面试算法题 43-45

43. 字符串相乘&#xff0c;44. 通配符匹配&#xff0c;45. 跳跃游戏 II&#xff0c;每题做详细思路梳理&#xff0c;配套Python&Java双语代码&#xff0c; 2024.03.18 可通过leetcode所有测试用例。 目录 43. 字符串相乘 解题思路 完整代码 Python Java 44. 通配符…

【JAVA快速编写UI】 Java 编写一个编码转换和加解密工具,可以创建一个简单的 GUI 应用程序(例子)

EncodingDecodingTool/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── rockmelodies/ │ │ │ └── encodingdecodingtool/ │ │ │ ├── MainApp.java │ │ │ …

MongoDB启动报错

spring boot 引入MongoDB启动报错 java.lang.IllegalStateException: Failed to introspect Class [io.micrometer.core.instrument.binder.mongodb.DefaultMongoConnectionPoolTagsProvider] from ClassLoader [sun.misc.Launcher$AppClassLoader18b4aac2] at org.springfra…

初识STL(标准模板库)

目录 ​编辑 什么是STL STL的版本 STL的六大组件 如何学习STL STL的优势 STL的缺陷 ⭐什么是STL STL(standard template libaray- 标准模板库 ) &#xff1a; 是 C 标准库的重要组成部分 &#xff0c;不仅是一个可复用的组件库&#xff0c;而且 是一个包罗数据结构与算法…

C语言笔记:函数与程序结构

目录 ACM金牌带你零基础直达C语言精通-课程资料 一.作用域的基本概念 二.函数 1. 函数的定义和使用 2.为什么一定要有函数结构 3.形参与实参 4.函数的声明和定义 5.递归函数 此代码中递归函数执行流程&#xff1a; 练习&#xff1a;求斐波那契数列第n项的值&#xff1a; 欧几里…

洛谷_P1104 生日_python写法

P1104 生日 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 知识点&#xff1a; 还是自定义规则的排序&#xff0c;然后这里还有python中如何在一行中输入多种类型的数据。 n int(input()) data [] num 1 for i in range(n):img list(input().split())s img[0]y int(img…