练习SpringBoot烘培坊项目

news2025/1/13 9:29:00

烘培坊项目

文章目录

  • 烘培坊项目
    • 项目概述
    • 项目搭建
    • 项目关键代码实现
      • 实现首页轮播图
      • 实现注册功能
      • 实现登录功能
      • 实现图片上传
      • 稿件管理页面内容列表功能
      • 稿件管理页面修改功能
      • 稿件管理页面删除功能
      • 首页三个类型下的分类展示
      • 首页三种类型的内容展示
      • 点击查看更多进入列表页面
      • 在header里面的导航菜单中进行页面跳转
      • 点击首页和内容列表页面中的内容时查看详情
      • 作者其它文章
      • 浏览量
      • 热门文章
      • 发评论
      • 评论列表
      • 后台管理页面- 用户列表
      • 后台管理页面-轮播图
      • 后台管理页面-食谱/视频/资讯

项目概述

烘培坊(Bakery)是一个虚构的在线烘焙产品销售网站,主要面向烘焙爱好者和美食爱好者。该网站提供各种烘焙产品的食谱介绍、视频教学服务,包括烘焙原料、工具和书籍等,烘培坊网站使用了现代化的Web技术,如HTML5、CSS3和JavaScript等,使用Vue、ElementUI框架,后台使用包括MyBatis、MySQL、SpringBoot搭建,作为学习一整个项目搭建来练习使用

项目搭建

  • 创建baking工程,勾选这三个依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-skUHIh5H-1686133312982)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20230606181518180.png)]

  • pom.xml里面添加以下三个的依赖
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!--添加Knife4j依赖-->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
    <version>4.1.0</version>
</dependency>

<!--Spring Validation依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
  • 配置application.properties配置文件
spring.datasource.url=jdbc:mysql://localhost:3306/baking?serverTimezone=Asia/Shanghai&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root

server.port=8080

# 设置MyBatis框架的映射(Mapper)配置文件的位置
mybatis.mapper-locations=classpath:mappers/*.xml
# 配置表字段名和属性名命名规范不一致时自动匹配
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.com.liner=debug

# 设置单个上传文件的大小,默认1M   ssmm
spring.servlet.multipart.max-file-size=100MB
# 设置多文件上传的大小            ssmm
spring.servlet.multipart.max-request-size=200MB

#客户端只能访问静态资源文件夹中的文件,设置某个文件夹为静态资源文件夹  swrs
spring.web.resources.static-locations=file:${filePath},classpath:static

#初始文件
filePath=d:/projectFiles
  • 引入sql数据库

    可在附件中查看

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K0nPbYh0-1686133312984)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20230606183026605.png)]

  • 搭建前端页面,详见上一篇文章

  • 创建配置类

    @Configuration
    @MapperScan("com.liner.baking.mapper")
    public class MyBatisConfig {
    
    }
    
  • 创建实体类

    • Banner
    • Category
    • Comment
    • Content
    • User
    • Admin

项目关键代码实现

实现首页轮播图

创建BannerController

@RestController
@RequestMapping("/v1/banners")
public class BannerController {

    @Autowired
    private BannerMapper bannerMapper;

    @GetMapping("/")
    public ResultVO getBanners() {
        return ResultVO.ok(bannerMapper.select());
    }
}

创建BannerVO

@Data
public class BannerVO {
    private Long id;
    private String imgUrl;
}

创建BannerMapper

<select id="select" resultType="com.liner.baking.pojo.vo.BannerVO">
    SELECT id, img_url
    FROM t_banner
    ORDER BY sort
</select>

index.html添加created 方法,在里面请求所有轮播图数据,赋值给bannerArr数组,让页面中的轮播图和这个数组进行绑定

<el-carousel height="375px">
    <el-carousel-item v-for="ban in bannerArr">
        <img :src="ban.imgUrl" width="100%">
    </el-carousel-item>
</el-carousel>
created: function () {
    //发请求获取所有的轮播图数据
    axios.get("/v1/banners/").then(function (r) {
        v.bannerArr = r.data.data;
    })
}

实现注册功能

@PostMapping("/reg")
public ResultVO reg(@RequestBody UserRegDTO userRegDTO) {
    UserVO userVO = userMapper.selectByUserName(userRegDTO.getUserName());
    if (userVO!= null) {
        return new ResultVO(StatusCode.USERNAME_ALREADY_EXISTS);
    }
    User user = new User();
    BeanUtils.copyProperties(userRegDTO, user);
    user.setCreateTime(new Date());
    user.setIsAdmin(0); //默认不是管理员
    user.setImgUrl("/imgs/icon.png");   //默认头像
    //对user对象里面的密码进行加密 -- 加密之后得到60个字符长度的密码
    user.setPassword(passwordEncoder.encode(user.getPassword()));
    userMapper.insert(user);
    return ResultVO.ok();
}

实现登录功能

UserController里面添加login方法 ,创建UserLoginDTO

@PostMapping("/login")
public ResultVO login(@RequestBody UserLoginDTO userLoginDTO) {
    //通过认证管理器启动Security的认证流程返回认证结果对象
    Authentication authenticate = manager.authenticate(
        new UsernamePasswordAuthenticationToken(userLoginDTO.getUserName(), userLoginDTO.getPassword()));
    //将认证结果保存到Security上下文中让Security框架记住登录状态
    SecurityContextHolder.getContext().setAuthentication(authenticate);
    //代码执行到这里时代表登录成功!如果登录失败Security框架会抛出异常
    return ResultVO.ok(authenticate.getPrincipal());
}

在项目中添加Security依赖

<!--引入spring-security依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

添加Security相关的类文件

  • CustomUserDetails
@Getter
@ToString
public class CustomUserDetails extends User {
    private Long id;
    private String nickName;
    private String imgUrl;
    public CustomUserDetails(Long id,String nickName,String imgUrl,String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
        this.id = id;
        this.nickName = nickName;
        this.imgUrl = imgUrl;
    }
}
  • UserDetailServiceImpl
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Resource
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserVO user = userMapper.selectByUserName(username);
        if (user == null) {
            return null;    //默认抛出 AuthenticationException 用户名找不到异常
        }

        //模拟王五是管理员,其他是用户
        String role = username.equals("王五") ? "ADMIN" : "USER";
        List<GrantedAuthority> list = AuthorityUtils.createAuthorityList(role);
        //创建自定义的UserDetails并把后期需要的id和nickName保存进去
        CustomUserDetails userDetail = new CustomUserDetails(
            user.getId(),
            user.getNickName(),
            user.getImgUrl(),
            username,
            user.getPassword(),
            list
        );

        //如果用户输入的密码和数据库中查询到的密码不一致则会抛出异常
        return userDetail;
    }
}
  • SecurityConfig
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启方法授权的检测
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().loginPage("/login.html");  //设置自定义登录页面
        http.csrf().disable();  //关闭跨域攻击防御策略

        //设置黑名单(需要登录才能访问资源)  顺序为:    authorizeHttpRequests -> mvcMatchers -> authenticated -> anyRequest -> permitAll
        String[] urls = {"/admin.html""/personal.html""/articleManagement.html""/postArticle.html"};
        http.authorizeHttpRequests()//对请求进行授权
            .mvcMatchers(urls)//匹配某些路径
            .authenticated()//要求通过认证的
            .anyRequest() //任意请求
            .permitAll(); //直接许可,即不需要认证即可访问
    }

    //配置认证管理器
    @Bean   //实现在Controller中自动装配
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    //配置密码加密的方式
    @Bean
    public PasswordEncoder passwordEncoder(){
        //返回此加密的编码器之后, 用户输入的密码会通过此编码器加密之后再和数据库里面的密码进行比较
        return new BCryptPasswordEncoder();

    }
}

登录失败会抛出异常,需要全局异常

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler({InternalAuthenticationServiceException.classBadCredentialsException.class})
    public ResultVO handleAuthenticationServiceException(AuthenticationException e){
        log.error("异常信息 is {}",e.getMessage());//日志级别trace<debug<info<warn<error
        if (e instanceof InternalAuthenticationServiceException){
            log.warn("用户名不存在!!!");
            return new ResultVO(StatusCode.USERNAME_ERROR,e.getMessage());
        }
        log.warn("密码错误!!!");
        return new ResultVO(StatusCode.PASSWORD_ERROR,e.getMessage());
    }

    /**
     * 用户无权访问抛出异常
     */
    @ExceptionHandler(AccessDeniedException.class)
    public ResultVO handleAccessDeniedException(AccessDeniedException e){
        log.warn("无权访问!");
        return new ResultVO(StatusCode.FORBIDDEN_ERROR);
    }
}

注册时需要密码加密

//对user对象里面的密码进行加密 -- 加密之后得到60个字符长度的密码
user.setPassword(passwordEncoder.encode(user.getPassword()));
userMapper.insert(user);

实现图片上传

@RestController
@RequestMapping("/v1/upload")
public class UploadController {
    @Value("${filePath}")
    private String filePath;
    @RequestMapping("")
    public ResultVO upload(MultipartFile file) throws IOException {
        //得到上传文件的名称  初始文件名
        String fileName = file.getOriginalFilename();

        //得到文件的后缀  从最后一个 . 位置开始截取最后的 a.jpg   .jpg
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        //使用UUID 十六进制的 得到一个唯一标识符  得到唯一文件名
        fileName = UUID.randomUUID() + suffix;
        System.out.println(fileName);

        //准备保存文件的文件夹路径

        SimpleDateFormat f = new SimpleDateFormat("/yyyy/MM/dd/");
        String datePath = f.format(new Date()); //以日存储
        File dirFile = new File(filePath + datePath);
        if (!dirFile.exists()) {
            dirFile.mkdirs();   //创建文件夹
        }

        //把图片存进文件夹  d:/projectFiles//2023/06/1.xxx.jpg 异常抛出
        file.transferTo(new File(filePath + datePath + fileName));
        //把图片路径 响应给客户端 /2023/06/1.xxx.jpg
        return ResultVO.ok(datePath + fileName);
    }

    @GetMapping("/remove")
    public void remove(String url){
        // url =  /2023/06/1/xxxx.jpg
        //完整路径   c:/files/2023/06/1/xxxx.jpg
        //删除和路径对应的图片文件
        new File(filePath + url).delete();
    }   
}

稿件管理页面内容列表功能

articleManagement.html页面中删除contentArr数组里面的内容, 在created方法中发请求获取所有内容, 然后把得到的数据赋值给contentArr

created: function () {
    //created方法是在Vue对象初始化过程中调用的方法,在方法中不能直接调用v,否则会报错
    //需要访问v里面的内容时,使用this代替
    this.loadList(this.type);
}

ContentController中处理第一步发出的请求, 调用mapper里面的方法得到list集合把集合响应给客户端

@RequestMapping("/{type}/management")
public ResultVO management(@PathVariable Integer type,
                           @AuthenticationPrincipal CustomUserDetails userDetails) {
    System.out.println("type = " + type + ",userDetails = " + userDetails);

    if (userDetails == null) {
        return new ResultVO(StatusCode.NOT_LOGIN);
    }
    return ResultVO.ok(contentMapper.selectByType(type,userDetails.getId()));
}

稿件管理页面修改功能

在点击编辑按钮时跳转到postArticle.html页面中,并且把点击的内容id传递过去.

handleEdit(i,content) {
    location.href = "/postArticle.html?id=" + content.id;
}

postArticle.html页面中的created方法里面取出地址栏中的id, 通过id查询内容的详情,查询到数据后赋值给v.c 此时页面中的内容和c对象进行了双向绑定 就可以显示出需要修改的信息

created:function () {
    //判断此时页面的作用是添加还是修改
    //location.search得到地址栏中?及后面的内容   ?id=25
    // "abc".includes("a")也是判断是否包含
    if (location.search.indexOf("id")!=-1){//包含id 代表编辑
        this.isEdit = true;
        let id = location.search.split("=")[1];
        //通过地址栏中id请求内容详情
        axios.get("/v1/contents/"+id+"/edit").then(function (response) {
            if (response.data.code==1){
                v.c = response.data.data;
                //更新富文本编辑器内容
                editor.setHtml(v.c.content);
                //给单独的变量赋值,让页面中的图片标签和oldImgUrl绑定
                v.oldImgUrl = v.c.imgUrl;
                //请求当前编辑内容类型下的所有分类
                //此处是发完请求回调回来的方法, 此处的this和v不是同一个对象
                //this代表的window对象,访问Vue对象中的方法时使用v
                v.loadCategories(v.c.type);
            }
        })
    }else{//代表发布
        //如果是发布的话, 地址栏中会包含type    ?type=3
        this.c.type = location.search.split("=")[1];
        //请求type对应的所有分类
        this.loadCategories(this.c.type);
    }
}
})

ContentController中处理上一步发出的请求, 把id对应的数据查询到后响应给客户端

@GetMapping("/{id}/edit")
public ResultVO getId(@PathVariable Long id) {
    return ResultVO.ok(contentMapper.selectByIdForEdit(id));
}

postArticle.html页面的作用为修改时, 发布按钮改成修改按钮, 点击按钮时所做的操作和发布一样, 包括请求的路径都一样

ContentController里面处理add-new请求时判断传递过来的内容数据中是否包含id,包含id代表此次请求为修改请求,不包含为发布

@PostMapping("/add-new")
public ResultVO addNew(@RequestBody ContentDTO contentDTO,@AuthenticationPrincipal CustomUserDetails userDetails) {
    System.out.println("contentDTO = " + contentDTO);
    Content content = new Content();
    BeanUtils.copyProperties(contentDTO,content);
    if (contentDTO.getId() == null) {
        content.setCreateTime(new Date());
        contentMapper.insert(content);
    } else {
        content.setUpdateTime(new Date());
        content.setUpdateBy(userDetails.getId());   //设置修改人为当前登录的用户
        contentMapper.update(content);
    }
    return ResultVO.ok();

}

稿件管理页面删除功能

当点击表格中的删除按钮时向服务器发出删除请求,并删除对应的本地存档中的图片或视频

@GetMapping("{id}/delete")
public ResultVO delete(@PathVariable Long id) {
    //得到封面的图片路径
    ContentEditVO contentEditVO = contentMapper.selectByIdForEdit(id);
    new File(filePath+  contentEditVO.getImgUrl()).delete();
    //如果内容为视频,得到视频路径并删除
    if (contentEditVO.getType() == 2){
        new File(filePath+ contentEditVO.getVideoUrl()).delete();
    }
    return ResultVO.ok(contentMapper.deleteById(id));
}

首页三个类型下的分类展示

created方法里面请求三种类型下的分类数据

//请求食谱相关分类数据
axios.get("/v1/categories/1/sub").then(function (response) {
    if (response.data.code==1){
        v.recipeCategoryArr = response.data.data;
    }
})
//请求视频相关分类数据
axios.get("/v1/categories/2/sub").then(function (response) {
    if (response.data.code==1){
        v.videoCategoryArr = response.data.data;
    }
})
//请求资讯相关分类数据
axios.get("/v1/categories/3/sub").then(function (response) {
    if (response.data.code==1){
        v.infoCategoryArr = response.data.data;
    }
})

在页面中和分类数组进行绑定

<!--视频导航开始-->
<el-menu mode="horizontal" @select="videoSelect" default-active="0" active-text-color="orange">
    <el-menu-item index="0">全部</el-menu-item>
    <el-menu-item v-for="c in videoCategoryArr" :index="c.id">{{c.name}}</el-menu-item>
</el-menu>
<!--视频导航结束-->

首页三种类型的内容展示

created方法中请求三种类型的所有数据

//请求全部数据
this.loadContent(10);
this.loadContent(20);
this.loadContent(30);
loadContent(type,categoryId) {
    //发请求获取某个类型下某个分类的数据
    axios.get("/v1/contents/" + type + "/" + categoryId + "/index")
        .then(function (response) {
        if (response.data.code === 1) {
            switch (type) {
                case 1://食谱
                    v.recipeArr = response.data.data;
                    break;
                case 2://视频
                    v.videoArr = response.data.data;
                    break;
                case 3://资讯
                    v.infoArr = response.data.data;
                    break;
            }
        }
    })
}

ContentController中处理上面的请求

@GetMapping("{type}/{categoryId}/index")
public ResultVO index(@PathVariable Integer type,@PathVariable Integer categoryId) {
    return ResultVO.ok(contentMapper.selectByTypeAndCategoryId(type,categoryId));
}

当点击每个类型下的分类导航菜单时 需要再次发出请求

recipeSelect(key,keyPath) {
    //key对应的是index,而index的值就是分类的id
    this.loadContent(1,key);
},
 videoSelect(key,keyPath) {
     this.loadContent(2,key);
},
infoSelect(key,keyPath) {
     this.loadContent(3,key);
},

点击查看更多进入列表页面

给按钮添加点击事件跳转到contentList.html页面

<div style="text-align: center;margin-bottom: 20px">
    <el-button @click="location.href='/contentList.html?type=3'">点击查看更多资讯</el-button>
</div>

contentList.html页面的created方法中通过地址栏中的type请求这个类型下的所有内容

created: function () {
    //判断地址栏中是否包含type
    if (location.search.includes("type")) {
        let type = location.search.split("=")[1];
        //通过type 请求相关的内容
        axios.get("/v1/contents/" + type + "/list").then(function (r) {
            if (r.data.code === 1) {
                v.contentArr = r.data.data;
            }
        })
    }
}

ContentController中处理上面的请求

@GetMapping("{type}/list")
public ResultVO index(@PathVariable Integer type) {
    return ResultVO.ok(contentMapper.selectByTypeForList(type));
}

在header里面的导航菜单中进行页面跳转

my-header里面给导航菜单添加select事件调用handleSelect方法,方法中进行页面跳转

handleSelect(key,keyPath){
    if (key==0){
        location.href="/";
    }else{
        location.href="/contentList.html?type="+key;
    }
}

点击首页和内容列表页面中的内容时查看详情

给列表中的内容添加超链接 请求地址为 /detail.html?id=xxxx

<a style="color: #333;text-decoration: none;" :href="'/detail.html?id=' + r.id">
    <img :src="r.imgUrl" width="100%" height="144">
    <p class="title">{{r.title}}</p>
</a>

detail.html详情页面中 created方法里面得到地址栏中的id,通过id查询内容详情,把得到的数据赋值给data里面的c变量, 让页面中显示的内容和c进行绑定

created: function () {
    let id = new URLSearchParams(location.search).get("id");
    //发请求获取内容详情
    axios.get("/v1/contents/" + id + "/detail").then(function (r) {
        if (r.data.code === 1) {
            v.c = r.data.data;
            //请求其他内容
            v.loadUserOtherContent();
        }
    })
}

ContentController中处理上面的请求

@RequestMapping("/{id}/detail")
public ResultVO detail(@PathVariable Long id){
    //通过id查询详情 代表浏览了一次
    contentMapper.updateViewCountById(id);
    return ResultVO.ok(contentMapper.selectByIdForDetail(id));
}

作者其它文章

detail.html详情页面中查询到内容详情时 再次发出请求获取当前内容作者相关的其它内容

let id = new URLSearchParams(location.search).get("id");
//发请求获取内容详情
axios.get("/v1/contents/" + id + "/detail").then(function (r) {
    if (r.data.code === 1) {
        v.c = r.data.data;
        //请求其他内容
        v.loadUserOtherContent();
    }
})
loadUserOtherContent: function () {
    //请求当前文章作者其他内容
    axios.get("/v1/contents/" + v.c.userId + "/others").then(function (r) {
        if (r.data.code === 1) {
            v.othersArr = r.data.data
        }
    })
}

ContentController里面处理上面的请求

@RequestMapping("/{userId}/others")
public ResultVO other(@PathVariable Long userId) {
    return ResultVO.ok(contentMapper.selectOtherByUserId(userId));
}

最后在detail.html页面中让显示的内容和得到的数组进行绑定

<el-card style="margin:10px 0">
    <h3>作者其它文章</h3>
    <el-divider></el-divider>
    <!--文章列表开始-->
    <el-row gutter="10" v-for="c in othersArr">

        <el-col span="10">
            <a style="color: #333;text-decoration: none;" :href="'/detail.html?id=' + c.id">
                <img :src="c.imgUrl" width="100%" height="100px">
            </a>
        </el-col>
        <el-col span="14">
            <a style="color: #333;text-decoration: none;" :href="'/detail.html?id=' + c.id">
                <p class="title">{{c.title}}</p>
            </a>
            <i class="el-icon-time" style="color: #666">{{c.createTime}}</i>
        </el-col>

    </el-row>
    <!--文章列表结束-->
</el-card>

浏览量

ContentController中 处理通过id查询详情的方法中 调用mapper里面修改浏览量的方法

@RequestMapping("/{id}/detail")
public ResultVO detail(@PathVariable Long id){
    //通过id查询详情 代表浏览了一次
    contentMapper.updateViewCountById(id);
    return ResultVO.ok(contentMapper.selectByIdForDetail(id));
}

ContentMapper中实现上面的方法

<update id="updateViewCountById">
    UPDATE t_content
    SET view_count = view_count + 1
    WHERE id = #{id}
</update>

热门文章

detail.html页面中的created方法里面请求按照浏览量降序排序的内容

//请求热门文章
axios.get("/v1/contents/hot").then(function (r) {
    if (r.data.code === 1) {
        v.allArr = r.data.data
    }
})

ContentController里面处理上面的请求

@GetMapping("/hot")
public ResultVO hot() {
    return ResultVO.ok(contentMapper.selectHot());
}

发评论

detail.html页面 发布按钮添加点击事件,在对应的方法中发出发评论的请求

post(){
    if (v.comment.content.trim()==""){
        v.$message.error("请输入评论的内容!");
        return
    }

    if (localStorage.user==null){
        alert("请先登录!");
        location.href="/login.html";
        return;
    }
    //设置被评论的内容id
    v.comment.contentId = v.c.id;
    axios.post("/v1/comments/add-new",v.comment).then(function (response) {
        if (response.data.code==1){
            alert("评论完成!");
            location.reload();
        }else{
            alert("登录超时请重新登录!");
            location.href="/login.html"
        }
    })
},

CommentController中处理上面的请求

@PostMapping("/add-new")
public ResultVO addNew(@RequestBody CommentDTO commentDTO, @AuthenticationPrincipal CustomUserDetails userDetails) {
    if (userDetails == null) {
        return new ResultVO(StatusCode.NOT_LOGIN);
    }
    //让评论数量 + 1
    contentMapper.updateCommentCount(commentDTO.getContentId());
    
    Comment comment = new Comment();
    BeanUtils.copyProperties(commentDTO,comment);
    comment.setUserId(userDetails.getId());
    comment.setCreateTime(new Date());
    commentMapper.insert(comment);
    return ResultVO.ok();
}

评论列表

detail.html页面的created方法中 请求所有评论数据,让页面的内容和得到的评论数据进行绑定

//发请求获取评论
axios.get("/v1/comments/" + id).then(function (r){
    if (r.data.code === 1){
        v.commentArr = r.data.data;
    }
})

CommentController里面处理上面的请求

@GetMapping("/{id}")
public ResultVO selectCommentById(@PathVariable Long id){
    return ResultVO.ok(commentMapper.selectByCommentId(id));
}

后台管理页面- 用户列表

admin.html页面的created方法里面发请求获取用户数据,让表格和数据进行绑定

//请求用户列表数据
axios.get("/v1/users/").then(function (r) {
    if (r.data.code === 1) {
        v.userArr = r.data.data;
    }
})

UserController中处理上面的请求

@GetMapping("")
public ResultVO list(){
    return ResultVO.ok(userMapper.select());
}

修改是否是管理员,给el-switch控件添加了change事件,在事件方法中发出修改管理员状态的请求

changAdmin(user) {
    axios.post("/v1/users/" + user.id + "/" + (user.isAdmin ? 1 : 0) + "/change").then(function (r) {
        if (r.data.code === 1) {
            v.$message.success("修改完成!");
        }
    })
},

UserContentController里面处理上面的请求

@PostMapping("{id}/{isAdmin}/change")
public ResultVO change(@PathVariable Long id, @PathVariable Integer isAdmin){
    User user = new User();
    user.setId(id);
    user.setIsAdmin(isAdmin);
    return ResultVO.ok(userMapper.update(user));
}

删除用户

deleteUser(i, user) {
    if (user.id === v.user.id) {
        alert("不能删除当前登录的用户!");
        return
    }
    if (confirm("您确定删除此用户吗?"))
        //发出删除请求
        axios.post("/v1/users/" + user.id + "/delete").then(function (r) {
            if (r.data.code === 1) {
                v.$message.success("删除成功!")
                v.userArr.splice(i, 1);   //删除数组中数据
            } else {
                alert("删除失败!");
            }
        })
},

后台管理页面-轮播图

admin.html页面中的created方法请求所有轮播图数据

//请求轮播图列表数据
axios.get("/v1/banners/").then(function (r) {
    if (r.data.code === 1) {
        v.bannerArr = r.data.data;
    }
})

BannerController里面处理上面的请求

@RequestMapping("admin")
public ResultVO select(){
    return ResultVO.ok(bannerMapper.selectForAdmin());
}

实现删除轮播图功能

deleteBanner(i, banner) {
    if (confirm("您确定删除此轮播图吗?"))
        //发出删除请求
        axios.post("/v1/banners/" + banner.id + "/delete").then(function (r) {
            if (r.data.code === 1) {
                v.$message.success("删除成功!")
                v.bannerArr.splice(i, 1);   //删除数组中数据
            } else {
                alert("删除失败!");
            }
        })
},

后台管理页面-食谱/视频/资讯

在点击导航菜单中的食谱/视频/资讯时,调用了handleSelect方法,在方法中根据点击的类型请求对应的数据

handleSelect(key, keyPath) {
    //key就是点击菜单项的index值
    v.currentIndex = key;
    if (key >= 3) {
        let type = key - 2;
        //请求和类型相对应的内容
        axios.get("/v1/contents/" + type + "/admin").then(function (r) {
            if (r.data.code === 1) {
                v.contentArr = r.data.data;
            }
        })
    }
},

ContentController里面处理上面的请求

@GetMapping("/{type}/admin")
public ResultVO admin(@PathVariable Integer type){
    return ResultVO.ok(contentMapper.selectByTypeForAdmin(type));
}

修改功能

handleEdit(i, content) {
    location.href = "/postArticle.html?id=" + content.id;
},

删除功能

handleDelete(i, content) {
    if (confirm("您确定删除此内容吗?"))
        //发出删除请求
        axios.get("/v1/contents/" + content.id + "/delete").then(function (r) {
            if (r.data.code === 1) {
                v.$message.success("删除成功!")
                v.contentArr.splice(i,1);   //删除页面中内容
            } else {
                alert("删除失败!");
            }
        })
},

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

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

相关文章

mysql waf绕过-WAF Bypass技巧(2)

数据库waf绕过的一些玩法 WAF Bypass技巧(0)_luozhonghua2000的博客-CSDN博客 WAF绕过-WAF Bypass技巧(1)_luozhonghua2000的博客-CSDN博客 数据库特性 第一篇: Mysql数据库特性 0x01 前言 我们经常利用一些数据库特性来进行WAF绕过。在Mysgl中,比如可以这样 位置一: 参数和…

泰坦尼克号幸存者预测

本文所有的代码及数据均存放与https://github.com/MADMAX110/Titanic 泰坦尼克号幸存者预测挑战赛是一个非常受欢迎的机器学习实践项目。这是 Kaggle.com 上最受欢迎的比赛之一。 数据中有3个文件:train.csv&#xff0c;test.csv&#xff0c;和gender_submit .csv。 train.csv包…

内存碎片:理解、应用场景和防止措施

目录 摘要1. 引言2. 内存碎片的概念3. 内存碎片的产生原因4. 应用场景4.1 应用场景一&#xff1a;长时间运行的服务器4.2 应用场景二&#xff1a;嵌入式系统 5. 预防和处理内存碎片6. 示例代码&#xff1a;生成内存碎片7. 总结 摘要 本文旨在向初学者详细介绍内存碎片的概念、…

【爬虫】4.1 Scrapy 框架爬虫简介

目录 1. Scrapy 框架介绍 2. 建立 Scrapy 项目 3. 入口函数与入口地址 4. Python 的 yield 语句 5. Scrapy 爬虫的数据类型 1. Scrapy 框架介绍 1.1 Scrapy 的安装 pip install scrapy -i https://pypi.tuna.tsinghua.edu.cn/simple/ 1.2 Scrapy爬虫框架结构 “52”结构&…

「HTML和CSS入门指南」table 标签详解

什么是 table 标签? 在 HTML 中,table 标签用于创建表格。使用 table 标签可以帮助您以可视化和结构化的方式呈现数据。 table 标签的基本语法 以下是 table 标签的基本语法: <table><tr><th>姓名</th><th>年龄</th><th>性别&l…

chatgpt赋能python:Python如何辨别非数字符号

Python如何辨别非数字符号 在进行数据处理或者字符串操作时&#xff0c;经常会遇到需要识别和处理非数字符号的情况&#xff0c;如何在Python中进行辨别呢&#xff1f; 什么是非数字符号 非数字符号是指数字以外的字符&#xff0c;包括但不限于以下类型&#xff1a; 字母&a…

Drools 规则引擎原理

目录 Drools规则引擎基本定义介绍优劣优点缺点 基本概念规则引擎构成 规则结构规则引擎执行过程规则存储Kie 介绍Kie 相关组件知识库规则引擎与知识库Drools 存储规则的数据结构 Rete 算法原理优缺点优点缺点 举例 前向和后向链推理机制前向链后向链对比 应用场景概念和特点架构…

MMPretrain

title: mmpretrain实战 date: 2023-06-07 16:04:01 tags: [image classification,mmlab] mmpretrain实战 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ccTl9bOl-1686129437336)(null)] 主要讲解了安装,还有使用教程.安装教程直接参考官网.下面讲…

【mmpose】从openmmlab官方文档看mmpose架构设计,模块组成,快速上手实现关键点检测效果(动物,人体,手部等)

MMPOSE 架构设计 MMPose 1.0 与之前的版本有较大改动&#xff0c;对部分模块进行了重新设计和组织&#xff0c;降低代码冗余度&#xff0c;提升运行效率&#xff0c;降低学习难度。使用 MMPose 1.0 时开发者会关心的内容&#xff1a; 整体代码架构与设计逻辑&#xff1b; 如何…

告别加班!9款Figma汉化插件助你高效工作,提升生产力!

Figma是一款极受设计师欢迎的设计工具&#xff0c;而Figma中文版即时设计则是在Figma基础上改进而来&#xff0c;有着众多的Figma汉化插件&#xff0c;可以加速设计工作&#xff0c;让每位设计师完成更好的创作。 打开即时设计官网首页&#xff0c;点击【设计社区】-【插件广场…

虚函数表详解及其应用场景

目录 概述1. 虚函数表概述2. 虚函数表的实现原理2.1. 虚函数的声明和定义2.2. 虚函数表的创建和初始化2.3. 虚函数调用的过程 3. 虚函数表的应用场景3.1. 多态性3.2. 基类指针和引用的使用3.3. 动态绑定3.4. 接口定义 结论 概述 在面向对象编程中&#xff0c;虚函数表&#xf…

java-JDBC

java-JDBC 1. JDBC概念 JDBC 就是使用Java语言操作关系型数据库的一套API 全称&#xff1a;( Java DataBase Connectivity ) Java 数据库连接 sun公司就指定了一套标准接口&#xff08;JDBC&#xff09;&#xff0c;JDBC中定义了所有操作关系型数据库的规则。 我们需要使用接口…

人群聚集监测预警算法 python

人群聚集监测预警系统采用pythonopencv网络模型AI视频智能分析技术&#xff0c;人群聚集监测预警算法对人员聚集情况进行实时监测&#xff0c;当人群聚集过于密集时&#xff0c;系统将自动发出警报。OpenCV基于C实现&#xff0c;同时提供python, Ruby, Matlab等语言的接口。Ope…

R语言 tidyverse系列学习笔记(系列1)

tidyverse 译 “洁净的宇宙” > “极乐净土” 以 iris 鸢尾花数据集为例 ** 查看数据集** ** 查看维度dimention** dim(iris)iris 数据集有150个对象&#xff08;observation&#xff09;&#xff0c;5列 ( Sepal.Length , Sepal.Width , Petal.Length , Petal.Width , S…

阿里服务器配置服务器自启动

一开始 是在 /etc/rc.local 文件中添加的如下脚本 bash /mnt/cangjie-server/action.sh start bash /usr/local/nginx/sbin/nginx pm2 start npm--name"cangjieWeb"run start 启动服务器&#xff0c;服务并没有执行。 后面把执行脚本的 bash 指令去掉 如下&#xf…

Linux内核中断和Linux内核定时器

目录 Linux内核中断 Linux内核定时器 Linux内核中断 int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev) 功能&#xff1a;注册中断 参数&#xff1a; irq : 软中断号 gpio的软中断号 软中断号 gpio_to_i…

期末复习样卷

期末复习样卷 目录 期末复习样卷选择题简答题1. 画E-R图并将其转换为适当的关系模型2. 规范化模式分解——教学关系&#xff08;学号&#xff0c;姓名&#xff0c;年龄&#xff0c;性别&#xff0c;系别&#xff0c;系主任&#xff0c;课程名&#xff0c;成绩&#xff09;3.关系…

重装Windows系统

1.前言 有的时候&#xff0c;面对杂乱的文件系统&#xff0c;整理是十分困难的…… 有的时候&#xff0c;下载软件的时候会附带上某一些病毒、木马…… 有的时候&#xff0c;不满于更新后的系统…… 这些种种都可以使用重装系统解决&#xff0c;接下来我来带您学习重装windows…

煤炭价格学习

大佬发表时间在2022.11.17 神华月线&#xff0c;因为没有送配股&#xff0c;所以肯定是除权看的&#xff08;前复权看的不要跟我谈技术&#xff0c;因为你不配&#xff09; 除权&#xff0c;前复权&#xff0c;后复权 理解这三者区别之前&#xff0c;首先我们要简单了解 除权和…

【String字符串之后续】

我们继续上一篇文章为大家讲解&#xff0c;String字符串的相关知识&#xff0c;希望大家有所收获&#x1f49e;&#x1f49e;&#x1f49e; 字符串前篇的链接: link 目录 1.字符串的替换2.字符串的拆分3.字符串截取4.去掉空格5.String的不可变性6. 字符串的修改7.StringBuilde…