vue3+node.js+mysql写接口(一)
目录
一、项目准备
1.1、安装创建vue3项目
1.2、前端配置
1.3、创建Express后端项目
二、用户模块(user表)
2.1、登录(/adminapi/user/login)
2.2、个人中心(/adminapi/user/upload)
2.3、添加用户(/adminapi/user/add)
2.4、用户列表(/adminapi/user/list)
2.5、删除用户(/adminapi/user/delete/:id)
2.6、编辑用户(/adminapi/user/update)
2.7、用户权限控制
三、新闻模块(news)
3.1、创建新闻(/adminapi/news/add)
3.2、新闻列表(/adminapi/news/list)
3.2.1、分页实现
3.2.2、时间渲染
3.3、发布新闻(/adminapi/news/publish)
3.4、预览新闻(/adminapi/news/detail/:id)
3.5、删除新闻(/adminapi/news/delete/:id)
3.7、编辑新闻(/adminapi/news/detail/:id)
一、项目准备
前言:
这是一个用vue3+node.js+mysql实现的三端项目:分为后台管理系统、web端展示、后端操作数据库,包含用户管理、新闻管理、产品管理三大模块,其中,web端就是直接展示后台管理系统中用户添加的数据。
1.1、安装创建vue3项目
(1)、npm install -g @vue/cli-service-global (看自己有没有安装)
(2)、vue create admin (admin是项目名,随自己)
(3)、Manually select features后用空格键盘选中需要的features:Babel、Router、Vuex、CSS Pre-processors,选择3.x的version
(4)、创好项目,建立路由后:npm install element-plus --save
1.2、前端配置
登录页面的运动粒子效果:npm i particles.vue3 【使用教程 :particles.vue3 - npm】
引入组件报错,使用npm install tsparticles-slim进行解决
<!-- 粒子背景 --><vue-particlesid="tsparticles":particlesInit="particlesInit":particlesLoaded="particlesLoaded":options="particlesOptions"/>
<script setup>
import { loadSlim } from "tsparticles-slim";
// 粒子配置
const particlesOptions = {background: {color: {value: "#9c64a7",},},fpsLimit: 120,interactivity: {events: {onClick: {enable: true,mode: "push",},onHover: {enable: true,mode: "repulse",},resize: true,},modes: {bubble: {distance: 400,duration: 2,opacity: 0.8,size: 40,},push: {quantity: 4,},repulse: {distance: 200,duration: 0.4,},},},particles: {color: {value: "#ffffff",},links: {color: "#ffffff",distance: 150,enable: true,opacity: 0.5,width: 1,},collisions: {enable: true,},move: {direction: "none",enable: true,outModes: {default: "bounce",},random: false,speed: 6,straight: false,},number: {density: {enable: true,area: 800,},value: 80,},opacity: {value: 0.5,},shape: {type: "circle",},size: {value: { min: 1, max: 5 },},},detectRetina: true,
};
const particlesInit = async (engine) => {await loadSlim(engine);
};
const particlesLoaded = async (container) => {console.log("Particles container loaded", container);
};
</script>
<style scoped>
#apps {position: relative;width: 100vw;height: 100vh;overflow: hidden;
}
#tsparticles {position: absolute;top: 0;left: 0;width: 100%;height: 100%;z-index: 1;
}
</style>
vuex持久化:npm install vuex-persistedstate --save
在store的index.js文件里
import createPersistedState from "vuex-persistedstate";// 与 state、mutations 同级plugins: [createPersistedState({paths: ["isCollapsed", "userInfo"], //只让它持久化}),],
axios下载:npm i axios
在api的axios.js文件里
import Axios from "axios";
const axios = Axios.create({// baseURL: import.meta.env.VITE_API_BASE,timeout: 300000,
});axios.interceptors.request.use((config) => {const token = localStorage.getItem("token");if (token) {config.headers.Authorization = `Bearer ${token}`;}return config;},(error) => {Promise.reject(error);}
);
axios.interceptors.response.use((response) => {// 1. 判断响应码const data = response.data;const { authorization } = response.headers;authorization && localStorage.setItem("token", authorization);return data;},(error) => {const { status } = error.response;if (status == 401) {localStorage.removeItem("token");window.location.href = "#/login";}return Promise.reject(error);}
);
export default axios;
1.3、创建Express后端项目
npm install mysql2 【连接数据库】
在db.js里
const mysql = require("mysql2");
// 创建数据库连接配置
const connection = mysql.createConnection({host: "localhost", // 数据库主机地址user: "root", // 数据库用户名password: "123456", // 数据库密码database: "my-school", // 数据库名称port: 3307, // 数据库端口号,默认为3306
});
// 连接数据库
connection.connect((err) => {if (err) {console.log("MySQL连接失败:", err);} else {console.log("MySQL数据库连接成功");}
});
module.exports = connection;
npm install jsonwebtoken【登录成功后返回的token,并校验token的存在与更新】
在项目util文件夹的JWT.js
const jsonwebtoken = require("jsonwebtoken");
const secret = "my_super_secret_key_2024!@#";
const JWT = {generate(value, expires) {return jsonwebtoken.sign(value, secret, { expiresIn: expires });},verify(token) {try {return jsonwebtoken.verify(token, secret);} catch (err) {return false;}},
};
module.exports = JWT;
配置参考:node.js连接mysql写接口(一)_node.js 连接数据库,写一个查询接口-CSDN博客
每一个接口的完整流程:
先在routes里写接口,在controlles里处理接口逻辑,再在services里处理sql,改变或者获取数据库表里的数据。
二、用户模块(user表)
连接数据库后,先建一个user表:
-- 创建users表
CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) NOT NULL UNIQUE,password VARCHAR(255) NOT NULL,gender INT DEFAULT 0,introduction TEXT,avatar VARCHAR(255),role INT DEFAULT 2, -- 管理员 1,编辑 2created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);-- 插入测试用户数据
INSERT INTO users (username, password, gender, introduction, avatar, role) VALUES
('admin', '123456', 1, '系统管理员', '/avatar/admin.jpg', 1),
('editor', '123456', 0, '内容编辑', '/avatar/editor.jpg', 2),
('test', 'test', 1, '测试用户', '/avatar/test.jpg', 2);
2.1、登录(/adminapi/user/login)
前端传递用户名和密码,后端接收并判断是否在数据库里存在,相当于数据库表user的指定信息查询,验证成功后返回用户信息,并且通过JWT,每24h更新一次token,将返回的token和userInfo信息存在vuex里,退出登陆时进行清空。
后端代码解释:
下方是controlles文件夹里的admin中的login方法
login: async (req, res) => {try {console.log("收到登录请求:", req.body); //{ username: 'admin', password: '123456' }const { username, password } = req.body;// 参数验证if (!username || !password) {return res.status(400).json({code: -1,message: "用户名和密码不能为空",});}// 查询用户const result = await UserService.login({ username, password });console.log("数据库查询结果:", result); //result数组里是admin的所有信息if (result.length === 0) {return res.json({code: 500,message: "用户名或密码错误",});}const user = result[0];// 生成tokenconst token = JWT.generate({id: user.id,username: user.username,role: user.role,},"24h");res.header("Authorization", token);// 返回成功响应res.json({code: 200,message: "登录成功",data: {token,userInfo: {id: user.id,username: user.username,role: user.role,gender: user.gender || 0,introduction: user.introduction,avatar: user.avatar,},},});} catch (error) {console.log("登录接口错误:", error);res.status(500).json({code: -1,message: "服务器内部错误",});}},
前端处理逻辑:
const submitForm = (formEl) => {if (!formEl) return;formEl.validate((valid) => {if (valid) {axios.post("/adminapi/user/login", loginForm).then((res) => {if (res.code == 200) {// localStorage.setItem("token", res.data.token);// localStorage.setItem(// "USER_INFO",// JSON.stringify(res.data.userInfo)// );store.commit("changeUserInfo", res.data.userInfo);router.push("/index");} else {ElMessage.error(res.message);}}).catch((err) => {console.log(err);});} else {console.log("error submit!");}});
};
登录成功渲染首页:首页头像和时间判断都是通过computed进行
2.2、个人中心(/adminapi/user/upload)
拿的是vuex持久化插件保存的信息,这里牵扯到个人信息上传,相当于数据库表user的信息修改【UPDATE users SET】,前端传递FormData格式数据到后端,此时后端需要下载multer【github.com里搜索下载】来处理multipart/form-data类型的表单数据,并判断前端是否传递了file二进制文件,进行非空校验,并返回修改后的信息。
前端逻辑:
const uploadChange = (file) => {userForm.avatar = URL.createObjectURL(file.raw);userForm.file = file.raw; //file文件【二进制】格式,传给后端时用FormData形式
};
const submitForm = async (formEl) => {if (!formEl) return;await formEl.validate(async (valid, fields) => {if (valid) {const res = await upload("adminapi/user/upload", userForm);if (res.code === 200) {store.commit("changeUserInfo", res.data);ElMessage.success(res.message);}} else {console.log("error submit!", fields);}});
};
公共上传方法:upload(在前端的utils文件夹里,引入即可)
import axios from '../api/axios'
function upload(path, userForm) {const params = new FormData();for (let key in userForm) {params.append(key, userForm[key]);}return axios.post(path, params, {headers: { "Content-Type": "multipart/form-data" },}).then((res) => res);
}
export default upload;
后端逻辑:
// 图片上传
const multer = require("multer");
//在项目中的public建立一个avataruploads文件夹
const upload = multer({ dest: "public/avataruploads/" });
UserRouter.post("/adminapi/user/upload",upload.single("file"),UserController.upload
);
下方是controlles文件夹里的admin中的upload方法
upload: async (req, res) => {const { username, introduction, gender } = req.body;const token = req.headers["authorization"]?.split(" ")[1];// 如果前端未传递图像的file数据const avatar = req.file ? `/avataruploads/${req.file.filename}` : "";let payload;if (token) {payload = JWT.verify(token);const updateData = {id: payload.id,username,introduction,gender: Number(gender),avatar,};try {await UserService.upload(updateData);res.send({code: 200,data: {username,introduction,gender: Number(gender),avatar: req.file ? avatar : undefined,},message: "修改成功",});} catch (error) {res.status(500).send({code: 500,message: "用户信息更新失败",});}} else {res.status(401).send({code: 401,message: "未授权",});}console.log(req.body, "req.file:", req.file, payload && payload.id);},
// services里核心sql
const sql ="UPDATE users SET username = ?, introduction = ?, gender = ?, avatar = ? WHERE id = ?";
让前端更新vuex里的信息,从而达到全局信息替换的目的,此时前端将上传图片组件进行封装:提取upload组件并将上传方法进行封装,便于二次使用。
<template><el-uploadclass="avatar-uploader"action="":show-file-list="false":auto-upload="false":on-change="handleChange"><img v-if="avatar" :src="backUrl" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload>
</template>
<script setup>
import { Plus } from "@element-plus/icons-vue";
import { defineEmits, defineProps, computed } from "vue";
const props = defineProps({avatar: {type: String,default: "",},
});
const backUrl = computed(() =>props.avatar.includes("blob")? props.avatar: "http://localhost:3000" + props.avatar
);
const emit = defineEmits(["uploadChange"]);
const handleChange = (file) => {emit("uploadChange", file);
};
</script>
<style lang="scss" scoped>
.avatar-uploader .avatar {width: 178px;height: 178px;display: block;
}
::v-deep .el-upload {border: 1px dashed var(--el-border-color);border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;transition: var(--el-transition-duration-fast);
}::v-deep .el-upload:hover {border-color: var(--el-color-primary);
}::v-deep .el-icon.avatar-uploader-icon {font-size: 28px;color: #8c939d;width: 178px;height: 178px;text-align: center;
}
</style>
2.3、添加用户(/adminapi/user/add)
前端数据基本同个人中心,只是没有id,这里就是将前端的数据插入【INSERT INTO】数据库表users,总列表数也随之增加一个。
后端sql核心代码
addList: async ({username,introduction,gender,role,password,avatar,}) => {return new Promise((resolve, reject) => {const sql ="INSERT INTO users (username,introduction,gender,role,password,avatar) VALUES (?,?,?,?,?,?)";connection.query(sql,[username, introduction, gender, role, password, avatar],(err, results) => {if (err) {console.log("添加用户信息错误:", err);reject(err);} else {resolve(results);}});});},
2.4、用户列表(/adminapi/user/list)
相当于数据库表users的查询【SELECT * FROM users】,并将总列表数返回给前端,前端直接用表格进行渲染。
getList: async () => {return new Promise((resolve, reject) => {const sql = "SELECT * FROM users";connection.query(sql, (err, results) => {if (err) {console.log("查询错误:", err);reject(err);} else {console.log("查询结果:", results);resolve(results);}});});},
2.5、删除用户(/adminapi/user/delete/:id)
根据id删除【DELETE FROM users】数据库表users中的相关信息,总列表数也随之减少一个。
delList: async (id) => {return new Promise((resolve, reject) => {const sql = "DELETE FROM users WHERE id = ?";connection.query(sql, [id], (err, results) => {if (err) {console.log("删除用户错误:", err);reject(err);} else {resolve(results);}});});},
2.6、编辑用户(/adminapi/user/update)
参考个人中心,这里在编辑弹窗回显时存在ref和reactive赋值会改变原始数据问题。
const userForm = reactive({username: "",password: "",role: 2, //1 管理员 2 编辑introduction: "",
});
const editRow = (row) => {// 如果userForm是ref对象,直接赋值,会改变原始数据tableData,需要深拷贝// 1、简单对象:userForm.value = JSON.parse(JSON.stringify(row));// 2、复杂对象:使用lodash的_.cloneDeep创建深拷贝【npm install lodash后引入import _ from 'lodash';】// userForm.value = _.cloneDeep(row);// 如果userForm是reactive对象,直接赋值,会改变原始数据tableData// 1、使用toRaw将reactive对象转换为普通对象,进行深拷贝后再将其转换回reactive对象// const rawRow = toRaw(row);// const copiedRow = _.cloneDeep(rawRow);// userForm = reactive(copiedRow);// 2、直接Object.assign(userForm,row)Object.assign(userForm, row);dialogFormVisible.value = true;
};
后端核心代码
updateList: async ({ id, username, introduction, role, password }) => {return new Promise((resolve, reject) => {const sql ="UPDATE users SET username = ?, introduction = ?, role = ?, password = ? WHERE id = ?";connection.query(sql,[id, username, introduction, role, password],(err, results) => {if (err) {console.log("修改用户信息错误:", err);reject(err);} else {resolve(results);}});});},
2.7、用户权限控制
界面菜单用v-admin指令控制,但是还要处理路由:将需要控制的路由加上requireAdmin属性,在router的index.js加上判断,注意在登陆时将 store.commit("changeGetterRouter", false);这样就防止编辑人员通过url直接访问到管理人员菜单和页面。
// 界面删除 ,还需要加上路由删除
const vAdmin = {mounted(el) {// 判断当前用户是否为管理员,不是的话就从父节点中移除当前元素(el)if (store.state.userInfo.role !== 1) {el.parentNode.removeChild(el);}},
};
import { createRouter, createWebHashHistory } from "vue-router";
import Login from "../views/Login.vue";
import MainBox from "../views/MainBox.vue";
import RoutesConfig from "./config";
import store from "../store/index";
const routes = [{path: "/login",name: "login",component: Login,},{path: "/mainbox",name: "mainbox",component: MainBox,},// mainbox的嵌套路由,后面根据权限动态添加
];
const router = createRouter({history: createWebHashHistory(),routes,
});// 配置动态路由的函数
const ConfigRouter = () => {if (!router.hasRoute("mainbox")) {router.addRoute({path: "/mainbox",name: "mainbox",component: MainBox,});}RoutesConfig.forEach((item) => {checkPermission(item) && router.addRoute("mainbox", item);});// 改变isGetterRouter=truestore.commit("changeGetterRouter", true);
};
const checkPermission = (item) => {if (item.requireAdmin) {return store.state.userInfo.role === 1;} else {return true;}
};
// 每次路由跳转之前
router.beforeEach((to, from, next) => {if (to.name === "login") {next();} else {// 未授权,重定向到loginif (!localStorage.getItem("token")) {next({ name: "login" });} else {if (!store.state.isGetterRouter) {// 删除所有的嵌套路由router.removeRoute("mainbox");ConfigRouter();// 重新导航到目标路由,确保新添加的路由生效next({ ...to, replace: true });} else {next();}}}
});
export default router;
三、新闻模块(news)
建一个名为news的表:
-- 创建news表
CREATE TABLE IF NOT EXISTS news (id INT AUTO_INCREMENT PRIMARY KEY,title VARCHAR(50) NOT NULL UNIQUE,content LONGTEXT NOT NULL,-- 超大文本category INT DEFAULT 0,cover VARCHAR(255),isPublish INT DEFAULT 0,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,edit_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FULLTEXT INDEX idx_content (content) -- 添加全文索引(如果支持中文搜索)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;-- 解释
-- 1、使用InnoDB引擎:为了事务支持和数据完整性。
-- 2、使用utf8mb4字符集:为了全面支持Unicode字符(包括中文生僻字、emoji、多语言等)。
-- 3、使用utf8mb4_unicode_ci排序规则:为了更准确的排序和比较-- 插入测试新闻数据
INSERT INTO news (title, content, category, cover, isPublish) VALUES
('标题1', '富文本内容...', 1, '/avatar/admin.jpg', 0),
('editor', '123456...', 0, '/avatar/editor.jpg', 0),
3.1、创建新闻(/adminapi/news/add)
富文本编辑器:Introduction · wangEditor 用户文档
下载v4版本:npm i wangeditor --save
前端编辑器组件如下,其中的封面上传参考个人中心,注意在提交时将当前用户加入,为了在渲染列表时只看到当前用户创建的,后端接收数据后,添加进news表即可
编辑器组件(前端):
<template><div id="myeditor"></div>
</template>
<script setup>
import { onMounted, defineEmits, defineProps } from "vue";
import E from "wangeditor";
const emits = defineEmits(["change"]);
const props = defineProps({content: String,
});
onMounted(() => {const editor = new E("#myeditor");editor.create();// 设置初始值props.content && editor.txt.html(props.content);editor.config.onchange = function (newHtml) {emits("change", newHtml);};// 配置触发 onchange 的时间频率,默认为 200mseditor.config.onchangeTimeout = 500; // 修改为 500ms
});
</script>
<style lang="scss" scoped>
#myeditor {width: 100%;
}
</style>
首页引用(前端):
<el-form-item label="内容" prop="content">
<Editor @change="handleChange" :content="newsForm.content" v-if="newsForm.content"/>
</el-form-item>
// 富文本编辑器内容改变的回调
const handleChange = (data) => {newsForm.content = data;
};
后端直接分享NewsService.addList:
addList: async ({title,content,category,cover,isPublish,created_at,userId,}) => {return new Promise((resolve, reject) => {const sql ="INSERT INTO news (title, content, category, cover, isPublish, created_at, userId) VALUES (?, ?, ?, ?, ?, ?, ?)";connection.query(sql,[title, content, category, cover, isPublish, created_at, userId],(err, results) => {if (err) {console.log("添加新闻信息错误:", err);reject(err);} else {resolve(results);}});});},
3.2、新闻列表(/adminapi/news/list)
前端传递当前用户id、类别id、分页参数,后端按照关键词返回。这里后端的优化就是在返回列表时,富文本内容过大,先不返回,等编辑或者预览,通过详情接口,查询全部数据。
3.2.1、分页实现
前端借助分页器
const currentPage = ref(1);
const pageSize = ref(5);
const total = ref(0);
const handleSizeChange = (val) => {pageSize.value = val;currentPage.value = 1; // 切换每页条数时重置到第一页getData();
};
const handleCurrentChange = (val) => {currentPage.value = val;getData();
};
const getData = async () => {const params = {currentPage: currentPage.value,pageSize: pageSize.value,category: searchForm.category,userId:store.state.userInfo.role === 2 ? store.state.userInfo.id : undefined, //管理员查看所有,自己查看自己};const res = await axios.get("/adminapi/news/list", { params });if (res.code == 200) {tableData.value = res.data;total.value = res.total;} else {ElMessage.error(res.message);}
};
后端核心代码:
getList: async (page = 1, pageSize = 10, category = "", userId = null) => {return new Promise((resolve, reject) => {let sql = "SELECT * FROM news";let params = [];let whereArr = [];if (category) {whereArr.push("category = ?");params.push(category);}if (userId) {whereArr.push("userId = ?");params.push(userId);}if (whereArr.length > 0) {sql += " WHERE " + whereArr.join(" AND ");// SELECT * FROM news WHERE category = ? AND userId = ?}// 添加排序和分页sql += " ORDER BY created_at DESC LIMIT ? OFFSET ?";const offset = (page - 1) * pageSize;params.push(pageSize, offset);connection.query(sql, params, (err, results) => {if (err) {console.log("查询错误:", err);reject(err);} else {console.log("查询结果:", results);resolve(results);}});});},getTotal: async (category = "", userId = null) => {return new Promise((resolve, reject) => {let sql = "SELECT COUNT(*) as total FROM news";let params = [];let whereArr = [];if (category) {whereArr.push("category = ?");params.push(category);}if (userId) {whereArr.push("userId = ?");params.push(userId);}if (whereArr.length > 0) {sql += " WHERE " + whereArr.join(" AND ");}connection.query(sql, params, (err, results) => {if (err) {console.log("查询总数错误:", err);reject(err);} else {resolve(results[0].total);}});});},
3.2.2、时间渲染
渲染更新时间:Moment.js 中文网
npm install moment --save
<el-table-column label="更新时间" width="180" align="center"><template #default="{ row }"><span>{{ formatTime.getTime(row.edit_time) }}</span></template></el-table-column>
utils文件夹里的formatTime.js代码
import moment from 'moment'
moment.locale('zh-cn')
const formatTime={getTime(time) {return moment(time).format('YYYY-MM-DD HH:mm:ss')},
}
export default formatTime
3.3、发布新闻(/adminapi/news/publish)
点击前端表格该列的“是否发布”按钮,传递id和自身状态,后端接收后,加上edit_time: new Date(),记录更新时间,改变sql该条数据后,返回改变后的数据列表。
<el-table-column label="是否发布" width="180" align="center"><template #default="{ row }"><!-- 用返回的数字来控制 switch 的值 --><el-switch:active-value="1":inactive-value="0"v-model="row.isPublish"@change="row.isPublish ? changeSwitch(row) : ''"></el-switch></template></el-table-column>
const changeSwitch = async (row) => {const res = await axios.post("/adminapi/news/publish", {id: row.id,isPublish: row.isPublish,});if (res.code === 200) {ElMessage.success(res.message);getData();} else {ElMessage.error(res.message);}
};
后端核心代码:
publishList: async ({ id, isPublish, edit_time }) => {return new Promise((resolve, reject) => {// sql语句里的参数顺序最好和传递给数据库的参数顺序一致let sql = "UPDATE news SET isPublish = ?,edit_time = ? WHERE id = ?";connection.query(sql, [isPublish, edit_time, id], (err, results) => {if (err) {console.log("发布新闻信息错误:", err);reject(err);} else {resolve(results);}});});},
3.4、预览新闻(/adminapi/news/detail/:id)
点击预览,调用详情接口,用v-html回显富文本内容。
::v-deep .news-content {img {max-width: 100%;}
}
<el-dialog v-model="previewFormVisible" title="预览新闻" width="70%"><div><h2>{{ previewInfo.title }}</h2><p>更新时间:{{ formatTime.getTime(previewInfo.edit_time) }}</p><el-divider><el-icon><star-filled /></el-icon></el-divider><!-- 解析富文本代码 :v-html="previewInfo.content"--><div class="news-content" v-html="previewInfo.content"></div></div></el-dialog>
后端核心代码
getById: async (id) => {return new Promise((resolve, reject) => {const sql = "SELECT * FROM news WHERE id = ?";connection.query(sql, [id], (err, results) => {if (err) {console.log("根据ID查询新闻错误:", err);reject(err);} else {resolve(results[0] || null);}});});},
3.5、删除新闻(/adminapi/news/delete/:id)
逻辑参考用户删除
3.7、编辑新闻(/adminapi/news/detail/:id)
编辑新闻放在一个新的页面(新建路由:/news-manage/newsedit/:id),点击编辑按钮,跳转到详情页面,初始化时进行页面回显,提交后返回列表页
后端核心代码:
updateList: async ({id,title,content,category,cover,isPublish,edit_time,}) => {return new Promise((resolve, reject) => {let sql ="UPDATE news SET title = ?, content = ?, category = ?, isPublish = ?, edit_time = ?";let params = [title, content, category, isPublish, edit_time];// 如果提供了cover字段,则更新coverif (cover !== undefined) {sql ="UPDATE news SET title = ?, content = ?, category = ?, cover = ?, isPublish = ?, edit_time = ?";params = [title, content, category, cover, isPublish, edit_time];}sql += " WHERE id = ?";params.push(id);connection.query(sql, params, (err, results) => {if (err) {console.log("修改新闻信息错误:", err);reject(err);} else {resolve(results);}});});},
最后,这个项目预计分为两页讲解,后续会上传前后端所有代码,在gitCode可以自行下载,快速查看后端逻辑,可通过子目录的接口进行搜索。