day01 - 本地仓库+远程仓库

git基础入门

Git是一款免费、开源的分布式 版本控制系统 ,用于敏捷高效地处理任何或小或大的项目。

Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

git的安装

下载地址:https://git-scm.com/download

注意:

  1. 不要安装在中文目录
  2. 不要使用桌面管理软件

安装很简单,一直下一步即可。在任意的目录下右键,能看到菜单, 就表示安装成功了。

git三个区

要对某个项目使用git进行管理,需要使用git init命令初始化git仓库
git init会在当前目录生成一个隐藏文件夹 .git 不要去修改这个文件夹下的任意东西。

git仓库会分成三个区

工作区:我们书写代码的地方,工作的目录就叫工作区。

暂存区:暂时存储的区域,在git中,代码无法直接从工作区提交到仓库区,而是需要先从工作区添加到暂存区,然后才能从暂存区提交到仓库区。暂存区的目的是避免误操作。

本地仓库区:将保存在暂存区域的内容永久转储到 Git 仓库中,生成版本号。生成版本号之后,就可以任何的回退到某一个具体的版本。

git基本命令

git init

  • 作用:初始化git仓库,想要使用git对某个项目进行管理,需要git init进行初始化
# 初始化仓库, 在当前目录下生成一个隐藏文件夹.git
git init

git add

  • 作用:将文件由 工作区 添加到 暂存区,在git中,文件无法直接从工作区直接添加到仓库区,必须先从工作区添加到暂存区,再从暂存区添加到仓库区。
  • 命令:git add 文件名/目录名
# 将index.html添加到暂存区
git add index.html

# 将css目录下所有的文件添加到暂存区
git add css

# 将当前目录下所有的js文件添加到暂存区
git add *.js

# 添加当前目录下所有的文件
git add .
git add -A
git add --all

git commit

作用:将文件由 暂存区 添加到 仓库区,生成版本号

# 将文件从暂存区提交到仓库
git commit -m "提交说明"

# 如果是一个已经暂存过的文件,可以快速提交,如果是未追踪的文件,那么命令将不生效。
git commit -a -m '提交说明'

# 修改最近的一次提交说明, 如果提交说明不小心输错了,可以使用这个命令
git commit --amend -m "提交说明"

git config配置

如果是第一次提交,需要配置提交者信息,推荐和公司邮箱一致(可以用QQ邮箱)

# git config  user.name 你的目标用户名
# git config  user.email 你的目标邮箱名

# 使用--global参数,配置全局的用户名和邮箱,只需要配置一次即可
git config  --global user.name jepson
git config  --global user.email jepsonpp@qq.com

# 查看配置信息
git config --list

# 取消配置
git config --unset --global user.name
git config --unset --global user.email 

git status

  • 作用:查看文件的状态

  • 命令:git status

    • 红色表示工作区中的文件需要提交
    • 绿色表示暂存区中的文件需要提交
  • 命令:git stauts -s 简化日志输出格式

git log

  • 作用:查看提交日志
  • git log 查看提交的日志
  • git log --oneline 简洁的日志信息

git重置

git reset

  • 作用:版本回退,将代码恢复到已经提交的某一个版本中。

  • git reset --hard 版本号 将代码回退到某个指定的版本(版本号只要有前几位即可)

  • 当使用了git reset命令后,版本会回退,使用git log只能看到当前版本之前的信息。使用git reflog可以查看所有的版本信息

git忽视文件

在仓库中,有些文件是不想被git管理的,比如数据的配置密码、写代码的一些思路,node_modules等。git可以通过配置从而达到忽视掉一些文件,这样这些文件就可以不用提交了。

  • 在仓库的根目录创建一个.gitignore的文件,文件名是固定的。
  • 将不需要被git管理的文件路径添加到.gitignore
# 忽视idea.txt文件
idea.txt

# 忽视css下的index.js文件
css/index.js

# 忽视css下的所有的js文件
css/*.js

# 忽视css文件夹
css

git分支操作

分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。

如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!

为什么要有分支?

  • 如果你要开发一个新的功能,需要2周时间,第一周你只能写50%代码,如果此时立即提交,代码没写完,不完整的代码会影响到别人无法工作。如果等代码写完再提交,代码很容易丢失,风险很大。
  • 有了分支,你就可以创建一个属于自己的分支,别人看不到,也不影响别人,你在自己的分支上工作,提交到自己的分支上,等到功能开发完毕,一次性的合并到原来的分支。这样既安全,又不影响他人工作。

git分支命令

创建分支

  • git branch 分支名称创建分支,分支中的代码,在创建时与当前分支的内容完全相同。
  • git在第一次提交时,就有了一个叫master的主分支。
  • git branch dev,创建了一个叫做dev的分支

查看分支

  • git branch可以查看所有的分支,
  • 在当前分支的前面会有一个*
  • 在git中,有一个特殊指针HEAD,永远会指向当前分支

切换分支

  • git checkout 分支名称切换分支 HEAD指针指向了另一个分支
  • 在当前分支的任何操作,都不会影响到其他的分支,除非进行了分支合并。
  • 提交代码时,会生产版本号,当前分支会指向最新的版本号。

创建并切换分支

  • git checkout -b 分支名称 创建并切换分支
  • 切换分支会做两件事情
    • 创建一个新分支
    • 把head指针指向当前的分支

删除分支

  • git branch -d 分支名称 可以删除分支
  • 注意:不能在当前分支删除当前分支,需要切换到其他分支才能删除。
  • 注意:master分支是可以删除的,但是不推荐那么做。

合并分支

  • git merge 分支名称 将其他分支的内容合并到当前分支。
  • master分支中执行git merge devdev分支中的代码合并到master分支
  • 分支合并

git合并冲突

  • 对于同一个文件,如果有多个分支需要合并时,容易出现冲突。
  • 合并分支时,如果出现冲突,只能手动处理,再次提交,一般的作法,把自己的代码放到冲突代码的后面即可。

在git中,分支实质上仅仅是一个指针,每次代码提交后,这个分支指针就会向后移动,保证一直指向最后一次提交的的版本。git中使用HEAD指向当前分支

git远程仓库

github、gitee、gitlab

  • git是一个版本控制工具。
  • github是一个代码托管平台,开源社区,是git的一个远程代码仓库平台。
//1. gitHub是一个面向开源及私有软件项目的托管平台,因为只支持git 作为唯一的版本库格式进行托管,故名gitHub。
//2. github免费,代码所有人都能看到,但是只有你自己能修改。付费的可以隐藏。
//3. 创建git项目时,不能有中文。

github官网: 国外开源 git 代码托管平台

开源中国-git: 国内开源 git 代码托管平台

gitlab:企业级 git 代码托管平台

码云创建远程仓库

  1. 新建右上角仓库

  2. 输入仓库名 不勾初始化!!!

  3. 点击创建

SSH免密码登陆

git支持多种数据传输协议:

  • https协议:https://gitee.com/jepsonpp/test.git 需要输入用户名和密码
  • ssh协议:git@gitee.com:jepsonpp/test.git 可以配置免密码登录

每次push或者pull代码,如果使用https协议,那么都需要输入用户名和密码进行身份的确认,非常麻烦。

  • github为了账户的安全,需要对每一次push请求都要验证用户的身份,只有合法的用户才可以push
  • 使用ssh协议,配置ssh免密码,可以做到免密码往github推送代码

SSH免密码登录配置

  • 1 创建SSH Key:ssh-keygen -t rsa (注意:这些命令需要在bash中敲)
  • 2 在文件路径 C:\用户\当前用户名\ 找到 .ssh 文件夹
  • 3 文件夹中有两个文件:
    • 私钥:id_rsa
    • 公钥:id_rsa.pub
  • 4 在 码云-> 设置 -> SSH公钥页面中
  • 5 粘贴 公钥 id_rsa.pub 内容到对应文本框中, 添加公钥

git push

  • 作用:将本地仓库中代码提交到远程仓库
  • git push 仓库地址 分支名 将代码提交到远程仓库对应分支
  • 例子:git push git@gitee.com:jepsonpp/test.git master
git push <远程主机名> <本地分支名>:<远程分支名>
如果本地分支名与远程分支名相同,则可以省略冒号

git pull

  • 作用:拉取更新,将远程的代码下载合并到本地的分支

  • 通常在push前,需要先pull一次。

# 获取远程仓库的更新,并且与本地的分支进行合并
git pull
git pull <远程主机名> <分支名>
git pull origin login  # 获取远程分支的更新,并更新合并到login分支

git clone

  • 作用:克隆远程仓库的代码到本地
  • git clone [远程仓库地址]
  • git clone git@gitee.com:jepsonpp/test.git会在本地新建一个test文件夹
  • 在test中包含了一个.git目录,用于保存所有的版本记录,同时test文件中还有最新的代码,可以进行后续的开发。
  • git克隆默认会使用远程仓库的项目名字,也可以自己指定。命令:git clone [远程仓库地址] [本地项目名]

tips: git branch -a 查看分支

下载远端分支本地

checkout-t (或) --track )选项仅在创建新的(本地)分支时使用

先在本地建立一个分支,并切换到该分支,然后从远程分支上同步代码到该分支上,并建立关联

git checkout -t origin/develop     #远端分支名和本地新建分支名同名

后续拉取该分支的更新,就是切换到该分支,git pull origin 分支名

git remote

每次push操作都需要带上远程仓库的地址,非常的麻烦,我们可以给仓库地址设置一个别名

# 给远程仓库设置一个别名
git remote add 仓库别名 仓库地址
git remote add origin git@gitee.com:jepsonpp/test.git

# 删除origin这个别名
git remote remove origin

演示命令:git push -u 仓库别名 分支名

day02 - 可视化项目-注册登录&页面访问控制

可视化项目

接口地址

项目初始化

  1. 基于模板,初始化仓库(模板文件拷贝到项目文件夹中)
git init
  1. 提交到暂存区
git add .

git commit -m '初始化仓库'
  1. 创建并切换到develop分支
git checkout -b develop

bootstrap轻提示

bootstrap轻提示 - 测试

官网地址:https://v5.bootcss.com/docs/components/toasts/

  1. 准备结构, 必须有 toast
<div id="box" class="toast">我是轻提示</div>
  1. 引包 js 和 css
<link rel="stylesheet" href="./bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="./bootstrap/bootstrap-icons.css" />
<script src="./bootstrap/bootstrap.min.js"></script>
  1. 编写js代码实例化
// bootstrap轻提示
const toastBox = document.querySelector('#box')
const toast = new bootstrap.Toast(toastBox, {
  animation: true, // 开启过渡动画
  autohide: true, // 开启自动隐藏
  delay: 3000 // 3000ms后自动隐藏
})

toast.show()

bootstrap轻提示 - 应用

  1. 准备结构(已准备)
  2. 引包(已引入)
  3. 编写js代码
// 把bootstrap轻提示的代码封装到一个函数中,并放入common.js中
function tip(msg) {
   	const toastBox = document.querySelector('#myToast')
    const toast = new bootstrap.Toast(toastBox, {
      animation: true, // 开启过渡动画
      autohide: true, // 开启自动隐藏
      delay: 3000 // 3000ms后自动隐藏
    })
  	toastBox.querySelector('.toast-body').innerHTML = msg
  	toast.show()
}


// === 测试代码 ===
tip('请输入用户名')

表单数据收集

文档地址:https://www.npmjs.com/package/form-serialize

  1. 准备form表单, 表单元素需要添加 name
<form id="form">
  <input type="text" name="username"> <br>
  <input type="password" name="password"> <br>
  <button>登录</button>
</form>
  1. 引包
<script src="./lib/form-serialize.js"></script>
  1. 注册事件,收集表单信息
const form = document.querySelector('#form')

form.addEventListener('submit', function(e) {
  e.preventDefault()
  console.log(serialize(form)) // 把数据收集到了键值对字符串中
  console.log(serialize(form, { hash: true })) // 把数据收集到了对象中
})

注册功能

  1. 给form表单注册提交事件,并阻止默认行为

  2. 收集表单数据

    // 1-注册submit提交事件
    const form = document.querySelector('form')
    form.addEventListener('submit', function (e) {
        // 2-阻止默认行为
        // 3-收集表单数据
        e.preventDefault()
        const data = serialize(form, { hash: true })
        console.log(data)
    })
    
  3. 数据校验(非空和格式校验)

    // 如果没填数据,则data对象中没有对应的属性
    
    const reg1 = /^[a-zA-Z0-9_]{6,}$/
    if (!data.username || !reg1.test(data.username)) {
        return tip('用户名至少6位')
    }
    
    const reg2 = /^\S{6,18}$/
    if (!data.password || !reg2.test(data.password)) {
        return tip('密码必须是6-18位的非空字符!')
    }
    
  4. common.js 配置请求基地址

axios.defaults.baseURL = 'http://ajax-api.itheima.net'
  1. 请求注册,处理异常提示
// 1-注册submit提交事件
const form = document.querySelector('form')
form.addEventListener('submit', async function (e) {
    // 2-阻止默认行为
    // 3-收集表单数据
    e.preventDefault()
    const data = serialize(form, { hash: true })
    // console.log(data)
    // 4-校验数据
    const reg1 = /^[a-zA-Z0-9_]{6,}$/
    if (!data.username || !reg1.test(data.username)) {
        return tip('用户名至少6位')
    }

    const reg2 = /^\S{6,18}$/
    if (!data.password || !reg2.test(data.password)) {
        return tip('密码必须是6-18位的非空字符!')
    }
    // 5-发请求,提交数据
    // 处理请求失败的异常错误
    try {
        const res = await axios.post('/register', data)
        tip('注册成功!')
        // 跳转到登录页
        setTimeout(function () {
            location.href = './login.html'
        }, 500)
    } catch (e) {
        // console.dir(e)
        if (e.response.status === 409) {
            return tip(e.response.data.message)
        }
        tip('注册失败,服务器繁忙!')
    }
})

代码提交

git add .

git commit -m '注册功能完成'

登录功能

登录的代码如下:

// 1-注册submit提交事件
const form = document.querySelector('form')
form.addEventListener('submit', async function (e) {
    // 2-阻止默认行为
    // 3-收集表单数据
    e.preventDefault()
    const data = serialize(form, { hash: true })
    // console.log(data)
    // 4-校验数据(只做非空判断)
    if (!data.username) {
        return tip('请输入用户名')
    }

    if (!data.password) {
        return tip('请输入密码!')
    }
    // 5-发请求,提交数据
    // 处理请求失败的异常错误
    try {
        const res = await axios.post('/login', data)
        tip('登录成功!')
        // 跳转到登录页
        setTimeout(function () {
            location.href = './index.html'
        }, 500)
    } catch (e) {
        // console.dir(e)
        if (e.response.status === 400) {
            return tip(e.response.data.message)
        }
        tip('登录失败,服务器繁忙!')
    }
})

代码提交

git add .

git commit -m '登录功能完成'

JWT-接口权限访问限制(理解)

什么是jwt身份认证

在前后端分离模式的开发中,服务器如何知道来访者的身份呢?

  • 在登录后,服务器会响应给用户一个 令牌 (token)
  • 令牌中会包括该用户的id等唯一标识
  • 浏览器收到令牌后,自己保存
  • 下次请求其他接口时,(在请求头中)携带这个令牌去请求
  • 这样服务器就知道来访者的身份了,服务器就会为该用户开发接口的访问权限,并处理该用户的数据

前端代码中的token使用

登录后:要将token保存到本地存储中了。顺带个人信息也存一下,用于回显。

// 保存登录成功,响应的token数据到本地存储
localStorage.setItem('user-token', res.data.data.token)
// 把用户名也保存到本地存储中
localStorage.setItem('user-name', res.data.data.username)

页面访问权限控制

页面访问拦截

系统管理页面,如果用户没有登录,能直接访问吗?不能

如果控制用户的范文权限呢?可以通过合理使用令牌,控制页面的访问权限。

比如,用户默认只能访问登录页,如果不登录就不能访问首页,怎么做?

首页访问限制:判断本地存储是否有token, 没有token拦截到登录

<!-- 本地存储有token,则说明用户登录了;没有token,则说明用户没有登录,不允许访问首页 -->
<script>
  if (localStorage.getItem('user-token') === null) location.href = './login.html'
</script>

注意:上述判断只能判断token有没有,但不能判断token的真假,所以将来需要发送Ajax请求,根据服务器响应结果再次判断

代码提交

git add .

git commit -m '页面访问拦截完成'

首页显示用户名称和退出登录

common.js

// 显示用户名称和退出登录
const userName = document.querySelector('.navbar .font-weight-bold')
if (userName) {
  userName.innerHTML = localStorage.getItem('user-name')
}

const logout = document.querySelector('#logout')
if (logout) {
  logout.addEventListener('click', () => {
    localStorage.removeItem('user-token')
    localStorage.removeItem('user-name')
    location.href = './login.html'
  })
}

axios 拦截器

首页获取统计数据

在首页获取后台统计数据,发现401

// DOMContentLoaded 当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发
// 而无需等待样式表、图像和子框架的完成加载
document.addEventListener('DOMContentLoaded', async () => {
  const res = await axios.get('/dashboard')
  console.log(res);
})

原因:

  1. 后台接口需要进行身份认证,请求时,需要在请求头中携带 token
  2. 未携带token,或token过期,后台都会返回401
document.addEventListener('DOMContentLoaded', async () => {
  const token = localStorage.getItem('user-token')
  const res = await axios.get('/dashboard', {
    headers: {
      'Authorization': token
    }
  })
  console.log(res);
})

请求拦截器

axios拦截器:http://www.axios-js.com/zh-cn/docs/#拦截器

利用请求拦截器, 本地读取token, 设置给请求头

common.js:

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    
  // 在发送请求之前做些什么
  const token = localStorage.getItem('user-token')
  if (token) {
    config.headers.Authorization = token
  }
    
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response;
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error);
});

响应拦截器

应用一:处理401状态码, 清除本地存储的数据(token),跳转到登录

token时效两小时,修改本地token,发现401 (模拟token失效)

// 添加响应拦截器
axios.interceptors.response.use(
  function (response) {
    // 对响应数据做点什么
    return response
  },
  function (error) {
    // 对响应错误做点什么
    // 统一处理请求失败
    if (error.response.status === 401) {
      localStorage.removeItem('user-token')
      localStorage.removeItem('user-name')
      location.href = './login.html'
    }
    return Promise.reject(error)
  }
)

应用二:分析响应回来的数据层级,进行数据剥离,数据剥离后,更新login逻辑

// 添加响应拦截器
axios.interceptors.response.use(
  function (response) {
    // 对响应数据做点什么
    // 剥离一层数据
    return response.data
  },
  function (error) {
    // 对响应错误做点什么
    if (error.response.status === 401) {
      localStorage.removeItem('user-token')
      localStorage.removeItem('user-name')
      location.href = './login.html'
    }
    return Promise.reject(error)
  }
)

修改login.html中,登录的代码:

form.addEventListener('submit', async () => {
  // 省略其他代码...
  try {
      const res = await axios.post('/login', data)
      tip('登录成功!')
      // 保存token到本地存储
      // localStorage.setItem('user-token', res.data.data.token)
      // localStorage.setItem('user-name', res.data.data.username)
      localStorage.setItem('user-token', res.data.token)
      localStorage.setItem('user-name', res.data.username)
      // 跳转到登录页
      setTimeout(function () {
          location.href = './index.html'
      }, 500)
  } catch (e) {
      // console.dir(e)
      if (e.response.status === 400) {
          return tip(e.response.data.message)
      }
      tip('登录失败,服务器繁忙!')
  }
})

代码提交

git add .

git commit -m 'axios拦截器添加完成'

综合练习

  1. 创建远程仓库,将数据可视化项目上传到码云仓库, 主分支与开发分支均要上传
git push origin master

git push origin develop
  1. 删除本地项目,通过克隆把远端仓库项目拉取下来,切换到develop分支
git clone 仓库地址

git checkout -t origin/develop

day03 - Echarts数据可视化

Echarts-介绍

ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖矢量图形库 ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图表。

大白话:

  • 是一个JS插件
  • 性能好可流畅运行PC与移动设备
  • 兼容主流浏览器
  • 提供很多常用图表,且可定制

Echarts-入门

官方教程:快速上手ECharts

使用步骤

<div id="main" style="width: 600px; height:400px;"></div>
  • 初始化echart实例
const myChart = echarts.init(document.getElementById('main'));
  • 指定图表的配置项和数据 (根据文档提供示例找到option)
// 指定图表的配置项和数据
const option = {
  title: {
    text: 'ECharts 入门示例',
  },
  tooltip: {},
  legend: {
    data: ['销量'],
  },
  xAxis: {
    data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
  },
  yAxis: {},
  series: [
    {
      name: '销量',
      type: 'bar',
      data: [5, 20, 36, 10, 10, 20],
    },
  ],
}
  • 使用刚指定的配置项和数据显示图表
myChart.setOption(option);

示例配置项&文档查看方式

上节我们绘制了一个简单的柱状图图表,本节教大家如何通过文档中查看配置项option的一些说明

讲解内容: 通过文档找到刚刚完成的柱状图配置项对应的一些配置的说明

// 指定图表的配置项和数据
const option = {
    title: {  // 标题组件
        text: 'ECharts 入门示例', // 主标题文本
    },
    tooltip: {}, // 提示框组件
    legend: {  // 图例组件
        data: ['销量2']  // 图例的数据数组,对应series里的name
    },
    xAxis: { // 直角坐标系 grid 中的 x 轴
        data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
    },
    yAxis: { // 直角坐标系 grid 中的 y 轴, y轴里的data没有指定会自动从series.data里获取
    },
    series: [{
        name: '销量2', // 系列名称,用于tooltip的显示,legend 的图例筛选
        type: 'bar',  // 柱状图
        data: [5, 20, 36, 10, 10, 20] // 系列中的数据内容数组。数组项通常为具体的数据项
    }]
};

Echarts基本配置项(★)

  • title:标题组件
  • tooltip:提示框组件
  • legend:图例组件
  • grid:直角坐标系内绘图网格。
  • xAxis:直角坐标系 grid 中的 x 轴
  • yAxis:直角坐标系 grid 中的 y 轴
  • series:系列列表。每个系列通过 type 决定自己的图表类型
  • color:调色盘颜色列表
option = {
  // 标题
  title: {
    text: '折线图'
  },
  // 提示框
  tooltip: {
    trigger: 'axis'
  },
  // 图例
  legend: {
    // data中的值要和series中的name一致!
    data: ['邮箱', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine']
  },
  // 网格
  grid: {
    left: '13%',
    right: '14%',
    bottom: '13%',
    top: 100,
    // 网格溢出了容器,是否展示刻度标签(轴上的文字)
    containLabel: true
  },
  // 工具栏
  toolbox: {
    feature: {
      saveAsImage: {}
    }
  },
  // x轴的配置
  xAxis: {
    // 轴的类型,category类目轴,value数值轴
    type: 'category',
    // 留白策略(刻度标签是否和刻度重合)
    boundaryGap: false,
    // 类目数据
    data: ['星期一', '星期二', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  },
  yAxis: {
    type: 'value',
    // 轴线
    axisLine:{
      show: true
    },
    axisTick:{
      show: true
    }
  },
  // 图表的颜色
  color:['red', 'green', 'blue', 'pink', 'hotpink'],
  // 系列(具体的图表数据)
  series: [
    {
      // 系列的名字(图例展示的名字)
      name: '邮箱',
      // 图表的类型,line 折线图
      type: 'line',
      // 数据叠加
      // stack: 'Total1',
      // 数据
      data: [120, 132, 101, 134, 90, 230, 210]
    },
    {
      name: 'Union Ads',
      type: 'line',
      // stack: 'Total2',
      data: [220, 182, 191, 234, 290, 330, 310]
    },
    {
      name: 'Video Ads',
      type: 'line',
      // stack: 'Total',
      data: [150, 232, 201, 154, 190, 330, 410]
    },
    {
      name: 'Direct',
      type: 'line',
      // stack: 'Total',
      data: [320, 332, 301, 334, 390, 330, 320]
    },
    {
      name: 'Search Engine',
      type: 'line',
      // stack: 'Total',
      data: [820, 932, 901, 934, 1290, 1330, 1320]
    }
  ]
};

day04 - 学生管理案例

学生信息渲染

把获取学生数据和渲染表格的代码封装到函数中,页面初始化时调用。

async function renderList() {
  // 1-获取学生数据
  const {data} = await axios.get('/students')
  // 2-遍历数组对象,生成tr
  const htmlArr = data
    .map((item, i) => {
      return `
          <tr>
            <td>${item.name}</td>
            <td>${item.age}</td>
            <td>${item.gender === 0 ? '男' : '女'}</td>
            <td>第${item.group}组</td>
            <td>${item.hope_salary}</td>
            <td>${item.salary}</td>
            <td>${item.province} ${item.city} ${item.area}</td>
            <td>
              <a href="javascript:;" class="text-success mr-3"><i data-id=${
                item.id
              } class="bi bi-pen"></i></a>
              <a href="javascript:;" class="text-danger"><i data-id=${
                item.id
              } class="bi bi-trash"></i></a>
            </td>
          </tr>
        `
    })
  // 3-渲染数据
  document.querySelector('.list').innerHTML = htmlArr.join('')
  document.querySelector('.total').innerHTML = res.data.length
}
renderList()

学生信息删除

  1. 事件委托绑定点击事件,获取学生ID
  2. 调用删除接口完成删除,删除完毕更新列表
// 删除学生
document.querySelector('.list').addEventListener('click', async (e) => {
  const btn = e.target
  // 判断点击的是否为“删除按钮”
  if (btn.classList.contains('bi-trash')) {
    // 删除
    try {
      await axios.delete(`/students/${btn.dataset.id}`)
      renderList()
    } catch (error) {
      alert('删除失败')
    }
  }
})

学生信息添加

bootstrap模态框

模态框 - 显示

官方地址:https://v5.bootcss.com/docs/components/modal/#methods

  1. 准备结构(已准备)
  2. 实例化模态框
  3. API方式显示隐藏
const modalBox = document.querySelector('#modal')
const modal = new bootstrap.Modal(modalBox)

modal.show() // 显示
modal.hide() // 隐藏
modal.toggle() // 切换

模态框 - 优化

const modalBox = document.querySelector('#modal')
const modal = new bootstrap.Modal(modalBox)

document.querySelector('#openModal').addEventListener('click', () => {
  // 清空重置模态框中的表单
  modalBox.querySelector('form').reset()
  // 设置标题
  modalBox.querySelector('.modal-title').innerHTML = '添加学员'
  // 给模态框盒子设置 自定义属性(为了区分是添加,还是修改操作)
  modalBox.dataset.id = 'add'
  // 展示模态框
  modal.show()
})

省市区联动(★)

核心思路:

  1. 动态渲染省份
  2. 监听省份选择
  3. 选择省动态渲染市
  4. 监听市的选择
  5. 选择市动态渲染地区

【1】渲染省份数据:

/******** 四、省市区下拉框联动 ********/
// 获取省市区元素
const pSelect = document.querySelector('[name=province]')
const cSelect = document.querySelector('[name=city]')
const aSelect = document.querySelector('[name=area]')

// 4.1 渲染省份数据
// 1)获取省份数据;2)渲染
async function initProvince() {
    const { data: provinceArr } = await axios.get('/api/province')
    // console.log(provinceArr)
    const phtml = provinceArr.map(item => `<option value="${item}">${item}</option>`).join('')
    pSelect.innerHTML = '<option value="">--省份--</option>' + phtml
}
initProvince()

【2】监听省份的选择,渲染城市

// 4.2 监听省份的变化,渲染城市数据
// 1)注册change事件;2)获取选中的省份;3)获取对应的城市数据; 4)渲染
pSelect.addEventListener('change', async function () {
    const p = this.value
    const { data: cityArr } = await axios.get('/api/city', {
        params: {
            pname: p
        }
    })
    const chtml = cityArr.map(item => `<option value="${item}">${item}</option>`).join('')
    cSelect.innerHTML = '<option value="">--城市--</option>' + chtml
})

【3】监听城市的选择,渲染区县

// 4.3 监听城市的变化,渲染区县数据
// 1)注册change事件;2)获取选中的城市;3)获取对应的区县;4)渲染
cSelect.addEventListener('change', async function () {
    const c = this.value
    const { data: areaArr } = await axios.get('/api/area', {
        params: {
            pname: pSelect.value,
            cname: c
        }
    })
    const ahtml = areaArr.map(item => `<option value="${item}">${item}</option>`).join('')
    aSelect.innerHTML = '<option value="">--区县--</option>' + ahtml
})

【4】小优化,省变了,区县要置空

// 4.2 监听省份的变化,渲染城市数据
// 1)注册change事件;2)获取选中的省份;3)获取对应的城市数据; 4)渲染
pSelect.addEventListener('change', async function () {
    // 重置区县
    aSelect.innerHTML = '<option>--区县--</option>'

    const p = this.value
    const { data: cityArr } = await axios.get('/api/city', {
        params: {
            pname: p
        }
    })
    const chtml = cityArr.map(item => `<option value="${item}">${item}</option>`).join('')
    cSelect.innerHTML = '<option value="">--城市--</option>' + chtml
})

调用添加接口完成添加

核心思路:

  1. 点击确认按钮
  2. 收集表单数据, 处理格式
  3. 判断是添加操作,还是编辑操作(★)
  4. 提交表单,页面渲染
/******** 五、添加学生 ********/
// 1)注册事件;2)收集表单数据;3)判断是不是添加操作;4)发请求;5)重新渲染学生列表;
const submitBtn = document.querySelector("#submit")
submitBtn.addEventListener('click', async function () {
    const form = document.querySelector('form')
    const data = serialize(form, { hash: true })

    data.age = +data.age
    data.gender = +data.gender
    data.hope_salary = +data.hope_salary
    data.salary = +data.salary
    data.group = +data.group

    if (modalBox.dataset.id === 'add') {
        // 添加逻辑
        try {
            await axios.post('/students', data)
            modal.hide()
            renderList()
        } catch (error) {
            alert('添加失败')
        }
    }
})

学生信息修改(★)

点修改 - 显示弹框

核心思路:

  1. 事件委托 - 注册点击事件
  2. 设置弹框标题,记录操作 id
  3. 调用API显示弹框

渲染列表时,给编辑按钮,添加自定义属性,保存学生的id

1657591808468

document.querySelector('.list').addEventListener('click', async (e) => {
  const btn = e.target
  // 判断点击的是否为“删除按钮”
  if (btn.classList.contains('bi-trash')) {
    // 删除请求
    try {
      await axios.delete(`/students/${btn.dataset.id}`)
      renderList()
    } catch (error) {
      alert('删除失败')
    }
  }
    /******** 六、修改学生数据 ********/
    // 6.1 显示模态框
    // 1)判断单击“编辑”按钮;2)获取学生id;3)设置标题;4)给模态框设置标识;5)展示模态框;
    if (btn.classList.contains('bi-pen')) {
        const id = btn.dataset.id
        document.querySelector('#modal .modal-title').innerHTML = '修改学员'
        modalBox.dataset.id = id
        modal.show()
    }  
   
})

显示弹框后 - 回显学生数据(*)

核心思路:

  1. 根据 id 获取学生数据
  2. 遍历表单实现基本回显
  3. 处理性别回显
  4. 处理省市区回显

核心代码:

  1. 获取学生数据
if (btn.classList.contains('bi-pen')) {
    const id = btn.dataset.id
    document.querySelector('#modal .modal-title').innerHTML = '修改学员'
    modalBox.dataset.id = id

    // 6.2 获取学生数据
    const { data: studentInfo } = await axios.get(`/students/${id}`)
    console.log(studentInfo)
    
    modal.show()
}
  1. 基本回显
document.querySelector('.list').addEventListener('click', async (e) => {
  const btn = e.target
  ...
  
  if (btn.classList.contains('bi-pen')) {
    const id = btn.dataset.id
    document.querySelector('#modal .modal-title').innerHTML = '修改学员'
    modalBox.dataset.id = id
      
    // 6.2 获取学生数据
    const { data: studentInfo } = await axios.get(`/students/${id}`)
    // console.log(studentInfo)
    // 6.3 回显数据(把学生数据展示到表单中)
    // 1)获取所有的表单项;2)遍历表单项;3)给每个表单项赋值;
    const formItems = [...document.querySelectorAll('form [name]')]
    // console.log(formItems)
    formItems.forEach(item => {
        if (item.name === 'gender') {

        } else {
            item.value = studentInfo[item.name]
        }
    })
      
    modal.show()
  }
})
  1. 处理性别回显
formItems.forEach(item => {
    if (item.name === 'gender') {
        // 判断单选按钮的value 和 学生性别的值 是否相等,如果相等,则单选按钮被选中
        if (+item.value === studentInfo.gender) {
            item.checked = true
        }
    } else {
        item.value = studentInfo[item.name]
    }
})
  1. 处理省市区回显
// 6.4 回显城市和区县
// 1)根据studentInfo的省份,获取所有的城市;2)渲染城市;3)选中学生的市;
const { data: cityArr } = await axios.get('/api/city', {
    params: {
        pname: studentInfo.province
    }
})
// console.log(cityArr)
const htmlStr = cityArr.map(item => `<option value="${item}">${item}</option>`).join('')
cSelect.innerHTML = '<option value="">--城市--</option>' + htmlStr
cSelect.value = studentInfo.city

// 1)根据sutdentInfo中的城市,获取所有的区县;2)渲染区县;3)选中学生的区县;
const { data: areaArr } = await axios.get('/api/area', {
    params: {
        pname: studentInfo.province,
        cname: studentInfo.city
    }
})
// console.log(areaArr)
const ahtml = areaArr.map(item => `<option value="${item}">${item}</option>`).join('')
aSelect.innerHTML = '<option value="">--区县--</option>' + ahtml
aSelect.value = studentInfo.area

调用接口完成修改

核心思路:

  1. 根据修改的 id
  2. 发送请求请求
  3. 关闭弹框
  4. 重新渲染
// 添加 & 修改学生
submitBtn.addEventListener('click', async () => {
  // 收集表单数据
  const data = serialize(form, { hash: true })
  // 处理格式(后台部分数据需要number格式)
  data.age = +data.age
  data.hope_salary = +data.hope_salary
  data.salary = +data.salary
  data.gender = +data.gender
  data.group = +data.group

  if (modalBox.dataset.id === 'add') {
    // 添加逻辑
    try {
      await axios.post('/students', data)
      modal.hide()
      renderList()
      tip('添加成功')
    } catch (error) {
      alert('添加失败')
    }
  } else {
    // 修改逻辑
    try {
      await axios.put(`/students/${modalBox.dataset.id}`, data)
      modal.hide()
      renderList()
    } catch (error) {
      alert('修改失败')
    }
  }
})

码云page服务

  1. 合并到master分支, 推送远程仓库
git checkout master

git merge develop

git push origin master
  1. 使用pages服务发布静态页面

  1. 实名认证

  1. 开启服务

Q.E.D.