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/: 新增开发文档与工作日志
This commit is contained in:
@@ -1,403 +1,403 @@
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
|
||||
function readCurrentWebEndpoint(initialState) {
|
||||
if (typeof window === 'undefined') {
|
||||
return {
|
||||
host: initialState?.web?.host || '0.0.0.0',
|
||||
port: Number(initialState?.web?.port || 5173)
|
||||
}
|
||||
}
|
||||
|
||||
const fallbackPort = Number(initialState?.web?.port || 5173)
|
||||
const port = Number(window.location.port || fallbackPort)
|
||||
|
||||
return {
|
||||
host: window.location.hostname || initialState?.web?.host || '0.0.0.0',
|
||||
port: Number.isInteger(port) && port > 0 ? port : fallbackPort
|
||||
}
|
||||
}
|
||||
|
||||
function shouldExposeServerHost() {
|
||||
if (typeof window === 'undefined') {
|
||||
return false
|
||||
}
|
||||
|
||||
const host = String(window.location.hostname || '').toLowerCase()
|
||||
return Boolean(host && host !== '127.0.0.1' && host !== 'localhost' && host !== '::1')
|
||||
}
|
||||
|
||||
function resolveInitialServerHost(initialState) {
|
||||
const host = String(initialState?.server?.host || '0.0.0.0').trim()
|
||||
const normalized = host.toLowerCase()
|
||||
|
||||
if (shouldExposeServerHost() && (normalized === '127.0.0.1' || normalized === 'localhost' || normalized === '::1')) {
|
||||
return '0.0.0.0'
|
||||
}
|
||||
|
||||
return host || '0.0.0.0'
|
||||
}
|
||||
|
||||
function createForm(initialState) {
|
||||
const currentWeb = readCurrentWebEndpoint(initialState)
|
||||
|
||||
return {
|
||||
company_name: initialState?.company?.name || '',
|
||||
company_code: initialState?.company?.code || '',
|
||||
admin_email: initialState?.company?.admin_email || '',
|
||||
admin_username: '',
|
||||
admin_password: '',
|
||||
admin_password_confirm: '',
|
||||
web_host: currentWeb.host,
|
||||
web_port: currentWeb.port,
|
||||
server_host: resolveInitialServerHost(initialState),
|
||||
server_port: initialState?.server?.port || 8000,
|
||||
postgres_host: initialState?.database?.host || '127.0.0.1',
|
||||
postgres_port: initialState?.database?.port || 5432,
|
||||
postgres_db: initialState?.database?.name || 'x_financial',
|
||||
postgres_user: initialState?.database?.username || 'postgres',
|
||||
postgres_password: '',
|
||||
redis_url: initialState?.redis?.url || ''
|
||||
}
|
||||
}
|
||||
|
||||
function buildPayload(form) {
|
||||
const currentWeb = readCurrentWebEndpoint({
|
||||
web: {
|
||||
host: form.web_host,
|
||||
port: form.web_port
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
company_name: form.company_name.trim(),
|
||||
company_code: form.company_code.trim(),
|
||||
admin_email: form.admin_email.trim(),
|
||||
admin_username: form.admin_username.trim(),
|
||||
admin_password: String(form.admin_password || ''),
|
||||
admin_password_confirm: String(form.admin_password_confirm || ''),
|
||||
web_host: currentWeb.host,
|
||||
web_port: currentWeb.port,
|
||||
server_host: shouldExposeServerHost() && ['127.0.0.1', 'localhost', '::1'].includes(form.server_host.trim().toLowerCase())
|
||||
? '0.0.0.0'
|
||||
: form.server_host.trim(),
|
||||
server_port: Number(form.server_port),
|
||||
postgres_host: form.postgres_host.trim(),
|
||||
postgres_port: Number(form.postgres_port),
|
||||
postgres_db: form.postgres_db.trim(),
|
||||
postgres_user: form.postgres_user.trim(),
|
||||
postgres_password: String(form.postgres_password || ''),
|
||||
redis_url: form.redis_url.trim()
|
||||
}
|
||||
}
|
||||
|
||||
function buildRuntimeFingerprint(form) {
|
||||
return JSON.stringify({
|
||||
server_host: form.server_host.trim(),
|
||||
server_port: String(form.server_port).trim()
|
||||
})
|
||||
}
|
||||
|
||||
function buildDatabaseFingerprint(form) {
|
||||
return JSON.stringify({
|
||||
postgres_host: form.postgres_host.trim(),
|
||||
postgres_port: String(form.postgres_port).trim(),
|
||||
postgres_db: form.postgres_db.trim(),
|
||||
postgres_user: form.postgres_user.trim(),
|
||||
postgres_password: String(form.postgres_password || ''),
|
||||
redis_url: form.redis_url.trim()
|
||||
})
|
||||
}
|
||||
|
||||
function isEmail(value) {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(String(value || '').trim())
|
||||
}
|
||||
|
||||
export function useSetupView(props, emit) {
|
||||
const form = reactive(createForm(props.initialState))
|
||||
const activeSection = ref('company')
|
||||
let syncingFromProps = false
|
||||
|
||||
watch(
|
||||
() => props.initialState,
|
||||
(state) => {
|
||||
syncingFromProps = true
|
||||
Object.assign(form, createForm(state))
|
||||
queueMicrotask(() => {
|
||||
syncingFromProps = false
|
||||
})
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => buildRuntimeFingerprint(form),
|
||||
(_value, oldValue) => {
|
||||
if (oldValue !== undefined && !syncingFromProps) {
|
||||
emit('runtime-dirty')
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => buildDatabaseFingerprint(form),
|
||||
(_value, oldValue) => {
|
||||
if (oldValue !== undefined && !syncingFromProps) {
|
||||
emit('database-dirty')
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const companyReady = computed(() => form.company_name.trim().length >= 2)
|
||||
const adminReady = computed(() => {
|
||||
return Boolean(
|
||||
isEmail(form.admin_email) &&
|
||||
form.admin_username.trim().length >= 4 &&
|
||||
String(form.admin_password || '').length >= 5 &&
|
||||
form.admin_password === form.admin_password_confirm
|
||||
)
|
||||
})
|
||||
const runtimeInputsReady = computed(() => {
|
||||
return Boolean(
|
||||
form.server_host.trim() &&
|
||||
String(form.server_port).trim()
|
||||
)
|
||||
})
|
||||
const databaseInputsReady = computed(() => {
|
||||
return Boolean(
|
||||
form.postgres_host.trim() &&
|
||||
String(form.postgres_port).trim() &&
|
||||
form.postgres_db.trim() &&
|
||||
form.postgres_user.trim() &&
|
||||
String(form.postgres_password || '').length > 0
|
||||
)
|
||||
})
|
||||
|
||||
const runtimeReady = computed(() => runtimeInputsReady.value && props.runtimeTestPassed)
|
||||
const databaseReady = computed(() => databaseInputsReady.value && props.databaseTestPassed)
|
||||
const finalReady = computed(() => companyReady.value && adminReady.value && runtimeReady.value && databaseReady.value)
|
||||
|
||||
const sections = computed(() => [
|
||||
{
|
||||
id: 'company',
|
||||
index: '01',
|
||||
title: '企业信息',
|
||||
desc: '填写企业名称与识别编码。',
|
||||
complete: companyReady.value
|
||||
},
|
||||
{
|
||||
id: 'admin',
|
||||
index: '02',
|
||||
title: '管理员安全',
|
||||
desc: '配置管理员邮箱、账号与密码。',
|
||||
complete: adminReady.value
|
||||
},
|
||||
{
|
||||
id: 'runtime',
|
||||
index: '03',
|
||||
title: '运行端口',
|
||||
desc: 'Web 端口跟随当前启动实例,只检测后端端口占用。',
|
||||
complete: runtimeReady.value
|
||||
},
|
||||
{
|
||||
id: 'database',
|
||||
index: '04',
|
||||
title: '数据库',
|
||||
desc: '检测 PostgreSQL 连接,Redis 暂时可选。',
|
||||
complete: databaseReady.value
|
||||
}
|
||||
])
|
||||
|
||||
const activeStep = computed(() => sections.value.find((section) => section.id === activeSection.value) || sections.value[0])
|
||||
const completionCount = computed(() => sections.value.filter((section) => section.complete).length)
|
||||
|
||||
const runtimeEndpoints = computed(() => [
|
||||
{
|
||||
label: 'Web 当前访问',
|
||||
value: `${form.web_host}:${form.web_port}`
|
||||
},
|
||||
{
|
||||
label: 'Server 待启动',
|
||||
value: `${form.server_host}:${form.server_port}`
|
||||
}
|
||||
])
|
||||
|
||||
const currentTestMessage = computed(() => {
|
||||
if (activeSection.value === 'runtime') {
|
||||
return props.runtimeTestMessage
|
||||
}
|
||||
|
||||
if (activeSection.value === 'database') {
|
||||
return props.databaseTestMessage
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
|
||||
const currentTestPassed = computed(() => {
|
||||
if (activeSection.value === 'runtime') {
|
||||
return props.runtimeTestPassed
|
||||
}
|
||||
|
||||
if (activeSection.value === 'database') {
|
||||
return props.databaseTestPassed
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
const showTestAction = computed(() => ['runtime', 'database'].includes(activeSection.value))
|
||||
const testButtonLabel = computed(() => {
|
||||
if (activeSection.value === 'runtime') {
|
||||
return props.runtimeTesting ? '检测中...' : '检测端口占用'
|
||||
}
|
||||
|
||||
if (activeSection.value === 'database') {
|
||||
return props.databaseTesting ? '检测中...' : '检测数据库连接'
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
const testButtonIcon = computed(() => {
|
||||
if ((activeSection.value === 'runtime' && props.runtimeTesting) || (activeSection.value === 'database' && props.databaseTesting)) {
|
||||
return 'pi pi-spin pi-spinner'
|
||||
}
|
||||
|
||||
return activeSection.value === 'runtime' ? 'pi pi-server' : 'pi pi-database'
|
||||
})
|
||||
|
||||
const canRuntimeTest = computed(() => Boolean(runtimeInputsReady.value && !props.submitting && !props.runtimeTesting && !props.databaseTesting))
|
||||
const canDatabaseTest = computed(() => Boolean(databaseInputsReady.value && !props.submitting && !props.runtimeTesting && !props.databaseTesting))
|
||||
const canTest = computed(() => {
|
||||
if (activeSection.value === 'runtime') {
|
||||
return canRuntimeTest.value
|
||||
}
|
||||
|
||||
if (activeSection.value === 'database') {
|
||||
return canDatabaseTest.value
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
const submitHint = computed(() => {
|
||||
if (activeSection.value === 'admin') {
|
||||
if (!form.admin_email.trim() && !form.admin_username.trim() && !String(form.admin_password || '').length) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!form.admin_email.trim()) {
|
||||
return '请填写管理员邮箱。'
|
||||
}
|
||||
|
||||
if (!isEmail(form.admin_email)) {
|
||||
return '管理员邮箱格式不正确。'
|
||||
}
|
||||
|
||||
if (form.admin_username.trim() && form.admin_username.trim().length < 4) {
|
||||
return '管理员账号至少 4 位。'
|
||||
}
|
||||
|
||||
if (String(form.admin_password || '').length > 0 && String(form.admin_password || '').length < 5) {
|
||||
return '管理员密码当前至少 5 位。'
|
||||
}
|
||||
|
||||
if (
|
||||
String(form.admin_password_confirm || '').length > 0 &&
|
||||
form.admin_password !== form.admin_password_confirm
|
||||
) {
|
||||
return '两次输入的管理员密码不一致。'
|
||||
}
|
||||
}
|
||||
|
||||
if (activeSection.value === 'runtime') {
|
||||
if (!runtimeInputsReady.value) {
|
||||
return '请先填写 Server 的主机和端口。'
|
||||
}
|
||||
|
||||
if (!props.runtimeTestPassed) {
|
||||
return '请先完成端口占用检测。'
|
||||
}
|
||||
}
|
||||
|
||||
if (activeSection.value === 'database') {
|
||||
if (!databaseInputsReady.value) {
|
||||
return '请先填写 PostgreSQL 连接信息。'
|
||||
}
|
||||
|
||||
if (!props.databaseTestPassed) {
|
||||
return '请先完成数据库连接检测。'
|
||||
}
|
||||
}
|
||||
|
||||
if (activeSection.value === 'company') {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!companyReady.value) {
|
||||
return '请先完成企业信息。'
|
||||
}
|
||||
|
||||
if (!adminReady.value) {
|
||||
return '请先完成管理员安全配置。'
|
||||
}
|
||||
|
||||
if (!runtimeReady.value) {
|
||||
return '请先完成运行端口检测。'
|
||||
}
|
||||
|
||||
if (!databaseReady.value) {
|
||||
return '请先完成数据库连接检测。'
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
|
||||
function goToSection(id) {
|
||||
activeSection.value = id
|
||||
}
|
||||
|
||||
function submitForm() {
|
||||
if (!finalReady.value || props.submitting) {
|
||||
return
|
||||
}
|
||||
|
||||
emit('submit', buildPayload(form))
|
||||
}
|
||||
|
||||
function testSetup() {
|
||||
if (!canTest.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const payload = buildPayload(form)
|
||||
|
||||
if (activeSection.value === 'runtime') {
|
||||
emit('runtime-test', payload)
|
||||
return
|
||||
}
|
||||
|
||||
if (activeSection.value === 'database') {
|
||||
emit('database-test', payload)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
activeSection,
|
||||
activeStep,
|
||||
canSubmit: finalReady,
|
||||
canTest,
|
||||
completionCount,
|
||||
currentTestMessage,
|
||||
currentTestPassed,
|
||||
form,
|
||||
goToSection,
|
||||
runtimeEndpoints,
|
||||
sections,
|
||||
showTestAction,
|
||||
submitForm,
|
||||
submitHint,
|
||||
testButtonIcon,
|
||||
testButtonLabel,
|
||||
testSetup
|
||||
}
|
||||
}
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
|
||||
function readCurrentWebEndpoint(initialState) {
|
||||
if (typeof window === 'undefined') {
|
||||
return {
|
||||
host: initialState?.web?.host || '0.0.0.0',
|
||||
port: Number(initialState?.web?.port || 5173)
|
||||
}
|
||||
}
|
||||
|
||||
const fallbackPort = Number(initialState?.web?.port || 5173)
|
||||
const port = Number(window.location.port || fallbackPort)
|
||||
|
||||
return {
|
||||
host: window.location.hostname || initialState?.web?.host || '0.0.0.0',
|
||||
port: Number.isInteger(port) && port > 0 ? port : fallbackPort
|
||||
}
|
||||
}
|
||||
|
||||
function shouldExposeServerHost() {
|
||||
if (typeof window === 'undefined') {
|
||||
return false
|
||||
}
|
||||
|
||||
const host = String(window.location.hostname || '').toLowerCase()
|
||||
return Boolean(host && host !== '127.0.0.1' && host !== 'localhost' && host !== '::1')
|
||||
}
|
||||
|
||||
function resolveInitialServerHost(initialState) {
|
||||
const host = String(initialState?.server?.host || '0.0.0.0').trim()
|
||||
const normalized = host.toLowerCase()
|
||||
|
||||
if (shouldExposeServerHost() && (normalized === '127.0.0.1' || normalized === 'localhost' || normalized === '::1')) {
|
||||
return '0.0.0.0'
|
||||
}
|
||||
|
||||
return host || '0.0.0.0'
|
||||
}
|
||||
|
||||
function createForm(initialState) {
|
||||
const currentWeb = readCurrentWebEndpoint(initialState)
|
||||
|
||||
return {
|
||||
company_name: initialState?.company?.name || '',
|
||||
company_code: initialState?.company?.code || '',
|
||||
admin_email: initialState?.company?.admin_email || '',
|
||||
admin_username: '',
|
||||
admin_password: '',
|
||||
admin_password_confirm: '',
|
||||
web_host: currentWeb.host,
|
||||
web_port: currentWeb.port,
|
||||
server_host: resolveInitialServerHost(initialState),
|
||||
server_port: initialState?.server?.port || 8000,
|
||||
postgres_host: initialState?.database?.host || '127.0.0.1',
|
||||
postgres_port: initialState?.database?.port || 5432,
|
||||
postgres_db: initialState?.database?.name || 'x_financial',
|
||||
postgres_user: initialState?.database?.username || 'postgres',
|
||||
postgres_password: '',
|
||||
redis_url: initialState?.redis?.url || ''
|
||||
}
|
||||
}
|
||||
|
||||
function buildPayload(form) {
|
||||
const currentWeb = readCurrentWebEndpoint({
|
||||
web: {
|
||||
host: form.web_host,
|
||||
port: form.web_port
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
company_name: form.company_name.trim(),
|
||||
company_code: form.company_code.trim(),
|
||||
admin_email: form.admin_email.trim(),
|
||||
admin_username: form.admin_username.trim(),
|
||||
admin_password: String(form.admin_password || ''),
|
||||
admin_password_confirm: String(form.admin_password_confirm || ''),
|
||||
web_host: currentWeb.host,
|
||||
web_port: currentWeb.port,
|
||||
server_host: shouldExposeServerHost() && ['127.0.0.1', 'localhost', '::1'].includes(form.server_host.trim().toLowerCase())
|
||||
? '0.0.0.0'
|
||||
: form.server_host.trim(),
|
||||
server_port: Number(form.server_port),
|
||||
postgres_host: form.postgres_host.trim(),
|
||||
postgres_port: Number(form.postgres_port),
|
||||
postgres_db: form.postgres_db.trim(),
|
||||
postgres_user: form.postgres_user.trim(),
|
||||
postgres_password: String(form.postgres_password || ''),
|
||||
redis_url: form.redis_url.trim()
|
||||
}
|
||||
}
|
||||
|
||||
function buildRuntimeFingerprint(form) {
|
||||
return JSON.stringify({
|
||||
server_host: form.server_host.trim(),
|
||||
server_port: String(form.server_port).trim()
|
||||
})
|
||||
}
|
||||
|
||||
function buildDatabaseFingerprint(form) {
|
||||
return JSON.stringify({
|
||||
postgres_host: form.postgres_host.trim(),
|
||||
postgres_port: String(form.postgres_port).trim(),
|
||||
postgres_db: form.postgres_db.trim(),
|
||||
postgres_user: form.postgres_user.trim(),
|
||||
postgres_password: String(form.postgres_password || ''),
|
||||
redis_url: form.redis_url.trim()
|
||||
})
|
||||
}
|
||||
|
||||
function isEmail(value) {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(String(value || '').trim())
|
||||
}
|
||||
|
||||
export function useSetupView(props, emit) {
|
||||
const form = reactive(createForm(props.initialState))
|
||||
const activeSection = ref('company')
|
||||
let syncingFromProps = false
|
||||
|
||||
watch(
|
||||
() => props.initialState,
|
||||
(state) => {
|
||||
syncingFromProps = true
|
||||
Object.assign(form, createForm(state))
|
||||
queueMicrotask(() => {
|
||||
syncingFromProps = false
|
||||
})
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => buildRuntimeFingerprint(form),
|
||||
(_value, oldValue) => {
|
||||
if (oldValue !== undefined && !syncingFromProps) {
|
||||
emit('runtime-dirty')
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => buildDatabaseFingerprint(form),
|
||||
(_value, oldValue) => {
|
||||
if (oldValue !== undefined && !syncingFromProps) {
|
||||
emit('database-dirty')
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const companyReady = computed(() => form.company_name.trim().length >= 2)
|
||||
const adminReady = computed(() => {
|
||||
return Boolean(
|
||||
isEmail(form.admin_email) &&
|
||||
form.admin_username.trim().length >= 4 &&
|
||||
String(form.admin_password || '').length >= 5 &&
|
||||
form.admin_password === form.admin_password_confirm
|
||||
)
|
||||
})
|
||||
const runtimeInputsReady = computed(() => {
|
||||
return Boolean(
|
||||
form.server_host.trim() &&
|
||||
String(form.server_port).trim()
|
||||
)
|
||||
})
|
||||
const databaseInputsReady = computed(() => {
|
||||
return Boolean(
|
||||
form.postgres_host.trim() &&
|
||||
String(form.postgres_port).trim() &&
|
||||
form.postgres_db.trim() &&
|
||||
form.postgres_user.trim() &&
|
||||
String(form.postgres_password || '').length > 0
|
||||
)
|
||||
})
|
||||
|
||||
const runtimeReady = computed(() => runtimeInputsReady.value && props.runtimeTestPassed)
|
||||
const databaseReady = computed(() => databaseInputsReady.value && props.databaseTestPassed)
|
||||
const finalReady = computed(() => companyReady.value && adminReady.value && runtimeReady.value && databaseReady.value)
|
||||
|
||||
const sections = computed(() => [
|
||||
{
|
||||
id: 'company',
|
||||
index: '01',
|
||||
title: '企业信息',
|
||||
desc: '填写企业名称与识别编码。',
|
||||
complete: companyReady.value
|
||||
},
|
||||
{
|
||||
id: 'admin',
|
||||
index: '02',
|
||||
title: '管理员安全',
|
||||
desc: '配置管理员邮箱、账号与密码。',
|
||||
complete: adminReady.value
|
||||
},
|
||||
{
|
||||
id: 'runtime',
|
||||
index: '03',
|
||||
title: '运行端口',
|
||||
desc: 'Web 端口跟随当前启动实例,只检测后端端口占用。',
|
||||
complete: runtimeReady.value
|
||||
},
|
||||
{
|
||||
id: 'database',
|
||||
index: '04',
|
||||
title: '数据库',
|
||||
desc: '检测 PostgreSQL 连接,Redis 暂时可选。',
|
||||
complete: databaseReady.value
|
||||
}
|
||||
])
|
||||
|
||||
const activeStep = computed(() => sections.value.find((section) => section.id === activeSection.value) || sections.value[0])
|
||||
const completionCount = computed(() => sections.value.filter((section) => section.complete).length)
|
||||
|
||||
const runtimeEndpoints = computed(() => [
|
||||
{
|
||||
label: 'Web 当前访问',
|
||||
value: `${form.web_host}:${form.web_port}`
|
||||
},
|
||||
{
|
||||
label: 'Server 待启动',
|
||||
value: `${form.server_host}:${form.server_port}`
|
||||
}
|
||||
])
|
||||
|
||||
const currentTestMessage = computed(() => {
|
||||
if (activeSection.value === 'runtime') {
|
||||
return props.runtimeTestMessage
|
||||
}
|
||||
|
||||
if (activeSection.value === 'database') {
|
||||
return props.databaseTestMessage
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
|
||||
const currentTestPassed = computed(() => {
|
||||
if (activeSection.value === 'runtime') {
|
||||
return props.runtimeTestPassed
|
||||
}
|
||||
|
||||
if (activeSection.value === 'database') {
|
||||
return props.databaseTestPassed
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
const showTestAction = computed(() => ['runtime', 'database'].includes(activeSection.value))
|
||||
const testButtonLabel = computed(() => {
|
||||
if (activeSection.value === 'runtime') {
|
||||
return props.runtimeTesting ? '检测中...' : '检测端口占用'
|
||||
}
|
||||
|
||||
if (activeSection.value === 'database') {
|
||||
return props.databaseTesting ? '检测中...' : '检测数据库连接'
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
const testButtonIcon = computed(() => {
|
||||
if ((activeSection.value === 'runtime' && props.runtimeTesting) || (activeSection.value === 'database' && props.databaseTesting)) {
|
||||
return 'pi pi-spin pi-spinner'
|
||||
}
|
||||
|
||||
return activeSection.value === 'runtime' ? 'pi pi-server' : 'pi pi-database'
|
||||
})
|
||||
|
||||
const canRuntimeTest = computed(() => Boolean(runtimeInputsReady.value && !props.submitting && !props.runtimeTesting && !props.databaseTesting))
|
||||
const canDatabaseTest = computed(() => Boolean(databaseInputsReady.value && !props.submitting && !props.runtimeTesting && !props.databaseTesting))
|
||||
const canTest = computed(() => {
|
||||
if (activeSection.value === 'runtime') {
|
||||
return canRuntimeTest.value
|
||||
}
|
||||
|
||||
if (activeSection.value === 'database') {
|
||||
return canDatabaseTest.value
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
const submitHint = computed(() => {
|
||||
if (activeSection.value === 'admin') {
|
||||
if (!form.admin_email.trim() && !form.admin_username.trim() && !String(form.admin_password || '').length) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!form.admin_email.trim()) {
|
||||
return '请填写管理员邮箱。'
|
||||
}
|
||||
|
||||
if (!isEmail(form.admin_email)) {
|
||||
return '管理员邮箱格式不正确。'
|
||||
}
|
||||
|
||||
if (form.admin_username.trim() && form.admin_username.trim().length < 4) {
|
||||
return '管理员账号至少 4 位。'
|
||||
}
|
||||
|
||||
if (String(form.admin_password || '').length > 0 && String(form.admin_password || '').length < 5) {
|
||||
return '管理员密码当前至少 5 位。'
|
||||
}
|
||||
|
||||
if (
|
||||
String(form.admin_password_confirm || '').length > 0 &&
|
||||
form.admin_password !== form.admin_password_confirm
|
||||
) {
|
||||
return '两次输入的管理员密码不一致。'
|
||||
}
|
||||
}
|
||||
|
||||
if (activeSection.value === 'runtime') {
|
||||
if (!runtimeInputsReady.value) {
|
||||
return '请先填写 Server 的主机和端口。'
|
||||
}
|
||||
|
||||
if (!props.runtimeTestPassed) {
|
||||
return '请先完成端口占用检测。'
|
||||
}
|
||||
}
|
||||
|
||||
if (activeSection.value === 'database') {
|
||||
if (!databaseInputsReady.value) {
|
||||
return '请先填写 PostgreSQL 连接信息。'
|
||||
}
|
||||
|
||||
if (!props.databaseTestPassed) {
|
||||
return '请先完成数据库连接检测。'
|
||||
}
|
||||
}
|
||||
|
||||
if (activeSection.value === 'company') {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!companyReady.value) {
|
||||
return '请先完成企业信息。'
|
||||
}
|
||||
|
||||
if (!adminReady.value) {
|
||||
return '请先完成管理员安全配置。'
|
||||
}
|
||||
|
||||
if (!runtimeReady.value) {
|
||||
return '请先完成运行端口检测。'
|
||||
}
|
||||
|
||||
if (!databaseReady.value) {
|
||||
return '请先完成数据库连接检测。'
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
|
||||
function goToSection(id) {
|
||||
activeSection.value = id
|
||||
}
|
||||
|
||||
function submitForm() {
|
||||
if (!finalReady.value || props.submitting) {
|
||||
return
|
||||
}
|
||||
|
||||
emit('submit', buildPayload(form))
|
||||
}
|
||||
|
||||
function testSetup() {
|
||||
if (!canTest.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const payload = buildPayload(form)
|
||||
|
||||
if (activeSection.value === 'runtime') {
|
||||
emit('runtime-test', payload)
|
||||
return
|
||||
}
|
||||
|
||||
if (activeSection.value === 'database') {
|
||||
emit('database-test', payload)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
activeSection,
|
||||
activeStep,
|
||||
canSubmit: finalReady,
|
||||
canTest,
|
||||
completionCount,
|
||||
currentTestMessage,
|
||||
currentTestPassed,
|
||||
form,
|
||||
goToSection,
|
||||
runtimeEndpoints,
|
||||
sections,
|
||||
showTestAction,
|
||||
submitForm,
|
||||
submitHint,
|
||||
testButtonIcon,
|
||||
testButtonLabel,
|
||||
testSetup
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user