【综合案例】原生JS实现购物商城
创始人
2024-03-25 18:05:00
0

目录

    • 一、案例说明
      • 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、代码实现


一、案例说明

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代码


Document

登录页

用户名密码错误, 请重试 ! ^_^没有账号, 请进入注册
  • 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代码


Document

首页

您好, 请登录

您好, 用户名个人中心

  • 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代码


Document

个人页

回到首页; 修改密码

  • 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


Document

修改密码

回到首页; 修改密码

  • 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代码


Document

注册页

  • 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代码


Document

商品列表

去到购物车; 回到首页

分类 :

    筛选 :

    • 全部
    • 热销
    • 折扣

    折扣 :

    • 全部
    • 5
    • 6
    • 7
    • 8
    • 9

    搜索 :

    排序 :

    • 综合升序
    • 综合降序
    • 价格升序
    • 价格降序
    • 折扣升序
    • 折扣降序
    上一页1 / 100下一页
    • 热销折扣

      ashjdkgashjdg

      ¥ 80.00¥ 100.00

    • 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 += `
  • ${item}
  • `);}, "
  • 全部
  • "); } 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 += `
  • item.goods_id}">
    item.img_big_logo}" alt="">${item.is_hot ? '热销' : ""}${item.is_sale ? '折扣' : ""}

    ${item.title}

    ¥ ${item.current_price}¥ ${item.price}

  • `);}, ""); } 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代码
    
    
    Document
    
    

    商品详情

    继续购物去到购物车回到首页

    asjhdgj
    ¥ 100.00

    XSSMLXL

    a
    • 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 = `
    info.info.img_big_logo}" alt="">
    ${info.info.title}
    ¥ ${info.info.current_price}

    XSSMLXL

    `;desc.innerHTML = info.info.goods_introduce } getItem();

    九、购物车的操作

    1、案例效果

    在这里插入图片描述

    2、代码实现

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

    • HTML代码
    
    
    Document
    
    
    页面顶部
    全选
      总件数 : 3
      总价格 : ¥ 100.00
      页面底部
      • 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 = `
      // 选中的商品数量如果等于商品数量, 代表所有商品被选中selctItem === cartList.length ? "checked" : ""}> 全选
        `;cartList.forEach(function (item) {str += `
      • item.goods_id}" class="item" type="checkbox" ${item.is_select ? "checked" : ""}>
        item.img_small_logo}" alt="">
        ${item.title}
        ¥ ${item.current_price}
        item.cart_number}">
        ¥ ${(item.cart_number * item.current_price).toFixed(2)}
      • `;});str += `
      总件数 : ${selctTotalNum}
      总价格 : ¥ ${totalPrice.toFixed(2)}
      `;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();} };

      相关内容

      热门资讯

      AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
      银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
      【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
      不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
      AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
      月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
      ​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
      北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
      AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
      AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...