@kine-design/crud 0.0.1-beta.14 → 0.0.1-beta.16

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.
@@ -67,7 +67,19 @@ export default defineComponent({
67
67
  case 'date': return formatDate(val);
68
68
  case 'datetime': return formatDateTime(val);
69
69
  case 'image': {
70
- if (!val) return '';
70
+ if (!val) {
71
+ return (
72
+ <div class="k-image" style={{ width: '40px', height: '40px' }}>
73
+ <div class="k-image-placeholder k-image-empty">
74
+ <svg viewBox="0 0 24 24" fill="none" class="k-image-placeholder-icon">
75
+ <rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="1.5" />
76
+ <circle cx="8.5" cy="8.5" r="1.5" fill="currentColor" opacity="0.5" />
77
+ <path d="M3 15l5-5 4 4 3-3 6 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
78
+ </svg>
79
+ </div>
80
+ </div>
81
+ );
82
+ }
71
83
  const raw = String(val);
72
84
  const resolver = props.config.imageResolver;
73
85
  const src = resolver ? resolver(raw) : raw;
@@ -34,3 +34,31 @@
34
34
  width: 100%;
35
35
  }
36
36
 
37
+ /* ===== 列表图片 ===== */
38
+ .k-crud-page .k-image-placeholder {
39
+ min-width: unset;
40
+ min-height: unset;
41
+ }
42
+
43
+ .k-image-empty {
44
+ color: var(--kine-color-text-muted);
45
+ opacity: 0.4;
46
+ }
47
+
48
+ /* ================================================================
49
+ 移动端(<768px)
50
+ ================================================================ */
51
+
52
+ @media (max-width: 767px) {
53
+ .k-crud-page {
54
+ padding: var(--kine-spacing-4);
55
+ gap: var(--kine-spacing-4);
56
+ }
57
+
58
+ /* 筛选项自动换行,撑满宽度 */
59
+ .k-crud-page-filter-item {
60
+ min-width: 0;
61
+ flex: 1 1 100%;
62
+ }
63
+ }
64
+
@@ -2,16 +2,14 @@
2
2
  * @description KEditableTable — 可编辑行表格组件
3
3
  * @author 阿怪
4
4
  * @date 2026/3/22
5
- * @version v0.0.1
5
+ * @version v0.1.0
6
6
  *
7
7
  * 江湖的业务千篇一律,复杂的代码好几百行。
8
8
  *
9
- * 支持行内编辑、添加行、删除行、汇总行。
10
- * 设计参考 AIDesigner EditableDataTable 规范:
11
- * - 正常行:纯展示
12
- * - 编辑行:左侧靛蓝竖线,cell 变为 input
13
- * - 删除确认行:红色高亮 + 内联确认
14
- * - 新增行:底部空行带搜索输入
9
+ * 所有行永远可编辑,不区分查看/编辑态。
10
+ * - 每个 cell 直接渲染为 input
11
+ * - 删除:内联确认
12
+ * - 新增:底部按钮
15
13
  * - 汇总行:底部加粗合计
16
14
  */
17
15
  import { defineComponent, ref, type PropType } from 'vue';
@@ -31,7 +29,7 @@ export interface EditableColumn {
31
29
  label: string;
32
30
  /** 列宽 */
33
31
  width?: string;
34
- /** 编辑态下的输入类型 */
32
+ /** 输入类型 */
35
33
  editType?: 'text' | 'number' | 'select' | 'search' | 'readonly';
36
34
  /** select 类型的选项 */
37
35
  options?: Array<{ label: string; value: string | number }>;
@@ -78,43 +76,22 @@ export default defineComponent({
78
76
  /** 币种前缀(用于金额汇总展示) */
79
77
  currencyPrefix: { type: String, default: '' },
80
78
  },
81
- emits: ['update:modelValue', 'add', 'edit', 'save', 'cancel', 'remove'],
79
+ emits: ['update:modelValue', 'add', 'remove'],
82
80
  setup(props, { emit, slots }) {
83
- const editingId = ref<string | number | null>(null);
84
81
  const deletingId = ref<string | number | null>(null);
85
- const editingData = ref<Record<string, unknown>>({});
86
82
 
87
- // ── 行操作 ────────────────────────────────────
83
+ // ── 更新某行某字段 ──────────────────────────────
88
84
 
89
- const startEdit = (row: EditableTableRow) => {
90
- editingId.value = row._id;
91
- editingData.value = { ...row };
92
- deletingId.value = null;
93
- emit('edit', row);
94
- };
95
-
96
- const saveEdit = () => {
97
- if (editingId.value === null) return;
85
+ const updateCell = (rowIdx: number, param: string, value: unknown) => {
98
86
  const rows = [...props.modelValue];
99
- const idx = rows.findIndex(r => r._id === editingId.value);
100
- if (idx >= 0) {
101
- rows[idx] = { ...editingData.value } as EditableTableRow;
102
- emit('update:modelValue', rows);
103
- emit('save', rows[idx]);
104
- }
105
- editingId.value = null;
106
- editingData.value = {};
87
+ rows[rowIdx] = { ...rows[rowIdx], [param]: value };
88
+ emit('update:modelValue', rows);
107
89
  };
108
90
 
109
- const cancelEdit = () => {
110
- editingId.value = null;
111
- editingData.value = {};
112
- emit('cancel');
113
- };
91
+ // ── 删除 ────────────────────────────────────────
114
92
 
115
93
  const startDelete = (row: EditableTableRow) => {
116
94
  deletingId.value = row._id;
117
- editingId.value = null;
118
95
  };
119
96
 
120
97
  const confirmDelete = () => {
@@ -145,37 +122,30 @@ export default defineComponent({
145
122
  return sum.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
146
123
  };
147
124
 
148
- // ── 渲染 ──────────────────────────────────────
125
+ // ── 渲染单元格(永远是输入框) ──────────────────
126
+
127
+ const renderCell = (col: EditableColumn, row: EditableTableRow, rowIdx: number) => {
128
+ const editSlot = slots[`edit-${col.param}`];
129
+ if (editSlot) return editSlot({ row, value: row[col.param], update: (v: unknown) => updateCell(rowIdx, col.param, v) });
149
130
 
150
- /** 渲染普通单元格 */
151
- const renderCell = (col: EditableColumn, row: EditableTableRow) => {
152
131
  const customSlot = slots[`column-${col.param}`];
153
132
  if (customSlot) return customSlot({ row, value: row[col.param] });
154
133
 
155
- const val = row[col.param];
156
- return val != null ? String(val) : '';
157
- };
158
-
159
- /** 渲染编辑态单元格 */
160
- const renderEditCell = (col: EditableColumn) => {
161
- const editSlot = slots[`edit-${col.param}`];
162
- if (editSlot) return editSlot({ data: editingData.value });
163
-
164
134
  const type = col.editType ?? 'text';
165
135
 
166
136
  if (type === 'readonly') {
167
- return <span class="k-et-cell-readonly">{editingData.value[col.param] as string ?? ''}</span>;
137
+ return <span class="k-et-cell-readonly">{row[col.param] as string ?? ''}</span>;
168
138
  }
169
139
 
170
140
  if (type === 'select' && col.options) {
171
141
  return (
172
142
  <KSelect
173
143
  class="k-et-select-wrap"
174
- modelValue={editingData.value[col.param]}
144
+ modelValue={row[col.param]}
175
145
  options={col.options}
176
146
  optionParam="label"
177
147
  valueParam="value"
178
- onUpdate:modelValue={(v: unknown) => { editingData.value[col.param] = v; }}
148
+ onUpdate:modelValue={(v: unknown) => updateCell(rowIdx, col.param, v)}
179
149
  />
180
150
  );
181
151
  }
@@ -184,8 +154,8 @@ export default defineComponent({
184
154
  <KInput
185
155
  class="k-et-input-wrap"
186
156
  type={type === 'number' ? 'number' : 'text'}
187
- modelValue={editingData.value[col.param] as string | number}
188
- onUpdate:modelValue={(v: string) => { editingData.value[col.param] = v; }}
157
+ modelValue={row[col.param] as string | number}
158
+ onUpdate:modelValue={(v: string) => updateCell(rowIdx, col.param, v)}
189
159
  />
190
160
  );
191
161
  };
@@ -233,58 +203,34 @@ export default defineComponent({
233
203
 
234
204
  <tbody class="k-et-tbody">
235
205
  {rows.map((row, i) => {
236
- const isEditing = editingId.value === row._id;
237
206
  const isDeleting = deletingId.value === row._id;
238
207
 
239
- // 删除确认行
240
208
  if (isDeleting) {
241
209
  return (
242
210
  <tr key={row._id} class="k-et-tr k-et-tr--deleting">
243
211
  {props.showIndex && <td class="k-et-td k-et-td-index">{String(i + 1).padStart(2, '0')}</td>}
244
212
  <td class="k-et-td" colspan={cols.length + (props.showActions ? 1 : 0)}>
245
213
  <span class="k-et-delete-confirm">
246
- <s class="k-et-deleted-text">{renderCell(cols[0], row)}</s>
247
- <span class="k-et-delete-ask">Are you sure you want to delete this line?</span>
248
- <KButton type="danger" size="small" text="Delete" onClick={confirmDelete} />
249
- <KButton plain size="small" text="Cancel" onClick={cancelDelete} />
214
+ <s class="k-et-deleted-text">{row[cols[0].param] as string ?? ''}</s>
215
+ <span class="k-et-delete-ask">确定删除这一行?</span>
216
+ <KButton type="danger" size="small" text="删除" onClick={confirmDelete} />
217
+ <KButton plain size="small" text="取消" onClick={cancelDelete} />
250
218
  </span>
251
219
  </td>
252
220
  </tr>
253
221
  );
254
222
  }
255
223
 
256
- // 编辑行
257
- if (isEditing) {
258
- return (
259
- <tr key={row._id} class="k-et-tr k-et-tr--editing">
260
- {props.showIndex && <td class="k-et-td k-et-td-index">{String(i + 1).padStart(2, '0')}</td>}
261
- {cols.map(col => (
262
- <td key={col.param} class={['k-et-td', col.align && `k-et-align-${col.align}`]}>
263
- {renderEditCell(col)}
264
- </td>
265
- ))}
266
- {props.showActions && (
267
- <td class="k-et-td k-et-td-actions">
268
- <KButton plain size="small" text="✓" onClick={saveEdit} />
269
- <KButton plain size="small" text="×" onClick={cancelEdit} />
270
- </td>
271
- )}
272
- </tr>
273
- );
274
- }
275
-
276
- // 正常行
277
224
  return (
278
- <tr key={row._id} class="k-et-tr">
225
+ <tr key={row._id} class="k-et-tr k-et-tr--editing">
279
226
  {props.showIndex && <td class="k-et-td k-et-td-index">{String(i + 1).padStart(2, '0')}</td>}
280
227
  {cols.map(col => (
281
228
  <td key={col.param} class={['k-et-td', col.align && `k-et-align-${col.align}`]}>
282
- {renderCell(col, row)}
229
+ {renderCell(col, row, i)}
283
230
  </td>
284
231
  ))}
285
232
  {props.showActions && (
286
233
  <td class="k-et-td k-et-td-actions">
287
- <KButton plain size="small" text="✎" onClick={() => startEdit(row)} />
288
234
  <KButton plain size="small" text="×" onClick={() => startDelete(row)} />
289
235
  </td>
290
236
  )}
@@ -585,3 +585,45 @@
585
585
  color: var(--kine-color-text-primary, #e5e5e5);
586
586
  }
587
587
 
588
+ /* ================================================================
589
+ 移动端(<768px)
590
+ ================================================================ */
591
+
592
+ @media (max-width: 767px) {
593
+ .k-form-page {
594
+ padding: 0 0 var(--kine-spacing-6);
595
+ }
596
+
597
+ /* 多列网格在移动端强制单列 */
598
+ .k-fp-grid--2,
599
+ .k-fp-grid--3 {
600
+ grid-template-columns: 1fr;
601
+ }
602
+
603
+ /* 跨列字段在单列模式下不需要 span */
604
+ .k-fp-field[style*="grid-column"] {
605
+ grid-column: span 1 !important;
606
+ }
607
+
608
+ .k-form-card-body {
609
+ padding: var(--kine-spacing-6);
610
+ }
611
+
612
+ .k-form-card-header {
613
+ padding: var(--kine-spacing-6) var(--kine-spacing-6);
614
+ }
615
+
616
+ .k-fp-header {
617
+ margin-bottom: var(--kine-spacing-6);
618
+ }
619
+
620
+ .k-sticky-action-bar {
621
+ padding: var(--kine-spacing-4) var(--kine-spacing-6);
622
+ margin: 0 calc(-1 * var(--kine-spacing-6)) calc(-1 * var(--kine-spacing-6));
623
+ }
624
+
625
+ .k-approval-dialog {
626
+ width: calc(100vw - 32px);
627
+ }
628
+ }
629
+
@@ -6,13 +6,21 @@
6
6
  *
7
7
  * 江湖的业务千篇一律,复杂的代码好几百行。
8
8
  */
9
- import { defineComponent } from 'vue';
9
+ import { defineComponent, inject } from 'vue';
10
+ import { LAYOUT_TOGGLE_DRAWER_KEY } from './KLayout';
10
11
 
11
12
  export default defineComponent({
12
13
  name: 'KHeader',
13
14
  setup(_, ctx) {
15
+ const toggleDrawer = inject(LAYOUT_TOGGLE_DRAWER_KEY, () => {});
16
+
14
17
  return () => (
15
18
  <header class="k-crud-header">
19
+ {/* 汉堡按钮(移动端可见,桌面端 CSS 隐藏) */}
20
+ <button class="k-crud-header-hamburger" onClick={toggleDrawer}>
21
+ <span class="k-crud-header-hamburger-icon" />
22
+ </button>
23
+
16
24
  {/* 左侧/默认内容区(面包屑等) */}
17
25
  <div class="k-crud-header-main">
18
26
  {ctx.slots.default?.()}
@@ -13,6 +13,8 @@ export const LAYOUT_COLLAPSED_KEY: InjectionKey<Ref<boolean>> = Symbol('kCrudLay
13
13
  export const LAYOUT_TOGGLE_KEY: InjectionKey<() => void> = Symbol('kCrudLayoutToggle');
14
14
  export const LAYOUT_SIDER_WIDTH_KEY: InjectionKey<string> = Symbol('kCrudLayoutSiderWidth');
15
15
  export const LAYOUT_COLLAPSED_WIDTH_KEY: InjectionKey<string> = Symbol('kCrudLayoutCollapsedWidth');
16
+ export const LAYOUT_DRAWER_OPEN_KEY: InjectionKey<Ref<boolean>> = Symbol('kCrudLayoutDrawerOpen');
17
+ export const LAYOUT_TOGGLE_DRAWER_KEY: InjectionKey<() => void> = Symbol('kCrudLayoutToggleDrawer');
16
18
 
17
19
  export default defineComponent({
18
20
  name: 'KLayout',
@@ -54,14 +56,25 @@ export default defineComponent({
54
56
  ctx.emit('update:collapsed', next);
55
57
  };
56
58
 
59
+ // 移动端抽屉开关状态
60
+ const drawerOpen = ref(false);
61
+ const toggleDrawer = () => { drawerOpen.value = !drawerOpen.value; };
62
+
57
63
  // 向子组件提供折叠状态、切换方法和宽度配置
58
64
  provide(LAYOUT_COLLAPSED_KEY, innerCollapsed);
59
65
  provide(LAYOUT_TOGGLE_KEY, toggleCollapsed);
60
66
  provide(LAYOUT_SIDER_WIDTH_KEY, props.siderWidth);
61
67
  provide(LAYOUT_COLLAPSED_WIDTH_KEY, props.collapsedWidth);
68
+ provide(LAYOUT_DRAWER_OPEN_KEY, drawerOpen);
69
+ provide(LAYOUT_TOGGLE_DRAWER_KEY, toggleDrawer);
62
70
 
63
71
  return () => (
64
72
  <div class="k-crud-layout">
73
+ {/* 移动端遮罩层,点击关闭抽屉 */}
74
+ <div
75
+ class={['k-crud-sider-overlay', drawerOpen.value && 'k-crud-sider-overlay--visible']}
76
+ onClick={toggleDrawer}
77
+ />
65
78
  {ctx.slots.default?.()}
66
79
  </div>
67
80
  );
@@ -6,12 +6,13 @@
6
6
  *
7
7
  * 江湖的业务千篇一律,复杂的代码好几百行。
8
8
  */
9
- import { computed, defineComponent, inject } from 'vue';
9
+ import { computed, defineComponent, inject, ref } from 'vue';
10
10
  import {
11
11
  LAYOUT_COLLAPSED_KEY,
12
12
  LAYOUT_TOGGLE_KEY,
13
13
  LAYOUT_SIDER_WIDTH_KEY,
14
14
  LAYOUT_COLLAPSED_WIDTH_KEY,
15
+ LAYOUT_DRAWER_OPEN_KEY,
15
16
  } from './KLayout';
16
17
 
17
18
  export default defineComponent({
@@ -20,6 +21,7 @@ export default defineComponent({
20
21
  // 从 KLayout 注入折叠状态、切换方法和宽度配置
21
22
  const collapsed = inject(LAYOUT_COLLAPSED_KEY);
22
23
  const toggle = inject(LAYOUT_TOGGLE_KEY);
24
+ const drawerOpen = inject(LAYOUT_DRAWER_OPEN_KEY, ref(false));
23
25
  const siderWidth = inject(LAYOUT_SIDER_WIDTH_KEY, '240px');
24
26
  const collapsedWidth = inject(LAYOUT_COLLAPSED_WIDTH_KEY, '64px');
25
27
 
@@ -52,6 +54,7 @@ export default defineComponent({
52
54
  class={[
53
55
  'k-crud-sider',
54
56
  isCollapsed.value && 'k-crud-sider--collapsed',
57
+ drawerOpen.value && 'k-crud-sider--drawer-open',
55
58
  ]}
56
59
  style={{ width: currentWidth.value }}
57
60
  >
@@ -147,3 +147,116 @@
147
147
  padding: var(--kine-spacing-8);
148
148
  box-sizing: border-box;
149
149
  }
150
+
151
+ /* ===== 遮罩层(桌面端隐藏) ===== */
152
+ .k-crud-sider-overlay {
153
+ display: none;
154
+ position: fixed;
155
+ inset: 0;
156
+ z-index: 999;
157
+ background: rgba(0, 0, 0, 0);
158
+ pointer-events: none;
159
+ transition: background 0.3s var(--kine-motion-easing-default);
160
+ }
161
+
162
+ .k-crud-sider-overlay--visible {
163
+ background: rgba(0, 0, 0, 0.45);
164
+ pointer-events: auto;
165
+ }
166
+
167
+ /* ===== 汉堡按钮 ===== */
168
+ .k-crud-header-hamburger {
169
+ display: none; /* 桌面端隐藏 */
170
+ flex-direction: column;
171
+ justify-content: center;
172
+ align-items: center;
173
+ width: 36px;
174
+ height: 36px;
175
+ flex-shrink: 0;
176
+ margin-right: var(--kine-spacing-4);
177
+ background: none;
178
+ border: none;
179
+ cursor: pointer;
180
+ padding: var(--kine-spacing-2);
181
+ border-radius: var(--kine-radius-xs);
182
+ color: var(--kine-color-text-secondary);
183
+ transition: background var(--kine-motion-duration-fast) var(--kine-motion-easing-default);
184
+ }
185
+
186
+ .k-crud-header-hamburger:hover {
187
+ background: var(--kine-color-bg-hover);
188
+ color: var(--kine-color-text-primary);
189
+ }
190
+
191
+ /* 三条横线图标 */
192
+ .k-crud-header-hamburger-icon,
193
+ .k-crud-header-hamburger-icon::before,
194
+ .k-crud-header-hamburger-icon::after {
195
+ display: block;
196
+ width: 18px;
197
+ height: 2px;
198
+ background: currentColor;
199
+ border-radius: 1px;
200
+ }
201
+
202
+ .k-crud-header-hamburger-icon {
203
+ position: relative;
204
+ }
205
+
206
+ .k-crud-header-hamburger-icon::before,
207
+ .k-crud-header-hamburger-icon::after {
208
+ content: '';
209
+ position: absolute;
210
+ left: 0;
211
+ }
212
+
213
+ .k-crud-header-hamburger-icon::before { top: -5px; }
214
+ .k-crud-header-hamburger-icon::after { top: 5px; }
215
+
216
+ /* ================================================================
217
+ 移动端(<768px)
218
+ ================================================================ */
219
+
220
+ @media (max-width: 767px) {
221
+ /* 汉堡按钮显示 */
222
+ .k-crud-header-hamburger {
223
+ display: flex;
224
+ }
225
+
226
+ /* 侧边栏变为 fixed 抽屉,默认隐藏 */
227
+ .k-crud-sider {
228
+ position: fixed;
229
+ top: 0;
230
+ left: 0;
231
+ height: 100%;
232
+ width: 240px !important; /* 覆盖 inline style */
233
+ z-index: 1000;
234
+ transform: translateX(-100%);
235
+ transition:
236
+ transform 0.3s var(--kine-motion-easing-default),
237
+ box-shadow 0.3s var(--kine-motion-easing-default);
238
+ box-shadow: none;
239
+ border-right: none;
240
+ }
241
+
242
+ /* 抽屉打开 */
243
+ .k-crud-sider--drawer-open {
244
+ transform: translateX(0);
245
+ box-shadow: 4px 0 24px rgba(0, 0, 0, 0.18);
246
+ }
247
+
248
+ /* 遮罩层 */
249
+ .k-crud-sider-overlay {
250
+ display: block;
251
+ }
252
+
253
+ /* 底部折叠 trigger 在移动端隐藏 */
254
+ .k-crud-sider-trigger {
255
+ display: none;
256
+ }
257
+
258
+ /* 内容区 padding 缩减 */
259
+ .k-crud-content {
260
+ padding: var(--kine-spacing-4);
261
+ }
262
+ }
@@ -91,3 +91,31 @@
91
91
  flex-shrink: 0;
92
92
  padding: var(--kine-spacing-4) 0;
93
93
  }
94
+
95
+ /* ================================================================
96
+ 移动端(<768px)
97
+ ================================================================ */
98
+
99
+ @media (max-width: 767px) {
100
+ /* 搜索区从横排改为纵排 */
101
+ .k-search-table-search {
102
+ flex-direction: column;
103
+ align-items: stretch;
104
+ padding: var(--kine-spacing-6);
105
+ gap: var(--kine-spacing-4);
106
+ }
107
+
108
+ .k-search-table-search-form {
109
+ flex-direction: column;
110
+ }
111
+
112
+ /* 搜索按钮区靠右 */
113
+ .k-search-table-search-actions {
114
+ justify-content: flex-end;
115
+ }
116
+
117
+ /* 表格区允许横向滚动 */
118
+ .k-search-table-body {
119
+ overflow-x: auto;
120
+ }
121
+ }
@@ -89,6 +89,20 @@ async function unwrapResponse<T>(
89
89
  ): Promise<T> {
90
90
  // HTTP 错误
91
91
  if (response.status < 200 || response.status >= 300) {
92
+ // 4xx:尝试解析 body,提取业务错误信息
93
+ if (response.status >= 400 && response.status < 500) {
94
+ const ct = response.headers['content-type'] ?? '';
95
+ if (ct.includes('application/json')) {
96
+ try {
97
+ const json = await response.json<WrappedResponse<unknown>>();
98
+ if (isWrappedResponse(json) && !json.success) {
99
+ throw new BusinessError(json.message || `请求失败(${response.status})`);
100
+ }
101
+ } catch (e) {
102
+ if (e instanceof BusinessError) throw e;
103
+ }
104
+ }
105
+ }
92
106
  throw new NetworkRequestError({
93
107
  type: 'httpError',
94
108
  status: response.status,
@@ -6,7 +6,7 @@ export interface EditableColumn {
6
6
  label: string;
7
7
  /** 列宽 */
8
8
  width?: string;
9
- /** 编辑态下的输入类型 */
9
+ /** 输入类型 */
10
10
  editType?: 'text' | 'number' | 'select' | 'search' | 'readonly';
11
11
  /** select 类型的选项 */
12
12
  options?: Array<{
@@ -77,7 +77,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
77
77
  type: StringConstructor;
78
78
  default: string;
79
79
  };
80
- }>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, ("add" | "update:modelValue" | "remove" | "cancel" | "edit" | "save")[], "add" | "update:modelValue" | "remove" | "cancel" | "edit" | "save", import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
80
+ }>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, ("add" | "update:modelValue" | "remove")[], "add" | "update:modelValue" | "remove", import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
81
81
  /** 列定义 */
82
82
  columns: {
83
83
  type: PropType<EditableColumn[]>;
@@ -130,11 +130,8 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
130
130
  };
131
131
  }>> & Readonly<{
132
132
  "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
133
- onCancel?: ((...args: any[]) => any) | undefined;
134
133
  onRemove?: ((...args: any[]) => any) | undefined;
135
134
  onAdd?: ((...args: any[]) => any) | undefined;
136
- onEdit?: ((...args: any[]) => any) | undefined;
137
- onSave?: ((...args: any[]) => any) | undefined;
138
135
  }>, {
139
136
  title: string;
140
137
  modelValue: EditableTableRow[];
@@ -3,6 +3,8 @@ export declare const LAYOUT_COLLAPSED_KEY: InjectionKey<Ref<boolean>>;
3
3
  export declare const LAYOUT_TOGGLE_KEY: InjectionKey<() => void>;
4
4
  export declare const LAYOUT_SIDER_WIDTH_KEY: InjectionKey<string>;
5
5
  export declare const LAYOUT_COLLAPSED_WIDTH_KEY: InjectionKey<string>;
6
+ export declare const LAYOUT_DRAWER_OPEN_KEY: InjectionKey<Ref<boolean>>;
7
+ export declare const LAYOUT_TOGGLE_DRAWER_KEY: InjectionKey<() => void>;
6
8
  declare const _default: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
7
9
  /** 受控模式:外部传入 collapsed 状态 */
8
10
  collapsed: {