Backend (FastAPI + SQLAlchemy + Claude API + RAG): - Config management with Pydantic v2 - Database engine with connection pooling and SQL injection prevention - AI engine with Claude API integration (support custom base URL) - RAG engine with ChromaDB and sentence-transformers - Requirement analysis service - Config generation service - Executor engine with SQL validation - REST API endpoints: /analyze, /generate, /execute Frontend (Vue 3 + Element Plus + Pinia): - Complete 3-step workflow: analyze → generate → execute - Step indicator with progress visualization - Analysis result display with field table - SQL preview with monospace font - Execute confirmation dialog with safety warning - Execution result display - State management with Pinia - API service integration Security: - SQL injection prevention with parameterized queries - Dangerous SQL operation blocking - Database password URL encoding - Transaction auto-rollback - Pydantic config validation Features: - Natural language requirement analysis - Automated SQL configuration generation - Safe execution with human review - LAN access support - Custom Claude API endpoint support Documentation: - README with quick start guide - Quick start guide - LAN access configuration - Dependency fixes guide - Claude API configuration - Git operation guide - Implementation report Dependencies fixed: - numpy<2.0.0 for chromadb compatibility - sentence-transformers==2.7.0 for huggingface_hub compatibility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
380 lines
9.8 KiB
Vue
380 lines
9.8 KiB
Vue
<template>
|
||
<div class="create-function">
|
||
<!-- 步骤指示器 -->
|
||
<el-steps :active="currentStep" finish-status="success" simple class="steps">
|
||
<el-step title="需求分析" />
|
||
<el-step title="配置生成" />
|
||
<el-step title="执行配置" />
|
||
</el-steps>
|
||
|
||
<!-- 步骤 1: 需求输入 -->
|
||
<el-card v-if="currentStep === 0" class="section-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>步骤 1: 输入需求</span>
|
||
</div>
|
||
</template>
|
||
|
||
<el-form :model="form" label-width="120px">
|
||
<el-form-item label="需求描述">
|
||
<el-input
|
||
v-model="form.requirement"
|
||
type="textarea"
|
||
:rows="6"
|
||
placeholder="请输入功能需求,例如:创建一个销售订单管理页面,包含订单号、客户、订单日期、金额等字段"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item>
|
||
<el-button
|
||
type="primary"
|
||
@click="handleAnalyze"
|
||
:loading="functionStore.loading.analyzing"
|
||
:disabled="!form.requirement.trim()"
|
||
>
|
||
开始分析需求
|
||
</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- 步骤 2: 分析结果 -->
|
||
<el-card v-if="functionStore.analysisResult" class="section-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>需求分析结果</span>
|
||
<el-tag type="success">已完成</el-tag>
|
||
</div>
|
||
</template>
|
||
|
||
<el-descriptions :column="2" border>
|
||
<el-descriptions-item label="功能名称">
|
||
{{ functionStore.analysisResult.功能名称 || '-' }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="功能类型">
|
||
{{ functionStore.analysisResult.功能类型 || '-' }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="功能号建议">
|
||
{{ functionStore.analysisResult.功能号建议 || '-' }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="窗体类型">
|
||
{{ functionStore.analysisResult.窗体类型 || '-' }}
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
|
||
<div v-if="functionStore.analysisResult.主表字段" class="field-list">
|
||
<h4>主表字段:</h4>
|
||
<el-table :data="functionStore.analysisResult.主表字段" border size="small">
|
||
<el-table-column prop="字段名" label="字段名" />
|
||
<el-table-column prop="字段类型" label="字段类型" />
|
||
<el-table-column prop="必填" label="必填">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.必填 ? 'danger' : 'info'" size="small">
|
||
{{ row.必填 ? '是' : '否' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<div class="button-group">
|
||
<el-button @click="resetAll">重新分析</el-button>
|
||
<el-button
|
||
type="primary"
|
||
@click="handleGenerate"
|
||
:loading="functionStore.loading.generating"
|
||
>
|
||
生成配置方案
|
||
</el-button>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 步骤 3: 配置方案 -->
|
||
<el-card v-if="functionStore.configResult" class="section-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>配置方案</span>
|
||
<el-tag type="success">已生成</el-tag>
|
||
</div>
|
||
</template>
|
||
|
||
<el-alert
|
||
title="请仔细检查以下 SQL 配置,确认无误后执行"
|
||
type="warning"
|
||
:closable="false"
|
||
show-icon
|
||
class="alert-box"
|
||
/>
|
||
|
||
<div v-if="functionStore.configResult.配置方案" class="config-section">
|
||
<h4>SQL 配置语句:</h4>
|
||
<el-input
|
||
v-model="sqlDisplay"
|
||
type="textarea"
|
||
:rows="15"
|
||
readonly
|
||
class="sql-textarea"
|
||
/>
|
||
</div>
|
||
|
||
<div class="button-group">
|
||
<el-button @click="backToAnalysis">返回修改</el-button>
|
||
<el-button
|
||
type="danger"
|
||
@click="showExecuteConfirm"
|
||
:loading="functionStore.loading.executing"
|
||
>
|
||
确认并执行
|
||
</el-button>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 执行结果 -->
|
||
<el-card v-if="functionStore.executeResult" class="section-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>执行结果</span>
|
||
<el-tag :type="functionStore.executeResult.status === 'success' ? 'success' : 'danger'">
|
||
{{ functionStore.executeResult.status === 'success' ? '执行成功' : '执行失败' }}
|
||
</el-tag>
|
||
</div>
|
||
</template>
|
||
|
||
<el-result
|
||
:icon="functionStore.executeResult.status === 'success' ? 'success' : 'error'"
|
||
:title="functionStore.executeResult.message"
|
||
>
|
||
<template #extra>
|
||
<el-button type="primary" @click="resetAll">创建新功能</el-button>
|
||
</template>
|
||
</el-result>
|
||
</el-card>
|
||
|
||
<!-- 执行确认对话框 -->
|
||
<el-dialog
|
||
v-model="executeDialogVisible"
|
||
title="确认执行"
|
||
width="500px"
|
||
>
|
||
<el-alert
|
||
type="warning"
|
||
:closable="false"
|
||
show-icon
|
||
>
|
||
<template #title>
|
||
<strong>警告:此操作将直接修改数据库</strong>
|
||
</template>
|
||
<div style="margin-top: 10px;">
|
||
<p>• 请确认已仔细检查所有 SQL 语句</p>
|
||
<p>• 建议在生产环境执行前先备份数据库</p>
|
||
<p>• 执行后可通过历史记录查看详情</p>
|
||
</div>
|
||
</el-alert>
|
||
|
||
<template #footer>
|
||
<el-button @click="executeDialogVisible = false">取消</el-button>
|
||
<el-button type="danger" @click="handleExecute">
|
||
确认执行
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { useFunctionStore } from '@/stores/function'
|
||
import { analyzeRequirement, generateConfig, executeConfig } from '@/api'
|
||
|
||
const functionStore = useFunctionStore()
|
||
|
||
const form = ref({
|
||
requirement: ''
|
||
})
|
||
|
||
const executeDialogVisible = ref(false)
|
||
|
||
// 当前步骤
|
||
const currentStep = computed(() => {
|
||
if (functionStore.executeResult) return 3
|
||
if (functionStore.configResult) return 2
|
||
if (functionStore.analysisResult) return 1
|
||
return 0
|
||
})
|
||
|
||
// SQL 显示
|
||
const sqlDisplay = computed(() => {
|
||
if (!functionStore.configResult?.配置方案?.sql_list) return ''
|
||
return functionStore.configResult.配置方案.sql_list.join('\n\n')
|
||
})
|
||
|
||
// 分析需求
|
||
const handleAnalyze = async () => {
|
||
if (!form.value.requirement.trim()) {
|
||
ElMessage.warning('请输入需求描述')
|
||
return
|
||
}
|
||
|
||
functionStore.loading.analyzing = true
|
||
|
||
try {
|
||
const result = await analyzeRequirement({
|
||
content: form.value.requirement,
|
||
session_id: null
|
||
})
|
||
|
||
functionStore.setSession(result.session_id)
|
||
functionStore.setAnalysisResult(result.data)
|
||
|
||
ElMessage.success('需求分析成功')
|
||
} catch (error) {
|
||
console.error('Analysis failed:', error)
|
||
ElMessage.error(
|
||
'需求分析失败: ' + (error.response?.data?.detail?.message || error.message)
|
||
)
|
||
} finally {
|
||
functionStore.loading.analyzing = false
|
||
}
|
||
}
|
||
|
||
// 生成配置
|
||
const handleGenerate = async () => {
|
||
if (!functionStore.currentSession || !functionStore.analysisResult) {
|
||
ElMessage.warning('请先完成需求分析')
|
||
return
|
||
}
|
||
|
||
functionStore.loading.generating = true
|
||
|
||
try {
|
||
const result = await generateConfig({
|
||
session_id: functionStore.currentSession,
|
||
requirements: functionStore.analysisResult
|
||
})
|
||
|
||
functionStore.setConfigResult(result.data)
|
||
|
||
ElMessage.success('配置生成成功')
|
||
} catch (error) {
|
||
console.error('Generation failed:', error)
|
||
ElMessage.error(
|
||
'配置生成失败: ' + (error.response?.data?.detail?.message || error.message)
|
||
)
|
||
} finally {
|
||
functionStore.loading.generating = false
|
||
}
|
||
}
|
||
|
||
// 显示执行确认对话框
|
||
const showExecuteConfirm = () => {
|
||
executeDialogVisible.value = true
|
||
}
|
||
|
||
// 执行配置
|
||
const handleExecute = async () => {
|
||
if (!functionStore.currentSession) {
|
||
ElMessage.warning('会话信息丢失,请重新开始')
|
||
return
|
||
}
|
||
|
||
executeDialogVisible.value = false
|
||
functionStore.loading.executing = true
|
||
|
||
try {
|
||
const result = await executeConfig({
|
||
session_id: functionStore.currentSession,
|
||
confirmed: true,
|
||
backup_enabled: true
|
||
})
|
||
|
||
functionStore.setExecuteResult(result)
|
||
|
||
if (result.status === 'success') {
|
||
ElMessage.success('配置执行成功')
|
||
} else {
|
||
ElMessage.error('配置执行失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('Execution failed:', error)
|
||
ElMessage.error(
|
||
'配置执行失败: ' + (error.response?.data?.detail?.message || error.message)
|
||
)
|
||
} finally {
|
||
functionStore.loading.executing = false
|
||
}
|
||
}
|
||
|
||
// 返回分析步骤
|
||
const backToAnalysis = () => {
|
||
functionStore.setConfigResult(null)
|
||
}
|
||
|
||
// 重置所有状态
|
||
const resetAll = () => {
|
||
functionStore.reset()
|
||
form.value.requirement = ''
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.create-function {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.steps {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.section-card {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.field-list {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.field-list h4 {
|
||
margin-bottom: 10px;
|
||
color: #606266;
|
||
}
|
||
|
||
.alert-box {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.config-section {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.config-section h4 {
|
||
margin-bottom: 10px;
|
||
color: #606266;
|
||
}
|
||
|
||
.sql-textarea {
|
||
font-family: 'Courier New', monospace;
|
||
}
|
||
|
||
.sql-textarea :deep(textarea) {
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.button-group {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-top: 20px;
|
||
justify-content: flex-end;
|
||
}
|
||
</style> |