代码下载
完善服务器
对于上一篇博客开发的服务端项目,还需要增加文章列表查询
,文章删除
,文章更新
三个接口。
文章列表查询接口
验证表单数据
在路径 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;
// 查询文章的sql
let sql;
// 查询文章数量的sql
let 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) {
// 缓存token
localStorage.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);
// 清除 token
localStorage.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);
// 遍历 FormData
fm.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);
}
// 遍历 FormData
fm.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);
}
}
});
});
});