mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-09-08 21:19:30 +00:00
feat(design,editor): 新增magic-admin主题样式,优化组件表单配置布局样式
This commit is contained in:
parent
89689bb9fd
commit
b13cd3425b
@ -162,7 +162,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #409eff;
|
color: var(--el-color-primary,#409eff);;
|
||||||
background-color: #f9fafc;
|
background-color: #f9fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export default defineConfig([
|
|||||||
'*/**/public/**/*',
|
'*/**/public/**/*',
|
||||||
'*/**/types/**/*',
|
'*/**/types/**/*',
|
||||||
'*/**/*.config.ts',
|
'*/**/*.config.ts',
|
||||||
'./tepm/**/*',
|
'./temp/**/*',
|
||||||
'vite-env.d.ts',
|
'vite-env.d.ts',
|
||||||
]),
|
]),
|
||||||
...eslintConfig(path.join(path.dirname(fileURLToPath(import.meta.url)), 'tsconfig.json')),
|
...eslintConfig(path.join(path.dirname(fileURLToPath(import.meta.url)), 'tsconfig.json')),
|
||||||
|
|||||||
23
packages/design/src/Alert.vue
Normal file
23
packages/design/src/Alert.vue
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<component class="tmagic-design-alert" :is="uiComponent" v-bind="uiProps">
|
||||||
|
<slot></slot>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { getDesignConfig } from './config';
|
||||||
|
import type { AlertProps } from './types';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'TMAlert',
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps<AlertProps>();
|
||||||
|
|
||||||
|
const ui = getDesignConfig('components')?.alert;
|
||||||
|
const uiComponent = ui?.component || 'el-alert';
|
||||||
|
|
||||||
|
const uiProps = computed<AlertProps>(() => ui?.props(props) || props);
|
||||||
|
</script>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<component class="tmagic-design-card" :is="uiComponent" v-bind="uiProps">
|
<component :is="uiComponent" :class="['tmagic-design-card', { 'tmagic-design-card--flat': isFlat }]" v-bind="uiProps">
|
||||||
<template #header v-if="$slots.header">
|
<template #header v-if="$slots.header">
|
||||||
<slot name="header" class="header"></slot>
|
<slot name="header" class="header"></slot>
|
||||||
</template>
|
</template>
|
||||||
@ -14,6 +14,7 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { getDesignConfig } from './config';
|
import { getDesignConfig } from './config';
|
||||||
|
import { isGlobalFlat } from './index';
|
||||||
import type { CardProps } from './types';
|
import type { CardProps } from './types';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@ -26,5 +27,14 @@ const ui = getDesignConfig('components')?.card;
|
|||||||
|
|
||||||
const uiComponent = ui?.component || 'el-card';
|
const uiComponent = ui?.component || 'el-card';
|
||||||
|
|
||||||
const uiProps = computed<CardProps>(() => ui?.props(props) || props);
|
// 当祖先 `<MEditor>` / `<MForm>` 的 theme 是 `magic-admin` 时,整套设计语言默认走「无卡片感」,
|
||||||
|
// 因此这里自动等价于 `flat=true`;调用方仍可显式传 `flat` 强制开启。
|
||||||
|
const isFlat = computed(() => !!props.flat || isGlobalFlat.value);
|
||||||
|
|
||||||
|
// 把 `flat` 从转发给底层 UI 库(el-card / t-card 等)的 props 中剥离:
|
||||||
|
// 它仅作用于本组件的视觉修饰类,不需要也不应该作为属性落到底层组件 / DOM 上。
|
||||||
|
const uiProps = computed<CardProps>(() => {
|
||||||
|
const { flat: _flat, ...rest } = props;
|
||||||
|
return ui?.props(rest as CardProps) || (rest as CardProps);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -7,18 +7,39 @@
|
|||||||
@update:modelValue="updateModelValue"
|
@update:modelValue="updateModelValue"
|
||||||
>
|
>
|
||||||
</component>
|
</component>
|
||||||
|
<MInput
|
||||||
|
v-if="isFlat && !isLargeStageContainer"
|
||||||
|
@update:modelValue="updateModelValue"
|
||||||
|
@change="changeHandler"
|
||||||
|
v-bind="uiProps"
|
||||||
|
class="tmagic-design-color-picker-input"
|
||||||
|
clearable
|
||||||
|
></MInput>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed, type ComputedRef, inject } from 'vue';
|
||||||
|
|
||||||
import { getDesignConfig } from './config';
|
import { getDesignConfig } from './config';
|
||||||
|
import { isGlobalFlat } from './index';
|
||||||
|
import MInput from './Input.vue';
|
||||||
import type { ColorPickerProps } from './types';
|
import type { ColorPickerProps } from './types';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'TMColorPicker',
|
name: 'TMColorPicker',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isFlat = computed(() => !!props.flat || isGlobalFlat.value);
|
||||||
|
|
||||||
|
// 祖先 `<MEditor>` 用 computed 包着 provide,值会随 props.isLargeStageContainer 变化。
|
||||||
|
// 类型标成 `ComputedRef<boolean>`,模板里 `<script setup>` 自动解包,仍可直接写
|
||||||
|
// `!isLargeStageContainer`;默认值也得包成 computed,保持形状一致,避免祖先没提供时
|
||||||
|
// 模板里对一个裸 `false` 和 `ComputedRef` 走两条不同的取值路径。
|
||||||
|
const isLargeStageContainer = inject<ComputedRef<boolean>>(
|
||||||
|
'isLargeStageContainer',
|
||||||
|
computed(() => false),
|
||||||
|
);
|
||||||
|
|
||||||
const props = withDefaults(defineProps<ColorPickerProps>(), {
|
const props = withDefaults(defineProps<ColorPickerProps>(), {
|
||||||
showAlpha: false,
|
showAlpha: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
|||||||
@ -1,12 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<component class="tmagic-design-form-item" :is="uiComponent" v-bind="uiProps">
|
<component
|
||||||
|
class="tmagic-design-form-item"
|
||||||
|
:class="{ 'has-extra-tips': adapterType === 'element-plus' && extraTips }"
|
||||||
|
:is="uiComponent"
|
||||||
|
v-bind="uiProps"
|
||||||
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<slot name="label"></slot>
|
<slot name="label"> </slot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #default>
|
<template #default>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<div v-if="adapterType === 'element-plus' && extra" v-html="extra" class="m-form-tip"></div>
|
<alert
|
||||||
|
v-if="adapterType === 'element-plus' && extraTips"
|
||||||
|
:title="extraTips"
|
||||||
|
type="warning"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
></alert>
|
||||||
|
<div v-else-if="adapterType === 'element-plus' && extra" v-html="extra" class="m-form-tip"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="adapterType === 'element-plus'" #error="{ error }">
|
<template v-if="adapterType === 'element-plus'" #error="{ error }">
|
||||||
@ -16,26 +28,32 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed, inject } from 'vue';
|
||||||
|
|
||||||
|
import Alert from './Alert.vue';
|
||||||
import { getDesignConfig } from './config';
|
import { getDesignConfig } from './config';
|
||||||
import { stripValidateSuggestion } from './formValidateMessage';
|
import { stripValidateSuggestion } from './formValidateMessage';
|
||||||
|
import { isGlobalFlat } from './index';
|
||||||
import type { FormItemProps } from './types';
|
import type { FormItemProps } from './types';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'TMFormItem',
|
name: 'TMFormItem',
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<FormItemProps>();
|
const props = defineProps<FormItemProps & { theme?: string }>();
|
||||||
|
|
||||||
const ui = getDesignConfig('components')?.formItem;
|
const ui = getDesignConfig('components')?.formItem;
|
||||||
|
|
||||||
const uiComponent = ui?.component || 'el-form-item';
|
const uiComponent = ui?.component || 'el-form-item';
|
||||||
|
|
||||||
const adapterType = getDesignConfig('adapterType');
|
const adapterType = getDesignConfig('adapterType');
|
||||||
|
|
||||||
|
const formInline = inject<boolean>('formInline');
|
||||||
|
|
||||||
const uiProps = computed<FormItemProps>(() => {
|
const uiProps = computed<FormItemProps>(() => {
|
||||||
const { extra, ...rest } = ui?.props(props) || props;
|
const { extra, extraTips, ...rest } = ui?.props(props) || props;
|
||||||
|
if (isGlobalFlat.value && rest.labelPosition === undefined) {
|
||||||
|
return { ...rest, labelPosition: formInline ? 'right' : 'left' };
|
||||||
|
}
|
||||||
return rest;
|
return rest;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,10 +4,9 @@
|
|||||||
<div
|
<div
|
||||||
v-if="popoverVisible || !destroyOnClose"
|
v-if="popoverVisible || !destroyOnClose"
|
||||||
v-show="popoverVisible"
|
v-show="popoverVisible"
|
||||||
class="tmagic-design-popper"
|
|
||||||
ref="popperElementRef"
|
ref="popperElementRef"
|
||||||
:tabindex="tabindex"
|
:tabindex="tabindex"
|
||||||
:class="popperClass"
|
:class="['tmagic-design-popper', popperClass, themeClass]"
|
||||||
:style="style"
|
:style="style"
|
||||||
@mouseenter.once="popperMouseenterHandler"
|
@mouseenter.once="popperMouseenterHandler"
|
||||||
>
|
>
|
||||||
@ -22,7 +21,7 @@ import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, ref
|
|||||||
import type { Instance } from '@popperjs/core';
|
import type { Instance } from '@popperjs/core';
|
||||||
import { createPopper } from '@popperjs/core';
|
import { createPopper } from '@popperjs/core';
|
||||||
|
|
||||||
import { useZIndex } from './index';
|
import { useThemeClass, useZIndex } from './index';
|
||||||
import type { PopoverProps } from './types';
|
import type { PopoverProps } from './types';
|
||||||
|
|
||||||
defineSlots<{
|
defineSlots<{
|
||||||
@ -46,6 +45,12 @@ const props = withDefaults(defineProps<PopoverProps>(), {
|
|||||||
closeOnClickOutside: true,
|
closeOnClickOutside: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题修饰类(来自最近的 `<MEditor>` / `<MForm>` 祖先 provide)。
|
||||||
|
* 挂在 `Teleport` 出去的 popper 根节点上,让主题级 CSS 变量(`--el-color-primary` 等)
|
||||||
|
* 在 portal 节点上也能命中。详见 `@tmagic/design/theme.ts`。
|
||||||
|
*/
|
||||||
|
const themeClass = useThemeClass();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
/** 受控模式(传入了 visible)下点击外部收起时触发,便于配合 v-model:visible。 */
|
/** 受控模式(传入了 visible)下点击外部收起时触发,便于配合 v-model:visible。 */
|
||||||
'update:visible': [_visible: boolean];
|
'update:visible': [_visible: boolean];
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import './theme/index.scss';
|
|||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './config';
|
export * from './config';
|
||||||
|
export * from './theme';
|
||||||
export * from './formValidateMessage';
|
export * from './formValidateMessage';
|
||||||
|
|
||||||
export { default as TMagicAutocomplete } from './Autocomplete.vue';
|
export { default as TMagicAutocomplete } from './Autocomplete.vue';
|
||||||
@ -94,6 +95,10 @@ export let useZIndex = (zIndexOverrides?: Ref<number>) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 与 form/utils/config、editor/utils/config 里的同名变量保持一致:用 ref 持有 flat 全局开关,
|
||||||
|
// const 引用本身不可变,规避 `no-mutable-exports` / `naming-convention` 对 `let` 模块变量的限制。
|
||||||
|
export const isGlobalFlat = ref(false);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install(app: App, options: DesignPluginOptions) {
|
install(app: App, options: DesignPluginOptions) {
|
||||||
tMagicMessage =
|
tMagicMessage =
|
||||||
@ -129,6 +134,8 @@ export default {
|
|||||||
useZIndex = options.useZIndex;
|
useZIndex = options.useZIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isGlobalFlat.value = options.flat ?? false;
|
||||||
|
|
||||||
if (options.adapterType && globalThis.document?.documentElement) {
|
if (options.adapterType && globalThis.document?.documentElement) {
|
||||||
globalThis.document.documentElement.classList.add(`tmagic-adapter-${options.adapterType}`);
|
globalThis.document.documentElement.classList.add(`tmagic-adapter-${options.adapterType}`);
|
||||||
}
|
}
|
||||||
|
|||||||
59
packages/design/src/theme.ts
Normal file
59
packages/design/src/theme.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 主题作用域穿透到 portal 节点的基建。
|
||||||
|
*
|
||||||
|
* `<MEditor>` / `<MForm>` 通过 provide 暴露当前的 `theme`,包含 `<Teleport>` 的组件
|
||||||
|
* 通过 `useThemeClass()` inject 得到主题修饰类,挂到传送目标根节点上即可让主题级
|
||||||
|
* CSS 变量(如 `--el-color-primary`)在 body 下的 portal 节点上也命中。
|
||||||
|
*
|
||||||
|
* 命名约定:portal 节点本身既不是 `m-editor` 也不是 `m-form`,因此使用独立的中性类
|
||||||
|
* `m-theme--<theme>`,避免语义混淆。同时把跨组件共享的主题级 CSS 变量统一挂在该类上,
|
||||||
|
* editor 根 / form 根 / portal 根都加上该类即可统一受用,editor / form 各自的内部
|
||||||
|
* 样式仍保留在 `.m-editor.m-editor--<theme>` / `.m-form.m-form--<theme>` 双类选择器
|
||||||
|
* 上,互不污染。
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ComputedRef, InjectionKey, Ref } from 'vue';
|
||||||
|
import { computed, inject } from 'vue';
|
||||||
|
|
||||||
|
/** 祖先 `<MEditor>` / `<MForm>` 暴露的当前 theme(一般来自 props.theme,经 MForm 时
|
||||||
|
* 若自身没设 theme,会透传外层 `<MEditor>` 的值,详见 Form.vue 中的 effectiveTheme)。 */
|
||||||
|
export const M_THEME_KEY: InjectionKey<Ref<string>> = Symbol('mTheme');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得来自最近 `<MEditor>` / `<MForm>` 祖先 provide 的主题,拼成可直接套到 class 字符串
|
||||||
|
* 的格式:例如 `"m-theme--magic-admin"`。
|
||||||
|
*
|
||||||
|
* - 不在主题作用域里时返回空串,调用方可直接拼接,无需额外判空。
|
||||||
|
* - 与 `m-editor` / `m-form` 同名修饰类完全解耦:portal 节点只挂这一个中性主题类,
|
||||||
|
* 不会意外命中 editor / form 内部 DOM 的样式作用域。
|
||||||
|
*
|
||||||
|
* 使用场景:组件自身渲染了 `<Teleport>`(如 `TMagicPopover` / `FloatingBox` /
|
||||||
|
* `ContentMenu`),需要在传送目标根节点上挂主题类,让主题 CSS 变量能命中。
|
||||||
|
*/
|
||||||
|
export const useThemeClass = (): ComputedRef<string> => {
|
||||||
|
const theme = inject(M_THEME_KEY, null);
|
||||||
|
return computed(() => {
|
||||||
|
const t = theme?.value;
|
||||||
|
return t ? `m-theme--${t}` : '';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在组件内统一解析当前生效的 theme:以 `prop.theme` 为准,缺省时回退到祖先
|
||||||
|
* `<MEditor>` / `<MForm>` provide 的主题。
|
||||||
|
*
|
||||||
|
* 入参 `prop` 是任意结构,约束只看 `theme?: string`。一般直接把 `defineProps()` 的
|
||||||
|
* 返回值传进来即可(reactive proxy,computed 可正确跟踪外部更新);不要传
|
||||||
|
* `toRefs` 解构后的对象。
|
||||||
|
*
|
||||||
|
* 若 `prop.theme` 与祖先都没有,返回空串,调用方可直接拼接。
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const props = defineProps<{ theme?: string }>();
|
||||||
|
* const theme = useTheme(props);
|
||||||
|
* // theme.value -> 'magic-admin' 或 ''
|
||||||
|
*/
|
||||||
|
export const useTheme = <T extends { theme?: string }>(prop: T): ComputedRef<string> => {
|
||||||
|
const injected = inject(M_THEME_KEY, null);
|
||||||
|
return computed(() => prop.theme || injected?.value || '');
|
||||||
|
};
|
||||||
67
packages/design/src/theme/card.scss
Normal file
67
packages/design/src/theme/card.scss
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// `TMagicCard` 的「铺平」模式:去掉卡片自身的视觉表达(背景 / 阴影 / 边框 / 圆角 /
|
||||||
|
// header 分隔线 / header & body 内边距),让卡片在视觉上与外层容器融为一体。
|
||||||
|
//
|
||||||
|
// 不动 body-style、不动折叠相关 prop —— 这些上层用法仍可正常工作。
|
||||||
|
// `is-always-shadow` 是 el-card 在 `shadow="always"` 时附加的类,需要单独再压一次。
|
||||||
|
.tmagic-design-card.tmagic-design-card--flat {
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-top: 0;
|
||||||
|
border-left: 0;
|
||||||
|
border-right: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
|
||||||
|
&.m-fields-group-list-item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
&.is-always-shadow {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card__header {
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 16px;
|
||||||
|
&:hover {
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card__body {
|
||||||
|
padding: 0 16px 0 16px;
|
||||||
|
// 这里每个form-item应该要有一个16px间距,但是除了tbody中的form-item
|
||||||
|
.tmagic-design-form-item:not(tbody .tmagic-design-form-item) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
> .m-container-row {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tmagic-design-card--flat {
|
||||||
|
// .el-card__body {
|
||||||
|
// padding: 0;
|
||||||
|
// }
|
||||||
|
// .el-card__header {
|
||||||
|
// padding: 0 16px;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-split-title-card-container {
|
||||||
|
> .tmagic-design-card.tmagic-design-card--flat {
|
||||||
|
background-color: transparent;
|
||||||
|
> .el-card__header {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
> .el-card__body {
|
||||||
|
background-color: #fff;
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
packages/design/src/theme/colorPicker.scss
Normal file
4
packages/design/src/theme/colorPicker.scss
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.tmagic-design-color-picker-input {
|
||||||
|
margin-left: 8px;
|
||||||
|
width: calc(var(--el-input-width, 100%) - 40px);
|
||||||
|
}
|
||||||
@ -1 +1,3 @@
|
|||||||
@use "./popover.scss";
|
@use "./popover.scss";
|
||||||
|
@use "./card.scss";
|
||||||
|
@use "./colorPicker.scss";
|
||||||
|
|||||||
38
packages/design/src/theme/themes/magic-admin/index.scss
Normal file
38
packages/design/src/theme/themes/magic-admin/index.scss
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@use "../../index.scss";
|
||||||
|
|
||||||
|
.m-form.m-form--magic-admin {
|
||||||
|
.el-collapse-item__header {
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 24px;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tmagic-design-form-item {
|
||||||
|
&.has-extra-tips {
|
||||||
|
> .el-form-item__content {
|
||||||
|
gap: 10px;
|
||||||
|
> .tmagic-design-alert {
|
||||||
|
border-radius: 2px;
|
||||||
|
width: fit-content;
|
||||||
|
padding: 0 8px;
|
||||||
|
color: var(--el-color-primary, #0056ea);
|
||||||
|
background-color: var(--el-color-primary-light-9, rgb(236, 245, 255));
|
||||||
|
.el-icon.el-alert__icon {
|
||||||
|
fill: var(--el-color-primary, #0056ea);
|
||||||
|
color: var(--el-color-primary, #0056ea);
|
||||||
|
}
|
||||||
|
.el-alert__title {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,6 +24,17 @@ export interface BadgeProps {
|
|||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AlertProps {
|
||||||
|
title?: string;
|
||||||
|
/** ElAlert 实际支持的 type;与 ButtonProps.type 不同,没有 `primary` / `danger` */
|
||||||
|
type?: 'success' | 'warning' | 'info' | 'error' | 'primary';
|
||||||
|
description?: string;
|
||||||
|
closable?: boolean;
|
||||||
|
center?: boolean;
|
||||||
|
closeText?: string;
|
||||||
|
showIcon?: boolean;
|
||||||
|
effect?: 'light' | 'dark';
|
||||||
|
}
|
||||||
export interface ButtonProps {
|
export interface ButtonProps {
|
||||||
type?: string;
|
type?: string;
|
||||||
size?: FieldSize;
|
size?: FieldSize;
|
||||||
@ -39,6 +50,11 @@ export interface CardProps {
|
|||||||
bodyStyle?: Record<string, any>;
|
bodyStyle?: Record<string, any>;
|
||||||
shadow?: string;
|
shadow?: string;
|
||||||
header?: string;
|
header?: string;
|
||||||
|
/**
|
||||||
|
* 铺平模式:去除卡片自身的视觉(背景 / 阴影 / 边框 / 圆角 / header 分隔线 / header 与 body 内边距)。
|
||||||
|
* 用于已有外层容器或者使用方希望「无卡片感」的场景,TMagicCard 的折叠等 prop 仍可正常使用。
|
||||||
|
*/
|
||||||
|
flat?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CascaderProps {
|
export interface CascaderProps {
|
||||||
@ -97,6 +113,7 @@ export interface ColorPickerProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
showAlpha?: boolean;
|
showAlpha?: boolean;
|
||||||
size?: FieldSize;
|
size?: FieldSize;
|
||||||
|
flat?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatePickerProps {
|
export interface DatePickerProps {
|
||||||
@ -185,7 +202,9 @@ export interface FormItemProps {
|
|||||||
labelWidth?: string | number;
|
labelWidth?: string | number;
|
||||||
rules?: any;
|
rules?: any;
|
||||||
extra?: string;
|
extra?: string;
|
||||||
|
extraTips?: string;
|
||||||
labelPosition?: 'top' | 'left' | 'right';
|
labelPosition?: 'top' | 'left' | 'right';
|
||||||
|
text?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InputProps {
|
export interface InputProps {
|
||||||
@ -464,6 +483,11 @@ export interface Components {
|
|||||||
props: (props: BadgeProps) => BadgeProps;
|
props: (props: BadgeProps) => BadgeProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
alert: {
|
||||||
|
component: DefineComponent<AlertProps, {}, any> | string;
|
||||||
|
props: (props: AlertProps) => AlertProps;
|
||||||
|
};
|
||||||
|
|
||||||
autocomplete: {
|
autocomplete: {
|
||||||
component: DefineComponent<AutocompleteProps, {}, any> | string;
|
component: DefineComponent<AutocompleteProps, {}, any> | string;
|
||||||
props: (props: AutocompleteProps) => AutocompleteProps;
|
props: (props: AutocompleteProps) => AutocompleteProps;
|
||||||
@ -726,6 +750,7 @@ export interface Components {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DesignPluginOptions {
|
export interface DesignPluginOptions {
|
||||||
|
flat?: boolean;
|
||||||
adapterType?: string;
|
adapterType?: string;
|
||||||
message?: TMagicMessage;
|
message?: TMagicMessage;
|
||||||
messageBox?: TMagicMessageBox;
|
messageBox?: TMagicMessageBox;
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
:page-bar-sort-options="pageBarSortOptions"
|
:page-bar-sort-options="pageBarSortOptions"
|
||||||
:page-filter-function="pageFilterFunction"
|
:page-filter-function="pageFilterFunction"
|
||||||
:hide-sidebar="hideSidebar"
|
:hide-sidebar="hideSidebar"
|
||||||
|
:theme="theme"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<slot name="header"></slot>
|
<slot name="header"></slot>
|
||||||
@ -135,9 +136,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
import { provide, ref } from 'vue';
|
import { computed, provide, ref } from 'vue';
|
||||||
|
|
||||||
import type { MApp } from '@tmagic/core';
|
import type { MApp } from '@tmagic/core';
|
||||||
|
import { M_THEME_KEY } from '@tmagic/design';
|
||||||
|
|
||||||
import Framework from './layouts/Framework.vue';
|
import Framework from './layouts/Framework.vue';
|
||||||
import TMagicNavMenu from './layouts/NavMenu.vue';
|
import TMagicNavMenu from './layouts/NavMenu.vue';
|
||||||
@ -241,6 +243,15 @@ provide(ENABLE_PROPS_FORM_VALIDATE, props.enablePropsFormValidate ?? false);
|
|||||||
* 与 PropsPanel 通过 `:extend-state` 显式传入的方式保持等价。
|
* 与 PropsPanel 通过 `:extend-state` 显式传入的方式保持等价。
|
||||||
*/
|
*/
|
||||||
provide('extendFormState', props.extendFormState);
|
provide('extendFormState', props.extendFormState);
|
||||||
|
|
||||||
|
// 用 computed 包一层再 provide,否则传下去的是 provide 那一刻的值快照,
|
||||||
|
// props.isLargeStageContainer 后续变化不会同步到子孙(如 @tmagic/design/ColorPicker)。
|
||||||
|
// 子孙侧 `inject<ComputedRef<boolean>>('isLargeStageContainer', computed(() => false))`
|
||||||
|
// 用 `.value` 或模板自动解包读取即可拿到最新值。
|
||||||
|
provide(
|
||||||
|
'isLargeStageContainer',
|
||||||
|
computed(() => props.isLargeStageContainer),
|
||||||
|
);
|
||||||
/**
|
/**
|
||||||
* 提供 PropsPanel 主属性表单的 formState getter,供历史差异弹窗复用,
|
* 提供 PropsPanel 主属性表单的 formState getter,供历史差异弹窗复用,
|
||||||
* 让 CompareForm 与 PropsPanel 的 filterFunction 上下文保持一致。
|
* 让 CompareForm 与 PropsPanel 的 filterFunction 上下文保持一致。
|
||||||
@ -256,6 +267,16 @@ provide('historyListExtraTabs', props.historyListExtraTabs);
|
|||||||
|
|
||||||
provide<EventBus>('eventBus', new EventEmitter());
|
provide<EventBus>('eventBus', new EventEmitter());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把当前主题以响应式 ref 形式 provide 给后代,供包含 `Teleport` 的组件
|
||||||
|
* (如 `TMagicPopover` / `FloatingBox` / `ContentMenu`)在传送目标上挂 `m-theme--<theme>`
|
||||||
|
* 类,让主题级 CSS 变量在 portal 节点上也能命中。详见 `@tmagic/design/theme.ts`。
|
||||||
|
*/
|
||||||
|
provide(
|
||||||
|
M_THEME_KEY,
|
||||||
|
computed(() => props.theme ?? ''),
|
||||||
|
);
|
||||||
|
|
||||||
const propsPanelMountedHandler = (e: InstanceType<typeof FormPanel>) => {
|
const propsPanelMountedHandler = (e: InstanceType<typeof FormPanel>) => {
|
||||||
propsPanelRef.value = e;
|
propsPanelRef.value = e;
|
||||||
emit('props-panel-mounted', e);
|
emit('props-panel-mounted', e);
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div
|
<div
|
||||||
v-show="visible"
|
v-show="visible"
|
||||||
class="magic-editor-content-menu"
|
|
||||||
ref="menu"
|
ref="menu"
|
||||||
|
:class="['magic-editor-content-menu', themeClass]"
|
||||||
:style="menuStyle"
|
:style="menuStyle"
|
||||||
@mouseenter="mouseenterHandler()"
|
@mouseenter="mouseenterHandler()"
|
||||||
@contextmenu.prevent
|
@contextmenu.prevent
|
||||||
@ -39,7 +39,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, type Ref, ref, useTemplateRef } from 'vue';
|
import { computed, nextTick, onBeforeUnmount, onMounted, type Ref, ref, useTemplateRef } from 'vue';
|
||||||
|
|
||||||
import { useZIndex } from '@tmagic/design';
|
import { useThemeClass, useZIndex } from '@tmagic/design';
|
||||||
|
|
||||||
import { MenuButton, MenuComponent } from '@editor/type';
|
import { MenuButton, MenuComponent } from '@editor/type';
|
||||||
|
|
||||||
@ -73,6 +73,14 @@ const menuEl = useTemplateRef<HTMLDivElement>('menu');
|
|||||||
const buttonRefs = useTemplateRef<InstanceType<typeof ToolButton>[]>('buttons');
|
const buttonRefs = useTemplateRef<InstanceType<typeof ToolButton>[]>('buttons');
|
||||||
const subMenuRef = useTemplateRef<any>('subMenu');
|
const subMenuRef = useTemplateRef<any>('subMenu');
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题修饰类(来自最近的 `<MEditor>` / `<MForm>` 祖先 provide)。
|
||||||
|
* - 顶层 `ContentMenu` 渲染在编辑器子树中,加上去是冗余但无害;
|
||||||
|
* - 子菜单(递归 `<content-menu :is-sub-menu="true">` 被 `<teleport to="body">` 送到 body)
|
||||||
|
* 不在编辑器子树中,必须显式挂上主题类,主题级 CSS 变量才能命中。
|
||||||
|
*/
|
||||||
|
const themeClass = useThemeClass();
|
||||||
const subMenuData: Ref<(MenuButton | MenuComponent)[]> = ref<(MenuButton | MenuComponent)[]>([]);
|
const subMenuData: Ref<(MenuButton | MenuComponent)[]> = ref<(MenuButton | MenuComponent)[]>([]);
|
||||||
const zIndex = useZIndex();
|
const zIndex = useZIndex();
|
||||||
const curZIndex = ref<number>(0);
|
const curZIndex = ref<number>(0);
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<Teleport to="body" v-if="visible">
|
<Teleport to="body" v-if="visible">
|
||||||
<div
|
<div
|
||||||
ref="target"
|
ref="target"
|
||||||
class="m-editor-float-box"
|
:class="['m-editor-float-box', themeClass]"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
:style="{ ...style, zIndex: curZIndex }"
|
:style="{ ...style, zIndex: curZIndex }"
|
||||||
@mousedown="nextZIndex"
|
@mousedown="nextZIndex"
|
||||||
@ -27,7 +27,7 @@ import { computed, type CSSProperties, nextTick, onBeforeUnmount, provide, ref,
|
|||||||
import { Close } from '@element-plus/icons-vue';
|
import { Close } from '@element-plus/icons-vue';
|
||||||
import VanillaMoveable from 'moveable';
|
import VanillaMoveable from 'moveable';
|
||||||
|
|
||||||
import { TMagicButton, useZIndex } from '@tmagic/design';
|
import { TMagicButton, useThemeClass, useZIndex } from '@tmagic/design';
|
||||||
|
|
||||||
import MIcon from '@editor/components/Icon.vue';
|
import MIcon from '@editor/components/Icon.vue';
|
||||||
|
|
||||||
@ -62,6 +62,12 @@ const props = withDefaults(
|
|||||||
const targetEl = useTemplateRef<HTMLDivElement>('target');
|
const targetEl = useTemplateRef<HTMLDivElement>('target');
|
||||||
const titleEl = useTemplateRef<HTMLDivElement>('title');
|
const titleEl = useTemplateRef<HTMLDivElement>('title');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题修饰类(来自最近的 `<MEditor>` / `<MForm>` 祖先 provide)。
|
||||||
|
* 挂在 `Teleport` 出去的浮动面板根节点上,让主题级 CSS 变量在 portal 节点上也能命中。
|
||||||
|
*/
|
||||||
|
const themeClass = useThemeClass();
|
||||||
|
|
||||||
const zIndex = useZIndex();
|
const zIndex = useZIndex();
|
||||||
const curZIndex = ref<number>(0);
|
const curZIndex = ref<number>(0);
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,10 @@ import type {
|
|||||||
export const ENABLE_PROPS_FORM_VALIDATE: InjectionKey<boolean> = Symbol('enablePropsFormValidate');
|
export const ENABLE_PROPS_FORM_VALIDATE: InjectionKey<boolean> = Symbol('enablePropsFormValidate');
|
||||||
|
|
||||||
export interface EditorProps {
|
export interface EditorProps {
|
||||||
|
/** 是否是大屏模拟器容器,大屏容器时,左侧属性面板会扩展为两列(正常3列),颜色表单不扩展两列等 */
|
||||||
|
isLargeStageContainer?: boolean;
|
||||||
|
|
||||||
|
theme?: string;
|
||||||
/** 页面初始值 */
|
/** 页面初始值 */
|
||||||
modelValue?: MApp;
|
modelValue?: MApp;
|
||||||
/** 左侧面板中的组件类型列表 */
|
/** 左侧面板中的组件类型列表 */
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="m-fields-code-select" :class="config.className">
|
<div class="m-fields-code-select" :class="config.className">
|
||||||
<TMagicCard>
|
<TMagicCard :flat="config.flat">
|
||||||
<MContainer
|
<MContainer
|
||||||
:config="codeConfig"
|
:config="codeConfig"
|
||||||
:size="size"
|
:size="size"
|
||||||
|
class="code-select-content"
|
||||||
:prop="prop"
|
:prop="prop"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:is-compare="isCompareMode"
|
:is-compare="isCompareMode"
|
||||||
@ -12,16 +13,20 @@
|
|||||||
@change="changeHandler"
|
@change="changeHandler"
|
||||||
>
|
>
|
||||||
</MContainer>
|
</MContainer>
|
||||||
|
<TMagicButton class="create-button fullWidth" :icon="Plus" :size="size" :disabled="disabled" @click="newHandler()"
|
||||||
|
>添加{{ config.text }}</TMagicButton
|
||||||
|
>
|
||||||
</TMagicCard>
|
</TMagicCard>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch } from 'vue';
|
import { computed, watch } from 'vue';
|
||||||
|
import { Plus } from '@element-plus/icons-vue';
|
||||||
import { isEmpty } from 'lodash-es';
|
import { isEmpty } from 'lodash-es';
|
||||||
|
|
||||||
import { HookCodeType, HookType } from '@tmagic/core';
|
import { HookCodeType, HookType } from '@tmagic/core';
|
||||||
import { TMagicCard } from '@tmagic/design';
|
import { TMagicButton, TMagicCard } from '@tmagic/design';
|
||||||
import type { CodeSelectConfig, ContainerChangeEventData, FieldProps, GroupListConfig } from '@tmagic/form';
|
import type { CodeSelectConfig, ContainerChangeEventData, FieldProps, GroupListConfig } from '@tmagic/form';
|
||||||
import { MContainer } from '@tmagic/form';
|
import { MContainer } from '@tmagic/form';
|
||||||
|
|
||||||
@ -53,12 +58,24 @@ const props = withDefaults(defineProps<FieldProps<CodeSelectConfig>>(), {});
|
|||||||
* 仅当存在历史值时才启用对比,避免 lastValues 缺失时退化为「全部新增」的空对比。
|
* 仅当存在历史值时才启用对比,避免 lastValues 缺失时退化为「全部新增」的空对比。
|
||||||
*/
|
*/
|
||||||
const isCompareMode = computed(() => Boolean(props.isCompare && props.lastValues));
|
const isCompareMode = computed(() => Boolean(props.isCompare && props.lastValues));
|
||||||
|
const newHandler = () => {
|
||||||
|
const defaultCode = {
|
||||||
|
codeType: HookCodeType.CODE,
|
||||||
|
codeId: '',
|
||||||
|
};
|
||||||
|
const name = props.config.name || '';
|
||||||
|
const hookData = props.model[name]?.hookData || [];
|
||||||
|
emit('change', defaultCode, {
|
||||||
|
modifyKey: `hookData.${hookData.length}`,
|
||||||
|
});
|
||||||
|
};
|
||||||
const codeConfig = computed<GroupListConfig>(() => ({
|
const codeConfig = computed<GroupListConfig>(() => ({
|
||||||
type: 'group-list',
|
type: 'group-list',
|
||||||
name: 'hookData',
|
name: 'hookData',
|
||||||
enableToggleMode: false,
|
enableToggleMode: false,
|
||||||
expandAll: true,
|
expandAll: true,
|
||||||
|
flat: true,
|
||||||
|
addable: () => false,
|
||||||
title: (mForm, { model, index }: any) => {
|
title: (mForm, { model, index }: any) => {
|
||||||
if (model.codeType === HookCodeType.DATA_SOURCE_METHOD) {
|
if (model.codeType === HookCodeType.DATA_SOURCE_METHOD) {
|
||||||
if (Array.isArray(model.codeId)) {
|
if (Array.isArray(model.codeId)) {
|
||||||
@ -81,49 +98,44 @@ const codeConfig = computed<GroupListConfig>(() => ({
|
|||||||
|
|
||||||
return model.codeId || index;
|
return model.codeId || index;
|
||||||
},
|
},
|
||||||
|
titlePrefix: props.config.name === undefined ? undefined : String(props.config.name),
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: 'row',
|
text: '代码类型',
|
||||||
items: [
|
type: 'select',
|
||||||
{
|
name: 'codeType',
|
||||||
type: 'select',
|
labelPosition: 'right',
|
||||||
name: 'codeType',
|
rules: [{ typeMatch: true, trigger: 'change' }],
|
||||||
span: 6,
|
options: [
|
||||||
options: [
|
{ value: HookCodeType.CODE, text: '代码块' },
|
||||||
{ value: HookCodeType.CODE, text: '代码块' },
|
{ value: HookCodeType.DATA_SOURCE_METHOD, text: '数据源方法' },
|
||||||
{ value: HookCodeType.DATA_SOURCE_METHOD, text: '数据源方法' },
|
|
||||||
],
|
|
||||||
rules: [{ typeMatch: true, trigger: 'change' }],
|
|
||||||
defaultValue: HookCodeType.CODE,
|
|
||||||
onChange: (_mForm, v: HookCodeType, { setModel }) => {
|
|
||||||
if (v === HookCodeType.DATA_SOURCE_METHOD) {
|
|
||||||
setModel('codeId', []);
|
|
||||||
} else {
|
|
||||||
setModel('codeId', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return v;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'code-select-col',
|
|
||||||
name: 'codeId',
|
|
||||||
span: 18,
|
|
||||||
labelWidth: 0,
|
|
||||||
display: (_mForm, { model }) => model.codeType !== HookCodeType.DATA_SOURCE_METHOD,
|
|
||||||
notEditable: () => !codeBlockService.getEditStatus(),
|
|
||||||
rules: [{ typeMatch: true, trigger: 'change' }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'data-source-method-select',
|
|
||||||
name: 'codeId',
|
|
||||||
span: 18,
|
|
||||||
labelWidth: 0,
|
|
||||||
display: (_mForm, { model }) => model.codeType === HookCodeType.DATA_SOURCE_METHOD,
|
|
||||||
notEditable: () => !dataSourceService.get('editable'),
|
|
||||||
rules: [{ typeMatch: true, trigger: 'change' }],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
defaultValue: HookCodeType.CODE,
|
||||||
|
onChange: (_mForm, v: HookCodeType, { setModel }) => {
|
||||||
|
if (v === HookCodeType.DATA_SOURCE_METHOD) {
|
||||||
|
setModel('codeId', []);
|
||||||
|
} else {
|
||||||
|
setModel('codeId', '');
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'code-select-col',
|
||||||
|
name: 'codeId',
|
||||||
|
text: '代码块',
|
||||||
|
rules: [{ typeMatch: true, trigger: 'change' }],
|
||||||
|
|
||||||
|
display: (_mForm, { model }) => model.codeType !== HookCodeType.DATA_SOURCE_METHOD,
|
||||||
|
notEditable: () => !codeBlockService.getEditStatus(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'data-source-method-select',
|
||||||
|
name: 'codeId',
|
||||||
|
text: '数据源字段',
|
||||||
|
rules: [{ typeMatch: true, trigger: 'change' }],
|
||||||
|
display: (_mForm, { model }) => model.codeType === HookCodeType.DATA_SOURCE_METHOD,
|
||||||
|
notEditable: () => !dataSourceService.get('editable'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -147,6 +147,7 @@ const selectConfig: SelectConfig = {
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
name: props.name,
|
name: props.name,
|
||||||
disabled: props.disabled,
|
disabled: props.disabled,
|
||||||
|
text: props.config.text,
|
||||||
options: () => {
|
options: () => {
|
||||||
if (codeDsl.value) {
|
if (codeDsl.value) {
|
||||||
return map(codeDsl.value, (value, key) => ({
|
return map(codeDsl.value, (value, key) => ({
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="m-fields-data-source-field-select">
|
<div
|
||||||
|
class="m-fields-data-source-field-select"
|
||||||
|
:class="{
|
||||||
|
[`data-source-field-${type}`]: !isSelectValid,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
v-if="!disabledDataSource && (showDataSourceFieldSelect || !config.fieldConfig)"
|
v-if="isSelectValid"
|
||||||
:model-value="model[name]"
|
:model-value="model[name]"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:size="size"
|
:size="size"
|
||||||
@ -37,15 +42,14 @@
|
|||||||
:size="size"
|
:size="size"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@click="onToggleDataSourceFieldSelectHandler"
|
@click="onToggleDataSourceFieldSelectHandler"
|
||||||
><MIcon :icon="Coin"></MIcon
|
><MIcon :icon="dataSourceIcon"></MIcon>
|
||||||
></TMagicButton>
|
</TMagicButton>
|
||||||
</TMagicTooltip>
|
</TMagicTooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, inject, ref, resolveComponent, watch } from 'vue';
|
import { computed, inject, ref, resolveComponent, watch } from 'vue';
|
||||||
import { Coin } from '@element-plus/icons-vue';
|
|
||||||
|
|
||||||
import { DataSchema } from '@tmagic/core';
|
import { DataSchema } from '@tmagic/core';
|
||||||
import { TMagicButton, tMagicMessage, TMagicTooltip } from '@tmagic/design';
|
import { TMagicButton, tMagicMessage, TMagicTooltip } from '@tmagic/design';
|
||||||
@ -61,6 +65,8 @@ import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, removeDataSourceFieldPrefix } f
|
|||||||
import MIcon from '@editor/components/Icon.vue';
|
import MIcon from '@editor/components/Icon.vue';
|
||||||
import { useServices } from '@editor/hooks/use-services';
|
import { useServices } from '@editor/hooks/use-services';
|
||||||
|
|
||||||
|
import dataSourceIcon from '../../icons/DatasourceIcon.vue';
|
||||||
|
|
||||||
import FieldSelect from './FieldSelect.vue';
|
import FieldSelect from './FieldSelect.vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@ -75,6 +81,9 @@ const props = withDefaults(defineProps<FieldProps<DataSourceFieldSelectConfig>>(
|
|||||||
|
|
||||||
const showDataSourceFieldSelect = ref(false);
|
const showDataSourceFieldSelect = ref(false);
|
||||||
|
|
||||||
|
const isSelectValid = computed(
|
||||||
|
() => !disabledDataSource.value && (showDataSourceFieldSelect.value || !props.config.fieldConfig),
|
||||||
|
);
|
||||||
watch(
|
watch(
|
||||||
() => props.model[props.name],
|
() => props.model[props.name],
|
||||||
(value) => {
|
(value) => {
|
||||||
|
|||||||
@ -68,12 +68,15 @@ const config = computed<GroupListConfig>(() => ({
|
|||||||
titlePrefix: props.config.titlePrefix,
|
titlePrefix: props.config.titlePrefix,
|
||||||
expandAll: true,
|
expandAll: true,
|
||||||
enableToggleMode: false,
|
enableToggleMode: false,
|
||||||
|
flat: props.config.flat,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: 'table',
|
type: 'table',
|
||||||
name: 'cond',
|
name: 'cond',
|
||||||
operateColWidth: 80,
|
operateColWidth: props.config.operateColWidth,
|
||||||
enableToggleMode: false,
|
enableToggleMode: false,
|
||||||
|
fixed: props.config.fixed,
|
||||||
|
flat: props.config.flat,
|
||||||
items: [
|
items: [
|
||||||
parentFields.value.length
|
parentFields.value.length
|
||||||
? {
|
? {
|
||||||
|
|||||||
@ -12,16 +12,21 @@
|
|||||||
@change="onChangeHandler"
|
@change="onChangeHandler"
|
||||||
></MTable>
|
></MTable>
|
||||||
|
|
||||||
<div v-else class="fullWidth">
|
<div v-else class="fullWidth event-select-container">
|
||||||
<TMagicButton
|
<div class="event-select-header">
|
||||||
v-if="!isCompareMode"
|
<div class="event-select-title">事件配置</div>
|
||||||
class="create-button"
|
<TMagicButton
|
||||||
type="primary"
|
v-if="!isCompareMode && displayList.length > 0"
|
||||||
:size="size"
|
class="create-button"
|
||||||
:disabled="disabled"
|
text
|
||||||
@click="addEvent()"
|
type="primary"
|
||||||
>添加事件</TMagicButton
|
:icon="Plus"
|
||||||
>
|
:size="size"
|
||||||
|
:disabled="disabled"
|
||||||
|
@click="addEvent()"
|
||||||
|
>添加事件</TMagicButton
|
||||||
|
>
|
||||||
|
</div>
|
||||||
<MPanel
|
<MPanel
|
||||||
v-for="entry in displayList"
|
v-for="entry in displayList"
|
||||||
:key="entry.index"
|
:key="entry.index"
|
||||||
@ -32,32 +37,45 @@
|
|||||||
:model="entry.cardItem"
|
:model="entry.cardItem"
|
||||||
:last-values="entry.lastCardItem"
|
:last-values="entry.lastCardItem"
|
||||||
:is-compare="isCompareMode"
|
:is-compare="isCompareMode"
|
||||||
|
:hide-expand="true"
|
||||||
:label-width="config.labelWidth || '100px'"
|
:label-width="config.labelWidth || '100px'"
|
||||||
@change="onChangeHandler"
|
@change="onChangeHandler"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<MFormContainer
|
<div class="event-item-header">
|
||||||
class="fullWidth"
|
<div class="event-item-title">事件{{ Number(entry.index) + 1 }}</div>
|
||||||
:config="eventNameConfig"
|
<MFormContainer
|
||||||
:model="entry.cardItem"
|
class="fullWidth"
|
||||||
:last-values="entry.lastCardItem"
|
:config="eventNameConfig"
|
||||||
:is-compare="isCompareMode"
|
:model="entry.cardItem"
|
||||||
:disabled="disabled"
|
:last-values="entry.lastCardItem"
|
||||||
:size="size"
|
:is-compare="isCompareMode"
|
||||||
:prop="`${prop}.${entry.index}`"
|
:disabled="disabled"
|
||||||
@change="eventNameChangeHandler"
|
:size="size"
|
||||||
></MFormContainer>
|
:prop="`${prop}.${entry.index}`"
|
||||||
<TMagicButton
|
@change="eventNameChangeHandler"
|
||||||
v-if="!isCompareMode"
|
></MFormContainer>
|
||||||
style="color: #f56c6c"
|
<TMagicButton
|
||||||
link
|
class="event-item-delete-button"
|
||||||
:icon="Delete"
|
v-if="!isCompareMode"
|
||||||
:disabled="disabled"
|
link
|
||||||
:size="size"
|
:icon="Delete"
|
||||||
@click="removeEvent(Number(entry.index))"
|
:disabled="disabled"
|
||||||
></TMagicButton>
|
:size="size"
|
||||||
|
@click="removeEvent(Number(entry.index))"
|
||||||
|
></TMagicButton>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MPanel>
|
</MPanel>
|
||||||
|
|
||||||
|
<TMagicButton
|
||||||
|
v-if="!isCompareMode"
|
||||||
|
class="create-button fullWidth"
|
||||||
|
:icon="Plus"
|
||||||
|
:disabled="disabled"
|
||||||
|
@click="addEvent()"
|
||||||
|
>添加事件</TMagicButton
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -65,6 +83,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { Delete } from '@element-plus/icons-vue';
|
import { Delete } from '@element-plus/icons-vue';
|
||||||
|
import { Plus } from '@element-plus/icons-vue';
|
||||||
import { has } from 'lodash-es';
|
import { has } from 'lodash-es';
|
||||||
|
|
||||||
import { ActionType } from '@tmagic/core';
|
import { ActionType } from '@tmagic/core';
|
||||||
@ -108,7 +127,7 @@ const { editorService, dataSourceService, eventsService, codeBlockService, props
|
|||||||
const eventNameConfig = computed(() => {
|
const eventNameConfig = computed(() => {
|
||||||
const defaultEventNameConfig = {
|
const defaultEventNameConfig = {
|
||||||
name: 'name',
|
name: 'name',
|
||||||
text: '事件',
|
text: '事件类型',
|
||||||
type: (mForm: FormState | undefined, { formValue }: any) => {
|
type: (mForm: FormState | undefined, { formValue }: any) => {
|
||||||
if (
|
if (
|
||||||
props.config.src !== 'component' ||
|
props.config.src !== 'component' ||
|
||||||
@ -118,7 +137,7 @@ const eventNameConfig = computed(() => {
|
|||||||
}
|
}
|
||||||
return 'select';
|
return 'select';
|
||||||
},
|
},
|
||||||
labelWidth: '40px',
|
labelWidth: '70px',
|
||||||
checkStrictly: () => props.config.src !== 'component',
|
checkStrictly: () => props.config.src !== 'component',
|
||||||
valueSeparator: '.',
|
valueSeparator: '.',
|
||||||
options: (_mForm: FormState, { formValue }: any) => getEventNameOptions(props.config.src, formValue),
|
options: (_mForm: FormState, { formValue }: any) => getEventNameOptions(props.config.src, formValue),
|
||||||
@ -178,6 +197,7 @@ const actionTypeConfig = computed(() => {
|
|||||||
name: 'actionType',
|
name: 'actionType',
|
||||||
text: '联动类型',
|
text: '联动类型',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
|
labelPosition: 'left',
|
||||||
defaultValue: ActionType.COMP,
|
defaultValue: ActionType.COMP,
|
||||||
options: actionTypeOptions.value,
|
options: actionTypeOptions.value,
|
||||||
rules: [
|
rules: [
|
||||||
@ -200,6 +220,7 @@ const targetCompConfig = computed(() => {
|
|||||||
name: 'to',
|
name: 'to',
|
||||||
text: '联动组件',
|
text: '联动组件',
|
||||||
type: 'ui-select',
|
type: 'ui-select',
|
||||||
|
labelPosition: 'left',
|
||||||
display: (_mForm, { model }) => model.actionType === ActionType.COMP,
|
display: (_mForm, { model }) => model.actionType === ActionType.COMP,
|
||||||
onChange: (_MForm, _v, { setModel }) => {
|
onChange: (_MForm, _v, { setModel }) => {
|
||||||
setModel('method', '');
|
setModel('method', '');
|
||||||
@ -219,6 +240,7 @@ const compActionConfig = computed(() => {
|
|||||||
const defaultCompActionConfig: DynamicTypeConfig = {
|
const defaultCompActionConfig: DynamicTypeConfig = {
|
||||||
name: 'method',
|
name: 'method',
|
||||||
text: '动作',
|
text: '动作',
|
||||||
|
labelPosition: 'left',
|
||||||
type: (mForm: FormState | undefined, { model }: any) => {
|
type: (mForm: FormState | undefined, { model }: any) => {
|
||||||
const to = editorService.getNodeById(model.to);
|
const to = editorService.getNodeById(model.to);
|
||||||
|
|
||||||
@ -317,6 +339,8 @@ const actionsConfig = computed(
|
|||||||
() =>
|
() =>
|
||||||
defineFormItem({
|
defineFormItem({
|
||||||
type: 'panel',
|
type: 'panel',
|
||||||
|
labelPosition: 'left',
|
||||||
|
flat: true,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: 'group-list',
|
type: 'group-list',
|
||||||
@ -324,6 +348,8 @@ const actionsConfig = computed(
|
|||||||
expandAll: true,
|
expandAll: true,
|
||||||
enableToggleMode: false,
|
enableToggleMode: false,
|
||||||
titlePrefix: '动作',
|
titlePrefix: '动作',
|
||||||
|
labelPosition: 'left',
|
||||||
|
flat: true,
|
||||||
items: [
|
items: [
|
||||||
actionTypeConfig.value,
|
actionTypeConfig.value,
|
||||||
targetCompConfig.value,
|
targetCompConfig.value,
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<TMagicCollapse class="m-fields-style-setter" v-model="collapseValue">
|
<TMagicCollapse class="m-fields-style-setter" v-model="collapseValue">
|
||||||
<template v-for="(item, index) in list" :key="index">
|
<template v-for="(item, index) in list" :key="index">
|
||||||
<TMagicCollapseItem :name="`${index}`">
|
<TMagicCollapseItem :name="`${index}`">
|
||||||
<template #title><MIcon :icon="Grid"></MIcon>{{ item.title }}</template>
|
<template #title>{{ item.title }}</template>
|
||||||
<component
|
<component
|
||||||
v-if="item.component"
|
v-if="item.component"
|
||||||
:is="item.component"
|
:is="item.component"
|
||||||
@ -22,14 +22,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { shallowRef } from 'vue';
|
import { shallowRef } from 'vue';
|
||||||
import { Grid } from '@element-plus/icons-vue';
|
|
||||||
|
|
||||||
import { TMagicCollapse, TMagicCollapseItem } from '@tmagic/design';
|
import { TMagicCollapse, TMagicCollapseItem } from '@tmagic/design';
|
||||||
import type { ContainerChangeEventData, FieldProps } from '@tmagic/form';
|
import type { ContainerChangeEventData, FieldProps } from '@tmagic/form';
|
||||||
import type { StyleSchema } from '@tmagic/schema';
|
import type { StyleSchema } from '@tmagic/schema';
|
||||||
|
|
||||||
import MIcon from '@editor/components/Icon.vue';
|
|
||||||
|
|
||||||
import { Background, Border, Font, Layout, Position, Transform } from './pro/';
|
import { Background, Border, Font, Layout, Position, Transform } from './pro/';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
@click="selectDirection('Left')"
|
@click="selectDirection('Left')"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
class="border-icon"
|
class="border-icon center"
|
||||||
:class="{ active: direction === '', configured: isConfigured('') }"
|
:class="{ active: direction === '', configured: isConfigured('') }"
|
||||||
@click="selectDirection()"
|
@click="selectDirection()"
|
||||||
></div>
|
></div>
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M12 4C12.5523 4 13 4.44772 13 5V11C13 11.5523 12.5523 12 12 12H4C3.44772 12 3 11.5523 3 11V5C3 4.44772 3.44772 4 4 4H12ZM4.5 5C4.22386 5 4 5.22386 4 5.5V10.5C4 10.7761 4.22386 11 4.5 11H11.5C11.7761 11 12 10.7761 12 10.5V5.5C12 5.22386 11.7761 5 11.5 5H4.5Z"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="13.75"
|
||||||
|
y="7.75"
|
||||||
|
width="0.5"
|
||||||
|
height="11.5"
|
||||||
|
rx="0.25"
|
||||||
|
transform="rotate(90 13.75 7.75)"
|
||||||
|
stroke="black"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
style="stroke: black; stroke-opacity: 0.3"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M12 3C12.5523 3 13 3.44772 13 4V10C13 10.5523 12.5523 11 12 11H4C3.44772 11 3 10.5523 3 10V4C3 3.44772 3.44772 3 4 3H12ZM4.5 4C4.22386 4 4 4.22386 4 4.5V9.5C4 9.77614 4.22386 10 4.5 10H11.5C11.7761 10 12 9.77614 12 9.5V4.5C12 4.22386 11.7761 4 11.5 4H4.5Z"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="13.75"
|
||||||
|
y="12.25"
|
||||||
|
width="0.5"
|
||||||
|
height="11.5"
|
||||||
|
rx="0.25"
|
||||||
|
transform="rotate(90 13.75 12.25)"
|
||||||
|
stroke="black"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
style="stroke: black; stroke-opacity: 0.3"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M12 5C12.5523 5 13 5.44772 13 6V12C13 12.5523 12.5523 13 12 13H4C3.44772 13 3 12.5523 3 12V6C3 5.44772 3.44772 5 4 5H12ZM4.5 6C4.22386 6 4 6.22386 4 6.5V11.5C4 11.7761 4.22386 12 4.5 12H11.5C11.7761 12 12 11.7761 12 11.5V6.5C12 6.22386 11.7761 6 11.5 6H4.5Z"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="13.75"
|
||||||
|
y="3.25"
|
||||||
|
width="0.5"
|
||||||
|
height="11.5"
|
||||||
|
rx="0.25"
|
||||||
|
transform="rotate(90 13.75 3.25)"
|
||||||
|
stroke="white"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
style="stroke: white; stroke-opacity: 0.3"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M12 8C12.5523 8 13 8.44772 13 9V12C13 12.5523 12.5523 13 12 13H4C3.44772 13 3 12.5523 3 12V9C3 8.44772 3.44772 8 4 8H12ZM4.5 9C4.22386 9 4 9.22386 4 9.5V11.5C4 11.7761 4.22386 12 4.5 12H11.5C11.7761 12 12 11.7761 12 11.5V9.5C12 9.22386 11.7761 9 11.5 9H4.5Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M12 2C12.5523 2 13 2.44772 13 3V6C13 6.55228 12.5523 7 12 7H4C3.44772 7 3 6.55228 3 6V3C3 2.44772 3.44772 2 4 2H12ZM4.5 3C4.22386 3 4 3.22386 4 3.5V5.5C4 5.77614 4.22386 6 4.5 6H11.5C11.7761 6 12 5.77614 12 5.5V3.5C12 3.22386 11.7761 3 11.5 3H4.5Z"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="13.75"
|
||||||
|
y="10.25"
|
||||||
|
width="0.5"
|
||||||
|
height="11.5"
|
||||||
|
rx="0.25"
|
||||||
|
transform="rotate(90 13.75 10.25)"
|
||||||
|
stroke="black"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
style="stroke: black; stroke-opacity: 0.3"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="13.75"
|
||||||
|
y="4.25"
|
||||||
|
width="0.5"
|
||||||
|
height="11.5"
|
||||||
|
rx="0.25"
|
||||||
|
transform="rotate(90 13.75 4.25)"
|
||||||
|
stroke="black"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
style="stroke: black; stroke-opacity: 0.3"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M12 9C12.5523 9 13 9.44772 13 10V12C13 12.5523 12.5523 13 12 13H4C3.44772 13 3 12.5523 3 12V10C3 9.44772 3.44772 9 4 9H12ZM4.5 10C4.22386 10 4 10.2239 4 10.5V11.5C4 11.7761 4.22386 12 4.5 12H11.5C11.7761 12 12 11.7761 12 11.5V10.5C12 10.2239 11.7761 10 11.5 10H4.5Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M12 3C12.5523 3 13 3.44772 13 4V6C13 6.55228 12.5523 7 12 7H4C3.44772 7 3 6.55228 3 6V4C3 3.44772 3.44772 3 4 3H12ZM4.5 4C4.22386 4 4 4.22386 4 4.5V5.5C4 5.77614 4.22386 6 4.5 6H11.5C11.7761 6 12 5.77614 12 5.5V4.5C12 4.22386 11.7761 4 11.5 4H4.5Z"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="13.75"
|
||||||
|
y="14.25"
|
||||||
|
width="0.5"
|
||||||
|
height="11.5"
|
||||||
|
rx="0.25"
|
||||||
|
transform="rotate(90 13.75 14.25)"
|
||||||
|
stroke="black"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
style="stroke: black; stroke-opacity: 0.3"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="13.75"
|
||||||
|
y="1.25"
|
||||||
|
width="0.5"
|
||||||
|
height="11.5"
|
||||||
|
rx="0.25"
|
||||||
|
transform="rotate(90 13.75 1.25)"
|
||||||
|
stroke="black"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
style="stroke: black; stroke-opacity: 0.3"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export { default as AlignItemsCenter } from './Center.vue';
|
||||||
|
export { default as AlignItemsFlexEnd } from './FlexEnd.vue';
|
||||||
|
export { default as AlignItemsFlexStart } from './FlexStart.vue';
|
||||||
|
export { default as AlignItemsSpaceAround } from './SpaceAround.vue';
|
||||||
|
export { default as AlignItemsSpaceBetween } from './SpaceBetween.vue';
|
||||||
@ -1,8 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10" fill="currentColor">
|
||||||
<path
|
<path
|
||||||
d="M912.526651 867.741144 555.540144 510.712681l356.986507-357.000833c11.171434-11.18576 11.171434-29.257348 0-40.443108-11.20111-11.18576-29.272697-11.18576-40.444131 0L515.096013 470.267527 158.096203 113.267716c-11.187807-11.159154-29.258371-11.159154-40.444131 0-11.186783 11.186783-11.186783 29.286 0 40.47176L474.623229 510.712681 117.623419 867.741144c-11.159154 11.172457-11.159154 29.216415 0 40.443108 11.18576 11.17348 29.284977 11.17348 40.47176 0l357.000833-357.027439 356.985484 357.027439c11.171434 11.17348 29.243021 11.17348 40.444131 0C923.698085 896.957559 923.725714 878.913601 912.526651 867.741144z"
|
d="M0 0.625C0 0.279822 0.248731 0 0.555556 0H9.44444C9.75127 0 10 0.279822 10 0.625C10 0.970178 9.75127 1.25 9.44444 1.25H0.555556C0.248731 1.25 0 0.970178 0 0.625Z"
|
||||||
fill="#5D5D5D"
|
fill-opacity="0.3"
|
||||||
></path>
|
/>
|
||||||
|
<path
|
||||||
|
d="M0 9.375C0 9.02982 0.248731 8.75 0.555556 8.75H9.44444C9.75127 8.75 10 9.02982 10 9.375C10 9.72018 9.75127 10 9.44444 10H0.555556C0.248731 10 0 9.72018 0 9.375Z"
|
||||||
|
fill-opacity="0.3"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M0.555556 4.375C0.248731 4.375 0 4.65482 0 5C0 5.34518 0.248731 5.625 0.555556 5.625H2.87307L3.42868 4.99994L2.87318 4.375H0.555556Z"
|
||||||
|
fill-opacity="0.3"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M9.44444 5.625H7.12699L6.57138 4.99994L7.12688 4.375H9.44444C9.75127 4.375 10 4.65482 10 5C10 5.34518 9.75127 5.625 9.44444 5.625Z"
|
||||||
|
fill-opacity="0.3"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
transform="translate(0 2.6072)"
|
||||||
|
d="M3.30542 0.183058C3.52237 -0.0610194 3.87413 -0.0610194 4.09109 0.183058C4.30805 0.427136 4.30805 0.822864 4.09109 1.06694L2.91256 2.39279L4.0911 3.71865C4.30806 3.96273 4.30806 4.35846 4.0911 4.60253C3.87414 4.84661 3.52239 4.84661 3.30543 4.60253L2.12688 3.27667L0.948393 4.60248C0.731435 4.84655 0.379676 4.84655 0.162719 4.60248C-0.0542395 4.3584 -0.0542395 3.96267 0.162719 3.71859L1.34121 2.39279L0.162731 1.067C-0.0542269 0.822922 -0.0542268 0.427194 0.162731 0.183116C0.379689 -0.0609612 0.731447 -0.0609612 0.948405 0.183116L2.12688 1.50891L3.30542 0.183058Z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,31 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<path
|
<rect x="13" y="3" width="2" height="2" rx="0.5" transform="rotate(90 13 3)" fill-opacity="0.3" />
|
||||||
d="M884.736 102.4l-147.456 0c-20.48 0-36.864 16.384-36.864 36.864l0 147.456C696.32 311.296 712.704 327.68 733.184 327.68l147.456 0c20.48 0 36.864-16.384 36.864-36.864L917.504 139.264C921.6 118.784 905.216 102.4 884.736 102.4zM884.736 290.816l-147.456 0L737.28 139.264l147.456 0L884.736 290.816z"
|
<rect x="13" y="7" width="2" height="2" rx="0.5" transform="rotate(90 13 7)" fill-opacity="0.3" />
|
||||||
></path>
|
<rect x="13" y="11" width="2" height="2" rx="0.5" transform="rotate(90 13 11)" fill-opacity="0.3" />
|
||||||
<path
|
<rect x="9" y="3" width="2" height="2" rx="0.5" transform="rotate(90 9 3)" />
|
||||||
d="M884.736 696.32l-147.456 0c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864l0-147.456C921.6 712.704 905.216 696.32 884.736 696.32zM884.736 884.736l-147.456 0 0-147.456 147.456 0L884.736 884.736z"
|
<rect x="9" y="7" width="2" height="2" rx="0.5" transform="rotate(90 9 7)" />
|
||||||
></path>
|
<rect x="9" y="11" width="2" height="2" rx="0.5" transform="rotate(90 9 11)" fill-opacity="0.3" />
|
||||||
<path
|
<rect x="5" y="3" width="2" height="2" rx="0.5" transform="rotate(90 5 3)" />
|
||||||
d="M884.736 401.408l-147.456 0c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864l0-147.456C921.6 417.792 905.216 401.408 884.736 401.408zM884.736 585.728l-147.456 0 0-147.456 147.456 0L884.736 585.728z"
|
<rect x="5" y="7" width="2" height="2" rx="0.5" transform="rotate(90 5 7)" />
|
||||||
></path>
|
<rect x="5" y="11" width="2" height="2" rx="0.5" transform="rotate(90 5 11)" fill-opacity="0.3" />
|
||||||
<path
|
|
||||||
d="M585.728 401.408l-147.456 0c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864l0-147.456C622.592 417.792 606.208 401.408 585.728 401.408zM585.728 585.728l-147.456 0 0-147.456 147.456 0L585.728 585.728z"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M585.728 102.4l-147.456 0c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864L622.592 139.264C622.592 118.784 606.208 102.4 585.728 102.4zM585.728 290.816l-147.456 0L438.272 139.264l147.456 0L585.728 290.816z"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M585.728 696.32l-147.456 0c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864l0-147.456C622.592 712.704 606.208 696.32 585.728 696.32zM585.728 884.736l-147.456 0 0-147.456 147.456 0L585.728 884.736z"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M290.816 696.32 139.264 696.32c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864l0-147.456C327.68 712.704 311.296 696.32 290.816 696.32zM290.816 884.736 139.264 884.736l0-147.456 147.456 0L286.72 884.736z"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M290.816 401.408 139.264 401.408c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864l0-147.456C327.68 417.792 311.296 401.408 290.816 401.408zM290.816 585.728 139.264 585.728l0-147.456 147.456 0L286.72 585.728z"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M290.816 102.4 139.264 102.4c-20.48 0-36.864 16.384-36.864 36.864l0 147.456C102.4 311.296 118.784 327.68 139.264 327.68l147.456 0C311.296 327.68 327.68 311.296 327.68 290.816L327.68 139.264C327.68 118.784 311.296 102.4 290.816 102.4zM290.816 290.816 139.264 290.816 139.264 139.264l147.456 0L286.72 290.816z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<path
|
<rect x="3" y="3" width="2" height="10" rx="1" />
|
||||||
d="M796.444444 170.666667h-227.555555v682.666666h227.555555V170.666667z m-56.888888 625.777777h-113.777778V227.555556h113.777778v568.888888zM455.111111 170.666667H227.555556v682.666666h227.555555V170.666667zM398.222222 796.444444H284.444444V227.555556h113.777778v568.888888zM910.222222 56.888889h56.888889v910.222222h-56.888889zM56.888889 56.888889h56.888889v910.222222H56.888889z"
|
<rect x="7" y="3" width="2" height="10" rx="1" />
|
||||||
fill="#333333"
|
<rect x="11" y="3" width="2" height="10" rx="1" fill-opacity="0.3" />
|
||||||
></path>
|
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<path
|
<rect x="13" y="3" width="2" height="10" rx="1" transform="rotate(90 13 3)" />
|
||||||
d="M170.666667 227.555556v227.555555h682.666666V227.555556H170.666667z m625.777777 56.888888v113.777778H227.555556V284.444444h568.888888zM170.666667 568.888889v227.555555h682.666666v-227.555555H170.666667z m625.777777 56.888889v113.777778H227.555556v-113.777778h568.888888zM56.888889 56.888889h910.222222v56.888889H56.888889zM56.888889 910.222222h910.222222v56.888889H56.888889z"
|
<rect x="13" y="7" width="2" height="10" rx="1" transform="rotate(90 13 7)" />
|
||||||
fill="#333333"
|
<rect x="13" y="11" width="2" height="10" rx="1" transform="rotate(90 13 11)" fill-opacity="0.3" />
|
||||||
></path>
|
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<path
|
<path
|
||||||
d="M896 320H128V0h768v320z m0 320H128v-256h768v256z m-128 192l-256 192-256-192 192-0.032V704h128v128h192z"
|
d="M12 3C12.5523 3 13 3.44772 13 4V10C13 10.5523 12.5523 11 12 11H4C3.44772 11 3 10.5523 3 10V4C3 3.44772 3.44772 3 4 3H12ZM4.5 4C4.22386 4 4 4.22386 4 4.5V9.5C4 9.77614 4.22386 10 4.5 10H11.5C11.7761 10 12 9.77614 12 9.5V4.5C12 4.22386 11.7761 4 11.5 4H4.5Z"
|
||||||
></path>
|
/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 12L8 15L5 12L11 12Z" fill-opacity="0.3" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<path
|
<path
|
||||||
d="M896 704H128v320h768V704z m0-320H128v256h768v-256z m-128-192l-256-192-256 192 192 0.032V320h128V192h192z"
|
d="M12 5C12.5523 5 13 5.44772 13 6V12C13 12.5523 12.5523 13 12 13H4C3.44772 13 3 12.5523 3 12V6C3 5.44772 3.44772 5 4 5H12ZM4.5 6C4.22386 6 4 6.22386 4 6.5V11.5C4 11.7761 4.22386 12 4.5 12H11.5C11.7761 12 12 11.7761 12 11.5V6.5C12 6.22386 11.7761 6 11.5 6H4.5Z"
|
||||||
></path>
|
/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 4L8 1L11 4L5 4Z" fill-opacity="0.3" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<path
|
<path
|
||||||
d="M320 128v768H0V128h320z m320 0v768h-256V128h256z m192 128l192 256-192 256-0.032-192H704v-128h128V256z"
|
d="M10 4C10.5523 4 11 4.44772 11 5V11C11 11.5523 10.5523 12 10 12H2C1.44772 12 1 11.5523 1 11V5C1 4.44772 1.44772 4 2 4H10ZM2.5 5C2.22386 5 2 5.22386 2 5.5V10.5C2 10.7761 2.22386 11 2.5 11H9.5C9.77614 11 10 10.7761 10 10.5V5.5C10 5.22386 9.77614 5 9.5 5H2.5Z"
|
||||||
></path>
|
/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 5L15 8L12 11L12 5Z" fill-opacity="0.3" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<path
|
<path
|
||||||
d="M704 128v768h320V128H704zM384 128v768h256V128h-256zM192 256l-192 256 192 256 0.032-192H320v-128H192V256z"
|
d="M14 4C14.5523 4 15 4.44772 15 5V11C15 11.5523 14.5523 12 14 12H6C5.44772 12 5 11.5523 5 11V5C5 4.44772 5.44772 4 6 4H14ZM6.5 5C6.22386 5 6 5.22386 6 5.5V10.5C6 10.7761 6.22386 11 6.5 11H13.5C13.7761 11 14 10.7761 14 10.5V5.5C14 5.22386 13.7761 5 13.5 5H6.5Z"
|
||||||
></path>
|
/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 11L1 8L4 5L4 11Z" fill-opacity="0.3" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<path d="M480 1024V0h64v1024h-64z m128-64V64h320v896H608zM96 960V64h320v896H96z"></path>
|
<path
|
||||||
|
d="M13 4C13.5523 4 14 4.44772 14 5V11C14 11.5523 13.5523 12 13 12H3C2.44772 12 2 11.5523 2 11V5C2 4.44772 2.44772 4 3 4H13ZM3.59082 5C3.31472 5.00005 3.09082 5.22389 3.09082 5.5V10.5C3.09082 10.7761 3.31472 11 3.59082 11H12.4092C12.6853 11 12.9092 10.7761 12.9092 10.5V5.5C12.9092 5.22389 12.6853 5.00005 12.4092 5H3.59082Z"
|
||||||
|
/>
|
||||||
|
<rect x="7.5" y="2" width="1" height="12" rx="0.5" fill-opacity="0.3" style="fill: black; fill-opacity: 0.3" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,5 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<path d="M416 160H96v704h320V160z m384 0H480v704h320V160z m128-160h-64v1024h64V0z"></path>
|
<path
|
||||||
|
d="M11 4C11.5523 4 12 4.44772 12 5V11C12 11.5523 11.5523 12 11 12H2C1.44772 12 1 11.5523 1 11V5C1 4.44772 1.44772 4 2 4H11ZM2.5 5C2.22386 5 2 5.22386 2 5.5V10.5C2 10.7761 2.22386 11 2.5 11H10.5C10.7761 11 11 10.7761 11 10.5V5.5C11 5.22386 10.7761 5 10.5 5H2.5Z"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="14.25"
|
||||||
|
y="2.25"
|
||||||
|
width="0.5"
|
||||||
|
height="11.5"
|
||||||
|
rx="0.25"
|
||||||
|
stroke="black"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
style="stroke: black; stroke-opacity: 0.3"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,5 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<path d="M608 160h320v704H608V160zM224 160h320v704H224V160zM96 0h64v1024H96V0z"></path>
|
<path
|
||||||
|
d="M14 4C14.5523 4 15 4.44772 15 5V11C15 11.5523 14.5523 12 14 12H5C4.44772 12 4 11.5523 4 11V5C4 4.44772 4.44772 4 5 4H14ZM5.5 5C5.22386 5 5 5.22386 5 5.5V10.5C5 10.7761 5.22386 11 5.5 11H13.5C13.7761 11 14 10.7761 14 10.5V5.5C14 5.22386 13.7761 5 13.5 5H5.5Z"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="1.25"
|
||||||
|
y="2.25"
|
||||||
|
width="0.5"
|
||||||
|
height="11.5"
|
||||||
|
rx="0.25"
|
||||||
|
stroke="white"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
style="stroke: white; stroke-opacity: 0.3"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,7 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<path
|
<path
|
||||||
d="M320 864H192v160H128v-160H0V160h128V0h64v160h128v704z m704 0h-128v160h-64v-160h-128V160h128V0h64v160h128v704z"
|
d="M6 4C6.55228 4 7 4.44772 7 5V11C7 11.5523 6.55228 12 6 12H3C2.44772 12 2 11.5523 2 11V5C2 4.44772 2.44772 4 3 4H6ZM3.5 5C3.22386 5 3 5.22386 3 5.5V10.5C3 10.7761 3.22386 11 3.5 11H5.5C5.77614 11 6 10.7761 6 10.5V5.5C6 5.22386 5.77614 5 5.5 5H3.5Z"
|
||||||
></path>
|
/>
|
||||||
|
<path
|
||||||
|
d="M13 4C13.5523 4 14 4.44772 14 5V11C14 11.5523 13.5523 12 13 12H10C9.44772 12 9 11.5523 9 11V5C9 4.44772 9.44772 4 10 4H13ZM10.5 5C10.2239 5 10 5.22386 10 5.5V10.5C10 10.7761 10.2239 11 10.5 11H12.5C12.7761 11 13 10.7761 13 10.5V5.5C13 5.22386 12.7761 5 12.5 5H10.5Z"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="11.25"
|
||||||
|
y="2.25"
|
||||||
|
width="0.5"
|
||||||
|
height="11.5"
|
||||||
|
rx="0.25"
|
||||||
|
stroke="black"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
style="stroke: black; stroke-opacity: 0.3"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="4.25"
|
||||||
|
y="2.25"
|
||||||
|
width="0.5"
|
||||||
|
height="11.5"
|
||||||
|
rx="0.25"
|
||||||
|
stroke="black"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
style="stroke: black; stroke-opacity: 0.3"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,5 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<path d="M64 1024H0V0h64v1024z m384-160H128V160h320v704z m448 0H576V160h320v704z m128 160h-64V0h64v1024z"></path>
|
<path
|
||||||
|
d="M6 4C6.55228 4 7 4.44772 7 5V11C7 11.5523 6.55228 12 6 12H4C3.44772 12 3 11.5523 3 11V5C3 4.44772 3.44772 4 4 4H6ZM4.5 5C4.22386 5 4 5.22386 4 5.5V10.5C4 10.7761 4.22386 11 4.5 11H5.5C5.77614 11 6 10.7761 6 10.5V5.5C6 5.22386 5.77614 5 5.5 5H4.5Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M12 4C12.5523 4 13 4.44772 13 5V11C13 11.5523 12.5523 12 12 12H10C9.44772 12 9 11.5523 9 11V5C9 4.44772 9.44772 4 10 4H12ZM10.5 5C10.2239 5 10 5.22386 10 5.5V10.5C10 10.7761 10.2239 11 10.5 11H11.5C11.7761 11 12 10.7761 12 10.5V5.5C12 5.22386 11.7761 5 11.5 5H10.5Z"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="14.25"
|
||||||
|
y="2.25"
|
||||||
|
width="0.5"
|
||||||
|
height="11.5"
|
||||||
|
rx="0.25"
|
||||||
|
stroke="black"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="1.25"
|
||||||
|
y="2.25"
|
||||||
|
width="0.5"
|
||||||
|
height="11.5"
|
||||||
|
rx="0.25"
|
||||||
|
stroke="black"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
style="stroke: black; stroke-opacity: 0.3"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -29,6 +29,7 @@ defineProps<{
|
|||||||
isCompare?: boolean;
|
isCompare?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
size?: 'large' | 'default' | 'small';
|
size?: 'large' | 'default' | 'small';
|
||||||
|
theme?: string;
|
||||||
prop?: string;
|
prop?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@ -112,10 +113,14 @@ const formConfig = defineFormConfig([
|
|||||||
childType: 'button',
|
childType: 'button',
|
||||||
labelWidth: '68px',
|
labelWidth: '68px',
|
||||||
options: [
|
options: [
|
||||||
{ value: 'repeat', icon: markRaw(BackgroundRepeat), tooltip: '垂直和水平方向重复 repeat' },
|
{ value: 'no-repeat', icon: markRaw(BackgroundNoRepeat), tooltip: '不重复 no-repeat' },
|
||||||
{ value: 'repeat-x', icon: markRaw(BackgroundRepeatX), tooltip: '水平方向重复 repeat-x' },
|
{ value: 'repeat-x', icon: markRaw(BackgroundRepeatX), tooltip: '水平方向重复 repeat-x' },
|
||||||
{ value: 'repeat-y', icon: markRaw(BackgroundRepeatY), tooltip: '垂直方向重复 repeat-y' },
|
{ value: 'repeat-y', icon: markRaw(BackgroundRepeatY), tooltip: '垂直方向重复 repeat-y' },
|
||||||
{ value: 'no-repeat', icon: markRaw(BackgroundNoRepeat), tooltip: '不重复 no-repeat' },
|
{
|
||||||
|
value: 'repeat',
|
||||||
|
icon: markRaw(BackgroundRepeat),
|
||||||
|
tooltip: '垂直和水平方向重复 repeat',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -95,9 +95,9 @@ const formConfig = defineFormConfig([
|
|||||||
childType: 'button',
|
childType: 'button',
|
||||||
labelWidth: '68px',
|
labelWidth: '68px',
|
||||||
options: [
|
options: [
|
||||||
{ value: 'left', icon: markRaw(AlignLeft), tooltip: '左对齐 row' },
|
{ value: 'left', icon: markRaw(AlignLeft), tooltip: '左对齐 row', text: '左对齐' },
|
||||||
{ value: 'center', icon: markRaw(AlignCenter), tooltip: '居中对齐 center' },
|
{ value: 'center', icon: markRaw(AlignCenter), tooltip: '居中对齐 center', text: '居中对齐' },
|
||||||
{ value: 'right', icon: markRaw(AlignRight), tooltip: '右对齐 right' },
|
{ value: 'right', icon: markRaw(AlignRight), tooltip: '右对齐 right', text: '右对齐' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -26,11 +26,19 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { markRaw } from 'vue';
|
import { markRaw } from 'vue';
|
||||||
|
|
||||||
|
import { useTheme } from '@tmagic/design';
|
||||||
import type { ContainerChangeEventData } from '@tmagic/form';
|
import type { ContainerChangeEventData } from '@tmagic/form';
|
||||||
import { defineFormConfig, MContainer } from '@tmagic/form';
|
import { defineFormConfig, MContainer } from '@tmagic/form';
|
||||||
import type { StyleSchema } from '@tmagic/schema';
|
import type { StyleSchema } from '@tmagic/schema';
|
||||||
|
|
||||||
import Box from '../components/Box.vue';
|
import Box from '../components/Box.vue';
|
||||||
|
import {
|
||||||
|
AlignItemsCenter,
|
||||||
|
AlignItemsFlexEnd,
|
||||||
|
AlignItemsFlexStart,
|
||||||
|
AlignItemsSpaceAround,
|
||||||
|
AlignItemsSpaceBetween,
|
||||||
|
} from '../icons/align-items';
|
||||||
import { DisplayBlock, DisplayFlex, DisplayInline, DisplayInlineBlock, DisplayNone } from '../icons/display';
|
import { DisplayBlock, DisplayFlex, DisplayInline, DisplayInlineBlock, DisplayNone } from '../icons/display';
|
||||||
import {
|
import {
|
||||||
FlexDirectionColumn,
|
FlexDirectionColumn,
|
||||||
@ -46,12 +54,13 @@ import {
|
|||||||
JustifyContentSpaceBetween,
|
JustifyContentSpaceBetween,
|
||||||
} from '../icons/justify-content';
|
} from '../icons/justify-content';
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
values: Partial<StyleSchema>;
|
values: Partial<StyleSchema>;
|
||||||
lastValues?: Partial<StyleSchema>;
|
lastValues?: Partial<StyleSchema>;
|
||||||
isCompare?: boolean;
|
isCompare?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
size?: 'large' | 'default' | 'small';
|
size?: 'large' | 'default' | 'small';
|
||||||
|
theme?: string;
|
||||||
prop?: string;
|
prop?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@ -60,19 +69,42 @@ const emit = defineEmits<{
|
|||||||
addDiffCount: [];
|
addDiffCount: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const displayTheme = useTheme(props);
|
||||||
|
|
||||||
const formConfig = defineFormConfig([
|
const formConfig = defineFormConfig([
|
||||||
{
|
{
|
||||||
name: 'display',
|
name: 'display',
|
||||||
text: '模式',
|
text: '模式',
|
||||||
type: 'radioGroup',
|
type: 'radioGroup',
|
||||||
childType: 'button',
|
childType: 'button',
|
||||||
labelWidth: '68px',
|
labelWidth: '90px',
|
||||||
|
iconSize: '24px',
|
||||||
options: [
|
options: [
|
||||||
{ value: 'inline', icon: markRaw(DisplayInline), tooltip: '内联布局 inline' },
|
{
|
||||||
{ value: 'flex', icon: markRaw(DisplayFlex), tooltip: '弹性布局 flex' },
|
value: 'inline',
|
||||||
{ value: 'block', icon: markRaw(DisplayBlock), tooltip: '块级布局 block' },
|
icon: markRaw(DisplayInline),
|
||||||
{ value: 'inline-block', icon: markRaw(DisplayInlineBlock), tooltip: '内联块布局 inline-block' },
|
tooltip: '内联布局 inline',
|
||||||
{ value: 'none', icon: markRaw(DisplayNone), tooltip: '隐藏 none' },
|
},
|
||||||
|
{
|
||||||
|
value: 'flex',
|
||||||
|
icon: markRaw(DisplayFlex),
|
||||||
|
tooltip: '弹性布局 flex',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'block',
|
||||||
|
icon: markRaw(DisplayBlock),
|
||||||
|
tooltip: '块级布局 block',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'inline-block',
|
||||||
|
icon: markRaw(DisplayInlineBlock),
|
||||||
|
tooltip: '内联块布局 inline-block',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'none',
|
||||||
|
icon: markRaw(DisplayNone),
|
||||||
|
tooltip: '隐藏 none',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -80,11 +112,20 @@ const formConfig = defineFormConfig([
|
|||||||
text: '主轴方向',
|
text: '主轴方向',
|
||||||
type: 'radioGroup',
|
type: 'radioGroup',
|
||||||
childType: 'button',
|
childType: 'button',
|
||||||
labelWidth: '68px',
|
labelWidth: '90px',
|
||||||
|
iconSize: '24px',
|
||||||
options: [
|
options: [
|
||||||
{ value: 'row', icon: markRaw(FlexDirectionRow), tooltip: '水平方向 起点在左侧 row' },
|
{ value: 'row', icon: markRaw(FlexDirectionRow), tooltip: '水平方向 起点在左侧 row' },
|
||||||
{ value: 'row-reverse', icon: markRaw(FlexDirectionRowReverse), tooltip: '水平方向 起点在右侧 row-reverse' },
|
{
|
||||||
{ value: 'column', icon: markRaw(FlexDirectionColumn), tooltip: '垂直方向 起点在上沿 column' },
|
value: 'row-reverse',
|
||||||
|
icon: markRaw(FlexDirectionRowReverse),
|
||||||
|
tooltip: '水平方向 起点在右侧 row-reverse',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'column',
|
||||||
|
icon: markRaw(FlexDirectionColumn),
|
||||||
|
tooltip: '垂直方向 起点在上沿 column',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: 'column-reverse',
|
value: 'column-reverse',
|
||||||
icon: markRaw(FlexDirectionColumnReverse),
|
icon: markRaw(FlexDirectionColumnReverse),
|
||||||
@ -98,13 +139,22 @@ const formConfig = defineFormConfig([
|
|||||||
text: '主轴对齐',
|
text: '主轴对齐',
|
||||||
type: 'radioGroup',
|
type: 'radioGroup',
|
||||||
childType: 'button',
|
childType: 'button',
|
||||||
labelWidth: '68px',
|
labelWidth: '90px',
|
||||||
|
iconSize: '24px',
|
||||||
options: [
|
options: [
|
||||||
{ value: 'flex-start', icon: markRaw(JustifyContentFlexStart), tooltip: '左对齐 flex-start' },
|
{ value: 'flex-start', icon: markRaw(JustifyContentFlexStart), tooltip: '左对齐 flex-start' },
|
||||||
{ value: 'flex-end', icon: markRaw(JustifyContentFlexEnd), tooltip: '右对齐 flex-end' },
|
{ value: 'flex-end', icon: markRaw(JustifyContentFlexEnd), tooltip: '右对齐 flex-end' },
|
||||||
{ value: 'center', icon: markRaw(JustifyContentCenter), tooltip: '居中 center' },
|
{ value: 'center', icon: markRaw(JustifyContentCenter), tooltip: '居中 center' },
|
||||||
{ value: 'space-between', icon: markRaw(JustifyContentSpaceBetween), tooltip: '两端对齐 space-between' },
|
{
|
||||||
{ value: 'space-around', icon: markRaw(JustifyContentSpaceAround), tooltip: '横向平分 space-around' },
|
value: 'space-between',
|
||||||
|
icon: markRaw(JustifyContentSpaceBetween),
|
||||||
|
tooltip: '两端对齐 space-between',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'space-around',
|
||||||
|
icon: markRaw(JustifyContentSpaceAround),
|
||||||
|
tooltip: '横向平分 space-around',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
|
display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
|
||||||
},
|
},
|
||||||
@ -113,13 +163,22 @@ const formConfig = defineFormConfig([
|
|||||||
text: '辅轴对齐',
|
text: '辅轴对齐',
|
||||||
type: 'radioGroup',
|
type: 'radioGroup',
|
||||||
childType: 'button',
|
childType: 'button',
|
||||||
labelWidth: '68px',
|
labelWidth: '90px',
|
||||||
|
iconSize: '24px',
|
||||||
options: [
|
options: [
|
||||||
{ value: 'flex-start', icon: markRaw(JustifyContentFlexStart), tooltip: '左对齐 flex-start' },
|
{ value: 'flex-start', icon: markRaw(AlignItemsFlexStart), tooltip: '左对齐 flex-start' },
|
||||||
{ value: 'flex-end', icon: markRaw(JustifyContentFlexEnd), tooltip: '右对齐 flex-end' },
|
{ value: 'flex-end', icon: markRaw(AlignItemsFlexEnd), tooltip: '右对齐 flex-end' },
|
||||||
{ value: 'center', icon: markRaw(JustifyContentCenter), tooltip: '居中 center' },
|
{ value: 'center', icon: markRaw(AlignItemsCenter), tooltip: '居中 center' },
|
||||||
{ value: 'space-between', icon: markRaw(JustifyContentSpaceBetween), tooltip: '两端对齐 space-between' },
|
{
|
||||||
{ value: 'space-around', icon: markRaw(JustifyContentSpaceAround), tooltip: '横向平分 space-around' },
|
value: 'space-between',
|
||||||
|
icon: markRaw(AlignItemsSpaceBetween),
|
||||||
|
tooltip: '两端对齐 space-between',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'space-around',
|
||||||
|
icon: markRaw(AlignItemsSpaceAround),
|
||||||
|
tooltip: '横向平分 space-around',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
|
display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
|
||||||
},
|
},
|
||||||
@ -127,8 +186,9 @@ const formConfig = defineFormConfig([
|
|||||||
name: 'flexWrap',
|
name: 'flexWrap',
|
||||||
text: '换行',
|
text: '换行',
|
||||||
type: 'radioGroup',
|
type: 'radioGroup',
|
||||||
childType: 'button',
|
childType: displayTheme.value !== 'magic-admin' ? 'button' : 'default',
|
||||||
labelWidth: '68px',
|
labelWidth: '90px',
|
||||||
|
iconSize: '24px',
|
||||||
options: [
|
options: [
|
||||||
{ value: 'nowrap', text: '不换行', tooltip: '不换行 nowrap' },
|
{ value: 'nowrap', text: '不换行', tooltip: '不换行 nowrap' },
|
||||||
{ value: 'wrap', text: '正换行', tooltip: '第一行在上方 wrap' },
|
{ value: 'wrap', text: '正换行', tooltip: '第一行在上方 wrap' },
|
||||||
@ -141,17 +201,22 @@ const formConfig = defineFormConfig([
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'width',
|
name: 'width',
|
||||||
text: '宽度',
|
text: '宽度(px)',
|
||||||
labelWidth: '68px',
|
labelWidth: '90px',
|
||||||
type: 'data-source-field-select',
|
type: 'data-source-field-select',
|
||||||
fieldConfig: {
|
fieldConfig: {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'row',
|
||||||
|
items: [
|
||||||
{
|
{
|
||||||
name: 'height',
|
name: 'height',
|
||||||
text: '高度',
|
text: '高度(px)',
|
||||||
labelWidth: '68px',
|
labelWidth: '90px',
|
||||||
type: 'data-source-field-select',
|
type: 'data-source-field-select',
|
||||||
fieldConfig: {
|
fieldConfig: {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@ -166,7 +231,7 @@ const formConfig = defineFormConfig([
|
|||||||
type: 'data-source-field-select',
|
type: 'data-source-field-select',
|
||||||
text: 'overflow',
|
text: 'overflow',
|
||||||
name: 'overflow',
|
name: 'overflow',
|
||||||
labelWidth: '68px',
|
labelWidth: '90px',
|
||||||
checkStrictly: false,
|
checkStrictly: false,
|
||||||
dataSourceFieldType: ['string'],
|
dataSourceFieldType: ['string'],
|
||||||
fieldConfig: {
|
fieldConfig: {
|
||||||
@ -184,11 +249,16 @@ const formConfig = defineFormConfig([
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'row',
|
||||||
|
items: [
|
||||||
{
|
{
|
||||||
type: 'data-source-field-select',
|
type: 'data-source-field-select',
|
||||||
text: '透明度',
|
text: '透明度(%)',
|
||||||
name: 'opacity',
|
name: 'opacity',
|
||||||
labelWidth: '68px',
|
labelWidth: '90px',
|
||||||
dataSourceFieldType: ['string', 'number'],
|
dataSourceFieldType: ['string', 'number'],
|
||||||
fieldConfig: {
|
fieldConfig: {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
|||||||
7
packages/editor/src/icons/DatasourceIcon.vue
Normal file
7
packages/editor/src/icons/DatasourceIcon.vue
Normal file
File diff suppressed because one or more lines are too long
@ -32,6 +32,9 @@ export { DepTargetType } from '@tmagic/core';
|
|||||||
export * from './type';
|
export * from './type';
|
||||||
export * from './hooks';
|
export * from './hooks';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
// `isGlobalFlat` 同时在 `@tmagic/design` 和 `./utils/config` 中存在,
|
||||||
|
// 通过显式重导一次消除 `export *` 带来的歧义,并固定使用 editor 自己那份。
|
||||||
|
export { isGlobalFlat } from './utils/config';
|
||||||
export { default as TMagicEditor } from './Editor.vue';
|
export { default as TMagicEditor } from './Editor.vue';
|
||||||
export { default as TMagicCodeEditor } from './layouts/CodeEditor.vue';
|
export { default as TMagicCodeEditor } from './layouts/CodeEditor.vue';
|
||||||
export { default as editorService } from './services/editor';
|
export { default as editorService } from './services/editor';
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="m-editor" ref="content" style="min-width: 900px">
|
<div
|
||||||
|
ref="content"
|
||||||
|
:class="['m-editor', theme ? `m-editor--${theme}` : '', theme ? `m-theme--${theme}` : '']"
|
||||||
|
style="min-width: 900px"
|
||||||
|
>
|
||||||
<slot name="header"></slot>
|
<slot name="header"></slot>
|
||||||
|
|
||||||
<slot name="nav"></slot>
|
<slot name="nav"></slot>
|
||||||
@ -97,6 +101,8 @@ const props = defineProps<{
|
|||||||
pageFilterFunction?: (_page: MPage | MPageFragment, _keyword: string) => boolean;
|
pageFilterFunction?: (_page: MPage | MPageFragment, _keyword: string) => boolean;
|
||||||
/** 是否隐藏左侧面板 */
|
/** 是否隐藏左侧面板 */
|
||||||
hideSidebar?: boolean;
|
hideSidebar?: boolean;
|
||||||
|
/** 主题名称,会在根节点追加 `m-editor--<theme>` 修饰类 */
|
||||||
|
theme?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const codeOptions = inject('codeOptions', {});
|
const codeOptions = inject('codeOptions', {});
|
||||||
|
|||||||
@ -111,14 +111,31 @@ const styleFormConfig = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 用单调递增序号标记每次 init 调用,只让"最新一次"的 await 结果落到 ref 上。
|
||||||
|
// 避免节点快速切换时多个 init 并发 + 解析顺序错乱导致 stale config 覆盖最新选中节点,
|
||||||
|
// 以及组件已卸载 / FormPanel 正在 remount 的中间态下写 ref 触发 Vue setRef 把 __vnode
|
||||||
|
// 设到 null 上的报错(TypeError: Cannot set properties of null (setting '__vnode'))。
|
||||||
|
let initSeq = 0;
|
||||||
|
let mounted = true;
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
|
initSeq += 1;
|
||||||
|
const seq = initSeq;
|
||||||
|
|
||||||
if (!node.value) {
|
if (!node.value) {
|
||||||
|
if (seq !== initSeq || !mounted) return;
|
||||||
curFormConfig.value = [];
|
curFormConfig.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = node.value.type || (node.value.items ? 'container' : 'text');
|
const type = node.value.type || (node.value.items ? 'container' : 'text');
|
||||||
curFormConfig.value = await propsService.getPropsConfig(type, { node: node.value });
|
const config = await propsService.getPropsConfig(type, { node: node.value });
|
||||||
|
|
||||||
|
// 期间被新一次 init 取代 / 组件已卸载,丢弃本次结果
|
||||||
|
if (seq !== initSeq || !mounted) return;
|
||||||
|
if (!node.value) return;
|
||||||
|
|
||||||
|
curFormConfig.value = config;
|
||||||
values.value = node.value;
|
values.value = node.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,6 +143,7 @@ watchEffect(init);
|
|||||||
propsService.on('props-configs-change', init);
|
propsService.on('props-configs-change', init);
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
mounted = false;
|
||||||
propsService.off('props-configs-change', init);
|
propsService.off('props-configs-change', init);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -155,7 +155,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, ref, watch } from 'vue';
|
import { computed, nextTick, ref, watch } from 'vue';
|
||||||
import { Close, Coin, EditPen, Goods, List } from '@element-plus/icons-vue';
|
import { Box, Close, Coin, EditPen, Finished } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
import FloatingBox from '@editor/components/FloatingBox.vue';
|
import FloatingBox from '@editor/components/FloatingBox.vue';
|
||||||
import MIcon from '@editor/components/Icon.vue';
|
import MIcon from '@editor/components/Icon.vue';
|
||||||
@ -253,7 +253,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
|
|||||||
[SideItemKey.COMPONENT_LIST]: {
|
[SideItemKey.COMPONENT_LIST]: {
|
||||||
$key: SideItemKey.COMPONENT_LIST,
|
$key: SideItemKey.COMPONENT_LIST,
|
||||||
type: 'component',
|
type: 'component',
|
||||||
icon: Goods,
|
icon: Box,
|
||||||
text: '组件',
|
text: '组件',
|
||||||
component: ComponentListPanel,
|
component: ComponentListPanel,
|
||||||
slots: {},
|
slots: {},
|
||||||
@ -261,7 +261,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
|
|||||||
layer: {
|
layer: {
|
||||||
$key: SideItemKey.LAYER,
|
$key: SideItemKey.LAYER,
|
||||||
type: 'component',
|
type: 'component',
|
||||||
icon: List,
|
icon: Finished,
|
||||||
text: '已选组件',
|
text: '已选组件',
|
||||||
props: {
|
props: {
|
||||||
layerContentMenu: props.layerContentMenu,
|
layerContentMenu: props.layerContentMenu,
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { App } from 'vue';
|
import { type App } from 'vue';
|
||||||
|
|
||||||
import type { DesignPluginOptions } from '@tmagic/design';
|
import type { DesignPluginOptions } from '@tmagic/design';
|
||||||
import designPlugin from '@tmagic/design';
|
import designPlugin from '@tmagic/design';
|
||||||
@ -56,6 +56,8 @@ const defaultInstallOpt: EditorInstallOptions = {
|
|||||||
customCreateMonacoEditor: (monaco, codeEditorEl, options) => monaco.editor.create(codeEditorEl, options),
|
customCreateMonacoEditor: (monaco, codeEditorEl, options) => monaco.editor.create(codeEditorEl, options),
|
||||||
customCreateMonacoDiffEditor: (monaco, codeEditorEl, options) =>
|
customCreateMonacoDiffEditor: (monaco, codeEditorEl, options) =>
|
||||||
monaco.editor.createDiffEditor(codeEditorEl, options),
|
monaco.editor.createDiffEditor(codeEditorEl, options),
|
||||||
|
|
||||||
|
flat: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -69,7 +71,6 @@ export default {
|
|||||||
|
|
||||||
app.config.globalProperties.$TMAGIC_EDITOR = option;
|
app.config.globalProperties.$TMAGIC_EDITOR = option;
|
||||||
setEditorConfig(option);
|
setEditorConfig(option);
|
||||||
|
|
||||||
app.component(`${Editor.name || 'MEditor'}`, Editor);
|
app.component(`${Editor.name || 'MEditor'}`, Editor);
|
||||||
app.component('magic-code-editor', CodeEditor);
|
app.component('magic-code-editor', CodeEditor);
|
||||||
app.component('m-fields-ui-select', uiSelect);
|
app.component('m-fields-ui-select', uiSelect);
|
||||||
|
|||||||
@ -49,13 +49,14 @@ const state = shallowReactive<UiState>({
|
|||||||
width: 375,
|
width: 375,
|
||||||
height: 817,
|
height: 817,
|
||||||
},
|
},
|
||||||
|
// 先给 columnWidth 留占位,真正的 clamp 逻辑在 state 创建完成后
|
||||||
|
// 用 state.minLeftColumnWidth / state.minRightColumnWidth 作为下限重新赋值 —
|
||||||
|
// 这样以后从外部(比如 uiService.set('minLeftColumnWidth', ...))动态改最小宽度,
|
||||||
|
// 只改一处 state 字段即可,不用同步 MIN_* 常量。
|
||||||
columnWidth: {
|
columnWidth: {
|
||||||
left:
|
left: 0,
|
||||||
storageService.getItem(LEFT_COLUMN_WIDTH_STORAGE_KEY, { protocol: Protocol.NUMBER }) || DEFAULT_LEFT_COLUMN_WIDTH,
|
|
||||||
center: 0,
|
center: 0,
|
||||||
right:
|
right: 0,
|
||||||
storageService.getItem(RIGHT_COLUMN_WIDTH_STORAGE_KEY, { protocol: Protocol.NUMBER }) ||
|
|
||||||
DEFAULT_RIGHT_COLUMN_WIDTH,
|
|
||||||
},
|
},
|
||||||
minLeftColumnWidth: MIN_LEFT_COLUMN_WIDTH,
|
minLeftColumnWidth: MIN_LEFT_COLUMN_WIDTH,
|
||||||
minCenterColumnWidth: MIN_CENTER_COLUMN_WIDTH,
|
minCenterColumnWidth: MIN_CENTER_COLUMN_WIDTH,
|
||||||
@ -83,6 +84,20 @@ const state = shallowReactive<UiState>({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 左右两栏宽度延后初始化:
|
||||||
|
// - 优先取 storage 里用户上次拖到的值;storage 空时回落到 DEFAULT_*;
|
||||||
|
// - 再用 state.min*ColumnWidth 兜底,防止历史 storage 里存的是老版本或用户拖过头
|
||||||
|
// 留下的"比当前 min 还小"的数值。放到 state 之后写是因为初始化时无法在同一个
|
||||||
|
// 对象字面量里读到自己的其它字段。
|
||||||
|
state.columnWidth.left = Math.max(
|
||||||
|
storageService.getItem(LEFT_COLUMN_WIDTH_STORAGE_KEY, { protocol: Protocol.NUMBER }) || DEFAULT_LEFT_COLUMN_WIDTH,
|
||||||
|
state.minLeftColumnWidth,
|
||||||
|
);
|
||||||
|
state.columnWidth.right = Math.max(
|
||||||
|
storageService.getItem(RIGHT_COLUMN_WIDTH_STORAGE_KEY, { protocol: Protocol.NUMBER }) || DEFAULT_RIGHT_COLUMN_WIDTH,
|
||||||
|
state.minRightColumnWidth,
|
||||||
|
);
|
||||||
|
|
||||||
const canUsePluginMethods = {
|
const canUsePluginMethods = {
|
||||||
async: ['zoom', 'calcZoom'] as const,
|
async: ['zoom', 'calcZoom'] as const,
|
||||||
sync: [] as const,
|
sync: [] as const,
|
||||||
|
|||||||
@ -1,3 +1,40 @@
|
|||||||
.m-fields-code-select {
|
.m-fields-code-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
> .el-card.tmagic-design-card--flat {
|
||||||
|
background-color: transparent;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
> .el-card__body {
|
||||||
|
padding-left: 0;
|
||||||
|
.code-select-content {
|
||||||
|
> .m-fields-group-list {
|
||||||
|
> .tmagic-design-card--flat.el-card {
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.el-card__body {
|
||||||
|
.tmagic-design-form-item {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.code-select-content {
|
||||||
|
> .m-fields-group-list {
|
||||||
|
> .tmagic-design-card--flat {
|
||||||
|
> .el-card__header {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.create-button {
|
||||||
|
&.fullWidth {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
$theme-color: #2882e0;
|
$theme-color: var(--el-color-primary, #2882e0);
|
||||||
|
|
||||||
$font-color: #313a40;
|
$font-color: #313a40;
|
||||||
$border-color: #d9dbdd;
|
$border-color: #d9dbdd;
|
||||||
|
|||||||
@ -77,9 +77,9 @@
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #2882e0;
|
background: var(--el-color-primary, #2882e0);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-color: #4e8be1;
|
border-color: var(--el-color-primary, #4e8be1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,10 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
&.data-source-field-img-upload,
|
||||||
|
&.data-source-field-big-img-upload {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.m-fields-select-action-button {
|
.m-fields-select-action-button {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
|
|||||||
11
packages/editor/src/theme/display-conds.scss
Normal file
11
packages/editor/src/theme/display-conds.scss
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.m-container-text.display-conds-title {
|
||||||
|
.tmagic-design-form-item {
|
||||||
|
.el-form-item__label {
|
||||||
|
color: #111;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
line-height: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,8 +2,29 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
.fullWidth {
|
.fullWidth {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.event-select-container {
|
||||||
|
padding: 0 16px;
|
||||||
|
> .el-card.tmagic-design-card--flat {
|
||||||
|
> .el-card__header {
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
> .el-card__body {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
.m-fields-group-list-footer {
|
||||||
|
div {
|
||||||
|
justify-content: flex-start !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.el-card.tmagic-design-card--flat {
|
||||||
|
> .el-card__header {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-select-code {
|
.event-select-code {
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
width: auto;
|
width: auto;
|
||||||
@ -36,10 +57,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-form-item {
|
.event-select-container {
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-select-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
.event-select-title {
|
||||||
|
color: #0f1113;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.event-item-header {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
.event-item-title {
|
||||||
|
color: #0f1113;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.event-item-delete-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.el-form-item {
|
.el-form-item {
|
||||||
&.is-error {
|
.el-form-item {
|
||||||
margin-bottom: 18px;
|
&.is-error {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,10 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&-content {
|
&-content {
|
||||||
height: calc(100% - #{$nav-height});
|
// 与 nav-menu.scss 的 `--editor-nav-height` 联动:默认 fallback 到 SCSS
|
||||||
|
// `$nav-height`(35px),主题覆盖时两处会同时切成同一个高度,保证 nav 和
|
||||||
|
// 主内容区总高度 = 100%。
|
||||||
|
height: calc(100% - var(--editor-nav-height, #{$nav-height}));
|
||||||
}
|
}
|
||||||
|
|
||||||
&-framework-center {
|
&-framework-center {
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
color: #909399;
|
color: #909399;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #409eff;
|
color: var(--el-color-primary, #409eff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +96,7 @@
|
|||||||
// 故这里仅作用于「非合并组」的单步条目,避免与卡片样式互相干扰。
|
// 故这里仅作用于「非合并组」的单步条目,避免与卡片样式互相干扰。
|
||||||
&.is-current:not(.m-editor-history-list-group.is-merged) {
|
&.is-current:not(.m-editor-history-list-group.is-merged) {
|
||||||
background-color: rgba(64, 158, 255, 0.1);
|
background-color: rgba(64, 158, 255, 0.1);
|
||||||
box-shadow: inset 2px 0 0 #409eff;
|
box-shadow: inset 2px 0 0 var(--el-color-primary, #409eff);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(64, 158, 255, 0.16);
|
background-color: rgba(64, 158, 255, 0.16);
|
||||||
@ -104,7 +104,7 @@
|
|||||||
|
|
||||||
.m-editor-history-list-item-desc {
|
.m-editor-history-list-item-desc {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #409eff;
|
color: var(--el-color-primary, #409eff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,11 +177,11 @@
|
|||||||
&.is-current {
|
&.is-current {
|
||||||
background-color: rgba(64, 158, 255, 0.08);
|
background-color: rgba(64, 158, 255, 0.08);
|
||||||
border-color: rgba(64, 158, 255, 0.3);
|
border-color: rgba(64, 158, 255, 0.3);
|
||||||
border-left-color: #409eff;
|
border-left-color: var(--el-color-primary, #409eff);
|
||||||
box-shadow: none; // 覆盖 .is-current 公共的 inset 阴影
|
box-shadow: none; // 覆盖 .is-current 公共的 inset 阴影
|
||||||
|
|
||||||
.m-editor-history-list-group-head {
|
.m-editor-history-list-group-head {
|
||||||
color: #409eff;
|
color: var(--el-color-primary, #409eff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,7 +216,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.is-current {
|
&.is-current {
|
||||||
color: #409eff;
|
color: var(--el-color-primary, #409eff);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background-color: rgba(64, 158, 255, 0.08);
|
background-color: rgba(64, 158, 255, 0.08);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -231,7 +231,7 @@
|
|||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #409eff;
|
background-color: var(--el-color-primary, #409eff);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,7 +395,7 @@
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
color: #409eff;
|
color: var(--el-color-primary, #409eff);
|
||||||
background-color: rgba(64, 158, 255, 0.1);
|
background-color: rgba(64, 158, 255, 0.1);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@ -471,7 +471,7 @@
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #409eff;
|
color: var(--el-color-primary, #409eff);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@ -13,7 +13,11 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
flex: 0 0 $nav-height;
|
// 用 CSS 变量做主题化开关:默认没设 `--editor-nav-height` 时回落到
|
||||||
|
// SCSS 变量 `$nav-height`(common/var.scss,35px);主题(如 magic-admin)
|
||||||
|
// 只需在祖先节点声明 `--editor-nav-height: 40px` 即可运行时覆盖,避免
|
||||||
|
// 走 `@use with` 那条对加载顺序极敏感的 SCSS 模块配置路径。
|
||||||
|
flex: 0 0 var(--editor-nav-height, #{$nav-height});
|
||||||
border-bottom: 1px solid #d8dee8;
|
border-bottom: 1px solid #d8dee8;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
|
|||||||
@ -129,7 +129,8 @@
|
|||||||
> .m-container-tab {
|
> .m-container-tab {
|
||||||
> .tmagic-design-tabs {
|
> .tmagic-design-tabs {
|
||||||
> .el-tabs__content {
|
> .el-tabs__content {
|
||||||
padding-top: 55px;
|
margin-top: var(--el-tabs-header-height);
|
||||||
|
padding-top: 15px;
|
||||||
}
|
}
|
||||||
> .el-tabs__header.is-top {
|
> .el-tabs__header.is-top {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
.border-box-container {
|
.border-box-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
--unuse-border-style: dashed;
|
||||||
.border-icon-container {
|
.border-icon-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -28,24 +29,32 @@
|
|||||||
border-color: var(--el-color-success, var(--td-success-color, #2ba471));
|
border-color: var(--el-color-success, var(--td-success-color, #2ba471));
|
||||||
}
|
}
|
||||||
&.active {
|
&.active {
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
border-width: 1px;
|
||||||
|
// border-color: var(--el-color-primary, var(--td-brand-color, #0052d9));
|
||||||
|
}
|
||||||
|
&.center {
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-color: var(--el-color-primary, var(--td-brand-color, #0052d9));
|
|
||||||
}
|
}
|
||||||
&.border-icon-top {
|
&.border-icon-top {
|
||||||
border-top-width: 2px;
|
border-top-width: 2px;
|
||||||
border-style: solid dashed dashed dashed;
|
border-style: solid var(--unuse-border-style) var(--unuse-border-style)
|
||||||
|
var(--unuse-border-style);
|
||||||
}
|
}
|
||||||
&.border-icon-right {
|
&.border-icon-right {
|
||||||
border-right-width: 2px;
|
border-right-width: 2px;
|
||||||
border-style: dashed solid dashed dashed;
|
border-style: var(--unuse-border-style) solid var(--unuse-border-style)
|
||||||
|
var(--unuse-border-style);
|
||||||
}
|
}
|
||||||
&.border-icon-bottom {
|
&.border-icon-bottom {
|
||||||
border-bottom-width: 2px;
|
border-bottom-width: 2px;
|
||||||
border-style: dashed dashed solid dashed;
|
border-style: var(--unuse-border-style) var(--unuse-border-style) solid
|
||||||
|
var(--unuse-border-style);
|
||||||
}
|
}
|
||||||
&.border-icon-left {
|
&.border-icon-left {
|
||||||
border-left-width: 2px;
|
border-left-width: 2px;
|
||||||
border-style: dashed dashed dashed solid;
|
border-style: var(--unuse-border-style) var(--unuse-border-style)
|
||||||
|
var(--unuse-border-style) solid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
@use "./page-fragment-select.scss";
|
@use "./page-fragment-select.scss";
|
||||||
@use "./data-source-field.scss";
|
@use "./data-source-field.scss";
|
||||||
@use "./data-source-field-select.scss";
|
@use "./data-source-field-select.scss";
|
||||||
|
@use "./display-conds.scss";
|
||||||
@use "./scroll-bar.scss";
|
@use "./scroll-bar.scss";
|
||||||
@use "./ui-select.scss";
|
@use "./ui-select.scss";
|
||||||
@use "./layer-node-content.scss";
|
@use "./layer-node-content.scss";
|
||||||
|
|||||||
214
packages/editor/src/theme/themes/magic-admin/index.scss
Normal file
214
packages/editor/src/theme/themes/magic-admin/index.scss
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
// magic-admin 主题:所有规则均以 `.m-editor.m-editor--magic-admin` 为前缀,
|
||||||
|
// 仅在 `<MEditor theme="magic-admin" />` 时生效,避免污染默认样式。
|
||||||
|
|
||||||
|
@use "@tmagic/design/src/theme/themes/magic-admin/index.scss" as tMagicDesign;
|
||||||
|
@use "@tmagic/table/src/theme/index.scss" as tMagicTable;
|
||||||
|
@use "@tmagic/form/src/theme/themes/magic-admin/index.scss" as tMagicForm;
|
||||||
|
@use "../../theme.scss";
|
||||||
|
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
}
|
||||||
|
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅作用于编辑器内部 DOM 的具体样式仍保留「双类」,避免误中 portal 上只挂 `m-theme--*`
|
||||||
|
// 的节点。
|
||||||
|
.m-editor.m-editor--magic-admin {
|
||||||
|
// 主题级 CSS 变量入口:想覆盖 nav 高度等 token 时,改这里一行即可;
|
||||||
|
// 走 CSS 级联,运行时切主题(换 `.m-editor--magic-admin` class)即可即时生效,
|
||||||
|
// 不受 SCSS `@use` 加载顺序限制。fallback 值定义在 common/var.scss,未启用主题
|
||||||
|
// 时自动回落 35px。
|
||||||
|
--editor-nav-height: 40px;
|
||||||
|
|
||||||
|
.m-editor-sidebar {
|
||||||
|
.m-editor-sidebar-header {
|
||||||
|
// padding: 8px 0;
|
||||||
|
width: 61px;
|
||||||
|
padding-top: 4px;
|
||||||
|
// sidebar 父容器是 `display: flex` (row),同级 `.m-editor-sidebar-content` 的 width
|
||||||
|
// 是 `calc(100% - 40px)`(sidebar.scss:52,40 是老 header 宽度)。当 header 改成 61px
|
||||||
|
// 后,两者之和会溢出 21px 左右,默认 `flex-shrink: 1` 会按权重压缩 header,最终渲染
|
||||||
|
// 出 56.3px 而非 61px。这里强制 `flex-shrink: 0` 让 header 守住宽度,多余的部分由
|
||||||
|
// content 侧承担(content 的 `100% - 40` 会自然让出)。
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-right: 1px solid rgba(0, 84, 225, 0.1);
|
||||||
|
display: flex;
|
||||||
|
background-color: #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
.m-editor-sidebar-header-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 2px;
|
||||||
|
width: 52px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #84909d;
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
> i {
|
||||||
|
color: #84909d;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
&.is-active {
|
||||||
|
background-color: var(--el-button-hover-bg-color, rgb(230, 238, 253));
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
&:hover {
|
||||||
|
background-color: var(
|
||||||
|
--el-button-hover-bg-color,
|
||||||
|
rgb(230, 238, 253)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
> i {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
.magic-editor-tab-panel-title {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.magic-editor-tab-panel-title {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 0 4px;
|
||||||
|
width: 52px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.m-editor-sidebar-content {
|
||||||
|
width: calc(100% - 61px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.m-editor-props-panel .el-input__wrapper {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-box-container .border-icon-container .border-icon {
|
||||||
|
border-color: #ccc;
|
||||||
|
--unuse-border-style: solid;
|
||||||
|
&.configured {
|
||||||
|
border-color: var(--el-color-success, var(--td-success-color, #2ba471));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
background-color: rgba(0, 84, 225, 0.1);
|
||||||
|
&.center {
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
// 命中 `.border-icon-<side>` 时只保留该侧 2px,其余三边清零。
|
||||||
|
// 用 `@each` 循环避免四面手抄;后续要加禁用、hover 等状态也可以照搬这套结构。
|
||||||
|
@each $side in (top, right, bottom, left) {
|
||||||
|
&.border-icon-#{$side} {
|
||||||
|
border-width: 0;
|
||||||
|
border-#{$side}-width: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.center {
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-container-code-select {
|
||||||
|
> .el-form-item:first-child {
|
||||||
|
> .el-form-item__label:first-child > :not(.m-form-tip) {
|
||||||
|
color: #111;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-editor.m-theme--magic-admin {
|
||||||
|
.m-editor-props-form-panel-form {
|
||||||
|
> .m-container-tab {
|
||||||
|
> .tmagic-design-tabs {
|
||||||
|
> .el-tabs__content {
|
||||||
|
background-color: #fafafa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-editor.m-theme--magic-admin {
|
||||||
|
.m-fields-style-setter {
|
||||||
|
border: 0;
|
||||||
|
.tmagic-design-collapse-item {
|
||||||
|
.el-collapse-item__wrap {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-editor.m-theme--magic-admin {
|
||||||
|
.magic-right-panel-tabs-top {
|
||||||
|
> .el-tabs {
|
||||||
|
> .el-tabs__header {
|
||||||
|
.el-tabs__nav-wrap.is-top {
|
||||||
|
.el-tabs__nav.is-top {
|
||||||
|
.el-tabs__active-bar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.el-tabs__item {
|
||||||
|
&:not(:last-child)::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
height: 12px;
|
||||||
|
border-right: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-editor.m-theme--magic-admin {
|
||||||
|
.m-editor-nav-menu {
|
||||||
|
.menu-right,
|
||||||
|
.menu-left {
|
||||||
|
> .menu-item {
|
||||||
|
> button {
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.menu-item {
|
||||||
|
margin-right: 8px;
|
||||||
|
.el-button--small {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
> button {
|
||||||
|
> i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,6 @@
|
|||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
}
|
}
|
||||||
span {
|
span {
|
||||||
color: #2882e0;
|
color: var(--el-color-primary, #2882e0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -140,6 +140,7 @@ export type BeforeAdd = (config: MNode, parent: MContainer) => Promise<MNode> |
|
|||||||
export type GetConfig = (config: FormConfig) => Promise<FormConfig> | FormConfig;
|
export type GetConfig = (config: FormConfig) => Promise<FormConfig> | FormConfig;
|
||||||
|
|
||||||
export interface EditorInstallOptions {
|
export interface EditorInstallOptions {
|
||||||
|
flat?: boolean;
|
||||||
parseDSL: <T = any>(dsl: string) => T;
|
parseDSL: <T = any>(dsl: string) => T;
|
||||||
customCreateMonacoEditor: (
|
customCreateMonacoEditor: (
|
||||||
monaco: typeof import('monaco-editor'),
|
monaco: typeof import('monaco-editor'),
|
||||||
|
|||||||
@ -16,14 +16,22 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { EditorInstallOptions } from '@editor/type';
|
import { EditorInstallOptions } from '@editor/type';
|
||||||
|
|
||||||
let $TMAGIC_EDITOR: EditorInstallOptions = {} as any;
|
let $TMAGIC_EDITOR: EditorInstallOptions = {} as any;
|
||||||
|
|
||||||
|
// 用 ref 持有 flat 全局开关:const 引用本身不可变,符合 `import/no-mutable-exports`;
|
||||||
|
// 通过 `.value` 改值仍保持模块级响应式语义,与 editor/plugin.ts、design/index.ts 里
|
||||||
|
// 的同名变量写法对齐。
|
||||||
|
const isGlobalFlat = ref(false);
|
||||||
|
|
||||||
const setEditorConfig = (option: EditorInstallOptions): void => {
|
const setEditorConfig = (option: EditorInstallOptions): void => {
|
||||||
$TMAGIC_EDITOR = option;
|
$TMAGIC_EDITOR = option;
|
||||||
|
isGlobalFlat.value = option.flat ?? false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEditorConfig = <K extends keyof EditorInstallOptions>(key: K): EditorInstallOptions[K] => $TMAGIC_EDITOR[key];
|
const getEditorConfig = <K extends keyof EditorInstallOptions>(key: K): EditorInstallOptions[K] => $TMAGIC_EDITOR[key];
|
||||||
|
|
||||||
export { getEditorConfig, setEditorConfig };
|
export { getEditorConfig, isGlobalFlat, setEditorConfig };
|
||||||
|
|||||||
@ -5,10 +5,11 @@ export const LEFT_COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorLeftColumnWidthData';
|
|||||||
export const RIGHT_COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorRightColumnWidthData';
|
export const RIGHT_COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorRightColumnWidthData';
|
||||||
export const PROPS_PANEL_WIDTH_STORAGE_KEY = '$MagicEditorPropsPanelWidthData';
|
export const PROPS_PANEL_WIDTH_STORAGE_KEY = '$MagicEditorPropsPanelWidthData';
|
||||||
|
|
||||||
export const DEFAULT_LEFT_COLUMN_WIDTH = 310;
|
export const DEFAULT_LEFT_COLUMN_WIDTH = 345;
|
||||||
export const DEFAULT_RIGHT_COLUMN_WIDTH = 480;
|
export const DEFAULT_RIGHT_COLUMN_WIDTH = 480;
|
||||||
|
|
||||||
export const MIN_LEFT_COLUMN_WIDTH = 200;
|
// 2026-06-11 设计稿修改,固定左侧栏存在时最小宽度为 345
|
||||||
|
export const MIN_LEFT_COLUMN_WIDTH = 345;
|
||||||
export const MIN_CENTER_COLUMN_WIDTH = 400;
|
export const MIN_CENTER_COLUMN_WIDTH = 400;
|
||||||
export const MIN_RIGHT_COLUMN_WIDTH = 300;
|
export const MIN_RIGHT_COLUMN_WIDTH = 300;
|
||||||
|
|
||||||
|
|||||||
@ -180,6 +180,7 @@ export const advancedTabConfig: TabPaneConfig = {
|
|||||||
name: NODE_DISABLE_CODE_BLOCK_KEY,
|
name: NODE_DISABLE_CODE_BLOCK_KEY,
|
||||||
text: '禁用代码块',
|
text: '禁用代码块',
|
||||||
type: 'switch',
|
type: 'switch',
|
||||||
|
labelPosition: 'left',
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
extra: '开启后,配置的代码块将不会被执行',
|
extra: '开启后,配置的代码块将不会被执行',
|
||||||
},
|
},
|
||||||
@ -187,15 +188,17 @@ export const advancedTabConfig: TabPaneConfig = {
|
|||||||
name: NODE_DISABLE_DATA_SOURCE_KEY,
|
name: NODE_DISABLE_DATA_SOURCE_KEY,
|
||||||
text: '禁用数据源',
|
text: '禁用数据源',
|
||||||
type: 'switch',
|
type: 'switch',
|
||||||
|
labelPosition: 'left',
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
extra: '开启后,组件内配置的数据源相关配置将不会被编译,显隐条件将失效',
|
extra: '开启后,组件内配置的数据源相关配置将不会被编译,显隐条件将失效',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'created',
|
name: 'created',
|
||||||
text: 'created',
|
text: 'Created',
|
||||||
|
flat: true,
|
||||||
labelPosition: 'top',
|
labelPosition: 'top',
|
||||||
type: 'code-select',
|
type: 'code-select',
|
||||||
extra: '组件初始化时执行',
|
titleExtra: '组件初始化时执行',
|
||||||
rules: [
|
rules: [
|
||||||
{ typeMatch: true, trigger: 'change' },
|
{ typeMatch: true, trigger: 'change' },
|
||||||
{
|
{
|
||||||
@ -210,10 +213,12 @@ export const advancedTabConfig: TabPaneConfig = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'mounted',
|
name: 'mounted',
|
||||||
text: 'mounted',
|
text: 'Mounted',
|
||||||
labelPosition: 'top',
|
labelPosition: 'top',
|
||||||
|
flat: true,
|
||||||
|
|
||||||
type: 'code-select',
|
type: 'code-select',
|
||||||
extra: '组件挂载到dom时执行',
|
titleExtra: '组件挂载到dom时执行',
|
||||||
rules: [
|
rules: [
|
||||||
{ typeMatch: true, trigger: 'change' },
|
{ typeMatch: true, trigger: 'change' },
|
||||||
{
|
{
|
||||||
@ -228,8 +233,9 @@ export const advancedTabConfig: TabPaneConfig = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'display',
|
name: 'display',
|
||||||
text: 'display',
|
text: 'Display',
|
||||||
extra: '控制组件是否渲染,关系的代码块返回值为false时不渲染',
|
flat: true,
|
||||||
|
titleExtra: '控制组件是否渲染,关系的代码块返回值为false时不渲染',
|
||||||
labelPosition: 'top',
|
labelPosition: 'top',
|
||||||
type: 'code-select',
|
type: 'code-select',
|
||||||
rules: [
|
rules: [
|
||||||
@ -263,10 +269,18 @@ export const displayTabConfig: TabPaneConfig<DisplayCondsConfig> = {
|
|||||||
extra: (_state, { model }) =>
|
extra: (_state, { model }) =>
|
||||||
`条件成立时${model[NODE_CONDS_RESULT_KEY] ? '隐藏' : '显示'},不成立时${model[NODE_CONDS_RESULT_KEY] ? '显示' : '隐藏'};<br />同一条件组内的所有条件配置同时成立时表示该条件组成立,任意一个条件组成立时表示条件成立(条件组内为且的关系,条件组间为或的关系);<br />条件为空时表示成立;`,
|
`条件成立时${model[NODE_CONDS_RESULT_KEY] ? '隐藏' : '显示'},不成立时${model[NODE_CONDS_RESULT_KEY] ? '显示' : '隐藏'};<br />同一条件组内的所有条件配置同时成立时表示该条件组成立,任意一个条件组成立时表示条件成立(条件组内为且的关系,条件组间为或的关系);<br />条件为空时表示成立;`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: '条件组配置',
|
||||||
|
static: true,
|
||||||
|
className: 'display-conds-title',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'display-conds',
|
type: 'display-conds',
|
||||||
name: NODE_CONDS_KEY,
|
name: NODE_CONDS_KEY,
|
||||||
titlePrefix: '条件组',
|
titlePrefix: '条件组',
|
||||||
|
flat: true,
|
||||||
|
fixed: 'right',
|
||||||
|
operateColWidth: 112,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
rules: [{ typeMatch: true }],
|
rules: [{ typeMatch: true }],
|
||||||
},
|
},
|
||||||
@ -303,10 +317,13 @@ export const fillConfig = (
|
|||||||
name: 'id',
|
name: 'id',
|
||||||
text: 'ID',
|
text: 'ID',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
disabled: true,
|
// 走纯文本渲染,避免出现 disabled 的灰底输入框;append 仍正常显示。
|
||||||
|
static: true,
|
||||||
|
|
||||||
append: {
|
append: {
|
||||||
type: 'button',
|
type: 'icon',
|
||||||
text: '复制',
|
text: 'https://vip.image.video.qpic.cn/vupload/20260615/36cf7e1781493669935.svg',
|
||||||
|
extra: '复制',
|
||||||
handler: (vm, { model }) => {
|
handler: (vm, { model }) => {
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(`${model.id}`)
|
.writeText(`${model.id}`)
|
||||||
@ -339,6 +356,7 @@ export const fillConfig = (
|
|||||||
const tabConfig: TabConfig = {
|
const tabConfig: TabConfig = {
|
||||||
type: 'tab',
|
type: 'tab',
|
||||||
labelWidth,
|
labelWidth,
|
||||||
|
className: 'magic-right-panel-tabs-top',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: '属性',
|
title: '属性',
|
||||||
|
|||||||
@ -44,6 +44,12 @@ vi.mock('@tmagic/design', () => ({
|
|||||||
return () => h('div', { class: 'fake-card' }, slots.default?.());
|
return () => h('div', { class: 'fake-card' }, slots.default?.());
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
TMagicButton: defineComponent({
|
||||||
|
name: 'TMagicButton',
|
||||||
|
setup(_p, { slots }) {
|
||||||
|
return () => h('button', { class: 'fake-button' }, slots.default?.());
|
||||||
|
},
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const baseProps = (extra: any = {}) => ({
|
const baseProps = (extra: any = {}) => ({
|
||||||
@ -108,13 +114,11 @@ describe('CodeSelect', () => {
|
|||||||
expect((props.model.cs as any).hookData).toEqual([]);
|
expect((props.model.cs as any).hookData).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('codeType row items 配置正确', () => {
|
test('codeType items 配置正确', () => {
|
||||||
const wrapper = mount(CodeSelect, { props: baseProps() as any });
|
const wrapper = mount(CodeSelect, { props: baseProps() as any });
|
||||||
const container = wrapper.findComponent({ name: 'MContainer' });
|
const container = wrapper.findComponent({ name: 'MContainer' });
|
||||||
const config = container.props('config') as any;
|
const config = container.props('config') as any;
|
||||||
const row = config.items[0];
|
const codeTypeSelect = config.items[0];
|
||||||
expect(row.type).toBe('row');
|
|
||||||
const codeTypeSelect = row.items[0];
|
|
||||||
expect(codeTypeSelect.name).toBe('codeType');
|
expect(codeTypeSelect.name).toBe('codeType');
|
||||||
const setModel = vi.fn();
|
const setModel = vi.fn();
|
||||||
codeTypeSelect.onChange(undefined, 'data-source-method', { setModel });
|
codeTypeSelect.onChange(undefined, 'data-source-method', { setModel });
|
||||||
@ -128,9 +132,8 @@ describe('CodeSelect', () => {
|
|||||||
const wrapper = mount(CodeSelect, { props: baseProps() as any });
|
const wrapper = mount(CodeSelect, { props: baseProps() as any });
|
||||||
const container = wrapper.findComponent({ name: 'MContainer' });
|
const container = wrapper.findComponent({ name: 'MContainer' });
|
||||||
const config = container.props('config') as any;
|
const config = container.props('config') as any;
|
||||||
const row = config.items[0];
|
const codeIdCol = config.items[1];
|
||||||
const codeIdCol = row.items[1];
|
const dsCol = config.items[2];
|
||||||
const dsCol = row.items[2];
|
|
||||||
expect(codeIdCol.display(undefined, { model: { codeType: 'code' } })).toBe(true);
|
expect(codeIdCol.display(undefined, { model: { codeType: 'code' } })).toBe(true);
|
||||||
expect(codeIdCol.display(undefined, { model: { codeType: 'data-source-method' } })).toBe(false);
|
expect(codeIdCol.display(undefined, { model: { codeType: 'data-source-method' } })).toBe(false);
|
||||||
expect(dsCol.display(undefined, { model: { codeType: 'data-source-method' } })).toBe(true);
|
expect(dsCol.display(undefined, { model: { codeType: 'data-source-method' } })).toBe(true);
|
||||||
@ -141,9 +144,8 @@ describe('CodeSelect', () => {
|
|||||||
const wrapper = mount(CodeSelect, { props: baseProps() as any });
|
const wrapper = mount(CodeSelect, { props: baseProps() as any });
|
||||||
const container = wrapper.findComponent({ name: 'MContainer' });
|
const container = wrapper.findComponent({ name: 'MContainer' });
|
||||||
const config = container.props('config') as any;
|
const config = container.props('config') as any;
|
||||||
const row = config.items[0];
|
const codeIdCol = config.items[1];
|
||||||
const codeIdCol = row.items[1];
|
const dsCol = config.items[2];
|
||||||
const dsCol = row.items[2];
|
|
||||||
expect(codeIdCol.type).toBe('code-select-col');
|
expect(codeIdCol.type).toBe('code-select-col');
|
||||||
expect(codeIdCol.rules).toEqual([{ typeMatch: true, trigger: 'change' }]);
|
expect(codeIdCol.rules).toEqual([{ typeMatch: true, trigger: 'change' }]);
|
||||||
expect(dsCol.type).toBe('data-source-method-select');
|
expect(dsCol.type).toBe('data-source-method-select');
|
||||||
@ -156,9 +158,8 @@ describe('CodeSelect', () => {
|
|||||||
const wrapper = mount(CodeSelect, { props: baseProps() as any });
|
const wrapper = mount(CodeSelect, { props: baseProps() as any });
|
||||||
const container = wrapper.findComponent({ name: 'MContainer' });
|
const container = wrapper.findComponent({ name: 'MContainer' });
|
||||||
const config = container.props('config') as any;
|
const config = container.props('config') as any;
|
||||||
const row = config.items[0];
|
expect(config.items[1].notEditable()).toBe(true);
|
||||||
expect(row.items[1].notEditable()).toBe(true);
|
expect(config.items[2].notEditable()).toBe(true);
|
||||||
expect(row.items[2].notEditable()).toBe(true);
|
|
||||||
codeBlockService.getEditStatus.mockReturnValue(true);
|
codeBlockService.getEditStatus.mockReturnValue(true);
|
||||||
dataSourceService.get.mockReturnValue(true);
|
dataSourceService.get.mockReturnValue(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -526,9 +526,11 @@ describe('EventSelect', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
const wrapper = mount(EventSelect, { props: baseProps({ model: m }) as any });
|
const wrapper = mount(EventSelect, { props: baseProps({ model: m }) as any });
|
||||||
const buttons = wrapper.findAll('button');
|
// 用 class 选择器直击 panel header 里的删除按钮:模板里同时存在顶部 / 底部「添加事件」按钮,
|
||||||
const deleteBtn = buttons[buttons.length - 1];
|
// 早期靠 `buttons[length - 1]` 取最后一个会误选到底部添加按钮,导致 events 没被删减。
|
||||||
await deleteBtn.trigger('click');
|
const deleteBtns = wrapper.findAll('.event-item-delete-button');
|
||||||
|
expect(deleteBtns.length).toBe(m.events.length);
|
||||||
|
await deleteBtns[deleteBtns.length - 1].trigger('click');
|
||||||
expect(m.events.length).toBe(1);
|
expect(m.events.length).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
ElAlert,
|
||||||
ElAutocomplete,
|
ElAutocomplete,
|
||||||
ElBadge,
|
ElBadge,
|
||||||
ElButton,
|
ElButton,
|
||||||
@ -48,6 +49,7 @@ import {
|
|||||||
} from 'element-plus';
|
} from 'element-plus';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
AlertProps,
|
||||||
AutocompleteProps,
|
AutocompleteProps,
|
||||||
BadgeProps,
|
BadgeProps,
|
||||||
ButtonProps,
|
ButtonProps,
|
||||||
@ -104,6 +106,11 @@ const adapter: DesignPluginOptions = {
|
|||||||
props: (props: AutocompleteProps) => props,
|
props: (props: AutocompleteProps) => props,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
alert: {
|
||||||
|
component: ElAlert as any,
|
||||||
|
props: (props: AlertProps) => props,
|
||||||
|
},
|
||||||
|
|
||||||
badge: {
|
badge: {
|
||||||
component: ElBadge as any,
|
component: ElBadge as any,
|
||||||
props: (props: BadgeProps) => props,
|
props: (props: BadgeProps) => props,
|
||||||
@ -261,7 +268,7 @@ const adapter: DesignPluginOptions = {
|
|||||||
|
|
||||||
select: {
|
select: {
|
||||||
component: ElSelect as any,
|
component: ElSelect as any,
|
||||||
props: (props: SelectProps) => props,
|
props: (props: SelectProps) => ({ remoteShowSuffix: true, ...props }),
|
||||||
},
|
},
|
||||||
|
|
||||||
step: {
|
step: {
|
||||||
|
|||||||
@ -50,6 +50,9 @@ export interface FieldProps<T = any> {
|
|||||||
size?: 'large' | 'default' | 'small';
|
size?: 'large' | 'default' | 'small';
|
||||||
lastValues?: Record<string, any>;
|
lastValues?: Record<string, any>;
|
||||||
isCompare?: boolean;
|
isCompare?: boolean;
|
||||||
|
text?: string;
|
||||||
|
labelWidth?: string | number;
|
||||||
|
labelPosition?: 'top' | 'left' | 'right';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,6 +113,8 @@ export interface FormItem {
|
|||||||
name?: string | number;
|
name?: string | number;
|
||||||
/** 额外的提示信息,和 help 类似,当提示文案同时出现时,可以使用这个。 */
|
/** 额外的提示信息,和 help 类似,当提示文案同时出现时,可以使用这个。 */
|
||||||
extra?: string | FilterFunction<string>;
|
extra?: string | FilterFunction<string>;
|
||||||
|
/** 额外的提示信息,和 extra 类似,着重强调时用这个显示,优先级高于extra*/
|
||||||
|
extraTips?: string;
|
||||||
/** 配置提示信息 */
|
/** 配置提示信息 */
|
||||||
tooltip?: ToolTipConfigType | FilterFunction<ToolTipConfigType>;
|
tooltip?: ToolTipConfigType | FilterFunction<ToolTipConfigType>;
|
||||||
/** 是否置灰 */
|
/** 是否置灰 */
|
||||||
@ -130,6 +135,8 @@ export interface FormItem {
|
|||||||
trim?: boolean;
|
trim?: boolean;
|
||||||
/** 默认值 */
|
/** 默认值 */
|
||||||
defaultValue?: any | DefaultValueFunction;
|
defaultValue?: any | DefaultValueFunction;
|
||||||
|
/** 标题下方的额外提示信息 */
|
||||||
|
titleExtra?: string;
|
||||||
/** 表单验证规则 */
|
/** 表单验证规则 */
|
||||||
rules?: Rule[];
|
rules?: Rule[];
|
||||||
extensible?: boolean;
|
extensible?: boolean;
|
||||||
@ -139,6 +146,9 @@ export interface FormItem {
|
|||||||
style?: Record<string, any>;
|
style?: Record<string, any>;
|
||||||
fieldStyle?: Record<string, any>;
|
fieldStyle?: Record<string, any>;
|
||||||
labelPosition?: 'top' | 'left' | 'right';
|
labelPosition?: 'top' | 'left' | 'right';
|
||||||
|
flat?: boolean;
|
||||||
|
fixed?: boolean | 'left' | 'right';
|
||||||
|
operateColWidth?: number | string;
|
||||||
}
|
}
|
||||||
// #endregion FormItem
|
// #endregion FormItem
|
||||||
|
|
||||||
@ -386,6 +396,12 @@ export interface TextConfig extends FormItem, Input {
|
|||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
/** 是否可清空 */
|
/** 是否可清空 */
|
||||||
clearable?: boolean;
|
clearable?: boolean;
|
||||||
|
/**
|
||||||
|
* 是否以纯文本(非输入框)形态渲染。
|
||||||
|
* 开启后字段值以 `<span>` 展示,不参与编辑,但保留 `prepend` / `append` 槽位,
|
||||||
|
* 适合用于「只读但需要保留操作按钮(如复制 icon)」的场景。
|
||||||
|
*/
|
||||||
|
static?: boolean;
|
||||||
prepend?: string;
|
prepend?: string;
|
||||||
/** 后置元素,一般为标签或按钮 */
|
/** 后置元素,一般为标签或按钮 */
|
||||||
append?:
|
append?:
|
||||||
@ -393,7 +409,8 @@ export interface TextConfig extends FormItem, Input {
|
|||||||
| {
|
| {
|
||||||
text: string;
|
text: string;
|
||||||
value?: 0 | 1;
|
value?: 0 | 1;
|
||||||
type: 'button';
|
type: 'button' | 'icon';
|
||||||
|
extra?: string;
|
||||||
handler?: (
|
handler?: (
|
||||||
mForm: FormState | undefined,
|
mForm: FormState | undefined,
|
||||||
data: {
|
data: {
|
||||||
@ -529,6 +546,7 @@ export interface SwitchConfig extends FormItem {
|
|||||||
export interface RadioGroupConfig extends FormItem {
|
export interface RadioGroupConfig extends FormItem {
|
||||||
type: 'radio-group' | 'radioGroup';
|
type: 'radio-group' | 'radioGroup';
|
||||||
childType?: 'default' | 'button';
|
childType?: 'default' | 'button';
|
||||||
|
iconSize?: string;
|
||||||
options: {
|
options: {
|
||||||
value: string | number | boolean;
|
value: string | number | boolean;
|
||||||
text?: string;
|
text?: string;
|
||||||
@ -718,6 +736,7 @@ export interface TabPaneConfig<T = never> {
|
|||||||
title: string;
|
title: string;
|
||||||
lazy?: boolean;
|
lazy?: boolean;
|
||||||
labelWidth?: string;
|
labelWidth?: string;
|
||||||
|
titleExtra?: string;
|
||||||
items: FormConfig<T>;
|
items: FormConfig<T>;
|
||||||
display?: boolean | 'expand' | FilterFunction<boolean | 'expand'>;
|
display?: boolean | 'expand' | FilterFunction<boolean | 'expand'>;
|
||||||
onTabClick?: (mForm: FormState | undefined, tab: any, data: any) => void;
|
onTabClick?: (mForm: FormState | undefined, tab: any, data: any) => void;
|
||||||
@ -772,6 +791,11 @@ export interface PanelConfig<T = never> extends FormItem, ContainerCommonConfig<
|
|||||||
expand?: boolean;
|
expand?: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
schematic?: string;
|
schematic?: string;
|
||||||
|
/**
|
||||||
|
* 是否「铺平」:去掉 panel 自身的卡片视觉(背景 / 阴影 / 边框 / 圆角 / 内边距等),
|
||||||
|
* 让 panel 在视觉上与外层容器融为一体。仍保留 header、折叠等交互。
|
||||||
|
*/
|
||||||
|
flat?: boolean;
|
||||||
}
|
}
|
||||||
// #endregion PanelConfig
|
// #endregion PanelConfig
|
||||||
|
|
||||||
@ -825,6 +849,8 @@ export interface TableConfig<T = never> extends TableGroupListCommonConfig {
|
|||||||
/** 操作栏宽度 */
|
/** 操作栏宽度 */
|
||||||
operateColWidth?: number | string;
|
operateColWidth?: number | string;
|
||||||
pagination?: boolean;
|
pagination?: boolean;
|
||||||
|
/** 表格标题前缀 ,新增xxx,暂无xxx数据,表明表格内容是什么 */
|
||||||
|
titlePrefix?: string;
|
||||||
/** 是否显示删除按钮 */
|
/** 是否显示删除按钮 */
|
||||||
delete?: (model: any, index: number, values: any) => boolean | boolean;
|
delete?: (model: any, index: number, values: any) => boolean | boolean;
|
||||||
copyable?: (model: any, data: any) => boolean | boolean;
|
copyable?: (model: any, data: any) => boolean | boolean;
|
||||||
@ -865,6 +891,13 @@ export interface GroupListConfig<T = never> extends TableGroupListCommonConfig {
|
|||||||
title?: string | FilterFunction<string>;
|
title?: string | FilterFunction<string>;
|
||||||
itemExtra?: string | FilterFunction<string>;
|
itemExtra?: string | FilterFunction<string>;
|
||||||
expandAll?: boolean;
|
expandAll?: boolean;
|
||||||
|
/**
|
||||||
|
* 铺平模式:去除每个 item 外层卡片视觉(背景、阴影、顶/左/右边框、header 分隔线、内边距),
|
||||||
|
* 但保留标题 + 删除/复制/上/下移/移动至等头部按钮。适合外层已有自己的视觉容器、
|
||||||
|
* 不希望再嵌套一层 card 的场景(例如 `EventSelect` 里的 actions 列表)。
|
||||||
|
* item 之间仍保留底部分隔线,最后一项除外。
|
||||||
|
*/
|
||||||
|
flat?: boolean;
|
||||||
/**
|
/**
|
||||||
* 默认展开的数量,用于控制分组列表默认展示的项数
|
* 默认展开的数量,用于控制分组列表默认展示的项数
|
||||||
* 当设置为数字时,表示默认展开指定数量的项
|
* 当设置为数字时,表示默认展开指定数量的项
|
||||||
|
|||||||
@ -70,6 +70,7 @@ export interface CodeSelectConfig extends FormItem {
|
|||||||
// #region CodeSelectColConfig
|
// #region CodeSelectColConfig
|
||||||
export interface CodeSelectColConfig extends FormItem {
|
export interface CodeSelectColConfig extends FormItem {
|
||||||
type: 'code-select-col';
|
type: 'code-select-col';
|
||||||
|
|
||||||
/** 是否可以编辑代码块,disable表示的是是否可以选择代码块 */
|
/** 是否可以编辑代码块,disable表示的是是否可以选择代码块 */
|
||||||
notEditable?: boolean | FilterFunction;
|
notEditable?: boolean | FilterFunction;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<TMagicForm
|
<TMagicForm
|
||||||
class="m-form"
|
:class="[
|
||||||
|
'm-form',
|
||||||
|
effectiveTheme ? `m-form--${effectiveTheme}` : '',
|
||||||
|
effectiveTheme ? `m-theme--${effectiveTheme}` : '',
|
||||||
|
]"
|
||||||
ref="tMagicForm"
|
ref="tMagicForm"
|
||||||
:model="values"
|
:model="values"
|
||||||
:label-width="labelWidth"
|
:label-width="labelWidth"
|
||||||
@ -32,10 +36,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, provide, reactive, ref, shallowRef, toRaw, useTemplateRef, watch, watchEffect } from 'vue';
|
import {
|
||||||
|
computed,
|
||||||
|
inject,
|
||||||
|
nextTick,
|
||||||
|
provide,
|
||||||
|
reactive,
|
||||||
|
ref,
|
||||||
|
shallowRef,
|
||||||
|
toRaw,
|
||||||
|
useTemplateRef,
|
||||||
|
watch,
|
||||||
|
watchEffect,
|
||||||
|
} from 'vue';
|
||||||
import { cloneDeep, isEqual } from 'lodash-es';
|
import { cloneDeep, isEqual } from 'lodash-es';
|
||||||
|
|
||||||
import { TMagicForm, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
import { M_THEME_KEY, TMagicForm, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
||||||
import { setValueByKeyPath } from '@tmagic/utils';
|
import { setValueByKeyPath } from '@tmagic/utils';
|
||||||
|
|
||||||
import Container from './containers/Container.vue';
|
import Container from './containers/Container.vue';
|
||||||
@ -116,6 +132,15 @@ const props = withDefaults(
|
|||||||
* 通过 provide 下发,对整棵表单的所有层级 Container 生效,只需在 MForm 这一层传一次。
|
* 通过 provide 下发,对整棵表单的所有层级 Container 生效,只需在 MForm 这一层传一次。
|
||||||
*/
|
*/
|
||||||
selfDiffFieldTypes?: string[] | ((_defaultTypes: string[]) => string[]);
|
selfDiffFieldTypes?: string[] | ((_defaultTypes: string[]) => string[]);
|
||||||
|
/**
|
||||||
|
* 主题名称:对应 `packages/form/src/theme/themes/<theme>/index.scss` 的目录名。
|
||||||
|
*
|
||||||
|
* 设置后会在表单根元素上追加 `m-form--<theme>` 修饰类,配合按需引入
|
||||||
|
* `@tmagic/form/dist/themes/<theme>.css` 即可启用主题样式。
|
||||||
|
*
|
||||||
|
* 例如:`<MForm theme="magic-admin" />` + `import '@tmagic/form/dist/themes/magic-admin.css'`。
|
||||||
|
*/
|
||||||
|
theme?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
config: () => [],
|
config: () => [],
|
||||||
@ -149,6 +174,27 @@ const fields = new Map<string, any>();
|
|||||||
|
|
||||||
const requestFuc = getConfig('request') as Function;
|
const requestFuc = getConfig('request') as Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前表单生效的主题名称:
|
||||||
|
* - 优先用本组件自己的 `props.theme`;
|
||||||
|
* - 没设置时回退到最近祖先 `<MEditor>` / `<MForm>` provide 的主题,便于内嵌于编辑器
|
||||||
|
* 时自动跟随外层主题,无需在每个 `MForm` 上重复传 `theme`。
|
||||||
|
*
|
||||||
|
* 同时把合并后的值再 provide 出去(见下方 `provide(M_THEME_KEY, ...)`),让 form 子树
|
||||||
|
* 里再嵌套的 portal 组件(`TMagicPopover` 等)依然能拿到非空主题。
|
||||||
|
*/
|
||||||
|
const ancestorTheme = inject(M_THEME_KEY, null);
|
||||||
|
const effectiveTheme = computed(() => props.theme || ancestorTheme?.value || '');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拼到 `formState.popperClass` 上的主题修饰类(仅 `m-theme--<theme>`,
|
||||||
|
* 不带 `m-form` / `m-editor` 前缀,因为 Element Plus 弹层节点本身既不是 form 也不是 editor)。
|
||||||
|
*
|
||||||
|
* 这条类会随所有读 `mForm.popperClass` 的字段(Select / DateTime / Cascader 等)下发到
|
||||||
|
* Element Plus 的 `popper-class`,让 portal 节点也命中 `m-theme--<theme>` 上的 CSS 变量。
|
||||||
|
*/
|
||||||
|
const themeClass = computed(() => (effectiveTheme.value ? `m-theme--${effectiveTheme.value}` : ''));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* formState 实现说明:
|
* formState 实现说明:
|
||||||
*
|
*
|
||||||
@ -165,13 +211,21 @@ const requestFuc = getConfig('request') as Function;
|
|||||||
* 把最新值刷进 formState。
|
* 把最新值刷进 formState。
|
||||||
* - accessor 描述符(`{ get stage() { return ... } }`)按原样写入,调用方可以控制
|
* - accessor 描述符(`{ get stage() { return ... } }`)按原样写入,调用方可以控制
|
||||||
* 读时求值,每次读取都会重新执行 getter。
|
* 读时求值,每次读取都会重新执行 getter。
|
||||||
|
*
|
||||||
|
* 4. `popperClass` 会自动拼接 `themeClass`:调用方传入的 `popperClass` + 当前主题
|
||||||
|
* 修饰类(含祖先 `<MEditor>` provide 的主题)。这样所有透传到 Element Plus 弹层
|
||||||
|
* `popper-class` 的字段(Select / DateTime / Cascader 等)能自带主题作用域。
|
||||||
*/
|
*/
|
||||||
const formState: FormState = reactive<FormState>({
|
const formState: FormState = reactive<FormState>({
|
||||||
get keyProp() {
|
get keyProp() {
|
||||||
return props.keyProp;
|
return props.keyProp;
|
||||||
},
|
},
|
||||||
get popperClass() {
|
get popperClass() {
|
||||||
return props.popperClass;
|
const userClass = props.popperClass ?? '';
|
||||||
|
const tc = themeClass.value;
|
||||||
|
if (!userClass) return tc;
|
||||||
|
if (!tc) return userClass;
|
||||||
|
return `${userClass} ${tc}`;
|
||||||
},
|
},
|
||||||
get config() {
|
get config() {
|
||||||
return props.config;
|
return props.config;
|
||||||
@ -250,6 +304,13 @@ watchEffect(async (onCleanup) => {
|
|||||||
|
|
||||||
provide('mForm', formState);
|
provide('mForm', formState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把生效主题(自身或祖先)再 provide 出去,供 form 子树内含 `Teleport` 的组件
|
||||||
|
* (如 `TMagicPopover`)在传送目标上挂 `m-theme--<theme>` 类。
|
||||||
|
* 详见 `@tmagic/design/theme.ts`。
|
||||||
|
*/
|
||||||
|
provide(M_THEME_KEY, effectiveTheme);
|
||||||
|
provide('formInline', props.inline);
|
||||||
// 对比相关配置单独通过 provide 下发,所有层级的 Container 通过 inject 获取,无需逐层透传 prop。
|
// 对比相关配置单独通过 provide 下发,所有层级的 Container 通过 inject 获取,无需逐层透传 prop。
|
||||||
// 用 getter 对象保证读取时回到最新的 props 值,维持响应式。
|
// 用 getter 对象保证读取时回到最新的 props 值,维持响应式。
|
||||||
provide(FORM_DIFF_CONFIG_KEY, {
|
provide(FORM_DIFF_CONFIG_KEY, {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<TMagicDialog
|
<TMagicDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
class="m-form-dialog"
|
:class="['m-form-dialog', effectiveTheme ? `m-theme--${effectiveTheme}` : '']"
|
||||||
top="20px"
|
top="20px"
|
||||||
append-to-body
|
append-to-body
|
||||||
:title="title"
|
:title="title"
|
||||||
@ -34,6 +34,7 @@
|
|||||||
:use-field-text-in-error="useFieldTextInError"
|
:use-field-text-in-error="useFieldTextInError"
|
||||||
:type-match-valid="typeMatchValid"
|
:type-match-valid="typeMatchValid"
|
||||||
:extend-state="extendState"
|
:extend-state="extendState"
|
||||||
|
:theme="effectiveTheme"
|
||||||
@change="changeHandler"
|
@change="changeHandler"
|
||||||
></Form>
|
></Form>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
@ -66,9 +67,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { computed, inject, provide, ref } from 'vue';
|
||||||
|
|
||||||
import { TMagicButton, TMagicCol, TMagicDialog, TMagicRow } from '@tmagic/design';
|
import { M_THEME_KEY, TMagicButton, TMagicCol, TMagicDialog, TMagicRow } from '@tmagic/design';
|
||||||
|
|
||||||
import Form from './Form.vue';
|
import Form from './Form.vue';
|
||||||
import { ContainerChangeEventData, FormConfig, FormState, FormValue, StepConfig } from './schema';
|
import { ContainerChangeEventData, FormConfig, FormState, FormValue, StepConfig } from './schema';
|
||||||
@ -104,6 +105,12 @@ const props = withDefaults(
|
|||||||
useFieldTextInError?: boolean;
|
useFieldTextInError?: boolean;
|
||||||
/** 透传给内部 `MForm`,用于扩展 `formState`(如注入 `$message` / `$store` 等) */
|
/** 透传给内部 `MForm`,用于扩展 `formState`(如注入 `$message` / `$store` 等) */
|
||||||
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
||||||
|
/**
|
||||||
|
* 主题名。优先级:传入 `theme` prop > 祖先 `provide(M_THEME_KEY)` > 空串。
|
||||||
|
* 计算结果会再次 `provide` 出去,使得 Dialog 被 Teleport 到 body 后,内部子树
|
||||||
|
* (包括 `MForm` 以及更深的 popover / dropdown)仍能拿到主题。
|
||||||
|
*/
|
||||||
|
theme?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
config: () => [],
|
config: () => [],
|
||||||
@ -118,6 +125,10 @@ const props = withDefaults(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ancestorTheme = inject(M_THEME_KEY, null);
|
||||||
|
const effectiveTheme = computed(() => props.theme || ancestorTheme?.value || '');
|
||||||
|
provide(M_THEME_KEY, effectiveTheme);
|
||||||
|
|
||||||
const emit = defineEmits(['close', 'submit', 'error', 'change']);
|
const emit = defineEmits(['close', 'submit', 'error', 'change']);
|
||||||
|
|
||||||
const form = ref<InstanceType<typeof Form>>();
|
const form = ref<InstanceType<typeof Form>>();
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<TMagicDrawer
|
<TMagicDrawer
|
||||||
class="m-form-drawer"
|
:class="['m-form-drawer', effectiveTheme ? `m-theme--${effectiveTheme}` : '']"
|
||||||
ref="drawer"
|
ref="drawer"
|
||||||
v-model="visible"
|
v-model="dialogVisible"
|
||||||
:title="title"
|
:title="title"
|
||||||
:close-on-press-escape="closeOnPressEscape"
|
:close-on-press-escape="closeOnPressEscape"
|
||||||
:append-to-body="true"
|
:append-to-body="true"
|
||||||
@ -16,7 +16,7 @@
|
|||||||
@close="closeHandler"
|
@close="closeHandler"
|
||||||
@closed="closedHandler"
|
@closed="closedHandler"
|
||||||
>
|
>
|
||||||
<div v-if="visible" ref="drawerBody" class="m-drawer-body">
|
<div v-if="dialogVisible" ref="drawerBody" class="m-drawer-body">
|
||||||
<Form
|
<Form
|
||||||
ref="form"
|
ref="form"
|
||||||
:size="size"
|
:size="size"
|
||||||
@ -31,6 +31,7 @@
|
|||||||
:use-field-text-in-error="useFieldTextInError"
|
:use-field-text-in-error="useFieldTextInError"
|
||||||
:type-match-valid="typeMatchValid"
|
:type-match-valid="typeMatchValid"
|
||||||
:extend-state="extendState"
|
:extend-state="extendState"
|
||||||
|
:theme="effectiveTheme"
|
||||||
@change="changeHandler"
|
@change="changeHandler"
|
||||||
></Form>
|
></Form>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
@ -57,18 +58,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watchEffect } from 'vue';
|
import { computed, inject, provide, ref, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { TMagicButton, TMagicCol, TMagicDrawer, TMagicRow } from '@tmagic/design';
|
import { M_THEME_KEY, TMagicButton, TMagicCol, TMagicDrawer, TMagicRow } from '@tmagic/design';
|
||||||
|
|
||||||
import Form from './Form.vue';
|
import Form from './Form.vue';
|
||||||
import type { ContainerChangeEventData, FormConfig, FormState, FormValue } from './schema';
|
import type { ContainerChangeEventData, FormConfig, FormState, FormValue } from './schema';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'MFormDialog',
|
name: 'MFormDrawer',
|
||||||
});
|
});
|
||||||
|
|
||||||
withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
config?: FormConfig;
|
config?: FormConfig;
|
||||||
values?: Object;
|
values?: Object;
|
||||||
@ -92,6 +93,12 @@ withDefaults(
|
|||||||
beforeClose?: (_done: (_cancel?: boolean) => void) => void;
|
beforeClose?: (_done: (_cancel?: boolean) => void) => void;
|
||||||
/** 透传给内部 `MForm`,用于扩展 `formState`(如注入 `$message` / `$store` 等) */
|
/** 透传给内部 `MForm`,用于扩展 `formState`(如注入 `$message` / `$store` 等) */
|
||||||
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
||||||
|
/**
|
||||||
|
* 主题名。优先级:传入 `theme` prop > 祖先 `provide(M_THEME_KEY)` > 空串。
|
||||||
|
* 计算结果会再次 `provide` 出去,使得 Drawer 被 Teleport 到 body 后,内部子树
|
||||||
|
* (包括 `MForm` 以及更深的 popover / dropdown)仍能拿到主题。
|
||||||
|
*/
|
||||||
|
theme?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
closeOnPressEscape: true,
|
closeOnPressEscape: true,
|
||||||
@ -102,12 +109,16 @@ withDefaults(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ancestorTheme = inject(M_THEME_KEY, null);
|
||||||
|
const effectiveTheme = computed(() => props.theme || ancestorTheme?.value || '');
|
||||||
|
provide(M_THEME_KEY, effectiveTheme);
|
||||||
|
|
||||||
const emit = defineEmits(['close', 'closed', 'submit', 'error', 'change', 'open', 'opened']);
|
const emit = defineEmits(['close', 'closed', 'submit', 'error', 'change', 'open', 'opened']);
|
||||||
|
|
||||||
const drawer = ref<InstanceType<typeof TMagicDrawer>>();
|
const drawer = ref<InstanceType<typeof TMagicDrawer>>();
|
||||||
const form = ref<InstanceType<typeof Form>>();
|
const form = ref<InstanceType<typeof Form>>();
|
||||||
const drawerBody = ref<HTMLDivElement>();
|
const drawerBody = ref<HTMLDivElement>();
|
||||||
const visible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
const saveFetch = ref(false);
|
const saveFetch = ref(false);
|
||||||
const bodyHeight = ref(0);
|
const bodyHeight = ref(0);
|
||||||
|
|
||||||
@ -148,25 +159,42 @@ const closedHandler = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const show = () => {
|
const show = () => {
|
||||||
visible.value = true;
|
dialogVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const hide = () => {
|
const hide = () => {
|
||||||
visible.value = false;
|
dialogVisible.value = false;
|
||||||
|
};
|
||||||
|
const close = () => {
|
||||||
|
dialogVisible.value = false;
|
||||||
|
};
|
||||||
|
const cancel = () => {
|
||||||
|
hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 用于关闭 Drawer, 该方法会调用传入的 before-close 方法 */
|
/** 用于关闭 Drawer, 该方法会调用传入的 before-close 方法 */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
drawer.value?.handleClose();
|
drawer.value?.handleClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
try {
|
||||||
|
const changeRecords = [...(form.value?.changeRecords || [])];
|
||||||
|
const values = await form.value?.submitForm();
|
||||||
|
emit('submit', values, { changeRecords });
|
||||||
|
} catch (e) {
|
||||||
|
emit('error', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
form,
|
form,
|
||||||
saveFetch,
|
saveFetch,
|
||||||
bodyHeight,
|
bodyHeight,
|
||||||
|
close,
|
||||||
show,
|
show,
|
||||||
hide,
|
hide,
|
||||||
|
save,
|
||||||
|
cancel,
|
||||||
handleClose,
|
handleClose,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -35,6 +35,9 @@
|
|||||||
:label-title="config.labelTitle"
|
:label-title="config.labelTitle"
|
||||||
:text="text"
|
:text="text"
|
||||||
></FormLabel>
|
></FormLabel>
|
||||||
|
<span class="m-form-tip m-form-title-extra" v-if="config.titleExtra && config.labelPosition === 'top'">
|
||||||
|
{{ config.titleExtra }}</span
|
||||||
|
>
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -222,17 +225,23 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div style="text-align: center" v-if="config.expand && type !== 'fieldset'">
|
<div class="m-form-container-expand" v-if="config.expand && type !== 'fieldset'">
|
||||||
<TMagicButton type="primary" size="small" :disabled="false" link @click="expandHandler">{{
|
<TMagicButton
|
||||||
expand ? '收起配置' : '展开更多配置'
|
type="primary"
|
||||||
}}</TMagicButton>
|
size="small"
|
||||||
|
:disabled="false"
|
||||||
|
link
|
||||||
|
@click="expandHandler"
|
||||||
|
:icon="expand ? ArrowUp : ArrowDown"
|
||||||
|
>{{ expand ? '收起配置' : '展开更多配置' }}</TMagicButton
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, inject, readonly, ref, toRaw, watch, watchEffect } from 'vue';
|
import { computed, inject, readonly, ref, toRaw, watch, watchEffect } from 'vue';
|
||||||
import { WarningFilled } from '@element-plus/icons-vue';
|
import { ArrowDown, ArrowUp, WarningFilled } from '@element-plus/icons-vue';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
import { TMagicButton, TMagicFormItem, TMagicIcon, TMagicTooltip } from '@tmagic/design';
|
import { TMagicButton, TMagicFormItem, TMagicIcon, TMagicTooltip } from '@tmagic/design';
|
||||||
@ -440,8 +449,10 @@ const formItemProps = computed(() => ({
|
|||||||
prop: itemProp.value,
|
prop: itemProp.value,
|
||||||
labelWidth: itemLabelWidth.value,
|
labelWidth: itemLabelWidth.value,
|
||||||
labelPosition: props.config.labelPosition,
|
labelPosition: props.config.labelPosition,
|
||||||
|
|
||||||
rules: rule.value,
|
rules: rule.value,
|
||||||
extra: filterFunction(mForm, props.config.extra, props),
|
extra: filterFunction(mForm, props.config.extra, props),
|
||||||
|
extraTips: props.config.extraTips,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const itemLabelWidth = computed(() => props.config.labelWidth ?? props.labelWidth);
|
const itemLabelWidth = computed(() => props.config.labelWidth ?? props.labelWidth);
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="m-fields-group-list">
|
<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="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">
|
<div v-if="!model[name] || !model[name].length" class="el-table__empty-block">
|
||||||
<span class="el-table__empty-text t-table__empty">暂无数据</span>
|
<span class="el-table__empty-text t-table__empty">暂无{{ config.titlePrefix || '' }}数据</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MFieldsGroupListItem
|
<MFieldsGroupListItem
|
||||||
|
|||||||
@ -1,26 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<TMagicCard class="m-fields-group-list-item" :body-style="{ display: expand ? 'block' : 'none' }">
|
<TMagicCard class="m-fields-group-list-item" :flat="config.flat" :body-style="{ display: expand ? 'block' : 'none' }">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div>
|
<div class="m-fields-group-list-item-header">
|
||||||
<TMagicButton link :disabled="disabled" @click="expandHandler">
|
<TMagicButton link :disabled="disabled" @click="expandHandler" class="expand-button">
|
||||||
<TMagicIcon><CaretBottom v-if="expand" /><CaretRight v-else /></TMagicIcon><span v-html="title"></span>
|
<TMagicIcon><ArrowDown v-if="expand" /><ArrowRight v-else /></TMagicIcon>
|
||||||
</TMagicButton>
|
</TMagicButton>
|
||||||
|
|
||||||
<TMagicButton
|
<span v-html="title"></span>
|
||||||
v-if="!isCompare"
|
<TMagicTooltip :content="`删除 ${title}`">
|
||||||
v-show="showDelete"
|
<TMagicButton
|
||||||
type="danger"
|
v-if="!isCompare"
|
||||||
size="small"
|
v-show="showDelete"
|
||||||
link
|
size="default"
|
||||||
:icon="Delete"
|
link
|
||||||
:disabled="disabled"
|
class="delete-button"
|
||||||
@click="removeHandler"
|
:icon="Delete"
|
||||||
></TMagicButton>
|
:disabled="disabled"
|
||||||
|
@click="removeHandler"
|
||||||
|
></TMagicButton>
|
||||||
|
</TMagicTooltip>
|
||||||
|
|
||||||
<TMagicButton
|
<TMagicButton
|
||||||
v-if="copyable && !isCompare"
|
v-if="copyable && !isCompare"
|
||||||
link
|
link
|
||||||
size="small"
|
size="default"
|
||||||
type="primary"
|
type="primary"
|
||||||
:icon="DocumentCopy"
|
:icon="DocumentCopy"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@ -32,18 +35,18 @@
|
|||||||
<TMagicButton
|
<TMagicButton
|
||||||
v-show="index !== 0"
|
v-show="index !== 0"
|
||||||
link
|
link
|
||||||
size="small"
|
size="default"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:icon="CaretTop"
|
:icon="Top"
|
||||||
@click="changeOrder(-1)"
|
@click="changeOrder(-1)"
|
||||||
>上移</TMagicButton
|
>上移</TMagicButton
|
||||||
>
|
>
|
||||||
<TMagicButton
|
<TMagicButton
|
||||||
v-show="index !== length - 1"
|
v-show="index !== length - 1"
|
||||||
link
|
link
|
||||||
size="small"
|
size="default"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:icon="CaretBottom"
|
:icon="Bottom"
|
||||||
@click="changeOrder(1)"
|
@click="changeOrder(1)"
|
||||||
>下移</TMagicButton
|
>下移</TMagicButton
|
||||||
>
|
>
|
||||||
@ -107,9 +110,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, inject, ref } from 'vue';
|
import { computed, inject, ref } from 'vue';
|
||||||
import { CaretBottom, CaretRight, CaretTop, Delete, DocumentCopy, Position } from '@element-plus/icons-vue';
|
import { ArrowDown, ArrowRight, Bottom, Delete, DocumentCopy, Position, Top } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
import { TMagicButton, TMagicCard, TMagicIcon, TMagicInputNumber, TMagicPopover } from '@tmagic/design';
|
import { TMagicButton, TMagicCard, TMagicIcon, TMagicInputNumber, TMagicPopover, TMagicTooltip } from '@tmagic/design';
|
||||||
|
|
||||||
import type { ContainerChangeEventData, FormState, GroupListConfig } from '../schema';
|
import type { ContainerChangeEventData, FormState, GroupListConfig } from '../schema';
|
||||||
import { filterFunction } from '../utils/form';
|
import { filterFunction } from '../utils/form';
|
||||||
@ -152,7 +155,10 @@ const rowConfig = computed(() => ({
|
|||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
if (props.config.titleKey && props.model[props.config.titleKey]) {
|
if (props.config.titleKey && props.model[props.config.titleKey]) {
|
||||||
return props.model[props.config.titleKey];
|
const { titlePrefix } = props.config;
|
||||||
|
return titlePrefix
|
||||||
|
? `${titlePrefix} ${String(props.index + 1)}: ${props.model[props.config.titleKey]}`
|
||||||
|
: `${props.model[props.config.titleKey]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.config.title) {
|
if (props.config.title) {
|
||||||
|
|||||||
@ -3,10 +3,17 @@
|
|||||||
v-if="items && items.length"
|
v-if="items && items.length"
|
||||||
class="box-card m-form-panel"
|
class="box-card m-form-panel"
|
||||||
:body-style="{ display: expand ? 'block' : 'none' }"
|
:body-style="{ display: expand ? 'block' : 'none' }"
|
||||||
|
:flat="config.flat"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div style="width: 100%; display: flex; align-items: center">
|
<div style="width: 100%; display: flex; align-items: center">
|
||||||
<TMagicButton style="padding: 0" link :icon="expand ? CaretBottom : CaretRight" @click="expand = !expand">
|
<TMagicButton
|
||||||
|
style="padding: 0; margin-right: 10px"
|
||||||
|
link
|
||||||
|
:icon="expand ? ArrowDown : ArrowRight"
|
||||||
|
@click="expand = !expand"
|
||||||
|
v-if="!hideExpand"
|
||||||
|
>
|
||||||
</TMagicButton>
|
</TMagicButton>
|
||||||
<slot name="header">
|
<slot name="header">
|
||||||
<span style="cursor: pointer" @click="expand = !expand">{{ filter(config.title) }}</span>
|
<span style="cursor: pointer" @click="expand = !expand">{{ filter(config.title) }}</span>
|
||||||
@ -62,7 +69,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, inject, ref } from 'vue';
|
import { computed, inject, ref } from 'vue';
|
||||||
import { CaretBottom, CaretRight } from '@element-plus/icons-vue';
|
import { ArrowDown, ArrowRight } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
import { TMagicButton, TMagicCard } from '@tmagic/design';
|
import { TMagicButton, TMagicCard } from '@tmagic/design';
|
||||||
|
|
||||||
@ -85,6 +92,7 @@ const props = defineProps<{
|
|||||||
prop?: string;
|
prop?: string;
|
||||||
size?: string;
|
size?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
hideExpand?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -107,7 +115,6 @@ const onAddDiffCount = () => emit('addDiffCount');
|
|||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
getExpand: () => expand.value,
|
getExpand: () => expand.value,
|
||||||
|
|
||||||
setExpand: (v: boolean) => {
|
setExpand: (v: boolean) => {
|
||||||
expand.value = v;
|
expand.value = v;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -37,11 +37,17 @@
|
|||||||
:size="addButtonSize"
|
:size="addButtonSize"
|
||||||
:plain="displayMode === 'table'"
|
:plain="displayMode === 'table'"
|
||||||
:icon="Plus"
|
:icon="Plus"
|
||||||
|
text
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
v-bind="currentConfig.addButtonConfig?.props || { type: 'primary' }"
|
v-bind="currentConfig.addButtonConfig?.props || { type: 'primary' }"
|
||||||
@click="newHandler"
|
@click="newHandler"
|
||||||
>
|
>
|
||||||
{{ currentConfig.addButtonConfig?.text || (displayMode === 'table' ? '新增一行' : '新增') }}
|
{{
|
||||||
|
currentConfig.addButtonConfig?.text ||
|
||||||
|
(displayMode === 'table'
|
||||||
|
? `新增一行${groupListConfig.titlePrefix || ''}`
|
||||||
|
: `新增${groupListConfig.titlePrefix || ''}`)
|
||||||
|
}}
|
||||||
</TMagicButton>
|
</TMagicButton>
|
||||||
</template>
|
</template>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@ -9,15 +9,18 @@
|
|||||||
>
|
>
|
||||||
</TMagicButton>
|
</TMagicButton>
|
||||||
</TMagicTooltip>
|
</TMagicTooltip>
|
||||||
|
|
||||||
<TMagicButton
|
<TMagicButton
|
||||||
v-show="showDelete(index + 1 + currentPage * pageSize - 1)"
|
v-show="showDelete(index + 1 + currentPage * pageSize - 1)"
|
||||||
size="small"
|
size="small"
|
||||||
type="danger"
|
type="danger"
|
||||||
link
|
link
|
||||||
title="删除"
|
title="删除"
|
||||||
:icon="config.deleteActionButtonIcon || Delete"
|
:icon="flat ? undefined : config.deleteActionButtonIcon || Delete"
|
||||||
@click="removeHandler(index + 1 + currentPage * pageSize - 1)"
|
@click="removeHandler(index + 1 + currentPage * pageSize - 1)"
|
||||||
></TMagicButton>
|
>
|
||||||
|
<template v-if="flat">删除</template>
|
||||||
|
</TMagicButton>
|
||||||
|
|
||||||
<TMagicButton
|
<TMagicButton
|
||||||
v-if="copyable(index + 1 + currentPage * pageSize - 1)"
|
v-if="copyable(index + 1 + currentPage * pageSize - 1)"
|
||||||
@ -25,10 +28,12 @@
|
|||||||
size="small"
|
size="small"
|
||||||
type="primary"
|
type="primary"
|
||||||
title="复制"
|
title="复制"
|
||||||
:icon="config.copyActionButtonIcon || DocumentCopy"
|
:icon="flat ? undefined : config.copyActionButtonIcon || DocumentCopy"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@click="copyHandler(index + 1 + currentPage * pageSize - 1)"
|
@click="copyHandler(index + 1 + currentPage * pageSize - 1)"
|
||||||
></TMagicButton>
|
>
|
||||||
|
<template v-if="flat">复制</template>
|
||||||
|
</TMagicButton>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -53,6 +58,7 @@ const props = defineProps<{
|
|||||||
row: any;
|
row: any;
|
||||||
prop?: string;
|
prop?: string;
|
||||||
sortKey?: string;
|
sortKey?: string;
|
||||||
|
flat?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const mForm = inject<FormState | undefined>('mForm');
|
const mForm = inject<FormState | undefined>('mForm');
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
:key="updateKey"
|
:key="updateKey"
|
||||||
@select="selectHandle"
|
@select="selectHandle"
|
||||||
@sort-change="sortChangeHandler"
|
@sort-change="sortChangeHandler"
|
||||||
|
:empty-text="`暂无${config.titlePrefix || ''}数据`"
|
||||||
></TMagicTable>
|
></TMagicTable>
|
||||||
</TMagicTooltip>
|
</TMagicTooltip>
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { type TableColumnOptions, TMagicIcon, TMagicTooltip } from '@tmagic/desi
|
|||||||
import type { FormItemConfig, FormState, TableColumnConfig } from '@tmagic/form-schema';
|
import type { FormItemConfig, FormState, TableColumnConfig } from '@tmagic/form-schema';
|
||||||
|
|
||||||
import type { ContainerChangeEventData } from '../../schema';
|
import type { ContainerChangeEventData } from '../../schema';
|
||||||
|
import { isGlobalFlat } from '../../utils/config';
|
||||||
import { display as displayFunc, getDataByPage, sortArray } from '../../utils/form';
|
import { display as displayFunc, getDataByPage, sortArray } from '../../utils/form';
|
||||||
import Container from '../Container.vue';
|
import Container from '../Container.vue';
|
||||||
|
|
||||||
@ -111,23 +112,26 @@ export const useTableColumns = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const defaultFixed: 'left' | 'right' = isGlobalFlat.value ? 'right' : 'left';
|
||||||
let actionFixed: 'left' | 'right' | undefined = props.config.fixed === false ? undefined : 'left';
|
let actionFixed: 'left' | 'right' | undefined = props.config.fixed === false ? undefined : defaultFixed;
|
||||||
|
|
||||||
if (typeof props.config.fixed === 'string' && ['left', 'right'].includes(props.config.fixed)) {
|
if (typeof props.config.fixed === 'string' && ['left', 'right'].includes(props.config.fixed)) {
|
||||||
actionFixed = props.config.fixed;
|
actionFixed = props.config.fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const actionFlat = props.config.flat === undefined && isGlobalFlat.value ? true : props.config.flat;
|
||||||
|
|
||||||
const actionColumn = {
|
const actionColumn = {
|
||||||
props: {
|
props: {
|
||||||
label: '操作',
|
label: '操作',
|
||||||
fixed: actionFixed,
|
fixed: actionFixed,
|
||||||
|
|
||||||
width: props.config.operateColWidth ?? (props.config.dropSortHandle && props.config.dropSort ? 132 : 112),
|
width: props.config.operateColWidth ?? (props.config.dropSortHandle && props.config.dropSort ? 132 : 112),
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
cell: ({ row, $index }: any) =>
|
cell: ({ row, $index }: any) =>
|
||||||
h(ActionsColumn, {
|
h(ActionsColumn, {
|
||||||
row,
|
row,
|
||||||
|
flat: actionFlat,
|
||||||
index: $index,
|
index: $index,
|
||||||
model: props.model,
|
model: props.model,
|
||||||
config: props.config,
|
config: props.config,
|
||||||
|
|||||||
@ -1,5 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<a v-if="config.href && !disabled" target="_blank" :href="href" :style="config.css || {}">{{ displayText }}</a>
|
<a
|
||||||
|
target="_blank"
|
||||||
|
:href="href"
|
||||||
|
v-if="isGlobalFlat && config.href && !disabled"
|
||||||
|
class="magic-admin-link"
|
||||||
|
:style="config.css || {}"
|
||||||
|
>
|
||||||
|
<TMagicButton link type="primary">
|
||||||
|
<TMagicIcon><Notebook /></TMagicIcon>{{ displayText }}
|
||||||
|
</TMagicButton>
|
||||||
|
</a>
|
||||||
|
<a v-else-if="config.href && !disabled" target="_blank" :href="href" :style="config.css || {}">{{ displayText }}</a>
|
||||||
<span v-else-if="config.href && disabled" :style="config.disabledCss || {}">{{ displayText }}</span>
|
<span v-else-if="config.href && disabled" :style="config.disabledCss || {}">{{ displayText }}</span>
|
||||||
<div v-else class="m-fields-link">
|
<div v-else class="m-fields-link">
|
||||||
<TMagicButton link type="primary" @click="editHandler">点击编辑</TMagicButton>
|
<TMagicButton link type="primary" @click="editHandler">点击编辑</TMagicButton>
|
||||||
@ -18,18 +29,20 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, readonly, ref } from 'vue';
|
import { computed, inject, readonly, ref } from 'vue';
|
||||||
|
import { Notebook } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
import { TMagicButton } from '@tmagic/design';
|
import { TMagicButton, TMagicIcon } from '@tmagic/design';
|
||||||
|
|
||||||
import FormDialog from '../FormDialog.vue';
|
import FormDialog from '../FormDialog.vue';
|
||||||
import type { FieldProps, FormState, LinkConfig } from '../schema';
|
import type { FieldProps, FormState, LinkConfig } from '../schema';
|
||||||
|
import { isGlobalFlat } from '../utils/config';
|
||||||
import { useAddField } from '../utils/useAddField';
|
import { useAddField } from '../utils/useAddField';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'MFormLink',
|
name: 'MFormLink',
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<FieldProps<LinkConfig>>();
|
const props = defineProps<FieldProps<LinkConfig> & { theme?: string }>();
|
||||||
|
|
||||||
const emit = defineEmits(['change']);
|
const emit = defineEmits(['change']);
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
v-if="model"
|
v-if="model"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
clearable
|
clearable
|
||||||
controls-position="right"
|
:controls-position="isGlobalFlat ? '' : 'right'"
|
||||||
:size="size"
|
:size="size"
|
||||||
:max="config.max"
|
:max="config.max"
|
||||||
:min="config.min"
|
:min="config.min"
|
||||||
@ -21,6 +21,7 @@ import { inject, ref, watch } from 'vue';
|
|||||||
import { TMagicInputNumber } from '@tmagic/design';
|
import { TMagicInputNumber } from '@tmagic/design';
|
||||||
|
|
||||||
import type { FieldProps, FormState, NumberConfig } from '../schema';
|
import type { FieldProps, FormState, NumberConfig } from '../schema';
|
||||||
|
import { isGlobalFlat } from '../utils/config';
|
||||||
import { useAddField } from '../utils/useAddField';
|
import { useAddField } from '../utils/useAddField';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<TMagicRadioGroup v-if="model" :model-value="model[name]" :size="size" :disabled="disabled">
|
<TMagicRadioGroup
|
||||||
|
v-if="model"
|
||||||
|
:model-value="model[name]"
|
||||||
|
:size="size"
|
||||||
|
:disabled="disabled"
|
||||||
|
class="icon-{{ iconSize }}"
|
||||||
|
>
|
||||||
<component
|
<component
|
||||||
v-for="option in config.options"
|
v-for="option in config.options"
|
||||||
:is="itemComponent"
|
:is="itemComponent"
|
||||||
@ -8,9 +14,14 @@
|
|||||||
@click="clickHandler(option.value)"
|
@click="clickHandler(option.value)"
|
||||||
>
|
>
|
||||||
<TMagicTooltip :disabled="!Boolean(option.tooltip)" placement="top-start" :content="option.tooltip">
|
<TMagicTooltip :disabled="!Boolean(option.tooltip)" placement="top-start" :content="option.tooltip">
|
||||||
<div>
|
<div class="m-fields-radio-group__option">
|
||||||
<TMagicIcon v-if="option.icon" :size="iconSize"><component :is="option.icon"></component></TMagicIcon>
|
<TMagicIcon
|
||||||
<span v-if="option.text">{{ option.text }}</span>
|
v-if="option.icon"
|
||||||
|
:size="iconSize"
|
||||||
|
:class="{ 'm-fields-radio-group__icon_with_text': !!option.text }"
|
||||||
|
><component :is="option.icon"></component
|
||||||
|
></TMagicIcon>
|
||||||
|
<span v-if="option.text" class="m-fields-radio-group__text">{{ option.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
</TMagicTooltip>
|
</TMagicTooltip>
|
||||||
</component>
|
</component>
|
||||||
@ -45,13 +56,17 @@ const clickHandler = (item: string | number | boolean) => {
|
|||||||
|
|
||||||
useAddField(props.prop);
|
useAddField(props.prop);
|
||||||
|
|
||||||
|
// 这里换了设计稿里的图标,所以需要调整一下图标大小
|
||||||
const iconSize = computed(() => {
|
const iconSize = computed(() => {
|
||||||
|
if (props.config.iconSize) {
|
||||||
|
return props.config.iconSize;
|
||||||
|
}
|
||||||
if (props.size === 'small') {
|
if (props.size === 'small') {
|
||||||
return '12';
|
return '14';
|
||||||
}
|
}
|
||||||
if (props.size === 'large') {
|
if (props.size === 'large') {
|
||||||
return '16';
|
return '16';
|
||||||
}
|
}
|
||||||
return '14';
|
return '16';
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,6 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="m-fields-text">
|
<div :class="['m-fields-text', { 'm-fields-text--static': config.static }]">
|
||||||
|
<div v-if="config.disabled"></div>
|
||||||
|
<!-- 新增静态展示文本样式 -->
|
||||||
|
<template v-if="config.static">
|
||||||
|
<span v-if="config.prepend" class="m-fields-text__prepend">{{ config.prepend }}</span>
|
||||||
|
<span class="m-fields-text__value">{{ value }}</span>
|
||||||
|
<template v-if="appendConfig">
|
||||||
|
<TMagicButton
|
||||||
|
v-if="appendConfig.type === 'button'"
|
||||||
|
style="color: #0056ea"
|
||||||
|
:size="size"
|
||||||
|
@click.prevent="buttonClickHandler"
|
||||||
|
>
|
||||||
|
{{ appendConfig.text }}
|
||||||
|
</TMagicButton>
|
||||||
|
<img
|
||||||
|
v-else-if="appendConfig.type === 'icon'"
|
||||||
|
class="m-fields-text__icon"
|
||||||
|
:src="appendConfig.text"
|
||||||
|
@click="buttonClickHandler"
|
||||||
|
/>
|
||||||
|
<span v-else>{{ appendConfig.text }}</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
<TMagicInput
|
<TMagicInput
|
||||||
|
v-else
|
||||||
v-model="value"
|
v-model="value"
|
||||||
ref="input"
|
ref="input"
|
||||||
:clearable="config.clearable ?? true"
|
:clearable="config.clearable ?? true"
|
||||||
@ -17,17 +42,18 @@
|
|||||||
<template #append v-if="appendConfig">
|
<template #append v-if="appendConfig">
|
||||||
<TMagicButton
|
<TMagicButton
|
||||||
v-if="appendConfig.type === 'button'"
|
v-if="appendConfig.type === 'button'"
|
||||||
style="color: #409eff"
|
style="color: #0056ea"
|
||||||
:size="size"
|
:size="size"
|
||||||
@click.prevent="buttonClickHandler"
|
@click.prevent="buttonClickHandler"
|
||||||
>
|
>
|
||||||
{{ appendConfig.text }}
|
{{ appendConfig.text }}
|
||||||
</TMagicButton>
|
</TMagicButton>
|
||||||
|
<img v-else-if="appendConfig.type === 'icon'" :src="appendConfig.text" @click="buttonClickHandler" />
|
||||||
<span v-else>{{ appendConfig.text }}</span>
|
<span v-else>{{ appendConfig.text }}</span>
|
||||||
</template>
|
</template>
|
||||||
</TMagicInput>
|
</TMagicInput>
|
||||||
|
|
||||||
<Teleport to="body">
|
<Teleport to="body" v-if="!config.static">
|
||||||
<div v-if="popoverVisible" class="tmagic-form-text-popper m-form-item__content" ref="popoverEl">
|
<div v-if="popoverVisible" class="tmagic-form-text-popper m-form-item__content" ref="popoverEl">
|
||||||
<div class="m-form-validate__warning">输入内容前后有空格,是否移除空格?</div>
|
<div class="m-form-validate__warning">输入内容前后有空格,是否移除空格?</div>
|
||||||
<div style="display: flex; justify-content: flex-end">
|
<div style="display: flex; justify-content: flex-end">
|
||||||
@ -80,28 +106,26 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const appendConfig = computed(() => {
|
const appendConfig = computed(() => {
|
||||||
if (typeof props.config.append === 'string') {
|
const { append } = props.config;
|
||||||
|
|
||||||
|
// 字符串形态:纯文本 append。
|
||||||
|
if (typeof append === 'string') {
|
||||||
return {
|
return {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: props.config.append,
|
text: append,
|
||||||
handler: undefined,
|
handler: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
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;
|
// 对象形态:按 schema 定义,`type` 可为 `'button' | 'icon'`,`handler` 可选。
|
||||||
}
|
//
|
||||||
|
// 旧逻辑里只要带 handler 就强制覆盖为 `type: 'button'`,导致 schema 中显式传入的
|
||||||
|
// `type: 'icon'` 在到达模板前被抹掉(参见 `@editor/utils/props.ts` 中 ID 字段
|
||||||
|
// 的复制按钮配置)。这里直接返回原对象,模板按 `type` 分支渲染 button / icon / text。
|
||||||
|
if (typeof append === 'object' && append) {
|
||||||
|
// `value === 0` 沿用旧约定:作为「隐藏 append」的开关。
|
||||||
|
if (append.value === 0) return false;
|
||||||
|
return append;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -50,11 +50,13 @@ import { setConfig } from './utils/config';
|
|||||||
import { registerTypeMatchRules, type TypeMatchValidator } from './utils/typeMatch';
|
import { registerTypeMatchRules, type TypeMatchValidator } from './utils/typeMatch';
|
||||||
import Form from './Form.vue';
|
import Form from './Form.vue';
|
||||||
import FormDialog from './FormDialog.vue';
|
import FormDialog from './FormDialog.vue';
|
||||||
|
import FormDrawer from './FormDrawer.vue';
|
||||||
|
|
||||||
import './theme/index.scss';
|
import './theme/index.scss';
|
||||||
|
|
||||||
// #region FormInstallOptions
|
// #region FormInstallOptions
|
||||||
export interface FormInstallOptions {
|
export interface FormInstallOptions {
|
||||||
|
flat?: boolean;
|
||||||
/** 自定义字段 type 的 typeMatch 校验规则,可覆盖内置规则或扩展业务字段 */
|
/** 自定义字段 type 的 typeMatch 校验规则,可覆盖内置规则或扩展业务字段 */
|
||||||
typeMatchRules?: Record<string, TypeMatchValidator>;
|
typeMatchRules?: Record<string, TypeMatchValidator>;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
@ -76,6 +78,7 @@ export default {
|
|||||||
|
|
||||||
app.component('m-form', Form);
|
app.component('m-form', Form);
|
||||||
app.component('m-form-dialog', FormDialog);
|
app.component('m-form-dialog', FormDialog);
|
||||||
|
app.component('m-form-drawer', FormDrawer);
|
||||||
app.component('m-form-container', Container);
|
app.component('m-form-container', Container);
|
||||||
app.component('m-form-fieldset', Fieldset);
|
app.component('m-form-fieldset', Fieldset);
|
||||||
app.component('m-form-group-list', TableGroupList);
|
app.component('m-form-group-list', TableGroupList);
|
||||||
|
|||||||
@ -22,4 +22,7 @@
|
|||||||
background: #f7dadd;
|
background: #f7dadd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.m-form-title-extra {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,54 @@
|
|||||||
|
.m-container-fieldset {
|
||||||
|
&.no-border-fieldset {
|
||||||
|
&.fieldset-in-row {
|
||||||
|
> fieldset.m-fieldset {
|
||||||
|
legend {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> fieldset.m-fieldset {
|
||||||
|
border: 0;
|
||||||
|
legend {
|
||||||
|
position: relative;
|
||||||
|
left: -5px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 24px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.fieldset-in-card-fit {
|
||||||
|
border-bottom: 1px solid #e6e6e6;
|
||||||
|
> fieldset.m-fieldset {
|
||||||
|
padding: 16px 0 16px 0 !important;
|
||||||
|
border: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
legend {
|
||||||
|
position: relative;
|
||||||
|
top: 10px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.fieldset-in-card-fit-last {
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
&.fieldset-in-card-fit-first {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
&:first {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fieldset.m-fieldset {
|
fieldset.m-fieldset {
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 1px solid rgb(229, 229, 229);
|
border: 1px solid rgb(229, 229, 229);
|
||||||
|
|||||||
@ -53,3 +53,9 @@
|
|||||||
margin-bottom: var(--td-comp-margin-xxl);
|
margin-bottom: var(--td-comp-margin-xxl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.m-form-container {
|
||||||
|
.m-form-container-expand {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
.m-fields-group-list {
|
.m-fields-group-list {
|
||||||
|
.m-fields-group-list-item.tmagic-design-card--flat:last-child {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.el-button--text {
|
.el-button--text {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-bottom: 7px;
|
margin-bottom: 7px;
|
||||||
@ -19,14 +23,11 @@
|
|||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.tmagic-design-card {
|
.m-fields-group-list-item-header {
|
||||||
.el-card__header {
|
display: flex;
|
||||||
padding: 5px 20px;
|
align-items: center;
|
||||||
}
|
gap: 8px;
|
||||||
.t-card__header {
|
|
||||||
padding: 5px 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,5 +35,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,3 +13,4 @@
|
|||||||
@use "./tabs.scss";
|
@use "./tabs.scss";
|
||||||
@use "./number-range.scss";
|
@use "./number-range.scss";
|
||||||
@use "./form-box.scss";
|
@use "./form-box.scss";
|
||||||
|
@use "./radio-group.scss";
|
||||||
|
|||||||
@ -1,3 +1,20 @@
|
|||||||
div.m-fields-link {
|
div.m-fields-link {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
a.magic-admin-link {
|
||||||
|
width: calc(100%);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
background-color: #fafafa;
|
||||||
|
padding: 0 8px;
|
||||||
|
height: 28px;
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
background: #f2f6fc;
|
background: #f2f6fc;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #409eff;
|
color: var(--el-color-primary, #409eff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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