在线教学管理系统
本项目旨在为教师和学生提供一个在线教学管理平台,教师可以发布课程、布置作业、批改作业、发布公告等,学生可以查看课程、提交作业、查看公告等。项目包括一个后端(NodeJS+express+MySQL)、一个小程序应用(原生+vant)。
用户表(user)
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(255) NOT NULL COMMENT '用户名',
`password` varchar(255) NOT NULL COMMENT '密码',
`name` varchar(255) NOT NULL COMMENT '姓名',
`role` enum('teacher','student') NOT NULL COMMENT '角色(教师或学生)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
课程表(course)
CREATE TABLE `course` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '课程ID',
`name` varchar(255) NOT NULL COMMENT '课程名称',
`description` text NOT NULL COMMENT '课程描述',
`cover` varchar(255) NOT NULL COMMENT '课程封面',
`teacher_id` int(11) NOT NULL COMMENT '发布教师ID',
FOREIGN KEY (`teacher_id`) REFERENCES `user` (`id`),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='课程表';
作业表(homework)
CREATE TABLE `homework` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '作业ID',
`name` varchar(255) NOT NULL COMMENT '作业名称',
`description` text NOT NULL COMMENT '作业描述',
`deadline` datetime NOT NULL COMMENT '截止日期',
`course_id` int(11) NOT NULL COMMENT '所属课程ID',
FOREIGN KEY (`course_id`) REFERENCES `course` (`id`),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='作业表';
作业提交表(homework_submission)
CREATE TABLE `homework_submission` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '提交ID',
`student_id` int(11) NOT NULL COMMENT '提交学生ID',
`homework_id` int(11) NOT NULL COMMENT '作业ID',
`content` text NOT NULL COMMENT '作业内容',
`file` varchar(255) DEFAULT NULL COMMENT '附件',
`score` int(11) DEFAULT NULL COMMENT '评分',
`comment` text DEFAULT NULL COMMENT '评语',
FOREIGN KEY (`student_id`) REFERENCES `user` (`id`),
FOREIGN KEY (`homework_id`) REFERENCES `homework` (`id`),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='作业提交表';
公告表(announcement)
CREATE TABLE `announcement` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '公告ID',
`title` varchar(255) NOT NULL COMMENT '公告标题',
`content` text NOT NULL COMMENT '公告内容',
`teacher_id` int(11) NOT NULL COMMENT '发布教师ID',
FOREIGN KEY (`teacher_id`) REFERENCES `user` (`id`),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='公告表';
完整SQL:
/*
Navicat Premium Data Transfer
Source Server : mysql5
Source Server Type : MySQL
Source Server Version : 50717 (5.7.17-log)
Source Host : localhost:3306
Source Schema : online_edu_manager
Target Server Type : MySQL
Target Server Version : 50717 (5.7.17-log)
File Encoding : 65001
Date: 04/06/2023 11:55:25
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for announcement
-- ----------------------------
DROP TABLE IF EXISTS `announcement`;
CREATE TABLE `announcement` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '公共ID',
`title` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '公告标题',
`content` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '公告内容',
`teacher_id` int(11) NOT NULL COMMENT '外键,关联user表',
PRIMARY KEY (`id`) USING BTREE,
INDEX `teacher_id`(`teacher_id`) USING BTREE,
CONSTRAINT `announcement_ibfk_1` FOREIGN KEY (`teacher_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for course
-- ----------------------------
DROP TABLE IF EXISTS `course`;
CREATE TABLE `course` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '课程ID',
`name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '课程名称',
`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '课程描述',
`cover` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '课程封面',
`teacher_id` int(11) NOT NULL COMMENT '外键,关联user表',
PRIMARY KEY (`id`) USING BTREE,
INDEX `teacher_id`(`teacher_id`) USING BTREE,
CONSTRAINT `course_ibfk_1` FOREIGN KEY (`teacher_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for homework
-- ----------------------------
DROP TABLE IF EXISTS `homework`;
CREATE TABLE `homework` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '作业ID',
`name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '作业名称',
`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '作业描述',
`deadline` datetime NULL DEFAULT NULL COMMENT '截止日期',
`course_id` int(11) NULL DEFAULT NULL COMMENT '外键,关联course',
PRIMARY KEY (`id`) USING BTREE,
INDEX `course_id`(`course_id`) USING BTREE,
CONSTRAINT `homework_ibfk_1` FOREIGN KEY (`course_id`) REFERENCES `course` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for homework_submission
-- ----------------------------
DROP TABLE IF EXISTS `homework_submission`;
CREATE TABLE `homework_submission` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '提交ID',
`student_id` int(11) NOT NULL COMMENT '外键,关联user表',
`homework_id` int(11) NOT NULL COMMENT '外键,关联homework表',
`content` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '作业内容',
`file` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '附件,可选',
`score` int(11) NULL DEFAULT NULL COMMENT '评分',
`comment` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '评语',
PRIMARY KEY (`id`) USING BTREE,
INDEX `student_id`(`student_id`) USING BTREE,
INDEX `homework_id`(`homework_id`) USING BTREE,
CONSTRAINT `homework_submission_ibfk_1` FOREIGN KEY (`student_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `homework_submission_ibfk_2` FOREIGN KEY (`homework_id`) REFERENCES `homework` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
`password` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
`name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '姓名',
`role` enum('teacher','student') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色(老师、学生)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
online-teaching-management-system
- static #存放小程序上传的图片
- utils
- db.js
- controllers
- userController.js
- courseController.js
- homeworkController.js
- announcementController.js
- models
- userModel.js
- courseModel.js
- homeworkModel.js
- announcementModel.js
- routes
- userRoutes.js
- courseRoutes.js
- homeworkRoutes.js
- announcementRoutes.js
- app.js
- uploadserver.js #这是一个图片服务器,负责图片的上传和下载
npm init -y // 会帮助我们生成一个package.json
npm install express mysql body-parser cors multer uuid
在根目录下新建跟app.js
同级别的uploadserver.js
文件,用来搭建文件服务器,并新建跟uploadserver.js
同级别的static
文件夹,用来存放上传的文件,编写代码如下:
// 图片上传服务器
const express = require("express")
const app = express()
const port = 9001 // 图片上传服务器的端口号
// 配置跨域
const cors = require("cors")
app.use(cors({
origin: "*"
}))
// 配置multer文件上传中间件配置
const multer = require("multer")
// 使用uuid动态生成不重名的图片
const uuid = require("uuid")
// 配置上传参数
const uploadTools = multer({
storage: multer.diskStorage({// 该存储方案将会把文件直接存入磁盘
destination: (req, file, callback) => {// 处理存储
callback(null, 'static')
},
filename: (req, file, callback) => {// 处理文件名,在这对文件名进行重新命名,防止文件名重复被替换
// 通过file,获取原始文件名,比如:wanyin.jpeg
let name = file.originalname
console.log("--------文件名:", name)
// 截取文件的扩展名,比如:.jpeg .png
let ext = name.substr(name.lastIndexOf('.'))
// 通过uuid生成一个16进制的随机文件名
let newName = uuid.v4() + ext
// 通过callback进行重新设置
callback(null, newName)
}
})
})
// 配置static目录为静态资源托管文件夹,这样就可以直接通过:http://localhost:9001/文件名 访问static目录下的资源
app.use(express.static("static"))
// 配置上传请求
// 上传action: http://localhost:9001/upload
app.post('/upload', uploadTools.single('file'), (req, res) => {
// 这是需要响应给前端的图片的地址,如果真机调试,这里的localhost换成你的电脑
let url = "http://localhost:9001/" + req.file.filename
console.log("图片上传成功!", url)
res.status(200).json({code:1,msg:"图片上传成功!",url})
})
// 监听服务器启动状态
app.listen(port, () => {
console.log("文件上传服务器启动成功!")
})
执行以下命令,启动文件服务器。
node uploadserver.js
app.js:服务端主配置文件
// 这个就是后端服务器的入口文件
// 引入所需要的模块
const express = require("express") // 有了这个模块,我们就可以通过js做后端开发
const bodyParser = require("body-parser")// 有了这个模块,就可以自动实现对象和json字符出之间的转换
const mysql = require("mysql") // 有了这个代码模块,我们就可以使用js访问和操作数据库
// 创建后端服务
const app = express()
// 配置MySQL连接
const db = mysql.createConnection({
host: "localhost",// 主机
user: "root",// 数据库默认的账户
password: "123456", // 数据库访问密码
database: "online_edu_manager", // 需要连接到的数据库名称
})
// 连接数据库
db.connect((err) => {
if (err) {
console.log("数据库连接失败!");
throw err;
}
console.log("数据库连接成功!");
})
// 测试访问
app.get('/', (req, res) => {
res.send('Hello World!');
});
// 配置body-parser中间件
app.use(bodyParser.urlencoded({ extended: false })) // 防止数据乱码
app.use(bodyParser.json()) // 对象转json
// 监听服务器是否启动
app.listen(3000, () => {
console.log("服务器启动成功:http://localhost:3000");
})
请输入以下命令启动服务器:
node app.js
在浏览器访问http://localhost:3000
针对上面的文件进行优化,将数据库配置和连接抽取到utils/db.js文件中:
db.js:用于连接MySQL数据库
/**
* MySQL数据库的配置与连接
*/
// 导入我们安装的mysql模块,这里需要注意,如果你的数据库是MySQL8.X,你需要安装mysql2
// 卸载:npm un mysql
// 安装:npm i mysql2
const mysql = require('mysql')
// 配置连接数据库的信息
const connection = mysql.createConnection({
host:"localhost", // 这里是主机的地址,我们教学都是使用这个
port:"3306",// 数据库的端口,有些同学装的数据库端口是3307,需要在这里配置,默认是3306
user:"root",// 数据库默认的管理员账户
password:"123456",// 数据库连接密码
database:"online_edu_manager" // 数据库的名字
})
// 连接数据库
connection.connect(err=>{
if(err){
console.log("数据库连接失败");
throw err
// return
}
console.log("数据库连接成功");
})
// 公开,给其他文件调用
module.exports = connection
用户表的数据操作,所有对于user表增删改查的代码都在这个文件。
/**
* 用户表(user)的操作
*/
const db = require("../utils/db.js")
/**
* 注册,添加用户,包括老师和学生
* @param {*} user:存放我们要添加到数据库的用户信息(username,password,name,role)
* @param {*} callback:回调函数,操作数据库
*/
const addUser = (user,callback)=>{
// 编写sql,这里需要使用?帮助我们占位
const sql = "insert into user(username,password,name,role) values(?,?,?,?)"
// 执行sql语句,同时使用我们传入的数据,替换到对应位置的?
db.query(sql,[user.username,user.password,user.name,user.role],(err,result)=>{
if(err){
callback(err,null) // 失败
}else{
callback(null,result.insertId) // 成功
}
})
}
/**
* 登录,查找用户,包括老师和学生
* @param {*} user:user是一个对象,里面存放前端传入的username,password
* @param {*} callback
*/
const login = (user,callback)=>{
// 编写sql
const sql = "select id,username,name,role from user where username=? and password=?"
// 执行sql
db.query(sql,[user.username,user.password],(err,result)=>{
if(err){
callback(err,null) // 失败
}else{
callback(null,result[0]) // 我们只要一个信息
}
})
}
/**
* 根据用户ID修改用户信息(除了密码),包括老师和学生
* @param {*} user:user是一个对象,里面存放前端传入的username,name,id
* @param {*} callback
*/
const updateUser = (user,callback)=>{
// 编写sql
const sql = "update user set username=?,name=? where id=?"
// 执行sql
db.query(sql,[user.username,user.name,user.id],(err,result)=>{
if(err){
callback(err,null)// 失败
}else{
callback(null,result.affectedRows) // 成功
}
})
}
/**
* 修改密码,根据账户设置新密码,包括老师和学生 {password:"xxx",id:xx}
* @param {*} user
* @param {*} callback
*/
const changePwd = (user,callback)=>{
// 编写sql
const sql = "update user set password=? where id=?"
// 执行sql
db.query(sql,[user.password,user.id],(err,result)=>{
if(err){
callback(err,null) // 失败
}else{
callback(null,result.affectedRows) // 成功
}
})
}
/**
* 注销,根据用户ID删除信息,包括老师和学生
* @param {*} userId
* @param {*} callback
*/
const logout = (userId,callback)=>{
// 编写sql
const sql = "delete from user where id=?"
// 执行sql
db.query(sql,[userId],(err,result)=>{
if(err){
callback(err,null) // 失败
}else{
callback(null,result.affectedRows) // 成功
}
})
}
// 公开方法,给外部其他文件访问
module.exports = {
addUser,
login,
updateUser,
changePwd,
logout
}
用户相关的接口逻辑,所有后端业务逻辑处理(包括前端参数的接收和后端数据的响应)都在这个文件
/**
* 控制层,实现用户的业务逻辑
*/
const userModel = require("../models/userModels") // module.exports
// 用户注册
const register = (req, res) => {
// 获取前端页面传递过来的信息
const { username, password, name, role } = req.body;
// 非空校验,可选
if (!username) {
res.status(200).json({ code: -1, msg:"用户名不能为空" })
}
if (!password) {
res.status(200).json({ code: -1, msg:"密码不能为空" })
}
if (!name) {
res.status(200).json({ code: -1, msg:"姓名不能为空" })
}
if (!role) {
res.status(200).json({ code: -1, msg:"姓名不能为空" })
}
// 访问数据库
userModel.addUser({ username, password, name, role }, (err, userId) => {
if (err) {
// res.status().json() 响应信息到前端
res.status(500).json({code:-3,msg: "服务器错误" })
} else {
if (userId) {
res.status(200).json({ code: 1, msg: "用户注册成功!" })
} else {
res.status(200).json({ code: 0, msg: "用户注册失败!" })
}
}
})
}
/**
* 用户登录
* @param {*} req:request的缩写,作用是获取到前端的请求信息
* @param {*} res:response的缩写,作用是将后端数据响应给前端
*/
const login = (req, res) => {
// 如果前端是通过表单提交的数据,后端获取使用:req.body
const { username, password } = req.body
// 非空校验
if (!username) {
res.status(200).json({ "code": -1, msg: "用户名不能为空" })
}
if (!password) {
res.status(200).json({ "code": -1, msg: "用户密码不能为空" })
}
// 访问数据库
userModel.login({ username, password }, (err, user) => {
if (err) {
console.error("用户登录:",err);
res.status(500).json({code:-3,msg:"服务器错误"})
}else{
if(user){
res.status(200).json({code:1,msg:"用户登录成功!",data:user})
}else{
res.status(200).json({code:0,msg:"用户登录失败!"})
}
}
})
}
/**
* 根据id修改用户信息
* @param {*} req
* @param {*} res
*/
const updateUser = (req,res)=>{
// 获取前端传过来的表单信息
const {id,username,name} = req.body
// 非空校验
if(!id){
res.status(200).json({code:-1,msg:"用户id不能为空"})
}
if(!username){
res.status(200).json({code:-1,msg:"用户登录名不能为空"})
}
if(!name){
res.status(200).json({code:-1,msg:"用户姓名不能为空"})
}
// 访问数据库
userModel.updateUser({id,username,name},(err,rows)=>{
if(err){
console.error("根据id修改用户信息:",err);
res.status(500).json({code:-3,msg:"服务器错误"})
}else{
if(rows && rows>0){
res.status(200).json({code:1,msg:"用户信息修改成功!"})
}else{
res.status(200).json({code:0,msg:"用户信息修改失败!"})
}
}
})
}
/**
* 修改密码
* @param {*} req
* @param {*} res
*/
const changePwd = (req,res)=>{
// 获取前端传过来的表单信息
const {id,password} = req.body
// 非空校验
if(!id){
res.status(200).json({code:-1,msg:"用户id不能为空"})
}
if(!password){
res.status(200).json({code:-1,msg:"用户密码不能为空"})
}
// 访问数据库
userModel.changePwd({id,password},(err,rows)=>{
if(err){
console.error("根据id修改用户密码:",err);
res.status(500).json({code:-3,msg:"服务器错误"})
}else{
if(rows && rows>0){
res.status(200).json({code:1,msg:"用户密码修改成功!"})
}else{
res.status(200).json({code:0,msg:"用户密码修改失败!"})
}
}
})
}
/**
* 注销
* @param {*} req
* @param {*} res
*/
const logout = (req,res)=>{
// 获取地址栏携带的id:req.params
const {id} = req.params
// 非空校验
if(!id){
res.status(200).json({code:-1,msg:"用户id不能为空"})
}
// 访问数据库
userModel.logout(id,(err,rows)=>{
if(err){
console.error("注销用户:",err);
res.status(200).json({code:-3,msg:"服务器错误"})
}else{
if(rows && rows >0){
res.status(200).json({code:1,msg:"用户注销成功!"})
}else{
res.status(200).json({code:0,msg:"用户注销失败!"})
}
}
})
}
module.exports = {
register,
login,
updateUser,
changePwd,
logout
}
用户相关的路由,所有用户模块对应的接口都在这个文件进行配置,包括请求地址、请求方式等。
const express = require('express')
const router = express.Router()
// 导入controller
const userController = require("../controllers/userController")
/**
* 用户注册接口
* method:post
* url:http://localhost:3000/user/register
* params: {username:"",password:"",name:"",role:""}
*/
router.post('/register',userController.register)
/**
* 用户登录接口
* method:post
* url:http://localhost:3000/user/login
* params:{username:"",password:""}
*/
router.post('/login',userController.login)
/**
* 用户信息修改接口
* method:put
* url:http://localhost:3000/user/update
* params:{id:'',username:'',name:''}
*/
router.put("/update",userController.updateUser)
/**
* 用户密码修改接口
* method:put
* url:http://localhost:3000/user/changePwd
* params:{id:'',password:''}
*/
router.put("/changePwd",userController.changePwd)
/**
* 用户注销接口
* method:delete
* url:http://localhost:3000/user/delete/1
*/
router.delete("/delete/:id",userController.logout)
module.exports = router
项目入口文件,包括依赖管理,服务器配置、服务监听等
// 这个就是后端服务器的入口文件
// 引入所需要的模块
const express = require("express") // 有了这个模块,我们就可以通过js做后端开发
const bodyParser = require("body-parser")// 有了这个模块,就可以自动实现对象和json字符出之间的转换
const cors = require("cors")
// 创建后端服务
const app = express()
// 导入路由
const userRoutes = require("./routes/userRoutes")
// 配置跨域
app.use(cors())
// 配置body-parser中间件
app.use(bodyParser.urlencoded({ extended: false })) // 防止数据乱码
app.use(bodyParser.json()) // 对象转json
// 注册路由
app.use("/user",userRoutes) // http://localhost:3000/user/register
// 监听服务器是否启动
app.listen(3000, () => {
console.log("服务器启动成功:http://localhost:3000");
})
课程表的数据操作,所有对于user表增删改查的代码都在这个文件。
/**
* 课程表(course)的操作
*/
const db = require("../utils/db")
// 添加课程,跟老师有关
const addCourse = (course, callback) => {
// 编写sql
const sql = "insert into course(name,description,cover,teacher_id) values(?,?,?,?)"
// 执行sql
db.query(sql, [course.name, course.description, course.cover, course.teacher_id], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results.insertId) // 成功
}
})
}
// 修改课程,跟老师有关
const updateCourse = (course, callback) => {
// 编写sql
const sql = "update course set name=?,description=?,cover=? where id=?"
// 执行sql
db.query(sql, [course.name, course.description, course.cover, course.id], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results.affectedRows) // 成功
}
})
}
// 删除课程,跟老师有关
const deleteCourse = (courseId, callback) => {
// 编写sql
const sql = "delete from course where id=?"
// 执行sql
db.query(sql, [courseId], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results.affectedRows) // 成功
}
})
}
// 根据老师ID查询课程
const findCoursesByTeacherId = (course, callback) => {
// 编写sql
const sql = "select * from course where teacher_id=? and name like ? limit ?,?"
// 解构参数
let { teacherId, keywords, startIndex, pageSize } = course
// 执行sql
db.query(sql, [teacherId, `%${keywords}%`, startIndex, pageSize], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results) // 成功
}
})
}
// 查询所有课程,给学生用
const findCourses = (course, callback) => {
// 编写sql
const sql = `select
c.id,
c.name,
c.description,
c.cover,
u.id as teacher_id,
u.name as techer_name
from
course c,
user u
where c.teacher_id = u.id
and u.name like ?
limit ?, ? `
// 解构参数
const {keywords,startIndex,pageSize} = course // 关键字,开始索引,每页条数
// 执行sql
db.query(sql,[`%${keywords}%`,startIndex,pageSize],(err,results)=>{
if(err){
callback(err,null) // 失败
}else{
callback(null,results) // 成功
}
})
}
// 查询课程总数,预留
const countCourses = (course, callback) => {
// 编写sql course{keywords,teacherId}
}
module.exports = {
addCourse,
updateCourse,
deleteCourse,
findCoursesByTeacherId,
findCourses,
countCourses
}
课程相关的接口逻辑,所有后端业务逻辑处理(包括前端参数的接收和后端数据的响应)都在这个文件
/**
* 控制层,实现课程的业务逻辑
*/
const courseModel = require("../models/courseModels")
// 添加课程
const addCourse = (req, res) => {
// 获取前端请求回来的数据
const { name, description, cover, teacher_id } = req.body
// 非空校验
if (!name) {
res.status(200).json({ code: -1, msg: "课程名称不能为空" })
}
if (!description) {
res.status(200).json({ code: -1, msg: "课程描述不能为空" })
}
if (!cover) {
res.status(200).json({ code: -1, msg: "课程封面不能为空" })
}
if (!teacher_id) {
res.status(200).json({ code: -1, msg: "老师ID不能为空" })
}
// 访问数据库
courseModel.addCourse({ name, description, cover, teacher_id }, (err, courseId) => {
if (err) {
console.log("添加课程:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (courseId) {
res.status(200).json({ code: 1, msg: "课程添加成功!" })
} else {
res.status(200).json({ code: 0, msg: "课程添加失败!" })
}
}
})
}
// 修改课程
const updateCourse = (req, res) => {
// 获取前端传递过来的请求数据
const { name, description, cover, id } = req.body
// 非空校验
if (!name) {
res.status(200).json({ code: -1, msg: "课程名称不能为空" })
}
if (!description) {
res.status(200).json({ code: -1, msg: "课程描述不能为空" })
}
if (!cover) {
res.status(200).json({ code: -1, msg: "课程封面不能为空" })
}
if (!id) {
res.status(200).json({ code: -1, msg: "课程ID不能为空" })
}
// 访问数据库
courseModel.updateCourse({ name, description, cover, id }, (err, rows) => {
if (err) {
console.log("修改课程:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (rows && rows > 0) {
res.status(200).json({ code: 1, msg: "课程修改成功!" })
} else {
res.status(200).json({ code: 0, msg: "课程修改失败!" })
}
}
})
}
// 删除课程
const deleteCourse = (req, res) => {
// 获取前端地址栏传递的课程ID
const { id } = req.params
// 非空校验
if (!id) {
res.status(200).json({ code: -1, msg: "课程ID不能为空" })
}
// 访问数据库
courseModel.deleteCourse(id, (err, rows) => {
if (err) {
console.log("删除课程:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (rows && rows > 0) {
res.status(200).json({ code: 1, msg: "课程删除成功!" })
} else {
res.status(200).json({ code: 0, msg: "课程删除失败!" })
}
}
})
}
// 根据老师id查询课程
const findCoursesById = (req, res) => {
// 获取前端地址栏传递的老师ID,关键字,分页信息
let { teacherId, keywords, startIndex, pageSize } = req.params
// 处理关键字
if (keywords == "''") {
keywords = ""
}
// 访问数据库,转过来的数字字符串需要转换为数字
courseModel.findCoursesByTeacherId({ teacherId:Number(teacherId), keywords, startIndex:Number(startIndex), pageSize:Number(pageSize) }, (err, results) => {
if (err) {
console.log("根据老师id查询课程:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (results && results.length > 0) {
res.status(200).json({ code: 1, msg: "课程列表获取成功!", data: results })
} else {
res.status(200).json({ code: 0, msg: "课程列表获取失败!" })
}
}
})
}
// 查询所有课程
const findCourses = (req, res) => {
// 获取前端地址栏传递的老师ID,关键字,分页信息
let { keywords, startIndex, pageSize } = req.params
// 处理关键字
if (keywords == "''") {
keywords = ""
}
// 访问数据库
courseModel.findCourses({ keywords, startIndex:Number(startIndex), pageSize:Number(pageSize) } , (err, results) => {
if (err) {
console.log("查询所有课程:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (results && results.length > 0) {
res.status(200).json({ code: 1, msg: "课程列表获取成功!", data: results })
} else {
res.status(200).json({ code: 0, msg: "课程列表获取失败!" })
}
}
})
}
module.exports = {
addCourse,
updateCourse,
deleteCourse,
findCoursesById,
findCourses
}
课程相关的路由,所有用户模块对应的接口都在这个文件进行配置,包括请求地址、请求方式等。
const express = require('express')
const router = express.Router()
// 导入courseController
const courseController = require("../controllers/courseController")
/**
* 添加课程接口
* method:post
* url:http://localhost:3000/course/addCourse
* data:{name:"",description:"",cover:"",teacher_id:0}
*/
router.post("/addCourse", courseController.addCourse)
/**
* 修改课程接口
* method:put
* url:http://localhost:3000/course/updateCourse
* data:{name:"",description:"",cover:"",id:0}
*/
router.put("/updateCourse", courseController.updateCourse)
/**
* 删除课程接口
* method:delete
* url:http://localhost:3000/course/deleteCourse/1
*/
router.delete("/deleteCourse/:id", courseController.deleteCourse)
/**
* 根据老师id查询课程接口
* method:get
* url:http://localhost:3000/course/findCoursesById/1/''/0/5
*/
router.get("/findCoursesById/:teacherId/:keywords/:startIndex/:pageSize", courseController.findCoursesById)
/**
* 查询所有课程接口
* method:get
* url:http://localhost:3000/course/findCourses/''/0/5
*/
router.get("/findCourses/:keywords/:startIndex/:pageSize",courseController.findCourses)
module.exports = router
项目入口文件,包括依赖管理,服务器配置、服务监听等
// 这个就是后端服务器的入口文件
// 引入所需要的模块
const express = require("express") // 有了这个模块,我们就可以通过js做后端开发
const bodyParser = require("body-parser")// 有了这个模块,就可以自动实现对象和json字符出之间的转换
const cors = require("cors")
// 创建后端服务
const app = express()
// 导入路由
const userRoutes = require("./routes/userRoutes")
const courseRoutes = require("./routes/courseRoutes")
// 配置跨域
app.use(cors())
// 配置body-parser中间件
app.use(bodyParser.urlencoded({ extended: false })) // 防止数据乱码
app.use(bodyParser.json()) // 对象转json
// 注册路由
app.use("/user",userRoutes)
app.use("/course",courseRoutes)
// 监听服务器是否启动
app.listen(3000, () => {
console.log("服务器启动成功:http://localhost:3000");
})
作业表的数据操作,所有对于user表增删改查的代码都在这个文件。
/**
* 作业表(homework)的操作
*/
const db = require("../utils/db")
// 老师添加作业
const addHomework = (homework, callback) => {
// 编写sql
const sql = "insert into homework(name,description,deadline,course_id) values(?,?,?,?)"
// 执行sql
db.query(sql, [homework.name, homework.description, homework.deadline, homework.course_id], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results.insertId) // 成功
}
})
}
// 老师修改作业
const updateHomework = (homework, callback) => {
// 编写sql
const sql = "update homework set name=?,description=?,deadline=? where id=?"
// 执行sql
db.query(sql, [homework.name, homework.description, homework.deadline, homework.id], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results.affectedRows) // 成功
}
})
}
// 老师删除作业
const deleteHomework = (homeworkId, callback) => {
// 编写sql
const sql = "delete from homework where id=?"
// 执行sql
db.query(sql, [homeworkId], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results.affectedRows) // 成功
}
})
}
// 老师/学生查看作业
const findHomeworks = (homework, callback) => {
console.log("homework:", homework);
// 创建一个数组,存放参数
let paramsArr = []
// 编写sql
let sql = `
select
h.id,
c.cover,
h.name,
h.description,
h.deadline,
c.name as course_name,
u.name as teacher_name
from
user u,
course c,
homework h
where c.teacher_id = u.id
and h.course_id = c.id
`
// 根据老师id查询某个老师的作业
if (homework && homework.teacherId != -1) {
sql += `
and u.id = ?
`
paramsArr.push(homework.teacherId)
}
// 根据老师名称、课程名称、作业名称模糊查询作业
sql += `
and (
h.name like ?
or c.name like ?
or u.name like ?
)
limit ?,?
`
paramsArr.push(`%${homework.keywords}%`) // 作业名称
paramsArr.push(`%${homework.keywords}%`) // 课程名称
paramsArr.push(`%${homework.keywords}%`) // 老师名称
paramsArr.push(homework.startIndex)// 开始索引
paramsArr.push(homework.pageSize) // 每页显示条数
console.log(sql, paramsArr);
// 执行sql
db.query(sql, paramsArr, (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results) // 成功
}
})
}
// 学生提交作业
const subHomework = (homework, callback) => {
// 编写sql
const sql = "insert into homework_submission(student_id,homework_id,content,file) values(?,?,?,?)"
// 执行sql
db.query(sql, [homework.studentId, homework.homeworkId, homework.content, homework.file], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results.insertId) // 成功
}
})
}
// 老师查看学生提交的作业
const findSubHomeworks = (homework, callback) => {
// 编写sql
const sql = `
SELECT
h.name as homework_name,
h.deadline,
c.name as course_name,
c.cover,
u.name as student_name,
hs.content,
hs.file
FROM
user u,
course c,
homework h,
homework_submission hs
WHERE
hs.homework_id = h.id and
h.course_id = c.id and
hs.student_id = u.id and
c.teacher_id = ? and
ISNULL(hs.score)
limit ?,?
`
// 执行sql
db.query(sql, [homework.teacherId, homework.startIndex, homework.pageSize], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results) // 成功
}
})
}
// 老师批改作业
const correctingHomework = (homework, callback) => {
// 编写sql
const sql = "update homework_submission set score=?,comment=? where id=?"
// 执行sql
db.query(sql, [homework.score, homework.comment, homework.id], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results.affectedRows) // 成功
}
})
}
// 学生查看已批改的作业
const findCorrectedHomeworks = (homework,callback)=>{
// 编写sql
const sql = `
SELECT
h.name as homework_name,
h.deadline,
c.name as course_name,
c.cover,
u.name as teacher_name,
hs.score,
hs.comment
FROM
user u,
course c,
homework h,
homework_submission hs
WHERE
hs.homework_id = h.id and
h.course_id = c.id and
c.teacher_id = u.id and
hs.score is not null and
hs.student_id = ?
limit ?,?
`
// 执行sql
db.query(sql, [homework.studentId, homework.startIndex, homework.pageSize], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results) // 成功
}
})
}
module.exports = {
addHomework,
updateHomework,
deleteHomework,
findHomeworks,
subHomework,
findSubHomeworks,
correctingHomework,
findCorrectedHomeworks
}
作业相关的接口逻辑,所有后端业务逻辑处理(包括前端参数的接收和后端数据的响应)都在这个文件
/**
* 控制层,实现作业的业务逻辑
*/
const homeworkModel = require("../models/homeworkModel")
// 老师布置作业
const addHomework = (req, res) => {
// 获取前端表单提交的内容
const { name, description, deadline, course_id } = req.body
if (!name) {
res.status(200).json({ code: -1, msg: "作业名称不能为空" })
}
if (!description) {
res.status(200).json({ code: -1, msg: "作业描述不能为空" })
}
if (!deadline) {
res.status(200).json({ code: -1, msg: "截至日期不能为空" })
}
if (!course_id) {
res.status(200).json({ code: -1, msg: "课程ID不能为空" })
}
// 访问数据库
homeworkModel.addHomework({ name, description, deadline, course_id }, (err, homeworkId) => {
if (err) {
console.log("布置作业:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (homeworkId) {
res.status(200).json({ code: 1, msg: "作业布置成功!" })
} else {
res.status(200).json({ code: 0, msg: "作业布置失败!" })
}
}
})
}
// 老师修改作业
const updateHomework = (req, res) => {
// 获取前端表单传递过来的请求数据
const { name, description, deadline, id } = req.body
if (!name) {
res.status(200).json({ code: -1, msg: "作业名称不能为空" })
}
if (!description) {
res.status(200).json({ code: -1, msg: "作业描述不能为空" })
}
if (!deadline) {
res.status(200).json({ code: -1, msg: "截至日期不能为空" })
}
if (!id) {
res.status(200).json({ code: -1, msg: "作业ID不能为空" })
}
// 访问数据库
homeworkModel.updateHomework({ name, description, deadline, id }, (err, rows) => {
if (err) {
console.log("修改作业:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (rows && rows > 0) {
res.status(200).json({ code: 1, msg: "作业修改成功!" })
} else {
res.status(200).json({ code: 0, msg: "作业修改失败!" })
}
}
})
}
// 老师删除作业
const deleteHomework = (req, res) => {
// 获取路径上携带的作业id
const { id } = req.params
// 非空校验
if (!id) {
res.status(200).json({ code: -1, msg: "作业ID不能为空" })
}
// 访问数据库
homeworkModel.deleteHomework(id, (err, rows) => {
if (err) {
console.log("删除作业:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (rows && rows > 0) {
res.status(200).json({ code: 1, msg: "作业删除成功!" })
} else {
res.status(200).json({ code: 0, msg: "作业删除失败!" })
}
}
})
}
// 老师/学生查看作业
const findHomeworks = (req, res) => {
console.log("-----", req.params);
// 获取路径上携带的信息
let { teacherId, keywords, startIndex, pageSize } = req.params
// 处理老师编号
if (teacherId == -1) {
teacherId == null // 此时查所有课程
}
// 处理关键字
if (keywords == "''") {
keywords = ""
}
// 访问数据库
homeworkModel.findHomeworks({ teacherId: Number(teacherId), keywords, startIndex: Number(startIndex), pageSize: Number(pageSize) }, (err, results) => {
if (err) {
console.log("查询所有作业:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (results && results.length > 0) {
res.status(200).json({ code: 1, msg: "作业列表获取成功!", data: results })
} else {
res.status(200).json({ code: 0, msg: "作业列表获取失败!" })
}
}
})
}
// 学生提交作业
const subHomework = (req, res) => {
// 获取前端表单提交的数据
const { studentId, homeworkId, content, file } = req.body
// 非空判断
if (!studentId) {
res.status(200).json({ code: -1, msg: "学生ID不能为空" })
}
if (!homeworkId) {
res.status(200).json({ code: -1, msg: "作业ID不能为空" })
}
if (!content) {
res.status(200).json({ code: -1, msg: "作业内容不能为空" })
}
// 访问数据库
homeworkModel.subHomework({ studentId, homeworkId, content, file }, (err, subHomeworkId) => {
if (err) {
console.log("提交作业:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (subHomeworkId) {
res.status(200).json({ code: 1, msg: "作业提交成功!" })
} else {
res.status(200).json({ code: 0, msg: "作业提交失败!" })
}
}
})
}
// 老师查看已提交作业
const findSubHomeworks = (req, res) => {
// 获取路径上携带的参数
let { teacherId, startIndex, pageSize } = req.params
// 访问数据库
homeworkModel.findSubHomeworks({ teacherId: Number(teacherId), startIndex: Number(startIndex), pageSize: Number(pageSize) }, (err, results) => {
if (err) {
console.log("查询所有已提交作业:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (results && results.length > 0) {
res.status(200).json({ code: 1, msg: "已提交作业列表获取成功!", data: results })
} else {
res.status(200).json({ code: 0, msg: "已提交作业列表获取失败!" })
}
}
})
}
// 老师批改作业
const correctingHomework = (req, res) => {
// 获取前端表单传递过来的数据
const { score, comment, id } = req.body
// 非空校验
if (!score) {
res.status(200).json({ code: -1, msg: "评分不能为空" })
}
if (!comment) {
res.status(200).json({ code: -1, msg: "评语不能为空" })
}
if (!id) {
res.status(200).json({ code: -1, msg: "作业ID不能为空" })
}
// 访问数据库
homeworkModel.correctingHomework({ score, comment, id }, (err, rows) => {
if (err) {
console.log("批改作业:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (rows && rows > 0) {
res.status(200).json({ code: 1, msg: "作业批改成功!" })
} else {
res.status(200).json({ code: 0, msg: "作业批改失败!" })
}
}
})
}
// 学生查看已批改的作业
const findCorrectedHomeworks = (req,res)=>{
// 获取前端路径携带的数据
let {studentId, startIndex, pageSize} = req.params
// 访问数据库
homeworkModel.findCorrectedHomeworks({studentId:Number(studentId), startIndex:Number(startIndex), pageSize:Number(pageSize)},(err,results)=>{
if (err) {
console.log("查询已批改的作业:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (results && results.length > 0) {
res.status(200).json({ code: 1, msg: "已批改作业列表获取成功!", data: results })
} else {
res.status(200).json({ code: 0, msg: "已批改作业列表获取失败!" })
}
}
})
}
module.exports = {
addHomework,
updateHomework,
deleteHomework,
findHomeworks,
subHomework,
findSubHomeworks,
correctingHomework,
findCorrectedHomeworks
}
作业相关的路由,所有用户模块对应的接口都在这个文件进行配置,包括请求地址、请求方式等。
const express = require("express")
const router = express.Router()
// 导入homeworkController
const homeworkController = require("../controllers/homeworkController")
/**
* 老师布置作业接口
* method:post
* url:http://localhost:3000/homework/addHomework
* data:{name,description,deadline,course_id}
*/
router.post("/addHomework",homeworkController.addHomework)
/**
* 老师修改作业接口
* method:put
* url:http://localhost:3000/homework/updateHomework
* data:{name,description,deadline,id}
*/
router.put("/updateHomework",homeworkController.updateHomework)
/**
* 老师删除作业接口
* method:delete
* url:http://localhost:3000/homework/deleteHomework/作业id
*/
router.delete("/deleteHomework/:id",homeworkController.deleteHomework)
/**
* 老师/学生查询作业接口
* method:get
* url:http://localhost:3000/homework/findHomeworks/老师ID/关键字(可匹配课程名|老师名|作业名)/开始索引/每页条数
* 注意:如果不是查询某个老师的课程,就给teacherId设置为-1,举例如下:
* 1- 查询某个老师布置的所有作业:http://localhost:3000/homework/findHomeworks/2/''/0/5
* 2- 查询所有作业:http://localhost:3000/homework/findHomeworks/-1/''/0/5
*/
router.get("/findHomeworks/:teacherId/:keywords/:startIndex/:pageSize",homeworkController.findHomeworks)
/**
* 学生提交作业接口
* method:post
* url:http://localhost:3000/homework/subHomework
* data:{studentId,homeworkId,content,file[可为空]}
*/
router.post("/subHomework",homeworkController.subHomework)
/**
* 老师查看已提交作业接口
* method:get
* url:http://localhost:3000/homework/findSubHomeworks/老师ID/开始索引/每页显示条数
*/
router.get("/findSubHomeworks/:teacherId/:startIndex/:pageSize",homeworkController.findSubHomeworks)
/**
* 老师批改作业接口
* method:put
* url:http://localhost:3000/homework/correctingHomework
* data:{score,comment,id}
*/
router.put("/correctingHomework",homeworkController.correctingHomework)
/**
* 学生查看已批改作业接口
* method:get
* url:http://localhost:3000/homework/findCorrectedHomeworks/学生ID/开始索引/每页显示条数
*/
router.get("/findCorrectedHomeworks/:studentId/:startIndex/:pageSize",homeworkController.findCorrectedHomeworks)
module.exports = router
项目入口文件,包括依赖管理,服务器配置、服务监听等
// 这个就是后端服务器的入口文件
// 引入所需要的模块
const express = require("express") // 有了这个模块,我们就可以通过js做后端开发
const bodyParser = require("body-parser")// 有了这个模块,就可以自动实现对象和json字符出之间的转换
const cors = require("cors")
// 创建后端服务
const app = express()
// 导入路由
const userRoutes = require("./routes/userRoutes")
const courseRoutes = require("./routes/courseRoutes")
const homeworkRoutes = require("./routes/homeworkRoutes")
// 配置跨域
app.use(cors())
// 配置body-parser中间件
app.use(bodyParser.urlencoded({ extended: false })) // 防止数据乱码
app.use(bodyParser.json()) // 对象转json
// 注册路由
app.use("/user",userRoutes) // http://localhost:3000/user/register
app.use("/course",courseRoutes)
app.use("/homework",homeworkRoutes)
// 监听服务器是否启动
app.listen(3000, () => {
console.log("服务器启动成功:http://localhost:3000");
})
公告表的数据操作,所有对于user表增删改查的代码都在这个文件。
/**
* 公告表(Announcement)的操作
*/
const db = require("../utils/db")
// 添加公告,跟老师有关
const addAnnouncement = (announcement, callback) => {
// 编写sql
const sql = "insert into announcement(title,content,teacher_id) values(?,?,?)"
// 执行sql
db.query(sql, [announcement.title, announcement.content, announcement.teacherId], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results.insertId) // 成功
}
})
}
// 修改公告,跟老师有关
const updateAnnouncement = (announcement, callback) => {
// 编写sql
const sql = "update announcement set title=?,content=? where id=?"
// 执行sql
db.query(sql, [announcement.title, announcement.content, announcement.id], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results.affectedRows) // 成功
}
})
}
// 删除公告,跟老师有关
const deleteAnnouncement = (announcementId, callback) => {
// 编写sql
const sql = "delete from announcement where id=?"
// 执行sql
db.query(sql, [announcementId], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results.affectedRows) // 成功
}
})
}
// 根据老师ID查询公告
const findAnnouncementsByTeacherId = (announcement, callback) => {
// 编写sql
const sql = "select * from announcement where teacher_id=? and title like ? limit ?,?"
// 解构参数
let { teacherId, keywords, startIndex, pageSize } = announcement
// 执行sql
db.query(sql, [teacherId, `%${keywords}%`, startIndex, pageSize], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results) // 成功
}
})
}
// 查询所有公告,给学生用
const findAnnouncements = (announcement, callback) => {
// 编写sql
const sql = "select * from announcement where title like ? limit ?,?"
// 解构参数
let { keywords, startIndex, pageSize } = announcement
// 执行sql
db.query(sql, [`%${keywords}%`, startIndex, pageSize], (err, results) => {
if (err) {
callback(err, null) // 失败
} else {
callback(null, results) // 成功
}
})
}
module.exports = {
addAnnouncement,
updateAnnouncement,
deleteAnnouncement,
findAnnouncementsByTeacherId,
findAnnouncements
}
公告相关的接口逻辑,所有后端业务逻辑处理(包括前端参数的接收和后端数据的响应)都在这个文件
/**
* 控制层,实现公告的业务逻辑
*/
const announcementModel = require("../models/announcementModels")
// 添加公告
const addAnnouncement = (req, res) => {
// 获取前端请求回来的数据
const { title, content,teacherId } = req.body
// 非空校验
if (!title) {
res.status(200).json({ code: -1, msg: "公告题目不能为空" })
}
if (!content) {
res.status(200).json({ code: -1, msg: "公告内容不能为空" })
}
if (!teacherId) {
res.status(200).json({ code: -1, msg: "老师ID不能为空" })
}
// 访问数据库
announcementModel.addAnnouncement({ title, content,teacherId }, (err, announcementId) => {
if (err) {
console.log("添加公告:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (announcementId) {
res.status(200).json({ code: 1, msg: "公告添加成功!" })
} else {
res.status(200).json({ code: 0, msg: "公告添加失败!" })
}
}
})
}
// 修改公告
const updateAnnouncement = (req, res) => {
// 获取前端传递过来的请求数据
const { title, content, id } = req.body
// 非空校验
if (!title) {
res.status(200).json({ code: -1, msg: "公告题目不能为空" })
}
if (!content) {
res.status(200).json({ code: -1, msg: "公告内容不能为空" })
}
if (!id) {
res.status(200).json({ code: -1, msg: "公告ID不能为空" })
}
// 访问数据库
announcementModel.updateAnnouncement({ title, content, id }, (err, rows) => {
if (err) {
console.log("修改公告:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (rows && rows > 0) {
res.status(200).json({ code: 1, msg: "公告修改成功!" })
} else {
res.status(200).json({ code: 0, msg: "公告修改失败!" })
}
}
})
}
// 删除公告
const deleteAnnouncement = (req, res) => {
// 获取前端地址栏传递的公告ID
const { id } = req.params
// 非空校验
if (!id) {
res.status(200).json({ code: -1, msg: "公告ID不能为空" })
}
// 访问数据库
announcementModel.deleteAnnouncement(id, (err, rows) => {
if (err) {
console.log("删除公告:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (rows && rows > 0) {
res.status(200).json({ code: 1, msg: "公告删除成功!" })
} else {
res.status(200).json({ code: 0, msg: "公告删除失败!" })
}
}
})
}
// 根据老师id查询公告
const findAnnouncementsById = (req, res) => {
// 获取前端地址栏传递的老师ID,关键字,分页信息
let { teacherId, keywords, startIndex, pageSize } = req.params
// 处理关键字
if (keywords == "''") {
keywords = ""
}
// 访问数据库,转过来的数字字符串需要转换为数字
announcementModel.findAnnouncementsByTeacherId({ teacherId:Number(teacherId), keywords, startIndex:Number(startIndex), pageSize:Number(pageSize) }, (err, results) => {
if (err) {
console.log("根据老师id查询公告:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (results && results.length > 0) {
res.status(200).json({ code: 1, msg: "公告列表获取成功!", data: results })
} else {
res.status(200).json({ code: 0, msg: "公告列表获取失败!" })
}
}
})
}
// 查询所有公告
const findAnnouncements = (req, res) => {
// 获取前端地址栏传递的老师ID,关键字,分页信息
let { keywords, startIndex, pageSize } = req.params
// 处理关键字
if (keywords == "''") {
keywords = ""
}
// 访问数据库
announcementModel.findAnnouncements({ keywords, startIndex:Number(startIndex), pageSize:Number(pageSize) } , (err, results) => {
if (err) {
console.log("查询所有公告:", err);
res.status(500).json({ code: -3, msg: "服务器错误" })
} else {
if (results && results.length > 0) {
res.status(200).json({ code: 1, msg: "公告列表获取成功!", data: results })
} else {
res.status(200).json({ code: 0, msg: "公告列表获取失败!" })
}
}
})
}
module.exports = {
addAnnouncement,
updateAnnouncement,
deleteAnnouncement,
findAnnouncementsById,
findAnnouncements
}
公告相关的路由,所有用户模块对应的接口都在这个文件进行配置,包括请求地址、请求方式等。
const express = require('express')
const router = express.Router()
// 导入announcementController
const announcementController = require("../controllers/announcementController")
/**
* 添加公告接口
* method:post
* url:http://localhost:3000/announcement/addAnnouncement
* data:{title:"",content:"",teacher_id:0}
*/
router.post("/addAnnouncement", announcementController.addAnnouncement)
/**
* 修改公告接口
* method:put
* url:http://localhost:3000/announcement/updateAnnouncement
* data:{title:"",content:"",id:0}
*/
router.put("/updateAnnouncement", announcementController.updateAnnouncement)
/**
* 删除公告接口
* method:delete
* url:http://localhost:3000/announcement/deleteAnnouncement/公告ID
*/
router.delete("/deleteAnnouncement/:id", announcementController.deleteAnnouncement)
/**
* 根据老师id查询公告接口
* method:get
* url:http://localhost:3000/announcement/findAnnouncementsById/老师ID/(''|关键字)/开始索引/每页显示条数
*/
router.get("/findAnnouncementsById/:teacherId/:keywords/:startIndex/:pageSize", announcementController.findAnnouncementsById)
/**
* 查询所有公告接口
* method:get
* url:http://localhost:3000/announcement/findAnnouncements/(''|关键字)/开始索引/每页显示条数
*/
router.get("/findAnnouncements/:keywords/:startIndex/:pageSize", announcementController.findAnnouncements)
module.exports = router
项目入口文件,包括依赖管理,服务器配置、服务监听等
// 这个就是后端服务器的入口文件
// 引入所需要的模块
const express = require("express") // 有了这个模块,我们就可以通过js做后端开发
const bodyParser = require("body-parser")// 有了这个模块,就可以自动实现对象和json字符出之间的转换
const cors = require("cors")
// 创建后端服务
const app = express()
// 导入路由
const userRoutes = require("./routes/userRoutes")
const courseRoutes = require("./routes/courseRoutes")
const homeworkRoutes = require("./routes/homeworkRoutes")
const announcementRoutes = require("./routes/announcementRoutes")
// 配置跨域
app.use(cors())
// 配置body-parser中间件
app.use(bodyParser.urlencoded({ extended: false })) // 防止数据乱码
app.use(bodyParser.json()) // 对象转json
// 注册路由
app.use("/user",userRoutes) // http://localhost:3000/user/register
app.use("/course",courseRoutes)
app.use("/homework",homeworkRoutes)
app.use("/announcement",announcementRoutes)
// 监听服务器是否启动
app.listen(3000, () => {
console.log("服务器启动成功:http://localhost:3000");
})
我们的接口开发好之后,我们需要使用第三方的PostMan/ApiPost等接口测试工具进行测试。
参照:https://vant-contrib.gitee.io/vant-weapp/#/quickstart
第一步:
阿里巴巴矢量图标库:https://www.iconfont.cn/
通过阿里巴巴矢量图标库,设计自己的tabBar图标,将图片下载存放到iamges
文件夹,效果如下:
第二步:
在pages
下创建三个page
,效果如下:
第三步:
在app.json
中配置tabBar
,代码如下:
"tabBar": {
"color": "#8a8a8a",
"selectedColor": "#07c160",
"backgroundColor": "#f7f8fa",
"borderStyle": "white",
"list": [{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "images/icon_home_off.png",
"selectedIconPath": "images/icon_home_on.png"
},{
"pagePath": "pages/announcement/announcement",
"text":"公告",
"iconPath": "images/icon_anno_off.png",
"selectedIconPath": "images/icon_anno_on.png"
},{
"pagePath": "pages/mine/mine",
"text":"我的",
"iconPath": "images/icon_mine_off.png",
"selectedIconPath": "images/icon_mine_on.png"
}]
},
效果图,如下:
// 将host提取出来,这样方便更改
// 电脑端访问
// const base = "http://localhost"
// 手机端访问
const base = "http://172.23.1.10"
const host = `${base}:3000`
const fileHost = `${base}:9001`
module.exports = {
host,
fileHost
}
/**app.wxss**/
/* .container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200rpx 0;
box-sizing: border-box;
} */
首先,我们需要通过U钙网
设置一枚LOGO,将设计好的logo存放至images
中。
<view class="container">
<!-- LOGO -->
<view class="logo">
<image src="../../images/logo.png" mode="" />
</view>
<!-- 表单 -->
<form bindsubmit="" class="form">
<van-cell-group>
<van-field bind:change="getUsername" value="{{ username }}" label="账号" placeholder="请输入用户账号" error-message="{{usernameErrMsg}}" />
<van-field bind:change="getPassword" value="{{ password }}" type="password" password label="密码" placeholder="请输入用户密码" error-message="{{passwordErrMsg}}" border="{{ false }}" />
</van-cell-group>
<view class="login">
<van-button type="primary" class="login-btn" block bind:click="login">登录</van-button>
</view>
</form>
<view class="register">
<view class="title">您还没有账号?</view>
<navigator class="href" url="../register/register">请立即注册</navigator>
</view>
<!-- 技术支持 -->
<view class="footer">
技术支持:西安粤嵌信息科技有限公司
</view>
</view>
page{
width: 100%;
height: 100%;
background-color: #f2f2f2;
}
.container{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: start;
}
.container .logo{
width: 100%;
height: 380rpx;
margin-top: 40rpx;
display: flex;
justify-content: center;
}
.container .logo image{
width: 380rpx;
height: 380rpx;
}
.container .form{
margin: 0;
padding: 0;
width: 100%;
margin-top: 40rpx;
}
.login{
width: 100%;
box-sizing: border-box;
margin: 60rpx 0rpx 60rpx 0rpx;
padding: 0 20rpx;
}
.register{
width: 100%;
padding: 20rpx;
display: flex;
justify-content: center;
color: #333;
font-size: 28rpx;
}
.register .href{
color:#29a1f7;
}
.footer{
position: fixed;
bottom: 60rpx;
font-size: 24rpx;
color:#666;
}
{
"navigationStyle": "custom",
"usingComponents": {
"van-field": "@vant/weapp/field/index",
"van-button": "@vant/weapp/button/index"
}
}
// pages/login/login.js
const http = require("../../utils/http")
Page({
/**
* 页面的初始数据
*/
data: {
username: '', // 用来存表单中的账号信息
password: '', // 用来存表单中的账号密码
usernameErrMsg: '', // 账号错误提示
passwordErrMsg: '', // 密码错误提示
},
/**
* 接收用户输入的账号
* @param {*} e
*/
getUsername(e) {
this.setData({
username: e.detail
})
},
/**
* 接收用户输入的密码
* @param {*} e
*/
getPassword(e) {
this.setData({
password: e.detail
})
},
/**
* 登录的方法
*/
login() {
let that = this
// 解构username,password
const {
username,
password
} = this.data
// 非空校验
if (!username || username.length == 0) {
this.setData({
usernameErrMsg: "账号不能为空"
})
return
} else {
// 重置提示框
this.setData({
usernameErrMsg: "",
})
}
if (!password || password.length == 0) {
this.setData({
passwordErrMsg: "密码不能为空"
})
return // return之后,后续代码不再问题
} else {
// 重置提示框
this.setData({
passwordErrMsg: ""
})
}
// 请求后端服务器
wx.request({
url: `${http.host}/user/login`,
method: "post",
data: {
username: username,
password: password
},
success: (res) => {
if (res && res.data && res.data.code == 1) { // 登录成功
// 全局保存用户信息,其他地方也能用 localStorage
wx.setStorageSync('userId', res.data.data.id)
wx.setStorageSync('username', res.data.data.username)
wx.setStorageSync('name', res.data.data.name)
wx.setStorageSync('role', res.data.data.role)
// 弹出消息提示框
wx.showToast({
title: res.data.msg, //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'success', //图标,支持"success"、"loading"
success: function () {
// 登录成功,跳转
wx.switchTab({
url: '/pages/home/home',
})
}
})
} else { // 登录失败
wx.showToast({
title: res.data.msg, //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'error', //图标,支持"success"、"loading"
})
}
},
fail: (err) => {
wx.showToast({
title: "服务器请求异常", //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'error', //图标,支持"success"、"loading"
})
}
})
console.log("请求后端服务器!");
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
})
<!-- 表单 -->
<form bindsubmit="" class="form">
<van-cell-group>
<van-field bind:change="getUsername" value="{{ username }}" label="账号" placeholder="请输入用户账号" error-message="{{usernameErrMsg}}" />
<van-field bind:change="getPassword" value="{{ password }}" type="password" password label="密码" placeholder="请输入用户密码" error-message="{{passwordErrMsg}}" />
<van-field bind:change="getName" value="{{ name }}" label="姓名" placeholder="请输入用户姓名" error-message="{{nameErrMsg}}" />
<van-cell title="身份" title-width="100px">
<van-radio-group value="{{ role }}" bind:change="onChange" direction="horizontal" slot="right-icon">
<van-radio name="teacher">老师</van-radio>
<van-radio name="student">学生</van-radio>
</van-radio-group>
</van-cell>
</van-cell-group>
<view class="login">
<van-button type="primary" class="login-btn" block bind:click="register">注册</van-button>
</view>
</form>
page{
width: 100%;
height: 100%;
background-color: #f2f2f2;
}
.container{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: start;
}
.login{
width: 100%;
box-sizing: border-box;
margin: 60rpx 0rpx 60rpx 0rpx;
padding: 0 20rpx;
}
{
"navigationBarTitleText": "用户注册",
"usingComponents": {
"van-field": "@vant/weapp/field/index",
"van-button": "@vant/weapp/button/index",
"van-cell": "@vant/weapp/cell/index",
"van-cell-group": "@vant/weapp/cell-group/index",
"van-radio": "@vant/weapp/radio/index",
"van-radio-group": "@vant/weapp/radio-group/index"
}
}
// pages/register/register.js
const http = require("../../utils/http")
Page({
/**
* 页面的初始数据
*/
data: {
username: '', // 用来存表单中的账号信息
password: '', // 用来存表单中的账号密码
name: '', // 用来存表单中的用户姓名
usernameErrMsg: '', // 账号错误提示
passwordErrMsg: '', // 密码错误提示
nameErrMsg: '', // 姓名错误提示
role: 'student', // 单选按钮的值
},
/**
* 接收用户输入的账号
* @param {*} e
*/
getUsername(e) {
this.setData({
username: e.detail
})
},
/**
* 接收用户输入的密码
* @param {*} e
*/
getPassword(e) {
this.setData({
password: e.detail
})
},
/**
* 接收用户输入的姓名
* @param {*} e
*/
getName(e) {
this.setData({
name: e.detail
})
},
/**
* 接收用户选择的用户身份
* @param {*} event
*/
onChange(event) {
this.setData({
role: event.detail,
});
},
/**
* 注册的方法
*/
register() {
let that = this
// 解构username,password
const {
username,
password,
name,
role,
} = this.data
// 非空校验
if (!username || username.length == 0) {
this.setData({
usernameErrMsg: "账号不能为空"
})
return
} else {
// 重置提示框
this.setData({
usernameErrMsg: "",
})
}
if (!password || password.length == 0) {
this.setData({
passwordErrMsg: "密码不能为空"
})
return // return之后,后续代码不再问题
} else {
// 重置提示框
this.setData({
passwordErrMsg: ""
})
}
if (!name || name.length == 0) {
this.setData({
nameErrMsg: "姓名不能为空"
})
return // return之后,后续代码不再问题
} else {
// 重置提示框
this.setData({
nameErrMsg: ""
})
}
// 请求后端服务器
wx.request({
url: `${http.host}/user/register`,
method: "post",
data: {
username,password,name,role
},
success: (res) => {
if (res && res.data && res.data.code == 1) { // 注册成功
wx.showToast({
title: res.data.msg, //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'success', //图标,支持"success"、"loading"
success: function () {
// 注册成功,返回到登录
wx.navigateBack()
}
})
} else { // 注册失败
wx.showToast({
title: res.data.msg, //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'error', //图标,支持"success"、"loading"
})
}
},
fail: (err) => {
wx.showToast({
title: "服务器请求异常", //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'error', //图标,支持"success"、"loading"
})
}
})
console.log("请求后端服务器!");
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
})
<view class="container">
<!-- banner -->
<view class="banner">
<image src="../../images/banner.png" mode="" />
</view>
<!-- item -->
<view class="item-box">
<view class="item" bindtap="gotoCourseList">
<image src="../../images/item_course.png"></image>
<view class="title">课程管理</view>
</view>
<view class="item" >
<image src="../../images/item_homework.png"></image>
<view class="title">作业管理</view>
</view>
</view>
</view>
page{
background-color: #f2f2f2;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.container{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: start;
}
.banner{
width: 100%;
height: 300rpx;
}
.banner image{
width: 100%;
height: 100%;
}
.item-box{
width: 100%;
height: 260rpx;
display: flex;
justify-content: space-around;
align-items: center;
background-color: #fff;
}
.item-box .item{
width: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.item-box .item image{
width: 50px;
height: 50px;
}
.item-box .item .title{
margin-top: 10px;
color:#333;
font-size: 14px;
}
{
"usingComponents": {
}
}
// pages/home/home.js
Page({
/**
* 页面的初始数据
*/
data: {
role: 'student'
},
/**
* 跳转到课程管理列表界面
*/
gotoCourseList() {
let {
role
} = this.data
if (role == 'teacher') {
// 跳转老师的课程管理
wx.navigateTo({
url: '/pages/teacher/courseList/courseList',
})
} else {
// 跳转学生的课程管理
wx.navigateTo({
url: '/pages/student/courseList/courseList',
})
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 获取登录时,保存的用户身份信息
let role = wx.getStorageSync('role')
this.setData({
role
})
},
})
<view class="container">
<!-- header -->
<view class="header">
<view class="header-l">
<view class="title">
我的课程
</view>
<view class="total">
授课讲师:{{name}}
</view>
</view>
<view class="header-r">
<van-button round icon="add-o" type="info" size="small" color="linear-gradient(to right, #ef0b24, #ef0b24)" >添加课程</van-button>
</view>
</view>
<!-- 搜索框 -->
<view class="search">
<van-search value="{{ keywords }}" shape="round" background="#fff" placeholder="请输入搜索关键词" bind:search="searchChange" bind:blur="searchChange" />
</view>
<!-- main -->
<view class="main" wx:if="{{isShow}}">
<!-- 列表项 courses:后端返回的数组,item:固定写法,从数组中遍历出来的对象-->
<view class="item" wx:for="{{courses}}" wx:key="*this">
<!-- 封面图片 -->
<view class="item-logo">
<!-- 如果后端返回封面,就使用后端服务器返回的封面 -->
<image wx:if="{{item.cover}}" src="{{item.cover}}" mode="" />
<!-- 如果后端没有返回封面,就是用默认的封面 -->
<image wx:else src="/images/default_cover.png" mode="" />
</view>
<!-- 标题、描述、操作按钮 -->
<view class="content">
<!-- 课程标题 -->
<view class="title">{{item.name}}</view>
<!-- 课程描述 -->
<view class="detail">
{{item.description}}
</view>
<!-- 操作按钮 -->
<view class="btn">
<van-button size="mini" plain round type="info" icon="edit" style="margin-right: 2px;" data-course="{{item}}" >编辑</van-button>
<van-button size="mini" plain round type="danger" icon="delete-o" data-id="{{item.id}}" >删除</van-button>
</view>
</view>
</view>
</view>
<!-- 没有数据的时候显示 -->
<van-empty wx:else description="未查询到课程信息" class="empty" />
</view>
page {
width: 100%;
min-height: 100%;
background-color: #f2f2f2;
}
.container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.header {
width: 100%;
height: 100px;
background: linear-gradient(to right, #07c160, #6fe79d);
display: flex;
flex-direction: row;
align-items: center;
box-sizing: border-box;
padding: 0 20px;
color: #fff;
border-top: 1px solid #fff;
}
.header-l,
.header-r {
width: 50%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.header-r {
display: flex;
align-items: flex-end;
}
.header-l .total {
margin-top: 10px;
font-size: 14px;
}
.main {
width: 100%;
display: flex;
flex-direction: column;
}
.main .item {
width: 100%;
height: 100px;
margin: 5px 0;
background-color: #fff;
display: flex;
box-sizing: border-box;
align-items: center;
padding: 10px;
}
.search {
width: 100%;
}
.main .item .item-logo {
width: 100%;
height: 100%;
margin-right: 10px;
flex: 3;
}
.main .item-logo image {
width: 100%;
height: 100%;
}
.main .item .content {
display: flex;
flex-direction: column;
flex: 7;
justify-content: flex-start;
align-items: flex-start;
}
.main .item .content .title {
color: #333;
font-size: 14px;
}
.main .item .content .detail {
color: #888;
margin: 5px 0;
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
/* 只要超过宽度就换行,不论中文还是英文 */
word-break: break-all;
/* 最多展示两行 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
/* 根据样式设置 */
height: 2rem;
}
.main .item .content .btn {
align-self: flex-end;
justify-content: right;
}
.empty{
text-align: center;
color:#666;
}
{
"navigationBarTitleText": "课程管理",
"usingComponents": {
"van-button": "@vant/weapp/button/index",
"van-card": "@vant/weapp/card/index",
"van-field": "@vant/weapp/field/index",
"van-search": "@vant/weapp/search/index",
"van-empty": "@vant/weapp/empty/index"
}
}
// pages/courseList/courseList.js
const http = require("../../../utils/http")
import Dialog from '@vant/weapp/dialog/dialog';
Page({
/**
* 页面的初始数据
*/
data: {
name: wx.getStorageSync('name') || '保密',
keywords: null,
userId: wx.getStorageSync('userId'), // 老师id
isShow: false, // 是否显示列表
courses: [] // 课程信息
},
/**
* 生命周期函数--监听页面显示
* @param {*} options
*/
onShow() {
this.getCourses()
},
/**
* 监听搜索框
*/
searchChange(e) {
this.setData({
keywords: e.detail.value
})
this.getCourses()
},
/**
* 通过访问服务端获取课程信息
*/
getCourses() {
let that = this
// 获取keywords
let {
keywords,
userId
} = this.data
// 处理keywords
if (!keywords || keywords.length == 0) { // 查所有
keywords = "''"
}
console.log("关键字:", keywords);
// 访问网络
wx.request({
url: `${http.host}/course/findCoursesById/${userId}/${keywords}/0/50`,
method: "GET",
success: (res) => {
if (res && res.data && res.data.data && res.data.data.length > 0) {
// 设置列表显示
this.setData({
isShow: true,
courses: res.data.data
})
} else {
// 设置列表隐藏,空状态显示
this.setData({
isShow: false
})
}
console.log(res);
}
})
},
})
<view class="header-r">
<van-button round icon="add-o" type="info" size="small" color="linear-gradient(to right, #ef0b24, #ef0b24)" bind:click="gotoAddCourse">添加课程</van-button>
</view>
...省略其他代码,细节请参考 【课程管理-老师端】 对应的代码
/**
* 跳转到添加课程界面
*/
gotoAddCourse() {
wx.navigateTo({
url: '/pages/teacher/courseAdd/courseAdd',
})
},
...省略其他代码,细节请参考 【课程管理-老师端】 对应的代码
<!-- 表单 -->
<form bindsubmit="" class="form">
<van-cell-group>
<van-cell title="课程封面" title-width="100px">
<van-uploader bind:delete="deleteImg" max-count="1" file-list="{{ fileList }}" bind:after-read="afterRead" deletable="{{ true }}" slot="right-icon" />
</van-cell>
<van-field bind:change="getName" value="{{ name }}" label="课程名称" placeholder="请输入课程名称" error-message="{{nameErrMsg}}" />
<van-field type="textarea" autosize border="{{ false }}" bind:change="getDesc" value="{{ desc }}" label="课程描述" placeholder="请输入课程描述" error-message="{{descErrMsg}}" />
</van-cell-group>
<view class="login">
<van-button type="primary" class="login-btn" block bind:click="save">保存</van-button>
</view>
</form>
page{
width: 100%;
height: 100%;
background-color: #f2f2f2;
}
.container{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: start;
}
.login{
width: 100%;
box-sizing: border-box;
margin: 60rpx 0rpx 60rpx 0rpx;
padding: 0 20rpx;
}
.van-cell__value{
text-align: left;
}
{
"navigationBarTitleText": "添加课程",
"usingComponents": {
"van-field": "@vant/weapp/field/index",
"van-button": "@vant/weapp/button/index",
"van-cell": "@vant/weapp/cell/index",
"van-cell-group": "@vant/weapp/cell-group/index",
"van-uploader": "@vant/weapp/uploader/index"
}
}
// pages/teacher/courseAdd/courseAdd.js
const http = require("../../../utils/http")
Page({
/**
* 页面的初始数据
*/
data: {
fileList: [], // 存放选中的图片
name: '', // 用来存表单中的课程名称
desc: '', // 用来存表单中的课程描述
nameErrMsg: '', // 课程名称错误提示
descErrMsg: '', // 课程描述错误提示
userId: wx.getStorageSync('userId') // 登录的老师的ID
},
/**
* 接收用户输入的课程名称
* @param {*} e
*/
getName(e) {
this.setData({
name: e.detail
})
},
/**
* 接收用户输入的课程描述
* @param {*} e
*/
getDesc(e) {
this.setData({
desc: e.detail
})
},
/**
* 读取本地图片
* @param {*} event
*/
afterRead(event) {
let that = this
const {
file
} = event.detail;
// 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
wx.uploadFile({
url: `${http.fileHost}/upload`, // 后端接口地址
filePath: file.url,
name: 'file',
// formData: {
// user: 'test'
// },
success(res) {
console.log("文件上传:", res);
// 上传完成需要更新 fileList
if (res && res.statusCode == 200 && res.data) {
console.log("-------------");
if (res.data) {
const fileData = JSON.parse(res.data)
const {
fileList = [],
} = that.data;
fileList.push({
...file,
url: fileData.url,
isImage: true,
deletable: true,
// status: 'uploading',
// message: '上传中',
});
that.setData({
fileList
});
console.log("====", that.data, res.data);
}
}
},
});
},
/**
* 保存的方法
*/
save() {
let that = this
// 解构name,desc,fileList,userId
const {
name,
desc,
fileList,
userId,
} = this.data
// 非空校验
if (!name || name.length == 0) {
this.setData({
nameErrMsg: "课程名称不能为空"
})
return
} else {
// 重置提示框
this.setData({
nameErrMsg: "",
})
}
if (!desc || desc.length == 0) {
this.setData({
descErrMsg: "课程描述不能为空"
})
return // return之后,后续代码不再问题
} else {
// 重置提示框
this.setData({
descErrMsg: ""
})
}
// 请求后端服务器
wx.request({
url: `${http.host}/course/addCourse`,
method: "post",
data: {
name,
description: desc,
cover: fileList[0].url, //
teacher_id: userId
},
success: (res) => {
if (res && res.data && res.data.code == 1) { // 保存成功
wx.showToast({
title: res.data.msg, //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'success', //图标,支持"success"、"loading"
success: function () {
// 保存成功,跳转课程列表
wx.redirectTo({
url: '/pages/teacher/courseList/courseList',
})
}
})
} else { // 保存失败
wx.showToast({
title: res.data.msg, //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'error', //图标,支持"success"、"loading"
})
}
},
fail: (err) => {
wx.showToast({
title: "服务器请求异常", //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'error', //图标,支持"success"、"loading"
})
}
})
console.log("请求后端服务器!");
},
/**
* 删除图片
* @param {*} options
*/
deleteImg(e){
const {fileList} = this.data
fileList.splice(e.detail.index,1)
this.setData({
fileList
})
},
})
{
"navigationBarTitleText": "课程管理",
"usingComponents": {
...省略其他代码,细节请参考 【课程管理-老师端】 对应的代码
"van-dialog": "@vant/weapp/dialog/index"
}
}
<view class="container">
<!-- header -->
<!-- 操作按钮 -->
<view class="btn">
<van-button size="mini" plain round type="danger" icon="delete-o" data-id="{{item.id}}" bind:click="delete">删除</van-button>
</view>
...省略其他代码,细节请参考 【课程管理-老师端】 对应的代码
<!-- 删除确认框,放到其他代码的最后面 -->
<van-dialog id="van-dialog" />
</view>
// pages/courseList/courseList.js
const http = require("../../../utils/http")
// 导入删除信息确认框
import Dialog from '@vant/weapp/dialog/dialog';
Page({
...省略其他代码,细节请参考 【课程管理-老师端】 对应的代码
/**
* 删除课程
*/
delete(e) {
let that = this
// 接收删除按钮 data-* 传递的数据 e.target.dataset.*
const id = e?.target?.dataset?.id
if (id) {
Dialog.confirm({
selector: "#van-dialog",
title: '温馨提示',
message: '是否确定删除当前课程?',
})
.then(() => {
// 请求服务器,删除课程
wx.request({
url: `${http.host}/course/deleteCourse/${id}`,
method: "delete",
success: (res) => {
if (res && res.data && res.data.code == 1) { // 删除成功
wx.showToast({
title: res.data.msg, //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'success', //图标,支持"success"、"loading"
success: function () {
// 保存成功,跳转课程列表
that.getCourses();
}
})
} else { // 删除失败
wx.showToast({
title: res.data.msg, //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'error', //图标,支持"success"、"loading"
})
}
},
fail: (err) => {
console.error("删除课程:", err);
wx.showToast({
title: "服务器请求异常", //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'error', //图标,支持"success"、"loading"
})
}
})
})
.catch(() => {
// on cancel
});
}
console.log("删除课程:", id);
}
})
<!-- 操作按钮 -->
<view class="btn">
<van-button size="mini" plain round type="info" icon="edit" style="margin-right: 2px;" data-course="{{item}}" bind:click="gotoCourseUpdate">编辑</van-button>
</view>
...省略其他代码,细节请参考 【课程管理-老师端】 对应的代码
/**
* 跳转到修改课程界面
* @param {*} e
*/
gotoCourseUpdate(e) {
let that = this
// 获取编辑按钮携带的参数
const course = e?.target?.dataset?.course
if (course) {
// 跳转到课程修改界面
wx.navigateTo({
url: '/pages/teacher/courseUpdate/courseUpdate',
success: function (res) {
// 通过eventChannel向被打开页面传送数据
res.eventChannel.emit('courseUpdate', {
data: course
})
}
})
}
},
...省略其他代码,细节请参考 【课程管理-老师端】 对应的代码
<!-- 表单 -->
<form bindsubmit="" class="form">
<van-cell-group>
<van-cell title="课程封面" title-width="100px">
<van-uploader bind:delete="deleteImg" max-count="1" file-list="{{ fileList }}" bind:after-read="afterRead" deletable="{{ true }}" slot="right-icon" />
</van-cell>
<van-field bind:change="getName" value="{{ name }}" label="课程名称" placeholder="请输入课程名称" error-message="{{nameErrMsg}}" />
<van-field type="textarea" autosize border="{{ false }}" bind:change="getDesc" value="{{ desc }}" label="课程描述" placeholder="请输入课程描述" error-message="{{descErrMsg}}" />
</van-cell-group>
<view class="login">
<van-button type="primary" class="login-btn" block bind:click="save">保存</van-button>
</view>
</form>
/* pages/teacher/courseUpdate/courseUpdate.wxss */
page{
width: 100%;
height: 100%;
background-color: #f2f2f2;
}
.container{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: start;
}
.login{
width: 100%;
box-sizing: border-box;
margin: 60rpx 0rpx 60rpx 0rpx;
padding: 0 20rpx;
}
.van-cell__value{
text-align: left;
}
{
"navigationBarTitleText": "修改课程",
"usingComponents": {
"van-field": "@vant/weapp/field/index",
"van-button": "@vant/weapp/button/index",
"van-cell": "@vant/weapp/cell/index",
"van-cell-group": "@vant/weapp/cell-group/index",
"van-uploader": "@vant/weapp/uploader/index"
}
}
// pages/teacher/courseAdd/courseAdd.js
const http = require("../../../utils/http")
Page({
/**
* 页面的初始数据
*/
data: {
fileList: [], // 存放选中的图片
id:0,// 用来存表单中的课程ID
name: '', // 用来存表单中的课程名称
desc: '', // 用来存表单中的课程描述
cover: '', // 封面图片
nameErrMsg: '', // 课程名称错误提示
descErrMsg: '', // 课程描述错误提示
},
/**
* 接收用户输入的课程名称
* @param {*} e
*/
getName(e) {
this.setData({
name: e.detail
})
},
/**
* 接收用户输入的课程描述
* @param {*} e
*/
getDesc(e) {
this.setData({
desc: e.detail
})
},
/**
* 读取本地图片
* @param {*} event
*/
afterRead(event) {
let that = this
const {
file
} = event.detail;
// 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
wx.uploadFile({
url: `${http.fileHost}/upload`, // 后端接口地址
filePath: file.url,
name: 'file',
// formData: {
// user: 'test'
// },
success(res) {
console.log("文件上传:", res);
// 上传完成需要更新 fileList
if (res && res.statusCode == 200 && res.data) {
if (res.data) {
const fileData = JSON.parse(res.data)
const {
fileList = [],
} = that.data;
fileList.push({
...file,
url: fileData.url,
isImage: true,
deletable: true,
});
that.setData({
fileList
});
}
}
},
});
},
/**
* 保存的方法
*/
save() {
let that = this
// 解构name,desc,fileList,id
const {
name,
desc,
fileList,
id,
} = this.data
// 非空校验
if (!name || name.length == 0) {
this.setData({
nameErrMsg: "课程名称不能为空"
})
return
} else {
// 重置提示框
this.setData({
nameErrMsg: "",
})
}
if (!desc || desc.length == 0) {
this.setData({
descErrMsg: "课程描述不能为空"
})
return // return之后,后续代码不再问题
} else {
// 重置提示框
this.setData({
descErrMsg: ""
})
}
// 请求后端服务器
wx.request({
url: `${http.host}/course/updateCourse`,
method: "put",
data: {
name,
description: desc,
cover: fileList[0].url, // 封面图片的地址
id: id
},
success: (res) => {
if (res && res.data && res.data.code == 1) { // 保存成功
wx.showToast({
title: res.data.msg, //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'success', //图标,支持"success"、"loading"
success: function () {
// 保存成功,跳转课程列表
wx.redirectTo({
url: '/pages/teacher/courseList/courseList',
})
}
})
} else { // 保存失败
wx.showToast({
title: res.data.msg, //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'error', //图标,支持"success"、"loading"
})
}
},
fail: (err) => {
wx.showToast({
title: "服务器请求异常", //提示文字
duration: 2000, //显示时长
mask: true, //是否显示透明蒙层,防止触摸穿透,默认:false
icon: 'error', //图标,支持"success"、"loading"
})
}
})
console.log("请求后端服务器!");
},
/**
* 删除图片
* @param {*} options
*/
deleteImg(e){
console.log("===",e.detail.index);
const {fileList} = this.data
fileList.splice(e.detail.index,1)
this.setData({
fileList
})
},
/**
* 页面加载后执行
* @param {*} options
*/
onLoad(options) {
// 接收上一个页面传递的数据
const that = this
// 监听acceptDataFromOpenerPage事件,获取上一页面通过eventChannel传送到当前页面的数据
const eventChannel = this.getOpenerEventChannel()
eventChannel.on('courseUpdate', function (data) {
console.log(data) //输出{data: 'test'}
let fileList;
// 如果服务器有图片,就显示原来的图片
if (data.data.cover) {
fileList = [{
url:data.data.cover, // 封面图片的路径
deletable:true,
isImage:true
}]
} else {// 如果服务器没有图片,就置空
fileList = []
}
that.setData({
id:data.data.id,
name: data.data.name,
desc: data.data.description,
fileList: fileList
})
})
}
})
基于RestFull风格的请求
序号 | 请求方式 | 应用场合 | 举例 |
---|---|---|---|
1 | get | 查询 | http://localhost:3000/user/userList |
2 | post | 增加、登录 | http://localhost:3000/user/addUser |
3 | put | 修改 | http://localhost:3000/user/updateUser |
4 | delete | 删除 | http://localhost:3000/user/deleteUser/1 |
我们前后端分离开发,会存在一个问题,就是前后端使用的编程语言可能不一样,那么前后端如何交互呢?目前主流的交互是,将数据以字符串的方式进行交互,因为所有的编程语言都支持字符串。
但是普通的字符串无法传输过于复杂的数据,因此JSON就诞生了,JSON就是一种拥有标准格式的字符串。
Array格式
[]
Object格式
{
"key":value
...
}
解释
key:JSON中,key只能是字符串类型
value:JSON中,value支持string,number,boolean,object,array,null
[
{
"id":1,
"username":"zs",
"name":"张三",
"role":"student",
"hobbies":["唱歌","跳舞","如阿普"],
"friends":{
...
}
}
]
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。