阅读视图
type-challenges(ts类型体操): 10 - 元组转合集
为网页注入灵魂:Live2D Widget看板娘,打造会动的互动伙伴!
什么是大语言模型-00
Flutter最佳实践:Sliver族网络刷新组件NCustomScrollView
flutter添加间隙gap源码解析
React 手写实现的 KeepAlive 组件 🚀
React-Hooks逻辑复用艺术
一个Vite插件实现PC到移动端项目的高效迁移方案
《打造高效的前端工程师终端:一份可复制的 Zsh + Powerlevel10k 配置实践》
React-Scheduler 调度器如何掌控主线程?
如何监控qiankun中子应用的内存使用情况
【配置化 CRUD 完结篇】新增编辑页 : 统一风格
深度解析 React Router v6:构建企业级单页应用(SPA)的全栈式指南
vite+vue3+antd4项目兼容低版本chrome86+ && chrome68+
兼容性问题解决方案汇总
一、兼容到chrome86+
1.antd4 向下兼容方案
1、文档流中的样式和位置问题,主入口app.vue添加如下内容, 解决方案:
App组件容易漏,会导致message提示无法显示,需要注意~
import { ConfigProvider, App } from 'ant-design-vue';
import { StyleProvider, legacyLogicalPropertiesTransformer } from 'ant-design-vue/es/_util/cssinjs';
<StyleProvider
hash-priority="high"
:prefix="configProviderPrefixCls"
:transformers="[legacyLogicalPropertiesTransformer]"
>
<a-style-provider
hash-priority="high"
:prefix="configProviderPrefixCls"
:transformers="[legacyLogicalPropertiesTransformer]"
>
<App>
<RouterView />
</App>
</a-style-provider>
</StyleProvider>
2、 全局弹出、全局提示等脱离文档流的位置问题。 解决方案:
import { App } from 'ant-design-vue';
使用 const { modal } = App.useApp(); 替换Modal.confirm等,message alert 同理
3、 前缀是antd,没有被prefix前缀处理到的组件位置问题(如画布节点) 解决方案:采用问题一的解决方案,把画布自定义节点包裹一次
4、 使用creatApp或者creatVnode创建的模块,脱离了vue的上下文文档流
解决方案:采用问题一的解决方案,把节点包裹一次
样式及js兼容修改
1.建议初始化配置时在vite.config中加上如下内容:
build:{
... 原来的内容,
// js最低兼容的浏览器版本
target: ['chrome86', 'edge88', 'firefox78', 'safari14'],
// 禁用 CSS 代码压缩,防止 top/right/bottom/left 被转换成 inset
cssMinify: false
}
本地启动报错
1.集成unocss出现报错问题
注意⚠️: 如果使用了unocss或taiwindcss覆盖antd原有的样式会失效,兼容后antd的样式等级会提高。我采取的方法比较笨但可靠,在覆盖的css类名后面添加‘!’,编译后会给unocss或taiwindcss的css后面添加!important。这样能解决!
position导致的样式失效
360极速版position导致的样式失效 如果同时有top:0;left:0,bottom:0;right:0;vite会打包成一个insert:0,但是360极速版不支持该属性加上width:100%和height:100%
二、兼容到chrome68+
在兼容chrome86的基础上添加如下配置
全程基于项目根目录执行操作,核心依赖为 Vite5 官方兼容插件@vitejs/plugin-legacy+ API 补全库core-js@3,所有配置可直接复制使用,按步骤执行即可完成兼容。
前提:Chrome68 支持 ES2017,缺失 ES2018+ 新语法(?.、?? 等)和部分全局 API,兼容核心是语法转译 + 自动 polyfill 注入 + 适配构建目标。
步骤 1:安装核心兼容依赖
# npm 安装
npm install @vitejs/plugin-legacy core-js @babel/core -D
步骤 2:配置 browserslist(统一所有工具的兼容目标)
在项目根目录的package.json中新增browserslist字段,统一 Vite、Babel、PostCSS 的浏览器兼容规则,仅指定Chrome 68即可,修改后如下:
{
"name": "your-vue-project", // 你的项目名
"version": "0.0.0",
"browserslist": ["Chrome 68"], // 新增这行,统一兼容目标
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
// 保留项目原有dependencies、devDependencies等配置...
}
步骤 3:修改 Vite 核心配置(vite.config.js/ts)
这是兼容的核心步骤,修改项目根目录的vite.config.js(ts 项目为vite.config.ts,配置完全一致),完成插件注册 + 构建目标降级 + CSS 适配,直接替换原有配置即可:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import legacy from '@vitejs/plugin-legacy' // 1. 引入legacy兼容插件
// Vite5 核心配置
export default defineConfig({
plugins: [
vue(), // Vue3 插件
// 2. 配置legacy插件,自动转译语法+注入polyfill
legacy({
targets: 'Chrome 68', // 明确指定兼容Chrome68(也可读取browserslist)
polyfills: true, // 开启自动polyfill注入(基于core-js@3)
renderLegacyChunks: true, // 生成旧浏览器兼容产物
modernPolyfills: true // 现代浏览器兜底polyfill
})
],
build: {
target: 'es2017', // 3. 构建目标降级为Chrome68支持的ES2017(Vite5默认es2020)
cssTarget: 'chrome68', // 4. CSS适配Chrome68,避免生成不兼容CSS语法
minify: 'terser', // 可选:用terser压缩,避免新压缩特性导致兼容问题
terserOptions: {
compress: { drop_console: false } // 可选:保留console,方便调试
}
}
})
步骤 4:Vue3.3.0 兼容兜底(无额外配置,仅 2 个避坑点)
Vue3 本身已放弃 IE 但完全兼容 Chrome68,无需修改任何 Vue 相关配置,只需注意 2 个细节即可:
- 代码中避免使用 Vue3 的实验性特性(如
defineModel高级用法、props解构的新特性),若使用,legacy 插件会自动转译; - Vue3 模板编译产物为 ES5 级别,Chrome68 可直接解析,无需修改
@vitejs/plugin-vue的编译配置。
步骤 5:执行生产构建(兼容逻辑仅对构建生效)
Vite 的 legacy 兼容处理仅在生产构建时生效(开发环境 Chrome68 已支持 ES 模块,可直接运行npm run dev开发,无需额外处理,本地还是无法访问的!!!!!),执行构建命令生成兼容包:
# 生成兼容Chrome68的生产包,输出到项目根目录的dist文件夹
npm run build
构建完成后,dist目录会自动生成 2 类产物,且index.html内置浏览器嗅探逻辑:Chrome68 会自动加载兼容产物 + polyfill,高版本浏览器加载现代产物,无需手动判断。
注意: 本地无法访问,看不到页面呈现,只有部署后才能打开!!!!!!!
步骤 6:Chrome68 兼容性测试(2 种便捷方法,无需安装旧浏览器)
方法 1:Chrome 开发者工具模拟(推荐,最快)
- 打开新版 Chrome 浏览器,运行
npm run preview启动预览服务,打开项目预览地址; - 按
F12打开开发者工具 → 点击右上角「⋮」→ 更多工具 → 设备模拟; - 左上角设备下拉框选择「自定义」→ 输入
Chrome 68,刷新页面即可模拟运行。
方法 2:本地启静态服务测试
- 全局安装静态服务工具
serve:npm install serve -g; - 项目根目录执行:
serve dist,会生成本地访问地址(如http://localhost:3000); - 在模拟的 Chrome68 中访问该地址,验证页面渲染、按钮点击、接口请求等交互是否正常。
步骤 7:常见兼容问题解决(按需处理)
若测试时出现报错 / 样式错乱,按以下场景针对性解决,均为 Chrome68 兼容的高频问题:
问题 1:第三方依赖未被转译(如 Element Plus/axios 用了 ES2018 + 语法)
Vite5 默认不转译node_modules,需在vite.config.js中强制指定转译的依赖,修改后如下:
export default defineConfig({
// 保留原有配置...
optimizeDeps: {
include: ['element-plus', 'axios'] // 按需添加需要转译的第三方依赖
},
build: {
// 保留原有配置...
commonjsOptions: {
include: [/element-plus/, /axios/, /node_modules/] // 强制转为CommonJS格式
}
}
})
问题 2:个别全局 API 未被 polyfill(如 URLSearchParams/fetch)
Chrome68 已支持大部分 API,若遇到缺失,在 src/main.js/ts 中手动引入即可:
// src/main.js(Vue入口文件)
import 'core-js/es/url-search-params' // 手动注入URLSearchParams polyfill
import 'core-js/es/fetch' // 按需注入fetch polyfill
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
问题 3:CSS 样式错乱(如高级 CSS 选择器 / 特性)
Chrome68 支持基础 CSS 变量 / Flex 布局,样式问题多为第三方 CSS 用了高级特性,安装postcss-preset-env兜底:
- 安装依赖:
pnpm add postcss-preset-env -D - 项目根目录新建
postcss.config.js,添加配置:
module.exports = {
plugins: [
require('postcss-preset-env')({
browsers: 'Chrome 68', // 适配Chrome68
features: { 'custom-properties': { preserve: true } } // 保留CSS变量
})
]
}
核心步骤总结
- 安装
@vitejs/plugin-legacy@5.x + core-js@3 + @babel/core三大核心依赖; -
package.json中配置browserslist: ["Chrome 68"]统一兼容目标; - Vite 配置中注册 legacy 插件,降级
build.target为 es2017、cssTarget为 chrome68; - 执行
npm run build生成兼容包,通过 Chrome 开发者工具模拟 Chrome68 测试; - 按需解决第三方依赖转译、手动 polyfill、CSS 适配等问题。
公司低代码框架-列表个性化开发最佳实践
一、引言
当前低代码组件的功能框架已趋于稳定,而业务侧的需求设计却持续迭代、不断涌现。要落地各类个性化需求,正需要我们秉持‘人有多大胆,地有多大产’的探索精神,勇于构思、大胆尝试。比如低代码列表中,针对字段内容过多的问题,就需要自己开发部分展示的功能。
二、使用场景
1、列表只展示前三行,剩余的放在查看按钮内,弹框展示
实现思路:借助列表字段的自定义内容-复杂模式,实现设计稿里的展示效果,在页面加载事件里监听点击事件,实现弹窗效果
let pileList = rowData.row.AccountList || [];
let resStrShow = '';
let resStrHide = '';
for (let id = 0; id < pileList.length; id++) {
let itemShow = "";
...
if (rowData.row.AutoChargeType == '2') {//企业总账户
if (pileList.length > 1) {
itemShow += `${pileList[id].BusUnitAttrCompanyName}:`;
}
let BusUnitOrGroupBalance = Funcs.FormatDecimal(pileList[id]?.BusUnitOrGroupBalance, 2, "", ".").replace(/,/g, '');
itemShow += `企业现金余额${BusUnitOrGroupBalance}元`;
}
...
}
if (id < 3) {
if (resStrShow) {
resStrShow += '</br>';
}
resStrShow += itemShow;
}
if (resStrHide) {
resStrHide += '</br>';
}
resStrHide += itemShow;
}
if (pileList.length > 3) {
let tipQuestion = `<div style="opacity: 1; margin-top: 2px;" instancecode=""tabindex="1">`
+ `<span>`
+ `<span class="qiestionIcon" style="position: relative;display: flex;justify-content: flex-start;">`
+ `<i class="material-icons" style="display:none" aria-hidden="true" role="presentation">help_outline</i>`
+ `<div class="viewAll"
style="
padding:0px 7px;
border-radius:10px;
border:1px solid #D9D9D9;
font-family:AlibabaPuHuiTi;
font-size:12px;
cursor: pointer;
color:#3656FF;"
>查看全部(${pileList.length})</div>`
+ `<div style="visibility:hidden;position: absolute; width: 560px; height: auto; left: 0px; top: -8px; box-sizing: content-box; padding-top: 8px;margin-left:20px;">`
+ `<span style="" class="resStrHide">${resStrHide}</span>`
+ `</div>`
+ `</span>`
+ `</span>`
+ `</div>`;
return `<div style="position: relative; display: inline-flex;flex-direction: column;">
<div>${resStrShow}</div>
${tipQuestion}
</div>`;
} else {
return resStrShow;
}
js代码,点击实现弹框效果:
var content = document.querySelector(`.UIControl_VehicleSettingList_Ecms_New`);
content.addEventListener('click', function (event) {
if (event.target.className == "viewAll") {
const nextSiblingElement = event.target.nextElementSibling;
if (nextSiblingElement) {
const targetSpan = nextSiblingElement.querySelector('span.resStrHide');
if (targetSpan) {
LocalVars.Variable_viewAll = targetSpan.innerHTML//把全部内容赋值给弹窗变量
Widget.fasr_dialog_viewALLNew.showDialog()//展示弹框
}
}
}
});
2、列表只展示前三个,剩余的放在悬浮气泡里展示
实现思路:借助列表字段的自定义内容-复杂模式,实现设计稿里的展示效果,在页面加载事件里监听mousemove事件,实现气泡效果
let PileRangeDesc = rowData.row.PileRangeDesc || '';
let pileList = PileRangeDesc.split(',') || [];
let resStrShow = '';
let resStrHide = '';
for (let id = 0; id < pileList.length; id++) {
if (id < 3) {
if (resStrShow) {
resStrShow += '、';
}
resStrShow += pileList[id];
} else {
if (resStrHide) {
resStrHide += '、';
}
resStrHide += pileList[id]
}
}
if (pileList.length > 3) {
let tipQuestion = `<div style="opacity: 1;margin-left:7px" instancecode=""tabindex="1">`
+ `<span>`
+ `<span class="qiestionIcon" style="position: relative;display: flex;justify-content: center;">`
+ `<i class="material-icons" style="display:none" aria-hidden="true" role="presentation">help_outline</i>`
+ `<div
style="
padding:0px 7px;
border-radius:10px;
border:1px solid #D9D9D9;
font-family:AlibabaPuHuiTi;
font-size:12px;
color:#3656FF;"
>+${pileList.length - 3}</div>`
+ `<div style="visibility:hidden;position: absolute; width: 560px; height: auto; left: 0px; top: -8px; box-sizing: content-box; padding-top: 8px;margin-left:20px;">`
+ `<div class="q-tooltip--style q-position-engine arrow-top question-tooltip" style="--q-transition-duration: 30oms; --left: 27px; --top: false; -px;width:auto;
padding: 16px 20px;
background:#fff;
font-family: AlibabaPuHuiTi;
font-weight: 400;
font-size: 14px;
line-height: 20px;
border-radius: 12px;
color: rgba(0,0,0,0.8);
max-width: 560px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
margin-bottom:0;">`
+ `<span style="width:100%;
display: inline-block;
white-space: pre-wrap;">${resStrHide}</span>`
+ `<div style="width:12px;
position:absolute;
visibility:visible;
bottom:-12px;
height:12px;
left:calc(50% - 6px);
background-color:#fff;
margin-top:0;
clip-path: polygon(0 0,100% 0, 50% 100%);"></div>`
+ `</div>`
+ `</div>`
+ `</span>`
+ `</span>`
+ `</div>`;
return `<div style="position: relative; display: inline-flex;align-items:center">
<div>${resStrShow}</div>
${tipQuestion}
</div>`;
} else {
return resStrShow;
}
js代码,实现气泡效果:
var content = document.querySelector(`.${LocalVars.InParam_UISign}`);
content.addEventListener('mousemove', function (event) {
let table = content.querySelector(".fasr_mixview");
if (table) {
let tipElems = table.getElementsByClassName("question-tooltip");
if (tipElems.length > 0) {
for (let id in tipElems) {
if (tipElems[id].style) {
let parentNode = tipElems[id].parentNode;
// 清除TD提示框
let signParentNode = parentNode;
for (let i = 0; i < 10; i++) {
if (signParentNode && signParentNode.tagName === 'TD') {
signParentNode.title = '';
break;
}
signParentNode = signParentNode.parentNode;
}
let position = parentNode.getBoundingClientRect()
let pX = position.left;
let pY = position.top;
let height = tipElems[id].offsetHeight
let width = tipElems[id].offsetWidth
// tipElems[id].style.left = pX - width / 2 - 14.3 + 'px';
tipElems[id].style.left = pX - width / 2 - 4 + 'px';
tipElems[id].style.top = pY - height - 7 + 'px';
}
}
}
}
});
3、列表格式化时间/字段内容
如果后台给我们的数据未经格式化,需要前端再次处理,我们除了在action里的返回值自定义扩展里面进行修改外,也可以利用列表字段的自定义设置,更加方便
例如,格式化时间:
return Funcs.FormatDateTime(rowData.row.LastModifyTime, 'yyyy-MM-dd HH:mm:ss');
格式化字段,没有值时展示‘--’
return `<div id="div">${rowData.value?rowData.value:'--'}</div>`
4、列表字段不固定,根据某个值动态展示部分字段,隐藏部分字段
实现思路:设置pc查询方案延迟加载,在onLoaded中,根据当前的模式处理当前方案下需要展示的字段,手动触发查询
setTimeout(() => {
let Scheme = Widget.fasr_mixed_view.getScheme()
let ListViewSet = JSON.parse(Scheme.ListViewSet);
if( LocalVars.Variable_mdoe=="1"){
ListViewSet.displayColumns = ListViewSet.displayColumns.filter(item => (item.value != "AccountDesc"&&item.value != "AutoChargeTypeName"))
}
if( LocalVars.Variable_mdoe=="2"|| LocalVars.Variable_mdoe=="3"|| LocalVars.Variable_mdoe=="4"){
ListViewSet.displayColumns = ListViewSet.displayColumns.filter(item => (item.value != "CarUseCustMobile"))
}
Scheme.ListViewSet = JSON.stringify(ListViewSet)
Widget.fasr_mixed_view.setScheme(Scheme)
}, 0)
5、列表的查询条件不固定,根据某个值动态展示隐藏
实现思路:我们可以利用js操作原始dom,对条件进行显示隐藏,简单实现这个需求(低代码提供了硬写的方案,但是很繁琐)
if(LocalVars.Variable_PVMismatchSwitch == "0"){
document.querySelector(".Page_DriverManageListALLForNew .el-col-6:last-of-type").style.display = "none";
}else{
document.querySelector(".Page_DriverManageListALLForNew .el-col-6:last-of-type").style.display = "block";
}
6、列表某些场景下列表每页的条数不允许切换,始终保持每页10条
实现思路:通过修改元素的行内样式,让这个元素彻底无法响应任何鼠标 / 触摸交互事件
document.querySelector(".t-prefab-pagination__perfective-page-size").style.pointerEvents='none';
7、列表按钮点击触发服务响应缓慢,超过3秒,为防止白屏手动弹出loading
低代码提供了Funcs.ShowLoading(),但是不生效,考虑使用原生dom实现
let eload1 = window.top.document.getElementById('tff_page_loading');
eload1 && (eload1.style.display = "block");//显示
eload1 && (eload1.style.display = "none");//隐藏
8、列表按钮触发后端服务每次只能校验一个,批量操作下需等待所有检查完成后才能进行下一步
实现思路:Promise.all()
const checkPromises = [];
let hasBoundVehicle = false;
let CarLicenseArr = [];
// 1. 检查所有车辆是否被绑定
selectedRowDatas.forEach(item => {
if (item.VehiclesID) {
checkPromises.push(
Action.Action_CheckVehicleIfBindMembers_Ecms({ VehicleID: item.VehiclesID })
.then(result => {
const hasCarObj = JSON.parse(result.Record.Data);
if (hasCarObj.data) {
CarLicenseArr.push(item.CarLicense);
hasBoundVehicle = true;
}
})
);
}
});
// 2. 等待所有检查完成
Promise.all(checkPromises).then(() => {
debugger
var loadingMask = document.querySelector("#mvcFrameDiv")?.querySelector(".web-loading_mask");
loadingMask && (loadingMask.style.display = "none");
console.log(CarLicenseArr)
let tipI = ""
if (CarLicenseArr.length) {
CarLicenseArr.map(item => {
tipI += item + "、"
});
tipI = "当前车辆" + tipI.substring(0, tipI.length - 1) + "已被司机绑定,删除后司机将无法使用企业账户为该车辆充电,确认要删除吗?";
Funcs.Confirm(confirm, tipI, function () {
VehicleInformationNewUnbind()
})
} else {
Funcs.Confirm(confirm, confirmInfo, function () {
VehicleInformationNewUnbind()
})
}
});
9、移动端列表没有数据时‘暂无数据’不展示
后端接口返回的count值不对,低代码模版根据count值决定是否展示暂无数据
10、移动端app框架内页面修改表头标题
需写在onloading方法里
if(TFF.common.ctx.CLIENT_TYPE.TeldApp == TFF.common.ctx.getClientType()){
window.pageNameTerm = ''
window.pageTitle = '添加司机'
if(LocalVars.InParam_FormState ==1){
window.pageTitle ='编辑司机'
}
TFF.jsdk.ready({ url: '', sgUrlPrefix: '' }, () => {
window.envApi.setTitle({
"title" : window.pageTitle ,
success : function (res) {
}
})
});
}
css代码规范
css规范
使用属性简写
/* 使用属性简写 */
.container {
margin: 10px 20px;
}
/* 不使用属性简写 */
.container {
margin-top: 10px;
margin-right: 20px;
margin-bottom: 10px;
margin-left: 20px;
}
统一样式格式
/* 统一使用双引号 */
.button {
font-family: "Arial", sans-serif;
}
/* 统一使用分号结尾 */
.button {
color: red;
}
避免使用全局样式
全局样式是指能够影响整个页面的样式,比如 body 元素或者 * 选择器。虽然在某些情况下使用全局样式会很方便,但是过度依赖全局样式会增加代码的复杂度,降低代码的可维护性。在编写 CSS 代码时,应该尽量避免使用全局样式,而是优先使用类名和 ID 来控制样式。
!important
除公共样式之外,在业务代码中尽量不能使用 !important
z-index
建议将 z-index 进行分层,对文档流外绝对定位元素的视觉层级关系进行管理。
不要为 id、class 选择器添加类型选择器
/* good */
#error,.message {
font-color: #c00;
}
/* bad */
input#error,p.message {
font-color: #c00;
}
文字排版
- 字号 不要小于12px
- 字重使用数值
- 行高使用数字
/* good */
h1 {
font-weight: 700;
line-height: 1.5;
font-size: 12px
}
/* bad */
h1 {
font-weight: bold;
line-height: 15px;
font-size: 10px
}
清楚浮动
.clearfix::before,
.clearfix::after {
content: "";
display: block;
clear: both;
}
CSS代码命名规范
前端css代码规范:主要遵循BEM命名规范,BEM分别对应的是block,element和modifier,为的是结束混乱的命名方式,达到一个语义话的css命名方式
BEM命名规范
block
block 表示一个外层组件的意思,表示盒子的呈现的内容,如button,card,tabs等,在块被写为和class的名字一样 常见的block有:
布局类
.header /* 页头 */
.footer /* 页脚 */
.sidebar /* 侧边栏 */
.container /* 容器 */
.layout /* 布局 */
.grid /* 网格布局 */
.wrapper /* 包装器 */
.frame /* 框架 */
.holder /* 容器 */
.box /* 盒子 */
.panel /* 面板 */
.segment /* 分段 */
.section /* 区块 */
.stack /* 堆叠布局 */
.column /* 列 */
.row /* 行 */
.main /* 主要内容区 */
.aside /* 侧边内容 */
UI组件类
.button /* 按钮 */
.modal /* 模态框 */
.card /* 卡片 */
.form /* 表单 */
.input /* 输入框 */
.select /* 选择器 */
.checkbox /* 复选框 */
.radio /* 单选框 */
.toggle /* 切换开关 */
.switch /* 开关 */
.slider /* 滑块 */
.range /* 范围选择器 */
.stepper /* 步进器 */
.rate /* 评分组件 */
导航类
.menu /* 菜单 */
.nav /* 导航 */
.breadcrumb /* 面包屑 */
.pagination /* 分页 */
.tabs /* 标签页 */
.dropdown /* 下拉菜单 */
.pager /* 分页器 */
.quick-nav /* 快速导航 */
.action-sheet /* 动作面板 */
.bottom-sheet /* 底部面板 */
.drawer /* 抽屉 */
表单控件
.search /* 搜索框 */
.filter /* 过滤器 */
.sort /* 排序器 */
.upload /* 上传组件 */
.date-picker /* 日期选择器 */
.time-picker /* 时间选择器 */
.color-picker /* 颜色选择器 */
.uploader /* 上传器 */
.search-bar /* 搜索栏 */
.filter-bar /* 筛选栏 */
.textarea /* 文本域 */
.file-input /* 文件输入 */
数据展示
.table /* 表格 */
.list /* 列表 */
.chart /* 图表 */
.graph /* 图形 */
.gauge /* 仪表盘 */
.progress /* 进度条 */
.timeline /* 时间轴 */
.statistic /* 统计数字 */
.counter /* 计数器 */
.metric /* 指标 */
.dashboard /* 仪表板 */
.data-table /* 数据表格 */
.pie-chart /* 饼图 */
.bar-chart /* 柱状图 */
.line-chart /* 折线图 */
内容容器
.accordion /* 手风琴 */
.carousel /* 轮播图 */
.widget /* 小部件 */
.tile /* 磁贴 */
.collection /* 集合 */
.feed /* 信息流 */
.stream /* 流 */
.grid-view /* 网格视图 */
.list-view /* 列表视图 */
.portfolio /* 作品集 */
.gallery /* 画廊 */
交互反馈
.alert /* 警告提示 */
.toast /* 轻量提示 */
.notification /* 通知 */
.snackbar /* 底部提示 */
.popover /* 弹出框 */
.tooltip /* 工具提示 */
.spinner /* 加载器 */
.skeleton /* 骨架屏 */
.placeholder /* 占位符 */
.overlay /* 遮罩层 */
.backdrop /* 背景遮罩 */
.indicator /* 指示器 */
多媒体
.player /* 播放器 */
.audio-player /* 音频播放器 */
.video-player /* 视频播放器 */
.lightbox /* 灯箱 */
.slideshow /* 幻灯片 */
.image /* 图片容器 */
.video /* 视频容器 */
.audio /* 音频容器 */
装饰元素
.avatar /* 头像 */
.icon /* 图标 */
.badge /* 徽章 */
.label /* 标签 */
.chip /* 碎片标签 */
.tag /* 标签 */
.mark /* 标记 */
.highlight /* 高亮 */
.divider /* 分割线 */
.ornament /* 装饰元素 */
.decoration /* 装饰 */
.pattern /* 图案 */
文本相关
.heading /* 标题 */
.subheading /* 副标题 */
.caption /* 说明文字 */
.quote /* 引用 */
.code-block /* 代码块 */
.text /* 文本容器 */
.article /* 文章 */
.testimonial /* 推荐语 */
.title /* 标题 */
.subtitle /* 副标题 */
.paragraph /* 段落 */
功能模块
.wizard /* 向导步骤 */
.tour /* 引导漫游 */
.help /* 帮助组件 */
.sorter /* 排序器 */
.paginator /* 分页器 */
.wishlist /* 收藏夹 */
.cart /* 购物车 */
.checkout /* 结账流程 */
.settings /* 设置面板 */
.profile /* 个人资料 */
.account /* 账户管理 */
移动端
.floating-action /* 浮动按钮 */
.swipe /* 滑动组件 */
.swipeable /* 可滑动 */
.pull-to-refresh /* 下拉刷新 */
.infinite-scroll /* 无限滚动 */
.app-bar /* 应用栏 */
.bottom-nav /* 底部导航 */
.tab-bar /* 标签栏 */
业务相关
.product-card /* 商品卡片 */
.quick-view /* 快速查看 */
.add-to-cart /* 加入购物车 */
.review /* 评论 */
.rating-stars /* 星级评分 */
.price-display /* 价格显示 */
.post /* 帖子 */
.comment-thread /* 评论线程 */
.like-button /* 点赞按钮 */
.share-menu /* 分享菜单 */
.notification-bell /* 通知铃 */
.profile-card /* 个人资料卡片 */
营销展示
.banner /* 横幅 */
.hero /* 主视觉区 */
.feature /* 特色区 */
.cta /* 行动号召 */
.promo /* 推广区块 */
.spotlight /* 聚光灯区 */
.showcase /* 展示区 */
.ad /* 广告 */
.newsletter /* 新闻订阅 */
形状组件
.circle /* 圆形 */
.square /* 方形 */
.triangle /* 三角形 */
.diamond /* 菱形 */
.hexagon /* 六边形 */
.oval /* 椭圆形 */
.polygon /* 多边形 */
状态指示器
.status /* 状态显示 */
.signal /* 信号指示 */
.dot /* 点状指示 */
.marker /* 标记 */
.flag /* 标志 */
链接相关
.link /* 链接 */
.external-link /* 外部链接 */
.internal-link /* 内部链接 */
.nav-link /* 导航链接 */
.button-link /* 按钮样式链接 */
用户界面
.user-profile /* 用户资料 */
.user-settings /* 用户设置 */
.preferences /* 偏好设置 */
.theme-switcher /* 主题切换器 */
通知系统
.notification-center /* 通知中心 */
.notification-bell /* 通知铃 */
.message-center /* 消息中心 */
.inbox /* 收件箱 */
数据分析
.analytics /* 分析 */
.report /* 报告 */
.report-generator /* 报告生成器 */
.data-export /* 数据导出 */
.statistics /* 统计数据 */
系统管理
.admin-panel /* 管理面板 */
.system-status /* 系统状态 */
.backup /* 备份 */
.restore /* 恢复 */
.logs /* 日志 */
.audit /* 审计 */
时间相关
.timeline /* 时间线 */
.schedule /* 计划 */
.calendar /* 日历 */
.time-range /* 时间范围 */
.date-selector /* 日期选择器 */
状态指示
.status-indicator /* 状态指示器 */
.health-status /* 健康状态 */
.connection-status /* 连接状态 */
.battery-level /* 电池电量 */
图表类型
.line-chart /* 折线图 */
.bar-chart /* 柱状图 */
.pie-chart /* 饼图 */
.area-chart /* 面积图 */
.scatter-plot /* 散点图 */
.heatmap /* 热力图 */
.gauge-chart /* 仪表图 */
工具类组件
.toolbar /* 工具栏 */
.context-menu /* 上下文菜单 */
.shortcut /* 快捷方式 */
.quick-action /* 快速操作 */
.bulk-action /* 批量操作 */
.wizard /* 向导 */
.tour /* 引导 */
.help-tooltip /* 帮助提示 */
响应式组件
.mobile-view /* 移动端视图 */
.tablet-view /* 平板视图 */
.desktop-view /* 桌面视图 */
.responsive-grid /* 响应式网格 */
.adaptive-layout /* 自适应布局 */
交互组件
.drag-drop /* 拖放 */
.dropzone /* 放置区域 */
.draggable /* 可拖动 */
.sortable /* 可排序 */
.resizable /* 可调整大小 */
.collapsible /* 可折叠 */
.accordion /* 手风琴 */
存储相关
.storage /* 存储 */
.storage-usage /* 存储使用情况 */
.quota /* 配额 */
.backup-status /* 备份状态 */
.cloud-storage /* 云存储 */
.local-storage /* 本地存储 */
安全组件
.authentication /* 身份验证 */
.login /* 登录 */
.logout /* 登出 */
.password /* 密码 */
.two-factor /* 双重认证 */
.encryption /* 加密 */
.security-log /* 安全日志 */
监控组件
.monitoring /* 监控 */
.real-time /* 实时 */
.historical /* 历史 */
.trend-analysis /* 趋势分析 */
.performance /* 性能 */
.uptime /* 运行时间 */
.downtime /* 停机时间 */
同步组件
.sync /* 同步 */
.sync-status /* 同步状态 */
.offline /* 离线 */
.online /* 在线 */
.conflict /* 冲突 */
.conflict-resolution /* 冲突解决 */
报告组件
.report /* 报告 */
.report-builder /* 报告构建器 */
.report-template /* 报告模板 */
.export-report /* 导出报告 */
.print-report /* 打印报告 */
.schedule-report /* 计划报告 */
Element
元素是块的子节点,元素表示的目的,而不是状态。块和元素之间用一个双下划线划开。
布局结构类
__header /* 头部 */
__footer /* 脚部 */
__body /* 主体 */
__main /* 主要区域 */
__aside /* 侧边区域 */
__content /* 内容区域 */
__container /* 容器 */
__wrapper /* 包装器 */
__inner /* 内部容器 */
__section /* 区块 */
__group /* 分组 */
__panel /* 面板 */
__frame /* 框架 */
导航与交互
__item /* 项目/项 */
__link /* 链接 */
__button /* 按钮 */
__icon /* 图标 */
__toggle /* 切换开关 */
__arrow /* 箭头 */
__caret /* 指示箭头 */
__handle /* 手柄/把手 */
__drag /* 拖拽手柄 */
__scroll /* 滚动区域 */
__trigger /* 触发器 */
__action /* 操作按钮 */
内容显示
__title /* 标题 */
__subtitle /* 副标题 */
__heading /* 标题(可细分 __heading-1, __heading-2) */
__text /* 文本 */
__label /* 标签文字 */
__caption /* 说明文字 */
__description/* 描述 */
__summary /* 摘要 */
__paragraph /* 段落 */
__quote /* 引用 */
__code /* 代码 */
__image /* 图片 */
__video /* 视频 */
__audio /* 音频 */
__media /* 媒体内容 */
表单元素
__input /* 输入框 */
__textarea /* 文本域 */
__select /* 选择框 */
__option /* 选项 */
__checkbox /* 复选框 */
__radio /* 单选按钮 */
__field /* 表单字段 */
__label /* 表单标签 */
__hint /* 提示文字 */
__error /* 错误信息 */
__success /* 成功信息 */
__warning /* 警告信息 */
__validation /* 验证信息 */
列表与集合
__list /* 列表 */
__item /* 列表项 */
__entry /* 条目 */
__row /* 行 */
__cell /* 单元格 */
__col /* 列 */
__grid /* 网格项 */
__card /* 卡片 */
__tile /* 磁贴 */
__block /* 块状项 */
__segment /* 分段 */
__piece /* 片段 */
信息与状态
__status /* 状态显示 */
__badge /* 徽章 */
__tag /* 标签 */
__marker /* 标记 */
__indicator /* 指示器 */
__signal /* 信号 */
__dot /* 点状指示 */
__counter /* 计数器 */
__number /* 数字显示 */
__value /* 数值 */
__percentage /* 百分比 */
__rating /* 评分 */
__star /* 星星 */
装饰与辅助
__icon /* 图标 */
__avatar /* 头像 */
__thumbnail /* 缩略图 */
__preview /* 预览图 */
__background /* 背景 */
__overlay /* 遮罩层 */
__shadow /* 阴影 */
__border /* 边框 */
__divider /* 分割线 */
__separator /* 分隔符 */
__ornament /* 装饰元素 */
__pattern /* 图案 */
__gradient /* 渐变 */
工具与控制
__toolbar /* 工具栏 */
__tool /* 工具按钮 */
__control /* 控制元素 */
__settings /* 设置按钮 */
__config /* 配置 */
__option /* 选项按钮 */
__switch /* 开关 */
__slider /* 滑块 */
__handle /* 滑块手柄 */
__knob /* 旋钮 */
__dial /* 刻度盘 */
时间与进度
__time /* 时间显示 */
__date /* 日期显示 */
__timestamp /* 时间戳 */
__duration /* 时长 */
__progress /* 进度条 */
__bar /* 进度条条状部分 */
__track /* 轨道 */
__step /* 步骤 */
__milestone /* 里程碑 */
__clock /* 时钟 */
__calendar /* 日历 */
交互反馈
__loading /* 加载指示器 */
__spinner /* 旋转器 */
__skeleton /* 骨架屏元素 */
__placeholder/* 占位符 */
__hint /* 提示 */
__tooltip /* 工具提示内容 */
__popup /* 弹出内容 */
__message /* 消息内容 */
__alert /* 警告内容 */
__toast /* 轻提示内容 */
__notification /* 通知内容 */
导航组件 (Navigation)
/* .menu 的 Element */
__item /* 菜单项 */
__link /* 菜单链接 */
__icon /* 菜单图标 */
__text /* 菜单文字 */
__submenu /* 子菜单 */
__dropdown /* 下拉菜单 */
/* .tabs 的 Element */
__tab /* 标签页 */
__content /* 标签内容 */
__nav /* 标签导航 */
__panel /* 标签面板 */
/* .breadcrumb 的 Element */
__crumb /* 面包屑项 */
__separator /* 分隔符 */
表单组件 (Forms)
/* .form 的 Element */
__group /* 表单组 */
__field /* 表单字段 */
__label /* 标签 */
__input /* 输入框 */
__error /* 错误信息 */
__help /* 帮助文字 */
__submit /* 提交按钮 */
__reset /* 重置按钮 */
/* .input 的 Element */
__wrapper /* 包装器 */
__field /* 输入区域 */
__prefix /* 前缀 */
__suffix /* 后缀 */
__clear /* 清除按钮 */
卡片组件 (Cards)
/* .card 的 Element */
__header /* 卡片头部 */
__title /* 卡片标题 */
__subtitle /* 卡片副标题 */
__body /* 卡片主体 */
__content /* 卡片内容 */
__footer /* 卡片脚部 */
__image /* 卡片图片 */
__actions /* 卡片操作区 */
__button /* 卡片按钮 */
模态框 (Modals)
/* .modal 的 Element */
__overlay /* 遮罩层 */
__dialog /* 对话框 */
__header /* 头部 */
__title /* 标题 */
__close /* 关闭按钮 */
__body /* 主体 */
__content /* 内容 */
__footer /* 脚部 */
__actions /* 操作区 */
数据表格 (Tables)
/* .table 的 Element */
__header /* 表头 */
__head /* 头部区域 */
__body /* 表格主体 */
__footer /* 表格脚部 */
__row /* 行 */
__cell /* 单元格 */
__col /* 列 */
__sort /* 排序按钮 */
__filter /* 筛选按钮 */
__pagination /* 分页区域 */
工具栏 (Toolbars)
/* .toolbar 的 Element */
__left /* 左侧区域 */
__center /* 中间区域 */
__right /* 右侧区域 */
__item /* 工具项 */
__button /* 工具按钮 */
__separator /* 分隔符 */
__search /* 搜索框 */
__filter /* 筛选器 */
__sort /* 排序器 */
轮播图 (Carousels)
/* .carousel 的 Element */
__slide /* 幻灯片 */
__content /* 幻灯片内容 */
__image /* 幻灯片图片 */
__caption /* 幻灯片说明 */
__prev /* 上一个按钮 */
__next /* 下一个按钮 */
__dots /* 指示点区域 */
__dot /* 单个指示点 */
__pagination /* 分页指示器 */
图表组件 (Charts)
/* .chart 的 Element */
__container /* 容器 */
__canvas /* 画布 */
__axis /* 坐标轴 */
__axis-x /* X轴 */
__axis-y /* Y轴 */
__grid /* 网格线 */
__legend /* 图例 */
__tooltip /* 工具提示 */
__data /* 数据点 */
__bar /* 柱状 */
__line /* 线条 */
__area /* 面积 */
通知组件 (Notifications)
/* .notification 的 Element */
__icon /* 图标 */
__title /* 标题 */
__message /* 消息内容 */
__close /* 关闭按钮 */
__actions /* 操作按钮 */
__time /* 时间显示 */
__progress /* 进度条 */
位置关系
__top /* 顶部 */
__bottom /* 底部 */
__left /* 左侧 */
__right /* 右侧 */
__center /* 中间 */
__side /* 侧边 */
__edge /* 边缘 */
__corner /* 角落 */
__start /* 起始端 */
__end /* 结束端 */
大小尺寸
__small /* 小尺寸 */
__medium /* 中尺寸 */
__large /* 大尺寸 */
__mini /* 迷你尺寸 */
__tiny /* 超小尺寸 */
__compact /* 紧凑型 */
__expanded /* 展开型 */
__full /* 全尺寸 */
状态指示
__active /* 激活状态 */
__inactive /* 非激活状态 */
__enabled /* 启用状态 */
__disabled /* 禁用状态 */
__selected /* 选中状态 */
__checked /* 勾选状态 */
__focused /* 聚焦状态 */
__hover /* 悬停状态 */
__pressed /* 按下状态 */
数量关系
__single /* 单个 */
__multiple /* 多个 */
__first /* 第一个 */
__last /* 最后一个 */
__even /* 偶数 */
__odd /* 奇数 */
__nth /* 第n个 */
__all /* 所有 */
__none /* 无 */
时间关系
__past /* 过去 */
__present /* 现在 */
__future /* 未来 */
__old /* 旧的 */
__new /* 新的 */
__current /* 当前的 */
__previous /* 上一个 */
__next /* 下一个 */
功能角色
__primary /* 主要的 */
__secondary /* 次要的 */
__tertiary /* 第三级的 */
__auxiliary /* 辅助的 */
__main /* 主要的 */
__sub /* 次要的 */
__support /* 支持的 */
__detail /* 详细的 */
Modifier
修饰符是改变某个块的外观的标志。要使用修饰符,用一个双短横线线隔开
通用状态
--active /* 激活/活动状态 */
--inactive /* 非激活状态 */
--enabled /* 启用状态 */
--disabled /* 禁用状态 */
--selected /* 选中状态 */
--checked /* 已勾选 */
--unchecked /* 未勾选 */
--focused /* 获得焦点 */
--blurred /* 失去焦点 */
--hover /* 悬停状态 */
--pressed /* 按下状态 */
--dragging /* 拖拽中 */
--loading /* 加载中 */
--processing /* 处理中 */
--waiting /* 等待中 */
可见性状态
--visible /* 可见 */
--hidden /* 隐藏 */
--collapsed /* 折叠 */
--expanded /* 展开 */
--closed /* 关闭 */
--open /* 打开 */
--show /* 显示 */
--hide /* 隐藏 */
交互状态
--clickable /* 可点击 */
--editable /* 可编辑 */
--draggable /* 可拖动 */
--droppable /* 可放置 */
--sortable /* 可排序 */
--resizable /* 可调整大小 */
--selectable /* 可选择 */
数据状态
--empty /* 空状态 */
--filled /* 已填充 */
--valid /* 有效 */
--invalid /* 无效 */
--verified /* 已验证 */
--unverified /* 未验证 */
--dirty /* 已修改(表单) */
--clean /* 未修改 */
文件/上传状态
--uploading /* 上传中 */
--uploaded /* 已上传 */
--failed /* 失败 */
--success /* 成功 */
--pending /* 等待中 */
--completed /* 已完成 */
--cancelled /* 已取消 */
网络/连接状态
--online /* 在线 */
--offline /* 离线 */
--connected /* 已连接 */
--disconnected /* 断开连接 */
--connecting /* 连接中 */
--syncing /* 同步中 */
--synced /* 已同步 */
通用尺寸
/* 通用尺寸 */
--small /* 小尺寸 */
--medium /* 中等尺寸 */
--large /* 大尺寸 */
--xl /* 加大尺寸 */
--xxl /* 特大尺寸 */
--tiny /* 超小尺寸 */
--mini /* 迷你尺寸 */
--compact /* 紧凑型 */
/* 宽度尺寸 */
--narrow /* 窄 */
--wide /* 宽 */
--full /* 全宽 */
--half /* 半宽 */
--third /* 三分之一宽 */
--quarter /* 四分之一宽 */
--fluid /* 流体宽度 */
--fixed /* 固定宽度 */
/* 高度尺寸 */
--short /* 矮 */
--tall /* 高 */
--full-height /* 全高 */
--auto-height /* 自动高度 */
/* 间距尺寸 */
--dense /* 密集间距 */
--loose /* 宽松间距 */
--tight /* 紧凑间距 */
--spacious /* 宽敞间距 */
颜色主题
--primary /* 主要颜色 */
--secondary /* 次要颜色 */
--tertiary /* 第三颜色 */
--accent /* 强调色 */
--muted /* 柔和色 */
--light /* 浅色 */
--dark /* 深色 */
--inverse /* 反色 */
语义颜色
--success /* 成功/绿色 */
--error /* 错误/红色 */
--warning /* 警告/黄色 */
--info /* 信息/蓝色 */
--danger /* 危险/红色 */
--safe /* 安全/绿色 */
--critical /* 严重/橙色 */
样式变体
--outline /* 轮廓样式 */
--solid /* 实心样式 */
--ghost /* 幽灵样式(透明背景) */
--flat /* 扁平样式 */
--raised /* 凸起样式 */
--shadow /* 有阴影 */
--borderless /* 无边框 */
--rounded /* 圆角 */
--square /* 直角 */
--circle /* 圆形 */
透明度
--transparent /* 透明 */
--semi-transparent /* 半透明 */
--opaque /* 不透明 */
--translucent /* 半透明 */
布局方向
--vertical /* 垂直排列 */
--horizontal /* 水平排列 */
--row /* 行方向 */
--column /* 列方向 */
文本方向
--left /* 左对齐 */
--center /* 居中对齐 */
--right /* 右对齐 */
--justify /* 两端对齐 */
--start /* 起始对齐 */
--end /* 结束对齐 */
位置方向
--top /* 顶部 */
--bottom /* 底部 */
--left /* 左侧 */
--right /* 右侧 */
--middle /* 中间 */
--center /* 居中 */
--absolute /* 绝对定位 */
--relative /* 相对定位 */
--fixed /* 固定定位 */
--sticky /* 粘性定位 */
浮动方向
--float-left /* 左浮动 */
--float-right /* 右浮动 */
--float-none /* 不浮动 */
行为模式
--readonly /* 只读 */
--editable /* 可编辑 */
--required /* 必填 */
--optional /* 可选 */
--multiple /* 多选 */
--single /* 单选 */
--searchable /* 可搜索 */
--filterable /* 可筛选 */
--sortable /* 可排序 */
交互模式
--interactive /* 可交互 */
--static /* 静态 */
--dynamic /* 动态 */
--animated /* 有动画 */
--static /* 静态 */
--fixed /* 固定 */
--sticky /* 粘性 */
数据模式
--empty /* 空数据 */
--loading /* 加载数据 */
--loaded /* 数据已加载 */
--error /* 数据错误 */
--no-data /* 无数据 */
--has-data /* 有数据 */
内容类型
--text /* 文本类型 */
--number /* 数字类型 */
--date /* 日期类型 */
--file /* 文件类型 */
--image /* 图片类型 */
--video /* 视频类型 */
--audio /* 音频类型 */
文件类型
--pdf /* PDF文件 */
--doc /* Word文档 */
--xls /* Excel文件 */
--ppt /* PowerPoint文件 */
--image /* 图片文件 */
--archive /* 压缩文件 */
--code /* 代码文件 */
设备类型
--mobile /* 移动设备 */
--tablet /* 平板设备 */
--desktop /* 桌面设备 */
--phone /* 手机 */
--watch /* 手表 */
--tv /* 电视 */
时间状态
--new /* 新的 */
--old /* 旧的 */
--recent /* 最近的 */
--past /* 过去的 */
--future /* 未来的 */
--expired /* 已过期 */
--upcoming /* 即将到来 */
频率状态
--frequent /* 频繁的 */
--rare /* 罕见的 */
--once /* 一次性的 */
--recurring /* 重复的 */
--daily /* 每日的 */
--weekly /* 每周的 */
--monthly /* 每月的 */
屏幕尺寸
--mobile /* 移动端 */
--tablet /* 平板端 */
--desktop /* 桌面端 */
--sm /* 小屏幕 */
--md /* 中等屏幕 */
--lg /* 大屏幕 */
--xl /* 超大屏幕 */
设备方向
--portrait /* 竖屏 */
--landscape /* 横屏 */
断点相关
--below-md /* 小于中等屏幕 */
--above-lg /* 大于大屏幕 */
--only-mobile /* 仅移动端 */
--only-desktop /* 仅桌面端 */
数据质量
--valid /* 数据有效 */
--invalid /* 数据无效 */
--verified /* 已验证 */
--pending /* 待验证 */
--expired /* 已过期 */
--fresh /* 新鲜数据 */
--stale /* 陈旧数据 */
数据量
--empty /* 空数据 */
--few /* 少量数据 */
--many /* 大量数据 */
--full /* 数据已满 */
--overflow /* 数据溢出 */
安全状态 Modifier
--secured /* 已保护 */
--unsecured /* 未保护 */
--encrypted /* 已加密 */
--decrypted /* 已解密 */
--authenticated /* 已认证 */
--unauthenticated /* 未认证 */
--authorized /* 已授权 */
--unauthorized /* 未授权 */
按钮 Button
--primary /* 主要按钮 */
--secondary /* 次要按钮 */
--danger /* 危险操作按钮 */
--warning /* 警告操作按钮 */
--success /* 成功操作按钮 */
--link /* 链接样式按钮 */
--icon /* 图标按钮 */
--block /* 块级按钮(宽度100%) */
输入框 Input
--filled /* 已填充 */
--error /* 错误状态 */
--success /* 成功状态 */
--warning /* 警告状态 */
--disabled /* 禁用状态 */
--readonly /* 只读状态 */
--search /* 搜索框样式 */
表格 Table
--striped /* 斑马纹 */
--bordered /* 有边框 */
--hover /* 悬停效果 */
--condensed /* 紧凑型 */
--responsive /* 响应式表格 */
--sortable /* 可排序 */
卡片 Card
--shadow /* 有阴影 */
--border /* 有边框 */
--hoverable /* 悬停效果 */
--selected /* 选中状态 */
--clickable /* 可点击 */
--draggable /* 可拖动 */
模态框 Modal
--small /* 小尺寸模态框 */
--medium /* 中尺寸模态框 */
--large /* 大尺寸模态框 */
--fullscreen /* 全屏模态框 */
--centered /* 居中显示 */
导航 Nav
--vertical /* 垂直导航 */
--horizontal /* 水平导航 */
--pills /* 胶囊式导航 */
--tabs /* 标签式导航 */
--underline /* 下划线式导航 */
国际化 Modifier
--ltr /* 从左到右 */
--rtl /* 从右到左 */
--en /* 英语 */
--zh /* 中文 */
--ja /* 日语 */
--ar /* 阿拉伯语 */
--locale-en /* 英语地区 */
--locale-zh /* 中文地区 */
优先级 Modifier
--high /* 高优先级 */
--medium /* 中优先级 */
--low /* 低优先级 */
--critical /* 关键优先级 */
--normal /* 普通优先级 */
--urgent /* 紧急优先级 */
为什么在使用Vue的v-for时,一定要加上key字段?
假设我们要渲染一个简单的待办事项列表:
<div id="app">
<div v-for="item in list">
<input type="checkbox">
<span>{{ item.text }}</span>
</div>
</div>
当我们删除中间某个项目时,你可能会发现复选框的状态出现了错误。选中的项目被删除了,但其他项目的选中状态却乱了套。
问题的根源
Vue在更新DOM时,会尽量复用已有的元素。这是一种优化策略,可以减少DOM操作,提高性能。
但是,当数据顺序发生变化时,Vue需要知道哪些元素可以复用,哪些需要重新创建。如果没有key,Vue只能按照顺序进行对比。
没有key的情况:
- • Vue按顺序对比新旧节点
- • 删除第二个元素后,后面的元素会前移
- • Vue认为这是同一个元素,只是内容变了
- • 复用的元素保留了之前的状态
key的作用
key给每个节点一个唯一标识。Vue通过这个标识来跟踪每个节点的身份。
<!-- 正确的写法 -->
<div v-for="item in list" :key="item.id">
<input type="checkbox">
<span>{{ item.text }}</span>
</div>
加上key之后:
- • Vue知道每个节点的唯一身份
- • 删除某个节点时,其他节点身份不变
- • 不会错误地复用其他节点的状态
- • 列表更新更加准确
深入理解虚拟DOM
要理解key的重要性,我们需要了解Vue的虚拟DOM机制。
虚拟DOM是什么
虚拟DOM是真实DOM的JavaScript对象表示。Vue通过对比新旧虚拟DOM的差异,来决定如何更新真实DOM。
Diff算法
Vue使用Diff算法来比较虚拟DOM的差异。这个算法会找出最小的变更,然后应用到真实DOM上。
没有key的Diff过程:
- • 按顺序逐个比较节点
- • 发现长度变化,进行插入或删除
- • 可能导致大量不必要的DOM操作
有key的Diff过程:
- • 根据key建立映射关系
- • 精确找到新增、删除、移动的节点
- • 最小化DOM操作
key的选择
选择合适的key很重要。不合适的key可能带来问题。
好的key选择
- • 数据中的唯一标识符
- • 稳定的、不会改变的值
- • 如:数据库ID、UUID等
// 好的例子
const list = [
{ id: 1, text: '学习Vue' },
{ id: 2, text: '写代码' },
{ id: 3, text: '阅读文档' }
]
不好的key选择
- • 数组索引(在排序、过滤时会出问题)
- • 随机数(每次渲染都会变化)
- • 可能重复的值
// 不好的例子 - 使用索引作为key
<div v-for="(item, index) in list" :key="index">
// 不好的例子 - 使用随机数作为key
<div v-for="item in list" :key="Math.random()">
实际开发中的场景
列表排序
当列表需要排序时,key的作用特别明显。
// 初始列表
[
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' },
{ id: 3, name: '橙子' }
]
// 排序后
[
{ id: 3, name: '橙子' },
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' }
]
有key时,Vue知道这是元素位置的移动,而不是内容的修改。
列表过滤
过滤列表时,key确保正确的元素被保留或移除。
动态组件
在动态组件中,key可以强制组件重新创建:
<component :is="currentComponent" :key="componentKey">
改变componentKey会触发组件的重新渲染。
性能考虑
什么时候可以不加key
在某些简单场景下,不加key可能不会立即发现问题:
- • 静态列表,永远不会改变
- • 列表项没有内部状态
- • 列表项非常简单
但为了代码的健壮性,建议始终加上key。
错误的使用方式
有些开发者会这样使用key:
<!-- 错误:使用索引作为key -->
<div v-for="(item, index) in list" :key="index">
<!-- 错误:使用不稳定的值作为key -->
<div v-for="item in list" :key="Math.random()">
这些用法都会导致各种奇怪的问题。
常见问题解答
为什么不能用索引作为key?
当列表发生变化时,索引也会变化。原来索引为1的元素,在删除前面的元素后,可能变成索引0。这会导致Vue错误地复用元素。
key一定要全局唯一吗?
在同一个v-for中唯一即可,不需要全局唯一。
如果没有唯一标识怎么办?
如果数据源没有提供唯一标识,可以考虑:
-
- 在获取数据时生成唯一ID
-
- 使用多个字段组合作为key
-
- 使用第三方库生成UUID
记住:key是Vue跟踪节点身份的标识,不是普通的属性。
写在最后
理解key的作用,能帮助我们写出更稳定、性能更好的Vue应用。这个看似小的细节,在实际开发中却很重要。
下次使用v-for时,记得给它一个合适的key。