<?php
// +----------------------------------------------------------------------
// | 麦沃德科技赋能开发者，助力中小企业发展
// +----------------------------------------------------------------------
// | Copyright (c) 2017～2024  www.wdadmin.cn    All rights reserved.
// +----------------------------------------------------------------------
// | Wdadmin系统产品软件并不是自由软件，不加密，并不代表开源，未经许可不可自由转售和商用
// +----------------------------------------------------------------------
// | Author: MY WORLD Team <bd@maiwd.cn>   www.wdadmin.cn
// +----------------------------------------------------------------------

namespace app\adminapi\service\tool;

use think\facade\Db;
use think\facade\App;

class GeneratorService
{
    /**
     * 获取数据库表列表
     */
    public function getTableList($params)
    {
        $tables = Db::query("SHOW TABLE STATUS");
        $list = [];
        $exclude = ['migrations']; // 排除的表

        // 获取已导入的表（从数据库读取）
        $importedConfigs = Db::name('generator_config')
            ->column('id', 'table_name');
        
        $type = $params['type'] ?? 'imported'; // imported=已导入(默认), db=未导入

        foreach ($tables as $table) {
            $name = $table['Name'];
            if (in_array($name, $exclude)) continue;
            
            // 筛选
            if (!empty($params['name']) && stripos($name, $params['name']) === false && stripos($table['Comment'], $params['name']) === false) {
                continue;
            }
            
            $isImported = isset($importedConfigs[$name]);
            
            if ($type == 'imported') {
                if (!$isImported) continue;
            } else {
                if ($isImported) continue;
            }

            $list[] = [
                'id' => $isImported ? $importedConfigs[$name] : 0,
                'name' => $name,
                'comment' => $table['Comment'],
                'engine' => $table['Engine'],
                'rows' => $table['Rows'],
                'create_time' => $table['Create_time'],
                'update_time' => $table['Update_time'],
            ];
        }

        // 分页处理
        $page = intval($params['page'] ?? 1);
        $limit = intval($params['limit'] ?? 10);
        $total = count($list);
        $offset = ($page - 1) * $limit;
        $data = array_slice($list, $offset, $limit);

        return [
            'total' => $total,
            'per_page' => $limit,
            'current_page' => $page,
            'last_page' => ceil($total / $limit) ?: 1,
            'data' => $data
        ];
    }

    /**
     * 获取表字段详情
     */
    /**
     * 导入表
     */
    public function import(array $tables)
    {
        foreach ($tables as $tableName) {
             // 获取表注释
             $tableInfo = Db::query("SHOW TABLE STATUS WHERE Name = '{$tableName}'");
             $tableComment = $tableInfo[0]['Comment'] ?? '';
             
             $config = [
                 'table_name' => $tableName,
                 'class_comment' => $tableComment,
                 'fields' => [], // 空字段，getColumns会自动获取
                 'config' => ['delete_type' => 0]
             ];
             $this->saveConfig($config);
        }
        return true;
    }

    /**
     * 保存配置
     */
    public function saveConfig($params)
    {
        // 准备存储数据
        $data = [
            'class_name' => $params['class_name'] ?? '',
            'class_comment' => $params['class_comment'] ?? '',
            'module_name' => $params['module_name'] ?? '',
            'fields' => json_encode($params['fields'] ?? [], JSON_UNESCAPED_UNICODE),
            'config' => json_encode($params['config'] ?? [], JSON_UNESCAPED_UNICODE),
            'update_time' => time(),
        ];
        
        // 优先通过 id 更新
        if (!empty($params['id'])) {
            return Db::name('generator_config')->where('id', $params['id'])->update($data);
        }
        
        // 通过 table_name 新增或更新（用于导入）
        if (!empty($params['table_name'])) {
            $tableName = $params['table_name'];
            $data['table_name'] = $tableName;
            
            // 如果没有设置 class_name，自动推断
            if (empty($data['class_name'])) {
                $data['class_name'] = $this->studly(str_replace(config('database.connections.mysql.prefix'), '', $tableName));
            }
            
            $exists = Db::name('generator_config')->where('table_name', $tableName)->find();
            
            if ($exists) {
                return Db::name('generator_config')->where('table_name', $tableName)->update($data);
            } else {
                $data['create_time'] = time();
                return Db::name('generator_config')->insert($data);
            }
        }
        
        throw new \Exception('ID或表名不能为空');
    }

    /**
     * 获取表字段详情（支持通过 id 或 table_name 查询）
     */
    public function getColumns($idOrTableName)
    {
        // 判断是 id 还是 table_name
        $dbConfig = null;
        if (is_numeric($idOrTableName)) {
            // 通过 id 查询
            $dbConfig = Db::name('generator_config')->where('id', $idOrTableName)->find();
            if (!$dbConfig) {
                throw new \Exception('配置记录不存在');
            }
            $tableName = $dbConfig['table_name'];
        } else {
            // 通过 table_name 查询
            $tableName = $idOrTableName;
            $dbConfig = Db::name('generator_config')->where('table_name', $tableName)->find();
        }
        
        $columns = Db::query("SHOW FULL COLUMNS FROM {$tableName}");
        
        // 从数据库读取保存的配置
        $savedConfig = [];
        $savedFields = [];
        if ($dbConfig) {
            $savedConfig = [
                'id' => $dbConfig['id'],
                'table_name' => $dbConfig['table_name'],
                'class_name' => $dbConfig['class_name'],
                'class_comment' => $dbConfig['class_comment'],
                'module_name' => $dbConfig['module_name'],
                'config' => json_decode($dbConfig['config'], true) ?: [],
            ];
            $savedFields = json_decode($dbConfig['fields'], true) ?: [];
        }
        $savedFieldsMap = array_column($savedFields, null, 'name');

        $list = [];
        
        foreach ($columns as $col) {
            $name = $col['Field'];
            $type = $col['Type'];
            $comment = $col['Comment'] ? explode(' ', $col['Comment'])[0] : $name;
            
            // 默认配置推断
            $isPk = $col['Key'] == 'PRI';
            $isRequired = $col['Null'] == 'NO' && $col['Default'] === null && !$isPk;
            
            $formType = 'input';
            if (strpos($type, 'datetime') !== false || strpos($type, 'timestamp') !== false) {
                $formType = 'datetime';
            } elseif (strpos($type, 'date') !== false) {
                $formType = 'date';
            } elseif (strpos($type, 'time') !== false) {
                $formType = 'datetime';
            }
            if (preg_match('/(image|avatar|images|avatars)$/i', $name)) $formType = 'image';
            if (preg_match('/(file|files)$/i', $name)) $formType = 'file';
            if (preg_match('/^video$|video$/i', $name)) $formType = 'video';
            if ($name == 'content') $formType = 'editor';

            $item = [
                'name' => $name,
                'desc' => $comment,
                'type' => $type,
                'is_pk' => $isPk,
                'is_required' => $isRequired,
                'is_insert' => !$isPk && $name != 'create_time' && $name != 'update_time' && $name != 'delete_time',
                'is_edit' => !$isPk && $name != 'create_time' && $name != 'update_time' && $name != 'delete_time',
                'is_list' => $name != 'delete_time',
                'is_query' => $name == 'name' || $name == 'title' || $name == 'mobile',
                'query_type' => 'like',
                'form_type' => $formType,
                'validation_type' => '',
                'options' => '',
                'dict_type' => ''
            ];

            // 合并保存的配置
            if (isset($savedFieldsMap[$name])) {
                $sf = $savedFieldsMap[$name];
                $item['desc'] = $sf['desc'] ?? $item['desc'];
                $item['is_required'] = $sf['is_required'] ?? $item['is_required'];
                $item['is_insert'] = $sf['is_insert'] ?? $item['is_insert'];
                $item['is_edit'] = $sf['is_edit'] ?? $item['is_edit'];
                $item['is_list'] = $sf['is_list'] ?? $item['is_list'];
                $item['is_query'] = $sf['is_query'] ?? $item['is_query'];
                $item['query_type'] = $sf['query_type'] ?? $item['query_type'];
                $item['form_type'] = $sf['form_type'] ?? $item['form_type'];
                $item['validation_type'] = $sf['validation_type'] ?? '';
                $item['options'] = $sf['options'] ?? '';
            }
            
            $list[] = $item;
        }
        
        return [
            'fields' => $list,
            'info' => $savedConfig
        ];
    }

    /**
     * 预览代码（只需传 id，自动从数据库读取配置）
     */
    public function preview($params)
    {
        // 如果只传了 id，从数据库读取完整配置
        if (!empty($params['id'])) {
            $dbConfig = Db::name('generator_config')->where('id', $params['id'])->find();
            if (!$dbConfig) {
                return []; // 配置不存在，返回空
            }
            
            $tableName = $dbConfig['table_name'];
            $fields = json_decode($dbConfig['fields'], true) ?: [];
            $className = $dbConfig['class_name'];
            $classComment = $dbConfig['class_comment'] ?: '';
            
            // 如果配置不完整，根据数据表自动生成默认配置
            if (empty($className) || empty($fields)) {
                // 自动推断类名：wd_article -> Article
                $prefix = config('database.connections.mysql.prefix');
                $modelName = $tableName;
                if (!empty($prefix) && strpos($tableName, $prefix) === 0) {
                    $modelName = substr($tableName, strlen($prefix));
                }
                $className = $this->studly($modelName);
                
                // 获取表注释作为类描述
                if (empty($classComment)) {
                    $tableInfo = Db::query("SHOW TABLE STATUS WHERE Name = '{$tableName}'");
                    $classComment = $tableInfo[0]['Comment'] ?? $className;
                }
                
                // 从数据表读取字段信息
                $columnsData = $this->getColumns($params['id']);
                $fields = $columnsData['fields'] ?? [];
            }
            
            // 构建完整参数
            $params = [
                'table_name' => $tableName,
                'class_name' => $className,
                'class_comment' => $classComment,
                'module_name' => $dbConfig['module_name'] ?: '',
                'fields' => $fields,
                'config' => json_decode($dbConfig['config'], true) ?: [],
            ];
        }
        
        return $this->prepareCode($params);
    }

    /**
     * 删除生成的代码（支持批量删除，id 可以是单个或数组）
     */
    public function delete($id)
    {
        $ids = is_array($id) ? $id : [$id];
        if (empty($ids)) {
            throw new \Exception('请选择要删除的记录');
        }
        
        foreach ($ids as $configId) {
            // 获取配置信息
            $dbConfig = Db::name('generator_config')->where('id', $configId)->find();
            if (!$dbConfig) {
                continue;
            }
            
            $tableName = $dbConfig['table_name'];
            $fields = json_decode($dbConfig['fields'], true) ?: [];
            $className = $dbConfig['class_name'];
            $classComment = $dbConfig['class_comment'] ?: '';
            
            // 如果配置不完整，根据数据表自动生成默认配置
            if (empty($className) || empty($fields)) {
                $prefix = config('database.connections.mysql.prefix');
                $modelName = $tableName;
                if (!empty($prefix) && strpos($tableName, $prefix) === 0) {
                    $modelName = substr($tableName, strlen($prefix));
                }
                $className = $this->studly($modelName);
                
                if (empty($classComment)) {
                    $tableInfo = Db::query("SHOW TABLE STATUS WHERE Name = '{$tableName}'");
                    $classComment = $tableInfo[0]['Comment'] ?? $className;
                }
                
                $columnsData = $this->getColumns($configId);
                $fields = $columnsData['fields'] ?? [];
            }
            
            // 构建参数用于获取文件路径
            $params = [
                'table_name' => $tableName,
                'class_name' => $className,
                'class_comment' => $classComment,
                'module_name' => $dbConfig['module_name'] ?: '',
                'fields' => $fields,
                'config' => json_decode($dbConfig['config'], true) ?: [],
            ];
            
            // 删除生成的代码文件
            $codeData = $this->prepareCode($params);
            foreach ($codeData as $item) {
                $file = $item['path'] . $item['filename'];
                if (file_exists($file)) {
                    @unlink($file);
                }
                @rmdir($item['path']);
            }
            
            // 删除相关菜单
            $moduleName = $dbConfig['module_name'] ?: '';
            $lcClass = $this->lcfirst($this->studly($className));
            $componentPath = $moduleName ? "{$moduleName}/{$lcClass}/index" : "{$lcClass}/index";
            
            // 查找主菜单
            $menu = Db::name('system_menu')->where('component', $componentPath)->find();
            if ($menu) {
                // 删除子菜单（按钮权限）
                Db::name('system_menu')->where('pid', $menu['id'])->delete();
                // 删除主菜单
                Db::name('system_menu')->where('id', $menu['id'])->delete();
            }
            
            // 删除数据库配置记录
            Db::name('generator_config')->where('id', $configId)->delete();
        }
        
        return true;
    }

    /**
     * 生成代码（支持批量生成，id 可以是单个或数组）
     */
    public function generate($id)
    {
        $ids = is_array($id) ? $id : [$id];
        if (empty($ids)) {
            throw new \Exception('请选择要生成的记录');
        }
        
        $successCount = 0;
        foreach ($ids as $configId) {
            $dbConfig = Db::name('generator_config')->where('id', $configId)->find();
            if (!$dbConfig) {
                continue;
            }
            
            $tableName = $dbConfig['table_name'];
            $fields = json_decode($dbConfig['fields'], true) ?: [];
            $className = $dbConfig['class_name'];
            $classComment = $dbConfig['class_comment'] ?: '';
            
            // 如果配置不完整，根据数据表自动生成默认配置
            if (empty($className) || empty($fields)) {
                $prefix = config('database.connections.mysql.prefix');
                $modelName = $tableName;
                if (!empty($prefix) && strpos($tableName, $prefix) === 0) {
                    $modelName = substr($tableName, strlen($prefix));
                }
                $className = $this->studly($modelName);
                
                if (empty($classComment)) {
                    $tableInfo = Db::query("SHOW TABLE STATUS WHERE Name = '{$tableName}'");
                    $classComment = $tableInfo[0]['Comment'] ?? $className;
                }
                
                $columnsData = $this->getColumns($configId);
                $fields = $columnsData['fields'] ?? [];
            }
            
            // 构建完整参数
            $params = [
                'table_name' => $tableName,
                'class_name' => $className,
                'class_comment' => $classComment,
                'module_name' => $dbConfig['module_name'] ?: '',
                'fields' => $fields,
                'config' => json_decode($dbConfig['config'], true) ?: [],
            ];

            // 如果没有配置菜单名称，自动使用表描述作为菜单名称，如果表描述也没有，则使用表名
            if (empty($params['config']['menu_name'])) {
                $params['config']['menu_name'] = !empty($classComment) ? $classComment : $tableName;
            }
            
            $codeData = $this->prepareCode($params);
            foreach ($codeData as $item) {
                $this->writeFile($item['path'], $item['filename'], $item['content']);
            }
            
            // 生成菜单
            $this->generateMenu($params);
            
            $successCount++;
        }
        
        return $successCount;
    }

    /**
     * 生成菜单
     */
    private function generateMenu($params)
    {
        $config = $params['config'] ?? [];
        $menuPid = $config['menu_pid'] ?? 0;
        $menuName = $config['menu_name'] ?? '';
        
        // 如果没有配置菜单名称，不生成菜单
        if (empty($menuName)) {
            return;
        }
        
        $tableName = $params['table_name'];
        $prefix = config('database.connections.mysql.prefix');
        $modelName = $tableName;
        if (!empty($prefix) && strpos($tableName, $prefix) === 0) {
            $modelName = substr($tableName, strlen($prefix));
        }
        
        $className = $this->studly($params['class_name']);
        $moduleName = !empty($params['module_name']) ? strtolower($params['module_name']) : '';
        $lcClass = $this->lcfirst($className);
        
        // 路由路径和组件路径
        $routePath = $moduleName ? "{$moduleName}/{$lcClass}" : $lcClass;
        $componentPath = $moduleName ? "{$moduleName}/{$lcClass}/index" : "{$lcClass}/index";
        $perms = "{$lcClass}/lists";
        
        // 检查菜单是否已存在（通过 component 判断）
        $exists = Db::name('system_menu')
            ->where('component', $componentPath)
            ->find();
        
        if ($exists) {
            // 菜单已存在，不重复添加
            return;
        }
        
        // 添加主菜单
        $menuData = [
            'pid' => $menuPid,
            'type' => 2, // 菜单类型
            'name' => $menuName,
            'icon' => '',
            'icon_type' => 0,
            'sort' => 100,
            'perms' => $perms,
            'paths' => $routePath,
            'component' => $componentPath,
            'selected' => '',
            'params' => '',
            'is_cache' => 0,
            'is_show' => 1,
            'is_disable' => 0,
            'create_time' => time(),
            'update_time' => time(),
        ];
        
        $menuId = Db::name('system_menu')->insertGetId($menuData);
        
        // 添加按钮权限（detail, add, edit, delete, export）
        $buttons = [
            ['name' => '详情', 'perms' => "{$lcClass}/detail"],
            ['name' => '新增', 'perms' => "{$lcClass}/add"],
            ['name' => '编辑', 'perms' => "{$lcClass}/edit"],
            ['name' => '删除', 'perms' => "{$lcClass}/delete"],
            ['name' => '导出', 'perms' => "{$lcClass}/export"],
        ];
        
        $sort = 1;
        foreach ($buttons as $btn) {
            Db::name('system_menu')->insert([
                'pid' => $menuId,
                'type' => 3, // 按钮类型
                'name' => $btn['name'],
                'icon' => '',
                'icon_type' => 0,
                'sort' => $sort++,
                'perms' => $btn['perms'],
                'paths' => '',
                'component' => '',
                'selected' => '',
                'params' => '',
                'is_cache' => 0,
                'is_show' => 1,
                'is_disable' => 0,
                'create_time' => time(),
                'update_time' => time(),
            ]);
        }
    }

    /**
     * 准备代码数据
     */
    private function prepareCode($params)
    {
        $tableName = $params['table_name'];
        // 移除表前缀
        $prefix = config('database.connections.mysql.prefix');
        $modelName = $tableName;
        if (!empty($prefix) && strpos($tableName, $prefix) === 0) {
            $modelName = substr($tableName, strlen($prefix));
        }
        // 类名：强制转为大驼峰
        $className = $this->studly($params['class_name']);
        // 模块名：如果为空，则为空字符串，用于 namespace
        $moduleName = !empty($params['module_name']) ? strtolower($params['module_name']) : '';
        
        $classComment = $params['class_comment'];
        $fields = $params['fields'];
        $config = $params['config'] ?? [];
        // DEBUG LOG
        file_put_contents(root_path() . 'runtime/gen_debug.log', "Config: " . json_encode($config, JSON_UNESCAPED_UNICODE) . PHP_EOL, FILE_APPEND);

        // 主键
        $pk = 'id';
        foreach ($fields as $field) {
            if ($field['is_pk']) {
                $pk = $field['name'];
                break;
            }
        }

        // 关联关系
        $relations = $config['relations'] ?? [];
        $relationMethods = '';
        
        foreach ($relations as $relation) {
            if (empty($relation['name']) || empty($relation['model']) || empty($relation['type'])) continue;
            
            $relModel = $relation['model'];
            $relModelClass = '\\' . ltrim($relModel, '\\');
            
            // 获取外键和主键
            $foreignKey = $relation['foreign_key'] ?? '';
            $localKey = $relation['local_key'] ?? '';
            
            // 根据关联类型构建参数
            // belongsTo(model, foreignKey, ownerKey) - 外键在当前表，关联到对方表的主键
            // hasOne(model, foreignKey, localKey) - 外键在对方表，关联到当前表的主键
            // hasMany(model, foreignKey, localKey) - 同 hasOne
            
            $params = "{$relModelClass}::class";
            
            // 针对 belongsTo，如果是反的，说明配置中 foreign_key 存的是对方主键(id)，local_key 存的是当前外键(category_id)
            // 或者仅仅是生成逻辑需要调整。根据用户反馈进行调整：交换顺序。
            if ($relation['type'] === 'belongsTo') {
                 // 对于 belongsTo，实际上我们想要的是 (当前外键, 关联主键)
                 // 用户反馈生成的是 ('id', 'category_id') -> 这里 id 在前， category_id 在后
                 // 说明 foreign_key=id, local_key=category_id
                 // 我们需要 ('category_id', 'id')，即 (local_key, foreign_key)
                 if (!empty($localKey)) {
                     $params .= ", '{$localKey}'";
                     if (!empty($foreignKey)) {
                         $params .= ", '{$foreignKey}'";
                     }
                 } elseif (!empty($foreignKey)) {
                     // 只有 foreignKey (id) 的情况，可能比较少见，保持原样或根据实际调整，这里假设通常都有
                     $params .= ", '{$foreignKey}'";
                 }
            } else {
                // hasOne / hasMany 保持原样 (foreignKey, localKey)
                if (!empty($foreignKey)) {
                    $params .= ", '{$foreignKey}'";
                    if (!empty($localKey)) {
                        $params .= ", '{$localKey}'";
                    }
                }
            }
            
            $methodBody = "return \$this->{$relation['type']}({$params});";
            
            $relationMethods .= "    public function {$relation['name']}()\n";
            $relationMethods .= "    {\n";
            $relationMethods .= "        {$methodBody}\n";
            $relationMethods .= "    }\n\n";
        }
        
        // 自动从远程下拉配置中推断并生成 belongsTo 关联
        // 收集已存在的关联名称，避免重复
        $existingRelationNames = [];
        foreach ($relations as $relation) {
            if (!empty($relation['name'])) {
                $existingRelationNames[] = $relation['name'];
            }
        }
        
        foreach ($fields as $field) {
            if (in_array($field['form_type'], ['select', 'radio', 'mulselect']) && !empty($field['options'])) {
                $jsonOptions = json_decode($field['options'], true);
                if (json_last_error() === JSON_ERROR_NONE && isset($jsonOptions['type']) && $jsonOptions['type'] === 'remote') {
                    // 推断关联名称：category_id -> category
                    $guessRelationName = preg_replace('/_id$/', '', $field['name']);
                    $guessRelationName = lcfirst($this->studly($guessRelationName));
                    
                    // 如果关联名已存在，跳过
                    if (in_array($guessRelationName, $existingRelationNames)) {
                        continue;
                    }
                    
                    // 获取关联模型
                    $relModel = $jsonOptions['model'] ?? '';
                    if (empty($relModel)) continue;
                    
                    $relModelClass = '\\' . ltrim($relModel, '\\');
                    $foreignKey = $field['name']; // 当前表的外键字段（如 category_id）
                    $ownerKey = $jsonOptions['value_field'] ?? 'id'; // 关联表的主键
                    
                    // 生成 belongsTo 关联方法
                    $methodBody = "return \$this->belongsTo({$relModelClass}::class, '{$foreignKey}', '{$ownerKey}');";
                    
                    $relationMethods .= "    public function {$guessRelationName}()\n";
                    $relationMethods .= "    {\n";
                    $relationMethods .= "        {$methodBody}\n";
                    $relationMethods .= "    }\n\n";
                    
                    $existingRelationNames[] = $guessRelationName;
                }
            }
        }
        
        $data = [
            'table_name' => $modelName,  // 使用去除前缀后的表名
            'class_name' => $className,
            'module_name' => $moduleName,
            'class_comment' => $classComment,
            'pk' => $pk,
            'fields' => $fields,
            'config' => $config,
            'relations' => $relationMethods, // 传递给模板
            'sub_dir' => strtolower($config['sub_dir'] ?? ''), // 子目录
        ];

        // 模板路径
        $tplPath = root_path() . 'server/app/adminapi/view/generator/';
        if (!is_dir($tplPath)) {
             // 兼容旧路径或自定义路径，这里假设存在
             // 实际上我们要看 makeModel 里怎么用的
        }

        $codeList = [];

        // 1. 生成模型
        $codeList[] = $this->makeModel($data);

        // 2. 生成验证器
        $codeList[] = $this->makeValidate($data);

        // 3. 生成服务层
        $codeList[] = $this->makeService($data);

        // 4. 生成控制器
        $codeList[] = $this->makeController($data);
        
        // 5. 生成前端 API
        $codeList[] = $this->makeFrontendApi($data);

        // 6. 生成前端 Vue
        // $vueFiles = $this->makeFrontendVue($moduleName, $className, $fields, $classComment, $pk, $config);
        // $codeList = array_merge($codeList, $vueFiles);
        $codeList[] = $this->makeFrontendIndex($data);
        $codeList[] = $this->makeFrontendOperate($data);
        $codeList[] = $this->makeSql($data);

        return $codeList;
    }
    
    /**
     * 获取系统模型列表
     */
    public function getModels()
    {
        $models = [];
        $paths = [
            root_path() . 'app/common/model',
            root_path() . 'app/adminapi/model',
        ];

        foreach ($paths as $path) {
            if (!is_dir($path)) continue;
            $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
            foreach ($files as $file) {
                if ($file->isFile() && $file->getExtension() == 'php') {
                    $content = file_get_contents($file->getRealPath());
                    if (preg_match('/namespace\s+(.+?);/', $content, $matches)) {
                        $namespace = $matches[1];
                        if (preg_match('/class\s+(\w+)/', $content, $matches2)) {
                            $className = $matches2[1];
                            $fullClassName = $namespace . '\\' . $className;
                            
                            $tableName = '';
                            try {
                                if (class_exists($fullClassName)) {
                                    $reflection = new \ReflectionClass($fullClassName);
                                    if (!$reflection->isAbstract()) {
                                        $instance = new $fullClassName;
                                        // 简单检查是否继承自 Model
                                        if ($instance instanceof \think\Model) {
                                            $tableName = $instance->getTable();
                                        }
                                    }
                                }
                            } catch (\Throwable $e) {
                                // 忽略无法实例化的类
                            }

                            $models[] = [
                                'name' => $className,
                                'value' => $fullClassName,
                                'namespace' => $namespace,
                                'table_name' => $tableName
                            ];
                        }
                    }
                }
            }
        }
        return $models;
    }

    /**
     * 获取菜单树（用于选择父级菜单）
     */
    public function getMenuTree()
    {
        $menus = Db::name('system_menu')
            ->field('id, pid, name, type')
            ->where('is_disable', 0)
            ->where('type', 'in', [1, 2]) // 只获取目录和菜单，不要按钮
            ->order('sort asc, id asc')
            ->select()
            ->toArray();
        
        return tree($menus, 'children', 'id', 'pid', 0);
    }

    /**
     * 生成模型
     */
    private function makeModel($data)
    {
        // module_name 作为子目录（如 article），为空则不加子目录
        $subDir = $data['module_name'] ?? '';
        $namespaceSuffix = $subDir ? '\\' . $subDir : '';
        
        $template = <<<EOT
<?php
namespace app\adminapi\model{{sub_namespace}};

use core\base\BaseAdminModel;
{{use_soft_delete}}

/**
 * {{class_comment}}
 * Class {{class_name}}
 * @package app\adminapi\model{{sub_namespace}}
 */
class {{class_name}} extends BaseAdminModel
{
{{soft_delete_trait}}

    // 表名
    protected \$name = '{{table_name}}';
    
    // 主键
    protected \$pk = '{{pk}}';
    
    // 自动时间戳
    protected \$autoWriteTimestamp = true;

{{delete_time_property}}

{{relations}}
{{json_fields}}
}
EOT;

        // 处理JSON字段
        $jsonPropertys = "";
        $jsonFields = [];
        foreach ($data['fields'] as $field) {
             // 复选框、图片组、多选下拉 等需要存为数组
            // 判断是否为多图字段：form_type为image 且 名字以此结尾
            $isMultiImage = $field['form_type'] === 'image' && preg_match('/(images|avatars)$/i', $field['name']);
            // 判断是否为多文件字段：form_type为file 且 名字以此结尾
            $isMultiFile = $field['form_type'] === 'file' && preg_match('/(files)$/i', $field['name']);
            
            if (in_array($field['form_type'], ['checkbox', 'mulselect']) || $isMultiImage || $isMultiFile) {
                $jsonFields[] = $field['name'];
            }
        }
        
        if (!empty($jsonFields)) {
            $jsonStr = "'" . implode("', '", $jsonFields) . "'";
            $jsonPropertys .= "    // JSON字段\n";
            $jsonPropertys .= "    protected \$json = [$jsonStr];\n";
            $jsonPropertys .= "    protected \$type = [\n";
            foreach($jsonFields as $f) {
                $jsonPropertys .= "        '$f' => 'json',\n";
            }
            $jsonPropertys .= "    ];\n";
        }
        
        // Generate Setters/Getters for JSON fields (to compat with existing Test.php style if needed, or rely on TP)
        // Since user saw setters in Test.php, let's generate them to be safe/consistent
        $setters = "";
        foreach ($jsonFields as $f) {
            $studlyName = $this->studly($f);
            $setters .= "\n    public function set{$studlyName}Attr(\$value)\n";
            $setters .= "    {\n";
            $setters .= "        return is_array(\$value) ? json_encode(\$value, JSON_UNESCAPED_UNICODE) : \$value;\n";
            $setters .= "    }\n";
            
            $setters .= "\n    public function get{$studlyName}Attr(\$value)\n";
            $setters .= "    {\n";
            $setters .= "        if (is_array(\$value)) return array_values(array_filter(\$value, fn(\$v) => \$v !== '' && \$v !== null));\n";
            $setters .= "        if (empty(\$value)) return [];\n";
            $setters .= "        \$arr = json_decode(\$value, true) ?: [];\n";
            $setters .= "        return array_values(array_filter(\$arr, fn(\$v) => \$v !== '' && \$v !== null));\n";
            $setters .= "    }\n";
        }

        // 当模块名为空时不需要此变量了

        // 处理软删除配置
        // 处理软删除配置
        $deleteType = $data['config']['delete_type'] ?? 0; // 0:物理删除, 1:软删除
        
        $useSoftDelete = "";
        $softDeleteTrait = "";
        $deleteTimeProperty = "";
        
        if ($deleteType == 1) {
            $useSoftDelete = "use think\\model\\concern\\SoftDelete;";
            $softDeleteTrait = "    use SoftDelete;";
            $deleteTimeProperty = "    // 软删除\n    protected \$deleteTime = 'delete_time';";
        }

        $content = str_replace(
            ['{{sub_namespace}}', '{{class_comment}}', '{{class_name}}', '{{table_name}}', '{{pk}}', '{{relations}}', '{{json_fields}}', '{{use_soft_delete}}', '{{soft_delete_trait}}', '{{delete_time_property}}'],
            [$namespaceSuffix, $data['class_comment'], $data['class_name'], $data['table_name'], $data['pk'], $data['relations'], $jsonPropertys . $setters, $useSoftDelete, $softDeleteTrait, $deleteTimeProperty],
            $template
        );
        
        // 生成路径：app/adminapi/model/{subDir}/
        $subDirPath = $subDir ? $subDir . '/' : '';
        
        return [
            'path' => root_path() . 'app/adminapi/model/' . $subDirPath,
            'filename' => $data['class_name'] . '.php',
            'content' => $content,
            'type' => 'model',
            'desc' => '模型类'
        ];
    }

    private function makeValidate($data)
    {
        $module = $data['module_name'];
        $class = $data['class_name'];
        $fields = $data['fields'];
        $comment = $data['class_comment'];
        $rules = [];
        $messages = [];
        $sceneAdd = [];
        $sceneEdit = [];

        foreach ($fields as $field) {
            $fieldName = $field['name'];
            $desc = $field['desc'];
            $ruleParts = [];

            if ($field['is_required']) {
                $ruleParts[] = 'require';
            }
            if (!empty($field['validation_type'])) {
                $ruleParts[] = $field['validation_type'];
            }

            if (empty($ruleParts)) continue;

            $ruleStr = implode('|', $ruleParts);
            $rules[] = "'{$fieldName}' => '{$ruleStr}'";
            
            if ($field['is_required']) {
                 $messages[] = "'{$fieldName}.require' => '{$desc}不能为空'";
            }
            // Add messages for other validation types if needed
            
            if ($field['is_insert']) $sceneAdd[] = "'{$fieldName}'";
            if ($field['is_edit']) $sceneEdit[] = "'{$fieldName}'";
        }
        $sceneEdit[] = "'id'";
        
        $sceneLists = [];
        $removeCalls = "";
        foreach ($fields as $field) {
            if ($field['is_query']) {
                 $sceneLists[] = "'{$field['name']}'";
                 // 列表查询时，移除必填和唯一性校验，确保搜索灵活性
                 $removeCalls .= "\n            ->remove('{$field['name']}', 'require|unique')";
            }
        }
        $sceneLists[] = "'page'";
        $sceneLists[] = "'limit'";
        
        $listsStr = implode(',', $sceneLists);

        $rulesStr = implode(",\n        ", $rules);
        $msgsStr = implode(",\n        ", $messages);

        // 生成字段描述映射
        $fieldDescArr = [];
        foreach ($fields as $field) {
            $fieldDescArr[] = "'{$field['name']}' => '{$field['desc']}'";
        }
        $fieldDescStr = implode(",\n        ", $fieldDescArr);

        $addStr = implode(',', $sceneAdd);
        $editStr = implode(',', $sceneEdit);

        $namespace = "app\\adminapi\\validate";
        $path = root_path() . 'app/adminapi/validate/';
        
        // 使用 module_name 作为子目录
        $subDir = $data['module_name'] ?? '';
        if (!empty($subDir)) {
            $namespace .= "\\{$subDir}";
            $path .= $subDir . '/';
        }

        $content = <<<PHP
<?php

namespace {$namespace};

use core\base\BaseValidate;

class {$class}Validate extends BaseValidate
{
    protected \$rule = [
        {$rulesStr}
    ];

    protected \$message = [
        {$msgsStr}
    ];

    protected \$field = [
        {$fieldDescStr}
    ];

    public function sceneAdd()
    {
        return \$this->only([{$addStr}]);
    }

    public function sceneEdit()
    {
        return \$this->only([{$editStr}]);
    }

    public function sceneLists()
    {
        return \$this->only([{$listsStr}]){$removeCalls};
    }
}
PHP;
         return ['path' => $path, 'filename' => $class . 'Validate.php', 'content' => $content, 'type' => 'validate', 'desc' => '验证器'];
    }

    private function makeService($data)
    {
        $module = $data['module_name'];
        $class = $data['class_name'];
        $pk = $data['pk'];
        $fields = $data['fields'];
        $config = $data['config'];
        $searchLogic = "";
        $namespace = "app\\adminapi\\service";
        $modelNamespace = "app\\adminapi\\model";
        $path = root_path() . 'app/adminapi/service/';
        
        foreach ($fields as $field) {
            if ($field['is_query']) {
                $fieldName = $field['name'];
                $op = $field['query_type'];
                $ft = $field['form_type']; // 获取表单类型

                // 针对日期类型的特殊处理
                if (in_array($ft, ['date', 'datetime'])) {
                    if (strtoupper($op) === 'BETWEEN') {
                        // 日期范围查询
                        $searchLogic .= "
        if (!empty(\$where['{$fieldName}']) && is_array(\$where['{$fieldName}']) && count(\$where['{$fieldName}']) == 2) {
            \$map[] = ['{$fieldName}', '>=', \$where['{$fieldName}'][0]];
            \$map[] = ['{$fieldName}', '<=', \$where['{$fieldName}'][1]];
        }";
                    } else {
                        // 单日期精确查询
                        $searchLogic .= "
        if (!empty(\$where['{$fieldName}'])) {
            \$map[] = ['{$fieldName}', '=', \$where['{$fieldName}']];
        }";
                    }
                } elseif ($op == 'like') {
                     $searchLogic .= "
        if (!empty(\$where['{$fieldName}'])) {
            \$map[] = ['{$fieldName}', 'like', '%' . \$where['{$fieldName}'] . '%'];
        }";
                } else if ($op) {
                     $searchLogic .= "
        if (!empty(\$where['{$fieldName}'])) {
            \$map[] = ['{$fieldName}', '{$op}', \$where['{$fieldName}']];
        }";
                }
            }
        }

        // 使用 module_name 作为子目录
        $subDir = $data['module_name'] ?? '';
        if (!empty($subDir)) {
             $namespace .= "\\{$subDir}";
             $modelNamespace .= "\\{$subDir}";
             $path .= $subDir . '/';
        }

        // 生成关联预加载代码
        $relations = $config['relations'] ?? [];
        $withRelations = [];
        foreach ($relations as $relation) {
            if (!empty($relation['name'])) {
                $withRelations[] = "'{$relation['name']}'";
            }
        }
        
        // 自动检测远程下拉字段，推断关联名称并添加到 with 列表
        foreach ($fields as $field) {
            if (in_array($field['form_type'], ['select', 'radio', 'mulselect']) && !empty($field['options'])) {
                $jsonOptions = json_decode($field['options'], true);
                if (json_last_error() === JSON_ERROR_NONE && isset($jsonOptions['type']) && $jsonOptions['type'] === 'remote') {
                    // 推断关联名称：将 category_id 转换为 category
                    $guessRelationName = preg_replace('/_id$/', '', $field['name']);
                    $guessRelationName = lcfirst($this->studly($guessRelationName));
                    // 避免重复添加
                    $quoteGuessName = "'{$guessRelationName}'";
                    if (!in_array($quoteGuessName, $withRelations)) {
                        $withRelations[] = $quoteGuessName;
                    }
                }
            }
        }
        
        $withCode = "";
        if (!empty($withRelations)) {
            $withCode = "->with([" . implode(', ', $withRelations) . "])";
        }

        $content = <<<PHP
<?php

namespace {$namespace};

use core\\base\\BaseService;
use {$modelNamespace}\\{$class};

class {$class}Service extends BaseService
{
    public function __construct()
    {
        parent::__construct();
        \$this->model = new {$class}();
    }

    /**
     * 获取列表
     */
    public function getPage(array \$where = [])
    {
        \$map = [];{$searchLogic}
        \$query = \$this->model{$withCode}->where(\$map)->order('{$pk} desc');
        return \$this->pageQuery(\$query);
    }

    /**
     * 添加
     */
    public function add(\$params)
    {
        return \$this->model->save(\$params);
    }

    /**
     * 编辑
     */
    public function edit(\$id, \$params)
    {
        \$row = \$this->model->find(\$id);
        return \$row ? \$row->save(\$params) : false;
    }

    /**
     * 删除
     */
    public function delete(\$id)
    {
        return \$this->model->destroy(\$id);
    }

    /**
     * 详情
     */
    public function detail(\$id)
    {
        return \$this->model{$withCode}->find(\$id);
    }

    /**
     * 导出
     */
    public function export(\$where)
    {
        if (isset(\$where['ids']) && !empty(\$where['ids'])) {
            return \$this->model{$withCode}->where('{$pk}', 'in', \$where['ids'])->order('{$pk} desc')->select();
        }
        \$map = [];{$searchLogic}
        return \$this->model{$withCode}->where(\$map)->order('{$pk} desc')->select();
    }
}
PHP;
        return ['path' => $path, 'filename' => $class . 'Service.php', 'content' => $content, 'type' => 'service', 'desc' => '服务类'];
    }

    private function makeController($data)
    {
        $module = $data['module_name'];
        $class = $data['class_name'];
        $comment = $data['class_comment'];
        $namespace = "app\\adminapi\\controller";
        $serviceNamespace = "app\\adminapi\\service";
        $validateNamespace = "app\\adminapi\\validate";
        $path = root_path() . 'app/adminapi/controller/';
        $routePrefix = "";

        // 使用 module_name 作为子目录
        $subDir = $data['module_name'] ?? '';
        if (!empty($subDir)) {
            $namespace .= "\\{$subDir}";
            $serviceNamespace .= "\\{$subDir}";
            $validateNamespace .= "\\{$subDir}";
            $path .= $subDir . '/';
            $routePrefix = "{$subDir}." . lcfirst($class);
        } else {
            $routePrefix = lcfirst($class);
        }

        $serviceClass = "{$serviceNamespace}\\{$class}Service";
        $validateClass = "{$validateNamespace}\\{$class}Validate";
        
        $content = <<<PHP
<?php

namespace {$namespace};

use core\base\BaseAdminController;
use {$serviceClass};
use {$validateClass};

/**
 * @OA\Tag(
 *     name="{$comment}管理",
 *     description="{$comment}相关接口"
 * )
 */
class {$class}Controller extends BaseAdminController
{
    /**
     * @OA\Get(
     *     path="/{$routePrefix}/lists",
     *     summary="{$comment}列表",
     *     tags={"{$comment}管理"},
     *     @OA\Response(response=200, description="成功")
     * )
     */
    public function lists()
    {
        \$data = (new {$class}Validate())->get()->goCheck('lists');
        \$list = (new {$class}Service())->getPage(\$data);
        return success('请求成功', \$list);
    }

    /**
     * @OA\Get(
     *     path="/{$routePrefix}/detail",
     *     summary="{$comment}详情",
     *     tags={"{$comment}管理"},
     *     @OA\Response(response=200, description="成功")
     * )
     */
    public function detail()
    {
        \$id = \$this->request->get('id');
        \$data = (new {$class}Service())->detail(\$id);
        return success('请求成功', \$data);
    }

    /**
     * @OA\Post(
     *     path="/{$routePrefix}/add",
     *     summary="{$comment}添加",
     *     tags={"{$comment}管理"},
     *     @OA\Response(response=200, description="成功")
     * )
     */
    public function add()
    {
        \$params = (new {$class}Validate())->post()->goCheck('add');
        (new {$class}Service())->add(\$params);
        return success('添加成功');
    }

    /**
     * @OA\Post(
     *     path="/{$routePrefix}/edit",
     *     summary="{$comment}编辑",
     *     tags={"{$comment}管理"},
     *     @OA\Response(response=200, description="成功")
     * )
     */
    public function edit()
    {
        \$params = (new {$class}Validate())->post()->goCheck('edit');
        (new {$class}Service())->edit(\$params['id'], \$params);
        return success('编辑成功');
    }

    /**
     * @OA\Post(
     *     path="/{$routePrefix}/delete",
     *     summary="{$comment}删除",
     *     tags={"{$comment}管理"},
     *     @OA\Response(response=200, description="成功")
     * )
     */
    public function delete()
    {
        \$params = \$this->request->post();
        (new {$class}Service())->delete(\$params['id']);
        return success('删除成功');
    }

    /**
     * @OA\Get(
     *     path="/{$routePrefix}/export",
     *     summary="{$comment}导出",
     *     tags={"{$comment}管理"},
     *     @OA\Response(response=200, description="成功")
     * )
     */
    public function export()
    {
        \$params = \$this->request->get();
        \$list = (new {$class}Service())->export(\$params);
        return success('请求成功', \$list);
    }
}
PHP;
        return ['path' => $path, 'filename' => $class . 'Controller.php', 'content' => $content, 'type' => 'controller', 'desc' => '控制器'];
    }

    private function makeFrontendApi($data)
    {
        $module = $data['module_name'];
        $class = $data['class_name'];
        $lcClass = lcfirst($class);
        
        if (!empty($module)) {
             $routePrefix = "{$module}.{$lcClass}";
             $frontPath = dirname(root_path()) . '/admin/src/api/' . $module . '/';
        } else {
             $routePrefix = "{$lcClass}";
             $frontPath = dirname(root_path()) . '/admin/src/api/';
        }
        
        $content = <<<TS
import request from '@/utils/request'

// 列表
export function {$lcClass}Lists(params: any) {
  return request.get({ url: '/{$routePrefix}/lists', params })
}

// 添加
export function {$lcClass}Add(params: any) {
  return request.post({ url: '/{$routePrefix}/add', params })
}

// 编辑
export function {$lcClass}Edit(params: any) {
  return request.post({ url: '/{$routePrefix}/edit', params })
}

// 删除
export function {$lcClass}Delete(params: any) {
  return request.post({ url: '/{$routePrefix}/delete', params })
}

// 详情
export function {$lcClass}Detail(params: any) {
  return request.get({ url: '/{$routePrefix}/detail', params })
}

// 导出
export function {$lcClass}Export(params: any) {
  return request.get({ url: '/{$routePrefix}/export', params })
}
TS;
        return ['path' => $frontPath, 'filename' => $lcClass . '.ts', 'content' => $content, 'type' => 'api', 'desc' => '前端API'];
    }

    private function makeFrontendIndex($data)
    {
        $module = $data['module_name'];
        $class = $data['class_name'];
        $fields = $data['fields'];
        $pk = $data['pk'];
        $config = $data['config'];
        $lcClass = lcfirst($class);
        $ucClass = ucfirst($class);
        $componentName = $ucClass;
        if (!empty($module)) {
            $componentName = $this->studly($module) . $ucClass;
        }
        
        $apiPath = "";
        $routePrefix = "";
        if (!empty($module)) {
            $frontPath = dirname(root_path()) . '/admin/src/views/' . $module . '/' . $lcClass . '/';
            $apiPath = "@/api/{$module}/{$lcClass}";
            $routePrefix = "{$module}.{$lcClass}";
        } else {
            $frontPath = dirname(root_path()) . '/admin/src/views/' . $lcClass . '/';
            $apiPath = "@/api/{$lcClass}";
            $routePrefix = "{$lcClass}";
        }
        
        // 1. Index.vue - 搜索区
        $searchFormItems = "";
        $queryParamsInitArr = [];
        $remoteSelects = [];
        $hasSearchFields = false;
        
        foreach ($fields as $field) {
            $name = $field['name'];
            $desc = $field['desc'];
            $ft = $field['form_type'];
            
            // 解析配置
            $isRemote = false;
            $remoteConfig = [];
            if (in_array($ft, ['select', 'radio', 'checkbox', 'mulselect']) && !empty($field['options'])) {
                $jsonOptions = json_decode($field['options'], true);
                if (json_last_error() === JSON_ERROR_NONE && isset($jsonOptions['type']) && $jsonOptions['type'] === 'remote') {
                    $isRemote = true;
                    $remoteConfig = $jsonOptions;
                    $remoteSelects[$name] = $jsonOptions;
                }
            }

            if ($field['is_query']) {
                $hasSearchFields = true;
                $queryType = $field['query_type'] ?? '=';
                
                // 日期范围类型需要初始化为数组
                if (in_array($ft, ['date', 'datetime']) && strtoupper($queryType) === 'BETWEEN') {
                    $queryParamsInitArr[] = "  {$name}: []";
                } else {
                    $queryParamsInitArr[] = "  {$name}: ''";
                }

                $searchFormItems .= "        <el-form-item class=\"w-[280px]\" label=\"{$desc}\" prop=\"{$name}\">\n";
                
                if ($ft == 'date') {
                    if (strtoupper($queryType) === 'BETWEEN') {
                        // 日期范围选择器
                        $searchFormItems .= "          <el-date-picker\n";
                        $searchFormItems .= "            v-model=\"queryParams.{$name}\"\n";
                        $searchFormItems .= "            type=\"daterange\"\n";
                        $searchFormItems .= "            value-format=\"YYYY-MM-DD\"\n";
                        $searchFormItems .= "            start-placeholder=\"开始日期\"\n";
                        $searchFormItems .= "            end-placeholder=\"结束日期\"\n";
                        $searchFormItems .= "            clearable\n";
                        $searchFormItems .= "          />\n";
                    } else {
                        // 单日期选择器
                        $searchFormItems .= "          <el-date-picker\n";
                        $searchFormItems .= "            v-model=\"queryParams.{$name}\"\n";
                        $searchFormItems .= "            type=\"date\"\n";
                        $searchFormItems .= "            value-format=\"YYYY-MM-DD\"\n";
                        $searchFormItems .= "            placeholder=\"请选择{$desc}\"\n";
                        $searchFormItems .= "            clearable\n";
                        $searchFormItems .= "          />\n";
                    }
                } elseif ($ft == 'datetime') {
                    if (strtoupper($queryType) === 'BETWEEN') {
                        // 日期时间范围选择器
                        $searchFormItems .= "          <el-date-picker\n";
                        $searchFormItems .= "            v-model=\"queryParams.{$name}\"\n";
                        $searchFormItems .= "            type=\"datetimerange\"\n";
                        $searchFormItems .= "            value-format=\"YYYY-MM-DD HH:mm:ss\"\n";
                        $searchFormItems .= "            start-placeholder=\"开始时间\"\n";
                        $searchFormItems .= "            end-placeholder=\"结束时间\"\n";
                        $searchFormItems .= "            clearable\n";
                        $searchFormItems .= "          />\n";
                    } else {
                        // 单日期时间选择器
                        $searchFormItems .= "          <el-date-picker\n";
                        $searchFormItems .= "            v-model=\"queryParams.{$name}\"\n";
                        $searchFormItems .= "            type=\"datetime\"\n";
                        $searchFormItems .= "            value-format=\"YYYY-MM-DD HH:mm:ss\"\n";
                        $searchFormItems .= "            placeholder=\"请选择{$desc}\"\n";
                        $searchFormItems .= "            clearable\n";
                        $searchFormItems .= "          />\n";
                    }
                } elseif (in_array($ft, ['select', 'radio', 'checkbox', 'mulselect'])) {
                    if ($isRemote) {
                        $labelField = $remoteConfig['label_field'] ?? 'name';
                        $valueField = $remoteConfig['value_field'] ?? 'id';
                        $camelName = lcfirst($this->studly($name));
                        $searchFormItems .= "          <el-select v-model=\"queryParams.{$name}\" placeholder=\"请选择{$desc}\" clearable>\n";
                        $searchFormItems .= "            <el-option v-for=\"item in {$camelName}List\" :key=\"item.{$valueField}\" :label=\"item.{$labelField}\" :value=\"item.{$valueField}\" />\n";
                        $searchFormItems .= "          </el-select>\n";
                    } else {
                        $camelName = lcfirst($this->studly($name));
                        $searchFormItems .= "          <el-select v-model=\"queryParams.{$name}\" placeholder=\"请选择{$desc}\" clearable>\n";
                        $searchFormItems .= "            <el-option v-for=\"(item, key) in {$camelName}Dict\" :key=\"key\" :label=\"item\" :value=\"key\" />\n";
                        $searchFormItems .= "          </el-select>\n";
                    }
                } else {
                    $searchFormItems .= "          <el-input v-model=\"queryParams.{$name}\" placeholder=\"请输入{$desc}\" clearable @keyup.enter=\"handleQuery\" />\n";
                }
                
                $searchFormItems .= "        </el-form-item>\n";
            }
        }

        // 2. Index.vue - 表格列
        $tableCols = "";
        $dictsCode = "";
        $columnsInit = [];
        $hasVideo = false;

        foreach ($fields as $field) {
            $name = $field['name'];
            $desc = $field['desc'];
            $ft = $field['form_type'];

            // 处理下拉框 options
            $isRemote = false;
            $remoteConfig = [];
            
            if (in_array($ft, ['select', 'radio', 'checkbox', 'mulselect']) && !empty($field['options'])) {
                $jsonOptions = json_decode($field['options'], true);
                if (json_last_error() === JSON_ERROR_NONE && isset($jsonOptions['type']) && $jsonOptions['type'] === 'remote') {
                    $isRemote = true;
                    $remoteConfig = $jsonOptions;
                    $remoteSelects[$name] = $jsonOptions;
                } else {
                    // 普通下拉，生成字典
                    $arr = explode(',', $field['options']);
                    $dictItems = [];
                    foreach ($arr as $item) {
                        $kv = explode(':', $item);
                        $k = trim($kv[0] ?? '');
                        $v = trim($kv[1] ?? $k);
                        if ($k !== '') {
                            $dictItems[] = "'{$k}': '{$v}'";
                        }
                    }
                    $dictStr = '{ ' . implode(', ', $dictItems) . ' }';
                    $camelName = lcfirst($this->studly($name));
                    $dictsCode .= "const {$camelName}Dict: Record<string, string> = {$dictStr}\n";
                }
            }

            if ($field['is_list']) {
                $columnsInit[] = "  {$name}: { label: '{$desc}', visible: true }";
                $vIf = "v-if=\"columns.{$name}.visible\"";

                $customColumn = false;
                
                // 检查是否为关联字段
                $relations = $config['relations'] ?? [];
                foreach ($relations as $relation) {
                    if ($relation['type'] == 'belongsTo' && $relation['local_key'] == $name) {
                        $relationName = $relation['name'];
                        $displayField = 'name'; 
                        
                        $tableCols .= "        <el-table-column label=\"{$desc}\" min-width=\"120\" align=\"center\" {$vIf}>\n";
                        $tableCols .= "          <template #default=\"scope\">\n";
                        $tableCols .= "            {{ scope.row.{$relationName} ? scope.row.{$relationName}.{$displayField} : scope.row.{$name} }}\n";
                        $tableCols .= "          </template>\n";
                        $tableCols .= "        </el-table-column>\n";
                        $customColumn = true;
                        break;
                    }
                }
                
                if ($customColumn) continue;

                if ($field['form_type'] == 'image') {
                    if (preg_match('/(images|avatars)$/i', $name)) {
                        $tableCols .= "        <el-table-column label=\"{$desc}\" width=\"100\" align=\"center\" {$vIf}>\n";
                        $tableCols .= "          <template #default=\"scope\">\n";
                        $tableCols .= "            <div v-if=\"scope.row.{$name} && scope.row.{$name}.length\">\n";
                        $tableCols .= "              <el-image\n";
                        $tableCols .= "                :src=\"scope.row.{$name}[0]\"\n";
                        $tableCols .= "                style=\"width: 50px; height: 50px\"\n";
                        $tableCols .= "                preview-teleported\n";
                        $tableCols .= "                :preview-src-list=\"scope.row.{$name}\"\n";
                        $tableCols .= "              />\n";
                        $tableCols .= "            </div>\n";
                        $tableCols .= "          </template>\n";
                        $tableCols .= "        </el-table-column>\n";
                    } else {
                        $tableCols .= "        <el-table-column label=\"{$desc}\" width=\"100\" align=\"center\" {$vIf}>\n";
                        $tableCols .= "          <template #default=\"scope\">\n";
                        $tableCols .= "            <el-image\n";
                        $tableCols .= "              v-if=\"scope.row.{$name}\"\n";
                        $tableCols .= "              :src=\"scope.row.{$name}\"\n";
                        $tableCols .= "              style=\"width: 50px; height: 50px\"\n";
                        $tableCols .= "              preview-teleported\n";
                        $tableCols .= "              :preview-src-list=\"[scope.row.{$name}]\"\n";
                        $tableCols .= "            />\n";
                        $tableCols .= "          </template>\n";
                        $tableCols .= "        </el-table-column>\n";
                    }
                } elseif ($field['form_type'] == 'file') {
                    if (preg_match('/(files)$/i', $name)) {
                        $tableCols .= "        <el-table-column label=\"{$desc}\" min-width=\"150\" align=\"center\" {$vIf}>\n";
                        $tableCols .= "          <template #default=\"scope\">\n";
                        $tableCols .= "            <div v-if=\"scope.row.{$name} && scope.row.{$name}.length\">\n";
                        $tableCols .= "              <div v-for=\"(item, index) in scope.row.{$name}\" :key=\"index\">\n";
                        $tableCols .= "                <el-link\n";
                        $tableCols .= "                  v-if=\"item\"\n";
                        $tableCols .= "                  type=\"primary\"\n";
                        $tableCols .= "                  :href=\"item\"\n";
                        $tableCols .= "                  target=\"_blank\"\n";
                        $tableCols .= "                  class=\"duration-300\"\n";
                        $tableCols .= "                  :underline=\"false\"\n";
                        $tableCols .= "                >\n";
                        $tableCols .= "                  下载/预览\n";
                        $tableCols .= "                </el-link>\n";
                        $tableCols .= "              </div>\n";
                        $tableCols .= "            </div>\n";
                        $tableCols .= "          </template>\n";
                        $tableCols .= "        </el-table-column>\n";
                    } else {
                        $tableCols .= "        <el-table-column label=\"{$desc}\" min-width=\"120\" align=\"center\" {$vIf}>\n";
                        $tableCols .= "          <template #default=\"scope\">\n";
                        $tableCols .= "            <el-link\n";
                        $tableCols .= "              v-if=\"scope.row.{$name}\"\n";
                        $tableCols .= "              type=\"primary\"\n";
                        $tableCols .= "              :href=\"scope.row.{$name}\"\n";
                        $tableCols .= "              target=\"_blank\"\n";
                        $tableCols .= "              class=\"duration-300\"\n";
                        $tableCols .= "              :underline=\"false\"\n";
                        $tableCols .= "            >\n";
                        $tableCols .= "              下载/预览\n";
                        $tableCols .= "            </el-link>\n";
                        $tableCols .= "          </template>\n";
                        $tableCols .= "        </el-table-column>\n";
                    } 
                } elseif ($field['form_type'] == 'video') {
                    $hasVideo = true;
                    $tableCols .= "        <el-table-column prop=\"{$name}\" label=\"{$desc}\" min-width=\"120\" align=\"center\" {$vIf}>\n";
                    $tableCols .= "          <template #default=\"scope\">\n";
                    $tableCols .= "            <div class=\"flex justify-center items-center\" v-if=\"scope.row.{$name}\">\n";
                    $tableCols .= "              <material-file\n";
                    $tableCols .= "                class=\"cursor-pointer\"\n";
                    $tableCols .= "                :url=\"scope.row.{$name}\"\n";
                    $tableCols .= "                file-size=\"40px\"\n";
                    $tableCols .= "                @click.stop=\"handleVideoPreview(scope.row.{$name})\"\n";
                    $tableCols .= "              />\n";
                    $tableCols .= "            </div>\n";
                    $tableCols .= "          </template>\n";
                    $tableCols .= "        </el-table-column>\n";
                } elseif (in_array($ft, ['select', 'radio', 'checkbox', 'mulselect'])) {
                    if ($isRemote) {
                        $guessRelationName = preg_replace('/_id$/', '', $name);
                        $guessRelationName = lcfirst($this->studly($guessRelationName));
                        $labelField = $remoteConfig['labelField'] ?? 'name';
                        
                        foreach ($relations as $relation) {
                            if ($relation['local_key'] == $name || $relation['name'] == $guessRelationName) {
                                $guessRelationName = $relation['name'];
                                break;
                            }
                        }
                        
                        $tableCols .= "        <el-table-column label=\"{$desc}\" min-width=\"120\" align=\"center\" {$vIf}>\n";
                        $tableCols .= "          <template #default=\"scope\">\n";
                        $tableCols .= "            {{ scope.row.{$guessRelationName}?.{$labelField} || scope.row.{$name} }}\n";
                        $tableCols .= "          </template>\n";
                        $tableCols .= "        </el-table-column>\n";
                    } else {
                        $camelName = lcfirst($this->studly($name));
                        $tableCols .= "        <el-table-column\n";
                        $tableCols .= "          prop=\"{$name}\"\n";
                        $tableCols .= "          label=\"{$desc}\"\n";
                        $tableCols .= "          min-width=\"120\"\n";
                        $tableCols .= "          align=\"center\"\n";
                        $tableCols .= "          {$vIf}\n";
                        $tableCols .= "        >\n";
                        $tableCols .= "          <template #default=\"scope\">\n";
                        $tableCols .= "            {{ getDictLabel(scope.row.{$name}, {$camelName}Dict) }}\n";
                        $tableCols .= "          </template>\n";
                        $tableCols .= "        </el-table-column>\n";
                    }
                } else {
                    $tableCols .= "        <el-table-column prop=\"{$name}\" label=\"{$desc}\" min-width=\"120\" align=\"center\" {$vIf} />\n";
                }
            }
        }
        
        $columnsInitCode = implode(",\n", $columnsInit);

        // 生成辅助函数：getDictLabel
        $getDictLabelFunc = "";
        if (!empty($dictsCode)) {
            $getDictLabelFunc = "\n// 字典标签转换辅助函数\nconst getDictLabel = (value: any, dict: Record<string, string>): string => {\n  if (Array.isArray(value)) {\n    return value.map((v) => dict[v] || v).join(', ')\n  }\n  return dict[value] || value\n}\n";
        }
        
        // 处理远程下拉的 API 导入和数据加载
        $remoteImports = "";
        $remoteVars = "";
        $remoteLoadFuncs = "";
        $remoteLoadCalls = "";
        
        foreach ($remoteSelects as $fieldName => $cfg) {
            $model = $cfg['model'] ?? '';
            if (empty($model)) continue;
            
            // 转换为驼峰命名
            $camelFieldName = lcfirst($this->studly($fieldName));  // 用于变量名（首字母小写）
            $studlyFieldName = $this->studly($fieldName);           // 用于函数名（首字母大写）
            
            $modelParts = explode('\\', $model);
            $modelClass = end($modelParts);
            $apiModule = lcfirst($modelClass);
            
            $currentModule = $data['module_name'] ?? '';
            $apiPathName = $currentModule ? "{$currentModule}/{$apiModule}" : $apiModule;
            
            $listFunc = "{$apiModule}Lists";
            
            if (strpos($remoteImports, $listFunc) === false) {
                $remoteImports .= "import { {$listFunc} } from '@/api/{$apiPathName}'\n";
            }
            
            $remoteVars .= "const {$camelFieldName}List = ref<any[]>([])\n";
            
            $remoteLoadFuncs .= "const get{$studlyFieldName}List = async () => {\n  try {\n    const res = await {$listFunc}({})\n    {$camelFieldName}List.value = res.data || []\n  } catch (e) {\n    console.error('获取{$fieldName}列表失败', e)\n  }\n}\n";
            
            $remoteLoadCalls .= "  get{$studlyFieldName}List()\n";
        }

        // 准备导出字段
        $formatterCases = "";
        foreach ($fields as $field) {
            $name = $field['name'];
            $ft = $field['form_type'];
            
            $relations = $config['relations'] ?? [];
            foreach ($relations as $rel) {
                if ($rel['type'] == 'belongsTo' && $rel['local_key'] == $name) {
                    $formatterCases .= "      case '{$name}': return row.{$rel['name']}?.name || val\n";
                    continue 2;
                }
            }
            
            if (in_array($ft, ['select', 'radio', 'checkbox', 'mulselect']) && !empty($field['options'])) {
                $opts = json_decode($field['options'], true);
                if (json_last_error() === JSON_ERROR_NONE && ($opts['type'] ?? '') === 'remote') {
                    $guess = preg_replace('/_id$/', '', $name);
                    $guess = lcfirst($this->studly($guess));
                    $label = $opts['labelField'] ?? 'name';
                    $relName = $guess;
                    foreach ($relations as $rel) {
                        if ($rel['local_key'] == $name) { $relName = $rel['name']; break; }
                    }
                    $formatterCases .= "      case '{$name}': return row.{$relName}?.{$label} || val\n";
                } else {
                    $camelName = lcfirst($this->studly($name));
                    $formatterCases .= "      case '{$name}': return getDictLabel(val, {$camelName}Dict)\n";
                }
            }
        }

        // 生成搜索区域（仅当有搜索字段时）
        $searchSection = "";
        if ($hasSearchFields) {
            $searchSection = "    <el-card class=\"!border-none mb-4\" shadow=\"never\" v-if=\"showSearch\">\n      <el-form class=\"mb-[-16px]\" ref=\"queryFormRef\" :model=\"queryParams\" inline @submit.prevent>\n{$searchFormItems}        <el-form-item>\n          <el-button type=\"primary\" @click=\"handleQuery\">搜索</el-button>\n          <el-button @click=\"resetQuery\">重置</el-button>\n        </el-form-item>\n      </el-form>\n    </el-card>\n";
        }
        
        // 生成 queryParamsInit（最后一个字段不带逗号）
        $queryParamsInit = implode(",\n", $queryParamsInitArr);
        if (!empty($queryParamsInit)) {
            $queryParamsInit .= "\n";
        }
        
        // 生成视频预览代码（仅当有视频字段时）
        $videoPreviewCode = "";
        $videoPreviewTemplate = "";
        if ($hasVideo) {
            $videoPreviewCode = "\n// 视频预览\nconst videoUrlPreview = ref('')\nconst showVideoPreview = ref(false)\nconst handleVideoPreview = (url: string) => {\n  videoUrlPreview.value = url\n  showVideoPreview.value = true\n}\n";
            $videoPreviewTemplate = "    <material-preview v-model=\"showVideoPreview\" :url=\"videoUrlPreview\" type=\"video\" />\n";
        }
        
        $content = <<<VUE
<template>
  <div class="{$lcClass}">
{$searchSection}    <el-card v-loading="isLoading" class="!border-none" shadow="never">
      <div class="wd-card-header">
        <el-button v-perms="['{$routePrefix}/add']" type="primary" icon="Plus" @click="handleAdd">新增</el-button>
        <el-button
          v-perms="['{$routePrefix}/edit']"
          type="warning"
          icon="Edit"
          :disabled="multipleSelection.length != 1"
          @click="handleEdit()"
          >修改</el-button
        >
        <el-button
          v-perms="['{$routePrefix}/delete']"
          type="danger"
          icon="Delete"
          :disabled="multipleSelection.length == 0"
          @click="handleDelete()"
          >删除</el-button
        >
VUE;
        // 动态生成 right-toolbar 的搜索属性
        $showSearchAttr = $hasSearchFields ? ' v-model:showSearch="showSearch"' : ' :search="false"';
        $content .= <<<VUE

        <right-toolbar{$showSearchAttr} v-model:columns="columns" @refresh="getLists"></right-toolbar>
      </div>
      <el-table size="large" :data="tableData" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" />
{$tableCols}        <el-table-column label="操作" width="120" fixed="right" align="center" v-if="columns.operation.visible">
          <template #default="{ row }">
            <el-button v-perms="['{$routePrefix}/edit']" type="primary" link @click="handleEdit(row)">
              编辑
            </el-button>
            <el-button v-perms="['{$routePrefix}/delete']" type="danger" link @click="handleDelete(row.{$pk})">
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="flex mt-4 justify-end">
        <Pagination v-model="queryParams" :total="total" @change="getLists" />
      </div>
    </el-card>
    <edit-{$lcClass} ref="editRef" @success="getLists" />
{$videoPreviewTemplate}  </div>
</template>

<script setup lang="ts" name="{$componentName}">
import type { FormInstance } from 'element-plus'
import { {$lcClass}Lists, {$lcClass}Delete } from '{$apiPath}'
{$remoteImports}import Edit{$ucClass} from './edit.vue'

// 组件参数
const queryFormRef = shallowRef<FormInstance>()
const editRef = shallowRef<InstanceType<typeof Edit{$ucClass}>>()
const queryParams = ref({
  page: 1,
  limit: 10,
{$queryParamsInit}})
const total = ref(0)
const multipleSelection = ref<any[]>([])
const isLoading = ref(false)
const tableData = ref<any[]>([])
VUE;
        // 动态生成 showSearch 变量（仅当有搜索字段时）
        if ($hasSearchFields) {
            $content .= <<<VUE

const showSearch = ref(true)
VUE;
        }
        $content .= <<<VUE

const columns = ref({
{$columnsInitCode},
  operation: { label: '操作', visible: true }
})

VUE;
        // 动态生成字典数据定义（仅当有字典数据时）
        if (!empty($dictsCode)) {
            $content .= <<<VUE

// 字典数据定义
{$dictsCode}{$getDictLabelFunc}
VUE;
        }
        
        // 动态生成远程下拉数据（仅当有远程下拉时）
        if (!empty($remoteVars)) {
            $content .= <<<VUE

// 远程下拉数据
{$remoteVars}{$remoteLoadFuncs}
VUE;
        }
        
        $content .= <<<VUE

// 获取列表
const getLists = async () => {
  isLoading.value = true
  {$lcClass}Lists(queryParams.value)
    .then((res) => {
      tableData.value = res.data || []
      total.value = res.total || 0
      isLoading.value = false
    })
    .catch(() => {
      isLoading.value = false
    })
}

// 搜索
const handleQuery = () => {
  queryParams.value.page = 1
  getLists()
}

// 重置
const resetQuery = () => {
  queryFormRef.value?.resetFields()
  handleQuery()
}

// 多选框选中数据
const handleSelectionChange = (selection: any[]) => {
  multipleSelection.value = selection
}

// 新增
const handleAdd = async () => {
  editRef.value?.open('add')
  editRef.value?.setFormData()
}

// 编辑
const handleEdit = async (row?: any) => {
  editRef.value?.open('edit')
  editRef.value?.setFormData(row ? row : multipleSelection.value[0])
}

// 删除
const handleDelete = async (id?: number) => {
  const ids = id || multipleSelection.value.map((item) => item.{$pk})
  await ElMessageBox.confirm(`是否确认删除ID为 \${ids} 的数据项吗？`, '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(async () => {
      await {$lcClass}Delete({ {$pk}: ids })
      ElMessage.success('删除成功')
      getLists()
    })
    .catch(() => {})
}
{$videoPreviewCode}
onMounted(() => {
{$remoteLoadCalls}  getLists()
})
</script>
VUE;
        return ['path' => $frontPath, 'filename' => 'index.vue', 'content' => $content, 'type' => 'vue_index', 'desc' => '列表页'];
    }

    private function makeFrontendOperate($data)
    {
        $module = $data['module_name'];
        $class = $data['class_name'];
        $fields = $data['fields'];
        $pk = $data['pk'];
        $lcClass = lcfirst($class);
        
        $apiPath = "";
        if (!empty($module)) {
            $frontPath = dirname(root_path()) . '/admin/src/views/' . $module . '/' . $lcClass . '/';
            $apiPath = "@/api/{$module}/{$lcClass}";
        } else {
            $frontPath = dirname(root_path()) . '/admin/src/views/' . $lcClass . '/';
            $apiPath = "@/api/{$lcClass}";
        }

        $formItems = "";
        $rulesArr = [];
        $formDataInitArr = [];
        $hasImage = false;
        $hasEditor = false;
        $remoteSelects = []; // 收集远程下拉配置
        
        foreach ($fields as $field) {
            if (!$field['is_edit'] && !$field['is_insert']) continue;
            
            $name = $field['name'];
            $desc = $field['desc'];
            $ft = $field['form_type'];
            
            if (in_array($ft, ['image', 'file'])) $hasImage = true;
            if ($ft == 'editor') $hasEditor = true;

            $formItems .= "        <el-form-item label=\"{$desc}\" prop=\"{$name}\">\n";
            
            if ($ft == 'input') {
                $formItems .= "          <el-input v-model=\"formData.{$name}\" placeholder=\"请输入{$desc}\" clearable />\n";
            } elseif ($ft == 'textarea') {
                $formItems .= "          <el-input\n";
                $formItems .= "            v-model=\"formData.{$name}\"\n";
                $formItems .= "            type=\"textarea\"\n";
                $formItems .= "            placeholder=\"请输入{$desc}\"\n";
                $formItems .= "            :autosize=\"{ minRows: 2, maxRows: 4 }\"\n";
                $formItems .= "          />\n";
            } elseif ($ft == 'editor') {
                $formItems .= "          <editor v-model=\"formData.{$name}\" :height=\"500\" />\n";
            } elseif ($ft == 'image') {
                if (preg_match('/(images|avatars)$/i', $name)) {
                     $formItems .= "          <material-picker v-model=\"formData.{$name}\" :limit=\"-1\" />\n";
                } else {
                     $formItems .= "          <material-picker v-model=\"formData.{$name}\" :limit=\"1\" />\n";
                }
            } elseif ($ft == 'file') {
                 if (preg_match('/(files)$/i', $name)) {
                     $formItems .= "          <material-picker v-model=\"formData.{$name}\" type=\"file\" :limit=\"-1\" />\n";
                 } else {
                     $formItems .= "          <material-picker v-model=\"formData.{$name}\" type=\"file\" :limit=\"1\" />\n";
                 }
            } elseif ($ft == 'video') {
                 $formItems .= "          <material-picker v-model=\"formData.{$name}\" type=\"video\" :limit=\"1\" />\n";
            } elseif ($ft == 'date') {
                $formItems .= "          <el-date-picker\n";
                $formItems .= "            v-model=\"formData.{$name}\"\n";
                $formItems .= "            type=\"date\"\n";
                $formItems .= "            value-format=\"YYYY-MM-DD\"\n";
                $formItems .= "            placeholder=\"请选择{$desc}\"\n";
                $formItems .= "            clearable\n";
                $formItems .= "          />\n";
            } elseif ($ft == 'datetime') {
                $formItems .= "          <el-date-picker\n";
                $formItems .= "            v-model=\"formData.{$name}\"\n";
                $formItems .= "            type=\"datetime\"\n";
                $formItems .= "            value-format=\"YYYY-MM-DD HH:mm:ss\"\n";
                $formItems .= "            placeholder=\"请选择{$desc}\"\n";
                $formItems .= "            clearable\n";
                $formItems .= "          />\n";
            } elseif (in_array($ft, ['select', 'radio', 'checkbox', 'mulselect'])) {
                // 处理下拉/单选/多选
                $isRemote = false;
                $remoteConfig = [];
                // 尝试解析JSON
                if (!empty($field['options'])) {
                    $jsonOptions = json_decode($field['options'], true);
                    if (json_last_error() === JSON_ERROR_NONE && isset($jsonOptions['type']) && $jsonOptions['type'] === 'remote') {
                         $isRemote = true;
                         $remoteConfig = $jsonOptions;
                         $remoteSelects[$name] = $jsonOptions;
                    }
                }
                
                if ($isRemote && $ft == 'select') {
                     $labelField = $remoteConfig['label_field'] ?? 'name';
                     $valueField = $remoteConfig['value_field'] ?? 'id';
                     $camelName = lcfirst($this->studly($name));
                     $formItems .= "          <el-select class=\"flex-1\" v-model=\"formData.{$name}\" placeholder=\"请选择{$desc}\" clearable>\n";
                     $formItems .= "            <el-option v-for=\"(item, index) in {$camelName}List\" :key=\"index\" :label=\"item.{$labelField}\" :value=\"item.{$valueField}\" />\n";
                     $formItems .= "          </el-select>\n";
                } else {
                     $multipleAttr = in_array($ft, ['checkbox', 'mulselect']) ? ' multiple' : '';
                     $formItems .= "          <el-select class=\"flex-1\" v-model=\"formData.{$name}\" placeholder=\"请选择{$desc}\" clearable{$multipleAttr}>\n";
                     if (!empty($field['options'])) {
                         // 兼容 JSON (type=normal) 或 字符串
                         if (!$isRemote) { // 普通
                             $optionsArr = [];
                             if (json_last_error() === JSON_ERROR_NONE && is_array($jsonOptions)) {
                                 // 如果是 JSON 格式的普通选项 (暂未定义格式，假设不做复杂解析，或者如果 options 是字符串)
                                 // 简单起见，这里复用之前的 explode 逻辑，因为 type=remote 已经被拦截
                                 // 如果 options 是 JSON 字符串但不是 remote，可能是未来扩展
                                 // 目前假设普通下拉还是字符串，或者用户没传 type=remote
                                 // 如果是 JSON 且不是 remote，这里可能需要特殊处理，但目前先回退到字符串处理
                                 // 实际上 json_decode 如果成功，$field['options'] 也是字符串。
                             }
                             
                             // 无论是否 JSON，如果不是 remote，尝试按字符串解析（或者原本就是字符串）
                             // 如果是 JSON 格式的 field['options']，explode 可能会乱。
                             // 我们假设普通下拉还是 key:val,key:val 格式。
                             // 如果用户传了 JSON 格式的普通配置，比如 [{"label":"A","value":1}]，这里需要修改。
                             // 暂时保持 explode 兼容旧数据。如果 json_decode 成功但没 type=remote，看是否是数组?
                             
                             // 增强解析：如果是 JSON 数组
                             if (json_last_error() === JSON_ERROR_NONE && is_array($jsonOptions) && !isset($jsonOptions['type'])) {
                                 // 假设是 [{"label":"A", "value":"1"}] 格式?
                                 // 或者 {"1":"A", "2":"B"}
                                 foreach ($jsonOptions as $k => $v) {
                                      if (is_array($v)) { /* 复杂结构暂不在此处理 */ }
                                      else {
                                          $formItems .= "            <el-option label=\"{$v}\" value=\"{$k}\" />\n";
                                      }
                                 }
                             } else {
                                 $arr = explode(',', $field['options']);
                                 foreach ($arr as $item) {
                                     $kv = explode(':', $item);
                                     $k = trim($kv[0] ?? '');
                                     $v = trim($kv[1] ?? $k);
                                     if ($k !== '') {
                                         $formItems .= "            <el-option label=\"{$v}\" value=\"{$k}\" />\n";
                                     }
                                 }
                             }
                         }
                     }
                    $formItems .= "          </el-select>\n";
                }
            } else {
                $formItems .= "          <el-input v-model=\"formData.{$name}\" placeholder=\"请输入{$desc}\" clearable />\n";
            }
            
            $formItems .= "        </el-form-item>\n";

            // Init data
            $default = "''";
            if ($field['type'] == 'int' || strpos($field['type'], 'int') !== false) {
                // 如果是下拉、单选、日期等，即使是这类字段通常存int，前端初始值也最好是空，避免显示 0
                if (in_array($ft, ['select', 'radio', 'date', 'datetime'])) {
                    $default = "''";
                } else {
                    $default = "0";
                }
            }
            if (in_array($ft, ['checkbox', 'mulselect']) || preg_match('/(images|avatars|files)$/i', $name)) $default = "[]";
            $formDataInitArr[] = "  {$name}: {$default}";

            // 构建验证规则
            if (!empty($field['is_required'])) {
                 $trigger = in_array($ft, ['select', 'radio', 'checkbox', 'mulselect', 'date', 'datetime', 'image', 'images']) ? 'change' : 'blur';
                 $msgType = $trigger == 'change' ? '请选择' : '请输入';
                 $rulesArr[] = "  {$name}: [{ required: true, message: '{$msgType}{$desc}', trigger: ['blur', 'change'] }]";
            }
        }
        
        // 生成最终字符串（最后一个字段不带逗号）
        $formDataInit = implode(",\n", $formDataInitArr) . "\n";
        $rules = implode(",\n", $rulesArr);
        
        // 处理 Edit 页面的远程数据加载
        $remoteImports = "";
        $remoteVars = "";
        $remoteLoads = "";
        $remoteLoadCalls = ""; // 新增：收集在 open 方法中需要调用的语句
        
        foreach ($remoteSelects as $fieldName => $config) {
            $model = $config['model'] ?? ''; 
            if (empty($model)) continue;
            
            // 转换为驼峰命名
            $camelFieldName = lcfirst($this->studly($fieldName));  // 用于变量名（首字母小写）
            $studlyFieldName = $this->studly($fieldName);           // 用于函数名（首字母大写）
            
            $modelParts = explode('\\', $model);
            $modelClass = end($modelParts);
            $apiModule = lcfirst($modelClass);
            
            // 修复 API 路径：如果当前存在模块名，假设关联模型也在同模块下
            $currentModule = $data['module_name'] ?? '';
            $apiPathName = $currentModule ? "{$currentModule}/{$apiModule}" : $apiModule;
            
            $listFunc = "{$apiModule}Lists";
            
            if (strpos($remoteImports, $listFunc) === false) {
                 $remoteImports .= "import { {$listFunc} } from '@/api/{$apiPathName}'\n";
            }
            
            $remoteVars .= "const {$camelFieldName}List = ref<any[]>([])\n";
            $remoteLoads .= "\n// 获取远程下拉数据\nconst get{$studlyFieldName}List = async () => {\n  try {\n    const { data } = await {$listFunc}({})\n    {$camelFieldName}List.value = data\n  } catch (e) {\n    console.error('获取{$camelFieldName}列表失败', e)\n  }\n}";
            
            // 收集调用语句（在 open 方法中使用）
            $remoteLoadCalls .= "  get{$studlyFieldName}List()\n";
        }

        $comment = $data['table_comment'] ?? '';
        $editTitle = str_replace(['列表', '管理'], '', $comment);

        $operateContent = <<<VUE
<template>
  <popup v-model="showDialog" :title="popupTitle" width="550px" :confirmLoading="isLoading" @confirm="handleSubmit">
    <el-scrollbar>
      <el-form ref="formRef" :model="formData" label-width="100px" :rules="formRules" v-loading="isLoading">
{$formItems}      </el-form>
    </el-scrollbar>
  </popup>
</template>
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
import { {$lcClass}Add, {$lcClass}Edit } from '{$apiPath}'
{$remoteImports}
// 定义组件事件
const emit = defineEmits<{
  (event: 'success'): void
}>()

// 表单引用
const formRef = shallowRef<FormInstance>()
// 模式：add=新增，edit=编辑
const mode = ref<'add' | 'edit'>('add')
// 加载状态
const isLoading = ref(false)
// 是否显示弹窗
const showDialog = ref(false)
// 初始表单数据
const initialFormData = {
  id: '',
{$formDataInit}}
// 表单数据
const formData = reactive<Record<string, any>>({ ...initialFormData })
// 表单验证规则
const formRules = reactive({
{$rules}
})
// 弹窗标题
const popupTitle = computed(() => {
  return mode.value == 'edit' ? '编辑{$editTitle}' : '新增{$editTitle}'
})
// 远程下拉数据
{$remoteVars}{$remoteLoads}
// 打开弹窗
const open = (type: 'add' | 'edit' = 'add') => {
{$remoteLoadCalls}  mode.value = type
  showDialog.value = true
}
// 设置表单数据
const setFormData = (row?: Record<string, any>) => {
  resetForm()
  if (!row) return
  Object.keys(formData).forEach((key: string) => {
    if (row[key] != undefined) formData[key] = row[key]
  })
}
// 表单重置
const resetForm = () => {
  Object.assign(formData, initialFormData)
  formRef.value?.resetFields()
}
// 提交表单
const handleSubmit = () => {
  formRef.value?.validate(async (valid) => {
    if (valid) {
      isLoading.value = true
      const submitEvent = mode.value === 'edit' ? {$lcClass}Edit : {$lcClass}Add
      submitEvent(formData)
        .then(() => {
          isLoading.value = false
          showDialog.value = false
          ElMessage.success('操作成功')
          emit('success')
        })
        .catch(() => {
          isLoading.value = false
        })
    }
  })
}

defineExpose({
  open,
  setFormData
})
</script>
VUE;
        
        return ['path' => $frontPath, 'filename' => 'edit.vue', 'content' => $operateContent, 'type' => 'vue_edit', 'desc' => 'Vue表单页'];
    }

    private function makeSql($data)
    {
        $comment = $data['table_comment'] ?? $data['class_name'];
        $module = $data['module_name'] ?? '';
        $class = $data['class_name'];
        $lcClass = lcfirst($class);
        
        if (!empty($module)) {
            $permsPrefix = "{$module}.{$lcClass}";
            $componentPath = "{$module}/{$lcClass}/index";
        } else {
            $permsPrefix = "{$lcClass}";
            $componentPath = "{$lcClass}/index";
        }
        
        $now = time();
        $prefix = config('database.connections.mysql.prefix');
        $tableName = $prefix . 'system_menu';
        
        $sql = <<<SQL
-- ----------------------------
-- 菜单SQL - {$comment}
-- ----------------------------
-- 请将 @pid 替换为实际的上级菜单ID
SET @pid = 0; 

INSERT INTO `{$tableName}` (`pid`, `title`, `icon`, `perms`, `type`, `component`, `sort`, `status`, `create_time`, `update_time`) VALUES
(@pid, '{$comment}', 'List', '{$permsPrefix}:lists', 'C', '{$componentPath}', 0, 1, {$now}, {$now});

SET @menuId = LAST_INSERT_ID();

INSERT INTO `{$tableName}` (`pid`, `title`, `perms`, `type`, `sort`, `status`, `create_time`, `update_time`) VALUES
(@menuId, '列表', '{$permsPrefix}:lists', 'A', 0, 1, {$now}, {$now}),
(@menuId, '详情', '{$permsPrefix}:detail', 'A', 0, 1, {$now}, {$now}),
(@menuId, '新增', '{$permsPrefix}:add', 'A', 0, 1, {$now}, {$now}),
(@menuId, '编辑', '{$permsPrefix}:edit', 'A', 0, 1, {$now}, {$now}),
(@menuId, '删除', '{$permsPrefix}:delete', 'A', 0, 1, {$now}, {$now}),
(@menuId, '导出', '{$permsPrefix}:export', 'A', 0, 1, {$now}, {$now});
SQL;

        return ['path' => root_path() . 'sql/', 'filename' => "{$lcClass}_menu.sql", 'content' => $sql, 'type' => 'sql', 'desc' => '菜单SQL'];
    }

    private function writeFile($path, $fileName, $content)
    {
        try {
            // 检查目录是否存在
            if (!@is_dir($path)) {
                // 尝试创建目录
                if (!@mkdir($path, 0755, true) && !is_dir($path)) {
                    throw new \Exception("无法创建目录: {$path}，可能是 open_basedir 限制或权限问题");
                }
            }
            
            // 写入文件
            $result = @file_put_contents($path . $fileName, $content);
            if ($result === false) {
                throw new \Exception("无法写入文件: {$path}{$fileName}，可能是 open_basedir 限制或权限问题");
            }
        } catch (\Exception $e) {
            // 记录详细错误信息
            trace("代码生成写入文件失败: " . $e->getMessage(), 'error');
            trace("目标路径: {$path}{$fileName}", 'error');
            trace("open_basedir: " . ini_get('open_basedir'), 'error');
            throw $e;
        }
    }

    private function studly($value)
    {
        $value = ucwords(str_replace(['-', '_'], ' ', $value));
        return str_replace(' ', '', $value);
    }
    
    private function lcfirst($str) {
        return lcfirst($str);
    }

}
