全套筆記資料程式碼移步: 前往gitee倉庫檢視
感興趣的小夥伴可以自取哦,歡迎大家點贊轉發~
全套教程部分目錄:
部分檔案圖片:
什麼是pinia
Pinia 是 Vue 的專屬狀態管理庫,可以實現跨元件或頁面共享狀態,是 vuex 狀態管理工具的替代品,和 Vuex相比,具備以下優勢
- 提供更加簡單的API (去掉了 mutation )
- 提供符合組合式API風格的API (和 Vue3 新語法統一)
- 去掉了modules的概念,每一個store都是一個獨立的模組
- 搭配 TypeScript 一起使用提供可靠的型別推斷
建立空Vue專案並安裝Pinia
1. 建立空Vue專案
npm init vue@latest
2. 安裝Pinia並註冊
npm i pinia
import { createPinia } from 'pinia'
const app = createApp(App)
// 以外掛的形式註冊
app.use(createPinia())
app.use(router)
app.mount('#app')
實現counter
核心步驟:
- 定義store
- 元件使用store
1- 定義store
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCounterStore = defineStore('counter', ()=>{
// 資料 (state)
const count = ref(0)
// 修改資料的方法 (action)
const increment = ()=>{
count.value++
}
// 以物件形式返回
return {
count,
increment
}
})
2- 元件使用store
<script setup>
// 1. 匯入use方法
import { useCounterStore } from '@/stores/counter'
// 2. 執行方法得到store store裡有資料和方法
const counterStore = useCounterStore()
</script>
<template>
<button @click="counterStore.increment">
{{ counterStore.count }}
</button>
</template>
實現getters
getters直接使用計算屬性即可實現
// 資料(state)
const count = ref(0)
// getter (computed)
const doubleCount = computed(() => count.value * 2)
非同步action
思想:action函式既支援同步也支援非同步,和在元件中傳送網路請求寫法保持一致
步驟:
- store中定義action
- 元件中觸發action
1- store中定義action
const API_URL = '
export const useCounterStore = defineStore('counter', ()=>{
// 資料
const list = ref([])
// 非同步action
const loadList = async ()=>{
const res = await axios.get(API_URL)
list.value = res.data.data.channels
}
return {
list,
loadList
}
})
2- 元件中呼叫action
<script setup>
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
// 呼叫非同步action
counterStore.loadList()
</script>
<template>
<ul>
<li v-for="item in counterStore.list" :key="item.id">{{ item.name }}</li>
</ul>
</template>
storeToRefs保持響應式解構
直接基於store進行解構賦值,響應式資料(state和getter)會丟失響應式特性,使用storeToRefs輔助保持響應式
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
// 使用它storeToRefs包裹之後解構保持響應式
const { count } = storeToRefs(counterStore)
const { increment } = counterStore
</script>
<template>
<button @click="increment">
{{ count }}
</button>
</template>
建立專案並整理目錄
npm init vue@latest
![image.png](
jsconfig.json配置別名路徑
配置別名路徑可以在寫程式碼時聯想提示路徑
{
"compilerOptions" : {
"baseUrl" : "./",
"paths" : {
"@/*":["src/*"]
}
}
}
elementPlus引入
1. 安裝elementPlus和自動匯入外掛
npm i elementPlus
npm install -D unplugin-vue-components unplugin-auto-import
2. 配置自動按需匯入
// 引入外掛
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
// 配置外掛
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
]
})
3. 測試元件
<template>
<el-button type="primary">i am button</el-button>
</template>
定製elementPlus主題
1. 安裝sass
基於vite的專案預設不支援css前處理器,需要開發者單獨安裝
npm i sass -D
2. 準備定製化的樣式檔案
/* 只需要重寫你需要的即可 */
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
// 主色
'base': #27ba9b,
),
'success': (
// 成功色
'base': #1dc779,
),
'warning': (
// 警告色
'base': #ffb302,
),
'danger': (
// 危險色
'base': #e26237,
),
'error': (
// 錯誤色
'base': #cf4444,
),
)
)
3. 自動匯入配置
這裡自動匯入需要深入到elementPlus的元件中,按照官方的配置文件來
- 自動匯入定製化樣式檔案進行樣式覆蓋
- 按需定製主題配置 (需要安裝 unplugin-element-plus)
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// 匯入對應包
import ElementPlus from 'unplugin-element-plus/vite'
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
// 按需定製主題配置
ElementPlus({
useSource: true,
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
css: {
preprocessorOptions: {
scss: {
// 自動匯入定製化樣式檔案進行樣式覆蓋
additionalData: `
@use "@/styles/element/index.scss" as *;
`,
}
}
}
})
axios安裝並簡單封裝
1. 安裝axios
npm i axios
2. 基礎配置
官方文件地址:[
基礎配置通常包括:
- 例項化 - baseURL + timeout
- 攔截器 - 攜帶token 401攔截等
import axios from 'axios'
// 建立axios例項
const http = axios.create({
baseURL: '
timeout: 5000
})
// axios請求攔截器
instance.interceptors.request.use(config => {
return config
}, e => Promise.reject(e))
// axios響應式攔截器
instance.interceptors.response.use(res => res.data, e => {
return Promise.reject(e)
})
export default http
3. 封裝請求函式並測試
import http from '@/utils/http'
export function getCategoryAPI () {
return http({
url: 'home/category/head'
})
}
路由整體設計
路由設計原則:找頁面的切換方式,如果是整體切換,則為一級路由,如果是在一級路由的內部進行的內容切換,則為二級路由
<template>
我是登入頁
</template>
<template>
我是首頁
</template>
<template>
我是home
</template>
<template>
我是分類
</template>
// createRouter:建立router例項物件
// createWebHistory:建立history模式的路由
import { createRouter, createWebHistory } from 'vue-router'
import Login from '@/views/Login/index.vue'
import Layout from '@/views/Layout/index.vue'
import Home from '@/views/Home/index.vue'
import Category from '@/views/Category/index.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
// path和component對應關係的位置
routes: [
{
path: '/',
component: Layout,
children: [
{
path: '',
component: Home
},
{
path: 'category',
component: Category
}
]
},
{
path: '/login',
component: Login
}
]
})
export default router
靜態資源引入和Error Lens安裝
1. 靜態資源引入
- 圖片資源 - 把 images 資料夾放到 assets 目錄下
- 樣式資源 - 把 common.scss 檔案放到 styles 目錄下
2. Error Lens外掛安裝
![image.png](
scss變數自動匯入
$xtxColor: #27ba9b;
$helpColor: #e26237;
$sucColor: #1dc779;
$warnColor: #ffb302;
$priceColor: #cf4444;
css: {
preprocessorOptions: {
scss: {
// 自動匯入scss檔案
additionalData: `
@use "@/styles/element/index.scss" as *;
@use "@/styles/var.scss" as *;
`,
}
}
}
元件結構快速搭建
<script setup>
</script>
<template>
<nav class="app-topnav">
<div class="container">
<ul>
<template v-if="true">
<li><a href="javascript:;""><i class="iconfont icon-user"></i>周杰倫</a></li>
<li>
<el-popconfirm title="確認退出嗎?" confirm-button-text="確認" cancel-button-text="取消">
<template #reference>
<a href="javascript:;">退出登入</a>
</template>
</el-popconfirm>
</li>
<li><a href="javascript:;">我的訂單</a></li>
<li><a href="javascript:;">會員中心</a></li>
</template>
<template v-else>
<li><a href="javascript:;">請先登入</a></li>
<li><a href="javascript:;">幫助中心</a></li>
<li><a href="javascript:;">關於我們</a></li>
</template>
</ul>
</div>
</nav>
</template>
<style scoped lang="scss">
.app-topnav {
background: #333;
ul {
display: flex;
height: 53px;
justify-content: flex-end;
align-items: center;
li {
a {
padding: 0 15px;
color: #cdcdcd;
line-height: 1;
display: inline-block;
i {
font-size: 14px;
margin-right: 2px;
}
&:hover {
color: $xtxColor;
}
}
~li {
a {
border-left: 2px solid #666;
}
}
}
}
}
</style>
<script setup>
</script>
<template>
<header class='app-header'>
<div class="container">
<h1 class="logo">
<RouterLink to="/">小兔鮮</RouterLink>
</h1>
<ul class="app-header-nav">
<li class="home">
<RouterLink to="/">首頁</RouterLink>
</li>
<li> <RouterLink to="/">居家</RouterLink> </li>
<li> <RouterLink to="/">美食</RouterLink> </li>
<li> <RouterLink to="/">服飾</RouterLink> </li>
</ul>
<div class="search">
<i class="iconfont icon-search"></i>
<input type="text" placeholder="搜一搜">
</div>
<!-- 頭部購物車 -->
</div>
</header>
</template>
<style scoped lang='scss'>
.app-header {
background: #fff;
.container {
display: flex;
align-items: center;
}
.logo {
width: 200px;
a {
display: block;
height: 132px;
width: 100%;
text-indent: -9999px;
background: url('@/assets/images/logo.png') no-repeat center 18px / contain;
}
}
.app-header-nav {
width: 820px;
display: flex;
padding-left: 40px;
position: relative;
z-index: 998;
li {
margin-right: 40px;
width: 38px;
text-align: center;
a {
font-size: 16px;
line-height: 32px;
height: 32px;
display: inline-block;
&:hover {
color: $xtxColor;
border-bottom: 1px solid $xtxColor;
}
}
.active {
color: $xtxColor;
border-bottom: 1px solid $xtxColor;
}
}
}
.search {
width: 170px;
height: 32px;
position: relative;
border-bottom: 1px solid #e7e7e7;
line-height: 32px;
.icon-search {
font-size: 18px;
margin-left: 5px;
}
input {
width: 140px;
padding-left: 5px;
color: #666;
}
}
.cart {
width: 50px;
.curr {
height: 32px;
line-height: 32px;
text-align: center;
position: relative;
display: block;
.icon-cart {
font-size: 22px;
}
em {
font-style: normal;
position: absolute;
right: 0;
top: 0;
padding: 1px 6px;
line-height: 1;
background: $helpColor;
color: #fff;
font-size: 12px;
border-radius: 10px;
font-family: Arial;
}
}
}
}
</style>
<template>
<footer class="app_footer">
<!-- 聯絡我們 -->
<div class="contact">
<div class="container">
<dl>
<dt>客戶服務</dt>
<dd><i class="iconfont icon-kefu"></i> 線上客服</dd>
<dd><i class="iconfont icon-question"></i> 問題反饋</dd>
</dl>
<dl>
<dt>關注我們</dt>
<dd><i class="iconfont icon-weixin"></i> 公眾號</dd>
<dd><i class="iconfont icon-weibo"></i> 微博</dd>
</dl>
<dl>
<dt>下載APP</dt>
<dd class="qrcode"><img src="images/41474d5f-a7c8-40c2-b60c-14bbbfa30f13-1710675035.jpg" /></dd>
<dd class="download">
<span>掃描二維碼</span>
<span>立馬下載APP</span>
<a href="javascript:;">下載頁面</a>
</dd>
</dl>
<dl>
<dt>服務熱線</dt>
<dd class="hotline">400-0000-000 <small>週一至週日 8:00-18:00</small></dd>
</dl>
</div>
</div>
<!-- 其它 -->
<div class="extra">
<div class="container">
<div class="slogan">
<a href="javascript:;">
<i class="iconfont icon-footer01"></i>
<span>價格親民</span>
</a>
<a href="javascript:;">
<i class="iconfont icon-footer02"></i>
<span>物流快捷</span>
</a>
<a href="javascript:;">
<i class="iconfont icon-footer03"></i>
<span>品質新鮮</span>
</a>
</div>
<!-- 版權資訊 -->
<div class="copyright">
<p>
<a href="javascript:;">關於我們</a>
<a href="javascript:;">幫助中心</a>
<a href="javascript:;">售後服務</a>
<a href="javascript:;">配送與驗收</a>
<a href="javascript:;">商務合作</a>
<a href="javascript:;">搜尋推薦</a>
<a href="javascript:;">友情連結</a>
</p>
<p>CopyRight © 小兔鮮兒</p>
</div>
</div>
</div>
</footer>
</template>
<style scoped lang='scss'>
.app_footer {
overflow: hidden;
background-color: #f5f5f5;
padding-top: 20px;
.contact {
background: #fff;
.container {
padding: 60px 0 40px 25px;
display: flex;
}
dl {
height: 190px;
text-align: center;
padding: 0 72px;
border-right: 1px solid #f2f2f2;
color: #999;
&:first-child {
padding-left: 0;
}
&:last-child {
border-right: none;
padding-right: 0;
}
}
dt {
line-height: 1;
font-size: 18px;
}
dd {
margin: 36px 12px 0 0;
float: left;
width: 92px;
height: 92px;
padding-top: 10px;
border: 1px solid #ededed;
.iconfont {
font-size: 36px;
display: block;
color: #666;
}
&:hover {
.iconfont {
color: $xtxColor;
}
}
&:last-child {
margin-right: 0;
}
}
.qrcode {
width: 92px;
height: 92px;
padding: 7px;
border: 1px solid #ededed;
}
.download {
padding-top: 5px;
font-size: 14px;
width: auto;
height: auto;
border: none;
span {
display: block;
}
a {
display: block;
line-height: 1;
padding: 10px 25px;
margin-top: 5px;
color: #fff;
border-radius: 2px;
background-color: $xtxColor;
}
}
.hotline {
padding-top: 20px;
font-size: 22px;
color: #666;
width: auto;
height: auto;
border: none;
small {
display: block;
font-size: 15px;
color: #999;
}
}
}
.extra {
background-color: #333;
}
.slogan {
height: 178px;
line-height: 58px;
padding: 60px 100px;
border-bottom: 1px solid #434343;
display: flex;
justify-content: space-between;
a {
height: 58px;
line-height: 58px;
color: #fff;
font-size: 28px;
i {
font-size: 50px;
vertical-align: middle;
margin-right: 10px;
font-weight: 100;
}
span {
vertical-align: middle;
text-shadow: 0 0 1px #333;
}
}
}
.copyright {
height: 170px;
padding-top: 40px;
text-align: center;
color: #999;
font-size: 15px;
p {
line-height: 1;
margin-bottom: 20px;
}
a {
color: #999;
line-height: 1;
padding: 0 10px;
border-right: 1px solid #999;
&:last-child {
border-right: none;
}
}
}
}
</style>
<script setup>
import LayoutNav from './components/LayoutNav.vue'
import LayoutHeader from './components/LayoutHeader.vue'
import LayoutFooter from './components/LayoutFooter.vue'
</script>
<template>
<LayoutNav />
<LayoutHeader />
<RouterView />
<LayoutFooter />
</template>
字型圖示渲染
字型圖示採用的是阿里的字型圖示庫,樣式檔案已經準備好,在
index.html
檔案中引入即可
<link rel="stylesheet" href="//at.alicdn.com/t/font_2143783_iq6z4ey5vu.css">
一級導航渲染
![image.png](
實現步驟
- 封裝介面函式
- 呼叫介面函式
- v-for渲染模版
程式碼落地
import httpInstance from '@/utils/http'
export function getCategoryAPI () {
return httpInstance({
url: '/home/category/head'
})
}
<script setup>
import { getCategoryAPI } from '@/apis/layout'
import { onMounted, ref } from 'vue'
const categoryList = ref([])
const getCategory = async () => {
const res = await getCategoryAPI()
categoryList.value = res.result
}
onMounted(() => getCategory())
</script>
<template>
<header class='app-header'>
<div class="container">
<h1 class="logo">
<RouterLink to="/">小兔鮮</RouterLink>
</h1>
<ul class="app-header-nav">
<li class="home" v-for="item in categoryList" :key="item.id">
<RouterLink to="/">{{ item.name }}</RouterLink>
</li>
</ul>
<div class="search">
<i class="iconfont icon-search"></i>
<input type="text" placeholder="搜一搜">
</div>
<!-- 頭部購物車 -->
</div>
</header>
</template>
吸頂導航互動實現
1. 準備元件靜態結構
<script setup>
</script>
<template>
<div class="app-header-sticky">
<div class="container">
<RouterLink class="logo" to="/" />
<!-- 導航區域 -->
<ul class="app-header-nav ">
<li class="home">
<RouterLink to="/">首頁</RouterLink>
</li>
<li>
<RouterLink to="/">居家</RouterLink>
</li>
<li>
<RouterLink to="/">美食</RouterLink>
</li>
<li>
<RouterLink to="/">服飾</RouterLink>
</li>
<li>
<RouterLink to="/">母嬰</RouterLink>
</li>
<li>
<RouterLink to="/">個護</RouterLink>
</li>
<li>
<RouterLink to="/">嚴選</RouterLink>
</li>
<li>
<RouterLink to="/">數碼</RouterLink>
</li>
<li>
<RouterLink to="/">運動</RouterLink>
</li>
<li>
<RouterLink to="/">雜項</RouterLink>
</li>
</ul>
<div class="right">
<RouterLink to="/">品牌</RouterLink>
<RouterLink to="/">專題</RouterLink>
</div>
</div>
</div>
</template>
<style scoped lang='scss'>
.app-header-sticky {
width: 100%;
height: 80px;
position: fixed;
left: 0;
top: 0;
z-index: 999;
background-color: #fff;
border-bottom: 1px solid #e4e4e4;
// 此處為關鍵樣式!!!
// 狀態一:往上平移自身高度 + 完全透明
transform: translateY(-100%);
opacity: 0;
// 狀態二:移除平移 + 完全不透明
&.show {
transition: all 0.3s linear;
transform: none;
opacity: 1;
}
.container {
display: flex;
align-items: center;
}
.logo {
width: 200px;
height: 80px;
background: url("@/assets/images/logo.png") no-repeat right 2px;
background-size: 160px auto;
}
.right {
width: 220px;
display: flex;
text-align: center;
padding-left: 40px;
border-left: 2px solid $xtxColor;
a {
width: 38px;
margin-right: 40px;
font-size: 16px;
line-height: 1;
&:hover {
color: $xtxColor;
}
}
}
}
.app-header-nav {
width: 820px;
display: flex;
padding-left: 40px;
position: relative;
z-index: 998;
li {
margin-right: 40px;
width: 38px;
text-align: center;
a {
font-size: 16px;
line-height: 32px;
height: 32px;
display: inline-block;
&:hover {
color: $xtxColor;
border-bottom: 1px solid $xtxColor;
}
}
.active {
color: $xtxColor;
border-bottom: 1px solid $xtxColor;
}
}
}
</style>
2. 渲染基礎資料
3. 實現吸頂互動
核心邏輯:根據滾動距離判斷當前show類名是否顯示,大於78顯示,小於78,不顯示
<script setup>
import LayoutHeaderUl from './LayoutHeaderUl.vue'
// vueUse
import { useScroll } from '@vueuse/core'
const { y } = useScroll(window)
</script>
<template>
<div class="app-header-sticky" :class="{ show: y > 78 }">
<!-- 省略部分程式碼 -->
</div>
</template>
Pinia最佳化重複請求
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { getCategoryAPI } from '@/apis/layout'
export const useCategoryStore = defineStore('category', () => {
// 導航列表的資料管理
// state 導航列表資料
const categoryList = ref([])
// action 獲取導航資料的方法
const getCategory = async () => {
const res = await getCategoryAPI()
categoryList.value = res.result
}
return {
categoryList,
getCategory
}
})