练习SpringBoot烘培坊项目

  • Post author:
  • Post category:其他




烘培坊项目



项目概述

烘培坊(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.htm

l页面中的

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("删除失败!");
            }
        })
},



版权声明:本文为weixin_65777087原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。