add bind mobile, bind email, bind wechat function is ready

This commit is contained in:
RockYang 2024-08-14 15:56:50 +08:00
parent a2583c0591
commit ad6da0d320
18 changed files with 386 additions and 65 deletions

View File

@ -329,8 +329,10 @@ func (h *UserHandler) CLogin(c *gin.Context) {
// CLoginCallback 第三方登录回调
func (h *UserHandler) CLoginCallback(c *gin.Context) {
loginType := h.GetTrim(c, "login_type")
code := h.GetTrim(c, "code")
loginType := c.Query("login_type")
code := c.Query("code")
userId := h.GetInt(c, "user_id", 0)
action := c.Query("action")
var res types.BizVo
apiURL := fmt.Sprintf("%s/api/clogin/info", h.App.Config.ApiConfig.ApiURL)
@ -355,11 +357,34 @@ func (h *UserHandler) CLoginCallback(c *gin.Context) {
// login successfully
data := res.Data.(map[string]interface{})
session := gin.H{}
var user model.User
tx := h.DB.Debug().Where("openid", data["openid"]).First(&user)
if tx.Error != nil { // user not exist, create new user
// 检测最大注册人数
if action == "bind" && userId > 0 {
err = h.DB.Where("openid", data["openid"]).First(&user).Error
if err == nil {
resp.ERROR(c, "该微信已经绑定其他账号,请先解绑")
return
}
err = h.DB.Where("id", userId).First(&user).Error
if err != nil {
resp.ERROR(c, "绑定用户不存在")
return
}
err = h.DB.Model(&user).UpdateColumn("openid", data["openid"]).Error
if err != nil {
resp.ERROR(c, "更新用户信息失败,"+err.Error())
return
}
resp.SUCCESS(c, gin.H{"token": ""})
return
}
session := gin.H{}
tx := h.DB.Where("openid", data["openid"]).First(&user)
if tx.Error != nil {
// create new user
var totalUser int64
h.DB.Model(&model.User{}).Count(&totalUser)
if h.licenseService.GetLicense().Configs.UserNum > 0 && int(totalUser) >= h.licenseService.GetLicense().Configs.UserNum {
@ -534,7 +559,7 @@ func (h *UserHandler) UpdatePass(c *gin.Context) {
resp.SUCCESS(c)
}
// ResetPass 重置密码
// ResetPass 找回密码
func (h *UserHandler) ResetPass(c *gin.Context) {
var data struct {
Username string `json:"username"`
@ -572,11 +597,11 @@ func (h *UserHandler) ResetPass(c *gin.Context) {
}
}
// BindUsername 重置账
func (h *UserHandler) BindUsername(c *gin.Context) {
// BindMobile 绑定手机
func (h *UserHandler) BindMobile(c *gin.Context) {
var data struct {
Username string `json:"username"`
Code string `json:"code"`
Mobile string `json:"mobile"`
Code string `json:"code"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
@ -584,7 +609,7 @@ func (h *UserHandler) BindUsername(c *gin.Context) {
}
// 检查验证码
key := CodeStorePrefix + data.Username
key := CodeStorePrefix + data.Mobile
code, err := h.redis.Get(c, key).Result()
if err != nil || code != data.Code {
resp.ERROR(c, "验证码错误")
@ -593,19 +618,54 @@ func (h *UserHandler) BindUsername(c *gin.Context) {
// 检查手机号是否被其他账号绑定
var item model.User
res := h.DB.Where("username = ?", data.Username).First(&item)
res := h.DB.Where("mobile", data.Mobile).First(&item)
if res.Error == nil {
resp.ERROR(c, "该账号已经被其他账号绑定")
resp.ERROR(c, "该手机号已经绑定了其他账号,请更换手机号")
return
}
user, err := h.GetLoginUser(c)
if err != nil {
resp.NotAuth(c)
return
}
userId := h.GetLoginUserId(c)
err = h.DB.Model(&user).UpdateColumn("username", data.Username).Error
err = h.DB.Model(&item).Where("id", userId).UpdateColumn("mobile", data.Mobile).Error
if err != nil {
resp.ERROR(c, err.Error())
return
}
_ = h.redis.Del(c, key) // 删除短信验证码
resp.SUCCESS(c)
}
// BindEmail 绑定邮箱
func (h *UserHandler) BindEmail(c *gin.Context) {
var data struct {
Email string `json:"email"`
Code string `json:"code"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
// 检查验证码
key := CodeStorePrefix + data.Email
code, err := h.redis.Get(c, key).Result()
if err != nil || code != data.Code {
resp.ERROR(c, "验证码错误")
return
}
// 检查手机号是否被其他账号绑定
var item model.User
res := h.DB.Where("email", data.Email).First(&item)
if res.Error == nil {
resp.ERROR(c, "该邮箱地址已经绑定了其他账号,请更邮箱地址")
return
}
userId := h.GetLoginUserId(c)
err = h.DB.Model(&item).Where("id", userId).UpdateColumn("email", data.Email).Error
if err != nil {
resp.ERROR(c, err.Error())
return

View File

@ -231,7 +231,8 @@ func main() {
group.GET("profile", h.Profile)
group.POST("profile/update", h.ProfileUpdate)
group.POST("password", h.UpdatePass)
group.POST("bind/username", h.BindUsername)
group.POST("bind/mobile", h.BindMobile)
group.POST("bind/email", h.BindEmail)
group.POST("resetPass", h.ResetPass)
group.GET("clogin", h.CLogin)
group.GET("clogin/callback", h.CLoginCallback)

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4125778 */
src: url('iconfont.woff2?t=1723434190230') format('woff2'),
url('iconfont.woff?t=1723434190230') format('woff'),
url('iconfont.ttf?t=1723434190230') format('truetype');
src: url('iconfont.woff2?t=1723593727785') format('woff2'),
url('iconfont.woff?t=1723593727785') format('woff'),
url('iconfont.ttf?t=1723593727785') format('truetype');
}
.iconfont {
@ -13,6 +13,14 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-email:before {
content: "\e670";
}
.icon-mobile:before {
content: "\e79a";
}
.icon-drag:before {
content: "\e8ec";
}

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,20 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "15838472",
"name": "email",
"font_class": "email",
"unicode": "e670",
"unicode_decimal": 58992
},
{
"icon_id": "6151052",
"name": "mobile-alt",
"font_class": "mobile",
"unicode": "e79a",
"unicode_decimal": 59290
},
{
"icon_id": "15617554",
"name": "drag",

Binary file not shown.

View File

@ -0,0 +1,108 @@
<template>
<el-dialog
v-model="showDialog"
:close-on-click-modal="true"
style="max-width: 400px"
@close="close"
:title="title"
>
<div class="form">
<div class="text-center" v-if="email !== ''">当前已绑定邮箱{{ email }}</div>
<el-form label-position="top">
<el-form-item label="邮箱地址">
<el-input v-model="form.email"/>
</el-form-item>
<el-form-item label="验证码">
<el-row :gutter="0">
<el-col :span="16">
<el-input v-model="form.code" maxlength="6"/>
</el-col>
<el-col :span="8" style="padding-left: 10px">
<send-msg :receiver="form.email" type="email"/>
</el-col>
</el-row>
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="save">
提交绑定
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import {computed, ref, watch} from "vue";
import SendMsg from "@/components/SendMsg.vue";
import {ElMessage} from "element-plus";
import {httpPost} from "@/utils/http";
import {checkSession, removeUserInfo} from "@/store/cache";
const props = defineProps({
show: Boolean,
});
const showDialog = computed(() => {
return props.show
})
const title = ref('绑定邮箱')
const email = ref('')
const form = ref({
email: '',
code: ''
})
watch(showDialog, (val) => {
if (val) {
form.value.code = ''
form.value.email = ''
checkSession().then(user => {
email.value = user.email
})
}
})
const emits = defineEmits(['hide']);
const save = () => {
if (form.value.code === '') {
return ElMessage.error("请输入验证码");
}
httpPost('/api/user/bind/email', form.value).then(() => {
removeUserInfo()
ElMessage.success("绑定成功")
emits('hide')
}).catch(e => {
ElMessage.error("绑定失败:" + e.message);
})
}
const close = function () {
emits('hide');
}
</script>
<style lang="stylus" scoped>
.form {
.text-center {
text-align center
padding-bottom 15px
font-size 14px
color #a1a1a1
font-weight 700
}
.el-form-item__content {
.el-row {
width 100%
}
}
}
</style>

View File

@ -2,24 +2,24 @@
<el-dialog
v-model="showDialog"
:close-on-click-modal="true"
style="max-width: 600px"
:before-close="close"
style="max-width: 400px"
@close="close"
:title="title"
>
<div class="form">
<div class="text-center">当前已绑手机号{{ mobile }}</div>
<div class="text-center" v-if="mobile !== ''">当前已绑手机号{{ mobile }}</div>
<el-form :model="form" label-width="120px">
<el-form label-position="top">
<el-form-item label="手机号">
<el-input v-model="form.mobile"/>
</el-form-item>
<el-form-item label="验证码">
<el-row :gutter="20">
<el-row :gutter="0">
<el-col :span="16">
<el-input v-model="form.code" maxlength="6"/>
</el-col>
<el-col :span="8">
<send-msg size="" :receiver="form.username" type="mobile"/>
<el-col :span="8" style="padding-left: 10px">
<send-msg :receiver="form.mobile" type="mobile"/>
</el-col>
</el-row>
</el-form-item>
@ -37,12 +37,11 @@
</template>
<script setup>
import {computed, ref} from "vue";
import {computed, ref, watch} from "vue";
import SendMsg from "@/components/SendMsg.vue";
import {ElMessage} from "element-plus";
import {httpPost} from "@/utils/http";
import {validateEmail, validateMobile} from "@/utils/validate";
import {checkSession} from "@/store/cache";
import {checkSession, removeUserInfo} from "@/store/cache";
const props = defineProps({
show: Boolean,
@ -59,33 +58,37 @@ const form = ref({
code: ''
})
checkSession().then(user => {
mobile.value = user.mobile
watch(showDialog, (val) => {
if (val) {
form.value = {
mobile: '',
code: ''
}
checkSession().then(user => {
mobile.value = user.mobile
})
}
})
const emits = defineEmits(['hide']);
const save = () => {
if (!validateMobile(form.value.mobile) && !validateEmail(form.value.mobile)) {
return ElMessage.error("请输入合法的手机号/邮箱地址")
}
if (form.value.code === '') {
return ElMessage.error("请输入验证码");
}
httpPost('/api/user/bind/username', form.value).then(() => {
ElMessage.success({
message: '绑定成功',
duration: 1000,
onClose: () => emits('hide', false)
})
httpPost('/api/user/bind/mobile', form.value).then(() => {
removeUserInfo()
ElMessage.success("绑定成功")
emits('hide')
}).catch(e => {
ElMessage.error("绑定失败:" + e.message);
})
}
const close = function () {
emits('hide', false);
emits('hide');
}
</script>

View File

@ -66,7 +66,7 @@
<div class="c-login" v-if="wechatLoginURL !== ''">
<div class="text">其他登录方式</div>
<div class="login-type">
<a class="wechat-login" :href="wechatLoginURL"><i class="iconfont icon-wechat"></i></a>
<a class="wechat-login" :href="wechatLoginURL" @click="setRoute(router.currentRoute.value.path)"><i class="iconfont icon-wechat"></i></a>
</div>
</div>
</el-col>
@ -249,6 +249,8 @@ import {arrayContains} from "@/utils/libs";
import {getSystemInfo} from "@/store/cache";
import Captcha from "@/components/Captcha.vue";
import ResetPass from "@/components/ResetPass.vue";
import {setRoute} from "@/store/system";
import {useRouter} from "vue-router";
// eslint-disable-next-line no-undef
const props = defineProps({
@ -282,9 +284,10 @@ const emits = defineEmits(['hide', 'success']);
const action = ref("login")
const enableVerify = ref(false)
const showResetPass = ref(false)
const router = useRouter()
onMounted(() => {
const returnURL = `${location.protocol}//${location.host}/login/callback`
const returnURL = `${location.protocol}//${location.host}/login/callback?action=login`
httpGet("/api/user/clogin?return_url="+returnURL).then(res => {
wechatLoginURL.value = res.data.url
}).catch(e => {

View File

@ -2,7 +2,6 @@
<el-dialog
v-model="showDialog"
:close-on-click-modal="true"
:show-close="mobile !== ''"
:before-close="close"
:width="450"
:title="title"

View File

@ -58,7 +58,7 @@ const doSendMsg = (data) => {
canSend.value = false
httpPost('/api/sms/code', {receiver: props.receiver, key: data.key, dots: data.dots, x:data.x}).then(() => {
showMessageOK('验证码发送成功')
let time = 120
let time = 60
btnText.value = time
const handler = setInterval(() => {
time = time - 1

View File

@ -0,0 +1,93 @@
<template>
<el-dialog
v-model="showDialog"
:close-on-click-modal="true"
style="max-width: 400px"
@close="close"
:title="title"
>
<div class="third-login" v-loading="loading">
<div class="item" v-if="wechatBindURL !== ''">
<a class="link" :href="wechatBindURL"><i class="iconfont icon-wechat"></i></a>
<span class="text ok" v-if="openid !== ''">已绑定</span>
<span class="text" v-else>未绑定</span>
</div>
</div>
</el-dialog>
</template>
<script setup>
import {computed, ref, watch} from "vue";
import {httpGet} from "@/utils/http";
import {checkSession} from "@/store/cache";
import {showMessageError} from "@/utils/dialog";
const props = defineProps({
show: Boolean,
});
const emits = defineEmits(['hide']);
const showDialog = computed(() => {
return props.show
})
const title = ref('绑定第三方登录')
const openid = ref('')
const wechatBindURL = ref('')
const loading = ref(true)
watch(showDialog, (val) => {
if (val) {
checkSession().then(user => {
openid.value = user.openid
})
const returnURL = `${location.protocol}//${location.host}/login/callback?action=bind`
httpGet("/api/user/clogin?return_url="+returnURL).then(res => {
wechatBindURL.value = res.data.url
loading.value = false
}).catch(e => {
showMessageError(e.message)
})
}
})
const close = function () {
emits('hide');
}
</script>
<style lang="stylus" scoped>
.third-login {
display flex
justify-content center
min-height 100px
.item {
display flex
flex-flow column
align-items center
.link {
display flex
.iconfont {
font-size 30px
cursor pointer
background #e9f1f6
padding 10px
border-radius 50%
}
margin-bottom 10px
}
.text {
font-size 14px
}
.icon-wechat,.ok {
color: #0bc15f;
}
}
}
</style>

View File

@ -6,6 +6,7 @@
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import Storage from "good-storage";
import {useRouter} from "vue-router";
const MOBILE_THEME = process.env.VUE_APP_KEY_PREFIX + "MOBILE_THEME"
const ADMIN_THEME = process.env.VUE_APP_KEY_PREFIX + "ADMIN_THEME"
@ -62,4 +63,12 @@ export function FormatFileSize(bytes) {
const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
export function setRoute(path) {
Storage.set(process.env.VUE_APP_KEY_PREFIX + 'ROUTE_',path)
}
export function getRoute() {
return Storage.get(process.env.VUE_APP_KEY_PREFIX + 'ROUTE_')
}

View File

@ -114,7 +114,7 @@ onMounted(() => {
}).catch(() => {
})
const returnURL = `${location.protocol}//${location.host}/login/callback`
const returnURL = `${location.protocol}//${location.host}/login/callback?action=login`
httpGet("/api/user/clogin?return_url="+returnURL).then(res => {
wechatLoginURL.value = res.data.url
}).catch(e => {

View File

@ -39,9 +39,10 @@ import {useRouter} from "vue-router"
import {ElMessage, ElMessageBox} from "element-plus";
import {httpGet} from "@/utils/http";
import {setUserToken} from "@/store/session";
import {isMobile} from "@/utils/libs";
import Clipboard from "clipboard";
import {showMessageError, showMessageOK} from "@/utils/dialog";
import {getRoute} from "@/store/system";
import {checkSession, removeUserInfo} from "@/store/cache";
const winHeight = ref(window.innerHeight)
const loading = ref(true)
@ -52,12 +53,25 @@ const password = ref('')
const code = router.currentRoute.value.query.code
const action = router.currentRoute.value.query.action
if (code === "") {
ElMessage.error({message: "登录失败code 参数不能为空",duration: 2000, onClose: () => router.push("/")})
} else {
checkSession().then(user => {
// bind user
doLogin(user.id)
}).catch(() => {
doLogin(0)
})
}
const doLogin = (userId) => {
//
httpGet("/api/user/clogin/callback",{login_type: "wx",code: code}).then(res => {
setUserToken(res.data.token)
httpGet("/api/user/clogin/callback",{login_type: "wx",code: code, action:action, user_id: userId}).then(res => {
removeUserInfo()
if (res.data.token) {
setUserToken(res.data.token)
}
if (res.data.username) {
username.value = res.data.username
password.value = res.data.password
@ -96,11 +110,7 @@ onUnmounted(() => {
const finishLogin = () => {
show.value = false
if (isMobile()) {
router.push('/mobile')
} else {
router.push('/chat')
}
router.push(getRoute())
}
</script>

View File

@ -7,13 +7,19 @@
<el-row class="user-opt" :gutter="20">
<el-col :span="12">
<el-button type="primary" @click="showPasswordDialog = true">修改密码</el-button>
<el-button type="primary" @click="showBindEmailDialog = true">绑定邮箱</el-button>
</el-col>
<el-col :span="12">
<el-button type="primary" @click="showBindMobileDialog = true">绑定手机</el-button>
</el-col>
<el-col :span="12">
<el-button type="primary" @click="showThirdLoginDialog = true">第三方登录</el-button>
</el-col>
<el-col :span="12">
<el-button type="primary" @click="showPasswordDialog = true">修改密码</el-button>
</el-col>
<el-col :span="24">
<el-button type="success" @click="showRedeemVerifyDialog = true">兑换码核销
<el-button type="primary" @click="showRedeemVerifyDialog = true">卡密兑换
</el-button>
</el-col>
@ -95,6 +101,9 @@
<bind-mobile v-if="isLogin" :show="showBindMobileDialog" @hide="showBindMobileDialog = false"/>
<bind-email v-if="isLogin" :show="showBindEmailDialog" @hide="showBindEmailDialog = false"/>
<third-login v-if="isLogin" :show="showThirdLoginDialog" @hide="showThirdLoginDialog = false"/>
<redeem-verify v-if="isLogin" :show="showRedeemVerifyDialog" @hide="redeemCallback"/>
<el-dialog
@ -149,6 +158,8 @@ import {removeUserToken} from "@/store/session";
import UserOrder from "@/components/UserOrder.vue";
import CountDown from "@/components/CountDown.vue";
import {useSharedStore} from "@/store/sharedata";
import BindEmail from "@/components/BindEmail.vue";
import ThirdLogin from "@/components/ThirdLogin.vue";
const list = ref([])
const showPayDialog = ref(false)
@ -156,9 +167,11 @@ const vipImg = ref("/images/vip.png")
const enableReward = ref(false) //
const rewardImg = ref('/images/reward.png')
const qrcode = ref("")
const showPasswordDialog = ref(false);
const showBindMobileDialog = ref(false);
const showRedeemVerifyDialog = ref(false);
const showPasswordDialog = ref(false)
const showBindMobileDialog = ref(false)
const showBindEmailDialog = ref(false)
const showRedeemVerifyDialog = ref(false)
const showThirdLoginDialog = ref(false)
const text = ref("")
const user = ref(null)
const isLogin = ref(false)