大事件项目分享---实现后台操作交互---后台页面数据的增删改查
项目介绍:
实现的页面效果:
后台管理页面:
以上的静态页面,通过 HTML结构 和 CSS样式 实现 而页面的交互由 JS 来实现
练习项目的目标:
-
(1)前端三层分离的编程思想
-
-
(1)独立小功能需求分析->页面需求分析->整个项目需求分析
-
-
(1)项目技术难点的分析与解决方案
-
回顾模板引擎实现数据的动态渲染
掌握token实现状态保持的前台页面实现过程
进一步的掌握和体会前台页面的交互,理解前台页面交互的处理流程
项目之前需要使用数据可视化软件 naVicat 和 mysql数据库 将以存储的数据导入和服务器的开启
打开大事件项目文件夹 >> 找到大事件接口文件夹 >> 找到BigEventServers文件夹 >> 找到config 下的文件 index.js 打开 根据文件内容
根据以上内容,在Navicat 中新建一个名字叫 'bignews' 的数据库 >> 然后在BigEventServers文件夹中找到 reset 文件打开 >> 在打开的该目录下打开小黑窗 >> 输入命令 node index.js 出现'搞定'两个字即数据导入成功.
打开服务器: 在BigEventServers文件夹目录下 打开小黑窗 输入命令 app.js 运行 出现: 开启成功: http://localhost:8080 即服务器打开成功.
1 <!-- 模态框做为body的直接子元素 --> 2 <div class="modal fade" id="myModal" tabindex="-1" role="dialog"> 3 <div class="modal-dialog" role="document"> 4 <div class="modal-content"> 5 <div class="modal-header"> 6 <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span 7 aria-hidden="true">×</span></button> 8 <h4 class="modal-title">友情提示</h4> 9 </div> 10 <div class="modal-body"> 11 <p class="logininfo">One fine body…</p> 12 </div> 13 <div class="modal-footer"> 14 <button type="button" class="btn btn-primary btnconfirm">确定</button> 15 </div> 16 </div> 17 </div> 18 </div>
注意点: 模态框的整体结构要直接做 body 的子元素, 如果嵌套在其他HTML结构中会出现模态框无法点击使用
1 (function (w) { 2 var baseURL = 'http://127.0.0.1:8080/api/v1' 3 w.itcast = { 4 // 定义基准路径,所谓基准路径就是资源url前面那一坨 5 baseURL: baseURL,//基地址 6 user_login: baseURL + '/admin/user/login',//用户登录 7 user_info: baseURL + '/admin/user/info',//用户信息 8 user_detail: baseURL + '/admin/user/detail',//用户详情 9 user_edit: baseURL + '/admin/user/edit',//用户编辑 10 category_list: baseURL + '/admin/category/list',//文章类别查询 11 category_add: baseURL + '/admin/category/add',//文章类别新增 12 category_search: baseURL + '/admin/category/search',//文章类别搜索 13 category_edit: baseURL + '/admin/category/edit',//文章类别编辑 14 category_delete: baseURL + '/admin/category/delete',//文章类别删除 15 article_query: baseURL + '/admin/article/query',//文章搜索 16 article_publish: baseURL + '/admin/article/publish',//文章发布 17 article_search: baseURL + '/admin/article/search',//文章信息查询 18 article_edit: baseURL + '/admin/article/edit',//文章编辑 19 article_delete: baseURL + '/admin/article/delete',//文章删除 20 comment_search: baseURL + '/admin/comment/search',//文章评论列表 21 comment_pass: baseURL + '/admin/comment/pass',//文章评论通过 22 comment_reject: baseURL + '/admin/comment/reject',//文章评论不通过 23 comment_delete: baseURL + '/admin/comment/delete',//文章评论删除 24 } 25 })(window)
后台登录页面
实现点击登录按钮, 登录到后台首页页面
1 // 实现登陆 2 // 1、用户登录 3 // 请求地址:/admin/user/login 4 // 请求方式:post 5 $('.input_sub').on('click', function () { 6 // serialize:获取获取指定表单中拥有name属性的表单元素的value值 7 console.log($('.login_form').serialize()) 8 $.ajax({ 9 type: 'post', 10 url: itcast.user_login, 11 data: $('.login_form').serialize(), 12 dataType: 'json', 13 success: function (res) { 14 console.log(res) 15 $('.logininfo').text(res.msg) 16 if (res.code == 200) { 17 // 将后台返回的token数据存储到本地 18 localStorage.setItem('bignews_token_58', res.token) 19 $('#myModal').modal('show') 20 // hidden.bs.modal:在当前模态框被隐藏之后触发 21 $('#myModal').on('hidden.bs.modal', function (e) { 22 location.href = './index.html' 23 }) 24 } else { 25 // alert(res.msg) 26 $('#myModal').modal('show') 27 } 28 } 29 }) 30 }) 31 32 // 单击模态框中的确定,隐藏模态框 33 $('.btnconfirm').on('click', function () { 34 $('#myModal').modal('hide') 35 })
因为后续的页面的操作都需要权限操作 , 即如果用户登录成功了才可以在后续的页面中使用增删改查的权限 , 那就需要给页面传一个tokan令牌,证明用户已经登录过了,即可继续操作页面,
因为后续的页面有很多请求, 如果一个个去加既麻烦还冗余, 这时候就需要一个全局函数来解决这种
该函数可以写在页面引入的 jQuery文件 的 最后 不要加在中间或其他地方,容易更改了原代码的结构
1 // 这里添加ajax的全局函数的配置 2 $.ajaxSetup({ 3 // 每个请求发送之前都会经过beforeSend,可以在这个回调进行请求头的统一设置 4 beforeSend: function (xhr) { 5 let token = localStorage.getItem('bignews_token_58') 6 if (token) { 7 xhr.setRequestHeader('Authorization', token) 8 } 9 }, 10 // 添加统一的错误处理 11 error: function (xhr, status, error) { 12 console.log(xhr, status, error) 13 if (error == 'Forbidden') {//用户未登录 14 alert('请先登录!') 15 window.location.href = './login.html';//跳转登陆页面 16 }; 17 } 18 })
注意事项:
在前端页面结构中如果按钮的类型为: 'submit' 会有一个默认行为,会影响到后面的操作, 所以将登陆按钮的type属性从submit修改为button
参数获取不到,我们需要人为设置表单元素的name, 因为 serialize() 的方法会获取到表单域中带有name属性的元素的表单值 name值一定要参照后台接口
token的本地存储和传递
在登陆成功之后将token存储到本地
后续请求传递token
为了简化操作,使用$.ajaxSetup进行统一的处理
// 将后台返回的token数据存储到本地 localStorage.setItem('bignews_token_58', res.token)
首页
用户信息的动态渲染
首页主体内容页面的展示
左侧菜单的功能处理
顶部栏的个人中心和退出
// 动态渲染用户信息 // 2、获取用户信息 // 请求地址:/admin/user/info // 请求方式:get $.ajax({ url: allSite.user_info, dataType: 'json', // beforeSend: function (xhr) { // xhr.setRequestHeader('Authorization', localStorage.getItem('token_58')) // }, success: function (res) { console.log(res); if (res.code == 200) { $('.user_info>img').attr('src', res.data.userPic) $('.user_info>span').html(`欢迎 ${res.data.nickname}`) $('.user_center_link>img').attr('src', res.data.userPic) } } })
首页主体内容页面的展示
使用方式
2.设置超链接的target属性的值为iframe的name属性的 值
<!-- 右侧主体内容 --> <div class="main" id="main_body"> <!-- 添加一个浮动框架 --> <iframe class="myframe" src="./main_count.html" frameborder="0" name='main_frame'></iframe> </div>
1 //主页左侧导航栏功能实现 2 $('.level01').on('click', function () { 3 $(this).addClass('active').siblings().removeClass('active') 4 5 if ($(this).next().hasClass('level02')) { 6 $(this).next().slideToggle() 7 $(this).find('b').toggleClass('rotate0') 8 } else { 9 $('.level02').slideUp() 10 $('.level02>li').removeClass('active') 11 } 12 }) 13 14 $('.level02>li').on('click', function () { 15 $(this).addClass('active').siblings().removeClass('active') 16 })
后台首页顶部的个人中心功能:
效果: 实现点击个人中心跳转到个人中心页面,并给左侧的导航栏的个人中心导航选项添加样式
<a href="./user.html" target="main_frame" onclick='$("#user").click()'>个人中心</a>
后台首页顶部的退出功能:
效果: 点击退出 实现删除之前存储的token 并且跳转到登录页面
//点击退出按钮实现删除token令牌,跳转到登录页面 $('.logout').on('click', function () { localStorage.removeItem('token_58') window.location.href = './login.html' })
文章列表页
主要实现功能:
- 页面数据的渲染,让后台文章数据动态渲染到页面中
- 数据筛选功能, 选择文章类型和状态,点击筛选, 使页面数据根据选项动态渲染
- 分页插件, 在列表页下方生成一个分页结构,使用户点击对应键,跳转到对应的页面
- 实现发表文章 和 编辑文章 的页面跳转
页面数据的渲染
使用模板引擎动态渲染页面会更简便
1 <!-- 页面需要先引入模板引擎js文件 --> 2 <script src="./libs/template-web.js"></script> 3 ----------------------------------------------------- 4 5 <!-- 模板结构 --> 6 <script type="text/template" id="articleListTemp"> 7 {{each data}} 8 <tr> 9 <td>{{$value.title}}</td> 10 <td>{{$value.author}}</td> 11 <td>{{$value.category}}</td> 12 <td class="text-center">{{$value.date}}</td> 13 <td class="text-center">{{$value.state}}</td> 14 <td class="text-center"> 15 <a href="article_edit.html" class="btn btn-default btn-xs">编辑</a> 16 <a href="javascript:void(0);" class="btn btn-danger btn-xs delete">删除</a> 17 </td> 18 </tr> 19 {{/each}} 20 </script>
实现动态渲染:
注意点: 在这里发起请求时 对应的传参需要根据服务器接口文档 所需的参数来传递
文档所示所有的值可以为空, 则有默认值
// 获取所有文章数据,实现文章数据的列表渲染
// 10、文章搜索
// 请求地址:/admin/article/query
// 请求方式:get
$.ajax({
url: itcast.article_query,
data: {
page: 1, // 当前页码
perpage: 10 // 每页展示的数量
},
dataType: 'json',
success: function (res) {
console.log(res)
if (res.code == 200) {
$('tbody').html(template('articleListTemp', res.data))
}
}
})
数据筛选功能
难点: 下拉列表的使用
-
如果没有设置value属性,那么后期获取到的select下拉列表的value属性值为option标签之间的值
-
如果设置了value属性,那么后期获取到的就是value属性所绑定的值
下拉列表的使用场景
-
展示固定选项 -- 根据后台需求和展示需要固定写死
-
渲染动态选项 -- 实现下拉列表的动态渲染,同时要根据数据业务的处理需求设置value属性
// 分类数据的动态渲染
// 5、所有文章类别 // 请求地址:/admin/category/list // 请求方式:get
url: itcast.category_list, dataType: 'json', success: function (res) { console.log(res) if (res.code == 200) { let str = `<option value="">所有分类</option>` for (let i = 0; i < res.data.length; i++) { str += `<option value='${res.data[i].id}'>${res.data[i].name}</option>` } $('#selCategory').html(str) } } })
这时候会返回的数据
数据的筛选
-
-
对于文章分类,它的数据是来自于后台数据库,所以需要动态渲染
- 对于后台的处理是:如果传入的分类或状态参数,就会拼接条件,否则不拼接条件,而查询所有数据
function init () { $.ajax({ url: itcast.article_query, data: { page: 1, // 当前页码 perpage: 10, // 每页展示的数量 type: $('#selCategory').val(), // 文章的分类,如果为''则查询所有分类的文章数据 state: $('#selStatus').val() // 文章的状态,如果为''则查询所有状态的文章数据 }, dataType: 'json', success: function (res) { console.log(res) if (res.code == 200) { $('tbody').html(template('articleListTemp', res.data)) } } }) }
// 实现数据筛选 $('#btnSearch').on('click', function (e) { e.preventDefault() page = 1 init() })
分页插件 实现分页结构
当数据量比较大的时候,不适合在一页展示完所有数据的时候,就应该进行分页
需要用到分页插件 在Bootstrap官网中就提供了这种分页插件
bootstrapMajorVersion 需要根据版本传入对应的值
页面需要引入的文件:
<link rel="stylesheet" href="./lib/bootstrap.css"> <!--使用bootstrap插件必须使用引入jquery,因为bootstrap是基于jquery开发的--> <script src="./lib/jquery-2.1.1.min.js"></script> <!--bootstrap插件--> <script src="./lib/bootstrap.js"></script> <!--分页插件--> <script src="./lib/bootstrap-paginator.js"></script>
实现分页结构
//实现分页结构的显示 /** * * @param pageCurrent 当前所在页 因为有全局变量定义则不用传递 * @param pageSum 总页数 * @param callback 调用ajax 不需要触发 */ function setPage(pageSum) { $(".pagination").bootstrapPaginator({ //设置版本号 bootstrapMajorVersion: 3, // 显示第几页 currentPage: page, // 总页数 totalPages: pageSum, //当单击操作按钮的时候, 执行该函数, 调用ajax渲染页面 onPageClicked: function (event, originalEvent, type, getpage) { // getpage 为第四个参数,获取用户点击的页码数 console.log(getpage); //获取的页码赋值给全局变量 page = getpage; // 再重新渲染页面,跳转到对应页数 init() } }) }
打开发布文章页面就开始渲染分类数据
这里要注意: 在页面文件中要传入封装好的 http.js 文件 即 url 对应的地址
1 //打开发布文章,渲染页面的文章类别 2 $.ajax({ 3 // 所有文章类别 4 // 请求地址:/ admin / category / list 5 // 请求方式:get 6 url: allSite.category_list, 7 dataType: 'json', 8 success: function (res) { 9 // console.log(res); 10 let str = '' 11 for (let index = 0; index < res.data.length; index++) { 12 str += `<option value="${res.data[index].id}">${res.data[index].name}</option>` 13 } 14 $('.category').html(str) 15 } 16 })
实现发布文章页面的文件预览
注意事项,因为使用的是formdata 方式上传 即用户选择好图片时就实现预览但不上传文件, 这时候就需要本地预览
URL.creatObjectUrl:它可以将指定的资源托管到内置临时服务器
1 /*2.文件预览 */ 2 //1.给file表单元素注册onchange事件 3 $('#inputCover').change(function () { 4 //1.2 获取用户选择的图片 通过URL的createObjectUrl获取托管在服务器端的资源路径 5 var file = $('#inputCover')[0].files[0]; 6 //1.3 将文件转为src路径 7 var url = window.URL.createObjectURL(file); 8 //1.4 将url路径赋值给img标签的src 9 $('.article_cover').attr('src', url); 10 });
实现发布文章页面的时间功能
需要一个日期插件 在页面文件中需要引入css 和 js 在页面结构中还需要一个载体 建议使用文本框
引入文件
<link rel="stylesheet" href="./libs/jedate/css/jedate.css"> <script src="./libs/jedate/js/jedate.js"></script>
添加载体
<div class="form-group"> <label class="col-sm-2 control-label">发布时间:</label> <div class="col-sm-4">
--------添加一个文本框载体--------------- <input type="text" class="jeinput" id="testico" placeholder="YYYY-MM-DD" name='date'> </div> </div>
中有两个参数, 第一个参数传入载体, 第二参数传的是一个对象,用于定制日期插件的功能样式, 可以不传 则有默认值,但样式会很丑.
jeDate插件获取日期值 用value方式就可以获取
//加入日期插件 $(function () {
jeDate("#testico", { trigger: "click", format: "YYYY-MM-DD", theme: { bgcolor: "#D91600", pnColor: "#FF6653" }, isinitVal: true }) });
实现发布文章页面
<script src="./libs/tinymce/tinymce.min.js"></script>
添加载体
<div class="form-group"> <label for="inputEmail3" class="col-sm-2 control-label">文章内容:</label> <div class="col-sm-10">
------------------添加载体------------
<textarea id="mytextarea"></textarea> </div> </div>
在页面的js文件中 进行插件的初始化
注意事项:
- 富文本框插件会在载体上方添加一个新的结构,用户的数据是输入在这个新结构中,意味着我们无法从文本框载体中获取数据
- 富文本框的内容是网页结构,因为它要记录用户输入内容的格式,而html可以描述这种结构
//加入富文本框插件 tinymce.init({ selector: '#mytextarea', height: '350px', language: 'zh_CN', directionality: 'ltl', browser_spellcheck: true, contextmenu: false, plugins: [ "advlist autolink lists link image charmap print preview anchor", "searchreplace visualblocks code fullscreen", "insertdatetime media table contextmenu paste imagetools wordcount", "code" ], toolbar: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | code", });
获取插件的内容的方法: tinymce.activeEditor.getContent()
再将数据添加到 formdata 中
let formdata = new FormData($('#form')[0]) // console.log(tinymce.activeEditor.getContent()) // 将富文本框的内容添加到formdata formdata.append('content', tinymce.activeEditor.getContent())
实现文章的新增
下面是完整的实现代码 其中没有 分类的动态渲染和插件的使用 的代码 是因为后期还需要复用到 所以封装到一个js文件中去引入使用 则在页面HTML文件中引入即可,下方有详细代码
注意事项:
- 使用formdata方式获取数据时 会少了 富文本框的数据 和 文章状态的数据 就需要主动添加进去
- 发布文章的图片一定要上传, 因为后台接口是固定需要不可以为空 ,不传递会报400的错误 即参数不正确
- 图片的格式支持jpg,png,gif,扩展名要求小写
1 $(function () { 2 /*2.文件预览 */ 3 //1.给file表单元素注册onchange事件 4 $('#inputCover').change(function () { 5 //1.2 获取用户选择的图片 6 var file = $('#inputCover')[0].files[0]; 7 //1.3 将文件转为src路径 通过URL的createObjectUrl获取托管在服务器端的资源路径 8 var url = window.URL.createObjectURL(file); 9 //1.4 将url路径赋值给img标签的src 10 $('.article_cover').attr('src', url); 11 }); 12 //点击发布按钮,获取用户数据 13 // 11、发布文章 14 // 请求地址:/admin/article / publish 15 // 请求方式:post 16 // 请求参数:通过formData提交 17 // 由于发布按钮和草稿按钮业务逻辑相同, 18 // 只是state参数不同,可以封装一个函数,将state作为参数传递 19 $('.btn-release').on('click', function (e) { 20 //阻止默认行为 21 e.preventDefault() 22 uploadingData('已发布') 23 }) 24 $('.btn-draft').on('click', function (e) { 25 //阻止默认行为 26 e.preventDefault() 27 uploadingData('草稿') 28 }) 29 30 function uploadingData(state) { 31 //创建FormData 32 let formdata = new FormData($('#form')[0]) 33 //tinymce.activeEditor.getContent()获取富文本框的文本内容 获取的数据是以 HTML格式显示 ,后期渲染时需要使用html方法 34 formdata.append('content', tinymce.activeEditor.getContent()) 35 //追加文章状态 36 formdata.append('state', state) 37 console.log(...formdata); 38 //实现文章发布 39 $.ajax({ 40 type: 'post', 41 url: allSite.article_publish, 42 data: formdata, 43 dataType: 'json', 44 contentType: false, 45 processData: false, 46 success: function (res) { 47 console.log(res); 48 if (res.code == 200) { 49 alert(res.msg) 50 //返回文章列表页 ,给左侧列表页导航栏项添加样式 51 $('.level02>li:eq(0)', window.parent.document).addClass('active').siblings().removeClass('active'); 52 window.location.href = './article_list.html' 53 } 54 } 55 }) 56 } 57 58 })
公共代码 comment.js
1 $(function () { 2 //打开发布文章,渲染页面的文章类别 3 $.ajax({ 4 // 所有文章类别 5 // 请求地址:/ admin / category / list 6 // 请求方式:get 7 url: allSite.category_list, 8 dataType: 'json', 9 success: function (res) { 10 // console.log(res); 11 let str = '' 12 for (let index = 0; index < res.data.length; index++) { 13 str += `<option value="${res.data[index].id}">${res.data[index].name}</option>` 14 } 15 $('.category').html(str) 16 } 17 }) 18 19 //加入日期插件 20 $(function () { 21 //或者为这样的 22 jeDate("#testico", { 23 trigger: "click", 24 format: "YYYY-MM-DD", 25 theme: { bgcolor: "#D91600", pnColor: "#FF6653" }, 26 isinitVal: true 27 }) 28 }); 29 30 //加入富文本框插件 31 tinymce.init({ 32 selector: '#mytextarea', 33 height: '350px', 34 language: 'zh_CN', 35 directionality: 'ltl', 36 browser_spellcheck: true, 37 contextmenu: false, 38 plugins: [ 39 "advlist autolink lists link image charmap print preview anchor", 40 "searchreplace visualblocks code fullscreen", 41 "insertdatetime media table contextmenu paste imagetools wordcount", 42 "code" 43 ], 44 toolbar: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | code", 45 }); 46 })
文章删除功能
注意事项:
- 在文章列表页面中 文章的数据时使用模板引擎动态渲染的 所有在绑定事件时 不可以直接绑定在元素上, 要通过结构中存在的父元素来绑定--事件委托的方式
- 删除事件需要根据id来进行删除 而结构中又没有id参数传递 这时候就需要自己添加 自定义属性 data 来存储id 再去获取id
在模板结构的删除结构 添加自定义属性
1 <script type="text/template" id="articleListTemp"> 2 {{each data}} 3 <tr> 4 <td>{{$value.title}}</td> 5 <td>{{$value.author}}</td> 6 <td>{{$value.category}}</td> 7 <td class="text-center">{{$value.date}}</td> 8 <td class="text-center">{{$value.state}}</td> 9 <td class="text-center"> 10 <a href="article_edit.html?id={{$value.id}}" class="btn btn-default btn-xs" id="compile_btn">编辑</a>
--------------添加自定义属性--------------------------- 11 <a href="javascript:void(0);" class="btn btn-danger btn-xs delete_btn" data-id='{{$value.id}}'>删除</a> 12 </td> 13 </tr> 14 {{/each}}
实现删除效果
注意获取页面传递的id
1 //实现删除文章功能 2 // 14、删除文章 3 // 请求地址:/admin/article / delete 4 // 请求方式:post 5 $('table>tbody').on('click', '.delete_btn', function () { 6 $.ajax({ 7 type: 'post', 8 url: allSite.article_delete, 9 dataType: 'json', 10 data: {
--------------获取页面传递的id------------- 11 id: $(this).attr('data-id') 12 }, 13 success: function (res) { 14 console.log(res); 15 if (res.code == 204) { 16 alert(res.msg) 17 location.reload(); 18 } else { 19 alert(res.msg) 20 } 21 } 22 }) 23 })
文章编辑功能
用户点击编辑, 跳转到编辑页面 根据后台数据渲染 编辑文章页面
渲染页面注意事项:
- 在编辑跳转时要传递id
- 在编辑页接收参数id
- 根据id查询文章详情数据
- dom赋值
实现数据的默认展示
传递id
<script type="text/template" id="articleListTemp"> {{each data}} <tr> <td>{{$value.title}}</td> <td>{{$value.author}}</td> <td>{{$value.category}}</td> <td class="text-center">{{$value.date}}</td> <td class="text-center">{{$value.state}}</td> <td class="text-center">
-------------添加?id={{$value.id}} 用于传递id-----------------------
<a href="article_edit.html?id={{$value.id}}" class="btn btn-default btn-xs" id="compile_btn">编辑</a> <a href="javascript:void(0);" class="btn btn-danger btn-xs delete_btn" data-id='{{$value.id}}'>删除</a> </td> </tr> {{/each}}
在编辑页面获取id
使用 itcast.getParameter 需要在页面文件中引入一个js文件 用于把传递的参数结构为 key=value&key=value&key=value.... 转换为对象
//1.获取artile_list页面传递过来的文章id let id = itcast.getParameter(location.search).id // console.log(id);
引入的文件结构 可以创建文件再复制粘贴代码使用
1 var itcast = { 2 getParameter: function (str) { // ?id=7&name=jack 3 // 删除? 4 str = str.replace('?', '') // id=7&name=jack 5 // 分割字符串 6 var arr = str.split('&') // ["id=7","name=jack"] 7 // 循环遍历再次分割 8 var obj = {} 9 for (var i = 0; i < arr.length; i++) { // 1.id=7 10 var temp = arr[i].split('=') // ["id",7] 11 // 将数据添加到对象 12 obj[temp[0]] = temp[1] // {id:7} 13 } 14 return obj 15 } 16 }
- 富文本框的赋值 插件默认会将文本域的内容同步到富文本框插件,当插件加载完毕之后同步
1 //根据id获取文章信息 2 // 12、根据id获取文章信息 3 // 请求地址:/admin/article / search 4 // 请求方式:get 5 6 //1.获取artile_list页面传递过来的文章id 7 let id = itcast.getParameter(location.search).id 8 // console.log(id); 9 $.ajax({ 10 url: allSite.article_search, 11 data: { id }, 12 dataType: 'json', 13 success: function (res) { 14 // console.log(res); 15 if (res.code == 200) { 16 $('#inputTitle').val(res.data.title) 17 $('.article_cover').attr('src', res.data.cover)
// settimeout中的代码,会在其它所有代码执行完毕之后再执行 18 setTimeout(function () { 19 $('select.category').val(res.data.categoryId) 20 }, 0) 21 $('#testico').val(res.data.date)
//富文本框的赋值 插件默认会将文本域的内容同步到富文本框插件,当插件加载完毕之后同步
22 $('#mytextarea').val(res.data.content) 23 } 24 } 25 })
1 $(function () { 2 //根据id获取文章信息 3 // 12、根据id获取文章信息 4 // 请求地址:/admin/article / search 5 // 请求方式:get 6 //1.获取artile_list页面传递过来的文章id 7 let id = itcast.getParameter(location.search).id 8 // console.log(id); 9 $.ajax({ 10 url: allSite.article_search, 11 data: { id }, 12 dataType: 'json', 13 success: function (res) { 14 // console.log(res); 15 if (res.code == 200) { 16 $('#inputTitle').val(res.data.title) 17 $('.article_cover').attr('src', res.data.cover) 18 setTimeout(function () { 19 $('select.category').val(res.data.categoryId) 20 }, 0) 21 $('#testico').val(res.data.date) 22 $('#mytextarea').val(res.data.content) 23 } 24 } 25 }) 26 // 13、文章编辑 27 // 请求地址:/admin/article / edit 28 // 请求方式:post 29 $('.btn-edit').on('click', function (e) { 30 //阻止默认行为 31 e.preventDefault() 32 uploadingData('已发布') 33 }) 34 $('.btn-draft').on('click', function (e) { 35 //阻止默认行为 36 e.preventDefault() 37 uploadingData('草稿') 38 }) 39 40 function uploadingData(state) { 41 //创建FormData 42 let formdata = new FormData($('#form')[0]) 43 //tinymce.activeEditor.getContent()获取富文本框的文本内容 获取的数据是以 HTML格式显示 44 formdata.append('content', tinymce.activeEditor.getContent()) 45 //追加文章状态 46 formdata.append('state', state) 47 //追加id 48 formdata.append('id', id) 49 // console.log(...formdata); 50 //实现文章发布 51 $.ajax({ 52 type: 'post', 53 url: allSite.article_edit, 54 data: formdata, 55 dataType: 'json', 56 contentType: false, 57 processData: false, 58 success: function (res) { 59 console.log(res); 60 if (res.code == 200) { 61 alert('修改成功') 62 //返回文章列表页 ,给左侧列表页导航栏项添加样式 63 $('.level02>li:eq(0)', window.parent.document).addClass('active').siblings().removeClass('active'); 64 window.location.href = './article_list.html' 65 } 66 } 67 }) 68 } 69 /*2.文件预览 */ 70 //1.给file表单元素注册onchange事件 71 $('#inputCover').change(function () { 72 //1.2 获取用户选择的图片 73 var file = $('#inputCover')[0].files[0]; 74 //1.3 将文件转为src路径 75 var url = window.URL.createObjectURL(file); 76 //1.4 将url路径赋值给img标签的src 77 $('.article_cover').attr('src', url); 78 }); 79 })
<link rel="stylesheet" href="./libs/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="css/reset.css"> <link rel="stylesheet" href="css/iconfont.css"> <link rel="stylesheet" href="css/main.css"> <script src="./libs/jquery-1.12.4.min.js"></script> <script src="./libs/bootstrap/js/bootstrap.min.js"></script> <script src="./js/http.js"></script> <!-- 引入模板引擎js文件 --> <script src="./libs/template-web.js"></script> <script src="./js/article_category.js"></script>
1 <!-- 模板结构 --> 2 <script type="text/template" id="cataTemp"> 3 {{each data}} 4 <tr> 5 <td>{{$value.name}}</td> 6 <td>{{$value.slug}}</td> 7 <td class="text-center"> 8 <!-- 加入自定义属性 用于存储数据,为编辑所需的数据做准备 --> 9 <a href="javascript:void(0)" data-toggle="modal" class="btn btn-info btn-xs btnedit" data-id="{{$value.id}}" data-name="{{$value.name}}" data-slug="{{$value.slug}}">编辑</a> 10 <a href="javascript:void(0)" class="btn btn-danger btn-xs btndel" data-id="{{$value.id}}">删除</a> 11 </td> 12 </tr> 13 {{/each}} 14 </script>
// 5、所有文章类别 // 请求地址:/admin/category / list // 请求方式:get let paradata = [] let id //设置全局变量 用于获取当前编辑的分类数据的id function init() { $.ajax({ url: allSite.category_list, dataType: 'json', success: function (res) { // console.log(res); if (res.code == 200) { paradata = res.data $('tbody').html(template('cataTemp', res)) } } }) } init()
文章类别的新增
这里因为新增文章类别的信息特别的少,所以没有必要去做一个新页面来做新增,而可以选择使用 模态框 或者 在页面的某个位置单独的划出一块区域进行新增操作
-
可以使用js的方式--最终选择它来实现
-
1 <!-- 模态框的添加 --> 2 <div class="modal fade" tabindex="-1" role="dialog" id="myModal"> 3 <div class="modal-dialog" role="document"> 4 <div class="modal-content"> 5 <div class="modal-header"> 6 <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span 7 aria-hidden="true">×</span></button> 8 <h4 class="modal-title">新增分类</h4> 9 </div> 10 <div class="modal-body"> 11 <form action="" id="form"> 12 <div class="form-group"> 13 <label for="name">分类名称</label> 14 <input type="text" class="form-control" id="name" name="name" placeholder="分类名称"> 15 </div> 16 <div class="form-group"> 17 <label for="slug">分类别名</label> 18 <input type="text" class="form-control" id="slug" name="slug" placeholder="分类别名"> 19 </div> 20 </form> 21 </div> 22 <div class="modal-footer"> 23 <button type="button" class="btn btn-default" data-dismiss="modal">取消</button> 24 <button type="button" class="btn btn-primary btnset">新增</button> 25 </div> 26 </div> 27 </div> 28 </div>
-
获取到当前的所有分类数据
-
在用户输入失焦的时候验证用户是否存在,生成一个标记
-
-
在查询到所有的分类数据之后,将分类数据存储到变量,供后期的判断使用
-
在每次用户输入完值之后,使用之前存储的分类数据做为参照,查询当前用户的输入是否已存在
-
1 //验证用户输入 2 let flag = true //标记当前请求是否要发送 3 $('#name').on('blur', function () { 4 let value = $(this).val() 5 let temp = paradata.filter(v => { 6 return v.name == value 7 })[0] 8 if (temp) { 9 alert('分类名称重复,请重新输入') 10 flag = false 11 } else { 12 flag = true 13 } 14 }) 15 $('#slug').on('blur', function () { 16 let value = $(this).val() 17 let temp = paradata.filter(v => { 18 return v.slug == value 19 })[0] 20 if (temp) { 21 alert('分类别名重复,请重新输入') 22 flag = false 23 } else { 24 flag = true 25 } 26 })
-
-
如果只是普通的键值对那么就使用serialize
-
类别的编辑
编辑都需要完成两个功能
1.数据的默认展示
2.编辑功能
数据的默认展示
-
如果是单独的页面实现编辑,那么就需要传递参数,获取参数,查询数据,默认展示
-
页面跳转是get请求,get无法传递太多的数据,且传递数据的方式不安全
-
所以这种情况下一般传递id,到编辑页面进行数据的查询,再展示
-
-
如果是当前页面实现编辑操作(弹框,单独区域),一般来说编辑的数据比较的简单
-
由于没有跳转页面,那么意味着本质上没有必须进行参数的传递
-
所以一般情况下这种场景可以先存储数据,再获取数据进行展示
-
如果没有特别的需要,可以考虑使用自定义属性
-
<td class="text-center"> <!-- 加入自定义属性 用于存储数据,为编辑所需的数据做准备 --> <a href="javascript:void(0)" data-toggle="modal" class="btn btn-info btn-xs btnedit" data-id="{{$value.id}}" data-name="{{$value.name}}" data-slug="{{$value.slug}}">编辑</a> <a href="javascript:void(0)" class="btn btn-danger btn-xs btndel" data-id="{{$value.id}}">删除</a> </td>
在编辑事件中获取自定义属性,实现展示. 注意: 因为文章类别是使用模板结构动态渲染的, 要使用事件委托的方式来触发事件, 获取自定义属性 , 弹出模态框 ,wei元素赋值
编辑分类
-
编辑和新增是复用同一个模态框
- 编辑过后的新增,需要将数据,提示信息重置
-
可以根据文本内容或者id进行判断,决定是进行编辑还是新增
-
编辑和新增的业务是一样,可以进行业务的封装
1 $(function () { 2 // 5、所有文章类别 3 // 请求地址:/admin/category / list 4 // 请求方式:get 5 let paradata = [] 6 let id //设置全局变量 用于获取当前编辑的分类数据的id 7 function init() { 8 $.ajax({ 9 url: allSite.category_list, 10 dataType: 'json', 11 success: function (res) { 12 // console.log(res); 13 if (res.code == 200) { 14 paradata = res.data 15 $('tbody').html(template('cataTemp', res)) 16 } 17 } 18 }) 19 } 20 init() 21 22 //验证用户输入 23 let flag = true //标记当前请求是否要发送 24 $('#name').on('blur', function () { 25 let value = $(this).val() 26 let temp = paradata.filter(v => { 27 return v.name == value 28 })[0] 29 if (temp) { 30 alert('分类名称重复,请重新输入') 31 flag = false 32 } else { 33 flag = true 34 } 35 }) 36 $('#slug').on('blur', function () { 37 let value = $(this).val() 38 let temp = paradata.filter(v => { 39 return v.slug == value 40 })[0] 41 if (temp) { 42 alert('分类别名重复,请重新输入') 43 flag = false 44 } else { 45 flag = true 46 } 47 }) 48 // 6、新增文章类别 49 // 请求地址:/admin/category / add 50 // 请求方式:post 51 52 //封装函数 因为新增功能 和 编辑功能 都需要重新渲染页面数据 ,可以用函数封装,再传递参数 53 function operate(url, data) { 54 $.ajax({ 55 //ajax支持三种格式的参数传递 56 url: url, 57 dataType: 'json', 58 data: data, 59 type: 'post', 60 success: function (res) { 61 // console.log(res); 62 if (res.code == 201 || res.code == 200) { 63 alert(res.msg) 64 $('#myModal').modal('hide') 65 init() 66 } 67 } 68 }) 69 } 70 71 72 73 //点击新增按钮弹出模态框 74 $('#xinzengfenlei').on('click', function () { 75 //因为新增功能和编辑功能是共用一个模态框,所有模态框的值需要动态渲染,点击不同功能显示不同的文本信息 76 //新增的提示信息 77 $('.modal-title').text('新增分类') 78 $('.btnset').text('新增') 79 //弹出模态框 80 $('#myModal').modal('show') 81 //新增功能的输入框内容为空 82 $('#name').val('') 83 $('#slug').val('') 84 }) 85 86 //编辑功能时模态框展示的数据 87 $('tbody').on('click', '.btnedit', function () { 88 //获取自定义的属性 89 let obj = $(this).data() 90 // console.log(obj); 91 //修改模态框提示信息 92 $('.modal-title').text('编辑分类') 93 $('.btnset').text('编辑') 94 //弹出模态框 95 $('#myModal').modal('show') 96 //编辑功能的默认值 97 id = obj.id 98 $('#name').val(obj.name) 99 $('#slug').val(obj.slug) 100 }) 101 102 //这是点击模态框确认键的触发的事件 103 $('.btnset').on('click', function () { 104 if (flag = false) { 105 return 106 } 107 let obj = $('#form').serialize() 108 // console.log(obj); 109 if ($(this).text() == '新增') { 110 operate(allSite.category_add, obj) 111 } else if ($(this).text() == '编辑') { 112 operate(allSite.category_edit, obj + '&id=' + id) 113 } 114 }) 115 116 //删除文章分类 117 // 9、删除文章类别 118 // 请求地址:/admin/category / delete 119 // 请求方式:post 120 121 //使用模态引擎动态生成的结构需要使用事件委托来触发事件 122 $('tbody').on('click', '.btndel', function () { 123 let id = $(this).data().id 124 // console.log(id); 125 $.ajax({ 126 url: allSite.category_delete, 127 type: 'post', 128 dataType: 'json', 129 data: { id }, 130 success: function (res) { 131 // console.log(res); 132 if (res.code == 204) { 133 alert(res.msg) 134 init() 135 } else { 136 alert(res.msg) 137 } 138 } 139 }) 140 }) 141 142 })
1 <!-- 模板结构 --> 2 <script type="text/template" id="comTemp"> 3 {{each data}} 4 <tr> 5 <td>{{$value.author}}</td> 6 <td>{{$value.content}}</td> 7 <td>{{$value.title}}</td> 8 <td>{{$value.date}}</td> 9 <td>{{$value.state}}</td> 10 <td class="text-center"> 11 {{if $value.state == '待审核'}} 12 <a href="#" class="btn btn-success btn-xs btnRatify" data-id="{{$value.id}}">批准</a> 13 {{else if $value.state == '已通过'}} 14 <a href="#" class="btn btn-warning btn-xs btnReject" data-id="{{$value.id}}">拒绝</a> 15 {{/if}} 16 <a href="#" class="btn btn-danger btn-xs btnDel" data-id="{{$value.id}}">删除</a> 17 </td> 18 </tr> 19 {{/each}} 20 </script>
实现 批准 拒绝 删除 等功能
1 $(function () { 2 3 //动态渲染评论数据 4 // 19、文章评论搜索 5 // 请求地址:/admin/comment / search 6 // 请求方式:get 7 let page = 1, perpage = 10 8 function init() { 9 $.ajax({ 10 url: allSite.comment_search, 11 dataType: 'json', 12 data: { 13 page, 14 perpage 15 }, 16 success: function (res) { 17 // console.log(res); 18 if (res.code == 200) { 19 $('tbody').html(template('comTemp', res.data)) 20 //给分页函数传参 生成分页结构 21 setPage(res.data.totalPage) 22 } 23 } 24 }) 25 } 26 init() 27 28 //添加分页插件 29 //实现分页结构的显示 30 /** 31 * 32 * @param pageCurrent 当前所在页 33 * @param pageSum 总页数 34 * @param callback 调用ajax 35 */ 36 function setPage(pageSum) { 37 $("#pagination").bootstrapPaginator({ 38 //设置版本号 39 bootstrapMajorVersion: 3, 40 // 显示第几页 41 currentPage: page, 42 // 总页数 43 totalPages: pageSum, 44 //当单击操作按钮的时候, 执行该函数, 调用ajax渲染页面 45 onPageClicked: function (event, originalEvent, type, getpage) { 46 // getpage 为第四个参数,获取用户点击的页码数 47 // console.log(getpage); 48 //获取的页码赋值给全局变量 49 page = getpage; 50 // 再重新渲染页面,跳转到对应页数 51 init() 52 } 53 }) 54 } 55 56 //批准 拒绝 删除 评论的操作 封装函数 57 function comopt(id, url) { 58 $.ajax({ 59 url: url, 60 type: 'post', 61 dataType: 'json', 62 data: { id }, 63 success: function (res) { 64 // console.log(res); 65 if (res.code == 200) { 66 alert(res.msg) 67 init(); 68 } 69 } 70 }) 71 } 72 //点击批准 实现审核通过 73 // 20、评论审核通过 74 // 请求地址:/admin/comment / pass 75 // 请求方式:post 76 $('tbody').on('click', '.btnRatify', function () { 77 let id = $(this).data().id 78 // console.log(id); 79 comopt(id, allSite.comment_pass) 80 }) 81 82 //点击拒绝 实现审核不通过 83 // 21、评论审核不通过 84 // 请求地址:/admin/comment / reject 85 // 请求方式:post 86 $('tbody').on('click', '.btnReject', function () { 87 let id = $(this).data().id 88 // console.log(id); 89 comopt(id, allSite.comment_reject) 90 }) 91 92 //点击删除 删除评论数据 93 // 22、删除评论 94 // 请求地址:/admin/comment / delete 95 // 请求方式:post 96 97 $('tbody').on('click', '.btnDel', function () { 98 let id = $(this).data().id 99 console.log(id); 100 if ($('tbody').find('tr').length == 1) { 101 if (page > 1) { 102 page-- 103 } 104 } 105 comopt(id, allSite.comment_delete) 106 107 }) 108 })
1 $(function () { 2 //当页面一加载就开始渲染页面 3 //渲染个人中心页 4 function init() { 5 $.ajax({ 6 // 请求地址:/ admin / user / detail 7 // 请求方式:get 8 // 请求参数:无 9 url: allSite.user_detail, 10 dataType: 'json', 11 success: function (res) { 12 console.log(res); 13 for (let key in res.data) { 14 $('input.' + key).val(res.data[key]) 15 } 16 $('.col-sm-10>.user_pic').attr('src', res.data.userPic) 17 } 18 }) 19 } 20 init() 21 22 //实现个人中心页用户上传图片预览功能 23 $('#exampleInputFile').change(function () { 24 //1.2 获取用户选择的图片 25 // var file = this.files[0]; 两种方式都可以获取 26 let file = $(this)[0].files[0] //两种方式都可以获取 27 //1.3 将文件转为src路径 28 let url = URL.createObjectURL(file); 29 //1.4 将url路径赋值给img标签的src 30 $('.user_pic').attr('src', url); 31 }); 32 33 34 //实现个人中心页的用户编辑 点击页面修改上传修改信息 35 // 4、编辑用户信息 36 // 请求地址:/admin/user / edit 37 // 请求方式:post 38 // 请求数据:使用formData提交 39 // 当前页面所在的窗口window对象是user.html, 如果想要获取父窗口,则需要使用window.parent,这种用法一般用于获取iframe标签的父窗口。 40 //submit 上传事件 41 $('form').on('submit', function (e) { 42 e.preventDefault(); 43 $.ajax({ 44 url: allSite.user_edit, 45 data: new FormData(this), 46 type: 'post', 47 dataType: 'json', 48 contentType: false, 49 processData: false, 50 success: function (res) { 51 console.log(res); 52 // console.log(window); 53 if (res.code == 200) { 54 // window.parent.location.reload() 55 alert('修改成功') 56 window.parent.location.reload() 57 } 58 } 59 }) 60 }) 61 })