mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-12-15 03:26:57 +08:00
Compare commits
37 Commits
master
...
v1.7.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3b1b9543c | ||
|
|
cafd187d98 | ||
|
|
be75ac994f | ||
|
|
723e79c0fd | ||
|
|
2a29971318 | ||
|
|
09663c8320 | ||
|
|
57a634a687 | ||
|
|
af20a3c51f | ||
|
|
14e4a175b4 | ||
|
|
5b5d1f3a4e | ||
|
|
96b2a428f0 | ||
|
|
cd1a876aac | ||
|
|
d22e520fbf | ||
|
|
b312b5a5bc | ||
|
|
02e1c7f479 | ||
|
|
fa208372ab | ||
|
|
9729c38ed2 | ||
|
|
611628de78 | ||
|
|
df497579c9 | ||
|
|
c7174726b3 | ||
|
|
b2474909cf | ||
|
|
318433aafe | ||
|
|
b85cf6257d | ||
|
|
dca25e4b8f | ||
|
|
184d931dbe | ||
|
|
8ce5f71aa2 | ||
|
|
6a2436fb99 | ||
|
|
1eeabc8220 | ||
|
|
cea6569020 | ||
|
|
3435661348 | ||
|
|
3e76d34f59 | ||
|
|
3181f32b38 | ||
|
|
dd3e901a3d | ||
|
|
17b52aeaa0 | ||
|
|
5b16ec00e1 | ||
|
|
566b754887 | ||
|
|
d1da5fed55 |
64
CHANGELOG.md
64
CHANGELOG.md
@ -1,3 +1,67 @@
|
||||
# [1.7.0-beta.3](https://github.com/Tencent/tmagic-editor/compare/v1.7.0-beta.2...v1.7.0-beta.3) (2025-10-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **editor:** 表单组件保持单向数据流 ([09663c8](https://github.com/Tencent/tmagic-editor/commit/09663c8320829dc52a4d4ffb65c73fe147a3f5fc))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **design,tdesign-vue,next-adapter:** textarea支持autosize ([be75ac9](https://github.com/Tencent/tmagic-editor/commit/be75ac994f0c03a9bdae05bc433513278d32c905))
|
||||
* **element-plus-adapter, from, tdesign-vue-adapter:** button兼容type=default和type为空的情况 ([2a29971](https://github.com/Tencent/tmagic-editor/commit/2a2997131873a4836303ac2411371dd78726f6f9))
|
||||
* **form:** text组件配置的append.hander函数添加setModel/setFormValue方法 ([57a634a](https://github.com/Tencent/tmagic-editor/commit/57a634a687c53132f521a8ed35a649edec488738))
|
||||
|
||||
|
||||
|
||||
# [1.7.0-beta.2](https://github.com/Tencent/tmagic-editor/compare/v1.7.0-beta.1...v1.7.0-beta.2) (2025-10-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **form:** daterange 配置names后配置失效 ([d22e520](https://github.com/Tencent/tmagic-editor/commit/d22e520fbf6fc95c2c9600f02107d987b3a46fa7))
|
||||
* **form:** dialog submit event获取到的changeRecords为空 ([02e1c7f](https://github.com/Tencent/tmagic-editor/commit/02e1c7f479e5370ba6ea2153b9547bbb0c090431))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **design, element-plus-adapter, tdesign-vue-next-adapter:** 添加adapterType, 完善tdesign useZIndex ([b312b5a](https://github.com/Tencent/tmagic-editor/commit/b312b5a5bc827a8199d5a7b36418f0ad933be081))
|
||||
* **design, tdesign-vue-next-adapter, table, element-plus-adapter:** 完善tdesign适配 ([fa20837](https://github.com/Tencent/tmagic-editor/commit/fa208372ab5c204b6cb34ff977cd133156513fda))
|
||||
|
||||
|
||||
|
||||
# [1.7.0-beta.1](https://github.com/Tencent/tmagic-editor/compare/v1.7.0-beta.0...v1.7.0-beta.1) (2025-10-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **design, form, form-schema, tdesign-vue-next-adapter:** textarea支持rows配置 ([c717472](https://github.com/Tencent/tmagic-editor/commit/c7174726b3f017afb5666f9ccc343a0c98d8d1fa))
|
||||
* **form:** 表单校验后的错误信息将name转换成text ([b247490](https://github.com/Tencent/tmagic-editor/commit/b2474909cff049b0800da5be3293ad35309c1b1e))
|
||||
|
||||
|
||||
|
||||
# [1.7.0-beta.0](https://github.com/Tencent/tmagic-editor/compare/v1.6.1...v1.7.0-beta.0) (2025-10-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **form:** tabel复制行不生效 ([6a2436f](https://github.com/Tencent/tmagic-editor/commit/6a2436fb99f15cdf662f39fd86952955fb78a9d2))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **design, element-plus-adapter, tdesign-vue-next-adapter:** 新增popconfirm组件 ([3181f32](https://github.com/Tencent/tmagic-editor/commit/3181f32b389325ca1524c42af25279ceab0a9a09))
|
||||
* **design, form, tdesign-vue-next-adapter:** 完善tdesign适配 ([8ce5f71](https://github.com/Tencent/tmagic-editor/commit/8ce5f71aa26ea3fb1516aa020fa65fefdaa9cc55))
|
||||
* **design,editor,element-plus-adapter,form,table,tdesign-vue-next-adapter:** 重构table组件,适配tdesign ([5b16ec0](https://github.com/Tencent/tmagic-editor/commit/5b16ec00e1de45ed50f1c206de10ad48f9d71275))
|
||||
* **form:** fieldset中checkbox新增name,trueValue,falseValue配置 ([3435661](https://github.com/Tencent/tmagic-editor/commit/3435661348efce00390d0a79361073e8fea8af33))
|
||||
* **form:** text新增prepend, append不默认使用button ([cea6569](https://github.com/Tencent/tmagic-editor/commit/cea65690204dbe8ccf178f92fa7c47ad6fd24a8c))
|
||||
* **form:** 新增flex-layout组件 ([3e76d34](https://github.com/Tencent/tmagic-editor/commit/3e76d34f59343b5c3e833d32a35fad152a5bf7fd))
|
||||
* **form:** 新增style,fieldStyle配置;tooltip支持配置placement;配置中的函数新增getFormValue方法 ([1eeabc8](https://github.com/Tencent/tmagic-editor/commit/1eeabc8220649405746558d09bd648d2b650854d))
|
||||
* **playground:** 支持UI组件库切换 ([dd3e901](https://github.com/Tencent/tmagic-editor/commit/dd3e901a3d4122e6f4043521ff776fc0df478f98))
|
||||
* **table:** action支持配置disabled ([8809351](https://github.com/Tencent/tmagic-editor/commit/88093515373e1b431c83c0da9c28a2688421d151))
|
||||
|
||||
|
||||
|
||||
## [1.6.1](https://github.com/Tencent/tmagic-editor/compare/v1.6.0...v1.6.1) (2025-10-14)
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "tmagic",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "@tmagic/cli",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "@tmagic/core",
|
||||
"type": "module",
|
||||
"main": "dist/tmagic-core.umd.cjs",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "@tmagic/data-source",
|
||||
"type": "module",
|
||||
"main": "dist/tmagic-data-source.umd.cjs",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "@tmagic/dep",
|
||||
"type": "module",
|
||||
"main": "dist/tmagic-dep.umd.cjs",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "@tmagic/design",
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<component class="tmagic-design-button" :is="uiComponent" v-bind="uiProps" @click="clickHandler">
|
||||
<template #icon v-if="$slots.icon">
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
<template #default v-if="$slots.default">
|
||||
<slot></slot>
|
||||
</template>
|
||||
@ -30,3 +33,15 @@ const clickHandler = (...args: any[]) => {
|
||||
emit('click', ...args);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.tmagic-design-button {
|
||||
.t-button__text {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
+ .tmagic-design-button {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -19,3 +19,16 @@ const uiComponent = ui?.component || 'el-icon';
|
||||
const props = defineProps<IconProps>();
|
||||
const uiProps = computed<IconProps>(() => ui?.props(props) || props);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.t-t-design-adapter-icon {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
@change="changeHandler"
|
||||
@input="inputHandler"
|
||||
@update:modelValue="updateModelValue"
|
||||
@blur="blurHandler"
|
||||
@focus="focusHandler"
|
||||
>
|
||||
<template #prepend v-if="$slots.prepend">
|
||||
<slot name="prepend"></slot>
|
||||
@ -41,7 +43,7 @@ const uiComponent = ui?.component || 'el-input';
|
||||
|
||||
const uiProps = computed<InputProps>(() => ui?.props(props) || props);
|
||||
|
||||
const emit = defineEmits(['change', 'input', 'update:modelValue']);
|
||||
const emit = defineEmits(['change', 'input', 'blur', 'focus', 'update:modelValue']);
|
||||
|
||||
const instance = ref<any>();
|
||||
|
||||
@ -57,13 +59,56 @@ const updateModelValue = (...args: any[]) => {
|
||||
emit('update:modelValue', ...args);
|
||||
};
|
||||
|
||||
const blurHandler = (...args: any[]) => {
|
||||
emit('blur', ...args);
|
||||
};
|
||||
|
||||
const focusHandler = (...args: any[]) => {
|
||||
emit('focus', ...args);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
instance,
|
||||
getInput() {
|
||||
return instance.value.input;
|
||||
if (instance.value.input) {
|
||||
return instance.value.input;
|
||||
}
|
||||
return instance.value?.$el?.querySelector('input');
|
||||
},
|
||||
getTextarea() {
|
||||
return instance.value.textarea;
|
||||
if (instance.value.textarea) {
|
||||
return instance.value.textarea;
|
||||
}
|
||||
return instance.value?.$el?.querySelector('textarea');
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.tmagic-design-input {
|
||||
&.t-input-adornment {
|
||||
.t-input-adornment__prepend {
|
||||
> span {
|
||||
border-radius: var(--td-radius-default) 0 0 var(--td-radius-default);
|
||||
}
|
||||
}
|
||||
.t-input-adornment__append {
|
||||
> span {
|
||||
border-radius: 0 var(--td-radius-default) var(--td-radius-default) 0;
|
||||
}
|
||||
}
|
||||
.t-input-adornment__prepend,
|
||||
.t-input-adornment__append {
|
||||
> span {
|
||||
display: inline-flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
padding: 0 var(--td-comp-paddingLR-s);
|
||||
border: 1px solid var(--td-border-level-2-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -4,8 +4,9 @@
|
||||
:is="uiComponent"
|
||||
v-bind="uiProps"
|
||||
@size-change="handleSizeChange"
|
||||
@page-size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
@update:current-page="updateCurrentPage"
|
||||
@update:page-size="updatePageSize"
|
||||
></component>
|
||||
</template>
|
||||
|
||||
@ -21,7 +22,7 @@ defineOptions({
|
||||
|
||||
const props = defineProps<PaginationProps>();
|
||||
|
||||
const emit = defineEmits(['size-change', 'current-change']);
|
||||
const emit = defineEmits(['size-change', 'current-change', 'update:current-page', 'update:page-size']);
|
||||
|
||||
const ui = getDesignConfig('components')?.pagination;
|
||||
|
||||
@ -35,4 +36,10 @@ const handleSizeChange = (...args: any[]) => {
|
||||
const handleCurrentChange = (...args: any[]) => {
|
||||
emit('current-change', ...args);
|
||||
};
|
||||
const updateCurrentPage = (...args: any[]) => {
|
||||
emit('update:current-page', ...args);
|
||||
};
|
||||
const updatePageSize = (...args: any[]) => {
|
||||
emit('update:page-size', ...args);
|
||||
};
|
||||
</script>
|
||||
|
||||
42
packages/design/src/Popconfirm.vue
Normal file
42
packages/design/src/Popconfirm.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<component
|
||||
class="tmagic-design-popconfirm"
|
||||
:is="uiComponent"
|
||||
v-bind="uiProps"
|
||||
@confirm="confirmHandler"
|
||||
@cancel="cancelHandler"
|
||||
>
|
||||
<template #reference>
|
||||
<slot name="reference"></slot>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { getDesignConfig } from './config';
|
||||
import type { PopconfirmProps } from './types';
|
||||
|
||||
defineOptions({
|
||||
name: 'TMPopconfirm',
|
||||
});
|
||||
|
||||
const emit = defineEmits(['confirm', 'cancel']);
|
||||
|
||||
const props = defineProps<PopconfirmProps>();
|
||||
|
||||
const ui = getDesignConfig('components')?.popconfirm;
|
||||
|
||||
const uiComponent = ui?.component || 'el-popconfirm';
|
||||
|
||||
const uiProps = computed<PopconfirmProps>(() => ui?.props(props) || props);
|
||||
|
||||
const confirmHandler = (...args: any[]) => {
|
||||
emit('confirm', ...args);
|
||||
};
|
||||
|
||||
const cancelHandler = (...args: any[]) => {
|
||||
emit('cancel', ...args);
|
||||
};
|
||||
</script>
|
||||
@ -16,7 +16,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watchEffect } from 'vue';
|
||||
import { computed, useTemplateRef } from 'vue';
|
||||
|
||||
import { getDesignConfig } from './config';
|
||||
import type { TableProps } from './types';
|
||||
@ -37,7 +37,7 @@ const uiProps = computed<TableProps>(() => ui?.props(props) || props);
|
||||
|
||||
const emit = defineEmits(['select', 'sort-change', 'expand-change', 'cell-click']);
|
||||
|
||||
const table = ref<any>();
|
||||
const tableRef = useTemplateRef('table');
|
||||
|
||||
const selectHandler = (...args: any[]) => {
|
||||
emit('select', ...args);
|
||||
@ -55,27 +55,21 @@ const cellClickHandler = (...args: any[]) => {
|
||||
emit('cell-click', ...args);
|
||||
};
|
||||
|
||||
let $el: HTMLDivElement | undefined;
|
||||
|
||||
watchEffect(() => {
|
||||
$el = table.value?.$el;
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
instance: table,
|
||||
getEl: () => tableRef.value?.getTableRef().$el,
|
||||
|
||||
$el,
|
||||
getTableRef: () => tableRef.value.getTableRef(),
|
||||
|
||||
clearSelection(...args: any[]) {
|
||||
return table.value?.clearSelection(...args);
|
||||
return tableRef.value?.clearSelection(...args);
|
||||
},
|
||||
|
||||
toggleRowSelection(...args: any[]) {
|
||||
return table.value?.toggleRowSelection(...args);
|
||||
return tableRef.value?.toggleRowSelection(...args);
|
||||
},
|
||||
|
||||
toggleRowExpansion(...args: any[]) {
|
||||
return table.value?.toggleRowExpansion(...args);
|
||||
return tableRef.value?.toggleRowExpansion(...args);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
<template>
|
||||
<component :is="uiComponent" v-bind="uiProps">
|
||||
<template #default="{ $index, row }">
|
||||
<!-- eslint-disable-next-line vue/valid-attribute-name -->
|
||||
<slot :$index="$index" :row="row"></slot>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { getDesignConfig } from './config';
|
||||
import type { TableColumnProps } from './types';
|
||||
|
||||
defineOptions({
|
||||
name: 'TMTableColumn',
|
||||
});
|
||||
|
||||
const props = defineProps<TableColumnProps>();
|
||||
|
||||
const ui = getDesignConfig('components')?.tableColumn;
|
||||
|
||||
const uiComponent = ui?.component || 'el-table-column';
|
||||
|
||||
const uiProps = computed<TableColumnProps>(() => ui?.props(props) || props);
|
||||
</script>
|
||||
@ -8,6 +8,9 @@
|
||||
@tab-remove="onTabRemove"
|
||||
@update:model-value="updateModelName"
|
||||
>
|
||||
<template #add-icon v-if="$slots['add-icon']">
|
||||
<slot name="add-icon"></slot>
|
||||
</template>
|
||||
<template #default>
|
||||
<slot></slot>
|
||||
</template>
|
||||
|
||||
@ -1,110 +0,0 @@
|
||||
<template>
|
||||
<component
|
||||
class="tmagic-design-tree"
|
||||
ref="tree"
|
||||
:is="uiComponent"
|
||||
v-bind="uiProps"
|
||||
@node-click="nodeClickHandler"
|
||||
@node-contextmenu="contextmenu"
|
||||
@node-drag-end="handleDragEnd"
|
||||
@node-collapse="handleCollapse"
|
||||
@node-expand="handleExpand"
|
||||
@check="checkHandler"
|
||||
@mousedown="mousedownHandler"
|
||||
@mouseup="mouseupHandler"
|
||||
>
|
||||
<template #default="{ data, node }">
|
||||
<slot :data="data" :node="node"></slot>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { getDesignConfig } from './config';
|
||||
import type { TreeProps } from './types';
|
||||
|
||||
defineOptions({
|
||||
name: 'TMTree',
|
||||
});
|
||||
|
||||
const props = defineProps<TreeProps>();
|
||||
|
||||
const ui = getDesignConfig('components')?.tree;
|
||||
|
||||
const uiComponent = ui?.component || 'el-tree';
|
||||
|
||||
const uiProps = computed<TreeProps>(() => ui?.props(props) || props);
|
||||
|
||||
const emit = defineEmits([
|
||||
'node-click',
|
||||
'node-contextmenu',
|
||||
'node-drag-end',
|
||||
'node-collapse',
|
||||
'node-expand',
|
||||
'check',
|
||||
'mousedown',
|
||||
'mouseup',
|
||||
]);
|
||||
|
||||
const nodeClickHandler = (...args: any[]) => {
|
||||
emit('node-click', ...args);
|
||||
};
|
||||
|
||||
const contextmenu = (...args: any[]) => {
|
||||
emit('node-contextmenu', ...args);
|
||||
};
|
||||
|
||||
const handleDragEnd = (...args: any[]) => {
|
||||
emit('node-drag-end', ...args);
|
||||
};
|
||||
|
||||
const handleCollapse = (...args: any[]) => {
|
||||
emit('node-collapse', ...args);
|
||||
};
|
||||
|
||||
const handleExpand = (...args: any[]) => {
|
||||
emit('node-expand', ...args);
|
||||
};
|
||||
|
||||
const checkHandler = (...args: any[]) => {
|
||||
emit('check', ...args);
|
||||
};
|
||||
|
||||
const mousedownHandler = (...args: any[]) => {
|
||||
emit('mousedown', ...args);
|
||||
};
|
||||
|
||||
const mouseupHandler = (...args: any[]) => {
|
||||
emit('mouseup', ...args);
|
||||
};
|
||||
|
||||
const tree = ref<any>();
|
||||
|
||||
defineExpose({
|
||||
getData() {
|
||||
return tree.value?.data;
|
||||
},
|
||||
|
||||
getStore() {
|
||||
return tree.value?.store;
|
||||
},
|
||||
|
||||
filter(...args: any[]) {
|
||||
return tree.value?.filter(...args);
|
||||
},
|
||||
|
||||
getNode(...args: any[]) {
|
||||
return tree.value?.getNode(...args);
|
||||
},
|
||||
|
||||
setCheckedKeys(...args: any[]) {
|
||||
return tree.value?.setCheckedKeys(...args);
|
||||
},
|
||||
|
||||
setCurrentKey(...args: any[]) {
|
||||
return tree.value?.setCurrentKey(...args);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -46,30 +46,16 @@ export { default as TMagicStep } from './Step.vue';
|
||||
export { default as TMagicSteps } from './Steps.vue';
|
||||
export { default as TMagicSwitch } from './Switch.vue';
|
||||
export { default as TMagicTable } from './Table.vue';
|
||||
export { default as TMagicTableColumn } from './TableColumn.vue';
|
||||
export { default as TMagicTabPane } from './TabPane.vue';
|
||||
export { default as TMagicTabs } from './Tabs.vue';
|
||||
export { default as TMagicTag } from './Tag.vue';
|
||||
export { default as TMagicTimePicker } from './TimePicker.vue';
|
||||
export { default as TMagicTooltip } from './Tooltip.vue';
|
||||
export { default as TMagicTree } from './Tree.vue';
|
||||
export { default as TMagicUpload } from './Upload.vue';
|
||||
export { default as TMagicPopconfirm } from './Popconfirm.vue';
|
||||
|
||||
export const tMagicMessage = {
|
||||
error: (msg: string) => {
|
||||
console.error(msg);
|
||||
},
|
||||
success: (msg: string) => {
|
||||
console.log(msg);
|
||||
},
|
||||
warning: (msg: string) => {
|
||||
console.warn(msg);
|
||||
},
|
||||
info: (msg: string) => {
|
||||
console.info(msg);
|
||||
},
|
||||
closeAll: (_msg: string) => {},
|
||||
} as unknown as TMagicMessage;
|
||||
// eslint-disable-next-line import/no-mutable-exports
|
||||
export let tMagicMessage: TMagicMessage;
|
||||
|
||||
export const tMagicMessageBox = {
|
||||
alert: (msg: string) => {
|
||||
@ -109,13 +95,23 @@ export let useZIndex = (zIndexOverrides?: Ref<number>) => {
|
||||
|
||||
export default {
|
||||
install(app: App, options: DesignPluginOptions) {
|
||||
if (options.message) {
|
||||
tMagicMessage.error = options.message?.error;
|
||||
tMagicMessage.success = options.message?.success;
|
||||
tMagicMessage.warning = options.message?.warning;
|
||||
tMagicMessage.info = options.message?.info;
|
||||
tMagicMessage.closeAll = options.message?.closeAll;
|
||||
}
|
||||
tMagicMessage =
|
||||
options.message ||
|
||||
({
|
||||
error: (msg: string) => {
|
||||
console.error(msg);
|
||||
},
|
||||
success: (msg: string) => {
|
||||
console.log(msg);
|
||||
},
|
||||
warning: (msg: string) => {
|
||||
console.warn(msg);
|
||||
},
|
||||
info: (msg: string) => {
|
||||
console.info(msg);
|
||||
},
|
||||
closeAll: (_msg: string) => {},
|
||||
} as unknown as TMagicMessage);
|
||||
|
||||
if (options.messageBox) {
|
||||
tMagicMessageBox.alert = options.messageBox?.alert;
|
||||
|
||||
@ -32,6 +32,7 @@ export interface ButtonProps {
|
||||
text?: boolean;
|
||||
circle?: boolean;
|
||||
icon?: any;
|
||||
variant?: string;
|
||||
}
|
||||
|
||||
export interface CardProps {
|
||||
@ -182,6 +183,7 @@ export interface FormItemProps {
|
||||
prop?: string;
|
||||
labelWidth?: string | number;
|
||||
rules?: any;
|
||||
extra?: string;
|
||||
}
|
||||
|
||||
export interface InputProps {
|
||||
@ -192,6 +194,7 @@ export interface InputProps {
|
||||
rows?: number;
|
||||
type?: string;
|
||||
size?: FieldSize;
|
||||
autosize?: boolean | { minRows: number; maxRows: number };
|
||||
}
|
||||
|
||||
export interface InputNumberProps {
|
||||
@ -222,8 +225,26 @@ export interface PaginationProps {
|
||||
hideOnSinglePage?: boolean;
|
||||
curPage?: number;
|
||||
pageSizes?: number[];
|
||||
pagesize?: number;
|
||||
pageSize?: number;
|
||||
total?: number;
|
||||
size?: 'large' | 'default' | 'small';
|
||||
}
|
||||
|
||||
export interface PopconfirmProps {
|
||||
title?: string;
|
||||
placement?:
|
||||
| 'top'
|
||||
| 'left'
|
||||
| 'right'
|
||||
| 'bottom'
|
||||
| 'top-left'
|
||||
| 'top-right'
|
||||
| 'bottom-left'
|
||||
| 'bottom-right'
|
||||
| 'left-top'
|
||||
| 'left-bottom'
|
||||
| 'right-top'
|
||||
| 'right-bottom';
|
||||
}
|
||||
|
||||
export interface PopoverProps {
|
||||
@ -295,17 +316,36 @@ export interface SwitchProps {
|
||||
}
|
||||
|
||||
export interface TableProps {
|
||||
columns?: TableColumnOptions[];
|
||||
data?: any[];
|
||||
border?: boolean;
|
||||
maxHeight?: number | string;
|
||||
defaultExpandAll?: boolean;
|
||||
showHeader?: boolean;
|
||||
rowKey?: string;
|
||||
treeProps?: Record<string, any>;
|
||||
emptyText?: string;
|
||||
tooltipEffect?: string;
|
||||
tooltipOptions?: any;
|
||||
showOverflowTooltip?: boolean;
|
||||
spanMethod?: (data: any) => any;
|
||||
}
|
||||
|
||||
export interface TableColumnProps {
|
||||
label?: string;
|
||||
align?: string;
|
||||
fixed?: string | boolean;
|
||||
width?: string | number;
|
||||
export interface TableColumnOptions<T = any> {
|
||||
props: {
|
||||
class?: string;
|
||||
label?: string;
|
||||
fixed?: 'left' | 'right' | boolean;
|
||||
width?: number | string;
|
||||
type?: 'default' | 'selection' | 'index' | 'expand';
|
||||
prop?: string;
|
||||
align?: string;
|
||||
headerAlign?: string;
|
||||
sortable?: boolean;
|
||||
sortOrders?: Array<'ascending' | 'descending'>;
|
||||
selectable?: (row: T, index: number) => boolean;
|
||||
};
|
||||
cell?: (scope: { row: T; $index: number }) => any;
|
||||
}
|
||||
|
||||
export interface TabPaneProps {
|
||||
@ -317,7 +357,7 @@ export interface TabPaneProps {
|
||||
export interface TabsProps {
|
||||
type?: string;
|
||||
editable?: boolean;
|
||||
tabPosition?: string;
|
||||
tabPosition?: 'left' | 'right' | 'top' | 'bottom';
|
||||
modelValue?: string | number;
|
||||
}
|
||||
|
||||
@ -342,33 +382,6 @@ export interface TooltipProps {
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface TreeProps {
|
||||
data?: any[];
|
||||
emptyText?: string;
|
||||
nodeKey?: string;
|
||||
props?: any;
|
||||
renderAfterExpand?: boolean;
|
||||
load?: any;
|
||||
renderContent?: any;
|
||||
highlightCurrent?: boolean;
|
||||
defaultExpandAll?: boolean;
|
||||
checkOnClickNode?: boolean;
|
||||
autoExpandParent?: boolean;
|
||||
defaultExpandedKeys?: any[];
|
||||
showCheckbox?: boolean;
|
||||
checkStrictly?: boolean;
|
||||
defaultCheckedKeys?: any[];
|
||||
currentNodeKey?: string | number;
|
||||
filterNodeMethod?: (value: any, data: any, node: any) => boolean;
|
||||
accordion?: boolean;
|
||||
indent?: number;
|
||||
icon?: any;
|
||||
lazy?: boolean;
|
||||
draggable?: boolean;
|
||||
allowDrag?: (node: any) => boolean;
|
||||
allowDrop?: any;
|
||||
}
|
||||
|
||||
export interface UploadProps {
|
||||
action?: string;
|
||||
autoUpload?: boolean;
|
||||
@ -388,7 +401,7 @@ export interface IconProps {
|
||||
size?: string;
|
||||
}
|
||||
|
||||
export interface TMagicMessage {
|
||||
interface ExtraApi {
|
||||
success: (msg: string) => void;
|
||||
warning: (msg: string) => void;
|
||||
info: (msg: string) => void;
|
||||
@ -396,6 +409,14 @@ export interface TMagicMessage {
|
||||
closeAll: () => void;
|
||||
}
|
||||
|
||||
export type TMagicMessage = ExtraApi &
|
||||
((options: {
|
||||
type?: 'info' | 'success' | 'warning' | 'error';
|
||||
message?: string;
|
||||
dangerouslyUseHTMLString?: boolean;
|
||||
duration?: number;
|
||||
}) => void);
|
||||
|
||||
export type ElMessageBoxShortcutMethod = ((
|
||||
message: string,
|
||||
title: string,
|
||||
@ -635,8 +656,8 @@ export interface Components {
|
||||
| DefineComponent<
|
||||
TableProps,
|
||||
{
|
||||
instance: any;
|
||||
$el: HTMLDivElement | undefined;
|
||||
getEl: () => HTMLElement | undefined;
|
||||
getTableRef: () => any;
|
||||
clearSelection: (...args: any[]) => void;
|
||||
toggleRowSelection: (...args: any[]) => void;
|
||||
toggleRowExpansion: (...args: any[]) => void;
|
||||
@ -647,11 +668,6 @@ export interface Components {
|
||||
props: (props: TableProps) => TableProps;
|
||||
};
|
||||
|
||||
tableColumn: {
|
||||
component: DefineComponent<TableColumnProps, {}, any> | string;
|
||||
props: (props: TableColumnProps) => TableColumnProps;
|
||||
};
|
||||
|
||||
tabPane: {
|
||||
component: DefineComponent<TabPaneProps, {}, any> | string;
|
||||
props: (props: TabPaneProps) => TabPaneProps;
|
||||
@ -677,24 +693,6 @@ export interface Components {
|
||||
props: (props: TooltipProps) => TooltipProps;
|
||||
};
|
||||
|
||||
tree: {
|
||||
component:
|
||||
| DefineComponent<
|
||||
TreeProps,
|
||||
{
|
||||
getData: () => TreeProps['data'];
|
||||
getStore: () => any;
|
||||
filter: (...args: any[]) => any;
|
||||
getNode: (...args: any[]) => any;
|
||||
setCheckedKeys: (...args: any[]) => any;
|
||||
setCurrentKey: (...args: any[]) => any;
|
||||
},
|
||||
any
|
||||
>
|
||||
| string;
|
||||
props: (props: TreeProps) => TreeProps;
|
||||
};
|
||||
|
||||
upload: {
|
||||
component:
|
||||
| DefineComponent<
|
||||
@ -707,9 +705,15 @@ export interface Components {
|
||||
| string;
|
||||
props: (props: UploadProps) => UploadProps;
|
||||
};
|
||||
|
||||
popconfirm: {
|
||||
component: DefineComponent<PopconfirmProps, {}, any> | string;
|
||||
props: (props: PopconfirmProps) => PopconfirmProps;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DesignPluginOptions {
|
||||
adapterType?: string;
|
||||
message?: TMagicMessage;
|
||||
messageBox?: TMagicMessageBox;
|
||||
components?: Components;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "@tmagic/editor",
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
|
||||
@ -12,13 +12,20 @@
|
||||
|
||||
<template v-else-if="data.type === 'button'">
|
||||
<TMagicTooltip v-if="data.tooltip" effect="dark" placement="bottom-start" :content="data.tooltip">
|
||||
<TMagicButton size="small" link :disabled="disabled"
|
||||
><MIcon v-if="data.icon" :icon="data.icon"></MIcon><span>{{ data.text }}</span></TMagicButton
|
||||
>
|
||||
<TMagicButton size="small" link :disabled="disabled">
|
||||
<template #icon v-if="data.icon">
|
||||
<MIcon :icon="data.icon"></MIcon>
|
||||
</template>
|
||||
<template #default v-if="data.text">{{ data.text }}</template>
|
||||
</TMagicButton>
|
||||
</TMagicTooltip>
|
||||
<TMagicButton v-else size="small" link :disabled="disabled" :title="data.text"
|
||||
><MIcon v-if="data.icon" :icon="data.icon"></MIcon><span>{{ data.text }}</span></TMagicButton
|
||||
>
|
||||
|
||||
<TMagicButton v-else size="small" link :disabled="disabled" :title="data.text">
|
||||
<template #icon v-if="data.icon">
|
||||
<MIcon :icon="data.icon"></MIcon>
|
||||
</template>
|
||||
<template #default v-if="data.text">{{ data.text }}</template>
|
||||
</TMagicButton>
|
||||
</template>
|
||||
|
||||
<TMagicDropdown
|
||||
|
||||
@ -2,13 +2,15 @@
|
||||
<div class="m-fields-code-select-col">
|
||||
<div class="code-select-container">
|
||||
<!-- 代码块下拉框 -->
|
||||
<MContainer
|
||||
<MSelect
|
||||
class="select"
|
||||
:config="selectConfig"
|
||||
:name="name"
|
||||
:model="model"
|
||||
:size="size"
|
||||
:prop="prop"
|
||||
@change="onCodeIdChangeHandler"
|
||||
></MContainer>
|
||||
></MSelect>
|
||||
|
||||
<!-- 查看/编辑按钮 -->
|
||||
<TMagicButton
|
||||
@ -28,6 +30,7 @@
|
||||
:key="model[name]"
|
||||
:model="model"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
:params-config="paramsConfig"
|
||||
@change="onParamsChangeHandler"
|
||||
></CodeParams>
|
||||
@ -48,7 +51,8 @@ import {
|
||||
type FieldProps,
|
||||
filterFunction,
|
||||
type FormState,
|
||||
MContainer,
|
||||
MSelect,
|
||||
type SelectConfig,
|
||||
} from '@tmagic/form';
|
||||
|
||||
import CodeParams from '@editor/components/CodeParams.vue';
|
||||
@ -108,7 +112,7 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
const selectConfig = {
|
||||
const selectConfig: SelectConfig = {
|
||||
type: 'select',
|
||||
name: props.name,
|
||||
disable: props.disabled,
|
||||
@ -122,33 +126,26 @@ const selectConfig = {
|
||||
}
|
||||
return [];
|
||||
},
|
||||
onChange: (formState: any, codeId: Id, { setModel, model }: any) => {
|
||||
// 通过下拉框选择的codeId变化后修正model的值,避免写入其他codeId的params
|
||||
paramsConfig.value = getParamItemsConfig(codeId);
|
||||
|
||||
if (paramsConfig.value.length) {
|
||||
setModel('params', createValues(formState, paramsConfig.value, {}, model.params));
|
||||
} else {
|
||||
setModel('params', {});
|
||||
}
|
||||
|
||||
return codeId;
|
||||
},
|
||||
};
|
||||
|
||||
const onCodeIdChangeHandler = (value: any, eventData: ContainerChangeEventData) => {
|
||||
props.model.params = value.params;
|
||||
const onCodeIdChangeHandler = (value: any) => {
|
||||
// 通过下拉框选择的codeId变化后修正model的值,避免写入其他codeId的params
|
||||
paramsConfig.value = getParamItemsConfig(value);
|
||||
|
||||
emit('change', props.model, {
|
||||
changeRecords: eventData.changeRecords?.map((item) => ({
|
||||
prop: `${props.prop.replace(props.name, '')}${item.propPath}`,
|
||||
value: item.value,
|
||||
})) || [
|
||||
{
|
||||
propPath: props.prop,
|
||||
value: value[props.name],
|
||||
},
|
||||
],
|
||||
const changeRecords = [
|
||||
{
|
||||
propPath: props.prop,
|
||||
value,
|
||||
},
|
||||
];
|
||||
|
||||
changeRecords.push({
|
||||
propPath: props.prop.replace(`${props.name}`, 'params'),
|
||||
value: paramsConfig.value.length ? createValues(mForm, paramsConfig.value, {}, props.model.params) : {},
|
||||
});
|
||||
|
||||
emit('change', value, {
|
||||
changeRecords,
|
||||
});
|
||||
};
|
||||
|
||||
@ -156,14 +153,10 @@ const onCodeIdChangeHandler = (value: any, eventData: ContainerChangeEventData)
|
||||
* 参数值修改更新
|
||||
*/
|
||||
const onParamsChangeHandler = (value: any, eventData: ContainerChangeEventData) => {
|
||||
props.model.params = value.params;
|
||||
emit('change', props.model, {
|
||||
...eventData,
|
||||
changeRecords: (eventData.changeRecords || []).map((item) => ({
|
||||
prop: `${props.prop.replace(props.name, '')}${item.propPath}`,
|
||||
value: item.value,
|
||||
})),
|
||||
eventData.changeRecords?.forEach((record) => {
|
||||
record.propPath = `${props.prop.replace(`${props.name}`, '')}${record.propPath}`;
|
||||
});
|
||||
emit('change', props.model[props.name], eventData);
|
||||
};
|
||||
|
||||
const editCode = (id: string) => {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<TMagicSelect
|
||||
v-model="model[name]"
|
||||
:model-value="model[name]"
|
||||
clearable
|
||||
filterable
|
||||
:size="size"
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
<template>
|
||||
<div class="m-fields-data-source-method-select">
|
||||
<div class="data-source-method-select-container">
|
||||
<MContainer
|
||||
<MCascader
|
||||
class="select"
|
||||
:config="cascaderConfig"
|
||||
:model="model"
|
||||
:name="name"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
:prop="prop"
|
||||
@change="onChangeHandler"
|
||||
></MContainer>
|
||||
></MCascader>
|
||||
|
||||
<TMagicTooltip
|
||||
v-if="model[name] && isCustomMethod && hasDataSourceSidePanel"
|
||||
@ -22,11 +25,12 @@
|
||||
<CodeParams
|
||||
v-if="paramsConfig.length"
|
||||
name="params"
|
||||
:key="model[name]"
|
||||
:model="model"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
:params-config="paramsConfig"
|
||||
@change="onChangeHandler"
|
||||
@change="onParamsChangeHandler"
|
||||
></CodeParams>
|
||||
</div>
|
||||
</template>
|
||||
@ -38,13 +42,14 @@ import { Edit, View } from '@element-plus/icons-vue';
|
||||
import type { Id } from '@tmagic/core';
|
||||
import { TMagicButton, TMagicTooltip } from '@tmagic/design';
|
||||
import {
|
||||
type CascaderConfig,
|
||||
type ContainerChangeEventData,
|
||||
createValues,
|
||||
type DataSourceMethodSelectConfig,
|
||||
type FieldProps,
|
||||
filterFunction,
|
||||
type FormState,
|
||||
MContainer,
|
||||
type OnChangeHandlerData,
|
||||
MCascader,
|
||||
} from '@tmagic/form';
|
||||
|
||||
import CodeParams from '@editor/components/CodeParams.vue';
|
||||
@ -100,21 +105,6 @@ const getParamItemsConfig = ([dataSourceId, methodName]: [Id, string] = ['', '']
|
||||
|
||||
const paramsConfig = ref<CodeParamStatement[]>(getParamItemsConfig(props.model[props.name || 'dataSourceMethod']));
|
||||
|
||||
const setParamsConfig = (
|
||||
dataSourceMethod: [Id, string],
|
||||
formState: any = {},
|
||||
setModel: OnChangeHandlerData['setModel'],
|
||||
) => {
|
||||
// 通过下拉框选择的codeId变化后修正model的值,避免写入其他codeId的params
|
||||
paramsConfig.value = dataSourceMethod ? getParamItemsConfig(dataSourceMethod) : [];
|
||||
|
||||
if (paramsConfig.value.length) {
|
||||
setModel('params', createValues(formState, paramsConfig.value, {}, props.model.params));
|
||||
} else {
|
||||
setModel('params', {});
|
||||
}
|
||||
};
|
||||
|
||||
const methodsOptions = computed(
|
||||
() =>
|
||||
dataSources.value
|
||||
@ -132,24 +122,42 @@ const methodsOptions = computed(
|
||||
})) || [],
|
||||
);
|
||||
|
||||
const cascaderConfig = computed(() => ({
|
||||
const cascaderConfig = computed<CascaderConfig>(() => ({
|
||||
type: 'cascader',
|
||||
name: props.name,
|
||||
options: methodsOptions.value,
|
||||
disable: props.disabled,
|
||||
onChange: (formState: any, dataSourceMethod: [Id, string], { setModel }: OnChangeHandlerData) => {
|
||||
setParamsConfig(dataSourceMethod, formState, setModel);
|
||||
|
||||
return dataSourceMethod;
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* 参数值修改更新
|
||||
*/
|
||||
const onChangeHandler = (value: any) => {
|
||||
props.model.params = value.params;
|
||||
emit('change', props.model);
|
||||
paramsConfig.value = getParamItemsConfig(value);
|
||||
|
||||
const changeRecords = [
|
||||
{
|
||||
propPath: props.prop,
|
||||
value,
|
||||
},
|
||||
];
|
||||
|
||||
changeRecords.push({
|
||||
propPath: props.prop.replace(`${props.name}`, 'params'),
|
||||
value: paramsConfig.value.length ? createValues(mForm, paramsConfig.value, {}, props.model.params) : {},
|
||||
});
|
||||
|
||||
emit('change', value, {
|
||||
changeRecords,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 参数值修改更新
|
||||
*/
|
||||
const onParamsChangeHandler = (value: any, eventData: ContainerChangeEventData) => {
|
||||
eventData.changeRecords?.forEach((record) => {
|
||||
record.propPath = `${props.prop.replace(`${props.name}`, '')}${record.propPath}`;
|
||||
});
|
||||
emit('change', props.model[props.name], eventData);
|
||||
};
|
||||
|
||||
const editCodeHandler = () => {
|
||||
|
||||
@ -55,7 +55,7 @@ const config = computed<GroupListConfig>(() => ({
|
||||
{
|
||||
type: 'table',
|
||||
name: 'cond',
|
||||
operateColWidth: 100,
|
||||
operateColWidth: 80,
|
||||
enableToggleMode: false,
|
||||
items: [
|
||||
parentFields.value.length
|
||||
@ -93,7 +93,7 @@ const config = computed<GroupListConfig>(() => ({
|
||||
type: 'cond-op-select',
|
||||
parentFields: parentFields.value,
|
||||
label: '条件',
|
||||
width: 160,
|
||||
width: 140,
|
||||
name: 'op',
|
||||
},
|
||||
{
|
||||
|
||||
@ -219,7 +219,6 @@ const targetCompConfig = computed(() => {
|
||||
display: (mForm: FormState, { model }: { model: Record<any, any> }) => model.actionType === ActionType.COMP,
|
||||
onChange: (MForm: FormState, v: string, { setModel }: OnChangeHandlerData) => {
|
||||
setModel('method', '');
|
||||
return v;
|
||||
},
|
||||
};
|
||||
return { ...defaultTargetCompConfig, ...props.config.targetCompConfig };
|
||||
|
||||
@ -2,13 +2,16 @@
|
||||
<div class="m-fields-page-fragment-select">
|
||||
<div class="page-fragment-select-container">
|
||||
<!-- 页面片下拉框 -->
|
||||
<m-form-container
|
||||
<MSelect
|
||||
class="select"
|
||||
:config="selectConfig"
|
||||
:model="model"
|
||||
:name="name"
|
||||
:size="size"
|
||||
:prop="prop"
|
||||
:disabled="disabled"
|
||||
@change="changeHandler"
|
||||
></m-form-container>
|
||||
></MSelect>
|
||||
<!-- 编辑按钮 -->
|
||||
<Icon v-if="model[name]" class="icon" :icon="Edit" @click="editPageFragment(model[name])"></Icon>
|
||||
</div>
|
||||
@ -20,7 +23,7 @@ import { computed } from 'vue';
|
||||
import { Edit } from '@element-plus/icons-vue';
|
||||
|
||||
import { Id, NodeType } from '@tmagic/core';
|
||||
import { FieldProps, type PageFragmentSelectConfig } from '@tmagic/form';
|
||||
import { FieldProps, MSelect, type PageFragmentSelectConfig, type SelectConfig } from '@tmagic/form';
|
||||
|
||||
import Icon from '@editor/components/Icon.vue';
|
||||
import { useServices } from '@editor/hooks/use-services';
|
||||
@ -32,16 +35,16 @@ defineOptions({
|
||||
const { editorService } = useServices();
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
const props = withDefaults(defineProps<FieldProps<PageFragmentSelectConfig>>(), {
|
||||
withDefaults(defineProps<FieldProps<PageFragmentSelectConfig>>(), {
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const pageList = computed(() =>
|
||||
editorService.get('root')?.items.filter((item) => item.type === NodeType.PAGE_FRAGMENT),
|
||||
);
|
||||
|
||||
const selectConfig = {
|
||||
const selectConfig: SelectConfig = {
|
||||
type: 'select',
|
||||
name: props.name,
|
||||
options: () => {
|
||||
if (pageList.value) {
|
||||
return pageList.value.map((item) => ({
|
||||
@ -53,8 +56,8 @@ const selectConfig = {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
const changeHandler = async () => {
|
||||
emit('change', props.model[props.name]);
|
||||
const changeHandler = (v: Id) => {
|
||||
emit('change', v);
|
||||
};
|
||||
|
||||
const editPageFragment = (id: Id) => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TMagicCollapse class="m-fields-style-setter" :model-value="collapseValue">
|
||||
<TMagicCollapse class="m-fields-style-setter" v-model="collapseValue">
|
||||
<template v-for="(item, index) in list" :key="index">
|
||||
<TMagicCollapseItem :name="`${index}`">
|
||||
<template #title><MIcon :icon="Grid"></MIcon>{{ item.title }}</template>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<span class="help-txt" v-if="item.text">{{ item.text }}</span>
|
||||
<span class="next-input">
|
||||
<input
|
||||
v-model="model[item.name]"
|
||||
:model-value="model[item.name]"
|
||||
placeholder="0"
|
||||
:title="model[item.name]"
|
||||
:disabled="disabled"
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<div v-for="(item, index) in list" :key="index" :class="item.class">
|
||||
<span class="next-input">
|
||||
<input
|
||||
v-model="model[item.name]"
|
||||
:model-value="model[item.name]"
|
||||
placeholder="0"
|
||||
:title="model[item.name]"
|
||||
:disabled="disabled"
|
||||
|
||||
@ -79,7 +79,6 @@ const clickHandler = ({ detail }: Event & { detail: HTMLElement | MNode }) => {
|
||||
id = getIdFromEl()(detail as HTMLElement) || id;
|
||||
}
|
||||
if (id) {
|
||||
props.model[props.name] = id;
|
||||
emit('change', id);
|
||||
mForm?.$emit('field-change', props.prop, id);
|
||||
}
|
||||
@ -102,7 +101,6 @@ const startSelect = () => {
|
||||
|
||||
const deleteHandler = () => {
|
||||
if (props.model) {
|
||||
props.model[props.name] = '';
|
||||
emit('change', '');
|
||||
mForm?.$emit('field-change', props.prop, '');
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<SearchInput @search="filterTextChangeHandler"></SearchInput>
|
||||
|
||||
<slot name="component-list" :component-group-list="list">
|
||||
<TMagicCollapse class="ui-component-panel" :model-value="collapseValue">
|
||||
<TMagicCollapse class="ui-component-panel" v-model="collapseValue">
|
||||
<template v-for="(group, index) in list">
|
||||
<TMagicCollapseItem v-if="group.items && group.items.length" :key="index" :name="`${index}`">
|
||||
<template #title><MIcon :icon="Grid"></MIcon>{{ group.title }}</template>
|
||||
@ -34,7 +34,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref } from 'vue';
|
||||
import { computed, inject, ref, watch } from 'vue';
|
||||
import { Grid } from '@element-plus/icons-vue';
|
||||
import serialize from 'serialize-javascript';
|
||||
|
||||
@ -74,10 +74,19 @@ const list = computed<ComponentGroup[]>(() =>
|
||||
items: group.items.filter((item: ComponentItem) => item.text.includes(searchText.value)),
|
||||
})),
|
||||
);
|
||||
const collapseValue = computed(() =>
|
||||
Array(list.value?.length)
|
||||
.fill(1)
|
||||
.map((x, i) => `${i}`),
|
||||
|
||||
const collapseValue = ref();
|
||||
|
||||
watch(
|
||||
list,
|
||||
() => {
|
||||
collapseValue.value = Array(list.value?.length)
|
||||
.fill(1)
|
||||
.map((x, i) => `${i}`);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
@ -574,8 +574,6 @@ class Editor extends BaseService {
|
||||
nodes.splice(targetIndex, 1, newConfig);
|
||||
this.set('nodes', [...nodes]);
|
||||
|
||||
// update后会触发依赖收集,收集完后会掉stage.update方法
|
||||
|
||||
if (isPage(newConfig) || isPageFragment(newConfig)) {
|
||||
this.set('page', newConfig as MPage | MPageFragment);
|
||||
}
|
||||
@ -591,6 +589,7 @@ class Editor extends BaseService {
|
||||
|
||||
/**
|
||||
* 更新节点
|
||||
* update后会触发依赖收集,收集完后会掉stage.update方法
|
||||
* @param config 新的节点配置,配置中需要有id信息
|
||||
* @returns 更新后的节点配置
|
||||
*/
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
align-items: center;
|
||||
@ -76,5 +77,10 @@
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.t-button {
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
@use "../common/var" as *;
|
||||
|
||||
.background-position-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@ -6,8 +8,8 @@
|
||||
flex-wrap: wrap;
|
||||
width: 80px;
|
||||
height: auto;
|
||||
.el-button {
|
||||
& + .el-button {
|
||||
.tmagic-design-button {
|
||||
& + .tmagic-design-button {
|
||||
margin-left: 2px;
|
||||
}
|
||||
&:nth-child(3n + 1) {
|
||||
@ -15,13 +17,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.t-button--variant-text {
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.position-icon {
|
||||
position: relative;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 1px solid #1d1f24;
|
||||
&.active {
|
||||
background-color: var(--el-color-primary);
|
||||
background-color: $theme-color;
|
||||
&::after {
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||
*
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "@tmagic/element-plus-adapter",
|
||||
"type": "module",
|
||||
"main": "dist/tmagic-element-plus-adapter.umd.cjs",
|
||||
|
||||
27
packages/element-plus-adapter/src/FormItem.vue
Normal file
27
packages/element-plus-adapter/src/FormItem.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<ElFormItem v-bind="itemProps">
|
||||
<template #label>
|
||||
<slot name="label"></slot>
|
||||
</template>
|
||||
<slot></slot>
|
||||
<div v-if="extra" v-html="extra" class="m-form-tip"></div>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { ElFormItem } from 'element-plus';
|
||||
|
||||
import { FormItemProps } from '@tmagic/design';
|
||||
|
||||
defineOptions({
|
||||
name: 'TElAdapterFormItem',
|
||||
});
|
||||
|
||||
const props = defineProps<FormItemProps>();
|
||||
|
||||
const itemProps = computed(() => {
|
||||
const { extra, ...rest } = props;
|
||||
return rest;
|
||||
});
|
||||
</script>
|
||||
92
packages/element-plus-adapter/src/Table.vue
Normal file
92
packages/element-plus-adapter/src/Table.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<ElTable
|
||||
ref="table"
|
||||
:data="data"
|
||||
:border="border"
|
||||
:max-height="maxHeight"
|
||||
:default-expand-all="defaultExpandAll"
|
||||
:show-header="showHeader"
|
||||
:row-key="rowKey"
|
||||
:tree-props="treeProps"
|
||||
:empty-text="emptyText"
|
||||
:show-overflow-tooltip="showOverflowTooltip"
|
||||
:tooltip-effect="tooltipEffect"
|
||||
:tooltip-options="tooltipOptions"
|
||||
:span-method="spanMethod"
|
||||
@sort-change="sortChange"
|
||||
@select="selectHandler"
|
||||
@select-all="selectAllHandler"
|
||||
@selection-change="selectionChangeHandler"
|
||||
@cell-click="cellClickHandler"
|
||||
@expand-change="expandChange"
|
||||
>
|
||||
<template v-for="(item, columnIndex) in columns" :key="columnIndex">
|
||||
<ElTableColumn v-bind="item.props || {}">
|
||||
<template #default="scope" v-if="item.cell">
|
||||
<component :is="item.cell(scope)"></component>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ElTable>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTemplateRef } from 'vue';
|
||||
import { ElTable, ElTableColumn } from 'element-plus';
|
||||
|
||||
import type { TableProps } from '@tmagic/design';
|
||||
|
||||
defineOptions({
|
||||
name: 'TElAdapterTable',
|
||||
});
|
||||
|
||||
const emit = defineEmits(['sort-change', 'select', 'select-all', 'selection-change', 'expand-change', 'cell-click']);
|
||||
|
||||
defineProps<TableProps>();
|
||||
|
||||
const tableRef = useTemplateRef('table');
|
||||
|
||||
const sortChange = (data: any) => {
|
||||
emit('sort-change', data);
|
||||
};
|
||||
|
||||
const selectHandler = (...args: any[]) => {
|
||||
emit('select', ...args);
|
||||
};
|
||||
|
||||
const selectAllHandler = (...args: any[]) => {
|
||||
emit('select-all', ...args);
|
||||
};
|
||||
|
||||
const selectionChangeHandler = (...args: any[]) => {
|
||||
emit('selection-change', ...args);
|
||||
};
|
||||
|
||||
const cellClickHandler = (...args: any[]) => {
|
||||
emit('cell-click', ...args);
|
||||
};
|
||||
|
||||
const expandChange = (...args: any[]) => {
|
||||
emit('expand-change', ...args);
|
||||
};
|
||||
|
||||
const toggleRowSelection = (row: any, selected: boolean) => {
|
||||
tableRef.value?.toggleRowSelection(row, selected);
|
||||
};
|
||||
|
||||
const toggleRowExpansion = (row: any, expanded: boolean) => {
|
||||
tableRef.value?.toggleRowExpansion(row, expanded);
|
||||
};
|
||||
|
||||
const clearSelection = () => {
|
||||
tableRef.value?.clearSelection();
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
getEl: () => tableRef.value?.$el,
|
||||
getTableRef: () => tableRef.value,
|
||||
clearSelection,
|
||||
toggleRowSelection,
|
||||
toggleRowExpansion,
|
||||
});
|
||||
</script>
|
||||
@ -18,7 +18,6 @@ import {
|
||||
ElDropdownItem,
|
||||
ElDropdownMenu,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElIcon,
|
||||
ElInput,
|
||||
ElInputNumber,
|
||||
@ -28,6 +27,7 @@ import {
|
||||
ElOption,
|
||||
ElOptionGroup,
|
||||
ElPagination,
|
||||
ElPopconfirm,
|
||||
ElRadio,
|
||||
ElRadioButton,
|
||||
ElRadioGroup,
|
||||
@ -37,14 +37,11 @@ import {
|
||||
ElStep,
|
||||
ElSteps,
|
||||
ElSwitch,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElTabPane,
|
||||
ElTabs,
|
||||
ElTag,
|
||||
ElTimePicker,
|
||||
ElTooltip,
|
||||
ElTree,
|
||||
ElUpload,
|
||||
useZIndex,
|
||||
} from 'element-plus';
|
||||
@ -76,6 +73,7 @@ import type {
|
||||
OptionGroupProps,
|
||||
OptionProps,
|
||||
PaginationProps,
|
||||
PopconfirmProps,
|
||||
RadioButtonProps,
|
||||
RadioGroupProps,
|
||||
RadioProps,
|
||||
@ -83,18 +81,20 @@ import type {
|
||||
StepProps,
|
||||
StepsProps,
|
||||
SwitchProps,
|
||||
TableColumnProps,
|
||||
TableProps,
|
||||
TabPaneProps,
|
||||
TabsProps,
|
||||
TagProps,
|
||||
TimePickerProps,
|
||||
TooltipProps,
|
||||
TreeProps,
|
||||
UploadProps,
|
||||
} from '@tmagic/design';
|
||||
|
||||
import FormItem from './FormItem.vue';
|
||||
import Table from './Table.vue';
|
||||
|
||||
const adapter: DesignPluginOptions = {
|
||||
adapterType: 'element-plus',
|
||||
useZIndex,
|
||||
message: ElMessage,
|
||||
messageBox: ElMessageBox,
|
||||
@ -111,7 +111,12 @@ const adapter: DesignPluginOptions = {
|
||||
|
||||
button: {
|
||||
component: ElButton as any,
|
||||
props: (props: ButtonProps) => props,
|
||||
props: (props: ButtonProps) => {
|
||||
return {
|
||||
...props,
|
||||
type: props.type === 'default' ? '' : props.type,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
card: {
|
||||
@ -195,7 +200,7 @@ const adapter: DesignPluginOptions = {
|
||||
},
|
||||
|
||||
formItem: {
|
||||
component: ElFormItem as any,
|
||||
component: FormItem as any,
|
||||
props: (props: FormItemProps) => props,
|
||||
},
|
||||
|
||||
@ -275,15 +280,10 @@ const adapter: DesignPluginOptions = {
|
||||
},
|
||||
|
||||
table: {
|
||||
component: ElTable as any,
|
||||
component: Table as any,
|
||||
props: (props: TableProps) => props,
|
||||
},
|
||||
|
||||
tableColumn: {
|
||||
component: ElTableColumn as any,
|
||||
props: (props: TableColumnProps) => props,
|
||||
},
|
||||
|
||||
tabPane: {
|
||||
component: ElTabPane as any,
|
||||
props: (props: TabPaneProps) => props,
|
||||
@ -309,15 +309,15 @@ const adapter: DesignPluginOptions = {
|
||||
props: (props: TooltipProps) => props,
|
||||
},
|
||||
|
||||
tree: {
|
||||
component: ElTree as any,
|
||||
props: (props: TreeProps) => props,
|
||||
},
|
||||
|
||||
upload: {
|
||||
component: ElUpload as any,
|
||||
props: (props: UploadProps) => props,
|
||||
},
|
||||
|
||||
popconfirm: {
|
||||
component: ElPopconfirm as any,
|
||||
props: (props: PopconfirmProps) => props,
|
||||
},
|
||||
},
|
||||
loading: ElLoading.directive,
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "@tmagic/form-schema",
|
||||
"type": "module",
|
||||
"main": "dist/tmagic-form-schema.umd.cjs",
|
||||
|
||||
@ -15,13 +15,14 @@ export interface ChangeRecord {
|
||||
|
||||
export interface OnChangeHandlerData {
|
||||
model: FormValue;
|
||||
values?: FormValue;
|
||||
values?: Readonly<FormValue> | null;
|
||||
parent?: FormValue;
|
||||
formValue?: FormValue;
|
||||
config: any;
|
||||
config: Readonly<any>;
|
||||
prop: string;
|
||||
changeRecords: ChangeRecord[];
|
||||
setModel: (prop: string, value: any) => void;
|
||||
setFormValue: (prop: string, value: any) => void;
|
||||
}
|
||||
|
||||
export type FormValue = Record<string | number, any>;
|
||||
@ -83,6 +84,8 @@ export interface SortProp {
|
||||
order: 'ascending' | 'descending';
|
||||
}
|
||||
|
||||
export type ToolTipConfigType = string | { text?: string; placement?: string };
|
||||
|
||||
export interface FormItem {
|
||||
/** vnode的key值,默认是遍历数组时的index */
|
||||
__key?: string | number;
|
||||
@ -98,7 +101,7 @@ export interface FormItem {
|
||||
/** 额外的提示信息,和 help 类似,当提示文案同时出现时,可以使用这个。 */
|
||||
extra?: string | FilterFunction<string>;
|
||||
/** 配置提示信息 */
|
||||
tooltip?: string | FilterFunction<string>;
|
||||
tooltip?: ToolTipConfigType | FilterFunction<ToolTipConfigType>;
|
||||
/** 是否置灰 */
|
||||
disabled?: boolean | FilterFunction;
|
||||
/** 使用表单中的值作为key,例如配置了text,则使用model.text作为key */
|
||||
@ -123,6 +126,8 @@ export interface FormItem {
|
||||
dynamicKey?: string;
|
||||
/** 是否需要显示`展开更多配置` */
|
||||
expand?: boolean;
|
||||
style?: Record<string, any>;
|
||||
fieldStyle?: Record<string, any>;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@ -193,6 +198,7 @@ export type FilterFunction<T = boolean> = (
|
||||
prop: string;
|
||||
config: any;
|
||||
index?: number;
|
||||
getFormValue: (prop: string) => any;
|
||||
},
|
||||
) => T;
|
||||
|
||||
@ -343,6 +349,7 @@ export interface DisplayConfig extends FormItem {
|
||||
export interface TextConfig extends FormItem, Input {
|
||||
type?: 'text';
|
||||
tooltip?: string;
|
||||
prepend?: string;
|
||||
/** 后置元素,一般为标签或按钮 */
|
||||
append?:
|
||||
| string
|
||||
@ -354,7 +361,10 @@ export interface TextConfig extends FormItem, Input {
|
||||
mForm: FormState | undefined,
|
||||
data: {
|
||||
model: any;
|
||||
values: any;
|
||||
values?: Readonly<FormValue> | null;
|
||||
formValue?: FormValue;
|
||||
setModel: (prop: string, value: any) => void;
|
||||
setFormValue: (prop: string, value: any) => void;
|
||||
},
|
||||
) => void;
|
||||
};
|
||||
@ -366,6 +376,7 @@ export interface TextConfig extends FormItem, Input {
|
||||
export interface TextareaConfig extends FormItem {
|
||||
type: 'textarea';
|
||||
placeholder?: string;
|
||||
rows?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -385,6 +396,7 @@ export interface NumberConfig extends FormItem {
|
||||
*/
|
||||
export interface NumberRangeConfig extends FormItem {
|
||||
type?: 'number-range';
|
||||
clearable?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -429,6 +441,7 @@ export interface CheckboxConfig extends FormItem {
|
||||
type: 'checkbox';
|
||||
activeValue?: number | string;
|
||||
inactiveValue?: number | string;
|
||||
useLabel?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -543,7 +556,8 @@ export interface LinkConfig extends FormItem {
|
||||
mForm: FormState | undefined,
|
||||
data: {
|
||||
model: Record<any, any>;
|
||||
values: Record<any, any>;
|
||||
values?: Readonly<FormValue> | null;
|
||||
formValue?: FormValue;
|
||||
},
|
||||
) => FormConfig);
|
||||
fullscreen?: boolean;
|
||||
@ -640,7 +654,13 @@ export interface TabConfig extends FormItem, ContainerCommonConfig {
|
||||
*/
|
||||
export interface FieldsetConfig extends FormItem, ContainerCommonConfig {
|
||||
type: 'fieldset';
|
||||
checkbox?: boolean;
|
||||
checkbox?:
|
||||
| boolean
|
||||
| {
|
||||
name: string;
|
||||
trueValue?: string | number;
|
||||
falseValue?: string | number;
|
||||
};
|
||||
expand?: boolean;
|
||||
legend?: string;
|
||||
schematic?: string;
|
||||
@ -698,6 +718,7 @@ export interface TableConfig extends FormItem {
|
||||
onSelect?: (mForm: FormState | undefined, data: any) => any;
|
||||
defautSort?: SortProp;
|
||||
defaultSort?: SortProp;
|
||||
/** 是否支持拖拽排序 */
|
||||
dropSort?: boolean;
|
||||
/** 是否显示全屏按钮 */
|
||||
enableFullscreen?: boolean;
|
||||
@ -758,6 +779,10 @@ export interface ComponentConfig extends FormItem {
|
||||
display: any;
|
||||
}
|
||||
|
||||
export interface FlexLayoutConfig extends FormItem, ContainerCommonConfig {
|
||||
type: 'flex-layout';
|
||||
}
|
||||
|
||||
export type ChildConfig =
|
||||
| FormItem
|
||||
| TabConfig
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "@tmagic/form",
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
|
||||
@ -28,10 +28,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { provide, reactive, ref, shallowRef, toRaw, watch, watchEffect } from 'vue';
|
||||
import { provide, reactive, ref, shallowRef, toRaw, useTemplateRef, watch, watchEffect } from 'vue';
|
||||
import { cloneDeep, isEqual } from 'lodash-es';
|
||||
|
||||
import { TMagicForm, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
||||
import { setValueByKeyPath } from '@tmagic/utils';
|
||||
|
||||
import Container from './containers/Container.vue';
|
||||
import { getConfig } from './utils/config';
|
||||
@ -83,7 +84,7 @@ const props = withDefaults(
|
||||
|
||||
const emit = defineEmits(['change', 'error', 'field-input', 'field-change', 'update:stepActive']);
|
||||
|
||||
const tMagicForm = ref<InstanceType<typeof TMagicForm>>();
|
||||
const tMagicFormRef = useTemplateRef('tMagicForm');
|
||||
const initialized = ref(false);
|
||||
const values = ref<FormValue>({});
|
||||
const lastValuesProcessed = ref<FormValue>({});
|
||||
@ -173,7 +174,18 @@ watch(
|
||||
|
||||
const changeHandler = (v: FormValue, eventData: ContainerChangeEventData) => {
|
||||
if (eventData.changeRecords?.length) {
|
||||
changeRecords.value.push(...eventData.changeRecords);
|
||||
for (const record of eventData.changeRecords) {
|
||||
if (record.propPath) {
|
||||
const index = changeRecords.value.findIndex((item) => item.propPath === record.propPath);
|
||||
if (index > -1) {
|
||||
changeRecords.value[index] = record;
|
||||
} else {
|
||||
changeRecords.value.push(record);
|
||||
}
|
||||
|
||||
setValueByKeyPath(record.propPath, record.value, values.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
emit('change', values.value, eventData);
|
||||
};
|
||||
@ -184,6 +196,46 @@ const submitHandler = (e: SubmitEvent) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 通过 name 从 config 中查找对应的 text
|
||||
* @param name - 字段名,支持点分隔的路径格式,如 'a.b.c'
|
||||
* @param config - 表单配置数组
|
||||
* @returns 找到的 text 值,如果未找到则返回 undefined
|
||||
*/
|
||||
const getTextByName = (name: string, config: FormConfig = props.config): string | undefined => {
|
||||
if (!name || !Array.isArray(config)) return undefined;
|
||||
|
||||
const nameParts = name.split('.');
|
||||
|
||||
const findInConfig = (configs: FormConfig, parts: string[]): string | undefined => {
|
||||
if (parts.length === 0) return undefined;
|
||||
|
||||
const [currentPart, ...remainingParts] = parts;
|
||||
|
||||
for (const item of configs) {
|
||||
if (item.name === currentPart) {
|
||||
if (remainingParts.length === 0) {
|
||||
return typeof item.text === 'string' ? item.text : undefined;
|
||||
}
|
||||
|
||||
if (item.items && Array.isArray(item.items)) {
|
||||
const result = findInConfig(item.items, remainingParts);
|
||||
if (result !== undefined) return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.items && Array.isArray(item.items)) {
|
||||
const result = findInConfig(item.items, parts);
|
||||
if (result !== undefined) return result;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return findInConfig(config, nameParts);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
values,
|
||||
lastValuesProcessed,
|
||||
@ -194,29 +246,38 @@ defineExpose({
|
||||
changeHandler,
|
||||
|
||||
resetForm: () => {
|
||||
tMagicForm.value?.resetFields();
|
||||
tMagicFormRef.value?.resetFields();
|
||||
changeRecords.value = [];
|
||||
},
|
||||
|
||||
submitForm: async (native?: boolean): Promise<any> => {
|
||||
try {
|
||||
await tMagicForm.value?.validate();
|
||||
const result = await tMagicFormRef.value?.validate();
|
||||
// tdesign 错误通过返回值返回
|
||||
// element-plus 通过throw error
|
||||
if (result !== true) {
|
||||
throw result;
|
||||
}
|
||||
changeRecords.value = [];
|
||||
return native ? values.value : cloneDeep(toRaw(values.value));
|
||||
} catch (invalidFields: any) {
|
||||
emit('error', invalidFields);
|
||||
|
||||
const error: string[] = [];
|
||||
|
||||
Object.entries(invalidFields).forEach(([, ValidateError]) => {
|
||||
Object.entries(invalidFields).forEach(([prop, ValidateError]) => {
|
||||
(ValidateError as ValidateError[]).forEach(({ field, message }) => {
|
||||
if (field && message) error.push(`${field} -> ${message}`);
|
||||
if (field && !message) error.push(`${field} -> 出现错误`);
|
||||
if (!field && message) error.push(`${message}`);
|
||||
const name = field || prop;
|
||||
const text = getTextByName(name, props.config) || name;
|
||||
|
||||
error.push(`${text} -> ${message}`);
|
||||
});
|
||||
});
|
||||
|
||||
throw new Error(error.join('<br>'));
|
||||
}
|
||||
},
|
||||
|
||||
getTextByName,
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -101,8 +101,9 @@ watchEffect(() => {
|
||||
|
||||
const submitHandler = async () => {
|
||||
try {
|
||||
const changeRecords = form.value?.changeRecords;
|
||||
const values = await form.value?.submitForm();
|
||||
emit('submit', values, { changeRecords: form.value?.changeRecords });
|
||||
emit('submit', values, { changeRecords });
|
||||
} catch (e) {
|
||||
emit('error', e);
|
||||
}
|
||||
|
||||
@ -131,8 +131,9 @@ const closeHandler = () => {
|
||||
|
||||
const save = async () => {
|
||||
try {
|
||||
const changeRecords = form.value?.changeRecords;
|
||||
const values = await form.value?.submitForm();
|
||||
emit('submit', values, { changeRecords: form.value?.changeRecords });
|
||||
emit('submit', values, { changeRecords });
|
||||
} catch (e) {
|
||||
emit('error', e);
|
||||
}
|
||||
|
||||
@ -109,8 +109,9 @@ watchEffect(() => {
|
||||
|
||||
const submitHandler = async () => {
|
||||
try {
|
||||
const changeRecords = form.value?.changeRecords;
|
||||
const values = await form.value?.submitForm();
|
||||
emit('submit', values, { changeRecords: form.value?.changeRecords });
|
||||
emit('submit', values, { changeRecords });
|
||||
} catch (e) {
|
||||
emit('error', e);
|
||||
}
|
||||
|
||||
@ -1,84 +1,55 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="config"
|
||||
:data-tmagic-id="config.id"
|
||||
:data-tmagic-form-item-prop="itemProp"
|
||||
:style="config.tip ? 'display: flex;align-items: baseline;' : ''"
|
||||
:class="`m-form-container m-container-${type || ''} ${config.className || ''}`"
|
||||
:class="`m-form-container m-container-${type || ''} ${config.className || ''}${config.tip ? ' has-tip' : ''}`"
|
||||
:style="config.style"
|
||||
>
|
||||
<m-fields-hidden
|
||||
v-if="type === 'hidden'"
|
||||
:model="model"
|
||||
:config="config"
|
||||
:name="config.name"
|
||||
:disabled="disabled"
|
||||
:prop="itemProp"
|
||||
></m-fields-hidden>
|
||||
<m-fields-hidden v-if="type === 'hidden'" v-bind="fieldsProps" :model="model"></m-fields-hidden>
|
||||
|
||||
<component
|
||||
v-else-if="items && !text && type && display"
|
||||
:key="key(config)"
|
||||
:size="size"
|
||||
v-bind="fieldsProps"
|
||||
:is="tagName"
|
||||
:model="model"
|
||||
:last-values="lastValues"
|
||||
:is-compare="isCompare"
|
||||
:config="config"
|
||||
:disabled="disabled"
|
||||
:name="name"
|
||||
:prop="itemProp"
|
||||
:step-active="stepActive"
|
||||
:expand-more="expand"
|
||||
:label-width="itemLabelWidth"
|
||||
:style="config.fieldStyle"
|
||||
@change="onChangeHandler"
|
||||
@addDiffCount="onAddDiffCount"
|
||||
></component>
|
||||
|
||||
<template v-else-if="type && display && !showDiff">
|
||||
<TMagicFormItem
|
||||
:style="config.tip ? 'flex: 1' : ''"
|
||||
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text }"
|
||||
:prop="itemProp"
|
||||
:label-width="itemLabelWidth"
|
||||
:label-position="config.labelPosition"
|
||||
:rules="rule"
|
||||
>
|
||||
<template #label><span v-html="type === 'checkbox' ? '' : text" :title="config.labelTitle"></span></template>
|
||||
<TMagicTooltip v-if="tooltip">
|
||||
<TMagicFormItem v-bind="formItemProps" :class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text }">
|
||||
<template #label
|
||||
><span v-html="type === 'checkbox' && !config.useLabel ? '' : text" :title="config.labelTitle"></span
|
||||
></template>
|
||||
<TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
|
||||
<component
|
||||
:key="key(config)"
|
||||
:size="size"
|
||||
v-bind="fieldsProps"
|
||||
:is="tagName"
|
||||
:model="model"
|
||||
:last-values="lastValues"
|
||||
:config="config"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
:prop="itemProp"
|
||||
@change="onChangeHandler"
|
||||
@addDiffCount="onAddDiffCount"
|
||||
></component>
|
||||
<template #content>
|
||||
<div v-html="tooltip"></div>
|
||||
<div v-html="tooltip.text"></div>
|
||||
</template>
|
||||
</TMagicTooltip>
|
||||
|
||||
<component
|
||||
v-else
|
||||
:key="key(config)"
|
||||
:size="size"
|
||||
v-bind="fieldsProps"
|
||||
:is="tagName"
|
||||
:model="model"
|
||||
:last-values="lastValues"
|
||||
:config="config"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
:prop="itemProp"
|
||||
@change="onChangeHandler"
|
||||
@addDiffCount="onAddDiffCount"
|
||||
></component>
|
||||
|
||||
<div v-if="extra && type !== 'table'" v-html="extra" class="m-form-tip"></div>
|
||||
</TMagicFormItem>
|
||||
|
||||
<TMagicTooltip v-if="config.tip" placement="left">
|
||||
@ -93,46 +64,18 @@
|
||||
<template v-else-if="type && display && showDiff">
|
||||
<!-- 上次内容 -->
|
||||
<TMagicFormItem
|
||||
:style="config.tip ? 'flex: 1' : ''"
|
||||
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text }"
|
||||
:prop="itemProp"
|
||||
:label-width="itemLabelWidth"
|
||||
:label-position="config.labelPosition"
|
||||
:rules="rule"
|
||||
style="background: #f7dadd"
|
||||
v-bind="formItemProps"
|
||||
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text, 'show-diff': true }"
|
||||
>
|
||||
<template #label><span v-html="type === 'checkbox' ? '' : text" :title="config.labelTitle"></span></template>
|
||||
<TMagicTooltip v-if="tooltip">
|
||||
<component
|
||||
:key="key(config)"
|
||||
:size="size"
|
||||
:is="tagName"
|
||||
:model="lastValues"
|
||||
:config="config"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
:prop="itemProp"
|
||||
@change="onChangeHandler"
|
||||
></component>
|
||||
<TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
|
||||
<component v-bind="fieldsProps" :is="tagName" :model="lastValues" @change="onChangeHandler"></component>
|
||||
<template #content>
|
||||
<div v-html="tooltip"></div>
|
||||
<div v-html="tooltip.text"></div>
|
||||
</template>
|
||||
</TMagicTooltip>
|
||||
|
||||
<component
|
||||
v-else
|
||||
:key="key(config)"
|
||||
:size="size"
|
||||
:is="tagName"
|
||||
:model="lastValues"
|
||||
:config="config"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
:prop="itemProp"
|
||||
@change="onChangeHandler"
|
||||
></component>
|
||||
|
||||
<div v-if="extra" v-html="extra" class="m-form-tip"></div>
|
||||
<component v-else v-bind="fieldsProps" :is="tagName" :model="lastValues" @change="onChangeHandler"></component>
|
||||
</TMagicFormItem>
|
||||
|
||||
<TMagicTooltip v-if="config.tip" placement="left">
|
||||
@ -141,48 +84,22 @@
|
||||
<div v-html="config.tip"></div>
|
||||
</template>
|
||||
</TMagicTooltip>
|
||||
|
||||
<!-- 当前内容 -->
|
||||
<TMagicFormItem
|
||||
v-bind="formItemProps"
|
||||
:style="config.tip ? 'flex: 1' : ''"
|
||||
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text }"
|
||||
:prop="itemProp"
|
||||
:label-width="itemLabelWidth"
|
||||
:label-position="config.labelPosition"
|
||||
:rules="rule"
|
||||
style="background: #def7da"
|
||||
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text, 'show-diff': true }"
|
||||
>
|
||||
<template #label><span v-html="type === 'checkbox' ? '' : text" :title="config.labelTitle"></span></template>
|
||||
<TMagicTooltip v-if="tooltip">
|
||||
<component
|
||||
:key="key(config)"
|
||||
:size="size"
|
||||
:is="tagName"
|
||||
:model="model"
|
||||
:config="config"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
:prop="itemProp"
|
||||
@change="onChangeHandler"
|
||||
></component>
|
||||
<TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
|
||||
<component v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component>
|
||||
<template #content>
|
||||
<div v-html="tooltip"></div>
|
||||
<div v-html="tooltip.text"></div>
|
||||
</template>
|
||||
</TMagicTooltip>
|
||||
|
||||
<component
|
||||
v-else
|
||||
:key="key(config)"
|
||||
:size="size"
|
||||
:is="tagName"
|
||||
:model="model"
|
||||
:config="config"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
:prop="itemProp"
|
||||
@change="onChangeHandler"
|
||||
></component>
|
||||
|
||||
<div v-if="extra" v-html="extra" class="m-form-tip"></div>
|
||||
<component v-else v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component>
|
||||
</TMagicFormItem>
|
||||
|
||||
<TMagicTooltip v-if="config.tip" placement="left">
|
||||
@ -223,14 +140,21 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, ref, toRaw, watch, watchEffect } from 'vue';
|
||||
import { computed, inject, readonly, ref, toRaw, watch, watchEffect } from 'vue';
|
||||
import { WarningFilled } from '@element-plus/icons-vue';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import { TMagicButton, TMagicFormItem, TMagicIcon, TMagicTooltip } from '@tmagic/design';
|
||||
import { setValueByKeyPath } from '@tmagic/utils';
|
||||
import { getValueByKeyPath } from '@tmagic/utils';
|
||||
|
||||
import type { ChildConfig, ContainerChangeEventData, ContainerCommonConfig, FormState, FormValue } from '../schema';
|
||||
import type {
|
||||
ChildConfig,
|
||||
ContainerChangeEventData,
|
||||
ContainerCommonConfig,
|
||||
FormState,
|
||||
FormValue,
|
||||
ToolTipConfigType,
|
||||
} from '../schema';
|
||||
import { display as displayFunction, filterFunction, getRules } from '../utils/form';
|
||||
|
||||
defineOptions({
|
||||
@ -284,10 +208,7 @@ const items = computed(() => (props.config as ContainerCommonConfig).items);
|
||||
|
||||
const itemProp = computed(() => {
|
||||
let n: string | number = '';
|
||||
const { names } = props.config as any;
|
||||
if (names?.[0]) {
|
||||
[n] = names;
|
||||
} else if (name.value) {
|
||||
if (name.value) {
|
||||
n = name.value;
|
||||
} else {
|
||||
return props.prop;
|
||||
@ -311,9 +232,20 @@ const disabled = computed(() => props.disabled || filterFunction(mForm, props.co
|
||||
|
||||
const text = computed(() => filterFunction(mForm, props.config.text, props));
|
||||
|
||||
const tooltip = computed(() => filterFunction(mForm, props.config.tooltip, props));
|
||||
const tooltip = computed(() => {
|
||||
const config = filterFunction<ToolTipConfigType>(mForm, props.config.tooltip, props);
|
||||
if (typeof config === 'string') {
|
||||
return {
|
||||
text: config,
|
||||
placement: 'top',
|
||||
};
|
||||
}
|
||||
|
||||
const extra = computed(() => filterFunction(mForm, props.config.extra, props));
|
||||
return {
|
||||
text: config?.text,
|
||||
placement: config?.placement || 'top',
|
||||
};
|
||||
});
|
||||
|
||||
const rule = computed(() => getRules(mForm, props.config.rules, props));
|
||||
|
||||
@ -334,6 +266,24 @@ const display = computed((): boolean => {
|
||||
return value;
|
||||
});
|
||||
|
||||
const fieldsProps = computed(() => ({
|
||||
size: props.size,
|
||||
config: props.config,
|
||||
name: name.value,
|
||||
disabled: disabled.value,
|
||||
prop: itemProp.value,
|
||||
key: props.config[mForm?.keyProps],
|
||||
style: props.config.fieldStyle,
|
||||
}));
|
||||
|
||||
const formItemProps = computed(() => ({
|
||||
prop: itemProp.value,
|
||||
labelWidth: itemLabelWidth.value,
|
||||
labelPosition: props.config.labelPosition,
|
||||
rules: rule.value,
|
||||
extra: filterFunction(mForm, props.config.extra, props),
|
||||
}));
|
||||
|
||||
const itemLabelWidth = computed(() => props.config.labelWidth ?? props.labelWidth);
|
||||
|
||||
watchEffect(() => {
|
||||
@ -367,6 +317,7 @@ const filterHandler = (filter: any, value: FormValue | number | string) => {
|
||||
formValue: mForm?.values,
|
||||
prop: itemProp.value,
|
||||
config: props.config,
|
||||
getFormValue: (prop: string) => getValueByKeyPath(prop, mForm?.values || props.model),
|
||||
});
|
||||
}
|
||||
|
||||
@ -406,8 +357,29 @@ const isValidName = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const createModelProxy = (
|
||||
target: any,
|
||||
setModelFn: (_key: string, _value: any) => void,
|
||||
pathPrefix: string = '',
|
||||
): any => {
|
||||
return new Proxy(target, {
|
||||
get: (obj, key: string) => {
|
||||
const value = obj[key];
|
||||
if (value && typeof value === 'object') {
|
||||
const newPath = pathPrefix ? `${pathPrefix}.${key}` : key;
|
||||
return createModelProxy(value, setModelFn, newPath);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
set: (obj, key: string, value) => {
|
||||
setModelFn(pathPrefix ? `${pathPrefix}.${key}` : key, value);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeHandler = async function (v: any, eventData: ContainerChangeEventData = {}) {
|
||||
const { filter, onChange, trim, dynamicKey } = props.config as any;
|
||||
const { filter, onChange, trim } = props.config as any;
|
||||
let value: FormValue | number | string | any[] = toRaw(v);
|
||||
const changeRecords = eventData.changeRecords || [];
|
||||
const newChangeRecords = [...changeRecords];
|
||||
@ -416,20 +388,29 @@ const onChangeHandler = async function (v: any, eventData: ContainerChangeEventD
|
||||
value = filterHandler(filter, v);
|
||||
|
||||
if (typeof onChange === 'function') {
|
||||
const setModel = (key: string, value: any) => {
|
||||
if (props.config.name) {
|
||||
newChangeRecords.push({ propPath: itemProp.value.replace(`${props.config.name}`, key), value });
|
||||
} else {
|
||||
newChangeRecords.push({ propPath: itemProp.value, value });
|
||||
}
|
||||
};
|
||||
|
||||
const setFormValue = (key: string, value: any) => {
|
||||
newChangeRecords.push({ propPath: key, value });
|
||||
};
|
||||
|
||||
value =
|
||||
(await onChange(mForm, value, {
|
||||
model: props.model,
|
||||
values: mForm?.initValues,
|
||||
formValue: mForm?.values,
|
||||
model: createModelProxy(props.model, setModel),
|
||||
values: mForm ? readonly(mForm.initValues) : null,
|
||||
formValue: createModelProxy(mForm?.values || {}, setFormValue),
|
||||
prop: itemProp.value,
|
||||
config: props.config,
|
||||
changeRecords: newChangeRecords,
|
||||
setModel: (key: string, value: any) => {
|
||||
setValueByKeyPath(key, value, props.model);
|
||||
if (props.config.name) {
|
||||
newChangeRecords.push({ propPath: itemProp.value.replace(`${props.config.name}`, key), value });
|
||||
}
|
||||
},
|
||||
setModel,
|
||||
setFormValue,
|
||||
getFormValue: (prop: string) => getValueByKeyPath(prop, mForm?.values || props.model),
|
||||
})) ?? value;
|
||||
}
|
||||
value = trimHandler(trim, value) ?? value;
|
||||
@ -440,19 +421,10 @@ const onChangeHandler = async function (v: any, eventData: ContainerChangeEventD
|
||||
let valueProp = itemProp.value;
|
||||
|
||||
if (hasModifyKey(eventData)) {
|
||||
if (dynamicKey) {
|
||||
props.model[eventData.modifyKey!] = value;
|
||||
} else if (isValidName()) {
|
||||
props.model[name.value][eventData.modifyKey!] = value;
|
||||
}
|
||||
|
||||
valueProp = valueProp ? `${valueProp}.${eventData.modifyKey}` : eventData.modifyKey!;
|
||||
|
||||
// 需要清除掉modifyKey,不然往上层抛出后还会被认为需要修改
|
||||
delete eventData.modifyKey;
|
||||
} else if (isValidName() && props.model !== value && (v !== value || props.model[name.value] !== value)) {
|
||||
// field内容下包含field-link时,model===value, 这里避免循环引用
|
||||
props.model[name.value] = value;
|
||||
}
|
||||
|
||||
if (changeRecords.length === 0) {
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
<fieldset v-if="name ? model[name] : model" class="m-fieldset" :style="show ? 'padding: 15px' : 'border: 0'">
|
||||
<component v-if="name && config.checkbox" :is="!show ? 'div' : 'legend'">
|
||||
<TMagicCheckbox
|
||||
v-model="model[name].value"
|
||||
:prop="`${prop}${prop ? '.' : ''}${config.name}.value`"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
:model-value="(name ? model[name] : model)[checkboxName]"
|
||||
:prop="`${prop}${prop ? '.' : ''}${config.name}.${checkboxName}`"
|
||||
:true-value="checkboxTrueValue"
|
||||
:false-value="checkboxFalseValue"
|
||||
@update:modelValue="valueChangeHandler"
|
||||
><span v-html="config.legend"></span><span v-if="config.extra" v-html="config.extra" class="m-form-tip"></span
|
||||
></TMagicCheckbox>
|
||||
@ -99,9 +99,33 @@ const mForm = inject<FormState | undefined>('mForm');
|
||||
|
||||
const name = computed(() => props.config.name || '');
|
||||
|
||||
const checkboxName = computed(() => {
|
||||
if (typeof props.config.checkbox === 'object' && typeof props.config.checkbox.name === 'string') {
|
||||
return props.config.checkbox.name;
|
||||
}
|
||||
|
||||
return 'value';
|
||||
});
|
||||
|
||||
const checkboxTrueValue = computed(() => {
|
||||
if (typeof props.config.checkbox === 'object' && typeof props.config.checkbox.trueValue !== 'undefined') {
|
||||
return props.config.checkbox.trueValue;
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
|
||||
const checkboxFalseValue = computed(() => {
|
||||
if (typeof props.config.checkbox === 'object' && typeof props.config.checkbox.falseValue !== 'undefined') {
|
||||
return props.config.checkbox.falseValue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
const show = computed(() => {
|
||||
if (props.config.expand && name.value) {
|
||||
return props.model[name.value]?.value;
|
||||
if (props.config.expand && checkboxName.value) {
|
||||
return (name.value ? props.model[name.value] : props.model)?.[checkboxName.value] === checkboxTrueValue.value;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@ -114,7 +138,7 @@ const lWidth = computed(() => {
|
||||
});
|
||||
|
||||
const valueChangeHandler = (value: number | boolean) => {
|
||||
emit('change', value, { modifyKey: 'value' });
|
||||
emit('change', value, { modifyKey: checkboxName.value });
|
||||
};
|
||||
|
||||
const changeHandler = (v: any, eventData: ContainerChangeEventData) => emit('change', v, eventData);
|
||||
|
||||
59
packages/form/src/containers/FlexLayout.vue
Normal file
59
packages/form/src/containers/FlexLayout.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="m-form-flex-layout" :style="{ display: 'flex', flexWrap: 'wrap', gap }">
|
||||
<Container
|
||||
v-for="(item, index) in config.items"
|
||||
:key="(item as Record<string, any>)[mForm?.keyProp || '__key'] ?? index"
|
||||
:config="item"
|
||||
:model="name ? model[name] : model"
|
||||
:lastValues="name ? lastValues[name] : lastValues"
|
||||
:is-compare="isCompare"
|
||||
:prop="prop"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
:label-width="config.labelWidth || labelWidth"
|
||||
@change="changeHandler"
|
||||
@addDiffCount="onAddDiffCount()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from 'vue';
|
||||
|
||||
import type { FlexLayoutConfig } from '@tmagic/form-schema';
|
||||
|
||||
import type { ContainerChangeEventData, FormState } from '../schema';
|
||||
|
||||
import Container from './Container.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'MFormFlexLayout',
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
model: any;
|
||||
lastValues?: any;
|
||||
isCompare?: boolean;
|
||||
config: FlexLayoutConfig;
|
||||
name?: string;
|
||||
labelWidth?: string;
|
||||
prop?: string;
|
||||
size?: string;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [v: any, eventData: ContainerChangeEventData];
|
||||
addDiffCount: [];
|
||||
}>();
|
||||
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
|
||||
const gap = computed(() => props.config.gap || '16px');
|
||||
|
||||
const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
|
||||
emit('change', props.model, eventData);
|
||||
};
|
||||
|
||||
const onAddDiffCount = () => emit('addDiffCount');
|
||||
</script>
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="m-fields-group-list">
|
||||
<div v-if="config.extra" v-html="config.extra" style="color: rgba(0, 0, 0, 0.45)"></div>
|
||||
<div v-if="!model[name] || !model[name].length" class="el-table__empty-block">
|
||||
<span class="el-table__empty-text">暂无数据</span>
|
||||
<span class="el-table__empty-text t-table__empty">暂无数据</span>
|
||||
</div>
|
||||
|
||||
<MFieldsGroupListItem
|
||||
@ -26,16 +26,21 @@
|
||||
@addDiffCount="onAddDiffCount()"
|
||||
></MFieldsGroupListItem>
|
||||
|
||||
<TMagicButton
|
||||
v-if="addable"
|
||||
type="primary"
|
||||
:size="config.enableToggleMode ? 'small' : 'default'"
|
||||
:disabled="disabled"
|
||||
@click="addHandler"
|
||||
>新增</TMagicButton
|
||||
>
|
||||
|
||||
<TMagicButton :icon="Grid" size="small" @click="toggleMode" v-if="config.enableToggleMode">切换为表格</TMagicButton>
|
||||
<div class="m-fields-group-list-footer">
|
||||
<TMagicButton v-if="config.enableToggleMode" :icon="Grid" size="small" @click="toggleMode"
|
||||
>切换为表格</TMagicButton
|
||||
>
|
||||
<div style="display: flex; justify-content: flex-end; flex: 1">
|
||||
<TMagicButton
|
||||
v-if="addable"
|
||||
type="primary"
|
||||
:size="config.enableToggleMode ? 'small' : 'default'"
|
||||
:disabled="disabled"
|
||||
@click="addHandler"
|
||||
>新增</TMagicButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,90 +1,92 @@
|
||||
<template>
|
||||
<div class="m-fields-group-list-item">
|
||||
<div>
|
||||
<TMagicButton link :disabled="disabled" @click="expandHandler">
|
||||
<TMagicIcon><CaretBottom v-if="expand" /><CaretRight v-else /></TMagicIcon>{{ title }}
|
||||
</TMagicButton>
|
||||
<TMagicCard class="m-fields-group-list-item" :body-style="{ display: expand ? 'block' : 'none' }">
|
||||
<template #header>
|
||||
<div>
|
||||
<TMagicButton link :disabled="disabled" @click="expandHandler">
|
||||
<TMagicIcon><CaretBottom v-if="expand" /><CaretRight v-else /></TMagicIcon>{{ title }}
|
||||
</TMagicButton>
|
||||
|
||||
<TMagicButton
|
||||
v-show="showDelete"
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
:icon="Delete"
|
||||
:disabled="disabled"
|
||||
@click="removeHandler"
|
||||
></TMagicButton>
|
||||
|
||||
<TMagicButton
|
||||
v-if="copyable"
|
||||
link
|
||||
size="small"
|
||||
type="primary"
|
||||
:icon="DocumentCopy"
|
||||
:disabled="disabled"
|
||||
@click="copyHandler"
|
||||
>复制</TMagicButton
|
||||
>
|
||||
|
||||
<template v-if="movable">
|
||||
<TMagicButton
|
||||
v-show="index !== 0"
|
||||
v-show="showDelete"
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
:icon="Delete"
|
||||
:disabled="disabled"
|
||||
@click="removeHandler"
|
||||
></TMagicButton>
|
||||
|
||||
<TMagicButton
|
||||
v-if="copyable"
|
||||
link
|
||||
size="small"
|
||||
type="primary"
|
||||
:icon="DocumentCopy"
|
||||
:disabled="disabled"
|
||||
:icon="CaretTop"
|
||||
@click="changeOrder(-1)"
|
||||
>上移</TMagicButton
|
||||
@click="copyHandler"
|
||||
>复制</TMagicButton
|
||||
>
|
||||
<TMagicButton
|
||||
v-show="index !== length - 1"
|
||||
link
|
||||
size="small"
|
||||
:disabled="disabled"
|
||||
:icon="CaretBottom"
|
||||
@click="changeOrder(1)"
|
||||
>下移</TMagicButton
|
||||
>
|
||||
</template>
|
||||
|
||||
<TMagicPopover
|
||||
v-if="config.moveSpecifyLocation"
|
||||
trigger="click"
|
||||
placement="top"
|
||||
width="200"
|
||||
:visible="moveSpecifyLocationVisible"
|
||||
>
|
||||
<template #reference>
|
||||
<template v-if="movable">
|
||||
<TMagicButton
|
||||
v-show="index !== 0"
|
||||
link
|
||||
size="small"
|
||||
type="primary"
|
||||
:icon="Position"
|
||||
:disabled="disabled"
|
||||
@click="moveSpecifyLocationVisible = true"
|
||||
>移动至</TMagicButton
|
||||
:icon="CaretTop"
|
||||
@click="changeOrder(-1)"
|
||||
>上移</TMagicButton
|
||||
>
|
||||
<TMagicButton
|
||||
v-show="index !== length - 1"
|
||||
link
|
||||
size="small"
|
||||
:disabled="disabled"
|
||||
:icon="CaretBottom"
|
||||
@click="changeOrder(1)"
|
||||
>下移</TMagicButton
|
||||
>
|
||||
</template>
|
||||
<div>
|
||||
<div>
|
||||
第<TMagicInputNumber
|
||||
style="margin: 0 5px"
|
||||
v-model="moveSpecifyLocationIndex"
|
||||
size="small"
|
||||
:min="1"
|
||||
:disabled="disabled"
|
||||
></TMagicInputNumber
|
||||
>行
|
||||
</div>
|
||||
<div style="text-align: right; margin-top: 20px">
|
||||
<TMagicButton size="small" text @click="moveSpecifyLocationVisible = false">取消</TMagicButton>
|
||||
<TMagicButton size="small" type="primary" @click="moveSpecifyLocationHandler">确认</TMagicButton>
|
||||
</div>
|
||||
</div>
|
||||
</TMagicPopover>
|
||||
|
||||
<span v-if="itemExtra" v-html="itemExtra" class="m-form-tip"></span>
|
||||
</div>
|
||||
<TMagicPopover
|
||||
v-if="config.moveSpecifyLocation"
|
||||
trigger="click"
|
||||
placement="top"
|
||||
width="200"
|
||||
:visible="moveSpecifyLocationVisible"
|
||||
>
|
||||
<template #reference>
|
||||
<TMagicButton
|
||||
link
|
||||
size="small"
|
||||
type="primary"
|
||||
:icon="Position"
|
||||
:disabled="disabled"
|
||||
@click="moveSpecifyLocationVisible = true"
|
||||
>移动至</TMagicButton
|
||||
>
|
||||
</template>
|
||||
<div>
|
||||
<div>
|
||||
第<TMagicInputNumber
|
||||
style="margin: 0 5px"
|
||||
v-model="moveSpecifyLocationIndex"
|
||||
size="small"
|
||||
:min="1"
|
||||
:disabled="disabled"
|
||||
></TMagicInputNumber
|
||||
>行
|
||||
</div>
|
||||
<div style="text-align: right; margin-top: 20px">
|
||||
<TMagicButton size="small" text @click="moveSpecifyLocationVisible = false">取消</TMagicButton>
|
||||
<TMagicButton size="small" type="primary" @click="moveSpecifyLocationHandler">确认</TMagicButton>
|
||||
</div>
|
||||
</div>
|
||||
</TMagicPopover>
|
||||
|
||||
<span v-if="itemExtra" v-html="itemExtra" class="m-form-tip"></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Container
|
||||
v-if="expand"
|
||||
@ -99,14 +101,14 @@
|
||||
@change="changeHandler"
|
||||
@addDiffCount="onAddDiffCount()"
|
||||
></Container>
|
||||
</div>
|
||||
</TMagicCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, ref } from 'vue';
|
||||
import { CaretBottom, CaretRight, CaretTop, Delete, DocumentCopy, Position } from '@element-plus/icons-vue';
|
||||
|
||||
import { TMagicButton, TMagicIcon, TMagicInputNumber, TMagicPopover } from '@tmagic/design';
|
||||
import { TMagicButton, TMagicCard, TMagicIcon, TMagicInputNumber, TMagicPopover } from '@tmagic/design';
|
||||
|
||||
import type { ContainerChangeEventData, FormState, GroupListConfig } from '../schema';
|
||||
import { filterFunction } from '../utils/form';
|
||||
|
||||
@ -1,681 +0,0 @@
|
||||
<template>
|
||||
<div class="m-fields-table-wrap">
|
||||
<teleport to="body" :disabled="!isFullscreen">
|
||||
<div ref="mTable" class="m-fields-table" :class="{ 'm-fields-table-item-extra': config.itemExtra }">
|
||||
<span v-if="config.extra" style="color: rgba(0, 0, 0, 0.45)" v-html="config.extra"></span>
|
||||
<TMagicTooltip content="拖拽可排序" placement="left-start" :disabled="config.dropSort !== true">
|
||||
<TMagicTable
|
||||
v-if="model[modelName]"
|
||||
ref="tMagicTable"
|
||||
style="width: 100%"
|
||||
:row-key="config.rowKey || 'id'"
|
||||
:data="data"
|
||||
:lastData="lastData"
|
||||
:border="config.border"
|
||||
:max-height="config.maxHeight"
|
||||
:default-expand-all="true"
|
||||
:key="updateKey"
|
||||
@select="selectHandle"
|
||||
@sort-change="sortChange"
|
||||
>
|
||||
<TMagicTableColumn v-if="config.itemExtra && !config.dropSort" :fixed="'left'" width="30" type="expand">
|
||||
<template v-slot="scope">
|
||||
<span v-html="itemExtra(config.itemExtra, scope.$index)" class="m-form-tip"></span>
|
||||
</template>
|
||||
</TMagicTableColumn>
|
||||
|
||||
<TMagicTableColumn
|
||||
label="操作"
|
||||
:width="config.operateColWidth || 100"
|
||||
align="center"
|
||||
:fixed="config.fixed === false ? undefined : 'left'"
|
||||
>
|
||||
<template v-slot="scope">
|
||||
<slot name="operateCol" :scope="scope"></slot>
|
||||
<TMagicButton
|
||||
v-show="showDelete(scope.$index + 1 + pagecontext * pagesize - 1)"
|
||||
size="small"
|
||||
type="danger"
|
||||
link
|
||||
title="删除"
|
||||
:icon="Delete"
|
||||
@click="removeHandler(scope.$index + 1 + pagecontext * pagesize - 1)"
|
||||
></TMagicButton>
|
||||
|
||||
<TMagicButton
|
||||
v-if="copyable(scope.$index + 1 + pagecontext * pagesize - 1)"
|
||||
link
|
||||
size="small"
|
||||
type="primary"
|
||||
title="复制"
|
||||
:icon="DocumentCopy"
|
||||
:disabled="disabled"
|
||||
@click="copyHandler(scope.$index + 1 + pagecontext * pagesize - 1)"
|
||||
></TMagicButton>
|
||||
</template>
|
||||
</TMagicTableColumn>
|
||||
|
||||
<TMagicTableColumn v-if="sort && model[modelName] && model[modelName].length > 1" label="排序" width="60">
|
||||
<template v-slot="scope">
|
||||
<TMagicTooltip
|
||||
v-if="scope.$index + 1 + pagecontext * pagesize - 1 !== 0"
|
||||
content="点击上移,双击置顶"
|
||||
placement="top"
|
||||
>
|
||||
<TMagicButton
|
||||
plain
|
||||
size="small"
|
||||
type="primary"
|
||||
:icon="ArrowUp"
|
||||
:disabled="disabled"
|
||||
link
|
||||
@click="upHandler(scope.$index + 1 + pagecontext * pagesize - 1)"
|
||||
@dblclick="topHandler(scope.$index + 1 + pagecontext * pagesize - 1)"
|
||||
></TMagicButton>
|
||||
</TMagicTooltip>
|
||||
<TMagicTooltip
|
||||
v-if="scope.$index + 1 + pagecontext * pagesize - 1 !== model[modelName].length - 1"
|
||||
content="点击下移,双击置底"
|
||||
placement="top"
|
||||
>
|
||||
<TMagicButton
|
||||
plain
|
||||
size="small"
|
||||
type="primary"
|
||||
:icon="ArrowDown"
|
||||
:disabled="disabled"
|
||||
link
|
||||
@click="downHandler(scope.$index + 1 + pagecontext * pagesize - 1)"
|
||||
@dblclick="bottomHandler(scope.$index + 1 + pagecontext * pagesize - 1)"
|
||||
></TMagicButton>
|
||||
</TMagicTooltip>
|
||||
</template>
|
||||
</TMagicTableColumn>
|
||||
|
||||
<TMagicTableColumn
|
||||
v-if="selection"
|
||||
align="center"
|
||||
header-align="center"
|
||||
type="selection"
|
||||
width="45"
|
||||
></TMagicTableColumn>
|
||||
|
||||
<TMagicTableColumn width="60" label="序号" v-if="showIndex && config.showIndex">
|
||||
<template v-slot="scope">{{ scope.$index + 1 + pagecontext * pagesize }}</template>
|
||||
</TMagicTableColumn>
|
||||
|
||||
<template v-for="(column, index) in config.items">
|
||||
<TMagicTableColumn
|
||||
v-if="column.type !== 'hidden' && display(column.display)"
|
||||
:prop="column.name"
|
||||
:width="column.width"
|
||||
:label="column.label"
|
||||
:sortable="column.sortable"
|
||||
:sort-orders="['ascending', 'descending']"
|
||||
:key="column[mForm?.keyProp || '__key'] ?? index"
|
||||
:class-name="config.dropSort === true ? 'el-table__column--dropable' : ''"
|
||||
>
|
||||
<template #default="scope">
|
||||
<Container
|
||||
v-if="scope.$index > -1"
|
||||
labelWidth="0"
|
||||
:disabled="disabled"
|
||||
:prop="getProp(scope.$index)"
|
||||
:rules="column.rules"
|
||||
:config="makeConfig(column, scope.row)"
|
||||
:model="scope.row"
|
||||
:lastValues="lastData[scope.$index]"
|
||||
:is-compare="isCompare"
|
||||
:size="size"
|
||||
@change="changeHandler"
|
||||
@addDiffCount="onAddDiffCount()"
|
||||
></Container>
|
||||
</template>
|
||||
</TMagicTableColumn>
|
||||
</template>
|
||||
</TMagicTable>
|
||||
</TMagicTooltip>
|
||||
<slot></slot>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; margin: 10px 0">
|
||||
<TMagicButton v-if="addable" size="small" type="primary" :disabled="disabled" plain @click="newHandler()"
|
||||
>新增一行</TMagicButton
|
||||
>
|
||||
|
||||
<div style="display: flex">
|
||||
<TMagicButton
|
||||
:icon="Grid"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="toggleMode"
|
||||
v-if="enableToggleMode && config.enableToggleMode !== false && !isFullscreen"
|
||||
>展开配置</TMagicButton
|
||||
>
|
||||
<TMagicButton
|
||||
:icon="FullScreen"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="toggleFullscreen"
|
||||
v-if="config.enableFullscreen !== false"
|
||||
>
|
||||
{{ isFullscreen ? '退出全屏' : '全屏编辑' }}
|
||||
</TMagicButton>
|
||||
<TMagicUpload
|
||||
v-if="importable"
|
||||
style="display: inline-block"
|
||||
ref="excelBtn"
|
||||
action="/noop"
|
||||
:disabled="disabled"
|
||||
:on-change="excelHandler"
|
||||
:auto-upload="false"
|
||||
>
|
||||
<TMagicButton size="small" type="success" :disabled="disabled" plain>导入EXCEL</TMagicButton>
|
||||
</TMagicUpload>
|
||||
<TMagicButton
|
||||
v-if="importable"
|
||||
size="small"
|
||||
type="warning"
|
||||
:disabled="disabled"
|
||||
plain
|
||||
@click="clearHandler()"
|
||||
>清空</TMagicButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bottom" style="text-align: right" v-if="config.pagination">
|
||||
<TMagicPagination
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:hide-on-single-page="model[modelName].length < pagesize"
|
||||
:current-page="pagecontext + 1"
|
||||
:page-sizes="[pagesize, 60, 120, 300]"
|
||||
:page-size="pagesize"
|
||||
:total="model[modelName].length"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
</TMagicPagination>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, onMounted, ref, toRefs, watchEffect } from 'vue';
|
||||
import { ArrowDown, ArrowUp, Delete, DocumentCopy, FullScreen, Grid } from '@element-plus/icons-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import Sortable, { SortableEvent } from 'sortablejs';
|
||||
|
||||
import {
|
||||
TMagicButton,
|
||||
tMagicMessage,
|
||||
TMagicPagination,
|
||||
TMagicTable,
|
||||
TMagicTableColumn,
|
||||
TMagicTooltip,
|
||||
TMagicUpload,
|
||||
useZIndex,
|
||||
} from '@tmagic/design';
|
||||
import { asyncLoadJs } from '@tmagic/utils';
|
||||
|
||||
import type { ContainerChangeEventData, FormState, SortProp, TableColumnConfig, TableConfig } from '../schema';
|
||||
import { display as displayFunc, initValue } from '../utils/form';
|
||||
|
||||
import Container from './Container.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'MFormTable',
|
||||
});
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
model: any;
|
||||
lastValues?: any;
|
||||
isCompare?: boolean;
|
||||
config: TableConfig;
|
||||
name: string;
|
||||
prop?: string;
|
||||
labelWidth?: string;
|
||||
sort?: boolean;
|
||||
disabled?: boolean;
|
||||
sortKey?: string;
|
||||
text?: string;
|
||||
size?: string;
|
||||
enableToggleMode?: boolean;
|
||||
showIndex?: boolean;
|
||||
}>(),
|
||||
{
|
||||
prop: '',
|
||||
sortKey: '',
|
||||
enableToggleMode: true,
|
||||
showIndex: true,
|
||||
lastValues: () => ({}),
|
||||
isCompare: false,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits(['change', 'select', 'addDiffCount']);
|
||||
|
||||
let timer: any | null = null;
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
|
||||
const { nextZIndex } = useZIndex();
|
||||
|
||||
const tMagicTable = ref<InstanceType<typeof TMagicTable>>();
|
||||
const excelBtn = ref<InstanceType<typeof TMagicUpload>>();
|
||||
const mTable = ref<HTMLDivElement>();
|
||||
|
||||
const pagesize = ref(10);
|
||||
const pagecontext = ref(0);
|
||||
const updateKey = ref(1);
|
||||
const isFullscreen = ref(false);
|
||||
|
||||
const modelName = computed(() => props.name || props.config.name || '');
|
||||
|
||||
const getDataByPage = (data: any[] = []) =>
|
||||
data.filter(
|
||||
(item: any, index: number) =>
|
||||
index >= pagecontext.value * pagesize.value && index + 1 <= (pagecontext.value + 1) * pagesize.value,
|
||||
);
|
||||
|
||||
const pageinationData = computed(() => getDataByPage(props.model[modelName.value]));
|
||||
|
||||
const data = computed(() => (props.config.pagination ? pageinationData.value : props.model[modelName.value]));
|
||||
|
||||
const lastData = computed(() =>
|
||||
props.config.pagination ? getDataByPage(props.lastValues[modelName.value]) : props.lastValues[modelName.value] || [],
|
||||
);
|
||||
|
||||
const sortChange = ({ prop, order }: SortProp) => {
|
||||
if (order === 'ascending') {
|
||||
props.model[modelName.value] = props.model[modelName.value].sort((a: any, b: any) => a[prop] - b[prop]);
|
||||
} else if (order === 'descending') {
|
||||
props.model[modelName.value] = props.model[modelName.value].sort((a: any, b: any) => b[prop] - a[prop]);
|
||||
}
|
||||
};
|
||||
|
||||
const swapArray = (index1: number, index2: number) => {
|
||||
props.model[modelName.value].splice(index1, 0, props.model[modelName.value].splice(index2, 1)[0]);
|
||||
|
||||
if (props.sortKey) {
|
||||
for (let i = props.model[modelName.value].length - 1, v = 0; i >= 0; i--, v++) {
|
||||
props.model[modelName.value][v][props.sortKey] = i;
|
||||
}
|
||||
}
|
||||
mForm?.$emit('field-change', props.prop, props.model[modelName.value]);
|
||||
};
|
||||
|
||||
let sortable: Sortable | undefined;
|
||||
const rowDrop = () => {
|
||||
sortable?.destroy();
|
||||
const tableEl = tMagicTable.value?.instance.$el;
|
||||
const tBodyEl = tableEl?.querySelector('.el-table__body > tbody');
|
||||
if (!tBodyEl) {
|
||||
return;
|
||||
}
|
||||
sortable = Sortable.create(tBodyEl, {
|
||||
draggable: '.tmagic-design-table-row',
|
||||
filter: 'input', // 表单组件选字操作和触发拖拽会冲突,优先保证选字操作
|
||||
preventOnFilter: false, // 允许选字
|
||||
direction: 'vertical',
|
||||
onEnd: ({ newIndex, oldIndex }: SortableEvent) => {
|
||||
if (typeof newIndex === 'undefined') return;
|
||||
if (typeof oldIndex === 'undefined') return;
|
||||
swapArray(newIndex, oldIndex);
|
||||
emit('change', props.model[modelName.value]);
|
||||
mForm?.$emit('field-change', props.prop, props.model[modelName.value]);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const newHandler = async (row?: any) => {
|
||||
if (props.config.max && props.model[modelName.value].length >= props.config.max) {
|
||||
tMagicMessage.error(`最多新增配置不能超过${props.config.max}条`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof props.config.beforeAddRow === 'function') {
|
||||
const beforeCheckRes = props.config.beforeAddRow(mForm, {
|
||||
model: props.model[modelName.value],
|
||||
formValue: mForm?.values,
|
||||
prop: props.prop,
|
||||
});
|
||||
if (!beforeCheckRes) return;
|
||||
}
|
||||
|
||||
const columns = props.config.items;
|
||||
const enumValues = props.config.enum || [];
|
||||
let enumV = [];
|
||||
const { length } = props.model[modelName.value];
|
||||
const key = props.config.key || 'id';
|
||||
let inputs: any = {};
|
||||
|
||||
if (enumValues.length) {
|
||||
if (length >= enumValues.length) {
|
||||
return;
|
||||
}
|
||||
enumV = enumValues.filter((item) => {
|
||||
let i = 0;
|
||||
for (; i < length; i++) {
|
||||
if (item[key] === props.model[modelName.value][i][key]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i === length;
|
||||
});
|
||||
|
||||
if (enumV.length > 0) {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
inputs = enumV[0];
|
||||
}
|
||||
} else if (Array.isArray(row)) {
|
||||
columns.forEach((column, index) => {
|
||||
column.name && (inputs[column.name] = row[index]);
|
||||
});
|
||||
} else {
|
||||
if (typeof props.config.defaultAdd === 'function') {
|
||||
inputs = await props.config.defaultAdd(mForm, {
|
||||
model: props.model[modelName.value],
|
||||
formValue: mForm?.values,
|
||||
});
|
||||
} else if (props.config.defaultAdd) {
|
||||
inputs = props.config.defaultAdd;
|
||||
}
|
||||
|
||||
inputs = await initValue(mForm, {
|
||||
config: columns,
|
||||
initValues: inputs,
|
||||
});
|
||||
}
|
||||
|
||||
if (props.sortKey && length) {
|
||||
inputs[props.sortKey] = props.model[modelName.value][length - 1][props.sortKey] - 1;
|
||||
}
|
||||
|
||||
props.model[modelName.value].push(inputs);
|
||||
|
||||
emit('change', props.model[modelName.value], {
|
||||
changeRecords: [
|
||||
{
|
||||
propPath: `${props.prop}.${props.model[modelName.value].length - 1}`,
|
||||
value: inputs,
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (props.config.defautSort) {
|
||||
sortChange(props.config.defautSort);
|
||||
} else if (props.config.defaultSort) {
|
||||
sortChange(props.config.defaultSort);
|
||||
}
|
||||
|
||||
if (props.sort && props.sortKey) {
|
||||
props.model[modelName.value].sort((a: any, b: any) => b[props.sortKey] - a[props.sortKey]);
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.config.dropSort) {
|
||||
rowDrop();
|
||||
}
|
||||
});
|
||||
|
||||
const addable = computed(() => {
|
||||
if (!props.model[modelName.value].length) {
|
||||
return true;
|
||||
}
|
||||
if (typeof props.config.addable === 'function') {
|
||||
return props.config.addable(mForm, {
|
||||
model: props.model[modelName.value],
|
||||
formValue: mForm?.values,
|
||||
prop: props.prop,
|
||||
});
|
||||
}
|
||||
return typeof props.config.addable === 'undefined' ? true : props.config.addable;
|
||||
});
|
||||
|
||||
const selection = computed(() => {
|
||||
if (typeof props.config.selection === 'function') {
|
||||
return props.config.selection(mForm, { model: props.model[modelName.value] });
|
||||
}
|
||||
return props.config.selection;
|
||||
});
|
||||
|
||||
const importable = computed(() => {
|
||||
if (typeof props.config.importable === 'function') {
|
||||
return props.config.importable(mForm, {
|
||||
formValue: mForm?.values,
|
||||
model: props.model[modelName.value],
|
||||
});
|
||||
}
|
||||
return typeof props.config.importable === 'undefined' ? false : props.config.importable;
|
||||
});
|
||||
|
||||
const display = (fuc: any) => displayFunc(mForm, fuc, props);
|
||||
|
||||
const itemExtra = (fuc: any, index: number) => {
|
||||
if (typeof fuc === 'function') {
|
||||
return fuc(mForm, {
|
||||
values: mForm?.initValues,
|
||||
model: props.model,
|
||||
formValue: mForm ? mForm.values : props.model,
|
||||
prop: props.prop,
|
||||
index,
|
||||
});
|
||||
}
|
||||
|
||||
return fuc;
|
||||
};
|
||||
|
||||
const removeHandler = (index: number) => {
|
||||
if (props.disabled) return;
|
||||
props.model[modelName.value].splice(index, 1);
|
||||
emit('change', props.model[modelName.value]);
|
||||
};
|
||||
|
||||
const selectHandle = (selection: any, row: any) => {
|
||||
if (typeof props.config.selection === 'string' && props.config.selection === 'single') {
|
||||
tMagicTable.value?.clearSelection();
|
||||
tMagicTable.value?.toggleRowSelection(row, true);
|
||||
}
|
||||
emit('select', selection, row);
|
||||
if (typeof props.config.onSelect === 'function') {
|
||||
props.config.onSelect(mForm, { selection, row, config: props.config });
|
||||
}
|
||||
};
|
||||
|
||||
const toggleRowSelection = (row: any, selected: boolean) => {
|
||||
tMagicTable.value?.toggleRowSelection.call(tMagicTable.value, row, selected);
|
||||
};
|
||||
|
||||
const makeConfig = (config: TableColumnConfig, row: any) => {
|
||||
const newConfig = cloneDeep(config);
|
||||
if (typeof config.itemsFunction === 'function') {
|
||||
newConfig.items = config.itemsFunction(row);
|
||||
}
|
||||
delete newConfig.display;
|
||||
return newConfig;
|
||||
};
|
||||
|
||||
const upHandler = (index: number) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
timer = setTimeout(() => {
|
||||
swapArray(index, index - 1);
|
||||
timer = undefined;
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const topHandler = (index: number) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
// 首先判断当前元素需要上移几个位置,置底移动到数组的第一位
|
||||
const moveNum = index;
|
||||
|
||||
// 循环出需要一个一个上移的次数
|
||||
for (let i = 0; i < moveNum; i++) {
|
||||
swapArray(index, index - 1);
|
||||
index -= 1;
|
||||
}
|
||||
};
|
||||
|
||||
const downHandler = (index: number) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
timer = setTimeout(() => {
|
||||
swapArray(index, index + 1);
|
||||
timer = undefined;
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const bottomHandler = (index: number) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
// 首先判断当前元素需要上移几个位置,置底移动到数组的第一位
|
||||
const moveNum = props.model[modelName.value].length - 1 - index;
|
||||
|
||||
// 循环出需要一个一个上移的次数
|
||||
for (let i = 0; i < moveNum; i++) {
|
||||
swapArray(index, index + 1);
|
||||
index += 1;
|
||||
}
|
||||
};
|
||||
|
||||
// 希望支持单行可控制是否显示删除按钮,不会影响现有逻辑
|
||||
const showDelete = (index: number) => {
|
||||
const deleteFunc = props.config.delete;
|
||||
if (deleteFunc && typeof deleteFunc === 'function') {
|
||||
return deleteFunc(props.model[modelName.value], index, mForm?.values);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const copyable = (index: number) => {
|
||||
const copyableFunc = props.config.copyable;
|
||||
if (copyableFunc && typeof copyableFunc === 'function') {
|
||||
return copyableFunc(mForm, {
|
||||
values: mForm?.initValues || {},
|
||||
model: props.model,
|
||||
parent: mForm?.parentValues || {},
|
||||
formValue: mForm?.values || props.model,
|
||||
prop: props.prop,
|
||||
config: props.config,
|
||||
index,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const clearHandler = () => {
|
||||
const len = props.model[modelName.value].length;
|
||||
props.model[modelName.value].splice(0, len);
|
||||
mForm?.$emit('field-change', props.prop, props.model[modelName.value]);
|
||||
};
|
||||
|
||||
const excelHandler = async (file: any) => {
|
||||
if (!file?.raw) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(globalThis as any).XLSX) {
|
||||
await asyncLoadJs('https://cdn.bootcdn.net/ajax/libs/xlsx/0.17.0/xlsx.full.min.js');
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const data = reader.result;
|
||||
const pdata = (globalThis as any).XLSX.read(data, { type: 'array' });
|
||||
pdata.SheetNames.forEach((sheetName: string) => {
|
||||
const arr = (globalThis as any).XLSX.utils.sheet_to_json(pdata.Sheets[sheetName], { header: 1 });
|
||||
if (arr?.[0]) {
|
||||
arr.forEach((row: any) => {
|
||||
newHandler(row);
|
||||
});
|
||||
}
|
||||
setTimeout(() => {
|
||||
excelBtn.value?.clearFiles();
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(file.raw);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleSizeChange = (val: number) => {
|
||||
pagesize.value = val;
|
||||
};
|
||||
|
||||
const handleCurrentChange = (val: number) => {
|
||||
pagecontext.value = val - 1;
|
||||
};
|
||||
|
||||
const copyHandler = (index: number) => {
|
||||
props.model[modelName.value].push(cloneDeep(props.model[modelName.value][index]));
|
||||
};
|
||||
|
||||
const toggleMode = () => {
|
||||
const calcLabelWidth = (label: string) => {
|
||||
if (!label) return '0px';
|
||||
const zhLength = label.match(/[^\x00-\xff]/g)?.length || 0;
|
||||
const chLength = label.length - zhLength;
|
||||
return `${Math.max(chLength * 8 + zhLength * 20, 80)}px`;
|
||||
};
|
||||
|
||||
// 切换为groupList的形式
|
||||
props.config.type = 'groupList';
|
||||
props.config.enableToggleMode = true;
|
||||
props.config.tableItems = props.config.items;
|
||||
props.config.items =
|
||||
props.config.groupItems ||
|
||||
props.config.items.map((item: any) => {
|
||||
const text = item.text || item.label;
|
||||
const labelWidth = calcLabelWidth(text);
|
||||
return {
|
||||
...item,
|
||||
text,
|
||||
labelWidth,
|
||||
span: item.span || 12,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
if (!mTable.value) return;
|
||||
|
||||
if (isFullscreen.value) {
|
||||
mTable.value.classList.remove('fixed');
|
||||
isFullscreen.value = false;
|
||||
} else {
|
||||
mTable.value.classList.add('fixed');
|
||||
mTable.value.style.zIndex = `${nextZIndex()}`;
|
||||
isFullscreen.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const getProp = (index: number) => {
|
||||
const { prop } = toRefs(props);
|
||||
return `${prop.value}${prop.value ? '.' : ''}${index + 1 + pagecontext.value * pagesize.value - 1}`;
|
||||
};
|
||||
|
||||
const onAddDiffCount = () => emit('addDiffCount');
|
||||
|
||||
const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
|
||||
emit('change', props.model, eventData);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
toggleRowSelection,
|
||||
});
|
||||
</script>
|
||||
@ -167,7 +167,17 @@ watchEffect(() => {
|
||||
|
||||
const tabItems = (tab: TabPaneConfig) => (props.config.dynamic ? props.config.items : tab.items);
|
||||
|
||||
const tabClickHandler = (tab: any) => tabClick(mForm, tab, props);
|
||||
const tabClickHandler = (tab: any) => {
|
||||
if (typeof tab === 'object') {
|
||||
tabClick(mForm, tab, props);
|
||||
} else {
|
||||
let item = tabs.value.find((tab: any) => tab.status === tab);
|
||||
if (!item) {
|
||||
item = tabs.value[tab];
|
||||
}
|
||||
tabClick(mForm, item, props);
|
||||
}
|
||||
};
|
||||
|
||||
const onTabAdd = async () => {
|
||||
if (!props.name) throw new Error('dynamic tab 必须配置name');
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<TMagicCascader
|
||||
v-model="value"
|
||||
:model-value="value"
|
||||
ref="tMagicCascader"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
@ -15,6 +15,7 @@
|
||||
emitPath: config.emitPath ?? true,
|
||||
checkStrictly: checkStrictly ?? false,
|
||||
}"
|
||||
@update:model-value="updateModelValueHandler"
|
||||
@change="changeHandler"
|
||||
></TMagicCascader>
|
||||
</template>
|
||||
@ -51,22 +52,31 @@ const remoteData = ref<any>(null);
|
||||
const checkStrictly = computed(() => filterFunction(mForm, props.config.checkStrictly, props));
|
||||
const valueSeparator = computed(() => filterFunction<string>(mForm, props.config.valueSeparator, props));
|
||||
|
||||
const value = computed({
|
||||
get() {
|
||||
if (typeof props.model[props.name] === 'string' && valueSeparator.value) {
|
||||
return props.model[props.name].split(valueSeparator.value);
|
||||
}
|
||||
return props.model[props.name];
|
||||
},
|
||||
set(value) {
|
||||
let result = value;
|
||||
if (valueSeparator.value) {
|
||||
result = value.join(valueSeparator.value);
|
||||
}
|
||||
props.model[props.name] = result;
|
||||
},
|
||||
const value = computed(() => {
|
||||
if (typeof props.model[props.name] === 'string' && valueSeparator.value) {
|
||||
return props.model[props.name].split(valueSeparator.value);
|
||||
}
|
||||
return props.model[props.name];
|
||||
});
|
||||
|
||||
const updateModelValueHandler = (value: string[] | number[] | any) => {
|
||||
let result = value;
|
||||
if (valueSeparator.value) {
|
||||
result = value.join(valueSeparator.value);
|
||||
}
|
||||
|
||||
if (typeof result === 'undefined') {
|
||||
if (Array.isArray(props.model[props.name])) {
|
||||
emit('change', []);
|
||||
} else if (typeof props.model[props.name] === 'string') {
|
||||
emit('change', '');
|
||||
} else if (typeof props.model[props.name] === 'object') {
|
||||
emit('change', null);
|
||||
}
|
||||
}
|
||||
emit('change', result);
|
||||
};
|
||||
|
||||
const setRemoteOptions = async function () {
|
||||
const { config } = props;
|
||||
const { option } = config;
|
||||
@ -126,6 +136,5 @@ const changeHandler = () => {
|
||||
if (!tMagicCascader.value) return;
|
||||
tMagicCascader.value.setQuery('');
|
||||
tMagicCascader.value.setPreviousQuery(null);
|
||||
emit('change', props.model[props.name]);
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<TMagicCheckbox
|
||||
v-model="model[name]"
|
||||
:model-value="model[name]"
|
||||
:size="size"
|
||||
:trueValue="activeValue"
|
||||
:falseValue="inactiveValue"
|
||||
:disabled="disabled"
|
||||
@change="changeHandler"
|
||||
>{{ config.text }}</TMagicCheckbox
|
||||
@update:model-value="changeHandler"
|
||||
><template #default v-if="!config.useLabel">{{ config.text }}</template></TMagicCheckbox
|
||||
>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TMagicCheckboxGroup v-model="model[name]" :size="size" :disabled="disabled" @change="changeHandler">
|
||||
<TMagicCheckboxGroup :model-value="model[name]" :size="size" :disabled="disabled" @update:model-value="changeHandler">
|
||||
<TMagicCheckbox v-for="option in options" :value="option.value" :key="option.value" :disabled="option.disabled"
|
||||
>{{ option.text }}
|
||||
</TMagicCheckbox>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<TMagicColorPicker
|
||||
v-model="model[name]"
|
||||
:model-value="model[name]"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
:showAlpha="true"
|
||||
@change="changeHandler"
|
||||
@update:model-value="changeHandler"
|
||||
></TMagicColorPicker>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<TMagicDatePicker
|
||||
v-model="model[name]"
|
||||
:model-value="model[name]"
|
||||
type="date"
|
||||
:size="size"
|
||||
:placeholder="config.placeholder"
|
||||
:disabled="disabled"
|
||||
:format="config.format || 'YYYY/MM/DD'"
|
||||
:value-format="config.valueFormat || 'YYYY/MM/DD'"
|
||||
@change="changeHandler"
|
||||
@update:model-value="changeHandler"
|
||||
></TMagicDatePicker>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<TMagicDatePicker
|
||||
v-model="model[name]"
|
||||
:model-value="model[name]"
|
||||
popper-class="magic-datetime-picker-popper"
|
||||
type="datetime"
|
||||
:size="size"
|
||||
@ -9,7 +9,7 @@
|
||||
:format="config.format || 'YYYY/MM/DD HH:mm:ss'"
|
||||
:value-format="config.valueFormat || 'YYYY/MM/DD HH:mm:ss'"
|
||||
:default-time="config.defaultTime"
|
||||
@change="changeHandler"
|
||||
@update:model-value="changeHandler"
|
||||
></TMagicDatePicker>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<TMagicDatePicker
|
||||
v-model="value"
|
||||
:model-value="value"
|
||||
type="datetimerange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
@ -9,19 +9,20 @@
|
||||
:unlink-panels="true"
|
||||
:disabled="disabled"
|
||||
:default-time="config.defaultTime"
|
||||
:format="`${config.dateFormat || 'YYYY/MM/DD'} ${config.timeFormat || 'HH:mm:ss'}`"
|
||||
:value-format="config.valueFormat || 'YYYY/MM/DD HH:mm:ss'"
|
||||
:date-format="config.dateFormat || 'YYYY/MM/DD'"
|
||||
:time-format="config.timeFormat || 'HH:mm:ss'"
|
||||
@change="changeHandler"
|
||||
@update:model-value="changeHandler"
|
||||
></TMagicDatePicker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import { TMagicDatePicker } from '@tmagic/design';
|
||||
|
||||
import type { DaterangeConfig, FieldProps } from '../schema';
|
||||
import type { ChangeRecord, DaterangeConfig, FieldProps } from '../schema';
|
||||
import { datetimeFormatter } from '../utils/form';
|
||||
import { useAddField } from '../utils/useAddField';
|
||||
|
||||
@ -40,7 +41,7 @@ const value = ref<(Date | string | undefined)[] | null>([]);
|
||||
|
||||
if (props.model !== undefined) {
|
||||
if (names?.length) {
|
||||
watch(
|
||||
const unWatch = watch(
|
||||
[() => props.model[names[0]], () => props.model[names[1]]],
|
||||
([start, end], [preStart, preEnd]) => {
|
||||
if (!value.value) {
|
||||
@ -56,8 +57,12 @@ if (props.model !== undefined) {
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
unWatch();
|
||||
});
|
||||
} else if (props.name && props.model[props.name]) {
|
||||
watch(
|
||||
const unWatch = watch(
|
||||
() => props.model[props.name],
|
||||
(start, preStart) => {
|
||||
const format = `${props.config.dateFormat || 'YYYY/MM/DD'} ${props.config.timeFormat || 'HH:mm:ss'}`;
|
||||
@ -71,32 +76,41 @@ if (props.model !== undefined) {
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
onUnmounted(() => {
|
||||
unWatch();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const setValue = (v: Date[] | Date) => {
|
||||
names?.forEach((item, index) => {
|
||||
if (!props.model) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(v)) {
|
||||
props.model[item] = v[index];
|
||||
} else {
|
||||
props.model[item] = undefined;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const changeHandler = (v: Date[]) => {
|
||||
const value = v || [];
|
||||
|
||||
if (props.name) {
|
||||
emit('change', value);
|
||||
} else {
|
||||
if (names?.length) {
|
||||
setValue(value);
|
||||
if (props.config.names?.length) {
|
||||
const newChangeRecords: ChangeRecord[] = [];
|
||||
props.config.names.forEach((item, index) => {
|
||||
if (!props.model) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(v)) {
|
||||
newChangeRecords.push({
|
||||
propPath: props.prop ? `${props.prop}.${item}` : item,
|
||||
value: v[index],
|
||||
});
|
||||
} else {
|
||||
newChangeRecords.push({
|
||||
propPath: props.prop ? `${props.prop}.${item}` : item,
|
||||
value: undefined,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
emit('change', props.model, {
|
||||
changeRecords: newChangeRecords,
|
||||
});
|
||||
}
|
||||
emit('change', props.model);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref } from 'vue';
|
||||
import { computed, inject, readonly, ref } from 'vue';
|
||||
|
||||
import { TMagicButton } from '@tmagic/design';
|
||||
|
||||
@ -54,7 +54,8 @@ const formConfig = computed(() => {
|
||||
if (typeof props.config.form === 'function') {
|
||||
return props.config.form(mForm, {
|
||||
model: props.model || {},
|
||||
values: props.values || {},
|
||||
values: mForm ? readonly(mForm.initValues) : null,
|
||||
formValue: props.values || {},
|
||||
});
|
||||
}
|
||||
return props.config.form;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<TMagicInputNumber
|
||||
v-if="model"
|
||||
v-model="model[name]"
|
||||
:model-value="model[name]"
|
||||
clearable
|
||||
controls-position="right"
|
||||
:size="size"
|
||||
@ -10,7 +10,7 @@
|
||||
:step="config.step"
|
||||
:placeholder="config.placeholder"
|
||||
:disabled="disabled"
|
||||
@change="changeHandler"
|
||||
@update:model-value="changeHandler"
|
||||
@input="inputHandler"
|
||||
></TMagicInputNumber>
|
||||
</template>
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<div class="m-fields-number-range">
|
||||
<TMagicInput
|
||||
v-model="model[name][0]"
|
||||
clearable
|
||||
:model-value="model[name][0]"
|
||||
:clearable="config.clearable ?? true"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
@change="minChangeHandler"
|
||||
@update:model-value="minChangeHandler"
|
||||
></TMagicInput>
|
||||
<span class="split-tag">-</span>
|
||||
<TMagicInput
|
||||
v-model="model[name][1]"
|
||||
clearable
|
||||
:model-value="model[name][1]"
|
||||
:clearable="config.clearable ?? true"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
@change="maxChangeHandler"
|
||||
@update:model-value="maxChangeHandler"
|
||||
></TMagicInput>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1,22 +1,18 @@
|
||||
<template>
|
||||
<TMagicRadioGroup v-if="model" v-model="model[name]" :size="size" :disabled="disabled">
|
||||
<TMagicRadioGroup v-if="model" :model-value="model[name]" :size="size" :disabled="disabled">
|
||||
<component
|
||||
:is="itemComponent"
|
||||
v-for="option in config.options"
|
||||
:is="itemComponent"
|
||||
:value="option.value"
|
||||
:key="`${option.value}`"
|
||||
@click.prevent="clickHandler(option.value)"
|
||||
@click="clickHandler(option.value)"
|
||||
>
|
||||
<TMagicTooltip v-if="option.tooltip" placement="top-start" :content="option.tooltip">
|
||||
<TMagicTooltip :disabled="!Boolean(option.tooltip)" placement="top-start" :content="option.tooltip">
|
||||
<div>
|
||||
<TMagicIcon v-if="option.icon" :size="iconSize"><component :is="option.icon"></component></TMagicIcon>
|
||||
<span>{{ option.text }}</span>
|
||||
</div>
|
||||
</TMagicTooltip>
|
||||
<div v-else>
|
||||
<TMagicIcon v-if="option.icon" :size="iconSize"><component :is="option.icon"></component></TMagicIcon>
|
||||
<span>{{ option.text }}</span>
|
||||
</div>
|
||||
</component>
|
||||
</TMagicRadioGroup>
|
||||
</template>
|
||||
@ -39,13 +35,9 @@ const itemComponent = computed(() => (props.config.childType === 'button' ? TMag
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
const changeHandler = (value: number) => {
|
||||
emit('change', value);
|
||||
};
|
||||
|
||||
const clickHandler = (item: any) => {
|
||||
props.model[props.name] = props.model[props.name] === item ? '' : item;
|
||||
changeHandler(props.model[props.name]);
|
||||
const clickHandler = (item: string | number | boolean) => {
|
||||
// 再次点击取消选中
|
||||
emit('change', props.model[props.name] === item ? '' : item);
|
||||
};
|
||||
|
||||
useAddField(props.prop);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<TMagicSelect
|
||||
v-if="model"
|
||||
v-model="model[name]"
|
||||
:model-value="model[name]"
|
||||
v-loading="loading"
|
||||
class="m-select"
|
||||
ref="tMagicSelect"
|
||||
@ -16,7 +16,7 @@
|
||||
:allow-create="config.allowCreate"
|
||||
:disabled="disabled"
|
||||
:remote-method="config.remote && remoteMethod"
|
||||
@change="changeHandler"
|
||||
@update:model-value="changeHandler"
|
||||
@visible-change="visibleHandler"
|
||||
>
|
||||
<template v-if="config.group">
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<TMagicSwitch
|
||||
v-model="model[name]"
|
||||
:model-value="model[name]"
|
||||
:size="size"
|
||||
:activeValue="activeValue"
|
||||
:inactiveValue="inactiveValue"
|
||||
:disabled="disabled"
|
||||
@change="changeHandler"
|
||||
@update:model-value="changeHandler"
|
||||
></TMagicSwitch>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
<template>
|
||||
<div class="m-fields-text">
|
||||
<TMagicInput
|
||||
v-model="model[name]"
|
||||
:model-value="model[name]"
|
||||
ref="input"
|
||||
clearable
|
||||
:size="size"
|
||||
:placeholder="config.placeholder"
|
||||
:disabled="disabled"
|
||||
@change="changeHandler"
|
||||
@update:model-value="changeHandler"
|
||||
@input="inputHandler"
|
||||
@keyup="keyUpHandler($event)"
|
||||
>
|
||||
<template #prepend v-if="config.prepend">
|
||||
<span>{{ config.prepend }}</span>
|
||||
</template>
|
||||
<template #append v-if="appendConfig">
|
||||
<TMagicButton
|
||||
v-if="appendConfig.type === 'button'"
|
||||
@ -20,6 +23,7 @@
|
||||
>
|
||||
{{ appendConfig.text }}
|
||||
</TMagicButton>
|
||||
<span v-else>{{ appendConfig.text }}</span>
|
||||
</template>
|
||||
</TMagicInput>
|
||||
|
||||
@ -37,7 +41,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref, shallowRef, watch } from 'vue';
|
||||
import { computed, inject, readonly, ref, shallowRef, watch } from 'vue';
|
||||
import type { Instance } from '@popperjs/core';
|
||||
import { createPopper } from '@popperjs/core';
|
||||
import { debounce } from 'lodash-es';
|
||||
@ -45,7 +49,7 @@ import { debounce } from 'lodash-es';
|
||||
import { TMagicButton, TMagicInput } from '@tmagic/design';
|
||||
import { isNumber } from '@tmagic/utils';
|
||||
|
||||
import type { FieldProps, FormState, TextConfig } from '../schema';
|
||||
import type { ChangeRecord, ContainerChangeEventData, FieldProps, FormState, TextConfig } from '../schema';
|
||||
import { useAddField } from '../utils/useAddField';
|
||||
|
||||
defineOptions({
|
||||
@ -55,7 +59,7 @@ defineOptions({
|
||||
const props = defineProps<FieldProps<TextConfig>>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [value: string];
|
||||
change: [value: string, eventData?: ContainerChangeEventData];
|
||||
input: [value: string];
|
||||
}>();
|
||||
|
||||
@ -66,18 +70,26 @@ const mForm = inject<FormState | undefined>('mForm');
|
||||
const appendConfig = computed(() => {
|
||||
if (typeof props.config.append === 'string') {
|
||||
return {
|
||||
type: 'text',
|
||||
text: props.config.append,
|
||||
type: 'button',
|
||||
handler: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (props.config.append && typeof props.config.append === 'object') {
|
||||
if (props.config.append.value === 0) {
|
||||
return false;
|
||||
if (typeof props.config.append === 'object') {
|
||||
if (typeof props.config.append?.handler === 'function') {
|
||||
return {
|
||||
type: 'button',
|
||||
text: props.config.append.text,
|
||||
handler: props.config.append.handler,
|
||||
};
|
||||
}
|
||||
if (props.config.append) {
|
||||
if (props.config.append.value === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return props.config.append;
|
||||
return props.config.append;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -109,10 +121,28 @@ const inputHandler = (v: string) => {
|
||||
const buttonClickHandler = () => {
|
||||
if (!appendConfig.value) return;
|
||||
if (typeof appendConfig.value.handler === 'function') {
|
||||
const newChangeRecords: ChangeRecord[] = [];
|
||||
const setModel = (key: string, value: any) => {
|
||||
newChangeRecords.push({ propPath: props.prop.replace(`${props.name}`, key), value });
|
||||
};
|
||||
|
||||
const setFormValue = (key: string, value: any) => {
|
||||
newChangeRecords.push({ propPath: key, value });
|
||||
};
|
||||
|
||||
appendConfig.value.handler(mForm, {
|
||||
model: props.model,
|
||||
values: mForm?.values,
|
||||
values: mForm ? readonly(mForm.initValues) : null,
|
||||
formValue: props.values || {},
|
||||
setModel,
|
||||
setFormValue,
|
||||
});
|
||||
|
||||
if (newChangeRecords.length > 0) {
|
||||
emit('change', props.model[props.name], {
|
||||
changeRecords: newChangeRecords,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<TMagicInput
|
||||
v-model="model[name]"
|
||||
:model-value="model[name]"
|
||||
type="textarea"
|
||||
:size="size"
|
||||
clearable
|
||||
:placeholder="config.placeholder"
|
||||
:disabled="disabled"
|
||||
@change="changeHandler"
|
||||
:rows="config.rows"
|
||||
@update:model-value="changeHandler"
|
||||
@input="inputHandler"
|
||||
>
|
||||
</TMagicInput>
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<TMagicTimePicker
|
||||
v-model="model[name]"
|
||||
:model-value="model[name]"
|
||||
:value-format="config.valueFormat || 'HH:mm:ss'"
|
||||
:format="config.format || 'HH:mm:ss'"
|
||||
:size="size"
|
||||
:placeholder="config.placeholder"
|
||||
:disabled="disabled"
|
||||
@change="changeHandler"
|
||||
@update:model-value="changeHandler"
|
||||
></TMagicTimePicker>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<TMagicTimePicker
|
||||
v-model="value"
|
||||
:model-value="value"
|
||||
is-range
|
||||
range-separator="-"
|
||||
start-placeholder="开始时间"
|
||||
@ -9,7 +9,7 @@
|
||||
:unlink-panels="true"
|
||||
:disabled="disabled"
|
||||
:default-time="config.defaultTime"
|
||||
@change="changeHandler"
|
||||
@update:model-value="changeHandler"
|
||||
></TMagicTimePicker>
|
||||
</template>
|
||||
|
||||
|
||||
@ -20,11 +20,11 @@ import type { App } from 'vue';
|
||||
|
||||
import Container from './containers/Container.vue';
|
||||
import Fieldset from './containers/Fieldset.vue';
|
||||
import FlexLayout from './containers/FlexLayout.vue';
|
||||
import GroupList from './containers/GroupList.vue';
|
||||
import Panel from './containers/Panel.vue';
|
||||
import Row from './containers/Row.vue';
|
||||
import MStep from './containers/Step.vue';
|
||||
import Table from './containers/Table.vue';
|
||||
import Tabs from './containers/Tabs.vue';
|
||||
import Cascader from './fields/Cascader.vue';
|
||||
import Checkbox from './fields/Checkbox.vue';
|
||||
@ -46,6 +46,7 @@ import Text from './fields/Text.vue';
|
||||
import Textarea from './fields/Textarea.vue';
|
||||
import Time from './fields/Time.vue';
|
||||
import Timerange from './fields/Timerange.vue';
|
||||
import Table from './table/Table.vue';
|
||||
import { setConfig } from './utils/config';
|
||||
import Form from './Form.vue';
|
||||
import FormDialog from './FormDialog.vue';
|
||||
@ -63,10 +64,11 @@ export { default as MFormDrawer } from './FormDrawer.vue';
|
||||
export { default as MFormBox } from './FormBox.vue';
|
||||
export { default as MContainer } from './containers/Container.vue';
|
||||
export { default as MFieldset } from './containers/Fieldset.vue';
|
||||
export { default as MFlexLayout } from './containers/FlexLayout.vue';
|
||||
export { default as MPanel } from './containers/Panel.vue';
|
||||
export { default as MRow } from './containers/Row.vue';
|
||||
export { default as MTabs } from './containers/Tabs.vue';
|
||||
export { default as MTable } from './containers/Table.vue';
|
||||
export { default as MTable } from './table/Table.vue';
|
||||
export { default as MGroupList } from './containers/GroupList.vue';
|
||||
export { default as MText } from './fields/Text.vue';
|
||||
export { default as MNumber } from './fields/Number.vue';
|
||||
@ -114,6 +116,7 @@ export default {
|
||||
app.component('m-form-step', MStep);
|
||||
app.component('m-form-table', Table);
|
||||
app.component('m-form-tab', Tabs);
|
||||
app.component('m-form-flex-layout', FlexLayout);
|
||||
app.component('m-fields-text', Text);
|
||||
app.component('m-fields-img-upload', Text);
|
||||
app.component('m-fields-number', Number);
|
||||
|
||||
97
packages/form/src/table/ActionsColumn.vue
Normal file
97
packages/form/src/table/ActionsColumn.vue
Normal file
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<slot name="operateCol" :scope="{ $index: index, row: row }"></slot>
|
||||
<TMagicButton
|
||||
v-show="showDelete(index + 1 + currentPage * pageSize - 1)"
|
||||
size="small"
|
||||
type="danger"
|
||||
link
|
||||
title="删除"
|
||||
:icon="Delete"
|
||||
@click="removeHandler(index + 1 + currentPage * pageSize - 1)"
|
||||
></TMagicButton>
|
||||
|
||||
<TMagicButton
|
||||
v-if="copyable(index + 1 + currentPage * pageSize - 1)"
|
||||
link
|
||||
size="small"
|
||||
type="primary"
|
||||
title="复制"
|
||||
:icon="DocumentCopy"
|
||||
:disabled="disabled"
|
||||
@click="copyHandler(index + 1 + currentPage * pageSize - 1)"
|
||||
></TMagicButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { inject } from 'vue';
|
||||
import { Delete, DocumentCopy } from '@element-plus/icons-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import { TMagicButton } from '@tmagic/design';
|
||||
|
||||
import type { FormState, TableConfig } from '../schema';
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
const props = defineProps<{
|
||||
config: TableConfig;
|
||||
model: any;
|
||||
name: string | number;
|
||||
disabled?: boolean;
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
index: number;
|
||||
row: any;
|
||||
prop?: string;
|
||||
sortKey?: string;
|
||||
}>();
|
||||
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
|
||||
const removeHandler = (index: number) => {
|
||||
if (props.disabled) return;
|
||||
emit('change', props.model[props.name].toSpliced(index, 1));
|
||||
};
|
||||
|
||||
const copyHandler = (index: number) => {
|
||||
const inputs = cloneDeep(props.model[props.name][index]);
|
||||
const { length } = props.model[props.name];
|
||||
if (props.sortKey && length) {
|
||||
inputs[props.sortKey] = props.model[props.name][length - 1][props.sortKey] - 1;
|
||||
}
|
||||
|
||||
emit('change', [...props.model[props.name], inputs], {
|
||||
changeRecords: [
|
||||
{
|
||||
propPath: `${props.prop}.${props.model[props.name].length}`,
|
||||
value: inputs,
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
// 希望支持单行可控制是否显示删除按钮,不会影响现有逻辑
|
||||
const showDelete = (index: number) => {
|
||||
const deleteFunc = props.config.delete;
|
||||
if (deleteFunc && typeof deleteFunc === 'function') {
|
||||
return deleteFunc(props.model[props.name], index, mForm?.values);
|
||||
}
|
||||
return props.config.delete ?? true;
|
||||
};
|
||||
|
||||
const copyable = (index: number) => {
|
||||
const copyableFunc = props.config.copyable;
|
||||
if (copyableFunc && typeof copyableFunc === 'function') {
|
||||
return copyableFunc(mForm, {
|
||||
values: mForm?.initValues || {},
|
||||
model: props.model,
|
||||
parent: mForm?.parentValues || {},
|
||||
formValue: mForm?.values || props.model,
|
||||
prop: props.prop,
|
||||
config: props.config,
|
||||
index,
|
||||
});
|
||||
}
|
||||
return props.config.copyable ?? true;
|
||||
};
|
||||
</script>
|
||||
101
packages/form/src/table/SortColumn.vue
Normal file
101
packages/form/src/table/SortColumn.vue
Normal file
@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<TMagicTooltip v-if="index + 1 + currentPage * pageSize - 1 !== 0" content="点击上移,双击置顶" placement="top">
|
||||
<TMagicButton
|
||||
plain
|
||||
size="small"
|
||||
type="primary"
|
||||
:icon="ArrowUp"
|
||||
:disabled="disabled"
|
||||
link
|
||||
@click="upHandler(index + 1 + currentPage * pageSize - 1)"
|
||||
@dblclick="topHandler(index + 1 + currentPage * pageSize - 1)"
|
||||
></TMagicButton>
|
||||
</TMagicTooltip>
|
||||
<TMagicTooltip
|
||||
v-if="index + 1 + currentPage * pageSize - 1 !== model[name].length - 1"
|
||||
content="点击下移,双击置底"
|
||||
placement="top"
|
||||
>
|
||||
<TMagicButton
|
||||
plain
|
||||
size="small"
|
||||
type="primary"
|
||||
:icon="ArrowDown"
|
||||
:disabled="disabled"
|
||||
link
|
||||
@click="downHandler(index + 1 + currentPage * pageSize - 1)"
|
||||
@dblclick="bottomHandler(index + 1 + currentPage * pageSize - 1)"
|
||||
></TMagicButton>
|
||||
</TMagicTooltip>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
||||
|
||||
import { TMagicButton, TMagicTooltip } from '@tmagic/design';
|
||||
|
||||
const props = defineProps<{
|
||||
index: number;
|
||||
disabled?: boolean;
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
name: string | number;
|
||||
model: any;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['swap']);
|
||||
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const upHandler = (index: number) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
timer = setTimeout(() => {
|
||||
emit('swap', index, index - 1);
|
||||
timer = null;
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const topHandler = (index: number) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
// 首先判断当前元素需要上移几个位置,置底移动到数组的第一位
|
||||
const moveNum = index;
|
||||
|
||||
// 循环出需要一个一个上移的次数
|
||||
for (let i = 0; i < moveNum; i++) {
|
||||
emit('swap', index, index - 1);
|
||||
index -= 1;
|
||||
}
|
||||
};
|
||||
|
||||
const downHandler = (index: number) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
timer = setTimeout(() => {
|
||||
emit('swap', index, index + 1);
|
||||
timer = null;
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const bottomHandler = (index: number) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
// 首先判断当前元素需要上移几个位置,置底移动到数组的第一位
|
||||
const moveNum = props.model[props.name].length - 1 - index;
|
||||
|
||||
// 循环出需要一个一个上移的次数
|
||||
for (let i = 0; i < moveNum; i++) {
|
||||
emit('swap', index, index + 1);
|
||||
index += 1;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
174
packages/form/src/table/Table.vue
Normal file
174
packages/form/src/table/Table.vue
Normal file
@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<teleport to="body" :disabled="!isFullscreen">
|
||||
<div
|
||||
v-bind="$attrs"
|
||||
class="m-fields-table-wrap"
|
||||
:class="{ fixed: isFullscreen }"
|
||||
:style="isFullscreen ? `z-index: ${nextZIndex()}` : ''"
|
||||
>
|
||||
<div class="m-fields-table" :class="{ 'm-fields-table-item-extra': config.itemExtra }">
|
||||
<span v-if="config.extra" style="color: rgba(0, 0, 0, 0.45)" v-html="config.extra"></span>
|
||||
<TMagicTooltip content="拖拽可排序" placement="left-start" :disabled="config.dropSort !== true">
|
||||
<TMagicTable
|
||||
v-if="model[modelName]"
|
||||
ref="tMagicTable"
|
||||
style="width: 100%"
|
||||
show-header
|
||||
:row-key="config.rowKey || 'id'"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
:border="config.border"
|
||||
:max-height="config.maxHeight"
|
||||
:default-expand-all="true"
|
||||
:key="updateKey"
|
||||
@select="selectHandle"
|
||||
@sort-change="sortChangeHandler"
|
||||
></TMagicTable>
|
||||
</TMagicTooltip>
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; margin: 10px 0">
|
||||
<div style="display: flex">
|
||||
<TMagicButton
|
||||
:icon="Grid"
|
||||
size="small"
|
||||
@click="toggleMode"
|
||||
v-if="enableToggleMode && config.enableToggleMode !== false && !isFullscreen"
|
||||
>展开配置</TMagicButton
|
||||
>
|
||||
<TMagicButton
|
||||
:icon="FullScreen"
|
||||
size="small"
|
||||
@click="toggleFullscreen"
|
||||
v-if="config.enableFullscreen !== false"
|
||||
>
|
||||
{{ isFullscreen ? '退出全屏' : '全屏编辑' }}
|
||||
</TMagicButton>
|
||||
<TMagicUpload
|
||||
v-if="importable"
|
||||
style="display: inline-block"
|
||||
ref="excelBtn"
|
||||
action="/noop"
|
||||
:disabled="disabled"
|
||||
:on-change="excelHandler"
|
||||
:auto-upload="false"
|
||||
>
|
||||
<TMagicButton size="small" type="success" :disabled="disabled" plain>导入EXCEL</TMagicButton>
|
||||
</TMagicUpload>
|
||||
<TMagicButton v-if="importable" size="small" type="warning" :disabled="disabled" plain @click="clearHandler"
|
||||
>清空</TMagicButton
|
||||
>
|
||||
</div>
|
||||
<TMagicButton v-if="addable" size="small" type="primary" :disabled="disabled" plain @click="newHandler()"
|
||||
>新增一行</TMagicButton
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="bottom" style="text-align: right" v-if="config.pagination">
|
||||
<TMagicPagination
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:hide-on-single-page="model[modelName].length < pageSize"
|
||||
:current-page="currentPage + 1"
|
||||
:page-sizes="[pageSize, 60, 120, 300]"
|
||||
:page-size="pageSize"
|
||||
:total="model[modelName].length"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
</TMagicPagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
import { FullScreen, Grid } from '@element-plus/icons-vue';
|
||||
|
||||
import { TMagicButton, TMagicPagination, TMagicTable, TMagicTooltip, TMagicUpload, useZIndex } from '@tmagic/design';
|
||||
|
||||
import type { SortProp } from '../schema';
|
||||
import { sortChange } from '../utils/form';
|
||||
|
||||
import type { TableProps } from './type';
|
||||
import { useAdd } from './useAdd';
|
||||
import { useFullscreen } from './useFullscreen';
|
||||
import { useImport } from './useImport';
|
||||
import { usePagination } from './usePagination';
|
||||
import { useSelection } from './useSelection';
|
||||
import { useSortable } from './useSortable';
|
||||
import { useTableColumns } from './useTableColumns';
|
||||
|
||||
defineOptions({
|
||||
name: 'MFormTable',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<TableProps>(), {
|
||||
prop: '',
|
||||
sortKey: '',
|
||||
enableToggleMode: true,
|
||||
showIndex: true,
|
||||
lastValues: () => ({}),
|
||||
isCompare: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['change', 'select', 'addDiffCount']);
|
||||
|
||||
const modelName = computed(() => props.name || props.config.name || '');
|
||||
const tMagicTableRef = useTemplateRef<InstanceType<typeof TMagicTable>>('tMagicTable');
|
||||
|
||||
const { pageSize, currentPage, paginationData, handleSizeChange, handleCurrentChange } = usePagination(
|
||||
props,
|
||||
modelName,
|
||||
);
|
||||
|
||||
const { nextZIndex } = useZIndex();
|
||||
|
||||
const { addable, newHandler } = useAdd(props, emit);
|
||||
const { columns } = useTableColumns(props, emit, currentPage, pageSize, modelName);
|
||||
useSortable(props, emit, tMagicTableRef, modelName);
|
||||
const { isFullscreen, toggleFullscreen } = useFullscreen();
|
||||
const { importable, excelHandler, clearHandler } = useImport(props, emit, newHandler);
|
||||
const { selectHandle, toggleRowSelection } = useSelection(props, emit, tMagicTableRef);
|
||||
|
||||
const updateKey = ref(1);
|
||||
|
||||
const data = computed(() => (props.config.pagination ? paginationData.value : props.model[modelName.value]));
|
||||
|
||||
const toggleMode = () => {
|
||||
const calcLabelWidth = (label: string) => {
|
||||
if (!label) return '0px';
|
||||
const zhLength = label.match(/[^\x00-\xff]/g)?.length || 0;
|
||||
const chLength = label.length - zhLength;
|
||||
return `${Math.max(chLength * 8 + zhLength * 20, 80)}px`;
|
||||
};
|
||||
|
||||
// 切换为groupList的形式
|
||||
props.config.type = 'groupList';
|
||||
props.config.enableToggleMode = true;
|
||||
props.config.tableItems = props.config.items;
|
||||
props.config.items =
|
||||
props.config.groupItems ||
|
||||
props.config.items.map((item: any) => {
|
||||
const text = item.text || item.label;
|
||||
const labelWidth = calcLabelWidth(text);
|
||||
return {
|
||||
...item,
|
||||
text,
|
||||
labelWidth,
|
||||
span: item.span || 12,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const sortChangeHandler = (sortOptions: SortProp) => {
|
||||
const modelName = props.name || props.config.name || '';
|
||||
sortChange(props.model[modelName], sortOptions);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
toggleRowSelection,
|
||||
});
|
||||
</script>
|
||||
18
packages/form/src/table/type.ts
Normal file
18
packages/form/src/table/type.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { TableConfig } from '@tmagic/form-schema';
|
||||
|
||||
export interface TableProps {
|
||||
model: any;
|
||||
lastValues?: any;
|
||||
isCompare?: boolean;
|
||||
config: TableConfig;
|
||||
name: string;
|
||||
prop?: string;
|
||||
labelWidth?: string;
|
||||
sort?: boolean;
|
||||
disabled?: boolean;
|
||||
sortKey?: string;
|
||||
text?: string;
|
||||
size?: string;
|
||||
enableToggleMode?: boolean;
|
||||
showIndex?: boolean;
|
||||
}
|
||||
112
packages/form/src/table/useAdd.ts
Normal file
112
packages/form/src/table/useAdd.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { computed, inject } from 'vue';
|
||||
|
||||
import { tMagicMessage } from '@tmagic/design';
|
||||
import type { FormState } from '@tmagic/form-schema';
|
||||
|
||||
import { initValue } from '../utils/form';
|
||||
|
||||
import type { TableProps } from './type';
|
||||
|
||||
export const useAdd = (
|
||||
props: TableProps,
|
||||
emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
|
||||
) => {
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
|
||||
const addable = computed(() => {
|
||||
const modelName = props.name || props.config.name || '';
|
||||
if (!props.model[modelName].length) {
|
||||
return true;
|
||||
}
|
||||
if (typeof props.config.addable === 'function') {
|
||||
return props.config.addable(mForm, {
|
||||
model: props.model[modelName],
|
||||
formValue: mForm?.values,
|
||||
prop: props.prop,
|
||||
});
|
||||
}
|
||||
return typeof props.config.addable === 'undefined' ? true : props.config.addable;
|
||||
});
|
||||
|
||||
const newHandler = async (row?: any) => {
|
||||
const modelName = props.name || props.config.name || '';
|
||||
|
||||
if (props.config.max && props.model[modelName].length >= props.config.max) {
|
||||
tMagicMessage.error(`最多新增配置不能超过${props.config.max}条`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof props.config.beforeAddRow === 'function') {
|
||||
const beforeCheckRes = props.config.beforeAddRow(mForm, {
|
||||
model: props.model[modelName],
|
||||
formValue: mForm?.values,
|
||||
prop: props.prop,
|
||||
});
|
||||
if (!beforeCheckRes) return;
|
||||
}
|
||||
|
||||
const columns = props.config.items;
|
||||
const enumValues = props.config.enum || [];
|
||||
let enumV = [];
|
||||
const { length } = props.model[modelName];
|
||||
const key = props.config.key || 'id';
|
||||
let inputs: any = {};
|
||||
|
||||
if (enumValues.length) {
|
||||
if (length >= enumValues.length) {
|
||||
return;
|
||||
}
|
||||
enumV = enumValues.filter((item) => {
|
||||
let i = 0;
|
||||
for (; i < length; i++) {
|
||||
if (item[key] === props.model[modelName][i][key]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i === length;
|
||||
});
|
||||
|
||||
if (enumV.length > 0) {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
inputs = enumV[0];
|
||||
}
|
||||
} else if (Array.isArray(row)) {
|
||||
columns.forEach((column, index) => {
|
||||
column.name && (inputs[column.name] = row[index]);
|
||||
});
|
||||
} else {
|
||||
if (typeof props.config.defaultAdd === 'function') {
|
||||
inputs = await props.config.defaultAdd(mForm, {
|
||||
model: props.model[modelName],
|
||||
prop: props.prop,
|
||||
formValue: mForm?.values,
|
||||
});
|
||||
} else if (props.config.defaultAdd) {
|
||||
inputs = props.config.defaultAdd;
|
||||
}
|
||||
|
||||
inputs = await initValue(mForm, {
|
||||
config: columns,
|
||||
initValues: inputs,
|
||||
});
|
||||
}
|
||||
|
||||
if (props.sortKey && length) {
|
||||
inputs[props.sortKey] = props.model[modelName][length - 1][props.sortKey] - 1;
|
||||
}
|
||||
|
||||
emit('change', [...props.model[modelName], inputs], {
|
||||
changeRecords: [
|
||||
{
|
||||
propPath: `${props.prop}.${props.model[modelName].length}`,
|
||||
value: inputs,
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
addable,
|
||||
newHandler,
|
||||
};
|
||||
};
|
||||
18
packages/form/src/table/useFullscreen.ts
Normal file
18
packages/form/src/table/useFullscreen.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
export const useFullscreen = () => {
|
||||
const isFullscreen = ref(false);
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
if (isFullscreen.value) {
|
||||
isFullscreen.value = false;
|
||||
} else {
|
||||
isFullscreen.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
isFullscreen,
|
||||
toggleFullscreen,
|
||||
};
|
||||
};
|
||||
68
packages/form/src/table/useImport.ts
Normal file
68
packages/form/src/table/useImport.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { computed, inject, useTemplateRef } from 'vue';
|
||||
|
||||
import type { TMagicUpload } from '@tmagic/design';
|
||||
import type { FormState } from '@tmagic/form-schema';
|
||||
import { asyncLoadJs } from '@tmagic/utils';
|
||||
|
||||
import type { TableProps } from './type';
|
||||
|
||||
export const useImport = (
|
||||
props: TableProps,
|
||||
emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
|
||||
newHandler: (row: any) => void,
|
||||
) => {
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
const modelName = computed(() => props.name || props.config.name || '');
|
||||
const importable = computed(() => {
|
||||
if (typeof props.config.importable === 'function') {
|
||||
return props.config.importable(mForm, {
|
||||
formValue: mForm?.values,
|
||||
model: props.model[modelName.value],
|
||||
});
|
||||
}
|
||||
return typeof props.config.importable === 'undefined' ? false : props.config.importable;
|
||||
});
|
||||
|
||||
const excelBtn = useTemplateRef<InstanceType<typeof TMagicUpload>>('excelBtn');
|
||||
|
||||
const excelHandler = async (file: any) => {
|
||||
if (!file?.raw) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(globalThis as any).XLSX) {
|
||||
await asyncLoadJs('https://cdn.bootcdn.net/ajax/libs/xlsx/0.17.0/xlsx.full.min.js');
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const data = reader.result;
|
||||
const pdata = (globalThis as any).XLSX.read(data, { type: 'array' });
|
||||
pdata.SheetNames.forEach((sheetName: string) => {
|
||||
const arr = (globalThis as any).XLSX.utils.sheet_to_json(pdata.Sheets[sheetName], { header: 1 });
|
||||
if (arr?.[0]) {
|
||||
arr.forEach((row: any) => {
|
||||
newHandler(row);
|
||||
});
|
||||
}
|
||||
setTimeout(() => {
|
||||
excelBtn.value?.clearFiles();
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(file.raw);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const clearHandler = () => {
|
||||
emit('change', []);
|
||||
mForm?.$emit('field-change', props.prop, props.model[modelName.value]);
|
||||
};
|
||||
|
||||
return {
|
||||
importable,
|
||||
excelHandler,
|
||||
clearHandler,
|
||||
};
|
||||
};
|
||||
30
packages/form/src/table/usePagination.ts
Normal file
30
packages/form/src/table/usePagination.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { computed, type Ref, ref } from 'vue';
|
||||
|
||||
import { getDataByPage } from '../utils/form';
|
||||
|
||||
import type { TableProps } from './type';
|
||||
|
||||
export const usePagination = (props: TableProps, modelName: Ref<string | number>) => {
|
||||
const pageSize = ref(10);
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
const currentPage = ref(0);
|
||||
|
||||
const paginationData = computed(() => getDataByPage(props.model[modelName.value], currentPage.value, pageSize.value));
|
||||
const handleSizeChange = (val: number) => {
|
||||
pageSize.value = val;
|
||||
};
|
||||
|
||||
const handleCurrentChange = (val: number) => {
|
||||
currentPage.value = val - 1;
|
||||
};
|
||||
|
||||
return {
|
||||
pageSize,
|
||||
currentPage,
|
||||
paginationData,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
};
|
||||
};
|
||||
34
packages/form/src/table/useSelection.ts
Normal file
34
packages/form/src/table/useSelection.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { inject, type ShallowRef } from 'vue';
|
||||
|
||||
import type { TMagicTable } from '@tmagic/design';
|
||||
import type { FormState } from '@tmagic/form-schema';
|
||||
|
||||
import type { TableProps } from './type';
|
||||
|
||||
export const useSelection = (
|
||||
props: TableProps,
|
||||
emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
|
||||
tMagicTableRef: ShallowRef<InstanceType<typeof TMagicTable> | null>,
|
||||
) => {
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
|
||||
const selectHandle = (selection: any, row: any) => {
|
||||
if (typeof props.config.selection === 'string' && props.config.selection === 'single') {
|
||||
tMagicTableRef.value?.clearSelection();
|
||||
tMagicTableRef.value?.toggleRowSelection(row, true);
|
||||
}
|
||||
emit('select', selection, row);
|
||||
if (typeof props.config.onSelect === 'function') {
|
||||
props.config.onSelect(mForm, { selection, row, config: props.config });
|
||||
}
|
||||
};
|
||||
|
||||
const toggleRowSelection = (row: any, selected: boolean) => {
|
||||
tMagicTableRef.value?.toggleRowSelection.call(tMagicTableRef.value?.getTableRef(), row, selected);
|
||||
};
|
||||
|
||||
return {
|
||||
selectHandle,
|
||||
toggleRowSelection,
|
||||
};
|
||||
};
|
||||
48
packages/form/src/table/useSortable.ts
Normal file
48
packages/form/src/table/useSortable.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { inject, type Ref, type ShallowRef, watchEffect } from 'vue';
|
||||
import Sortable, { type SortableEvent } from 'sortablejs';
|
||||
|
||||
import { type TMagicTable } from '@tmagic/design';
|
||||
import type { FormState } from '@tmagic/form-schema';
|
||||
|
||||
import { sortArray } from '../utils/form';
|
||||
|
||||
import type { TableProps } from './type';
|
||||
|
||||
export const useSortable = (
|
||||
props: TableProps,
|
||||
emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
|
||||
tMagicTableRef: ShallowRef<InstanceType<typeof TMagicTable> | null>,
|
||||
modelName: Ref<string | number>,
|
||||
) => {
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
|
||||
let sortable: Sortable | undefined;
|
||||
const rowDrop = () => {
|
||||
sortable?.destroy();
|
||||
const tableEl = tMagicTableRef.value?.getEl();
|
||||
const tBodyEl = tableEl?.querySelector('.el-table__body > tbody');
|
||||
if (!tBodyEl) {
|
||||
return;
|
||||
}
|
||||
sortable = Sortable.create(tBodyEl, {
|
||||
draggable: '.tmagic-design-table-row',
|
||||
filter: 'input', // 表单组件选字操作和触发拖拽会冲突,优先保证选字操作
|
||||
preventOnFilter: false, // 允许选字
|
||||
direction: 'vertical',
|
||||
onEnd: ({ newIndex, oldIndex }: SortableEvent) => {
|
||||
if (typeof newIndex === 'undefined') return;
|
||||
if (typeof oldIndex === 'undefined') return;
|
||||
const newData = sortArray(props.model[modelName.value], newIndex, oldIndex, props.sortKey);
|
||||
|
||||
emit('change', newData);
|
||||
mForm?.$emit('field-change', newData);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.config.dropSort) {
|
||||
rowDrop();
|
||||
}
|
||||
});
|
||||
};
|
||||
194
packages/form/src/table/useTableColumns.ts
Normal file
194
packages/form/src/table/useTableColumns.ts
Normal file
@ -0,0 +1,194 @@
|
||||
import { computed, h, inject, type Ref } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import type { TableColumnOptions } from '@tmagic/design';
|
||||
import type { FormState, TableColumnConfig } from '@tmagic/form-schema';
|
||||
|
||||
import Container from '../containers/Container.vue';
|
||||
import type { ContainerChangeEventData } from '../schema';
|
||||
import { display as displayFunc, getDataByPage, sortArray } from '../utils/form';
|
||||
|
||||
import ActionsColumn from './ActionsColumn.vue';
|
||||
import SortColumn from './SortColumn.vue';
|
||||
import type { TableProps } from './type';
|
||||
|
||||
export const useTableColumns = (
|
||||
props: TableProps,
|
||||
emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
|
||||
currentPage: Ref<number>,
|
||||
pageSize: Ref<number>,
|
||||
modelName: Ref<string | number>,
|
||||
) => {
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
|
||||
const display = (fuc: any) => displayFunc(mForm, fuc, props);
|
||||
|
||||
const lastData = computed(() =>
|
||||
props.config.pagination
|
||||
? getDataByPage(props.lastValues[modelName.value], currentPage.value, pageSize.value)
|
||||
: props.lastValues[modelName.value] || [],
|
||||
);
|
||||
|
||||
const itemExtra = (fuc: any, index: number) => {
|
||||
if (typeof fuc === 'function') {
|
||||
return fuc(mForm, {
|
||||
values: mForm?.initValues,
|
||||
model: props.model,
|
||||
formValue: mForm ? mForm.values : props.model,
|
||||
prop: props.prop,
|
||||
index,
|
||||
});
|
||||
}
|
||||
|
||||
return fuc;
|
||||
};
|
||||
|
||||
const selection = computed(() => {
|
||||
if (typeof props.config.selection === 'function') {
|
||||
return props.config.selection(mForm, { model: props.model[modelName.value] });
|
||||
}
|
||||
return props.config.selection;
|
||||
});
|
||||
|
||||
const getProp = (index: number) => {
|
||||
return `${props.prop}${props.prop ? '.' : ''}${index + 1 + currentPage.value * pageSize.value - 1}`;
|
||||
};
|
||||
|
||||
const makeConfig = (config: TableColumnConfig, row: any) => {
|
||||
const newConfig = cloneDeep(config);
|
||||
if (typeof config.itemsFunction === 'function') {
|
||||
newConfig.items = config.itemsFunction(row);
|
||||
}
|
||||
delete newConfig.display;
|
||||
return newConfig;
|
||||
};
|
||||
|
||||
const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
|
||||
emit('change', props.model, eventData);
|
||||
};
|
||||
|
||||
const onAddDiffCount = () => emit('addDiffCount');
|
||||
|
||||
const columns = computed<TableColumnOptions[]>(() => {
|
||||
const columns: TableColumnOptions[] = [];
|
||||
|
||||
if (props.config.itemExtra && !props.config.dropSort) {
|
||||
columns.push({
|
||||
props: {
|
||||
fixed: 'left',
|
||||
width: 30,
|
||||
type: 'expand',
|
||||
},
|
||||
cell: ({ $index }: any) =>
|
||||
h('span', {
|
||||
innerHTML: itemExtra(props.config.itemExtra, $index),
|
||||
class: 'm-form-tip',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
columns.push({
|
||||
props: {
|
||||
label: '操作',
|
||||
fixed: props.config.fixed === false ? undefined : 'left',
|
||||
width: props.config.operateColWidth || 112,
|
||||
align: 'center',
|
||||
},
|
||||
cell: ({ row, $index }: any) =>
|
||||
h(ActionsColumn, {
|
||||
row,
|
||||
index: $index,
|
||||
model: props.model,
|
||||
config: props.config,
|
||||
prop: props.prop,
|
||||
disabled: props.disabled,
|
||||
sortKey: props.sortKey,
|
||||
name: modelName.value,
|
||||
currentPage: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
onChange: (v: any) => {
|
||||
emit('change', v);
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
if (props.sort && props.model[modelName.value] && props.model[modelName.value].length > 1) {
|
||||
columns.push({
|
||||
props: {
|
||||
label: '排序',
|
||||
width: 80,
|
||||
},
|
||||
cell: ({ $index }: any) =>
|
||||
h(SortColumn, {
|
||||
index: $index,
|
||||
model: props.model,
|
||||
disabled: props.disabled,
|
||||
name: modelName.value,
|
||||
currentPage: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
onSwap: (index1: number, index2: number) => {
|
||||
const newData = sortArray(props.model[modelName.value], index1, index2, props.sortKey);
|
||||
emit('change', newData);
|
||||
mForm?.$emit('field-change', newData);
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (selection.value) {
|
||||
columns.push({
|
||||
props: {
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
type: 'selection',
|
||||
width: 45,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (props.showIndex && props.config.showIndex) {
|
||||
columns.push({
|
||||
props: {
|
||||
label: '序号',
|
||||
width: 60,
|
||||
},
|
||||
cell: ({ $index }: any) => h('span', $index + 1 + currentPage.value * pageSize.value),
|
||||
});
|
||||
}
|
||||
|
||||
for (const column of props.config.items) {
|
||||
if (column.type !== 'hidden' && display(column.display)) {
|
||||
columns.push({
|
||||
props: {
|
||||
prop: column.name,
|
||||
label: column.label,
|
||||
width: column.width,
|
||||
sortable: column.sortable,
|
||||
sortOrders: ['ascending', 'descending'],
|
||||
class: props.config.dropSort === true ? 'el-table__column--dropable' : '',
|
||||
},
|
||||
cell: ({ row, $index }: any) =>
|
||||
h(Container, {
|
||||
labelWidth: '0',
|
||||
disabled: props.disabled,
|
||||
prop: getProp($index),
|
||||
rules: column.rules,
|
||||
config: makeConfig(column, row),
|
||||
model: row,
|
||||
lastValues: lastData.value[$index],
|
||||
isCompare: props.isCompare,
|
||||
size: props.size,
|
||||
onChange: changeHandler,
|
||||
onAddDiffCount,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return columns;
|
||||
});
|
||||
|
||||
return {
|
||||
columns,
|
||||
};
|
||||
};
|
||||
@ -3,3 +3,20 @@
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
.m-form-container {
|
||||
&.has-tip {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
.tmagic-design-form-item {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tmagic-design-form-item {
|
||||
&.show-diff {
|
||||
background: #f7dadd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,17 +19,21 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
.tmagic-design-table {
|
||||
.cell > div.m-form-container {
|
||||
display: block;
|
||||
|
||||
&.has-tip {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs {
|
||||
.tmagic-design-tabs {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.el-form-item.tmagic-form-hidden {
|
||||
.tmagic-design-form-item.tmagic-form-hidden {
|
||||
> .el-form-item__label {
|
||||
display: none;
|
||||
}
|
||||
@ -39,5 +43,13 @@
|
||||
> .t-form__label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> .t-form__controls {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.t-form:not(.t-form-inline) .t-form__item:last-of-type {
|
||||
margin-bottom: var(--td-comp-margin-xxl);
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,4 +20,19 @@
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tmagic-design-card {
|
||||
.el-card__header {
|
||||
padding: 5px 20px;
|
||||
}
|
||||
.t-card__header {
|
||||
padding: 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.m-fields-group-list-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
.m-fields-table-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.m-fields-table {
|
||||
width: 100%;
|
||||
&.fixed {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
@ -14,6 +11,8 @@
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
& > .el-form-item__content {
|
||||
z-index: 101;
|
||||
@ -25,6 +24,10 @@
|
||||
width: 95vw !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.m-fields-table {
|
||||
width: 100%;
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2 !important;
|
||||
|
||||
@ -21,6 +21,8 @@ import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import { getValueByKeyPath } from '@tmagic/utils';
|
||||
|
||||
import {
|
||||
ChildConfig,
|
||||
ContainerCommonConfig,
|
||||
@ -31,6 +33,7 @@ import {
|
||||
FormValue,
|
||||
HtmlField,
|
||||
Rule,
|
||||
SortProp,
|
||||
TabPaneConfig,
|
||||
TypeFunction,
|
||||
} from '../schema';
|
||||
@ -89,8 +92,13 @@ const setValue = (mForm: FormState | undefined, value: FormValue, initValue: For
|
||||
|
||||
// 如果fieldset配置checkbox,checkbox的值保存在value中
|
||||
if (type === 'fieldset' && checkbox) {
|
||||
if (typeof value[name] === 'object') {
|
||||
value[name].value = typeof initValue[name] === 'object' ? initValue[name].value || 0 : 0;
|
||||
const checkboxName = typeof checkbox === 'object' && typeof checkbox.name === 'string' ? checkbox.name : 'value';
|
||||
const checkboxFalseValue =
|
||||
typeof checkbox === 'object' && typeof checkbox.falseValue !== 'undefined' ? checkbox.falseValue : 0;
|
||||
|
||||
if (name && typeof value[name] === 'object') {
|
||||
value[name][checkboxName] =
|
||||
typeof initValue[name] === 'object' ? initValue[name][checkboxName] || checkboxFalseValue : checkboxFalseValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -136,6 +144,18 @@ const initValueItem = function (
|
||||
|
||||
setValue(mForm, value, initValue, item);
|
||||
|
||||
if (type === 'table') {
|
||||
if (item.defautSort) {
|
||||
sortChange(value[name], item.defautSort);
|
||||
} else if (item.defaultSort) {
|
||||
sortChange(value[name], item.defaultSort);
|
||||
}
|
||||
|
||||
if (item.sort && item.sortKey) {
|
||||
value[name].sort((a: any, b: any) => b[item.sortKey] - a[item.sortKey]);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
@ -197,6 +217,7 @@ export const filterFunction = <T = any>(
|
||||
prop: props.prop,
|
||||
config: props.config,
|
||||
index: props.index,
|
||||
getFormValue: (prop: string) => getValueByKeyPath(prop, mForm?.values || props.model),
|
||||
});
|
||||
}
|
||||
|
||||
@ -297,3 +318,36 @@ export const datetimeFormatter = (
|
||||
}
|
||||
return defaultValue;
|
||||
};
|
||||
|
||||
export const getDataByPage = (data: any[] = [], pagecontext: number, pagesize: number) =>
|
||||
data.filter(
|
||||
(item: any, index: number) => index >= pagecontext * pagesize && index + 1 <= (pagecontext + 1) * pagesize,
|
||||
);
|
||||
|
||||
export const sortArray = (data: any[], newIndex: number, oldIndex: number, sortKey?: string) => {
|
||||
if (newIndex === oldIndex) {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (newIndex < 0 || newIndex >= data.length || oldIndex < 0 || oldIndex >= data.length) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const newData = data.toSpliced(newIndex, 0, ...data.splice(oldIndex, 1));
|
||||
|
||||
if (sortKey) {
|
||||
for (let i = newData.length - 1, v = 0; i >= 0; i--, v++) {
|
||||
newData[v][sortKey] = i;
|
||||
}
|
||||
}
|
||||
|
||||
return newData;
|
||||
};
|
||||
|
||||
export const sortChange = (data: any[], { prop, order }: SortProp) => {
|
||||
if (order === 'ascending') {
|
||||
data = data.sort((a: any, b: any) => a[prop] - b[prop]);
|
||||
} else if (order === 'descending') {
|
||||
data = data.sort((a: any, b: any) => b[prop] - a[prop]);
|
||||
}
|
||||
};
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
*/
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { nextTick } from 'vue';
|
||||
import MagicForm, { MForm } from '@form/index';
|
||||
import MagicForm, { createForm, MForm } from '@form/index';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import ElementPlus from 'element-plus';
|
||||
|
||||
@ -45,4 +45,192 @@ describe('表单', () => {
|
||||
|
||||
expect(wrapper.text()).toBe('text');
|
||||
});
|
||||
|
||||
test('changeRecords', async () => {
|
||||
const initValues = {};
|
||||
const config = [
|
||||
{
|
||||
text: 'text',
|
||||
name: 'text',
|
||||
},
|
||||
{
|
||||
name: 'object',
|
||||
items: [
|
||||
{
|
||||
text: 'text',
|
||||
name: 'objectText',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
text: 'text',
|
||||
name: 'text1',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
items: [
|
||||
{
|
||||
name: 'object',
|
||||
items: [
|
||||
{
|
||||
text: 'text',
|
||||
name: 'text1',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const wrapper = mount(MForm, {
|
||||
global: {
|
||||
plugins: [ElementPlus as any, MagicForm as any],
|
||||
},
|
||||
props: {
|
||||
initValues,
|
||||
config,
|
||||
},
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
|
||||
const inputs = wrapper.findAll('input');
|
||||
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
const input = inputs[i];
|
||||
input.setValue(i);
|
||||
}
|
||||
expect(wrapper.vm.changeRecords).toEqual([
|
||||
{ propPath: 'text', value: '0' },
|
||||
{ propPath: 'object.objectText', value: '1' },
|
||||
{ propPath: 'text1', value: '2' },
|
||||
{ propPath: 'object.text1', value: '3' },
|
||||
]);
|
||||
expect(wrapper.vm.values).toEqual({
|
||||
text: '0',
|
||||
object: {
|
||||
objectText: '1',
|
||||
text1: '3',
|
||||
},
|
||||
text1: '2',
|
||||
});
|
||||
});
|
||||
|
||||
test('onChange setFormValue', async () => {
|
||||
const initValues = {};
|
||||
const config = createForm([
|
||||
{
|
||||
text: 'text',
|
||||
name: 'text',
|
||||
onChange: (vm, value: string, { formValue, setFormValue }: any) => {
|
||||
setFormValue('object.objectText', value);
|
||||
formValue!.object.objectText2 = value;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'object',
|
||||
items: [
|
||||
{
|
||||
text: 'text',
|
||||
name: 'objectText',
|
||||
},
|
||||
{
|
||||
text: 'text',
|
||||
name: 'objectText2',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
const wrapper = mount(MForm, {
|
||||
global: {
|
||||
plugins: [ElementPlus as any, MagicForm as any],
|
||||
},
|
||||
props: {
|
||||
initValues,
|
||||
config,
|
||||
},
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
|
||||
const input = wrapper.find('input');
|
||||
|
||||
input.setValue('a');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.changeRecords).toEqual([
|
||||
{ propPath: 'object.objectText', value: 'a' },
|
||||
{ propPath: 'object.objectText2', value: 'a' },
|
||||
{ propPath: 'text', value: 'a' },
|
||||
]);
|
||||
expect(wrapper.vm.values).toEqual({
|
||||
text: 'a',
|
||||
object: {
|
||||
objectText: 'a',
|
||||
objectText2: 'a',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('onChange setModel', async () => {
|
||||
const initValues = {};
|
||||
const config = createForm([
|
||||
{
|
||||
name: 'object',
|
||||
items: [
|
||||
{
|
||||
text: 'text',
|
||||
name: 'objectText',
|
||||
onChange: (vm: any, value: string, { model, setModel }: any) => {
|
||||
model.objectText2 = value;
|
||||
setModel('objectText3', value);
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'text',
|
||||
name: 'objectText2',
|
||||
},
|
||||
|
||||
{
|
||||
text: 'text',
|
||||
name: 'objectText3',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
const wrapper = mount(MForm, {
|
||||
global: {
|
||||
plugins: [ElementPlus as any, MagicForm as any],
|
||||
},
|
||||
props: {
|
||||
initValues,
|
||||
config,
|
||||
},
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
|
||||
const input = wrapper.find('input');
|
||||
|
||||
input.setValue('a');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.changeRecords).toEqual([
|
||||
{ propPath: 'object.objectText2', value: 'a' },
|
||||
{ propPath: 'object.objectText3', value: 'a' },
|
||||
{ propPath: 'object.objectText', value: 'a' },
|
||||
]);
|
||||
expect(wrapper.vm.values).toEqual({
|
||||
object: {
|
||||
objectText: 'a',
|
||||
objectText2: 'a',
|
||||
objectText3: 'a',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import type { FormState } from '@form/index';
|
||||
import { datetimeFormatter, display, filterFunction, getRules, initValue } from '@form/utils/form';
|
||||
import { datetimeFormatter, display, filterFunction, getRules, initValue, sortArray } from '@form/utils/form';
|
||||
|
||||
// form state mock 数据
|
||||
const mForm: FormState = {
|
||||
@ -32,6 +32,19 @@ const mForm: FormState = {
|
||||
setField: (prop: string, field: any) => field,
|
||||
getField: (prop: string) => prop,
|
||||
deleteField: (prop: string) => prop,
|
||||
$messageBox: {
|
||||
alert: () => Promise.resolve(),
|
||||
confirm: () => Promise.resolve(),
|
||||
prompt: () => Promise.resolve(),
|
||||
close: () => undefined,
|
||||
},
|
||||
$message: {
|
||||
success: () => undefined,
|
||||
warning: () => undefined,
|
||||
info: () => undefined,
|
||||
error: () => undefined,
|
||||
closeAll: () => undefined,
|
||||
},
|
||||
};
|
||||
|
||||
describe('filterFunction', () => {
|
||||
@ -339,3 +352,71 @@ describe('datetimeFormatter', () => {
|
||||
expect(datetimeFormatter(date.toISOString(), defaultValue, 'timestamp')).toBe(date.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortArray', () => {
|
||||
test('索引相同时不执行任何操作', () => {
|
||||
const data = [1, 2, 3, 4, 5];
|
||||
|
||||
expect(sortArray(data, 2, 2)).toEqual(data);
|
||||
});
|
||||
|
||||
test('正常交换两个元素的位置', () => {
|
||||
const data = [1, 2, 3, 4, 5];
|
||||
|
||||
expect(sortArray(data, 0, 3)).toEqual([4, 1, 2, 3, 5]);
|
||||
});
|
||||
|
||||
test('从后往前移动元素', () => {
|
||||
const data = [1, 2, 3, 4, 5];
|
||||
|
||||
expect(sortArray(data, 3, 1)).toEqual([1, 3, 4, 2, 5]);
|
||||
});
|
||||
|
||||
test('使用sortKey参数重新排序', () => {
|
||||
const data = [
|
||||
{ id: 1, order: 0 },
|
||||
{ id: 2, order: 1 },
|
||||
{ id: 3, order: 2 },
|
||||
{ id: 4, order: 3 },
|
||||
];
|
||||
|
||||
expect(sortArray(data, 0, 2, 'order')).toEqual([
|
||||
{ id: 3, order: 3 },
|
||||
{ id: 1, order: 2 },
|
||||
{ id: 2, order: 1 },
|
||||
{ id: 4, order: 0 },
|
||||
]);
|
||||
});
|
||||
|
||||
test('移动第一个元素到最后', () => {
|
||||
const data = [1, 2, 3, 4, 5];
|
||||
|
||||
expect(sortArray(data, 4, 0)).toEqual([2, 3, 4, 5, 1]);
|
||||
});
|
||||
|
||||
test('移动最后一个元素到第一个', () => {
|
||||
const data = [1, 2, 3, 4, 5];
|
||||
|
||||
expect(sortArray(data, 0, 4)).toEqual([5, 1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
test('空数组不执行任何操作', () => {
|
||||
const data: any[] = [];
|
||||
|
||||
expect(sortArray(data, 0, 1)).toEqual([]);
|
||||
});
|
||||
|
||||
test('只有一个元素的数组不执行任何操作', () => {
|
||||
const data = [1];
|
||||
|
||||
expect(sortArray(data, 0, 0)).toEqual([1]);
|
||||
});
|
||||
|
||||
test('索引超出范围时正常处理', () => {
|
||||
const data = [1, 2, 3];
|
||||
|
||||
// 索引超出范围应该由调用方处理,这里测试函数的行为
|
||||
expect(sortArray(data, 5, 1)).toEqual(data);
|
||||
expect(sortArray(data, 1, 5)).toEqual(data);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "@tmagic/schema",
|
||||
"type": "module",
|
||||
"main": "dist/tmagic-schema.umd.cjs",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "@tmagic/stage",
|
||||
"type": "module",
|
||||
"main": "dist/tmagic-stage.umd.cjs",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "@tmagic/table",
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
|
||||
@ -1,49 +1,46 @@
|
||||
<template>
|
||||
<TMagicTableColumn :label="config.label" :width="config.width" :fixed="config.fixed">
|
||||
<template v-slot="scope">
|
||||
<TMagicTooltip
|
||||
v-for="(action, actionIndex) in config.actions"
|
||||
:placement="action.tooltipPlacement || 'top'"
|
||||
:key="actionIndex"
|
||||
:disabled="!Boolean(action.tooltip)"
|
||||
:content="action.tooltip"
|
||||
>
|
||||
<TMagicButton
|
||||
v-show="display(action.display, scope.row) && !editState[scope.$index]"
|
||||
class="action-btn"
|
||||
link
|
||||
size="small"
|
||||
:type="action.buttonType || 'primary'"
|
||||
:icon="action.icon"
|
||||
:disabled="disabled(action.disabled, scope.row)"
|
||||
@click="actionHandler(action, scope.row, scope.$index)"
|
||||
><span v-html="formatter(action.text, scope.row)"></span
|
||||
></TMagicButton>
|
||||
</TMagicTooltip>
|
||||
<TMagicButton
|
||||
class="action-btn"
|
||||
v-show="editState[scope.$index]"
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="save(scope.$index, config)"
|
||||
>保存</TMagicButton
|
||||
>
|
||||
<TMagicButton
|
||||
class="action-btn"
|
||||
v-show="editState[scope.$index]"
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="editState[scope.$index] = undefined"
|
||||
>取消</TMagicButton
|
||||
>
|
||||
</template>
|
||||
</TMagicTableColumn>
|
||||
<TMagicTooltip
|
||||
v-for="(action, actionIndex) in config.actions"
|
||||
:placement="action.tooltipPlacement || 'top'"
|
||||
:key="actionIndex"
|
||||
:disabled="!Boolean(action.tooltip)"
|
||||
:content="action.tooltip"
|
||||
>
|
||||
<TMagicButton
|
||||
v-show="display(action.display, row) && !editState[index]"
|
||||
class="action-btn"
|
||||
link
|
||||
size="small"
|
||||
:type="action.buttonType || 'primary'"
|
||||
:icon="action.icon"
|
||||
:disabled="disabled(action.disabled, row)"
|
||||
@click="actionHandler(action, row, index)"
|
||||
><span v-html="formatter(action.text, row)"></span
|
||||
></TMagicButton>
|
||||
</TMagicTooltip>
|
||||
|
||||
<TMagicButton
|
||||
class="action-btn"
|
||||
v-show="editState[index]"
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="save(index, config)"
|
||||
>保存</TMagicButton
|
||||
>
|
||||
<TMagicButton
|
||||
class="action-btn"
|
||||
v-show="editState[index]"
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="editState[index] = undefined"
|
||||
>取消</TMagicButton
|
||||
>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TMagicButton, tMagicMessage, TMagicTableColumn, TMagicTooltip } from '@tmagic/design';
|
||||
import { TMagicButton, tMagicMessage, TMagicTooltip } from '@tmagic/design';
|
||||
|
||||
import { ColumnActionConfig, ColumnConfig } from './schema';
|
||||
|
||||
@ -53,10 +50,12 @@ defineOptions({
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
columns: any[];
|
||||
columns: ColumnConfig[];
|
||||
config: ColumnConfig;
|
||||
rowkeyName?: string;
|
||||
editState?: any;
|
||||
row: any;
|
||||
index: number;
|
||||
}>(),
|
||||
{
|
||||
columns: () => [],
|
||||
@ -114,7 +113,9 @@ const save = async (index: number, config: ColumnConfig) => {
|
||||
props.columns
|
||||
.filter((item) => item.type)
|
||||
.forEach((column) => {
|
||||
data[column.prop] = row[column.prop];
|
||||
if (column.prop) {
|
||||
data[column.prop] = row[column.prop];
|
||||
}
|
||||
});
|
||||
|
||||
const res: any = await action({
|
||||
|
||||
@ -1,25 +1,12 @@
|
||||
<template>
|
||||
<TMagicTableColumn
|
||||
show-overflow-tooltip
|
||||
:label="config.label"
|
||||
:width="config.width"
|
||||
:fixed="config.fixed"
|
||||
:sortable="config.sortable"
|
||||
:prop="config.prop"
|
||||
>
|
||||
<template v-slot="scope">
|
||||
<component
|
||||
:is="config.component"
|
||||
v-bind="componentProps(scope.row, scope.$index)"
|
||||
v-on="componentListeners(scope.row, scope.$index)"
|
||||
></component>
|
||||
</template>
|
||||
</TMagicTableColumn>
|
||||
<component
|
||||
:is="config.component"
|
||||
v-bind="componentProps(row, index)"
|
||||
v-on="componentListeners(row, index)"
|
||||
></component>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TMagicTableColumn } from '@tmagic/design';
|
||||
|
||||
import { ColumnConfig } from './schema';
|
||||
|
||||
defineOptions({
|
||||
@ -29,6 +16,8 @@ defineOptions({
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
config: ColumnConfig;
|
||||
row: any;
|
||||
index: number;
|
||||
}>(),
|
||||
{
|
||||
config: () => ({}),
|
||||
|
||||
@ -1,26 +1,20 @@
|
||||
<template>
|
||||
<!-- @ts-nocheck -->
|
||||
<TMagicTableColumn type="expand" :width="config.width" :fixed="config.fixed">
|
||||
<template #default="scope">
|
||||
<MTable
|
||||
v-if="config.table"
|
||||
:show-header="false"
|
||||
:columns="config.table"
|
||||
:data="(config.prop && scope.row[config.prop]) || []"
|
||||
></MTable>
|
||||
<MForm
|
||||
v-if="config.form"
|
||||
:config="config.form"
|
||||
:init-values="config.values || (config.prop && scope.row[config.prop]) || {}"
|
||||
></MForm>
|
||||
<div v-if="config.expandContent" v-html="config.expandContent(scope.row, config.prop)"></div>
|
||||
<component v-if="config.component" :is="config.component" v-bind="componentProps(scope.row)"></component>
|
||||
</template>
|
||||
</TMagicTableColumn>
|
||||
<MTable
|
||||
v-if="config.table"
|
||||
:show-header="false"
|
||||
:columns="config.table"
|
||||
:data="(config.prop && row[config.prop]) || []"
|
||||
></MTable>
|
||||
<MForm
|
||||
v-if="config.form"
|
||||
:config="config.form"
|
||||
:init-values="config.values || (config.prop && row[config.prop]) || {}"
|
||||
></MForm>
|
||||
<div v-if="config.expandContent" v-html="config.expandContent(row, config.prop)"></div>
|
||||
<component v-if="config.component" :is="config.component" v-bind="componentProps(row)"></component>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TMagicTableColumn } from '@tmagic/design';
|
||||
import { MForm } from '@tmagic/form';
|
||||
|
||||
import { ColumnConfig } from './schema';
|
||||
@ -33,6 +27,7 @@ defineOptions({
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
config: ColumnConfig;
|
||||
row: any;
|
||||
}>(),
|
||||
{
|
||||
config: () => ({}),
|
||||
|
||||
@ -1,32 +1,25 @@
|
||||
<template>
|
||||
<!-- @ts-nocheck -->
|
||||
<TMagicTableColumn :label="config.label" :width="config.width" :fixed="config.fixed">
|
||||
<template v-slot="scope">
|
||||
<TMagicPopover
|
||||
v-if="config.popover"
|
||||
:placement="config.popover.placement"
|
||||
:width="config.popover.width"
|
||||
:trigger="config.popover.trigger"
|
||||
:destroy-on-close="config.popover.destroyOnClose ?? true"
|
||||
>
|
||||
<MTable
|
||||
v-if="config.popover.tableEmbed"
|
||||
:show-header="config.showHeader"
|
||||
:columns="config.table"
|
||||
:data="(config.prop && scope.row[config.prop]) || []"
|
||||
></MTable>
|
||||
<template #reference>
|
||||
<TMagicButton link type="primary">
|
||||
{{ config.text || formatter(config, scope.row, { index: scope.$index }) }}</TMagicButton
|
||||
>
|
||||
</template>
|
||||
</TMagicPopover>
|
||||
<TMagicPopover
|
||||
v-if="config.popover"
|
||||
:placement="config.popover.placement"
|
||||
:width="config.popover.width"
|
||||
:trigger="config.popover.trigger"
|
||||
:destroy-on-close="config.popover.destroyOnClose ?? true"
|
||||
>
|
||||
<MTable
|
||||
v-if="config.popover.tableEmbed"
|
||||
:show-header="config.showHeader"
|
||||
:columns="config.table"
|
||||
:data="(config.prop && row[config.prop]) || []"
|
||||
></MTable>
|
||||
<template #reference>
|
||||
<TMagicButton link type="primary">{{ config.text || formatter(config, row, { index: index }) }}</TMagicButton>
|
||||
</template>
|
||||
</TMagicTableColumn>
|
||||
</TMagicPopover>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TMagicButton, TMagicPopover, TMagicTableColumn } from '@tmagic/design';
|
||||
import { TMagicButton, TMagicPopover } from '@tmagic/design';
|
||||
|
||||
import { ColumnConfig } from './schema';
|
||||
import MTable from './Table.vue';
|
||||
@ -39,6 +32,8 @@ defineOptions({
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
config: ColumnConfig;
|
||||
row: any;
|
||||
index: number;
|
||||
}>(),
|
||||
{
|
||||
config: () => ({}),
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<TMagicTable
|
||||
tooltip-effect="dark"
|
||||
:tooltip-options="{ popperOptions: { strategy: 'absolute' } }"
|
||||
v-loading="loading"
|
||||
class="m-table"
|
||||
ref="tMagicTable"
|
||||
v-loading="loading"
|
||||
:show-overflow-tooltip="true"
|
||||
tooltip-effect="dark"
|
||||
:tooltip-options="{ popperOptions: { strategy: 'absolute' } }"
|
||||
:data="tableData"
|
||||
:show-header="showHeader"
|
||||
:max-height="bodyHeight"
|
||||
@ -14,59 +15,21 @@
|
||||
:tree-props="{ children: 'children' }"
|
||||
:empty-text="emptyText || '暂无数据'"
|
||||
:span-method="objectSpanMethod"
|
||||
:columns="tableColumns"
|
||||
@sort-change="sortChange"
|
||||
@select="selectHandler"
|
||||
@select-all="selectAllHandler"
|
||||
@selection-change="selectionChangeHandler"
|
||||
@cell-click="cellClickHandler"
|
||||
@expand-change="expandChange"
|
||||
>
|
||||
<template v-for="(item, columnIndex) in columns">
|
||||
<template v-if="item.type === 'expand'">
|
||||
<ExpandColumn :config="item" :key="columnIndex"></ExpandColumn>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.type === 'component'">
|
||||
<ComponentColumn :config="item" :key="columnIndex"></ComponentColumn>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.selection">
|
||||
<component
|
||||
width="40"
|
||||
type="selection"
|
||||
:is="tableColumnComponent?.component || 'el-table-column'"
|
||||
:key="columnIndex"
|
||||
:selectable="item.selectable"
|
||||
></component>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.actions">
|
||||
<ActionsColumn
|
||||
:columns="columns"
|
||||
:config="item"
|
||||
:rowkey-name="rowkeyName"
|
||||
:edit-state="editState"
|
||||
:key="columnIndex"
|
||||
@after-action="$emit('after-action')"
|
||||
></ActionsColumn>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.type === 'popover'">
|
||||
<PopoverColumn :key="columnIndex" :config="item"></PopoverColumn>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<TextColumn :key="columnIndex" :config="item" :edit-state="editState"></TextColumn>
|
||||
</template>
|
||||
</template>
|
||||
</TMagicTable>
|
||||
></TMagicTable>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, h, ref, useTemplateRef } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import { getDesignConfig, TMagicTable } from '@tmagic/design';
|
||||
import { TMagicTable } from '@tmagic/design';
|
||||
|
||||
import ActionsColumn from './ActionsColumn.vue';
|
||||
import ComponentColumn from './ComponentColumn.vue';
|
||||
@ -117,11 +80,72 @@ const emit = defineEmits([
|
||||
'cell-click',
|
||||
]);
|
||||
|
||||
const tMagicTable = ref<InstanceType<typeof TMagicTable>>();
|
||||
const cellRender = (config: ColumnConfig, { row = {}, $index }: any) => {
|
||||
if (config.type === 'expand') {
|
||||
return h(ExpandColumn, {
|
||||
config,
|
||||
row,
|
||||
});
|
||||
}
|
||||
if (config.type === 'component') {
|
||||
return h(ComponentColumn, {
|
||||
config,
|
||||
row,
|
||||
index: $index,
|
||||
});
|
||||
}
|
||||
if (config.actions) {
|
||||
return h(ActionsColumn, {
|
||||
config,
|
||||
row,
|
||||
index: $index,
|
||||
rowkeyName: props.rowkeyName,
|
||||
editState: editState.value,
|
||||
columns: props.columns,
|
||||
});
|
||||
}
|
||||
if (config.type === 'popover') {
|
||||
return h(PopoverColumn, {
|
||||
config,
|
||||
row,
|
||||
index: $index,
|
||||
});
|
||||
}
|
||||
return h(TextColumn, {
|
||||
config,
|
||||
row,
|
||||
index: $index,
|
||||
editState: editState.value,
|
||||
});
|
||||
};
|
||||
|
||||
const tableColumns = computed(() =>
|
||||
props.columns.map((item) => {
|
||||
let type: 'default' | 'selection' | 'index' | 'expand' = 'default';
|
||||
if (item.type === 'expand') {
|
||||
type = 'expand';
|
||||
} else if (item.selection) {
|
||||
type = 'selection';
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
label: item.label,
|
||||
fixed: item.fixed,
|
||||
width: item.width ?? (item.selection ? 40 : undefined),
|
||||
prop: item.prop,
|
||||
type,
|
||||
selectable: item.selectable,
|
||||
},
|
||||
cell: type === 'selection' ? undefined : ({ row, $index }: any) => cellRender(item, { row, $index }),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const tMagicTableRef = useTemplateRef('tMagicTable');
|
||||
|
||||
const editState = ref([]);
|
||||
|
||||
const tableColumnComponent = getDesignConfig('components')?.tableColumn;
|
||||
const selectionColumn = computed(() => {
|
||||
const column = props.columns.filter((item) => item.selection);
|
||||
return column.length ? column[0] : null;
|
||||
@ -171,15 +195,15 @@ const expandChange = (...args: any[]) => {
|
||||
};
|
||||
|
||||
const toggleRowSelection = (row: any, selected: boolean) => {
|
||||
tMagicTable.value?.toggleRowSelection(row, selected);
|
||||
tMagicTableRef.value?.toggleRowSelection(row, selected);
|
||||
};
|
||||
|
||||
const toggleRowExpansion = (row: any, expanded: boolean) => {
|
||||
tMagicTable.value?.toggleRowExpansion(row, expanded);
|
||||
tMagicTableRef.value?.toggleRowExpansion(row, expanded);
|
||||
};
|
||||
|
||||
const clearSelection = () => {
|
||||
tMagicTable.value?.clearSelection();
|
||||
tMagicTableRef.value?.clearSelection();
|
||||
};
|
||||
|
||||
const objectSpanMethod = (data: any) => {
|
||||
|
||||
@ -1,69 +1,52 @@
|
||||
<template>
|
||||
<TMagicTableColumn
|
||||
show-overflow-tooltip
|
||||
:label="config.label"
|
||||
:width="config.width"
|
||||
:fixed="config.fixed"
|
||||
:sortable="config.sortable"
|
||||
:prop="config.prop"
|
||||
<div v-if="config.type === 'index'">
|
||||
{{ config.pageIndex && config.pageSize ? config.pageIndex * config.pageSize + index + 1 : index + 1 }}
|
||||
</div>
|
||||
<TMagicForm v-else-if="config.type && editState[index]" label-width="0" :model="editState[index]">
|
||||
<m-form-container
|
||||
:prop="config.prop"
|
||||
:rules="config.rules"
|
||||
:config="config"
|
||||
:name="config.prop"
|
||||
:model="editState[index]"
|
||||
></m-form-container>
|
||||
</TMagicForm>
|
||||
|
||||
<TMagicButton
|
||||
v-else-if="config.action === 'actionLink' && config.prop"
|
||||
link
|
||||
type="primary"
|
||||
@click="config.handler?.(row)"
|
||||
>
|
||||
<template v-slot="scope">
|
||||
<div v-if="config.type === 'index'">
|
||||
{{
|
||||
config.pageIndex && config.pageSize ? config.pageIndex * config.pageSize + scope.$index + 1 : scope.$index + 1
|
||||
}}
|
||||
</div>
|
||||
<TMagicForm v-else-if="config.type && editState[scope.$index]" label-width="0" :model="editState[scope.$index]">
|
||||
<m-form-container
|
||||
:prop="config.prop"
|
||||
:rules="config.rules"
|
||||
:config="config"
|
||||
:name="config.prop"
|
||||
:model="editState[scope.$index]"
|
||||
></m-form-container>
|
||||
</TMagicForm>
|
||||
<span v-html="formatter(config, row, { index: index })"></span>
|
||||
</TMagicButton>
|
||||
|
||||
<TMagicButton
|
||||
v-else-if="config.action === 'actionLink' && config.prop"
|
||||
link
|
||||
type="primary"
|
||||
@click="config.handler?.(scope.row)"
|
||||
>
|
||||
<span v-html="formatter(config, scope.row, { index: scope.$index })"></span>
|
||||
</TMagicButton>
|
||||
<a v-else-if="config.action === 'img' && config.prop" target="_blank" :href="row[config.prop]"
|
||||
><img :src="row[config.prop]" height="50"
|
||||
/></a>
|
||||
|
||||
<a v-else-if="config.action === 'img' && config.prop" target="_blank" :href="scope.row[config.prop]"
|
||||
><img :src="scope.row[config.prop]" height="50"
|
||||
/></a>
|
||||
<a v-else-if="config.action === 'link' && config.prop" target="_blank" :href="row[config.prop]" class="keep-all">{{
|
||||
row[config.prop]
|
||||
}}</a>
|
||||
|
||||
<a
|
||||
v-else-if="config.action === 'link' && config.prop"
|
||||
target="_blank"
|
||||
:href="scope.row[config.prop]"
|
||||
class="keep-all"
|
||||
>{{ scope.row[config.prop] }}</a
|
||||
>
|
||||
|
||||
<el-tooltip v-else-if="config.action === 'tip'" placement="left">
|
||||
<template #content>
|
||||
<div>{{ formatter(config, scope.row, { index: scope.$index }) }}</div>
|
||||
</template>
|
||||
<TMagicButton link type="primary">{{ config.buttonText || '扩展配置' }}</TMagicButton>
|
||||
</el-tooltip>
|
||||
|
||||
<TMagicTag
|
||||
v-else-if="config.action === 'tag' && config.prop"
|
||||
:type="typeof config.type === 'function' ? config.type(scope.row[config.prop], scope.row) : config.type"
|
||||
close-transition
|
||||
>{{ formatter(config, scope.row, { index: scope.$index }) }}</TMagicTag
|
||||
>
|
||||
<div v-else v-html="formatter(config, scope.row, { index: scope.$index })"></div>
|
||||
<TMagicTooltip v-else-if="config.action === 'tip'" placement="left">
|
||||
<template #content>
|
||||
<div>{{ formatter(config, row, { index: index }) }}</div>
|
||||
</template>
|
||||
</TMagicTableColumn>
|
||||
<TMagicButton link type="primary">{{ config.buttonText || '扩展配置' }}</TMagicButton>
|
||||
</TMagicTooltip>
|
||||
|
||||
<TMagicTag
|
||||
v-else-if="config.action === 'tag' && config.prop"
|
||||
:type="typeof config.type === 'function' ? config.type(row[config.prop], row) : config.type"
|
||||
close-transition
|
||||
>{{ formatter(config, row, { index: index }) }}</TMagicTag
|
||||
>
|
||||
<div v-else v-html="formatter(config, row, { index: index })"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TMagicButton, TMagicForm, TMagicTableColumn, TMagicTag } from '@tmagic/design';
|
||||
import { TMagicButton, TMagicForm, TMagicTag, TMagicTooltip } from '@tmagic/design';
|
||||
|
||||
import { ColumnConfig } from './schema';
|
||||
import { formatter } from './utils';
|
||||
@ -76,6 +59,8 @@ withDefaults(
|
||||
defineProps<{
|
||||
config: ColumnConfig;
|
||||
editState?: any;
|
||||
row: any;
|
||||
index: number;
|
||||
}>(),
|
||||
{
|
||||
config: () => ({}),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta.3",
|
||||
"name": "@tmagic/tdesign-vue-next-adapter",
|
||||
"type": "module",
|
||||
"main": "dist/tmagic-tdesign-vue-next-adapter.umd.cjs",
|
||||
@ -39,7 +39,7 @@
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@tmagic/design": "workspace:*",
|
||||
"tdesign-vue-next": "^1.9.8",
|
||||
"tdesign-vue-next": "^1.17.1",
|
||||
"vue": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
|
||||
47
packages/tdesign-vue-next-adapter/src/Checkbox.vue
Normal file
47
packages/tdesign-vue-next-adapter/src/Checkbox.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<TCheckbox v-model="checked" :disabled="disabled" :value="value" @change="changeHandler">
|
||||
<template #default v-if="$slots.default"> <slot></slot> </template>
|
||||
</TCheckbox>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { Checkbox as TCheckbox } from 'tdesign-vue-next';
|
||||
|
||||
import type { CheckboxProps } from '@tmagic/design';
|
||||
|
||||
defineOptions({
|
||||
name: 'TTDesignAdapterCheckbox',
|
||||
});
|
||||
|
||||
const props = defineProps<CheckboxProps>();
|
||||
|
||||
const emit = defineEmits(['change', 'update:modelValue']);
|
||||
|
||||
const checked = ref(false);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(v) => {
|
||||
if (typeof props.trueValue !== 'undefined') {
|
||||
checked.value = v === props.trueValue;
|
||||
} else if (typeof props.falseValue !== 'undefined') {
|
||||
checked.value = v !== props.falseValue;
|
||||
} else {
|
||||
checked.value = Boolean(v);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
const changeHandler = (v: boolean) => {
|
||||
updateModelValue(v);
|
||||
emit('change', v ? (props.trueValue ?? true) : (props.falseValue ?? false));
|
||||
};
|
||||
|
||||
const updateModelValue = (v: boolean) => {
|
||||
emit('update:modelValue', v ? (props.trueValue ?? true) : (props.falseValue ?? false));
|
||||
};
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user