Compare commits
13 Commits
69f8875c67
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| d6daf1dce6 | |||
| 25f46209d9 | |||
| 258d5de815 | |||
| 3710f5d666 | |||
| 917397bcf6 | |||
| 8d67081a4d | |||
| 31fd128633 | |||
| c9a3aa0567 | |||
| 097b23cc1e | |||
| d568a31fb3 | |||
| 24446260c2 | |||
| 956e645144 | |||
| f89879eead |
@@ -7,11 +7,11 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
** Version | 版本 **
|
||||
**Version | 版本**
|
||||
eg: v1.0.0
|
||||
|
||||
** Environment | 运行环境 **
|
||||
**Environment | 运行环境**
|
||||
eg: docker / ubuntu / windows
|
||||
|
||||
** Detailed description | 详细说明 **
|
||||
**Detailed description | 详细说明**
|
||||
...
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[Feature] Short description | 简短说明,每条issue最好写一个建议,不要重复提出"
|
||||
title: "[Feature] Short description | 简短说明"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ name: docker-push-beta
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
# 暂时取消自动编译,优化后重新开启
|
||||
# push:
|
||||
# tags:
|
||||
# - 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -2,9 +2,10 @@ name: docker-push
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
# 暂时取消自动编译,优化后重新开启
|
||||
# push:
|
||||
# tags:
|
||||
# - 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## 项目简介
|
||||
|
||||
Sun-Panel 是服务器/NAS 导航面板应用,前后端分离架构:
|
||||
|
||||
- **前端**:Vue 3 + TypeScript + Vite + Naive UI + Pinia
|
||||
- **后端**:Go + Gin + GORM,支持 SQLite(默认)或 MySQL,可选 Redis
|
||||
|
||||
## 常用命令
|
||||
|
||||
### 前端
|
||||
|
||||
```bash
|
||||
pnpm dev # 启动开发服务器,端口 1002
|
||||
pnpm build # 生产构建(类型检查 + Vite 打包)
|
||||
pnpm lint # ESLint 检查
|
||||
pnpm lint:fix # 自动修复
|
||||
pnpm type-check # 仅 TypeScript 类型检查
|
||||
```
|
||||
|
||||
### 后端
|
||||
|
||||
```bash
|
||||
cd service
|
||||
go run main.go # 启动,默认端口 3002
|
||||
```
|
||||
|
||||
### Docker 全量构建
|
||||
|
||||
```bash
|
||||
docker build -t sun-panel .
|
||||
```
|
||||
|
||||
## 架构要点
|
||||
|
||||
### 前后端通信
|
||||
|
||||
开发时,Vite 将 `/api/*` 和 `/uploads/*` 代理到 `http://127.0.0.1:3002`(由 `.env` 中 `VITE_APP_API_BASE_URL` 控制)。生产部署时前后端同端口,Go 直接 serve 静态文件。
|
||||
|
||||
### 前端分层
|
||||
|
||||
```
|
||||
src/api/ → API 调用函数(按模块分:panel/ system/ 等)
|
||||
src/store/ → Pinia 状态(auth/user/panel/admin/notice/moduleConfig/app)
|
||||
src/views/ → 页面组件(home/login/exception)
|
||||
src/components/ → UI 组件(apps/ common/ deskModule/)
|
||||
src/utils/request/ → axios 封装,含 token 拦截器
|
||||
src/hooks/ → Composition API(useTheme/useLanguage/useBasicLayout/useIconRender)
|
||||
src/locales/ → 国际化(zh-CN.json / en-US.json)
|
||||
```
|
||||
|
||||
### 后端分层
|
||||
|
||||
```
|
||||
service/router/ → 路由注册,入口 A_ENTER.go
|
||||
service/api/api_v1/ → Handler 层,中间件在 middleware/
|
||||
service/lib/ → 业务逻辑(user/cache/monitor/siteFavicon 等)
|
||||
service/models/ → GORM 模型
|
||||
service/global/ → 全局变量(Db/Logger/Redis 等)
|
||||
service/initialize/ → 启动初始化流程,入口 A_ENTER.go
|
||||
```
|
||||
|
||||
后端每个子目录都以 `A_ENTER.go` 作为该模块的入口聚合文件。
|
||||
|
||||
### 路由分组(后端)
|
||||
|
||||
- `/system/*` — 登录/用户/系统设置,需 LoginInterceptor
|
||||
- `/panel/*` — 面板数据/图标管理,需 LoginInterceptor
|
||||
- `/openness/*` — 无需认证的公开接口
|
||||
|
||||
### 配置文件
|
||||
|
||||
后端使用 INI 格式配置,示例见 `service/assets/conf.example.ini`。关键字段:
|
||||
- `database_drive`:`sqlite`(默认)或 `mysql`
|
||||
- `cache_drive` / `queue_drive`:`memory`(默认)或 `redis`
|
||||
- `source_path`:上传文件存储路径,默认 `./uploads`
|
||||
|
||||
## 开发注意
|
||||
|
||||
- 前端路径别名 `@` 指向 `src/`
|
||||
- 提交前 Husky + lint-staged 会自动对 `.ts/.tsx/.vue` 运行 `eslint --fix`
|
||||
- TypeScript 开启 `strict` 和 `noUnusedLocals`,避免引入 `any`
|
||||
- 国际化新增文案需同时更新 `zh-CN.json` 和 `en-US.json`
|
||||
+1
-1
@@ -4,7 +4,7 @@ FROM node AS web_image
|
||||
# 华为源
|
||||
# RUN npm config set registry https://repo.huaweicloud.com/repository/npm/
|
||||
|
||||
RUN npm install pnpm -g
|
||||
RUN npm install pnpm@8 -g
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
|
||||
@@ -21,57 +21,57 @@
|
||||
[[ Document ]](https://sun-panel-doc.enianteam.com) |
|
||||
[[ Demo ]](http://sunpaneldemo.enianteam.com)
|
||||
|
||||
Server, NAS navigation panel, Homepage, Browser homepage.
|
||||
A server, NAS navigation panel, Homepage, Browser homepage.
|
||||
<br>
|
||||
一个服务器、NAS导航面板、Homepage、浏览器首页。
|
||||
|
||||
</div>
|
||||
|
||||
<!-- <img src="./doc/images/logo.png" align="left" width="180px" height="180px"/>
|
||||
<img align="left" width="0" height="192px" hspace="10"/>
|
||||
|
||||
# Sun-Panel
|
||||
Server, NAS navigation panel, Homepage, Browser homepage.
|
||||
<br>
|
||||
一个服务器、NAS导航面板、Homepage、浏览器首页。 -->
|
||||

|
||||
|
||||
> [!IMPORTANT]
|
||||
> In order to maintain the livelihood, the author added some [`PRO`] (https://pro.sun-panel.top) function, so the project temporarily entered a closed source state.; At present, the latest version of the open source is `v1.3.0`, [Please see the latest version of closed source](https://github.com/hslr-s/sun-panel/releases).; When the modular technology is developed, the separation of the PRO and the programs will be opened again, and the closed source will have no effect on ordinary users.; Let's look forward to open source again, and at the same time, we are welcome to supervise and review the security of the program.
|
||||
>
|
||||
> 作者为了维持生计,增加了一些 [`PRO`](https://pro.sun-panel.top) 功能,所以项目暂时进入闭源状态。目前开源最新版本为`v1.3.0`,[闭源最新版本请查看](https://github.com/hslr-s/sun-panel/releases)。待开发出模块化技术,然后对PRO和主程序进行分离会再次开源,闭源对普通用户没有任何影响。我们一起期待再次开源吧,同时也欢迎各位大佬对程序的安全性进行监督和审查。
|
||||
|
||||
## 😎 Features
|
||||
|
||||
- 🍉 Clean interface, powerful functionality, low resource consumption
|
||||
- 🍊 Easy to use, visual operation, zero-code usage
|
||||
- 🍠 One-click switch between internal and external network modes
|
||||
- 🍵 Supports Docker deployment (compatible with Arm systems)
|
||||
- 🎪 Supports multi-account isolation
|
||||
- 🎏 Supports viewing system status
|
||||
- 🫙 Supports custom JS, CSS
|
||||
- 🍻 Simple usage without the need to connect to an external database
|
||||
- 🍾 Rich icon styles for free combination, supports [Iconify icon library](https://icon-sets.iconify.design/)
|
||||
- 🚁 Supports opening small windows in the webpage (some third-party websites may block this feature)
|
||||
|
||||
## 🖼️ Preview Screenshots
|
||||
|
||||
**Various styles, freely combined**
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
**Built-in small windows**
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
## 🐳 Deployment tutorial
|
||||
[Deployment Tutorial](https://sun-panel-doc.enianteam.com/usage/quick_deploy.html)
|
||||
|
||||

|
||||
## 🍵 Donate
|
||||
|
||||
## 😎 特点
|
||||
> Open-source development is not easy. If you feel that my project has helped you, you are welcome to [donate](./doc/donate.md) or buy me a cup of tea☕ (please leave your nickname or name in the note if possible). Your support is my motivation, thank you.
|
||||
|
||||
- 简洁
|
||||
- 局域网内外网链接切换
|
||||
- docker部署,对arm系统支持
|
||||
- 上手简单,免修改代码
|
||||
- 无需连接外部数据库
|
||||
- 丰富图标自由搭配(文字图标+svg图标+内置三方图标库)
|
||||
- 支持网页内置小窗口打开(部分网站屏蔽此功能)
|
||||
- 占用资源小
|
||||
|
||||
## 🧊 最新完整文档(DOC)
|
||||
|
||||
[最新完整文档(DOC)](https://sun-panel-doc.enianteam.com/)
|
||||
|
||||
|
||||
## 🎨 演示(demo)
|
||||
|
||||
[查看演示站](https://sun-panel-doc.enianteam.com/introduce/demo_site.html)
|
||||
|
||||
## 🐳 交流群&社区
|
||||
开发者:**[红烧猎人](https://blog.enianteam.com/u/sun/content/11)**
|
||||
|
||||
QQ交流群,进不去可以点上方连接联系作者
|
||||
|
||||
<img src="./doc/images/qq_group_qr2.png" height="350" />
|
||||
|
||||
Github社区板块:https://github.com/hslr-s/sun-panel/discussions
|
||||
|
||||
## 🍵 打赏
|
||||
|
||||
> 开源开发不易,如果觉得我的项目有帮到你,欢迎给我[打赏](./doc/donate.md)或者请我喝个奶茶☕(如果可以备注下您的昵称或者名字),你的支持就是我的动力,谢谢。
|
||||
|
||||
<a href="https://www.paypal.me/hslrs">
|
||||
<img height="60" src="./doc/images/donate/paypal.png" target="_blank"></img>
|
||||
@@ -82,117 +82,23 @@ Github社区板块:https://github.com/hslr-s/sun-panel/discussions
|
||||
| ------------ | ------------ |
|
||||
| <img height="300" src="./doc/images/donate/weixin.png"/> | <img height="300" src="./doc/images/donate/alipay.png" /> |
|
||||
|
||||
## 🫓 TODO
|
||||
## 🏖️ Communication group & community
|
||||
|
||||
- [x] 分组,拖拽排序
|
||||
- [x] 导入导出功能
|
||||
- [x] 增加访客账号
|
||||
- [x] 帐号解除邮箱限制
|
||||
- [x] 对上传的文件管理(针对账户增强重复利用,节省空间)
|
||||
- [x] 服务器监控
|
||||
- [x] 多国语言支持
|
||||
- [ ] 用户自定义搜索框搜索引擎
|
||||
- [ ] 搜索框样式自定义(背景颜色,文字颜色)
|
||||
- [ ] docker管理器
|
||||
- [ ] 计划任务
|
||||
Author:**[红烧猎人](https://blog.enianteam.com/u/sun/content/11)**
|
||||
|
||||
[Github Discussions](https://github.com/hslr-s/sun-panel/discussions)
|
||||
|
||||
QQ交流群,进不去可以点上方连接联系作者
|
||||
|
||||
## 🖼️ 预览截图
|
||||
<img src="./doc/images/qq_group_qr2.png" height="350" />
|
||||
|
||||
**各种风格,自由搭配**
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
**内置小窗口**
|
||||
|
||||

|
||||

|
||||
|
||||
## 🍜 使用运行教程
|
||||
|
||||
<div id="default-username"></div>
|
||||
|
||||
### 默认账号密码
|
||||
账号:admin@sun.cc
|
||||
|
||||
密码:12345678
|
||||
|
||||
### 命令参数
|
||||
|参数|说明|
|
||||
|---|---|
|
||||
|-h|查看命令说明|
|
||||
|-config|生成配置文件(conf/conf.ini)|
|
||||
|-password-reset|重置第一个用户的密码|
|
||||
|
||||
### 二进制文件运行
|
||||
|
||||
去 [Releases](https://github.com/hslr-s/sun-panel/releases) 下载二进制文件
|
||||
|
||||
执行示例
|
||||
|
||||
```sh
|
||||
./sun-panel
|
||||
```
|
||||
|
||||
#### 重置密码
|
||||
|
||||
执行示例
|
||||
|
||||
```sh
|
||||
./sun-panel -password-reset
|
||||
```
|
||||
输出
|
||||
```
|
||||
密码已经重置成功,以下是账号信息
|
||||
用户名 xxx@qq.com
|
||||
密码 12345678
|
||||
```
|
||||
|
||||
### docker 运行
|
||||
|
||||
目录挂载 `-v`,根据自己的需求选择:
|
||||
|容器目录|说明|
|
||||
|---|---|
|
||||
|/app/conf|配置文件|
|
||||
|/app/uploads|上传的文件|
|
||||
|/app/database|数据库文件|
|
||||
|/app/runtime|运行日志(不推荐挂载)|
|
||||
|
||||
1. 拉取镜像
|
||||
```sh
|
||||
docker pull hslr/sun-panel
|
||||
```
|
||||
|
||||
2. 直接下载运行
|
||||
```sh
|
||||
docker run -d --restart=always -p 3002:3002 \
|
||||
-v ~/docker_data/sun-panel/conf:/app/conf \
|
||||
-v ~/docker_data/sun-panel/uploads:/app/uploads \
|
||||
-v ~/docker_data/sun-panel/database:/app/database \
|
||||
--name sun-panel \
|
||||
hslr/sun-panel
|
||||
```
|
||||
|
||||
|
||||
### 自编译运行
|
||||
|
||||
[请参考完整文档](https://sun-panel-doc.enianteam.com/zh_cn/usage/compile.html)
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#hslr-s/sun-panel&Date)
|
||||
|
||||
|
||||
## ❤️ 感谢
|
||||
## ❤️ Thanks
|
||||
|
||||
- [Roc](https://github.com/RocCheng)
|
||||
- [jackloves111](https://github.com/jackloves111)
|
||||
- [Rock.L](https://github.com/gitlyp)
|
||||
|
||||
## LICENSE
|
||||
[MIT](./LICENSE)
|
||||
|
||||
---
|
||||
|
||||
[](https://star-history.com/#hslr-s/sun-panel&Date)
|
||||
|
||||
@@ -4,7 +4,9 @@ const moment = require('moment')
|
||||
|
||||
// git 最新标签
|
||||
// const latestTag = execSync('git describe --tags --abbrev=0').toString().trim()
|
||||
const packDate = moment().format('YYYYMMDD-HH')
|
||||
|
||||
// 设置默认时区为 'Asia/Shanghai'
|
||||
const packDate = moment().utc().format('YYYYMMDD')
|
||||
|
||||
// 要追加的内容
|
||||
const contentToAppend = `\nVITE_APP_VERSION=${packDate}`
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 317 KiB |
@@ -68,5 +68,8 @@
|
||||
"*.{ts,tsx,vue}": [
|
||||
"pnpm lint:fix"
|
||||
]
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": ["esbuild", "vue-demi"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package system
|
||||
|
||||
type ApiSystem struct {
|
||||
About About
|
||||
LoginApi LoginApi
|
||||
UserApi UserApi
|
||||
FileApi FileApi
|
||||
NoticeApi NoticeApi
|
||||
ModuleConfigApi ModuleConfigApi
|
||||
MonitorApi MonitorApi
|
||||
About About
|
||||
LoginApi LoginApi
|
||||
UserApi UserApi
|
||||
FileApi FileApi
|
||||
NoticeApi NoticeApi
|
||||
ModuleConfigApi ModuleConfigApi
|
||||
MonitorApi MonitorApi
|
||||
SiteCustomizeApi SiteCustomizeApi
|
||||
CustomStyleApi CustomStyleApi
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cmn/systemSetting"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
type CustomStyleApi struct{}
|
||||
|
||||
// 获取自定义 CSS/JS(无需登录)
|
||||
func (a *CustomStyleApi) Get(c *gin.Context) {
|
||||
cfg := systemSetting.CustomStyle{}
|
||||
global.SystemSetting.GetValueByInterface(systemSetting.CUSTOM_STYLE, &cfg)
|
||||
apiReturn.SuccessData(c, cfg)
|
||||
}
|
||||
|
||||
// 保存自定义 CSS/JS(管理员)
|
||||
func (a *CustomStyleApi) Set(c *gin.Context) {
|
||||
cfg := systemSetting.CustomStyle{}
|
||||
if err := c.ShouldBindBodyWith(&cfg, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
if err := global.SystemSetting.Set(systemSetting.CUSTOM_STYLE, cfg); err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cmn/systemSetting"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
type SiteCustomizeApi struct{}
|
||||
|
||||
// 获取站点自定义配置(无需登录,登录页使用)
|
||||
func (a *SiteCustomizeApi) Get(c *gin.Context) {
|
||||
cfg := systemSetting.SiteCustomize{}
|
||||
global.SystemSetting.GetValueByInterface(systemSetting.SITE_CUSTOMIZE, &cfg)
|
||||
apiReturn.SuccessData(c, cfg)
|
||||
}
|
||||
|
||||
// 保存站点自定义配置(管理员)
|
||||
func (a *SiteCustomizeApi) Set(c *gin.Context) {
|
||||
cfg := systemSetting.SiteCustomize{}
|
||||
if err := c.ShouldBindBodyWith(&cfg, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
if err := global.SystemSetting.Set(systemSetting.SITE_CUSTOMIZE, cfg); err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
9|1.3.0-beta24-01-25
|
||||
10|1.3.0
|
||||
@@ -135,13 +135,13 @@ func NotFoundAndCreateUser(db *gorm.DB) error {
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return err
|
||||
}
|
||||
username := "admin@sun.cc"
|
||||
username := "admin"
|
||||
fUser.Mail = username
|
||||
fUser.Username = username
|
||||
fUser.Name = username
|
||||
fUser.Status = 1
|
||||
fUser.Role = 1
|
||||
fUser.Password = cmn.PasswordEncryption("12345678")
|
||||
fUser.Password = cmn.PasswordEncryption("1234")
|
||||
|
||||
if errCreate := db.Create(&fUser).Error; errCreate != nil {
|
||||
return errCreate
|
||||
|
||||
@@ -15,6 +15,8 @@ const (
|
||||
DISCLAIMER = "disclaimer" // 免责声明 储存类型:字符串
|
||||
WEB_ABOUT_DESCRIPTION = "web_about_description" // 关于的描述信息
|
||||
PANEL_PUBLIC_USER_ID = "panel_public_user_id" // 公开访问模式用户id *uint|null
|
||||
SITE_CUSTOMIZE = "site_customize" // 站点自定义配置
|
||||
CUSTOM_STYLE = "custom_style" // 自定义 CSS/JS
|
||||
)
|
||||
|
||||
type SystemSettingCache struct {
|
||||
@@ -43,6 +45,19 @@ type ApplicationSetting struct {
|
||||
WebSiteUrl string `json:"webSiteUrl"` // 站点地址
|
||||
}
|
||||
|
||||
// 站点自定义配置
|
||||
type SiteCustomize struct {
|
||||
SiteTitle string `json:"siteTitle"` // 站点标题(显示在登录页)
|
||||
FaviconUrl string `json:"faviconUrl"` // Favicon 图标 URL
|
||||
LoginDescription string `json:"loginDescription"` // 登录页描述文字
|
||||
}
|
||||
|
||||
// 自定义 CSS/JS
|
||||
type CustomStyle struct {
|
||||
Css string `json:"css"` // 自定义 CSS 内容
|
||||
Js string `json:"js"` // 自定义 JS 内容
|
||||
}
|
||||
|
||||
var (
|
||||
ErrorNoExists = errors.New("no exists")
|
||||
)
|
||||
|
||||
@@ -10,4 +10,6 @@ func Init(routerGroup *gin.RouterGroup) {
|
||||
InitNoticeRouter(routerGroup)
|
||||
InitModuleConfigRouter(routerGroup)
|
||||
InitMonitorRouter(routerGroup)
|
||||
InitSiteCustomizeRouter(routerGroup)
|
||||
InitCustomStyleRouter(routerGroup)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1"
|
||||
"sun-panel/api/api_v1/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func InitCustomStyleRouter(router *gin.RouterGroup) {
|
||||
api := api_v1.ApiGroupApp.ApiSystem.CustomStyleApi
|
||||
|
||||
// 无需登录
|
||||
router.POST("/system/customStyle/get", api.Get)
|
||||
|
||||
// 管理员才能修改
|
||||
rAdmin := router.Group("", middleware.LoginInterceptor, middleware.AdminInterceptor)
|
||||
rAdmin.POST("/system/customStyle/set", api.Set)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1"
|
||||
"sun-panel/api/api_v1/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func InitSiteCustomizeRouter(router *gin.RouterGroup) {
|
||||
api := api_v1.ApiGroupApp.ApiSystem.SiteCustomizeApi
|
||||
|
||||
// 无需登录(登录页读取)
|
||||
router.POST("/system/siteCustomize/get", api.Get)
|
||||
|
||||
// 管理员才能修改
|
||||
rAdmin := router.Group("", middleware.LoginInterceptor, middleware.AdminInterceptor)
|
||||
rAdmin.POST("/system/siteCustomize/set", api.Set)
|
||||
}
|
||||
+25
@@ -1,11 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { NConfigProvider } from 'naive-ui'
|
||||
import { NaiveProvider } from '@/components/common'
|
||||
import { useTheme } from '@/hooks/useTheme'
|
||||
import { useLanguage } from '@/hooks/useLanguage'
|
||||
import { getCustomStyle } from '@/api/system/customStyle'
|
||||
import type { CustomStyle } from '@/api/system/customStyle'
|
||||
|
||||
const { theme, themeOverrides } = useTheme()
|
||||
const { language } = useLanguage()
|
||||
|
||||
// 注入自定义 CSS/JS
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const res = await getCustomStyle<CustomStyle>()
|
||||
if (res.code === 0 && res.data) {
|
||||
if (res.data.css) {
|
||||
const style = document.createElement('style')
|
||||
style.id = 'sun-panel-custom-css'
|
||||
style.textContent = res.data.css
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
if (res.data.js) {
|
||||
const script = document.createElement('script')
|
||||
script.id = 'sun-panel-custom-js'
|
||||
script.textContent = res.data.js
|
||||
document.body.appendChild(script)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { post } from '@/utils/request'
|
||||
|
||||
export interface CustomStyle {
|
||||
css: string
|
||||
js: string
|
||||
}
|
||||
|
||||
export function getCustomStyle<T>() {
|
||||
return post<T>({
|
||||
url: '/system/customStyle/get',
|
||||
})
|
||||
}
|
||||
|
||||
export function setCustomStyle<T>(data: CustomStyle) {
|
||||
return post<T>({
|
||||
url: '/system/customStyle/set',
|
||||
data,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { post } from '@/utils/request'
|
||||
|
||||
export interface SiteCustomize {
|
||||
siteTitle: string
|
||||
faviconUrl: string
|
||||
loginDescription: string
|
||||
}
|
||||
|
||||
export function getSiteCustomize<T>() {
|
||||
return post<T>({
|
||||
url: '/system/siteCustomize/get',
|
||||
})
|
||||
}
|
||||
|
||||
export function setSiteCustomize<T>(data: SiteCustomize) {
|
||||
return post<T>({
|
||||
url: '/system/siteCustomize/set',
|
||||
data,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { NButton, NForm, NFormItem, NInput, NText, useMessage } from 'naive-ui'
|
||||
import { getCustomStyle, setCustomStyle } from '@/api/system/customStyle'
|
||||
import type { CustomStyle } from '@/api/system/customStyle'
|
||||
import { t } from '@/locales'
|
||||
|
||||
const ms = useMessage()
|
||||
const saving = ref(false)
|
||||
|
||||
const form = ref<CustomStyle>({
|
||||
css: '',
|
||||
js: '',
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const res = await getCustomStyle<CustomStyle>()
|
||||
if (res.code === 0 && res.data)
|
||||
form.value = res.data
|
||||
}
|
||||
catch {}
|
||||
})
|
||||
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
const res = await setCustomStyle<any>(form.value)
|
||||
if (res.code === 0)
|
||||
ms.success(t('apps.customStyle.saveSuccess'))
|
||||
}
|
||||
catch {}
|
||||
finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<NText depth="3" class="block mb-3 text-sm">
|
||||
{{ t('apps.customStyle.tip') }}
|
||||
</NText>
|
||||
<NForm label-placement="top">
|
||||
<NFormItem :label="t('apps.customStyle.cssLabel')">
|
||||
<NInput
|
||||
v-model:value="form.css"
|
||||
type="textarea"
|
||||
:rows="10"
|
||||
:placeholder="t('apps.customStyle.cssPlaceholder')"
|
||||
style="font-family: monospace; font-size: 13px;"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem :label="t('apps.customStyle.jsLabel')">
|
||||
<NInput
|
||||
v-model:value="form.js"
|
||||
type="textarea"
|
||||
:rows="10"
|
||||
:placeholder="t('apps.customStyle.jsPlaceholder')"
|
||||
style="font-family: monospace; font-size: 13px;"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem>
|
||||
<NButton type="primary" :loading="saving" @click="handleSave">
|
||||
{{ t('common.save') }}
|
||||
</NButton>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
@@ -8,6 +8,7 @@ import { ConfigVersionLowError, FormatError, exportJson, importJsonString } from
|
||||
import { get as getAbout } from '@/api/system/about'
|
||||
import { edit as addGroup, getList as getGroupList } from '@/api/panel/itemIconGroup'
|
||||
import { addMultiple as addMultipleIcons, getListByGroupId } from '@/api/panel/itemIcon'
|
||||
import { get as getUserConfig, set as setUserConfig } from '@/api/panel/userConfig'
|
||||
|
||||
import { t } from '@/locales'
|
||||
|
||||
@@ -28,8 +29,8 @@ const debug = ref(false)
|
||||
|
||||
const importObj = ref<ImportJsonResult | null> (null)
|
||||
|
||||
const importItems = ref<string[]>(['icons']) // 当前软件版本支持导入导出的项目
|
||||
const checkedItems = ref<string[]>(['icons']) // 当前准备导入的项目
|
||||
const importItems = ref<string[]>(['icons', 'userConfig']) // 当前软件版本支持导入导出的项目
|
||||
const checkedItems = ref<string[]>(['icons', 'userConfig']) // 当前准备导入的项目
|
||||
|
||||
// 导入图标
|
||||
async function importIcons(): Promise<string | null> {
|
||||
@@ -210,36 +211,70 @@ function importCheck() {
|
||||
}
|
||||
}
|
||||
|
||||
// 导出面板配置
|
||||
async function exportUserConfig(): Promise<Panel.userConfig | null> {
|
||||
try {
|
||||
const res = await getUserConfig<Panel.userConfig>()
|
||||
if (res.code === 0)
|
||||
return res.data
|
||||
}
|
||||
catch {}
|
||||
return null
|
||||
}
|
||||
|
||||
// 导入面板配置
|
||||
async function importUserConfig(): Promise<string | null> {
|
||||
const cfg = importObj.value?.getUserConfig()
|
||||
if (!cfg)
|
||||
return null
|
||||
try {
|
||||
const res = await setUserConfig<any>(cfg)
|
||||
if (res.code !== 0)
|
||||
return res.msg
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error)
|
||||
return error.message
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 开始导出
|
||||
async function handleStartExport() {
|
||||
loading.value = true
|
||||
// console.log('要导出的项目', checkedItems.value)
|
||||
// 获取软件版本号
|
||||
const exportResult = exportJson(version.value)
|
||||
|
||||
if (checkedItems.value.includes('icons')) {
|
||||
console.log('export icons ...')
|
||||
const iconGroups = await exportIcons()
|
||||
exportResult.addIconsData(iconGroups)
|
||||
console.log('export icons finish', iconGroups)
|
||||
}
|
||||
|
||||
// console.log('导出结果')
|
||||
if (checkedItems.value.includes('userConfig')) {
|
||||
const cfg = await exportUserConfig()
|
||||
if (cfg)
|
||||
exportResult.addUserConfigData(cfg)
|
||||
}
|
||||
|
||||
jsonData.value = exportResult.string()
|
||||
exportResult.exportFile()
|
||||
loading.value = false
|
||||
exportRoundModalShow.value = false
|
||||
// ms.success(t('common.success'))
|
||||
}
|
||||
|
||||
// 开始导入
|
||||
async function handleStartImport() {
|
||||
loading.value = true
|
||||
|
||||
if (checkedItems.value.includes('icons')) {
|
||||
console.log('export icons ...')
|
||||
const errMsg = await importIcons()
|
||||
if (errMsg !== null)
|
||||
ms.success(`${t('common.failed')}:${errMsg}`)
|
||||
ms.error(`${t('common.failed')}:${errMsg}`)
|
||||
}
|
||||
|
||||
if (checkedItems.value.includes('userConfig')) {
|
||||
const errMsg = await importUserConfig()
|
||||
if (errMsg !== null)
|
||||
ms.error(`${t('common.failed')}:${errMsg}`)
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
@@ -325,6 +360,7 @@ async function handleStartImport() {
|
||||
<NSpace justify="center" style="margin-top: 20px;">
|
||||
<NCheckboxGroup v-model:value="checkedItems">
|
||||
<NCheckbox v-if="importItems.includes('icons')" value="icons" :label="$t('apps.exportImport.moduleIcon')" />
|
||||
<NCheckbox v-if="importItems.includes('userConfig')" value="userConfig" :label="$t('apps.exportImport.moduleUserConfig')" />
|
||||
<NCheckbox v-if="importItems.includes('style')" value="style" :label="$t('apps.exportImport.moduleStyle')" />
|
||||
</NCheckboxGroup>
|
||||
</NSpace>
|
||||
@@ -345,6 +381,7 @@ async function handleStartImport() {
|
||||
<NSpace justify="center" style="margin-top: 20px;">
|
||||
<NCheckboxGroup v-model:value="checkedItems">
|
||||
<NCheckbox v-if="importItems.includes('icons')" value="icons" :label="$t('apps.exportImport.moduleIcon')" />
|
||||
<NCheckbox v-if="importItems.includes('userConfig')" value="userConfig" :label="$t('apps.exportImport.moduleUserConfig')" />
|
||||
<NCheckbox v-if="importItems.includes('style')" value="style" :label="$t('apps.exportImport.moduleStyle')" />
|
||||
</NCheckboxGroup>
|
||||
</NSpace>
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { NButton, NForm, NFormItem, NInput, useMessage } from 'naive-ui'
|
||||
import { getSiteCustomize, setSiteCustomize } from '@/api/system/siteCustomize'
|
||||
import type { SiteCustomize } from '@/api/system/siteCustomize'
|
||||
import { t } from '@/locales'
|
||||
|
||||
const ms = useMessage()
|
||||
const saving = ref(false)
|
||||
|
||||
const form = ref<SiteCustomize>({
|
||||
siteTitle: '',
|
||||
faviconUrl: '',
|
||||
loginDescription: '',
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const res = await getSiteCustomize<SiteCustomize>()
|
||||
if (res.code === 0 && res.data)
|
||||
form.value = res.data
|
||||
}
|
||||
catch {}
|
||||
})
|
||||
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
const res = await setSiteCustomize<any>(form.value)
|
||||
if (res.code === 0)
|
||||
ms.success(t('apps.siteCustomize.saveSuccess'))
|
||||
}
|
||||
catch {}
|
||||
finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<NForm label-placement="top">
|
||||
<NFormItem :label="t('apps.siteCustomize.siteTitle')">
|
||||
<NInput
|
||||
v-model:value="form.siteTitle"
|
||||
:placeholder="t('apps.siteCustomize.siteTitlePlaceholder')"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem :label="t('apps.siteCustomize.faviconUrl')">
|
||||
<NInput
|
||||
v-model:value="form.faviconUrl"
|
||||
:placeholder="t('apps.siteCustomize.faviconUrlPlaceholder')"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem :label="t('apps.siteCustomize.loginDescription')">
|
||||
<NInput
|
||||
v-model:value="form.loginDescription"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
:placeholder="t('apps.siteCustomize.loginDescriptionPlaceholder')"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem>
|
||||
<NButton type="primary" :loading="saving" @click="handleSave">
|
||||
{{ t('common.save') }}
|
||||
</NButton>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
@@ -90,6 +90,7 @@
|
||||
"fileModified": "The file has been modified, import with caution",
|
||||
"import": "Import configuration",
|
||||
"moduleIcon": "Icon configuration",
|
||||
"moduleUserConfig": "Panel configuration",
|
||||
"moduleStyle": "Style configuration",
|
||||
"selectExportData": "Select the configuration data to export",
|
||||
"selectImportData": "Select the configuration data to import",
|
||||
@@ -125,6 +126,25 @@
|
||||
"dark": "Dark ",
|
||||
"light": "Light "
|
||||
}
|
||||
},
|
||||
"siteCustomize": {
|
||||
"appName": "Site Customize",
|
||||
"siteTitle": "Site Title",
|
||||
"siteTitlePlaceholder": "Leave blank to use default title Sun-Panel",
|
||||
"faviconUrl": "Favicon URL",
|
||||
"faviconUrlPlaceholder": "Leave blank to use default icon",
|
||||
"loginDescription": "Login Page Description",
|
||||
"loginDescriptionPlaceholder": "Leave blank to hide",
|
||||
"saveSuccess": "Saved successfully"
|
||||
},
|
||||
"customStyle": {
|
||||
"appName": "Custom CSS/JS",
|
||||
"cssLabel": "Custom CSS",
|
||||
"jsLabel": "Custom JS",
|
||||
"cssPlaceholder": "/* Enter custom CSS here */",
|
||||
"jsPlaceholder": "// Enter custom JS here",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"tip": "Refresh the page after saving to apply changes"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
"fileModified": "文件被修改过,谨慎导入",
|
||||
"import": "导入配置",
|
||||
"moduleIcon": "图标配置",
|
||||
"moduleUserConfig": "面板配置",
|
||||
"moduleStyle": "样式配置",
|
||||
"selectExportData": "请选择要导出的配置数据",
|
||||
"selectImportData": "请选择要导入的配置数据",
|
||||
@@ -125,6 +126,25 @@
|
||||
"dark": "深色 ",
|
||||
"light": "浅色 "
|
||||
}
|
||||
},
|
||||
"siteCustomize": {
|
||||
"appName": "站点自定义",
|
||||
"siteTitle": "站点标题",
|
||||
"siteTitlePlaceholder": "留空则使用默认标题 Sun-Panel",
|
||||
"faviconUrl": "Favicon 图标地址",
|
||||
"faviconUrlPlaceholder": "留空则使用默认图标",
|
||||
"loginDescription": "登录页描述",
|
||||
"loginDescriptionPlaceholder": "留空不显示",
|
||||
"saveSuccess": "保存成功"
|
||||
},
|
||||
"customStyle": {
|
||||
"appName": "自定义 CSS/JS",
|
||||
"cssLabel": "自定义 CSS",
|
||||
"jsLabel": "自定义 JS",
|
||||
"cssPlaceholder": "/* 在此输入自定义 CSS */",
|
||||
"jsPlaceholder": "// 在此输入自定义 JS",
|
||||
"saveSuccess": "保存成功",
|
||||
"tip": "修改后刷新页面生效"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface JsonStructure {
|
||||
exportTime: string
|
||||
appVersion: string
|
||||
icons?: any
|
||||
// styleConfig: Panel.panelConfig
|
||||
userConfig?: Panel.userConfig
|
||||
md5: string
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ export interface IconGroup {
|
||||
|
||||
interface ExportJsonResult {
|
||||
addIconsData(datas: IconGroup[]): ExportJsonResult
|
||||
addUserConfigData(data: Panel.userConfig): ExportJsonResult
|
||||
exportFile(): void
|
||||
string(): string
|
||||
}
|
||||
@@ -75,6 +76,12 @@ export function exportJson(appVersion?: string): ExportJsonResult {
|
||||
return this
|
||||
},
|
||||
|
||||
// 添加用户面板配置
|
||||
addUserConfigData(data: Panel.userConfig) {
|
||||
jsonData.userConfig = data
|
||||
return this
|
||||
},
|
||||
|
||||
// 导出json文件
|
||||
exportFile() {
|
||||
generateMD5AndUpdate()
|
||||
@@ -103,7 +110,8 @@ export interface ImportJsonResult {
|
||||
isPassCheckConfigVersionBest: () => boolean // 验证程序的导入版本驱动是否为最佳 当配置文件和驱动版本相等的时候为最佳,否则不匹配过新或者过旧
|
||||
jsonStruct: JsonStructure // 根据实际情况提供更具体的类型定义
|
||||
hasProperty: (key: string) => boolean
|
||||
geticons: () => IconGroup[] // 根据实际情况提供更具体的类型定义
|
||||
geticons: () => IconGroup[]
|
||||
getUserConfig: () => Panel.userConfig | null
|
||||
}
|
||||
|
||||
// 导入json数据
|
||||
@@ -140,6 +148,9 @@ export function importJsonString(jsonString: string): ImportJsonResult | null {
|
||||
geticons: (): IconGroup[] => {
|
||||
return jsonStruct.icons || []
|
||||
},
|
||||
getUserConfig: (): Panel.userConfig | null => {
|
||||
return jsonStruct.userConfig || null
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -35,19 +35,19 @@ function http<T = any>(
|
||||
return res.data
|
||||
|
||||
if (res.data.code === 1001) {
|
||||
// 避免重复弹窗
|
||||
if (loginMessageShow === false) {
|
||||
loginMessageShow = true
|
||||
message.warning(t('api.loginExpires'), {
|
||||
// message.warning('登录过期', {
|
||||
onLeave() {
|
||||
loginMessageShow = false
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
router.push({ path: '/login' })
|
||||
authStore.removeToken()
|
||||
// 已在登录页不弹提示,避免带旧 token 进入时误报
|
||||
if (router.currentRoute.value.path !== '/login') {
|
||||
if (loginMessageShow === false) {
|
||||
loginMessageShow = true
|
||||
message.warning(t('api.loginExpires'), {
|
||||
onLeave() {
|
||||
loginMessageShow = false
|
||||
},
|
||||
})
|
||||
}
|
||||
router.push({ path: '/login' })
|
||||
}
|
||||
return res.data
|
||||
}
|
||||
|
||||
|
||||
@@ -92,15 +92,26 @@ function handleResize() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const adminApp: App = {
|
||||
name: t('adminSettingUsers.appName'),
|
||||
componentName: 'Users',
|
||||
icon: 'lucide-users',
|
||||
auth: 1,
|
||||
if (authStore.userInfo?.role === 1) {
|
||||
apps.value.push({
|
||||
name: t('adminSettingUsers.appName'),
|
||||
componentName: 'Users',
|
||||
icon: 'lucide-users',
|
||||
auth: 1,
|
||||
})
|
||||
apps.value.push({
|
||||
name: t('apps.siteCustomize.appName'),
|
||||
componentName: 'SiteCustomize',
|
||||
icon: 'mdi-earth-settings',
|
||||
auth: 1,
|
||||
})
|
||||
apps.value.push({
|
||||
name: t('apps.customStyle.appName'),
|
||||
componentName: 'CustomStyle',
|
||||
icon: 'mdi-code-braces',
|
||||
auth: 1,
|
||||
})
|
||||
}
|
||||
// 初始化
|
||||
if (authStore.userInfo?.role === 1)
|
||||
apps.value.push(adminApp)
|
||||
|
||||
window.addEventListener('resize', handleResize)
|
||||
handleResize()
|
||||
|
||||
@@ -524,7 +524,7 @@ function handleAddItem(itemIconGroupId?: number) {
|
||||
</template>
|
||||
</NButton>
|
||||
|
||||
<NButton v-if="authStore.visitMode === VisitMode.VISIT_MODE_LOGIN" color="#2a2a2a6b" @click="settingModalShow = !settingModalShow">
|
||||
<NButton v-if="authStore.visitMode === VisitMode.VISIT_MODE_LOGIN" color="#f97316" @click="settingModalShow = !settingModalShow">
|
||||
<template #icon>
|
||||
<SvgIcon class="text-white font-xl" icon="majesticons-applications" />
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NCard, NForm, NFormItem, NGradientText, NInput, NSelect, useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { login } from '@/api'
|
||||
import { useAppStore, useAuthStore } from '@/store'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
@@ -8,14 +8,37 @@ import { router } from '@/router'
|
||||
import { t } from '@/locales'
|
||||
import { languageOptions } from '@/utils/defaultData'
|
||||
import type { Language } from '@/store/modules/app/helper'
|
||||
import { getSiteCustomize } from '@/api/system/siteCustomize'
|
||||
import type { SiteCustomize } from '@/api/system/siteCustomize'
|
||||
|
||||
// const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
const appStore = useAppStore()
|
||||
const ms = useMessage()
|
||||
const loading = ref(false)
|
||||
const languageValue = ref<Language>(appStore.language)
|
||||
|
||||
const siteCustomize = ref<SiteCustomize>({
|
||||
siteTitle: '',
|
||||
faviconUrl: '',
|
||||
loginDescription: '',
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const res = await getSiteCustomize<SiteCustomize>()
|
||||
if (res.code === 0 && res.data) {
|
||||
siteCustomize.value = res.data
|
||||
// 动态设置 favicon
|
||||
if (res.data.faviconUrl) {
|
||||
const link = document.querySelector<HTMLLinkElement>('link[rel~="icon"]')
|
||||
if (link)
|
||||
link.href = res.data.faviconUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
})
|
||||
|
||||
// const isShowCaptcha = ref<boolean>(false)
|
||||
// const isShowRegister = ref<boolean>(false)
|
||||
|
||||
@@ -75,9 +98,12 @@ function handleChangeLanuage(value: Language) {
|
||||
|
||||
<div class="login-title ">
|
||||
<NGradientText :size="30" type="success" class="!font-bold">
|
||||
{{ $t('common.appName') }}
|
||||
{{ siteCustomize.siteTitle || $t('common.appName') }}
|
||||
</NGradientText>
|
||||
</div>
|
||||
<div v-if="siteCustomize.loginDescription" class="text-center text-slate-400 text-sm mb-3">
|
||||
{{ siteCustomize.loginDescription }}
|
||||
</div>
|
||||
<NForm :model="form" label-width="100px" @keydown.enter="handleSubmit">
|
||||
<NFormItem>
|
||||
<NInput v-model:value="form.username" :placeholder="$t('login.usernamePlaceholder')">
|
||||
|
||||
Reference in New Issue
Block a user