feat: 新增权限指令并集成到多个页面组件

refactor(backend): 重构数据库模型和初始化逻辑

fix(frontend): 修复权限指令在多个组件中的使用问题

perf(backend): 优化数据库连接和初始化性能

docs: 更新低代码生成器的README文档

style: 清理无用代码和注释

chore: 移除不再需要的依赖项

test: 更新用户权限相关测试用例

ci: 更新CI配置以支持新的权限检查

build: 更新依赖项版本
This commit is contained in:
zhangtao
2025-09-28 23:42:26 +08:00
parent 3138fde133
commit 0aeb77e0bd
91 changed files with 5102 additions and 3872 deletions
@@ -0,0 +1,606 @@
/**
* 低代码页面生成器 - 组件 Schema 定义
* 定义组件的 JSON 结构和类型
*/
// 基础组件属性接口
export interface BaseComponentProps {
id: string;
type: string;
label?: string;
children?: ComponentSchema[];
style?: Record<string, any>;
class?: string | string[];
}
// Element Plus 组件类型枚举(只包含已配置的组件)
export enum ComponentType {
// 基础组件
BUTTON = 'el-button',
LINK = 'el-link',
TEXT = 'el-text',
// 布局组件
ROW = 'el-row',
COL = 'el-col',
CONTAINER = 'el-container',
HEADER = 'el-header',
MAIN = 'el-main',
ASIDE = 'el-aside',
FOOTER = 'el-footer',
// 表单组件
FORM = 'el-form',
FORM_ITEM = 'el-form-item',
INPUT = 'el-input',
INPUT_NUMBER = 'el-input-number',
SELECT = 'el-select',
RADIO = 'el-radio',
RADIO_GROUP = 'el-radio-group',
CHECKBOX = 'el-checkbox',
CHECKBOX_GROUP = 'el-checkbox-group',
SWITCH = 'el-switch',
SLIDER = 'el-slider',
TIME_PICKER = 'el-time-picker',
DATE_PICKER = 'el-date-picker',
// 数据展示组件
CARD = 'el-card',
TAG = 'el-tag',
PROGRESS = 'el-progress',
// 反馈组件
ALERT = 'el-alert',
// 其他组件
DIVIDER = 'el-divider'
}
// 组件 Schema 接口
export interface ComponentSchema extends BaseComponentProps {
props?: Record<string, any>;
events?: Record<string, string>;
slots?: Record<string, string>;
}
// 页面 Schema 接口
export interface PageSchema {
id: string;
name: string;
components: ComponentSchema[];
globalStyle?: Record<string, any>;
}
// 组件配置接口
export interface ComponentConfig {
type: ComponentType;
label: string;
icon: string;
category: string;
defaultProps: Record<string, any>;
editableProps: string[];
canHaveChildren: boolean;
maxChildren?: number;
}
// 组件库配置
export const COMPONENT_CONFIGS: Record<ComponentType, ComponentConfig> = {
// 基础组件
[ComponentType.BUTTON]: {
type: ComponentType.BUTTON,
label: '按钮',
icon: 'el-icon-edit',
category: '基础',
defaultProps: {
type: 'primary',
size: 'default',
plain: false,
round: false,
circle: false,
disabled: false,
loading: false,
children: '按钮'
},
editableProps: ['type', 'size', 'plain', 'round', 'circle', 'disabled', 'loading', 'children'],
canHaveChildren: false
},
[ComponentType.LINK]: {
type: ComponentType.LINK,
label: '链接',
icon: 'el-icon-link',
category: '基础',
defaultProps: {
type: 'primary',
href: '',
disabled: false,
underline: true,
children: '链接文本'
},
editableProps: ['type', 'href', 'disabled', 'underline', 'children'],
canHaveChildren: false
},
[ComponentType.TEXT]: {
type: ComponentType.TEXT,
label: '文本',
icon: 'el-icon-document',
category: '基础',
defaultProps: {
type: 'primary',
size: 'default',
tag: 'span',
children: '文本内容'
},
editableProps: ['type', 'size', 'tag', 'children'],
canHaveChildren: false
},
[ComponentType.INPUT]: {
type: ComponentType.INPUT,
label: '输入框',
icon: 'el-icon-edit',
category: '表单',
defaultProps: {
type: 'text',
placeholder: '请输入内容',
clearable: true,
disabled: false,
readonly: false,
maxlength: null,
showWordLimit: false,
size: 'default'
},
editableProps: ['type', 'placeholder', 'clearable', 'disabled', 'readonly', 'maxlength', 'showWordLimit', 'size'],
canHaveChildren: false
},
[ComponentType.INPUT_NUMBER]: {
type: ComponentType.INPUT_NUMBER,
label: '数字输入框',
icon: 'el-icon-plus',
category: '表单',
defaultProps: {
min: 0,
max: 100,
step: 1,
precision: 0,
size: 'default',
disabled: false,
controls: true,
controlsPosition: ''
},
editableProps: ['min', 'max', 'step', 'precision', 'size', 'disabled', 'controls', 'controlsPosition'],
canHaveChildren: false
},
[ComponentType.SELECT]: {
type: ComponentType.SELECT,
label: '选择器',
icon: 'el-icon-arrow-down',
category: '表单',
defaultProps: {
placeholder: '请选择',
clearable: true,
disabled: false,
multiple: false,
filterable: false,
size: 'default'
},
editableProps: ['placeholder', 'clearable', 'disabled', 'multiple', 'filterable', 'size'],
canHaveChildren: true
},
[ComponentType.RADIO]: {
type: ComponentType.RADIO,
label: '单选框',
icon: 'el-icon-check',
category: '表单',
defaultProps: {
label: '选项',
disabled: false,
border: false,
size: 'default'
},
editableProps: ['label', 'disabled', 'border', 'size'],
canHaveChildren: false
},
[ComponentType.RADIO_GROUP]: {
type: ComponentType.RADIO_GROUP,
label: '单选框组',
icon: 'el-icon-check',
category: '表单',
defaultProps: {
disabled: false,
size: 'default',
textColor: '#ffffff',
fill: '#409eff'
},
editableProps: ['disabled', 'size', 'textColor', 'fill'],
canHaveChildren: true
},
[ComponentType.CHECKBOX]: {
type: ComponentType.CHECKBOX,
label: '复选框',
icon: 'el-icon-check',
category: '表单',
defaultProps: {
label: '选项',
disabled: false,
border: false,
size: 'default',
indeterminate: false
},
editableProps: ['label', 'disabled', 'border', 'size', 'indeterminate'],
canHaveChildren: false
},
[ComponentType.CHECKBOX_GROUP]: {
type: ComponentType.CHECKBOX_GROUP,
label: '复选框组',
icon: 'el-icon-check',
category: '表单',
defaultProps: {
disabled: false,
min: null,
max: null,
size: 'default',
textColor: '#ffffff',
fill: '#409eff'
},
editableProps: ['disabled', 'min', 'max', 'size', 'textColor', 'fill'],
canHaveChildren: true
},
[ComponentType.SWITCH]: {
type: ComponentType.SWITCH,
label: '开关',
icon: 'el-icon-switch-button',
category: '表单',
defaultProps: {
disabled: false,
width: 40,
activeText: '',
inactiveText: '',
activeValue: true,
inactiveValue: false,
activeColor: '#409eff',
inactiveColor: '#dcdfe6'
},
editableProps: ['disabled', 'width', 'activeText', 'inactiveText', 'activeValue', 'inactiveValue', 'activeColor', 'inactiveColor'],
canHaveChildren: false
},
[ComponentType.SLIDER]: {
type: ComponentType.SLIDER,
label: '滑块',
icon: 'el-icon-minus',
category: '表单',
defaultProps: {
min: 0,
max: 100,
step: 1,
disabled: false,
showStops: false,
showTooltip: true,
range: false
},
editableProps: ['min', 'max', 'step', 'disabled', 'showStops', 'showTooltip', 'range'],
canHaveChildren: false
},
[ComponentType.DATE_PICKER]: {
type: ComponentType.DATE_PICKER,
label: '日期选择器',
icon: 'el-icon-date',
category: '表单',
defaultProps: {
type: 'date',
placeholder: '选择日期',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
disabled: false,
clearable: true,
size: 'default'
},
editableProps: ['type', 'placeholder', 'format', 'valueFormat', 'disabled', 'clearable', 'size'],
canHaveChildren: false
},
[ComponentType.TIME_PICKER]: {
type: ComponentType.TIME_PICKER,
label: '时间选择器',
icon: 'el-icon-time',
category: '表单',
defaultProps: {
placeholder: '选择时间',
format: 'HH:mm:ss',
valueFormat: 'HH:mm:ss',
disabled: false,
clearable: true,
size: 'default'
},
editableProps: ['placeholder', 'format', 'valueFormat', 'disabled', 'clearable', 'size'],
canHaveChildren: false
},
[ComponentType.CARD]: {
type: ComponentType.CARD,
label: '卡片',
icon: 'el-icon-document',
category: '数据展示',
defaultProps: {
header: '卡片标题',
shadow: 'always',
bodyStyle: {}
},
editableProps: ['header', 'shadow', 'bodyStyle'],
canHaveChildren: true
},
[ComponentType.TAG]: {
type: ComponentType.TAG,
label: '标签',
icon: 'el-icon-price-tag',
category: '数据展示',
defaultProps: {
type: 'primary',
closable: false,
disableTransitions: false,
hit: false,
color: '',
size: 'default',
effect: 'light',
children: '标签'
},
editableProps: ['type', 'closable', 'disableTransitions', 'hit', 'color', 'size', 'effect', 'children'],
canHaveChildren: false
},
[ComponentType.PROGRESS]: {
type: ComponentType.PROGRESS,
label: '进度条',
icon: 'el-icon-loading',
category: '数据展示',
defaultProps: {
percentage: 50,
type: 'line',
strokeWidth: 6,
textInside: false,
status: '',
color: '#409eff',
showText: true,
format: null
},
editableProps: ['percentage', 'type', 'strokeWidth', 'textInside', 'status', 'color', 'showText'],
canHaveChildren: false
},
[ComponentType.ALERT]: {
type: ComponentType.ALERT,
label: '警告',
icon: 'el-icon-warning',
category: '反馈',
defaultProps: {
title: '警告标题',
type: 'info',
description: '',
closable: true,
center: false,
closeText: '',
showIcon: false,
effect: 'light'
},
editableProps: ['title', 'type', 'description', 'closable', 'center', 'closeText', 'showIcon', 'effect'],
canHaveChildren: false
},
[ComponentType.FORM]: {
type: ComponentType.FORM,
label: '表单',
icon: 'el-icon-document',
category: '表单',
defaultProps: {
model: 'formData',
rules: 'formRules',
labelWidth: '120px',
labelPosition: 'right',
inline: false,
labelSuffix: '',
hideRequiredAsterisk: false,
showMessage: true,
inlineMessage: false,
statusIcon: false,
validateOnRuleChange: true,
size: 'default',
disabled: false
},
editableProps: ['labelWidth', 'labelPosition', 'inline', 'labelSuffix', 'hideRequiredAsterisk', 'showMessage', 'inlineMessage', 'statusIcon', 'validateOnRuleChange', 'size', 'disabled'],
canHaveChildren: true
},
[ComponentType.FORM_ITEM]: {
type: ComponentType.FORM_ITEM,
label: '表单项',
icon: 'el-icon-tickets',
category: '表单',
defaultProps: {
label: '标签',
labelWidth: '',
required: false,
rules: null,
error: '',
showMessage: true,
inlineMessage: false,
size: 'default'
},
editableProps: ['label', 'labelWidth', 'required', 'showMessage', 'inlineMessage', 'size'],
canHaveChildren: true
},
[ComponentType.ROW]: {
type: ComponentType.ROW,
label: '行布局',
icon: 'el-icon-menu',
category: '布局',
defaultProps: {
style: {
display: 'flex',
flexDirection: 'row',
gap: '12px',
padding: '12px',
border: '1px dashed #dcdfe6',
borderRadius: '4px',
minHeight: '60px'
}
},
editableProps: ['gap', 'justifyContent', 'alignItems'],
canHaveChildren: true
},
[ComponentType.COL]: {
type: ComponentType.COL,
label: '列布局',
icon: 'el-icon-menu',
category: '布局',
defaultProps: {
style: {
display: 'flex',
flexDirection: 'column',
gap: '12px',
padding: '12px',
border: '1px dashed #dcdfe6',
borderRadius: '4px',
minHeight: '60px'
}
},
editableProps: ['gap', 'justifyContent', 'alignItems'],
canHaveChildren: true
},
[ComponentType.CONTAINER]: {
type: ComponentType.CONTAINER,
label: '容器',
icon: 'el-icon-s-grid',
category: '布局',
defaultProps: {
direction: 'vertical'
},
editableProps: ['direction'],
canHaveChildren: true
},
[ComponentType.HEADER]: {
type: ComponentType.HEADER,
label: '头部',
icon: 'el-icon-top',
category: '布局',
defaultProps: {
height: '60px'
},
editableProps: ['height'],
canHaveChildren: true
},
[ComponentType.MAIN]: {
type: ComponentType.MAIN,
label: '主要区域',
icon: 'el-icon-s-grid',
category: '布局',
defaultProps: {},
editableProps: [],
canHaveChildren: true
},
[ComponentType.ASIDE]: {
type: ComponentType.ASIDE,
label: '侧边栏',
icon: 'el-icon-s-unfold',
category: '布局',
defaultProps: {
width: '300px'
},
editableProps: ['width'],
canHaveChildren: true
},
[ComponentType.FOOTER]: {
type: ComponentType.FOOTER,
label: '底部',
icon: 'el-icon-bottom',
category: '布局',
defaultProps: {
height: '60px'
},
editableProps: ['height'],
canHaveChildren: true
},
[ComponentType.DIVIDER]: {
type: ComponentType.DIVIDER,
label: '分割线',
icon: 'el-icon-minus',
category: '其他',
defaultProps: {
direction: 'horizontal',
contentPosition: 'center',
children: ''
},
editableProps: ['direction', 'contentPosition', 'children'],
canHaveChildren: false
}
};
// 组件分类
export const COMPONENT_CATEGORIES = {
'基础': [ComponentType.BUTTON, ComponentType.LINK, ComponentType.TEXT],
'布局': [ComponentType.ROW, ComponentType.COL, ComponentType.CONTAINER, ComponentType.HEADER, ComponentType.MAIN, ComponentType.ASIDE, ComponentType.FOOTER],
'表单': [ComponentType.FORM, ComponentType.FORM_ITEM, ComponentType.INPUT, ComponentType.INPUT_NUMBER, ComponentType.SELECT, ComponentType.RADIO, ComponentType.RADIO_GROUP, ComponentType.CHECKBOX, ComponentType.CHECKBOX_GROUP, ComponentType.SWITCH, ComponentType.SLIDER, ComponentType.TIME_PICKER, ComponentType.DATE_PICKER],
'数据展示': [ComponentType.CARD, ComponentType.TAG, ComponentType.PROGRESS],
'反馈': [ComponentType.ALERT],
'其他': [ComponentType.DIVIDER]
};
// 生成唯一 ID
export function generateId(): string {
return `component_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// 创建组件实例
export function createComponent(type: ComponentType, props: Record<string, any> = {}): ComponentSchema {
const config = COMPONENT_CONFIGS[type];
if (!config) {
throw new Error(`Unknown component type: ${type}`);
}
const component: ComponentSchema = {
id: generateId(),
type,
props: { ...config.defaultProps, ...props },
children: config.canHaveChildren ? [] : undefined
};
// 布局组件初始化为空容器,不自动生成子组件
return component;
}
// 验证组件 Schema
export function validateComponentSchema(schema: ComponentSchema): boolean {
if (!schema.id || !schema.type) {
return false;
}
const config = COMPONENT_CONFIGS[schema.type as ComponentType];
if (!config) {
return false;
}
if (config.canHaveChildren && schema.children) {
return schema.children.every(child => validateComponentSchema(child));
}
return true;
}
@@ -0,0 +1,412 @@
/**
* 低代码页面生成器 - 序列化器
* 将 JSON Schema 转换为 Vue3 + TypeScript 单文件组件
*/
import { ComponentSchema, PageSchema, ComponentType, COMPONENT_CONFIGS } from './schema';
// 生成 Vue 组件的模板字符串
export function generateVueTemplate(schema: PageSchema): string {
const template = generateTemplateFromComponents(schema.components);
const script = generateScriptSetup(schema);
const style = generateStyle();
return `<template>
${template}
</template>
${script}
${style}`;
}
// 递归生成组件模板
function generateTemplateFromComponents(components: ComponentSchema[], indent = 0): string {
const spaces = ' '.repeat(indent);
let template = '';
for (const component of components) {
template += generateComponentTemplate(component, indent);
}
return template;
}
// 生成单个组件的模板
function generateComponentTemplate(component: ComponentSchema, indent = 0): string {
const spaces = ' '.repeat(indent);
const { type, props = {}, children = [], events = {}, slots = {} } = component;
// 对于行列布局,生成为div
const actualType = (type === 'el-row' || type === 'el-col') ? 'div' : type;
// 生成属性字符串
const propsString = generatePropsString(props, type);
// 生成事件字符串
const eventsString = generateEventsString(events);
// 获取组件内容
const componentContent = getComponentContent(component, indent);
// 生成子组件模板
const childrenTemplate = children.length > 0
? `\n${generateTemplateFromComponents(children, indent + 1)}\n${spaces}`
: '';
// 自闭合标签处理
const isSelfClosing = isSelfClosingTag(actualType);
if (isSelfClosing) {
return `${spaces}<${actualType}${propsString}${eventsString} />\n`;
} else {
const content = componentContent || childrenTemplate;
return `${spaces}<${actualType}${propsString}${eventsString}>${content}</${actualType}>\n`;
}
}
// 获取组件内容
function getComponentContent(component: ComponentSchema, indent = 0): string {
const spaces = ' '.repeat(indent + 1);
// 有文本内容的组件
if (component.props?.children && typeof component.props.children === 'string') {
return component.props.children;
}
// 特殊组件的默认内容
const defaultContent: Record<string, string> = {
'el-button': '按钮',
'el-link': '链接',
'el-text': '文本',
'el-tag': '标签',
'el-alert': '', // alert 通过 title 属性显示内容
'el-card': `\n${spaces}<div>卡片内容</div>\n${spaces.slice(2)}`,
'el-form': `\n${spaces}<!-- 表单项 -->\n${spaces.slice(2)}`,
'el-form-item': `\n${spaces}<!-- 表单控件 -->\n${spaces.slice(2)}`,
'el-container': `\n${spaces}<!-- 容器内容 -->\n${spaces.slice(2)}`,
'el-header': `\n${spaces}<!-- 头部内容 -->\n${spaces.slice(2)}`,
'el-main': `\n${spaces}<!-- 主要内容 -->\n${spaces.slice(2)}`,
'el-aside': `\n${spaces}<!-- 侧边栏内容 -->\n${spaces.slice(2)}`,
'el-footer': `\n${spaces}<!-- 底部内容 -->\n${spaces.slice(2)}`
};
return defaultContent[component.type] || '';
}
// 生成属性字符串
function generatePropsString(props: Record<string, any>, componentType?: string): string {
const propStrings: string[] = [];
for (const [key, value] of Object.entries(props)) {
if (value === null || value === undefined) continue;
// 对于行列布局,将样式属性转换为style
if ((componentType === 'el-row' || componentType === 'el-col') && key === 'style') {
const styleString = Object.entries(value)
.map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${v}`)
.join('; ');
propStrings.push(`style="${styleString}"`);
continue;
}
// 跳过布局组件的特殊属性
if ((componentType === 'el-row' || componentType === 'el-col') &&
['gap', 'justifyContent', 'alignItems'].includes(key)) {
continue;
}
if (typeof value === 'boolean') {
if (value) {
propStrings.push(key);
}
} else if (typeof value === 'string') {
propStrings.push(`${key}="${escapeHtml(value)}"`);
} else if (typeof value === 'number') {
propStrings.push(`${key}="${value}"`);
} else if (Array.isArray(value)) {
propStrings.push(`:${key}="[${value.map(v => JSON.stringify(v)).join(', ')}]"`);
} else if (typeof value === 'object') {
propStrings.push(`:${key}="${JSON.stringify(value)}"`);
} else {
propStrings.push(`:${key}="${value}"`);
}
}
return propStrings.length > 0 ? ` ${propStrings.join(' ')}` : '';
}
// 生成事件字符串
function generateEventsString(events: Record<string, string>): string {
const eventStrings: string[] = [];
for (const [event, handler] of Object.entries(events)) {
eventStrings.push(`@${event}="${handler}"`);
}
return eventStrings.length > 0 ? ` ${eventStrings.join(' ')}` : '';
}
// 生成插槽字符串
function generateSlotsString(slots: Record<string, string>): string {
const slotStrings: string[] = [];
for (const [slotName, slotContent] of Object.entries(slots)) {
if (slotName === 'default') {
slotStrings.push(slotContent);
} else {
slotStrings.push(`<template #${slotName}>${slotContent}</template>`);
}
}
return slotStrings.length > 0 ? ` ${slotStrings.join(' ')}` : '';
}
// 生成 Script Setup
function generateScriptSetup(schema: PageSchema): string {
const imports = generateImports(schema);
const reactiveData = generateReactiveData(schema);
const methods = generateMethods(schema);
const computed = generateComputed(schema);
return `<script setup lang="ts">
${imports}
${reactiveData}
${computed}
${methods}
</script>`;
}
// 生成导入语句
function generateImports(schema: PageSchema): string {
const componentTypes = new Set<string>();
// 收集所有使用的组件类型
function collectComponentTypes(components: ComponentSchema[]) {
for (const component of components) {
componentTypes.add(component.type);
if (component.children) {
collectComponentTypes(component.children);
}
}
}
collectComponentTypes(schema.components);
// 生成导入语句
const imports = [
"import { ref, reactive, computed, onMounted } from 'vue'",
"import { ElMessage } from 'element-plus'"
];
// 添加 Element Plus 组件导入
const elementComponents = Array.from(componentTypes).filter(type =>
type.startsWith('el-') && !['el-icon', 'el-text'].includes(type)
);
if (elementComponents.length > 0) {
imports.push(`import { ${elementComponents.join(', ')} } from 'element-plus'`);
}
return imports.join('\n');
}
// 生成响应式数据
function generateReactiveData(schema: PageSchema): string {
const dataProperties: string[] = [];
// 收集所有需要的数据属性
function collectDataProperties(components: ComponentSchema[]) {
for (const component of components) {
if (component.type === ComponentType.FORM) {
dataProperties.push('formData: {}');
dataProperties.push('formRules: {}');
} else if (component.type === ComponentType.INPUT ||
component.type === ComponentType.SELECT ||
component.type === ComponentType.INPUT_NUMBER) {
const fieldName = component.props?.model || `field_${component.id}`;
dataProperties.push(`${fieldName}: ""`);
}
if (component.children) {
collectDataProperties(component.children);
}
}
}
collectDataProperties(schema.components);
if (dataProperties.length === 0) {
return '// 响应式数据\nconst data = reactive({})';
}
return `// 响应式数据
const data = reactive({
${dataProperties.join(',\n ')}
})`;
}
// 生成计算属性
function generateComputed(schema: PageSchema): string {
return `// 计算属性
const computedValue = computed(() => {
// 在这里添加计算逻辑
return 'computed value'
})`;
}
// 生成方法
function generateMethods(schema: PageSchema): string {
const methods: string[] = [];
// 收集所有需要的方法
function collectMethods(components: ComponentSchema[]) {
for (const component of components) {
if (component.events) {
for (const [event, handler] of Object.entries(component.events)) {
if (!methods.includes(handler)) {
methods.push(handler);
}
}
}
if (component.children) {
collectMethods(component.children);
}
}
}
collectMethods(schema.components);
// 添加默认方法
methods.push('handleSubmit');
methods.push('handleReset');
const methodBodies = methods.map(method => {
switch (method) {
case 'handleSubmit':
return `function handleSubmit() {
ElMessage.success('提交成功')
}`;
case 'handleReset':
return `function handleReset() {
// 重置表单逻辑
}`;
default:
return `function ${method}() {
// ${method} 方法实现
}`;
}
});
return `// 方法
${methodBodies.join('\n\n')}
// 生命周期
onMounted(() => {
// 组件挂载后的逻辑
})`;
}
// 生成样式
function generateStyle(): string {
return `<style scoped>
/* 页面样式 */
.page-container {
padding: 20px;
}
/* 组件样式 */
.component-wrapper {
margin-bottom: 16px;
}
/* 响应式样式 */
@media (max-width: 768px) {
.page-container {
padding: 10px;
}
}
</style>`;
}
// 判断是否为自闭合标签
function isSelfClosingTag(tag: string): boolean {
const selfClosingTags = [
'el-input',
'el-input-number',
'el-icon',
'el-image',
'el-progress',
'el-skeleton',
'el-empty',
'el-backtop'
];
return selfClosingTags.includes(tag);
}
// HTML 转义
function escapeHtml(text: string): string {
const map: Record<string, string> = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, (m) => map[m]);
}
// 生成完整的 Vue 文件内容
export function generateVueFile(schema: PageSchema, filename?: string): string {
const template = generateVueTemplate(schema);
const header = `<!--
文件名: ${filename || 'generated-page.vue'}
生成时间: ${new Date().toLocaleString()}
描述: 由低代码页面生成器自动生成
-->
`;
return header + template;
}
// 导出为文件
export function exportAsFile(schema: PageSchema, filename: string = 'generated-page.vue'): void {
const content = generateVueFile(schema, filename);
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
// 复制到剪贴板
export async function copyToClipboard(schema: PageSchema): Promise<void> {
const content = generateVueFile(schema);
try {
await navigator.clipboard.writeText(content);
console.log('代码已复制到剪贴板');
} catch (err) {
console.error('复制失败:', err);
// 降级方案
const textArea = document.createElement('textarea');
textArea.value = content;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
}
}