From bb6e90d50a663d982a78e6d2b871dd5a682568b8 Mon Sep 17 00:00:00 2001 From: RockYang Date: Wed, 6 Aug 2025 09:57:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=A7=BB=E5=8A=A8=E7=AB=AF?= =?UTF-8?q?=E9=82=80=E8=AF=B7=E9=A1=B5=E9=9D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/handler/invite_handler.go | 132 ++- api/handler/power_log_handler.go | 44 + api/store/vo/invite_log.go | 1 + api/store/vo/invite_stats.go | 9 + api/store/vo/reward_rule.go | 10 + web/src/components/LoginDialog.vue | 65 +- web/src/components/ui/CustomTabs.vue | 221 ++++- web/src/main.js | 4 + web/src/router.js | 42 +- web/src/views/ImageMj.vue | 4 +- web/src/views/Invitation.vue | 2 +- web/src/views/Login.vue | 36 + web/src/views/LoginCallback.vue | 148 --- web/src/views/Register.vue | 51 ++ web/src/views/mobile/Apps.vue | 333 ++++--- web/src/views/mobile/ChatSession.vue | 78 +- web/src/views/mobile/Create.vue | 247 +---- web/src/views/mobile/Discover.vue | 22 +- web/src/views/mobile/Feedback.vue | 174 ---- web/src/views/mobile/Help.vue | 855 ------------------ web/src/views/mobile/Index.vue | 14 +- web/src/views/mobile/Invite.vue | 447 +++------ web/src/views/mobile/Member.vue | 132 +-- web/src/views/mobile/PowerLog.vue | 346 +++---- web/src/views/mobile/Settings.vue | 2 +- web/src/views/mobile/Tools.vue | 756 ---------------- web/src/views/mobile/components/AppCard.vue | 110 +++ .../views/mobile/components/EmptyState.vue | 74 ++ web/src/views/mobile/pages/ImageDall.vue | 2 +- web/src/views/mobile/pages/ImageMj.vue | 4 +- web/src/views/mobile/pages/ImageSd.vue | 2 +- web/src/views/mobile/pages/ImgWall.vue | 27 +- web/src/views/mobile/pages/JimengCreate.vue | 693 ++++++++++++++ web/src/views/mobile/pages/SunoCreate.vue | 689 ++++++++++++++ web/src/views/mobile/pages/VideoCreate.vue | 792 ++++++++++++++++ 35 files changed, 3335 insertions(+), 3233 deletions(-) create mode 100644 api/store/vo/invite_stats.go create mode 100644 api/store/vo/reward_rule.go delete mode 100644 web/src/views/LoginCallback.vue delete mode 100644 web/src/views/mobile/Feedback.vue delete mode 100644 web/src/views/mobile/Help.vue delete mode 100644 web/src/views/mobile/Tools.vue create mode 100644 web/src/views/mobile/components/AppCard.vue create mode 100644 web/src/views/mobile/components/EmptyState.vue create mode 100644 web/src/views/mobile/pages/JimengCreate.vue create mode 100644 web/src/views/mobile/pages/SunoCreate.vue create mode 100644 web/src/views/mobile/pages/VideoCreate.vue diff --git a/api/handler/invite_handler.go b/api/handler/invite_handler.go index 40477fed..14c51c5c 100644 --- a/api/handler/invite_handler.go +++ b/api/handler/invite_handler.go @@ -8,14 +8,17 @@ package handler // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import ( + "fmt" "geekai/core" "geekai/store/model" "geekai/store/vo" "geekai/utils" "geekai/utils/resp" + "strings" + "time" + "github.com/gin-gonic/gin" "gorm.io/gorm" - "strings" ) // InviteHandler 用户邀请 @@ -33,6 +36,8 @@ func (h *InviteHandler) RegisterRoutes() { group.GET("code", h.Code) group.GET("list", h.List) group.GET("hits", h.Hits) + group.GET("stats", h.Stats) + group.GET("rules", h.Rules) } // Code 获取当前用户邀请码 @@ -73,21 +78,34 @@ func (h *InviteHandler) List(c *gin.Context) { var total int64 session.Model(&model.InviteLog{}).Count(&total) var items []model.InviteLog - var list = make([]vo.InviteLog, 0) offset := (page - 1) * pageSize - res := session.Order("id DESC").Offset(offset).Limit(pageSize).Find(&items) - if res.Error == nil { - for _, item := range items { - var v vo.InviteLog - err := utils.CopyObject(item, &v) - if err == nil { - v.Id = item.Id - v.CreatedAt = item.CreatedAt.Unix() - list = append(list, v) - } else { - logger.Error(err) - } + err := session.Order("id DESC").Offset(offset).Limit(pageSize).Find(&items).Error + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + userIds := make([]uint, 0) + for _, item := range items { + userIds = append(userIds, item.UserId) + } + userMap := make(map[uint]model.User) + var users []model.User + h.DB.Model(&model.User{}).Where("id IN (?)", userIds).Find(&users) + for _, user := range users { + userMap[user.Id] = user + } + + var list = make([]vo.InviteLog, 0) + for _, item := range items { + var v vo.InviteLog + err := utils.CopyObject(item, &v) + if err != nil { + continue } + v.CreatedAt = item.CreatedAt.Unix() + v.Avatar = userMap[item.UserId].Avatar + list = append(list, v) } resp.SUCCESS(c, vo.NewPage(total, page, pageSize, list)) } @@ -98,3 +116,89 @@ func (h *InviteHandler) Hits(c *gin.Context) { h.DB.Model(&model.InviteCode{}).Where("code = ?", code).UpdateColumn("hits", gorm.Expr("hits + ?", 1)) resp.SUCCESS(c) } + +// Stats 获取邀请统计 +func (h *InviteHandler) Stats(c *gin.Context) { + userId := h.GetLoginUserId(c) + + // 获取邀请码 + var inviteCode model.InviteCode + res := h.DB.Where("user_id = ?", userId).First(&inviteCode) + if res.Error != nil { + resp.ERROR(c, "邀请码不存在") + return + } + + // 统计累计邀请数 + var totalInvite int64 + h.DB.Model(&model.InviteLog{}).Where("inviter_id = ?", userId).Count(&totalInvite) + + // 统计今日邀请数 + today := time.Now().Format("2006-01-02") + var todayInvite int64 + h.DB.Model(&model.InviteLog{}).Where("inviter_id = ? AND DATE(created_at) = ?", userId, today).Count(&todayInvite) + + // 获取系统配置中的邀请奖励 + var config model.Config + var invitePower int = 200 // 默认值 + if h.DB.Where("name = ?", "system").First(&config).Error == nil { + var configMap map[string]any + if utils.JsonDecode(config.Value, &configMap) == nil { + if power, ok := configMap["invite_power"].(float64); ok { + invitePower = int(power) + } + } + } + + // 计算获得奖励总数 + rewardTotal := int(totalInvite) * invitePower + + // 构建邀请链接 + inviteLink := fmt.Sprintf("%s/register?invite=%s", h.App.Config.StaticUrl, inviteCode.Code) + + stats := vo.InviteStats{ + InviteCount: int(totalInvite), + RewardTotal: rewardTotal, + TodayInvite: int(todayInvite), + InviteCode: inviteCode.Code, + InviteLink: inviteLink, + } + + resp.SUCCESS(c, stats) +} + +// Rules 获取奖励规则 +func (h *InviteHandler) Rules(c *gin.Context) { + // 获取系统配置中的邀请奖励 + var config model.Config + var invitePower int = 200 // 默认值 + if h.DB.Where("name = ?", "system").First(&config).Error == nil { + var configMap map[string]interface{} + if utils.JsonDecode(config.Value, &configMap) == nil { + if power, ok := configMap["invite_power"].(float64); ok { + invitePower = int(power) + } + } + } + + rules := []vo.RewardRule{ + { + Id: 1, + Title: "好友注册", + Desc: "好友通过邀请链接成功注册", + Icon: "icon-user-fill", + Color: "#1989fa", + Reward: invitePower, + }, + { + Id: 2, + Title: "好友首次充值", + Desc: "好友首次充值任意金额", + Icon: "icon-money", + Color: "#07c160", + Reward: invitePower * 2, // 假设首次充值奖励是注册奖励的2倍 + }, + } + + resp.SUCCESS(c, rules) +} diff --git a/api/handler/power_log_handler.go b/api/handler/power_log_handler.go index d4c1f418..a74389fd 100644 --- a/api/handler/power_log_handler.go +++ b/api/handler/power_log_handler.go @@ -14,6 +14,7 @@ import ( "geekai/store/vo" "geekai/utils" "geekai/utils/resp" + "time" "github.com/gin-gonic/gin" "gorm.io/gorm" @@ -31,6 +32,7 @@ func NewPowerLogHandler(app *core.AppServer, db *gorm.DB) *PowerLogHandler { func (h *PowerLogHandler) RegisterRoutes() { group := h.App.Engine.Group("/api/powerLog/") group.POST("list", h.List) + group.GET("stats", h.Stats) } func (h *PowerLogHandler) List(c *gin.Context) { @@ -78,3 +80,45 @@ func (h *PowerLogHandler) List(c *gin.Context) { } resp.SUCCESS(c, vo.NewPage(total, data.Page, data.PageSize, list)) } + +// Stats 获取用户算力统计 +func (h *PowerLogHandler) Stats(c *gin.Context) { + userId := h.GetLoginUserId(c) + if userId == 0 { + resp.NotAuth(c) + return + } + + // 获取用户信息(包含余额) + var user model.User + if err := h.DB.Where("id", userId).First(&user).Error; err != nil { + resp.ERROR(c, "用户不存在") + return + } + + // 计算总消费(所有支出记录) + var totalConsume int64 + h.DB.Model(&model.PowerLog{}). + Where("user_id", userId). + Where("mark", types.PowerSub). + Select("COALESCE(SUM(amount), 0)"). + Scan(&totalConsume) + + // 计算今日消费 + today := time.Now().Format("2006-01-02") + var todayConsume int64 + h.DB.Model(&model.PowerLog{}). + Where("user_id", userId). + Where("mark", types.PowerSub). + Where("DATE(created_at) = ?", today). + Select("COALESCE(SUM(amount), 0)"). + Scan(&todayConsume) + + stats := map[string]interface{}{ + "total": totalConsume, + "today": todayConsume, + "balance": user.Power, + } + + resp.SUCCESS(c, stats) +} diff --git a/api/store/vo/invite_log.go b/api/store/vo/invite_log.go index 3be80c32..4a17f6d3 100644 --- a/api/store/vo/invite_log.go +++ b/api/store/vo/invite_log.go @@ -5,6 +5,7 @@ type InviteLog struct { InviterId uint `json:"inviter_id"` UserId uint `json:"user_id"` Username string `json:"username"` + Avatar string `json:"avatar"` InviteCode string `json:"invite_code"` Remark string `json:"remark"` CreatedAt int64 `json:"created_at"` diff --git a/api/store/vo/invite_stats.go b/api/store/vo/invite_stats.go new file mode 100644 index 00000000..ce372c3c --- /dev/null +++ b/api/store/vo/invite_stats.go @@ -0,0 +1,9 @@ +package vo + +type InviteStats struct { + InviteCount int `json:"invite_count"` // 累计邀请数 + RewardTotal int `json:"reward_total"` // 获得奖励总数 + TodayInvite int `json:"today_invite"` // 今日邀请数 + InviteCode string `json:"invite_code"` // 邀请码 + InviteLink string `json:"invite_link"` // 邀请链接 +} diff --git a/api/store/vo/reward_rule.go b/api/store/vo/reward_rule.go new file mode 100644 index 00000000..b932c3a4 --- /dev/null +++ b/api/store/vo/reward_rule.go @@ -0,0 +1,10 @@ +package vo + +type RewardRule struct { + Id int `json:"id"` // 规则ID + Title string `json:"title"` // 规则标题 + Desc string `json:"desc"` // 规则描述 + Icon string `json:"icon"` // 图标类名 + Color string `json:"color"` // 图标颜色 + Reward int `json:"reward"` // 奖励算力 +} diff --git a/web/src/components/LoginDialog.vue b/web/src/components/LoginDialog.vue index 2c705920..e183895a 100644 --- a/web/src/components/LoginDialog.vue +++ b/web/src/components/LoginDialog.vue @@ -46,24 +46,6 @@ >忘记密码? -
- -
其他登录方式
-
- -
@@ -265,6 +247,14 @@ import { useRouter } from 'vue-router' // eslint-disable-next-line no-undef const props = defineProps({ show: Boolean, + active: { + type: String, + default: 'login', + }, + inviteCode: { + type: String, + default: '', + }, }) const showDialog = ref(false) watch( @@ -274,7 +264,7 @@ watch( } ) -const login = ref(true) +const login = ref(props.active === 'login') const data = ref({ username: import.meta.env.VITE_USER, password: import.meta.env.VITE_PASS, @@ -282,13 +272,13 @@ const data = ref({ email: '', repass: '', code: '', - invite_code: '', + invite_code: props.inviteCode, }) const enableMobile = ref(false) const enableEmail = ref(false) const enableUser = ref(false) const enableRegister = ref(true) -const wechatLoginURL = ref('') + const activeName = ref('') const wxImg = ref('/images/wx.png') const captchaRef = ref(null) @@ -301,15 +291,6 @@ const router = useRouter() const store = useSharedStore() onMounted(() => { - 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) => { - console.log(e.message) - }) - getSystemInfo() .then((res) => { if (res.data) { @@ -472,30 +453,6 @@ const doRegister = (verifyData) => { } } - .c-login { - display: flex; - .text { - font-size: 16px; - color: #a1a1a1; - display: flex; - align-items: center; - } - .login-type { - display: flex; - justify-content: center; - - .iconfont { - font-size: 18px; - background: #e9f1f6; - padding: 8px; - border-radius: 50%; - } - .iconfont.icon-wechat { - color: #0bc15f; - } - } - } - .text { color: var(--el-text-color-primary); } diff --git a/web/src/components/ui/CustomTabs.vue b/web/src/components/ui/CustomTabs.vue index a34b0636..11d7c813 100644 --- a/web/src/components/ui/CustomTabs.vue +++ b/web/src/components/ui/CustomTabs.vue @@ -1,12 +1,61 @@ -
@@ -43,13 +87,11 @@ const emit = defineEmits(['update:modelValue', 'tab-click']) const tabsHeader = ref(null) const tabsContainer = ref(null) const tabItems = ref([]) -const indicator = ref(null) const panes = ref([]) -const indicatorStyle = ref({ - transform: 'translateX(0px)', - width: '0px', -}) +// 滑动状态 +const canScrollLeft = ref(false) +const canScrollRight = ref(false) // 提供当前激活的 tab 给子组件 provide( @@ -70,50 +112,151 @@ provide('registerPane', (pane) => { } }) +// 检查滑动位置状态 +const checkScrollPosition = () => { + if (!tabsContainer.value) return + + const container = tabsContainer.value + canScrollLeft.value = container.scrollLeft > 0 + canScrollRight.value = container.scrollLeft < container.scrollWidth - container.clientWidth +} + +// 向左滑动 +const scrollLeft = () => { + if (!tabsContainer.value) return + + const container = tabsContainer.value + const scrollAmount = Math.min(200, container.scrollLeft) // 每次滑动200px或剩余距离 + + container.scrollTo({ + left: container.scrollLeft - scrollAmount, + behavior: 'smooth', + }) +} + +// 向右滑动 +const scrollRight = () => { + if (!tabsContainer.value) return + + const container = tabsContainer.value + const maxScroll = container.scrollWidth - container.clientWidth + const scrollAmount = Math.min(200, maxScroll - container.scrollLeft) // 每次滑动200px或剩余距离 + + container.scrollTo({ + left: container.scrollLeft + scrollAmount, + behavior: 'smooth', + }) +} + const handleTabClick = (tabName, index) => { emit('update:modelValue', tabName) emit('tab-click', tabName, index) - updateIndicator(index) + scrollToTab(index) } -const updateIndicator = async (activeIndex) => { +// 简化后的滚动到指定tab的函数 +const scrollToTab = async (activeIndex) => { await nextTick() - if (tabItems.value && tabItems.value.length > 0 && tabsHeader.value) { - const activeTab = tabItems.value[activeIndex] - if (activeTab) { - const tabRect = activeTab.getBoundingClientRect() - const containerRect = tabsHeader.value.getBoundingClientRect() - - const leftPosition = tabRect.left - containerRect.left - const tabWidth = tabRect.width - - indicatorStyle.value = { - transform: `translateX(${leftPosition}px)`, - width: `${tabWidth}px`, - } - } + if (!tabsContainer.value || !tabItems.value || tabItems.value.length === 0) { + return } + + const activeTab = tabItems.value[activeIndex] + if (!activeTab) { + return + } + + const container = tabsContainer.value + const tabRect = activeTab.getBoundingClientRect() + const containerRect = container.getBoundingClientRect() + + // 计算tab相对于容器的位置 + const tabLeft = tabRect.left - containerRect.left + const tabRight = tabLeft + tabRect.width + const containerWidth = containerRect.width + + // 检查tab是否在可视区域内(增加一些容错空间) + const tolerance = 4 // 4px的容错空间 + const isVisible = tabLeft >= -tolerance && tabRight <= containerWidth + tolerance + + if (!isVisible) { + let scrollLeft = container.scrollLeft + + if (tabLeft < -tolerance) { + // tab在左侧不可见,滚动到tab的起始位置 + scrollLeft += tabLeft - 12 // 留出12px的边距 + } else if (tabRight > containerWidth + tolerance) { + // tab在右侧不可见,滚动到tab的结束位置 + scrollLeft += tabRight - containerWidth + 12 // 留出12px的边距 + } + + // 确保滚动位置不超出边界 + scrollLeft = Math.max(0, Math.min(scrollLeft, container.scrollWidth - containerWidth)) + + // 平滑滚动到目标位置 + container.scrollTo({ + left: scrollLeft, + behavior: 'smooth', + }) + } + + // 更新滑动状态 + setTimeout(checkScrollPosition, 300) } -// 监听 modelValue 变化,更新指示器位置 +// 监听 modelValue 变化,滚动到tab watch( () => props.modelValue, (newValue) => { const activeIndex = panes.value.findIndex((pane) => pane.name === newValue) if (activeIndex !== -1) { - updateIndicator(activeIndex) + scrollToTab(activeIndex) } } ) +// 监听 panes 变化,当tab数量变化时重新计算 +watch( + () => panes.value.length, + () => { + nextTick(() => { + const activeIndex = panes.value.findIndex((pane) => pane.name === props.modelValue) + if (activeIndex !== -1) { + scrollToTab(activeIndex) + } + // 检查滑动状态 + setTimeout(checkScrollPosition, 100) + }) + } +) + onMounted(() => { - // 初始化指示器位置 + // 初始化时滚动到选中tab nextTick(() => { const activeIndex = panes.value.findIndex((pane) => pane.name === props.modelValue) if (activeIndex !== -1) { - updateIndicator(activeIndex) + scrollToTab(activeIndex) } + // 检查初始滑动状态 + setTimeout(checkScrollPosition, 100) }) + + // 监听窗口大小变化,重新计算滚动 + const handleResize = () => { + const activeIndex = panes.value.findIndex((pane) => pane.name === props.modelValue) + if (activeIndex !== -1) { + scrollToTab(activeIndex) + } + // 检查滑动状态 + setTimeout(checkScrollPosition, 100) + } + + window.addEventListener('resize', handleResize) + + // 清理事件监听器 + return () => { + window.removeEventListener('resize', handleResize) + } }) @@ -140,4 +283,14 @@ onMounted(() => { .flex-shrink-0:last-child { margin-right: 0; } + +/* 添加平滑滚动效果 */ +.overflow-x-auto { + scroll-behavior: smooth; +} + +/* 滑动指示器样式 */ +.absolute { + transition: opacity 0.2s ease-in-out; +} diff --git a/web/src/main.js b/web/src/main.js index 3ef007e3..2cd6fe53 100644 --- a/web/src/main.js +++ b/web/src/main.js @@ -24,6 +24,7 @@ import { ActionSheet, Badge, Button, + Calendar, Cell, CellGroup, Circle, @@ -52,6 +53,7 @@ import { Overlay, Picker, Popup, + PullRefresh, Radio, RadioGroup, Row, @@ -130,5 +132,7 @@ app.use(Tabs) app.use(Divider) app.use(NoticeBar) app.use(ActionSheet) +app.use(PullRefresh) +app.use(Calendar) app.use(Toast) app.use(router).use(ElementPlus).mount('#app') diff --git a/web/src/router.js b/web/src/router.js index c1b648b1..4c1b006d 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -123,12 +123,7 @@ const routes = [ meta: { title: '导出会话记录' }, component: () => import('@/views/ChatExport.vue'), }, - { - name: 'login-callback', - path: '/login/callback', - meta: { title: '用户登录' }, - component: () => import('@/views/LoginCallback.vue'), - }, + { name: 'login', path: '/login', @@ -296,31 +291,37 @@ const routes = [ component: () => import('@/views/mobile/Index.vue'), }, { + meta: { title: 'AI对话' }, path: '/mobile/chat', name: 'mobile-chat', component: () => import('@/views/mobile/ChatList.vue'), }, { + meta: { title: '创作中心' }, path: '/mobile/create', name: 'mobile-create', component: () => import('@/views/mobile/Create.vue'), }, { + meta: { title: '发现' }, path: '/mobile/discover', name: 'mobile-discover', component: () => import('@/views/mobile/Discover.vue'), }, { + meta: { title: '个人中心' }, path: '/mobile/profile', name: 'mobile-profile', component: () => import('@/views/mobile/Profile.vue'), }, { + meta: { title: '会员充值' }, path: '/mobile/member', name: 'mobile-member', component: () => import('@/views/mobile/Member.vue'), }, { + meta: { title: '作品展示' }, path: '/mobile/imgWall', name: 'mobile-img-wall', component: () => import('@/views/mobile/pages/ImgWall.vue'), @@ -332,40 +333,47 @@ const routes = [ }, { + meta: { title: '应用中心' }, path: '/mobile/apps', name: 'mobile-apps', component: () => import('@/views/mobile/Apps.vue'), }, // 新增的功能页面路由 { + meta: { title: '消费日志' }, path: '/mobile/power-log', name: 'mobile-power-log', component: () => import('@/views/mobile/PowerLog.vue'), }, { + meta: { title: '推广计划' }, path: '/mobile/invite', name: 'mobile-invite', component: () => import('@/views/mobile/Invite.vue'), }, { - path: '/mobile/tools', - name: 'mobile-tools', - component: () => import('@/views/mobile/Tools.vue'), - }, - { + meta: { title: '设置' }, path: '/mobile/settings', name: 'mobile-settings', component: () => import('@/views/mobile/Settings.vue'), }, { - path: '/mobile/help', - name: 'mobile-help', - component: () => import('@/views/mobile/Help.vue'), + meta: { title: 'Suno音乐创作' }, + path: '/mobile/suno-create', + name: 'mobile-suno-create', + component: () => import('@/views/mobile/pages/SunoCreate.vue'), }, { - path: '/mobile/feedback', - name: 'mobile-feedback', - component: () => import('@/views/mobile/Feedback.vue'), + meta: { title: '视频生成' }, + path: '/mobile/video-create', + name: 'mobile-video-create', + component: () => import('@/views/mobile/pages/VideoCreate.vue'), + }, + { + meta: { title: '即梦AI' }, + path: '/mobile/jimeng-create', + name: 'mobile-jimeng-create', + component: () => import('@/views/mobile/pages/JimengCreate.vue'), }, ], }, diff --git a/web/src/views/ImageMj.vue b/web/src/views/ImageMj.vue index e005464b..ccd27013 100644 --- a/web/src/views/ImageMj.vue +++ b/web/src/views/ImageMj.vue @@ -915,10 +915,10 @@ const initParams = { model: models[0].value, chaos: 0, stylize: 0, - seed: 0, + seed: -1, img_arr: [], raw: false, - iw: 0, + iw: 0.7, prompt: router.currentRoute.value.params['prompt'] ?? '', neg_prompt: '', tile: false, diff --git a/web/src/views/Invitation.vue b/web/src/views/Invitation.vue index 8b775f7e..cbfe996d 100644 --- a/web/src/views/Invitation.vue +++ b/web/src/views/Invitation.vue @@ -60,7 +60,7 @@
-
+
diff --git a/web/src/views/Login.vue b/web/src/views/Login.vue index b580ad67..56fd7190 100644 --- a/web/src/views/Login.vue +++ b/web/src/views/Login.vue @@ -1,5 +1,8 @@