课程类培训机构微信小程序-毕业设计及商用完整项目介绍(微信小程序+springboot+Shiro+vue+ElementUI后台管理)
微信小程序端:1、小程序端包含首页、产品、我的三个主模块。2、首页模块实现机构的课程列表、课程详情及课程学习,产品模块实现产品搜索、产品分类和详情,个人模块实现我的足迹、个人信息和我的课程。小程序服务端:1、实现对小程序功能的接口及业务逻辑支撑。2、对接数据端处理。Web后台管理端:1、实现对不同角色的权限管理,管理员以及普通用户操作权限分离。2、Web端管理实现对会员的管理。3、实现对课程的管理
基于SpringBoot+Mysql+Bootstrap+VUE+微信小程序实现的乐器培训机构系统
前言:可作为毕设学习使用,有相关场景需要可在此基础上快速搭建成型系统。目前有些东西还不太全,比如支付接口(这个的话需要可以进行具体对接)、课程的付费模式等,但是其他的整体流程已经开发好了。小程序端、小程序服务端、小程序后台、数据端、前后端的交互等,都已经可以使用。这篇博客分享,主要是为了帮助想独立开发小程序的朋友,以及一些需要定制类似系统的作者做参考。
一、项目介绍
微信小程序端:
1、小程序端包含首页、产品、我的三个主模块。
2、首页模块实现机构的课程列表、课程详情及课程学习,产品模块实现产品搜索、产品分类和详情,个人模块实现我的足迹、个人信息和我的课程。
小程序服务端:
1、实现对小程序功能的接口及业务逻辑支撑。
2、对接数据端处理。
Web后台管理端:
1、实现对不同角色的权限管理,管理员以及普通用户操作权限分离。
2、Web端管理实现对会员的管理。
3、实现对课程的管理(课程录入、课程分类、章节管理等)
4、Web端管理员实现对商场的关键词设置。
5、实现对商品的管理(商品上架、品牌管理、类目管理、库存管理等)
6、实现推广管理、一键运营,轻松设置广告banner。
二、相关技术
1、小程序采用小程序原生语言开发。
2、小程序管理后台系统采用目前流行的前后端分离模式,前端使用Vue+ElementUI搭建后台管理界面。
3、小程序服务端已经管理后台均采用Springboot+Mybatis实现。
4、权限处理使用了Shiro框架实现。
5、使用了lombok、SelectOneByExamplePlugin/ExampleEnhancedPlugin等插件。
6、对于系统一些商品、课程的上传存储,目前实现了对接对地存储、阿里云对象存储、腾讯云对象存储、七牛云对象存储四种方式。
三、系统结构
四、界面展示
小程序端:
首页(课程展示):
产品模块(产品展示、购买):
我的模块:
Web后台管理端:
管理登录页面:
所有模块菜单:
用户管理:
课程管理:
商场关键词设置:
商品管理:
推广管理:
四、部分关键代码
本系统前后陆陆续续用了2个多月,也是在边学习新技术,边上手做东西,真的是实际开发成品才会遇到一个又一个技术难关、一个又一个自己知识的盲区,还好坚持下来了~~
小程序端,说两个页面的布局吧,一个是主页面的,一个是购物车页面。
主页面 index.wxml:
<!--index.wxml-->
<view class="container">
<!-- 搜索 -->
<view class="search">
<navigator url="/pages/search/search?source=course" class="input">
<image class="icon"></image>
<text class="txt">课程搜索, 共{{courseCount}}门课程</text>
</navigator>
</view>
<!-- 活动轮播 -->
<swiper class="banner" indicator-dots="true" autoplay="true" interval="3000" duration="1000">
<swiper-item wx:for="{{banner}}" wx:key="id">
<navigator url="{{item.link}}">
<image src="{{item.url}}" background-size="cover"></image>
</navigator>
</swiper-item>
</swiper>
<!--快捷入口 -->
<view class="fast_column">
<navigator bindtap="openPage" class="item" data-url="/pages/courseList/courseList?type=hot">
<!-- <image src="{{item.iconUrl}}" background-size="cover"></image>
<text>{{item.name}}</text> -->
<image src="/static/images/shortCut/hot_course.png" background-size="cover"></image>
<text>热门课程</text>
</navigator>
<navigator bindtap="openPage" class="item" data-url="/pages/musicianStyle/index">
<!-- <image src="{{item.iconUrl}}" background-size="cover"></image>
<text>{{item.name}}</text> -->
<image src="/static/images/shortCut/musician_style.png" background-size="cover"></image>
<text>乐手风采</text>
</navigator>
<navigator class="item">
<!-- <image src="{{item.iconUrl}}" background-size="cover"></image>
<text>{{item.name}}</text> -->
<image src="/static/images/shortCut/about_me.png" background-size="cover"></image>
<text>关于我们</text>
</navigator>
</view>
<!-- 体系课程 -->
<view class="a-section a-type">
<view class="h">
<navigator url="">
<text class="text"> — 体系课程系列 — </text>
</navigator>
</view>
<view class="b">
<view class="item item-1" wx:for="{{sysCatList}}" wx:key="id">
<navigator url="/pages/courseList/courseList?id={{item.id}}">
<view class="wrap">
<image class="img" src="{{item.iconUrl}}" mode="aspectFill"></image>
<view class="mt">
<text class="name">{{item.name}}</text>
</view>
</view>
</navigator>
</view>
</view>
<view class="h">
<navigator url="" >
<text class="text"> — 特色课程系列 — </text>
</navigator>
</view>
<view class="b">
<view class="item item-1" wx:for="{{feaCatList}}" wx:key="id">
<navigator url="/pages/courseList/courseList?id={{item.id}}">
<view class="wrap">
<image class="img" src="{{item.iconUrl}}" mode="aspectFill"></image>
<view class="mt">
<text class="brand">{{item.name}}</text>
</view>
</view>
</navigator>
</view>
</view>
</view>
</view>
购物车页面:cart.wxml
<view class="container">
<view class="no-login" wx:if="{{!hasLogin}}">
<view class="c">
<image src="http://nos.netease.com/mailpub/hxm/yanxuan-wap/p/20150730/style/img/icon-normal/noCart-a8fe3f12e5.png" />
<text>还没有登录</text>
<button style="background-color:#A9A9A9" bindtap="goLogin">去登录</button>
</view>
</view>
<view class='login' wx:else>
<view class="service-policy">
<view class="item">30天无忧退货</view>
<view class="item">48小时快速退款</view>
<view class="item">满88元免邮费</view>
</view>
<view class="no-cart" wx:if="{{cartGoods.length <= 0}}">
<view class="c">
<image src="http://nos.netease.com/mailpub/hxm/yanxuan-wap/p/20150730/style/img/icon-normal/noCart-a8fe3f12e5.png" />
<text>去添加点什么吧</text>
</view>
</view>
<view class="cart-view" wx:else>
<view class="list">
<view class="group-item">
<view class="goods">
<view class="item {{isEditCart ? 'edit' : ''}}" wx:for="{{cartGoods}}" wx:key="id">
<view class="checkbox {{item.checked ? 'checked' : ''}}" bindtap="checkedItem" data-item-index="{{index}}"></view>
<view class="cart-goods">
<image class="img" src="{{item.picUrl}}"></image>
<view class="info">
<view class="t">
<text class="name">{{item.goodsName}}</text>
<text class="num">x{{item.number}}</text>
</view>
<view class="attr">{{ isEditCart ? '已选择:' : ''}}{{item.goodsSpecificationValues||''}}</view>
<view class="b">
<text class="price">¥{{item.price}}</text>
<view class="selnum">
<view class="cut" bindtap="cutNumber" data-item-index="{{index}}">-</view>
<input value="{{item.number}}" class="number" disabled="true" type="number" />
<view class="add" bindtap="addNumber" data-item-index="{{index}}">+</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="cart-bottom">
<view class="checkbox {{checkedAllStatus ? 'checked' : ''}}" bindtap="checkedAll">全选({{cartTotal.checkedGoodsCount}})</view>
<view class="total">{{!isEditCart ? '¥'+cartTotal.checkedGoodsAmount : ''}}</view>
<view class='action_btn_area'>
<view class="{{!isEditCart ? 'edit' : 'sure'}}" bindtap="editCart">{{!isEditCart ? '编辑' : '完成'}}</view>
<view class="delete" bindtap="deleteCart" wx:if="{{isEditCart}}">删除({{cartTotal.checkedGoodsCount}})</view>
<view class="checkout" bindtap="checkoutOrder" wx:if="{{!isEditCart}}">下单</view>
<!-- </view> -->
</view>
</view>
</view>
</view>
</view>
小程序服务端:
首页Api:MiniHomeApi.java
package com.code2life.course.mini.api;
import com.code2life.course.core.util.ResponseUtil;
import com.code2life.course.db.domain.MallCourseCategory;
import com.code2life.course.db.service.MallAdService;
import com.code2life.course.db.service.MallCourseCategoryService;
import com.code2life.course.db.service.MallCourseChapterService;
import com.code2life.course.db.service.MallCourseService;
import com.code2life.course.mini.service.HomeCacheManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
/**
* @author: wen
* @date: 2019/11/20 10:50
* @description: 小程序首页服务
*/
@RestController
@RequestMapping("/mini/home")
@Validated
public class MiniHomeApi {
@Autowired
private MallCourseCategoryService categoryService;
@Autowired
private MallCourseService courseService;
@Autowired
private MallCourseChapterService chapterService;
@Autowired
private MallAdService adService;
@GetMapping("/index")
public Object index(){
ExecutorService executorService = Executors.newFixedThreadPool(5);
Callable<List> bannerListCallable = () -> adService.queryIndex();
//体系课程
Callable<List> systemListCallable = null;
List<MallCourseCategory> systemList = categoryService.findL1ByMark("system");
if(systemList!=null && !systemList.isEmpty()){
Integer id = systemList.get(0).getId();
systemListCallable = () -> categoryService.queryByPid(id);
}else {
systemListCallable = () -> new ArrayList();
}
//特色课程
Callable<List> featureListCallable;
List<MallCourseCategory> featureList = categoryService.findL1ByMark("feature");
if(featureList!=null && !featureList.isEmpty()){
Integer id = featureList.get(0).getId();
featureListCallable = () -> categoryService.queryByPid(id);
}else {
featureListCallable = () -> new ArrayList();
}
FutureTask<List> bannerTask = new FutureTask<>(bannerListCallable);
FutureTask<List> sysCatTask = new FutureTask<>(systemListCallable);
FutureTask<List> feaCatTask = new FutureTask<>(featureListCallable);
executorService.submit(bannerTask);
executorService.submit(sysCatTask);
executorService.submit(feaCatTask);
Map<String, Object> entity = new HashMap<>();
try {
entity.put("banner", bannerTask.get());
entity.put("sysCatList", sysCatTask.get());
entity.put("feaCatList", feaCatTask.get());
//缓存数据
HomeCacheManager.loadData(HomeCacheManager.INDEX, entity);
}
catch (Exception e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
return ResponseUtil.ok(entity);
}
}
购物车订单处理API:MiniCourseOrderApi.java
package com.code2life.course.mini.api;
import com.code2life.course.mini.annotation.LoginUser;
import com.code2life.course.mini.service.MiniCourserOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author: wen
* @date: 2019/11/29 17:55
* @description:
*/
@RestController
@RequestMapping("/mini/course/order")
@Validated
public class MiniCourseOrderApi {
@Autowired
private MiniCourserOrderService orderService;
/**
* 订单提交
* @param userId
* @param body
* @return
*/
@PostMapping("submit")
public Object submit(@LoginUser Integer userId, @RequestBody String body) {
return orderService.submit(userId, body);
}
/**
* 付款订单的预支付会话标识
*
* @param userId 用户ID
* @param body 订单信息,{ orderId:xxx }
* @return 支付订单ID
*/
@PostMapping("prepay")
public Object prepay(@LoginUser Integer userId, @RequestBody String body, HttpServletRequest request) {
return orderService.prepay(userId, body, request);
}
/**
* 微信付款成功或失败回调接口
* <p>
* TODO
* 注意,这里pay-notify是示例地址,建议开发者应该设立一个隐蔽的回调地址
*
* @param request 请求内容
* @param response 响应内容
* @return 操作结果
*/
@PostMapping("pay-notify")
public Object payNotify(HttpServletRequest request, HttpServletResponse response) {
return orderService.payNotify(request, response);
}
}
Web后台管理端:
后台管理系统在线课程相关API:MallCourseApi.java
package com.code2life.course.admin.api;
import com.code2life.course.admin.dto.CourseAllinone;
import com.code2life.course.admin.service.AdminCourseService;
import com.code2life.course.core.util.ResponseUtil;
import com.code2life.course.core.validator.Order;
import com.code2life.course.core.validator.Sort;
import com.code2life.course.db.domain.MallCourse;
import io.swagger.annotations.Api;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* @author: wen
* @date: 2019/11/14 23:26
* @description:
*/
@RestController
@RequestMapping("/mall/course")
@Validated
@Api(value = "后台管理系统在线课程相关API")
public class MallCourseApi {
private final Log logger = LogFactory.getLog(MallCourseApi.class);
@Autowired
private AdminCourseService courseService;
@GetMapping("/list")
public Object list(String courseSn, String name,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer limit,
@Sort @RequestParam(defaultValue = "add_time") String sort,
@Order @RequestParam(defaultValue = "desc") String order) {
return courseService.list(courseSn, name, page, limit, sort, order);
}
@PostMapping("/delete")
public Object delete(@RequestBody MallCourse course) {
courseService.delete(course);
return ResponseUtil.vueOk();
}
@PostMapping("/add")
public Object create(@RequestBody CourseAllinone allinone) {
return courseService.add(allinone);
}
@GetMapping("/getRelatedList")
public Object getListCat(){
return courseService.relatedList();
}
@GetMapping("/getCourseById")
public Object getChapterNeed(Integer id) {
return courseService.getChapterNeed(id);
}
@GetMapping("/courseOption")
public Object courseOption(){
return courseService.courseOption();
}
}
后台管理系统商品相关API:MallGoodsApi.java
package com.code2life.course.admin.api;
import com.code2life.course.admin.dto.GoodsAllinone;
import com.code2life.course.admin.service.AdminGoodsService;
import com.code2life.course.core.validator.Order;
import com.code2life.course.core.validator.Sort;
import com.code2life.course.db.domain.MallGoods;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotNull;
/**
* @author: wen
* @date: 2019/11/23 19:53
* @description:
*/
@RestController
@RequestMapping("/mall/goods")
@Validated
@Api(value = "后台管理系统商品相关API")
public class MallGoodsApi {
@Autowired
private AdminGoodsService goodsService;
@GetMapping("/list")
public Object list(String goodsSn, String GoodsName,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer limit,
@Sort @RequestParam(defaultValue = "add_time") String sort,
@Order @RequestParam(defaultValue = "desc") String order) {
return goodsService.list(goodsSn, GoodsName, page, limit, sort, order);
}
@GetMapping("/catAndBrand")
public Object list2() {
return goodsService.list2();
}
@PostMapping("/add")
public Object add(@RequestBody GoodsAllinone goodsAllinone) {
return goodsService.add(goodsAllinone);
}
@PostMapping("/update")
public Object update(@RequestBody GoodsAllinone goodsAllinone) {
return goodsService.update(goodsAllinone);
}
@PostMapping("/delete")
public Object delete(@RequestBody MallGoods goods) {
return goodsService.delete(goods);
}
@GetMapping("/detail")
public Object detail(@NotNull Integer id) {
return goodsService.detail(id);
}
}
Shiro登录系统验证:SysAuthorizingRealm.java
package com.code2life.course.admin.shiro;
import com.code2life.course.admin.util.Permission;
import com.code2life.course.admin.util.PermissionUtil;
import com.code2life.course.admin.vo.PermissionVo;
import com.code2life.course.core.util.bcrypt.BCryptPasswordEncoder;
import com.code2life.course.db.domain.SysRole;
import com.code2life.course.db.domain.SysUser;
import com.code2life.course.db.service.SysRoleService;
import com.code2life.course.db.service.SysUserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author: wen
* @date: 2019/11/5 15:14
* @description: Shiro登录系统验证
*/
public class SysAuthorizingRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Autowired
private ApplicationContext context;
private List<PermissionVo> allPermissions = null;
private Set<String> allPermissionsString = null;
private List<PermissionVo> getSystemPermissions() {
final String basicPackage = "com.code2life.course.admin";
if (allPermissions == null) {
List<Permission> permissions = PermissionUtil.listPermission(context, basicPackage);
allPermissions = PermissionUtil.listPermVo(permissions);
allPermissionsString = PermissionUtil.listPermissionString(permissions);
}
return allPermissions;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
System.out.println("------------用户授权开始------------");
if (principals == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
SysUser user = (SysUser) getAvailablePrincipal(principals);
//存放角色对象
List<SysRole> roles = new ArrayList<>();
//存放角色名称
Set<String> roleNames = new HashSet<String>();
//存放用户的权限信息
Set<String> permissions = new HashSet<String>();
roles = sysRoleService.findByUsercode(user.getUsercode());
for(SysRole role : roles){
roleNames.add(role.getName());
//判断是否有超级权限,有需要转换成所有权限
if(sysRoleService.checkSuperPermission(role.getId())){
getSystemPermissions();
}
}
//拥有超级权限
if(allPermissionsString!=null && !allPermissionsString.isEmpty()) {
permissions = allPermissionsString;
}else{
//查询拥有角色的权限
for(SysRole role : roles) {
Set<String> perms = sysRoleService.queryPermissionByRoleId(role.getId());
permissions.addAll(perms);
}
}
//通过用户账号获取用户的所有资源,并把资源存入info中
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roleNames);
info.addStringPermissions(permissions);
System.out.println("------------用户授权结束------------");
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("------------用户认证开始------------");
//将AuthenticationToken转换为UsernamePasswordToken,token中存储着用户输入的账号和密码
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
//获取用户账号和密码
String usercode = upToken.getUsername();
String password = new String(upToken.getPassword());
if (StringUtils.isEmpty(usercode)) {
throw new AccountException("用户名不能为空");
}
if (StringUtils.isEmpty(password)) {
throw new AccountException("密码不能为空");
}
List<SysUser> list = sysUserService.findByUsercode(usercode);
if(list==null || list.isEmpty()){
//账号不存在
throw new UnknownAccountException("账号不存在");
}
Assert.state(list.size() < 2, "同一个用户名存在两个账户");
SysUser user = list.get(0);
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
if (!encoder.matches(password, user.getPassword())) {
throw new UnknownAccountException("找不到用户(" + usercode + ")的帐号信息");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
System.out.println("------------用户认证结束------------");
return info;
}
}
Web后台系统前端Vue部分:
后台课程管理首页:index.vue
<template>
<div class="app-container">
<!-- 查询和其他操作 -->
<div class="filter-container">
<el-input v-model="listQuery.courseSn" clearable class="filter-item" style="width: 200px;" placeholder="请输入编号"/>
<el-input v-model="listQuery.name" clearable class="filter-item" style="width: 200px;" placeholder="支持名称模糊搜索"/>
<el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">搜索</el-button>
<el-button class="filter-item" type="primary" icon="el-icon-edit" @click="handleCreate">添加</el-button>
</div>
<!-- 查询结果 -->
<el-table v-loading="listLoading" :data="list" element-loading-text="正在查询中。。。" border fit highlight-current-row row-key="id">
<el-table-column type="expand">
<template slot-scope="props">
<el-form label-position="left" class="table-expand">
<el-form-item label="宣传画廊">
<img v-for="pic in props.row.gallery" :key="pic" :src="pic" class="gallery">
</el-form-item>
<el-form-item label="商品介绍">
<span>{{ props.row.brief }}</span>
</el-form-item>
<el-form-item label="关键字">
<span>{{ props.row.keywords }}</span>
</el-form-item>
<el-form-item label="类目ID">
<span>{{ props.row.categoryId }}</span>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column align="center" label="课程编号" prop="courseSn"/>
<el-table-column align="center" min-width="100" label="名称" prop="name"/>
<el-table-column align="center" property="iconUrl" label="图片">
<template slot-scope="scope">
<img :src="scope.row.iconUrl" width="40">
</template>
</el-table-column>
<el-table-column align="center" label="详情" prop="detail">
<template slot-scope="scope">
<el-dialog :visible.sync="detailDialogVisible" title="课程详情">
<div v-html="courseDetail"/>
</el-dialog>
<el-button type="primary" size="mini" @click="showDetail(scope.row.detail)">查看</el-button>
</template>
</el-table-column>
<el-table-column align="center" label="课程价格" prop="cPrice"/>
<el-table-column align="center" label="是否新品" prop="isNew">
<template slot-scope="scope">
<el-tag :type="scope.row.isNew ? 'success' : 'error' ">{{ scope.row.isNew ? '新品' : '非新品' }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="是否热品" prop="isHot">
<template slot-scope="scope">
<el-tag :type="scope.row.isHot ? 'success' : 'error' ">{{ scope.row.isHot ? '热门' : '普通' }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="300" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="small" @click="handleChapter(scope.row)">章节录入</el-button>
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
<el-tooltip placement="top" content="返回顶部">
<back-to-top :visibility-height="100" />
</el-tooltip>
</div>
</template>
<script>
import { listCourse, deleteCourse } from '@/api/course'
import BackToTop from '@/components/BackToTop'
import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
export default {
name: 'Course',
components: { BackToTop, Pagination },
data() {
return {
list: [],
total: 0,
listLoading: true,
listQuery: {
page: 1,
limit: 10,
courseSn: undefined,
name: undefined,
sort: 'add_time',
order: 'desc'
},
courseDetail: '',
detailDialogVisible: false
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listLoading = true
listCourse(this.listQuery).then(response => {
this.list = response.data.list
this.total = response.data.total
this.listLoading = false
}).catch(() => {
this.list = []
this.total = 0
this.listLoading = false
})
},
handleFilter() {
this.listQuery.page = 1
this.getList()
},
showDetail(detail) {
this.courseDetail = detail
this.detailDialogVisible = true
},
handleCreate() {
this.$router.push({ path: '/course-management/createCourse' })
},
handleChapter(row) {
const data = row
this.$router.push({ name: 'ChapterAdd', params: { id: data.id }})
},
handleUpdate(row) {
this.$notify.warning({
title: '警告',
message: '该功能暂未开放',
duration: 2000
})
// this.$router.push({ path: '/goods/edit', query: { id: row.id }})
},
handleDelete(row) {
deleteCourse(row).then(response => {
this.$notify.success({
title: '成功',
message: '删除成功'
})
const index = this.list.indexOf(row)
this.list.splice(index, 1)
}).catch(response => {
this.$notify.error({
title: '失败',
message: response.msg
})
})
}
}
}
</script>
<style scoped>
.table-expand {
font-size: 0;
}
.table-expand label {
width: 100px;
color: #99a9bf;
}
.table-expand .el-form-item {
margin-right: 0;
margin-bottom: 0;
}
.gallery {
width: 80px;
margin-right: 10px;
}
</style>
Vue菜单路由:index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
/* Router Modules */
import sysSettingRouter from './modules/sysSetting'
import courserRouter from './modules/course'
import goodsRouter from './modules/goods'
import mallUserRouter from './modules/mallUser'
import promotionRouter from './modules/promotion'
import mallSettingRouter from './modules/mallSetting'
/**
* Note: sub-menu only appear when route children.length >= 1
* Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
*
* hidden: true if set true, item will not show in the sidebar(default is false)
* alwaysShow: true if set true, will always show the root menu
* if not set alwaysShow, when item has more than one children route,
* it will becomes nested mode, otherwise not show the root menu
* redirect: noredirect if `redirect:noredirect` will no redirect in the breadcrumb
* name:'router-name' the name is used by <keep-alive> (must set!!!)
* meta : {
roles: ['admin','editor'] control the page roles (you can set multiple roles)
title: 'title' the name show in sidebar and breadcrumb (recommend set)
icon: 'svg-name' the icon show in the sidebar
noCache: true if set true, the page will no be cached(default is false)
affix: true if set true, the tag will affix in the tags-view
breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
}
*/
/**
* constantRoutes
* a base page that does not have permission requirements
* all roles can be accessed
*/
export const constantRoutes = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path*',
component: () => import('@/views/redirect/index')
}
]
},
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/auth-redirect',
component: () => import('@/views/login/auth-redirect'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/error-page/404'),
hidden: true
},
{
path: '/401',
component: () => import('@/views/error-page/401'),
hidden: true
},
{
path: '',
component: Layout,
redirect: 'dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index'),
name: 'Dashboard',
meta: { title: 'dashboard', icon: 'dashboard', noCache: true, affix: true }
}
]
},
// {
// path: '/documentation',
// component: Layout,
// children: [
// {
// path: 'index',
// component: () => import('@/views/documentation/index'),
// name: 'Documentation',
// meta: { title: 'documentation', icon: 'documentation', affix: true }
// }
// ]
// },
{
path: '/guide',
component: Layout,
redirect: '/guide/index',
children: [
{
path: 'index',
component: () => import('@/views/guide/index'),
name: 'Guide',
meta: { title: 'guide', icon: 'guide', noCache: true }
}
]
}
]
/**
* asyncRoutes
* the routes that need to be dynamically loaded based on user roles
*/
export const asyncRoutes = [
courserRouter,
goodsRouter,
promotionRouter,
mallSettingRouter,
mallUserRouter,
sysSettingRouter,
{ path: '*', redirect: '/404', hidden: true }
]
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
五、写在最后
本博客属于个人原创,转载请注明出处;
本系统还要很多不足的地方,由于时间有限,后续会继续更新功能;
如果感觉兴趣,可以扫码关注咨询,获取源码。
更多推荐
所有评论(0)