LoginModal.vue 2.85 KB
<script setup>
/**
 * 登录 / 注册弹窗。小镇默认公开可看;只有接入自己的 Agent、发指令时才需要登录。
 * 路由到 /login(可带 ?next=)即打开本弹窗,关闭后回到小镇。
 */
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import { Button, Input, Modal } from '@drama/ui'

import { api, setToken } from './api.js'

const { t, locale } = useI18n()
const router = useRouter()
const route = useRoute()
const open = computed(() => route.path === '/login')
const mode = ref('login')
const email = ref('')
const password = ref('')
const code = ref('')
const handle = ref('')
const message = ref('')

async function sendCode() {
  try {
    await api('POST', '/api/voko/auth/send-code', { email: email.value, locale: locale.value })
    message.value = t('login.codeSent')
  } catch (err) {
    message.value = err instanceof Error ? err.message : String(err)
  }
}

async function submit() {
  try {
    const session = mode.value === 'login'
      ? await api('POST', '/api/voko/auth/login', { method: 'password', email: email.value, password: password.value })
      : await api('POST', '/api/voko/auth/register', { email: email.value, password: password.value, verification_code: code.value, locale: locale.value, handle: handle.value.trim().toLowerCase() })
    setToken(session.access_token)
    password.value = ''
    message.value = ''
    // 只接受站内路径(如认领链接登录后回到认领页)。
    const next = typeof route.query.next === 'string' && route.query.next.startsWith('/') && !route.query.next.startsWith('//') ? route.query.next : '/agents'
    await router.push(next)
  } catch (err) {
    message.value = err instanceof Error ? err.message : String(err)
  }
}
</script>

<template>
  <Modal :open="open" :title="mode === 'login' ? t('login.title') : t('login.registerTitle')" width="420px" @close="router.push('/')">
    <p>{{ t('login.why') }}</p>
    <label class="voko-field">{{ t('login.email') }}<Input v-model="email" type="email" /></label>
    <label class="voko-field">{{ t('login.password') }}<Input v-model="password" type="password" /></label>
    <template v-if="mode === 'register'">
      <label class="voko-field">{{ t('login.handle') }}<Input v-model="handle" /></label>
      <label class="voko-field">{{ t('login.code') }}<Input v-model="code" /></label>
      <Button variant="secondary" @click="sendCode">{{ t('login.sendCode') }}</Button>
    </template>
    <p v-if="message" role="status">{{ message }}</p>
    <template #footer>
      <Button variant="ghost" @click="mode = mode === 'login' ? 'register' : 'login'">{{ mode === 'login' ? t('login.toRegister') : t('login.toLogin') }}</Button>
      <Button @click="submit">{{ mode === 'login' ? t('login.submit') : t('login.register') }}</Button>
    </template>
  </Modal>
</template>