avatar

mdo

Hello

  • 首页
  • 知识库
  • 归档
  • 标签
  • 关于
主页 可视化的 UI 控制台页面
文章

可视化的 UI 控制台页面

发表于 2026-08-27 更新于 2026-08- 27
作者 mdo
33~42 分钟 阅读

为了实现前后端分离架构,并且配备一个可视化的 UI 控制台页面,我们需要对代码生成流程进行两项升级:

  1. 前端与接口升级: 采用标准的 RESTful API 规范。后端 TP6 仅返回结构化的 JSON 数据(包含状态码、提示信息和数据负载),前端通过 AJAX (Fetch) 进行异步交互。

  2. UI 页面实现: 编写一个基于 HTML5 + Tailwind CSS 的现代化单页面控制台。用户输入需求后,页面会展示美观的“全自动开发”进度条。

以下是为您量身定制的完整落地代码:


第一步:升级 TP6 控制器 (app\controller\CodeFactory.php)

我们需要修改 submit 方法使其支持前端跨域(如果需要),并新增一个查询任务状态/获取生成代码的接口。同时,为了安全起见,我们将 AI 生成的代码存储在本地缓存或数据库中,由前端 UI 呼叫接口来渲染,而不是直接静默写入服务器文件(这样更符合前后端分离的安全规范)。

php

<?php
namespace app\controller;

use app\BaseController;
use think\facade\Http;
use think\facade\Cache;

class CodeFactory extends BaseController
{
    // Python CrewAI 服务的 API 地址
    private $pythonServiceUrl = 'http://127.0.0';

    /**
     * 1. 渲染/输出 UI 界面
     * URL: http://your-domain/codegen/ui
     */
    public function index()
    {
        // 渲染单页面静态 UI(代码见第二步)
        return view('index'); 
    }

    /**
     * 2. API: 接收前端需求并下发给 CrewAI
     * URL: POST http://your-domain/codegen/api/submit
     */
    public function submit()
    {
        $requirement = $this->request->param('requirement');
        if (empty($requirement)) {
            return json(['code' => 400, 'msg' => '请输入您的业务需求描述']);
        }

        // 生成一个唯一的任务 ID 供前端后续轮询
        $taskId = 'task_' . uniqid() . '_' . rand(1000, 9999);
        
        // 初始化任务状态为 "processing" (处理中)
        Cache::set($taskId, ['status' => 'processing', 'progress' => 30, 'data' => null], 1800);
        
        // 动态生成回调地址,并在 URL 中携带这个 taskId
        $callbackUrl = url('codegen/api/callback', ['task_id' => $taskId])->suffix(false)->domain(true)->build();

        try {
            // 呼叫 CrewAI 异步研发团队
            Http::post($this->pythonServiceUrl, [
                'requirement'  => $requirement . '(注意:请务必按照前后端分离的纯 API 格式编写控制器,返回 json 格式数据,包含 code, msg, data 字段)',
                'callback_url' => $callbackUrl
            ]);

            return json([
                'code' => 200, 
                'msg'  => 'AI 研发团队已成功组建,正在为您紧急编写代码...',
                'data' => ['task_id' => $taskId]
            ]);
        } catch (\Exception $e) {
            return json(['code' => 500, 'msg' => '连接 AI 服务失败:' . $e->getMessage()]);
        }
    }

    /**
     * 3. API: 接收 CrewAI 回调并将结果写入缓存
     * URL: POST http://your-domain/codegen/api/callback?task_id=xxxx
     */
    public function receiveCode()
    {
        $taskId = $this->request->param('task_id');
        $payload = $this->request->param();

        if (empty($taskId) || !isset($payload['code_package'])) {
            return json(['code' => 400, 'msg' => '无效的回调参数']);
        }

        // 更新缓存状态为 "success" (成功) 并存入代码数据
        Cache::set($taskId, [
            'status'   => 'success',
            'progress' => 100,
            'data'     => $payload['code_package']
        ], 1800);

        return json(['code' => 200, 'msg' => 'TP6 已安全接收代码资产']);
    }

    /**
     * 4. API: 供前端 UI 轮询开发进度的接口
     * URL: GET http://your-domain/codegen/api/status?task_id=xxxx
     */
    public function getStatus()
    {
        $taskId = $this->request->param('task_id');
        if (empty($taskId)) {
            return json(['code' => 400, 'msg' => '缺少任务ID']);
        }

        $task = Cache::get($taskId);
        if (!$task) {
            return json(['code' => 404, 'msg' => '任务不存在或已过期']);
        }

        return json([
            'code' => 200,
            'msg'  => '获取状态成功',
            'data' => $task
        ]);
    }
}

请谨慎使用此类代码。


第二步:编写 UI 控制台页面 (app/view/index.html)

这是一个基于 Tailwind CSS 构建的单页面应用(SPA)前端。它包含了:业务需求提交表单、动态进度条、以及按标签页(SQL、Controller、Model、Validate)分类的高亮代码展示区。

html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CrewAI & TP6 自动代码生成控制台</title>
    <!-- 引入 Tailwind CSS 提升 UI 美观度 -->
    <script src="https://jsdelivr.net"></script>
    <style>
        .tab-content { display: none; }
        .tab-content.active { display: block; }
        .tab-btn.active { border-color: #3b82f6; color: #3b82f6; font-weight: bold; }
    </style>
</head>
<body class="bg-gray-50 min-h-screen font-sans antialiased text-gray-900">

    <div class="max-w-6xl mx-auto px-4 py-8">
        <!-- 头部 Header -->
        <header class="flex items-center justify-between pb-6 mb-8 border-b border-gray-200">
            <div>
                <h1 class="text-3xl font-extrabold tracking-tight text-gray-900">AI 业务代码自动化生成控制台</h1>
                <p class="text-sm text-gray-500 mt-1">基于 CrewAI (多智能体) & ThinkPHP 6 前后端分离架构</p>
            </div>
            <span class="px-3 py-1 bg-blue-100 text-blue-800 text-xs font-semibold rounded-full">v1.0 生产级</span>
        </header>

        <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
            <!-- 左侧:输入控制区 -->
            <div class="bg-white p-6 rounded-2xl shadow-xs border border-gray-200 h-fit">
                <h2 class="text-lg font-bold mb-4 text-gray-800">🛠 需求下发配置</h2>
                
                <div class="mb-4">
                    <label class="block text-sm font-medium text-gray-700 mb-2">业务模块描述</label>
                    <textarea id="requirement" rows="6" class="w-full p-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all text-sm" placeholder="例如:创建一个商城商品管理模块。包含商品名称、价格、库存。需要支持分类筛选,控制器中所有方法必须返回统一的 JSON 格式。"></textarea>
                </div>

                <button id="submitBtn" onclick="startGeneration()" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-4 rounded-xl transition-colors cursor-pointer shadow-sm flex items-center justify-center gap-2">
                    🚀 召集 AI 团队开发
                </button>

                <!-- 进度条面板 (默认隐藏) -->
                <div id="progressPanel" class="mt-6 pt-6 border-t border-gray-100 hidden">
                    <div class="flex justify-between text-xs font-medium text-gray-500 mb-1">
                        <span id="statusText">正在召集架构师与程序员...</span>
                        <span id="progressPercent">30%</span>
                    </div>
                    <div class="w-full bg-gray-200 rounded-full h-2">
                        <div id="progressBar" class="bg-blue-600 h-2 rounded-full transition-all duration-500" style="width: 30%"></div>
                    </div>
                    <p class="text-xs text-amber-600 mt-2 bg-amber-50 p-2 rounded border border-amber-100 animate-pulse">
                        💡 大模型生成完整业务逻辑大约需要 30秒 ~ 2分钟,请勿关闭页面。
                    </p>
                </div>
            </div>

            <!-- 右侧:成果输出区 -->
            <div class="lg:col-span-2 bg-white rounded-2xl shadow-xs border border-gray-200 overflow-hidden flex flex-col min-h-[500px]">
                <!-- 标签页头部 -->
                <div class="bg-gray-50 border-b border-gray-200 px-4 flex gap-2">
                    <button onclick="switchTab('sql')" class="tab-btn active px-4 py-3 text-sm font-medium border-b-2 border-transparent hover:text-gray-700 cursor-pointer">📄 MySQL 建表</button>
                    <button onclick="switchTab('controller')" class="tab-btn px-4 py-3 text-sm font-medium border-b-2 border-transparent hover:text-gray-700 cursor-pointer">📦 Controller</button>
                    <button onclick="switchTab('model')" class="tab-btn px-4 py-3 text-sm font-medium border-b-2 border-transparent hover:text-gray-700 cursor-pointer">💎 Model</button>
                    <button onclick="switchTab('validate')" class="tab-btn px-4 py-3 text-sm font-medium border-b-2 border-transparent hover:text-gray-700 cursor-pointer">🛡 Validate</button>
                </div>

                <!-- 代码展示内容区 -->
                <div class="p-6 flex-1 bg-gray-900 text-gray-100 font-mono text-sm overflow-auto max-h-[600px]">
                    <div id="sql" class="tab-content active"><span class="text-gray-500">// AI 编写的 SQL 脚本将在这里展示</span></div>
                    <div id="controller" class="tab-content"><span class="text-gray-500">// 前后端分离 API 控制器代码将在这里展示</span></div>
                    <div id="model" class="tab-content"><span class="text-gray-500">// 模型层代码将在这里展示</span></div>
                    <div id="validate" class="tab-content"><span class="text-gray-500">// 验证器层代码将在这里展示</span></div>
                </div>
            </div>
        </div>
    </div>

    <!-- 前端核心逻辑:异步联调与轮询 -->
    <script>
        let timer = null;

        // 标签页切换逻辑
        function switchTab(tabId) {
            document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
            document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
            
            event.target.classList.add('active');
            document.getElementById(tabId).classList.add('active');
        }

        // 发起开发任务
        async function startGeneration() {
            const reqText = document.getElementById('requirement').value.trim();
            if(!reqText) return alert('请先输入业务模块的需求描述!');

            const submitBtn = document.getElementById('submitBtn');
            submitBtn.disabled = true;
            submitBtn.classList.add('opacity-50');

            document.getElementById('progressPanel').classList.remove('hidden');
            updateProgress(30, '正在分配 Agent 架构师分析需求...');

            try {
                // 1. 请求 TP6 下发任务
                const response = await fetch('/codegen/api/submit', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: `requirement=${encodeURIComponent(reqText)}`
                });
                const res = await response.json();

                if(res.code === 200) {
                    const taskId = res.data.task_id;
                    // 2. 启动定时器,每隔 3 秒轮询一次状态
                    timer = setInterval(() => checkStatus(taskId), 3000);
                } else {
                    alert('下发任务失败:' + res.msg);
                    resetButton();
                }
            } catch (e) {
                alert('网络请求出错');
                resetButton();
            }
        }

        // 轮询状态接口
        async function checkStatus(taskId) {
            try {
                const response = await fetch(`/codegen/api/status?task_id=${taskId}`);
                const res = await response.json();

                if(res.code === 200) {
                    if (res.data.status === 'processing') {
                        // 模拟进度条缓慢增长,给用户良好体验
                        let currentProgress = parseInt(document.getElementById('progressBar').style.width);
                        if(currentProgress < 90) {
                            updateProgress(currentProgress + 5, '高级 PHP 工程师正在疯狂敲击键盘编写代码...');
                        }
                    } else if (res.data.status === 'success') {
                        // 生成成功,清除定时器
                        clearInterval(timer);
                        updateProgress(100, '代码全部生成成功!已安全缓存。');
                        resetButton();
                        // 渲染代码到页面
                        renderCode(res.data.data);
                    }
                }
            } catch (e) {
                console.error('轮询失败', e);
            }
        }

        // 更新进度条 UI
        function updateProgress(num, text) {
            document.getElementById('progressBar').style.width = num + '%';
            document.getElementById('progressPercent').innerText = num + '%';
            document.getElementById('statusText').innerText = text;
        }

        function resetButton() {
            const submitBtn = document.getElementById('submitBtn');
            submitBtn.disabled = false;
            submitBtn.classList.remove('opacity-50');
        }

        // 将 AI 代码填入对应标签页 (替换文本中的特殊 HTML 字符防止错乱)
        function renderCode(pkg) {
            document.getElementById('sql').innerHTML = `<pre>${escapeHtml(pkg.sql || '未生成')}</pre>`;
            document.getElementById('controller').innerHTML = `<pre>${escapeHtml(pkg.controller || '未生成')}</pre>`;
            document.getElementById('model').innerHTML = `<pre>${escapeHtml(pkg.model || '未生成')}</pre>`;
            document.getElementById('validate').innerHTML = `<pre>${escapeHtml(pkg.validate || '未生成')}</pre>`;
        }

        function escapeHtml(string) {
            return String(string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
        }
    </script>
</body>
</html>

请谨慎使用此类代码。


第三步:更新 TP6 路由配置 (route\app.php)

在您的路由文件中,配置以下统一路由规则:

php

use think\facade\Route;

Route::group('codegen', function () {
    // 渲染控制台 UI 页面
    Route::get('ui', 'CodeFactory/index');
    
    // 前后端分离 API 接口
    Route::post('api/submit', 'CodeFactory/submit');
    Route::get('api/status', 'CodeFactory/getStatus');
    
    // 供 Python CrewAI 调用的跨服务回调
    Route::post('api/callback', 'CodeFactory/receiveCode');
});

请谨慎使用此类代码。

有了这套架构,您的前端不仅可以完美监控 AI 的工作进度,生成的 Controller 也会完全遵循您要求的前后端分离风格(使用 return json([...]) 模式进行响应)。

针对生成的代码,我们需要考虑持久化和可追溯性:

  • 是否需要我帮您编写“一键下载代码 ZIP 压缩包”的 TP6 后端功能,以便您直接解压使用?

  • 还是需要我帮您加上“一键将 SQL 写入真实 MySQL 数据库”的闭环执行代码?

技术
许可协议:  CC BY 4.0
分享

相关文章

10月 2, 2026

iOS 侧载(Sideloading)工具

AltStore 和 Sideloadly 都是目前最流行的 iOS 侧载(Sideloading)工具。 简单来说,它们的作用就是让你绕过苹果的官方 App Store,直接把第三方的 IPA 安装包(也就是 iOS 的应用安装文件)安装到你的 iPhone 或 iPad 上。 通过这两个工具,你

9月 28, 2026

注册 Chat Participant

将当前服务接入 VS Code 的 AI 聊天窗口是完全可行的。VS Code 在近期的版本中正式推出了 Chat Extensions API(github.copilot.chat),允许第三方扩展将自定义的 LLM 服务或工具注册到 Copilot Chat 面板中。 结合当前 code_ge

9月 23, 2026

php intelephense 设置 if 空格

在 VS Code 中,如果你使用的是 PHP Intelephense 插件,控制 if 关键字后面是否加空格的设置是由其内置的格式化引擎(基于 PSR-12 标准)决定的。 你可以通过修改 VS Code 的 settings.json 来配置这个行为: 1. 开启 if 后面的空格(默认/推荐

下一篇

自动化研发

上一篇

命令行代码助手

最近更新

  • 比尔盖茨的智慧:想多赚钱,就每天循环做这3件事
  • iOS 侧载(Sideloading)工具
  • 程序员越想创业,越不要急着动手
  • 注册 Chat Participant
  • 中秋给在外游子的一封信

热门标签

API CodeGeex Coding Cursor DeepSeek Docker Gitkraken Harness Laravel Management

目录

©2026 mdo. 保留部分权利。