| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932 |
- <template>
- <div class="register-page">
- <!-- 顶部导航 -->
- <div class="nav-header">
- <van-icon name="arrow-left" @click="goBack" />
- <span class="title">{{ $t('auth.register') }}</span>
- <span></span>
- </div>
- <!-- 注册表单 -->
- <div class="form-section">
- <!-- 注册方式切换 -->
- <div class="register-tabs">
- <div
- class="tab-item"
- :class="{ active: registerType === 'phone' }"
- @click="registerType = 'phone'"
- >
- {{ $t('auth.phone') }}
- </div>
- <div
- class="tab-item"
- :class="{ active: registerType === 'email' }"
- @click="registerType = 'email'"
- >
- {{ $t('auth.email') }}
- </div>
- </div>
- <!-- 手机号注册 -->
- <div v-if="registerType === 'phone'" class="form-content">
- <div class="input-group">
- <van-field
- v-model="phoneForm.phone"
- type="tel"
- :placeholder="$t('auth.pleaseInputPhone')"
- clearable
- >
- <template #left-icon>
- <div class="area-code" @click="showAreaCode = true">
- +{{ areaCode }}
- <van-icon name="arrow-down" />
- </div>
- </template>
- </van-field>
- </div>
- <div class="input-group">
- <van-field
- v-model="phoneForm.code"
- type="number"
- maxlength="6"
- :placeholder="$t('auth.pleaseInputVerifyCode')"
- >
- <template #button>
- <van-button
- size="small"
- type="primary"
- :disabled="countdown > 0"
- @click="sendPhoneCode"
- >
- {{ countdown > 0 ? `${countdown}s` : $t('auth.getCode') }}
- </van-button>
- </template>
- </van-field>
- </div>
- <div class="input-group">
- <van-field
- v-model="phoneForm.password"
- :type="showPassword ? 'text' : 'password'"
- :placeholder="$t('auth.pleaseInputPassword')"
- >
- <template #right-icon>
- <van-icon
- :name="showPassword ? 'eye-o' : 'closed-eye'"
- @click="showPassword = !showPassword"
- />
- </template>
- </van-field>
- </div>
- <div class="input-group">
- <van-field
- v-model="phoneForm.confirmPassword"
- :type="showConfirmPassword ? 'text' : 'password'"
- :placeholder="$t('auth.pleaseInputConfirmPassword')"
- >
- <template #right-icon>
- <van-icon
- :name="showConfirmPassword ? 'eye-o' : 'closed-eye'"
- @click="showConfirmPassword = !showConfirmPassword"
- />
- </template>
- </van-field>
- </div>
- <div class="input-group">
- <van-field
- v-model="phoneForm.inviteCode"
- :placeholder="$t('auth.inviteCodeOptional')"
- clearable
- >
- <template #left-icon>
- <van-icon name="friends-o" />
- </template>
- </van-field>
- </div>
- </div>
- <!-- 邮箱注册 -->
- <div v-if="registerType === 'email'" class="form-content">
- <div class="input-group">
- <van-field
- v-model="emailForm.email"
- type="email"
- :placeholder="$t('auth.pleaseInputEmail')"
- clearable
- >
- <template #left-icon>
- <van-icon name="envelop-o" />
- </template>
- </van-field>
- </div>
- <div class="input-group">
- <van-field
- v-model="emailForm.code"
- type="number"
- maxlength="6"
- :placeholder="$t('auth.pleaseInputVerifyCode')"
- >
- <template #button>
- <van-button
- size="small"
- type="primary"
- :disabled="countdown > 0"
- @click="sendEmailCode"
- >
- {{ countdown > 0 ? `${countdown}s` : $t('auth.getCode') }}
- </van-button>
- </template>
- </van-field>
- </div>
- <div class="input-group">
- <van-field
- v-model="emailForm.password"
- :type="showPassword ? 'text' : 'password'"
- :placeholder="$t('auth.pleaseInputPassword')"
- >
- <template #right-icon>
- <van-icon
- :name="showPassword ? 'eye-o' : 'closed-eye'"
- @click="showPassword = !showPassword"
- />
- </template>
- </van-field>
- </div>
- <div class="input-group">
- <van-field
- v-model="emailForm.confirmPassword"
- :type="showConfirmPassword ? 'text' : 'password'"
- :placeholder="$t('auth.pleaseInputConfirmPassword')"
- >
- <template #right-icon>
- <van-icon
- :name="showConfirmPassword ? 'eye-o' : 'closed-eye'"
- @click="showConfirmPassword = !showConfirmPassword"
- />
- </template>
- </van-field>
- </div>
- <div class="input-group">
- <van-field
- v-model="emailForm.inviteCode"
- :placeholder="$t('auth.inviteCodeOptional')"
- clearable
- >
- <template #left-icon>
- <van-icon name="friends-o" />
- </template>
- </van-field>
- </div>
- </div>
- <!-- 用户协议 -->
- <div class="agreement">
- <van-checkbox v-model="agreed" icon-size="16px">
- <span class="text">
- {{ $t('auth.agreeTerms') }}
- <span class="link" @click.stop="showTerms">{{ $t('auth.termsOfService') }}</span>
- {{ $t('auth.and') }}
- <span class="link" @click.stop="showPrivacy">{{ $t('auth.privacyPolicy') }}</span>
- </span>
- </van-checkbox>
- </div>
- <!-- 注册按钮 -->
- <van-button
- class="register-btn"
- type="primary"
- block
- :loading="loading"
- :disabled="!agreed"
- @click="handleRegister"
- >
- {{ $t('auth.register') }}
- </van-button>
- <!-- 第三方登录 -->
- <div class="third-party-section">
- <div class="divider">
- <span>{{ $t('auth.or') || '或' }}</span>
- </div>
- <!-- Google 登录按钮 -->
- <div class="oauth-btn google-btn" :class="{ loading: googleLoading }" @click="handleGoogleLogin">
- <svg class="google-icon" viewBox="0 0 24 24" width="20" height="20">
- <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
- <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
- <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
- <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
- </svg>
- <span>{{ $t('auth.googleLogin') || '使用 Google 登录' }}</span>
- <van-loading v-if="googleLoading" size="16px" color="#fff" />
- </div>
- <!-- Telegram 登录按钮 -->
- <div class="oauth-btn telegram-btn" @click="handleTelegramLogin" v-if="telegramBotName">
- <svg class="telegram-icon" viewBox="0 0 24 24" width="20" height="20">
- <path fill="#fff" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5.46 7.12l-1.67 6.14c-.13.46-.47.58-.95.36l-2.62-1.93-1.26 1.22c-.14.14-.26.26-.53.26l.19-2.67 4.84-4.37c.21-.19-.05-.29-.32-.12l-5.99 3.78-2.58-.81c-.56-.17-.57-.56.12-.83l10.1-3.9c.46-.17.87.11.67.87z"/>
- </svg>
- <span>{{ $t('auth.telegramLogin') || 'Telegram 登录' }}</span>
- </div>
- <!-- TikTok 登录按钮 -->
- <div class="oauth-btn tiktok-btn" :class="{ loading: tiktokLoading }" @click="handleTiktokLogin" v-if="tiktokClientKey">
- <svg class="tiktok-icon" viewBox="0 0 24 24" width="20" height="20">
- <path fill="#25F4EE" d="M16.6 5.82s.51.5 0 0A4.278 4.278 0 0 1 15.54 3h-3.09v12.4a2.592 2.592 0 0 1-2.59 2.5c-1.42 0-2.6-1.16-2.6-2.6 0-1.72 1.66-3.01 3.37-2.48V9.66c-3.45-.46-6.47 2.22-6.47 5.64 0 3.33 2.76 5.7 5.69 5.7 3.14 0 5.69-2.55 5.69-5.7V9.01a7.35 7.35 0 0 0 4.3 1.38V7.3s-1.88.09-3.24-1.48z"/>
- <path fill="#FE2C55" d="M17.6 5.82s.51.5 0 0A4.278 4.278 0 0 1 16.54 3h-3.09v12.4a2.592 2.592 0 0 1-2.59 2.5c-1.42 0-2.6-1.16-2.6-2.6 0-1.72 1.66-3.01 3.37-2.48V9.66c-3.45-.46-6.47 2.22-6.47 5.64 0 3.33 2.76 5.7 5.69 5.7 3.14 0 5.69-2.55 5.69-5.7V9.01a7.35 7.35 0 0 0 4.3 1.38V7.3s-1.88.09-3.24-1.48z"/>
- </svg>
- <span>{{ $t('auth.tiktokLogin') || 'TikTok 登录' }}</span>
- <van-loading v-if="tiktokLoading" size="16px" color="#fff" />
- </div>
- <!-- Zalo 登录按钮 -->
- <div class="oauth-btn zalo-btn" @click="handleZaloLogin">
- <svg class="zalo-icon" viewBox="0 0 24 24" width="20" height="20">
- <path fill="#0068FF" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5.46 7.12l-1.67 6.14c-.13.46-.47.58-.95.36l-2.62-1.93-1.26 1.22c-.14.14-.26.26-.53.26l.19-2.67 4.84-4.37c.21-.19-.05-.29-.32-.12l-5.99 3.78-2.58-.81c-.56-.17-.57-.56.12-.83l10.1-3.9c.46-.17.87.11.67.87z"/>
- </svg>
- <span>{{ $t('auth.zaloLogin') || '使用 Zalo 登录' }}</span>
- </div>
- </div>
- <!-- 登录入口 -->
- <div class="login-entry">
- {{ $t('auth.hasAccount') }}
- <span class="link" @click="goLogin">{{ $t('auth.goLogin') }}</span>
- </div>
- </div>
- <!-- 区号选择弹窗 -->
- <van-popup v-model:show="showAreaCode" position="bottom" round>
- <van-picker
- :columns="areaCodes"
- @confirm="onAreaCodeConfirm"
- @cancel="showAreaCode = false"
- show-toolbar
- />
- </van-popup>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, reactive, onMounted } from 'vue';
- import { useRouter, useRoute } from 'vue-router';
- import { useI18n } from 'vue-i18n';
- import { toast } from 'vue-sonner';
- import { requestRegister, requestSendCode, requestOAuthLogin, requestOAuthConfig } from '@/api/auth';
- import { useUserStore } from '@/store/modules/userStore';
- const { t } = useI18n();
- const router = useRouter();
- const route = useRoute();
- const userStore = useUserStore();
- const googleLoading = ref(false);
- const googleClientId = ref('');
- const telegramBotName = ref('');
- const telegramBotId = ref('');
- const tiktokLoading = ref(false);
- const tiktokClientKey = ref('');
- const registerType = ref<'phone' | 'email'>('phone');
- const showPassword = ref(false);
- const showConfirmPassword = ref(false);
- const loading = ref(false);
- const showAreaCode = ref(false);
- const areaCode = ref('84');
- const agreed = ref(false);
- const countdown = ref(0);
- const phoneForm = reactive({
- phone: '',
- code: '',
- password: '',
- confirmPassword: '',
- inviteCode: (route.query.smid as string) || ''
- });
- const emailForm = reactive({
- email: '',
- code: '',
- password: '',
- confirmPassword: '',
- inviteCode: (route.query.smid as string) || ''
- });
- const areaCodes = [
- { text: '+84 Vietnam', value: '84' },
- { text: '+62 Indonesia', value: '62' },
- { text: '+86 China', value: '86' },
- { text: '+1 USA', value: '1' }
- ];
- const onAreaCodeConfirm = ({ selectedValues }) => {
- areaCode.value = selectedValues[0];
- showAreaCode.value = false;
- };
- const startCountdown = () => {
- countdown.value = 60;
- const timer = setInterval(() => {
- countdown.value--;
- if (countdown.value <= 0) {
- clearInterval(timer);
- }
- }, 1000);
- };
- const sendPhoneCode = async () => {
- if (!phoneForm.phone) {
- toast.error(t('auth.pleaseInputPhone'));
- return;
- }
- try {
- const res = await requestSendCode({
- type: 'phone',
- account: phoneForm.phone,
- areaCode: areaCode.value,
- scene: 'register'
- });
- if (res.code === 200) {
- toast.success('验证码已发送');
- startCountdown();
- }
- } catch (e) {
- // error handled in http interceptor
- }
- };
- const sendEmailCode = async () => {
- if (!emailForm.email) {
- toast.error(t('auth.pleaseInputEmail'));
- return;
- }
- try {
- const res = await requestSendCode({
- type: 'email',
- account: emailForm.email,
- scene: 'register'
- });
- if (res.code === 200) {
- toast.success('验证码已发送');
- startCountdown();
- }
- } catch (e) {
- // error handled in http interceptor
- }
- };
- const validateForm = (form: typeof phoneForm | typeof emailForm, type: 'phone' | 'email') => {
- if (type === 'phone' && !form.phone) {
- toast.error(t('auth.pleaseInputPhone'));
- return false;
- }
- if (type === 'email' && !(form as typeof emailForm).email) {
- toast.error(t('auth.pleaseInputEmail'));
- return false;
- }
- if (!form.code) {
- toast.error(t('auth.pleaseInputVerifyCode'));
- return false;
- }
- if (!form.password) {
- toast.error(t('auth.pleaseInputPassword'));
- return false;
- }
- if (form.password.length < 6) {
- toast.error('密码至少6位');
- return false;
- }
- if (form.password !== form.confirmPassword) {
- toast.error(t('auth.passwordNotMatch'));
- return false;
- }
- return true;
- };
- const handleRegister = async () => {
- const form = registerType.value === 'phone' ? phoneForm : emailForm;
- if (!validateForm(form, registerType.value)) return;
- loading.value = true;
- try {
- const res = await requestRegister({
- type: registerType.value,
- account: registerType.value === 'phone' ? phoneForm.phone : emailForm.email,
- password: form.password,
- code: form.code,
- inviteCode: form.inviteCode,
- areaCode: registerType.value === 'phone' ? areaCode.value : undefined
- });
- if (res.code === 200) {
- localStorage.setItem('token', res.data.token);
- userStore.setUserInfo(res.data.user);
- toast.success(t('auth.registerSuccess'));
- router.replace('/home');
- }
- } finally {
- loading.value = false;
- }
- };
- const goBack = () => {
- router.back();
- };
- const goLogin = () => {
- router.push('/login');
- };
- const showTerms = () => {
- router.push('/terms');
- };
- const showPrivacy = () => {
- router.push('/privacy');
- };
- // 获取OAuth配置并初始化 Google Sign-In
- const initGoogleSignIn = async () => {
- try {
- const configRes = await requestOAuthConfig();
- if (configRes.code === 200) {
- if (configRes.data.googleClientId) {
- googleClientId.value = configRes.data.googleClientId;
- const script = document.createElement('script');
- script.src = 'https://accounts.google.com/gsi/client';
- script.async = true;
- script.defer = true;
- script.onload = () => {
- if (window.google && googleClientId.value) {
- window.google.accounts.id.initialize({
- client_id: googleClientId.value,
- callback: handleGoogleCallback,
- auto_select: false,
- cancel_on_tap_outside: true
- });
- }
- };
- document.head.appendChild(script);
- }
- if (configRes.data.telegramBotName) {
- telegramBotName.value = configRes.data.telegramBotName;
- telegramBotId.value = configRes.data.telegramBotId || '';
- }
- // 初始化 TikTok Login
- if (configRes.data.tiktokClientKey) {
- tiktokClientKey.value = configRes.data.tiktokClientKey;
- }
- }
- } catch (error) {
- console.warn('Failed to load OAuth config:', error);
- }
- };
- // Google 登录回调处理
- const handleGoogleCallback = async (response: any) => {
- if (!response.credential) {
- toast.error('Google 登录失败');
- return;
- }
- googleLoading.value = true;
- try {
- const payload = JSON.parse(atob(response.credential.split('.')[1]));
- const inviteCode = phoneForm.inviteCode || emailForm.inviteCode || (route.query.smid as string) || '';
- const res = await requestOAuthLogin({
- provider: 'google',
- openId: payload.sub,
- nickname: payload.name,
- avatar: payload.picture,
- email: payload.email,
- inviteCode
- });
- if (res.code === 200) {
- localStorage.setItem('token', res.data.token);
- userStore.setUserInfo(res.data.user);
- toast.success(t('auth.loginSuccess'));
- router.replace('/home');
- }
- } catch (error) {
- console.error('Google login error:', error);
- toast.error('Google 登录失败');
- } finally {
- googleLoading.value = false;
- }
- };
- const handleGoogleLogin = () => {
- if (!googleClientId.value) {
- toast.error('Google 登录未配置');
- return;
- }
- if (window.google) {
- window.google.accounts.id.prompt();
- } else {
- toast.error('Google SDK 加载失败');
- }
- };
- const handleZaloLogin = () => {
- toast.info('Zalo Login - Coming Soon');
- };
- // 初始化 Telegram 登录 - 加载 widget 脚本以获取 Telegram.Login.auth API
- // 点击 Telegram 登录按钮 - 直接打开 Telegram OAuth 弹窗
- const handleTelegramLogin = () => {
- if (!telegramBotId.value) {
- toast.error(t('auth.telegramNotConfigured') || 'Telegram 登录未配置');
- return;
- }
- // 支持传入完整 bot token (如 123456:ABC) 或纯数字 ID
- const botId = telegramBotId.value.split(':')[0];
- const origin = encodeURIComponent(window.location.origin);
- const authUrl = `https://oauth.telegram.org/auth?bot_id=${botId}&origin=${origin}&request_access=write`;
- const width = 550;
- const height = 470;
- const left = Math.round((screen.width - width) / 2);
- const top = Math.round((screen.height - height) / 2);
- const popup = window.open(
- authUrl,
- 'telegram_oauth',
- `width=${width},height=${height},left=${left},top=${top},toolbar=no,menubar=no,scrollbars=no`
- );
- const onMessage = (event: MessageEvent) => {
- if (event.origin !== 'https://oauth.telegram.org') return;
- try {
- let data = event.data;
- if (typeof data === 'string') {
- try { data = JSON.parse(data); } catch { return; }
- }
- if (data?.event === 'auth_result' && data?.result) {
- window.removeEventListener('message', onMessage);
- handleTelegramCallback(data.result);
- }
- } catch (e) {
- console.error('Telegram auth message error:', e);
- }
- };
- window.addEventListener('message', onMessage);
- const checkClosed = setInterval(() => {
- if (popup?.closed) {
- clearInterval(checkClosed);
- window.removeEventListener('message', onMessage);
- }
- }, 500);
- };
- // Telegram 登录回调
- const handleTelegramCallback = async (user: any) => {
- try {
- const inviteCode = phoneForm.inviteCode || emailForm.inviteCode || (route.query.smid as string) || '';
- const res = await requestOAuthLogin({
- provider: 'telegram',
- openId: String(user.id),
- nickname: [user.first_name, user.last_name].filter(Boolean).join(' '),
- avatar: user.photo_url || '',
- extra: JSON.stringify(user),
- inviteCode
- });
- if (res.code === 200) {
- localStorage.setItem('token', res.data.token);
- userStore.setUserInfo(res.data.user);
- toast.success(t('auth.loginSuccess'));
- router.replace('/home');
- }
- } catch (error) {
- console.error('Telegram login error:', error);
- toast.error(t('auth.telegramLoginFailed') || 'Telegram 登录失败');
- }
- };
- // 生成随机字符串
- const generateRandomString = (length: number): string => {
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
- let result = '';
- for (let i = 0; i < length; i++) {
- result += chars.charAt(Math.floor(Math.random() * chars.length));
- }
- return result;
- };
- // 初始化 TikTok 登录(检查回调参数)
- const initTiktokLogin = () => {
- const code = sessionStorage.getItem('tiktok_oauth_code');
- const redirectUri = sessionStorage.getItem('tiktok_redirect_uri');
- if (code && redirectUri) {
- sessionStorage.removeItem('tiktok_oauth_code');
- sessionStorage.removeItem('tiktok_oauth_state');
- sessionStorage.removeItem('tiktok_redirect_uri');
- handleTiktokCallback(code, redirectUri);
- }
- };
- // TikTok 登录回调处理
- const handleTiktokCallback = async (code: string, redirectUri: string) => {
- tiktokLoading.value = true;
- try {
- const inviteCode = phoneForm.inviteCode || emailForm.inviteCode || (route.query.smid as string) || '';
- const res = await requestOAuthLogin({
- provider: 'tiktok',
- openId: code,
- nickname: '',
- avatar: '',
- extra: redirectUri,
- inviteCode
- });
- if (res.code === 200) {
- localStorage.setItem('token', res.data.token);
- userStore.setUserInfo(res.data.user);
- toast.success(t('auth.loginSuccess'));
- router.replace('/home');
- } else {
- toast.error(res.msg || t('auth.tiktokLoginFailed') || 'TikTok 登录失败');
- }
- } catch (error) {
- console.error('TikTok login error:', error);
- toast.error(t('auth.tiktokLoginFailed') || 'TikTok 登录失败');
- } finally {
- tiktokLoading.value = false;
- }
- };
- // 触发 TikTok 登录
- const handleTiktokLogin = () => {
- if (!tiktokClientKey.value) {
- toast.error(t('auth.tiktokNotConfigured') || 'TikTok 登录未配置');
- return;
- }
- const redirectUri = window.location.origin + '/login';
- sessionStorage.setItem('tiktok_redirect_uri', redirectUri);
- const state = 'tiktok_' + generateRandomString(16);
- const authUrl = `https://www.tiktok.com/v2/auth/authorize/?client_key=${tiktokClientKey.value}&scope=user.info.basic&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}`;
- window.location.href = authUrl;
- };
- onMounted(() => {
- initTiktokLogin();
- initGoogleSignIn();
- });
- </script>
- <style lang="scss" scoped>
- .register-page {
- min-height: 100vh;
- background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%);
- padding-bottom: 40px;
- }
- .nav-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 16px 20px;
- color: #fff;
- .van-icon {
- font-size: 20px;
- cursor: pointer;
- }
- .title {
- font-size: 18px;
- font-weight: 500;
- }
- }
- .form-section {
- padding: 20px 24px;
- }
- .register-tabs {
- display: flex;
- margin-bottom: 24px;
- background: rgba(255, 255, 255, 0.1);
- border-radius: 8px;
- padding: 4px;
- .tab-item {
- flex: 1;
- text-align: center;
- padding: 10px 0;
- color: rgba(255, 255, 255, 0.6);
- font-size: 14px;
- border-radius: 6px;
- cursor: pointer;
- transition: all 0.3s;
- &.active {
- background: #ffc300;
- color: #000;
- font-weight: 500;
- }
- }
- }
- .form-content {
- .input-group {
- margin-bottom: 16px;
- :deep(.van-field) {
- background: rgba(255, 255, 255, 0.1);
- border-radius: 8px;
- padding: 12px 16px;
- .van-field__control {
- color: #fff;
- &::placeholder {
- color: rgba(255, 255, 255, 0.4);
- }
- }
- .van-icon {
- color: rgba(255, 255, 255, 0.6);
- }
- .van-button--primary {
- background: #ffc300;
- border-color: #ffc300;
- color: #000;
- border-radius: 4px;
- }
- }
- .area-code {
- display: flex;
- align-items: center;
- gap: 4px;
- color: #fff;
- padding-right: 12px;
- border-right: 1px solid rgba(255, 255, 255, 0.2);
- margin-right: 12px;
- cursor: pointer;
- }
- }
- }
- .agreement {
- margin: 20px 0;
- :deep(.van-checkbox) {
- .van-checkbox__icon {
- .van-icon {
- border-color: rgba(255, 255, 255, 0.4);
- background: transparent;
- }
- }
- .van-checkbox__icon--checked .van-icon {
- background: #ffc300;
- border-color: #ffc300;
- color: #000;
- }
- }
- .text {
- color: rgba(255, 255, 255, 0.6);
- font-size: 12px;
- .link {
- color: #ffc300;
- }
- }
- }
- .register-btn {
- height: 48px;
- border-radius: 8px;
- font-size: 16px;
- font-weight: 500;
- background: linear-gradient(135deg, #ffc300 0%, #ff9500 100%);
- border: none;
- &:disabled {
- opacity: 0.5;
- }
- }
- .third-party-section {
- margin-top: 24px;
- .divider {
- display: flex;
- align-items: center;
- margin-bottom: 16px;
- &::before,
- &::after {
- content: '';
- flex: 1;
- height: 1px;
- background: rgba(255, 255, 255, 0.2);
- }
- span {
- padding: 0 16px;
- color: rgba(255, 255, 255, 0.4);
- font-size: 12px;
- }
- }
- .oauth-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 10px;
- height: 48px;
- border-radius: 8px;
- font-size: 15px;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.3s;
- margin-bottom: 12px;
- &.loading {
- pointer-events: none;
- opacity: 0.7;
- }
- .google-icon, .zalo-icon, .telegram-icon, .tiktok-icon {
- flex-shrink: 0;
- }
- &.google-btn {
- background: #fff;
- color: #333;
- border: 1px solid rgba(255, 255, 255, 0.2);
- &:active {
- background: #f5f5f5;
- }
- }
- &.telegram-btn {
- background: #0088cc;
- color: #fff;
- &:active {
- background: #0077b5;
- }
- }
- &.tiktok-btn {
- background: #000;
- color: #fff;
- &:active {
- background: #333;
- }
- }
- &.zalo-btn {
- background: #0068FF;
- color: #fff;
- &:active {
- background: #0055cc;
- }
- }
- }
- }
- .login-entry {
- text-align: center;
- margin-top: 24px;
- color: rgba(255, 255, 255, 0.6);
- font-size: 14px;
- .link {
- color: #ffc300;
- margin-left: 4px;
- cursor: pointer;
- }
- }
- </style>
|