feat: implement ERP AI Assistant Phase 1
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>
This commit is contained in:
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ERP AI Assistant</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1750
frontend/package-lock.json
generated
Normal file
1750
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
frontend/package.json
Normal file
23
frontend/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "erp-ai-assistant-frontend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.3.8",
|
||||
"vue-router": "^4.2.5",
|
||||
"pinia": "^2.1.7",
|
||||
"axios": "^1.6.2",
|
||||
"element-plus": "^2.4.3",
|
||||
"@element-plus/icons-vue": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.5.1",
|
||||
"vite": "^5.0.4"
|
||||
}
|
||||
}
|
||||
22
frontend/src/App.vue
Normal file
22
frontend/src/App.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: 'Microsoft YaHei', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
52
frontend/src/api/index.js
Normal file
52
frontend/src/api/index.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: '/api/v1',
|
||||
timeout: 60000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// 响应拦截器
|
||||
api.interceptors.response.use(
|
||||
response => response.data,
|
||||
error => {
|
||||
console.error('API Error:', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 分析用户需求
|
||||
*/
|
||||
export const analyzeRequirement = (data) => {
|
||||
return api.post('/analyze', {
|
||||
input_type: 'natural_language',
|
||||
content: data.content,
|
||||
session_id: data.session_id
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成配置
|
||||
*/
|
||||
export const generateConfig = (data) => {
|
||||
return api.post('/generate', {
|
||||
session_id: data.session_id,
|
||||
requirements: data.requirements
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行配置
|
||||
*/
|
||||
export const executeConfig = (data) => {
|
||||
return api.post('/execute', {
|
||||
session_id: data.session_id,
|
||||
confirmed: data.confirmed || true,
|
||||
backup_enabled: data.backup_enabled || true
|
||||
})
|
||||
}
|
||||
|
||||
export default api
|
||||
14
frontend/src/main.js
Normal file
14
frontend/src/main.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(ElementPlus)
|
||||
|
||||
app.mount('#app')
|
||||
31
frontend/src/router/index.js
Normal file
31
frontend/src/router/index.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
component: () => import('../views/Layout.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: '/create'
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
name: 'CreateFunction',
|
||||
component: () => import('../views/CreateFunction.vue')
|
||||
},
|
||||
{
|
||||
path: 'history',
|
||||
name: 'History',
|
||||
component: () => import('../views/History.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
61
frontend/src/stores/function.js
Normal file
61
frontend/src/stores/function.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useFunctionStore = defineStore('function', () => {
|
||||
// 状态
|
||||
const currentSession = ref(null)
|
||||
const analysisResult = ref(null)
|
||||
const configResult = ref(null)
|
||||
const executeResult = ref(null)
|
||||
const loading = ref({
|
||||
analyzing: false,
|
||||
generating: false,
|
||||
executing: false
|
||||
})
|
||||
|
||||
// 重置状态
|
||||
const reset = () => {
|
||||
currentSession.value = null
|
||||
analysisResult.value = null
|
||||
configResult.value = null
|
||||
executeResult.value = null
|
||||
loading.value = {
|
||||
analyzing: false,
|
||||
generating: false,
|
||||
executing: false
|
||||
}
|
||||
}
|
||||
|
||||
// 设置会话
|
||||
const setSession = (sessionId) => {
|
||||
currentSession.value = sessionId
|
||||
}
|
||||
|
||||
// 设置分析结果
|
||||
const setAnalysisResult = (result) => {
|
||||
analysisResult.value = result
|
||||
}
|
||||
|
||||
// 设置配置结果
|
||||
const setConfigResult = (result) => {
|
||||
configResult.value = result
|
||||
}
|
||||
|
||||
// 设置执行结果
|
||||
const setExecuteResult = (result) => {
|
||||
executeResult.value = result
|
||||
}
|
||||
|
||||
return {
|
||||
currentSession,
|
||||
analysisResult,
|
||||
configResult,
|
||||
executeResult,
|
||||
loading,
|
||||
reset,
|
||||
setSession,
|
||||
setAnalysisResult,
|
||||
setConfigResult,
|
||||
setExecuteResult
|
||||
}
|
||||
})
|
||||
3
frontend/src/stores/index.js
Normal file
3
frontend/src/stores/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
export const pinia = createPinia()
|
||||
380
frontend/src/views/CreateFunction.vue
Normal file
380
frontend/src/views/CreateFunction.vue
Normal file
@@ -0,0 +1,380 @@
|
||||
<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>
|
||||
28
frontend/src/views/History.vue
Normal file
28
frontend/src/views/History.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="history">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>历史记录</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-empty description="暂无历史记录" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.history {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
69
frontend/src/views/Layout.vue
Normal file
69
frontend/src/views/Layout.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<el-container class="layout-container">
|
||||
<el-header class="header">
|
||||
<div class="logo">ERP智能助手</div>
|
||||
<div class="user-info">用户: Admin</div>
|
||||
</el-header>
|
||||
|
||||
<el-container>
|
||||
<el-aside width="200px" class="sidebar">
|
||||
<el-menu
|
||||
:default-active="$route.path"
|
||||
router
|
||||
>
|
||||
<el-menu-item index="/create">
|
||||
<el-icon><Edit /></el-icon>
|
||||
<span>新建功能</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/history">
|
||||
<el-icon><Document /></el-icon>
|
||||
<span>历史记录</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
<el-main class="main-content">
|
||||
<router-view />
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Edit, Document } from '@element-plus/icons-vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.layout-container {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #409EFF;
|
||||
color: white;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background: #f5f7fa;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
background: #f0f2f5;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
17
frontend/vite.config.js
Normal file
17
frontend/vite.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
host: '0.0.0.0', // 允许局域网访问
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8000',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user