目录

  • 一、案例说明
    • 1、目录结构
    • 2、conf文件夹
    • 3、用户名密码的正则和ajax的封装
  • 二、登录页的实现
    • 1、案例效果
    • 2、登录页逻辑
    • 3、接口文档
    • 4、代码实现
    • 5、返回信息显示
  • 三、首页的实现
    • 1、案例效果
    • 2、首页的逻辑
    • 3、接口文档
    • 4、代码实现
    • 5、返回信息显示
  • 四、个人中心
    • 1、案例效果
    • 2、个人页的逻辑
    • 3、接口文档
    • 4、代码实现
    • 5、返回信息显示
  • 五、修改密码
    • 1、案例效果
    • 2、修改密码的逻辑
    • 3、接口文档
    • 4、代码实现
  • 六、注册新用户
    • 1、案例效果
    • 2、注册新用户的逻辑
    • 3、接口文档
    • 4、代码实现
    • 5、服务器数据
  • 七、商品列表
    • 1、案例效果
    • 2、商品列表的逻辑
    • 3、接口文档
    • 4、代码实现
  • 八、商品详情页
    • 1、案例效果
    • 2、接口文档
    • 4、代码实现
  • 九、购物车的操作
    • 1、案例效果
    • 2、代码实现

资料链接和启动方法:https://pan.baidu.com/s/1Ka2xcPmsL31MpQhqNnZlCw 提取码:1115

一、案例说明

1、目录结构

2、conf文件夹

3、用户名密码的正则和ajax的封装

import { confg } from "../cof/config.js";
// 1. 正则封装
function test(reg) {return function (str) {return reg.test(str);};
}const testName = test(confg.nameReg);
const testPwd = test(confg.pwdReg);// 2. 请求封装
function objToStr(obj) {let str = "";for (let k in obj) {str += `${k}=${obj[k]}&`;}str = str.slice(0, str.length - 1);return str;
}
function createAjax(url) {let baseUrl = url;function ajax(options) {if (options.url === undefined) {throw new Error("您没有传递 url, url 为 必传");}if (!(/^(GET|POST)$/i.test(options.method) ||options.method === undefined)) {throw new Error("method 目前仅支持 post 或者 get");}if (!(options.async === undefined || typeof options.async === "boolean")) {throw new Error("async 目前仅支持 ture 或者 false");}const optionsDataType = Object.prototype.toString.call(options.data);if (!(optionsDataType === "[object Object]" ||optionsDataType === "[object String]" ||optionsDataType === "[object Undefined]")) {throw new Error("data 目前仅支持 字符串或者 对象");}const headersType = Object.prototype.toString.call(options.headers);if (!(headersType === "[object Undefined]" ||headersType === "[object Object]")) {throw new Error("header 暂时仅支持 对象格式");}if (!(options.dataType === undefined ||/^(string|json)$/.test(options.dataType))) {throw new Error("dataType 目前仅支持 'string' 或者 'json'");}const _options = {url: baseUrl + options.url,method: options.method || "GET",async: options.async ?? true,data: options.data || "",headers: {"content-type": "application/x-www-form-urlencoded",...options.headers,},dataType: options.dataType || "string",};if (!(typeof _options.data === "string")) {_options.data = objToStr(_options.data);}if (/^GET$/i.test(_options.method)) {_options.url = _options.url + "?" + _options.data;}const p = new Promise(function (res, rej) {const xhr = new XMLHttpRequest();xhr.open(_options.method, _options.url, _options.async);xhr.onload = function () {try {if (_options.dataType === "string") {res({code: 1,info: xhr.responseText,});} else {res({code: 1,info: JSON.parse(xhr.responseText),});}} catch (error) {res({code: 0,info: xhr.responseText,});}};if (/^POST$/i.test(_options.method)) {xhr.setRequestHeader("content-type",_options.headers["content-type"]);}if (_options.headers.authorization) {xhr.setRequestHeader("authorization",_options.headers.authorization);}/^POST$/i.test(_options.method)? xhr.send(_options.data): xhr.send();});return p;}return ajax;
}const ajax = createAjax(confg.baseUrl);export const utils = {testName,testPwd,ajax,
};

二、登录页的实现

1、案例效果

2、登录页逻辑

  1. 采集用户信息     —点击登录时
  2. 验证信息
    • 非空验证
    • 正则校验
  3. 把用户名和密码发送给后端 —> 根据后端返回结果, 做不同的事
    • 跳转首页
    • 提示用账号密码错误

3、接口文档

  • 请求地址/users/login
  • 请求方式post
  • 携带参数application/x-www-form-urlencoded 格式传递
  • 响应数据根据你的用户名和密码返回登录状态

4、代码实现

  • HTML代码
<!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>Document</title><link rel="stylesheet" href="../css/login.css">
</head>
<body><h1>登录页</h1><div class="box"><span>用户名密码错误, 请重试 ! ^_^</span><label>用户名: <input class="name" type="text"></label><label>密码: <input class="pwd" type="text"></label><button>登录</button><a href="./register.html">没有账号, 请进入注册</a></div><script src="../js/login.js" type="module"></script>
</body>
</html>
  • CSS代码
* {margin: 0;padding: 0;
}h1 {width: 100%;height: 80px;display: flex;justify-content: center;align-items: center;background-color: skyblue;
}.box {width: 600px;display: flex;flex-direction: column;padding: 20px;border: 3px solid pink;border-radius: 15px;margin: 30px auto;padding-top: 50px;position: relative;
}.box>label {height: 50px;font-size: 22px;
}.box>label>input {padding-left: 20px;font-size: 22px;
}.box>button {font-size: 22px;
}.box>span {position: absolute;left: 50%;transform: translateX(-50%);top: 10px;color: red;display: none;
}.box>span.active {display: block;
}
  • JS代码
// 导入公共方法
import { utils } from "../utils/utils.js";
const { testName, testPwd, ajax } = utils;// 获取标签对象
const oBtn = document.querySelector("button");
const nameInp = document.querySelector(".name");
const pwdInp = document.querySelector(".pwd");
const errBox = document.querySelector("span");// 给button添加点击事件
oBtn.addEventListener('click', async function(){// 采集用户输入的用户名和密码const nameVal = nameInp.value;const pwdVal = pwdInp.value;// 验证用户信息 --- 非空校验// if(nameVal === '' || pwdVal === '')if(!nameVal || !pwdVal) {return alert("请填写用户名或密码");}// 验证用户信息 --- 正则验证// if (testName(nameVal) === false  || testPwd(pwdVal) === false) {if(!testName(nameVal) || !testPwd(pwdVal)) {return alert("您的用户名密码, 不符合规则, 请重新填写");}// 想后端返发送请求const res = await ajax({method: "POST",url: "/users/login",data: `username=${nameVal}&password=${pwdVal}`,dataType: 'json'});console.log(res);if (res.code === 0) {errBox.classList.add("active");} else {window.localStorage.setItem("token", res.info.token);window.localStorage.setItem("id", res.info.user.id);// 1. 先拿到跳转前存储的路径const page = window.sessionStorage.getItem('page');// 2. 清除存储的路径window.sessionStorage.removeItem('page');window.location.href = page || "./index.html";}
})

5、返回信息显示

  • 登录失败

  • 登录成功

  • token

三、首页的实现

1、案例效果

  • 没有登录前
  • 登录后

2、首页的逻辑

  • 分析原因
  1. http是一个无状态请求,每次请求之间没有任何关联
  2. 刚刚登陆成功, 并立马跳转到首页, 此时发送获取用户详情的请求,在服务端看来, 是两个独立的请求
  3. 所以我们需要一个东西, 来证明我们刚刚登陆成功了
  • 解决方法
  1. 有一个 叫做token的东西, 是服务端给我们的, 注意有过期时间
  2. 当我们请求的时候, 我把用户账号和密码给到服务端, 然后服务端会生成一个token信息
  3. 我们后续发送请求时, 携带上这个token,服务端就能直到我们刚刚登陆成功、
  4. token是后端根据我们信息生成一个只属于我们自己的加密的文本
  • 代码逻辑
  1. 判断token和id都正常存在
  2. 向服务端发请求,根据请求结果, 展示不同的页面

3、接口文档

  • 注意: 登录后方可查看
  • 请求地址/users/info
  • 请求方式get
  • 携带参数支持restful风格 localhost:8888/users/info/:id
  • 响应数据

4、代码实现

  • HTML代码
<!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>Document</title><link rel="stylesheet" href="../css/index.css">
</head>
<body><h1>首页<p class="off"><a href="./login.html">您好, 请登录</a></p><p class="on">您好, <span>用户名</span><a href="./self.html">个人中心</a></p></h1><div style="font-size: 40px;"><a href="./list.html">商品列表</a></div><script src="../js/index.js" type="module"></script>
</body>
</html>
  • CSS代码
* {margin: 0;padding: 0;
}h1 {width: 100%;height: 80px;background-color: skyblue;display: flex;justify-content: center;align-items: center;position: relative;
}h1>p {font-size: 20px;position: absolute;top: 50%;transform: translateY(-50%);right: 50px;display: none;
}h1>p.active {display: block;
}h1>p>span {color: red;
}
  • JS代码
// 导入公共方法
import { utils } from "../utils/utils.js";
const { ajax } = utils;// 获取元素
const offBox = document.querySelector(".off");
const onBox = document.querySelector(".on");test();
async function test() {const token = window.localStorage.getItem("token");const id = window.localStorage.getItem("id");if (!token || !id) {// 展示请登录offBox.classList.add("active");onBox.classList.remove("active");return alert("您的token 或者 id 为空, 请先登录");}// 如果运行这个位置, 证明token和id都存在let res = await ajax({url: "/users/info",data: `id=${id}`,headers: {authorization: token,},dataType: "json",});console.log(res);if (res.code == 1) {if (res.info.code === 1) {offBox.classList.remove("active");onBox.classList.add("active");console.log(res);onBox.firstElementChild.innerHTML = res.info.info.nickname;} else {window.location.href = './login.html'}} else {// 可能是token过期, 或者token是伪造的offBox.classList.add("active");onBox.classList.remove("active");}
}

5、返回信息显示

四、个人中心

1、案例效果

2、个人页的逻辑

  1. 这个页面, 能随便进入吗?

    • 判断当前是否登录 (token)
  2. 请求用户信息渲染页面(users/info)
  3. 修改用户信息后, 点击修改

3、接口文档

  • 页面渲染的接口文档
  • 注意: 登录后方可查看
  • 请求地址/users/info
  • 请求方式get
  • 携带参数支持restful风格 localhost:8888/users/info/:id
  • 响应数据
  • 修改个人信息的接口文档
  • 注意: 登录后方可修改
  • 请求地址/users/update
  • 请求方式post
  • 携带参数
  • 响应数据

4、代码实现

  • HTML代码
<!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>Document</title><link rel="stylesheet" href="../css/self.css">
</head>
<body><h1>个人页<p><a href="./index.html">回到首页</a><a href="./rpwd.html">; 修改密码</a></p></h1><div class="box"><label>用户名: <input class="name" type="text" disabled></label><label>用户年龄: <input class="age" type="text"></label><label>用户昵称: <input class="nickname" type="text"></label><label>用户性别: <select id="sel"><option value="">请选择</option><option value="男">男</option><option value="女">女</option></select></label><button>确认修改</button></div><script src="../js/self.js" type="module"></script>
</body>
</html>
  • CSS代码
* {margin: 0;padding: 0;
}h1 {width: 100%;height: 80px;display: flex;justify-content: space-evenly;align-items: center;background-color: skyblue;
}.box {width: 600px;display: flex;flex-direction: column;padding: 20px;border: 3px solid pink;border-radius: 15px;margin: 30px auto;padding-top: 50px;position: relative;
}.box > label {height: 50px;font-size: 22px;
}.box > label > input {padding-left: 20px;font-size: 22px;
}.box > button {font-size: 22px;
}.box > span {position: absolute;left: 50%;transform: translateX(-50%);top: 10px;color: red;display: none;
}.box > span.active {display: block;
}
.box select {font-size: 20px;padding-left: 15px;
}
  • JS代码
// 导入公共方法
import { utils } from "../utils/utils.js";
const { ajax } = utils;// 获取DOM节点
const nameInp = document.querySelector(".name");
const ageInp = document.querySelector(".age");
const nickInp = document.querySelector(".nickname");
const selBox = document.querySelector("#sel");
const btn = document.querySelector("button");// 获取到 token 与 id
const token = window.localStorage.getItem("token");
const id = window.localStorage.getItem("id");test();
async function test() {// 0. 必须登陆状态, 才能进入页面if (!token || !id) {if (confirm("您当前没有登陆, 点击确定跳转登录页")) {window.sessionStorage.setItem("page", window.location.href);window.location.href = "./login.html";}}// 1. 确保登陆过后, 拿到用户信息并渲染页面let { info } = await ajax({url: "/users/info",data: `id=${id}`,dataType: "json",headers: {authorization: token,},});console.log(info);if (info.code === 1) {// 页面渲染nameInp.value = info.info.username;ageInp.value = info.info.age;nickInp.value = info.info.nickname;selBox.value = info.info.gender;} else if (info.code === 401 || info.code === 0) {window.location.href = "./login.html";}
}// 2. 修改用户信息, 发送请求
btn.onclick = async function () {// 2.1 用户信息收集const age = ageInp.value;const gender = selBox.value;const nickname = nickInp.value;// console.log(age, gender, nickname)if (!age || !gender || !nickname) {alert("请输入年龄昵称,以及性别后再次修改");return;}// 2.2 拿到用户信息 发送请求let { info } = await ajax({url: "/users/update",method: "POST",data: { id, age, gender, nickname },dataType: "json",headers: {authorization: token,},});if (info.code == 1) {alert("用户信息修改成功");}
};

5、返回信息显示

五、修改密码

1、案例效果

2、修改密码的逻辑

  1. 思考:能随便进吗?

    • 必须是登陆状态的 (token)
  2. 前端验证
    • 不能为空
    • 正则验证
    • 验证新密码与重复新密码必须相同
  3. 满足上述三点, 发送请求

3、接口文档

  • 注意: 登录后方可修改
  • 请求地址/users/rpwd
  • 请求方式post
  • 携带参数
  • 响应数据修改密码成功后, 会自动注销当前登录状态, 需要重新登录

4、代码实现

  • HTML
<!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>Document</title><link rel="stylesheet" href="../css/self.css">
</head>
<body><h1>修改密码<p><a href="./index.html">回到首页</a><a href="./rpwd.html">; 修改密码</a></p></h1><div class="box"><label>旧密码: <input class="oldpwd" type="text"></label><label>新密码: <input class="newpwd" type="text"></label><label>重复新密码: <input class="rnewpwd" type="text"></label><button>确认修改</button></div><script src="../js/rpwd.js" type="module"></script>
</body>
</html>
  • CSS代码
* {margin: 0;padding: 0;
}h1 {width: 100%;height: 80px;display: flex;justify-content: space-evenly;align-items: center;background-color: skyblue;
}.box {width: 600px;display: flex;flex-direction: column;padding: 20px;border: 3px solid pink;border-radius: 15px;margin: 30px auto;padding-top: 50px;position: relative;
}.box > label {height: 50px;font-size: 22px;
}.box > label > input {padding-left: 20px;font-size: 22px;
}.box > button {font-size: 22px;
}.box > span {position: absolute;left: 50%;transform: translateX(-50%);top: 10px;color: red;display: none;
}.box > span.active {display: block;
}
.box select {font-size: 20px;padding-left: 15px;
}
  • JS代码
import { utils } from "../utils/utils.js";
const { ajax, testPwd } = utils;// 0. 获取元素
const oBtn = document.querySelector("button");
const oldpwd = document.querySelector(".oldpwd");
const newpwd = document.querySelector(".newpwd");
const rnewpwd = document.querySelector(".rnewpwd");const token = window.localStorage.getItem("token");
const id = window.localStorage.getItem("id");test();
function test() {if (!token || !id) {window.sessionStorage.setItem("page", window.location.href);window.location.href = "./login.html";}
}oBtn.addEventListener('click', async function () {// 收集用户输入的信息const oldPassword = oldpwd.value;const newPassword = newpwd.value;const rNewPassword = rnewpwd.value;// 1. 验证密码不能为空if (!oldPassword || !newPassword || !rNewPassword) {return alert("密码不能为空");}// 2. 正则校验if (!testPwd(oldPassword) || !testPwd(newPassword) || !testPwd(rNewPassword)) {return alert("请正确填写密码");}// 3. 新密码与重复新密码 必须相同if (newPassword !== rNewPassword) {return alert("新密码与重复新密码 必须相同");}let { info } = await ajax({url: "/users/rpwd",method: "POST",data: { id, oldPassword, newPassword, rNewPassword },dataType: 'json',headers: {authorization: token}});if (info.code === 1) {if (confirm('修改密码成功, 已经注销登录状态, 点击确定, 跳转登录页 ^_^')) {// window.sessionStorage.setItem("page", window.location.href);window.location.href = "./login.html";}}console.log(info)
})

六、注册新用户

1、案例效果

2、注册新用户的逻辑

  1. 思考:需要登陆状态吗?

    • 不需要登陆状态
  2. 点击事件内
    • 收集用户信息
    • 非空校验
    • 正则校验:密码与重复密码必须相同
    • 发送注册请求
      成功:提示用户注册成功; 跳转到登录页
      失败:可能就用户名重复, 提示用户重新输入用户名

3、接口文档

  • 请求地址/users/register
  • 请求方式post
  • 携带参数application/x-www-form-urlencoded 格式传递
  • 响应数据

4、代码实现

  • HTML代码
<!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>Document</title><link rel="stylesheet" href="../css/self.css">
</head>
<body><h1>注册页</h1><div class="box"><label>用户名: <input class="username" type="text"></label><label>密码: <input class="pwd" type="text"></label><label>重复密码: <input class="rpwd" type="text"></label><label>用户昵称: <input class="nickname" type="text"></label><label>已有账号, <a href="./login.html">直接登录</a></label><button>注册</button></div><script src="../js/register.js" type="module"></script>
</body>
</html>
  • CSS代码
* {margin: 0;padding: 0;
}h1 {width: 100%;height: 80px;display: flex;justify-content: space-evenly;align-items: center;background-color: skyblue;
}.box {width: 600px;display: flex;flex-direction: column;padding: 20px;border: 3px solid pink;border-radius: 15px;margin: 30px auto;padding-top: 50px;position: relative;
}.box > label {height: 50px;font-size: 22px;
}.box > label > input {padding-left: 20px;font-size: 22px;
}.box > button {font-size: 22px;
}.box > span {position: absolute;left: 50%;transform: translateX(-50%);top: 10px;color: red;display: none;
}.box > span.active {display: block;
}
.box select {font-size: 20px;padding-left: 15px;
}
  • JS代码
// 导入公共方法
import { utils } from "../utils/utils.js";
const { testName, testPwd, ajax } = utils;// 获取 DOM 节点
const btn = document.querySelector("button");
const usernameInp = document.querySelector(".username");
const pwdInp = document.querySelector(".pwd");
const rpwdInp = document.querySelector(".rpwd");
const nicknameInp = document.querySelector(".nickname");btn.onclick = async function () {// 收集用户信息const username = usernameInp.value;const password = pwdInp.value;const rpassword = rpwdInp.value;const nickname = nicknameInp.value;// 非空校验if (!username || !password || !rpassword || !nickname) {alert("请输入用户名、密码、重复密码和昵称");return;}// 3. 正则校验if (!testName(username) || !testPwd(password) || !testPwd(rpassword)) {alert("请按照格式输入用户名或密码");return;}// 密码与重复密码必须相同if (password !== rpassword) {alert("密码与重复密码不相同");return;}let { info } = await ajax({url: "/users/register",method: "POST",data: { username, password, rpassword, nickname },dataType: "json",});if (info.code === 1) {alert("注册成功, 请跳转登录页登录");} else {alert("注册失败, 用户名重复, 请更改用户名重新注册");}
};

5、服务器数据

七、商品列表

1、案例效果

2、商品列表的逻辑

  • 渲染分类
  • 渲染商品列表
  • 切换分类
  • 筛选切换
  • 折扣切换
  • 排序切换
  • 页码切换
  • 每页展示数据切换
  • 搜索内容
  • 切换分类后, 要求页码回归到第一页
  • 点击商品(图片)进入商品详情页(页面自由飞翔, 要求能够拿到对应的商品数据即可, 方式不限)

3、接口文档

获取购物车列表

  • 请求地址/goods/list
  • 请求方式get
  • 携带参数
  • 响应数据

加入购物车的接口文档

  • 请求地址:/cart/add
  • 请求方式:post
  • 携带参数:
  • 响应数据

4、代码实现

  • HTML代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="../css/base.css"><link rel="stylesheet" href="../css/index.css"><link rel="stylesheet" href="../css/list.css">
</head>
<body><h1>商品列表<p class="active"><a href="./cart.html">去到购物车; </a><a href="./index.html">回到首页</a></p></h1><div class="filterBox container"><div class="cateBox box"><p>分类 : </p><ul></ul></div><div class="saleBox box"><p>筛选 : </p><ul><li class="saleItem active" data-sale="">全部</li><li class="saleItem" data-sale="hot">热销</li><li class="saleItem" data-sale="sale">折扣</li></ul></div><div class="numberBox box"><p>折扣 : </p><ul><li class="numberItem active" data-number="10">全部</li><li class="numberItem" data-number="5">5</li><li class="numberItem" data-number="6">6</li><li class="numberItem" data-number="7">7</li><li class="numberItem" data-number="8">8</li><li class="numberItem" data-number="9">9</li></ul></div><div class="searchBox box"><p>搜索 : </p><input class="search" type="text"></div><div class="sortBox box"><p>排序 : </p><ul><li class="sortItem active" data-type="id" data-method="ASC">综合升序</li><li class="sortItem" data-type="id" data-method="DESC">综合降序</li><li class="sortItem" data-type="price" data-method="ASC">价格升序</li><li class="sortItem" data-type="price" data-method="DESC">价格降序</li><li class="sortItem" data-type="sale" data-method="ASC">折扣升序</li><li class="sortItem" data-type="sale" data-method="DESC">折扣降序</li></ul></div></div><div class="pagination container"><span class="prev disable">上一页</span><span class="total">1 / 100</span><span class="next">下一页</span><select><option value="12">12</option><option value="16">16</option><option value="20">20</option><option value="24">24</option></select></div><ul class="list container"><li><div class="show"><img src="" alt=""><span class="hot">热销</span><span class="sale">折扣</span></div><p class="title">ashjdkgashjdg</p><p class="price"><span class="current">¥ 80.00</span><span class="origin">¥ 100.00</span></p><button>加入购物车</button></li></ul><script src="../js/list.js" type="module"></script>
</body>
</html>
  • CSS代码
.filterBox {border: 1px solid #333;padding: 20px;
}.filterBox > .box {font-size: 20px;font-weight: 400;
}.filterBox > .box {display: flex;margin-bottom: 10px;
}.filterBox > .box > p {width: 150px;text-align: right;padding-right: 30px;box-sizing: border-box;
}.filterBox > .box > ul {flex: 1;display: flex;flex-wrap: wrap;
}.filterBox > .box > ul > li {padding: 5px 10px;cursor: pointer;margin: 5px 10px;
}.filterBox > .box > ul > li.active {background-color: skyblue;color: #fff;
}.filterBox > .box > input {width: 220px;padding: 5px 0 5px 20px;font-size: 20px;
}.pagination {border: 1px solid #333;margin: 10px auto;font-size: 22px;font-weight: 400;height: 50px;display: flex;align-items: center;padding: 0 10px;
}.pagination > span {padding: 5px 10px;margin: 0 15px;
}.pagination > span.prev,
.pagination > span.next {background-color: skyblue;
}.pagination > span.disable {background-color: #ccc;color: #fff;cursor: not-allowed;
}.pagination > select {padding: 0 0 0 20px;font-size: 22px;
}.list {display: flex;flex-wrap: wrap;justify-content: space-between;
}.list > li {height: 480px;width: 290px;border: 1px solid #333;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: space-between;
}.list > li > .show {width: 290px;height: 290px;border-bottom: 1px solid #333;box-sizing: border-box;padding: 5px;position: relative;
}.list > li > .show > span {padding: 10px 20px;background-color: red;color: #fff;position: absolute;right: 0;top: 0;font-size: 20px;
}.list > li > .show > span.sale {background-color: orange;right: 90px;
}.list > li > p.title {overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical;
}.list > li > p.price {font-size: 26px;color: red;font-weight: 600;margin: 10px;
}.list > li > p.price > .origin {color: #ccc;text-decoration: line-through;font-size: 20px;
}.list > li > * {pointer-events: none;/*该元素永远不会成为鼠标事件的 target但是,当其后代元素的 pointer-events 属性指定其他值时,鼠标事件可以指向后代元素*/
}.list > li > button {padding: 10px 0;font-size: 22px;pointer-events: all;
}
  • JS代码
// 0. 准备全局变量
let totalNum = 0;
const id = window.localStorage.getItem("id");
const token = window.localStorage.getItem("token");// 1. 渲染分类列表
async function getCategory() {let { info } = await ajax({url: "/goods/category",dataType: "json",});cateBoxUl.innerHTML = info.list.reduce((prev, item) => {return (prev += `<li class='cate_box_item'>${item}</li>`);}, "<li class='cate_box_item active'>全部</li>");
}
getCategory();// 2. 渲染商品列表// 全局的参数
const data = {current: 1,pagesize: 12,search: "",filter: "",saleType: 10,sortType: "id",sortMethod: "ASC",category: "",
};// 请求数据渲染页面
async function getList() {let { info } = await ajax({url: "/goods/list",// data: datadata,dataType: "json",});// 保存总页码totalNum = info.total;// 修改页面 页码展示total.innerHTML = `${data.current} / ${info.total}`;// 修改按钮样式if (data.current > 1) {prev.classList.remove("disable");}if (data.current === info.total) {next.classList.add("disable");}if (data.current === 1) {prev.classList.add("disable");}if (data.current !== info.total) {next.classList.remove("disable");}// 商品列表渲染listBox.innerHTML = info.list.reduce((prev, item) => {return (prev += `<li class="list-item" data-goods_id="${item.goods_id}"><div class="show"><img src="${item.img_big_logo}" alt="">${item.is_hot ? '<span class="hot">热销</span>' : ""}${item.is_sale ? '<span class="sale">折扣</span>' : ""}</div><p class="title">${item.title}</p><p class="price"><span class="current">¥ ${item.current_price}</span><span class="origin">¥ ${item.price}</span></p><button>加入购物车</button></li>`);}, "");
}
getList();// 事件委托---切换分类;筛选;折扣;排序
filterBox.onclick = function (e) {// 1. 点击分类if (e.target.className === "cate_box_item" ||e.target.className === "cate_box_item active") {removeClass(e);data.category = e.target.innerText === "全部" ? "" : e.target.innerText;data.current = 1;getList();}// 点击筛选if (e.target.className === "saleItem") {removeClass(e);// console.log(e.target.dataset.sale)data.filter = e.target.dataset.sale;data.current = 1;getList();}// 点击 折扣if (e.target.className === "numberItem") {removeClass(e);data.saleType = e.target.dataset.number;data.current = 1;getList();}// 点击排序if (e.target.className === "sortItem") {removeClass(e);data.sortType = e.target.dataset.type;data.sortMethod = e.target.dataset.method;data.current = 1;getList();}
};
// 排他; 修改样式
function removeClass(e) {// 获取到自己父级的所有子级, 并放到数组内const list = [...e.target.parentElement.children];// 遍历数组, 给数组内所有元素, 取消 active 类名list.forEach((item) => item.classList.remove("active"));// 给自身添加类名e.target.classList.add("active");
}
// 模糊搜索
searchBox.oninput = function () {// 1. 拿到用户输入的值const inpVal = this.value;// 2. 改变参数data.search = inpVal;data.current = 1;// 3. 发送请求getList();
};// 上一页
prev.onclick = function () {if (data.current === 1) return;data.current -= 1;getList();
};// 下一页
next.onclick = function () {if (data.current === totalNum) return;data.current += 1;getList();
};// 切换每页展示数据
selBox.onchange = function () {data.pagesize = this.value;getList();
};// 点击商品
listBox.onclick = async function (e) {if (e.target.className === "list-item") {// 拿到商品IDwindow.sessionStorage.setItem("goods_id", e.target.dataset.goods_id);// 跳转商品详情页面window.location.href = "./detail.html";}// 点击加入购物车if (e.target.nodeName == "BUTTON") {console.log("点击按钮, 发请求, 加入购物车");// 如果我们现在没有 用户ID 需要跳转登录// console.log(id, token)if (!id || !token) {window.sessionStorage.setItem("page", window.location.href);window.location.href = "./login.html";}const goodsId = e.target.parentElement.dataset.goods_id;// 商品 ID 和 用户 ID 和 token 都有了let { info } = await ajax({url: "/cart/add",method: "POST",data: { id, goodsId },headers: { authorization: token },dataType: "json",});// console.log(info);if (info.code === 1) {alert(info.message);} else {// alert('登陆状态过期,  请重新登陆')if (confirm("登陆状态过期,  点击确定跳转登录页")) {window.sessionStorage.setItem("page", window.location.href);window.location.href = "./login.html";}}}
};

八、商品详情页

1、案例效果

2、接口文档

获取商品详细信息

  • 请求地址localhost:8888/goods/item
  • 请求方式get
  • 携带参数支持 restful 风格 localhost:8888/goods/item/:id
  • 响应数据如果该商品存在, 即为该商品的详细信息

4、代码实现

  • HTML代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="../css/base.css"><link rel="stylesheet" href="../css/index.css"><link rel="stylesheet" href="../css/detail.css">
</head>
<body><h1>商品详情<p class="active"><a href="./list.html">继续购物</a><a href="./cart.html">去到购物车</a><a href="./index.html">回到首页</a></p></h1><div class="content container"><div class="left"><div class="show"><img src="" alt=""></div><ul class="list"><li></li><li></li></ul></div><div class="right"><div class="title">asjhdgj</div><div class="price">¥ 100.00</div><p class="size"><span>XS</span><span>S</span><span>M</span><span>L</span><span>XL</span></p><p class="btns"><button>加入购物车</button><button>立即结算</button></p></div></div><div class="desc container">a</div><script src="../js/detail.js" type="module"></script>
</body>
</html>
  • CSS代码
.desc {margin-top: 30px;
}.content {display: flex;justify-content: space-between;
}.content > .left {width: 450px;height: 600px;margin-right: 15px;display: flex;flex-direction: column;border: 1px solid #333;
}.content > .left > .show {width: 450px;height: 450px;border-bottom: 1px solid #333;
}.content > .left > .list {flex: 1;align-items: center;display: flex;
}.content > .left > .list > li {width: 70px;height: 70px;border: 1px solid #333;margin-left: 20px;
}.content > .right {flex: 1;display: flex;flex-direction: column;justify-content: space-between;box-sizing: border-box;padding: 10px;
}.content > .right > .title {font-weight: 700;font-size: 22px;
}.content > .right > .price {font-size: 60px;color: red;
}.content > .right > .size {display: flex;
}.content > .right > .size > span {padding: 5px 10px;height: 30px;border: 1px solid #333;border-right: none
}.content > .right > .size > span:last-child {border-right: 1px solid #333;border-radius: 0 10px 10px 0;
}.content > .right > .size > span:first-child {border-radius: 10px 0 0 10px;
}.content > .right > .btns {display: flex;justify-content: space-between;
}.content > .right > .btns > button {width: 45%;height: 50px;font-size: 26px;border: none;background-color: lightgreen;color: #fff;
}.content > .right > .btns > button:first-child {background-color: lightblue;
}
  • JS代码
// 导入公共方法
import { utils } from "../utils/utils.js";
const { ajax } = utils;//  获取DOM节点
const content = document.querySelector(".content");
const desc = document.querySelector(".desc");const id = window.sessionStorage.getItem("goods_id");// console.log(id)
if (!id) {// 没有 商品 ID 跳转 商品详情页window.location.href = "./list.html";
}// 发送请求
async function getItem() {const { info } = await ajax({url: "/goods/item",data: { id },dataType: "json",});console.log(info.info);content.innerHTML = `<div class="left"><div class="show"><img src="${info.info.img_big_logo}" alt=""></div><ul class="list"><li></li><li></li></ul></div><div class="right"><div class="title">${info.info.title}</div><div class="price">¥ ${info.info.current_price}</div><p class="size"><span>XS</span><span>S</span><span>M</span><span>L</span><span>XL</span></p><p class="btns"><button>加入购物车</button><button>立即结算</button></p></div>`;desc.innerHTML = info.info.goods_introduce
}
getItem();

九、购物车的操作

1、案例效果

2、代码实现

这里对购物车操作的接口文档还挺多,就不一一列举了

  • HTML代码
<!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>Document</title><link rel="stylesheet" href="../css/cart.css">
</head>
<body><div class="header">页面顶部</div><div class="content"><div class="top"><input type="checkbox"> 全选</div><!-- 动态生成 --><ul></ul><div class="bottom"><div class="totalNum">总件数 : 3</div><div class="btns"><button>清空购物车</button><button>去结算</button><button>删除所有已选中</button></div><div class="totalPrice">总价格 : ¥ <span>100.00</span></div></div></div><div class="footer">页面底部</div><script src="../js/cart.js" type="module"></script>
</body>
</html>
  • CSS代码
* {margin: 0;padding: 0;
}ul,ol,li {list-style: none;
}.header,.footer {width: 1200px;height: 100px;background-color: skyblue;color: #fff;font-size: 50px;display: flex;justify-content: center;align-items: center;margin: 0 auto;
}.footer {height: 400px;
}.content {width: 1200px;margin: 0 auto;padding: 10px 0;
}.content > .top,
.content > .bottom {height: 50px;background-color: pink;display: flex;align-items: center;
}.content > .bottom {justify-content: space-between;box-sizing: border-box;padding: 0 10px;
}.content > .bottom > .totalPrice > span {font-size: 20px;color: red;
}.content > .bottom > .btns > button {font-size: 18px;padding: 5px 10px;cursor: pointer;
}.content > .top > input {width: 30px;height: 30px;margin: 0 15px 0 50px;
}.content > ul {padding-top: 10px;
}.content > ul > li {width: 100%;border: 1px solid #333;box-sizing: border-box;height: 100px;margin-bottom: 10px;display: flex;
}.content > ul > li > div {display: flex;justify-content: center;align-items: center;border-right: 1px solid #333;
}.content > ul > li > div:last-child {border: none;
}.content > ul > li > .show,
.content > ul > li > .status {width: 100px;
}.content > ul > li > .status > input {width: 30px;height: 30px;
}.content > ul > li > .show > img {width: 100%;height: 100%;display: block;
}.content > ul > li > .price,
.content > ul > li > .sub {width: 200px;color: red;font-size: 20px;
}.content > ul > li > .title {width: 300px;align-items: flex-start;justify-content: flex-start;box-sizing: border-box;padding: 5px;
}.content > ul > li > .number {width: 230px;
}.content > ul > li > .number > input {width: 50px;height: 30px;text-align: center;margin: 0 5px;border: none;outline: none;font-size: 18px;
}.content > ul > li > .number > button {width: 30px;height: 30px;cursor: pointer;
}.content > ul > li > .destory {flex: 1;
}.content > ul > li > .destory > button {padding: 5px;font-size: 18px;cursor: pointer;
}
  • JS代码
// 导入公共方法
import { utils } from "../utils/utils.js";
const { ajax } = utils;// 判断用户是否登录, 没有登录跳转登录页
const id = window.localStorage.getItem("id");
const token = window.localStorage.getItem("token");
if (!id || !token) {window.sessionStorage.setItem("page", window.location.href);window.location.href = "./login.html";
}// 获取元素
var content = document.querySelector(".content");// 准备渲染函数
async function bindHtml() {// 发送请求, 请求到原本的cartListlet { info } = await ajax({url: "/cart/list",data: { id },headers: { authorization: token },dataType: "json",});let cartList = info.cart;var selctItem = 0;      // 存储选中商品的数量var selctTotalNum = 0;  // 存储选中商品的总数量var totalPrice = 0;     // 存储选中商品的总价// 1.0 找到选中商品cartList.forEach(function (item) {if (item.is_select) {selctItem++;selctTotalNum += item.cart_number;totalPrice += item.cart_number * item.current_price;}});// 1.1 查询数据, 渲染页面var str = `<div class="top"><input class="selcet_all" type="checkbox" ${// 选中的商品数量如果等于商品数量, 代表所有商品被选中selctItem === cartList.length ? "checked" : ""}> 全选</div><ul>`;cartList.forEach(function (item) {str += `<li><div class="status"><input data-id="${item.goods_id}" class="item" type="checkbox" ${item.is_select ? "checked" : ""}></div><div class="show"><img src="${item.img_small_logo}" alt=""></div><div class="title">${item.title}</div><div class="price">¥ ${item.current_price}</div><div class="number"><button class="sub_btn" data-id=${item.goods_id} data-num="${item.cart_number}">-</button><input type="text" value="${item.cart_number}"><button class="add_btn"data-id="${item.goods_id}"data-num="${item.cart_number}"data-goods_number="${item.goods_number}">+</button></div><div class="sub">¥ ${(item.cart_number * item.current_price).toFixed(2)}</div><div class="destory"><button data-id="${item.goods_id}" class="del">删除</button></div></li>`;});str += `</ul><div class="bottom"><div class="totalNum">总件数 : ${selctTotalNum}</div><div class="btns"><button class="clear">清空购物车</button><button class="go_pay" data-TotalPrice="${totalPrice}">去结算</button><button class="del_item">删除所有已选中</button></div><div class="totalPrice">总价格 : ¥ <span>${totalPrice.toFixed(2)}</span></div></div>`;content.innerHTML = str;
}// 2. 首次打开页面, 调用渲染函数
bindHtml();// 3. 利用事件冒泡, 将所有的事件委托给统一的父级
content.onclick = async function (e) {// 3.1 全选按钮事件if (e.target.className === "selcet_all") {// 发送 修改 全选按钮 的请求await ajax({url: "/cart/select/all",method: "POST",data: {id,type: e.target.checked ? 1 : 0,},headers: { authorization: token },});//重新渲染视图bindHtml();}//清空购物车if (e.target.className === "clear") {var boo = confirm("请问您确定清空吗");if (boo) {await ajax({url: "/cart/clear",data: { id },headers: { authorization: token },});// 重新渲染页面bindHtml();}}// 删除已选中  (没有选中项时 禁止执行)if (e.target.className === "del_item") {var boo = confirm("请问您确定删除已选中吗");if (boo) {await ajax({url: "/cart/remove/select",data: { id },headers: { authorization: token },});// 重新渲染视图bindHtml();}}// 减少商品数量if (e.target.className === "sub_btn") {const goodsId = e.target.dataset.id;const number = e.target.dataset.num - 0 - 1;if (number < 1) return;await ajax({url: "/cart/number",method: "POST",data: { id, goodsId, number },headers: { authorization: token },});// 重新渲染视图bindHtml();}// 增加商品数量if (e.target.className === "add_btn") {const goodsId = e.target.dataset.id;const number = e.target.dataset.num - 0 + 1;const goods_number = e.target.dataset.goods_number - 0;if (number > goods_number) return;await ajax({url: "/cart/number",method: "POST",data: { id, goodsId, number },headers: { authorization: token },});// 重新渲染视图bindHtml();}// 选中商品if (e.target.className === "item") {const goodsId = e.target.dataset.id;await ajax({url: "/cart/select",method: "POST",data: { id, goodsId },headers: { authorization: token },});// 重新渲染视图bindHtml();}// 删除某一项if (e.target.className === "del") {var boo = confirm("请问您确定删除当前项吗");// 询问用户是否需要删除if (!boo) return;const goodsId = e.target.dataset.id - 0;await ajax({url: "/cart/remove",data: { id, goodsId },headers: { authorization: token },});// 重新渲染视图bindHtml();}
};

【综合案例】原生JS实现购物商城相关推荐

  1. 20190726——综合练习——幸运抽奖、购物商城

    一.小知识点 方法中不能含输入功能,通过参数实现传递 一旦有输出,方法必须带有返回值 二.综合练习--幸运抽奖 1.任务:模拟注册登录幸运抽奖全过程 主要功能 注册 登录 幸运抽奖 阶段1:实现 ...

  2. 原生js实现京东商城楼梯效果

    这个可能有些兼容问题和小bug,新手写的不完善 欢迎指出 <!DOCTYPE html> <html> <head><title></title& ...

  3. 基于vue2.0打造移动商城页面实践 vue实现商城购物车功能 基于Vue、Vuex、Vue-router实现的购物商城(原生切换动画)效果...

    基于vue2.0打造移动商城页面实践 地址:https://www.jianshu.com/p/2129bc4d40e9 vue实现商城购物车功能 地址:http://www.jb51.net/art ...

  4. 【014】基于Vue.js的移动端购物商城网站(含源码、课设报告)

    文章目录 一.项目介绍 二.代码及报告获取 一.项目介绍 基于Vue.js的移动端购物商城网站(含源码.课设报告),代码获取放在文末了,码字不易,感谢点赞~ 一.系统概述 本部分主要是对项目进行简要描 ...

  5. 前端实现模拟购物商城案例实现

    学习HTML,css和js前端的小伙伴们,这次来分享购物商城案例的实现! 准备阶段: 准备一些需要放到页面上的图片,小图和其对应的大图,博主这边举例为小图(40 x 40),大图(321 x 430) ...

  6. [Vue.js] 基础 -- 综合案例 -- 图书管理

    综合案例 – 图书管理 补充知识(数组相关API) 变异方法(修改原有数据) push() pop() shift() unshift() splice() sort() reverse() 替换数组 ...

  7. HTML+CSS+JS—时尚服装购物商城(1页) 大学生服装购物商城网页作品 服装网页设计作业模板 学生网页制作源代码下载

    HTML5期末大作业:服装购物商城网站设计--时尚服装购物商城(1页) 文章目录 HTML5期末大作业:服装购物商城网站设计--时尚服装购物商城(1页) 一.作品展示 二.文件目录 三.代码实现 四. ...

  8. javaweb JAVA JSP汽车配件销售系统jsp配件销售网站 (jsp电子商务系统,购物商城)在线购物案例

    JSP汽车配件销售系统jsp配件销售网站 (jsp电子商务系统,购物商城)在线购物案例 大家好,很高兴和大家分享Java项目和经验.不管同学们是出于什么需求.都希望各位计算机专业的同学有一个提高. 本 ...

  9. 原生JS仿造华为商城案例-实现了简单页面-两种轮播图思路的实现-动态展示数据

    文章目录 功能简介 项目准备 项目说明 方案一:源代码 CSS样式 HTML结构 JS逻辑 方案二:源代码 CSS样式 HTML结构 JS逻辑 功能简介 页面展示 该项目功能实现: 无限滚动轮播图 j ...

最新文章

  1. 怎么发表论文能保证被收录
  2. Android Retrofit框架请求复杂json数据
  3. 【LeetCode】面试题 03. 数组中重复的数字
  4. python进阶15变量作用域LEGB
  5. 李开复:2018中国最大AI红利?是政策
  6. 15拆分成3个不同的自然数_素数大概有多少个?15岁的高斯翻过素数表之后给出了答案...
  7. C++时间类型详解( time_t 和 tm )
  8. 零基础学习UI设计,有哪些软件推荐
  9. 高质量WordPress下载站模板5play主题源码
  10. 自动化测试如何计算ROI
  11. Q_D以及Q_Q指针理解
  12. 膨胀腐蚀-OpenCL加速及kernel变成二进制文件
  13. 排序算法——冒泡排序(JavaPython)
  14. LeetCode之玩筹码
  15. SMTP rcpt error: 5.7.1 Relay access denied
  16. foter 图像处理APP_摄影师必备的图像编辑APP
  17. 常用小程序开发框架浅析
  18. springcloud宕机问题
  19. 移动UI 设计有哪些文字规范
  20. 2022云计算技能大赛

热门文章

  1. 怎么给计算机主机设置密码,怎么给电脑设置密码,详细教您电脑怎么设置密码...
  2. 工业4.0-----老黄新天地之MES系统通信原理
  3. 存储卡插入电脑提示磁盘有写保护怎么办
  4. 大牛证券平台解析有量轮动还会继续
  5. c#将字符串转换为数组_C# 字符串转数组
  6. Vue中的methods方法使用技巧,三分钟迅速读懂
  7. EXCEL之跨工图表数据区域
  8. 成为真正生意人的七种基本能力
  9. 安卓查看内存读写测试软件_为什么手机逐渐不支持内存卡了呢?
  10. pjax php demo,jQuery pjax 应用简单示例