mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-17 16:56:38 +08:00
feat: optimize login dialog
This commit is contained in:
parent
c5200aada8
commit
8f47474edd
@ -146,6 +146,7 @@ func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
|
|||||||
c.Request.URL.Path == "/api/invite/hits" ||
|
c.Request.URL.Path == "/api/invite/hits" ||
|
||||||
c.Request.URL.Path == "/api/sd/imgWall" ||
|
c.Request.URL.Path == "/api/sd/imgWall" ||
|
||||||
c.Request.URL.Path == "/api/sd/client" ||
|
c.Request.URL.Path == "/api/sd/client" ||
|
||||||
|
c.Request.URL.Path == "/api/config/get" ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
|
||||||
|
@ -277,6 +277,128 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
|||||||
resp.SUCCESS(c, gin.H{"order_no": orderNo, "image": fmt.Sprintf("data:image/jpg;base64, %s", imgDataBase64), "url": imageURL})
|
resp.SUCCESS(c, gin.H{"order_no": orderNo, "image": fmt.Sprintf("data:image/jpg;base64, %s", imgDataBase64), "url": imageURL})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mobile 移动端支付
|
||||||
|
func (h *PaymentHandler) Mobile(c *gin.Context) {
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
PayWay string `json:"pay_way"` // 支付方式
|
||||||
|
ProductId uint `json:"product_id"`
|
||||||
|
UserId int `json:"user_id"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var product model.Product
|
||||||
|
res := h.db.First(&product, data.ProductId)
|
||||||
|
if res.Error != nil {
|
||||||
|
resp.ERROR(c, "Product not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orderNo, err := h.snowflake.Next(false)
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, "error with generate trade no: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var user model.User
|
||||||
|
res = h.db.First(&user, data.UserId)
|
||||||
|
if res.Error != nil {
|
||||||
|
resp.ERROR(c, "Invalid user ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var payWay string
|
||||||
|
var notifyURL string
|
||||||
|
switch data.PayWay {
|
||||||
|
case "hupi":
|
||||||
|
payWay = PayWayXunHu
|
||||||
|
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
|
||||||
|
case "payjs":
|
||||||
|
payWay = PayWayJs
|
||||||
|
notifyURL = h.App.Config.JPayConfig.NotifyURL
|
||||||
|
default:
|
||||||
|
payWay = PayWayAlipay
|
||||||
|
notifyURL = h.App.Config.AlipayConfig.NotifyURL
|
||||||
|
}
|
||||||
|
// 创建订单
|
||||||
|
remark := types.OrderRemark{
|
||||||
|
Days: product.Days,
|
||||||
|
Power: product.Power,
|
||||||
|
Name: product.Name,
|
||||||
|
Price: product.Price,
|
||||||
|
Discount: product.Discount,
|
||||||
|
}
|
||||||
|
|
||||||
|
amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64()
|
||||||
|
order := model.Order{
|
||||||
|
UserId: user.Id,
|
||||||
|
Username: user.Username,
|
||||||
|
ProductId: product.Id,
|
||||||
|
OrderNo: orderNo,
|
||||||
|
Subject: product.Name,
|
||||||
|
Amount: amount,
|
||||||
|
Status: types.OrderNotPaid,
|
||||||
|
PayWay: payWay,
|
||||||
|
Remark: utils.JsonEncode(remark),
|
||||||
|
}
|
||||||
|
res = h.db.Create(&order)
|
||||||
|
if res.Error != nil || res.RowsAffected == 0 {
|
||||||
|
resp.ERROR(c, "error with create order: "+res.Error.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PayJs 单独处理,只能用官方生成的二维码
|
||||||
|
if data.PayWay == "payjs" {
|
||||||
|
params := payment.JPayReq{
|
||||||
|
TotalFee: int(math.Ceil(order.Amount * 100)),
|
||||||
|
OutTradeNo: order.OrderNo,
|
||||||
|
Subject: product.Name,
|
||||||
|
}
|
||||||
|
r := h.js.Pay(params)
|
||||||
|
if r.IsOK() {
|
||||||
|
resp.SUCCESS(c, gin.H{"order_no": order.OrderNo, "image": r.Qrcode})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
resp.ERROR(c, "error with generating payment qrcode: "+r.ReturnMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var logo string
|
||||||
|
if data.PayWay == "alipay" {
|
||||||
|
logo = "res/img/alipay.jpg"
|
||||||
|
} else if data.PayWay == "hupi" {
|
||||||
|
if h.App.Config.HuPiPayConfig.Name == "wechat" {
|
||||||
|
logo = "res/img/wechat-pay.jpg"
|
||||||
|
} else {
|
||||||
|
logo = "res/img/alipay.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := h.fs.Open(logo)
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, "error with open qrcode log file: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parse, err := url.Parse(notifyURL)
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
imageURL := fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s", parse.Scheme, parse.Host, orderNo, data.PayWay)
|
||||||
|
imgData, err := utils.GenQrcode(imageURL, 400, file)
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
imgDataBase64 := base64.StdEncoding.EncodeToString(imgData)
|
||||||
|
resp.SUCCESS(c, gin.H{"order_no": orderNo, "image": fmt.Sprintf("data:image/jpg;base64, %s", imgDataBase64), "url": imageURL})
|
||||||
|
}
|
||||||
|
|
||||||
// 异步通知回调公共逻辑
|
// 异步通知回调公共逻辑
|
||||||
func (h *PaymentHandler) notify(orderNo string, tradeNo string) error {
|
func (h *PaymentHandler) notify(orderNo string, tradeNo string) error {
|
||||||
var order model.Order
|
var order model.Order
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
VUE_APP_API_HOST=http://localhost:5678
|
VUE_APP_API_HOST=http://172.22.11.200:5678
|
||||||
VUE_APP_WS_HOST=ws://localhost:5678
|
VUE_APP_WS_HOST=ws://172.22.11.200:5678
|
||||||
VUE_APP_USER=18575670125
|
VUE_APP_USER=18575670125
|
||||||
VUE_APP_PASS=12345678
|
VUE_APP_PASS=12345678
|
||||||
VUE_APP_ADMIN_USER=admin
|
VUE_APP_ADMIN_USER=admin
|
||||||
|
723
web/package-lock.json
generated
723
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -40,7 +40,8 @@
|
|||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-plugin-vue": "^8.0.3",
|
"eslint-plugin-vue": "^8.0.3",
|
||||||
"stylus": "^0.58.1",
|
"stylus": "^0.58.1",
|
||||||
"stylus-loader": "^7.0.0"
|
"stylus-loader": "^7.0.0",
|
||||||
|
"webpack": "^5.90.3"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
|
7627
web/pnpm-lock.yaml
7627
web/pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,26 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {ElConfigProvider} from 'element-plus';
|
import {ElConfigProvider} from 'element-plus';
|
||||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||||
|
|
||||||
|
const debounce = (fn, delay) => {
|
||||||
|
let timer
|
||||||
|
return (...args) => {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
fn(...args)
|
||||||
|
}, delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _ResizeObserver = window.ResizeObserver;
|
||||||
|
window.ResizeObserver = class ResizeObserver extends _ResizeObserver {
|
||||||
|
constructor(callback) {
|
||||||
|
callback = debounce(callback, 200);
|
||||||
|
super(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,12 +3,21 @@
|
|||||||
class="login-dialog"
|
class="login-dialog"
|
||||||
v-model="showDialog"
|
v-model="showDialog"
|
||||||
:close-on-click-modal="true"
|
:close-on-click-modal="true"
|
||||||
:show-close="true"
|
:show-close="false"
|
||||||
:before-close="close"
|
:before-close="close"
|
||||||
:width="400"
|
|
||||||
title="用户登录"
|
|
||||||
>
|
>
|
||||||
<div class="form">
|
<template #header="{ close, titleId, titleClass }">
|
||||||
|
<div class="header">
|
||||||
|
<div class="title">用户登录</div>
|
||||||
|
<div class="close-icon">
|
||||||
|
<el-icon>
|
||||||
|
<Close/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="login-box">
|
||||||
<el-form label-width="75px">
|
<el-form label-width="75px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<template #label>
|
<template #label>
|
||||||
@ -20,7 +29,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<el-input v-model="username" size="large" placeholder="手机号码"/>
|
<el-input v-model="data.username" size="large" placeholder="账号"/>
|
||||||
</template>
|
</template>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
@ -33,15 +42,100 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<el-input v-model="password" type="password" size="large" placeholder="密码"/>
|
<el-input v-model="data.password" type="password" size="large" placeholder="密码"/>
|
||||||
</template>
|
</template>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<div class="login-btn">
|
<div class="login-btn">
|
||||||
<el-button type="primary" @click="submit" size="large" round>登录</el-button>
|
<el-button type="primary" @click="submit" size="large" round>登录</el-button>
|
||||||
|
<el-button plain @click="submit" size="large" round>注册</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="register-box">
|
||||||
|
<el-form :model="data" label-width="120px" ref="formRef">
|
||||||
|
<div class="block">
|
||||||
|
<el-input placeholder="账号"
|
||||||
|
size="large"
|
||||||
|
v-model="data.username"
|
||||||
|
autocomplete="off">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Iphone/>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<el-input placeholder="请输入密码(8-16位)"
|
||||||
|
maxlength="16" size="large"
|
||||||
|
v-model="data.password" show-password
|
||||||
|
autocomplete="off">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Lock/>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<el-input placeholder="重复密码(8-16位)"
|
||||||
|
size="large" maxlength="16" v-model="data.repass" show-password
|
||||||
|
autocomplete="off">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Lock/>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<el-row :gutter="10">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-input placeholder="验证码"
|
||||||
|
size="large" maxlength="30"
|
||||||
|
v-model="data.code"
|
||||||
|
autocomplete="off">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Checked/>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<send-msg size="large" :receiver="data.username"/>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<el-input placeholder="邀请码(可选)"
|
||||||
|
size="large"
|
||||||
|
v-model="data.invite_code"
|
||||||
|
autocomplete="off">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Message/>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-row class="btn-row">
|
||||||
|
<el-button class="login-btn" type="primary" @click="">注册</el-button>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row class="text-line">
|
||||||
|
已经有账号?
|
||||||
|
<el-link type="primary" @click="">登录</el-link>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -51,7 +145,8 @@ import {httpPost} from "@/utils/http";
|
|||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {setUserToken} from "@/store/session";
|
import {setUserToken} from "@/store/session";
|
||||||
import {validateMobile} from "@/utils/validate";
|
import {validateMobile} from "@/utils/validate";
|
||||||
import {Lock, User} from "@element-plus/icons-vue";
|
import {Checked, Close, Iphone, Lock, Message, Position, User} from "@element-plus/icons-vue";
|
||||||
|
import SendMsg from "@/components/SendMsg.vue";
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -60,19 +155,14 @@ const props = defineProps({
|
|||||||
const showDialog = computed(() => {
|
const showDialog = computed(() => {
|
||||||
return props.show
|
return props.show
|
||||||
})
|
})
|
||||||
const username = ref("")
|
const data = ref({
|
||||||
const password = ref("")
|
username: "",
|
||||||
|
password: ""
|
||||||
|
})
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
const emits = defineEmits(['hide']);
|
const emits = defineEmits(['hide']);
|
||||||
const submit = function () {
|
const submit = function () {
|
||||||
if (!validateMobile(username.value)) {
|
httpPost('/api/user/login', data.value).then((res) => {
|
||||||
return ElMessage.error('请输入合法的手机号');
|
|
||||||
}
|
|
||||||
if (password.value.trim() === '') {
|
|
||||||
return ElMessage.error('请输入密码');
|
|
||||||
}
|
|
||||||
|
|
||||||
httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
|
|
||||||
setUserToken(res.data)
|
setUserToken(res.data)
|
||||||
ElMessage.success("登录成功!")
|
ElMessage.success("登录成功!")
|
||||||
emits("hide")
|
emits("hide")
|
||||||
@ -87,30 +177,58 @@ const close = function () {
|
|||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
.login-dialog {
|
.login-dialog {
|
||||||
border-radius 20px
|
border-radius 10px
|
||||||
|
max-width 600px
|
||||||
|
|
||||||
.label {
|
.header {
|
||||||
padding-top 3px
|
position relative
|
||||||
|
|
||||||
.el-icon {
|
.title {
|
||||||
position relative
|
padding 0
|
||||||
|
font-size 18px
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
cursor pointer
|
||||||
|
position absolute
|
||||||
|
right 0
|
||||||
|
top 0
|
||||||
|
font-weight normal
|
||||||
font-size 20px
|
font-size 20px
|
||||||
margin-right 6px
|
|
||||||
top 4px
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
&:hover {
|
||||||
font-size 16px
|
color #20a0ff
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-btn {
|
.login-box {
|
||||||
text-align center
|
.label {
|
||||||
padding-top 10px
|
padding-top 3px
|
||||||
|
|
||||||
.el-button {
|
.el-icon {
|
||||||
width 50%
|
position relative
|
||||||
|
font-size 20px
|
||||||
|
margin-right 6px
|
||||||
|
top 4px
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size 16px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn {
|
||||||
|
display flex
|
||||||
|
padding-top 10px
|
||||||
|
justify-content center
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
font-size 16px
|
||||||
|
width 100px
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -237,6 +237,8 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<config-dialog v-if="isLogin" :show="showConfigDialog" :models="models" @hide="showConfigDialog = false"/>
|
<config-dialog v-if="isLogin" :show="showConfigDialog" :models="models" @hide="showConfigDialog = false"/>
|
||||||
|
|
||||||
|
<login-dialog :show="true"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -271,6 +273,7 @@ import {checkSession} from "@/action/session";
|
|||||||
import Welcome from "@/components/Welcome.vue";
|
import Welcome from "@/components/Welcome.vue";
|
||||||
import ChatMidJourney from "@/components/ChatMidJourney.vue";
|
import ChatMidJourney from "@/components/ChatMidJourney.vue";
|
||||||
import FileSelect from "@/components/FileSelect.vue";
|
import FileSelect from "@/components/FileSelect.vue";
|
||||||
|
import LoginDialog from "@/components/LoginDialog.vue";
|
||||||
|
|
||||||
const title = ref('ChatGPT-智能助手');
|
const title = ref('ChatGPT-智能助手');
|
||||||
const models = ref([])
|
const models = ref([])
|
||||||
|
Loading…
Reference in New Issue
Block a user