@hz_yujin/vue-smart-table 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/README.md ADDED
@@ -0,0 +1,668 @@
1
+ # @hz_yujin/vue-smart-table
2
+
3
+ > 本包位于 monorepo `packages/smart-table`。仓库根目录见 [README](../../README.md)。
4
+
5
+ 基于 **Element Plus `el-table`** 的 Smart Table:**一份列 schema** 同时驱动表格列、查询条件与新增/编辑表单,并内置列管理抽屉、分页、骨架屏。
6
+
7
+ 与 `@hz_yujin/vue-base-table`(VXE)互补,互不影响。
8
+
9
+ ## 目录
10
+
11
+ - [安装](#安装)
12
+ - [快速开始](#快速开始)
13
+ - [设计思路](#设计思路)
14
+ - [SmartColumnSchema](#smartcolumnschema)
15
+ - [Props](#props)
16
+ - [Events](#events)
17
+ - [插槽](#插槽)
18
+ - [方法(expose)](#方法expose)
19
+ - [分页](#分页)
20
+ - [远程列表(推荐写法)](#远程列表推荐写法)
21
+ - [排序](#排序)
22
+ - [列管理与持久化](#列管理与持久化)
23
+ - [表单自定义 / 嵌套弹框](#表单自定义--嵌套弹框)
24
+ - [骨架屏与加载](#骨架屏与加载)
25
+ - [全局注册](#全局注册)
26
+ - [工具函数](#工具函数)
27
+
28
+ ---
29
+
30
+ ## 安装
31
+
32
+ ```bash
33
+ npm install @hz_yujin/vue-smart-table element-plus @element-plus/icons-vue
34
+ ```
35
+
36
+ `element-plus`、`@element-plus/icons-vue`、`vue` 为 **peerDependencies**,需由宿主项目安装。
37
+
38
+ ```ts
39
+ import { createApp } from 'vue'
40
+ import ElementPlus from 'element-plus'
41
+ import 'element-plus/dist/index.css'
42
+ import zhCn from 'element-plus/es/locale/lang/zh-cn'
43
+ import '@hz_yujin/vue-smart-table/style.css'
44
+
45
+ import { ProSmartTable } from '@hz_yujin/vue-smart-table'
46
+ // 或全局注册
47
+ import VueSmartTable from '@hz_yujin/vue-smart-table'
48
+
49
+ const app = createApp(App)
50
+ app.use(ElementPlus, { locale: zhCn })
51
+ app.use(VueSmartTable) // 可选
52
+ app.mount('#app')
53
+ ```
54
+
55
+ ---
56
+
57
+ ## 快速开始
58
+
59
+ ```vue
60
+ <script setup lang="ts">
61
+ import { ref } from 'vue'
62
+ import {
63
+ ProSmartTable,
64
+ type SmartColumnSchema,
65
+ } from '@hz_yujin/vue-smart-table'
66
+ import '@hz_yujin/vue-smart-table/style.css'
67
+
68
+ const columns = ref<SmartColumnSchema[]>([
69
+ {
70
+ id: 'name',
71
+ prop: 'name',
72
+ label: '名称',
73
+ minWidth: 160,
74
+ searchable: true,
75
+ formable: true,
76
+ required: true,
77
+ formType: 'input',
78
+ source: 'fixed',
79
+ },
80
+ {
81
+ id: 'status',
82
+ prop: 'status',
83
+ label: '状态',
84
+ minWidth: 120,
85
+ searchable: true,
86
+ formable: true,
87
+ formType: 'select',
88
+ formOptions: [
89
+ { label: '启用', value: '启用' },
90
+ { label: '禁用', value: '禁用' },
91
+ ],
92
+ source: 'fixed',
93
+ },
94
+ ])
95
+
96
+ const data = ref([
97
+ { name: '示例 A', status: '启用' },
98
+ { name: '示例 B', status: '禁用' },
99
+ ])
100
+
101
+ function onSearch(model: Record<string, unknown>) {
102
+ console.log('search', model)
103
+ }
104
+ </script>
105
+
106
+ <template>
107
+ <ProSmartTable
108
+ v-model:columns="columns"
109
+ :data="data"
110
+ persist-key="demo-smart-table"
111
+ form-title="记录"
112
+ border
113
+ stripe
114
+ @search="onSearch"
115
+ @create="(row) => data.push(row)"
116
+ @update="(row) => console.log('update', row)"
117
+ />
118
+ </template>
119
+ ```
120
+
121
+ 未在 `ProSmartTable` 上声明的属性(如 `border`、`stripe`、`height`、`row-key`)会透传给底层 `el-table`。
122
+
123
+ ---
124
+
125
+ ## 设计思路
126
+
127
+ | 区域 | 由谁驱动 |
128
+ |------|----------|
129
+ | 表格列 | `columns` 中 `visible !== false` 且有 `prop` 的项 |
130
+ | 查询区 | `searchable: true` 的列 |
131
+ | 新增/编辑表单 | `formable: true` 的列 |
132
+ | 控件类型 | `formType` + `formOptions` / `formProps` |
133
+
134
+ 业务侧通常只需维护一份 `SmartColumnSchema[]`,再监听 `search` / `create` / `update` / `page-change` 对接接口。
135
+
136
+ ### 能力一览
137
+
138
+ | 能力 | 说明 |
139
+ |------|------|
140
+ | el-table 透传 | 未声明的 attrs / 默认插槽全部落到 `el-table` |
141
+ | 查询 | 可收起/展开;默认收起展示前 3 项 |
142
+ | 新增/编辑 | 内置弹框 + 必填校验;可用插槽自定义字段 |
143
+ | 列管理 | 显隐、拖拽排序、增删列、查询/表单开关、固定列 |
144
+ | 持久化 | `persistKey` → `localStorage`;也可自行存服务端 |
145
+ | 分页 | 默认开启;`local` 前端切片 / `remote` 服务端分页 |
146
+ | 排序 | 支持本地 / 远程(`sortable: 'custom'` 或 remote 分页自动提升) |
147
+ | 骨架屏 | 默认开启;无数据时骨架,有数据刷新转圈 |
148
+ | 序号 / 多选 | `showSeq` / `showSelection` |
149
+
150
+ ---
151
+
152
+ ## SmartColumnSchema
153
+
154
+ ```ts
155
+ interface SmartColumnSchema {
156
+ /** 稳定唯一 id(拖拽 / 删除 / 持久化必填) */
157
+ id: string
158
+ /** 对应 el-table-column 的 prop / 数据字段 */
159
+ prop: string
160
+ /** 列标题 */
161
+ label: string
162
+ minWidth?: number
163
+ width?: number | string
164
+ /** 是否可排序;远程分页下 true 会自动提升为 'custom' */
165
+ sortable?: boolean | 'custom'
166
+ /** 是否作为查询条件 */
167
+ searchable?: boolean
168
+ /** 是否出现在新增/编辑表单 */
169
+ formable?: boolean
170
+ /** 表单是否必填(仅 formable 时有效) */
171
+ required?: boolean
172
+ /** 表单 / 查询控件类型 */
173
+ formType?: SmartFormControlType
174
+ /** select / radio / cascader / tree-select 等选项 */
175
+ formOptions?: SmartSelectOption[]
176
+ /** 查询占位符 */
177
+ searchPlaceholder?: string
178
+ /** 固定列 */
179
+ fixed?: boolean | 'left' | 'right'
180
+ /** 是否在表格中显示,默认 true */
181
+ visible?: boolean
182
+ /** fixed:业务预置;custom:列管理新增 */
183
+ source?: 'fixed' | 'custom'
184
+ /** 是否允许删除(fixed 默认不可删,custom 默认可删) */
185
+ deletable?: boolean
186
+ /** 透传给 el-table-column */
187
+ columnProps?: Record<string, unknown>
188
+ /** 透传给表单控件 */
189
+ formProps?: Record<string, unknown>
190
+ }
191
+ ```
192
+
193
+ ### formType 一览
194
+
195
+ | formType | 说明 |
196
+ |----------|------|
197
+ | `input` | 输入框(默认) |
198
+ | `password` | 密码 |
199
+ | `textarea` | 多行文本 |
200
+ | `number` | 数字 |
201
+ | `select` / `select-multiple` | 下拉单选 / 多选 |
202
+ | `radio` | 单选 |
203
+ | `checkbox` / `checkbox-group` | 复选 / 复选组 |
204
+ | `switch` | 开关 |
205
+ | `date` / `datetime` | 日期 / 日期时间 |
206
+ | `daterange` / `datetimerange` | 日期范围 / 日期时间范围(亦支持 `date-range` / `datetime-range` 别名) |
207
+ | `time` / `timerange` | 时间 / 时间范围(亦支持 `time-range`) |
208
+ | `month` / `year` / `week` | 月 / 年 / 周 |
209
+ | `cascader` | 级联 |
210
+ | `tree-select` | 树选择 |
211
+ | `autocomplete` | 自动完成 |
212
+ | `rate` / `slider` / `color` | 评分 / 滑块 / 颜色 |
213
+
214
+ 选项结构:
215
+
216
+ ```ts
217
+ interface SmartSelectOption {
218
+ label: string
219
+ value: string | number | boolean
220
+ disabled?: boolean
221
+ children?: SmartSelectOption[] // cascader / tree-select
222
+ }
223
+ ```
224
+
225
+ ### 列配置示例
226
+
227
+ ```ts
228
+ const columns: SmartColumnSchema[] = [
229
+ {
230
+ id: 'workshop',
231
+ prop: 'workshop',
232
+ label: '车间',
233
+ minWidth: 120,
234
+ searchable: true,
235
+ formable: true,
236
+ required: true,
237
+ formType: 'tree-select',
238
+ formOptions: [
239
+ {
240
+ label: '东区',
241
+ value: 'east',
242
+ children: [
243
+ { label: '一车间', value: '一车间' },
244
+ { label: '二车间', value: '二车间' },
245
+ ],
246
+ },
247
+ ],
248
+ formProps: { checkStrictly: false, filterable: true },
249
+ source: 'fixed',
250
+ deletable: false,
251
+ },
252
+ {
253
+ id: 'duration',
254
+ prop: 'duration',
255
+ label: '持续时长(分)',
256
+ minWidth: 120,
257
+ sortable: 'custom',
258
+ searchable: true,
259
+ formable: true,
260
+ formType: 'number',
261
+ formProps: { min: 0, max: 999, controlsPosition: 'right' },
262
+ source: 'fixed',
263
+ },
264
+ ]
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Props
270
+
271
+ | Prop | 类型 | 默认值 | 说明 |
272
+ |------|------|--------|------|
273
+ | `data` | `Record<string, unknown>[]` | `[]` | 表格数据 |
274
+ | `columns` | `SmartColumnSchema[]` | `[]` | 列 schema;支持 `v-model:columns` |
275
+ | `showToolbar` | `boolean` | `true` | 是否展示工具栏 |
276
+ | `showColumnEditor` | `boolean` | `true` | 是否展示「编辑列」按钮 |
277
+ | `showRefresh` | `boolean` | `true` | 是否展示刷新按钮 |
278
+ | `showCreate` | `boolean` | `true` | 是否展示默认「新增」按钮 |
279
+ | `showSeq` | `boolean` | `true` | 是否展示序号列 |
280
+ | `seqWidth` | `number \| string` | `60` | 序号列宽度 |
281
+ | `showSelection` | `boolean` | `false` | 是否展示多选列 |
282
+ | `showSearch` | `boolean` | `true` | 是否展示查询区 |
283
+ | `searchCollapsible` | `boolean` | `true` | 查询区是否可收起展开 |
284
+ | `searchCollapseVisibleCount` | `number` | `3` | 收起时展示的字段数 |
285
+ | `searchDefaultExpanded` | `boolean` | `false` | 查询区默认是否展开 |
286
+ | `persistKey` | `string` | — | 有值时列配置写入 `localStorage` |
287
+ | `formTitle` | `string` | `''` | 弹框标题前缀,如「告警」→「新增告警」 |
288
+ | `formWidth` | `number \| string` | `'45%'` | 弹框宽度 |
289
+ | `loading` | `boolean` | `false` | 加载中(骨架 / 转圈) |
290
+ | `skeleton` | `boolean \| ProSmartTableSkeletonConfig` | `true` | 骨架屏;`false` 关闭 |
291
+ | `pagination` | `false \| ProSmartTablePagination` | `{}` | 分页;`false` 关闭 |
292
+
293
+ ### skeleton 配置
294
+
295
+ ```ts
296
+ interface ProSmartTableSkeletonConfig {
297
+ rows?: number // 骨架行数,默认取 pageSize(上限 10),否则 5
298
+ animated?: boolean // 默认 true
299
+ onlyEmpty?: boolean // 默认 true:仅无数据时用骨架,有数据刷新仍用转圈
300
+ }
301
+ ```
302
+
303
+ ### pagination 配置
304
+
305
+ ```ts
306
+ interface ProSmartTablePagination {
307
+ currentPage?: number
308
+ pageSize?: number
309
+ total?: number
310
+ pageSizes?: number[]
311
+ layout?: string // 默认 total, sizes, prev, pager, next, jumper
312
+ background?: boolean
313
+ size?: 'large' | 'default' | 'small' // 默认 small
314
+ pagerCount?: number
315
+ /** local:前端切片;remote:data 为当前页,需传 total */
316
+ mode?: 'local' | 'remote'
317
+ }
318
+ ```
319
+
320
+ 未显式传 `mode` 时:有 `total` 走 `remote`,否则 `local`。
321
+
322
+ ---
323
+
324
+ ## Events
325
+
326
+ | 事件 | 参数 | 说明 |
327
+ |------|------|------|
328
+ | `update:columns` | `SmartColumnSchema[]` | 列变更(配合 `v-model:columns`) |
329
+ | `columns-change` | `SmartColumnSchema[]` | 列管理保存成功 |
330
+ | `columns-reset` | — | 列管理「恢复默认」;业务侧应清空服务端配置 |
331
+ | `search` | `model` | 点击查询,或重置后也会再抛一次 `search` |
332
+ | `reset` | — | 点击重置 |
333
+ | `create` | `model` | 新增弹框确认(已过必填校验) |
334
+ | `update` | `model` | 编辑弹框确认(已过必填校验) |
335
+ | `page-change` | `{ currentPage, pageSize }` | 页码 / 每页条数变化 |
336
+ | `update:pagination` | `ProSmartTablePagination` | 分页受控同步 |
337
+ | `refresh` | — | 点击刷新 |
338
+ | `sort-change` | `{ column, prop, order }` | 透传 el-table;远程排序请监听此事件 |
339
+
340
+ `reset` 后组件会清空查询模型并发出 `search`(带空条件),远程列表可统一在 `@search` 里拉数。
341
+
342
+ ---
343
+
344
+ ## 插槽
345
+
346
+ | 插槽 | 作用域 | 说明 |
347
+ |------|--------|------|
348
+ | `toolbar-left` | — | 工具栏左侧;覆盖默认「新增」时可配合 `show-create={false}` |
349
+ | `toolbar-right` | — | 工具栏右侧(刷新 / 编辑列按钮之前) |
350
+ | `col-{prop}` | el-table 单元格 scope | 自定义列渲染,如 `#col-status` |
351
+ | (默认插槽) | — | 追加额外 `el-table-column`(如操作列) |
352
+ | `form-{prop}` | `{ model, column, value, mode }` | 覆盖某个表单字段;`mode`: `create` \| `edit` |
353
+ | `form-extra` | `{ model, mode, visible }` | 写在弹框内部,用于嵌套二级弹框 |
354
+ | `pagination` | — | 完全自定义分页 |
355
+ | `skeleton` | — | 自定义骨架屏 |
356
+
357
+ ### 自定义列 + 操作列
358
+
359
+ ```vue
360
+ <ProSmartTable :data="data" :columns="columns" ...>
361
+ <template #col-status="{ row }">
362
+ <el-tag :type="row.status === '启用' ? 'success' : 'info'">
363
+ {{ row.status }}
364
+ </el-tag>
365
+ </template>
366
+
367
+ <el-table-column label="操作" width="140" fixed="right">
368
+ <template #default="{ row }">
369
+ <el-button link type="primary" @click="tableRef?.openEdit(row)">
370
+ 编辑
371
+ </el-button>
372
+ </template>
373
+ </el-table-column>
374
+ </ProSmartTable>
375
+ ```
376
+
377
+ ---
378
+
379
+ ## 方法(expose)
380
+
381
+ ```ts
382
+ interface ProSmartTableExpose {
383
+ tableRef: TableInstance | null // 底层 el-table
384
+ openColumnEditor: () => void
385
+ openCreate: () => void
386
+ openEdit: (row: Record<string, unknown>) => void
387
+ getSearchModel: () => Record<string, unknown>
388
+ getColumns: () => SmartColumnSchema[]
389
+ }
390
+ ```
391
+
392
+ ```vue
393
+ <script setup lang="ts">
394
+ import { ref } from 'vue'
395
+ import type { ProSmartTableExpose } from '@hz_yujin/vue-smart-table'
396
+
397
+ const tableRef = ref<ProSmartTableExpose>()
398
+
399
+ function onEdit(row: Record<string, unknown>) {
400
+ tableRef.value?.openEdit(row)
401
+ }
402
+ </script>
403
+
404
+ <template>
405
+ <ProSmartTable ref="tableRef" ... />
406
+ </template>
407
+ ```
408
+
409
+ ---
410
+
411
+ ## 分页
412
+
413
+ ### 本地分页(默认)
414
+
415
+ 不传 `total` 时,组件对 `data` 做前端切片:
416
+
417
+ ```vue
418
+ <ProSmartTable :data="allRows" :columns="columns" />
419
+ ```
420
+
421
+ ### 远程分页
422
+
423
+ ```vue
424
+ <script setup lang="ts">
425
+ import { ref } from 'vue'
426
+ import type { ProSmartTablePagination } from '@hz_yujin/vue-smart-table'
427
+
428
+ const pagination = ref<ProSmartTablePagination>({
429
+ currentPage: 1,
430
+ pageSize: 10,
431
+ total: 0,
432
+ mode: 'remote',
433
+ pageSizes: [10, 20, 50],
434
+ })
435
+
436
+ async function onPageChange({ currentPage, pageSize }) {
437
+ pagination.value = { ...pagination.value, currentPage, pageSize }
438
+ await fetchList()
439
+ }
440
+ </script>
441
+
442
+ <template>
443
+ <ProSmartTable
444
+ :data="list"
445
+ :columns="columns"
446
+ v-model:pagination="pagination"
447
+ :loading="loading"
448
+ @page-change="onPageChange"
449
+ @search="onSearch"
450
+ />
451
+ </template>
452
+ ```
453
+
454
+ 关闭分页:`:pagination="false"`。
455
+
456
+ ---
457
+
458
+ ## 远程列表(推荐写法)
459
+
460
+ 与 playground `SmartTableDemo` 一致的模式:
461
+
462
+ 1. `v-model:pagination` + `mode: 'remote'`
463
+ 2. `@search` / `@reset`(重置也会走 `search`)回到第 1 页并拉数
464
+ 3. `@page-change` 更新页码后拉数
465
+ 4. `@create` / `@update` 调接口后刷新
466
+ 5. 自定义「新增」时设 `:show-create="false"`,用 `#toolbar-left` + `openCreate()`
467
+
468
+ ```ts
469
+ const lastSearchModel = ref<Record<string, unknown>>({})
470
+ const pagination = ref<ProSmartTablePagination>({
471
+ currentPage: 1,
472
+ pageSize: 10,
473
+ total: 0,
474
+ mode: 'remote',
475
+ })
476
+
477
+ async function onSearch(model: Record<string, unknown>) {
478
+ lastSearchModel.value = { ...model }
479
+ pagination.value = { ...pagination.value, currentPage: 1 }
480
+ await fetchList()
481
+ }
482
+
483
+ async function onPageChange(payload: { currentPage: number; pageSize: number }) {
484
+ pagination.value = { ...pagination.value, ...payload }
485
+ await fetchList()
486
+ }
487
+
488
+ async function fetchList() {
489
+ loading.value = true
490
+ try {
491
+ const res = await api.list({
492
+ ...lastSearchModel.value,
493
+ currentPage: pagination.value.currentPage,
494
+ pageSize: pagination.value.pageSize,
495
+ })
496
+ tableData.value = res.list
497
+ pagination.value = {
498
+ ...pagination.value,
499
+ total: res.total,
500
+ currentPage: res.currentPage,
501
+ pageSize: res.pageSize,
502
+ }
503
+ } finally {
504
+ loading.value = false
505
+ }
506
+ }
507
+ ```
508
+
509
+ 查询 / 重置时组件会 **silent** 把页码置 1(不额外抛 `page-change`),避免连打两次接口;业务只需在 `@search` 里请求即可。
510
+
511
+ ---
512
+
513
+ ## 排序
514
+
515
+ - **本地**:`sortable: true`,el-table 本地排序即可。
516
+ - **远程**:设 `sortable: 'custom'`,或分页为 `remote` 时 `sortable: true` 会自动提升为 `custom`,监听 `@sort-change`:
517
+
518
+ ```ts
519
+ function onSortChange(payload: {
520
+ prop: string
521
+ order: 'ascending' | 'descending' | null
522
+ }) {
523
+ sortState.value = !payload.prop || !payload.order
524
+ ? {}
525
+ : {
526
+ sortBy: payload.prop,
527
+ sortOrder: payload.order === 'ascending' ? 'asc' : 'desc',
528
+ }
529
+ pagination.value = { ...pagination.value, currentPage: 1 }
530
+ void fetchList()
531
+ }
532
+ ```
533
+
534
+ > `sort-change` 由组件显式转发,不会再落入 attrs 重复绑定到 `el-table`。
535
+
536
+ ---
537
+
538
+ ## 列管理与持久化
539
+
540
+ 右上角「编辑列」打开 `ProColumnEditor`,可:
541
+
542
+ - 显隐、拖拽排序
543
+ - 改 field / 标题 / 最小宽
544
+ - 开关:排序 / 查询 / 表单
545
+ - 改表单类型、左右固定
546
+ - 新增自定义列、删除(受 `deletable` 控制)
547
+ - 「恢复默认」
548
+
549
+ ### 本地持久化
550
+
551
+ ```vue
552
+ <ProSmartTable persist-key="alarm-list" v-model:columns="columns" ... />
553
+ ```
554
+
555
+ 有 `persistKey` 时,保存列配置写入 `localStorage`;恢复默认会清除该 key。
556
+
557
+ ### 服务端持久化
558
+
559
+ ```vue
560
+ <ProSmartTable
561
+ v-model:columns="columns"
562
+ @columns-change="(cols) => saveTableColumns(cols)"
563
+ @columns-reset="() => saveTableColumns([])"
564
+ />
565
+ ```
566
+
567
+ 「恢复默认」后组件会应用内置默认列(首次传入的 `columns` 快照)并发出 `columns-reset`。业务侧建议把服务端配置存成 `[]`,下次读取空数组时回落默认列。
568
+
569
+ ---
570
+
571
+ ## 表单自定义 / 嵌套弹框
572
+
573
+ ```vue
574
+ <ProSmartTable ...>
575
+ <!-- 覆盖某个表单字段 -->
576
+ <template #form-deviceName="{ model }">
577
+ <el-input :model-value="model.deviceName" readonly placeholder="请选择设备">
578
+ <template #append>
579
+ <el-button @click="pickerVisible = true">选择</el-button>
580
+ </template>
581
+ </el-input>
582
+ </template>
583
+
584
+ <!-- 二级弹框必须写在 form-extra 内(父级弹框插槽) -->
585
+ <template #form-extra="{ model }">
586
+ <ProDialog v-model="pickerVisible" nested title="选择设备" show-footer>
587
+ <!-- 选中后写回 model.deviceName / model.deviceCode -->
588
+ </ProDialog>
589
+ </template>
590
+ </ProSmartTable>
591
+ ```
592
+
593
+ - `#form-{prop}`:`model` / `column` / `value` / `mode`(`create` | `edit`)
594
+ - `#form-extra`:`model` / `mode` / `visible`
595
+
596
+ 必填:`required: true` 时,`null` / `undefined` / 空串 / 纯空格 / 空数组视为未填;`0` / `false` 合法。
597
+
598
+ ---
599
+
600
+ ## 骨架屏与加载
601
+
602
+ | 场景 | 表现 |
603
+ |------|------|
604
+ | `loading=true` 且无数据 | 默认骨架屏(`onlyEmpty: true`) |
605
+ | `loading=true` 且已有数据 | 表格上转圈 |
606
+ | 点击刷新 | 先发 `refresh`,并等到外部 `loading` 结束再停动画 |
607
+
608
+ ```vue
609
+ <!-- 关闭骨架 -->
610
+ <ProSmartTable :skeleton="false" :loading="loading" />
611
+
612
+ <!-- 自定义 -->
613
+ <ProSmartTable :skeleton="{ rows: 8, animated: true, onlyEmpty: true }" />
614
+ ```
615
+
616
+ ---
617
+
618
+ ## 全局注册
619
+
620
+ ```ts
621
+ import VueSmartTable from '@hz_yujin/vue-smart-table'
622
+ app.use(VueSmartTable)
623
+ ```
624
+
625
+ 注册后可直接使用 `<ProSmartTable>` / `<ProColumnEditor>`。
626
+
627
+ 也可按需引入:
628
+
629
+ ```ts
630
+ import {
631
+ ProSmartTable,
632
+ ProColumnEditor,
633
+ SMART_FORM_CONTROL_OPTIONS,
634
+ createColumnId,
635
+ normalizeColumn,
636
+ cloneColumns,
637
+ createEmptyColumn,
638
+ resolveFormControlType,
639
+ } from '@hz_yujin/vue-smart-table'
640
+ ```
641
+
642
+ ---
643
+
644
+ ## 工具函数
645
+
646
+ | 函数 | 说明 |
647
+ |------|------|
648
+ | `createColumnId(prefix?)` | 生成列 id |
649
+ | `normalizeColumn(col)` | 补齐默认值(visible / formType / deletable 等) |
650
+ | `cloneColumns(cols)` | 深拷贝并 normalize |
651
+ | `createEmptyColumn(partial?)` | 新建 custom 列 |
652
+ | `resolveFormControlType(type)` | 统一别名(如 `date-range` → `daterange`) |
653
+ | `SMART_FORM_CONTROL_OPTIONS` | 列管理里表单类型下拉选项 |
654
+
655
+ ---
656
+
657
+ ## 本地开发
658
+
659
+ ```bash
660
+ # 在 monorepo 根目录
661
+ pnpm --filter @hz_yujin/vue-smart-table build
662
+ # playground 演示页:SmartTableDemo
663
+ pnpm --filter playground dev
664
+ ```
665
+
666
+ ## License
667
+
668
+ MIT
package/dist/index.css ADDED
@@ -0,0 +1,2 @@
1
+ .pro-smart-table{--pro-smart-table-bg:#fff;--pro-smart-table-border:#ebeef5;--pro-smart-table-radius:8px;--pro-smart-table-padding:16px;--pro-smart-table-padding-mobile:12px;--pro-smart-table-font-size:12px;--pro-smart-table-row-height:36.6px;--pro-smart-table-header-bg:#f8f9fc;--pro-smart-table-header-color:#091e40a8;--pro-smart-table-text-color:#091e40;flex-direction:column;display:flex}.pro-smart-table__body{background:var(--pro-smart-table-bg);border:1px solid var(--pro-smart-table-border);border-radius:var(--pro-smart-table-radius);padding:0 var(--pro-smart-table-padding) var(--pro-smart-table-padding)}.pro-smart-search{background:var(--pro-smart-table-bg);border:1px solid var(--pro-smart-table-border);border-radius:var(--pro-smart-table-radius);margin-bottom:16px;padding:16px 20px 20px}.pro-smart-search__form .el-form-item{margin-bottom:0;margin-right:0}.pro-smart-search__fields{flex-wrap:wrap;align-items:flex-end;gap:12px 16px;width:100%;display:flex}.pro-smart-search__item{flex:none;max-width:100%}.pro-smart-search__actions{flex:none;align-items:center;gap:8px;margin-left:auto;display:flex}.pro-smart-search__toggle-icon{margin-left:4px}@media (width<=768px){.pro-smart-table__body{padding:var(--pro-smart-table-padding-mobile)}.pro-smart-search{padding:12px 14px 16px}.pro-smart-search__fields{gap:10px 12px}.pro-smart-search__item{flex:100%}.pro-smart-search__item .el-input,.pro-smart-search__item .el-select,.pro-smart-search__item .el-date-editor{width:100%!important}.pro-smart-search__actions{justify-content:flex-start;width:100%;margin-left:0}}.pro-smart-table__toolbar{justify-content:space-between;align-items:center;gap:12px;padding:12px 0 8px;display:flex}.pro-smart-table__toolbar-left{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.pro-smart-table__toolbar-right{flex-shrink:0;align-items:center;gap:4px;margin-left:auto;display:inline-flex!important}.pro-smart-table__toolbar-right .el-button+.el-button{margin-left:0}.pro-smart-table__icon-btn.el-button.is-circle{color:#606266;width:28px;min-width:28px;height:28px;min-height:28px;padding:0;font-size:14px;background:#fff!important;border:1px solid #dcdfe6!important;display:inline-flex!important}.pro-smart-table__icon-btn.el-button.is-circle:hover,.pro-smart-table__icon-btn.el-button.is-circle:focus{color:var(--el-color-primary,#409eff)!important;border-color:var(--el-color-primary-light-5,#a0cfff)!important;background:#fff!important}.pro-smart-table__icon-btn.el-button.is-circle.is-disabled{opacity:.65}.pro-smart-table__refresh-spin{animation:.8s linear infinite pro-smart-table-refresh-spin}@keyframes pro-smart-table-refresh-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pro-smart-table__skeleton{width:100%;min-height:120px}.pro-smart-table__table-wrap{min-height:120px;position:relative}.pro-smart-table-skeleton{width:100%;padding:8px 0 4px}.pro-smart-table-skeleton__header,.pro-smart-table-skeleton__row{align-items:center;gap:16px;padding:12px 8px;display:flex}.pro-smart-table-skeleton__header{border-bottom:1px solid var(--el-border-color-lighter,#ebeef5);margin-bottom:4px}.pro-smart-table-skeleton__row+.pro-smart-table-skeleton__row{border-top:1px solid var(--el-border-color-extra-light,#f2f6fc)}.pro-smart-table-skeleton__cell{flex:1;height:14px}.pro-smart-table__table{width:100%;font-size:var(--pro-smart-table-font-size);color:var(--pro-smart-table-text-color);--el-table-header-bg-color:var(--pro-smart-table-header-bg);--el-table-header-text-color:var(--pro-smart-table-header-color);--el-table-text-color:var(--pro-smart-table-text-color);--el-table-row-hover-bg-color:#f5f7fa;--el-table-border-color:#ebeef5}.pro-smart-table__table .el-table__header-wrapper th.el-table__cell,.pro-smart-table__table .el-table__fixed-header-wrapper th.el-table__cell{height:var(--pro-smart-table-row-height);background-color:var(--pro-smart-table-header-bg);color:var(--pro-smart-table-header-color);font-size:var(--pro-smart-table-font-size);padding:0;font-weight:500}.pro-smart-table__table .el-table__body td.el-table__cell,.pro-smart-table__table .el-table__fixed-body-wrapper td.el-table__cell{height:var(--pro-smart-table-row-height);color:var(--pro-smart-table-text-color);font-size:var(--pro-smart-table-font-size);padding:0}.pro-smart-table__table .el-table__cell .cell{line-height:var(--pro-smart-table-row-height);text-overflow:ellipsis;white-space:nowrap;word-break:keep-all;padding:0 12px;overflow:hidden}.pro-smart-table__pagination{justify-content:flex-end;width:100%;padding:12px 0 0;display:flex}.pro-column-editor__toolbar{justify-content:flex-end;align-items:center;margin-bottom:12px;display:flex}.pro-column-editor__footer-left{align-items:center;gap:8px;display:flex}.pro-column-editor__table-wrap{border:1px solid #ebeef5;border-radius:6px;overflow:auto}.pro-column-editor__table{min-width:780px;font-size:13px}.pro-column-editor__row{background:#fff;border-bottom:1px solid #ebeef5;grid-template-columns:36px 36px 64px 110px 110px 80px 52px 52px 52px 52px 100px 88px 36px;align-items:center;gap:4px;min-height:36px;padding:4px 6px;transition:background-color .15s,opacity .15s;display:grid}.pro-column-editor__row.is-head{z-index:1;color:#091e40a8;background:#f8f9fc;border-bottom:1px solid #e4e7ed;min-height:32px;padding:6px;font-weight:500;position:sticky;top:0}.pro-column-editor__row>.is-check,.pro-column-editor__row>.is-drag,.pro-column-editor__row>.is-action{justify-content:center;align-items:center;display:flex}.pro-column-editor__rank{color:#909399;cursor:grab;-webkit-user-select:none;user-select:none;touch-action:none;border-radius:4px;justify-content:center;align-items:center;width:24px;height:24px;display:inline-flex}.pro-column-editor__rank:hover{color:#606266;background:#f0f2f5}.pro-column-editor__row.is-dragging{opacity:.55;background:#ecf5ff}.pro-column-editor__row.is-inactive{opacity:.55;background:#fafafa}.pro-column-editor__row.is-inactive .pro-column-editor__rank,.pro-column-editor__rank.is-disabled{cursor:not-allowed;color:#c0c4cc}.pro-column-editor__row.is-inactive .pro-column-editor__rank:hover,.pro-column-editor__rank.is-disabled:hover{color:#c0c4cc;background:0 0}.pro-column-editor__row.is-dragging .pro-column-editor__rank{cursor:grabbing;color:#409eff}body.pro-column-editor--dragging,body.pro-column-editor--dragging *{cursor:grabbing!important;-webkit-user-select:none!important;user-select:none!important}.pro-column-editor__num{width:80px}.pro-column-editor__row .el-input__wrapper{min-height:24px;padding-top:1px;padding-bottom:1px}.pro-column-editor__row .el-input__inner{height:24px;line-height:24px}.pro-column-editor__row .el-input-number{line-height:24px}.pro-column-editor__row .el-input-number .el-input__wrapper{padding-left:8px;padding-right:8px}.pro-column-editor__row .el-select .el-select__wrapper{min-height:24px;padding-top:0;padding-bottom:0}.pro-column-editor__row .el-tag{height:20px;padding:0 6px;line-height:18px}.pro-column-editor__row .el-button+.el-button{margin-left:0}.pro-column-editor__row .el-button-group .el-button{height:24px;padding:4px 8px}.pro-column-editor__row .el-switch{height:20px;line-height:20px}.pro-column-editor__row .el-switch__core{min-width:32px;height:16px}.pro-column-editor__row .el-switch__core .el-switch__action{width:12px;height:12px}.pro-column-editor__row .el-switch.is-checked .el-switch__core .el-switch__action{left:calc(100% - 13px)}.pro-column-editor__footer{justify-content:space-between;align-items:center;gap:12px;width:100%;display:flex}.pro-column-editor__footer-right{align-items:center;gap:12px;margin-left:auto;display:flex}
2
+ /*$vite$:1*/
@@ -0,0 +1,2 @@
1
+ export * from './src/index'
2
+ export { default } from './src/index'