@veltra/desktop 1.7.8 → 1.7.10
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/dist/components/batch-edit/batch-edit-form.js +7 -3
- package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
- package/dist/components/batch-edit/batch-edit.js +1 -0
- package/dist/components/batch-edit/batch-edit.js.map +1 -1
- package/dist/components/batch-edit/use-edit-state.js.map +1 -1
- package/dist/components/form/form.js.map +1 -1
- package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js +31 -6
- package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/form/is-field-modified.js +12 -0
- package/dist/components/form/is-field-modified.js.map +1 -0
- package/dist/components/form/style2.css +37 -1
- package/dist/components/form/use-form-fields.js +11 -6
- package/dist/components/form/use-form-fields.js.map +1 -1
- package/dist/components/form-item/form-item.js.map +1 -1
- package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js +18 -5
- package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/image-cropper/draw-crop.js +63 -0
- package/dist/components/image-cropper/draw-crop.js.map +1 -0
- package/dist/components/image-cropper/image-cropper.js +344 -0
- package/dist/components/image-cropper/image-cropper.js.map +1 -0
- package/dist/components/image-cropper/style.d.ts +1 -0
- package/dist/components/image-cropper/style.js +1 -0
- package/dist/components/image-cropper/style2.css +205 -0
- package/dist/components/image-cropper/use-image-loader.js +59 -0
- package/dist/components/image-cropper/use-image-loader.js.map +1 -0
- package/dist/components/image-cropper/use-preview.js +44 -0
- package/dist/components/image-cropper/use-preview.js.map +1 -0
- package/dist/components/image-cropper/use-selection.js +183 -0
- package/dist/components/image-cropper/use-selection.js.map +1 -0
- package/dist/components/image-cropper/use-transform.js +182 -0
- package/dist/components/image-cropper/use-transform.js.map +1 -0
- package/dist/components/index.d.ts +2 -1
- package/dist/components/index.js +3 -1
- package/dist/components/index.js.map +1 -1
- package/dist/index.d.ts +47 -45
- package/dist/index.js +46 -45
- package/dist/types/batch-edit.d.ts +2 -0
- package/dist/types/form-item.d.ts +4 -1
- package/dist/types/form.d.ts +10 -1
- package/dist/types/image-cropper.d.ts +68 -0
- package/dist/types/index.d.ts +2 -1
- package/package.json +6 -6
- package/src/components/auto-complete/style.scss +3 -15
- package/src/components/batch-edit/__test__/batch-edit.test.ts +138 -0
- package/src/components/batch-edit/batch-edit-form.vue +18 -4
- package/src/components/batch-edit/use-edit-state.ts +1 -1
- package/src/components/contextmenu/style.scss +4 -20
- package/src/components/form/__test__/form.test.ts +80 -0
- package/src/components/form/__test__/is-field-modified.test.ts +37 -0
- package/src/components/form/form.vue +28 -4
- package/src/components/form/is-field-modified.ts +16 -0
- package/src/components/form/style.scss +53 -2
- package/src/components/form/use-form-fields.ts +20 -6
- package/src/components/form-item/__test__/form-item.test.ts +90 -0
- package/src/components/form-item/form-item.vue +42 -7
- package/src/components/image-cropper/__test__/draw-crop.test.ts +131 -0
- package/src/components/image-cropper/__test__/image-cropper.test.ts +744 -0
- package/src/components/image-cropper/__test__/use-transform.test.ts +229 -0
- package/src/components/image-cropper/draw-crop.ts +112 -0
- package/src/components/image-cropper/image-cropper.vue +302 -0
- package/src/components/image-cropper/index.ts +1 -0
- package/src/components/image-cropper/style.scss +232 -0
- package/src/components/image-cropper/style.ts +1 -0
- package/src/components/image-cropper/use-image-loader.ts +79 -0
- package/src/components/image-cropper/use-preview.ts +58 -0
- package/src/components/image-cropper/use-selection.ts +255 -0
- package/src/components/image-cropper/use-transform.ts +256 -0
- package/src/components/index.ts +2 -0
- package/src/components/select/style.scss +3 -15
- package/src/types/batch-edit.ts +2 -0
- package/src/types/form-item.ts +4 -1
- package/src/types/form.ts +10 -2
- package/src/types/image-cropper.ts +73 -0
- package/src/types/index.ts +1 -0
|
@@ -15,7 +15,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
15
15
|
__name: "batch-edit-form",
|
|
16
16
|
setup(__props, { expose: __expose }) {
|
|
17
17
|
const batchEditCtx = inject(BatchEditDIKey);
|
|
18
|
-
const { cls, props, state, handleClose, handleSave, staticFeatures, dynamicFeatures, syncing } = batchEditCtx;
|
|
18
|
+
const { cls, props, state, emit, handleClose, handleSave, staticFeatures, dynamicFeatures, syncing } = batchEditCtx;
|
|
19
19
|
const focused = toRef(batchEditCtx, "focused");
|
|
20
20
|
const formComponentRef = shallowRef();
|
|
21
21
|
__expose({
|
|
@@ -24,10 +24,13 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
24
24
|
clearValidate: () => formComponentRef.value?.clearValidate(),
|
|
25
25
|
reset: () => formComponentRef.value?.reset()
|
|
26
26
|
});
|
|
27
|
-
function
|
|
27
|
+
function handleFieldUpdate(field, value) {
|
|
28
28
|
if (syncing.value) return;
|
|
29
29
|
if (state.row && props.quickEdit) o(state.row.data).set(field, value);
|
|
30
30
|
}
|
|
31
|
+
function handleFormFieldChange(field, ...args) {
|
|
32
|
+
emit("field:change", field, ...args);
|
|
33
|
+
}
|
|
31
34
|
const creatable = computed(() => {
|
|
32
35
|
if (state.formActionType !== "create") return false;
|
|
33
36
|
return staticFeatures.value.has("create") || dynamicFeatures.value.create?.(state.row);
|
|
@@ -86,7 +89,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
86
89
|
model: unref(props).model,
|
|
87
90
|
readonly: unref(props).readonly,
|
|
88
91
|
"label-width": unref(props).labelWidth,
|
|
89
|
-
"onField:
|
|
92
|
+
"onField:update": handleFieldUpdate,
|
|
93
|
+
"onField:change": handleFormFieldChange
|
|
90
94
|
}, {
|
|
91
95
|
default: withCtx(() => [renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps({
|
|
92
96
|
row: unref(state).row,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batch-edit-form.js","names":[],"sources":["../../../src/components/batch-edit/batch-edit-form.vue"],"sourcesContent":["<template>\n <!-- 表单常驻挂载(快照只在挂载时对传入的 model 拍一次),仅用 v-show 控制显隐 -->\n <aside :class=\"cls.e('form')\" v-if=\"!!props.model\" v-show=\"state.formVisible\">\n <header :class=\"cls.e('form-header')\">\n <span :class=\"[cls.e('form-icon'), bem.is(state.formActionType)]\">\n <u-icon>\n <component :is=\"headerInfo.icon\" />\n </u-icon>\n </span>\n <div :class=\"cls.e('form-title')\">\n <h3 :class=\"cls.e('form-title-text')\">{{ headerInfo.title }}</h3>\n </div>\n <u-button\n :class=\"cls.e('form-close')\"\n :icon=\"Close\"\n text\n circle\n size=\"small\"\n title=\"关闭\"\n @click=\"handleClose\"\n />\n </header>\n\n <u-scroll always :class=\"cls.e('form-body')\">\n <transition name=\"fade\" appear mode=\"out-in\">\n <u-form\n ref=\"formComponentRef\"\n :model=\"props.model\"\n :readonly=\"props.readonly\"\n :label-width=\"props.labelWidth\"\n @field:change=\"
|
|
1
|
+
{"version":3,"file":"batch-edit-form.js","names":[],"sources":["../../../src/components/batch-edit/batch-edit-form.vue"],"sourcesContent":["<template>\n <!-- 表单常驻挂载(快照只在挂载时对传入的 model 拍一次),仅用 v-show 控制显隐 -->\n <aside :class=\"cls.e('form')\" v-if=\"!!props.model\" v-show=\"state.formVisible\">\n <header :class=\"cls.e('form-header')\">\n <span :class=\"[cls.e('form-icon'), bem.is(state.formActionType)]\">\n <u-icon>\n <component :is=\"headerInfo.icon\" />\n </u-icon>\n </span>\n <div :class=\"cls.e('form-title')\">\n <h3 :class=\"cls.e('form-title-text')\">{{ headerInfo.title }}</h3>\n </div>\n <u-button\n :class=\"cls.e('form-close')\"\n :icon=\"Close\"\n text\n circle\n size=\"small\"\n title=\"关闭\"\n @click=\"handleClose\"\n />\n </header>\n\n <u-scroll always :class=\"cls.e('form-body')\">\n <transition name=\"fade\" appear mode=\"out-in\">\n <u-form\n ref=\"formComponentRef\"\n :model=\"props.model\"\n :readonly=\"props.readonly\"\n :label-width=\"props.labelWidth\"\n @field:update=\"handleFieldUpdate\"\n @field:change=\"handleFormFieldChange\"\n >\n <slot\n v-bind=\"{\n row: state.row,\n parentRow: state.parentRow,\n formActionType: state.formActionType,\n depth: state.depth,\n indexes: state.indexPath,\n index: state.row?.index\n }\"\n />\n </u-form>\n </transition>\n </u-scroll>\n\n <footer :class=\"cls.e('form-footer')\">\n <span :class=\"cls.e('form-hint')\">\n <template v-if=\"props.readonly\"> 只读模式 </template>\n\n <span :class=\"{ [bem.is('concealed')]: !focused }\">\n <template v-if=\"!(state.formActionType === 'update' && props.quickEdit)\">\n <u-kbd>Ctrl + S</u-kbd> 保存 ·\n </template>\n <u-kbd>Esc</u-kbd> 关闭\n </span>\n </span>\n\n <div :class=\"cls.e('form-actions')\">\n <u-button size=\"small\" text :loading=\"state.loading\" @click=\"handleClose\"> 取消 </u-button>\n <u-button\n v-if=\"showSaveBtn\"\n type=\"primary\"\n size=\"small\"\n title=\"保存\"\n :icon=\"Save\"\n :loading=\"state.loading\"\n @click=\"handleSave\"\n />\n </div>\n </footer>\n </aside>\n</template>\n\n<script lang=\"ts\" setup>\nimport { o } from '@cat-kit/core'\nimport { Close, Save } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, shallowRef, toRef } from 'vue'\n\nimport type { _FormExposed, FormExposed } from '../../types/form'\nimport { UButton } from '../button'\nimport { UForm } from '../form'\nimport { UIcon } from '../icon'\nimport { UKbd } from '../kbd'\nimport { UScroll } from '../scroll'\nimport { BatchEditDIKey } from './di'\nimport { FORM_ACTION_HEADER_MAP } from './form-action-header'\n\ndefineOptions({ name: 'UBatchEditForm' })\n\nconst batchEditCtx = inject(BatchEditDIKey)!\n\nconst {\n cls,\n props,\n state,\n emit,\n handleClose,\n handleSave,\n staticFeatures,\n dynamicFeatures,\n syncing\n} = batchEditCtx\n\nconst focused = toRef(batchEditCtx, 'focused')\n\nconst formComponentRef = shallowRef<FormExposed>()\n\ndefineExpose<_FormExposed>({\n el: toRef(() => formComponentRef.value?.el),\n validate: () => formComponentRef.value?.validate() ?? Promise.resolve(true),\n clearValidate: () => formComponentRef.value?.clearValidate(),\n reset: () => formComponentRef.value?.reset()\n})\n\nfunction handleFieldUpdate(field: string, value: any) {\n // 编程方式重置/回显期间不写回行数据,避免默认值污染源数据\n if (syncing.value) return\n\n if (state.row && props.quickEdit) {\n o(state.row.data).set(field, value)\n }\n}\n\nfunction handleFormFieldChange(field: string, ...args: any[]) {\n emit('field:change', field, ...args)\n}\n\nconst creatable = computed(() => {\n if (state.formActionType !== 'create') return false\n return staticFeatures.value.has('create') || dynamicFeatures.value.create?.(state.row)\n})\n\nconst creatableChild = computed(() => {\n if (state.formActionType !== 'createChild') return false\n return (\n staticFeatures.value.has('createChild') || dynamicFeatures.value.createChild?.(state.parentRow)\n )\n})\n\nconst updatable = computed(() => {\n if (state.formActionType !== 'update') return false\n return staticFeatures.value.has('update') || dynamicFeatures.value.update?.(state.row)\n})\n\nconst showSaveBtn = computed(() => {\n if (props.readonly) return false\n if (props.quickEdit && state.formActionType === 'update') return false\n return creatable.value || updatable.value || creatableChild.value\n})\n\nconst headerInfo = computed(() => FORM_ACTION_HEADER_MAP[state.formActionType])\n</script>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batch-edit.js","names":[],"sources":["../../../src/components/batch-edit/batch-edit.vue"],"sourcesContent":["<template>\n <u-layout\n :class=\"cls.b\"\n :cols=\"cols\"\n rows=\"minmax(0, 1fr) \"\n gap=\"8px\"\n resizable\n tabindex=\"-1\"\n @focusin=\"handleFocusIn\"\n @focusout=\"handleFocusOut\"\n @keydown.capture=\"handleKeydown\"\n >\n <!-- 编辑列表 -->\n <BatchEditList :slots=\"slots\" />\n\n <!-- 编辑表单 -->\n <BatchEditForm ref=\"form\" v-slot=\"scoped\">\n <slot name=\"form\" v-bind=\"scoped\" />\n </BatchEditForm>\n </u-layout>\n</template>\n\n<script lang=\"ts\" setup>\nimport { bem } from '@veltra/utils'\nimport { computed, provide, useTemplateRef } from 'vue'\n\nimport type { BatchEditEmits, BatchEditProps, BatchEditSlots, FormExposed } from '../../types'\nimport { ULayout } from '../layout'\nimport BatchEditForm from './batch-edit-form.vue'\nimport BatchEditList from './batch-edit-list.vue'\nimport { BatchEditDIKey } from './di'\nimport { useEditState } from './use-edit-state'\nimport { useFeatures } from './use-features'\nimport { useHandlers } from './use-handlers'\nimport { useShortcutKey } from './use-shortcut-key'\n\ndefineOptions({ name: 'UBatchEdit' })\n\nconst props = withDefaults(defineProps<BatchEditProps>(), { cols: () => ['1fr', '420px'] })\n\nconst emit = defineEmits<BatchEditEmits>()\n\nconst slots = defineSlots<BatchEditSlots>()\n\nconst cls = bem('batch-edit')\n\nconst formRef = useTemplateRef<FormExposed>('form')\n\nconst { state, resetState, syncing } = useEditState({ props, formRef })\n\nconst { staticFeatures, dynamicFeatures } = useFeatures({ props })\n\nconst handlers = useHandlers({ props, emit, state, resetState, formRef })\n\n// 快捷键处理\nconst { handleFocusIn, handleFocusOut, handleKeydown, focused } = useShortcutKey({\n props,\n onSave: handlers.handleSave,\n onClose: handlers.handleClose,\n state\n})\n\nconst cols = computed(() => {\n return state.formVisible ? props.cols : undefined\n})\n\nprovide(BatchEditDIKey, {\n cls,\n props,\n emit,\n state,\n staticFeatures,\n dynamicFeatures,\n focused,\n syncing,\n ...handlers\n})\n</script>\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"batch-edit.js","names":[],"sources":["../../../src/components/batch-edit/batch-edit.vue"],"sourcesContent":["<template>\n <u-layout\n :class=\"cls.b\"\n :cols=\"cols\"\n rows=\"minmax(0, 1fr) \"\n gap=\"8px\"\n resizable\n tabindex=\"-1\"\n @focusin=\"handleFocusIn\"\n @focusout=\"handleFocusOut\"\n @keydown.capture=\"handleKeydown\"\n >\n <!-- 编辑列表 -->\n <BatchEditList :slots=\"slots\" />\n\n <!-- 编辑表单 -->\n <BatchEditForm ref=\"form\" v-slot=\"scoped\">\n <slot name=\"form\" v-bind=\"scoped\" />\n </BatchEditForm>\n </u-layout>\n</template>\n\n<script lang=\"ts\" setup>\nimport { bem } from '@veltra/utils'\nimport { computed, provide, useTemplateRef } from 'vue'\n\nimport type { BatchEditEmits, BatchEditProps, BatchEditSlots, FormExposed } from '../../types'\nimport { ULayout } from '../layout'\nimport BatchEditForm from './batch-edit-form.vue'\nimport BatchEditList from './batch-edit-list.vue'\nimport { BatchEditDIKey } from './di'\nimport { useEditState } from './use-edit-state'\nimport { useFeatures } from './use-features'\nimport { useHandlers } from './use-handlers'\nimport { useShortcutKey } from './use-shortcut-key'\n\ndefineOptions({ name: 'UBatchEdit' })\n\nconst props = withDefaults(defineProps<BatchEditProps>(), { cols: () => ['1fr', '420px'] })\n\nconst emit = defineEmits<BatchEditEmits>()\n\nconst slots = defineSlots<BatchEditSlots>()\n\nconst cls = bem('batch-edit')\n\nconst formRef = useTemplateRef<FormExposed>('form')\n\nconst { state, resetState, syncing } = useEditState({ props, formRef })\n\nconst { staticFeatures, dynamicFeatures } = useFeatures({ props })\n\nconst handlers = useHandlers({ props, emit, state, resetState, formRef })\n\n// 快捷键处理\nconst { handleFocusIn, handleFocusOut, handleKeydown, focused } = useShortcutKey({\n props,\n onSave: handlers.handleSave,\n onClose: handlers.handleClose,\n state\n})\n\nconst cols = computed(() => {\n return state.formVisible ? props.cols : undefined\n})\n\nprovide(BatchEditDIKey, {\n cls,\n props,\n emit,\n state,\n staticFeatures,\n dynamicFeatures,\n focused,\n syncing,\n ...handlers\n})\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsCA,MAAM,QAAQ;EAEd,MAAM,OAAO;EAEb,MAAM,QAAQ,SAAA;EAEd,MAAM,MAAM,IAAI,YAAY;EAE5B,MAAM,UAAU,eAA4B,MAAM;EAElD,MAAM,EAAE,OAAO,YAAY,YAAY,aAAa;GAAE;GAAO;EAAQ,CAAC;EAEtE,MAAM,EAAE,gBAAgB,oBAAoB,YAAY,EAAE,MAAM,CAAC;EAEjE,MAAM,WAAW,YAAY;GAAE;GAAO;GAAM;GAAO;GAAY;EAAQ,CAAC;EAGxE,MAAM,EAAE,eAAe,gBAAgB,eAAe,YAAY,eAAe;GAC/E;GACA,QAAQ,SAAS;GACjB,SAAS,SAAS;GAClB;EACF,CAAC;EAED,MAAM,OAAO,eAAe;GAC1B,OAAO,MAAM,cAAc,MAAM,OAAO,KAAA;EAC1C,CAAC;EAED,QAAQ,gBAAgB;GACtB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,GAAG;EACL,CAAC;;GA3EC,OAAA,UAAA,GAAA,YAkBW,MAAA,WAAA,GAAA;IAjBR,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,CAAC;IACZ,MAAM,KAAA;IACP,MAAK;IACL,KAAI;IACJ,WAAA;IACA,UAAS;IACR,WAAS,MAAA,aAAA;IACT,YAAU,MAAA,cAAA;IACO,kBAAA,MAAA,aAAA;;IAElB,SAAA,cAAa;KAAb,mBAAA,QAAA;KACA,YAAgC,aAAA,EAAT,MAAK,CAAA;KAE5B,mBAAA,QAAA;KACA,YAEgB,aAAA,EAFD,KAAI,OAAM,GAAA;MACvB,SAAA,SADgC,WAAM,CACtC,WAAoC,KAAA,QAAA,QAAA,eAAA,mBAAV,MAAM,CAAA,CAAA,CAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-edit-state.js","names":[],"sources":["../../../src/components/batch-edit/use-edit-state.ts"],"sourcesContent":["import { o } from '@cat-kit/core'\nimport { nextTick, shallowReactive, shallowRef, watch, type ShallowRef } from 'vue'\n\nimport type { BatchEditProps, BatchEditStates, FormExposed } from '../../types'\n\ninterface Options {\n props: BatchEditProps\n formRef: ShallowRef<FormExposed | null>\n}\n\nexport function useEditState(options: Options) {\n const { props, formRef } = options\n\n const defaultState: BatchEditStates = {\n depth: -1,\n formVisible: false,\n formActionType: 'create',\n loading: false,\n indexPath: []\n }\n\n const state = shallowReactive({ ...defaultState })\n\n /** 是否正在以编程方式重置/回显表单,此期间禁止 quick-edit 回写行数据 */\n const syncing = shallowRef(false)\n\n function resetState() {\n // 表单常驻挂载,重置即恢复到 model 初始快照\n formRef.value?.reset()\n\n if (state.row) {\n state.row.isCurrent = false\n }\n\n Object.keys(state).forEach((key) => {\n delete state[key]\n })\n\n Object.assign(state, defaultState)\n }\n\n watch(\n () => state.row,\n (row) => {\n syncing.value = true\n\n // 先重置回 model 初始快照,再同步回显行数据。\n // 同一 tick 内的连续写入会被表单字段 watcher 合并,\n // 重置产生的默认值不会触发 field:
|
|
1
|
+
{"version":3,"file":"use-edit-state.js","names":[],"sources":["../../../src/components/batch-edit/use-edit-state.ts"],"sourcesContent":["import { o } from '@cat-kit/core'\nimport { nextTick, shallowReactive, shallowRef, watch, type ShallowRef } from 'vue'\n\nimport type { BatchEditProps, BatchEditStates, FormExposed } from '../../types'\n\ninterface Options {\n props: BatchEditProps\n formRef: ShallowRef<FormExposed | null>\n}\n\nexport function useEditState(options: Options) {\n const { props, formRef } = options\n\n const defaultState: BatchEditStates = {\n depth: -1,\n formVisible: false,\n formActionType: 'create',\n loading: false,\n indexPath: []\n }\n\n const state = shallowReactive({ ...defaultState })\n\n /** 是否正在以编程方式重置/回显表单,此期间禁止 quick-edit 回写行数据 */\n const syncing = shallowRef(false)\n\n function resetState() {\n // 表单常驻挂载,重置即恢复到 model 初始快照\n formRef.value?.reset()\n\n if (state.row) {\n state.row.isCurrent = false\n }\n\n Object.keys(state).forEach((key) => {\n delete state[key]\n })\n\n Object.assign(state, defaultState)\n }\n\n watch(\n () => state.row,\n (row) => {\n syncing.value = true\n\n // 先重置回 model 初始快照,再同步回显行数据。\n // 同一 tick 内的连续写入会被表单字段 watcher 合并,\n // 重置产生的默认值不会触发 field:update 回写行数据\n formRef.value?.reset()\n\n if (row) {\n if (props.model) {\n o(props.model).deepExtend(row.data)\n }\n state.formActionType = props.readonly ? 'view' : 'update'\n state.formVisible = true\n state.depth = row.depth\n state.indexPath = [...row.indexes]\n } else {\n resetState()\n }\n\n nextTick(() => {\n syncing.value = false\n })\n }\n )\n\n return { state, resetState, syncing }\n}\n"],"mappings":";;;AAUA,SAAgB,aAAa,SAAkB;CAC7C,MAAM,EAAE,OAAO,YAAY;CAE3B,MAAM,eAAgC;EACpC,OAAO;EACP,aAAa;EACb,gBAAgB;EAChB,SAAS;EACT,WAAW,CAAC;CACd;CAEA,MAAM,QAAQ,gBAAgB,EAAE,GAAG,aAAa,CAAC;;CAGjD,MAAM,UAAU,WAAW,KAAK;CAEhC,SAAS,aAAa;EAEpB,QAAQ,OAAO,MAAM;EAErB,IAAI,MAAM,KACR,MAAM,IAAI,YAAY;EAGxB,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,QAAQ;GAClC,OAAO,MAAM;EACf,CAAC;EAED,OAAO,OAAO,OAAO,YAAY;CACnC;CAEA,YACQ,MAAM,MACX,QAAQ;EACP,QAAQ,QAAQ;EAKhB,QAAQ,OAAO,MAAM;EAErB,IAAI,KAAK;GACP,IAAI,MAAM,OACR,EAAE,MAAM,KAAK,CAAC,CAAC,WAAW,IAAI,IAAI;GAEpC,MAAM,iBAAiB,MAAM,WAAW,SAAS;GACjD,MAAM,cAAc;GACpB,MAAM,QAAQ,IAAI;GAClB,MAAM,YAAY,CAAC,GAAG,IAAI,OAAO;EACnC,OACE,WAAW;EAGb,eAAe;GACb,QAAQ,QAAQ;EAClB,CAAC;CACH,CACF;CAEA,OAAO;EAAE;EAAO;EAAY;CAAQ;AACtC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.js","names":[],"sources":["../../../src/components/form/form.vue"],"sourcesContent":["<template>\n <u-grid\n tag=\"form\"\n ref=\"grid\"\n @submit.prevent\n gap=\"0 12px\"\n :cols=\"cols || breakpointCols\"\n :class=\"[cls.b, bem.is('readonly', readonly)]\"\n >\n <template\n v-for=\"{ node, isFormItem, formItemProps, field, modelValue } of getSlotsNodes()\"\n :key=\"node.key\"\n >\n <u-form-item v-if=\"!isFormItem && field\" v-bind=\"formItemProps\">\n <component\n :is=\"node\"\n :model-value=\"modelValue ?? o(model ?? {}).get(field)\"\n @update:model-value=\"handleUpdateValue(field, $event)\"\n />\n </u-form-item>\n\n <component v-else :is=\"node\" />\n </template>\n </u-grid>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { bem } from '@veltra/utils'\nimport { provideFormContext } from '@veltra/utils'\nimport { nextTick, toRef, useTemplateRef } from 'vue'\n\nimport type { BreakCols, FormProps, _FormExposed, FormEmits } from '../../types'\nimport { UFormItem } from '../form-item'\nimport { UGrid } from '../grid'\nimport { useFormFields } from './use-form-fields'\nimport { useNodeInterceptor } from './use-node-interceptor'\n\ndefineOptions({ name: 'UForm' })\n\nconst props = defineProps<FormProps>()\n\nconst emit = defineEmits<FormEmits>()\n\ndefineSlots<{ default(props?: { data: Record<string, any> | undefined }): any }>()\n\nconst cls = bem('form')\nconst formItemCls = bem('form-item')\nconst gridRef = useTemplateRef('grid')\n\nconst breakpointCols: BreakCols = { xs: 1, md: 2, lg: 3, xl: 4, default: 4 }\n\nconst {\n validate: runValidate,\n clearValidate,\n reset,\n registerField,\n unregisterField,\n shouldValidate\n} = useFormFields({ props })\n\nasync function validate(keys?: string[]) {\n const valid = await runValidate(keys)\n if (!valid) {\n await nextTick()\n\n gridRef.value?.el\n ?.querySelector(`.${formItemCls.e('error-text')}`)\n ?.scrollIntoView({ block: 'center' })\n }\n return valid\n}\n\nfunction
|
|
1
|
+
{"version":3,"file":"form.js","names":[],"sources":["../../../src/components/form/form.vue"],"sourcesContent":["<template>\n <u-grid\n tag=\"form\"\n ref=\"grid\"\n @submit.prevent\n gap=\"0 12px\"\n :cols=\"cols || breakpointCols\"\n :class=\"[cls.b, bem.is('readonly', readonly)]\"\n >\n <template\n v-for=\"{ node, isFormItem, formItemProps, field, modelValue } of getSlotsNodes()\"\n :key=\"node.key\"\n >\n <u-form-item v-if=\"!isFormItem && field\" v-bind=\"formItemProps\">\n <component\n :is=\"node\"\n :model-value=\"modelValue ?? o(model ?? {}).get(field)\"\n @update:model-value=\"handleUpdateValue(field, $event)\"\n />\n <div v-if=\"shouldShowModified(field)\" :class=\"cls.e('data-before')\">\n <span :class=\"cls.e('changed-tag')\">{{ modifiedLabel }}</span>\n <component :is=\"node\" readonly :model-value=\"getBaselineFieldValue(field)\" />\n </div>\n </u-form-item>\n\n <component v-else :is=\"node\" />\n </template>\n </u-grid>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { bem } from '@veltra/utils'\nimport { provideFormContext } from '@veltra/utils'\nimport { nextTick, toRef, useTemplateRef } from 'vue'\n\nimport type { BreakCols, FormProps, _FormExposed, FormEmits } from '../../types'\nimport { UFormItem } from '../form-item'\nimport { UGrid } from '../grid'\nimport { isFieldModified } from './is-field-modified'\nimport { useFormFields } from './use-form-fields'\nimport { useNodeInterceptor } from './use-node-interceptor'\n\ndefineOptions({ name: 'UForm' })\n\nconst props = withDefaults(defineProps<FormProps>(), { modifiedLabel: '变更前:' })\n\nconst emit = defineEmits<FormEmits>()\n\ndefineSlots<{ default(props?: { data: Record<string, any> | undefined }): any }>()\n\nconst cls = bem('form')\nconst formItemCls = bem('form-item')\nconst gridRef = useTemplateRef('grid')\n\nconst breakpointCols: BreakCols = { xs: 1, md: 2, lg: 3, xl: 4, default: 4 }\n\nconst {\n validate: runValidate,\n clearValidate,\n reset,\n registerField,\n unregisterField,\n shouldValidate,\n getBaselineModel\n} = useFormFields({ props })\n\nasync function validate(keys?: string[]) {\n const valid = await runValidate(keys)\n if (!valid) {\n await nextTick()\n\n gridRef.value?.el\n ?.querySelector(`.${formItemCls.e('error-text')}`)\n ?.scrollIntoView({ block: 'center' })\n }\n return valid\n}\n\nfunction handleFieldUpdate(field: string, value: any) {\n emit('field:update', field, value)\n}\n\nfunction handleFieldChange(field: string, ...args: any[]) {\n emit('field:change', field, ...args)\n}\n\nprovideFormContext({\n formProps: props,\n registerField,\n unregisterField,\n validateFields: validate,\n shouldValidate,\n handleFieldUpdate,\n handleFieldChange\n})\n\nconst { getSlotsNodes } = useNodeInterceptor()\n\nfunction handleUpdateValue(field: string, value: any) {\n if (!props.model) return\n o(props.model).set(field, value)\n}\n\nfunction getBaselineFieldValue(field: string) {\n return o(getBaselineModel() ?? {}).get(field)\n}\n\nfunction shouldShowModified(field: string) {\n if (!props.showModified) return false\n const baseline = getBaselineModel()\n if (!baseline || !props.model) return false\n const current = o(props.model).get(field)\n const initial = o(baseline).get(field)\n return isFieldModified(current, initial)\n}\n\ndefineExpose<_FormExposed>({ el: toRef(() => gridRef.value?.el), validate, clearValidate, reset })\n</script>\n"],"mappings":""}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import _sfc_main from "../grid/grid.js";
|
|
2
2
|
import form_item_default from "../form-item/form-item.js";
|
|
3
|
+
import { isFieldModified } from "./is-field-modified.js";
|
|
3
4
|
import { useFormFields } from "./use-form-fields.js";
|
|
4
5
|
import { useNodeInterceptor } from "./use-node-interceptor.js";
|
|
5
|
-
import { Fragment, createBlock, createElementBlock, defineComponent, mergeProps, nextTick, normalizeClass, openBlock, renderList, resolveDynamicComponent, toRef, unref, useTemplateRef, withCtx, withModifiers } from "vue";
|
|
6
|
+
import { Fragment, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, mergeProps, nextTick, normalizeClass, openBlock, renderList, resolveDynamicComponent, toDisplayString, toRef, unref, useTemplateRef, withCtx, withModifiers } from "vue";
|
|
6
7
|
import { bem, provideFormContext } from "@veltra/utils";
|
|
7
8
|
import { o } from "@cat-kit/core";
|
|
8
9
|
//#region src/components/form/form.vue?vue&type=script&setup=true&lang.tsx
|
|
@@ -12,6 +13,9 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
12
13
|
props: {
|
|
13
14
|
cols: {},
|
|
14
15
|
model: {},
|
|
16
|
+
showModified: { type: Boolean },
|
|
17
|
+
modifiedLabel: { default: "变更前:" },
|
|
18
|
+
initialModel: {},
|
|
15
19
|
labelWidth: {},
|
|
16
20
|
labelPosition: {},
|
|
17
21
|
noTips: { type: Boolean },
|
|
@@ -19,7 +23,7 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
19
23
|
disabled: { type: Boolean },
|
|
20
24
|
size: {}
|
|
21
25
|
},
|
|
22
|
-
emits: ["field:change"],
|
|
26
|
+
emits: ["field:change", "field:update"],
|
|
23
27
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
24
28
|
const props = __props;
|
|
25
29
|
const emit = __emit;
|
|
@@ -33,7 +37,7 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
33
37
|
xl: 4,
|
|
34
38
|
default: 4
|
|
35
39
|
};
|
|
36
|
-
const { validate: runValidate, clearValidate, reset, registerField, unregisterField, shouldValidate } = useFormFields({ props });
|
|
40
|
+
const { validate: runValidate, clearValidate, reset, registerField, unregisterField, shouldValidate, getBaselineModel } = useFormFields({ props });
|
|
37
41
|
async function validate(keys) {
|
|
38
42
|
const valid = await runValidate(keys);
|
|
39
43
|
if (!valid) {
|
|
@@ -42,8 +46,11 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
42
46
|
}
|
|
43
47
|
return valid;
|
|
44
48
|
}
|
|
45
|
-
function
|
|
46
|
-
emit("field:
|
|
49
|
+
function handleFieldUpdate(field, value) {
|
|
50
|
+
emit("field:update", field, value);
|
|
51
|
+
}
|
|
52
|
+
function handleFieldChange(field, ...args) {
|
|
53
|
+
emit("field:change", field, ...args);
|
|
47
54
|
}
|
|
48
55
|
provideFormContext({
|
|
49
56
|
formProps: props,
|
|
@@ -51,6 +58,7 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
51
58
|
unregisterField,
|
|
52
59
|
validateFields: validate,
|
|
53
60
|
shouldValidate,
|
|
61
|
+
handleFieldUpdate,
|
|
54
62
|
handleFieldChange
|
|
55
63
|
});
|
|
56
64
|
const { getSlotsNodes } = useNodeInterceptor();
|
|
@@ -58,6 +66,17 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
58
66
|
if (!props.model) return;
|
|
59
67
|
o(props.model).set(field, value);
|
|
60
68
|
}
|
|
69
|
+
function getBaselineFieldValue(field) {
|
|
70
|
+
return o(getBaselineModel() ?? {}).get(field);
|
|
71
|
+
}
|
|
72
|
+
function shouldShowModified(field) {
|
|
73
|
+
if (!props.showModified) return false;
|
|
74
|
+
const baseline = getBaselineModel();
|
|
75
|
+
if (!baseline || !props.model) return false;
|
|
76
|
+
const current = o(props.model).get(field);
|
|
77
|
+
const initial = o(baseline).get(field);
|
|
78
|
+
return isFieldModified(current, initial);
|
|
79
|
+
}
|
|
61
80
|
__expose({
|
|
62
81
|
el: toRef(() => gridRef.value?.el),
|
|
63
82
|
validate,
|
|
@@ -81,7 +100,13 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
81
100
|
default: withCtx(() => [(openBlock(), createBlock(resolveDynamicComponent(node), {
|
|
82
101
|
"model-value": modelValue ?? unref(o)(__props.model ?? {}).get(field),
|
|
83
102
|
"onUpdate:modelValue": ($event) => handleUpdateValue(field, $event)
|
|
84
|
-
}, null, 8, ["model-value", "onUpdate:modelValue"]))
|
|
103
|
+
}, null, 8, ["model-value", "onUpdate:modelValue"])), shouldShowModified(field) ? (openBlock(), createElementBlock("div", {
|
|
104
|
+
key: 0,
|
|
105
|
+
class: normalizeClass(unref(cls).e("data-before"))
|
|
106
|
+
}, [createElementVNode("span", { class: normalizeClass(unref(cls).e("changed-tag")) }, toDisplayString(__props.modifiedLabel), 3), (openBlock(), createBlock(resolveDynamicComponent(node), {
|
|
107
|
+
readonly: "",
|
|
108
|
+
"model-value": getBaselineFieldValue(field)
|
|
109
|
+
}, null, 8, ["model-value"]))], 2)) : createCommentVNode("v-if", true)]),
|
|
85
110
|
_: 2
|
|
86
111
|
}, 1040)) : (openBlock(), createBlock(resolveDynamicComponent(node), { key: 1 }))], 64);
|
|
87
112
|
}), 128))]),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/form/form.vue"],"sourcesContent":["<template>\n <u-grid\n tag=\"form\"\n ref=\"grid\"\n @submit.prevent\n gap=\"0 12px\"\n :cols=\"cols || breakpointCols\"\n :class=\"[cls.b, bem.is('readonly', readonly)]\"\n >\n <template\n v-for=\"{ node, isFormItem, formItemProps, field, modelValue } of getSlotsNodes()\"\n :key=\"node.key\"\n >\n <u-form-item v-if=\"!isFormItem && field\" v-bind=\"formItemProps\">\n <component\n :is=\"node\"\n :model-value=\"modelValue ?? o(model ?? {}).get(field)\"\n @update:model-value=\"handleUpdateValue(field, $event)\"\n />\n </u-form-item>\n\n <component v-else :is=\"node\" />\n </template>\n </u-grid>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { bem } from '@veltra/utils'\nimport { provideFormContext } from '@veltra/utils'\nimport { nextTick, toRef, useTemplateRef } from 'vue'\n\nimport type { BreakCols, FormProps, _FormExposed, FormEmits } from '../../types'\nimport { UFormItem } from '../form-item'\nimport { UGrid } from '../grid'\nimport { useFormFields } from './use-form-fields'\nimport { useNodeInterceptor } from './use-node-interceptor'\n\ndefineOptions({ name: 'UForm' })\n\nconst props = defineProps<FormProps>()\n\nconst emit = defineEmits<FormEmits>()\n\ndefineSlots<{ default(props?: { data: Record<string, any> | undefined }): any }>()\n\nconst cls = bem('form')\nconst formItemCls = bem('form-item')\nconst gridRef = useTemplateRef('grid')\n\nconst breakpointCols: BreakCols = { xs: 1, md: 2, lg: 3, xl: 4, default: 4 }\n\nconst {\n validate: runValidate,\n clearValidate,\n reset,\n registerField,\n unregisterField,\n shouldValidate\n} = useFormFields({ props })\n\nasync function validate(keys?: string[]) {\n const valid = await runValidate(keys)\n if (!valid) {\n await nextTick()\n\n gridRef.value?.el\n ?.querySelector(`.${formItemCls.e('error-text')}`)\n ?.scrollIntoView({ block: 'center' })\n }\n return valid\n}\n\nfunction
|
|
1
|
+
{"version":3,"file":"form.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/form/form.vue"],"sourcesContent":["<template>\n <u-grid\n tag=\"form\"\n ref=\"grid\"\n @submit.prevent\n gap=\"0 12px\"\n :cols=\"cols || breakpointCols\"\n :class=\"[cls.b, bem.is('readonly', readonly)]\"\n >\n <template\n v-for=\"{ node, isFormItem, formItemProps, field, modelValue } of getSlotsNodes()\"\n :key=\"node.key\"\n >\n <u-form-item v-if=\"!isFormItem && field\" v-bind=\"formItemProps\">\n <component\n :is=\"node\"\n :model-value=\"modelValue ?? o(model ?? {}).get(field)\"\n @update:model-value=\"handleUpdateValue(field, $event)\"\n />\n <div v-if=\"shouldShowModified(field)\" :class=\"cls.e('data-before')\">\n <span :class=\"cls.e('changed-tag')\">{{ modifiedLabel }}</span>\n <component :is=\"node\" readonly :model-value=\"getBaselineFieldValue(field)\" />\n </div>\n </u-form-item>\n\n <component v-else :is=\"node\" />\n </template>\n </u-grid>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { bem } from '@veltra/utils'\nimport { provideFormContext } from '@veltra/utils'\nimport { nextTick, toRef, useTemplateRef } from 'vue'\n\nimport type { BreakCols, FormProps, _FormExposed, FormEmits } from '../../types'\nimport { UFormItem } from '../form-item'\nimport { UGrid } from '../grid'\nimport { isFieldModified } from './is-field-modified'\nimport { useFormFields } from './use-form-fields'\nimport { useNodeInterceptor } from './use-node-interceptor'\n\ndefineOptions({ name: 'UForm' })\n\nconst props = withDefaults(defineProps<FormProps>(), { modifiedLabel: '变更前:' })\n\nconst emit = defineEmits<FormEmits>()\n\ndefineSlots<{ default(props?: { data: Record<string, any> | undefined }): any }>()\n\nconst cls = bem('form')\nconst formItemCls = bem('form-item')\nconst gridRef = useTemplateRef('grid')\n\nconst breakpointCols: BreakCols = { xs: 1, md: 2, lg: 3, xl: 4, default: 4 }\n\nconst {\n validate: runValidate,\n clearValidate,\n reset,\n registerField,\n unregisterField,\n shouldValidate,\n getBaselineModel\n} = useFormFields({ props })\n\nasync function validate(keys?: string[]) {\n const valid = await runValidate(keys)\n if (!valid) {\n await nextTick()\n\n gridRef.value?.el\n ?.querySelector(`.${formItemCls.e('error-text')}`)\n ?.scrollIntoView({ block: 'center' })\n }\n return valid\n}\n\nfunction handleFieldUpdate(field: string, value: any) {\n emit('field:update', field, value)\n}\n\nfunction handleFieldChange(field: string, ...args: any[]) {\n emit('field:change', field, ...args)\n}\n\nprovideFormContext({\n formProps: props,\n registerField,\n unregisterField,\n validateFields: validate,\n shouldValidate,\n handleFieldUpdate,\n handleFieldChange\n})\n\nconst { getSlotsNodes } = useNodeInterceptor()\n\nfunction handleUpdateValue(field: string, value: any) {\n if (!props.model) return\n o(props.model).set(field, value)\n}\n\nfunction getBaselineFieldValue(field: string) {\n return o(getBaselineModel() ?? {}).get(field)\n}\n\nfunction shouldShowModified(field: string) {\n if (!props.showModified) return false\n const baseline = getBaselineModel()\n if (!baseline || !props.model) return false\n const current = o(props.model).get(field)\n const initial = o(baseline).get(field)\n return isFieldModified(current, initial)\n}\n\ndefineExpose<_FormExposed>({ el: toRef(() => gridRef.value?.el), validate, clearValidate, reset })\n</script>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/components/form/is-field-modified.ts
|
|
2
|
+
/** 判断字段当前值相对基准值是否已变更 */
|
|
3
|
+
function isFieldModified(current, initial) {
|
|
4
|
+
if (current === initial) return false;
|
|
5
|
+
if ((current === null || current === void 0 || current === "") && (initial === null || initial === void 0 || initial === "")) return false;
|
|
6
|
+
if (current !== null && typeof current === "object" || initial !== null && typeof initial === "object") return JSON.stringify(current) !== JSON.stringify(initial);
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { isFieldModified };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=is-field-modified.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-field-modified.js","names":[],"sources":["../../../src/components/form/is-field-modified.ts"],"sourcesContent":["/** 判断字段当前值相对基准值是否已变更 */\nexport function isFieldModified(current: unknown, initial: unknown): boolean {\n if (current === initial) return false\n\n const currentNullish = current === null || current === undefined || current === ''\n const initialNullish = initial === null || initial === undefined || initial === ''\n if (currentNullish && initialNullish) return false\n\n const currentIsObject = current !== null && typeof current === 'object'\n const initialIsObject = initial !== null && typeof initial === 'object'\n if (currentIsObject || initialIsObject) {\n return JSON.stringify(current) !== JSON.stringify(initial)\n }\n\n return true\n}\n"],"mappings":";;AACA,SAAgB,gBAAgB,SAAkB,SAA2B;CAC3E,IAAI,YAAY,SAAS,OAAO;CAIhC,KAFuB,YAAY,QAAQ,YAAY,KAAA,KAAa,YAAY,QACzD,YAAY,QAAQ,YAAY,KAAA,KAAa,YAAY,KAC1C,OAAO;CAI7C,IAFwB,YAAY,QAAQ,OAAO,YAAY,YACvC,YAAY,QAAQ,OAAO,YAAY,UAE7D,OAAO,KAAK,UAAU,OAAO,MAAM,KAAK,UAAU,OAAO;CAG3D,OAAO;AACT"}
|
|
@@ -26,10 +26,46 @@
|
|
|
26
26
|
padding: 0 8px;
|
|
27
27
|
}
|
|
28
28
|
.u-form__data-before {
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: 4px;
|
|
32
|
+
flex-wrap: wrap;
|
|
33
|
+
margin-top: 4px;
|
|
34
|
+
padding: 2px 8px;
|
|
35
|
+
border-left: 2px solid var(--u-color-info);
|
|
36
|
+
background-color: var(--u-color-info-a-8);
|
|
37
|
+
border-radius: 2px;
|
|
38
|
+
width: fit-content;
|
|
39
|
+
max-width: 100%;
|
|
40
|
+
line-height: 1.4;
|
|
41
|
+
font-size: var(--u-font-size-assist-default);
|
|
42
|
+
}
|
|
43
|
+
.u-form__data-before:has(.u-input), .u-form__data-before:has(.u-textarea), .u-form__data-before:has(.u-select), .u-form__data-before:has(.u-multi-select), .u-form__data-before:has(.u-multi-tree-select), .u-form__data-before:has(.u-auto-complete), .u-form__data-before:has(.u-cascade), .u-form__data-before:has(.u-date-picker), .u-form__data-before:has(.u-date-range-picker), .u-form__data-before:has(.u-number-input), .u-form__data-before:has(.u-tree-select) {
|
|
29
44
|
width: 100%;
|
|
30
|
-
|
|
45
|
+
}
|
|
46
|
+
.u-form__data-before .u-form-item__content {
|
|
47
|
+
background: transparent;
|
|
48
|
+
padding: 0;
|
|
49
|
+
}
|
|
50
|
+
.u-form__data-before .u-input,
|
|
51
|
+
.u-form__data-before .u-textarea,
|
|
52
|
+
.u-form__data-before .u-select,
|
|
53
|
+
.u-form__data-before .u-multi-select,
|
|
54
|
+
.u-form__data-before .u-multi-tree-select,
|
|
55
|
+
.u-form__data-before .u-auto-complete,
|
|
56
|
+
.u-form__data-before .u-cascade,
|
|
57
|
+
.u-form__data-before .u-date-picker,
|
|
58
|
+
.u-form__data-before .u-date-range-picker {
|
|
59
|
+
border: none;
|
|
60
|
+
box-shadow: none;
|
|
61
|
+
background: transparent;
|
|
62
|
+
min-height: auto;
|
|
63
|
+
height: auto;
|
|
64
|
+
padding: 0;
|
|
31
65
|
}
|
|
32
66
|
.u-form__changed-tag {
|
|
33
67
|
color: var(--u-color-info);
|
|
34
68
|
font-size: var(--u-font-size-assist-default);
|
|
69
|
+
font-style: normal;
|
|
70
|
+
flex-shrink: 0;
|
|
35
71
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { nextTick, watch } from "vue";
|
|
1
|
+
import { nextTick, shallowRef, watch } from "vue";
|
|
2
2
|
import { copy, o } from "@cat-kit/core";
|
|
3
3
|
//#region src/components/form/use-form-fields.ts
|
|
4
4
|
/** 按快照逐字段写回 model,支持 undefined 与嵌套对象 */
|
|
@@ -22,7 +22,7 @@ function useFormFields(options) {
|
|
|
22
22
|
const fields = {};
|
|
23
23
|
let willValidate = true;
|
|
24
24
|
/** model 初始快照,供 reset 恢复 */
|
|
25
|
-
|
|
25
|
+
const initialSnapshot = shallowRef();
|
|
26
26
|
function runWithoutChangeValidate(fn) {
|
|
27
27
|
willValidate = false;
|
|
28
28
|
fn();
|
|
@@ -34,7 +34,7 @@ function useFormFields(options) {
|
|
|
34
34
|
return willValidate;
|
|
35
35
|
}
|
|
36
36
|
watch(() => props.model, (model) => {
|
|
37
|
-
initialSnapshot = model ? copy(model) : void 0;
|
|
37
|
+
initialSnapshot.value = model ? copy(model) : void 0;
|
|
38
38
|
}, {
|
|
39
39
|
immediate: true,
|
|
40
40
|
deep: false
|
|
@@ -55,12 +55,16 @@ function useFormFields(options) {
|
|
|
55
55
|
return validList.every((valid) => valid);
|
|
56
56
|
}
|
|
57
57
|
function reset() {
|
|
58
|
-
if (!props.model || !initialSnapshot) return;
|
|
58
|
+
if (!props.model || !initialSnapshot.value) return;
|
|
59
59
|
runWithoutChangeValidate(() => {
|
|
60
|
-
applySnapshot(props.model, initialSnapshot);
|
|
60
|
+
applySnapshot(props.model, initialSnapshot.value);
|
|
61
61
|
Object.values(fields).forEach((item) => item.clearValidate?.());
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
+
/** 变更前基准数据:优先 initialModel,否则为 model 引用变更时的快照 */
|
|
65
|
+
function getBaselineModel() {
|
|
66
|
+
return props.initialModel ?? initialSnapshot.value;
|
|
67
|
+
}
|
|
64
68
|
return {
|
|
65
69
|
fields,
|
|
66
70
|
registerField,
|
|
@@ -68,7 +72,8 @@ function useFormFields(options) {
|
|
|
68
72
|
unregisterField,
|
|
69
73
|
clearValidate,
|
|
70
74
|
reset,
|
|
71
|
-
shouldValidate
|
|
75
|
+
shouldValidate,
|
|
76
|
+
getBaselineModel
|
|
72
77
|
};
|
|
73
78
|
}
|
|
74
79
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-form-fields.js","names":[],"sources":["../../../src/components/form/use-form-fields.ts"],"sourcesContent":["import { copy, o } from '@cat-kit/core'\nimport type { FormFieldItem } from '@veltra/utils'\nimport { nextTick, watch } from 'vue'\n\nimport type { FormProps } from '../../types/form'\n\ninterface Options {\n props: FormProps\n}\n\n/** 按快照逐字段写回 model,支持 undefined 与嵌套对象 */\nfunction applySnapshot(target: Record<string, any>, source: Record<string, any>) {\n for (const key of Object.keys(source)) {\n const sourceValue = source[key]\n const targetValue = target[key]\n\n if (\n sourceValue !== null &&\n typeof sourceValue === 'object' &&\n !Array.isArray(sourceValue) &&\n targetValue !== null &&\n typeof targetValue === 'object' &&\n !Array.isArray(targetValue)\n ) {\n applySnapshot(targetValue, sourceValue)\n continue\n }\n\n if (Array.isArray(sourceValue)) {\n o(target).set(key, copy(sourceValue))\n continue\n }\n\n o(target).set(key, sourceValue)\n }\n}\n\nexport function useFormFields(options: Options) {\n const { props } = options\n const fields: Record<string, FormFieldItem> = {}\n\n let willValidate = true\n\n /** model 初始快照,供 reset 恢复 */\n
|
|
1
|
+
{"version":3,"file":"use-form-fields.js","names":[],"sources":["../../../src/components/form/use-form-fields.ts"],"sourcesContent":["import { copy, o } from '@cat-kit/core'\nimport type { FormFieldItem } from '@veltra/utils'\nimport { nextTick, shallowRef, watch } from 'vue'\n\nimport type { FormProps } from '../../types/form'\n\ninterface Options {\n props: FormProps\n}\n\n/** 按快照逐字段写回 model,支持 undefined 与嵌套对象 */\nfunction applySnapshot(target: Record<string, any>, source: Record<string, any>) {\n for (const key of Object.keys(source)) {\n const sourceValue = source[key]\n const targetValue = target[key]\n\n if (\n sourceValue !== null &&\n typeof sourceValue === 'object' &&\n !Array.isArray(sourceValue) &&\n targetValue !== null &&\n typeof targetValue === 'object' &&\n !Array.isArray(targetValue)\n ) {\n applySnapshot(targetValue, sourceValue)\n continue\n }\n\n if (Array.isArray(sourceValue)) {\n o(target).set(key, copy(sourceValue))\n continue\n }\n\n o(target).set(key, sourceValue)\n }\n}\n\nexport function useFormFields(options: Options) {\n const { props } = options\n const fields: Record<string, FormFieldItem> = {}\n\n let willValidate = true\n\n /** model 初始快照,供 reset 恢复 */\n const initialSnapshot = shallowRef<Record<string, any> | undefined>()\n\n function runWithoutChangeValidate(fn: () => void) {\n willValidate = false\n fn()\n nextTick(() => {\n willValidate = true\n })\n }\n\n function shouldValidate() {\n return willValidate\n }\n\n watch(\n () => props.model,\n (model) => {\n initialSnapshot.value = model ? copy(model) : undefined\n },\n { immediate: true, deep: false }\n )\n\n function registerField(field: string, item: FormFieldItem) {\n fields[field] = item\n }\n\n function unregisterField(field: string) {\n delete fields[field]\n }\n\n function clearValidate() {\n Object.values(fields).forEach((item) => item.clearValidate?.())\n }\n\n async function validate(keys?: string[]) {\n let validList: boolean[]\n if (keys) {\n validList = await Promise.all(\n keys.map((key) => fields[key]?.validate() ?? Promise.resolve(true))\n )\n } else {\n validList = await Promise.all(Object.values(fields).map((field) => field.validate()))\n }\n return validList.every((valid) => valid)\n }\n\n function reset() {\n if (!props.model || !initialSnapshot.value) return\n\n runWithoutChangeValidate(() => {\n applySnapshot(props.model!, initialSnapshot.value!)\n Object.values(fields).forEach((item) => item.clearValidate?.())\n })\n }\n\n /** 变更前基准数据:优先 initialModel,否则为 model 引用变更时的快照 */\n function getBaselineModel() {\n return props.initialModel ?? initialSnapshot.value\n }\n\n return {\n fields,\n registerField,\n validate,\n unregisterField,\n clearValidate,\n reset,\n shouldValidate,\n getBaselineModel\n }\n}\n"],"mappings":";;;;AAWA,SAAS,cAAc,QAA6B,QAA6B;CAC/E,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,GAAG;EACrC,MAAM,cAAc,OAAO;EAC3B,MAAM,cAAc,OAAO;EAE3B,IACE,gBAAgB,QAChB,OAAO,gBAAgB,YACvB,CAAC,MAAM,QAAQ,WAAW,KAC1B,gBAAgB,QAChB,OAAO,gBAAgB,YACvB,CAAC,MAAM,QAAQ,WAAW,GAC1B;GACA,cAAc,aAAa,WAAW;GACtC;EACF;EAEA,IAAI,MAAM,QAAQ,WAAW,GAAG;GAC9B,EAAE,MAAM,CAAC,CAAC,IAAI,KAAK,KAAK,WAAW,CAAC;GACpC;EACF;EAEA,EAAE,MAAM,CAAC,CAAC,IAAI,KAAK,WAAW;CAChC;AACF;AAEA,SAAgB,cAAc,SAAkB;CAC9C,MAAM,EAAE,UAAU;CAClB,MAAM,SAAwC,CAAC;CAE/C,IAAI,eAAe;;CAGnB,MAAM,kBAAkB,WAA4C;CAEpE,SAAS,yBAAyB,IAAgB;EAChD,eAAe;EACf,GAAG;EACH,eAAe;GACb,eAAe;EACjB,CAAC;CACH;CAEA,SAAS,iBAAiB;EACxB,OAAO;CACT;CAEA,YACQ,MAAM,QACX,UAAU;EACT,gBAAgB,QAAQ,QAAQ,KAAK,KAAK,IAAI,KAAA;CAChD,GACA;EAAE,WAAW;EAAM,MAAM;CAAM,CACjC;CAEA,SAAS,cAAc,OAAe,MAAqB;EACzD,OAAO,SAAS;CAClB;CAEA,SAAS,gBAAgB,OAAe;EACtC,OAAO,OAAO;CAChB;CAEA,SAAS,gBAAgB;EACvB,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,SAAS,KAAK,gBAAgB,CAAC;CAChE;CAEA,eAAe,SAAS,MAAiB;EACvC,IAAI;EACJ,IAAI,MACF,YAAY,MAAM,QAAQ,IACxB,KAAK,KAAK,QAAQ,OAAO,IAAI,EAAE,SAAS,KAAK,QAAQ,QAAQ,IAAI,CAAC,CACpE;OAEA,YAAY,MAAM,QAAQ,IAAI,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK,UAAU,MAAM,SAAS,CAAC,CAAC;EAEtF,OAAO,UAAU,OAAO,UAAU,KAAK;CACzC;CAEA,SAAS,QAAQ;EACf,IAAI,CAAC,MAAM,SAAS,CAAC,gBAAgB,OAAO;EAE5C,+BAA+B;GAC7B,cAAc,MAAM,OAAQ,gBAAgB,KAAM;GAClD,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,SAAS,KAAK,gBAAgB,CAAC;EAChE,CAAC;CACH;;CAGA,SAAS,mBAAmB;EAC1B,OAAO,MAAM,gBAAgB,gBAAgB;CAC/C;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-item.js","names":[],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', !!rules?.required)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\" :show-delay=\"500\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <
|
|
1
|
+
{"version":3,"file":"form-item.js","names":[],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', !!rules?.required)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\" :show-delay=\"500\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <ContentSlot />\n </div>\n\n <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->\n <section :class=\"cls.e('error')\" v-if=\"!readonly && !formProps?.noTips\">\n <transition name=\"form-item-tips\" mode=\"out-in\">\n <span :class=\"cls.e('error-text')\" v-if=\"!!errorTip\">\n {{ errorTip }}\n </span>\n </transition>\n </section>\n </section>\n </u-grid-item>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { useConfig, useFallbackProps } from '@veltra/compositions'\nimport { bem, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {\n type CSSProperties,\n type VNode,\n cloneVNode,\n computed,\n onBeforeUnmount,\n shallowRef,\n useSlots,\n watch\n} from 'vue'\n\nimport type { FormItemEmits, FormItemProps, ComponentSize } from '../../types'\nimport { UGridItem } from '../grid'\nimport { UTip } from '../tip'\nimport { formItemCls as cls, defineField } from './helper'\nimport { validateField } from './validate'\n\ndefineOptions({ name: 'UFormItem' })\n\nconst props = withDefaults(defineProps<FormItemProps>(), { readonly: undefined })\n\nconst emit = defineEmits<FormItemEmits>()\n\nconst slots = useSlots()\n\ndefineSlots<{\n /** 标签插槽 */\n label?: () => any\n default?: () => any\n}>()\n\n/** 表单组件上下文 */\nconst {\n formProps,\n registerField,\n unregisterField,\n shouldValidate,\n handleFieldUpdate,\n handleFieldChange\n} = injectFormContext()\n\nfunction wrapControlChange(node: VNode) {\n if (!node || typeof node.type === 'symbol') return node\n\n return cloneVNode(node, {\n onChange: (...args: any[]) => {\n emit('change', ...args)\n if (props.field) handleFieldChange?.(props.field, ...args)\n }\n })\n}\n\nfunction ContentSlot() {\n const nodes = slots.default?.() ?? []\n return nodes.map(wrapControlChange)\n}\n\nconst { config } = useConfig()\n\nconst { size, readonly } = useFallbackProps([formProps ?? {}, props], {\n size: 'default' as ComponentSize,\n readonly: false\n})\n\nconst errorTip = shallowRef<string>()\n/** 递增序号,丢弃过期的异步校验结果 */\nlet validateSeq = 0\n/** 最近一次 validate 的 Promise,供过期调用等待最新结果 */\nlet latestValidatePromise: Promise<boolean> = Promise.resolve(true)\n\nconst labelPosition = computed(() => props.labelPosition ?? formProps?.labelPosition ?? 'left')\n\nconst className = computed(() => {\n return [\n cls.b,\n cls.m(size.value),\n bem.is('error', !!errorTip.value),\n bem.is('label-top', labelPosition.value === 'top')\n ].join(' ')\n})\n\n/** label样式 */\nconst labelStyles = computed<CSSProperties>(() => {\n if (labelPosition.value === 'top') return {}\n return {\n width: withUnit(props.labelWidth ?? formProps?.labelWidth ?? config.form.labelWidth, 'px')\n }\n})\n\nconst fieldItem = defineField({\n clearValidate() {\n errorTip.value = ''\n },\n async validate() {\n if (!props.field || !formProps?.model || !props.rules || !shouldValidate?.()) return true\n\n const seq = ++validateSeq\n\n latestValidatePromise = validateField(formProps.model!, props.field!, props.rules!).then(\n (tip) => {\n if (seq !== validateSeq) return latestValidatePromise\n\n errorTip.value = tip\n return !errorTip.value\n }\n )\n return latestValidatePromise\n }\n})\n\nwatch(\n () => props.field,\n (field, oldField) => {\n if (oldField) {\n unregisterField?.(oldField)\n }\n if (field) {\n registerField?.(field, fieldItem)\n }\n },\n { immediate: true }\n)\n\nlet stopWatchFieldValue: (() => void) | undefined\n\nwatch(\n [() => formProps?.model, () => props.field],\n ([model, field]) => {\n stopWatchFieldValue?.()\n\n if (!field || !model) return\n\n stopWatchFieldValue = watch(\n () => o(model).get(field),\n (value) => {\n handleFieldUpdate?.(field, value)\n fieldItem.validate()\n }\n )\n },\n { immediate: true }\n)\n\nonBeforeUnmount(() => {\n props.field && unregisterField?.(props.field)\n})\n</script>\n"],"mappings":""}
|
|
@@ -2,7 +2,7 @@ import _sfc_main from "../tip/tip.js";
|
|
|
2
2
|
import _sfc_main$1 from "../grid/grid-item.js";
|
|
3
3
|
import { defineField, formItemCls } from "./helper.js";
|
|
4
4
|
import { validateField } from "./validate.js";
|
|
5
|
-
import { Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, renderSlot, shallowRef, toDisplayString, unref, watch, withCtx } from "vue";
|
|
5
|
+
import { Transition, cloneVNode, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, renderSlot, shallowRef, toDisplayString, unref, useSlots, watch, withCtx } from "vue";
|
|
6
6
|
import { useConfig, useFallbackProps } from "@veltra/compositions";
|
|
7
7
|
import { bem, injectFormContext, withUnit } from "@veltra/utils";
|
|
8
8
|
import { o } from "@cat-kit/core";
|
|
@@ -25,10 +25,23 @@ var form_item_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
25
25
|
rules: {},
|
|
26
26
|
size: {}
|
|
27
27
|
},
|
|
28
|
-
|
|
28
|
+
emits: ["change"],
|
|
29
|
+
setup(__props, { emit: __emit }) {
|
|
29
30
|
const props = __props;
|
|
31
|
+
const emit = __emit;
|
|
32
|
+
const slots = useSlots();
|
|
30
33
|
/** 表单组件上下文 */
|
|
31
|
-
const { formProps, registerField, unregisterField, shouldValidate, handleFieldChange } = injectFormContext();
|
|
34
|
+
const { formProps, registerField, unregisterField, shouldValidate, handleFieldUpdate, handleFieldChange } = injectFormContext();
|
|
35
|
+
function wrapControlChange(node) {
|
|
36
|
+
if (!node || typeof node.type === "symbol") return node;
|
|
37
|
+
return cloneVNode(node, { onChange: (...args) => {
|
|
38
|
+
emit("change", ...args);
|
|
39
|
+
if (props.field) handleFieldChange?.(props.field, ...args);
|
|
40
|
+
} });
|
|
41
|
+
}
|
|
42
|
+
function ContentSlot() {
|
|
43
|
+
return (slots.default?.() ?? []).map(wrapControlChange);
|
|
44
|
+
}
|
|
32
45
|
const { config } = useConfig();
|
|
33
46
|
const { size, readonly } = useFallbackProps([formProps ?? {}, props], {
|
|
34
47
|
size: "default",
|
|
@@ -77,7 +90,7 @@ var form_item_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
77
90
|
stopWatchFieldValue?.();
|
|
78
91
|
if (!field || !model) return;
|
|
79
92
|
stopWatchFieldValue = watch(() => o(model).get(field), (value) => {
|
|
80
|
-
|
|
93
|
+
handleFieldUpdate?.(field, value);
|
|
81
94
|
fieldItem.validate();
|
|
82
95
|
});
|
|
83
96
|
}, { immediate: true });
|
|
@@ -102,7 +115,7 @@ var form_item_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
102
115
|
default: withCtx(() => [createElementVNode("span", { class: normalizeClass(unref(formItemCls).e("tip-label")) }, [renderSlot(_ctx.$slots, "label", {}, () => [createTextVNode(toDisplayString(__props.label) + toDisplayString(labelPosition.value === "top" ? "" : ":"), 1)])], 2)]),
|
|
103
116
|
_: 3
|
|
104
117
|
}, 8, ["content", "class"])) : renderSlot(_ctx.$slots, "label", {}, () => [createTextVNode(toDisplayString(__props.label) + toDisplayString(labelPosition.value === "top" ? "" : ":"), 1)], void 0, 1)], 6)) : createCommentVNode("v-if", true), createElementVNode("section", { class: normalizeClass(unref(formItemCls).e("wrapper")) }, [
|
|
105
|
-
createElementVNode("div", { class: normalizeClass(unref(formItemCls).e("content")) }, [
|
|
118
|
+
createElementVNode("div", { class: normalizeClass(unref(formItemCls).e("content")) }, [createVNode(ContentSlot)], 2),
|
|
106
119
|
createCommentVNode(" 只有表单控件处于非只读状态时,才显示错误提示 "),
|
|
107
120
|
!unref(readonly) && !unref(formProps)?.noTips ? (openBlock(), createElementBlock("section", {
|
|
108
121
|
key: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-item.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', !!rules?.required)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\" :show-delay=\"500\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <
|
|
1
|
+
{"version":3,"file":"form-item.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', !!rules?.required)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\" :show-delay=\"500\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <ContentSlot />\n </div>\n\n <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->\n <section :class=\"cls.e('error')\" v-if=\"!readonly && !formProps?.noTips\">\n <transition name=\"form-item-tips\" mode=\"out-in\">\n <span :class=\"cls.e('error-text')\" v-if=\"!!errorTip\">\n {{ errorTip }}\n </span>\n </transition>\n </section>\n </section>\n </u-grid-item>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { useConfig, useFallbackProps } from '@veltra/compositions'\nimport { bem, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {\n type CSSProperties,\n type VNode,\n cloneVNode,\n computed,\n onBeforeUnmount,\n shallowRef,\n useSlots,\n watch\n} from 'vue'\n\nimport type { FormItemEmits, FormItemProps, ComponentSize } from '../../types'\nimport { UGridItem } from '../grid'\nimport { UTip } from '../tip'\nimport { formItemCls as cls, defineField } from './helper'\nimport { validateField } from './validate'\n\ndefineOptions({ name: 'UFormItem' })\n\nconst props = withDefaults(defineProps<FormItemProps>(), { readonly: undefined })\n\nconst emit = defineEmits<FormItemEmits>()\n\nconst slots = useSlots()\n\ndefineSlots<{\n /** 标签插槽 */\n label?: () => any\n default?: () => any\n}>()\n\n/** 表单组件上下文 */\nconst {\n formProps,\n registerField,\n unregisterField,\n shouldValidate,\n handleFieldUpdate,\n handleFieldChange\n} = injectFormContext()\n\nfunction wrapControlChange(node: VNode) {\n if (!node || typeof node.type === 'symbol') return node\n\n return cloneVNode(node, {\n onChange: (...args: any[]) => {\n emit('change', ...args)\n if (props.field) handleFieldChange?.(props.field, ...args)\n }\n })\n}\n\nfunction ContentSlot() {\n const nodes = slots.default?.() ?? []\n return nodes.map(wrapControlChange)\n}\n\nconst { config } = useConfig()\n\nconst { size, readonly } = useFallbackProps([formProps ?? {}, props], {\n size: 'default' as ComponentSize,\n readonly: false\n})\n\nconst errorTip = shallowRef<string>()\n/** 递增序号,丢弃过期的异步校验结果 */\nlet validateSeq = 0\n/** 最近一次 validate 的 Promise,供过期调用等待最新结果 */\nlet latestValidatePromise: Promise<boolean> = Promise.resolve(true)\n\nconst labelPosition = computed(() => props.labelPosition ?? formProps?.labelPosition ?? 'left')\n\nconst className = computed(() => {\n return [\n cls.b,\n cls.m(size.value),\n bem.is('error', !!errorTip.value),\n bem.is('label-top', labelPosition.value === 'top')\n ].join(' ')\n})\n\n/** label样式 */\nconst labelStyles = computed<CSSProperties>(() => {\n if (labelPosition.value === 'top') return {}\n return {\n width: withUnit(props.labelWidth ?? formProps?.labelWidth ?? config.form.labelWidth, 'px')\n }\n})\n\nconst fieldItem = defineField({\n clearValidate() {\n errorTip.value = ''\n },\n async validate() {\n if (!props.field || !formProps?.model || !props.rules || !shouldValidate?.()) return true\n\n const seq = ++validateSeq\n\n latestValidatePromise = validateField(formProps.model!, props.field!, props.rules!).then(\n (tip) => {\n if (seq !== validateSeq) return latestValidatePromise\n\n errorTip.value = tip\n return !errorTip.value\n }\n )\n return latestValidatePromise\n }\n})\n\nwatch(\n () => props.field,\n (field, oldField) => {\n if (oldField) {\n unregisterField?.(oldField)\n }\n if (field) {\n registerField?.(field, fieldItem)\n }\n },\n { immediate: true }\n)\n\nlet stopWatchFieldValue: (() => void) | undefined\n\nwatch(\n [() => formProps?.model, () => props.field],\n ([model, field]) => {\n stopWatchFieldValue?.()\n\n if (!field || !model) return\n\n stopWatchFieldValue = watch(\n () => o(model).get(field),\n (value) => {\n handleFieldUpdate?.(field, value)\n fieldItem.validate()\n }\n )\n },\n { immediate: true }\n)\n\nonBeforeUnmount(() => {\n props.field && unregisterField?.(props.field)\n})\n</script>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//#region src/components/image-cropper/draw-crop.ts
|
|
2
|
+
/**
|
|
3
|
+
* 把选区按当前旋转 / 翻转变换绘制到目标 canvas,预览(P4)与 getResult()(P5)共用。
|
|
4
|
+
* 输出为屏幕上看到的裁剪结果:先旋转后翻转,与舞台 transform 链一致
|
|
5
|
+
*/
|
|
6
|
+
function drawCropToCanvas(img, selection, transform, canvas, outSize) {
|
|
7
|
+
const { rotation, flipX, flipY } = transform;
|
|
8
|
+
const swapped = rotation % 180 !== 0;
|
|
9
|
+
const baseW = swapped ? selection.height : selection.width;
|
|
10
|
+
const baseH = swapped ? selection.width : selection.height;
|
|
11
|
+
const outW = Math.max(1, Math.round(outSize?.width ?? (outSize?.height ?? baseH) / baseH * baseW));
|
|
12
|
+
const outH = Math.max(1, Math.round(outSize?.height ?? (outSize?.width ?? baseW) / baseW * baseH));
|
|
13
|
+
canvas.width = outW;
|
|
14
|
+
canvas.height = outH;
|
|
15
|
+
const ctx = canvas.getContext("2d");
|
|
16
|
+
if (!ctx) return;
|
|
17
|
+
ctx.translate(outW / 2, outH / 2);
|
|
18
|
+
ctx.rotate(rotation * Math.PI / 180);
|
|
19
|
+
ctx.scale((flipX ? -1 : 1) * outW / baseW, (flipY ? -1 : 1) * outH / baseH);
|
|
20
|
+
ctx.drawImage(img, selection.x, selection.y, selection.width, selection.height, -selection.width / 2, -selection.height / 2, selection.width, selection.height);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* canvas 导出为 Blob。
|
|
24
|
+
* 跨域图片污染画布时 toBlob 同步抛 SecurityError,包装为可捕获的 Promise 拒绝
|
|
25
|
+
*/
|
|
26
|
+
function canvasToBlob(canvas) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
try {
|
|
29
|
+
canvas.toBlob((blob) => {
|
|
30
|
+
if (blob) resolve(blob);
|
|
31
|
+
else reject(/* @__PURE__ */ new Error("Export crop result failed: canvas.toBlob got null"));
|
|
32
|
+
}, "image/png");
|
|
33
|
+
} catch (err) {
|
|
34
|
+
reject(new Error("Export crop result failed: canvas is tainted by cross-origin image", { cause: err }));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** Blob 读取为 dataURL 形式的 base64 */
|
|
39
|
+
function blobToBase64(blob) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const reader = new FileReader();
|
|
42
|
+
reader.onload = () => resolve(reader.result);
|
|
43
|
+
reader.onerror = () => reject(reader.error ?? /* @__PURE__ */ new Error("Export crop result failed: read blob as base64 failed"));
|
|
44
|
+
reader.readAsDataURL(blob);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 输出选区裁剪结果:离屏 canvas 经 drawCropToCanvas 按原图像素绘制后导出 Blob + base64。
|
|
49
|
+
* outSize 缺省为原图选区像素尺寸,传入则按该尺寸缩放输出
|
|
50
|
+
*/
|
|
51
|
+
async function cropToResult(img, selection, transform, outSize) {
|
|
52
|
+
const canvas = document.createElement("canvas");
|
|
53
|
+
drawCropToCanvas(img, selection, transform, canvas, outSize);
|
|
54
|
+
const blob = await canvasToBlob(canvas);
|
|
55
|
+
return {
|
|
56
|
+
blob,
|
|
57
|
+
base64: await blobToBase64(blob)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { cropToResult, drawCropToCanvas };
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=draw-crop.js.map
|