# 演示地址和接口文档

案例接口文档:https://docs.apipost.cn/view/8675137ccc2b3ac0#3361314

线上演示地址:http://www.itcbc.com:8888/login.html

# 安装vscode的live server插件

# 安装

image-2020112309057847

# 作用

  • 模拟真实环境(服务器环境)打开页面。
  • 修改页面后,浏览器会自动刷新。

# 使用

安装后,编辑器打开html文件,点击右键菜单的 “Open with Live Server”,就会在浏览器预览页面了。

使用 Live Server 插件打开HTML之后,浏览器地址栏一般都会显示 127.0.0.1:5500/xxx.html

# 必须使用live server的硬性条件

  1. 大事件中,有一个图片剪裁插件,必须使用live server的方式打开页面

  2. 项目使用了iframe标签,并且涉及到调用父页面的函数,必须使用live server的方式打开页面

# 易错点和注意事项

  1. 该插件的快捷键不是 Alt + B,不要按照之前的思维打开你的页面,需要慢慢适应。

  2. 如果你的live server不能用,安装 preview on web server 插件也可以,安装之后,编辑器打开html文件,点击右键菜单的 “vscode-preview-server:Launch on browser”,就会在浏览器预览页面了。

# 搭建项目目录

  • 创建 bigevent 文件夹,它就是我们的项目文件夹
  • 资料 里面的 assetshome 复制到 bigevent 里面
  • vscode打开 bigevent 文件夹
|- home
	|- dashboard.html      ---    后台首页图表页面
|- assets
	|- js                  ---    空文件夹,里面准备存放自己写的js文件
	|- css                 ---    空文件夹,里面准备存放自己写的css文件
	|- images              ---    存放的页面布局所需的图片
	|- lib                 ---    存放第三方工具
		|- jquery.js
		|- template-web.js
		|- layui
		|- tinymce         ---    富文本编辑器插件,添加文章时使用
		|- cropper         ---    图片剪裁插件(更换头像、添加文章使用)

# 使用Git管理项目

  • 初始化 git init
  • 添加基础的代码 到 暂存区 git add .
  • 提交代码到本地仓库 git commit -m '提交了基础的代码'
  • 创建远程仓库(自愿创建码云或github仓库)
  • 添加远程仓库地址 git remote add 别名 地址
  • 首次推送 git push -u 别名 master

具体操作:

# 项目即推送到码云、也推送到github

# 初始化
git init

# 添加初始文件到暂存区(windows可能看的一堆警告,没有问题,正常)
git add .

# 提交文件到本地仓库
git commit -m '提交了初始的文件'

# 创建远程仓库

# 添加两个远程仓库的 ssh地址
git remote add o1 git@gitee.com:laotang1234/bigevent-123.git
git remote add o2 git@github.com:Laotang1234/bigevent-123.git

# 推送到码云
git push -u o1 master

# 推送到github
git push -u o2 master

后续,新增了什么功能,及时的让Git记录。

# 创建项目通用的JS文件

项目的Ajax请求根路径 为 http://www.itcbc.com:8080 。所以,可以创建 assets/js/common.js 。在该js文件中 使用jQuery提供的 $.ajaxPrefilter() 方法统一配置大事件项目的接口根路径、headers和complete。

/assets/js/common.js

// 全局变量 baseUrl,以便后续多次使用
let baseUrl = 'http://www.itcbc.com:8080';

$.ajaxPrefilter(function (option) {
    // option 就是ajax选项;我们可以修改它,也可以增加一些选项
    // 1. 统一配置url
    option.url = baseUrl + option.url;

    // 2. 统一配置请求头
    option.headers = {
        Authorization: localStorage.getItem('token')
    };
    
    // 3. 请求完成后,如果接口返回“身份认证失败”,则需要跳转到登录页面
    option.complete = function (xhr) {
        var res = xhr.responseJSON;
        if (res && res.status === 1 && res.message === '身份认证失败!') {
            localStorage.removeItem('token');
            location.href = './login.html';
        }
    }
});

配置好之后,各个页面,在调用接口之前,只需要提前加载好common.js即可

# 登录和注册页面处理

# 准备工作

bigevent 根目录中

  • 创建 login.html (登录注册页面)
  • 创建 /assets/css/login.css
  • 创建 /assets/js/login.js
  • 加载所需的css和js文件

login.html

<title>Document</title>
<!-- 无论是css还是js,都需要先加载别人的css和js,最后加载自己的css和自己的js -->

<!-- 加载css文件 -->
<link rel="stylesheet" href="./assets/lib/layui/css/layui.css">
<link rel="stylesheet" href="./assets/css/login.css">


<!-- body区,加载js文件 -->
<script src="./assets/lib/jquery.js"></script>
<script src="./assets/lib/layui/layui.all.js"></script>
<script src="./assets/js/common.js"></script>
<script src="./assets/js/login.js"></script>

# 页面布局

login.html

<body>

    <!-- logo图片 -->
    <img id="logo" src="./assets/images/logo.png" alt="">


    <!-- 登录的盒子 start -->
    <div class="box login">
        <div class="title">大事件后台管理系统</div>
        <!-- 登录的表单 start -->
        <form class="layui-form" action="">
            <!-- 第一项:用户名 -->
            <div class="layui-form-item">
                <i class="layui-icon layui-icon-username"></i>
                <input type="text" name="title" required lay-verify="required" placeholder="请输入用户名" autocomplete="off"
                    class="layui-input">
            </div>
            <!-- 第二项:密码 -->
            <div class="layui-form-item">
                <i class="layui-icon layui-icon-password"></i>
                <input type="text" name="title" required lay-verify="required" placeholder="请输入密码" autocomplete="off"
                    class="layui-input">
            </div>
            <!-- 第三项:按钮 -->
            <div class="layui-form-item">
                <button class="layui-btn layui-btn-fluid layui-bg-blue" lay-submit lay-filter="formDemo">登录</button>
            </div>
            <!-- 第四项:超链接 -->
            <div class="layui-form-item">
                <a href="javascript:;">去注册账号</a>
            </div>
        </form>
        <!-- 登录的表单 end -->
    </div>
    <!-- 登录的盒子 end -->


    <!-- 注册的盒子 start -->
    <div class="box register">
        <div class="title">大事件后台管理系统</div>
        <!-- 注册的表单 start -->
        <form class="layui-form" action="">
            <!-- 第一项:用户名 -->
            <div class="layui-form-item">
                <i class="layui-icon layui-icon-username"></i>
                <input type="text" name="title" required lay-verify="required" placeholder="请输入用户名" autocomplete="off"
                    class="layui-input">
            </div>
            <!-- 第二项:密码 -->
            <div class="layui-form-item">
                <i class="layui-icon layui-icon-password"></i>
                <input type="text" name="title" required lay-verify="required" placeholder="请输入密码" autocomplete="off"
                    class="layui-input">
            </div>
            <!-- 第三项:确认密码 -->
            <div class="layui-form-item">
                <i class="layui-icon layui-icon-password"></i>
                <input type="text" name="title" required lay-verify="required" placeholder="请输入确认密码" autocomplete="off"
                    class="layui-input">
            </div>
            <!-- 第四项:按钮 -->
            <div class="layui-form-item">
                <button class="layui-btn layui-btn-fluid layui-bg-blue" lay-submit lay-filter="formDemo">注册</button>
            </div>
            <!-- 第五项:超链接 -->
            <div class="layui-form-item">
                <a href="javascript:;">去登录</a>
            </div>
        </form>
        <!-- 注册的表单 end -->
    </div>
    <!-- 注册的盒子 end -->


    <script src="./assets/lib/layui/layui.all.js"></script>
    <script src="./assets/lib/jquery.js"></script>
    <script src="./assets/js/common.js"></script>
    <script src="./assets/js/login.js"></script>
</body>

CSS代码:

html, body {
    height: 100%;
}

body {
    background-color: #5ea0f1;
}

#logo {
    margin: 20px 0 0 240px;
}

/* 登录和注册盒子 */
.box {
    height: 310px;
    width: 400px;
    background-color: #f5da78;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    box-shadow: 5px 5px 5px 5px;
}

.title {
    height: 60px;
    line-height: 60px;
    text-align: center;
    font-size: 26px;
}

/* 表单 */
form {
    margin: 0 30px;
}

form a {
    float: right;
}

/* 字体图标 */
.layui-form-item {
    position: relative;
}

.layui-icon {
    position: absolute;
    left: 6px;
    top: 11px;
}

.layui-input {
    padding-left: 24px;
}

/* 默认,让注册的盒子隐藏 */
.register {
    display: none;
}

# 切换登录和注册的盒子

// --------------------- 切换登录和注册的盒子 --------------------
$('.login a').click(function () {
    $('.register').show().prev().hide();
});

$('.register a').click(function () {
    $('.login').show().next().hide();
});

# 注册功能

# 完成注册功能

  • 注意serialize() 是根据表单项的name属性值获取值的
  • 一定要检查表单项的name属性,是否和接口要求的请求参数一致
// ----------------------   注册功能  ----------------------
// 表单提交 -> 阻止默认行为 -> 收集表单数据(查询字符串) -> ajax提交
$('.register form').on('submit', function (e) {
    e.preventDefault();
    var data = $(this).serialize();
    // console.log(data);
    $.ajax({
        type: 'POST',
        url: '/api/reguser',
        data: data,
        success: function (res) {
            // 提示
            layer.msg(res.message);
            if (res.status === 0) {
                // 清空输入框。找到表单,转成DOM对象,调用DOM方法reset,来重置表单
                $('.register form')[0].reset();
                // 切换到登录的盒子
                $('.login').show().next().hide();
            }
        }
    })
});

如果注册的用户名重复了,但是没有提示。因为如果用户名重复,服务器返回的状态码是500,$.ajax里面的success函数就不会执行了。

所以要加一个 error(请求出错时执行) 或者 complete(请求完成后执行),用于错误提示。

有因为,我们已经在common.js 里面写了 complete 的了,所以只需加一个判断条件即可。

common.js

option.complete = function (xhr) {
    var res = xhr.responseJSON;
    if (res && res.status === 1 && res.message === '身份认证失败!') {
        // 清除掉过期的token
        localStorage.removeItem('token');
        // 跳转到登录页
        location.href = './login.html';
    }
    // -------------------  其他错误  -----------------------
    if (res && res.status === 1) {
        layer.msg(res.message);
    }
}

# 表单验证

# 内置验证规则使用方法

layui -> 文档 -> 左侧边栏(内置模块) --> 表单 --> 右(目录)--> 表单验证。

layui提供了表单验证规则。使用方法:

<input type="text" lay-verify="验证规则|验证规则|验证规则" />

layui提供了几个内置的验证规则:

  • required(必填项)
  • phone(手机号)
  • email(邮箱)
  • url(网址)
  • number(数字)
  • date(日期)
  • identity(身份证)

比如,一个输入框必填、必填保证邮箱格式,代码如下:

<input type="text" lay-verify="required|email" />

# 自定义验证规则

layui支持自定义验证规则。

// ----------------------   自定义表单验证  ----------------------
// 必须使用 layui 的内置模块 - form 模块
// 只要使用layui的模块,必须加载模块
var form = layui.form;  // 加载form模块
// var laypage = layui.laypage; // =加载laypage分页模块
// var tree = layui.tree; // 加载树形组件模块

// 调用 form 模块内置方法verify,自定义验证规则
form.verify({
    // 键(验证规则): 值(验证方法)
    
    // 比如验证用户名长度2~10位,只能是数字字母组合
    // user: [/正则表达式/, '验证不通过时的提示']
    user: [/^[a-zA-Z0-9]{2,10}$/, '用户名只能是数字字母,且2~10位'], // {2,10} 不是 {2, 10}

    len: [/^\S{6,12}$/, '密码6~12位且不能有空格'],

    same: function (val) {
        // 形参,表示使用该验证规则的输入框的值(谁用这个验证规则,val表示谁的值)
        // 案例中,重复密码使用了这个验证规则,所以形参val表示输入的重复密码
        if (val !== $('.pwd').val()) {
            // return '错误提示'
            return '两次密码不一致'
        }
    }
    
});

定义完验证规则之后,在HTML页面中,使用该验证规则即可,如下:

用户名
<input type="text" lay-verify="required|user" />
密码框
<input type="password" lay-verify="required|len" />
重复密码框
<input type="password" lay-verify="required|len|same" />

# 细节问题

  • 表单(form标签)必须有 layui-form 这个类。

  • 按钮必须是submit类型的,如果按钮没有指定type,就是提交按钮,那么默认就是submit类型的

  • 按钮必须有 lay-submit 属性,注意是属性,不是类。

  • HTML中使用验证规则

    • 无论用的内置的验证规则,还是自定义的验证规则,用法都一样。
    • <input lay-verify="验证规则|验证规则" />
    • <input lay-verify="required|email|len|same" />
  • 编写自定义验证规则,需加载 form 模块;(硬性要求:使用layui的模块,必须先加载)

    • var 变量 = layui.模块名;

    • var form = layui.form; // 加载得到一个对象

    • 加载得到的 form 模块,是一个对象,该对象内置一个verify方法,我们调用它编写自定义验证规则。

# 登录功能

// ----------------------   登录功能  ----------------------
// 表单提交 -> 阻止默认行为 -> 收集表单数据(查询字符串) -> ajax提交
$('.login form').on('submit', function (e) {
    e.preventDefault();
    var data = $(this).serialize();  // 必须检查name属性值
    $.ajax({
        type: 'POST',
        url: '/api/login',
        data: data,
        success: function (res) {
            layer.msg(res.message);
            if (res.status === 0) {
                // 登录成功,保存token
                localStorage.setItem('token', res.token);
                // 跳转到首页面 index.html
                location.href = './index.html';
            }
        }
    });
})

# layer弹出层

可以在layui官网 (opens new window)查看弹出层模块的使用,也可以直接进入 layer 独立版本 (opens new window)演示网站。

我们在html中加载的是 layui.all.js,则可以直接使用layer模块,无需加载。

layer.msg() // 方法的作用是在页面中提示一个消息,3秒后自动关闭这个消息框

// 示例如下:
layer.msg('xxxxx');

// 项目中可以使用如下代码:
layer.msg(res.message);

# token的原理

当我们登录成功之后,服务器返回了一个token字符串。

token是一个令牌,访问以 /my 开头的接口的时候,必须携带token,否则会提示身份认证失败。

所以,登录成功之后,获取到token,浏览器端(客户端)需要保存token,以便于后续请求使用。

image-20201125143403162

# 后台首页

# 页面布局

  • 到layui官网,文档-->页面元素-->布局-->后台布局。
  • 复制后台布局 全部 的代码,粘贴到你的 index.html 中。
  • 修改layui.css 和 layui.all.js 的路径。
    • 去掉复制过来的全部JS相关代码
    • 更换成我们自己的 layui.all.js 即可。
  • 至此,index.html 页面布局基本上就实现了。

# 头部处理

  • 不对的换掉
  • 不要的删除

# 侧边栏导航处理

  • 自行调整成和线上效果一样的结构(调整顺序)
  • 给“首页” 添加 layui-this 类,表示默认该项选中
  • 去掉 文章管理 的 “layui-nav-itemed” 类,刷新后,该项为收缩状态
  • 给 ul 添加 lay-shrink="all" 属性,则会出现排他(手风琴)效果。

# 创建文件,加载css和js

上面能够修改的都已经修改完毕了,接下来需要的样式需要我们自己编写了,所以不得不创建自己的JS文件和CSS文件了。记得加载他们,包括common.js。

  • 创建了 /assets/css/index.css
  • 创建了 /assets/js/index.js

# 使用字体图标

<a href=""><i class="layui-icon layui-icon-logout"></i>退出</a>

<a href=""><i class="layui-icon layui-icon-home"></i>首页</a>

<a class="" href="javascript:;"><i class="layui-icon layui-icon-form"></i>文章管理</a>

<a href="javascript:;"><i class="layui-icon layui-icon-username"></i>个人中心</a>

子菜单全部一致
<a href="javascript:;"><i class="layui-icon layui-icon-app"></i>列表一</a>

CSS样式:

/* 所有菜单的调整 */
.layui-icon {
    margin: 0 5px;
    font-size: 16px;
}

/* 单独调整子菜单 */
.layui-icon-app {
    margin-left: 20px;
}

# 头像处理

  • 头部的头像和侧边栏的头像一样

  • 复制头部区域的 a 标签,放到侧边栏开始的位置,修改a标签为div

  • 自定义类。并添加样式,完成最终的效果。

    <!-- 侧边栏代码 -->
    <div class="userinfo" href="javascript:;">
        <img src="http://t.cn/RCzsdCq" class="layui-nav-img">
        个人中心
    </div>
    
    /* 侧边栏的头像位置 div */
    div.userinfo {
        height: 60px;
        text-align: center;
        line-height: 60px;
    }
    
  • 设置欢迎语

    <div class="userinfo">
        <img src="http://t.cn/RCzsdCq" class="layui-nav-img">
        欢迎你<span class="username">老汤</span>
    </div>
    
  • 设置文字头像

    因为新注册的账号没有图片类型的头像,所以取用户名的第一个字符当做头像。

    如果后续,用户更换了图片头像,那么就显示图片头像。

    <!--    头部  --- 添加 span.text-avatar 标签    -->
    <a href="javascript:;">
        <span class="text-avatar">A</span>
        <img src="http://t.cn/RCzsdCq" class="layui-nav-img">
        个人中心
    </a>
    <!--    侧边栏 --- 添加 span.text-avatar 标签   -->
    <div class="userinfo">
        <span class="text-avatar">A</span>
        <img src="http://t.cn/RCzsdCq" class="layui-nav-img">
        欢迎你<span class="username">老汤</span>
    </div>
    
    /* 字体头像 */
    .text-avatar {
        width: 30px;
        height: 30px;
        text-align: center;
        line-height: 30px;
        font-size: 20px;
        color: #fff;
        background-color: #419488;
        display: inline-block;
        border-radius: 50%;
    }
    
    /* 默认隐藏两个头像 */
    .text-head, .layui-nav-img {
        display: none;
    }
    

# 首页获取用户信息并渲染

开发之前,记得把jQuery和自己的js先加载好

  • 封装一个函数 getUserInfo(),完成ajax请求,获取用户信息并渲染
  • getUserInfo() 函数要放到入口函数外部
    • 封装成函数,后续,其他页面会使用
  • 发送请求的时候,必须在请求头中,携带token
  • 渲染
    • 设置欢迎语
      • 优先使用昵称,没有昵称则使用登录账号
    • 设置头像
      • 优先使用图片,没有图片,则使用名字的第一个字符
      • 设置字体头像的时候,不要用show()方法,要自己设置css样式
function getUserInfo() {
    $.ajax({
        // type: 'GET', // type不填,默认就是GET
        url: '/my/user/userinfo',
        success: function (res) {
            if (res.status === 0) {
                // 1、设置欢迎语(有昵称,就使用昵称,没有昵称,使用用户名)
                var myname = res.data.nickname || res.data.username;
                $('.myname').text(myname);
                // 2、设置头像(有图片,使用图片;没有图片,使用名字的首字母)
                if (res.data.user_pic) {
                    // 使用图片
                    $('.layui-nav-img').attr('src', res.data.user_pic).show();
                    $('.text-avatar').hide();
                } else {
                    var t = myname.substr(0, 1).toUpperCase();
                    // jQuery中的show方法,会设置元素 display:inline;
                    $('.text-avatar').text(t).css('display', 'inline-block');
                    $('.layui-nav-img').hide();
                }
            }
        },
        // jQuery中ajax选项,有一个headers,通过他,可以设置请求头
        headers: {
            'Authorization': localStorage.getItem('token')
        }
    });
}

# 退出功能

  • 退出超链接
    • 加入 id="logout"
    • href="javascript:;" 这点一定要注意,必填
  • 点击退出
    • 询问
    • 删除token
    • 跳转到 login.html
// --------------  退出功能 ---------------------
// 退出的时候,两个操作
// - 删除token
// - 页面跳转到登录页面
$('#logout').click(function () {
    // 弹出层,询问是否要退出
    layer.confirm('你确定退出吗?', function (index) {
        //do something
        // 如果点击了确定,删除token,页面跳转
        localStorage.removeItem('token');
        location.href = '/login.html';
        layer.close(index); // 关闭当前弹出层
    });
});

# 其他页面处理

# 文件存放思路

如果继续把html文件存放到项目根目录,那么根目录的文件会越来越多,会越来越乱,所以建议单独存放个人中心相关的几个页面。老师的做法如下(仅供参考)

  • html放到根目录下的user文件夹
  • css,也要放到 /assets/css/user/ 这里
  • js,也要放到 /assets/js/user/ 这里

# 首页内容区说明

使用iframe标签

  • iframe标签是HTML标签
  • iframe在整个页面(父页面)中,占用一个区域,这个区域可以引入其他(子)页面
  • src属性用于引入默认的子页面
  • 侧边栏的 a 标签,href属性正常挂链接
  • 侧边栏的 a 标签,target属性,表示打开新页面的位置;
  • 通过指定 target=“iframe标签的name值” ,可以在iframe区域打开链接的页面

image-2020071312071501

# 基本资料

# 准备工作

  • 创建HTML文件、css文件、js文件
    • 创建 /user/userinfo.html
    • 创建 /assets/css/userinfo.css
    • 创建 /assets/js/user/userinfo.js
  • index.html 头部和侧边栏,挂超链接,链接到 /user/userinfo.html,注意target="fm"

# 页面布局

略(因为是复制过来的)

只需要修改一下页面中的文字、修改一下类名、name属性值等等

  • 修改了input的name属性值(分别是username、nickname、email)
  • 设置登录账号这个input disable属性,因为修改的时候,不允许修改登录账号
  • 给邮箱加一个email验证规则

# 为表单赋值

修改页面的js文件,为userinfo.js

思路:

  • 发送ajax请求,获取用户信息
  • 设置表单各项(每个输入框)的value值。

具体步骤:

  • 需要在 form 中,添加一个隐藏域,用于保存id值,前面已经添加过了

    <input type="hidden" name="id" />
    <!-- 隐藏域,只要放到 form 里面即可 -->
    
  • 先设置表单各项的 name 属性(username/nickname/email/id)

  • 发送ajax请求,获取用户信息

  • 使用layui的from模块快速为表单赋值

    • 为表单设置 lay-filter="user" ,值随便定义,我这里使用的是 user
    • JS代码中,一行代码为表单赋值
    let form = layui.form;
    form.val('user', res.data);
    
    • 要求,res.data 这个对象的属性(key)要和表单各项的name属性值相同,才能赋值

只要是修改操作:

  1. 必须要进行数据回填操作,保证输入框是有值的
  2. 修改的表单中,一般都有隐藏域 id

# 完成更新用户信息的功能

  • 设置 登录账号为 disabled
    • 不允许修改
    • 通过 $('form').serialize() 不能获取到 username 值,刚刚好是我们的需要。
  • 注册表单的提交事件
  • 阻止默认行为
  • 收集表单数据,使用 $('form').serialize() 。(id、nickname、email)
  • 发送ajax请求,完成更新
  • 更新成功之后,提示,并且调用父页面的 getUserInfo() 从新渲染用户的头像
// ------------------   表单提交的时候,完成用户信息的更新 -----------------
// 监听表单的提交事件。
$('form').on('submit', function (e) {

    // 阻止默认行为
    e.preventDefault();
    // 获取id、nickname、email的值
    var data = $(this).serialize();
    // console.log(data);
    // ajax提交给接口,从而完成更新
    $.ajax({
        type: 'POST',
        url: '/my/userinfo',
        data: data,
        success: function (res) {
            // 无论成功还是失败,都要提示
            layer.msg(res.message);
            if (res.status === 0) {
                window.parent.getUserInfo();
            }
        }
    });
});

# 重置表单

// ---------------------  重置表单  -------------------------
$('button:contains("重置")').click(function (e) {
    e.preventDefault();
    renderForm(); // 调用renderForm(),为表单重新赋值,就可以恢复成原样
});

# 更新密码

# 准备工作

  • 创建所需的HTML、js文件、css文件
    • 经过观察,所有小页面的布局都一样,所以这里可以使用base.css
    • 在 /assets/css 里面创建 base.css (只规定body的背景色、卡片布局的边距)
  • 首页侧边栏和头部区域挂好链接
    • href=“/user/repwd.html”
    • target="fm" fm是iframe的name属性值
  • 加载好所需的css和js文件
<!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/userinfo.css">
</head>
<body>
    
    <!--
	1. html代码,去userinfo.html 中复制
	2. 修改文字
	3. 去掉第一个输入框的 disabled 属性
	4. 去掉 隐藏域id
	5. 修改原密码的name属性为 oldPwd;修改新密码的name为 newPwd;去掉重复密码的name属性
	-->
    
    <script src="/assets/lib/jquery.js"></script>
    <script src="/assets/lib/layui/layui.all.js"></script>
    <script src="/assets/js/common.js"></script>
    <script src="/assets/js/user/repwd.js"></script>
</body>
</html>

# 表单验证

// --------------------- 1. 表单验证 ------------------------
// 1) 长度6~12位,不能有空格 (两个新密码都要用)
// 2) 新密码不能和原密码相同
// 3) 两次新密码必须一致

// 加载 layui 的 form 模块
var form = layui.form;
// 自定义验证规则
form.verify({
    // 1) 长度6~12位,不能有空格
    len: [/^\S{6,12}$/, '密码长度必须是6~12且不能有空格'],

    // 2) 新密码不能和原密码相同
    diff: function (val) {
        // 形参表示使用该验证规则的输入框的值,新密码使用这个验证规则,所以val表示填写的新密码
        // 获取原密码
        var oldPwd = $('input[name=oldPwd]').val();
        if (val === oldPwd) {
            return '新密码不能和原密码相同';
        }
    },


    // 3) 两次新密码必须一致 (重复密码 用这个验证规则)
    same: function (val) {
        // val 表示填写的重复密码
        // 获取新密码
        var newPwd = $('input[name=newPwd]').val();
        if (newPwd !== val) {
            return '两次密码不一样哟~';
        }
    }
});
  • 三个密码框,都使用len这个验证规则
  • 新密码,使用diff,这个验证规则
  • 确认密码,使用 same 验证规则

# ajax请求,完成更新

// 监听表单的提交事件
$('form').on('submit', function (e) {
    e.preventDefault();
    var data = $(this).serialize(); // serialize是根据表单input的name属性值获取值的,所以一定要检查name属性值
    $.post('/my/updatepwd', data, function (res) {
        // 无论修改成功还是失败,都给出提示
        layer.msg(res.message);
        if (res.status === 0) {
            // 修改成功,清空输入框的值
            $('form')[0].reset(); // DOM方法reset表示重置表单
        }
    });
});

# 更换头像

# 准备工作

  • 创建文件

    • 创建 /user/avatar.html
    • 创建 /assets/css/user/avatar.css
    • 创建 /assets/js/user/avatar.js
  • index.html 中,侧边栏和头部区域挂好超链接

  • avatar.html 中 引入所需的css和js文件

    <!-- 加载layui.css -->
    <link rel="stylesheet" href="/assets/lib/layui/css/layui.css">
    <!-- 加载cropper.css -->
    <link rel="stylesheet" href="/assets/lib/cropper/cropper.css">
    <!-- 加载自己的css -->
    <link rel="stylesheet" href="/assets/css/user/avatar.css">
    
    
    
    <!-- 加载jQuery -->
    <script src="/assets/lib/jquery.js"></script>
    <!-- 加载layui.all.js -->
    <script src="/assets/lib/layui/layui.all.js"></script>
    <!-- 按顺序 加载Cropper.js -->
    <script src="/assets/lib/cropper/Cropper.js"></script>
    <!-- 按顺序 jquery-cropper.js -->
    <script src="/assets/lib/cropper/jquery-cropper.js"></script>
    <!-- 加载common.js -->
    <script src="/assets/js/common.js"></script>
    <!-- 加载avatar.js -->
    <script src="/assets/js/user/avatar.js"></script>
    

# 复制HTML和CSS(完成布局)

  • 首先,得有一个卡片面板布局(去layui复制)

    <div class="layui-card">
      <div class="layui-card-header">卡片面板</div>
      <div class="layui-card-body">
        卡片式面板面板通常用于非白色背景色的主体内<br>
        从而映衬出边框投影
      </div>
    </div>
    
  • 去 “cropper的基本用法.md” 笔记中,复制代码(html和css),放到卡片布局的内容区。

  • 完成后的效果:

image-2020060955256

# 创建剪裁区(初始化剪裁区)

  • 使用插件 cropper ,提供的方法,实现剪裁区的创建
  • 具体做法:
    • 找到剪裁区的图片 (img#image)
    • 设置配置项
    • 调用cropper方法,创建剪裁区
// ---------------  创建剪裁区 ------------------
// - 找到剪裁区的图片 (img#image)
var $image = $('#image');
// - 设置配置项
var option = {
    // 纵横比(宽高比)
    aspectRatio: 1, // 正方形
    // 指定预览区域
    preview: '.img-preview' // 指定预览区的类名(选择器)
};
// - 调用cropper方法,创建剪裁区
$image.cropper(option);

# 点击按钮,可选择图片

  • html中加入一个隐藏的文件域
  • 点击上传按钮的时候,触发文件域的单击事件
<!-- 加一个隐藏的文件域 -->
<input type="file" id="file" style="display: none;" accept="image/*">
<button type="button" class="layui-btn">上传</button>

// -------------  点击  上传  ,可以选择图片  ------------
$('button:contains("上传")').click(function () {
    $('#file').click();
});

# 更换图片,重置剪裁区

  • 找到选择的文件(文件对象)
  • 为文件对象创建一个临时的url
  • 更换剪裁区的图片
    • 先销毁原来的剪裁区
    • 更改图片的src属性
    • 重新生成剪裁区
// 文件域的内容改变的时候,更换剪裁区的图片
$('#file').change(function () {
    // 3.1) 先找到文件对象
    // console.dir(this)
    var fileObj = this.files[0];
    // 3.2) 为选择的图片生成一个临时的url
    var url = URL.createObjectURL(fileObj);
    // console.log(url);
    // 3.3) 更换图片的src属性即可(销毁剪裁区 --> 更换src属性 --> 重新创建剪裁框)
    $image.cropper('destroy').attr('src', url).cropper(option);
});

# 点击确定,实现剪裁并修改头像

  • 调用 cropper 方法,传递 getCroppedCanvas 参数,得到一个canvas图片(对象)
  • 调用canvas的toDataURL()方法,得到base64格式的图片
  • ajax提交即可
// ------------- 4. 点击确定按钮,剪裁图片,把图片转成base64格式,ajax提交字符串,完成更换 ----
$('button:contains("确定")').click(function () {
    // 4.1)调用插件方法,剪裁图片;剪裁之后得到一张canvas格式的图片
    var canvas = $image.cropper('getCroppedCanvas', {
        width: 100,
        height: 100
    });
    // 4.2) 把canvas图片转成base64格式,得到超长字符串
    var base64 = canvas.toDataURL('image/png');
    // console.log(base64);
    // 4.3) ajax提交字符串,完成更新
    $.ajax({
        type: 'POST',
        url: '/my/update/avatar',
        data: { avatar: base64 },
        success: function (res) {
            layer.msg(res.message);
            if (res.status === 0) {
                // 重新渲染父页面的头像
                window.parent.getUserInfo();
            }
        }
    });
});

# 关于base64格式的图片说明

  • base64格式只是图片的一种格式,用字符串表示图片的格式
  • base64格式的优点:减少http请求,加快小图片的响应速度,减轻服务器的压力
  • base64格式的缺点:体积比正常图片大 30% 左右。
  • 如果是小图片,可以使用base64格式,大图片不建议使用了。

https://www.css-js.com/tools/base64.html

# 文章列表页布局

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文章列表页</title>
    <link rel="stylesheet" href="../assets/lib/layui/css/layui.css">
    <link rel="stylesheet" href="./css/list.css">
</head>

<body>


    <div class="layui-card">
        <div class="layui-card-header">文章列表</div>
        <div class="layui-card-body">
            <!-- 内容区一 表单搜索区 start -->
            <form class="layui-form" action="">
                <div class="layui-form-item">

                    <div class="layui-inline">
                        <div class="layui-input-inline" style="width: 200px;">
                            <select name="city" lay-verify="">
                                <option value="">请选择一个城市</option>
                                <option value="010">北京</option>
                                <option value="021">上海</option>
                                <option value="0571">杭州</option>
                            </select>
                        </div>

                        <div class="layui-input-inline" style="width: 200px;">
                            <select name="city" lay-verify="">
                                <option value="">请选择一个城市</option>
                                <option value="010">北京</option>
                                <option value="021">上海</option>
                                <option value="0571">杭州</option>
                            </select>
                        </div>
                    </div>

                    <div class="layui-inline">
                        <div class="layui-input-inline" style="width: 100px;">
                            <button class="layui-btn" lay-submit lay-filter="formDemo">立即提交</button>
                        </div>
                    </div>

                </div>
            </form>
            <!-- 内容区一 表单搜索区 end -->

            <!-- 内容区二 表格区 start -->
            <table class="layui-table">
                <colgroup>
                    <col width="40%">
                    <col width="15%">
                    <col width="15%">
                    <col width="15%">
                    <col>
                </colgroup>
                <thead>
                    <tr>
                        <th>文章标题</th>
                        <th>分类</th>
                        <th>发布时间</th>
                        <th>状态</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>静夜思</td>
                        <td>艺术</td>
                        <td>2021-01-13 12:39:08</td>
                        <td>已发布</td>
                        <td>
                            <button type="button" class="layui-btn layui-btn-xs">编辑</button>
                            <button type="button" class="layui-btn layui-btn-xs layui-btn-danger">删除</button>
                        </td>
                    </tr>
                </tbody>
            </table>
            <!-- 内容区二 表格区 end -->

            <!-- 内容区三 分页区 start -->
            <div id="page"></div>
            <!-- 内容区三 分页区 end -->
        </div>
    </div>


    <script src="../assets/lib/layui/layui.all.js"></script>
    <script src="../assets/lib/jquery.js"></script>
    <script src="../assets/lib/template-web.js"></script>
    <script src="../assets/js/common.js"></script>
    <script src="./js/list.js"></script>
</body>

</html>

CSS:

body {
    background-color: #f2f3f5;
    padding: 15px;
}

# 添加文章页面布局

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>添加文章页面</title>
    <link rel="stylesheet" href="../assets/lib/layui/css/layui.css">
    <link rel="stylesheet" href="../assets/lib/cropper/cropper.css">
    <link rel="stylesheet" href="./css/add.css">
</head>

<body>

    <div class="layui-card">
        <div class="layui-card-header">发布文章</div>
        <div class="layui-card-body">
            <form class="layui-form" action="">
                <!-- 第一项:标题 -->
                <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">
                        <select name="city" lay-verify="required">
                            <option value=""></option>
                            <option value="0">北京</option>
                            <option value="1">上海</option>
                            <option value="2">广州</option>
                            <option value="3">深圳</option>
                            <option value="4">杭州</option>
                        </select>
                    </div>
                </div>
                <!-- 第三项:文章内容 -->
                <div class="layui-form-item layui-form-text">
                    <label class="layui-form-label">文本域</label>
                    <div class="layui-input-block">
                        <textarea name="desc" placeholder="请输入内容" class="layui-textarea"></textarea>
                    </div>
                </div>
                <!-- 第四项:封面图片 -->
                <div class="layui-form-item">
                    <!-- 左侧的 label -->
                    <label class="layui-form-label">文章封面</label>
                    <!-- 选择封面区域 -->
                    <div class="layui-input-block cover-box">
                        <!-- 左侧裁剪区域 -->
                        <div class="cover-left">
                            <img id="image" src="/assets/images/sample2.jpg" alt="" />
                        </div>
                        <!-- 右侧预览区域和选择封面区域 -->
                        <div class="cover-right">
                            <!-- 预览的区域 -->
                            <div class="img-preview"></div>
                            <!-- 选择封面按钮 -->
                            <button type="button" class="layui-btn layui-btn-danger">选择封面</button>
                        </div>
                    </div>
                </div>
                <!-- 第五项:选择状态 -->
                <div class="layui-form-item">
                    <label class="layui-form-label">单选框</label>
                    <div class="layui-input-block">
                        <input type="radio" name="sex" value="" title="">
                        <input type="radio" name="sex" value="" title="" checked>
                    </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>
        </div>
    </div>

    <script src="../assets/lib/layui/layui.all.js"></script>
    <script src="../assets/lib/jquery.js"></script>
    <script src="../assets/lib/template-web.js"></script>
    <!-- 加载富文本编辑器插件,按照顺序加载 -->
    <script src="../assets/lib/tinymce/tinymce.min.js"></script>
    <script src="../assets/lib/tinymce/tinymce_setup.js"></script>
    <!-- 按照顺序,加载剪裁插件的js -->
    <script src="../assets/lib/cropper/Cropper.js"></script>
    <script src="../assets/lib/cropper/jquery-cropper.js"></script>
    
    <script src="../assets/js/common.js"></script>
    <script src="./js/add.js"></script>
</body>
</html>

CSS:

body {
    background-color: #f2f3f5;
    padding: 15px;
}

/* 封面容器的样式 */
.cover-box {
    display: flex;
  }
  
  /* 左侧裁剪区域的样式 */
  .cover-left {
    width: 400px;
    height: 280px;
    overflow: hidden;
    margin-right: 20px;
  }
  
  /* 右侧盒子的样式 */
  .cover-right {
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  
  /* 预览区域的样式 */
  .img-preview {
    width: 200px;
    height: 140px;
    background-color: #ccc;
    margin-bottom: 20px;
    overflow: hidden;
  }

JS:

initEditor(); // 调用函数,就会把 textarea 替换为富文本编辑器

// 1. 初始化剪裁框
// 1. 初始化图片裁剪器
var $image = $('#image')

// 2. 裁剪选项
var options = {
    aspectRatio: 400 / 280,
    preview: '.img-preview'
}

// 3. 初始化裁剪区域
$image.cropper(options)

# 文章列表

# 准备工作

  • 准备工作(创建页面、挂好链接、引入所需的css和js文件)
<!-- 加载所需的js和css -->
<link rel="stylesheet" href="../assets/lib/layui/css/layui.css">
<link rel="stylesheet" href="./css/list.css">

<script src="../assets/lib/layui/layui.all.js"></script>
<script src="../assets/lib/jquery.js"></script>
<script src="../assets/lib/template-web.js"></script>
<script src="../assets/js/common.js"></script>
<script src="./js/list.js"></script>

# 页面布局

  • layui的卡片面板

  • 筛选区

    • 找到 “页面元素 --> 表单 --> 目录 --> 组装行内表单”
    • 不需要的文字删除
    • 更换文本框为 下拉框和按钮
  • 表格区

    • 自行复制代码,然后调整宽度、设置按钮
  • 分页区

    • 一个 id为page的空div

完整的页面结构

<div class="layui-card">
<div class="layui-card-header">文章列表</div>
<div class="layui-card-body">
  <!-- 内容区一 表单搜索区 start -->
  <form class="layui-form" action="">
    <div class="layui-form-item">

      <div class="layui-inline">
        <div class="layui-input-inline" style="width: 200px;">
          <select name="city" lay-verify="">
            <option value="">请选择一个城市</option>
            <option value="010">北京</option>
            <option value="021">上海</option>
            <option value="0571">杭州</option>
          </select>
        </div>

        <div class="layui-input-inline" style="width: 200px;">
          <select name="city" lay-verify="">
            <option value="">请选择一个城市</option>
            <option value="010">北京</option>
            <option value="021">上海</option>
            <option value="0571">杭州</option>
          </select>
        </div>
      </div>

      <div class="layui-inline">
        <div class="layui-input-inline" style="width: 100px;">
          <button class="layui-btn" lay-submit lay-filter="formDemo">立即提交</button>
        </div>
      </div>

    </div>
  </form>
  <!-- 内容区一 表单搜索区 end -->

  <!-- 内容区二 表格区 start -->
  <table class="layui-table">
    <colgroup>
      <col width="40%">
      <col width="15%">
      <col width="15%">
      <col width="15%">
      <col>
    </colgroup>
    <thead>
      <tr>
        <th>文章标题</th>
        <th>分类</th>
        <th>发布时间</th>
        <th>状态</th>
        <th>操作</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>静夜思</td>
        <td>艺术</td>
        <td>2021-01-13 12:39:08</td>
        <td>已发布</td>
        <td>
          <button type="button" class="layui-btn layui-btn-xs">编辑</button>
          <button type="button" class="layui-btn layui-btn-xs layui-btn-danger">删除</button>
        </td>
      </tr>
    </tbody>
  </table>
  <!-- 内容区二 表格区 end -->

  <!-- 内容区三 分页区 start -->
  <div id="page"></div>
  <!-- 内容区三 分页区 end -->
</div>
</div>

# 渲染数据

  • 定义渲染文章列表的函数 (renderArticle)
  • ajax请求参数,我们先定义成全局变量(后面还需要使用)
  • 定义renderArticle函数,函数内容,发送ajax请求,获取数据,并调用template渲染

JS代码:

// 分页获取文章列表的请求参数
var data = {
  pagenum: 1, // 页码值,比如2,将获取到第2页的数据
  pagesize: 2, // 每页有多少条数据,比如5,表示每页5条数据
  // cate_id: 1,
  // state: '已发布'
}

// ------------------ 获取文章并渲染到表格中 ------------------
function renderArticle() {
  $.ajax({
    url: '/my/article/list',
    data: data,
    success: function (res) {
      // console.log(res);
      // 使用模板引擎,渲染数据
      var htmlStr = template('tpl-article', res);
      $('tbody').html(htmlStr);
    }
  });
}
renderArticle();

HTML模板:

<!-- 文章列表的模板 start -->
<script type="text/html" id="tpl-article">
  {{each data item}}
  <tr>
    <td>{{item.title}}</td>
    <td>{{item.cate_name}}</td>
    <td>{{item.pub_date}}</td>
    <td>{{item.state}}</td>
    <td>
      <a href="./edit.html?id={{item.id}}" class="layui-btn layui-btn-xs">编辑</a>
      <button type="button" class="layui-btn layui-btn-xs layui-btn-danger">删除</button>
    </td>
  </tr>
  {{/each}}
</script>
<!-- 文章列表的模板 end -->

# 定义模板引擎过滤器函数

使用自定义函数,处理时间日期

template.defaults.imports.dateFormat = function (time) {
  var date = new Data(time);
  var y = date.getFullYear();
  var m = addZero(date.getMonth() + 1);
  var d = addZero(date.getDate());
  // 时分秒自己写
  return y + '-' + m + '-' + d;
}

// 补零函数
function addZero (n) {
  return n < 10 ? '0' + n : n;
}

模板中使用自定义的过滤器函数处理时间

<td>{{item.pub_date | dateFormat}}</td>

# 删除文章

  • 给删除按钮,添加一个data-id属性,值就是当前文章的id,添加一个类 delete

    <button data-id="{{item.id}}" type="button" class="delete layui-btn layui-btn-xs layui-btn-danger">删除</button>
    
  • JS代码中,事件委托的方案,给删除注册单击事件

  • 事件内部,获取id

  • 询问是否要删除

  • 如果确定删除,则发送ajax请求,完成删除

  • 完成删除之后,从新渲染页面

这里的id参数,是一种url参数,只需要在接口后面 连接 上id即可。

比如:/my/article/delete/2 表示删除id为2的文章。

完整的代码:

// -------------------------- 删除 ------------------------------
$('tbody').on('click', 'button:contains("删除")', function () {
  var id = $(this).data('id');
  layer.confirm('确定删除吗?', function (index) {

    $.ajax({
      // url: '/my/article/delete/2',
      url: '/my/article/delete/' + id,
      success: function (res) {
        layer.msg(res.message);
        if (res.status === 0) {
          renderArticle();
        }
      }
    });

    layer.close(index); // 关闭弹层
  });
})

另外一个删除思路,当前页的文章删没了,我们显示上一页的数据。

  • 当确定删除了,首先用dom的方式,把tr移除。
  • 当删除的请求成功后,判断tbody里面是否有tr
    • 如果有tr,那么还获取当前页的数据
    • 如果没有tr,说明当前页的数据被删没了,则 修改 pagenum--,获取上一页的数据
// -------------------------- 删除 ------------------------------
$('tbody').on('click', 'button:contains("删除")', function () {
  var id = $(this).data('id');
  var that = $(this);
  layer.confirm('确定删除吗?', function (index) {

    // 使用dom的方式删除该行
    that.parents('tr').remove();

    $.ajax({
      // url: '/my/article/delete/2',
      url: '/my/article/delete/' + id,
      success: function (res) {
        layer.msg(res.message);
        if (res.status === 0) {
          if ($('tbody').children().length > 0) {
            renderArticle();
          } else {
            data.pagenum--;
            if (data.pagenum === 0) return;
            renderArticle();
          }
        }
      }
    });

    layer.close(index); // 关闭弹层
  });
})

# 添加文章

# 准备工作

创建html、css、js文件;

链接好所需的css和js文件

index.html 侧边栏挂好链接

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>发布文章</title>
  <!-- 加载所需的js和css -->
  <link rel="stylesheet" href="../assets/lib/layui/css/layui.css">
  <link rel="stylesheet" href="../assets/lib/cropper/cropper.css">
  <link rel="stylesheet" href="./css/add.css">
</head>
<body>
    
  内容区,还是卡片面板

  <script src="../assets/lib/layui/layui.all.js"></script>
  <script src="../assets/lib/jquery.js"></script>
  <script src="../assets/lib/template-web.js"></script>
  <!-- 加载富文本编辑器插件,按照顺序加载 -->
  <script src="../assets/lib/tinymce/tinymce.min.js"></script>
  <script src="../assets/lib/tinymce/tinymce_setup.js"></script>
  <!-- 按照顺序,加载剪裁插件的js -->
  <script src="../assets/lib/cropper/Cropper.js"></script>
  <script src="../assets/lib/cropper/jquery-cropper.js"></script>

  <script src="../assets/js/common.js"></script>
  <script src="./js/add.js"></script>
</body>
</html>

# 页面布局

  • 使用卡片面板

  • 卡片的body区放表单

  • 表单的内容区(content)

    • 去 layui --> 文档 --> 表单 --> 小睹为快 复制 多行文本域。
    • 在自己的js中,调用一个 initEditor() 函数,该函数会把 textarea替换成富文本框
  • 表单的图片裁剪区(cover_img),添加如下的表单行

    <div class="layui-form-item">
      <!-- 左侧的 label -->
      <label class="layui-form-label">文章封面</label>
      <!-- 选择封面区域 -->
      <div class="layui-input-block cover-box">
        <!-- 左侧裁剪区域 -->
        <div class="cover-left">
          <img id="image" src="/assets/images/sample2.jpg" alt="" />
        </div>
        <!-- 右侧预览区域和选择封面区域 -->
        <div class="cover-right">
          <!-- 预览的区域 -->
          <div class="img-preview"></div>
          <!-- 选择封面按钮 -->
          <button type="button" class="layui-btn layui-btn-danger">选择封面</button>
        </div>
      </div>
    </div>
    

    CSS样式

    /* 封面容器的样式 */
    .cover-box {
      display: flex;
    }
    
    /* 左侧裁剪区域的样式 */
    .cover-left {
      width: 400px;
      height: 280px;
      overflow: hidden;
      margin-right: 20px;
    }
    
    /* 右侧盒子的样式 */
    .cover-right {
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    
    /* 预览区域的样式 */
    .img-preview {
      width: 200px;
      height: 140px;
      background-color: #ccc;
      margin-bottom: 20px;
      overflow: hidden;
    }
    

    JS,实现基本的剪裁框

    // 1. 初始化图片裁剪器
    var $image = $('#image')
    
    // 2. 裁剪选项
    var options = {
      aspectRatio: 400 / 280,
      preview: '.img-preview'
    }
    
    // 3. 初始化裁剪区域
    $image.cropper(options)
    
  • 按钮区

    • 使用一个提交按钮

完整的HTML结构:

<div class="layui-card">
	<div class="layui-card-header">发布文章</div>
	<div class="layui-card-body">
		<form class="layui-form" action="">
			<!-- 第一项:标题 -->
			<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">
					<select name="cate_id" lay-verify="required">
						<option value=""></option>
						<option value="0">北京</option>
						<option value="1">上海</option>
						<option value="2">广州</option>
						<option value="3">深圳</option>
						<option value="4">杭州</option>
					</select>
				</div>
			</div>
			<!-- 第三项:文章内容 -->
			<div class="layui-form-item layui-form-text">
				<label class="layui-form-label">文章内容</label>
				<div class="layui-input-block">
					<textarea name="content" placeholder="请输入内容" class="layui-textarea"></textarea>
				</div>
			</div>
			<!-- 第四项:封面图片 -->
			<div class="layui-form-item">
				<!-- 左侧的 label -->
				<label class="layui-form-label">文章封面</label>
				<!-- 选择封面区域 -->
				<div class="layui-input-block cover-box">
					<!-- 左侧裁剪区域 -->
					<div class="cover-left">
						<img id="image" src="/assets/images/sample2.jpg" alt="" />
					</div>
					<!-- 右侧预览区域和选择封面区域 -->
					<div class="cover-right">
						<!-- 预览的区域 -->
						<div class="img-preview"></div>
						<!-- 选择封面按钮 -->
						<button type="button" class="layui-btn layui-btn-danger">选择封面</button>
					</div>
				</div>
			</div>
			<!-- 第五项:选择状态 -->
			<div class="layui-form-item">
				<label class="layui-form-label">文章状态</label>
				<div class="layui-input-block">
					<input type="radio" name="state" value="已发布" title="发布">
					<input type="radio" name="state" value="草稿" title="存为草稿" checked>
				</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>
	</div>
</div>

# 获取分类渲染到下拉列表

  • 获取所有的分类,渲染到下拉框
    • ajax请求之后,获取到分类
    • 使用模板引擎渲染select框
    • 动态添加select框之后,发现页面中的下拉框看不见了,需要使用 form.render() 方法更新渲染
// ------------------  获取分类,渲染到下拉框中 -------------
$.ajax({
    url: '/my/category/list',
    success: function (res) {
        var str = template('tpl-category', res);
        $('select').html(str);
        // 模板引擎处理完之后,重新渲染select
        form.render('select');
    }
});

HTML模板:

<script type="text/html" id="tpl-category">
  <option value="">请选择文章类别</option>
  {{each data item}}
  <option value="{{item.id}}">{{item.name}}</option>
  {{/each}}
</script>

# 处理封面区

你可以复制之前的代码。

// -------------------------- 处理封面图片 -----------------
// 1. 初始化剪裁框
// 1.1) 初始化图片裁剪器
var $image = $('#image')
// 1.2) 裁剪选项
var options = {
  aspectRatio: 400 / 280,
  preview: '.img-preview'
}
// 1.3) 初始化裁剪区域
$image.cropper(options);


// 2. 点击 “选择封面” 能够选择图片
$('button:contains("选择封面")').click(function () {
  $('#file').trigger('click');
});

// 3. 图片切换的时候,更换剪裁区的图片
$('#file').change(function () {
  // 3.1) 找到文件对象
  var fileObj = this.files[0];
  // 3.2) 创建url
  var url = URL.createObjectURL(fileObj);
  // 3.3) 更换图片
  $image.cropper('destroy').attr('src', url).cropper(options);
});

# 实现最终的发布

  • 把表单中,每个表单元素的name检查一下,因为FormData是根据name获取值的
  • 注册表单提交事件
    • 收集表单各项数据 (FormData只收集到了 title/state/cate_id 这三个值)
    • content需要通过插件 tinymce 的特有方式来获取,获取之后,更改fd中的content值
      • tinyMCE.activeEditor.getContent() 使用这行代码获取文章内容
      • 使用 fd.set('content', tinyMCE.activeEditor.getContent());
    • 完成图片裁剪,转换成blob格式,并将得到的图片追加到FormData中
// ---------------------  完成最终的添加文章 ----------------------
$('#add-form').on('submit', function (e) {
  e.preventDefault();
  // 收集表单数据(必须是FormData)
  var fd = new FormData(this);
  // fd对象中,有content,但是值为空; 根本就没有 图片
  // 1. 获取富文本编辑器里面的内容,并不是追加到fd中,而是更改fd里面的内容
  fd.set('content', tinyMCE.activeEditor.getContent());

  // 2. 剪裁图片,转成 blob 形参(二进制形式或文件对象形式),追加到fd中
  var canvas = $image.cropper('getCroppedCanvas', {
    width: 400,
    height: 280
  });

  // 把canvas图片转成二进制形式
  canvas.toBlob(function (blob) {
    // 追加文件对象到fd中
    fd.append('cover_img', blob);

    // 检查一下,fd对象中,是否取得了接口要求的所有参赛
    // fd.forEach((val, key) => {
    //     console.log(key, val);
    // });
    // return;
    // 发送ajax请求,完成最终的添加
    $.ajax({
      type: 'POST',
      url: '/my/article/add',
      data: fd,
      success: function (res) {
        layer.msg(res.message);
        if (res.status === 0) {
          // 添加成功,跳转到 文章列表 页面
          location.href = '/article/article.html'
        }
      },
      processData: false, // 不要处理数据;意思是不要把对象形式的fd转换成查询字符串形式
      contentType: false // 不要加默认的请求头(application/x-www-form-urlencoded),让浏览器自行设置请求头
    });
  });
})

# 编辑文章

# 思路

  • 复制添加文章页为编辑页面(edit.html),css和js同样复制一份,并修改css和js链接。

  • 编辑页面,打开之后,需要做数据回填。

  • 后续实现,和添加基本一样。

# 实现

  • 复制 add.html 为 edit.html (编辑页面)、css和js自行复制,别忘记修改css和js链接。

  • 文章列表页面(list.html),给 ”编辑“ 挂超链接,链接到 edit.html ,并且传递 id 参数

    <a href="./edit.html?id={{val.id}}" class="layui-btn layui-btn-xs">编辑</a>
    

    vscode中千万不要直接打开edit.html ,否则获取不到文章id;

    应该先打开文章列表页面,通过点击编辑按钮跳转到edit.html才是正确的。

  • edit.js 中 获取地址栏的id,根据id查询一篇文章详情,然后完成表单数据渲染

// 获取地址栏的id,这个id是文章的id; var id = new URLSearchParams(location.search).get('id'); // console.log(id);

```
  • 下拉框的分类渲染成功后,完成数据回填

    
    // -------------------------- 获取分类,渲染到下拉框的位置 --------
    $.ajax({
      url: '/my/article/list',
      success: function (res) {
        var html = template('tpl-category', res);
        $('select[name=cate_id]').html(html);
        form.render('select');
        // 下拉框的分类渲染完成,然后再去发送ajax请求,获取文章详情
        // 根据id可以获取文章详情(标题、内容、状态、图片.....)全部获取到
        $.ajax({
          // url: '/my/article/:id', // 把 :id 换成真实的id即可
          url: '/my/article/' + id,
          success: function (res) {
            // console.log(res);
            // 获取到详情后,做数据回填 (使用layui提供的 form.val())
            form.val('article', res.data);
            // 一定先做数据回填,然后在把 textarea 换成 富文本编辑器
            initEditor();
            // 更换图片(销毁剪裁区 --> 更换图片 --> 重建剪裁区)
            $image
              .cropper('destroy')
              .attr('src', baseUrl + '/' + res.data.cover_img)
              .cropper(options);
          }
        });
      }
    });
    
  • edit.js中,图片剪裁默认铺满整个区域

    var options = {
            // 宽高比
            aspectRatio: 400 / 280,
            autoCropArea: 1, // 让剪裁框铺满整个剪裁区
            // 设置预览区的选择器
            preview: '.img-preview'
        };
    
  • 添加Id

    // 追加Id
    data.append('id', id);
    
  • 修改添加文章的接口为更新文章的接口即可,其他都不需要修改。

# 分页

  • 文章列表页,加载layui的laypage模块
  • 编写渲染分页的函数 (showPage)
  • 渲染完文章列表之后,马上渲染分页(在renderArticle函数里面,ajax请求成功后,调用showPage(res.total)
  • showPage函数
    • 根据官方文档,生成分页效果
    • jump事件中,修改请求参数中的pagenum和pagesize,并重新渲染列表
/****          加载layui的laypage模块       *******/
var laypage = layui.laypage;


/****          全局设置请求参数       *******/
var data = {
    pagenum: 1, // 页码值
    pagesize: 2, // 每页显示多少条
    // cate_id: ,
    // state: ,
};



/****          定义renderArticle函数,获取文章列表数据;成功后调用createPage() 函数 ******/
function renderArticle() {
    $.ajax({
        url: '/my/article/list',
        data: data,
        success: function (res) {
            console.log(res);
            // res.total // 总数
            // 通过模板引擎,渲染
            let str = template('list', res);
            $('tbody').html(str);
            // 当ajax请求成功之后,获取到总数之后,调用显示分页的函数
            showPage(res.total);
        }
    });
}

/*********    定义showPage函数   **********/
// 实现分页
function showPage (t) {
    laypage.render({
        elem: 'page', // 不要加 #
        count: t, // 表示总计有多少条数据
        limit: data.pagesize, // 每页显示多少条
        limits: [2, 3, 4, 5],
        curr: data.pagenum, //  起始页(控制页码的背景色,表示是选中状态)
        // prev: '上一个'
        layout: ['limit', 'prev', 'page', 'next', 'count', 'skip'],
        // 点击页码的时候,会触发下面的jump函数。页面刷新之后,也会触发一次
        jump: function (obj, first) {
            // console.log(obj); // 表示前面控制分页的所有属性
            // console.log(first); // 刷新页面之后,是tru,再点击页码,它就是undefined了
            // 点击页码的时候,jump函数会触发,此时,改变data.pagesize和data.pagenum,调用renderArticle即可看对对应页的数据
            if (!first) {
                // console.log(obj.curr);
                data.pagenum = obj.curr;
                data.pagesize = obj.limit;
                renderArticle();
            }        
        }
    });
}

实现分页,就是修改请求参数(pagenum和pagesize),然后重新发送ajax请求,重新渲染页面。

# 筛选

# 处理搜索区的两个下拉框

  • 分类的获取

    // -------------------------- 筛选 ------------------------------
    var form = layui.form;
    // 1. 获取真实的分类,渲染到下拉框的位置
    $.ajax({
      url: '/my/category/list',
      success: function (res) {
        // console.log(res)
        var str = template('tpl-category', res);
        // $('#category').append(str);
        $('#category').html(str);
        // 更新渲染
        // form.render('select', 'lay-filter属性值');
        form.render('select');
      }
    })
    
    <!-- 分类下拉框留空 -->
    <select id="category" lay-verify="">
        
    </select>
    
    <!-- 分类的模板 -->
    <script type="text/html" id="tpl-category">
      <option value="">请选择一个分类</option>
      {{each data item}}
      <option value="{{item.id}}">{{item.name}}</option>
      {{/each}}
    </script>
    
  • 状态自行处理即可

    <select id="state" lay-verify="">
        <option value="">所有状态</option>
        <option value="已发布">已发布</option>
        <option value="草稿">草稿</option>
    </select>
    

# 完成搜索功能

  • 思路
    • 根据搜索条件,改变请求参数即可。
  • 监听搜索区的表单(自己加id=search)的提交事件
    • 获取下拉框的值,根据下拉框的id获取值
    • 修改获取文章列表的请求参数
    • 重置页码为1
    • 重新渲染文章列表
// 2. 完成筛选
$('#search').on('submit', function (e) {
  e.preventDefault();
  // 获取两个下拉框的值
  var cate_id = $('#category').val();
  var state = $('#state').val();
  // 设置ajax请求的参数
  if (cate_id) {
    data.cate_id = cate_id;
  } else {
    delete data.cate_id; // delete 用于删除对象的属性
  }

  if (state) {
    data.state = state;
  } else {
    delete data.state;
  }

  // 重置页码为 1
  data.pagenum = 1;

  renderArticle(); // 调用renderArticle();渲染页面即可

})

# 处理common.js

当token过期了,需要跳转到登录页重新登录。

但是,从 index.html 跳转到 login.html ,路径是 ./login.html

从其他小页面跳转到 login.html ,路径是 ../login.html,而且应该让父页面跳转。

所以,当ajax请求完成后,判断如果是身份认证失败了。继续判断是哪个页面。

option.complete = function (xhr) {
  var res = xhr.responseJSON;
  if (res && res.status === 1 && res.message === '身份认证失败!') {
    // 清除掉过期的token
    localStorage.removeItem('token');
    // 跳转到登录页 (location.pathname 表示url的路径部分)
    if (location.pathname === '/index.html') {
      location.href = './login.html';
    } else {
      window.parent.location.href = '../login.html';
    }
  }
  // 其他错误
  if (res && res.status === 1) {
    layer.msg(res.message);
  }
}