烘培坊项目
文章目录
- 烘培坊项目
- 项目概述
- 项目搭建
- 项目关键代码实现
- 实现首页轮播图
- 实现注册功能
- 实现登录功能
- 实现图片上传
- 稿件管理页面内容列表功能
- 稿件管理页面修改功能
- 稿件管理页面删除功能
- 首页三个类型下的分类展示
- 首页三种类型的内容展示
- 点击查看更多进入列表页面
- 在header里面的导航菜单中进行页面跳转
- 点击首页和内容列表页面中的内容时查看详情
- 作者其它文章
- 浏览量
- 热门文章
- 发评论
- 评论列表
- 后台管理页面- 用户列表
- 后台管理页面-轮播图
- 后台管理页面-食谱/视频/资讯
项目概述
烘培坊(Bakery)是一个虚构的在线烘焙产品销售网站,主要面向烘焙爱好者和美食爱好者。该网站提供各种烘焙产品的食谱介绍、视频教学服务,包括烘焙原料、工具和书籍等,烘培坊网站使用了现代化的Web技术,如HTML5、CSS3和JavaScript等,使用Vue、ElementUI框架,后台使用包括MyBatis、MySQL、SpringBoot搭建,作为学习一整个项目搭建来练习使用
项目搭建
- 创建
baking
工程,勾选这三个依赖
- 在
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数据库
可在附件中查看
-
搭建前端页面,详见上一篇文章
-
创建配置类
@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.class, BadCredentialsException.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(1,0);
this.loadContent(2,0);
this.loadContent(3,0);
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("删除失败!");
}
})
},