# 大事件综合项目实战

# 1. 初始化环境

大事件为综合实战项目,但是我们只关注前端的实现,数据库与后端相关内容用老师提供的即可。

# 1.1 数据库初始化

执行初始化数据库脚本前,需要确保已经创建bignews数据库

// 登录数据库后,可以执行下面命令创建
create database bignews

# 1.2 执行初始化数据库脚本

执行脚本之前,需要修改配置文件config/index.js,把数据库密码修改为自己的密码。

module.exports ={
    // 数据库名
    database:'bignews',
    // 用户名
    username:'root',
    // 密码
    password:'123456', 
    host:'127.0.0.1',
    port:8080,
    baseUrl:'http://localhost'
}

修改完毕后,双击根目录的初始化数据.bat文件把初始数据导入数据库中。

image-20200820111634567

# 1.3 启动服务端

因为页面需要请求服务器数据,在开发时需要确保服务端已经启动,双击根目录下的开始.bat启动即可

image-20200820111842928

# 2. 项目结构

# 3.登录页面

image-20200821105645016

页面路径:/admin/login.html

逻辑路径:/admin/js/login.js

💡知识点一:token处理,为了保存用户登录状态,需要把token在登录成功时保存到本地缓存

💡知识点二:模态框显示与隐藏, 浏览器自带的alert提示框太简陋,可以集成bootstrap的模态框插件更友好的提示信息

# 3.1 登录功能实现

// 1、用户登录
function login() {
  // 1.1 获取用户名和密码,并且去除空格
  const username = $('.input_txt').val().trim();
  const password = $('.input_pass').val().trim();

  // 1.2 判断用户名或者密码是否为空
  if (username === '' || password === '') {
    // 显示模态框
    $('.modal-body').text('用户名和密码不能为空');
    $('#loginModal').modal('show')
    // 阻止后面代码的执行
    return;
  }

  // 1.3 发送用户名和密码到服务端
  $.post('http://localhost:8080/api/v1/admin/user/login', { username, password }, (res) => {
    // code等于200表示登录成功
    if (res.code === 200) {
      // 1.4 把服务端返回的token记录到本地存储
      window.localStorage.setItem('bignews_token', res.token);
      // 1.5 跳转到首页
      window.location.href = './index.html'
    } else {
      // 1.6 登录失败,使用模态框显示错误信息
      $('.modal-body').text(res.msg);
      $('#loginModal').modal('show')
    }
  })
}

# 3.2 模态框提示信息

image-20200821112428791

login.html加入模态框代码片段

<!-- 模态框 -->
<div class="modal fade" id="loginModal" tabindex="-1" role="dialog">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title">友情提示</h4>
      </div>
      <div class="modal-body">
        <p></p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-primary" data-dismiss="modal">朕知道了</button>
      </div>
    </div>
  </div>
</div>

login.js控制模态框显示状态与文本

// 设置模态框文本内容
$('.modal-body').text('提示内容');
// 弹出模态框
$('#loginModal').modal('show')

# 4. 首页

image-20200821114956290

页面路径:/admin/index.html

逻辑路径:/admin/js/index.js

💡知识点一:iframe的使用,首页通过点击侧边栏,中间区域切换显示不同内容

# 4.1 显示个人信息

image-20200821151843684

因为个人信息接口需要登录后才能访问,所以我们把登录成功后得到的token放到请求头中

$.ajax({
  type: 'get',
  url: 'http://localhost:8080/api/v1/admin/user/info',
  headers: {
    // 记得把token放在请求头,否则会出现权限问题
    Authorization: window.localStorage.getItem('bignews_token'),
  },
  success: (res) => {
    if (res.code == 200) {
      // 返回数据后,把昵称头像等信息放到页面上
      $('.user_info > span').html('欢迎&nbsp;&nbsp;' + res.data.nickname);
      $('.user_info > img').attr('src', res.data.userPic);
      $('.user_center_link > img').attr('src', res.data.userPic);
    }
  },
  error: (err) => {
    // 如果服务端返回403的壮体,说明没有访问权限,跳转到登录页
    if (err.status === 403) {
      window.location.href = './login.html';
    }
  },
});

# 4.2 利用iframe实现页面切换

首先在div中插入iframe,并设置name属性,name属性可以让a标签找到它,设置宽高为100%同时把src属性设置为./main_count.html

<!-- 右侧主体内容 -->
<div class="main" id="main_body">
  <iframe name="main_frame" src="./main_count.html" frameborder="0" width="100%" height="100%"></iframe>
</div>

把侧边栏a标签的src属性设置为不同页面,下面是html文件对应关系

main_count.html 首页

article_list.html 文章列表

article_release.html 发表文章

article_category.html 文章类别管理

comment_list.html 评论管理

user.html 个人中心

# 4.3 侧边栏点击效果

1、实现点击一级分类显示激活状态

2、实现点击“文章管理”分类展开二级分类

// 点击侧边栏切换成激活状态
$('.level01').on('click', function(e) {
  // 1.1 把已经选中的元素先删除active状态
  $('.level01.active').removeClass('active')
  // 1.2 把点击的元素增加active的类名
  $(this).addClass('active');
  // 2.1 如果点击的是“文章管理”分类,展开二级分类
  if($(this).html().indexOf('文章管理') > -1) {
    // 2.2 展开子分类
    $('.level02').slideToggle();
    // 2.3 把箭头旋转
    $(this).find('b').toggleClass('rotate0')
  }
})

3、点击二级分类,二级分类显示激活状态

// 把二级分类设置点击显示激活状态
$('.level02>li').on('click',function(){
  $('.level02>li.active').removeClass('active')
  $(this).addClass('active')
});

# 5. 个人中心

image-20200821173400647

页面路径:/admin/user.html

逻辑路径:/admin/js/user.js

# 5.1 显示个人信息到输入框

// 进入页面,获取用户信息
$.ajax({
  type: 'get',
  url: 'http://localhost:8080/api/v1/admin/user/detail',
  headers: {
    // 记得把token放在请求头,否则会出现权限问题
    Authorization: window.localStorage.getItem('bignews_token'),
  },
  success: (res) => {
    if (res.code == 200) {
      // 用户名
      $('#username').val(res.data.username);
      // 昵称
      $('#nickname').val(res.data.nickname);
      // 邮箱
      $('#email').val(res.data.email);
      // 密码
      $('#password').val(res.data.password);
      // 头像
      $('.user_pic').attr('src', res.data.userPic);
    }
  },
  error: (err) => {
    // 如果服务端返回403的壮体,说明没有访问权限,跳转到登录页
    if (err.status === 403) {
      window.location.href = './login.html';
    }
  },
});

# 5.2 封装网络层

因为发送ajax请求时,都需要在发送前获取token放在请求头中,如果服务端响应403错误码都要跳转到登录页面,这些相同的处理会出现大量重复代码,所以我们需要把代码进行封装

http.js文件代码

const http = { };
// 基础URL
const BASE_URL = 'http://localhost:8080/api/v1'
/**
 * get请求方法
 * @param {*} url 请求的地址
 * @param {*} data 请求参数
 * @param {*} success 成功回调
 */
http.request = function(type ,url, data, success) {
  $.ajax({
    type: type,
    url: BASE_URL + url,
    data: data,
    headers: {
      // 记得把token放在请求头,否则会出现权限问题
      Authorization: window.localStorage.getItem('bignews_token'),
    },
    success: (res) => {
      success(res)
    },
    error: (err) => {
      // 如果服务端返回403的壮体,说明没有访问权限,跳转到登录页
      if (err.status === 403) {
        window.location.href = './login.html';
      }
    },
  });
}

那么刚才获取用户信息的请求就可以改成下面代码

http.request('get', '/admin/user/detail', {}, (res) => {
  if (res.code == 200) {
    // 用户名
    $('#username').val(res.data.username);
    // 昵称
    $('#nickname').val(res.data.nickname);
    // 邮箱
    $('#email').val(res.data.email);
    // 密码
    $('#password').val(res.data.password);
    // 头像
    $('.user_pic').attr('src', res.data.userPic);
  }
})

# 5.3 预览上传图片

预览图片上传最核心是使用了URL.createObjectURL方法创建图片URL,然后把URL放到img标签中

// 2、图片预览
// 2.1 监听onchange事件
$('#exampleInputFile').on('change', () => {
  // 2.2 获取file对象
  const file = $('#exampleInputFile')[0].files[0];
  // 2.3 使用createObjectURL方法创建临时URL
  const url = URL.createObjectURL(file);
  // 2.4 把临时图片URL放回image标签的src属性
  $('.user_pic').attr('src', url);
})

# 5.4 修改个人信息

$('.btn-edit').on('click', (e) => {
  e.preventDefault();
  // 3、收集编辑信息并提交给服务端
  // 3.1 服务端需要使用FromData的数据类型,那我们就可以利用FromData对象自动收集表单数据了
  const myform = $('#form')[0]; // 把jq对象转换为普通对象
  // 3.2 创建formdata对象,把表单作为参数参数,会自动收集具有name属性的input字段
  const formdata = new FormData(myform);
  // 3.3 发送请求
  http.request('post', '/admin/user/edit', formdata, (res) => {
    if(res.code === 200) {
      alert(res.msg);
    }
  })
});

⚠️注意,因为上面封装的http.request方法不支持FormData类型数据发送,所以在调用该方法前需要先进行类型判断,加入processDatacontentType属性设置

http.request = function(type ,url, data, success) {
  // 如果是FormData对象,需要设置processData为false,contentType为false
  const isFormData = (data instanceof FormData); 
  $.ajax({
    processData: isFormData? false: true,
    contentType: isFormData? false: 'application/x-www-form-urlencoded',
  });
}

# 6. 文章管理--分类模块

image-20200824110213437

页面路径:/admin/article_category.html

逻辑路径:/admin/js/article_category.js

# 6.1 获取所有分类

接口地址 /admin/category/list

/admin/js/article_category.js

// 1、获取所有分类
function getAll() {
  http.request('get', '/admin/category/list', {}, (res) => {
    if (res.code === 200) {
      // 使用artTemplate渲染数据
      const renderResult = template('categoryTemplate', res);
      $('tbody').html(renderResult);
    }
  })
}
getAll();

/admin/article_category.html

<script id='categoryTemplate' type="text/html">
        {{each data}}
            <tr>
                <td>{{$value.name}}</td>
                <td>{{$value.slug}}</td>
                <td class="text-center">
                    <a href="javascript:void(0)" data-toggle="modal" class=" btn btn-info btn-xs">编辑</a>
                    <a href="javascript:void(0)" class="btn btn-danger btn-xs">删除</a>
  </td>
  </tr>
        {{/each}}
</script>

# 6.2 删除分类

/admin/js/article_category.js

// 2、实现删除分类
function deleteCategory(id) {
  http.request('post', '/admin/category/delete', { id }, (res) => {
    if (res.code === 204) {
      alert(res.msg);
      this.getAll();
    }
  })
}

/admin/article_category.html

<!-- 加入删除方法的调用 -->
<a href="javascript:void(0)" class="btn btn-danger btn-xs" onclick="deleteCategory({{$value.id}})">删除</a>

# 6.3 编辑分类

点击编辑按钮的时,传入data数据,data数据表示当前编辑的分类,将数据还原到输入框中

⚠️注意,当前分类id因为发送请求的时候需要用到,我们把它保存在this中

function editCategory(data) {
  // 3.1 显示编辑分类窗
  $('.add_category_wraper').show();
  // 3.2 data为当前编辑的分类数据,需要获取data的数据还原到输入框上
  $('#name').val(data.name);
  $('#slug').val(data.slug);
  this.id = data.id;
}

点击保存时,要注意因为新增和编辑都是用同一个按钮,可以通过this.id是否有值判断是否为编辑操作

function saveCategory() {
  const id = this.id;
  const name = $('#name').val();
  const slug = $('#slug').val();
  // 如果有id值,说明是在编辑状态
  if (id) {
    http.request('post', '/admin/category/edit', { id, name, slug }, (res) => {
      alert(res.msg);
      // 隐藏分类窗
      this.hideCategory();
      this.getAll();
    })
  } else {
    // 新增状态
    http.request('post', '/admin/category/add', { name, slug }, (res) => {
      alert(res.msg);
      this.hideCategory();
      this.getAll();
    })
  }
}

# 6.4 新增操作

点击新增按钮,只要把输入框弹出即可

function showCategory() {
  $('.add_category_wraper').show();
}

点击保存的时候,发送ajax请求

function saveCategory() {
  const id = this.id;
  const name = $('#name').val();
  const slug = $('#slug').val();
  // 如果有id值,说明是在编辑状态
  if (id) {
    http.request('post', '/admin/category/edit', { id, name, slug }, (res) => {
      alert(res.msg);
      // 隐藏分类窗
      this.hideCategory();
      this.getAll();
    })
  } else {
    // 新增状态
    http.request('post', '/admin/category/add', { name, slug }, (res) => {
      alert(res.msg);
      this.hideCategory();
      this.getAll();
    })
  }
}

# 6.5 修复数据显示的bug

当点击编辑后,数据会还原到输入框,当再次点击新增会导致输入框内有上次编辑的内容,为了修复这个问题,可以在隐藏编辑框的时候清空数据

function hideCategory() {
  $('.add_category_wraper').hide();
  // 为了修复点击编辑后再点击新增,导致数据显示问题,所以每次隐藏的时候,清空数据
  this.id = null;
  $('#name').val('');
  $('#slug').val('');
}

# 7.文章管理

image-20200824161947556

页面路径:/admin/article_list.html

逻辑路径:/admin/js/article_list.js

# 7.1 获取所有分类

image-20200824162141674

/admin/js/article_list.js

function getAllCategory() {
  http.request('get', '/admin/category/list', {}, (res) => {
    if (res.code === 200) {
      const renderResult = template('categoryTemplate', res)
      $('#selCategory').html(renderResult);
    }
  })
}
getAllCategory();

/admin/article_list.html

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

# 7.2 获取文章列表

image-20200824170020242

/admin/js/article_list.js

function getArticle() {
  // 筛选分类
  const type = $('#selCategory').val();
  // 文章状态,草稿或者已发布
  const state = $('#selStatus').val();
  http.request('get', '/admin/article/query', {type, state}, (res) => {
    const renderResult = template('articleTemplate', res.data)
    $('tbody').html(renderResult);
  });
}

/admin/article_list.html

<!-- 文章列表模版 -->
<script id="articleTemplate" type="text/html">
        {{each data}}
        <tr>
            <td>{{$value.title}}</td>
            <td>{{$value.author}}</td>
            <td>{{$value.category}}</td>
            <td class="text-center">{{$value.daate}}</td>
            <td class="text-center">{{$value.state}}</td>
            <td class="text-center">
                <a href="article_edit.html?id={{$value.id}}" class="btn btn-default btn-xs">编辑</a>
                <a href="javascript:void(0);" class="btn btn-danger btn-xs delete">删除</a>
  </td>
  </tr>
        {{/each}}
</script>

# 7.3 删除文章

// 3、删除文章
function deleteArticle(id) {
  http.request('post', '/admin/article/delete', {id}, (res) => {
    if (res.code === 204) {
      alert('删除成功')
      this.getArticle();
    } else {
      alert(res.msg);
    }
  })
}

# 7.4 文章分页

image-20200827092307867

由于文章数据较多,一次请求所有文章会导致访问非常缓慢,所以当碰到较多数据时,往往通过分页来实现分批获取数据。

分页中常出现的字段:

  1. totalCount:文章的总条数
  2. perpage:每页显示条数
  3. totalPage:文章的总页数,该字段和每页显示的条数有关联关系,例如totalCount为10,perpage为2条,那totalPage就是5。
  4. page:当前页

分页功能我们可以借助jquery的插件完成,简化实现的过程,当然你也可以自己实现,也不是很困难。

使用twbsPagination插件实现分页步骤:

1、引入twbsPagination插件

2、初始化分页插件

首先要在页面增加一个元素,用于显示分页HTML

<ul id="pagination"></ul>

通过js初始化分页插件

  $('#pagination').twbsPagination({
    totalPages: total, // 总的页数
    visiblePages: 5, // 显示多少页
    first:'首页',
    last:'末页',
    prev:'上一页',
    next:'下一页',
    // 当点击页码获取对应页面数据
    onPageClick:  (event, page) => {
      this.getArticle(page);
    }
  });

根据页码获取文章:

function getArticle(page) {
  // 筛选分类
  const type = $('#selCategory').val();
  // 文章状态,草稿或者已发布
  const state = $('#selStatus').val();
  http.request('get', '/admin/article/query', { type, state, page }, (res) => {
    const renderResult = template('articleTemplate', res.data);
    $('tbody').html(renderResult);
    // 初始化分页组件
    this.initPagination(res.data.totalPage);
  });
}

# 7.5 文章编辑

image-20200827163419776

准备知识:jedate时间插件tinyMCE富文本编辑插件

# 7.5.1 jedate的使用

image-20200827141106777

文章编辑模块有一个发布时间输入框用于获取文章的发布时间,如果管理员手工输入时间,会比较麻烦而且容易出错,所以我们需要一个时间插件让用户选择时间。

下面是最简单的使用代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- 1、引入jedate插件资源 -->
    <link rel="stylesheet" href="./jedate/css/jedate.css" />
    <script src="./jedate/js/jedate.js"></script>
    <title>Document</title>
  </head>
  <body>
    <!-- 2、页面定义input用户选择时间 -->
    <input id="myDate" type="text" placeholder="请选择"  readonly>
  </body>
  <script>
    // 3、初始化日期插件
    jeDate('#myDate', {
      format: 'YYYY-MM-DD', // 显示格式
    });
  </script>
</html>

# 7.5.2 tinyMCE使用

image-20200827141752103

在编写文章时,往往需要对文字进行加粗,加斜体之类的格式,但是网页不像word那样可以随意设置格式,html中不同格式需要相应的标签进行包裹,例如加粗<strong>文章标题</strong>,为了实现网页能像word一样编辑文字,需要用到富文本编辑器tinyMCE

tinyMCE使用代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>富文本</title>
  <!-- 1、引入资源 -->
  <script src="./tinymce/tinymce.min.js"></script>
</head>
<body>
  <!-- 2、定义富文本元素 -->
  <div id="richText"></div>
</body>
<!-- 3、初始化 -->
<script>
  tinymce.init({
    selector: "#richText"
  })
</script>
</html>

# 7.5.3 整合时间插件和富文本插件

/admin/article_edit.html

1、头部引入资源

<!-- 引入时间插件 -->
<script src="./libs/jedate/js/jedate.js"></script>
<link rel="stylesheet" href="./libs/jedate/css/jedate.css">
<!-- 引入富文本插件 -->
<script src="./libs/tinymce/tinymce.min.js"></script>

2、页面加入必要元素

<div class="col-sm-4">
  <!-- 时间选择框 -->
  <input id="articleDate" name='date' class="jeinput" type="text" placeholder="请选择发布日期"  readonly>
</div>

<div class="col-sm-10">
  <!-- 富文本编辑区域 -->
  <textarea id="articleContent">请输入文章内容</textarea>
</div>

/admin/js/article_edit.js

// 1、初始化时间插件和富文本插件
function initPlugin() {
  // 初始化日期插件
  jeDate('#articleDate', {
    format: 'YYYY-MM-DD', // 显示格式
  });
  // 初始化富文本插件
  tinymce.init({
    selector: "#articleContent",
    language: 'zh_CN',
  })
}
// 页面进入即初始化插件
initPlugin();

# 7.5.4 获取文章分类

Screenshot020-08-274.58.10

/admin/js/article_edit.js

// 1、获取分类
function getCategory() {
  http.request('get' ,'/admin/category/list', {} ,(res) => {
    const renderResult = template('categoryTemplate', res);
    console.log(renderResult)
    $('#category').html(renderResult);
  })
}
getCategory();

/admin/article_edit.html

<script id="categoryTemplate" type="text/html">
        {{each data}}
            <option value="{{$value.id}}">{{$value.name}}</option>
        {{/each}}
</script>

# 7.6.5 封装把url参数转换为对象方法

从列表页跳转到编辑页面,需要把当前编辑文章的id传递给当前页面,一般使用url进行传值,例如

http://127.0.0.1:3000/article_edit.html?id=1

可以通过window.location.search获取请求参数?id=1,再通过=作为标记获取id的值,但是这样操作非常麻烦。我们需要把这种操作封装到一个公共方法中。

1、新建common.js

2、代码如下

const itcast = {
  // 动态的添加项目中需要的功能函数
  getArguments:function(){ //?id=2&name=jack
      const str = window.location.search;
      let obj = {}
      // 1.去除?
      str = str.substring(1) // id=2&name=jack
      // 2.将str以&符合进行分隔
      let arr = str.split('&') // ["id=2","name=jack"]
      // 3.循环,并以=做为分隔符对数组中的元素再次分隔,将分隔的结构一个做为键,一个做为值
      for(let i=0;i<arr.length;i++){
          // 第一次:id=2
          let temp = arr[i].split('=') // ["id",2]
          // 生成对象
          obj[temp[0]] = temp[1]
      }
      return obj
  }
}

# 7.5.6文章数据回显到输入框

// 3、获取文章详情
function getArticle() {
  // 3.1 获取url上的id值
  const id = window.location.search.split('=')[1];
  // 3.2 获取文章数据
  http.request('get', '/admin/article/search', { id }, (res) => {
    if (res.code === 200) {
      $('#inputTitle').val(res.data.title);
      // 下拉列表
      $('#category').val(res.data.categoryId);
      // 文章封面
      $('.article_cover').attr('src', res.data.cover);
      // 发布时间
      $('#articleDate').val(res.data.date);
      // 富文本框
      $('#articleContent').val(res.data.content);
    }
  });
}
getArticle();

# 7.5.7 实现更改封面图片回显

// 4、实现数据回显
function changeCoverImage() {
  // 1.获取图片对象
  let myfile = $('#inputCover')[0].files[0];
  // 2.根据图片对象创建一个url
  let url = URL.createObjectURL(myfile);
  // 3.将url赋值给img标签
  $('.article_cover').attr('src', url);
}

# 7.5.8 修改文章

⚠️注意,因为编辑接口需要提交FormData类型数据,所以提交数据时需要利用FormData对象收集表单数据,富文本插件数据需要用过tinymce.activeEditor.getContent()方法获取,再通过append方法添加到formdata中。

/**
 * 保存文章
 * @param {*} state 已发布 或者 草稿
 */
function saveArticle(state) {
  let myform = new FormData($('form')[0]);
  // 获取富文本内容
  const content = tinymce.activeEditor.getContent();
  // 获取文章id
  const id = window.location.search.split('=')[1];
  myform.append('content', content);
  myform.append('id', id);
  myform.append('state', state);
  // 发送请求,把数据传递给服务器
  http.request('post', '/admin/article/edit', myform, (res) => {
    if (res.code === 200) {
      alert(res.msg);
      window.location.href = "./article_list.html"
    }
  })
  // 阻止表单自动跳转
  return false;
}

# 7.6 发表新文章

页面地址:/admin/article_release.html

逻辑地址:/admin/js/article_release.js

发表新文章代码基本和编辑相同,可以复制编辑功能代码或者对编辑代码进行封装适配新增功能

新增功能和编辑功能不同点:

1、新增不需要还原数据

2、保存数据的时候,不需要提交id给服务端,请求地址也不同。

# 8、首页图表

image-20200827175544064

# 8.1 eCharts快速入门

1、引入 ECharts

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <!-- 引入 ECharts 文件 -->
    <script src="echarts.min.js"></script>
</head>
</html>

2、绘制一个简单的图表

在绘图前我们需要为 ECharts 准备一个具备高宽的 DOM 容器。

<body>
    <!-- 为 ECharts 准备一个具备大小(宽高)的 DOM -->
    <div id="main" style="width: 600px;height:400px;"></div>
</body>

然后就可以通过 echarts.init 方法初始化一个 echarts 实例并通过 setOption 方法生成一个简单的柱状图,下面是完整代码。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>ECharts</title>
    <!-- 引入 echarts.js -->
    <script src="echarts.min.js"></script>
</head>
<body>
    <!-- 为ECharts准备一个具备大小(宽高)的Dom -->
    <div id="main" style="width: 600px;height:400px;"></div>
    <script type="text/javascript">
        // 基于准备好的dom,初始化echarts实例
        var myChart = echarts.init(document.getElementById('main'));

        // 指定图表的配置项和数据
        var option = {
            title: {
                text: 'ECharts 入门示例'
            },
            tooltip: {},
            legend: {
                data:['销量']
            },
            xAxis: {
                data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
            },
            yAxis: {},
            series: [{
                name: '销量',
                type: 'bar',
                data: [5, 20, 36, 10, 10, 20]
            }]
        };

        // 使用刚指定的配置项和数据显示图表。
        myChart.setOption(option);
    </script>
</body>
</html>

这样你的第一个图表就诞生了!

image-20200828113256749

# 9、评论管理

image-20200828171209398

页面地址:/admin/comment_list.html

逻辑地址:/admin/js/comment_list.js

# 9.1 显示评论列表

/admin/comment_list.html

  <script id="commentTemplate" type="text/html">
    {{each data item index}}
    <tr>
      <td>{{item.author}}</td>
      <td>{{item.content}}</td>
      <td>{{item.title}}</td>
      <td>{{item.date}}</td>
      <td>{{item.state}}</td>
      <td class="text-center">
        <a href="javascript:void(0);;" class="btn btn-warning btn-xs">拒绝</a>
        <a href="javascript:void(0);;" class="btn btn-danger btn-xs">删除</a>
      </td>
    </tr>
    {{/each}}
  </script>

/admin/js/comment_list.js

获取评论数据并调用template方法渲染模版

// 1、获取评论列表数据
function getCommentList(page) {
  http.request('get', '/admin/comment/search', { page, perpage: 10 }, (res) => {
    if (res.code === 200) {
      // 使用art-template渲染数据
      const renderResult = template('commentTemplate', res.data);
      $('tbody').html(renderResult);
      // 2.1 初始化分页插件
      initPagination(res.data.totalPage, 1);
    }
  })
  
}
getCommentList(1);

# 9.2 实现分页

1、引入jquery分页插件

<script src="./libs/jquery.twbsPagination.js"></script>

2、分页插件初始化

// 2、实现分页
function initPagination(totalPages) {
  $('#pagination').twbsPagination({
    totalPages, // 总的页数
    visiblePages: 5, // 显示多少页
    first:'首页',
    last:'末页',
    prev:'上一页',
    next:'下一页',
    onPageClick:  (event, page) => {
      this.getCommentList(page);
    }
  });
}

# 9.3 批准评论

// 3、批准评论
$('tbody').on('click', '.btnAccept', (e) =>{
  const id = $(e.target).attr('data-id');
  http.request('post', '/admin/comment/pass', { id }, (res) => {
    if (res.code === 200) {
      // 刷新页面数据
      this.getCommentList();
    } else {
      alert(res.msg);
    }
  })
})

# 9.4 拒绝评论

// 4、拒绝评论
$('tbody').on('click', '.btnReject', (e) =>{
  const id = $(e.target).attr('data-id');
  http.request('post', '/admin/comment/reject', { id }, (res) => {
    if (res.code === 200) {
      // 刷新页面数据
      this.getCommentList();
    } else {
      alert(res.msg);
    }
  })
})

# 9.5 删除评论


// 5、删除评论
$('tbody').on('click', '.btnDelete', (e) =>{
  const id = $(e.target).attr('data-id');
  http.request('post', '/admin/comment/delete', { id }, (res) => {
    if (res.code === 200) {
      // 刷新页面数据
      this.getCommentList();
    } else {
      alert(res.msg);
    }
  })
})