验证码配置重构完成

This commit is contained in:
RockYang
2025-08-29 17:45:31 +08:00
parent 539e91c12e
commit 3a6f8ccc16
32 changed files with 272 additions and 248 deletions

View File

@@ -94,7 +94,6 @@ type BaseConfig struct {
DefaultNickname string `json:"default_nickname"` // 默认昵称
ICP string `json:"icp"` // ICP 备案号
EnabledVerify bool `json:"enabled_verify"` // 是否启用验证码
EmailWhiteList []string `json:"email_white_list"` // 邮箱白名单列表
AssistantModelId int `json:"assistant_model_id"` // 用来做提示词,翻译的AI模型 id
MaxFileSize int `json:"max_file_size"` // 最大文件大小,单位MB
@@ -118,6 +117,7 @@ const (
ConfigKeyNotice = "notice"
ConfigKeyAgreement = "agreement"
ConfigKeyPrivacy = "privacy"
ConfigKeyMarkMap = "mark_map"
ConfigKeyCaptcha = "captcha"
ConfigKeyWxLogin = "wx_login"
ConfigKeyLicense = "license"

View File

@@ -81,18 +81,18 @@ func (h *ManagerHandler) Login(c *gin.Context) {
return
}
if h.App.SysConfig.Base.EnabledVerify {
var check bool
if data.X != 0 {
check = h.captcha.SlideCheck(data)
} else {
check = h.captcha.Check(data)
}
if !check {
resp.ERROR(c, "请先完人机验证")
return
}
}
// if h.App.SysConfig.Base.EnabledVerify {
// var check bool
// if data.X != 0 {
// check = h.captcha.SlideCheck(data)
// } else {
// check = h.captcha.Check(data)
// }
// if !check {
// resp.ERROR(c, "请先完人机验证")
// return
// }
// }
var manager model.AdminUser
res := h.DB.Model(&model.AdminUser{}).Where("username = ?", data.Username).First(&manager)

View File

@@ -92,6 +92,9 @@ func (h *ConfigHandler) RegisterRoutes() {
rg.POST("update/base", h.UpdateBase)
rg.POST("update/power", h.UpdatePower)
rg.POST("update/notice", h.UpdateNotice)
rg.POST("update/agreement", h.UpdateAgreement)
rg.POST("update/privacy", h.UpdatePrivacy)
rg.POST("update/mark_map", h.UpdateMarkMap)
rg.POST("update/captcha", h.UpdateCaptcha)
rg.POST("update/wx_login", h.UpdateWxLogin)
rg.POST("update/payment", h.UpdatePayment)
@@ -193,6 +196,64 @@ func (h *ConfigHandler) UpdateNotice(c *gin.Context) {
resp.SUCCESS(c, data)
}
// UpdateAgreement 更新用户协议配置
func (h *ConfigHandler) UpdateAgreement(c *gin.Context) {
var data struct {
Content string `json:"content"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.Update(types.ConfigKeyAgreement, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
resp.SUCCESS(c, data)
}
// UpdatePrivacy 更新隐私政策配置
func (h *ConfigHandler) UpdatePrivacy(c *gin.Context) {
var data struct {
Content string `json:"content"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.Update(types.ConfigKeyPrivacy, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
resp.SUCCESS(c, data)
}
// UpdateMarkMap 更新思维导图配置
func (h *ConfigHandler) UpdateMarkMap(c *gin.Context) {
var data struct {
Content string `json:"content"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.Update(types.ConfigKeyMarkMap, data)
if err != nil {
resp.ERROR(c, err.Error())
return
}
resp.SUCCESS(c, data)
}
// UpdateCaptcha 更新行为验证码配置
func (h *ConfigHandler) UpdateCaptcha(c *gin.Context) {
var data types.CaptchaConfig
@@ -206,10 +267,7 @@ func (h *ConfigHandler) UpdateCaptcha(c *gin.Context) {
resp.ERROR(c, err.Error())
return
}
if data.Enabled {
h.captchaService.UpdateConfig(data)
}
h.sysConfig.Captcha = data
h.captchaService.UpdateConfig(data)
resp.SUCCESS(c, data)
}

View File

@@ -16,16 +16,13 @@ import (
"github.com/gin-gonic/gin"
)
// 今日头条函数实现
type CaptchaHandler struct {
App *core.AppServer
service *service.CaptchaService
config types.CaptchaConfig
}
func NewCaptchaHandler(app *core.AppServer, s *service.CaptchaService, sysConfig *types.SystemConfig) *CaptchaHandler {
return &CaptchaHandler{App: app, service: s, config: sysConfig.Captcha}
return &CaptchaHandler{App: app, service: s}
}
// RegisterRoutes 注册路由
@@ -37,10 +34,15 @@ func (h *CaptchaHandler) RegisterRoutes() {
group.POST("check", h.Check)
group.GET("slide/get", h.SlideGet)
group.POST("slide/check", h.SlideCheck)
group.GET("config", h.GetConfig)
}
func (h *CaptchaHandler) GetConfig(c *gin.Context) {
resp.SUCCESS(c, gin.H{"enabled": h.service.GetConfig().Enabled, "type": h.service.GetConfig().Type})
}
func (h *CaptchaHandler) Get(c *gin.Context) {
if !h.config.Enabled {
if !h.service.GetConfig().Enabled {
resp.ERROR(c, "验证码服务未启用")
return
}
@@ -56,7 +58,7 @@ func (h *CaptchaHandler) Get(c *gin.Context) {
// Check verify the captcha data
func (h *CaptchaHandler) Check(c *gin.Context) {
if !h.config.Enabled {
if !h.service.GetConfig().Enabled {
resp.ERROR(c, "验证码服务未启用")
return
}
@@ -80,7 +82,7 @@ func (h *CaptchaHandler) Check(c *gin.Context) {
// SlideGet 获取滑动验证图片
func (h *CaptchaHandler) SlideGet(c *gin.Context) {
if !h.config.Enabled {
if !h.service.GetConfig().Enabled {
resp.ERROR(c, "验证码服务未启用")
return
}
@@ -96,7 +98,7 @@ func (h *CaptchaHandler) SlideGet(c *gin.Context) {
// SlideCheck 滑动验证结果校验
func (h *CaptchaHandler) SlideCheck(c *gin.Context) {
if !h.config.Enabled {
if !h.service.GetConfig().Enabled {
resp.ERROR(c, "验证码服务未启用")
return
}

View File

@@ -9,7 +9,6 @@ package handler
import (
"geekai/core"
"geekai/core/middleware"
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
@@ -30,12 +29,7 @@ func NewChatModelHandler(app *core.AppServer, db *gorm.DB) *ChatModelHandler {
// RegisterRoutes 注册路由
func (h *ChatModelHandler) RegisterRoutes() {
group := h.App.Engine.Group("/api/model/")
// 需要用户授权的接口
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
{
group.GET("list", h.List)
}
group.GET("list", h.List)
}
// List 模型列表

View File

@@ -24,10 +24,10 @@ const CodeStorePrefix = "/verify/codes/"
type SmsHandler struct {
BaseHandler
redis *redis.Client
sms *sms.SmsManager
smtp *service.SmtpService
captcha *service.CaptchaService
redis *redis.Client
sms *sms.SmsManager
smtp *service.SmtpService
captchaService *service.CaptchaService
}
func NewSmsHandler(
@@ -37,11 +37,11 @@ func NewSmsHandler(
smtp *service.SmtpService,
captcha *service.CaptchaService) *SmsHandler {
return &SmsHandler{
redis: client,
sms: sms,
captcha: captcha,
smtp: smtp,
BaseHandler: BaseHandler{App: app}}
redis: client,
sms: sms,
captchaService: captcha,
smtp: smtp,
BaseHandler: BaseHandler{App: app}}
}
// RegisterRoutes 注册路由
@@ -63,12 +63,12 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
resp.ERROR(c, types.InvalidArgs)
return
}
if h.App.SysConfig.Base.EnabledVerify {
if h.captchaService.GetConfig().Enabled {
var check bool
if data.X != 0 {
check = h.captcha.SlideCheck(data)
check = h.captchaService.SlideCheck(data)
} else {
check = h.captcha.Check(data)
check = h.captchaService.Check(data)
}
if !check {
resp.ERROR(c, "请先完人机验证")

View File

@@ -35,7 +35,7 @@ type UserHandler struct {
redis *redis.Client
levelDB *store.LevelDB
licenseService *service.LicenseService
captcha *service.CaptchaService
captchaService *service.CaptchaService
userService *service.UserService
}
@@ -53,7 +53,7 @@ func NewUserHandler(
searcher: searcher,
redis: client,
levelDB: levelDB,
captcha: captcha,
captchaService: captcha,
licenseService: licenseService,
userService: userService,
}
@@ -104,12 +104,13 @@ func (h *UserHandler) Register(c *gin.Context) {
return
}
if h.App.SysConfig.Base.EnabledVerify && data.RegWay == "username" {
// 如果注册方式不是账号密码,则需要验证码
if h.captchaService.GetConfig().Enabled && data.RegWay != "username" {
var check bool
if data.X != 0 {
check = h.captcha.SlideCheck(data)
check = h.captchaService.SlideCheck(data)
} else {
check = h.captcha.Check(data)
check = h.captchaService.Check(data)
}
if !check {
resp.ERROR(c, "请先完人机验证")
@@ -279,15 +280,12 @@ func (h *UserHandler) Login(c *gin.Context) {
resp.ERROR(c, types.InvalidArgs)
return
}
verifyKey := fmt.Sprintf("users/verify/%s", data.Username)
needVerify, err := h.redis.Get(c, verifyKey).Bool()
if h.App.SysConfig.Base.EnabledVerify && needVerify {
if h.captchaService.GetConfig().Enabled {
var check bool
if data.X != 0 {
check = h.captcha.SlideCheck(data)
check = h.captchaService.SlideCheck(data)
} else {
check = h.captcha.Check(data)
check = h.captchaService.Check(data)
}
if !check {
resp.ERROR(c, "请先完人机验证")
@@ -298,19 +296,17 @@ func (h *UserHandler) Login(c *gin.Context) {
var user model.User
res := h.DB.Where("username = ?", data.Username).First(&user)
if res.Error != nil {
h.redis.Set(c, verifyKey, true, 0)
resp.ERROR(c, "用户名不存在")
return
}
password := utils.GenPassword(data.Password, user.Salt)
if password != user.Password {
h.redis.Set(c, verifyKey, true, 0)
resp.ERROR(c, "用户名或密码错误")
return
}
if user.Status == false {
if !user.Status {
resp.ERROR(c, "该用户已被禁止登录,请联系管理员")
return
}
@@ -343,8 +339,6 @@ func (h *UserHandler) Login(c *gin.Context) {
resp.ERROR(c, "error with save token: "+err.Error())
return
}
// 移除登录行为验证码
h.redis.Del(c, verifyKey)
resp.SUCCESS(c, gin.H{"token": tokenString, "user_id": user.Id, "username": user.Username})
}

View File

@@ -31,6 +31,10 @@ func (s *CaptchaService) UpdateConfig(config types.CaptchaConfig) {
s.config = config
}
func (s *CaptchaService) GetConfig() types.CaptchaConfig {
return s.config
}
func (s *CaptchaService) Get() (interface{}, error) {
url := fmt.Sprintf("%s/api/captcha/get", types.GeekAPIURL)
var res types.BizVo
@@ -48,7 +52,7 @@ func (s *CaptchaService) Get() (interface{}, error) {
return res.Data, nil
}
func (s *CaptchaService) Check(data interface{}) bool {
func (s *CaptchaService) Check(data any) bool {
url := fmt.Sprintf("%s/api/captcha/check", types.GeekAPIURL)
var res types.BizVo
r, err := s.client.R().
@@ -66,7 +70,7 @@ func (s *CaptchaService) Check(data interface{}) bool {
return true
}
func (s *CaptchaService) SlideGet() (interface{}, error) {
func (s *CaptchaService) SlideGet() (any, error) {
url := fmt.Sprintf("%s/api/captcha/slide/get", types.GeekAPIURL)
var res types.BizVo
r, err := s.client.R().
@@ -83,7 +87,7 @@ func (s *CaptchaService) SlideGet() (interface{}, error) {
return res.Data, nil
}
func (s *CaptchaService) SlideCheck(data interface{}) bool {
func (s *CaptchaService) SlideCheck(data any) bool {
url := fmt.Sprintf("%s/api/captcha/slide/check", types.GeekAPIURL)
var res types.BizVo
r, err := s.client.R().

View File

@@ -4,19 +4,16 @@
v-model="show"
:close-on-click-modal="true"
:show-close="isMobileInternal"
:append-to-body="true"
style="width: 360px; --el-dialog-padding-primary: 5px 15px 15px 15px"
>
<template #header>
<div
class="text-center p-3"
style="color: var(--el-text-color-primary)"
v-if="isMobileInternal"
>
<div class="text-center p-3" style="color: var(--el-text-color-primary)">
<span>人机验证</span>
</div>
</template>
<slide-captcha
v-if="isMobileInternal"
v-if="type === 'slide'"
:bg-img="bgImg"
:bk-img="bkImg"
:result="result"
@@ -48,6 +45,13 @@ import { isMobile } from '@/utils/libs'
import lodash from 'lodash'
import { ref } from 'vue'
const props = defineProps({
type: {
type: String,
default: 'slide',
},
})
const show = ref(false)
const maxDot = ref(5)
const imageBase64 = ref('')
@@ -98,7 +102,7 @@ const handleConfirm = (dts) => {
const loadCaptcha = () => {
show.value = true
// 手机用滑动验证码
if (isMobile()) {
if (props.type === 'slide') {
getSlideCaptcha()
} else {
handleRequestCaptCode()

View File

@@ -226,6 +226,7 @@ export default {
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
height: 360px;
.wg-cap-wrap__header {
height: 50px;

View File

@@ -207,6 +207,17 @@
</el-input>
</div>
<div class="block text-sm">
<el-checkbox v-model="agreeChecked">
我已阅读并同意
<a href="javascript:void(0)" class="text-blue-500" @click="openAgreement"
>用户协议</a
>
<a href="javascript:void(0)" class="text-blue-500" @click="openPrivacy">隐私政策</a>
</el-checkbox>
</div>
<div class="w-full">
<button
class="w-full h-12 rounded-xl text-base font-medium text-white bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg active:translate-y-0 shadow-md"
@@ -252,9 +263,17 @@
</el-row>
</div>
</div>
<captcha v-if="enableVerify" @success="submit" ref="captchaRef" />
<captcha v-if="enableCaptcha" :type="captchaType" @success="submit" ref="captchaRef" />
<reset-pass @hide="showResetPass = false" :show="showResetPass" />
<el-dialog v-model="showAgreement" title="用户协议" :append-to-body="true">
<div class="prose" v-html="agreementHtml"></div>
</el-dialog>
<el-dialog v-model="showPrivacy" title="隐私政策" :append-to-body="true">
<div class="prose" v-html="privacyHtml"></div>
</el-dialog>
</div>
</template>
@@ -265,12 +284,13 @@ import SendMsg from '@/components/SendMsg.vue'
import { getSystemInfo } from '@/store/cache'
import { setUserToken } from '@/store/session'
import { useSharedStore } from '@/store/sharedata'
import { httpPost } from '@/utils/http'
import { httpGet, httpPost } from '@/utils/http'
import { arrayContains } from '@/utils/libs'
import { validateEmail, validateMobile } from '@/utils/validate'
import { Checked, Iphone, Lock, Message } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { onMounted, ref, watch } from 'vue'
import { marked } from 'marked'
// eslint-disable-next-line no-undef
const props = defineProps({
@@ -313,10 +333,16 @@ const captchaRef = ref(null)
// eslint-disable-next-line no-undef
const emits = defineEmits(['hide', 'success'])
const action = ref('login')
const enableVerify = ref(false)
const enableCaptcha = ref(false)
const captchaType = ref('')
const showResetPass = ref(false)
const store = useSharedStore()
const loading = ref(false)
const agreeChecked = ref(false)
const showAgreement = ref(false)
const showPrivacy = ref(false)
const agreementHtml = ref('')
const privacyHtml = ref('')
onMounted(() => {
getSystemInfo()
@@ -341,12 +367,16 @@ onMounted(() => {
if (res.data['wechat_card_url'] !== '') {
wxImg.value = res.data['wechat_card_url']
}
enableVerify.value = res.data['enabled_verify']
}
})
.catch((e) => {
ElMessage.error('获取系统配置失败:' + e.message)
})
httpGet('/api/captcha/config').then((res) => {
enableCaptcha.value = res.data['enabled']
captchaType.value = res.data['type']
})
})
const submit = (verifyData) => {
@@ -365,7 +395,7 @@ const submitLogin = () => {
if (!data.value.password) {
return ElMessage.error('请输入密码')
}
if (enableVerify.value) {
if (enableCaptcha.value) {
captchaRef.value.loadCaptcha()
action.value = 'login'
} else {
@@ -418,7 +448,10 @@ const submitRegister = () => {
if ((activeName.value === 'mobile' || activeName.value === 'email') && data.value.code === '') {
return ElMessage.error('请输入验证码')
}
if (enableVerify.value && activeName.value === 'username') {
if (!agreeChecked.value) {
return ElMessage.error('请先阅读并同意《用户协议》和《隐私政策》')
}
if (enableCaptcha.value && activeName.value === 'username') {
captchaRef.value.loadCaptcha()
action.value = 'register'
} else {
@@ -451,6 +484,34 @@ const doRegister = (verifyData) => {
loading.value = false
})
}
// 打开并加载协议
const openAgreement = () => {
if (!agreementHtml.value) {
httpGet('/api/config/get?key=agreement')
.then((res) => {
agreementHtml.value = marked.parse(res.data?.content || '')
showAgreement.value = true
})
.catch((e) => ElMessage.error('加载用户协议失败:' + e.message))
} else {
showAgreement.value = true
}
}
// 打开并加载隐私政策
const openPrivacy = () => {
if (!privacyHtml.value) {
httpGet('/api/config/get?key=privacy')
.then((res) => {
privacyHtml.value = marked.parse(res.data?.content || '')
showPrivacy.value = true
})
.catch((e) => ElMessage.error('加载隐私政策失败:' + e.message))
} else {
showPrivacy.value = true
}
}
</script>
<style lang="scss">

View File

@@ -7,6 +7,7 @@
:before-close="close"
:title="title"
class="reset-pass-dialog"
:append-to-body="true"
>
<div class="form">
<el-form :model="form" label-width="80px" label-position="left">

View File

@@ -106,6 +106,7 @@
<div class="py-4">
<button
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2 text-base"
type="button"
@click="generate"
>
<i v-if="isGenerating" class="iconfont icon-loading animate-spin"></i>

View File

@@ -616,6 +616,7 @@
<button
class="px-10 py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2 text-base"
@click="generate"
type="button"
>
<i v-if="isGenerating" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>

View File

@@ -304,6 +304,7 @@
<button
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2 text-base"
@click="generate"
type="button"
>
<i v-if="isGenerating" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>

View File

@@ -312,6 +312,7 @@
@click="store.submitTask"
:disabled="store.submitting"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2 text-base"
type="button"
>
<i v-if="store.submitting" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>

View File

@@ -46,10 +46,13 @@
<div class="p-4">
<button
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2 text-base"
type="button"
@click="generateAI"
:loading="loading"
:disabled="loading"
>
生成思维导图
<i v-if="loading" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>
<span>生成思维导图</span>
</button>
</div>
@@ -67,6 +70,7 @@
<button
class="w-full py-3 bg-gradient-to-r from-green-400 to-blue-500 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-green-500 hover:to-blue-600 transition-all duration-200 flex items-center justify-center space-x-2 text-base"
@click="generate"
type="button"
>
直接生成免费
</button>
@@ -140,10 +144,8 @@ onMounted(async () => {
if (cache) {
text.value = cache
} else {
const res = await getSystemInfo().catch((e) => {
ElMessage.error('获取系统配置失败:' + e.message)
})
text.value = res.data['mark_map_text']
const res = await httpGet('/api/config/get?key=mark_map')
text.value = res.data?.content || ''
content.value = text.value
}

View File

@@ -205,6 +205,7 @@
@click="store.create"
:disabled="store.loading"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
type="button"
>
<i v-if="store.loading" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>
@@ -245,6 +246,7 @@
<div class="w-full py-2">
<button
class="w-full py-3 bg-gradient-to-r from-orange-300 to-purple-500 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-orange-300 hover:to-red-500 transition-all duration-200 flex items-center justify-center space-x-2"
type="button"
>
<i class="iconfont icon-upload mr-2"></i>
<span>上传音乐</span>

View File

@@ -139,6 +139,7 @@
@click="store.createLumaVideo"
:disabled="store.generating"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2 text-base"
type="button"
>
<i v-if="store.generating" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>
@@ -457,6 +458,7 @@
@click="store.createKelingVideo"
:disabled="store.generating"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2 text-base"
type="button"
>
<i v-if="store.generating" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>

View File

@@ -80,22 +80,24 @@ const enableVerify = ref(false)
const captchaRef = ref(null)
const loading = ref(false)
checkAdminSession()
.then(() => {
router.push('/admin')
})
.catch(() => {})
onMounted(() => {
// 判断是否登录
checkAdminSession()
.then(() => {
router.push('/admin')
})
.catch(() => {})
// 加载系统配置
getSystemInfo()
.then((res) => {
title.value = res.data.admin_title
logo.value = res.data.logo
enableVerify.value = res.data['enabled_verify']
})
.catch((e) => {
ElMessage.error('加载系统配置失败: ' + e.message)
})
// 加载系统配置
getSystemInfo()
.then((res) => {
title.value = res.data.admin_title
logo.value = res.data.logo
})
.catch((e) => {
ElMessage.error('加载系统配置失败: ' + e.message)
})
})
const login = function () {
if (username.value === '') {

View File

@@ -1,18 +1,13 @@
<template>
<div class="agreement-config form" v-loading="loading">
<div class="container">
<h3>用户协议</h3>
<md-editor
class="mgb20"
v-model="agreement"
:theme="store.theme"
@on-upload-img="onUploadImg"
/>
<el-form-item>
<div style="padding-top: 10px; margin-left: 150px">
<el-button type="primary" @click="save">保存</el-button>
</div>
</el-form-item>
<div class="agreement-config container" v-loading="loading">
<md-editor
class="mgb20"
v-model="agreement"
:theme="store.theme"
@on-upload-img="onUploadImg"
/>
<div class="flex justify-center p-5">
<el-button type="primary" @click="save">保存</el-button>
</div>
</div>
</template>
@@ -43,7 +38,7 @@ onMounted(() => {
})
const save = () => {
httpPost('/api/admin/config/update/base', { mark_map_text: agreement.value })
httpPost('/api/admin/config/update/agreement', { content: agreement.value })
.then(() => {
ElMessage.success('操作成功!')
})

View File

@@ -1,56 +0,0 @@
<template>
<div class="form" v-loading="loading">
<el-form :model="api" label-width="140px">
<el-form-item label="API 网关"><el-input v-model="api.api_url" /></el-form-item>
<el-form-item label="AppId"><el-input v-model="api.app_id" /></el-form-item>
<el-form-item label="Token"><el-input v-model="api.token" type="password" /></el-form-item>
<el-divider>即梦 AI</el-divider>
<el-form-item label="AccessKey"
><el-input v-model="api.jimeng_config.access_key"
/></el-form-item>
<el-form-item label="SecretKey"
><el-input v-model="api.jimeng_config.secret_key"
/></el-form-item>
<el-form-item>
<el-button type="primary" @click="save">保存</el-button>
<el-button @click="test">测试</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { httpGet } from '@/utils/http'
import { ElMessage } from 'element-plus'
import { onMounted, ref } from 'vue'
const loading = ref(true)
const api = ref({
api_url: '',
app_id: '',
token: '',
jimeng_config: { access_key: '', secret_key: '' },
})
onMounted(() => {
httpGet('/api/admin/config/get?key=api')
.then((res) => (api.value = res.data || api.value))
.catch(() => {})
.finally(() => (loading.value = false))
})
const save = () => {
ElMessage.info('当前后端未提供 /api 配置的更新接口,已保留只读展示')
}
const test = () => {
ElMessage.info('请在对应服务端手动测试 API 可用性')
}
</script>
<style scoped>
.form {
padding: 10px 20px 40px 20px;
}
</style>

View File

@@ -90,18 +90,6 @@
<el-switch v-model="system['enabled_register']" />
</el-form-item>
<el-form-item>
<template #label>
<div class="label-title">
启用验证码
<span class="text-xs text-gray-500"
>启用验证码之后注册登录都会加载行为验证码增加安全性</span
>
</div>
</template>
<el-switch v-model="system['enabled_verify']" />
</el-form-item>
<el-form-item label="注册方式" prop="register_ways">
<el-checkbox-group v-model="system['register_ways']">
<el-checkbox value="mobile">手机注册</el-checkbox>

View File

@@ -1,45 +1,14 @@
<template>
<div class="markmap-config form" v-loading="loading">
<div class="container">
<h3>思维导图配置</h3>
<el-form
:model="system"
label-width="150px"
label-position="right"
ref="systemFormRef"
:rules="rules"
>
<el-form-item>
<template #label>
<div class="label-title">
思维导图默认文本
<el-tooltip
effect="dark"
content="用户访问思维导图页面时显示的默认文本内容,支持 Markdown 格式"
raw-content
placement="right"
>
<el-icon>
<InfoFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<md-editor
class="mgb20"
:theme="store.theme"
v-model="system['mark_map_text']"
@on-upload-img="onUploadImg"
placeholder="请输入思维导图页面的默认文本内容,支持 Markdown 格式"
/>
</el-form-item>
<div class="container" v-loading="loading">
<md-editor
:theme="store.theme"
v-model="content"
@on-upload-img="onUploadImg"
placeholder="请输入思维导图页面的默认文本内容,支持 Markdown 格式"
/>
<div style="padding: 10px">
<el-form-item>
<el-button type="primary" @click="save">保存</el-button>
</el-form-item>
</div>
</el-form>
<div class="flex justify-center p-5">
<el-button type="primary" @click="save">保存</el-button>
</div>
</div>
</template>
@@ -47,26 +16,25 @@
<script setup>
import { useSharedStore } from '@/store/sharedata'
import { httpGet, httpPost } from '@/utils/http'
import { InfoFilled } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import MdEditor from 'md-editor-v3'
import 'md-editor-v3/lib/style.css'
import { onMounted, reactive, ref } from 'vue'
const system = ref({})
const content = ref('')
const loading = ref(true)
const systemFormRef = ref(null)
const store = useSharedStore()
onMounted(() => {
// 加载系统配置
httpGet('/api/admin/config/get?key=system')
httpGet('/api/admin/config/get?key=mark_map')
.then((res) => {
system.value = res.data
loading.value = false
content.value = res.data?.content || ''
})
.catch((e) => {
ElMessage.error('加载系统配置失败: ' + e.message)
})
.finally(() => {
loading.value = false
})
})
@@ -74,9 +42,8 @@ onMounted(() => {
const rules = reactive({})
const save = function () {
httpPost('/api/admin/config/update', {
key: 'system',
config: { mark_map_text: system.value.mark_map_text },
httpPost('/api/admin/config/update/mark_map', {
content: content.value,
})
.then(() => {
ElMessage.success('操作成功!')
@@ -111,8 +78,8 @@ const onUploadImg = (files, callback) => {
</script>
<style lang="scss" scoped>
@use '../../../assets/css/admin/form.scss' as *;
@use '../../../assets/css/main.scss' as *;
@use '@/assets/css/admin/form.scss' as *;
@use '@/assets/css/main.scss' as *;
.markmap-config {
display: flex;

View File

@@ -1,13 +1,8 @@
<template>
<div class="notice-config form" v-loading="loading">
<div class="container">
<h3>公告配置</h3>
<md-editor class="mgb20" v-model="notice" :theme="store.theme" @on-upload-img="onUploadImg" />
<el-form-item>
<div style="padding-top: 10px; margin-left: 150px">
<el-button type="primary" @click="save">保存</el-button>
</div>
</el-form-item>
<div class="notice-config container" v-loading="loading">
<md-editor class="mgb20" v-model="notice" :theme="store.theme" @on-upload-img="onUploadImg" />
<div class="flex justify-center p-5">
<el-button type="primary" @click="save">保存</el-button>
</div>
</div>
</template>

View File

@@ -1,18 +1,15 @@
<template>
<div class="privacy-config form" v-loading="loading">
<div class="privacy-config container" v-loading="loading">
<div class="container">
<h3>隐私声明</h3>
<md-editor
class="mgb20"
v-model="privacy"
:theme="store.theme"
@on-upload-img="onUploadImg"
/>
<el-form-item>
<div style="padding-top: 10px; margin-left: 150px">
<el-button type="primary" @click="save">保存</el-button>
</div>
</el-form-item>
<div class="flex justify-center p-5">
<el-button type="primary" @click="save">保存</el-button>
</div>
</div>
</div>
</template>
@@ -43,7 +40,7 @@ onMounted(() => {
})
const save = () => {
httpPost('/api/admin/config/update/notice', { content: privacy.value })
httpPost('/api/admin/config/update/privacy', { content: privacy.value })
.then(() => {
ElMessage.success('操作成功!')
})

View File

@@ -250,6 +250,7 @@
<div class="bg-white rounded-xl p-4 shadow-sm mb-3">
<button
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2 text-base"
type="button"
@click="jimengStore.submitTask"
:disabled="jimengStore.submitting"
>

View File

@@ -191,6 +191,7 @@
<button
@click="suno.create"
:disabled="suno.loading"
type="button"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i v-if="suno.loading" class="iconfont icon-loading animate-spin"></i>

View File

@@ -186,6 +186,7 @@
<button
@click="video.createLumaVideo"
:disabled="video.generating"
type="button"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i v-if="video.generating" class="iconfont icon-loading animate-spin"></i>
@@ -423,6 +424,7 @@
<button
@click="video.createKelingVideo"
:disabled="video.generating"
type="button"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i v-if="video.generating" class="iconfont icon-loading animate-spin"></i>

View File

@@ -78,6 +78,7 @@
<button
@click="generate"
:disabled="loading"
type="button"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white font-semibold rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i v-if="loading" class="iconfont icon-loading animate-spin"></i>

View File

@@ -195,6 +195,7 @@
<button
@click="generate"
:disabled="loading"
type="button"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white font-semibold rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i v-if="loading" class="iconfont icon-loading animate-spin"></i>

View File

@@ -134,6 +134,7 @@
<button
@click="generate"
:disabled="loading"
type="button"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white font-semibold rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i v-if="loading" class="iconfont icon-loading animate-spin"></i>