收货地址之三级联动式菜单(省、市、区),利用AJAX通信,面向对象编程,并且将菜单放入指定容器中
程序员文章站
2024-03-14 20:46:29
...
效果图
实现功能:各级菜单之间实现联动效果。
请求方式:我是利用的AJAX请求数据,GET方式发送,利用了一个组件,将省份名和城市名存为对象直接发送给了服务端。后面我也会将组件的代码开源出来,以便你们理解。
// 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>
<style>
*{
margin: 0;
padding: 0;
}
#box{
margin: 50px 300px;
}
</style>
</head>
<body>
<div id="box"></div>
<script type="module">
import Menu from "./js/Menu.js"
let div=document.querySelector("#box");
let menu=new Menu();
menu.appendTo(div);
</script>
</body>
</html>
// JS代码
import QueryString from "./QueryString.js"
export default class Menu {
elem;
constructor(){
this.elem=this.createMenu(); // 创建DOM元素
this.ajax("province"); // 初次打开页面,设置菜单默认选项
}
createMenu(){
if(this.elem) return this.elem;
let div=document.createElement("div");// 创建最外层容器,用于存放所有的菜单容器
this.render(div);// 创建菜单并渲染
return div;
}
appendTo(parent){ // 将该对象插入到指定容器中
if(typeof parent==="string") parent=document.querySelector(parent);
parent.appendChild(this.elem);
}
ajax(type,data){ // 创建发送AJAX请求的方法,传入的参数为接口和请求数据
if(data===undefined) data="";
data=QueryString.stringify(data);
var xhr=new XMLHttpRequest();
xhr.addEventListener("load",e=>this.loadHanlder(e));
xhr.open("GET","http://10.9.72.222:4008/"+type+"/?"+data);
xhr.send();
}
render(parent){
for(let i=0;i<3;i++){
let div=document.createElement("div");// 菜单容器
let div1=document.createElement("div");// 文本容器
let span1=document.createElement("span");// 存放选项的文本内容
let span2=document.createElement("span");// 设置下拉箭头
let ul=document.createElement("ul"); // 下拉菜单
Object.assign(div.style,{
marginLeft:"40px",
cursor:'pointer',
display:"inline-block",
position:"relative",
backgroundColor:'#fff',
borderRadius: '4px',
border: '1px solid #ccc',
})
Object.assign(div1.style,{
color:'#333',
padding:'6px 12px',
height:'20px',
fontSize:'14px',
userSelect: 'none',
})
Object.assign(span1.style,{
userSelect: 'none',
fontSize:"14px",
textAlign:"center",
whiteSpace:"nowrap",
lineHeight:"20px",
})
Object.assign(span2.style,{
width:'0',
height:'0',
margin:"0px 2px",
borderTop:'4px solid',
borderRight:'4px solid transparent',
borderLeft:'4px solid transparent',
verticalAlign:'middle',
display:'inline-block',
})
Object.assign(ul.style,{
display:'none',
color:'#262626',
position:'absolute',
top:'31px',
left:'-2px',
float:'left',
minWidth:'100px',
padding:'5px 0',
margin:'2px 0 0',
fontSize:'14px',
textAlign:'left',
listStyle:'none',
backgroundColor:'#fff',
border:'1px solid rgba(0, 0, 0, 0.15)',
borderRadius:'4px',
boxShadow:'0 6px 12px rgba(0, 0, 0, 0.175)',
})
div.addEventListener("mouseover",this.mouseHandler1);// 控制下拉菜单的显示
div.addEventListener("mouseleave",e=>this.mouseHandler1(e));// 控制下拉菜单的隐藏
ul.addEventListener("click",e=>this.clickHandler2(e));// 事件委托,点击li,切换菜单选项,并生成后续菜单内容
ul.addEventListener("mouseover",e=>this.mouseHandler2(e));// 事件委托,li的鼠标划入效果
ul.addEventListener("mouseout",e=>this.mouseHandler2(e));// 事件委托,li的鼠标划出效果
ul.addEventListener("mouseleave",e=>this.mouseHandler2(e));// 鼠标离开ul时,ul列表隐藏
div.setAttribute("class",`div${i}`);// 设置菜单容器的类名,用于区别三个菜单
div1.appendChild(span1);
div1.appendChild(span2);
div.appendChild(div1);
div.appendChild(ul);
parent.appendChild(div);
}
}
mouseHandler1(e){
if(e.type==="mouseover"){
Object.assign(e.currentTarget.style,{
backgroundColor:'#d4d4d4',
borderColor: '#8c8c8c',
})
e.currentTarget.lastElementChild.style.display='block';
}else{
Object.assign(e.currentTarget.style,{
backgroundColor: '#fff',
borderColor: '#ccc',
})
e.currentTarget.lastElementChild.style.display='none';
}
}
clickHandler2(e){
if(e.target.nodeName!=="LI") return;// 判断如果点击的不是li元素,则跳出
switch(e.currentTarget.parentElement.getAttribute("class")){
case "div0":
e.currentTarget.previousElementSibling.firstElementChild.textContent=e.target.textContent;
this.ajax("city",{city:e.target.textContent})
break;
case "div1":
e.currentTarget.previousElementSibling.firstElementChild.textContent=e.target.textContent;
this.ajax("county",{city:e.currentTarget.parentElement.previousElementSibling.firstElementChild.firstElementChild.textContent,county:e.target.textContent})
break;
case "div2":
e.currentTarget.previousElementSibling.firstElementChild.textContent=e.target.textContent;
break;
}
}
mouseHandler2(e){
if(e.type==="mouseleave") e.currentTarget.style.display="none";
if(e.target.nodeName!=="LI") return;
if(e.type==="mouseover"){
e.target.style.backgroundColor='#f5f5f5';
}else if(e.type==="mouseout"){
e.target.style.backgroundColor='#fff';
}
}
loadHanlder(e){ // AJAX请求完成后,触发该事件
var xhr=e.currentTarget;
var type=xhr.responseURL.trim().split("?")[0];// 获取请求的接口类型,判断接下来进行哪步操作
if(type.slice(-1)==="/") type=type.slice(0,-1);
type=type.split("/").pop();
var o=JSON.parse(xhr.response);// 获取服务器发过来的数据
switch(type){
case "province":
this.createList(o,0);// 创建第一层菜单的li,并添加至第一层菜单的ul下
this.ajax("city",{city:this.elem.children[0].firstElementChild.textContent})// 创建第二层菜单,将第一层的选项文本内容发送给服务器
break;
case "city":
this.createList(o,1);// 创建第二层菜单的li,并添加至第二层菜单的ul下
this.ajax("county",{city:this.elem.children[0].firstElementChild.textContent,county:this.elem.children[1].firstElementChild.textContent})// 创建第三层菜单,将第一层和第二层的选项文本内容发送给服务器
break;
case "county":
this.createList(o,2);// 创建第三层菜单的li,并添加至第三层菜单的ul下
break;
}
}
createList(data,n){
this.elem.children[n].lastElementChild.innerHTML="";// 清空ul列表下的所有子元素
let parent=document.createDocumentFragment();// 创建碎片容器,用于存放创建的li
data.forEach((item,index)=>{ // 根据服务器返回的数据,遍历生成li
let li=document.createElement("li");
Object.assign(li.style,{
padding:'3px 0px 3px 10px',
color:'#333333',
whiteSpace:'nowrap',
})
li.textContent=item;
parent.appendChild(li);
if(index===0) this.elem.children[n].firstElementChild.firstElementChild.textContent=item;// 设置菜单的默认选项
})
this.elem.children[n].lastElementChild.appendChild(parent);// 将创建好的li,放到对应菜单的ul中
}
}
JS代码22行,转换数据用到的组件
export default class QueryString {
static hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
static parse(qs, sep, eq, options) {
sep = sep || '&';
eq = eq || '=';
var obj = {};
if (typeof qs !== 'string' || qs.length === 0) {
return obj;
}
var regexp = /\+/g;
qs = qs.split(sep);
var maxKeys = 1000;
if (options && typeof options.maxKeys === 'number') {
maxKeys = options.maxKeys;
}
var len = qs.length;
// maxKeys <= 0 means that we should not limit keys count
if (maxKeys > 0 && len > maxKeys) {
len = maxKeys;
}
for (var i = 0; i < len; ++i) {
var x = qs[i].replace(regexp, '%20'),
idx = x.indexOf(eq),
kstr, vstr, k, v;
if (idx >= 0) {
kstr = x.substr(0, idx);
vstr = x.substr(idx + 1);
} else {
kstr = x;
vstr = '';
}
k = decodeURIComponent(kstr);
v = decodeURIComponent(vstr);
if (!QueryString.hasOwnProperty(obj, k)) {
obj[k] = v;
} else if (Array.isArray(obj[k])) {
obj[k].push(v);
} else {
obj[k] = [obj[k], v];
}
}
return obj;
}
static stringify(obj, sep, eq, name) {
sep = sep || '&';
eq = eq || '=';
if (obj === null) {
obj = undefined;
}
if (typeof obj === 'object') {
return Object.keys(obj).map(function (k) {
var ks = encodeURIComponent(QueryString.stringifyPrimitive(k)) + eq;
if (Array.isArray(obj[k])) {
return obj[k].map(function (v) {
return ks + encodeURIComponent(QueryString.stringifyPrimitive(v));
}).join(sep);
} else {
return ks + encodeURIComponent(QueryString.stringifyPrimitive(obj[k]));
}
}).join(sep);
}
if (!name) return '';
return encodeURIComponent(QueryString.stringifyPrimitive(name)) + eq +
encodeURIComponent(QueryString.stringifyPrimitive(obj));
}
static stringifyPrimitive(v) {
switch (typeof v) {
case 'string':
return v;
case 'boolean':
return v ? 'true' : 'false';
case 'number':
return isFinite(v) ? v : '';
default:
return '';
}
};
}
Node.js
var querystring=require("querystring");
var server=http.createServer(function(req,res){
var data="";
res.writeHead(200,{
"content-type":"text/html;charset=utf-8",
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Headers":"*",// 请求头跨域 如果请求头发生修改并且非同源,就需要申请请求头跨域
});
req.on("data",function(_data){
data+=_data;
});
req.on("end",function(){ // 传输数据完成时触发
// type是接口名称
var type=req.url.trim().split("?")[0].replace(/\//g,"");
// reg.url的值即为网址IP+端口后面的内容,例:10.9.72.236:4010/project/?... reg.url的值即为 /project/?/...
if(req.method.toLowerCase()==="get"){
if(req.url.includes("favicon.ico")) return res.end();//如果get请求的是图标直接返回空跳出
// 如果是get请求,就从url中重新获取数据存入data变量
data=req.url.includes("?") ? req.url.split("?")[1] : "";
}
try{ // 首先判断是否可以通过JSON解析,如果通过JSON直接转换,如果不能就是querystring解析
data=JSON.parse(data);
}catch(e){
data=data ? querystring.parse(data) : {};
}
console.log(data);
var o={}
switch(type){
case "province":
o=Object.keys(obj);
break;
case "city":
o=Object.keys(obj[data["city"]]);
break;
case "county":
o=obj[data["city"]][data["county"]];
break;
}
res.write(JSON.stringify(o));
res.end();
})
});
server.listen(4008,"10.9.72.222",function(){
console.log("服务器开启成功");
})
接口文档
级联式菜单接口
URL :http://10.9.72.222:4010
Method :GET
DataType :
请求消息: QueryString
响应消息: JSON
接口名称
1、
?type=province
?type=city
?type=county
2、
http://10.9.72.222:4010/province/
http://10.9.72.222:4010/city/
http://10.9.72.222:4010/county/
3、当使用Post发送时带入type类型判断
获取所有的省份
接口名 province
请求 :无
响应 :[“北京”,“天津”,“河北”,“山西”,“内蒙古”,…]
获取某省下所有的市
接口名 city
请求 :{city:‘省份名’}
响应 :[“石家庄”,“唐山”,“秦皇岛”,“邯郸”,“邢台”,…]
获取某市下所有的县区
接口名 county
请求 :{city:‘省份名’, county:‘城市名’}
响应 : [“东城区”,“西城区”,“宣武区”,“朝阳区”,…]
JSON文件,因为JSON文件过大,所以截图看下结构是怎样的就行
上一篇: Mxnet练习: ResNet网络
下一篇: 求1-n之间的所有素数