Files
huangjingfen/pro_v3.5.1/view/uniapp/pages/users/login/index.vue
apple 8592243d36 feat(hjf): H5路由修复、分销等级显示优化、个人中心等级徽章
H5 部署与路由:
- manifest.json: router.base 改为 "/" 适配 public/ 根目录部署
- nginx-crmeb.conf: 恢复与 feature/fsgx 一致的原始配置
- App.vue: PC端重定向路径改为动态推导,修复死循环加载问题
- static/html/pc.html: 动态推导 H5 根路径,适配本地/云端两种部署

H5登录:
- pages/users/login/index.vue: H5端获取验证码跳过安全验证(条件编译)

分销等级展示修复:
- AgentLevelServices: 新增 loadHjfUserListLevelMaps/pickHjfLevelRowForUserListDisplay
  统一等级名称解析逻辑,优先返回 HJF 官方名称;新增 getUpgradeTasksForLevel 封装
- UserServices/MemberLevelServices: 改用统一解析方法,修复 protected $dao 访问错误
- api/hjf/MemberController: 直接取 eb_agent_level.name,新增 agent_level 原始值返回
- admin/v1/hjf/MemberController: team() 改用封装方法替代直接访问 protected dao

个人中心等级徽章:
- pages/user/index.vue + member/index.vue: memberInfo 沿链路透传
- member/template1.vue: UID右侧显示HjfMemberBadge,直接读 userInfo.agent_level_name
  无需等待异步 memberInfo,agentLevelGrade 计算属性从名称推导颜色等级

商品列表修复:
- BaseController.php/Common.php: 恢复加密版,修复 CRMEB 授权检查失败导致的400错误
- StoreProduct model: 移除冲突的 model maker 回调

数据库:
- hjf_migration.sql: 完善会员等级体系迁移脚本
- eb_agent_level.sql: 新增等级初始数据脚本

Made-with: Cursor
2026-03-22 01:43:36 +08:00

905 lines
20 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="wrapper" :style="colorStyle">
<view class="bag">
<img :src="imgHost+'/statics/images/users/login-bg.jpg'" alt="" srcset="">
</view>
<!-- #ifdef MP -->
<view class="title-bar" style="height: 43px;">
<view class="icon" @click="back" v-if="!isHome">
<image src="../static/left.png"></image>
</view>
<view class="icon" @click="home" v-else>
<image src="../static/home.png"></image>
</view>
{{pageTitle}}
</view>
<!-- #endif -->
<view class="page-msg">
<view class="title">
{{current ? '快速登录' :'账号登录'}}
</view>
<view class="tip">
首次登录会自动注册
</view>
</view>
<view class="page-form">
<view class="item">
<input type='number' placeholder='请输入手机号码' placeholder-class='placeholder' v-model="account"
:maxlength="11" :adjust-position="false"></input>
</view>
<view class="item acea-row row-between-wrapper" v-if="!current">
<input type='password' placeholder='请输入密码' placeholder-class='placeholder'
class="codeIput" v-model="password" :adjust-position="false"></input>
<view class="line"></view>
<navigator class="code font-num" hover-class="none" url="/pages/users/retrievePassword/index">
忘记密码
</navigator>
</view>
<view class="item acea-row row-between-wrapper" v-else>
<input type='number' placeholder='请输入验证码' placeholder-class='placeholder' :maxlength="6"
class="codeIput" v-model="captcha"></input>
<view class="line">
</view>
<button class="code font-num" :class="disabled === true ? 'on' : ''" :disabled='disabled' @click="code">
{{ text }}
</button>
</view>
<view class="btn" @click="submitData">
立即登录
</view>
<view class="text-center fs-32 text--w111-999 mt-32" @click="current = !current">{{current ? '账号登录' :'手机号登录'}}</view>
<!-- #ifdef APP-PLUS -->
<view class="appLogin" v-if="!appLoginStatus && !appleLoginStatus">
<view class="hds">
<span class="line"></span>
<p>其他方式登录</p>
<span class="line"></span>
</view>
<view class="btn-wrapper">
<view class="btn wx" @click="wxLogin">
<span class="iconfont icon-ic_wechat"></span>
</view>
<view class="btn pingguo" @click="appleLogin" v-if="appleShow">
<view class="iconfont icon-ic_apple"></view>
</view>
</view>
</view>
<!-- #endif -->
</view>
<view class="protocol">
<checkbox-group @click.stop='ChangeIsDefault'>
<checkbox :class="inAnimation?'trembling':''"
@animationend='inAnimation=false'
:checked="protocol ? true : false"
color="#ffffff" backgroundColor="#ffffff"
activeBackgroundColor="var(--view-theme)"
activeBorderColor="var(--view-theme)"/> <text @click.stop='ChangeIsDefault'>已阅读并同意</text>
<text class="main-color" @click.stop="privacy('user')">用户协议</text>
<text class="main-color" @click.stop="privacy('privacy')">隐私协议</text>
</checkbox-group>
</view>
<!-- #ifndef H5 -->
<Verify v-if="!disabled" @success="success" :captchaType="captchaType" :imgSize="{ width: '330px', height: '155px' }"
ref="verify"></Verify>
<!-- #endif -->
</view>
</template>
<script>
import sendVerifyCode from "@/mixins/SendVerifyCode";
import {
loginH5,
loginMobile,
registerVerify,
register,
getCodeApi,
getUserInfo,
appleLogin
} from "@/api/user";
import attrs, {
required,
alpha_num,
chs_phone
} from "@/utils/validate";
import {
getLogo
} from "@/api/public";
// import cookie from "@/utils/store/cookie";
import {
VUE_APP_API_URL
} from "@/utils";
// #ifdef APP-PLUS
import {
wechatAppAuth
} from '@/api/api.js'
// #endif
const BACK_URL = "login_back_url";
import colors from '@/mixins/color.js';
// #ifndef H5
import Verify from '../components/verify/verify.vue';
// #endif
import { HTTP_REQUEST_URL, CAPTCHA_TYPE } from '@/config/app';
export default {
name: "Login",
components: {
// #ifndef H5
Verify
// #endif
},
mixins: [sendVerifyCode, colors],
data: function() {
return {
captchaType: CAPTCHA_TYPE,
inAnimation: false,
protocol: false,
imgHost: HTTP_REQUEST_URL,
navList: ["快速登录", "账号登录"],
current: true,
account: "",
password: "",
captcha: "",
formItem: 1,
type: "login",
logoUrl: "",
keyCode: "",
codeUrl: "",
codeVal: "",
isShowCode: false,
appLoginStatus: false, // 微信登录强制绑定手机号码状态
appUserInfo: null, // 微信登录保存的用户信息
appleLoginStatus: false, // 苹果登录强制绑定手机号码状态
appleUserInfo: null,
appleShow: false, // 苹果登录版本必须要求ios13以上的
keyLock: true,
copyrightContext:'',
};
},
watch: {
formItem: function(nval, oVal) {
if (nval == 1) {
this.type = 'login'
} else {
this.type = 'register'
}
}
},
onLoad() {
let self = this
uni.getSystemInfo({
success: (res) => {
if (res.platform.toLowerCase() == 'ios' && this.getSystem(res.system)) {
self.appleShow = true
}
}
});
},
mounted() {
// this.getCode();
this.getLogoImage();
},
methods: {
domainTap(url){
// #ifdef H5
location.href = url
// #endif
// #ifdef MP || APP-PLUS
uni.navigateTo({
url: `/pages/annex/web_view/index?url=${url}`
});
// #endif
},
changeMsg() {
this.inAnimation = true;
},
ChangeIsDefault(e) {
this.$set(this, 'protocol', !this.protocol);
},
// IOS 版本号判断
getSystem(system) {
let str
system.toLowerCase().indexOf('ios') === -1 ? str = system : str = system.split(' ')[1]
if (str.indexOf('.'))
return str.split('.')[0] >= 13
return str >= 13
},
// 苹果登录
appleLogin() {
let self = this
this.account = ''
this.captcha = ''
if (!self.protocol) {
this.inAnimation = true
return self.$util.Tips({
title: '请先阅读并同意协议'
});
}
uni.showLoading({
title: '登录中'
})
uni.login({
provider: 'apple',
timeout: 10000,
success(loginRes) {
uni.getUserInfo({
provider: 'apple',
success: function(infoRes) {
self.appleUserInfo = infoRes.userInfo
self.appleLoginApi()
},
fail() {
uni.showToast({
title: '获取用户信息失败',
icon: 'none',
duration: 2000
})
},
complete() {
uni.hideLoading()
}
});
},
fail(error) {
console.log(error)
}
})
},
// 苹果登录Api
appleLoginApi() {
let self = this
appleLogin({
openId: self.appleUserInfo.openId,
email: self.appleUserInfo.email || '',
phone: this.account,
captcha: this.captcha
}).then(({
data
}) => {
if (data.isbind) {
uni.showModal({
title: '提示',
content: '请绑定手机号后,继续操作',
showCancel: false,
success: function(res) {
if (res.confirm) {
self.current = true
self.appleLoginStatus = true
}
}
});
} else {
self.$store.commit("LOGIN", {
'token': data.token,
'time': data.expires_time - self.$Cache.time()
});
let backUrl = self.$Cache.get(BACK_URL) || "/pages/index/index";
self.$Cache.clear(BACK_URL);
self.$store.commit("SETUID", data.userInfo.uid);
self.$store.commit("UPDATE_USERINFO", data.userInfo);
uni.reLaunch({
url: backUrl
});
}
}).catch(error => {
uni.showModal({
title: '提示',
content: `错误信息${error}`,
success: function(res) {
if (res.confirm) {
console.log('用户点击确定');
} else if (res.cancel) {
console.log('用户点击取消');
}
}
});
})
},
// App微信登录
wxLogin() {
if (!this.protocol) {
this.inAnimation = true
return this.$util.Tips({
title: '请先阅读并同意协议'
});
}
let self = this
this.account = ''
this.captcha = ''
uni.showLoading({
title: '登录中'
})
uni.login({
provider: 'weixin',
success: function(loginRes) {
// 获取用户信息
uni.getUserInfo({
provider: 'weixin',
success: function(infoRes) {
self.appUserInfo = infoRes.userInfo
self.wxLoginApi()
},
fail() {
uni.showToast({
title: '获取用户信息失败',
icon: 'none',
duration: 2000
})
},
complete() {
uni.hideLoading()
}
});
},
fail() {
uni.showToast({
title: '登录失败',
icon: 'none',
duration: 2000
})
}
});
},
submitData(){
if(this.current){
this.loginMobile()
}else{
this.submit()
}
},
wxLoginApi() {
let self = this
wechatAppAuth({
userInfo: self.appUserInfo,
phone: this.account,
code: this.captcha
}).then(({
data
}) => {
if (data.isbind) {
uni.showModal({
title: '提示',
content: '请绑定手机号后,继续操作',
showCancel: false,
success: function(res) {
if (res.confirm) {
self.current = true
self.appLoginStatus = true
}
}
});
} else {
self.$store.commit("LOGIN", {
'token': data.token,
'time': data.expires_time - self.$Cache.time()
});
let backUrl = self.$Cache.get(BACK_URL) || "/pages/index/index";
self.$Cache.clear(BACK_URL);
self.$store.commit("SETUID", data.userInfo.uid);
self.$store.commit("UPDATE_USERINFO", data.userInfo);
uni.reLaunch({
url: backUrl
});
}
}).catch(error => {
uni.showModal({
title: '提示',
content: `错误信息${error}`,
success: function(res) {
if (res.confirm) {
console.log('用户点击确定');
} else if (res.cancel) {
console.log('用户点击取消');
}
}
});
})
},
again() {
this.codeUrl =
VUE_APP_API_URL +
"/sms_captcha?" +
"key=" +
this.keyCode +
Date.parse(new Date());
},
success(data) {
// #ifndef H5
if (this.$refs.verify) {
this.$refs.verify.hide();
}
// #endif
getCodeApi()
.then(res => {
this.keyCode = res.data.key;
this.getCode(data);
})
.catch(res => {
this.$util.Tips({
title: res
});
});
},
code() {
let that = this
if (!that.protocol) {
this.inAnimation = true
return that.$util.Tips({
title: '请先阅读并同意协议'
});
}
if (!that.account) return that.$util.Tips({
title: '请填写手机号码'
});
if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(that.account)) return that.$util.Tips({
title: '请输入正确的手机号码'
});
// #ifdef H5
// H5不弹出滑块/图形安全验证,直接取 key 并发短信(后端 register/verify 已关闭 aj 二次校验)
getCodeApi()
.then(res => {
that.keyCode = res.data.key;
that.getCode({ captchaVerification: '' });
})
.catch(res => {
that.$util.Tips({
title: res
});
});
// #endif
// #ifndef H5
this.$refs.verify.show();
// #endif
},
async getLogoImage() {
let that = this;
getLogo(2).then(res => {
that.logoUrl = res.data.logo_url;
that.copyrightContext = res.data.copyrightContext;
});
},
async loginMobile() {
let that = this;
if (!that.protocol) {
this.inAnimation = true
return that.$util.Tips({
title: '请先阅读并同意协议'
});
}
if (!that.account) return that.$util.Tips({
title: '请填写手机号码'
});
if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(that.account)) return that.$util.Tips({
title: '请输入正确的手机号码'
});
if (!that.captcha) return that.$util.Tips({
title: '请填写验证码'
});
if (!/^[\w\d]+$/i.test(that.captcha)) return that.$util.Tips({
title: '请输入正确的验证码'
});
if (that.appLoginStatus) {
that.wxLoginApi()
} else if (that.appleLoginStatus) {
that.appleLoginApi()
} else {
if (this.keyLock) {
this.keyLock = !this.keyLock
} else {
return that.$util.Tips({
title: '请勿重复点击'
});
}
loginMobile({
phone: that.account,
captcha: that.captcha,
spread_spid: that.$Cache.get("spread")
})
.then(res => {
let data = res.data;
that.$store.commit("LOGIN", {
'token': data.token,
'time': data.expires_time - this.$Cache.time()
});
let backUrl = that.$Cache.get(BACK_URL) || "/pages/index/index";
that.$Cache.clear(BACK_URL);
getUserInfo().then(res => {
this.keyLock = true
that.$store.commit("SETUID", res.data.uid);
that.$store.commit("UPDATE_USERINFO", res.data);
if (backUrl.indexOf('/pages/users/login/index') !== -1) {
backUrl = '/pages/index/index';
}
uni.reLaunch({
url: backUrl
});
})
})
.catch(res => {
this.keyLock = true
that.$util.Tips({
title: res
});
});
}
},
async register() {
let that = this;
if (!that.account) return that.$util.Tips({
title: '请填写手机号码'
});
if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(that.account)) return that.$util.Tips({
title: '请输入正确的手机号码'
});
if (!that.captcha) return that.$util.Tips({
title: '请填写验证码'
});
if (!/^[\w\d]+$/i.test(that.captcha)) return that.$util.Tips({
title: '请输入正确的验证码'
});
if (!that.password) return that.$util.Tips({
title: '请填写密码'
});
if (/^([0-9]|[a-z]|[A-Z]){0,6}$/i.test(that.password)) return that.$util.Tips({
title: '您输入的密码过于简单'
});
register({
account: that.account,
captcha: that.captcha,
password: that.password,
spread_spid: that.$Cache.get("spread")
})
.then(res => {
that.$util.Tips({
title: res
});
that.formItem = 1;
})
.catch(res => {
that.$util.Tips({
title: res
});
});
},
async getCode(data){
let that = this;
if (!that.account) return that.$util.Tips({
title: '请填写手机号码'
});
if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(that.account)) return that.$util.Tips({
title: '请输入正确的手机号码'
});
if (that.formItem == 2) that.type = "register";
await registerVerify({
phone: that.account,
type: that.type,
key: that.keyCode,
captchaType: CAPTCHA_TYPE,
captchaVerification: data.captchaVerification
})
.then(res => {
that.$util.Tips({
title: res.msg
});
that.sendCode();
})
.catch(res => {
// #ifndef H5
if (this.$refs.verify) {
this.$refs.verify.refresh();
}
// #endif
that.$util.Tips({
title: res
});
});
},
async submit() {
let that = this;
if (!that.protocol) {
this.inAnimation = true
return that.$util.Tips({
title: '请先阅读并同意协议'
});
}
if (!that.account) return that.$util.Tips({
title: '请填写账号'
});
if (!/^[\w\d]{5,16}$/i.test(that.account)) return that.$util.Tips({
title: '请输入正确的账号'
});
if (!that.password) return that.$util.Tips({
title: '请填写密码'
});
if (this.keyLock) {
this.keyLock = !this.keyLock
} else {
return that.$util.Tips({
title: '请勿重复点击'
});
}
loginH5({
account: that.account,
password: that.password,
spread_spid: that.$Cache.get("spread")
})
.then(({
data
}) => {
that.$store.commit("LOGIN", {
'token': data.token,
'time': data.expires_time - this.$Cache.time()
});
let backUrl = that.$Cache.get(BACK_URL) || "/pages/index/index";
that.$Cache.clear(BACK_URL);
getUserInfo().then(res => {
this.keyLock = true
that.$store.commit("SETUID", res.data.uid);
that.$store.commit("UPDATE_USERINFO", res.data);
uni.reLaunch({
url: backUrl
});
}).catch(error => {
this.keyLock = true
})
})
.catch(e => {
this.keyLock = true
that.$util.Tips({
title: e
});
});
},
privacy(type) {
uni.navigateTo({
url: "/pages/users/privacy/index?type=" + type
})
}
}
};
</script>
<style lang="scss" scoped>
/deep/ checkbox .wx-checkbox-input.wx-checkbox-input-checked {
border: 1px solid var(--view-theme) !important;
background-color: var(--view-theme) !important;
width: 28rpx;
height: 28rpx;
font-size: 24rpx;
color: #fff !important;
}
.wrapper {
background-color: #fff;
min-height: 100vh;
position: relative;
.bag {
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: 0;
/* #ifdef H5 */
z-index: 0;
/* #endif */
img {
width: 100%;
height: 544rpx;
}
}
.page-msg {
padding-top: 160rpx;
margin-left: 72rpx;
z-index: 2;
position: relative;
.title {
font-size: 48rpx;
font-weight: 500;
color: #333333;
line-height: 68rpx;
}
.tip {
font-size: 28rpx;
font-weight: 400;
color: #333333;
line-height: 40rpx;
}
}
.page-form {
width: 606rpx;
margin: 100rpx auto 0 auto;
z-index: 2;
position: relative;
.item {
width: 100%;
height: 88rpx;
background: #F5F5F5;
border-radius: 45rpx;
padding: 24rpx 48rpx;
margin-bottom: 32rpx;
input {
width: 100%;
height: 100%;
font-size: 32rpx;
}
.placeholder {
color: #BBBBBB;
font-size: 28rpx;
}
input.codeIput {
width: 300rpx;
}
.line {
width: 2rpx;
height: 28rpx;
background: #CCCCCC;
}
.code {
font-size: 28rpx;
color: var(--view-theme);
background-color: rgba(255, 255, 255, 0);
}
.code.on {
color: #BBBBBB !important;
}
}
.btn {
width: 606rpx;
height: 88rpx;
background: var(--view-theme);
border-radius: 200rpx 200rpx 200rpx 200rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
color: #FFFFFF;
line-height: 44rpx;
margin-top: 48rpx;
letter-spacing: 1px;
}
}
}
.title-bar {
position: relative;
display: flex;
align-items: center;
justify-content: center;
font-size: 34rpx;
font-weight: 500;
color: #333333;
line-height: 48rpx;
}
.icon {
position: absolute;
left: 30rpx;
top: 0;
display: flex;
align-items: center;
justify-content: center;
width: 80rpx;
height: 80rpx;
image {
width: 35rpx;
height: 35rpx;
}
}
.protocol {
position: fixed;
bottom: 52rpx;
left: 0;
width: 100%;
margin: 0 auto;
color: #999999;
font-size: 24rpx;
line-height: 22rpx;
text-align: center;
bottom: calc(52rpx + constant(safe-area-inset-bottom)); ///兼容 IOS<11.2/
bottom: calc(52rpx + env(safe-area-inset-bottom)); ///兼容 IOS>11.2/
.main-color {
color: var(--view-theme);
}
.trembling {
animation: shake 0.6s;
}
}
.appLogin {
margin-top: 60rpx;
.hds {
display: flex;
justify-content: center;
align-items: center;
font-size: 24rpx;
color: #B4B4B4;
.line {
width: 68rpx;
height: 1rpx;
background: #CCCCCC;
}
p {
margin: 0 20rpx;
}
}
.btn-wrapper {
display: flex;
align-items: center;
justify-content: center;
margin-top: 30rpx;
.btn {
display: flex;
align-items: center;
justify-content: center;
width: 68rpx;
height: 68rpx;
border-radius: 50%;
}
.apple-btn {
display: flex;
align-items: center;
justify-content: center;
width: 246rpx;
height: 66rpx;
margin-left: 30rpx;
background: #EAEAEA;
border-radius: 34rpx;
font-size: 24rpx;
.icon-s-pingguo {
color: #333;
margin-right: 10rpx;
font-size: 34rpx;
}
}
.iconfont {
font-size: 40rpx;
color: #fff;
}
.wx {
background-color: #61C64F;
}
.mima {
background-color: #28B3E9;
}
.yanzheng {
background-color: #F89C23;
}
.pingguo {
margin-left: 60rpx;
background-color: #000;
}
}
}
</style>