@h-ai/ui 0.1.0-alpha.21 → 0.1.0-alpha.23
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 +41 -5
- package/auto-import.d.ts +2 -2
- package/auto-import.js +2 -2
- package/dist/components/compounds/DataTable.svelte +4 -1
- package/dist/components/compounds/DataTable.svelte.d.ts +2 -0
- package/dist/components/compounds/Drawer.svelte +7 -2
- package/dist/components/compounds/Modal.svelte +2 -0
- package/dist/components/compounds/Pagination.svelte +35 -8
- package/dist/components/scenes/ai/AiDocumentEditor.svelte +102 -0
- package/dist/components/scenes/ai/code-preview.js +9 -0
- package/dist/components/scenes/ai/document-parse.js +11 -0
- package/dist/components/scenes/ai/document-types.d.ts +1 -1
- package/dist/components/scenes/ai/mermaid-render.d.ts +14 -0
- package/dist/components/scenes/ai/mermaid-render.js +42 -0
- package/dist/components/scenes/crud/{CrudDetailDrawer.svelte → CrudDetailPanel.svelte} +60 -23
- package/dist/components/scenes/crud/{CrudDetailDrawer.svelte.d.ts → CrudDetailPanel.svelte.d.ts} +12 -6
- package/dist/components/scenes/crud/CrudEditPanel.svelte +265 -0
- package/dist/components/scenes/crud/{CrudEditDrawer.svelte.d.ts → CrudEditPanel.svelte.d.ts} +12 -6
- package/dist/components/scenes/crud/CrudPage.svelte +84 -49
- package/dist/components/scenes/crud/CrudPage.svelte.d.ts +6 -4
- package/dist/components/scenes/crud/crud-types.d.ts +36 -2
- package/dist/components/scenes/crud/index.d.ts +3 -3
- package/dist/components/scenes/crud/index.js +3 -3
- package/dist/messages/en-US.json +4 -2
- package/dist/messages/zh-CN.json +4 -2
- package/dist/types.d.ts +10 -0
- package/package.json +4 -3
- package/dist/components/scenes/crud/CrudEditDrawer.svelte +0 -225
|
@@ -15,6 +15,36 @@ export interface CrudPermissions {
|
|
|
15
15
|
/** 允许删除 */
|
|
16
16
|
delete?: boolean;
|
|
17
17
|
}
|
|
18
|
+
/** 新建/编辑表单的展示形式 */
|
|
19
|
+
export type CrudFormVariant = 'drawer' | 'modal';
|
|
20
|
+
/** 列表显示密度 */
|
|
21
|
+
export type CrudDensity = 'normal' | 'compact';
|
|
22
|
+
/** 新建/编辑表单展示配置 */
|
|
23
|
+
export interface CrudFormConfig {
|
|
24
|
+
/** 展示形式:'drawer'(抽屉,默认)或 'modal'(弹出窗口) */
|
|
25
|
+
variant?: CrudFormVariant;
|
|
26
|
+
/** 抽屉尺寸预设(variant='drawer',默认 '2xl') */
|
|
27
|
+
drawerSize?: Size;
|
|
28
|
+
/** 抽屉自定义宽度 CSS 值,优先级高于 drawerSize(variant='drawer') */
|
|
29
|
+
drawerWidth?: string;
|
|
30
|
+
/** 弹窗尺寸预设(variant='modal',默认 '2xl') */
|
|
31
|
+
modalSize?: Size | 'full';
|
|
32
|
+
/** 弹窗自定义宽度 CSS 值(variant='modal') */
|
|
33
|
+
modalWidth?: string;
|
|
34
|
+
/** 弹窗自定义高度 CSS 值(variant='modal') */
|
|
35
|
+
modalHeight?: string;
|
|
36
|
+
}
|
|
37
|
+
/** 分页栏展示配置 */
|
|
38
|
+
export interface CrudPaginationConfig {
|
|
39
|
+
/** 是否显示每页条数选择器(默认 true) */
|
|
40
|
+
showSizeChanger?: boolean;
|
|
41
|
+
/** 每页条数候选项(默认 [10, 20, 50, 100]) */
|
|
42
|
+
pageSizeOptions?: number[];
|
|
43
|
+
/** 是否显示跳页输入(默认 true) */
|
|
44
|
+
showJumper?: boolean;
|
|
45
|
+
/** 是否显示总数(默认 true) */
|
|
46
|
+
showTotal?: boolean;
|
|
47
|
+
}
|
|
18
48
|
/** CrudPage 初始列表数据(由 +page.server.ts load 返回) */
|
|
19
49
|
export interface CrudPageData<T = Record<string, unknown>> {
|
|
20
50
|
items: T[];
|
|
@@ -108,8 +138,12 @@ export interface CrudPageProps<T = Record<string, unknown>> {
|
|
|
108
138
|
data: CrudPageData<T>;
|
|
109
139
|
/** 权限控制 */
|
|
110
140
|
permissions?: CrudPermissions;
|
|
111
|
-
/**
|
|
112
|
-
|
|
141
|
+
/** 新建/编辑表单展示配置(抽屉或弹窗、尺寸、宽高) */
|
|
142
|
+
form?: CrudFormConfig;
|
|
143
|
+
/** 分页栏展示配置(每页条数选择、跳页、总数) */
|
|
144
|
+
pagination?: CrudPaginationConfig;
|
|
145
|
+
/** 列表显示密度:'normal'(默认)或 'compact'(紧凑) */
|
|
146
|
+
density?: CrudDensity;
|
|
113
147
|
/** 列表行点击是否打开详情(默认 true) */
|
|
114
148
|
rowClickDetail?: boolean;
|
|
115
149
|
/** 列表行操作按钮插槽 */
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @h-ai/ui — CRUD 场景组件
|
|
3
3
|
*
|
|
4
|
-
* 提供声明式 CRUD 页面组件,基于 kit.crud.define()
|
|
4
|
+
* 提供声明式 CRUD 页面组件,基于 kit.crud.define() 定义自动渲染列表、搜索过滤、分页、详情/编辑面板和删除确认。
|
|
5
5
|
* @module crud
|
|
6
6
|
*/
|
|
7
7
|
export type * from './crud-types.js';
|
|
8
8
|
export { default as CrudDeleteConfirm } from './CrudDeleteConfirm.svelte';
|
|
9
|
-
export { default as
|
|
10
|
-
export { default as
|
|
9
|
+
export { default as CrudDetailPanel } from './CrudDetailPanel.svelte';
|
|
10
|
+
export { default as CrudEditPanel } from './CrudEditPanel.svelte';
|
|
11
11
|
export { default as CrudFilterBar } from './CrudFilterBar.svelte';
|
|
12
12
|
export { default as CrudPage } from './CrudPage.svelte';
|
|
13
13
|
export type { NavAdapter } from './nav-adapter.js';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @h-ai/ui — CRUD 场景组件
|
|
3
3
|
*
|
|
4
|
-
* 提供声明式 CRUD 页面组件,基于 kit.crud.define()
|
|
4
|
+
* 提供声明式 CRUD 页面组件,基于 kit.crud.define() 定义自动渲染列表、搜索过滤、分页、详情/编辑面板和删除确认。
|
|
5
5
|
* @module crud
|
|
6
6
|
*/
|
|
7
7
|
export { default as CrudDeleteConfirm } from './CrudDeleteConfirm.svelte';
|
|
8
|
-
export { default as
|
|
9
|
-
export { default as
|
|
8
|
+
export { default as CrudDetailPanel } from './CrudDetailPanel.svelte';
|
|
9
|
+
export { default as CrudEditPanel } from './CrudEditPanel.svelte';
|
|
10
10
|
export { default as CrudFilterBar } from './CrudFilterBar.svelte';
|
|
11
11
|
export { default as CrudPage } from './CrudPage.svelte';
|
|
12
12
|
export { createBrowserNavAdapter } from './nav-adapter.js';
|
package/dist/messages/en-US.json
CHANGED
|
@@ -156,9 +156,10 @@
|
|
|
156
156
|
"feedback_type_feature": "Feature Request",
|
|
157
157
|
"feedback_type_question": "Question",
|
|
158
158
|
"feedback_type_other": "Other",
|
|
159
|
-
"pagination_total": "
|
|
159
|
+
"pagination_total": "{count} items total",
|
|
160
160
|
"pagination_jump_to": "Go to",
|
|
161
161
|
"pagination_page": "page",
|
|
162
|
+
"pagination_page_size": "{size} / page",
|
|
162
163
|
"severity_critical": "Critical",
|
|
163
164
|
"severity_high": "High",
|
|
164
165
|
"severity_medium": "Medium",
|
|
@@ -248,6 +249,7 @@
|
|
|
248
249
|
"markdown_run_failed": "Run failed",
|
|
249
250
|
"markdown_run_unavailable": "No preview runner is configured for this code block.",
|
|
250
251
|
"markdown_run_unavailable_language": "Local preview for {language} is not supported. Reason: no runtime is configured for this language.",
|
|
252
|
+
"markdown_mermaid_failed": "Failed to render diagram",
|
|
251
253
|
"table_editor_eyebrow": "Editable table",
|
|
252
254
|
"table_copy": "Copy table",
|
|
253
255
|
"table_download": "Download table",
|
|
@@ -273,7 +275,7 @@
|
|
|
273
275
|
"crud_delete_failed": "Delete failed",
|
|
274
276
|
"crud_network_error": "Network error, please try again later",
|
|
275
277
|
"crud_filter_all": "All",
|
|
276
|
-
"crud_total": "
|
|
278
|
+
"crud_total": "{count} items total",
|
|
277
279
|
"crud_detail_empty": "-",
|
|
278
280
|
"crud_field_required": "This field is required"
|
|
279
281
|
}
|
package/dist/messages/zh-CN.json
CHANGED
|
@@ -156,9 +156,10 @@
|
|
|
156
156
|
"feedback_type_feature": "功能建议",
|
|
157
157
|
"feedback_type_question": "问题咨询",
|
|
158
158
|
"feedback_type_other": "其他",
|
|
159
|
-
"pagination_total": "共 {count}",
|
|
159
|
+
"pagination_total": "共 {count} 条",
|
|
160
160
|
"pagination_jump_to": "跳转到",
|
|
161
161
|
"pagination_page": "页",
|
|
162
|
+
"pagination_page_size": "{size} 条/页",
|
|
162
163
|
"severity_critical": "严重",
|
|
163
164
|
"severity_high": "高",
|
|
164
165
|
"severity_medium": "中",
|
|
@@ -248,6 +249,7 @@
|
|
|
248
249
|
"markdown_run_failed": "运行失败",
|
|
249
250
|
"markdown_run_unavailable": "当前代码块未配置运行预览器。",
|
|
250
251
|
"markdown_run_unavailable_language": "暂不支持 {language} 的本地预览。原因:当前环境未配置该语言的运行器。",
|
|
252
|
+
"markdown_mermaid_failed": "图表渲染失败",
|
|
251
253
|
"table_editor_eyebrow": "可编辑表格",
|
|
252
254
|
"table_copy": "复制表格",
|
|
253
255
|
"table_download": "下载表格",
|
|
@@ -273,7 +275,7 @@
|
|
|
273
275
|
"crud_delete_failed": "删除失败",
|
|
274
276
|
"crud_network_error": "网络错误,请稍后重试",
|
|
275
277
|
"crud_filter_all": "全部",
|
|
276
|
-
"crud_total": "共 {count}",
|
|
278
|
+
"crud_total": "共 {count} 条",
|
|
277
279
|
"crud_detail_empty": "-",
|
|
278
280
|
"crud_field_required": "此字段为必填项"
|
|
279
281
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -475,6 +475,8 @@ export interface DrawerProps {
|
|
|
475
475
|
position?: Position;
|
|
476
476
|
/** 尺寸 */
|
|
477
477
|
size?: Size;
|
|
478
|
+
/** 自定义宽度(任意合法 CSS 宽度值,优先级高于 size 预设) */
|
|
479
|
+
width?: string;
|
|
478
480
|
/** 是否可通过点击遮罩关闭 */
|
|
479
481
|
closeOnBackdrop?: boolean;
|
|
480
482
|
/** 是否显示关闭按钮 */
|
|
@@ -585,6 +587,10 @@ export interface PaginationProps {
|
|
|
585
587
|
showTotal?: boolean;
|
|
586
588
|
/** 是否显示跳转 */
|
|
587
589
|
showJumper?: boolean;
|
|
590
|
+
/** 是否显示每页条数选择器 */
|
|
591
|
+
showSizeChanger?: boolean;
|
|
592
|
+
/** 每页条数候选项(配合 showSizeChanger,默认 [10, 20, 50, 100]) */
|
|
593
|
+
pageSizeOptions?: number[];
|
|
588
594
|
/** i18n 文案配置 */
|
|
589
595
|
labels?: {
|
|
590
596
|
/** 总数文案,{count} 会被替换为实际数字 */
|
|
@@ -593,11 +599,15 @@ export interface PaginationProps {
|
|
|
593
599
|
jumpTo?: string;
|
|
594
600
|
/** 页 */
|
|
595
601
|
page?: string;
|
|
602
|
+
/** 每页条数文案,{size} 会被替换为实际数字 */
|
|
603
|
+
pageSize?: string;
|
|
596
604
|
};
|
|
597
605
|
/** 自定义类名 */
|
|
598
606
|
class?: string;
|
|
599
607
|
/** 页码变化事件 */
|
|
600
608
|
onchange?: (page: number) => void;
|
|
609
|
+
/** 每页条数变化事件 */
|
|
610
|
+
onpagesizechange?: (pageSize: number) => void;
|
|
601
611
|
}
|
|
602
612
|
/**
|
|
603
613
|
* 面包屑项
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.23",
|
|
5
5
|
"description": "Hai Framework UI component library for Svelte 5 Runes.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"svelte": "./dist/index.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"svelte": "^5.55.
|
|
36
|
+
"svelte": "^5.55.9"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@internationalized/date": "^3.8.0",
|
|
@@ -42,9 +42,10 @@
|
|
|
42
42
|
"html2canvas": "^1.4.1",
|
|
43
43
|
"jspdf": "^4.2.1",
|
|
44
44
|
"marked": "^18.0.0",
|
|
45
|
+
"mermaid": "^11.12.0",
|
|
45
46
|
"shiki": "^4.0.2",
|
|
46
47
|
"tailwind-merge": "^3.5.0",
|
|
47
|
-
"@h-ai/core": "0.1.0-alpha.
|
|
48
|
+
"@h-ai/core": "0.1.0-alpha.23"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@sveltejs/package": "^2.5.7",
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
@component CrudEditDrawer
|
|
3
|
-
CRUD 编辑/新建抽屉组件
|
|
4
|
-
|
|
5
|
-
使用 Svelte 5 Runes ($props, $state, $derived)
|
|
6
|
-
使用 compounds/primitives 组件:Drawer, FormField, Input, Select, Textarea, Checkbox, Button
|
|
7
|
-
-->
|
|
8
|
-
<script lang='ts'>
|
|
9
|
-
import type { Snippet } from 'svelte'
|
|
10
|
-
import type { Size } from '../../../types.js'
|
|
11
|
-
import { uiM } from '../../../messages.js'
|
|
12
|
-
import Drawer from '../../compounds/Drawer.svelte'
|
|
13
|
-
import FormField from '../../compounds/FormField.svelte'
|
|
14
|
-
import Button from '../../primitives/Button.svelte'
|
|
15
|
-
import Checkbox from '../../primitives/Checkbox.svelte'
|
|
16
|
-
import Input from '../../primitives/Input.svelte'
|
|
17
|
-
import Select from '../../primitives/Select.svelte'
|
|
18
|
-
import Textarea from '../../primitives/Textarea.svelte'
|
|
19
|
-
|
|
20
|
-
type FieldDef = {
|
|
21
|
-
id: string
|
|
22
|
-
label: string | (() => string)
|
|
23
|
-
type: string
|
|
24
|
-
readonly?: boolean
|
|
25
|
-
options?: Array<{ label: string, value: string | number | boolean }> | (() => Array<{ label: string, value: string | number | boolean }>)
|
|
26
|
-
validation?: { required?: boolean, min?: number, max?: number, pattern?: string, message?: string }
|
|
27
|
-
placeholder?: string | (() => string)
|
|
28
|
-
order?: number
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let {
|
|
32
|
-
open = $bindable(false),
|
|
33
|
-
mode = 'create' as 'create' | 'edit',
|
|
34
|
-
fields = [],
|
|
35
|
-
formData = $bindable<Record<string, unknown>>({}),
|
|
36
|
-
title = '',
|
|
37
|
-
size = '2xl' as Size,
|
|
38
|
-
submitting = false,
|
|
39
|
-
error = '',
|
|
40
|
-
onsubmit,
|
|
41
|
-
onclose,
|
|
42
|
-
editFormExtra,
|
|
43
|
-
editingItem = null,
|
|
44
|
-
}: {
|
|
45
|
-
open?: boolean
|
|
46
|
-
mode?: 'create' | 'edit'
|
|
47
|
-
fields?: FieldDef[]
|
|
48
|
-
formData?: Record<string, unknown>
|
|
49
|
-
title?: string
|
|
50
|
-
size?: Size
|
|
51
|
-
submitting?: boolean
|
|
52
|
-
error?: string
|
|
53
|
-
onsubmit?: (data: Record<string, unknown>) => Promise<void>
|
|
54
|
-
onclose?: () => void
|
|
55
|
-
editFormExtra?: Snippet<[Record<string, unknown> | null, 'create' | 'edit']>
|
|
56
|
-
editingItem?: Record<string, unknown> | null
|
|
57
|
-
} = $props()
|
|
58
|
-
|
|
59
|
-
function resolveText(text: string | (() => string)): string {
|
|
60
|
-
return typeof text === 'function' ? text() : text
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function resolveOptions(options?: FieldDef['options']): Array<{ label: string, value: string | number | boolean }> {
|
|
64
|
-
if (!options)
|
|
65
|
-
return []
|
|
66
|
-
return typeof options === 'function' ? options() : options
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const sortedFields = $derived(
|
|
70
|
-
[...fields].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)),
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
function handleClose() {
|
|
74
|
-
open = false
|
|
75
|
-
onclose?.()
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function handleSubmit(e: Event) {
|
|
79
|
-
e.preventDefault()
|
|
80
|
-
await onsubmit?.(formData)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function getInputType(fieldType: string): 'email' | 'url' | 'tel' | 'password' | 'number' | 'text' {
|
|
84
|
-
switch (fieldType) {
|
|
85
|
-
case 'email': return 'email'
|
|
86
|
-
case 'url': return 'url'
|
|
87
|
-
case 'tel': return 'tel'
|
|
88
|
-
case 'password': return 'password'
|
|
89
|
-
case 'number': return 'number'
|
|
90
|
-
case 'date': return 'text'
|
|
91
|
-
case 'datetime': return 'text'
|
|
92
|
-
default: return 'text'
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function updateField(fieldId: string, value: unknown) {
|
|
97
|
-
formData = { ...formData, [fieldId]: value }
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function toggleMultiSelect(fieldId: string, optValue: string | number | boolean) {
|
|
101
|
-
const current = (formData[fieldId] as Array<string | number | boolean>) ?? []
|
|
102
|
-
const strVal = String(optValue)
|
|
103
|
-
if (current.map(String).includes(strVal)) {
|
|
104
|
-
formData = { ...formData, [fieldId]: current.filter(v => String(v) !== strVal) }
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
formData = { ...formData, [fieldId]: [...current, optValue] }
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
</script>
|
|
111
|
-
|
|
112
|
-
<Drawer bind:open {title} position='right' {size} onclose={handleClose} closeOnBackdrop={false}>
|
|
113
|
-
<form onsubmit={handleSubmit} class='space-y-4 pb-20'>
|
|
114
|
-
{#if error}
|
|
115
|
-
<div class='p-3 bg-error/10 border border-error/20 rounded-lg text-sm text-error flex items-center gap-2'>
|
|
116
|
-
<span class='icon-[tabler--alert-circle] size-4 shrink-0'></span>
|
|
117
|
-
<span>{error}</span>
|
|
118
|
-
</div>
|
|
119
|
-
{/if}
|
|
120
|
-
|
|
121
|
-
{#each sortedFields as field (field.id)}
|
|
122
|
-
{@const isReadonly = field.readonly && mode === 'edit'}
|
|
123
|
-
{@const isRequired = field.validation?.required}
|
|
124
|
-
{@const fieldValue = formData[field.id]}
|
|
125
|
-
{@const placeholder = field.placeholder ? resolveText(field.placeholder) : ''}
|
|
126
|
-
|
|
127
|
-
{#if field.type === 'textarea'}
|
|
128
|
-
<FormField label={resolveText(field.label)} required={isRequired}>
|
|
129
|
-
<Textarea
|
|
130
|
-
id={field.id}
|
|
131
|
-
value={String(fieldValue ?? '')}
|
|
132
|
-
{placeholder}
|
|
133
|
-
disabled={submitting || isReadonly}
|
|
134
|
-
required={isRequired}
|
|
135
|
-
oninput={e => updateField(field.id, (e.target as HTMLTextAreaElement).value)}
|
|
136
|
-
/>
|
|
137
|
-
</FormField>
|
|
138
|
-
|
|
139
|
-
{:else if field.type === 'select' || field.type === 'radio'}
|
|
140
|
-
{@const opts = resolveOptions(field.options)}
|
|
141
|
-
<FormField label={resolveText(field.label)} required={isRequired}>
|
|
142
|
-
<Select
|
|
143
|
-
id={field.id}
|
|
144
|
-
value={String(fieldValue ?? '')}
|
|
145
|
-
disabled={submitting || isReadonly}
|
|
146
|
-
onchange={value => updateField(field.id, value)}
|
|
147
|
-
>
|
|
148
|
-
<option value="">{placeholder || uiM('crud_filter_all')}</option>
|
|
149
|
-
{#each opts as opt (String(opt.value))}
|
|
150
|
-
<option value={String(opt.value)}>{opt.label}</option>
|
|
151
|
-
{/each}
|
|
152
|
-
</Select>
|
|
153
|
-
</FormField>
|
|
154
|
-
|
|
155
|
-
{:else if field.type === 'multi-select'}
|
|
156
|
-
{@const opts = resolveOptions(field.options)}
|
|
157
|
-
{@const selectedValues = ((fieldValue ?? []) as Array<string | number | boolean>).map(String)}
|
|
158
|
-
<FormField label={resolveText(field.label)} required={isRequired}>
|
|
159
|
-
<div class='flex flex-wrap gap-3 p-3 bg-base-200 rounded-lg max-h-48 overflow-y-auto'>
|
|
160
|
-
{#each opts as opt (String(opt.value))}
|
|
161
|
-
<label class='inline-flex items-center gap-2 cursor-pointer'>
|
|
162
|
-
<Checkbox
|
|
163
|
-
size='sm'
|
|
164
|
-
checked={selectedValues.includes(String(opt.value))}
|
|
165
|
-
onchange={() => toggleMultiSelect(field.id, opt.value)}
|
|
166
|
-
disabled={submitting || isReadonly}
|
|
167
|
-
/>
|
|
168
|
-
<span class='text-sm text-base-content'>{opt.label}</span>
|
|
169
|
-
</label>
|
|
170
|
-
{/each}
|
|
171
|
-
{#if opts.length === 0}
|
|
172
|
-
<span class='text-sm text-base-content/60'>{uiM('crud_no_data')}</span>
|
|
173
|
-
{/if}
|
|
174
|
-
</div>
|
|
175
|
-
</FormField>
|
|
176
|
-
|
|
177
|
-
{:else if field.type === 'boolean' || field.type === 'checkbox'}
|
|
178
|
-
<FormField label={resolveText(field.label)}>
|
|
179
|
-
<Checkbox
|
|
180
|
-
checked={Boolean(fieldValue)}
|
|
181
|
-
onchange={(checked: boolean) => updateField(field.id, checked)}
|
|
182
|
-
disabled={submitting || isReadonly}
|
|
183
|
-
/>
|
|
184
|
-
</FormField>
|
|
185
|
-
|
|
186
|
-
{:else}
|
|
187
|
-
<FormField label={resolveText(field.label)} required={isRequired}>
|
|
188
|
-
<Input
|
|
189
|
-
id={field.id}
|
|
190
|
-
type={getInputType(field.type)}
|
|
191
|
-
value={String(fieldValue ?? '')}
|
|
192
|
-
{placeholder}
|
|
193
|
-
disabled={submitting || isReadonly}
|
|
194
|
-
required={isRequired}
|
|
195
|
-
pattern={field.validation?.pattern}
|
|
196
|
-
minlength={field.type !== 'number' ? field.validation?.min : undefined}
|
|
197
|
-
maxlength={field.type !== 'number' ? field.validation?.max : undefined}
|
|
198
|
-
min={field.type === 'number' ? field.validation?.min : undefined}
|
|
199
|
-
max={field.type === 'number' ? field.validation?.max : undefined}
|
|
200
|
-
oninput={e => updateField(field.id, field.type === 'number' ? Number((e.currentTarget as HTMLInputElement).value) : (e.currentTarget as HTMLInputElement).value)}
|
|
201
|
-
/>
|
|
202
|
-
</FormField>
|
|
203
|
-
{/if}
|
|
204
|
-
{/each}
|
|
205
|
-
|
|
206
|
-
{#if editFormExtra}
|
|
207
|
-
<div class='border-t border-base-content/5 pt-4'>
|
|
208
|
-
{@render editFormExtra(editingItem, mode)}
|
|
209
|
-
</div>
|
|
210
|
-
{/if}
|
|
211
|
-
</form>
|
|
212
|
-
|
|
213
|
-
<!-- 底部操作栏 -->
|
|
214
|
-
<div class='absolute bottom-0 left-0 right-0 p-4 bg-base-200 border-t border-base-content/10 flex justify-end gap-2'>
|
|
215
|
-
<Button variant='ghost' onclick={handleClose} disabled={submitting}>
|
|
216
|
-
{uiM('crud_cancel')}
|
|
217
|
-
</Button>
|
|
218
|
-
<Button variant='primary' onclick={handleSubmit} disabled={submitting}>
|
|
219
|
-
{#if submitting}
|
|
220
|
-
<span class='loading loading-spinner loading-xs mr-2'></span>
|
|
221
|
-
{/if}
|
|
222
|
-
{mode === 'create' ? uiM('crud_create') : uiM('crud_save')}
|
|
223
|
-
</Button>
|
|
224
|
-
</div>
|
|
225
|
-
</Drawer>
|