| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657 |
- <template>
- <view class="container">
- <!-- 顶部 Banner 区域 -->
- <view class="banner-area">
- <image class="banner-img" :src="loginBackgroundUrl" mode="widthFix"></image>
- </view>
- <!-- 白色内容卡片 -->
- <view class="content-card">
- <!-- 悬浮 Logo -->
- <view class="logo-wrapper">
- <image class="logo-img" :src="loginIconUrl" mode="widthFix"></image>
- </view>
- <!-- 登录 Tab 切换 -->
- <!-- <view class="tabs">
- <view
- class="tab-item"
- :class="{ active: currentTab === 0 }"
- @click="currentTab = 0"
- >
- <text class="tab-text">免密登录</text>
- <view class="tab-indicator" v-if="currentTab === 0"></view>
- </view>
- <view class="divider"></view>
- <view
- class="tab-item"
- :class="{ active: currentTab === 1 }"
- @click="currentTab = 1"
- >
- <text class="tab-text">密码登录</text>
- <view class="tab-indicator" v-if="currentTab === 1"></view>
- </view>
- </view> -->
- <view class="tabs">
- <view class="tab-item active">
- <text class="tab-text">密码登录</text>
- <view class="tab-indicator"></view>
- </view>
- </view>
- <!-- 表单区域 -->
- <view class="form-area">
- <!-- 手机号 (通用) -->
- <view class="input-group">
- <view class="area-code">
- <text>+86</text>
- <text class="arrow">﹀</text>
- </view>
- <input class="input" type="number" placeholder="手机号" placeholder-style="color: #ccc" v-model="mobile"
- maxlength="11" />
- </view>
- <!-- 免密登录: 验证码 -->
- <!-- <view class="input-group" v-if="currentTab === 0">
- <input
- class="input"
- type="number"
- placeholder="验证码"
- placeholder-style="color: #ccc"
- v-model="code"
- maxlength="6"
- />
- <view class="get-code-btn" @click="getVerifyCode">
- <text class="code-text">{{ countDown > 0 ? `${countDown}s后重试` : '获取验证码' }}</text>
- </view>
- </view> -->
- <!-- 密码登录: 密码框 -->
- <view class="input-group">
- <input class="input" :password="!showPassword" type="text" placeholder="请输入密码" placeholder-style="color: #ccc"
- v-model="password" />
- <view class="eye-icon" @click="showPassword = !showPassword">
- <template v-if="showPassword">
- <!-- 睁眼线框图标 -->
- <svg class="svg-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path
- d="M12 4.5C7 4.5 2.73 7.61 1 12C2.73 16.39 7 19.5 12 19.5C17 19.5 21.27 16.39 23 12C21.27 7.61 17 4.5 12 4.5ZM12 17C9.24 17 7 14.76 7 12C7 9.24 9.24 7 12 7C14.76 7 17 9.24 17 12C17 14.76 14.76 17 12 17ZM12 9C10.34 9 9 10.34 9 12C9 13.66 10.34 15 12 15C13.66 15 15 13.66 15 12C15 10.34 13.66 9 12 9Z"
- fill="#CCCCCC" />
- </svg>
- </template>
- <template v-else>
- <!-- 闭眼线框图标 (带睫毛) -->
- <svg class="svg-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M12 7C7 7 2.73 10.11 1 14.5" stroke="#CCCCCC" stroke-width="2" stroke-linecap="round" />
- <path d="M23 14.5C21.27 10.11 17 7 12 7" stroke="#CCCCCC" stroke-width="2" stroke-linecap="round" />
- <path d="M12 7V4" stroke="#CCCCCC" stroke-width="2" stroke-linecap="round" />
- <path d="M16 8L18 5" stroke="#CCCCCC" stroke-width="2" stroke-linecap="round" />
- <path d="M8 8L6 5" stroke="#CCCCCC" stroke-width="2" stroke-linecap="round" />
- <path d="M20 10L22 8" stroke="#CCCCCC" stroke-width="2" stroke-linecap="round" />
- <path d="M4 10L2 8" stroke="#CCCCCC" stroke-width="2" stroke-linecap="round" />
- </svg>
- </template>
- </view>
- </view>
- <!-- 忘记密码 -->
- <!-- <view class="forgot-pwd">
- <text @click="goToForgotPwd">忘记密码?</text>
- </view> -->
- <!-- 登录按钮 -->
- <button class="login-btn" @click="handleLogin">登 录</button>
- <!-- 协议 -->
- <view class="agreement">
- <view class="checkbox" :class="{ checked: isAgreed }" @click="isAgreed = !isAgreed">
- <text v-if="isAgreed" class="check-mark">✓</text>
- </view>
- <text class="agree-text">
- 我已经阅读并同意 <text class="link" @click.stop="showAgreement(1)">《用户服务协议》</text> 和 <text class="link"
- @click.stop="showAgreement(2)">《隐私政策》</text>
- </text>
- </view>
- </view>
- <!-- 底部招募入口 (固定底部) -->
- <view class="footer-recruit" @click="goToRecruit">
- <view class="recruit-badge">
- <!-- 旗帜线框图标 -->
- <svg class="svg-icon flag-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"
- style="width:30rpx; height:30rpx;">
- <path d="M4 14V4H18L17 9L18 14H4Z" stroke="#FF5722" stroke-width="2" stroke-linejoin="round" />
- <path d="M4 22V14" stroke="#FF5722" stroke-width="2" stroke-linecap="round" />
- </svg>
- <text> 宠宝履约者招募</text>
- </view>
- </view>
- <!-- 协议弹窗 公共组件 -->
- <agreement :visible="showAgreementModal" :title="agreementTitle" :content="agreementContent"
- @close="showAgreementModal = false">
- </agreement>
- </view>
- </view>
- </template>
- <script>
- import { loginByPassword, loginBySms } from '@/api/auth'
- import { sendSmsCode } from '@/api/resource/sms'
- import { getAgreement } from '@/api/system/agreement'
- import { getAppSetting } from '@/api/system/appSetting'
- import { setToken } from '@/utils/auth'
- import { startGpsTimer } from '@/utils/gps'
- import Agreement from '@/components/agreement/index.vue'
- export default {
- components: {
- Agreement
- },
- data() {
- return {
- currentTab: 1, // 0: 免密, 1: 密码
- mobile: '',
- code: '',
- password: '',
- showPassword: false,
- isAgreed: false,
- countDown: 0,
- timer: null,
- showAgreementModal: false,
- agreementTitle: '', // 协议标题
- agreementContent: '', // 协议内容
- loginLoading: false,
- loginIconUrl: '/static/logo.png', // 登录图标
- loginBackgroundUrl: '/static/header.png' // 登录背景
- }
- },
- async onLoad() {
- // 进入登录页时,清除招募认证暂存数据
- uni.removeStorageSync('recruit_form_data');
- uni.removeStorageSync('recruit_auth_data');
- uni.removeStorageSync('recruit_qual_data');
- // 获取应用配置
- await this.fetchAppSetting();
- },
- methods: {
- /**
- * 获取应用配置
- */
- async fetchAppSetting() {
- try {
- const res = await getAppSetting(1);
- if (res.code === 200 && res.data) {
- if (res.data.loginIconUrl) {
- this.loginIconUrl = res.data.loginIconUrl;
- }
- if (res.data.loginBackgroundUrl) {
- this.loginBackgroundUrl = res.data.loginBackgroundUrl;
- }
- }
- } catch (err) {
- console.error('获取应用配置失败:', err);
- }
- },
- /**
- * 显示协议弹窗
- * @param {Number} id 协议ID (1: 用户服务协议, 2: 隐私政策)
- */
- async showAgreement(id) {
- try {
- uni.showLoading({ title: '加载中...' });
- const res = await getAgreement(id);
- this.agreementTitle = res.data.title;
- this.agreementContent = res.data.content;
- this.showAgreementModal = true;
- } catch (err) {
- console.error('获取协议详情失败:', err);
- } finally {
- uni.hideLoading();
- }
- },
- /* async getVerifyCode() {
- if (this.currentTab === 1) return;
- if (this.countDown > 0) return;
- if (!this.mobile || this.mobile.length !== 11) {
- uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
- return;
- }
- try {
- const res = await sendSmsCode(this.mobile);
- // 发送成功,启动倒计时
- this.countDown = 60;
- this.timer = setInterval(() => {
- this.countDown--;
- if (this.countDown <= 0) {
- clearInterval(this.timer);
- }
- }, 1000);
- // TODO 【生产环境必须删除】开发模式下后端会返回验证码,自动填入方便测试
- const devCode = res.data;
- if (devCode) {
- this.code = devCode;
- uni.showToast({ title: '验证码: ' + devCode, icon: 'none', duration: 3000 });
- } else {
- uni.showToast({ title: '验证码已发送', icon: 'none' });
- }
- } catch (err) {
- console.error('发送验证码失败:', err);
- }
- }, */
- async handleLogin() {
- if (!this.isAgreed) {
- uni.showToast({ title: '请先同意用户协议', icon: 'none' });
- return;
- }
- if (!this.mobile) {
- uni.showToast({ title: '请输入手机号', icon: 'none' });
- return;
- }
- /* if (this.currentTab === 0) {
- // 免密登录
- if (!this.code) {
- uni.showToast({ title: '请输入验证码', icon: 'none' });
- return;
- }
- } else {
- // 密码登录
- if (!this.password) {
- uni.showToast({ title: '请输入密码', icon: 'none' });
- return;
- }
- } */
- if (!this.password) {
- uni.showToast({ title: '请输入密码', icon: 'none' });
- return;
- }
- if (this.loginLoading) return;
- this.loginLoading = true;
- uni.showLoading({
- title: '登录中...',
- mask: true
- });
- try {
- let res;
- /* if (this.currentTab === 0) {
- // 短信验证码登录
- res = await loginBySms(this.mobile, this.code);
- } else {
- // 密码登录
- res = await loginByPassword(this.mobile, this.password);
- } */
- res = await loginByPassword(this.mobile, this.password);
- // 保存 Token
- const token = res.data?.access_token || res.access_token;
- if (token) {
- setToken(token);
- }
- startGpsTimer();
- uni.showToast({ title: '登录成功', icon: 'success' });
- setTimeout(() => {
- uni.reLaunch({
- url: '/pages/home/index'
- });
- }, 1000);
- } catch (err) {
- // 错误已在 request.js 中统一处理
- console.error('登录失败:', err);
- } finally {
- this.loginLoading = false;
- uni.hideLoading();
- }
- },
- goToRecruit() {
- uni.navigateTo({
- url: '/pages/recruit/landing/index'
- });
- },
- goToForgotPwd() {
- uni.navigateTo({
- url: '/pages/login/reset-pwd-verify/index'
- });
- }
- }
- }
- </script>
- <style>
- /* 页面容器 */
- .container {
- height: 100vh;
- /* 强制全屏高度 */
- overflow: hidden;
- /* 禁止溢出滚动 */
- background-color: #fff;
- position: relative;
- display: flex;
- flex-direction: column;
- }
- /* 顶部 Banner */
- .banner-area {
- width: 100%;
- position: relative;
- /* 移除高度限制,依靠 content-card 的 margin-top 控制遮挡 */
- }
- .banner-img {
- width: 100%;
- display: block;
- /* 消除图片底部空隙 */
- }
- /* 内容卡片 - 核心布局技巧 */
- .content-card {
- flex: 1;
- background-color: #fff;
- margin-top: -80rpx;
- /* 向上覆盖 banner,形成遮挡 */
- /* 实现向上凸起的圆弧效果: 使用 border-radius 实现顶部椭圆 */
- border-radius: 100% 100% 0 0 / 60rpx 60rpx 0 0;
- position: relative;
- z-index: 10;
- padding: 0 50rpx;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- /* 悬浮 Logo */
- .logo-wrapper {
- margin-top: -70rpx;
- /* Logo 向上悬浮 */
- margin-bottom: 50rpx;
- /* 添加白色光晕/阴影增强悬浮感 */
- border-radius: 50%;
- box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.1);
- background-color: #fff;
- padding: 8rpx;
- /* 白色边框 */
- }
- .logo-img {
- width: 120rpx;
- height: 120rpx;
- border-radius: 50%;
- display: block;
- }
- /* Tabs */
- .tabs {
- display: flex;
- align-items: center;
- margin-bottom: 60rpx;
- }
- .tab-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 0 30rpx;
- }
- .tab-text {
- font-size: 32rpx;
- color: #999;
- font-weight: 500;
- margin-bottom: 10rpx;
- }
- .tab-item.active .tab-text {
- color: #FF5722;
- /* 主色调橙色 */
- font-weight: bold;
- font-size: 36rpx;
- }
- .tab-indicator {
- width: 40rpx;
- height: 6rpx;
- background-color: #FF5722;
- border-radius: 3rpx;
- }
- .divider {
- width: 1px;
- height: 30rpx;
- background-color: #eee;
- margin: 0 10rpx;
- }
- /* 表单 */
- .form-area {
- width: 100%;
- }
- .input-group {
- display: flex;
- align-items: center;
- border-bottom: 1px solid #f0f0f0;
- padding: 30rpx 0;
- margin-bottom: 20rpx;
- }
- .area-code {
- display: flex;
- align-items: center;
- margin-right: 20rpx;
- font-size: 32rpx;
- color: #333;
- height: 100%;
- /* 确保高度撑满 */
- }
- .arrow {
- font-size: 20rpx;
- color: #999;
- margin-left: 8rpx;
- /* margin-top: 4rpx; 移除微调,依靠 flex 居中 */
- }
- .input {
- flex: 1;
- font-size: 30rpx;
- }
- .get-code-btn {
- background-color: #FFF0E9;
- /* 浅橙色背景 */
- padding: 15rpx 30rpx;
- /* 增大内边距 */
- border-radius: 40rpx;
- /* 稍微增大圆角 */
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .code-text {
- color: #FF5722;
- font-size: 28rpx;
- /* 增大字体 */
- line-height: 1;
- /* 修正文字垂直居中 */
- }
- .eye-icon {
- padding: 10rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .svg-icon {
- width: 40rpx;
- height: 40rpx;
- }
- .forgot-pwd {
- width: 100%;
- text-align: right;
- padding-top: 20rpx;
- font-size: 26rpx;
- color: #666;
- }
- /* 按钮 */
- .login-btn {
- /* 鲜亮的橙色渐变 */
- background: linear-gradient(90deg, #FF9E60 0%, #FF5722 100%);
- color: #fff;
- border-radius: 50rpx;
- margin-top: 60rpx;
- margin-bottom: 40rpx;
- font-size: 34rpx;
- font-weight: bold;
- box-shadow: 0 10rpx 30rpx rgba(255, 87, 34, 0.35);
- /* 增强投影 */
- }
- .login-btn::after {
- border: none;
- }
- /* 协议 */
- .agreement {
- display: flex;
- align-items: center;
- /* 改为居中对齐,如果文字换行可能需要改为 flex-start 并调整 margin-top */
- justify-content: center;
- }
- .checkbox {
- width: 32rpx;
- height: 32rpx;
- border: 2rpx solid #ccc;
- border-radius: 50%;
- margin-right: 12rpx;
- /* margin-top: 4rpx; 移除顶部 margin,由 align-items: center 控制 */
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .checkbox.checked {
- border-color: #FF5722;
- background-color: #FF5722;
- }
- .check-mark {
- color: #fff;
- font-size: 22rpx;
- line-height: 1;
- /* 确保勾选符号居中 */
- }
- .agree-text {
- font-size: 24rpx;
- color: #999;
- line-height: 1.5;
- }
- .link {
- color: #666;
- }
- /* 底部招募 */
- .footer-recruit {
- margin-top: auto;
- padding-bottom: 80rpx;
- /* 增加底部距离 */
- width: 100%;
- display: flex;
- justify-content: center;
- }
- .recruit-badge {
- display: flex;
- align-items: center;
- padding: 20rpx 50rpx;
- background-color: #fff;
- /* 白色背景 */
- border: 1px solid #FF5722;
- /* 橙色边框 */
- border-radius: 60rpx;
- /* 胶囊圆角 */
- color: #FF5722;
- /* 橙色文字 */
- font-size: 28rpx;
- font-weight: bold;
- box-shadow: 0 5rpx 15rpx rgba(255, 87, 34, 0.2);
- /* 橙色阴影 */
- }
- .flag-icon {
- margin-right: 15rpx;
- font-size: 28rpx;
- color: #FF5722;
- }
- /* 弹窗样式 */
- .modal-mask {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.5);
- z-index: 999;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .modal-content {
- width: 600rpx;
- background-color: #fff;
- border-radius: 20rpx;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- max-height: 70vh;
- }
- .modal-header {
- padding: 30rpx;
- text-align: center;
- border-bottom: 1px solid #eee;
- }
- .modal-title {
- font-size: 32rpx;
- font-weight: bold;
- }
- .modal-body {
- padding: 30rpx;
- flex: 1;
- overflow-y: auto;
- }
- .modal-text {
- font-size: 28rpx;
- color: #666;
- line-height: 1.6;
- }
- .modal-footer {
- padding: 20rpx 30rpx 30rpx;
- }
- .confirm-btn {
- background-color: #FF5722;
- color: #fff;
- font-size: 30rpx;
- border-radius: 40rpx;
- }
- </style>
|