mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-07-23 10:05:42 +08:00
feat(design,editor): 新增magic-admin主题样式,优化组件表单配置布局样式
This commit is contained in:
parent
89689bb9fd
commit
b13cd3425b
@ -162,7 +162,7 @@
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
color: var(--el-color-primary,#409eff);;
|
||||
background-color: #f9fafc;
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ export default defineConfig([
|
||||
'*/**/public/**/*',
|
||||
'*/**/types/**/*',
|
||||
'*/**/*.config.ts',
|
||||
'./tepm/**/*',
|
||||
'./temp/**/*',
|
||||
'vite-env.d.ts',
|
||||
]),
|
||||
...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>
|
||||
<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">
|
||||
<slot name="header" class="header"></slot>
|
||||
</template>
|
||||
@ -14,6 +14,7 @@
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { getDesignConfig } from './config';
|
||||
import { isGlobalFlat } from './index';
|
||||
import type { CardProps } from './types';
|
||||
|
||||
defineOptions({
|
||||
@ -26,5 +27,14 @@ const ui = getDesignConfig('components')?.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>
|
||||
|
||||
@ -7,18 +7,39 @@
|
||||
@update:modelValue="updateModelValue"
|
||||
>
|
||||
</component>
|
||||
<MInput
|
||||
v-if="isFlat && !isLargeStageContainer"
|
||||
@update:modelValue="updateModelValue"
|
||||
@change="changeHandler"
|
||||
v-bind="uiProps"
|
||||
class="tmagic-design-color-picker-input"
|
||||
clearable
|
||||
></MInput>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { computed, type ComputedRef, inject } from 'vue';
|
||||
|
||||
import { getDesignConfig } from './config';
|
||||
import { isGlobalFlat } from './index';
|
||||
import MInput from './Input.vue';
|
||||
import type { ColorPickerProps } from './types';
|
||||
|
||||
defineOptions({
|
||||
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>(), {
|
||||
showAlpha: false,
|
||||
disabled: false,
|
||||
|
||||
@ -1,12 +1,24 @@
|
||||
<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>
|
||||
<slot name="label"></slot>
|
||||
<slot name="label"> </slot>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<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 v-if="adapterType === 'element-plus'" #error="{ error }">
|
||||
@ -16,26 +28,32 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { computed, inject } from 'vue';
|
||||
|
||||
import Alert from './Alert.vue';
|
||||
import { getDesignConfig } from './config';
|
||||
import { stripValidateSuggestion } from './formValidateMessage';
|
||||
import { isGlobalFlat } from './index';
|
||||
import type { FormItemProps } from './types';
|
||||
|
||||
defineOptions({
|
||||
name: 'TMFormItem',
|
||||
});
|
||||
|
||||
const props = defineProps<FormItemProps>();
|
||||
const props = defineProps<FormItemProps & { theme?: string }>();
|
||||
|
||||
const ui = getDesignConfig('components')?.formItem;
|
||||
|
||||
const uiComponent = ui?.component || 'el-form-item';
|
||||
|
||||
const adapterType = getDesignConfig('adapterType');
|
||||
|
||||
const formInline = inject<boolean>('formInline');
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
|
||||
@ -4,10 +4,9 @@
|
||||
<div
|
||||
v-if="popoverVisible || !destroyOnClose"
|
||||
v-show="popoverVisible"
|
||||
class="tmagic-design-popper"
|
||||
ref="popperElementRef"
|
||||
:tabindex="tabindex"
|
||||
:class="popperClass"
|
||||
:class="['tmagic-design-popper', popperClass, themeClass]"
|
||||
:style="style"
|
||||
@mouseenter.once="popperMouseenterHandler"
|
||||
>
|
||||
@ -22,7 +21,7 @@ import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, ref
|
||||
import type { Instance } from '@popperjs/core';
|
||||
import { createPopper } from '@popperjs/core';
|
||||
|
||||
import { useZIndex } from './index';
|
||||
import { useThemeClass, useZIndex } from './index';
|
||||
import type { PopoverProps } from './types';
|
||||
|
||||
defineSlots<{
|
||||
@ -46,6 +45,12 @@ const props = withDefaults(defineProps<PopoverProps>(), {
|
||||
closeOnClickOutside: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* 主题修饰类(来自最近的 `<MEditor>` / `<MForm>` 祖先 provide)。
|
||||
* 挂在 `Teleport` 出去的 popper 根节点上,让主题级 CSS 变量(`--el-color-primary` 等)
|
||||
* 在 portal 节点上也能命中。详见 `@tmagic/design/theme.ts`。
|
||||
*/
|
||||
const themeClass = useThemeClass();
|
||||
const emit = defineEmits<{
|
||||
/** 受控模式(传入了 visible)下点击外部收起时触发,便于配合 v-model:visible。 */
|
||||
'update:visible': [_visible: boolean];
|
||||
|
||||
@ -8,6 +8,7 @@ import './theme/index.scss';
|
||||
|
||||
export * from './types';
|
||||
export * from './config';
|
||||
export * from './theme';
|
||||
export * from './formValidateMessage';
|
||||
|
||||
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 {
|
||||
install(app: App, options: DesignPluginOptions) {
|
||||
tMagicMessage =
|
||||
@ -129,6 +134,8 @@ export default {
|
||||
useZIndex = options.useZIndex;
|
||||
}
|
||||
|
||||
isGlobalFlat.value = options.flat ?? false;
|
||||
|
||||
if (options.adapterType && globalThis.document?.documentElement) {
|
||||
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 "./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;
|
||||
}
|
||||
|
||||
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 {
|
||||
type?: string;
|
||||
size?: FieldSize;
|
||||
@ -39,6 +50,11 @@ export interface CardProps {
|
||||
bodyStyle?: Record<string, any>;
|
||||
shadow?: string;
|
||||
header?: string;
|
||||
/**
|
||||
* 铺平模式:去除卡片自身的视觉(背景 / 阴影 / 边框 / 圆角 / header 分隔线 / header 与 body 内边距)。
|
||||
* 用于已有外层容器或者使用方希望「无卡片感」的场景,TMagicCard 的折叠等 prop 仍可正常使用。
|
||||
*/
|
||||
flat?: boolean;
|
||||
}
|
||||
|
||||
export interface CascaderProps {
|
||||
@ -97,6 +113,7 @@ export interface ColorPickerProps {
|
||||
disabled?: boolean;
|
||||
showAlpha?: boolean;
|
||||
size?: FieldSize;
|
||||
flat?: boolean;
|
||||
}
|
||||
|
||||
export interface DatePickerProps {
|
||||
@ -185,7 +202,9 @@ export interface FormItemProps {
|
||||
labelWidth?: string | number;
|
||||
rules?: any;
|
||||
extra?: string;
|
||||
extraTips?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export interface InputProps {
|
||||
@ -464,6 +483,11 @@ export interface Components {
|
||||
props: (props: BadgeProps) => BadgeProps;
|
||||
};
|
||||
|
||||
alert: {
|
||||
component: DefineComponent<AlertProps, {}, any> | string;
|
||||
props: (props: AlertProps) => AlertProps;
|
||||
};
|
||||
|
||||
autocomplete: {
|
||||
component: DefineComponent<AutocompleteProps, {}, any> | string;
|
||||
props: (props: AutocompleteProps) => AutocompleteProps;
|
||||
@ -726,6 +750,7 @@ export interface Components {
|
||||
}
|
||||
|
||||
export interface DesignPluginOptions {
|
||||
flat?: boolean;
|
||||
adapterType?: string;
|
||||
message?: TMagicMessage;
|
||||
messageBox?: TMagicMessageBox;
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
:page-bar-sort-options="pageBarSortOptions"
|
||||
:page-filter-function="pageFilterFunction"
|
||||
:hide-sidebar="hideSidebar"
|
||||
:theme="theme"
|
||||
>
|
||||
<template #header>
|
||||
<slot name="header"></slot>
|
||||
@ -135,9 +136,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import { provide, ref } from 'vue';
|
||||
import { computed, provide, ref } from 'vue';
|
||||
|
||||
import type { MApp } from '@tmagic/core';
|
||||
import { M_THEME_KEY } from '@tmagic/design';
|
||||
|
||||
import Framework from './layouts/Framework.vue';
|
||||
import TMagicNavMenu from './layouts/NavMenu.vue';
|
||||
@ -241,6 +243,15 @@ provide(ENABLE_PROPS_FORM_VALIDATE, props.enablePropsFormValidate ?? false);
|
||||
* 与 PropsPanel 通过 `:extend-state` 显式传入的方式保持等价。
|
||||
*/
|
||||
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,供历史差异弹窗复用,
|
||||
* 让 CompareForm 与 PropsPanel 的 filterFunction 上下文保持一致。
|
||||
@ -256,6 +267,16 @@ provide('historyListExtraTabs', props.historyListExtraTabs);
|
||||
|
||||
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>) => {
|
||||
propsPanelRef.value = e;
|
||||
emit('props-panel-mounted', e);
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-show="visible"
|
||||
class="magic-editor-content-menu"
|
||||
ref="menu"
|
||||
:class="['magic-editor-content-menu', themeClass]"
|
||||
:style="menuStyle"
|
||||
@mouseenter="mouseenterHandler()"
|
||||
@contextmenu.prevent
|
||||
@ -39,7 +39,7 @@
|
||||
<script lang="ts" setup>
|
||||
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';
|
||||
|
||||
@ -73,6 +73,14 @@ const menuEl = useTemplateRef<HTMLDivElement>('menu');
|
||||
const buttonRefs = useTemplateRef<InstanceType<typeof ToolButton>[]>('buttons');
|
||||
const subMenuRef = useTemplateRef<any>('subMenu');
|
||||
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 zIndex = useZIndex();
|
||||
const curZIndex = ref<number>(0);
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<Teleport to="body" v-if="visible">
|
||||
<div
|
||||
ref="target"
|
||||
class="m-editor-float-box"
|
||||
:class="['m-editor-float-box', themeClass]"
|
||||
v-bind="$attrs"
|
||||
:style="{ ...style, zIndex: curZIndex }"
|
||||
@mousedown="nextZIndex"
|
||||
@ -27,7 +27,7 @@ import { computed, type CSSProperties, nextTick, onBeforeUnmount, provide, ref,
|
||||
import { Close } from '@element-plus/icons-vue';
|
||||
import VanillaMoveable from 'moveable';
|
||||
|
||||
import { TMagicButton, useZIndex } from '@tmagic/design';
|
||||
import { TMagicButton, useThemeClass, useZIndex } from '@tmagic/design';
|
||||
|
||||
import MIcon from '@editor/components/Icon.vue';
|
||||
|
||||
@ -62,6 +62,12 @@ const props = withDefaults(
|
||||
const targetEl = useTemplateRef<HTMLDivElement>('target');
|
||||
const titleEl = useTemplateRef<HTMLDivElement>('title');
|
||||
|
||||
/**
|
||||
* 主题修饰类(来自最近的 `<MEditor>` / `<MForm>` 祖先 provide)。
|
||||
* 挂在 `Teleport` 出去的浮动面板根节点上,让主题级 CSS 变量在 portal 节点上也能命中。
|
||||
*/
|
||||
const themeClass = useThemeClass();
|
||||
|
||||
const zIndex = useZIndex();
|
||||
const curZIndex = ref<number>(0);
|
||||
|
||||
|
||||
@ -35,6 +35,10 @@ import type {
|
||||
export const ENABLE_PROPS_FORM_VALIDATE: InjectionKey<boolean> = Symbol('enablePropsFormValidate');
|
||||
|
||||
export interface EditorProps {
|
||||
/** 是否是大屏模拟器容器,大屏容器时,左侧属性面板会扩展为两列(正常3列),颜色表单不扩展两列等 */
|
||||
isLargeStageContainer?: boolean;
|
||||
|
||||
theme?: string;
|
||||
/** 页面初始值 */
|
||||
modelValue?: MApp;
|
||||
/** 左侧面板中的组件类型列表 */
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<div class="m-fields-code-select" :class="config.className">
|
||||
<TMagicCard>
|
||||
<TMagicCard :flat="config.flat">
|
||||
<MContainer
|
||||
:config="codeConfig"
|
||||
:size="size"
|
||||
class="code-select-content"
|
||||
:prop="prop"
|
||||
:disabled="disabled"
|
||||
:is-compare="isCompareMode"
|
||||
@ -12,16 +13,20 @@
|
||||
@change="changeHandler"
|
||||
>
|
||||
</MContainer>
|
||||
<TMagicButton class="create-button fullWidth" :icon="Plus" :size="size" :disabled="disabled" @click="newHandler()"
|
||||
>添加{{ config.text }}</TMagicButton
|
||||
>
|
||||
</TMagicCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch } from 'vue';
|
||||
import { Plus } from '@element-plus/icons-vue';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
|
||||
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 { MContainer } from '@tmagic/form';
|
||||
|
||||
@ -53,12 +58,24 @@ const props = withDefaults(defineProps<FieldProps<CodeSelectConfig>>(), {});
|
||||
* 仅当存在历史值时才启用对比,避免 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>(() => ({
|
||||
type: 'group-list',
|
||||
name: 'hookData',
|
||||
enableToggleMode: false,
|
||||
expandAll: true,
|
||||
flat: true,
|
||||
addable: () => false,
|
||||
title: (mForm, { model, index }: any) => {
|
||||
if (model.codeType === HookCodeType.DATA_SOURCE_METHOD) {
|
||||
if (Array.isArray(model.codeId)) {
|
||||
@ -81,49 +98,44 @@ const codeConfig = computed<GroupListConfig>(() => ({
|
||||
|
||||
return model.codeId || index;
|
||||
},
|
||||
titlePrefix: props.config.name === undefined ? undefined : String(props.config.name),
|
||||
items: [
|
||||
{
|
||||
type: 'row',
|
||||
items: [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'codeType',
|
||||
span: 6,
|
||||
options: [
|
||||
{ value: HookCodeType.CODE, 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' }],
|
||||
},
|
||||
text: '代码类型',
|
||||
type: 'select',
|
||||
name: 'codeType',
|
||||
labelPosition: 'right',
|
||||
rules: [{ typeMatch: true, trigger: 'change' }],
|
||||
options: [
|
||||
{ value: HookCodeType.CODE, text: '代码块' },
|
||||
{ value: HookCodeType.DATA_SOURCE_METHOD, text: '数据源方法' },
|
||||
],
|
||||
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',
|
||||
name: props.name,
|
||||
disabled: props.disabled,
|
||||
text: props.config.text,
|
||||
options: () => {
|
||||
if (codeDsl.value) {
|
||||
return map(codeDsl.value, (value, key) => ({
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<div class="m-fields-data-source-field-select">
|
||||
<div
|
||||
class="m-fields-data-source-field-select"
|
||||
:class="{
|
||||
[`data-source-field-${type}`]: !isSelectValid,
|
||||
}"
|
||||
>
|
||||
<FieldSelect
|
||||
v-if="!disabledDataSource && (showDataSourceFieldSelect || !config.fieldConfig)"
|
||||
v-if="isSelectValid"
|
||||
:model-value="model[name]"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
@ -37,15 +42,14 @@
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
@click="onToggleDataSourceFieldSelectHandler"
|
||||
><MIcon :icon="Coin"></MIcon
|
||||
></TMagicButton>
|
||||
><MIcon :icon="dataSourceIcon"></MIcon>
|
||||
</TMagicButton>
|
||||
</TMagicTooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, ref, resolveComponent, watch } from 'vue';
|
||||
import { Coin } from '@element-plus/icons-vue';
|
||||
|
||||
import { DataSchema } from '@tmagic/core';
|
||||
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 { useServices } from '@editor/hooks/use-services';
|
||||
|
||||
import dataSourceIcon from '../../icons/DatasourceIcon.vue';
|
||||
|
||||
import FieldSelect from './FieldSelect.vue';
|
||||
|
||||
defineOptions({
|
||||
@ -75,6 +81,9 @@ const props = withDefaults(defineProps<FieldProps<DataSourceFieldSelectConfig>>(
|
||||
|
||||
const showDataSourceFieldSelect = ref(false);
|
||||
|
||||
const isSelectValid = computed(
|
||||
() => !disabledDataSource.value && (showDataSourceFieldSelect.value || !props.config.fieldConfig),
|
||||
);
|
||||
watch(
|
||||
() => props.model[props.name],
|
||||
(value) => {
|
||||
|
||||
@ -68,12 +68,15 @@ const config = computed<GroupListConfig>(() => ({
|
||||
titlePrefix: props.config.titlePrefix,
|
||||
expandAll: true,
|
||||
enableToggleMode: false,
|
||||
flat: props.config.flat,
|
||||
items: [
|
||||
{
|
||||
type: 'table',
|
||||
name: 'cond',
|
||||
operateColWidth: 80,
|
||||
operateColWidth: props.config.operateColWidth,
|
||||
enableToggleMode: false,
|
||||
fixed: props.config.fixed,
|
||||
flat: props.config.flat,
|
||||
items: [
|
||||
parentFields.value.length
|
||||
? {
|
||||
|
||||
@ -12,16 +12,21 @@
|
||||
@change="onChangeHandler"
|
||||
></MTable>
|
||||
|
||||
<div v-else class="fullWidth">
|
||||
<TMagicButton
|
||||
v-if="!isCompareMode"
|
||||
class="create-button"
|
||||
type="primary"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
@click="addEvent()"
|
||||
>添加事件</TMagicButton
|
||||
>
|
||||
<div v-else class="fullWidth event-select-container">
|
||||
<div class="event-select-header">
|
||||
<div class="event-select-title">事件配置</div>
|
||||
<TMagicButton
|
||||
v-if="!isCompareMode && displayList.length > 0"
|
||||
class="create-button"
|
||||
text
|
||||
type="primary"
|
||||
:icon="Plus"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
@click="addEvent()"
|
||||
>添加事件</TMagicButton
|
||||
>
|
||||
</div>
|
||||
<MPanel
|
||||
v-for="entry in displayList"
|
||||
:key="entry.index"
|
||||
@ -32,32 +37,45 @@
|
||||
:model="entry.cardItem"
|
||||
:last-values="entry.lastCardItem"
|
||||
:is-compare="isCompareMode"
|
||||
:hide-expand="true"
|
||||
:label-width="config.labelWidth || '100px'"
|
||||
@change="onChangeHandler"
|
||||
>
|
||||
<template #header>
|
||||
<MFormContainer
|
||||
class="fullWidth"
|
||||
:config="eventNameConfig"
|
||||
:model="entry.cardItem"
|
||||
:last-values="entry.lastCardItem"
|
||||
:is-compare="isCompareMode"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
:prop="`${prop}.${entry.index}`"
|
||||
@change="eventNameChangeHandler"
|
||||
></MFormContainer>
|
||||
<TMagicButton
|
||||
v-if="!isCompareMode"
|
||||
style="color: #f56c6c"
|
||||
link
|
||||
:icon="Delete"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
@click="removeEvent(Number(entry.index))"
|
||||
></TMagicButton>
|
||||
<div class="event-item-header">
|
||||
<div class="event-item-title">事件{{ Number(entry.index) + 1 }}</div>
|
||||
<MFormContainer
|
||||
class="fullWidth"
|
||||
:config="eventNameConfig"
|
||||
:model="entry.cardItem"
|
||||
:last-values="entry.lastCardItem"
|
||||
:is-compare="isCompareMode"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
:prop="`${prop}.${entry.index}`"
|
||||
@change="eventNameChangeHandler"
|
||||
></MFormContainer>
|
||||
<TMagicButton
|
||||
class="event-item-delete-button"
|
||||
v-if="!isCompareMode"
|
||||
link
|
||||
:icon="Delete"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
@click="removeEvent(Number(entry.index))"
|
||||
></TMagicButton>
|
||||
</div>
|
||||
</template>
|
||||
</MPanel>
|
||||
|
||||
<TMagicButton
|
||||
v-if="!isCompareMode"
|
||||
class="create-button fullWidth"
|
||||
:icon="Plus"
|
||||
:disabled="disabled"
|
||||
@click="addEvent()"
|
||||
>添加事件</TMagicButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -65,6 +83,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { Delete } from '@element-plus/icons-vue';
|
||||
import { Plus } from '@element-plus/icons-vue';
|
||||
import { has } from 'lodash-es';
|
||||
|
||||
import { ActionType } from '@tmagic/core';
|
||||
@ -108,7 +127,7 @@ const { editorService, dataSourceService, eventsService, codeBlockService, props
|
||||
const eventNameConfig = computed(() => {
|
||||
const defaultEventNameConfig = {
|
||||
name: 'name',
|
||||
text: '事件',
|
||||
text: '事件类型',
|
||||
type: (mForm: FormState | undefined, { formValue }: any) => {
|
||||
if (
|
||||
props.config.src !== 'component' ||
|
||||
@ -118,7 +137,7 @@ const eventNameConfig = computed(() => {
|
||||
}
|
||||
return 'select';
|
||||
},
|
||||
labelWidth: '40px',
|
||||
labelWidth: '70px',
|
||||
checkStrictly: () => props.config.src !== 'component',
|
||||
valueSeparator: '.',
|
||||
options: (_mForm: FormState, { formValue }: any) => getEventNameOptions(props.config.src, formValue),
|
||||
@ -178,6 +197,7 @@ const actionTypeConfig = computed(() => {
|
||||
name: 'actionType',
|
||||
text: '联动类型',
|
||||
type: 'select',
|
||||
labelPosition: 'left',
|
||||
defaultValue: ActionType.COMP,
|
||||
options: actionTypeOptions.value,
|
||||
rules: [
|
||||
@ -200,6 +220,7 @@ const targetCompConfig = computed(() => {
|
||||
name: 'to',
|
||||
text: '联动组件',
|
||||
type: 'ui-select',
|
||||
labelPosition: 'left',
|
||||
display: (_mForm, { model }) => model.actionType === ActionType.COMP,
|
||||
onChange: (_MForm, _v, { setModel }) => {
|
||||
setModel('method', '');
|
||||
@ -219,6 +240,7 @@ const compActionConfig = computed(() => {
|
||||
const defaultCompActionConfig: DynamicTypeConfig = {
|
||||
name: 'method',
|
||||
text: '动作',
|
||||
labelPosition: 'left',
|
||||
type: (mForm: FormState | undefined, { model }: any) => {
|
||||
const to = editorService.getNodeById(model.to);
|
||||
|
||||
@ -317,6 +339,8 @@ const actionsConfig = computed(
|
||||
() =>
|
||||
defineFormItem({
|
||||
type: 'panel',
|
||||
labelPosition: 'left',
|
||||
flat: true,
|
||||
items: [
|
||||
{
|
||||
type: 'group-list',
|
||||
@ -324,6 +348,8 @@ const actionsConfig = computed(
|
||||
expandAll: true,
|
||||
enableToggleMode: false,
|
||||
titlePrefix: '动作',
|
||||
labelPosition: 'left',
|
||||
flat: true,
|
||||
items: [
|
||||
actionTypeConfig.value,
|
||||
targetCompConfig.value,
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<TMagicCollapse class="m-fields-style-setter" v-model="collapseValue">
|
||||
<template v-for="(item, index) in list" :key="index">
|
||||
<TMagicCollapseItem :name="`${index}`">
|
||||
<template #title><MIcon :icon="Grid"></MIcon>{{ item.title }}</template>
|
||||
<template #title>{{ item.title }}</template>
|
||||
<component
|
||||
v-if="item.component"
|
||||
:is="item.component"
|
||||
@ -22,14 +22,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { shallowRef } from 'vue';
|
||||
import { Grid } from '@element-plus/icons-vue';
|
||||
|
||||
import { TMagicCollapse, TMagicCollapseItem } from '@tmagic/design';
|
||||
import type { ContainerChangeEventData, FieldProps } from '@tmagic/form';
|
||||
import type { StyleSchema } from '@tmagic/schema';
|
||||
|
||||
import MIcon from '@editor/components/Icon.vue';
|
||||
|
||||
import { Background, Border, Font, Layout, Position, Transform } from './pro/';
|
||||
|
||||
defineOptions({
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
@click="selectDirection('Left')"
|
||||
></div>
|
||||
<div
|
||||
class="border-icon"
|
||||
class="border-icon center"
|
||||
:class="{ active: direction === '', configured: isConfigured('') }"
|
||||
@click="selectDirection()"
|
||||
></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>
|
||||
<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
|
||||
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"
|
||||
fill="#5D5D5D"
|
||||
></path>
|
||||
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-opacity="0.3"
|
||||
/>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
@ -1,31 +1,13 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<path
|
||||
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"
|
||||
></path>
|
||||
<path
|
||||
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"
|
||||
></path>
|
||||
<path
|
||||
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"
|
||||
></path>
|
||||
<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 xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<rect x="13" y="3" width="2" height="2" rx="0.5" transform="rotate(90 13 3)" fill-opacity="0.3" />
|
||||
<rect x="13" y="7" width="2" height="2" rx="0.5" transform="rotate(90 13 7)" fill-opacity="0.3" />
|
||||
<rect x="13" y="11" width="2" height="2" rx="0.5" transform="rotate(90 13 11)" fill-opacity="0.3" />
|
||||
<rect x="9" y="3" width="2" height="2" rx="0.5" transform="rotate(90 9 3)" />
|
||||
<rect x="9" y="7" width="2" height="2" rx="0.5" transform="rotate(90 9 7)" />
|
||||
<rect x="9" y="11" width="2" height="2" rx="0.5" transform="rotate(90 9 11)" fill-opacity="0.3" />
|
||||
<rect x="5" y="3" width="2" height="2" rx="0.5" transform="rotate(90 5 3)" />
|
||||
<rect x="5" y="7" width="2" height="2" rx="0.5" transform="rotate(90 5 7)" />
|
||||
<rect x="5" y="11" width="2" height="2" rx="0.5" transform="rotate(90 5 11)" fill-opacity="0.3" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<path
|
||||
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"
|
||||
fill="#333333"
|
||||
></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<rect x="3" y="3" width="2" height="10" rx="1" />
|
||||
<rect x="7" y="3" width="2" height="10" rx="1" />
|
||||
<rect x="11" y="3" width="2" height="10" rx="1" fill-opacity="0.3" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<path
|
||||
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"
|
||||
fill="#333333"
|
||||
></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<rect x="13" y="3" width="2" height="10" rx="1" transform="rotate(90 13 3)" />
|
||||
<rect x="13" y="7" width="2" height="10" rx="1" transform="rotate(90 13 7)" />
|
||||
<rect x="13" y="11" width="2" height="10" rx="1" transform="rotate(90 13 11)" fill-opacity="0.3" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<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="M896 320H128V0h768v320z m0 320H128v-256h768v256z m-128 192l-256 192-256-192 192-0.032V704h128v128h192z"
|
||||
></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"
|
||||
/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 12L8 15L5 12L11 12Z" fill-opacity="0.3" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<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="M896 704H128v320h768V704z m0-320H128v256h768v-256z m-128-192l-256-192-256 192 192 0.032V320h128V192h192z"
|
||||
></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"
|
||||
/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 4L8 1L11 4L5 4Z" fill-opacity="0.3" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<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="M320 128v768H0V128h320z m320 0v768h-256V128h256z m192 128l192 256-192 256-0.032-192H704v-128h128V256z"
|
||||
></path>
|
||||
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 fill-rule="evenodd" clip-rule="evenodd" d="M12 5L15 8L12 11L12 5Z" fill-opacity="0.3" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<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="M704 128v768h320V128H704zM384 128v768h256V128h-256zM192 256l-192 256 192 256 0.032-192H320v-128H192V256z"
|
||||
></path>
|
||||
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 fill-rule="evenodd" clip-rule="evenodd" d="M4 11L1 8L4 5L4 11Z" fill-opacity="0.3" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<path d="M480 1024V0h64v1024h-64z m128-64V64h320v896H608zM96 960V64h320v896H96z"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<path d="M416 160H96v704h320V160z m384 0H480v704h320V160z m128-160h-64v1024h64V0z"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<path d="M608 160h320v704H608V160zM224 160h320v704H224V160zM96 0h64v1024H96V0z"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,32 @@
|
||||
<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="M320 864H192v160H128v-160H0V160h128V0h64v160h128v704z m704 0h-128v160h-64v-160h-128V160h128V0h64v160h128v704z"
|
||||
></path>
|
||||
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
|
||||
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>
|
||||
</template>
|
||||
|
||||
@ -1,5 +1,31 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<path d="M64 1024H0V0h64v1024z m384-160H128V160h320v704z m448 0H576V160h320v704z m128 160h-64V0h64v1024z"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
@ -29,6 +29,7 @@ defineProps<{
|
||||
isCompare?: boolean;
|
||||
disabled?: boolean;
|
||||
size?: 'large' | 'default' | 'small';
|
||||
theme?: string;
|
||||
prop?: string;
|
||||
}>();
|
||||
|
||||
@ -112,10 +113,14 @@ const formConfig = defineFormConfig([
|
||||
childType: 'button',
|
||||
labelWidth: '68px',
|
||||
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-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',
|
||||
labelWidth: '68px',
|
||||
options: [
|
||||
{ value: 'left', icon: markRaw(AlignLeft), tooltip: '左对齐 row' },
|
||||
{ value: 'center', icon: markRaw(AlignCenter), tooltip: '居中对齐 center' },
|
||||
{ value: 'right', icon: markRaw(AlignRight), tooltip: '右对齐 right' },
|
||||
{ value: 'left', icon: markRaw(AlignLeft), tooltip: '左对齐 row', text: '左对齐' },
|
||||
{ value: 'center', icon: markRaw(AlignCenter), tooltip: '居中对齐 center', text: '居中对齐' },
|
||||
{ value: 'right', icon: markRaw(AlignRight), tooltip: '右对齐 right', text: '右对齐' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
@ -26,11 +26,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { useTheme } from '@tmagic/design';
|
||||
import type { ContainerChangeEventData } from '@tmagic/form';
|
||||
import { defineFormConfig, MContainer } from '@tmagic/form';
|
||||
import type { StyleSchema } from '@tmagic/schema';
|
||||
|
||||
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 {
|
||||
FlexDirectionColumn,
|
||||
@ -46,12 +54,13 @@ import {
|
||||
JustifyContentSpaceBetween,
|
||||
} from '../icons/justify-content';
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
values: Partial<StyleSchema>;
|
||||
lastValues?: Partial<StyleSchema>;
|
||||
isCompare?: boolean;
|
||||
disabled?: boolean;
|
||||
size?: 'large' | 'default' | 'small';
|
||||
theme?: string;
|
||||
prop?: string;
|
||||
}>();
|
||||
|
||||
@ -60,19 +69,42 @@ const emit = defineEmits<{
|
||||
addDiffCount: [];
|
||||
}>();
|
||||
|
||||
const displayTheme = useTheme(props);
|
||||
|
||||
const formConfig = defineFormConfig([
|
||||
{
|
||||
name: 'display',
|
||||
text: '模式',
|
||||
type: 'radioGroup',
|
||||
childType: 'button',
|
||||
labelWidth: '68px',
|
||||
labelWidth: '90px',
|
||||
iconSize: '24px',
|
||||
options: [
|
||||
{ value: 'inline', icon: markRaw(DisplayInline), tooltip: '内联布局 inline' },
|
||||
{ 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' },
|
||||
{
|
||||
value: 'inline',
|
||||
icon: markRaw(DisplayInline),
|
||||
tooltip: '内联布局 inline',
|
||||
},
|
||||
{
|
||||
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: '主轴方向',
|
||||
type: 'radioGroup',
|
||||
childType: 'button',
|
||||
labelWidth: '68px',
|
||||
labelWidth: '90px',
|
||||
iconSize: '24px',
|
||||
options: [
|
||||
{ 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',
|
||||
icon: markRaw(FlexDirectionColumnReverse),
|
||||
@ -98,13 +139,22 @@ const formConfig = defineFormConfig([
|
||||
text: '主轴对齐',
|
||||
type: 'radioGroup',
|
||||
childType: 'button',
|
||||
labelWidth: '68px',
|
||||
labelWidth: '90px',
|
||||
iconSize: '24px',
|
||||
options: [
|
||||
{ value: 'flex-start', icon: markRaw(JustifyContentFlexStart), tooltip: '左对齐 flex-start' },
|
||||
{ value: 'flex-end', icon: markRaw(JustifyContentFlexEnd), tooltip: '右对齐 flex-end' },
|
||||
{ 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',
|
||||
},
|
||||
@ -113,13 +163,22 @@ const formConfig = defineFormConfig([
|
||||
text: '辅轴对齐',
|
||||
type: 'radioGroup',
|
||||
childType: 'button',
|
||||
labelWidth: '68px',
|
||||
labelWidth: '90px',
|
||||
iconSize: '24px',
|
||||
options: [
|
||||
{ value: 'flex-start', icon: markRaw(JustifyContentFlexStart), tooltip: '左对齐 flex-start' },
|
||||
{ value: 'flex-end', icon: markRaw(JustifyContentFlexEnd), tooltip: '右对齐 flex-end' },
|
||||
{ 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: 'flex-start', icon: markRaw(AlignItemsFlexStart), tooltip: '左对齐 flex-start' },
|
||||
{ value: 'flex-end', icon: markRaw(AlignItemsFlexEnd), tooltip: '右对齐 flex-end' },
|
||||
{ value: 'center', icon: markRaw(AlignItemsCenter), tooltip: '居中 center' },
|
||||
{
|
||||
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',
|
||||
},
|
||||
@ -127,8 +186,9 @@ const formConfig = defineFormConfig([
|
||||
name: 'flexWrap',
|
||||
text: '换行',
|
||||
type: 'radioGroup',
|
||||
childType: 'button',
|
||||
labelWidth: '68px',
|
||||
childType: displayTheme.value !== 'magic-admin' ? 'button' : 'default',
|
||||
labelWidth: '90px',
|
||||
iconSize: '24px',
|
||||
options: [
|
||||
{ value: 'nowrap', text: '不换行', tooltip: '不换行 nowrap' },
|
||||
{ value: 'wrap', text: '正换行', tooltip: '第一行在上方 wrap' },
|
||||
@ -141,17 +201,22 @@ const formConfig = defineFormConfig([
|
||||
items: [
|
||||
{
|
||||
name: 'width',
|
||||
text: '宽度',
|
||||
labelWidth: '68px',
|
||||
text: '宽度(px)',
|
||||
labelWidth: '90px',
|
||||
type: 'data-source-field-select',
|
||||
fieldConfig: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
items: [
|
||||
{
|
||||
name: 'height',
|
||||
text: '高度',
|
||||
labelWidth: '68px',
|
||||
text: '高度(px)',
|
||||
labelWidth: '90px',
|
||||
type: 'data-source-field-select',
|
||||
fieldConfig: {
|
||||
type: 'text',
|
||||
@ -166,7 +231,7 @@ const formConfig = defineFormConfig([
|
||||
type: 'data-source-field-select',
|
||||
text: 'overflow',
|
||||
name: 'overflow',
|
||||
labelWidth: '68px',
|
||||
labelWidth: '90px',
|
||||
checkStrictly: false,
|
||||
dataSourceFieldType: ['string'],
|
||||
fieldConfig: {
|
||||
@ -184,11 +249,16 @@ const formConfig = defineFormConfig([
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
items: [
|
||||
{
|
||||
type: 'data-source-field-select',
|
||||
text: '透明度',
|
||||
text: '透明度(%)',
|
||||
name: 'opacity',
|
||||
labelWidth: '68px',
|
||||
labelWidth: '90px',
|
||||
dataSourceFieldType: ['string', 'number'],
|
||||
fieldConfig: {
|
||||
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 './hooks';
|
||||
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 TMagicCodeEditor } from './layouts/CodeEditor.vue';
|
||||
export { default as editorService } from './services/editor';
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
<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="nav"></slot>
|
||||
@ -97,6 +101,8 @@ const props = defineProps<{
|
||||
pageFilterFunction?: (_page: MPage | MPageFragment, _keyword: string) => boolean;
|
||||
/** 是否隐藏左侧面板 */
|
||||
hideSidebar?: boolean;
|
||||
/** 主题名称,会在根节点追加 `m-editor--<theme>` 修饰类 */
|
||||
theme?: string;
|
||||
}>();
|
||||
|
||||
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 () => {
|
||||
initSeq += 1;
|
||||
const seq = initSeq;
|
||||
|
||||
if (!node.value) {
|
||||
if (seq !== initSeq || !mounted) return;
|
||||
curFormConfig.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
@ -126,6 +143,7 @@ watchEffect(init);
|
||||
propsService.on('props-configs-change', init);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
mounted = false;
|
||||
propsService.off('props-configs-change', init);
|
||||
});
|
||||
|
||||
|
||||
@ -155,7 +155,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
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 MIcon from '@editor/components/Icon.vue';
|
||||
@ -253,7 +253,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
|
||||
[SideItemKey.COMPONENT_LIST]: {
|
||||
$key: SideItemKey.COMPONENT_LIST,
|
||||
type: 'component',
|
||||
icon: Goods,
|
||||
icon: Box,
|
||||
text: '组件',
|
||||
component: ComponentListPanel,
|
||||
slots: {},
|
||||
@ -261,7 +261,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
|
||||
layer: {
|
||||
$key: SideItemKey.LAYER,
|
||||
type: 'component',
|
||||
icon: List,
|
||||
icon: Finished,
|
||||
text: '已选组件',
|
||||
props: {
|
||||
layerContentMenu: props.layerContentMenu,
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { App } from 'vue';
|
||||
import { type App } from 'vue';
|
||||
|
||||
import type { DesignPluginOptions } from '@tmagic/design';
|
||||
import designPlugin from '@tmagic/design';
|
||||
@ -56,6 +56,8 @@ const defaultInstallOpt: EditorInstallOptions = {
|
||||
customCreateMonacoEditor: (monaco, codeEditorEl, options) => monaco.editor.create(codeEditorEl, options),
|
||||
customCreateMonacoDiffEditor: (monaco, codeEditorEl, options) =>
|
||||
monaco.editor.createDiffEditor(codeEditorEl, options),
|
||||
|
||||
flat: false,
|
||||
};
|
||||
|
||||
export default {
|
||||
@ -69,7 +71,6 @@ export default {
|
||||
|
||||
app.config.globalProperties.$TMAGIC_EDITOR = option;
|
||||
setEditorConfig(option);
|
||||
|
||||
app.component(`${Editor.name || 'MEditor'}`, Editor);
|
||||
app.component('magic-code-editor', CodeEditor);
|
||||
app.component('m-fields-ui-select', uiSelect);
|
||||
|
||||
@ -49,13 +49,14 @@ const state = shallowReactive<UiState>({
|
||||
width: 375,
|
||||
height: 817,
|
||||
},
|
||||
// 先给 columnWidth 留占位,真正的 clamp 逻辑在 state 创建完成后
|
||||
// 用 state.minLeftColumnWidth / state.minRightColumnWidth 作为下限重新赋值 —
|
||||
// 这样以后从外部(比如 uiService.set('minLeftColumnWidth', ...))动态改最小宽度,
|
||||
// 只改一处 state 字段即可,不用同步 MIN_* 常量。
|
||||
columnWidth: {
|
||||
left:
|
||||
storageService.getItem(LEFT_COLUMN_WIDTH_STORAGE_KEY, { protocol: Protocol.NUMBER }) || DEFAULT_LEFT_COLUMN_WIDTH,
|
||||
left: 0,
|
||||
center: 0,
|
||||
right:
|
||||
storageService.getItem(RIGHT_COLUMN_WIDTH_STORAGE_KEY, { protocol: Protocol.NUMBER }) ||
|
||||
DEFAULT_RIGHT_COLUMN_WIDTH,
|
||||
right: 0,
|
||||
},
|
||||
minLeftColumnWidth: MIN_LEFT_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 = {
|
||||
async: ['zoom', 'calcZoom'] as const,
|
||||
sync: [] as const,
|
||||
|
||||
@ -1,3 +1,40 @@
|
||||
.m-fields-code-select {
|
||||
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;
|
||||
$border-color: #d9dbdd;
|
||||
|
||||
@ -77,9 +77,9 @@
|
||||
margin-bottom: 5px;
|
||||
|
||||
&:hover {
|
||||
background: #2882e0;
|
||||
background: var(--el-color-primary, #2882e0);
|
||||
color: #fff;
|
||||
border-color: #4e8be1;
|
||||
border-color: var(--el-color-primary, #4e8be1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,10 @@
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&.data-source-field-img-upload,
|
||||
&.data-source-field-big-img-upload {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.m-fields-select-action-button {
|
||||
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%;
|
||||
.fullWidth {
|
||||
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 {
|
||||
margin-left: 20px;
|
||||
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 {
|
||||
&.is-error {
|
||||
margin-bottom: 18px;
|
||||
.el-form-item {
|
||||
&.is-error {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,10 @@
|
||||
width: 100%;
|
||||
|
||||
&-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 {
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
color: #909399;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
color: var(--el-color-primary, #409eff);
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@
|
||||
// 故这里仅作用于「非合并组」的单步条目,避免与卡片样式互相干扰。
|
||||
&.is-current:not(.m-editor-history-list-group.is-merged) {
|
||||
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 {
|
||||
background-color: rgba(64, 158, 255, 0.16);
|
||||
@ -104,7 +104,7 @@
|
||||
|
||||
.m-editor-history-list-item-desc {
|
||||
font-weight: 600;
|
||||
color: #409eff;
|
||||
color: var(--el-color-primary, #409eff);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,11 +177,11 @@
|
||||
&.is-current {
|
||||
background-color: rgba(64, 158, 255, 0.08);
|
||||
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 阴影
|
||||
|
||||
.m-editor-history-list-group-head {
|
||||
color: #409eff;
|
||||
color: var(--el-color-primary, #409eff);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -216,7 +216,7 @@
|
||||
}
|
||||
|
||||
&.is-current {
|
||||
color: #409eff;
|
||||
color: var(--el-color-primary, #409eff);
|
||||
font-weight: 600;
|
||||
background-color: rgba(64, 158, 255, 0.08);
|
||||
border-radius: 3px;
|
||||
@ -231,7 +231,7 @@
|
||||
font-size: 10px;
|
||||
line-height: 16px;
|
||||
color: #fff;
|
||||
background-color: #409eff;
|
||||
background-color: var(--el-color-primary, #409eff);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@ -395,7 +395,7 @@
|
||||
border-radius: 2px;
|
||||
font-size: 10px;
|
||||
line-height: 16px;
|
||||
color: #409eff;
|
||||
color: var(--el-color-primary, #409eff);
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
@ -471,7 +471,7 @@
|
||||
flex: 1 1 auto;
|
||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
font-size: 11px;
|
||||
color: #409eff;
|
||||
color: var(--el-color-primary, #409eff);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
@ -13,7 +13,11 @@
|
||||
font-weight: 400;
|
||||
box-sizing: border-box;
|
||||
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;
|
||||
|
||||
> div {
|
||||
|
||||
@ -129,7 +129,8 @@
|
||||
> .m-container-tab {
|
||||
> .tmagic-design-tabs {
|
||||
> .el-tabs__content {
|
||||
padding-top: 55px;
|
||||
margin-top: var(--el-tabs-header-height);
|
||||
padding-top: 15px;
|
||||
}
|
||||
> .el-tabs__header.is-top {
|
||||
position: absolute;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
.border-box-container {
|
||||
display: flex;
|
||||
--unuse-border-style: dashed;
|
||||
.border-icon-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -28,24 +29,32 @@
|
||||
border-color: var(--el-color-success, var(--td-success-color, #2ba471));
|
||||
}
|
||||
&.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-color: var(--el-color-primary, var(--td-brand-color, #0052d9));
|
||||
}
|
||||
&.border-icon-top {
|
||||
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-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-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-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 "./data-source-field.scss";
|
||||
@use "./data-source-field-select.scss";
|
||||
@use "./display-conds.scss";
|
||||
@use "./scroll-bar.scss";
|
||||
@use "./ui-select.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;
|
||||
}
|
||||
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 interface EditorInstallOptions {
|
||||
flat?: boolean;
|
||||
parseDSL: <T = any>(dsl: string) => T;
|
||||
customCreateMonacoEditor: (
|
||||
monaco: typeof import('monaco-editor'),
|
||||
|
||||
@ -16,14 +16,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { EditorInstallOptions } from '@editor/type';
|
||||
|
||||
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 => {
|
||||
$TMAGIC_EDITOR = option;
|
||||
isGlobalFlat.value = option.flat ?? false;
|
||||
};
|
||||
|
||||
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 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 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_RIGHT_COLUMN_WIDTH = 300;
|
||||
|
||||
|
||||
@ -180,6 +180,7 @@ export const advancedTabConfig: TabPaneConfig = {
|
||||
name: NODE_DISABLE_CODE_BLOCK_KEY,
|
||||
text: '禁用代码块',
|
||||
type: 'switch',
|
||||
labelPosition: 'left',
|
||||
defaultValue: false,
|
||||
extra: '开启后,配置的代码块将不会被执行',
|
||||
},
|
||||
@ -187,15 +188,17 @@ export const advancedTabConfig: TabPaneConfig = {
|
||||
name: NODE_DISABLE_DATA_SOURCE_KEY,
|
||||
text: '禁用数据源',
|
||||
type: 'switch',
|
||||
labelPosition: 'left',
|
||||
defaultValue: false,
|
||||
extra: '开启后,组件内配置的数据源相关配置将不会被编译,显隐条件将失效',
|
||||
},
|
||||
{
|
||||
name: 'created',
|
||||
text: 'created',
|
||||
text: 'Created',
|
||||
flat: true,
|
||||
labelPosition: 'top',
|
||||
type: 'code-select',
|
||||
extra: '组件初始化时执行',
|
||||
titleExtra: '组件初始化时执行',
|
||||
rules: [
|
||||
{ typeMatch: true, trigger: 'change' },
|
||||
{
|
||||
@ -210,10 +213,12 @@ export const advancedTabConfig: TabPaneConfig = {
|
||||
},
|
||||
{
|
||||
name: 'mounted',
|
||||
text: 'mounted',
|
||||
text: 'Mounted',
|
||||
labelPosition: 'top',
|
||||
flat: true,
|
||||
|
||||
type: 'code-select',
|
||||
extra: '组件挂载到dom时执行',
|
||||
titleExtra: '组件挂载到dom时执行',
|
||||
rules: [
|
||||
{ typeMatch: true, trigger: 'change' },
|
||||
{
|
||||
@ -228,8 +233,9 @@ export const advancedTabConfig: TabPaneConfig = {
|
||||
},
|
||||
{
|
||||
name: 'display',
|
||||
text: 'display',
|
||||
extra: '控制组件是否渲染,关系的代码块返回值为false时不渲染',
|
||||
text: 'Display',
|
||||
flat: true,
|
||||
titleExtra: '控制组件是否渲染,关系的代码块返回值为false时不渲染',
|
||||
labelPosition: 'top',
|
||||
type: 'code-select',
|
||||
rules: [
|
||||
@ -263,10 +269,18 @@ export const displayTabConfig: TabPaneConfig<DisplayCondsConfig> = {
|
||||
extra: (_state, { model }) =>
|
||||
`条件成立时${model[NODE_CONDS_RESULT_KEY] ? '隐藏' : '显示'},不成立时${model[NODE_CONDS_RESULT_KEY] ? '显示' : '隐藏'};<br />同一条件组内的所有条件配置同时成立时表示该条件组成立,任意一个条件组成立时表示条件成立(条件组内为且的关系,条件组间为或的关系);<br />条件为空时表示成立;`,
|
||||
},
|
||||
{
|
||||
text: '条件组配置',
|
||||
static: true,
|
||||
className: 'display-conds-title',
|
||||
},
|
||||
{
|
||||
type: 'display-conds',
|
||||
name: NODE_CONDS_KEY,
|
||||
titlePrefix: '条件组',
|
||||
flat: true,
|
||||
fixed: 'right',
|
||||
operateColWidth: 112,
|
||||
defaultValue: [],
|
||||
rules: [{ typeMatch: true }],
|
||||
},
|
||||
@ -303,10 +317,13 @@ export const fillConfig = (
|
||||
name: 'id',
|
||||
text: 'ID',
|
||||
type: 'text',
|
||||
disabled: true,
|
||||
// 走纯文本渲染,避免出现 disabled 的灰底输入框;append 仍正常显示。
|
||||
static: true,
|
||||
|
||||
append: {
|
||||
type: 'button',
|
||||
text: '复制',
|
||||
type: 'icon',
|
||||
text: 'https://vip.image.video.qpic.cn/vupload/20260615/36cf7e1781493669935.svg',
|
||||
extra: '复制',
|
||||
handler: (vm, { model }) => {
|
||||
navigator.clipboard
|
||||
.writeText(`${model.id}`)
|
||||
@ -339,6 +356,7 @@ export const fillConfig = (
|
||||
const tabConfig: TabConfig = {
|
||||
type: 'tab',
|
||||
labelWidth,
|
||||
className: 'magic-right-panel-tabs-top',
|
||||
items: [
|
||||
{
|
||||
title: '属性',
|
||||
|
||||
@ -44,6 +44,12 @@ vi.mock('@tmagic/design', () => ({
|
||||
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 = {}) => ({
|
||||
@ -108,13 +114,11 @@ describe('CodeSelect', () => {
|
||||
expect((props.model.cs as any).hookData).toEqual([]);
|
||||
});
|
||||
|
||||
test('codeType row items 配置正确', () => {
|
||||
test('codeType items 配置正确', () => {
|
||||
const wrapper = mount(CodeSelect, { props: baseProps() as any });
|
||||
const container = wrapper.findComponent({ name: 'MContainer' });
|
||||
const config = container.props('config') as any;
|
||||
const row = config.items[0];
|
||||
expect(row.type).toBe('row');
|
||||
const codeTypeSelect = row.items[0];
|
||||
const codeTypeSelect = config.items[0];
|
||||
expect(codeTypeSelect.name).toBe('codeType');
|
||||
const setModel = vi.fn();
|
||||
codeTypeSelect.onChange(undefined, 'data-source-method', { setModel });
|
||||
@ -128,9 +132,8 @@ describe('CodeSelect', () => {
|
||||
const wrapper = mount(CodeSelect, { props: baseProps() as any });
|
||||
const container = wrapper.findComponent({ name: 'MContainer' });
|
||||
const config = container.props('config') as any;
|
||||
const row = config.items[0];
|
||||
const codeIdCol = row.items[1];
|
||||
const dsCol = row.items[2];
|
||||
const codeIdCol = config.items[1];
|
||||
const dsCol = config.items[2];
|
||||
expect(codeIdCol.display(undefined, { model: { codeType: 'code' } })).toBe(true);
|
||||
expect(codeIdCol.display(undefined, { model: { codeType: 'data-source-method' } })).toBe(false);
|
||||
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 container = wrapper.findComponent({ name: 'MContainer' });
|
||||
const config = container.props('config') as any;
|
||||
const row = config.items[0];
|
||||
const codeIdCol = row.items[1];
|
||||
const dsCol = row.items[2];
|
||||
const codeIdCol = config.items[1];
|
||||
const dsCol = config.items[2];
|
||||
expect(codeIdCol.type).toBe('code-select-col');
|
||||
expect(codeIdCol.rules).toEqual([{ typeMatch: true, trigger: 'change' }]);
|
||||
expect(dsCol.type).toBe('data-source-method-select');
|
||||
@ -156,9 +158,8 @@ describe('CodeSelect', () => {
|
||||
const wrapper = mount(CodeSelect, { props: baseProps() as any });
|
||||
const container = wrapper.findComponent({ name: 'MContainer' });
|
||||
const config = container.props('config') as any;
|
||||
const row = config.items[0];
|
||||
expect(row.items[1].notEditable()).toBe(true);
|
||||
expect(row.items[2].notEditable()).toBe(true);
|
||||
expect(config.items[1].notEditable()).toBe(true);
|
||||
expect(config.items[2].notEditable()).toBe(true);
|
||||
codeBlockService.getEditStatus.mockReturnValue(true);
|
||||
dataSourceService.get.mockReturnValue(true);
|
||||
});
|
||||
|
||||
@ -526,9 +526,11 @@ describe('EventSelect', () => {
|
||||
],
|
||||
};
|
||||
const wrapper = mount(EventSelect, { props: baseProps({ model: m }) as any });
|
||||
const buttons = wrapper.findAll('button');
|
||||
const deleteBtn = buttons[buttons.length - 1];
|
||||
await deleteBtn.trigger('click');
|
||||
// 用 class 选择器直击 panel header 里的删除按钮:模板里同时存在顶部 / 底部「添加事件」按钮,
|
||||
// 早期靠 `buttons[length - 1]` 取最后一个会误选到底部添加按钮,导致 events 没被删减。
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import {
|
||||
ElAlert,
|
||||
ElAutocomplete,
|
||||
ElBadge,
|
||||
ElButton,
|
||||
@ -48,6 +49,7 @@ import {
|
||||
} from 'element-plus';
|
||||
|
||||
import type {
|
||||
AlertProps,
|
||||
AutocompleteProps,
|
||||
BadgeProps,
|
||||
ButtonProps,
|
||||
@ -104,6 +106,11 @@ const adapter: DesignPluginOptions = {
|
||||
props: (props: AutocompleteProps) => props,
|
||||
},
|
||||
|
||||
alert: {
|
||||
component: ElAlert as any,
|
||||
props: (props: AlertProps) => props,
|
||||
},
|
||||
|
||||
badge: {
|
||||
component: ElBadge as any,
|
||||
props: (props: BadgeProps) => props,
|
||||
@ -261,7 +268,7 @@ const adapter: DesignPluginOptions = {
|
||||
|
||||
select: {
|
||||
component: ElSelect as any,
|
||||
props: (props: SelectProps) => props,
|
||||
props: (props: SelectProps) => ({ remoteShowSuffix: true, ...props }),
|
||||
},
|
||||
|
||||
step: {
|
||||
|
||||
@ -50,6 +50,9 @@ export interface FieldProps<T = any> {
|
||||
size?: 'large' | 'default' | 'small';
|
||||
lastValues?: Record<string, any>;
|
||||
isCompare?: boolean;
|
||||
text?: string;
|
||||
labelWidth?: string | number;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -110,6 +113,8 @@ export interface FormItem {
|
||||
name?: string | number;
|
||||
/** 额外的提示信息,和 help 类似,当提示文案同时出现时,可以使用这个。 */
|
||||
extra?: string | FilterFunction<string>;
|
||||
/** 额外的提示信息,和 extra 类似,着重强调时用这个显示,优先级高于extra*/
|
||||
extraTips?: string;
|
||||
/** 配置提示信息 */
|
||||
tooltip?: ToolTipConfigType | FilterFunction<ToolTipConfigType>;
|
||||
/** 是否置灰 */
|
||||
@ -130,6 +135,8 @@ export interface FormItem {
|
||||
trim?: boolean;
|
||||
/** 默认值 */
|
||||
defaultValue?: any | DefaultValueFunction;
|
||||
/** 标题下方的额外提示信息 */
|
||||
titleExtra?: string;
|
||||
/** 表单验证规则 */
|
||||
rules?: Rule[];
|
||||
extensible?: boolean;
|
||||
@ -139,6 +146,9 @@ export interface FormItem {
|
||||
style?: Record<string, any>;
|
||||
fieldStyle?: Record<string, any>;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
flat?: boolean;
|
||||
fixed?: boolean | 'left' | 'right';
|
||||
operateColWidth?: number | string;
|
||||
}
|
||||
// #endregion FormItem
|
||||
|
||||
@ -386,6 +396,12 @@ export interface TextConfig extends FormItem, Input {
|
||||
tooltip?: string;
|
||||
/** 是否可清空 */
|
||||
clearable?: boolean;
|
||||
/**
|
||||
* 是否以纯文本(非输入框)形态渲染。
|
||||
* 开启后字段值以 `<span>` 展示,不参与编辑,但保留 `prepend` / `append` 槽位,
|
||||
* 适合用于「只读但需要保留操作按钮(如复制 icon)」的场景。
|
||||
*/
|
||||
static?: boolean;
|
||||
prepend?: string;
|
||||
/** 后置元素,一般为标签或按钮 */
|
||||
append?:
|
||||
@ -393,7 +409,8 @@ export interface TextConfig extends FormItem, Input {
|
||||
| {
|
||||
text: string;
|
||||
value?: 0 | 1;
|
||||
type: 'button';
|
||||
type: 'button' | 'icon';
|
||||
extra?: string;
|
||||
handler?: (
|
||||
mForm: FormState | undefined,
|
||||
data: {
|
||||
@ -529,6 +546,7 @@ export interface SwitchConfig extends FormItem {
|
||||
export interface RadioGroupConfig extends FormItem {
|
||||
type: 'radio-group' | 'radioGroup';
|
||||
childType?: 'default' | 'button';
|
||||
iconSize?: string;
|
||||
options: {
|
||||
value: string | number | boolean;
|
||||
text?: string;
|
||||
@ -718,6 +736,7 @@ export interface TabPaneConfig<T = never> {
|
||||
title: string;
|
||||
lazy?: boolean;
|
||||
labelWidth?: string;
|
||||
titleExtra?: string;
|
||||
items: FormConfig<T>;
|
||||
display?: boolean | 'expand' | FilterFunction<boolean | 'expand'>;
|
||||
onTabClick?: (mForm: FormState | undefined, tab: any, data: any) => void;
|
||||
@ -772,6 +791,11 @@ export interface PanelConfig<T = never> extends FormItem, ContainerCommonConfig<
|
||||
expand?: boolean;
|
||||
title?: string;
|
||||
schematic?: string;
|
||||
/**
|
||||
* 是否「铺平」:去掉 panel 自身的卡片视觉(背景 / 阴影 / 边框 / 圆角 / 内边距等),
|
||||
* 让 panel 在视觉上与外层容器融为一体。仍保留 header、折叠等交互。
|
||||
*/
|
||||
flat?: boolean;
|
||||
}
|
||||
// #endregion PanelConfig
|
||||
|
||||
@ -825,6 +849,8 @@ export interface TableConfig<T = never> extends TableGroupListCommonConfig {
|
||||
/** 操作栏宽度 */
|
||||
operateColWidth?: number | string;
|
||||
pagination?: boolean;
|
||||
/** 表格标题前缀 ,新增xxx,暂无xxx数据,表明表格内容是什么 */
|
||||
titlePrefix?: string;
|
||||
/** 是否显示删除按钮 */
|
||||
delete?: (model: any, index: number, values: 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>;
|
||||
itemExtra?: string | FilterFunction<string>;
|
||||
expandAll?: boolean;
|
||||
/**
|
||||
* 铺平模式:去除每个 item 外层卡片视觉(背景、阴影、顶/左/右边框、header 分隔线、内边距),
|
||||
* 但保留标题 + 删除/复制/上/下移/移动至等头部按钮。适合外层已有自己的视觉容器、
|
||||
* 不希望再嵌套一层 card 的场景(例如 `EventSelect` 里的 actions 列表)。
|
||||
* item 之间仍保留底部分隔线,最后一项除外。
|
||||
*/
|
||||
flat?: boolean;
|
||||
/**
|
||||
* 默认展开的数量,用于控制分组列表默认展示的项数
|
||||
* 当设置为数字时,表示默认展开指定数量的项
|
||||
|
||||
@ -70,6 +70,7 @@ export interface CodeSelectConfig extends FormItem {
|
||||
// #region CodeSelectColConfig
|
||||
export interface CodeSelectColConfig extends FormItem {
|
||||
type: 'code-select-col';
|
||||
|
||||
/** 是否可以编辑代码块,disable表示的是是否可以选择代码块 */
|
||||
notEditable?: boolean | FilterFunction;
|
||||
}
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<TMagicForm
|
||||
class="m-form"
|
||||
:class="[
|
||||
'm-form',
|
||||
effectiveTheme ? `m-form--${effectiveTheme}` : '',
|
||||
effectiveTheme ? `m-theme--${effectiveTheme}` : '',
|
||||
]"
|
||||
ref="tMagicForm"
|
||||
:model="values"
|
||||
:label-width="labelWidth"
|
||||
@ -32,10 +36,22 @@
|
||||
</template>
|
||||
|
||||
<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 { TMagicForm, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
||||
import { M_THEME_KEY, TMagicForm, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
||||
import { setValueByKeyPath } from '@tmagic/utils';
|
||||
|
||||
import Container from './containers/Container.vue';
|
||||
@ -116,6 +132,15 @@ const props = withDefaults(
|
||||
* 通过 provide 下发,对整棵表单的所有层级 Container 生效,只需在 MForm 这一层传一次。
|
||||
*/
|
||||
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: () => [],
|
||||
@ -149,6 +174,27 @@ const fields = new Map<string, any>();
|
||||
|
||||
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 实现说明:
|
||||
*
|
||||
@ -165,13 +211,21 @@ const requestFuc = getConfig('request') as Function;
|
||||
* 把最新值刷进 formState。
|
||||
* - accessor 描述符(`{ get stage() { return ... } }`)按原样写入,调用方可以控制
|
||||
* 读时求值,每次读取都会重新执行 getter。
|
||||
*
|
||||
* 4. `popperClass` 会自动拼接 `themeClass`:调用方传入的 `popperClass` + 当前主题
|
||||
* 修饰类(含祖先 `<MEditor>` provide 的主题)。这样所有透传到 Element Plus 弹层
|
||||
* `popper-class` 的字段(Select / DateTime / Cascader 等)能自带主题作用域。
|
||||
*/
|
||||
const formState: FormState = reactive<FormState>({
|
||||
get keyProp() {
|
||||
return props.keyProp;
|
||||
},
|
||||
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() {
|
||||
return props.config;
|
||||
@ -250,6 +304,13 @@ watchEffect(async (onCleanup) => {
|
||||
|
||||
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。
|
||||
// 用 getter 对象保证读取时回到最新的 props 值,维持响应式。
|
||||
provide(FORM_DIFF_CONFIG_KEY, {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<TMagicDialog
|
||||
v-model="dialogVisible"
|
||||
class="m-form-dialog"
|
||||
:class="['m-form-dialog', effectiveTheme ? `m-theme--${effectiveTheme}` : '']"
|
||||
top="20px"
|
||||
append-to-body
|
||||
:title="title"
|
||||
@ -34,6 +34,7 @@
|
||||
:use-field-text-in-error="useFieldTextInError"
|
||||
:type-match-valid="typeMatchValid"
|
||||
:extend-state="extendState"
|
||||
:theme="effectiveTheme"
|
||||
@change="changeHandler"
|
||||
></Form>
|
||||
<slot></slot>
|
||||
@ -66,9 +67,9 @@
|
||||
</template>
|
||||
|
||||
<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 { ContainerChangeEventData, FormConfig, FormState, FormValue, StepConfig } from './schema';
|
||||
@ -104,6 +105,12 @@ const props = withDefaults(
|
||||
useFieldTextInError?: boolean;
|
||||
/** 透传给内部 `MForm`,用于扩展 `formState`(如注入 `$message` / `$store` 等) */
|
||||
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: () => [],
|
||||
@ -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 form = ref<InstanceType<typeof Form>>();
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<TMagicDrawer
|
||||
class="m-form-drawer"
|
||||
:class="['m-form-drawer', effectiveTheme ? `m-theme--${effectiveTheme}` : '']"
|
||||
ref="drawer"
|
||||
v-model="visible"
|
||||
v-model="dialogVisible"
|
||||
:title="title"
|
||||
:close-on-press-escape="closeOnPressEscape"
|
||||
:append-to-body="true"
|
||||
@ -16,7 +16,7 @@
|
||||
@close="closeHandler"
|
||||
@closed="closedHandler"
|
||||
>
|
||||
<div v-if="visible" ref="drawerBody" class="m-drawer-body">
|
||||
<div v-if="dialogVisible" ref="drawerBody" class="m-drawer-body">
|
||||
<Form
|
||||
ref="form"
|
||||
:size="size"
|
||||
@ -31,6 +31,7 @@
|
||||
:use-field-text-in-error="useFieldTextInError"
|
||||
:type-match-valid="typeMatchValid"
|
||||
:extend-state="extendState"
|
||||
:theme="effectiveTheme"
|
||||
@change="changeHandler"
|
||||
></Form>
|
||||
<slot></slot>
|
||||
@ -57,18 +58,18 @@
|
||||
</template>
|
||||
|
||||
<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 type { ContainerChangeEventData, FormConfig, FormState, FormValue } from './schema';
|
||||
|
||||
defineOptions({
|
||||
name: 'MFormDialog',
|
||||
name: 'MFormDrawer',
|
||||
});
|
||||
|
||||
withDefaults(
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
config?: FormConfig;
|
||||
values?: Object;
|
||||
@ -92,6 +93,12 @@ withDefaults(
|
||||
beforeClose?: (_done: (_cancel?: boolean) => void) => void;
|
||||
/** 透传给内部 `MForm`,用于扩展 `formState`(如注入 `$message` / `$store` 等) */
|
||||
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,
|
||||
@ -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 drawer = ref<InstanceType<typeof TMagicDrawer>>();
|
||||
const form = ref<InstanceType<typeof Form>>();
|
||||
const drawerBody = ref<HTMLDivElement>();
|
||||
const visible = ref(false);
|
||||
const dialogVisible = ref(false);
|
||||
const saveFetch = ref(false);
|
||||
const bodyHeight = ref(0);
|
||||
|
||||
@ -148,25 +159,42 @@ const closedHandler = () => {
|
||||
};
|
||||
|
||||
const show = () => {
|
||||
visible.value = true;
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
const hide = () => {
|
||||
visible.value = false;
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
const close = () => {
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
const cancel = () => {
|
||||
hide();
|
||||
};
|
||||
|
||||
/** 用于关闭 Drawer, 该方法会调用传入的 before-close 方法 */
|
||||
const 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({
|
||||
form,
|
||||
saveFetch,
|
||||
bodyHeight,
|
||||
|
||||
close,
|
||||
show,
|
||||
hide,
|
||||
save,
|
||||
cancel,
|
||||
handleClose,
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -35,6 +35,9 @@
|
||||
:label-title="config.labelTitle"
|
||||
:text="text"
|
||||
></FormLabel>
|
||||
<span class="m-form-tip m-form-title-extra" v-if="config.titleExtra && config.labelPosition === 'top'">
|
||||
{{ config.titleExtra }}</span
|
||||
>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
@ -222,17 +225,23 @@
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<div style="text-align: center" v-if="config.expand && type !== 'fieldset'">
|
||||
<TMagicButton type="primary" size="small" :disabled="false" link @click="expandHandler">{{
|
||||
expand ? '收起配置' : '展开更多配置'
|
||||
}}</TMagicButton>
|
||||
<div class="m-form-container-expand" v-if="config.expand && type !== 'fieldset'">
|
||||
<TMagicButton
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="false"
|
||||
link
|
||||
@click="expandHandler"
|
||||
:icon="expand ? ArrowUp : ArrowDown"
|
||||
>{{ expand ? '收起配置' : '展开更多配置' }}</TMagicButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
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 { TMagicButton, TMagicFormItem, TMagicIcon, TMagicTooltip } from '@tmagic/design';
|
||||
@ -440,8 +449,10 @@ const formItemProps = computed(() => ({
|
||||
prop: itemProp.value,
|
||||
labelWidth: itemLabelWidth.value,
|
||||
labelPosition: props.config.labelPosition,
|
||||
|
||||
rules: rule.value,
|
||||
extra: filterFunction(mForm, props.config.extra, props),
|
||||
extraTips: props.config.extraTips,
|
||||
}));
|
||||
|
||||
const itemLabelWidth = computed(() => props.config.labelWidth ?? props.labelWidth);
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="m-fields-group-list">
|
||||
<div v-if="config.extra" v-html="config.extra" style="color: rgba(0, 0, 0, 0.45)"></div>
|
||||
<div v-if="!model[name] || !model[name].length" class="el-table__empty-block">
|
||||
<span class="el-table__empty-text t-table__empty">暂无数据</span>
|
||||
<span class="el-table__empty-text t-table__empty">暂无{{ config.titlePrefix || '' }}数据</span>
|
||||
</div>
|
||||
|
||||
<MFieldsGroupListItem
|
||||
|
||||
@ -1,26 +1,29 @@
|
||||
<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>
|
||||
<div>
|
||||
<TMagicButton link :disabled="disabled" @click="expandHandler">
|
||||
<TMagicIcon><CaretBottom v-if="expand" /><CaretRight v-else /></TMagicIcon><span v-html="title"></span>
|
||||
<div class="m-fields-group-list-item-header">
|
||||
<TMagicButton link :disabled="disabled" @click="expandHandler" class="expand-button">
|
||||
<TMagicIcon><ArrowDown v-if="expand" /><ArrowRight v-else /></TMagicIcon>
|
||||
</TMagicButton>
|
||||
|
||||
<TMagicButton
|
||||
v-if="!isCompare"
|
||||
v-show="showDelete"
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
:icon="Delete"
|
||||
:disabled="disabled"
|
||||
@click="removeHandler"
|
||||
></TMagicButton>
|
||||
<span v-html="title"></span>
|
||||
<TMagicTooltip :content="`删除 ${title}`">
|
||||
<TMagicButton
|
||||
v-if="!isCompare"
|
||||
v-show="showDelete"
|
||||
size="default"
|
||||
link
|
||||
class="delete-button"
|
||||
:icon="Delete"
|
||||
:disabled="disabled"
|
||||
@click="removeHandler"
|
||||
></TMagicButton>
|
||||
</TMagicTooltip>
|
||||
|
||||
<TMagicButton
|
||||
v-if="copyable && !isCompare"
|
||||
link
|
||||
size="small"
|
||||
size="default"
|
||||
type="primary"
|
||||
:icon="DocumentCopy"
|
||||
:disabled="disabled"
|
||||
@ -32,18 +35,18 @@
|
||||
<TMagicButton
|
||||
v-show="index !== 0"
|
||||
link
|
||||
size="small"
|
||||
size="default"
|
||||
:disabled="disabled"
|
||||
:icon="CaretTop"
|
||||
:icon="Top"
|
||||
@click="changeOrder(-1)"
|
||||
>上移</TMagicButton
|
||||
>
|
||||
<TMagicButton
|
||||
v-show="index !== length - 1"
|
||||
link
|
||||
size="small"
|
||||
size="default"
|
||||
:disabled="disabled"
|
||||
:icon="CaretBottom"
|
||||
:icon="Bottom"
|
||||
@click="changeOrder(1)"
|
||||
>下移</TMagicButton
|
||||
>
|
||||
@ -107,9 +110,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
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 { filterFunction } from '../utils/form';
|
||||
@ -152,7 +155,10 @@ const rowConfig = computed(() => ({
|
||||
|
||||
const title = computed(() => {
|
||||
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) {
|
||||
|
||||
@ -3,10 +3,17 @@
|
||||
v-if="items && items.length"
|
||||
class="box-card m-form-panel"
|
||||
:body-style="{ display: expand ? 'block' : 'none' }"
|
||||
:flat="config.flat"
|
||||
>
|
||||
<template #header>
|
||||
<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>
|
||||
<slot name="header">
|
||||
<span style="cursor: pointer" @click="expand = !expand">{{ filter(config.title) }}</span>
|
||||
@ -62,7 +69,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
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';
|
||||
|
||||
@ -85,6 +92,7 @@ const props = defineProps<{
|
||||
prop?: string;
|
||||
size?: string;
|
||||
disabled?: boolean;
|
||||
hideExpand?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@ -107,7 +115,6 @@ const onAddDiffCount = () => emit('addDiffCount');
|
||||
|
||||
defineExpose({
|
||||
getExpand: () => expand.value,
|
||||
|
||||
setExpand: (v: boolean) => {
|
||||
expand.value = v;
|
||||
},
|
||||
|
||||
@ -37,11 +37,17 @@
|
||||
:size="addButtonSize"
|
||||
:plain="displayMode === 'table'"
|
||||
:icon="Plus"
|
||||
text
|
||||
:disabled="disabled"
|
||||
v-bind="currentConfig.addButtonConfig?.props || { type: 'primary' }"
|
||||
@click="newHandler"
|
||||
>
|
||||
{{ currentConfig.addButtonConfig?.text || (displayMode === 'table' ? '新增一行' : '新增') }}
|
||||
{{
|
||||
currentConfig.addButtonConfig?.text ||
|
||||
(displayMode === 'table'
|
||||
? `新增一行${groupListConfig.titlePrefix || ''}`
|
||||
: `新增${groupListConfig.titlePrefix || ''}`)
|
||||
}}
|
||||
</TMagicButton>
|
||||
</template>
|
||||
</component>
|
||||
|
||||
@ -9,15 +9,18 @@
|
||||
>
|
||||
</TMagicButton>
|
||||
</TMagicTooltip>
|
||||
|
||||
<TMagicButton
|
||||
v-show="showDelete(index + 1 + currentPage * pageSize - 1)"
|
||||
size="small"
|
||||
type="danger"
|
||||
link
|
||||
title="删除"
|
||||
:icon="config.deleteActionButtonIcon || Delete"
|
||||
:icon="flat ? undefined : config.deleteActionButtonIcon || Delete"
|
||||
@click="removeHandler(index + 1 + currentPage * pageSize - 1)"
|
||||
></TMagicButton>
|
||||
>
|
||||
<template v-if="flat">删除</template>
|
||||
</TMagicButton>
|
||||
|
||||
<TMagicButton
|
||||
v-if="copyable(index + 1 + currentPage * pageSize - 1)"
|
||||
@ -25,10 +28,12 @@
|
||||
size="small"
|
||||
type="primary"
|
||||
title="复制"
|
||||
:icon="config.copyActionButtonIcon || DocumentCopy"
|
||||
:icon="flat ? undefined : config.copyActionButtonIcon || DocumentCopy"
|
||||
:disabled="disabled"
|
||||
@click="copyHandler(index + 1 + currentPage * pageSize - 1)"
|
||||
></TMagicButton>
|
||||
>
|
||||
<template v-if="flat">复制</template>
|
||||
</TMagicButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -53,6 +58,7 @@ const props = defineProps<{
|
||||
row: any;
|
||||
prop?: string;
|
||||
sortKey?: string;
|
||||
flat?: boolean;
|
||||
}>();
|
||||
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
:key="updateKey"
|
||||
@select="selectHandle"
|
||||
@sort-change="sortChangeHandler"
|
||||
:empty-text="`暂无${config.titlePrefix || ''}数据`"
|
||||
></TMagicTable>
|
||||
</TMagicTooltip>
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import { type TableColumnOptions, TMagicIcon, TMagicTooltip } from '@tmagic/desi
|
||||
import type { FormItemConfig, FormState, TableColumnConfig } from '@tmagic/form-schema';
|
||||
|
||||
import type { ContainerChangeEventData } from '../../schema';
|
||||
import { isGlobalFlat } from '../../utils/config';
|
||||
import { display as displayFunc, getDataByPage, sortArray } from '../../utils/form';
|
||||
import Container from '../Container.vue';
|
||||
|
||||
@ -111,23 +112,26 @@ export const useTableColumns = (
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let actionFixed: 'left' | 'right' | undefined = props.config.fixed === false ? undefined : 'left';
|
||||
|
||||
const defaultFixed: 'left' | 'right' = isGlobalFlat.value ? 'right' : 'left';
|
||||
let actionFixed: 'left' | 'right' | undefined = props.config.fixed === false ? undefined : defaultFixed;
|
||||
if (typeof props.config.fixed === 'string' && ['left', 'right'].includes(props.config.fixed)) {
|
||||
actionFixed = props.config.fixed;
|
||||
}
|
||||
|
||||
const actionFlat = props.config.flat === undefined && isGlobalFlat.value ? true : props.config.flat;
|
||||
|
||||
const actionColumn = {
|
||||
props: {
|
||||
label: '操作',
|
||||
fixed: actionFixed,
|
||||
|
||||
width: props.config.operateColWidth ?? (props.config.dropSortHandle && props.config.dropSort ? 132 : 112),
|
||||
align: 'center',
|
||||
},
|
||||
cell: ({ row, $index }: any) =>
|
||||
h(ActionsColumn, {
|
||||
row,
|
||||
flat: actionFlat,
|
||||
index: $index,
|
||||
model: props.model,
|
||||
config: props.config,
|
||||
|
||||
@ -1,5 +1,16 @@
|
||||
<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>
|
||||
<div v-else class="m-fields-link">
|
||||
<TMagicButton link type="primary" @click="editHandler">点击编辑</TMagicButton>
|
||||
@ -18,18 +29,20 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
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 type { FieldProps, FormState, LinkConfig } from '../schema';
|
||||
import { isGlobalFlat } from '../utils/config';
|
||||
import { useAddField } from '../utils/useAddField';
|
||||
|
||||
defineOptions({
|
||||
name: 'MFormLink',
|
||||
});
|
||||
|
||||
const props = defineProps<FieldProps<LinkConfig>>();
|
||||
const props = defineProps<FieldProps<LinkConfig> & { theme?: string }>();
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
v-if="model"
|
||||
v-model="value"
|
||||
clearable
|
||||
controls-position="right"
|
||||
:controls-position="isGlobalFlat ? '' : 'right'"
|
||||
:size="size"
|
||||
:max="config.max"
|
||||
:min="config.min"
|
||||
@ -21,6 +21,7 @@ import { inject, ref, watch } from 'vue';
|
||||
import { TMagicInputNumber } from '@tmagic/design';
|
||||
|
||||
import type { FieldProps, FormState, NumberConfig } from '../schema';
|
||||
import { isGlobalFlat } from '../utils/config';
|
||||
import { useAddField } from '../utils/useAddField';
|
||||
|
||||
defineOptions({
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
<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
|
||||
v-for="option in config.options"
|
||||
:is="itemComponent"
|
||||
@ -8,9 +14,14 @@
|
||||
@click="clickHandler(option.value)"
|
||||
>
|
||||
<TMagicTooltip :disabled="!Boolean(option.tooltip)" placement="top-start" :content="option.tooltip">
|
||||
<div>
|
||||
<TMagicIcon v-if="option.icon" :size="iconSize"><component :is="option.icon"></component></TMagicIcon>
|
||||
<span v-if="option.text">{{ option.text }}</span>
|
||||
<div class="m-fields-radio-group__option">
|
||||
<TMagicIcon
|
||||
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>
|
||||
</TMagicTooltip>
|
||||
</component>
|
||||
@ -45,13 +56,17 @@ const clickHandler = (item: string | number | boolean) => {
|
||||
|
||||
useAddField(props.prop);
|
||||
|
||||
// 这里换了设计稿里的图标,所以需要调整一下图标大小
|
||||
const iconSize = computed(() => {
|
||||
if (props.config.iconSize) {
|
||||
return props.config.iconSize;
|
||||
}
|
||||
if (props.size === 'small') {
|
||||
return '12';
|
||||
return '14';
|
||||
}
|
||||
if (props.size === 'large') {
|
||||
return '16';
|
||||
}
|
||||
return '14';
|
||||
return '16';
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1,6 +1,31 @@
|
||||
<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
|
||||
v-else
|
||||
v-model="value"
|
||||
ref="input"
|
||||
:clearable="config.clearable ?? true"
|
||||
@ -17,17 +42,18 @@
|
||||
<template #append v-if="appendConfig">
|
||||
<TMagicButton
|
||||
v-if="appendConfig.type === 'button'"
|
||||
style="color: #409eff"
|
||||
style="color: #0056ea"
|
||||
:size="size"
|
||||
@click.prevent="buttonClickHandler"
|
||||
>
|
||||
{{ appendConfig.text }}
|
||||
</TMagicButton>
|
||||
<img v-else-if="appendConfig.type === 'icon'" :src="appendConfig.text" @click="buttonClickHandler" />
|
||||
<span v-else>{{ appendConfig.text }}</span>
|
||||
</template>
|
||||
</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 class="m-form-validate__warning">输入内容前后有空格,是否移除空格?</div>
|
||||
<div style="display: flex; justify-content: flex-end">
|
||||
@ -80,28 +106,26 @@ watch(
|
||||
);
|
||||
|
||||
const appendConfig = computed(() => {
|
||||
if (typeof props.config.append === 'string') {
|
||||
const { append } = props.config;
|
||||
|
||||
// 字符串形态:纯文本 append。
|
||||
if (typeof append === 'string') {
|
||||
return {
|
||||
type: 'text',
|
||||
text: props.config.append,
|
||||
text: append,
|
||||
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;
|
||||
|
||||
@ -50,11 +50,13 @@ import { setConfig } from './utils/config';
|
||||
import { registerTypeMatchRules, type TypeMatchValidator } from './utils/typeMatch';
|
||||
import Form from './Form.vue';
|
||||
import FormDialog from './FormDialog.vue';
|
||||
import FormDrawer from './FormDrawer.vue';
|
||||
|
||||
import './theme/index.scss';
|
||||
|
||||
// #region FormInstallOptions
|
||||
export interface FormInstallOptions {
|
||||
flat?: boolean;
|
||||
/** 自定义字段 type 的 typeMatch 校验规则,可覆盖内置规则或扩展业务字段 */
|
||||
typeMatchRules?: Record<string, TypeMatchValidator>;
|
||||
[key: string]: any;
|
||||
@ -76,6 +78,7 @@ export default {
|
||||
|
||||
app.component('m-form', Form);
|
||||
app.component('m-form-dialog', FormDialog);
|
||||
app.component('m-form-drawer', FormDrawer);
|
||||
app.component('m-form-container', Container);
|
||||
app.component('m-form-fieldset', Fieldset);
|
||||
app.component('m-form-group-list', TableGroupList);
|
||||
|
||||
@ -22,4 +22,7 @@
|
||||
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 {
|
||||
position: relative;
|
||||
border: 1px solid rgb(229, 229, 229);
|
||||
|
||||
@ -53,3 +53,9 @@
|
||||
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-item.tmagic-design-card--flat:last-child {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.el-button--text {
|
||||
padding: 0;
|
||||
margin-bottom: 7px;
|
||||
@ -19,14 +23,11 @@
|
||||
&:last-of-type {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tmagic-design-card {
|
||||
.el-card__header {
|
||||
padding: 5px 20px;
|
||||
}
|
||||
.t-card__header {
|
||||
padding: 5px 0;
|
||||
.m-fields-group-list-item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,5 +35,6 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,3 +13,4 @@
|
||||
@use "./tabs.scss";
|
||||
@use "./number-range.scss";
|
||||
@use "./form-box.scss";
|
||||
@use "./radio-group.scss";
|
||||
|
||||
@ -1,3 +1,20 @@
|
||||
div.m-fields-link {
|
||||
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;
|
||||
}
|
||||
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