初始化:职引项目 v1.0

This commit is contained in:
yuzhiran
2026-06-08 16:28:00 +08:00
commit 511f60d0db
111 changed files with 27295 additions and 0 deletions
+156
View File
@@ -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>