fix(player): login footer layout, lang switcher z-index, profile.settings i18n; dev Vite proxy reads API PORT
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
|
import { resolveApiDevTarget } from '../../scripts/dev-api-target.mjs';
|
||||||
import { visualizer } from 'rollup-plugin-visualizer';
|
import { visualizer } from 'rollup-plugin-visualizer';
|
||||||
import AutoImport from 'unplugin-auto-import/vite';
|
import AutoImport from 'unplugin-auto-import/vite';
|
||||||
import Components from 'unplugin-vue-components/vite';
|
import Components from 'unplugin-vue-components/vite';
|
||||||
@@ -8,6 +9,7 @@ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
|
|||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const analyze = process.env.ANALYZE === '1' || mode === 'analyze';
|
const analyze = process.env.ANALYZE === '1' || mode === 'analyze';
|
||||||
|
const apiTarget = resolveApiDevTarget();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -75,8 +77,8 @@ export default defineConfig(({ mode }) => {
|
|||||||
server: {
|
server: {
|
||||||
port: 5174,
|
port: 5174,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': { target: 'http://localhost:3000', changeOrigin: true },
|
'/api': { target: apiTarget, changeOrigin: true },
|
||||||
'/uploads': { target: 'http://localhost:3000', changeOrigin: true },
|
'/uploads': { target: apiTarget, changeOrigin: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -476,6 +476,7 @@ export default {
|
|||||||
place_failed: 'Bet failed',
|
place_failed: 'Bet failed',
|
||||||
},
|
},
|
||||||
profile: {
|
profile: {
|
||||||
|
settings: 'Settings',
|
||||||
edit: 'Edit Profile',
|
edit: 'Edit Profile',
|
||||||
language: 'Language',
|
language: 'Language',
|
||||||
avatar: 'Avatar',
|
avatar: 'Avatar',
|
||||||
|
|||||||
@@ -482,6 +482,7 @@ export default {
|
|||||||
place_failed: 'Pertaruhan gagal',
|
place_failed: 'Pertaruhan gagal',
|
||||||
},
|
},
|
||||||
profile: {
|
profile: {
|
||||||
|
settings: 'Tetapan',
|
||||||
edit: 'Edit Profil',
|
edit: 'Edit Profil',
|
||||||
language: 'Bahasa',
|
language: 'Bahasa',
|
||||||
avatar: 'Avatar',
|
avatar: 'Avatar',
|
||||||
|
|||||||
@@ -476,6 +476,7 @@ export default {
|
|||||||
place_failed: '下注失败',
|
place_failed: '下注失败',
|
||||||
},
|
},
|
||||||
profile: {
|
profile: {
|
||||||
|
settings: '设置',
|
||||||
edit: '修改资料',
|
edit: '修改资料',
|
||||||
language: '语言',
|
language: '语言',
|
||||||
avatar: '选择头像',
|
avatar: '选择头像',
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ function goLogin() {
|
|||||||
<div class="login-lang">
|
<div class="login-lang">
|
||||||
<LocaleSwitcher compact />
|
<LocaleSwitcher compact />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="login-stack">
|
||||||
<form @submit.prevent="submit" class="login-form ps-gold-frame">
|
<form @submit.prevent="submit" class="login-form ps-gold-frame">
|
||||||
<h2 class="form-title">{{ t('auth.forgot_password_title') }}</h2>
|
<h2 class="form-title">{{ t('auth.forgot_password_title') }}</h2>
|
||||||
|
|
||||||
@@ -177,6 +178,7 @@ function goLogin() {
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -184,7 +186,7 @@ function goLogin() {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: max(12px, env(safe-area-inset-top));
|
top: max(12px, env(safe-area-inset-top));
|
||||||
right: 16px;
|
right: 16px;
|
||||||
z-index: 2;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-page {
|
.login-page {
|
||||||
@@ -217,14 +219,15 @@ function goLogin() {
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-page > * {
|
.login-stack {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 320px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ function goRegister() {
|
|||||||
<div class="login-lang">
|
<div class="login-lang">
|
||||||
<LocaleSwitcher compact />
|
<LocaleSwitcher compact />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="login-stack">
|
||||||
<form @submit.prevent="submit" class="login-form ps-gold-frame">
|
<form @submit.prevent="submit" class="login-form ps-gold-frame">
|
||||||
<div class="auth-tabs">
|
<div class="auth-tabs">
|
||||||
<button
|
<button
|
||||||
@@ -151,14 +152,17 @@ function goRegister() {
|
|||||||
<button type="submit" class="btn-login btn-gold-outline" :disabled="loading">
|
<button type="submit" class="btn-login btn-gold-outline" :disabled="loading">
|
||||||
{{ t('auth.login') }}
|
{{ t('auth.login') }}
|
||||||
</button>
|
</button>
|
||||||
|
</form>
|
||||||
|
<div class="login-actions">
|
||||||
<button type="button" class="btn-skip" @click="goRegister">
|
<button type="button" class="btn-skip" @click="goRegister">
|
||||||
{{ t('auth.go_register') }}
|
{{ t('auth.go_register') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
|
||||||
<button type="button" class="btn-skip-light" @click="continueBrowsing">
|
<button type="button" class="btn-skip-light" @click="continueBrowsing">
|
||||||
{{ t('auth.continue_browsing') }}
|
{{ t('auth.continue_browsing') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -166,7 +170,7 @@ function goRegister() {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: max(12px, env(safe-area-inset-top));
|
top: max(12px, env(safe-area-inset-top));
|
||||||
right: 16px;
|
right: 16px;
|
||||||
z-index: 2;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-page {
|
.login-page {
|
||||||
@@ -178,7 +182,7 @@ function goRegister() {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding: 34vh 20px calc(15vh + max(28px, env(safe-area-inset-bottom)));
|
padding: 34vh 20px max(20px, env(safe-area-inset-bottom));
|
||||||
background-color: var(--bg-body);
|
background-color: var(--bg-body);
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
@@ -199,20 +203,32 @@ function goRegister() {
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-page > * {
|
.login-stack {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
|
||||||
|
|
||||||
.login-form {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 0 14px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.auth-tabs {
|
.auth-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
@@ -315,14 +331,14 @@ label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-skip {
|
.btn-skip {
|
||||||
margin-top: 2px;
|
padding: 6px 14px;
|
||||||
padding: 8px 14px;
|
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: rgba(255, 255, 255, 0.55);
|
color: rgba(255, 255, 255, 0.55);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-skip:active {
|
.btn-skip:active {
|
||||||
@@ -330,11 +346,7 @@ label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-skip-light {
|
.btn-skip-light {
|
||||||
position: absolute;
|
padding: 6px 16px;
|
||||||
bottom: calc(20px + env(safe-area-inset-bottom));
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
padding: 8px 16px;
|
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: rgba(255, 255, 255, 0.45);
|
color: rgba(255, 255, 255, 0.45);
|
||||||
@@ -342,6 +354,7 @@ label {
|
|||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-skip-light:active {
|
.btn-skip-light:active {
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ const fieldError = () => {
|
|||||||
<div class="login-lang">
|
<div class="login-lang">
|
||||||
<LocaleSwitcher compact />
|
<LocaleSwitcher compact />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="login-stack">
|
||||||
<form @submit.prevent="submit" class="login-form ps-gold-frame" :class="{ 'is-busy': loading }">
|
<form @submit.prevent="submit" class="login-form ps-gold-frame" :class="{ 'is-busy': loading }">
|
||||||
<div v-if="loading" class="form-busy-overlay" aria-hidden="true">
|
<div v-if="loading" class="form-busy-overlay" aria-hidden="true">
|
||||||
<GoldSpinner :size="40" :active="true" />
|
<GoldSpinner :size="40" :active="true" />
|
||||||
@@ -199,14 +200,17 @@ const fieldError = () => {
|
|||||||
</span>
|
</span>
|
||||||
<span v-else>{{ t('auth.register_btn') }}</span>
|
<span v-else>{{ t('auth.register_btn') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
</form>
|
||||||
|
<div class="login-actions">
|
||||||
<button type="button" class="btn-skip" :disabled="loading" @click="goLogin">
|
<button type="button" class="btn-skip" :disabled="loading" @click="goLogin">
|
||||||
{{ t('auth.have_account') }}
|
{{ t('auth.have_account') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
<button type="button" class="btn-skip-light" :disabled="loading" @click="continueBrowsing">
|
||||||
<button type="button" class="btn-skip-light" @click="continueBrowsing">
|
|
||||||
{{ t('auth.continue_browsing') }}
|
{{ t('auth.continue_browsing') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -214,7 +218,7 @@ const fieldError = () => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: max(12px, env(safe-area-inset-top));
|
top: max(12px, env(safe-area-inset-top));
|
||||||
right: 16px;
|
right: 16px;
|
||||||
z-index: 2;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-page {
|
.login-page {
|
||||||
@@ -226,7 +230,7 @@ const fieldError = () => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding: 30vh 16px calc(10vh + max(20px, env(safe-area-inset-bottom)));
|
padding: 30vh 16px max(20px, env(safe-area-inset-bottom));
|
||||||
background-color: var(--bg-body);
|
background-color: var(--bg-body);
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
@@ -247,21 +251,33 @@ const fieldError = () => {
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-page > * {
|
.login-stack {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
|
||||||
|
|
||||||
.login-form {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 0 14px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.login-form.is-busy {
|
.login-form.is-busy {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -403,14 +419,14 @@ label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-skip {
|
.btn-skip {
|
||||||
margin-top: 2px;
|
padding: 6px 14px;
|
||||||
padding: 8px 14px;
|
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: rgba(255, 255, 255, 0.55);
|
color: rgba(255, 255, 255, 0.55);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-skip:active {
|
.btn-skip:active {
|
||||||
@@ -418,11 +434,7 @@ label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-skip-light {
|
.btn-skip-light {
|
||||||
position: absolute;
|
padding: 6px 16px;
|
||||||
bottom: calc(20px + env(safe-area-inset-bottom));
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
padding: 8px 16px;
|
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: rgba(255, 255, 255, 0.45);
|
color: rgba(255, 255, 255, 0.45);
|
||||||
@@ -430,6 +442,7 @@ label {
|
|||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-skip-light:active {
|
.btn-skip-light:active {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
|
import { resolveApiDevTarget } from '../../scripts/dev-api-target.mjs';
|
||||||
|
|
||||||
|
const apiTarget = resolveApiDevTarget();
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
@@ -37,8 +40,8 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
port: 5173,
|
port: 5173,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': { target: 'http://localhost:3000', changeOrigin: true },
|
'/api': { target: apiTarget, changeOrigin: true },
|
||||||
'/uploads': { target: 'http://localhost:3000', changeOrigin: true },
|
'/uploads': { target: apiTarget, changeOrigin: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
19
scripts/dev-api-target.mjs
Normal file
19
scripts/dev-api-target.mjs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { existsSync, readFileSync } from 'node:fs';
|
||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
|
|
||||||
|
/** 本地 dev:与 apps/api/.env 的 PORT 对齐,供 player/admin Vite 代理使用 */
|
||||||
|
export function resolveApiDevTarget() {
|
||||||
|
if (process.env.VITE_DEV_API_TARGET) return process.env.VITE_DEV_API_TARGET;
|
||||||
|
if (process.env.API_DEV_TARGET) return process.env.API_DEV_TARGET;
|
||||||
|
|
||||||
|
let port = '3000';
|
||||||
|
const envPath = resolve(repoRoot, 'apps/api/.env');
|
||||||
|
if (existsSync(envPath)) {
|
||||||
|
const match = readFileSync(envPath, 'utf8').match(/^PORT=(\d+)\s*$/m);
|
||||||
|
if (match) port = match[1];
|
||||||
|
}
|
||||||
|
return `http://localhost:${port}`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user