在 /components
目录下,创建 /navbar/index.vue
组件。
components\navbar\index.vue
<template>
<div class="navbar">
<div class="wrapper content">
navbar
</div>
</div>
</template>
在默认布局 default.vue
中,使用。
layouts\default.vue
<template>
<div class="default-layout">
<!-- header -->
<app-header></app-header>
<!-- navbar -->
<navbar></navbar>
<slot></slot>
<!-- footer -->
<app-footer></app-footer>
</div>
</template>
在 navbar/index.vue
中,分为三部分:
- logo;
- 菜单列表;
- 搜索区域;
分别编写其中的内容。
components\navbar\index.vue
<template>
<div class="navbar">
<div class="wrapper content">
<!-- logo -->
<div class="content-left">
<NuxtLink to="/">
<img class="logo" src="@/assets/images/logo.png" alt="logo" />
<!-- SEO 优化 -->
<h1 class="title">OPPO商城</h1>
</NuxtLink>
</div>
<!-- 菜单列表 -->
<ul class="content-center">
<li>
<NuxtLink class="link" to="/">
OPPO专区
</NuxtLink>
</li>
<li>
<NuxtLink class="link" to="/">
OnePlus专区
</NuxtLink>
</li>
<li>
<NuxtLink class="link" to="/">
智能硬件
</NuxtLink>
</li>
<li>
<NuxtLink class="link" to="/">
服务
</NuxtLink>
</li>
</ul>
<!-- 搜索框 -->
<div class="content-right">
<search></search>
</div>
</div>
</div>
</template>
在 /components
目录中,创建一个 search
组件,用于展示搜索框。
components\search\index.vue
<template>
<div class="search">
<input type="text" placeholder="Reno9 系列">
<img class="search-svg" src="@/assets/images/search.svg" alt="search">
</div>
</template>
将之前封装好的网络请求,/service/index
拷贝到项目中。修改 BASE_URL
service\index.ts
import type { AsyncData, UseFetchOptions } from 'nuxt/app'
const BASE_URL = 'http://codercba.com:9060/oppo-nuxt/api'
type Method = 'GET' | 'POST'
class ZtRequest {
request<T = any>(
url: string,
method: Method,
data?: any,
options?: UseFetchOptions<T>
): Promise<AsyncData<T, Error>> {
return new Promise((resolve, reject) => {
const newOption: UseFetchOptions<T> = {
baseURL: BASE_URL,
method,
...options
}
if (method === 'GET') {
newOption.query = data
} else if (method === 'POST') {
newOption.body = data
}
useFetch<T>(url, newOption as any)
.then(res => {
resolve(res as AsyncData<T, Error>)
})
.catch(err => {
reject(err)
})
})
}
get<T = any>(url: string, param?: any, options?: UseFetchOptions<T>) {
return this.request(url, 'GET', param, options)
}
post<T = any>(url: string, data?: any, options?: UseFetchOptions<T>) {
return this.request(url, 'POST', data, options)
}
}
export default new ZtRequest()
测试:在默认布局 default.vue
中,发送网络请求。
layouts\default.vue
<script setup lang="ts">
import { fetchHomeInfo } from '@/service/home';
const { data } = await fetchHomeInfo('oppo');
console.log('data.value.data:', data.value?.data)
</script>
安装依赖:
pnpm add pinia
pnpm add @pinia/nuxt
在 nuxt.config.ts
中,配置 modules
:
03-hello-nuxt\nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt']
})
在 /store
目录下,创建 home.ts
文件。
在 actions 中,使用封装好的网络请求。
为 state 指定类型。
给网络请求的返回值,指定类型。
service\home.ts
import ztRequest from './index'
import type { IResultData } from '@/types/global'
import type { HomeInfoType, IHomeData } from '@/types/home';
export const getHomeInfo = (type: HomeInfoType = 'oppo') => {
return ztRequest.get<IResultData<IHomeData>>('/home/info', {
type
})
}
store\home.ts
import type { HomeInfoType } from '@/types/home';
import type { INavbar, IBanner, ICategory } from '@/types/home';
import { defineStore } from 'pinia';
import { getHomeInfo } from '@/service/home';
interface IHomeState {
navbars: INavbar[]
banners: IBanner[]
categorys: ICategory[]
}
export const useHomeStore =defineStore('home', {
state: () : IHomeState => ({
navbars: [],
banners: [],
categorys: []
}),
actions: {
async fetchHomeInfoData(type: HomeInfoType) {
// 服务端发送网络请求,获取数据,会同步给客户端。客户端不发送网络请求。
const { data } = await getHomeInfo(type)
this.navbars = data.value.data.navbars || []
this.banners = data.value.data.banners || []
this.categorys = data.value.data.categorys || []
}
}
})
在默认布局 default.vue
中,派发 homeStore
的 action
,fetchHomeInfoData
,获取 navbar
数据。
将数据传给 <navbar>
组件。
layouts\default.vue
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useHomeStore } from '~/store/home';
const homeStore = useHomeStore()
homeStore.fetchHomeInfoData('oppo')
const { navbars } = storeToRefs(homeStore)
</script>
<template>
<div class="default-layout">
<!-- header -->
<app-header></app-header>
<!-- navbar -->
<navbar :listData="navbars"></navbar>
<slot></slot>
<!-- footer -->
<app-footer></app-footer>
</div>
</template>
在 /navbar/index.vue
中,接收布局 default.vue
中,传递过来的数据。
components\navbar\index.vue
<script setup lang="ts">
import type { INavbar } from '~/types/home'
interface IProps {
listData: INavbar[]
}
withDefaults(defineProps<IProps>(), {
listData: () => []
})
const currentIndex = ref<number>(0)
const onNavBarItemClick = (index: number) => {
currentIndex.value = index
}
const getPagePath = (item: INavbar) => {
return item.type === 'oppo' ? '/' : '/' + item.type
}
</script>
<template>
<div class="navbar">
<div class="wrapper content">
<!-- logo -->
<div class="content-left">
<NuxtLink to="/">
<img class="logo" src="@/assets/images/logo.png" alt="logo" />
<!-- SEO 优化 -->
<h1 class="title">OPPO商城</h1>
</NuxtLink>
</div>
<!-- 菜单列表 -->
<ul class="content-center">
<template v-for="(item, index) of listData" :key="index">
<li :class="{ active: currentIndex === index }">
<NuxtLink class="link" :to="getPagePath(item)" @click="onNavBarItemClick(index)">
{{ item.title }}
</NuxtLink>
</li>
</template>
</ul>
<!-- 搜索框 -->
<div class="content-right">
<search></search>
</div>
</div>
</div>
</template>
创建 server、onePlus、intelligence 三个页面。
在项目中,集成 Element Plus
1.安装依赖:
pnpm add element-plus
pnpm add -D @element-plus/nuxt
2;在 nuxt.config.ts
中,配置自动导包;
03-hello-nuxt\nuxt.config.ts
export default defineNuxtConfig({
modules: ['@element-plus/nuxt'],
})
在首页,编写轮播图。
在 /components
目录下,创建 swiper/index.vue
;
在其中,使用 Element Plus 的走马灯组件 ElCarousel
。
components\swiper\index.vue
<script setup lang="ts">
import { ElCarousel, ElCarouselItem } from 'element-plus'
</script>
<template>
<div class="home">
<div class="wrapper content">
<el-carousel height="480px">
<el-carousel-item v-for="item in 4" :key="item">
<h3 text="2xl">{{ item }}</h3>
</el-carousel-item>
</el-carousel>
</div>
</div>
</template>
在首页 index.vue
中,获取 homeStore
中的 banners
;
将 banners
,传递给组件 swiper/index.vue
;
自定义轮播图的“指示器”。
components\swiper\index.vue
<script setup lang="ts">
import { ElCarousel, ElCarouselItem } from 'element-plus'
import { IBanner } from '~/types/home';
interface IProps {
listData: IBanner[]
}
withDefaults(defineProps<IProps>(), {
listData: () => []
})
const currentindex = ref<number>(0)
const onCarouselChange = (index: number) => {
currentindex.value = index
}
</script>
<template>
<div class="swiper">
<div class="wrapper content">
<el-carousel height="480px" indicator-position="none" @change="onCarouselChange">
<el-carousel-item v-for="(item, index) of listData" :key="item.id">
<img class="pic-str" :src="item.picStr" alt="轮播图">
</el-carousel-item>
</el-carousel>
</div>
<!-- 指示器 -->
<ul class="dots">
<template v-for="item, index of listData" :key="item.id">
<li :class="['dot', { active: currentindex === index }]"></li>
</template>
</ul>
</div>
</template>
在 /components
目录下,创建 tab-category/index.vue
;
在首页 index.vue
中,将 categoriys
数据,传递给 tab-category/index.vue
中。
使用编程式导航,编写 item 点击功能。
components\tab-category\index.vue
<script setup lang="ts">
import type { ICategory } from '~/types/home';
interface IProps {
listData: ICategory[]
}
withDefaults(defineProps<IProps>(), {
listData: () => []
})
const emits = defineEmits<{
(e: 'itemClick', item: ICategory): void
}>()
const onItemClick = (item: ICategory) => {
emits('itemClick', item)
}
</script>
<template>
<div class="tab-category">
<template v-for="item, index of listData" :key="item.id">
<div class="category-item" @click="onItemClick(item)">
<img :src="item.picStr" alt="" class="pic-str">
<div class="title">{{ item.title }}</div>
</div>
</template>
</div>
</template>
在首页 index.vue
中,处理事件
pages\index.vue
<script setup lang="ts">
const handleTabCategoryItemClick = (item: ICategory) => {
console.log('item.title:', item.title)
}
</script>
<template>
<div class="home">
<!-- 分类 -->
<TabCategory :listData="categorys" @itemClick="handleTabCategoryItemClick"></TabCategory>
</div>
</template>
封装 section-title
组件。
components\section-title\index.vue
<script setup lang="ts">
interface IProps {
title: string
}
withDefaults(defineProps<IProps>(), {
title: ''
})
</script>
<template>
<h1 class="section-title">
{{ title }}
</h1>
</template>
创建 grid-view
组件,用于展示商品图片区域。
在其中接收 productsDetails
的数据:
components\grid-view\index.vue
<script setup lang="ts">
import { IProductDetailss } from '~/types/home'
interface IProps {
listData?: IProductDetailss[]
picUrl?: string
}
withDefaults(defineProps<IProps>(), {
listData: () => [],
picUrl: ''
})
</script>
<template>
<div class="grid-view">
<template v-for="item of listData" :key="item.id">
<div class="view-item">
<grid-view-item :itemData="item"></grid-view-item>
</div>
</template>
</div>
</template>
创建 grid-view-item
组件,用于展示商品图片。
在其中接收 productDetail
的数据。
components\grid-view-item\index.vue
<script setup lang="ts">
import type { IProductDetailss } from '~/types/home'
interface IProps {
itemData: IProductDetailss | null
}
withDefaults(defineProps<IProps>(), {
itemData: null
})
</script>
<template>
<div class="grid-view-item" v-if="!!itemData">
<!-- 产品图片 -->
<div class="item-img">
<img class="url" :src="itemData.url" alt="">
</div>
<!-- 产品标题 -->
<div class="item-title">{{ itemData.title }}</div>
<!-- 产品标签 -->
<div class="item-labels">
<template v-for="item, index of itemData.activityList" :key="index">
<span class="label">{{ item.activityInfo }}</span>
</template>
</div>
<!-- 产品价格 -->
<div class="item-price">
<span class="prefix">{{ itemData.priceInfo?.prefix }}</span>
<span class="prefix">{{ itemData.priceInfo?.currencyTag }}</span>
<span class="price">{{ itemData.priceInfo?.buyPrice }}</span>
</div>
</div>
</template>