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 地址由当前已启动的前端实例自动确定,这一步只需要配置并检测后端端口。运行端口配置
-
@@ -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({