客户端储存(cookie,localStorage及sessionStorage)

84 篇文章 3 订阅
1 篇文章 0 订阅

目录

1.知识要点及学习目标

2.客户端储存方案

2.1服务端储存

2.2客户端储存(浏览器离线储存)

3.cookie(前端/后台皆可用)

4.koa中cookie的使用(后台cookie的使用)

4.1储存cookie的值;

4.2获取cookie的值

4.3options常用设置

4.5koa中cookie的使用——登录案例(记住我)

 5.客户端cookie使用方式 (前端JS中cookie的使用)——通过本地cookie实现记录换肤功能

5.1设置

5.2获取

5.3封装js中设置和获取cookie方法

5.3.1设置cookie封装

5.3.2获取cookie

5.4几种皮肤背景色

5.5客户端操作cookie特点

5.6list页面动态渲染页面及实现换肤功能案例——js实现换肤功能

6.本地缓存Storage——localStorage及sessionStorage使用

6.1设置sessionStorage/localStorage.setItem(key, value)

6.2获取sessionStorage/localStorage.getItem(key)

6.3移出指定数据sessionStorage/localStorage.removeItem(key)

6.4清空所有数据sessionStorage/localStorage.clear()

6.5案例——通过storage来改造换肤功能

6.6案例——通过storage实现添加歌曲列表功能

7.本地存储异同

7.1共同点

7.1.1localStorage和sessionStorage和cookie共同点

7.1.2localStorage和sessionStorage共同点

7.2不同点

7.2.1localStorage

7.2.2sessionStorage

7.2.3cookie

8.项目需求总结及完整代码实现

8.1koa框架搭建

8.2登录

8.3歌曲列表

8.4详细页

8.5css文件


1.知识要点及学习目标

知识要点:

  • 在koa中cookie的使用方式
  • 客户端cookie的使用方式
  • localStorage及sessionStorage使用
  • 各种本地存储的异同

学习目标:

  • 学会cookie在服务端及客户端的使用方式
  • 会使用localStorage及sessionStorage做数据的持久化
  • 了解各种本地存储的异同

以登录音乐系统案例为例:

  • 登录页面:首次需要输入用户名和密码,点击记住我后,再次进入登陆页面时不需要再输入,可直接登录;
  • 音乐列表页面:正确登录后会进入到音乐列表页面;
  • 换肤功能:可给音乐列表页面换主题颜色等;
  • 添加功能:可添加音乐到播放列表(持久化存储);点击添加后会跳转到详细页
  • 删除和清空功能:可删除指定歌曲,可清空所有歌曲列表

2.客户端储存方案

2.1服务端储存

  1. 服务端文件储存:如node.js的fs模块
  2. 内存
  3. 数据库:mysql、mongoodb、Oracle等等。

2.2客户端储存(浏览器离线储存)

客户端储存即浏览器端储存,当浏览器刷新时,数据依然存在。建议客户端存储只储存一些简单,没有必要调用数据库的数据,而大数据量或关键数据还是建议通过服务端储存。具体依需求而定。

3.cookie(前端/后台皆可用)

cookie是http协议下,服务端或者脚本可以维护客户端信息的一种方式。可通过后台也可通过前端js操作cookie。

4.koa中cookie的使用(后台cookie的使用)

4.1储存cookie的值;

ctx.cookies.set(name, value, [options])

4.2获取cookie的值

ctx.cookies.get(name, [options])

4.3options常用设置

  • maxAge 一个数字表示从 Date.now() 得到的毫秒数。过期时间
  • expires cookie 过期的 Date。当前时间+过期时间
  • path cookie 路径, 默认是'/'。在哪个路径下cookie起作用
  • domain cookie 域名
  • secure 安全 cookie 设置后只能通过https来传递cookie
  • httpOnly 服务器可访问 cookie, 默认是 true。设置fasle的话,客户端不能访问
  • overwrite 一个布尔值,表示是否覆盖以前设置的同名的 cookie (默认是 false)。如果是 true, 在同一个请求中设置相同名称的所有 Cookie。

4.5koa中cookie的使用——登录案例(记住我)

  • 验证用户名密码是否正确;

  • 实现登录功能,通过记住我实现七天免登录;

步骤分析:

  1. 搭建koa环境:koa框架,koa-views,koa-static,koa-router;app.use()加载各个模块;设置模板引擎;async await异步加载登陆页面;
  2. 登录及错误页面处理:post请求方式(获取参数需要使用koa-bodyparser模块,通过ctx.request.body获取数据),且form表单信息以name提交;将表单输入用户名和密码与数据库数据(没有数据库模拟写死数据)进行校验,校验通过就跳转(ctx.redirect("/list"))到音乐列表页面list.pug,否则跳转到错误页面error.pug(需要添加相应路由);自定义错误页面,定时器5秒后跳转(window.location.href)或点击可直接跳转(注意pug模板引擎中js代码的书写 script.);
  3. 记住我(登录中cookie的应用):首次登录时记住cookie登录状态,再次登录时校验是否是登录状态(记住我是否勾选,没有勾选后台获取不到memberMe字段,勾选后ctx.request.body.memberMe需要储存登录成功的状态),ctx.cookies.set("isLogin");用户名和密码直接通过isLogin储存在浏览器上可以看到,所以使用md5进行简单的加密loginState = md5("zs"+"123"),并通过maxAge设置过期时间,ctx.cookies.set("isLogin",loginState,{maxAge:36000*1000*24*7})
  4. 浏览器端两种方式查看cookie:application下的Cookies;也可通过页面地址栏上“安全”按钮进行查看
  5. 再次登录时判断有没有过期ctx.cookies.get("isLogin"),过期了需要重新登录,没过期判断和原用户名和密码一致就可以直接自动跳转。
  6. cookies信息会有本地文件存储相关信息;且当有cookies时任何一次请求都会在头部信息中带上cookies信息

登录记住用户名密码完整案例实现:

index.js:

const Koa = require("koa");
const Router = require("koa-router");
const views = require("koa-views");
const static = require("koa-static");
// form表单提交是post请求,必须使用koa-bodyparser获取数据
const bodyparser = require("koa-bodyparser");
// md5加密登录信息
const md5 = require("md5");

// node.js通过require后,会自动将json文件中的数据转换为对象
const musicData = require("./data/music.json");

const app = new Koa();
const route = new Router();

// 注意引入的模块都需要通过app.use()将模块和router进行关联
app.use(bodyparser());

// 注意模板引擎是在views中设置
app.use(views(__dirname+"/views"),{
    map:{
        html:"pug"
    }
});
app.use(static(__dirname+"/static"));

route.get("/login",async ctx=>{
    // 每次链接到登录页面时,先检验是否有cookie登录状态,有则直接跳转至list页面
    let loginState = md5("zs"+"8888");
    if(ctx.cookies.get("isLogin") === loginState){
        ctx.redirect("/list");
    }else{
        await ctx.render("login.pug");
    }
});

// 登录页面点击登录后,校验cookie信息,成功自动登录,否则重新登录
route.post("/checkUser",async ctx=>{
    // 模拟数据库写死数据,当用户名为zs,密码为8888时登录成功,直接跳转到list页面
    if(ctx.request.body.username === "zs" && ctx.request.body.pwd === "8888"){
        // 登录信息校验成功后,判断如果点击了memberMe则将登录信息存入到cookie中
        if(ctx.request.body.memberMe === "on"){
            // 通过md5对用户名密码进行加密存到cookie中
            let loginState = md5(ctx.request.body.username+ctx.request.body.pwd);
            ctx.cookies.set("isLogin",loginState,{maxAge:36000*1000*24*7});
        }
        ctx.redirect("/list");

    // 登录失败跳转到错误页面
    }else{
        ctx.redirect("/error");
    }
});

// list歌曲列表页面
route.get("/list",async ctx=>{
    // 渲染list页面,并且推送歌曲数据
    await ctx.render("list.pug",{
        musicData
    });
});

// error错误页面
route.get("/error",async ctx=>{
    await ctx.render("error.pug");
});

app.use(route.routes());
app.listen("8989");

login.js:

doctype html
html(lang='en')
  head
    meta(charset='UTF-8')
    meta(name='viewport', content='width=device-width, initial-scale=1.0')
    meta(http-equiv='X-UA-Compatible', content='ie=edge')
    link(rel='stylesheet', href='css/login.css')
    title Document
  body
    .loginContainer
      h1 登录
      form(action='/checkUser', method='post')
        | 姓名:
        input.inputStyle(type='text', name='username')
        br
        | 密码:
        input.inputStyle(type='password', name='pwd')
        br
        input.loginStyle(type='submit', value='登录')
        |  |
        input(type='checkbox', name='memberMe')
        | 记住我

 login.css:

.loginContainer{
    margin: 0 auto;
    width: 600px;
    text-align: center;
    padding-top: 20px;
    padding-bottom: 50px;
    border: 1px solid;
}
.loginContainer input{
    margin-bottom: 20px;
}
.loginStyle{
    width: 180px;
    height: 40px;
    background: rgb(50,203,77);
    color: white;
    font-size: 17px;
}
.inputStyle{
    width: 200px;
    height: 30px;
    padding: 5px;
    outline: none;
}

.inputStyle:focus{
    border: 1px solid rgb(50,203,77);
}

error.pug:

<!DOCTYPE html>
html(lang="en")
    head
        meta(charset="UTF-8")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        meta(http-equiv="X-UA-Compatible", content="ie=edge")
        title 错误页面
    body
        h1 登录错误
        span.val 5
        span 秒之后自动跳转。。。。。。
        br
        a(href="/login") 点击直接跳转
    //- js定时器控制倒计时5秒后自动跳转
    script.
        let time = 5;
        let timer = setInterval(function(){
            time--;
            //- 如果time倒计时到0自动跳转
            document.querySelector(".val").innerHTML = time;
            if(time<1){
                window.location.href = "/login";
                clearInterval(timer);
            }
        },1000);

 list.pug:

doctype html
html(lang='en')
  head
    meta(charset='UTF-8')
    meta(name='viewport', content='width=device-width, initial-scale=1.0')
    meta(http-equiv='X-UA-Compatible', content='ie=edge')
    link(rel="stylesheet", href="css/list.css")
    title 音乐列表
  body
    button.changeSkin 换肤
    .singerContainer
      img.headImg(src='img/singer.jpg')
      .singerRight
        span.singerName 薛之谦
        span.singerDetail 外文名:Joker 国籍:中国 出生地:上海 职业:歌手、演员、主持人
        span
          span.titleKey 单曲
          span.titleValue 286
          span.titleKey 专辑
          span.titleValue 15
          span.titleKey MV
          span.titleValue 262
        .mytbn
          span.hotSong 播放热门歌曲
          span.attention 关注1316.4万
    .songList
      .hotSongList
        h2 热门歌曲
      ul.listContainer.grayWord
        li.grayWord
        li 歌曲
        li 专辑
        li 时长
    each item,key in musicData
      //- 通过key的奇偶性控制隔行显示灰色背景色grayBg,注意多个class且有判断时的写法
      ul(class="listContainer"+(key%2===0?" grayBg":""))
        li.grayWord #{key+1}
        li #{item.songName}
        li #{item.album}
        li.grayWord #{item.time}
        span.btnController
          a(href='#')
            img(src='img/play.png')
          img(src='img/add.png')

list.css:

li{
    list-style: none;
}
.singerContainer{
    height: 250px;
    margin-top: 40px;
    margin-bottom: 35px;
    width: 100%;
    display: flex;
    flex-direction: row;
}
.headImg{
    border-radius: 50%;
    width: 250px;
    margin-right: 5%;
    margin-left: 5%;
}
.singerRight{
    width: 60%;
    display: flex;
    flex-direction: column;
}
.singerName{
    font-size: 38px;
    font-weight: 400;
}
.singerContainer span{
    margin-bottom: 10px;
}
.titleKey{
    color: #000000;
    font-size: 18px;
    margin-right: 10px;

}
.titleValue{
    font-size: 24px; 
    margin-right: 15px;
}
.hotSong{
    width:178px;
    height: 38px;
    background: #31c27c;
    border: 1px solid #31c27c;
    text-align: center;
    color: white;
    line-height: 38px;
    display: block;
    float: left;
}
.attention{
    display: block;
    height: 38px;
    line-height: 38px;
    width: 180px;
    float: left;
    border: 1px solid #c9c9c9;
    text-align: center;
    margin-left: 10px;
}
.mytbn{
    margin-top: 20px;
}
.songList{
    margin-left: 5%;
    margin-right: 5%;
}
.listContainer{
    display: flex;
    flex-direction: row;
}
.grayWord{
    color: #999;
}
.listContainer li{
    flex: 5;
    height: 50px;
    line-height: 50px;
    position: relative;
}
.listContainer li:nth-child(1){
    flex: 1;
    height: 50px;
    line-height: 50px;
}
.listContainer li:nth-child(4){
    flex: 1;
}
.btnController{
    position: absolute;
    left: 30%;
    display: none;
}
.btnController img{
    width: 40px;
    margin-left: 10px;
}
.grayBg{
    background: #fbfbfd;
}
.changeSkin{
    width: 100px;
    height: 30px;
    background: rgb(97,190,130);
    color: white;
    font-size: 14px;
}

 5.客户端cookie使用方式 (前端JS中cookie的使用)——通过本地cookie实现记录换肤功能

5.1设置

document.cookie="key=value"
  • key和value是包含在一个字符串中
  • 多个key=value使用 ; (分号)分隔

key包含字段:

  • [name] 这个name为自己取的cookie名称,同名的值会覆盖
  • domain 所属域名
  • path 所属路径
  • Expires/Max-Age 到期时间/持续时间 (单位是秒)(注意客户端cookies过期时间单位是秒,服务器端是毫秒)
  • http-only 是否只作为http时使用,如果为true,那么客户端能够在http请求和响应中进行传输,但时客户端浏览器不能使用js去读取或修改

5.2获取

document.cookie

返回值是当前域名下的所有cookie,并按照某种格式组织的字符串 :key=value;key1=value1;......keyn=valuen

5.3封装js中设置和获取cookie方法

5.3.1设置cookie封装

//设置cookie
function setCookie(name,value,options={}){
    let cookieData = `${name}=${value};`;
    for(let key in options){
        let str = `${key}=${options[key]};`;
        cookieData += str;
    }
    document.cookie = cookieData;
}

5.3.2获取cookie

//获取Cookie
function getCookie(name){
    let arr = document.cookie.split("; ");
    for(let i=0;i<arr.length;i++){
        let items = arr[i].split("=");
        if(items[0]==name){
            return items[1];
        }
    }
    return "";
}

5.4几种皮肤背景色

["white","rgb(204,232,207)", "rgb(200,200,169)", "rgb(114,111,128)"]

5.5客户端操作cookie特点

  • 浏览器会主动存储接收到的 set-cookie 头信息的值
  • 有时效性;
  • 可以设置 http-only 属性为 true 来禁止客户端代码(js)修改该值

5.6list页面动态渲染页面及实现换肤功能案例——js实现换肤功能

  1. list页面动态渲染:引入data.json数据,将数据推送到list.pug文件
  2. 换肤功能:点击换肤时,修改list页面背景色ul(class="listContainer"+(key%2===0?" grayBg":""))。window.onload = function(){}。
  3. 换肤功能做本地缓存:ctx.cookies.set()是将cookies信息保存是在服务端;而此处是在js中通过document.cookie = "test=test;Max-Age=3600;",可以设置cookies的各种参数配置(注意客户端cookies过期时间单位是秒,服务器端是毫秒);document.cookie可以直接获取cookies值,如果有多个cookie会用"; "隔开
  4. 手动封装js中设置和获取cookie的方式
  5. 此处使用cookie存在的问题:每次浏览器发送请求都会在头信息里带着cookie信息,但是很多时候后台并不需要这些信息,而且每次地址栏中的信息都会被看到。所以换肤功能并不使用于这里,于是有了storage离线缓存。

list.js:

window.onload = function(){
    // 通过点击换肤按钮后实现换肤功能——js中存储cookie
    // 注意button中使用onclick方法无法调用到此处方法,所以必须获取到button元素,再添加onclick方法
    let changeSkin = document.querySelector(".changeSkin");
    let colorArr = ["white","rgb(204,232,207)", "rgb(200,200,169)", "rgb(114,111,128)"];  
    let body = document.querySelector("body");
    let index = 0;//点击换肤按钮标志

    // 每次刷新时,判断是否存在cookie设置的index,存在则显示已有的。 document.cookie没有时为空字符串为false
    // 如果isColor没有设置,getCookie("isColor")为undefined,也为false
    // if(document.cookie){
    if(getCookie("isColor")){
        // 直接获取cookie信息
        // index = parseInt(document.cookie.split("=")[1]);
        // 通过封装方法获取cookie信息
        index = parseInt(getCookie("isColor"));

        body.style.backgroundColor = colorArr[index];
    }
    changeSkin.addEventListener("click",function(){
        // 每点击一次换肤按钮,index++
        index++;
        // 当index值>colorArr数组长度时,重新回到第一个颜色
        index = index >= colorArr.length? 0 : index;
        // 设置背景颜色
        body.style.backgroundColor = colorArr[index];

        // 将背景颜色(index值)保存到cookie中
        // document.cookie = "bgColor="+index+"; Max-Age="+(3600*24*7);
        // 通过自定义封装方法设置cookie
        setCookie("bgColor",index,{
            "Max-Age":3600*24*7,path:"/",domin:"localhost:8989"
        });
    });
}

// 如果设置多个key=value获取时会相当麻烦,所以将设置和获取cookie封装成函数
//设置cookie
// name,value为自己需要设置的cookie标志,options为对cookie设置的过期时间等配置
function setCookie(name,value,options={}){
    console.log(name,value,options);
    let cookieData = `${name}=${value}; `;
    
    //循环将options对象中的数据,组装成key=value形式
    for(let key in options){
        let str = `${key}=${options[key]}; `;
        // 将原有的name=value和str值进行连接
        cookieData += str;
    }
    document.cookie = cookieData;//bgColor=1; Max-Age=604800; path=/; domin=localhost:8989; 
}
   
//获取cookie
// setCookie()不管设置了多少options配置,获取到的值都只有name=value,没有options,所以只需要处理name=value即可
function getCookie(name){
    return document.cookie.split("=")[1];
}

list.pug:

doctype html
html(lang='en')
  head
    meta(charset='UTF-8')
    meta(name='viewport', content='width=device-width, initial-scale=1.0')
    meta(http-equiv='X-UA-Compatible', content='ie=edge')
    link(rel="stylesheet", href="css/list.css")
    script(src="js/list.js")
    title 音乐列表
  body
    //- 点击换肤按钮,实现更换list页面背景色
    button.changeSkin 换肤
    .singerContainer
      img.headImg(src='img/singer.jpg')
      .singerRight
        span.singerName 薛之谦
        span.singerDetail 外文名:Joker 国籍:中国 出生地:上海 职业:歌手、演员、主持人
        span
          span.titleKey 单曲
          span.titleValue 286
          span.titleKey 专辑
          span.titleValue 15
          span.titleKey MV
          span.titleValue 262
        .mytbn
          span.hotSong 播放热门歌曲
          span.attention 关注1316.4万
    .songList
      .hotSongList
        h2 热门歌曲
      ul.listContainer.grayWord
        li.grayWord
        li 歌曲
        li 专辑
        li 时长
    each item,key in musicData
      //- 通过key的奇偶性控制隔行显示灰色背景色grayBg,注意多个class且有判断时的写法
      ul(class="listContainer"+(key%2===0?" grayBg":""))
        li.grayWord #{key+1}
        li #{item.songName}
        li #{item.album}
        li.grayWord #{item.time}
        span.btnController
          a(href='#')
            img(src='img/play.png')
          img(src='img/add.png')

6.本地缓存Storage——localStorage及sessionStorage使用

cookies每次发送请求都会在头部信息中带上cookies信息到服务端,但是换肤功能这种信息没必要带到服务端,服务端也用不到这些信息。针对这种情况就可以使用本地缓存Storage。

localStorage及sessionStorage的API和使用都是一致的。localStorage及sessionStorage没有过期时间(调用localStorage删除即销毁,sessionStorage浏览器关闭或调用删除即销毁)。

6.1设置sessionStorage/localStorage.setItem(key, value)

sessionStorage/localStorage.setItem(key, value) 添加或更新(如果数据项中已存在该key)数据项中指定key的value

6.2获取sessionStorage/localStorage.getItem(key)

sessionStorage/localStorage.getItem(key) 获取数据项中指定key对应的value

6.3移出指定数据sessionStorage/localStorage.removeItem(key)

sessionStorage/localStorage.removeItem(key) 删除数据项中指定key的value

6.4清空所有数据sessionStorage/localStorage.clear()

sessionStorage/localStorage.clear() 清空所有数据项

    // localStorage本地缓存测试
    // 设置localStorage缓存
    localStorage.setItem("test1","testValue1");
    localStorage.setItem("test2","testValue2");
    // 获取localStorage缓存
    console.log(localStorage.getItem("test1"));//testValue1
    // 删除某个localStorage缓存
    localStorage.removeItem("test1");
    console.log(localStorage.getItem("test1"));//null
    console.log(localStorage.getItem("test2"));//testValue2
    // 删除所有localStorage缓存
    localStorage.clear();
    console.log(localStorage.getItem("test1"));//null
    console.log(localStorage.getItem("test2"));//null

    // sessionStorage本地缓存测试
    // 设置sessionStorage缓存
    sessionStorage.setItem("session1","sessionValue1");
    sessionStorage.setItem("session2","sessionValue2");
    //获取sessionStorage缓存
    console.log(sessionStorage.getItem("session1"));//sessionValue1
    console.log(sessionStorage.getItem("session2"));//sessionValue2
    //删除某个sessionStorage缓存
    sessionStorage.removeItem("session1");
    console.log(sessionStorage.getItem("session1"));//null
    console.log(sessionStorage.getItem("session2"));//sessionValue2
    // 删除所有sessionStorage缓存
    sessionStorage.clear();
    console.log(sessionStorage.getItem("session1"));//null
    console.log(sessionStorage.getItem("session2"));//null

6.5案例——通过storage来改造换肤功能

设置和获取时都是用sessionStorage/localStorage即可

list.js:

window.onload = function(){   
    // 通过点击换肤按钮后实现换肤功能
    // 注意button中使用onclick方法无法调用到此处方法,所以必须获取到button元素,再添加onclick方法
    let changeSkin = document.querySelector(".changeSkin");
    let colorArr = ["white","rgb(204,232,207)", "rgb(200,200,169)", "rgb(114,111,128)"];  
    let body = document.querySelector("body");
    let index = 0;//点击换肤按钮标志

    // 通过localStorage实现换肤功能
    let indexVal = localStorage.getItem("bgColor");
    if(indexVal){
        index = parseInt(indexVal);
        body.style.backgroundColor = colorArr[index];
    }
    
    changeSkin.addEventListener("click",function(){
        // 每点击一次换肤按钮,index++
        index++;
        // 当index值>colorArr数组长度时,重新回到第一个颜色
        index = index >= colorArr.length? 0 : index;
        // 设置背景颜色
        body.style.backgroundColor = colorArr[index];

        // 通过localStorage实现换肤功能——设置
        localStorage.setItem("bgColor",index);
    });
}

6.6案例——通过storage实现添加歌曲列表功能

通过storage实现添加歌曲列表功能:

  • 通过stroage来处理本地多开音乐页面得问题;
  • 实现删除及删除所有列表的功能;

步骤分析:

  1. 隔行显示不同颜色,在ul行内添加样式,通过key的奇偶性显示不同颜色
  2. 控制添加按钮显示或隐藏(鼠标移入时显示,移出隐藏);
  3. 点击添加时显示详细页,利用localStorage解决多次开启页面问题(每次点击添加将数据进行存储,并且重新开启页面window.open(“/detail”))。detail.js将是否打开状态存到localStorage中,localStorage.setItem("isOpen",true),当页面关闭时清除开启状态,监听beforeunload事件,当关闭之前先清除开启状态。localStorage缓存多个页面可以共享,所以在list中判断如果已经开启就不打开新页面。
  4. 将添加的歌曲进行储存:list页面中将val对象通过JSON.stringify(val)传递给detail页面(带参方式)。list页面点击添加时将对象存到localStorage中,在detail页面判断如果localStorage中没有这个对象,就进行存储,有就进行去重(要转化数据为json数据JSON.parse())。注意存储到localStorage中的数据是对象数组。
  5. detail页面将localStorage中的缓存的数据进行显示。根据数据组装视图,并将detail中原有的视图.exchange替换即可。pug中.exchange是div的简写方式。
  6. 点击后自动监控localStorage中的storage事件如果有变化自动更新视图
  7. 删除和清空列表(删除和清空的是localStorage中的数据):注意此处清空所有也不能使用clear,因为除了对象数据,还有很多其他数据;删除部分,input框的索引和localStorage中对象索引一致,对应删除即可,再将删除后数据写入到localStorage

list.pug:注意通过onclick方法将参数传递给detail.pug页面

doctype html
html(lang='en')
  head
    meta(charset='UTF-8')
    meta(name='viewport', content='width=device-width, initial-scale=1.0')
    meta(http-equiv='X-UA-Compatible', content='ie=edge')
    link(rel="stylesheet", href="css/list.css")
    script(src="js/list.js")
    title 音乐列表
  body
    //- 点击换肤按钮,实现更换list页面背景色
    button.changeSkin 换肤
    .singerContainer
      img.headImg(src='img/singer.jpg')
      .singerRight
        span.singerName 薛之谦
        span.singerDetail 外文名:Joker 国籍:中国 出生地:上海 职业:歌手、演员、主持人
        span
          span.titleKey 单曲
          span.titleValue 286
          span.titleKey 专辑
          span.titleValue 15
          span.titleKey MV
          span.titleValue 262
        .mytbn
          span.hotSong 播放热门歌曲
          span.attention 关注1316.4万
    .songList
      .hotSongList
        h2 热门歌曲
      ul.listContainer.grayWord
        li.grayWord
        li 歌曲
        li 专辑
        li 时长
    each item,key in musicData
      //- 通过key的奇偶性控制隔行显示灰色背景色grayBg,注意多个class且有判断时的写法
      ul(class="listContainer"+(key%2===0?" grayBg":""))
        li.grayWord #{key+1}
        li #{item.songName}
        li #{item.album}
        li.grayWord #{item.time}
        //- 控制listContainer移入时btnController显示,移出时隐藏
        span.btnController
          a(href='#')
            img(src='img/play.png')
          //- 点击add按钮开启详细页(注意pug中都使用双引号隔开)
          img(src='img/add.png' class="addBtn" onclick="showDetail("+ JSON.stringify(item) +")")

list.js:注意showDetail()方法中参数有list.pug中onclick()传递的参数

window.onload = function () {
    // 通过点击换肤按钮后实现换肤功能
    // 注意button中使用onclick方法无法调用到此处方法,所以必须获取到button元素,再添加onclick方法
    let changeSkin = document.querySelector(".changeSkin");
    let colorArr = ["white", "rgb(204,232,207)", "rgb(200,200,169)", "rgb(114,111,128)"];
    let body = document.querySelector("body");
    let index = 0;//点击换肤按钮标志

    // 通过localStorage实现换肤功能
    let indexVal = localStorage.getItem("bgColor");
    if (indexVal) {
        index = parseInt(indexVal);
        body.style.backgroundColor = colorArr[index];
    }

    changeSkin.addEventListener("click", function () {
        // 每点击一次换肤按钮,index++
        index++;
        // 当index值>colorArr数组长度时,重新回到第一个颜色
        index = index >= colorArr.length ? 0 : index;
        // 设置背景颜色
        body.style.backgroundColor = colorArr[index];

        // 通过localStorage实现换肤功能——设置
        localStorage.setItem("bgColor", index);
    });

    //控制listContainer移入时btnController显示,移出时隐藏
    let uls = body.querySelectorAll(".listContainer");
    uls.forEach((ul, index) => {
        let btn = ul.querySelector(".btnController");
        if (btn) {
            ul.addEventListener("mouseover", function () {
                btn.style.display = "block";
            });
            ul.addEventListener("mouseout", function () {
                btn.style.display = "none";
            });
        }
    })

}

//显示详细页
function showDetail(musicData) {
    // 页面带过来的数据才会有id
    // 如果localStorage中已经存在musicData,就获取原有数据并push进新的数据
    if (localStorage.getItem("musicData")) {
        let localData = JSON.parse(localStorage.getItem("musicData"));
        // 判断原有数据和要添加的数据一样,就不添加
        localData.forEach(item=>{
            // 没找到id相同的数据时添加(注意:等于的数据只有一条,不等的有多天,所以过滤==时效率更高)
            if(!localData.find(item=>item.id==musicData.id)){
                localData.push(musicData);
                localStorage.setItem("musicData", JSON.stringify(localData));
            }
        });
    }else{
        localStorage.setItem("musicData", JSON.stringify([musicData]));//注意localStorage中的数据是数组
    }

    // 每次点击时先判断窗口是否已经打开(如果detail关闭会removeItem(),结果为null)
    if (!localStorage.getItem("isOpen")) {
        // 在页面打开前将数据存储到localStorage中,detail页面再获取一次(数据为拼接后的html)
        // window.open是以get方式发起请求
        window.open("/detail");
    }
}

detail.pug:注意重写myUl在.exchange的div下 

doctype html
html(lang='en')
  head
    meta(charset='UTF-8')
    meta(name='viewport', content='width=device-width, initial-scale=1.0')
    meta(http-equiv='X-UA-Compatible', content='ie=edge')
    link(rel='stylesheet', type='text/css', href='css/detail.css')
    script(src="js/detail.js" type="text/javascript")
    title 详情页
  body
    .headContainer
      a.btnStyle.deleteItem 删除
      a.btnStyle.deleteAll 清空列表
    .listContainer
      ul.myul
        input(type='checkbox')
        li 歌曲
        li 专辑
        li 时长
      .exchange

 detail.js:注意从list页面中添加歌曲需要监听storage事件自动更新视图;每次删除和清空后需要调用渲染视图的方法。

//自动监控storage,如果数据有更新,自动更新视图
window.addEventListener("storage",function(){
  renderPug();
})

window.onload = function () {
  /*
  将是否打开状态存到localStorage中,localStorage.setItem("isOpen",true),
  当页面关闭时清除开启状态,监听beforeunload事件,当关闭之前先清除开启状态。
  localStorage缓存多个页面可以共享,所以在list中判断如果已经开启就不打开新页面。
  */

  // 因为点击添加按钮,都会打开新窗口,所以需要将是否打开状态存到localStorage中,每次点击时先获取是否打开已经打开就不再打开新窗口
  localStorage.setItem("isOpen", true);
  // 打开窗口同时,将点击的歌曲数据存到localStorage中,detail页面可以进行共享
  if (localStorage.getItem("musicData")) {
    renderPug();
  }


  // 删除和清空列表
  let headContainer = document.querySelector(".headContainer");
  let deleteItemBtn = headContainer.querySelector(".deleteItem");
  let deleteAllBtn = headContainer.querySelector(".deleteAll");
  // console.log(deleteItem.checked);
  deleteItemBtn.addEventListener("click",deleteItem);
  deleteAllBtn.addEventListener("click",deleteAll);
  
}
// 删除歌曲
function deleteItem(){
  let musicData = JSON.parse(localStorage.getItem("musicData"));
  // 找到所有checkbox为checked的项,然后removeItem
  let myUls = document.querySelectorAll(".exchange ul");
  myUls.forEach((ul,index)=>{
    let input = ul.querySelector("input");
    // 删除musicData中对应checked项
    if(input.checked){
       // ul和input的索引值 === musicData数据的索引值(删除时可利用)
      let deleteData = musicData.filter((item,key)=>index!==key);
      localStorage.setItem("musicData",JSON.stringify(deleteData));
      // 注意更改localStorage后,要重新渲染视图
      renderPug();
    }
  });
}
// 清空所有歌曲列表(即清空所有musicData数据,注意localStorage中还有其他数据,所以不能使用clear())
function deleteAll(){
  localStorage.removeItem("musicData");
  renderPug();
}

//渲染详细页歌曲列表
function renderPug(){
  let musicData = JSON.parse(localStorage.getItem("musicData"));
  // 注意html render的位置在.exchange的div中
  let exchange = document.querySelector(".exchange");
  let innerHTML = '';
  musicData && musicData.forEach(music => {
    let content = `
      <ul class="myul">
        <input type='checkbox'/>
        <li>${music.songName}</li>
        <li>${music.album}</li>
        <li>${music.time}</li>
      </ul>
    `;
    innerHTML += content;
  });
  exchange.innerHTML = innerHTML;
}

//    需要在detail页面关闭时需要监听beforeunload关闭事件(注意不是beforeonload),清除isOpen状态,list页面才知道detail页面是否已经打开
window.addEventListener("beforeunload", function () {
  localStorage.removeItem("isOpen");
});

7.本地存储异同

7.1共同点

7.1.1localStorage和sessionStorage和cookie共同点

  • 同域(同源策略:基于本域名下)限制:同源策略:请求与响应的 协议、域名、端口都相同 则时同源,否则为 跨源/跨域
  • 存储的内容都会转为字符串格式:cookie相对小一些,localStorage和sessionStorage相对大一些
  • 都有存储大小限制

7.1.2localStorage和sessionStorage共同点

  • API相同
  • 存储大小限制一样基本类似
  • 无个数限制

7.2不同点

7.2.1localStorage

  • 没有有效期,除非删除,否则一直存在
  • 同域下页面共享(可以跨页面访问,不可以跨浏览器访问)
  • 支持 storage 事件

7.2.2sessionStorage

  • 浏览器关闭,自动销毁
  • 页面私有(不可以跨页面访问,更不可以跨浏览器访问)
  • 不支持 storage 事件

7.2.3cookie

  • 浏览器也会在每次请求的时候主动组织所有域下的cookie到请求头 cookie 中,发送给服务器端
  • 浏览器会主动存储接收到的 set-cookie 头信息的值
  • 安全性:可以设置 http-only 属性为 true 来禁止客户端代码(js)修改该值
  • 可以设置有效期 (默认浏览器关闭自动销毁)(不同浏览器有所不同)
  • 同域下个数有限制,最好不要超过50个(不同浏览器有所不同)
  • 单个cookie内容大小有限制,最好不要超过4000字节(不同浏览器有所不同)

8.项目需求总结及完整代码实现

  1. koa框架搭建,路由登录页面
  2. 登录及错误页面处理
  3. cookie记录登录状态实现自动登录——后台实现cookie信息保存
  4. 动态渲染list页面
  5. list页面(歌曲列表)实现换肤功能——js中实现cookie信息保存,js中cookie实现换肤功能存在的问题
  6. 换肤功能——localStorage实现
  7. list页面(歌曲列表)显示隐藏添加按钮
  8. list页面(歌曲列表)点击添加时通过localStorage操作是否显示detail(详情页)
  9. list页面(歌曲列表)点击添加后将歌曲信息存储到localStorage
  10. detail(详情页)获取到localStorage数据,并显示
  11. 通过localStorage实现detail(详情页)删除和清空歌曲列表

项目完整代码:

8.1koa框架搭建

index.js:

const Koa = require("koa");
const Router = require("koa-router");
const views = require("koa-views");
const static = require("koa-static");
// form表单提交是post请求,必须使用koa-bodyparser获取数据
const bodyparser = require("koa-bodyparser");
// md5加密登录信息
const md5 = require("md5");

// node.js通过require后,会自动将json文件中的数据转换为对象
const musicData = require("./data/music.json");

const app = new Koa();
const route = new Router();

// 注意引入的模块都需要通过app.use()将模块和router进行关联
app.use(bodyparser());

// 注意模板引擎是在views中设置
app.use(views(__dirname+"/views"),{
    map:{
        html:"pug"
    }
});
app.use(static(__dirname+"/static"));

route.get("/login",async ctx=>{
    // 每次链接到登录页面时,先检验是否有cookie登录状态,有则直接跳转至list页面
    let loginState = md5("zs"+"8888");
    if(ctx.cookies.get("isLogin") === loginState){
        ctx.redirect("/list");
    }else{
        await ctx.render("login.pug");
    }
});

// 登录页面点击登录后,校验cookie信息,成功自动登录,否则重新登录
route.post("/checkUser",async ctx=>{
    // 模拟数据库写死数据,当用户名为zs,密码为8888时登录成功,直接跳转到list页面
    if(ctx.request.body.username === "zs" && ctx.request.body.pwd === "8888"){
        // 登录信息校验成功后,判断如果点击了memberMe则将登录信息存入到cookie中
        if(ctx.request.body.memberMe === "on"){
            // 通过md5对用户名密码进行加密存到cookie中
            let loginState = md5(ctx.request.body.username+ctx.request.body.pwd);
            ctx.cookies.set("isLogin",loginState,{maxAge:36000*1000*24*7});
        }
        ctx.redirect("/list");

    // 登录失败跳转到错误页面
    }else{
        ctx.redirect("/error");
    }
});

// list歌曲列表页面
route.get("/list",async ctx=>{
    // 渲染list页面,并且推送歌曲数据
    await ctx.render("list.pug",{
        musicData
    });
});

// 详细页
route.get("/detail",async ctx=>{
    await ctx.render("detail.pug");
});

// error错误页面
route.get("/error",async ctx=>{
    await ctx.render("error.pug");
});

app.use(route.routes());
app.listen("8989");

./data/music.json:

[
    {
        "id":1,
        "songName": "演员",
        "album": "绅士",
        "singer": "薛之谦",
        "time": "04:21"
    },
    {
        "id":2,
        "songName": "天分",
        "album": "怪咖",
        "singer": "薛之谦",
        "time": "04:01"
    },
    {
        "id":3,
        "songName": "刚刚好",
        "album": "初学者",
        "singer": "薛之谦",
        "time": "04:10"
    },
    {
        "id":4,
        "songName": "意外",
        "album": "意外",
        "singer": "薛之谦",
        "time": "04:47"
    },
    {
        "id":5,
        "songName": "丑八怪",
        "album": "意外",
        "singer": "薛之谦",
        "time": "04:08"
    }
]

8.2登录

login.pug:

doctype html
html(lang='en')
  head
    meta(charset='UTF-8')
    meta(name='viewport', content='width=device-width, initial-scale=1.0')
    meta(http-equiv='X-UA-Compatible', content='ie=edge')
    link(rel='stylesheet', href='css/login.css')
    title Document
  body
    .loginContainer
      h1 登录
      form(action='/checkUser', method='post')
        | 姓名:
        input.inputStyle(type='text', name='username')
        br
        | 密码:
        input.inputStyle(type='password', name='pwd')
        br
        input.loginStyle(type='submit', value='登录')
        |  |
        input(type='checkbox', name='memberMe')
        | 记住我

error.pug:

<!DOCTYPE html>
html(lang="en")
    head
        meta(charset="UTF-8")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        meta(http-equiv="X-UA-Compatible", content="ie=edge")
        title 错误页面
    body
        h1 登录错误
        span.val 5
        span 秒之后自动跳转。。。。。。
        br
        a(href="/login") 点击直接跳转
    //- js定时器控制倒计时5秒后自动跳转
    script.
        let time = 5;
        let timer = setInterval(function(){
            time--;
            //- 如果time倒计时到0自动跳转
            document.querySelector(".val").innerHTML = time;
            if(time<1){
                window.location.href = "/login";
                clearInterval(timer);
            }
        },1000);

8.3歌曲列表

list.pug:

doctype html
html(lang='en')
  head
    meta(charset='UTF-8')
    meta(name='viewport', content='width=device-width, initial-scale=1.0')
    meta(http-equiv='X-UA-Compatible', content='ie=edge')
    link(rel="stylesheet", href="css/list.css")
    script(src="js/list.js")
    title 音乐列表
  body
    //- 点击换肤按钮,实现更换list页面背景色
    button.changeSkin 换肤
    .singerContainer
      img.headImg(src='img/singer.jpg')
      .singerRight
        span.singerName 薛之谦
        span.singerDetail 外文名:Joker 国籍:中国 出生地:上海 职业:歌手、演员、主持人
        span
          span.titleKey 单曲
          span.titleValue 286
          span.titleKey 专辑
          span.titleValue 15
          span.titleKey MV
          span.titleValue 262
        .mytbn
          span.hotSong 播放热门歌曲
          span.attention 关注1316.4万
    .songList
      .hotSongList
        h2 热门歌曲
      ul.listContainer.grayWord
        li.grayWord
        li 歌曲
        li 专辑
        li 时长
    each item,key in musicData
      //- 通过key的奇偶性控制隔行显示灰色背景色grayBg,注意多个class且有判断时的写法
      ul(class="listContainer"+(key%2===0?" grayBg":""))
        li.grayWord #{key+1}
        li #{item.songName}
        li #{item.album}
        li.grayWord #{item.time}
        //- 控制listContainer移入时btnController显示,移出时隐藏
        span.btnController
          a(href='#')
            img(src='img/play.png')
          //- 点击add按钮开启详细页(注意pug中都使用双引号隔开)
          img(src='img/add.png' class="addBtn" onclick="showDetail("+ JSON.stringify(item) +")")

list.js:

window.onload = function () {
    // // localStorage本地缓存测试
    // // 设置localStorage缓存
    // localStorage.setItem("test1","testValue1");
    // localStorage.setItem("test2","testValue2");
    // // 获取localStorage缓存
    // console.log(localStorage.getItem("test1"));//testValue1
    // // 删除某个localStorage缓存
    // localStorage.removeItem("test1");
    // console.log(localStorage.getItem("test1"));//null
    // console.log(localStorage.getItem("test2"));//testValue2
    // // 删除所有localStorage缓存
    // localStorage.clear();
    // console.log(localStorage.getItem("test1"));//null
    // console.log(localStorage.getItem("test2"));//null

    // // sessionStorage本地缓存测试
    // // 设置sessionStorage缓存
    // sessionStorage.setItem("session1","sessionValue1");
    // sessionStorage.setItem("session2","sessionValue2");
    // //获取sessionStorage缓存
    // console.log(sessionStorage.getItem("session1"));//sessionValue1
    // console.log(sessionStorage.getItem("session2"));//sessionValue2
    // //删除某个sessionStorage缓存
    // sessionStorage.removeItem("session1");
    // console.log(sessionStorage.getItem("session1"));//null
    // console.log(sessionStorage.getItem("session2"));//sessionValue2
    // // 删除所有sessionStorage缓存
    // sessionStorage.clear();
    // console.log(sessionStorage.getItem("session1"));//null
    // console.log(sessionStorage.getItem("session2"));//null


    // 通过点击换肤按钮后实现换肤功能
    // 注意button中使用onclick方法无法调用到此处方法,所以必须获取到button元素,再添加onclick方法
    let changeSkin = document.querySelector(".changeSkin");
    let colorArr = ["white", "rgb(204,232,207)", "rgb(200,200,169)", "rgb(114,111,128)"];
    let body = document.querySelector("body");
    let index = 0;//点击换肤按钮标志

    // 每次刷新时,判断是否存在cookie设置的index,存在则显示已有的。 document.cookie没有时为空字符串为false
    // 如果isColor没有设置,getCookie("isColor")为undefined,也为false
    // // if(document.cookie){
    // if(getCookie("isColor")){
    //     // 直接获取cookie信息
    //     // index = parseInt(document.cookie.split("=")[1]);
    //     // 通过封装方法获取cookie信息
    //     index = parseInt(getCookie("isColor"));
    //     body.style.backgroundColor = colorArr[index];
    // }

    // 通过localStorage实现换肤功能
    let indexVal = localStorage.getItem("bgColor");
    if (indexVal) {
        index = parseInt(indexVal);
        body.style.backgroundColor = colorArr[index];
    }

    changeSkin.addEventListener("click", function () {
        // 每点击一次换肤按钮,index++
        index++;
        // 当index值>colorArr数组长度时,重新回到第一个颜色
        index = index >= colorArr.length ? 0 : index;
        // 设置背景颜色
        body.style.backgroundColor = colorArr[index];
        // js中存储cookie
        // 将背景颜色(index值)保存到cookie中
        // document.cookie = "bgColor="+index+"; Max-Age="+(3600*24*7);
        // 通过自定义封装方法设置cookie
        // setCookie("bgColor",index,{
        //     "Max-Age":3600*24*7,path:"/",domin:"localhost:8989"
        // });

        // 通过localStorage实现换肤功能——设置
        localStorage.setItem("bgColor", index);
    });

    //控制listContainer移入时btnController显示,移出时隐藏
    let uls = body.querySelectorAll(".listContainer");
    uls.forEach((ul, index) => {
        let btn = ul.querySelector(".btnController");
        if (btn) {
            ul.addEventListener("mouseover", function () {
                btn.style.display = "block";
            });
            ul.addEventListener("mouseout", function () {
                btn.style.display = "none";
            });
        }
    })

}

//显示详细页
function showDetail(musicData) {
    // 页面带过来的数据才会有id
    // 如果localStorage中已经存在musicData,就获取原有数据并push进新的数据
    if (localStorage.getItem("musicData")) {
        let localData = JSON.parse(localStorage.getItem("musicData"));
        // 判断原有数据和要添加的数据一样,就不添加
        localData.forEach(item=>{
            // 没找到id相同的数据时添加(注意:等于的数据只有一条,不等的有多天,所以过滤==时效率更高)
            if(!localData.find(item=>item.id==musicData.id)){
                localData.push(musicData);
                localStorage.setItem("musicData", JSON.stringify(localData));
            }
        });
    }else{
        localStorage.setItem("musicData", JSON.stringify([musicData]));//注意localStorage中的数据是数组
    }

    // 每次点击时先判断窗口是否已经打开(如果detail关闭会removeItem(),结果为null)
    if (!localStorage.getItem("isOpen")) {
        // 在页面打开前将数据存储到localStorage中,detail页面再获取一次(数据为拼接后的html)
        // window.open是以get方式发起请求
        window.open("/detail");
    }
}

// 如果设置多个key=value获取时会相当麻烦,所以将设置和获取cookie封装成函数
//设置cookie
// name,value为自己需要设置的cookie标志,options为对cookie设置的过期时间等配置
function setCookie(name, value, options = {}) {
    console.log(name, value, options);
    let cookieData = `${name}=${value}; `;

    //循环将options对象中的数据,组装成key=value形式
    for (let key in options) {
        let str = `${key}=${options[key]}; `;
        // 将原有的name=value和str值进行连接
        cookieData += str;
    }
    document.cookie = cookieData;//bgColor=1; Max-Age=604800; path=/; domin=localhost:8989; 
}

//获取cookie
// setCookie()不管设置了多少options配置,获取到的值都只有name=value,没有options,所以只需要处理name=value即可
function getCookie(name) {
    return document.cookie.split("=")[1];
}

8.4详细页

detail.pug:

doctype html
html(lang='en')
  head
    meta(charset='UTF-8')
    meta(name='viewport', content='width=device-width, initial-scale=1.0')
    meta(http-equiv='X-UA-Compatible', content='ie=edge')
    link(rel='stylesheet', type='text/css', href='css/detail.css')
    script(src="js/detail.js" type="text/javascript")
    title 详情页
  body
    .headContainer
      a.btnStyle.deleteItem 删除
      a.btnStyle.deleteAll 清空列表
    .listContainer
      ul.myul
        input(type='checkbox')
        li 歌曲
        li 专辑
        li 时长
      .exchange

detail.js:

//自动监控storage,如果数据有更新,自动更新视图
window.addEventListener("storage",function(){
  renderPug();
})

window.onload = function () {
  /*
  将是否打开状态存到localStorage中,localStorage.setItem("isOpen",true),
  当页面关闭时清除开启状态,监听beforeunload事件,当关闭之前先清除开启状态。
  localStorage缓存多个页面可以共享,所以在list中判断如果已经开启就不打开新页面。
  */

  // 因为点击添加按钮,都会打开新窗口,所以需要将是否打开状态存到localStorage中,每次点击时先获取是否打开已经打开就不再打开新窗口
  localStorage.setItem("isOpen", true);
  // 打开窗口同时,将点击的歌曲数据存到localStorage中,detail页面可以进行共享
  if (localStorage.getItem("musicData")) {
    renderPug();
  }


  // 删除和清空列表
  let headContainer = document.querySelector(".headContainer");
  let deleteItemBtn = headContainer.querySelector(".deleteItem");
  let deleteAllBtn = headContainer.querySelector(".deleteAll");
  // console.log(deleteItem.checked);
  deleteItemBtn.addEventListener("click",deleteItem);
  deleteAllBtn.addEventListener("click",deleteAll);
  
}
// 删除歌曲
function deleteItem(){
  let musicData = JSON.parse(localStorage.getItem("musicData"));
  // 找到所有checkbox为checked的项,然后removeItem
  let myUls = document.querySelectorAll(".exchange ul");
  myUls.forEach((ul,index)=>{
    let input = ul.querySelector("input");
    // 删除musicData中对应checked项
    if(input.checked){
       // ul和input的索引值 === musicData数据的索引值(删除时可利用)
      let deleteData = musicData.filter((item,key)=>index!==key);
      localStorage.setItem("musicData",JSON.stringify(deleteData));
      // 注意更改localStorage后,要重新渲染视图
      renderPug();
    }
  });
}
// 清空所有歌曲列表(即清空所有musicData数据,注意localStorage中还有其他数据,所以不能使用clear())
function deleteAll(){
  localStorage.removeItem("musicData");
  renderPug();
}

//渲染详细页歌曲列表
function renderPug(){
  let musicData = JSON.parse(localStorage.getItem("musicData"));
  // 注意html render的位置在.exchange的div中
  let exchange = document.querySelector(".exchange");
  let innerHTML = '';
  musicData && musicData.forEach(music => {
    let content = `
      <ul class="myul">
        <input type='checkbox'/>
        <li>${music.songName}</li>
        <li>${music.album}</li>
        <li>${music.time}</li>
      </ul>
    `;
    innerHTML += content;
  });
  exchange.innerHTML = innerHTML;
}

//    需要在detail页面关闭时需要监听beforeunload关闭事件(注意不是beforeonload),清除isOpen状态,list页面才知道detail页面是否已经打开
window.addEventListener("beforeunload", function () {
  localStorage.removeItem("isOpen");
});

8.5css文件

detail.css:

body{
    background: rgb(41, 42, 43);
}
li{
    list-style: none;
}
.btnStyle{
    display: block;
    float: left;
    width: 120px;
    height: 38px;
    border: 1px solid #c9c9c9;
    text-align: center;
    line-height: 38px;
    color:  #c9c9c9;
    margin-left: 10px;
    cursor: pointer;
}
.headContainer{
    margin-top: 10%;
    width: 80%;
    height: 50px;
    margin-left: 10%;
    margin-right: 10%;
    color: white;
}

.listContainer{
    margin-left: 10%;
    margin-right: 10%;
}
.myul{
    display: flex;
    color: #c9c9c9;
}
.myul li {
    flex:1;
}
.myul input{
    margin-right: 20px;
}
a.btnStyle:hover{
    color: white;
    border: 1px solid white;
}


login.css:

.loginContainer{
    margin: 0 auto;
    width: 600px;
    text-align: center;
    padding-top: 20px;
    padding-bottom: 50px;
    border: 1px solid;
}
.loginContainer input{
    margin-bottom: 20px;
}
.loginStyle{
    width: 180px;
    height: 40px;
    background: rgb(50,203,77);
    color: white;
    font-size: 17px;
}
.inputStyle{
    width: 200px;
    height: 30px;
    padding: 5px;
    outline: none;
}

.inputStyle:focus{
    border: 1px solid rgb(50,203,77);
}

list.css:

li{
    list-style: none;
}
.singerContainer{
    height: 250px;
    margin-top: 40px;
    margin-bottom: 35px;
    width: 100%;
    display: flex;
    flex-direction: row;
}
.headImg{
    border-radius: 50%;
    width: 250px;
    margin-right: 5%;
    margin-left: 5%;
}
.singerRight{
    width: 60%;
    display: flex;
    flex-direction: column;
}
.singerName{
    font-size: 38px;
    font-weight: 400;
}
.singerContainer span{
    margin-bottom: 10px;
}
.titleKey{
    color: #000000;
    font-size: 18px;
    margin-right: 10px;

}
.titleValue{
    font-size: 24px; 
    margin-right: 15px;
}
.hotSong{
    width:178px;
    height: 38px;
    background: #31c27c;
    border: 1px solid #31c27c;
    text-align: center;
    color: white;
    line-height: 38px;
    display: block;
    float: left;
}
.attention{
    display: block;
    height: 38px;
    line-height: 38px;
    width: 180px;
    float: left;
    border: 1px solid #c9c9c9;
    text-align: center;
    margin-left: 10px;
}
.mytbn{
    margin-top: 20px;
}
.songList{
    margin-left: 5%;
    margin-right: 5%;
}
.listContainer{
    display: flex;
    flex-direction: row;
}
.grayWord{
    color: #999;
}
.listContainer li{
    flex: 5;
    height: 50px;
    line-height: 50px;
    position: relative;
}
.listContainer li:nth-child(1){
    flex: 1;
    height: 50px;
    line-height: 50px;
}
.listContainer li:nth-child(4){
    flex: 1;
}
.btnController{
    position: absolute;
    left: 30%;
    display: none;
}
.btnController img{
    width: 40px;
    margin-left: 10px;
}
.grayBg{
    background: #fbfbfd;
}
.changeSkin{
    width: 100px;
    height: 30px;
    background: rgb(97,190,130);
    color: white;
    font-size: 14px;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值