petstore-frontend/pages/login/login.vue
2026-04-12 22:57:48 +08:00

349 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="login-page">
<!-- Logo区 -->
<view class="login-logo">
<view class="login-logo-text">宠伴生活馆</view>
<view class="login-logo-sub">宠物服务让爱更专业</view>
</view>
<!-- 老板注册成功 -->
<view v-if="bossRegistered" class="form-card">
<view class="success-icon">🎉</view>
<view class="success-title">入驻成功</view>
<view class="success-sub">欢迎加入宠伴生活馆</view>
<view class="success-info">
<view class="info-row"><span class="label">店铺名称</span><span class="value">{{ regResult.store.name }}</span></view>
<view class="info-row"><span class="label">您的账号</span><span class="value">{{ regResult.user.phone }}</span></view>
<view class="info-row"><span class="label">初始密码</span><span class="value">{{ regResult.user.password }}</span></view>
<view class="info-row"><span class="label">员工邀请码</span><span class="value">{{ regResult.store.inviteCode }}</span></view>
</view>
<button class="btn-primary-full" @click="copyCode">📋 复制员工邀请码</button>
<button class="btn-default-full" @click="goLogin" style="margin-top:12px;">前往登录 →</button>
</view>
<!-- 员工注册成功 -->
<view v-else-if="staffRegistered" class="form-card">
<view class="success-icon">✅</view>
<view class="success-title">注册成功</view>
<view class="success-sub">您已成功加入 {{ regResult.store?.name }} 团队</view>
<view class="success-info">
<view class="info-row"><span class="label">所属店铺</span><span class="value">{{ regResult.store?.name }}</span></view>
<view class="info-row"><span class="label">您的账号</span><span class="value">{{ regResult.user?.phone }}</span></view>
</view>
<button class="btn-primary-full" @click="goLogin">前往登录 →</button>
</view>
<!-- 登录/注册表单 -->
<view v-else class="form-card">
<!-- Tab切换 -->
<view class="tab-header">
<view
v-for="tab in tabs"
:key="tab.key"
:class="['tab-item', { active: activeTab === tab.key }]"
@click="activeTab = tab.key"
>{{ tab.label }}</view>
</view>
<!-- 用户登录 -->
<view v-if="activeTab === 'staff'" class="tab-content">
<view class="form-group">
<input class="form-input" v-model="loginForm.phone" type="tel" placeholder="请输入手机号" maxlength="11" />
</view>
<view class="form-group">
<view class="code-input-wrap">
<input class="form-input" v-model="loginForm.code" type="digit" placeholder="短信验证码" maxlength="6" />
<view class="code-btn" :class="{ disabled: smsCountdown > 0 }" @click="handleSendSms">
{{ smsCountdown > 0 ? smsCountdown + 's' : '获取验证码' }}
</view>
</view>
</view>
<button class="btn-primary-full" :loading="loginLoading" @click="handleLogin">登录</button>
<view class="login-divider">其他登录方式</view>
<button class="btn-default-full" @click="handleWechatLogin">
<text class="wechat-icon">📱</text> 微信授权登录
</button>
<view class="links">
<text class="link" @click="activeTab = 'staff-reg'">员工注册</text>
<text class="link" @click="activeTab = 'boss-reg'">商家入驻</text>
</view>
</view>
<!-- 老板登录 -->
<view v-if="activeTab === 'boss'" class="tab-content">
<view class="form-group">
<input class="form-input" v-model="loginForm.phone" type="tel" placeholder="请输入手机号" maxlength="11" />
</view>
<view class="form-group">
<view class="code-input-wrap">
<input class="form-input" v-model="loginForm.code" type="digit" placeholder="短信验证码" maxlength="6" />
<view class="code-btn" :class="{ disabled: smsCountdown > 0 }" @click="handleSendSms">
{{ smsCountdown > 0 ? smsCountdown + 's' : '获取验证码' }}
</view>
</view>
</view>
<button class="btn-primary-full" :loading="loginLoading" @click="handleLogin">登录</button>
<view class="links" style="margin-top:20px;">
<text class="link" @click="activeTab = 'boss-reg'">商家入驻</text>
</view>
</view>
<!-- 注册老板 -->
<view v-if="activeTab === 'boss-reg'" class="tab-content">
<view class="form-group">
<input class="form-input" v-model="bossForm.storeName" placeholder="店铺名称" />
</view>
<view class="form-group">
<input class="form-input" v-model="bossForm.bossName" placeholder="您的姓名" />
</view>
<view class="form-group">
<input class="form-input" v-model="bossForm.phone" type="tel" placeholder="手机号" maxlength="11" />
</view>
<view class="form-group">
<input class="form-input" v-model="bossForm.password" password placeholder="登录密码至少6位" />
</view>
<button class="btn-primary-full" :loading="regLoading" @click="handleRegisterBoss">提交申请</button>
<view class="links" style="margin-top:20px;">
<text class="link" @click="activeTab = 'staff'">返回登录</text>
</view>
</view>
<!-- 注册员工 -->
<view v-if="activeTab === 'staff-reg'" class="tab-content">
<view class="invite-hint">请输入店长提供的邀请码加入团队</view>
<view class="form-group">
<input class="form-input" v-model="staffForm.inviteCode" placeholder="请输入8位邀请码" maxlength="8" />
</view>
<view class="form-group">
<input class="form-input" v-model="staffForm.name" placeholder="您的姓名" />
</view>
<view class="form-group">
<input class="form-input" v-model="staffForm.phone" type="tel" placeholder="手机号" maxlength="11" />
</view>
<view class="form-group">
<input class="form-input" v-model="staffForm.password" password placeholder="登录密码至少6位" />
</view>
<button class="btn-primary-full" :loading="regLoading" @click="handleRegisterStaff">注册</button>
<view class="links" style="margin-top:20px;">
<text class="link" @click="activeTab = 'staff'">返回登录</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { sendSms, login, registerBoss, registerStaff } from '../../utils/api.js'
const activeTab = ref('staff')
const loginForm = reactive({ phone: '13800138001', code: '123456' })
const bossForm = reactive({ storeName: '', bossName: '', phone: '', password: '' })
const staffForm = reactive({ inviteCode: '', name: '', phone: '', password: '' })
const loginLoading = ref(false)
const regLoading = ref(false)
const smsCountdown = ref(0)
const bossRegistered = ref(false)
const staffRegistered = ref(false)
const regResult = ref({ store: {}, user: {} })
const tabs = [
{ key: 'staff', label: '用户登录' },
{ key: 'boss', label: '老板登录' },
{ key: 'boss-reg', label: '注册老板' },
{ key: 'staff-reg', label: '注册员工' }
]
let smsTimer = null
const showToast = (msg) => uni.showToast({ title: msg, icon: 'none' })
const handleSendSms = async () => {
if (!loginForm.phone || loginForm.phone.length !== 11) return showToast('请输入正确的手机号')
const res = await sendSms(loginForm.phone)
if (res.code === 200) {
showToast('验证码已发送')
smsCountdown.value = 60
smsTimer = setInterval(() => {
smsCountdown.value--
if (smsCountdown.value <= 0) clearInterval(smsTimer)
}, 1000)
} else {
showToast(res.message || '发送失败')
}
}
const handleLogin = async () => {
if (!loginForm.phone || loginForm.phone.length !== 11) return showToast('请输入正确的手机号')
if (!loginForm.code || loginForm.code.length !== 6) return showToast('请输入6位验证码')
loginLoading.value = true
const res = await login(loginForm.phone, loginForm.code)
loginLoading.value = false
if (res.code === 200) {
uni.setStorageSync('petstore_user', JSON.stringify(res.data.user))
uni.setStorageSync('petstore_store', JSON.stringify(res.data.store))
uni.switchTab({ url: '/pages/home/home' })
} else {
showToast(res.message || '登录失败')
}
}
const handleWechatLogin = () => {
showToast('跳转到微信授权...')
setTimeout(() => {
const demoUser = { id: 99, name: '微信用户', phone: '', role: 'staff' }
uni.setStorageSync('petstore_user', JSON.stringify(demoUser))
uni.setStorageSync('petstore_store', JSON.stringify({ id: 2, name: '宠伴生活馆测试店' }))
uni.switchTab({ url: '/pages/home/home' })
}, 1500)
}
const handleRegisterBoss = async () => {
const f = bossForm
if (!f.storeName) return showToast('请输入店铺名称')
if (!f.bossName) return showToast('请输入您的姓名')
if (!f.phone || f.phone.length !== 11) return showToast('请输入正确的手机号')
if (!f.password || f.password.length < 6) return showToast('密码至少6位')
regLoading.value = true
const res = await registerBoss(f)
regLoading.value = false
if (res.code === 200) {
regResult.value = res.data
bossRegistered.value = true
} else {
showToast(res.message || '注册失败')
}
}
const handleRegisterStaff = async () => {
const f = staffForm
if (!f.inviteCode || f.inviteCode.length !== 8) return showToast('请输入8位邀请码')
if (!f.name) return showToast('请输入您的姓名')
if (!f.phone || f.phone.length !== 11) return showToast('请输入正确的手机号')
if (!f.password || f.password.length < 6) return showToast('密码至少6位')
regLoading.value = true
const res = await registerStaff(f)
regLoading.value = false
if (res.code === 200) {
regResult.value = res.data
staffRegistered.value = true
} else {
showToast(res.message || '注册失败')
}
}
const copyCode = () => {
uni.setClipboardData({ data: regResult.value.store.inviteCode, success: () => showToast('邀请码已复制') })
}
const goLogin = () => {
activeTab.value = 'staff'
bossRegistered.value = false
staffRegistered.value = false
}
</script>
<style scoped>
.login-page {
min-height: 100vh;
background: linear-gradient(135deg, #07c160 0%, #10b76f 100%);
display: flex;
flex-direction: column;
padding: 80px 32px 32px;
}
.login-logo { text-align: center; margin-bottom: 48px; }
.login-logo-text { font-size: 28px; font-weight: 700; color: #fff; letter-spacing: 2px; }
.login-logo-sub { font-size: 13px; color: rgba(255,255,255,0.7); margin-top: 4px; }
.form-card { background: #fff; border-radius: 16px; padding: 24px 20px; }
.tab-header {
display: flex;
flex-wrap: wrap;
border-bottom: 1px solid #eee;
margin-bottom: 4px;
}
.tab-item {
font-size: 14px;
color: #999;
padding: 10px 8px;
cursor: pointer;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
}
.tab-item.active { color: #07c160; border-bottom-color: #07c160; font-weight: 600; }
.tab-content { padding-top: 16px; }
.form-group { margin-bottom: 12px; }
.form-input {
width: 100%;
height: 44px;
border: 1px solid #eee;
border-radius: 8px;
padding: 0 12px;
font-size: 14px;
box-sizing: border-box;
background: #fafafa;
}
.form-input:focus { border-color: #07c160; outline: none; }
.code-input-wrap { display: flex; align-items: center; gap: 8px; }
.code-input-wrap .form-input { flex: 1; }
.code-btn {
font-size: 13px;
color: #07c160;
white-space: nowrap;
padding: 8px 12px;
border: 1px solid #07c160;
border-radius: 6px;
flex-shrink: 0;
}
.code-btn.disabled { color: #999; border-color: #ddd; }
.login-divider {
text-align: center;
margin: 16px 0 12px;
color: #999;
font-size: 13px;
position: relative;
}
.links { display: flex; justify-content: space-between; font-size: 13px; margin-top: 12px; }
.link { color: #07c160; cursor: pointer; }
.invite-hint {
background: #f0f9f4;
border: 1px solid #b7eb8f;
border-radius: 8px;
padding: 12px;
font-size: 13px;
color: #52c41a;
margin-bottom: 12px;
}
.wechat-icon { margin-right: 6px; }
.success-icon { font-size: 60px; text-align: center; margin-bottom: 16px; }
.success-title { font-size: 20px; font-weight: 600; text-align: center; color: #333; margin-bottom: 8px; }
.success-sub { font-size: 14px; color: #999; text-align: center; margin-bottom: 24px; }
.success-info { background: #f9f9f9; border-radius: 12px; padding: 16px; margin-bottom: 20px; }
.info-row { display: flex; justify-content: space-between; padding: 6px 0; font-size: 14px; }
.info-row .label { color: #999; }
.info-row .value { color: #333; font-weight: 500; }
.btn-primary-full {
width: 100%;
height: 44px;
background: #07c160;
color: #fff;
border: none;
border-radius: 8px;
font-size: 15px;
display: flex;
align-items: center;
justify-content: center;
}
.btn-default-full {
width: 100%;
height: 44px;
background: #fff;
color: #333;
border: 1px solid #eee;
border-radius: 8px;
font-size: 15px;
display: flex;
align-items: center;
justify-content: center;
}
</style>