主题
首页顶部第三个卡片完成与完成顶部四个卡片(Day11 2/2)
本页对应 day11,共 5 段视频,约 1 小时 44 分。
学习目标
- 能使用 ECharts 柱状图组件接收并替换首页顶部卡片数据,正确配置坐标轴、柱条宽度、背景色及 label 格式化内容。
- 能基于 ECharts 柱状图实现横向进度条效果,隐藏无关坐标轴、设置坐标轴最大最小值,并调整布局与颜色。
- 能使用 Element Plus 的 Tabs、日期选择器等组件完成首页中间区域静态结构搭建,并通过定位或弹性布局实现左右、上下结构。
- 能使用深度选择器修改 Element Plus 组件内部样式,并验证文字、背景色、间距等样式生效。
- 能使用日期范围选择器完成日历数据收集,配置
value-format,并使用 dayjs 格式化日期、计算今日/本周/本月/本年的起止日期。 - 能说明菜单权限与按钮权限的实现思路,包括路由拆分、用户信息过滤动态路由、
addRoute追加路由及自定义指令控制按钮显示。
知识点
本节要完成什么
本节聚焦首页顶部第三个卡片:今日交易用户数。它前面已经有写死的柱状图,现在要把写死数据换成父组件传进来的接口数据,并完成图表样式微调。转写里同时提到后台管理系统文档的使用,这里一并整理。
后台管理系统文档:先学会查,再动手写
为什么需要:后台管理系统需求多、组件多,工作里不可能每个都从零写;组件库文档和需求区就是现成参考。转写里说“以后工作了写这种后台管理系统项目,你就翻这个文档”,练习时也可以把它当文档看。
怎么想:先找同类需求,看别人怎么用组件、怎么组织数据;但项目字段要以自己的接口为准,不能照抄示例。
怎么做:翻文档找组件示例,再看需求区有没有类似场景;缺需求可以提需求。转写提到“将近两三万已经写好了的需求”,说明可参考内容很多。
易错点:文档示例是“用法”,不是“业务代码”。替换到项目里时,要确认字段名、数据层级、空数据情况,别把示例数据直接当成接口数据。
第三个卡片:从写死数据到接口数据
为什么需要:柱状图不能一直写死,否则首页展示的不是真实交易用户数。
怎么想:先确认图表组件需要什么数据,再去父组件找对应接口字段。转写里的顺序是:先看写死的柱状图,再找“今日交易用户数”的数据,最后把数据传给子组件。
怎么做:父组件里把接口返回的数据接到 ARR,再传给卡片组件;子组件用 defineProps 接收。模板里可以直接用 arr,但在脚本/配置函数里要用接收后的对象,例如转写口述的 d.arr。
js
// 父组件:把接口返回的数据交给 ARR
ARR = 后端返回的数据
// 模板中传给第三个卡片
<ThirdCard :arr="ARR" />js
// 子组件:接收父组件传入的数据
const d = defineProps({
arr: Array
})
// 图表数据处使用
data: d.arr易错点:props 在模板中可直接写,但在脚本里要先接收;转写反复强调“模板里直接写它,TS 里用数据得接收”。另外,父组件字段要找对,转写里说“第 10 行”“今日交易用户数”,就是提醒先定位字段再替换。
刷新调试:配置为什么执行两次
为什么需要:替换数据后必须刷新验证,确认图表拿到的是接口数据,而不是旧写死值。
怎么想:先看图表有没有数据,再看配置函数执行时数据是否已经到达。转写里刷新后发现配置项执行了两次,第一次没数据,第二次有数据。
怎么做:打开页面刷新,观察图表;在配置函数里打印 d.arr,确认第一次为空、第二次有值。然后把写死数据替换成 d.arr。
易错点:配置执行两次不是 bug,而是组件在监测数据变化。第一次数据没到,第二次数据到了才渲染。不要在第一次空数据时强行取值导致报错。
组件封装的好处:空数据不用自己判断
为什么需要:真实接口有延迟,初始数据可能为空;如果图表组件不处理,就容易报错或白屏。
怎么想:转写对比了以前写原生图表时要写 v-if 判断数据,而现在用的 vue-echarts 组件已经帮忙处理空数据。
怎么做:把数据交给组件,组件内部监测数据变化;空数据时先不渲染有效图形,有数据后自动更新。转写说“这玩意儿你就不用管,人家都帮你备好”。
易错点:虽然组件帮你处理,但父组件传参仍要保证类型正确,默认值最好是空数组;不要因为组件能处理就传 undefined。
样式微调:label 与柱条宽度
为什么需要:只有柱子不够,业务上常要把数值展示在柱条上,还要控制柱条宽度和颜色。
怎么想:这些都归图表配置管。转写提到“label 把这个值在柱状展示”“每个柱条颜色能不能调”“柱条宽度可以调”。
怎么做:在 series 里配置 label.show 显示文本,label.color 调文字颜色,barWidth 调柱条宽度。转写里柱宽试了 10 像素。
js
series: [{
type: 'bar',
data: d.arr,
barWidth: 10,
label: {
show: true,
color: 'red'
}
}]易错点:barWidth 不是 width;文字颜色写在 label.color 下,不要写到 series 顶层。调完样式要刷新看实际效果,别只看配置项。
本节小结
第三个卡片完成的关键不是“画一个柱状图”,而是把父组件接口数据 → 子组件 props → 图表配置这条链接通。调试时重点看两件事:字段有没有找对、数据第一次为空第二次到达时组件有没有正常更新。最后再用 label 和 barWidth 做展示优化。转写里“配置执行两次”的现象要理解成数据监测过程,不要误判为错误。
本节目标与整体思路
为什么需要:首页顶部四个卡片是后台管理系统的数据总览区,前两个卡片已经完成,第三个“今日交易用户数”要用柱状图展示,第四个“累积用户数”要用进度条展示。它们不是孤立样式,而是“接口数据 → 子组件接收 → 图表配置 → 页面呈现”的完整练习。
怎么想:先完成第三个卡片的数据接入,再做第四个卡片的进度条。第三个卡片重点是把父组件拿到的数组传给图表组件;第四个卡片重点是用“横向柱状图”模拟进度条,而不是再写一个水球图。
怎么做:父组件把接口返回的数组通过属性传给子组件;子组件用 defineProps 接收,模板中直接使用,TS 逻辑中通过 props 取值;图表组件用配置对象控制坐标轴、系列、柱宽、背景和标签。
易错点:转写稿里的“de范proers”应纠正为 defineProps;“getOP”是 getOption;“SA叉XX轴”是 xAxis,“YH叉SY轴”是 yAxis,“SERNEX”是 series。配置项一般叫 option,不是 options。
第三个卡片:今日交易用户数柱状图接数据
为什么需要:之前柱状图的数据是写死的,只能看效果;真实项目必须从接口返回的数据中取“今日交易用户数”,否则图表没有业务意义。
怎么想:先在父组件确认接口数据里哪个字段对应今日交易用户数,再把该数组传给柱状图子组件。转写稿中反复确认“第几个数据”“是不是这个”,说明字段不能凭感觉,要对着数据源核对。
怎么做:父组件把对应数组传给子组件;子组件用 defineProps 接收,例如接收 ARR。模板中可以直接写 ARR;如果在 TS 函数里使用,则要先接收 const props = defineProps(...),再通过 props.ARR 取。原来写死的数据替换成接收值后刷新页面。
调试过程:转写稿提到配置项会执行两次,第一次没有数据,第二次才有数据。这不是错误,而是组件在监测数据变化:接口数据未到时先执行一次,数据到达后再次执行。项目里的图表组件已经帮我们处理了空数据,所以不必像早期手写图表那样额外写 v-if 判断。
易错点:不要误以为执行两次是 bug;不要在模板里写 props.ARR 又在 TS 里直接写 ARR;字段名要对准接口;组件名首字母要大写,避免和原生标签冲突。
第四个卡片:用横向柱状图实现进度条
为什么需要:进度条能直观表达“当前值占目标值的比例”,比如 80% 就到接近末端,50% 在中间。它比水球图更常见,也更适合累积用户数这类指标。
怎么想:不要把它想成新组件,它的本质是柱状图:背景是一条灰色横条,前景是一条有颜色的横条,横轴范围是 0 到 100。竖着看是柱状图,横着看就是进度条。
怎么做:先搭卡片结构,再引入进度条组件。配置时把 xAxis 和 yAxis 的关系调换,让柱条横向分布;隐藏 yAxis;设置 xAxis 的 min 为 0、max 为 100;用 grid 把上下左右边距调小;series 的 type 设为柱状图;用 barWidth 调柱条宽度;用 showBackground 配合 backgroundStyle 设置灰色背景;用 label 的 formatter 自定义文字内容,用 position 调整标签位置。
调试过程:先做出竖向柱状图,再改成横向;发现柱条太宽就调 barWidth;发现没有灰色底条就检查是否加了 showBackground;想让标签显示百分号或自定义文本,就用 formatter;标签位置不对就调 position,横向时通常在右侧或上方。
易错点:option 不要多写 s;进度条组件名要规范,避免和原生标签混淆;label.formatter 是自定义标签内容的,不是直接改数值;颜色和文字样式可以用深度选择器调整,但先确认选择器是否命中了正确的类名。
完成顶部四卡后的检查清单
为什么需要:四个卡片容易“各自能跑,合起来乱”,必须统一检查数据、样式和空值表现。
怎么想:按“数据是否接对、配置是否生效、空数据是否正常、样式是否统一”四项检查。
怎么做:刷新页面看第三个卡片是否用真实数组;看第四个卡片是否按 0 到 100 的比例显示;检查柱条宽度、背景色、标签文字;检查父组件传参名和子组件接收名是否一致。
易错点:不要把“配置执行两次”当成异常;不要漏掉 props 接收;不要用错坐标轴;不要忘记 showBackground 和 backgroundStyle 要配合使用。
中间区域先搭骨架:卡片与选项卡
为什么需要:首页中间是主要信息区,既要放图表,又要放门店销售额等辅助信息。先把静态骨架搭出来,后续接数据、做交互时才有稳定的落点。
怎么想:转写稿里明确说“中间这里的东西我们就不拆出组件了”,所以这一节先在首页组件内用 el-card 包住头部和内容区,不急着拆子组件。
怎么做:先放一个卡片,顶部再放选项卡和日期选择器,底部用栅格分成左右两列。代码可以先写成这样:
vue
<el-card>
<div class="header">
<el-tabs v-model="activeName">
<el-tab-pane label="销售额" name="sale"></el-tab-pane>
<el-tab-pane label="访问量" name="visit"></el-tab-pane>
</el-tabs>
<el-date-picker
v-model="dateArr"
type="daterange"
range-separator="至"
start-placeholder="入住时间"
end-placeholder="离开时间"
size="small"
/>
</div>
<el-row>
<el-col :span="18">图表区域</el-col>
<el-col :span="6">门店销售额</el-col>
</el-row>
</el-card>易错点:el-tabs 的 name 要和 v-model 的值对应;el-col 的 span 相加不要超过 24,否则会换行。静态阶段先不接数据,但 v-model 可以先占位,方便后面继续做日历业务。
选项卡的选中值与文字
为什么需要:选项卡默认选中项如果不是“销售额”,页面一进来高亮就会错。
怎么想:转写稿里调试时发现默认亮的是别的项,于是用 v-model 收集当前选中的 name,再把初始值改成销售额对应的 sale。
怎么做:定义 const activeName = ref('sale'),模板里写 v-model="activeName";选项卡文字用 label="销售额"、label="访问量"。
易错点:name 是标识,label 是显示文字,二者不要混;改文字只改 label,不要改 name,否则选中值会对不上。
日期范围选择器放进头部右侧
为什么需要:首页中间需要按“今日、本周、本月、本年”或起止日期筛选,日期选择器是常见入口。
怎么想:转写稿里先试了单个日期选择器,发现不是想要的“开始和离开”两个输入框,最后确定用 type="daterange"。
怎么做:用 range-separator="至" 改中间分隔符,用 start-placeholder、end-placeholder 改占位文字,用 size="small" 让它在头部不显得过大。
易错点:只有 daterange 才会出现起止两个输入框;v-model 绑定的是数组,不是单个字符串。静态阶段可以先不处理数据格式,后面再补 value-format。
用栅格把图表区一左一右排开
为什么需要:底部左侧是主图表,右侧是门店销售额,必须一左一右,否则信息层级不清。
怎么想:转写稿里说“一行两列”,左侧占 18 份,右侧占 6 份,正好 24 份。
怎么做:外层 el-row,里面两个 el-col,分别写 :span="18" 和 :span="6";也可以后续换成 flex,但静态阶段栅格更直接。
易错点:不要给 el-col 写死宽度破坏栅格;左右两列之间需要间距时,优先用 gutter 或外层样式,不要靠空格硬撑。
样式微调用深度选择器
为什么需要:Element Plus 的选项卡默认颜色和项目设计不一致,需要改文字颜色和下方指示条颜色。
怎么想:转写稿里先改 .el-tabs__item,发现文字变了但下面小球没变;继续审查元素找类名,最后确认要改背景色。
怎么做:用 :deep() 穿透组件样式,例如:
css
.header {
display: flex;
justify-content: space-between;
}
:deep(.el-tabs__item) {
color: yellowgreen;
}
:deep(.el-tabs__active-bar) {
background-color: yellowgreen;
}易错点:不要凭感觉猜类名,第一次没生效就打开浏览器审查元素确认;改颜色时区分 color 和 background-color,文字颜色和指示条背景色不是同一个属性。转写稿里正是通过反复审查元素,才从“改文字”走到“改背景色”。
课堂画面





代码示例
示例1:ECharts 横向进度条(第四个卡片)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>横向进度条</title>
<script src="https://unpkg.com/echarts/dist/echarts.min.js"></script>
<style>
body {
margin: 40px;
font-family: Arial, sans-serif;
}
.progress-card {
width: 360px;
padding: 20px;
border: 1px solid #eee;
border-radius: 8px;
}
.progress-chart {
width: 100%;
height: 40px;
}
</style>
</head>
<body>
<div class="progress-card">
<div id="chart" class="progress-chart"></div>
</div>
<script>
const chart = echarts.init(document.getElementById('chart'));
chart.setOption({
grid: {
left: 0,
top: 0,
right: 0,
bottom: 0
},
xAxis: {
type: 'value',
min: 0,
max: 100,
show: false
},
yAxis: {
type: 'category',
data: [''],
show: false
},
series: [
{
type: 'bar',
data: [86],
barWidth: 10,
showBackground: true,
backgroundStyle: {
color: '#ccc'
},
itemStyle: {
color: '#409eff'
},
label: {
show: true,
position: 'right',
formatter: '{c}%',
color: 'red'
}
}
]
});
</script>
</body>
</html>要点解释:
yAxis.type = 'category'让柱状图横向排列;xAxis.min = 0、max = 100控制百分比范围。- 柱条背景色必须用
series.showBackground+series.backgroundStyle成对配置,不是写在itemStyle里。 barWidth控制柱条宽度;label.position = 'right'、formatter、color分别控制数值位置、内容和颜色。- 易错点:横向柱状图不是改
type: 'bar'就够,必须让类目轴在yAxis;label的position在横向柱状图中常用right。
示例2:Element Plus 日期范围 + 今日/本周/本月/本年
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>日期范围快捷选择</title>
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/element-plus/dist/index.full.js"></script>
<script src="https://unpkg.com/dayjs/dayjs.min.js"></script>
<style>
body {
margin: 40px;
font-family: Arial, sans-serif;
}
.calendar-card {
display: flex;
align-items: center;
gap: 10px;
}
.left span {
color: yellowgreen;
margin-right: 10px;
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<div class="calendar-card">
<div class="left">
<span @click="setToday">今日</span>
<span @click="setWeek">本周</span>
<span @click="setMonth">本月</span>
<span @click="setYear">本年</span>
</div>
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="至"
start-placeholder="入住时间"
end-placeholder="离开时间"
value-format="YYYY-MM-DD"
size="small"
></el-date-picker>
</div>
<p>当前值:{{ dateRange }}</p>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const dateRange = ref([]);
const fmt = (d) => d.format('YYYY-MM-DD');
const setToday = () => {
const d = dayjs().format('YYYY-MM-DD');
dateRange.value = [d, d];
};
const setWeek = () => {
const start = dayjs().day(1);
const end = start.add(6, 'day');
dateRange.value = [fmt(start), fmt(end)];
};
const setMonth = () => {
const start = dayjs().startOf('month');
const end = dayjs().endOf('month');
dateRange.value = [fmt(start), fmt(end)];
};
const setYear = () => {
const start = dayjs().startOf('year');
const end = dayjs().endOf('year');
dateRange.value = [fmt(start), fmt(end)];
};
return {
dateRange,
setToday,
setWeek,
setMonth,
setYear
};
}
}).use(ElementPlus).mount('#app');
</script>
</body>
</html>要点解释:
el-date-picker的type="daterange"对应开始/结束两个日期,v-model绑定的是数组。value-format="YYYY-MM-DD"决定收集到的数据格式;不写容易拿到 Date 对象或格林尼治格式,不利于提交后台。- 点击“今日/本周/本月/本年”本质是改
dateRange数组,组件会双向绑定回显。 - 易错点:
dayjs().startOf('week')/endOf('week')按老外习惯周日开始,国内项目要注意周一边界;转写稿中使用dayjs().day(1)表示周一,实际项目要处理周日边界。
示例3:首页中间静态搭建(el-card + el-tabs + el-row/el-col)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>首页中间静态搭建</title>
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/element-plus/dist/index.full.js"></script>
<style>
body {
margin: 40px;
font-family: Arial, sans-serif;
}
.middle-card {
margin: 10px 0;
}
.header {
position: relative;
margin-bottom: 10px;
}
.right {
position: absolute;
right: 0;
top: 0;
}
.right span {
color: yellowgreen;
margin-left: 5px;
cursor: pointer;
}
.chart-box,
.rank-box {
height: 260px;
background: #f5f7fa;
display: flex;
align-items: center;
justify-content: center;
color: #999;
}
</style>
</head>
<body>
<div id="app">
<el-card class="middle-card">
<div class="header">
<el-tabs v-model="activeName">
<el-tab-pane label="销售额" name="sales"></el-tab-pane>
<el-tab-pane label="访问量" name="visits"></el-tab-pane>
</el-tabs>
<div class="right">
<span>今日</span>
<span>本周</span>
<span>本月</span>
<span>本年</span>
</div>
</div>
<el-row :gutter="10">
<el-col :span="18">
<div class="chart-box">图表区域</div>
</el-col>
<el-col :span="6">
<div class="rank-box">门店销售额</div>
</el-col>
</el-row>
</el-card>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const activeName = ref('sales');
return { activeName };
}
}).use(ElementPlus).mount('#app');
</script>
</body>
</html>要点解释:
el-tabs的v-model绑定activeName,el-tab-pane的name对应切换值;默认sales时“销售额”高亮。el-row一行 24 栅格,el-col :span="18"与:span="6"形成左侧图表、右侧门店销售额布局。- 右侧“今日/本周/本月/本年”用绝对定位固定在头部右上角,避免 flex 撑开 tabs 宽度的问题。
- 易错点:Element Plus 组件内部样式在
scoped中常需:deep()深度选择器;栅格span总和应为 24;el-tabs的name不要和label混用。
常见错误
现象:父组件传了数组,子组件模板里
{{ arr }}能渲染,但 TS 逻辑里写arr.xxx直接报红/运行时 undefined。 原因:defineProps只在模板作用域自动展开,<script setup>里必须显式接住才能当变量用。 修法:const props = defineProps<{ arr: XXX[] }>(),逻辑里一律props.arr;别在模板里混用两种写法。现象:ECharts 组件初始化后图表空白,控制台报 option 相关错误,或改了
option名字却不生效。 原因:配置项名写错(如写成getOptions/options复数),或 option 是普通对象导致数据更新后视图不刷新。 修法:对照文档确认是getOption(无 s);数据变化需重算的用computed生成 option,避免一次性写死。现象:想用柱状图做横向进度条,结果柱子还是竖着、Y 轴刻度文字挡在前面、条宽巨粗。 原因:只改了
series.type,没把类目轴换到yAxis;轴线和刻度没隐藏;没设barWidth。 修法:xAxis设为数值轴(min:0 / max:100并隐藏轴线),yAxis设为type:'category'并show:false,series加barWidth与backgroundStyle。现象:用
::v-deep .el-tabs__item { color: xxx }改 Element Plus 样式,刷新后毫无变化。 原因:选择器名猜错(实际是el-tabs__active-bar/el-tabs__nav-wrap等),或改的是文字色而实际要改的是背景色,或预处理器语法不对(less 用/deep/、scss 用::v-deep/:deep())。 修法:先在浏览器审查元素拿到准确类名与准确属性(color 还是 background-color),再写深度选择器;改不到就往上找父级容器统一改。现象:日期范围选择器选完,
v-model拿到的是['2023-09-04', ...]之外的奇怪格式,或回显时页面不亮。 原因:没配value-format,默认给的是 Date 对象;或v-model绑的是字符串而非数组。 修法:加value-format="YYYY-MM-DD",v-model绑定ref<string[]>([]),回显时按同格式塞值。现象:点“本周”,日期区间变成周日~周六,和国内习惯(周一~周日)对不上。 原因:dayjs / moment 的
startOf('week')默认按西方习惯以周日为一周起点。 修法:改用dayjs().day(1)手动取周一,或引入weekStart: 1的 locale 配置;月/年用startOf('month')、startOf('year')无此问题。现象:头部“今日/本周/本月/本年”与右侧日期选择器用 flex 一左一右,结果日期选择器宽度被文字撑开、换行错位。 原因:日期组件在 flex 容器里没有固定宽度,被内容压缩。 修法:给日期组件设
width或flex: none,或干脆放弃 flex、对日期块用绝对定位贴右上角。
练习
- 把顶部第三个卡片的柱状图数据从写死改为
props传入,并保证父组件异步拿到数据后图表能刷新(提示:option 用computed)。 - 用横向柱状图完成第四个卡片的进度条:值为 86 时条长占 86%,右侧显示自定义文字“86%”,底色为浅灰、条为渐变绿。
- 用 dayjs 实现“今日/本周/本月/本年”四个按钮,点击后把
[开始, 结束]写回日期选择器的v-model,要求本周按周一起算,并验证回显。 - 用深度选择器把 Element Plus 选项卡的激活文字色改为黄绿色、底部滑块背景色改为同色,写出 scss 与 less 两种写法。
- 自定义一个
v-has指令:传入按钮权限标识数组,当前用户权限数组中不存在该标识时移除该 DOM,并在登录成功后验证效果。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 首页顶部第三个卡片完成 | 07:16 |
| 2 | 完成顶部四个卡片 | 18:45 |
| 3 | 完成首页中间静态的搭建 | 21:29 |
| 4 | 中间组件日历业务的完成 | 38:25 |
| 5 | 0904_161603 | 18:57 |
小结
- 图表类卡片的数据流固定为“父组件取数 → props 下发 → 子组件 computed 生成 option”,写死数据只适合搭静态阶段。
- 进度条不必新造轮子,横向柱状图 +
backgroundStyle+label.formatter即可,barWidth控制粗细。 - 第三方 UI 组件的样式修改,正确姿势是“审查元素拿准类名 → 深度选择器 → 改对属性”,猜类名是最常见的无效劳动。
- 日期类交互的核心是“格式统一”:
value-format决定收集格式,dayjs 决定计算与展示格式,两头必须一致。 - dayjs 与 moment 的 API 高度一致,会用其一即可迁移;但
startOf('week')的西方起点是必须记住的坑。 - 选项卡用
v-model绑定name值即可完成高亮切换,切换内容区靠name匹配,不必自己写激活逻辑。 - 菜单权限三步走:拆静态/异步/任意路由 → 登录后按用户标识过滤 →
router.addRoute动态追加;按钮权限一句话概括为自定义指令按标识判断。 - 布局能用 flex 就别用绝对定位;只有“必须贴角且不参与流”的元素(如右上角日期块)才用
position: absolute。