Compare commits

..

No commits in common. "master" and "v1.7.10" have entirely different histories.

53 changed files with 1248 additions and 1550 deletions

View File

@ -1,40 +1,3 @@
## [1.7.13](https://github.com/Tencent/tmagic-editor/compare/v1.7.12...v1.7.13) (2026-04-29)
### Bug Fixes
* **core:** getTransform 支持 string 类型并适配 hippy ([96801e2](https://github.com/Tencent/tmagic-editor/commit/96801e2ccba1fb400007e988aebbda7195e8ff15))
### Features
* **editor:** update 支持 selectedAfterUpdate 参数控制是否更新 nodes ([26efa75](https://github.com/Tencent/tmagic-editor/commit/26efa75ff2cf03d4e27e3e77592d0304d343e44a))
## [1.7.12](https://github.com/Tencent/tmagic-editor/compare/v1.7.11...v1.7.12) (2026-04-24)
## [1.7.11](https://github.com/Tencent/tmagic-editor/compare/v1.7.10...v1.7.11) (2026-04-23)
### Bug Fixes
* **form:** group-list lastValues 为空时兼容取值报错 ([7249b51](https://github.com/Tencent/tmagic-editor/commit/7249b5106eee06a4e22cc325e83fac4e09e146ba))
* **form:** table 全屏每次进入重新获取 z-index ([9ba12e9](https://github.com/Tencent/tmagic-editor/commit/9ba12e97afccbca6bafcacc1b5e79f4d7fed3341)), closes [#672](https://github.com/Tencent/tmagic-editor/issues/672)
* **vue-runtime-help:** 删除所有页面后,新增页面出错 ([6a4a4ed](https://github.com/Tencent/tmagic-editor/commit/6a4a4ed122ce19ece81d816cb9b7dfb473dc351a)), closes [#668](https://github.com/Tencent/tmagic-editor/issues/668)
### Features
* **editor:** 样式面板布局分组新增透明度配置 ([e106c08](https://github.com/Tencent/tmagic-editor/commit/e106c081c8fd6e0e8db9160605190e21ffccc1f7)), closes [#675](https://github.com/Tencent/tmagic-editor/issues/675)
* **editor:** 没有参考线时不显示参考线切换按钮 ([b46b571](https://github.com/Tencent/tmagic-editor/commit/b46b5712142c85fc18533f68d52ae0958c2835e0))
* **form:** group-list 支持 max 限制和 beforeAddRow 前置校验 ([ac755ac](https://github.com/Tencent/tmagic-editor/commit/ac755ac3d0328a2ca8e77d6f8b117b8b349e1bd0))
* **table:** 支持列排序配置 ([4cd54d1](https://github.com/Tencent/tmagic-editor/commit/4cd54d13979da5189ecbd19ec732a4653a4f7d0a))
## [1.7.10](https://github.com/Tencent/tmagic-editor/compare/v1.7.9...v1.7.10) (2026-04-13) ## [1.7.10](https://github.com/Tencent/tmagic-editor/compare/v1.7.9...v1.7.10) (2026-04-13)

View File

@ -16,9 +16,9 @@ https://tencent.github.io/tmagic-editor/playground/index.html
## 环境准备 ## 环境准备
node.js ^20.19.0 || >=22.12.0 node.js >= 18
pnpm >= 10 pnpm >= 9
先安装 pnpm 先安装 pnpm

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "tmagic", "name": "tmagic",
"private": true, "private": true,
"type": "module", "type": "module",
@ -44,8 +44,8 @@
"@commitlint/config-conventional": "^20.0.0", "@commitlint/config-conventional": "^20.0.0",
"@tmagic/eslint-config": "workspace:*", "@tmagic/eslint-config": "workspace:*",
"@types/node": "24.0.10", "@types/node": "24.0.10",
"@vitejs/plugin-vue": "^6.0.6", "@vitejs/plugin-vue": "^6.0.5",
"@vitest/coverage-v8": "^4.1.5", "@vitest/coverage-v8": "^4.1.0",
"@vue/compiler-sfc": "catalog:", "@vue/compiler-sfc": "catalog:",
"c8": "^10.1.3", "c8": "^10.1.3",
"commitizen": "^4.3.1", "commitizen": "^4.3.1",
@ -54,7 +54,7 @@
"cz-conventional-changelog": "^3.3.0", "cz-conventional-changelog": "^3.3.0",
"element-plus": "^2.11.8", "element-plus": "^2.11.8",
"enquirer": "^2.4.1", "enquirer": "^2.4.1",
"eslint": "^10.2.1", "eslint": "^10.0.3",
"execa": "^9.6.0", "execa": "^9.6.0",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"husky": "^9.1.7", "husky": "^9.1.7",
@ -62,12 +62,12 @@
"lint-staged": "^16.2.7", "lint-staged": "^16.2.7",
"minimist": "^1.2.8", "minimist": "^1.2.8",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"prettier": "^3.8.3", "prettier": "^3.8.1",
"recast": "^0.23.11", "recast": "^0.23.11",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rolldown": "^1.0.0-rc.17", "rolldown": "^1.0.0-rc.9",
"rolldown-plugin-dts": "^0.23.2", "rolldown-plugin-dts": "^0.22.5",
"sass-embedded": "^1.99.0", "sass-embedded": "^1.93.3",
"semver": "^7.7.3", "semver": "^7.7.3",
"serialize-javascript": "^7.0.0", "serialize-javascript": "^7.0.0",
"shx": "^0.3.4", "shx": "^0.3.4",
@ -76,7 +76,7 @@
"vitepress": "^1.6.4", "vitepress": "^1.6.4",
"vitest": "^4.1.0", "vitest": "^4.1.0",
"vue": "catalog:", "vue": "catalog:",
"vue-tsc": "^3.2.7" "vue-tsc": "^3.2.6"
}, },
"config": { "config": {
"commitizen": { "commitizen": {

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/cli", "name": "@tmagic/cli",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/core", "name": "@tmagic/core",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -56,22 +56,8 @@ export const fillBackgroundImage = (value: string) => {
return value; return value;
}; };
export const getTransform = (value: Record<string, string> | string, jsEngine: JsEngine) => { export const getTransform = (value: Record<string, string>, jsEngine: JsEngine) => {
const isHippy = jsEngine === 'hippy'; if (!value) return [];
if (!value) return isHippy ? [] : '';
if (typeof value === 'string') {
if (!isHippy) return value;
// Hippy 不支持 css transform 字符串,需要将 "rotate(90deg) scale(1.5)" 解析成 [{ rotate: '90deg' }, { scale: '1.5' }]
const transformArr: Record<string, string>[] = [];
value.replace(/(\w+)\(([^)]+)\)/g, (_, key, val) => {
transformArr.push({ [key]: val.trim() });
return '';
});
return transformArr;
}
const transform = Object.entries(value).map(([transformKey, transformValue]) => { const transform = Object.entries(value).map(([transformKey, transformValue]) => {
if (!transformValue.trim()) return ''; if (!transformValue.trim()) return '';
@ -79,14 +65,14 @@ export const getTransform = (value: Record<string, string> | string, jsEngine: J
transformValue = `${transformValue}deg`; transformValue = `${transformValue}deg`;
} }
return isHippy ? { [transformKey]: transformValue } : `${transformKey}(${transformValue})`; return jsEngine !== 'hippy' ? `${transformKey}(${transformValue})` : { [transformKey]: transformValue };
}); });
if (isHippy) { if (jsEngine === 'hippy') {
return transform; return transform;
} }
const values = transform.join(' '); const values = transform.join(' ');
return values.trim(); return !values.trim() ? 'none' : values;
}; };
/** /**
@ -116,11 +102,8 @@ export const transformStyle = (style: Record<string, any> | string, jsEngine: Js
results.transform = [{ scale: value }]; results.transform = [{ scale: value }];
} else if (key === 'backgroundImage' && !isHippy) { } else if (key === 'backgroundImage' && !isHippy) {
value && (results[key] = fillBackgroundImage(value)); value && (results[key] = fillBackgroundImage(value));
} else if (key === 'transform') { } else if (key === 'transform' && typeof value !== 'string') {
const transform = getTransform(value, jsEngine); results[key] = getTransform(value, jsEngine);
if (transform) {
results[key] = transform;
}
} else if (!whiteList.includes(key) && value && /^[-]?[0-9]*[.]?[0-9]*$/.test(value)) { } else if (!whiteList.includes(key) && value && /^[-]?[0-9]*[.]?[0-9]*$/.test(value)) {
results[key] = isHippy ? value : `${value / 100}rem`; results[key] = isHippy ? value : `${value / 100}rem`;
} else { } else {

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/data-source", "name": "@tmagic/data-source",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/dep", "name": "@tmagic/dep",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/design", "name": "@tmagic/design",
"type": "module", "type": "module",
"sideEffects": [ "sideEffects": [

View File

@ -342,7 +342,7 @@ export interface TableColumnOptions<T = any> {
prop?: string; prop?: string;
align?: string; align?: string;
headerAlign?: string; headerAlign?: string;
sortable?: boolean | string; sortable?: boolean;
sortOrders?: Array<'ascending' | 'descending'>; sortOrders?: Array<'ascending' | 'descending'>;
selectable?: (row: T, index: number) => boolean; selectable?: (row: T, index: number) => boolean;
}; };

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/editor", "name": "@tmagic/editor",
"type": "module", "type": "module",
"sideEffects": [ "sideEffects": [

View File

@ -67,7 +67,6 @@ const config = computed<GroupListConfig>(() => ({
name: props.name, name: props.name,
titlePrefix: props.config.titlePrefix, titlePrefix: props.config.titlePrefix,
expandAll: true, expandAll: true,
enableToggleMode: false,
items: [ items: [
{ {
type: 'table', type: 'table',

View File

@ -59,12 +59,10 @@ export const useStage = (stageOptions: StageOptions) => {
}, },
); );
const hGuidesCache = getGuideLineFromCache(getGuideLineKey(H_GUIDE_LINE_STORAGE_KEY)); stage.mask?.setGuides([
const vGuidesCache = getGuideLineFromCache(getGuideLineKey(V_GUIDE_LINE_STORAGE_KEY)); getGuideLineFromCache(getGuideLineKey(H_GUIDE_LINE_STORAGE_KEY)),
getGuideLineFromCache(getGuideLineKey(V_GUIDE_LINE_STORAGE_KEY)),
stage.mask?.setGuides([hGuidesCache, vGuidesCache]); ]);
uiService.set('hasGuides', hGuidesCache.length > 0 || vGuidesCache.length > 0);
stage.on('page-el-update', () => { stage.on('page-el-update', () => {
editorService.set('stageLoading', false); editorService.set('stageLoading', false);
@ -126,11 +124,6 @@ export const useStage = (stageOptions: StageOptions) => {
stage.on('change-guides', (e) => { stage.on('change-guides', (e) => {
uiService.set('showGuides', true); uiService.set('showGuides', true);
uiService.set(
'hasGuides',
(stage.mask?.horizontalGuidelines.length ?? 0) > 0 || (stage.mask?.verticalGuidelines.length ?? 0) > 0,
);
if (!root.value || !page.value) return; if (!root.value || !page.value) return;
const storageKey = getGuideLineKey( const storageKey = getGuideLineKey(

View File

@ -37,7 +37,6 @@ const columnWidth = computed(() => uiService.get('columnWidth'));
const keys = Object.values(ColumnLayout); const keys = Object.values(ColumnLayout);
const showGuides = computed((): boolean => uiService.get('showGuides')); const showGuides = computed((): boolean => uiService.get('showGuides'));
const hasGuides = computed((): boolean => uiService.get('hasGuides'));
const showRule = computed((): boolean => uiService.get('showRule')); const showRule = computed((): boolean => uiService.get('showRule'));
const zoom = computed((): number => uiService.get('zoom')); const zoom = computed((): number => uiService.get('zoom'));
@ -144,7 +143,6 @@ const getConfig = (item: MenuItem): (MenuButton | MenuComponent)[] => {
}); });
break; break;
case 'guides': case 'guides':
if (!hasGuides.value) break;
config.push({ config.push({
type: 'button', type: 'button',
className: 'guides', className: 'guides',

View File

@ -509,10 +509,7 @@ class Editor extends BaseService {
public async doUpdate( public async doUpdate(
config: MNode, config: MNode,
{ { changeRecords = [] }: { changeRecords?: ChangeRecord[] } = {},
changeRecords = [],
selectedAfterUpdate = true,
}: { changeRecords?: ChangeRecord[]; selectedAfterUpdate?: boolean } = {},
): Promise<{ newNode: MNode; oldNode: MNode; changeRecords?: ChangeRecord[] }> { ): Promise<{ newNode: MNode; oldNode: MNode; changeRecords?: ChangeRecord[] }> {
const root = this.get('root'); const root = this.get('root');
if (!root) throw new Error('root为空'); if (!root) throw new Error('root为空');
@ -557,12 +554,10 @@ class Editor extends BaseService {
parentNodeItems[index] = newConfig; parentNodeItems[index] = newConfig;
// 将update后的配置更新到nodes中 // 将update后的配置更新到nodes中
if (selectedAfterUpdate) { const nodes = this.get('nodes');
const nodes = this.get('nodes'); const targetIndex = nodes.findIndex((nodeItem: MNode) => `${nodeItem.id}` === `${newConfig.id}`);
const targetIndex = nodes.findIndex((nodeItem: MNode) => `${nodeItem.id}` === `${newConfig.id}`); nodes.splice(targetIndex, 1, newConfig);
nodes.splice(targetIndex, 1, newConfig); this.set('nodes', [...nodes]);
this.set('nodes', [...nodes]);
}
if (isPage(newConfig) || isPageFragment(newConfig)) { if (isPage(newConfig) || isPageFragment(newConfig)) {
this.set('page', newConfig as MPage | MPageFragment); this.set('page', newConfig as MPage | MPageFragment);
@ -585,7 +580,7 @@ class Editor extends BaseService {
*/ */
public async update( public async update(
config: MNode | MNode[], config: MNode | MNode[],
data: { changeRecords?: ChangeRecord[]; selectedAfterUpdate?: boolean } = {}, data: { changeRecords?: ChangeRecord[] } = {},
): Promise<MNode | MNode[]> { ): Promise<MNode | MNode[]> {
this.captureSelectionBeforeOp(); this.captureSelectionBeforeOp();

View File

@ -55,7 +55,6 @@ const state = shallowReactive<UiState>({
DEFAULT_RIGHT_COLUMN_WIDTH, DEFAULT_RIGHT_COLUMN_WIDTH,
}, },
showGuides: true, showGuides: true,
hasGuides: false,
showRule: true, showRule: true,
propsPanelSize: 'small', propsPanelSize: 'small',
showAddPageButton: true, showAddPageButton: true,

View File

@ -253,8 +253,6 @@ export interface UiState {
columnWidth: GetColumnWidth; columnWidth: GetColumnWidth;
/** 是否显示画布参考线true: 显示false: 不显示默认为true */ /** 是否显示画布参考线true: 显示false: 不显示默认为true */
showGuides: boolean; showGuides: boolean;
/** 画布上是否存在参考线 */
hasGuides: boolean;
/** 是否显示标尺true: 显示false: 不显示默认为true */ /** 是否显示标尺true: 显示false: 不显示默认为true */
showRule: boolean; showRule: boolean;
/** 用于控制该属性配置表单内组件的尺寸 */ /** 用于控制该属性配置表单内组件的尺寸 */

View File

@ -1,11 +1,11 @@
import type { DataSchema, DataSourceFieldType, DataSourceSchema } from '@tmagic/core'; import type { DataSchema, DataSourceFieldType, DataSourceSchema } from '@tmagic/core';
import { type CascaderOption, type FormConfig, type TabConfig } from '@tmagic/form'; import { type CascaderOption, defineFormItem, type FormConfig } from '@tmagic/form';
import { dataSourceTemplateRegExp, getKeysArray, isNumber } from '@tmagic/utils'; import { dataSourceTemplateRegExp, getKeysArray, isNumber } from '@tmagic/utils';
import BaseFormConfig from './formConfigs/base'; import BaseFormConfig from './formConfigs/base';
import HttpFormConfig from './formConfigs/http'; import HttpFormConfig from './formConfigs/http';
const dataSourceFormConfig: TabConfig = { const dataSourceFormConfig = defineFormItem({
type: 'tab', type: 'tab',
items: [ items: [
{ {
@ -73,13 +73,9 @@ const dataSourceFormConfig: TabConfig = {
], ],
}, },
], ],
}; });
const fillConfig = <T = never>(config: FormConfig<T>): FormConfig<T> => [ const fillConfig = (config: FormConfig): FormConfig => [...BaseFormConfig(), ...config, dataSourceFormConfig];
...BaseFormConfig(),
...config,
dataSourceFormConfig,
];
export const getFormConfig = (type: string, configs: Record<string, FormConfig>): FormConfig => { export const getFormConfig = (type: string, configs: Record<string, FormConfig>): FormConfig => {
switch (type) { switch (type) {

View File

@ -106,7 +106,6 @@ export const styleTabConfig: TabPaneConfig = {
'borderWidth', 'borderWidth',
'borderStyle', 'borderStyle',
'borderColor', 'borderColor',
'opacity',
], ],
} as unknown as ChildConfig, } as unknown as ChildConfig,
{ {

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/element-plus-adapter", "name": "@tmagic/element-plus-adapter",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/form-schema", "name": "@tmagic/form-schema",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -701,43 +701,28 @@ export interface PanelConfig<T = never> extends FormItem, ContainerCommonConfig<
schematic?: string; schematic?: string;
} }
export interface TableGroupListCommonConfig extends FormItem { export interface TableColumnConfig extends FormItem {
type: 'table' | 'groupList' | 'group-list';
enableToggleMode?: boolean;
/** 最大行数 */
max?: number;
enum?: any[];
/** 是否显示添加按钮 */
addable?: (mForm: FormState | undefined, data: any) => boolean | 'undefined' | boolean;
/** 新增的默认行,可以是函数动态生成或静态对象 */
defaultAdd?: ((mForm: FormState | undefined, data: any) => any) | Record<string, any>;
/** table 新增行时前置回调 */
beforeAddRow?: (mForm: FormState | undefined, data: any) => boolean | Promise<boolean>;
}
export interface TableColumnConfig<T = never> extends FormItem {
name?: string; name?: string;
label?: string; label: string;
text?: string;
width?: string | number; width?: string | number;
sortable?: boolean; sortable?: boolean;
items?: FormConfig<T>; items?: FormConfig;
itemsFunction?: (row: any) => FormConfig<T>; itemsFunction?: (row: any) => FormConfig;
titleTip?: FilterFunction<string>; titleTip?: FilterFunction<string>;
type?: string; type?: string;
addButtonConfig?: {
props?: Record<string, any>;
text?: string;
};
} }
/** /**
* *
*/ */
export interface TableConfig<T = never> extends TableGroupListCommonConfig { export interface TableConfig extends FormItem {
items: TableColumnConfig<T>[]; type: 'table' | 'groupList' | 'group-list';
tableItems?: TableColumnConfig<T>[]; items: TableColumnConfig[];
groupItems?: TableColumnConfig<T>[]; tableItems?: TableColumnConfig[];
groupItems?: TableColumnConfig[];
enableToggleMode?: boolean;
/** 最大行数 */
max?: number;
/** 最大高度 */ /** 最大高度 */
maxHeight?: number | string; maxHeight?: number | string;
border?: boolean; border?: boolean;
@ -746,6 +731,9 @@ export interface TableConfig<T = never> extends TableGroupListCommonConfig {
/** 操作栏宽度 */ /** 操作栏宽度 */
operateColWidth?: number | string; operateColWidth?: number | string;
pagination?: boolean; pagination?: boolean;
enum?: any[];
/** 是否显示添加按钮 */
addable?: (mForm: FormState | undefined, data: any) => boolean | 'undefined' | boolean;
/** 是否显示删除按钮 */ /** 是否显示删除按钮 */
delete?: (model: any, index: number, values: any) => boolean | boolean; delete?: (model: any, index: number, values: any) => boolean | boolean;
copyable?: (model: any, data: any) => boolean | boolean; copyable?: (model: any, data: any) => boolean | boolean;
@ -753,6 +741,8 @@ export interface TableConfig<T = never> extends TableGroupListCommonConfig {
importable?: (mForm: FormState | undefined, data: any) => boolean | 'undefined' | boolean; importable?: (mForm: FormState | undefined, data: any) => boolean | 'undefined' | boolean;
/** 是否显示checkbox */ /** 是否显示checkbox */
selection?: (mForm: FormState | undefined, data: any) => boolean | boolean | 'single'; selection?: (mForm: FormState | undefined, data: any) => boolean | boolean | 'single';
/** 新增的默认行,可以是函数动态生成或静态对象 */
defaultAdd?: ((mForm: FormState | undefined, data: any) => any) | Record<string, any>;
copyHandler?: (mForm: FormState | undefined, data: any) => any; copyHandler?: (mForm: FormState | undefined, data: any) => any;
onSelect?: (mForm: FormState | undefined, data: any) => any; onSelect?: (mForm: FormState | undefined, data: any) => any;
/** @deprecated 请使用 defaultSort */ /** @deprecated 请使用 defaultSort */
@ -770,12 +760,20 @@ export interface TableConfig<T = never> extends TableGroupListCommonConfig {
itemExtra?: string | FilterFunction<string>; itemExtra?: string | FilterFunction<string>;
titleTip?: FilterFunction<string>; titleTip?: FilterFunction<string>;
rowKey?: string; rowKey?: string;
/** table 新增行时前置回调 */
beforeAddRow?: (mForm: FormState | undefined, data: any) => boolean;
addButtonConfig?: {
props?: Record<string, any>;
text?: string;
};
sort?: boolean; sort?: boolean;
sortKey?: string; sortKey?: string;
} }
export interface GroupListConfig<T = never> extends TableGroupListCommonConfig { export interface GroupListConfig<T = never> extends FormItem {
type: 'table' | 'groupList' | 'group-list';
span?: number; span?: number;
enableToggleMode?: boolean;
items: FormConfig<T>; items: FormConfig<T>;
groupItems?: FormConfig<T>; groupItems?: FormConfig<T>;
tableItems?: FormConfig<T>; tableItems?: FormConfig<T>;
@ -790,6 +788,9 @@ export interface GroupListConfig<T = never> extends TableGroupListCommonConfig {
* *
*/ */
defaultExpandQuantity?: number; defaultExpandQuantity?: number;
addable?: (mForm: FormState | undefined, data: any) => boolean | 'undefined' | boolean;
/** 新增的默认值,可以是函数动态生成或静态对象 */
defaultAdd?: ((mForm: FormState | undefined, data: any) => any) | Record<string, any>;
delete?: (model: any, index: number | string | symbol, values: any) => boolean | boolean; delete?: (model: any, index: number | string | symbol, values: any) => boolean | boolean;
copyable?: FilterFunction<boolean>; copyable?: FilterFunction<boolean>;
movable?: ( movable?: (
@ -799,6 +800,10 @@ export interface GroupListConfig<T = never> extends TableGroupListCommonConfig {
groupModel: any, groupModel: any,
) => boolean | boolean; ) => boolean | boolean;
moveSpecifyLocation?: boolean; moveSpecifyLocation?: boolean;
addButtonConfig?: {
props?: Record<string, any>;
text?: string;
};
} }
interface StepItemConfig<T = never> extends FormItem, ContainerCommonConfig<T> { interface StepItemConfig<T = never> extends FormItem, ContainerCommonConfig<T> {

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/form", "name": "@tmagic/form",
"type": "module", "type": "module",
"sideEffects": [ "sideEffects": [

View File

@ -10,7 +10,7 @@
v-for="(item, index) in model[name]" v-for="(item, index) in model[name]"
:key="index" :key="index"
:model="item" :model="item"
:lastValues="getLastValues(lastValues?.[name], Number(index))" :lastValues="getLastValues(lastValues[name], Number(index))"
:is-compare="isCompare" :is-compare="isCompare"
:config="config" :config="config"
:prop="prop" :prop="prop"
@ -27,18 +27,33 @@
></MFieldsGroupListItem> ></MFieldsGroupListItem>
<div class="m-fields-group-list-footer"> <div class="m-fields-group-list-footer">
<slot name="toggle-button"></slot> <TMagicButton v-if="config.enableToggleMode" :icon="Grid" size="small" @click="toggleMode"
>切换为表格</TMagicButton
>
<div style="display: flex; justify-content: flex-end; flex: 1"> <div style="display: flex; justify-content: flex-end; flex: 1">
<slot name="add-button"></slot> <TMagicButton
v-if="addable"
:size="config.enableToggleMode ? 'small' : 'default'"
:icon="Plus"
v-bind="config.addButtonConfig?.props || { type: 'primary' }"
:disabled="disabled"
@click="addHandler"
>{{ config.addButtonConfig?.text || '新增' }}</TMagicButton
>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, inject } from 'vue';
import { Grid, Plus } from '@element-plus/icons-vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import type { ContainerChangeEventData, GroupListConfig } from '../schema'; import { TMagicButton } from '@tmagic/design';
import type { ContainerChangeEventData, FormState, GroupListConfig } from '../schema';
import { initValue } from '../utils/form';
import MFieldsGroupListItem from './GroupListItem.vue'; import MFieldsGroupListItem from './GroupListItem.vue';
@ -56,7 +71,6 @@ const props = defineProps<{
prop?: string; prop?: string;
size?: string; size?: string;
disabled?: boolean; disabled?: boolean;
showIndex?: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@ -64,10 +78,60 @@ const emit = defineEmits<{
addDiffCount: []; addDiffCount: [];
}>(); }>();
const mForm = inject<FormState | undefined>('mForm');
const addable = computed(() => {
if (!props.name) return false;
if (typeof props.config.addable === 'function') {
return props.config.addable(mForm, {
model: props.model[props.name],
formValue: mForm?.values,
prop: props.prop,
config: props.config,
});
}
return typeof props.config.addable === 'undefined' ? true : props.config.addable;
});
const changeHandler = (v: any, eventData: ContainerChangeEventData) => { const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
emit('change', props.model, eventData); emit('change', props.model, eventData);
}; };
const addHandler = async () => {
if (!props.name) return false;
let initValues = {};
if (typeof props.config.defaultAdd === 'function') {
initValues = await props.config.defaultAdd(mForm, {
model: props.model[props.name],
formValue: mForm?.values,
prop: props.prop,
config: props.config,
});
} else if (props.config.defaultAdd) {
initValues = props.config.defaultAdd;
}
const groupValue = await initValue(mForm, {
config: props.config.items,
initValues,
});
props.model[props.name].push(groupValue);
emit('change', props.model[props.name], {
changeRecords: [
{
propPath: `${props.prop}.${props.model[props.name].length - 1}`,
value: groupValue,
},
],
});
};
const removeHandler = (index: number) => { const removeHandler = (index: number) => {
if (!props.name) return false; if (!props.name) return false;
@ -89,6 +153,17 @@ const swapHandler = (idx1: number, idx2: number) => {
emit('change', props.model[props.name]); emit('change', props.model[props.name]);
}; };
const toggleMode = () => {
props.config.type = 'table';
props.config.groupItems = props.config.items;
props.config.items = (props.config.tableItems ||
props.config.items.map((item: any) => ({
...item,
label: item.label || item.text,
text: null,
}))) as any;
};
const onAddDiffCount = () => emit('addDiffCount'); const onAddDiffCount = () => emit('addDiffCount');
const getLastValues = (item: any, index: number) => item?.[index] || {}; const getLastValues = (item: any, index: number) => item?.[index] || {};

View File

@ -1,173 +0,0 @@
<template>
<component
:is="displayMode === 'table' ? MFormTable : MFormGroupList"
ref="tableGroupList"
v-bind="$attrs"
:model="model"
:name="`${name}`"
:config="currentConfig"
:disabled="disabled"
:size="size"
:is-compare="isCompare"
:last-values="lastValues"
:prop="prop"
:label-width="labelWidth"
:show-index="showIndex"
:sort-key="sortKey"
:sort="sort"
@change="onChange"
@select="onSelect"
@addDiffCount="onAddDiffCount"
@add="onAdd"
>
<template #toggle-button>
<TMagicButton
v-if="config.enableToggleMode || enableToggleMode"
:icon="Grid"
size="small"
@click="toggleDisplayMode"
>
{{ displayMode === 'table' ? '展开配置' : '切换为表格' }}
</TMagicButton>
</template>
<template #add-button v-if="addable">
<TMagicButton
:class="displayMode === 'table' ? 'm-form-table-add-button' : ''"
:size="addButtonSize"
:plain="displayMode === 'table'"
:icon="Plus"
:disabled="disabled"
v-bind="currentConfig.addButtonConfig?.props || { type: 'primary' }"
@click="newHandler"
>
{{ currentConfig.addButtonConfig?.text || (displayMode === 'table' ? '新增一行' : '新增') }}
</TMagicButton>
</template>
</component>
</template>
<script setup lang="ts">
import { computed, ref, useTemplateRef } from 'vue';
import { Grid, Plus } from '@element-plus/icons-vue';
import { TMagicButton } from '@tmagic/design';
import type { GroupListConfig, TableConfig } from '@tmagic/form-schema';
import type { ContainerChangeEventData } from '../../schema';
import MFormGroupList from '../GroupList.vue';
import MFormTable from '../table/Table.vue';
import { useAdd } from './useAdd';
defineOptions({
name: 'MFormTableGroupList',
inheritAttrs: false,
});
const props = defineProps<{
model: any;
lastValues?: any;
isCompare?: boolean;
config: TableConfig | GroupListConfig;
name: string;
prop?: string;
labelWidth?: string;
disabled?: boolean;
size?: string;
enableToggleMode?: true;
showIndex?: boolean;
sortKey?: string;
sort?: boolean;
}>();
const emit = defineEmits(['change', 'select', 'addDiffCount']);
const { addable, newHandler } = useAdd(props, emit);
const isGroupListType = (type: string | undefined) => type === 'groupList' || type === 'group-list';
const displayMode = ref<'table' | 'groupList'>(isGroupListType(props.config.type) ? 'groupList' : 'table');
const calcLabelWidth = (label: string) => {
if (!label) return '0px';
const zhLength = label.match(/[^\x00-\xff]/g)?.length || 0;
const chLength = label.length - zhLength;
return `${Math.max(chLength * 8 + zhLength * 20, 80)}px`;
};
// config table table
// groupList table config
const tableConfig = computed<TableConfig>(() => {
if (!isGroupListType(props.config.type)) {
return props.config as TableConfig;
}
const source = props.config as GroupListConfig;
return {
...props.config,
type: 'table',
groupItems: source.items,
items:
source.tableItems ||
(source.items as any[]).map((item: any) => ({
...item,
label: item.label || item.text,
text: null,
})),
} as any as TableConfig;
});
// groupList config
const groupListConfig = computed<GroupListConfig>(() => {
if (isGroupListType(props.config.type)) {
return props.config as GroupListConfig;
}
const source = props.config as TableConfig;
return {
...props.config,
type: 'groupList',
tableItems: source.items,
items:
source.groupItems ||
(source.items as any[]).map((item: any) => {
const text = item.text || item.label;
return {
...item,
text,
labelWidth: calcLabelWidth(text),
span: item.span || 12,
};
}),
} as any as GroupListConfig;
});
// displayMode `<component :is>` any
const currentConfig = computed<any>(() => (displayMode.value === 'table' ? tableConfig.value : groupListConfig.value));
// Table/GroupList
const addButtonSize = computed(() => {
if (displayMode.value === 'table') return 'small';
return props.config.enableToggleMode !== false ? 'small' : 'default';
});
const toggleDisplayMode = () => {
displayMode.value = displayMode.value === 'table' ? 'groupList' : 'table';
};
const onChange = (v: any, eventData?: ContainerChangeEventData) => emit('change', v, eventData);
const onSelect = (...args: any[]) => emit('select', ...args);
const onAddDiffCount = () => emit('addDiffCount');
const onAdd = (rows: any[]) => {
rows.forEach((row: any) => {
newHandler(row);
});
};
const tableGroupListRef = useTemplateRef<InstanceType<typeof MFormTable>>('tableGroupList');
defineExpose({
toggleRowSelection: (row: any, selected: boolean) => tableGroupListRef.value?.toggleRowSelection?.(row, selected),
});
</script>

View File

@ -1,31 +0,0 @@
import { ref, watch } from 'vue';
import { useZIndex } from '@tmagic/design';
export const useFullscreen = () => {
const { nextZIndex } = useZIndex();
const fullscreenZIndex = ref(nextZIndex());
const isFullscreen = ref(false);
const toggleFullscreen = () => {
if (isFullscreen.value) {
isFullscreen.value = false;
} else {
isFullscreen.value = true;
}
};
watch(isFullscreen, (value) => {
if (value) {
fullscreenZIndex.value = nextZIndex();
}
});
return {
isFullscreen,
fullscreenZIndex,
toggleFullscreen,
};
};

View File

@ -32,9 +32,8 @@ export { default as MFlexLayout } from './containers/FlexLayout.vue';
export { default as MPanel } from './containers/Panel.vue'; export { default as MPanel } from './containers/Panel.vue';
export { default as MRow } from './containers/Row.vue'; export { default as MRow } from './containers/Row.vue';
export { default as MTabs } from './containers/Tabs.vue'; export { default as MTabs } from './containers/Tabs.vue';
export { default as MTable } from './containers/table-group-list/TableGroupList.vue'; export { default as MTable } from './table/Table.vue';
export { default as MGroupList } from './containers/table-group-list/TableGroupList.vue'; export { default as MGroupList } from './containers/GroupList.vue';
export { default as MTableGroupList } from './containers/table-group-list/TableGroupList.vue';
export { default as MText } from './fields/Text.vue'; export { default as MText } from './fields/Text.vue';
export { default as MNumber } from './fields/Number.vue'; export { default as MNumber } from './fields/Number.vue';
export { default as MNumberRange } from './fields/NumberRange.vue'; export { default as MNumberRange } from './fields/NumberRange.vue';

View File

@ -21,10 +21,10 @@ import { type App } from 'vue';
import Container from './containers/Container.vue'; import Container from './containers/Container.vue';
import Fieldset from './containers/Fieldset.vue'; import Fieldset from './containers/Fieldset.vue';
import FlexLayout from './containers/FlexLayout.vue'; import FlexLayout from './containers/FlexLayout.vue';
import GroupList from './containers/GroupList.vue';
import Panel from './containers/Panel.vue'; import Panel from './containers/Panel.vue';
import Row from './containers/Row.vue'; import Row from './containers/Row.vue';
import MStep from './containers/Step.vue'; import MStep from './containers/Step.vue';
import TableGroupList from './containers/table-group-list/TableGroupList.vue';
import Tabs from './containers/Tabs.vue'; import Tabs from './containers/Tabs.vue';
import Cascader from './fields/Cascader.vue'; import Cascader from './fields/Cascader.vue';
import Checkbox from './fields/Checkbox.vue'; import Checkbox from './fields/Checkbox.vue';
@ -46,6 +46,7 @@ import Text from './fields/Text.vue';
import Textarea from './fields/Textarea.vue'; import Textarea from './fields/Textarea.vue';
import Time from './fields/Time.vue'; import Time from './fields/Time.vue';
import Timerange from './fields/Timerange.vue'; import Timerange from './fields/Timerange.vue';
import Table from './table/Table.vue';
import { setConfig } from './utils/config'; import { setConfig } from './utils/config';
import Form from './Form.vue'; import Form from './Form.vue';
import FormDialog from './FormDialog.vue'; import FormDialog from './FormDialog.vue';
@ -69,11 +70,11 @@ export default {
app.component('m-form-dialog', FormDialog); app.component('m-form-dialog', FormDialog);
app.component('m-form-container', Container); app.component('m-form-container', Container);
app.component('m-form-fieldset', Fieldset); app.component('m-form-fieldset', Fieldset);
app.component('m-form-group-list', TableGroupList); app.component('m-form-group-list', GroupList);
app.component('m-form-panel', Panel); app.component('m-form-panel', Panel);
app.component('m-form-row', Row); app.component('m-form-row', Row);
app.component('m-form-step', MStep); app.component('m-form-step', MStep);
app.component('m-form-table', TableGroupList); app.component('m-form-table', Table);
app.component('m-form-tab', Tabs); app.component('m-form-tab', Tabs);
app.component('m-form-flex-layout', FlexLayout); app.component('m-form-flex-layout', FlexLayout);
app.component('m-fields-text', Text); app.component('m-fields-text', Text);

View File

@ -38,7 +38,7 @@ import { cloneDeep } from 'lodash-es';
import { TMagicButton, TMagicTooltip } from '@tmagic/design'; import { TMagicButton, TMagicTooltip } from '@tmagic/design';
import type { FormState, TableConfig } from '../../schema'; import type { FormState, TableConfig } from '../schema';
const emit = defineEmits(['change']); const emit = defineEmits(['change']);

View File

@ -4,7 +4,7 @@
v-bind="$attrs" v-bind="$attrs"
class="m-fields-table-wrap" class="m-fields-table-wrap"
:class="{ fixed: isFullscreen }" :class="{ fixed: isFullscreen }"
:style="isFullscreen ? `z-index: ${fullscreenZIndex}` : ''" :style="isFullscreen ? `z-index: ${nextZIndex()}` : ''"
> >
<div class="m-fields-table" :class="{ 'm-fields-table-item-extra': config.itemExtra }"> <div class="m-fields-table" :class="{ 'm-fields-table-item-extra': config.itemExtra }">
<span v-if="config.extra" style="color: rgba(0, 0, 0, 0.45)" v-html="config.extra"></span> <span v-if="config.extra" style="color: rgba(0, 0, 0, 0.45)" v-html="config.extra"></span>
@ -34,7 +34,13 @@
<div style="display: flex; justify-content: space-between; margin: 10px 0"> <div style="display: flex; justify-content: space-between; margin: 10px 0">
<div style="display: flex"> <div style="display: flex">
<slot name="toggle-button" v-if="!isFullscreen"></slot> <TMagicButton
:icon="Grid"
size="small"
@click="toggleMode"
v-if="enableToggleMode && config.enableToggleMode !== false && !isFullscreen"
>展开配置</TMagicButton
>
<TMagicButton <TMagicButton
:icon="FullScreen" :icon="FullScreen"
size="small" size="small"
@ -58,7 +64,17 @@
>清空</TMagicButton >清空</TMagicButton
> >
</div> </div>
<slot name="add-button"></slot> <TMagicButton
v-if="addable"
class="m-form-table-add-button"
size="small"
plain
:icon="Plus"
v-bind="config.addButtonConfig?.props || { type: 'primary' }"
:disabled="disabled"
@click="newHandler()"
>{{ config.addButtonConfig?.text || '新增一行' }}</TMagicButton
>
</div> </div>
<div class="bottom" style="text-align: right" v-if="config.pagination"> <div class="bottom" style="text-align: right" v-if="config.pagination">
@ -81,14 +97,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, useTemplateRef } from 'vue'; import { computed, ref, useTemplateRef } from 'vue';
import { FullScreen } from '@element-plus/icons-vue'; import { FullScreen, Grid, Plus } from '@element-plus/icons-vue';
import { TMagicButton, TMagicPagination, TMagicTable, TMagicTooltip, TMagicUpload } from '@tmagic/design'; import { TMagicButton, TMagicPagination, TMagicTable, TMagicTooltip, TMagicUpload, useZIndex } from '@tmagic/design';
import type { SortProp } from '../../schema'; import type { SortProp } from '../schema';
import { sortChange } from '../../utils/form'; import { sortChange } from '../utils/form';
import type { TableProps } from './type'; import type { TableProps } from './type';
import { useAdd } from './useAdd';
import { useFullscreen } from './useFullscreen'; import { useFullscreen } from './useFullscreen';
import { useImport } from './useImport'; import { useImport } from './useImport';
import { usePagination } from './usePagination'; import { usePagination } from './usePagination';
@ -103,12 +120,13 @@ defineOptions({
const props = withDefaults(defineProps<TableProps>(), { const props = withDefaults(defineProps<TableProps>(), {
prop: '', prop: '',
sortKey: '', sortKey: '',
enableToggleMode: true,
showIndex: true, showIndex: true,
lastValues: () => ({}), lastValues: () => ({}),
isCompare: false, isCompare: false,
}); });
const emit = defineEmits(['change', 'select', 'addDiffCount', 'add']); const emit = defineEmits(['change', 'select', 'addDiffCount']);
const modelName = computed(() => props.name || props.config.name || ''); const modelName = computed(() => props.name || props.config.name || '');
const tMagicTableRef = useTemplateRef<InstanceType<typeof TMagicTable>>('tMagicTable'); const tMagicTableRef = useTemplateRef<InstanceType<typeof TMagicTable>>('tMagicTable');
@ -118,17 +136,44 @@ const { pageSize, currentPage, paginationData, handleSizeChange, handleCurrentCh
modelName, modelName,
); );
const { nextZIndex } = useZIndex();
const updateKey = ref(1); const updateKey = ref(1);
const { addable, newHandler } = useAdd(props, emit);
const { columns } = useTableColumns(props, emit, currentPage, pageSize, modelName); const { columns } = useTableColumns(props, emit, currentPage, pageSize, modelName);
useSortable(props, emit, tMagicTableRef, modelName, updateKey); useSortable(props, emit, tMagicTableRef, modelName, updateKey);
const { isFullscreen, fullscreenZIndex, toggleFullscreen } = useFullscreen(); const { isFullscreen, toggleFullscreen } = useFullscreen();
const { importable, excelHandler, clearHandler } = useImport(props, emit, newHandler);
const { importable, excelHandler, clearHandler } = useImport(props, emit);
const { selectHandle, toggleRowSelection } = useSelection(props, emit, tMagicTableRef); const { selectHandle, toggleRowSelection } = useSelection(props, emit, tMagicTableRef);
const data = computed(() => (props.config.pagination ? paginationData.value : props.model[modelName.value])); const data = computed(() => (props.config.pagination ? paginationData.value : props.model[modelName.value]));
const toggleMode = () => {
const calcLabelWidth = (label: string) => {
if (!label) return '0px';
const zhLength = label.match(/[^\x00-\xff]/g)?.length || 0;
const chLength = label.length - zhLength;
return `${Math.max(chLength * 8 + zhLength * 20, 80)}px`;
};
// groupList
props.config.type = 'groupList';
props.config.enableToggleMode = true;
props.config.tableItems = props.config.items;
props.config.items =
props.config.groupItems ||
props.config.items.map((item: any) => {
const text = item.text || item.label;
const labelWidth = calcLabelWidth(text);
return {
...item,
text,
labelWidth,
span: item.span || 12,
};
});
};
const sortChangeHandler = (sortOptions: SortProp) => { const sortChangeHandler = (sortOptions: SortProp) => {
const modelName = props.name || props.config.name || ''; const modelName = props.name || props.config.name || '';
sortChange(props.model[modelName], sortOptions); sortChange(props.model[modelName], sortOptions);

View File

@ -13,5 +13,6 @@ export interface TableProps {
sortKey?: string; sortKey?: string;
text?: string; text?: string;
size?: string; size?: string;
enableToggleMode?: boolean;
showIndex?: boolean; showIndex?: boolean;
} }

View File

@ -1,40 +1,30 @@
import { computed, inject } from 'vue'; import { computed, inject } from 'vue';
import { tMagicMessage } from '@tmagic/design'; import { tMagicMessage } from '@tmagic/design';
import type { FormConfig, FormState, TableConfig, TableGroupListCommonConfig } from '@tmagic/form-schema'; import type { FormConfig, FormState } from '@tmagic/form-schema';
import { initValue } from '../../utils/form'; import { initValue } from '../utils/form';
import type { TableProps } from '../table/type';
import type { TableProps } from './type';
export const useAdd = ( export const useAdd = (
props: Pick<TableProps, 'name' | 'model' | 'prop' | 'sortKey'> & { props: TableProps,
config: Pick<TableGroupListCommonConfig, 'addable' | 'max' | 'beforeAddRow' | 'defaultAdd' | 'enum'> & emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
Pick<TableConfig, 'key' | 'name'> & {
items: { name?: string | number }[];
};
},
emit: (event: 'change', ...args: any[]) => void,
) => { ) => {
const mForm = inject<FormState | undefined>('mForm'); const mForm = inject<FormState | undefined>('mForm');
const addable = computed(() => { const addable = computed(() => {
const modelName = props.name || props.config.name || ''; const modelName = props.name || props.config.name || '';
if (!modelName) return false;
if (!props.model[modelName].length) { if (!props.model[modelName].length) {
return true; return true;
} }
if (typeof props.config.addable === 'function') { if (typeof props.config.addable === 'function') {
return props.config.addable(mForm, { return props.config.addable(mForm, {
model: props.model[modelName], model: props.model[modelName],
formValue: mForm?.values, formValue: mForm?.values,
prop: props.prop, prop: props.prop,
config: props.config,
}); });
} }
return typeof props.config.addable === 'undefined' ? true : props.config.addable; return typeof props.config.addable === 'undefined' ? true : props.config.addable;
}); });
@ -47,7 +37,7 @@ export const useAdd = (
} }
if (typeof props.config.beforeAddRow === 'function') { if (typeof props.config.beforeAddRow === 'function') {
const beforeCheckRes = await props.config.beforeAddRow(mForm, { const beforeCheckRes = props.config.beforeAddRow(mForm, {
model: props.model[modelName], model: props.model[modelName],
formValue: mForm?.values, formValue: mForm?.values,
prop: props.prop, prop: props.prop,

View File

@ -0,0 +1,18 @@
import { ref } from 'vue';
export const useFullscreen = () => {
const isFullscreen = ref(false);
const toggleFullscreen = () => {
if (isFullscreen.value) {
isFullscreen.value = false;
} else {
isFullscreen.value = true;
}
};
return {
isFullscreen,
toggleFullscreen,
};
};

View File

@ -8,7 +8,8 @@ import type { TableProps } from './type';
export const useImport = ( export const useImport = (
props: TableProps, props: TableProps,
emit: (event: 'select' | 'change' | 'addDiffCount' | 'add', ...args: any[]) => void, emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
newHandler: (row: any) => void,
) => { ) => {
const mForm = inject<FormState | undefined>('mForm'); const mForm = inject<FormState | undefined>('mForm');
const modelName = computed(() => props.name || props.config.name || ''); const modelName = computed(() => props.name || props.config.name || '');
@ -40,7 +41,9 @@ export const useImport = (
pdata.SheetNames.forEach((sheetName: string) => { pdata.SheetNames.forEach((sheetName: string) => {
const arr = (globalThis as any).XLSX.utils.sheet_to_json(pdata.Sheets[sheetName], { header: 1 }); const arr = (globalThis as any).XLSX.utils.sheet_to_json(pdata.Sheets[sheetName], { header: 1 });
if (arr?.[0]) { if (arr?.[0]) {
emit('add', arr); arr.forEach((row: any) => {
newHandler(row);
});
} }
setTimeout(() => { setTimeout(() => {
excelBtn.value?.clearFiles(); excelBtn.value?.clearFiles();

View File

@ -1,6 +1,6 @@
import { computed, type Ref, ref } from 'vue'; import { computed, type Ref, ref } from 'vue';
import { getDataByPage } from '../../utils/form'; import { getDataByPage } from '../utils/form';
import type { TableProps } from './type'; import type { TableProps } from './type';

View File

@ -4,7 +4,7 @@ import type { default as SortableType, SortableEvent } from 'sortablejs';
import { type TMagicTable } from '@tmagic/design'; import { type TMagicTable } from '@tmagic/design';
import type { FormState } from '@tmagic/form-schema'; import type { FormState } from '@tmagic/form-schema';
import { sortArray } from '../../utils/form'; import { sortArray } from '../utils/form';
import type { TableProps } from './type'; import type { TableProps } from './type';

View File

@ -5,9 +5,9 @@ import { cloneDeep } from 'lodash-es';
import { type TableColumnOptions, TMagicIcon, TMagicTooltip } from '@tmagic/design'; import { type TableColumnOptions, TMagicIcon, TMagicTooltip } from '@tmagic/design';
import type { FormItemConfig, FormState, TableColumnConfig } from '@tmagic/form-schema'; import type { FormItemConfig, FormState, TableColumnConfig } from '@tmagic/form-schema';
import type { ContainerChangeEventData } from '../../schema'; import Container from '../containers/Container.vue';
import { display as displayFunc, getDataByPage, sortArray } from '../../utils/form'; import type { ContainerChangeEventData } from '../schema';
import Container from '../Container.vue'; import { display as displayFunc, getDataByPage, sortArray } from '../utils/form';
import ActionsColumn from './ActionsColumn.vue'; import ActionsColumn from './ActionsColumn.vue';
import SortColumn from './SortColumn.vue'; import SortColumn from './SortColumn.vue';
@ -187,7 +187,7 @@ export const useTableColumns = (
columns.push({ columns.push({
props: { props: {
prop: column.name, prop: column.name,
label: column.label || column.text, label: column.label,
width: column.width, width: column.width,
sortable: column.sortable, sortable: column.sortable,
sortOrders: ['ascending', 'descending'], sortOrders: ['ascending', 'descending'],
@ -223,10 +223,7 @@ export const useTableColumns = (
gap: '5px', gap: '5px',
}, },
}, },
[ [h('span', column.label), h(TMagicIcon, {}, { default: () => h(WarningFilled) })],
h('span', column.label || column.text),
h(TMagicIcon, {}, { default: () => h(WarningFilled) }),
],
), ),
content: () => content: () =>
h('div', { h('div', {

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/schema", "name": "@tmagic/schema",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/stage", "name": "@tmagic/stage",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/table", "name": "@tmagic/table",
"type": "module", "type": "module",
"sideEffects": [ "sideEffects": [

View File

@ -139,7 +139,6 @@ const tableColumns = computed(() =>
prop: item.prop, prop: item.prop,
type, type,
selectable: item.selectable, selectable: item.selectable,
sortable: item.sortable,
}, },
cell: type === 'selection' ? undefined : ({ row, $index }: any) => cellRender(item, { row, $index }), cell: type === 'selection' ? undefined : ({ row, $index }: any) => cellRender(item, { row, $index }),
}; };

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/tdesign-vue-next-adapter", "name": "@tmagic/tdesign-vue-next-adapter",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.13", "version": "1.7.10",
"name": "@tmagic/utils", "name": "@tmagic/utils",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "tmagic-playground", "name": "tmagic-playground",
"version": "1.7.13", "version": "1.7.10",
"type": "module", "type": "module",
"private": true, "private": true,
"scripts": { "scripts": {
@ -12,11 +12,11 @@
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.2", "@element-plus/icons-vue": "^2.3.2",
"@tmagic/core": "1.7.13", "@tmagic/core": "1.7.10",
"@tmagic/design": "1.7.13", "@tmagic/design": "1.7.10",
"@tmagic/editor": "1.7.13", "@tmagic/editor": "1.7.10",
"@tmagic/element-plus-adapter": "1.7.13", "@tmagic/element-plus-adapter": "1.7.10",
"@tmagic/tdesign-vue-next-adapter": "1.7.13", "@tmagic/tdesign-vue-next-adapter": "1.7.10",
"@tmagic/tmagic-form-runtime": "1.1.3", "@tmagic/tmagic-form-runtime": "1.1.3",
"element-plus": "^2.11.8", "element-plus": "^2.11.8",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
@ -30,8 +30,8 @@
"@types/lodash-es": "^4.17.4", "@types/lodash-es": "^4.17.4",
"@types/node": "^24.0.10", "@types/node": "^24.0.10",
"@types/serialize-javascript": "^5.0.4", "@types/serialize-javascript": "^5.0.4",
"@vitejs/plugin-legacy": "^8.0.1", "@vitejs/plugin-legacy": "^8.0.0",
"@vitejs/plugin-vue": "^6.0.6", "@vitejs/plugin-vue": "^6.0.5",
"@vitejs/plugin-vue-jsx": "^5.1.5", "@vitejs/plugin-vue-jsx": "^5.1.5",
"@vue/compiler-sfc": "catalog:", "@vue/compiler-sfc": "catalog:",
"typescript": "catalog:", "typescript": "catalog:",

1941
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,14 @@
packages: packages:
- "packages/*" - 'packages/*'
- "playground" - 'playground'
- "runtime/*" - 'runtime/*'
- "vue-components/*" - 'vue-components/*'
- "react-components/*" - 'react-components/*'
- "eslint-config" - 'eslint-config'
catalog: catalog:
vue: ^3.5.33 vue: ^3.5.24
"@vue/compiler-sfc": ^3.5.33 '@vue/compiler-sfc': ^3.5.24
vite: ^8.0.10 vite: ^8.0.3
typescript: "^6.0.3" typescript: "^6.0.2"

View File

@ -1,6 +1,6 @@
{ {
"name": "runtime-react", "name": "runtime-react",
"version": "1.7.13", "version": "1.7.10",
"type": "module", "type": "module",
"private": true, "private": true,
"engines": { "engines": {
@ -16,16 +16,16 @@
"build:playground": "node scripts/build.mjs --type=playground" "build:playground": "node scripts/build.mjs --type=playground"
}, },
"dependencies": { "dependencies": {
"@tmagic/core": "1.7.13", "@tmagic/core": "1.7.10",
"@tmagic/react-runtime-help": "0.2.2", "@tmagic/react-runtime-help": "0.2.2",
"@tmagic/stage": "1.7.13", "@tmagic/stage": "1.7.10",
"axios": "^1.13.2", "axios": "^1.13.2",
"qrcode": "^1.5.0", "qrcode": "^1.5.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1" "react-dom": "^18.3.1"
}, },
"devDependencies": { "devDependencies": {
"@tmagic/cli": "1.7.13", "@tmagic/cli": "1.7.10",
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",

View File

@ -1,5 +1,5 @@
{ {
"version": "2.0.3", "version": "2.0.1",
"name": "@tmagic/vue-runtime-help", "name": "@tmagic/vue-runtime-help",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,9 +1,9 @@
import { computed, inject, nextTick, reactive, ref, watch } from 'vue'; import { computed, inject, nextTick, reactive, ref, watch } from 'vue';
import type TMagicApp from '@tmagic/core'; import type TMagicApp from '@tmagic/core';
import type { Id, MApp, MNode, MPage, MPageFragment } from '@tmagic/core'; import type { Id, MApp, MNode } from '@tmagic/core';
import { asyncLoadCss, getElById, getNodePath, replaceChildNode } from '@tmagic/core'; import { getElById, getNodePath, replaceChildNode } from '@tmagic/core';
import type { Magic, RemoveData, Runtime, UpdateData } from '@tmagic/stage'; import type { Magic, RemoveData, UpdateData } from '@tmagic/stage';
declare global { declare global {
interface Window { interface Window {
@ -11,22 +11,7 @@ declare global {
} }
} }
let styleEl: HTMLStyleElement | null = null; export const useEditorDsl = (app = inject<TMagicApp>('app'), win = window) => {
const createCss = async (config: MPage | MPageFragment) => {
if (config.cssFile) {
await asyncLoadCss(config.cssFile, window.document);
}
if (config.css) {
if (!styleEl) {
styleEl = window.document.createElement('style');
window.document.head.appendChild(styleEl);
}
styleEl.innerHTML = config.css;
}
};
export const useEditorDsl = (app = inject<TMagicApp>('app'), runtimeApi: Runtime = {}, win = window) => {
const root = ref<MApp>(); const root = ref<MApp>();
const curPageId = ref<Id>(); const curPageId = ref<Id>();
const selectedId = ref<Id>(); const selectedId = ref<Id>();
@ -35,37 +20,31 @@ export const useEditorDsl = (app = inject<TMagicApp>('app'), runtimeApi: Runtime
() => root.value?.items?.find((item: MNode) => item.id === curPageId.value) || root.value?.items?.[0], () => root.value?.items?.find((item: MNode) => item.id === curPageId.value) || root.value?.items?.[0],
); );
watch(pageConfig, (config) => { watch(pageConfig, async () => {
if (!config) return; await nextTick();
const page =
setTimeout(() => { document.querySelector<HTMLElement>('.magic-ui-page') ||
const page = document.querySelector<HTMLElement>('.magic-ui-page-fragment');
document.querySelector<HTMLElement>('.magic-ui-page') || page && win.magic?.onPageElUpdate(page);
document.querySelector<HTMLElement>('.magic-ui-page-fragment');
page && win.magic?.onPageElUpdate(page);
});
createCss(config);
}); });
const updateRoot = (config: MApp) => { win.magic?.onRuntimeReady({
root.value = config; getApp() {
if (typeof curPageId.value === 'undefined') { return app;
curPageId.value = config.items?.[0]?.id; },
}
if (typeof selectedId.value === 'undefined') { updateRootConfig(config: MApp) {
selectedId.value = curPageId.value; root.value = config;
}
app?.setConfig(config, curPageId.value); if (typeof curPageId.value === 'undefined') {
}; curPageId.value = config.items?.[0]?.id;
}
window.magic?.onRuntimeReady({ if (typeof selectedId.value === 'undefined') {
getApp: () => app, selectedId.value = curPageId.value;
}
updateRootConfig: (config: MApp) => { app?.setConfig(config, curPageId.value);
updateRoot(config);
}, },
updatePageId(id: Id) { updatePageId(id: Id) {
@ -86,14 +65,8 @@ export const useEditorDsl = (app = inject<TMagicApp>('app'), runtimeApi: Runtime
return nextTick().then(() => getElById()(document, `${id}`)); return nextTick().then(() => getElById()(document, `${id}`));
}, },
add: ({ config, parentId, root: appConfig }: UpdateData) => { add({ config, parentId }: UpdateData) {
if (!root.value) { if (!root.value) throw new Error('error');
if (appConfig) {
updateRoot(appConfig);
return;
}
throw new Error('error');
}
if (!selectedId.value) throw new Error('error'); if (!selectedId.value) throw new Error('error');
if (!parentId) throw new Error('error'); if (!parentId) throw new Error('error');
@ -114,15 +87,13 @@ export const useEditorDsl = (app = inject<TMagicApp>('app'), runtimeApi: Runtime
} }
}, },
update: ({ config, parentId, root: appConfig }: UpdateData) => { update({ config, parentId }: UpdateData) {
if (!root.value) { if (!root.value || !app) throw new Error('error');
if (appConfig) {
updateRoot(appConfig); if (config.type === 'app') {
return; this.updateRootConfig?.(config as MApp);
} return;
throw new Error('error');
} }
if (!app) throw new Error('error');
const newNode = app.dataSourceManager?.compiledNode(config, undefined, true) || config; const newNode = app.dataSourceManager?.compiledNode(config, undefined, true) || config;
@ -138,7 +109,7 @@ export const useEditorDsl = (app = inject<TMagicApp>('app'), runtimeApi: Runtime
} }
}, },
remove: ({ id, parentId }: RemoveData) => { remove({ id, parentId }: RemoveData) {
if (!root.value) throw new Error('error'); if (!root.value) throw new Error('error');
const node = getNodePath(id, [root.value]).pop(); const node = getNodePath(id, [root.value]).pop();
@ -156,12 +127,9 @@ export const useEditorDsl = (app = inject<TMagicApp>('app'), runtimeApi: Runtime
const index = parent.items?.findIndex((child: MNode) => child.id === node.id); const index = parent.items?.findIndex((child: MNode) => child.id === node.id);
parent.items.splice(index, 1); parent.items.splice(index, 1);
}, },
...runtimeApi,
}); });
return { return {
root,
pageConfig, pageConfig,
app, app,
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "runtime-vue", "name": "runtime-vue",
"version": "1.7.13", "version": "1.7.10",
"type": "module", "type": "module",
"private": true, "private": true,
"engines": { "engines": {
@ -16,18 +16,18 @@
"build:playground": "node scripts/build.mjs --type=playground" "build:playground": "node scripts/build.mjs --type=playground"
}, },
"dependencies": { "dependencies": {
"@tmagic/core": "1.7.13", "@tmagic/core": "1.7.10",
"@tmagic/stage": "1.7.13", "@tmagic/stage": "1.7.10",
"@tmagic/vue-runtime-help": "^2.0.1", "@tmagic/vue-runtime-help": "^2.0.0",
"axios": "^1.13.2", "axios": "^1.13.2",
"vue": "catalog:" "vue": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"@tmagic/cli": "1.7.13", "@tmagic/cli": "1.7.10",
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/node": "^24.0.10", "@types/node": "^24.0.10",
"@vitejs/plugin-legacy": "^8.0.1", "@vitejs/plugin-legacy": "^8.0.0",
"@vitejs/plugin-vue": "^6.0.6", "@vitejs/plugin-vue": "^6.0.5",
"@vitejs/plugin-vue-jsx": "^5.1.5", "@vitejs/plugin-vue-jsx": "^5.1.5",
"@vue/compiler-sfc": "catalog:", "@vue/compiler-sfc": "catalog:",
"fs-extra": "^11.3.1", "fs-extra": "^11.3.1",

View File

@ -20,10 +20,9 @@ if (args.package) {
const pkgRoot = path.resolve(packagesDir, args.package); const pkgRoot = path.resolve(packagesDir, args.package);
if (fs.statSync(pkgRoot).isDirectory()) { if (fs.statSync(pkgRoot).isDirectory()) {
rimraf.sync(path.resolve(packagesDir, `./${args.package}/dist`)); rimraf.sync(path.resolve(packagesDir, `./${args.package}/dist`));
const pkg = createRequire(import.meta.url)(`../packages/${args.package}/package.json`);
build({ packageName: args.package, format: 'es', pkg, packagesDir }); build({ packageName: args.package, format: 'es' });
build({ packageName: args.package, format: 'umd', pkg, packagesDir }); build({ packageName: args.package, format: 'umd' });
} }
} else { } else {
const packages = getPackageNames(packagesDir); const packages = getPackageNames(packagesDir);
@ -46,30 +45,6 @@ if (args.package) {
} }
} }
// rolldown 在 UMD 输出顶部会注入
// Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
// 当内联的依赖(如 lodash-es 的 _Symbol.js声明 `var Symbol = root.Symbol;`
// 时,由于 var hoisting该局部 `Symbol` 会把上面一行引用到的全局 `Symbol`
// 遮蔽掉(此时局部变量还未赋值),运行时抛出
// TypeError: Cannot read properties of undefined (reading 'toStringTag')
// 这里通过后处理把该引用改为 `globalThis.Symbol.toStringTag`,绕开被 hoist
// 的局部绑定。rolldown 修好前先用此 workaround。
function fixUmdSymbolShadow() {
return {
name: 'tmagic:fix-umd-symbol-shadow',
generateBundle(outputOptions, bundle) {
if (outputOptions.format !== 'umd') return;
for (const file of Object.values(bundle)) {
if (file.type !== 'chunk' || typeof file.code !== 'string') continue;
file.code = file.code.replace(
/Object\.defineProperty\(exports,\s*Symbol\.toStringTag,/g,
'Object.defineProperty(exports, globalThis.Symbol.toStringTag,',
);
}
},
};
}
async function build({ packageName, format, pkg, packagesDir }) { async function build({ packageName, format, pkg, packagesDir }) {
await buildVite({ await buildVite({
root: path.resolve(packagesDir, `./${packageName}`), root: path.resolve(packagesDir, `./${packageName}`),
@ -94,7 +69,6 @@ async function build({ packageName, format, pkg, packagesDir }) {
}, },
rolldownOptions: { rolldownOptions: {
plugins: [fixUmdSymbolShadow()],
// 确保外部化处理那些你不想打包进库的依赖 // 确保外部化处理那些你不想打包进库的依赖
external(id) { external(id) {
if (format === 'umd' && id === 'lodash-es') { if (format === 'umd' && id === 'lodash-es') {