Introduction
LLH 是一个面向中后台的轻量 UI 框架:纯 CSS + 原生 JS,零依赖、零构建。 它解决三件事——统一视觉、直接可用的布局骨架、够用的组件。所有可调项都收敛成 CSS 变量, 不做封装、不做运行时,任何能写 HTML 的地方都能用。
| 路径 | 说明 |
|---|---|
oak/assets/llh.css | 核心样式,包含令牌、基础排版、全部组件 |
oak/assets/llh.js | 交互脚本,事件委托,无初始化代码 |
oak/index.html | 框架首页与快速开始 |
oak/dashboard.html | 后台布局演示(侧栏 + 顶栏 + 内容) |
oak/components.html | 组件总览 |
oak/docs.html | 本页 |
oak/assets/demo.css | 演示页专用样式,接入项目时不必拷贝 |
把 assets/ 整个目录放进项目,在页面里引两个文件即可。CSS 放 head,JS 放 body 末尾。
<link rel="stylesheet" href="/assets/llh.css">
<script src="/assets/llh.js"></script>
如果是 PHP、JSP 这类会缓存的模板,建议给样式加时间戳,改完立即生效:
<link rel="stylesheet" href="/assets/llh.css?v=<?= @filemtime('assets/llh.css') ?: '1' ?>">
file:// 下直接双击打开演示页也能正常工作,不需要起服务。
一个页面只需要四层:.llh-layout → .llh-sidebar + .llh-main → .llh-topbar + .llh-content。
侧边栏 232px,折叠后 76px;窗口 ≤900px 自动变成抽屉。
<div class="llh-layout" data-llh-layout>
<aside class="llh-sidebar">
<a class="llh-sidebar__brand" href="/">
<span class="llh-sidebar__logo">LLH</span>
<span class="llh-sidebar__name">控制台</span>
</a>
<nav class="llh-sidebar__nav">
<div class="llh-sidebar__group">工作台</div>
<a class="llh-sidebar__link is-active" href="/">
<svg class="llh-ico"><use href="#i-home"></use></svg>
<span>仪表盘</span>
</a>
</nav>
<div class="llh-sidebar__footer">
<div class="llh-sidebar__user">
<span class="llh-avatar llh-avatar--sm">杨</span>
<div class="llh-sidebar__user-meta">
<div class="llh-sidebar__user-name">杨东旭</div>
<div class="llh-sidebar__user-role">超级管理员</div>
</div>
</div>
</div>
</aside>
<div class="llh-main">
<header class="llh-topbar">
<div class="llh-topbar__left">
<button class="llh-ico-btn" data-llh-toggle="sidebar">菜单</button>
<span class="llh-topbar__title">仪表盘</span>
</div>
<div class="llh-topbar__right">...</div>
</header>
<main class="llh-content">页面内容</main>
</div>
</div>
| 结构 | 行为 |
|---|---|
.llh-layout[data-llh-layout] | 折叠 / 抽屉逻辑的容器,缺这个属性折叠按钮不生效 |
.llh-sidebar | 桌面端 sticky 满高,移动端 fixed 抽屉并带遮罩式关闭 |
.llh-sidebar__link span | 折叠时靠它淡出,图标必须和文字分开写,不要塞进同一个 span |
data-llh-toggle="sidebar" | 桌面端切换折叠,≤900px 时改为打开抽屉 |
.llh-content | 内容区自带 16px 内边距,移动端降为 12px |
.llh-sticky-bar | 页内 TAB 吸顶用,配合 --llh-sticky-top: 57px(顶栏高 56 + 1px 边框) |
全部挂在 :root 上,深色主题只覆盖同名变量,组件样式一行不改。
| 变量 | 默认值 | 用途 |
|---|---|---|
--llh-primary | #0056b3 | 主色:按钮、选中态、链接、焦点环 |
--llh-primary-hover | #004a99 | 主色悬停 |
--llh-primary-active | #003f80 | 主色按下 |
--llh-primary-soft | rgba(0,86,179,.10) | 主色淡底,用于选中背景、标签底 |
--llh-ink | #101828 | 标题与正文主色 |
--llh-ink-2 | #475467 | 次要正文 |
--llh-ink-3 | #667085 | 说明文字、表头 |
--llh-ink-4 | #98a2b3 | 占位符、禁用 |
--llh-line / -line-2 | #eef0f4 / #dfe3ea | 分割线 / 控件描边 |
--llh-bg | #f5f7fa | 页面底色、表头底色 |
--llh-card | #ffffff | 卡片、侧栏、顶栏底色 |
--llh-success / -soft | #067647 / #ecfdf3 | 成功态 |
--llh-warn / -soft | #b54708 / #fffaeb | 警告态 |
--llh-danger / -soft | #b42318 / #fef3f2 | 危险态 |
--llh-radius-sm/md/lg/xl/pill | 8/12/14/16/999 | 圆角梯度 |
--llh-shadow-1/2/3 | — | 卡片 / 悬停 / 浮层投影 |
--llh-font | MiSans, system-ui… | 全站字体栈 |
--llh-nav-w / -nav-w-min | 232px / 76px | 侧栏宽度、折叠宽度 |
--llh-topbar-h | 56px | 顶栏高度 |
--llh-speed | .18s | 全局过渡时长 |
:root {
--llh-primary: #0f766e;
--llh-primary-hover: #0d6a62;
--llh-primary-active: #0b5a53;
--llh-primary-soft: rgba(15, 118, 110, .10);
}
命名规则:llh-块 + __元素 + --修饰,与 BEM 一致,好猜也好搜。
| 类名 | 说明 |
|---|---|
.llh-layout / .llh-sidebar __brand/__logo/__name/__nav/__group/__link/__icon/__footer/__user* | 后台外壳与侧栏,折叠态由 html.is-collapsed 控制 |
.llh-main / .llh-topbar __left/__right/__title / .llh-content __header | 右侧主区、顶栏与页体 |
.llh-sticky-bar / .llh-page / .llh-body-reset / .llh-switch-page | 页内吸顶条 / 整页底色容器 / 样式重置 / 右侧浮动页面切换 |
.llh-container | 居中容器 1320px,--narrow 960 / --wide 1480 |
.llh-btn --primary/--soft/--ghost/--success/--danger/--danger-soft | 按钮类型 |
.llh-btn--sm/--lg/--block/--icon | 按钮尺寸与形态 |
.llh-btn-group | 按钮拼接组 |
.llh-card __header/__body/__footer | 卡片三段结构,--hover 加悬停上浮 |
.llh-stat __label/__value/__trend | 指标块,趋势加 --up/--down;.llh-stat--cn 切换为国内涨红跌绿 |
.llh-table | 表格,修饰 --striped/--compact,数字列 is-num |
.llh-table-wrap | 表格外框,负责圆角与横向滚动 |
.llh-table__empty | 无数据占位行 |
.llh-badge --primary/--success/--warn/--danger/--solid/--dot | 标签与徽章 |
.llh-avatar --sm/--lg、.llh-avatar-group | 头像与头像堆叠 |
.llh-field / .llh-label / .llh-help / .llh-error-text | 表单字段结构 |
.llh-input .llh-select .llh-textarea | 输入控件,尺寸 --sm/--lg,错误态 is-error |
.llh-input-group __addon | 带前后缀的输入组合 |
.llh-check / .llh-switch __track/__text | 勾选与开关,开关尺寸 --sm |
.llh-alert --info/--success/--warn/--danger __title/__close | 页面内提示 |
.llh-toasts / .llh-toast __body/__title/__desc/__close --success/--warn/--danger | 全局提示(由 JS 生成,不用手写) |
.llh-modal __backdrop/__dialog/__header/__title/__close/__body/__footer | 弹窗,尺寸 --sm/--lg |
.llh-dropdown __menu/__item/__divider | 下拉菜单,右对齐加 --right |
.llh-tabs / .llh-tab / .llh-pane | 标签页,线性风格 .llh-tabs--line |
.llh-carousel __track/__slide/__content/__title/__desc/__dots/__dot | 轮播,图片背景加 __slide--image |
.llh-steps / .llh-step is-done|is-active | 步骤条 |
.llh-timeline __item/__title/__desc/__time | 时间线 |
.llh-progress __bar --success/--warn/--danger | 进度条 |
.llh-pagination / .llh-page-item is-active|is-disabled | 分页 |
.llh-breadcrumb __sep/__current | 面包屑 |
.llh-empty __icon/__title/__desc | 空状态 |
.llh-skeleton --avatar/--title/--text | 骨架屏 |
.llh-divider --vertical | 分隔线 |
.llh-segmented | 分段控制器,radio + label 纯 CSS 切换 |
.llh-accordion __body | 折叠面板,基于原生 details/summary |
.llh-drawer __panel/__header/__title/__body/__footer | 抽屉,左侧加 --left,开关与弹窗同一套 data 属性 |
.llh-popover __panel | 气泡卡片,hover 或 focus-within 展开 |
.llh-tip + data-llh-tip | 文字提示气泡,纯 CSS,无需 JS |
.llh-desc --2 | 描述列表,dl/dt/dd 两列或四列 |
.llh-result __icon/__title/__desc/__actions | 结果页,状态色 --warn/--danger/--info |
.llh-upload __text/__hint、.llh-upload-item | 上传框与文件行 |
.llh-rate / .llh-range | 评分(radio 反序)与滑块(range) |
.llh-tree __node/__count | 树形列表,嵌套 ul 自动缩进 |
.llh-notice __dot/__body/__title/__desc/__time | 通知列表 |
.llh-spinner / .llh-loading-mask / .llh-loading | 加载指示、容器遮罩与行内加载文案,父级加 .llh-loading-wrap |
.llh-backtop + data-llh-backtop | 返回顶部,滚动超过 300px 自动显示 |
.llh-media __body | 媒体对象,左图右文自适应 |
.llh-ico --sm/--lg、.llh-ico-btn | 图标尺寸与图标按钮 |
.llh-ellipsis / .llh-nowrap / .llh-sr-only | 单行省略 / 不换行 / 仅读屏可见 |
组件状态统一用 is-* 表达,不加 llh- 前缀,跨组件同名同义。
| 状态类 | 作用对象与含义 |
|---|---|
.is-active | 当前项:TAB、侧栏导航、分页、轮播圆点、树节点、步骤条 |
.is-open | 弹窗、抽屉、下拉菜单已展开 |
.is-collapsed | 加在 html 上,侧栏收至 76px |
.is-disabled | 分页项、勾选项不可用 |
.is-done | 步骤条已完成的节点 |
.is-error | 输入控件校验失败,配合 .llh-error-text 使用 |
.is-loading | 作用于 .llh-btn,用伪元素显示转圈并屏蔽点击 |
.is-leaving | Toast 退出动画,由 JS 自动添加与移除 |
.is-show | 返回顶部按钮滚动超过 300px 后显示 |
.is-num | 表格数字列右对齐并启用等宽数字 |
.is-primary | 时间线条目的强调节点 |
布局:.llh-flex -col -wrap .llh-items-center .llh-justify-between .llh-gap-4/8/12/16 .llh-grow .llh-shrink-0
栅格:.llh-grid 配 --2/--3/--4/--auto,或 .llh-row + .llh-col
间距:.llh-mt-* .llh-mb-*(4/8/12/16/24)
其他:.llh-text-* .llh-w-full .llh-hidden .llh-hide-sm
脚本会自动初始化,window.LLH 上只暴露少量方法,够用即可。
LLH.modal.open('#myModal'); // 打开
LLH.modal.close('#myModal'); // 关闭指定
LLH.modal.closeAll(); // 关闭全部
LLH.dropdown.closeAll(); // 收起所有下拉
LLH.toast('文本', {
type: 'success', // info | success | warn | danger
title: '操作成功', // 可选标题
duration: 3200 // 毫秒,0 表示不自动关闭
});
LLH.theme.set('dark'); // 'light' | 'dark',写入 localStorage
LLH.theme.get(); // 当前主题
LLH.theme.toggle(); // 切换
LLH.init(scope); // 给动态插入的 DOM 重新绑定标签页与轮播
LLH.toast() 返回 { close, el },需要提前关掉时调用返回值上的 close()。
所有交互都以事件委托实现,写在 HTML 上就能用,不需要为每个组件写初始化。
| 属性 | 作用 |
|---|---|
data-llh-toggle="modal" | 打开弹窗或抽屉,配合 data-llh-target="#id" |
data-llh-toggle="modal-close" | 关闭当前弹窗 / 抽屉 |
data-llh-toggle="dropdown" | 切换所在下拉菜单,点外部自动收起 |
data-llh-toggle="theme" | 切换深浅主题 |
data-llh-toggle="sidebar" | 折叠侧栏 / 移动端抽屉 |
data-llh-dismiss="alert" | 关闭所在 Alert |
data-llh-tip="文案" | 提示气泡内容,纯 CSS 显示 |
data-llh-backtop | 标记返回顶部按钮,自动监听滚动 |
data-llh-tabs | 标签页容器,内部按钮用 data-llh-target="#pane" |
data-llh-panes | 面板容器,切换时给对应 .llh-pane 加 .is-active |
data-llh-layout | 标记布局根节点,启用侧栏逻辑 |
data-llh-interval="5000" | 轮播自动播放间隔(毫秒) |
data-llh-autofocus | 弹窗打开后优先聚焦该元素 |
data-llh-static | 加在弹窗上,点遮罩不再关闭 |
标签页切换后会派发 llh:tab,需要联动其他区域时监听它即可。
document.querySelector('[data-llh-tabs]').addEventListener('llh:tab', function (e) {
console.log(e.detail.target); // 被激活面板的选择器
});
深色主题实现方式是:在 <html> 上写 data-llh-theme="dark",
只覆盖同名 CSS 变量。所以自定义组件只要用变量取色,就自动支持深色。
<!-- 手动指定 -->
<html data-llh-theme="dark">
<!-- 避免刷新闪烁:在 head 里先读记忆 -->
<script>
(function () {
try {
if (localStorage.getItem('llh-theme') === 'dark') {
document.documentElement.setAttribute('data-llh-theme', 'dark');
}
} catch (err) {}
})();
</script>
<head> 内、body 之前,
浏览器首帧就已经是正确主题,不会出现“先白后黑”。
侧栏有折叠记忆,如果等 JS 加载完再加 is-collapsed,用户会看到“先展开、再收起”的一下抖动。
LLH 的做法是:head 内先把状态写到 <html data-llh-sidebar="collapsed">,
CSS 里对应该属性直接渲染折叠态,脚本随后接管并保持同步。
<head>
<link rel="stylesheet" href="assets/llh.css">
<script>
(function () {
try {
if (localStorage.getItem('llh-sidebar-collapsed') === '1') {
document.documentElement.setAttribute('data-llh-sidebar', 'collapsed');
}
} catch (err) {}
})();
</script>
</head>
llh.js 在初始化与每次点击时都会同步这个属性,因此两种状态永远一致,不需要你手动维护。
折叠态只在 min-width: 901px 下生效,移动端仍是完整抽屉。
可以。CSS 里的组件不依赖 JS,按钮、表单、表格、卡片全部纯样式;只有弹窗、下拉、Toast、标签页、轮播、折叠需要脚本。
把 assets/llh.css 与 llh.js 放进静态目录,在公共 header 里引一次,
然后把老的 class 逐步替换成 llh-*。两者可以共存,不需要一次性重写。
用 .llh-table-wrap 包住表格,它负责横向滚动与圆角裁切。注意别在祖先元素上加
overflow: hidden,否则里面的下拉菜单会被裁掉。
<div class="llh-tabs llh-sticky-bar" style="--llh-sticky-top:57px">...</div>
57px = 顶栏 56px + 1px 边框。改了 --llh-topbar-h 记得同步这个值。
下拉菜单是绝对定位,父级若有 overflow: hidden 就会裁。给弹窗内容区保留足够空间,
或把下拉挂到 body 上用 fixed 定位。
框架不绑定图标库,避免多一份字体文件和版本冲突。演示页用内联 SVG sprite,
你接入 Bootstrap Icons、Remix Icon 或自己的 iconfont 都可以,只要保证图标元素有 .llh-ico 控制尺寸。
面向现代浏览器:Chrome / Edge 90+、Firefox 90+、Safari 15+。不兼容 IE。
使用到的现代特性主要是 CSS 变量、flex/grid、position: sticky、:focus-visible。
新建一个 brand.css,在 llh.css 之后引入,里面只写 :root 覆盖主色、字体、圆角即可,
不要直接改 llh.css,方便以后升级。