主题
mock首页的接口与搭建首页顶部的静态与组件的拆分(Day11 1/2)
本页对应 day11,共 5 段视频,约 2 小时 14 分。
学习目标
- 能使用 mockjs 创建首页假接口,配置状态码与返回数据,并在入口文件引入使 mock 生效,通过实际请求验证接口可用。
- 能为 mock 接口创建独立请求实例与 API 函数,正确设置基础路径并调用获取首页数据。
- 能清理首页无用结构,将首页顶部按上、中、下拆分为子组件,并在父组件中完成引入与使用。
- 能封装可复用卡片组件,使用多个具名插槽暴露 header、number、chart、footer 区域,并在父组件中按需填充内容。
- 能在首页父组件挂载时请求并存储数据,通过 props 下发到顶部组件,再将今日销售额、日同比、月同比、昨日销售额等数据渲染到第一个卡片。
- 能在第一个卡片中引入阿里 SVG 图标、调整布局样式,并为数字区域添加 CSS animation 动画。
知识点
先明确本节目标
首页要做的是工作报表式的数据可视化:折线图、柱状图、水球图等,这些图形在工作中很常见。当前首页还放着之前演示用的组件,先把没用的结构删掉,留一个简单占位确认页面干净。真正的问题是:首页没有真实接口,数据从哪来?转写里的“work / walkS / 框着点GS”都应纠正为 mockjs,不是 mock.js。
- 为什么需要:前端静态页面和组件先写完了,后端接口可能还没好;没有数据就无法继续联调渲染。
- 怎么想:用
mockjs造一个假接口,让首页仍然走“发请求 → 拿数据 → 渲染”的流程,而不是把 JSON 直接塞进组件。 - 怎么做:准备 JSON 数据,安装
mockjs,定义Mock.mock接口,在入口文件引入一次,再复制一份mockRequest发请求。 - 易错点:
mockjs和mock.js是两个东西;假接口也要注意基础路径,不能直接复用真实请求的baseURL。
准备 JSON 数据
转写里的“阶省 / ja森 / 接层”都是 JSON。已写好的项目里有一个 mock 文件夹,里面放着首页需要的 JSON 数据;把它复制到当前项目的 src/mock/data.json。这个 JSON 是一个整体对象,里面有今日销售额、今日订单量、今日交易用户数、累积用户数等首页数据。
- 为什么需要:首页顶部、中间、底部的数据都来自同一份首页数据,先集中放 JSON 便于一次返回。
- 怎么想:首页所有可视化数据放在一个 JSON 对象里,接口发一次请求即可,后续父组件拿到数据再分发给子组件。
- 怎么做:在
src/mock下放data.json,文件名按项目实际命名。 - 易错点:JSON 文件不能写
export,export是 JS/TS 关键字;JSON 虽然不写导出,但可以被默认引入。
安装并引入 mockjs
安装 mockjs 后,在 src/mock/index.ts 中引入:
ts
// @ts-ignore
import Mock from 'mockjs'
import data from './data.json'mockjs 是 JS 包,没有 TS 类型声明,直接引入会出现红色波浪线。转写里的“艾特TS1个轴”就是 // @ts-ignore,用来忽略 TS 类型校验。
- 为什么需要:没有类型声明时 TS 会报红线,但不影响运行。
- 怎么想:先保证功能跑通,再用注释忽略类型检查。
- 怎么做:在
import Mock from 'mockjs'上一行写// @ts-ignore。 - 易错点:不要因为红线就删掉引入;也不要写成
mock.js。
定义假接口
在 src/mock/index.ts 中写:
ts
Mock.mock('/mock/home', {
code: 200,
data
})第一个参数是假接口地址 /mock/home,第二个参数是接口返回对象。转写里说返回状态码可以是 200 或项目约定的成功码,返回数据就是刚才的 JSON。
- 为什么需要:
Mock.mock把地址和返回数据绑定起来,后续 axios 才能请求到。 - 怎么想:接口地址要和请求路径拼接一致;返回对象里放
code和data。 - 怎么做:第一个参数写
/mock/home,第二个参数写{ code: 200, data }。 - 易错点:路径不要多写或少写;
data的键值要和后续取值一致。
让 mock 文件执行一次
Mock.mock 是一个函数,文件不执行就不会注册接口。因此在入口文件(如 main.ts)中引入:
ts
import './mock'- 为什么需要:只定义不执行,假接口不会生效。
- 怎么想:这里不需要接收变量,只需要让
src/mock/index.ts执行一次。 - 怎么做:在入口文件写
import './mock',默认会找index.ts。 - 易错点:不要写
import mock from './mock'期待它导出;该文件没有默认导出。
复制 request 做 mockRequest
真实请求的 baseURL 和 mock 请求不同,不能直接拿原来的 request 用。复制一份请求实例,把基础路径改成 /mock:
ts
import axios from 'axios'
const mockRequest = axios.create({
baseURL: '/mock'
})
export default mockRequest- 为什么需要:原
request的基础路径是真实接口路径,mock 接口以/mock开头。 - 怎么想:复制一份,只改
baseURL,避免影响真实接口。 - 怎么做:新建
mockRequest.ts,导出mockRequest。 - 易错点:不要直接改原
request的baseURL;/mock/home由baseURL: '/mock'加/home拼成。
写 API 并在首页验证
在 API 文件中写:
ts
import mockRequest from '@/api/mockRequest'
export const reqChart = () => mockRequest.get('/home')然后在首页 onMounted 中测试:
ts
import { onMounted } from 'vue'
import { reqChart } from '@/api/home'
onMounted(async () => {
const result = await reqChart()
console.log(result)
})刷新页面,如果控制台能打印出数据,说明 mock 接口已经通了;验证完把测试打印删掉。转写里还强调:首页数据请求发一次即可,因为顶部、中间、底部用的是同一份数据;后续由父组件取到数据再传给子组件。
- 为什么需要:先验证接口通不通,再写渲染逻辑,避免把问题混在一起。
- 怎么想:用
onMounted在组件挂载后发一次请求,打印结果。 - 怎么做:封装
reqChart,首页调用并console.log(result)。 - 易错点:如果打印不到,先查入口是否引入
./mock、baseURL是否为/mock、请求路径是否为/home、JSON 是否被正确默认引入。
先清理首页并准备 mock 假接口
为什么需要:首页要做数据可视化,但后端接口还没有;如果等接口,静态结构和组件拆分就无法推进。怎么想:先用 mockjs 造一个和真实接口同形的假接口,让页面能发请求、拿数据、渲染。怎么做:安装 mockjs,在 src/mock/index.ts 中引入并拦截 /mock/home,返回 { code: 200, data: homeData }。注意 mockjs 是 JS 文件,TS 引入可能报红,可用 @ts-ignore 忽略类型校验;JSON 文件不能写 export,只能默认导入。易错点:不要把 mockjs 写成 mock.js;mock 文件必须在入口 main.ts 中引入一次才会执行。
ts
// src/mock/index.ts
// @ts-ignore
import Mock from 'mockjs'
import homeData from './home.json'
Mock.mock('/mock/home', 'get', {
code: 200,
data: homeData
})ts
// src/main.ts
import './mock'拆分首页顶部为上中下组件
为什么需要:首页顶部结构多,全写在一个文件里难维护。怎么想:先按“上、中、下”一分为三,再在顶部内部拆卡片。怎么做:建 components/Home/Top、Middle、Bottom,首页引入三个子组件。易错点:不要一上来拆得过细;先保证父子结构和数据流清楚。
vue
<Top :data="homeData" />
<Middle />
<Bottom />顶部卡片用插槽复用
为什么需要:四个卡片外壳一致,只有标题、数字、图表、底部文字不同。怎么想:用一个 Card 组件复用四次,用具名插槽留坑。怎么做:Card 中留 header、number、chart、footer 四个具名插槽;父组件用 #header、#number 等填内容。易错点:Vue3 具名插槽写 #header,不要沿用 Vue2 的 v-slot:header 混写;默认插槽一般只有一个,多个坑要用具名插槽。
vue
<!-- Card/index.vue -->
<template>
<div class="card">
<div class="header"><slot name="header" /></div>
<div class="number"><slot name="number" /></div>
<div class="chart"><slot name="chart" /></div>
<div class="footer"><slot name="footer" /></div>
</div>
</template>父组件请求一次并下发数据
为什么需要:首页所有图表共用同一份首页数据,发多次请求浪费。怎么想:在首页父组件挂载时请求一次,存到 ref,再通过 props 传给顶部。怎么做:封装 getHomeData,onMounted 调用;接口返回后 homeData.value = result.data。易错点:接口字段如 date["-1"] 这种特殊 key 不能用点语法,要用中括号;首次渲染可能是空对象,子组件要判空。
ts
const homeData = ref({})
const getHomeData = async () => {
const result = await reqHome()
homeData.value = result.data
}
onMounted(() => getHomeData())第二个卡片:折线图封装与调试
为什么需要:折线图配置多,单独封装便于维护。怎么想:ECharts 三步:引包、准备有高度的容器、初始化实例并 setOption。怎么做:onMounted 初始化,xAxis/yAxis 的 show 设 false,grid 四边设 0 贴边,series.smooth 做圆滑曲线,areaStyle 填色,tooltip.confine 限制在容器内。易错点:容器没高度图表不显示;window.resize 只响应浏览器窗口,不响应容器变化;数据首次为空时用 v-if 等有数据再渲染。
ts
const option = {
tooltip: { confine: true, axisPointer: { type: 'line' } },
grid: { left: 0, right: 0, top: 0, bottom: 0 },
xAxis: { type: 'category', show: false, boundaryGap: false },
yAxis: { show: false },
series: [{ type: 'line', smooth: true, symbol: 'none', areaStyle: {}, data: [] }]
}第三个卡片:用 vue-echarts 简化图表
为什么需要:原生 ECharts 要手动初始化、setOption、处理自适应。怎么想:vue-echarts 把 ECharts 封装成 Vue 组件,配置项照旧。怎么做:安装 echarts 和 vue-echarts,全局注册 VChart,模板中写 <VChart :option="getOption()" autoresize />。易错点:option 建议用函数返回对象,避免模板里堆大段配置;autoresize 能跟随容器变化;提示框十字指示器用 axisPointer.type: 'cross'。
ts
const getOption = () => ({
tooltip: { axisPointer: { type: 'cross' } },
xAxis: { type: 'category', show: false },
yAxis: { show: false },
series: [{ type: 'bar', data: [10, 20, 30, 40] }]
})本节的关键不是“把图堆出来”,而是先 mock 数据、再拆组件、用插槽复用卡片,最后把 ECharts 和 vue-echarts 的配置、容器高度、空数据渲染这些调试点逐一排掉。
先造首页假接口:Mock 数据
为什么需要:首页图表依赖接口数据,但后端接口还没好;没有数据就无法联调渲染。
怎么想:用 mockjs 造一个和真实接口同结构的假接口,先让前端跑通“请求—拿数据—渲染”链路。
怎么做:把 home.json 放进 src/mock,写 mock/index.ts,再在入口文件引入一次,让 Mock 代码执行。
ts
// src/mock/index.ts
// @ts-ignore
import Mock from 'mockjs'
import homeData from './home.json'
Mock.mock('/mock/home', {
code: 200,
data: homeData
})ts
// src/main.ts
import './mock'易错点:JSON 文件不能写 export;Mock 接口基础路径与真实接口不同,要单独封装一个 requestMock,baseURL 指向 /mock,不要混用真实请求实例。
首页拆成上中下,顶部再拆卡片
为什么需要:首页结构多,全塞一个组件难维护。
怎么想:先按区域拆 Top、Middle、Bottom;顶部四个卡片结构相似,只封装一个 Card,用插槽传内容。
怎么做:Home 中引入三个子组件;Top 中用 el-row/el-col 或 flex 排四个卡片。
vue
<!-- Home.vue -->
<template>
<Top :data="chartObj" />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { reqChart } from '@/api/home'
import Top from './components/top/index.vue'
const chartObj = ref<any>({})
const getData = async () => {
const res = await reqChart()
chartObj.value = res.data
}
onMounted(() => getData())
</script>易错点:四个卡片不要拆成四个几乎相同的组件,具名插槽更合适;Card 根节点是块元素,默认一行一个,要用栅格或 flex 控制一行四个。
具名插槽完成第一个卡片
为什么需要:卡片头部、数字、图表、底部结构固定,内容由父组件决定。
怎么想:子组件留四个具名插槽:header、number、chart、bottom。
怎么做:
vue
<!-- Card.vue -->
<template>
<div class="card">
<div class="header"><slot name="header" /></div>
<div class="number"><slot name="number" /></div>
<div class="chart"><slot name="chart" /></div>
<div class="bottom"><slot name="bottom" /></div>
</div>
</template>父组件使用 #header、#number 等写法。数据 key 若含特殊字符,必须用中括号取值,不能写 data.--。
易错点:Vue3 具名插槽写 #header,不是 Vue2 的 v-slot:header 混写;默认插槽一般只有一个,具名插槽可多个。
第一个卡片接折线图并调试
为什么需要:卡片中间要展示趋势图。
怎么想:ECharts 三步:引包、准备有高度的容器、初始化实例并 setOption。
怎么做:
vue
<!-- Line.vue -->
<template>
<div ref="chartRef" class="line"></div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import * as echarts from 'echarts'
const chartRef = ref<HTMLDivElement>()
let chart: echarts.ECharts
const initChart = () => {
chart = echarts.init(chartRef.value!)
chart.setOption({
xAxis: { type: 'category', boundaryGap: false, show: false },
yAxis: { show: false },
grid: { left: 0, right: 0, top: 0, bottom: 0 },
tooltip: { trigger: 'axis', confine: true },
series: [{
type: 'line',
smooth: true,
symbol: 'none',
data: [20, 18, 30, 40, 50, 60],
areaStyle: { color: '#409eff' }
}]
})
}
onMounted(() => initChart())
window.addEventListener('resize', () => chart?.resize())
</script>
<style scoped>
.line { width: 100%; height: 80px; }
</style>调试时若图表不显示,先查容器高度;若子组件拿不到数据,是父组件初始值 {} 导致首次渲染为空,可在图表外包 v-if="data && data['字段名']",等接口回来再渲染。
易错点:ECharts 容器必须有高度;隐藏坐标轴后要用 grid 贴边;tooltip 需要拐点或坐标轴指示器才能触发;resize 只监听窗口变化,不监听容器自身变化。
课堂画面





代码示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>Mock 首页接口</title>
</head>
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/mock.min.js"></script>
<script>
const { createApp, ref, onMounted } = Vue;
// 模拟首页接口:地址 /mock/home,返回 code + data
Mock.mock('/mock/home', 'get', {
code: 200,
data: {
'//1': {
today: 303,
lastDay: '--',
dayRatio: '19%',
monthRatio: '56.67%'
}
}
});
// mock 接口的基础路径和真实接口不同,所以单独复制一份 request
const mockRequest = axios.create({
baseURL: '/mock'
});
const App = {
setup() {
const result = ref(null);
onMounted(async () => {
// baseURL + '/home' => /mock/home
const res = await mockRequest.get('/home');
result.value = res.data;
console.log(res.data);
});
return { result };
},
template: `
<pre>{{ result ? JSON.stringify(result, null, 2) : '请求中...' }}</pre>
`
};
createApp(App).mount('#app');
</script>
</body>
</html>要点解释
Mock.mock('/mock/home', 'get', {...}):第一个参数是假接口地址,第二个参数是请求方式,第三个参数是返回数据;地址必须和请求地址一致。mockRequest的baseURL是/mock,所以mockRequest.get('/home')实际请求/mock/home;真实接口基础路径不同,不能直接混用。- 特殊字段名
'//1'不能写成data.//1,必须用中括号:data['//1']。 - MockJS 文件必须在入口执行一次,否则假接口不会生效;JSON 文件不能写
export。 - 请求成功后先把数据存到
result,再渲染;否则首次渲染拿不到数据。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>首页顶部卡片拆分与第一个卡片</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
padding: 20px;
background: #f5f7fa;
font-family: Arial, sans-serif;
}
.top {
display: flex;
gap: 20px;
}
.card {
flex: 1;
background: #fff;
border: 1px solid #eee;
padding: 16px;
}
.card .header {
font-size: 16px;
font-weight: 900;
margin-bottom: 10px;
}
.card .number {
font-size: 24px;
font-weight: 800;
animation: numberIn 0.5s ease 1;
}
.card .chart {
min-height: 80px;
border-bottom: 1px dashed #ddd;
margin: 10px 0;
}
.card .bottom {
margin-top: 10px;
color: #666;
}
.ratio {
display: flex;
gap: 20px;
}
.ratio-item {
display: flex;
align-items: center;
gap: 4px;
}
@keyframes numberIn {
from {
transform: translateY(10px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
</style>
</head>
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
// 卡片子组件:预留 header / number / chart / bottom 四个具名插槽
const Card = {
template: `
<div class="card">
<div class="header"><slot name="header"></slot></div>
<div class="number"><slot name="number"></slot></div>
<div class="chart"><slot name="chart"></slot></div>
<div class="bottom"><slot name="bottom"></slot></div>
</div>
`
};
const Top = {
components: { Card },
setup() {
const homeData = {
'//1': {
today: 303,
lastDay: '--',
dayRatio: '19%',
monthRatio: '56.67%'
}
};
return { homeData };
},
template: `
<div class="top">
<Card>
<template #header>今日销售额</template>
<template #number>{{ homeData['//1'].today }}</template>
<template #chart>
<div class="ratio">
<div class="ratio-item">
<span>日同比</span>
<span>{{ homeData['//1'].dayRatio }}</span>
<span>↓</span>
</div>
<div class="ratio-item">
<span>月同比</span>
<span>{{ homeData['//1'].monthRatio }}</span>
<span>↑</span>
</div>
</div>
</template>
<template #bottom>
昨日销售额:{{ homeData['//1'].lastDay }}
</template>
</Card>
<Card>
<template #header>待接入</template>
<template #number>--</template>
<template #chart>图表容器必须有高度</template>
<template #bottom>--</template>
</Card>
<Card>
<template #header>待接入</template>
<template #number>--</template>
<template #chart>图表容器必须有高度</template>
<template #bottom>--</template>
</Card>
<Card>
<template #header>待接入</template>
<template #number>--</template>
<template #chart>图表容器必须有高度</template>
<template #bottom>--</template>
</Card>
</div>
`
};
createApp({
components: { Top },
template: `<Top />`
}).mount('#app');
</script>
</body>
</html>要点解释
Card组件里用<slot name="header"></slot>等预留具名插槽,父组件通过<template #header>...</template>填结构。- Vue3 具名插槽简写是
#header,不是 Vue2 的slot="header";这是易错点。 - 第一个卡片数据字段是特殊 key
'//1',模板中必须写homeData['//1'].today,不能写homeData.//1。 - 四个卡片用
display:flex排成一行,flex:1平均分配宽度;如果直接放块级div,默认会一人占一行。 .chart区域如果将来放 ECharts,必须有高度;转写稿里反复强调“图表容器没高度就看不见”。number上的动画由animation: numberIn 0.5s ease 1;使用,配合@keyframes定义;只定义动画不使用不会生效。
常见错误
现象:引入
mockjs后出现红色波浪线,项目仍能运行。
原因:mockjs是 JS 包,缺少 TS 类型声明。
修法:不影响运行可忽略;不想看到报错则用// @ts-ignore或补充声明文件。现象:在
.json文件里写export default后报错。
原因:JSON 不是 JS/TS 模块,不能写export。
修法:JSON 只保留纯数据;需要导出时在.ts/.js中import后使用。现象:Mock 接口已写好,但请求 404 或拿不到数据。
原因:Mock 文件没有被入口文件引入执行。
修法:在入口文件如main.ts中import './mock',确保 Mock 注册执行一次。现象:真实接口请求正常,Mock 接口请求失败。
原因:Axios 封装的基础路径是/api,Mock 地址却是/mock。
修法:为 Mock 单独复制一份请求封装,基础路径改为/mock,不要在同一个实例上混用。现象:具名插槽内容不显示或控制台提示插槽名不匹配。
原因:Vue3 具名插槽写法仍用旧写法,或#name与子组件name不一致。
修法:父组件用#插槽名,子组件用<slot name="插槽名" />,两边名称严格一致。现象:ECharts 图表区域空白,容器有宽但看不到图。
原因:图表容器没有明确高度,或初始化时ref容器尚未拿到。
修法:给图表容器设置固定高度或height: 100%且父级有高度;在onMounted后初始化。现象:接口数据已返回,但图表首次渲染报错或无数据。
原因:初始值是空对象,子组件首次渲染直接取深层字段。
修法:使用可选链/默认值,或用v-if="有数据"等数据到位后再渲染图表。现象:
vue-echarts注册后仍不显示,或option写法混乱。
原因:组件未全局/局部注册,或option直接塞在模板里且父容器无高度。
修法:正确注册v-chart,option用计算属性/函数返回对象,并保证父容器有高度。
练习
- 在入口文件接入 Mock,新增
/mock/home接口,返回{ code: 200, data: {...} },并在首页onMounted中打印请求结果。 - 将首页拆成
Top / Middle / Bottom三个子组件,并在父组件中完成引入、注册和使用。 - 用“一个卡片组件 + 多个具名插槽”复用四个顶部卡片,要求标题、数字、图表区、底部说明都能由父组件传入。
- 封装一个折线图子组件:父组件传入数据,子组件完成 ECharts 初始化、配置项设置和窗口
resize自适应。 - 用
vue-echarts完成柱状图卡片,要求配置项独立成函数/计算属性,并验证容器尺寸变化时图表能自适应。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | mock首页的接口 | 15:51 |
| 2 | 搭建首页顶部的静态与组件的拆分 | 25:59 |
| 3 | 首页顶部第一个卡片的完成 | 21:32 |
| 4 | 首页顶部的第二个卡片完成 | 43:34 |
| 5 | 首页顶部第三个卡片-vue-echarts | 27:58 |
小结
- Mock 接口的核心是“先注册执行,再按独立基础路径发请求”,不要和真实接口封装混用。
- 首页顶部优先做结构拆分,再抽卡片组件;复用场景优先考虑插槽,而不是复制四份结构。
- 具名插槽适合“子组件定布局、父组件填内容”的卡片类组件,插槽名必须成对维护。
- ECharts 图表能否显示,首先检查容器高度和初始化时机,其次检查配置项和数据。
- 接口数据未返回时要有兜底:默认值、可选链或条件渲染,避免首屏渲染直接取深层字段。
vue-echarts的优势是组件化、配置项集中、事件和自适应更省心,但同样依赖父容器高度。- 图表自适应要区分“窗口变化”和“容器变化”;后台管理系统里容器变化很常见,不能只监听
window.resize。 - 样式调整优先用 Flex/Grid 等布局手段解决对齐和间距,图表贴边问题重点检查
grid配置。