diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go index 2a7ba461..f93fec05 100644 --- a/api/handler/user_handler.go +++ b/api/handler/user_handler.go @@ -80,7 +80,7 @@ func (h *UserHandler) Register(c *gin.Context) { // check if the username is exists var item model.User res := h.db.Where("username = ?", data.Username).First(&item) - if res.RowsAffected > 0 { + if item.Id > 0 { resp.ERROR(c, "该用户名已经被注册") return } @@ -257,7 +257,7 @@ type userProfile struct { Calls int `json:"calls"` ImgCalls int `json:"img_calls"` TotalTokens int64 `json:"total_tokens"` - Tokens int64 `json:"tokens"` + Tokens int `json:"tokens"` ExpiredTime int64 `json:"expired_time"` Vip bool `json:"vip"` } diff --git a/database/update-v3.2.8.sql b/database/update-v3.2.8.sql new file mode 100644 index 00000000..bd031e71 --- /dev/null +++ b/database/update-v3.2.8.sql @@ -0,0 +1,23 @@ +-- 删除用户名重复的用户,只保留一条 +DELETE FROM chatgpt_users +WHERE username IN ( + SELECT username + FROM ( + SELECT username + FROM chatgpt_users + GROUP BY username + HAVING COUNT(*) > 1 + ) AS temp +) AND id NOT IN ( + SELECT MIN(id) + FROM ( + SELECT id, username + FROM chatgpt_users + GROUP BY id, username + HAVING COUNT(*) > 1 + ) AS temp + GROUP BY username +); + +-- 给 username 字段建立唯一索引 +ALTER TABLE `chatgpt_users` ADD UNIQUE(`username`) \ No newline at end of file diff --git a/web/src/assets/css/member.css b/web/src/assets/css/member.css new file mode 100644 index 00000000..2cf320ff --- /dev/null +++ b/web/src/assets/css/member.css @@ -0,0 +1,147 @@ +.member { + background-color: #282c34; + height: 100vh; +} +.member .el-dialog .el-dialog__body { + padding-top: 10px; +} +.member .el-dialog .el-dialog__body .pay-container .count-down { + display: flex; + justify-content: center; +} +.member .el-dialog .el-dialog__body .pay-container .pay-qrcode { + display: flex; + justify-content: center; +} +.member .el-dialog .el-dialog__body .pay-container .pay-qrcode .el-image { + width: 360px; + height: 360px; +} +.member .el-dialog .el-dialog__body .pay-container .tip { + display: flex; + justify-content: center; +} +.member .el-dialog .el-dialog__body .pay-container .tip .el-icon { + font-size: 24px; +} +.member .el-dialog .el-dialog__body .pay-container .tip .text { + font-size: 16px; + margin-left: 10px; +} +.member .el-dialog .el-dialog__body .pay-container .tip.success { + color: #07c160; +} +.member .title { + text-align: center; + background-color: #25272d; + font-size: 24px; + color: #fff; + padding: 10px; + border-bottom: 1px solid #3c3c3c; +} +.member .inner { + color: #fff; + padding: 15px 0 15px 15px; + overflow-x: hidden; + overflow-y: visible; +} +.member .inner .user-profile { + padding: 10px 20px 20px 20px; + background-color: #393f4a; + color: #fff; + border-radius: 10px; + height: 100vh; +} +.member .inner .user-profile .el-form-item__label { + color: #fff; + justify-content: start; +} +.member .inner .user-profile .user-opt .el-col { + padding: 10px; +} +.member .inner .user-profile .user-opt .el-col .el-button { + width: 100%; +} +.member .inner .product-box .info { + padding: 10px 20px 20px 0; +} +.member .inner .product-box .info .el-alert__description { + font-size: 14px !important; + margin: 0; +} +.member .inner .product-box .list-box .product-item { + border: 1px solid #666; + border-radius: 6px; + overflow: hidden; + cursor: pointer; + transition: all 0.3s ease; /* 添加过渡效果 */ +} +.member .inner .product-box .list-box .product-item .image-container { + display: flex; + justify-content: center; +} +.member .inner .product-box .list-box .product-item .image-container .el-image { + padding: 6px; +} +.member .inner .product-box .list-box .product-item .image-container .el-image .el-image__inner { + border-radius: 10px; +} +.member .inner .product-box .list-box .product-item .product-title { + display: flex; + padding: 10px; +} +.member .inner .product-box .list-box .product-item .product-title .name { + width: 100%; + text-align: center; + font-size: 16px; + font-weight: bold; + color: #47fff1; +} +.member .inner .product-box .list-box .product-item .product-info { + padding: 10px 20px; + font-size: 14px; + color: #999; +} +.member .inner .product-box .list-box .product-item .product-info .info-line { + display: flex; + width: 100%; + padding: 5px 0; +} +.member .inner .product-box .list-box .product-item .product-info .info-line .label { + display: flex; + width: 100%; +} +.member .inner .product-box .list-box .product-item .product-info .info-line .price, +.member .inner .product-box .list-box .product-item .product-info .info-line .expire, +.member .inner .product-box .list-box .product-item .product-info .info-line calls { + display: flex; + width: 90px; + justify-content: right; +} +.member .inner .product-box .list-box .product-item .product-info .info-line .price { + color: #f56c6c; +} +.member .inner .product-box .list-box .product-item .product-info .info-line .expire { + color: #409eff; +} +.member .inner .product-box .list-box .product-item .product-info .info-line .calls { + color: #f2cb51; +} +.member .inner .product-box .list-box .product-item .product-info .pay-way { + padding: 10px 0; + display: flex; + justify-content: space-between; +} +.member .inner .product-box .list-box .product-item .product-info .pay-way .iconfont { + margin-right: 5px; +} +.member .inner .product-box .list-box .product-item:hover { + box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */ + transform: translateY(-10px); /* 向上移动10像素 */ +} +.member .inner .product-box .headline { + padding: 0 20px 20px 0; +} +.member .inner .product-box .user-order { + padding: 0 20px 20px 0; +} diff --git a/web/src/assets/css/member.styl b/web/src/assets/css/member.styl new file mode 100644 index 00000000..65dcfcc2 --- /dev/null +++ b/web/src/assets/css/member.styl @@ -0,0 +1,191 @@ +.member { + background-color: #282c34; + height 100vh + + .el-dialog { + .el-dialog__body { + padding-top 10px + + .pay-container { + .count-down { + display flex + justify-content center + } + + .pay-qrcode { + display flex + justify-content center + + .el-image { + width 360px; + height 360px; + } + } + + .tip { + display flex + justify-content center + + .el-icon { + font-size 24px + } + + .text { + font-size: 16px + margin-left 10px + } + } + + .tip.success { + color #07c160 + } + } + } + } + + .title { + text-align center + background-color #25272d + font-size 24px + color #ffffff + padding 10px + border-bottom 1px solid #3c3c3c + } + + .inner { + color #ffffff + padding 15px 0 15px 15px; + overflow-x hidden + overflow-y visible + + .user-profile { + padding 10px 20px 20px 20px + background-color #393F4A + color #ffffff + border-radius 10px + height 100vh + + .el-form-item__label { + color #ffffff + justify-content start + } + + .user-opt { + .el-col { + padding 10px + + .el-button { + width 100% + } + } + } + } + + + .product-box { + .info { + .el-alert__description { + font-size 14px !important + margin 0 + } + padding 10px 20px 20px 0 + } + + .list-box { + .product-item { + border 1px solid #666666 + border-radius 6px + overflow hidden + cursor pointer + transition: all 0.3s ease; /* 添加过渡效果 */ + + .image-container { + display flex + justify-content center + + .el-image { + padding 6px + + .el-image__inner { + border-radius 10px + } + } + } + + .product-title { + display flex + padding 10px + + .name { + width 100% + text-align center + font-size 16px + font-weight bold + color #47fff1 + } + } + + .product-info { + padding 10px 20px + font-size 14px + color #999999 + + .info-line { + display flex + width 100% + padding 5px 0 + + .label { + display flex + width 100% + } + + .price, .expire, calls { + display flex + width 90px + justify-content right + } + + .price { + color #f56c6c + } + + .expire { + color #409eff + } + + .calls { + color #F2CB51 + } + } + + + .pay-way { + padding 10px 0 + display flex + justify-content: space-between + + .iconfont { + margin-right 5px + } + } + } + + &:hover { + box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */ + transform: translateY(-10px); /* 向上移动10像素 */ + } + } + } + + .headline { + padding 0 20px 20px 0 + } + + .user-order { + padding 0 20px 20px 0 + } + } + } + +} \ No newline at end of file diff --git a/web/src/components/ChatPrompt.vue b/web/src/components/ChatPrompt.vue index a1669c25..9bf53c43 100644 --- a/web/src/components/ChatPrompt.vue +++ b/web/src/components/ChatPrompt.vue @@ -9,7 +9,7 @@
{{ createdAt }} - tokens: {{ finalTokens }} + 算力消耗: {{ finalTokens }}
diff --git a/web/src/components/ChatReply.vue b/web/src/components/ChatReply.vue index 2aed7e5b..752282dc 100644 --- a/web/src/components/ChatReply.vue +++ b/web/src/components/ChatReply.vue @@ -9,7 +9,7 @@
{{ createdAt }} - tokens: {{ tokens }} + 算力消耗: {{ tokens }} {{ user['img_calls'] }} - + {{ user['tokens'] }} - + {{ user['total_tokens'] }} diff --git a/web/src/components/FileSelect.vue b/web/src/components/FileSelect.vue index 1b838f24..74d1e0ce 100644 --- a/web/src/components/FileSelect.vue +++ b/web/src/components/FileSelect.vue @@ -62,7 +62,7 @@ import {Delete, PictureFilled, Plus} from "@element-plus/icons-vue"; import {isImage, removeArrayItem} from "@/utils/libs"; const props = defineProps({ - userId: String, + userId: Number, }); const emits = defineEmits(['selected']); const show = ref(false) diff --git a/web/src/components/UserProfile.vue b/web/src/components/UserProfile.vue index 1d16ae84..0d07b7f1 100644 --- a/web/src/components/UserProfile.vue +++ b/web/src/components/UserProfile.vue @@ -35,10 +35,10 @@ {{ user['img_calls'] }} - + {{ user['tokens'] }} - + {{ user['total_tokens'] }} diff --git a/web/src/views/Member.vue b/web/src/views/Member.vue index 68798aff..8caeefd8 100644 --- a/web/src/views/Member.vue +++ b/web/src/views/Member.vue @@ -63,6 +63,18 @@ 长期有效
+
+ 对话次数: + {{ scope.item.calls }} + {{ vipMonthCalls }} +
+ +
+ 绘图次数: + {{ scope.item.img_calls }} + {{ vipMonthImgCalls }} +
+
支付宝 @@ -192,6 +204,8 @@ const countDownRef = ref(null) const orderTimeout = ref(1800) const loading = ref(true) const orderPayInfoText = ref("") +const vipMonthCalls = ref(0) +const vipMonthImgCalls = ref(0) const payWays = ref({}) const payName = ref("支付宝") @@ -218,6 +232,8 @@ onMounted(() => { if (res.data['order_pay_timeout'] > 0) { orderTimeout.value = res.data['order_pay_timeout'] } + vipMonthCalls.value = res.data['vip_month_calls'] + vipMonthImgCalls.value = res.data['vip_month_img_calls'] }).catch(e => { ElMessage.error("获取系统配置失败:" + e.message) }) @@ -341,191 +357,5 @@ const closeOrder = () => { diff --git a/web/src/views/mobile/Profile.vue b/web/src/views/mobile/Profile.vue index 6215edc9..80f60227 100644 --- a/web/src/views/mobile/Profile.vue +++ b/web/src/views/mobile/Profile.vue @@ -33,17 +33,12 @@ - + -
- - 提交 - -
@@ -66,7 +61,7 @@ const form = ref({ }) const fileList = ref([ { - url: 'https://fastly.jsdelivr.net/npm/@vant/assets/leaf.jpeg', + url: '', message: '上传中...', } ]); @@ -92,12 +87,15 @@ const afterRead = (file) => { formData.append('file', result, result.name); // 执行上传操作 httpPost('/api/upload', formData).then((res) => { - form.value.avatar = res.data + form.value.avatar = res.data.url file.status = 'success' - showNotify({type: 'success', message: '上传成功'}) + httpPost('/api/user/profile/update', form.value).then(() => { + showSuccessToast('上传成功') + }).catch(() => { + showFailToast('上传失败') + }) }).catch((e) => { - console.log(e.message) - showNotify({type: 'danger', message: '上传失败'}) + showNotify({type: 'danger', message: '上传失败:' + e.message}) }) }, error(err) { @@ -107,11 +105,7 @@ const afterRead = (file) => { }; const save = () => { - httpPost('/api/user/profile/update', form.value).then(() => { - showSuccessToast('保存成功') - }).catch(() => { - showFailToast('保存失败') - }) + }