|
|
@@ -9,16 +9,73 @@
|
|
|
<div v-if="ssoAppName" style="color: #67C23A; background-color: #f0f9eb; border: 1px solid #e1f3d8; padding: 6px 12px; border-radius: 4px; font-size: 16px; margin: 10px 0; display: inline-block;">{{ ssoAppName }}</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
- <el-form :model="loginForm" label-width="0px" class="login-form">
|
|
|
+
|
|
|
+ <!-- SMS Enabled and NOT OIDC/SSO (Simple implementation for now) -->
|
|
|
+ <el-tabs v-if="smsEnabled && !loginChallenge" v-model="activeTab" class="login-tabs" stretch>
|
|
|
+ <el-tab-pane label="密码登录" name="password">
|
|
|
+ <el-form :model="loginForm" label-width="0px" class="login-form">
|
|
|
+ <el-form-item>
|
|
|
+ <el-input v-model="loginForm.mobile" placeholder="手机号" :prefix-icon="Iphone" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-input
|
|
|
+ v-model="loginForm.password"
|
|
|
+ type="password"
|
|
|
+ placeholder="密码"
|
|
|
+ :prefix-icon="Lock"
|
|
|
+ show-password
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-checkbox v-model="loginForm.remember_me">记住我 (30天免登录)</el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" :loading="loading" @click="handleLogin" style="width: 100%">
|
|
|
+ 登录
|
|
|
+ </el-button>
|
|
|
+ </el-form-item>
|
|
|
+ <div style="text-align: center; margin-top: 10px;">
|
|
|
+ <router-link to="/reset-password">忘记密码?</router-link>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane label="验证码登录" name="sms">
|
|
|
+ <el-form :model="smsForm" label-width="0px" class="login-form">
|
|
|
+ <el-form-item>
|
|
|
+ <el-input v-model="smsForm.mobile" placeholder="手机号" :prefix-icon="Iphone" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-row :gutter="10" style="width: 100%">
|
|
|
+ <el-col :span="15">
|
|
|
+ <el-input v-model="smsForm.code" placeholder="验证码" :prefix-icon="Key" />
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="9">
|
|
|
+ <el-button @click="handleSendCode" :disabled="countdown > 0" style="width: 100%">
|
|
|
+ {{ countdown > 0 ? `${countdown}s` : '发送验证码' }}
|
|
|
+ </el-button>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" :loading="loading" @click="handleSmsLogin" style="width: 100%">
|
|
|
+ 登录
|
|
|
+ </el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+
|
|
|
+ <!-- Original Layout for fallback (OIDC/SSO or SMS disabled) -->
|
|
|
+ <el-form v-else :model="loginForm" label-width="0px" class="login-form">
|
|
|
<el-form-item>
|
|
|
- <el-input v-model="loginForm.mobile" placeholder="手机号" prefix-icon="Iphone" />
|
|
|
+ <el-input v-model="loginForm.mobile" placeholder="手机号" :prefix-icon="Iphone" />
|
|
|
</el-form-item>
|
|
|
<el-form-item>
|
|
|
<el-input
|
|
|
v-model="loginForm.password"
|
|
|
type="password"
|
|
|
placeholder="密码"
|
|
|
- prefix-icon="Lock"
|
|
|
+ :prefix-icon="Lock"
|
|
|
show-password
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
@@ -44,24 +101,99 @@ import { useRouter, useRoute } from 'vue-router'
|
|
|
import { useAuthStore } from '../store/auth'
|
|
|
import { getLoginRequest, acceptLogin } from '../api/oidc'
|
|
|
import { getSystemStatus, getAppPublicInfo, ssoLogin } from '../api/public'
|
|
|
+import { getPublicConfigs } from '../api/systemConfig'
|
|
|
+import { sendSmsCode, loginWithSms } from '../api/smsAuth'
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
+import { Iphone, Lock, Key } from '@element-plus/icons-vue'
|
|
|
|
|
|
const router = useRouter()
|
|
|
const route = useRoute()
|
|
|
const authStore = useAuthStore()
|
|
|
|
|
|
+const activeTab = ref('password')
|
|
|
+const smsEnabled = ref(false)
|
|
|
+const countdown = ref(0)
|
|
|
+let timer: any = null
|
|
|
+
|
|
|
const loginForm = reactive({
|
|
|
mobile: '',
|
|
|
password: '',
|
|
|
remember_me: true
|
|
|
})
|
|
|
|
|
|
+const smsForm = reactive({
|
|
|
+ mobile: '',
|
|
|
+ code: ''
|
|
|
+})
|
|
|
+
|
|
|
const loading = ref(false)
|
|
|
const checkLoading = ref(true)
|
|
|
const loginChallenge = ref('')
|
|
|
const ssoAppId = ref('')
|
|
|
const ssoAppName = ref('')
|
|
|
|
|
|
+const handleSendCode = async () => {
|
|
|
+ if (!smsForm.mobile) {
|
|
|
+ ElMessage.warning('请输入手机号')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await sendSmsCode(smsForm.mobile, 'pc')
|
|
|
+ ElMessage.success('验证码已发送')
|
|
|
+ countdown.value = 60
|
|
|
+ timer = setInterval(() => {
|
|
|
+ countdown.value--
|
|
|
+ if (countdown.value <= 0) {
|
|
|
+ clearInterval(timer)
|
|
|
+ }
|
|
|
+ }, 1000)
|
|
|
+ } catch (e) {
|
|
|
+ // handled by interceptor
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleSmsLogin = async () => {
|
|
|
+ if (!smsForm.mobile || !smsForm.code) {
|
|
|
+ ElMessage.warning('请输入手机号和验证码')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res = await loginWithSms({
|
|
|
+ mobile: smsForm.mobile,
|
|
|
+ code: smsForm.code,
|
|
|
+ platform: 'pc'
|
|
|
+ })
|
|
|
+
|
|
|
+ // Manual login success handling since authStore.login is for password
|
|
|
+ const token = res.data.access_token
|
|
|
+ authStore.token = token
|
|
|
+ localStorage.setItem('token', token)
|
|
|
+ await authStore.fetchUser()
|
|
|
+
|
|
|
+ // SSO Redirect Check
|
|
|
+ if (ssoAppId.value) {
|
|
|
+ const ssoRes = await ssoLogin({
|
|
|
+ app_id: ssoAppId.value,
|
|
|
+ username: '',
|
|
|
+ password: ''
|
|
|
+ })
|
|
|
+ if (ssoRes.data.redirect_url) {
|
|
|
+ ElMessage.success('登录成功,正在跳转...')
|
|
|
+ window.location.href = ssoRes.data.redirect_url
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ElMessage.success('登录成功')
|
|
|
+ router.push('/dashboard/launchpad')
|
|
|
+ } catch (e) {
|
|
|
+ // handled
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
const handleLogin = async () => {
|
|
|
if (!loginForm.mobile || !loginForm.password) {
|
|
|
ElMessage.warning('请输入手机号和密码')
|
|
|
@@ -111,6 +243,20 @@ onMounted(async () => {
|
|
|
const challenge = route.query.login_challenge as string
|
|
|
const appid = route.query.appid as string || route.query.app_id as string
|
|
|
|
|
|
+ // Fetch Config
|
|
|
+ try {
|
|
|
+ const configRes = await getPublicConfigs()
|
|
|
+ if (configRes.data) {
|
|
|
+ const pcConfig = configRes.data.find((c: any) => c.key === 'sms_login_pc_enabled')
|
|
|
+ if (pcConfig && pcConfig.value === 'true') {
|
|
|
+ smsEnabled.value = true
|
|
|
+ activeTab.value = 'sms'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch(e) {
|
|
|
+ console.error("Failed to fetch public config", e)
|
|
|
+ }
|
|
|
+
|
|
|
console.log('Login page mounted. Params:', { challenge, appid, fullQuery: route.query })
|
|
|
|
|
|
// 1. Check System Initialization Status
|