Files
X-Financial/web/src/views/SetupView.vue
caoxiaozhu 619281afc3 feat: 完善系统配置、安全增强与知识库功能
- .env.example: API基础路径改为相对路径 /api/v1,支持代理转发
- README.md: 完善项目结构与启动说明文档
- docker-compose.yml: 新增Docker编排配置,支持容器化部署
- docker/: 新增Docker部署相关文档与配置

- server_start.sh: 重构启动脚本,添加容器环境检测、隔离虚拟环境路径、环境变量覆盖机制
- deps.py: 完善API依赖注入,增强权限验证逻辑
- admin_secret.py: 优化管理员密钥加密存储与验证
- config.py: 扩展配置管理,支持多环境变量绑定
- security.py: 增强安全模块,完善加密与认证机制
- db/base.py: 优化数据库基础架构与连接管理
- main.py: 更新应用入口,整合新模块路由
- models/: 完善系统模型配置,支持模型设置持久化
- repositories/settings.py: 优化设置仓储层,增强数据持久化
- services/settings.py: 重构设置服务,精简代码结构
- router.py: 更新API路由配置

- endpoints/knowledge.py: 新增知识库API端点
- schemas/knowledge.py: 新增知识库数据模型
- services/knowledge.py: 新增知识库业务逻辑
- storage/knowledge/.index.json: 知识库索引存储

- api.js: 完善API服务层,增强错误处理
- bootstrap.js: 优化前端初始化与引导流程
- useSetupView.js / useSystemState.js: 重构组合式函数
- TopBar.vue: 优化顶部导航栏组件
- SettingsView.vue: 重构设置页面UI,增强用户体验
- SetupView.vue / SetupRouteView.vue: 完善引导流程页面
- PoliciesView.vue: 优化策略视图组件
- vite.config.js: 更新Vite构建配置
- web_start.sh: 完善前端启动脚本
- views/scripts/: 优化各业务视图JS逻辑

- settings-view.css: 重构设置页面样式
- setup-view.css: 完善引导页样式
- policies-view.css: 优化策略页样式

- test_auth_service.py: 完善认证服务测试
- test_settings_persistence.py: 增强设置持久化测试
- document/: 新增开发文档与工作日志
2026-05-09 03:04:40 +00:00

377 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<main class="setup-page">
<aside class="setup-context">
<div class="setup-brand">
<div class="setup-brand-mark" aria-hidden="true">
<span class="setup-brand-ring"></span>
<span class="setup-brand-core">XF</span>
</div>
<div>
<p class="setup-kicker">INITIAL SETUP</p>
<h1>初始化配置</h1>
</div>
</div>
<p class="setup-lead">
先完成 4 个必要步骤再进入主登录界面扩展服务当前不参与初始化完成条件
</p>
<nav class="setup-nav" aria-label="初始化步骤">
<button
v-for="section in sections"
:key="section.id"
class="setup-nav-item"
:class="{ 'is-active': activeSection === section.id, 'is-complete': section.complete }"
type="button"
@click="goToSection(section.id)"
>
<span class="setup-nav-index">{{ section.index }}</span>
<span class="setup-nav-copy">
<strong>{{ section.title }}</strong>
<small>{{ section.desc }}</small>
</span>
<i v-if="section.complete" class="pi pi-check setup-nav-check"></i>
</button>
</nav>
<div class="setup-progress">
<strong>{{ completionCount }} / {{ sections.length }} 已完成</strong>
<p>企业信息管理员安全运行端口数据库连接都通过后左下角会自动出现完成初始化按钮</p>
</div>
<div v-if="canSubmit" class="setup-complete">
<p>所有必要步骤已通过检测可以写入配置并进入登录界面</p>
<button class="primary-btn setup-complete-btn" type="button" :disabled="submitting" @click="submitForm">
<i :class="['pi', submitting ? 'pi-spin pi-spinner' : 'pi-check']"></i>
<span>{{ submitting ? '写入配置中...' : '完成初始化并进入登录' }}</span>
</button>
<p v-if="progressMessage" class="setup-complete-progress">
<i class="pi pi-spin pi-spinner"></i>
<span>{{ progressMessage }}</span>
</p>
</div>
</aside>
<section class="setup-panel">
<header class="setup-panel-head">
<div>
<p class="setup-kicker setup-kicker-light">{{ activeStep.index }}</p>
<h2>{{ activeStep.title }}</h2>
<p class="setup-panel-desc">{{ activeStep.desc }}</p>
</div>
<span class="setup-chip" :class="{ 'is-success': activeStep.complete }">
{{ activeStep.complete ? '已完成' : '待配置' }}
</span>
</header>
<div class="setup-form">
<section v-if="activeSection === 'company'" class="setup-stage">
<div class="section-head">
<h3>企业基础信息</h3>
<p>这里仅保留企业名称与企业编码不放管理员邮箱</p>
</div>
<div class="field-grid field-grid-2">
<label class="field">
<span>企业名称</span>
<input v-model.trim="form.company_name" type="text" placeholder="请输入企业名称" required />
</label>
<label class="field">
<span>企业编码</span>
<input v-model.trim="form.company_code" type="text" placeholder="例如 FIN" />
</label>
</div>
</section>
<section v-else-if="activeSection === 'admin'" class="setup-stage">
<div class="section-head">
<h3>管理员安全</h3>
<p>管理员邮箱账号和密码在这里配置密码不会写入 `.env`只会保存哈希后的密文</p>
</div>
<div class="field-grid field-grid-2">
<label class="field">
<span>管理员邮箱</span>
<input v-model.trim="form.admin_email" type="email" placeholder="admin@company.com" />
</label>
<label class="field">
<span>管理员账号</span>
<input v-model.trim="form.admin_username" type="text" placeholder="例如 superadmin" required />
</label>
<label class="field">
<span>管理员密码</span>
<input
v-model="form.admin_password"
type="password"
placeholder="请输入管理员密码"
autocomplete="new-password"
required
/>
</label>
<label class="field">
<span>确认密码</span>
<input
v-model="form.admin_password_confirm"
type="password"
placeholder="请再次输入管理员密码"
autocomplete="new-password"
required
/>
</label>
</div>
<p class="field-group-note">管理员密码当前暂定至少 5 </p>
</section>
<section v-else-if="activeSection === 'runtime'" class="setup-stage">
<div class="section-head">
<h3>运行端口配置</h3>
<p>Web 地址由当前已启动的前端实例自动确定这一步只需要配置并检测后端端口</p>
</div>
<div class="field-grid field-grid-2">
<label class="field">
<span>Server Host</span>
<input v-model.trim="form.server_host" type="text" placeholder="0.0.0.0" required />
</label>
<label class="field">
<span>Server Port</span>
<input v-model.number="form.server_port" type="number" min="1" max="65535" required />
</label>
</div>
<div class="setup-runtime">
<article v-for="item in runtimeEndpoints" :key="item.label">
<span>{{ item.label }}</span>
<strong>{{ item.value }}</strong>
</article>
</div>
</section>
<section v-else class="setup-stage">
<div class="section-head">
<h3>数据库连接</h3>
<p>这里检测 PostgreSQL 连接Redis 作为扩展服务暂时可选不影响完成初始化</p>
</div>
<div class="field-grid field-grid-2">
<label class="field">
<span>PostgreSQL Host</span>
<input v-model.trim="form.postgres_host" type="text" placeholder="127.0.0.1" required />
</label>
<label class="field">
<span>PostgreSQL Port</span>
<input v-model.number="form.postgres_port" type="number" min="1" max="65535" required />
</label>
<label class="field">
<span>数据库名称</span>
<input v-model.trim="form.postgres_db" type="text" placeholder="x_financial" required />
</label>
<label class="field">
<span>数据库用户</span>
<input v-model.trim="form.postgres_user" type="text" placeholder="postgres" required />
</label>
<label class="field field-span-2">
<span>数据库密码</span>
<input
v-model="form.postgres_password"
type="password"
placeholder="请输入数据库密码"
autocomplete="new-password"
required
/>
</label>
</div>
<div class="optional-block">
<div class="optional-block-head">
<strong>扩展服务</strong>
<span>可选</span>
</div>
<label class="field">
<span>Redis URL</span>
<input v-model.trim="form.redis_url" type="text" placeholder="redis://127.0.0.1:6379/0" />
</label>
</div>
</section>
<p v-if="currentTestMessage" :class="['setup-status', currentTestPassed ? 'is-success' : 'is-danger']">
{{ currentTestMessage }}
</p>
<p v-if="errorMessage" class="setup-error">{{ errorMessage }}</p>
<p v-if="submitHint" class="setup-gate">{{ submitHint }}</p>
<footer class="setup-actions">
<div class="setup-actions-right">
<button
v-if="showTestAction"
class="secondary-btn secondary-btn-strong"
type="button"
:disabled="!canTest"
@click="testSetup"
>
<i :class="testButtonIcon"></i>
<span>{{ testButtonLabel }}</span>
</button>
</div>
</footer>
</div>
</section>
</main>
<div v-if="startupVisible" class="setup-modal-backdrop" role="alertdialog" aria-modal="true">
<section class="setup-startup-modal" aria-label="后端启动进度">
<header class="setup-startup-head">
<div>
<p class="setup-kicker setup-kicker-light">BACKEND STARTUP</p>
<h2>正在完成系统启动</h2>
<span>{{ progressMessage || '正在准备后端服务...' }}</span>
</div>
<div class="setup-startup-spinner" aria-hidden="true">
<i v-if="!startupCountdownSeconds" class="pi pi-spin pi-spinner"></i>
<strong v-else>{{ startupCountdownSeconds }}</strong>
</div>
</header>
<div class="setup-startup-body">
<ol class="setup-startup-steps">
<li
v-for="step in startupSteps"
:key="step.id"
:class="['setup-startup-step', `is-${step.status || 'pending'}`]"
>
<i :class="startupStepIcon(step.status)"></i>
<div>
<strong>{{ step.label }}</strong>
<span>{{ step.detail }}</span>
</div>
</li>
</ol>
<section class="setup-startup-console" aria-label="后端启动日志">
<div class="setup-startup-console-head">
<strong>执行日志</strong>
<span>server/logs/bootstrap-backend.log</span>
</div>
<pre class="setup-startup-log">{{ startupLog || '等待后端启动输出...' }}</pre>
</section>
</div>
</section>
</div>
</template>
<script setup>
import { useSetupView } from '../composables/useSetupView.js'
const props = defineProps({
initialState: {
type: Object,
default: () => ({})
},
submitting: {
type: Boolean,
default: false
},
runtimeTesting: {
type: Boolean,
default: false
},
databaseTesting: {
type: Boolean,
default: false
},
runtimeTestPassed: {
type: Boolean,
default: false
},
databaseTestPassed: {
type: Boolean,
default: false
},
runtimeTestMessage: {
type: String,
default: ''
},
databaseTestMessage: {
type: String,
default: ''
},
errorMessage: {
type: String,
default: ''
},
progressMessage: {
type: String,
default: ''
},
startupCountdownSeconds: {
type: Number,
default: 0
},
startupLog: {
type: String,
default: ''
},
startupSteps: {
type: Array,
default: () => []
},
startupVisible: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['submit', 'runtime-test', 'database-test', 'runtime-dirty', 'database-dirty'])
const {
activeSection,
activeStep,
canSubmit,
canTest,
completionCount,
currentTestMessage,
currentTestPassed,
form,
goToSection,
runtimeEndpoints,
sections,
showTestAction,
submitForm,
submitHint,
testButtonIcon,
testButtonLabel,
testSetup
} = useSetupView(props, emit)
function startupStepIcon(status) {
if (status === 'success') {
return 'pi pi-check-circle'
}
if (status === 'error') {
return 'pi pi-times-circle'
}
if (status === 'running') {
return 'pi pi-spin pi-spinner'
}
return 'pi pi-circle'
}
</script>
<style scoped src="../assets/styles/views/setup-view.css"></style>