diff --git a/components.d.ts b/components.d.ts
index 820e77e..33b3ec1 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -46,9 +46,9 @@ declare module 'vue' {
RouterView: typeof import('vue-router')['RouterView']
TyBreadcrumb: typeof import('./src/components/ty-breadcrumb.vue')['default']
TyCrud: typeof import('./src/components/ty-crud.vue')['default']
+ TyDialog: typeof import('./src/components/ty-dialog.vue')['default']
TyFooter: typeof import('./src/components/ty-footer.vue')['default']
TyForm: typeof import('./src/components/ty-form.vue')['default']
- TyFormDialog: typeof import('./src/components/ty-form-dialog.vue')['default']
TyHeader: typeof import('./src/components/ty-header.vue')['default']
TyMixbar: typeof import('./src/components/ty-mixbar.vue')['default']
TySearchForm: typeof import('./src/components/ty-search-form.vue')['default']
@@ -99,9 +99,9 @@ declare global {
const RouterView: typeof import('vue-router')['RouterView']
const TyBreadcrumb: typeof import('./src/components/ty-breadcrumb.vue')['default']
const TyCrud: typeof import('./src/components/ty-crud.vue')['default']
+ const TyDialog: typeof import('./src/components/ty-dialog.vue')['default']
const TyFooter: typeof import('./src/components/ty-footer.vue')['default']
const TyForm: typeof import('./src/components/ty-form.vue')['default']
- const TyFormDialog: typeof import('./src/components/ty-form-dialog.vue')['default']
const TyHeader: typeof import('./src/components/ty-header.vue')['default']
const TyMixbar: typeof import('./src/components/ty-mixbar.vue')['default']
const TySearchForm: typeof import('./src/components/ty-search-form.vue')['default']
diff --git a/src/App.vue b/src/App.vue
index 02c5506..4383df7 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,6 +1,7 @@
+
diff --git a/src/components/ty-crud.vue b/src/components/ty-crud.vue
index 9fbd663..66d5a04 100644
--- a/src/components/ty-crud.vue
+++ b/src/components/ty-crud.vue
@@ -63,17 +63,6 @@
-
-
@@ -88,6 +77,8 @@ import type {
PageResult,
TableColumn,
} from '@/types/crud'
+import TyForm from '@/components/ty-form.vue'
+import { showDialog } from '@/utils/dialog'
defineOptions({ name: 'TyCrud' })
@@ -144,12 +135,6 @@ const selectedRows = ref[]>([])
const query = reactive>(buildModel(props.searchSchema))
-const dialogVisible = ref(false)
-const dialogMode = ref<'create' | 'edit'>('create')
-const editingId = ref(null)
-const submitting = ref(false)
-const form = reactive>({})
-
function buildModel(schema?: JsonSchema): Record {
const model: Record = {}
if (!schema) return model
@@ -183,39 +168,38 @@ function handleReset(): void {
fetchList()
}
-function openCreate(): void {
- dialogMode.value = 'create'
- editingId.value = null
- Object.assign(form, buildModel(props.formSchema))
- dialogVisible.value = true
+async function openCreate(): Promise {
+ const model = buildModel(props.formSchema)
+ const data = await showDialog({
+ component: TyForm,
+ props: { schema: props.formSchema, modelValue: model, mode: 'create' },
+ title: `新增${props.entityName}`,
+ })
+ if (data == null) return
+ await props.create(data)
+ ElMessage.success('新增成功')
+ fetchList()
}
-function openEdit(row: Record): void {
- dialogMode.value = 'edit'
- editingId.value = String(row[props.idKey])
+async function openEdit(row: Record): Promise {
const model = buildModel(props.formSchema)
for (const key of Object.keys(props.formSchema.properties)) {
model[key] = row[key] ?? model[key]
}
- Object.assign(form, model)
- dialogVisible.value = true
-}
-
-async function handleSubmit(): Promise {
- submitting.value = true
- try {
- if (dialogMode.value === 'edit') {
- await props.update(editingId.value!, { ...form })
- ElMessage.success('更新成功')
- } else {
- await props.create({ ...form })
- ElMessage.success('新增成功')
- }
- dialogVisible.value = false
- fetchList()
- } finally {
- submitting.value = false
- }
+ const data = await showDialog({
+ component: TyForm,
+ props: {
+ schema: props.formSchema,
+ modelValue: model,
+ mode: 'edit',
+ readonlyKeys: props.editReadonlyKeys,
+ },
+ title: `编辑${props.entityName}`,
+ })
+ if (data == null) return
+ await props.update(String(row[props.idKey]), data)
+ ElMessage.success('更新成功')
+ fetchList()
}
async function handleDelete(row: Record): Promise {
diff --git a/src/components/ty-dialog.vue b/src/components/ty-dialog.vue
new file mode 100644
index 0000000..1cb432f
--- /dev/null
+++ b/src/components/ty-dialog.vue
@@ -0,0 +1,61 @@
+
+
+
+
+ {{ dialog.options.cancelText ?? '取消' }}
+ {{ dialog.options.confirmText ?? '确定' }}
+
+
+
+
+
diff --git a/src/components/ty-form-dialog.vue b/src/components/ty-form-dialog.vue
deleted file mode 100644
index 77a97a3..0000000
--- a/src/components/ty-form-dialog.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
- 取消
- 确定
-
-
-
-
-
diff --git a/src/components/ty-form.vue b/src/components/ty-form.vue
index 0addad4..8ce5b34 100644
--- a/src/components/ty-form.vue
+++ b/src/components/ty-form.vue
@@ -147,7 +147,13 @@ function widgetStyle(prop: JsonSchemaProperty): Record {
return { width: prop.width ?? (props.inline ? '200px' : '100%') }
}
+async function submit(): Promise> {
+ await formRef.value?.validate()
+ return { ...props.modelValue }
+}
+
defineExpose({
+ submit,
validate: () => formRef.value?.validate(),
resetFields: () => formRef.value?.resetFields(),
clearValidate: () => formRef.value?.clearValidate(),
diff --git a/src/pages/system/user/index.vue b/src/pages/system/user/index.vue
index 446d157..ca881ad 100644
--- a/src/pages/system/user/index.vue
+++ b/src/pages/system/user/index.vue
@@ -25,32 +25,14 @@
-
-
-
- {{ resetPwd.username }}
-
-
-
-
-
- 取消
- 确定
-
-
diff --git a/src/utils/dialog.ts b/src/utils/dialog.ts
new file mode 100644
index 0000000..6b7c5e5
--- /dev/null
+++ b/src/utils/dialog.ts
@@ -0,0 +1,50 @@
+import { markRaw, reactive, type Component } from 'vue'
+
+export interface DialogOptions {
+ /** 对话框内容组件。 */
+ component: Component
+ title?: string
+ width?: string
+ /** 传给内容组件的 props。 */
+ props?: Record
+ confirmText?: string
+ cancelText?: string
+ /** 是否显示默认 取消/确定 底部(false 时内容组件自带底部)。 */
+ showFooter?: boolean
+ /** 确认回调,参数为内容组件 submit() 返回的数据。 */
+ onConfirm?: (data: any) => void | Promise
+}
+
+export interface DialogInstance {
+ id: number
+ options: DialogOptions
+ resolve: (value: any) => void
+}
+
+/** 当前打开的对话框栈(由 宿主渲染)。 */
+export const dialogs = reactive([])
+
+let seq = 0
+
+/**
+ * 打开一个对话框。返回 Promise:确认时 resolve 内容组件 submit() 返回的数据,
+ * 取消/关闭时 resolve null。
+ */
+export function showDialog(options: DialogOptions): Promise {
+ return new Promise((resolve) => {
+ dialogs.push({
+ id: ++seq,
+ options: { ...options, component: markRaw(options.component) },
+ resolve,
+ })
+ })
+}
+
+/** 关闭指定对话框并 resolve。 */
+export function closeDialog(id: number, result?: any): void {
+ const idx = dialogs.findIndex((d) => d.id === id)
+ if (idx >= 0) {
+ const [dialog] = dialogs.splice(idx, 1)
+ dialog.resolve(result)
+ }
+}
diff --git a/stats.html b/stats.html
index 37fb7b8..9591460 100644
--- a/stats.html
+++ b/stats.html
@@ -4930,7 +4930,7 @@ var drawChart = (function (exports) {