diff --git a/packages/element/docs/demos/guide/form-collapse/json-schema.vue b/packages/element/docs/demos/guide/form-collapse/json-schema.vue new file mode 100644 index 00000000000..c49463ca9ba --- /dev/null +++ b/packages/element/docs/demos/guide/form-collapse/json-schema.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/packages/element/docs/demos/guide/form-collapse/markup-schema.vue b/packages/element/docs/demos/guide/form-collapse/markup-schema.vue new file mode 100644 index 00000000000..5454a3bfe9e --- /dev/null +++ b/packages/element/docs/demos/guide/form-collapse/markup-schema.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/packages/element/docs/guide/form-collapse.md b/packages/element/docs/guide/form-collapse.md new file mode 100644 index 00000000000..e4e837f8587 --- /dev/null +++ b/packages/element/docs/guide/form-collapse.md @@ -0,0 +1,53 @@ +# FormCollapse + +> 折叠面板,通常用在布局空间要求较高的表单场景 +> +> 注意:只能用在 Schema 场景 + +## Markup Schema 案例 + + + +## JSON Schema 案例 + + + +## API + +### FormCollapse + +| 属性名 | 类型 | 描述 | 默认值 | +| ------------ | ------------- | ---------------------------------------------------------- | ------ | +| formCollapse | IFormCollapse | 传入通过 createFormCollapse/useFormCollapse 创建出来的模型 | | + +其余参考 [https://element.eleme.io/#/zh-CN/component/collapse](https://element.eleme.io/#/zh-CN/component/collapse) + +### FormCollapse.Item + +参考 [https://element.eleme.io/#/zh-CN/component/collapse](https://element.eleme.io/#/zh-CN/component/collapse) + +### FormCollapse.createFormCollapse + +```ts pure +type ActiveKey = string | number +type ActiveKeys = string | number | Array + +interface createFormCollapse { + (defaultActiveKeys?: ActiveKeys): IFormCollpase +} + +interface IFormCollapse { + //激活主键列表 + activeKeys: ActiveKeys + //是否存在该激活主键 + hasActiveKey(key: ActiveKey): boolean + //设置激活主键列表 + setActiveKeys(keys: ActiveKeys): void + //添加激活主键 + addActiveKey(key: ActiveKey): void + //删除激活主键 + removeActiveKey(key: ActiveKey): void + //开关切换激活主键 + toggleActiveKey(key: ActiveKey): void +} +``` diff --git a/packages/element/src/__builtins__/index.ts b/packages/element/src/__builtins__/index.ts new file mode 100644 index 00000000000..8bf1dec9f69 --- /dev/null +++ b/packages/element/src/__builtins__/index.ts @@ -0,0 +1,2 @@ +export * from './configs' +export * from './shared' diff --git a/packages/element/src/array-base/index.ts b/packages/element/src/array-base/index.ts index 0f174c0b6ad..305209003cc 100644 --- a/packages/element/src/array-base/index.ts +++ b/packages/element/src/array-base/index.ts @@ -201,10 +201,12 @@ const ArrayBaseIndex = defineComponent({ name: 'ArrayBaseIndex', setup(props, { attrs }) { const index = useIndex() + const prefixCls = `${stylePrefix}-array-base` return () => { return h( 'span', { + class: `${prefixCls}-index`, attrs, }, { diff --git a/packages/element/src/array-collapse/style.scss b/packages/element/src/array-collapse/style.scss index bd46b6a6a0f..6bdd5b35c30 100644 --- a/packages/element/src/array-collapse/style.scss +++ b/packages/element/src/array-collapse/style.scss @@ -17,6 +17,7 @@ $array-table-prefix-cls: '#{$formily-prefix}-array-collapse'; .#{$array-table-prefix-cls}-errors-badge { line-height: 1; + vertical-align: initial; } .#{$formily-prefix}-array-base-addition { diff --git a/packages/element/src/array-table/index.ts b/packages/element/src/array-table/index.ts index dead4f33ea5..510b4a5b2bc 100644 --- a/packages/element/src/array-table/index.ts +++ b/packages/element/src/array-table/index.ts @@ -230,7 +230,7 @@ const StatusSelect = observer( options: Array, pageSize: Number, }, - setup(props, { attrs }) { + setup(props) { const formRef = useForm() const fieldRef = useField() const prefixCls = `${stylePrefix}-array-table` diff --git a/packages/element/src/array-tabs/style.scss b/packages/element/src/array-tabs/style.scss index 97efae5fe2c..41162cc285e 100644 --- a/packages/element/src/array-tabs/style.scss +++ b/packages/element/src/array-tabs/style.scss @@ -11,5 +11,6 @@ $array-table-prefix-cls: '#{$formily-prefix}-array-tabs'; .#{$array-table-prefix-cls}-errors-badge { line-height: 1; + vertical-align: initial; } } diff --git a/packages/element/src/form-collapse/index.ts b/packages/element/src/form-collapse/index.ts new file mode 100644 index 00000000000..240bf08df82 --- /dev/null +++ b/packages/element/src/form-collapse/index.ts @@ -0,0 +1,207 @@ +import { Collapse, CollapseItem, Badge } from 'element-ui' +import { model } from '@formily/reactive' +import type { + Collapse as CollapseProps, + CollapseItem as CollapseItemProps, +} from 'element-ui' +import { + useField, + useFieldSchema, + RecursionField, + h, + Fragment, +} from '@formily/vue' +import { observer } from '@formily/reactive-vue' +import { Schema, SchemaKey } from '@formily/json-schema' +import { composeExport, stylePrefix } from '../__builtins__' +import { toArr } from '@formily/shared' +import { computed, defineComponent, PropType } from 'vue-demi' +import { GeneralField } from '@formily/core' + +type ActiveKeys = string | number | Array + +type ActiveKey = string | number + +type Panels = { name: SchemaKey; props: any; schema: Schema }[] + +export interface IFormCollapse { + activeKeys: ActiveKeys + hasActiveKey(key: ActiveKey): boolean + setActiveKeys(key: ActiveKeys): void + addActiveKey(key: ActiveKey): void + removeActiveKey(key: ActiveKey): void + toggleActiveKey(key: ActiveKey): void +} + +export interface IFormCollapseProps extends CollapseProps { + formCollapse?: IFormCollapse + activeKey?: ActiveKey +} + +const usePanels = (collapseField: GeneralField, schema: Schema) => { + const panels: Panels = [] + schema.mapProperties((schema, name) => { + const field = collapseField.query(collapseField.address.concat(name)).take() + if (field?.display === 'none' || field?.display === 'hidden') return + if (schema['x-component']?.indexOf('FormCollapse.Item') > -1) { + panels.push({ + name, + props: { + ...schema?.['x-component-props'], + key: schema?.['x-component-props']?.key || name, + }, + schema, + }) + } + }) + return panels +} + +const createFormCollapse = (defaultActiveKeys?: ActiveKeys) => { + const formCollapse = model({ + activeKeys: defaultActiveKeys, + setActiveKeys(keys: ActiveKeys) { + formCollapse.activeKeys = keys + }, + hasActiveKey(key: ActiveKey) { + if (Array.isArray(formCollapse.activeKeys)) { + if (formCollapse.activeKeys.includes(key)) { + return true + } + } else if (formCollapse.activeKeys == key) { + return true + } + return false + }, + addActiveKey(key: ActiveKey) { + if (formCollapse.hasActiveKey(key)) return + formCollapse.activeKeys = toArr(formCollapse.activeKeys).concat(key) + }, + removeActiveKey(key: ActiveKey) { + if (Array.isArray(formCollapse.activeKeys)) { + formCollapse.activeKeys = formCollapse.activeKeys.filter( + (item) => item != key + ) + } else { + formCollapse.activeKeys = '' + } + }, + toggleActiveKey(key: ActiveKey) { + if (formCollapse.hasActiveKey(key)) { + formCollapse.removeActiveKey(key) + } else { + formCollapse.addActiveKey(key) + } + }, + }) + return formCollapse +} + +const FormCollapse = observer( + defineComponent({ + inheritAttrs: false, + props: { + formCollapse: { type: Object as PropType }, + activeKey: { + type: [String, Number], + }, + }, + setup(props, { attrs, emit }) { + const field = useField() + const schema = useFieldSchema() + const prefixCls = `${stylePrefix}-form-collapse` + const _formCollapse = computed( + () => props.formCollapse ?? createFormCollapse() + ) + + const takeActiveKeys = (panels: Panels) => { + if (props.activeKey) return props.activeKey + if (_formCollapse.value?.activeKeys) + return _formCollapse.value?.activeKeys + if (attrs.accordion) return panels[0]?.name + return panels.map((item) => item.name) + } + + const badgedHeader = (key: SchemaKey, props: any) => { + const errors = field.value.form.queryFeedbacks({ + type: 'error', + address: `${field.value.address.concat(key)}.*`, + }) + if (errors.length) { + return h( + Badge, + { + class: [`${prefixCls}-errors-badge`], + props: { + value: errors.length, + }, + }, + { default: () => props.title } + ) + } + return props.title + } + + return () => { + const panels = usePanels(field.value, schema.value) + const activeKey = takeActiveKeys(panels) + return h( + Collapse, + { + class: prefixCls, + props: { + value: activeKey, + }, + on: { + change: (key: string | string[]) => { + emit('input', key) + _formCollapse.value.setActiveKeys(key) + }, + }, + }, + { + default: () => { + return panels.map(({ props, schema, name }, index) => { + return h( + CollapseItem, + { + key: index, + props: { + ...props, + name, + }, + }, + { + default: () => [ + h(RecursionField, { props: { schema, name } }, {}), + h( + 'span', + { slot: 'title' }, + { default: () => badgedHeader(name, props) } + ), + ], + } + ) + }) + }, + } + ) + } + }, + }) +) + +export const FormCollapseItem = defineComponent({ + name: 'FFormCollapseItem', + setup(_props, { slots }) { + return () => h(Fragment, {}, slots) + }, +}) + +const composeFormCollapse = composeExport(FormCollapse, { + Item: FormCollapseItem, + createFormCollapse, +}) + +export { composeFormCollapse as FormCollapse } +export default composeFormCollapse diff --git a/packages/element/src/form-collapse/style.scss b/packages/element/src/form-collapse/style.scss new file mode 100644 index 00000000000..e9368b76bf8 --- /dev/null +++ b/packages/element/src/form-collapse/style.scss @@ -0,0 +1,6 @@ +@import '../__builtins__/styles/common.scss'; + +.#{$formily-prefix}-form-collapse-errors-badge { + line-height: 1; + vertical-align: initial; +} diff --git a/packages/element/src/form-collapse/style.ts b/packages/element/src/form-collapse/style.ts new file mode 100644 index 00000000000..5835bf18c76 --- /dev/null +++ b/packages/element/src/form-collapse/style.ts @@ -0,0 +1,4 @@ +import './style.scss' +import 'element-ui/packages/theme-chalk/src/collapse.scss' +import 'element-ui/packages/theme-chalk/src/collapse-item.scss' +import 'element-ui/packages/theme-chalk/src/bdage.scss' diff --git a/packages/element/src/form-dialog/index.ts b/packages/element/src/form-dialog/index.ts index 44518e5defa..42745ef31a8 100644 --- a/packages/element/src/form-dialog/index.ts +++ b/packages/element/src/form-dialog/index.ts @@ -171,7 +171,6 @@ export function FormDialog( cancelText, okButtonProps, cancelButtonProps, - loadingText, ...dialogProps } = this.dialogProps diff --git a/packages/element/src/form-item/index.ts b/packages/element/src/form-item/index.ts index 98b7f7e3823..d717ec4b652 100644 --- a/packages/element/src/form-item/index.ts +++ b/packages/element/src/form-item/index.ts @@ -5,10 +5,11 @@ import { Ref, onBeforeUnmount, watch, + provide, } from '@vue/composition-api' import { isVoidField } from '@formily/core' import { connect, mapProps, h } from '@formily/vue' -import { useFormLayout } from '../form-layout' +import { useFormLayout, FormLayoutShallowContext } from '../form-layout' import { composeExport, resolveComponent } from '../__builtins__/shared' import { stylePrefix } from '../__builtins__/configs' import { Component } from 'vue' @@ -137,7 +138,7 @@ export const FormBaseItem = defineComponent({ }, setup(props, { slots, attrs, refs }) { const active = ref(false) - const deepLayout = useFormLayout() + const deepLayoutRef = useFormLayout() const prefixCls = `${stylePrefix}-form-item` @@ -148,7 +149,10 @@ export const FormBaseItem = defineComponent({ containerRef.value = refs.labelContainer }) + provide(FormLayoutShallowContext, ref(null)) + return () => { + const deepLayout = deepLayoutRef.value const { label, colon = deepLayout.colon ?? true, diff --git a/packages/element/src/form-layout/index.ts b/packages/element/src/form-layout/index.ts index 575b3735f79..86d8f64cf6c 100644 --- a/packages/element/src/form-layout/index.ts +++ b/packages/element/src/form-layout/index.ts @@ -3,6 +3,10 @@ import { inject, InjectionKey, defineComponent, + Ref, + ref, + watch, + computed, } from '@vue/composition-api' import { h } from '@formily/vue' import { stylePrefix } from '../__builtins__/configs' @@ -29,22 +33,24 @@ export type FormLayoutProps = { inset?: boolean } -export const FormLayoutDeepContext: InjectionKey = Symbol( +export const FormLayoutDeepContext: InjectionKey> = Symbol( 'FormLayoutDeepContext' ) -export const FormLayoutShallowContext: InjectionKey = Symbol( - 'FormLayoutShallowContext' -) +export const FormLayoutShallowContext: InjectionKey> = + Symbol('FormLayoutShallowContext') -export const useFormDeepLayout = () => inject(FormLayoutDeepContext, null) +export const useFormDeepLayout = () => inject(FormLayoutDeepContext, ref(null)) -export const useFormShallowLayout = () => inject(FormLayoutShallowContext, null) +export const useFormShallowLayout = () => + inject(FormLayoutShallowContext, ref(null)) -export const useFormLayout = () => ({ - ...useFormDeepLayout(), - ...useFormShallowLayout(), -}) +export const useFormLayout = () => { + return ref({ + ...useFormDeepLayout().value, + ...useFormShallowLayout().value, + }) +} export const FormLayout = defineComponent({ name: 'FFormLayout', @@ -69,25 +75,32 @@ export const FormLayout = defineComponent({ bordered: { default: true }, inset: { default: false }, }, - setup(props, { slots, attrs }) { + setup(props, { slots }) { const deepLayout = useFormDeepLayout() - - const newDeepLayout = { + const newDeepLayout = ref({ ...deepLayout, - } - if (!props.shallow) { - Object.assign(newDeepLayout, props) - } else { - if (props.size) { - newDeepLayout.size = props.size - } - if (props.colon) { - newDeepLayout.colon = props.colon - } - } + }) + const shallowProps = computed(() => (props.shallow ? props : undefined)) + + watch( + [props, deepLayout], + () => { + if (!props.shallow) { + Object.assign(newDeepLayout.value, props) + } else { + if (props.size) { + newDeepLayout.value.size = props.size + } + if (props.colon) { + newDeepLayout.value.colon = props.colon + } + } + }, + { deep: true, immediate: true } + ) provide(FormLayoutDeepContext, newDeepLayout) - provide(FormLayoutShallowContext, props.shallow ? props : undefined) + provide(FormLayoutShallowContext, shallowProps) const formPrefixCls = `${stylePrefix}-form` return () => { diff --git a/packages/element/src/form-tab/style.scss b/packages/element/src/form-tab/style.scss index 57c55941fa0..c3c93cfe37e 100644 --- a/packages/element/src/form-tab/style.scss +++ b/packages/element/src/form-tab/style.scss @@ -2,4 +2,5 @@ .#{$formily-prefix}-form-tab-errors-badge { line-height: 1; + vertical-align: initial; } diff --git a/packages/element/src/index.ts b/packages/element/src/index.ts index 3a14eef44be..46d20acad2a 100644 --- a/packages/element/src/index.ts +++ b/packages/element/src/index.ts @@ -25,6 +25,7 @@ export * from './time-picker' export * from './transfer' export * from './upload' export * from './preview-text' +export * from './form-collapse' export * from './form-tab' export * from './form-step' export * from './array-cards' diff --git a/packages/element/src/style.ts b/packages/element/src/style.ts index 98b3625453c..ee6d76db179 100644 --- a/packages/element/src/style.ts +++ b/packages/element/src/style.ts @@ -14,3 +14,4 @@ import './form-layout/style.scss' import './form-tab/style.scss' import './form/style.scss' import './space/style.scss' +import './form-collapse/style.scss' diff --git a/packages/element/transformer.ts b/packages/element/transformer.ts index ab8a563c497..45d5e129981 100644 --- a/packages/element/transformer.ts +++ b/packages/element/transformer.ts @@ -1,4 +1,3 @@ -import * as ts from 'typescript' import createTransformer from 'ts-import-plugin' const transformer = createTransformer({ @@ -8,6 +7,6 @@ const transformer = createTransformer({ style: false, }) -export default function (program: ts.Program, pluginOptions: {}) { +export default function () { return transformer }