diff --git a/.env.example b/.env.example index b911344..37b9cba 100644 --- a/.env.example +++ b/.env.example @@ -13,9 +13,9 @@ VITE_COMPANY_CODE= VITE_ADMIN_EMAIL= # Admin login credentials are stored separately under server/.secrets/ -WEB_HOST=127.0.0.1 +WEB_HOST=0.0.0.0 WEB_PORT=5173 -VITE_WEB_HOST=127.0.0.1 +VITE_WEB_HOST=0.0.0.0 VITE_WEB_PORT=5173 SERVER_HOST=127.0.0.1 @@ -43,4 +43,4 @@ SQLALCHEMY_ECHO=false REDIS_URL= VITE_REDIS_URL= -CORS_ORIGINS='["http://127.0.0.1:5173","http://localhost:5173"]' +CORS_ORIGINS='["http://127.0.0.1:5173","http://localhost:5173","http://0.0.0.0:5173"]' diff --git a/server/src/app/core/config.py b/server/src/app/core/config.py index 504c8ee..e0b6002 100644 --- a/server/src/app/core/config.py +++ b/server/src/app/core/config.py @@ -27,7 +27,7 @@ class Settings(BaseSettings): company_code: str = Field(default="", alias="COMPANY_CODE") admin_email: str = Field(default="", alias="ADMIN_EMAIL") - web_host: str = Field(default="127.0.0.1", alias="WEB_HOST") + web_host: str = Field(default="0.0.0.0", alias="WEB_HOST") web_port: int = Field(default=5173, alias="WEB_PORT") app_host: str = Field(default="127.0.0.1", alias="SERVER_HOST") app_port: int = Field(default=8000, alias="SERVER_PORT") diff --git a/server/start.sh b/server/start.sh old mode 100644 new mode 100755 diff --git a/start.sh b/start.sh old mode 100644 new mode 100755 diff --git a/web/package.json b/web/package.json index db45194..59a3318 100644 --- a/web/package.json +++ b/web/package.json @@ -4,10 +4,10 @@ "version": "0.1.0", "type": "module", "scripts": { - "start": "vite --host 127.0.0.1", - "dev": "vite --host 127.0.0.1", + "start": "vite --host 0.0.0.0", + "dev": "vite --host 0.0.0.0", "build": "vite build", - "preview": "vite preview --host 127.0.0.1" + "preview": "vite preview --host 0.0.0.0" }, "dependencies": { "@primevue/themes": "^4.5.4", diff --git a/web/src/assets/styles/views/setup-view.css b/web/src/assets/styles/views/setup-view.css index 345433a..25198a6 100644 --- a/web/src/assets/styles/views/setup-view.css +++ b/web/src/assets/styles/views/setup-view.css @@ -426,46 +426,6 @@ text-shadow: 0 2px 16px rgba(4, 9, 7, 0.22); } -.setup-summary-grid { - display: grid; - gap: 12px; -} - -.setup-summary-item { - padding: 16px 18px; - border: 1px solid rgba(16, 185, 129, 0.14); - border-radius: 8px; - display: flex; - justify-content: space-between; - align-items: center; - gap: 14px; - background: rgba(248, 250, 252, 0.9); -} - -.setup-summary-item strong { - display: block; - color: #0f172a; - font-size: 14px; -} - -.setup-summary-item span { - display: block; - margin-top: 4px; - color: #64748b; - font-size: 12px; - line-height: 1.55; -} - -.setup-summary-item .pi-check-circle { - color: #10b981; - font-size: 18px; -} - -.setup-summary-item .pi-clock { - color: #f59e0b; - font-size: 18px; -} - .setup-error { margin-top: 22px; padding: 14px 16px; diff --git a/web/src/composables/useSetupView.js b/web/src/composables/useSetupView.js index 7f23dd2..4dd3a3d 100644 --- a/web/src/composables/useSetupView.js +++ b/web/src/composables/useSetupView.js @@ -1,6 +1,25 @@ 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 createForm(initialState) { + const currentWeb = readCurrentWebEndpoint(initialState) + return { company_name: initialState?.company?.name || '', company_code: initialState?.company?.code || '', @@ -8,8 +27,8 @@ function createForm(initialState) { admin_username: '', admin_password: '', admin_password_confirm: '', - web_host: initialState?.web?.host || '127.0.0.1', - web_port: initialState?.web?.port || 5173, + web_host: currentWeb.host, + web_port: currentWeb.port, server_host: initialState?.server?.host || '127.0.0.1', server_port: initialState?.server?.port || 8000, postgres_host: initialState?.database?.host || '127.0.0.1', @@ -22,6 +41,13 @@ function createForm(initialState) { } 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(), @@ -29,8 +55,8 @@ function buildPayload(form) { admin_username: form.admin_username.trim(), admin_password: String(form.admin_password || ''), admin_password_confirm: String(form.admin_password_confirm || ''), - web_host: form.web_host.trim(), - web_port: Number(form.web_port), + web_host: currentWeb.host, + web_port: currentWeb.port, server_host: form.server_host.trim(), server_port: Number(form.server_port), postgres_host: form.postgres_host.trim(), @@ -44,8 +70,6 @@ function buildPayload(form) { function buildRuntimeFingerprint(form) { return JSON.stringify({ - web_host: form.web_host.trim(), - web_port: String(form.web_port).trim(), server_host: form.server_host.trim(), server_port: String(form.server_port).trim() }) @@ -112,9 +136,7 @@ export function useSetupView(props, emit) { }) const runtimeInputsReady = computed(() => { return Boolean( - form.web_host.trim() && - String(form.web_port).trim() && - form.server_host.trim() && + form.server_host.trim() && String(form.server_port).trim() ) }) @@ -151,7 +173,7 @@ export function useSetupView(props, emit) { id: 'runtime', index: '03', title: '运行端口', - desc: '单独检测 Web 与后端端口占用。', + desc: 'Web 端口跟随当前启动实例,只检测后端端口占用。', complete: runtimeReady.value }, { @@ -168,38 +190,15 @@ export function useSetupView(props, emit) { const runtimeEndpoints = computed(() => [ { - label: 'Web', + label: 'Web 当前访问', value: `${form.web_host}:${form.web_port}` }, { - label: 'Server', + label: 'Server 待启动', value: `${form.server_host}:${form.server_port}` } ]) - const summaryItems = computed(() => [ - { - label: '企业信息', - detail: form.company_name.trim() || '未完成', - complete: companyReady.value - }, - { - label: '管理员安全', - detail: form.admin_username.trim() || form.admin_email.trim() || '未完成', - complete: adminReady.value - }, - { - label: '运行端口', - detail: `${form.web_host}:${form.web_port} / ${form.server_host}:${form.server_port}`, - complete: runtimeReady.value - }, - { - label: '数据库', - detail: `${form.postgres_host}:${form.postgres_port}/${form.postgres_db}`, - complete: databaseReady.value - } - ]) - const currentTestMessage = computed(() => { if (activeSection.value === 'runtime') { return props.runtimeTestMessage @@ -290,7 +289,7 @@ export function useSetupView(props, emit) { if (activeSection.value === 'runtime') { if (!runtimeInputsReady.value) { - return '请先填写 Web 与 Server 的主机和端口。' + return '请先填写 Server 的主机和端口。' } if (!props.runtimeTestPassed) { @@ -375,7 +374,6 @@ export function useSetupView(props, emit) { showTestAction, submitForm, submitHint, - summaryItems, testButtonIcon, testButtonLabel, testSetup diff --git a/web/src/composables/useSystemState.js b/web/src/composables/useSystemState.js index 06e21d4..159084a 100644 --- a/web/src/composables/useSystemState.js +++ b/web/src/composables/useSystemState.js @@ -38,7 +38,7 @@ function readClientBootstrapState() { admin_email: env.VITE_ADMIN_EMAIL || '' }, web: { - host: env.VITE_WEB_HOST || '127.0.0.1', + host: env.VITE_WEB_HOST || '0.0.0.0', port: Number(env.VITE_WEB_PORT || 5173) }, server: { diff --git a/web/src/views/SetupView.vue b/web/src/views/SetupView.vue index 1dcdab0..3c7fdb5 100644 --- a/web/src/views/SetupView.vue +++ b/web/src/views/SetupView.vue @@ -126,20 +126,10 @@

运行端口配置

-

这一步只检测 Web 和 Server 端口占用情况,不检测数据库。

+

Web 地址由当前已启动的前端实例自动确定,这一步只需要配置并检测后端端口。

- - - -
-
-
-
- {{ item.label }} - {{ item.detail }} -
- -
-

@@ -306,7 +287,6 @@ const { showTestAction, submitForm, submitHint, - summaryItems, testButtonIcon, testButtonLabel, testSetup diff --git a/web/start.sh b/web/start.sh old mode 100644 new mode 100755 index 7bf9a7a..a080bb1 --- a/web/start.sh +++ b/web/start.sh @@ -24,7 +24,7 @@ if [ -f "$ROOT_ENV_FILE" ]; then set +a fi -WEB_HOST="${WEB_HOST:-127.0.0.1}" +WEB_HOST="${WEB_HOST:-0.0.0.0}" WEB_PORT="${WEB_PORT:-5173}" export VITE_SETUP_COMPLETED="${SETUP_COMPLETED:-false}" diff --git a/web/vite.config.js b/web/vite.config.js index 4b45029..84ec5e4 100644 --- a/web/vite.config.js +++ b/web/vite.config.js @@ -309,7 +309,7 @@ function normalizeState(env) { configured: Boolean(readAdminSecret()) }, web: { - host: env.WEB_HOST || '127.0.0.1', + host: env.WEB_HOST || '0.0.0.0', port: Number(env.WEB_PORT || 5173) }, server: { @@ -350,7 +350,6 @@ function sendJson(res, statusCode, payload) { function validateRuntimePayload(payload) { const fields = [ - ['web_host', 'Web Host'], ['server_host', 'Server Host'] ] @@ -361,7 +360,6 @@ function validateRuntimePayload(payload) { } const portFields = [ - ['web_port', 'Web Port'], ['server_port', 'Server Port'] ] @@ -376,6 +374,14 @@ function validateRuntimePayload(payload) { return '' } +function resolveRuntimePayload(payload, currentEnv) { + return { + ...payload, + web_host: String(payload.web_host || currentEnv.WEB_HOST || '0.0.0.0').trim(), + web_port: Number(payload.web_port || currentEnv.WEB_PORT || 5173) + } +} + function validateDatabasePayload(payload) { const fields = [ ['postgres_host', 'PostgreSQL Host'], @@ -444,13 +450,6 @@ function validateSetupPayload(payload) { return validateIdentityPayload(payload) || validateRuntimePayload(payload) || validateDatabasePayload(payload) } -function canReuseCurrentWebPort(payload, currentEnv) { - return ( - Number(payload.web_port) === Number(currentEnv.WEB_PORT || 5173) && - hostsConflict(String(payload.web_host || '').trim(), currentEnv.WEB_HOST || '127.0.0.1') - ) -} - async function assertPortAvailable(host, port) { await new Promise((resolve, reject) => { const tester = net.createServer() @@ -468,7 +467,7 @@ async function assertPortAvailable(host, port) { }) } -async function testRuntimePorts(payload, currentEnv) { +async function testRuntimePorts(payload) { const webPort = Number(payload.web_port) const serverPort = Number(payload.server_port) const webHost = String(payload.web_host || '').trim() @@ -478,14 +477,6 @@ async function testRuntimePorts(payload, currentEnv) { throw new Error('Web 与 Server 不能使用同一个主机与端口组合。') } - if (!canReuseCurrentWebPort(payload, currentEnv)) { - try { - await assertPortAvailable(webHost, webPort) - } catch { - throw new Error(`Web 端口 ${webHost}:${webPort} 已被占用。`) - } - } - try { await assertPortAvailable(serverHost, serverPort) } catch { @@ -569,7 +560,7 @@ function localSetupPlugin() { return } - const payload = await readJsonBody(req) + const payload = resolveRuntimePayload(await readJsonBody(req), readEnvState()) const validationError = validateRuntimePayload(payload) if (validationError) { @@ -578,8 +569,8 @@ function localSetupPlugin() { } try { - await testRuntimePorts(payload, readEnvState()) - sendJson(res, 200, { ok: true, detail: '端口占用检测通过。' }) + await testRuntimePorts(payload) + sendJson(res, 200, { ok: true, detail: 'Server 端口占用检测通过。' }) } catch (error) { sendJson(res, 400, { ok: false, @@ -636,7 +627,8 @@ function localSetupPlugin() { return } - const payload = await readJsonBody(req) + const currentEnv = readEnvState() + const payload = resolveRuntimePayload(await readJsonBody(req), currentEnv) const validationError = validateSetupPayload(payload) if (validationError) { @@ -645,7 +637,7 @@ function localSetupPlugin() { } try { - await testRuntimePorts(payload, readEnvState()) + await testRuntimePorts(payload) await testDatabaseConnection(payload) } catch (error) { sendJson(res, 400, { @@ -656,7 +648,6 @@ function localSetupPlugin() { persistAdminCredentials(payload) - const currentEnv = readEnvState() const apiBaseUrl = buildApiBaseUrl(payload, currentEnv) updateEnvFile({