Laimi UI · 移动端 / PC 通用组件库
一套零依赖的原生 CSS + JS 组件标准,覆盖后台管理、门店收银、移动端页面等场景。
引入两个文件即可使用全部组件,所有交互自带键盘快捷键与移动端适配。
概览这套 UI 是什么
Laimi UI 由三部分组成:一份样式表、一份交互脚本、一份演示页。样式与交互完全分离,业务页面只需要写 HTML 结构,剩下的样式和交互由组件库负责。
| 文件 | 作用 | 说明 |
|---|---|---|
| laimi.css | 组件样式库 | 全部组件样式 + CSS 变量主题,约 82 KB |
| laimi.js | 交互库 | 弹窗、Toast、下拉、选择器、抽屉等交互,约 80 KB |
| web移动通用.html | 组件演示页 | 所有组件的可视化示例,可作为页面骨架参考 |
| docs.html | 本文档 | 使用说明、API 参考、代码示例 |
快速开始4 步接入
1. 引入文件
把 laimi.css 与 laimi.js 放到项目目录,在页面中引入。图标库使用阿里 iconfont,按需引入。
<link rel="stylesheet" href="https://at.alicdn.com/t/c/font_5238787_v163maxwpnj.css?t=1"> <link rel="stylesheet" href="laimi.css"> <!-- 页面底部 --> <script src="laimi.js"></script>
2. 拆成 inc_head / inc_top / inc_nav
实际项目把公共部分拆成 include 文件,每个页面只写自己的内容。顶部栏与左侧栏的 id 必须原样保留(shopSelector、shopName、smsBalance、msgBtn、supportBtn、topbarUser、appSidebar、sidebarToggle),组件库靠这些 id 自动绑定交互,缺了对应交互就不会生效。
<meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?php echo $lm_page_title; ?> - 来米软件</title> <!-- 图标库(阿里 iconfont · 项目 5238787) --> <link rel="stylesheet" href="https://at.alicdn.com/t/c/font_5238787_v163maxwpnj.css?t=1"> <!-- 组件库样式 --> <link rel="stylesheet" href="laimi_ui/laimi.css">
<?php
// 顶部栏需要的数据:门店名、用户姓名、角色、短信余额
$lm_shop_name = $GLOBALS['gshop']['shop_name'] ?? '解放东路店';
$lm_user_name = $GLOBALS['guser']['user_name'] ?? '刘明轩';
$lm_role = ($GLOBALS['_SESSION']['login_type'] == 1) ? '超级管理员' : '操作员';
$lm_avatar = $lm_user_name !== '' ? mb_substr($lm_user_name, 0, 1, 'utf-8') : '用';
$lm_sms_count = 0;
if (!empty($GLOBALS['gcompany']['company_sms_level'])) { $lm_sms_count = $GLOBALS['gcompany']['company_sms_ycount']; }
$lm_cur = basename($_SERVER['PHP_SELF']);
?>
<header class="app-topbar">
<!-- 左段:Logo + 店名 -->
<div class="topbar-left">
<div class="topbar-logo">
<div class="logo-icon">M</div>
<span>我的软件</span>
</div>
<div class="shop-selector" id="shopSelector">
<span id="shopName"><?php echo $lm_shop_name; ?></span>
<i class="iconfont icon-angle-down"></i>
</div>
</div>
<!-- 中段:导航 -->
<nav class="topbar-center">
<a class="topbar-nav-item<?php if($lm_cur == 'main.php') echo ' active'; ?>" href="main.php"><span class="nav-icon"><i class="iconfont icon-gongzuotai"></i></span>工作台</a>
<a class="topbar-nav-item<?php if($lm_cur == 'b_pay.php') echo ' active'; ?>" href="b_pay.php"><span class="nav-icon"><i class="iconfont icon-jiaoyimingxi"></i></span>收银台</a>
<a class="topbar-nav-item<?php if($lm_cur == 'v_list.php') echo ' active'; ?>" href="v_list.php"><span class="nav-icon"><i class="iconfont icon-yuyue"></i></span>预约</a>
<a class="topbar-nav-item<?php if($lm_cur == 'o_writeoff.php') echo ' active'; ?>" href="o_writeoff.php"><span class="nav-icon"><i class="iconfont icon-meituan2"></i></span>核销</a>
<a class="topbar-nav-item<?php if($lm_cur == 'd_sms.php') echo ' active'; ?>" href="d_sms.php"><span class="nav-icon"><i class="iconfont icon-shouzhitongji"></i></span>营销</a>
<a class="topbar-nav-item<?php if($lm_cur == 'p_trade1.php') echo ' active'; ?>" href="p_trade1.php"><span class="nav-icon"><i class="iconfont icon-shezhi"></i></span>统计</a>
</nav>
<!-- 右段:余额 / 消息 / 客服 / 用户 -->
<div class="topbar-right">
<div class="topbar-sms" id="smsBalance" title="查看短信余额">
<span class="sms-icon"><i class="iconfont icon-duanxin1"></i></span>
<span>短信余额 <b class="fs-16"><?php echo $lm_sms_count; ?></b> 条</span>
</div>
<button class="topbar-action" id="msgBtn" title="系统消息">
<i class="iconfont icon-bell"></i><span class="action-badge"></span>
</button>
<button class="topbar-action" id="supportBtn" title="联系客服">
<i class="iconfont icon-headphones"></i>
</button>
<div class="topbar-user" id="topbarUser">
<div class="user-avatar"><?php echo $lm_avatar; ?></div>
<div class="user-info">
<div class="user-name"><?php echo $lm_user_name; ?></div>
<div class="user-role"><?php echo $lm_role; ?></div>
</div>
<span class="user-arrow"><i class="iconfont icon-angle-down"></i></span>
</div>
</div>
</header>
<?php $lm_cur = basename($_SERVER['PHP_SELF']); ?>
<aside class="app-sidebar" id="appSidebar">
<button class="sidebar-toggle" id="sidebarToggle">
<span class="arrow">‹</span>
</button>
<div class="sidebar-menu">
<div class="sidebar-group">
<div class="sidebar-group-title">门店管理</div>
<a class="sidebar-item<?php if($lm_cur == 'm_list.php') echo ' active'; ?>" href="m_list.php"><span class="item-icon"><i class="iconfont icon-huiyuandenglu"></i></span><span class="item-text">会员</span></a>
<a class="sidebar-item<?php if($lm_cur == 'm_detail.php') echo ' active'; ?>" href="m_detail.php"><span class="item-icon"><i class="iconfont icon-wpforms"></i></span><span class="item-text">明细</span></a>
<!-- 提成:二级菜单内容直接写在本文件里,data-submenu-for 与上级 data-submenu 对应 -->
<a class="sidebar-item" data-submenu="ticheng"><span class="item-icon"><i class="iconfont icon-tichengjilu"></i></span><span class="item-text">提成</span><span class="item-arrow">›</span></a>
<div class="sidebar-submenu" data-submenu-for="ticheng">
<div class="submenu-title">提成管理</div>
<a class="submenu-item" href="t_list.php"><i class="iconfont icon-caret-right1"></i><span>提成明细</span></a>
<a class="submenu-item" href="t_stat.php"><i class="iconfont icon-caret-right1"></i><span>提成统计</span></a>
<a class="submenu-item" href="t_salary.php"><i class="iconfont icon-caret-right1"></i><span>工资核算</span></a>
</div>
<!-- 库存 -->
<a class="sidebar-item" data-submenu="stock"><span class="item-icon"><i class="iconfont icon-kucunchaxun1"></i></span><span class="item-text">库存</span><span class="item-arrow">›</span></a>
<div class="sidebar-submenu" data-submenu-for="stock">
<div class="submenu-title">库存管理</div>
<a class="submenu-item" href="s_in.php"><i class="iconfont icon-caret-right1"></i><span>商品入库</span></a>
<a class="submenu-item" href="s_out.php"><i class="iconfont icon-caret-right1"></i><span>商品出库</span></a>
<a class="submenu-item" href="s_log.php"><i class="iconfont icon-caret-right1"></i><span>出入库查询</span></a>
<a class="submenu-item" href="s_check.php"><i class="iconfont icon-caret-right1"></i><span>库存盘点</span></a>
<a class="submenu-item" href="s_stat.php"><i class="iconfont icon-caret-right1"></i><span>库存统计</span></a>
</div>
<a class="sidebar-item"><span class="item-icon"><i class="iconfont icon-gongziticheng"></i></span><span class="item-text">收支</span></a>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">营销触达</div>
<!-- 短信 -->
<a class="sidebar-item" data-submenu="sms"><span class="item-icon"><i class="iconfont icon-duanxin3"></i></span><span class="item-text">短信</span><span class="item-arrow">›</span></a>
<div class="sidebar-submenu" data-submenu-for="sms">
<div class="submenu-title">短信群发</div>
<a class="submenu-item" href="d_template.php"><i class="iconfont icon-caret-right1"></i><span>短信模板</span></a>
<a class="submenu-item" href="d_send.php"><i class="iconfont icon-caret-right1"></i><span>短信群发</span></a>
<a class="submenu-item" href="d_log.php"><i class="iconfont icon-caret-right1"></i><span>群发记录</span></a>
</div>
<a class="sidebar-item"><span class="item-icon"><i class="iconfont icon-tixing2"></i></span><span class="item-text">提醒</span></a>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">系统设置</div>
<a class="sidebar-item"><span class="item-icon"><i class="iconfont icon-shezhi"></i></span><span class="item-text">设置</span></a>
<a class="sidebar-item"><span class="item-icon"><i class="iconfont icon-jiaocheng"></i></span><span class="item-text">教程</span></a>
</div>
</div>
</aside>
3. 完整页面(首页示例)
新建 index.php,把下面整段粘进去就能跑:头部、顶部栏、左侧栏都用 include 引入上一步的文件($lm_page_title 决定页面标题);内容区放概览卡片 + 列表;底部引 laimi.js。
<?php $lm_page_title = '工作台'; ?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<?php include 'inc_head.php'; ?>
</head>
<body>
<div class="app-layout">
<?php include 'inc_top.php'; ?>
<div class="app-body">
<?php include 'inc_nav.php'; ?>
<main class="app-content">
<!-- 见下方「内容区」 -->
</main>
</div>
</div>
<script src="laimi_ui/laimi.js"></script>
</body>
</html>
<div class="content-header">
<div>
<h1 class="content-title">
<span class="title-icon"><i class="iconfont icon-gongzuotai"></i></span>
今日工作台
</h1>
<div class="breadcrumb">
<span>首页</span><span class="sep">/</span><span class="current">工作台</span>
</div>
</div>
</div>
<!-- 数据概览 -->
<div class="row">
<div class="col col-3">
<div class="card"><div class="card-body">
<div class="fs-12" style="color:var(--color-text-muted);">今日营业额</div>
<div class="fs-20" style="font-weight:700;margin-top:6px;">¥ 12,860.00</div>
</div></div>
</div>
<div class="col col-3">
<div class="card"><div class="card-body">
<div class="fs-12" style="color:var(--color-text-muted);">今日客流</div>
<div class="fs-20" style="font-weight:700;margin-top:6px;">48 人</div>
</div></div>
</div>
<div class="col col-3">
<div class="card"><div class="card-body">
<div class="fs-12" style="color:var(--color-text-muted);">今日预约</div>
<div class="fs-20" style="font-weight:700;margin-top:6px;">16 单</div>
</div></div>
</div>
<div class="col col-3">
<div class="card"><div class="card-body">
<div class="fs-12" style="color:var(--color-text-muted);">新增会员</div>
<div class="fs-20" style="font-weight:700;margin-top:6px;">9 人</div>
</div></div>
</div>
</div>
<!-- 今日订单 -->
<div class="card">
<div class="card-header">
<div class="card-header-title">
<span class="card-header-icon"><i class="iconfont icon-mingxi"></i></span>今日订单
</div>
<div class="card-header-actions">
<button class="btn btn-sm btn-outline">查看全部</button>
</div>
</div>
<div class="card-body">
<div class="table-wrapper">
<table class="table table-hover table-align-middle">
<thead>
<tr><th>时间</th><th>会员</th><th>项目</th><th>金额</th><th>状态</th></tr>
</thead>
<tbody>
<tr><td>10:24</td><td>刘明轩</td><td>玫瑰精油护理</td><td>¥298.00</td><td><span class="tag tag-success">已完成</span></td></tr>
<tr><td>11:05</td><td>陈静怡</td><td>日式美睫嫁接</td><td>¥398.00</td><td><span class="tag tag-warning">服务中</span></td></tr>
<tr><td>13:40</td><td>王梓涵</td><td>头皮护理</td><td>¥168.00</td><td><span class="tag tag-success">已完成</span></td></tr>
</tbody>
</table>
</div>
</div>
</div>
4. 调用组件
弹层类组件用 JS 调用,普通组件只写 HTML 即可。例如:
// 轻提示
toast('保存成功', 'success');
// 确认框
confirmBox('确定要删除这条记录吗?', () => {
toast('已删除', 'success');
});
// 通用弹窗
openModal({
title: '新增会员',
size: 'md',
body: '<div class="form-group">...</div>',
onConfirm: () => toast('已提交')
});
toast / openModal / showLoading 等 API 全部挂在 window 上,页面脚本里可直接调用。Toast、Loading、弹窗的容器由库自动创建,无需手写。主题与变量CSS 变量
全部颜色、圆角、阴影、尺寸都通过 CSS 变量定义在 :root 上。修改一个变量即可全站生效,无需改组件代码。
品牌色与功能色
文字与背景
| 变量 | 值 | 用途 |
|---|---|---|
| --color-text | #1e2a3a | 主文字 |
| --color-text-secondary | #3e4d5f | 次级文字 |
| --color-text-muted | #7b8a9e | 辅助 / 说明文字 |
| --color-text-disabled | #b8c4d2 | 禁用文字 |
| --color-bg | #f2f4f8 | 页面背景 |
| --color-bg-card | #ffffff | 卡片背景 |
| --color-bg-hover | #f0f4fc | 悬停背景 |
| --color-border | #dce3ec | 常规边框 |
| --color-border-light | #eef2f7 | 浅边框 / 分割线 |
圆角 / 阴影 / 尺寸
| 变量 | 值 | 用途 |
|---|---|---|
| --radius-sm / md / lg / xl | 8 / 12 / 16 / 20 px | 圆角档位 |
| --shadow-sm / md / lg | — | 卡片、弹层阴影 |
| --shadow-pop | — | 弹出层阴影 |
| --topbar-height | 64px | 顶部栏高度 |
| --sidebar-width | 180px | 左侧栏展开宽度 |
| --sidebar-collapsed | 64px | 左侧栏折叠宽度 |
| --dropdown-max-h | 240px | 下拉面板统一最大高度 |
换色 / 暗色模式
给 <html> 加 data-theme 即可切换;也可以直接覆写 --color-primary 换品牌色(浅色变体由 color-mix() 自动派生)。
<!-- 预设主题:green / purple / orange / red / dark -->
<html data-theme="dark">
<!-- 自定义品牌色(写在 laimi.css 之后) -->
<style>
:root { --color-primary: #1677ff; }
</style>
--color-primary 一处即可,--color-primary-light / --color-primary-border 会自动跟随。布局骨架顶部栏 + 左侧栏 + 内容区
布局由 .app-layout 统一管理:顶部栏 position: fixed,左侧栏可折叠,内容区自动偏移。
| 结构 | 说明 |
|---|---|
| .app-layout | 最外层容器,管理整体高度与偏移 |
| .app-topbar | 顶部栏,三段式:.topbar-left / .topbar-center / .topbar-right |
| .topbar-logo | Logo + 系统名(.logo-icon 为方形图标) |
| .shop-selector | 门店切换入口,点击弹出门店列表(id 需为 shopSelector,名称 id 为 shopName) |
| .topbar-nav-item | 中部导航项,加 .active 高亮;.nav-icon 包裹图标 |
| .app-body | 顶部栏下方主体,包含左侧栏与内容区 |
| .app-sidebar | 左侧栏,含 .sidebar-toggle 折叠按钮、.sidebar-menu 菜单 |
| .sidebar-group / -title | 菜单分组与分组标题 |
| .sidebar-item | 菜单项,.item-icon / .item-text / .item-arrow;data-submenu="xx" 触发二级弹层 |
| .app-content | 内容区,含 .content-header / .content-title / .breadcrumb |
完整骨架代码
<div class="app-layout">
<!-- 顶部栏 -->
<header class="app-topbar">
<div class="topbar-left">
<div class="topbar-logo">
<div class="logo-icon">M</div>
<span>我的软件</span>
</div>
<div class="shop-selector" id="shopSelector">
<span id="shopName">解放东路店</span>
<i class="iconfont icon-angle-down"></i>
</div>
</div>
<nav class="topbar-center">
<a class="topbar-nav-item active">
<span class="nav-icon"><i class="iconfont icon-gongzuotai"></i></span>工作台
</a>
<a class="topbar-nav-item">
<span class="nav-icon"><i class="iconfont icon-jiaoyimingxi"></i></span>收银台
</a>
</nav>
<div class="topbar-right">
<div class="topbar-sms" id="smsBalance" title="查看短信余额">
<span class="sms-icon"><i class="iconfont icon-duanxin1"></i></span>
<span>短信余额 <b class="fs-16">12,680</b> 条</span>
</div>
<button class="topbar-action" id="msgBtn" title="系统消息">
<i class="iconfont icon-bell"></i><span class="action-badge"></span>
</button>
<button class="topbar-action" id="supportBtn" title="联系客服">
<i class="iconfont icon-headphones"></i>
</button>
<div class="topbar-user" id="topbarUser">
<div class="user-avatar">刘</div>
<div class="user-info">
<div class="user-name">刘明轩</div>
<div class="user-role">超级管理员</div>
</div>
<span class="user-arrow"><i class="iconfont icon-angle-down"></i></span>
</div>
</div>
</header>
<div class="app-body">
<!-- 左侧栏 -->
<aside class="app-sidebar" id="appSidebar">
<button class="sidebar-toggle" id="sidebarToggle"><span class="arrow">‹</span></button>
<div class="sidebar-menu">
<div class="sidebar-group">
<div class="sidebar-group-title">门店管理</div>
<a class="sidebar-item active">
<span class="item-icon"><i class="iconfont icon-huiyuandenglu"></i></span>
<span class="item-text">会员</span>
</a>
<a class="sidebar-item" data-submenu="stock">
<span class="item-icon"><i class="iconfont icon-kucunchaxun1"></i></span>
<span class="item-text">库存</span><span class="item-arrow">›</span>
</a>
</div>
</div>
</aside>
<!-- 内容区 -->
<main class="app-content">
<div class="content-header">
<div>
<h1 class="content-title">
<span class="title-icon"><i class="iconfont icon-gongzuotai"></i></span>
今日工作台
</h1>
<div class="breadcrumb">
<span>首页</span><span class="sep">/</span><span class="current">工作台</span>
</div>
</div>
</div>
<!-- 页面内容 -->
</main>
</div>
</div>
#sidebarToggle、门店切换 #shopSelector、用户下拉 #topbarUser 由组件库自动绑定交互,只要 id 一致即可。data-submenu="xx" 的菜单项会自动弹出二级菜单层。图标规范阿里 iconfont
全站统一使用 iconfont 图标,禁止使用 emoji。写法为 <i class="iconfont icon-图标名"></i>。
<!-- 基础用法 --> <i class="iconfont icon-bell"></i> <!-- 按钮内图标(自动带右间距) --> <button class="btn btn-primary"> <i class="iconfont icon-check-circle"></i> 确认提交 </button> <!-- 图标颜色继承父级,用 style 或 CSS 控制 --> <i class="iconfont icon-trash-can" style="color:var(--color-danger);"></i>
按钮.btn
基础类 .btn,配合颜色类与尺寸类使用。颜色类:btn-primary / btn-success / btn-warning / btn-danger / btn-outline / btn-outline-primary / btn-ghost / btn-blue / btn-green / btn-orange / btn-red。尺寸类:btn-xs / btn-sm / btn-lg。
<button class="btn btn-primary">主色</button> <button class="btn btn-danger">危险</button> <button class="btn btn-outline-primary">主色描边</button> <button class="btn btn-sm btn-primary">小按钮</button> <button class="btn" disabled>禁用</button> <!-- 带图标 --> <button class="btn btn-success"><i class="iconfont icon-check-circle"></i> 确认</button> <!-- 药丸按钮 --> <button class="pill-btn pill-btn-solid">预约列表</button> <button class="pill-btn pill-btn-light">+ 新增预约</button>
标签 / 徽标 / 头像.tag / .badge / .avatar
<span class="tag tag-success">成功</span> <span class="tag tag-warning tag-dot">进行中</span> <span class="badge badge-primary">12</span> <span class="badge badge-dot" style="background:var(--color-success)"></span> <div class="avatar avatar-sm">刘</div> <div class="avatar avatar-lg">刘</div> <div class="avatar avatar-gradient">M</div>
卡片.card
标准卡片由 .card-header / .card-body / .card-footer 组成;变体:card-bordered(描边)、card-flat(无阴影)、card-glow(悬停光晕)。
<div class="card">
<div class="card-header">
<span class="card-header-title">
<span class="card-header-icon"><i class="iconfont icon-wpforms"></i></span>
任务列表
</span>
<div class="card-header-actions">
<button class="btn-icon" title="编辑"><i class="iconfont icon-user-edit"></i></button>
</div>
</div>
<div class="card-body">
<!-- 列表项 -->
<div class="card-list-item">
<div class="card-list-icon" style="background:var(--color-primary-light);color:var(--color-primary);">
<i class="iconfont icon-circle"></i>
</div>
<div class="card-list-body">
<div class="card-list-title">完成项目需求文档</div>
<div class="card-list-desc">截止日期:2026-10-01</div>
</div>
<span class="tag tag-warning tag-dot">进行中</span>
</div>
</div>
<div class="card-footer">
<button class="btn btn-sm btn-outline">取消</button>
<button class="btn btn-sm btn-primary">确认</button>
</div>
</div>
<!-- 并排卡片 -->
<div class="card-row">
<div class="card card-bordered">...</div>
<div class="card card-flat">...</div>
<div class="card card-glow">...</div>
</div>
表格.table
用 .table-wrapper 包裹实现横向滚动。table-hover 悬停高亮,table-striped 斑马纹。
| 姓名 | 部门 | 状态 | 操作 |
|---|---|---|---|
| 张三 | 技术部 | 在职 | |
| 李四 | 产品部 | 休假 | |
| 王五 | 设计部 | 在职 |
<div class="table-wrapper">
<table class="table table-hover">
<thead>
<tr><th>姓名</th><th>部门</th><th>状态</th><th>操作</th></tr>
</thead>
<tbody>
<tr>
<td>张三</td>
<td>技术部</td>
<td><span class="tag tag-success tag-dot">在职</span></td>
<td><button class="btn btn-xs btn-outline-primary">编辑</button></td>
</tr>
</tbody>
</table>
</div>
table-bordered / table-sm(紧凑)。表格扩展.cell-stack / .cell-avatar / .table-scroll-x / .table-sm
在基础表格之上提供四种常用扩展:两行数据单元格、头像单元格、横向滚动容器、紧凑尺寸。单元格结构类都作用于 .table 内部,可与 table-hover / table-align-middle 自由组合。
| 类名 | 作用 | 用法 |
|---|---|---|
| .cell-stack | 单元格内两行文本容器 | 内部放 .cell-main / .cell-sub |
| .cell-main | 主标题(深色、中等字重) | 放在 .cell-stack 内 |
| .cell-sub | 副标题(浅灰、小字) | 放在 .cell-stack 内 |
| .cell-avatar | 头像 + 两行文本组合 | 内部放 .avatar 与 .cell-stack |
| .cell-actions | 操作按钮横排紧凑组 | 包住多个 .btn.btn-xs |
| .table-scroll-x | 横向滚动容器(表格 min-width 720px) | 替代 .table-wrapper 作为外层 |
| .table-sm | 紧凑表格(减小内边距) | 加在 .table 上 |
| .table-align-middle | 表头 / 单元格垂直居中 | 加在 .table 上 |
| 商品信息 | 单价 | 库存 | 状态 |
|---|---|---|---|
玫瑰精油护理SPA · 60分钟 |
¥298 | 12 | 在售 |
日式美睫嫁接美睫 · 90分钟 |
¥398 | 8 | 在售 |
采耳舒缓护理采耳 · 45分钟 |
¥168 | 0 | 缺货 |
<div class="table-wrapper" style="margin-bottom:0;">
<table class="table table-hover table-align-middle">
<thead><tr><th style="width:35%;">商品信息</th><th>单价</th><th>库存</th><th>状态</th></tr></thead>
<tbody>
<tr>
<td><div class="cell-stack"><span class="cell-main">玫瑰精油护理</span><span class="cell-sub">SPA · 60分钟</span></div></td>
<td>¥298</td><td>12</td><td><span class="tag tag-success tag-dot">在售</span></td>
</tr>
<tr>
<td><div class="cell-stack"><span class="cell-main">日式美睫嫁接</span><span class="cell-sub">美睫 · 90分钟</span></div></td>
<td>¥398</td><td>8</td><td><span class="tag tag-success tag-dot">在售</span></td>
</tr>
<tr>
<td><div class="cell-stack"><span class="cell-main">采耳舒缓护理</span><span class="cell-sub">采耳 · 45分钟</span></div></td>
<td>¥168</td><td>0</td><td><span class="tag tag-danger tag-dot">缺货</span></td>
</tr>
</tbody>
</table>
</div>
| 会员 | 手机号 | 等级 | 操作 |
|---|---|---|---|
|
刘
刘明轩注册于 2025-03-12
|
138****1234 | 黄金 | |
|
陈
陈思琪注册于 2025-06-08
|
159****5678 | 铂金 |
<div class="table-wrapper" style="margin-bottom:0;">
<table class="table table-hover table-align-middle">
<thead><tr><th style="width:40%;">会员</th><th>手机号</th><th>等级</th><th>操作</th></tr></thead>
<tbody>
<tr>
<td>
<div class="cell-avatar">
<div class="avatar avatar-gradient">刘</div>
<div class="cell-stack"><span class="cell-main">刘明轩</span><span class="cell-sub">注册于 2025-03-12</span></div>
</div>
</td>
<td>138****1234</td>
<td><span class="tag tag-primary tag-dot">黄金</span></td>
<td><div class="cell-actions"><button class="btn btn-xs btn-outline-primary">详情</button><button class="btn btn-xs btn-outline">编辑</button></div></td>
</tr>
<tr>
<td>
<div class="cell-avatar">
<div class="avatar" style="background:var(--color-success-light);color:var(--color-success);">陈</div>
<div class="cell-stack"><span class="cell-main">陈思琪</span><span class="cell-sub">注册于 2025-06-08</span></div>
</div>
</td>
<td>159****5678</td>
<td><span class="tag tag-success tag-dot">铂金</span></td>
<td><div class="cell-actions"><button class="btn btn-xs btn-outline-primary">详情</button><button class="btn btn-xs btn-outline">编辑</button></div></td>
</tr>
</tbody>
</table>
</div>
| 订单编号 | 会员 | 项目 | 金额 | 支付方式 | 服务技师 | 门店 | 下单时间 | 状态 |
|---|---|---|---|---|---|---|---|---|
| #20260929001 | 刘明轩 | 玫瑰精油护理 | ¥298 | 微信支付 | 小美 | 解放东路店 | 2026-09-29 09:12 | 已完成 |
| #20260929002 | 陈思琪 | 日式美睫嫁接 | ¥398 | 支付宝 | 小丽 | 解放东路店 | 2026-09-29 10:05 | 服务中 |
<div class="table-scroll-x">
<table class="table table-hover table-align-middle">
<thead><tr><th>订单编号</th><th>会员</th><th>项目</th><th>金额</th><th>支付方式</th><th>服务技师</th><th>门店</th><th>下单时间</th><th>状态</th></tr></thead>
<tbody>
<tr><td>#20260929001</td><td>刘明轩</td><td>玫瑰精油护理</td><td>¥298</td><td>微信支付</td><td>小美</td><td>解放东路店</td><td>2026-09-29 09:12</td><td><span class="tag tag-success tag-dot">已完成</span></td></tr>
<tr><td>#20260929002</td><td>陈思琪</td><td>日式美睫嫁接</td><td>¥398</td><td>支付宝</td><td>小丽</td><td>解放东路店</td><td>2026-09-29 10:05</td><td><span class="tag tag-warning tag-dot">服务中</span></td></tr>
</tbody>
</table>
</div>
| 时间 | 项目 | 技师 | 状态 |
|---|---|---|---|
| 09:00 | 玫瑰精油护理 | 小美 | 已完成 |
| 10:00 | 日式美睫嫁接 | 小丽 | 进行中 |
| 11:00 | 采耳舒缓护理 | 小芳 | 待开始 |
<div class="table-wrapper" style="margin-bottom:0;">
<table class="table table-sm table-hover table-align-middle">
<thead><tr><th>时间</th><th>项目</th><th>技师</th><th>状态</th></tr></thead>
<tbody>
<tr><td>09:00</td><td>玫瑰精油护理</td><td>小美</td><td><span class="tag tag-success tag-dot">已完成</span></td></tr>
<tr><td>10:00</td><td>日式美睫嫁接</td><td>小丽</td><td><span class="tag tag-warning tag-dot">进行中</span></td></tr>
<tr><td>11:00</td><td>采耳舒缓护理</td><td>小芳</td><td><span class="tag tag-info tag-dot">待开始</span></td></tr>
</tbody>
</table>
</div>
表格批量选择.table-checkbox / .table-select-bar
在表格上加复选框实现批量操作:表格 id 固定为 batchTable,表头全选框 id 固定为 checkAll,行内复选框加 .row-check,底部 .table-select-bar 实时显示已选数量(#selectedCount 由组件库自动更新,全选后自动显示半选态)。
| 类名 / id | 作用 |
|---|---|
| .table-checkbox | 表格复选框(跟随主题色) |
| #checkAll | 表头全选框,自动显示半选状态 |
| .row-check | 行内复选框 |
| .table-select-bar | 表格下方批量操作条(主色浅底) |
| .select-count | 已选数量文案,内部 <b id="selectedCount"> 自动更新 |
| 会员 | 手机号 | 等级 | 余额 | |
|---|---|---|---|---|
|
刘
刘明轩注册于 2025-03-12
|
138****1234 | 黄金 | ¥2,860.00 | |
|
陈
陈思琪注册于 2025-06-08
|
159****5678 | 铂金 | ¥5,320.00 | |
|
王
王雅婷注册于 2025-09-21
|
186****9012 | 白银 | ¥860.00 |
<table class="table table-hover table-align-middle" id="batchTable">
<thead>
<tr>
<th style="width:40px;"><input type="checkbox" class="table-checkbox" id="checkAll"></th>
<th>会员</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox" class="table-checkbox row-check"></td>
<td>刘明轩</td>
</tr>
</tbody>
</table>
<!-- 底部批量操作条:已选数量自动更新 -->
<div class="table-select-bar">
<span class="select-count">已选 <b id="selectedCount">0</b> 项</span>
<div class="select-actions">
<button class="btn btn-sm btn-outline-primary">批量打标签</button>
<button class="btn btn-sm btn-danger">删除</button>
</div>
</div>
batchTable、全选框 id 必须是 checkAll,否则联动不生效。顶部通知条.notice-bar
页面顶部的横幅提示,结构为「图标 + 文案 + 操作 + 关闭」。默认主色,可用 notice-success / notice-warning / notice-danger / notice-plain 切换语义色;点右侧 ✕ 会淡出并移除。
| 类名 | 作用 |
|---|---|
| .notice-bar | 通知条容器(默认主色) |
| .notice-icon | 左侧图标,颜色跟随语义色 |
| .notice-text | 正文,支持 <b> 强调 |
| .notice-action | 右侧文字操作(可点击) |
| .notice-close | 关闭按钮,自动绑定淡出移除 |
| notice-success / -warning / -danger / -plain | 语义色变体 |
<div class="notice-bar"> <span class="notice-icon"><i class="iconfont icon-bullhorn"></i></span> <span class="notice-text">系统将于今晚 <b>23:00 - 24:00</b> 升级维护。</span> <span class="notice-action">查看详情</span> <button class="notice-close" title="关闭">✕</button> </div> <!-- 语义色:notice-success / notice-warning / notice-danger / notice-plain --> <div class="notice-bar notice-warning">...</div>
下拉刷新 / 上拉加载.pull-refresh / .load-more
列表顶部的「下拉刷新」与底部的「上拉加载更多」。加上 .loading 后图标自动旋转并禁止重复点击,适合接入真实请求。
<!-- 列表顶部 --> <div class="pull-refresh"> <i class="iconfont icon-recycle"></i><span>下拉刷新</span> </div> <!-- 列表底部 --> <div class="load-more"> <i class="iconfont icon-hourglass-half"></i><span>上拉加载更多</span> </div>
.loading 类,图标自动旋转并禁止重复点击;请求结束后移除即可。表单.form-group / .form-input
表单结构:.form-row(一行多列)→ .form-group(字段)→ .form-label + 控件 + .form-hint(说明)。
<div class="form-row">
<div class="form-group">
<label class="form-label">用户名 <span class="required">*</span></label>
<input type="text" class="form-input" placeholder="请输入用户名">
<div class="form-hint">用于登录,4-20 个字符</div>
</div>
<div class="form-group">
<label class="form-label">带图标输入框</label>
<div class="input-icon-wrapper">
<span class="input-icon"><i class="iconfont icon-search"></i></span>
<input type="text" class="form-input" placeholder="搜索内容...">
</div>
</div>
</div>
<!-- 下拉选择 / 开关 -->
<select class="form-select"><option>北京</option></select>
<label class="switch">
<input type="checkbox" checked><span class="switch-slider"></span>
</label>
数字加减框.number-input-group
结构为「减号按钮 + 数字输入 + 加号按钮」,按钮通过 data-target 指向输入框 id、data-step 指定步长,由组件库自动绑定。
尺寸档位:number-input-group-lg / -sm / -xs / -mini;竖直紧凑排列加 .num-vertical(可叠加 num-vertical-mini)。小数用 data-decimals="1" 控制位数。
<!-- 横向:减 - 输入 - 加 -->
<div class="number-input-group">
<button class="number-btn" data-target="num1" data-step="-1">−</button>
<input type="number" class="number-input" id="num1" value="1" min="1" max="99">
<button class="number-btn" data-target="num1" data-step="1">+</button>
</div>
<!-- 小数:data-decimals 控制小数位 -->
<div class="number-input-group">
<button class="number-btn" data-target="num2" data-step="-0.1">−</button>
<input type="number" class="number-input" id="num2" value="1.5" min="0" max="99" data-decimals="1">
<button class="number-btn" data-target="num2" data-step="0.1">+</button>
</div>
<!-- 竖直紧凑:输入在左,加减在右上下排列 -->
<div class="number-input-group num-vertical num-vertical-mini">
<input type="number" class="number-input" id="num3" value="1" min="0" max="99">
<div class="num-vertical-btns">
<button class="number-btn" data-target="num3" data-step="1">+</button>
<button class="number-btn" data-target="num3" data-step="-1">−</button>
</div>
</div>
min / max 边界时,对应方向的按钮会自动置灰禁用。下拉菜单.dropdown
结构固定为 .dropdown → .dropdown-toggle(含 .dropdown-value 显示当前值)+ .dropdown-menu → .dropdown-item。分组用 .dropdown-group + .dropdown-group-title。
限高:面板统一由 --dropdown-max-h 控制(默认 240px),也可用档位类 h-160 / h-200 / h-240 / h-300 / h-auto,超出即出现滚动条。
<!-- 基础下拉:选中后自动写入 .dropdown-value 并加 .active -->
<div class="dropdown">
<button class="dropdown-toggle placeholder" type="button">
<span class="dropdown-value">请选择耗材</span>
<i class="iconfont icon-angle-down dropdown-arrow"></i>
</button>
<div class="dropdown-menu h-200">
<div class="dropdown-item" data-value="玫瑰精油">玫瑰精油<i class="iconfont icon-check dropdown-check"></i></div>
<div class="dropdown-item" data-value="美睫胶水">美睫胶水<i class="iconfont icon-check dropdown-check"></i></div>
</div>
</div>
<!-- 分组下拉:分组标题为浅灰小字,选项缩进一级 -->
<div class="dropdown">
<button class="dropdown-toggle placeholder" type="button">
<span class="dropdown-value">按分类选择</span>
<i class="iconfont icon-angle-down dropdown-arrow"></i>
</button>
<div class="dropdown-menu h-240">
<div class="dropdown-group">
<div class="dropdown-group-title">护理类</div>
<div class="dropdown-item" data-value="玫瑰精油">玫瑰精油<i class="iconfont icon-check dropdown-check"></i></div>
</div>
<div class="dropdown-group">
<div class="dropdown-group-title">美睫类</div>
<div class="dropdown-item" data-value="美睫胶水">美睫胶水<i class="iconfont icon-check dropdown-check"></i></div>
</div>
</div>
</div>
<!-- 自定义限高 -->
<div class="dropdown-menu" style="--dropdown-max-h:180px;">...</div>
.dropdown-value、移除 .placeholder、关闭面板,并弹出一条 toast 提示。分组标题在滚动时吸顶。搜索筛选栏.filter-bar
列表页顶部的筛选组合。容器 .filter-bar 使用 flex 自动换行,.filter-item 为单个筛选项(关键词项加 filter-item-keyword 占更宽),按钮组用 .filter-actions。窄屏自动变成一列。下面给出四种常用组合:关键词、会员搜索、日期区间、综合筛选栏。
.member-search-value。候选数据可用容器的 data-members(JSON 数组)传入,不传则用内置演示数据。picker-date,无需额外 JS;快捷按钮加 data-range-preset(today / 7d / 30d / month)即可自动回填并高亮。<!-- ① 关键词 + 下拉选择 + 查询 -->
<form class="filter-bar" onsubmit="return false;">
<div class="filter-item filter-item-keyword">
<div class="input-icon-wrapper">
<span class="input-icon"><i class="iconfont icon-search"></i></span>
<input type="text" class="form-input" placeholder="会员姓名 / 手机号 / 订单号">
</div>
</div>
<div class="filter-item">
<select class="form-select">
<option>全部状态</option>
<option>已完成</option>
<option>服务中</option>
<option>待服务</option>
</select>
</div>
<div class="filter-item filter-actions">
<button class="btn btn-primary" type="button"><i class="iconfont icon-search"></i> 查询</button>
<button class="btn btn-outline" type="button"><i class="iconfont icon-recycle"></i> 重置</button>
</div>
</form>
<!-- ② 会员搜索(输入联想 + 下拉候选) -->
<div class="member-search" style="max-width:360px;">
<div class="input-icon-wrapper">
<span class="input-icon"><i class="iconfont icon-search"></i></span>
<input type="text" class="form-input member-search-input" placeholder="输入会员姓名或手机号" autocomplete="off">
</div>
<input type="hidden" class="member-search-value">
<div class="member-search-panel"></div>
</div>
<!-- ③ 日期区间(从日期到日期) -->
<div style="max-width:440px;">
<div class="date-range">
<div class="picker-trigger">
<input type="text" class="form-input picker-date date-range-start" placeholder="开始日期" readonly>
</div>
<span class="date-range-sep">至</span>
<div class="picker-trigger">
<input type="text" class="form-input picker-date date-range-end" placeholder="结束日期" readonly>
</div>
</div>
<div class="date-range-presets">
<button class="date-range-chip" type="button" data-range-preset="today">今天</button>
<button class="date-range-chip" type="button" data-range-preset="7d">近 7 天</button>
<button class="date-range-chip" type="button" data-range-preset="30d">近 30 天</button>
<button class="date-range-chip" type="button" data-range-preset="month">本月</button>
</div>
</div>
<!-- ④ 综合筛选栏:会员 + 日期区间 + 两个下拉 + 查询(快捷区间放重置后面) -->
<form class="filter-bar" onsubmit="return false;">
<div class="filter-item filter-item-keyword">
<div class="member-search">
<div class="input-icon-wrapper">
<span class="input-icon"><i class="iconfont icon-search"></i></span>
<input type="text" class="form-input member-search-input" placeholder="搜索会员姓名 / 手机号" autocomplete="off">
</div>
<input type="hidden" class="member-search-value">
<div class="member-search-panel"></div>
</div>
</div>
<div class="filter-item" style="flex:1 1 300px;">
<div class="date-range">
<div class="picker-trigger">
<input type="text" class="form-input picker-date date-range-start" placeholder="开始日期" readonly>
</div>
<span class="date-range-sep">至</span>
<div class="picker-trigger">
<input type="text" class="form-input picker-date date-range-end" placeholder="结束日期" readonly>
</div>
</div>
</div>
<div class="filter-item">
<select class="form-select">
<option>全部门店</option>
<option>旗舰店</option>
<option>社区店</option>
</select>
</div>
<div class="filter-item">
<select class="form-select">
<option>全部状态</option>
<option>已完成</option>
<option>服务中</option>
<option>待服务</option>
<option>已取消</option>
</select>
</div>
<div class="filter-item filter-actions">
<button class="btn btn-primary" type="button"><i class="iconfont icon-search"></i> 查询</button>
<button class="btn btn-outline" type="button"><i class="iconfont icon-recycle"></i> 重置</button>
<div class="date-range-presets">
<button class="date-range-chip" type="button" data-range-preset="today">今天</button>
<button class="date-range-chip" type="button" data-range-preset="7d">近 7 天</button>
<button class="date-range-chip" type="button" data-range-preset="month">本月</button>
</div>
</div>
</form>
// 会员搜索候选数据:写在容器 data-members 上(JSON 数组),不写则用内置演示数据
<div class="member-search"
data-members='[{"id":"M1001","name":"刘明轩","phone":"13800001234","level":"黄金"},
{"id":"M1002","name":"陈静怡","phone":"13900008821","level":"铂金"}]'>
// 读取选中结果:姓名在输入框,ID 在隐藏域
const keyword = document.querySelector('.member-search-input').value; // 刘明轩
const memberId = document.querySelector('.member-search-value').value; // M1001
// 收集整个筛选栏的条件
function collectFilter(form) {
return {
memberId: form.querySelector('.member-search-value')?.value || '',
keyword: form.querySelector('.member-search-input')?.value || '',
start: form.querySelector('.date-range-start')?.value || '',
end: form.querySelector('.date-range-end')?.value || '',
selects: Array.from(form.querySelectorAll('.form-select')).map(s => s.value)
};
}
picker-date,会员搜索、快捷区间按钮由库在加载时自动绑定。金额输入框.money-input-group
收银 / 结算场景专用:左侧固定 ¥ 前缀,输入自动加千分位、限制两位小数,失焦时补齐两位小数。给 input 加 .money-input 即自动绑定,无需额外 JS。
<div class="money-input-group"> <span class="money-prefix">¥</span> <input type="text" class="money-input" placeholder="0.00"> </div>
parseFloat(input.value.replace(/,/g, '')) 去掉千分位。大备注框.remark-box
多行备注输入:头部标题 + 工具栏,中部 textarea,底部字数统计。字数达上限时计数变红,data-remark-clear 按钮一键清空,加 remark-box-lg 可增大高度。
| 类名 | 作用 |
|---|---|
| .remark-box | 外层容器(聚焦时主色描边) |
| .remark-box-head | 头部:标题 + 工具按钮 |
| .remark-box-textarea | 多行输入区(可拖拽调整高度) |
| .remark-box-foot | 底部:提示 + 字数统计 |
| .remark-box-count | 字数统计,内部 <b> 自动更新,超限加 .over |
| data-remark-clear | 点击清空输入框 |
<div class="remark-box">
<div class="remark-box-head">
<div class="remark-box-title">会员备注</div>
<div class="remark-box-tools">
<button class="remark-box-tool" type="button" data-remark-clear>清空</button>
</div>
</div>
<textarea class="remark-box-textarea" maxlength="500" placeholder="填写备注"></textarea>
<div class="remark-box-foot">
<span>支持换行</span>
<span class="remark-box-count"><b>0</b> / 500</span>
</div>
</div>
项目 / 商品选择器.goods-picker
带搜索过滤的多选列表,常用于开单时挑选服务项目或商品。结构为「搜索框 + 列表 + 底部已选汇总」。搜索框 id 固定 goodsSearch、列表 id 固定 goodsList,点击 .goods-item 切换选中态,底部 #goodsCount 自动统计。
<div class="goods-picker">
<div class="goods-search">
<div class="input-icon-wrapper">
<span class="input-icon"><i class="iconfont icon-search"></i></span>
<input type="text" class="form-input" id="goodsSearch" placeholder="搜索项目 / 商品名称">
</div>
</div>
<div class="goods-list" id="goodsList">
<div class="goods-item" data-name="玫瑰精油护理" data-price="298">
<div class="goods-thumb"><img src="..." alt=""></div>
<div class="goods-info">
<div class="goods-name">玫瑰精油护理</div>
<div class="goods-meta">60分钟 · ¥298</div>
</div>
<span class="goods-check"><i class="iconfont icon-check"></i></span>
</div>
</div>
<div class="goods-footer">
<span class="goods-selected">已选 <b id="goodsCount">0</b> 项</span>
<button class="btn btn-sm btn-primary" id="goodsConfirm">确认添加</button>
</div>
</div>
验证码.verify-code-group
两种形态:一体式 .verify-code-group(输入框与按钮合并)和分行式 .verify-code-row。按钮 onclick="startCountdown(this)" 触发 60 秒倒计时。
<!-- 一体式 -->
<div class="verify-code-group">
<input type="tel" class="verify-code-input" placeholder="请输入手机号码" maxlength="11">
<button class="verify-code-btn" onclick="startCountdown(this)">获取验证码</button>
</div>
<!-- 分行式 -->
<div class="verify-code-row">
<div class="verify-code-input-wrap">
<input type="tel" class="verify-code-input" placeholder="请输入手机号码" maxlength="11">
</div>
<button class="verify-code-btn" onclick="startCountdown(this)">获取验证码</button>
</div>
日期 / 时间选择下拉面板 + 滚轮选择器
三种交互:PC 端使用下拉面板(input.picker-date / input.picker-time / input.picker-datetime 自动绑定,无遮罩);移动端使用滚轮选择器(data-wheel="date|time|datetime",弹窗内上下滑动)。picker-datetime 把日期与时间集成在同一面板,输出 YYYY-MM-DD HH:mm:ss。窄屏或触屏设备会自动切换到滚轮模式。
<!-- 下拉面板(自动绑定) --> <div class="picker-trigger"> <input type="text" class="form-input picker-date" placeholder="点击选择日期" readonly> </div> <div class="picker-trigger"> <input type="text" class="form-input picker-time" placeholder="点击选择时间" readonly> </div> <!-- 日期 + 时间一体选择(输出 2026-09-29 14:30:00) --> <div class="picker-trigger"> <input type="text" class="form-input picker-datetime" placeholder="点击选择日期时间" readonly> </div> <!-- 滚轮选择器(自动绑定) --> <button class="btn btn-outline-primary" data-wheel="date">滚轮选择日期</button> <button class="btn btn-outline-primary" data-wheel="time">滚轮选择时间</button> <button class="btn btn-outline-primary" data-wheel="datetime">滚轮选择日期时间</button>
// 主动调用滚轮选择器,回调返回格式化字符串
openWheelDatePicker(val => console.log('日期:' + val)); // 2026-09-29
openWheelTimePicker(val => console.log('时间:' + val)); // 14:30:00
openWheelDateTimePicker(val => console.log('日期时间:' + val)); // 2026-09-29 14:30:00
选项卡.tabs
结构:.tabs 内放 .tab-item[data-tab],面板为 .tab-panel#对应id。药丸样式加 .tabs-pill。
<div class="tabs"> <button class="tab-item active" data-tab="t1">推荐</button> <button class="tab-item" data-tab="t2">热门</button> <button class="tab-item" data-tab="t3">最新</button> </div> <div class="tab-panel active" id="t1">推荐内容面板</div> <div class="tab-panel" id="t2">热门内容面板</div> <div class="tab-panel" id="t3">最新内容面板</div> <!-- 药丸样式 --> <div class="tabs tabs-pill">...</div>
分页.pagination
三种形态:标准分页(#paginationDemo 由库自动渲染)、简单上一页/下一页(.pagination-simple)、带每页条数(.pagination-with-size)。
<!-- 标准分页:库会自动渲染,容器 id 固定为 paginationDemo -->
<div class="pagination" id="paginationDemo"></div>
<!-- 简单分页 -->
<div class="pagination-simple" id="paginationSimple">
<button class="btn btn-sm btn-outline" data-pg-prev>‹ 上一页</button>
<span class="page-info">第 <strong>1</strong> / 20 页</span>
<button class="btn btn-sm btn-outline" data-pg-next>下一页 ›</button>
</div>
<!-- 带每页条数 -->
<div class="pagination-with-size">
<span>共 186 条记录</span>
<select class="page-size-select" id="pageSizeSelect">
<option>10</option><option selected>20</option><option>50</option>
</select>
</div>
进度条 / 步骤条.progress / .steps
<!-- 进度条:width 控制进度,success / warning / danger 换色 --> <div class="progress"><div class="progress-bar" style="width:60%"></div></div> <div class="progress"><div class="progress-bar success" style="width:100%"></div></div> <!-- 步骤条:done / active / 默认 --> <div class="steps"> <div class="step done"><div class="step-num">✓</div><div class="step-text">填写信息</div><div class="step-line"></div></div> <div class="step active"><div class="step-num">2</div><div class="step-text">确认订单</div><div class="step-line"></div></div> <div class="step"><div class="step-num">3</div><div class="step-text">完成</div></div> </div>
骨架屏 / 空状态.skeleton / .empty
<!-- 骨架屏 --> <div class="skeleton" style="height:20px;width:60%;"></div> <!-- 空状态 --> <div class="empty"> <div class="empty-icon"><i class="iconfont icon-box"></i></div> <div class="empty-text">暂无数据</div> </div>
图文 / 列表卡片.media-card
首页、工作台最常用的卡片形态。由 .media-card-head(标题 + 更多)+ .media-list → .media-item 组成;列表项可带缩略图 .media-item-thumb、主副标题、右侧徽标。
<div class="media-card">
<div class="media-card-head">
<div class="media-card-title"><i class="iconfont icon-exclamation-triangle"></i>库存预警</div>
<span class="media-card-more" data-media-more>全部 <i class="iconfont icon-angle-right"></i></span>
</div>
<div class="media-list">
<!-- 纯文字列表项 -->
<div class="media-item">
<div class="media-item-main">
<div class="media-item-title">玫瑰精油</div>
<div class="media-item-sub">SPA耗材</div>
</div>
<span class="media-item-badge">仅剩 3</span>
</div>
<!-- 带缩略图的列表项 -->
<div class="media-item">
<div class="media-item-thumb"><img src="图片地址" alt=""></div>
<div class="media-item-main">
<div class="media-item-title">玫瑰精油护理</div>
<div class="media-item-sub">60 分钟 · ¥298</div>
</div>
<span class="media-item-badge badge-primary">热销</span>
</div>
</div>
</div>
badge-primary / badge-success / badge-warning / badge-danger;不带类时为中性灰。点击列表项 / 更多由页面自行处理。业务卡片.card-birthday / .card-add
工作台常用的「提醒卡 + 快捷入口」组合:.card-birthday 用于提醒类卡片(带角标数字),.card-add 用于新增入口。
<!-- 提醒卡片 -->
<div class="card-birthday">
<div class="birthday-icon"><i class="iconfont icon-birthday-cake"></i></div>
<div class="birthday-body">
<div class="birthday-title">会员生日</div>
<div class="birthday-sub">今日生日提醒</div>
</div>
<div class="birthday-count">2</div>
</div>
<!-- 快捷新增入口 -->
<div class="card-row">
<div class="card-add">
<div class="add-icon" style="background:var(--color-success);">+</div>
<div class="add-title">新增会员</div>
</div>
<div class="card-add">
<div class="add-icon" style="background:var(--color-warning);">+</div>
<div class="add-title">新增预约</div>
</div>
</div>
时间轴.timeline / .timeline-item
纵向时间轴,用于展示流程进度、操作日志、订单流转等。结构:.timeline(列表)→ .timeline-item(节点)→ .timeline-badge(圆点)+ .timeline-content(卡片)。连接竖线由 ::before 自动绘制,最后一个节点自动收尾,无需手动处理。
| 类名 | 作用 |
|---|---|
| .timeline | 时间轴容器(ul) |
| .timeline-item | 单个节点(li),自动绘制连接竖线 |
| .timeline-badge | 左侧圆形节点,默认主色;可加 success / warning / danger |
| .timeline-content | 右侧内容卡片(悬停加深阴影) |
| .timeline-header | 标题行:左标题 + 右时间,窄屏自动换行 |
| .timeline-title | 节点标题 |
| .timeline-time | 右侧时间文本(浅灰小字) |
| .timeline-desc | 描述正文 |
| .timeline-footer | 底部信息行(常放状态标签 + 操作人) |
-
预约确认 2026-09-29 09:00刘明轩 已成功预约「玫瑰精油护理」,服务技师:小美
-
客户到店 2026-09-29 09:10会员 刘明轩 已到店,前台接待并确认服务项目。
-
服务中 2026-09-29 09:20技师 小美 开始为会员提供玫瑰精油护理服务,预计 60 分钟。
-
订单完成 2026-09-29 10:25订单 #20260929001 已结算,金额 ¥298,支付方式:微信支付。
<ul class="timeline">
<li class="timeline-item">
<!-- 圆点:默认主色,可加 success / warning / danger -->
<div class="timeline-badge success"><i class="iconfont icon-check"></i></div>
<div class="timeline-content">
<div class="timeline-header">
<span class="timeline-title">预约确认</span>
<span class="timeline-time">2026-09-29 09:00</span>
</div>
<div class="timeline-desc">刘明轩 已成功预约「玫瑰精油护理」,服务技师:小美</div>
<div class="timeline-footer">
<span class="tag tag-success tag-dot">已完成</span>
<span>操作人:系统</span>
</div>
</div>
</li>
<li class="timeline-item">
<div class="timeline-badge"><i class="iconfont icon-user"></i></div>
<div class="timeline-content">...</div>
</li>
</ul>
栅格系统.row / .col
12 列等宽栅格:col-1 ~ col-12;五等分 col-5th;偏移 col-offset-*;响应式 col-resp。
<div class="row"> <div class="col col-4">1/3</div> <div class="col col-4">1/3</div> <div class="col col-4">1/3</div> </div> <div class="row"> <div class="col col-6">1/2</div> <div class="col col-3">1/4</div> <div class="col col-3">1/4</div> </div> <!-- 五等分 --> <div class="row"> <div class="col col-5th">1/5</div> <div class="col col-5th">1/5</div> <div class="col col-5th">1/5</div> <div class="col col-5th">1/5</div> <div class="col col-5th">1/5</div> </div>
文字大小.fs-12 ~ .fs-20
字号工具类:fs-12 / fs-14(正文默认)/ fs-16 / fs-18 / fs-20。
<span class="fs-12">辅助说明文字</span> <span class="fs-14">正文默认</span> <span class="fs-16">小标题</span> <span class="fs-18">标题</span> <span class="fs-20">大标题</span>
移动端底部导航.mobile-tabbar
固定底部 Tab 导航:.tabbar-item[data-tab] 对应页面 .tab-page[data-page],点击自动切换高亮与页面。角标用 .tab-badge。
<div class="mobile-frame">
<div class="mobile-tab-view">
<div class="tab-page active" data-page="home">首页内容</div>
<div class="tab-page" data-page="member">会员内容</div>
</div>
<div class="mobile-tabbar">
<div class="tabbar-item active" data-tab="home">
<span class="tab-icon"><i class="iconfont icon-gongzuotai"></i></span>
<span class="tab-label">首页</span>
</div>
<div class="tabbar-item" data-tab="member">
<span class="tab-icon"><i class="iconfont icon-huiyuan"></i></span>
<span class="tab-label">会员</span>
</div>
<div class="tabbar-item" data-tab="msg">
<span class="tab-icon"><i class="iconfont icon-comment-dots"></i></span>
<span class="tab-label">消息</span>
<span class="tab-badge">3</span> <!-- 未读角标 -->
</div>
</div>
</div>
抽屉.drawer
左右两侧滑出的面板。左侧用于菜单导航,右侧用于筛选条件。结构固定:外层 .drawer-mask#drawerMask,内层 .drawer#appDrawer(右侧抽屉加 .drawer-right,id 为 drawerRightMask / appDrawerRight)。触发按钮加 data-open="drawer" 或 data-open="drawerRight"。
<!-- 触发按钮 -->
<button class="btn" data-open="drawer">打开左侧抽屉</button>
<button class="btn" data-open="drawerRight">打开右侧抽屉</button>
<!-- 左侧抽屉容器 -->
<div class="drawer-mask" id="drawerMask">
<div class="drawer" id="appDrawer">
<div class="drawer-header">
<div class="drawer-title">菜单导航</div>
<button class="drawer-close" data-drawer-close>✕</button>
</div>
<div class="drawer-body">
<ul class="drawer-menu">
<li class="drawer-menu-item active">首页</li>
<li class="drawer-menu-item">会员管理<span class="menu-badge">12</span></li>
<!-- 二级菜单:data-submenu-toggle 控制展开 -->
<li class="drawer-menu-item" data-submenu-toggle>预约管理<span class="arrow">▶</span></li>
<ul class="drawer-submenu">
<li class="drawer-menu-item">今日预约</li>
<li class="drawer-menu-item">预约列表</li>
</ul>
</ul>
</div>
<div class="drawer-footer">v1.0.0</div>
</div>
</div>
<!-- 右侧抽屉容器(筛选面板) -->
<div class="drawer-mask" id="drawerRightMask">
<div class="drawer drawer-right" id="appDrawerRight">
<div class="drawer-header">
<div class="drawer-title">筛选条件</div>
<button class="drawer-close" data-drawer-right-close>✕</button>
</div>
<div class="drawer-body">
<div class="drawer-section">
<div class="drawer-section-title">项目类型</div>
<div class="drawer-tag-row">
<span class="drawer-tag active">全部</span>
<span class="drawer-tag">护理</span>
</div>
</div>
</div>
<div class="drawer-actions">
<button class="btn" data-drawer-right-close>重置</button>
<button class="btn btn-primary" data-drawer-right-close>确定</button>
</div>
</div>
</div>
drawerMask / appDrawer / drawerRightMask / appDrawerRight,否则交互不生效。点击遮罩、按 Esc 均可关闭。顶部快捷菜单.topbar(面板)
从顶部下拉的快捷操作面板,常用于「新建 / 收银 / 报表」等高频入口。容器 id 固定为 topbarMask / topbarPanel,触发按钮加 data-open="topbar"。
<button class="btn" data-open="topbar">打开快捷菜单</button>
<div class="topbar-mask" id="topbarMask">
<div class="topbar" id="topbarPanel">
<div class="topbar-header">
<div class="topbar-title">快捷操作</div>
<button class="topbar-close" data-topbar-close>✕</button>
</div>
<div class="topbar-body">
<!-- 图标九宫格 -->
<div class="topbar-icon-grid">
<div class="topbar-icon-item">
<span class="icon-circle"><i class="iconfont icon-user-plus"></i></span>
<span class="icon-label">新增会员</span>
</div>
<div class="topbar-icon-item">
<span class="icon-circle icon-circle-green"><i class="iconfont icon-yuyue1"></i></span>
<span class="icon-label">新增预约</span>
</div>
</div>
<!-- 菜单列表 -->
<ul class="topbar-menu">
<li class="topbar-menu-item">系统设置<span class="menu-desc">版本 1.0.0</span></li>
<li class="topbar-menu-item">消息通知<span class="menu-badge">5</span></li>
</ul>
</div>
</div>
</div>
底部操作面板.actionsheet
从底部升起的选择面板,移动端常用。容器 id 固定为 actionsheetMask / actionsheetPanel,触发按钮加 data-open="actionsheet",取消按钮加 data-actionsheet-cancel。
<button class="btn" data-open="actionsheet">打开底部面板</button>
<div class="actionsheet-mask" id="actionsheetMask">
<div class="actionsheet" id="actionsheetPanel">
<div class="actionsheet-header">选择操作</div>
<div class="actionsheet-menu">
<div class="actionsheet-menu-item">拍照</div>
<div class="actionsheet-menu-item">从相册选择</div>
<div class="actionsheet-menu-item danger">删除</div>
</div>
<div class="actionsheet-cancel" data-actionsheet-cancel>取消</div>
</div>
</div>
Tip 气泡.tip-trigger
点击触发的轻量气泡提示。data-tip 为内容,data-placement 控制气泡相对触发元素的方向,取值 top(默认)/ bottom / left / right。悬停提示用 .hover-tip-wrapper,方向同样由 data-placement 控制。
| data-placement | 气泡位置 | 小箭头朝向 |
|---|---|---|
| top(默认) | 触发元素上方 | 向下 ↓ |
| bottom | 触发元素下方 | 向上 ↑ |
| left | 触发元素左侧 | 向右 → |
| right | 触发元素右侧 | 向左 ← |
<!-- 点击气泡:data-placement 控制方向 top / bottom / left / right,省略则默认 top --> <div class="tip-trigger" data-placement="top" data-tip="气泡在触发元素上方"> <span class="dot"></span> 上 </div> <div class="tip-trigger" data-placement="bottom" data-tip="气泡在触发元素下方">下</div> <div class="tip-trigger" data-placement="left" data-tip="气泡在触发元素左侧">左</div> <div class="tip-trigger" data-placement="right" data-tip="气泡在触发元素右侧">右</div> <!-- 悬停气泡:方向同样用 data-placement 控制 --> <span class="hover-tip-wrapper" data-placement="right"> 鼠标移到这里 <span class="hover-tip-content">右侧悬停提示气泡。</span> </span>
Toast 轻提示toast(message, type, duration)
顶部居中的轻量提示,自动消失。容器 #toastContainer 由库自动创建,无需手写。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| message | string | — | 提示文字(支持 HTML) |
| type | string | 'info' | success / error / warning / info |
| duration | number | 2500 | 停留毫秒数 |
toast('保存成功', 'success');
toast('网络异常,请重试', 'error');
toast('库存不足,请注意', 'warning');
toast('已复制到剪贴板'); // 默认 info
toast('这条提示停留 5 秒', 'info', 5000);
Loading 加载showLoading / hideLoading
全屏加载遮罩,容器 #loadingMask 由库自动创建。
showLoading('正在提交...'); // 显示,可传自定义文案
setTimeout(hideLoading, 2000); // 隐藏
// 典型用法
showLoading();
fetch('/api/save').then(() => {
hideLoading();
toast('保存成功', 'success');
}).catch(() => {
hideLoading();
toast('保存失败', 'error');
});
弹窗系统openModal / alertBox / confirmBox / promptBox
统一的弹窗 API,支持普通 / 大 / 表单 / iframe 四种形态与 4 个尺寸档位。所有弹窗与提示框均支持 Enter 确认、Esc 取消(输入框内回车同样确认,多行文本除外)。
openModal(options)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| title | string | '提示' | 标题,可含图标 HTML |
| body | string | '' | 内容 HTML |
| size | string | 'sm' | sm / md / lg / xl |
| iframe | string | '' | 传入地址则以 iframe 方式加载,忽略 body |
| scrollable | boolean | false | 内容超出高度时是否可滚动 |
| buttons | array | [{text:'确定',type:'primary'}] | 按钮配置数组,见下表 |
| onOpen | function | null | 打开后回调,参数为弹窗容器 DOM,用于绑定内部元素 |
| onConfirm | function | null | 点确定 / 按 Enter 触发 |
| onCancel | function | null | 点取消 / 按 Esc 触发 |
buttons 数组项:text(按钮文字)、type(primary 或 outline)、onOk(box)(点击回调)、close(设为 false 时点击不关闭,用于表单校验失败时保留弹窗)。
// 1. 普通弹窗
openModal({
title: '普通弹窗',
body: '支持 <strong>HTML</strong> 内容',
onConfirm: () => toast('已确认')
});
// 2. 大弹窗(可滚动)
openModal({
title: '详情预览',
size: 'lg',
scrollable: true,
body: '<div>...</div>'
});
// 3. 表单弹窗:校验失败时不关闭
openModal({
title: '新增会员',
size: 'md',
body: '<input class="form-input" id="mName">',
buttons: [
{ text: '取消', type: 'outline' },
{ text: '保存', type: 'primary', close: false,
onOk: () => {
const name = document.getElementById('mName').value.trim();
if (!name) { toast('请输入姓名', 'error'); return; } // 不关闭,留在弹窗
toast('已保存:' + name, 'success');
}
}
]
});
// 4. iframe 弹窗
openModal({ title: '选择商品', size: 'xl', iframe: '/goods/select.html' });
// 5. 打开后操作内部元素
openModal({
title: '绑定事件',
body: '<button id="innerBtn">内部按钮</button>',
onOpen: (box) => {
box.querySelector('#innerBtn').addEventListener('click', () => toast('点击了内部按钮'));
}
});
快捷方法
// Alert 提示
alertBox('这是一个提示', '提示');
// Confirm 确认(点确定执行回调)
confirmBox('确定要删除吗?此操作不可撤销。', () => {
toast('已删除', 'success');
});
// Prompt 输入(回调接收输入值)
promptBox('请输入你的名字:', '', (val) => {
toast('你输入了:' + val);
});
验证码倒计时startCountdown(btn)
给按钮绑定 60 秒倒计时。若按钮位于 .verify-code-group 内且输入框为 type="tel",会先校验手机号。
<button class="verify-code-btn" onclick="startCountdown(this)">获取验证码</button>
<!-- 也可在脚本中绑定 -->
<script>
document.querySelector('#sendCodeBtn').addEventListener('click', function () {
startCountdown(this);
});
</script>
滚轮选择器openWheelDatePicker / openWheelTimePicker
移动端风格的上下滑动选择器,在弹窗内使用,同样支持 Enter 确认 / Esc 取消。
openWheelDatePicker(function (val) {
console.log('选择的日期:', val); // "2026-09-29"
});
openWheelTimePicker(function (val) {
console.log('选择的时间:', val); // "14:30:00"
});
// 不传回调时,默认用 toast 显示结果
data-* 自动绑定免写 JS
大部分交互通过 data-* 属性自动绑定,页面无需写一行 JavaScript。下表为完整清单。
| 属性 | 可用值 | 作用 |
|---|---|---|
| data-toast | success / error / warning / info | 点击弹出对应类型的 Toast |
| data-loading | — | 点击显示 Loading 1.5 秒 |
| data-modal | alert / confirm / prompt / open / large / form / iframe | 点击打开对应弹窗 |
| data-open | drawer / drawerRight / topbar / actionsheet | 点击打开对应面板 |
| data-drawer-close | — | 关闭左侧抽屉 |
| data-drawer-right-close | — | 关闭右侧抽屉 |
| data-topbar-close | — | 关闭顶部快捷菜单 |
| data-actionsheet-cancel | — | 关闭底部操作面板 |
| data-submenu-toggle | — | 抽屉内二级菜单展开 / 收起 |
| data-submenu | 自定义 key | 侧边栏菜单项,触发二级弹出菜单 |
| data-dropdown | — | 标记下拉容器(可不写,按 .dropdown 识别) |
| data-value | 字符串 | 下拉选项的值,选中后写入 .dropdown-value |
| data-tab | 目标面板 id | 选项卡按钮 / 底部导航项 |
| data-page | 页面 key | 底部导航对应的页面 |
| data-target | 输入框 id | 数字加减按钮的目标输入框 |
| data-step | 数字 | 数字加减按钮的步长(可为小数) |
| data-decimals | 数字 | 输入框保留的小数位 |
| data-wheel | date / time | 点击打开滚轮选择器 |
| data-tip / data-placement | 文本 / top | bottom | left | right | Tip 气泡内容与方向(默认 top) |
| data-media-more | — | 图文卡片「全部」入口 |
| data-pg-prev / data-pg-next | — | 简单分页上一页 / 下一页 |
| data-close | — | 弹窗内关闭按钮 |
openModal 的 onOpen 回调。交互规范统一行为约定
| 场景 | 约定行为 |
|---|---|
| 弹窗确认 | Enter 触发最后一个按钮 / onConfirm;输入框内回车同样确认,多行文本 textarea 除外 |
| 弹窗取消 | Esc 触发 onCancel 并关闭;点遮罩同样关闭 |
| 逐层关闭 | Esc 按「下拉 → 弹窗 → 抽屉 → 顶部菜单 → 底部面板」顺序逐层关闭 |
| 抽屉 / 面板 | 点击遮罩关闭、Esc 关闭;打开一个面板时自动关闭其他面板 |
| 下拉菜单 | 点击外部区域关闭;同一时间只允许一个下拉展开 |
| 数字加减 | 到达 min / max 时对应按钮自动禁用 |
| 焦点管理 | 弹窗打开后自动聚焦主按钮;Prompt 自动聚焦输入框 |
| 快捷键提示 | 弹窗底部固定显示 Enter / Esc 提示标签 |
开发规范团队约定
目录与引入
- 组件库文件固定为
laimi.css与laimi.js,放置于laimi_ui/目录。 - 页面只写结构,不重复实现组件样式;页面独有样式单独写在页面
<style>中,统一用lmcard-前缀,避免污染全局。 - 页面逻辑与全局交互分离:全局交互(折叠、下拉、弹层、快捷键)由
laimi.js统一负责,页面脚本只处理业务逻辑。 - 修改源码前先备份,备份文件放在 Web 目录之外,避免源码通过 HTTP 泄露。
命名约定
- 组件类名使用语义化短横线命名:
.card-list-item、.media-item-badge。 - 页面专属样式使用
lmcard-前缀,例如.lmcard-stat。 - 状态类统一用
.active(选中/高亮)、.open(展开)、.disabled(禁用)。 - 图标统一
<i class="iconfont icon-xxx"></i>,不使用 emoji。
布局约定
- 顶部栏
position: fixed+z-index: 99990,左侧栏z-index: 99980,保证二级弹层不被遮挡。 - 绝对定位元素的参照容器需显式设置
position: relative。 - 顶部栏高度、侧栏宽度统一使用 CSS 变量(
--topbar-height/--sidebar-width),不要写死数值。 - 主题变量必须在
<head>同步应用,脚本不要加defer/async,避免颜色闪烁。
表格与列表
- 表格正文默认 14px,颜色使用主题变量。
- 操作列优先使用文字按钮,不使用小图标;员工筛选等选择元素用标签式小按钮,独立成行放在表格上方。
- 下拉分组遵循 layui 风格:分组标题为 0.75rem 浅灰小字(左内边距 10px),选项缩进一级(左内边距 22px),不使用背景块或竖色条。
常见问题FAQ
组件没反应 / 样式没生效?
按顺序检查:① laimi.css 是否引入且路径正确;② laimi.js 是否在 </body> 前引入;③ 容器 id 是否与文档一致(抽屉、顶部菜单、底部面板对 id 有硬性要求);④ 浏览器控制台是否有报错。
动态插入的 HTML 里按钮点了没反应?
data-* 绑定只在页面加载时执行一次。动态内容需在插入后手动绑定事件,或放在 openModal 的 onOpen 回调里绑定。
想换品牌色怎么办?
在 laimi.css 之后写一段 :root { --color-primary: #1677ff; } 即可,浅色变体会由 color-mix() 自动派生。
为什么页面没有抽屉/顶部菜单也不报错?
组件库对所有面板交互都做了元素存在判断,页面未放置对应容器时静默跳过,不影响其他组件。Toast、Loading、弹窗容器则由库自动创建。
图标显示成方框?
说明 iconfont 未加载或图标名不存在。检查 <link> 地址是否可访问,并把 ?t= 后的数字 +1 强制刷新缓存。图标名以 图标解析.html 为准。
移动端和 PC 端表现不一致?
日期 / 时间选择器会根据屏幕宽度与触屏能力自动切换:宽屏用下拉面板,窄屏 / 触屏用滚轮选择器。左侧栏在 < 768px 时自动折叠为图标模式。