初始化:职引项目 v1.0
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<view class="page fade-in">
|
||||
<view class="hero">
|
||||
<view class="brand">
|
||||
<text class="brand-name">AI 磁场</text>
|
||||
<text class="brand-tagline">AI 助力你的求职之路</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-section">
|
||||
<!-- 登录方式切换 -->
|
||||
<view class="tab-bar">
|
||||
<text class="tab" :class="{ active: mode === 'email' }" @click="mode='email'">邮箱登录</text>
|
||||
<text class="tab" :class="{ active: mode === 'wechat' }" @click="mode='wechat'" v-if="isMp">微信登录</text>
|
||||
</view>
|
||||
|
||||
<!-- 邮箱登录 -->
|
||||
<view class="card" v-if="mode === 'email'">
|
||||
<text class="card-title">邮箱登录</text>
|
||||
|
||||
<view class="field">
|
||||
<text class="field-label">邮箱</text>
|
||||
<input class="input" type="text" v-model="email" placeholder="请输入邮箱" @confirm="sendEmailCode" />
|
||||
</view>
|
||||
|
||||
<view class="field" v-if="emailSent">
|
||||
<text class="field-label">验证码</text>
|
||||
<view class="code-row">
|
||||
<input class="input code-input" type="number" maxlength="6" v-model="emailCode" placeholder="6位验证码" @confirm="doEmailLogin" />
|
||||
<button class="code-btn" :disabled="cooldown > 0" @click="sendEmailCode">
|
||||
{{ cooldown > 0 ? cooldown + 's' : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="login-btn" v-if="!emailSent" @click="sendEmailCode">{{ emailSending ? '发送中...' : '获取验证码' }}</button>
|
||||
<button class="login-btn" v-else :disabled="!emailCode" @click="doEmailLogin">{{ emailLoading ? '登录中...' : '登录' }}</button>
|
||||
</view>
|
||||
|
||||
<!-- 微信登录(仅小程序) -->
|
||||
<view class="card" v-if="mode === 'wechat' && isMp">
|
||||
<text class="card-title">微信一键登录</text>
|
||||
<text class="card-sub">授权后自动创建账号</text>
|
||||
<button class="login-btn wx-btn" @click="doWxLogin">{{ wxLoading ? '登录中...' : '微信一键登录' }}</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { api } from '../../config'
|
||||
|
||||
const mode = ref('email')
|
||||
const isMp = ref(false)
|
||||
const email = ref('')
|
||||
const emailCode = ref('')
|
||||
const emailSent = ref(false)
|
||||
const emailSending = ref(false)
|
||||
const emailLoading = ref(false)
|
||||
const cooldown = ref(0)
|
||||
let timer = null
|
||||
|
||||
onMounted(() => {
|
||||
// #ifdef MP-WEIXIN
|
||||
isMp.value = true
|
||||
mode.value = 'wechat'
|
||||
// #endif
|
||||
})
|
||||
|
||||
// 邮箱验证码
|
||||
const sendEmailCode = async () => {
|
||||
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (!re.test(email.value)) { uni.showToast({ title: '请输入正确的邮箱', icon: 'none' }); return }
|
||||
emailSending.value = true
|
||||
try {
|
||||
const res = await uni.request({ url: api('/user/send-email-code'), method: 'POST', data: { email: email.value } })
|
||||
if (res.statusCode === 200) {
|
||||
emailSent.value = true
|
||||
uni.showToast({ title: '验证码已发送', icon: 'success' })
|
||||
startCooldown()
|
||||
} else { uni.showToast({ title: res.data?.message || '发送失败', icon: 'none' }) }
|
||||
} catch { uni.showToast({ title: '网络错误', icon: 'none' }) }
|
||||
finally { emailSending.value = false }
|
||||
}
|
||||
|
||||
const startCooldown = () => {
|
||||
cooldown.value = 60
|
||||
if (timer) clearInterval(timer)
|
||||
timer = setInterval(() => { if (--cooldown.value <= 0) { clearInterval(timer); timer = null } }, 1000)
|
||||
}
|
||||
|
||||
// 邮箱登录
|
||||
const doEmailLogin = async () => {
|
||||
if (!emailCode.value) return
|
||||
emailLoading.value = true
|
||||
try {
|
||||
const res = await uni.request({ url: api('/user/email-login'), method: 'POST', data: { email: email.value, code: emailCode.value } })
|
||||
if (res.statusCode === 200 && res.data?.token) {
|
||||
uni.setStorageSync('token', res.data.token)
|
||||
if (res.data.user) uni.setStorageSync('userInfo', JSON.stringify(res.data.user))
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 500)
|
||||
} else { uni.showToast({ title: res.data?.message || '登录失败', icon: 'none' }) }
|
||||
} catch { uni.showToast({ title: '登录失败', icon: 'none' }) }
|
||||
finally { emailLoading.value = false }
|
||||
}
|
||||
|
||||
// 微信静默登录
|
||||
const doWxLogin = async () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
wxLoading.value = true
|
||||
try {
|
||||
const { code } = await uni.login()
|
||||
const res = await uni.request({ url: api('/user/wx-login'), method: 'POST', data: { code } })
|
||||
if (res.statusCode === 200 && res.data?.token) {
|
||||
uni.setStorageSync('token', res.data.token)
|
||||
if (res.data.user) uni.setStorageSync('userInfo', JSON.stringify(res.data.user))
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 500)
|
||||
} else { uni.showToast({ title: '微信登录失败', icon: 'none' }) }
|
||||
} catch { uni.showToast({ title: '微信登录失败', icon: 'none' }) }
|
||||
finally { wxLoading.value = false }
|
||||
// #endif
|
||||
}
|
||||
|
||||
const wxLoading = ref(false)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { min-height: 100vh; background: var(--color-bg); display: flex; flex-direction: column; }
|
||||
.hero { padding: 80rpx 32rpx 60rpx; text-align: center; }
|
||||
.brand-name { font-size: 48rpx; font-weight: 800; color: var(--color-primary); }
|
||||
.brand-tagline { font-size: 24rpx; color: var(--color-text-tertiary); margin-top: 8rpx; display: block; }
|
||||
.form-section { padding: 0 32rpx; flex: 1; }
|
||||
|
||||
/* Tab */
|
||||
.tab-bar { display: flex; gap: 0; margin-bottom: 24rpx; background: #FFFFFF; border-radius: var(--radius-md); padding: 4rpx; }
|
||||
.tab { flex: 1; text-align: center; padding: 16rpx; font-size: 26rpx; color: var(--color-text-secondary); border-radius: var(--radius-sm); }
|
||||
.tab.active { background: var(--color-primary); color: #FFFFFF; font-weight: 600; }
|
||||
|
||||
/* Form */
|
||||
.card { background: #FFFFFF; border-radius: var(--radius-lg); padding: 32rpx; box-shadow: var(--shadow-sm); }
|
||||
.card-title { font-size: 30rpx; font-weight: 700; color: var(--color-text); display: block; }
|
||||
.card-sub { font-size: 22rpx; color: var(--color-text-tertiary); margin-top: 6rpx; margin-bottom: 24rpx; display: block; }
|
||||
.field { margin-bottom: 20rpx; }
|
||||
.field-label { font-size: 22rpx; color: var(--color-text-secondary); margin-bottom: 8rpx; display: block; }
|
||||
.input { width: 100%; height: 72rpx; background: #F9FAFB; border: 2rpx solid var(--color-border); border-radius: var(--radius-sm); padding: 0 20rpx; font-size: 26rpx; box-sizing: border-box; }
|
||||
.code-row { display: flex; gap: 12rpx; align-items: center; }
|
||||
.code-input { flex: 1; }
|
||||
.code-btn { height: 72rpx; padding: 0 24rpx; background: #F3F4F6; border: 2rpx solid var(--color-border); border-radius: var(--radius-sm); font-size: 24rpx; color: var(--color-primary); white-space: nowrap; flex-shrink: 0; }
|
||||
.code-btn:disabled { color: var(--color-text-tertiary); }
|
||||
.login-btn { width: 100%; height: 80rpx; background: linear-gradient(135deg, var(--color-gradient-start), var(--color-gradient-mid)); color: #FFFFFF; border-radius: var(--radius-md); font-size: 28rpx; font-weight: 600; border: none; margin-top: 16rpx; display: flex; align-items: center; justify-content: center; }
|
||||
.login-btn:disabled { opacity: 0.5; }
|
||||
.wx-btn { background: linear-gradient(135deg, #07C160, #06AD56); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user