/* AUTO-GENERATED by build-site.ps1 - do not edit by hand */ window.AA_DATA = {"meta":{"generated":"2026-09-10 19:23","lib":"aurora-admin","total":79,"core":10,"extension":69,"brand":"#2F54EB","version":"1.2.0","note":"generated by build-site.ps1 from components/index.json + frameworks/ + specs + contracts + tokens"},"categories":[{"key":"general","count":4},{"key":"navigation","count":10},{"key":"input","count":24},{"key":"display","count":20},{"key":"feedback","count":15},{"key":"system","count":6}],"tokens":[{"comment":"品牌主色:主按钮、选中态、链接","name":"au-color-brand","value":"#2F54EB"},{"comment":"品牌主色悬停","name":"au-color-brand-hover","value":"#1D39C4"},{"comment":"品牌主色点击","name":"au-color-brand-active","value":"#10239E"},{"comment":"品牌浅色背景:选中背景、标签背景、表格悬停","name":"au-color-brand-bg","value":"#F0F5FF"},{"comment":"辅助色","name":"au-color-secondary","value":"#13C2C2"},{"comment":"页面背景","name":"au-color-page-bg","value":"#F5F7FA"},{"comment":"卡片背景","name":"au-color-card-bg","value":"#FFFFFF"},{"comment":"表头背景","name":"au-color-table-header-bg","value":"#FAFAFA"},{"comment":"禁用背景(按钮禁用态)","name":"au-color-disabled-bg","value":"#F5F5F5"},{"comment":"标签绿底","name":"au-color-tag-green-bg","value":"#F6FFED"},{"comment":"Tooltip 深色背景","name":"au-color-tooltip-bg","value":"#1F2329"},{"comment":"标题文字","name":"au-color-text-title","value":"#1F2329"},{"comment":"正文文字","name":"au-color-text-body","value":"#262626"},{"comment":"次要文字","name":"au-color-text-secondary","value":"#8C8C8C"},{"comment":"占位文字","name":"au-color-text-placeholder","value":"#BFBFBF"},{"comment":"反色文字(品牌色背景上的白字)","name":"au-color-text-inverse","value":"#FFFFFF"},{"comment":"分割线/边框","name":"au-color-border","value":"#E8ECF1"},{"comment":"成功色","name":"au-color-success","value":"#52C41A"},{"comment":"警告色","name":"au-color-warning","value":"#FAAD14"},{"comment":"错误色","name":"au-color-error","value":"#F5222D"},{"comment":"信息色","name":"au-color-info","value":"#1890FF"},{"comment":"弹窗遮罩","name":"au-color-mask","value":"rgba(0, 0, 0, 0.45)"},{"comment":"图片遮罩","name":"au-color-mask-strong","value":"rgba(0, 0, 0, 0.85)"},{"comment":"输入框聚焦外发光","name":"au-color-focus-ring","value":"rgba(47, 84, 235, 0.2)"},{"comment":"","name":"au-font-family","value":"\"PingFang SC\", \"Source Han Sans SC\", \"Source Han Sans\", \"Microsoft YaHei\", \"Inter\", \"DIN\", -apple-system, \"Segoe UI\", sans-serif"},{"comment":"","name":"au-font-family-cn","value":"\"PingFang SC\", \"Source Han Sans SC\", \"Source Han Sans\", \"Microsoft YaHei\", sans-serif"},{"comment":"","name":"au-font-family-num","value":"\"Inter\", \"DIN\", \"Segoe UI\", sans-serif"},{"comment":"","name":"au-type-display-size","value":"24px"},{"comment":"","name":"au-type-display-height","value":"32px"},{"comment":"Semibold:页面大标题","name":"au-type-display-weight","value":"600"},{"comment":"","name":"au-type-h1-size","value":"20px"},{"comment":"","name":"au-type-h1-height","value":"28px"},{"comment":"Semibold:卡片标题、弹窗标题","name":"au-type-h1-weight","value":"600"},{"comment":"","name":"au-type-h2-size","value":"16px"},{"comment":"","name":"au-type-h2-height","value":"24px"},{"comment":"Medium:区块标题","name":"au-type-h2-weight","value":"500"},{"comment":"","name":"au-type-body-size","value":"14px"},{"comment":"","name":"au-type-body-height","value":"22px"},{"comment":"Regular:正文、表格、表单","name":"au-type-body-weight","value":"400"},{"comment":"","name":"au-type-caption-size","value":"12px"},{"comment":"","name":"au-type-caption-height","value":"20px"},{"comment":"Regular:辅助说明、时间戳","name":"au-type-caption-weight","value":"400"},{"comment":"","name":"au-space-4","value":"4px"},{"comment":"","name":"au-space-8","value":"8px"},{"comment":"","name":"au-space-12","value":"12px"},{"comment":"","name":"au-space-16","value":"16px"},{"comment":"","name":"au-space-20","value":"20px"},{"comment":"","name":"au-space-24","value":"24px"},{"comment":"","name":"au-space-32","value":"32px"},{"comment":"","name":"au-space-40","value":"40px"},{"comment":"","name":"au-space-48","value":"48px"},{"comment":"","name":"au-space-64","value":"64px"},{"comment":"标签、徽章、复选框","name":"au-radius-small","value":"2px"},{"comment":"按钮、输入框、选择器","name":"au-radius-base","value":"4px"},{"comment":"表格、卡片内部元素、Popover、日期面板","name":"au-radius-medium","value":"6px"},{"comment":"卡片、弹窗、抽屉","name":"au-radius-large","value":"8px"},{"comment":"卡片默认","name":"au-shadow-low","value":"0 1px 4px rgba(0, 0, 0, 0.06)"},{"comment":"卡片悬停、下拉菜单、Popover","name":"au-shadow-medium","value":"0 4px 12px rgba(0, 0, 0, 0.08)"},{"comment":"弹窗、抽屉","name":"au-shadow-high","value":"0 8px 24px rgba(0, 0, 0, 0.12)"},{"comment":"","name":"au-duration-fast","value":"0.2s"},{"comment":"","name":"au-duration-base","value":"0.3s"},{"comment":"","name":"au-duration-slow","value":"0.4s"},{"comment":"","name":"au-ease-standard","value":"ease"},{"comment":"","name":"au-ease-out","value":"ease-out"},{"comment":"","name":"au-ease-in-out","value":"ease-in-out"},{"comment":"","name":"au-icon-size-16","value":"16px"},{"comment":"","name":"au-icon-size-20","value":"20px"},{"comment":"","name":"au-icon-size-24","value":"24px"},{"comment":"","name":"au-control-height-small","value":"24px"},{"comment":"","name":"au-control-height-default","value":"32px"},{"comment":"","name":"au-control-height-large","value":"40px"}],"changelog":[{"date":"2026-09-10","sections":[{"title":"Added — 设计契约与工程化","items":["B1 契约第一批:15 个输入类组件补齐契约 JSON(autocomplete / cascader / transfer / rate / slider / segmented / radiocard / checkboxcard / switchgroup / numberrangeinput / codeinput / passwordinput / phoneinput / colorpicker / mention),契约化覆盖 6 → 21/79","C4 静态薄壳:`site/components/.html` × 79(每组件独立可爬 URL:title + meta description + canonical + 刷新跳转),`sitemap.xml`(80 URL,部署后回填 `$SiteUrlBase`)","C3 headless 回归:79 个测试页全量执行 812 条断言(当前通过率 75.1%,失败项即改进 backlog),产出 `tests/report.json`;CI runner `tools/run-regression.mjs`(Playwright,含 JUnit XML 输出)","首页 Hero 显示「测试通过率」(读取 tests/report.json,无报告时静默隐藏)","B3 Playground:组件详情页「▶ 在线试玩」——textarea 编辑 H5 源码,iframe srcdoc 防抖实时重渲染,支持复位 / 退出,不影响源文件","C1 交叉引用:组件详情页新增「相关组件」(相似 / 搭配维度映射 + 同分类兜底)","场景示范页:`site/scenario/user-management.html`(筛选表单 + 数据表格 + 分页 + 新建弹窗 + 消息反馈,视觉全部来自令牌与 components.css),首页 Hero 加入口","部署物料:根 `README.md`、Pages 入口 `index.html` 重定向、`.github/workflows/deploy-pages.yml`、`.gitignore`"]},{"title":"Fixed","items":["测试页断言协议落地:`tests/_runtime.js` 运行期自动标注快照节点 `data-assert`(多级结构启发式:.row → .demo → .demo-block → 首层;此前 79 页实际无断言节点,PLAN v1.1 声称与实现不符)","`site/data.js` / `site/data.json` 去除 UTF-8 BOM(PS5.1 `Set-Content` 默认带 BOM,严格 JSON 解析器如 node require 直接解析失败)","断言可见性判定放宽(`getClientRects` 兜底),减少隐藏变体误报"]}],"version":"1.2.0"},{"date":"2026-09-08","sections":[{"title":"Added — 竞品对齐改造(第一批,依据《竞品网站检查报告》与《现状问题分析与改造清单》)","items":["For Agents 页(`#/agents`):AI / Agent 消费入口——机器可读资源清单、data.json 数据形状、消费示例、使用规则","`site/llms.txt`:LLM 入口清单,指向 data.json、契约 JSON、令牌文件与测试页","站内更新日志页(`#/changelog`):`build-site.ps1` 解析本文件注入 `data.js`,站点展示与仓库同源","主题预设库:主题定制器内置 6 套品牌色预设(科技蓝 / 青碧 / 极夜紫 / 暖橙 / 绯红 / 松石绿),一键切换后仍可微调","组件详情页新增「API 源码提炼」徽章,区分契约化组件(6 核心)与源码提炼组件(69 扩展)"]},{"title":"Fixed — 文档与实现不符","items":["搜索增强补票:Ctrl+K 弹窗补齐分类 chip 过滤、命中关键词 `` 高亮、子序列模糊回退(PLAN v1.1 曾声称交付但代码缺失);搜索结果行补写 `data-slug` 修复搜索日志点击目标为空的问题"]},{"title":"Changed","items":["暗色模式令牌映射补全:`html.aa-dark` 新增品牌色族 / 语义色 / focus-ring / 阴影映射,并修复「主题内联样式压过暗色类」的层级问题——暗色下品牌色自动向白色混合提亮一档,`aa-mode` 切换后即时重算","`build-site.ps1`:`meta.version` 不再硬编码,改为从 CHANGELOG.md 最新版本号自动同步"]}],"version":"1.1.1"},{"date":"2026-09-07","sections":[{"title":"Added — 文档体系补齐","items":["`PLAN.md`:完整组件文档体系规划与路线图,含 5 大模块、3 版本路线、验收标准","`CHANGELOG.md`:本文档","`CONTRIBUTING.md`:贡献指南(如何新增组件 / 修契约 / 加测试)","`TESTING.md`:测试矩阵、断言协议、回归流程","`tests/.html` × 79:每个组件独立测试页(HTML+CSS+原生 JS 零依赖)","`tests/index.html`:测试总览页(按类目聚合 + 通过率 + 失败项)","`run-tests.ps1`:批量构建测试目录脚本"]},{"title":"Added — 日志系统","items":["`site/logger.js`:构建 / 渲染 / 搜索 / 测试 四类事件,落 `localStorage[aa-render-log]` / `localStorage[aa-search-log]` / `localStorage[aa-test-log]`","控制台 `aaLogger.export()` 导出 JSON","测试页内置 `data-assert` + `data-status`,便于 Playwright / Selenium 回归"]},{"title":"Added — 搜索增强","items":["Ctrl+K 弹窗内新增分类 chips 过滤(general / navigation / input / display / feedback / system)","命中关键词 `` 高亮","模糊容错:去空格 + 子串匹配 + 大小写不敏感","搜索日志:query / 命中数 / 首个命中 slug / 耗时"]},{"title":"Added — 文档骨架","items":["顶栏工具新增「测试」「日志」入口(HAMBURGER 之后)","组件详情页新增「测试页」链接","首页 hero stats 加入「测试页 79」一项"]},{"title":"Changed","items":["`site/app.js` 改为引用 `site/logger.js`,原搜索行为保留","扩展组件契约新增「doNotInvent / unknowns」字段自动从 spec 文本提取"]},{"title":"Fixed","items":["主题定制器焦点环 rgba 计算 bug:hex 转 rgba 不再被截断为 6 位","类别切换时滚动条保持原位(避免视觉跳动)"]}],"version":"1.1.0"},{"date":"2026-09-06","sections":[{"title":"Added","items":["79 个组件实现(H5 / React / Vue 2 / Vue 3 四端)","设计令牌:`colors_and_type.css` / `css.json` / `components.css`","6 个核心组件契约 JSON","`components/index.json` 全量组件索引(spec 批次映射)","`site/` 文档站:首页 / 组件总览 / 快速开始 / 设计规范","Ctrl+K 全局搜索","主题定制器(4 键覆盖)","`build-site.ps1` 构建脚本"]},{"title":"Notes","items":["数据生成产物:`site/data.js`(自动生成,请勿手动编辑)","`Added`:新功能","`Changed`:现有功能变更","`Deprecated`:即将移除","`Removed`:已移除","`Fixed`:缺陷修复","`Security`:安全相关","主版本号:不兼容的 API 变更","次版本号:向后兼容的功能新增","修订号:向后兼容的问题修复"]}],"version":"1.0.0"}],"components":[{"slug":"button","name":"按钮 Button","tier":"core","confidence":"high","specBatch":1,"specFile":"组件1.txt","contractRef":"components/button.json","category":"general","specLines":["- 类型:主按钮、次按钮、文字按钮、链接按钮、危险按钮","- 尺寸:Large(40px高)、Default(32px高)、Small(24px高)","- 圆角:4px","- 主按钮:背景#2F54EB,文字白色,悬停#1D39C4","- 次按钮:白底边框#E8ECF1,文字#262626,悬停边框变蓝","- 文字按钮:无背景,文字#2F54EB,悬停浅蓝背景","- 危险按钮:背景#F5222D","- 禁用态:背景#F5F5F5,文字#BFBFBF","- 最小宽度64px"],"specSummary":"- 类型:主按钮、次按钮、文字按钮、链接按钮、危险按钮","contract":{"variants":["主按钮","次按钮","文字按钮","链接按钮","危险按钮","大尺寸主按钮","小尺寸主按钮","禁用态"],"dims":[{"name":"type","values":["primary","default","text","link","danger"]},{"name":"size","values":["large","default","small"]},{"name":"state","values":["default","hover","active","focus","disabled","loading"]}],"unknowns":["加载态具体样式细节"],"anatomy":{"container":"按钮容器,圆角4px,最小宽度64px","label":"按钮文字,字号14px","icon":"可选图标,与文字间距4px"},"doNot":["图标按钮尺寸规范","按钮组规范"],"usage":["主按钮:背景 var(--color-brand),文字白色,悬停 var(--color-brand-hover)","次按钮:白底边框 var(--color-border),文字 var(--color-text-body),悬停边框变蓝","文字按钮:无背景,文字 var(--color-brand),悬停浅蓝背景 var(--color-brand-bg)","危险按钮:背景 var(--color-error)","禁用态:背景 var(--color-bg-disabled),文字 var(--color-text-placeholder)"],"structure":{"height":"@{large=40px; default=32px; small=24px}","radius":"var(--radius-base)","padding":"左右12px(默认尺寸)"}},"files":{"html":"../frameworks/button.html","css":"../frameworks/Button.css","jsx":"../frameworks/Button.jsx","vue2":"../frameworks/Button.vue2.vue","vue3":"../frameworks/Button.vue3.vue"},"sizes":{"html":5668,"css":1831,"jsx":1586,"vue2":3232,"vue3":3122},"sources":{"html":"\n\n\n\n\nAurora Admin · Button (H5)\n\n\n\n\n\n

Aurora Admin · Button 组件(纯 H5)

\n

对齐 button.json 契约:5 类型 × 3 尺寸,含禁用 / 加载 / 图标场景

\n\n
\n

1. 类型(默认尺寸)

\n
\n \n \n \n \n \n
\n
\n\n
\n

2. 尺寸(主按钮)

\n
\n \n \n \n
\n
\n\n
\n

3. 禁用态

\n
\n \n \n \n
\n
\n\n
\n

4. 加载态

\n
\n \n \n
\n
\n\n
\n

5. 带图标(内联 SVG 占位)

\n
\n \n \n \n
\n
\n\n\n","css":"/* Aurora Admin Button 样式 — 对齐 components.css 与 button.json 契约 */\n.btn {\n display: inline-flex; align-items: center; justify-content: center;\n gap: 4px; border: 1px solid transparent; cursor: pointer;\n white-space: nowrap; font-size: 14px; line-height: 22px;\n border-radius: 4px; min-width: 64px; padding: 0 12px;\n box-sizing: border-box; transition: background .15s, border-color .15s;\n font-family: \"PingFang SC\", \"Source Han Sans SC\", \"Microsoft YaHei\", sans-serif;\n}\n.btn:focus-visible { outline: 2px solid rgba(47,84,235,0.2); outline-offset: 2px; }\n\n.btn-primary { background: #2F54EB; color: #fff; }\n.btn-primary:hover { background: #1D39C4; }\n.btn-primary:active { background: #10239E; }\n\n.btn-default { background: #fff; border-color: #E8ECF1; color: #262626; }\n.btn-default:hover { border-color: #2F54EB; }\n\n.btn-text { background: transparent; color: #2F54EB; }\n.btn-text:hover { background: #F0F5FF; }\n\n.btn-link { background: transparent; border: none; color: #2F54EB; min-width: auto; padding: 0; }\n.btn-link:hover { background: #F0F5FF; }\n\n.btn-danger { background: #F5222D; color: #fff; }\n.btn-danger:hover { filter: brightness(.92); }\n\n.btn-lg { height: 40px; }\n.btn-md { height: 32px; }\n.btn-sm { height: 24px; }\n\n.btn-disabled, .btn:disabled {\n background: #F5F5F5; border-color: transparent; color: #BFBFBF; cursor: not-allowed;\n}\n.btn-disabled:hover, .btn:disabled:hover { background: #F5F5F5; filter: none; }\n\n.btn .btn-icon { flex-shrink: 0; }\n\n.btn.is-loading { cursor: progress; position: relative; }\n.btn.is-loading .btn-label { opacity: .65; }\n.btn .spinner {\n width: 14px; height: 14px; border: 2px solid currentColor; border-top-color: transparent;\n border-radius: 50%; animation: aa-btn-spin .6s linear infinite;\n}\n@keyframes aa-btn-spin { to { transform: rotate(360deg); } }\n","jsx":"import React from 'react';\nimport './Button.css';\n\n/**\n * Aurora Admin Button(React)\n * 对齐 button.json 契约:\n * - type: primary | default | text | link | danger\n * - size: large | default | small\n * - disabled / loading / icon / children\n */\nconst sizeMap = { large: 'btn-lg', default: 'btn-md', small: 'btn-sm' };\n\nexport default function Button({\n type = 'default',\n size = 'default',\n disabled = false,\n loading = false,\n icon = '',\n text = '',\n className = '',\n children,\n onClick,\n ...rest\n}) {\n const isDisabled = disabled || loading;\n\n const classes = [\n 'btn',\n `btn-${type}`,\n sizeMap[size],\n isDisabled && !loading ? 'btn-disabled' : '',\n loading ? 'is-loading' : '',\n className\n ].filter(Boolean).join(' ');\n\n const handleClick = (e) => {\n if (isDisabled) return;\n onClick && onClick(e);\n };\n\n return (\n \n );\n}\n\n/*\n用法示例:\nimport Button from './Button';\n\n\n\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"input","name":"输入框 Input","tier":"core","confidence":"high","specBatch":1,"specFile":"组件1.txt","contractRef":"components/input.json","category":"input","specLines":["- 尺寸:Default(32px高)、Large(40px高)、Small(24px高)","- 圆角4px,边框1px solid #E8ECF1,内边距左右12px","- 聚焦:边框#2F54EB,外发光0 0 0 2px rgba(47,84,235,0.2)","- 错误:边框#F5222D,下方红色12px错误提示","- 支持前后缀、清除按钮、数字输入、密码切换"],"specSummary":"- 尺寸:Default(32px高)、Large(40px高)、Small(24px高)","contract":{"variants":["默认输入框","聚焦态","错误态","大尺寸","小尺寸","前后缀","密码切换"],"dims":[{"name":"size","values":["default","large","small"]},{"name":"state","values":["default","focus","error","disabled"]},{"name":"feature","values":["prefix","suffix","clearable","number","password"]}],"unknowns":["textarea 多行输入规范"],"anatomy":{"container":"输入框容器,圆角4px,边框1px solid var(--color-border),内边距左右12px","input":"文本输入区域,字号14px","prefix":"可选前缀元素","suffix":"可选后缀元素(清除按钮、密码切换等)","errorText":"错误提示文字,字号12px,位于输入框下方4px"},"doNot":["输入框宽度规范"],"usage":["聚焦:边框 var(--color-brand),外发光 0 0 0 2px rgba(47,84,235,0.2)","错误:边框 var(--color-error),下方红色12px错误提示","支持前后缀、清除按钮、数字输入、密码切换"],"structure":{"height":"@{default=32px; large=40px; small=24px}","radius":"var(--radius-base)","border":"1px solid var(--color-border)"}},"files":{"html":"../frameworks/input.html","css":"../frameworks/Input.css","jsx":"../frameworks/Input.jsx","vue2":"../frameworks/Input.vue2.vue","vue3":"../frameworks/Input.vue3.vue"},"sizes":{"html":5708,"css":1582,"jsx":3920,"vue2":4594,"vue3":4619},"sources":{"html":"\n\n\n\n\nAurora Admin · Input (H5)\n\n\n\n\n

Aurora Admin · Input 组件(纯 H5)

\n

对齐 input.json 契约:3 尺寸、聚焦/错误/禁用态,前后缀 / 清除 / 密码切换

\n\n
\n

1. 默认输入框

\n
\n
\n
\n
\n\n
\n

2. 三种尺寸

\n
\n
\n
\n
\n
\n
\n\n
\n

3. 错误态(含错误提示)

\n
\n
\n
\n 联系电话格式不正确,请检查后重试\n
\n
\n
\n\n
\n

4. 前后缀

\n
\n
\n
\n \n \n
\n
\n
\n
\n \n .com\n
\n
\n
\n
\n\n
\n

5. 清除按钮(clearable)

\n
\n
\n
\n \n \n
\n
\n
\n
\n\n
\n

6. 密码切换

\n
\n
\n
\n \n \n
\n
\n
\n
\n\n
\n

7. 禁用态

\n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin Input 样式 — 对齐 components.css 与 input.json 契约 */\n.au-field { display: inline-flex; flex-direction: column; gap: 4px; }\n\n.au-input {\n display: inline-flex; align-items: center; gap: 8px; height: 32px;\n padding: 0 12px; background: #fff; border: 1px solid #E8ECF1;\n border-radius: 4px; transition: border-color .15s, box-shadow .15s; box-sizing: border-box;\n}\n.au-input input {\n border: none; outline: none; background: transparent; font-size: 14px;\n color: #262626; min-width: 160px; flex: 1;\n font-family: \"PingFang SC\", \"Source Han Sans SC\", \"Microsoft YaHei\", sans-serif;\n}\n.au-input input::placeholder { color: #BFBFBF; }\n.au-input:focus-within { border-color: #2F54EB; box-shadow: 0 0 0 2px rgba(47,84,235,0.2); }\n.au-input.is-error { border-color: #F5222D; }\n.au-input.is-error:focus-within { border-color: #F5222D; box-shadow: 0 0 0 2px rgba(47,84,235,0.2); }\n.au-input.is-disabled, .au-input:disabled { background: #F5F5F5; border-color: transparent; }\n.au-input.is-disabled input, .au-input:disabled input { color: #BFBFBF; cursor: not-allowed; }\n.au-input .affix { display: inline-flex; flex-shrink: 0; color: #8C8C8C; align-items: center; }\n.au-input .action {\n display: inline-flex; flex-shrink: 0; padding: 0; border: none; background: none;\n cursor: pointer; color: #8C8C8C; align-items: center;\n}\n.au-input .action:hover { color: #2F54EB; }\n.au-input-lg { height: 40px; }\n.au-input-sm { height: 24px; }\n.au-input-sm input { font-size: 12px; min-width: 120px; }\n.error-text { font-size: 12px; color: #F5222D; line-height: 20px; }\n","jsx":"import React, { useState, useRef } from 'react';\nimport './Input.css';\n\n/**\n * Aurora Admin Input(React)\n * 对齐 input.json 契约:\n * - size: large | default | small\n * - state: default | focus | error | disabled\n * - feature: prefix | suffix | clearable | number | password\n * 受控用法:\n * 非受控用法:\n */\nconst sizeMap = { large: 'au-input-lg', default: 'au-input-md', small: 'au-input-sm' };\n\nconst EyeIcon = () => (\n \n \n \n \n);\nconst EyeOffIcon = () => (\n \n \n \n);\nconst CloseIcon = () => (\n \n \n \n);\n\nexport default function Input({\n value,\n defaultValue = '',\n size = 'default',\n type = 'text',\n placeholder = '',\n disabled = false,\n error = false,\n errorText = '',\n clearable = false,\n className = '',\n prefix,\n suffix,\n onChange,\n onFocus,\n onBlur,\n ...rest\n}) {\n const isControlled = value !== undefined;\n const [inner, setInner] = useState(defaultValue);\n const [showPassword, setShowPassword] = useState(false);\n const inputRef = useRef(null);\n\n const current = isControlled ? value : inner;\n\n const handleInput = (e) => {\n const next = e.target.value;\n if (!isControlled) setInner(next);\n onChange && onChange(next, e);\n };\n\n const handleClear = () => {\n if (!isControlled) setInner('');\n onChange && onChange('');\n requestAnimationFrame(() => inputRef.current && inputRef.current.focus());\n };\n\n const resolvedType = type === 'password' ? (showPassword ? 'text' : 'password') : type;\n\n const classes = [\n 'au-input',\n sizeMap[size],\n error ? 'is-error' : '',\n disabled ? 'is-disabled' : '',\n className\n ].filter(Boolean).join(' ');\n\n return (\n
\n
\n {prefix && {prefix}}\n\n \n\n {clearable && current && !disabled && (\n \n )}\n\n {type === 'password' && (\n setShowPassword((v) => !v)}\n >\n {showPassword ? : }\n \n )}\n\n {suffix && {suffix}}\n
\n\n {error && errorText && {errorText}}\n
\n );\n}\n\n/*\n用法示例:\nimport Input from './Input';\n\n\n\n\n\n\n} placeholder=\"搜索订单编号\" />\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"select","name":"选择器 Select","tier":"core","confidence":"high","specBatch":1,"specFile":"组件1.txt","contractRef":"components/select.json","category":"input","specLines":["- 触发器样式同Input","- 下拉面板:白底、圆角4px、阴影中、最大高度256px","- 选中项背景#F0F5FF,文字#2F54EB,左侧勾选图标","- 支持单选、多选、搜索、分组、级联"],"specSummary":"- 触发器样式同Input","contract":{"variants":["单选","多选","可搜索","分组","下拉展开"],"dims":[{"name":"mode","values":["single","multiple","searchable","grouped","cascader"]},{"name":"state","values":["default","open","disabled"]}],"unknowns":["多选标签样式细节"],"anatomy":{"trigger":"触发器,样式同 Input","panel":"下拉面板:白底、圆角4px、阴影中、最大高度256px","option":"选项项,选中项背景 var(--color-brand-bg),文字 var(--color-brand),左侧勾选图标","searchInput":"可搜索模式下的搜索输入框"},"doNot":["级联面板具体布局"],"usage":["触发器样式同 Input","下拉面板:白底、圆角4px、阴影中、最大高度256px","选中项背景 var(--color-brand-bg),文字 var(--color-brand),左侧勾选图标","支持单选、多选、搜索、分组、级联"],"structure":{"panelRadius":"var(--radius-base)","panelMaxHeight":"256px","optionHeight":"32px"}},"files":{"html":"../frameworks/select.html","css":"../frameworks/Select.css","jsx":"../frameworks/Select.jsx","vue2":"../frameworks/Select.vue2.vue","vue3":"../frameworks/Select.vue3.vue"},"sizes":{"html":9562,"css":2548,"jsx":5803,"vue2":7153,"vue3":7205},"sources":{"html":"\n\n\n\n\nAurora Admin · Select (H5)\n\n\n\n\n
\n

Aurora Admin · Select 组件(纯 H5)

\n

对齐 select.json 契约:触发器同 Input,面板圆角4px / 阴影中 / 最大高度256px,选中项品牌色 + 勾选图标

\n\n
\n

1. 单选

\n
\n
\n
请选择城市▾
\n \n
\n
\n
\n\n
\n

2. 多选

\n
\n
\n
请选择角色▾
\n \n
\n
\n
\n\n
\n

3. 可搜索

\n
\n
\n
搜索并选择用户▾
\n \n
\n
\n
\n\n
\n

4. 分组

\n
\n
\n
请选择部门▾
\n \n
\n
\n
\n
\n\n\n\n\n","css":"/* Aurora Admin Select 样式 — 对齐 components.css 与 select.json 契约(触发器同 Input) */\n.aa-select { position: relative; display: inline-block; min-width: 220px; }\n.aa-select-trigger {\n display: flex; align-items: center; gap: 8px; min-height: 32px; height: auto;\n padding: 2px 28px 2px 12px; background: #fff; border: 1px solid #E8ECF1;\n border-radius: 4px; box-sizing: border-box; cursor: pointer; transition: border-color .15s, box-shadow .15s;\n font-size: 14px; color: #262626; flex-wrap: wrap; position: relative;\n}\n.aa-select-trigger:hover { border-color: #2F54EB; }\n.aa-select.is-open .aa-select-trigger { border-color: #2F54EB; box-shadow: 0 0 0 2px rgba(47,84,235,0.2); }\n.aa-select.is-disabled .aa-select-trigger { background: #F5F5F5; border-color: transparent; color: #BFBFBF; cursor: not-allowed; }\n.aa-select-trigger .placeholder { color: #BFBFBF; }\n.aa-select-trigger .caret { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); transition: transform .15s; color: #8C8C8C; }\n.aa-select.is-open .caret { transform: translateY(-50%) rotate(180deg); }\n.aa-select-trigger .tag {\n display: inline-flex; align-items: center; gap: 4px; background: #F0F5FF; color: #2F54EB;\n border-radius: 2px; padding: 1px 6px; font-size: 12px; line-height: 20px;\n}\n.aa-select-trigger .tag .x { cursor: pointer; }\n.aa-select-panel {\n position: absolute; z-index: 10; top: calc(100% + 4px); left: 0; right: 0;\n background: #fff; border-radius: 4px; box-shadow: 0 4px 16px rgba(0,0,0,0.12);\n max-height: 256px; overflow-y: auto; padding: 4px 0;\n}\n.aa-select-search { padding: 6px 8px; border-bottom: 1px solid #F0F0F0; }\n.aa-select-search input { width: 100%; box-sizing: border-box; border: 1px solid #E8ECF1; border-radius: 4px; padding: 4px 8px; font-size: 13px; outline: none; }\n.aa-select-search input:focus { border-color: #2F54EB; }\n.aa-select-group-label { padding: 6px 12px; font-size: 12px; color: #8C8C8C; }\n.aa-select-option {\n display: flex; align-items: center; gap: 8px; padding: 0 12px; height: 32px; cursor: pointer;\n font-size: 14px; color: #262626; box-sizing: border-box;\n}\n.aa-select-option:hover { background: #F5F7FA; }\n.aa-select-option.is-selected { background: #F0F5FF; color: #2F54EB; }\n.aa-select-option .check { visibility: hidden; margin-left: auto; }\n.aa-select-option.is-selected .check { visibility: visible; }\n.aa-select-option.is-disabled { color: #BFBFBF; cursor: not-allowed; }\n.aa-select-empty { padding: 16px 12px; text-align: center; color: #BFBFBF; font-size: 13px; }\n","jsx":"import React, { useState, useRef, useEffect, useMemo } from 'react';\nimport './Select.css';\n\n/**\n * Aurora Admin Select(React)\n * 对齐 select.json 契约:\n * - mode: single | multiple(multiple 属性)\n * - feature: searchable | grouped(groups 属性)\n * - state: default | open | disabled\n * 触发器样式同 Input;面板圆角4px / 阴影中 / 最大高度256px;选中项品牌色 + 勾选图标\n * 受控:\n */\nfunction flatten(groups) {\n return (groups || []).flatMap((g) => g.options);\n}\n\nexport default function Select({\n value,\n defaultValue = null,\n options = [],\n groups = null,\n multiple = false,\n searchable = false,\n disabled = false,\n placeholder = '请选择',\n className = '',\n onChange,\n ...rest\n}) {\n const isControlled = value !== undefined;\n const [inner, setInner] = useState(defaultValue);\n const [open, setOpen] = useState(false);\n const [keyword, setKeyword] = useState('');\n const rootRef = useRef(null);\n\n const current = isControlled ? value : inner;\n const selectedArr = Array.isArray(current) ? current : [];\n const selectedVal = Array.isArray(current) ? null : current;\n\n const filteredOptions = useMemo(() => {\n if (!searchable || !keyword) return options;\n const q = keyword.trim().toLowerCase();\n return options.filter((o) => String(o.label).toLowerCase().includes(q));\n }, [options, searchable, keyword]);\n\n const filteredGroups = useMemo(() => {\n if (!groups) return [];\n if (!searchable || !keyword) return groups;\n const q = keyword.trim().toLowerCase();\n return groups\n .map((g) => ({ label: g.label, options: g.options.filter((o) => String(o.label).toLowerCase().includes(q)) }))\n .filter((g) => g.options.length);\n }, [groups, searchable, keyword]);\n\n const isEmpty = groups ? filteredGroups.length === 0 : filteredOptions.length === 0;\n\n const flat = options.length ? options : flatten(groups);\n\n function labelOf(v) {\n const found = flat.find((o) => o.value === v);\n return found ? found.label : v;\n }\n function isSelected(v) {\n return multiple ? selectedArr.includes(v) : selectedVal === v;\n }\n function emit(next) {\n if (!isControlled) setInner(next);\n onChange && onChange(next);\n }\n function choose(o) {\n if (o.disabled) return;\n if (multiple) {\n const next = selectedArr.includes(o.value)\n ? selectedArr.filter((x) => x !== o.value)\n : [...selectedArr, o.value];\n emit(next);\n } else {\n emit(o.value);\n setOpen(false);\n }\n }\n function remove(v) {\n emit(selectedArr.filter((x) => x !== v));\n }\n\n useEffect(() => {\n function onDocClick(e) {\n if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);\n }\n document.addEventListener('click', onDocClick);\n return () => document.removeEventListener('click', onDocClick);\n }, []);\n\n const classes = ['aa-select', open ? 'is-open' : '', disabled ? 'is-disabled' : '', className]\n .filter(Boolean)\n .join(' ');\n\n return (\n
\n
!disabled && setOpen((o) => !o)} tabIndex={0}>\n {multiple ? (\n selectedArr.length ? (\n selectedArr.map((v) => (\n \n {labelOf(v)}\n { e.stopPropagation(); remove(v); }}>✕\n \n ))\n ) : (\n {placeholder}\n )\n ) : selectedVal == null ? (\n {placeholder}\n ) : (\n {labelOf(selectedVal)}\n )}\n ▾\n
\n\n {open && (\n
\n {searchable && (\n
\n setKeyword(e.target.value)}\n onClick={(e) => e.stopPropagation()}\n />\n
\n )}\n\n {groups ? (\n filteredGroups.map((g) => (\n \n
{g.label}
\n {g.options.map((o) => (\n choose(o)}\n >\n {o.label}✓\n
\n ))}\n \n ))\n ) : (\n filteredOptions.map((o) => (\n choose(o)}\n >\n {o.label}✓\n
\n ))\n )}\n\n {isEmpty &&
无匹配选项
}\n \n )}\n \n );\n}\n\n/*\n用法示例:\nimport Select from './Select';\n\nconst cities = [{ value: 'bj', label: '北京' }, { value: 'sh', label: '上海' }];\n\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"table","name":"数据表格 Table","tier":"core","confidence":"high","specBatch":1,"specFile":"组件1.txt","contractRef":"components/table.json","category":"display","specLines":["- 表头:背景#FAFAFA,文字#262626,行高48px","- 行高48px,分割线#E8ECF1,悬停背景#F5F7FA","- 单元格内边距左右16px","- 文本左对齐,数字/金额右对齐","- 选中行背景#F0F5FF","- 支持排序、筛选、自定义列、固定列、展开行、行选择、批量操作","- 操作列右对齐,文字按钮间距8px"],"specSummary":"- 表头:背景#FAFAFA,文字#262626,行高48px","contract":{"variants":["基础表格","可排序","行选择","固定列","展开行","批量操作"],"dims":[{"name":"feature","values":["sortable","filterable","selectable","fixed-column","expandable","batch-actions"]},{"name":"state","values":["default","hover","selected","loading","empty"]}],"unknowns":["表格密度切换具体实现"],"anatomy":{"header":"表头:背景 var(--color-bg-table-header),文字 var(--color-text-body),行高48px","row":"数据行:行高48px,分割线 var(--color-border),悬停背景 var(--color-bg-page)","cell":"单元格:内边距左右16px,文本左对齐,数字/金额右对齐","selectedRow":"选中行背景 var(--color-brand-bg)","actionColumn":"操作列右对齐,文字按钮间距8px"},"doNot":["分页器与表格的组合规范"],"usage":["表头:背景 var(--color-bg-table-header),文字 var(--color-text-body),行高48px","行高48px,分割线 var(--color-border),悬停背景 var(--color-bg-page)","单元格内边距左右16px,文本左对齐,数字/金额右对齐","选中行背景 var(--color-brand-bg)","支持排序、筛选、自定义列、固定列、展开行、行选择、批量操作","操作列右对齐,文字按钮间距8px"],"structure":{"headerHeight":"48px","rowHeight":"48px","cellPadding":"左右16px","divider":"1px solid var(--color-border)"}},"files":{"html":"../frameworks/table.html","css":"../frameworks/Table.css","jsx":"../frameworks/Table.jsx","vue2":"../frameworks/Table.vue2.vue","vue3":"../frameworks/Table.vue3.vue"},"sizes":{"html":6252,"css":1657,"jsx":5568,"vue2":5981,"vue3":5887},"sources":{"html":"\n\n\n\n\nAurora Admin · Table (H5)\n\n\n\n\n
\n

Aurora Admin · Table 组件(纯 H5)

\n

对齐 table.json 契约:表头/行高48px、分割线 #E8ECF1、悬停 #F5F7FA、选中 #F0F5FF、单元格左右16px、数字右对齐、操作列右对齐、加载/空态

\n\n \n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n
客户名称负责人合同金额↕状态操作
\n \n
\n
\n\n\n\n\n","css":"/* Aurora Admin Table 样式 — 对齐 components.css 与 table.json 契约 */\n.aa-table { width: 100%; border: 1px solid #E8ECF1; border-radius: 8px; overflow: hidden; background: #fff; position: relative; }\n.aa-table table { width: 100%; border-collapse: collapse; font-size: 14px; color: #262626; }\n.aa-table thead th {\n background: #FAFAFA; color: #262626; font-weight: 600; height: 48px; text-align: left;\n padding: 0 16px; border-bottom: 1px solid #E8ECF1; white-space: nowrap;\n}\n.aa-table tbody td { height: 48px; padding: 0 16px; border-bottom: 1px solid #E8ECF1; text-align: left; }\n.aa-table tbody tr:last-child td { border-bottom: none; }\n.aa-table tbody tr:hover { background: #F5F7FA; }\n.aa-table tbody tr.is-selected { background: #F0F5FF; }\n.aa-table .align-right { text-align: right; }\n.aa-table .sortable { cursor: pointer; user-select: none; }\n.aa-table .sort-ind { color: #BFBFBF; margin-left: 4px; }\n.aa-table .sort-ind.active { color: #2F54EB; }\n.aa-table .col-action { text-align: right; }\n.aa-table .col-selection { text-align: center; }\n.aa-table .checkbox { width: 16px; height: 16px; cursor: pointer; vertical-align: middle; }\n.aa-table .empty { padding: 48px 16px; text-align: center; color: #BFBFBF; font-size: 14px; }\n.aa-table .loading-mask {\n position: absolute; inset: 0; background: rgba(255,255,255,0.7); display: flex;\n align-items: center; justify-content: center; z-index: 2;\n}\n.aa-table .spinner {\n width: 20px; height: 20px; border: 2px solid #2F54EB; border-top-color: transparent;\n border-radius: 50%; animation: aa-tbl-spin .6s linear infinite;\n}\n@keyframes aa-tbl-spin { to { transform: rotate(360deg); } }\n","jsx":"import React, { useState, useMemo } from 'react';\nimport './Table.css';\n\n/**\n * Aurora Admin Table(React)\n * 对齐 table.json 契约:\n * - feature: sortable / selectable / batch-actions(含全选)\n * - state: hover / selected / loading / empty\n * - 表头/行高48px、分割线 #E8ECF1、悬停 #F5F7FA、选中 #F0F5FF、单元格左右16px、数字右对齐、操作列右对齐\n *\n * columns: [{ key, title, align?:'left'|'right', sortable?, width?, fixed?, type?:'action' }]\n * 受控选择:selectedKeys={keys} onSelectionChange={setKeys}\n * 操作列:renderAction={(row, index) => }\n */\nfunction sortData(data, key, order) {\n if (!key) return data;\n const dir = order === 'asc' ? 1 : -1;\n return [...data].sort((a, b) => {\n const av = a[key], bv = b[key];\n if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * dir;\n return String(av).localeCompare(String(bv), 'zh') * dir;\n });\n}\n\nexport default function Table({\n columns = [],\n data = [],\n rowKey = 'id',\n selectable = false,\n selectedKeys = [],\n loading = false,\n emptyText = '暂无数据',\n onSelectionChange,\n onSortChange,\n renderAction\n}) {\n const [sortKey, setSortKey] = useState(null);\n const [sortOrder, setSortOrder] = useState(null); // asc | desc | null\n\n const sortedData = useMemo(\n () => sortData(data, sortKey, sortOrder),\n [data, sortKey, sortOrder]\n );\n\n const allSelected = data.length > 0 && selectedKeys.length === data.length;\n const someSelected = selectedKeys.length > 0 && selectedKeys.length < data.length;\n\n const isSelected = (id) => selectedKeys.includes(id);\n const emitSelection = (next) => onSelectionChange && onSelectionChange(next);\n\n const toggleRow = (id) => {\n const next = isSelected(id) ? selectedKeys.filter((x) => x !== id) : [...selectedKeys, id];\n emitSelection(next);\n };\n const toggleAll = () => {\n const next = allSelected ? [] : data.map((r) => r[rowKey]);\n emitSelection(next);\n };\n const onSort = (col) => {\n if (!col.sortable) return;\n let k = sortKey, o = sortOrder;\n if (k !== col.key) { k = col.key; o = 'asc'; }\n else { o = o === 'asc' ? 'desc' : o === 'desc' ? null : 'asc'; if (o === null) k = null; }\n setSortKey(k); setSortOrder(o);\n onSortChange && onSortChange({ key: k, order: o });\n };\n\n const colCount = columns.length + (selectable ? 1 : 0);\n\n return (\n
\n \n \n \n {selectable && (\n \n )}\n {columns.map((col) => (\n onSort(col)}\n >\n {col.title}\n {col.sortable && (\n \n {sortKey === col.key ? (sortOrder === 'asc' ? '↑' : sortOrder === 'desc' ? '↓' : '↕') : '↕'}\n \n )}\n \n ))}\n \n \n \n {sortedData.length === 0 ? (\n \n ) : (\n sortedData.map((row, index) => (\n \n {selectable && (\n \n )}\n {columns.map((col) => (\n \n {col.type === 'action' && renderAction ? renderAction(row, index) : row[col.key]}\n \n ))}\n \n ))\n )}\n \n
\n el && (el.indeterminate = someSelected)}\n onChange={toggleAll}\n />\n
{emptyText}
\n toggleRow(row[rowKey])}\n />\n
\n {loading && (\n
\n )}\n
\n );\n}\n\n/*\n用法示例:\nimport Table from './Table';\n\nconst columns = [\n { key: 'name', title: '客户名称' },\n { key: 'amount', title: '合同金额', align: 'right', sortable: true },\n { type: 'action', title: '操作' }\n];\n (<>\n \n \n )}\n/>\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"card","name":"卡片 Card","tier":"core","confidence":"high","specBatch":1,"specFile":"组件1.txt","contractRef":"components/card.json","category":"general","specLines":["- 圆角8px,边框1px #E8ECF1 或低阴影","- 内边距16px/24px","- 结构:标题区、内容区、操作区"],"specSummary":"- 圆角8px,边框1px #E8ECF1 或低阴影","contract":{"variants":["边框卡片","阴影卡片","含标题与操作区"],"dims":[{"name":"style","values":["bordered","shadowed"]},{"name":"padding","values":["16px","24px"]},{"name":"section","values":["title","content","actions"]}],"unknowns":["卡片网格间距规范"],"anatomy":{"container":"卡片容器:圆角8px,边框1px var(--color-border) 或低阴影","titleArea":"标题区:可含标题与右侧操作","contentArea":"内容区","actionArea":"操作区"},"doNot":["卡片 hover 交互规范"],"usage":["圆角8px,边框1px var(--color-border) 或低阴影","内边距16px/24px","结构:标题区、内容区、操作区"],"structure":{"radius":"var(--radius-large)","border":"1px solid var(--color-border)","shadow":"var(--shadow-low)","padding":"16px / 24px"}},"files":{"html":"../frameworks/card.html","css":"../frameworks/Card.css","jsx":"../frameworks/Card.jsx","vue2":"../frameworks/Card.vue2.vue","vue3":"../frameworks/Card.vue3.vue"},"sizes":{"html":2836,"css":695,"jsx":1337,"vue2":1732,"vue3":1714},"sources":{"html":"\n\n\n\n\nAurora Admin · Card (H5)\n\n\n\n\n
\n

Aurora Admin · Card 组件(纯 H5)

\n

对齐 card.json 契约:圆角8px、边框1px / 低阴影、内边距16/24px、结构含标题区/内容区/操作区

\n\n
\n
\n
边框卡片
\n
用于轻量内容承载,1px 边框区分卡片边界。
\n
\n\n
\n
阴影卡片
\n
通过低阴影表现层级浮起,适合突出展示类内容。
\n
\n\n
\n
\n
客户详情
\n
查看全部
\n
\n
含标题区与右侧操作区,内容区展示客户关键信息。
\n
\n \n \n
\n
\n
\n
\n\n\n","css":"/* Aurora Admin Card 样式 — 对齐 components.css 与 card.json 契约 */\n.aa-card {\n background: #fff; border-radius: 8px; box-sizing: border-box;\n min-width: 280px; max-width: 360px;\n}\n.aa-card--bordered { border: 1px solid #E8ECF1; }\n.aa-card--shadowed { box-shadow: 0 1px 4px rgba(0,0,0,0.06); }\n.aa-card__header {\n display: flex; align-items: center; justify-content: space-between; gap: 8px;\n font-weight: 600; color: #1F2329;\n}\n.aa-card__title { font-size: 16px; }\n.aa-card__extra { color: #2F54EB; font-size: 14px; cursor: pointer; }\n.aa-card__body { color: #262626; font-size: 14px; line-height: 22px; }\n.aa-card__actions { display: flex; justify-content: flex-end; gap: 8px; }\n","jsx":"import React from 'react';\nimport './Card.css';\n\n/**\n * Aurora Admin Card(React)\n * 对齐 card.json 契约:\n * - style: bordered | shadowed\n * - padding: 16 | 24\n * - section: title(含右侧 extra)/ content / actions\n */\nexport default function Card({\n title = '',\n bordered = false,\n shadowed = false,\n padding = 16,\n className = '',\n children,\n extra,\n actions\n}) {\n const paddingPx = typeof padding === 'number' ? `${padding}px` : padding;\n const classes = [\n 'aa-card',\n bordered ? 'aa-card--bordered' : '',\n shadowed ? 'aa-card--shadowed' : '',\n className\n ].filter(Boolean).join(' ');\n\n return (\n
\n {(title || extra) && (\n
\n
{title}
\n {extra &&
{extra}
}\n
\n )}\n
{children}
\n {actions &&
{actions}
}\n
\n );\n}\n\n/*\n用法示例:\nimport Card from './Card';\n\n查看全部} shadowed padding={24}>\n 内容区...\n\n}>\n 内容区...\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"modal","name":"对话框 Modal","tier":"core","confidence":"high","specBatch":1,"specFile":"组件1.txt","contractRef":"components/modal.json","category":"feedback","specLines":["- 宽度480px(默认)/680px(大)/320px(小)","- 圆角8px,遮罩rgba(0,0,0,0.45)","- 结构:标题、内容、右下角操作按钮(次按钮+主按钮)"],"specSummary":"- 宽度480px(默认)/680px(大)/320px(小)","contract":{"variants":["默认对话框","大对话框","小对话框"],"dims":[{"name":"size","values":["small","default","large"]},{"name":"state","values":["closed","open"]}],"unknowns":["抽屉 Drawer 与 Modal 的共用规范"],"anatomy":{"mask":"遮罩 rgba(0,0,0,0.45)","container":"对话框容器:圆角8px,阴影高","title":"标题区","content":"内容区","footer":"右下角操作按钮(次按钮+主按钮)"},"doNot":["对话框动画规范"],"usage":["宽度480px(默认)/680px(大)/320px(小)","圆角8px,遮罩 rgba(0,0,0,0.45)","结构:标题、内容、右下角操作按钮(次按钮+主按钮)"],"structure":{"width":"@{small=320px; default=480px; large=680px}","radius":"var(--radius-large)","mask":"rgba(0,0,0,0.45)"}},"files":{"html":"../frameworks/modal.html","css":"../frameworks/Modal.css","jsx":"../frameworks/Modal.jsx","vue2":"../frameworks/Modal.vue2.vue","vue3":"../frameworks/Modal.vue3.vue"},"sizes":{"html":3374,"css":1265,"jsx":1849,"vue2":2706,"vue3":2645},"sources":{"html":"\n\n\n\n\nAurora Admin · Modal (H5)\n\n\n\n\n
\n

Aurora Admin · Modal 组件(纯 H5)

\n

对齐 modal.json 契约:宽度480(默认)/680(大)/320(小),圆角8px,遮罩 rgba(0,0,0,0.45),右下角操作按钮

\n\n \n \n \n
\n\n
\n\n\n\n\n","css":"/* Aurora Admin Modal 样式 — 对齐 components.css 与 modal.json 契约 */\n.aa-mask {\n position: fixed; inset: 0; background: rgba(0,0,0,0.45);\n display: flex; align-items: center; justify-content: center; z-index: 1000;\n}\n.aa-modal {\n background: #fff; border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.12);\n display: flex; flex-direction: column; max-width: 90vw;\n}\n.aa-modal--small { width: 320px; }\n.aa-modal--default { width: 480px; }\n.aa-modal--large { width: 680px; }\n.aa-modal__header {\n display: flex; align-items: center; justify-content: space-between;\n padding: 16px 24px; font-size: 16px; font-weight: 600; color: #1F2329;\n}\n.aa-modal__close { border: none; background: none; cursor: pointer; color: #8C8C8C; font-size: 16px; line-height: 1; }\n.aa-modal__close:hover { color: #262626; }\n.aa-modal__body { padding: 0 24px 24px; color: #262626; font-size: 14px; line-height: 22px; }\n.aa-modal__footer { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 24px 16px; }\n.btn-base { border-radius: 4px; padding: 4px 16px; height: 32px; cursor: pointer; font-size: 14px; }\n.btn-ghost { border: 1px solid #E8ECF1; background: #fff; color: #262626; }\n.btn-primary { border: 1px solid #2F54EB; background: #2F54EB; color: #fff; }\n","jsx":"import React from 'react';\nimport './Modal.css';\n\n/**\n * Aurora Admin Modal(React)\n * 对齐 modal.json 契约:\n * - size: small(320) | default(480) | large(680)\n * - 遮罩 rgba(0,0,0,0.45),圆角8px,右下角操作按钮(次+主)\n * 受控:\n */\nexport default function Modal({\n open = false,\n title = '',\n size = 'default', // small | default | large\n closable = true,\n maskClosable = true,\n hideFooter = false,\n cancelText = '取消',\n okText = '确定',\n onClose,\n onOk,\n footer,\n children\n}) {\n if (!open) return null;\n\n const handleMask = (e) => {\n if (maskClosable && e.target === e.currentTarget) onClose && onClose();\n };\n\n return (\n
\n
e.stopPropagation()}>\n
\n {title}\n {closable && (\n \n )}\n
\n
{children}
\n {!hideFooter && (\n
\n {footer || (\n <>\n \n \n \n )}\n
\n )}\n
\n
\n );\n}\n\n/*\n用法示例:\nimport Modal from './Modal';\nconst [open, setOpen] = useState(false);\n\n setOpen(false)} onOk={handleOk}>\n 对话框内容...\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"tag","name":"标签 Tag","tier":"core","confidence":"high","specBatch":1,"specFile":"组件1.txt","contractRef":null,"category":"general","specLines":["- 圆角2px,高度22px,内边距左右8px","- 颜色:浅色背景+深色文字(如蓝#F0F5FF/#2F54EB,绿#F6FFED/#52C41A等)","- 可关闭标签悬停显示关闭按钮"],"specSummary":"- 圆角2px,高度22px,内边距左右8px","contract":null,"files":{"html":"../frameworks/tag.html","css":"../frameworks/Tag.css","jsx":"../frameworks/Tag.jsx","vue2":"../frameworks/Tag.vue2.vue","vue3":"../frameworks/Tag.vue3.vue"},"sizes":{"html":2505,"css":1305,"jsx":1483,"vue2":2400,"vue3":2395},"sources":{"html":"\n\n\n\n\nAurora Admin · Tag (H5)\n\n\n\n\n\n
\n

Aurora Admin · Tag 组件(纯 H5)

\n

对齐设计令牌:bg #F0F5FF / color #2F54EB / 圆角 2px / padding 1px 8px

\n\n
\n

预设状态

\n
\n 品牌标签\n 成功\n 错误\n 警告\n 默认\n
\n
\n\n
\n

带圆点

\n
\n 在线\n 已发布\n 异常\n
\n
\n\n
\n

可关闭(点击 × 移除)

\n
\n 前端 ×\n 后端 ×\n 设计 ×\n
\n
\n\n
\n

自定义颜色

\n
\n 紫色\n 青色\n
\n
\n
\n\n\n\n\n","css":"/* Aurora Admin Tag 样式 — 对齐设计令牌:bg #F0F5FF / color #2F54EB / 圆角 2px / padding 1px 8px */\n\n.aa-tag {\n display: inline-flex; align-items: center; gap: 4px;\n height: 22px; padding: 1px 8px; box-sizing: border-box;\n font-size: 12px; line-height: 1; border-radius: 2px;\n background: #F0F5FF; color: #2F54EB; white-space: nowrap;\n border: 1px solid transparent;\n}\n\n/* 状态预设 */\n.aa-tag.aa-tag-green { background: #F6FFED; color: #52C41A; }\n.aa-tag.aa-tag-red { background: #FFF1F0; color: #F5222D; }\n.aa-tag.aa-tag-orange { background: #FFF7E6; color: #FA8C16; }\n.aa-tag.aa-tag-gray { background: #F5F5F5; color: #8C8C8C; }\n\n/* 自定义颜色(行内 style 提供 --aa-tag-color / --aa-tag-bg) */\n.aa-tag.aa-tag-custom { background: var(--aa-tag-bg); color: var(--aa-tag-color); }\n\n/* 圆点模式 */\n.aa-tag.aa-tag-dot::before {\n content: ''; width: 6px; height: 6px; border-radius: 50%;\n background: currentColor; display: inline-block;\n}\n\n/* 关闭按钮 */\n.aa-tag-close { display: inline-flex; align-items: center; justify-content: center;\n width: 14px; height: 14px; margin-right: -2px; border-radius: 50%; cursor: pointer;\n font-size: 11px; line-height: 1; color: inherit; opacity: .65; }\n.aa-tag-close:hover { opacity: 1; background: rgba(0,0,0,0.08); }\n","jsx":"import React from 'react';\nimport './Tag.css';\n\n/**\n * Aurora Admin Tag(React)\n * 对齐设计令牌:bg #F0F5FF / color #2F54EB / 圆角 2px / padding 1px 8px\n *\n * props:\n * - color : '' | 'blue' | 'green' | 'red' | 'orange' | 'gray' | 自定义 hex\n * - dot : Boolean(圆点模式)\n * - closable: Boolean\n * - onClose()\n * - children: 标签内容\n */\nexport default function Tag({ color = '', dot = false, closable = false, onClose, children }) {\n const PRESET = ['green', 'red', 'orange', 'gray'];\n const isCustom = !!color && !PRESET.includes(color) && color !== 'blue';\n\n const cls = ['aa-tag'];\n if (dot) cls.push('aa-tag-dot');\n if (isCustom) cls.push('aa-tag-custom');\n else if (color && color !== 'blue') cls.push('aa-tag-' + color);\n\n const style = isCustom ? { '--aa-tag-color': color, '--aa-tag-bg': hexToTint(color) } : undefined;\n\n return (\n \n {children}\n {closable && (\n { e.stopPropagation(); onClose && onClose(); }}>×\n )}\n \n );\n}\n\nfunction hexToTint(hex) {\n const h = hex.replace('#', '');\n const r = parseInt(h.slice(0, 2), 16), g = parseInt(h.slice(2, 4), 16), b = parseInt(h.slice(4, 6), 16);\n return `rgba(${r},${g},${b},0.1)`;\n}\n\n/*\n用法示例:\nimport Tag from './Tag';\n已发布\n紫色\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"tabs","name":"标签页 Tabs","tier":"core","confidence":"high","specBatch":1,"specFile":"组件1.txt","contractRef":null,"category":"navigation","specLines":["- 线型:底部2px品牌色指示条","- 卡片型:选中白底无边框,未选中浅灰背景"],"specSummary":"- 线型:底部2px品牌色指示条","contract":null,"files":{"html":"../frameworks/tabs.html","css":"../frameworks/Tabs.css","jsx":"../frameworks/Tabs.jsx","vue2":"../frameworks/Tabs.vue2.vue","vue3":"../frameworks/Tabs.vue3.vue"},"sizes":{"html":4747,"css":3131,"jsx":2284,"vue2":4236,"vue3":4280},"sources":{"html":"\n\n\n\n\nAurora Admin · Tabs (H5)\n\n\n\n\n\n
\n

Aurora Admin · Tabs 组件(纯 H5)

\n

对齐 PageTabs 规范:线型 / 卡片型 / 胶囊型,支持禁用、关闭、新增

\n\n \n
\n

线型(默认)

\n
\n
\n
基本信息
\n
安全设置
\n
通知
\n
授权
\n
\n
\n
这里是「基本信息」面板内容。
\n \n \n \n
\n
\n
\n\n \n
\n

卡片型(可关闭 / 新增)

\n
\n
\n
订单 ×
\n
退款 ×
\n
+
\n
\n
\n
订单标签页内容。
\n \n
\n
\n
\n\n \n
\n

胶囊型

\n
\n
\n
全部
\n
待办
\n
已完成
\n
\n
\n
全部任务。
\n \n \n
\n
\n
\n
\n\n\n\n\n","css":"/* Aurora Admin Tabs 样式 — 对齐 组件7.txt PageTabs 规范:线型 / 卡片型 / 胶囊型 */\n\n.aa-tabs { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n\n/* 标签导航条 */\n.aa-tabs-nav {\n display: flex; align-items: flex-end; gap: 4px;\n position: relative; padding: 0 4px;\n}\n/* 线型:整条导航底部 1px 边框 */\n.aa-tabs.type-line .aa-tabs-nav { border-bottom: 1px solid #E8ECF1; }\n/* 胶囊型:整条导航为浅灰胶囊容器 */\n.aa-tabs.type-pill .aa-tabs-nav { background: #F5F7FA; border-radius: 16px; padding: 4px; gap: 4px; }\n\n/* 单个标签 */\n.aa-tabs-tab {\n position: relative; box-sizing: border-box;\n display: inline-flex; align-items: center; gap: 6px;\n height: 40px; padding: 0 16px; cursor: pointer;\n font-size: 14px; line-height: 40px; color: #262626;\n border: 1px solid transparent; background: none;\n transition: color .15s, background .15s, border-color .15s;\n user-select: none; white-space: nowrap;\n}\n.aa-tabs-tab:hover:not(.is-active):not(.is-disabled) { color: #2F54EB; }\n\n/* 线型 active:文字品牌色 + 底部 2px 指示条 */\n.aa-tabs.type-line .aa-tabs-tab { border-bottom: 2px solid transparent; margin-bottom: -1px; border-radius: 0; }\n.aa-tabs.type-line .aa-tabs-tab.is-active {\n color: #2F54EB; font-weight: 500; border-bottom-color: #2F54EB;\n}\n\n/* 卡片型 active:边框 + 白底,与内容区连成一体 */\n.aa-tabs.type-card .aa-tabs-nav { border-bottom: 1px solid #E8ECF1; }\n.aa-tabs.type-card .aa-tabs-tab { border: 1px solid #E8ECF1; border-bottom: none; border-radius: 6px 6px 0 0; background: #F5F7FA; }\n.aa-tabs.type-card .aa-tabs-tab.is-active { background: #fff; color: #2F54EB; border-color: #E8ECF1; }\n\n/* 胶囊型 active:品牌色填充白字 */\n.aa-tabs.type-pill .aa-tabs-tab { border: none; border-radius: 14px; height: 32px; line-height: 32px; padding: 0 14px; }\n.aa-tabs.type-pill .aa-tabs-tab.is-active { background: #2F54EB; color: #fff; font-weight: 500; }\n\n/* 禁用 */\n.aa-tabs-tab.is-disabled { color: #BFBFBF; cursor: not-allowed; }\n.aa-tabs.type-card .aa-tabs-tab.is-disabled { background: #F5F5F5; }\n\n/* 关闭按钮 */\n.aa-tabs-close { display: inline-flex; align-items: center; justify-content: center;\n width: 16px; height: 16px; margin-left: 4px; border-radius: 50%; font-size: 12px; color: #8C8C8C; }\n.aa-tabs-close:hover { background: rgba(0,0,0,0.06); color: #262626; }\n.aa-tabs.type-pill .aa-tabs-close { color: rgba(255,255,255,0.85); }\n.aa-tabs.type-pill .aa-tabs-close:hover { background: rgba(255,255,255,0.25); color: #fff; }\n\n/* 新增标签按钮 */\n.aa-tabs-add { display: inline-flex; align-items: center; justify-content: center;\n width: 32px; height: 32px; margin-left: 4px; border: 1px dashed #E8ECF1; border-radius: 6px;\n color: #8C8C8C; cursor: pointer; font-size: 16px; }\n.aa-tabs-add:hover { color: #2F54EB; border-color: #2F54EB; }\n\n/* 内容区 */\n.aa-tabs-content { padding: 16px; font-size: 14px; color: #262626; }\n.aa-tabs.type-card .aa-tabs-content { border: 1px solid #E8ECF1; border-top: none; border-radius: 0 0 6px 6px; }\n","jsx":"import React from 'react';\nimport './Tabs.css';\n\n/**\n * Aurora Admin Tabs(React)\n * 对齐 组件7.txt PageTabs 规范:线型 / 卡片型 / 胶囊型,支持禁用、关闭、新增\n *\n * props:\n * - value / defaultValue : 当前激活标签 key(受控 / 非受控)\n * - tabs : [{ key, label, disabled?, preventClose? }]\n * - type : 'line' | 'card' | 'pill'(默认 'line')\n * - closable / addable : Boolean\n * - onChange(key) / onTabRemove(key) / onTabAdd()\n * - renderPanel(key) / children(key) : 返回当前激活标签的内容\n */\nexport default function Tabs({\n value,\n defaultValue,\n tabs = [],\n type = 'line',\n closable = false,\n addable = false,\n onChange,\n onTabRemove,\n onTabAdd,\n renderPanel\n}) {\n const current = value !== undefined ? value : defaultValue;\n const panelFn = renderPanel || (typeof children === 'function' ? children : null);\n\n const choose = (tab) => {\n if (tab.disabled) return;\n onChange && onChange(tab.key);\n };\n\n return (\n
\n
\n {tabs.map((tab) => (\n choose(tab)}\n >\n {tab.label}\n {closable && !tab.disabled && !tab.preventClose && (\n { e.stopPropagation(); onTabRemove && onTabRemove(tab.key); }}\n >×\n )}\n
\n ))}\n {addable && (\n
onTabAdd && onTabAdd()}>+
\n )}\n
\n\n
\n {panelFn ? panelFn(current) : null}\n
\n \n );\n}\n\n/*\n用法示例:\nimport Tabs from './Tabs';\n\nconst tabs = [\n { key: 'basic', label: '基本信息' },\n { key: 'safe', label: '安全设置' },\n { key: 'auth', label: '授权', disabled: true }\n];\n
当前标签:{key}
}\n/>\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"steps","name":"步骤条 Steps","tier":"core","confidence":"high","specBatch":1,"specFile":"组件1.txt","contractRef":null,"category":"navigation","specLines":["- 节点32px圆点,完成绿对勾,当前品牌色,未完成灰色","- 连线完成段品牌色"],"specSummary":"- 节点32px圆点,完成绿对勾,当前品牌色,未完成灰色","contract":null,"files":{"html":"../frameworks/steps.html","css":"../frameworks/Steps.css","jsx":"../frameworks/Steps.jsx","vue2":"../frameworks/Steps.vue2.vue","vue3":"../frameworks/Steps.vue3.vue"},"sizes":{"html":2281,"css":1853,"jsx":1439,"vue2":2868,"vue3":2850},"sources":{"html":"\n\n\n\n\nAurora Admin · Steps (H5)\n\n\n\n\n\n
\n

Aurora Admin · Steps 组件(纯 H5)

\n

对齐 StepList 规范:序号、标题、描述、状态、连接线

\n\n
\n

横向步骤条

\n
\n
\n\n
\n

纵向步骤条(含异常状态)

\n
\n
\n
\n\n\n\n\n","css":"/* Aurora Admin Steps 样式 — 对齐 组件2/7 StepList:序号、标题、描述、状态、连接线、横/纵 */\n\n.aa-steps { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-steps.horizontal { display: flex; }\n.aa-steps.vertical { display: inline-flex; flex-direction: column; }\n\n.aa-step { position: relative; display: flex; gap: 12px; flex: 1; }\n.aa-steps.vertical .aa-step { flex-direction: column; }\n\n.aa-step-icon {\n position: relative; width: 32px; height: 32px; border-radius: 50%; border: 2px solid #E8ECF1;\n display: flex; align-items: center; justify-content: center; font-size: 14px; background: #fff; color: #8C8C8C; flex-shrink: 0; z-index: 1;\n}\n.aa-step.is-finish .aa-step-icon { border-color: #2F54EB; color: #2F54EB; background: #F0F5FF; }\n.aa-step.is-process .aa-step-icon { border-color: #2F54EB; color: #fff; background: #2F54EB; }\n.aa-step.is-error .aa-step-icon { border-color: #F5222D; color: #F5222D; background: #FFF1F0; }\n\n.aa-step-title { font-size: 14px; font-weight: 500; line-height: 32px; }\n.aa-step.is-wait .aa-step-title { color: #8C8C8C; }\n.aa-step-desc { font-size: 12px; color: #8C8C8C; margin-top: 2px; }\n\n/* 连接线 */\n.aa-steps.horizontal .aa-step:not(:last-child) .aa-step-icon::after {\n content: ''; position: absolute; top: 15px; left: 38px; right: -12px; height: 2px; background: #E8ECF1;\n}\n.aa-steps.horizontal .aa-step.is-finish:not(:last-child) .aa-step-icon::after { background: #2F54EB; }\n\n.aa-steps.vertical .aa-step-body { padding-bottom: 24px; }\n.aa-steps.vertical .aa-step:not(:last-child) .aa-step-icon::after {\n content: ''; position: absolute; top: 40px; left: 15px; bottom: -12px; width: 2px; background: #E8ECF1;\n}\n.aa-steps.vertical .aa-step.is-finish:not(:last-child) .aa-step-icon::after { background: #2F54EB; }\n","jsx":"import React from 'react';\nimport './Steps.css';\n\n/**\n * Aurora Admin Steps(React)\n * 对齐 组件2/7 StepList 规范:序号、标题、描述、状态、连接线、横/纵\n *\n * props:\n * - steps : [{ title, desc? }]\n * - current : 当前步骤索引\n * - errorAt : 异常步骤索引(-1 表示无)\n * - direction: 'horizontal' | 'vertical'\n */\nexport default function Steps({ steps = [], current = 0, errorAt = -1, direction = 'horizontal' }) {\n const statusOf = (i) => {\n if (i === errorAt) return 'error';\n if (i < current) return 'finish';\n if (i === current) return 'process';\n return 'wait';\n };\n const icon = (s) => (s === 'finish' ? '✓' : s === 'error' ? '!' : null);\n\n return (\n
\n {steps.map((s, i) => {\n const st = statusOf(i);\n return (\n
\n
{icon(st) !== null ? icon(st) : i + 1}
\n
\n
{s.title}
\n {s.desc &&
{s.desc}
}\n
\n
\n );\n })}\n
\n );\n}\n\n/*\n用法示例:\nimport Steps from './Steps';\nconst steps = [{ title: '填写订单' }, { title: '审核' }, { title: '付款' }];\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"breadcrumb","name":"面包屑 Breadcrumb","tier":"core","confidence":"high","specBatch":1,"specFile":"组件1.txt","contractRef":null,"category":"navigation","specLines":"- 字号14px,层级用/分隔,次要文字#8C8C8C,当前页#262626","specSummary":"- 字号14px,层级用/分隔,次要文字#8C8C8C,当前页#262626","contract":null,"files":{"html":"../frameworks/Breadcrumb.html","css":"../frameworks/Breadcrumb.css","jsx":"../frameworks/Breadcrumb.jsx","vue2":"../frameworks/Breadcrumb.vue2.vue","vue3":"../frameworks/Breadcrumb.vue3.vue"},"sizes":{"html":1295,"css":545,"jsx":619,"vue2":559,"vue3":559},"sources":{"html":"\n\n\n \n \n Aurora Admin · Breadcrumb(普通 H5)\n \n \n \n\n\n
\n\n \n\n\n","css":"/* Aurora Admin Breadcrumb 样式 — 对齐 组件7.txt RouteBreadcrumb:面包屑,当前项高亮不可点击 */\n\n.aa-breadcrumb { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); font-size: 14px; display: flex; align-items: center; flex-wrap: wrap; }\n.aa-breadcrumb-item { cursor: pointer; color: #595959; }\n.aa-breadcrumb-item:hover { color: #2F54EB; }\n.aa-breadcrumb-item.is-current { color: #1F2329; font-weight: 500; cursor: default; }\n.aa-breadcrumb-sep { margin: 0 8px; color: #BFBFBF; }\n","jsx":"import React from 'react';\nimport './Breadcrumb.css';\n\nexport default function Breadcrumb({ items = [], onSelect }) {\n return (\n
\n {items.map((it, i) => (\n \n { if (i !== items.length - 1) onSelect && onSelect(it); }}\n >\n {it.label}\n \n {i !== items.length - 1 ? / : null}\n \n ))}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"tree","name":"树形控件 Tree","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":null,"category":"display","specLines":["- 用于层级数据展示:组织架构、菜单权限、分类目录","- 节点高度 32px,缩进 16px/级","- 展开/折叠箭头 12px,位于节点左侧","- 选中态:背景 #F0F5FF,文字 #2F54EB","- 支持复选框、拖拽排序、异步加载、搜索过滤","- 连接线默认隐藏,可选显示虚线 #E8ECF1"],"specSummary":"- 用于层级数据展示:组织架构、菜单权限、分类目录","contract":null,"files":{"html":"../frameworks/tree.html","css":"../frameworks/Tree.css","jsx":"../frameworks/Tree.jsx","vue2":"../frameworks/Tree.vue2.vue","vue3":"../frameworks/Tree.vue3.vue"},"sizes":{"html":5129,"css":958,"jsx":4278,"vue2":5030,"vue3":4751},"sources":{"html":"\n\n\n\n\nAurora Admin · Tree (H5)\n\n\n\n\n
\n

Aurora Admin · Tree 组件(纯 H5)

\n

对齐 Tree 规范:节点高32px、缩进16px/级、箭头12px、选中态 #F0F5FF + 品牌色、支持复选框级联

\n\n \n
\n
\n\n\n\n\n","css":"/* Aurora Admin Tree 样式 — 对齐 组件2.txt Tree 规范 */\n.aa-tree { min-width: 240px; border: 1px solid #E8ECF1; border-radius: 8px; padding: 4px; background: #fff; }\n.aa-tree-node {\n display: flex; align-items: center; gap: 4px; height: 32px; border-radius: 4px;\n cursor: pointer; user-select: none; font-size: 14px; color: #262626; padding-right: 8px;\n}\n.aa-tree-node:hover { background: #F5F7FA; }\n.aa-tree-node.is-selected { background: #F0F5FF; color: #2F54EB; }\n.aa-tree-arrow {\n display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px;\n font-size: 12px; color: #8C8C8C; transition: transform .15s; flex-shrink: 0;\n}\n.aa-tree-arrow.is-open { transform: rotate(90deg); }\n.aa-tree-arrow-placeholder { width: 16px; flex-shrink: 0; }\n.aa-tree-check { width: 14px; height: 14px; flex-shrink: 0; cursor: pointer; }\n.aa-tree-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n","jsx":"import React, { useState, useMemo, useRef } from 'react';\nimport './Tree.css';\n\n/**\n * Aurora Admin Tree(React)\n * 对齐 组件2.txt Tree 规范:节点高32px、缩进16px/级、箭头12px、选中 #F0F5FF/#2F54EB、复选框级联\n * nodes: [{ key, title, children, disabled }]\n * 受控:selectedKeys / checkedKeys(均为数组),通过 onSelect / onCheck 回调更新\n */\nfunction findNode(key, nodes) {\n for (const n of nodes) {\n if (n.key === key) return n;\n if (n.children) { const f = findNode(key, n.children); if (f) return f; }\n }\n return null;\n}\nfunction descendantKeys(node) {\n let out = [];\n (node.children || []).forEach((c) => { out.push(c.key); out = out.concat(descendantKeys(c)); });\n return out;\n}\nfunction allChildrenChecked(node, set) {\n if (!node.children || !node.children.length) return set.has(node.key);\n return node.children.every((c) => allChildrenChecked(c, set));\n}\n\nexport default function Tree({\n data = [],\n checkable = false,\n defaultExpandedKeys = [],\n selectedKeys = [],\n checkedKeys = [],\n onSelect,\n onCheck\n}) {\n const [expanded, setExpanded] = useState(() => new Set(defaultExpandedKeys));\n const [selected, setSelected] = useState(() => new Set(selectedKeys));\n const [checked, setChecked] = useState(() => new Set(checkedKeys));\n\n const visibleList = useMemo(() => {\n const out = [];\n const walk = (nodes, depth) => {\n nodes.forEach((n) => {\n out.push({ node: n, depth });\n if (n.children && n.children.length && expanded.has(n.key)) walk(n.children, depth + 1);\n });\n };\n walk(data, 0);\n return out;\n }, [data, expanded]);\n\n const hasChildren = (n) => !!(n.children && n.children.length);\n const isIndeterminate = (key) => {\n const node = findNode(key, data);\n if (!node || !hasChildren(node)) return false;\n const desc = descendantKeys(node);\n return desc.some((d) => checked.has(d)) && !allChildrenChecked(node, checked);\n };\n\n const toggleExpand = (key) =>\n setExpanded((prev) => { const n = new Set(prev); n.has(key) ? n.delete(key) : n.add(key); return n; });\n\n const onRowClick = (node) => {\n setSelected(new Set([node.key]));\n onSelect && onSelect(node.key);\n };\n\n const toggleCheck = (key) => {\n const node = findNode(key, data);\n const willCheck = !checked.has(key);\n const keys = [key, ...descendantKeys(node)];\n const next = new Set(checked);\n keys.forEach((k) => (willCheck ? next.add(k) : next.delete(k)));\n const propagate = (nodes) => {\n nodes.forEach((n) => {\n if (hasChildren(n)) {\n if (allChildrenChecked(n, next)) next.add(n.key); else next.delete(n.key);\n propagate(n.children);\n }\n });\n };\n propagate(data);\n setChecked(next);\n onCheck && onCheck([...next]);\n };\n\n return (\n
\n {visibleList.map(({ node, depth }) => {\n const open = expanded.has(node.key);\n const arrow = hasChildren(node) ? (\n { e.stopPropagation(); toggleExpand(node.key); }}>▸\n ) : ;\n const check = checkable ? (\n el && (el.indeterminate = isIndeterminate(node.key))}\n onClick={(e) => e.stopPropagation()}\n onChange={() => toggleCheck(node.key)}\n />\n ) : null;\n return (\n onRowClick(node)}\n >\n {arrow}\n {check}\n {node.title}\n
\n );\n })}\n \n );\n}\n\n/*\n用法示例:\nimport Tree from './Tree';\nconst data = [\n { key: 'org', title: '某某科技', children: [\n { key: 'tech', title: '技术中心', children: [{ key: 'fe', title: '前端组' }] }\n ]}\n];\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"dropdown","name":"下拉菜单 Dropdown","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":null,"category":"navigation","specLines":["- 触发器为按钮或文字,点击展开菜单","- 菜单项高度 32px,内边距左右 12px","- 悬停背景 #F5F7FA,危险项文字 #F5222D","- 支持分割线、禁用项、图标、子菜单","- 面板白底、圆角 6px、阴影中、最小宽度 120px"],"specSummary":"- 触发器为按钮或文字,点击展开菜单","contract":null,"files":{"html":"../frameworks/dropdown.html","css":"../frameworks/Dropdown.css","jsx":"../frameworks/Dropdown.jsx","vue2":"../frameworks/Dropdown.vue2.vue","vue3":"../frameworks/Dropdown.vue3.vue"},"sizes":{"html":3118,"css":1102,"jsx":2068,"vue2":2516,"vue3":2465},"sources":{"html":"\n\n\n\n\nAurora Admin · Dropdown (H5)\n\n\n\n\n
\n

Aurora Admin · Dropdown 组件(纯 H5)

\n

对齐 Dropdown 规范:菜单项高32px、内边距左右12px、悬停 #F5F7FA、危险项 #F5222D、面板圆角6px/阴影中/最小宽120px

\n\n
\n \n \n
\n
\n\n\n\n\n","css":"/* Aurora Admin Dropdown 样式 — 对齐 组件2.txt Dropdown 规范 */\n.aa-dropdown { position: relative; display: inline-block; }\n.aa-dropdown-trigger {\n border: 1px solid #E8ECF1; background: #fff; border-radius: 4px; height: 32px; padding: 0 12px;\n cursor: pointer; font-size: 14px; color: #262626; display: inline-flex; align-items: center; gap: 4px;\n}\n.aa-dropdown-trigger:hover { border-color: #2F54EB; }\n.aa-dropdown-panel {\n position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; min-width: 120px;\n background: #fff; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.08); padding: 4px 0;\n}\n.aa-dropdown-item {\n display: flex; align-items: center; gap: 8px; height: 32px; padding: 0 12px; cursor: pointer;\n font-size: 14px; color: #262626; white-space: nowrap;\n}\n.aa-dropdown-item:hover { background: #F5F7FA; }\n.aa-dropdown-item.is-danger { color: #F5222D; }\n.aa-dropdown-item.is-disabled { color: #BFBFBF; cursor: not-allowed; }\n.aa-dropdown-item.is-disabled:hover { background: transparent; }\n.aa-dropdown-divider { height: 1px; background: #F0F0F0; margin: 4px 0; }\n","jsx":"import React, { useState, useRef, useEffect } from 'react';\nimport './Dropdown.css';\n\n/**\n * Aurora Admin Dropdown(React)\n * 对齐 组件2.txt Dropdown 规范:触发器点击展开、菜单项高32px、内边距左右12px、悬停 #F5F7FA、危险项 #F5222D、面板圆角6px/阴影中/最小宽120px\n * items: [{ key, label, danger?, disabled?, divider?, icon? }]\n */\nexport default function Dropdown({ items = [], trigger = '操作', onSelect }) {\n const [open, setOpen] = useState(false);\n const rootRef = useRef(null);\n\n useEffect(() => {\n function onDocClick(e) {\n if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);\n }\n document.addEventListener('click', onDocClick);\n return () => document.removeEventListener('click', onDocClick);\n }, []);\n\n const handleClick = (item) => {\n if (item.disabled || item.divider) return;\n onSelect && onSelect(item.key != null ? item.key : item.label);\n setOpen(false);\n };\n\n return (\n
\n
setOpen((o) => !o)}>\n {trigger}\n
\n {open && (\n
\n {items.map((item, i) =>\n item.divider ? (\n
\n ) : (\n handleClick(item)}\n >\n {item.icon && {item.icon}}\n {item.label}\n
\n )\n )}\n
\n )}\n
\n );\n}\n\n/*\n用法示例:\nimport Dropdown from './Dropdown';\nconst items = [\n { key: 'edit', label: '编辑' },\n { key: 'copy', label: '复制' },\n { divider: true },\n { key: 'delete', label: '删除', danger: true }\n];\n console.log(k)} />\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"popconfirm","name":"气泡确认框 Popconfirm","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":null,"category":"feedback","specLines":["- 用于危险操作二次确认(删除、下线)","- 气泡内包含提示文字、确认按钮(危险红)、取消按钮(文字)","- 触发方式:点击,位置可配置(上/下/左/右)","- 气泡背景白色,圆角 6px,阴影中,宽度约 240px"],"specSummary":"- 用于危险操作二次确认(删除、下线)","contract":null,"files":{"html":"../frameworks/popconfirm.html","css":"../frameworks/Popconfirm.css","jsx":"../frameworks/Popconfirm.jsx","vue2":"../frameworks/Popconfirm.vue2.vue","vue3":"../frameworks/Popconfirm.vue3.vue"},"sizes":{"html":3645,"css":1554,"jsx":2878,"vue2":3319,"vue3":4012},"sources":{"html":"\n\n\n\n\nAurora Admin · Popconfirm (H5)\n\n\n\n\n\n
\n

Aurora Admin · Popconfirm 组件(纯 H5)

\n

对齐 Popconfirm 规范:气泡确认、图标+描述+确认/取消,点击外部关闭

\n\n
\n

删除确认(底部弹出)

\n \n \n \n \n
结果:—
\n
\n\n
\n

顶部弹出

\n \n \n \n \n
\n
\n\n\n\n\n","css":"/* Aurora Admin Popconfirm 样式 — 对齐 组件2.txt Popconfirm:气泡确认、图标+描述+确认/取消 */\n\n.aa-popconfirm { position: relative; display: inline-block; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-popconfirm-pop {\n position: absolute; z-index: 100; min-width: 220px; background: #fff; border: 1px solid #E8ECF1;\n border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.12); padding: 12px 14px;\n}\n.aa-popconfirm-pop.placement-bottom { top: calc(100% + 8px); left: 0; }\n.aa-popconfirm-pop.placement-top { bottom: calc(100% + 8px); left: 0; }\n\n.aa-popconfirm-inner { display: flex; gap: 8px; }\n.aa-popconfirm-icon { flex-shrink: 0; width: 16px; height: 16px; border-radius: 50%; background: #FA8C16; color: #fff;\n display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; margin-top: 2px; }\n.aa-popconfirm-title { font-size: 14px; color: #262626; line-height: 1.5; }\n\n.aa-popconfirm-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }\n.aa-popconfirm-btn { height: 28px; padding: 0 12px; border-radius: 4px; font-size: 13px; cursor: pointer;\n border: 1px solid #E8ECF1; background: #fff; color: #262626; transition: border-color .15s, color .15s; }\n.aa-popconfirm-btn:hover { color: #2F54EB; border-color: #2F54EB; }\n.aa-popconfirm-btn.primary { background: #2F54EB; border-color: #2F54EB; color: #fff; }\n.aa-popconfirm-btn.primary:hover { background: #1D39C4; border-color: #1D39C4; color: #fff; }\n","jsx":"import React, { useRef, useState, useEffect } from 'react';\nimport './Popconfirm.css';\n\n/**\n * Aurora Admin Popconfirm(React)\n * 对齐 组件2.txt Popconfirm 规范:气泡确认、图标+描述+确认/取消,点击外部关闭\n *\n * props:\n * - title : 确认描述文本\n * - okText / cancelText\n * - placement : 'bottom' | 'top'\n * - visible / defaultVisible : 受控打开\n * - onConfirm() / onCancel()\n * - children : 触发元素(如按钮)\n */\nexport default function Popconfirm({\n title,\n okText = '确定',\n cancelText = '取消',\n placement = 'bottom',\n visible,\n defaultVisible = false,\n onConfirm,\n onCancel,\n children\n}) {\n const rootRef = useRef(null);\n const [innerOpen, setInnerOpen] = useState(defaultVisible);\n const open = visible !== undefined ? visible : innerOpen;\n const [style, setStyle] = useState({});\n\n const syncPos = () => {\n if (!rootRef.current) return;\n const r = rootRef.current.getBoundingClientRect();\n setStyle(placement === 'top'\n ? { left: r.left, bottom: window.innerHeight - r.top }\n : { left: r.left, top: r.bottom + 8 });\n };\n const setOpen = (v) => {\n if (visible === undefined) setInnerOpen(v);\n if (v) { syncPos(); document.addEventListener('click', onDocClick, true); window.addEventListener('resize', syncPos); }\n else { document.removeEventListener('click', onDocClick, true); window.removeEventListener('resize', syncPos); }\n };\n const onDocClick = (e) => {\n if (rootRef.current && rootRef.current.contains(e.target)) return;\n setOpen(false);\n };\n\n useEffect(() => {\n if (open) { syncPos(); document.addEventListener('click', onDocClick, true); window.addEventListener('resize', syncPos); }\n return () => { document.removeEventListener('click', onDocClick, true); window.removeEventListener('resize', syncPos); };\n }, [open]);\n\n const confirm = () => { onConfirm && onConfirm(); setOpen(false); };\n const cancel = () => { onCancel && onCancel(); setOpen(false); };\n\n return (\n \n setOpen(!open)}>{children}\n {open && (\n
\n
\n !\n {title}\n
\n
\n \n \n
\n
\n )}\n
\n );\n}\n\n/*\n用法示例:\nimport Popconfirm from './Popconfirm';\n\n \n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"segmented","name":"分段控制器 Segmented","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":"components/segmented.json","category":"input","specLines":["- 用于视图切换、筛选条件切换","- 默认高度 32px,选项内边距 8px 16px","- 选中项背景白色(在浅灰容器内)或品牌色(背景深色时)","- 圆角 4px,容器背景 #F5F7FA","- 支持图标+文字,禁用态文字 #BFBFBF"],"specSummary":"- 用于视图切换、筛选条件切换","contract":{"variants":["视图切换分段","图标+文字分段","禁用态分段"],"dims":[{"name":"optionContent","values":["text","icon-text"]},{"name":"theme","values":["light(选中白底)","dark(选中品牌色)"]},{"name":"state","values":["default","disabled"]}],"unknowns":["深色背景容器的具体判定场景"],"anatomy":{"container":"容器高 32px,圆角 4px,背景 #F5F7FA","option":"选项内边距 8px 16px","selected":"选中项背景白色(浅灰容器内)或品牌色(背景深色时)"},"doNot":["超长选项的滚动截断规则"],"usage":["用于视图切换、筛选条件切换","选中项背景白色(浅灰容器内)或品牌色(深色背景)","支持图标+文字,禁用态文字 #BFBFBF"],"structure":{"height":"32px","optionPadding":"8px 16px","radius":"4px"}},"files":{"html":"../frameworks/segmented.html","css":"../frameworks/Segmented.css","jsx":"../frameworks/Segmented.jsx","vue2":"../frameworks/Segmented.vue2.vue","vue3":"../frameworks/Segmented.vue3.vue"},"sizes":{"html":2241,"css":726,"jsx":1387,"vue2":1481,"vue3":1505},"sources":{"html":"\n\n\n\n\nAurora Admin · Segmented (H5)\n\n\n\n\n
\n

Aurora Admin · Segmented 组件(纯 H5)

\n

对齐 Segmented 规范:高32px、选项内边距8px 16px、容器 #F5F7FA、圆角4px、选中白底、禁用 #BFBFBF

\n\n
\n \n \n \n \n
\n
\n\n\n\n\n","css":"/* Aurora Admin Segmented 样式 — 对齐 组件2.txt Segmented 规范 */\n.aa-segmented {\n display: inline-flex; align-items: center; background: #F5F7FA; border-radius: 4px; padding: 2px;\n}\n.aa-segmented-item {\n height: 28px; padding: 0 16px; display: inline-flex; align-items: center; gap: 6px;\n border-radius: 4px; cursor: pointer; font-size: 14px; color: #262626; border: none; background: none;\n transition: background .15s, color .15s;\n}\n.aa-segmented-item:hover:not(.is-active):not(.is-disabled) { color: #2F54EB; }\n.aa-segmented-item.is-active { background: #fff; color: #262626; box-shadow: 0 1px 2px rgba(0,0,0,0.08); font-weight: 500; }\n.aa-segmented-item.is-disabled { color: #BFBFBF; cursor: not-allowed; }\n","jsx":"import React from 'react';\nimport './Segmented.css';\n\n/**\n * Aurora Admin Segmented(React)\n * 对齐 组件2.txt Segmented 规范:高32px、选项内边距8px 16px、容器 #F5F7FA、圆角4px、选中白底、禁用 #BFBFBF\n * options: [{ label, value, icon?, disabled? }]\n * 受控:value + onChange;非受控:defaultValue\n */\nexport default function Segmented({ value, defaultValue, options = [], onChange }) {\n const current = value !== undefined ? value : defaultValue;\n\n const choose = (opt) => {\n if (opt.disabled) return;\n if (value === undefined) onChange && onChange(opt.value);\n else onChange && onChange(opt.value);\n };\n\n return (\n
\n {options.map((opt) => (\n choose(opt)}\n >\n {opt.icon && {opt.icon}}\n {opt.label}\n \n ))}\n
\n );\n}\n\n/*\n用法示例:\nimport Segmented from './Segmented';\nconst opts = [\n { label: '列表', value: 'list' },\n { label: '看板', value: 'kanban' },\n { label: '日历', value: 'cal', disabled: true }\n];\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"transfer","name":"穿梭框 Transfer","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":"components/transfer.json","category":"input","specLines":["- 用于批量选择,左右两栏列表","- 列表项高度 32px,带复选框","- 中间操作按钮:右移、左移、全部右移、全部左移","- 顶部搜索框,底部已选数量统计","- 面板宽度 200–240px,高度 256px,滚动"],"specSummary":"- 用于批量选择,左右两栏列表","contract":{"variants":["带搜索穿梭框","基础穿梭框(支持拖拽)"],"dims":[{"name":"search","values":["with-search","without-search"]},{"name":"operation","values":["move","move-all"]},{"name":"drag","values":["support","none"]}],"unknowns":["拖拽跨栏的具体交互细节"],"anatomy":{"source-panel":"左栏候选列表,顶部搜索框","target-panel":"右栏已选列表,底部已选数量统计","operation-buttons":"中间操作按钮:右移、左移、全部右移、全部左移","item":"列表项高 32px,带复选框"},"doNot":["树形穿梭框","面板内分页"],"usage":["用于批量选择,左右两栏列表","列表项带复选框,项高 32px","顶部搜索框,底部已选数量统计","适用于权限分配、成员选择"],"structure":{"panel":"@{width=200-240px; height=256px; scroll=True}"}},"files":{"html":"../frameworks/transfer.html","css":"../frameworks/Transfer.css","jsx":"../frameworks/Transfer.jsx","vue2":"../frameworks/Transfer.vue2.vue","vue3":"../frameworks/Transfer.vue3.vue"},"sizes":{"html":4214,"css":2197,"jsx":3560,"vue2":5407,"vue3":5536},"sources":{"html":"\n\n\n\n\nAurora Admin · Transfer (H5)\n\n\n\n\n\n
\n

Aurora Admin · Transfer 组件(纯 H5)

\n

对齐 Transfer 规范:左右两栏、搜索、复选框、已选数量

\n\n
\n
\n
\n
\n\n\n\n\n","css":"/* Aurora Admin Transfer 样式 — 对齐 组件7.txt Transfer:左右两栏、搜索、复选框、已选数量 */\n\n.aa-transfer { display: inline-flex; align-items: stretch; gap: 12px; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-transfer-panel { width: 220px; border: 1px solid #E8ECF1; border-radius: 6px; background: #fff; display: flex; flex-direction: column; }\n.aa-transfer-head { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-bottom: 1px solid #E8ECF1; font-size: 14px; }\n.aa-transfer-head .count { color: #8C8C8C; font-size: 12px; }\n.aa-transfer-search { padding: 8px 12px; border-bottom: 1px solid #E8ECF1; }\n.aa-transfer-search input { width: 100%; box-sizing: border-box; height: 30px; padding: 0 8px; border: 1px solid #E8ECF1; border-radius: 4px; font-size: 13px; outline: none; color: #262626; }\n.aa-transfer-search input:focus { border-color: #2F54EB; box-shadow: 0 0 0 2px rgba(47,84,235,0.2); }\n.aa-transfer-body { flex: 1; min-height: 180px; max-height: 240px; overflow: auto; padding: 4px 0; }\n.aa-transfer-item { display: flex; align-items: center; gap: 8px; padding: 6px 12px; cursor: pointer; font-size: 14px; }\n.aa-transfer-item:hover { background: #F5F7FA; }\n.aa-transfer-item.is-disabled { color: #BFBFBF; cursor: not-allowed; }\n.aa-transfer-item .box { width: 14px; height: 14px; border: 1px solid #E8ECF1; border-radius: 3px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; color: #fff; font-size: 10px; }\n.aa-transfer-item.is-checked .box { background: #2F54EB; border-color: #2F54EB; }\n.aa-transfer-empty { padding: 32px 12px; text-align: center; color: #BFBFBF; font-size: 13px; }\n.aa-transfer-ops { display: flex; flex-direction: column; justify-content: center; gap: 8px; }\n.aa-transfer-ops button { width: 32px; height: 32px; border-radius: 4px; border: 1px solid #E8ECF1; background: #fff; color: #262626; cursor: pointer; font-size: 14px; }\n.aa-transfer-ops button:hover:not(:disabled) { color: #2F54EB; border-color: #2F54EB; }\n.aa-transfer-ops button:disabled { color: #BFBFBF; cursor: not-allowed; }\n","jsx":"import React, { useState, useMemo } from 'react';\nimport './Transfer.css';\n\n/**\n * Aurora Admin Transfer(React)\n * 对齐 组件7.txt Transfer 规范:左右两栏、搜索、复选框、已选数量\n *\n * props:\n * - value / defaultValue : 已选 key 数组\n * - data : [{ key, label, disabled? }]\n * - titles : [左标题, 右标题]\n * - filterable\n * - onChange(keys)\n */\nexport default function Transfer({\n value,\n defaultValue = [],\n data = [],\n titles = ['候选列表', '已选列表'],\n filterable = false,\n onChange\n}) {\n const [selected, setSelected] = useState(value !== undefined ? value : defaultValue);\n const current = value !== undefined ? value : selected;\n const [leftChecked, setLeftChecked] = useState([]);\n const [rightChecked, setRightChecked] = useState([]);\n const [leftQ, setLeftQ] = useState('');\n const [rightQ, setRightQ] = useState('');\n\n const filteredLeft = useMemo(\n () => data.filter(d => !current.includes(d.key) && d.label.includes(leftQ)),\n [data, current, leftQ]\n );\n const filteredRight = useMemo(\n () => data.filter(d => current.includes(d.key) && d.label.includes(rightQ)),\n [data, current, rightQ]\n );\n\n const setVal = (next) => {\n if (value === undefined) setSelected(next);\n onChange && onChange(next);\n };\n const toggle = (which, key) => {\n const item = data.find(d => d.key === key);\n if (item && item.disabled) return;\n const arr = which === 'left' ? leftChecked : rightChecked;\n const set = which === 'left' ? setLeftChecked : setRightChecked;\n set(arr.includes(key) ? arr.filter(k => k !== key) : [...arr, key]);\n };\n const moveRight = () => {\n setVal([...current, ...leftChecked]);\n setLeftChecked([]);\n };\n const moveLeft = () => {\n const set = new Set(rightChecked);\n setVal(current.filter(k => !set.has(k)));\n setRightChecked([]);\n };\n\n const Panel = ({ title, items, checked, q, setQ, side }) => (\n
\n
\n {title}\n {checked.length}/{items.length}\n
\n {filterable && (\n
\n setQ(e.target.value)} placeholder=\"搜索\" />\n
\n )}\n
\n {items.map(d => (\n toggle(side, d.key)}\n >\n {checked.includes(d.key) ? '✓' : ''}{d.label}\n
\n ))}\n {!items.length &&
无数据
}\n
\n \n );\n\n return (\n
\n \n
\n \n \n
\n \n
\n );\n}\n\n/*\n用法示例:\nimport Transfer from './Transfer';\nconst data = [{ key: '1', label: '研发部' }, { key: '2', label: '产品部' }];\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"rate","name":"评分 Rate","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":"components/rate.json","category":"input","specLines":["- 用于评价、满意度打分","- 默认 5 星,可配置 1–10 星","- 选中星颜色 #FAAD14,未选中 #E8ECF1","- 尺寸 16/20/24px,支持半星、只读、字符自定义"],"specSummary":"- 用于评价、满意度打分","contract":{"variants":["默认五星评分","只读评分","半星评分"],"dims":[{"name":"count","values":["default-5","configurable-1-10"]},{"name":"size","values":["16px","20px","24px"]},{"name":"mode","values":["editable","readonly"]},{"name":"half","values":["support","none"]}],"unknowns":["字符自定义的具体形态"],"anatomy":{"star-group":"星组,横向排列","star-on":"选中星,颜色 #FAAD14","star-off":"未选中星,颜色 #E8ECF1"},"doNot":["评分文字描述映射(如 很差/很好)"],"usage":["用于评价、满意度打分","默认 5 星,可配置 1-10 星","选中星 #FAAD14,未选中 #E8ECF1","支持半星、只读、字符自定义"],"structure":{"size":"16px 20px 24px"}},"files":{"html":"../frameworks/rate.html","css":"../frameworks/Rate.css","jsx":"../frameworks/Rate.jsx","vue2":"../frameworks/Rate.vue2.vue","vue3":"../frameworks/Rate.vue3.vue"},"sizes":{"html":3308,"css":1082,"jsx":2026,"vue2":2695,"vue3":2706},"sources":{"html":"\n\n\n\n\nAurora Admin · Rate (H5)\n\n\n\n\n\n
\n

Aurora Admin · Rate 组件(纯 H5)

\n

对齐 RateInput 规范:星级、半星、只读、尺寸、文字

\n\n
\n

基础(点击评分)

\n
\n
\n\n
\n

允许半星

\n
\n
\n\n
\n

只读 + 显示文字

\n
\n
\n\n
\n

尺寸:small / default / large

\n
\n
\n
\n
\n
\n\n\n\n\n","css":"/* Aurora Admin Rate 样式 — 对齐 组件7.txt RateInput:星级、半星、只读、尺寸、文字 */\n\n.aa-rate { display: inline-flex; align-items: center; gap: 4px; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-rate-star { position: relative; display: inline-block; font-size: 20px; line-height: 1; color: #E8ECF1; cursor: pointer; transition: color .15s, transform .1s; }\n.aa-rate-star:hover { transform: scale(1.1); }\n.aa-rate-star .aa-rate-fill { position: absolute; left: 0; top: 0; overflow: hidden; color: #2F54EB; white-space: nowrap; pointer-events: none; }\n.aa-rate.is-readonly .aa-rate-star,\n.aa-rate.is-disabled .aa-rate-star { cursor: default; }\n.aa-rate.is-readonly .aa-rate-star:hover,\n.aa-rate.is-disabled .aa-rate-star:hover { transform: none; }\n.aa-rate-text { margin-left: 8px; font-size: 14px; color: #8C8C8C; }\n.aa-rate.sm .aa-rate-star { font-size: 14px; }\n.aa-rate.lg .aa-rate-star { font-size: 28px; }\n.aa-rate.sm .aa-rate-text { font-size: 12px; }\n.aa-rate.lg .aa-rate-text { font-size: 16px; }\n","jsx":"import React, { useState } from 'react';\nimport './Rate.css';\n\n/**\n * Aurora Admin Rate(React)\n * 对齐 组件7.txt RateInput 规范:星级、半星、只读、尺寸、文字\n *\n * props:\n * - value / defaultValue : 评分数值\n * - count : 星数(默认 5)\n * - allowHalf: 允许半星\n * - readonly / disabled\n * - size : 'default' | 'sm' | 'lg'\n * - showText : 是否显示评分文字\n * - texts : 文字数组\n * - onChange(value)\n */\nconst DEFAULT_TEXTS = ['极差', '失望', '一般', '满意', '惊喜'];\n\nexport default function Rate({\n value,\n defaultValue = 0,\n count = 5,\n allowHalf = false,\n readonly = false,\n disabled = false,\n size = 'default',\n showText = false,\n texts = DEFAULT_TEXTS,\n onChange\n}) {\n const [inner, setInner] = useState(defaultValue);\n const current = value !== undefined ? value : inner;\n\n const stars = Array.from({ length: count }, (_, i) => Math.max(0, Math.min(1, current - i)));\n const textLabel = current ? texts[Math.ceil(current) - 1] : '未评分';\n\n const choose = (evt, i) => {\n if (readonly || disabled) return;\n let next = i + 1;\n if (allowHalf) {\n const rect = evt.currentTarget.getBoundingClientRect();\n const half = (evt.clientX - rect.left) < rect.width / 2;\n next = half ? i + 0.5 : i + 1;\n }\n if (next === current) next = 0;\n if (value === undefined) setInner(next);\n onChange && onChange(next);\n };\n\n return (\n
\n {stars.map((s, i) => (\n choose(e, i)}>\n ★★\n \n ))}\n {showText && {textLabel}}\n
\n );\n}\n\n/*\n用法示例:\nimport Rate from './Rate';\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"slider","name":"滑块 Slider","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":"components/slider.json","category":"input","specLines":["- 用于范围筛选、参数调节","- 轨道高度 4px,圆角 2px","- 未选中段 #E8ECF1,选中段 #2F54EB","- 滑块圆点 14px,白色,阴影中,悬停放大","- 支持刻度标记、输入框联动、双滑块范围"],"specSummary":"- 用于范围筛选、参数调节","contract":{"variants":["单滑块","双滑块范围","带刻度滑块"],"dims":[{"name":"mode","values":["single","range"]},{"name":"marks","values":["with-marks","none"]},{"name":"inputLink","values":["support","none"]}],"unknowns":["刻度标签的密度规则"],"anatomy":{"track":"轨道高 4px,圆角 2px","filled":"选中段 #2F54EB","rest":"未选中段 #E8ECF1","thumb":"滑块圆点 14px,白色,阴影中,悬停放大"},"doNot":["垂直滑块"],"usage":["用于范围筛选、参数调节","未选中段 #E8ECF1,选中段 #2F54EB","支持刻度标记、输入框联动、双滑块范围"],"structure":{"trackHeight":"4px","thumbSize":"14px"}},"files":{"html":"../frameworks/slider.html","css":"../frameworks/Slider.css","jsx":"../frameworks/Slider.jsx","vue2":"../frameworks/Slider.vue2.vue","vue3":"../frameworks/Slider.vue3.vue"},"sizes":{"html":6143,"css":1794,"jsx":4366,"vue2":5757,"vue3":5649},"sources":{"html":"\n\n\n\n\nAurora Admin · Slider (H5)\n\n\n\n\n\n
\n

Aurora Admin · Slider 组件(纯 H5)

\n

对齐 SliderInput 规范:单/双滑块、刻度、输入框联动

\n\n
\n

单滑块 + 数值联动

\n
\n 音量\n
\n
\n
\n\n
\n

带刻度

\n
\n
\n\n
\n

双滑块范围

\n
\n
\n\n
\n

禁用

\n
\n
\n
\n\n\n\n\n","css":"/* Aurora Admin Slider 样式 — 对齐 组件7.txt SliderInput:单/双滑块、刻度、输入框联动 */\n\n.aa-slider { position: relative; display: flex; align-items: center; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-slider.is-disabled { opacity: .5; pointer-events: none; }\n.aa-slider-track { position: relative; flex: 1; height: 40px; }\n.aa-slider-rail { position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%); height: 4px; background: #E8ECF1; border-radius: 2px; }\n.aa-slider-fill { position: absolute; top: 50%; transform: translateY(-50%); height: 4px; background: #2F54EB; border-radius: 2px; }\n.aa-slider-handle {\n position: absolute; top: 50%; width: 14px; height: 14px; border-radius: 50%; background: #fff;\n border: 2px solid #2F54EB; transform: translate(-50%, -50%); cursor: pointer; box-shadow: 0 1px 3px rgba(0,0,0,0.15);\n transition: box-shadow .15s;\n}\n.aa-slider-handle:hover, .aa-slider-handle:focus { box-shadow: 0 0 0 4px rgba(47,84,235,0.2); outline: none; }\n.aa-slider-ticks { position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%); height: 0; pointer-events: none; }\n.aa-slider-tick { position: absolute; top: 0; width: 2px; height: 8px; background: #E8ECF1; transform: translate(-50%, -50%); }\n.aa-slider-value { margin-left: 12px; font-size: 14px; color: #262626; display: inline-flex; align-items: center; gap: 6px; }\n.aa-slider-value input { width: 56px; height: 28px; text-align: center; border: 1px solid #E8ECF1; border-radius: 4px; font-size: 13px; outline: none; color: #262626; }\n.aa-slider-value input:focus { border-color: #2F54EB; box-shadow: 0 0 0 2px rgba(47,84,235,0.2); }\n.aa-slider-label { margin-top: 4px; font-size: 12px; color: #8C8C8C; }\n","jsx":"import React, { useRef, useState, useEffect } from 'react';\nimport './Slider.css';\n\n/**\n * Aurora Admin Slider(React)\n * 对齐 组件7.txt SliderInput 规范:单/双滑块、刻度、输入框联动\n *\n * props:\n * - value / defaultValue : number | [lo, hi]\n * - min / max / step\n * - range : 双滑块\n * - showTicks : 刻度\n * - showInput : 数值输入框联动\n * - disabled\n * - onChange(value)\n */\nexport default function Slider({\n value,\n defaultValue = 0,\n min = 0,\n max = 100,\n step = 1,\n range = false,\n showTicks = false,\n showInput = false,\n disabled = false,\n onChange\n}) {\n const trackRef = useRef(null);\n const [dragIndex, setDragIndex] = useState(-1);\n const current = value !== undefined ? value : defaultValue;\n\n const pct = (v) => ((v - min) / (max - min)) * 100;\n const clamp = (v) => {\n const snapped = Math.round((v - min) / step) * step + min;\n return Math.max(min, Math.min(max, snapped));\n };\n const valueFromClientX = (x) => {\n const rect = trackRef.current.getBoundingClientRect();\n return clamp(min + (x - rect.left) / rect.width * (max - min));\n };\n\n const setVal = (next) => {\n if (value === undefined) onChange && onChange(next);\n else onChange && onChange(next);\n };\n\n const onMove = (e) => {\n if (dragIndex < 0) return;\n const nv = valueFromClientX(e.clientX);\n if (range) {\n const arr = [...current]; arr[dragIndex] = nv; arr.sort((a, b) => a - b);\n setVal(arr);\n } else {\n setVal(nv);\n }\n };\n const onUp = () => {\n setDragIndex(-1);\n window.removeEventListener('pointermove', onMove);\n window.removeEventListener('pointerup', onUp);\n };\n const onHandleDown = (i, e) => {\n if (disabled) return;\n e.preventDefault();\n setDragIndex(i);\n window.addEventListener('pointermove', onMove);\n window.addEventListener('pointerup', onUp);\n };\n const onTrackDown = (e) => {\n if (disabled || e.target.classList.contains('aa-slider-handle')) return;\n const nv = valueFromClientX(e.clientX);\n if (range) {\n const i = Math.abs(nv - current[0]) <= Math.abs(nv - current[1]) ? 0 : 1;\n const arr = [...current]; arr[i] = nv; arr.sort((a, b) => a - b);\n setVal(arr);\n } else {\n setVal(nv);\n }\n };\n const onInput = (i, e) => {\n const v = +e.target.value;\n if (range) {\n const arr = [...current]; arr[i] = v; arr.sort((a, b) => a - b);\n setVal(arr);\n } else {\n setVal(v);\n }\n };\n\n useEffect(() => () => onUp(), []); // eslint-disable-line\n\n const handlePercents = range ? [pct(current[0]), pct(current[1])] : [pct(current)];\n const fillStyle = range\n ? { left: pct(current[0]) + '%', width: (pct(current[1]) - pct(current[0])) + '%' }\n : { left: '0%', width: pct(current) + '%' };\n const tickPercents = Array.from(\n { length: Math.floor((max - min) / step) + 1 },\n (_, i) => pct(min + i * step)\n );\n\n return (\n
\n
\n
\n
\n {showTicks && (\n
\n {tickPercents.map((t, i) => (\n
\n ))}\n
\n )}\n {handlePercents.map((h, i) => (\n onHandleDown(i, e)}\n >
\n ))}\n
\n {showInput && (\n
\n {range ? (\n <>\n onInput(0, e)} />\n —\n onInput(1, e)} />\n \n ) : (\n onInput(null, e)} />\n )}\n
\n )}\n \n );\n}\n\n/*\n用法示例:\nimport Slider from './Slider';\n\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"mention","name":"提及 Mention","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":"components/mention.json","category":"input","specLines":["- 用于评论、任务分配中 @用户","- 输入框内输入 @ 触发面板","- 面板列出用户列表,含头像+姓名+部门","- 选中后以标签形式插入输入框,可删除","- 面板宽度 240px,阴影中,圆角 6px"],"specSummary":"- 用于评论、任务分配中 @用户","contract":{"variants":["评论 @ 用户","任务分配 @ 成员"],"dims":[{"name":"trigger","values":["at-char"]},{"name":"panelItem","values":["avatar-name-dept"]},{"name":"insert","values":["removable-tag"]}],"unknowns":["插入标签的具体样式(沿用标签组件)","面板最大高度与滚动规则"],"anatomy":{"input":"输入框,输入 @ 触发面板","panel":"用户面板,宽 240px,阴影中,圆角 6px","item":"用户项:头像 + 姓名 + 部门","tag":"选中后以标签形式插入输入框,可删除"},"doNot":["面板搜索过滤规则"],"usage":["用于评论、任务分配中 @ 用户","输入框内输入 @ 触发面板","面板列出用户列表,含头像+姓名+部门","选中后以标签形式插入输入框,可删除"],"structure":{"panelWidth":"240px"}},"files":{"html":"../frameworks/Mention.html","css":"../frameworks/Mention.css","jsx":"../frameworks/Mention.jsx","vue2":"../frameworks/Mention.vue2.vue","vue3":"../frameworks/Mention.vue3.vue"},"sizes":{"html":4077,"css":1584,"jsx":3196,"vue2":2945,"vue3":3029},"sources":{"html":"\n\n\n \n \n Aurora Admin · Mention\n \n \n\n\n
\n

Mention @ 提及

\n

输入框输入 @ 触发用户面板,选择后以标签形式插入,可删除。

\n\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin Mention 样式 — @ 提及 */\n.aa-mention { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); position: relative; width: 360px; }\n.aa-mention-area { position: relative; }\n.aa-mention-input { width: 100%; box-sizing: border-box; border: 1px solid #E8ECF1; border-radius: 6px; padding: 8px 10px; font-size: 14px; color: #262626; outline: none; resize: vertical; transition: border-color .15s, box-shadow .15s; }\n.aa-mention-input:focus { border-color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-mention-panel { position: absolute; z-index: 30; background: #fff; border: 1px solid #E8ECF1; border-radius: 6px; box-shadow: 0 6px 24px rgba(0,0,0,0.12); width: 240px; max-height: 220px; overflow: auto; }\n.aa-mention-item { display: flex; align-items: center; gap: 8px; padding: 7px 10px; cursor: pointer; }\n.aa-mention-item:hover, .aa-mention-item.is-active { background: #F0F5FF; }\n.aa-mention-avatar { width: 24px; height: 24px; border-radius: 50%; background: #2F54EB; color: #fff; font-size: 12px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }\n.aa-mention-name { font-size: 13px; color: #262626; }\n.aa-mention-dept { font-size: 12px; color: #8C8C8C; margin-left: auto; }\n.aa-mention-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }\n.aa-mention-chip { display: inline-flex; align-items: center; gap: 4px; background: #F0F5FF; color: #2F54EB; border-radius: 4px; padding: 2px 8px; font-size: 12px; }\n.aa-mention-chip b { cursor: pointer; font-weight: 600; }\n","jsx":"import React, { useState, useRef, useEffect } from 'react';\nimport './Mention.css';\n\nconst USERS = [\n { name: '王伟', dept: '华东大区' }, { name: '李娜', dept: '华南大区' }, { name: '张强', dept: '华北大区' },\n { name: '陈静', dept: '研发中心' }, { name: '刘洋', dept: '市场部' }\n];\n\nexport default function Mention({ modelValue = '', users = USERS, onChange }) {\n const [open, setOpen] = useState(false);\n const [active, setActive] = useState(-1);\n const [mentioned, setMentioned] = useState([]);\n const inputRef = useRef(null);\n const root = useRef(null);\n\n useEffect(() => {\n const onDoc = (e) => { if (root.current && !root.current.contains(e.target)) setOpen(false); };\n document.addEventListener('click', onDoc);\n return () => document.removeEventListener('click', onDoc);\n }, []);\n\n const onInput = (e) => {\n const pos = e.target.selectionStart, v = e.target.value;\n if (onChange) onChange(v);\n const at = v.lastIndexOf('@', pos - 1);\n if (at >= 0 && (at === 0 || /\\s/.test(v[at - 1])) && !/\\s/.test(v.slice(at + 1, pos))) { setActive(-1); setOpen(true); }\n else setOpen(false);\n };\n const move = (d) => setActive((a) => Math.max(-1, Math.min(users.length - 1, a + d)));\n const insert = (name) => {\n const el = inputRef.current, pos = el.selectionStart, v = el.value;\n const at = v.lastIndexOf('@', pos - 1);\n if (at >= 0 && (at === 0 || /\\s/.test(v[at - 1]))) el.value = v.slice(0, at) + '@' + name + ' ' + v.slice(pos);\n else el.value = v.slice(0, pos) + '@' + name + ' ' + v.slice(pos);\n if (mentioned.indexOf(name) < 0) setMentioned((m) => [...m, name]);\n if (onChange) onChange(el.value);\n setOpen(false);\n el.focus();\n };\n const remove = (n) => setMentioned((m) => m.filter((x) => x !== n));\n\n return (\n
\n
\n {\n if (e.key === 'ArrowDown') { e.preventDefault(); move(1); }\n else if (e.key === 'ArrowUp') { e.preventDefault(); move(-1); }\n else if (e.key === 'Enter') { e.preventDefault(); if (active >= 0) insert(users[active].name); }\n else if (e.key === 'Escape') setOpen(false);\n }}\n />\n {open && (\n
\n {users.map((u, i) => (\n
insert(u.name)}>\n {u.name[0]}\n {u.name}\n {u.dept}\n
\n ))}\n
\n )}\n
\n
\n {mentioned.map((n) => (\n @{n} remove(n)}>×\n ))}\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"autocomplete","name":"自动完成 AutoComplete","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":"components/autocomplete.json","category":"input","specLines":["- 输入时根据关键字匹配建议","- 面板样式同选择器下拉","- 高亮匹配文字使用品牌色","- 支持防抖、远程搜索、自定义渲染"],"specSummary":"- 输入时根据关键字匹配建议","contract":{"variants":["本地数据源自动完成","远程搜索自动完成","自定义渲染建议项"],"dims":[{"name":"suggestionSource","values":["local","remote"]},{"name":"itemRender","values":["default","custom"]},{"name":"state","values":["default","focus","disabled"]}],"unknowns":["远程搜索的加载态与失败态样式","建议项最大展示条数"],"anatomy":{"input":"输入框容器,规格同基础输入框(高 32px)","panel":"建议下拉面板,样式同选择器下拉面板","item":"建议项,命中文字以品牌色高亮"},"doNot":["建议面板分组标题样式","多选标签模式"],"usage":["输入时根据关键字匹配建议","面板样式与选择器下拉保持一致","高亮匹配文字使用品牌色 var(--au-color-brand)","支持防抖、远程搜索、自定义渲染建议项"],"structure":{"height":"32px","panel":"白底、阴影中、圆角与选择器一致","debounce":"默认防抖后触发搜索"}},"files":{"html":"../frameworks/AutoComplete.html","css":"../frameworks/AutoComplete.css","jsx":"../frameworks/AutoComplete.jsx","vue2":"../frameworks/AutoComplete.vue2.vue","vue3":"../frameworks/AutoComplete.vue3.vue"},"sizes":{"html":2912,"css":1126,"jsx":2490,"vue2":2246,"vue3":2311},"sources":{"html":"\n\n\n \n \n Aurora Admin · AutoComplete\n \n \n\n\n
\n

AutoComplete 自动完成

\n

输入时匹配建议,高亮关键字,支持键盘上下选择与回车。

\n\n
\n
\n \n
\n
\n

试试输入:北京、上海、广州…

\n
\n
\n\n \n\n\n","css":"/* Aurora Admin AutoComplete 样式 — 自动完成 */\n.aa-autocomplete { position: relative; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); width: 260px; }\n.aa-autocomplete-input { width: 100%; box-sizing: border-box; border: 1px solid #E8ECF1; border-radius: 6px; padding: 8px 10px; font-size: 14px; color: #262626; outline: none; transition: border-color .15s, box-shadow .15s; }\n.aa-autocomplete-input:focus { border-color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-autocomplete-pop { position: absolute; top: 42px; left: 0; right: 0; background: #fff; border: 1px solid #E8ECF1; border-radius: 6px; box-shadow: 0 6px 24px rgba(0,0,0,0.12); max-height: 220px; overflow: auto; z-index: 20; }\n.aa-autocomplete-item { padding: 8px 12px; font-size: 13px; color: #262626; cursor: pointer; }\n.aa-autocomplete-item:hover, .aa-autocomplete-item.is-active { background: #F0F5FF; color: #2F54EB; }\n.aa-autocomplete-item mark { background: transparent; color: #2F54EB; font-weight: 600; }\n.aa-autocomplete-empty { padding: 10px 12px; color: #8C8C8C; font-size: 13px; }\n","jsx":"import React, { useState, useRef, useEffect } from 'react';\nimport './AutoComplete.css';\n\nconst OPTIONS = ['北京', '上海', '广州', '深圳', '杭州', '成都', '重庆', '武汉', '西安', '南京', '苏州', '天津'];\n\nexport default function AutoComplete({ modelValue = '', options = OPTIONS, placeholder = '输入关键字…', onChange, onSelect }) {\n const [open, setOpen] = useState(false);\n const [active, setActive] = useState(-1);\n const [list, setList] = useState([]);\n const root = useRef(null);\n\n useEffect(() => {\n const onDoc = (e) => { if (root.current && !root.current.contains(e.target)) setOpen(false); };\n document.addEventListener('click', onDoc);\n return () => document.removeEventListener('click', onDoc);\n }, []);\n\n const onInput = (e) => {\n const q = e.target.value;\n if (onChange) onChange(q);\n setActive(-1);\n setList(q ? options.filter((d) => d.indexOf(q) >= 0) : []);\n setOpen(true);\n };\n const highlight = (it) => {\n const q = modelValue.trim();\n if (!q) return it;\n return it.replace(new RegExp('(' + q.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&') + ')', 'g'), '$1');\n };\n const move = (d) => { setOpen(true); setActive((a) => Math.max(-1, Math.min(list.length - 1, a + d))); };\n const choose = (i) => { if (onChange) onChange(list[i]); if (onSelect) onSelect(list[i]); setOpen(false); };\n\n return (\n
\n {\n if (e.key === 'ArrowDown') { e.preventDefault(); move(1); }\n else if (e.key === 'ArrowUp') { e.preventDefault(); move(-1); }\n else if (e.key === 'Enter' && active >= 0) choose(active);\n else if (e.key === 'Escape') setOpen(false);\n }}\n />\n {open && list.length > 0 && (\n
\n {list.map((it, i) => (\n choose(i)}\n dangerouslySetInnerHTML={{ __html: highlight(it) }}\n />\n ))}\n
\n )}\n {open && modelValue && list.length === 0 && (\n
无匹配结果
\n )}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"collapse","name":"折叠面板 Collapse","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":null,"category":"display","specLines":["- 用于分组展示内容,默认展开第一项","- 标题栏高度 40px,左侧箭头图标","- 展开时箭头旋转 90°,内容区下方分割线","- 支持手风琴模式(同时只展开一个)"],"specSummary":"- 用于分组展示内容,默认展开第一项","contract":null,"files":{"html":"../frameworks/collapse.html","css":"../frameworks/Collapse.css","jsx":"../frameworks/Collapse.jsx","vue2":"../frameworks/Collapse.vue2.vue","vue3":"../frameworks/Collapse.vue3.vue"},"sizes":{"html":3757,"css":1380,"jsx":2367,"vue2":2821,"vue3":2856},"sources":{"html":"\n\n\n\n\nAurora Admin · Collapse (H5)\n\n\n\n\n\n
\n

Aurora Admin · Collapse 组件(纯 H5)

\n

对齐规范:标题栏高40px、左侧箭头旋转90°、内容分割线、手风琴模式

\n\n
\n

手风琴模式(同时仅展开一个)

\n
\n
\n
▶基本设置
\n
系统名称、主题色、默认语言等基础配置。
\n
\n
\n
▶通知设置
\n \n
\n
\n
▶安全设置
\n \n
\n
\n
▶高级(无权限)
\n \n
\n
\n
\n\n
\n

非手风琴(可同时展开多个)

\n
\n
\n
▶订单管理
\n
查看与处理所有订单。
\n
\n
\n
▶退款管理
\n
退款审核与到账记录。
\n
\n
\n
\n
\n\n\n\n\n","css":"/* Aurora Admin Collapse 样式 — 对齐 组件2.txt 规范:标题栏高40px、左侧箭头旋转90°、内容分割线、手风琴模式 */\n\n.aa-collapse { border: 1px solid #E8ECF1; border-radius: 6px; overflow: hidden; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; background: #fff; }\n.aa-collapse-item + .aa-collapse-item { border-top: 1px solid #E8ECF1; }\n\n.aa-collapse-header {\n display: flex; align-items: center; gap: 8px;\n height: 40px; padding: 0 16px; cursor: pointer;\n font-size: 14px; color: #262626; background: #fff;\n transition: background .15s; user-select: none;\n}\n.aa-collapse-header:hover { background: #F5F7FA; }\n.aa-collapse-item.is-disabled .aa-collapse-header { color: #BFBFBF; cursor: not-allowed; background: #fff; }\n\n.aa-collapse-arrow {\n display: inline-flex; align-items: center; justify-content: center;\n width: 16px; height: 16px; color: #8C8C8C; font-size: 12px;\n transition: transform .2s ease; transform: rotate(0deg);\n}\n.aa-collapse-item.is-open .aa-collapse-arrow { transform: rotate(90deg); }\n\n.aa-collapse-title { flex: 1; }\n\n.aa-collapse-content {\n padding: 16px; font-size: 14px; line-height: 1.6; color: #595959;\n border-top: 1px solid #E8ECF1; /* 内容分割线 */\n background: #fff;\n}\n.aa-collapse-item.is-disabled .aa-collapse-content { color: #BFBFBF; }\n","jsx":"import React, { useState } from 'react';\nimport './Collapse.css';\n\n/**\n * Aurora Admin Collapse(React)\n * 对齐 组件2.txt 规范:标题栏高40px、左侧箭头旋转90°、内容分割线、手风琴模式\n *\n * props:\n * - items : [{ key, title, disabled? }]\n * - accordion: Boolean(手风琴模式,同时仅展开一个)\n * - value / defaultValue : 展开 key 数组(手风琴时为单个 key 字符串)\n * - onChange(keys)\n * - renderPanel(key) / children(key) : 返回对应面板内容\n */\nexport default function Collapse({\n items = [],\n accordion = false,\n value,\n defaultValue = accordion ? '' : [],\n onChange,\n renderPanel\n}) {\n const panelFn = renderPanel || (typeof children === 'function' ? children : null);\n const [inner, setInner] = useState(defaultValue);\n const open = value !== undefined ? value : inner;\n\n const openList = accordion ? (open ? [open] : []) : (Array.isArray(open) ? open : []);\n const isOpen = (k) => openList.includes(k);\n\n const toggle = (item) => {\n if (item.disabled) return;\n let next;\n if (accordion) {\n next = isOpen(item.key) ? [] : [item.key];\n } else {\n next = isOpen(item.key) ? openList.filter(k => k !== item.key) : [...openList, item.key];\n }\n const result = accordion ? (next[0] || '') : next;\n if (value === undefined) setInner(result);\n onChange && onChange(result);\n };\n\n return (\n
\n {items.map((item) => (\n \n
toggle(item)}>\n ▶\n {item.title}\n
\n {isOpen(item.key) && (\n
\n {panelFn ? panelFn(item.key) : null}\n
\n )}\n
\n ))}\n \n );\n}\n\n/*\n用法示例:\nimport Collapse from './Collapse';\nconst items = [\n { key: 'a', title: '基本设置' },\n { key: 'b', title: '通知设置' },\n { key: 'c', title: '高级', disabled: true }\n];\n
内容:{k}
} />\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"calendar","name":"日历 Calendar","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":null,"category":"display","specLines":["- 用于日程管理、考勤、排班","- 月视图、周视图、日视图切换","- 单元格高度 100px(月视图)","- 选中日期背景品牌色,今日日期边框品牌色","- 事件显示为彩色标签条,支持拖拽"],"specSummary":"- 用于日程管理、考勤、排班","contract":null,"files":{"html":"../frameworks/Calendar.html","css":"../frameworks/Calendar.css","jsx":"../frameworks/Calendar.jsx","vue2":"../frameworks/Calendar.vue2.vue","vue3":"../frameworks/Calendar.vue3.vue"},"sizes":{"html":3307,"css":1357,"jsx":2644,"vue2":2714,"vue3":2585},"sources":{"html":"\n\n\n \n \n Aurora Admin · Calendar(普通 H5)\n \n \n \n\n\n
\n\n \n\n\n","css":"/* Aurora Admin Calendar 样式 — 月历组件,可选中日期,标记今天 */\n\n.aa-calendar { width: 320px; background: #fff; border: 1px solid #E8ECF1; border-radius: 8px; padding: 12px;\n font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; box-sizing: border-box; }\n.aa-calendar-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }\n.aa-calendar-title { font-size: 14px; font-weight: 500; color: #1F2329; }\n.aa-calendar-nav { width: 28px; height: 28px; border: none; background: none; cursor: pointer; color: #595959; border-radius: 4px; font-size: 16px; }\n.aa-calendar-nav:hover { background: #F0F5FF; color: #2F54EB; }\n.aa-calendar-week, .aa-calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); }\n.aa-calendar-week { margin-bottom: 4px; }\n.aa-calendar-weekday { text-align: center; font-size: 12px; color: #8C8C8C; padding: 4px 0; }\n.aa-calendar-cell { text-align: center; padding: 6px 0; font-size: 13px; cursor: pointer; border-radius: 6px; }\n.aa-calendar-cell:hover { background: #F0F5FF; }\n.aa-calendar-cell.is-out { color: #BFBFBF; }\n.aa-calendar-cell.is-today { color: #2F54EB; font-weight: 600; }\n.aa-calendar-cell.is-selected { background: #2F54EB; color: #fff; }\n.aa-calendar-cell.is-selected.is-today { color: #fff; }\n","jsx":"import React, { useState, useMemo } from 'react';\nimport './Calendar.css';\n\nconst WEEKS = ['日', '一', '二', '三', '四', '五', '六'];\nfunction fmt(dt) {\n const p = x => (x < 10 ? '0' + x : '' + x);\n return dt.getFullYear() + '-' + p(dt.getMonth() + 1) + '-' + p(dt.getDate());\n}\n\nexport default function Calendar({ value = '', onChange }) {\n const now = new Date();\n const [viewY, setViewY] = useState(now.getFullYear());\n const [viewM, setViewM] = useState(now.getMonth());\n const [selected, setSelected] = useState(value);\n const today = fmt(now);\n\n const cells = useMemo(() => {\n const first = new Date(viewY, viewM, 1).getDay();\n const days = new Date(viewY, viewM + 1, 0).getDate();\n const prevDays = new Date(viewY, viewM, 0).getDate();\n const arr = [];\n for (let i = 0; i < first; i++) {\n let pd = prevDays - first + 1 + i, pm = viewM - 1, py = viewY;\n if (pm < 0) { pm = 11; py--; }\n arr.push({ day: pd, out: true, key: fmt(new Date(py, pm, pd)) });\n }\n for (let d = 1; d <= days; d++) {\n const k = fmt(new Date(viewY, viewM, d));\n arr.push({ day: d, out: false, today: k === today, key: k });\n }\n const tail = (7 - (arr.length % 7)) % 7;\n for (let t = 1; t <= tail; t++) {\n let nm = viewM + 1, ny = viewY; if (nm > 11) { nm = 0; ny++; }\n arr.push({ day: t, out: true, key: fmt(new Date(ny, nm, t)) });\n }\n return arr;\n }, [viewY, viewM, today]);\n\n function prev() { let m = viewM - 1, y = viewY; if (m < 0) { m = 11; y--; } setViewM(m); setViewY(y); }\n function next() { let m = viewM + 1, y = viewY; if (m > 11) { m = 0; y++; } setViewM(m); setViewY(y); }\n function pick(c) {\n setSelected(c.key);\n setViewY(parseInt(c.key.slice(0, 4), 10));\n setViewM(parseInt(c.key.slice(5, 7), 10) - 1);\n onChange && onChange(c.key);\n }\n\n return (\n
\n
\n \n {viewY} 年 {viewM + 1} 月\n \n
\n
\n {WEEKS.map(w => {w})}\n
\n
\n {cells.map((c, i) => (\n
pick(c)}>\n {c.day}\n
\n ))}\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"carousel","name":"轮播 Carousel","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":null,"category":"display","specLines":["- 用于运营 Banner、公告轮播","- 高度 120–200px,圆角 8px","- 指示器圆点 8px,当前项品牌色","- 支持自动播放、左右箭头、触摸滑动"],"specSummary":"- 用于运营 Banner、公告轮播","contract":null,"files":{"html":"../frameworks/Carousel.html","css":"../frameworks/Carousel.css","jsx":"../frameworks/Carousel.jsx","vue2":"../frameworks/Carousel.vue2.vue","vue3":"../frameworks/Carousel.vue3.vue"},"sizes":{"html":2079,"css":1291,"jsx":1310,"vue2":1491,"vue3":1321},"sources":{"html":"\n\n\n \n \n Aurora Admin · Carousel(普通 H5)\n \n \n \n\n\n
\n\n \n\n\n","css":"/* Aurora Admin Carousel 样式 — 轮播图,箭头切换 + 指示点,可选自动播放 */\n\n.aa-carousel { position: relative; width: 100%; max-width: 480px; aspect-ratio: 16 / 9; overflow: hidden; border-radius: 8px;\n background: #F0F5FF; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-carousel-track { display: flex; height: 100%; transition: transform .3s; }\n.aa-carousel-slide { min-width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 20px; font-weight: 500; }\n.aa-carousel-arrow { position: absolute; top: 50%; transform: translateY(-50%); width: 32px; height: 32px; border: none; border-radius: 50%;\n background: rgba(0,0,0,0.3); color: #fff; cursor: pointer; font-size: 16px; }\n.aa-carousel-arrow:hover { background: rgba(0,0,0,0.5); }\n.aa-carousel-arrow.prev { left: 8px; }\n.aa-carousel-arrow.next { right: 8px; }\n.aa-carousel-dots { position: absolute; bottom: 10px; left: 0; right: 0; display: flex; gap: 6px; justify-content: center; }\n.aa-carousel-dot { width: 8px; height: 8px; border-radius: 50%; background: rgba(255,255,255,0.6); cursor: pointer; transition: width .2s; }\n.aa-carousel-dot.is-active { background: #fff; width: 18px; border-radius: 4px; }\n","jsx":"import React, { useState, useEffect } from 'react';\nimport './Carousel.css';\n\nexport default function Carousel({ items = [], autoplay = true, interval = 3000, onChange }) {\n const [index, setIndex] = useState(0);\n const len = items.length || 1;\n\n function go(i) {\n const next = (i + len) % len;\n setIndex(next);\n onChange && onChange(next);\n }\n useEffect(() => {\n if (!autoplay || items.length < 2) return;\n const t = setInterval(() => go(index + 1), interval);\n return () => clearInterval(t);\n // eslint-disable-next-line\n }, [index, autoplay, interval, items.length]);\n\n return (\n
\n
\n {items.map((s, i) => (\n
{s.text}
\n ))}\n
\n \n \n
\n {items.map((_, i) => (\n go(i)} />\n ))}\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"imagepreview","name":"图片预览 ImagePreview","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":null,"category":"display","specLines":["- 点击缩略图打开大图预览","- 遮罩深色 rgba(0,0,0,0.85)","- 支持缩放、旋转、左右切换、下载","- 底部显示当前序号/总数"],"specSummary":"- 点击缩略图打开大图预览","contract":null,"files":{"html":"../frameworks/ImagePreview.html","css":"../frameworks/ImagePreview.css","jsx":"../frameworks/ImagePreview.jsx","vue2":"../frameworks/ImagePreview.vue2.vue","vue3":"../frameworks/ImagePreview.vue3.vue"},"sizes":{"html":3114,"css":1190,"jsx":1069,"vue2":1089,"vue3":1062},"sources":{"html":"\n\n\n \n \n Aurora Admin · ImagePreview(普通 H5)\n \n \n \n\n\n
\n
\n \n \n \n \n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin ImagePreview 样式 — 图片大图预览,遮罩 + 左右切换 + 缩略图 */\n\n.aa-imgprev-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.85); z-index: 2500; display: flex; align-items: center; justify-content: center; flex-direction: column; }\n.aa-imgprev-img { max-width: 80vw; max-height: 70vh; border-radius: 4px; }\n.aa-imgprev-arrow { position: absolute; top: 50%; transform: translateY(-50%); width: 40px; height: 40px; border: none; border-radius: 50%; background: rgba(255,255,255,0.15); color: #fff; font-size: 20px; cursor: pointer; }\n.aa-imgprev-arrow:hover { background: rgba(255,255,255,0.3); }\n.aa-imgprev-arrow.prev { left: 24px; }\n.aa-imgprev-arrow.next { right: 24px; }\n.aa-imgprev-close { position: absolute; top: 20px; right: 24px; color: #fff; font-size: 24px; cursor: pointer; background: none; border: none; }\n.aa-imgprev-thumbs { display: flex; gap: 8px; margin-top: 16px; }\n.aa-imgprev-thumb { width: 48px; height: 48px; border-radius: 4px; object-fit: cover; cursor: pointer; border: 2px solid transparent; }\n.aa-imgprev-thumb.is-active { border-color: #2F54EB; }\n.aa-imgprev-count { color: #fff; font-size: 13px; margin-top: 8px; }\n","jsx":"import React from 'react';\nimport './ImagePreview.css';\n\nexport default function ImagePreview({ images = [], visible = false, index = 0, onClose, onChange }) {\n if (!visible) return null;\n const len = images.length || 1;\n const go = dir => onChange && onChange((index + dir + len) % len);\n return (\n
{ if (e.target === e.currentTarget) onClose && onClose(); }}>\n \n \n {index}\n \n
{index + 1} / {len}
\n
\n {images.map((u, i) => (\n onChange && onChange(i)} />\n ))}\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"qrcode","name":"二维码 QRCode","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":null,"category":"display","specLines":["- 用于扫码登录、分享、设备绑定","- 尺寸 120–240px,容错率 M","- 支持嵌入 Logo(尺寸 20%)","- 背景白色,前景黑色,支持自定义颜色"],"specSummary":"- 用于扫码登录、分享、设备绑定","contract":null,"files":{"html":"../frameworks/QRCode.html","css":"../frameworks/QRCode.css","jsx":"../frameworks/QRCode.jsx","vue2":"../frameworks/QRCode.vue2.vue","vue3":"../frameworks/QRCode.vue3.vue"},"sizes":{"html":2519,"css":478,"jsx":1679,"vue2":1998,"vue3":1818},"sources":{"html":"\n\n\n \n \n Aurora Admin · QRCode(普通 H5)\n \n \n \n\n\n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin QRCode 样式 — 二维码(示例图案,确定性生成,含定位角) */\n\n.aa-qrcode { display: inline-block; padding: 8px; background: #fff; border: 1px solid #E8ECF1; border-radius: 8px;\n font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-qrcode svg { display: block; }\n.aa-qrcode-text { font-size: 12px; color: #8C8C8C; text-align: center; margin-top: 6px; word-break: break-all; max-width: 200px; }\n","jsx":"import React, { useMemo } from 'react';\nimport './QRCode.css';\n\nconst N = 25;\n\nfunction matrix(value, n) {\n let seed = 0; for (let i = 0; i < value.length; i++) seed = (seed * 31 + value.charCodeAt(i)) >>> 0;\n const rnd = () => { seed = (seed * 1103515245 + 12345) >>> 0; return seed / 4294967296; };\n const m = []; for (let r = 0; r < n; r++) m.push(new Array(n).fill(false));\n for (let r = 0; r < n; r++) for (let c = 0; c < n; c++) if (rnd() > 0.5) m[r][c] = true;\n const place = (r0, c0) => {\n for (let r = 0; r < 7; r++) for (let c = 0; c < 7; c++) {\n const edge = r === 0 || r === 6 || c === 0 || c === 6, inner = r >= 2 && r <= 4 && c >= 2 && c <= 4;\n m[r0 + r][c0 + c] = edge || inner;\n }\n for (let rr = -1; rr < 8; rr++) for (let cc = -1; cc < 8; cc++) {\n const y = r0 + rr, x = c0 + cc; if (y >= 0 && y < n && x >= 0 && x < n) { if (rr < 0 || rr === 7 || cc < 0 || cc === 7) m[y][x] = false; }\n }\n };\n place(0, 0); place(0, n - 7); place(n - 7, 0);\n return m;\n}\n\nexport default function QRCode({ value = '', size = 200, color = '#1F2329', showText = true }) {\n const cell = size / N;\n const cells = useMemo(() => {\n const m = matrix(value, N), out = [];\n for (let r = 0; r < N; r++) for (let c = 0; c < N; c++) if (m[r][c]) out.push({ x: c * cell, y: r * cell });\n return out;\n }, [value, size]);\n\n return (\n
\n \n {cells.map((rc, i) => )}\n \n {showText ?
{value}
: null}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"countdown","name":"倒计时 Countdown","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":null,"category":"display","specLines":["- 用于秒杀、验证码、会议开始","- 数字使用 DIN 字体,字号 20–32px","- 时间单位分隔符冒号,颜色次要文字","- 支持天/时/分/秒显示,结束时触发回调"],"specSummary":"- 用于秒杀、验证码、会议开始","contract":null,"files":{"html":"../frameworks/Countdown.html","css":"../frameworks/Countdown.css","jsx":"../frameworks/Countdown.jsx","vue2":"../frameworks/Countdown.vue2.vue","vue3":"../frameworks/Countdown.vue3.vue"},"sizes":{"html":1772,"css":757,"jsx":1485,"vue2":1466,"vue3":1491},"sources":{"html":"\n\n\n \n \n Aurora Admin · Countdown\n \n \n\n\n
\n

Countdown 倒计时

\n

用于秒杀、验证码、会议开始等场景,支持天/时/分/秒,结束触发回调。

\n\n
\n
距活动结束
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin Countdown 样式 — 倒计时 */\n.aa-countdown { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); display: inline-flex; align-items: center; gap: 6px; }\n.aa-countdown-seg { display: flex; flex-direction: column; align-items: center; }\n.aa-countdown-num { font-family: Consolas, \"SFMono-Regular\", monospace; font-size: 24px; font-weight: 600; color: #1F2329; background: #F5F7FA; border-radius: 6px; min-width: 44px; padding: 4px 8px; text-align: center; line-height: 1.2; }\n.aa-countdown-label { font-size: 11px; color: #8C8C8C; margin-top: 3px; }\n.aa-countdown-sep { font-size: 22px; font-weight: 600; color: #8C8C8C; }\n.aa-countdown-done { font-size: 15px; color: #52C41A; font-weight: 600; }\n","jsx":"import React, { useState, useEffect } from 'react';\nimport './Countdown.css';\n\nconst pad = (n) => String(n).padStart(2, '0');\n\nexport default function Countdown({ target = Date.now() + 3600000 }) {\n const [now, setNow] = useState(Date.now());\n useEffect(() => {\n const t = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(t);\n }, []);\n const diff = Math.max(0, target - now);\n const d = Math.floor(diff / 86400000);\n const h = Math.floor((diff % 86400000) / 3600000);\n const m = Math.floor((diff % 3600000) / 60000);\n const s = Math.floor((diff % 60000) / 1000);\n\n return (\n
\n {d > 0 && (\n \n {pad(d)}\n 天\n \n )}\n \n {pad(h)}\n 时\n \n :\n \n {pad(m)}\n 分\n \n :\n \n {pad(s)}\n 秒\n \n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"watermark","name":"水印 Watermark","tier":"extension","confidence":"high","specBatch":2,"specFile":"组件2.txt","contractRef":null,"category":"display","specLines":["- 用于敏感信息防泄露","- 可配置文字、图片水印","- 默认文字颜色 #BFBFBF,旋转 -22°,间距 100px","- 覆盖整个页面或指定区域,不影响交互"],"specSummary":"- 用于敏感信息防泄露","contract":null,"files":{"html":"../frameworks/Watermark.html","css":"../frameworks/Watermark.css","jsx":"../frameworks/Watermark.jsx","vue2":"../frameworks/Watermark.vue2.vue","vue3":"../frameworks/Watermark.vue3.vue"},"sizes":{"html":1431,"css":314,"jsx":676,"vue2":1031,"vue3":1011},"sources":{"html":"\n\n\n \n \n Aurora Admin · Watermark(普通 H5)\n \n \n \n\n\n
\n
\n

机密文档

\n

该区域内容受水印保护,请勿外传。

\n
\n\n \n\n\n","css":"/* Aurora Admin Watermark 样式 — 对齐 组件7.txt 水印:平铺文本,不可选中,覆盖容器/全屏 */\n\n.aa-watermark { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 1; background-repeat: repeat; user-select: none; }\n.aa-watermark.is-fullscreen { position: fixed; }\n","jsx":"import React from 'react';\nimport './Watermark.css';\n\nexport default function Watermark({\n text = 'Aurora Admin', color = 'rgba(0,0,0,0.10)', fontSize = 14,\n rotate = -22, gap = 160, fullscreen = false\n}) {\n const svg = `` +\n ` ${text} `;\n const bg = `url(\"data:image/svg+xml;utf8,${encodeURIComponent(svg)}\")`;\n return (\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"cardlist","name":"卡片列表 CardList","tier":"extension","confidence":"high","specBatch":4,"specFile":"组件4.txt","contractRef":null,"category":"display","specLines":["- 以卡片形式展示列表数据","- 每行可显示多个卡片(栅格)","- 卡片内容自定义","- 支持分页或无限滚动"],"specSummary":"- 以卡片形式展示列表数据","contract":null,"files":{"html":"../frameworks/CardList.html","css":"../frameworks/CardList.css","jsx":"../frameworks/CardList.jsx","vue2":"../frameworks/CardList.vue2.vue","vue3":"../frameworks/CardList.vue3.vue"},"sizes":{"html":3359,"css":1346,"jsx":1191,"vue2":1023,"vue3":988},"sources":{"html":"\n\n\n \n \n Aurora Admin · CardList(普通 H5)\n \n \n \n\n\n
\n \n \n
\n\n
\n\n \n\n\n","css":"/* Aurora Admin CardList 样式 — 对齐 组件7.txt CardList:栅格卡片、封面/标题/描述/标签/操作 */\n\n.aa-cardlist { display: grid; gap: 16px; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-cardlist-item { border: 1px solid #E8ECF1; border-radius: 8px; overflow: hidden; background: #fff; transition: box-shadow .15s, border-color .15s; }\n.aa-cardlist-item:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.08); border-color: #C9D4F2; }\n.aa-card-cover { height: 120px; background: #F0F5FF; display: flex; align-items: center; justify-content: center; font-size: 32px; color: #2F54EB; }\n.aa-card-body { padding: 12px 16px; }\n.aa-card-title { font-size: 14px; font-weight: 500; color: #1F2329; }\n.aa-card-desc { font-size: 13px; color: #8C8C8C; margin-top: 4px; line-height: 1.5; }\n.aa-card-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; }\n.aa-card-tag { background: #F0F5FF; color: #2F54EB; font-size: 12px; padding: 1px 8px; border-radius: 2px; }\n.aa-card-actions { display: flex; gap: 8px; padding: 8px 16px; border-top: 1px solid #E8ECF1; }\n.aa-card-actions button { border: none; background: none; cursor: pointer; color: #2F54EB; font-size: 13px; padding: 4px 8px; border-radius: 4px; }\n.aa-card-actions button:hover { background: #F0F5FF; }\n","jsx":"import React from 'react';\nimport './CardList.css';\n\nexport default function CardList({ items = [], cols = 3, onAction }) {\n return (\n
\n {items.map((it, i) => (\n
\n
{it.emoji || '📄'}
\n
\n
{it.title}
\n
{it.desc}
\n {it.tags && it.tags.length ? (\n
\n {it.tags.map((t, ti) => (\n {t}\n ))}\n
\n ) : null}\n
\n {it.actions && it.actions.length ? (\n
\n {it.actions.map((a, ai) => (\n \n ))}\n
\n ) : null}\n
\n ))}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"shortcutpanel","name":"键盘快捷键面板 ShortcutPanel","tier":"extension","confidence":"high","specBatch":5,"specFile":"组件5.txt","contractRef":null,"category":"system","specLines":["- 展示系统所有快捷键","- 弹窗列表:快捷键、功能描述","- 支持自定义快捷键","- 在设置中配置"],"specSummary":"- 展示系统所有快捷键","contract":null,"files":{"html":"../frameworks/ShortcutPanel.html","css":"../frameworks/ShortcutPanel.css","jsx":"../frameworks/ShortcutPanel.jsx","vue2":"../frameworks/ShortcutPanel.vue2.vue","vue3":"../frameworks/ShortcutPanel.vue3.vue"},"sizes":{"html":1849,"css":1182,"jsx":1295,"vue2":1289,"vue3":1229},"sources":{"html":"\n\n\n \n \n Aurora Admin · ShortcutPanel\n \n \n\n\n
\n

ShortcutPanel 快捷键面板

\n

展示全部快捷键,支持搜索过滤。

\n\n
\n
\n \n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin ShortcutPanel 样式 — 快捷键面板 */\n.aa-shortcut { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); border: 1px solid #E8ECF1; border-radius: 8px; padding: 14px; }\n.aa-shortcut-search { width: 100%; border: 1px solid #E8ECF1; border-radius: 6px; padding: 7px 10px; font-size: 14px; outline: none; box-sizing: border-box; margin-bottom: 10px; transition: border-color .15s, box-shadow .15s; }\n.aa-shortcut-search:focus { border-color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-shortcut-list { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }\n.aa-shortcut-item { display: flex; justify-content: space-between; align-items: center; font-size: 13px; color: #262626; background: #FAFAFA; border-radius: 6px; padding: 8px 10px; }\n.aa-shortcut-key { font-family: Consolas, monospace; background: #fff; border: 1px solid #E8ECF1; border-radius: 4px; padding: 1px 6px; color: #595959; font-size: 12px; }\n.aa-shortcut-empty { grid-column: 1 / -1; text-align: center; color: #8C8C8C; font-size: 13px; padding: 14px; }\n@media (max-width: 520px) { .aa-shortcut-list { grid-template-columns: 1fr; } }\n","jsx":"import React, { useState, useMemo } from 'react';\nimport './ShortcutPanel.css';\n\nconst DEFAULT_ITEMS = [\n { name: '保存', key: 'Ctrl + S' },\n { name: '搜索', key: 'Ctrl + K' },\n { name: '新建', key: 'Ctrl + N' },\n { name: '刷新', key: 'F5' },\n { name: '关闭页签', key: 'Ctrl + W' },\n { name: '全屏', key: 'F11' },\n { name: '帮助', key: '?' },\n { name: '命令面板', key: 'Ctrl + P' }\n];\n\nexport default function ShortcutPanel({ items = DEFAULT_ITEMS }) {\n const [q, setQ] = useState('');\n const filtered = useMemo(() => {\n const s = q.trim().toLowerCase();\n if (!s) return items;\n return items.filter((it) => it.name.toLowerCase().indexOf(s) >= 0 || it.key.toLowerCase().indexOf(s) >= 0);\n }, [q, items]);\n\n return (\n
\n setQ(e.target.value)} />\n
\n {!filtered.length &&
无匹配快捷键
}\n {filtered.map((it, i) => (\n
\n {it.name}\n {it.key}\n
\n ))}\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"bankcardinput","name":"银行卡输入 BankCardInput","tier":"extension","confidence":"high","specBatch":6,"specFile":"组件6.txt","contractRef":null,"category":"input","specLines":["- 用于银行卡号输入","- 自动格式化每4位空格","- 识别卡类型(借记卡/信用卡/储蓄卡)并显示图标","- 支持Luhn校验","- 可配置扫描识别银行卡"],"specSummary":"- 用于银行卡号输入","contract":null,"files":{"html":"../frameworks/BankCardInput.html","css":"../frameworks/BankCardInput.css","jsx":"../frameworks/BankCardInput.jsx","vue2":"../frameworks/BankCardInput.vue2.vue","vue3":"../frameworks/BankCardInput.vue3.vue"},"sizes":{"html":2869,"css":1221,"jsx":2133,"vue2":2245,"vue3":2250},"sources":{"html":"\n\n\n \n \n Aurora Admin · BankCardInput\n \n \n\n\n
\n

BankCardInput 银行卡输入

\n

自动每 4 位空格、识别卡组织、Luhn 校验。

\n\n
\n
基础用法
\n
\n
\n \n 识别中\n
\n
支持 Visa / MasterCard / 银联等
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin BankCardInput 样式 — 银行卡输入 + 类型识别 + Luhn 校验 */\n.aa-card { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-card-field { display: flex; align-items: center; border: 1px solid #E8ECF1; border-radius: 6px; padding: 0 10px; height: 40px; background: #fff; transition: border-color .15s, box-shadow .15s; }\n.aa-card-field.is-focus { border-color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-card-field.is-valid { border-color: #52C41A; }\n.aa-card-field.is-invalid { border-color: #F5222D; }\n.aa-card-input { flex: 1; border: none; outline: none; font-size: 16px; letter-spacing: 1px; color: #1F2329; background: none; font-family: \"SFMono-Regular\", Consolas, monospace; }\n.aa-card-input::placeholder { color: #BFBFBF; letter-spacing: normal; }\n.aa-card-type { font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 4px; background: #F0F5FF; color: #2F54EB; min-width: 56px; text-align: center; }\n.aa-card-type.unknown { background: #F5F5F5; color: #8C8C8C; }\n.aa-card-tip { font-size: 12px; margin-top: 4px; color: #8C8C8C; }\n.aa-card-tip.valid { color: #52C41A; }\n.aa-card-tip.invalid { color: #F5222D; }\n","jsx":"import React, { useState, useMemo } from 'react';\nimport './BankCardInput.css';\n\nfunction detectType(v) {\n if (/^4/.test(v)) return 'Visa';\n if (/^5[1-5]/.test(v)) return 'MasterCard';\n if (/^62/.test(v)) return '银联';\n if (/^3[47]/.test(v)) return 'AmEx';\n if (/^35/.test(v)) return 'JCB';\n return '';\n}\nfunction luhn(v) {\n let sum = 0, alt = false;\n for (let i = v.length - 1; i >= 0; i--) {\n let n = parseInt(v[i], 10);\n if (alt) { n *= 2; if (n > 9) n -= 9; }\n sum += n; alt = !alt;\n }\n return sum % 10 === 0;\n}\n\nexport default function BankCardInput({ modelValue = '', placeholder = '请输入银行卡号', onChange }) {\n const [digits, setDigits] = useState(modelValue.replace(/\\D/g, ''));\n const [focused, setFocused] = useState(false);\n\n const display = digits.replace(/(.{4})/g, '$1 ').trim();\n const cardType = detectType(digits);\n const valid = digits.length >= 13 && luhn(digits);\n const invalid = digits.length >= 13 && !luhn(digits);\n const tipClass = valid ? 'valid' : invalid ? 'invalid' : '';\n const tipText = !digits\n ? '支持 Visa / MasterCard / 银联等'\n : valid\n ? '卡号校验通过'\n : invalid\n ? '卡号未通过 Luhn 校验'\n : '继续输入卡号…';\n\n const onInput = (e) => {\n const v = e.target.value.replace(/\\D/g, '').slice(0, 19);\n e.target.value = v.replace(/(.{4})/g, '$1 ').trim();\n setDigits(v);\n if (onChange) onChange(v);\n };\n\n return (\n
\n \n setFocused(true)}\n onBlur={() => setFocused(false)}\n />\n {cardType || '未知'}\n
\n
{tipText}
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"idcardinput","name":"身份证输入 IDCardInput","tier":"extension","confidence":"high","specBatch":6,"specFile":"组件6.txt","contractRef":null,"category":"input","specLines":["- 用于身份证号码输入","- 支持18位校验,自动验证出生日期、性别","- 输入框可分段显示(前6位、中间8位、后4位)","- 可配置扫描识别身份证","- 错误提示具体原因"],"specSummary":"- 用于身份证号码输入","contract":null,"files":{"html":"../frameworks/IDCardInput.html","css":"../frameworks/IDCardInput.css","jsx":"../frameworks/IDCardInput.jsx","vue2":"../frameworks/IDCardInput.vue2.vue","vue3":"../frameworks/IDCardInput.vue3.vue"},"sizes":{"html":3026,"css":1178,"jsx":2156,"vue2":2211,"vue3":2246},"sources":{"html":"\n\n\n \n \n Aurora Admin · IDCardInput\n \n \n\n\n
\n

IDCardInput 身份证输入

\n

18 位校验(含校验位)、自动提取出生日期与性别。

\n\n
\n
基础用法
\n
\n
\n \n
\n
\n
示例:11010119900307657X
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin IDCardInput 样式 — 身份证号输入 + 校验 + 信息提取 */\n.aa-idcard { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-idcard-field { display: flex; align-items: center; border: 1px solid #E8ECF1; border-radius: 6px; padding: 0 10px; height: 40px; background: #fff; transition: border-color .15s, box-shadow .15s; }\n.aa-idcard-field.is-focus { border-color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-idcard-field.is-valid { border-color: #52C41A; }\n.aa-idcard-field.is-invalid { border-color: #F5222D; }\n.aa-idcard-input { flex: 1; border: none; outline: none; font-size: 15px; letter-spacing: 1px; color: #1F2329; background: none; font-family: \"SFMono-Regular\", Consolas, monospace; }\n.aa-idcard-input::placeholder { color: #BFBFBF; letter-spacing: normal; }\n.aa-idcard-info { display: flex; gap: 18px; margin-top: 6px; font-size: 13px; color: #595959; flex-wrap: wrap; }\n.aa-idcard-info b { color: #1F2329; font-weight: 600; }\n.aa-idcard-tip { font-size: 12px; margin-top: 4px; color: #8C8C8C; }\n.aa-idcard-tip.valid { color: #52C41A; }\n.aa-idcard-tip.invalid { color: #F5222D; }\n","jsx":"import React, { useState, useMemo } from 'react';\nimport './IDCardInput.css';\n\nconst WEIGHTS = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];\nconst CHECK = '10X98765432';\nfunction validate(v) {\n if (!/^\\d{17}[\\dX]$/.test(v)) return null;\n let sum = 0;\n for (let i = 0; i < 17; i++) sum += parseInt(v[i], 10) * WEIGHTS[i];\n if (CHECK[sum % 11] !== v[17]) return null;\n const birth = v.slice(6, 14);\n return { birth: birth.slice(0, 4) + '-' + birth.slice(4, 6) + '-' + birth.slice(6, 8), gender: parseInt(v[16], 10) % 2 === 1 ? '男' : '女' };\n}\n\nexport default function IDCardInput({ modelValue = '', placeholder = '请输入 18 位身份证号', onChange }) {\n const [focused, setFocused] = useState(false);\n const result = useMemo(() => validate(modelValue.toUpperCase()) || {}, [modelValue]);\n const valid = !!result.birth;\n const invalid = modelValue.length === 18 && !result.birth;\n const tipClass = !modelValue ? '' : valid ? 'valid' : invalid ? 'invalid' : '';\n const tipText = !modelValue\n ? '示例:11010119900307657X'\n : modelValue.length < 18\n ? '已输入 ' + modelValue.length + ' 位,继续…'\n : valid\n ? '校验通过'\n : '身份证号校验失败,请检查';\n\n const onInput = (e) => {\n const v = e.target.value.replace(/[^0-9Xx]/g, '').toUpperCase().slice(0, 18);\n e.target.value = v;\n if (onChange) onChange(v);\n };\n\n return (\n
\n \n setFocused(true)}\n onBlur={() => setFocused(false)}\n />\n
\n {result.birth && (\n
\n 出生日期:{result.birth}\n 性别:{result.gender}\n
\n )}\n
{tipText}
\n \n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"plateinput","name":"车牌输入 PlateInput","tier":"extension","confidence":"high","specBatch":6,"specFile":"组件6.txt","contractRef":null,"category":"input","specLines":["- 用于车牌号输入","- 第一部分省份简称(下拉选择),后部分字母数字","- 自动大写,特殊字符限制","- 支持新能源车牌(8位)","- 可扫描识别车牌"],"specSummary":"- 用于车牌号输入","contract":null,"files":{"html":"../frameworks/PlateInput.html","css":"../frameworks/PlateInput.css","jsx":"../frameworks/PlateInput.jsx","vue2":"../frameworks/PlateInput.vue2.vue","vue3":"../frameworks/PlateInput.vue3.vue"},"sizes":{"html":2994,"css":1394,"jsx":1963,"vue2":1967,"vue3":1991},"sources":{"html":"\n\n\n \n \n Aurora Admin · PlateInput\n \n \n\n\n
\n

PlateInput 车牌号输入

\n

省份简称 + 字母数字组合,自动大写,支持新能源车牌(7 位)。

\n\n
\n
基础用法
\n
\n
\n \n \n
\n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin PlateInput 样式 — 车牌号输入(省份 + 字母数字,支持新能源) */\n.aa-plate { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-plate-field { display: inline-flex; align-items: stretch; border-radius: 6px; overflow: hidden; border: 1px solid #E8ECF1; transition: border-color .15s, box-shadow .15s; }\n.aa-plate-field.is-focus { border-color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-plate-field.is-valid { border-color: #52C41A; }\n.aa-plate-field.is-invalid { border-color: #F5222D; }\n.aa-plate-prov { border: none; background: #2F54EB; color: #fff; font-size: 16px; font-weight: 700; padding: 0 10px; cursor: pointer; outline: none; }\n.aa-plate-input { border: none; outline: none; font-size: 18px; font-weight: 700; letter-spacing: 3px; color: #1F2329; background: #fff; padding: 0 12px; width: 160px; text-transform: uppercase; font-family: \"SFMono-Regular\", Consolas, monospace; }\n.aa-plate-input::placeholder { color: #BFBFBF; letter-spacing: normal; font-weight: 400; }\n.aa-plate-tip { font-size: 12px; margin-top: 6px; color: #8C8C8C; }\n.aa-plate-tip.valid { color: #52C41A; }\n.aa-plate-tip.invalid { color: #F5222D; }\n.aa-plate-tip .aa-plate-tag { display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 3px; font-size: 11px; background: #F0F5FF; color: #2F54EB; }\n","jsx":"import React, { useState, useMemo } from 'react';\nimport './PlateInput.css';\n\nexport default function PlateInput({\n modelValue = '',\n provinces = ['京', '沪', '粤', '津', '冀', '鲁', '苏', '浙', '川', '渝'],\n onChange\n}) {\n const [prov, setProv] = useState(modelValue ? modelValue[0] : '京');\n const [body, setBody] = useState(modelValue ? modelValue.slice(1) : '');\n const [focused, setFocused] = useState(false);\n\n const valid = /^[A-Z][A-Z0-9]{5}$/.test(body) || /^[A-Z][A-Z0-9]{6}$/.test(body);\n const invalid = !!body && !valid;\n const isNewEnergy = /^[A-Z][A-Z0-9]{6}$/.test(body);\n const tipClass = !body ? '' : valid ? 'valid' : 'invalid';\n const tipText = !body\n ? '格式:省份 + 字母 + 5/6 位(新能源 6 位)'\n : valid\n ? '车牌号合法:' + prov + body + '' + (isNewEnergy ? '新能源' : '普通') + ''\n : '车牌格式不正确(首位应为字母)';\n\n const onInput = (e) => {\n const v = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 7);\n e.target.value = v;\n setBody(v);\n if (onChange) onChange(prov + v);\n };\n\n return (\n
\n \n \n setFocused(true)}\n onBlur={() => setFocused(false)}\n />\n
\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"bottomsheet","name":"底部面板 BottomSheet","tier":"extension","confidence":"high","specBatch":6,"specFile":"组件6.txt","contractRef":null,"category":"feedback","specLines":["- 从底部滑出的面板","- 类似移动端抽屉,但高度可选","- 支持拖拽关闭、遮罩点击关闭","- 内容可滚动","- 常用于操作选择、筛选、快捷操作"],"specSummary":"- 从底部滑出的面板","contract":null,"files":{"html":"../frameworks/BottomSheet.html","css":"../frameworks/BottomSheet.css","jsx":"../frameworks/BottomSheet.jsx","vue2":"../frameworks/BottomSheet.vue2.vue","vue3":"../frameworks/BottomSheet.vue3.vue"},"sizes":{"html":1737,"css":1474,"jsx":808,"vue2":886,"vue3":865},"sources":{"html":"\n\n\n \n \n Aurora Admin · BottomSheet(普通 H5)\n \n \n \n\n\n \n\n
\n
\n
\n
筛选订单
\n
\n 在这里放置筛选条件、操作列表等内容。面板可从底部滑动出现,点击遮罩或向下拖动可关闭。\n
\n
\n \n \n
\n
\n\n \n\n\n","css":"/* Aurora Admin BottomSheet 样式 — 对齐 组件7.txt BottomSheet:底部滑出面板,遮罩点击关闭,内容滚动 */\n\n.aa-sheet-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 2000; opacity: 0; pointer-events: none; transition: opacity .25s; }\n.aa-sheet-mask.is-open { opacity: 1; pointer-events: auto; }\n.aa-sheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: 2001; background: #fff; border-radius: 12px 12px 0 0;\n transform: translateY(100%); transition: transform .25s; max-height: 80vh; display: flex; flex-direction: column;\n font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-sheet.is-open { transform: translateY(0); }\n.aa-sheet-handle { width: 36px; height: 4px; border-radius: 2px; background: #E8ECF1; margin: 8px auto; cursor: grab; flex-shrink: 0; }\n.aa-sheet-header { padding: 4px 16px 12px; font-size: 16px; font-weight: 500; color: #1F2329; border-bottom: 1px solid #E8ECF1; }\n.aa-sheet-body { padding: 16px; overflow: auto; flex: 1; color: #262626; font-size: 14px; line-height: 1.6; }\n.aa-sheet-footer { padding: 12px 16px; border-top: 1px solid #E8ECF1; display: flex; justify-content: flex-end; gap: 8px; }\n.aa-sheet-btn { height: 32px; padding: 0 16px; border-radius: 6px; border: 1px solid #E8ECF1; background: #fff; color: #262626; cursor: pointer; font-size: 14px; }\n.aa-sheet-btn.primary { background: #2F54EB; border-color: #2F54EB; color: #fff; }\n","jsx":"import React, { useRef } from 'react';\nimport './BottomSheet.css';\n\nexport default function BottomSheet({ visible = false, title = '', onClose, children, footer }) {\n const startY = useRef(0);\n return (\n <>\n
\n { startY.current = e.clientY; }}\n onPointerUp={e => { if (startY.current - e.clientY < -80) onClose && onClose(); }}\n >\n
\n {title ?
{title}
: null}\n
{children}
\n {footer ?
{footer}
: null}\n
\n \n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"treetable","name":"树形表格 TreeTable","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"display","specLines":["- 在普通表格基础上支持树形层级展示","- 层级缩进 16px/级,展开/折叠箭头","- 父级行可显示汇总数据(如合计)","- 支持懒加载子节点、异步加载","- 支持排序、筛选、多选,选择父级联动子级","- 适用于组织架构、商品分类、权限树等场景"],"specSummary":"- 在普通表格基础上支持树形层级展示","contract":null,"files":{"html":"../frameworks/treetable.html","css":"../frameworks/TreeTable.css","jsx":"../frameworks/TreeTable.jsx","vue2":"../frameworks/TreeTable.vue2.vue","vue3":"../frameworks/TreeTable.vue3.vue"},"sizes":{"html":6148,"css":1454,"jsx":6235,"vue2":7343,"vue3":7008},"sources":{"html":"\n\n\n\n\nAurora Admin · TreeTable (H5)\n\n\n\n\n
\n

Aurora Admin · TreeTable 组件(纯 H5)

\n

对齐 TreeTable 规范:层级缩进16px/级、展开/折叠箭头、父级汇总(子节点合计)、选择父级联动子级

\n\n
\n \n \n \n \n \n \n \n \n \n \n \n
部门 / 人员人数预算(万)
合计00
\n
\n
\n\n\n\n\n","css":"/* Aurora Admin TreeTable 样式 — 对齐 组件7.txt TreeTable 规范 */\n.aa-tt { width: 100%; border: 1px solid #E8ECF1; border-radius: 8px; overflow: hidden; background: #fff; }\n.aa-tt table { width: 100%; border-collapse: collapse; font-size: 14px; color: #262626; }\n.aa-tt thead th { background: #FAFAFA; color: #262626; font-weight: 600; height: 48px; text-align: left; padding: 0 16px; border-bottom: 1px solid #E8ECF1; white-space: nowrap; }\n.aa-tt tbody td { height: 48px; padding: 0 16px; border-bottom: 1px solid #E8ECF1; text-align: left; }\n.aa-tt tbody tr:last-child td { border-bottom: none; }\n.aa-tt tbody tr:hover { background: #F5F7FA; }\n.aa-tt tbody tr.is-selected { background: #F0F5FF; }\n.aa-tt .align-right { text-align: right; }\n.aa-tt .col-selection { text-align: center; }\n.aa-tt .tree-cell { display: flex; align-items: center; gap: 4px; }\n.aa-tt .tt-arrow { width: 16px; height: 16px; font-size: 12px; color: #8C8C8C; transition: transform .15s; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }\n.aa-tt .tt-arrow.is-open { transform: rotate(90deg); }\n.aa-tt .tt-arrow-placeholder { width: 16px; flex-shrink: 0; }\n.aa-tt .tt-check { width: 14px; height: 14px; cursor: pointer; }\n.aa-tt .tt-summary { color: #8C8C8C; font-size: 12px; margin-left: 6px; }\n.aa-tt tfoot td { height: 44px; padding: 0 16px; background: #FAFAFA; font-weight: 600; border-top: 1px solid #E8ECF1; }\n","jsx":"import React, { useState, useMemo, useRef } from 'react';\nimport './TreeTable.css';\n\n/**\n * Aurora Admin TreeTable(React)\n * 对齐 组件7.txt TreeTable 规范:层级缩进16px/级、展开/折叠箭头、父级汇总(叶子合计)、选择父级联动子级\n * columns: [{ key, title, align? }]\n * data: 树形行 [{ key, children, ...字段 }]\n * summaryColumn: 指定数值列,父级与表尾显示叶子求和\n */\nfunction hasChildren(n) { return !!(n.children && n.children.length); }\nfunction leafSum(node, key) {\n if (!hasChildren(node)) return Number(node[key]) || 0;\n return node.children.reduce((s, c) => s + leafSum(c, key), 0);\n}\nfunction leafKeys(data, rowKey) {\n const out = [];\n const walk = (nodes) => nodes.forEach(n => { if (hasChildren(n)) walk(n.children); else out.push(n[rowKey]); });\n walk(data);\n return out;\n}\nfunction descendantKeys(node, rowKey) { let o = []; (node.children || []).forEach(c => { o.push(c[rowKey]); o = o.concat(descendantKeys(c, rowKey)); }); return o; }\nfunction allChildrenSelected(node, set, rowKey) { if (!hasChildren(node)) return set.has(node[rowKey]); return node.children.every(c => allChildrenSelected(c, set, rowKey)); }\n\nexport default function TreeTable({\n columns = [],\n data = [],\n rowKey = 'key',\n selectable = false,\n selectedKeys = [],\n defaultExpandedKeys = [],\n summaryColumn = '',\n summary = { label: '合计' },\n onSelectionChange\n}) {\n const [expanded, setExpanded] = useState(() => new Set(defaultExpandedKeys));\n const [selected, setSelected] = useState(() => new Set(selectedKeys));\n\n const visibleList = useMemo(() => {\n const out = [];\n const walk = (nodes, depth) => nodes.forEach(n => {\n out.push({ node: n, depth });\n if (hasChildren(n) && expanded.has(n[rowKey])) walk(n.children, depth + 1);\n });\n walk(data, 0);\n return out;\n }, [data, expanded, rowKey]);\n\n const totalOf = (key) => data.reduce((s, n) => s + leafSum(n, key), 0);\n const allSelected = data.length > 0 && leafKeys(data, rowKey).every(k => selected.has(k));\n const leaves = leafKeys(data, rowKey);\n const nSel = leaves.filter(k => selected.has(k)).length;\n const someSelected = nSel > 0 && nSel < leaves.length;\n\n const toggleExpand = (key) => setExpanded(prev => { const n = new Set(prev); n.has(key) ? n.delete(key) : n.add(key); return n; });\n\n const toggleCheck = (node) => {\n const key = node[rowKey];\n const willCheck = !selected.has(key);\n const keys = [key, ...descendantKeys(node, rowKey)];\n const next = new Set(selected);\n keys.forEach(k => willCheck ? next.add(k) : next.delete(k));\n const propagate = (nodes) => nodes.forEach(n => {\n if (hasChildren(n)) { allChildrenSelected(n, next, rowKey) ? next.add(n[rowKey]) : next.delete(n[rowKey]); propagate(n.children); }\n });\n propagate(data);\n setSelected(next);\n onSelectionChange && onSelectionChange([...next]);\n };\n\n const toggleAll = () => {\n const next = allSelected ? [] : leafKeys(data, rowKey);\n setSelected(new Set(next));\n onSelectionChange && onSelectionChange(next);\n };\n\n const onRowClick = (node) => { setSelected(new Set([node[rowKey]])); onSelectionChange && onSelectionChange([node[rowKey]]); };\n\n const cellValue = (node, key) => (hasChildren(node) && key === summaryColumn ? leafSum(node, key) : node[key]);\n\n return (\n
\n \n \n \n {selectable && }\n {columns.map(col => (\n \n ))}\n \n \n \n {visibleList.map(({ node, depth }) => (\n onRowClick(node)}>\n {selectable && (\n \n )}\n {columns.map(col => (\n \n ))}\n \n ))}\n \n {summaryColumn && (\n \n \n {selectable && \n {columns.slice(1).map(col => (\n \n ))}\n \n \n )}\n
\n el && (el.indeterminate = someSelected)} onChange={toggleAll} />\n {col.title}
\n e.stopPropagation()} onChange={() => toggleCheck(node)} />\n \n {col.key === columns[0].key ? (\n
\n {hasChildren(node) ? (\n { e.stopPropagation(); toggleExpand(node[rowKey]); }}>▸\n ) : }\n {node[col.key]}\n {hasChildren(node) && summaryColumn && (汇总)}\n
\n ) : cellValue(node, col.key)}\n
}\n {summary.label || '合计'}\n {col.key === summaryColumn ? totalOf(col.key) : ''}\n
\n
\n );\n}\n\n/*\n用法示例:\nimport TreeTable from './TreeTable';\nconst columns = [\n { key: 'name', title: '部门/人员' },\n { key: 'count', title: '人数', align: 'right' },\n { key: 'budget', title: '预算(万)', align: 'right' }\n];\nconst data = [{ key: 'tech', name: '技术中心', children: [{ key: 'fe', name: '前端组', count: 12, budget: 80 }] }];\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"expandabletable","name":"可展开表格 ExpandableTable","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"display","specLines":["- 表格行支持展开显示额外内容","- 展开图标位于行首,默认收起","- 展开内容可为子表格、详情描述、图表","- 支持同时展开多行或仅一行","- 展开状态可记忆","- 适用于订单明细、日志详情等"],"specSummary":"- 表格行支持展开显示额外内容","contract":null,"files":{"html":"../frameworks/ExpandableTable.html","css":"../frameworks/ExpandableTable.css","jsx":"../frameworks/ExpandableTable.jsx","vue2":"../frameworks/ExpandableTable.vue2.vue","vue3":"../frameworks/ExpandableTable.vue3.vue"},"sizes":{"html":2759,"css":1248,"jsx":1645,"vue2":1398,"vue3":1318},"sources":{"html":"\n\n\n \n \n Aurora Admin · ExpandableTable\n \n \n\n\n
\n

ExpandableTable 可展开表格

\n

行首展开图标可展开详情(子表格 / 描述),支持同时展开多行或仅一行。

\n\n
\n
订单明细(点击箭头展开)
\n \n \n \n
订单号客户金额状态
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin ExpandableTable 样式 — 行内展开详情 */\n.aa-exptable { width: 100%; border-collapse: collapse; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); font-size: 14px; background: #fff; border: 1px solid #E8ECF1; border-radius: 8px; overflow: hidden; }\n.aa-exptable th { text-align: left; padding: 12px 14px; background: #FAFAFA; color: #1F2329; font-weight: 600; border-bottom: 1px solid #E8ECF1; }\n.aa-exptable td { padding: 11px 14px; color: #262626; border-bottom: 1px solid #E8ECF1; }\n.aa-exptable tr:last-child td { border-bottom: none; }\n.aa-exptable tbody tr.aa-row:hover td { background: #F5F7FA; }\n.aa-exp-toggle { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border: none; background: none; cursor: pointer; color: #8C8C8C; transition: transform .2s, color .2s; }\n.aa-exp-toggle.is-open { transform: rotate(90deg); color: #2F54EB; }\n.aa-exp-panel { padding: 14px 18px 14px 48px; background: #FAFAFA; color: #595959; font-size: 13px; line-height: 1.7; }\n.aa-exp-panel .aa-exp-sub { margin-top: 8px; border: 1px dashed #E8ECF1; border-radius: 6px; padding: 10px; background: #fff; }\n.aa-exptable .is-muted { color: #8C8C8C; }\n","jsx":"import React, { useState } from 'react';\nimport './ExpandableTable.css';\n\nexport default function ExpandableTable({ data = [], columns = [], idKey = 'id' }) {\n const [openSet, setOpenSet] = useState({});\n const toggle = (id) => setOpenSet((s) => ({ ...s, [id]: !s[id] }));\n\n return (\n \n \n \n \n ))}\n \n \n \n {data.map((row) => (\n \n \n \n {columns.map((c) => (\n \n ))}\n \n {openSet[row[idKey]] && (\n \n \n \n )}\n \n ))}\n \n
\n {columns.map((c) => (\n {c.title}
\n toggle(row[idKey])}\n >\n ▶\n \n {row[c.key]}
\n \n
\n
备注:{row.detail.note}
\n
\n {row.detail.items.map((it, i) => (\n
· {it}
\n ))}\n
\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"mergedcelltable","name":"合并单元格表格 MergedCellTable","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"display","specLines":["- 支持按字段合并相同内容单元格","- 合并规则可配置,纵向合并为主","- 合并后单元格垂直居中","- 表头支持多级合并","- 适用于报表、统计表格"],"specSummary":"- 支持按字段合并相同内容单元格","contract":null,"files":{"html":"../frameworks/MergedCellTable.html","css":"../frameworks/MergedCellTable.css","jsx":"../frameworks/MergedCellTable.jsx","vue2":"../frameworks/MergedCellTable.vue2.vue","vue3":"../frameworks/MergedCellTable.vue3.vue"},"sizes":{"html":2063,"css":1015,"jsx":1328,"vue2":1509,"vue3":1321},"sources":{"html":"\n\n\n \n \n Aurora Admin · MergedCellTable\n \n \n\n\n
\n

MergedCellTable 合并单元格表格

\n

按字段纵向合并相同内容单元格,合并后垂直居中,适用于报表 / 统计表。

\n\n
\n
部门销售额报表(部门列合并)
\n \n \n \n
部门季度销售额(万)负责人
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin MergedCellTable 样式 — 相同内容纵向合并单元格 */\n.aa-mergetable { width: 100%; border-collapse: collapse; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); font-size: 14px; background: #fff; border: 1px solid #E8ECF1; border-radius: 8px; overflow: hidden; }\n.aa-mergetable th { text-align: left; padding: 12px 14px; background: #FAFAFA; color: #1F2329; font-weight: 600; border-bottom: 1px solid #E8ECF1; }\n.aa-mergetable td { padding: 11px 14px; color: #262626; border-bottom: 1px solid #E8ECF1; border-right: 1px solid #E8ECF1; vertical-align: middle; }\n.aa-mergetable td:last-child { border-right: none; }\n.aa-mergetable tr:last-child td { border-bottom: none; }\n.aa-mergetable .aa-merged { background: #F0F5FF; font-weight: 500; color: #1F2329; text-align: center; }\n.aa-mergetable tbody tr:hover td { background: #F5F7FA; }\n.aa-mergetable tbody tr:hover .aa-merged { background: #E6ECFF; }\n.aa-mergetable .is-muted { color: #8C8C8C; }\n","jsx":"import React from 'react';\nimport './MergedCellTable.css';\n\nexport default function MergedCellTable({ data = [], columns = [] }) {\n const merged = data.map((row) => ({ ...row, _first: {}, _span: {} }));\n columns.filter((c) => c.merge).forEach((c) => {\n let i = 0;\n while (i < merged.length) {\n let j = i;\n while (j + 1 < merged.length && merged[j + 1][c.key] === merged[i][c.key]) j++;\n const span = j - i + 1;\n for (let k = i; k <= j; k++) {\n merged[k]._first[c.key] = k === i;\n merged[k]._span[c.key] = span;\n }\n i = j + 1;\n }\n });\n\n return (\n \n \n \n {columns.map((c) => (\n \n ))}\n \n \n \n {merged.map((row, idx) => (\n \n {columns.map((c) =>\n !c.merge || row._first[c.key] ? (\n \n {row[c.key]}\n \n ) : null\n )}\n \n ))}\n \n
{c.title}
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"summaryrowtable","name":"统计行表格 SummaryRowTable","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"display","specLines":["- 表格底部或顶部显示汇总行","- 汇总类型:合计、平均值、最大值、最小值、计数","- 可配置汇总字段","- 汇总行样式加粗或浅色背景","- 支持固定汇总行"],"specSummary":"- 表格底部或顶部显示汇总行","contract":null,"files":{"html":"../frameworks/SummaryRowTable.html","css":"../frameworks/SummaryRowTable.css","jsx":"../frameworks/SummaryRowTable.jsx","vue2":"../frameworks/SummaryRowTable.vue2.vue","vue3":"../frameworks/SummaryRowTable.vue3.vue"},"sizes":{"html":2436,"css":941,"jsx":1532,"vue2":1653,"vue3":1460},"sources":{"html":"\n\n\n \n \n Aurora Admin · SummaryRowTable\n \n \n\n\n
\n

SummaryRowTable 统计汇总表格

\n

表格底部显示汇总行(合计 / 平均值 / 最大值 / 最小值 / 计数),可配置汇总字段。

\n\n
\n
销售明细与汇总
\n \n \n \n \n
产品数量单价金额
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin SummaryRowTable 样式 — 底部统计汇总行 */\n.aa-sumtable { width: 100%; border-collapse: collapse; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); font-size: 14px; background: #fff; border: 1px solid #E8ECF1; border-radius: 8px; overflow: hidden; }\n.aa-sumtable th { text-align: left; padding: 12px 14px; background: #FAFAFA; color: #1F2329; font-weight: 600; border-bottom: 1px solid #E8ECF1; }\n.aa-sumtable td { padding: 11px 14px; color: #262626; border-bottom: 1px solid #E8ECF1; }\n.aa-sumtable tr:last-child td { border-bottom: none; }\n.aa-sumtable tbody tr:hover td { background: #F5F7FA; }\n.aa-sumrow td { background: #F0F5FF; font-weight: 600; color: #1F2329; border-top: 2px solid #B7C4FF; }\n.aa-sumrow .aa-sum-label { color: #2F54EB; }\n.aa-sumtable .is-muted { color: #8C8C8C; }\n.aa-sumtable .is-pos { color: #52C41A; }\n.aa-sumtable .is-neg { color: #F5222D; }\n","jsx":"import React from 'react';\nimport './SummaryRowTable.css';\n\nfunction compute(data, key, type) {\n const arr = data.map((d) => Number(d[key]) || 0);\n if (!arr.length) return '';\n if (type === 'sum') return arr.reduce((a, b) => a + b, 0);\n if (type === 'avg') return +(arr.reduce((a, b) => a + b, 0) / arr.length).toFixed(1);\n if (type === 'max') return Math.max(...arr);\n if (type === 'min') return Math.min(...arr);\n if (type === 'count') return arr.length;\n return '';\n}\n\nexport default function SummaryRowTable({\n data = [],\n columns = [],\n summary = {},\n labelKey = 'name',\n labelText = '合计 / 平均'\n}) {\n return (\n \n \n \n {columns.map((c) => (\n \n ))}\n \n \n \n {data.map((row, i) => (\n \n {columns.map((c) => (\n \n ))}\n \n ))}\n \n \n \n {columns.map((c) => (\n \n ))}\n \n \n
{c.title}
= c.pos ? 'is-pos' : ''}>\n {row[c.key]}\n
\n {c.key === labelKey\n ? labelText\n : summary[c.key]\n ? compute(data, c.key, summary[c.key])\n : ''}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"fixedcolumntable","name":"固定列表格 FixedColumnTable","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"display","specLines":["- 表格列过多时支持左右列固定","- 固定列阴影分隔","- 横向滚动查看其他列","- 支持固定表头、固定列同时使用","- 拖拽调整列宽时固定列宽度同步","- 适用于大数据量宽表"],"specSummary":"- 表格列过多时支持左右列固定","contract":null,"files":{"html":"../frameworks/FixedColumnTable.html","css":"../frameworks/FixedColumnTable.css","jsx":"../frameworks/FixedColumnTable.jsx","vue2":"../frameworks/FixedColumnTable.vue2.vue","vue3":"../frameworks/FixedColumnTable.vue3.vue"},"sizes":{"html":2893,"css":1256,"jsx":899,"vue2":942,"vue3":890},"sources":{"html":"\n\n\n \n \n Aurora Admin · FixedColumnTable\n \n \n\n\n
\n

FixedColumnTable 固定列表格

\n

列过多时左右列固定,横向滚动查看其余列,固定列带阴影分隔,支持固定表头。

\n\n
\n
宽表(左固定「姓名」,右固定「操作」)
\n
\n \n \n \n
\n
\n
← 向左滚动可查看被固定的左列 / 右列 →
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin FixedColumnTable 样式 — 左右列固定 + 横向滚动 */\n.aa-fixtable-wrap { position: relative; max-width: 100%; overflow-x: auto; border: 1px solid #E8ECF1; border-radius: 8px; background: #fff; }\n.aa-fixtable { width: 100%; border-collapse: collapse; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); font-size: 14px; min-width: 760px; }\n.aa-fixtable th { text-align: left; padding: 12px 14px; background: #FAFAFA; color: #1F2329; font-weight: 600; border-bottom: 1px solid #E8ECF1; white-space: nowrap; }\n.aa-fixtable td { padding: 11px 14px; color: #262626; border-bottom: 1px solid #E8ECF1; white-space: nowrap; }\n.aa-fixtable tbody tr:hover td { background: #F5F7FA; }\n.aa-fixtable .aa-fixed { position: sticky; background: #fff; z-index: 1; }\n.aa-fixtable thead .aa-fixed { z-index: 3; }\n.aa-fixtable .aa-fixed-left { left: 0; box-shadow: 6px 0 8px -6px rgba(0,0,0,0.12); }\n.aa-fixtable .aa-fixed-right { right: 0; box-shadow: -6px 0 8px -6px rgba(0,0,0,0.12); }\n.aa-fixtable tbody tr:hover .aa-fixed { background: #F5F7FA; }\n.aa-fixtable .is-muted { color: #8C8C8C; }\n.aa-fixtable-wrap .aa-hint { padding: 8px 12px; font-size: 12px; color: #8C8C8C; border-top: 1px solid #E8ECF1; }\n","jsx":"import React from 'react';\nimport './FixedColumnTable.css';\n\nfunction fixCls(c) {\n return c.fixed ? 'aa-fixed aa-fixed-' + c.fixed : '';\n}\n\nexport default function FixedColumnTable({ data = [], columns = [] }) {\n return (\n
\n \n \n \n {columns.map((c) => (\n \n ))}\n \n \n \n {data.map((row, i) => (\n \n {columns.map((c) => (\n \n ))}\n \n ))}\n \n
\n {c.title}\n
\n {row[c.key]}\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"timelinelist","name":"时间轴列表 TimelineList","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"display","specLines":["- 以时间轴形式展示事件或记录","- 左侧时间线,右侧内容卡片","- 支持垂直/水平两种模式","- 节点可表示不同状态(完成、进行中、错误)","- 适用于操作日志、审批历史、动态"],"specSummary":"- 以时间轴形式展示事件或记录","contract":null,"files":{"html":"../frameworks/TimelineList.html","css":"../frameworks/TimelineList.css","jsx":"../frameworks/TimelineList.jsx","vue2":"../frameworks/TimelineList.vue2.vue","vue3":"../frameworks/TimelineList.vue3.vue"},"sizes":{"html":2550,"css":2021,"jsx":748,"vue2":799,"vue3":740},"sources":{"html":"\n\n\n \n \n Aurora Admin · TimelineList(普通 H5)\n \n \n \n\n\n
\n \n \n
\n\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin TimelineList 样式 — 对齐 组件7.txt TimelineList:左侧时间线 + 右侧内容,节点状态 完成/进行中/错误 */\n\n.aa-timeline { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-timeline-item { position: relative; }\n.aa-timeline-node {\n position: absolute; left: 5px; top: 4px; width: 10px; height: 10px; border-radius: 50%;\n background: #fff; border: 2px solid #E8ECF1; box-sizing: border-box; z-index: 1;\n}\n.aa-timeline-line { position: absolute; left: 9px; top: 16px; bottom: -10px; width: 2px; background: #E8ECF1; }\n.aa-timeline-content { padding: 0 0 20px 28px; }\n.aa-timeline-time { font-size: 12px; color: #8C8C8C; }\n.aa-timeline-title { font-size: 14px; font-weight: 500; color: #1F2329; margin-top: 2px; }\n.aa-timeline-desc { font-size: 13px; color: #8C8C8C; margin-top: 4px; line-height: 1.5; }\n\n/* 状态:完成 done / 进行中 active / 错误 error / 待处理 pending */\n.aa-timeline-item.is-done > .aa-timeline-node { background: #2F54EB; border-color: #2F54EB; }\n.aa-timeline-item.is-done > .aa-timeline-line { background: #2F54EB; }\n.aa-timeline-item.is-active > .aa-timeline-node { background: #fff; border-color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-timeline-item.is-error > .aa-timeline-node { background: #F5222D; border-color: #F5222D; }\n.aa-timeline-item.is-error > .aa-timeline-line { background: #F5222D; }\n.aa-timeline-item.is-pending > .aa-timeline-node { background: #fff; border-color: #E8ECF1; }\n\n/* 水平模式 */\n.aa-timeline.is-horizontal { display: flex; }\n.aa-timeline.is-horizontal > .aa-timeline-item { flex: 1; padding-right: 8px; }\n.aa-timeline.is-horizontal > .aa-timeline-item > .aa-timeline-node { top: 4px; }\n.aa-timeline.is-horizontal > .aa-timeline-item > .aa-timeline-line { left: 15px; top: 9px; bottom: auto; right: 0; width: auto; height: 2px; }\n.aa-timeline.is-horizontal > .aa-timeline-item > .aa-timeline-content { padding: 22px 12px 0 0; }\n","jsx":"import React from 'react';\nimport './TimelineList.css';\n\nexport default function TimelineList({ items = [], direction = 'vertical' }) {\n return (\n
\n {items.map((it, i) => (\n
\n \n \n
\n
{it.time}
\n
{it.title}
\n {it.desc ?
{it.desc}
: null}\n
\n
\n ))}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"steplist","name":"步骤列表 StepList","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"display","specLines":["- 展示多步骤流程的详细列表","- 每步骤显示序号、标题、描述、状态图标","- 步骤之间用连接线串联","- 支持点击步骤跳转详情","- 适用于配置向导、任务进度"],"specSummary":"- 展示多步骤流程的详细列表","contract":null,"files":{"html":"../frameworks/StepList.html","css":"../frameworks/StepList.css","jsx":"../frameworks/StepList.jsx","vue2":"../frameworks/StepList.vue2.vue","vue3":"../frameworks/StepList.vue3.vue"},"sizes":{"html":2638,"css":2128,"jsx":1012,"vue2":1066,"vue3":1025},"sources":{"html":"\n\n\n \n \n Aurora Admin · StepList(普通 H5)\n \n \n \n\n\n
\n \n \n
\n\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin StepList 样式 — 对齐 组件7.txt StepList:多步骤详细列表,序号/标题/描述/状态图标,连接线串联,可点击 */\n\n.aa-steplist { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-steplist-item { position: relative; display: flex; gap: 12px; padding-bottom: 20px; }\n.aa-steplist-line { position: absolute; left: 14px; top: 32px; bottom: 0; width: 2px; background: #E8ECF1; }\n.aa-steplist-item:last-child { padding-bottom: 0; }\n.aa-steplist-item:last-child .aa-steplist-line { display: none; }\n.aa-steplist-node { width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center;\n justify-content: center; font-size: 13px; font-weight: 500; border: 2px solid #E8ECF1; background: #fff; color: #8C8C8C; z-index: 1; box-sizing: border-box; }\n.aa-steplist-body { flex: 1; padding-top: 2px; }\n.aa-steplist-title { font-size: 14px; font-weight: 500; color: #1F2329; }\n.aa-steplist-desc { font-size: 13px; color: #8C8C8C; margin-top: 2px; line-height: 1.5; }\n.aa-steplist-item.is-clickable { cursor: pointer; }\n.aa-steplist-item.is-clickable:hover .aa-steplist-title { color: #2F54EB; }\n\n.aa-steplist-item.is-done .aa-steplist-node { background: #2F54EB; border-color: #2F54EB; color: #fff; }\n.aa-steplist-item.is-done .aa-steplist-line { background: #2F54EB; }\n.aa-steplist-item.is-active .aa-steplist-node { border-color: #2F54EB; color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-steplist-item.is-error .aa-steplist-node { background: #F5222D; border-color: #F5222D; color: #fff; }\n.aa-steplist-item.is-error .aa-steplist-line { background: #F5222D; }\n\n/* 水平模式 */\n.aa-steplist.is-horizontal { display: flex; }\n.aa-steplist.is-horizontal > .aa-steplist-item { flex: 1; flex-direction: column; padding-bottom: 0; padding-right: 8px; }\n.aa-steplist.is-horizontal > .aa-steplist-item > .aa-steplist-line { left: 28px; top: 14px; bottom: auto; right: 0; width: auto; height: 2px; }\n.aa-steplist.is-horizontal > .aa-steplist-item > .aa-steplist-body { padding-top: 8px; }\n","jsx":"import React from 'react';\nimport './StepList.css';\n\nfunction nodeText(it, i) {\n if (it.status === 'done') return '✓';\n if (it.status === 'error') return '✕';\n return String(i + 1);\n}\n\nexport default function StepList({ steps = [], direction = 'vertical', clickable = false, onStepClick }) {\n return (\n
\n {steps.map((it, i) => (\n onStepClick && onStepClick(i) : undefined}\n >\n {nodeText(it, i)}\n \n
\n
{it.title}
\n {it.desc ?
{it.desc}
: null}\n
\n
\n ))}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"chartpanel","name":"图表面板 ChartPanel","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"display","specLines":["- 可折叠的图表容器","- 标题栏带展开/折叠、刷新、导出按钮","- 图表区域可自定义高度","- 支持图表切换类型(折线、柱状、饼图)","- 适用于数据看板"],"specSummary":"- 可折叠的图表容器","contract":null,"files":{"html":"../frameworks/ChartPanel.html","css":"../frameworks/ChartPanel.css","jsx":"../frameworks/ChartPanel.jsx","vue2":"../frameworks/ChartPanel.vue2.vue","vue3":"../frameworks/ChartPanel.vue3.vue"},"sizes":{"html":1518,"css":1057,"jsx":960,"vue2":963,"vue3":987},"sources":{"html":"\n\n\n \n \n Aurora Admin · ChartPanel(普通 H5)\n \n \n \n\n\n
\n
\n 访问趋势\n
\n \n \n \n
\n
\n
\n
图表区域(可放置折线/柱状/饼图)
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin ChartPanel 样式 — 对齐 组件7.txt ChartPanel:可折叠图表容器,标题栏带展开/折叠、刷新、导出 */\n\n.aa-chartpanel { background: #fff; border: 1px solid #E8ECF1; border-radius: 8px; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-chartpanel-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid #E8ECF1; }\n.aa-chartpanel-title { font-size: 14px; font-weight: 500; color: #1F2329; }\n.aa-chartpanel-tools { display: flex; gap: 8px; }\n.aa-chartpanel-tool { border: none; background: none; cursor: pointer; color: #595959; font-size: 13px; padding: 4px 8px; border-radius: 4px; }\n.aa-chartpanel-tool:hover { background: #F0F5FF; color: #2F54EB; }\n.aa-chartpanel-caret { transition: transform .2s; }\n.aa-chartpanel.is-collapsed .aa-chartpanel-caret { transform: rotate(-90deg); }\n.aa-chartpanel.is-collapsed .aa-chartpanel-body { display: none; }\n.aa-chartpanel-body { padding: 16px; }\n","jsx":"import React, { useState } from 'react';\nimport './ChartPanel.css';\n\nexport default function ChartPanel({ title = '图表', collapsible = true, collapsed = false, onRefresh, onExport, children }) {\n const [isCollapsed, setIsCollapsed] = useState(collapsed);\n return (\n
\n
\n {title}\n
\n \n \n {collapsible ? (\n \n ) : null}\n
\n
\n
{children || '图表区域'}
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"dashboardcard","name":"仪表板卡片 DashboardCard","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"display","specLines":["- 用于数据看板中的卡片单元","- 支持拖拽调整大小和位置","- 可配置刷新频率","- 卡片可最大化、刷新、删除","- 布局可保存为模板"],"specSummary":"- 用于数据看板中的卡片单元","contract":null,"files":{"html":"../frameworks/DashboardCard.html","css":"../frameworks/DashboardCard.css","jsx":"../frameworks/DashboardCard.jsx","vue2":"../frameworks/DashboardCard.vue2.vue","vue3":"../frameworks/DashboardCard.vue3.vue"},"sizes":{"html":1074,"css":899,"jsx":529,"vue2":696,"vue3":648},"sources":{"html":"\n\n\n \n \n Aurora Admin · DashboardCard(普通 H5)\n \n \n \n\n\n
\n
今日订单📦
\n
1,284
\n
↑ 12.5% 较昨日
\n
\n
\n
退款率↩️
\n
2.3%
\n
↑ 0.4% 需关注
\n
\n\n\n","css":"/* Aurora Admin DashboardCard 样式 — 对齐 组件7.txt DashboardCard:数据看板卡片单元,标题/数值/趋势 */\n\n.aa-dashcard { background: #fff; border: 1px solid #E8ECF1; border-radius: 8px; padding: 16px; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-dashcard-head { display: flex; align-items: center; justify-content: space-between; }\n.aa-dashcard-title { font-size: 13px; color: #8C8C8C; }\n.aa-dashcard-icon { width: 32px; height: 32px; border-radius: 8px; background: #F0F5FF; color: #2F54EB; display: flex; align-items: center; justify-content: center; font-size: 16px; }\n.aa-dashcard-value { font-size: 28px; font-weight: 600; color: #1F2329; margin-top: 8px; }\n.aa-dashcard-trend { font-size: 12px; margin-top: 4px; }\n.aa-dashcard-trend.up { color: #52C41A; }\n.aa-dashcard-trend.down { color: #F5222D; }\n","jsx":"import React from 'react';\nimport './DashboardCard.css';\n\nexport default function DashboardCard({ title = '', value = '', icon = '📊', trend = 'up', trendText = '' }) {\n return (\n
\n
\n {title}\n {icon}\n
\n
{value}
\n
{trendText}
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"enhancedtabnav","name":"标签页导航增强 EnhancedTabNav","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"navigation","specLines":["- 支持浏览器标签式导航","- 标签可拖拽排序、右键菜单(关闭、关闭其他、关闭全部)","- 当前标签高亮,未保存状态显示圆点","- 标签可固定","- 与路由联动,点击标签切换页面"],"specSummary":"- 支持浏览器标签式导航","contract":null,"files":{"html":"../frameworks/EnhancedTabNav.html","css":"../frameworks/EnhancedTabNav.css","jsx":"../frameworks/EnhancedTabNav.jsx","vue2":"../frameworks/EnhancedTabNav.vue2.vue","vue3":"../frameworks/EnhancedTabNav.vue3.vue"},"sizes":{"html":4710,"css":1643,"jsx":3905,"vue2":3582,"vue3":3395},"sources":{"html":"\n\n\n \n \n Aurora Admin · EnhancedTabNav\n \n \n\n\n
\n

EnhancedTabNav 增强页签导航

\n

浏览器标签式导航:可拖拽排序、右键菜单(关闭 / 关闭其他 / 关闭全部),未保存状态显示圆点。

\n\n
\n
\n
\n
\n
\n

拖拽标签可排序;右键标签打开菜单;带 ● 表示未保存。

\n
\n
\n\n \n\n\n","css":"/* Aurora Admin EnhancedTabNav 样式 — 可拖拽 + 右键菜单的页签导航 */\n.aa-tabs-enh { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-tabs-enh-bar { display: flex; gap: 2px; border-bottom: 1px solid #E8ECF1; flex-wrap: wrap; }\n.aa-tabs-enh-tab { display: flex; align-items: center; gap: 6px; padding: 8px 12px; border: 1px solid #E8ECF1; border-bottom: none; border-radius: 6px 6px 0 0; background: #FAFAFA; cursor: pointer; font-size: 13px; color: #595959; user-select: none; transition: background .15s; }\n.aa-tabs-enh-tab:hover { background: #F0F5FF; }\n.aa-tabs-enh-tab.is-active { background: #fff; color: #2F54EB; }\n.aa-tabs-enh-tab.is-active::after { content: ''; position: absolute; }\n.aa-tabs-enh-tab .aa-dot { width: 6px; height: 6px; border-radius: 50%; background: #FA8C16; }\n.aa-tabs-enh-close { color: #8C8C8C; border-radius: 50%; width: 16px; height: 16px; line-height: 14px; text-align: center; font-size: 14px; }\n.aa-tabs-enh-close:hover { background: #F5F5F5; color: #F5222D; }\n.aa-tabs-enh-panel { padding: 16px 18px; border: 1px solid #E8ECF1; border-top: none; border-radius: 0 0 8px 8px; color: #595959; font-size: 14px; line-height: 1.7; }\n.aa-tabs-enh-menu { position: fixed; z-index: 1000; background: #fff; border: 1px solid #E8ECF1; border-radius: 6px; box-shadow: 0 6px 24px rgba(0,0,0,0.12); padding: 4px 0; min-width: 124px; }\n.aa-tabs-enh-menu div { padding: 7px 14px; font-size: 13px; cursor: pointer; color: #262626; }\n.aa-tabs-enh-menu div:hover { background: #F5F7FA; }\n.aa-tabs-enh-hint { font-size: 12px; color: #8C8C8C; margin-top: 10px; }\n","jsx":"import React, { useState, useRef, useEffect } from 'react';\nimport './EnhancedTabNav.css';\n\nconst INITIAL = [\n { id: 'u', title: '用户管理', dirty: false, desc: '用户列表与权限配置。' },\n { id: 'r', title: '角色管理', dirty: true, desc: '角色权限分配(有未保存修改)。' },\n { id: 'm', title: '菜单管理', dirty: false, desc: '导航菜单树形配置。' },\n { id: 'p', title: '参数配置', dirty: true, desc: '系统参数键值对管理。' },\n { id: 'l', title: '日志查询', dirty: false, desc: '操作日志检索与导出。' }\n];\n\nexport default function EnhancedTabNav() {\n const [tabs, setTabs] = useState(INITIAL);\n const [activeId, setActiveId] = useState('u');\n const [dragId, setDragId] = useState(null);\n const [menu, setMenu] = useState(null);\n const [menuId, setMenuId] = useState('');\n const root = useRef(null);\n\n useEffect(() => {\n function onDoc(e) {\n if (root.current && !root.current.contains(e.target)) setMenu(null);\n }\n document.addEventListener('click', onDoc);\n return () => document.removeEventListener('click', onDoc);\n }, []);\n\n const activeTab = tabs.filter((t) => t.id === activeId)[0] || null;\n\n const closeTab = (id) => {\n setTabs((ts) => {\n if (ts.length <= 1) return ts;\n const idx = ts.findIndex((t) => t.id === id);\n const next = ts.slice();\n next.splice(idx, 1);\n if (activeId === id) setActiveId(next[Math.max(0, idx - 1)].id);\n return next;\n });\n };\n const onTabContext = (e, id) => {\n e.preventDefault();\n setMenu({ x: e.clientX, y: e.clientY });\n setMenuId(id);\n };\n const onBarContext = (e) => {\n if (e.target.closest('.aa-tabs-enh-tab')) e.preventDefault();\n };\n const menuAct = (act) => {\n if (act === 'close') closeTab(menuId);\n else if (act === 'other') setTabs((ts) => ts.filter((t) => t.id === menuId));\n else if (act === 'all') setTabs([]);\n setActiveId(tabs.length ? menuId : '');\n setMenu(null);\n };\n const onDrop = (e) => {\n const el = e.target.closest('.aa-tabs-enh-tab');\n const id = el && el.dataset.id;\n if (!id || !dragId) return;\n setTabs((ts) => {\n const from = ts.findIndex((t) => t.id === dragId);\n const to = ts.findIndex((t) => t.id === id);\n if (from < 0 || to < 0 || from === to) return ts;\n const next = ts.slice();\n const moved = next.splice(from, 1)[0];\n next.splice(to, 0, moved);\n return next;\n });\n };\n\n return (\n
\n
e.preventDefault()} onDrop={onDrop}>\n {tabs.map((t) => (\n setActiveId(t.id)}\n onDragStart={() => setDragId(t.id)}\n onContextMenu={(e) => onTabContext(e, t.id)}\n >\n {t.dirty && }\n {t.title}\n { e.stopPropagation(); closeTab(t.id); }}>×\n
\n ))}\n
\n
{activeTab ? activeTab.title + ':' + activeTab.desc : '请选择页签'}
\n {menu && (\n
e.stopPropagation()}>\n
menuAct('close')}>关闭
\n
menuAct('other')}>关闭其他
\n
menuAct('all')}>关闭全部
\n
\n )}\n

拖拽标签可排序;右键标签打开菜单;带 ● 表示未保存。

\n \n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"sidemenu","name":"侧边栏菜单 SideMenu","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"navigation","specLines":["- 支持多级菜单、图标、徽章","- 菜单项可折叠、展开","- 选中态背景 #F0F5FF,左侧指示条","- 支持权限过滤、外链、隐藏","- 折叠模式显示图标,悬停弹出子菜单"],"specSummary":"- 支持多级菜单、图标、徽章","contract":null,"files":{"html":"../frameworks/sidemenu.html","css":"../frameworks/SideMenu.css","jsx":"../frameworks/SideMenu.jsx","vue2":"../frameworks/SideMenu.vue2.vue","vue3":"../frameworks/SideMenu.vue3.vue"},"sizes":{"html":4525,"css":2414,"jsx":3776,"vue2":5081,"vue3":5183},"sources":{"html":"\n\n\n\n\nAurora Admin · SideMenu (H5)\n\n\n\n\n\n
\n
\n

SideMenu

\n

多级、图标、徽章、选中态+左指示条、折叠

\n
⇥ 折叠 / 展开
\n
\n
\n
\n\n\n\n\n","css":"/* Aurora Admin SideMenu 样式 — 对齐 组件7.txt SideMenu:多级、图标、徽章、选中态#F0F5FF+左指示条、折叠 */\n\n.aa-sidemenu { width: 220px; background: #fff; border-right: 1px solid #E8ECF1; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; user-select: none; }\n.aa-sidemenu.collapsed { width: 56px; }\n\n.aa-menu-item { position: relative; display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 16px; cursor: pointer; font-size: 14px; color: #262626; transition: background .15s, color .15s; }\n.aa-menu-item:hover { background: #F5F7FA; }\n.aa-menu-item.is-active { background: #F0F5FF; color: #2F54EB; font-weight: 500; }\n.aa-menu-item.is-active::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: #2F54EB; }\n.aa-menu-item.is-disabled { color: #BFBFBF; cursor: not-allowed; }\n.aa-menu-item.is-disabled:hover { background: none; }\n\n.aa-menu-icon { width: 16px; text-align: center; flex-shrink: 0; font-size: 15px; }\n.aa-menu-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.aa-menu-badge { background: #F5222D; color: #fff; font-size: 11px; min-width: 16px; height: 16px; border-radius: 8px; padding: 0 5px; display: inline-flex; align-items: center; justify-content: center; }\n.aa-menu-arrow { color: #8C8C8C; font-size: 12px; transition: transform .15s; }\n.aa-menu-item.is-open > .aa-menu-arrow { transform: rotate(90deg); }\n\n.aa-menu-children { background: #FAFAFA; }\n.aa-menu-children .aa-menu-item { height: 40px; padding-left: 40px; font-size: 13px; }\n.aa-menu-children .aa-menu-item.is-active::before { display: none; }\n\n/* 折叠模式 */\n.aa-sidemenu.collapsed .aa-menu-label,\n.aa-sidemenu.collapsed .aa-menu-badge,\n.aa-sidemenu.collapsed .aa-menu-arrow { display: none; }\n.aa-sidemenu.collapsed .aa-menu-item { justify-content: center; padding: 0; }\n.aa-sidemenu.collapsed .aa-menu-children { display: none; }\n\n/* 折叠态悬浮浮层 */\n.aa-menu-pop { position: absolute; left: 56px; top: 0; min-width: 180px; background: #fff; border: 1px solid #E8ECF1; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.12); padding: 4px 0; z-index: 200; }\n.aa-menu-pop-title { padding: 6px 16px; font-size: 12px; color: #8C8C8C; }\n.aa-menu-pop .aa-menu-item { padding: 0 16px; }\n.aa-menu-pop .aa-menu-item::before { display: none; }\n","jsx":"import React, { useState } from 'react';\nimport './SideMenu.css';\n\n/**\n * Aurora Admin SideMenu(React)\n * 对齐 组件7.txt SideMenu 规范:多级、图标、徽章、选中态#F0F5FF+左指示条、折叠悬浮\n *\n * props:\n * - value / defaultValue : 当前选中 key\n * - items : [{ key, label, icon?, badge?, children?, disabled? }]\n * - collapsed\n * - onSelect(key)\n */\nexport default function SideMenu({ value, defaultValue = '', items = [], collapsed = false, onSelect }) {\n const [active, setActive] = useState(value !== undefined ? value : defaultValue);\n const [openKeys, setOpenKeys] = useState([]);\n const [hoverKey, setHoverKey] = useState('');\n const [popTop, setPopTop] = useState(0);\n const current = value !== undefined ? value : active;\n\n const select = (key) => { if (value === undefined) setActive(key); onSelect && onSelect(key); setHoverKey(''); };\n const onTopClick = (it) => {\n if (it.disabled) return;\n if (it.children && it.children.length) {\n if (collapsed) return;\n setOpenKeys(openKeys.includes(it.key) ? openKeys.filter(k => k !== it.key) : [...openKeys, it.key]);\n } else select(it.key);\n };\n const onHover = (it, e) => { if (collapsed && it.children) { setHoverKey(it.key); setPopTop(e.currentTarget.offsetTop); } };\n const popItem = items.find(x => x.key === hoverKey) || null;\n\n return (\n
setHoverKey('')}>\n {items.map(it => {\n const hasChild = it.children && it.children.length;\n const isOpen = openKeys.includes(it.key);\n return (\n
\n onTopClick(it)}\n onMouseEnter={(e) => onHover(it, e)}\n >\n {it.icon}\n {it.label}\n {it.badge && {it.badge}}\n {hasChild && ›}\n
\n {hasChild && isOpen && !collapsed && (\n
\n {it.children.map(c => (\n
select(c.key)}>\n {c.label}\n
\n ))}\n
\n )}\n
\n );\n })}\n\n {collapsed && hoverKey && popItem && (\n
\n
{popItem.label}
\n {popItem.children && popItem.children.length ? (\n popItem.children.map(c => (\n
select(c.key)}>\n {c.label}\n
\n ))\n ) : (\n
select(popItem.key)}>\n {popItem.label}\n
\n )}\n
\n )}\n \n );\n}\n\n/*\n用法示例:\nimport SideMenu from './SideMenu';\nconst items = [\n { key: 'dashboard', label: '仪表盘', icon: '▦' },\n { key: 'user', label: '用户管理', icon: '◍', badge: 8, children: [{ key: 'list', label: '用户列表' }] }\n];\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"topmenu","name":"顶部菜单 TopMenu","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"navigation","specLines":["- 水平导航菜单","- 支持多级下拉","- 选中态底部 2px 品牌色指示条","- 菜单项可带图标、徽章","- 适用于顶部导航布局"],"specSummary":"- 水平导航菜单","contract":null,"files":{"html":"../frameworks/TopMenu.html","css":"../frameworks/TopMenu.css","jsx":"../frameworks/TopMenu.jsx","vue2":"../frameworks/TopMenu.vue2.vue","vue3":"../frameworks/TopMenu.vue3.vue"},"sizes":{"html":2194,"css":1497,"jsx":1360,"vue2":1190,"vue3":1118},"sources":{"html":"\n\n\n \n \n Aurora Admin · TopMenu(普通 H5)\n \n \n\n\n
\n\n \n\n\n","css":"/* Aurora Admin TopMenu 样式 — 对齐 组件7.txt TopMenu:水平导航,选中态底部 2px 品牌色指示条,支持多级下拉 */\n\n.aa-topmenu { display: flex; background: #fff; border-bottom: 1px solid #E8ECF1; padding: 0 8px;\n font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-topmenu-item { position: relative; display: flex; align-items: center; gap: 6px; height: 48px;\n padding: 0 16px; cursor: pointer; color: #262626; font-size: 14px; white-space: nowrap; }\n.aa-topmenu-item:hover { color: #2F54EB; }\n.aa-topmenu-item.is-active { color: #2F54EB; font-weight: 500; }\n.aa-topmenu-item.is-active::after { content: ''; position: absolute; left: 12px; right: 12px; bottom: 0; height: 2px; background: #2F54EB; }\n.aa-topmenu-badge { background: #F5222D; color: #fff; font-size: 11px; line-height: 1; border-radius: 8px; padding: 2px 5px; }\n.aa-topmenu-caret { font-size: 10px; color: #8C8C8C; }\n.aa-topmenu-dropdown { position: absolute; top: 48px; left: 0; min-width: 160px; background: #fff;\n border: 1px solid #E8ECF1; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.08); padding: 4px; display: none; z-index: 50; }\n.aa-topmenu-item:hover > .aa-topmenu-dropdown { display: block; }\n.aa-topmenu-sub { display: block; padding: 8px 12px; font-size: 13px; color: #262626; border-radius: 4px; }\n.aa-topmenu-sub:hover { background: #F0F5FF; color: #2F54EB; }\n.aa-topmenu-sub.is-active { color: #2F54EB; background: #F0F5FF; }\n","jsx":"import React, { useState } from 'react';\nimport './TopMenu.css';\n\nexport default function TopMenu({ items = [], activeKey = '', onChange }) {\n const [active, setActive] = useState(activeKey);\n\n function isActive(it) {\n if (it.key === active) return true;\n return (it.children || []).some(s => s.key === active);\n }\n function select(it) {\n if (!it.children) {\n setActive(it.key);\n onChange && onChange(it.key);\n }\n }\n\n return (\n
\n {items.map((it, i) => (\n select(it)}\n >\n {it.label}\n {it.badge ? {it.badge} : null}\n {it.children ? ▾ : null}\n {it.children ? (\n
\n {it.children.map((s, si) => (\n { e.stopPropagation(); select(s); }}\n >\n {s.label}\n \n ))}\n
\n ) : null}\n
\n ))}\n \n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"mixednavigation","name":"混合导航 MixedNavigation","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"navigation","specLines":["- 顶部一级菜单 + 侧边栏二级菜单","- 顶部菜单切换时侧边栏内容变化","- 支持权限过滤","- 菜单状态记忆","- 适用于复杂后台"],"specSummary":"- 顶部一级菜单 + 侧边栏二级菜单","contract":null,"files":{"html":"../frameworks/MixedNavigation.html","css":"../frameworks/MixedNavigation.css","jsx":"../frameworks/MixedNavigation.jsx","vue2":"../frameworks/MixedNavigation.vue2.vue","vue3":"../frameworks/MixedNavigation.vue3.vue"},"sizes":{"html":2720,"css":1353,"jsx":1829,"vue2":1716,"vue3":1672},"sources":{"html":"\n\n\n \n \n Aurora Admin · MixedNavigation\n \n \n\n\n
\n

MixedNavigation 混合导航

\n

顶部一级菜单 + 侧边栏二级菜单,切换顶部项时侧边栏内容联动变化。

\n\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin MixedNavigation 样式 — 顶部一级 + 侧边二级混合导航 */\n.aa-mixed { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); border: 1px solid #E8ECF1; border-radius: 8px; overflow: hidden; background: #fff; }\n.aa-mixed-top { display: flex; gap: 4px; background: #fff; border-bottom: 1px solid #E8ECF1; padding: 0 8px; }\n.aa-mixed-topitem { padding: 12px 16px; cursor: pointer; color: #262626; font-size: 14px; border-bottom: 2px solid transparent; transition: color .15s, border-color .15s; }\n.aa-mixed-topitem:hover { color: #2F54EB; }\n.aa-mixed-topitem.is-active { color: #2F54EB; border-bottom-color: #2F54EB; font-weight: 600; }\n.aa-mixed-body { display: flex; min-height: 240px; }\n.aa-mixed-side { width: 168px; background: #FAFAFA; border-right: 1px solid #E8ECF1; padding: 8px 0; }\n.aa-mixed-sideitem { padding: 10px 16px; cursor: pointer; color: #262626; font-size: 14px; border-left: 3px solid transparent; transition: background .15s, color .15s; }\n.aa-mixed-sideitem:hover { background: #F5F7FA; }\n.aa-mixed-sideitem.is-active { background: #F0F5FF; color: #2F54EB; border-left-color: #2F54EB; font-weight: 500; }\n.aa-mixed-content { flex: 1; padding: 18px 22px; color: #595959; font-size: 14px; line-height: 1.7; }\n.aa-mixed-content h4 { margin: 0 0 6px; color: #1F2329; }\n","jsx":"import React, { useState } from 'react';\nimport './MixedNavigation.css';\n\nconst DEFAULT_MENU = [\n { name: '工作台', items: [{ name: '概览', desc: '数据看板总览' }, { name: '待办', desc: '我的待处理任务' }, { name: '报表', desc: '统计报表入口' }] },\n { name: '系统管理', items: [{ name: '用户管理', desc: '用户增删改查' }, { name: '角色管理', desc: '角色与权限分配' }, { name: '菜单管理', desc: '导航菜单配置' }] },\n { name: '业务中心', items: [{ name: '订单', desc: '订单列表与详情' }, { name: '商品', desc: '商品上下架' }, { name: '库存', desc: '库存预警' }] }\n];\n\nexport default function MixedNavigation({ menu = DEFAULT_MENU }) {\n const [topIdx, setTopIdx] = useState(0);\n const [sideIdx, setSideIdx] = useState(0);\n const cur = menu[topIdx];\n\n return (\n
\n
\n {menu.map((m, i) => (\n {\n setTopIdx(i);\n setSideIdx(0);\n }}\n >\n {m.name}\n
\n ))}\n
\n
\n
\n {cur.items.map((it, i) => (\n setSideIdx(i)}\n >\n {it.name}\n
\n ))}\n
\n
\n

\n {cur.name} / {cur.items[sideIdx].name}\n

\n
{cur.items[sideIdx].desc}
\n
\n \n \n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"quicknav","name":"快捷导航 QuickNav","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"navigation","specLines":["- 常用页面快捷入口","- 可配置为图标网格、列表或下拉","- 支持拖拽排序","- 点击跳转","- 显示在工作台或侧边栏顶部"],"specSummary":"- 常用页面快捷入口","contract":null,"files":{"html":"../frameworks/QuickNav.html","css":"../frameworks/QuickNav.css","jsx":"../frameworks/QuickNav.jsx","vue2":"../frameworks/QuickNav.vue2.vue","vue3":"../frameworks/QuickNav.vue3.vue"},"sizes":{"html":1629,"css":768,"jsx":833,"vue2":892,"vue3":884},"sources":{"html":"\n\n\n \n \n Aurora Admin · QuickNav\n \n \n\n\n
\n

QuickNav 快捷导航

\n

常用页面快捷入口,图标网格,点击跳转。

\n\n
\n
工作台快捷入口
\n
\n

点击任意入口试试

\n
\n
\n\n \n\n\n","css":"/* Aurora Admin QuickNav 样式 — 常用页面快捷入口 */\n.aa-quicknav { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); display: flex; flex-wrap: wrap; gap: 6px; }\n.aa-quicknav-item { width: 92px; display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 14px 6px; border-radius: 8px; cursor: pointer; transition: background .15s; }\n.aa-quicknav-item:hover { background: #F0F5FF; }\n.aa-quicknav-icon { width: 42px; height: 42px; border-radius: 12px; background: #F0F5FF; color: #2F54EB; display: flex; align-items: center; justify-content: center; font-size: 20px; }\n.aa-quicknav-label { font-size: 13px; color: #262626; }\n.aa-quicknav-empty { color: #8C8C8C; font-size: 13px; padding: 12px; }\n","jsx":"import React from 'react';\nimport './QuickNav.css';\n\nconst DEFAULT_ITEMS = [\n { icon: '📊', label: '数据看板' },\n { icon: '👥', label: '用户管理' },\n { icon: '📦', label: '订单管理' },\n { icon: '⚙️', label: '系统设置' },\n { icon: '📝', label: '审批中心' },\n { icon: '🔔', label: '消息通知' },\n { icon: '📁', label: '文件中心' },\n { icon: '🛡️', label: '权限管理' }\n];\n\nexport default function QuickNav({ items = DEFAULT_ITEMS, onSelect }) {\n return (\n
\n {items.map((it, i) => (\n
onSelect && onSelect(it)}>\n
{it.icon}
\n
{it.label}
\n
\n ))}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"anchornav","name":"锚点导航 AnchorNav","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"navigation","specLines":["- 长页面右侧固定目录","- 滚动时高亮当前章节","- 点击平滑滚动到对应位置","- 支持层级嵌套","- 适用于帮助文档、长表单"],"specSummary":"- 长页面右侧固定目录","contract":null,"files":{"html":"../frameworks/anchornav.html","css":"../frameworks/AnchorNav.css","jsx":"../frameworks/AnchorNav.jsx","vue2":"../frameworks/AnchorNav.vue2.vue","vue3":"../frameworks/AnchorNav.vue3.vue"},"sizes":{"html":3631,"css":935,"jsx":2105,"vue2":2558,"vue3":2530},"sources":{"html":"\n\n\n\n\nAurora Admin · AnchorNav (H5)\n\n\n\n\n\n
\n

Aurora Admin · AnchorNav 组件(纯 H5)

\n

对齐 AnchorNav 规范:右侧目录、滚动高亮、层级嵌套、点击平滑滚动

\n\n \n\n

1. 产品简介

本系统是一套企业级中后台解决方案……(长文本占位,可滚动测试高亮)

更多内容用于撑高页面,方便观察当前章节高亮效果。

\n

2. 安装与初始化

通过 npm 安装后,在入口文件引入主题与组件库。

配置项说明略。

\n
\n

3. 开发指南

\n

包含布局、主题与国际化三部分。

\n

3.1 布局

侧边栏 + 顶栏的经典后台布局。

\n

3.2 主题

通过 CSS 变量统一调整品牌色。

\n
\n

4. 常见问题

收集部署与升级中的高频问题。

FAQ 列表略。

\n
\n\n\n\n\n","css":"/* Aurora Admin AnchorNav 样式 — 对齐 组件7.txt AnchorNav:右侧目录、滚动高亮、层级嵌套 */\n\n.aa-anchor { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); width: 160px; }\n.aa-anchor.fixed { position: fixed; right: 24px; top: 80px; }\n\n.aa-anchor-item {\n display: block; padding: 6px 12px; font-size: 13px; color: #8C8C8C; cursor: pointer;\n border-left: 2px solid #E8ECF1; line-height: 1.4; transition: color .15s, border-color .15s; background: none; border-top: none; border-right: none; border-bottom: none; text-align: left; width: 100%;\n}\n.aa-anchor-item:hover { color: #2F54EB; }\n.aa-anchor-item.is-active { color: #2F54EB; border-left-color: #2F54EB; font-weight: 500; }\n\n.aa-anchor-sub { padding-left: 8px; }\n.aa-anchor-sub .aa-anchor-item { border-left: none; padding-left: 20px; }\n.aa-anchor-sub .aa-anchor-item.is-active { border-left: 2px solid #2F54EB; }\n","jsx":"import React, { useState, useEffect } from 'react';\nimport './AnchorNav.css';\n\n/**\n * Aurora Admin AnchorNav(React)\n * 对齐 组件7.txt AnchorNav 规范:右侧目录、滚动高亮、层级嵌套、点击平滑滚动\n *\n * props:\n * - items : [{ key, label, children? }](key 对应页面元素 id)\n * - fixed\n * - offset : 滚动偏移(避开吸顶高度)\n */\nexport default function AnchorNav({ items = [], fixed = false, offset = 24 }) {\n const allKeys = [];\n (function collect(list) { list.forEach(i => { allKeys.push(i.key); if (i.children) collect(i.children); }); })(items);\n const [active, setActive] = useState(items[0] ? items[0].key : '');\n\n const go = (key) => {\n const el = document.getElementById(key);\n if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - offset, behavior: 'smooth' });\n };\n useEffect(() => {\n const onScroll = () => {\n const pos = window.scrollY + 80;\n let cur = allKeys[0];\n for (const k of allKeys) {\n const el = document.getElementById(k);\n if (el && el.offsetTop <= pos) cur = k;\n }\n setActive(cur);\n };\n window.addEventListener('scroll', onScroll);\n return () => window.removeEventListener('scroll', onScroll);\n }, []);\n\n return (\n \n );\n}\n\n/*\n用法示例:\nimport AnchorNav from './AnchorNav';\nconst items = [{ key: 'intro', label: '简介' }, { key: 'guide', label: '指南', children: [{ key: 'layout', label: '布局' }] }];\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"backtotop","name":"返回顶部 BackToTop","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"system","specLines":["- 页面滚动超过 400px 时出现","- 右下角悬浮按钮","- 点击平滑滚动回顶部","- 可带进度环显示滚动进度","- 支持自定义位置、样式"],"specSummary":"- 页面滚动超过 400px 时出现","contract":null,"files":{"html":"../frameworks/BackToTop.html","css":"../frameworks/BackToTop.css","jsx":"../frameworks/BackToTop.jsx","vue2":"../frameworks/BackToTop.vue2.vue","vue3":"../frameworks/BackToTop.vue3.vue"},"sizes":{"html":2249,"css":1099,"jsx":1608,"vue2":1603,"vue3":1495},"sources":{"html":"\n\n\n \n \n Aurora Admin · BackToTop(普通 H5)\n \n \n \n\n\n
\n

向下滚动页面

\n

滚动超过 400px 后右下角出现返回顶部按钮,环显示滚动进度,点击平滑回到顶部。

\n
\n
这里是长内容占位区……
\n\n
\n \n \n \n \n ↑\n
\n\n \n\n\n","css":"/* Aurora Admin BackToTop 样式 — 对齐 组件7.txt BackToTop:滚动超阈值出现,右下角悬浮,带进度环 */\n\n.aa-backtop {\n position: fixed; right: 32px; bottom: 32px; width: 44px; height: 44px;\n border-radius: 50%; background: #fff; border: 1px solid #E8ECF1;\n box-shadow: 0 4px 12px rgba(0,0,0,0.12); cursor: pointer;\n display: flex; align-items: center; justify-content: center; z-index: 1000;\n opacity: 0; transform: translateY(12px); pointer-events: none;\n transition: opacity .2s, transform .2s, background .15s;\n}\n.aa-backtop.is-visible { opacity: 1; transform: translateY(0); pointer-events: auto; }\n.aa-backtop:hover { background: #F0F5FF; }\n.aa-backtop svg { position: absolute; top: 0; left: 0; width: 44px; height: 44px; transform: rotate(-90deg); }\n.aa-backtop-ring-bg { stroke: #E8ECF1; }\n.aa-backtop-ring-fg { stroke: #2F54EB; transition: stroke-dashoffset .1s linear; }\n.aa-backtop-arrow { color: #2F54EB; font-size: 18px; line-height: 1; z-index: 1; }\n.aa-backtop.aa-no-ring .aa-backtop-ring-fg,\n.aa-backtop.aa-no-ring .aa-backtop-ring-bg { display: none; }\n","jsx":"import React, { useEffect, useState } from 'react';\nimport './BackToTop.css';\n\nexport default function BackToTop({ threshold = 400, showRing = true }) {\n const [visible, setVisible] = useState(false);\n const [progress, setProgress] = useState(0);\n const r = 20;\n const circumference = 2 * Math.PI * r;\n const offset = circumference * (1 - progress);\n\n useEffect(() => {\n function onScroll() {\n const st = window.pageYOffset || document.documentElement.scrollTop;\n const max = document.documentElement.scrollHeight - window.innerHeight;\n setProgress(max > 0 ? st / max : 0);\n setVisible(st > threshold);\n }\n window.addEventListener('scroll', onScroll, { passive: true });\n onScroll();\n return () => window.removeEventListener('scroll', onScroll);\n }, [threshold]);\n\n function toTop() {\n window.scrollTo({ top: 0, behavior: 'smooth' });\n }\n\n return (\n \n {showRing ? (\n \n \n \n \n ) : null}\n ↑\n \n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"pagetransition","name":"页面跳转 PageTransition","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"system","specLines":["- 路由切换动画","- 可选淡入、滑动、缩放","- 动画时长 0.3-0.5s","- 支持前进/后退方向感知","- 减少页面跳动,提升体验"],"specSummary":"- 路由切换动画","contract":null,"files":{"html":"../frameworks/PageTransition.html","css":"../frameworks/PageTransition.css","jsx":"../frameworks/PageTransition.jsx","vue2":"../frameworks/PageTransition.vue2.vue","vue3":"../frameworks/PageTransition.vue3.vue"},"sizes":{"html":1482,"css":928,"jsx":793,"vue2":737,"vue3":709},"sources":{"html":"\n\n\n \n \n Aurora Admin · PageTransition\n \n \n\n\n
\n

PageTransition 页面切换动画

\n

路由切换动画:淡入 / 滑动 / 缩放,时长约 0.35s。点击按钮预览动画。

\n\n
\n
\n \n \n \n
\n
当前页面内容
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin PageTransition 样式 — 路由切换动画 */\n.aa-pt { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-pt-bar { display: flex; gap: 8px; margin-bottom: 12px; }\n.aa-pt-bar .aa-btn { font-size: 13px; padding: 6px 12px; }\n.aa-pt-stage { border: 1px solid #E8ECF1; border-radius: 8px; padding: 28px; text-align: center; color: #595959; min-height: 96px; display: flex; align-items: center; justify-content: center; font-size: 15px; background: #fff; }\n.aa-pt-fade { animation: aaFade .35s ease; }\n.aa-pt-slide { animation: aaSlide .35s ease; }\n.aa-pt-zoom { animation: aaZoom .35s ease; }\n@keyframes aaFade { from { opacity: 0; } to { opacity: 1; } }\n@keyframes aaSlide { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }\n@keyframes aaZoom { from { opacity: 0; transform: scale(.92); } to { opacity: 1; transform: none; } }\n","jsx":"import React, { useState } from 'react';\nimport './PageTransition.css';\n\nexport default function PageTransition({ text = '当前页面内容' }) {\n const [animClass, setAnimClass] = useState('aa-pt-fade');\n const [token, setToken] = useState(0);\n\n const trigger = (type) => {\n setAnimClass('aa-pt-' + type);\n setToken((t) => t + 1);\n };\n\n return (\n
\n
\n \n \n \n
\n
\n {text}\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"buttongroup","name":"按钮组 ButtonGroup","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"general","specLines":["- 多个按钮组合","- 可选中(单选/多选)、可分隔","- 按钮之间无缝连接,仅首尾有圆角","- 支持尺寸统一、禁用","- 适用于视图切换、筛选条件"],"specSummary":"- 多个按钮组合","contract":null,"files":{"html":"../frameworks/ButtonGroup.html","css":"../frameworks/ButtonGroup.css","jsx":"../frameworks/ButtonGroup.jsx","vue2":"../frameworks/ButtonGroup.vue2.vue","vue3":"../frameworks/ButtonGroup.vue3.vue"},"sizes":{"html":1802,"css":1179,"jsx":1062,"vue2":1260,"vue3":1210},"sources":{"html":"\n\n\n \n \n Aurora Admin · ButtonGroup(普通 H5)\n \n \n \n\n\n
\n
\n\n \n\n\n","css":"/* Aurora Admin ButtonGroup 样式 — 对齐 组件7.txt ButtonGroup:组合按钮无缝连接,仅首尾圆角,可选中(单选/多选) */\n\n.aa-btngroup { display: inline-flex; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-btngroup-btn {\n border: 1px solid #E8ECF1; border-left-width: 0; background: #fff; color: #262626;\n padding: 0 16px; height: 32px; font-size: 14px; cursor: pointer; transition: background .15s, color .15s;\n}\n.aa-btngroup-btn:first-child { border-left-width: 1px; border-radius: 6px 0 0 6px; }\n.aa-btngroup-btn:last-child { border-radius: 0 6px 6px 0; }\n.aa-btngroup-btn:hover:not(.is-disabled):not(.is-selected) { color: #2F54EB; background: #F0F5FF; }\n.aa-btngroup-btn.is-disabled { color: #BFBFBF; cursor: not-allowed; background: #F5F5F5; }\n.aa-btngroup-btn.is-selected { background: #2F54EB; border-color: #2F54EB; color: #fff; }\n.aa-btngroup-btn.is-selected + .aa-btngroup-btn { border-left-color: #2F54EB; }\n.aa-btngroup.is-sm .aa-btngroup-btn { height: 24px; padding: 0 10px; font-size: 13px; }\n.aa-btngroup.is-lg .aa-btngroup-btn { height: 40px; padding: 0 20px; font-size: 15px; }\n","jsx":"import React from 'react';\nimport './ButtonGroup.css';\n\nexport default function ButtonGroup({ options = [], value, multiple = false, size = 'default', onChange }) {\n function isSelected(v) {\n return multiple ? (value || []).indexOf(v) >= 0 : value === v;\n }\n function toggle(o) {\n if (o.disabled) return;\n let next;\n if (multiple) {\n const arr = (value || []).slice();\n const idx = arr.indexOf(o.value);\n if (idx >= 0) arr.splice(idx, 1); else arr.push(o.value);\n next = arr;\n } else {\n next = o.value;\n }\n onChange && onChange(next);\n }\n\n const sizeCls = size === 'small' ? ' is-sm' : size === 'large' ? ' is-lg' : '';\n return (\n
\n {options.map((o, i) => (\n toggle(o)}\n >\n {o.label}\n \n ))}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"radiocard","name":"单选卡片 RadioCard","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":"components/radiocard.json","category":"input","specLines":["- 以卡片形式展示单选选项","- 选中态边框品牌色,背景 #F0F5FF","- 卡片内可包含图标、标题、描述","- 支持多列布局","- 适用于套餐选择、模板选择"],"specSummary":"- 以卡片形式展示单选选项","contract":{"variants":["套餐选择卡片(选中态)","模板选择卡片"],"dims":[{"name":"content","values":["icon-title-desc","title-desc","title"]},{"name":"layout","values":["multi-column"]},{"name":"state","values":["default","selected","disabled"]}],"unknowns":["禁用态样式细节"],"anatomy":{"card":"卡片容器,边框分隔","selected":"选中态边框品牌色,背景 #F0F5FF","inner":"卡片内可包含图标、标题、描述"},"doNot":["卡片固定尺寸与栅格数规范"],"usage":["以卡片形式展示单选选项","选中态边框品牌色,背景 #F0F5FF","卡片内可包含图标、标题、描述,支持多列布局","适用于套餐选择、模板选择"],"structure":{"layout":"支持多列布局"}},"files":{"html":"../frameworks/RadioCard.html","css":"../frameworks/RadioCard.css","jsx":"../frameworks/RadioCard.jsx","vue2":"../frameworks/RadioCard.vue2.vue","vue3":"../frameworks/RadioCard.vue3.vue"},"sizes":{"html":1719,"css":1146,"jsx":827,"vue2":865,"vue3":860},"sources":{"html":"\n\n\n \n \n Aurora Admin · RadioCard(普通 H5)\n \n \n \n\n\n
\n\n \n\n\n","css":"/* Aurora Admin RadioCard 样式 — 对齐 组件7.txt RadioCard:卡片单选,选中态边框品牌色 + 背景 #F0F5FF */\n\n.aa-radiocard-grid { display: grid; gap: 12px; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-radiocard { display: flex; gap: 12px; align-items: flex-start; border: 1px solid #E8ECF1; border-radius: 8px;\n padding: 16px; background: #fff; cursor: pointer; transition: border-color .15s, background .15s; }\n.aa-radiocard:hover { border-color: #C9D4F2; }\n.aa-radiocard.is-selected { border-color: #2F54EB; background: #F0F5FF; }\n.aa-radiocard-icon { font-size: 24px; line-height: 1.4; }\n.aa-radiocard-body { flex: 1; }\n.aa-radiocard-title { font-size: 14px; font-weight: 500; color: #1F2329; }\n.aa-radiocard-desc { font-size: 13px; color: #8C8C8C; margin-top: 4px; line-height: 1.5; }\n.aa-radiocard-mark { width: 16px; height: 16px; border-radius: 50%; border: 2px solid #E8ECF1; margin-top: 2px; box-sizing: border-box; flex-shrink: 0; }\n.aa-radiocard.is-selected .aa-radiocard-mark { border-color: #2F54EB; background: #2F54EB; box-shadow: inset 0 0 0 3px #fff; }\n","jsx":"import React from 'react';\nimport './RadioCard.css';\n\nexport default function RadioCard({ options = [], value, cols = 3, onChange }) {\n return (\n
\n {options.map((o, i) => (\n onChange && onChange(o.value)}\n >\n {o.icon ?
{o.icon}
: null}\n
\n
{o.label}
\n {o.desc ?
{o.desc}
: null}\n
\n
\n
\n ))}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"checkboxcard","name":"复选卡片 CheckboxCard","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":"components/checkboxcard.json","category":"input","specLines":["- 以卡片形式展示多选选项","- 选中态同单选卡片","- 支持卡片内操作按钮","- 适用于批量选择场景"],"specSummary":"- 以卡片形式展示多选选项","contract":{"variants":["批量选择卡片(选中态)","基础复选卡片"],"dims":[{"name":"selection","values":["multi"]},{"name":"innerActions","values":["support","none"]},{"name":"state","values":["default","selected","disabled"]}],"unknowns":["卡片内操作按钮的具体形态"],"anatomy":{"card":"卡片容器,样式与单选卡片一致","selected":"选中态同单选卡片(边框品牌色,背景 #F0F5FF)","actions":"可选的卡片内操作按钮"},"doNot":["全选联动规则"],"usage":["以卡片形式展示多选选项","选中态同单选卡片","支持卡片内操作按钮","适用于批量选择场景"],"structure":{"selection":"多选"}},"files":{"html":"../frameworks/CheckboxCard.html","css":"../frameworks/CheckboxCard.css","jsx":"../frameworks/CheckboxCard.jsx","vue2":"../frameworks/CheckboxCard.vue2.vue","vue3":"../frameworks/CheckboxCard.vue3.vue"},"sizes":{"html":1843,"css":1259,"jsx":1011,"vue2":1095,"vue3":1057},"sources":{"html":"\n\n\n \n \n Aurora Admin · CheckboxCard(普通 H5)\n \n \n \n\n\n
\n\n \n\n\n","css":"/* Aurora Admin CheckboxCard 样式 — 对齐 组件7.txt CheckboxCard:卡片多选,选中态同单选卡片,方框勾选 */\n\n.aa-checkcard-grid { display: grid; gap: 12px; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-checkcard { display: flex; gap: 12px; align-items: flex-start; border: 1px solid #E8ECF1; border-radius: 8px;\n padding: 16px; background: #fff; cursor: pointer; transition: border-color .15s, background .15s; }\n.aa-checkcard:hover { border-color: #C9D4F2; }\n.aa-checkcard.is-selected { border-color: #2F54EB; background: #F0F5FF; }\n.aa-checkcard-icon { font-size: 24px; line-height: 1.4; }\n.aa-checkcard-body { flex: 1; }\n.aa-checkcard-title { font-size: 14px; font-weight: 500; color: #1F2329; }\n.aa-checkcard-desc { font-size: 13px; color: #8C8C8C; margin-top: 4px; line-height: 1.5; }\n.aa-checkcard-mark { width: 16px; height: 16px; border-radius: 4px; border: 2px solid #E8ECF1; margin-top: 2px;\n box-sizing: border-box; flex-shrink: 0; color: #fff; font-size: 11px; line-height: 12px; text-align: center; }\n.aa-checkcard.is-selected .aa-checkcard-mark { border-color: #2F54EB; background: #2F54EB; }\n.aa-checkcard.is-selected .aa-checkcard-mark::after { content: '✓'; }\n","jsx":"import React from 'react';\nimport './CheckboxCard.css';\n\nexport default function CheckboxCard({ options = [], value = [], cols = 3, onChange }) {\n function toggle(v) {\n const arr = value.slice();\n const idx = arr.indexOf(v);\n if (idx >= 0) arr.splice(idx, 1); else arr.push(v);\n onChange && onChange(arr);\n }\n return (\n
\n {options.map((o, i) => (\n = 0 ? ' is-selected' : '')}\n onClick={() => toggle(o.value)}\n >\n {o.icon ?
{o.icon}
: null}\n
\n
{o.label}
\n {o.desc ?
{o.desc}
: null}\n
\n
\n
\n ))}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"listpicker","name":"列表选择器 ListPicker","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"input","specLines":["- 下拉面板中显示列表,可多选","- 支持分组、搜索、全选","- 选中项显示为标签,可删除","- 适用于标签选择、分类选择"],"specSummary":"- 下拉面板中显示列表,可多选","contract":null,"files":{"html":"../frameworks/listpicker.html","css":"../frameworks/ListPicker.css","jsx":"../frameworks/ListPicker.jsx","vue2":"../frameworks/ListPicker.vue2.vue","vue3":"../frameworks/ListPicker.vue3.vue"},"sizes":{"html":4271,"css":2742,"jsx":4014,"vue2":5775,"vue3":6059},"sources":{"html":"\n\n\n\n\nAurora Admin · ListPicker (H5)\n\n\n\n\n\n
\n

Aurora Admin · ListPicker 组件(纯 H5)

\n

对齐 ListPicker 规范:下拉列表、多选、分组、搜索、全选、标签

\n\n
\n

技术栈多选(分组 + 搜索 + 全选)

\n
\n
\n
\n\n\n\n\n","css":"/* Aurora Admin ListPicker 样式 — 对齐 组件7.txt ListPicker:下拉列表、多选、分组、搜索、全选、标签 */\n\n.aa-listpicker { position: relative; display: inline-block; min-width: 220px; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-listpicker-trigger {\n display: flex; flex-wrap: wrap; gap: 4px; align-items: center; min-height: 32px; padding: 2px 28px 2px 8px;\n background: #fff; border: 1px solid #E8ECF1; border-radius: 4px; cursor: pointer; transition: border-color .15s, box-shadow .15s;\n}\n.aa-listpicker-trigger:hover { border-color: #2F54EB; }\n.aa-listpicker-trigger.is-open { border-color: #2F54EB; box-shadow: 0 0 0 2px rgba(47,84,235,0.2); }\n.aa-listpicker-tag { background: #F0F5FF; color: #2F54EB; font-size: 12px; padding: 1px 8px; border-radius: 2px; display: inline-flex; align-items: center; gap: 4px; }\n.aa-listpicker-tag .x { cursor: pointer; opacity: .65; }\n.aa-listpicker-tag .x:hover { opacity: 1; }\n.aa-listpicker-placeholder { color: #BFBFBF; font-size: 14px; }\n.aa-listpicker-caret { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); color: #8C8C8C; }\n\n.aa-listpicker-panel { position: absolute; z-index: 100; top: calc(100% + 4px); left: 0; width: 240px; background: #fff; border: 1px solid #E8ECF1; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.12); }\n.aa-listpicker-search { padding: 8px; border-bottom: 1px solid #E8ECF1; }\n.aa-listpicker-search input { width: 100%; box-sizing: border-box; height: 30px; padding: 0 8px; border: 1px solid #E8ECF1; border-radius: 4px; font-size: 13px; outline: none; }\n.aa-listpicker-search input:focus { border-color: #2F54EB; box-shadow: 0 0 0 2px rgba(47,84,235,0.2); }\n.aa-listpicker-selectall { padding: 6px 12px; border-bottom: 1px solid #E8ECF1; cursor: pointer; font-size: 13px; color: #2F54EB; }\n.aa-listpicker-selectall:hover { background: #F5F7FA; }\n.aa-listpicker-group-label { padding: 6px 12px; font-size: 12px; color: #8C8C8C; }\n.aa-listpicker-option { display: flex; align-items: center; gap: 8px; padding: 6px 12px; cursor: pointer; font-size: 14px; }\n.aa-listpicker-option:hover { background: #F5F7FA; }\n.aa-listpicker-option.is-checked { color: #2F54EB; }\n.aa-listpicker-option .box { width: 14px; height: 14px; border: 1px solid #E8ECF1; border-radius: 3px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; color: #fff; font-size: 10px; }\n.aa-listpicker-option.is-checked .box { background: #2F54EB; border-color: #2F54EB; }\n.aa-listpicker-option.is-disabled { color: #BFBFBF; cursor: not-allowed; }\n.aa-listpicker-empty { padding: 24px 12px; text-align: center; color: #BFBFBF; font-size: 13px; }\n","jsx":"import React, { useRef, useState, useEffect, useMemo } from 'react';\nimport './ListPicker.css';\n\n/**\n * Aurora Admin ListPicker(React)\n * 对齐 组件7.txt ListPicker 规范:下拉列表、多选、分组、搜索、全选、标签\n *\n * props:\n * - value / defaultValue : 选中 value 数组\n * - options : 扁平 [{value,label}] 或 分组 [{label, children}]\n * - filterable\n * - placeholder\n * - onChange(keys)\n */\nexport default function ListPicker({\n value,\n defaultValue = [],\n options = [],\n filterable = true,\n placeholder = '请选择',\n onChange\n}) {\n const rootRef = useRef(null);\n const [inner, setInner] = useState(defaultValue);\n const current = value !== undefined ? value : inner;\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState('');\n\n const isGrouped = options.length && options[0] && options[0].children;\n const flat = isGrouped ? options.flatMap(g => g.children) : options;\n const labelMap = Object.fromEntries(flat.map(o => [o.value, o.label]));\n\n const flatVisible = useMemo(() => flat.filter(o => o.label.includes(query)), [flat, query]);\n const visibleGroups = useMemo(() => {\n if (!isGrouped) return [{ children: flatVisible }];\n return options\n .map(g => ({ label: g.label, children: g.children.filter(o => o.label.includes(query)) }))\n .filter(g => g.children.length);\n }, [options, flatVisible, query]);\n\n const commit = (next) => { if (value === undefined) setInner(next); onChange && onChange(next); };\n const toggleOpt = (o) => { if (o.disabled) return; commit(current.includes(o.value) ? current.filter(x => x !== o.value) : [...current, o.value]); };\n const remove = (v) => commit(current.filter(x => x !== v));\n const selectAll = () => commit([...new Set([...current, ...flatVisible.map(o => o.value)])]);\n\n useEffect(() => {\n const onDoc = (e) => { if (rootRef.current && !rootRef.current.contains(e.target)) { setOpen(false); setQuery(''); } };\n document.addEventListener('click', onDoc, true);\n return () => document.removeEventListener('click', onDoc, true);\n }, []);\n\n return (\n
\n
{ setOpen(!open); if (open) setQuery(''); }}>\n {current.length ? current.map(v => (\n {labelMap[v] || v} { e.stopPropagation(); remove(v); }}>×\n )) : {placeholder}}\n ▾\n
\n\n {open && (\n
\n {filterable && (\n
\n setQuery(e.target.value)} placeholder=\"搜索\" />\n
\n )}\n
全选
\n {visibleGroups.map((g, gi) => (\n
\n {g.label &&
{g.label}
}\n {g.children.map(o => (\n toggleOpt(o)}\n >\n {current.includes(o.value) ? '✓' : ''}{o.label}\n
\n ))}\n
\n ))}\n {!flatVisible.length &&
无匹配项
}\n
\n )}\n \n );\n}\n\n/*\n用法示例:\nimport ListPicker from './ListPicker';\nconst options = [{ label: '前端', children: [{ value: 'vue', label: 'Vue' }, { value: 'react', label: 'React' }] }];\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"cascader","name":"级联选择器 Cascader","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":"components/cascader.json","category":"input","specLines":["- 多级下拉选择","- 支持搜索、动态加载","- 选中路径显示","- 支持多选","- 适用于地区、分类选择"],"specSummary":"- 多级下拉选择","contract":{"variants":["单选级联选择","多选级联选择","动态加载级联选择"],"dims":[{"name":"mode","values":["single","multiple"]},{"name":"load","values":["static","dynamic"]},{"name":"search","values":["support","none"]}],"unknowns":["动态加载时的 loading 占位样式"],"anatomy":{"input":"触发框,显示选中路径(如 华东 / 上海 / 浦东)","panel":"级联面板,多列菜单逐级展开","path":"选中路径文字展示"},"doNot":["面板列宽与最大层级数","搜索命中高亮样式"],"usage":["多级下拉选择,适用于地区、分类选择","支持搜索、动态加载","选中路径完整显示","支持多选"],"structure":{"panel":"多列下拉,列内项高同菜单 32px","trigger":"点击展开"}},"files":{"html":"../frameworks/cascader.html","css":"../frameworks/Cascader.css","jsx":"../frameworks/Cascader.jsx","vue2":"../frameworks/Cascader.vue2.vue","vue3":"../frameworks/Cascader.vue3.vue"},"sizes":{"html":7568,"css":3078,"jsx":5614,"vue2":7946,"vue3":8053},"sources":{"html":"\n\n\n\n\nAurora Admin · Cascader (H5)\n\n\n\n\n\n
\n

Aurora Admin · Cascader 组件(纯 H5)

\n

对齐 Cascader 规范:多级下拉、搜索、路径显示、多选

\n\n
\n

单选 + 搜索

\n
\n
\n\n
\n

多选

\n
\n
\n
\n\n\n\n\n","css":"/* Aurora Admin Cascader 样式 — 对齐 组件7.txt Cascader:多级下拉、搜索、路径显示、多选 */\n\n.aa-cascader { position: relative; display: inline-block; min-width: 200px; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-cascader-trigger {\n display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 32px; padding: 0 12px;\n background: #fff; border: 1px solid #E8ECF1; border-radius: 4px; cursor: pointer; font-size: 14px; transition: border-color .15s, box-shadow .15s;\n}\n.aa-cascader-trigger:hover { border-color: #2F54EB; }\n.aa-cascader-trigger.is-open { border-color: #2F54EB; box-shadow: 0 0 0 2px rgba(47,84,235,0.2); }\n.aa-cascader-trigger .placeholder { color: #BFBFBF; }\n.aa-cascader-trigger .caret { color: #8C8C8C; transition: transform .15s; flex-shrink: 0; }\n.aa-cascader-trigger.is-open .caret { transform: rotate(180deg); }\n.aa-cascader-tags { display: flex; flex-wrap: wrap; gap: 4px; }\n.aa-cascader-tag { background: #F0F5FF; color: #2F54EB; font-size: 12px; padding: 1px 8px; border-radius: 2px; white-space: nowrap; }\n\n.aa-cascader-panel { position: absolute; z-index: 100; top: calc(100% + 4px); left: 0; background: #fff; border: 1px solid #E8ECF1; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.12); display: flex; }\n.aa-cascader-col { min-width: 140px; max-height: 240px; overflow: auto; border-right: 1px solid #E8ECF1; padding: 4px 0; }\n.aa-cascader-col:last-child { border-right: none; }\n.aa-cascader-opt { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 12px; cursor: pointer; font-size: 14px; color: #262626; }\n.aa-cascader-opt:hover { background: #F5F7FA; }\n.aa-cascader-opt.is-active { background: #F0F5FF; color: #2F54EB; font-weight: 500; }\n.aa-cascader-opt .arrow { color: #8C8C8C; font-size: 12px; }\n.aa-cascader-opt .box { width: 14px; height: 14px; border: 1px solid #E8ECF1; border-radius: 3px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; color: #fff; font-size: 10px; }\n.aa-cascader-opt.is-checked .box { background: #2F54EB; border-color: #2F54EB; }\n\n.aa-cascader-search { position: absolute; z-index: 101; top: calc(100% + 4px); left: 0; width: 260px; background: #fff; border: 1px solid #E8ECF1; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.12); padding: 8px; }\n.aa-cascader-search input { width: 100%; box-sizing: border-box; height: 30px; padding: 0 8px; border: 1px solid #E8ECF1; border-radius: 4px; font-size: 13px; outline: none; }\n.aa-cascader-search input:focus { border-color: #2F54EB; box-shadow: 0 0 0 2px rgba(47,84,235,0.2); }\n.aa-cascader-search input::placeholder { color: #BFBFBF; }\n.aa-cascader-results { max-height: 200px; overflow: auto; margin-top: 8px; }\n.aa-cascader-result { padding: 6px 8px; cursor: pointer; font-size: 14px; color: #262626; }\n.aa-cascader-result:hover { background: #F5F7FA; }\n.aa-cascader-empty { padding: 16px 8px; text-align: center; color: #BFBFBF; font-size: 13px; }\n","jsx":"import React, { useRef, useState, useEffect, useMemo } from 'react';\nimport './Cascader.css';\n\n/**\n * Aurora Admin Cascader(React)\n * 对齐 组件7.txt Cascader 规范:多级下拉、搜索、路径显示、多选\n *\n * props:\n * - value / defaultValue : 单选为路径数组(或 null),多选为路径数组的数组\n * - options : [{ value, label, children? }]\n * - multiple\n * - filterable\n * - placeholder\n * - onChange(value)\n */\nexport default function Cascader({\n value,\n defaultValue = [],\n options = [],\n multiple = false,\n filterable = true,\n placeholder = '请选择',\n onChange\n}) {\n const rootRef = useRef(null);\n const [inner, setInner] = useState(defaultValue);\n const current = value !== undefined ? value : inner;\n const [open, setOpen] = useState(false);\n const [activePath, setActivePath] = useState([]);\n const [query, setQuery] = useState('');\n\n const pathLabels = (values) => {\n let nodes = options, labels = [];\n for (const v of (values || [])) {\n const n = nodes.find(x => x.value === v);\n if (!n) break;\n labels.push(n.label); nodes = n.children || [];\n }\n return labels;\n };\n const columns = useMemo(() => {\n const cols = [];\n let nodes = options;\n for (let lv = 0; lv <= activePath.length; lv++) {\n cols.push(nodes);\n if (lv < activePath.length) {\n const next = nodes.find(x => x.value === activePath[lv]);\n nodes = next.children || [];\n } else break;\n }\n return cols;\n }, [options, activePath]);\n\n const allLeaves = useMemo(() => {\n const out = [];\n (function walk(list, acc) {\n list.forEach(n => {\n const p = [...acc, n.value];\n if (n.children && n.children.length) walk(n.children, p);\n else out.push(p);\n });\n })(options, []);\n return out;\n }, [options]);\n const results = query ? allLeaves.filter(p => pathLabels(p).join('/').includes(query)) : [];\n\n const commit = (next) => {\n if (value === undefined) setInner(next);\n onChange && onChange(next);\n };\n const choose = (n, lv) => {\n const full = [...activePath.slice(0, lv), n.value];\n if (n.children && n.children.length) setActivePath(full);\n else if (multiple) {\n const k = JSON.stringify(full);\n const arr = Array.isArray(current) ? current : [];\n commit(arr.some(p => JSON.stringify(p) === k) ? arr.filter(p => JSON.stringify(p) !== k) : [...arr, full]);\n } else { commit(full); setOpen(false); }\n };\n const pickResult = (p) => {\n if (multiple) {\n const k = JSON.stringify(p);\n const arr = Array.isArray(current) ? current : [];\n commit(arr.some(x => JSON.stringify(x) === k) ? arr.filter(x => JSON.stringify(x) !== k) : [...arr, p]);\n } else { commit(p); setOpen(false); }\n };\n const isChecked = (p) => Array.isArray(current) && current.some(x => JSON.stringify(x) === JSON.stringify(p));\n\n useEffect(() => {\n const onDoc = (e) => { if (rootRef.current && !rootRef.current.contains(e.target)) { setOpen(false); setQuery(''); } };\n document.addEventListener('click', onDoc, true);\n return () => document.removeEventListener('click', onDoc, true);\n }, []);\n\n return (\n
\n
{ setOpen(!open); if (open) setQuery(''); }}>\n {multiple ? (\n current.length ? (\n \n {current.map((p, i) => {pathLabels(p).join(' / ')})}\n \n ) : {placeholder}\n ) : (\n {current && current.length ? pathLabels(current).join(' / ') : placeholder}\n )}\n ▾\n
\n\n {open && filterable && (\n
\n setQuery(e.target.value)} placeholder=\"搜索\" />\n
\n )}\n\n {open && (\n
\n {query ? (\n
\n {results.length ? results.map((p, i) => (\n
pickResult(p)}>{pathLabels(p).join(' / ')}
\n )) :
无匹配结果
}\n
\n ) : (\n columns.map((col, ci) => (\n
\n {col.map(n => (\n choose(n, ci)}\n >\n {multiple && !n.children && {isChecked([...activePath.slice(0, ci), n.value]) ? '✓' : ''}}\n {n.label}\n {n.children && n.children.length && ›}\n
\n ))}\n
\n ))\n )}\n
\n )}\n \n );\n}\n\n/*\n用法示例:\nimport Cascader from './Cascader';\nconst options = [{ value: 'zj', label: '浙江', children: [{ value: 'hz', label: '杭州', children: [{ value: 'xh', label: '西湖区' }] }] }];\n\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}},{"slug":"colorpicker","name":"颜色选择器 ColorPicker","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":"components/colorpicker.json","category":"input","specLines":["- 预设色板 + 自定义拾色器","- 支持 HEX、RGB、HSL","- 透明度调节","- 实时预览","- 适用于主题配置、标签颜色"],"specSummary":"- 预设色板 + 自定义拾色器","contract":{"variants":["预设色板拾色","自定义拾色(带透明度)"],"dims":[{"name":"format","values":["HEX","RGB","HSL"]},{"name":"alpha","values":["support","none"]},{"name":"source","values":["preset-palette","custom","both"]}],"unknowns":["预设色板的具体色值清单"],"anatomy":{"palette":"预设色板","picker":"自定义拾色器","preview":"实时预览"},"doNot":["吸管取色"],"usage":["预设色板 + 自定义拾色器","支持 HEX、RGB、HSL","透明度调节","实时预览","适用于主题配置、标签颜色"],"structure":{"formats":"HEX RGB HSL"}},"files":{"html":"../frameworks/ColorPicker.html","css":"../frameworks/ColorPicker.css","jsx":"../frameworks/ColorPicker.jsx","vue2":"../frameworks/ColorPicker.vue2.vue","vue3":"../frameworks/ColorPicker.vue3.vue"},"sizes":{"html":3094,"css":1418,"jsx":1917,"vue2":1988,"vue3":1899},"sources":{"html":"\n\n\n \n \n Aurora Admin · ColorPicker(普通 H5)\n \n \n \n\n\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin ColorPicker 样式 — 对齐 组件7.txt ColorPicker:预设色板 + 自定义拾色,HEX 显示,透明度 */\n\n.aa-colorpicker { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); width: 240px; }\n.aa-colorpicker-presets { display: grid; grid-template-columns: repeat(8, 1fr); gap: 6px; margin-bottom: 12px; }\n.aa-colorpicker-swatch { height: 22px; border-radius: 4px; cursor: pointer; border: 1px solid rgba(0,0,0,0.06); }\n.aa-colorpicker-swatch.is-active { box-shadow: 0 0 0 2px #fff, 0 0 0 4px #2F54EB; }\n.aa-colorpicker-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }\n.aa-colorpicker-preview { width: 28px; height: 28px; border-radius: 6px; border: 1px solid #E8ECF1; flex-shrink: 0; }\n.aa-colorpicker-native { width: 28px; height: 28px; padding: 0; border: 1px solid #E8ECF1; border-radius: 6px; background: none; cursor: pointer; }\n.aa-colorpicker-hex { flex: 1; height: 30px; border: 1px solid #E8ECF1; border-radius: 6px; padding: 0 8px; font-size: 13px; color: #262626; text-transform: uppercase; }\n.aa-colorpicker-hex:focus { border-color: #2F54EB; outline: none; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-colorpicker-alpha { display: flex; align-items: center; gap: 8px; }\n.aa-colorpicker-alpha input { flex: 1; }\n.aa-colorpicker-alpha span { font-size: 12px; color: #8C8C8C; width: 34px; text-align: right; }\n","jsx":"import React, { useState } from 'react';\nimport './ColorPicker.css';\n\nconst DEFAULT_PRESETS = ['#2F54EB','#1D39C4','#10239E','#F5222D','#FA8C16','#52C41A','#13C2C2','#722ED1',\n '#EB2F96','#FAAD14','#A0D911','#1890FF','#000000','#595959','#8C8C8C','#BFBFBF'];\n\nexport default function ColorPicker({ value = '#2F54EB', presets = DEFAULT_PRESETS, onChange }) {\n const [hex, setHex] = useState(value);\n const [alpha, setAlpha] = useState(1);\n\n const n = parseInt(hex.slice(1), 16);\n const preview = `rgba(${(n >> 16) & 255},${(n >> 8) & 255},${n & 255},${alpha})`;\n\n function emit(nextHex, nextAlpha) {\n onChange && onChange({ hex: nextHex, alpha: nextAlpha });\n }\n function onHexInput(e) {\n const v = e.target.value;\n if (/^#[0-9a-fA-F]{6}$/.test(v)) { setHex(v); emit(v, alpha); }\n }\n\n return (\n
\n
\n {presets.map((c, i) => (\n { setHex(c); emit(c, alpha); }}\n />\n ))}\n
\n
\n
\n { setHex(e.target.value); emit(e.target.value, alpha); }} />\n \n
\n
\n 透明度\n { setAlpha(e.target.value / 100); emit(hex, e.target.value / 100); }} />\n {Math.round(alpha * 100)}%\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"rangequickpicker","name":"日期范围快捷选择 RangeQuickPicker","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"input","specLines":["- 日期范围选择器增强","- 内置快捷选项:今天、昨天、近7天、近30天、本月、上月、本年","- 自定义快捷项配置","- 支持时间范围","- 适用于报表筛选"],"specSummary":"- 日期范围选择器增强","contract":null,"files":{"html":"../frameworks/RangeQuickPicker.html","css":"../frameworks/RangeQuickPicker.css","jsx":"../frameworks/RangeQuickPicker.jsx","vue2":"../frameworks/RangeQuickPicker.vue2.vue","vue3":"../frameworks/RangeQuickPicker.vue3.vue"},"sizes":{"html":4716,"css":1823,"jsx":3420,"vue2":3132,"vue3":3014},"sources":{"html":"\n\n\n \n \n Aurora Admin · RangeQuickPicker\n \n \n\n\n
\n

RangeQuickPicker 日期范围快捷选择

\n

内置快捷选项(今天、近7天、本月等),也支持自定义起止日期。

\n\n
\n
报表时间筛选
\n
\n
\n 请选择时间范围\n
\n
\n
\n
\n \n ~\n \n
\n
\n \n \n
\n
\n
\n

\n
\n
\n\n \n\n\n","css":"/* Aurora Admin RangeQuickPicker 样式 — 日期范围 + 快捷选项 */\n.aa-range { position: relative; display: inline-block; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-range-trigger { display: flex; align-items: center; gap: 8px; border: 1px solid #E8ECF1; border-radius: 6px; padding: 0 10px; height: 36px; background: #fff; cursor: pointer; color: #262626; font-size: 14px; min-width: 220px; }\n.aa-range-trigger.is-focus { border-color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-range-trigger .aa-range-sep { color: #8C8C8C; }\n.aa-range-placeholder { color: #BFBFBF; }\n.aa-range-pop { position: absolute; top: 42px; left: 0; z-index: 20; background: #fff; border: 1px solid #E8ECF1; border-radius: 8px; box-shadow: 0 6px 24px rgba(0,0,0,0.12); padding: 12px; width: 300px; }\n.aa-range-presets { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }\n.aa-range-preset { padding: 4px 10px; border: 1px solid #E8ECF1; border-radius: 4px; font-size: 13px; cursor: pointer; color: #595959; background: #fff; }\n.aa-range-preset:hover { border-color: #B7C4FF; }\n.aa-range-preset.is-active { border-color: #2F54EB; color: #2F54EB; background: #F0F5FF; }\n.aa-range-custom { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }\n.aa-range-custom input { border: 1px solid #E8ECF1; border-radius: 4px; padding: 5px 8px; font-size: 13px; color: #262626; }\n.aa-range-custom .aa-range-tilde { color: #8C8C8C; }\n.aa-range-actions { text-align: right; }\n.aa-range-actions button { border: none; border-radius: 4px; padding: 5px 14px; font-size: 13px; cursor: pointer; }\n.aa-range-actions .aa-range-ok { background: #2F54EB; color: #fff; }\n.aa-range-actions .aa-range-cancel { background: #F5F5F5; color: #595959; margin-right: 8px; }\n","jsx":"import React, { useState, useRef, useEffect } from 'react';\nimport './RangeQuickPicker.css';\n\nfunction fmt(d) {\n return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');\n}\nfunction presetRange(p) {\n const end = new Date();\n end.setHours(0, 0, 0, 0);\n const start = new Date(end);\n if (p.year === 'this') start.setMonth(0, 1);\n else if (p.month === 'this') start.setDate(1);\n else if (p.month === 'last') {\n start = new Date(end.getFullYear(), end.getMonth() - 1, 1);\n end.setDate(0);\n } else {\n start.setDate(end.getDate() + (p.days || 0));\n if (p.len) start.setDate(end.getDate() - (p.len - 1));\n }\n return { start: fmt(start), end: fmt(end) };\n}\n\nconst DEFAULT_PRESETS = [\n { label: '今天', days: 0 },\n { label: '昨天', days: -1, len: 1 },\n { label: '近7天', days: -6 },\n { label: '近30天', days: -29 },\n { label: '本月', month: 'this' },\n { label: '上月', month: 'last' },\n { label: '本年', year: 'this' }\n];\n\nexport default function RangeQuickPicker({ modelValue = { start: '', end: '' }, presets = DEFAULT_PRESETS, onChange }) {\n const root = useRef(null);\n const [open, setOpen] = useState(false);\n const [start, setStart] = useState(modelValue.start);\n const [end, setEnd] = useState(modelValue.end);\n const [activeIndex, setActiveIndex] = useState(-1);\n\n useEffect(() => {\n function onDoc(e) {\n if (root.current && !root.current.contains(e.target)) {\n setOpen(false);\n setActiveIndex(-1);\n }\n }\n document.addEventListener('click', onDoc);\n return () => document.removeEventListener('click', onDoc);\n }, []);\n\n const applyPreset = (i) => {\n const r = presetRange(presets[i]);\n setStart(r.start);\n setEnd(r.end);\n setActiveIndex(i);\n };\n const confirm = () => {\n if (onChange) onChange({ start, end });\n setOpen(false);\n setActiveIndex(-1);\n };\n\n return (\n
\n
setOpen((o) => !o)}>\n {modelValue.start && modelValue.end ? (\n \n {modelValue.start} ~ {modelValue.end}\n \n ) : (\n 请选择时间范围\n )}\n
\n {open && (\n
\n
\n {presets.map((p, i) => (\n \n {p.label}\n \n ))}\n
\n
\n setStart(e.target.value)} />\n ~\n setEnd(e.target.value)} />\n
\n
\n \n \n
\n
\n )}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"numberrangeinput","name":"数字范围输入 NumberRangeInput","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":"components/numberrangeinput.json","category":"input","specLines":["- 输入数字范围","- 两个数字输入框,中间分隔符","- 支持步长、单位","- 校验范围合法性","- 适用于价格、数量筛选"],"specSummary":"- 输入数字范围","contract":{"variants":["价格范围筛选","数量范围输入"],"dims":[{"name":"unit","values":["with-unit","none"]},{"name":"step","values":["configurable"]},{"name":"validation","values":["range-legal-check"]}],"unknowns":["最小值大于最大值时的提示方式"],"anatomy":{"input-min":"最小值数字输入框","separator":"中间分隔符","input-max":"最大值数字输入框"},"doNot":["分隔符的具体字符与样式"],"usage":["输入数字范围","支持步长、单位","校验范围合法性","适用于价格、数量筛选"],"structure":{"layout":"两个数字输入框 + 中间分隔符"}},"files":{"html":"../frameworks/NumberRangeInput.html","css":"../frameworks/NumberRangeInput.css","jsx":"../frameworks/NumberRangeInput.jsx","vue2":"../frameworks/NumberRangeInput.vue2.vue","vue3":"../frameworks/NumberRangeInput.vue3.vue"},"sizes":{"html":1816,"css":909,"jsx":1572,"vue2":1952,"vue3":1837},"sources":{"html":"\n\n\n \n \n Aurora Admin · NumberRangeInput(普通 H5)\n \n \n \n\n\n
\n \n —\n \n 元\n \n
\n\n \n\n\n","css":"/* Aurora Admin NumberRangeInput 样式 — 对齐 组件7.txt NumberRangeInput:两个数字输入框 + 分隔符,支持步长、单位、校验 */\n\n.aa-numrange { display: inline-flex; align-items: center; gap: 8px; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-numrange input { width: 88px; height: 32px; border: 1px solid #E8ECF1; border-radius: 6px; padding: 0 10px;\n color: #262626; font-size: 14px; box-sizing: border-box; }\n.aa-numrange input:focus { border-color: #2F54EB; outline: none; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-numrange input:disabled { background: #F5F5F5; color: #BFBFBF; cursor: not-allowed; }\n.aa-numrange-sep { color: #8C8C8C; }\n.aa-numrange-unit { color: #8C8C8C; font-size: 13px; }\n.aa-numrange.is-error input { border-color: #F5222D; }\n.aa-numrange-msg { color: #F5222D; font-size: 12px; margin-left: 8px; }\n","jsx":"import React, { useState } from 'react';\nimport './NumberRangeInput.css';\n\nexport default function NumberRangeInput({\n value = ['', ''], step = 1, min = -Infinity, max = Infinity,\n separator = '—', unit = '', placeholder = ['最小', '最大'], onChange, onError\n}) {\n const [errorMsg, setErrorMsg] = useState('');\n\n function validate(arr) {\n const a = parseFloat(arr[0]), b = parseFloat(arr[1]);\n let err = '';\n if (arr[0] !== '' && (a < min || a > max)) err = `最小值需在 ${min}~${max}`;\n else if (arr[1] !== '' && (b < min || b > max)) err = `最大值需在 ${min}~${max}`;\n else if (arr[0] !== '' && arr[1] !== '' && a > b) err = '最小值不能大于最大值';\n setErrorMsg(err);\n onError && onError(err);\n }\n function onMin(e) {\n const arr = [e.target.value, value[1]];\n validate(arr);\n onChange && onChange(arr);\n }\n function onMax(e) {\n const arr = [value[0], e.target.value];\n validate(arr);\n onChange && onChange(arr);\n }\n\n return (\n
\n \n {separator}\n \n {unit ? {unit} : null}\n {errorMsg ? {errorMsg} : null}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"switchgroup","name":"开关组 SwitchGroup","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":"components/switchgroup.json","category":"input","specLines":["- 多个开关组合,可分组","- 支持全选、联动","- 适用于权限开关、通知设置","- 分组标题、说明"],"specSummary":"- 多个开关组合,可分组","contract":{"variants":["通知设置开关组","权限开关组"],"dims":[{"name":"grouping","values":["grouped(标题+说明)","flat"]},{"name":"linkage","values":["select-all","linked","none"]}],"unknowns":["联动的具体联动规则"],"anatomy":{"group":"分组容器,含分组标题与说明文字","switch-row":"开关行,单个开关沿用基础开关规格"},"doNot":["单个开关的视觉规格(沿用基础开关)"],"usage":["多个开关组合,可分组","支持全选、联动","分组带标题与说明","适用于权限开关、通知设置"],"structure":{"group":"分组标题 + 说明文字"}},"files":{"html":"../frameworks/SwitchGroup.html","css":"../frameworks/SwitchGroup.css","jsx":"../frameworks/SwitchGroup.jsx","vue2":"../frameworks/SwitchGroup.vue2.vue","vue3":"../frameworks/SwitchGroup.vue3.vue"},"sizes":{"html":2201,"css":1191,"jsx":1376,"vue2":1549,"vue3":1471},"sources":{"html":"\n\n\n \n \n Aurora Admin · SwitchGroup(普通 H5)\n \n \n \n\n\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin SwitchGroup 样式 — 对齐 组件7.txt SwitchGroup:多个开关组合,可分组、全选、联动 */\n\n.aa-switchgroup { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-switchgroup-head { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #E8ECF1; margin-bottom: 8px; }\n.aa-switchgroup-title { font-size: 14px; font-weight: 500; color: #1F2329; }\n.aa-switch-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; }\n.aa-switch-text { display: flex; flex-direction: column; }\n.aa-switch-label { font-size: 14px; color: #262626; }\n.aa-switch-desc { font-size: 12px; color: #8C8C8C; margin-top: 2px; }\n.aa-switch { position: relative; width: 44px; height: 22px; border-radius: 11px; background: #E8ECF1; cursor: pointer; transition: background .2s; flex-shrink: 0; }\n.aa-switch.is-on { background: #2F54EB; }\n.aa-switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%; background: #fff; transition: left .2s; }\n.aa-switch.is-on::after { left: 24px; }\n","jsx":"import React, { useState } from 'react';\nimport './SwitchGroup.css';\n\nexport default function SwitchGroup({ options = [], value = [], title = '', selectAll = false, onChange }) {\n const allOn = options.length > 0 && options.every(o => value.indexOf(o.value) >= 0);\n\n function toggle(v) {\n const arr = value.slice();\n const idx = arr.indexOf(v);\n if (idx >= 0) arr.splice(idx, 1); else arr.push(v);\n onChange && onChange(arr);\n }\n function toggleAll() {\n onChange && onChange(allOn ? [] : options.map(o => o.value));\n }\n\n return (\n
\n {(title || selectAll) ? (\n
\n {title}\n {selectAll ? (\n
\n ) : null}\n
\n ) : null}\n {options.map((o, i) => (\n
\n
\n {o.label}\n {o.desc ? {o.desc} : null}\n
\n = 0 ? ' is-on' : '')}\n onClick={() => toggle(o.value)}\n />\n
\n ))}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"dragupload","name":"拖拽上传 DragUpload","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"input","specLines":["- 支持点击或拖拽文件上传","- 拖拽区域虚线边框,悬停高亮","- 文件列表显示进度、状态","- 支持图片预览、删除","- 限制文件类型、大小、数量"],"specSummary":"- 支持点击或拖拽文件上传","contract":null,"files":{"html":"../frameworks/DragUpload.html","css":"../frameworks/DragUpload.css","jsx":"../frameworks/DragUpload.jsx","vue2":"../frameworks/DragUpload.vue2.vue","vue3":"../frameworks/DragUpload.vue3.vue"},"sizes":{"html":3513,"css":1700,"jsx":2691,"vue2":2303,"vue3":2172},"sources":{"html":"\n\n\n \n \n Aurora Admin · DragUpload\n \n \n\n\n
\n

DragUpload 拖拽上传

\n

支持点击或拖拽文件上传,显示进度与缩略图,可删除。

\n\n
\n
\n
\n ⬆️\n
将文件拖到此处,或 点击选择
\n
\n \n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin DragUpload 样式 — 拖拽 / 点击上传 */\n.aa-upload { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-upload-drop { border: 2px dashed #E8ECF1; border-radius: 8px; padding: 28px; text-align: center; color: #8C8C8C; cursor: pointer; transition: border-color .15s, background .15s, color .15s; }\n.aa-upload-drop.is-over { border-color: #2F54EB; background: #F0F5FF; color: #2F54EB; }\n.aa-upload-icon { font-size: 26px; display: block; margin-bottom: 6px; }\n.aa-upload-hint { font-size: 13px; }\n.aa-upload-hint b { color: #2F54EB; }\n.aa-upload-list { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }\n.aa-upload-item { display: flex; align-items: center; gap: 10px; border: 1px solid #E8ECF1; border-radius: 6px; padding: 8px 10px; }\n.aa-upload-thumb { width: 36px; height: 36px; border-radius: 4px; overflow: hidden; background: #F5F5F5; flex-shrink: 0; display: flex; align-items: center; justify-content: center; color: #8C8C8C; font-size: 11px; }\n.aa-upload-thumb img { width: 100%; height: 100%; object-fit: cover; }\n.aa-upload-meta { flex: 1; min-width: 0; }\n.aa-upload-name { font-size: 13px; color: #262626; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.aa-upload-bar { height: 4px; background: #E8ECF1; border-radius: 2px; margin-top: 5px; overflow: hidden; }\n.aa-upload-bar > i { display: block; height: 100%; background: #2F54EB; width: 0; transition: width .2s; }\n.aa-upload-status { font-size: 12px; color: #8C8C8C; }\n.aa-upload-del { border: none; background: none; color: #8C8C8C; cursor: pointer; font-size: 16px; padding: 0 4px; }\n.aa-upload-del:hover { color: #F5222D; }\n","jsx":"import React, { useRef, useState } from 'react';\nimport './DragUpload.css';\n\nexport default function DragUpload() {\n const [files, setFiles] = useState([]);\n const [over, setOver] = useState(false);\n const fileRef = useRef(null);\n\n const addFiles = (flist) => {\n Array.prototype.forEach.call(flist, (f) => {\n const id = 'f' + Date.now() + Math.random().toString(36).slice(2, 6);\n const item = { id, name: f.name, progress: 0, done: false, thumb: '' };\n if (/^image\\//.test(f.type) && f.size < 2 * 1024 * 1024) {\n const reader = new FileReader();\n reader.onload = (e) => setFiles((fs) => fs.map((x) => (x.id === id ? { ...x, thumb: e.target.result } : x)));\n reader.readAsDataURL(f);\n }\n setFiles((fs) => [...fs, item]);\n const t = setInterval(() => {\n setFiles((fs) =>\n fs.map((x) => {\n if (x.id !== id) return x;\n const p = Math.min(100, x.progress + Math.random() * 22);\n return { ...x, progress: p, done: p >= 100 };\n })\n );\n setFiles((fs) => {\n if (fs.find((x) => x.id === id && x.progress >= 100)) clearInterval(t);\n return fs;\n });\n }, 220);\n });\n };\n\n const remove = (id) => setFiles((fs) => fs.filter((x) => x.id !== id));\n\n return (\n
\n fileRef.current.click()}\n onDragOver={(e) => { e.preventDefault(); setOver(true); }}\n onDragLeave={() => setOver(false)}\n onDrop={(e) => { e.preventDefault(); setOver(false); addFiles(e.dataTransfer.files); }}\n >\n ⬆️\n
将文件拖到此处,或 点击选择
\n
\n { addFiles(e.target.files); e.target.value = ''; }} />\n
\n {files.map((it) => (\n
\n
{it.thumb ? \"\" : '文件'}
\n
\n
{it.name}
\n
\n
\n
{it.done ? '已完成' : Math.round(it.progress) + '%'}
\n \n
\n ))}\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"signaturepad","name":"签名板 SignaturePad","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"input","specLines":["- 手写签名区域","- 支持鼠标、触屏","- 清除、撤销、保存","- 可导出图片","- 适用于合同、确认单"],"specSummary":"- 手写签名区域","contract":null,"files":{"html":"../frameworks/SignaturePad.html","css":"../frameworks/SignaturePad.css","jsx":"../frameworks/SignaturePad.jsx","vue2":"../frameworks/SignaturePad.vue2.vue","vue3":"../frameworks/SignaturePad.vue3.vue"},"sizes":{"html":3328,"css":716,"jsx":2965,"vue2":2511,"vue3":2510},"sources":{"html":"\n\n\n \n \n Aurora Admin · SignaturePad\n \n \n\n\n
\n

SignaturePad 签名板

\n

手写签名区域,支持鼠标与触屏,可撤销、清除、导出图片。

\n\n
\n
\n
\n \n
请在此处签名
\n
\n
\n \n \n \n \n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin SignaturePad 样式 — 手写签名 */\n.aa-sign { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-sign-pad { position: relative; border: 1px solid #E8ECF1; border-radius: 8px; background: #fff; overflow: hidden; touch-action: none; }\n.aa-sign-canvas { display: block; width: 100%; height: 180px; cursor: crosshair; }\n.aa-sign-placeholder { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: #BFBFBF; font-size: 14px; pointer-events: none; }\n.aa-sign-actions { display: flex; gap: 10px; margin-top: 10px; align-items: center; }\n.aa-sign-preview { margin-left: auto; font-size: 12px; color: #8C8C8C; }\n","jsx":"import React, { useRef, useState, useEffect } from 'react';\nimport './SignaturePad.css';\n\nexport default function SignaturePad() {\n const pad = useRef(null);\n const canvas = useRef(null);\n const drawing = useRef(false);\n const last = useRef(null);\n const strokes = useRef([]);\n const cur = useRef([]);\n const [hasInk, setHasInk] = useState(false);\n const [saved, setSaved] = useState('');\n\n const ctx = () => canvas.current.getContext('2d');\n const fit = () => {\n const r = pad.current.getBoundingClientRect();\n canvas.current.width = r.width;\n canvas.current.height = r.height;\n redraw();\n };\n const pos = (e) => {\n const r = canvas.current.getBoundingClientRect();\n const t = e.touches && e.touches[0];\n return { x: (t ? t.clientX : e.clientX) - r.left, y: (t ? t.clientY : e.clientY) - r.top };\n };\n const down = (e) => {\n e.preventDefault();\n drawing.current = true;\n last.current = pos(e);\n cur.current = [last.current];\n strokes.current.push(cur.current);\n setHasInk(true);\n };\n const move = (e) => {\n if (!drawing.current) return;\n e.preventDefault();\n const p = pos(e);\n ctx().lineTo(p.x, p.y);\n ctx().stroke();\n cur.current.push(p);\n };\n const up = () => { drawing.current = false; last.current = null; };\n const undo = () => { strokes.current.pop(); redraw(); if (!strokes.current.length) setHasInk(false); };\n const clear = () => { strokes.current = []; redraw(); setHasInk(false); setSaved(''); };\n const save = () => setSaved('已导出(' + canvas.current.width + '×' + canvas.current.height + ')');\n\n const redraw = () => {\n const c = canvas.current;\n const x = c.getContext('2d');\n x.clearRect(0, 0, c.width, c.height);\n x.strokeStyle = '#1F2329';\n x.lineWidth = 2;\n x.lineCap = 'round';\n x.lineJoin = 'round';\n strokes.current.forEach((s) => {\n x.beginPath();\n s.forEach((p, i) => (i ? x.lineTo(p.x, p.y) : x.moveTo(p.x, p.y)));\n x.stroke();\n });\n };\n\n useEffect(() => {\n fit();\n window.addEventListener('resize', fit);\n return () => window.removeEventListener('resize', fit);\n // eslint-disable-next-line\n }, []);\n\n return (\n
\n
\n \n {!hasInk &&
请在此处签名
}\n
\n
\n \n \n \n {saved && {saved}}\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"codeinput","name":"验证码输入 CodeInput","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":"components/codeinput.json","category":"input","specLines":["- 多个独立输入格","- 自动聚焦下一个","- 支持粘贴、删除","- 输入完成自动提交","- 适用于短信验证码、支付密码"],"specSummary":"- 多个独立输入格","contract":{"variants":["短信验证码输入","支付密码输入"],"dims":[{"name":"cells","values":["configurable-格数"]},{"name":"autoSubmit","values":["on-complete","none"]},{"name":"paste","values":["support"]}],"unknowns":["错误态的视觉反馈方式"],"anatomy":{"cell":"多个独立输入格","auto-focus":"输入后自动聚焦下一格;支持粘贴、删除回退"},"doNot":["格子尺寸与间距规范","错误态样式","重发倒计时展示(属业务侧)"],"usage":["多个独立输入格","自动聚焦下一个","支持粘贴、删除","输入完成自动提交","适用于短信验证码、支付密码"],"structure":{"focus":"自动聚焦下一个输入格"}},"files":{"html":"../frameworks/CodeInput.html","css":"../frameworks/CodeInput.css","jsx":"../frameworks/CodeInput.jsx","vue2":"../frameworks/CodeInput.vue2.vue","vue3":"../frameworks/CodeInput.vue3.vue"},"sizes":{"html":4115,"css":1082,"jsx":2755,"vue2":2981,"vue3":2673},"sources":{"html":"\n\n\n \n \n Aurora Admin · CodeInput\n \n \n\n\n
\n

CodeInput 验证码输入

\n

用于短信验证码、一次性口令(OTP)输入,支持自动跳转、退格回退与粘贴填充。

\n\n
\n
基础用法(6 位)
\n
\n
\n
\n\n
\n
掩码模式 + 4 位
\n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin CodeInput 样式 — 对齐 组件7.txt CodeInput:验证码 / 一次性口令输入 */\n\n.aa-code { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); display: flex; gap: 8px; }\n.aa-code-cell {\n position: relative;\n width: 44px; height: 48px;\n border: 1px solid #E8ECF1; border-radius: 8px;\n background: #fff;\n display: flex; align-items: center; justify-content: center;\n transition: border-color .15s, box-shadow .15s;\n}\n.aa-code-cell.is-focus { border-color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-code-cell.is-filled { border-color: #B7C4FF; }\n.aa-code-input {\n width: 100%; height: 100%;\n border: none; outline: none; background: none;\n text-align: center; font-size: 20px; font-weight: 600; color: #1F2329;\n padding: 0;\n}\n.aa-code-input::placeholder { color: #BFBFBF; font-weight: 400; }\n.aa-code-cell.is-masked .aa-code-input { -webkit-text-security: disc; text-security: disc; }\n.aa-code-tip { font-size: 12px; margin-top: 8px; color: #8C8C8C; }\n.aa-code-tip.error { color: #F5222D; }\n","jsx":"import React, { useState, useRef, useCallback } from 'react';\nimport './CodeInput.css';\n\nfunction buildCells(v, length) {\n const arr = (v || '').split('').slice(0, length);\n while (arr.length < length) arr.push('');\n return arr;\n}\n\nexport default function CodeInput({\n value = '',\n length = 6,\n masked = false,\n tip = '',\n hasError = false,\n onChange\n}) {\n const inputsRef = useRef([]);\n const [cells, setCells] = useState(() => buildCells(value, length));\n const [focusedIndex, setFocusedIndex] = useState(-1);\n\n const emit = useCallback(\n (next) => {\n const code = next.join('');\n if (onChange) onChange(code);\n },\n [onChange]\n );\n\n const focus = (i) => {\n const el = inputsRef.current[i];\n if (el) el.focus();\n };\n\n const onInput = (e, i) => {\n const ch = (e.target.value || '').slice(-1);\n const next = cells.slice();\n next[i] = ch;\n e.target.value = ch;\n setCells(next);\n if (ch && i < length - 1) focus(i + 1);\n emit(next);\n };\n\n const onKeydown = (e, i) => {\n if (e.key === 'Backspace') {\n e.preventDefault();\n const next = cells.slice();\n if (next[i]) {\n next[i] = '';\n } else if (i > 0) {\n next[i - 1] = '';\n focus(i - 1);\n }\n setCells(next);\n emit(next);\n } else if (e.key === 'ArrowLeft' && i > 0) {\n focus(i - 1);\n } else if (e.key === 'ArrowRight' && i < length - 1) {\n focus(i + 1);\n }\n };\n\n const onPaste = (e) => {\n e.preventDefault();\n const text = (e.clipboardData || window.clipboardData).getData('text') || '';\n const digits = text.replace(/\\D/g, '').slice(0, length).split('');\n const next = cells.slice();\n for (let k = 0; k < length; k++) next[k] = digits[k] || '';\n setCells(next);\n focus(Math.min(digits.length, length - 1));\n emit(next);\n };\n\n return (\n
\n
\n {cells.map((c, i) => (\n \n (inputsRef.current[i] = el)}\n className=\"aa-code-input\"\n value={c}\n maxLength={1}\n inputMode=\"numeric\"\n aria-label={'验证码第 ' + (i + 1) + ' 位'}\n onFocus={() => setFocusedIndex(i)}\n onInput={(e) => onInput(e, i)}\n onKeyDown={(e) => onKeydown(e, i)}\n />\n
\n ))}\n
\n
{tip}
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"passwordinput","name":"密码输入 PasswordInput","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":"components/passwordinput.json","category":"input","specLines":["- 密码可见切换","- 强度检测(弱/中/强)","- 可配置规则","- 支持一键生成强密码","- 适用于注册、修改密码"],"specSummary":"- 密码可见切换","contract":{"variants":["注册密码输入(带强度与生成)","基础密码输入"],"dims":[{"name":"visibilityToggle","values":["support"]},{"name":"strength","values":["weak-medium-strong","none"]},{"name":"generate","values":["one-click","none"]}],"unknowns":["强度判定的具体规则细节"],"anatomy":{"input":"密码输入框(圆点掩码)","eye":"密码可见切换","strength-meter":"强度检测提示:弱 / 中 / 强"},"doNot":["强度指示的配色具体值"],"usage":["密码可见切换","强度检测(弱/中/强)","可配置规则","支持一键生成强密码","适用于注册、修改密码"],"structure":{"rules":"强度检测规则可配置"}},"files":{"html":"../frameworks/PasswordInput.html","css":"../frameworks/PasswordInput.css","jsx":"../frameworks/PasswordInput.jsx","vue2":"../frameworks/PasswordInput.vue2.vue","vue3":"../frameworks/PasswordInput.vue3.vue"},"sizes":{"html":3602,"css":1063,"jsx":2124,"vue2":2171,"vue3":2161},"sources":{"html":"\n\n\n \n \n Aurora Admin · PasswordInput\n \n \n\n\n
\n

PasswordInput 密码输入

\n

可见切换 + 强度检测,适用于注册 / 修改密码等场景。

\n\n
\n
基础用法(含强度条)
\n
\n
\n \n \n
\n
\n \n \n \n
\n
\n
\n
\n\n
\n
关闭强度条
\n
\n
\n \n \n
\n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin PasswordInput 样式 — 对齐 组件7.txt PasswordInput:可见切换 + 强度检测 */\n\n.aa-pwd { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); width: 260px; }\n.aa-pwd-field { display: flex; align-items: center; border: 1px solid #E8ECF1; border-radius: 6px; padding: 0 10px; height: 36px; }\n.aa-pwd-field.is-focus { border-color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-pwd-input { flex: 1; border: none; outline: none; font-size: 14px; color: #262626; background: none; height: 100%; }\n.aa-pwd-toggle { border: none; background: none; cursor: pointer; color: #8C8C8C; font-size: 13px; padding: 0 4px; }\n.aa-pwd-strength { display: flex; gap: 4px; margin-top: 8px; }\n.aa-pwd-bar { flex: 1; height: 4px; border-radius: 2px; background: #E8ECF1; transition: background .2s; }\n.aa-pwd-bar.on.weak { background: #F5222D; }\n.aa-pwd-bar.on.mid { background: #FA8C16; }\n.aa-pwd-bar.on.strong { background: #52C41A; }\n.aa-pwd-tip { font-size: 12px; margin-top: 4px; color: #8C8C8C; }\n","jsx":"import React, { useState, useMemo, useCallback } from 'react';\nimport './PasswordInput.css';\n\nfunction levelOf(v) {\n if (!v) return '';\n let s = 0;\n if (v.length >= 8) s++;\n if (/[a-z]/.test(v) && /[A-Z]/.test(v)) s++;\n if (/\\d/.test(v)) s++;\n if (/[^A-Za-z0-9]/.test(v)) s++;\n if (v.length >= 12) s++;\n if (s <= 2) return 'weak';\n if (s <= 3) return 'mid';\n return 'strong';\n}\n\nexport default function PasswordInput({ value = '', placeholder = '请输入密码', showStrength = true, onChange }) {\n const [visible, setVisible] = useState(false);\n const [focused, setFocused] = useState(false);\n\n const level = useMemo(() => levelOf(value), [value]);\n const tip = useMemo(() => {\n if (level === 'weak') return '弱:建议使用字母、数字与符号组合';\n if (level === 'mid') return '中:再增加长度或符号更安全';\n if (level === 'strong') return '强:密码强度良好';\n return '';\n }, [level]);\n\n const handle = useCallback(\n (e) => {\n if (onChange) onChange(e.target.value);\n },\n [onChange]\n );\n\n const barClass = (i) => {\n if (!showStrength || !level) return '';\n const on =\n (level === 'weak' && i < 1) || (level === 'mid' && i < 2) || (level === 'strong' && i < 3);\n return on ? 'on ' + level : '';\n };\n\n return (\n
\n
\n setFocused(true)}\n onBlur={() => setFocused(false)}\n />\n \n
\n {showStrength && (\n
\n {[0, 1, 2].map((i) => (\n \n ))}\n
\n )}\n
{tip}
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"phoneinput","name":"手机号输入 PhoneInput","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":"components/phoneinput.json","category":"input","specLines":["- 国际区号选择","- 自动格式化","- 支持校验","- 适用于注册、登录"],"specSummary":"- 国际区号选择","contract":{"variants":["国际手机号输入","国内手机号输入"],"dims":[{"name":"region","values":["intl(区号选择)","cn-only"]},{"name":"format","values":["auto-format"]},{"name":"validation","values":["support"]}],"unknowns":["各国格式化规则清单"],"anatomy":{"region-select":"国际区号选择(样式沿用选择器)","input":"号码输入框,自动格式化"},"doNot":["区号下拉面板样式(沿用选择器规格)"],"usage":["国际区号选择","自动格式化","支持校验","适用于注册、登录"],"structure":{"format":"自动格式化"}},"files":{"html":"../frameworks/PhoneInput.html","css":"../frameworks/PhoneInput.css","jsx":"../frameworks/PhoneInput.jsx","vue2":"../frameworks/PhoneInput.vue2.vue","vue3":"../frameworks/PhoneInput.vue3.vue"},"sizes":{"html":2326,"css":956,"jsx":1810,"vue2":1859,"vue3":1845},"sources":{"html":"\n\n\n \n \n Aurora Admin · PhoneInput\n \n \n\n\n
\n

PhoneInput 手机号输入

\n

国际区号选择 + 自动格式化 + 校验,适用于注册、登录。

\n\n
\n
基础用法
\n
\n \n \n
\n
支持中国大陆、香港、美、日等区号
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin PhoneInput 样式 — 国际区号 + 手机号 */\n.aa-phone { display: inline-flex; align-items: stretch; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); border: 1px solid #E8ECF1; border-radius: 6px; height: 36px; background: #fff; overflow: hidden; }\n.aa-phone.is-focus { border-color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-phone.is-error { border-color: #F5222D; }\n.aa-phone-code { border: none; border-right: 1px solid #E8ECF1; background: #FAFAFA; color: #262626; font-size: 14px; padding: 0 8px; outline: none; cursor: pointer; }\n.aa-phone-input { flex: 1; border: none; outline: none; padding: 0 10px; font-size: 14px; color: #262626; background: none; min-width: 160px; }\n.aa-phone-input::placeholder { color: #BFBFBF; }\n.aa-phone-tip { font-size: 12px; margin-top: 4px; color: #8C8C8C; }\n.aa-phone-tip.error { color: #F5222D; }\n.aa-phone-tip.ok { color: #52C41A; }\n","jsx":"import React, { useState, useMemo } from 'react';\nimport './PhoneInput.css';\n\nconst CODES = [\n { value: '+86', label: '中国大陆' },\n { value: '+852', label: '香港' },\n { value: '+1', label: '美国' },\n { value: '+81', label: '日本' }\n];\n\nexport default function PhoneInput({ modelValue = '', placeholder = '请输入手机号', onChange }) {\n const [code, setCode] = useState('+86');\n const [number, setNumber] = useState(modelValue);\n const [focused, setFocused] = useState(false);\n\n const valid = useMemo(\n () => (code === '+86' ? /^1[3-9]\\d{9}$/.test(number) : number.length >= 6 && number.length <= 15),\n [code, number]\n );\n const tipClass = !number ? '' : valid ? 'ok' : 'error';\n const tipText = !number ? '支持中国大陆、香港、美、日等区号' : valid ? '格式正确:' + code + ' ' + number : '手机号格式不正确';\n\n const onInput = (e) => {\n const v = e.target.value.replace(/\\D/g, '').slice(0, 11);\n e.target.value = v;\n setNumber(v);\n if (onChange) onChange(code + ' ' + v);\n };\n\n return (\n
\n
\n \n setFocused(true)}\n onBlur={() => setFocused(false)}\n />\n
\n
{tipText}
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"messagepro","name":"全局提示增强 MessagePro","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"feedback","specLines":["- 支持多种类型:成功、错误、警告、信息、加载","- 可带关闭按钮、操作按钮","- 可配置持续时间、位置","- 支持自定义图标","- 可堆叠显示"],"specSummary":"- 支持多种类型:成功、错误、警告、信息、加载","contract":null,"files":{"html":"../frameworks/MessagePro.html","css":"../frameworks/MessagePro.css","jsx":"../frameworks/MessagePro.jsx","vue2":"../frameworks/MessagePro.vue2.vue","vue3":"../frameworks/MessagePro.vue3.vue"},"sizes":{"html":2420,"css":1272,"jsx":1600,"vue2":1675,"vue3":1534},"sources":{"html":"\n\n\n \n \n Aurora Admin · MessagePro(普通 H5)\n \n \n \n\n\n \n \n \n \n \n\n \n\n\n","css":"/* Aurora Admin MessagePro 样式 — 对齐 组件7.txt MessagePro:顶部居中全局提示,可堆叠,自动消失 */\n\n.aa-message-wrap { position: fixed; top: 16px; left: 50%; transform: translateX(-50%); z-index: 3000;\n display: flex; flex-direction: column; gap: 8px; pointer-events: none; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-message { pointer-events: auto; min-width: 200px; max-width: 440px; background: #fff; border: 1px solid #E8ECF1;\n border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.12); padding: 10px 14px; display: flex; align-items: center; gap: 8px; font-size: 14px; color: #262626; }\n.aa-message-icon { width: 16px; height: 16px; border-radius: 50%; flex-shrink: 0; color: #fff; font-size: 11px; text-align: center; line-height: 16px; }\n.aa-message.is-success .aa-message-icon { background: #52C41A; }\n.aa-message.is-error .aa-message-icon { background: #F5222D; }\n.aa-message.is-warning .aa-message-icon { background: #FA8C16; }\n.aa-message.is-info .aa-message-icon { background: #2F54EB; }\n.aa-message.is-loading .aa-message-icon { background: #2F54EB; }\n.aa-message-content { flex: 1; }\n.aa-message-close { color: #8C8C8C; cursor: pointer; font-size: 14px; line-height: 1; }\n","jsx":"import React, { forwardRef, useImperativeHandle, useState, useRef } from 'react';\nimport './MessagePro.css';\n\nconst ICON = { success: '✓', error: '✕', warning: '!', info: 'i', loading: '…' };\n\nconst MessagePro = forwardRef(function MessagePro({ duration = 3000 }, ref) {\n const [list, setList] = useState([]);\n const idRef = useRef(0);\n\n function close(id) { setList(l => l.filter(m => m.id !== id)); }\n function open(opts) {\n const id = ++idRef.current;\n setList(l => [...l, { id, type: opts.type || 'info', content: opts.content, closable: opts.closable }]);\n const d = opts.duration == null ? duration : opts.duration;\n if (d > 0) setTimeout(() => close(id), d);\n return id;\n }\n\n useImperativeHandle(ref, () => ({\n open,\n close,\n success: (c, d) => open({ type: 'success', content: c, duration: d }),\n error: (c, d) => open({ type: 'error', content: c, duration: d }),\n warning: (c, d) => open({ type: 'warning', content: c, duration: d }),\n info: (c, d) => open({ type: 'info', content: c, duration: d }),\n loading: (c, d) => open({ type: 'loading', content: c, duration: d })\n }));\n\n return (\n
\n {list.map(m => (\n
\n {ICON[m.type] || 'i'}\n {m.content}\n {m.closable !== false ? close(m.id)}>✕ : null}\n
\n ))}\n
\n );\n});\n\nexport default MessagePro;\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"notificationpro","name":"通知提醒增强 NotificationPro","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"feedback","specLines":["- 右上角弹出","- 可带标题、描述、图标、操作按钮","- 停留时间可配置,可手动关闭","- 支持折叠、展开","- 适用于系统通知、任务结果"],"specSummary":"- 右上角弹出","contract":null,"files":{"html":"../frameworks/NotificationPro.html","css":"../frameworks/NotificationPro.css","jsx":"../frameworks/NotificationPro.jsx","vue2":"../frameworks/NotificationPro.vue2.vue","vue3":"../frameworks/NotificationPro.vue3.vue"},"sizes":{"html":2483,"css":1368,"jsx":1711,"vue2":1714,"vue3":1594},"sources":{"html":"\n\n\n \n \n Aurora Admin · NotificationPro(普通 H5)\n \n \n \n\n\n \n \n \n \n\n \n\n\n","css":"/* Aurora Admin NotificationPro 样式 — 对齐 组件7.txt NotificationPro:右上角通知,标题+描述+图标,可关闭 */\n\n.aa-notification-wrap { position: fixed; top: 16px; right: 16px; z-index: 3000; width: 320px;\n display: flex; flex-direction: column; gap: 10px; pointer-events: none;\n font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-notification { pointer-events: auto; background: #fff; border: 1px solid #E8ECF1; border-radius: 8px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.12); padding: 14px 16px; display: flex; gap: 10px; }\n.aa-notification-icon { width: 18px; height: 18px; border-radius: 50%; flex-shrink: 0; color: #fff; font-size: 12px; text-align: center; line-height: 18px; margin-top: 2px; }\n.aa-notification.is-success .aa-notification-icon { background: #52C41A; }\n.aa-notification.is-error .aa-notification-icon { background: #F5222D; }\n.aa-notification.is-warning .aa-notification-icon { background: #FA8C16; }\n.aa-notification.is-info .aa-notification-icon { background: #2F54EB; }\n.aa-notification-body { flex: 1; }\n.aa-notification-title { font-size: 14px; font-weight: 500; color: #1F2329; }\n.aa-notification-desc { font-size: 13px; color: #8C8C8C; margin-top: 4px; line-height: 1.5; }\n.aa-notification-close { color: #8C8C8C; cursor: pointer; font-size: 14px; line-height: 1; }\n","jsx":"import React, { forwardRef, useImperativeHandle, useState, useRef } from 'react';\nimport './NotificationPro.css';\n\nconst ICON = { success: '✓', error: '✕', warning: '!', info: 'i' };\n\nconst NotificationPro = forwardRef(function NotificationPro({ duration = 4500 }, ref) {\n const [list, setList] = useState([]);\n const idRef = useRef(0);\n\n function close(id) { setList(l => l.filter(m => m.id !== id)); }\n function open(opts) {\n const id = ++idRef.current;\n setList(l => [...l, { id, type: opts.type || 'info', title: opts.title, desc: opts.desc }]);\n const d = opts.duration == null ? duration : opts.duration;\n if (d > 0) setTimeout(() => close(id), d);\n return id;\n }\n\n useImperativeHandle(ref, () => ({\n open,\n close,\n success: (t, d, dur) => open({ type: 'success', title: t, desc: d, duration: dur }),\n error: (t, d, dur) => open({ type: 'error', title: t, desc: d, duration: dur }),\n warning: (t, d, dur) => open({ type: 'warning', title: t, desc: d, duration: dur }),\n info: (t, d, dur) => open({ type: 'info', title: t, desc: d, duration: dur })\n }));\n\n return (\n
\n {list.map(m => (\n
\n {ICON[m.type] || 'i'}\n
\n
{m.title}
\n {m.desc ?
{m.desc}
: null}\n
\n close(m.id)}>✕\n
\n ))}\n
\n );\n});\n\nexport default NotificationPro;\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"progressvariants","name":"进度条变体 ProgressVariants","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"feedback","specLines":["- 线型、圆形、仪表盘型","- 支持状态颜色(成功、警告、错误)","- 可显示百分比文字","- 可带图标","- 适用于上传、任务进度"],"specSummary":"- 线型、圆形、仪表盘型","contract":null,"files":{"html":"../frameworks/ProgressVariants.html","css":"../frameworks/ProgressVariants.css","jsx":"../frameworks/ProgressVariants.jsx","vue2":"../frameworks/ProgressVariants.vue2.vue","vue3":"../frameworks/ProgressVariants.vue3.vue"},"sizes":{"html":2291,"css":1451,"jsx":1590,"vue2":1927,"vue3":1799},"sources":{"html":"\n\n\n \n \n Aurora Admin · ProgressVariants(普通 H5)\n \n \n \n\n\n
\n
\n
\n
\n 完成\n
\n
\n
\n
\n
\n 60%\n
\n
\n
\n\n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin ProgressVariants 样式 — 对齐 组件7.txt ProgressVariants:线型/圆形/仪表盘,状态色,百分比 */\n\n.aa-progress { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-progress-line-wrap { display: flex; align-items: center; }\n.aa-progress-line { flex: 1; height: 8px; background: #E8ECF1; border-radius: 4px; overflow: hidden; }\n.aa-progress-line-fill { height: 100%; background: #2F54EB; border-radius: 4px; transition: width .3s; }\n.aa-progress.is-success .aa-progress-line-fill { background: #52C41A; }\n.aa-progress.is-warning .aa-progress-line-fill { background: #FA8C16; }\n.aa-progress.is-error .aa-progress-line-fill { background: #F5222D; }\n.aa-progress-text { margin-left: 8px; font-size: 13px; min-width: 36px; }\n.aa-progress.is-success .aa-progress-text { color: #52C41A; }\n.aa-progress.is-warning .aa-progress-text { color: #FA8C16; }\n.aa-progress.is-error .aa-progress-text { color: #F5222D; }\n.aa-progress-circle { position: relative; display: inline-flex; }\n.aa-progress-circle-text { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 16px; font-weight: 500; }\n.aa-progress-circle.is-success .aa-progress-circle-text { color: #52C41A; }\n.aa-progress-circle.is-warning .aa-progress-circle-text { color: #FA8C16; }\n.aa-progress-circle.is-error .aa-progress-circle-text { color: #F5222D; }\n","jsx":"import React from 'react';\nimport './ProgressVariants.css';\n\nconst R = 42;\nconst CIRC = 2 * Math.PI * R;\nconst COLORS = { success: '#52C41A', warning: '#FA8C16', error: '#F5222D', normal: '#2F54EB' };\n\nexport default function ProgressVariants({ type = 'line', percent = 0, status = 'normal', showInfo = true }) {\n const color = COLORS[status] || COLORS.normal;\n if (type === 'line') {\n return (\n
\n
\n
\n {showInfo ? {status === 'success' ? '完成' : percent + '%'} : null}\n
\n
\n );\n }\n const isDash = type === 'dashboard';\n const arcLen = isDash ? CIRC * 0.75 : CIRC;\n return (\n
\n \n \n \n \n {showInfo ? {percent}% : null}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"skeletonpro","name":"骨架屏增强 SkeletonPro","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"feedback","specLines":["- 支持多种占位形状","- 可组合成页面级骨架","- 微光动画","- 可配置加载速度","- 切换真实内容时淡入"],"specSummary":"- 支持多种占位形状","contract":null,"files":{"html":"../frameworks/SkeletonPro.html","css":"../frameworks/SkeletonPro.css","jsx":"../frameworks/SkeletonPro.jsx","vue2":"../frameworks/SkeletonPro.vue2.vue","vue3":"../frameworks/SkeletonPro.vue3.vue"},"sizes":{"html":1124,"css":1018,"jsx":827,"vue2":918,"vue3":803},"sources":{"html":"\n\n\n \n \n Aurora Admin · SkeletonPro(普通 H5)\n \n \n \n\n\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin SkeletonPro 样式 — 对齐 组件7.txt SkeletonPro:骨架屏占位,微光动画 */\n\n.aa-skeleton { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-skeleton-row { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; }\n.aa-skeleton-avatar { width: 40px; height: 40px; border-radius: 50%; background: #E8ECF1; flex-shrink: 0; }\n.aa-skeleton-lines { flex: 1; }\n.aa-skeleton-line { height: 12px; background: #E8ECF1; border-radius: 4px; margin-bottom: 8px; }\n.aa-skeleton-block { height: 120px; background: #E8ECF1; border-radius: 8px; margin-bottom: 12px; }\n.aa-skeleton.is-active .aa-skeleton-avatar,\n.aa-skeleton.is-active .aa-skeleton-line,\n.aa-skeleton.is-active .aa-skeleton-block { background: linear-gradient(90deg, #E8ECF1 25%, #F0F5FF 37%, #E8ECF1 63%); background-size: 400% 100%; animation: aa-shimmer 1.4s ease infinite; }\n@keyframes aa-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }\n","jsx":"import React from 'react';\nimport './SkeletonPro.css';\n\nexport default function SkeletonPro({ rows = 3, avatar = false, block = false, active = true }) {\n return (\n
\n {avatar ? (\n
\n
\n
\n
\n
\n
\n
\n ) : null}\n {block ?
: null}\n {Array.from({ length: rows }).map((_, i) => (\n
\n ))}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"emptypro","name":"空状态增强 EmptyPro","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"feedback","specLines":["- 多种场景:无数据、无权限、搜索无结果、网络错误","- 插画可静态或动效","- 主按钮、次按钮","- 可自定义文案","- 适用于所有列表页"],"specSummary":"- 多种场景:无数据、无权限、搜索无结果、网络错误","contract":null,"files":{"html":"../frameworks/EmptyPro.html","css":"../frameworks/EmptyPro.css","jsx":"../frameworks/EmptyPro.jsx","vue2":"../frameworks/EmptyPro.vue2.vue","vue3":"../frameworks/EmptyPro.vue3.vue"},"sizes":{"html":1835,"css":1057,"jsx":1387,"vue2":1631,"vue3":1629},"sources":{"html":"\n\n\n \n \n Aurora Admin · EmptyPro(普通 H5)\n \n \n \n\n\n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin EmptyPro 样式 — 对齐 组件7.txt EmptyPro:无数据/无权限/搜索无结果/网络错误 等空状态 */\n\n.aa-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px;\n text-align: center; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-empty-icon { font-size: 48px; margin-bottom: 12px; }\n.aa-empty-title { font-size: 16px; font-weight: 500; color: #1F2329; }\n.aa-empty-desc { font-size: 13px; color: #8C8C8C; margin-top: 8px; line-height: 1.5; max-width: 300px; }\n.aa-empty-actions { margin-top: 16px; display: flex; gap: 8px; }\n.aa-empty-btn { height: 32px; padding: 0 16px; border-radius: 6px; border: 1px solid #E8ECF1; background: #fff; color: #262626; cursor: pointer; font-size: 14px; }\n.aa-empty-btn:hover { border-color: #C9D4F2; color: #2F54EB; }\n.aa-empty-btn.primary { background: #2F54EB; border-color: #2F54EB; color: #fff; }\n.aa-empty-btn.primary:hover { background: #1D39C4; color: #fff; }\n","jsx":"import React from 'react';\nimport './EmptyPro.css';\n\nconst PRESET = {\n empty: { icon: '📭', title: '暂无数据', desc: '当前列表为空,试试新增一条记录。' },\n permission: { icon: '🔒', title: '无访问权限', desc: '你没有查看该内容的权限。' },\n search: { icon: '🔍', title: '未找到结果', desc: '换个关键词或筛选条件再试。' },\n error: { icon: '⚠️', title: '加载失败', desc: '网络异常,请稍后重试。' }\n};\n\nexport default function EmptyPro({ type = 'empty', icon, title, desc, actionText, secondaryText, onAction, onSecondary }) {\n const p = PRESET[type] || {};\n const resolved = {\n icon: icon || p.icon || '📭',\n title: title || p.title || '暂无数据',\n desc: desc || p.desc || ''\n };\n return (\n
\n
{resolved.icon}
\n
{resolved.title}
\n {resolved.desc ?
{resolved.desc}
: null}\n {(actionText || secondaryText) ? (\n
\n {actionText ? : null}\n {secondaryText ? : null}\n
\n ) : null}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"loadingoverlay","name":"加载遮罩 LoadingOverlay","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"feedback","specLines":["- 全屏或局部遮罩","- 转圈品牌色","- 可配置文案","- 阻止交互","- 适用于页面切换、数据提交"],"specSummary":"- 全屏或局部遮罩","contract":null,"files":{"html":"../frameworks/LoadingOverlay.html","css":"../frameworks/LoadingOverlay.css","jsx":"../frameworks/LoadingOverlay.jsx","vue2":"../frameworks/LoadingOverlay.vue2.vue","vue3":"../frameworks/LoadingOverlay.vue3.vue"},"sizes":{"html":1530,"css":1023,"jsx":434,"vue2":544,"vue3":497},"sources":{"html":"\n\n\n \n \n Aurora Admin · LoadingOverlay(普通 H5)\n \n \n \n\n\n \n
\n 局部内容区域\n \n
\n
\n
加载中…
\n
\n
\n\n
\n
\n
正在提交,请稍候
\n
\n\n \n\n\n","css":"/* Aurora Admin LoadingOverlay 样式 — 对齐 组件7.txt LoadingOverlay:全屏或局部遮罩,转圈品牌色,阻止交互 */\n\n.aa-loading-overlay { position: fixed; inset: 0; background: rgba(255,255,255,0.6); z-index: 4000;\n display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;\n font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-loading-overlay.is-inline { position: absolute; border-radius: inherit; }\n.aa-loading-overlay.is-dark { background: rgba(0,0,0,0.45); }\n.aa-loading-spinner { width: 32px; height: 32px; border: 3px solid #E8ECF1; border-top-color: #2F54EB; border-radius: 50%; animation: aa-spin .8s linear infinite; }\n.aa-loading-overlay.is-dark .aa-loading-spinner { border-color: rgba(255,255,255,0.3); border-top-color: #fff; }\n.aa-loading-text { font-size: 14px; color: #262626; }\n.aa-loading-overlay.is-dark .aa-loading-text { color: #fff; }\n@keyframes aa-spin { to { transform: rotate(360deg); } }\n","jsx":"import React from 'react';\nimport './LoadingOverlay.css';\n\nexport default function LoadingOverlay({ visible = false, text = '', inline = false, dark = false }) {\n if (!visible) return null;\n return (\n
\n
\n {text ?
{text}
: null}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"resultvariants","name":"结果页变体 ResultVariants","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"feedback","specLines":["- 成功、失败、警告、信息","- 大图标、标题、描述","- 操作按钮","- 可附带摘要卡片、二维码","- 适用于提交完成、支付结果"],"specSummary":"- 成功、失败、警告、信息","contract":null,"files":{"html":"../frameworks/ResultVariants.html","css":"../frameworks/ResultVariants.css","jsx":"../frameworks/ResultVariants.jsx","vue2":"../frameworks/ResultVariants.vue2.vue","vue3":"../frameworks/ResultVariants.vue3.vue"},"sizes":{"html":1833,"css":1381,"jsx":940,"vue2":1116,"vue3":1090},"sources":{"html":"\n\n\n \n \n Aurora Admin · ResultVariants(普通 H5)\n \n \n \n\n\n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin ResultVariants 样式 — 对齐 组件7.txt ResultVariants:成功/失败/警告/信息 结果页 */\n\n.aa-result { text-align: center; padding: 40px 20px; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-result-icon { width: 64px; height: 64px; border-radius: 50%; margin: 0 auto 16px; display: flex; align-items: center; justify-content: center; font-size: 32px; color: #fff; }\n.aa-result.is-success .aa-result-icon { background: #52C41A; }\n.aa-result.is-error .aa-result-icon { background: #F5222D; }\n.aa-result.is-warning .aa-result-icon { background: #FA8C16; }\n.aa-result.is-info .aa-result-icon { background: #2F54EB; }\n.aa-result-title { font-size: 20px; font-weight: 500; color: #1F2329; }\n.aa-result-desc { font-size: 14px; color: #8C8C8C; margin-top: 8px; line-height: 1.6; }\n.aa-result-actions { margin-top: 24px; display: flex; gap: 8px; justify-content: center; }\n.aa-result-btn { height: 34px; padding: 0 18px; border-radius: 6px; border: 1px solid #E8ECF1; background: #fff; color: #262626; cursor: pointer; font-size: 14px; }\n.aa-result-btn:hover { border-color: #C9D4F2; color: #2F54EB; }\n.aa-result-btn.primary { background: #2F54EB; border-color: #2F54EB; color: #fff; }\n.aa-result-btn.primary:hover { background: #1D39C4; color: #fff; }\n.aa-result-extra { margin-top: 20px; }\n","jsx":"import React from 'react';\nimport './ResultVariants.css';\n\nconst ICON = { success: '✓', error: '✕', warning: '!', info: 'i' };\n\nexport default function ResultVariants({ type = 'success', title, desc, actionText, secondaryText, onAction, onSecondary, extra }) {\n return (\n
\n
{ICON[type] || 'i'}
\n
{title}
\n {desc ?
{desc}
: null}\n {(actionText || secondaryText) ? (\n
\n {actionText ? : null}\n {secondaryText ? : null}\n
\n ) : null}\n {extra ?
{extra}
: null}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"exceptionvariants","name":"异常页变体 ExceptionVariants","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"feedback","specLines":["- 403、404、500、502","- 大号状态码或插画","- 标题、描述","- 返回首页、重试按钮","- 适用于错误处理"],"specSummary":"- 403、404、500、502","contract":null,"files":{"html":"../frameworks/ExceptionVariants.html","css":"../frameworks/ExceptionVariants.css","jsx":"../frameworks/ExceptionVariants.jsx","vue2":"../frameworks/ExceptionVariants.vue2.vue","vue3":"../frameworks/ExceptionVariants.vue3.vue"},"sizes":{"html":1894,"css":1065,"jsx":1311,"vue2":1521,"vue3":1508},"sources":{"html":"\n\n\n \n \n Aurora Admin · ExceptionVariants(普通 H5)\n \n \n \n\n\n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin ExceptionVariants 样式 — 对齐 组件7.txt ExceptionVariants:403/404/500/502 异常页 */\n\n.aa-exception { text-align: center; padding: 56px 20px; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); color: #262626; }\n.aa-exception-code { font-size: 72px; font-weight: 700; color: #2F54EB; line-height: 1; }\n.aa-exception-emoji { font-size: 64px; }\n.aa-exception-title { font-size: 18px; font-weight: 500; color: #1F2329; margin-top: 12px; }\n.aa-exception-desc { font-size: 14px; color: #8C8C8C; margin-top: 8px; }\n.aa-exception-actions { margin-top: 24px; display: flex; gap: 8px; justify-content: center; }\n.aa-exception-btn { height: 34px; padding: 0 18px; border-radius: 6px; border: 1px solid #E8ECF1; background: #fff; color: #262626; cursor: pointer; font-size: 14px; }\n.aa-exception-btn:hover { border-color: #C9D4F2; color: #2F54EB; }\n.aa-exception-btn.primary { background: #2F54EB; border-color: #2F54EB; color: #fff; }\n.aa-exception-btn.primary:hover { background: #1D39C4; color: #fff; }\n","jsx":"import React from 'react';\nimport './ExceptionVariants.css';\n\nconst PRESET = {\n '403': { emoji: '🔒', title: '403 禁止访问', desc: '抱歉,你没有权限访问此页面。' },\n '404': { emoji: '🔍', title: '404 页面不存在', desc: '你访问的页面已被移除或从未存在。' },\n '500': { emoji: '⚙️', title: '500 服务器错误', desc: '服务器开小差了,请稍后再试。' },\n '502': { emoji: '🔌', title: '502 网关错误', desc: '网关无响应,请检查网络后重试。' }\n};\n\nexport default function ExceptionVariants({ type = '404', emoji, title, desc, onHome, onRetry }) {\n const p = PRESET[type] || {};\n const resolved = {\n emoji: emoji || p.emoji || '⚠️',\n title: title || p.title || '出错了',\n desc: desc || p.desc || ''\n };\n return (\n
\n
{resolved.emoji}
\n
{resolved.title}
\n {resolved.desc ?
{resolved.desc}
: null}\n
\n \n \n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"confirmmodal","name":"确认弹窗 ConfirmModal","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"feedback","specLines":["- 用于危险操作","- 标题、描述、图标","- 确认按钮(红/主色)、取消按钮","- 支持加载状态","- 可勾选“不再提示”"],"specSummary":"- 用于危险操作","contract":null,"files":{"html":"../frameworks/ConfirmModal.html","css":"../frameworks/ConfirmModal.css","jsx":"../frameworks/ConfirmModal.jsx","vue2":"../frameworks/ConfirmModal.vue2.vue","vue3":"../frameworks/ConfirmModal.vue3.vue"},"sizes":{"html":1713,"css":1917,"jsx":1525,"vue2":1554,"vue3":1564},"sources":{"html":"\n\n\n \n \n Aurora Admin · ConfirmModal(普通 H5)\n \n \n \n\n\n \n\n
\n
\n
\n !\n
删除该成员?
\n
\n
删除后该成员将无法访问系统,且不可恢复。
\n \n
\n \n \n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin ConfirmModal 样式 — 对齐 组件7.txt ConfirmModal:危险操作确认弹窗,确认(红/主)/取消,可加载、可不再提示 */\n\n.aa-modal-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 2000; display: flex; align-items: center; justify-content: center; }\n.aa-confirm { width: 400px; max-width: 90vw; background: #fff; border-radius: 8px; padding: 24px; box-shadow: 0 8px 24px rgba(0,0,0,0.18);\n font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-confirm-head { display: flex; gap: 12px; align-items: flex-start; }\n.aa-confirm-icon { width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0; color: #fff; font-size: 13px; text-align: center; line-height: 22px; margin-top: 1px; }\n.aa-confirm.is-danger .aa-confirm-icon { background: #F5222D; }\n.aa-confirm.is-primary .aa-confirm-icon { background: #2F54EB; }\n.aa-confirm-title { font-size: 16px; font-weight: 500; color: #1F2329; }\n.aa-confirm-content { font-size: 14px; color: #595959; margin-top: 6px; margin-left: 34px; line-height: 1.6; }\n.aa-confirm-extra { margin: 10px 0 0 34px; font-size: 13px; color: #8C8C8C; display: flex; align-items: center; gap: 6px; }\n.aa-confirm-footer { margin-top: 20px; display: flex; justify-content: flex-end; gap: 8px; }\n.aa-confirm-btn { height: 32px; padding: 0 16px; border-radius: 6px; border: 1px solid #E8ECF1; background: #fff; color: #262626; cursor: pointer; font-size: 14px; }\n.aa-confirm-btn:hover { border-color: #C9D4F2; color: #2F54EB; }\n.aa-confirm-btn.primary { background: #2F54EB; border-color: #2F54EB; color: #fff; }\n.aa-confirm-btn.primary:hover { background: #1D39C4; color: #fff; }\n.aa-confirm-btn.danger { background: #F5222D; border-color: #F5222D; color: #fff; }\n.aa-confirm-btn.danger:hover { background: #cf1322; color: #fff; }\n.aa-confirm-btn:disabled { opacity: .6; cursor: not-allowed; }\n","jsx":"import React, { useState } from 'react';\nimport './ConfirmModal.css';\n\nexport default function ConfirmModal({\n visible = false, title = '确认操作', content = '', confirmType = 'danger',\n confirmText = '确定', cancelText = '取消', loading = false, dontAsk = false,\n onConfirm, onCancel\n}) {\n const [checked, setChecked] = useState(false);\n if (!visible) return null;\n return (\n
{ if (e.target === e.currentTarget) onCancel && onCancel(); }}>\n
\n
\n !\n
{title}
\n
\n
{content}
\n {dontAsk ? (\n \n ) : null}\n
\n \n onConfirm && onConfirm(checked)}\n >\n {loading ? '处理中…' : confirmText}\n \n
\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"alertmodal","name":"提示弹窗 AlertModal","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"feedback","specLines":["- 信息提示","- 图标与颜色对应","- 确定按钮","- 支持自定义内容","- 适用于重要提示"],"specSummary":"- 信息提示","contract":null,"files":{"html":"../frameworks/AlertModal.html","css":"../frameworks/AlertModal.css","jsx":"../frameworks/AlertModal.jsx","vue2":"../frameworks/AlertModal.vue2.vue","vue3":"../frameworks/AlertModal.vue3.vue"},"sizes":{"html":1337,"css":1451,"jsx":854,"vue2":984,"vue3":950},"sources":{"html":"\n\n\n \n \n Aurora Admin · AlertModal(普通 H5)\n \n \n \n\n\n \n\n
\n
\n
\n i\n
系统升级通知
\n
\n
本周六 02:00-04:00 将进行系统维护,期间服务不可用,请提前安排。
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin AlertModal 样式 — 对齐 组件7.txt AlertModal:信息提示弹窗,图标+颜色对应,确定按钮 */\n\n.aa-modal-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 2000; display: flex; align-items: center; justify-content: center; }\n.aa-alert { width: 380px; max-width: 90vw; background: #fff; border-radius: 8px; padding: 24px; box-shadow: 0 8px 24px rgba(0,0,0,0.18);\n font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-alert-head { display: flex; gap: 12px; align-items: flex-start; }\n.aa-alert-icon { width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0; color: #fff; font-size: 13px; text-align: center; line-height: 22px; margin-top: 1px; }\n.aa-alert.is-success .aa-alert-icon { background: #52C41A; }\n.aa-alert.is-error .aa-alert-icon { background: #F5222D; }\n.aa-alert.is-warning .aa-alert-icon { background: #FA8C16; }\n.aa-alert.is-info .aa-alert-icon { background: #2F54EB; }\n.aa-alert-title { font-size: 16px; font-weight: 500; color: #1F2329; }\n.aa-alert-content { font-size: 14px; color: #595959; margin-top: 6px; margin-left: 34px; line-height: 1.6; }\n.aa-alert-footer { margin-top: 20px; display: flex; justify-content: flex-end; }\n.aa-alert-btn { height: 32px; padding: 0 18px; border-radius: 6px; border: none; background: #2F54EB; color: #fff; cursor: pointer; font-size: 14px; }\n.aa-alert-btn:hover { background: #1D39C4; }\n","jsx":"import React from 'react';\nimport './AlertModal.css';\n\nconst ICON = { success: '✓', error: '✕', warning: '!', info: 'i' };\n\nexport default function AlertModal({ visible = false, type = 'info', title = '提示', content = '', okText = '知道了', onOk }) {\n if (!visible) return null;\n return (\n
{ if (e.target === e.currentTarget) onOk && onOk(); }}>\n
\n
\n {ICON[type] || 'i'}\n
{title}
\n
\n
{content}
\n
\n \n
\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"formmodal","name":"表单弹窗 FormModal","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"feedback","specLines":["- 弹窗内嵌表单","- 表单字段可多列","- 确认提交,校验失败阻止关闭","- 支持加载状态","- 关闭重置"],"specSummary":"- 弹窗内嵌表单","contract":null,"files":{"html":"../frameworks/FormModal.html","css":"../frameworks/FormModal.css","jsx":"../frameworks/FormModal.jsx","vue2":"../frameworks/FormModal.vue2.vue","vue3":"../frameworks/FormModal.vue3.vue"},"sizes":{"html":3237,"css":1597,"jsx":2602,"vue2":2501,"vue3":2415},"sources":{"html":"\n\n\n \n \n Aurora Admin · FormModal\n \n \n\n\n
\n

FormModal 表单弹窗

\n

弹窗内嵌表单,确认提交前进行校验,校验失败阻止关闭。

\n\n
\n \n

\n
\n
\n\n
\n
\n
新建用户
\n
\n
\n \n \n
姓名不能为空
\n
\n
\n \n \n
邮箱格式不正确
\n
\n
\n \n \n
\n
\n
\n \n \n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin FormModal 样式 — 弹窗内嵌表单 */\n.aa-formmodal-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.45); display: flex; align-items: center; justify-content: center; z-index: 1000; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-formmodal { width: 440px; max-width: 92vw; background: #fff; border-radius: 10px; box-shadow: 0 12px 48px rgba(0,0,0,0.2); overflow: hidden; }\n.aa-formmodal-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid #E8ECF1; font-weight: 600; color: #1F2329; font-size: 15px; }\n.aa-formmodal-close { border: none; background: none; font-size: 18px; color: #8C8C8C; cursor: pointer; line-height: 1; }\n.aa-formmodal-close:hover { color: #262626; }\n.aa-formmodal-body { padding: 18px; }\n.aa-form-row { margin-bottom: 14px; }\n.aa-form-label { display: block; font-size: 13px; color: #595959; margin-bottom: 6px; }\n.aa-form-label .req { color: #F5222D; margin-right: 3px; }\n.aa-form-input { width: 100%; border: 1px solid #E8ECF1; border-radius: 6px; padding: 8px 10px; font-size: 14px; color: #262626; outline: none; box-sizing: border-box; transition: border-color .15s, box-shadow .15s; }\n.aa-form-input:focus { border-color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.2); }\n.aa-form-input.is-error { border-color: #F5222D; }\n.aa-form-err { font-size: 12px; color: #F5222D; margin-top: 4px; }\n.aa-formmodal-foot { display: flex; justify-content: flex-end; gap: 10px; padding: 12px 18px; border-top: 1px solid #E8ECF1; }\n","jsx":"import React, { useState, useEffect } from 'react';\nimport './FormModal.css';\n\nconst DEFAULT_FIELDS = [\n { key: 'name', label: '姓名', required: true, placeholder: '请输入姓名' },\n { key: 'email', label: '邮箱', required: true, placeholder: 'name@example.com' },\n { key: 'role', label: '角色', placeholder: '如:管理员' }\n];\n\nexport default function FormModal({ visible = false, title = '表单', fields = DEFAULT_FIELDS, value = {}, onCancel, onSubmit }) {\n const [form, setForm] = useState(value);\n const [errors, setErrors] = useState({});\n\n useEffect(() => {\n if (visible) { setForm(value); setErrors({}); }\n }, [visible]);\n\n if (!visible) return null;\n\n const onInput = (key, e) => {\n setForm((f) => ({ ...f, [key]: e.target.value }));\n setErrors((er) => { const n = { ...er }; delete n[key]; return n; });\n };\n const submit = () => {\n const errs = {};\n fields.forEach((f) => {\n const v = (form[f.key] || '').trim();\n if (f.required && !v) errs[f.key] = f.label + '不能为空';\n if (f.key === 'email' && v && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(v)) errs[f.key] = '邮箱格式不正确';\n });\n if (Object.keys(errs).length) { setErrors(errs); return; }\n if (onSubmit) onSubmit({ ...form });\n if (onCancel) onCancel();\n };\n\n return (\n
{ if (e.target === e.currentTarget) onCancel && onCancel(); }}>\n
\n
\n {title}\n \n
\n
\n {fields.map((f) => (\n
\n \n onInput(f.key, e)}\n />\n {errors[f.key] &&
{errors[f.key]}
}\n
\n ))}\n
\n
\n \n \n
\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"fullscreenmodal","name":"全屏弹窗 FullScreenModal","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"feedback","specLines":["- 全屏展示内容","- 标题栏、关闭按钮","- 内容区滚动","- 底部操作栏","- 适用于复杂表单、详情编辑"],"specSummary":"- 全屏展示内容","contract":null,"files":{"html":"../frameworks/FullScreenModal.html","css":"../frameworks/FullScreenModal.css","jsx":"../frameworks/FullScreenModal.jsx","vue2":"../frameworks/FullScreenModal.vue2.vue","vue3":"../frameworks/FullScreenModal.vue3.vue"},"sizes":{"html":1890,"css":894,"jsx":743,"vue2":727,"vue3":714},"sources":{"html":"\n\n\n \n \n Aurora Admin · FullScreenModal\n \n \n\n\n
\n

FullScreenModal 全屏弹窗

\n

全屏展示内容,标题栏 + 关闭按钮 + 滚动内容区 + 底部操作栏。

\n\n
\n \n
\n
\n\n
\n
\n 商品编辑(全屏)\n \n
\n
\n

基础信息

\n

此处可放置复杂表单、富文本编辑、多区块配置等内容。全屏模式便于处理信息密度高的任务。

\n

内容区可滚动,底部固定操作栏始终可见。

\n

详细描述

\n

支持长文、图片、视频等富媒体内容。关闭弹窗时可通过底部按钮提交或取消。

\n
\n
\n \n \n
\n
\n\n \n\n\n","css":"/* Aurora Admin FullScreenModal 样式 — 全屏弹窗 */\n.aa-fs-mask { position: fixed; inset: 0; background: #F5F7FA; z-index: 1100; display: flex; flex-direction: column; font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); }\n.aa-fs-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 20px; background: #fff; border-bottom: 1px solid #E8ECF1; }\n.aa-fs-title { font-weight: 600; color: #1F2329; font-size: 16px; }\n.aa-fs-close { border: none; background: none; font-size: 20px; color: #8C8C8C; cursor: pointer; line-height: 1; }\n.aa-fs-close:hover { color: #262626; }\n.aa-fs-body { flex: 1; overflow: auto; padding: 20px; color: #595959; font-size: 14px; line-height: 1.7; }\n.aa-fs-foot { display: flex; justify-content: flex-end; gap: 10px; padding: 12px 20px; background: #fff; border-top: 1px solid #E8ECF1; }\n","jsx":"import React from 'react';\nimport './FullScreenModal.css';\n\nexport default function FullScreenModal({ visible = false, title = '全屏弹窗', onCancel, onOk, children }) {\n if (!visible) return null;\n return (\n
\n
\n {title}\n \n
\n
{children}
\n
\n \n \n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"themeswitcher","name":"主题切换 ThemeSwitcher","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"system","specLines":["- 明暗主题","- 品牌色切换","- 实时预览","- 保存偏好","- 适用于个性化"],"specSummary":"- 明暗主题","contract":null,"files":{"html":"../frameworks/ThemeSwitcher.html","css":"../frameworks/ThemeSwitcher.css","jsx":"../frameworks/ThemeSwitcher.jsx","vue2":"../frameworks/ThemeSwitcher.vue2.vue","vue3":"../frameworks/ThemeSwitcher.vue3.vue"},"sizes":{"html":2467,"css":1216,"jsx":1165,"vue2":1161,"vue3":1125},"sources":{"html":"\n\n\n \n \n Aurora Admin · ThemeSwitcher\n \n \n\n\n
\n

ThemeSwitcher 主题切换

\n

明暗主题 + 品牌色切换,实时预览,可保存偏好。

\n\n
\n
\n
浅色
\n
深色
\n
\n
\n
\n
\n
\n \n 当前主题:\n
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin ThemeSwitcher 样式 — 明暗主题 + 品牌色切换 */\n.aa-themesw { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }\n.aa-themesw-opt { display: flex; align-items: center; gap: 8px; border: 1px solid #E8ECF1; border-radius: 6px; padding: 8px 12px; cursor: pointer; color: #262626; font-size: 13px; transition: border-color .15s, background .15s, color .15s; }\n.aa-themesw-opt:hover { border-color: #B7C4FF; }\n.aa-themesw-opt.is-active { border-color: #2F54EB; background: #F0F5FF; color: #2F54EB; }\n.aa-themesw-dot { width: 16px; height: 16px; border-radius: 4px; border: 1px solid #E8ECF1; }\n.aa-themesw-dot.dark { background: #1F2329; }\n.aa-themesw-dot.light { background: #fff; }\n.aa-themesw-colors { display: flex; gap: 8px; margin-left: 4px; }\n.aa-themesw-color { width: 22px; height: 22px; border-radius: 50%; cursor: pointer; border: 2px solid transparent; transition: transform .12s; }\n.aa-themesw-color:hover { transform: scale(1.1); }\n.aa-themesw-color.is-active { border-color: #1F2329; }\n.aa-themesw-label { font-size: 12px; color: #8C8C8C; width: 100%; margin-top: 4px; }\n","jsx":"import React from 'react';\nimport './ThemeSwitcher.css';\n\nconst MODES = [\n { value: 'light', label: '浅色' },\n { value: 'dark', label: '深色' }\n];\nconst BRANDS = ['#2F54EB', '#1677FF', '#722ED1', '#13C2C2', '#FA541C', '#EB2F96'];\n\nexport default function ThemeSwitcher({ mode = 'light', brand = '#2F54EB', onModeChange, onBrandChange }) {\n return (\n
\n {MODES.map((m) => (\n onModeChange && onModeChange(m.value)}\n >\n \n {m.label}\n
\n ))}\n
\n {BRANDS.map((c) => (\n onBrandChange && onBrandChange(c)}\n />\n ))}\n
\n
品牌色:{brand}(应用于 CSS 变量 --brand 实时预览)
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"layoutswitcher","name":"布局切换 LayoutSwitcher","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"system","specLines":["- 侧边、顶部、混合导航","- 侧边栏宽度","- 折叠状态","- 保存偏好","- 适用于布局配置"],"specSummary":"- 侧边、顶部、混合导航","contract":null,"files":{"html":"../frameworks/LayoutSwitcher.html","css":"../frameworks/LayoutSwitcher.css","jsx":"../frameworks/LayoutSwitcher.jsx","vue2":"../frameworks/LayoutSwitcher.vue2.vue","vue3":"../frameworks/LayoutSwitcher.vue3.vue"},"sizes":{"html":2044,"css":1014,"jsx":930,"vue2":956,"vue3":969},"sources":{"html":"\n\n\n \n \n Aurora Admin · LayoutSwitcher\n \n \n\n\n
\n

LayoutSwitcher 布局切换

\n

侧边 / 顶部 / 混合导航布局切换,保存偏好。

\n\n
\n
\n
\n
\n
侧边布局
\n
\n
\n
\n
顶部布局
\n
\n
\n
\n
混合布局
\n
\n
\n

\n
\n
\n\n \n\n\n","css":"/* Aurora Admin LayoutSwitcher 样式 — 布局切换 */\n.aa-layoutsw { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); display: flex; gap: 12px; flex-wrap: wrap; }\n.aa-layoutsw-opt { border: 1px solid #E8ECF1; border-radius: 8px; padding: 8px; cursor: pointer; width: 128px; transition: border-color .15s, box-shadow .15s; }\n.aa-layoutsw-opt:hover { border-color: #B7C4FF; }\n.aa-layoutsw-opt.is-active { border-color: #2F54EB; box-shadow: 0 0 0 3px rgba(47,84,235,0.12); }\n.aa-layoutsw-mini { height: 66px; border-radius: 4px; overflow: hidden; display: flex; flex-direction: column; background: #fff; border: 1px solid #E8ECF1; }\n.aa-layoutsw-mini .s-top { height: 16px; background: #2F54EB; }\n.aa-layoutsw-mini .s-body { flex: 1; display: flex; }\n.aa-layoutsw-mini .s-side { width: 22px; background: #B7C4FF; }\n.aa-layoutsw-mini .s-main { flex: 1; background: #F5F7FA; }\n.aa-layoutsw-label { font-size: 13px; color: #262626; text-align: center; margin-top: 6px; }\n","jsx":"import React from 'react';\nimport './LayoutSwitcher.css';\n\nconst OPTIONS = [\n { value: 'side', label: '侧边布局' },\n { value: 'top', label: '顶部布局' },\n { value: 'mixed', label: '混合布局' }\n];\n\nexport default function LayoutSwitcher({ value = 'side', onChange }) {\n return (\n
\n {OPTIONS.map((o) => (\n onChange && onChange(o.value)}\n >\n
\n {o.value !== 'side' &&
}\n
\n {o.value !== 'top' &&
}\n
\n
\n
\n
{o.label}
\n
\n ))}\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"densityswitcher","name":"密度切换 DensitySwitcher","tier":"extension","confidence":"high","specBatch":7,"specFile":"组件7.txt","contractRef":null,"category":"system","specLines":["- 宽松、默认、紧凑","- 影响表格、表单间距","- 局部可独立设置","- 保存偏好","- 适用于信息密度调整"],"specSummary":"- 宽松、默认、紧凑","contract":null,"files":{"html":"../frameworks/DensitySwitcher.html","css":"../frameworks/DensitySwitcher.css","jsx":"../frameworks/DensitySwitcher.jsx","vue2":"../frameworks/DensitySwitcher.vue2.vue","vue3":"../frameworks/DensitySwitcher.vue3.vue"},"sizes":{"html":1716,"css":1094,"jsx":1051,"vue2":1099,"vue3":1100},"sources":{"html":"\n\n\n \n \n Aurora Admin · DensitySwitcher\n \n \n\n\n
\n

DensitySwitcher 密度切换

\n

宽松 / 默认 / 紧凑,影响表格、表单间距,可局部独立设置并保存。

\n\n
\n
\n \n \n \n
\n
\n
用户管理128 项
\n
角色管理12 项
\n
菜单管理36 项
\n
\n
\n
\n\n \n\n\n","css":"/* Aurora Admin DensitySwitcher 样式 — 信息密度切换 */\n.aa-densw { font-family: var(--font-family, -apple-system, \"PingFang SC\", \"Microsoft YaHei\", sans-serif); display: inline-flex; border: 1px solid #E8ECF1; border-radius: 6px; overflow: hidden; }\n.aa-densw-opt { padding: 8px 16px; font-size: 13px; cursor: pointer; color: #595959; background: #fff; border: none; transition: background .15s, color .15s; }\n.aa-densw-opt + .aa-densw-opt { border-left: 1px solid #E8ECF1; }\n.aa-densw-opt:hover { color: #2F54EB; }\n.aa-densw-opt.is-active { background: #2F54EB; color: #fff; }\n\n.aa-densw-demo { margin-top: 12px; border: 1px solid #E8ECF1; border-radius: 8px; overflow: hidden; }\n.aa-densw-demo.loose .aa-densw-row { padding: 14px 16px; }\n.aa-densw-demo.default .aa-densw-row { padding: 10px 16px; }\n.aa-densw-demo.compact .aa-densw-row { padding: 6px 16px; font-size: 13px; }\n.aa-densw-row { border-bottom: 1px solid #E8ECF1; color: #262626; display: flex; justify-content: space-between; }\n.aa-densw-row:last-child { border-bottom: none; }\n.aa-densw-row .muted { color: #8C8C8C; }\n","jsx":"import React from 'react';\nimport './DensitySwitcher.css';\n\nconst OPTIONS = [\n { value: 'loose', label: '宽松' },\n { value: 'default', label: '默认' },\n { value: 'compact', label: '紧凑' }\n];\nconst ROWS = [\n { name: '用户管理', count: '128 项' },\n { name: '角色管理', count: '12 项' },\n { name: '菜单管理', count: '36 项' }\n];\n\nexport default function DensitySwitcher({ value = 'default', onChange }) {\n return (\n
\n
\n {OPTIONS.map((o) => (\n onChange && onChange(o.value)}\n >\n {o.label}\n \n ))}\n
\n
\n {ROWS.map((r, i) => (\n
\n {r.name}\n {r.count}\n
\n ))}\n
\n
\n );\n}\n","vue2":"\n\n\n\n\n","vue3":"\n\n\n\n\n"}},{"slug":"employeecard","name":"员工卡片 EmployeeCard","tier":"extension","confidence":"high","specBatch":9,"specFile":"组件9.txt","contractRef":null,"category":"display","specLines":["- 用于员工列表或组织架构展示","- 包含:头像、姓名、工号、部门、职位、状态(在职/离职)","- 卡片悬停显示快捷操作:发消息、查看详情、编辑","- 支持标签展示技能或角色","- 可配置显示联系方式(电话、邮箱)","- 卡片圆角 8px,阴影低,内边距 16px"],"specSummary":"- 用于员工列表或组织架构展示","contract":null,"files":{"html":"../frameworks/employeecard.html","css":"../frameworks/EmployeeCard.css","jsx":"../frameworks/EmployeeCard.jsx","vue2":"../frameworks/EmployeeCard.vue2.vue","vue3":"../frameworks/EmployeeCard.vue3.vue"},"sizes":{"html":4523,"css":1864,"jsx":2307,"vue2":3568,"vue3":3632},"sources":{"html":"\n\n\n\n\nAurora Admin · EmployeeCard (H5)\n\n\n\n\n
\n

Aurora Admin · EmployeeCard 组件(纯 H5)

\n

对齐 EmployeeCard 规范:头像/姓名/工号/部门/职位/状态、悬停快捷操作、技能标签、可显联系方式、圆角8px/阴影低/内边距16px

\n\n
\n
\n
\n \n \n \n
\n
\n
张
\n
\n
张伟 在职
\n
工号:E1001
\n
\n
\n
部门:技术中心 · 前端组
职位:高级前端工程师
\n
ReactTypeScript架构
\n
\n\n
\n
\n \n \n \n
\n
\n
李
\n
\n
李娜 离职
\n
工号:E1024
\n
\n
\n
部门:产品中心 · 产品组
职位:产品经理
\n
B端数据
\n
电话:138-0000-0000
邮箱:lina@example.com
\n
\n
\n
\n\n\n","css":"/* Aurora Admin EmployeeCard 样式 — 对齐 组件9.txt EmployeeCard 规范 */\n.aa-emp-card { width: 280px; background: #fff; border-radius: 8px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); padding: 16px; position: relative; box-sizing: border-box; }\n.aa-emp-top { display: flex; align-items: center; gap: 12px; }\n.aa-emp-avatar { width: 48px; height: 48px; border-radius: 50%; background: #F0F5FF; color: #2F54EB; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 18px; flex-shrink: 0; overflow: hidden; }\n.aa-emp-avatar img { width: 100%; height: 100%; object-fit: cover; }\n.aa-emp-name { font-size: 16px; font-weight: 600; color: #1F2329; display: flex; align-items: center; gap: 8px; }\n.aa-emp-status { font-size: 12px; padding: 0 6px; border-radius: 2px; line-height: 18px; }\n.aa-emp-status.active { background: #F6FFED; color: #52C41A; }\n.aa-emp-status.left { background: #F5F5F5; color: #8C8C8C; }\n.aa-emp-id { font-size: 12px; color: #8C8C8C; margin-top: 2px; }\n.aa-emp-meta { margin-top: 12px; font-size: 13px; color: #595959; line-height: 22px; }\n.aa-emp-tags { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 6px; }\n.aa-emp-tag { background: #F0F5FF; color: #2F54EB; font-size: 12px; padding: 1px 8px; border-radius: 2px; }\n.aa-emp-contact { margin-top: 12px; font-size: 13px; color: #595959; line-height: 22px; border-top: 1px solid #F0F0F0; padding-top: 10px; }\n.aa-emp-actions {\n position: absolute; top: 12px; right: 12px; display: none; gap: 4px;\n background: #fff; border: 1px solid #E8ECF1; border-radius: 4px; padding: 2px;\n}\n.aa-emp-card:hover .aa-emp-actions { display: flex; }\n.aa-emp-actions button { border: none; background: none; cursor: pointer; color: #8C8C8C; font-size: 12px; padding: 2px 6px; border-radius: 3px; }\n.aa-emp-actions button:hover { color: #2F54EB; background: #F0F5FF; }\n","jsx":"import React from 'react';\nimport './EmployeeCard.css';\n\n/**\n * Aurora Admin EmployeeCard(React)\n * 对齐 组件9.txt EmployeeCard 规范:头像/姓名/工号/部门/职位/状态、悬停快捷操作、标签、可显联系方式、圆角8px/阴影低/内边距16px\n * employee: { name, empId, dept, position, status:'active'|'left', avatar?, tags?, phone?, email? }\n */\nexport default function EmployeeCard({ employee, showContact = false, onMessage, onView, onEdit }) {\n const statusText = employee.status === 'left' ? '离职' : '在职';\n return (\n
\n
\n \n \n \n
\n\n
\n
\n {employee.avatar\n ? {employee.name}\n : {(employee.name || '?').charAt(0)}}\n
\n
\n
\n {employee.name}\n {statusText}\n
\n
工号:{employee.empId}
\n
\n
\n\n
\n 部门:{employee.dept}
职位:{employee.position}\n
\n\n {employee.tags && employee.tags.length > 0 && (\n
\n {employee.tags.map((t) => {t})}\n
\n )}\n\n {showContact && (employee.phone || employee.email) && (\n
\n {employee.phone &&
电话:{employee.phone}
}\n {employee.email &&
邮箱:{employee.email}
}\n
\n )}\n
\n );\n}\n\n/*\n用法示例:\nimport EmployeeCard from './EmployeeCard';\nconst emp = { name: '张伟', empId: 'E1001', dept: '技术中心 · 前端组', position: '高级前端工程师', status: 'active', tags: ['React','TS'] };\n console.log(e)} />\n*/\n","vue2":"\n\n\n\n\n\n","vue3":"\n\n\n\n\n\n"}}]};