easybill-ui 1.2.26 → 1.3.1
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/components/CurdForm/src/CurdForm.vue +252 -39
- package/components/CurdForm/src/components/index.ts +2 -0
- package/components/CurdForm/src/components/schema-form-choose.vue +225 -0
- package/components/CurdForm/src/types.ts +7 -1
- package/components/CurdGroupForm/index.ts +2 -0
- package/components/CurdGroupForm/src/CurdGroupForm.vue +95 -0
- package/components/CurdGroupForm/src/components/CurdFormGroup.vue +39 -0
- package/components/CurdGroupForm/src/hooks/useFormHook.ts +55 -0
- package/components/CurdGroupForm/types.ts +22 -0
- package/components/FormDialog/src/FormDialog.vue +107 -47
- package/components/FormDialog/src/hooks/index.ts +2 -0
- package/components/FormDialog/src/hooks/useStepList.ts +49 -0
- package/components/FormDialog/src/hooks/useStepNavigation.ts +74 -0
- package/components/FormDialog/src/types.ts +29 -1
- package/index.ts +5 -2
- package/package.json +1 -1
- package/theme-chalk/curd-form.css +1 -1
- package/theme-chalk/curd-group-form.css +1 -0
- package/theme-chalk/index.css +1 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<CurdFormGroup v-for="(item, index) in props.schema.items" v-show="!schemaItemHidden(item)" :key="index" :item="item" :title="item.groupName" :icon="item.icon" :label-position="item.labelPosition">
|
|
3
|
+
<template v-if="item.headerRight" #headerRight>
|
|
4
|
+
<component :is="item.headerRight" :schema="props.schema" :schemaItem="item" :form-model="form" />
|
|
5
|
+
</template>
|
|
6
|
+
<template v-if="item.headerTitle" #title>
|
|
7
|
+
<component :is="item.headerTitle" :schema="props.schema" :schemaItem="item" :form-model="form" />
|
|
8
|
+
</template>
|
|
9
|
+
<template v-if="item.headerBottom" #headerBottom>
|
|
10
|
+
<component :is="item.headerBottom" :schema="props.schema" :schemaItem="item" :form-model="form" />
|
|
11
|
+
</template>
|
|
12
|
+
<div class="curdform-group-content" data-instance-form-wrap>
|
|
13
|
+
<CurdForm
|
|
14
|
+
:ref="(el: Element | ComponentPublicInstance | null) => (formRef[(item.step || '') + item.groupName + index] = el as InstanceType<typeof CurdForm>)"
|
|
15
|
+
v-model="form"
|
|
16
|
+
:form-schema="item.schema"
|
|
17
|
+
:extend-context="extendContext"
|
|
18
|
+
:validateOnRuleChange="false"
|
|
19
|
+
:scroll-to-error="true"
|
|
20
|
+
@change="onChange"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
</CurdFormGroup>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script lang="ts" setup>
|
|
27
|
+
import { CurdForm } from "easybill-ui"
|
|
28
|
+
import type { ComponentPublicInstance, PropType } from "vue"
|
|
29
|
+
import { watch } from "vue"
|
|
30
|
+
import type { CurdGroupFormSchema } from "../types"
|
|
31
|
+
import CurdFormGroup from "./components/CurdFormGroup.vue"
|
|
32
|
+
import { useFormHook } from "./hooks/useFormHook"
|
|
33
|
+
const props = defineProps({
|
|
34
|
+
modelValue: {
|
|
35
|
+
type: Object as PropType<Record<string, unknown>>,
|
|
36
|
+
default: () => ({}),
|
|
37
|
+
},
|
|
38
|
+
schema: {
|
|
39
|
+
type: Object as PropType<CurdGroupFormSchema>,
|
|
40
|
+
default: () => ({}),
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const emits = defineEmits(["update:modelValue", "change", "loadOptions"])
|
|
45
|
+
const { form, formRef, schema: formSchema, validate, schemaItemHidden } = useFormHook()
|
|
46
|
+
const init = () => {
|
|
47
|
+
form.value = props.modelValue || {}
|
|
48
|
+
formSchema.value = props.schema
|
|
49
|
+
}
|
|
50
|
+
init()
|
|
51
|
+
watch(
|
|
52
|
+
() => props.schema,
|
|
53
|
+
() => {
|
|
54
|
+
formSchema.value = props.schema
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
watch(
|
|
59
|
+
() => props.modelValue,
|
|
60
|
+
() => {
|
|
61
|
+
if (form.value != props.modelValue) {
|
|
62
|
+
form.value = props.modelValue
|
|
63
|
+
// emits("update:modelValue", form.value)
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
const loadOptions = (prop: string, config?: unknown) => {
|
|
69
|
+
if (!props.schema) return
|
|
70
|
+
let has = false
|
|
71
|
+
for (const index in props.schema.items) {
|
|
72
|
+
const group = props.schema.items[index]
|
|
73
|
+
const formItem = group.schema.formItem
|
|
74
|
+
for (const i in formItem) {
|
|
75
|
+
const item = formItem[i]
|
|
76
|
+
if (item.prop == prop) {
|
|
77
|
+
has = true
|
|
78
|
+
formRef.value[(group.step || "") + group.groupName + index]?.loadOptions(prop, config)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!has) {
|
|
83
|
+
emits("loadOptions", prop)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const extendContext = {
|
|
87
|
+
loadOptions,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const onChange = (v: unknown, l: unknown) => {
|
|
91
|
+
emits("change", v, l)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
defineExpose({ loadOptions, validate })
|
|
95
|
+
</script>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="curdform-group" :style="props.item.style" :class="props.item.class">
|
|
3
|
+
<div v-if="props.title || $slots.title" class="curdform-group-head">
|
|
4
|
+
<div class="left">
|
|
5
|
+
<span v-if="props.icon" class="icon"><i v-if="typeof props.icon == 'string'" :class="props.icon"></i><component v-else :is="props.icon"></component></span>
|
|
6
|
+
<div v-if="!$slots.title" class="title">{{ props.title }}</div>
|
|
7
|
+
<slot name="title"></slot>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="right">
|
|
10
|
+
<slot name="headerRight"></slot>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<div v-if="props.item.desc" class="curdform-group-desc">{{ props.item.desc }}</div>
|
|
14
|
+
<slot name="headerBottom"></slot>
|
|
15
|
+
<div v-if="$slots.default" class="curdform-group-body">
|
|
16
|
+
<slot></slot>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script lang="ts" setup>
|
|
22
|
+
import type { PropType } from "vue"
|
|
23
|
+
import type { CurdGroupFormSchemaItem } from "../../types"
|
|
24
|
+
|
|
25
|
+
const props = defineProps({
|
|
26
|
+
title: {
|
|
27
|
+
type: String,
|
|
28
|
+
default: "",
|
|
29
|
+
},
|
|
30
|
+
icon: {
|
|
31
|
+
type: String,
|
|
32
|
+
default: "",
|
|
33
|
+
},
|
|
34
|
+
item: {
|
|
35
|
+
type: Object as PropType<CurdGroupFormSchemaItem>,
|
|
36
|
+
default: () => ({}),
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
</script>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { CurdForm } from "easybill-ui"
|
|
2
|
+
import { ref, type Ref } from "vue"
|
|
3
|
+
import type { Schema, SchemaItem } from "../../types"
|
|
4
|
+
|
|
5
|
+
export const useFormHook = (): {
|
|
6
|
+
form: Ref<Record<string, unknown>>
|
|
7
|
+
formRef: Ref<Record<string, InstanceType<typeof CurdForm>>>
|
|
8
|
+
submit: () => void
|
|
9
|
+
schema: Ref<Schema | undefined>
|
|
10
|
+
validate: (callback: (valid: boolean) => void) => void
|
|
11
|
+
loading: Ref<boolean>
|
|
12
|
+
schemaItemHidden: (item: SchemaItem) => boolean
|
|
13
|
+
} => {
|
|
14
|
+
const formRef = ref<Record<string, InstanceType<typeof CurdForm>>>({})
|
|
15
|
+
const form = ref<Record<string, unknown>>({})
|
|
16
|
+
const schema = ref<Schema>()
|
|
17
|
+
const loading = ref(false)
|
|
18
|
+
const schemaItemHidden = (item: SchemaItem) => {
|
|
19
|
+
if (item?.hidden && item?.hidden instanceof Function) {
|
|
20
|
+
return item.hidden(form.value, item)
|
|
21
|
+
}
|
|
22
|
+
return !!item?.hidden
|
|
23
|
+
}
|
|
24
|
+
const validate = (callback: (valid: boolean) => void) => {
|
|
25
|
+
const validMap: boolean[] = []
|
|
26
|
+
for (const i in formRef.value) {
|
|
27
|
+
const curSchema = schema.value?.items.find((a, index) => (a.step || "") + a.groupName + index == i)
|
|
28
|
+
// 如果当前item隐藏了,则直接跳过校验
|
|
29
|
+
if (curSchema && schemaItemHidden(curSchema)) {
|
|
30
|
+
validMap.push(true)
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
const cur = formRef.value[i]
|
|
34
|
+
cur.validate((valid: boolean) => {
|
|
35
|
+
validMap.push(valid)
|
|
36
|
+
if (validMap.length >= Object.keys(formRef.value).length && callback) callback(validMap.every((a) => a))
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const submit = () => {
|
|
41
|
+
validate((valid) => {
|
|
42
|
+
if (!valid) return
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
form,
|
|
48
|
+
formRef,
|
|
49
|
+
submit,
|
|
50
|
+
schema,
|
|
51
|
+
validate,
|
|
52
|
+
loading,
|
|
53
|
+
schemaItemHidden,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Fields, FormSchema } from "easybill-ui"
|
|
2
|
+
import type { Component, VNode } from "vue"
|
|
3
|
+
export interface CurdGroupFormSchema {
|
|
4
|
+
items: CurdGroupFormSchemaItem[]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CurdGroupFormSchemaItem {
|
|
8
|
+
groupName: string
|
|
9
|
+
schema: FormSchema
|
|
10
|
+
icon?: string
|
|
11
|
+
prop?: string // 唯一标识 不可修改
|
|
12
|
+
desc?: string
|
|
13
|
+
step?: string
|
|
14
|
+
stepIndex?: number
|
|
15
|
+
class?: string
|
|
16
|
+
style?: string
|
|
17
|
+
labelPosition?: string | "left" | "right" | "top"
|
|
18
|
+
hidden?: boolean | ((form: Fields, schemaItem: CurdGroupFormSchemaItem) => boolean)
|
|
19
|
+
headerRight?: Component | ((props: { schema: CurdGroupFormSchema; schemaItem: CurdGroupFormSchemaItem; formModel: Fields }) => VNode | VNode[])
|
|
20
|
+
headerBottom?: Component | ((props: { schema: CurdGroupFormSchema; schemaItem: CurdGroupFormSchemaItem; formModel: Fields }) => VNode | VNode[])
|
|
21
|
+
headerTitle?: Component | ((props: { schema: CurdGroupFormSchema; schemaItem: CurdGroupFormSchemaItem; formModel: Fields }) => VNode | VNode[])
|
|
22
|
+
}
|
|
@@ -1,30 +1,43 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<component :is="componentName" v-bind="containerProps">
|
|
3
|
-
<el-steps v-if="
|
|
4
|
-
<el-step v-for="(
|
|
3
|
+
<el-steps v-if="isGroupMode && hasMultipleSteps && !stepComponent" :active="activeStep" align-center style="margin-bottom: 20px; position: sticky; top: 0; z-index: 2000; background: var(--el-bg-color)" v-bind="stepProps">
|
|
4
|
+
<el-step v-for="(stepItem, i) in stepList" :key="i" :title="stepItem.name" />
|
|
5
5
|
</el-steps>
|
|
6
|
+
<component v-if="stepComponent" :is="stepComponent" :step="activeStep" :steps="stepList" />
|
|
6
7
|
<div v-loading="confirmLoading" class="form-dialog-content">
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
<!-- 单表单模式 -->
|
|
9
|
+
<curd-form v-if="isFormMode" ref="curdFormRef" v-model="form" :fields="fields" :form-schema="formSchema" :extend-context="extendContexts" />
|
|
10
|
+
<!-- 分组表单模式 -->
|
|
11
|
+
<template v-else-if="isGroupMode">
|
|
12
|
+
<div v-for="(stepItem, i) in stepList" :key="i" class="form-dialog-step" v-show="activeStep === i">
|
|
13
|
+
<curd-group-form :ref="(el: any) => el && (groupFormRef[i] = el)" v-model="form" :schema="stepItem.schema" :fields="fields" :extend-context="extendContexts" />
|
|
14
|
+
</div>
|
|
9
15
|
</template>
|
|
10
16
|
</div>
|
|
11
17
|
<template #footer>
|
|
12
|
-
<
|
|
18
|
+
<component v-if="footerComponent" :is="footerComponent" />
|
|
19
|
+
<span v-else-if="handleOk" class="dialog-footer">
|
|
13
20
|
<el-button v-if="cancelBtnTextLabel" :disabled="confirmLoading" type="default" @click="onCancel()">{{ cancelBtnTextLabel }}</el-button>
|
|
14
|
-
<el-button v-if="
|
|
15
|
-
<el-button v-if="
|
|
16
|
-
<el-button v-if="
|
|
21
|
+
<el-button v-if="isGroupMode && hasMultipleSteps && canPrev" :disabled="confirmLoading" type="primary" plain @click="prev()">{{ t("el.formDialog.preStep") }}</el-button>
|
|
22
|
+
<el-button v-if="isGroupMode && hasMultipleSteps && canNext" :disabled="confirmLoading" type="primary" plain @click="next()">{{ t("el.formDialog.nextStep") }}</el-button>
|
|
23
|
+
<el-button v-if="!isGroupMode || isLastStep" :disabled="confirmLoading" type="primary" :loading="confirmLoading" @click="onOk">{{ confirmBtnTextLabel }}</el-button>
|
|
17
24
|
</span>
|
|
18
25
|
</template>
|
|
19
26
|
</component>
|
|
20
27
|
</template>
|
|
21
28
|
|
|
22
29
|
<script lang="ts">
|
|
23
|
-
import { useLocale } from "easybill-ui"
|
|
24
|
-
import { computed, defineComponent, type PropType, provide, reactive, ref, type Ref,
|
|
30
|
+
import { CurdForm, CurdGroupForm, type CurdGroupFormSchema, useLocale } from "easybill-ui"
|
|
31
|
+
import { computed, defineComponent, type PropType, provide, reactive, ref, type Ref, toRefs, useAttrs } from "vue"
|
|
25
32
|
import { type Fields, type FormSchema } from "../../CurdForm"
|
|
33
|
+
import { useStepList } from "./hooks/useStepList"
|
|
34
|
+
import { useStepNavigation } from "./hooks/useStepNavigation"
|
|
26
35
|
export default defineComponent({
|
|
27
36
|
name: "FormDialog",
|
|
37
|
+
components: {
|
|
38
|
+
CurdForm,
|
|
39
|
+
CurdGroupForm,
|
|
40
|
+
},
|
|
28
41
|
props: {
|
|
29
42
|
title: {
|
|
30
43
|
type: String,
|
|
@@ -35,14 +48,15 @@ export default defineComponent({
|
|
|
35
48
|
type: Function as PropType<() => void>,
|
|
36
49
|
default: null,
|
|
37
50
|
},
|
|
38
|
-
stepSchema: {
|
|
39
|
-
type: Array as PropType<{ name: string; description?: string; formSchema: FormSchema }[]>,
|
|
40
|
-
default: () => [],
|
|
41
|
-
},
|
|
42
51
|
formSchema: {
|
|
43
52
|
// 表单项
|
|
44
53
|
type: Object as PropType<FormSchema>,
|
|
45
|
-
default: () =>
|
|
54
|
+
default: () => null,
|
|
55
|
+
},
|
|
56
|
+
groupSchema: {
|
|
57
|
+
// 分组表单项
|
|
58
|
+
type: Object as PropType<CurdGroupFormSchema>,
|
|
59
|
+
default: () => null,
|
|
46
60
|
},
|
|
47
61
|
fields: {
|
|
48
62
|
// 默认值,一般编辑时传入
|
|
@@ -84,45 +98,66 @@ export default defineComponent({
|
|
|
84
98
|
type: String as PropType<"dialog" | "drawer">,
|
|
85
99
|
default: "dialog",
|
|
86
100
|
},
|
|
101
|
+
stepComponent: {
|
|
102
|
+
type: Object,
|
|
103
|
+
default: null,
|
|
104
|
+
},
|
|
105
|
+
footerComponent: {
|
|
106
|
+
type: Object,
|
|
107
|
+
default: null,
|
|
108
|
+
},
|
|
87
109
|
},
|
|
88
110
|
setup(props) {
|
|
89
111
|
const attrs = useAttrs()
|
|
90
112
|
const curdFormRef = ref()
|
|
91
113
|
const form = ref<Fields>({})
|
|
92
114
|
const state = reactive({
|
|
93
|
-
step: 0,
|
|
94
115
|
visible: true,
|
|
95
116
|
confirmLoading: false,
|
|
96
117
|
})
|
|
97
118
|
const { t } = useLocale()
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (valid) state.step++
|
|
110
|
-
})
|
|
111
|
-
}
|
|
112
|
-
}
|
|
119
|
+
|
|
120
|
+
// 模式判断
|
|
121
|
+
const isGroupMode = computed(() => !!props.groupSchema)
|
|
122
|
+
const isFormMode = computed(() => !!props.formSchema && !isGroupMode.value)
|
|
123
|
+
|
|
124
|
+
// 步骤列表
|
|
125
|
+
const { stepList, stepCount, hasMultipleSteps } = useStepList(computed(() => props.groupSchema))
|
|
126
|
+
|
|
127
|
+
// 步骤导航
|
|
128
|
+
const { activeStep, groupFormRef, canPrev, canNext, isLastStep, prev, next, validateCurrentStep } = useStepNavigation(stepCount)
|
|
129
|
+
|
|
113
130
|
const onOk = () => {
|
|
114
131
|
state.confirmLoading = true
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
132
|
+
|
|
133
|
+
const getValidatePromise = (): Promise<boolean> => {
|
|
134
|
+
if (isFormMode.value) {
|
|
135
|
+
return (
|
|
136
|
+
curdFormRef.value
|
|
137
|
+
?.validate()
|
|
138
|
+
.then(() => true)
|
|
139
|
+
.catch(() => false) ?? Promise.resolve(true)
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return new Promise<boolean>((resolve) => {
|
|
144
|
+
validateCurrentStep((valid) => resolve(valid))
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
getValidatePromise()
|
|
149
|
+
.then(async (valid) => {
|
|
150
|
+
if (!valid) {
|
|
151
|
+
state.confirmLoading = false
|
|
152
|
+
return
|
|
153
|
+
}
|
|
118
154
|
const pass = await (props.handleOk && props.handleOk(form.value, state)).finally(() => (state.confirmLoading = false))
|
|
119
155
|
if (typeof pass == "undefined" || pass) {
|
|
120
156
|
state.visible = false
|
|
121
157
|
onCancel()
|
|
122
158
|
}
|
|
123
159
|
})
|
|
124
|
-
.catch((
|
|
125
|
-
console.error(e)
|
|
160
|
+
.catch(() => {
|
|
126
161
|
state.confirmLoading = false
|
|
127
162
|
})
|
|
128
163
|
}
|
|
@@ -141,16 +176,12 @@ export default defineComponent({
|
|
|
141
176
|
}, 300)
|
|
142
177
|
}
|
|
143
178
|
const loadOptions = (prop: string, config?: unknown) => {
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (item.prop == prop) {
|
|
151
|
-
curdFormRef.value[i]?.loadOptions(prop, config)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
179
|
+
if (isFormMode.value) {
|
|
180
|
+
curdFormRef.value?.loadOptions(prop, config)
|
|
181
|
+
} else if (isGroupMode.value) {
|
|
182
|
+
Object.values(groupFormRef.value).forEach((form) => {
|
|
183
|
+
form?.loadOptions(prop, config)
|
|
184
|
+
})
|
|
154
185
|
}
|
|
155
186
|
}
|
|
156
187
|
const extendContexts = {
|
|
@@ -186,11 +217,31 @@ export default defineComponent({
|
|
|
186
217
|
const formDialogContext = reactive({
|
|
187
218
|
form,
|
|
188
219
|
state,
|
|
220
|
+
stepList,
|
|
221
|
+
activeStep,
|
|
189
222
|
loadOptions,
|
|
190
223
|
prev,
|
|
191
224
|
next,
|
|
225
|
+
goTo: (step: number) => {
|
|
226
|
+
if (step >= 0 && step < stepCount.value) {
|
|
227
|
+
// 验证当前步骤
|
|
228
|
+
validateCurrentStep((valid) => {
|
|
229
|
+
if (valid) {
|
|
230
|
+
activeStep.value = step
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
},
|
|
192
235
|
close: onBeforeClose,
|
|
193
236
|
handleOk: onOk,
|
|
237
|
+
validateCurrentStep,
|
|
238
|
+
canPrev,
|
|
239
|
+
canNext,
|
|
240
|
+
isLastStep,
|
|
241
|
+
isGroupMode,
|
|
242
|
+
isFormMode,
|
|
243
|
+
confirmBtnTextLabel,
|
|
244
|
+
cancelBtnTextLabel,
|
|
194
245
|
})
|
|
195
246
|
provide("formDialogContext", formDialogContext)
|
|
196
247
|
return {
|
|
@@ -199,7 +250,7 @@ export default defineComponent({
|
|
|
199
250
|
onCancel,
|
|
200
251
|
onBeforeClose,
|
|
201
252
|
curdFormRef,
|
|
202
|
-
|
|
253
|
+
stepList,
|
|
203
254
|
form,
|
|
204
255
|
prev,
|
|
205
256
|
next,
|
|
@@ -209,6 +260,15 @@ export default defineComponent({
|
|
|
209
260
|
cancelBtnTextLabel,
|
|
210
261
|
componentName,
|
|
211
262
|
containerProps,
|
|
263
|
+
isGroupMode,
|
|
264
|
+
isFormMode,
|
|
265
|
+
hasMultipleSteps,
|
|
266
|
+
activeStep,
|
|
267
|
+
groupFormRef,
|
|
268
|
+
canPrev,
|
|
269
|
+
canNext,
|
|
270
|
+
isLastStep,
|
|
271
|
+
validateCurrentStep,
|
|
212
272
|
}
|
|
213
273
|
},
|
|
214
274
|
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { computed, type ComputedRef } from "vue"
|
|
2
|
+
import type { CurdGroupFormSchema } from "../../../CurdGroupForm/types"
|
|
3
|
+
|
|
4
|
+
export interface StepItem {
|
|
5
|
+
name: string
|
|
6
|
+
stepIndex?: number
|
|
7
|
+
schema: {
|
|
8
|
+
items: CurdGroupFormSchema["items"]
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useStepList(groupSchema: ComputedRef<CurdGroupFormSchema | null>) {
|
|
13
|
+
const stepList = computed<StepItem[]>(() => {
|
|
14
|
+
if (!groupSchema.value?.items) return []
|
|
15
|
+
|
|
16
|
+
const groups = new Map<string, StepItem>()
|
|
17
|
+
|
|
18
|
+
groupSchema.value.items.forEach((item) => {
|
|
19
|
+
const stepName = item.step || "其他"
|
|
20
|
+
|
|
21
|
+
if (!groups.has(stepName)) {
|
|
22
|
+
groups.set(stepName, {
|
|
23
|
+
name: stepName,
|
|
24
|
+
stepIndex: item.stepIndex,
|
|
25
|
+
schema: { items: [] }
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
groups.get(stepName)!.schema.items.push(item)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// 按 stepIndex 排序(undefined 排最后)
|
|
33
|
+
return Array.from(groups.values()).sort((a, b) => {
|
|
34
|
+
const indexA = a.stepIndex ?? Infinity
|
|
35
|
+
const indexB = b.stepIndex ?? Infinity
|
|
36
|
+
return indexA - indexB
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const stepCount = computed(() => stepList.value.length)
|
|
41
|
+
|
|
42
|
+
const hasMultipleSteps = computed(() => stepList.value.length > 1)
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
stepList,
|
|
46
|
+
stepCount,
|
|
47
|
+
hasMultipleSteps
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { computed, ref, type Ref } from "vue"
|
|
2
|
+
import type { CurdGroupForm } from "../../../CurdGroupForm"
|
|
3
|
+
|
|
4
|
+
interface ValidateFunction {
|
|
5
|
+
validate: (callback: (valid: boolean) => void) => void
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function useStepNavigation(stepCount: Ref<number>, onStepChange?: (step: number) => void) {
|
|
9
|
+
const activeStep = ref(0)
|
|
10
|
+
const groupFormRef = ref<Record<number, InstanceType<typeof CurdGroupForm>>>({})
|
|
11
|
+
|
|
12
|
+
const canPrev = computed(() => activeStep.value > 0)
|
|
13
|
+
const canNext = computed(() => activeStep.value < stepCount.value - 1)
|
|
14
|
+
const isLastStep = computed(() => activeStep.value >= stepCount.value - 1)
|
|
15
|
+
|
|
16
|
+
const setStep = (newStep: number) => {
|
|
17
|
+
if (newStep >= 0 && newStep < stepCount.value) {
|
|
18
|
+
activeStep.value = newStep
|
|
19
|
+
onStepChange?.(newStep)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const prev = () => {
|
|
24
|
+
if (canPrev.value) {
|
|
25
|
+
setStep(activeStep.value - 1)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const next = (onValidateFail?: () => void) => {
|
|
30
|
+
if (!canNext.value) return false
|
|
31
|
+
|
|
32
|
+
const currentForm = groupFormRef.value[activeStep.value] as unknown as ValidateFunction | undefined
|
|
33
|
+
if (currentForm?.validate) {
|
|
34
|
+
currentForm.validate((valid: boolean) => {
|
|
35
|
+
if (valid) {
|
|
36
|
+
setStep(activeStep.value + 1)
|
|
37
|
+
} else if (onValidateFail) {
|
|
38
|
+
onValidateFail()
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
} else {
|
|
42
|
+
setStep(activeStep.value + 1)
|
|
43
|
+
}
|
|
44
|
+
return true
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const validateCurrentStep = (callback: (valid: boolean) => void) => {
|
|
48
|
+
const currentForm = groupFormRef.value[activeStep.value] as unknown as ValidateFunction | undefined
|
|
49
|
+
if (currentForm?.validate) {
|
|
50
|
+
currentForm.validate(callback)
|
|
51
|
+
} else {
|
|
52
|
+
callback(true)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const validateCurrentStepAsync = (): Promise<boolean> => {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
validateCurrentStep((valid) => resolve(valid))
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
activeStep,
|
|
64
|
+
groupFormRef,
|
|
65
|
+
canPrev,
|
|
66
|
+
canNext,
|
|
67
|
+
isLastStep,
|
|
68
|
+
setStep,
|
|
69
|
+
prev,
|
|
70
|
+
next,
|
|
71
|
+
validateCurrentStep,
|
|
72
|
+
validateCurrentStepAsync,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import type { CurdGroupFormSchema } from "../../CurdGroupForm/types"
|
|
1
2
|
import type { DialogProps, StepProps } from "element-plus"
|
|
2
3
|
import type { Fields, FormContext, FormSchema } from "../../CurdForm"
|
|
4
|
+
import type { StepItem } from "./hooks/useStepList"
|
|
3
5
|
|
|
4
6
|
export interface FormDialogOptions extends Partial<DialogProps> {
|
|
5
7
|
title?: string
|
|
6
8
|
width?: string | number
|
|
7
9
|
fields?: Fields
|
|
8
|
-
stepSchema?: { name: string; description?: string; formSchema: FormSchema }[]
|
|
9
10
|
formSchema?: FormSchema
|
|
11
|
+
groupSchema?: CurdGroupFormSchema
|
|
10
12
|
handleOk?: (modelRef: Fields) => Promise<void>
|
|
11
13
|
handleClose?: (e: "close" | "cancel") => void
|
|
12
14
|
setForm?: (form: Fields) => void
|
|
@@ -14,4 +16,30 @@ export interface FormDialogOptions extends Partial<DialogProps> {
|
|
|
14
16
|
extendContext?: Partial<FormContext>
|
|
15
17
|
confirmBtnText?: string
|
|
16
18
|
cancelBtnText?: string
|
|
19
|
+
stepComponent?: object
|
|
20
|
+
footerComponent?: object
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FormDialogContext {
|
|
24
|
+
form: Fields
|
|
25
|
+
state: {
|
|
26
|
+
visible: boolean
|
|
27
|
+
confirmLoading: boolean
|
|
28
|
+
}
|
|
29
|
+
stepList: StepItem[]
|
|
30
|
+
activeStep: { value: number }
|
|
31
|
+
loadOptions: (prop: string, config?: unknown) => void
|
|
32
|
+
prev: () => void
|
|
33
|
+
next: () => void
|
|
34
|
+
goTo: (step: number) => void
|
|
35
|
+
close: () => void
|
|
36
|
+
handleOk: () => void
|
|
37
|
+
validateCurrentStep: (callback: (valid: boolean) => void) => void
|
|
38
|
+
canPrev: { value: boolean }
|
|
39
|
+
canNext: { value: boolean }
|
|
40
|
+
isLastStep: { value: boolean }
|
|
41
|
+
isGroupMode: { value: boolean }
|
|
42
|
+
isFormMode: { value: boolean }
|
|
43
|
+
confirmBtnTextLabel: { value: string }
|
|
44
|
+
cancelBtnTextLabel: { value: string }
|
|
17
45
|
}
|
package/index.ts
CHANGED
|
@@ -2,21 +2,24 @@ import type { Plugin } from "vue"
|
|
|
2
2
|
import ConstantStatus from "./components/ConstantStatus"
|
|
3
3
|
import CurdForm from "./components/CurdForm"
|
|
4
4
|
import CurdFormItem from "./components/CurdForm/src/CurdFormItem.vue"
|
|
5
|
+
import { CurdGroupForm } from "./components/CurdGroupForm"
|
|
5
6
|
import CurdTable from "./components/CurdTable"
|
|
6
7
|
import DetailInfo from "./components/DetailInfo"
|
|
7
8
|
import FormDialog from "./components/FormDialog"
|
|
8
9
|
import TableFilter from "./components/TableFilter"
|
|
9
10
|
import { makeInstaller } from "./utils/vue/make-installer"
|
|
10
11
|
|
|
11
|
-
const Components = [ConstantStatus, CurdForm, TableFilter, DetailInfo, CurdFormItem, CurdTable, FormDialog] as Plugin[]
|
|
12
|
+
const Components = [ConstantStatus, CurdForm, TableFilter, DetailInfo, CurdFormItem, CurdTable, FormDialog, CurdGroupForm] as Plugin[]
|
|
12
13
|
export * from "./components/ConstantStatus/src/types"
|
|
13
14
|
export * from "./components/CurdForm"
|
|
15
|
+
export * from "./components/CurdGroupForm/types"
|
|
14
16
|
export * from "./components/CurdTable/src/types"
|
|
15
17
|
export * from "./components/DetailInfo/src/types"
|
|
16
18
|
export * from "./components/FormDialog/src/types"
|
|
17
19
|
export * from "./components/TableFilter/types"
|
|
18
20
|
export * from "./utils/hooks/useGlobalConfig"
|
|
19
21
|
export * from "./utils/hooks/useLocal"
|
|
20
|
-
|
|
22
|
+
|
|
23
|
+
export { ConstantStatus, CurdForm, CurdFormItem, CurdGroupForm, CurdTable, DetailInfo, FormDialog, TableFilter }
|
|
21
24
|
|
|
22
25
|
export default makeInstaller([...Components])
|