@vue-ui-kit/ant 1.7.3 → 1.8.0

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.
@@ -0,0 +1,31 @@
1
+ import { PFormItemProps } from '../../declarations/antProxy';
2
+ declare const _default: <T extends Recordable = Recordable<any>>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_Prettify<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
+ props: __VLS_Prettify<Pick<Partial<{}> & Omit<{
4
+ readonly onTrigger?: ((cusFields?: string | string[] | undefined) => any) | undefined;
5
+ readonly onReset?: (() => any) | undefined;
6
+ readonly onDelayTrigger?: ((cusFields?: string | string[] | undefined, time?: number | undefined) => any) | undefined;
7
+ } & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps & Readonly<{}> & Readonly<{
8
+ onTrigger?: ((cusFields?: string | string[] | undefined) => any) | undefined;
9
+ onReset?: (() => any) | undefined;
10
+ onDelayTrigger?: ((cusFields?: string | string[] | undefined, time?: number | undefined) => any) | undefined;
11
+ }>, never>, "onReset" | "onTrigger" | "onDelayTrigger"> & {
12
+ item: PFormItemProps<T>;
13
+ formData: T;
14
+ }> & import('vue').PublicProps;
15
+ expose(exposed: import('vue').ShallowUnwrapRef<{}>): void;
16
+ attrs: any;
17
+ slots: ReturnType<() => {}>;
18
+ emit: {
19
+ (e: "delayTrigger", cusFields?: string | string[], time?: number): void;
20
+ (e: "trigger", cusFields?: string | string[]): void;
21
+ (e: "reset"): void;
22
+ } & {};
23
+ }>) => import('vue').VNode<import('vue').RendererNode, import('vue').RendererElement, {
24
+ [key: string]: any;
25
+ }> & {
26
+ __ctx?: Awaited<typeof __VLS_setup>;
27
+ };
28
+ export default _default;
29
+ type __VLS_Prettify<T> = {
30
+ [K in keyof T]: T[K];
31
+ } & {};
@@ -10,8 +10,8 @@ declare const _default: <F = Recordable<any>>(__VLS_props: NonNullable<Awaited<t
10
10
  expose(exposed: import('vue').ShallowUnwrapRef<{
11
11
  activeKey: import('vue').ComputedRef<number>;
12
12
  setActiveKey: (key: number) => void;
13
- validateAll: () => Promise<any[]>;
14
- validate: (__index: number) => any;
13
+ validateAll: () => Promise<PromiseSettledResult<any>[]>;
14
+ validate: (__index: number) => Promise<void>;
15
15
  }>): void;
16
16
  attrs: any;
17
17
  slots: ReturnType<() => {
@@ -39,16 +39,17 @@ declare const _default: <D = Recordable<any>, F = Recordable<any>>(__VLS_props:
39
39
  }>, never>, "onPick" | "onToolbarButtonClick" | "onToolbarToolClick" | "onResetQuery"> & PGridProps<D, F>> & import('vue').PublicProps;
40
40
  expose(exposed: import('vue').ShallowUnwrapRef<{
41
41
  commitProxy: {
42
- query: import('lodash').DebouncedFunc<() => Promise<D[]>>;
42
+ query: (this: any, ...args: any[]) => any;
43
43
  reload: () => void;
44
44
  reloadPage: () => Promise<D[]>;
45
- passQuery: (params: Partial<F>, lazy?: boolean) => Promise<void> | Promise<D[]> | undefined;
45
+ passQuery: (params: Partial<F>, lazy?: boolean) => any;
46
46
  };
47
47
  $table: import('vue').ComputedRef<any>;
48
48
  selectedRowKeys: import('vue').ComputedRef<(string | number)[]>;
49
49
  setBtnLoading: (code: string, value: boolean) => void;
50
50
  selectedRecords: import('vue').ComputedRef<D[]>;
51
51
  $form: import('vue').ComputedRef<any>;
52
+ getFormData: () => Partial<F>;
52
53
  setLoadings: (value: boolean | Record<string, boolean>) => void;
53
54
  resizeTable: () => void;
54
55
  }>): void;
@@ -5,7 +5,7 @@ declare const _default: import('vue').DefineSetupFnComponent<{
5
5
  defaultHandler: {
6
6
  [key: string]: (...args: any[]) => any;
7
7
  };
8
- }, "trigger"[], {}, {
8
+ }, ("trigger" | "delayTrigger")[], {}, {
9
9
  itemRender: ItemRender;
10
10
  renderFormParams: RenderFormParams<Recordable<any>>;
11
11
  defaultHandler: {
@@ -13,5 +13,6 @@ declare const _default: import('vue').DefineSetupFnComponent<{
13
13
  };
14
14
  } & {
15
15
  onTrigger?: ((...args: any[]) => any) | undefined;
16
+ onDelayTrigger?: ((...args: any[]) => any) | undefined;
16
17
  }, import('vue').PublicProps>;
17
18
  export default _default;
@@ -2,9 +2,11 @@ declare const _default: import('vue').DefineSetupFnComponent<{
2
2
  formData: any;
3
3
  item: any;
4
4
  passTrigger: any;
5
+ passDelayTrigger: any;
5
6
  }, {}, {}, {
6
7
  formData: any;
7
8
  item: any;
8
9
  passTrigger: any;
10
+ passDelayTrigger: any;
9
11
  } & {}, import('vue').PublicProps>;
10
12
  export default _default;
@@ -5,4 +5,4 @@ export declare const defaultItemResponsive: Responsive;
5
5
  export declare const defaultLabelCol: Responsive;
6
6
  export declare const labelColDict: Record<number, Responsive>;
7
7
  export declare const get24rest: (exist: number[]) => number;
8
- export declare const getButtonResponsive: (itemResponsive: number | Responsive[]) => import('lodash').Dictionary<number>;
8
+ export declare const getButtonResponsive: (itemResponsive: number | Responsive[]) => any;
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- @charset "UTF-8";:root{--p-theme-bg: #fff}.p-pane{background-color:var(--p-theme-bg);padding:16px;border-radius:4px}.w-full{width:100%}.cursor-pointer{cursor:pointer}.py-4x{padding-top:4px;padding-bottom:4px}.px-2x{padding-left:2px;padding-right:2px}.pl-4x{padding-left:1em}.pl-8{padding-left:2em}.h-0{height:0}.h-full{height:100%}.flex{display:flex}.flex-col{flex-direction:column}.flex-1{flex:1}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-4px{gap:4px}.gap-8px{gap:8px}.pt-8px{padding-top:8px}.px-16px{padding-left:16px;padding-right:16px}.mt-4px{margin-top:4px}.text-right{text-align:right}.overflow-y-auto{overflow-y:auto}.p-theme-bg{background-color:var(--p-theme-bg)}.p-wrapper .p-form-wrapper{z-index:4}.p-wrapper .p-toolbar-wrapper{box-shadow:0 8px #fff;border-radius:.5em .5em 0 0;margin-bottom:-.5em}.p-wrapper .ant-form .ant-form-item{margin-bottom:0}.p-wrapper .ant-form .ant-form-item.p-required .ant-form-item-label>label:before{display:inline-block;margin-inline-end:4px;color:#ff4d4f;font-size:14px;font-family:SimSun,sans-serif;line-height:1;content:"*"}.p-wrapper .ant-form .ant-form-item .ant-form-item-label{display:flex;align-items:start;justify-content:flex-end}.p-wrapper .ant-form .ant-form-item .ant-form-item-label>label{white-space:break-spaces;align-items:flex-start;height:fit-content;padding-top:.36em}.p-wrapper .ant-form .ant-form-item .ant-form-item-label>label:after{padding-left:4px;margin-inline-start:unset}.p-wrapper .ant-form.ant-form-vertical .ant-form-item .ant-form-item-label{justify-content:flex-start;font-weight:600}.p-wrapper .ant-table-sticky-scroll{visibility:hidden}.p-wrapper .ant-table-header.ant-table-sticky-holder:before{content:"";width:100%;position:absolute;height:22px;display:block;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);top:-16px}.p-wrapper .ant-table-wrapper .ant-table-pagination.ant-pagination{margin:unset;padding:16px 0}.p-wrapper .p-inner-scroll>.ant-table-wrapper{height:100%}.p-wrapper .p-inner-scroll>.ant-table-wrapper>.ant-spin-nested-loading{height:100%}.p-wrapper .p-inner-scroll>.ant-table-wrapper>.ant-spin-nested-loading>.ant-spin-container{height:100%;display:flex;flex-direction:column}.p-wrapper .p-inner-scroll>.ant-table-wrapper>.ant-spin-nested-loading>.ant-spin-container>.ant-table{border-radius:unset}.p-wrapper .ant-pagination-options .ant-select{width:100px}.p-content-align-right.ant-form-item .ant-form-item-control-input-content{display:flex;justify-content:flex-end;align-items:center;text-align:right}.p-content-align-center.ant-form-item .ant-form-item-control-input-content{display:flex;justify-content:center;align-items:center;text-align:center}
1
+ @charset "UTF-8";:root{--p-theme-bg: #fff}.p-pane{background-color:var(--p-theme-bg);padding:16px;border-radius:4px}.w-full{width:100%}.cursor-pointer{cursor:pointer}.py-4x{padding-top:4px;padding-bottom:4px}.px-2x{padding-left:2px;padding-right:2px}.pl-4x{padding-left:1em}.pl-8{padding-left:2em}.h-0{height:0}.h-full{height:100%}.flex{display:flex}.flex-col{flex-direction:column}.flex-1{flex:1}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-4px{gap:4px}.gap-8px{gap:8px}.pt-8px{padding-top:8px}.px-16px{padding-left:16px;padding-right:16px}.mt-4px{margin-top:4px}.text-right{text-align:right}.overflow-y-auto{overflow-y:auto}.p-theme-bg{background-color:var(--p-theme-bg)}.p-wrapper .p-form-wrapper{z-index:4}.p-wrapper .p-toolbar-wrapper{box-shadow:0 8px #fff;border-radius:.5em .5em 0 0;margin-bottom:-.5em}.p-wrapper .ant-form .ant-form-item{margin-bottom:0}.p-wrapper .ant-form .ant-form-item.p-required .ant-form-item-label>label:before{display:inline-block;margin-inline-end:4px;color:#ff4d4f;font-size:14px;font-family:SimSun,sans-serif;line-height:1;content:"*"}.p-wrapper .ant-form .ant-form-item .ant-form-item-label{display:flex;align-items:start;justify-content:flex-end}.p-wrapper .ant-form .ant-form-item .ant-form-item-label>label{white-space:break-spaces;align-items:flex-start;height:fit-content;padding-top:.36em}.p-wrapper .ant-form .ant-form-item .ant-form-item-label>label:after{padding-left:4px;margin-inline-start:unset}.p-wrapper .ant-form.ant-form-vertical .ant-form-item .ant-form-item-label{justify-content:flex-start;font-weight:600}.p-wrapper .ant-table-sticky-scroll{visibility:hidden}.p-wrapper .ant-table-header.ant-table-sticky-holder:before{content:"";width:100%;position:absolute;height:22px;display:block;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);top:-16px}.p-wrapper .ant-table-wrapper .ant-table-pagination.ant-pagination{margin:unset;padding:16px 0}.p-wrapper .p-inner-scroll>.ant-table-wrapper{height:100%}.p-wrapper .p-inner-scroll>.ant-table-wrapper>.ant-spin-nested-loading{height:100%}.p-wrapper .p-inner-scroll>.ant-table-wrapper>.ant-spin-nested-loading>.ant-spin-container{height:100%;display:flex;flex-direction:column}.p-wrapper .p-inner-scroll>.ant-table-wrapper>.ant-spin-nested-loading>.ant-spin-container>.ant-table{border-radius:unset}.p-wrapper .ant-pagination-options .ant-select{width:100px}.p-error-group-tab{position:relative;overflow:visible;color:#f5222d!important}.p-error-group-tab:after{content:"🏷️";position:absolute;top:-1em;right:0;width:1em;height:1em;font-size:.8em;animation:heartbeat 2s infinite}.p-content-align-right.ant-form-item .ant-form-item-control-input-content{display:flex;justify-content:flex-end;align-items:center;text-align:right}.p-content-align-center.ant-form-item .ant-form-item-control-input-content{display:flex;justify-content:center;align-items:center;text-align:center}@keyframes heartbeat{0%{transform:scale(1)}6%{transform:scale(1.22)}12%{transform:scale(1)}18%{transform:scale(1.18)}24%{transform:scale(1)}48%{transform:scale(1)}54%{transform:scale(1.22)}60%{transform:scale(1)}66%{transform:scale(1.18)}72%{transform:scale(1)}to{transform:scale(1)}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vue-ui-kit/ant",
3
- "version": "1.7.3",
3
+ "version": "1.8.0",
4
4
  "description": "Vue3 UI Kit based on Ant Design",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -39,8 +39,8 @@
39
39
  "email": "adoin@qq.com"
40
40
  },
41
41
  "dependencies": {
42
- "lodash-es": "^4.17.21",
43
- "uuid": "^10.0.0"
42
+ "uuid": "^10.0.0",
43
+ "xe-utils": "^3.7.6"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@ant-design/icons-vue": ">=7.0.0",
@@ -55,7 +55,6 @@
55
55
  "@ant-design/icons-vue": ">=7.0.0",
56
56
  "@commitlint/cli": "^19.4.1",
57
57
  "@commitlint/config-conventional": "^19.4.1",
58
- "@types/lodash-es": "^4.17.12",
59
58
  "@types/uuid": "^10.0.0",
60
59
  "@vitejs/plugin-vue": "^5.1.2",
61
60
  "@vitejs/plugin-vue-jsx": "^4.0.1",
@@ -54,6 +54,8 @@ export interface PFormItemProps<F = Recordable> {
54
54
  title?: string;
55
55
  span?: number;
56
56
  colon?: boolean;
57
+ labelCol?: ColProps
58
+ wrapperCol?: ColProps
57
59
  forceRequired?: boolean; // 在页面需要必填标示,但field不需要必填校验时使用
58
60
  align?: 'left' | 'right' | 'center';
59
61
  col?: ColProps;
@@ -65,7 +67,9 @@ export interface PFormItemProps<F = Recordable> {
65
67
  default?: (
66
68
  { data, field }: ItemFuncArg<F>,
67
69
  passTrigger?: (cusFields?: string | string[]) => void,
70
+ passDelayTrigger?: (cusFields?: string | string[], time?: number) => void
68
71
  ) => any;
72
+ defaultValue?: any
69
73
  };
70
74
  }
71
75
 
@@ -108,12 +112,13 @@ export interface PFormGroupProps<F = Recordable> {
108
112
  tabLabel?: string;
109
113
  editAble?: boolean;
110
114
  showAdd?: boolean;
115
+ lazyErrorMark?: boolean;
111
116
  forceRender?: boolean;
112
117
  /* 是否保持连续编号 */
113
118
  keepSerial?: boolean;
114
119
  loading?: boolean;
115
120
  itemMenus?: Array<GroupMenuItem<F>>;
116
- creatItem?: ({ list }: { list?: Partial<F>[] }) => Promise<Partial<F>>;
121
+ createItem?: ({ list }: { list?: Partial<F>[] }) => Promise<Partial<F>>;
117
122
  max?: number;
118
123
  menuHandler?: GroupMenuItemHandler<F>;
119
124
  }
@@ -242,6 +247,12 @@ export interface RenderOptions {
242
247
  * @param cusFields field或者field数组
243
248
  */
244
249
  handleTrigger?: (cusFields?: string | string[]) => void;
250
+ /**
251
+ * 延迟校验触发器
252
+ * @param cusFields field或者field数组
253
+ * @param time 延迟时间
254
+ */
255
+ handleDelayTrigger?: (cusFields?: string | string[], time?: number) => void;
245
256
  }
246
257
 
247
258
  export interface RenderFormParams<F = Recordable> {
@@ -268,6 +279,7 @@ export interface PGridInstance<D = Recordable, F = Recordable> {
268
279
  selectedRecords: D[];
269
280
  $table: Recordable;
270
281
  $form: Recordable;
282
+ getFormData: () => Partial<F>;
271
283
  setLoadings: (value: boolean) => void;
272
284
  setBtnLoading: (code: string, status: boolean) => void;
273
285
  resizeTable: () => void;
@@ -1,21 +1,15 @@
1
1
  <script generic="F = Recordable" lang="ts" name="PForm" setup>
2
2
  import { PFormItemProps, PFormProps } from '#/antProxy';
3
- import { computed, onMounted, ref, toRefs, watch } from 'vue';
4
- import { debounce, omit, isFunction } from 'lodash-es';
3
+ import { computed, ref, toRefs } from 'vue';
4
+ import { omit } from 'xe-utils';
5
5
  import { v4 as uuid_v4 } from 'uuid';
6
- import RenderAntItem from '@/components/RenderAntItem';
7
- import RenderItemSlots from '@/components/RenderItemSlots';
8
- import { isGoodValue, valued } from '@/utils/is';
6
+ import { isGoodValue } from '@/utils/is';
9
7
  import { eachTree } from '@/utils/treeHelper';
10
- import { defaultItemResponsive } from '@/utils/core';
8
+ import PFormCol from '@/components/PFormCol.vue';
11
9
  import {
12
10
  Form as AForm,
13
11
  Row as ARow,
14
- Col as ACol,
15
- FormItem as AFormItem,
16
- Tooltip as ATooltip,
17
12
  } from 'ant-design-vue';
18
- import { InfoCircleOutlined } from '@ant-design/icons-vue';
19
13
 
20
14
  const props = defineProps<PFormProps<F> & { data: F }>();
21
15
  const emit = defineEmits(['apply', 'reset']);
@@ -30,8 +24,6 @@
30
24
  const resetForm = () => {
31
25
  renderFormKey.value = uuid_v4();
32
26
  };
33
- const debounceResetForm = debounce(resetForm, 100);
34
- watch(() => items.value, debounceResetForm, { deep: true });
35
27
  const resetFormData = () => {
36
28
  if (props.customReset) {
37
29
  props.customReset();
@@ -45,9 +37,18 @@
45
37
  obj[item.field] = undefined;
46
38
  }
47
39
  }
40
+ else if (item.field && item.slots) {
41
+ if (isGoodValue(item.slots.defaultValue)) {
42
+ obj[item.field] = item.slots.defaultValue
43
+ }
44
+ else {
45
+ obj[item.field] = undefined;
46
+ }
47
+ }
48
48
  });
49
49
  Object.assign(formData.value, obj);
50
50
  }
51
+ emit('reset');
51
52
  };
52
53
  // omit({labelCol:defaultLabelCol,...props},['items','data','model'])
53
54
  const fc = computed(() => ({
@@ -63,13 +64,16 @@
63
64
  const handleTrigger = (cusFields?: string | string[]) => {
64
65
  validateField(cusFields);
65
66
  };
67
+ const handleDelayTrigger = (cusFields?: string | string[], time?: number) => {
68
+ const delayTime = time ?? 222;
69
+ setTimeout(() => {
70
+ validateField(cusFields);
71
+ }, delayTime);
72
+ };
66
73
  defineExpose({
67
74
  reset: resetForm,
68
75
  $form: computed(() => formEl.value),
69
76
  });
70
- onMounted(() => {
71
- resetForm();
72
- });
73
77
  </script>
74
78
  <template>
75
79
  <div
@@ -78,66 +82,15 @@
78
82
  >
79
83
  <a-form :key="renderFormKey" ref="formEl" :model="formData" v-bind="fc" @submit="handleSubmit">
80
84
  <a-row :gutter="[6, 12]">
81
- <a-col
85
+ <p-form-col
82
86
  v-for="(item, idx) in items"
83
- :key="'_col_' + idx"
84
- v-bind="item.col ?? (item.span ? { span: item.span } : defaultItemResponsive)"
85
- >
86
- <a-form-item
87
- :key="'_item_' + idx"
88
- :label="item.title"
89
- :name="item.field"
90
- :wrapper-col="item.wrapperCol ?? (item.title ? undefined : { span: 24 })"
91
- colon
92
- :class="`p-content-align-${item.align ?? 'left'} ${item.forceRequired ? 'p-required' : ''}`"
93
- v-bind="
94
- omit(item, [
95
- 'field',
96
- 'title',
97
- 'span',
98
- 'col',
99
- 'wrapperCol',
100
- 'itemRender',
101
- 'forceRequired',
102
- 'tooltip',
103
- ])
104
- "
105
- >
106
- <template v-if="item.slots?.title" #label>
107
- <component :is="item.slots.title" />
108
- </template>
109
- <render-item-slots
110
- v-if="item.slots?.default"
111
- :form-data="formData"
112
- :item="item"
113
- :key="'_sl_' + (item.field ?? '_') + '_' + idx"
114
- :pass-trigger="handleTrigger"
115
- />
116
- <render-ant-item
117
- v-else-if="item.itemRender?.name"
118
- :key="'_re_' + (item.field ?? '_') + '_' + idx"
119
- :default-handler="{ reset: resetFormData }"
120
- :item-render="item.itemRender"
121
- @trigger="handleTrigger"
122
- :render-form-params="{ data: formData, field: item.field }"
123
- />
124
- <span v-else></span>
125
- <template #tooltip v-if="item.tooltipConfig">
126
- <a-tooltip
127
- v-if="isFunction(item.tooltipConfig.title)"
128
- v-bind="omit(item.tooltipConfig, ['title'])"
129
- >
130
- <InfoCircleOutlined class="cursor-pointer py-4x px-2x" />
131
- <template #title>
132
- <div v-html="item.tooltipConfig.title()"></div>
133
- </template>
134
- </a-tooltip>
135
- <a-tooltip v-else v-bind="item.tooltipConfig">
136
- <InfoCircleOutlined class="cursor-pointer py-4x px-2x" />
137
- </a-tooltip>
138
- </template>
139
- </a-form-item>
140
- </a-col>
87
+ :key="`_col_${idx}`"
88
+ :form-data="formData"
89
+ :item="item"
90
+ @delay-trigger="handleDelayTrigger"
91
+ @reset="resetFormData"
92
+ @trigger="handleTrigger"
93
+ />
141
94
  </a-row>
142
95
  </a-form>
143
96
  </div>
@@ -0,0 +1,89 @@
1
+ <script setup lang="ts" generic="T extends Recordable = Recordable">
2
+ import type { PFormItemProps } from '#/antProxy'
3
+ import { defaultItemResponsive } from '@/utils/core';
4
+ import RenderAntItem from '@/components/RenderAntItem';
5
+ import RenderItemSlots from '@/components/RenderItemSlots';
6
+ import { InfoCircleOutlined } from '@ant-design/icons-vue'
7
+ import { v4 as uuid_v4 } from 'uuid'
8
+ import { ref, watch } from 'vue'
9
+ import { isEqual,debounce, isFunction, omit } from 'xe-utils'
10
+
11
+ const props = defineProps<{
12
+ item: PFormItemProps<T>
13
+ formData: T
14
+ }>()
15
+ const emit = defineEmits<{
16
+ (e: 'delayTrigger', cusFields?: string | string[], time?: number): void
17
+ (e: 'trigger', cusFields?: string | string[]): void
18
+ (e: 'reset'): void
19
+ }>()
20
+ const renderFormKey = ref(uuid_v4())
21
+ const handleDelayTrigger = (cusFields?: string | string[], time?: number) => {
22
+ emit('delayTrigger', cusFields, time)
23
+ }
24
+ const handleTrigger = (cusFields?: string | string[]) => {
25
+ emit('trigger', cusFields)
26
+ }
27
+ const resetFormData = () => {
28
+ emit('reset')
29
+ }
30
+ const refreshCol = () => {
31
+ renderFormKey.value = uuid_v4()
32
+ }
33
+ const debounceRefreshCol = debounce(refreshCol, 100)
34
+ watch(() => props.item, (cur, old) => {
35
+ if (!isEqual(cur, old)) {
36
+ debounceRefreshCol()
37
+ }
38
+ }, { deep: true })
39
+ </script>
40
+
41
+ <template>
42
+ <a-col
43
+ :key="`_col_${renderFormKey}`"
44
+ v-bind="item.col ?? (item.span ? { span: item.span } : defaultItemResponsive)"
45
+ >
46
+ <a-form-item
47
+ :key="`_item_${renderFormKey}`"
48
+ :class="`p-content-align-${item.align ?? 'left'} ${item.forceRequired ? 'p-required' : ''}`"
49
+ colon
50
+ :label="item.title"
51
+ :name="item.field"
52
+ :wrapper-col="item.wrapperCol ?? (item.title ? undefined : { span: 24 })"
53
+ v-bind="omit(item, ['field', 'title', 'span', 'col', 'wrapperCol', 'itemRender', 'forceRequired', 'tooltip'])"
54
+ >
55
+ <template v-if="item.slots?.title" #label>
56
+ <component :is="item.slots.title" />
57
+ </template>
58
+ <render-item-slots
59
+ v-if="item.slots?.default"
60
+ :key="`_sl_${item.field ?? '_'}_${renderFormKey}`"
61
+ :form-data="formData"
62
+ :item="item"
63
+ :pass-delay-trigger="handleDelayTrigger"
64
+ :pass-trigger="handleTrigger"
65
+ />
66
+ <render-ant-item
67
+ v-else-if="item.itemRender?.name"
68
+ :key="`_re_${item.field ?? '_'}_${renderFormKey}`"
69
+ :default-handler="{ reset: resetFormData }"
70
+ :item-render="item.itemRender"
71
+ :render-form-params="{ data: formData as Recordable, field: item.field }"
72
+ @delay-trigger="handleDelayTrigger"
73
+ @trigger="handleTrigger"
74
+ />
75
+ <span v-else />
76
+ <template v-if="item.tooltipConfig" #tooltip>
77
+ <a-tooltip v-if="isFunction(item.tooltipConfig.title)" v-bind="omit(item.tooltipConfig, ['title'])">
78
+ <info-circle-outlined class="cursor-pointer py-4 px-2" />
79
+ <template #title>
80
+ <div v-html="item.tooltipConfig.title()" />
81
+ </template>
82
+ </a-tooltip>
83
+ <a-tooltip v-else v-bind="item.tooltipConfig">
84
+ <info-circle-outlined class="cursor-pointer py-4 px-2" />
85
+ </a-tooltip>
86
+ </template>
87
+ </a-form-item>
88
+ </a-col>
89
+ </template>
@@ -1,9 +1,10 @@
1
1
  <script lang="ts" generic="F = Recordable" setup name="PFormGroup">
2
- import { computed, nextTick, PropType, ref, watchEffect } from 'vue';
2
+ import { computed, nextTick, PropType, ref, watch, watchEffect } from 'vue';
3
3
  import { PFormGroupProps, PFormBlockInstance } from '#/antProxy';
4
4
  import { MoreOutlined } from '@ant-design/icons-vue';
5
+ import type { Tabs } from 'ant-design-vue'
5
6
  import { Form } from 'ant-design-vue'
6
- import { cloneDeep, toString, isFunction, omit, maxBy, debounce } from 'lodash-es';
7
+ import { clone, toString, isFunction, omit, max, debounce } from 'xe-utils';
7
8
  import PGroupBlock from '@/components/PGroupBlock.vue';
8
9
  import {
9
10
  Card as ACard,
@@ -19,25 +20,28 @@
19
20
  import { $warning } from '@/hooks/useMessage';
20
21
 
21
22
  const useForm = Form.useForm
23
+ const tabsRef = ref<InstanceType<typeof Tabs>>()
22
24
  const props = defineProps<PFormGroupProps<F>>();
25
+ const rootRef = ref<InstanceType<typeof ACard>>()
23
26
  const model = defineModel({
24
27
  type: Array as PropType<Partial<F & { __index: number }>[]>,
25
28
  default: () => [],
26
29
  });
27
30
  const activeKey = ref(0);
31
+ const error_indexes = ref<number[]>([])
28
32
  const blockInstance = ref<PFormBlockInstance[]>([]);
29
33
  const setActiveKey = (key: number) => {
30
34
  activeKey.value = key;
31
35
  };
32
36
  const maxLen = computed(() => props.max ?? Infinity);
33
37
  const handleAddItem = (idx: number) => {
34
- const creator = props.creatItem ?? (() => Promise.resolve({} as Partial<F>));
38
+ const creator = props.createItem ?? (() => Promise.resolve({} as Partial<F>));
35
39
  creator({ list: model.value }).then((item) => {
36
40
  model.value = [
37
41
  ...model.value,
38
42
  {
39
43
  ...item,
40
- __index: (maxBy(model.value, (m) => m.__index ?? -1)?.__index ?? -1) + 1,
44
+ __index: (max(model.value, (m) => m.__index ?? -1)?.__index ?? -1) + 1,
41
45
  } as Partial<F & { __index: number }>,
42
46
  ];
43
47
  activeKey.value = idx;
@@ -47,26 +51,34 @@
47
51
  { content: '复制', code: 'copy' },
48
52
  { content: '删除', code: 'delete' },
49
53
  ];
54
+ const getPopupContainer = () => rootRef.value?.$el ?? document.body
50
55
  // 实际是否强制渲染
51
56
  const fr = computed(() => {
52
57
  return props.forceRender || model.value.length <= 5
53
58
  })
59
+ const handleBlockFocus = (idx: number) => {
60
+ const target_index = model.value.find((f, index) => index === idx)?.__index
61
+ if (valued(target_index)) {
62
+ error_indexes.value = error_indexes.value.filter(f => f !== target_index)
63
+ }
64
+ }
54
65
  const handleTabChange = () => {
55
66
  nextTick().then(() => {
67
+ handleBlockFocus(activeKey.value)
56
68
  blockInstance.value[activeKey.value]?.$form?.validate()
57
69
  })
58
70
  }
59
- watchEffect(() => {
60
- if (!props.keepSerial) {
61
- const unSortItems = model.value.filter((f) => !valued(f.__index));
62
- if (unSortItems.length > 0) {
63
- unSortItems.forEach((item) => {
64
- // @ts-ignore
65
- item.__index = (maxBy(model.value, (m) => m.__index ?? -1)?.__index ?? -1) + 1;
66
- });
67
- }
71
+ watch(() => model.value, () => {
72
+ if (!props.keepSerial) {
73
+ const unSortItems = model.value.filter(f => !valued(f.__index))
74
+ if (unSortItems.length > 0) {
75
+ unSortItems.forEach((item) => {
76
+ // @ts-ignore
77
+ item.__index = (max(model.value, m => m.__index ?? -1)?.__index ?? -1) + 1
78
+ })
68
79
  }
69
- });
80
+ }
81
+ }, { immediate: true })
70
82
  const handleMenu = ({ key }: MenuInfo, item: Partial<F & { __index: number }>, idx: number) => {
71
83
  if (props.menuHandler && isFunction(props.menuHandler)) {
72
84
  props.menuHandler({ code: toString(key), data: item, index: idx });
@@ -88,9 +100,9 @@
88
100
  }
89
101
  model.value = [
90
102
  ...model.value,
91
- cloneDeep({
103
+ clone({
92
104
  ...omit(item, ['id', '__index']),
93
- __index: (maxBy(model.value, (m) => m.__index ?? -1)?.__index ?? -1) + 1,
105
+ __index: (max(model.value, (m) => m.__index ?? -1)?.__index ?? -1) + 1,
94
106
  }),
95
107
  ] as Partial<F & { __index: number }>[];
96
108
  break;
@@ -105,26 +117,74 @@
105
117
  activeKey.value = model.value.length - 1;
106
118
  }
107
119
  });
120
+
121
+ const debounceHandleBlockFocus = debounce(handleBlockFocus, 50)
122
+ watchEffect(() => {
123
+ const errorKeys = model.value.map((m, idx) => error_indexes.value.includes(m.__index!) ? idx : undefined).filter(valued)
124
+ const rootDom = tabsRef.value?.$el
125
+ if (rootDom) {
126
+ const tabPanel = rootDom.querySelectorAll(':scope >.ant-tabs-nav>.ant-tabs-nav-wrap>.ant-tabs-nav-list>.ant-tabs-tab>.ant-tabs-tab-btn')
127
+ tabPanel.forEach((tab, idx) => {
128
+ if (errorKeys.includes(idx)) {
129
+ tab.classList.add('p-error-group-tab')
130
+ }
131
+ else {
132
+ tab.classList.remove('p-error-group-tab')
133
+ }
134
+ })
135
+ }
136
+ })
108
137
  defineExpose({
109
138
  activeKey: computed(() => activeKey.value),
110
139
  setActiveKey,
111
140
  validateAll: () => {
112
- return Promise.all(blockInstance.value.map((block, idx) =>
113
- fr.value ? (block.$form?.validate() ?? Promise.resolve()) : (useForm(model.value[idx], props.getFormSetting(model.value[idx]).rules)?.validate() ?? Promise.resolve()),
114
- ))
115
- },
116
- validate: (__index: number) => {
117
- const index = model.value.findIndex(f => f.__index === __index)
118
- return fr.value
141
+ const promiseList = fr.value
142
+ ? blockInstance.value.map(block => block.$form?.validate())
143
+ : model.value.map(m => useForm(m, props.getFormSetting(m).rules)?.validate() ?? Promise.resolve())
144
+ return Promise.allSettled(promiseList).then((results) => {
145
+ // 更新 error_indexes
146
+ error_indexes.value = results
147
+ .map((res, idx) => (res.status === 'rejected' && typeof model.value[idx]?.__index === 'number' ? model.value[idx].__index as number : undefined))
148
+ .filter((v): v is number => typeof v === 'number')
149
+ if (!props.lazyErrorMark && error_indexes.value.length) {
150
+ // 跳到第一个出错的tab
151
+ const firstErrorIndex = error_indexes.value[0]
152
+ const firstErrorTab = model.value.findIndex(f => f.__index === firstErrorIndex)
153
+ if (firstErrorTab !== -1) {
154
+ activeKey.value = firstErrorTab
155
+ }
156
+ }
157
+ // 判断是否有错误
158
+ if (error_indexes.value.length) {
159
+ return Promise.reject({ error_indexes: error_indexes.value, results })
160
+ }
161
+ return Promise.resolve(results)
162
+ })
163
+ },
164
+ validate: async (__index: number) => {
165
+ const index = model.value.findIndex(f => f.__index === __index)
166
+ try {
167
+ await (fr.value
119
168
  ? (blockInstance.value[index]?.$form?.validate() ?? Promise.resolve())
120
- : (useForm(model.value[index], props.getFormSetting(model.value[index]).rules)?.validate() ?? Promise.resolve())
121
- },
169
+ : (useForm(model.value[index], props.getFormSetting(model.value[index]).rules)?.validate() ?? Promise.resolve()))
170
+ // 校验通过,移除error_indexes中的__index
171
+ error_indexes.value = error_indexes.value.filter(f => f !== __index)
172
+ return Promise.resolve()
173
+ }
174
+ catch (e) {
175
+ // 校验失败,加入error_indexes
176
+ if (!error_indexes.value.includes(__index)) {
177
+ error_indexes.value = [...error_indexes.value, __index]
178
+ }
179
+ return Promise.reject(e)
180
+ }
181
+ },
122
182
  });
123
183
  </script>
124
184
  <template>
125
- <a-card :title="title" size="small">
185
+ <a-card ref="rootRef" :title="title" size="small">
126
186
  <a-spin v-if="loading" class="w-full" />
127
- <a-tabs v-else type="editable-card" v-model:activeKey="activeKey" hide-add @change="handleTabChange">
187
+ <a-tabs v-else ref="tabsRef" type="editable-card" v-model:activeKey="activeKey" hide-add @change="handleTabChange">
128
188
  <template #rightExtra>
129
189
  <slot name="rightExtra">
130
190
  <a-button
@@ -143,7 +203,7 @@
143
203
  :force-render="fr"
144
204
  >
145
205
  <template #closeIcon>
146
- <a-dropdown v-if="editAble && itemMenus?.length">
206
+ <a-dropdown v-if="editAble && itemMenus?.length" :get-popup-container="getPopupContainer">
147
207
  <MoreOutlined />
148
208
  <template #overlay>
149
209
  <a-menu @click="(e) => debounceHandleMenu(e, item, idx)">
@@ -173,6 +233,8 @@
173
233
  :key="idx"
174
234
  :source="item"
175
235
  :get-form-setting="getFormSetting"
236
+ @focus.capture="debounceHandleBlockFocus(idx)"
237
+ @click.capture="debounceHandleBlockFocus(idx)"
176
238
  />
177
239
  </a-tab-pane>
178
240
  </a-tabs>