资料链接和启动方法: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、登录页逻辑
采集用户信息
—点击登录时验证信息
- 非空验证
- 正则校验
把用户名和密码发送给后端
—> 根据后端返回结果, 做不同的事
- 跳转首页
- 提示用账号密码错误
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、首页的逻辑
- 分析原因
- http是一个
无状态请求
,每次请求之间没有任何关联- 刚刚登陆成功, 并立马跳转到首页, 此时发送获取用户详情的请求,在服务端看来, 是
两个独立
的请求- 所以我们需要一个东西, 来
证明我们刚刚登陆成功了
- 解决方法
- 有一个 叫做
token
的东西, 是服务端给我们的, 注意有过期时间
- 当我们请求的时候, 我把
用户账号和密码给到服务端
, 然后服务端会生成一个token信息
- 我们后续发送请求时, 携带上这个
token
,服务端就能直到我们刚刚登陆成功、- token是后端根据我们信息生成一个只属于我们自己的
加密的文本
- 代码逻辑
- 判断token和id都正常存在
- 向服务端发请求,根据请求结果, 展示不同的页面
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、个人页的逻辑
- 这个页面, 能随便进入吗?
- 判断当前
是否登录 (token)
- 请求用户信息
渲染页面(users/info)
- 修改用户信息后, 点击修改
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、修改密码的逻辑
- 思考:能随便进吗?
- 必须是
登陆状态的 (token)
- 前端验证
不能为空
正则验证
- 验证
新密码与重复新密码必须相同
- 满足上述三点, 发送请求
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、注册新用户的逻辑
- 思考:需要登陆状态吗?
不需要登陆状态
- 点击事件内
收集用户信息
非空校验
- 正则校验:
密码与重复密码必须相同
- 发送注册请求
成功:提示用户注册成功; 跳转到登录页
失败:可能就用户名重复, 提示用户重新输入用户名
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: data
data,
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") {
// 拿到商品ID
window.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、案例效果
文章来源:https://www.toymoban.com/news/detail-477971.html
2、代码实现
这里对购物车操作的接口文档还挺多,就不一一列举了
文章来源地址https://www.toymoban.com/news/detail-477971.html
- 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() {
// 发送请求, 请求到原本的cartList
let { 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实现购物商城的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!