-
-
-
-
-
-
-
-
-
-
-
-
- 搜索
-
-
-
-
-
- 重置
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 生成代码
-
-
-
-
-
-
- 重置配置
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 上级菜单
-
-
- 选择上级菜单,生成代码后会自动创建对应菜单。
-
- 注意1:生成菜单后需分配权限给角色,否则菜单将无法显示。
-
- 注意2:演示环境默认不生成菜单,如需生成,请在本地部署数据库。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ scope.row.columnName }}
-
-
-
-
-
- {{ scope.row.columnType }}
-
-
-
-
-
-
-
-
-
- {{ scope.row.fieldType }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 查询
-
-
-
-
-
-
-
-
-
-
-
- 列表
-
-
-
-
-
-
-
-
-
-
-
-
- 表单
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
- {{ data.label }}
-
-
-
-
-
-
-
-
-
-
-
- 一键复制
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ prevBtnText }}
-
-
- {{ nextBtnText }}
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/frontend/src/views/gencode/backcode/utils/schema.ts b/frontend/src/views/gencode/backcode/utils/schema.ts
new file mode 100644
index 00000000..4621fc0e
--- /dev/null
+++ b/frontend/src/views/gencode/backcode/utils/schema.ts
@@ -0,0 +1,606 @@
+/**
+ * 低代码页面生成器 - 组件 Schema 定义
+ * 定义组件的 JSON 结构和类型
+ */
+
+// 基础组件属性接口
+export interface BaseComponentProps {
+ id: string;
+ type: string;
+ label?: string;
+ children?: ComponentSchema[];
+ style?: Record
;
+ 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;
+ events?: Record;
+ slots?: Record;
+}
+
+// 页面 Schema 接口
+export interface PageSchema {
+ id: string;
+ name: string;
+ components: ComponentSchema[];
+ globalStyle?: Record;
+}
+
+// 组件配置接口
+export interface ComponentConfig {
+ type: ComponentType;
+ label: string;
+ icon: string;
+ category: string;
+ defaultProps: Record;
+ editableProps: string[];
+ canHaveChildren: boolean;
+ maxChildren?: number;
+}
+
+// 组件库配置
+export const COMPONENT_CONFIGS: Record = {
+ // 基础组件
+ [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 = {}): 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;
+}
diff --git a/frontend/src/views/gencode/backcode/utils/serializer.ts b/frontend/src/views/gencode/backcode/utils/serializer.ts
new file mode 100644
index 00000000..5c5afa1e
--- /dev/null
+++ b/frontend/src/views/gencode/backcode/utils/serializer.ts
@@ -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}
+
+
+${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 = {
+ 'el-button': '按钮',
+ 'el-link': '链接',
+ 'el-text': '文本',
+ 'el-tag': '标签',
+ 'el-alert': '', // alert 通过 title 属性显示内容
+ 'el-card': `\n${spaces}卡片内容
\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, 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 {
+ 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 {
+ const slotStrings: string[] = [];
+
+ for (const [slotName, slotContent] of Object.entries(slots)) {
+ if (slotName === 'default') {
+ slotStrings.push(slotContent);
+ } else {
+ slotStrings.push(`${slotContent}`);
+ }
+ }
+
+ 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 ``;
+}
+
+// 生成导入语句
+function generateImports(schema: PageSchema): string {
+ const componentTypes = new Set();
+
+ // 收集所有使用的组件类型
+ 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 ``;
+}
+
+// 判断是否为自闭合标签
+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 = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ };
+
+ return text.replace(/[&<>"']/g, (m) => map[m]);
+}
+
+// 生成完整的 Vue 文件内容
+export function generateVueFile(schema: PageSchema, filename?: string): string {
+ const template = generateVueTemplate(schema);
+ const header = `
+
+`;
+
+ 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 {
+ 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);
+ }
+}
diff --git a/frontend/src/views/system/role/components/PermissonDrawer.vue b/frontend/src/views/system/role/components/PermissonDrawer.vue
index a80c4405..c62bd0f8 100644
--- a/frontend/src/views/system/role/components/PermissonDrawer.vue
+++ b/frontend/src/views/system/role/components/PermissonDrawer.vue
@@ -170,7 +170,7 @@ const drawerVisible = computed({
}
})
const isExpanded = ref(true);
-const parentChildLinked = ref(true)
+const parentChildLinked = ref(false)
const loading = ref(false);
const deptTreeData = ref([]);
const menuTreeData = ref([]);
@@ -205,6 +205,9 @@ const init = async () => {
dept_ids: roleResponse.data.data.depts?.map(dept => dept.id) || []
};
+ // 根据保存的权限数据判断是否应该开启父子联动
+ parentChildLinked.value = checkParentChildLinked(permissionState.value.menu_ids, menuTreeData.value);
+
// 回显菜单树选中项
if (permTreeRef.value) {
await permTreeRef.value.setCheckedKeys(permissionState.value.menu_ids);
@@ -295,6 +298,67 @@ function handleFilter(
return data.label.includes(value);
}
+// 检查权限数据是否遵循父子联动模式
+function checkParentChildLinked(menuIds: number[], menuTreeData: permissionMenuType[]): boolean {
+ if (!menuIds.length || !menuTreeData.length) return false;
+
+ // 创建一个映射来快速查找菜单项
+ const menuMap = new Map();
+ const buildMenuMap = (menus: permissionMenuType[]) => {
+ menus.forEach(menu => {
+ menuMap.set(menu.id, menu);
+ if (menu.children) {
+ buildMenuMap(menu.children);
+ }
+ });
+ };
+ buildMenuMap(menuTreeData);
+
+ let hasParentChildConflict = false;
+
+ // 检查每个选中的菜单项
+ for (const menuId of menuIds) {
+ const menu = menuMap.get(menuId);
+ if (!menu) continue;
+
+ // 如果选中了父菜单,检查是否有子菜单未被选中
+ if (menu.children && menu.children.length > 0) {
+ const hasUnselectedChildren = menu.children.some(child => !menuIds.includes(child.id));
+ if (hasUnselectedChildren) {
+ hasParentChildConflict = true;
+ break; // 发现冲突,直接返回false
+ }
+ }
+
+ // 如果选中了子菜单,检查父菜单是否也被选中
+ const parentMenu = findParentMenu(menuId, menuTreeData);
+ if (parentMenu && !menuIds.includes(parentMenu.id)) {
+ hasParentChildConflict = true;
+ break; // 发现冲突,直接返回false
+ }
+ }
+
+ // 如果没有发现父子冲突,说明是父子联动模式
+ return !hasParentChildConflict;
+}
+
+// 查找父菜单
+function findParentMenu(menuId: number, menuTreeData: permissionMenuType[]): permissionMenuType | null {
+ for (const menu of menuTreeData) {
+ if (menu.children) {
+ for (const child of menu.children) {
+ if (child.id === menuId) {
+ return menu;
+ }
+ // 递归查找更深层的父菜单
+ const found = findParentMenu(menuId, [child]);
+ if (found) return found;
+ }
+ }
+ }
+ return null;
+}
+
// 父子菜单节点是否联动
function handleParentChildLinkedChange(val: any) {
parentChildLinked.value = val;