js商品列表组件开发(数据驱动显示)
商品列表
商品列表
作为一个前端程序员,最重要的之一就是数据驱动显示。mvc和mvvc框架是我们熟知的,其中vue框架使用的就是mvvc结构,但不论哪种结构,
重要的都是有数据驱动我们显示出来的页面,例如商品列表,每个数据都是别人发过来的,我们总不能根据设计图用css和html写成固定的格式,否则数据换了,怎么办?
需求分析
1.根据设计图展示不同的商品
2.后端数据随时会换,也要能展示出来,增加商品和减少商品,样式不能变化
3.大图刚开始默认为小图的第一张,鼠标滑过小图时,大图要跟着变化
4.小图第一张有默认的边框,鼠标划过时,边框在滑过的图片上
3.性能优化,不能加载时间长
效果图
默认样式:
鼠标滑过小图:
分析
- 不能按照平时的写行内样式,js每次操作DOM都会给性能造成影响
- 通过js书写html标签和css样式
- 分开模块写,html标签模块,css模块,写入数据模块,鼠标滑过大图随小图改变模块
下面,附上代码
html页面根据数据数量,实例化对象,传入数据,引入组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script type="module">
import GoodsItem from "./js/GoodsItem.js";
var arr = [
{
id: 1001,
icon: ["./img/a1.jpg"],
miniIcon: ["./img/mini_a1.jpg"],
price: 1199,
info: "荣耀Play4T 全网通6GB+128GB大内存 蓝水翡翠 4000mAh大电池 4800万AI摄影 6.39英寸魅眼屏",
info1: "",
used: false,
evaluate: "31万+",
shopName: "荣耀京东自营旗舰店",
presell: "",
tag: ["自营", "放心购"]
},
{
id: 1002,
icon: ["./img/a2.jpg"],
miniIcon: ["./img/mini_a2.jpg"],
price: 1389,
info: "荣耀Play4T Pro 麒麟810芯片 OLED屏幕指纹 4800万高感光夜拍三摄 22.5W超级快充 全网通6GB+128GB 幻夜黑",
info1: "",
used: false,
evaluate: "10万+",
shopName: "荣耀京东自营旗舰店",
presell: "预售中",
tag: ["自营", "放心购", "秒杀", "赠"]
},
{
id: 1003,
icon: ["./img/b1_1.jpg", "./img/b1_2.jpg", "./img/b1_3.jpg"],
miniIcon: ["./img/mini_b1_1.jpg", "./img/mini_b1_2.jpg", "./img/mini_b1_3.jpg"],
price: 1389,
info: "荣耀Play4T Pro 麒麟810芯片 OLED屏幕指纹 4800万高感光夜拍三摄 22.5W超级快充 全网通6GB+128GB 蓝水翡翠",
info1: "",
used: false,
evaluate: "37万+",
shopName: "荣耀京东自营旗舰店",
presell: "",
tag: ["自营", "放心购", "本地仓", "秒杀", "赠"]
},
{
id: 1004,
icon: ["./img/b2_1.jpg", "./img/b2_2.jpg", "./img/b2_3.jpg"],
miniIcon: ["./img/mini_b2_1.jpg", "./img/mini_b2_2.jpg", "./img/mini_b2_3.jpg"],
price: 1199,
info: "荣耀Play4T 全网通6GB+128GB大内存 蓝水翡翠 4000mAh大电池 4800万AI摄影 6.39英寸魅眼屏",
info1: "",
used: false,
evaluate: "31万+",
shopName: "荣耀京东自营旗舰店",
presell: "",
tag: ["自营", "放心购", "本地仓", "赠"]
},
{
id: 1005,
icon: ["./img/c1.jpg"],
miniIcon: ["./img/mini_c1.jpg"],
price: 1389,
info: "荣耀Play4T Pro 麒麟810芯片 OLED屏幕指纹 4800万高感光夜拍三摄 22.5W超级快充 全网通6GB+128GB 幻夜黑",
info1: "",
evaluate: "10万+",
used: false,
shopName: "荣耀京东自营旗舰店",
presell: "",
tag: ["自营", "放心购", "秒杀", "赠"],
},
{
id: 1006,
icon: ["./img/c2_1.jpg", "./img/c2_2.jpg", "./img/c2_3.jpg", "./img/c2_4.jpg"],
miniIcon: ["./img/mini_c2_1.jpg", "./img/mini_c2_2.jpg", "./img/mini_c2_3.jpg", "./img/mini_c2_4.jpg"],
price: 1389,
info: "麒麟980芯片;超感光徕卡四摄10倍混合变焦;店铺首页领白条免息券",
info1: "",
evaluate: "81万+",
used: true,
shopName: "华为京东自营官方旗舰店",
presell: "",
tag: ["自营", "放心购", "秒杀", "赠"],
},
{
id: 1007,
icon: ["./img/d1_1.jpg", "./img/d1_2.jpg", "./img/d1_3.jpg", "./img/d1_4.jpg", "./img/d1_5.jpg"],
miniIcon: ["./img/mini_d1_1.jpg", "./img/mini_d1_2.jpg", "./img/mini_d1_3.jpg", "./img/mini_d1_4.jpg", "./img/mini_d1_5.jpg"],
price: 2489,
info: "荣耀Play4 Pro 5G双模 麒麟990 4000万超感光暗拍 40W超级快充 8GB+128GB幻夜黑",
info1: "",
used: false,
evaluate: "21万+",
shopName: "荣耀京东自营官方旗舰店",
presell: "",
tag: ["自营", "放心购", "秒杀", "赠"]
},
{
id: 1008,
icon: ["./img/d2_1.jpg", "./img/d2_2.jpg", "./img/d2_3.jpg", "./img/d2_4.jpg"],
miniIcon: ["./img/mini_d2_1.jpg", "./img/mini_d2_2.jpg", "./img/mini_d2_3.jpg", "./img/mini_d2_4.jpg"],
price: 2399,
info: " HUAWEI nova 5 Pro 前置3200万人像超级夜景4800万AI四摄麒麟980芯片8GB+128GB亮黑色全网通双4G",
info1: "",
used: true,
evaluate: "57万+",
shopName: "华为京东自营官方旗舰店",
presell: "",
tag: ["自营", "放心购", "秒杀"]
},
{
id: 1009,
icon: ["./img/e1_1.jpg", "./img/e1_2.jpg", "./img/e1_3.jpg", "./img/e1_4.jpg", "./img/e1_5.jpg"],
miniIcon: ["./img/mini_e1_1.jpg", "./img/mini_e1_2.jpg", "./img/mini_e1_3.jpg", "./img/mini_e1_4.jpg", "./img/mini_e1_5.jpg"],
price: 2489,
info: "荣耀V30 5G 双模 麒麟990 突破性相机矩阵 游戏手机 8GB+128GB 幻夜星河 移动联通电信5G 双卡双待",
info1: "",
used: false,
evaluate: "21万+",
shopName: "荣耀京东自营旗舰店",
presell: "",
tag: ["自营", "放心购", "秒杀", "赠"],
},
{
id: 1010,
icon: ["./img/e2_1.jpg", "./img/e2_2.jpg", "./img/e2_3.jpg", "./img/e2_4.jpg", "./img/e2_5.jpg"],
miniIcon: ["./img/mini_e2_1.jpg", "./img/mini_e2_2.jpg", "./img/mini_e2_3.jpg", "./img/mini_e2_4.jpg", "./img/mini_e2_5.jpg"],
price: 1889,
info: "荣耀Play4 5G双模 6400万锐力四摄 4300mAh大电池 VC液冷散热 8GB+128GB 幻夜黑TNNH-AN00",
info1: "",
used: false,
evaluate: "170万+",
shopName: "荣耀京东自营旗舰店",
presell: "",
tag: ["自营", "放心购", "秒杀", "赠"],
},
{
id: 1011,
icon: ["./img/f1_1.jpg", "./img/f1_2.jpg", "./img/f1_3.jpg"],
miniIcon: ["./img/mini_f1_1.jpg", "./img/mini_f1_2.jpg", "./img/mini_f1_3.jpg"],
price: 1889,
info: "荣耀Play4 5G双模 6400万锐力四摄 4300mAh大电池 VC液冷散热",
info1: "",
used: true,
evaluate: "1.3万+",
shopName: "荣耀京东自营旗舰店",
presell:"",
tag: ["自营", "放心购", "秒杀", "赠"],
},
{
id: 1012,
icon: ["./img/f2_1.jpg", "./img/f2_2.jpg", "./img/f2_3.jpg"],
miniIcon: ["./img/mini_f2_1.jpg", "./img/mini_f2_2.jpg", "./img/mini_f2_3.jpg"],
price: 1889,
info: "华为畅享10e 手机 翡冷翠 移动全网通(4G+64G)",
info1: "【4 + 64移动绿秒杀低至828元!直降170元!】现货速发,5000mAh超长续航,支持反向充电~!《畅享20pro咨询减钱》戳~",
used: true,
evaluate: "1.3万+",
shopName: "荣耀京东自营旗舰店",
presell:"",
tag: ["京东物流", "放心购", "秒杀", "免邮", "险"],
}
]
var list=[];
arr.forEach(item=>{
let goods=new GoodsItem();
goods.appendTo("body");
goods.setData(item);
list.push(goods);
});
js代码,书写样式,填入数据,引入工具包
import Utils from "./Utils.js";
export default class GoodsItem {
static styleBool = false;
data;
preIcon;
constructor() {
this.elem = this.createElem();
if (!GoodsItem.styleBool) GoodsItem.setStyle(); //通过bool值控制每个元素css样式的创建
this.renderHTML();
}
createElem() { //通过实例创建,不需要传参
if (this.elem) return this.elem;
var div = Utils.ce("div");
div.className = "goodsItem"; //每一个元素的最外层div 根据数据有多少个创建多少个
return div;
}
appendTo(parent) {
if (typeof parent === "string") parent = document.querySelector(parent);
parent.appendChild(this.elem);
}
setData(_data) { //外部数据的传入 每次实例化都要执行
this.data = _data; //后面能用到的数据设置成全局的
this.icon = this.elem.querySelector(".icon");
let presell = this.elem.querySelector(".presell");
this.miniIconCon = this.elem.querySelector(".miniIconCon");
var price = this.elem.querySelector(".price");
var infoCon = this.elem.querySelector(".infoCon");
var evaluate = this.elem.querySelector(".evaluate");
var shopCon = this.elem.querySelector(".shopCon");
var tagCon = this.elem.querySelector(".tagCon");
if (_data.presell.trim().length > 0) {
presell.textContent = _data.presell;
} else {
presell.style.display = "none"
}
var miniIconStr = "";
_data.miniIcon.forEach(item => { //设置小图片
miniIconStr += `<img src=${item} class="miniIcon">`
})
this.miniIconCon.innerHTML = miniIconStr;
this.miniIconCon.addEventListener("mouseover", e => this.iconMouseHandler(e)) //小图片的划过事件
this.changeIcon(0); //大图的默认显示图片
price.textContent = _data.price.toFixed(2);
infoCon.textContent = _data.info
evaluate.textContent = _data.evaluate;
shopCon.textContent = _data.shopName;
var dic={ //下面小标签 数据表
"自营":"goods_icon_0",
"放心购":"goods_icon_1",
"本地仓":"goods_icon_0",
"赠":"goods_icon_2",
"京东物流":"goods_icon_2",
"秒杀":"goods_icon_2",
"免邮":"goods_icon_2",
"险":"goods_icon_3",
}
var tagStr="";
_data.tag.forEach(item=>{
tagStr+=`<span class=\'${dic[item]}\'>${item}</span>`
});
tagCon.innerHTML=tagStr;
}
iconMouseHandler(e) { //根据划过的图片在小图片中的坐标 ,来执行大图的改变事件
if (e.target.constructor !== HTMLImageElement) return;
var index = Array.from(e.currentTarget.children).indexOf(e.target);
this.changeIcon(index); //大图改变
}
changeIcon(index) {
if (this.preIcon) {
this.preIcon.style.border = "1px solid #CCCCCC";
}
this.icon.src = this.data.icon[index];
this.preIcon = this.miniIconCon.children[index]; //????this.preIcon
this.preIcon.style.border = "2px solid #e4393c";
}
renderHTML() { //设置每个元素内部 html标签
this.elem.innerHTML = `
<div class="iconCon"> <!--图片容器-->
<img class="icon"> <!--图片-->
<div class="presell"> <!--预售-->
</div>
<div class="miniIconCon"></div> <!--小图-->
<div class="priceCon"> <!--价钱-->
<span class="money">¥</span>
<span class="price"></span>
</div>
<a class="infoCon" href="#"></a> <!--信息-->
<div class="evaluateCon"><span class="evaluate"></span>条评价</div> <!--评价-->
<div class="shopCon"></div> <!--商店-->
<div class="tagCon"> <!--标签-->
</div>
`
}
static setStyle() { //设置css样式
GoodsItem.styleBool = true;
Utils.setStyle({
".goodsItem": { //最外层div样式
width: "240px",
height: "466px",
float: "left",
margin: "5px"
},
".goodsItem:hover": {
boxShadow: "0px 0px 4px #999999"
},
".iconCon": { //大图
width: "220px",
height: "220px",
marginBottom: "5px",
position: "relative",
margin: "auto",
left: 0,
right: 0,
},
".presell": { //预售
width: "200px",
height: "25px",
position: "absolute",
bottom: "0px",
backgroundColor: "rgba(0,0,0,0.4)",
fontSize: "12px",
color: "#FFFFFF",
paddingLeft: "20px",
lineHeight: "25px"
},
".miniIcon": { //小图
width: "25px",
height: "25px",
border: "1px solid #CCCCCC",
marginRight: "5px"
},
".priceCon": { //价钱
width: "220px",
height: "22px",
color: "#e4393c",
fontSize: "20px",
marginTop:"5px",
},
".money": {
fontSize: "16px",
},
".price": {
marginLeft: "-10px"
},
".infoCon": { //信息
width: "220px",
height: "40px",
wordWrap: "break-word",
overflow: "hidden",
display: "block",
fontSize: "12px",
color: "#333333",
lineHeight: "20px",
marginTop: "10px",
textDecoration: "none"
},
".evaluateCon": { //评价
width: "220px",
height: "18px",
fontSize: "12px",
color: "#333333",
marginTop: "5px"
},
".evaluate": {
color: "#646FB0",
fontWeight: "600",
},
".shopCon": { //商店
fontSize: "12px",
marginTop: "5px",
color: "#AAAAAA",
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
width: "122px",
height: "18px"
},
".goods_icon_0": { //下面标签
float: "left",
height: "16px",
lineHeight: "16px",
padding: "0 3px",
marginRight: "3px",
overflow: "hidden",
textAlign: "center",
fontStyle: "normal",
fontSize: "12px",
fontFamily: \'"Helvetica Neue","Hiragino Sans GB",SimSun,serif\',
background: "#e23a3a",
color: "#FFF",
cursor: "default",
borderRadius: "2px",
},
".goods_icon_1": {
border: "1px solid #e23a3a",
borderColor: "#4d88ff",
color: "#4d88ff",
float: "left",
height: "14px",
lineHeight: "14px",
padding: "0 3px",
marginRight: "3px",
overflow: "hidden",
textAlign: "center",
fontStyle: "normal",
fontSize: "12px",
fontFamily: \'"Helvetica Neue","Hiragino Sans GB",SimSun,serif"\',
borderRadius: "2px",
},
".goods_icon_2": {
float: \'left\',
height: \'14px\',
lineHeight: \'14px\',
padding: \'0 3px\',
border: \'1px solid #e23a3a\',
marginRight: \'3px\',
overflow: \'hidden\',
textAlign: \'center\',
fontStyle: \'normal\',
fontSize: \'12px\',
fontFamily: \'"Helvetica Neue","Hiragino Sans GB",SimSun,serif\',
borderRadius: \'2px\',
color: \'#e23a3a\',
},
".goods_icon_3": {
float: \'left\',
height: \'16px\',
lineHeight: \'16px\',
padding: \'0 3px\',
marginRight: \'3px\',
overflow: \'hidden\',
textAlign: \'center\',
fontStyle: \'normal\',
fontSize: \'12px\',
fontFamily: \'"Helvetica Neue","Hiragino Sans GB",SimSun,serif\',
background: \'#e23a3a\',
color: \'#FFF\',
cursor: \'default\',
borderRadius: \'2px\',
background: "#4b9bfc",
},
".tagCon":{
marginTop:"10px"
}
})
}
}
Utils.js是个工具包
export default class Utils{
static time=0;
static ids=0;
static timeManage={};
//代码执行时间
static timeStart(){
if(Utils.time) return;
Utils.time=new Date().getTime();
}
static timeEnd(){
var t=new Date().getTime()-Utils.time;
Utils.time=0;
return t;
}
static ts(){
Utils.ids++;
Utils.timeManage[Utils.ids]=new Date().getTime();
return ids;
}
static te(id){
if(!Utils.timeManage[Utils.id]) return 0;
var t=new Date().getTime()-Utils.timeManage[Utils.id];
delete Utils.timeManage[Utils.id];
return t;
}
//随机颜色
static randomColor(){
var col="#";
for(var i=0;i<6;i++){
col+=Math.floor(Math.random()*16).toString(16);
}
return col;
}
//随机数值
static random(min,max){
return Math.floor(Math.random()*(max-min)+min);
}
//创建元素 设置样式 插入父级元素
static ce(type,style,parent){
var elem=document.createElement(type);
if(style){
for(var prop in style){
elem.style[prop]=style[prop];
}
}
if(typeof parent==="string") parent=document.querySelector(parent);
if(parent) parent.appendChild(elem);
return elem;
}
//读取css样式
static setStyle(styles){
var style=document.createElement("style");
document.head.appendChild(style);
var styleSheet=document.styleSheets[document.styleSheets.length-1];
for(var prop in styles){
Utils.addCss(styleSheet,prop,styles[prop]);
}
}
//添加css样式
static addCss(styleSheet,selector,style){
var str=selector+" {";
for(var prop in style){
var value=style[prop]
prop=prop.replace(/([A-Z])/g,function($1){
return "-"+$1.toLowerCase();
})
str+=prop+":"+value+";"
}
str+=" }";
styleSheet.insertRule(str,styleSheet.cssRules.length);
}
//css样式转化为js内样式
static CSStoString(str){
return str.replace(/(?<=:)(.*?)(?=;)|-[a-z](?=.+:)|;/g,function(item){
if(item===";") return ","
if(item[0]==="-") return item[1].toUpperCase();
return "\'"+item.trim()+"\'";
});
}
//字符串css转换为js内样式
static CSStoObject(str){
str=Utils.CSStoString(str);
return str.split(",").reduce((value,item)=>{
item=item.replace(/\n/g,"");
var arr=item.split(":");
arr[0]=arr[0].replace(/\s/g,"");
if(arr[1]===undefined) return value;
arr[1]=arr[1].replace(/\'/g,"");
value[arr[0]]=arr[1];
return value;
},{})
}
}