前言
本章节为选修课,所谓选修课就是:大家自行选择学习本章节课程,也就是说你跳过本章节的课程,直接学习下一个章节,不会影响你学习本套课程。
那为什么本章节要出这套选修课呢?
① 基于部分同学的请求,有同学私信老师,希望老师讲一下后端api的相关知识;
② 基于我们课程的教学理念,让从零开始学习的同学,能学习一个完整项目的前后端开发;
哪些同学可以学习本章节,哪些同学可以直接跳过本章节直接学下个章节呢?
可以学习本章节的同学:
① 学习过我们 第二学期第三季:网站前后端完整开发的同学,可以直接学习本章节;
② 对后端开发感兴趣的同学,想知道服务器如何将数据处理之后传递给我们前端H5、小程序、APP的同学 (但是,如果你没有学习我们前面的第二学期第三季:网站前后端完整开发的同学,需要先学完前面的课程再来看本章节,本章节相当于是第二学期第三季:网站前后端完整开发课程的续集、增值服务课)直接跳过本章节直接学下个章节的同学:
① 对全栈、后端不感兴趣,只想学前端的同学;
说明:我们会在后面的章节,提供一套线上的api接口,供同学们测试和开发用,所以不学习本章节也可以继续开发我们本季度课程项目。
本章节讲的是什么?
我们在上个章节,已经将我们的vue页面的数据统一做了处理,就等着服务器给我们提供数据接口api,因此本章节教大家如何写api接口。另外,在群文件里,我们也会提供最终的后端源码给大家,大家可以自行上线源码到你的服务器。
大家对号入座,根据你的情况来学习本章节。
# 一、启动项目打开后台,新增一个管理板块
# ① /data/root.json
[
...
{"id":13,"pid":0, "name": "H5-小程序-App", "icon": "fa fa-tablet", "url": "" },
{"id":14,"pid":13, "name": "底部导航栏管理", "icon": "fa fa-sitemap", "url": "xxx" }
]
# 二、底部导航栏管理
# ① 控制器 /app/controller/admin/wap_uTabbar.js
'use strict';
const fs = require('node:fs');
const path = require('node:path');
//写入json文件
let paths = {
dir: './data',
file: './data/wap_uTabbar.json'
// dir:'./Appdata',
// file:'./Appdata/data.json'
}
const Controller = require('egg').Controller;
class Wap_uTabbarController extends Controller {
//创建界面
async create() {
const { ctx, app } = this;
//渲染公共模版
await ctx.renderTemplate({
title: '创建底部导航栏',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板
form: {
//提交地址
action: "/admin/wapUTabbar/save",
// 字段
fields: [
{
label: '名称',
type: 'text',
name: 'text',
placeholder: '请输入底部导航栏名称',
// default:'默认值测试', //新增时候默认值,可选
},
{
label: '图标',
type: 'text',
name: 'icon',
placeholder: '请输入图标名',
// default:'默认值测试', //新增时候默认值,可选
},
{
label: '页面地址',
type: 'text',
name: 'openurl',
placeholder: '请输入点击导航栏之后打开哪个页面的地址',
// default:'默认值测试', //新增时候默认值,可选
},
{
label: '角标',
type: 'number',
name: 'badge',
placeholder: '角标数,选填',
default:'0', //新增时候默认值,可选
},
{
label: '是否有红点',
type: 'btncheck', //按钮组选择
name: 'dot',
default: JSON.stringify([
{ value: false, name: '没有红点', checked: true },
{ value: true, name: '有红点' },
]),
placeholder: '是否有红点 false没有红点 true有红点 等等状态',
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '/admin/wapUTabbar/index',
});
}
//创建提交数据
async save() {
//1.参数验证
this.ctx.validate({
text: {
type: 'string', //参数类型
required: true, //是否必须
// defValue: '',
desc: '导航栏名称' //字段含义
},
icon: {
type: 'string',
required: true,
// defValue: '',
desc: '导航栏图标'
},
openurl: {
type: 'string',
required: true,
// defValue: '',
desc: '页面地址'
},
badge: {
type: 'string',
required: false,
defValue: 0,
desc: '角标数'
},
dot: {
type: 'bool',
required: false,
defValue: false,
desc: '是否有红点'
},
});
//写入数据
let data = this.ctx.request.body;
await this.addconfig(data, paths);
}
//配置写入json文件
async addconfig(data, paths) {
//创建一个文件夹data
if (!fs.existsSync(paths.dir)) {
fs.mkdirSync(paths.dir);
};
//判断json文件是否存在,存在说明之前写入过了,先读一下
let flag = fs.existsSync(paths.file);
if (flag) {
//存在先读取一下
await this.readconfig(paths.file, data)
} else {
//不存在,首次直接写
let ms = data;
ms.id = 1;
//加入时间,所在地等等
// ms.timestamp = new Date().getTime();
// console.log(ms);
let o = {};
o.data = [];
o.data.push(ms);
o.total = 1;
o.currentId = 1;
// console.log(o);
// console.log(JSON.stringify(o));
//写入内容,同步异步promise,以及可写流
await this.writeconfig(paths.file, o);
}
}
//存在先读取一下
async readconfig(path, data) {
//读取内容,同步异步promise,以及可写流
fs.readFile(path, {
flag: 'r',
encoding: 'utf-8',
}, (err, oldmessage) => {
if (err) throw err;
oldmessage = JSON.parse(oldmessage);
console.log(oldmessage)
console.log(data);
//处理数据
data.id = oldmessage.currentId + 1;
//加入时间,所在地等等
// data.timestamp = new Date().getTime();
//大对象
oldmessage.data.push(data);
oldmessage.total = oldmessage.data.length;
oldmessage.currentId = data.id;
console.log(oldmessage);
//写入内容,同步异步promise,以及可写流
this.writeconfig(path, oldmessage);
})
}
//写入
async writeconfig(path, data) {
fs.writeFile(path, JSON.stringify(data), (err) => {
if (err) throw err;
console.log('写入成功')
});
}
//列表界面
async index() {
const { ctx, app } = this;
let data = await this.getsiteConfigJson();
if (typeof data == 'object') {
//渲染公共模版
await ctx.renderTemplate({
title: '底部导航栏栏目列表',//现在网页title,面包屑导航title,页面标题
data: data.data,
tempType: 'table', //模板类型:table表格模板 ,form表单模板
table: {
//表格上方按钮,没有不要填buttons
buttons: [
{
url: '/admin/wapUTabbar/create',//新增路径
desc: '新建底部导航栏',//新增 //按钮名称
// icon: 'fa fa-plus fa-lg',//按钮图标
}
],
//表头
columns: [
{
title: '底部导航栏栏目信息',
// key: 'category_id',
class: 'text-left',//可选
render(item) {
return `
<ul>
<li>名称:${item.text}</li>
<li>图标:${item.icon}</li>
<li>打开地址:${item.openurl}</li>
<li>角标数:${item.badge}</li>
<li>是否有红点:${item.dot}</li>
</ul>
`;
}
},
{
title: '操作',
class: 'text-right',//可选
action: {
//修改
edit: function (id) {
return `/admin/wapUTabbar/edit/${id}`;
},
//删除
delete: function (id) {
return `/admin/wapUTabbar/delete/${id}`;
}
}
},
],
},
});
}
}
//获取数据
async getsiteConfigJson() {
//先判断是否存在这个json文件或者文件夹
if (!fs.existsSync(paths.file)) {
this.ctx.redirect('/admin/wapUTabbar/create');
return '';
} else {
// console.log(__dirname);//D:\【第二学期第3季】课程代码\myegg\app\controller
//D:\【第二学期第3季】课程代码\myegg\data\message.json
// console.log(path.resolve(__dirname,'../../','data/message.json'));
// let data = fs.readFileSync(path.resolve(__dirname, '../../../', 'data/siteConfig.json'), {
// encoding: 'utf-8'
// });
// let data = fs.readFileSync('./data/siteConfig.json',{
// encoding: 'utf-8'
// });
let data = fs.readFileSync(paths.file, {
encoding: 'utf-8'
});
console.log(JSON.parse(data));
return JSON.parse(data)
}
}
// 修改配置
async edit() {
const { ctx, app } = this;
const id = ctx.params.id;
let data = await this.getsiteConfigJson();
data = data.data.find(item => item.id == id);
console.log('即将修改渲染的内容', data);
//渲染公共模版
await ctx.renderTemplate({
id,
title: '修改导航栏',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板
form: {
//修改配置提交地址
action: '/admin/wapUTabbar/update/' + id,
// 字段
fields: [
{
label: '名称',
type: 'text',
name: 'text',
placeholder: '请输入底部导航栏名称',
// default:'默认值测试', //新增时候默认值,可选
},
{
label: '图标',
type: 'text',
name: 'icon',
placeholder: '请输入图标名',
// default:'默认值测试', //新增时候默认值,可选
},
{
label: '页面地址',
type: 'text',
name: 'openurl',
placeholder: '请输入点击导航栏之后打开哪个页面的地址',
// default:'默认值测试', //新增时候默认值,可选
},
{
label: '角标',
type: 'number',
name: 'badge',
placeholder: '角标数,选填',
default:'0', //新增时候默认值,可选
},
{
label: '是否有红点',
type: 'btncheck', //按钮组选择
name: 'dot',
default: JSON.stringify([
{ value: false, name: '没有红点', checked: true },
{ value: true, name: '有红点' },
]),
placeholder: '是否有红点 false没有红点 true有红点 等等状态',
},
],
//修改内容默认值
data: data,
},
//修改成功之后跳转到哪个页面
successUrl: '/admin/wapUTabbar/index',
});
}
//修改配置提交数据到json
async update() {
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
id: {
type: 'int',
required: true,
desc: 'id'
},
text: {
type: 'string', //参数类型
required: true, //是否必须
// defValue: '',
desc: '导航栏名称' //字段含义
},
icon: {
type: 'string',
required: true,
// defValue: '',
desc: '导航栏图标'
},
openurl: {
type: 'string',
required: true,
// defValue: '',
desc: '页面地址'
},
badge: {
type: 'string',
required: false,
defValue: 0,
desc: '角标数'
},
dot: {
type: 'bool',
required: false,
defValue: false,
desc: '是否有红点'
},
});
const id = ctx.params.id;
let data = await this.getsiteConfigJson();
let index = data.data.findIndex(item => item.id == id);
if (index == -1) {
return this.apiFail('该数据不存在');
}
let params = ctx.request.body;
params.id = id;
console.log('修改的数据', params);
console.log('修改json文件的数组元素索引', index);
console.log('原json文件数据', data);
data.data[index] = params;
console.log('写入json文件的最终所有数据', data);
fs.writeFile(paths.file, JSON.stringify(data), (err) => {
if (err) throw err;
console.log('写入成功');
});
}
//删除
async delete() {
const { ctx, app } = this;
const id = ctx.params.id;
let data = await this.getsiteConfigJson();
let index = data.data.findIndex(item => item.id == id);
if (index == -1) {
return this.apiFail('该数据不存在');
}
data.data.splice(index, 1);
data.total = data.data.length;
await this.writeconfig(paths.file, data);
//提示
ctx.toast('删除成功', 'success');
//跳转
ctx.redirect('/admin/wapUTabbar/index');
}
}
module.exports = Wap_uTabbarController;
# ② 路由 /app/router/admin/admin.js
//底部导航栏列表界面
router.get('/admin/wapUTabbar/index', controller.admin.wapUTabbar.index);
//创建底部导航栏界面
router.get('/admin/wapUTabbar/create', controller.admin.wapUTabbar.create);
//创建底部导航栏数据
router.post('/admin/wapUTabbar/save', controller.admin.wapUTabbar.save);
// 修改界面
router.get('/admin/wapUTabbar/edit/:id', controller.admin.wapUTabbar.edit);
// 修改提交数据
router.post('/admin/wapUTabbar/update/:id', controller.admin.wapUTabbar.update);
//删除
router.get('/admin/wapUTabbar/delete/:id', controller.admin.wapUTabbar.delete);
# ③ /data/root.json
[
...
{"id":13,"pid":0, "name": "H5-小程序-App", "icon": "fa fa-tablet", "url": "" },
{"id":14,"pid":13, "name": "底部导航栏管理", "icon": "fa fa-sitemap", "url": "/admin/wapUTabbar/index" }
]
# 三、主要栏目(主要分类)管理
我们移动端的
主要栏目板块,对应的就是网站的导航栏(即网站分类管理),只是每个分类多了图标,然后点击之后链接地址不一样,因此,我们首先对数据库新增三个字段:wap_icon、wap_url、wap_isnav。
# ① 数据库新增三个字段,并对迁移文件和模型文件进行修改
表名:category
字段名 数据类型 描述 空 默认值 字段含义
... ... ... ... ... wap_icon varchar(100) 是 移动端分类图标 wap_url varchar(255) 是 移动端点击分类打开的页面 wap_isnav int(1) 否 0 该分类是否是移动端的主要栏目:0-否,1-是 迁移文件
/database/migrations/分类迁移表、 模型文件/app/model/category.js
...
wap_icon: {
type: STRING(100),//不限定长度.默认varchar(255)
allowNull: true,
defaultValue: '',
comment: '移动端分类图标'
},
wap_url: {
type: STRING,//不限定长度.默认varchar(255)
allowNull: true,
defaultValue: '',
comment: '移动端点击分类打开的页面'
},
wap_isnav: {
type: INTEGER(1),
allowNull: false,
defaultValue: 0,
comment: '该分类是否是移动端的主要栏目:0-否,1-是'
},
...
# ② 新增栏目:主要栏目管理 /data/root.json
[
...
{"id":15,"pid":13, "name": "主要分类管理", "icon": "fa fa-list", "url": "/admin/wapCategory/index" }
]
# ③ 控制器 /app/controller/admin/wap_category.js
'use strict';
const Controller = require('egg').Controller;
class Wap_categoryController extends Controller {
//分类列表页面
async index() {
const { ctx, app } = this;
//分页:可以提炼成一个公共方法page(模型名称,where条件,其他参数options)
// let data = await ctx.page('Category');
let data = await ctx.service.category.datalist({ limit: 1000 });
console.log('分类数据', data);
// return;
data = data.rules;
//渲染公共模版
await ctx.renderTemplate({
title: '分类列表',//现在网页title,面包屑导航title,页面标题
data,
tempType: 'table', //模板类型:table表格模板 ,form表单模板
table: {
//表格上方按钮,没有不要填buttons
buttons: [
{
url: '/admin/wapCategory/create',//新增路径
desc: '新增分类',//新增 //按钮名称
// icon: 'fa fa-plus fa-lg',//按钮图标
}
],
//表头
columns: [
{
title: '分类名称',
key: 'name',
class: 'text-left',//可选
render(item) { //树形数据
// console.log('每个item',item);
if (item.level) {
let w = item.level * 40;
return `<span style="display:inline-block;width:${w}px"></span>`;
}
}
},
{
title: '移动端分类图标',
key: 'wap_icon',
class: 'text-center',//可选
},
{
title: '该分类是否是移动端的主要栏目',
key: 'wap_isnav',
width: 200,//可选
class: 'text-center',//可选
hidekeyData: true,//是否隐藏key对应的数据
render(item) {
console.log('可用状态里面每个item', item);
let arr = [
{ value: 1, name: '是' },
{ value: 0, name: '否' },
];
let str = `<div class="btn-group btn-group-${item.id}">`;
for (let i = 0; i < arr.length; i++) {
str += `<button type="button" class="btn btn-light" data="${item.wap_isnav}"
value="${arr[i].value}"
@click="changeBtnStatus('wap_isnav','btn-group-${item.id}',${arr[i].value},${i},${item.id},'wapCategory','Category')">${arr[i].name}</button>`;
}
str += `</div>`;
return str;
}
},
{
title: '移动端点击分类打开的页面',
key: 'wap_url',
class: 'text-center',//可选
},
{
title: '分类排序',
key: 'order',
class: 'text-center',//可选
},
{
title: '可用状态',
key: 'status',
width: 200,//可选
class: 'text-center',//可选
hidekeyData: true,//是否隐藏key对应的数据
render(item) {
console.log('可用状态里面每个item', item);
let arr = [
{ value: 1, name: '可用' },
{ value: 0, name: '不可用' },
];
let str = `<div class="btn-group btn-group-${item.id}">`;
for (let i = 0; i < arr.length; i++) {
str += `<button type="button" class="btn btn-light" data="${item.status}"
value="${arr[i].value}"
@click="changeBtnStatus('status','btn-group-${item.id}',${arr[i].value},${i},${item.id},'wapCategory','Category')">${arr[i].name}</button>`;
}
str += `</div>`;
return str;
}
},
{
title: '操作',
class: 'text-right',//可选
action: {
//修改
edit: function (id) {
return `/admin/wapCategory/edit/${id}`;
},
//删除
delete: function (id) {
return `/admin/wapCategory/delete/${id}`;
}
}
},
],
},
});
}
//创建分类表单
async create() {
const { ctx, app } = this;
// 渲染模版前先拿到所有分类
let data = await ctx.service.category.dropdown_Categorylist();
//渲染公共模版
await ctx.renderTemplate({
title: '创建移动端分类表单',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板
form: {
//提交地址
action: "/admin/wapCategory/save",
// 字段
fields: [
{
label: '放在哪个分类里面',
type: 'dropdown', //下拉框
name: 'pid',
default: JSON.stringify(data),
placeholder: '',
},
{
label: '分类名称',
type: 'text',
name: 'name',
placeholder: '请输入分类名称',
// default:'默认值测试', //新增时候默认值,可选
},
{
label: '分类英文名称',
type: 'text',
name: 'enname',
placeholder: '请输入分类英文名称',
},
// {
// label: '分类描述',
// type: 'text',
// name: 'description',
// placeholder: '请输入分类描述,选填',
// },
// {
// label: '分类链接地址',
// type: 'text',
// name: 'categoryurl',
// placeholder: '请输入分类链接地址,选填',
// },
{
label: '分类排序',
type: 'number',
name: 'order',
placeholder: '请输入分类排序',
default:50,
},
// {
// label: '可用状态',
// type: 'btncheck', //按钮组选择
// name: 'status',
// default: JSON.stringify([
// { value: 1, name: '可用', checked: true },
// { value: 0, name: '不可用' },
// ]),
// placeholder: '分类状态 0不可用 1可用 等等状态',
// },
{
label: '移动端分类图标',
type: 'text',
name: 'wap_icon',
placeholder: '请输入移动端分类图标,选填',
},
{
label: '移动端点击分类打开的页面',
type: 'text',
name: 'wap_url',
placeholder: '移动端点击分类打开的页面,选填',
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '/admin/wapCategory/index',
});
}
//分类表单提交数据
async save() {
//一般处理流程
//1.参数验证
this.ctx.validate({
name: {
type: 'string', //参数类型
required: true, //是否必须
// defValue: '',
desc: '分类名称' //字段含义
},
enname: {
type: 'string',
required: false,
defValue: '',
desc: '分类英文名称'
},
// status: {
// type: 'int',
// required: true,
// defValue: 1,
// desc: '分类状态 0不可用 1可用 等等状态'
// },
pid: {
type: 'int',
required: true,
defValue: 0,
desc: '上一级(父级)id'
},
// description: {
// type: 'string',
// required: false,
// defValue: '',
// desc: '分类描述,分类简单介绍'
// },
// categoryurl: {
// type: 'string',
// required: false,
// defValue: '',
// desc: '分类链接地址'
// },
order: {
type: 'int',
required: false,
defValue: 50,
desc: '分类排序'
},
wap_icon: {
type: 'string',
required: false,
defValue: '',
desc: '移动端分类图标'
},
wap_url: {
type: 'string',
required: false,
defValue: '',
desc: '移动端点击分类打开的页面'
}
});
//先判断一下直播功能中的礼物账号是否存在,不存在在写入数据库
//2.写入数据库
//3.成功之后给页面反馈
let {
name,
enname,
// status,
pid,
// description,
order,
// categoryurl,
wap_icon,
wap_url
} = this.ctx.request.body;
if (await this.app.model.Category.findOne({ where: { name } })) {
return this.ctx.apiFail('分类名称已存在');
}
//否则不存在则写入数据库
const res = await this.app.model.Category.create({
name,
enname,
// status,
pid,
// description,
order,
// categoryurl,
wap_icon,
wap_url
});
this.ctx.apiSuccess('创建分类成功');
}
//根据id修改分类状态
async updateStatus() {
const { id, status } = this.ctx.request.body;
//1.参数验证
this.ctx.validate({
status: {
type: 'int',
required: true,
// defValue: 1,
desc: '分类状态 0不可用 1可用 等等状态'
},
id: {
type: 'int',
required: true,
// defValue: 0,
desc: '分类id'
}
});
let data = await this.app.model.Category.findByPk(id);
if (!data) {
return this.ctx.apiFail('分类数据不存在');
}
data.status = status;
await data.save();
this.ctx.apiSuccess('修改分类状态成功');
}
//修改分类界面
async edit() {
const { ctx, app } = this;
const id = ctx.params.id;
let currentdata = await app.model.Category.findOne({
where: {
id
}
});
if (!currentdata) {
return ctx.apiFail('该分类不存在');
}
currentdata = JSON.parse(JSON.stringify(currentdata));
console.log('当前分类数据', currentdata);
// return;
// 渲染模版前先拿到所有分类
let data = await ctx.service.category.dropdown_Categorylist();
console.log('下拉框显示的所有分类', JSON.stringify(data));
// return;
//渲染公共模版
await ctx.renderTemplate({
id,
title: '修改分类:' + currentdata.name,//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板
form: {
//修改直播功能中的礼物提交地址
action: '/admin/wapCategory/update/' + id,
// 字段
fields: [
{
label: '放在哪个分类里面',
type: 'dropdown', //下拉框
name: 'pid',
default: JSON.stringify(data),
placeholder: '不调整(如需调整请选择)',
},
{
label: '分类名称',
type: 'text',
name: 'name',
placeholder: '请输入分类名称',
// default:'默认值测试', //新增时候默认值,可选
},
{
label: '分类英文名称',
type: 'text',
name: 'enname',
placeholder: '请输入分类英文名称',
},
// {
// label: '分类描述',
// type: 'text',
// name: 'description',
// placeholder: '请输入分类描述,选填',
// },
// {
// label: '分类链接地址',
// type: 'text',
// name: 'categoryurl',
// placeholder: '请输入分类链接地址,选填',
// },
{
label: '分类排序',
type: 'number',
name: 'order',
placeholder: '请输入分类排序',
default:50,
},
// {
// label: '可用状态',
// type: 'btncheck', //按钮组选择
// name: 'status',
// default: JSON.stringify([
// { value: 1, name: '可用', checked: true },
// { value: 0, name: '不可用' },
// ]),
// placeholder: '分类状态 0不可用 1可用 等等状态',
// },
{
label: '移动端分类图标',
type: 'text',
name: 'wap_icon',
placeholder: '请输入移动端分类图标,选填',
},
{
label: '移动端点击分类打开的页面',
type: 'text',
name: 'wap_url',
placeholder: '移动端点击分类打开的页面,选填',
},
],
//修改内容默认值
data:currentdata,
},
//修改成功之后跳转到哪个页面
successUrl: '/admin/wapCategory/index',
});
}
//修改分类提交数据
async update() {
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
id: {
type: 'int',
required: true,
desc: '直播功能中的礼物id'
},
name: {
type: 'string', //参数类型
required: true, //是否必须
// defValue: '',
desc: '分类名称' //字段含义
},
enname: {
type: 'string',
required: false,
defValue: '',
desc: '分类英文名称'
},
// status: {
// type: 'int',
// required: true,
// defValue: 1,
// desc: '分类状态 0不可用 1可用 等等状态'
// },
pid: {
type: 'int',
required: true,
defValue: 0,
desc: '上一级(父级)id'
},
// description: {
// type: 'string',
// required: false,
// defValue: '',
// desc: '分类描述,分类简单介绍'
// },
// categoryurl: {
// type: 'string',
// required: false,
// defValue: '',
// desc: '分类链接地址'
// },
order: {
type: 'int',
required: false,
defValue: 50,
desc: '分类排序'
},
wap_icon: {
type: 'string',
required: false,
defValue: '',
desc: '移动端分类图标'
},
wap_url: {
type: 'string',
required: false,
defValue: '',
desc: '移动端点击分类打开的页面'
}
});
// 参数
const id = ctx.params.id;
const {
name,
enname,
//status,
pid,
//description,
order,
//categoryurl,
wap_icon,
wap_url
} = ctx.request.body;
// 先看一下是否存在
const category = await app.model.Category.findOne({ where: { id } });
if (!category) {
return ctx.apiFail('该分类记录不存在');
}
//存在,分类名称可以一样,只要保证它在不同的分类下面
const Op = this.app.Sequelize.Op;//拿Op,固定写法
//先查一下修改的分类名称,是否已经存在,如果存在,但是只要不放在同一分类下还是可以的
const hasdata = await app.model.Category.findOne({
where: {
name,
id: {
[Op.ne]: id
}
}
});
if (hasdata && hasdata.pid == pid) {
return ctx.apiFail('同一个分类下不能有相同的分类名称');
}
// 修改数据
category.name = name;
category.enname = enname;
//category.status = status;
category.pid = pid;
//category.description = description;
category.order = order;
//category.categoryurl = categoryurl;
category.wap_icon = wap_icon;
category.wap_url = wap_url;
await category.save();
// 给一个反馈
ctx.apiSuccess('修改分类成功');
}
//删除分类
async delete() {
const { ctx, app } = this;
const id = ctx.params.id;
let data = await ctx.service.category.categorylist();
// console.log('所有分类', data);
let ids = app._collectNodeIds(data,Number(id));
// console.log(ids);
await app.model.Category.destroy({
where: {
id:{
[this.app.Sequelize.Op.in] : ids
}
}
});
//提示
ctx.toast('分类删除成功', 'success');
//跳转
ctx.redirect('/admin/wapCategory/index');
}
}
module.exports = Wap_categoryController;
# ④ 路由 /app/router/admin/admin.js
// 创建分类界面
router.get('/admin/wapCategory/create', controller.admin.wapCategory.create);
//创建分类提交数据
router.post('/admin/wapCategory/save', controller.admin.wapCategory.save);
//创建分类列表页面
router.get('/admin/wapCategory/index', controller.admin.wapCategory.index);
//根据id修改分类状态
router.post('/admin/wapCategory/updateStatus', controller.admin.wapCategory.updateStatus);
//修改分类界面
router.get('/admin/wapCategory/edit/:id', controller.admin.wapCategory.edit);
//修改分类数据功能
router.post('/admin/wapCategory/update/:id', controller.admin.wapCategory.update);
//删除分类功能
router.get('/admin/wapCategory/delete/:id', controller.admin.wapCategory.delete);
# 四、网站配置(联系我们)管理
# ① 控制器 /app/controller/admin/wap_config.js
'use strict';
const fs = require('node:fs');
const path = require('node:path');
//留言写入json文件
let paths = {
dir: './data',
file: './data/wap_config.json'
// dir:'./Appdata',
// file:'./Appdata/data.json'
}
const Controller = require('egg').Controller;
class Wap_configController extends Controller {
//创建配置界面
async create() {
const { ctx, app } = this;
//渲染公共模版
await ctx.renderTemplate({
title: '创建配置',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板
form: {
//提交地址
action: "/admin/wapConfig/save",
// 字段
fields: [
{
label: '配置项称呼',
type: 'text',
name: 'configName',
placeholder: '如:公司名称/联系地址等',
// default:'默认值测试', //新增时候默认值,可选
},
// {
// label: '配置项值',
// type: 'text',
// name: 'configValue',
// placeholder: '如:睿晨电网建设工程有限公司',
// // default:'默认值测试', //新增时候默认值,可选
// },
{
label: '配置项值',
type: 'diy', //自定义类型组件
name: 'configValue',
placeholder: '请输入配置项值',
render(item){
const name = item.name;
console.log(name);
return `
<div class="btn-group btn-group-${name}">
<button type="button" class="btn btn-success"
@click="diyBtnGroup('btn-group-${name}','btn-group-${name}-check',0)">文本</button>
<button type="button" class="btn btn-light"
@click="diyBtnGroup('btn-group-${name}','btn-group-${name}-check',1)">文件</button>
</div>
<div class="btn-group-${name}-check">
<div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">配置项值</span>
</div>
<input type="text" class="form-control"
placeholder="如:睿晨电网建设工程有限公司" name='${name}'
v-model="form.${name}" />
</div>
</div>
<div style="display:none;">
<input class="form-control" type="file" name="${name}"
@change="uploadFile($event,'${name}',(e)=>{
console.log(e);
})" />
<div v-if="uploadedFiles.hasOwnProperty('${name}') && uploadedFiles['${name}'] ">
<div v-if="uploadedFiles['${name}'].endsWith('.jpg') ||
uploadedFiles['${name}'].endsWith('.jpeg') ||
uploadedFiles['${name}'].endsWith('.png') ||
uploadedFiles['${name}'].endsWith('.gif') ">
<img :src="uploadedFiles['${name}']" class="mt-2 p-1 rounded border avatar-lg" >
</div>
<div v-else>
<span class="text-success">附件上传成功!</span>
</div>
</div>
</div>
</div>
`;
}
},
{
label: '配置项图标',
type: 'text',
name: 'prefixIcon',
placeholder: '请输入配置项图标',
// default:'默认值测试', //新增时候默认值,可选
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '/admin/wapConfig/index',
});
}
//创建配置提交数据
async save() {
//1.参数验证
this.ctx.validate({
configName: {
type: 'string', //参数类型
required: true, //是否必须
// defValue: '',
desc: '配置项称呼' //字段含义
},
configValue: {
type: 'string',
required: true,
// defValue: '',
desc: '配置项值'
},
prefixIcon: {
type: 'string',
required: true,
// defValue: '',
desc: '配置项图标'
},
});
//写入数据
let data = this.ctx.request.body;
await this.addconfig(data, paths);
}
//配置写入json文件
async addconfig(data, paths) {
//创建一个文件夹data
if (!fs.existsSync(paths.dir)) {
fs.mkdirSync(paths.dir);
};
//判断json文件是否存在,存在说明之前写入过了,先读一下
let flag = fs.existsSync(paths.file);
if (flag) {
//存在先读取一下
await this.readconfig(paths.file, data)
} else {
//不存在,首次直接写
let ms = data;
ms.id = 1;
//加入时间,所在地等等
// ms.timestamp = new Date().getTime();
// console.log(ms);
let o = {};
o.data = [];
o.data.push(ms);
o.total = 1;
o.currentId = 1;
// console.log(o);
// console.log(JSON.stringify(o));
//写入内容,同步异步promise,以及可写流
await this.writeconfig(paths.file, o);
}
}
//存在先读取一下
async readconfig(path, data) {
//读取内容,同步异步promise,以及可写流
fs.readFile(path, {
flag: 'r',
encoding: 'utf-8',
}, (err, oldmessage) => {
if (err) throw err;
oldmessage = JSON.parse(oldmessage);
console.log(oldmessage)
console.log(data);
//处理数据
data.id = oldmessage.currentId + 1;
//加入时间,所在地等等
// data.timestamp = new Date().getTime();
//大对象
oldmessage.data.push(data);
oldmessage.total = oldmessage.data.length;
oldmessage.currentId = data.id;
console.log(oldmessage);
//写入内容,同步异步promise,以及可写流
this.writeconfig(path, oldmessage);
})
}
//写入配置
async writeconfig(path, data) {
fs.writeFile(path, JSON.stringify(data), (err) => {
if (err) throw err;
console.log('写入成功')
});
}
//列表界面
async index() {
const { ctx, app } = this;
let data = await this.getsiteConfigJson();
if (typeof data == 'object') {
//渲染公共模版
await ctx.renderTemplate({
title: '配置列表',//现在网页title,面包屑导航title,页面标题
data: data.data,
tempType: 'table', //模板类型:table表格模板 ,form表单模板
table: {
//表格上方按钮,没有不要填buttons
buttons: [
{
url: '/admin/wapConfig/create',//新增路径
desc: '新建配置',//新增 //按钮名称
// icon: 'fa fa-plus fa-lg',//按钮图标
}
],
//表头
columns: [
{
title: '配置列表项信息',
// key: 'category_id',
class: 'text-left',//可选
render(item) {
return `
<ul>
<li>${item.configName} :${item.configValue}</li>
<li>图标:${item.prefixIcon}</li>
</ul>
`;
}
},
{
title: '操作',
class: 'text-right',//可选
action: {
//修改
edit: function (id) {
return `/admin/wapConfig/edit/${id}`;
},
//删除
delete: function (id) {
return `/admin/wapConfig/delete/${id}`;
}
}
},
],
},
});
}
}
//获取配置数据
async getsiteConfigJson() {
//先判断是否存在这个json文件或者文件夹
if (!fs.existsSync(paths.file)) {
this.ctx.redirect('/admin/wapConfig/create');
return '';
} else {
// console.log(__dirname);//D:\【第二学期第3季】课程代码\myegg\app\controller
//D:\【第二学期第3季】课程代码\myegg\data\message.json
// console.log(path.resolve(__dirname,'../../','data/message.json'));
// let data = fs.readFileSync(path.resolve(__dirname, '../../../', 'data/siteConfig.json'), {
// encoding: 'utf-8'
// });
// let data = fs.readFileSync('./data/siteConfig.json',{
// encoding: 'utf-8'
// });
let data = fs.readFileSync(paths.file, {
encoding: 'utf-8'
});
console.log(JSON.parse(data));
return JSON.parse(data)
}
}
// 修改
async edit() {
const { ctx, app } = this;
const id = ctx.params.id;
let data = await this.getsiteConfigJson();
data = data.data.find(item => item.id == id);
console.log('即将修改渲染的内容', data);
//渲染公共模版
await ctx.renderTemplate({
id,
title: '修改配置',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板
form: {
//修改配置提交地址
action: '/admin/wapConfig/update/' + id,
// 字段
fields: [
{
label: '配置项称呼',
type: 'text',
name: 'configName',
placeholder: '如:公司名称/联系地址等',
// default:'默认值测试', //新增时候默认值,可选
},
// {
// label: '配置项值',
// type: 'text',
// name: 'configValue',
// placeholder: '如:睿晨电网建设工程有限公司',
// // default:'默认值测试', //新增时候默认值,可选
// },
{
label: '配置项值',
type: 'diy', //自定义类型组件
name: 'configValue',
placeholder: '请输入配置项值',
render(item){
const name = item.name;
console.log(name);
return `
<div class="btn-group btn-group-${name}">
<button type="button" class="btn btn-success"
@click="diyBtnGroup('btn-group-${name}','btn-group-${name}-check',0)">文本</button>
<button type="button" class="btn btn-light"
@click="diyBtnGroup('btn-group-${name}','btn-group-${name}-check',1)">文件</button>
</div>
<div class="btn-group-${name}-check">
<div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">配置项值</span>
</div>
<input type="text" class="form-control"
placeholder="如:睿晨电网建设工程有限公司" name='${name}'
v-model="form.${name}" />
</div>
</div>
<div style="display:none;">
<input class="form-control" type="file" name="${name}"
@change="uploadFile($event,'${name}',(e)=>{
console.log(e);
})" />
<div v-if="uploadedFiles.hasOwnProperty('${name}') && uploadedFiles['${name}'] ">
<div v-if="uploadedFiles['${name}'].endsWith('.jpg') ||
uploadedFiles['${name}'].endsWith('.jpeg') ||
uploadedFiles['${name}'].endsWith('.png') ||
uploadedFiles['${name}'].endsWith('.gif') ">
<img :src="uploadedFiles['${name}']" class="mt-2 p-1 rounded border avatar-lg" >
</div>
<div v-else>
<span class="text-success">附件上传成功!</span>
</div>
</div>
</div>
</div>
`;
}
},
{
label: '配置项图标',
type: 'text',
name: 'prefixIcon',
placeholder: '请输入配置项图标',
// default:'默认值测试', //新增时候默认值,可选
},
],
//修改内容默认值
data: data,
},
//修改成功之后跳转到哪个页面
successUrl: '/admin/wapConfig/index',
});
}
//修改提交数据到json
async update() {
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
id: {
type: 'int',
required: true,
desc: '配置id'
},
configName: {
type: 'string', //参数类型
required: true, //是否必须
// defValue: '',
desc: '配置项称呼' //字段含义
},
configValue: {
type: 'string',
required: true,
// defValue: '',
desc: '配置项值'
},
prefixIcon: {
type: 'string',
required: true,
// defValue: '',
desc: '配置项图标'
},
});
const id = ctx.params.id;
let data = await this.getsiteConfigJson();
let index = data.data.findIndex(item => item.id == id);
if (index == -1) {
return this.apiFail('该数据不存在');
}
let params = ctx.request.body;
params.id = id;
console.log('修改的数据', params);
console.log('修改json文件的数组元素索引', index);
console.log('原json文件数据', data);
data.data[index] = params;
console.log('写入json文件的最终所有数据', data);
fs.writeFile(paths.file, JSON.stringify(data), (err) => {
if (err) throw err;
console.log('写入成功');
});
}
//删除
async delete() {
const { ctx, app } = this;
const id = ctx.params.id;
let data = await this.getsiteConfigJson();
let index = data.data.findIndex(item => item.id == id);
if (index == -1) {
return this.apiFail('该数据不存在');
}
data.data.splice(index, 1);
data.total = data.data.length;
await this.writeconfig(paths.file, data);
//提示
ctx.toast('删除配置数据成功', 'success');
//跳转
ctx.redirect('/admin/wapConfig/index');
}
}
module.exports = Wap_configController;
# ② 路由 /app/router/admin/admin.js
//界面
router.get('/admin/wapConfig/index', controller.admin.wapConfig.index);
//创建底部导航栏界面
router.get('/admin/wapConfig/create', controller.admin.wapConfig.create);
//创建数据
router.post('/admin/wapConfig/save', controller.admin.wapConfig.save);
// 修改
router.get('/admin/wapConfig/edit/:id', controller.admin.wapConfig.edit);
// 修改提交数据
router.post('/admin/wapConfig/update/:id', controller.admin.wapConfig.update);
//删除
router.get('/admin/wapConfig/delete/:id', controller.admin.wapConfig.delete);
# ③ /data/root.json
[
...
{"id":16,"pid":13, "name": "配置管理", "icon": "fa fa-wrench", "url": "/admin/wapConfig/index" }
]
# 五、后端api接口开发代码
具体查看 后端api接口开发代码