Compare commits

...

15 Commits

Author SHA1 Message Date
roymondchen
b5af91f86c chore: update lock 2026-04-23 20:52:39 +08:00
roymondchen
540d9cd5bb chore(vue-runtime-help): release v2.0.2 2026-04-23 20:50:21 +08:00
roymondchen
407572ee3a chore: release v1.7.11 2026-04-23 20:41:21 +08:00
roymondchen
d7ad63d3a2 refactor(form): 调整 table-group-list 属性透传与切换按钮控制方式
- TableGroupList 新增 showIndex、sortKey、sort 属性透传至 Table
- 切换按钮显示改由 config.enableToggleMode 或组件 prop 控制,Table 不再内置 enableToggleMode
- GroupList 新增 showIndex 属性
- DisplayConds 关闭切换模式
- GroupListConfig.beforeAddRow 返回值去掉 Promise 支持

Made-with: Cursor
2026-04-23 20:38:56 +08:00
roymondchen
4cd54d1397 feat(table): 支持列排序配置
- design: TableColumnOptions.sortable 支持 boolean | string
- table: 将 sortable 传递到表格列配置

Made-with: Cursor
2026-04-23 20:04:44 +08:00
roymondchen
7249b5106e fix(form): group-list lastValues 为空时兼容取值报错
Made-with: Cursor
2026-04-23 19:45:43 +08:00
roymondchen
9ba12e97af fix(form): table 全屏每次进入重新获取 z-index
close #672

Made-with: Cursor
2026-04-23 17:19:52 +08:00
roymondchen
e106c081c8 feat(editor): 样式面板布局分组新增透明度配置
closes #675

Made-with: Cursor
2026-04-23 17:08:51 +08:00
roymondchen
9f21f8f1d5 refactor(form): 抽出 TableGroupList 父组件统一管理 Table/GroupList 切换
- 新增 TableGroupList 父组件集中持有 displayMode 状态并派生两种形态的
  config,避免原实现通过直接改写 props.config 来触发视图切换
- 将公共的 toggle/add 按钮上移到 TableGroupList,Table/GroupList 通过
  具名 slot + scoped slot 暴露位置与业务钩子(newHandler/addHandler)
- m-form-table、m-form-group-list 统一注册为 TableGroupList,对外导出
  的 MTable/MGroupList 也指向它,新增 MTableGroupList 显式导出
- useAdd 移除重复的 addable 计算,由父组件统一管理

Made-with: Cursor
2026-04-23 17:03:04 +08:00
roymondchen
b46b571214 feat(editor): 没有参考线时不显示参考线切换按钮
Made-with: Cursor
2026-04-23 15:42:10 +08:00
roymondchen
6a4a4ed122 fix(vue-runtime-help): 删除所有页面后,新增页面出错
fix #668
2026-04-23 15:33:19 +08:00
roymondchen
63698af00a chore: update deps 2026-04-23 15:13:37 +08:00
roymondchen
ac755ac3d0 feat(form): group-list 支持 max 限制和 beforeAddRow 前置校验
- group-list 新增 max 配置项,限制最大行数
- group-list 支持 beforeAddRow 异步前置校验回调
- table 的 beforeAddRow 支持返回 Promise

Made-with: Cursor
2026-04-23 15:06:45 +08:00
EvanWu
9bf42f9007 docs: 更新 README.md 环境要求
更新 Node.js 和 pnpm 版本要求,与 package.json engines 和 packageManager 保持一致。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 11:24:53 +08:00
roymondchen
3875ccde33 build: 升级 vite@8.0.8 并修复 rolldown UMD Symbol.toStringTag 遮蔽问题
- 升级 vite 至 ^8.0.8(catalog 及 lockfile 同步)
- 在 scripts/build.mjs 中新增 fixUmdSymbolShadow 插件,将 UMD 产物中
  `Object.defineProperty(exports, Symbol.toStringTag, ...)` 替换为
  `globalThis.Symbol.toStringTag`,规避 lodash-es 内联 `var Symbol` 声明
  因变量提升遮蔽全局 Symbol 导致运行时报错的问题

Made-with: Cursor
2026-04-17 15:20:28 +08:00
41 changed files with 1431 additions and 1134 deletions

View File

@ -1,3 +1,22 @@
## [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 >= 18 node.js ^20.19.0 || >=22.12.0
pnpm >= 9 pnpm >= 10
先安装 pnpm 先安装 pnpm

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.10", "version": "1.7.11",
"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.5", "@vitejs/plugin-vue": "^6.0.6",
"@vitest/coverage-v8": "^4.1.0", "@vitest/coverage-v8": "^4.1.5",
"@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.0.3", "eslint": "^10.2.1",
"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.1", "prettier": "^3.8.3",
"recast": "^0.23.11", "recast": "^0.23.11",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rolldown": "^1.0.0-rc.9", "rolldown": "^1.0.0-rc.17",
"rolldown-plugin-dts": "^0.22.5", "rolldown-plugin-dts": "^0.23.2",
"sass-embedded": "^1.93.3", "sass-embedded": "^1.99.0",
"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.6" "vue-tsc": "^3.2.7"
}, },
"config": { "config": {
"commitizen": { "commitizen": {

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.10", "version": "1.7.11",
"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.10", "version": "1.7.11",
"name": "@tmagic/core", "name": "@tmagic/core",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.10", "version": "1.7.11",
"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.10", "version": "1.7.11",
"name": "@tmagic/dep", "name": "@tmagic/dep",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,5 +1,5 @@
{ {
"version": "1.7.10", "version": "1.7.11",
"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; sortable?: boolean | string;
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.10", "version": "1.7.11",
"name": "@tmagic/editor", "name": "@tmagic/editor",
"type": "module", "type": "module",
"sideEffects": [ "sideEffects": [

View File

@ -67,6 +67,7 @@ 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,10 +59,12 @@ export const useStage = (stageOptions: StageOptions) => {
}, },
); );
stage.mask?.setGuides([ const hGuidesCache = getGuideLineFromCache(getGuideLineKey(H_GUIDE_LINE_STORAGE_KEY));
getGuideLineFromCache(getGuideLineKey(H_GUIDE_LINE_STORAGE_KEY)), const vGuidesCache = getGuideLineFromCache(getGuideLineKey(V_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);
@ -124,6 +126,11 @@ 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,6 +37,7 @@ 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'));
@ -143,6 +144,7 @@ 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

@ -55,6 +55,7 @@ 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,6 +253,8 @@ 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

@ -106,6 +106,7 @@ 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.10", "version": "1.7.11",
"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.10", "version": "1.7.11",
"name": "@tmagic/form-schema", "name": "@tmagic/form-schema",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -761,7 +761,7 @@ export interface TableConfig extends FormItem {
titleTip?: FilterFunction<string>; titleTip?: FilterFunction<string>;
rowKey?: string; rowKey?: string;
/** table 新增行时前置回调 */ /** table 新增行时前置回调 */
beforeAddRow?: (mForm: FormState | undefined, data: any) => boolean; beforeAddRow?: (mForm: FormState | undefined, data: any) => boolean | Promise<boolean>;
addButtonConfig?: { addButtonConfig?: {
props?: Record<string, any>; props?: Record<string, any>;
text?: string; text?: string;
@ -804,6 +804,9 @@ export interface GroupListConfig<T = never> extends FormItem {
props?: Record<string, any>; props?: Record<string, any>;
text?: string; text?: string;
}; };
/** 最大行数 */
max?: number;
beforeAddRow?: (mForm: FormState | undefined, data: any) => boolean;
} }
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.10", "version": "1.7.11",
"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,30 +27,19 @@
></MFieldsGroupListItem> ></MFieldsGroupListItem>
<div class="m-fields-group-list-footer"> <div class="m-fields-group-list-footer">
<TMagicButton v-if="config.enableToggleMode" :icon="Grid" size="small" @click="toggleMode" <slot name="toggle-button"></slot>
>切换为表格</TMagicButton
>
<div style="display: flex; justify-content: flex-end; flex: 1"> <div style="display: flex; justify-content: flex-end; flex: 1">
<TMagicButton <slot name="add-button" :trigger="addHandler"></slot>
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 { inject } from 'vue';
import { Grid, Plus } from '@element-plus/icons-vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { TMagicButton } from '@tmagic/design'; import { tMagicMessage } from '@tmagic/design';
import type { ContainerChangeEventData, FormState, GroupListConfig } from '../schema'; import type { ContainerChangeEventData, FormState, GroupListConfig } from '../schema';
import { initValue } from '../utils/form'; import { initValue } from '../utils/form';
@ -71,6 +60,7 @@ const props = defineProps<{
prop?: string; prop?: string;
size?: string; size?: string;
disabled?: boolean; disabled?: boolean;
showIndex?: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@ -80,21 +70,6 @@ const emit = defineEmits<{
const mForm = inject<FormState | undefined>('mForm'); 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);
}; };
@ -102,6 +77,20 @@ const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
const addHandler = async () => { const addHandler = async () => {
if (!props.name) return false; if (!props.name) return false;
if (props.config.max && props.model[props.name].length >= props.config.max) {
tMagicMessage.error(`最多新增配置不能超过${props.config.max}`);
return;
}
if (typeof props.config.beforeAddRow === 'function') {
const beforeCheckRes = await props.config.beforeAddRow(mForm, {
model: props.model[props.name],
formValue: mForm?.values,
prop: props.prop,
});
if (!beforeCheckRes) return;
}
let initValues = {}; let initValues = {};
if (typeof props.config.defaultAdd === 'function') { if (typeof props.config.defaultAdd === 'function') {
@ -153,17 +142,6 @@ 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

@ -0,0 +1,181 @@
<template>
<component
:is="displayMode === 'table' ? MFormTable : MFormGroupList"
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"
>
<template #toggle-button>
<TMagicButton
v-if="config.enableToggleMode || enableToggleMode"
:icon="Grid"
size="small"
@click="toggleDisplayMode"
>
{{ displayMode === 'table' ? '展开配置' : '切换为表格' }}
</TMagicButton>
</template>
<template #add-button="{ trigger }">
<TMagicButton
v-if="addable"
: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="trigger"
>
{{ currentConfig.addButtonConfig?.text || (displayMode === 'table' ? '新增一行' : '新增') }}
</TMagicButton>
</template>
</component>
</template>
<script setup lang="ts">
import { computed, inject, ref } from 'vue';
import { Grid, Plus } from '@element-plus/icons-vue';
import { TMagicButton } from '@tmagic/design';
import type { FormState, GroupListConfig, TableConfig } from '@tmagic/form-schema';
import type { ContainerChangeEventData } from '../schema';
import MFormTable from '../table/Table.vue';
import MFormGroupList from './GroupList.vue';
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 mForm = inject<FormState | undefined>('mForm');
const addable = computed(() => {
const modelName = props.name || props.config.name || '';
if (!modelName) return false;
if (!props.model[modelName].length) {
return true;
}
if (typeof props.config.addable === 'function') {
return props.config.addable(mForm, {
model: props.model[modelName],
formValue: mForm?.values,
prop: props.prop,
config: props.config,
});
}
return typeof props.config.addable === 'undefined' ? true : props.config.addable;
});
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');
</script>

View File

@ -32,8 +32,9 @@ 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 './table/Table.vue'; export { default as MTable } from './containers/TableGroupList.vue';
export { default as MGroupList } from './containers/GroupList.vue'; export { default as MGroupList } from './containers/TableGroupList.vue';
export { default as MTableGroupList } from './containers/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/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,7 +46,6 @@ 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';
@ -70,11 +69,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', GroupList); app.component('m-form-group-list', TableGroupList);
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', Table); app.component('m-form-table', TableGroupList);
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

@ -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: ${nextZIndex()}` : ''" :style="isFullscreen ? `z-index: ${fullscreenZIndex}` : ''"
> >
<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,13 +34,7 @@
<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">
<TMagicButton <slot name="toggle-button" v-if="!isFullscreen"></slot>
:icon="Grid"
size="small"
@click="toggleMode"
v-if="enableToggleMode && config.enableToggleMode !== false && !isFullscreen"
>展开配置</TMagicButton
>
<TMagicButton <TMagicButton
:icon="FullScreen" :icon="FullScreen"
size="small" size="small"
@ -64,17 +58,7 @@
>清空</TMagicButton >清空</TMagicButton
> >
</div> </div>
<TMagicButton <slot name="add-button" :trigger="newHandler"></slot>
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">
@ -96,8 +80,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, useTemplateRef } from 'vue'; import { computed, ref, useTemplateRef, watch } from 'vue';
import { FullScreen, Grid, Plus } from '@element-plus/icons-vue'; import { FullScreen } from '@element-plus/icons-vue';
import { TMagicButton, TMagicPagination, TMagicTable, TMagicTooltip, TMagicUpload, useZIndex } from '@tmagic/design'; import { TMagicButton, TMagicPagination, TMagicTable, TMagicTooltip, TMagicUpload, useZIndex } from '@tmagic/design';
@ -120,7 +104,6 @@ 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,
@ -138,42 +121,24 @@ const { pageSize, currentPage, paginationData, handleSizeChange, handleCurrentCh
const { nextZIndex } = useZIndex(); const { nextZIndex } = useZIndex();
const updateKey = ref(1); const updateKey = ref(1);
const fullscreenZIndex = ref(nextZIndex());
const { addable, newHandler } = useAdd(props, emit); const { 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, toggleFullscreen } = useFullscreen(); const { isFullscreen, toggleFullscreen } = useFullscreen();
watch(isFullscreen, (value) => {
if (value) {
fullscreenZIndex.value = nextZIndex();
}
});
const { importable, excelHandler, clearHandler } = useImport(props, emit, newHandler); const { importable, excelHandler, clearHandler } = useImport(props, emit, newHandler);
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,6 +13,5 @@ export interface TableProps {
sortKey?: string; sortKey?: string;
text?: string; text?: string;
size?: string; size?: string;
enableToggleMode?: boolean;
showIndex?: boolean; showIndex?: boolean;
} }

View File

@ -1,4 +1,4 @@
import { computed, inject } from 'vue'; import { inject } from 'vue';
import { tMagicMessage } from '@tmagic/design'; import { tMagicMessage } from '@tmagic/design';
import type { FormConfig, FormState } from '@tmagic/form-schema'; import type { FormConfig, FormState } from '@tmagic/form-schema';
@ -13,21 +13,6 @@ export const useAdd = (
) => { ) => {
const mForm = inject<FormState | undefined>('mForm'); const mForm = inject<FormState | undefined>('mForm');
const addable = computed(() => {
const modelName = props.name || props.config.name || '';
if (!props.model[modelName].length) {
return true;
}
if (typeof props.config.addable === 'function') {
return props.config.addable(mForm, {
model: props.model[modelName],
formValue: mForm?.values,
prop: props.prop,
});
}
return typeof props.config.addable === 'undefined' ? true : props.config.addable;
});
const newHandler = async (row?: any) => { const newHandler = async (row?: any) => {
const modelName = props.name || props.config.name || ''; const modelName = props.name || props.config.name || '';
@ -37,7 +22,7 @@ export const useAdd = (
} }
if (typeof props.config.beforeAddRow === 'function') { if (typeof props.config.beforeAddRow === 'function') {
const beforeCheckRes = props.config.beforeAddRow(mForm, { const beforeCheckRes = await props.config.beforeAddRow(mForm, {
model: props.model[modelName], model: props.model[modelName],
formValue: mForm?.values, formValue: mForm?.values,
prop: props.prop, prop: props.prop,
@ -106,7 +91,6 @@ export const useAdd = (
}; };
return { return {
addable,
newHandler, newHandler,
}; };
}; };

View File

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

View File

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

View File

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

View File

@ -139,6 +139,7 @@ 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.10", "version": "1.7.11",
"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.10", "version": "1.7.11",
"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.10", "version": "1.7.11",
"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.10", "@tmagic/core": "1.7.11",
"@tmagic/design": "1.7.10", "@tmagic/design": "1.7.11",
"@tmagic/editor": "1.7.10", "@tmagic/editor": "1.7.11",
"@tmagic/element-plus-adapter": "1.7.10", "@tmagic/element-plus-adapter": "1.7.11",
"@tmagic/tdesign-vue-next-adapter": "1.7.10", "@tmagic/tdesign-vue-next-adapter": "1.7.11",
"@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.0", "@vitejs/plugin-legacy": "^8.0.1",
"@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue": "^6.0.6",
"@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,14 +1,13 @@
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.24 vue: ^3.5.33
'@vue/compiler-sfc': ^3.5.24 "@vue/compiler-sfc": ^3.5.33
vite: ^8.0.3 vite: ^8.0.10
typescript: "^6.0.2" typescript: "^6.0.3"

View File

@ -1,6 +1,6 @@
{ {
"name": "runtime-react", "name": "runtime-react",
"version": "1.7.10", "version": "1.7.11",
"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.10", "@tmagic/core": "1.7.11",
"@tmagic/react-runtime-help": "0.2.2", "@tmagic/react-runtime-help": "0.2.2",
"@tmagic/stage": "1.7.10", "@tmagic/stage": "1.7.11",
"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.10", "@tmagic/cli": "1.7.11",
"@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.1", "version": "2.0.2",
"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 } from '@tmagic/core'; import type { Id, MApp, MNode, MPage, MPageFragment } from '@tmagic/core';
import { getElById, getNodePath, replaceChildNode } from '@tmagic/core'; import { asyncLoadCss, getElById, getNodePath, replaceChildNode } from '@tmagic/core';
import type { Magic, RemoveData, UpdateData } from '@tmagic/stage'; import type { Magic, RemoveData, Runtime, UpdateData } from '@tmagic/stage';
declare global { declare global {
interface Window { interface Window {
@ -11,7 +11,22 @@ declare global {
} }
} }
export const useEditorDsl = (app = inject<TMagicApp>('app'), win = window) => { let styleEl: HTMLStyleElement | null = null;
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>();
@ -20,22 +35,21 @@ export const useEditorDsl = (app = inject<TMagicApp>('app'), win = window) => {
() => 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, async () => { watch(pageConfig, (config) => {
await nextTick(); if (!config) return;
setTimeout(() => {
const page = const page =
document.querySelector<HTMLElement>('.magic-ui-page') || document.querySelector<HTMLElement>('.magic-ui-page') ||
document.querySelector<HTMLElement>('.magic-ui-page-fragment'); document.querySelector<HTMLElement>('.magic-ui-page-fragment');
page && win.magic?.onPageElUpdate(page); page && win.magic?.onPageElUpdate(page);
}); });
win.magic?.onRuntimeReady({ createCss(config);
getApp() { });
return app;
},
updateRootConfig(config: MApp) { const updateRoot = (config: MApp) => {
root.value = config; root.value = config;
if (typeof curPageId.value === 'undefined') { if (typeof curPageId.value === 'undefined') {
curPageId.value = config.items?.[0]?.id; curPageId.value = config.items?.[0]?.id;
} }
@ -45,6 +59,13 @@ export const useEditorDsl = (app = inject<TMagicApp>('app'), win = window) => {
} }
app?.setConfig(config, curPageId.value); app?.setConfig(config, curPageId.value);
};
window.magic?.onRuntimeReady({
getApp: () => app,
updateRootConfig: (config: MApp) => {
updateRoot(config);
}, },
updatePageId(id: Id) { updatePageId(id: Id) {
@ -65,8 +86,14 @@ export const useEditorDsl = (app = inject<TMagicApp>('app'), win = window) => {
return nextTick().then(() => getElById()(document, `${id}`)); return nextTick().then(() => getElById()(document, `${id}`));
}, },
add({ config, parentId }: UpdateData) { add: ({ config, parentId, root: appConfig }: UpdateData) => {
if (!root.value) throw new Error('error'); if (!root.value) {
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');
@ -87,13 +114,15 @@ export const useEditorDsl = (app = inject<TMagicApp>('app'), win = window) => {
} }
}, },
update({ config, parentId }: UpdateData) { update: ({ config, parentId, root: appConfig }: UpdateData) => {
if (!root.value || !app) throw new Error('error'); if (!root.value) {
if (appConfig) {
if (config.type === 'app') { updateRoot(appConfig);
this.updateRootConfig?.(config as MApp);
return; 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;
@ -109,7 +138,7 @@ export const useEditorDsl = (app = inject<TMagicApp>('app'), win = window) => {
} }
}, },
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();
@ -127,6 +156,8 @@ export const useEditorDsl = (app = inject<TMagicApp>('app'), win = window) => {
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 {

View File

@ -1,6 +1,6 @@
{ {
"name": "runtime-vue", "name": "runtime-vue",
"version": "1.7.10", "version": "1.7.11",
"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.10", "@tmagic/core": "1.7.11",
"@tmagic/stage": "1.7.10", "@tmagic/stage": "1.7.11",
"@tmagic/vue-runtime-help": "^2.0.0", "@tmagic/vue-runtime-help": "^2.0.1",
"axios": "^1.13.2", "axios": "^1.13.2",
"vue": "catalog:" "vue": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"@tmagic/cli": "1.7.10", "@tmagic/cli": "1.7.11",
"@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.0", "@vitejs/plugin-legacy": "^8.0.1",
"@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue": "^6.0.6",
"@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,9 +20,10 @@ 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' }); build({ packageName: args.package, format: 'es', pkg, packagesDir });
build({ packageName: args.package, format: 'umd' }); build({ packageName: args.package, format: 'umd', pkg, packagesDir });
} }
} else { } else {
const packages = getPackageNames(packagesDir); const packages = getPackageNames(packagesDir);
@ -45,6 +46,30 @@ 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}`),
@ -69,6 +94,7 @@ 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') {