sky-ui-kit 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/AI-GUIDE.md CHANGED
@@ -2,8 +2,42 @@
2
2
 
3
3
  适用于 Vue 3 + TypeScript。此文件描述包的真实 API 和约束,不是自动执行的代理指令。AI 工具需要主动读取这些文档;安装包不会自动改变其上下文。
4
4
 
5
+ 预览站已移除“统一组件入口 / 组件入口与兼容”菜单与展示示例。安装和导入方式查看快速开始,组件效果查看各组件页面;此调整不改变组件包出口或 API。
6
+
7
+ 项目选择与时间展示为 AppSelect、TreeSelectPanel、TimeRangeQuickSelect、MapTimeline 分别提供查看代码与复制入口;各自代码从 src/examples.ts 同步生成,使用 sky-ui-kit 公开封装组件,包含状态与 change 事件处理。
8
+
9
+ 四个项目选择与时间组件分别由 AppSelectPreview.vue、TreeSelectPreview.vue、TimeRangePreview.vue、MapTimelinePreview.vue 展示。每个组件有独立卡片、导航锚点、状态和查看代码入口,不再使用 ProjectComponents 合并展示。
10
+
11
+ 分割线、链接、间距和文本均有独立效果页与完整复制代码。示例由对应 Demo.vue 原始源码生成,保留属性、默认插槽、链接图标与点击反馈;业务导航由应用接入,UiSpace 不新增业务事件。
12
+
13
+ 表单 UiForm / UiFormItem 提供测站配置独立示例,包含 model、rules、validate、resetFields、禁用状态、输入/选择/日期/数字/单选/多选/开关及文本域。FormDemo.vue 同步生成复制代码;提交仅显示本地数据,实际请求由应用接入。
14
+
15
+ UiAutocomplete 提供独立站点搜索示例:fetch-suggestions 回调、聚焦建议、仅输入触发、图标、自定义建议插槽、选择/清空事件及禁用状态。原项目静态扫描未发现自动补全调用,此为站点搜索场景建议,非原页面复刻。AutocompleteDemo 源码同步生成网站复制代码与本文。
16
+
17
+ 预览左侧菜单不再单列“表单组件”,其组件入口合并到“基础组件”;组件、子组件关系、示例和公开 API 保留。底层组件目录元数据仍保留原分类,导航使用合并显示。
18
+
19
+ 选择、时间、级联、颜色、日期、数字、标签、验证码、评分、输入和折叠动画入口提供各自独立 Demo 与代码,不再只展示导入语句。示例演示本地状态和 change 反馈,实际请求由应用接入;面板使用对应 Panel 组件。
20
+
21
+ 已完成的基础及表单控件示例常驻同一长页面,每项独立展示、独立状态、独立代码入口;菜单点击滚动定位,避免复用顶部单一展示区。尚未完成的组件保留 API 索引入口。
22
+
23
+ 时间下拉选择、穿梭框、树选择器、上传、提及也有常驻独立示例与复制代码。上传演示使用 auto-upload=false,只处理本地文件选择/移除/数量限制,真实上传通过应用 http-request 接入;不虚构服务器上传成功。
24
+
25
+ 数据展示、导航、反馈、其他配置的主组件与服务/指令连续展示独立示例,子组件在父组件组合中展示,菜单定位父示例。UiPopper 是底层定位出口,页面通过 UiPopover 展示使用场景,不宣称提供完整底层交互示例。全部示例从 CatalogDemo 源码生成复制代码。
26
+
27
+ 项目 SVG 图标菜单定位到图标页面内的 SvgIcon 独立锚点,不再跳到 UiIcon 通用入口;两个菜单的 hash 分开,保持项目图标选择状态。
28
+
5
29
  ## 文档读取顺序
6
30
 
31
+ 安装或升级最新正式版使用 `npm install sky-ui-kit@latest`。latest 指向 npm 的正式发布标签,尚未发布的源码修改不在安装包内;依据当前已安装包的版本和文档使用组件。
32
+
33
+ 团队先读取 llms.txt,并结合 AI-CHANGELOG.md 了解模块变更及接入影响。后续所有代码、预览页面、样式和接入说明修改均同步相关 AI 文档,并运行 npm run docs:ai;快速开始代码与页面使用同一份源文件生成。
34
+
35
+ 预览站导航按“使用指南 / 团队专用组件 / 项目特色组件”分区;团队专用组件基于 Element Plus 封装。一级分类使用手风琴展开,二级列出主组件,结构子组件收在对应主组件下并可单独展开。搜索子组件时保留父组件上下文,选择后自动展开所在分类及父组件。
36
+
37
+ 预览站目录支持中文 / Ui 名称 / El 名称搜索及分类折叠。Element Plus 全部标准组件、服务和指令入口来自 src/element-plus-catalog.ts,每项提供导入名称与官方 API 索引;完整分类见 ELEMENT-PLUS-COVERAGE.md。API 索引不代表该组件已具备独立交互示例。“待补充”用于尚无页面的指南入口。SiteNavigation 仅用于预览网站,不属于组件包公开 API。
38
+
39
+ 基础层基准为 Element Plus 2.14.7,项目特色组件保留原名。结构子组件放在对应 Ui 父组件内部,不独立提供主题边界。UiLoading 使用 service(options) / directive,不提供 install;vUiInfiniteScroll、vUiPopover 保留官方指令语义。不要将内部工具函数作为 Ui 组件导入。
40
+
7
41
  1. llms.txt:入口和文档索引。
8
42
  2. components.json:完整公开组件、服务和指令清单;项目组件属性、事件、插槽、默认表达式。
9
43
  3. COMPONENT-API.md:项目组件精确 API 与类型定义。
@@ -12,6 +46,8 @@
12
46
 
13
47
  ## 生成代码的规则
14
48
 
49
+ - 滚动条直接 `import { ElScrollbar } from 'element-plus'`,不定义 UiScrollbar 或从 sky-ui-kit 导入滚动条。预览目录保留滚动条入口以索引官方 API;它没有库包装层,样式取决于已有应用主题范围。
50
+
15
51
  - 在 main.ts 只引入一次 element-plus/dist/index.css 和 sky-ui-kit/style.css。
16
52
  - 按需从 sky-ui-kit 导入组件,不需要库主题 store、Pinia 或全局插件注册。项目已有插件注册继续保留。
17
53
  - style.css 默认隔离;global.css 是主动整站换肤入口,不能默认添加。
@@ -23,9 +59,39 @@
23
59
  - MapTools 的 context-change 返回布尔值;当前没有外部受控右键状态 prop,不要虚构 v-model:contextEnabled。
24
60
  - AtlasRightToggleButton 使用 open + toggle;不是 v-model:open。TreeSelectPanel 的 select 参数为 node, category,change 参数为 { node, category }。
25
61
  - MapTimeline 的 modelValue 是索引;change 返回 TimelinePoint。不要把日期字符串传给 modelValue。
26
- - 本包并不覆盖 Element Plus 全部组件。components.json 不存在的 Ui 名称不能假定可导入,例如链接和面包屑可直接导入 Element Plus。
62
+ - 标准层覆盖基准版本的官方组件、服务与指令;以 components.json 核对实际出口,不假定导出内部工具或未来版本组件。链接与面包屑使用 UiLink / UiBreadcrumb / UiBreadcrumbItem。
27
63
  - 保留项目现有业务代码和注释,先局部迁移核对,不能用组件示例替换整个业务页面。
28
64
 
65
+ ## 按钮独立示例
66
+
67
+ ## 布局容器与目录
68
+
69
+ 布局容器及其四个子组件统一显示完整布局页;目录新增“地图业务布局”子项,直接定位 project-template-layout 示例。
70
+
71
+ TemplateLayoutPreview 独立说明地图业务布局,TemplateLayoutDemo 提供可操作右栏、时间轴及避让轮廓;组合示例使用库按钮与应用侧 CSS,不假定 sky-ui-kit 导出 TemplateLayout。原地图实例、ui store、工具栏、图层和主题弹窗仍由应用维护。
72
+
73
+ 布局容器先展示顶栏、左侧栏、主体、底栏齐全的整体布局及代码,再依此顺序展示四个区域。整体示例 fullLayoutExample 同步网站和 AI 文档。
74
+
75
+ 点击任何布局子组件均同时显示侧栏、顶栏、底栏、主体四个独立示例,菜单定位各自锚点。演示使用明确外框、区域虚线和主色高亮;包装组件转发 class 时预览局部样式通过原生外框与 :deep 选择器匹配。示意边框为展示样式。
76
+
77
+ UiAside、UiHeader、UiFooter、UiMain 的目录入口分别提供实际布局效果与独立可复制代码,由 ContainerPartPreview 展示;使用 UiContainer 保留上下文,分别演示 width、height 和主体剩余空间。结构组件无新增业务事件,使用默认插槽放置内容。
78
+
79
+ 全局融合以 GLOBAL-ADAPTATION.md 为准:原 main.ts VFit 1920×912 auto → App.vue 根 --cp-vfit-scale → cp 尺寸 token → 原 theme store 与布局 → 库组件。保留原 VFit 注册、两个 watch、高度链、中文 locale 和业务地图样式;库不注册 VFit,也不接管 resize。不要额外叠加 transform/zoom。布局页与下方生成代码共用 global-adaptation-examples.ts;body 浮层、地图/图表 resize 和窄屏重排由应用处理。
80
+
81
+ 布局示例共六种:系统侧栏、无菜单整宽、地图浮动、横向导航、双侧栏、上下分区。后三种由 LayoutVariants 演示,菜单切换、信息栏和过程区可操作,完整代码自动同步到本文。系统/无菜单/地图布局对应原项目,双侧栏和普通上下分区是组合建议,不宣称原项目已使用相同容器实现;Footer 为可选状态或过程区域。
82
+
83
+ 布局容器新增 BusinessLayoutExamples:系统页菜单切换、测站筛选与表格、无菜单整宽页,以及地图菜单/工具/图例/时间轴/测站面板组合。菜单、工具、测站与时间轴事件展示反馈;右面板开关改变工具和时间轴避让。可复制代码直接来自预览组件源码,AI 示例同步生成。业务地图宿主为示意背景,不接地图或请求;图例阈值颜色为业务示例,不作为通用主题变量。
84
+
85
+ 左侧移除 UiRow / UiCol(栅格行 / 栅格列)入口,仅调整预览目录,已有公开 API 保留。UiContainer 页面参考原项目 MainLayout、ContentLayoutSystem、MainLayoutMap、ContentLayoutMap、ContentLayoutNomenu 与 TemplateLayout,提供系统管理布局、地图浮动布局、侧栏收起和右面板避让示例;containerExample 为可复制基础容器示例,滚动直接使用 ElScrollbar。地图示意不创建地图实例。
86
+
87
+ TemplateLayout 仍由原项目维护,不是 sky-ui-kit 出口;实际 props 为 rightPanelWidth / rightPanelDefaultOpen / rightPanelEnabled。插槽 top / toolbar / right / legend / timeline / content / right-toggle / map-toolbar-info,right-toggle 提供 open / toggle。地图、图层、区域定位、ui store、RouterView、权限和监听释放保留应用侧。部分原页面传 timeline-visible,但当前 TemplateLayout 没声明该 prop,实际时间轴依据 timeline 插槽是否存在;不要将它写入库 API。
88
+
89
+ 原地图布局为底层地图 + pointer-events:none 的覆盖层,只有工具、图例、面板恢复交互;迁移时保留该规则,避免透明层挡住地图。尺寸继承 --header-height / --sidebar-width / --rightpanel-width / --layout-gutter 和 VFit 变量;容器高度链及 min-width/min-height:0 要核对。
90
+
91
+ ## 按钮效果
92
+
93
+ UiButton 页面提供语义色、plain、round、text/bg/link、图标/circle、尺寸、disabled/loading 和可结束的模拟保存。源例 buttonModuleExample 与下方生成代码同步。点击事件由应用处理,真实请求在 finally 中恢复 loading;纯图标按钮提供 aria-label。主题只来自已有变量,不增加业务请求或权限行为。
94
+
29
95
  ## 默认样式
30
96
 
31
97
  明亮六色:#ffffff / #1f2937 / #1677ff / #dbe3ef99 / #ffffffd9 / #ffffff。
@@ -40,9 +106,401 @@
40
106
  ## 主题与业务示例
41
107
 
42
108
  以下完整代码由预览站示例生成;公共入口完成后,各页面不重复引入 CSS。
43
-
109
+
110
+ ## 图标模块
111
+
112
+ 自有 SVG 必须按下方“图标安装 / Vite 配置 / 入口注册 / 页面配置”完成接入:安装 vite-plugin-svg-icons,合并 Vite plugins,在 main.ts 导入 virtual:svg-icons-register,SvgIcon 传 prefix="icon-"。新项目推荐 icon-[dir]-[name];原项目已配置 icon-[name],迁移首轮保留规则。库内置资源自动注册不需要该插件。两种来源不能混淆。
113
+
114
+ ## 原项目使用依据与适配
115
+
116
+ COMPONENT-USAGE.md / component-usage.json 为 2026-10-12 只读扫描结果,覆盖 590 个 Vue / TS / JS 文件、57 种 EP 模板组件、3 种显式服务导入和 56 个公共 Vue 组件。频次是引用文件数;动态、别名、自动注册和间接调用可能漏计。含原工作区未提交源码,仅作为参考。ADAPTATION.md 给出逐模块方案、业务边界、SVG 路由转换、依赖、主题、VFit、浮层和验证顺序。原项目接入页展示当前清单与方案,不再是占位页。
117
+
118
+ 生成业务替换代码前先核对 COMPONENT-USAGE.md 的原路径和 ADAPTATION.md:基础 Ui 成组迁移;common 组件沿用实际 props/events;菜单/地图工具经应用薄适配层接路由、权限、请求;TableLayout、图表、图层、图例、用户权限等尚未公开的组件仍留在应用。原项目 EP 声明 ^2.14.2 不代表实际安装低于 2.14.7,检查 lockfile 与已安装版本再决定升级。
119
+
120
+ UiIcon 保留 ElIcon 的 size、color、默认插槽;通用图标从 @element-plus/icons-vue 导入。SvgIcon 是项目 SVG 组件,274 个内置图标来自原项目 src/assets/icons。projectIcons 提供 name、category、source、followsColor、svg 清单;ProjectIconName 是内置名称类型。内置图标在挂载及名称变化时按需注册,无需使用方安装 Vite SVG 插件。registerProjectIcons(names?, document?) 可显式注册指定名称,不传 names 注册全部;SSR 导入安全,SVG sprite 在客户端创建。
121
+
122
+ SvgIcon 保留 name、size、title、decorative,新增 color 和 prefix。默认 prefix 为 sky-icon-,防止与原项目 icon- 冲突;原项目已有 sprite 或使用方自定义 sprite 可传 prefix="icon-"。此时资源注册由使用方负责,库不覆盖图标。数字尺寸按 --cp-font-1(默认 1px)缩放。属性及原生事件透传 SVG,无内容插槽。未知名称不显示图形。固定色和多色 SVG 保留原配色,color 只影响使用 currentColor 的路径。子目录图标采用目录前缀(如 threeDivision-measure),以 projectIcons 清单为准。
123
+
124
+ 菜单和工具栏的 icon 属性仍接收 Vue 组件,项目图标可用 () => h(SvgIcon, { name: 'ii_rain' }) 绑定。不要把项目名称字符串直接传给 Element Plus 的 icon 属性。地图点位 PNG、Logo 等业务图片未纳入 SVG 组件。
125
+
126
+ 维护 SVG 快照:node scripts/sync-project-icons.mjs "原项目/src/assets/icons",仅只读读取参考项目;生成后核对重复名称与配色并运行 npm run docs:ai。
127
+
44
128
  <!-- GENERATED EXAMPLES -->
45
129
 
130
+ ### 快速开始:安装最新正式版本
131
+
132
+ ```sh
133
+ # 安装或升级到 npm 已发布的最新正式版本
134
+ npm install sky-ui-kit@latest
135
+
136
+ # 项目缺少这些依赖时再安装(Vue 3 项目)
137
+ npm install "element-plus@^2.14.7" @element-plus/icons-vue motion-v
138
+ ```
139
+
140
+ ### 快速开始:公共入口
141
+
142
+ ```ts
143
+ // main.ts:公共样式只引入一次
144
+ import { createApp } from 'vue'
145
+ import App from './App.vue'
146
+ import 'element-plus/dist/index.css'
147
+ import 'sky-ui-kit/style.css'
148
+
149
+ // 已有项目保留原插件注册、路由和挂载逻辑
150
+ createApp(App).mount('#app')
151
+ ```
152
+
153
+ ### 快速开始:首个组件
154
+
155
+ ```vue
156
+ <script setup lang="ts">
157
+ import { ref } from 'vue'
158
+ import { UiButton, AppSelect, UiMessage } from 'sky-ui-kit'
159
+ const station = ref('hankou')
160
+ const options = [
161
+ { label: '汉口站', value: 'hankou' },
162
+ { label: '宜昌站', value: 'yichang' }
163
+ ]
164
+ function confirm() { UiMessage.success('当前站点:' + station.value) }
165
+ </script>
166
+ <template>
167
+ <AppSelect v-model="station" :options="options" />
168
+ <UiButton type="primary" @click="confirm">确认选择</UiButton>
169
+ </template>
170
+ ```
171
+
172
+ ### 快速开始:团队 AI 读取提示
173
+
174
+ ```text
175
+ 请先读取 node_modules/sky-ui-kit/llms.txt,并按索引读取:
176
+ - components.json:当前已安装版本的公开组件与 API 清单
177
+ - docs/AI-GUIDE.md:使用规则和完整示例
178
+ - docs/AI-CHANGELOG.md:模块变更及接入影响
179
+ - docs/COMPONENT-API.md:属性、事件、插槽与类型
180
+ - docs/CSS-THEME.md:主题变量、样式隔离与浮层限制
181
+ - docs/INTEGRATION.md:原项目替换和业务接线
182
+ - docs/COMPONENT-USAGE.md:原项目实际使用的组件与调用位置
183
+ - docs/ADAPTATION.md:逐模块替换、Vite 图标配置、主题及业务适配方案
184
+
185
+ 请依据当前已安装包的版本与文档生成代码,不要虚构组件名称或 API。
186
+ 公共 CSS 仅在 main.ts 引入一次;业务请求、路由、权限由应用接入。
187
+ ```
188
+
189
+ ### 图标安装:内置与自有资源
190
+
191
+ ```sh
192
+ # 仅使用库内置 SVG:安装组件库即可
193
+ npm install sky-ui-kit@latest
194
+
195
+ # 项目要维护自己的 src/assets/icons/*.svg 时,再安装 Vite 插件
196
+ npm install -D vite-plugin-svg-icons
197
+ ```
198
+
199
+ ### 自有 SVG:Vite 配置
200
+
201
+ ```js
202
+ // vite.config.js / vite.config.ts
203
+ import { defineConfig } from 'vite'
204
+ import vue from '@vitejs/plugin-vue'
205
+ import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
206
+ import path from 'node:path'
207
+
208
+ export default defineConfig({
209
+ plugins: [
210
+ vue(),
211
+ // 合并到现有 plugins;保留原来的自动导入、代理、别名等配置。
212
+ createSvgIconsPlugin({
213
+ iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
214
+ symbolId: 'icon-[dir]-[name]',
215
+ svgoOptions: false,
216
+ }),
217
+ ],
218
+ })
219
+ ```
220
+
221
+ ### 自有 SVG:入口注册
222
+
223
+ ```ts
224
+ // main.ts:各资源只引入一次;保留原路由、Pinia、VFit 等注册。
225
+ import 'virtual:svg-icons-register'
226
+ import 'element-plus/dist/index.css'
227
+ import 'sky-ui-kit/style.css'
228
+ // TypeScript 虚拟模块声明放在 src/vite-env.d.ts,见下一段。
229
+ ```
230
+
231
+ ### 自有 SVG:TypeScript 虚拟模块声明
232
+
233
+ ```ts
234
+ // src/vite-env.d.ts:已有文件追加此声明,不覆盖原有声明。
235
+ declare module 'virtual:svg-icons-register'
236
+ ```
237
+
238
+ ### 自有 SVG:页面配置
239
+
240
+ ```vue
241
+ <script setup lang="ts">
242
+ import { SvgIcon } from 'sky-ui-kit'
243
+ </script>
244
+ <template>
245
+ <!-- 库内置图标:自动注册,无需 Vite SVG 插件 -->
246
+ <SvgIcon name="ii_rain" size="24px" />
247
+ <!-- 使用方 src/assets/icons/company.svg -->
248
+ <SvgIcon name="company" prefix="icon-" size="24px" />
249
+ <!-- 使用方 src/assets/icons/weather/rain.svg -->
250
+ <SvgIcon name="weather-rain" prefix="icon-" title="降水" :decorative="false" />
251
+ </template>
252
+ ```
253
+
254
+ ### 原项目 SVG 命名适配
255
+
256
+ ```js
257
+ // 原业务项目已有配置:保留即可,无需重复安装和注册。
258
+ createSvgIconsPlugin({
259
+ iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
260
+ symbolId: 'icon-[name]',
261
+ svgoOptions: false,
262
+ })
263
+
264
+ // 替换原 SvgIcon 的 import 后,原 sprite 用 prefix="icon-":
265
+ // <SvgIcon name="ii_rain" prefix="icon-" />
266
+ // 若切换为库内置图标:<SvgIcon name="ii_rain" />
267
+ // 新项目推荐 icon-[dir]-[name],同名子目录不会冲突。
268
+ // 旧项目改命名规则前必须检查所有 name 和路由 meta.icon。
269
+ ```
270
+
271
+ ### 全局适配:fitBridgeExample
272
+
273
+ ```ts
274
+ // 原项目 App.vue 已有这段桥接,直接保留。
275
+ import { watch, onBeforeUnmount } from 'vue'
276
+ import { useFitScale } from 'vfit'
277
+ import { useThemeStore } from '@/stores/theme'
278
+ const fitScale = useFitScale()
279
+ const theme = useThemeStore()
280
+ const stopScaleWatch = watch(fitScale, scale => {
281
+ document.documentElement.style.setProperty('--cp-vfit-scale', String(scale))
282
+ }, { immediate: true })
283
+ const stopThemeWatch = watch(() => theme.$state, () => theme.applyCssVars(), {
284
+ immediate: true, deep: true,
285
+ })
286
+ onBeforeUnmount(() => {
287
+ stopScaleWatch()
288
+ stopThemeWatch()
289
+ document.documentElement.style.removeProperty('--cp-vfit-scale')
290
+ })
291
+ ```
292
+
293
+ ### 全局适配:fitEntryExample
294
+
295
+ ```ts
296
+ // 原项目 main.ts 已配置,融合时保留,不重复 app.use。
297
+ import { createFitScale } from 'vfit'
298
+ import 'vfit/style.css'
299
+ import 'element-plus/dist/index.css'
300
+ import './styles/theme.css'
301
+ import './styles/base.css'
302
+ import 'sky-ui-kit/style.css'
303
+
304
+ // 放在现有 app 注册链中;路由和 Pinia 等注册保留。
305
+ app.use(createFitScale({
306
+ target: '#app',
307
+ designWidth: 1920,
308
+ designHeight: 912,
309
+ scaleMode: 'auto',
310
+ }))
311
+ ```
312
+
313
+ ### 全局适配:fitLayoutExample
314
+
315
+ ```css
316
+ /* 应用侧布局样式;全局 reset 仍留在原 base.css。 */
317
+ html, body, #app { height: 100%; margin: 0; }
318
+ .app-shell { height: 100%; min-width: 0; overflow: hidden; }
319
+ .workspace {
320
+ position: relative;
321
+ height: calc(100% - var(--header-height, calc(58px * var(--cp-vfit-scale, 1))));
322
+ min-width: 0;
323
+ min-height: 0;
324
+ }
325
+ .page-content { min-width: 0; min-height: 0; overflow: hidden; }
326
+ .business-panel {
327
+ width: var(--rightpanel-width, calc(320px * var(--cp-vfit-scale, 1)));
328
+ padding: var(--layout-gutter, calc(10px * var(--cp-vfit-scale, 1)));
329
+ font-size: var(--font-size-body, calc(14px * var(--cp-vfit-scale, 1)));
330
+ }
331
+ /* 不再对这些已消费缩放变量的组件添加第二套 transform:scale / zoom。 */
332
+ ```
333
+
334
+ ### UiAsideExample
335
+
336
+ ```vue
337
+ <script setup lang="ts">
338
+ import { UiContainer, UiAside, UiHeader, UiFooter, UiMain } from 'sky-ui-kit'
339
+ </script>
340
+ <template>
341
+ <UiContainer direction="horizontal" class="layout-demo">
342
+ <UiAside width="160px" class="focus-region">业务菜单</UiAside>
343
+ <UiMain>内容区域</UiMain>
344
+ </UiContainer>
345
+ </template>
346
+ <style scoped>
347
+ .layout-demo { height: 240px; border: 2px solid var(--color-border, #c5d2e5); border-radius: 8px; overflow: hidden; }
348
+ .layout-demo :deep(.el-main), .layout-demo :deep(.el-aside), .layout-demo :deep(.el-header), .layout-demo :deep(.el-footer) { display: flex; align-items: center; justify-content: center; box-sizing: border-box; border: 1px dashed var(--color-border, #c5d2e5); padding: 16px; }
349
+ .layout-demo :deep(.focus-region) { color: var(--color-primary, #1677ff); background: color-mix(in srgb, var(--color-primary, #1677ff) 14%, var(--color-panel, white)); border: 2px solid var(--color-primary, #1677ff); font-weight: 700; }
350
+ </style>
351
+ ```
352
+
353
+ ### UiFooterExample
354
+
355
+ ```vue
356
+ <script setup lang="ts">
357
+ import { UiContainer, UiAside, UiHeader, UiFooter, UiMain } from 'sky-ui-kit'
358
+ </script>
359
+ <template>
360
+ <UiContainer direction="vertical" class="layout-demo">
361
+ <UiMain>内容区域</UiMain>
362
+ <UiFooter height="48px" class="focus-region">状态与辅助信息</UiFooter>
363
+ </UiContainer>
364
+ </template>
365
+ <style scoped>
366
+ .layout-demo { height: 240px; border: 2px solid var(--color-border, #c5d2e5); border-radius: 8px; overflow: hidden; }
367
+ .layout-demo :deep(.el-main), .layout-demo :deep(.el-aside), .layout-demo :deep(.el-header), .layout-demo :deep(.el-footer) { display: flex; align-items: center; justify-content: center; box-sizing: border-box; border: 1px dashed var(--color-border, #c5d2e5); padding: 16px; }
368
+ .layout-demo :deep(.focus-region) { color: var(--color-primary, #1677ff); background: color-mix(in srgb, var(--color-primary, #1677ff) 14%, var(--color-panel, white)); border: 2px solid var(--color-primary, #1677ff); font-weight: 700; }
369
+ </style>
370
+ ```
371
+
372
+ ### UiHeaderExample
373
+
374
+ ```vue
375
+ <script setup lang="ts">
376
+ import { UiContainer, UiAside, UiHeader, UiFooter, UiMain } from 'sky-ui-kit'
377
+ </script>
378
+ <template>
379
+ <UiContainer direction="vertical" class="layout-demo">
380
+ <UiHeader height="56px" class="focus-region">顶部导航</UiHeader>
381
+ <UiMain>内容区域</UiMain>
382
+ </UiContainer>
383
+ </template>
384
+ <style scoped>
385
+ .layout-demo { height: 240px; border: 2px solid var(--color-border, #c5d2e5); border-radius: 8px; overflow: hidden; }
386
+ .layout-demo :deep(.el-main), .layout-demo :deep(.el-aside), .layout-demo :deep(.el-header), .layout-demo :deep(.el-footer) { display: flex; align-items: center; justify-content: center; box-sizing: border-box; border: 1px dashed var(--color-border, #c5d2e5); padding: 16px; }
387
+ .layout-demo :deep(.focus-region) { color: var(--color-primary, #1677ff); background: color-mix(in srgb, var(--color-primary, #1677ff) 14%, var(--color-panel, white)); border: 2px solid var(--color-primary, #1677ff); font-weight: 700; }
388
+ </style>
389
+ ```
390
+
391
+ ### UiMainExample
392
+
393
+ ```vue
394
+ <script setup lang="ts">
395
+ import { UiContainer, UiAside, UiHeader, UiFooter, UiMain } from 'sky-ui-kit'
396
+ </script>
397
+ <template>
398
+ <UiContainer direction="horizontal" class="layout-demo">
399
+ <UiAside width="120px">侧栏</UiAside>
400
+ <UiMain class="focus-region">页面内容</UiMain>
401
+ </UiContainer>
402
+ </template>
403
+ <style scoped>
404
+ .layout-demo { height: 240px; border: 2px solid var(--color-border, #c5d2e5); border-radius: 8px; overflow: hidden; }
405
+ .layout-demo :deep(.el-main), .layout-demo :deep(.el-aside), .layout-demo :deep(.el-header), .layout-demo :deep(.el-footer) { display: flex; align-items: center; justify-content: center; box-sizing: border-box; border: 1px dashed var(--color-border, #c5d2e5); padding: 16px; }
406
+ .layout-demo :deep(.focus-region) { color: var(--color-primary, #1677ff); background: color-mix(in srgb, var(--color-primary, #1677ff) 14%, var(--color-panel, white)); border: 2px solid var(--color-primary, #1677ff); font-weight: 700; }
407
+ </style>
408
+ ```
409
+
410
+ ### appSelectExample
411
+
412
+ ```vue
413
+ <script setup lang="ts">
414
+ import { ref } from 'vue'
415
+ import { AppSelect } from 'sky-ui-kit'
416
+ const selected = ref('hankou')
417
+ const multiple = ref(['hankou'])
418
+ const options = [{ label: '汉口站', value: 'hankou' }, { label: '宜昌站', value: 'yichang' }, { label: '维护中的站点', value: 'disabled', disabled: true }]
419
+ const message = ref('尚未操作')
420
+ // 将事件接入应用自己的查询或地图逻辑。
421
+ </script>
422
+ <template>
423
+ <AppSelect v-model="selected" :options="options" filterable clearable placeholder="搜索站点" @change="message = '单选:' + String($event)" />
424
+ <AppSelect v-model="multiple" :options="options" multiple collapse-tags placeholder="选择多个站点" @change="message = '多选:' + String($event)" />
425
+ <AppSelect model-value="hankou" :options="options" disabled />
426
+ <AppSelect :options="[]" placeholder="暂无可选站点" />
427
+ <p role="status">{{ message }}</p>
428
+ </template>
429
+ ```
430
+
431
+ ### buttonModuleExample
432
+
433
+ ```vue
434
+ <script setup lang="ts">
435
+ import { ref } from 'vue'
436
+ import { Search, Plus, Download, Delete } from '@element-plus/icons-vue'
437
+ import { UiButton } from 'sky-ui-kit'
438
+ const saving = ref(false)
439
+ const message = ref('尚未操作')
440
+ function save() { saving.value = true; message.value = '正在保存' }
441
+ function finish() { saving.value = false; message.value = '保存完成' }
442
+ </script>
443
+ <template>
444
+ <UiButton @click="message = '取消'">取消</UiButton>
445
+ <UiButton type="primary" :icon="Search" @click="message = '查询'">查询</UiButton>
446
+ <UiButton type="success" plain>确认</UiButton>
447
+ <UiButton type="warning">提醒</UiButton>
448
+ <UiButton type="info">详情</UiButton>
449
+ <UiButton type="primary" plain>主色描边</UiButton>
450
+ <UiButton type="primary" round>圆角按钮</UiButton>
451
+ <UiButton type="primary" text>文字操作</UiButton>
452
+ <UiButton type="primary" text bg>文字带背景</UiButton>
453
+ <UiButton type="primary" link>查看详情</UiButton>
454
+ <UiButton :icon="Plus" @click="message = '新增'">新增</UiButton>
455
+ <UiButton :icon="Download" @click="message = '导出'">导出</UiButton>
456
+ <UiButton type="danger" :icon="Delete" circle aria-label="删除记录" @click="message = '删除'" />
457
+ <UiButton size="small">小按钮</UiButton><UiButton>默认按钮</UiButton><UiButton size="large">大按钮</UiButton>
458
+ <UiButton disabled>禁用</UiButton><UiButton type="primary" disabled>禁用主按钮</UiButton>
459
+ <UiButton loading>加载中</UiButton>
460
+ <UiButton type="primary" :loading="saving" @click="save">保存配置</UiButton>
461
+ <UiButton :disabled="!saving" @click="finish">完成模拟保存</UiButton>
462
+ <p role="status">{{ message }}</p>
463
+ </template>
464
+ ```
465
+
466
+ ### completeElementPlusExample
467
+
468
+ ```vue
469
+ <script setup lang="ts">
470
+ import { ref } from 'vue'
471
+ import { DataAnalysis, Location } from '@element-plus/icons-vue'
472
+ import { UiAvatar, UiBreadcrumb, UiBreadcrumbItem, UiButton, UiCarousel, UiCarouselItem, UiIcon, UiInputTag, UiMenu, UiMenuItem, UiSegmented, UiSkeleton, UiSpace, UiStatistic, UiStep, UiSteps, UiSubMenu, UiText, UiTimeline, UiTimelineItem, UiTimePicker } from 'sky-ui-kit'
473
+ const menu = ref('stations')
474
+ const segment = ref('实况')
475
+ const tags = ref(['汉口站', '宜昌站'])
476
+ const time = ref<Date>()
477
+ const step = ref(1)
478
+ </script>
479
+ <template>
480
+ <UiMenu :default-active="menu" @select="menu = $event">
481
+ <UiMenuItem index="stations"><UiIcon><Location /></UiIcon><span>站点管理</span></UiMenuItem>
482
+ <UiSubMenu index="analysis">
483
+ <template #title><UiIcon><DataAnalysis /></UiIcon><span>数据分析</span></template>
484
+ <UiMenuItem index="rain">降雨分析</UiMenuItem>
485
+ <UiMenuItem index="flow">径流分析</UiMenuItem>
486
+ </UiSubMenu>
487
+ </UiMenu>
488
+ <p>菜单选择事件:{{ menu }}</p>
489
+ <UiBreadcrumb separator="/"><UiBreadcrumbItem>监测中心</UiBreadcrumbItem><UiBreadcrumbItem>站点管理</UiBreadcrumbItem></UiBreadcrumb>
490
+ <UiSegmented v-model="segment" :options="['实况', '预报', '历史']" />
491
+ <p>当前模式:{{ segment }}</p>
492
+ <UiInputTag v-model="tags" placeholder="添加站点标签" />
493
+ <UiTimePicker v-model="time" placeholder="选择观测时间" />
494
+ <UiSteps :active="step" finish-status="success"><UiStep title="选择数据" /><UiStep title="配置分析" /><UiStep title="查看结果" /></UiSteps>
495
+ <UiButton @click="step = (step + 1) % 4">下一步</UiButton>
496
+ <UiTimeline><UiTimelineItem timestamp="08:00" type="primary">接收观测数据</UiTimelineItem><UiTimelineItem timestamp="09:00" type="success">完成质量检查</UiTimelineItem><UiTimelineItem timestamp="10:00">生成分析结果</UiTimelineItem></UiTimeline>
497
+ <UiSpace><UiAvatar>水</UiAvatar><UiText type="primary">实时监测</UiText></UiSpace>
498
+ <UiStatistic title="正常站点" :value="128" />
499
+ <UiSkeleton :rows="2" animated />
500
+ <UiCarousel height="100px" :autoplay="false"><UiCarouselItem v-for="label in ['实时监测', '趋势分析', '预报产品']" :key="label"><div style="display: grid; place-items: center; height: 100%">{{ label }}</div></UiCarouselItem></UiCarousel>
501
+ </template>
502
+ ```
503
+
46
504
  ### componentsExample
47
505
 
48
506
  ```vue
@@ -74,6 +532,107 @@ const ranges = [{ key: '1h', label: '近1小时' }, { key: '24h', label: '近24
74
532
  <!-- 这些组件保留 v-model 交互;可通过 @change 接收值变化。 -->
75
533
  ```
76
534
 
535
+ ### containerExample
536
+
537
+ ```vue
538
+ <script setup lang="ts">
539
+ import { ref } from 'vue'
540
+ import { ElScrollbar } from 'element-plus'
541
+ import { UiContainer, UiHeader, UiAside, UiMain, UiButton } from 'sky-ui-kit'
542
+ const collapsed = ref(false)
543
+ </script>
544
+ <template>
545
+ <UiContainer direction="vertical" class="app-layout">
546
+ <UiHeader height="var(--header-height, 58px)">应用导航</UiHeader>
547
+ <UiContainer>
548
+ <UiAside :width="collapsed ? '64px' : 'var(--sidebar-width, 244px)'">业务菜单</UiAside>
549
+ <UiMain>
550
+ <UiButton @click="collapsed = !collapsed">切换侧栏</UiButton>
551
+ <ElScrollbar height="200px"><p v-for="index in 10" :key="index">页面内容 {{ index }}</p></ElScrollbar>
552
+ <!-- 业务项目可在此放 RouterView,保留自己的路由和权限。 -->
553
+ </UiMain>
554
+ </UiContainer>
555
+ </UiContainer>
556
+ </template>
557
+ <style scoped>
558
+ .app-layout { min-height: 300px; color: var(--color-text, #1f2937); background: var(--color-panel, white); }
559
+ .app-layout :deep(.el-main) { min-width: 0; min-height: 0; }
560
+ .app-layout :deep(.el-header), .app-layout :deep(.el-aside) { background: var(--surface-control, #eef5ff); }
561
+ </style>
562
+ ```
563
+
564
+ ### fullLayoutExample
565
+
566
+ ```vue
567
+ <script setup lang="ts">
568
+ import { UiContainer, UiHeader, UiAside, UiMain, UiFooter } from 'sky-ui-kit'
569
+ </script>
570
+ <template>
571
+ <UiContainer direction="vertical" class="full-layout">
572
+ <UiHeader height="56px">顶栏 · 顶部导航</UiHeader>
573
+ <UiContainer class="content-row">
574
+ <UiAside width="160px">左侧栏 · 业务菜单</UiAside>
575
+ <UiMain>主体 · 页面内容</UiMain>
576
+ </UiContainer>
577
+ <UiFooter height="48px">底栏 · 状态信息</UiFooter>
578
+ </UiContainer>
579
+ </template>
580
+ <style scoped>
581
+ .full-layout { height: 320px; border: 2px solid var(--color-border, #c5d2e5); border-radius: 8px; overflow: hidden; }
582
+ .full-layout :deep(.content-row) { flex: 1; min-height: 0; }
583
+ .full-layout :deep(.el-header), .full-layout :deep(.el-footer), .full-layout :deep(.el-aside), .full-layout :deep(.el-main) { display: flex; align-items: center; justify-content: center; border: 1px dashed var(--color-border, #c5d2e5); box-sizing: border-box; }
584
+ .full-layout :deep(.el-header), .full-layout :deep(.el-footer) { background: color-mix(in srgb, var(--color-primary, #1677ff) 14%, var(--color-panel, white)); }
585
+ .full-layout :deep(.el-aside) { background: var(--color-surface, #f4f7fb); }
586
+ .full-layout :deep(.el-main) { background: var(--color-panel, white); }
587
+ </style>
588
+ ```
589
+
590
+ ### iconsExample
591
+
592
+ ```vue
593
+ <script setup lang="ts">
594
+ import { ref, h } from 'vue'
595
+ import { Search } from '@element-plus/icons-vue'
596
+ import { UiIcon, UiButton, SvgIcon, MapSideMenu } from 'sky-ui-kit'
597
+ const active = ref('rain')
598
+ const message = ref('尚未操作')
599
+ // 菜单 icon 接收 Vue 组件,用 h 绑定项目 SVG 名称。
600
+ const RainIcon = () => h(SvgIcon, { name: 'ii_rain' })
601
+ const items = [{ key: 'rain', label: '降水', icon: RainIcon }]
602
+ </script>
603
+ <template>
604
+ <UiIcon :size="24" color="var(--color-primary, #1677ff)"><Search /></UiIcon>
605
+ <SvgIcon name="ii_rain" :size="24" :decorative="false" title="降水" />
606
+ <UiButton :icon="Search" @click="message = '搜索事件由业务方处理'">搜索</UiButton>
607
+ <UiButton @click="message = '查看降水'"><SvgIcon name="ii_rain" />查看降水</UiButton>
608
+ <UiButton disabled :icon="Search">不可用</UiButton>
609
+ <UiButton loading>加载中</UiButton>
610
+ <MapSideMenu v-model:active="active" :items="items" @select="message = '选择:' + $event.label" />
611
+ <p role="status">{{ message }}</p>
612
+ </template>
613
+ ```
614
+
615
+ ### mapTimelineExample
616
+
617
+ ```vue
618
+ <script setup lang="ts">
619
+ import { ref } from 'vue'
620
+ import { MapTimeline } from 'sky-ui-kit'
621
+ const frame = ref(6)
622
+ const playing = ref(false)
623
+ const message = ref('尚未操作')
624
+ // 将事件接入应用自己的查询或地图逻辑。
625
+ </script>
626
+ <template>
627
+ <MapTimeline v-model="frame" v-model:playing="playing"
628
+ start-time="2026-10-08T08:00:00+08:00" time-zone="Asia/Shanghai"
629
+ :past-steps="6" :future-steps="12" :page-size="18"
630
+ @change="message = '当前时间:' + $event.label" />
631
+ <p>当前帧:{{ frame }} · {{ playing ? '播放中' : '已暂停' }}</p>
632
+ <p role="status">{{ message }}</p>
633
+ </template>
634
+ ```
635
+
77
636
  ### mapToolsExample
78
637
 
79
638
  ```vue
@@ -165,3 +724,2126 @@ function onSelect(item: { key: string; label: string }) {
165
724
  <!-- 纵向菜单在每个菜单项的 icon 位置渲染图标;横向菜单为文字样式。 -->
166
725
  <!-- MapSideMenu 的搜索、分组收起和图标模式切换由组件内部完成。 -->
167
726
  ```
727
+
728
+ ### timeRangeExample
729
+
730
+ ```vue
731
+ <script setup lang="ts">
732
+ import { ref } from 'vue'
733
+ import { TimeRangeQuickSelect } from 'sky-ui-kit'
734
+ const range = ref('24h')
735
+ const options = [{ key: '1h', label: '近1小时' }, { key: '24h', label: '近24小时' }, { key: '3d', label: '近3天' }, { key: '7d', label: '近7天' }]
736
+ const message = ref('尚未操作')
737
+ // 将事件接入应用自己的查询或地图逻辑。
738
+ </script>
739
+ <template>
740
+ <TimeRangeQuickSelect v-model="range" :options="options" @change="message = '切换范围:' + $event.label" />
741
+ <p>当前范围:{{ range }}</p>
742
+ <p role="status">{{ message }}</p>
743
+ </template>
744
+ ```
745
+
746
+ ### treeSelectExample
747
+
748
+ ```vue
749
+ <script setup lang="ts">
750
+ import { ref } from 'vue'
751
+ import { TreeSelectPanel } from 'sky-ui-kit'
752
+ const region = ref<string | number | null>('hubei')
753
+ const dark = ref(false)
754
+ const categories = [
755
+ { type: 'region', label: '行政区', children: [{ id: 'hubei', label: '湖北省', children: [{ id: 'wuhan', label: '武汉市', tag: '市' }] }, { id: 'hunan', label: '湖南省' }] },
756
+ { type: 'basin', label: '流域', children: [{ id: 'yangtze', label: '长江流域', children: [{ id: 'dongting', label: '洞庭湖', tag: '湖区' }] }] }
757
+ ]
758
+ const message = ref('尚未操作')
759
+ // 将事件接入应用自己的查询或地图逻辑。
760
+ </script>
761
+ <template>
762
+ <TreeSelectPanel v-model="region" :categories="categories" :dark="dark" close-on-select
763
+ @change="message = $event.category.label + ':' + $event.node.label" />
764
+ <p>当前节点:{{ region }}</p>
765
+ <p role="status">{{ message }}</p>
766
+ </template>
767
+ ```
768
+
769
+ ### 完整项目布局组合示例
770
+
771
+ ```vue
772
+ <script setup lang="ts">
773
+ import { computed, ref } from 'vue'
774
+ import { ElScrollbar } from 'element-plus'
775
+ import { UiContainer, UiHeader, UiAside, UiMain, UiButton, UiInput, UiTable, UiTableColumn, ProjectMenu, MapSideMenu, MapTools, MapTimeline, SvgIcon } from 'sky-ui-kit'
776
+ const page = ref('stations')
777
+ const collapsed = ref(false)
778
+ const keyword = ref('')
779
+ const rightOpen = ref(true)
780
+ const activeLayer = ref('rain')
781
+ const tool = ref('layers')
782
+ const frame = ref(0)
783
+ const message = ref('请选择菜单或工具')
784
+ const navigation = [{ key: 'stations', label: '测站管理' }, { key: 'users', label: '用户管理' }]
785
+ const layers = [{ key: 'rain', label: '降水' }, { key: 'water', label: '水位' }]
786
+ const rows = [{ name: '汉口站', region: '长江流域', value: 28.6 }, { name: '宜昌站', region: '长江流域', value: 42.3 }, { name: '城陵矶站', region: '洞庭湖流域', value: 16.8 }]
787
+ const filtered = computed(() => rows.filter(row => `${row.name}${row.region}`.includes(keyword.value)))
788
+ const startTime = '2026-10-12T00:00:00+08:00'
789
+ </script>
790
+ <template>
791
+ <h3>项目系统页:导航、查询与数据列表</h3>
792
+ <UiContainer direction="vertical" class="business-system">
793
+ <UiHeader height="58px" class="system-header"><strong>一体化平台</strong><span>数据维护 / {{ page === 'stations' ? '测站管理' : '用户管理' }}</span><UiButton size="small" @click="collapsed = !collapsed">{{ collapsed ? '展开菜单' : '收起菜单' }}</UiButton></UiHeader>
794
+ <UiContainer><UiAside :width="collapsed ? '0px' : '160px'" class="system-aside"><ProjectMenu direction="vertical" :groups="[{ label: '数据维护', items: navigation }]" v-model:active="page" /></UiAside><UiMain class="system-main"><ElScrollbar height="260px"><template v-if="page === 'stations'"><h4>测站管理</h4><div class="query-row"><UiInput v-model="keyword" clearable placeholder="输入测站或流域名称" /><UiButton type="primary" @click="message = '查询结果:' + filtered.length + ' 个测站'">查询</UiButton><UiButton @click="keyword = ''">重置</UiButton></div><UiTable :data="filtered" empty-text="没有匹配测站"><UiTableColumn prop="name" label="测站" /><UiTableColumn prop="region" label="流域" /><UiTableColumn prop="value" label="水位 (m)" /></UiTable></template><template v-else><h4>用户管理</h4><p>此区域由应用的 RouterView 渲染;用户、角色和权限继续由业务项目管理。</p><UiButton @click="page = 'stations'">返回测站</UiButton></template></ElScrollbar></UiMain></UiContainer>
795
+ </UiContainer>
796
+ <h3>项目无菜单页:顶部导航与整宽内容</h3>
797
+ <UiContainer direction="vertical" class="no-menu"><UiHeader height="58px" class="system-header"><strong>一体化平台</strong><span>专题概览</span></UiHeader><UiMain><h4>专题内容</h4><p>对应 MainLayoutNoMenu / ContentLayoutNomenu,内容不预留左栏宽度,保留原路由和主题。</p></UiMain></UiContainer>
798
+ <h3>项目地图页:菜单、工具、图例、时间轴与业务面板</h3>
799
+ <p class="muted">参考降水页面的 content / legend / timeline / right 插槽组合。底层为示意背景;真实地图实例和数据查询由应用接入。</p>
800
+ <div class="business-map" :class="{ 'with-right': rightOpen }">
801
+ <div class="map-host">地图宿主 · 当前图层:{{ activeLayer === 'rain' ? '降水' : '水位' }}</div>
802
+ <div class="map-overlay">
803
+ <header class="map-header"><strong>实况(测站)</strong><UiButton size="small" @click="rightOpen = !rightOpen">{{ rightOpen ? '收起业务面板' : '展开业务面板' }}</UiButton></header>
804
+ <div class="map-menu"><MapSideMenu title="监测要素" :items="layers" v-model:active="activeLayer" @select="message = '已选择:' + $event.label" /></div>
805
+ <div class="map-tools"><MapTools v-model:active="tool" @tool-click="message = '地图工具:' + $event" @context-change="message = '右键菜单:' + ($event ? '开启' : '关闭')" /></div>
806
+ <aside v-if="rightOpen" class="map-panel"><h4>测站数据</h4><ElScrollbar height="200px"><button v-for="row in rows" :key="row.name" class="station-row" @click="message = '定位请求由应用处理:' + row.name"><SvgIcon name="ii_water" />{{ row.name }}<strong>{{ row.value }} m</strong></button></ElScrollbar><p>点击测站查看事件反馈</p></aside>
807
+ <div class="map-legend"><strong>降水图例(mm)</strong><span v-for="(level, index) in ['0–10', '10–25', '25–50']" :key="level"><i :style="{ background: ['#b8e7ff', '#4aa3df', '#2563a7'][index] }" />{{ level }}</span></div>
808
+ <div class="map-timeline"><MapTimeline v-model="frame" :start-time="startTime" :steps="12" @change="message = '时间帧:' + $event.index" /></div>
809
+ </div>
810
+ </div>
811
+ <p role="status">{{ message }}</p>
812
+ </template>
813
+ <style scoped>
814
+ .business-system, .no-menu { border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow: hidden; margin: 16px 0 28px; background: var(--color-panel, white); }
815
+ .system-header { display: flex; align-items: center; gap: 16px; background: var(--surface-control, #eef5ff); border-bottom: 1px solid var(--color-border, #dbe3ef); }
816
+ .system-header span { flex: 1; } .system-aside { background: var(--app-sidebar-bg, #eef5ff); overflow: hidden; } .system-main { min-width: 0; min-height: 0; } .query-row { display: flex; gap: 8px; margin-bottom: 16px; }
817
+ .business-map { height: 560px; position: relative; overflow: hidden; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; --preview-right: 0px; }
818
+ .business-map.with-right { --preview-right: 220px; } .map-host { position: absolute; inset: 0; display: grid; place-items: center; color: var(--color-muted, #64748b); background: var(--surface-control, #eef5ff); }
819
+ .map-overlay { position: absolute; inset: 0; pointer-events: none; } .map-overlay > * { pointer-events: auto; }
820
+ .map-header { position: absolute; left: 12px; right: 12px; top: 12px; display: flex; justify-content: space-between; align-items: center; }
821
+ .map-menu { position: absolute; left: 12px; top: 64px; width: 160px; } .map-tools { position: absolute; top: 64px; right: calc(var(--preview-right) + 12px); }
822
+ .map-panel { position: absolute; right: 12px; top: 64px; width: 196px; padding: 10px; border-radius: 8px; background: var(--color-panel, white); box-sizing: border-box; }
823
+ .station-row { display: flex; gap: 8px; align-items: center; width: 100%; padding: 12px 4px; border: 0; border-bottom: 1px solid var(--color-border, #dbe3ef); background: transparent; color: var(--color-text, #1f2937); cursor: pointer; } .station-row strong { margin-left: auto; }
824
+ .map-legend { position: absolute; left: 12px; bottom: 138px; padding: 10px; background: var(--color-panel, white); border-radius: 8px; display: grid; gap: 6px; } .map-legend i { display: inline-block; width: 20px; height: 8px; margin-right: 8px; }
825
+ .map-timeline { position: absolute; left: 12px; right: calc(var(--preview-right) + 12px); bottom: 12px; background: var(--color-panel, white); border-radius: 8px; overflow: auto; padding: 8px; }
826
+ @media (max-width: 700px) { .business-map { height: 740px; } .business-map.with-right { --preview-right: 0px; } .map-panel { top: 350px; } .map-legend { bottom: 150px; } .system-header { flex-wrap: wrap; gap: 6px; font-size: 12px; } .query-row { flex-wrap: wrap; } }
827
+ </style>
828
+
829
+ ```
830
+
831
+ ### 布局变体:横向导航、双侧栏与上下分区
832
+
833
+ ```vue
834
+ <script setup lang="ts">
835
+ import { ref } from 'vue'
836
+ import { UiContainer, UiHeader, UiAside, UiMain, UiFooter, UiButton, ProjectMenu } from 'sky-ui-kit'
837
+ const active = ref('overview')
838
+ const rightOpen = ref(true)
839
+ const bottomOpen = ref(true)
840
+ const items = [{ key: 'overview', label: '运行概览' }, { key: 'data', label: '数据维护' }, { key: 'analysis', label: '分析报表' }]
841
+ </script>
842
+ <template>
843
+ <h3>横向导航布局:顶部菜单 + 整宽工作区</h3>
844
+ <p class="muted">参考原 Header 的横向应用菜单;适合导航较少的专题与工作台。ProjectMenu 只负责菜单展示,select 由业务方接路由。</p>
845
+ <UiContainer direction="vertical" class="variant"><UiHeader height="auto" class="top"><strong>一体化平台</strong><ProjectMenu :items="items" v-model:active="active" /></UiHeader><UiMain><h4>{{ items.find(item => item.key === active)?.label }}</h4><p>整宽内容区域,不预留左侧菜单。</p></UiMain><UiFooter height="40px" class="foot">可选状态栏 · 当前模块 {{ active }}</UiFooter></UiContainer>
846
+ <h3>双侧栏布局:功能导航 + 主内容 + 信息面板</h3>
847
+ <p class="muted">适合列表与详情并列、参数配置与结果展示。右侧是普通信息区;地图浮动业务面板使用上方地图布局。</p>
848
+ <UiButton size="small" @click="rightOpen = !rightOpen">{{ rightOpen ? '隐藏信息栏' : '显示信息栏' }}</UiButton>
849
+ <UiContainer direction="vertical" class="variant"><UiHeader height="58px" class="top">数据分析工作台</UiHeader><UiContainer><UiAside width="130px" class="side">功能导航<br>测站 / 数据 / 分析</UiAside><UiMain><h4>主工作区</h4><p>查询结果、业务表格或图表。</p></UiMain><UiAside v-if="rightOpen" width="170px" class="side">信息与参数<br>当前测站:汉口站<br>业务详情由页面传入</UiAside></UiContainer></UiContainer>
850
+ <h3>上下分区布局:主视图 + 底部过程区</h3>
851
+ <p class="muted">参考地图页的过程图/时间轴位置;用于主视图与辅助结果分区。这里采用普通文档布局,底部开关由页面控制。</p>
852
+ <UiButton size="small" @click="bottomOpen = !bottomOpen">{{ bottomOpen ? '收起过程区' : '展开过程区' }}</UiButton>
853
+ <UiContainer direction="vertical" class="variant"><UiHeader height="58px" class="top">监测分析</UiHeader><UiMain class="main-view"><h4>主视图</h4><p>地图、图表或数据概览宿主</p></UiMain><UiFooter v-if="bottomOpen" height="100px" class="bottom"><strong>过程区</strong><p>时间轴 / 过程曲线 / 结果摘要</p></UiFooter></UiContainer>
854
+ </template>
855
+ <style scoped>
856
+ .variant { margin: 16px 0 28px; overflow: hidden; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; background: var(--color-panel, white); color: var(--color-text, #1f2937); }
857
+ .top { display: flex; align-items: center; gap: 16px; padding: 14px; background: var(--surface-control, #eef5ff); border-bottom: 1px solid var(--color-border, #dbe3ef); }
858
+ .side { padding: 16px; line-height: 2; background: var(--app-sidebar-bg, #eef5ff); }
859
+ .foot, .bottom { padding: 12px; background: var(--surface-control, #eef5ff); border-top: 1px solid var(--color-border, #dbe3ef); }
860
+ .main-view { min-height: 180px; } .variant :deep(.el-main) { min-width: 0; min-height: 0; }
861
+ @media (max-width: 600px) { .top { flex-wrap: wrap; } .variant :deep(.el-container) { flex-wrap: wrap; } .side { max-width: 30%; padding: 8px; font-size: 12px; } }
862
+ </style>
863
+
864
+ ```
865
+
866
+ ### 地图业务布局轮廓示例
867
+
868
+ ```vue
869
+ <script setup lang="ts">
870
+ import { ref } from 'vue'
871
+ import { UiButton } from 'sky-ui-kit'
872
+ const rightOpen = ref(true)
873
+ const timelineVisible = ref(true)
874
+ </script>
875
+ <template>
876
+ <div class="template-demo" :class="{ 'right-open': rightOpen }">
877
+ <div class="map-placeholder">地图宿主(由 MainLayoutMap 提供)</div>
878
+ <div class="top-region">top · 标题 / 查询条件</div>
879
+ <div class="toolbar-region"><span>toolbar · 地图工具</span><UiButton size="small" @click="rightOpen = !rightOpen">{{ rightOpen ? '收起右栏' : '展开右栏' }}</UiButton><UiButton size="small" @click="timelineVisible = !timelineVisible">切换时间轴</UiButton></div>
880
+ <aside v-if="rightOpen" class="right-region">right · 业务面板<br>测站详情 / 过程数据</aside>
881
+ <div class="content-region">content · 业务浮层</div>
882
+ <div class="legend-region" :class="{ 'above-timeline': timelineVisible }">legend · 图例</div>
883
+ <div v-if="timelineVisible" class="timeline-region">timeline · 实况 / 预报时间轴</div>
884
+ </div>
885
+ </template>
886
+ <style scoped>
887
+ .template-demo { position: relative; height: 360px; overflow: hidden; border: 2px solid var(--color-border, #c5d2e5); border-radius: 8px; --demo-panel-width: min(220px, 30%); --demo-right-offset: 12px; }
888
+ .right-open { --demo-right-offset: calc(var(--demo-panel-width) + 24px); }
889
+ .map-placeholder { position: absolute; inset: 0; display: grid; place-items: center; color: var(--color-muted, #748096); background-color: var(--color-surface, #f4f7fb); background-image: linear-gradient(var(--color-border, #dbe3ef) 1px, transparent 1px), linear-gradient(90deg, var(--color-border, #dbe3ef) 1px, transparent 1px); background-size: 32px 32px; }
890
+ .top-region, .toolbar-region, .right-region, .content-region, .legend-region, .timeline-region { position: absolute; padding: 12px; box-sizing: border-box; border: 1px solid var(--color-primary, #1677ff); border-radius: 6px; background: var(--color-panel, white); color: var(--color-text, #1f2937); }
891
+ .top-region { top: 12px; left: 12px; }
892
+ .toolbar-region { top: 64px; right: var(--demo-right-offset); display: flex; gap: 8px; align-items: center; flex-wrap: wrap; max-width: 65%; }
893
+ .right-region { top: 12px; right: 12px; bottom: 12px; width: var(--demo-panel-width); }
894
+ .content-region { top: 124px; left: 12px; }
895
+ .legend-region { bottom: 12px; right: var(--demo-right-offset); }
896
+ .legend-region.above-timeline { bottom: 76px; }
897
+ .timeline-region { bottom: 12px; left: 12px; right: var(--demo-right-offset); text-align: center; }
898
+ </style>
899
+
900
+ ```
901
+
902
+ ### UiDivider 独立使用示例
903
+
904
+ ```vue
905
+ <script setup lang="ts">
906
+ import { UiDivider } from 'sky-ui-kit'
907
+ </script>
908
+ <template>
909
+ <p>测站基础信息</p><UiDivider /><p>监测数据</p>
910
+ <UiDivider content-position="left">左侧标题</UiDivider>
911
+ <UiDivider>居中标题</UiDivider>
912
+ <UiDivider content-position="right" border-style="dashed">虚线分隔</UiDivider>
913
+ <div>实况<UiDivider direction="vertical" />预报<UiDivider direction="vertical" />历史</div>
914
+ </template>
915
+
916
+ ```
917
+
918
+ ### UiLink 独立使用示例
919
+
920
+ ```vue
921
+ <script setup lang="ts">
922
+ import { ref } from 'vue'
923
+ import { View } from '@element-plus/icons-vue'
924
+ import { UiLink, UiSpace } from 'sky-ui-kit'
925
+ const message = ref('尚未操作')
926
+ </script>
927
+ <template>
928
+ <UiSpace wrap :size="16">
929
+ <UiLink @click="message = '查看测站详情'">默认链接</UiLink>
930
+ <UiLink v-for="type in ['primary', 'success', 'warning', 'danger', 'info'] as const" :key="type" :type="type" @click="message = type">{{ type }}</UiLink>
931
+ <UiLink disabled>禁用链接</UiLink>
932
+ <UiLink type="primary" :underline="false" :icon="View" @click="message = '查看详情'">查看详情</UiLink>
933
+ <UiLink href="https://element-plus.org/" target="_blank" rel="noopener noreferrer">外部文档</UiLink>
934
+ </UiSpace>
935
+ <p role="status">{{ message }}</p>
936
+ </template>
937
+
938
+ ```
939
+
940
+ ### UiSpace 独立使用示例
941
+
942
+ ```vue
943
+ <script setup lang="ts">
944
+ import { ref } from 'vue'
945
+ import { UiSpace, UiButton } from 'sky-ui-kit'
946
+ const gap = ref(16)
947
+ </script>
948
+ <template>
949
+ <UiButton @click="gap = gap === 16 ? 32 : 16">切换间距:{{ gap }} px</UiButton>
950
+ <h3>水平排列与自动换行</h3>
951
+ <UiSpace wrap :size="gap"><UiButton v-for="name in ['查询', '重置', '导出']" :key="name">{{ name }}</UiButton></UiSpace>
952
+ <h3>垂直排列</h3>
953
+ <UiSpace direction="vertical" alignment="start" :size="gap"><span>测站信息</span><span>监测数据</span><span>更新时间</span></UiSpace>
954
+ </template>
955
+
956
+ ```
957
+
958
+ ### UiText 独立使用示例
959
+
960
+ ```vue
961
+ <script setup lang="ts">
962
+ import { UiText, UiSpace } from 'sky-ui-kit'
963
+ </script>
964
+ <template>
965
+ <UiSpace wrap :size="16">
966
+ <UiText>默认正文</UiText>
967
+ <UiText v-for="type in ['primary', 'success', 'warning', 'danger', 'info'] as const" :key="type" :type="type">{{ type }} 文本</UiText>
968
+ </UiSpace>
969
+ <p><UiText size="small">小号文本</UiText> <UiText>默认尺寸</UiText> <UiText size="large">大号文本</UiText></p>
970
+ <p><UiText tag="b">重要说明</UiText> <UiText tag="i">辅助说明</UiText></p>
971
+ <div style="width: 180px"><UiText truncated>汉口站实时监测数据与历史过程信息,超出宽度省略</UiText></div>
972
+ <div style="width: 220px"><UiText :line-clamp="2">多行数据说明:这里展示测站监测情况、更新时间和数据来源,超出两行的内容将自动省略。</UiText></div>
973
+ </template>
974
+
975
+ ```
976
+
977
+ ### UiForm 独立使用示例
978
+
979
+ ```vue
980
+ <script setup lang="ts">
981
+ import { reactive, ref } from 'vue'
982
+ import type { FormInstance, FormRules } from 'element-plus'
983
+ import { Search } from '@element-plus/icons-vue'
984
+ import { UiForm, UiFormItem, UiInput, UiSelect, UiOption, UiDatePicker, UiInputNumber, UiRadioGroup, UiRadio, UiCheckboxGroup, UiCheckbox, UiSwitch, UiButton } from 'sky-ui-kit'
985
+ const formRef = ref<FormInstance>()
986
+ const disabled = ref(false)
987
+ const result = ref('填写表单后提交,查看校验和数据反馈')
988
+ const model = reactive({ name: '', basin: '', date: '', threshold: 20, mode: 'observed', elements: ['rain'], enabled: true, remark: '' })
989
+ const rules: FormRules = {
990
+ name: [{ required: true, message: '请输入测站名称', trigger: 'blur' }],
991
+ basin: [{ required: true, message: '请选择流域', trigger: 'change' }],
992
+ }
993
+ async function submit() {
994
+ if (!formRef.value) return
995
+ const valid = await formRef.value.validate().catch(() => false)
996
+ result.value = valid ? '模拟提交:' + JSON.stringify(model) : '校验未通过,请补全必填项'
997
+ // 实际保存请求由业务方接入。
998
+ }
999
+ function reset() { formRef.value?.resetFields(); result.value = '表单已重置' }
1000
+ </script>
1001
+ <template>
1002
+ <UiSwitch v-model="disabled" active-text="禁用表单" />
1003
+ <UiForm ref="formRef" :model="model" :rules="rules" :disabled="disabled" label-width="100px" class="station-form" @submit.prevent="submit">
1004
+ <UiFormItem label="测站名称" prop="name"><UiInput v-model="model.name" clearable placeholder="请输入测站名称" /></UiFormItem>
1005
+ <UiFormItem label="所属流域" prop="basin"><UiSelect v-model="model.basin" clearable placeholder="选择流域"><UiOption label="长江流域" value="yangtze" /><UiOption label="黄河流域" value="yellow" /></UiSelect></UiFormItem>
1006
+ <UiFormItem label="观测日期" prop="date"><UiDatePicker v-model="model.date" type="date" value-format="YYYY-MM-DD" placeholder="选择日期" /></UiFormItem>
1007
+ <UiFormItem label="预警阈值" prop="threshold"><UiInputNumber v-model="model.threshold" :min="0" :max="200" :step="5" /></UiFormItem>
1008
+ <UiFormItem label="数据类型" prop="mode"><UiRadioGroup v-model="model.mode"><UiRadio value="observed">实况</UiRadio><UiRadio value="forecast">预报</UiRadio></UiRadioGroup></UiFormItem>
1009
+ <UiFormItem label="监测要素" prop="elements"><UiCheckboxGroup v-model="model.elements"><UiCheckbox value="rain">降雨</UiCheckbox><UiCheckbox value="water">水位</UiCheckbox></UiCheckboxGroup></UiFormItem>
1010
+ <UiFormItem label="启用监测" prop="enabled"><UiSwitch v-model="model.enabled" /></UiFormItem>
1011
+ <UiFormItem label="备注" prop="remark"><UiInput v-model="model.remark" type="textarea" :rows="3" maxlength="200" show-word-limit /></UiFormItem>
1012
+ <UiFormItem><UiButton type="primary" :icon="Search" native-type="submit">校验并提交</UiButton><UiButton @click="reset">重置</UiButton></UiFormItem>
1013
+ </UiForm>
1014
+ <p role="status" class="form-result">{{ result }}</p>
1015
+ </template>
1016
+ <style scoped>
1017
+ .station-form { max-width: 640px; margin-top: 20px; }
1018
+ .form-result { overflow-wrap: anywhere; padding: 12px; background: var(--color-surface, #f4f7fb); border: 1px solid var(--color-border, #dbe3ef); border-radius: 6px; }
1019
+ </style>
1020
+
1021
+ ```
1022
+
1023
+ ### UiAutocomplete 独立使用示例
1024
+
1025
+ ```vue
1026
+ <script setup lang="ts">
1027
+ import { ref } from 'vue'
1028
+ import { Search } from '@element-plus/icons-vue'
1029
+ import { UiAutocomplete } from 'sky-ui-kit'
1030
+ const station = ref('')
1031
+ const onInputStation = ref('')
1032
+ const message = ref('输入站名或流域,选择建议项')
1033
+ const stations = [
1034
+ { value: '汉口站', basin: '长江流域', id: 'hankou' },
1035
+ { value: '宜昌站', basin: '长江流域', id: 'yichang' },
1036
+ { value: '城陵矶站', basin: '洞庭湖流域', id: 'chenglingji' },
1037
+ ]
1038
+ function suggestions(query: string, callback: (items: typeof stations) => void) {
1039
+ const keyword = query.trim().toLowerCase()
1040
+ callback(stations.filter(item => (item.value + item.basin).toLowerCase().includes(keyword)))
1041
+ // 实际远程查询由应用提供,查询结束后调用 callback。
1042
+ }
1043
+ function selected(item: Record<string, any>) { message.value = '已选择:' + item.value + ' · ' + item.basin + ' · ' + item.id }
1044
+ </script>
1045
+ <template>
1046
+ <h3>站点搜索 · 聚焦显示建议</h3>
1047
+ <UiAutocomplete v-model="station" :fetch-suggestions="suggestions" :prefix-icon="Search" clearable
1048
+ placeholder="输入汉口、宜昌或长江" @select="selected" @clear="message = '已清空站点'">
1049
+ <template #default="{ item }"><strong>{{ item.value }}</strong><span class="basin-label">{{ item.basin }}</span></template>
1050
+ </UiAutocomplete>
1051
+ <p role="status">{{ message }}</p>
1052
+ <h3>仅输入后显示建议</h3>
1053
+ <UiAutocomplete v-model="onInputStation" :fetch-suggestions="suggestions" :trigger-on-focus="false" clearable placeholder="输入站点关键词" @select="selected" />
1054
+ <h3>禁用状态</h3>
1055
+ <UiAutocomplete model-value="汉口站" :fetch-suggestions="suggestions" disabled />
1056
+ </template>
1057
+ <style scoped>
1058
+ .basin-label { margin-left: 16px; color: var(--color-muted, #748096); }
1059
+ </style>
1060
+
1061
+ ```
1062
+
1063
+ ### UiTimePicker 独立交互示例
1064
+
1065
+ ```vue
1066
+ <script setup lang="ts">
1067
+ import { ref } from 'vue'
1068
+ import { UiTimePicker } from 'sky-ui-kit'
1069
+ const value = ref<Date>()
1070
+ const message = ref('尚未操作')
1071
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1072
+ </script>
1073
+ <template>
1074
+ <div class="control-demo">
1075
+ <UiTimePicker v-model="value" placeholder="选择观测时间" clearable @change="record" />
1076
+ <p>当前值:{{ value }}</p>
1077
+ <p role="status">{{ message }}</p>
1078
+ </div>
1079
+ </template>
1080
+ <style scoped>
1081
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1082
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1083
+ </style>
1084
+
1085
+ ```
1086
+
1087
+ ### UiSelect 独立交互示例
1088
+
1089
+ ```vue
1090
+ <script setup lang="ts">
1091
+ import { ref } from 'vue'
1092
+ import { UiSelect, UiOption } from 'sky-ui-kit'
1093
+ const value = ref('rain')
1094
+ const message = ref('尚未操作')
1095
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1096
+ </script>
1097
+ <template>
1098
+ <div class="control-demo">
1099
+ <UiSelect v-model="value" filterable clearable placeholder="选择要素" @change="record"><UiOption label="降雨" value="rain" /><UiOption label="水位" value="water" /><UiOption label="不可用" value="disabled" disabled /></UiSelect>
1100
+ <p>当前值:{{ value }}</p>
1101
+ <p role="status">{{ message }}</p>
1102
+ </div>
1103
+ </template>
1104
+ <style scoped>
1105
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1106
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1107
+ </style>
1108
+
1109
+ ```
1110
+
1111
+ ### UiCheckbox 独立交互示例
1112
+
1113
+ ```vue
1114
+ <script setup lang="ts">
1115
+ import { ref } from 'vue'
1116
+ import { UiCheckbox, UiCheckboxGroup } from 'sky-ui-kit'
1117
+ const value = ref(['rain'])
1118
+ const message = ref('尚未操作')
1119
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1120
+ </script>
1121
+ <template>
1122
+ <div class="control-demo">
1123
+ <UiCheckboxGroup v-model="value" @change="record"><UiCheckbox value="rain">降雨</UiCheckbox><UiCheckbox value="water">水位</UiCheckbox><UiCheckbox value="disabled" disabled>禁用</UiCheckbox></UiCheckboxGroup>
1124
+ <p>当前值:{{ value }}</p>
1125
+ <p role="status">{{ message }}</p>
1126
+ </div>
1127
+ </template>
1128
+ <style scoped>
1129
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1130
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1131
+ </style>
1132
+
1133
+ ```
1134
+
1135
+ ### UiRadio 独立交互示例
1136
+
1137
+ ```vue
1138
+ <script setup lang="ts">
1139
+ import { ref } from 'vue'
1140
+ import { UiRadio, UiRadioGroup } from 'sky-ui-kit'
1141
+ const value = ref('observed')
1142
+ const message = ref('尚未操作')
1143
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1144
+ </script>
1145
+ <template>
1146
+ <div class="control-demo">
1147
+ <UiRadioGroup v-model="value" @change="record"><UiRadio value="observed">实况</UiRadio><UiRadio value="forecast">预报</UiRadio><UiRadio value="disabled" disabled>禁用</UiRadio></UiRadioGroup>
1148
+ <p>当前值:{{ value }}</p>
1149
+ <p role="status">{{ message }}</p>
1150
+ </div>
1151
+ </template>
1152
+ <style scoped>
1153
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1154
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1155
+ </style>
1156
+
1157
+ ```
1158
+
1159
+ ### UiCascaderPanel 独立交互示例
1160
+
1161
+ ```vue
1162
+ <script setup lang="ts">
1163
+ import { ref } from 'vue'
1164
+ import { UiCascaderPanel } from 'sky-ui-kit'
1165
+ const value = ref<string[]>([])
1166
+ const message = ref('尚未操作')
1167
+ const options = [{ value: 'hubei', label: '湖北省', children: [{ value: 'wuhan', label: '武汉市', children: [{ value: 'hankou', label: '汉口站' }] }] }]
1168
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1169
+ </script>
1170
+ <template>
1171
+ <div class="control-demo">
1172
+ <UiCascaderPanel v-model="value" :options="options" @change="record" />
1173
+ <p>当前值:{{ value }}</p>
1174
+ <p role="status">{{ message }}</p>
1175
+ </div>
1176
+ </template>
1177
+ <style scoped>
1178
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1179
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1180
+ </style>
1181
+
1182
+ ```
1183
+
1184
+ ### UiCascader 独立交互示例
1185
+
1186
+ ```vue
1187
+ <script setup lang="ts">
1188
+ import { ref } from 'vue'
1189
+ import { UiCascader } from 'sky-ui-kit'
1190
+ const value = ref<string[]>([])
1191
+ const message = ref('尚未操作')
1192
+ const options = [{ value: 'hubei', label: '湖北省', children: [{ value: 'wuhan', label: '武汉市', children: [{ value: 'hankou', label: '汉口站' }] }] }]
1193
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1194
+ </script>
1195
+ <template>
1196
+ <div class="control-demo">
1197
+ <UiCascader v-model="value" :options="options" filterable clearable placeholder="选择区域与测站" @change="record" />
1198
+ <p>当前值:{{ value }}</p>
1199
+ <p role="status">{{ message }}</p>
1200
+ </div>
1201
+ </template>
1202
+ <style scoped>
1203
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1204
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1205
+ </style>
1206
+
1207
+ ```
1208
+
1209
+ ### UiColorPickerPanel 独立交互示例
1210
+
1211
+ ```vue
1212
+ <script setup lang="ts">
1213
+ import { ref } from 'vue'
1214
+ import { UiColorPickerPanel } from 'sky-ui-kit'
1215
+ const value = ref('#1677ff')
1216
+ const message = ref('尚未操作')
1217
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1218
+ </script>
1219
+ <template>
1220
+ <div class="control-demo">
1221
+ <UiColorPickerPanel v-model="value" show-alpha @change="record" /><div :style="{ background: value, height: '32px', width: '120px' }">颜色效果</div>
1222
+ <p>当前值:{{ value }}</p>
1223
+ <p role="status">{{ message }}</p>
1224
+ </div>
1225
+ </template>
1226
+ <style scoped>
1227
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1228
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1229
+ </style>
1230
+
1231
+ ```
1232
+
1233
+ ### UiColorPicker 独立交互示例
1234
+
1235
+ ```vue
1236
+ <script setup lang="ts">
1237
+ import { ref } from 'vue'
1238
+ import { UiColorPicker } from 'sky-ui-kit'
1239
+ const value = ref('#1677ff')
1240
+ const message = ref('尚未操作')
1241
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1242
+ </script>
1243
+ <template>
1244
+ <div class="control-demo">
1245
+ <UiColorPicker v-model="value" show-alpha @change="record" /><div :style="{ background: value, height: '32px', width: '120px' }">颜色效果</div>
1246
+ <p>当前值:{{ value }}</p>
1247
+ <p role="status">{{ message }}</p>
1248
+ </div>
1249
+ </template>
1250
+ <style scoped>
1251
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1252
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1253
+ </style>
1254
+
1255
+ ```
1256
+
1257
+ ### UiDatePickerPanel 独立交互示例
1258
+
1259
+ ```vue
1260
+ <script setup lang="ts">
1261
+ import { ref } from 'vue'
1262
+ import { UiDatePickerPanel } from 'sky-ui-kit'
1263
+ const value = ref<Date>()
1264
+ const message = ref('尚未操作')
1265
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1266
+ </script>
1267
+ <template>
1268
+ <div class="control-demo">
1269
+ <UiDatePickerPanel v-model="value" type="date" @change="record" />
1270
+ <p>当前值:{{ value }}</p>
1271
+ <p role="status">{{ message }}</p>
1272
+ </div>
1273
+ </template>
1274
+ <style scoped>
1275
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1276
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1277
+ </style>
1278
+
1279
+ ```
1280
+
1281
+ ### UiDatePicker 独立交互示例
1282
+
1283
+ ```vue
1284
+ <script setup lang="ts">
1285
+ import { ref } from 'vue'
1286
+ import { UiDatePicker } from 'sky-ui-kit'
1287
+ const value = ref('')
1288
+ const message = ref('尚未操作')
1289
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1290
+ </script>
1291
+ <template>
1292
+ <div class="control-demo">
1293
+ <UiDatePicker v-model="value" type="date" value-format="YYYY-MM-DD" clearable placeholder="选择日期" @change="record" />
1294
+ <p>当前值:{{ value }}</p>
1295
+ <p role="status">{{ message }}</p>
1296
+ </div>
1297
+ </template>
1298
+ <style scoped>
1299
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1300
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1301
+ </style>
1302
+
1303
+ ```
1304
+
1305
+ ### UiInputNumber 独立交互示例
1306
+
1307
+ ```vue
1308
+ <script setup lang="ts">
1309
+ import { ref } from 'vue'
1310
+ import { UiInputNumber } from 'sky-ui-kit'
1311
+ const value = ref(20)
1312
+ const message = ref('尚未操作')
1313
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1314
+ </script>
1315
+ <template>
1316
+ <div class="control-demo">
1317
+ <UiInputNumber v-model="value" :min="0" :max="100" :step="5" @change="record" />
1318
+ <p>当前值:{{ value }}</p>
1319
+ <p role="status">{{ message }}</p>
1320
+ </div>
1321
+ </template>
1322
+ <style scoped>
1323
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1324
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1325
+ </style>
1326
+
1327
+ ```
1328
+
1329
+ ### UiInputTag 独立交互示例
1330
+
1331
+ ```vue
1332
+ <script setup lang="ts">
1333
+ import { ref } from 'vue'
1334
+ import { UiInputTag } from 'sky-ui-kit'
1335
+ const value = ref(['汉口站', '宜昌站'])
1336
+ const message = ref('尚未操作')
1337
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1338
+ </script>
1339
+ <template>
1340
+ <div class="control-demo">
1341
+ <UiInputTag v-model="value" clearable placeholder="输入站名后按回车" @change="record" />
1342
+ <p>当前值:{{ value }}</p>
1343
+ <p role="status">{{ message }}</p>
1344
+ </div>
1345
+ </template>
1346
+ <style scoped>
1347
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1348
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1349
+ </style>
1350
+
1351
+ ```
1352
+
1353
+ ### UiInputOtp 独立交互示例
1354
+
1355
+ ```vue
1356
+ <script setup lang="ts">
1357
+ import { ref } from 'vue'
1358
+ import { UiInputOtp } from 'sky-ui-kit'
1359
+ const value = ref('')
1360
+ const message = ref('尚未操作')
1361
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1362
+ </script>
1363
+ <template>
1364
+ <div class="control-demo">
1365
+ <UiInputOtp v-model="value" :length="6" @change="record" />
1366
+ <p>当前值:{{ value }}</p>
1367
+ <p role="status">{{ message }}</p>
1368
+ </div>
1369
+ </template>
1370
+ <style scoped>
1371
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1372
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1373
+ </style>
1374
+
1375
+ ```
1376
+
1377
+ ### UiRate 独立交互示例
1378
+
1379
+ ```vue
1380
+ <script setup lang="ts">
1381
+ import { ref } from 'vue'
1382
+ import { UiRate } from 'sky-ui-kit'
1383
+ const value = ref(3)
1384
+ const message = ref('尚未操作')
1385
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1386
+ </script>
1387
+ <template>
1388
+ <div class="control-demo">
1389
+ <UiRate v-model="value" show-score @change="record" />
1390
+ <p>当前值:{{ value }}</p>
1391
+ <p role="status">{{ message }}</p>
1392
+ </div>
1393
+ </template>
1394
+ <style scoped>
1395
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1396
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1397
+ </style>
1398
+
1399
+ ```
1400
+
1401
+ ### UiInput 独立交互示例
1402
+
1403
+ ```vue
1404
+ <script setup lang="ts">
1405
+ import { ref } from 'vue'
1406
+ import { UiInput } from 'sky-ui-kit'
1407
+ const value = ref('')
1408
+ const message = ref('尚未操作')
1409
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1410
+ </script>
1411
+ <template>
1412
+ <div class="control-demo">
1413
+ <UiInput v-model="value" clearable placeholder="输入测站名称" @change="record" /><UiInput model-value="禁用状态" disabled />
1414
+ <p>当前值:{{ value }}</p>
1415
+ <p role="status">{{ message }}</p>
1416
+ </div>
1417
+ </template>
1418
+ <style scoped>
1419
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1420
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1421
+ </style>
1422
+
1423
+ ```
1424
+
1425
+ ### UiCollapseTransition 独立交互示例
1426
+
1427
+ ```vue
1428
+ <script setup lang="ts">
1429
+ import { ref } from 'vue'
1430
+ import { UiCollapseTransition, UiButton } from 'sky-ui-kit'
1431
+ const value = ref(true)
1432
+ const message = ref('尚未操作')
1433
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1434
+ </script>
1435
+ <template>
1436
+ <div class="control-demo">
1437
+ <UiButton @click="value = !value">展开 / 收起</UiButton><UiCollapseTransition><div v-show="value" class="collapse-content">测站信息 · 点击按钮查看折叠过渡效果</div></UiCollapseTransition>
1438
+ <p>当前值:{{ value }}</p>
1439
+ <p role="status">{{ message }}</p>
1440
+ </div>
1441
+ </template>
1442
+ <style scoped>
1443
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1444
+ .collapse-content { padding: 24px; background: var(--color-surface, #eef5ff); margin-top: 12px; }
1445
+ </style>
1446
+
1447
+ ```
1448
+
1449
+ ### UiTimeSelect 独立交互示例
1450
+
1451
+ ```vue
1452
+ <script setup lang="ts">
1453
+ import { ref } from 'vue'
1454
+ import { UiTimeSelect } from 'sky-ui-kit'
1455
+ const message = ref('尚未操作')
1456
+ const value = ref('09:00')
1457
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1458
+ </script>
1459
+ <template>
1460
+ <div class="control-demo">
1461
+ <UiTimeSelect v-model="value" start="08:00" step="00:30" end="18:00" clearable placeholder="选择时间" @change="record" />
1462
+ <p>当前值:{{ value }}</p>
1463
+ <p role="status">{{ message }}</p>
1464
+ </div>
1465
+ </template>
1466
+ <style scoped>
1467
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1468
+ </style>
1469
+
1470
+ ```
1471
+
1472
+ ### UiTransfer 独立交互示例
1473
+
1474
+ ```vue
1475
+ <script setup lang="ts">
1476
+ import { ref } from 'vue'
1477
+ import { UiTransfer } from 'sky-ui-kit'
1478
+ const message = ref('尚未操作')
1479
+ const value = ref([1])
1480
+ const data = [{ key: 1, label: '汉口站' }, { key: 2, label: '宜昌站' }, { key: 3, label: '城陵矶站' }, { key: 4, label: '维护站点', disabled: true }]
1481
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1482
+ </script>
1483
+ <template>
1484
+ <div class="control-demo">
1485
+ <UiTransfer v-model="value" :data="data" filterable :titles="['可选测站', '已选测站']" @change="record" />
1486
+ <p>当前值:{{ value }}</p>
1487
+ <p role="status">{{ message }}</p>
1488
+ </div>
1489
+ </template>
1490
+ <style scoped>
1491
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1492
+ </style>
1493
+
1494
+ ```
1495
+
1496
+ ### UiTreeSelect 独立交互示例
1497
+
1498
+ ```vue
1499
+ <script setup lang="ts">
1500
+ import { ref } from 'vue'
1501
+ import { UiTreeSelect } from 'sky-ui-kit'
1502
+ const message = ref('尚未操作')
1503
+ const value = ref('hankou')
1504
+ const data = [{ value: 'yangtze', label: '长江流域', children: [{ value: 'hankou', label: '汉口站' }, { value: 'yichang', label: '宜昌站' }] }, { value: 'dongting', label: '洞庭湖流域', children: [{ value: 'chenglingji', label: '城陵矶站' }] }]
1505
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1506
+ </script>
1507
+ <template>
1508
+ <div class="control-demo">
1509
+ <UiTreeSelect v-model="value" :data="data" filterable clearable placeholder="搜索测站" @change="record" />
1510
+ <p>当前值:{{ value }}</p>
1511
+ <p role="status">{{ message }}</p>
1512
+ </div>
1513
+ </template>
1514
+ <style scoped>
1515
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1516
+ </style>
1517
+
1518
+ ```
1519
+
1520
+ ### UiMention 独立交互示例
1521
+
1522
+ ```vue
1523
+ <script setup lang="ts">
1524
+ import { ref } from 'vue'
1525
+ import { UiMention } from 'sky-ui-kit'
1526
+ const message = ref('尚未操作')
1527
+ const value = ref('')
1528
+ const options = [{ value: 'zhang', label: '张工' }, { value: 'li', label: '李工' }, { value: 'wang', label: '王工' }]
1529
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1530
+ </script>
1531
+ <template>
1532
+ <div class="control-demo">
1533
+ <UiMention v-model="value" :options="options" type="textarea" placeholder="输入 @ 选择团队成员" @select="record" />
1534
+ <p>当前值:{{ value }}</p>
1535
+ <p role="status">{{ message }}</p>
1536
+ </div>
1537
+ </template>
1538
+ <style scoped>
1539
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1540
+ </style>
1541
+
1542
+ ```
1543
+
1544
+ ### UiUpload 独立交互示例
1545
+
1546
+ ```vue
1547
+ <script setup lang="ts">
1548
+ import { ref } from 'vue'
1549
+ import type { UploadUserFile, UploadProps } from 'element-plus'
1550
+ import { Upload } from '@element-plus/icons-vue'
1551
+ import { UiUpload, UiButton } from 'sky-ui-kit'
1552
+ const message = ref('尚未操作')
1553
+ const files = ref<UploadUserFile[]>([])
1554
+ const value = ref('尚未选择文件')
1555
+ const changed: UploadProps['onChange'] = file => { value.value = file.name; message.value = '已选择本地文件:' + file.name }
1556
+ function record(next: unknown) { message.value = '变化:' + JSON.stringify(next) }
1557
+ </script>
1558
+ <template>
1559
+ <div class="control-demo">
1560
+ <UiUpload v-model:file-list="files" :auto-upload="false" :on-change="changed" multiple accept=".csv,.xlsx" :limit="3" :on-exceed="() => message = '最多选择 3 个文件'" :on-remove="() => message = '已移除文件'">
1561
+ <UiButton type="primary" :icon="Upload">选择数据文件</UiButton>
1562
+ <template #tip><p>支持 CSV / XLSX,最多 3 个文件。演示仅选择和移除文件;真实上传由应用配置 http-request。</p></template>
1563
+ </UiUpload>
1564
+ <p>当前值:{{ value }}</p>
1565
+ <p role="status">{{ message }}</p>
1566
+ </div>
1567
+ </template>
1568
+ <style scoped>
1569
+ .control-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1570
+ </style>
1571
+
1572
+ ```
1573
+
1574
+ ### UiAffix 分类独立示例
1575
+
1576
+ ```vue
1577
+ <script setup lang="ts">
1578
+ import { ref } from 'vue'
1579
+ import { UiAffix, UiButton } from 'sky-ui-kit'
1580
+ const message = ref('尚未操作')
1581
+ const open = ref(false)
1582
+
1583
+
1584
+ </script>
1585
+ <template>
1586
+ <div class="catalog-demo affix-example-host">
1587
+ <div class="scroll-host"><div style="height: 500px"><UiAffix target=".affix-example-host" :offset="20"><UiButton>固定操作</UiButton></UiAffix><p>滚动页面观察固定效果</p></div></div>
1588
+ <p role="status">{{ message }}</p>
1589
+ </div>
1590
+ </template>
1591
+ <style scoped>
1592
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1593
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1594
+ .scroll-host { height: 180px; overflow: auto; }
1595
+ </style>
1596
+
1597
+ ```
1598
+
1599
+ ### UiAlert 分类独立示例
1600
+
1601
+ ```vue
1602
+ <script setup lang="ts">
1603
+ import { ref } from 'vue'
1604
+ import { UiAlert } from 'sky-ui-kit'
1605
+ const message = ref('尚未操作')
1606
+ const open = ref(false)
1607
+
1608
+
1609
+ </script>
1610
+ <template>
1611
+ <div class="catalog-demo affix-example-host">
1612
+ <UiAlert title="数据已更新" type="success" show-icon @close="message = '提示已关闭'" /><UiAlert title="部分测站数据缺失" type="warning" show-icon />
1613
+ <p role="status">{{ message }}</p>
1614
+ </div>
1615
+ </template>
1616
+ <style scoped>
1617
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1618
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1619
+ .scroll-host { height: 180px; overflow: auto; }
1620
+ </style>
1621
+
1622
+ ```
1623
+
1624
+ ### UiPopper 分类独立示例
1625
+
1626
+ ```vue
1627
+ <script setup lang="ts">
1628
+ import { ref } from 'vue'
1629
+ import { UiButton, UiPopover } from 'sky-ui-kit'
1630
+ const message = ref('尚未操作')
1631
+ const open = ref(false)
1632
+
1633
+
1634
+ </script>
1635
+ <template>
1636
+ <div class="catalog-demo affix-example-host">
1637
+ <p>UiPopper 为浮层底层定位组件,应用通常通过 UiPopover / UiTooltip 使用。</p><UiPopover content="通过公开浮层组件使用定位能力" trigger="click"><template #reference><UiButton>查看定位效果</UiButton></template></UiPopover>
1638
+ <p role="status">{{ message }}</p>
1639
+ </div>
1640
+ </template>
1641
+ <style scoped>
1642
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1643
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1644
+ .scroll-host { height: 180px; overflow: auto; }
1645
+ </style>
1646
+
1647
+ ```
1648
+
1649
+ ### UiTooltip 分类独立示例
1650
+
1651
+ ```vue
1652
+ <script setup lang="ts">
1653
+ import { ref } from 'vue'
1654
+ import { UiButton, UiTooltip } from 'sky-ui-kit'
1655
+ const message = ref('尚未操作')
1656
+ const open = ref(false)
1657
+
1658
+
1659
+ </script>
1660
+ <template>
1661
+ <div class="catalog-demo affix-example-host">
1662
+ <UiTooltip content="查询最新监测数据" placement="top"><UiButton>悬停查看说明</UiButton></UiTooltip>
1663
+ <p role="status">{{ message }}</p>
1664
+ </div>
1665
+ </template>
1666
+ <style scoped>
1667
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1668
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1669
+ .scroll-host { height: 180px; overflow: auto; }
1670
+ </style>
1671
+
1672
+ ```
1673
+
1674
+ ### UiAvatar 分类独立示例
1675
+
1676
+ ```vue
1677
+ <script setup lang="ts">
1678
+ import { ref } from 'vue'
1679
+ import { UiAvatar, UiAvatarGroup } from 'sky-ui-kit'
1680
+ const message = ref('尚未操作')
1681
+ const open = ref(false)
1682
+
1683
+
1684
+ </script>
1685
+ <template>
1686
+ <div class="catalog-demo affix-example-host">
1687
+ <UiAvatar>张</UiAvatar><UiAvatar :size="48">团队</UiAvatar><UiAvatarGroup><UiAvatar>李</UiAvatar><UiAvatar>王</UiAvatar></UiAvatarGroup>
1688
+ <p role="status">{{ message }}</p>
1689
+ </div>
1690
+ </template>
1691
+ <style scoped>
1692
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1693
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1694
+ .scroll-host { height: 180px; overflow: auto; }
1695
+ </style>
1696
+
1697
+ ```
1698
+
1699
+ ### UiBacktop 分类独立示例
1700
+
1701
+ ```vue
1702
+ <script setup lang="ts">
1703
+ import { ref } from 'vue'
1704
+ import { UiBacktop } from 'sky-ui-kit'
1705
+ const message = ref('尚未操作')
1706
+ const open = ref(false)
1707
+
1708
+
1709
+ </script>
1710
+ <template>
1711
+ <div class="catalog-demo affix-example-host">
1712
+ <div class="backtop-host" style="height: 160px; overflow: auto"><div style="height: 600px">在这里向下滚动<UiBacktop target=".backtop-host" :visibility-height="60" :right="40" :bottom="40" /></div></div>
1713
+ <p role="status">{{ message }}</p>
1714
+ </div>
1715
+ </template>
1716
+ <style scoped>
1717
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1718
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1719
+ .scroll-host { height: 180px; overflow: auto; }
1720
+ </style>
1721
+
1722
+ ```
1723
+
1724
+ ### UiBadge 分类独立示例
1725
+
1726
+ ```vue
1727
+ <script setup lang="ts">
1728
+ import { ref } from 'vue'
1729
+ import { UiBadge, UiButton } from 'sky-ui-kit'
1730
+ const message = ref('尚未操作')
1731
+ const open = ref(false)
1732
+
1733
+
1734
+ </script>
1735
+ <template>
1736
+ <div class="catalog-demo affix-example-host">
1737
+ <UiBadge :value="12"><UiButton>消息</UiButton></UiBadge>
1738
+ <p role="status">{{ message }}</p>
1739
+ </div>
1740
+ </template>
1741
+ <style scoped>
1742
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1743
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1744
+ .scroll-host { height: 180px; overflow: auto; }
1745
+ </style>
1746
+
1747
+ ```
1748
+
1749
+ ### UiBreadcrumb 分类独立示例
1750
+
1751
+ ```vue
1752
+ <script setup lang="ts">
1753
+ import { ref } from 'vue'
1754
+ import { UiBreadcrumb, UiBreadcrumbItem } from 'sky-ui-kit'
1755
+ const message = ref('尚未操作')
1756
+ const open = ref(false)
1757
+
1758
+
1759
+ </script>
1760
+ <template>
1761
+ <div class="catalog-demo affix-example-host">
1762
+ <UiBreadcrumb separator="/"><UiBreadcrumbItem>平台</UiBreadcrumbItem><UiBreadcrumbItem>数据维护</UiBreadcrumbItem><UiBreadcrumbItem>测站管理</UiBreadcrumbItem></UiBreadcrumb>
1763
+ <p role="status">{{ message }}</p>
1764
+ </div>
1765
+ </template>
1766
+ <style scoped>
1767
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1768
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1769
+ .scroll-host { height: 180px; overflow: auto; }
1770
+ </style>
1771
+
1772
+ ```
1773
+
1774
+ ### UiConfigProvider 分类独立示例
1775
+
1776
+ ```vue
1777
+ <script setup lang="ts">
1778
+ import { ref } from 'vue'
1779
+ import { UiButton, UiConfigProvider, UiRadio, UiRadioGroup } from 'sky-ui-kit'
1780
+ const message = ref('尚未操作')
1781
+ const open = ref(false)
1782
+ const size = ref<'small' | 'default' | 'large'>('default')
1783
+
1784
+ </script>
1785
+ <template>
1786
+ <div class="catalog-demo affix-example-host">
1787
+ <UiConfigProvider :size="size"><UiRadioGroup v-model="size"><UiRadio value="small">小</UiRadio><UiRadio value="default">默认</UiRadio><UiRadio value="large">大</UiRadio></UiRadioGroup><UiButton>跟随配置尺寸</UiButton></UiConfigProvider>
1788
+ <p role="status">{{ message }}</p>
1789
+ </div>
1790
+ </template>
1791
+ <style scoped>
1792
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1793
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1794
+ .scroll-host { height: 180px; overflow: auto; }
1795
+ </style>
1796
+
1797
+ ```
1798
+
1799
+ ### UiTag 分类独立示例
1800
+
1801
+ ```vue
1802
+ <script setup lang="ts">
1803
+ import { ref } from 'vue'
1804
+ import { UiTag } from 'sky-ui-kit'
1805
+ const message = ref('尚未操作')
1806
+ const open = ref(false)
1807
+ const tags = ref(['降雨', '水位'])
1808
+
1809
+ </script>
1810
+ <template>
1811
+ <div class="catalog-demo affix-example-host">
1812
+ <UiTag v-for="tag in tags" :key="tag" closable @close="tags = tags.filter(item => item !== tag)">{{ tag }}</UiTag>
1813
+ <p role="status">{{ message }}</p>
1814
+ </div>
1815
+ </template>
1816
+ <style scoped>
1817
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1818
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1819
+ .scroll-host { height: 180px; overflow: auto; }
1820
+ </style>
1821
+
1822
+ ```
1823
+
1824
+ ### UiCalendar 分类独立示例
1825
+
1826
+ ```vue
1827
+ <script setup lang="ts">
1828
+ import { ref } from 'vue'
1829
+ import { UiCalendar } from 'sky-ui-kit'
1830
+ const message = ref('尚未操作')
1831
+ const open = ref(false)
1832
+ const date = ref(new Date())
1833
+
1834
+ </script>
1835
+ <template>
1836
+ <div class="catalog-demo affix-example-host">
1837
+ <UiCalendar v-model="date" />
1838
+ <p role="status">{{ message }}</p>
1839
+ </div>
1840
+ </template>
1841
+ <style scoped>
1842
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1843
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1844
+ .scroll-host { height: 180px; overflow: auto; }
1845
+ </style>
1846
+
1847
+ ```
1848
+
1849
+ ### UiCard 分类独立示例
1850
+
1851
+ ```vue
1852
+ <script setup lang="ts">
1853
+ import { ref } from 'vue'
1854
+ import { UiCard } from 'sky-ui-kit'
1855
+ const message = ref('尚未操作')
1856
+ const open = ref(false)
1857
+
1858
+
1859
+ </script>
1860
+ <template>
1861
+ <div class="catalog-demo affix-example-host">
1862
+ <UiCard><template #header>测站信息</template>汉口站 · 长江流域<template #footer>更新于 08:00</template></UiCard>
1863
+ <p role="status">{{ message }}</p>
1864
+ </div>
1865
+ </template>
1866
+ <style scoped>
1867
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1868
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1869
+ .scroll-host { height: 180px; overflow: auto; }
1870
+ </style>
1871
+
1872
+ ```
1873
+
1874
+ ### UiCarousel 分类独立示例
1875
+
1876
+ ```vue
1877
+ <script setup lang="ts">
1878
+ import { ref } from 'vue'
1879
+ import { UiCarousel, UiCarouselItem } from 'sky-ui-kit'
1880
+ const message = ref('尚未操作')
1881
+ const open = ref(false)
1882
+
1883
+
1884
+ </script>
1885
+ <template>
1886
+ <div class="catalog-demo affix-example-host">
1887
+ <UiCarousel height="160px" :autoplay="false"><UiCarouselItem v-for="name in ['实况', '预报', '历史']" :key="name"><div class="demo-block">{{ name }}</div></UiCarouselItem></UiCarousel>
1888
+ <p role="status">{{ message }}</p>
1889
+ </div>
1890
+ </template>
1891
+ <style scoped>
1892
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1893
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1894
+ .scroll-host { height: 180px; overflow: auto; }
1895
+ </style>
1896
+
1897
+ ```
1898
+
1899
+ ### UiCheckTag 分类独立示例
1900
+
1901
+ ```vue
1902
+ <script setup lang="ts">
1903
+ import { ref } from 'vue'
1904
+ import { UiCheckTag } from 'sky-ui-kit'
1905
+ const message = ref('尚未操作')
1906
+ const open = ref(false)
1907
+ const checked = ref(true)
1908
+
1909
+ </script>
1910
+ <template>
1911
+ <div class="catalog-demo affix-example-host">
1912
+ <UiCheckTag :checked="checked" @change="checked = $event">降雨图层</UiCheckTag>
1913
+ <p role="status">{{ message }}</p>
1914
+ </div>
1915
+ </template>
1916
+ <style scoped>
1917
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1918
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1919
+ .scroll-host { height: 180px; overflow: auto; }
1920
+ </style>
1921
+
1922
+ ```
1923
+
1924
+ ### UiCollapse 分类独立示例
1925
+
1926
+ ```vue
1927
+ <script setup lang="ts">
1928
+ import { ref } from 'vue'
1929
+ import { UiCollapse, UiCollapseItem } from 'sky-ui-kit'
1930
+ const message = ref('尚未操作')
1931
+ const open = ref(false)
1932
+ const active = ref(['station'])
1933
+
1934
+ </script>
1935
+ <template>
1936
+ <div class="catalog-demo affix-example-host">
1937
+ <UiCollapse v-model="active"><UiCollapseItem title="测站信息" name="station">汉口站 · 长江流域</UiCollapseItem><UiCollapseItem title="监测数据" name="data">水位 28.6 m</UiCollapseItem></UiCollapse>
1938
+ <p role="status">{{ message }}</p>
1939
+ </div>
1940
+ </template>
1941
+ <style scoped>
1942
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1943
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1944
+ .scroll-host { height: 180px; overflow: auto; }
1945
+ </style>
1946
+
1947
+ ```
1948
+
1949
+ ### UiStatistic 分类独立示例
1950
+
1951
+ ```vue
1952
+ <script setup lang="ts">
1953
+ import { ref } from 'vue'
1954
+ import { UiStatistic } from 'sky-ui-kit'
1955
+ const message = ref('尚未操作')
1956
+ const open = ref(false)
1957
+
1958
+
1959
+ </script>
1960
+ <template>
1961
+ <div class="catalog-demo affix-example-host">
1962
+ <UiStatistic title="监测站数量" :value="128" />
1963
+ <p role="status">{{ message }}</p>
1964
+ </div>
1965
+ </template>
1966
+ <style scoped>
1967
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1968
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1969
+ .scroll-host { height: 180px; overflow: auto; }
1970
+ </style>
1971
+
1972
+ ```
1973
+
1974
+ ### UiCountdown 分类独立示例
1975
+
1976
+ ```vue
1977
+ <script setup lang="ts">
1978
+ import { ref } from 'vue'
1979
+ import { UiCountdown } from 'sky-ui-kit'
1980
+ const message = ref('尚未操作')
1981
+ const open = ref(false)
1982
+ const deadline = Date.now() + 3600000
1983
+
1984
+ </script>
1985
+ <template>
1986
+ <div class="catalog-demo affix-example-host">
1987
+ <UiCountdown title="下次更新" :value="deadline" @finish="message = '倒计时结束'" />
1988
+ <p role="status">{{ message }}</p>
1989
+ </div>
1990
+ </template>
1991
+ <style scoped>
1992
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
1993
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
1994
+ .scroll-host { height: 180px; overflow: auto; }
1995
+ </style>
1996
+
1997
+ ```
1998
+
1999
+ ### UiDescriptions 分类独立示例
2000
+
2001
+ ```vue
2002
+ <script setup lang="ts">
2003
+ import { ref } from 'vue'
2004
+ import { UiDescriptions, UiDescriptionsItem } from 'sky-ui-kit'
2005
+ const message = ref('尚未操作')
2006
+ const open = ref(false)
2007
+
2008
+
2009
+ </script>
2010
+ <template>
2011
+ <div class="catalog-demo affix-example-host">
2012
+ <UiDescriptions title="测站档案" :column="2" border><UiDescriptionsItem label="测站">汉口站</UiDescriptionsItem><UiDescriptionsItem label="流域">长江流域</UiDescriptionsItem></UiDescriptions>
2013
+ <p role="status">{{ message }}</p>
2014
+ </div>
2015
+ </template>
2016
+ <style scoped>
2017
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2018
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2019
+ .scroll-host { height: 180px; overflow: auto; }
2020
+ </style>
2021
+
2022
+ ```
2023
+
2024
+ ### UiDialog 分类独立示例
2025
+
2026
+ ```vue
2027
+ <script setup lang="ts">
2028
+ import { ref } from 'vue'
2029
+ import { UiButton, UiDialog } from 'sky-ui-kit'
2030
+ const message = ref('尚未操作')
2031
+ const open = ref(false)
2032
+
2033
+
2034
+ </script>
2035
+ <template>
2036
+ <div class="catalog-demo affix-example-host">
2037
+ <UiButton @click="open = true">打开对话框</UiButton><UiDialog v-model="open" title="测站详情" width="480px"><p>汉口站 · 水位 28.6 m</p><template #footer><UiButton @click="open = false">关闭</UiButton></template></UiDialog>
2038
+ <p role="status">{{ message }}</p>
2039
+ </div>
2040
+ </template>
2041
+ <style scoped>
2042
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2043
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2044
+ .scroll-host { height: 180px; overflow: auto; }
2045
+ </style>
2046
+
2047
+ ```
2048
+
2049
+ ### UiDrawer 分类独立示例
2050
+
2051
+ ```vue
2052
+ <script setup lang="ts">
2053
+ import { ref } from 'vue'
2054
+ import { UiButton, UiDrawer } from 'sky-ui-kit'
2055
+ const message = ref('尚未操作')
2056
+ const open = ref(false)
2057
+
2058
+
2059
+ </script>
2060
+ <template>
2061
+ <div class="catalog-demo affix-example-host">
2062
+ <UiButton @click="open = true">打开抽屉</UiButton><UiDrawer v-model="open" title="测站详情" size="320px">汉口站 · 长江流域</UiDrawer>
2063
+ <p role="status">{{ message }}</p>
2064
+ </div>
2065
+ </template>
2066
+ <style scoped>
2067
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2068
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2069
+ .scroll-host { height: 180px; overflow: auto; }
2070
+ </style>
2071
+
2072
+ ```
2073
+
2074
+ ### UiDropdown 分类独立示例
2075
+
2076
+ ```vue
2077
+ <script setup lang="ts">
2078
+ import { ref } from 'vue'
2079
+ import { UiButton, UiDropdown, UiDropdownItem, UiDropdownMenu } from 'sky-ui-kit'
2080
+ const message = ref('尚未操作')
2081
+ const open = ref(false)
2082
+
2083
+
2084
+ </script>
2085
+ <template>
2086
+ <div class="catalog-demo affix-example-host">
2087
+ <UiDropdown @command="message = '操作:' + $event"><UiButton>更多操作</UiButton><template #dropdown><UiDropdownMenu><UiDropdownItem command="export">导出</UiDropdownItem><UiDropdownItem command="refresh">刷新</UiDropdownItem></UiDropdownMenu></template></UiDropdown>
2088
+ <p role="status">{{ message }}</p>
2089
+ </div>
2090
+ </template>
2091
+ <style scoped>
2092
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2093
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2094
+ .scroll-host { height: 180px; overflow: auto; }
2095
+ </style>
2096
+
2097
+ ```
2098
+
2099
+ ### UiEmpty 分类独立示例
2100
+
2101
+ ```vue
2102
+ <script setup lang="ts">
2103
+ import { ref } from 'vue'
2104
+ import { UiButton, UiEmpty } from 'sky-ui-kit'
2105
+ const message = ref('尚未操作')
2106
+ const open = ref(false)
2107
+
2108
+
2109
+ </script>
2110
+ <template>
2111
+ <div class="catalog-demo affix-example-host">
2112
+ <UiEmpty description="暂无监测数据"><UiButton @click="message = '刷新事件由应用接入'">刷新</UiButton></UiEmpty>
2113
+ <p role="status">{{ message }}</p>
2114
+ </div>
2115
+ </template>
2116
+ <style scoped>
2117
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2118
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2119
+ .scroll-host { height: 180px; overflow: auto; }
2120
+ </style>
2121
+
2122
+ ```
2123
+
2124
+ ### UiImageViewer 分类独立示例
2125
+
2126
+ ```vue
2127
+ <script setup lang="ts">
2128
+ import { ref } from 'vue'
2129
+ import { UiButton, UiImageViewer } from 'sky-ui-kit'
2130
+ const message = ref('尚未操作')
2131
+ const open = ref(false)
2132
+
2133
+ const imageUrl = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="360" height="200"><rect width="360" height="200" fill="#e8f2ff"/><path d="M0 150L90 60L180 120L270 40L360 150" fill="none" stroke="#1677ff" stroke-width="6"/></svg>')
2134
+ </script>
2135
+ <template>
2136
+ <div class="catalog-demo affix-example-host">
2137
+ <UiButton @click="open = true">打开图片预览</UiButton><UiImageViewer v-if="open" :url-list="[imageUrl]" @close="open = false" />
2138
+ <p role="status">{{ message }}</p>
2139
+ </div>
2140
+ </template>
2141
+ <style scoped>
2142
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2143
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2144
+ .scroll-host { height: 180px; overflow: auto; }
2145
+ </style>
2146
+
2147
+ ```
2148
+
2149
+ ### UiImage 分类独立示例
2150
+
2151
+ ```vue
2152
+ <script setup lang="ts">
2153
+ import { ref } from 'vue'
2154
+ import { UiImage } from 'sky-ui-kit'
2155
+ const message = ref('尚未操作')
2156
+ const open = ref(false)
2157
+
2158
+ const imageUrl = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="360" height="200"><rect width="360" height="200" fill="#e8f2ff"/><path d="M0 150L90 60L180 120L270 40L360 150" fill="none" stroke="#1677ff" stroke-width="6"/></svg>')
2159
+ </script>
2160
+ <template>
2161
+ <div class="catalog-demo affix-example-host">
2162
+ <UiImage style="width: 180px; height: 100px" :src="imageUrl" :preview-src-list="[imageUrl]" fit="cover" />
2163
+ <p role="status">{{ message }}</p>
2164
+ </div>
2165
+ </template>
2166
+ <style scoped>
2167
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2168
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2169
+ .scroll-host { height: 180px; overflow: auto; }
2170
+ </style>
2171
+
2172
+ ```
2173
+
2174
+ ### UiMenu 分类独立示例
2175
+
2176
+ ```vue
2177
+ <script setup lang="ts">
2178
+ import { ref } from 'vue'
2179
+ import { UiMenu, UiMenuItem, UiSubMenu } from 'sky-ui-kit'
2180
+ const message = ref('尚未操作')
2181
+ const open = ref(false)
2182
+
2183
+
2184
+ </script>
2185
+ <template>
2186
+ <div class="catalog-demo affix-example-host">
2187
+ <UiMenu default-active="station" @select="message = $event"><UiMenuItem index="station">测站管理</UiMenuItem><UiSubMenu index="data"><template #title>数据管理</template><UiMenuItem index="rain">降雨数据</UiMenuItem></UiSubMenu></UiMenu>
2188
+ <p role="status">{{ message }}</p>
2189
+ </div>
2190
+ </template>
2191
+ <style scoped>
2192
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2193
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2194
+ .scroll-host { height: 180px; overflow: auto; }
2195
+ </style>
2196
+
2197
+ ```
2198
+
2199
+ ### UiPageHeader 分类独立示例
2200
+
2201
+ ```vue
2202
+ <script setup lang="ts">
2203
+ import { ref } from 'vue'
2204
+ import { UiPageHeader } from 'sky-ui-kit'
2205
+ const message = ref('尚未操作')
2206
+ const open = ref(false)
2207
+
2208
+
2209
+ </script>
2210
+ <template>
2211
+ <div class="catalog-demo affix-example-host">
2212
+ <UiPageHeader @back="message = '返回列表事件'"><template #content>测站详情</template></UiPageHeader>
2213
+ <p role="status">{{ message }}</p>
2214
+ </div>
2215
+ </template>
2216
+ <style scoped>
2217
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2218
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2219
+ .scroll-host { height: 180px; overflow: auto; }
2220
+ </style>
2221
+
2222
+ ```
2223
+
2224
+ ### UiPagination 分类独立示例
2225
+
2226
+ ```vue
2227
+ <script setup lang="ts">
2228
+ import { ref } from 'vue'
2229
+ import { UiPagination } from 'sky-ui-kit'
2230
+ const message = ref('尚未操作')
2231
+ const open = ref(false)
2232
+ const page = ref(1)
2233
+
2234
+ </script>
2235
+ <template>
2236
+ <div class="catalog-demo affix-example-host">
2237
+ <UiPagination v-model:current-page="page" :page-size="10" :total="100" layout="prev, pager, next, total" @current-change="message = '页码:' + $event" />
2238
+ <p role="status">{{ message }}</p>
2239
+ </div>
2240
+ </template>
2241
+ <style scoped>
2242
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2243
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2244
+ .scroll-host { height: 180px; overflow: auto; }
2245
+ </style>
2246
+
2247
+ ```
2248
+
2249
+ ### UiPopconfirm 分类独立示例
2250
+
2251
+ ```vue
2252
+ <script setup lang="ts">
2253
+ import { ref } from 'vue'
2254
+ import { UiButton, UiPopconfirm } from 'sky-ui-kit'
2255
+ const message = ref('尚未操作')
2256
+ const open = ref(false)
2257
+
2258
+
2259
+ </script>
2260
+ <template>
2261
+ <div class="catalog-demo affix-example-host">
2262
+ <UiPopconfirm title="确认删除该记录?" @confirm="message = '已确认删除(演示)'" @cancel="message = '已取消'"><template #reference><UiButton type="danger">删除记录</UiButton></template></UiPopconfirm>
2263
+ <p role="status">{{ message }}</p>
2264
+ </div>
2265
+ </template>
2266
+ <style scoped>
2267
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2268
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2269
+ .scroll-host { height: 180px; overflow: auto; }
2270
+ </style>
2271
+
2272
+ ```
2273
+
2274
+ ### UiProgress 分类独立示例
2275
+
2276
+ ```vue
2277
+ <script setup lang="ts">
2278
+ import { ref } from 'vue'
2279
+ import { UiButton, UiProgress } from 'sky-ui-kit'
2280
+ const message = ref('尚未操作')
2281
+ const open = ref(false)
2282
+ const percent = ref(40)
2283
+
2284
+ </script>
2285
+ <template>
2286
+ <div class="catalog-demo affix-example-host">
2287
+ <UiProgress :percentage="percent" /><UiProgress type="circle" :percentage="percent" /><UiButton @click="percent = (percent + 10) % 110">更新进度</UiButton>
2288
+ <p role="status">{{ message }}</p>
2289
+ </div>
2290
+ </template>
2291
+ <style scoped>
2292
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2293
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2294
+ .scroll-host { height: 180px; overflow: auto; }
2295
+ </style>
2296
+
2297
+ ```
2298
+
2299
+ ### UiResult 分类独立示例
2300
+
2301
+ ```vue
2302
+ <script setup lang="ts">
2303
+ import { ref } from 'vue'
2304
+ import { UiButton, UiResult } from 'sky-ui-kit'
2305
+ const message = ref('尚未操作')
2306
+ const open = ref(false)
2307
+
2308
+
2309
+ </script>
2310
+ <template>
2311
+ <div class="catalog-demo affix-example-host">
2312
+ <UiResult icon="success" title="保存完成" sub-title="测站配置已更新"><template #extra><UiButton @click="message = '返回列表'">返回</UiButton></template></UiResult>
2313
+ <p role="status">{{ message }}</p>
2314
+ </div>
2315
+ </template>
2316
+ <style scoped>
2317
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2318
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2319
+ .scroll-host { height: 180px; overflow: auto; }
2320
+ </style>
2321
+
2322
+ ```
2323
+
2324
+ ### UiSkeleton 分类独立示例
2325
+
2326
+ ```vue
2327
+ <script setup lang="ts">
2328
+ import { ref } from 'vue'
2329
+ import { UiSkeleton, UiSkeletonItem, UiSwitch } from 'sky-ui-kit'
2330
+ const message = ref('尚未操作')
2331
+ const open = ref(false)
2332
+ const loading = ref(true)
2333
+
2334
+ </script>
2335
+ <template>
2336
+ <div class="catalog-demo affix-example-host">
2337
+ <UiSwitch v-model="loading" active-text="加载状态" /><UiSkeleton :loading="loading" animated><template #template><UiSkeletonItem variant="text" /></template><template #default>汉口站数据已加载</template></UiSkeleton>
2338
+ <p role="status">{{ message }}</p>
2339
+ </div>
2340
+ </template>
2341
+ <style scoped>
2342
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2343
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2344
+ .scroll-host { height: 180px; overflow: auto; }
2345
+ </style>
2346
+
2347
+ ```
2348
+
2349
+ ### UiSteps 分类独立示例
2350
+
2351
+ ```vue
2352
+ <script setup lang="ts">
2353
+ import { ref } from 'vue'
2354
+ import { UiButton, UiStep, UiSteps } from 'sky-ui-kit'
2355
+ const message = ref('尚未操作')
2356
+ const open = ref(false)
2357
+ const step = ref(1)
2358
+
2359
+ </script>
2360
+ <template>
2361
+ <div class="catalog-demo affix-example-host">
2362
+ <UiSteps :active="step" finish-status="success"><UiStep title="选择测站" /><UiStep title="配置数据" /><UiStep title="完成" /></UiSteps><UiButton @click="step = (step + 1) % 4">下一步</UiButton>
2363
+ <p role="status">{{ message }}</p>
2364
+ </div>
2365
+ </template>
2366
+ <style scoped>
2367
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2368
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2369
+ .scroll-host { height: 180px; overflow: auto; }
2370
+ </style>
2371
+
2372
+ ```
2373
+
2374
+ ### UiTable 分类独立示例
2375
+
2376
+ ```vue
2377
+ <script setup lang="ts">
2378
+ import { ref } from 'vue'
2379
+ import { UiTable, UiTableColumn } from 'sky-ui-kit'
2380
+ const message = ref('尚未操作')
2381
+ const open = ref(false)
2382
+
2383
+ const rows = [{ id: '1', name: '汉口站', value: 28.6 }, { id: '2', name: '宜昌站', value: 42.3 }]
2384
+ </script>
2385
+ <template>
2386
+ <div class="catalog-demo affix-example-host">
2387
+ <UiTable :data="rows" stripe @row-click="message = $event.name"><UiTableColumn prop="name" label="测站" /><UiTableColumn prop="value" label="水位 (m)" /></UiTable>
2388
+ <p role="status">{{ message }}</p>
2389
+ </div>
2390
+ </template>
2391
+ <style scoped>
2392
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2393
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2394
+ .scroll-host { height: 180px; overflow: auto; }
2395
+ </style>
2396
+
2397
+ ```
2398
+
2399
+ ### UiTableV2 分类独立示例
2400
+
2401
+ ```vue
2402
+ <script setup lang="ts">
2403
+ import { ref } from 'vue'
2404
+ import { UiTableV2 } from 'sky-ui-kit'
2405
+ const message = ref('尚未操作')
2406
+ const open = ref(false)
2407
+ const columns = [{ key: 'name', dataKey: 'name', title: '测站', width: 300 }, { key: 'value', dataKey: 'value', title: '水位', width: 300 }]
2408
+ const rows = [{ id: '1', name: '汉口站', value: 28.6 }, { id: '2', name: '宜昌站', value: 42.3 }]
2409
+ </script>
2410
+ <template>
2411
+ <div class="catalog-demo affix-example-host">
2412
+ <UiTableV2 :columns="columns" :data="rows" :width="600" :height="220" />
2413
+ <p role="status">{{ message }}</p>
2414
+ </div>
2415
+ </template>
2416
+ <style scoped>
2417
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2418
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2419
+ .scroll-host { height: 180px; overflow: auto; }
2420
+ </style>
2421
+
2422
+ ```
2423
+
2424
+ ### UiTabs 分类独立示例
2425
+
2426
+ ```vue
2427
+ <script setup lang="ts">
2428
+ import { ref } from 'vue'
2429
+ import { UiTabPane, UiTabs } from 'sky-ui-kit'
2430
+ const message = ref('尚未操作')
2431
+ const open = ref(false)
2432
+ const tab = ref('observed')
2433
+
2434
+ </script>
2435
+ <template>
2436
+ <div class="catalog-demo affix-example-host">
2437
+ <UiTabs v-model="tab"><UiTabPane label="实况" name="observed">实况监测数据</UiTabPane><UiTabPane label="预报" name="forecast">预报数据</UiTabPane></UiTabs>
2438
+ <p role="status">{{ message }}</p>
2439
+ </div>
2440
+ </template>
2441
+ <style scoped>
2442
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2443
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2444
+ .scroll-host { height: 180px; overflow: auto; }
2445
+ </style>
2446
+
2447
+ ```
2448
+
2449
+ ### UiTimeline 分类独立示例
2450
+
2451
+ ```vue
2452
+ <script setup lang="ts">
2453
+ import { ref } from 'vue'
2454
+ import { UiTimeline, UiTimelineItem } from 'sky-ui-kit'
2455
+ const message = ref('尚未操作')
2456
+ const open = ref(false)
2457
+
2458
+
2459
+ </script>
2460
+ <template>
2461
+ <div class="catalog-demo affix-example-host">
2462
+ <UiTimeline><UiTimelineItem timestamp="08:00">收到监测数据</UiTimelineItem><UiTimelineItem timestamp="09:00" type="success">完成数据校验</UiTimelineItem></UiTimeline>
2463
+ <p role="status">{{ message }}</p>
2464
+ </div>
2465
+ </template>
2466
+ <style scoped>
2467
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2468
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2469
+ .scroll-host { height: 180px; overflow: auto; }
2470
+ </style>
2471
+
2472
+ ```
2473
+
2474
+ ### UiTree 分类独立示例
2475
+
2476
+ ```vue
2477
+ <script setup lang="ts">
2478
+ import { ref } from 'vue'
2479
+ import { UiTree } from 'sky-ui-kit'
2480
+ const message = ref('尚未操作')
2481
+ const open = ref(false)
2482
+
2483
+ const tree = [{ id: 'basin', label: '长江流域', children: [{ id: 'hankou', label: '汉口站' }] }]
2484
+ </script>
2485
+ <template>
2486
+ <div class="catalog-demo affix-example-host">
2487
+ <UiTree :data="tree" node-key="id" show-checkbox @node-click="message = $event.label" />
2488
+ <p role="status">{{ message }}</p>
2489
+ </div>
2490
+ </template>
2491
+ <style scoped>
2492
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2493
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2494
+ .scroll-host { height: 180px; overflow: auto; }
2495
+ </style>
2496
+
2497
+ ```
2498
+
2499
+ ### UiTreeV2 分类独立示例
2500
+
2501
+ ```vue
2502
+ <script setup lang="ts">
2503
+ import { ref } from 'vue'
2504
+ import { UiTreeV2 } from 'sky-ui-kit'
2505
+ const message = ref('尚未操作')
2506
+ const open = ref(false)
2507
+
2508
+ const tree = [{ id: 'basin', label: '长江流域', children: [{ id: 'hankou', label: '汉口站' }] }]
2509
+ </script>
2510
+ <template>
2511
+ <div class="catalog-demo affix-example-host">
2512
+ <UiTreeV2 :data="tree" :height="180" show-checkbox @node-click="message = $event.label" />
2513
+ <p role="status">{{ message }}</p>
2514
+ </div>
2515
+ </template>
2516
+ <style scoped>
2517
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2518
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2519
+ .scroll-host { height: 180px; overflow: auto; }
2520
+ </style>
2521
+
2522
+ ```
2523
+
2524
+ ### UiWatermark 分类独立示例
2525
+
2526
+ ```vue
2527
+ <script setup lang="ts">
2528
+ import { ref } from 'vue'
2529
+ import { UiWatermark } from 'sky-ui-kit'
2530
+ const message = ref('尚未操作')
2531
+ const open = ref(false)
2532
+
2533
+
2534
+ </script>
2535
+ <template>
2536
+ <div class="catalog-demo affix-example-host">
2537
+ <UiWatermark content="一体化平台"><div class="demo-block">监测数据 · 水印区域</div></UiWatermark>
2538
+ <p role="status">{{ message }}</p>
2539
+ </div>
2540
+ </template>
2541
+ <style scoped>
2542
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2543
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2544
+ .scroll-host { height: 180px; overflow: auto; }
2545
+ </style>
2546
+
2547
+ ```
2548
+
2549
+ ### UiTour 分类独立示例
2550
+
2551
+ ```vue
2552
+ <script setup lang="ts">
2553
+ import { ref } from 'vue'
2554
+ import { UiButton, UiTour, UiTourStep } from 'sky-ui-kit'
2555
+ const message = ref('尚未操作')
2556
+ const open = ref(false)
2557
+ const tourTarget = ref()
2558
+
2559
+ </script>
2560
+ <template>
2561
+ <div class="catalog-demo affix-example-host">
2562
+ <UiButton ref="tourTarget" @click="open = true">开始引导</UiButton><UiTour v-model="open"><UiTourStep :target="tourTarget?.$el" title="数据查询" description="通过这里查询测站数据" /></UiTour>
2563
+ <p role="status">{{ message }}</p>
2564
+ </div>
2565
+ </template>
2566
+ <style scoped>
2567
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2568
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2569
+ .scroll-host { height: 180px; overflow: auto; }
2570
+ </style>
2571
+
2572
+ ```
2573
+
2574
+ ### UiAnchor 分类独立示例
2575
+
2576
+ ```vue
2577
+ <script setup lang="ts">
2578
+ import { ref } from 'vue'
2579
+ import { UiAnchor, UiAnchorLink } from 'sky-ui-kit'
2580
+ const message = ref('尚未操作')
2581
+ const open = ref(false)
2582
+
2583
+
2584
+ </script>
2585
+ <template>
2586
+ <div class="catalog-demo affix-example-host">
2587
+ <UiAnchor><UiAnchorLink href="#anchor-demo-one" title="测站信息" /><UiAnchorLink href="#anchor-demo-two" title="监测数据" /></UiAnchor><div id="anchor-demo-one" class="demo-block">测站信息</div><div id="anchor-demo-two" class="demo-block">监测数据</div>
2588
+ <p role="status">{{ message }}</p>
2589
+ </div>
2590
+ </template>
2591
+ <style scoped>
2592
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2593
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2594
+ .scroll-host { height: 180px; overflow: auto; }
2595
+ </style>
2596
+
2597
+ ```
2598
+
2599
+ ### UiSegmented 分类独立示例
2600
+
2601
+ ```vue
2602
+ <script setup lang="ts">
2603
+ import { ref } from 'vue'
2604
+ import { UiSegmented } from 'sky-ui-kit'
2605
+ const message = ref('尚未操作')
2606
+ const open = ref(false)
2607
+ const selected = ref('实况')
2608
+
2609
+ </script>
2610
+ <template>
2611
+ <div class="catalog-demo affix-example-host">
2612
+ <UiSegmented v-model="selected" :options="['实况', '预报', '历史']" /><p>当前:{{ selected }}</p>
2613
+ <p role="status">{{ message }}</p>
2614
+ </div>
2615
+ </template>
2616
+ <style scoped>
2617
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2618
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2619
+ .scroll-host { height: 180px; overflow: auto; }
2620
+ </style>
2621
+
2622
+ ```
2623
+
2624
+ ### UiSplitter 分类独立示例
2625
+
2626
+ ```vue
2627
+ <script setup lang="ts">
2628
+ import { ref } from 'vue'
2629
+ import { UiSplitter, UiSplitterPanel } from 'sky-ui-kit'
2630
+ const message = ref('尚未操作')
2631
+ const open = ref(false)
2632
+
2633
+
2634
+ </script>
2635
+ <template>
2636
+ <div class="catalog-demo affix-example-host">
2637
+ <UiSplitter style="height: 200px"><UiSplitterPanel size="30%"><div class="demo-block">拖动分隔条 · 左栏</div></UiSplitterPanel><UiSplitterPanel><div class="demo-block">主体</div></UiSplitterPanel></UiSplitter>
2638
+ <p role="status">{{ message }}</p>
2639
+ </div>
2640
+ </template>
2641
+ <style scoped>
2642
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2643
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2644
+ .scroll-host { height: 180px; overflow: auto; }
2645
+ </style>
2646
+
2647
+ ```
2648
+
2649
+ ### UiPopover 分类独立示例
2650
+
2651
+ ```vue
2652
+ <script setup lang="ts">
2653
+ import { ref } from 'vue'
2654
+ import { UiButton, UiPopover } from 'sky-ui-kit'
2655
+ const message = ref('尚未操作')
2656
+ const open = ref(false)
2657
+
2658
+
2659
+ </script>
2660
+ <template>
2661
+ <div class="catalog-demo affix-example-host">
2662
+ <UiPopover title="测站信息" content="汉口站 · 长江流域" trigger="click"><template #reference><UiButton>点击查看浮层</UiButton></template></UiPopover>
2663
+ <p role="status">{{ message }}</p>
2664
+ </div>
2665
+ </template>
2666
+ <style scoped>
2667
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2668
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2669
+ .scroll-host { height: 180px; overflow: auto; }
2670
+ </style>
2671
+
2672
+ ```
2673
+
2674
+ ### UiMessage 分类独立示例
2675
+
2676
+ ```vue
2677
+ <script setup lang="ts">
2678
+ import { ref } from 'vue'
2679
+ import { UiButton, UiMessage } from 'sky-ui-kit'
2680
+ const message = ref('尚未操作')
2681
+ const open = ref(false)
2682
+
2683
+
2684
+ </script>
2685
+ <template>
2686
+ <div class="catalog-demo affix-example-host">
2687
+ <UiButton @click="UiMessage.success('保存成功')">成功消息</UiButton><UiButton @click="UiMessage.warning('请检查数据')">警告消息</UiButton>
2688
+ <p role="status">{{ message }}</p>
2689
+ </div>
2690
+ </template>
2691
+ <style scoped>
2692
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2693
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2694
+ .scroll-host { height: 180px; overflow: auto; }
2695
+ </style>
2696
+
2697
+ ```
2698
+
2699
+ ### UiMessageBox 分类独立示例
2700
+
2701
+ ```vue
2702
+ <script setup lang="ts">
2703
+ import { ref } from 'vue'
2704
+ import { UiButton, UiMessage, UiMessageBox } from 'sky-ui-kit'
2705
+ const message = ref('尚未操作')
2706
+ const open = ref(false)
2707
+ async function confirm() { try { await UiMessageBox.confirm('确认更新测站数据?', '操作确认'); message.value = '已确认' } catch { message.value = '已取消' } }
2708
+
2709
+ </script>
2710
+ <template>
2711
+ <div class="catalog-demo affix-example-host">
2712
+ <UiButton @click="confirm">打开确认框</UiButton>
2713
+ <p role="status">{{ message }}</p>
2714
+ </div>
2715
+ </template>
2716
+ <style scoped>
2717
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2718
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2719
+ .scroll-host { height: 180px; overflow: auto; }
2720
+ </style>
2721
+
2722
+ ```
2723
+
2724
+ ### UiNotification 分类独立示例
2725
+
2726
+ ```vue
2727
+ <script setup lang="ts">
2728
+ import { ref } from 'vue'
2729
+ import { UiButton, UiNotification } from 'sky-ui-kit'
2730
+ const message = ref('尚未操作')
2731
+ const open = ref(false)
2732
+
2733
+
2734
+ </script>
2735
+ <template>
2736
+ <div class="catalog-demo affix-example-host">
2737
+ <UiButton @click="UiNotification({ title: '数据更新', message: '监测数据已刷新', type: 'success' })">显示通知</UiButton>
2738
+ <p role="status">{{ message }}</p>
2739
+ </div>
2740
+ </template>
2741
+ <style scoped>
2742
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2743
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2744
+ .scroll-host { height: 180px; overflow: auto; }
2745
+ </style>
2746
+
2747
+ ```
2748
+
2749
+ ### UiLoading 分类独立示例
2750
+
2751
+ ```vue
2752
+ <script setup lang="ts">
2753
+ import { ref } from 'vue'
2754
+ import { UiButton, UiLoading } from 'sky-ui-kit'
2755
+ const message = ref('尚未操作')
2756
+ const open = ref(false)
2757
+ const loadingTarget = ref<HTMLElement>()
2758
+ async function showLoading() { const instance = UiLoading.service({ target: loadingTarget.value, text: '正在加载' }); try { await new Promise(resolve => setTimeout(resolve, 1000)) } finally { instance.close() } }
2759
+
2760
+ </script>
2761
+ <template>
2762
+ <div class="catalog-demo affix-example-host">
2763
+ <UiButton @click="showLoading">显示加载服务</UiButton><div ref="loadingTarget" class="demo-block">加载目标区域</div>
2764
+ <p role="status">{{ message }}</p>
2765
+ </div>
2766
+ </template>
2767
+ <style scoped>
2768
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2769
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2770
+ .scroll-host { height: 180px; overflow: auto; }
2771
+ </style>
2772
+
2773
+ ```
2774
+
2775
+ ### vUiLoading 分类独立示例
2776
+
2777
+ ```vue
2778
+ <script setup lang="ts">
2779
+ import { ref } from 'vue'
2780
+ import { UiSwitch, vUiLoading } from 'sky-ui-kit'
2781
+ const message = ref('尚未操作')
2782
+ const open = ref(false)
2783
+ const loading = ref(false)
2784
+
2785
+ </script>
2786
+ <template>
2787
+ <div class="catalog-demo affix-example-host">
2788
+ <UiSwitch v-model="loading" active-text="切换加载" /><div v-ui-loading="loading" class="demo-block">数据内容</div>
2789
+ <p role="status">{{ message }}</p>
2790
+ </div>
2791
+ </template>
2792
+ <style scoped>
2793
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2794
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2795
+ .scroll-host { height: 180px; overflow: auto; }
2796
+ </style>
2797
+
2798
+ ```
2799
+
2800
+ ### vUiInfiniteScroll 分类独立示例
2801
+
2802
+ ```vue
2803
+ <script setup lang="ts">
2804
+ import { ref } from 'vue'
2805
+ import { vUiInfiniteScroll } from 'sky-ui-kit'
2806
+ const message = ref('尚未操作')
2807
+ const open = ref(false)
2808
+ const count = ref(8)
2809
+ function loadMore() { count.value = Math.min(30, count.value + 4) }
2810
+
2811
+ </script>
2812
+ <template>
2813
+ <div class="catalog-demo affix-example-host">
2814
+ <div style="height: 160px; overflow: auto"><ul v-ui-infinite-scroll="loadMore" :infinite-scroll-disabled="count >= 30"><li v-for="n in count" :key="n">测站记录 {{ n }}</li></ul></div><p>最多演示 30 条</p>
2815
+ <p role="status">{{ message }}</p>
2816
+ </div>
2817
+ </template>
2818
+ <style scoped>
2819
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2820
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2821
+ .scroll-host { height: 180px; overflow: auto; }
2822
+ </style>
2823
+
2824
+ ```
2825
+
2826
+ ### vUiPopover 分类独立示例
2827
+
2828
+ ```vue
2829
+ <script setup lang="ts">
2830
+ import { ref } from 'vue'
2831
+ import { UiButton, UiPopover, vUiPopover } from 'sky-ui-kit'
2832
+ const message = ref('尚未操作')
2833
+ const open = ref(false)
2834
+ const popoverRef = ref()
2835
+
2836
+ </script>
2837
+ <template>
2838
+ <div class="catalog-demo affix-example-host">
2839
+ <UiButton v-ui-popover="popoverRef">指令绑定浮层</UiButton><UiPopover ref="popoverRef" trigger="click" title="测站说明" content="汉口站 · 长江流域" />
2840
+ <p role="status">{{ message }}</p>
2841
+ </div>
2842
+ </template>
2843
+ <style scoped>
2844
+ .catalog-demo { padding: 20px; border: 1px solid var(--color-border, #dbe3ef); border-radius: 8px; overflow-x: auto; }
2845
+ .demo-block { min-height: 140px; display: grid; place-items: center; background: var(--color-surface, #eef5ff); padding: 16px; }
2846
+ .scroll-host { height: 180px; overflow: auto; }
2847
+ </style>
2848
+
2849
+ ```