更新v1.1.0
Squashed commit of the following: commit 4443f7c8251b31687ed93114930ab3d769f4ed6c Author: Sun <95302870@qq.com> Date: Tue Nov 28 22:10:49 2023 +0800 美化关于页 commit 95ca46d460eba469ca8ae54f65c7773835061c0f Author: Sun <95302870@qq.com> Date: Tue Nov 28 21:59:48 2023 +0800 更新版本号,更新说明文件增加新版预览截图 commit 052e5f81fe4065e10199d52bc041329fc9c5fe86 Author: Sun <95302870@qq.com> Date: Tue Nov 28 21:06:52 2023 +0800 修复后端mkdirAll权限的问题 commit ace57d5ba69c311e40997d5791cf03a8b28e0c07 Author: Sun <95302870@qq.com> Date: Tue Nov 28 20:59:35 2023 +0800 修改配置文件 commit 099015f2767cedfd6eae91e60131817471eb1f24 Author: Sun <95302870@qq.com> Date: Tue Nov 28 14:10:39 2023 +0800 增加docker-compose文件 commit e229003431ff2476f0ab63a8dffb88504716ba48 Author: Sun <95302870@qq.com> Date: Tue Nov 28 13:53:20 2023 +0800 提交更新日志文件 commit e8736b8b62db6d590c063b42757599381429541e Author: Sun <95302870@qq.com> Date: Tue Nov 28 13:49:38 2023 +0800 增加隐藏小图标 commit 038af3aaa91a023cc10aabff5b0cfd15c64d0b46 Author: Sun <95302870@qq.com> Date: Tue Nov 28 13:49:04 2023 +0800 优化 密码限制 commit 4cd15a383923bf3de56e9e4dc6df3bf97236ed18 Author: Sun <95302870@qq.com> Date: Tue Nov 28 12:34:59 2023 +0800 增加反馈入口 commit daf6aea902893f816dca5c0bb09326f2f110ddcc Merge: 3edfadd b057e25 Author: Sun <95302870@qq.com> Date: Mon Nov 27 22:19:59 2023 +0800 Merge branch 'master' into dev commit 3edfaddd173efcbbde867dc9ce9b022920b39061 Author: Sun <95302870@qq.com> Date: Mon Nov 27 22:17:08 2023 +0800 修改docker的编译镜像和运行镜像为alpine,兼容极空间设备 commit 3445f97152c2f6b9f1f9f68b46e4b84f8240c9c2 Author: Sun <95302870@qq.com> Date: Mon Nov 27 13:58:59 2023 +0800 修复前端编译错误 commit 3ef02013ffb595e7805692350389bb623155cfe9 Author: Sun <95302870@qq.com> Date: Mon Nov 27 13:56:12 2023 +0800 更新beta版本号 commit 620f0f1e1523f34e87001e8a2bbe3d4a01cb0b9b Author: Sun <95302870@qq.com> Date: Mon Nov 27 13:53:46 2023 +0800 修复 添加图标成功后遗留旧数据的问题 commit 55d877d1ca11e83d9f7325a321aeb5b65ad4ee8b Author: Sun <95302870@qq.com> Date: Mon Nov 27 13:41:55 2023 +0800 增加置顶按钮 commit f28dd63328aeca5d8c3c036c5786304f8a33b1f9 Author: Sun <95302870@qq.com> Date: Mon Nov 27 12:56:14 2023 +0800 优化roundmodal的样式和手机端设置样式 commit c19ce176878ea2ef06b0a5dc75bd6d8239892302 Author: Sun <95302870@qq.com> Date: Mon Nov 27 11:06:44 2023 +0800 优化手机端logo文字显示问题 commit 018dabb2faddc0541fb33ef5a51a126575a59cf5 Author: Sun <95302870@qq.com> Date: Mon Nov 27 10:51:02 2023 +0800 更新说明文件 commit 02239e3686933e4c33e430be8300ec2d0be41887 Author: Sun <95302870@qq.com> Date: Sun Nov 26 22:59:11 2023 +0800 优化 登录页面 commit 6aa92e8ba6c4eeb2fa2995ee93aeeac86b54b551 Author: Sun <95302870@qq.com> Date: Sat Nov 25 23:59:40 2023 +0800 增加编译脚本 commit d93df810fa95a7baa28ca5323903b93f286ba741 Author: Sun <95302870@qq.com> Date: Sat Nov 25 15:48:00 2023 +0800 修改相关logo图片 commit 036a56ddc7a555d6227c92dfa2abfe84f9042662 Merge: 7018872 feacc89 Author: Sun <95302870@qq.com> Date: Fri Nov 24 16:00:23 2023 +0800 Merge branch 'master' into dev commit 7018872ce9fd0fa8f1ff4731a16b2ea90fb9153f Author: Sun <95302870@qq.com> Date: Fri Nov 24 15:31:31 2023 +0800 更新版本标签 commit 4fae97dd932ce4638d869a0c7a123c788c3e3e43 Author: Sun <95302870@qq.com> Date: Fri Nov 24 15:07:39 2023 +0800 更新版本1.1.0 测试版 commit 890a3c3dbdccbe4dfd5a6915e87a2649c9141e7b Author: Sun <95302870@qq.com> Date: Fri Nov 24 14:31:26 2023 +0800 右键菜单新增打开局域网或者互联网地址,优化分组管理图标不统一的问题 commit 4f014cf4aa384a2c8a03585ffd6ce41c941b9356 Author: Sun <95302870@qq.com> Date: Fri Nov 24 13:33:43 2023 +0800 增加 关联删除,优化添加密码长度限制20 commit 5658e6c379b077d359fff75c5e9b904cbce5f81e Author: Sun <95302870@qq.com> Date: Fri Nov 24 12:09:41 2023 +0800 增加更新日志 commit f142d1b378e0525db157a93cca61ee86bf1eb08d Author: Sun <95302870@qq.com> Date: Fri Nov 24 12:09:30 2023 +0800 添加应用图标验证分组信息必填 commit 2ff2b6b32a4bb70653e3a7312ccb0f4b0b945f07 Author: Sun <95302870@qq.com> Date: Thu Nov 23 23:45:10 2023 +0800 优化关于页面,及更新版本序号为2 commit c9b482b24e2d23d638501dbaa44f826386c420b5 Author: Sun <95302870@qq.com> Date: Thu Nov 23 22:12:13 2023 +0800 优化关于设置版本号 commit ed70059ffbce1ae8a9e2e0378803f7875ada342b Author: Sun <95302870@qq.com> Date: Thu Nov 23 21:41:22 2023 +0800 修复分组管理不能滚动的问题 commit faa4222b1494271878c2c7469c14a4efa49c6761 Author: Sun <95302870@qq.com> Date: Thu Nov 23 21:24:34 2023 +0800 修复分组写死的问题 commit 4f2d0c858e55735b9ba3a8453de9407b76346805 Author: Sun <95302870@qq.com> Date: Thu Nov 23 20:24:39 2023 +0800 初步尝试构建测试版本 commit 596bed19dcf3bceb77e30f9c24218888f8da7e64 Author: Sun <95302870@qq.com> Date: Thu Nov 23 19:24:05 2023 +0800 修复搜索框配置bug,云端没有默认值,前端打不开搜索引擎选择栏 commit 489fbf748a7e35c6b69198b19038c01a548e20f2 Author: Sun <95302870@qq.com> Date: Thu Nov 23 19:22:57 2023 +0800 增加logo和版本打印,修复模块配置的索引报错 commit 263dab607af8a830acee44e37776bda4da814b40 Author: Sun <95302870@qq.com> Date: Wed Nov 22 23:02:37 2023 +0800 调整排序样式 commit c0adf335d3e48e6770d56eb506b471852fbfbc43 Author: Sun <95302870@qq.com> Date: Wed Nov 22 22:58:18 2023 +0800 说明文件增加logo commit 721d22e75b93d3646f5f206d208a9a743840a25b Author: Sun <95302870@qq.com> Date: Wed Nov 22 22:40:40 2023 +0800 更换logo commit 4df58fec7b2054ce97cf2989045affd144aa4f8b Author: Sun <95302870@qq.com> Date: Wed Nov 22 22:38:27 2023 +0800 完善关于页面 commit 63777f0bbac85550fafe1b084bd664c7722ab934 Author: Sun <95302870@qq.com> Date: Wed Nov 22 21:12:14 2023 +0800 字体为纯白色的时候,详情图标会根据背景的明暗度计算字体颜色 commit f328dc73305665a921e030dd4a06d759d0cac3bf Author: Sun <95302870@qq.com> Date: Wed Nov 22 17:21:40 2023 +0800 详情图标居中 commit 663f37bf1a26b7dff24edcfb149222ac780cb90d Author: Sun <95302870@qq.com> Date: Wed Nov 22 16:45:11 2023 +0800 将图标单独拆分为子组件应用图标 commit 30cd5ab460e032f7f6d7c23eb7a9c7af735d0f41 Author: Sun <95302870@qq.com> Date: Wed Nov 22 13:36:32 2023 +0800 增加详情图标隐藏描述信息等设置 commit 945a94e76cae4251953512cc09f348ad38bb9a38 Author: Sun <95302870@qq.com> Date: Wed Nov 22 12:28:46 2023 +0800 优化图标背景色:支持透明图标并更换背景颜色字段 commit 437053fc9d8d9e3c55aac4d259e4b4c4bc11de58 Author: Sun <95302870@qq.com> Date: Wed Nov 22 11:20:56 2023 +0800 完善搜索框 commit a9914f8e8ced23b8c50701a85d522c8f0fcd1c2b Author: Sun <95302870@qq.com> Date: Wed Nov 22 01:27:16 2023 +0800 关闭模块配置相关接口开发模式 commit 2a9e22d4b781f43c0f9b8a867d26c295e756175b Author: Sun <95302870@qq.com> Date: Wed Nov 22 01:24:09 2023 +0800 完成搜索框的样式和模块配置的state等api对接 commit 7f771650ef7272e474f74ed689ab844bf90b946f Author: Sun <95302870@qq.com> Date: Wed Nov 22 00:45:25 2023 +0800 增加搜索引擎图标 commit a0e0039ae89eaa27e4b849baa0168716866682ea Author: Sun <95302870@qq.com> Date: Tue Nov 21 19:54:36 2023 +0800 增加 模块配置表 commit 017869794177d7a5d4c12c0eac6fb7c7fe79734e Author: Sun <95302870@qq.com> Date: Tue Nov 21 13:10:39 2023 +0800 图标标题加粗 commit 7a2d896a44262b54d6a1d2d12fe6bbbc31b1ca49 Author: Sun <95302870@qq.com> Date: Tue Nov 21 12:53:39 2023 +0800 增加图标的预设颜色 commit a6c3120c186646b323e57ecce0f85ec9c79a41a5 Author: Sun <95302870@qq.com> Date: Tue Nov 21 12:18:46 2023 +0800 增加遮罩 commit 84d3db81ea2aaa0f67a67690e449d15401e8e511 Author: Sun <95302870@qq.com> Date: Tue Nov 21 11:05:24 2023 +0800 极简小图标增加鼠标悬浮详情 commit 666a6a117bc30c64a78ab0fe2cb7836c602b2741 Author: Sun <95302870@qq.com> Date: Mon Nov 20 23:33:10 2023 +0800 修复 sort字段未修改归0的问题 commit 71afd530d7a740763326a6117f8e7c04ac1f7f69 Author: Sun <95302870@qq.com> Date: Mon Nov 20 23:32:48 2023 +0800 适配纯透明图标,增强图标背景色,增加图标url连接支持 commit 619c5e28e1c51c16e14ed09601709ab751ee2454 Author: Sun <95302870@qq.com> Date: Mon Nov 20 22:37:43 2023 +0800 修复 每次修改图标都重置了排序号 commit 8a17f1c0bf2f00c530dee61cd5070a74ca4a53b2 Author: Sun <95302870@qq.com> Date: Mon Nov 20 21:21:20 2023 +0800 分组为空的时候显示添加图标的图标 commit 755cf3dc569e402cb3dfc9915e94de4f2595571b Author: Sun <95302870@qq.com> Date: Mon Nov 20 20:52:45 2023 +0800 首页图标排序完成 commit 5ccf23c68b3284be00dadf94073a665826737a77 Author: Sun <95302870@qq.com> Date: Mon Nov 20 14:30:12 2023 +0800 添加修改图标适配分组 commit 47209d729270bf4704428ecd91606d4721bd9a13 Author: Sun <95302870@qq.com> Date: Mon Nov 20 11:06:32 2023 +0800 保存分组和分组排序已经完成 commit 17403de7ed236a097d70cd5ecbbe261e620ff377 Merge: d0d88eb 980d81a Author: Sun <95302870@qq.com> Date: Sun Nov 19 23:38:00 2023 +0800 Merge branch 'master' into dev commit d0d88eb548bbe9d7f5ad663f383db858843a8d8c Merge: 728dbc8 47b479c Author: Sun <95302870@qq.com> Date: Sun Nov 19 11:13:34 2023 +0800 Merge branch 'docker-build' into dev commit 47b479cf8da7214dd9e0592b461743ab7d3824ed Author: Sun <95302870@qq.com> Date: Sun Nov 19 11:12:54 2023 +0800 修改前端程序名 commit 728dbc80ff7885d0b4cf289b06763cc60ed17d7e Author: Sun <95302870@qq.com> Date: Thu Nov 16 13:44:47 2023 +0800 新增删除应用分组和修改应用分组,以及图标真正的按组读取 commit a3dbd948ca743384a2de3685083695603d674bf1 Author: Sun <95302870@qq.com> Date: Wed Nov 15 22:49:58 2023 +0800 增加图标组api commit de21f3f232c1243917b5c55ba4bedb01437f8564 Author: Sun <95302870@qq.com> Date: Wed Nov 15 22:49:44 2023 +0800 重新划分应用盒子的结构 commit 7c409112ba1f8eefb7df7fffdb78b285e3f5322c Author: Sun <95302870@qq.com> Date: Wed Nov 15 22:27:07 2023 +0800 [后端] 增加应用分组 commit ebf9500529c7db30b1c6e1ed4056013d0f83827a Merge: acedcb3 97d4f83 Author: Sun <95302870@qq.com> Date: Wed Nov 15 20:38:05 2023 +0800 Merge branch 'feature/drag' into dev commit acedcb32a03ed0ee1833143912a9215182da3fb6 Merge: f105e10 c84eae3 Author: Sun <95302870@qq.com> Date: Wed Nov 15 20:37:26 2023 +0800 Merge branch 'master' into dev commit 97d4f8368dffca2a16d729e666068a552feca87d Author: Sun <95302870@qq.com> Date: Wed Nov 15 20:36:19 2023 +0800 更新软件包 commit 5108f65275181b899b8fc100c615cb6065dcca5d Author: Sun <95302870@qq.com> Date: Wed Nov 15 20:30:39 2023 +0800 简单监听了一下拖拽 commit dae9aea41f1540ccb74abea2a31af5d2a1e4dcfd Merge: 396db51 f672034 Author: Sun <95302870@qq.com> Date: Wed Nov 15 10:01:00 2023 +0800 Merge branch 'master' into feature/drag commit 396db51979d513559512b0a9702dd0d616c2872b Author: Sun <95302870@qq.com> Date: Wed Nov 15 00:08:02 2023 +0800 历史性时刻,拖拽图标 commit f105e10fe1ced11d0b32eba37cfbfdb94f6ad07b Author: Sun <95302870@qq.com> Date: Tue Nov 14 11:35:52 2023 +0800 尝试增加一个分组标题 commit 7e2354f4ed509c7d05667604b7eb56e91f911ed0 Author: Sun <95302870@qq.com> Date: Sun Nov 12 23:07:37 2023 +0800 优化 枚举引用错误 commit 27e85b7da339706ea97604a785bf013dad5f9534 Author: Sun <95302870@qq.com> Date: Sun Nov 12 23:06:50 2023 +0800 优化路由 commit fef462804c0d445f5b9bc7e38e226b55c26017ee Author: Sun <95302870@qq.com> Date: Sun Nov 12 21:28:57 2023 +0800 更换enums的位置
@ -16,12 +16,13 @@ COPY . /build
|
||||
RUN pnpm run build
|
||||
|
||||
# build backend
|
||||
FROM golang:1.19 as server_image
|
||||
FROM golang:1.21-alpine as server_image
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY ./service .
|
||||
|
||||
RUN apk add --no-cache bash curl gcc git go musl-dev
|
||||
|
||||
# 执行指令 关闭链接确认
|
||||
RUN go env -w GO111MODULE=on \
|
||||
@ -34,7 +35,7 @@ RUN go env -w GO111MODULE=on \
|
||||
|
||||
|
||||
# run_image
|
||||
FROM ubuntu
|
||||
FROM alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@ -42,6 +43,8 @@ COPY --from=web_image /build/dist /app/web
|
||||
|
||||
COPY --from=server_image /build/sun-panel /app/sun-panel
|
||||
|
||||
RUN apt-get update && apt-get install -y ca-certificates &&./sun-panel -config
|
||||
RUN apk add --no-cache bash ca-certificates su-exec tzdata \
|
||||
&& chmod +x ./sun-panel \
|
||||
&& ./sun-panel -config
|
||||
|
||||
CMD ./sun-panel
|
||||
|
51
README.md
@ -1,5 +1,7 @@
|
||||
<div align=center>
|
||||
|
||||
|
||||
<img src="./doc/images/logo.png" width="100" height="100" />
|
||||
|
||||
# Sun-Panel
|
||||
|
||||
<a href="https://github.com/hslr-s/sun-panel.git">Github</a> | <a href="https://gitee.com/hslr/sun-panel.git">Gitee</a> | <a href="https://hub.docker.com/r/hslr/sun-panel">Docker Hub</a> | <a href="https://www.bilibili.com/video/BV1AC4y1U7va">B站视频</a>
|
||||
@ -8,13 +10,13 @@
|
||||
|
||||
</div>
|
||||
|
||||

|
||||

|
||||
|
||||
## 😎 特点
|
||||
|
||||
- 局域网内外网链接切换
|
||||
- 简洁
|
||||
- docker 部署
|
||||
- 局域网内外网链接切换
|
||||
- docker部署,对arm系统支持
|
||||
- 上手简单,免修改代码
|
||||
- 无需连接外部数据库
|
||||
- 丰富图标自由搭配(文字图标+svg图标+内置三方图标库)
|
||||
@ -47,22 +49,36 @@
|
||||
|
||||
先画个饼
|
||||
|
||||
- [ ] 图标排序
|
||||
- [x] 分组,拖拽排序
|
||||
- [ ] 导入导出功能
|
||||
- [ ] 增加访客账号
|
||||
- [ ] 用户自定义搜索框搜索引擎
|
||||
- [ ] 搜索框样式自定义(背景颜色,文字颜色)
|
||||
- [ ] 帐号解除邮箱限制
|
||||
- [ ] 对上传的文件管理(针对账户增强重复利用,节省空间)
|
||||
- [ ] 多国语言支持
|
||||
- [ ] 服务器监控
|
||||
- [ ] docker管理器
|
||||
- [ ] 计划任务
|
||||
|
||||
|
||||
|
||||
## 🖼️ 预览截图
|
||||
|
||||

|
||||

|
||||
**各种风格,自由搭配**
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

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

|
||||

|
||||
|
||||
## 🍜 使用教程
|
||||
## 🍜 使用运行教程
|
||||
|
||||
<div id="default-username"></div>
|
||||
|
||||
@ -78,7 +94,20 @@
|
||||
|-config|生成配置文件(conf/conf.ini)|
|
||||
|-password-reset|重置第一个用户的密码|
|
||||
|
||||
### 二进制文件运行
|
||||
|
||||
去 [Releases](https://github.com/hslr-s/sun-panel/releases) 下载二进制文件
|
||||
|
||||
执行示例
|
||||
|
||||
```sh
|
||||
./sun-panel
|
||||
```
|
||||
|
||||
#### 重置密码
|
||||
|
||||
执行示例
|
||||
|
||||
```sh
|
||||
./sun-panel -password-reset
|
||||
```
|
||||
@ -115,14 +144,14 @@ hslr/sun-panel
|
||||
```
|
||||
|
||||
|
||||
### 编译和运行
|
||||
### 自编译运行
|
||||
|
||||
#### 前端
|
||||
```
|
||||
# 开发运行
|
||||
pnpm dev
|
||||
|
||||
# 编译打包
|
||||
# 编译打包(打包后生成dist目录,若需要结合后端使用请改成web)
|
||||
pnpm build
|
||||
```
|
||||
|
||||
|
22
UPDATELOG.md
Normal file
@ -0,0 +1,22 @@
|
||||
# 更新说明
|
||||
> 老用户版本升级需要看升级说明,并且一定提前备份好重要数据。新用户可以直接使用最新版本。
|
||||
|
||||
|
||||
## v1.1.0
|
||||
> 支持上个版本直接升级无需特殊处理
|
||||
|
||||
- [新增] 增加分组,拖拽排序
|
||||
- [新增] 搜索框
|
||||
- [新增] 应用图标支持URL外链
|
||||
- [新增] 图标支持纯透明
|
||||
- [新增] 壁纸背景增加遮罩设置
|
||||
- [新增] 右键菜单新增打开局域网或者互联网地址
|
||||
- [优化] 网址输入框增加https/http提示
|
||||
- [优化] 小图标模式,鼠标悬浮显示详情,支持隐藏图标标题
|
||||
- [优化] 详情图标样式,支持隐藏描述信息
|
||||
- [优化] 添加用户密码时限制字符
|
||||
- [其他] 新增arm版本docker支持。[DockerHub](https://hub.docker.com/r/hslr/sun-panel)直接拉取即可
|
||||
- [其他] 新增多平台二进制文件运行。[Releases](https://github.com/hslr-s/sun-panel/releases)
|
||||
|
||||
## v1.0.0
|
||||
- 首个版本
|
143
build.sh
Normal file
@ -0,0 +1,143 @@
|
||||
#!/bin/bash
|
||||
|
||||
REPO=$(
|
||||
cd $(dirname $0)
|
||||
pwd
|
||||
)
|
||||
COMMIT_SHA=$(git rev-parse --short HEAD)
|
||||
VERSION=$(git describe --tags)
|
||||
# VERSION="0.1.1"
|
||||
FRONTEND="false"
|
||||
BINARY="false"
|
||||
RELEASE="false"
|
||||
|
||||
debugInfo() {
|
||||
echo "Repo: $REPO"
|
||||
echo "Build frontend: $FRONTEND"
|
||||
echo "Build binary: $BINARY"
|
||||
echo "Release: $RELEASE"
|
||||
echo "Version: $VERSION"
|
||||
echo "Commit: $COMMIT_SHA"
|
||||
}
|
||||
|
||||
buildFrontend() {
|
||||
cd $REPO
|
||||
pwd
|
||||
# npm install pnpm -g
|
||||
pnpm install
|
||||
pnpm run build
|
||||
}
|
||||
|
||||
buildBackEndAssets() {
|
||||
cd $REPO/service
|
||||
# export PATH=$PATH:/root/go/bin
|
||||
go install -a -v github.com/go-bindata/go-bindata/...@latest
|
||||
go install -a -v github.com/elazarl/go-bindata-assetfs/...@latest
|
||||
go-bindata-assetfs -o=assets/bindata.go -pkg=assets assets/...
|
||||
}
|
||||
|
||||
# buildBinary() {
|
||||
# cd $REPO/service
|
||||
# # mv "${REPO}/dist" "${REPO}/web"
|
||||
# go build -o "sun-panel" --ldflags="-X sun-panel/global.RUNCODE=release" main.go
|
||||
# }
|
||||
|
||||
_build() {
|
||||
cd $REPO/service
|
||||
pwd
|
||||
local osarch=$1
|
||||
IFS=/ read -r -a arr <<<"$osarch"
|
||||
os="${arr[0]}"
|
||||
arch="${arr[1]}"
|
||||
gcc="${arr[2]}"
|
||||
|
||||
# Go build to build the binary.
|
||||
export GOOS=$os
|
||||
export GOARCH=$arch
|
||||
export CC=$gcc
|
||||
export CGO_ENABLED=1
|
||||
|
||||
pathRelease=$REPO/release
|
||||
|
||||
if [ -n "$VERSION" ]; then
|
||||
outPath="sun-panel_${VERSION}_${os}_${arch}"
|
||||
else
|
||||
outPath="sun-panel_${COMMIT_SHA}_${os}_${arch}"
|
||||
fi
|
||||
outname="${pathRelease}/${outPath}/sun-panel"
|
||||
go build -o "${outname}" --ldflags="-X sun-panel/global.RUNCODE=release" main.go
|
||||
|
||||
cd $pathRelease
|
||||
# copy front file
|
||||
cp -r "${REPO}/dist" "${pathRelease}/${outPath}/web"
|
||||
|
||||
echo "Release ${outPath}"
|
||||
if [ "$os" = "windows" ]; then
|
||||
mv $outname $outPath/sun-panel.exe
|
||||
zip -r "${pathRelease}/${outPath}.zip" $outPath
|
||||
else
|
||||
mv $outname $outPath/sun-panel
|
||||
tar -zcvf "${pathRelease}/${outPath}.tar.gz" $outPath
|
||||
fi
|
||||
rm -rf "${pathRelease}/${outPath}"
|
||||
}
|
||||
|
||||
release() {
|
||||
cd $REPO/service
|
||||
## List of architectures and OS to test coss compilation.
|
||||
SUPPORTED_OSARCH="linux/amd64/gcc linux/arm/arm-linux-gnueabihf-gcc windows/amd64/x86_64-w64-mingw32-gcc linux/arm64/aarch64-linux-gnu-gcc"
|
||||
|
||||
echo "Release builds for OS/Arch/CC: ${SUPPORTED_OSARCH}"
|
||||
for each_osarch in ${SUPPORTED_OSARCH}; do
|
||||
_build "${each_osarch}"
|
||||
done
|
||||
}
|
||||
|
||||
usage() {
|
||||
# echo "Usage: $0 [-f] [-c] [-b] [-r]" 1>&2
|
||||
echo "Usage: $0 [-f] [-b] [-r]" 1>&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
while getopts "bfcrd" o; do
|
||||
case "${o}" in
|
||||
b)
|
||||
FRONTEND="true"
|
||||
BINARY="true"
|
||||
;;
|
||||
f)
|
||||
FRONTEND="true"
|
||||
;;
|
||||
c)
|
||||
BINARY="true"
|
||||
;;
|
||||
r)
|
||||
FRONTEND="true"
|
||||
RELEASE="true"
|
||||
;;
|
||||
d)
|
||||
DEBUG="true"
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
if [ "$DEBUG" = "true" ]; then
|
||||
debugInfo
|
||||
fi
|
||||
|
||||
if [ "$FRONTEND" = "true" ]; then
|
||||
buildFrontend
|
||||
fi
|
||||
|
||||
# if [ "$BINARY" = "true" ]; then
|
||||
# buildBinary
|
||||
# fi
|
||||
|
||||
if [ "$RELEASE" = "true" ]; then
|
||||
buildBackEndAssets
|
||||
release
|
||||
fi
|
BIN
doc/images/icon-info-new.png
Normal file
After Width: | Height: | Size: 234 KiB |
BIN
doc/images/icon-small-new.png
Normal file
After Width: | Height: | Size: 238 KiB |
BIN
doc/images/logo.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
doc/images/solid-color-info.png
Normal file
After Width: | Height: | Size: 231 KiB |
BIN
doc/images/transparent-info.png
Normal file
After Width: | Height: | Size: 262 KiB |
BIN
doc/images/transparent-small.png
Normal file
After Width: | Height: | Size: 271 KiB |
14
docker-compose.yml
Normal file
@ -0,0 +1,14 @@
|
||||
version: "3.2"
|
||||
|
||||
services:
|
||||
sun-panel:
|
||||
image: 'hslr/sun-panel:latest'
|
||||
container_name: sun-panel
|
||||
volumes:
|
||||
- ./conf:/app/conf
|
||||
- ./uploads:/app/uploads
|
||||
- ./database:/app/database
|
||||
# - ./runtime:/app/runtime
|
||||
ports:
|
||||
- 3002:3002
|
||||
restart: always
|
4
package-lock.json
generated
@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "chatgpt-web",
|
||||
"name": "sun-panel",
|
||||
"version": "2.10.9",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "chatgpt-web",
|
||||
"name": "sun-panel",
|
||||
"version": "2.10.9",
|
||||
"dependencies": {
|
||||
"@traptitech/markdown-it-katex": "^3.6.0",
|
||||
|
@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "chatgpt-web",
|
||||
"name": "sun-panel",
|
||||
"version": "2.10.9",
|
||||
"private": false,
|
||||
"description": "ChatGPT Web",
|
||||
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
||||
"keywords": [
|
||||
"chatgpt-web",
|
||||
"Sun-Panel",
|
||||
"chatgpt",
|
||||
"chatbot",
|
||||
"vue"
|
||||
@ -32,6 +32,7 @@
|
||||
"naive-ui": "^2.34.3",
|
||||
"pinia": "^2.0.33",
|
||||
"vue": "^3.2.47",
|
||||
"vue-draggable-plus": "^0.2.6",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.1.6",
|
||||
"vuedraggable": "^4.1.0"
|
||||
|
74
pnpm-lock.yaml
generated
@ -11,15 +11,9 @@ dependencies:
|
||||
'@vueuse/core':
|
||||
specifier: ^9.13.0
|
||||
version: 9.13.0(vue@3.2.47)
|
||||
echarts:
|
||||
specifier: ^5.4.2
|
||||
version: 5.4.2
|
||||
highlight.js:
|
||||
specifier: ^11.7.0
|
||||
version: 11.7.0
|
||||
html2canvas:
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1
|
||||
katex:
|
||||
specifier: ^0.16.4
|
||||
version: 0.16.4
|
||||
@ -38,6 +32,9 @@ dependencies:
|
||||
vue:
|
||||
specifier: ^3.2.47
|
||||
version: 3.2.47
|
||||
vue-draggable-plus:
|
||||
specifier: ^0.2.6
|
||||
version: 0.2.6(@types/sortablejs@1.15.5)
|
||||
vue-i18n:
|
||||
specifier: ^9.2.2
|
||||
version: 9.2.2(vue@3.2.47)
|
||||
@ -2174,6 +2171,10 @@ packages:
|
||||
resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==}
|
||||
dev: true
|
||||
|
||||
/@types/sortablejs@1.15.5:
|
||||
resolution: {integrity: sha512-qqqbEFbB1EZt08I1Ok2BA3Sx0zlI8oizdIguMsajk4Yo/iHgXhCb3GM6N09JOJqT9xIMYM9LTFy8vit3RNY71Q==}
|
||||
dev: false
|
||||
|
||||
/@types/trusted-types@2.0.3:
|
||||
resolution: {integrity: sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==}
|
||||
dev: true
|
||||
@ -2733,11 +2734,6 @@ packages:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
dev: true
|
||||
|
||||
/base64-arraybuffer@1.0.2:
|
||||
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
dev: false
|
||||
|
||||
/binary-extensions@2.2.0:
|
||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||
engines: {node: '>=8'}
|
||||
@ -3087,12 +3083,6 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/css-line-break@2.1.0:
|
||||
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
|
||||
dependencies:
|
||||
utrie: 1.0.2
|
||||
dev: false
|
||||
|
||||
/css-render@0.15.12:
|
||||
resolution: {integrity: sha512-eWzS66patiGkTTik+ipO9qNGZ+uNuGyTmnz6/+EJIiFg8+3yZRpnMwgFo8YdXhQRsiePzehnusrxVvugNjXzbw==}
|
||||
dependencies:
|
||||
@ -3279,13 +3269,6 @@ packages:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
dev: true
|
||||
|
||||
/echarts@5.4.2:
|
||||
resolution: {integrity: sha512-2W3vw3oI2tWJdyAz+b8DuWS0nfXtSDqlDmqgin/lfzbkB01cuMEN66KWBlmur3YMp5nEDEEt5s23pllnAzB4EA==}
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
zrender: 5.4.3
|
||||
dev: false
|
||||
|
||||
/ejs@3.1.8:
|
||||
resolution: {integrity: sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -4238,14 +4221,6 @@ packages:
|
||||
lru-cache: 6.0.0
|
||||
dev: true
|
||||
|
||||
/html2canvas@1.4.1:
|
||||
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
dependencies:
|
||||
css-line-break: 2.1.0
|
||||
text-segmentation: 1.0.3
|
||||
dev: false
|
||||
|
||||
/htmlparser2@8.0.1:
|
||||
resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==}
|
||||
dependencies:
|
||||
@ -5093,6 +5068,7 @@ packages:
|
||||
|
||||
/ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
|
||||
/muggle-string@0.2.2:
|
||||
@ -6224,12 +6200,6 @@ packages:
|
||||
engines: {node: '>=0.10'}
|
||||
dev: true
|
||||
|
||||
/text-segmentation@1.0.3:
|
||||
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
|
||||
dependencies:
|
||||
utrie: 1.0.2
|
||||
dev: false
|
||||
|
||||
/text-table@0.2.0:
|
||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||
dev: true
|
||||
@ -6314,10 +6284,6 @@ packages:
|
||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||
dev: true
|
||||
|
||||
/tslib@2.3.0:
|
||||
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
|
||||
dev: false
|
||||
|
||||
/tslib@2.5.0:
|
||||
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
|
||||
dev: true
|
||||
@ -6462,12 +6428,6 @@ packages:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
dev: true
|
||||
|
||||
/utrie@1.0.2:
|
||||
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
|
||||
dependencies:
|
||||
base64-arraybuffer: 1.0.2
|
||||
dev: false
|
||||
|
||||
/v8-compile-cache-lib@3.0.1:
|
||||
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
||||
dev: true
|
||||
@ -6566,6 +6526,18 @@ packages:
|
||||
vue: 3.2.47
|
||||
dev: false
|
||||
|
||||
/vue-draggable-plus@0.2.6(@types/sortablejs@1.15.5):
|
||||
resolution: {integrity: sha512-d+0omKIBIfLiJFggc6H4ePRaifbX+33+OiCMsxn8rG59yWXlJGrobexxgXetnSo/1NLTd0TkYZKNc4CA6iwJZw==}
|
||||
peerDependencies:
|
||||
'@types/sortablejs': ^1.15.0
|
||||
'@vue/composition-api': '*'
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/sortablejs': 1.15.5
|
||||
dev: false
|
||||
|
||||
/vue-eslint-parser@9.1.0(eslint@8.35.0):
|
||||
resolution: {integrity: sha512-NGn/iQy8/Wb7RrRa4aRkokyCZfOUWk19OP5HP6JEozQFX5AoS/t+Z0ZN7FY4LlmWc4FNI922V7cvX28zctN8dQ==}
|
||||
engines: {node: ^14.17.0 || >=16.0.0}
|
||||
@ -6953,9 +6925,3 @@ packages:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/zrender@5.4.3:
|
||||
resolution: {integrity: sha512-DRUM4ZLnoaT0PBVvGBDO9oWIDBKFdAVieNWxWwK0niYzJCMwGchRk21/hsE+RKkIveH3XHCyvXcJDkgLVvfizQ==}
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
dev: false
|
||||
|
1
public/favicon-black.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 566.93 566.93"><defs><style>.cls-1{fill:#040000;}</style></defs><path class="cls-1" d="M99.37,502.4H345.56A156.16,156.16,0,0,0,501.72,346.25h0A156.16,156.16,0,0,0,345.56,190.1h-123A33.54,33.54,0,0,0,189,223.61v1a33.54,33.54,0,0,0,33.54,33.56H344.44c48.51,0,88.77,38.74,89.24,87.25a88.22,88.22,0,0,1-88.12,89s-163.09.37-245.94-.27a34.15,34.15,0,0,0-34.41,34.15h0A34.15,34.15,0,0,0,99.37,502.4Z"/><path class="cls-1" d="M467.56,64.53H221.37A156.15,156.15,0,0,0,65.21,220.68h0A156.15,156.15,0,0,0,221.37,376.83h123a33.54,33.54,0,0,0,33.54-33.51v-1a33.54,33.54,0,0,0-33.54-33.56H222.49c-48.52,0-88.77-38.74-89.24-87.25a88.22,88.22,0,0,1,88.12-89s163.09-.37,245.94.27a34.15,34.15,0,0,0,34.41-34.15h0A34.15,34.15,0,0,0,467.56,64.53Z"/></svg>
|
After Width: | Height: | Size: 823 B |
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 11 KiB |
@ -1 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"><svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none"><path d="M663 1649 c-131 -41 -254 -157 -304 -286 -33 -86 -38 -221 -11 -310 36 -117 97 -200 196 -265 108 -70 153 -78 438 -78 l250 0 29 29 c38 39 40 97 5 138 l-24 28 -239 5 c-270 7 -300 13 -370 74 -149 131 -116 368 62 452 l50 24 463 0 462 0 0 105 0 105 -472 -1 c-442 0 -477 -1 -535 -20z"/><path d="M739 1261 c-38 -39 -40 -97 -5 -138 l24 -28 239 -5 c270 -7 300 -13 370 -74 150 -132 115 -373 -67 -455 -43 -20 -64 -21 -507 -21 l-463 0 0 -105 0 -105 469 0 c443 0 472 1 535 20 133 42 256 156 307 287 33 86 38 221 11 310 -23 74 -81 171 -128 214 -49 45 -126 89 -193 109 -56 18 -93 20 -313 20 l-250 0 -29 -29z"/></g></svg>
|
||||
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 566.93 566.93"><defs><style>.cls-1{fill:#4fb3bb;}</style></defs><path class="cls-1" d="M99.37,502.4H345.56A156.16,156.16,0,0,0,501.72,346.25h0A156.16,156.16,0,0,0,345.56,190.1h-123A33.54,33.54,0,0,0,189,223.61v1a33.54,33.54,0,0,0,33.54,33.56H344.44c48.51,0,88.77,38.74,89.24,87.25a88.22,88.22,0,0,1-88.12,89s-163.09.37-245.94-.27a34.15,34.15,0,0,0-34.41,34.15h0A34.15,34.15,0,0,0,99.37,502.4Z"/><path class="cls-1" d="M467.56,64.53H221.37A156.15,156.15,0,0,0,65.21,220.68h0A156.15,156.15,0,0,0,221.37,376.83h123a33.54,33.54,0,0,0,33.54-33.51v-1a33.54,33.54,0,0,0-33.54-33.56H222.49c-48.52,0-88.77-38.74-89.24-87.25a88.22,88.22,0,0,1,88.12-89s163.09-.37,245.94.27a34.15,34.15,0,0,0,34.41-34.15h0A34.15,34.15,0,0,0,467.56,64.53Z"/></svg>
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 823 B |
BIN
public/logo.png
Normal file
After Width: | Height: | Size: 62 KiB |
@ -20,3 +20,12 @@ type VerificationResponse struct {
|
||||
Result bool `json:"result"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type SortRequestItem struct {
|
||||
Id uint `json:"id"`
|
||||
Sort uint `json:"sort"`
|
||||
}
|
||||
|
||||
type SortRequest struct {
|
||||
SortItems []SortRequestItem `json:"sortItems"`
|
||||
}
|
||||
|
@ -1,8 +1,16 @@
|
||||
package adminApiStructs
|
||||
package panelApiStructs
|
||||
|
||||
import "sun-panel/models"
|
||||
import (
|
||||
"sun-panel/api/api_v1/common/apiData/commonApiStructs"
|
||||
"sun-panel/models"
|
||||
)
|
||||
|
||||
type ItemIconEditRequest struct {
|
||||
models.ItemIcon
|
||||
IconJson string
|
||||
}
|
||||
|
||||
type ItemIconSaveSortRequest struct {
|
||||
SortItems []commonApiStructs.SortRequestItem `json:"sortItems"`
|
||||
ItemIconGroupId uint `json:"itemIconGroupId"`
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package panel
|
||||
|
||||
type ApiPanel struct {
|
||||
ItemIcon ItemIcon
|
||||
UserConfig UserConfig
|
||||
UsersApi UsersApi
|
||||
ItemIcon ItemIcon
|
||||
UserConfig UserConfig
|
||||
UsersApi UsersApi
|
||||
ItemIconGroup ItemIconGroup
|
||||
}
|
||||
|
152
service/api/api_v1/panel/ItemIconGroup.go
Normal file
@ -0,0 +1,152 @@
|
||||
package panel
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sun-panel/api/api_v1/common/apiData/commonApiStructs"
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
"sun-panel/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ItemIconGroup struct {
|
||||
}
|
||||
|
||||
func (a *ItemIconGroup) Edit(c *gin.Context) {
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
req := models.ItemIconGroup{}
|
||||
|
||||
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
req.UserId = userInfo.ID
|
||||
|
||||
if req.ID != 0 {
|
||||
// 修改
|
||||
updateField := []string{"IconJson", "Icon", "Title", "Url", "LanUrl", "Description", "OpenMethod", "GroupId", "UserId"}
|
||||
if req.Sort != 0 {
|
||||
updateField = append(updateField, "Sort")
|
||||
}
|
||||
global.Db.Model(&models.ItemIconGroup{}).
|
||||
Select(updateField).
|
||||
Where("id=?", req.ID).Updates(&req)
|
||||
} else {
|
||||
// 创建
|
||||
global.Db.Create(&req)
|
||||
}
|
||||
|
||||
apiReturn.SuccessData(c, req)
|
||||
}
|
||||
|
||||
func (a *ItemIconGroup) GetList(c *gin.Context) {
|
||||
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
groups := []models.ItemIconGroup{}
|
||||
|
||||
if err := global.Db.Order("sort ,created_at").Where("user_id=?", userInfo.ID).Find(&groups).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 判断分组是否为空,为空将自动创建默认分组
|
||||
if len(groups) == 0 {
|
||||
defaultGroup := models.ItemIconGroup{
|
||||
Title: "APP",
|
||||
UserId: userInfo.ID,
|
||||
Icon: "material-symbols:ad-group-outline"}
|
||||
if err := global.Db.Create(&defaultGroup).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 并将当前账号下所有无分组的图标更新到当前组
|
||||
if err := global.Db.Model(&models.ItemIcon{}).Where("user_id=?", userInfo.ID).Update("item_icon_group_id", defaultGroup.ID).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
groups = append(groups, defaultGroup)
|
||||
}
|
||||
|
||||
apiReturn.SuccessListData(c, groups, 0)
|
||||
}
|
||||
|
||||
func (a *ItemIconGroup) Deletes(c *gin.Context) {
|
||||
req := commonApiStructs.RequestDeleteIds[uint]{}
|
||||
|
||||
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
|
||||
var count int64
|
||||
if err := global.Db.Model(&models.ItemIconGroup{}).Where(" user_id=?", userInfo.ID).Count(&count).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
} else {
|
||||
if math.Abs(float64(len(req.Ids))-float64(count)) < 1 {
|
||||
apiReturn.Error(c, "至少要保留一个")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
txErr := global.Db.Transaction(func(tx *gorm.DB) error {
|
||||
mitemIcon := models.ItemIcon{}
|
||||
if err := tx.Delete(&models.ItemIconGroup{}, "id in ? AND user_id=?", req.Ids, userInfo.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mitemIcon.DeleteByItemIconGroupIds(tx, userInfo.ID, req.Ids); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if txErr != nil {
|
||||
apiReturn.ErrorDatabase(c, txErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
|
||||
// 保存排序
|
||||
func (a *ItemIconGroup) SaveSort(c *gin.Context) {
|
||||
req := commonApiStructs.SortRequest{}
|
||||
|
||||
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
|
||||
transactionErr := global.Db.Transaction(func(tx *gorm.DB) error {
|
||||
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
|
||||
for _, v := range req.SortItems {
|
||||
if err := tx.Model(&models.ItemIconGroup{}).Where("user_id=? AND id=?", userInfo.ID, v.Id).Update("sort", v.Sort).Error; err != nil {
|
||||
// 返回任何错误都会回滚事务
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 返回 nil 提交事务
|
||||
return nil
|
||||
})
|
||||
|
||||
if transactionErr != nil {
|
||||
apiReturn.ErrorDatabase(c, transactionErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
apiReturn.Success(c)
|
||||
}
|
@ -3,6 +3,7 @@ package panel
|
||||
import (
|
||||
"encoding/json"
|
||||
"sun-panel/api/api_v1/common/apiData/commonApiStructs"
|
||||
"sun-panel/api/api_v1/common/apiData/panelApiStructs"
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
@ -10,6 +11,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ItemIcon struct {
|
||||
@ -24,9 +26,12 @@ func (a *ItemIcon) Edit(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.ItemIconGroupId == 0 {
|
||||
apiReturn.Error(c, "分组为必填项")
|
||||
return
|
||||
}
|
||||
|
||||
req.UserId = userInfo.ID
|
||||
req.GroupId = 1
|
||||
req.Sort = 1
|
||||
|
||||
// json转字符串
|
||||
if j, err := json.Marshal(req.Icon); err == nil {
|
||||
@ -35,10 +40,15 @@ func (a *ItemIcon) Edit(c *gin.Context) {
|
||||
|
||||
if req.ID != 0 {
|
||||
// 修改
|
||||
updateField := []string{"IconJson", "Icon", "Title", "Url", "LanUrl", "Description", "OpenMethod", "GroupId", "UserId", "ItemIconGroupId"}
|
||||
if req.Sort != 0 {
|
||||
updateField = append(updateField, "Sort")
|
||||
}
|
||||
global.Db.Model(&models.ItemIcon{}).
|
||||
Select("IconJson", "Icon", "Title", "Url", "LanUrl", "Description", "OpenMethod", "Sort", "GroupId", "UserId").
|
||||
Select(updateField).
|
||||
Where("id=?", req.ID).Updates(&req)
|
||||
} else {
|
||||
req.Sort = 9999
|
||||
// 创建
|
||||
global.Db.Create(&req)
|
||||
}
|
||||
@ -77,7 +87,7 @@ func (a *ItemIcon) Edit(c *gin.Context) {
|
||||
// }
|
||||
|
||||
func (a *ItemIcon) GetListByGroupId(c *gin.Context) {
|
||||
req := commonApiStructs.RequestPage{}
|
||||
req := models.ItemIcon{}
|
||||
|
||||
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
@ -87,7 +97,7 @@ func (a *ItemIcon) GetListByGroupId(c *gin.Context) {
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
itemIcons := []models.ItemIcon{}
|
||||
|
||||
if err := global.Db.Order("sort ,created_at DESC").Where("user_id=?", userInfo.ID).Find(&itemIcons, "group_id = ? AND user_id=?", 1, userInfo.ID).Error; err != nil {
|
||||
if err := global.Db.Order("sort ,created_at").Find(&itemIcons, "item_icon_group_id = ? AND user_id=?", req.ItemIconGroupId, userInfo.ID).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
@ -108,10 +118,42 @@ func (a *ItemIcon) Deletes(c *gin.Context) {
|
||||
}
|
||||
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
if err := global.Db.Debug().Delete(&models.ItemIcon{}, "id in ? AND user_id=?", req.Ids, userInfo.ID).Error; err != nil {
|
||||
if err := global.Db.Delete(&models.ItemIcon{}, "id in ? AND user_id=?", req.Ids, userInfo.ID).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
|
||||
// 保存排序
|
||||
func (a *ItemIcon) SaveSort(c *gin.Context) {
|
||||
req := panelApiStructs.ItemIconSaveSortRequest{}
|
||||
|
||||
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
|
||||
transactionErr := global.Db.Transaction(func(tx *gorm.DB) error {
|
||||
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
|
||||
for _, v := range req.SortItems {
|
||||
if err := tx.Model(&models.ItemIcon{}).Where("user_id=? AND id=? AND item_icon_group_id=?", userInfo.ID, v.Id, req.ItemIconGroupId).Update("sort", v.Sort).Error; err != nil {
|
||||
// 返回任何错误都会回滚事务
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 返回 nil 提交事务
|
||||
return nil
|
||||
})
|
||||
|
||||
if transactionErr != nil {
|
||||
apiReturn.ErrorDatabase(c, transactionErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package panel
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
@ -9,6 +10,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 此API 临时使用,后期带有管理功能,将废除!!!
|
||||
@ -64,10 +66,55 @@ func (a UsersApi) Deletes(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := global.Db.Delete(&models.User{}, ¶m.UserIds).Error; err != nil {
|
||||
var count int64
|
||||
if err := global.Db.Model(&models.User{}).Count(&count).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
} else {
|
||||
if math.Abs(float64(len(param.UserIds))-float64(count)) < 1 {
|
||||
apiReturn.Error(c, "至少要保留一个")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
txErr := global.Db.Transaction(func(tx *gorm.DB) error {
|
||||
mitemIconGroup := models.ItemIconGroup{}
|
||||
|
||||
for _, v := range param.UserIds {
|
||||
// 删除图标
|
||||
if err := tx.Delete(&models.ItemIcon{}, "user_id=?", v).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 删除分组
|
||||
if err := mitemIconGroup.DeleteByUserId(tx, v); err != nil {
|
||||
return err
|
||||
}
|
||||
// 删除模块配置
|
||||
if err := tx.Delete(&models.ModuleConfig{}, "user_id=?", v).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 删除用户配置
|
||||
if err := tx.Delete(&models.ModuleConfig{}, "user_id=?", v).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// // 删除文件记录(不删除资源文件)
|
||||
// if err := tx.Delete(&models.File{}, "user_id=?", v).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
}
|
||||
|
||||
if err := tx.Delete(&models.User{}, ¶m.UserIds).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if txErr != nil {
|
||||
apiReturn.ErrorDatabase(c, txErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
package system
|
||||
|
||||
type ApiSystem struct {
|
||||
About About
|
||||
LoginApi LoginApi
|
||||
UserApi UserApi
|
||||
FileApi FileApi
|
||||
CaptchaApi CaptchaApi
|
||||
RegisterApi RegisterApi
|
||||
NoticeApi NoticeApi
|
||||
About About
|
||||
LoginApi LoginApi
|
||||
UserApi UserApi
|
||||
FileApi FileApi
|
||||
CaptchaApi CaptchaApi
|
||||
RegisterApi RegisterApi
|
||||
NoticeApi NoticeApi
|
||||
ModuleConfigApi ModuleConfigApi
|
||||
}
|
||||
|
53
service/api/api_v1/system/ModuleConfig.go
Normal file
@ -0,0 +1,53 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
"sun-panel/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
type ModuleConfigApi struct{}
|
||||
|
||||
func (a *ModuleConfigApi) GetByName(c *gin.Context) {
|
||||
req := models.ModuleConfig{}
|
||||
|
||||
if err := c.ShouldBindWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
|
||||
mCfg := models.ModuleConfig{}
|
||||
if cfg, err := mCfg.GetConfigByUserIdAndName(global.Db, userInfo.ID, req.Name); err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
} else {
|
||||
apiReturn.SuccessData(c, cfg)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (a *ModuleConfigApi) Save(c *gin.Context) {
|
||||
req := models.ModuleConfig{}
|
||||
if err := c.ShouldBindWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
mCfg := models.ModuleConfig{}
|
||||
mCfg.UserId = userInfo.ID
|
||||
mCfg.Value = req.Value
|
||||
mCfg.Name = req.Name
|
||||
|
||||
if err := mCfg.Save(global.Db); err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
apiReturn.Success(c)
|
||||
}
|
@ -23,8 +23,8 @@ type LoginApi struct {
|
||||
|
||||
// 登录输入验证
|
||||
type LoginLoginVerify struct {
|
||||
Username string `json:"username" validate:"required,min=5"`
|
||||
Password string `json:"password" validate:"required,min=5,max=20"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required,max=50"`
|
||||
VCode string `json:"vcode" validate:"max=6"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ host=127.0.0.1
|
||||
port=3306
|
||||
username=root
|
||||
password=root
|
||||
db_name=chatgpt
|
||||
db_name=sun_panel
|
||||
wait_timeout=100
|
||||
|
||||
# ======================
|
||||
@ -39,37 +39,4 @@ file_path=./database/database.db
|
||||
address=127.0.0.1:6379
|
||||
password=
|
||||
prefix=sun_panel:
|
||||
db=0
|
||||
|
||||
# ================
|
||||
# proxy
|
||||
# ================
|
||||
[proxy]
|
||||
url=
|
||||
|
||||
# ======================
|
||||
# Automatically generated
|
||||
# Prohibit modification
|
||||
# ======================
|
||||
[build]
|
||||
conf_version=1
|
||||
; install_time=132456
|
||||
|
||||
|
||||
# ======================
|
||||
# Initial configuration
|
||||
# ======================
|
||||
# Automatically delete after successful initialization
|
||||
[init]
|
||||
# Administrator account (try to use email format; 6-16 digits)
|
||||
admin_username=admin
|
||||
# Administrator password (6-16 digits)
|
||||
admin_password=123456
|
||||
|
||||
# ======================
|
||||
# 支付宝支付相关配置
|
||||
# ======================
|
||||
[alipay]
|
||||
appid=
|
||||
private_key=
|
||||
alipay_public_key=
|
||||
db=0
|
@ -1 +1 @@
|
||||
1|1.0.0
|
||||
3|1.1.0
|
@ -29,6 +29,7 @@ var DB_DRIVER = database.SQLITE
|
||||
// var ISDOCER = "" // 是否为docker模式
|
||||
|
||||
func InitApp() error {
|
||||
Logo()
|
||||
gin.SetMode(global.RUNCODE) // GIN 运行模式
|
||||
|
||||
// 日志
|
||||
@ -176,3 +177,17 @@ func CommandRun() {
|
||||
}
|
||||
os.Exit(0) // 务必退出
|
||||
}
|
||||
|
||||
func Logo() {
|
||||
fmt.Println(" ____ ___ __")
|
||||
fmt.Println(" / __/_ _____ / _ \\___ ____ ___ / /")
|
||||
fmt.Println(" _\\ \\/ // / _ \\ / ___/ _ `/ _ \\/ -_) / ")
|
||||
fmt.Println(" /___/\\_,_/_//_/ /_/ \\_,_/_//_/\\__/_/ ")
|
||||
fmt.Println("")
|
||||
|
||||
versionInfo := cmn.GetSysVersionInfo()
|
||||
fmt.Println("Version:", versionInfo.Version)
|
||||
fmt.Println("Welcome to the Sun-Panel.")
|
||||
fmt.Println("Project address:", "https://github.com/hslr-s/sun-panel")
|
||||
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ func (d *SQLiteConfig) Connect() (db *gorm.DB, err error) {
|
||||
|
||||
// 创建文件夹
|
||||
if !exists {
|
||||
if err = os.MkdirAll(path.Dir(filePath), 0666); err != nil {
|
||||
if err = os.MkdirAll(path.Dir(filePath), 0700); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -121,6 +121,8 @@ func CreateDatabase(driver string, db *gorm.DB) error {
|
||||
&models.ItemIcon{},
|
||||
&models.UserConfig{},
|
||||
&models.File{},
|
||||
&models.ItemIconGroup{},
|
||||
&models.ModuleConfig{},
|
||||
)
|
||||
|
||||
return err
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
@ -198,7 +197,7 @@ func AssetsTakeFileToPath(assetsPath, targetPath string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return ioutil.WriteFile(targetPath, bytes, 0666)
|
||||
return os.WriteFile(targetPath, bytes, 0666)
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
|
@ -93,7 +93,7 @@ func NewLog(log_file_name string) *LogStruct {
|
||||
logDir := path.Dir(log_file_name)
|
||||
ok, _ := PathExists(logDir)
|
||||
if !ok {
|
||||
if err := os.MkdirAll(logDir, 0666); err != nil {
|
||||
if err := os.MkdirAll(logDir, 0700); err != nil {
|
||||
fmt.Println("创建日志文件错误", err.Error())
|
||||
}
|
||||
}
|
||||
@ -128,7 +128,7 @@ func RunLog() *LogStruct {
|
||||
runLogStatic.Writer = io.MultiWriter(f)
|
||||
} else {
|
||||
if runLogStatic.File == nil {
|
||||
f, _ := os.OpenFile(log_file_name, os.O_APPEND|os.O_WRONLY, 0666)
|
||||
f, _ := os.OpenFile(log_file_name, os.O_APPEND|os.O_WRONLY, 0700)
|
||||
runLogStatic.File = f
|
||||
runLogStatic.Writer = io.MultiWriter(f)
|
||||
}
|
||||
|
@ -4,5 +4,6 @@ type ItemIconIconInfo struct {
|
||||
ItemType int `json:"itemType"`
|
||||
Src string `json:"src"`
|
||||
Text string `json:"text"`
|
||||
BgColor string `json:"bgColor"`
|
||||
// BgColor string `json:"bgColor"`
|
||||
BackgroundColor string `json:"backgroundColor"`
|
||||
}
|
||||
|
@ -1,18 +1,31 @@
|
||||
package models
|
||||
|
||||
import "sun-panel/models/datatype"
|
||||
import (
|
||||
"sun-panel/models/datatype"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ItemIcon struct {
|
||||
BaseModel
|
||||
IconJson string `gorm:"type:varchar(1000)" json:"-"`
|
||||
Icon datatype.ItemIconIconInfo `gorm:"-" json:"icon"`
|
||||
Title string `gorm:"type:varchar(50)" json:"title"`
|
||||
Url string `gorm:"type:varchar(1000)" json:"url"`
|
||||
LanUrl string `gorm:"type:varchar(1000)" json:"lanUrl"`
|
||||
Description string `gorm:"type:varchar(1000)" json:"description"`
|
||||
OpenMethod int `gorm:"type:tinyint(1)" json:"openMethod"`
|
||||
Sort int `gorm:"type:int(11)" json:"sort"`
|
||||
GroupId int `json:"groupId"` // 为以后分组做准备
|
||||
UserId uint `json:"userId"`
|
||||
User User `json:"user"`
|
||||
IconJson string `gorm:"type:varchar(1000)" json:"-"`
|
||||
Icon datatype.ItemIconIconInfo `gorm:"-" json:"icon"`
|
||||
Title string `gorm:"type:varchar(50)" json:"title"`
|
||||
Url string `gorm:"type:varchar(1000)" json:"url"`
|
||||
LanUrl string `gorm:"type:varchar(1000)" json:"lanUrl"`
|
||||
Description string `gorm:"type:varchar(1000)" json:"description"`
|
||||
OpenMethod int `gorm:"type:tinyint(1)" json:"openMethod"`
|
||||
Sort int `gorm:"type:int(11)" json:"sort"`
|
||||
ItemIconGroupId int `json:"itemIconGroupId"`
|
||||
UserId uint `json:"userId"`
|
||||
User User `json:"user"`
|
||||
}
|
||||
|
||||
func (m *ItemIcon) DeleteByItemIconGroupIds(db *gorm.DB, userId uint, itemIconGroupIds []uint) (err error) {
|
||||
err = db.Delete(&ItemIcon{}, "item_icon_group_id in ? AND user_id=?", itemIconGroupIds, userId).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (m *ItemIcon) DeleteByUserId(db *gorm.DB, userId uint) (err error) {
|
||||
return db.Delete(&ItemIcon{}, "user_id=?", userId).Error
|
||||
}
|
||||
|
20
service/models/itemIconGroup.go
Normal file
@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ItemIconGroup struct {
|
||||
BaseModel
|
||||
Icon string `json:"icon"`
|
||||
Title string `gorm:"type:varchar(50)" json:"title"`
|
||||
Description string `gorm:"type:varchar(1000)" json:"description"`
|
||||
Sort int `gorm:"type:int(11)" json:"sort"`
|
||||
UserId uint `json:"userId"`
|
||||
User User `json:"user"`
|
||||
}
|
||||
|
||||
func (m *ItemIconGroup) DeleteByUserId(db *gorm.DB, userId uint) (err error) {
|
||||
err = db.Delete(&ItemIconGroup{}, "user_id = ?", userId).Error
|
||||
return
|
||||
}
|
61
service/models/moduleConfig.go
Normal file
@ -0,0 +1,61 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ModuleConfig struct {
|
||||
BaseModel
|
||||
UserId uint `gorm:"index" json:"userId"`
|
||||
Name string `gorm:"type:varchar(255)" json:"name"`
|
||||
ValueJson string `gorm:"type:text" json:"-"`
|
||||
Value map[string]interface{} `gorm:"-" json:"value"`
|
||||
}
|
||||
|
||||
func (m *ModuleConfig) GetConfigByUserIdAndName(db *gorm.DB, userId uint, name string) (map[string]interface{}, error) {
|
||||
cfg := ModuleConfig{}
|
||||
if err := db.First(&cfg, "user_id=? AND name=?", userId, name).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 处理字段
|
||||
if err := json.Unmarshal([]byte(cfg.ValueJson), &cfg.Value); err != nil {
|
||||
cfg.Value = nil
|
||||
}
|
||||
return cfg.Value, nil
|
||||
}
|
||||
|
||||
func (m *ModuleConfig) Save(db *gorm.DB) error {
|
||||
|
||||
// 处理字段
|
||||
if jb, err := json.Marshal(m.Value); err != nil {
|
||||
m.ValueJson = "{}"
|
||||
} else {
|
||||
m.ValueJson = string(jb)
|
||||
}
|
||||
|
||||
// 保存操作
|
||||
if err := db.First(&ModuleConfig{}, "user_id=? AND name=?", m.UserId, m.Name).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// 新增
|
||||
if err := db.Create(&m).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 修改
|
||||
if err := db.Select("Name", "UserId", "ValueJson").Where("user_id=? AND name=?", m.UserId, m.Name).Updates(&m).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -6,4 +6,5 @@ func Init(routerGroup *gin.RouterGroup) {
|
||||
InitItemIcon(routerGroup)
|
||||
InitUserConfig(routerGroup)
|
||||
InitUsersRouter(routerGroup)
|
||||
InitItemIconGroup(routerGroup)
|
||||
}
|
||||
|
@ -14,5 +14,6 @@ func InitItemIcon(router *gin.RouterGroup) {
|
||||
r.POST("/panel/itemIcon/edit", itemIcon.Edit)
|
||||
r.POST("/panel/itemIcon/getListByGroupId", itemIcon.GetListByGroupId)
|
||||
r.POST("/panel/itemIcon/deletes", itemIcon.Deletes)
|
||||
r.POST("/panel/itemIcon/saveSort", itemIcon.SaveSort)
|
||||
}
|
||||
}
|
||||
|
19
service/router/panel/itemIconGroup.go
Normal file
@ -0,0 +1,19 @@
|
||||
package panel
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1"
|
||||
"sun-panel/api/api_v1/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func InitItemIconGroup(router *gin.RouterGroup) {
|
||||
itemIconGroup := api_v1.ApiGroupApp.ApiPanel.ItemIconGroup
|
||||
r := router.Group("", middleware.LoginInterceptor)
|
||||
{
|
||||
r.POST("/panel/itemIconGroup/edit", itemIconGroup.Edit)
|
||||
r.POST("/panel/itemIconGroup/getList", itemIconGroup.GetList)
|
||||
r.POST("/panel/itemIconGroup/deletes", itemIconGroup.Deletes)
|
||||
r.POST("/panel/itemIconGroup/saveSort", itemIconGroup.SaveSort)
|
||||
}
|
||||
}
|
@ -10,4 +10,5 @@ func Init(routerGroup *gin.RouterGroup) {
|
||||
InitCaptchaRouter(routerGroup)
|
||||
InitRegister(routerGroup)
|
||||
InitNoticeRouter(routerGroup)
|
||||
InitModuleConfigRouter(routerGroup)
|
||||
}
|
||||
|
16
service/router/system/moduleConfig.go
Normal file
@ -0,0 +1,16 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1"
|
||||
"sun-panel/api/api_v1/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func InitModuleConfigRouter(router *gin.RouterGroup) {
|
||||
api := api_v1.ApiGroupApp.ApiSystem.ModuleConfigApi
|
||||
r := router.Group("", middleware.LoginInterceptor)
|
||||
r.POST("/system/moduleConfig/getByName", api.GetByName)
|
||||
r.POST("/system/moduleConfig/save", api.Save)
|
||||
|
||||
}
|
@ -14,9 +14,10 @@ export function edit<T>(req: Panel.ItemInfo) {
|
||||
// })
|
||||
// }
|
||||
|
||||
export function getListByGroupId<T>() {
|
||||
export function getListByGroupId<T>(itemIconGroupId: number | undefined) {
|
||||
return post<T>({
|
||||
url: '/panel/itemIcon/getListByGroupId',
|
||||
data: { itemIconGroupId },
|
||||
})
|
||||
}
|
||||
|
||||
@ -26,3 +27,10 @@ export function deletes<T>(ids: number[]) {
|
||||
data: { ids },
|
||||
})
|
||||
}
|
||||
|
||||
export function saveSort<T>(data: Panel.ItemIconSortRequest) {
|
||||
return post<T>({
|
||||
url: '/panel/itemIcon/saveSort',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
28
src/api/panel/itemIconGroup.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { post } from '@/utils/request'
|
||||
|
||||
export function edit<T>(req: Panel.ItemIconGroup) {
|
||||
return post<T>({
|
||||
url: '/panel/itemIconGroup/edit',
|
||||
data: req,
|
||||
})
|
||||
}
|
||||
|
||||
export function getList<T>() {
|
||||
return post<T>({
|
||||
url: '/panel/itemIconGroup/getList',
|
||||
})
|
||||
}
|
||||
|
||||
export function deletes<T>(ids: number[]) {
|
||||
return post<T>({
|
||||
url: '/panel/itemIconGroup/deletes',
|
||||
data: { ids },
|
||||
})
|
||||
}
|
||||
|
||||
export function saveSort<T>(sortItems: Common.SortItemRequest[]) {
|
||||
return post<T>({
|
||||
url: '/panel/itemIconGroup/saveSort',
|
||||
data: { sortItems },
|
||||
})
|
||||
}
|
18
src/api/system/moduleConfig.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { post } from '@/utils/request'
|
||||
|
||||
export function getValueByName<T>(name: string) {
|
||||
return post<T>({
|
||||
url: '/system/moduleConfig/getByName',
|
||||
data: { name },
|
||||
})
|
||||
}
|
||||
|
||||
export function save<T>(name: string, value: any) {
|
||||
return post<T>({
|
||||
url: '/system/moduleConfig/save',
|
||||
data: {
|
||||
name,
|
||||
value,
|
||||
},
|
||||
})
|
||||
}
|
BIN
src/assets/about_image/docker.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/about_image/gitee.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
src/assets/about_image/github.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
1
src/assets/logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 566.93 566.93"><defs><style>.cls-1{fill:#4fb3bb;}</style></defs><path class="cls-1" d="M99.37,502.4H345.56A156.16,156.16,0,0,0,501.72,346.25h0A156.16,156.16,0,0,0,345.56,190.1h-123A33.54,33.54,0,0,0,189,223.61v1a33.54,33.54,0,0,0,33.54,33.56H344.44c48.51,0,88.77,38.74,89.24,87.25a88.22,88.22,0,0,1-88.12,89s-163.09.37-245.94-.27a34.15,34.15,0,0,0-34.41,34.15h0A34.15,34.15,0,0,0,99.37,502.4Z"/><path class="cls-1" d="M467.56,64.53H221.37A156.15,156.15,0,0,0,65.21,220.68h0A156.15,156.15,0,0,0,221.37,376.83h123a33.54,33.54,0,0,0,33.54-33.51v-1a33.54,33.54,0,0,0-33.54-33.56H222.49c-48.52,0-88.77-38.74-89.24-87.25a88.22,88.22,0,0,1,88.12-89s163.09-.37,245.94.27a34.15,34.15,0,0,0,34.41-34.15h0A34.15,34.15,0,0,0,467.56,64.53Z"/></svg>
|
After Width: | Height: | Size: 823 B |
1
src/assets/search_engine_svg/baidu.svg
Normal file
After Width: | Height: | Size: 9.7 KiB |
20
src/assets/search_engine_svg/bing.svg
Normal file
@ -0,0 +1,20 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M23.2739 83.9026C24.8696 84.9049 27.8614 87.6111 34.3437 84.6042C37.8342 82.6999 40.128 81.2967 41.3247 80.3947L40.826 80.6953L40.9258 54.3354L40.7263 17.9527C40.7263 17.9527 40.0282 12.7409 36.1388 10.2352C31.8505 6.82742 24.8696 2.01648 23.972 1.7158C23.1742 1.0142 20.1824 -0.0883057 18.2875 3.62013L18.3873 76.6862C18.3873 76.6862 18.487 77.4881 18.6864 78.791C19.8832 80.6953 21.4788 82.9004 23.2739 83.9026Z" fill="url(#paint0_linear_102_2)"/>
|
||||
<path d="M70.2458 62.7546L41.3247 80.2944C40.0282 81.1965 37.8342 82.5997 34.244 84.504C27.8614 87.5109 24.8696 84.8047 23.1742 83.8024C21.3791 82.8001 19.7835 80.5951 18.5867 78.5906C19.1851 81.397 20.5813 86.4083 24.1715 89.9163C28.759 94.627 34.3437 100.741 49.4027 98.5359C52.7934 97.5336 56.4833 95.5291 63.0654 91.1191C68.0518 87.8115 73.1379 86.1077 77.3265 80.2944C82.0137 67.3651 72.739 63.5564 70.2458 62.7546Z" fill="url(#paint1_linear_102_2)"/>
|
||||
<path d="M80.8169 54.0347C77.6256 47.9208 77.127 45.3149 67.1542 39.5017C56.9819 34.2898 55.486 33.488 55.486 33.488C55.486 33.488 52.9928 31.3832 50.5993 31.5837C48.3056 32.586 46.8097 33.9891 48.5051 38.3992C50.7988 44.7135 53.4915 51.6293 53.4915 51.6293C53.4915 51.6293 54.4887 56.7409 58.5776 58.4447C65.6583 60.75 70.2457 62.6543 70.2457 62.6543L70.146 62.7545C72.6392 63.5564 81.9139 67.2648 77.3264 80.3946C78.8223 78.3901 80.1188 75.7842 81.3155 72.5769C82.8115 66.9641 83.5096 64.1577 80.8169 54.0347Z" fill="url(#paint2_linear_102_2)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_102_2" x1="10.1209" y1="6.26564" x2="45.1332" y2="79.3835" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3BBDF5"/>
|
||||
<stop offset="1" stop-color="#1B45D9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_102_2" x1="18.7266" y1="80.9445" x2="78.701" y2="80.9445" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5AD9FE"/>
|
||||
<stop offset="0.51" stop-color="#43AAE1"/>
|
||||
<stop offset="1" stop-color="#144AD5"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_102_2" x1="55.3044" y1="46.4248" x2="82.9308" y2="67.615" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3DC0FE"/>
|
||||
<stop offset="1" stop-color="#61DDD5"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
1
src/assets/search_engine_svg/google.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1639468659890" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3509" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><defs><style type="text/css"></style></defs><path d="M214.101333 512c0-32.512 5.546667-63.701333 15.36-92.928L57.173333 290.218667A491.861333 491.861333 0 0 0 4.693333 512c0 79.701333 18.858667 154.88 52.394667 221.610667l172.202667-129.066667A290.56 290.56 0 0 1 214.101333 512" fill="#FBBC05" p-id="3510"></path><path d="M516.693333 216.192c72.106667 0 137.258667 25.002667 188.458667 65.96266699L854.101333 136.533333C763.349333 59.178667 646.997333 11.392 516.693333 11.392c-202.325333 0-376.234667 113.28-459.52 278.826667l172.373334 128.853333c39.68-118.016 152.832-202.88 287.146666-202.88" fill="#EA4335" p-id="3511"></path><path d="M516.693333 807.808c-134.357333 0-247.509333-84.864-287.232-202.88l-172.288 128.853333c83.242667 165.546667 257.152 278.826667 459.52 278.826667 124.842667 0 244.053333-43.392 333.568-124.757333l-163.584-123.818667c-46.122667 28.458667-104.234667 43.776-170.026666 43.776" fill="#34A853" p-id="3512"></path><path d="M1005.397333 512c0-29.568-4.693333-61.44-11.64799999-91.008H516.650667V614.4h274.602666c-13.696 65.962667-51.072 116.650667-104.533333 149.632l163.541333 123.818667c93.994667-85.418667 155.136-212.650667 155.136-375.850667" fill="#4285F4" p-id="3513"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -1,40 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { NAvatar, NImage } from 'naive-ui'
|
||||
import { computed, withDefaults } from 'vue'
|
||||
import { computed, ref, withDefaults } from 'vue'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
|
||||
interface Prop {
|
||||
itemIcon?: Panel.ItemIcon | null
|
||||
size?: number // 默认70
|
||||
forceBackground?: string // 强制背景色
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Prop>(), { size: 70 })
|
||||
const defaultStyle = { width: `${props.size}px`, height: `${props.size}px` }
|
||||
const defaultBackground = '#2a2a2a6b'
|
||||
const defaultStyle = ref({
|
||||
width: `${props.size}px`,
|
||||
height: `${props.size}px`,
|
||||
})
|
||||
const iconExt = computed(() => {
|
||||
return props.itemIcon?.src?.split('.').pop()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="overflow-hidden rounded-2xl" :style="defaultStyle">
|
||||
<div :style="defaultStyle">
|
||||
<slot>
|
||||
<div v-if="itemIcon">
|
||||
<div v-if="itemIcon?.itemType === 1">
|
||||
<NAvatar :size="props.size" :style="{ backgroundColor: itemIcon?.bgColor }">
|
||||
<NAvatar :size="props.size" :style="{ backgroundColor: (forceBackground ?? itemIcon?.backgroundColor) || defaultBackground }">
|
||||
{{ itemIcon.text }}
|
||||
</NAvatar>
|
||||
</div>
|
||||
|
||||
<div v-else-if="itemIcon?.itemType === 2">
|
||||
<div v-if="iconExt === 'svg'" :style="defaultStyle" class="flex justify-center items-center">
|
||||
<div v-if="iconExt === 'svg'" :style="{ backgroundColor: (forceBackground ?? itemIcon?.backgroundColor) || defaultBackground, ...defaultStyle }" class="flex justify-center items-center">
|
||||
<img :src="itemIcon?.src" class="w-[35px] h-[35px]">
|
||||
<!-- <object :data="itemIcon?.src" type="image/svg+xml" class="w-[35px] h-[35px]" style="fill: rgb(255, 255, 255) !important;" /> -->
|
||||
</div>
|
||||
<NImage v-else :style="defaultStyle" :src="itemIcon?.src" preview-disabled />
|
||||
<NImage v-else :style="{ backgroundColor: (forceBackground ?? itemIcon?.backgroundColor) || defaultBackground, ...defaultStyle }" :src="itemIcon?.src" preview-disabled />
|
||||
</div>
|
||||
|
||||
<div v-else-if="itemIcon?.itemType === 3">
|
||||
<NAvatar :size="props.size" :style="{ backgroundColor: itemIcon?.bgColor }">
|
||||
<NAvatar :size="props.size" :style="{ backgroundColor: (forceBackground ?? itemIcon?.backgroundColor) || defaultBackground }">
|
||||
<SvgIcon style="font-size: 35px;" :icon="itemIcon.text" />
|
||||
</NAvatar>
|
||||
</div>
|
||||
|
@ -30,7 +30,7 @@ const showModal = computed({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal v-model:show="showModal" preset="card" :size="size" v-bind="bindAttrs" style="border-radius: 1rem;width: 600px;" :title="title">
|
||||
<NModal v-model:show="showModal" preset="card" :size="size" v-bind="bindAttrs" style="border-radius: 1rem;" :style="$parent" :title="title">
|
||||
<template #cover>
|
||||
<slot name="cover" />
|
||||
</template>
|
||||
|
@ -50,33 +50,6 @@ async function getAboutDescription() {
|
||||
<div>
|
||||
<span v-html="content" />
|
||||
</div>
|
||||
|
||||
<!-- <div class="p-2 space-y-2 rounded-md bg-neutral-100 dark:bg-neutral-700">
|
||||
<p>
|
||||
此项目开源于
|
||||
<a
|
||||
class="text-blue-600 dark:text-blue-500"
|
||||
href="https://github.com/Chanzhaoyu/chatgpt-web"
|
||||
target="_blank"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
,免费且基于 MIT 协议,没有任何形式的付费行为!
|
||||
</p>
|
||||
<p>
|
||||
如果你觉得此项目对你有帮助,请在 GitHub 帮我点个 Star 或者给予一点赞助,谢谢!
|
||||
</p>
|
||||
</div> -->
|
||||
<!-- <p>{{ $t("setting.api") }}:{{ config?.apiModel ?? '-' }}</p>
|
||||
<p v-if="isChatGPTAPI">
|
||||
{{ $t("setting.monthlyUsage") }}:{{ config?.usage ?? '-' }}
|
||||
</p>
|
||||
<p v-if="!isChatGPTAPI">
|
||||
{{ $t("setting.reverseProxy") }}:{{ config?.reverseProxy ?? '-' }}
|
||||
</p>
|
||||
<p>{{ $t("setting.timeout") }}:{{ config?.timeoutMs ?? '-' }}</p>
|
||||
<p>{{ $t("setting.socks") }}:{{ config?.socksProxy ?? '-' }}</p>
|
||||
<p>{{ $t("setting.httpsProxy") }}:{{ config?.httpsProxy ?? '-' }}</p> -->
|
||||
</div>
|
||||
</NSpin>
|
||||
</template>
|
||||
|
@ -1,23 +1,178 @@
|
||||
<script setup lang="ts">
|
||||
import { NInput } from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { NAvatar, NCheckbox } from 'naive-ui'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { useModuleConfig } from '@/store/modules'
|
||||
|
||||
import SvgSrcBaidu from '@/assets/search_engine_svg/baidu.svg'
|
||||
import SvgSrcBing from '@/assets/search_engine_svg/bing.svg'
|
||||
import SvgSrcGoogle from '@/assets/search_engine_svg/google.svg'
|
||||
|
||||
interface State {
|
||||
currentSearchEngine: DeskModule.SearchBox.SearchEngine
|
||||
searchEngineList: DeskModule.SearchBox.SearchEngine[]
|
||||
newWindowOpen: boolean
|
||||
}
|
||||
|
||||
withDefaults(defineProps<{
|
||||
background?: string
|
||||
textColor?: string
|
||||
}>(), {
|
||||
background: '#2a2a2a6b',
|
||||
textColor: 'white',
|
||||
})
|
||||
|
||||
const moduleConfigName = 'deskModuleSearchBox'
|
||||
const moduleConfig = useModuleConfig()
|
||||
const searchTerm = ref('')
|
||||
const isFocused = ref(false)
|
||||
const searchSelectListShow = ref(false)
|
||||
const defaultSearchEngineList = ref<DeskModule.SearchBox.SearchEngine[]>([
|
||||
{
|
||||
iconSrc: SvgSrcGoogle,
|
||||
title: 'Google',
|
||||
url: 'https://www.google.com/search?q=%s',
|
||||
},
|
||||
{
|
||||
iconSrc: SvgSrcBaidu,
|
||||
title: 'Baidu',
|
||||
url: 'https://www.baidu.com/s?wd=%s',
|
||||
},
|
||||
{
|
||||
iconSrc: SvgSrcBing,
|
||||
title: 'Bing',
|
||||
url: 'https://www.bing.com/search?q=%s',
|
||||
},
|
||||
])
|
||||
|
||||
const defaultState: State = {
|
||||
currentSearchEngine: defaultSearchEngineList.value[0],
|
||||
searchEngineList: [] || defaultSearchEngineList,
|
||||
newWindowOpen: false,
|
||||
}
|
||||
|
||||
const state = ref<State>({ ...defaultState })
|
||||
|
||||
const onFocus = (): void => {
|
||||
isFocused.value = true
|
||||
}
|
||||
|
||||
const onBlur = (): void => {
|
||||
isFocused.value = false
|
||||
}
|
||||
|
||||
function handleEngineClick() {
|
||||
searchSelectListShow.value = !searchSelectListShow.value
|
||||
}
|
||||
|
||||
function handleEngineUpdate(engine: DeskModule.SearchBox.SearchEngine) {
|
||||
state.value.currentSearchEngine = engine
|
||||
moduleConfig.saveToCloud(moduleConfigName, state.value)
|
||||
}
|
||||
|
||||
function handleSearchClick() {
|
||||
const url = state.value.currentSearchEngine.url
|
||||
const keyword = searchTerm
|
||||
// 如果网址中存在 %s,则直接替换为关键字
|
||||
const fullUrl = replaceOrAppendKeywordToUrl(url, keyword.value)
|
||||
|
||||
if (state.value.newWindowOpen)
|
||||
window.open(fullUrl)
|
||||
else
|
||||
window.location.href = fullUrl
|
||||
}
|
||||
|
||||
function replaceOrAppendKeywordToUrl(url: string, keyword: string) {
|
||||
// 如果网址中存在 %s,则直接替换为关键字
|
||||
if (url.includes('%s'))
|
||||
return url.replace('%s', encodeURIComponent(keyword))
|
||||
|
||||
// 如果网址中不存在 %s,则将关键字追加到末尾
|
||||
return url + (keyword ? `${encodeURIComponent(keyword)}` : '')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
moduleConfig.getValueByNameFromCloud<State>('deskModuleSearchBox').then(({ code, data }) => {
|
||||
if (code === 0)
|
||||
state.value = data || defaultState
|
||||
else
|
||||
state.value = defaultState
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<NInput size="large" class="background" round placeholder="输入搜索内容">
|
||||
<template #prefix>
|
||||
百度
|
||||
</template>
|
||||
<template #suffix>
|
||||
<div class="w-full" @keydown.enter="handleSearchClick">
|
||||
<div class="search-container flex rounded-2xl items-center justify-center text-white w-full" :style="{ background, color: textColor }" :class="{ focused: isFocused }">
|
||||
<div class="w-[40px] flex justify-center cursor-pointer" @click="handleEngineClick">
|
||||
<NAvatar :src="state.currentSearchEngine.iconSrc" style="background-color: transparent;" :size="20" />
|
||||
</div>
|
||||
|
||||
<input v-model="searchTerm" placeholder="请输入搜索内容" @focus="onFocus" @blur="onBlur">
|
||||
<div class="w-[20px] flex justify-center cursor-pointer" @click="handleSearchClick">
|
||||
<SvgIcon icon="iconamoon:search-fill" />
|
||||
</template>
|
||||
</NInput>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索引擎选择 -->
|
||||
<div v-if="searchSelectListShow" class="w-full mt-[10px] rounded-xl p-[10px]" :style="{ background }">
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
v-for="item, index in defaultSearchEngineList"
|
||||
:key="index"
|
||||
:title="item.title"
|
||||
class="w-[40px] h-[40px] mr-[10px] cursor-pointer bg-[#ffffff] flex items-center justify-center rounded-xl"
|
||||
@click="handleEngineUpdate(item)"
|
||||
>
|
||||
<NAvatar :src="item.iconSrc" style="background-color: transparent;" :size="20" />
|
||||
</div>
|
||||
<!-- <div class="w-[40px] h-[40px] ml-[10px] flex justify-center items-center cursor-pointer" @click="handleEngineClick">
|
||||
<NAvatar style="background-color: transparent;" :size="30">
|
||||
<SvgIcon icon="lets-icons:setting-alt-fill" style="font-size: 20px;" />
|
||||
</NAvatar>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-[10px]">
|
||||
<NCheckbox v-model:checked="state.newWindowOpen" @update-checked="moduleConfig.saveToCloud(moduleConfigName, state)">
|
||||
<span :style="{ color: textColor }">
|
||||
新窗口打开
|
||||
</span>
|
||||
</NCheckbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.background{
|
||||
background-color: #ffffff78;
|
||||
.search-container {
|
||||
border: 1px solid #ccc;
|
||||
transition: border-color 0.5s;
|
||||
padding: 2px 10px;
|
||||
}
|
||||
|
||||
.focused {
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
.before {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.after {
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: transparent;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 10px 5px;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 17px;
|
||||
}
|
||||
</style>
|
||||
|
1
src/enums/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './panel'
|
@ -6,6 +6,6 @@ export enum PanelStateNetworkModeEnum {
|
||||
}
|
||||
|
||||
export enum PanelPanelConfigStyleEnum {
|
||||
'icon' = 0, // 图标风格
|
||||
'info' = 1, // 详情风格
|
||||
'icon' = 1, // 图标风格
|
||||
'info' = 0, // 详情风格
|
||||
}
|
@ -7,16 +7,7 @@ const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
// component: () => import('@/views/home/Layout.vue'),
|
||||
component: () => import('@/views/home/index.vue'),
|
||||
// children: [
|
||||
|
||||
// {
|
||||
// path: '/edit/:noteId?',
|
||||
// name: 'EditNote',
|
||||
// component: () => import('@/views/home/index.vue'),
|
||||
// },
|
||||
// ],
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -5,3 +5,4 @@ export * from './auth'
|
||||
export * from './admin'
|
||||
export * from './notice'
|
||||
export * from './panel'
|
||||
export * from './moduleConfig'
|
||||
|
22
src/store/modules/moduleConfig/helper.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { ss } from '@/utils/storage'
|
||||
// import userDefaultAvatar from '@/assets/userDefaultAvatar.png'
|
||||
|
||||
const LOCAL_NAME = 'moduleConfig'
|
||||
|
||||
export interface Config {
|
||||
name: string
|
||||
config: any
|
||||
}
|
||||
|
||||
export interface ModuleConfigState {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export function getLocalState(): ModuleConfigState {
|
||||
const localSetting: ModuleConfigState | undefined = ss.get(LOCAL_NAME)
|
||||
return { ...localSetting }
|
||||
}
|
||||
|
||||
export function setLocalState(setting: ModuleConfigState): void {
|
||||
ss.set(LOCAL_NAME, setting)
|
||||
}
|
54
src/store/modules/moduleConfig/index.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { ModuleConfigState } from './helper'
|
||||
import { getLocalState, setLocalState } from './helper'
|
||||
import { getValueByName, save } from '@/api/system/moduleConfig'
|
||||
|
||||
export const useModuleConfig = defineStore('module-config-store', {
|
||||
state: (): ModuleConfigState => getLocalState(),
|
||||
actions: {
|
||||
|
||||
// 保存
|
||||
// save(name: string, value: any) {
|
||||
// const moduleName = `module-${name}`
|
||||
// // 保存至网络
|
||||
// console.log('保存模块配置', name, value)
|
||||
// this.$state[moduleName] = value
|
||||
// this.recordState()
|
||||
// save(moduleName, value)
|
||||
// },
|
||||
|
||||
// // 获取值
|
||||
// getValueByName<T>(name: string): T | null {
|
||||
// const moduleName = `module-${name}`
|
||||
// this.syncFromCloud(moduleName)
|
||||
// if (this.$state[moduleName])
|
||||
// return this.$state[moduleName]
|
||||
// return null
|
||||
// },
|
||||
|
||||
// 获取值
|
||||
async getValueByNameFromCloud<T>(name: string) {
|
||||
const moduleName = `module-${name}`
|
||||
return await getValueByName<T>(moduleName)
|
||||
},
|
||||
|
||||
// 保存到网络
|
||||
saveToCloud(name: string, value: any) {
|
||||
const moduleName = `module-${name}`
|
||||
// 保存至网络
|
||||
save(moduleName, value)
|
||||
},
|
||||
|
||||
// 从网络同步
|
||||
// syncFromCloud(moduleName: string) {
|
||||
// getValueByName<any>(moduleName).then(({ code, data, msg }) => {
|
||||
// if (code === 0)
|
||||
// this.$state[moduleName] = data
|
||||
// })
|
||||
// },
|
||||
|
||||
recordState() {
|
||||
setLocalState(this.$state)
|
||||
},
|
||||
},
|
||||
})
|
@ -1,5 +1,5 @@
|
||||
import { ss } from '@/utils/storage'
|
||||
import { PanelPanelConfigStyleEnum, PanelStateNetworkModeEnum } from '@/enum'
|
||||
import { PanelPanelConfigStyleEnum, PanelStateNetworkModeEnum } from '@/enums'
|
||||
import defaultBackground from '@/assets/defaultBackground.webp'
|
||||
const LOCAL_NAME = 'panelStorage'
|
||||
|
||||
@ -7,11 +7,15 @@ export function defaultStatePanelConfig(): Panel.panelConfig {
|
||||
return {
|
||||
backgroundImageSrc: defaultBackground,
|
||||
backgroundBlur: 0,
|
||||
backgroundMaskNumber: 0,
|
||||
iconStyle: PanelPanelConfigStyleEnum.icon,
|
||||
iconTextColor: '#ffffff',
|
||||
iconTextInfoHideDescription: false,
|
||||
iconTextIconHideTitle: false,
|
||||
logoText: 'Sun-Panel',
|
||||
logoImageSrc: '',
|
||||
clockShowSecond: false,
|
||||
searchBoxShow: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { defaultState, defaultStatePanelConfig, getLocalState, removeLocalState, setLocalState } from './helper'
|
||||
import { router } from '@/router'
|
||||
import type { PanelStateNetworkModeEnum } from '@/enum'
|
||||
import type { PanelStateNetworkModeEnum } from '@/enums'
|
||||
import { get as getUserConfig } from '@/api/panel/userConfig'
|
||||
export const usePanelState = defineStore('panel', {
|
||||
state: (): Panel.State => getLocalState() || defaultState(),
|
||||
|
5
src/typings/common.d.ts
vendored
@ -28,4 +28,9 @@ declare namespace Common {
|
||||
result?:boolean
|
||||
message?:string
|
||||
}
|
||||
|
||||
interface SortItemRequest{
|
||||
id:number
|
||||
sort:number
|
||||
}
|
||||
}
|
10
src/typings/deskModule/searchBox.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
declare namespace DeskModule.SearchBox {
|
||||
|
||||
interface SearchEngine {
|
||||
iconSrc: string
|
||||
title: string
|
||||
url: string
|
||||
}
|
||||
|
||||
}
|
||||
|
20
src/typings/panel.d.ts
vendored
@ -11,13 +11,21 @@ declare namespace Panel {
|
||||
lanUrl?: string
|
||||
description?: string
|
||||
openMethod: number
|
||||
itemIconGroupId ?:number
|
||||
}
|
||||
|
||||
interface ItemIconGroup extends Common.InfoBase {
|
||||
icon?: string
|
||||
title?: string
|
||||
sort?:number
|
||||
}
|
||||
|
||||
interface ItemIcon {
|
||||
itemType: number
|
||||
src ?: string
|
||||
text ?: string
|
||||
bgColor ?: string
|
||||
// bgColor ?: string
|
||||
backgroundColor ?: string
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -30,18 +38,26 @@ declare namespace Panel {
|
||||
interface panelConfig{
|
||||
backgroundImageSrc?:string
|
||||
backgroundBlur?:number
|
||||
backgroundMaskNumber?:number
|
||||
iconStyle?:PanelPanelConfigStyleEnum
|
||||
iconTextColor?:string
|
||||
iconTextInfoHideDescription?:boolean
|
||||
iconTextIconHideTitle?:boolean
|
||||
logoText?:string
|
||||
logoImageSrc?:string
|
||||
clockShowSecond?:boolean
|
||||
clockColor?:string
|
||||
|
||||
searchBoxShow?:boolean
|
||||
}
|
||||
|
||||
interface userConfig{
|
||||
panel:panelConfig
|
||||
searchEngine?:any
|
||||
}
|
||||
|
||||
interface ItemIconSortRequest{
|
||||
sortItems:Common.SortItemRequest[]
|
||||
itemIconGroupId:number
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,33 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import RecursiveList from './RecursiveList.vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { NColorPicker } from 'naive-ui'
|
||||
|
||||
const multiLevelData = [
|
||||
{
|
||||
name: '开发笔记',
|
||||
children: [
|
||||
{
|
||||
name: 'Level 2 Item 1',
|
||||
extand: true,
|
||||
children: [
|
||||
{ name: 'SAI-Chat开发' },
|
||||
{ name: '笔记本项目' },
|
||||
],
|
||||
},
|
||||
{ name: '测试项目' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '学习笔记',
|
||||
children: [
|
||||
{ name: 'Blender' },
|
||||
{ name: 'mongo' },
|
||||
],
|
||||
},
|
||||
]
|
||||
// 获取背景颜色的 RGB 值(假设这里获取到了背景颜色的值)
|
||||
const bgColor = ref('#000000') // 假设为蓝色
|
||||
|
||||
// 计算颜色的明暗度
|
||||
const calculateLuminance = (color: string) => {
|
||||
const hex = color.replace(/^#/, '')
|
||||
const r = parseInt(hex.substring(0, 2), 16)
|
||||
const g = parseInt(hex.substring(2, 4), 16)
|
||||
const b = parseInt(hex.substring(4, 6), 16)
|
||||
return (0.299 * r + 0.587 * g + 0.114 * b) / 255
|
||||
}
|
||||
|
||||
// 根据明暗度切换文本颜色
|
||||
const textColor = computed(() => {
|
||||
const luminance = calculateLuminance(bgColor.value)
|
||||
return luminance > 0.5 ? 'black' : 'white'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<RecursiveList :items="multiLevelData" />
|
||||
<div :style="{ backgroundColor: bgColor, color: textColor }">
|
||||
Background Color Example
|
||||
</div>
|
||||
<NColorPicker v-model:value="bgColor" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 样式可以根据实际需求自定义 */
|
||||
div {
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,50 +0,0 @@
|
||||
<script setup lang='ts'>
|
||||
import { NLayout, NLayoutContent, NLayoutSider } from 'naive-ui'
|
||||
import { computed } from 'vue'
|
||||
// import { Header } from './components'
|
||||
// import { LeftSider, RightSider } from './layout'
|
||||
import Index from './index.vue'
|
||||
import { usePanelState } from '@/store/modules'
|
||||
|
||||
const panelState = usePanelState()
|
||||
|
||||
const leftSiderCollapsed = computed(() => panelState.leftSiderCollapsed)
|
||||
const rightSiderCollapsed = computed(() => panelState.rightSiderCollapsed)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<NLayout has-sider class="h-full">
|
||||
<NLayoutSider
|
||||
v-model:collapsed="leftSiderCollapsed"
|
||||
collapse-mode="transform"
|
||||
:collapsed-width="0"
|
||||
:width="240"
|
||||
bordered
|
||||
>
|
||||
<LeftSider />
|
||||
</NLayoutSider>
|
||||
<NLayoutContent>
|
||||
<NLayout has-sider sider-placement="right" class="h-full">
|
||||
<NLayoutContent class="h-full">
|
||||
<!-- 内容 -->
|
||||
<!-- <Header /> -->
|
||||
<div>
|
||||
<Index />
|
||||
</div>
|
||||
</NLayoutContent>
|
||||
<NLayoutSider
|
||||
v-model:collapsed="rightSiderCollapsed"
|
||||
collapse-mode="transform"
|
||||
:collapsed-width="0"
|
||||
:width="280"
|
||||
content-style="padding: 20px;"
|
||||
bordered
|
||||
>
|
||||
<RightSider />
|
||||
</NLayoutSider>
|
||||
</NLayout>
|
||||
</NLayoutContent>
|
||||
</NLayout>
|
||||
</div>
|
||||
</template>
|
0
src/views/home/applist/index.vue
Normal file
87
src/views/home/components/AppIcon/index.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { NEllipsis } from 'naive-ui'
|
||||
import { ItemIcon } from '@/components/common'
|
||||
import { PanelPanelConfigStyleEnum } from '@/enums'
|
||||
|
||||
interface Prop {
|
||||
itemInfo?: Panel.ItemInfo
|
||||
size?: number // 默认70
|
||||
forceBackground?: string // 强制背景色
|
||||
iconTextColor?: string
|
||||
iconTextInfoHideDescription: boolean
|
||||
iconTextIconHideTitle: boolean
|
||||
style: PanelPanelConfigStyleEnum
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Prop>(), {
|
||||
size: 70,
|
||||
})
|
||||
|
||||
const defaultBackground = '#2a2a2a6b'
|
||||
|
||||
const calculateLuminance = (color: string) => {
|
||||
const hex = color.replace(/^#/, '')
|
||||
const r = parseInt(hex.substring(0, 2), 16)
|
||||
const g = parseInt(hex.substring(2, 4), 16)
|
||||
const b = parseInt(hex.substring(4, 6), 16)
|
||||
return (0.299 * r + 0.587 * g + 0.114 * b) / 255
|
||||
}
|
||||
|
||||
const textColor = computed(() => {
|
||||
const luminance = calculateLuminance(props.itemInfo?.icon?.backgroundColor || defaultBackground)
|
||||
return luminance > 0.5 ? 'black' : 'white'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<!-- 详情图标 -->
|
||||
<div
|
||||
v-if="style === PanelPanelConfigStyleEnum.info"
|
||||
class="w-full rounded-2xl transition-all duration-200 hover:shadow-[0_0_20px_10px_rgba(0,0,0,0.2)] flex"
|
||||
:style="{ background: itemInfo?.icon?.backgroundColor || defaultBackground }"
|
||||
>
|
||||
<!-- 图标 -->
|
||||
<div class="w-[70px] h-[70px]">
|
||||
<div class="w-[70px] h-full flex items-center justify-center ">
|
||||
<ItemIcon :item-icon="itemInfo?.icon" force-background="transparent" :size="50" class="overflow-hidden rounded-xl" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文字 -->
|
||||
<!-- 如果为纯白色,将自动根据背景的明暗计算字体的黑白色 -->
|
||||
<div class="text-white flex items-center" :style="{ color: (iconTextColor === '#ffffff') ? textColor : iconTextColor, maxWidth: 'calc(100% - 80px)' }">
|
||||
<div class="w-full">
|
||||
<div class="font-semibold w-full">
|
||||
<NEllipsis>
|
||||
{{ itemInfo?.title }}
|
||||
</NEllipsis>
|
||||
</div>
|
||||
<div v-if="!iconTextInfoHideDescription">
|
||||
<NEllipsis :line-clamp="2" class="text-xs">
|
||||
{{ itemInfo?.description }}
|
||||
</NEllipsis>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 极简图标(APP) -->
|
||||
<div v-if="style === PanelPanelConfigStyleEnum.icon">
|
||||
<div
|
||||
class="overflow-hidden rounded-2xl sunpanel w-[70px] h-[70px] mx-auto rounded-2xl transition-all duration-200 hover:shadow-[0_0_20px_10px_rgba(0,0,0,0.2)]"
|
||||
:title="itemInfo?.description"
|
||||
>
|
||||
<ItemIcon :item-icon="itemInfo?.icon" />
|
||||
</div>
|
||||
<div
|
||||
v-if="!iconTextIconHideTitle"
|
||||
class="text-center app-icon-text-shadow cursor-pointer mt-[2px]"
|
||||
:style="{ color: iconTextColor }"
|
||||
>
|
||||
<span>{{ itemInfo?.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NColorPicker, NInput, NRadio, NUpload, useMessage } from 'naive-ui'
|
||||
import type { UploadFileInfo } from 'naive-ui'
|
||||
import { defineProps, ref } from 'vue'
|
||||
import { defineProps, ref, watch } from 'vue'
|
||||
import { ItemIcon } from '@/components/common'
|
||||
import { useAuthStore } from '@/store'
|
||||
|
||||
@ -17,19 +17,27 @@ const checkedValueRef = ref<number | null>(props.itemIcon?.itemType || 1)
|
||||
|
||||
// 默认图标背景色
|
||||
const defautSwatchesBackground = [
|
||||
'#000',
|
||||
'#00000000',
|
||||
'#000000',
|
||||
'#ffffff',
|
||||
'#18A058',
|
||||
'#2080F0',
|
||||
'#F0A020',
|
||||
'rgba(208, 48, 80, 1)',
|
||||
'#C418D1FF',
|
||||
]
|
||||
|
||||
const initData: Panel.ItemIcon = {
|
||||
itemType: 1,
|
||||
bgColor: '#000',
|
||||
backgroundColor: '#2a2a2a6b',
|
||||
}
|
||||
|
||||
const itemIconInfo = ref<Panel.ItemIcon>(props.itemIcon ? { ...props.itemIcon } : { ...initData })
|
||||
// const itemIconInfo = ref<Panel.ItemIcon>(props.itemIcon ?? { ...initData })
|
||||
const itemIconInfo = ref<Panel.ItemIcon>({
|
||||
...initData,
|
||||
...props.itemIcon,
|
||||
backgroundColor: props.itemIcon?.backgroundColor || initData.backgroundColor,
|
||||
})
|
||||
|
||||
function handleIconTypeRadioChange(type: number) {
|
||||
checkedValueRef.value = type
|
||||
@ -38,7 +46,6 @@ function handleIconTypeRadioChange(type: number) {
|
||||
}
|
||||
|
||||
function handleChange() {
|
||||
// console.log('值', itemIconInfo.value)
|
||||
emit('update:itemIcon', itemIconInfo.value || null)
|
||||
}
|
||||
|
||||
@ -61,6 +68,10 @@ const handleUploadFinish = ({
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
watch(itemIconInfo.value, () => {
|
||||
handleChange()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -94,19 +105,57 @@ const handleUploadFinish = ({
|
||||
</NRadio>
|
||||
</div>
|
||||
|
||||
<div class="flex h-[100px]">
|
||||
<div>
|
||||
<div class="border rounded-2xl bg-slate-200">
|
||||
<ItemIcon :item-icon="itemIconInfo" />
|
||||
<div class=" h-[100px]">
|
||||
<div class="flex">
|
||||
<div>
|
||||
<div class="border rounded-2xl bg-slate-200 overflow-hidden rounded-2xl transparent-grid">
|
||||
<ItemIcon :item-icon="itemIconInfo" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 文字 -->
|
||||
<div class="ml-[20px]">
|
||||
<!-- <NImage :src="model.icon" preview-disabled /> -->
|
||||
<div v-if="checkedValueRef === 1">
|
||||
<NInput v-model:value="itemIconInfo.text" class="mb-[5px]" size="small" type="text" placeholder="请输入文字作为图标" @input="handleChange" />
|
||||
</div>
|
||||
|
||||
<div v-if="checkedValueRef === 3">
|
||||
<div>
|
||||
<NInput v-model:value="itemIconInfo.text" class="mb-[5px]" size="small" type="text" placeholder="请输入图标名字" @input="handleChange" />
|
||||
|
||||
<NButton quaternary type="info">
|
||||
<a target="_blank" href="https://icon-sets.iconify.design/">图标库</a>
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片 -->
|
||||
<div v-if="checkedValueRef === 2">
|
||||
<NInput v-model:value="itemIconInfo.src" class="mb-[5px] w-full" size="small" type="text" placeholder="输入图标地址或上传" @input="handleChange" />
|
||||
<NUpload
|
||||
action="/api/file/uploadImg"
|
||||
:show-file-list="false"
|
||||
name="imgfile"
|
||||
:headers="{
|
||||
token: authStore.token as string,
|
||||
}"
|
||||
@finish="handleUploadFinish"
|
||||
>
|
||||
<NButton size="small">
|
||||
点击上传
|
||||
</NButton>
|
||||
</NUpload>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 文字 -->
|
||||
<div class="ml-[20px]">
|
||||
<!-- <NImage :src="model.icon" preview-disabled /> -->
|
||||
<div v-if="checkedValueRef === 1">
|
||||
<NInput v-model:value="itemIconInfo.text" class="mb-[5px]" size="small" type="text" placeholder="请输入文字作为图标" @input="handleChange" />
|
||||
|
||||
<div class="flex items-center mt-[10px]">
|
||||
<div class="w-auto text-slate-500 mr-[10px]">
|
||||
背景色:
|
||||
</div>
|
||||
<div class="w-[150px] flex items-center mr-[10px]">
|
||||
<NColorPicker
|
||||
v-model:value="itemIconInfo.bgColor"
|
||||
v-model:value="itemIconInfo.backgroundColor"
|
||||
size="small"
|
||||
:modes="['hex']"
|
||||
:swatches="defautSwatchesBackground"
|
||||
@ -114,39 +163,21 @@ const handleUploadFinish = ({
|
||||
@update-value="handleChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="checkedValueRef === 3">
|
||||
<div>
|
||||
<NInput v-model:value="itemIconInfo.text" class="mb-[5px]" size="small" type="text" placeholder="请输入图标名字" @input="handleChange" />
|
||||
<a target="_blank" href="https://icon-sets.iconify.design/" class="text-[blue]">图标库</a>
|
||||
</div>
|
||||
<NColorPicker
|
||||
v-model:value="itemIconInfo.bgColor"
|
||||
size="small"
|
||||
:modes="['hex']"
|
||||
:swatches="defautSwatchesBackground"
|
||||
@complete="handleChange"
|
||||
@update-value="handleChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 图片 -->
|
||||
<div v-if="checkedValueRef === 2">
|
||||
<NUpload
|
||||
action="/api/file/uploadImg"
|
||||
:show-file-list="false"
|
||||
name="imgfile"
|
||||
:headers="{
|
||||
token: authStore.token as string,
|
||||
}"
|
||||
@finish="handleUploadFinish"
|
||||
>
|
||||
<NButton size="small">
|
||||
点击上传
|
||||
</NButton>
|
||||
</NUpload>
|
||||
<div v-if="itemIconInfo.backgroundColor !== initData.backgroundColor" class="w-auto text-slate-500 mr-[10px] cursor-pointer">
|
||||
<NButton quaternary type="info" @click="itemIconInfo.backgroundColor = initData.backgroundColor">
|
||||
恢复默认
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.transparent-grid {
|
||||
background-image: linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%),
|
||||
linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%);
|
||||
background-size: 16px 16px;
|
||||
background-position: 0 0, 8px 8px;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineEmits, defineProps, onMounted, ref, watch } from 'vue'
|
||||
import type { FormInst, FormRules } from 'naive-ui'
|
||||
import { NButton, NForm, NFormItem, NInput, NModal, NSelect, useMessage } from 'naive-ui'
|
||||
import { NButton, NForm, NFormItem, NGrid, NGridItem, NInput, NModal, NSelect, useMessage } from 'naive-ui'
|
||||
import IconEditor from './IconEditor.vue'
|
||||
import { edit } from '@/api/panel/itemIcon'
|
||||
import { getList as getGroupList } from '@/api/panel/itemIconGroup'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
@ -13,6 +14,10 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emit>()
|
||||
const ms = useMessage()
|
||||
const itemIconGroupOptions = ref<{
|
||||
label: string
|
||||
value: number
|
||||
}[]>([])
|
||||
|
||||
const restoreDefault: Panel.Info = {
|
||||
icon: null,
|
||||
@ -29,6 +34,9 @@ interface Emit {
|
||||
}
|
||||
|
||||
const model = ref<Panel.Info>(props.itemInfo !== null ? { ...props.itemInfo } : { ...restoreDefault })
|
||||
// const model = computed(()=>{
|
||||
// return props.itemInfo !== null ? { ...props.itemInfo } : { ...restoreDefault }
|
||||
// })
|
||||
const formRef = ref<FormInst | null>(null)
|
||||
|
||||
const rules: FormRules = {
|
||||
@ -43,6 +51,11 @@ const rules: FormRules = {
|
||||
type: 'string',
|
||||
message: '必填项',
|
||||
},
|
||||
// itemIconGroupId: {
|
||||
// required: true,
|
||||
// trigger: ['blur', 'change'],
|
||||
// message: '必填项',
|
||||
// },
|
||||
}
|
||||
|
||||
const options = [
|
||||
@ -61,6 +74,22 @@ const options = [
|
||||
},
|
||||
]
|
||||
|
||||
// const urlProtocolOptions = [
|
||||
// {
|
||||
// default: true,
|
||||
// label: 'http://',
|
||||
// value: 'http://',
|
||||
// },
|
||||
// {
|
||||
// label: 'https://',
|
||||
// value: 'https://',
|
||||
// },
|
||||
// {
|
||||
// label: '不使用',
|
||||
// value: '',
|
||||
// },
|
||||
// ]
|
||||
|
||||
// 更新值父组件传来的值
|
||||
const show = computed({
|
||||
get: () => props.visible,
|
||||
@ -70,15 +99,15 @@ const show = computed({
|
||||
})
|
||||
|
||||
async function editApi() {
|
||||
const { code, data } = await edit<Panel.ItemInfo>(model.value)
|
||||
const { code, data, msg } = await edit<Panel.ItemInfo>(model.value)
|
||||
if (code === 0) {
|
||||
show.value = false
|
||||
model.value = restoreDefault
|
||||
model.value = { ...restoreDefault }
|
||||
|
||||
emit('done', data)
|
||||
}
|
||||
else {
|
||||
ms.error('保存失败')
|
||||
ms.error(`保存失败:${msg}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,39 +120,77 @@ const handleValidateButtonClick = (e: MouseEvent) => {
|
||||
}
|
||||
|
||||
watch(() => props.itemInfo, (newValue) => {
|
||||
model.value = newValue || restoreDefault
|
||||
model.value = newValue || { ...restoreDefault }
|
||||
getGroupListOptions()
|
||||
})
|
||||
|
||||
function getGroupListOptions() {
|
||||
getGroupList<Common.ListResponse<Panel.ItemIconGroup[]>>().then(({ data, code, msg }) => {
|
||||
if (code === 0) {
|
||||
itemIconGroupOptions.value = []
|
||||
|
||||
for (let i = 0; i < data.list.length; i++) {
|
||||
const element = data.list[i]
|
||||
if (i === 0 && !model.value.itemIconGroupId) {
|
||||
restoreDefault.itemIconGroupId = element.id
|
||||
model.value.itemIconGroupId = element.id
|
||||
}
|
||||
|
||||
itemIconGroupOptions.value.push({
|
||||
value: element.id as number,
|
||||
label: element.title as string,
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
ms.error(`分组信息获取失败:${msg}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// rolesLoading.value = true
|
||||
getGroupListOptions()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal v-model:show="show" preset="card" style="width: 600px;border-radius: 1rem;" :title="itemInfo ? '修改项目' : '添加项目'">
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem path="title" label="标题">
|
||||
<NInput v-model:value="model.title" type="text" show-count :maxlength="20" placeholder="请输入标题" />
|
||||
</NFormItem>
|
||||
<NFormItem path="icon" label="图标">
|
||||
<IconEditor v-model:item-icon="model.icon" />
|
||||
</NFormItem>
|
||||
<NFormItem path="url" label="跳转地址">
|
||||
<NInput v-model:value="model.url" type="text" :maxlength="1000" placeholder="请输入跳转地址" />
|
||||
</NFormItem>
|
||||
<NFormItem path="lanUrl" label="局域网跳转地址">
|
||||
<NInput v-model:value="model.lanUrl" type="text" :maxlength="1000" placeholder="(可以留空)切换到局域网模式,点击会使用该地址" />
|
||||
</NFormItem>
|
||||
<NFormItem path="description" label="描述信息">
|
||||
<NInput v-model:value="model.description" type="text" show-count :maxlength="100" placeholder="请填写描述信息" />
|
||||
</NFormItem>
|
||||
<NFormItem path="openMethod" label="打开方式">
|
||||
<NSelect v-model:value="model.openMethod" :options="options" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<div class="h-[600px] overflow-auto p-[5px]">
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NGrid cols="2" :x-gap="10" item-responsive>
|
||||
<NGridItem span="2 500:1">
|
||||
<NFormItem path="itemIconGroupId" label="分组">
|
||||
<NSelect v-model:value="model.itemIconGroupId" :options="itemIconGroupOptions" />
|
||||
</NFormItem>
|
||||
</NGridItem>
|
||||
<NGridItem span="2 500:1">
|
||||
<NFormItem path="title" label="标题">
|
||||
<NInput v-model:value="model.title" type="text" show-count :maxlength="20" placeholder="请输入标题" />
|
||||
</NFormItem>
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
|
||||
<NFormItem path="icon" label="图标">
|
||||
<IconEditor v-model:item-icon="model.icon" />
|
||||
</NFormItem>
|
||||
<NFormItem path="url" label="跳转地址">
|
||||
<!-- <NSelect :style="{ width: '100px' }" :options="urlProtocolOptions" /> -->
|
||||
<NInput v-model:value="model.url" type="text" :maxlength="1000" placeholder="http(s)://" />
|
||||
</NFormItem>
|
||||
<NFormItem path="lanUrl" label="局域网跳转地址">
|
||||
<NInput v-model:value="model.lanUrl" type="text" :maxlength="1000" placeholder="http(s)://(可以留空,切换到局域网模式,点击会使用该地址)" />
|
||||
</NFormItem>
|
||||
<NFormItem path="description" label="描述信息">
|
||||
<NInput v-model:value="model.description" type="text" show-count :maxlength="100" placeholder="请填写描述信息" />
|
||||
</NFormItem>
|
||||
<NFormItem path="openMethod" label="打开方式">
|
||||
<NSelect v-model:value="model.openMethod" :options="options" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<NButton type="success" @click="handleValidateButtonClick">
|
||||
<NButton type="success" style="float: right;" @click="handleValidateButtonClick">
|
||||
确定
|
||||
</NButton>
|
||||
</template>
|
||||
|
@ -5,6 +5,8 @@ import Style from './tabs/Style.vue'
|
||||
import About from './tabs/About.vue'
|
||||
import Users from './tabs/Users.vue'
|
||||
import UserInfo from './tabs/UserInfo.vue'
|
||||
import ItemGroupManage from './tabs/ItemGroupManage.vue'
|
||||
|
||||
import { RoundCardModal } from '@/components/common'
|
||||
|
||||
const props = defineProps<{
|
||||
@ -25,11 +27,14 @@ const show = computed({
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<RoundCardModal v-model:show="show" title="设置" style="max-height: 700px;">
|
||||
<NTabs type="line" animated>
|
||||
<RoundCardModal v-model:show="show" title="设置" style="max-height: 700px;max-width: 600px;">
|
||||
<NTabs type="line" size="small" animated>
|
||||
<NTabPane name="style" tab="样式">
|
||||
<Style />
|
||||
</NTabPane>
|
||||
<NTabPane name="itemGroupManage" tab="分组管理">
|
||||
<ItemGroupManage />
|
||||
</NTabPane>
|
||||
<NTabPane name="userInfo" tab="登录信息">
|
||||
<UserInfo />
|
||||
</NTabPane>
|
||||
@ -45,7 +50,7 @@ const show = computed({
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.text-shadow{
|
||||
.text-shadow {
|
||||
text-shadow: 0px 0px 5px gray;
|
||||
}
|
||||
</style>
|
||||
|
@ -2,6 +2,10 @@
|
||||
import { NDivider, NGradientText } from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { get } from '@/api/system/about'
|
||||
import srcSvglogo from '@/assets/logo.svg'
|
||||
import srcGitee from '@/assets/about_image/gitee.png'
|
||||
import srcGithub from '@/assets/about_image/github.png'
|
||||
import srcDocker from '@/assets/about_image/docker.png'
|
||||
|
||||
interface Version {
|
||||
versionName: string
|
||||
@ -20,26 +24,53 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="text-3xl">
|
||||
<NGradientText type="danger">
|
||||
Sun-Panel
|
||||
</NGradientText>
|
||||
v{{ versionName }}
|
||||
<div>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<img :src="srcSvglogo" width="100" height="100" alt="">
|
||||
<div class="text-3xl font-semibold">
|
||||
{{ $t('common.appName') }}
|
||||
</div>
|
||||
<div class="text-xl">
|
||||
<NGradientText type="info">
|
||||
<a href="https://github.com/hslr-s/sun-panel/releases" class="font-semibold" title="点此查看更新说明" target="_blank">v{{ versionName }}</a>
|
||||
</NGradientText>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NDivider />
|
||||
<div class="text-lg">
|
||||
开发者: <a href="https://blog.enianteam.com/u/sun/content/11" target="_blank" class="link">红烧猎人</a>
|
||||
</div>
|
||||
<div class="text-lg">
|
||||
项目开源地址:
|
||||
<a href="https://github.com/hslr-s/sun-panel" target="_blank" class="link">Github</a> |
|
||||
<a href="https://gitee.com/hslr/sun-panel" target="_blank" class="link">Gitee</a>
|
||||
<div class="flex flex-col items-center justify-center text-base">
|
||||
<div>
|
||||
建议反馈:<a href="https://github.com/hslr-s/sun-panel/issues" target="_blank" class="link">Github Issues</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
QQ交流群:<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=_I9WIoJn1roIdoaAqelSj9qClLKlXIa1&authKey=GfsQP2GagHnus0jMc7U8Sm6VhWjtsipXUzCHbFwQsGyHMgmYWx6ZbAP%2Bhut%2B4D6N&noverify=0&group_code=276594668" target="_blank" class="link">276594668</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
开发者:<a href="https://blog.enianteam.com/u/sun/content/11" target="_blank" class="link">红烧猎人</a> | <a href="https://github.com/hslr-s/sun-panel/blob/master/doc/donate.md" target="_blank" class="text-red-600 hover:text-red-900">🧧打赏</a>
|
||||
</div>
|
||||
|
||||
<div class="flex mt-[10px]">
|
||||
<div class="flex items-center mx-[10px]">
|
||||
<img class="w-[20px] h-[20px] mr-[5px]" :src="srcGithub" alt="">
|
||||
<a href="https://github.com/hslr-s/sun-panel" target="_blank" class="link">Github</a>
|
||||
</div>
|
||||
<div class="flex items-center mx-[10px]">
|
||||
<img class="w-[20px] h-[20px] mr-[5px]" :src="srcGitee" alt="">
|
||||
<a href="https://gitee.com/hslr/sun-panel" target="_blank" class="link">Gitee</a>
|
||||
</div>
|
||||
<div class="flex items-center mx-[10px]">
|
||||
<img class="w-[20px] h-[20px] mr-[5px]" :src="srcDocker" alt="">
|
||||
<a href="https://hub.docker.com/r/hslr/sun-panel" target="_blank" class="link">Docker</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.link{
|
||||
color:blue
|
||||
color:rgb(0, 89, 255)
|
||||
}
|
||||
</style>
|
||||
|
@ -56,6 +56,12 @@ const rules: FormRules = {
|
||||
type: 'number',
|
||||
message: '请选择账号状态',
|
||||
},
|
||||
password: {
|
||||
trigger: 'blur',
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: '6-20个字符',
|
||||
},
|
||||
}
|
||||
|
||||
// 更新值父组件传来的值
|
||||
@ -106,7 +112,7 @@ const handleValidateButtonClick = (e: MouseEvent) => {
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem path="password" label="密码">
|
||||
<NInput v-model:value="model.password" type="text" :placeholder="`${userInfo?.id ? '请输入新密码,留空密码不变' : '请输入密码'}`" />
|
||||
<NInput v-model:value="model.password" :maxlength="20" type="password" :placeholder="`${userInfo?.id ? '请输入新密码,留空密码不变' : '请输入密码'}`" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
|
||||
|
198
src/views/home/components/Setting/tabs/ItemGroupManage.vue
Normal file
@ -0,0 +1,198 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import type { FormInst, FormRules } from 'naive-ui'
|
||||
import { NButton, NCard, NForm, NFormItem, NInput, useDialog, useMessage } from 'naive-ui'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
import { deletes, edit, getList, saveSort } from '@/api/panel/itemIconGroup'
|
||||
import { RoundCardModal, SvgIcon } from '@/components/common'
|
||||
|
||||
interface EditModalArg {
|
||||
show: boolean
|
||||
editStatus: number // 1.添加 2.编辑
|
||||
model: Panel.ItemIconGroup
|
||||
rules: FormRules
|
||||
}
|
||||
|
||||
const formRef = ref<FormInst | null>(null)
|
||||
const ms = useMessage()
|
||||
const dialog = useDialog()
|
||||
const sortStatus = ref(false)
|
||||
|
||||
const defaultMNodal = {
|
||||
title: '',
|
||||
icon: 'material-symbols:folder-outline',
|
||||
sort: 9999,
|
||||
}
|
||||
|
||||
const editModalArg = ref<EditModalArg>({
|
||||
show: false,
|
||||
editStatus: 1,
|
||||
model: defaultMNodal,
|
||||
rules: {
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const groups = ref<Panel.ItemIconGroup[]>([])
|
||||
|
||||
function handleAddGroup() {
|
||||
editModalArg.value.show = !editModalArg.value.show
|
||||
}
|
||||
|
||||
function handleEditGroup(groupInfo: Panel.ItemIconGroup) {
|
||||
editModalArg.value.show = true
|
||||
editModalArg.value.model = groupInfo
|
||||
}
|
||||
|
||||
function handleDragSort() {
|
||||
sortStatus.value = true
|
||||
}
|
||||
|
||||
function handleSaveSort() {
|
||||
const saveItems: Common.SortItemRequest[] = []
|
||||
for (let i = 0; i < groups.value.length; i++) {
|
||||
const element = groups.value[i]
|
||||
saveItems.push({
|
||||
id: element.id as number,
|
||||
sort: i + 1,
|
||||
})
|
||||
}
|
||||
saveSort(saveItems).then(({ code, msg }) => {
|
||||
if (code === 0) {
|
||||
ms.success('保存成功')
|
||||
sortStatus.value = false
|
||||
}
|
||||
else {
|
||||
ms.error(`保存失败:${msg}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleDelete(groupInfo: Panel.ItemIconGroup) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: `你确定删除此分组[ ${groupInfo.title} ],删除后此分组应用图标将丢失?`,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
if (groupInfo.id) {
|
||||
deletes([groupInfo.id]).then(({ code, msg }) => {
|
||||
if (code !== 0)
|
||||
ms.error(`删除失败:${msg}`)
|
||||
else
|
||||
refreshList()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function handleSaveGroup() {
|
||||
formRef.value?.validate((errors) => {
|
||||
if (!errors) {
|
||||
edit(editModalArg.value.model).then(({ code, msg }) => {
|
||||
if (code !== 0)
|
||||
ms.error(msg)
|
||||
|
||||
refreshList()
|
||||
editModalArg.value.show = false
|
||||
editModalArg.value.model = { ...defaultMNodal }
|
||||
})
|
||||
}
|
||||
else { console.log(errors) }
|
||||
})
|
||||
}
|
||||
|
||||
function refreshList() {
|
||||
getList<Common.ListResponse<Panel.ItemIconGroup[]>>().then(({ code, data }) => {
|
||||
groups.value = data.list
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refreshList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[500px]">
|
||||
<div>
|
||||
<NButton type="success" size="small" style="margin-right: 10px;" @click="handleAddGroup">
|
||||
新增分组
|
||||
</NButton>
|
||||
|
||||
<NButton v-if="!sortStatus" size="small" @click="handleDragSort">
|
||||
排序
|
||||
</NButton>
|
||||
|
||||
<NButton v-else type="warning" size="small" @click="handleSaveSort">
|
||||
保存排序
|
||||
</NButton>
|
||||
</div>
|
||||
|
||||
<div class=" overflow-auto w-full mt-[20px] bg-slate-200 rounded-xl" style="height:calc(100% - 50px)">
|
||||
<VueDraggable
|
||||
v-model="groups"
|
||||
item-key="sort" :animation="300"
|
||||
:style="{ padding: sortStatus ? '20px' : '10px' }"
|
||||
:disabled="!sortStatus"
|
||||
>
|
||||
<div v-for="(item, index) in groups" :key="index" class="w-full">
|
||||
<NCard size="small" style="border-radius:10px;margin-bottom: 10px;">
|
||||
<div class="flex" :class="sortStatus ? 'cursor-move' : ''">
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[10px]">
|
||||
<SvgIcon class="text-[20px]" icon="material-symbols:ad-group-outline" />
|
||||
<!-- <SvgIcon class="text-[20px]" :icon="item.icon" /> -->
|
||||
</span>
|
||||
<span>
|
||||
{{ item.title }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<span>
|
||||
<NButton strong secondary type="success" size="small" @click="handleEditGroup(item)">
|
||||
<template #icon>
|
||||
<SvgIcon icon="basil:edit-solid" />
|
||||
</template>
|
||||
</NButton>
|
||||
</span>
|
||||
<span class="ml-[10px]">
|
||||
<NButton strong secondary type="error" size="small" class="ml-[10px]" @click="handleDelete(item)">
|
||||
<template #icon>
|
||||
<SvgIcon icon="material-symbols:delete" />
|
||||
</template>
|
||||
</NButton>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</NCard>
|
||||
</div>
|
||||
</VueDraggable>
|
||||
</div>
|
||||
|
||||
<RoundCardModal v-model:show="editModalArg.show" type="small" :title="editModalArg.editStatus === 1 ? '添加' : '编辑'" style="width: 400px;">
|
||||
<NForm ref="formRef" :model="editModalArg.model" :rules="editModalArg.rules">
|
||||
<NFormItem path="title" label="分组名称">
|
||||
<NInput v-model:value="editModalArg.model.title" type="text" :maxlength="20" show-count placeholder="请输入" />
|
||||
</NFormItem>
|
||||
|
||||
<!-- <NFormItem path="name" label="昵称">
|
||||
<NInput v-model:value="editModalArg.model" type="text" placeholder="请输入昵称" />
|
||||
</NFormItem> -->
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NButton type="success" size="small" class="float-right" @click="handleSaveGroup">
|
||||
确定
|
||||
</NButton>
|
||||
</template>
|
||||
</RoundCardModal>
|
||||
</div>
|
||||
</template>
|
@ -4,6 +4,7 @@ import type { UploadFileInfo } from 'naive-ui'
|
||||
import { NButton, NCard, NColorPicker, NInput, NPopconfirm, NSelect, NSlider, NSwitch, NUpload, NUploadDragger, useMessage } from 'naive-ui'
|
||||
import { useAuthStore, usePanelState } from '@/store'
|
||||
import { set as setUserConfig } from '@/api/panel/userConfig'
|
||||
import { PanelPanelConfigStyleEnum } from '@/enums/panel'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const panelState = usePanelState()
|
||||
@ -14,11 +15,11 @@ const isSaveing = ref(false)
|
||||
const iconTypeOptions = [
|
||||
{
|
||||
label: '详情图标',
|
||||
value: 0,
|
||||
value: PanelPanelConfigStyleEnum.info,
|
||||
},
|
||||
{
|
||||
label: '小图标',
|
||||
value: 1,
|
||||
value: PanelPanelConfigStyleEnum.icon,
|
||||
},
|
||||
]
|
||||
|
||||
@ -85,6 +86,68 @@ function resetPanelConfig() {
|
||||
</div>
|
||||
</NCard>
|
||||
|
||||
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
|
||||
<div class="text-slate-500 mb-[5px]">
|
||||
搜索框
|
||||
</div>
|
||||
<div class="flex items-center mt-[5px]">
|
||||
<span class="mr-[10px]">显示</span>
|
||||
<NSwitch v-model:value="panelState.panelConfig.searchBoxShow" />
|
||||
</div>
|
||||
</NCard>
|
||||
|
||||
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
|
||||
<div class="text-slate-500 mb-[5px]">
|
||||
图标
|
||||
</div>
|
||||
<div class="mt-[5px]">
|
||||
<div>
|
||||
样式
|
||||
</div>
|
||||
<div class="flex items-center mt-[5px]">
|
||||
<NSelect v-model:value="panelState.panelConfig.iconStyle" :options="iconTypeOptions" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="panelState.panelConfig.iconStyle === PanelPanelConfigStyleEnum.info" class="mt-[5px]">
|
||||
<div>
|
||||
隐藏描述信息
|
||||
</div>
|
||||
<div class="flex items-center mt-[5px]">
|
||||
<NSwitch v-model:value="panelState.panelConfig.iconTextInfoHideDescription" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="panelState.panelConfig.iconStyle === PanelPanelConfigStyleEnum.icon" class="mt-[5px]">
|
||||
<div>
|
||||
隐藏标题
|
||||
</div>
|
||||
<div class="flex items-center mt-[5px]">
|
||||
<NSwitch v-model:value="panelState.panelConfig.iconTextIconHideTitle" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-[5px]">
|
||||
<div>
|
||||
文字颜色
|
||||
</div>
|
||||
<div class="flex items-center mt-[5px]">
|
||||
<NColorPicker
|
||||
v-model:value="panelState.panelConfig.iconTextColor"
|
||||
:show-alpha="false"
|
||||
size="small"
|
||||
:modes="['hex']"
|
||||
:swatches="[
|
||||
'#000000',
|
||||
'#ffffff',
|
||||
'#18A058',
|
||||
'#2080F0',
|
||||
'#F0A020',
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</NCard>
|
||||
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
|
||||
<div class="text-slate-500 mb-[5px]">
|
||||
壁纸
|
||||
@ -99,9 +162,9 @@ function resetPanelConfig() {
|
||||
:directory-dnd="true"
|
||||
@finish="handleUploadBackgroundFinish"
|
||||
>
|
||||
<NUploadDragger>
|
||||
<NUploadDragger style="width: 100%;">
|
||||
<div
|
||||
class="h-[150px] w-[280px] border bg-slate-100 flex justify-center items-center cursor-pointer rounded-[10px]"
|
||||
class="h-[200px] w-full border bg-slate-100 flex justify-center items-center cursor-pointer rounded-[10px]"
|
||||
:style="{ background: `url(${panelState.panelConfig.backgroundImageSrc}) no-repeat`, backgroundSize: 'cover' }"
|
||||
>
|
||||
<div class="text-shadow text-white">
|
||||
@ -111,39 +174,14 @@ function resetPanelConfig() {
|
||||
</NUploadDragger>
|
||||
</NUpload>
|
||||
|
||||
<div class="flex items-center mt-[5px]">
|
||||
<span class="mr-[10px]">模糊处理</span>
|
||||
<div class="flex items-center mt-[10px]">
|
||||
<span class="mr-[10px]">模糊</span>
|
||||
<NSlider v-model:value="panelState.panelConfig.backgroundBlur" class="max-w-[200px]" :step="2" :max="20" />
|
||||
</div>
|
||||
</NCard>
|
||||
|
||||
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
|
||||
<div class="text-slate-500 mb-[5px]">
|
||||
图标
|
||||
</div>
|
||||
<div>
|
||||
样式
|
||||
</div>
|
||||
<div class="flex items-center mt-[5px]">
|
||||
<NSelect v-model:value="panelState.panelConfig.iconStyle" :options="iconTypeOptions" />
|
||||
</div>
|
||||
<div>
|
||||
文字颜色
|
||||
</div>
|
||||
<div class="flex items-center mt-[5px]">
|
||||
<NColorPicker
|
||||
v-model:value="panelState.panelConfig.iconTextColor"
|
||||
:show-alpha="false"
|
||||
size="small"
|
||||
:modes="['hex']"
|
||||
:swatches="[
|
||||
'#000000',
|
||||
'#ffffff',
|
||||
'#18A058',
|
||||
'#2080F0',
|
||||
'#F0A020',
|
||||
]"
|
||||
/>
|
||||
<div class="flex items-center mt-[10px]">
|
||||
<span class="mr-[10px]">遮罩</span>
|
||||
<NSlider v-model:value="panelState.panelConfig.backgroundMaskNumber" class="max-w-[200px]" :step="0.1" :max="1" />
|
||||
</div>
|
||||
</NCard>
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { NButton, NCard, useDialog, useMessage } from 'naive-ui'
|
||||
import { useAuthStore, usePanelState, useUserStore } from '@/store'
|
||||
import { logout } from '@/api'
|
||||
import { router } from '@/router'
|
||||
import { SvgIcon } from '@/components/common/'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
@ -51,6 +52,9 @@ function handleLogiut() {
|
||||
|
||||
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
|
||||
<NButton size="small" quaternary type="error" @click="handleLogiut">
|
||||
<template #icon>
|
||||
<SvgIcon icon="tabler:logout" />
|
||||
</template>
|
||||
退出登录
|
||||
</NButton>
|
||||
</NCard>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, onMounted, reactive, ref } from 'vue'
|
||||
import { NButton, NDataTable, NDropdown, useDialog, useMessage } from 'naive-ui'
|
||||
import { NAlert, NButton, NDataTable, NDropdown, useDialog, useMessage } from 'naive-ui'
|
||||
import type { DataTableColumns, PaginationProps } from 'naive-ui'
|
||||
import EditUser from './EditUser/index.vue'
|
||||
import { deletes as usersDeletes, getList as usersGetList } from '@/api/panel/users'
|
||||
@ -171,7 +171,10 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="h-[500px] overflow-auto">
|
||||
<div class="mb-[10px]">
|
||||
<NAlert type="info" :bordered="false">
|
||||
账号之间的数据不互通
|
||||
</NAlert>
|
||||
<div class="my-[10px]">
|
||||
<NButton type="primary" size="small" ghost @click="handleAdd">
|
||||
添加
|
||||
</NButton>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import Result from './Result/index.vue'
|
||||
import EditItem from './EditItem/index.vue'
|
||||
import Setting from './Setting/index.vue'
|
||||
import AppIcon from './AppIcon/index.vue'
|
||||
|
||||
export {
|
||||
Result, EditItem, Setting,
|
||||
Result, EditItem, Setting, AppIcon,
|
||||
}
|
||||
|
@ -1,20 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NButtonGroup, NDropdown, NEllipsis, NGrid, NGridItem, NModal, NSkeleton, NSpin, useDialog, useMessage } from 'naive-ui'
|
||||
import { nextTick, onMounted, ref } from 'vue'
|
||||
import { EditItem, Setting } from './components'
|
||||
import { Clock } from '@/components/deskModule'
|
||||
import { ItemIcon, SvgIcon } from '@/components/common'
|
||||
import { deletes, getListByGroupId } from '@/api/panel/itemIcon'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
import { NBackTop, NButton, NButtonGroup, NDropdown, NModal, NSkeleton, NSpin, useDialog, useMessage } from 'naive-ui'
|
||||
import { nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { AppIcon, EditItem, Setting } from './components'
|
||||
import { Clock, SearchBox } from '@/components/deskModule'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { deletes, getListByGroupId, saveSort } from '@/api/panel/itemIcon'
|
||||
import { getList as getGroupList } from '@/api/panel/itemIconGroup'
|
||||
|
||||
import { getInfo } from '@/api/system/user'
|
||||
import { usePanelState, useUserStore } from '@/store'
|
||||
import { PanelStateNetworkModeEnum } from '@/enum'
|
||||
import { PanelPanelConfigStyleEnum, PanelStateNetworkModeEnum } from '@/enums'
|
||||
import { setTitle } from '@/utils/cmn'
|
||||
|
||||
interface StateDragAppSort {
|
||||
status: boolean
|
||||
}
|
||||
interface ItemGroup extends Panel.ItemIconGroup {
|
||||
items?: Panel.ItemInfo[]
|
||||
}
|
||||
|
||||
const stateDragAppSort = ref<StateDragAppSort>({
|
||||
status: false,
|
||||
})
|
||||
|
||||
const ms = useMessage()
|
||||
const dialog = useDialog()
|
||||
const panelState = usePanelState()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const scrollContainerRef = ref<HTMLElement | undefined>(undefined)
|
||||
|
||||
const editItemInfoShow = ref<boolean>(false)
|
||||
const editItemInfoData = ref<Panel.ItemInfo | null>(null)
|
||||
const windowShow = ref<boolean>(false)
|
||||
@ -31,27 +47,33 @@ const currentRightSelectItem = ref<Panel.ItemInfo | null>(null)
|
||||
|
||||
const settingModalShow = ref(false)
|
||||
|
||||
const dropdownMenuOptions = [
|
||||
{
|
||||
label: '新窗口打开',
|
||||
key: 'newWindows',
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
key: 'edit',
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
key: 'delete',
|
||||
},
|
||||
]
|
||||
const items = ref<Panel.ItemInfo[]>()
|
||||
const items = ref<ItemGroup[]>([])
|
||||
|
||||
function handleAddAppClick() {
|
||||
editItemInfoData.value = null
|
||||
editItemInfoShow.value = true
|
||||
}
|
||||
|
||||
function openPage(openMethod: number, url: string, title?: string) {
|
||||
switch (openMethod) {
|
||||
case 1:
|
||||
window.location.href = url
|
||||
break
|
||||
case 2:
|
||||
window.open(url)
|
||||
break
|
||||
case 3:
|
||||
windowShow.value = true
|
||||
windowSrc.value = url
|
||||
windowTitle.value = title || url
|
||||
windowIframeIsLoad.value = true
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function handleItemClick(item: Panel.ItemInfo) {
|
||||
let jumpUrl = ''
|
||||
|
||||
@ -60,23 +82,7 @@ function handleItemClick(item: Panel.ItemInfo) {
|
||||
if (item.lanUrl === '')
|
||||
jumpUrl = item.url
|
||||
|
||||
switch (item.openMethod) {
|
||||
case 1:
|
||||
window.location.href = jumpUrl
|
||||
break
|
||||
case 2:
|
||||
window.open(jumpUrl)
|
||||
break
|
||||
case 3:
|
||||
windowShow.value = true
|
||||
windowSrc.value = jumpUrl
|
||||
windowTitle.value = item.title
|
||||
windowIframeIsLoad.value = true
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
openPage(item.openMethod, jumpUrl, item.title)
|
||||
}
|
||||
|
||||
function handWindowIframeIdLoad(payload: Event) {
|
||||
@ -84,9 +90,18 @@ function handWindowIframeIdLoad(payload: Event) {
|
||||
}
|
||||
|
||||
function getList() {
|
||||
getListByGroupId<Common.ListResponse<Panel.ItemInfo[]>>().then((res) => {
|
||||
if (res.code === 0)
|
||||
items.value = res.data.list
|
||||
// 获取组数据
|
||||
getGroupList<Common.ListResponse<ItemGroup[]>>().then(({ code, data, msg }) => {
|
||||
if (code === 0)
|
||||
items.value = data.list
|
||||
for (let i = 0; i < data.list.length; i++) {
|
||||
const element = data.list[i]
|
||||
getListByGroupId<Common.ListResponse<Panel.ItemInfo[]>>(element.id).then((res) => {
|
||||
if (res.code === 0)
|
||||
items.value[i].items = res.data.list
|
||||
})
|
||||
}
|
||||
// console.log(items)
|
||||
})
|
||||
}
|
||||
|
||||
@ -100,6 +115,14 @@ function handleSelect(key: string | number) {
|
||||
case 'newWindows':
|
||||
window.open(jumpUrl)
|
||||
break
|
||||
case 'openWanUrl':
|
||||
if (currentRightSelectItem.value)
|
||||
openPage(currentRightSelectItem.value?.openMethod, currentRightSelectItem.value?.url, currentRightSelectItem.value?.title)
|
||||
break
|
||||
case 'openLanUrl':
|
||||
if (currentRightSelectItem.value && currentRightSelectItem.value.lanUrl)
|
||||
openPage(currentRightSelectItem.value?.openMethod, currentRightSelectItem.value.lanUrl, currentRightSelectItem.value?.title)
|
||||
break
|
||||
case 'edit':
|
||||
// 这里有个奇怪的问题,如果不使用{...}的方式 父组件的值会同步修改 标记一下
|
||||
editItemInfoData.value = { ...currentRightSelectItem.value } as Panel.ItemInfo
|
||||
@ -153,12 +176,83 @@ function handleEditSuccess(item: Panel.ItemInfo) {
|
||||
function handleChangeNetwork(mode: PanelStateNetworkModeEnum) {
|
||||
panelState.setNetworkMode(mode)
|
||||
if (mode === PanelStateNetworkModeEnum.lan)
|
||||
ms.success('已经切换成局域网模式,此时再点击已填写局域网地址的图标将跳转至局域网地址(此配置仅保存在本地)')
|
||||
ms.success('已经切换成局域网模式(此配置仅保存在本地)')
|
||||
|
||||
else
|
||||
ms.success('已经切换成互联网模式(此配置仅保存在本地)')
|
||||
}
|
||||
|
||||
// 结束拖拽
|
||||
function handleEndDrag(event: any, itemIconGroup: Panel.ItemIconGroup) {
|
||||
// console.log(event)
|
||||
// console.log(items.value)
|
||||
}
|
||||
|
||||
function handleSaveSort(itemGroup: ItemGroup) {
|
||||
const saveItems: Common.SortItemRequest[] = []
|
||||
if (itemGroup.items) {
|
||||
for (let i = 0; i < itemGroup.items.length; i++) {
|
||||
const element = itemGroup.items[i]
|
||||
saveItems.push({
|
||||
id: element.id as number,
|
||||
sort: i + 1,
|
||||
})
|
||||
}
|
||||
|
||||
saveSort({ itemIconGroupId: itemGroup.id as number, sortItems: saveItems }).then(({ code, msg }) => {
|
||||
if (code === 0) {
|
||||
//
|
||||
ms.success('保存成功')
|
||||
// sortStatus.value = false
|
||||
}
|
||||
else {
|
||||
ms.error(`保存失败:${msg}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getDropdownMenuOptions() {
|
||||
const dropdownMenuOptions = [
|
||||
{
|
||||
label: '新窗口打开',
|
||||
key: 'newWindows',
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
if (currentRightSelectItem.value?.lanUrl && panelState.networkMode === PanelStateNetworkModeEnum.wan) {
|
||||
dropdownMenuOptions.push({
|
||||
label: '打开局域网地址',
|
||||
key: 'openLanUrl',
|
||||
})
|
||||
}
|
||||
|
||||
if (currentRightSelectItem.value?.lanUrl && panelState.networkMode === PanelStateNetworkModeEnum.lan) {
|
||||
dropdownMenuOptions.push({
|
||||
label: '打开互联网地址',
|
||||
key: 'openWanUrl',
|
||||
})
|
||||
}
|
||||
|
||||
dropdownMenuOptions.push({
|
||||
label: '编辑',
|
||||
key: 'edit',
|
||||
}, {
|
||||
label: '删除',
|
||||
key: 'delete',
|
||||
})
|
||||
|
||||
return dropdownMenuOptions
|
||||
}
|
||||
|
||||
watch(() => stateDragAppSort.value.status, (newvalue: boolean) => {
|
||||
if (newvalue === false)
|
||||
getList()
|
||||
else
|
||||
ms.warning('进入排序模式,记得点击保存再退出')
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
|
||||
@ -180,120 +274,137 @@ onMounted(() => {
|
||||
<template>
|
||||
<div class="w-full h-full sun-main ">
|
||||
<div
|
||||
class="cover"
|
||||
:style="{
|
||||
class="cover" :style="{
|
||||
filter: `blur(${panelState.panelConfig.backgroundBlur}px)`,
|
||||
background: `url(${panelState.panelConfig.backgroundImageSrc}) no-repeat`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}"
|
||||
/>
|
||||
<div class="absolute w-full h-full overflow-auto">
|
||||
<div class="mask" :style="{ backgroundColor: `rgba(0,0,0,${panelState.panelConfig.backgroundMaskNumber})` }" />
|
||||
<div ref="scrollContainerRef" class="absolute w-full h-full overflow-auto">
|
||||
<div class="p-2.5 max-w-[1200px] mx-auto mt-[10%]">
|
||||
<!-- 头 -->
|
||||
<div class="mx-[auto] w-[80%]">
|
||||
<div class="flex mx-[auto] items-center justify-center text-white">
|
||||
<div>
|
||||
<span class="text-5xl font-bold text-shadow">
|
||||
<span class="text-2xl md:text-5xl font-bold text-shadow">
|
||||
{{ panelState.panelConfig.logoText }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-2xl mx-[10px]">
|
||||
<div class="text-base lg:text-2xl mx-[10px]">
|
||||
|
|
||||
</div>
|
||||
<div class="text-shadow">
|
||||
<Clock :hide-second="!panelState.panelConfig.clockShowSecond" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="flex mt-[20px] mx-auto w-[80%]">
|
||||
<SearchBox />
|
||||
</div> -->
|
||||
<div v-if="panelState.panelConfig.searchBoxShow" class="flex mt-[20px] mx-auto sm:w-full lg:w-[80%]">
|
||||
<SearchBox />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图标 -->
|
||||
<!-- 应用盒子 -->
|
||||
<div class="mt-[50px]">
|
||||
<!-- 详情图标 -->
|
||||
<div v-if="panelState.panelConfig.iconStyle === 0">
|
||||
<NGrid :x-gap="15" :y-gap="15" item-responsive cols="1 200:1 400:2 600:3 800:4 1000:5 1200:6">
|
||||
<NGridItem v-for="(item, index) in items" :key="index">
|
||||
<div @contextmenu="(e) => handleContextMenu(e, item)">
|
||||
<div
|
||||
class="w-full rounded-2xl cursor-pointer transition-all duration-200 hover:shadow-[0_0_20px_10px_rgba(0,0,0,0.2)] bg-[#2a2a2a6b] flex"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
<div class="w-[70px]">
|
||||
<ItemIcon :item-icon="item.icon" />
|
||||
</div>
|
||||
<div class="text-white m-[8px_8px_0_8px]" :style="{ color: panelState.panelConfig.iconTextColor }">
|
||||
<div>
|
||||
<NEllipsis>
|
||||
{{ item.title }}
|
||||
</NEllipsis>
|
||||
</div>
|
||||
<div>
|
||||
<NEllipsis :line-clamp="2" class="text-xs">
|
||||
{{ item.description }}
|
||||
</NEllipsis>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
<!-- 组纵向排列 -->
|
||||
<div
|
||||
v-for="(itemGroup, itemGroupIndex) in items"
|
||||
:key="itemGroupIndex"
|
||||
class="mt-[50px]"
|
||||
:class="stateDragAppSort.status ? 'shadow-2xl border shadow-[0_0_30px_10px_rgba(0,0,0,0.8)] p-[10px] rounded-2xl' : ''"
|
||||
>
|
||||
<!-- 分组标题 -->
|
||||
<div class="text-white text-xl font-extrabold mb-[20px] ml-[10px]">
|
||||
{{ itemGroup.title }}
|
||||
</div>
|
||||
|
||||
<NGridItem>
|
||||
<div>
|
||||
<div
|
||||
class="w-full rounded-2xl cursor-pointer transition-all duration-200 hover:shadow-[0_0_20px_10px_rgba(0,0,0,0.2)] bg-[#2a2a2a6b] flex"
|
||||
@click="handleAddAppClick"
|
||||
>
|
||||
<ItemIcon :item-icon="{ itemType: 3, text: 'subway:add', bgColor: '#00000000' }" />
|
||||
<div class="text-white m-[8px]" :style="{ color: panelState.panelConfig.iconTextColor }">
|
||||
<div>
|
||||
<NEllipsis>
|
||||
添加图标
|
||||
</NEllipsis>
|
||||
</div>
|
||||
<!-- 详情图标 -->
|
||||
<div v-if="panelState.panelConfig.iconStyle === PanelPanelConfigStyleEnum.info">
|
||||
<div v-if="itemGroup.items">
|
||||
<VueDraggable
|
||||
v-model="itemGroup.items" item-key="sort" :animation="300"
|
||||
class="mx-auto mt-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:12 gap-5"
|
||||
filter=".not-drag"
|
||||
:disabled="!stateDragAppSort.status"
|
||||
@end="(event) => handleEndDrag(event, itemGroup)"
|
||||
>
|
||||
<div v-for="item, index in itemGroup.items" :key="index" :title="item.description" @contextmenu="(e) => handleContextMenu(e, item)">
|
||||
<AppIcon
|
||||
:class="stateDragAppSort.status ? 'cursor-move' : 'cursor-pointer'"
|
||||
:item-info="item"
|
||||
:icon-text-color="panelState.panelConfig.iconTextColor"
|
||||
:icon-text-info-hide-description="panelState.panelConfig.iconTextInfoHideDescription || false"
|
||||
:icon-text-icon-hide-title="panelState.panelConfig.iconTextIconHideTitle || false"
|
||||
:style="0"
|
||||
@click="handleItemClick(item)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="text text-xs">
|
||||
<NEllipsis>
|
||||
新增一个新的图标
|
||||
</NEllipsis>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="itemGroup.items.length === 0" class="not-drag">
|
||||
<AppIcon
|
||||
:class="stateDragAppSort.status ? 'cursor-move' : 'cursor-pointer'"
|
||||
:item-info="{ icon: { itemType: 3, text: 'subway:add' }, title: '添加图标', url: '', openMethod: 0 }"
|
||||
:icon-text-color="panelState.panelConfig.iconTextColor"
|
||||
:icon-text-info-hide-description="panelState.panelConfig.iconTextInfoHideDescription || false"
|
||||
:icon-text-icon-hide-title="panelState.panelConfig.iconTextIconHideTitle || false"
|
||||
:style="0"
|
||||
@click="handleAddAppClick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
</div>
|
||||
</VueDraggable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- APP图标宫型盒子 -->
|
||||
<div v-if="panelState.panelConfig.iconStyle === 1">
|
||||
<NGrid :x-gap="12" :y-gap="8" item-responsive cols="3 300:4 600:6 900:8">
|
||||
<NGridItem v-for="(item, index) in items" :key="index">
|
||||
<div @contextmenu="(e) => handleContextMenu(e, item)">
|
||||
<div
|
||||
class="w-[70px] h-[70px] mx-auto rounded-2xl cursor-pointer transition-all duration-200 hover:shadow-[0_0_20px_10px_rgba(0,0,0,0.2)] bg-[#2a2a2a6b]"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
<ItemIcon :item-icon="item.icon" />
|
||||
</div>
|
||||
<div class="text-center app-icon-text-shadow cursor-pointer mt-[2px]" :style="{ color: panelState.panelConfig.iconTextColor }" @click="handleItemClick(item)">
|
||||
<span>{{ item.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
<!-- APP图标宫型盒子 -->
|
||||
<div v-if="panelState.panelConfig.iconStyle === PanelPanelConfigStyleEnum.icon">
|
||||
<div v-if="itemGroup.items">
|
||||
<VueDraggable
|
||||
v-model="itemGroup.items" item-key="id" :animation="300"
|
||||
class="mx-auto mt-4 grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 lg:grid-cols-10 xl:12 gap-5"
|
||||
|
||||
<NGridItem>
|
||||
<div>
|
||||
<div class="w-[70px] h-[70px] mx-auto rounded-2xl cursor-pointer transition-all duration-200 hover:shadow-[0_0_20px_10px_rgba(0,0,0,0.2)]" @click="handleAddAppClick">
|
||||
<ItemIcon :item-icon="{ itemType: 3, text: 'subway:add', bgColor: '#343434' }" />
|
||||
filter=".not-drag"
|
||||
:disabled="!stateDragAppSort.status"
|
||||
>
|
||||
<div v-for="item, index in itemGroup.items" :key="index" :title="item.description" @contextmenu="(e) => handleContextMenu(e, item)">
|
||||
<AppIcon
|
||||
:class="stateDragAppSort.status ? 'cursor-move' : 'cursor-pointer'"
|
||||
:item-info="item"
|
||||
:icon-text-color="panelState.panelConfig.iconTextColor"
|
||||
:icon-text-info-hide-description="!panelState.panelConfig.iconTextInfoHideDescription"
|
||||
:icon-text-icon-hide-title="panelState.panelConfig.iconTextIconHideTitle || false"
|
||||
:style="1"
|
||||
@click="handleItemClick(item)"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-center app-icon-text-shadow cursor-pointer mt-[2px]" :style="{ color: panelState.panelConfig.iconTextColor }" @click="handleAddAppClick">
|
||||
添加图标
|
||||
|
||||
<div v-if="itemGroup.items.length === 0" class="not-drag">
|
||||
<AppIcon
|
||||
:class="stateDragAppSort.status ? 'cursor-move' : 'cursor-pointer'"
|
||||
:item-info="{ icon: { itemType: 3, text: 'subway:add' }, title: '添加图标', url: '', openMethod: 0 }"
|
||||
:icon-text-color="panelState.panelConfig.iconTextColor"
|
||||
:icon-text-info-hide-description="!panelState.panelConfig.iconTextInfoHideDescription"
|
||||
:icon-text-icon-hide-title="panelState.panelConfig.iconTextIconHideTitle || false"
|
||||
:style="1"
|
||||
@click="handleAddAppClick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
</vuedraggable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑栏 -->
|
||||
<div v-if="stateDragAppSort.status" class="flex mt-[10px]">
|
||||
<div>
|
||||
<NButton color="#2a2a2a6b" @click="handleSaveSort(itemGroup)">
|
||||
<template #icon>
|
||||
<SvgIcon class="text-white font-xl" icon="material-symbols:save" />
|
||||
</template>
|
||||
<div>
|
||||
保存排序
|
||||
</div>
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -301,29 +412,48 @@ onMounted(() => {
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<NDropdown
|
||||
placement="bottom-start"
|
||||
trigger="manual"
|
||||
:x="dropdownMenuX"
|
||||
:y="dropdownMenuY"
|
||||
:options="dropdownMenuOptions"
|
||||
:show="dropdownShow"
|
||||
:on-clickoutside="onClickoutside"
|
||||
@select="handleSelect"
|
||||
placement="bottom-start" trigger="manual" :x="dropdownMenuX" :y="dropdownMenuY"
|
||||
:options="getDropdownMenuOptions()" :show="dropdownShow" :on-clickoutside="onClickoutside" @select="handleSelect"
|
||||
/>
|
||||
|
||||
<!-- 悬浮按钮 -->
|
||||
<div class="fixed-element shadow-[0_0_10px_2px_rgba(0,0,0,0.2)]">
|
||||
<NButtonGroup vertical>
|
||||
<NButton v-if="panelState.networkMode === PanelStateNetworkModeEnum.lan" color="#2a2a2a6b" title="当前:局域网模式,点击切换成互联网模式" @click="handleChangeNetwork(PanelStateNetworkModeEnum.wan)">
|
||||
<NButton v-if="stateDragAppSort.status" color="#2a2a2a6b" @click="stateDragAppSort.status = !stateDragAppSort.status">
|
||||
<template #icon>
|
||||
<SvgIcon class="text-white font-xl" icon="ri:drag-drop-line" />
|
||||
</template>
|
||||
</NButton>
|
||||
<NButtonGroup v-if="!stateDragAppSort.status" vertical>
|
||||
<NButton color="#2a2a2a6b" @click="handleAddAppClick">
|
||||
<template #icon>
|
||||
<SvgIcon class="text-white font-xl" icon="typcn:plus" />
|
||||
</template>
|
||||
</NButton>
|
||||
|
||||
<NButton
|
||||
v-if="panelState.networkMode === PanelStateNetworkModeEnum.lan" color="#2a2a2a6b"
|
||||
title="当前:局域网模式,点击切换成互联网模式" @click="handleChangeNetwork(PanelStateNetworkModeEnum.wan)"
|
||||
>
|
||||
<template #icon>
|
||||
<SvgIcon class="text-white font-xl" icon="material-symbols:lan-outline" />
|
||||
</template>
|
||||
</NButton>
|
||||
<NButton v-if="panelState.networkMode === PanelStateNetworkModeEnum.wan" color="#2a2a2a6b" title="当前:互联网模式,点击切换成局域网模式" @click="handleChangeNetwork(PanelStateNetworkModeEnum.lan)">
|
||||
|
||||
<NButton
|
||||
v-if="panelState.networkMode === PanelStateNetworkModeEnum.wan" color="#2a2a2a6b"
|
||||
title="当前:互联网模式,点击切换成局域网模式" @click="handleChangeNetwork(PanelStateNetworkModeEnum.lan)"
|
||||
>
|
||||
<template #icon>
|
||||
<SvgIcon class="text-white font-xl" icon="mdi:wan" />
|
||||
</template>
|
||||
</NButton>
|
||||
|
||||
<NButton color="#2a2a2a6b" title="排序模式" @click="stateDragAppSort.status = !stateDragAppSort.status">
|
||||
<template #icon>
|
||||
<SvgIcon class="text-white font-xl" icon="ri:drag-drop-line" />
|
||||
</template>
|
||||
</NButton>
|
||||
|
||||
<NButton color="#2a2a2a6b" @click="settingModalShow = !settingModalShow">
|
||||
<template #icon>
|
||||
<SvgIcon class="text-white font-xl" icon="ep:setting" />
|
||||
@ -331,20 +461,30 @@ onMounted(() => {
|
||||
</NButton>
|
||||
</NButtonGroup>
|
||||
|
||||
<NBackTop
|
||||
:listen-to="() => scrollContainerRef"
|
||||
:right="10"
|
||||
:bottom="10"
|
||||
style="background-color:transparent;border: none;box-shadow: none;"
|
||||
>
|
||||
<div class="shadow-[0_0_10px_2px_rgba(0,0,0,0.2)]">
|
||||
<NButton color="#2a2a2a6b">
|
||||
<template #icon>
|
||||
<SvgIcon class="text-white font-xl" icon="icon-park-outline:to-top" />
|
||||
</template>
|
||||
</NButton>
|
||||
</div>
|
||||
</NBackTop>
|
||||
|
||||
<Setting v-model:visible="settingModalShow" />
|
||||
</div>
|
||||
|
||||
<EditItem v-model:visible="editItemInfoShow" :item-info="editItemInfoData" @done="handleEditSuccess" />
|
||||
|
||||
<!-- 新窗口 -->
|
||||
<!-- 弹窗 -->
|
||||
<NModal
|
||||
v-model:show="windowShow"
|
||||
:mask-closable="false"
|
||||
preset="card"
|
||||
style="max-width: 1000px;height: 600px;border-radius: 1rem;"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
role="dialog"
|
||||
v-model:show="windowShow" :mask-closable="false" preset="card"
|
||||
style="max-width: 1000px;height: 600px;border-radius: 1rem;" :bordered="false" size="small" role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<template #header>
|
||||
@ -358,45 +498,60 @@ onMounted(() => {
|
||||
</template>
|
||||
<div class="w-full h-full rounded-2xl overflow-hidden border">
|
||||
<NSkeleton v-if="windowIframeIsLoad" height="100%" width="100%" />
|
||||
<iframe v-show="!windowIframeIsLoad" id="windowIframeId" ref="windowIframeRef" :src="windowSrc" class="w-full h-full" frameborder="0" @load="handWindowIframeIdLoad" />
|
||||
<iframe
|
||||
v-show="!windowIframeIsLoad" id="windowIframeId" ref="windowIframeRef" :src="windowSrc"
|
||||
class="w-full h-full" frameborder="0" @load="handWindowIframeIdLoad"
|
||||
/>
|
||||
</div>
|
||||
</NModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
body,html{
|
||||
body,
|
||||
html {
|
||||
overflow: hidden;
|
||||
background-color: rgb(54, 54, 54);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.sun-main{
|
||||
.mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sun-main {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.cover{
|
||||
position:absolute;
|
||||
width:100%;
|
||||
height:100%;
|
||||
.cover {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
/* background: url(@/assets/start_sky.jpg) no-repeat; */
|
||||
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.text-shadow{
|
||||
.text-shadow {
|
||||
text-shadow: 2px 2px 50px rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
.app-icon-text-shadow{
|
||||
.app-icon-text-shadow {
|
||||
text-shadow: 2px 2px 5px rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
.fixed-element {
|
||||
position: fixed; /* 将元素固定在屏幕上 */
|
||||
right: 30px; /* 距离屏幕顶部的距离 */
|
||||
bottom: 50px; /* 距离屏幕左侧的距离 */
|
||||
position: fixed;
|
||||
/* 将元素固定在屏幕上 */
|
||||
right: 10px;
|
||||
/* 距离屏幕顶部的距离 */
|
||||
bottom: 50px;
|
||||
/* 距离屏幕左侧的距离 */
|
||||
}
|
||||
</style>
|
||||
|
@ -10,7 +10,7 @@ const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
const ms = useMessage()
|
||||
const isShowCaptcha = ref<boolean>(false)
|
||||
const isShowRegister = ref<boolean>(false)
|
||||
// const isShowRegister = ref<boolean>(false)
|
||||
|
||||
const captchaRef = ref()
|
||||
|
||||
@ -42,13 +42,13 @@ function handleSubmit() {
|
||||
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<NCard class="login-card">
|
||||
<div class="login-title">
|
||||
<NGradientText :size="30" type="success">
|
||||
<NCard class="login-card" style="border-radius: 20px;">
|
||||
<div class="login-title ">
|
||||
<NGradientText :size="30" type="success" class="!font-bold">
|
||||
{{ $t('common.appName') }}
|
||||
</NGradientText>
|
||||
</div>
|
||||
<NForm :model="form" label-width="100px">
|
||||
<NForm :model="form" label-width="100px" @keydown.enter="handleSubmit">
|
||||
<NFormItem>
|
||||
<NInput v-model:value="form.username" placeholder="请输入邮箱地址作为账号">
|
||||
<template #prefix>
|
||||
@ -77,13 +77,17 @@ function handleSubmit() {
|
||||
</NButton>
|
||||
</NFormItem>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<!-- <div class="flex justify-end">
|
||||
<NButton v-if="isShowRegister" quaternary type="info" class="flex" @click="$router.push({ path: '/register' })">
|
||||
注册
|
||||
</NButton>
|
||||
<!-- <NButton quaternary type="info" class="flex" @click="$router.push({ path: '/resetPassword' })">
|
||||
<NButton quaternary type="info" class="flex" @click="$router.push({ path: '/resetPassword' })">
|
||||
忘记密码?
|
||||
</NButton> -->
|
||||
</NButton>
|
||||
</div> -->
|
||||
|
||||
<div class="flex justify-center text-slate-300">
|
||||
Powered By <a href="https://github.com/hslr-s/sun-panel" target="_blank" class="ml-[5px] text-slate-500">Sun-Panel</a>
|
||||
</div>
|
||||
</NForm>
|
||||
</NCard>
|
||||
|