petstore-frontend/login.html
2026-04-19 14:26:25 +08:00

164 lines
8.2 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>登录 - 宠小它</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; }
#app { max-width: 430px; margin: 0 auto; min-height: 100vh; }
.page { min-height: 100vh; background: linear-gradient(135deg, #07c160 0%, #10b76f 100%); display: flex; flex-direction: column; padding: 60px 32px 32px; }
.logo { text-align: center; margin-bottom: 48px; }
.logo-text { font-size: 28px; font-weight: 700; color: #fff; letter-spacing: 2px; }
.logo-sub { font-size: 13px; color: rgba(255,255,255,0.7); margin-top: 4px; }
.form-card { background: #fff; border-radius: 16px; padding: 32px 24px; }
.form-group { margin-bottom: 16px; }
.form-input { width: 100%; height: 48px; border: 1px solid #eee; border-radius: 8px; padding: 0 16px; font-size: 15px; outline: none; box-sizing: border-box; transition: border-color 0.2s; }
.form-input:focus { border-color: #07c160; }
.sms-row { display: flex; gap: 12px; }
.sms-row .form-input { flex: 1; }
.sms-btn { height: 48px; min-width: 100px; background: #f5f5f5; color: #07c160; border: 1px solid #07c160; border-radius: 8px; font-size: 13px; cursor: pointer; white-space: nowrap; }
.sms-btn:disabled { color: #ccc; border-color: #eee; cursor: not-allowed; }
.login-btn { width: 100%; height: 48px; background: #07c160; color: #fff; border: none; border-radius: 8px; font-size: 16px; font-weight: 500; cursor: pointer; margin-top: 8px; }
.login-btn:active { opacity: 0.9; }
.login-btn:disabled { background: #ccc; cursor: not-allowed; }
.links { display: flex; justify-content: space-between; margin-top: 20px; font-size: 13px; }
.links a { color: #07c160; text-decoration: none; }
.login-divider { text-align: center; margin: 16px 0 12px; color: #999; font-size: 13px; position: relative; }
.login-divider::before, .login-divider::after { content: ''; position: absolute; top: 50%; width: 28%; height: 1px; background: #eee; }
.login-divider::before { left: 0; }
.login-divider::after { right: 0; }
.wechat-btn { width: 100%; height: 48px; background: #fff; color: #333; border: 1px solid #eee; border-radius: 8px; font-size: 15px; font-weight: 500; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 4px; }
.wechat-btn:active { opacity: 0.9; }
.wechat-btn img { width: 22px; height: 22px; }
.toast { position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,0.7);color:#fff;padding:12px 24px;border-radius:8px;font-size:14px;z-index:1000; }
</style>
</head>
<body>
<div id="app">
<div class="page">
<div class="logo">
<div class="logo-text">宠小它</div>
<div class="logo-sub">用心宠小它,温暖伴一生</div>
</div>
<div class="form-card">
<div class="form-group">
<input class="form-input" type="tel" v-model="loginForm.phone" placeholder="请输入手机号" maxlength="11" />
</div>
<div class="form-group sms-row">
<input class="form-input" type="tel" v-model="loginForm.code" placeholder="短信验证码" maxlength="6" @keyup.enter="handleLogin" />
<button class="sms-btn" :disabled="smsCountdown > 0" @click="handleSendSms">{{ smsCountdown > 0 ? smsCountdown + 's' : '获取验证码' }}</button>
</div>
<button class="login-btn" :disabled="loading" @click="handleLogin">{{ loading ? '登录中...' : '登录' }}</button>
<div class="login-divider">其他登录方式</div>
<button class="wechat-btn" @click="handleWechatLogin">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'><path fill='%2307C160' d='M24 6c-9.9 0-18 6.7-18 15 0 3.3 1.4 6.4 3.8 8.7-.6 2.3-2.2 4.3-4.1 5.6l-.4-1.6c-.2-.8-1-1.3-1.8-1.1-.6.2-1 .8-1 1.4 0 2.3 2.8 4 6.2 4s6.2-1.7 6.2-4c0-.6-.4-1.2-1-1.4-.8-.2-1.6.3-1.8 1.1l-.4 1.6c-2.4-1.6-3.9-4.2-4.2-7.1-.1-.8-.1-1.6-.1-2.4 0-.4 0-.8.1-1.2C9.3 15.7 16.3 10 24 10c.4 0 .8 0 1.2.1C26.4 7.3 25.3 6.6 24 6.6c-.6 0-1.1.5-1.1 1.1V9c0 .6.5 1.1 1.1 1.1h1.2c.6 0 1.1.5 1.1 1.1v.3C27.4 10.1 25.8 9.3 24 9.3c-1.8 0-3.4.8-4.5 2-.3.3-.8.4-1.2.2-.4-.2-.6-.6-.6-1 0-.4.2-.8.6-1C20.6 7.8 22.2 7 24 7c4.4 0 8 3.6 8 8s-3.6 8-8 8c-.4 0-.8 0-1.2-.1.7 1.5 1.1 3.2 1.1 5 0 5.5-3.6 10.2-8.5 11.6C16.1 44.6 19.9 46 24 46c9.9 0 18-6.7 18-15S33.9 6 24 6z'/></svg>" alt="微信">
微信授权登录
</button>
<div class="links">
<a href="register-staff.html">员工注册</a>
<a href="register-boss.html">商家入驻</a>
</div>
</div>
</div>
<div v-if="toast.show" class="toast">{{ toast.text }}</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script>
const { createApp, ref } = Vue
const BASE = 'http://localhost:8080'
createApp({
setup() {
const loginForm = ref({ phone: '13800138001', code: '123456' })
const loading = ref(false)
const smsCountdown = ref(0)
let smsTimer = null
const toast = ref({ show: false, text: '' })
const showToast = (text) => {
toast.value = { show: true, text }
setTimeout(() => { toast.value.show = false }, 1500)
}
const handleSendSms = async () => {
if (!loginForm.value.phone || loginForm.value.phone.length !== 11) {
return showToast('请输入正确的手机号')
}
try {
const res = await fetch(`${BASE}/api/sms/send`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone: loginForm.value.phone })
})
const data = await res.json()
if (data.code === 200) {
showToast('验证码已发送')
smsCountdown.value = 60
smsTimer = setInterval(() => {
smsCountdown.value--
if (smsCountdown.value <= 0) clearInterval(smsTimer)
}, 1000)
} else {
showToast(data.message || '发送失败')
}
} catch (e) {
showToast('网络错误')
}
}
const handleLogin = async () => {
if (!loginForm.value.phone || loginForm.value.phone.length !== 11) return showToast('请输入正确的手机号')
if (!loginForm.value.code || loginForm.value.code.length !== 6) return showToast('请输入6位验证码')
loading.value = true
try {
const res = await fetch(`${BASE}/api/user/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(loginForm.value)
})
const data = await res.json()
if (data.code === 200) {
const { user, store } = data.data
localStorage.setItem('petstore_user', JSON.stringify(user))
localStorage.setItem('petstore_store', JSON.stringify(store))
window.location.href = 'index.html'
} else {
showToast(data.message || '登录失败')
}
} catch (e) {
showToast('网络错误,请稍后重试')
}
loading.value = false
}
const handleWechatLogin = () => {
showToast('跳转到微信授权...')
setTimeout(() => {
// 模拟微信授权,演示用
const demoUser = { id: 99, name: '微信用户', phone: '', role: 'staff' }
localStorage.setItem('petstore_user', JSON.stringify(demoUser))
localStorage.setItem('petstore_store', JSON.stringify({ id: 2, name: '宠小它测试店' }))
window.location.href = 'index.html'
}, 1500)
}
return { loginForm, loading, smsCountdown, toast, handleLogin, handleSendSms, handleWechatLogin }
}
}).mount('#app')
</script>
</body>
</html>