前后端交互—使用自己的服务器开发项目

代码下载

完善服务器

对于上一篇博客开发的服务端项目,还需要增加文章列表查询文章删除文章更新三个接口。

文章列表查询接口

验证表单数据

在路径 schema/user.js 中增加 articles 验证表单数据,其中使用的 .allow('') 表示允许空字符串:

    articles: {query: {pagenum,pagesize,cate_id: joi.number().integer().min(1).allow('').optional(),state: joi.string().valid('已发布', '草稿').allow('').optional()}},
处理函数
  1. 文章查询这里是根据 cate_idstate 进行的多表条件查询,对相应参数进行有效判断而给出合适的 sql 查询语句。
  2. 数量查询通过 select count (*/字段) from 表名 来进行。
  3. 分页查询通过 select * from 表名 limit 数值1, 数值2 来进行,其中 数值1 表示跳过前面多少条数据,数值2 表示取多少条数据。
  4. 排序通过 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 ServerExpress这两个插件都可以做到。

Live Server

  1. 在插件市场,搜索 Live Server 并安装
  2. 在页面上鼠标右键,选择 Open With Live Server 即可快速使用 http 协议访问页面,点击下方 Port 按钮可以关闭

Express

由于 Live Server 在页面保存之后会自动刷新整个页面,在不需要自动刷新的时候就不是很方便了,这时 Express 就会比较合适。

  1. 在插件市场,搜索 Express 并安装
  2. 打开命令面板(F1,或者Windows和Linux上的Ctrl+Shift+P,或者OSX上的Shift+CMD+P)
  3. 输入或选择 “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(`欢迎&nbsp;&nbsp;${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);}}});});});

(补充)编辑文章

编辑文章与发布文章十分相似,主要区别为一下两点:

  1. 需要使用初始数据填充文章编辑表单
  2. 提交表单的接口与参数不同

基于上述分析的两点,使用发布文章的页面做下细微改动来实现文章编辑功能。

在 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);}}});});});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/651104.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【misc | CTF】攻防世界 适合作为桌面

天命&#xff1a;这题还挺繁琐的&#xff0c;知识点还不少 目录 步骤1&#xff1a;图片隐写 步骤2&#xff1a;Winhex查看ascii码 步骤1&#xff1a;图片隐写 拿到这张图片&#xff0c;不可能扔进ps会有多图层&#xff0c;普通图片也就一个图层而已 但居然可以有隐写图片这…

【C语言】深入理解指针(3)数组名与函数传参

正文开始——数组与指针是紧密联系的 &#xff08;一&#xff09;数组名的理解 &#xff08;1&#xff09;数组名是数组首元素的地址 int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *parr &arr[0]; 上述代码通过&arr[0] 的方式得到了数组第一个元素的地址&#xff0c;…

绿色制造的行业标杆OEKO-TEX STeP认证

STeP是“OEKO-TEX国际环保纺织协会”推出的一个独立的第三方认证体系。STeP全称Sustainable Textile & Leather Production&#xff08;可持续纺织和皮革生产&#xff09;&#xff0c;是面向纺织和皮革供应链中环保且负有社会责任的生产工厂推出的透明认证体系。 STeP认证的…

【Mybatis plus】使用分页查询,报错 Parameter ‘xxx‘ not found. Available parameters are xxx

文章目录 0 先给出错误场景java entity 实体类java mapper 接口方法Java mapper 所对应的 mapper.xml 信息异常信息 1 解决办法step1: 给 mapper 接口方法加上具名参数指定&#xff0c;如下&#xff1a;step2: 修改 mapper.xml 的查询信息&#xff0c;都加上 具名 限定 2 异常原…

快快销ShopMatrix 分销商城多端uniapp可编译5端-代理商收益管理:差价奖励和销售额统计

代理商收益管理是一种针对代理商的利润分配模式&#xff0c;主要通过差价奖励和销售额统计来实现。这种模式的核心思想是通过激励代理商的销售行为&#xff0c;提高代理商的积极性和销售效率&#xff0c;从而实现整个销售网络的增长。 差价奖励是代理商收益管理中的一种常见方…

电商系统设计到开发03 引入Kafka异步削峰

一、前言 系统设计&#xff1a;电商系统设计到开发01 第一版设计到编码-CSDN博客 接着上篇文章&#xff1a;电商系统设计到开发02 单机性能压测-CSDN博客 本篇为大制作&#xff0c;内容有点多&#xff0c;也比较干货&#xff0c;希望可以耐心看看 已经开发的代码&#xff0…

配置ARP安全综合功能示例

组网图形 ARP安全简介 ARP&#xff08;Address Resolution Protocol&#xff09;安全是针对ARP攻击的一种安全特性&#xff0c;它通过一系列对ARP表项学习和ARP报文处理的限制、检查等措施来保证网络设备的安全性。ARP安全特性不仅能够防范针对ARP协议的攻击&#xff0c;还可以…

uniapp点击事件报错 Cannot read property ‘stopPropagation‘ of undefined

问题产生&#xff1a;在列表上有个小按钮&#xff0c;可点击弹出选择框。 列表本身可点击进入详情页。所以想用click.stop来阻止点击小按钮时候&#xff0c;触发列表的点击事件。 结果&#xff1a;如图所示 解决方案&#xff1a;发现自己用的是icon&#xff0c;在icon上加click…

2. HarmonyOS 应用开发 DevEco Studio 准备-2

2. HarmonyOS 应用开发 DevEco Studio 准备-2 首选项设置 中文设置 主题 字体 插件安装和使用 保存时操作 编辑器 工程树管理 代码树管理 标记 字符串可视化编辑 参考文档 常用快捷键 编辑 查找或替换 编译与运行 调试 其他 预览 页面预览 自定义组件预览 预览…

[学习笔记] ONNX 基础知识

1. ONNX 简介 1.1 什么是 ONNX 开放神经网络交换 ONNX&#xff08;Open Neural Network Exchange&#xff09;是一套表示深度神经网络模型的开放格式&#xff0c;由微软和 Facebook 于 2017 推出&#xff0c;然后迅速得到了各大厂商和框架的支持。通过短短几年的发展&#xf…

文档 OCR 识别优化为异步思路逻辑

文档 OCR 识别优化 同步处理&#xff08;原逻辑&#xff09; 当前系统识别文档为同步处理&#xff0c;已调整过 python 服务部分参数&#xff0c;但 一份40M左右文档识别仍需要几十秒 文档转为图片集合for 循环中一直调用 ocr 识别 异步处理 nginx 增加 requestId header …

系统架构17 - 软件工程(5)

软件工程 系统测试测试原则测试方法静态测试动态测试黑盒测试白盒测试灰盒测试自动化测试 系统测试 软件测试是使用人工或自动的手段来运行或测定某个软件系统的过程&#xff0c;其目的在于检验它是否满足规定的需求或弄清预期结果与实际结果之间的差别。 软件测试方法的分类有…

C++大学教程(第九版)7.19 将7.10节vector对象的例子转换成array对象

文章目录 题目代码运行截图 题目 (将7.10节vector 对象的例子转换成array 对象)将图7.26中 vector 对象的例子转换成使用array 对象。请消除任何 vector 对象仅有的特性。 分析&#xff1a; vector对象独有的特性&#xff1a; 1.vector对象长度可变 2.长度不同的vector对象可…

查看php-fpm占用内存情况

1、查看每个php-fpm占用的内存大小 ps -ylC php-fpm --sort:rss 2 查看单个php-fpm进程消耗内存的明细 pmap $(pgrep php-fpm) | less pmap pmap命令用于显示一个或多个进程的内存状态 pmap [ -x | -d ] [ -q ] pids 参数&#xff1a; -x extended Show the extended f…

蓝桥小白赛4 乘飞机 抽屉原理 枚举

&#x1f468;‍&#x1f3eb; 乘飞机 &#x1f437; 抽屉原理 import java.util.Scanner;public class Main {static int N 100010;static int[] a new int[N];public static void main(String[] args){Scanner sc new Scanner(System.in);int n sc.nextInt();int q s…

【Godot4自学手册】第七节背景搭建

各位同学&#xff0c;今天是第七节&#xff0c;在本节我会学习如何使用TileMap来完成背景搭建。 一、添加TileMap结点 先做个介绍&#xff0c;TileMap是基于 2D 图块的地图节点。Tilemap&#xff08;图块地图&#xff09;使用 TileSet&#xff0c;其中包含了图块的列表&#…

dvwa靶场文件上传high

dvwa upload high 第一次尝试&#xff08;查看是否是前端验证&#xff09;第二次尝试我的上传思路最后发现是图片码上传修改配置文件尝试蚁&#x1f5e1;连接菜刀连接 第一次尝试&#xff08;查看是否是前端验证&#xff09; 因为我是初学者&#xff0c;所以无法从代码审计角度…

JSON 字符串转换 —— Map、List

目录 转 Map maven 依赖 转换 转 List maven依赖 转换 转 Map JSON格式为 {key:value,key2:value2,......} maven 依赖 <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.28</version>…

三元运算符是什么?怎么用?

三元表达式又叫三元运算符&#xff0c;它必须要有三个操作数参与的运算 操作符号&#xff1a;&#xff1f; 表达式&#xff1a;在参与js程序时&#xff0c;都必须先计算出表达式结果&#xff0c;才能参与后续程序 语法&#xff1a;布尔表达式&#xff1f;true&#xff1a;fal…

Day34- 动态规划part02

一、不同路径 题目一&#xff1a;62. 不同路径 62. 不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish”…