頁設(shè)計(jì)期末作業(yè)全攻略:從站點(diǎn)規(guī)劃到購物車交互一次講透)
做網(wǎng)頁設(shè)計(jì)期末大作業(yè)這件事每年期末都有大批學(xué)生卡在同一個(gè)地方題目看著不難比如水果超市網(wǎng)站設(shè)計(jì)六張頁面、HTMLCSSJavaScript、用DW交稿但真動(dòng)手以后才發(fā)現(xiàn)——要么布局怎么調(diào)都不對(duì)要么交互效果做不出來要么做完一檢查漏洞百出最后交上去的作業(yè)自己都不好意思看。這篇文章我就以水果超市6頁這個(gè)題目為例從拿到題目開始到最終提交完整拆一遍應(yīng)該怎么規(guī)劃、怎么寫代碼、怎么調(diào)試以及哪些細(xì)節(jié)是評(píng)分老師真正在意的點(diǎn)。內(nèi)容覆蓋站點(diǎn)結(jié)構(gòu)設(shè)計(jì)、全局CSS、首頁輪播、商品列表與詳情、購物車JS邏輯、登錄表單校驗(yàn)、提交前自檢這些環(huán)節(jié)適合正在做web課程設(shè)計(jì)的學(xué)生參考也適合想系統(tǒng)補(bǔ)一遍網(wǎng)頁三件套基礎(chǔ)的同學(xué)當(dāng)復(fù)盤材料。1. 拿到水果超市題目先搞清楚評(píng)分老師在找什么1.1 評(píng)審視角下的四個(gè)得分維度先說個(gè)很多學(xué)生容易忽略的事實(shí)網(wǎng)頁設(shè)計(jì)作業(yè)的評(píng)分標(biāo)準(zhǔn)和商業(yè)網(wǎng)站不完全一樣老師打分時(shí)看重的不是功能有多復(fù)雜而是以下幾個(gè)維度。第一是完整性。標(biāo)題明確寫了6頁那就必須有6個(gè)HTML文件少一頁直接扣分。而且每頁之間要通過導(dǎo)航欄或鏈接串聯(lián)起來不能是六個(gè)孤立文件。第二是規(guī)范性。HTML結(jié)構(gòu)是否語義化CSS是否單獨(dú)寫在外部樣式表里JavaScript是否獨(dú)立成文件或用合理的方式內(nèi)嵌命名是否清晰注釋是否到位。這些都屬于看起來像專業(yè)開發(fā)人員寫的東西的范疇。第三是功能性。JavaScript部分有沒有實(shí)際能跑的交互——輪播、菜單切換、表單驗(yàn)證、購物車計(jì)算任意兩到三個(gè)就能滿足要求。只寫靜態(tài)頁面沒有JS基本告別高分段。第四是視覺完成度。顏色是否統(tǒng)一間距是否舒服hover有沒有反饋圖片是否清晰整體能不能看出設(shè)計(jì)過。這決定了作業(yè)是及格還是優(yōu)秀的分水嶺。1.2 六頁站點(diǎn)地圖不是隨便湊六個(gè)頁面是邏輯閉環(huán)拿水果超市這個(gè)題目來說六頁的分配可以這樣規(guī)劃index.html首頁品牌banner、輪播圖、熱門推薦、樓層導(dǎo)購、促銷活動(dòng)任務(wù)是吸引用戶點(diǎn)進(jìn)下一層。products.html商品列表頁展示全部分類下的水果商品卡片帶分類篩選和排序。detail.html商品詳情頁單款水果的大圖、簡介、價(jià)格、規(guī)格選擇、加入購物車按鈕是從列表頁到?jīng)Q策頁的過渡。cart.html購物車頁展示已加入的商品、數(shù)量增減、自動(dòng)計(jì)算總價(jià)、模擬結(jié)算流程。login.html登錄/注冊(cè)頁表單加校驗(yàn)驗(yàn)證用戶名、密碼格式可加驗(yàn)證碼倒計(jì)時(shí)之類的小交互。about.html關(guān)于我們/水果知識(shí)頁品牌故事、水果營養(yǎng)知識(shí)順便展示圖片和排版能力。這六頁的邏輯是一條完整的用戶動(dòng)線進(jìn)店首頁→ 逛貨架列表→ 看商品詳情→ 加入購物車購物車→ 登錄結(jié)算登錄頁→ 了解品牌關(guān)于頁。評(píng)閱老師打開任何一個(gè)頁面都能通過導(dǎo)航跳到其他頁面這實(shí)現(xiàn)的是網(wǎng)購商城的基本信息架構(gòu)。在動(dòng)手寫代碼之前強(qiáng)烈建議用一張紙把上面這個(gè)關(guān)系和每個(gè)頁面的模塊清單畫出來。這一步看著浪費(fèi)時(shí)間實(shí)際能省下后面反復(fù)改結(jié)構(gòu)的功夫。1.3 開發(fā)工具用DW還是HBuilder標(biāo)題里帶DW這里就多說一句。Dreamweaver在設(shè)計(jì)網(wǎng)頁作業(yè)場景下依然能用它的實(shí)時(shí)視圖和站點(diǎn)管理功能對(duì)新手友好。但如果你手里一臺(tái)電腦能裝其他軟件我更推薦HBuilder或VS Code原因有三個(gè)代碼補(bǔ)全更智能寫類名和標(biāo)簽時(shí)的效率高很多內(nèi)置的本地預(yù)覽服務(wù)器比如HBuilder的運(yùn)行到瀏覽器不會(huì)出現(xiàn)file協(xié)議下的路徑跨域問題控制臺(tái)報(bào)錯(cuò)信息更完整排查JS錯(cuò)誤時(shí)能省不少時(shí)間。工具本身不影響成績交上去的是代碼不是截圖所以挑自己順手的就行。2. 先寫全局CSS風(fēng)格統(tǒng)一比單個(gè)頁面驚艷更重要很多新手習(xí)慣一頁一頁寫樣式結(jié)果首頁一個(gè)色調(diào)、列表頁又一個(gè)色調(diào)放在一起像拼盤。正確做法是先建一個(gè)全局樣式表我習(xí)慣命名為style.css或common.css把整個(gè)站點(diǎn)的視覺規(guī)范定下來其他頁面都引用它。2.1 用CSS變量統(tǒng)一主題色水果超市這個(gè)品類天然適合綠色系——新鮮、健康、有食欲。用CSS變量定義主題色后續(xù)所有頁面的顏色都用var()引用想換配色時(shí)只需要改一處。:root { --primary: #43a047; /* 主色新鮮綠 */ --primary-dark: #2e7d32; /* 深色用于導(dǎo)航、頁腳 */ --primary-light: #66bb6a; /* 淺色用于hover背景 */ --accent: #ff9800; /* 強(qiáng)調(diào)色促銷標(biāo)簽、價(jià)格 */ --bg: #f7f8f5; /* 頁面背景 */ --card-bg: #ffffff; /* 卡片背景 */ --text-main: #333333; /* 主文字 */ --text-sub: #757575; /* 次要文字 */ --radius: 10px; /* 統(tǒng)一圓角 */ --shadow: 0 2px 12px rgba(0,0,0,0.08); /* 卡片陰影 */ }定義好之后寫按鈕、卡片、導(dǎo)航的背景色和文字色時(shí)全用變量。這樣假如老師提了一句換成暖色調(diào)更有食欲你只需要把--primary改成橙紅色系幾分鐘完成全局換膚。2.2 重置瀏覽器默認(rèn)樣式瀏覽器自帶的body有8px外邊距ul有默認(rèn)圓點(diǎn)a有下劃線這些默認(rèn)樣式會(huì)讓你的頁面在不同瀏覽器里長不一樣。因此全局樣式第一段就應(yīng)該做基礎(chǔ)重置。* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: PingFang SC, Microsoft YaHei, sans-serif; background: var(--bg); color: var(--text-main); line-height: 1.6; } ul { list-style: none; } a { text-decoration: none; color: inherit; } img { display: block; max-width: 100%; } .container { width: 1200px; max-width: 100%; margin: 0 auto; padding: 0 15px; }這里的.container是全局通用的容器類各頁面內(nèi)容區(qū)都用它包裹保證頁面兩側(cè)留白一致。box-sizing: border-box這個(gè)屬性尤其重要沒有它一個(gè)設(shè)定寬度的盒子加了padding后實(shí)際寬度會(huì)超出預(yù)期布局亂掉的常見原因就在這。2.3 導(dǎo)航欄和頁腳六張頁面長一個(gè)樣評(píng)分老師拿到作業(yè)后第一件事是挨個(gè)打開頁面這時(shí)導(dǎo)航欄和頁腳是否完全一致直接影響網(wǎng)站整體性的印象分。所以這兩個(gè)組件必須在全局CSS里統(tǒng)一定義好。導(dǎo)航的結(jié)構(gòu)推薦這樣寫header classsite-header div classcontainer nav-wrap div classlogo a hrefindex.html img srcimages/logo.png alt鮮果超市 /a /div nav classmain-nav ul li classactivea hrefindex.html首頁/a/li lia hrefproducts.html全部水果/a/li lia hrefabout.html關(guān)于我們/a/li lia hreflogin.html登錄/a/li lia hrefcart.html購物車/a/li /ul /nav div classheader-search input typetext placeholder搜索水果... button搜索/button /div /div /header導(dǎo)航的高亮當(dāng)前頁是區(qū)分頁面的關(guān)鍵。當(dāng)前頁對(duì)應(yīng)的li加上active類顏色用主色調(diào)加粗其他頁的鏈接保持普通樣式。頁腳部分包含三列信息品牌介紹文字、快捷鏈接列表、聯(lián)系方式可以寫400電話這類模擬信息底部再放一行版權(quán)聲明。注意版權(quán)聲明里的年份用JS動(dòng)態(tài)獲取這是一個(gè)很多人沒想到、但老師會(huì)注意到的小細(xì)節(jié)document.getElementById(year).textContent new Date().getFullYear();2.4 一組通用按鈕和卡片樣式水果超市的交互以查看詳情加入購物車結(jié)算為主所以按鈕樣式必須全局統(tǒng)一。我建議定義兩個(gè)類.btn是基礎(chǔ)按鈕.btn-primary是主按鈕.btn-outline是描邊按鈕。.btn { display: inline-block; padding: 10px 24px; border: none; border-radius: 50px; font-size: 14px; cursor: pointer; transition: all 0.3s ease; text-align: center; } .btn-primary { background: var(--primary); color: #fff; } .btn-primary:hover { background: var(--primary-dark); transform: translateY(-2px); box-shadow: 0 6px 16px rgba(67, 160, 71, 0.35); } .btn-outline { background: transparent; color: var(--primary); border: 2px solid var(--primary); } .btn-outline:hover { background: var(--primary-light); color: #fff; }商品卡片是列表頁、首頁推薦區(qū)共用的元素同樣在全局CSS里定義好避免每個(gè)頁面重復(fù)寫。.card { background: var(--card-bg); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; transition: transform 0.3s ease, box-shadow 0.3s ease; } .card:hover { transform: translateY(-6px); box-shadow: 0 12px 24px rgba(0,0,0,0.12); }2.5 CSS漸變和文本溢出省略的使用做促銷標(biāo)簽時(shí)CSS漸變能做出比純色更有質(zhì)感的視覺效果。比如限時(shí)特惠標(biāo)簽用漸變背景更醒目.tag-hot { background: linear-gradient(135deg, #ff5722, #ff9800); color: #fff; font-size: 12px; padding: 4px 10px; border-radius: 20px; }商品標(biāo)題如果太長不要讓它換行撐亂卡片用單行溢出省略處理.card-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }這兩處都是高頻用到的小技巧很多初學(xué)者不知道text-overflow要和white-space、overflow配合才生效單獨(dú)寫一個(gè)屬性是沒用的。3. 首頁的視覺錨點(diǎn)輪播圖讓作品第一眼像樣首頁是網(wǎng)站的門面。打開首頁的視覺效果直接決定了老師對(duì)這個(gè)作業(yè)的初始印象分。而首頁里最能撐場面的組件就是首屏的大尺寸輪播圖。3.1 先說清輪播圖的實(shí)現(xiàn)原理輪播圖本質(zhì)上是一個(gè)視口容器、一組水平排列的slide、一段控制位移的JavaScript。每切換一次就是讓slide數(shù)組的父容器往左平移一個(gè)slide寬度。平移用CSS的transform: translateX()實(shí)現(xiàn)性能最好配合transition: 0.5s就能獲得平滑動(dòng)畫。很多學(xué)生一上來就想用jQuery插件或Swiper交作業(yè)當(dāng)然可以但如果能在純JavaScript層面把它實(shí)現(xiàn)出來代碼量不大又能體現(xiàn)你對(duì)DOM操作和事件處理的理解平時(shí)分和答辯時(shí)的印象分會(huì)明顯更好。3.2 一個(gè)自帶指示器和小箭頭的完整輪播HTML結(jié)構(gòu)div classbanner div classbanner-viewport ul classbanner-slides idbannerSlides li classslideimg srcimages/banner1.jpg alt當(dāng)季鮮果推薦/li li classslideimg srcimages/banner2.jpg alt進(jìn)口水果專區(qū)/li li classslideimg srcimages/banner3.jpg alt會(huì)員日優(yōu)惠/li /ul /div button classbanner-arrow prev idprevBtn#10094;/button button classbanner-arrow next idnextBtn#10095;/button ul classbanner-dots idbannerDots li classactive/li li/li li/li /ul /divCSS關(guān)鍵部分.banner { position: relative; overflow: hidden; border-radius: 0 0 20px 20px; } .banner-viewport { width: 100%; height: 420px; overflow: hidden; } .banner-slides { display: flex; width: 100%; height: 100%; transition: transform 0.5s ease; } .slide { flex-shrink: 0; width: 100%; height: 100%; } .slide img { width: 100%; height: 100%; object-fit: cover; }注意flex-shrink: 0這個(gè)細(xì)節(jié)它保證每個(gè)slide不會(huì)在flex容器里被壓縮是輪播能正常橫排的關(guān)鍵。JavaScript核心邏輯let currentIndex 0; const slides document.querySelectorAll(.slide); const dots document.querySelectorAll(.banner-dots li); const slidesWrap document.getElementById(bannerSlides); const totalSlides slides.length; let timer null; function goToSlide(index) { if (index 0) { index totalSlides - 1; } else if (index totalSlides) { index 0; } currentIndex index; slidesWrap.style.transform translateX(- currentIndex * 100 %); updateDots(); } function updateDots() { dots.forEach((dot, i) { dot.classList.toggle(active, i currentIndex); }); } function nextSlide() { goToSlide(currentIndex 1); } function prevSlide() { goToSlide(currentIndex - 1); } document.getElementById(nextBtn).addEventListener(click, function() { nextSlide(); resetAutoPlay(); }); document.getElementById(prevBtn).addEventListener(click, function() { prevSlide(); resetAutoPlay(); }); dots.forEach((dot, index) { dot.addEventListener(click, function() { goToSlide(index); resetAutoPlay(); }); }); function startAutoPlay() { timer setInterval(nextSlide, 3000); } function resetAutoPlay() { clearInterval(timer); startAutoPlay(); } startAutoPlay();這里有一點(diǎn)值得注意translateX用百分比而不是固定像素值這樣不管瀏覽器窗口怎么縮放每個(gè)slide平移的距離都等于自身寬度輪播不會(huì)錯(cuò)位。這個(gè)寫法的健壯性比寫死像素值好很多。3.3 一個(gè)錦上添花的做法鼠標(biāo)懸停暫停輪播評(píng)分老師在演示時(shí)鼠標(biāo)一定會(huì)在頁面上移動(dòng)如果輪播遇到鼠標(biāo)懸停能暫??雌饋頃?huì)非常專業(yè)。實(shí)現(xiàn)非常簡單在banner容器上監(jiān)聽mouseenter和mouseleave即可const banner document.querySelector(.banner); banner.addEventListener(mouseenter, function() { clearInterval(timer); timer null; }); banner.addEventListener(mouseleave, function() { startAutoPlay(); });3.4 首頁的樓層導(dǎo)購錨點(diǎn)跳轉(zhuǎn)加上吸頂導(dǎo)航首屏輪播下面可以加精選水果進(jìn)口專區(qū)時(shí)令促銷三個(gè)樓層模塊。每個(gè)樓層的左側(cè)或頂部放一個(gè)樓層標(biāo)題點(diǎn)擊右側(cè)樓層導(dǎo)航里的對(duì)應(yīng)條目頁面平滑滾動(dòng)到對(duì)應(yīng)區(qū)域。平滑滾動(dòng)的實(shí)現(xiàn)有兩種方式。一種是用CSS的scroll-behavior: smooth在html上設(shè)置然后a標(biāo)簽href直接指向錨點(diǎn)id零代碼實(shí)現(xiàn)滾動(dòng)動(dòng)畫html { scroll-behavior: smooth; }另一種是用JS的window.scrollTo配合behavior: smooth參數(shù)適合需要在滾動(dòng)前后執(zhí)行其他邏輯的場景。這一整套首頁下來就有了視覺重點(diǎn)輪播、商品展示樓層、輔助導(dǎo)航小箭頭和錨點(diǎn)首屏內(nèi)容非常充實(shí)。4. 商品列表與詳情頁網(wǎng)購商城的核心交互體驗(yàn)4.1 列表頁的分類篩選用data屬性做最簡單可靠的數(shù)據(jù)篩選商品列表頁的篩選功能很多學(xué)生第一反應(yīng)就是每個(gè)分類做一套標(biāo)簽/循環(huán)其實(shí)用data屬性做篩選是工作中最常用的輕量方案它把分類信息直接掛在商品卡片上點(diǎn)擊按鈕時(shí)遍歷卡片按data值決定顯示還是隱藏。HTML部分div classfilter-bar button classfilter-btn active>const filterBtns document.querySelectorAll(.filter-btn); const productCards document.querySelectorAll(.product-card); filterBtns.forEach(function(btn) { btn.addEventListener(click, function() { filterBtns.forEach(function(item) { item.classList.remove(active); }); this.classList.add(active); const filterValue this.getAttribute(data-filter); productCards.forEach(function(card) { if (filterValue all || card.getAttribute(data-category) filterValue) { card.style.display block; } else { card.style.display none; } }); }); });4.2 列表和網(wǎng)格視圖切換商品列表頁的另一個(gè)實(shí)用交互是列表視圖和網(wǎng)格視圖切換。網(wǎng)格視圖是默認(rèn)的商品卡片橫排列表視圖則是每個(gè)商品占一整行圖在左、信息在右。實(shí)現(xiàn)方式不復(fù)雜在同結(jié)構(gòu)的基礎(chǔ)上給容器換一個(gè)類名CSS里為不同類名下的卡片定義不同布局。比如容器上有g(shù)rid-view時(shí)卡片豎排三列容器變成list-view時(shí)卡片變橫排行。切換類名的本質(zhì)是CSS在不同上下文下的樣式覆蓋。.product-grid.grid-view .product-card { width: 23%; /* 豎排卡片樣式 */ } .product-grid.list-view .product-card { width: 100%; display: flex; align-items: center; /* 橫排樣式 */ }JS只要切換product-grid的class再配合一個(gè)小的過渡動(dòng)畫效果就非常顯眼。4.3 加入購物車的即時(shí)反饋設(shè)計(jì)列表頁里每個(gè)商品卡片都放一個(gè)加入購物車按鈕。點(diǎn)擊后的反饋不能只是數(shù)據(jù)變了用戶看不到反饋就等于沒生效。我實(shí)操中覺得最順手的反饋組合有這么幾種按鈕文字瞬間變?yōu)橐鸭尤?1.5秒后恢復(fù)右上角購物車圖標(biāo)上的數(shù)字1帶一個(gè)彈跳動(dòng)效頁面右下角浮出一個(gè)小toast提示已成功加入購物車。這三個(gè)效果都不復(fù)雜卻能極大提升交互完成度。toast的實(shí)現(xiàn)方法是創(chuàng)建一個(gè)絕對(duì)定位的div用setTimeout控制它在2秒后漸隱并移除function showToast(message) { let toast document.createElement(div); toast.className toast; toast.textContent message; document.body.appendChild(toast); setTimeout(function() { toast.classList.add(show); }, 10); setTimeout(function() { toast.classList.remove(show); setTimeout(function() { document.body.removeChild(toast); }, 300); }, 2000); }CSS里讓.toast默認(rèn)透明、固定在右下角加上transition實(shí)現(xiàn)淡入淡出效果很自然。4.4 詳情頁的圖片放大鏡一個(gè)能加分的視覺交互商品詳情頁是學(xué)生作業(yè)里最容易做得空的一頁因?yàn)閮?nèi)容結(jié)構(gòu)簡單就一張大圖加幾行文字。但加一個(gè)鼠標(biāo)移入放大的效果電商里叫放大鏡頁面檔次立刻不一樣。原理其實(shí)只有三步在原圖上監(jiān)聽mousemove根據(jù)鼠標(biāo)位置計(jì)算放大區(qū)域的位置百分比把大圖背景的background-position按比例移動(dòng)。const smallImg document.querySelector(.detail-img); const zoomBox document.querySelector(.zoom-box); smallImg.addEventListener(mousemove, function(e) { const rect smallImg.getBoundingClientRect(); const x e.clientX - rect.left; const y e.clientY - rect.top; const percentX (x / rect.width) * 100; const percentY (y / rect.height) * 100; zoomBox.style.backgroundPosition percentX % percentY %; zoomBox.style.display block; }); smallImg.addEventListener(mouseleave, function() { zoomBox.style.display none; });如果覺得實(shí)現(xiàn)放大鏡有點(diǎn)難度詳情頁的備選方案是做圖片縮略圖切換——下方一排小圖點(diǎn)擊后上方大圖跟著切換。這個(gè)用JS也很容易實(shí)現(xiàn)而且同樣能體現(xiàn)交互感。5. 購物車頁面的純?cè)鶭S狀態(tài)管理不靠框架也能能買能算購物車是整個(gè)網(wǎng)站里JavaScript含量最高的一頁也是最能體現(xiàn)編程能力的地方。不用Vue或React純?cè)鶭S也一樣能做出完整可用的購物車邏輯。5.1 數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)數(shù)組加對(duì)象購物車的狀態(tài)就是一組數(shù)據(jù)。我把每個(gè)商品抽象成一個(gè)對(duì)象let cartItems [ { id: 1, name: 贛南臍橙, price: 12.8, count: 2, checked: true }, { id: 2, name: 海南貴妃芒, price: 19.9, count: 1, checked: false } ];id用于唯一標(biāo)識(shí)每個(gè)商品name和price用于展示count記錄購買數(shù)量checked標(biāo)記是否勾選。后續(xù)所有頁面交互——數(shù)量加減、刪除、勾選、總價(jià)計(jì)算——都圍繞這個(gè)數(shù)組展開。5.2 渲染函數(shù)數(shù)據(jù)變一次頁面就重畫一次購物車頁面的核心原則是數(shù)據(jù)驅(qū)動(dòng)視圖——數(shù)據(jù)變了頁面就重新渲染。我習(xí)慣寫一個(gè)renderCart()函數(shù)每次數(shù)據(jù)有變化就調(diào)用它用數(shù)組map或循環(huán)生成HTML字符串再賦值給tbody的innerHTML。function renderCart() { const tbody document.getElementById(cartBody); let html ; cartItems.forEach(function(item) { html tr tdinput typecheckbox classitem-check>function updateTotal() { let total 0; let selectedCount 0; cartItems.forEach(function(item) { if (item.checked) { total item.price * item.count; selectedCount item.count; } }); document.getElementById(totalPrice).textContent ¥ total.toFixed(2); document.getElementById(selectedCount).textContent selectedCount; }事件綁定用的是事件委托機(jī)制。所謂事件委托就是不給每個(gè)加減按鈕單獨(dú)綁定而是利用DOM事件冒泡讓父容器統(tǒng)一處理。這樣做的好處是即使以后商品數(shù)量變化、DOM結(jié)構(gòu)重新渲染事件也不會(huì)丟失。document.getElementById(cartBody).addEventListener(click, function(e) { const target e.target; const id parseInt(target.getAttribute(data-id)); if (!isNaN(id)) { if (target.classList.contains(plus)) { changeCount(id, 1); } else if (target.classList.contains(minus)) { changeCount(id, -1); } else if (target.classList.contains(btn-delete)) { deleteItem(id); } } });5.3 數(shù)量加減和刪除不可變更新加減和刪除的邏輯就是對(duì)數(shù)組做操作然后再渲染。function changeCount(id, delta) { const item cartItems.find(function(item) { return item.id id; }); if (item) { item.count delta; if (item.count 1) { item.count 1; // 最少不能為0 } } renderCart(); } function deleteItem(id) { cartItems cartItems.filter(function(item) { return item.id ! id; }); renderCart(); }這里給count加了最小值1的判斷防止用戶一路減到0甚至負(fù)數(shù)。這是一個(gè)非常實(shí)際的邊界情況作業(yè)里處理好了會(huì)顯得很嚴(yán)謹(jǐn)。5.4 全選與反選全選按鈕的邏輯也很直白點(diǎn)擊全選時(shí)如果當(dāng)前不是全選狀態(tài)就把所有商品的checked設(shè)為true如果已經(jīng)是全選就全部設(shè)為false。同時(shí)全選框的狀態(tài)要在每次渲染時(shí)根據(jù)當(dāng)前數(shù)據(jù)重新計(jì)算保證顯示和實(shí)際數(shù)據(jù)一致。document.getElementById(selectAll).addEventListener(change, function() { cartItems.forEach(function(item) { item.checked this.checked; }, this); renderCart(); });5.5 localStorage持久化刷新不丟數(shù)據(jù)購物車刷新就清空了對(duì)于作業(yè)來說能用但如果你想讓頁面看起來更完整加上localStorage持久化是很加分的。思路是在數(shù)據(jù)變化的每個(gè)操作后面都調(diào)用一個(gè)saveCart()函數(shù)把cartItems存進(jìn)localStorage頁面加載時(shí)再讀出來初始化。function saveCart() { localStorage.setItem(fruitCart, JSON.stringify(cartItems)); } function loadCart() { const saved localStorage.getItem(fruitCart); if (saved) { cartItems JSON.parse(saved); } }在changeCount、deleteItem等數(shù)據(jù)變化的函數(shù)內(nèi)部調(diào)用saveCart()然后loadCart()放在頁面初始化的地方執(zhí)行。這個(gè)方案沒有引入任何復(fù)雜的依賴但對(duì)數(shù)據(jù)持久化的表達(dá)能力比純頁面狀態(tài)高一個(gè)臺(tái)階。6. 登錄頁的表單校驗(yàn)三個(gè)小細(xì)節(jié)把交互體驗(yàn)做滿第六頁登錄/注冊(cè)頁是表單交互的展示窗口。一個(gè)完整的表單校驗(yàn)需要兼顧即時(shí)反饋和提交時(shí)的整體校驗(yàn)。6.1 即時(shí)校驗(yàn)與提交校驗(yàn)的配合我的習(xí)慣是blur事件做單項(xiàng)校驗(yàn)鼠標(biāo)移開輸入框立刻出結(jié)果submit事件做整體校驗(yàn)有錯(cuò)就不允許提交。單項(xiàng)校驗(yàn)的結(jié)果通過一個(gè)span放在輸入框下方展示不通過時(shí)文字變紅輸入框邊框也變紅。以手機(jī)號(hào)校驗(yàn)為例document.getElementById(phone).addEventListener(blur, function() { validatePhone(); }); function validatePhone() { const phone document.getElementById(phone).value.trim(); const msg document.getElementById(phoneMsg); const pattern /^1[3-9]\d{9}$/; if (!phone) { msg.textContent 手機(jī)號(hào)不能為空; return false; } else if (!pattern.test(phone)) { msg.textContent 請(qǐng)輸入正確的手機(jī)號(hào)碼; return false; } else { msg.textContent ; return true; } }6.2 密碼強(qiáng)度檢測(cè)密碼強(qiáng)度檢測(cè)是一個(gè)很多人想做但不會(huì)拆解的功能。實(shí)現(xiàn)思路是給密碼框綁定input事件每次輸入時(shí)用多條正則分別判斷長度、小寫字母、大寫字母、數(shù)字、特殊字符然后根據(jù)命中條件數(shù)量顯示弱/中/強(qiáng)。function checkPasswordStrength(pwd) { let score 0; if (pwd.length 8) score; if (/[a-z]/.test(pwd) /[A-Z]/.test(pwd)) score; if (/\d/.test(pwd)) score; if (/[^a-zA-Z0-9]/.test(pwd)) score; const strengthBar document.getElementById(strengthBar); if (score 1) { strengthBar.textContent 弱; strengthBar.className strength weak; } else if (score 2 || score 3) { strengthBar.textContent 中; strengthBar.className strength medium; } else { strengthBar.textContent 強(qiáng); strengthBar.className strength strong; } }6.3 獲取驗(yàn)證碼的60秒倒計(jì)時(shí)登錄頁的獲取驗(yàn)證碼按鈕倒計(jì)時(shí)是一個(gè)很典型的定時(shí)器應(yīng)用let codeBtn document.getElementById(getCode); let countdown 60; let timer null; codeBtn.addEventListener(click, function() { if (timer) return; codeBtn.disabled true; codeBtn.textContent countdown s后重新獲取; timer setInterval(function() { countdown--; codeBtn.textContent countdown s后重新獲取; if (countdown 0) { clearInterval(timer); timer null; countdown 60; codeBtn.disabled false; codeBtn.textContent 獲取驗(yàn)證碼; } }, 1000); });這組定時(shí)器邏輯完整覆蓋了啟動(dòng)、運(yùn)行、歸零恢復(fù)幾個(gè)階段是一個(gè)能獨(dú)立跑通的小組件。6.4 密碼可見性切換密碼框右側(cè)的小眼睛切換明文/密文是用type屬性切換實(shí)現(xiàn)的。默認(rèn)是password點(diǎn)擊后變成text再點(diǎn)變回password。真實(shí)的密碼可見切換還可能涉及光標(biāo)位置問題但作業(yè)場景里用type切換足夠。7. 扣分雷區(qū)與交作業(yè)前的自檢清單7.1 最常見的問題有哪些根據(jù)我的觀察網(wǎng)頁設(shè)計(jì)作業(yè)的扣分往往不是出在不會(huì)做而是出在沒檢查。常見問題大致有問題類型具體表現(xiàn)影響程度圖片外鏈用網(wǎng)絡(luò)圖片鏈接斷網(wǎng)就顯示空白嚴(yán)重路徑錯(cuò)誤images/xxx.jpg寫成image/xxx.jpg嚴(yán)重內(nèi)聯(lián)樣式泛濫每個(gè)標(biāo)簽都寫styleCSS文件形同虛設(shè)較嚴(yán)重JS控制臺(tái)報(bào)錯(cuò)某個(gè)函數(shù)調(diào)用找不到DOM節(jié)點(diǎn)導(dǎo)致后續(xù)代碼不執(zhí)行嚴(yán)重頁面間缺乏導(dǎo)航6頁之間沒有互相跳轉(zhuǎn)的鏈接嚴(yán)重標(biāo)簽嵌套錯(cuò)誤ul直接包div、a標(biāo)簽包a標(biāo)簽中等圖片沒有alt圖片無法加載時(shí)顯示破圖中等命名隨意頁面文件叫aa.html、bb.html中等7.2 提交前15分鐘項(xiàng)目自檢我建議提交前按下面的順序過一遍全程大概15分鐘打開每個(gè)HTML文件按F12打開控制臺(tái)確認(rèn)沒有紅色報(bào)錯(cuò)逐頁點(diǎn)擊所有導(dǎo)航鏈接確認(rèn)6個(gè)頁面都能互相訪問把所有圖片路徑復(fù)制到瀏覽器地址欄里驗(yàn)證一遍確保圖片不依賴網(wǎng)絡(luò)縮小瀏覽器到手機(jī)寬度確認(rèn)頁面沒有大面積橫向溢出用不同瀏覽器打開Chrome和Edge即可確認(rèn)布局沒有太大差異檢查文件名統(tǒng)一用英文小寫加連字符不要有空格和中文確認(rèn)CSS和JS文件是外部文件HTML里只有結(jié)構(gòu)和少量內(nèi)聯(lián)腳本。7.3 如果還有時(shí)間這三點(diǎn)能讓作品再上一個(gè)臺(tái)階第一為頁面添加一個(gè)簡單的favicon.ico標(biāo)簽頁上有一個(gè)小水果圖標(biāo)作品精致度會(huì)明顯提升。第二給商品卡片做一個(gè)售罄的灰化狀態(tài)。把某個(gè)商品標(biāo)記為售罄過濾或點(diǎn)擊時(shí)給出提示這屬于業(yè)務(wù)邏輯層面的思考很能展示綜合能力。第三當(dāng)頁面高度不足時(shí)確保頁腳仍能固定在底部或保持視覺協(xié)調(diào)。簡單方案是body用flex布局讓main區(qū)域flex: 1自動(dòng)撐開。關(guān)于css優(yōu)惠券圓切這種效果其實(shí)就是一個(gè)用徑向漸變?cè)诳ㄆ锹淅锴谐霭雸A鋸齒的視覺技巧。如果促銷卡片上想做出優(yōu)惠券撕邊的感覺可以試試.coupon { background: radial-gradient(circle at 10px 10px, transparent 10px, #fff 11px); border-radius: 8px; padding: 20px; }這是一個(gè)不加分但很有意思的小技巧能做出來會(huì)讓頁面很有設(shè)計(jì)感。最后聊幾句實(shí)操感受做完一個(gè)六頁的完整網(wǎng)站我最深的體會(huì)是網(wǎng)頁設(shè)計(jì)期末作業(yè)真正考察的并不是你會(huì)用多少新特性而是你有沒有把一個(gè)東西從頭到尾完整做出來的能力。很多同學(xué)容易在開頭糾結(jié)——用什么布局方式、要不要學(xué)一下動(dòng)畫庫、要不要引入Bootstrap——其實(shí)這些都是次要的。先把六個(gè)頁面全部做出來把每個(gè)頁面里最重要的一個(gè)核心交互跑通再去談優(yōu)化和加分這個(gè)順序不能反。我是建議第一次做這類作業(yè)時(shí)全程手寫代碼不要復(fù)制模板。模板確實(shí)快但當(dāng)你被布局卡住、報(bào)錯(cuò)報(bào)得一頭霧水的時(shí)候那個(gè)排查問題的過程才是這門課真正留給你的東西。把CSS布局想成一個(gè)貨架擺放問題把JavaScript想成一個(gè)個(gè)事件響應(yīng)和狀態(tài)變化你會(huì)慢慢發(fā)現(xiàn)網(wǎng)頁設(shè)計(jì)這件事跟搭積木很像——有了清晰的規(guī)劃和統(tǒng)一的規(guī)范后面每一塊都能嚴(yán)絲合縫地拼上去。水果超市這個(gè)題目本身不難難的是愿不愿意靜下心把細(xì)節(jié)做扎實(shí)。希望這篇拆解能幫你在交作業(yè)前少走幾段彎路。