1.创意广告牌
.billboard {
position: relative;
background-color: #8e6534;
color: #fff;
padding: 20px;
box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.3);
background-size: cover;
/* TODO:待补充代码 设置圆角 10px,背景图片为woodiness.jpg */
background-image: url(../images/woodiness.jpg);
border-radius: 10px;
}
.top-sign {
position: relative;
width: 200px;
height: 100px;
background-color: #a87f4a;
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
/* TODO:待补充代码 上面两个角是圆角 15px,下面两个角是直角 元素 x 轴倾斜 20度*/
border-radius: 15px 15px 0px 0px ;
transform: skewX(-20deg);
}
2原子化 CSS
/* TODO: 实现原子化 flex */
div {
display: flex;
flex-direction: column;
}
3神秘咒语
TODO:新增或者修改以下代码
key1Button.addEventListener('click', async () => {
// 从后台请求钥匙1的咒语部分
key1Button.disabled = true;
let {data} = await axios.get('/spellone',{
headers:{
'Authorization':'2b58f9a8-7d73-4a9c-b8a2-9f05d6e8e3c7',
}
})
spell1.innerHTML = data;
tryOpenTreasureBox();
});
key2Button.addEventListener('click', async () => {
// 从后台请求钥匙2的咒语部分
key2Button.disabled = true;
let {data} = await axios.get('/spelltwo',{
headers:{
Authorization:'2b58f9a8-7d73-4a9c-b8a2-9f05d6e8e3c7',
}
})
spell2.innerHTML = data;
tryOpenTreasureBox();
});
4. 朋友圈
目标
请在 index.js
文件中补全代码,具体需求如下:
- 请将
debounce
函数补充完整,实现一个延迟为delay
毫秒的防抖函数。 - 用户在输入框(
id=text
)输入文字时,将用户输入的内容存入localStorage
中,缓存的key
名称为savedText
;页面加载时检查localStorage
中是否有缓存文本数据,若有则将输入框(id=text
)内容设置为相应的文本;当用户点击“发表”按钮(id=post
)时,清空输入框(id=text
)中的内容,并将localStorage
内缓存的文本数据移除。- 此阶段的页面效果可以查看
effect-1.gif
文件。
- 此阶段的页面效果可以查看
- 当输入框中没有文字时,将“发表”按钮(
id=post
)的disabled
属性值设置为disabled
;如果输入框中有文字则移除该属性。- 注意:当用户点击“发表”按钮和初次进入页面时也会改变输入框的内容,此时也需要对按钮的情况作出判断。
- 页面最终效果可以查看
effect-2.gif
文件
目标一:防抖函数
function debounce(fn, delay) {
//return fn; // 这一行是为了确保页面正常运行,可以去掉
// TODO: 请实现函数防抖的功能
let timer=null
return function(){
clearTimeout(timer)
timer =setTimeout(fn,delay)
}
}
目标二:
document.addEventListener("DOMContentLoaded", function() {
// TODO: 请在此补充页面加载时缓存检查的代码
let savedText=localStorage.getItem('savedText')
if(savedText){
document.querySelector('#text').value=savedText
}
const content =document.querySelector('#text').value
if(content==''){
document.querySelector('#post').setAttribute('disabled','disabled')
}else{
document.querySelector('#post').removeAttribute('disabled')
}
});
// TODO: 请在此补充用户输入时设置缓存和调整按钮状态的代码
const content =document.querySelector('#text').value
localStorage.setItem('savadText',content)
if(content==''){
document.querySelector('#post').setAttribute('disabled','disabled')
}else{
document.querySelector('#post').removeAttribute('disabled')
}
document.getElementById("post").addEventListener("click", function() {
const content = document.getElementById("text").value;
const element = createContent(content);
document.querySelector(".contents").appendChild(element);
document.getElementById("prompt").textContent = "";
// TODO: 请在此补充用户点击“发表”按钮时清空文本框和缓存的代码
document.querySelector('#text').value=''
localStorage.clear();
document.querySelector('#post').setAttribute('disabled','disabled');
})
5. 美食蛋白质揭秘
目标
找到 index.html
中的 TODO 部分,完成以下目标:
- 在不使用任何第三方库的情况下完成数据请求,请求地址必须使用提供的变量
MockURL
,数据中name
表示食物名称,value
表示蛋白质含量,将数据正确渲染到.protein-container
中,使用.protein-item
元素渲染数据,元素中显示食物名称和蛋白质含量,正确渲染后的 DOM 如下所示:
<div class="protein-container">
<div class="protein-item">鸡胸肉 30</div>
<div class="protein-item">牛肉 26</div>
<!-- 省略代码...... -->
</div>
- 在请求完成后,调用
echartsInit
方法渲染图表,参数data
须是下面的数据结构图标才会正确渲染:
[
{ name: "表头", icon: "none" },
// 原有数据
{ value: 30, name: "鸡胸肉" },
// 省略 .......
];
完成后效果如下:
题解
<div class="protein-container" >
<!-- TODO:待补充代码,渲染获取的数据 -->
<div class="protein-item" v-for="item in proteinList">{{item.name}} {{item.value}}</div>
</div>
async function fetchData() {
// TODO:待补充代码
const data = await fetch(MockURL).then(response=>response.json())
console.log(data);
proteinList.value = data
let obj = { name:'表头',icon:'none' }
const echartsData = Array.from(data)
echartsData.unshift(obj)
echartsInit(echartsData)
}
fetchData()
return {
echartsInit,
proteinList,
};
6. 营业状态切换
function useToggle(state) {
// TODO:待补充代码
// const [state, setState] = useState(initialState);
// return true
const toggle = () => {
setState(!state);
};
// 返回状态和切换状态函数的数组
return [state, toggle];
}
const app = Vue.createApp({
setup() {
const [isWorking, toggleWorking] = useToggle(false) // 使用自定义的useToggle函数创建状态和切换函数
const workImage = './images/open.jpg' // 营业状态的图片路径
const restImage = './images/close.jpg' // 打烊状态的图片路径
return {
isWorking,
toggleWorking,
workImage,
restImage
}
}
});
9. 这是一个“浏览器”
目标
完善 js/index.js
文件,找到其中的 TODO 部分,完成代码,达到以下目标:
-
补全
js/index.js
中的toggleTab
函数,实现当点击标签页时,标签页与其内容页变为选中状态(即标签页加上类名liactive
,内容页加上类名conactive
)。上述且后文描述中的标签页是指
.fisrstnav ul
下的每个li
标签,内容页是指.tabscon
下的每个section
标签。完成效果如下:
-
完善
js/index.js
中的editTab
函数,实现当双击标签页文字或者内容页文字时出现输入框,当输入框失焦时,原标签页文字或内容页文字替换为输入框中输入的值。上述且后文描述中的标签页文字是指类名为
content
的span
标签中的文本;内容页文字是指.tabscon
下的每个section
标签的文本;输入框是指双击后出现的input
标签。完成效果如下:
-
补全
js/index.js
中的addTab
函数,实现当点击.tabadd
时,页面添加新的标签页(即创建一个li
标签作为子元素插入到.firstnav ul
节点下)和内容页(即创建一个section
标签作为子元素插入到.tabscon
节点下),新标签页及其内容页默认是选中状态。标签页及其内容页内容替换规则:
-
标签页的内容分别按照序号 “标签页1、标签页2、标签页3......” 依次递增。
-
内容页的内容分别按照序号 “标签页1的内容、标签页2的内容、标签页3的内容......” 依次递增。
.firstnav ul
的 DOM 结构为:<ul> <li> <span class="content">标签页1</span> <span class="iconfont icon-guanbi"> <span class="glyphicon glyphicon-remove"> </span> </span> </li> <li> <span class="content">标签页2</span> <span class="iconfont icon-guanbi"> <span class="glyphicon glyphicon-remove"> </span> </span> </li> <li> <span class="content">标签页3</span> <span class="iconfont icon-guanbi"> <span class="glyphicon glyphicon-remove"> </span> </span> </li> </ul>
.tabscon
的 DOM 结构为:<div class="tabscon"> <section>标签页1的内容</section> <section>标签页2的内容</section> <section>标签页3的内容</section> </div>
完成效果如下:
- 补全
js/index.js
中的removeTab
函数,实现当点击某个标签页的.icon-guanbi
时,该标签页及其内容页从页面中删除,所有标签页及其内容页的内容仍以目标 3 中的规则开始重新排,标签页的选中状态也随之改变。标签页状态改变规则:
-
若删除的标签页是当前选中的标签页,且非最后一个,则该标签页临近的下一个标签页变为选中状态。
-
若删除的标签页是当前选中的标签页,且为最后一个,则该标签页临近的上一个标签页变为选中状态。
-
若删除的标签页不是当前选中的标签页,则标签页选中状态不变。
完成后效果如下:
题解
目标一
// 1.切换功能
toggleTab(tab) {
// TODO: 添加代码,点击标签页,切换到对应标签页的功能
tab.clearClass();
this.classList.add('liactive')
tab.sections[this.index].classList.add('conactive')
// TODO结束
}
目标二
// TODO:实现双击修改内容,当文本框失焦时,把修改的值赋给被双击的对象,并作上已修改的标记
input.onblur=()=>{
this.innerText=input.value
}
// TODO结束
目标三
addTab(tabObj) {
tabObj.clearClass();
let num = tabObj.lis.length;
let li = `<li class="liactive"><span class="content">标签页${num + 1}</span><span class="iconfont icon-guanbi"><span class="glyphicon glyphicon-remove"></span></span></li>`
let section = `<section class="conactive">标签页${ num + 1 }的内容</section>`
tabObj.ul.insertAdjacentHTML('beforeend', li);
tabObj.fsection.insertAdjacentHTML('beforeend', section);
tabObj.init();
}
目标四
removeTab(tabObj, e) {
e.stopPropagation();
let index = this.parentNode.index, flag = 0;
if(tabObj.lis[index].classList.value){
flag = ((index !== tabObj.lis.length - 1) ? 2 : -1)
}
let nextP = tabObj.lis[index + flag],
nextT = tabObj.sections[index + flag];
tabObj.lis[tabObj.lis.length - 1].remove();
tabObj.sections[tabObj.lis.length - 1].remove();
if(tabObj.lis.length === 1){
tabObj.init();return;
}
nextP ? nextP.className += 'liactive' : '';
nextT ? nextT.className += 'conactive' : '';
tabObj.init();
}
完整代码
"use strict";
class Tab {
// 构造方法
constructor(id) {
// 获取元素
this.main = document.querySelector(id);
this.add = this.main.querySelector(".tabadd");
this.ul = this.main.querySelector(".fisrstnav ul");
this.fsection = this.main.querySelector(".tabscon");
this.init();
}
// 初始化
init() {
this.updateNode();
// init初始化操作让相关元素绑定事件
this.add.onclick = this.addTab.bind(this.add, this);
for (var i = 0; i < this.lis.length; i++) {
this.lis[i].index = i;
this.lis[i].onclick = this.toggleTab.bind(this.lis[i], this);
this.remove[i].onclick = this.removeTab.bind(this.remove[i], this);
this.spans[i].ondblclick = this.editTab;
this.sections[i].ondblclick = this.editTab;
}
}
// 更新所有的li和section
updateNode() {
this.lis = this.main.querySelectorAll("li");
this.remove = this.main.querySelectorAll(".icon-guanbi");
this.sections = this.main.querySelectorAll("section");
this.spans = this.main.querySelectorAll(".content");
}
// 1.切换功能
toggleTab(tab) {
// TODO: 添加代码,点击标签页,切换到对应标签页的功能
tab.clearClass();
this.classList.add('liactive')
tab.sections[this.index].classList.add('conactive')
// TODO结束
}
// 2.清空所有标签页及其内容页类名
clearClass() {
for (var i = 0; i < this.lis.length; i++) {
this.lis[i].className = "";
this.sections[i].className = "";
}
}
addTab(tabObj) {
tabObj.clearClass();
let num = tabObj.lis.length;
let li = `<li class="liactive"><span class="content">标签页${num + 1}</span><span class="iconfont icon-guanbi"><span class="glyphicon glyphicon-remove"></span></span></li>`
let section = `<section class="conactive">标签页${ num + 1 }的内容</section>`
tabObj.ul.insertAdjacentHTML('beforeend', li);
tabObj.fsection.insertAdjacentHTML('beforeend', section);
tabObj.init();
}
removeTab(tabObj, e) {
e.stopPropagation();
let index = this.parentNode.index, flag = 0;
if(tabObj.lis[index].classList.value){
flag = ((index !== tabObj.lis.length - 1) ? 2 : -1)
}
let nextP = tabObj.lis[index + flag],
nextT = tabObj.sections[index + flag];
tabObj.lis[tabObj.lis.length - 1].remove();
tabObj.sections[tabObj.lis.length - 1].remove();
if(tabObj.lis.length === 1){
tabObj.init();return;
}
nextP ? nextP.className += 'liactive' : '';
nextT ? nextT.className += 'conactive' : '';
tabObj.init();
}
// 5.修改功能
editTab() {
var str = this.innerHTML;
window.getSelection
? window.getSelection().removeAllRanges()
: document.Selection.empty();
this.innerHTML = '<input type="text" />';
var input = this.children[0];
input.value = str;
input.select(); //让文本框里的文字处于选定状态
// TODO:实现双击修改内容,当文本框失焦时,把修改的值赋给被双击的对象,并作上已修改的标记
input.onblur=()=>{
this.innerText=input.value
}
// TODO结束
}
}
var tab = new Tab("#tab");