代码下载
完善服务器
对于上一篇博客开发的服务端项目,还需要增加文章列表查询
,文章删除
,文章更新
三个接口。
文章列表查询接口
验证表单数据
在路径 schema/user.js
中增加 articles 验证表单数据,其中使用的 .allow('')
表示允许空字符串:
articles: {query: {pagenum,pagesize,cate_id: joi.number().integer().min(1).allow('').optional(),state: joi.string().valid('已发布', '草稿').allow('').optional()}},
处理函数
- 文章查询这里是根据
cate_id
和state
进行的多表条件查询,对相应参数进行有效判断而给出合适的sql
查询语句。 - 数量查询通过
select count (*/字段) from 表名
来进行。 - 分页查询通过
select * from 表名 limit 数值1, 数值2
来进行,其中 数值1 表示跳过前面多少条数据,数值2 表示取多少条数据。 - 排序通过
order by 字段 asc/desc
来进行。
// 查询文章
const articles = (req, res) => {console.log('query: ', req.query);// 跳过的条数let num = (req.query.pagenum - 1)*req.query.pagesize;// 查询文章的sqllet sql;// 查询文章数量的sqllet numSql;// 文章查询是根据参数按条件查询if (typeof req.query.cate_id === 'number' && typeof req.query.state === 'string' && req.query.state.length > 0) {sql = `select articles.*, article_cate.name cate_name, article_cate.alias cate_alias, article_cate.is_delete cate_is_delete from articles, article_cate where cate_id = ? and state = ? and articles.cate_id = article_cate.id order by pub_date desc limit ${num}, ${req.query.pagesize}`;numSql = `select count (*) total from articles, article_cate where cate_id = ? and state = ? and articles.cate_id = article_cate.id`} else if (typeof req.query.cate_id === 'number') {sql = `select articles.*, article_cate.name cate_name, article_cate.alias cate_alias, article_cate.is_delete cate_is_delete from articles, article_cate where cate_id = ? and articles.cate_id = article_cate.id order by pub_date desc limit ${num}, ${req.query.pagesize}`;numSql = `select count (*) total from articles, article_cate where cate_id = ? and articles.cate_id = article_cate.id`} else if (typeof req.query.state === 'string' && req.query.state.length > 0) {sql = `select articles.*, article_cate.name cate_name, article_cate.alias cate_alias, article_cate.is_delete cate_is_delete from articles, article_cate where state = ? and articles.cate_id = article_cate.id order by pub_date desc limit ${num}, ${req.query.pagesize}`;numSql = `select count (*) total from articles, article_cate where state = ? and articles.cate_id = article_cate.id`} else {sql = `select articles.*, article_cate.name cate_name, article_cate.alias cate_alias, article_cate.is_delete cate_is_delete from articles, article_cate where articles.cate_id = article_cate.id order by pub_date desc limit ${num}, ${req.query.pagesize}`;numSql = `select count (*) total from articles, article_cate where articles.cate_id = article_cate.id`}console.log('sql: ', sql);console.log('numSql: ', numSql);let params = new Array();console.log('init params: ', params);if (typeof req.query.cate_id === 'number') {params.push(req.query.cate_id);}if (typeof req.query.state === 'string' && req.query.state.length > 0) {params.push(req.query.state);}console.log('param: ', params);// 查询文章db.query(sql, params, (err, result) => {if (err) {res.cc(err);} else {console.log('result: ', result);// 查询文章数量db.query(numSql, params, (e, r) => {if (err) {res.cc(err);} else {console.log('total result: ', r[0].total);let data = {data: result,total: r[0].total}res.send({status: 0,data: data,message: '文章列表获取成功!'});}});}});
}
实现路由
在路径 router/article.js
中实现如下路由:
routor.post('/addArticle', uploads.single('cover_img'), expressJoi(schema.addArticle), handler.addArticle);
文章删除接口
文章删除的实现跟文章类别删除类似,具体实现见代码……
文章更新接口
文章更新的实现跟文章发布和用户信息跟新类似,具体实现见代码……
安装VSCode插件辅助开发
由于使用VSCode访问开发的项目页面使用的是 file 协议,file 协议在开发当中会有很多的问题,通常需要 http 协议来访问开发的 html 文件,Live Server
和 Express
这两个插件都可以做到。
Live Server
- 在插件市场,搜索 Live Server 并安装
- 在页面上鼠标右键,选择 Open With Live Server 即可快速使用 http 协议访问页面,点击下方 Port 按钮可以关闭
Express
由于 Live Server 在页面保存之后会自动刷新整个页面,在不需要自动刷新的时候就不是很方便了,这时 Express 就会比较合适。
- 在插件市场,搜索 Express 并安装
- 打开命令面板(F1,或者Windows和Linux上的Ctrl+Shift+P,或者OSX上的Shift+CMD+P)
- 输入或选择 “Express: Host current workspace and open in browser”
配置 ajaxPrefilter
在目录中新建 baseApi.js 文件,编写如下代码:
// 在每个请求之前被发送和$.ajax()处理它们前处理,设置自定义Ajax选项或修改现有选项。
let host = 'http://127.0.0.1/';
$.ajaxPrefilter(function(options) {console.log('---ajaxPrefilter----');// 统一设置请求头if (options.url.indexOf('my/') === 0) {options.headers = {Authorization: localStorage.getItem('token')}}// 在发起真正的 Ajax 请求之前,统一拼接请求的根路径options.url = host + options.url;console.log('url: ', options.url);console.log('type: ', options.type);console.log('headers: ', options.headers);console.log('params: ', options.data);// 统一挂载 complete 回调 options.complete = function(res) {console.log('complete: ', res.responseJSON);// 处理身份验证失败if (res.responseJSON.status === 1 && res.responseJSON.message === '身份验证失败!') {// 清除 token 跳到登录页面localStorage.removeItem('token');location.href = 'project/login.html';}}
});
登录注册
使用 layui
绘制页面,具体实现见代码……
表单的验证
导入 layui 的 js 文件,为需要验证的表单项添加 lay-verify 属性,同时指定具体的校验规则即可。
通过 layui.form.verify() 函数自定义校验规则:
// 表单验证layui.form.verify({username: [/^[\w]{2,20}$/,'用户名必须为2~20位字母和数字!'],password: [/^[\S]{6,16}$/,'密码必须为6~16位非空格字符!'],repassword: function(value) {if ($('.signin [name=password]').val() !== value) {return '两次输入的密码不一致!';}}});
实现
登录注册切换:
// 去注册、去登录$('.to-signin a').click(function(){$('.login').hide();$('.signin').show();});$('.to-login a').click(function(){$('.signin').hide();$('.login').show();});
登录注册实现:
let layer = layui.layer;// 登录$('.login form').on('submit', function(e) {// 阻止默认行为e.preventDefault();let params = {// 属性选择器username: $('.login [name=username]').val(),password: $('.login [name=password]').val()}$.post('api/login', params, function(data) {// 登录成功if (data.status === 0) {// 缓存tokenlocalStorage.setItem('token', data.token);// 跳首页location.href = 'index.html'}layer.msg(data.message);});});// 注册$('.signin form').submit(function(e) {// 阻止默认行为e.preventDefault();$('.to-login a').click();let params = {username: $('.signin [name=username]').val(),password: $('.signin [name=password]').val()}$.post('api/signin',params, function(data) {// 注册成功if (data.status === 0) {layer.msg('注册成功,请登录!');// 切换到登录$('.to-login a').click();// 填充用户名$('.login [name=username]').val(params.username);// 获得焦点$('.login [name=password]').focus();} else {layer.msg(data.message);}});});
按需为表单项添加校验规则:
<input type="password" name="repassword" required lay-verify="required|password|repassword" placeholder="请再次输入密码" autocomplete="off" class="layui-input">
…
主页
使用 layui
绘制页面,具体实现见代码……
使用iframe标签在内容主体区域显示网页内容
在页面主体的 div 中添加 iframe :
<div class="layui-body"><!-- 内容主体区域 --><iframe name="fm" src="" frameborder="0"></iframe>
</div>
为 首页 链接添加 href 和 target 属性:
<a href="/home/dashboard.html" target="fm"><span class="iconfont icon- home"></span>首页</a>
为 首页 对应的导航 Item 项添加 layui-this 属性选中此 Item:
<li class="layui-nav-item layui-this"><a href="/home/dashboard.html" target="fm"><span class="iconfont icon-home"></span>首页</a>
</li>
**禁止过渡动画,强制清除 链接的 CSS3 动画: **
a {/* 禁止过渡动画 */transition: none !important;
}
在目录中新建 index.js 文件,实现如下代码渲染用户头像:
// 用户信息数据
var userinfo = null;// 渲染头像
function renderAvatar(user) {let name = user.nickname || user.username;$('#welcome').html(`欢迎 ${name}`);if (user.user_pic) {$('.layui-nav-img').attr('src', user.user_pic).show();$('.text-avatar').hide();} else {// 渲染大写首字母$('.text-avatar').html(name[0].toUpperCase()).show();$('.layui-nav-img').hide();}// 渲染完成后,保存数据userinfo = user;
}
获取用户的基本信息:
$(function(){// 用户信息getUserinfo();
});
// 请求用户信息数据
function getUserinfo() {$.get('my/userinfo', function(data) {if (data.status === 0) {renderAvatar(data.data);} else {layer.msg(data.message);}})
}
实现退出功能:
$(function(){// 退出$('#logout').click(function() {console.log('logout');layer.confirm('是否退出?', {icon: 3,title: '提示'}, function(index) {console.log(index);// 清除 tokenlocalStorage.removeItem('token');// 进入登录页location.href = 'login.html';// 关闭 询问框layer.close(index);});});
});
基本资料
使用 layui
绘制页面,具体实现见代码……
获取用户的基本信息
在目录中新建 userinfo.js 文件,实现如下代码填充表单数据:
let form = layui.form;var layer = layui.layer;// 填充初始数据// 通过 window.parent 调用父框架 属性、方法 必须在http协议中打开form.val('userinfo', window.parent.userinfo);
}
中的子页面,如果想要调用父页面中的属性、方法,使用 window.parent 即可(但是必须http协议中打开)。
调用 layui.form.val()
可以快速为表单赋值。
实现表单的重置效果,阻止表单的默认重置行为,再重新为表单赋值即可:
// 重置对表单的修改$('#reset-btn').click(function(e) {// 阻止默认行为e.preventDefault();// 用原始数据重新填充表单form.val('userinfo', window.parent.userinfo);});
发起请求更新用户的信息,阻止表单的默认提交行为,并发起数据请求:
// 提交数据$('.layui-form').submit(function(e) {// 阻止默认行为e.preventDefault();let params = $(this).serialize();$.post('my/userinfo', params, function(data) {if (data['status'] === 0) {// 调用父框架的方法重新获取数据(缺点就是需要重新请求网络)// window.parent.getUserinfo();// 通过修改父框架的数据重新渲染(不需要请求网络,但是需要拼接数据)let userinfo = window.parent.userinfo// 属性选择器userinfo.nickname = $('.layui-form [name=nickname]').val();userinfo.email = $('.layui-form [name=email]').val();console.log('userinfo: ', userinfo);window.parent.renderAvatar(userinfo);} else {layer.msg(data['message']);}});});
重置密码
使用 layui
绘制页面,具体实现见代码……
发起请求实现重置密码的功能:
$('.layui-form').submit(function(e) {// 阻止默认行为e.preventDefault();$.post('my/updatePassword', {oldPassword: $('[name=oldPassword]').val(),newPassword: $('[name=password]').val()}, function(data) {if (data.status === 0) {$(this)[0].reset();layui.layer.msg('修改密码成功!');} else {console.log('data: ', data);layui.layer.msg(data.message);}});});
使用 reset()
方法实现重置表单功能:
$('#reset-btn').on('click', function() {$('.layui-form')[0].reset();})
更换头像
使用 layui
绘制页面,具体实现见代码……
cropper 实现图片裁剪预览
在 中导入 cropper.css 样式表,在 的结束标签之前,按顺序导入如下的 js 脚本:
<head><link rel="stylesheet" href="../assets/lib/cropper/cropper.css">
</head><body><script src="../assets/lib/jquery/jquery-3.7.1.min.js"></script><script src="../assets/lib/cropper/Cropper.js"></script><script src="../assets/lib/cropper/jquery-cropper.js"></script>
</body>
定义裁剪区与预览区的 html 结构:
<!-- 第一行的图片裁剪和预览区域 --> <div class="row1"><!-- 图片裁剪区域 --><div class="cropper-box"><!-- 这个 img 标签很重要,将来会把它初始化为裁剪区域 --><img id="image" src="../assets/images/sample.jpg" /></div><!-- 图片的预览区域 --> <div class="preview-box"><div><!-- 宽高为 100px 的预览区域 --><div class="img-preview w100"></div> <p class="size">100 x 100</p></div> <div><!-- 宽高为 50px 的预览区域 --><div class="img-preview w50"></div> <p class="size">50 x 50</p></div></div></div>
实现基本裁剪效果:
// 获取裁剪区域的 DOM 元素 var $image = $('#image');// 配置选项const options = {// 纵横比aspectRatio: 1,// 指定预览区域preview: '.img-preview'};// 创建裁剪区域 $image.attr('src', window.parent.userinfo.user_pic).cropper(options);
更换裁剪的图片,首先拿到用户选择的文件;然后根据选择的文件,创建一个对应的 URL 地址;最后销毁 旧的裁剪区域,重新设置图片路径 ,创建新的裁剪区域:
let file = filelist[0];console.log('file: ', file);let url = URL.createObjectURL(file);console.log('url: ', url);$image.cropper('destroy') // 销毁旧的裁剪区域.attr('src', url) // 重新设置图片路径.cropper(options); // 重新初始化裁剪区域
读取文件,可以使用 URL.createObjectURL(file)
,还可以使用 FileReader
对象:
// 或者使用 FileReader 读取图片let reader = new FileReader();reader.readAsDataURL(file);reader.onload = function() {console.log('result: ', reader.result);$image.cropper('destroy') // 销毁旧的裁剪区域.attr('src', reader.result) // 重新设置图片路径.cropper(options); // 重新初始化裁剪区域};
将裁剪后的图片,输出为 base64 格式的字符串:
var dataURL = $image.cropper('getCroppedCanvas', { // 创建一个 Canvas 画布width: 100,height: 100}).toDataURL('image/png'); // 将 Canvas 画布上的内容,转化为 base64 格式的字符串
将裁剪后的图片,输出为二进制数据对象:
$image.cropper('getCroppedCanvas', { // 创建一个 Canvas 画布width: 400,height: 280}).toBlob(function(blob) {console.log('image: ', blob);});
将裁剪后的头像上传到服务器,更新父页面中的数据并渲染头像:
$.post('my/updateAvatar', {user_pic: dataURL}, function(data) {if (data.status === 0) {let userinfo = window.parent.userinfo;userinfo.user_pic = dataURL;window.parent.renderAvatar(userinfo);layui.layer.msg('修改头像成功!');} else {layui.layer.msg(data.message);}});
文章分类
使用 layui
绘制页面,具体实现见代码……
使用模板引擎渲染表格的数据,导入模板引擎并定义模板:
<script src="../assets/lib/template/template-web.js"></script><script type="text/html" id="tmp-table">{{each}}<tr><td>{{$value.name}}</td><td>{{$value.alias}}</td><td><button type="button" class="layui-btn layui-btn-xs btn-edit" data-id={{$value.id}}>编辑</button><button type="button" class="layui-btn layui-btn-danger layui-btn-xs btn-delete" data-id={{$value.id}}>删除</button></td></tr>{{/each}}</script>
发起请求获取数据渲染列表:
// 获取分类列表数据getListData();function getListData() {$.get('my/artcate', function(data) {if (data.status === 0) {let html = template('tmp-table', data.data);$('tbody').html(html);} else {layui.layer.msg(data.message);}});}
添加文章分类
在页面中定义如下的 script 标签,渲染添加文章分类的 form 表单结构:
<script type="text/html" id="editCateDialog"><form id="editForm" class="layui-form" lay-filter="editForm"><!-- 隐藏域 --><input type="hidden" name="id"><div class="layui-form-item"><label class="layui-form-label">分类名称</label><div class="layui-input-block"><input type="text" name="name" required lay-verify="required|password" placeholder="请输入分类名称" autocomplete="off" class="layui-input"></div></div><div class="layui-form-item"><label class="layui-form-label">分类别名</label><div class="layui-input-block"><input type="text" name="alias" required lay-verify="required|password|newPassword" placeholder="请输入分类别名" autocomplete="off" class="layui-input"></div></div><div class="layui-form-item"><div class="layui-input-block"><button class="layui-btn" lay-submit>修改</button></div></div></form></script>
使用 layui.layer.open 实现弹出层效果,在按钮的点击事件中,通过 layer.open() 展示弹出层,并在 layerIndex 中保存弹出层的索引:
// 添加按钮事件let layerIndex = null;$('#addCateBtn').click(function(e) {layerIndex = layui.layer.open({type: 1,area: ['500px', '250px'],title: '添加文章分类',content: $('#addCateDialog').html()}); });
发起Ajax请求,实现添加文章分类的功能,并使用 layui.layer.close 关闭弹出层:
// 事件委托绑定编辑表单提交事件$('body').on('submit', '#editForm', function(e) {e.preventDefault();$.post('my/updatecate', $(e.target).serialize(), function(data) {if (data.status === 0) {getListData();layui.layer.msg('文章分类添加成功!');layui.layer.close(layerIndex);} else {layui.layer.msg(data.message);}});});
编辑文章分类
修改文章分类的弹出层与添加文章分类类似,具体实现见代码……
通过 代理 的形式,为 btn-edit 按钮绑定点击事件,请求数据填充表单:
// 事件委托绑定编辑按钮事件$('tbody').on('click', '.btn-edit', function(e) {let cateid = $(e.target).attr('data-id');$.ajax({method: 'GET',url: 'my/cates/' + cateid,success: function(data) {if (data.status === 0) {layerIndex = layui.layer.open({type: 1,area: ['500px', '250px'],title: '修改文章分类',content: $('#editCateDialog').html()});layui.form.val('editForm', data.data[0]);} else {layui.layer.msg(data.message);}}})});
更新文章分类的数据,通过代理的形式,为修改分类的表单绑定 submit 事件:
// 事件委托绑定编辑表单提交事件$('body').on('submit', '#editForm', function(e) {e.preventDefault();$.post('my/updatecate', $(e.target).serialize(), function(data) {if (data.status === 0) {getListData();layui.layer.msg('文章分类添加成功!');layui.layer.close(layerIndex);} else {layui.layer.msg(data.message);}});});
删除文章分类
通过代理的形式,为删除按钮绑定点击事件:
// 事件委托绑定删除按钮事件$('tbody').on('click', '.btn-delete', function(e) {layer.confirm('是否删除?', {icon: 3, title:'提示'}, function(index){let cateid = $(e.target).attr('data-id');$.ajax({method: 'GET',url: 'my/deletecate/' + cateid,success: function(data) {if (data.status === 0) {layui.layer.msg('文章分类删除成功!');getListData();layer.close(index);} else {layui.layer.msg(data.message);}}});});});
文章列表
使用 layui
绘制页面,具体实现见代码……
文章列表主体区域
通过 template.defaults.imports 定义过滤器:
// 时间格式化template.defaults.imports.dateformatter = function(date) {let dt = new Date(date);let year = dt.getFullYear();let month = zeroTwo(dt.getMonth());let day = zeroTwo(dt.getDay());let hour = zeroTwo(dt.getHours());let minutes = zeroTwo(dt.getMinutes());let seconds = zeroTwo(dt.getSeconds());return year + '-' + month + '-' + day + ' ' + hour + ':' + minutes + ':' + seconds}// 补零函数function zeroTwo(value) {return value > 9 ? value : '0' + value}
使用过滤器定义列表数据的 template 模板结构:
<script type="text/html" id="tmp-table">{{each}}<tr><td>{{$value.title}}</td><td>{{$value.cate_name}}</td><td>{{$value.pub_date | dateformatter}}</td><td>{{$value.state}}</td><td><button type="button" class="layui-btn layui-btn-xs btn-edit" data-index={{$index}}>编辑</button><button type="button" class="layui-btn layui-btn-danger layui-btn-xs btn-delete" data-id={{$value}} data-index={{$index}}>删除</button></td></tr>{{/each}}</script>
定义一个查询的参数对象,获取文章列表数据并使用 template 渲染表格:
// 获取列表数据参数let params = {pagenum: 1,pagesize: 2,cate_id: '',state: ''};// 获取列表数据getListData();let dataList = null;function getListData() {$.ajax({method: 'GET',url: 'my/articles',data: params,success: function(data) {if (data.status === 0) {dataList = data.data.data;let html = template('tmp-table', dataList);$('tbody').html(html);renderPage(data.data.total);} else {layui.layer.msg(data.message);}}});}
文章列表筛选区域
定义分类可选项的 template 模板结构:
<script type="text/html" id="tmp-cate-list"><option value="">所有分类</option>{{each}}<option value={{$value.id}}>{{$value.name}}</option>{{/each}}</script>
发起请求获取并渲染文章分类的下拉选择框:
// 获取分类列表数据getCateListData();function getCateListData() {$.get('my/artcate', function(data) {if (data.status === 0) {let html = template('tmp-cate-list', data.data);$('[name=cate_id]').html(html);// 通过 layui 重新渲染表单区域的UI结构layui.form.render();} else {layui.layer.msg(data.message);}});}
为筛选表单绑定 submit 事件,实现筛选的功能:
$('.layui-form').on('submit', function(e) {e.preventDefault();params.cate_id = $('[name=cate_id]').val();params.state = $('[name=state]').val();params.pagenum = 1;getListData();});
文章列表分页区域
定义渲染分页的 renderPage 方法,调用 laypage.render() 方法来渲染分页的结构,并在 getListData 中调用 renderPage 方法:
function renderPage(total) {//执行一个laypage实例layui.laypage.render({elem: 'articlePage' //注意,这是 ID,不用加 # 号,count: total //数据总数,从服务端得到,curr: params.pagenum // 起始页,limit: params.pagesize // 每页显示的条数,limits: [2, 3, 4, 5] // 每页条数的选择项,layout: ['count', 'limit', 'prev', 'page', 'next', 'skip'], jump: function(obj, first) { // 当分页被切换时触发,函数返回两个参数:obj(当前分页的所有选项值)、first(是否首次,一般用于初始加载的判断)if (!first) {params.pagenum = obj.curr;params.pagesize = obj.limit;getListData();}}});}
删除文章
通过代理的形式,为删除按钮绑定点击事件处理函数:
// 事件委托绑定删除按钮事件$('tbody').on('click', '.btn-delete', function(e) {layer.confirm('是否删除?', {icon: 3, title:'提示'}, function(index){let articleid = $(e.target).attr('data-id');$.ajax({method: 'GET',url: 'my/deletearticle/' + articleid,success: function(data) {if (data.status === 0) {layui.layer.msg('文章删除成功!');let i = $(e.target).attr('data-index');console.log('index: ', i);if (i === '0' && params.pagenum > 1) {params.pagenum = params.pagenum - 1;}console.log('params: ', params);getListData();layer.close(index);} else {layui.layer.msg(data.message);}}});});});
发布文章
使用 layui
绘制页面,具体实现见代码……
渲染文章类别对应的下拉选择框结构与文章列表页类似,具体实现见代码……
富文本编辑器
添加如下的 layui 表单行:
<div class="layui-form-item"><!-- 左侧的 label --><label class="layui-form-label">文章内容</label><!-- 为富文本编辑器外部的容器设置高度 --><div class="layui-input-block" style="height: 400px;"><!-- 重要:将来这个 textarea 会被初始化为富文本编辑器 --><textarea name="content"></textarea></div></div>
导入富文本必须的 script 脚本:
<script src="../assets/lib/tinymce/tinymce.min.js"></script><script src="../assets/lib/tinymce/tinymce_setup.js"></script>
调用 initEditor() 方法,初始化富文本编辑器:
// 初始化富文本编辑器initEditor()
图片封面
图片封面的实现与更换头像类似,具体实现见代码……
发布文章的实现
定义文章的发布状态,为存为草稿按钮,绑定点击事件处理函数::
let state = '已发布';$('#btnSave2').on('click', function() {state = '草稿';});
为表单绑定 submit 提交事件,将裁剪后的封面追加到FormData对象中,发起Ajax请求实现发布文章的功能:
$('.layui-form').on('submit', function(e) {e.preventDefault();let fm = new FormData(this);fm.append('state', state);// 遍历 FormDatafm.forEach((v, k) => {console.log(k, '-', v);});$image.cropper('getCroppedCanvas', { // 创建一个 Canvas 画布width: 400,height: 280}).toBlob(function(blob) {fm.append('cover_img', blob);$.ajax({method: 'POST',url: 'my/addArticle',data: fm,// 注意:如果向服务器提交的是 FormData 格式的数据,必须添加以下两个配置项// 不修改 Content-Type 属性,使用 FormData 默认的 Content-Type 值 contentType: false,// 不对 FormData 中的数据进行 url 编码,而是将 FormData 数据原样发送到服务器 processData: false,success: function(data) {if (data.status === 0) {layui.layer.msg('文章发表成功!');// 发布文章成功后,跳转到文章列表页面location.href = '/project/article/list.html';} else {layui.layer.msg(data.message);}}});});});
(补充)编辑文章
编辑文章与发布文章十分相似,主要区别为一下两点:
- 需要使用初始数据填充文章编辑表单
- 提交表单的接口与参数不同
基于上述分析的两点,使用发布文章的页面做下细微改动来实现文章编辑功能。
在 iframe 框架父页面的 index.js 中添加 editArticle 属性,绑定发布文章按钮将 editArticle 的值设置为null:
// 编辑的文章数据var editArticle = null;$('#publish').click(function() {editArticle = null;});
通过代理的形式,为文章列表页面中的编辑按钮绑定点击事件处理函数,取出文章数据,存到父页面的 editArticle 属性中,并跳转到发布文章页面:
$('tbody').on('click', '.btn-edit', function(e) {let index = $(this).attr('data-index');let data = dataList[index];window.parent.editArticle = data;location.href = '/project/article/publish.html';});
如此在发布文章的 publish.js 中就可以通过判断 window.parent.editArticle 的值是否为 null 确定是编辑文章还是发布文章。
修改 publish.js 在编辑文章时,使用初始数据填充表单:
// 填充初始数据// 通过 window.parent 调用父框架 属性、方法 必须在http协议中打开let article = window.parent.editArticle;if (article !== null) {layui.form.val('publish', window.parent.editArticle);}
修改 publish.js 在编辑文章时,使用文章封面创建封面裁剪区:
// 创建裁剪区域 let path = null;if (window.parent.editArticle === null) {path = '../assets/images/sample2.jpg';} else {path = 'http://localhost' + window.parent.editArticle.cover_img;}$image.attr('src', path).cropper(options);
修改 publish.js 在编辑文章时,根据发表文章还是编辑文章使用不同的参数和接口:
$('.layui-form').on('submit', function(e) {e.preventDefault();let fm = new FormData(this);fm.append('state', state);if (window.parent.editArticle !== null) {fm.append('id', window.parent.editArticle.id);}// 遍历 FormDatafm.forEach((v, k) => {console.log(k, '-', v);});$image.cropper('getCroppedCanvas', { // 创建一个 Canvas 画布width: 400,height: 280}).toBlob(function(blob) {fm.append('cover_img', blob);$.ajax({method: 'POST',url: window.parent.editArticle === null ? 'my/addArticle' : 'my/updateArticle',data: fm,// 注意:如果向服务器提交的是 FormData 格式的数据,必须添加以下两个配置项// 不修改 Content-Type 属性,使用 FormData 默认的 Content-Type 值 contentType: false,// 不对 FormData 中的数据进行 url 编码,而是将 FormData 数据原样发送到服务器 processData: false,success: function(data) {if (data.status === 0) {layui.layer.msg('文章发表成功!');// 发布文章成功后,跳转到文章列表页面location.href = '/project/article/list.html';} else {layui.layer.msg(data.message);}}});});});