文章目录

  • 续前篇:大事件后台管理系统开发实战(中)
  • 1. 文章类别
    • 1.1 点击编辑按钮展示修改文章分类的弹出层
    • 1.2 为修改文章分类的弹出层填充表单数据
    • 1.3 更新文章分类的数据
    • 1.4 删除文章分类
  • 2. 文章列表
    • 2.1 创建文章列表页面
    • 2.2 定义查询参数对象q
    • 2.3 请求文章列表数据并使用模板引擎渲染列表结构
    • 2.3.1 定义获取文章列表数据
    • 2.4 定义美化时间格式的过滤器
    • 2.5 绘制筛选区域的UI结构
    • 2.6 发起请求获取并渲染文章分类的下拉选择框
    • 2.7 实现筛选的功能
  • 3. 分页
    • 3.1 定义渲染分页的 renderPage 方法
    • 3.2 调用 laypage.render 方法渲染分页的基本结构
    • 3.3 在jump回调函数中通过obj.curr获取到最新的页码值
    • 3.4 解决 jump 回调函数发生死循环的问题
    • 3.5 自定义分页的功能项
    • 3.6 实现切换每页展示多少条数据的功能
  • 4. 删除文章
    • 4.1 实现删除文章的功能
    • 4.2 解决删除文章时的小 Bug
  • 5. 发布文章
    • 5.1 创建文章发布页面的基本结构
    • 5.2 新建基本的表单结构
    • 5.3 渲染文章类别对应的下拉选择框结构
    • 5.4 渲染富文本编辑器
    • 5.5 渲染封面裁剪区域
    • 5.6 渲染提交按钮区域
    • 5.7 点击选择封面按钮打开文件选择框
    • 5.8 将选择的图片设置到裁剪区域中
    • 小结 - 渲染封面裁剪区域
    • 5.9 分析发布文章的实现步骤
    • 5.10 基于Form表单快速创建FormData对象
    • 5.11 将裁剪后的封面追加到FormData对象中
    • 5.12 发起Ajax请求实现发布文章的功能
  • 6. 编辑文章
    • 6.1 创建文章编辑页面基本结构
    • 6.2 给编辑按钮添加点击事件
    • 6.3 新建基本的表单结构
    • 6.4 文章类别对应的下拉选择框结构
    • 6.5 渲染富文本编辑器
    • 6.6 封面裁剪区域结构
    • 6.7 提交按钮区域
    • 6.8 通过 URLSearchParams 对象,获取 URL 传递的参数
    • 6.9 发起请求获取文章详情
    • 6.10 选择文章封面
    • 6.11 监听文件(文章封面图片)选择框的 change 事件
    • 6.12 设置文章的发布状态
    • 6.13 发布文章
    • 6.14 完整html结构
    • 6.15 完整css 样式代码
    • 6.16 编辑功能完整 JS 代码
  • 7. 将开发完成的项目代码推送到GitHub

续前篇:大事件后台管理系统开发实战(中)

1. 文章类别

1.1 点击编辑按钮展示修改文章分类的弹出层

  1. 为编辑按钮添加 btn-edit 类名如下:

    <button type="button" class="layui-btn layui-btn-xs btn-edit" data-id="{{$value.Id}}">编辑</button>
    
  2. 定义 修改分类 的弹出层:

    <script type="text/html" id="dialog-edit"><form class="layui-form" id="form-edit" lay-filter="form-edit"><!-- 隐藏域,保存 Id 的值 --><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" 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" placeholder="请输入分类别名" autocomplete="off" class="layui-input"></div></div><div class="layui-form-item"><div class="layui-input-block"><button class="layui-btn" lay-submit lay-filter="formDemo">确认修改</button></div></div></form>
    </script>
    

快速为一个表单填充数据:先为form表单添加 lay-filter 属性,再调用form方法(前提是要先导入form:var form=layui.form)。同时,为表单创建一个隐藏域(input)用于保存id
3. 通过 代理 的形式,为 btn-edit 按钮绑定点击事件:

  var indexEdit = null$('tbody').on('click', '.btn-edit', function() {// 弹出一个修改文章分类信息的层indexEdit = layer.open({type: 1,area: ['500px', '250px'],title: '修改文章分类',content: $('#dialog-edit').html()})})

1.2 为修改文章分类的弹出层填充表单数据

  1. 为编辑按钮绑定 data-id 自定义属性:

    <button type="button" class="layui-btn layui-btn-xs btn-edit" data-id="{{$value.Id}}">编辑</button>
    
  2. 在展示弹出层之后,根据 id 的值发起请求获取文章分类的数据,并填充到表单中:

    var id = $(this).attr('data-id')
    // 发起请求获取对应分类的数据
    $.ajax({method: 'GET',url: '/my/article/cates/' + id,success: function(res) {form.val('form-edit', res.data)}
    })
    

1.3 更新文章分类的数据

  1. 通过代理的形式,为修改分类的表单绑定 submit 事件:

    $('body').on('submit', '#form-edit', function(e) {e.preventDefault()$.ajax({method: 'POST',url: '/my/article/updatecate',data: $(this).serialize(),success: function(res) {if (res.status !== 0) {return layer.msg('更新分类数据失败!')}layer.msg('更新分类数据成功!')layer.close(indexEdit)initArtCateList()}})
    })
    

1.4 删除文章分类

  1. 为删除按钮绑定 btn-delete 类名,并添加 data-id 自定义属性:

    <button type="button" class="layui-btn layui-btn-danger layui-btn-xs btn-delete" data-id="{{$value.Id}}">删除</button>
    
  2. 通过代理的形式,为删除按钮绑定点击事件:

    $('tbody').on('click', '.btn-delete', function() {var id = $(this).attr('data-id')     // 获取 id 的值// 提示用户是否要删除layer.confirm('确认删除?', { icon: 3, title: '提示' }, function(index) {$.ajax({method: 'GET',url: '/my/article/deletecate/' + id,success: function(res) {if (res.status !== 0) {return layer.msg('删除分类失败!')}layer.msg('删除分类成功!')layer.close(index)initArtCateList()}})})
    })
    

2. 文章列表

共分为3个区域,依卡片面板上的布局设计,从上到下依次是:

  • 筛选区域;
  • 列表区域;
  • 分页区域。

2.1 创建文章列表页面

首先,绘制卡片区域,打开layui官网,复制卡片面板的HTML结构代码

  1. 新建 /article/art_list.html 页面结构如下:

    <!DOCTYPE html>
    <html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><link rel="stylesheet" href="/assets/lib/layui/css/layui.css" /><link rel="stylesheet" href="/assets/css/article/art_list.css" /></head><body><!-- 卡片区域 --><div class="layui-card"><div class="layui-card-header">文章列表</div><div class="layui-card-body"></div></div><!-- 导入第三方的 JS 插件 --><script src="/assets/lib/layui/layui.all.js"></script><script src="/assets/lib/jquery.js"></script><script src="/assets/js/baseAPI.js"></script><!-- 导入自己的 JS 脚本 --><script src="/assets/js/article/art_list.js"></script></body>
    </html>
  2. 新建 /assets/css/article/art_list.css 样式表如下:

    html,
    body {margin: 0;padding: 0;
    }body {padding: 15px;background-color: #f2f3f5;
    }
    
  3. 新建 /assets/js/article/art_list.js 脚本文件。

2.2 定义查询参数对象q

  1. 定义一个查询的参数对象如下:

      // 定义一个查询的参数对象,将来请求数据的时候,// 需要将请求参数对象提交到服务器var q = {pagenum: 1, // 页码值,默认请求第一页的数据pagesize: 2, // 每页显示几条数据,默认每页显示2条cate_id: '', // 文章分类的 Idstate: '' // 文章的发布状态}
    

2.3 请求文章列表数据并使用模板引擎渲染列表结构

API 接口如图(注意,请求时需携带 4 个 参数):



2.3.1 定义获取文章列表数据

1)定义查询的参数对象q, 将来请求数据时,需要将请求参数对象提交到服务器。

var q = {pagenum: 1, // 默认值,默认请求第一页的数据 pagesize: 2, // 每页显示几条数据cate_id: '', // 文章分类的id,默认为空state: '' // 文章的发布状态}

2)定义一个获取文章列表数据的方法(自定义函数)
注: 要先导入模板引擎

initTable()  // 获取文章列表数据的方法
function initTable() {$.ajax({method: 'GET',url: '/my/article/list',data: q,success: function(res) {if (res.status !== 0) {return layer.msg('获取文章列表失败!')}// 使用模板引擎渲染页面的数据var htmlStr = template('tpl-table', res)$('tbody').html(htmlStr)}})
}

  1. 在页面中添加表格结构如下:
    在layui官网找到 【页面元素】⇒\Rightarrow⇒【表格】⇒\Rightarrow⇒【常规用法】,复制html结构到代码编辑器中。

修改后的表格结构如下:

<!-- 列表区域 -->
<table class="layui-table"><colgroup><col /><col width="150" /><col width="180" /><col width="150" /><col width="150" /></colgroup><thead><tr><th>文章标题</th><th>分类</th><th>发表时间</th><th>状态</th><th>操作</th></tr></thead><tbody></tbody>
</table>
  1. 定义列表数据的模板结构:

    <script type="text/html" id="tpl-table">{{each data}}<tr><td>{{$value.title}}</td><td>{{$value.cate_name}}</td><td>{{$value.pub_date|dataFormat}}</td><td>{{$value.state}}</td><td><button type="button" class="layui-btn layui-btn-xs">编辑</button><button type="button" class="layui-btn layui-btn-danger layui-btn-xs">删除</button></td></tr>{{/each}}
    </script>
    

2.4 定义美化时间格式的过滤器

  1. 通过 template.defaults.imports 定义过滤器(此属性模板引擎独有,需先导入模板引擎):

      // 定义美化时间的过滤器template.defaults.imports.dataFormat = function(date) {const dt = new Date(date)var y = dt.getFullYear()var m = padZero(dt.getMonth() + 1)var d = padZero(dt.getDate())var hh = padZero(dt.getHours())var mm = padZero(dt.getMinutes())var ss = padZero(dt.getSeconds())return y + '-' + m + '-' + d + ' ' + hh + ':' + mm + ':' + ss}// 定义补零的函数function padZero(n) {return n > 9 ? n : '0' + n}
    
  2. 在模板引擎中使用过滤器:

    <td>{{$value.pub_date|dataFormat}}</td>
    

2.5 绘制筛选区域的UI结构

  1. 绘制 UI 结构:

    <!-- 筛选区域 -->
    <form class="layui-form" id="form-search"><div class="layui-form-item layui-inline"><select name="cate_id"></select></div><div class="layui-form-item layui-inline"><select name="state"><option value="">所有状态</option><option value="已发布">已发布</option><option value="草稿">草稿</option></select></div><div class="layui-form-item layui-inline"><button class="layui-btn" lay-submit lay-filter="formDemo">筛选</button></div>
    </form>
    

2.6 发起请求获取并渲染文章分类的下拉选择框

  1. 定义 initCate 函数请求文章分类的列表数据:

      initCate()// 初始化文章分类的方法function initCate() {$.ajax({method: 'GET',url: '/my/article/cates',success: function(res) {if (res.status !== 0) {return layer.msg('获取分类数据失败!')}// 调用模板引擎渲染分类的可选项var htmlStr = template('tpl-cate', res)$('[name=cate_id]').html(htmlStr)// 通过 layui 重新渲染表单区域的UI结构form.render()}})}
    
  2. 定义分类可选项的模板结构:

    <script type="text/html" id="tpl-cate"><option value="">所有分类</option>{{each data}}<option value="{{$value.Id}}">{{$value.name}}</option>{{/each}}
    </script>
    

2.7 实现筛选的功能

  1. 为筛选表单绑定 submit 事件:

    $('#form-search').on('submit', function(e) {e.preventDefault()// 获取表单中选中项的值var cate_id = $('[name=cate_id]').val()var state = $('[name=state]').val()// 为查询参数对象 q 中对应的属性赋值q.cate_id = cate_idq.state = state// 根据最新的筛选条件,重新渲染表格的数据initTable()
    })
    

3. 分页

在 layui 官方文档中找到 【内置模块】⇒\Rightarrow⇒【分页】,



1)在自己 vs code 中 art_list.html 分页区域的位置 ,添加一个 id 名为 pageBox 的 div 容器,用于存放分页组件

2)初始化分页区:从 layui 中导入 laypage 对象,即复制上图上的代码 var laypage=layui.laypage,并在 art_list.js 头部导入此句代码。

3)js 中调用 laypage.render 方法渲染分页的结构。注意要传入 4 个参数

参数选项 说明 类型 默认值
elem 指向存放分页的容器,值可以是容器ID、DOM对象。如:
1. elem: ‘id’ 注意:这里不能加 # 号
2. elem: document.getElementById(‘id’)
String/Object -
count 数据总数。一般通过服务端得到 Number -
limit 每页显示的条数。laypage将会借助 count 和 limit 计算出分页数。 Number 10
curr 起始页。一般用于刷新类型的跳页以及HASH跳页。 Number 1
// 导入 laypage 对象
var laypage = layui.laypage;
// 渲染底部分分页区域的方法function renderPage(total) {laypage.render({elem: 'pageBox', //分页容器 Idcount: total, // 分页条数limit: q.pagesize, // 每页显示几条数据curr: q.pagenum // 指定默认选中哪一页})}

保存并刷新网页后,效果如下:


3.1 定义渲染分页的 renderPage 方法

  1. 定义渲染分页的方法:

    function renderPage(total) {console.log(total)
    }
    
  2. initTable 中调用 renderPage 方法:

    function initTable() {$.ajax({method: 'GET',url: '/my/article/list',data: q,success: function(res) {if (res.status !== 0) {return layer.msg('获取文章列表失败!')}// 使用模板引擎渲染页面的数据var htmlStr = template('tpl-table', res)$('tbody').html(htmlStr)// 调用渲染分页的方法renderPage(res.total)}})
    }
    

3.2 调用 laypage.render 方法渲染分页的基本结构

  1. 在页面中定义分页的区域:

    <!-- 分页区域 -->
    <div id="pageBox"></div>
    
  2. 调用 laypage.render() 方法来渲染分页的结构:

    // 定义渲染分页的方法
    function renderPage(total) {// 调用 laypage.render() 方法来渲染分页的结构laypage.render({elem: 'pageBox', // 分页容器的 Idcount: total, // 总数据条数limit: q.pagesize, // 每页显示几条数据curr: q.pagenum // 设置默认被选中的分页})
    }
    

3.3 在jump回调函数中通过obj.curr获取到最新的页码值

// 定义渲染分页的方法
function renderPage(total) {// 调用 laypage.render() 方法来渲染分页的结构laypage.render({elem: 'pageBox', // 分页容器的 Idcount: total, // 总数据条数limit: q.pagesize, // 每页显示几条数据curr: q.pagenum, // 设置默认被选中的分页// 分页发生切换的时候,触发 jump 回调jump: function(obj) {console.log(obj.curr)// 把最新的页码值,赋值到 q 这个查询参数对象中q.pagenum = obj.curr}})
}

3.4 解决 jump 回调函数发生死循环的问题

分页里面有个 jump 回调函数(包括两个参数,objfirst),通过这个回调就可以拿到当前的页码值。只要点击页码或者调用了 laypage.render() 方法,就会触发 jump 回调。

jump 中,能监听到分页切换事件

  // 定义渲染分页的方法function renderPage(total) {// 调用 laypage.render() 方法来渲染分页的结构laypage.render({elem: 'pageBox', // 分页容器的 Idcount: total, // 总数据条数limit: q.pagesize, // 每页显示几条数据curr: q.pagenum, // 设置默认被选中的分页// 分页发生切换的时候,触发 jump 回调// 触发 jump 回调的方式有两种:// 1. 点击页码的时候,会触发 jump 回调// 2. 只要调用了 laypage.render() 方法,就会触发 jump 回调jump: function(obj, first) {// 可以通过 first 的值,来判断是通过哪种方式,触发的 jump 回调// 如果 first 的值为 true,证明是方式2触发的// 否则就是方式1触发的console.log(first)console.log(obj.curr)// 把最新的页码值,赋值到 q 这个查询参数对象中q.pagenum = obj.curr// 根据最新的 q 获取对应的数据列表,并渲染表格// initTable()  // 不能直接在这里调用,会形成死循环。if (!first) {initTable()}}})}

3.5 自定义分页的功能项

// 定义渲染分页的方法
function renderPage(total) {// 调用 laypage.render() 方法来渲染分页的结构laypage.render({elem: 'pageBox', // 分页容器的 Idcount: total, // 总数据条数limit: q.pagesize, // 每页显示几条数据curr: q.pagenum, // 设置默认被选中的分页layout: ['count', 'limit', 'prev', 'page', 'next', 'skip'],limits: [2, 3, 5, 10],// 分页发生切换的时候,触发 jump 回调// 触发 jump 回调的方式有两种:// 1. 点击页码的时候,会触发 jump 回调// 2. 只要调用了 laypage.render() 方法,就会触发 jump 回调jump: function(obj, first) {// 可以通过 first 的值,来判断是通过哪种方式,触发的 jump 回调// 如果 first 的值为 true,证明是方式2触发的// 否则就是方式1触发的console.log(first)console.log(obj.curr)// 把最新的页码值,赋值到 q 这个查询参数对象中q.pagenum = obj.curr// 根据最新的 q 获取对应的数据列表,并渲染表格// initTable()if (!first) {initTable()}}})
}

3.6 实现切换每页展示多少条数据的功能

// 定义渲染分页的方法
function renderPage(total) {// 调用 laypage.render() 方法来渲染分页的结构laypage.render({elem: 'pageBox', // 分页容器的 Idcount: total, // 总数据条数limit: q.pagesize, // 每页显示几条数据curr: q.pagenum, // 设置默认被选中的分页layout: ['count', 'limit', 'prev', 'page', 'next', 'skip'],limits: [2, 3, 5, 10],// 分页发生切换的时候,触发 jump 回调// 触发 jump 回调的方式有两种:// 1. 点击页码的时候,会触发 jump 回调// 2. 只要调用了 laypage.render() 方法,就会触发 jump 回调jump: function(obj, first) {// 可以通过 first 的值,来判断是通过哪种方式,触发的 jump 回调// 如果 first 的值为 true,证明是方式2触发的// 否则就是方式1触发的console.log(first)console.log(obj.curr)// 把最新的页码值,赋值到 q 这个查询参数对象中q.pagenum = obj.curr// 把最新的条目数,赋值到 q 这个查询参数对象的 pagesize 属性中q.pagesize = obj.limit// 根据最新的 q 获取对应的数据列表,并渲染表格// initTable()if (!first) {initTable()}}})
}

4. 删除文章

4.1 实现删除文章的功能

  1. 为删除按钮绑定 btn-delete 类名和 添加data-id 自定义属性:

    <button type="button" class="layui-btn layui-btn-danger layui-btn-xs btn-delete" data-id="{{$value.Id}}">删除</button>
    
  2. 通过代理的形式,为删除按钮绑定点击事件处理函数:

    $('tbody').on('click', '.btn-delete', function() {// 获取到文章的 idvar id = $(this).attr('data-id')     // 这句一定要放在询问提示框之前,否则会删除失败!// 询问用户是否要删除数据layer.confirm('确认删除?', { icon: 3, title: '提示' }, function(index) {$.ajax({method: 'GET',url: '/my/article/delete/' + id,success: function(res) {if (res.status !== 0) {return layer.msg('删除文章失败!')}layer.msg('删除文章成功!')initTable()}})layer.close(index)})
    })
    

4.2 解决删除文章时的小 Bug

$('tbody').on('click', '.btn-delete', function() {// 获取删除按钮的个数var len = $('.btn-delete').length// 获取到文章的 idvar id = $(this).attr('data-id')// 询问用户是否要删除数据layer.confirm('确认删除?', { icon: 3, title: '提示' }, function(index) {$.ajax({method: 'GET',url: '/my/article/delete/' + id,success: function(res) {if (res.status !== 0) {return layer.msg('删除文章失败!')}layer.msg('删除文章成功!')// 当数据删除完成后,需要判断当前这一页中,是否还有剩余的数据// 如果没有剩余的数据了,则让页码值 -1 之后,// 再重新调用 initTable 方法// 4if (len === 1) {// 如果 len 的值等于1,证明删除完毕之后,页面上就没有任何数据了// 页码值最小必须是 1q.pagenum = q.pagenum === 1 ? 1 : q.pagenum - 1}initTable()}})layer.close(index)})
})

5. 发布文章

5.1 创建文章发布页面的基本结构

  1. 新建 /article/art_pub.html 页面结构如下:

    <!DOCTYPE html>
    <html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><link rel="stylesheet" href="/assets/lib/layui/css/layui.css" /><link rel="stylesheet" href="/assets/css/article/art_pub.css" /></head><body><!-- 卡片区域 --><div class="layui-card"><div class="layui-card-header">写文章</div><div class="layui-card-body">卡片式面板面板通常用于非白色背景色的主体内<br />从而映衬出边框投影</div></div><!-- 导入第三方的 JS 插件 --><script src="/assets/lib/layui/layui.all.js"></script><script src="/assets/lib/jquery.js"></script><script src="/assets/js/baseAPI.js"></script><!-- 导入自己的 JS --><script src="/assets/js/article/art_pub.js"></script></body>
    </html>
  2. 新建 /assets/css/article/art_pub.css 样式文件如下:

    html,
    body {margin: 0;padding: 0;
    }body {padding: 15px;background-color: #f2f3f5;
    }
    
  3. 新建 /assets/js/article/art_pub.js 脚本文件如下:

    $(function() { })
    

5.2 新建基本的表单结构

<!-- 发布文章的表单 -->
<form class="layui-form"><div class="layui-form-item"><label class="layui-form-label">文章标题</label><div class="layui-input-block"><input type="text" name="title" required lay-verify="required" placeholder="请输入标题" autocomplete="off" class="layui-input" /></div></div>
</form>

5.3 渲染文章类别对应的下拉选择框结构

  1. 定义 UI 结构:

      <!-- 第二行 --><div class="layui-form-item"><label class="layui-form-label">文章类别</label><div class="layui-input-block"><select name="cate_id" lay-verify="required"></select></div></div>
    
  2. 导入 art-template:

    <script src="/assets/lib/template-web.js"></script>
    
  3. 定义模板结构:

    <script type="text/html" id="tpl-cate"><option value="">请选择文章类别</option>{{each data}}<option value="{{$value.Id}}">{{$value.name}}</option>{{/each}}
    </script>
    
  4. 定义 initCate 方法:

    $(function() {var layer = layui.layervar form = layui.forminitCate()// 定义加载文章分类的方法function initCate() {$.ajax({method: 'GET',url: '/my/article/cates',success: function(res) {if (res.status !== 0) {return layer.msg('初始化文章分类失败!')}// 调用模板引擎,渲染分类的下拉菜单var htmlStr = template('tpl-cate', res)$('[name=cate_id]').html(htmlStr)// 一定要记得调用 form.render() 方法  // 由于layui不知道动态生成了分类form.render()}})}
    })
    

5.4 渲染富文本编辑器

参考我的另一篇文章: 基于 Layui 的富文本编辑器和封面图片剪裁的实现方案

与编辑器 & 图片裁剪实现方案相关的素材文件:
网盘下载链接:https://pan.baidu.com/s/1k9IBXKKQfD7pf9a6NNyQkQ

提取码:qw6o

5.5 渲染封面裁剪区域

参考 基于 Layui 的富文本编辑器和封面图片剪裁的实现方案

5.6 渲染提交按钮区域

  <!-- 第五行 --><div class="layui-form-item"><div class="layui-input-block"><button class="layui-btn" lay-submit>发布</button><button class="layui-btn layui-btn-primary" lay-submit>存为草稿</button></div></div>

5.7 点击选择封面按钮打开文件选择框

  1. 修改 UI 结构,为 选择封面 按钮添加 id,并且在按钮后面添加 文件选择框

    <!-- 选择封面按钮 -->
    <button type="button" class="layui-btn layui-btn-danger" id="btnChooseImage">选择封面</button>
    <!-- 隐藏的文件选择框 -->
    <input type="file" id="coverFile" style="display: none;" accept="image/png,image/jpeg,image/gif" />
    
  2. 为选择封面的按钮,绑定点击事件处理函数:

    $('#btnChooseImage').on('click', function() {$('#coverFile').click()
    })
    

5.8 将选择的图片设置到裁剪区域中

  1. 监听 coverFilechange 事件,获取用户选择的文件列表:

      // 监听 coverFile 的 change 事件,获取用户选择的文件列表$('#coverFile').on('change', function(e) {// 获取到文件的列表数组var files = e.target.files// 判断用户是否选择了文件if (files.length === 0) {return}// 根据文件,创建对应的 URL 地址var newImgURL = URL.createObjectURL(files[0])// 为裁剪区域重新设置图片$image.cropper('destroy') // 销毁旧的裁剪区域.attr('src', newImgURL) // 重新设置图片路径.cropper(options) // 重新初始化裁剪区域})
    

小结 - 渲染封面裁剪区域

第一步:为文件选择框绑定 change 事件;

第二步:在事件对象 e 中,获取到用户选择的文件列表files

第三步:判断用户是否选择了文件,如果没有选择文件,就直接return出去,不再执行后面代码;

第四步:用户选择了文件,我们需要将文件创建为对应的新的url地址;

第五步:重新为裁剪区域设置图片。

5.9 分析发布文章的实现步骤

API 接口如图所示:



  1. 存为草稿 按钮添加 id 属性:

    <button class="layui-btn layui-btn-primary" lay-submit id="btnSave2">存为草稿</button>
    
  2. 定义文章的发布状态:

    var art_state = '已发布'
    
  3. 为存为草稿按钮,绑定点击事件处理函数:

    $('#btnSave2').on('click', function() {art_state = '草稿'
    })
    

5.10 基于Form表单快速创建FormData对象

  1. 为发布文章的 Form 表单添加 id 属性:

    <form class="layui-form" id="form-pub"></form>
    
  2. 为表单绑定 submit 提交事件:

      $('#form-pub').on('submit', function(e) {// 1. 阻止表单的默认提交行为e.preventDefault()// 2. 基于 form 表单,快速创建一个 FormData 对象var fd = new FormData($(this)[0])// 3. 将文章的发布状态,追加到 fd 中fd.append('state', art_state)})
    

快速创建 FormData 对象:new FormData($(this)[0])

代码注释:
new FormData 期间,通过把 jQuery 对象 $(this)[0]的形式转换为一个原生的 DOM 对象,然后把这个 DOM 对象传递给new FormData(),就能够创建出一个 FormData 对象。

为了证实这个fd 对象中确实存了一些数据,我们可以用forEachFormData 中存储的每一个键循环打印到控制台查看,方法如下:

// 测试创建 FormData 是否成功
fd.forEach(function(v, k) {  // 用v 接收FormData中存的值,k 接收键console.log(k, v);
})

提交事件触发后,控制台打印如下:



5.11 将裁剪后的封面追加到FormData对象中

这里仍然参照文章 基于 Layui 的富文本编辑器和封面图片剪裁的实现方案 一文中 “4. 将裁剪后的图片,输出为文件” 的代码。

代码如图:


  // 为表单绑定 submit 提交事件$('#form-pub').on('submit', function(e) {// 1. 阻止表单的默认提交行为e.preventDefault()// 2. 基于 form 表单,快速创建一个 FormData 对象var fd = new FormData($(this)[0])// 3. 将文章的发布状态,存到 fd 中fd.append('state', art_state)// 4. 将封面裁剪过后的图片,输出为一个文件对象$image.cropper('getCroppedCanvas', {// 创建一个 Canvas 画布width: 400,height: 280}).toBlob(function(blob) {// 将 Canvas 画布上的内容,转化为文件对象// 得到文件对象后,进行后续的操作// 5. 将文件对象,存储到 fd 中fd.append('cover_img', blob)// 6. 发起 ajax 数据请求})})

只要调用了toBlob,就能将裁剪之后的图片输出为文件了,.toBlob(function(blob)中的blob就是图片文件。再将 blob存到 FormData(也就是fd) 里。

5.12 发起Ajax请求实现发布文章的功能

  1. 定义一个发布文章的方法:

    function publishArticle(fd) {$.ajax({method: 'POST',url: '/my/article/add',data: fd,// 注意:如果向服务器提交的是 FormData 格式的数据,// 必须添加以下两个配置项contentType: false,processData: false,success: function(res) {if (res.status !== 0) {return layer.msg('发布文章失败!')}layer.msg('发布文章成功!')// 发布文章成功后,跳转到文章列表页面location.href = '/article/art_list.html'}})
    }
    

注意:
如果向服务器提交的是 FormData 格式的数据,必须添加以下两个配置项
contentType: false,
processData: false,
否则,请求就会失败。

  1. 把裁剪的图片追加到 FormData 对象中之后,调用 publishArticle 方法:

    // 为表单绑定 submit 提交事件
    $('#form-pub').on('submit', function(e) {// 1. 阻止表单的默认提交行为e.preventDefault()// 2. 基于 form 表单,快速创建一个 FormData 对象var fd = new FormData($(this)[0])// 3. 将文章的发布状态,存到 fd 中fd.append('state', art_state)// 4. 将封面裁剪过后的图片,输出为一个文件对象$image.cropper('getCroppedCanvas', {// 创建一个 Canvas 画布width: 400,height: 280}).toBlob(function(blob) {// 将 Canvas 画布上的内容,转化为文件对象// 得到文件对象后,进行后续的操作// 5. 将文件对象,存储到 fd 中fd.append('cover_img', blob)// 6. 发起 ajax 数据请求publishArticle(fd)})
    })
    

6. 编辑文章

6.1 创建文章编辑页面基本结构

  1. 新建 /article/art_edit.html 页面结构如下:
 <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><link rel="stylesheet" href="/assets/lib/layui/css/layui.css" /><link rel="stylesheet" href="/assets/css/article/art_pub.css" /></head><body><!-- 卡片区域 --><div class="layui-card"><div class="layui-card-header">写文章</div><div class="layui-card-body">卡片式面板面板通常用于非白色背景色的主体内<br />从而映衬出边框投影</div></div><!-- 导入第三方的 JS 插件 --><script src="/assets/lib/layui/layui.all.js"></script><script src="/assets/lib/jquery.js"></script><script src="/assets/js/baseAPI.js"></script><!-- 导入自己的 JS --><script src="/assets/js/article/art_edit.js"></script></body></html>
  1. 新建 /assets/css/article/art_edit.css 样式文件(初始化页面)如下:
html,
body {margin: 0;padding: 0;
}body {padding: 15px;background-color: #f2f3f5;
}
  1. 新建 /assets/js/article/art_edit.js 脚本文件如下:
  $(function() { })

6.2 给编辑按钮添加点击事件

  1. 修改文章列表的模板引擎
    /article/art.list.html文件打开,给模板引擎中的“编辑”按钮添加类名btn_edit、自定义属性data-indexdata-id
<button type="button" data-index='{{$value.Id}}' class="layui-btn layui-btn-xs btn-edit" data-id="{{$value.Id}}">编辑</button>
  1. 给【编辑】按钮绑定点击事件
// 给文章编辑按钮绑定 click 单击事件$('body').on('click', '.btn-edit', function() {location.href = '/article/art_edit.html?id=' + $(this).attr('data-id')})

6.3 新建基本的表单结构

在上一步创建的html结构的卡片区域中,将类名为 layui-card-body 的 div 盒子内的文字替换为如下表单(在Layui官方文档中复制输入框html结构),并为form表单添加id="formAddArticle"

<!-- 修改文章的表单 -->
<form class="layui-form" lay-filter="addArticle" id="formAddArticle" enctype="multipart/form-data"><!-- 文章标题 --><div class="layui-form-item"><label class="layui-form-label">文章标题</label><div class="layui-input-block"><input type="text" name="title" required lay-verify="required" placeholder="请输入标题" autocomplete="off" class="layui-input" /></div></div>
</form>

6.4 文章类别对应的下拉选择框结构

  1. 定义 UI 结构:

      <!-- 文章类别 --><div class="layui-form-item"><label class="layui-form-label">文章类别</label><div class="layui-input-block" id="art_cate"></div></div>
    
  2. 导入 art-template:
    在html文件底部,在导入的 layui.js 后面导入template 模板引擎脚本文件。

    <script src="/assets/lib/template-web.js"></script>
    
  3. 定义文章分类列表模板结构:
    给模板结构添加 id属性:id="selectArtCates",方便后续渲染。

    <!-- 文章分类列表的模板 --><script type="text/html" id="selectArtCates"><select name="cate_id" lay-verify="required"><option value="">请选择文章类别</option>{{each data}}<option value="{{$value.Id}}">{{$value.name}}</option>{{/each}}</select></script>
    
  4. 定义 renderArticleCates 方法,渲染文章分类列表:

    $(function() {// 获取需要的 layui 对象var form = layui.form// 1. 渲染文章分类列表renderArticleCates()function renderArticleCates() {$.get('/my/article/cates', function(res) {if (res.status !== 0) {return layer.msg('获取文章分类列表失败!')}var htmlStr = template('selectArtCates', res)$('#art_cate').html(htmlStr)// 一定要记得调用 form.render() 方法  // 由于layui不知道动态生成了分类form.render()getArticleById()})}
    

6.5 渲染富文本编辑器

参考 基于 Layui 的富文本编辑器和封面图片剪裁的实现方案 完成。

6.6 封面裁剪区域结构

  1. 剪裁区域html
<!-- 文章封面 -->
<div class="layui-form-item"><label class="layui-form-label">文章封面</label><div class="layui-input-block cropper-container"><!-- 文件选择框 --><input type="file" id="fileCover" accept="image/jpeg,image/png,image/gif,image/bmp" style="display: none;" /><div class="cropper-box"><img id="image" src=""></div><div class="right-box"><div class="img-preview"></div><button class="layui-btn layui-btn-danger" id="btnChooseCoverImage">选择封面</button></div></div>
</div>
  1. art_edit.css 文件中添加剪裁区域 css 样式
.cropper-box {width: 400px;height: 280px;
}#image {max-width: 400px;/* 这个设置很重要 */max-height: 280px;
}.img-preview {width: 200px;height: 140px;overflow: hidden;margin-bottom: 20px;
}.cropper-container {display: flex;
}

6.7 提交按钮区域

<!-- 按钮区域 -->
<div class="layui-form-item"><div class="layui-input-block"><button class="layui-btn" lay-submit id="btnPublish">发布</button><button class="layui-btn layui-btn-primary" lay-submit id="btnSave">存为草稿</button></div>
</div>

给表单添加隐藏域,用于获取用户选择的文章的Id。

<input type="hidden" name="Id" />

6.8 通过 URLSearchParams 对象,获取 URL 传递的参数

// 通过 URLSearchParams 对象,获取 URL 传递过来的参数var params = new URLSearchParams(location.search)var artId = params.get('id')// 文章的发布状态var pubState = ''

解释
首先声明变量params,并通过URLSearchParams对象赋值,再通过params.get()的方式输入键、拿到值(这里赋值给artId),就很简单地 拿到了前一个页面(文章列表页点击“编辑”时)传过来的文章id值。

Location对象提供以下属性。

属性 说明
Location.href 整个 URL
Location.protocol 当前 URL 的协议,包括冒号(:
Location.host 主机,包括冒号(:)和端口(默认的80端口和443端口会省略)。
Location.hostname 主机名,不包括端口。
Location.port 端口号。
Location.pathname URL 的路径部分,从根路径/开始。
Location.search 查询字符串部分,从问号?开始。
Location.hash 片段字符串部分,从#开始。
Location.username 域名前面的用户名。
Location.password 域名前面的密码。
Location.origin URL 的协议、主机名和端口。

更多关于URLSearchParams(location.search),可参考MDN文档 和后面这篇简书文章 《JS Location对象,URL对象,URLSearchParams对象》

6.9 发起请求获取文章详情

根据文章的 Id,获取文章的详情,并初始化表单的数据内容:

function getArticleById() {// 发起请求,获取文章详情$.get('/my/article/' + artId, function(res) {// 获取数据失败if (res.status !== 0) {return layer.msg('获取文章失败!')}// 获取数据成功var art = res.data// 为 form 表单赋初始值form.val('addArticle', {Id: art.Id,title: art.title,cate_id: art.cate_id,content: art.content})// 手动初始化富文本编辑器initEditor()  // cropper插件自带方法,导入插件即可使用// 初始化图片裁剪器var $image = $('#image')// 设置图片路径$image.attr('src', 'http://ajax.frontend.itheima.net' + art.cover_img)// 裁剪选项var cropperOption = {aspectRatio: 400 / 280,preview: '.img-preview',// 初始化图片裁剪框的大小autoCropArea: 1}// 初始化裁剪区域$image.cropper(cropperOption)})}

form.val:携带有值就给表单赋值,未携带值则获取表单中的值。

语法form.val('filter', object)
用于给指定表单集合的元素赋值和取值。如果 object 参数存在,则为赋值;如果 object 参数不存在,则为取值。其中「取值」功能为 layui 2.2.5 开始新增

attr() 方法:设置被选元素(这里是image标签)的属性和值。详情参阅 W3School文档。

裁剪选项 var cropperOption={},参见 基于Layui 的富文本编辑器与封面的实现方案

6.10 选择文章封面

// 选择封面$('#btnChooseCoverImage').on('click', function(e) e.preventDefault()  // 阻止默认提交行为$('#fileCover').click()})

6.11 监听文件(文章封面图片)选择框的 change 事件

$('#fileCover').on('change', function(e) {var files = e.target.files// 没有选择文件if (files.length === 0) {return}// 重新为裁剪区域设置图片$('#image').cropper('destroy').attr('src', URL.createObjectURL(files[0])).cropper({aspectRatio: 400 / 280,preview: '.img-preview'})
})

6.12 设置文章的发布状态

$('#btnPublish').on('click', function() {pubState = '已发布'
})
$('#btnSave').on('click', function() {pubState = '草稿'
})

6.13 发布文章

绑定【发布】按钮的submit提交事件,同样的需参照 基于Layui 的富文本编辑器与封面的实现方案,完成后的代码如下:

$('#formAddArticle').on('submit', function(e) {e.preventDefault() // 阻止默认提交事件// 创建一个 Canvas 画布$('#image').cropper('getCroppedCanvas', {width: 400,height: 280})// 将 Canvas 画布上的内容,转化为文件对象.toBlob(function(blob) { // 5.1 组织参数对象 FormDatavar fd = new FormData($('#formAddArticle')[0])// 5.2 添加封面fd.append('cover_img', blob)// 5.3 添加文章的发表状态fd.append('state', pubState)// 5.4 发起请求$.ajax({method: 'POST',url: '/my/article/edit',data: fd,contentType: false,processData: false,success: function(res) {if (res.status !== 0) {return layer.msg('编辑文章失败!')}location.href = '/article/art_list.html'}})})
})

toBlob()方法的语法:canvas.toBlob(callback, type, encoderOptions)
用以展示canvas上的图片;这个图片文件可以被缓存或保存到本地,由用户代理端自行决定。如不特别指明,图片的类型默认为 image/png,分辨率为 96dpi。
第三个参数用于针对image/jpeg格式的图片进行输出图片的质量设置(值在0与1之间)。
更多详情请参阅 MDN文档

附< 文件在线转 Base64 地址>: https://www.css-js.com/tools/base64.html

至此,文章编辑功能的开发已算完成。

6.14 完整html结构

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="/assets/lib/layui/css/layui.css"><!-- 富文本样式 --><link rel="stylesheet" href="/assets/lib/cropper/cropper.css" /><link rel="stylesheet" href="/assets/css/article/art_edit.css">
</head><body><div class="layui-card"><div class="layui-card-header">修改文章</div><div class="layui-card-body"><!-- 修改文章的表单 --><form class="layui-form" lay-filter="addArticle" id="formAddArticle" enctype="multipart/form-data"><!-- 文章标题 --><div class="layui-form-item"><label class="layui-form-label">文章标题</label><div class="layui-input-block"><input type="text" name="title" required lay-verify="required" placeholder="请输入标题" autocomplete="off" class="layui-input" /></div></div><!-- 文章类别 --><div class="layui-form-item"><label class="layui-form-label">文章类别</label><div class="layui-input-block" id="art_cate"></div></div><!-- 文章内容 --><div class="layui-form-item"><label class="layui-form-label">文章内容</label><div class="layui-input-block" style="height: 400px;"><textarea name="content" id="content"></textarea></div></div><!-- 文章封面 --><div class="layui-form-item"><label class="layui-form-label">文章封面</label><div class="layui-input-block cropper-container"><!-- 文件选择框 --><input type="file" id="fileCover" accept="image/jpeg,image/png,image/gif,image/bmp" style="display: none;" /><div class="cropper-box"><img id="image" src=""></div><div class="right-box"><div class="img-preview"></div><button class="layui-btn layui-btn-danger" id="btnChooseCoverImage">选择封面</button></div></div></div><!-- 按钮区域 --><div class="layui-form-item"><div class="layui-input-block"><button class="layui-btn" lay-submit id="btnPublish">发布</button><button class="layui-btn layui-btn-primary" lay-submit id="btnSave">存为草稿</button></div></div><input type="hidden" name="Id" /></form></div></div><!-- 文章分类列表的模板 --><script type="text/html" id="selectArtCates"><select name="cate_id" lay-verify="required"><option value="">请选择文章类别</option>{{each data}}<option value="{{$value.Id}}">{{$value.name}}</option>{{/each}}</select></script><script src="/assets/lib/layui/layui.all.js"></script><script src="/assets/lib/template-web.js"></script><script src="/assets/lib/jquery.js"></script><script src="/assets/js/baseAPI.js"></script><!-- 富文本编辑器 --><script src="../../assets/lib/tinymce/tinymce.min.js"></script><script src="../../assets/lib/tinymce/tinymce_setup.js"></script><!-- 图片裁剪 --><script src="../../assets/lib/cropper/Cropper.js"></script><script src="../../assets/lib/cropper/jquery-cropper.js"></script><script src="/assets/js/article/art_edit.js"></script>
</body></html>

6.15 完整css 样式代码

html,body {margin: 0;padding: 0;
}body {padding: 15px;background-color: #f2f3f5;
}.cropper-box {width: 400px;height: 280px;
}#image {max-width: 400px;/* 这个设置很重要! */max-height: 280px;
}.img-preview {width: 200px;height: 140px;overflow: hidden;margin-bottom: 20px;
}.cropper-container {display: flex;
}.cropper-box {margin-right: 15px;
}.right-box {text-align: center;
}

6.16 编辑功能完整 JS 代码

$(function() {// 通过 URLSearchParams 对象,获取 前一个页面URL 传递过来的参数var params = new URLSearchParams(location.search)var artId = params.get('id')// 文章的发布状态var pubState = ''// 获取需要的 layui 对象var form = layui.form// 1. 渲染文章分类列表renderArticleCates()function renderArticleCates() {$.get('/my/article/cates', function(res) {if (res.status !== 0) {return layer.msg('获取文章分类列表失败!')}var htmlStr = template('selectArtCates', res)$('#art_cate').html(htmlStr)form.render()getArticleById()})}// 2. 根据文章的 Id,获取文章的详情,并初始化表单的数据内容function getArticleById() {// 发起请求,获取文章详情$.get('/my/article/' + artId, function(res) {// 获取数据失败if (res.status !== 0) {return layer.msg('获取文章失败!')}// 获取数据成功var art = res.data// 为 form 表单赋初始值form.val('addArticle', {Id: art.Id,title: art.title,cate_id: art.cate_id,content: art.content})// 手动初始化富文本编辑器initEditor()// 初始化图片裁剪器var $image = $('#image')$image.attr('src', 'http://ajax.frontend.itheima.net' + art.cover_img)// $image.attr('src', 'http://www.liulongbin.top:3007' + art.cover_img)// 裁剪选项var cropperOption = {aspectRatio: 400 / 280,preview: '.img-preview',// 初始化图片裁剪框的大小autoCropArea: 1}// 初始化裁剪区域$image.cropper(cropperOption)})}// 3. 选择封面$('#btnChooseCoverImage').on('click', function(e) {e.preventDefault()$('#fileCover').click()})// 4. 监听文件选择框的 change 事件$('#fileCover').on('change', function(e) {var files = e.target.files// 没有选择文件if (files.length === 0) {return}// 重新为裁剪区域设置图片$('#image').cropper('destroy').attr('src', URL.createObjectURL(files[0])).cropper({aspectRatio: 400 / 280,preview: '.img-preview'})})// 设置文章的发布状态$('#btnPublish').on('click', function() {pubState = '已发布'})$('#btnSave').on('click', function() {pubState = '草稿'})// 5. 发布文章$('#formAddArticle').on('submit', function(e) {e.preventDefault()$('#image').cropper('getCroppedCanvas', {width: 400,height: 280}).toBlob(function(blob) {// 5.1 组织参数对象var fd = new FormData($('#formAddArticle')[0])// 5.2 添加封面fd.append('cover_img', blob)// 5.3 添加文章的发表状态fd.append('state', pubState)// 5.4 发起请求$.ajax({method: 'POST',url: '/my/article/edit',data: fd,contentType: false,processData: false,success: function(res) {if (res.status !== 0) {return layer.msg('编辑文章失败!')}location.href = '/article/art_list.html'}})})})
})

7. 将开发完成的项目代码推送到GitHub

1、检查当前所在分支:

git branch

2、本地提交

 git add .

3、检查所有文件状态

git status

4、运行 git commit -m

git commit -m "完成文章管理相关功能的开发"

5、将本地article分支提交到 Github 存储

git push -u origin article

6、将本地article分支里最新的代码合并到master主分支
1) 切换到 master 主分支

git checkout master

2)合并article里最新的代码

git merge article

7、将本地 master最新代码推送到Github中

git push

大事件后台管理系统开发实战(下)相关推荐

  1. 大事件后台管理系统开发实战(上)

    文章目录 前言 0. 资源地址 1. 项目前期的准备工作 1.1 初始化项目结构 1.2 使用GitHub管理大事件的项目 1.3 安装VSCode的Live Server插件辅助开发 2. 登录注册 ...

  2. 大事件后台管理系统开发实战(中)

    文章目录 3. 后台主页 3.8 获取用户的基本信息 3.9 渲染用户头像 3.10为有权限的接口统一设置headers请求头 3.11 实现退出功能 3.12 控制用户的访问权限 3.13 优化权限 ...

  3. 视频教程-SSM后台管理系统开发实战-Java

    SSM后台管理系统开发实战 5年IT从业经验,目前职位是Java高级工程师.架构师,在gitchat发布有<SSM博客系统开发实战>达人课,CSDN博客专家,博客专栏作者,梦境网项目独立开 ...

  4. SSM后台管理系统开发实战

    一.简介: 通过这个课程带大家从零开发一款功能全面的后台管理系统,包括项目搭建.功能实现到最后的Linux系统部署全过程.本课程使用SpringMVC + Spring + Mybatis作为主体框架 ...

  5. 前端基础第三天项目 大事件后台管理系统

    0. 资源地址 线上 DEMO 项目地址:http://www.escook.cn:8086/ 项目的 API 接口地址: https://www.showdoc.cc/escook?page_id= ...

  6. 大事件后台管理系统——个人中心

    1.创建验证规则函数 个人中心--基本资料:用来修改用户信息 针对表单不同类型的数据需要设定验证规则函数,只有输入正确才能提交表单 form.verify({nickname: function(va ...

  7. 大事件后台管理系统——文章管理

    1.文章类别模块 1.1获取文章分类的列表 利用模版引擎快速渲染表格数据 <!-- 表格数据的模板 --><script type="text/html" id= ...

  8. 《伴我汽车》后台管理系统开发笔记 下

    ok,欢迎来到后半部分, 如果你是直接访问到我的第二部分的话,可以到第一部分下载源码:https://blog.csdn.net/qq_44850489/article/details/1095027 ...

  9. Vue 2.x 实战之后台管理系统开发(二)

    1. 导语 承接上文:Vue 2.x 实战之后台管理系统开发(一) 在上一篇文章中,我详细叙述了如何创建项目框架和引入各种后台常用插件,做好这些准备工作后,我们就可以着手进行页面的开发了.在开发过程中 ...

最新文章

  1. 【 C 】const 学习笔记
  2. android realm删除对象,Android Realm-从服务访问Realm对象
  3. 洛谷P1456 Monkey King
  4. PowerDesigner设计数据库
  5. UML建模之部署图(Deployment Diagram)
  6. mysql索引抽密度_使用python脚本从abaqus输出数据库获取元素密度
  7. linux sysstat rpm包下载,[20141201]SYSSTAT软件包.txt
  8. 打开python环境_windows下切换Python运行环境。
  9. python基础:数据类型一
  10. 第一百八十四节,jQuery-UI,验证注册表单
  11. 计算机网络的各层及其协议,计算机网络的体系结构 (architecture) 是计算机网络的各层及其协议的集合...
  12. SPSS异常值处理(图文+数据集)【SPSS 010期】
  13. android开发案例1---拦截电话,拯救史迪仔,有序广播
  14. 如何通过a链接实现图片下载
  15. 计算机关机键是,电脑关机快捷键是什么
  16. 弹丸论破2 中文攻略
  17. Qt教程(新手入门级)
  18. java imageio_java-ImageIO.write()方法和png
  19. vs编译错误:在查找预编译头时遇到意外的文件结尾。是否忘记了向源中添加“#include stdafx.h”?
  20. 疯狂动物消消乐html5游戏在线玩,疯狂动物消消乐

热门文章

  1. An Energy-Efficient Ant-Based Routing Algorithm for Wireless Sensor Networks (无线传感网中一种基于蚁群算法的能量有效路由)
  2. Win7虚拟无线AP以及Android手机抓包
  3. 共享内存:mmap函数实现
  4. zookeeper结构和命令详解
  5. Problem E: 校庆
  6. 如何缩小码农和高手的差距
  7. Java 调用 Impala - JDBC 调用Impala
  8. 「技术人生」第2篇:学会分析事物的本质
  9. 资深技术专家崮德在阿里管理研发团队的实践和思考
  10. 蚂蚁金服王旭:开源的意义是把社区往前推进一步