更新1.2.0
Squashed commit of the following: commit f6c4e5c18170f5699a680113c5591b80833548d8 Author: Sun <95302870@qq.com> Date: Wed Dec 27 17:33:49 2023 +0800 更新正式版本1.2.0 commit ebf493d6b1da02dd516a2cb5d0f61f70aaab242f Author: Sun <95302870@qq.com> Date: Tue Dec 26 15:43:32 2023 +0800 增加通过后端获取网站图标 commit 2462b364c2dca3648ea79a32d9305a8df86402b8 Author: Sun <95302870@qq.com> Date: Tue Dec 26 13:21:16 2023 +0800 优化导出导出提示语 commit 71f7cbb48ddc2c7c660f6cd57579422e99195fe1 Author: Sun <95302870@qq.com> Date: Tue Dec 26 13:15:45 2023 +0800 加载图片增加loading commit 546ad3c93e935262ee5959be075371127343b663 Author: Sun <95302870@qq.com> Date: Tue Dec 26 12:45:11 2023 +0800 优化文件管理返回的路径带有.的问题 commit 322c3f01cd0fbc2b21bd1e12f07d069a9dbc031e Author: Sun <95302870@qq.com> Date: Tue Dec 26 12:29:42 2023 +0800 修复 commit 8a718378a843bdd78d43b42ddd987ce5b1741000 Author: Sun <95302870@qq.com> Date: Wed Dec 20 13:44:28 2023 +0800 更新说明文件 commit 9295bd6e7b2d05c469d8b9123aadfb3dbb90499c Author: Sun <95302870@qq.com> Date: Wed Dec 20 11:51:57 2023 +0800 更新1.2.0-beta23-12-20 commit 17d61f1694131890d56e27f5dcde9f901a624452 Merge: 9697541 9e53e74 Author: Sun <95302870@qq.com> Date: Wed Dec 20 11:32:16 2023 +0800 Merge branch 'dev' of https://github.com/hslr-s/sun-panel into dev commit 9e53e74ea6bfcd92a1719256e8f37536fa20893d Author: 红烧猎人 <38825747+hslr-s@users.noreply.github.com> Date: Wed Dec 20 11:29:34 2023 +0800 Create LICENSE commit 9697541da670d84b5fca11283741296e389ed99f Author: Sun <95302870@qq.com> Date: Wed Dec 20 11:23:20 2023 +0800 增加编辑模式点击图标修改图标数据 commit a6045d78476e4e6dae3ed9377ed8aa2df72b9bed Author: Sun <95302870@qq.com> Date: Wed Dec 20 11:11:51 2023 +0800 优化添加图标的时候增加loading避免多次点击并添加 commit 1e8ae7a65dbad44a79a8eae1ec167883beb790dc Author: Sun <95302870@qq.com> Date: Wed Dec 20 10:53:31 2023 +0800 修改配置文件更改上传文件夹,路由没有监听的bug commit e21185e29ac8e90ef6b953f45824e8d0802d13c0 Author: Sun <95302870@qq.com> Date: Wed Dec 20 10:52:56 2023 +0800 优化内置app的文字说明 commit 266a8191ecfe356d936b79115da80a2a61fc57c9 Author: Sun <95302870@qq.com> Date: Tue Dec 19 20:46:38 2023 +0800 重新调整各个内置应用的样式适配app启动器 commit f9f9ebe9d2f23971c7dfd17740481b141111508e Author: Sun <95302870@qq.com> Date: Tue Dec 19 20:45:57 2023 +0800 升级naive-ui到2.36.0版本 commit 0c4ff7ca23202f35e5b72b5f2ec3a2b690b6926e Author: Sun <95302870@qq.com> Date: Tue Dec 19 16:50:38 2023 +0800 登录时账号首尾去空格 commit b55da5a3060f38581ff78cc85bd2de27b6c2ede3 Author: Sun <95302870@qq.com> Date: Tue Dec 19 12:07:01 2023 +0800 完成上传文件管理器 commit 6355b9f826e6e8882b87fc2aef80960fcdd8e752 Author: Sun <95302870@qq.com> Date: Tue Dec 19 11:44:12 2023 +0800 修改配置同步云端为保存 commit c3d2dd9b28d05ec7b07574721c8e473add67d9d3 Author: Sun <95302870@qq.com> Date: Tue Dec 19 11:43:21 2023 +0800 基础完成上传文件管理 commit 775c98a3cca3135e14fac24ac09af756b01f4ba5 Author: Sun <95302870@qq.com> Date: Mon Dec 18 22:08:50 2023 +0800 增加上传文件管理的内置应用 commit 79780faa6b52c48a506b78322e5499ccfaee4b47 Author: Sun <95302870@qq.com> Date: Mon Dec 18 13:45:06 2023 +0800 开发文件删除和文件列表 commit 3d2eac74ef32612334e6700a5a1c65e72ed5495f Author: Sun <95302870@qq.com> Date: Sun Dec 17 20:01:48 2023 +0800 修改启动后输出生成配置文件的错误级别 commit 083a34cdd8d3b713dfbaf193708cedaa5a9aadc6 Author: Sun <95302870@qq.com> Date: Sun Dec 17 17:17:52 2023 +0800 修复恢复默认背景色无效的问题 commit a933d5fd2f245c1df007dd7904f02178634b2d5f Author: Sun <95302870@qq.com> Date: Sun Dec 17 16:40:25 2023 +0800 优化 图标编辑组件并增加根据网址url提取图标 commit 2cd5c1ca60cc8310c361c6e49125eda96f8e1e59 Author: Sun <95302870@qq.com> Date: Sat Dec 16 21:48:41 2023 +0800 增加ico文件上传支持 commit 7b8bd203bdb2fd98db72e7058ee701ff9b1eb7ed Author: Sun <95302870@qq.com> Date: Sat Dec 16 21:23:54 2023 +0800 优化窗口尺寸 commit 7191817d3fd3c3c1ac33d30be9cebbbf719be3e1 Author: Sun <95302870@qq.com> Date: Sat Dec 16 21:16:38 2023 +0800 启动器左侧菜单根据屏幕自动伸缩 commit a060982fc368c86f44bb639a124fd4a209c4ebb8 Author: Sun <95302870@qq.com> Date: Sat Dec 16 21:12:54 2023 +0800 排序模式禁用左右键点击 commit 6a8b8b5effbc5c4f3866038d8bf547193352d028 Author: Sun <95302870@qq.com> Date: Sat Dec 16 13:47:25 2023 +0800 全新图标排序方式,减少首页全局按钮 Squashed commit of the following: commit f2ae66b7539e84f7f88fb739150775d2d4fd0107 Author: Sun <95302870@qq.com> Date: Sat Dec 16 13:45:46 2023 +0800 优化添加图标的方式和编辑弹窗的添加编辑逻辑 commit 09a89002274b693bbf4cc1c25bd50f8791a5d3b4 Author: Sun <95302870@qq.com> Date: Fri Dec 15 21:12:34 2023 +0800 优化保存排序 commit 08cb6f53ef448dcfb0858b329656eba48265f6e4 Author: Sun <95302870@qq.com> Date: Fri Dec 15 21:04:09 2023 +0800 取消全局排序模式,组单独排序 commit 080926d28bc4c15796d4fe956e6f865a9ac505d4 Author: Sun <95302870@qq.com> Date: Fri Dec 15 17:26:45 2023 +0800 重新调整图标显示 commit 1eb5a712c2917a0f35897a6444b5643a3eb0d809 Author: Sun <95302870@qq.com> Date: Fri Dec 15 13:38:30 2023 +0800 增加分组hover状态监听 commit 1ef17b5e8f754c2d5397466c9a82a23618f2b15f Author: Sun <95302870@qq.com> Date: Thu Dec 14 22:27:36 2023 +0800 解决旧版本升级导致刷新后丢失登录的问题 commit 56929001842242d99a8ea061fd663c99ad4ed2f5 Author: Sun <95302870@qq.com> Date: Thu Dec 14 13:44:35 2023 +0800 更新1.2.0-beta23-12-14 commit a1c7e98104f9a3939392452c8b5be62d69df97b4 Author: Sun <95302870@qq.com> Date: Thu Dec 14 13:43:35 2023 +0800 优化部分提示内容以及构建的测试 commit 62ca5f88bcad032082941752508299b05cdd3a3e Author: Sun <95302870@qq.com> Date: Thu Dec 14 12:47:25 2023 +0800 优化手机端时钟样式 commit a2ef23fdf867dfb007e3689e2cdf264f08597f37 Author: Sun <95302870@qq.com> Date: Thu Dec 14 12:22:19 2023 +0800 增加快捷图标搜索的开关 commit a41980331b6c9ca9ca3b34e6bfb4198bcb09c05a Author: Sun <95302870@qq.com> Date: Thu Dec 14 11:52:52 2023 +0800 优化代码 commit aee0329a8fcdc8459681e499ffe08c4191acb28b Author: Sun <95302870@qq.com> Date: Thu Dec 14 11:06:32 2023 +0800 更换设置组件为app启动器,增加内置应用的概念 Squashed commit of the following: commit a5e40ae3ccce65eb3208474dbc0fdd9850146195 Author: Sun <95302870@qq.com> Date: Thu Dec 14 11:04:39 2023 +0800 删除设置设置等组件完成替换app启动器 commit c2aedae0862abdbe2bafabfb46a1aafdc30fe3a2 Author: Sun <95302870@qq.com> Date: Thu Dec 14 10:48:20 2023 +0800 迁移设置的内置应用到组件apps,增加app启动器 commit adcabcc46fe4fee4a094250a78d2b1f0b1e012ba Author: Sun <95302870@qq.com> Date: Wed Dec 13 23:13:44 2023 +0800 完成新的设置功能 commit b1193e2252c69bd9bfb697ea587d706ccebf9346 Author: Sun <95302870@qq.com> Date: Wed Dec 13 20:05:02 2023 +0800 初步完成了设置组件的升级版本 commit f35223f40be4a575c89622427debeecad18d09cb Author: Sun <95302870@qq.com> Date: Sun Dec 10 18:45:23 2023 +0800 更换dockerfile基础镜像 Squashed commit of the following: commit bd722767ba695059e610d60597a1836708f0e300 Author: Sun <95302870@qq.com> Date: Sun Dec 10 17:56:33 2023 +0800 更换后端alpine基础镜像3.18旧版本,新版本导致sqlite3报错 commit e5a2855e3206bd86b5b800fbcf0c8b97da8feca9 Author: Sun <95302870@qq.com> Date: Sun Dec 10 14:38:21 2023 +0800 后端 尝试升级sqlite解决编译失败的问题 commit e1789e93dc25b678685405fd31163a08c6c5871a Author: Sun <95302870@qq.com> Date: Sun Dec 10 13:43:56 2023 +0800 尝试修改go.mod 版本号排查问题 commit f80eea9283741ff6344dabc0f355ee14b8b271e3 Author: Sun <95302870@qq.com> Date: Sun Dec 10 13:30:19 2023 +0800 调试编译错误 commit f1596cc7351ca998e3a00e2876c62466485b2a2c Author: Sun <95302870@qq.com> Date: Sun Dec 10 13:23:12 2023 +0800 增加编译调试信息 commit 314b81646247c3e453501754fc57650a3ed1d7c0 Author: Sun <95302870@qq.com> Date: Sun Dec 10 13:11:22 2023 +0800 取消golang国内镜像尝试,编译镜像 commit 809e4ab700cdf248027834441517fe005276bfa7 Author: Sun <95302870@qq.com> Date: Sun Dec 10 10:58:50 2023 +0800 更新1.2.0-beta23-12-10 Squashed commit of the following: commit ba962f6e7f9d45825f358ffe129e677bdf513c3e Author: Sun <95302870@qq.com> Date: Sun Dec 10 10:57:40 2023 +0800 更新1.2.0-beta23-12-10 commit f9c9d442dd1abe16eb5f2a3b5ffbda35a0a42858 Author: Sun <95302870@qq.com> Date: Sun Dec 10 10:57:21 2023 +0800 优化导入文件筛选类型 commit 0f103b87ec5581088cd521786d032cff5cf3ba58 Author: Sun <95302870@qq.com> Date: Sun Dec 10 10:38:09 2023 +0800 编译前的优化 commit 380575f5b23483bb5b31273a2578605aa32e9da9 Author: Sun <95302870@qq.com> Date: Sat Dec 9 23:31:33 2023 +0800 解决并发请求导致两个默认分组的bug commit 09ee3e4cfb282b059e28e7f56c3549d0b209ecbd Author: Sun <95302870@qq.com> Date: Sat Dec 9 22:28:40 2023 +0800 增加导入导出图标 Squashed commit of the following: commit b4a755aaf6eff6665866c1f08441f42a296ec81f Author: Sun <95302870@qq.com> Date: Sat Dec 9 22:18:49 2023 +0800 导出导入完成 commit 7be0436b18e1fee1c33013e27cadc838071b86a9 Author: Sun <95302870@qq.com> Date: Sat Dec 9 21:24:23 2023 +0800 增加批量添加图标接口 commit 96e15348aea83a0e0c9420c7182f7448f9482b10 Author: Sun <95302870@qq.com> Date: Sat Dec 9 21:23:55 2023 +0800 导入导出图标完成 commit 3fd4d29a4c6c1cc3090f1f5bbc6a1154a8923bbb Author: Sun <95302870@qq.com> Date: Sat Dec 9 20:00:57 2023 +0800 优化代码加入相关loading commit 2938eea93601ae1df92842f1f02f7471e0f0e85d Author: Sun <95302870@qq.com> Date: Sat Dec 9 19:17:53 2023 +0800 完成基础的导出,支持跨模块 commit 4c96702cd449bf7a6224cb2d6aa5117de75f24fd Author: Sun <95302870@qq.com> Date: Sat Dec 9 14:58:49 2023 +0800 初步完成导出导入工具和测试 commit 45bf44f3588df302ebaf7cf13cb15bb6a2b50d2a Author: Sun <95302870@qq.com> Date: Fri Dec 8 21:19:00 2023 +0800 更换本地化svg图标显示方案 Squashed commit of the following: commit e0241985b55fa39145abd2c468735bdf5182d57e Author: Sun <95302870@qq.com> Date: Fri Dec 8 21:04:06 2023 +0800 更换直接使用本地svg的方案 commit d20e11cd303240a6fdbce66a94c45956a28a175d Merge: 67023d0 f0e4257 Author: Sun <95302870@qq.com> Date: Fri Dec 8 16:22:19 2023 +0800 Merge branch 'dev' into feature/icon commit 67023d0ce494b46460729cf39a45af8c9fd10124 Author: Sun <95302870@qq.com> Date: Fri Dec 8 12:17:45 2023 +0800 修改原图标组件为在线图标组件 commit 379441c869489fdf44cb92a0cace75f9e5318915 Author: Sun <95302870@qq.com> Date: Fri Dec 8 12:14:59 2023 +0800 适配本地化图标 commit da6feaa655e792d176f56777e089393f7658eda2 Author: Sun <95302870@qq.com> Date: Fri Dec 8 12:14:07 2023 +0800 图标本地化 commit 02d84f66069653dedfc35578b1b26dad6edac0e3 Author: Sun <95302870@qq.com> Date: Thu Dec 7 23:09:41 2023 +0800 增加离线图标库(源svgicon组件)的兼容并增加在线图标库组件 commit f0e425737217083668a00212dba5f7e82cab9a02 Author: Sun <95302870@qq.com> Date: Fri Dec 8 13:16:42 2023 +0800 删除暂时无用语言包 commit 60983b94d8e50be08fb233b111b1008c22f86acc Author: Sun <95302870@qq.com> Date: Fri Dec 8 13:15:03 2023 +0800 删除无用文件 commit ba962f6e7f9d45825f358ffe129e677bdf513c3e Author: Sun <95302870@qq.com> Date: Sun Dec 10 10:57:40 2023 +0800 更新1.2.0-beta23-12-10 commit f9c9d442dd1abe16eb5f2a3b5ffbda35a0a42858 Author: Sun <95302870@qq.com> Date: Sun Dec 10 10:57:21 2023 +0800 优化导入文件筛选类型 commit 0f103b87ec5581088cd521786d032cff5cf3ba58 Author: Sun <95302870@qq.com> Date: Sun Dec 10 10:38:09 2023 +0800 编译前的优化 commit 380575f5b23483bb5b31273a2578605aa32e9da9 Author: Sun <95302870@qq.com> Date: Sat Dec 9 23:31:33 2023 +0800 解决并发请求导致两个默认分组的bug commit 09ee3e4cfb282b059e28e7f56c3549d0b209ecbd Author: Sun <95302870@qq.com> Date: Sat Dec 9 22:28:40 2023 +0800 增加导入导出图标 Squashed commit of the following: commit b4a755aaf6eff6665866c1f08441f42a296ec81f Author: Sun <95302870@qq.com> Date: Sat Dec 9 22:18:49 2023 +0800 导出导入完成 commit 7be0436b18e1fee1c33013e27cadc838071b86a9 Author: Sun <95302870@qq.com> Date: Sat Dec 9 21:24:23 2023 +0800 增加批量添加图标接口 commit 96e15348aea83a0e0c9420c7182f7448f9482b10 Author: Sun <95302870@qq.com> Date: Sat Dec 9 21:23:55 2023 +0800 导入导出图标完成 commit 3fd4d29a4c6c1cc3090f1f5bbc6a1154a8923bbb Author: Sun <95302870@qq.com> Date: Sat Dec 9 20:00:57 2023 +0800 优化代码加入相关loading commit 2938eea93601ae1df92842f1f02f7471e0f0e85d Author: Sun <95302870@qq.com> Date: Sat Dec 9 19:17:53 2023 +0800 完成基础的导出,支持跨模块 commit 4c96702cd449bf7a6224cb2d6aa5117de75f24fd Author: Sun <95302870@qq.com> Date: Sat Dec 9 14:58:49 2023 +0800 初步完成导出导入工具和测试 commit 45bf44f3588df302ebaf7cf13cb15bb6a2b50d2a Author: Sun <95302870@qq.com> Date: Fri Dec 8 21:19:00 2023 +0800 更换本地化svg图标显示方案 Squashed commit of the following: commit e0241985b55fa39145abd2c468735bdf5182d57e Author: Sun <95302870@qq.com> Date: Fri Dec 8 21:04:06 2023 +0800 更换直接使用本地svg的方案 commit d20e11cd303240a6fdbce66a94c45956a28a175d Merge: 67023d0 f0e4257 Author: Sun <95302870@qq.com> Date: Fri Dec 8 16:22:19 2023 +0800 Merge branch 'dev' into feature/icon commit 67023d0ce494b46460729cf39a45af8c9fd10124 Author: Sun <95302870@qq.com> Date: Fri Dec 8 12:17:45 2023 +0800 修改原图标组件为在线图标组件 commit 379441c869489fdf44cb92a0cace75f9e5318915 Author: Sun <95302870@qq.com> Date: Fri Dec 8 12:14:59 2023 +0800 适配本地化图标 commit da6feaa655e792d176f56777e089393f7658eda2 Author: Sun <95302870@qq.com> Date: Fri Dec 8 12:14:07 2023 +0800 图标本地化 commit 02d84f66069653dedfc35578b1b26dad6edac0e3 Author: Sun <95302870@qq.com> Date: Thu Dec 7 23:09:41 2023 +0800 增加离线图标库(源svgicon组件)的兼容并增加在线图标库组件 commit f0e425737217083668a00212dba5f7e82cab9a02 Author: Sun <95302870@qq.com> Date: Fri Dec 8 13:16:42 2023 +0800 删除暂时无用语言包 commit 60983b94d8e50be08fb233b111b1008c22f86acc Author: Sun <95302870@qq.com> Date: Fri Dec 8 13:15:03 2023 +0800 删除无用文件 commit 2f0b230812834ed306749a9c2f65bdcdfbda6dd6 Author: Sun <95302870@qq.com> Date: Fri Dec 8 12:19:11 2023 +0800 图标本地化处理 Squashed commit of the following: commit 67023d0ce494b46460729cf39a45af8c9fd10124 Author: Sun <95302870@qq.com> Date: Fri Dec 8 12:17:45 2023 +0800 修改原图标组件为在线图标组件 commit 379441c869489fdf44cb92a0cace75f9e5318915 Author: Sun <95302870@qq.com> Date: Fri Dec 8 12:14:59 2023 +0800 适配本地化图标 commit da6feaa655e792d176f56777e089393f7658eda2 Author: Sun <95302870@qq.com> Date: Fri Dec 8 12:14:07 2023 +0800 图标本地化 commit 02d84f66069653dedfc35578b1b26dad6edac0e3 Author: Sun <95302870@qq.com> Date: Thu Dec 7 23:09:41 2023 +0800 增加离线图标库(源svgicon组件)的兼容并增加在线图标库组件 commit 524230b66a7d6657b375ef0ce418b9c5332871e4 Author: Sun <95302870@qq.com> Date: Thu Dec 7 14:58:23 2023 +0800 修改自动构建文件版本号的问题 commit c03be7e4fee8c9b27ae2db3d1d6f72d211877bbf Author: Sun <95302870@qq.com> Date: Thu Dec 7 14:07:03 2023 +0800 解决偶然出现刷新页面后登录失效的问题 commit a48c6eefa843b6b739f17cc30617ec95d78d7bc7 Author: Sun <95302870@qq.com> Date: Thu Dec 7 11:16:11 2023 +0800 修复 账号管理页面语言包未适配的问题 commit 1392a5d9355b9e54da92891ebe312709e486618c Author: Sun <95302870@qq.com> Date: Thu Dec 7 00:26:21 2023 +0800 编译增加musl的amd64版本 commit ee49f4fef6aed5f7afedad995058724bffc7e38f Author: Sun <95302870@qq.com> Date: Wed Dec 6 16:22:45 2023 +0800 1.2.0-beta23-12-6编译前修正 commit ba13f996592749a60a2f676d03fa6e1d6b512f84 Merge: 4e4c6c2 e393366 Author: Sun <95302870@qq.com> Date: Wed Dec 6 15:55:37 2023 +0800 Merge branch 'dev' into feature/visitor commit e39336653c30bbaadcaeba327186e6ca3a4eab2b Author: Sun <95302870@qq.com> Date: Wed Dec 6 15:55:21 2023 +0800 重新适配新版国际化文件 commit 54aa74d3165169d5d745affe27a5d0aaf46b9f0d Author: Sun <95302870@qq.com> Date: Wed Dec 6 15:54:08 2023 +0800 修复创建用户无法保存成功昵称 commit 484fe270f88fdfb1d7e97c7a5ce22ecf5ba6af7f Author: Sun <95302870@qq.com> Date: Wed Dec 6 15:53:23 2023 +0800 修改 密码增加成功提示 commit 29a100a7971424b86fbff7b3650e06eed5904b7b Author: Sun <95302870@qq.com> Date: Wed Dec 6 15:38:16 2023 +0800 增加修改密码和修改昵称的功能 设置的账号信息页面国际化适配 commit 99e9365a1ba9d4c0aaabe6e4ba0b6b704bda0c63 Author: Sun <95302870@qq.com> Date: Wed Dec 6 15:29:38 2023 +0800 修改配置文件默认端口提示 commit 8c1a0d6d90fcdfb61916052040664ab42ac74b33 Author: Sun <95302870@qq.com> Date: Tue Dec 5 21:33:39 2023 +0800 增加上下边距百分比调整 commit 4e4c6c27817cc2bc038f156267caaec6233f52d2 Merge: 8a3ff34 0231d76 Author: Sun <95302870@qq.com> Date: Tue Dec 5 15:53:20 2023 +0800 Merge branch 'dev' into feature/visitor commit 0231d76d7a913bd1284bd3b9d98677aad4cf844c Author: Sun <95302870@qq.com> Date: Tue Dec 5 15:10:37 2023 +0800 公开模式隐藏右键中的敏感菜单 commit 3560f8f1859f3408c1837c4215255da933807e4e Author: Sun <95302870@qq.com> Date: Tue Dec 5 15:05:39 2023 +0800 修改语法,改为对象传值法 commit 2ec1277cf0a18716356e59de40e4d0078b054883 Author: Sun <95302870@qq.com> Date: Mon Dec 4 20:50:20 2023 +0800 初步测试完成访客模式 Squashed commit of the following: commit 8a3ff3477d9a6c388b0750d6011ba3c7a74442da Author: Sun <95302870@qq.com> Date: Mon Dec 4 20:47:44 2023 +0800 访客模式基本完成 commit b067b20c4377ca1fc9fd303feece815a7b0c6303 Author: Sun <95302870@qq.com> Date: Mon Dec 4 16:43:12 2023 +0800 适配访客模式 commit ed6dcdf4192bb9aa95cac2eabb65aff369f93bb4 Author: Sun <95302870@qq.com> Date: Mon Dec 4 14:17:51 2023 +0800 调整auth store并优化auth使用逻辑 commit be92c33496e59b5e8e187bec952d1fccb63ef08b Author: Sun <95302870@qq.com> Date: Sun Dec 3 21:47:43 2023 +0800 优化登录接口不返回密码等数据 commit e75a02071de8d11311c87de97378bf2a361512f9 Author: Sun <95302870@qq.com> Date: Sun Dec 3 21:08:51 2023 +0800 初步完成访客模式 commit c6805c99f5decaf204e9d3958914338649c9e9e6 Author: Sun <95302870@qq.com> Date: Sat Dec 2 22:26:08 2023 +0800 增加访客模式的获取和设置接口 commit 8a3ff3477d9a6c388b0750d6011ba3c7a74442da Author: Sun <95302870@qq.com> Date: Mon Dec 4 20:47:44 2023 +0800 访客模式基本完成 commit b067b20c4377ca1fc9fd303feece815a7b0c6303 Author: Sun <95302870@qq.com> Date: Mon Dec 4 16:43:12 2023 +0800 适配访客模式 commit ed6dcdf4192bb9aa95cac2eabb65aff369f93bb4 Author: Sun <95302870@qq.com> Date: Mon Dec 4 14:17:51 2023 +0800 调整auth store并优化auth使用逻辑 commit be92c33496e59b5e8e187bec952d1fccb63ef08b Author: Sun <95302870@qq.com> Date: Sun Dec 3 21:47:43 2023 +0800 优化登录接口不返回密码等数据 commit e75a02071de8d11311c87de97378bf2a361512f9 Author: Sun <95302870@qq.com> Date: Sun Dec 3 21:08:51 2023 +0800 初步完成访客模式 commit c6805c99f5decaf204e9d3958914338649c9e9e6 Author: Sun <95302870@qq.com> Date: Sat Dec 2 22:26:08 2023 +0800 增加访客模式的获取和设置接口 commit d81c86e23e272a654eb4af218891676b3d7835bf Author: Sun <95302870@qq.com> Date: Sat Dec 2 15:24:42 2023 +0800 搜索框增加清空搜索内容按钮并国际化支持 commit 1223502a9311393fbfa326f4adf9e49288361bb5 Author: Sun <95302870@qq.com> Date: Sat Dec 2 14:30:37 2023 +0800 管理员页面适配国际化并增加角色列 commit a0211b429de0636bbe0e109d3f02aaa42429828c Author: Sun <95302870@qq.com> Date: Sat Dec 2 12:05:33 2023 +0800 命令行重置密码改为重置管理员密码 commit 1825f931e8f974b50f9a83f19cc16334a578a798 Merge: 34370a4 6f8ba4e Author: Sun <95302870@qq.com> Date: Fri Dec 1 21:17:04 2023 +0800 Merge branch 'bug' into dev commit 6f8ba4e6949638dd9037507fc341365375ee74b6 Author: Sun <95302870@qq.com> Date: Fri Dec 1 21:16:18 2023 +0800 修复用户首次无云端数据将使用默认数据 commit 34370a48486632c4a6fced5a84df34713738233b Author: Sun <95302870@qq.com> Date: Fri Dec 1 20:34:28 2023 +0800 用户编辑窗口增加权限的保存并适配国际化 commit 393a80900a80097e99e569dbe35c0685a89b942f Author: Sun <95302870@qq.com> Date: Fri Dec 1 20:03:22 2023 +0800 取消登录调试 commit 38ec834354ad0c31b2c38872bb630ce2eb04dc12 Author: Sun <95302870@qq.com> Date: Fri Dec 1 20:03:10 2023 +0800 修复仅有一个管理员账号的时候删除拦截未成功的问题 commit 7c5130ad8dbccdaaf9cf8bd8a75f87a0ce9beeef Author: Sun <95302870@qq.com> Date: Fri Dec 1 19:59:20 2023 +0800 完成平台管理账号并在删除至少保留一个管理平台的账号 commit 907f93b9238052265e903529d0597dc91f9e1d21 Author: Sun <95302870@qq.com> Date: Fri Dec 1 19:30:22 2023 +0800 用户管理增加管理权限可以操作,并取消账号的邮箱限制 commit 41f5d20b4c6aad9b8f1992c171714d93e06b3243 Author: Sun <95302870@qq.com> Date: Fri Dec 1 12:28:32 2023 +0800 适配登录页面语言国际化 commit 7e54a46cddd8bdbd888349b1f663cfe320a34455 Merge: ae310fc 74bc8c0 Author: 红烧猎人 <38825747+hslr-s@users.noreply.github.com> Date: Thu Nov 30 22:53:10 2023 +0800 Merge pull request #28 from gitlyp/feature/frontEndSearch feature: 新增前端搜索功能 commit ae310fc2484e342f2847fe9c0ec7ecd848c3388b Author: Sun <95302870@qq.com> Date: Thu Nov 30 12:02:43 2023 +0800 更新构建文件 commit c0838ccec93685e359c68f263067722ca449c728 Merge: 57f54bd a62a4f5 Author: Sun <95302870@qq.com> Date: Thu Nov 30 10:40:29 2023 +0800 Merge branch 'master' into dev commit 74bc8c05d1f88f7536d350be53060775632e7b9b Author: Rock.L <redrockfly@outlook.com> Date: Wed Nov 29 18:03:47 2023 +0800 feature: 新增前端搜索功能 ts编译报错解决 commit 57f54bd034e6f039a1b1f290448b15aa21909050 Merge: 4443f7c 3154b67 Author: Sun <95302870@qq.com> Date: Wed Nov 29 10:51:32 2023 +0800 Merge branch 'master' into dev commit ca15d154604bdb908dab06e8a17eaa833161c403 Author: Rock.L <redrockfly@outlook.com> Date: Wed Nov 29 10:35:13 2023 +0800 feature: 新增前端搜索功能 fix:排序时禁用前端搜索功能 commit 68347888c481ae27c6cc3e66752ca073fbac393e Author: Rock.L <redrockfly@outlook.com> Date: Tue Nov 28 23:31:08 2023 +0800 feature: 新增前端搜索功能 新增前端搜索功能 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的位置
1
.gitignore
vendored
@ -27,6 +27,7 @@ coverage
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.pnpm-store
|
||||
|
||||
# Environment variables files
|
||||
/service/.env
|
||||
|
2
.vscode/settings.json
vendored
@ -2,7 +2,7 @@
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
|
22
Dockerfile
@ -1,6 +1,9 @@
|
||||
# build front-end
|
||||
# build frontend
|
||||
FROM node AS web_image
|
||||
|
||||
# 华为源
|
||||
# RUN npm config set registry https://repo.huaweicloud.com/repository/npm/
|
||||
|
||||
RUN npm install pnpm -g
|
||||
|
||||
WORKDIR /build
|
||||
@ -16,17 +19,22 @@ COPY . /build
|
||||
RUN pnpm run build
|
||||
|
||||
# build backend
|
||||
FROM golang:1.21-alpine as server_image
|
||||
# 最新alpine3.19导致sqlite3编译失败(https://github.com/mattn/go-sqlite3/issues/1164,
|
||||
# 临时解决方案:https://github.com/mattn/go-sqlite3/pull/1177)
|
||||
# sun-panel暂时解决方案使用golang:1.21-alpine3.18(因旧版本使用没问题,短期内较稳定)
|
||||
FROM golang:1.21-alpine3.18 as server_image
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY ./service .
|
||||
|
||||
RUN apk add --no-cache bash curl gcc git go musl-dev
|
||||
# 中国国内源
|
||||
# RUN sed -i "s@dl-cdn.alpinelinux.org@mirrors.aliyun.com@g" /etc/apk/repositories \
|
||||
# && go env -w GOPROXY=https://goproxy.cn,direct
|
||||
|
||||
RUN apk add --no-cache bash curl gcc git musl-dev
|
||||
|
||||
# 执行指令 关闭链接确认
|
||||
RUN go env -w GO111MODULE=on \
|
||||
&& go env -w GOPROXY=https://goproxy.cn,direct \
|
||||
&& export PATH=$PATH:/go/bin \
|
||||
&& go install -a -v github.com/go-bindata/go-bindata/...@latest \
|
||||
&& go install -a -v github.com/elazarl/go-bindata-assetfs/...@latest \
|
||||
@ -34,6 +42,7 @@ RUN go env -w GO111MODULE=on \
|
||||
&& go build -o sun-panel --ldflags="-X sun-panel/global.RUNCODE=release -X sun-panel/global.ISDOCKER=docker" main.go
|
||||
|
||||
|
||||
|
||||
# run_image
|
||||
FROM alpine
|
||||
|
||||
@ -43,6 +52,9 @@ COPY --from=web_image /build/dist /app/web
|
||||
|
||||
COPY --from=server_image /build/sun-panel /app/sun-panel
|
||||
|
||||
# 中国国内源
|
||||
# RUN sed -i "s@dl-cdn.alpinelinux.org@mirrors.aliyun.com@g" /etc/apk/repositories
|
||||
|
||||
RUN apk add --no-cache bash ca-certificates su-exec tzdata \
|
||||
&& chmod +x ./sun-panel \
|
||||
&& ./sun-panel -config
|
||||
|
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 红烧猎人
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
94
README.md
@ -4,7 +4,7 @@
|
||||
|
||||
# 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>
|
||||
<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">bilibili</a> | <a href="https://www.youtube.com/channel/UCKwbFmKU25R602z6P2fgPYg">YouTube</a>
|
||||
|
||||
一个服务器、NAS导航面板、Homepage、浏览器首页。
|
||||
|
||||
@ -23,18 +23,14 @@
|
||||
- 支持网页内置小窗口打开(部分网站屏蔽此功能)
|
||||
- 占用资源小
|
||||
|
||||
## 🧊 最新完整文档(DOC)
|
||||
|
||||
[最新完整文档(DOC)](https://sun-panel-doc.enianteam.com/)
|
||||
|
||||
|
||||
## 🎨 演示(demo)
|
||||
|
||||
演示站说明:
|
||||
1. 服务器资源有限请不要上传特大图片文件
|
||||
2. 方便他人访问,请不要修改主账号密码
|
||||
3. 数据不定期清理,请不要作为主力导航页使用
|
||||
4. 请不要放违规内容
|
||||
|
||||
演示地址:[点此跳转](http://sunpaneldemo.enianteam.com/#/)
|
||||
|
||||
账号密码: [默认账号密码](#默认账号密码)
|
||||
账号:admin@sun.cc 密码:12345678
|
||||
[查看演示站](https://sun-panel-doc.enianteam.com/introduce/demo_site.html)
|
||||
|
||||
## 🐳 联系作者&交流群
|
||||
开发者:**[红烧猎人](https://blog.enianteam.com/u/sun/content/11)**
|
||||
@ -45,19 +41,21 @@
|
||||
|
||||
## 🍵 打赏
|
||||
|
||||
开源不易,如果该项目对您有帮助,您可以选择对我打赏[[点此去打赏页面](./doc/donate.md)],在寒冷的冬天让我喝上一杯热乎乎的奶茶❤️。
|
||||
> 开源开发不易,如果觉得我的项目有帮到你,欢迎给我[打赏](./doc/donate.md)或者请我喝个奶茶☕(如果可以备注下您的昵称或者名字),你的支持就是我的动力,谢谢。
|
||||
|
||||
## 🫓 后面想做的事
|
||||
| | |
|
||||
| ------------ | ------------ |
|
||||
| <img height="300" src="./doc/images/donate/weixin.png"/> | <img height="300" src="./doc/images/donate/alipay.png" /> |
|
||||
|
||||
先画个饼
|
||||
## 🫓 TODO
|
||||
|
||||
- [x] 分组,拖拽排序
|
||||
- [ ] 导入导出功能
|
||||
- [ ] 增加访客账号
|
||||
- [x] 导入导出功能
|
||||
- [x] 增加访客账号
|
||||
- [x] 帐号解除邮箱限制
|
||||
- [x] 对上传的文件管理(针对账户增强重复利用,节省空间)
|
||||
- [ ] 用户自定义搜索框搜索引擎
|
||||
- [ ] 搜索框样式自定义(背景颜色,文字颜色)
|
||||
- [ ] 帐号解除邮箱限制
|
||||
- [ ] 对上传的文件管理(针对账户增强重复利用,节省空间)
|
||||
- [ ] 多国语言支持
|
||||
- [ ] 服务器监控
|
||||
- [ ] docker管理器
|
||||
@ -131,12 +129,12 @@
|
||||
|/app/runtime|运行日志(不推荐挂载)|
|
||||
|
||||
1. 拉取镜像
|
||||
```
|
||||
```sh
|
||||
docker pull hslr/sun-panel
|
||||
```
|
||||
|
||||
2. 直接下载运行
|
||||
```
|
||||
```sh
|
||||
docker run -d --restart=always -p 3002:3002 \
|
||||
-v ~/docker_data/sun-panel/conf:/app/conf \
|
||||
-v ~/docker_data/sun-panel/uploads:/app/uploads \
|
||||
@ -148,55 +146,13 @@ hslr/sun-panel
|
||||
|
||||
### 自编译运行
|
||||
|
||||
#### 前端
|
||||
```
|
||||
# 开发运行
|
||||
pnpm dev
|
||||
|
||||
# 编译打包(打包后生成dist目录,若需要结合后端使用请改成web)
|
||||
pnpm build
|
||||
```
|
||||
|
||||
#### 后端
|
||||
|
||||
1.正式编译程序前先进入`service`
|
||||
|
||||
2.按照[静态资源编译教程](./service/assets/readme.md)编译后端静态文件
|
||||
|
||||
3.正式编译
|
||||
```
|
||||
# 进入后端项目
|
||||
cd service
|
||||
|
||||
# 开发运行
|
||||
go run main.go
|
||||
|
||||
# 编译打包
|
||||
go build -o sun-panel main.go
|
||||
```
|
||||
|
||||
#### docker windows本地开发编译运行
|
||||
|
||||
```
|
||||
// 编译
|
||||
docker build -t sun-panel .
|
||||
|
||||
// 运行 D:\docker\data\sun-panel 为本地运行的路径
|
||||
docker run --rm -d -p 3003:3002 -v D:\docker\data\sun-panel\conf:/app/conf -v D:\docker\data\sun-panel\runtime:/app/runtime -v D:\docker\data\sun-panel\uploads:/app/uploads -v D:\docker\data\sun-panel\database:/app/database --name sun-panel sun-panel
|
||||
```
|
||||
|
||||
## 🎉 图标、壁纸网站推荐
|
||||
|
||||
图标:
|
||||
- [阿里巴巴矢量图标](https://www.iconfont.cn/)
|
||||
- [Icons8](https://icons8.com/)
|
||||
- [Vectorjunky](https://www.iconfinder.com/)
|
||||
|
||||
壁纸:
|
||||
- [Wallhaven](https://wallhaven.cc/)
|
||||
- [Wallpaperscraft](https://wallpaperscraft.com/)
|
||||
- [Wallpaper Abyss](https://wall.alphacoders.com/)
|
||||
[请参考完整文档](https://sun-panel-doc.enianteam.com/deploy/compile.html)
|
||||
|
||||
## ❤️ 感谢
|
||||
|
||||
- [Roc](https://github.com/RocCheng)提供自动构建多平台docker镜像[方案](https://github.com/hslr-s/sun-panel/issues/9#issuecomment-1817433439)
|
||||
- [Roc](https://github.com/RocCheng)提供自动构建多平台docker镜像[方案](https://github.com/hslr-s/sun-panel/issues/9#issuecomment-1817433439)
|
||||
- [jackloves111](https://github.com/jackloves111)帮助构建基础文档
|
||||
|
||||
|
||||
## LICENSE
|
||||
[MIT](./LICENSE)
|
72
build.sh
@ -5,8 +5,9 @@ REPO=$(
|
||||
pwd
|
||||
)
|
||||
COMMIT_SHA=$(git rev-parse --short HEAD)
|
||||
VERSION=$(git describe --tags)
|
||||
# VERSION="0.1.1"
|
||||
# VERSION=$(git describe --tags)
|
||||
VERSION="v${cut -d '|' -f 2 ./service/assets/version}"
|
||||
LATEST_TAG=$(git describe --tags --abbrev=0)
|
||||
FRONTEND="false"
|
||||
BINARY="false"
|
||||
RELEASE="false"
|
||||
@ -18,6 +19,7 @@ debugInfo() {
|
||||
echo "Release: $RELEASE"
|
||||
echo "Version: $VERSION"
|
||||
echo "Commit: $COMMIT_SHA"
|
||||
echo "LATEST_TAG: $LATEST_TAG"
|
||||
}
|
||||
|
||||
buildFrontend() {
|
||||
@ -61,6 +63,8 @@ _build() {
|
||||
|
||||
if [ -n "$VERSION" ]; then
|
||||
outPath="sun-panel_${VERSION}_${os}_${arch}"
|
||||
elif [ -n "$LATEST_TAG" ]; then
|
||||
outPath="sun-panel_${LATEST_TAG}_${os}_${arch}"
|
||||
else
|
||||
outPath="sun-panel_${COMMIT_SHA}_${os}_${arch}"
|
||||
fi
|
||||
@ -82,6 +86,67 @@ _build() {
|
||||
rm -rf "${pathRelease}/${outPath}"
|
||||
}
|
||||
|
||||
# 定义函数BuildReleaseLinuxMusl,用于构建正式版Linux-musl平台的二进制文件(参考Alist构建方案)
|
||||
buildReleaseLinuxMusl() {
|
||||
cd $REPO/service
|
||||
ldflags="-X sun-panel/global.RUNCODE=release"
|
||||
pathRelease=$REPO/release
|
||||
# 清理.git目录,创建build目录,并下载交叉编译工具
|
||||
# rm -rf .git/
|
||||
# mkdir -p "build"
|
||||
muslflags="--extldflags '-static -fpic' $ldflags"
|
||||
BASE="https://musl.nn.ci/"
|
||||
# FILES=(x86_64-linux-musl-cross aarch64-linux-musl-cross mips-linux-musl-cross mips64-linux-musl-cross mips64el-linux-musl-cross mipsel-linux-musl-cross powerpc64le-linux-musl-cross s390x-linux-musl-cross)
|
||||
FILES=(x86_64-linux-musl-cross)
|
||||
for i in "${FILES[@]}"; do
|
||||
url="${BASE}${i}.tgz"
|
||||
curl -L -o "${i}.tgz" "${url}"
|
||||
tar xf "${i}.tgz" --strip-components 1 -C /usr/local
|
||||
rm -f "${i}.tgz"
|
||||
done
|
||||
# OS_ARCHES=(linux-musl-amd64 linux-musl-arm64 linux-musl-mips linux-musl-mips64 linux-musl-mips64le linux-musl-mipsle linux-musl-ppc64le linux-musl-s390x)
|
||||
# CGO_ARGS=(x86_64-linux-musl-gcc aarch64-linux-musl-gcc mips-linux-musl-gcc mips64-linux-musl-gcc mips64el-linux-musl-gcc mipsel-linux-musl-gcc powerpc64le-linux-musl-gcc s390x-linux-musl-gcc)
|
||||
|
||||
# 暂时仅编译amd64
|
||||
OS_ARCHES=(linux-musl-amd64)
|
||||
CGO_ARGS=(x86_64-linux-musl-gcc)
|
||||
|
||||
|
||||
for i in "${!OS_ARCHES[@]}"; do
|
||||
os_arch=${OS_ARCHES[$i]}
|
||||
cgo_cc=${CGO_ARGS[$i]}
|
||||
echo building for ${os_arch}
|
||||
export GOOS=${os_arch%%-*}
|
||||
export GOARCH=${os_arch##*-}
|
||||
export CC=${cgo_cc}
|
||||
export CGO_ENABLED=1
|
||||
|
||||
if [ -n "$VERSION" ]; then
|
||||
outPath="sun-panel_${VERSION}_${GOOS}_musl_${GOARCH}"
|
||||
elif [ -n "$LATEST_TAG" ]; then
|
||||
outPath="sun-panel_${LATEST_TAG}_${GOOS}_musl_${GOARCH}"
|
||||
else
|
||||
outPath="sun-panel_${COMMIT_SHA}_${GOOS}_musl_${GOARCH}"
|
||||
fi
|
||||
|
||||
outname="${pathRelease}/${outPath}/sun-panel"
|
||||
|
||||
go build -o "${outname}" -ldflags="$muslflags" main.go
|
||||
# go build -o "${outname}" -ldflags="$muslflags" -tags=jsoniter main.go
|
||||
done
|
||||
|
||||
cd $pathRelease
|
||||
# copy front file
|
||||
cp -r "${REPO}/dist" "${pathRelease}/${outPath}/web"
|
||||
|
||||
echo "Release ${outPath}"
|
||||
|
||||
mv $outname $outPath/sun-panel
|
||||
tar -zcvf "${pathRelease}/${outPath}.tar.gz" $outPath
|
||||
|
||||
rm -rf "${pathRelease}/${outPath}"
|
||||
}
|
||||
|
||||
release() {
|
||||
cd $REPO/service
|
||||
## List of architectures and OS to test coss compilation.
|
||||
@ -91,6 +156,9 @@ release() {
|
||||
for each_osarch in ${SUPPORTED_OSARCH}; do
|
||||
_build "${each_osarch}"
|
||||
done
|
||||
|
||||
# 临时方案解决centos无法运行的问题
|
||||
buildReleaseLinuxMusl
|
||||
}
|
||||
|
||||
usage() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
> 开源不易,如果觉得我的项目有帮到你,欢迎给我打赏或者请我喝个奶茶☕,打赏不准超过你工资的一半。你的支持就是我的动力,谢谢。
|
||||
> 开源不易,如果觉得我的项目有帮到你,欢迎给我打赏或者请我喝个奶茶☕(如果可以备注下您的昵称或者名字),打赏不准超过你工资的一半。你的支持就是我的动力,谢谢。
|
||||
|
||||
| | |
|
||||
| ------------ | ------------ |
|
||||
|
@ -29,7 +29,7 @@
|
||||
"katex": "^0.16.4",
|
||||
"markdown-it": "^13.0.1",
|
||||
"moment": "^2.29.4",
|
||||
"naive-ui": "^2.34.3",
|
||||
"naive-ui": "^2.36.0",
|
||||
"pinia": "^2.0.33",
|
||||
"vue": "^3.2.47",
|
||||
"vue-draggable-plus": "^0.2.6",
|
||||
@ -62,6 +62,7 @@
|
||||
"typescript": "~4.9.5",
|
||||
"vite": "^4.2.0",
|
||||
"vite-plugin-pwa": "^0.14.4",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vue-tsc": "^1.2.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
3562
pnpm-lock.yaml
generated
@ -14,3 +14,11 @@ type ItemIconSaveSortRequest struct {
|
||||
SortItems []commonApiStructs.SortRequestItem `json:"sortItems"`
|
||||
ItemIconGroupId uint `json:"itemIconGroupId"`
|
||||
}
|
||||
|
||||
type ItemIconGetSiteFaviconReq struct {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type ItemIconGetSiteFaviconResp struct {
|
||||
IconUrl string `json:"iconUrl"`
|
||||
}
|
||||
|
@ -21,6 +21,15 @@ type PageLimitVerify struct {
|
||||
Limit int64
|
||||
}
|
||||
|
||||
const (
|
||||
VISIT_MODE_LOGIN = iota
|
||||
VISIT_MODE_PUBLIC
|
||||
)
|
||||
|
||||
const (
|
||||
GIN_GET_VISIT_MODE = "VISIT_MODE"
|
||||
)
|
||||
|
||||
// 验证输入是否有效并返回错误
|
||||
func validateInputStruct(params interface{}) (errMsg string, err error) {
|
||||
var validate = validator.New()
|
||||
@ -79,6 +88,16 @@ func GetCurrentUserInfo(c *gin.Context) (userInfo models.User, exist bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前访问模式
|
||||
func GetCurrentVisitMode(c *gin.Context) (visitMode int) {
|
||||
if value, exist := c.Get(GIN_GET_VISIT_MODE); exist {
|
||||
if v, ok := value.(int); ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 验证器验证
|
||||
func VerificationCheck(verificationId, vCode string) (errCode int, verificationIdRes string) {
|
||||
|
||||
|
74
service/api/api_v1/middleware/PublicModeInterceptor.go
Normal file
@ -0,0 +1,74 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cmn/systemSetting"
|
||||
"sun-panel/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 公开访问模式(访客模式)
|
||||
// [有token将自动登录,无token/过期将使用公开账号,不可以与LoginInterceptor一起使用]
|
||||
func PublicModeInterceptor(c *gin.Context) {
|
||||
|
||||
// 获得token
|
||||
cToken := c.GetHeader("token")
|
||||
token := ""
|
||||
|
||||
// 没有token信息视为未登录
|
||||
if cToken != "" {
|
||||
var ok bool
|
||||
token, ok = global.CUserToken.Get(cToken)
|
||||
if ok && token != "" {
|
||||
// 直接返回缓存的用户信息
|
||||
if userInfo, success := global.UserToken.Get(token); success {
|
||||
global.Logger.Debug("缓存的用户TOKEN:", token)
|
||||
c.Set("userInfo", userInfo)
|
||||
return
|
||||
} else {
|
||||
global.Logger.Debug("数据库查询TOKEN:", token)
|
||||
mUser := models.User{}
|
||||
// 去库中查询是否存在该用户
|
||||
if info, err := mUser.GetUserInfoByToken(token); err == nil && info.Token != "" && info.ID != 0 {
|
||||
global.Logger.Debug("数据库查询用户:", info.ID)
|
||||
// 通过 设置当前用户信息
|
||||
global.UserToken.SetDefault(info.Token, info)
|
||||
global.CUserToken.SetDefault(cToken, token)
|
||||
c.Set("userInfo", info)
|
||||
return
|
||||
} else {
|
||||
global.Logger.Debug("数据库查询用户失败", token)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
global.Logger.Debug("token为空或者不OK")
|
||||
}
|
||||
} else {
|
||||
global.Logger.Debug("cToken不存在")
|
||||
}
|
||||
|
||||
// 获取公开账号的信息
|
||||
var userId *uint
|
||||
if err := global.SystemSetting.GetValueByInterface(systemSetting.PANEL_PUBLIC_USER_ID, &userId); err == nil && userId != nil {
|
||||
userInfo := models.User{}
|
||||
if err := global.Db.First(&userInfo, "id=?", userId).Error; err != nil {
|
||||
apiReturn.ErrorCode(c, 1001, global.Lang.Get("login.err_token_expire"), nil)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
global.Logger.Debug("访客用户TOKEN:", token)
|
||||
global.Logger.Debug("访客用户ID:", userInfo.ID)
|
||||
c.Set("userInfo", userInfo)
|
||||
c.Set(base.GIN_GET_VISIT_MODE, base.VISIT_MODE_PUBLIC)
|
||||
return
|
||||
} else {
|
||||
global.Logger.Debug("访客用户不存在:", userId, " ", token)
|
||||
apiReturn.ErrorCode(c, 1001, global.Lang.Get("login.err_token_expire"), nil)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package panel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sun-panel/api/api_v1/common/apiData/commonApiStructs"
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
@ -49,32 +50,45 @@ 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 {
|
||||
err := global.Db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Order("sort ,created_at").Where("user_id=?", userInfo.ID).Find(&groups).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// 判断分组是否为空,为空将自动创建默认分组
|
||||
if len(groups) == 0 {
|
||||
defaultGroup := models.ItemIconGroup{
|
||||
Title: "APP",
|
||||
UserId: userInfo.ID,
|
||||
Icon: "material-symbols:ad-group-outline",
|
||||
}
|
||||
if err := tx.Create(&defaultGroup).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// 并将当前账号下所有无分组的图标更新到当前组
|
||||
if err := tx.Model(&models.ItemIcon{}).Where("user_id=?", userInfo.ID).Update("item_icon_group_id", defaultGroup.ID).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("创建了默认分组", defaultGroup.ID)
|
||||
|
||||
groups = append(groups, defaultGroup)
|
||||
}
|
||||
|
||||
// 返回 nil 提交事务
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
} else {
|
||||
apiReturn.SuccessListData(c, groups, 0)
|
||||
}
|
||||
|
||||
// 判断分组是否为空,为空将自动创建默认分组
|
||||
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) {
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/siteFavicon"
|
||||
"sun-panel/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -56,6 +57,34 @@ func (a *ItemIcon) Edit(c *gin.Context) {
|
||||
apiReturn.SuccessData(c, req)
|
||||
}
|
||||
|
||||
// 添加多个图标
|
||||
func (a *ItemIcon) AddMultiple(c *gin.Context) {
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
// type Request
|
||||
req := []models.ItemIcon{}
|
||||
|
||||
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(req); i++ {
|
||||
if req[i].ItemIconGroupId == 0 {
|
||||
apiReturn.Error(c, "分组为必填项")
|
||||
return
|
||||
}
|
||||
req[i].UserId = userInfo.ID
|
||||
// json转字符串
|
||||
if j, err := json.Marshal(req[i].Icon); err == nil {
|
||||
req[i].IconJson = string(j)
|
||||
}
|
||||
}
|
||||
|
||||
global.Db.Create(&req)
|
||||
|
||||
apiReturn.SuccessData(c, req)
|
||||
}
|
||||
|
||||
// // 获取详情
|
||||
// func (a *ItemIcon) GetInfo(c *gin.Context) {
|
||||
// req := systemApiStructs.AiDrawGetInfoReq{}
|
||||
@ -157,3 +186,19 @@ func (a *ItemIcon) SaveSort(c *gin.Context) {
|
||||
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
|
||||
func (a *ItemIcon) GetSiteFavicon(c *gin.Context) {
|
||||
req := panelApiStructs.ItemIconGetSiteFaviconReq{}
|
||||
|
||||
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
resp := panelApiStructs.ItemIconGetSiteFaviconResp{}
|
||||
if iconUrl, ok := siteFavicon.GetOneFaviconURL(req.Url); ok {
|
||||
resp.IconUrl = iconUrl
|
||||
apiReturn.SuccessData(c, resp)
|
||||
return
|
||||
}
|
||||
apiReturn.Error(c, "acquisition failed")
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
package panel
|
||||
|
||||
import (
|
||||
"math"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cmn"
|
||||
"sun-panel/lib/cmn/systemSetting"
|
||||
"sun-panel/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -17,6 +20,10 @@ import (
|
||||
type UsersApi struct {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrUsersApiAtLeastKeepOne = errors.New("at least keep one")
|
||||
)
|
||||
|
||||
func (a UsersApi) Create(c *gin.Context) {
|
||||
param := models.User{}
|
||||
if err := c.ShouldBindBodyWith(¶m, binding.JSON); err != nil {
|
||||
@ -29,14 +36,20 @@ func (a UsersApi) Create(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
param.Username = strings.Trim(param.Username, " ")
|
||||
if len(param.Username) < 5 {
|
||||
apiReturn.ErrorParamFomat(c, "账号不得少于5个字符")
|
||||
return
|
||||
}
|
||||
|
||||
mUser := models.User{
|
||||
Username: param.Username,
|
||||
Username: strings.Trim(param.Username, " "),
|
||||
Password: cmn.PasswordEncryption(param.Password),
|
||||
Name: param.Username,
|
||||
Name: param.Name,
|
||||
HeadImage: param.HeadImage,
|
||||
Status: 1,
|
||||
Role: 1, // 固定管理员
|
||||
Mail: param.Username,
|
||||
Role: param.Role,
|
||||
// Mail: param.Username, 不再保存邮箱账号字段
|
||||
}
|
||||
|
||||
// 验证账号是否存在
|
||||
@ -66,16 +79,16 @@ func (a UsersApi) Deletes(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
// 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{}
|
||||
@ -107,10 +120,21 @@ func (a UsersApi) Deletes(c *gin.Context) {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// 验证是否还存在管理员
|
||||
var count int64
|
||||
if err := tx.Model(&models.User{}).Where("role=?", 1).Count(&count).Error; err != nil {
|
||||
return err
|
||||
} else if count == 0 {
|
||||
return ErrUsersApiAtLeastKeepOne
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if txErr != nil {
|
||||
if txErr == ErrUsersApiAtLeastKeepOne {
|
||||
apiReturn.Error(c, "至少要保留一个平台管理")
|
||||
return
|
||||
} else if txErr != nil {
|
||||
apiReturn.ErrorDatabase(c, txErr.Error())
|
||||
return
|
||||
}
|
||||
@ -130,19 +154,26 @@ func (a UsersApi) Update(c *gin.Context) {
|
||||
param.Password = "-" // 修改不允许修改密码,为了验证通过
|
||||
}
|
||||
|
||||
param.Mail = param.Username // 密码邮箱同时修改
|
||||
// param.Mail = param.Username // 密码邮箱同时修改
|
||||
if errMsg, err := base.ValidateInputStruct(param); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
allowField := []string{"Username", "Name", "Mail", "Token"}
|
||||
param.Username = strings.Trim(param.Username, " ")
|
||||
if len(param.Username) < 5 {
|
||||
apiReturn.ErrorParamFomat(c, "账号不得少于5个字符")
|
||||
return
|
||||
}
|
||||
|
||||
allowField := []string{"Username", "Name", "Mail", "Token", "Role"}
|
||||
|
||||
// 密码不为默认“-”空,修改密码
|
||||
if param.Password != "-" {
|
||||
param.Password = cmn.PasswordEncryption(param.Password)
|
||||
allowField = append(allowField, "Password")
|
||||
}
|
||||
|
||||
mUser := models.User{}
|
||||
|
||||
userInfo := models.User{}
|
||||
@ -214,3 +245,44 @@ func (a UsersApi) GetList(c *gin.Context) {
|
||||
|
||||
apiReturn.SuccessListData(c, list, count)
|
||||
}
|
||||
|
||||
func (a UsersApi) SetPublicVisitUser(c *gin.Context) {
|
||||
type Req struct {
|
||||
UserId *uint `json:"userId"`
|
||||
}
|
||||
|
||||
req := Req{}
|
||||
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if req.UserId != nil {
|
||||
userInfo := models.User{}
|
||||
if err := global.Db.First(&userInfo, "id=?", req.UserId).Error; err != nil {
|
||||
fmt.Println(err, userInfo)
|
||||
apiReturn.ErrorDataNotFound(c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := global.SystemSetting.Set(systemSetting.PANEL_PUBLIC_USER_ID, req.UserId); err != nil {
|
||||
apiReturn.Error(c, "set fail")
|
||||
return
|
||||
}
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
|
||||
func (a UsersApi) GetPublicVisitUser(c *gin.Context) {
|
||||
var userId *uint
|
||||
if err := global.SystemSetting.GetValueByInterface(systemSetting.PANEL_PUBLIC_USER_ID, &userId); err == nil && userId != nil {
|
||||
userInfo := models.User{}
|
||||
if err := global.Db.First(&userInfo, "id=?", userId).Error; err == nil {
|
||||
apiReturn.SuccessData(c, userInfo)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 没有此配置
|
||||
apiReturn.ErrorDataNotFound(c)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"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"
|
||||
@ -13,6 +14,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type FileApi struct{}
|
||||
@ -26,8 +29,18 @@ func (a *FileApi) UploadImg(c *gin.Context) {
|
||||
return
|
||||
} else {
|
||||
fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||
if fileExt != ".png" && fileExt != ".jpg" && fileExt != ".gif" && fileExt != ".jpeg" && fileExt != ".webp" && fileExt != ".svg" {
|
||||
apiReturn.Error(c, "上传失败!只允许png,jpg,gif,jpeg,svg文件")
|
||||
agreeExts := []string{
|
||||
".png",
|
||||
".jpg",
|
||||
".gif",
|
||||
".jpeg",
|
||||
".webp",
|
||||
".svg",
|
||||
".ico",
|
||||
}
|
||||
|
||||
if !cmn.InArray(agreeExts, fileExt) {
|
||||
apiReturn.Error(c, "上传失败!只允许png,jpg,gif,jpeg,svg,ico文件")
|
||||
return
|
||||
}
|
||||
fileName := cmn.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
|
||||
@ -85,3 +98,56 @@ func (a *FileApi) UploadFiles(c *gin.Context) {
|
||||
"errFiles": errFiles,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *FileApi) GetList(c *gin.Context) {
|
||||
list := []models.File{}
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
var count int64
|
||||
if err := global.Db.Order("created_at desc").Find(&list, "user_id=?", userInfo.ID).Count(&count).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data := []map[string]interface{}{}
|
||||
for _, v := range list {
|
||||
data = append(data, map[string]interface{}{
|
||||
"src": v.Src[1:],
|
||||
"fileName": v.FileName,
|
||||
"id": v.ID,
|
||||
"createTime": v.CreatedAt,
|
||||
"updateTime": v.UpdatedAt,
|
||||
"path": v.Src,
|
||||
})
|
||||
}
|
||||
apiReturn.SuccessListData(c, data, count)
|
||||
}
|
||||
|
||||
func (a *FileApi) Deletes(c *gin.Context) {
|
||||
req := commonApiStructs.RequestDeleteIds[uint]{}
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
global.Db.Transaction(func(tx *gorm.DB) error {
|
||||
files := []models.File{}
|
||||
|
||||
if err := tx.Order("created_at desc").Find(&files, "user_id=? AND id in ?", userInfo.ID, req.Ids).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range files {
|
||||
os.Remove(v.Src)
|
||||
}
|
||||
|
||||
if err := tx.Order("created_at desc").Delete(&files, "user_id=? AND id in ?", userInfo.ID, req.Ids).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
apiReturn.Success(c)
|
||||
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ package system
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"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/lib/captcha"
|
||||
"sun-panel/lib/cmn"
|
||||
"sun-panel/lib/cmn/systemSetting"
|
||||
"sun-panel/lib/mail"
|
||||
@ -50,30 +50,13 @@ func (l LoginApi) Login(c *gin.Context) {
|
||||
settings := systemSetting.ApplicationSetting{}
|
||||
global.SystemSetting.GetValueByInterface("system_application", &settings)
|
||||
|
||||
// 验证验证码
|
||||
if settings.Login.LoginCaptcha {
|
||||
var captchaId string
|
||||
var err error
|
||||
|
||||
// 获取captchaId
|
||||
if captchaId, err = captcha.CaptchaGetIdByCookieHeader(c, "CaptchaId"); err != nil {
|
||||
apiReturn.Error(c, global.Lang.Get("login.err_captcha_check_fail"))
|
||||
return
|
||||
}
|
||||
|
||||
// 验证码错误
|
||||
if !captcha.CaptchaVerifyHandle(captchaId, param.VCode) {
|
||||
apiReturn.Error(c, global.Lang.Get("captcha.api_captcha_fail"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
mUser := models.User{}
|
||||
var (
|
||||
err error
|
||||
info models.User
|
||||
)
|
||||
bToken := ""
|
||||
param.Username = strings.TrimSpace(param.Username)
|
||||
if info, err = mUser.GetUserInfoByUsernameAndPassword(param.Username, cmn.PasswordEncryption(param.Password)); err != nil {
|
||||
// 未找到记录 账号或密码错误
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
@ -109,6 +92,8 @@ func (l LoginApi) Login(c *gin.Context) {
|
||||
}
|
||||
info.Token = bToken
|
||||
}
|
||||
info.Password = ""
|
||||
info.ReferralCode = ""
|
||||
|
||||
// global.UserToken.SetDefault(bToken, info)
|
||||
cToken := uuid.NewString() + "-" + cmn.Md5(cmn.Md5("userId"+strconv.Itoa(int(info.ID))))
|
||||
|
@ -27,6 +27,21 @@ func (a *UserApi) GetInfo(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (a *UserApi) GetAuthInfo(c *gin.Context) {
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
visitMode := base.GetCurrentVisitMode(c)
|
||||
user := models.User{}
|
||||
user.ID = userInfo.ID
|
||||
user.HeadImage = userInfo.HeadImage
|
||||
user.Name = userInfo.Name
|
||||
user.Role = userInfo.Role
|
||||
user.Username = userInfo.Username
|
||||
apiReturn.SuccessData(c, gin.H{
|
||||
"user": user,
|
||||
"visitMode": visitMode,
|
||||
})
|
||||
}
|
||||
|
||||
// 修改资料
|
||||
func (a *UserApi) UpdateInfo(c *gin.Context) {
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
|
@ -2,7 +2,7 @@
|
||||
# Basic configuration
|
||||
# ======================
|
||||
[base]
|
||||
# Web run port. Default:9090
|
||||
# Web run port. Default:3002
|
||||
http_port=3002
|
||||
# Database driver [mysql/sqlite(Default)]
|
||||
database_drive=sqlite
|
||||
@ -10,7 +10,8 @@ database_drive=sqlite
|
||||
cache_drive=memory
|
||||
# Queue driver [redis/memory(Default)]
|
||||
queue_drive=memory
|
||||
# File upload path.
|
||||
# File cache path (Please start with the current path './')
|
||||
# Warning: The files that have been uploaded after the modification cannot be accessed
|
||||
source_path=./uploads
|
||||
# File cache path.
|
||||
source_temp_path=./runtime/temp
|
||||
|
@ -1 +1 @@
|
||||
3|1.1.0
|
||||
7|1.2.0
|
@ -1,8 +1,9 @@
|
||||
module sun-panel
|
||||
|
||||
go 1.19
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.8.1
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/go-playground/locales v0.14.1
|
||||
@ -19,11 +20,12 @@ require (
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gorm.io/driver/mysql v1.5.0
|
||||
gorm.io/driver/sqlite v1.5.0
|
||||
gorm.io/gorm v1.25.0
|
||||
gorm.io/driver/sqlite v1.5.4
|
||||
gorm.io/gorm v1.25.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||
github.com/bytedance/sonic v1.8.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
@ -41,7 +43,7 @@ require (
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.18 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
|
@ -1,3 +1,7 @@
|
||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
@ -62,8 +66,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
@ -109,6 +113,7 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
||||
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
gitlab.com/tingshuo/go-diskstate v0.0.0-20191211131809-ee5e7223d03c h1:oX6xxxK4o3t4Zq6LPliClUj7Owszg9eelw91nrpNLE4=
|
||||
@ -122,22 +127,50 @@ go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 h1:TbGuee8sSq15Iguxu4deQ7+Bqq/d2rsQejGcEtADAMQ=
|
||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
@ -155,9 +188,9 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.0 h1:6hSAT5QcyIaty0jfnff0z0CLDjyRgZ8mlMHLqSt7uXM=
|
||||
gorm.io/driver/mysql v1.5.0/go.mod h1:FFla/fJuCvyTi7rJQd27qlNX2v3L6deTR1GgTjSOLPo=
|
||||
gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
|
||||
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
|
||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
|
||||
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
@ -144,15 +144,15 @@ func CommandRun() {
|
||||
fmt.Println("配置文件已经创建 conf/conf.ini ", "请按照自己的需求修改")
|
||||
os.Exit(0) // 务必退出
|
||||
} else if pwd {
|
||||
// 重置密码
|
||||
|
||||
// 配置初始化
|
||||
config, _ := config.ConfigInit()
|
||||
global.Config = config
|
||||
|
||||
// 重置密码
|
||||
DatabaseConnect()
|
||||
userInfo := models.User{}
|
||||
if err := global.Db.First(&userInfo).Error; err != nil {
|
||||
if err := global.Db.Where("role=?", 1).Order("id").First(&userInfo).Error; err != nil {
|
||||
fmt.Println("ERROR", err.Error())
|
||||
os.Exit(0) // 务必退出
|
||||
}
|
||||
@ -163,6 +163,7 @@ func CommandRun() {
|
||||
Password: cmn.PasswordEncryption(newPassword),
|
||||
Token: "",
|
||||
}
|
||||
// 重置第一个管理员的密码
|
||||
if err := global.Db.Select("Password", "Token").Where("id=?", userInfo.ID).Updates(&updateInfo).Error; err != nil {
|
||||
fmt.Println("ERROR", err.Error())
|
||||
os.Exit(0) // 务必退出
|
||||
|
@ -37,12 +37,12 @@ func ConfigInit() (*iniConfig.IniConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
global.Logger.Errorln("配置文件已经自动生成'conf/conf.ini',将再次读取配置")
|
||||
global.Logger.Infoln("配置文件已经自动生成'conf/conf.ini',将再次读取配置")
|
||||
// 创建成功再次读取文件
|
||||
if configAgain, errAgain, _ := Conf(getDefaultConfig()); errAgain != nil {
|
||||
return nil, errAgain
|
||||
} else {
|
||||
global.Logger.Errorln("尝试读取配置文件'conf/conf.ini',二次读取配置文件成功")
|
||||
global.Logger.Infoln("尝试读取配置文件'conf/conf.ini',二次读取配置文件成功")
|
||||
return configAgain, nil
|
||||
}
|
||||
} else {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sun-panel/assets"
|
||||
@ -181,6 +182,18 @@ func InStringArray(arr []string, item string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func InArray[T uint | int | int8 | int64 | float32 | float64 | string](arr []T, item T) bool {
|
||||
sort.Slice(arr, func(i, j int) bool {
|
||||
return arr[i] < arr[j]
|
||||
})
|
||||
|
||||
index := sort.Search(len(arr), func(i int) bool {
|
||||
return arr[i] >= item
|
||||
})
|
||||
|
||||
return index < len(arr) && arr[index] == item
|
||||
}
|
||||
|
||||
// 从Assets文件夹中抽取文件保存到路劲
|
||||
// AssetsTakeFileToPath("config.ini", targetPath string)
|
||||
func AssetsTakeFileToPath(assetsPath, targetPath string) error {
|
||||
|
@ -14,6 +14,7 @@ const (
|
||||
SYSTEM_EMAIL = "system_email"
|
||||
DISCLAIMER = "disclaimer" // 免责声明 储存类型:字符串
|
||||
WEB_ABOUT_DESCRIPTION = "web_about_description" // 关于的描述信息
|
||||
PANEL_PUBLIC_USER_ID = "panel_public_user_id" // 公开访问模式用户id *uint|null
|
||||
)
|
||||
|
||||
type SystemSettingCache struct {
|
||||
|
78
service/lib/siteFavicon/favico.go
Normal file
@ -0,0 +1,78 @@
|
||||
package siteFavicon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
func IsHTTPURL(url string) bool {
|
||||
httpPattern := `^(http://|https://)`
|
||||
match, err := regexp.MatchString(httpPattern, url)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return match
|
||||
}
|
||||
|
||||
func GetOneFaviconURL(urlStr string) (string, bool) {
|
||||
iconURLs, err := getFaviconURL(urlStr)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
return "", false
|
||||
}
|
||||
|
||||
for _, v := range iconURLs {
|
||||
// 标准的路径地址
|
||||
if IsHTTPURL(v) {
|
||||
return v, true
|
||||
} else {
|
||||
urlInfo, _ := url.Parse(urlStr)
|
||||
fullUrl := urlInfo.Scheme + "://" + urlInfo.Host + "/" + strings.TrimPrefix(v, "/")
|
||||
return fullUrl, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func getFaviconURL(url string) ([]string, error) {
|
||||
var icons []string
|
||||
icons = make([]string, 0)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return icons, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return icons, errors.New("HTTP request failed with status code " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
||||
if err != nil {
|
||||
return icons, err
|
||||
}
|
||||
|
||||
// 查找所有link标签,筛选包含rel属性为"icon"的标签
|
||||
doc.Find("link").Each(func(i int, s *goquery.Selection) {
|
||||
rel, _ := s.Attr("rel")
|
||||
href, _ := s.Attr("href")
|
||||
|
||||
if strings.Contains(rel, "icon") && href != "" {
|
||||
// fmt.Println(href)
|
||||
icons = append(icons, href)
|
||||
}
|
||||
})
|
||||
|
||||
if len(icons) == 0 {
|
||||
return icons, fmt.Errorf("favicon not found on the page")
|
||||
}
|
||||
|
||||
return icons, nil
|
||||
}
|
@ -7,14 +7,14 @@ import (
|
||||
// 用户表
|
||||
type User struct {
|
||||
BaseModel
|
||||
Username string `gorm:"index:;index:idx_username_password,priority:1;type:varchar(50)" json:"username" validate:"required,email"` // 账号
|
||||
Password string `gorm:"index:idx_username_password;type:varchar(32)" json:"password" validate:"required"` // 密码
|
||||
Name string `gorm:"type:varchar(20)" json:"name"` // 名称
|
||||
HeadImage string `gorm:"type:varchar(200)" json:"headImage"` // 头像地址
|
||||
Status int `gorm:"type:tinyint(1)" json:"status"` // 状态 1.启用 2.停用 3.未激活
|
||||
Role int `gorm:"type:int(11)" json:"role"` // 角色 1.管理员 2.普通用户
|
||||
Mail string `gorm:"type:varchar(50)" json:"mail"` // 邮箱
|
||||
ReferralCode string `gorm:"type:varchar(10)" json:"referralCode"` // 推荐码
|
||||
Username string `gorm:"index:;index:idx_username_password,priority:1;type:varchar(50)" json:"username" validate:"required"` // 账号
|
||||
Password string `gorm:"index:idx_username_password;type:varchar(32)" json:"password" validate:"required"` // 密码
|
||||
Name string `gorm:"type:varchar(20)" json:"name"` // 名称
|
||||
HeadImage string `gorm:"type:varchar(200)" json:"headImage"` // 头像地址
|
||||
Status int `gorm:"type:tinyint(1)" json:"status"` // 状态 1.启用 2.停用 3.未激活
|
||||
Role int `gorm:"type:int(11)" json:"role"` // 角色 1.管理员 2.普通用户
|
||||
Mail string `gorm:"type:varchar(50)" json:"mail"` // 邮箱
|
||||
ReferralCode string `gorm:"type:varchar(10)" json:"referralCode"` // 推荐码
|
||||
Token string `gorm:"type:varchar(32)" json:"token"`
|
||||
|
||||
UserId uint `gorm:"-" json:"userId"`
|
||||
|
@ -16,11 +16,8 @@ func InitRouters(addr string) error {
|
||||
rootRouter := router.Group("/")
|
||||
routerGroup := rootRouter.Group("api")
|
||||
|
||||
// 管理员接口
|
||||
|
||||
// 接口
|
||||
system.Init(routerGroup)
|
||||
// admin.Init(routerGroup)
|
||||
panel.Init(routerGroup)
|
||||
openness.Init(routerGroup)
|
||||
|
||||
@ -28,13 +25,14 @@ func InitRouters(addr string) error {
|
||||
{
|
||||
webPath := "./web"
|
||||
router.StaticFile("/", webPath+"/index.html")
|
||||
router.Static("/assets", webPath+"/assets")
|
||||
router.StaticFile("/favicon.ico", webPath+"/favicon.ico")
|
||||
router.StaticFile("/favicon.svg", webPath+"/favicon.svg")
|
||||
router.Static("/assets", webPath+"/assets")
|
||||
}
|
||||
|
||||
// 上传的文件
|
||||
router.Static("/uploads", "./uploads")
|
||||
sourcePath := global.Config.GetValueString("base", "source_path")
|
||||
router.Static(sourcePath[1:], sourcePath)
|
||||
|
||||
global.Logger.Info("Sun-Panel is Started. Listening and serving HTTP on ", addr)
|
||||
return router.Run(addr)
|
||||
|
@ -12,8 +12,15 @@ func InitItemIcon(router *gin.RouterGroup) {
|
||||
r := router.Group("", middleware.LoginInterceptor)
|
||||
{
|
||||
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)
|
||||
r.POST("/panel/itemIcon/addMultiple", itemIcon.AddMultiple)
|
||||
r.POST("/panel/itemIcon/getSiteFavicon", itemIcon.GetSiteFavicon)
|
||||
}
|
||||
|
||||
// 公开模式
|
||||
rPublic := router.Group("", middleware.PublicModeInterceptor)
|
||||
{
|
||||
rPublic.POST("/panel/itemIcon/getListByGroupId", itemIcon.GetListByGroupId)
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,13 @@ func InitItemIconGroup(router *gin.RouterGroup) {
|
||||
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)
|
||||
}
|
||||
|
||||
// 公开模式
|
||||
rPublic := router.Group("", middleware.PublicModeInterceptor)
|
||||
{
|
||||
rPublic.POST("/panel/itemIconGroup/getList", itemIconGroup.GetList)
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,11 @@ func InitUserConfig(router *gin.RouterGroup) {
|
||||
r := router.Group("", middleware.LoginInterceptor)
|
||||
{
|
||||
r.POST("/panel/userConfig/set", api.Set)
|
||||
r.POST("/panel/userConfig/get", api.Get)
|
||||
}
|
||||
|
||||
// 公开模式
|
||||
rPublic := router.Group("", middleware.PublicModeInterceptor)
|
||||
{
|
||||
rPublic.POST("/panel/userConfig/get", api.Get)
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,13 @@ import (
|
||||
func InitUsersRouter(router *gin.RouterGroup) {
|
||||
userApi := api_v1.ApiGroupApp.ApiPanel.UsersApi
|
||||
|
||||
r := router.Group("", middleware.LoginInterceptor)
|
||||
rAdmin := router.Group("", middleware.LoginInterceptor, middleware.AdminInterceptor)
|
||||
{
|
||||
r.POST("panel/users/create", userApi.Create)
|
||||
r.POST("panel/users/update", userApi.Update)
|
||||
r.POST("panel/users/getList", userApi.GetList)
|
||||
r.POST("panel/users/deletes", userApi.Deletes)
|
||||
rAdmin.POST("panel/users/create", userApi.Create)
|
||||
rAdmin.POST("panel/users/update", userApi.Update)
|
||||
rAdmin.POST("panel/users/getList", userApi.GetList)
|
||||
rAdmin.POST("panel/users/deletes", userApi.Deletes)
|
||||
rAdmin.POST("panel/users/getPublicVisitUser", userApi.GetPublicVisitUser)
|
||||
rAdmin.POST("panel/users/setPublicVisitUser", userApi.SetPublicVisitUser)
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,10 @@ func InitFileRouter(router *gin.RouterGroup) {
|
||||
{
|
||||
private.POST("/file/uploadImg", FileApi.UploadImg)
|
||||
private.POST("/file/uploadFiles", FileApi.UploadFiles)
|
||||
|
||||
private.POST("/file/getList", FileApi.GetList)
|
||||
private.POST("/file/deletes", FileApi.Deletes)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,7 +10,11 @@ import (
|
||||
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)
|
||||
|
||||
// 公开模式
|
||||
rPublic := router.Group("", middleware.PublicModeInterceptor)
|
||||
{
|
||||
rPublic.POST("/system/moduleConfig/getByName", api.GetByName)
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,13 @@ func InitUserRouter(router *gin.RouterGroup) {
|
||||
api := api_v1.ApiGroupApp.ApiSystem.UserApi
|
||||
r := router.Group("", middleware.LoginInterceptor)
|
||||
r.POST("/user/getInfo", api.GetInfo)
|
||||
r.POST("/user/updatePasssword", api.UpdatePasssword)
|
||||
r.POST("/user/updatePassword", api.UpdatePasssword)
|
||||
r.POST("/user/updateInfo", api.UpdateInfo)
|
||||
r.POST("/user/getReferralCode", api.GetReferralCode)
|
||||
|
||||
// 公开模式
|
||||
rPublic := router.Group("", middleware.PublicModeInterceptor)
|
||||
{
|
||||
rPublic.POST("/user/getAuthInfo", api.GetAuthInfo)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { post } from '@/utils/request'
|
||||
|
||||
export function addMultiple<T>(req: Panel.ItemInfo[]) {
|
||||
return post<T>({
|
||||
url: '/panel/itemIcon/addMultiple',
|
||||
data: req,
|
||||
})
|
||||
}
|
||||
|
||||
export function edit<T>(req: Panel.ItemInfo) {
|
||||
return post<T>({
|
||||
url: '/panel/itemIcon/edit',
|
||||
@ -34,3 +41,10 @@ export function saveSort<T>(data: Panel.ItemIconSortRequest) {
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function getSiteFavicon<T>(url: string) {
|
||||
return post<T>({
|
||||
url: '/panel/itemIcon/getSiteFavicon',
|
||||
data: { url },
|
||||
})
|
||||
}
|
||||
|
@ -25,3 +25,16 @@ export function deletes<T>(userIds: number[]) {
|
||||
data: { userIds },
|
||||
})
|
||||
}
|
||||
|
||||
export function getPublicVisitUser<T>() {
|
||||
return post<T>({
|
||||
url: '/panel/users/getPublicVisitUser',
|
||||
})
|
||||
}
|
||||
|
||||
export function setPublicVisitUser<T>(userId: number | null) {
|
||||
return post<T>({
|
||||
url: '/panel/users/setPublicVisitUser',
|
||||
data: { userId },
|
||||
})
|
||||
}
|
||||
|
14
src/api/system/file.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { post } from '@/utils/request'
|
||||
|
||||
export function getList<T>() {
|
||||
return post<T>({
|
||||
url: '/file/getList',
|
||||
})
|
||||
}
|
||||
|
||||
export function deletes<T>(ids: number[]) {
|
||||
return post<T>({
|
||||
url: '/file/deletes',
|
||||
data: { ids },
|
||||
})
|
||||
}
|
@ -1,8 +1,14 @@
|
||||
import { post } from '@/utils/request'
|
||||
|
||||
export function getInfo<T>() {
|
||||
// export function getInfo<T>() {
|
||||
// return post<T>({
|
||||
// url: '/user/getInfo',
|
||||
// })
|
||||
// }
|
||||
|
||||
export function getAuthInfo<T>() {
|
||||
return post<T>({
|
||||
url: '/user/getInfo',
|
||||
url: '/user/getAuthInfo',
|
||||
})
|
||||
}
|
||||
|
||||
@ -11,3 +17,17 @@ export function getReferralCode<T>() {
|
||||
url: '/user/getReferralCode',
|
||||
})
|
||||
}
|
||||
|
||||
export function updateInfo<T>(name: string) {
|
||||
return post<T>({
|
||||
url: '/user/updateInfo',
|
||||
data: { name },
|
||||
})
|
||||
}
|
||||
|
||||
export function updatePassword<T>(oldPassword: string, newPassword: string) {
|
||||
return post<T>({
|
||||
url: '/user/updatePassword',
|
||||
data: { newPassword, oldPassword },
|
||||
})
|
||||
}
|
||||
|
1
src/assets/svg-icons/basil-edit-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M16.477 3.004c.167.015.24.219.12.338l-8.32 8.32a.75.75 0 0 0-.195.34l-1 3.83a.75.75 0 0 0 .915.915l3.829-1a.751.751 0 0 0 .34-.196l8.438-8.438a.198.198 0 0 1 .339.12a45.723 45.723 0 0 1-.06 10.073c-.223 1.905-1.754 3.4-3.652 3.613a47.468 47.468 0 0 1-10.461 0c-1.899-.213-3.43-1.708-3.653-3.613a45.672 45.672 0 0 1 0-10.611C3.34 4.789 4.871 3.294 6.77 3.082a47.512 47.512 0 0 1 9.707-.078Z"/><path fill="currentColor" d="M17.823 4.237a.25.25 0 0 1 .354 0l1.414 1.415a.25.25 0 0 1 0 .353L11.298 14.3a.253.253 0 0 1-.114.065l-1.914.5a.25.25 0 0 1-.305-.305l.5-1.914a.25.25 0 0 1 .065-.114l8.293-8.294Z"/></svg>
|
After Width: | Height: | Size: 722 B |
1
src/assets/svg-icons/ep-setting.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 1024 1024"><path fill="currentColor" d="M600.704 64a32 32 0 0 1 30.464 22.208l35.2 109.376c14.784 7.232 28.928 15.36 42.432 24.512l112.384-24.192a32 32 0 0 1 34.432 15.36L944.32 364.8a32 32 0 0 1-4.032 37.504l-77.12 85.12a357.12 357.12 0 0 1 0 49.024l77.12 85.248a32 32 0 0 1 4.032 37.504l-88.704 153.6a32 32 0 0 1-34.432 15.296L708.8 803.904c-13.44 9.088-27.648 17.28-42.368 24.512l-35.264 109.376A32 32 0 0 1 600.704 960H423.296a32 32 0 0 1-30.464-22.208L357.696 828.48a351.616 351.616 0 0 1-42.56-24.64l-112.32 24.256a32 32 0 0 1-34.432-15.36L79.68 659.2a32 32 0 0 1 4.032-37.504l77.12-85.248a357.12 357.12 0 0 1 0-48.896l-77.12-85.248A32 32 0 0 1 79.68 364.8l88.704-153.6a32 32 0 0 1 34.432-15.296l112.32 24.256c13.568-9.152 27.776-17.408 42.56-24.64l35.2-109.312A32 32 0 0 1 423.232 64H600.64zm-23.424 64H446.72l-36.352 113.088l-24.512 11.968a294.113 294.113 0 0 0-34.816 20.096l-22.656 15.36l-116.224-25.088l-65.28 113.152l79.68 88.192l-1.92 27.136a293.12 293.12 0 0 0 0 40.192l1.92 27.136l-79.808 88.192l65.344 113.152l116.224-25.024l22.656 15.296a294.113 294.113 0 0 0 34.816 20.096l24.512 11.968L446.72 896h130.688l36.48-113.152l24.448-11.904a288.282 288.282 0 0 0 34.752-20.096l22.592-15.296l116.288 25.024l65.28-113.152l-79.744-88.192l1.92-27.136a293.12 293.12 0 0 0 0-40.256l-1.92-27.136l79.808-88.128l-65.344-113.152l-116.288 24.96l-22.592-15.232a287.616 287.616 0 0 0-34.752-20.096l-24.448-11.904L577.344 128zM512 320a192 192 0 1 1 0 384a192 192 0 0 1 0-384m0 64a128 128 0 1 0 0 256a128 128 0 0 0 0-256"/></svg>
|
After Width: | Height: | Size: 1.6 KiB |
1
src/assets/svg-icons/fa6-solid-file-export.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.13em" height="1em" viewBox="0 0 576 512"><path fill="currentColor" d="M0 64C0 28.7 28.7 0 64 0h160v128c0 17.7 14.3 32 32 32h128v128H216c-13.3 0-24 10.7-24 24s10.7 24 24 24h168v112c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V64zm384 272v-48h110.1l-39-39c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l80 80c9.4 9.4 9.4 24.6 0 33.9l-80 80c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l39-39H384zm0-208H256V0l128 128z"/></svg>
|
After Width: | Height: | Size: 464 B |
1
src/assets/svg-icons/fa6-solid-file-import.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"><path fill="currentColor" d="M128 64c0-35.3 28.7-64 64-64h160v128c0 17.7 14.3 32 32 32h128v288c0 35.3-28.7 64-64 64H192c-35.3 0-64-28.7-64-64V336h174.1l-39 39c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l80-80c9.4-9.4 9.4-24.6 0-33.9l-80-80c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l39 39H128V64zm0 224v48H24c-13.3 0-24-10.7-24-24s10.7-24 24-24h104zm384-160H384V0l128 128z"/></svg>
|
After Width: | Height: | Size: 464 B |
1
src/assets/svg-icons/ic-baseline-add-business.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M15 17h2v-3h1v-2l-1-5H2l-1 5v2h1v6h9v-6h4v3zm-6 1H4v-4h5v4zM2 4h15v2H2z"/><path fill="currentColor" d="M20 18v-3h-2v3h-3v2h3v3h2v-3h3v-2z"/></svg>
|
After Width: | Height: | Size: 260 B |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 48 48"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.99" d="m14 26l-9 9l9 9m-9-8.992h17.5M34 18l9 9l-9 9m9-8.992H25.5M4.5 24V7.5h39V15"/></svg>
|
After Width: | Height: | Size: 278 B |
1
src/assets/svg-icons/icon-park-outline-to-top.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 48 48"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M24.008 14.1V42M12 26l12-12l12 12M12 6h24"/></svg>
|
After Width: | Height: | Size: 242 B |
1
src/assets/svg-icons/iconamoon-search-fill.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M11 2a9 9 0 1 0 5.618 16.032l3.675 3.675a1 1 0 0 0 1.414-1.414l-3.675-3.675A9 9 0 0 0 11 2Zm-6 9a6 6 0 1 1 12 0a6 6 0 0 1-12 0Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 290 B |
1
src/assets/svg-icons/ion-copy.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 512 512"><path fill="currentColor" d="M408 480H184a72 72 0 0 1-72-72V184a72 72 0 0 1 72-72h224a72 72 0 0 1 72 72v224a72 72 0 0 1-72 72"/><path fill="currentColor" d="M160 80h235.88A72.12 72.12 0 0 0 328 32H104a72 72 0 0 0-72 72v224a72.12 72.12 0 0 0 48 67.88V160a80 80 0 0 1 80-80"/></svg>
|
After Width: | Height: | Size: 365 B |
1
src/assets/svg-icons/line-md-close-small.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-dasharray="16" stroke-dashoffset="16" stroke-linecap="round" stroke-width="2"><path d="M7 7L17 17"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.4s" values="16;0"/></path><path d="M17 7L7 17"><animate fill="freeze" attributeName="stroke-dashoffset" begin="0.4s" dur="0.4s" values="16;0"/></path></g></svg>
|
After Width: | Height: | Size: 452 B |
1
src/assets/svg-icons/lucide-info.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4m0-4h.01"/></g></svg>
|
After Width: | Height: | Size: 257 B |
1
src/assets/svg-icons/lucide-users.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87m-3-12a4 4 0 0 1 0 7.75"/></g></svg>
|
After Width: | Height: | Size: 339 B |
1
src/assets/svg-icons/lucide-wallpaper.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="#888888" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle cx="8" cy="9" r="2"/><path d="m9 17l6.1-6.1a2 2 0 0 1 2.81.01L22 15V5a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2M8 21h8m-4-4v4"/></g></svg>
|
After Width: | Height: | Size: 347 B |
1
src/assets/svg-icons/majesticons-applications.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M6 3a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h2a3 3 0 0 0 3-3V6a3 3 0 0 0-3-3H6zm0 10a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h2a3 3 0 0 0 3-3v-2a3 3 0 0 0-3-3H6zm10 0a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h2a3 3 0 0 0 3-3v-2a3 3 0 0 0-3-3h-2zm0-10a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h2a3 3 0 0 0 3-3V6a3 3 0 0 0-3-3h-2z" fill="currentColor"/></g></svg>
|
After Width: | Height: | Size: 465 B |
1
src/assets/svg-icons/material-symbols-account-circle.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M5.85 17.1q1.275-.975 2.85-1.537T12 15q1.725 0 3.3.563t2.85 1.537q.875-1.025 1.363-2.325T20 12q0-3.325-2.337-5.663T12 4Q8.675 4 6.337 6.338T4 12q0 1.475.488 2.775T5.85 17.1ZM12 13q-1.475 0-2.488-1.012T8.5 9.5q0-1.475 1.013-2.488T12 6q1.475 0 2.488 1.013T15.5 9.5q0 1.475-1.012 2.488T12 13Zm0 9q-2.075 0-3.9-.788t-3.175-2.137q-1.35-1.35-2.137-3.175T2 12q0-2.075.788-3.9t2.137-3.175q1.35-1.35 3.175-2.137T12 2q2.075 0 3.9.788t3.175 2.137q1.35 1.35 2.138 3.175T22 12q0 2.075-.788 3.9t-2.137 3.175q-1.35 1.35-3.175 2.138T12 22Z"/></svg>
|
After Width: | Height: | Size: 646 B |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M8 16h12V6H8v10Zm0 2q-.825 0-1.412-.587T6 16V4q0-.825.588-1.412T8 2h12q.825 0 1.413.588T22 4v12q0 .825-.587 1.413T20 18H8Zm-4 4q-.825 0-1.412-.587T2 20V7q0-.425.288-.712T3 6q.425 0 .713.288T4 7v13h13q.425 0 .713.288T18 21q0 .425-.288.713T17 22H4ZM8 4v12V4Z"/></svg>
|
After Width: | Height: | Size: 379 B |
1
src/assets/svg-icons/material-symbols-delete.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21H7Zm2-4h2V8H9v9Zm4 0h2V8h-2v9Z"/></svg>
|
After Width: | Height: | Size: 230 B |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M3 20v-3q0-.825.588-1.412T5 15h1v-2q0-.825.588-1.412T8 11h3V9h-1q-.825 0-1.412-.587T8 7V4q0-.825.588-1.412T10 2h4q.825 0 1.413.588T16 4v3q0 .825-.587 1.413T14 9h-1v2h3q.825 0 1.413.588T18 13v2h1q.825 0 1.413.588T21 17v3q0 .825-.587 1.413T19 22h-4q-.825 0-1.412-.587T13 20v-3q0-.825.588-1.412T15 15h1v-2H8v2h1q.825 0 1.413.588T11 17v3q0 .825-.587 1.413T9 22H5q-.825 0-1.412-.587T3 20Zm7-13h4V4h-4v3ZM5 20h4v-3H5v3Zm10 0h4v-3h-4v3ZM12 7ZM9 17Zm6 0Z"/></svg>
|
After Width: | Height: | Size: 569 B |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M12 18Zm-8 1v-1.8q0-.85.438-1.562T5.6 14.55q1.55-.775 3.15-1.162T12 13q.925 0 1.825.113t1.8.362l-1.675 1.7q-.5-.075-.975-.125T12 15q-1.4 0-2.775.338T6.5 16.35q-.225.125-.363.35T6 17.2v.8h6v2H5q-.425 0-.712-.288T4 19Zm10 1v-1.25q0-.4.163-.763t.437-.637l4.925-4.925q.225-.225.5-.325t.55-.1q.3 0 .575.113t.5.337l.925.925q.2.225.313.5t.112.55q0 .275-.1.563t-.325.512l-4.925 4.925q-.275.275-.637.425t-.763.15H15q-.425 0-.712-.288T14 20Zm7.5-5.575l-.925-.925l.925.925Zm-6 5.075h.95l3.025-3.05l-.45-.475l-.475-.45l-3.05 3.025v.95Zm3.525-3.525l-.475-.45l.925.925l-.45-.475ZM12 12q-1.65 0-2.825-1.175T8 8q0-1.65 1.175-2.825T12 4q1.65 0 2.825 1.175T16 8q0 1.65-1.175 2.825T12 12Zm0-2q.825 0 1.413-.587T14 8q0-.825-.587-1.412T12 6q-.825 0-1.412.588T10 8q0 .825.588 1.413T12 10Zm0-2Z"/></svg>
|
After Width: | Height: | Size: 894 B |
1
src/assets/svg-icons/material-symbols-save.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M21 7v12q0 .825-.587 1.413T19 21H5q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h12l4 4Zm-9 11q1.25 0 2.125-.875T15 15q0-1.25-.875-2.125T12 12q-1.25 0-2.125.875T9 15q0 1.25.875 2.125T12 18Zm-6-8h9V6H6v4Z"/></svg>
|
After Width: | Height: | Size: 327 B |
1
src/assets/svg-icons/mdi-information-box-outline.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M13 9h-2V7h2zm0 8h-2v-6h2zM5 3h14a2 2 0 0 1 2 2v14c0 .53-.21 1.04-.59 1.41c-.37.38-.88.59-1.41.59H5c-.53 0-1.04-.21-1.41-.59C3.21 20.04 3 19.53 3 19V5c0-1.11.89-2 2-2m14 16V5H5v14z"/></svg>
|
After Width: | Height: | Size: 303 B |
1
src/assets/svg-icons/mdi-information-variant.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13.5 4A1.5 1.5 0 0 0 12 5.5A1.5 1.5 0 0 0 13.5 7A1.5 1.5 0 0 0 15 5.5A1.5 1.5 0 0 0 13.5 4m-.36 4.77c-1.19.1-4.44 2.69-4.44 2.69c-.2.15-.14.14.02.42c.16.27.14.29.33.16c.2-.13.53-.34 1.08-.68c2.12-1.36.34 1.78-.57 7.07c-.36 2.62 2 1.27 2.61.87c.6-.39 2.21-1.5 2.37-1.61c.22-.15.06-.27-.11-.52c-.12-.17-.24-.05-.24-.05c-.65.43-1.84 1.33-2 .76c-.19-.57 1.03-4.48 1.7-7.17c.11-.64.41-2.04-.75-1.94Z"/></svg>
|
After Width: | Height: | Size: 516 B |
1
src/assets/svg-icons/mdi-password-outline.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M12 17a2 2 0 0 1-2-2c0-1.11.89-2 2-2a2 2 0 0 1 2 2a2 2 0 0 1-2 2m6 3V10H6v10h12m0-12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10c0-1.11.89-2 2-2h1V6a5 5 0 0 1 5-5a5 5 0 0 1 5 5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3Z"/></svg>
|
After Width: | Height: | Size: 349 B |
1
src/assets/svg-icons/mdi-wan.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2a8 8 0 0 0-8 8c0 4.03 3 7.42 7 7.93V19h-1a1 1 0 0 0-1 1H2v2h7a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1h7v-2h-7a1 1 0 0 0-1-1h-1v-1.07c4-.5 7-3.9 7-7.93a8 8 0 0 0-8-8m0 2s.74 1.28 1.26 3h-2.52C11.26 5.28 12 4 12 4m-2.23.43c-.27.5-.68 1.41-1.03 2.57H6.81C7.5 5.84 8.5 4.93 9.77 4.43m4.46.01c1.27.5 2.27 1.4 2.96 2.56h-1.93c-.35-1.16-.76-2.07-1.03-2.56M6.09 9h2.23c-.04.33-.07.66-.07 1c0 .34.03.67.07 1H6.09a5.551 5.551 0 0 1 0-2m4.23 0h3.36c.04.33.07.66.07 1c0 .34-.03.67-.07 1h-3.36c-.04-.33-.07-.66-.07-1c0-.34.03-.67.07-1m5.36 0h2.23a5.551 5.551 0 0 1 0 2h-2.23c.04-.33.07-.66.07-1c0-.34-.03-.67-.07-1m-8.87 4h1.93c.35 1.16.76 2.07 1.03 2.56c-1.27-.5-2.27-1.4-2.96-2.56m3.93 0h2.52c-.52 1.72-1.26 3-1.26 3s-.74-1.28-1.26-3m4.52 0h1.93c-.69 1.16-1.69 2.07-2.96 2.57c.27-.5.68-1.41 1.03-2.57Z"/></svg>
|
After Width: | Height: | Size: 908 B |
1
src/assets/svg-icons/mingcute-more-1-fill.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none"><path d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z"/><path fill="currentColor" d="M5 10a2 2 0 1 1 0 4a2 2 0 0 1 0-4Zm7 0a2 2 0 1 1 0 4a2 2 0 0 1 0-4Zm7 0a2 2 0 1 1 0 4a2 2 0 0 1 0-4Z"/></g></svg>
|
After Width: | Height: | Size: 813 B |
1
src/assets/svg-icons/ph-user-bold.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><path fill="currentColor" d="M234.38 210a123.36 123.36 0 0 0-60.78-53.23a76 76 0 1 0-91.2 0A123.36 123.36 0 0 0 21.62 210a12 12 0 1 0 20.77 12c18.12-31.32 50.12-50 85.61-50s67.49 18.69 85.61 50a12 12 0 0 0 20.77-12ZM76 96a52 52 0 1 1 52 52a52.06 52.06 0 0 1-52-52Z"/></svg>
|
After Width: | Height: | Size: 360 B |
1
src/assets/svg-icons/ri-drag-drop-line.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="m16 13l6.964 4.062l-2.973.85l2.125 3.681l-1.732 1l-2.125-3.68l-2.223 2.15L16 13Zm-2-7h2v2h5a1 1 0 0 1 1 1v4h-2v-3H10v10h4v2H9a1 1 0 0 1-1-1v-5H6v-2h2V9a1 1 0 0 1 1-1h5V6ZM4 14v2H2v-2h2Zm0-4v2H2v-2h2Zm0-4v2H2V6h2Zm0-4v2H2V2h2Zm4 0v2H6V2h2Zm4 0v2h-2V2h2Zm4 0v2h-2V2h2Z"/></svg>
|
After Width: | Height: | Size: 389 B |
1
src/assets/svg-icons/ri-settings-4-line.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M2 12c0-.865.11-1.704.316-2.504A3 3 0 0 0 4.99 4.867a9.99 9.99 0 0 1 4.335-2.506a3 3 0 0 0 5.348 0a9.99 9.99 0 0 1 4.335 2.506a3 3 0 0 0 2.675 4.63c.206.8.316 1.638.316 2.503c0 .864-.11 1.703-.316 2.503a3 3 0 0 0-2.675 4.63a9.99 9.99 0 0 1-4.335 2.505a3 3 0 0 0-5.348 0a9.99 9.99 0 0 1-4.335-2.505a3 3 0 0 0-2.675-4.63C2.11 13.703 2 12.864 2 12Zm4.804 3c.63 1.091.81 2.346.564 3.524c.408.29.842.541 1.297.75A4.993 4.993 0 0 1 12 18c1.26 0 2.438.471 3.335 1.274c.455-.209.889-.46 1.297-.75A4.993 4.993 0 0 1 17.196 15a4.993 4.993 0 0 1 2.77-2.25a8.142 8.142 0 0 0 0-1.5A4.993 4.993 0 0 1 17.196 9a4.993 4.993 0 0 1-.564-3.524a7.991 7.991 0 0 0-1.297-.75A4.993 4.993 0 0 1 12 6a4.993 4.993 0 0 1-3.335-1.274a7.99 7.99 0 0 0-1.297.75A4.993 4.993 0 0 1 6.804 9a4.993 4.993 0 0 1-2.77 2.25a8.125 8.125 0 0 0 0 1.5A4.993 4.993 0 0 1 6.805 15ZM12 15a3 3 0 1 1 0-6a3 3 0 0 1 0 6Zm0-2a1 1 0 1 0 0-2a1 1 0 0 0 0 2Z"/></svg>
|
After Width: | Height: | Size: 1.0 KiB |
1
src/assets/svg-icons/tabler-file-upload.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="#888888" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M14 3v4a1 1 0 0 0 1 1h4"/><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2m-5-10v6"/><path d="M9.5 13.5L12 11l2.5 2.5"/></g></svg>
|
After Width: | Height: | Size: 340 B |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M0 0h24v24H0z"/><path fill="currentColor" d="M18 3a3 3 0 0 1 2.995 2.824L21 6v12a3 3 0 0 1-2.824 2.995L18 21H6a3 3 0 0 1-2.995-2.824L3 18V6a3 3 0 0 1 2.824-2.995L6 3h12zm0 2H9v14h9a1 1 0 0 0 .993-.883L19 18V6a1 1 0 0 0-.883-.993L18 5zm-2.293 4.293a1 1 0 0 1 .083 1.32l-.083.094L14.415 12l1.292 1.293a1 1 0 0 1 .083 1.32l-.083.094a1 1 0 0 1-1.32.083l-.094-.083l-2-2a1 1 0 0 1-.083-1.32l.083-.094l2-2a1 1 0 0 1 1.414 0z"/></g></svg>
|
After Width: | Height: | Size: 603 B |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M0 0h24v24H0z"/><path fill="currentColor" d="M18 3a3 3 0 0 1 2.995 2.824L21 6v12a3 3 0 0 1-2.824 2.995L18 21H6a3 3 0 0 1-2.995-2.824L3 18V6a3 3 0 0 1 2.824-2.995L6 3h12zm-3 2H6a1 1 0 0 0-.993.883L5 6v12a1 1 0 0 0 .883.993L6 19h9V5zM9.613 9.21l.094.083l2 2a1 1 0 0 1 .083 1.32l-.083.094l-2 2a1 1 0 0 1-1.497-1.32l.083-.094L9.585 12l-1.292-1.293a1 1 0 0 1-.083-1.32l.083-.094a1 1 0 0 1 1.32-.083z"/></g></svg>
|
After Width: | Height: | Size: 580 B |
1
src/assets/svg-icons/tabler-logout.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M14 8V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2v-2"/><path d="M9 12h12l-3-3m0 6l3-3"/></g></svg>
|
After Width: | Height: | Size: 314 B |
1
src/assets/svg-icons/typcn-plus.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M18 10h-4V6a2 2 0 0 0-4 0l.071 4H6a2 2 0 0 0 0 4l4.071-.071L10 18a2 2 0 0 0 4 0v-4.071L18 14a2 2 0 0 0 0-4z"/></svg>
|
After Width: | Height: | Size: 230 B |
@ -26,7 +26,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="pt-10">
|
||||
<div>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<img :src="srcSvglogo" width="100" height="100" alt="">
|
||||
@ -40,7 +40,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NDivider />
|
||||
<NDivider> • </NDivider>
|
||||
<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>
|
360
src/components/apps/ImportExport/index.vue
Normal file
@ -0,0 +1,360 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import type { UploadFileInfo } from 'naive-ui'
|
||||
import { NAlert, NButton, NCheckbox, NCheckboxGroup, NDivider, NInput, NSpace, NUpload, useMessage } from 'naive-ui'
|
||||
import { RoundCardModal, SvgIcon } from '@/components/common'
|
||||
import type { IconGroup, ImportJsonResult } from '@/utils/jsonImportExport'
|
||||
import { ConfigVersionLowError, FormatError, exportJson, importJsonString } from '@/utils/jsonImportExport'
|
||||
import { get as getAbout } from '@/api/system/about'
|
||||
import { edit as addGroup, getList as getGroupList } from '@/api/panel/itemIconGroup'
|
||||
import { addMultiple as addMultipleIcons, getListByGroupId } from '@/api/panel/itemIcon'
|
||||
|
||||
import { t } from '@/locales'
|
||||
|
||||
interface ItemGroup extends Panel.ItemIconGroup {
|
||||
items?: Panel.ItemInfo[]
|
||||
}
|
||||
|
||||
const ms = useMessage()
|
||||
|
||||
const jsonData = ref<string | null>(null)
|
||||
const importWarning = ref<string[]>([])
|
||||
const importRoundModalShow = ref(false)
|
||||
const exportRoundModalShow = ref(false)
|
||||
const loading = ref(false)
|
||||
const uploadLoading = ref(false)
|
||||
const version = ref('') // 当前软件版本
|
||||
const debug = ref(false)
|
||||
|
||||
const importObj = ref<ImportJsonResult | null> (null)
|
||||
|
||||
const importItems = ref<string[]>(['icons']) // 当前软件版本支持导入导出的项目
|
||||
const checkedItems = ref<string[]>(['icons']) // 当前准备导入的项目
|
||||
|
||||
// 导入图标
|
||||
async function importIcons(): Promise<string | null> {
|
||||
const groups = importObj.value?.geticons()
|
||||
const batchSize = 50
|
||||
|
||||
if (!groups)
|
||||
return null
|
||||
|
||||
try {
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
const element = groups[i]
|
||||
|
||||
// 创建组得到组id
|
||||
const createGroupResponse = await addGroup<Panel.ItemIconGroup>({
|
||||
title: element.title,
|
||||
sort: element.sort,
|
||||
})
|
||||
|
||||
if (createGroupResponse.code === 0) {
|
||||
const groupId = createGroupResponse.data?.id
|
||||
|
||||
if (groupId) {
|
||||
let addIcons: Panel.ItemInfo[] = []
|
||||
|
||||
// 批量添加子项
|
||||
for (let iconI = 0; iconI < element.children.length; iconI++) {
|
||||
const iconElement = element.children[iconI]
|
||||
|
||||
addIcons.push({
|
||||
title: iconElement.title,
|
||||
sort: iconElement.sort,
|
||||
icon: iconElement.icon,
|
||||
url: iconElement.url,
|
||||
lanUrl: iconElement.lanUrl,
|
||||
description: iconElement.description,
|
||||
openMethod: iconElement.openMethod,
|
||||
itemIconGroupId: groupId,
|
||||
})
|
||||
|
||||
// 每 batchSize 个添加一次
|
||||
if (addIcons.length === batchSize || iconI === element.children.length - 1) {
|
||||
const response = await addMultipleIcons(addIcons)
|
||||
|
||||
if (response.code !== 0)
|
||||
return response.msg
|
||||
|
||||
addIcons = []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return createGroupResponse.msg
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error)
|
||||
return `发生错误: ${error.message}`
|
||||
else
|
||||
return '发生未知错误'
|
||||
}
|
||||
}
|
||||
|
||||
// 导出图标
|
||||
async function exportIcons(): Promise<IconGroup[]> {
|
||||
const iconGroups: IconGroup[] = []
|
||||
|
||||
// 获取组数据
|
||||
const { code, data } = await getGroupList<Common.ListResponse<ItemGroup[]>>()
|
||||
|
||||
if (code === 0) {
|
||||
// 使用 Promise.all 等待所有异步操作完成
|
||||
await Promise.all(data.list.map(async (element) => {
|
||||
const group: IconGroup = {
|
||||
title: element.title as string,
|
||||
sort: element.sort as 0,
|
||||
children: [],
|
||||
}
|
||||
|
||||
const res = await getListByGroupId<Common.ListResponse<Panel.ItemInfo[]>>(element.id)
|
||||
|
||||
if (res.code === 0) {
|
||||
for (const iconElement of res.data.list) {
|
||||
group.children.push({
|
||||
icon: iconElement.icon,
|
||||
sort: iconElement.sort || 99999,
|
||||
title: iconElement.title,
|
||||
url: iconElement.url,
|
||||
lanUrl: iconElement.lanUrl || '',
|
||||
description: iconElement.description || '',
|
||||
openMethod: iconElement.openMethod || 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
iconGroups.push(group)
|
||||
}))
|
||||
|
||||
return iconGroups
|
||||
}
|
||||
else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
interface Version {
|
||||
versionName: string
|
||||
versionCode: number
|
||||
}
|
||||
|
||||
getAbout<Version>().then((res) => {
|
||||
if (res.code === 0)
|
||||
version.value = res.data.versionName
|
||||
})
|
||||
})
|
||||
|
||||
function handleFileChange(options: { file: UploadFileInfo; fileList: Array<UploadFileInfo> }) {
|
||||
uploadLoading.value = true
|
||||
console.log(options.file.file)
|
||||
if (options.file.file) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
if (reader.result) {
|
||||
jsonData.value = reader.result as string
|
||||
importCheck()
|
||||
}
|
||||
else {
|
||||
ms.error('异常请重新上传')
|
||||
}
|
||||
uploadLoading.value = false
|
||||
}
|
||||
reader.readAsText(options.file.file)
|
||||
}
|
||||
}
|
||||
|
||||
// 验证导入文件
|
||||
function importCheck() {
|
||||
importWarning.value = []
|
||||
if (jsonData.value) {
|
||||
try {
|
||||
importObj.value = importJsonString(jsonData.value)
|
||||
if (importObj.value) {
|
||||
if (!importObj.value.isPassCheckMd5())
|
||||
importWarning.value.push('文件被修改过,谨慎导入')
|
||||
|
||||
if (!importObj.value.isPassCheckConfigVersionOld())
|
||||
importWarning.value.push('配置文件版本过低,但是兼容')
|
||||
|
||||
if (!importObj.value.isPassCheckConfigVersionNew())
|
||||
importWarning.value.push('当前软件版本可能过旧,很有可能无法兼容该配置文件,请谨慎导入。推荐将软件更新到新版后再次导入')
|
||||
|
||||
// (暂时不做)此处可以判断,当前的配置文件是否存在的导入项目(不存在隐藏importItems里面的值)操作变量:importItems
|
||||
|
||||
// 通过了验证,打开弹窗
|
||||
importRoundModalShow.value = !importRoundModalShow.value
|
||||
|
||||
// console.log(importObj.value.geticons())
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof ConfigVersionLowError) {
|
||||
ms.error('配置文件版本过低,无法兼容')
|
||||
console.log('配置文件版本过低')
|
||||
}
|
||||
else if (error instanceof FormatError) {
|
||||
ms.error('格式不正确,无法导入')
|
||||
console.log('格式不正确')
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
ms.error('数据不正确')
|
||||
}
|
||||
}
|
||||
|
||||
// 开始导出
|
||||
async function handleStartExport() {
|
||||
loading.value = true
|
||||
console.log('要导出的项目', checkedItems.value)
|
||||
// 获取软件版本号
|
||||
const exportResult = exportJson(version.value)
|
||||
if (checkedItems.value.includes('icons')) {
|
||||
console.log('export icons ...')
|
||||
const iconGroups = await exportIcons()
|
||||
exportResult.addIconsData(iconGroups)
|
||||
console.log('export icons finish', iconGroups)
|
||||
}
|
||||
|
||||
console.log('导出结果')
|
||||
|
||||
jsonData.value = exportResult.string()
|
||||
exportResult.exportFile()
|
||||
loading.value = false
|
||||
exportRoundModalShow.value = false
|
||||
// ms.success(t('common.success'))
|
||||
}
|
||||
|
||||
// 开始导入
|
||||
async function handleStartImport() {
|
||||
loading.value = true
|
||||
if (checkedItems.value.includes('icons')) {
|
||||
console.log('export icons ...')
|
||||
const errMsg = await importIcons()
|
||||
if (errMsg !== null)
|
||||
ms.success(`${t('common.failed')}:${errMsg}`)
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
importRoundModalShow.value = false
|
||||
ms.success(`${t('common.success')},请手动刷新页面`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pt-2">
|
||||
<NAlert type="info" :bordered="false">
|
||||
<p>导入图标配置数据不会清空现有图标数据</p>
|
||||
</NAlert>
|
||||
<div class="flex justify-center m-[50px]">
|
||||
<div class="m-[10px]">
|
||||
<NUpload
|
||||
accept=".sun-panel.json,.sunpanel.json"
|
||||
directory-dnd
|
||||
:default-upload="false"
|
||||
:show-file-list="false"
|
||||
@change="handleFileChange"
|
||||
>
|
||||
<NButton type="info" size="large" :loading="uploadLoading">
|
||||
<template #icon>
|
||||
<SvgIcon icon="fa6:solid-file-import" />
|
||||
</template>
|
||||
导入配置
|
||||
</NButton>
|
||||
</NUpload>
|
||||
</div>
|
||||
<div class="m-[10px]">
|
||||
<NButton type="info" size="large" @click="exportRoundModalShow = !exportRoundModalShow">
|
||||
<template #icon>
|
||||
<SvgIcon icon="fa6:solid-file-export" />
|
||||
</template>
|
||||
导出配置
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<a href="https://hslr-s.github.io/sun-panel-tool-page/#/" target="_blank">浏览器书签转换工具</a>
|
||||
</div>
|
||||
|
||||
<!-- 调试模式 -->
|
||||
<div v-if="debug">
|
||||
<NButton @click="importCheck">
|
||||
检查导入
|
||||
</NButton>
|
||||
|
||||
<!-- <NButton @click="exportJsonS">
|
||||
导出JSON
|
||||
</NButton> -->
|
||||
|
||||
<NButton @click="jsonData = ''">
|
||||
清空导入数据
|
||||
</NButton>
|
||||
|
||||
<NInput
|
||||
v-model:value="jsonData"
|
||||
type="textarea"
|
||||
placeholder="基本的 Textarea"
|
||||
/>
|
||||
|
||||
<div v-if="jsonData">
|
||||
<h2>JSON 数据</h2>
|
||||
<pre>{{ jsonData }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RoundCardModal v-model:show="importRoundModalShow" style="max-width: 400px;" title="导入">
|
||||
<div v-if="importWarning.length > 0">
|
||||
<NAlert :title="$t('common.warning')" type="warning">
|
||||
<div v-for="(text, index) in importWarning " :key="index">
|
||||
{{ text }}
|
||||
</div>
|
||||
</NAlert>
|
||||
</div>
|
||||
<NDivider title-placement="left">
|
||||
请选择要导入的配置数据
|
||||
</NDivider>
|
||||
|
||||
<NSpace justify="center" style="margin-top: 20px;">
|
||||
<NCheckboxGroup v-model:value="checkedItems">
|
||||
<NCheckbox v-if="importItems.includes('icons')" value="icons" label="图标" />
|
||||
<NCheckbox v-if="importItems.includes('style')" value="style" label="样式配置" />
|
||||
</NCheckboxGroup>
|
||||
</NSpace>
|
||||
<NSpace justify="center">
|
||||
<div class="mt-[50px]">
|
||||
<NButton type="success" :disabled="checkedItems.length === 0" :loading="loading" @click="handleStartImport">
|
||||
继续导入
|
||||
</NButton>
|
||||
</div>
|
||||
</NSpace>
|
||||
</RoundCardModal>
|
||||
|
||||
<RoundCardModal v-model:show="exportRoundModalShow" style="max-width: 400px;" title="导出">
|
||||
<NDivider title-placement="left">
|
||||
请选择要导出的配置数据
|
||||
</NDivider>
|
||||
|
||||
<NSpace justify="center" style="margin-top: 20px;">
|
||||
<NCheckboxGroup v-model:value="checkedItems">
|
||||
<NCheckbox v-if="importItems.includes('icons')" value="icons" label="图标" />
|
||||
<NCheckbox v-if="importItems.includes('style')" value="style" label="样式配置" />
|
||||
</NCheckboxGroup>
|
||||
</NSpace>
|
||||
<NSpace justify="center">
|
||||
<div class="mt-[50px]">
|
||||
<NButton type="success" :disabled="checkedItems.length === 0" :loading="loading" @click="handleStartExport">
|
||||
继续导出
|
||||
</NButton>
|
||||
</div>
|
||||
</NSpace>
|
||||
</RoundCardModal>
|
||||
</div>
|
||||
</template>
|
@ -48,6 +48,7 @@ function handleAddGroup() {
|
||||
function handleEditGroup(groupInfo: Panel.ItemIconGroup) {
|
||||
editModalArg.value.show = true
|
||||
editModalArg.value.model = groupInfo
|
||||
editModalArg.value.editStatus = 2
|
||||
}
|
||||
|
||||
function handleDragSort() {
|
||||
@ -122,8 +123,8 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[500px]">
|
||||
<div>
|
||||
<div class="h-full">
|
||||
<div class="p-2">
|
||||
<NButton type="success" size="small" style="margin-right: 10px;" @click="handleAddGroup">
|
||||
新增分组
|
||||
</NButton>
|
||||
@ -137,7 +138,7 @@ onMounted(() => {
|
||||
</NButton>
|
||||
</div>
|
||||
|
||||
<div class=" overflow-auto w-full mt-[20px] bg-slate-200 rounded-xl" style="height:calc(100% - 50px)">
|
||||
<div class=" overflow-auto w-full mt-[20px] bg-slate-200 rounded-xl" style="height:calc(100% - 65px)">
|
||||
<VueDraggable
|
||||
v-model="groups"
|
||||
item-key="sort" :animation="300"
|
||||
@ -149,7 +150,7 @@ onMounted(() => {
|
||||
<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="material-symbols:ad-group-outline-rounded" />
|
||||
<!-- <SvgIcon class="text-[20px]" :icon="item.icon" /> -->
|
||||
</span>
|
||||
<span>
|
||||
@ -178,7 +179,7 @@ onMounted(() => {
|
||||
</VueDraggable>
|
||||
</div>
|
||||
|
||||
<RoundCardModal v-model:show="editModalArg.show" type="small" :title="editModalArg.editStatus === 1 ? '添加' : '编辑'" style="width: 400px;">
|
||||
<RoundCardModal v-model:show="editModalArg.show" size="small" 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="请输入" />
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import type { UploadFileInfo } from 'naive-ui'
|
||||
import { NButton, NCard, NColorPicker, NInput, NPopconfirm, NSelect, NSlider, NSwitch, NUpload, NUploadDragger, useMessage } from 'naive-ui'
|
||||
import { NButton, NCard, NColorPicker, NGrid, NGridItem, 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'
|
||||
@ -31,9 +31,9 @@ watch(panelState.panelConfig, () => {
|
||||
panelState.recordState()// 本地记录
|
||||
setUserConfig({ panel: panelState.panelConfig }).then((res) => {
|
||||
if (res.code === 0)
|
||||
ms.success('配置已同步到云端')
|
||||
ms.success('配置已保存')
|
||||
else
|
||||
ms.error(`配置同步到云端失败${res.msg}`)
|
||||
ms.error(`配置保存失败${res.msg}`)
|
||||
isSaveing.value = false
|
||||
})
|
||||
}, 1000)
|
||||
@ -68,16 +68,24 @@ function resetPanelConfig() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-slate-200 rounded-[10px] p-[8px] h-[500px] overflow-auto">
|
||||
<div class="bg-slate-200 rounded-[10px] p-[8px] overflow-auto">
|
||||
<NCard style="border-radius:10px" size="small">
|
||||
<div class="text-slate-500 mb-[5px]">
|
||||
<div class="text-slate-500 mb-[5px] font-bold">
|
||||
LOGO
|
||||
</div>
|
||||
<NInput v-model:value="panelState.panelConfig.logoText" type="text" show-count :maxlength="20" placeholder="请输入文字" />
|
||||
|
||||
<div>
|
||||
<div>
|
||||
文本内容
|
||||
</div>
|
||||
<div class="flex items-center mt-[5px]">
|
||||
<NInput v-model:value="panelState.panelConfig.logoText" type="text" show-count :maxlength="20" placeholder="请输入文字" />
|
||||
</div>
|
||||
</div>
|
||||
</NCard>
|
||||
|
||||
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
|
||||
<div class="text-slate-500 mb-[5px]">
|
||||
<div class="text-slate-500 mb-[5px] font-bold">
|
||||
时钟
|
||||
</div>
|
||||
<div class="flex items-center mt-[5px]">
|
||||
@ -87,17 +95,21 @@ function resetPanelConfig() {
|
||||
</NCard>
|
||||
|
||||
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
|
||||
<div class="text-slate-500 mb-[5px]">
|
||||
<div class="text-slate-500 mb-[5px] font-bold">
|
||||
搜索框
|
||||
</div>
|
||||
<div class="flex items-center mt-[5px]">
|
||||
<span class="mr-[10px]">显示</span>
|
||||
<NSwitch v-model:value="panelState.panelConfig.searchBoxShow" />
|
||||
</div>
|
||||
<div v-if="panelState.panelConfig.searchBoxShow" class="flex items-center mt-[5px]">
|
||||
<span class="mr-[10px]">允许搜索快捷图标</span>
|
||||
<NSwitch v-model:value="panelState.panelConfig.searchBoxSearchIcon" />
|
||||
</div>
|
||||
</NCard>
|
||||
|
||||
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
|
||||
<div class="text-slate-500 mb-[5px]">
|
||||
<div class="text-slate-500 mb-[5px] font-bold">
|
||||
图标
|
||||
</div>
|
||||
<div class="mt-[5px]">
|
||||
@ -149,7 +161,7 @@ function resetPanelConfig() {
|
||||
</div>
|
||||
</NCard>
|
||||
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
|
||||
<div class="text-slate-500 mb-[5px]">
|
||||
<div class="text-slate-500 mb-[5px] font-bold">
|
||||
壁纸
|
||||
</div>
|
||||
<NUpload
|
||||
@ -185,6 +197,27 @@ function resetPanelConfig() {
|
||||
</div>
|
||||
</NCard>
|
||||
|
||||
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
|
||||
<div class="text-slate-500 mb-[5px] font-bold">
|
||||
其他
|
||||
</div>
|
||||
|
||||
<NGrid cols="2">
|
||||
<NGridItem span="12 400:12">
|
||||
<div class="flex items-center mt-[10px]">
|
||||
<span class="mr-[10px]">上边距 (%)</span>
|
||||
<NSlider v-model:value="panelState.panelConfig.marginTop" class="max-w-[200px]" :step="1" :max="50" />
|
||||
</div>
|
||||
</NGridItem>
|
||||
<NGridItem span="12 400:6">
|
||||
<div class="flex items-center mt-[10px]">
|
||||
<span class="mr-[10px]">下边距 (%)</span>
|
||||
<NSlider v-model:value="panelState.panelConfig.marginBottom" class="max-w-[200px]" :step="1" :max="50" />
|
||||
</div>
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
</NCard>
|
||||
|
||||
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
|
||||
<NPopconfirm
|
||||
@positive-click="resetPanelConfig"
|
183
src/components/apps/UploadFileManager/index.vue
Normal file
@ -0,0 +1,183 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NButtonGroup, NCard, NEllipsis, NGrid, NGridItem, NImage, NImageGroup, NSpin, useDialog, useMessage } from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { deletes, getList } from '@/api/system/file'
|
||||
import { set as savePanelConfig } from '@/api/panel/userConfig'
|
||||
import { RoundCardModal, SvgIcon } from '@/components/common'
|
||||
import { copyToClipboard, timeFormat } from '@/utils/cmn'
|
||||
import { t } from '@/locales'
|
||||
import { usePanelState } from '@/store'
|
||||
|
||||
interface InfoModalState {
|
||||
title: string
|
||||
show: boolean
|
||||
fileInfo: File.Info | null
|
||||
}
|
||||
const imageList = ref<File.Info[]>([])
|
||||
const ms = useMessage()
|
||||
const dialog = useDialog()
|
||||
const panelStore = usePanelState()
|
||||
const loading = ref(false)
|
||||
const infoModalState = ref<InfoModalState>({
|
||||
show: false,
|
||||
title: '',
|
||||
fileInfo: null,
|
||||
})
|
||||
|
||||
async function getFileList() {
|
||||
loading.value = true
|
||||
const { data } = await getList<Common.ListResponse<File.Info[]>>()
|
||||
imageList.value = data.list
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
async function copyImageUrl(text: string) {
|
||||
const res = await copyToClipboard(text)
|
||||
if (res)
|
||||
ms.success(t('apps.uploadsFileManager.copySuccess'))
|
||||
|
||||
else
|
||||
ms.error(t('apps.uploadsFileManager.copyFailed'))
|
||||
}
|
||||
|
||||
function handleDelete(id: number) {
|
||||
dialog.warning({
|
||||
title: t('common.warning'),
|
||||
content: t('apps.uploadsFileManager.deleteWarningText'),
|
||||
positiveText: t('common.confirm'),
|
||||
negativeText: t('common.cancel'),
|
||||
onPositiveClick: () => {
|
||||
deletesImges(id)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function deletesImges(id: number) {
|
||||
try {
|
||||
const { code, msg } = await deletes([id])
|
||||
if (code === 0) {
|
||||
getFileList()
|
||||
ms.success(t('common.success'))
|
||||
}
|
||||
else {
|
||||
ms.error(`${t('common.failed')}:${msg}`)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
ms.error(t('common.failed'))
|
||||
}
|
||||
}
|
||||
|
||||
function handleInfoClick(fileInfo: File.Info) {
|
||||
infoModalState.value.fileInfo = fileInfo
|
||||
infoModalState.value.show = true
|
||||
}
|
||||
|
||||
function handleSetWallpaper(imgSrc: string) {
|
||||
panelStore.panelConfig.backgroundImageSrc = imgSrc
|
||||
savePanelConfig({ panel: panelStore.panelConfig })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getFileList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-slate-200 p-2 h-full">
|
||||
<NSpin v-show="loading" size="small" />
|
||||
<div class="flex justify-center">
|
||||
<NImageGroup>
|
||||
<NGrid cols="2 300:2 600:4 900:6 1100:9" :x-gap="5" :y-gap="5">
|
||||
<NGridItem v-for=" item, index in imageList" :key="index">
|
||||
<NCard size="small" style="border-radius: 5px;" :bordered="true">
|
||||
<template #cover>
|
||||
<div class="card transparent-grid">
|
||||
<NImage :lazy="true" style="object-fit: contain;height: 100%;" :src="item.src" />
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<span class="text-xs">
|
||||
<NEllipsis>
|
||||
{{ item.fileName }}
|
||||
</NEllipsis>
|
||||
</span>
|
||||
<div class="flex justify-center mt-[10px]">
|
||||
<NButtonGroup>
|
||||
<NButton size="tiny" tertiary style="cursor: pointer;" :title="$t('apps.uploadsFileManager.copyLink')" @click="copyImageUrl(item.src)">
|
||||
<template #icon>
|
||||
<SvgIcon icon="ion-copy" />
|
||||
</template>
|
||||
</NButton>
|
||||
<NButton size="tiny" tertiary style="cursor: pointer;" :title="timeFormat(item.createTime)" @click="handleInfoClick(item)">
|
||||
<template #icon>
|
||||
<SvgIcon icon="mdi-information-box-outline" />
|
||||
</template>
|
||||
</NButton>
|
||||
<NButton size="tiny" tertiary style="cursor: pointer;" :title="$t('apps.uploadsFileManager.setWallpaper')" @click="handleSetWallpaper(item.src)">
|
||||
<template #icon>
|
||||
<SvgIcon icon="lucide:wallpaper" />
|
||||
</template>
|
||||
</NButton>
|
||||
<NButton size="tiny" tertiary type="error" style="cursor: pointer;" :title="$t('common.delete')" @click="handleDelete(item.id as number)">
|
||||
<template #icon>
|
||||
<SvgIcon icon="material-symbols-delete" />
|
||||
</template>
|
||||
</NButton>
|
||||
</NButtonGroup>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
</NImageGroup>
|
||||
</div>
|
||||
|
||||
<RoundCardModal v-model:show="infoModalState.show" style="max-width: 300px;" size="small" :title="$t('apps.uploadsFileManager.infoTitle')">
|
||||
<div>
|
||||
<div>
|
||||
<div class="mb-2">
|
||||
<span class="text-slate-500">
|
||||
{{ $t('apps.uploadsFileManager.fileName') }}
|
||||
</span>
|
||||
<div class="text-xs">
|
||||
{{ infoModalState.fileInfo?.fileName }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="text-slate-500">
|
||||
{{ $t('apps.uploadsFileManager.path') }}
|
||||
</span>
|
||||
<div class="text-xs">
|
||||
{{ infoModalState.fileInfo?.src }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="text-slate-500">
|
||||
{{ $t('apps.uploadsFileManager.uploadTime') }}
|
||||
</span>
|
||||
<div class="text-xs">
|
||||
{{ timeFormat(infoModalState.fileInfo?.createTime) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RoundCardModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.transparent-grid {
|
||||
background-image: linear-gradient(45deg, #f0f0f0 25%, transparent 25%, transparent 75%, #f0f0f0 75%),
|
||||
linear-gradient(45deg, #f0f0f0 25%, transparent 25%, transparent 75%, #f0f0f0 75%);
|
||||
background-size: 16px 16px;
|
||||
background-position: 0 0, 8px 8px;
|
||||
}
|
||||
</style>
|
201
src/components/apps/UserInfo/index.vue
Normal file
@ -0,0 +1,201 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormInst, FormRules } from 'naive-ui'
|
||||
import { NButton, NCard, NDivider, NForm, NFormItem, NInput, useDialog, useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
import { useAuthStore, usePanelState, useUserStore } from '@/store'
|
||||
import { logout } from '@/api'
|
||||
import { router } from '@/router'
|
||||
import { RoundCardModal, SvgIcon } from '@/components/common/'
|
||||
import { updateInfo, updatePassword } from '@/api/system/user'
|
||||
import { updateLocalUserInfo } from '@/utils/cmn'
|
||||
import { t } from '@/locales'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
const panelState = usePanelState()
|
||||
const ms = useMessage()
|
||||
const dialog = useDialog()
|
||||
|
||||
const nickName = ref(authStore.userInfo?.name || '')
|
||||
const isEditNickNameStatus = ref(false)
|
||||
const formRef = ref<FormInst | null>(null)
|
||||
|
||||
const updatePasswordModalState = ref({
|
||||
show: false,
|
||||
loading: false,
|
||||
form: {
|
||||
password: '',
|
||||
oldPassword: '',
|
||||
confirmPassword: '',
|
||||
},
|
||||
})
|
||||
|
||||
const updatePasswordModalFormRules: FormRules = {
|
||||
oldPassword: {
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: t('adminSettingUsers.formRules.passwordLimit'),
|
||||
},
|
||||
password: {
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: t('adminSettingUsers.formRules.passwordLimit'),
|
||||
},
|
||||
confirmPassword: {
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: t('adminSettingUsers.formRules.passwordLimit'),
|
||||
},
|
||||
}
|
||||
|
||||
async function logoutApi() {
|
||||
await logout()
|
||||
userStore.resetUserInfo()
|
||||
authStore.removeToken()
|
||||
panelState.removeState()
|
||||
ms.success(t('settingUserInfo.logoutSuccess'))
|
||||
router.push({ path: '/login' })
|
||||
location.reload()// 强制刷新一下页面
|
||||
}
|
||||
|
||||
function handleSaveInfo() {
|
||||
updateInfo(nickName.value).then(({ code, msg }) => {
|
||||
if (code === 0) {
|
||||
updateLocalUserInfo()
|
||||
isEditNickNameStatus.value = false
|
||||
}
|
||||
else {
|
||||
ms.error(`${t('common.editFail')}:${msg}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleUpdatePassword(e: MouseEvent) {
|
||||
e.preventDefault()
|
||||
formRef.value?.validate((errors) => {
|
||||
if (errors) {
|
||||
console.log(errors)
|
||||
return
|
||||
}
|
||||
|
||||
if (updatePasswordModalState.value.form.password !== updatePasswordModalState.value.form.confirmPassword) {
|
||||
ms.error(t('settingUserInfo.confirmPasswordInconsistentMsg'))
|
||||
return
|
||||
}
|
||||
updatePasswordModalState.value.loading = true
|
||||
updatePassword(updatePasswordModalState.value.form.oldPassword, updatePasswordModalState.value.form.password).then(({ code, msg }) => {
|
||||
if (code === 0) {
|
||||
// 成功
|
||||
updatePasswordModalState.value.show = false
|
||||
ms.success(t('common.success'))
|
||||
}
|
||||
else if (code === 0) {
|
||||
// 旧密码错误
|
||||
|
||||
}
|
||||
else {
|
||||
// 其他错误
|
||||
ms.error(`${t('common.failed')}:${msg}`)
|
||||
}
|
||||
}).finally(() => {
|
||||
updatePasswordModalState.value.loading = false
|
||||
}).catch(() => {
|
||||
ms.error(t('common.serverError'))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
dialog.warning({
|
||||
title: t('common.warning'),
|
||||
content: t('settingUserInfo.confirmLogoutText'),
|
||||
positiveText: t('common.confirm'),
|
||||
negativeText: t('common.cancel'),
|
||||
onPositiveClick: () => {
|
||||
logoutApi()
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-slate-200 p-2 h-full">
|
||||
<NCard style="border-radius:10px" size="small">
|
||||
<div>
|
||||
<div class="text-slate-500 font-bold">
|
||||
{{ $t('common.username') }}
|
||||
</div>
|
||||
{{ authStore.userInfo?.username }}
|
||||
</div>
|
||||
|
||||
<div class="mt-[10px]">
|
||||
<div class="text-slate-500 font-bold">
|
||||
{{ $t('common.nikeName') }}
|
||||
</div>
|
||||
|
||||
<div v-if="!isEditNickNameStatus">
|
||||
{{ authStore.userInfo?.name }}
|
||||
|
||||
<NButton size="small" text type="info" @click="isEditNickNameStatus = !isEditNickNameStatus">
|
||||
{{ $t('common.edit') }}
|
||||
</NButton>
|
||||
</div>
|
||||
|
||||
<div v-else class="flex items-center">
|
||||
<div class="max-w-[150px]">
|
||||
<NInput v-model:value="nickName" type="text" :placeholder="$t('common.inputPlaceholder')" />
|
||||
</div>
|
||||
<NButton size="small" quaternary type="info" @click="handleSaveInfo">
|
||||
{{ $t('common.save') }}
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NDivider style="margin: 10px 0;" dashed />
|
||||
<div>
|
||||
<NButton size="small" text type="info" @click="updatePasswordModalState.show = !updatePasswordModalState.show">
|
||||
{{ $t('settingUserInfo.updatePassword') }}
|
||||
</NButton>
|
||||
</div>
|
||||
</NCard>
|
||||
|
||||
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
|
||||
<NButton size="small" text type="error" @click="handleLogout">
|
||||
<template #icon>
|
||||
<SvgIcon icon="tabler:logout" />
|
||||
</template>
|
||||
{{ $t('settingUserInfo.logout') }}
|
||||
</NButton>
|
||||
</NCard>
|
||||
|
||||
<RoundCardModal v-model:show="updatePasswordModalState.show" size="small" preset="card" style="width: 400px" :title="$t('settingUserInfo.updatePassword')">
|
||||
<NForm ref="formRef" :model="updatePasswordModalState.form" :rules="updatePasswordModalFormRules">
|
||||
<NFormItem path="oldPassword" :label="$t('settingUserInfo.oldPassword')">
|
||||
<NInput v-model:value="updatePasswordModalState.form.oldPassword" :maxlength="20" type="password" :placeholder="$t('settingUserInfo.oldPassword')" />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem path="password" :label="$t('settingUserInfo.newPassword')">
|
||||
<NInput v-model:value="updatePasswordModalState.form.password" :maxlength="20" type="password" :placeholder="$t('settingUserInfo.newPassword')" />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem path="confirmPassword" :label="$t('settingUserInfo.confirmPassword')">
|
||||
<NInput v-model:value="updatePasswordModalState.form.confirmPassword" :maxlength="20" type="password" :placeholder="$t('settingUserInfo.confirmPassword')" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
|
||||
<template #footer>
|
||||
<div class="float-right">
|
||||
<NButton type="success" size="small" :loading="updatePasswordModalState.loading" @click="handleUpdatePassword">
|
||||
{{ $t('common.save') }}
|
||||
</NButton>
|
||||
</div>
|
||||
</template>
|
||||
</RoundCardModal>
|
||||
</div>
|
||||
</template>
|
@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineEmits, defineProps, ref, watch } from 'vue'
|
||||
import type { FormInst, FormRules } from 'naive-ui'
|
||||
import { NButton, NForm, NFormItem, NInput, useMessage } from 'naive-ui'
|
||||
import { NButton, NForm, NFormItem, NInput, NSelect, useMessage } from 'naive-ui'
|
||||
import { edit as userManageEdit } from '@/api/panel/users'
|
||||
import { RoundCardModal } from '@/components/common'
|
||||
import { t } from '@/locales'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
@ -30,37 +31,43 @@ const formInitValue = {
|
||||
const model = ref<User.Info>(formInitValue)
|
||||
const formRef = ref<FormInst | null>(null)
|
||||
|
||||
const roleOtions = ref([
|
||||
{
|
||||
label: t('common.role.regularUser'),
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: t('common.role.admin'),
|
||||
value: 1,
|
||||
},
|
||||
])
|
||||
|
||||
const rules: FormRules = {
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
message: '请输入账号且大于5个字符',
|
||||
message: t('adminSettingUsers.formRules.usernameRequired'),
|
||||
min: 5,
|
||||
},
|
||||
{
|
||||
trigger: 'blur',
|
||||
message: '请输入邮箱作为账号',
|
||||
type: 'email',
|
||||
},
|
||||
],
|
||||
role: {
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
type: 'number',
|
||||
message: '请选择角色',
|
||||
},
|
||||
status: {
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
type: 'number',
|
||||
message: '请选择账号状态',
|
||||
message: t('adminSettingUsers.formRules.roleRequired'),
|
||||
},
|
||||
// status: {
|
||||
// required: true,
|
||||
// trigger: 'blur',
|
||||
// type: 'number',
|
||||
// message: '请选择账号状态',
|
||||
// },
|
||||
password: {
|
||||
trigger: 'blur',
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: '6-20个字符',
|
||||
message: t('adminSettingUsers.formRules.passwordLimit'),
|
||||
},
|
||||
}
|
||||
|
||||
@ -86,7 +93,7 @@ const add = async () => {
|
||||
emit('done', res.data.id as number)
|
||||
|
||||
else if (res.code !== -1)
|
||||
message.warning('操作失败')
|
||||
message.warning(t('common.failed'))
|
||||
}
|
||||
|
||||
const handleValidateButtonClick = (e: MouseEvent) => {
|
||||
@ -101,25 +108,32 @@ const handleValidateButtonClick = (e: MouseEvent) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RoundCardModal v-model:show="show" size="small" preset="card" style="width: 400px" :title="`${userInfo?.id ? '编辑' : '添加'}用户`">
|
||||
<RoundCardModal v-model:show="show" size="small" preset="card" style="width: 400px" :title="`${userInfo?.id ? $t('common.edit') : $t('common.add')}`">
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem path="username" label="账号">
|
||||
<NInput v-model:value="model.username" type="text" placeholder="邮箱地址作为账号" />
|
||||
<NFormItem path="username" :label="$t('common.username')">
|
||||
<NInput v-model:value="model.username" type="text" :placeholder="$t('common.inputPlaceholder')" />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem path="name" label="昵称">
|
||||
<NInput v-model:value="model.name" type="text" placeholder="请输入昵称" />
|
||||
<NFormItem path="name" :label="$t('common.nikeName')">
|
||||
<NInput v-model:value="model.name" type="text" :placeholder="$t('common.inputPlaceholder')" />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem path="password" label="密码">
|
||||
<NInput v-model:value="model.password" :maxlength="20" type="password" :placeholder="`${userInfo?.id ? '请输入新密码,留空密码不变' : '请输入密码'}`" />
|
||||
<NFormItem path="role" :label="$t('adminSettingUsers.role')">
|
||||
<NSelect
|
||||
v-model:value="model.role"
|
||||
:options="roleOtions"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem path="password" :label="$t('common.password')">
|
||||
<NInput v-model:value="model.password" :maxlength="20" type="password" :placeholder="`${userInfo?.id ? $t('adminSettingUsers.EditpasswordPlaceholder') : $t('adminSettingUsers.passwordPlaceholder')}`" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
|
||||
<template #footer>
|
||||
<div class="float-right">
|
||||
<NButton type="success" size="small" @click="handleValidateButtonClick">
|
||||
保存
|
||||
{{ $t('common.save') }}
|
||||
</NButton>
|
||||
</div>
|
||||
</template>
|
@ -1,19 +1,22 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, onMounted, reactive, ref } from 'vue'
|
||||
import { NAlert, NButton, NDataTable, NDropdown, useDialog, useMessage } from 'naive-ui'
|
||||
import { NAlert, NButton, NDataTable, NDropdown, NTag, 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'
|
||||
import { getPublicVisitUser, setPublicVisitUser, deletes as usersDeletes, getList as usersGetList } from '@/api/panel/users'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { useUserStore } from '@/store'
|
||||
import { useAuthStore } from '@/store'
|
||||
import { t } from '@/locales'
|
||||
import { AdminAuthRole } from '@/enums/admin'
|
||||
|
||||
const message = useMessage()
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
const tableIsLoading = ref<boolean>(false)
|
||||
const editUserDialogShow = ref<boolean>(false)
|
||||
const keyWord = ref<string>()
|
||||
const editUserUserInfo = ref<User.Info>()
|
||||
const dialog = useDialog()
|
||||
const publicVisitUserId = ref<number | null>(null)
|
||||
|
||||
const createColumns = ({
|
||||
update,
|
||||
@ -22,20 +25,38 @@ const createColumns = ({
|
||||
}): DataTableColumns<User.Info> => {
|
||||
return [
|
||||
{
|
||||
title: '账号',
|
||||
title: t('common.username'),
|
||||
key: 'username',
|
||||
render(row: User.Info) {
|
||||
if (row.username === userStore.userInfo.username)
|
||||
return `${row.username} (当前账号)`
|
||||
return row.username
|
||||
let publicVisitHtml = ''
|
||||
if (publicVisitUserId.value && publicVisitUserId.value === row.id)
|
||||
publicVisitHtml = `[${t('adminSettingUsers.pblicText')}]-`
|
||||
|
||||
if (row.username === authStore.userInfo?.username)
|
||||
return `${publicVisitHtml}${row.username} (${t('adminSettingUsers.currentUseUsername')})`
|
||||
return publicVisitHtml + row.username
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '昵称',
|
||||
title: t('common.nikeName'),
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
title: t('adminSettingUsers.role'),
|
||||
key: 'role',
|
||||
render(row) {
|
||||
switch (row.role) {
|
||||
case AdminAuthRole.admin:
|
||||
return h(NTag, { type: 'info' }, t('common.role.admin'))
|
||||
case AdminAuthRole.regularUser:
|
||||
return h(NTag, t('common.role.regularUser'))
|
||||
default:
|
||||
return '-'
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('common.action'),
|
||||
key: '',
|
||||
render(row) {
|
||||
const btn = h(
|
||||
@ -59,17 +80,34 @@ const createColumns = ({
|
||||
return h(NDropdown, {
|
||||
trigger: 'click',
|
||||
onSelect(key: string | number) {
|
||||
console.log(key)
|
||||
switch (key) {
|
||||
case 'update':
|
||||
update(row)
|
||||
break
|
||||
case 'publicMode':
|
||||
// 取消
|
||||
if (publicVisitUserId.value && publicVisitUserId.value === row.id) {
|
||||
setPublicVisitUser(null).then(({ code }) => {
|
||||
if (code === 0)
|
||||
publicVisitUserId.value = null
|
||||
})
|
||||
}
|
||||
else {
|
||||
// 设置
|
||||
setPublicVisitUser(row.id as number).then(({ code }) => {
|
||||
if (code === 0)
|
||||
publicVisitUserId.value = row.id as number
|
||||
else if (code === 1111)
|
||||
message.error('用户不存在,请刷新后再试')
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'delete':
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: `你确定删除${row.name}(${row.username})?`,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
title: t('common.warning'),
|
||||
content: t('adminSettingUsers.deletePromptContent', { name: row.name, username: row.username }),
|
||||
positiveText: t('common.confirm'),
|
||||
negativeText: t('common.cancel'),
|
||||
onPositiveClick: () => {
|
||||
deletes([row.id as number])
|
||||
},
|
||||
@ -82,11 +120,15 @@ const createColumns = ({
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: '修改信息',
|
||||
label: t('common.edit'),
|
||||
key: 'update',
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
label: t('adminSettingUsers.setOrUnsetPublicMode'),
|
||||
key: 'publicMode',
|
||||
},
|
||||
{
|
||||
label: t('common.delete'),
|
||||
key: 'delete',
|
||||
},
|
||||
],
|
||||
@ -120,7 +162,7 @@ const pagination = reactive({
|
||||
getList(null)
|
||||
},
|
||||
prefix(item: PaginationProps) {
|
||||
return `共 ${item.itemCount} 位用户`
|
||||
return t('adminSettingUsers.userCountText', { count: item.itemCount })
|
||||
},
|
||||
})
|
||||
|
||||
@ -136,7 +178,7 @@ function handleAdd() {
|
||||
|
||||
function handelDone() {
|
||||
editUserDialogShow.value = false
|
||||
message.success('操作成功')
|
||||
message.success(t('common.success'))
|
||||
getList(null)
|
||||
}
|
||||
|
||||
@ -159,24 +201,27 @@ async function getList(page: number | null) {
|
||||
async function deletes(ids: number[]) {
|
||||
const { code } = await usersDeletes(ids)
|
||||
if (code === 0) {
|
||||
message.success('已删除')
|
||||
message.success(t('common.deleteSuccess'))
|
||||
getList(null)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getPublicVisitUser<User.Info>().then(({ data }) => {
|
||||
publicVisitUserId.value = data.id || null
|
||||
})
|
||||
getList(null)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[500px] overflow-auto">
|
||||
<div class="overflow-auto pt-2">
|
||||
<NAlert type="info" :bordered="false">
|
||||
账号之间的数据不互通
|
||||
{{ $t('adminSettingUsers.alertText') }}
|
||||
</NAlert>
|
||||
<div class="my-[10px]">
|
||||
<NButton type="primary" size="small" ghost @click="handleAdd">
|
||||
添加
|
||||
{{ $t('common.add') }}
|
||||
</NButton>
|
||||
</div>
|
||||
|
15
src/components/apps/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import About from './About/index.vue'
|
||||
import ImportExport from './ImportExport/index.vue'
|
||||
import ItemGroupManage from './ItemGroupManage/index.vue'
|
||||
import Style from './Style/index.vue'
|
||||
import UserInfo from './UserInfo/index.vue'
|
||||
import Users from './Users/index.vue'
|
||||
|
||||
export {
|
||||
About,
|
||||
ImportExport,
|
||||
ItemGroupManage,
|
||||
Style,
|
||||
UserInfo,
|
||||
Users,
|
||||
}
|
46
src/components/common/AppLoader/index.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, onMounted, shallowRef, watch } from 'vue'
|
||||
import { NSpin } from 'naive-ui'
|
||||
|
||||
const props = defineProps<{
|
||||
componentName: string | null
|
||||
}>()
|
||||
const loading = shallowRef(false)
|
||||
const dynamicComponent = shallowRef('')
|
||||
|
||||
function updateComponent() {
|
||||
loading.value = true
|
||||
dynamicComponent.value = defineAsyncComponent(() =>
|
||||
import(`../../apps/${props.componentName}/index.vue`)
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
}).catch(() => {
|
||||
// 组件不存在
|
||||
dynamicComponent.value = ''
|
||||
return null
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
watch(() => props.componentName, () => {
|
||||
updateComponent()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
updateComponent()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<NSpin :show="loading" style="height: 100%;" content-style="height: 100%;" :delay="500" description="loading...">
|
||||
<component :is="dynamicComponent" v-if="dynamicComponent" />
|
||||
<!-- <component :is="getComponent(componentName || '')" v-if="dynamicComponent" /> -->
|
||||
<div
|
||||
v-else-if="!dynamicComponent"
|
||||
>
|
||||
Component not found!
|
||||
</div>
|
||||
</NSpin>
|
||||
</div>
|
||||
</template>
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { NAvatar, NImage } from 'naive-ui'
|
||||
import { computed, ref, withDefaults } from 'vue'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { SvgIconOnline } from '@/components/common'
|
||||
|
||||
interface Prop {
|
||||
itemIcon?: Panel.ItemIcon | null
|
||||
@ -39,7 +39,7 @@ const iconExt = computed(() => {
|
||||
|
||||
<div v-else-if="itemIcon?.itemType === 3">
|
||||
<NAvatar :size="props.size" :style="{ backgroundColor: (forceBackground ?? itemIcon?.backgroundColor) || defaultBackground }">
|
||||
<SvgIcon style="font-size: 35px;" :icon="itemIcon.text" />
|
||||
<SvgIconOnline style="font-size: 35px;" :icon="itemIcon.text" />
|
||||
</NAvatar>
|
||||
</div>
|
||||
</div>
|
||||
|
43
src/components/common/JsonImportExport/index.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const jsonData = ref<string | null>(null)
|
||||
|
||||
const handleFileChange = (event: Event) => {
|
||||
const input = event.target as HTMLInputElement
|
||||
const file = input.files?.[0]
|
||||
|
||||
if (file) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
if (reader.result)
|
||||
jsonData.value = reader.result as string
|
||||
}
|
||||
reader.readAsText(file)
|
||||
}
|
||||
}
|
||||
|
||||
const exportJson = () => {
|
||||
if (jsonData.value) {
|
||||
const blob = new Blob([jsonData.value], { type: 'application/json' })
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = 'exported_data.json'
|
||||
link.click()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<input type="file" @change="handleFileChange">
|
||||
<button @click="exportJson">
|
||||
导出JSON
|
||||
</button>
|
||||
|
||||
<div v-if="jsonData">
|
||||
<h2>JSON 数据</h2>
|
||||
<pre>{{ jsonData }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -1,55 +0,0 @@
|
||||
<script setup lang='ts'>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { NSpin } from 'naive-ui'
|
||||
import { fetchChatConfig } from '@/api'
|
||||
import { getAboutDescription as getAboutDescriptionApi } from '@/api/openness'
|
||||
|
||||
interface ConfigState {
|
||||
timeoutMs?: number
|
||||
reverseProxy?: string
|
||||
apiModel?: string
|
||||
socksProxy?: string
|
||||
httpsProxy?: string
|
||||
usage?: string
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const config = ref<ConfigState>()
|
||||
|
||||
const content = ref<string>()
|
||||
|
||||
async function fetchConfig() {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data } = await fetchChatConfig<ConfigState>()
|
||||
config.value = data
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchConfig()
|
||||
getAboutDescription()
|
||||
})
|
||||
|
||||
async function getAboutDescription() {
|
||||
const { data } = await getAboutDescriptionApi<string>()
|
||||
content.value = data
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpin :show="loading">
|
||||
<div class="p-4 space-y-4">
|
||||
<h2 class="text-xl font-bold">
|
||||
{{ $t('common.appName') }}
|
||||
</h2>
|
||||
<div>
|
||||
<span v-html="content" />
|
||||
</div>
|
||||
</div>
|
||||
</NSpin>
|
||||
</template>
|
@ -1,225 +0,0 @@
|
||||
<!-- eslint-disable eslint-comments/no-unlimited-disable
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { NButton, NInput, NPopconfirm, NSelect, useMessage } from 'naive-ui'
|
||||
import type { Language, Theme } from '@/store/modules/app/helper'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { useAppStore, useUserStore } from '@/store'
|
||||
import type { UserInfo } from '@/store/modules/user/helper'
|
||||
import { getCurrentDate } from '@/utils/functions'
|
||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||
import { t } from '@/locales'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const { isMobile } = useBasicLayout()
|
||||
|
||||
const ms = useMessage()
|
||||
|
||||
const theme = computed(() => appStore.theme)
|
||||
|
||||
const userInfo = computed(() => userStore.userInfo)
|
||||
|
||||
const avatar = ref(userInfo.value.headImage ?? '')
|
||||
|
||||
const name = ref(userInfo.value.name ?? '')
|
||||
|
||||
// const description = ref(userInfo.value.description ?? '')
|
||||
|
||||
const language = computed({
|
||||
get() {
|
||||
return appStore.language
|
||||
},
|
||||
set(value: Language) {
|
||||
appStore.setLanguage(value)
|
||||
},
|
||||
})
|
||||
|
||||
const themeOptions: { label: string; key: Theme; icon: string }[] = [
|
||||
{
|
||||
label: 'Auto',
|
||||
key: 'auto',
|
||||
icon: 'ri:contrast-line',
|
||||
},
|
||||
{
|
||||
label: 'Light',
|
||||
key: 'light',
|
||||
icon: 'ri:sun-foggy-line',
|
||||
},
|
||||
{
|
||||
label: 'Dark',
|
||||
key: 'dark',
|
||||
icon: 'ri:moon-foggy-line',
|
||||
},
|
||||
]
|
||||
|
||||
const languageOptions: { label: string; key: Language; value: Language }[] = [
|
||||
{ label: '简体中文', key: 'zh-CN', value: 'zh-CN' },
|
||||
{ label: '繁體中文', key: 'zh-TW', value: 'zh-TW' },
|
||||
{ label: 'English', key: 'en-US', value: 'en-US' },
|
||||
{ label: '한국어', key: 'ko-KR', value: 'ko-KR' },
|
||||
]
|
||||
|
||||
function updateUserInfo(options: Partial<UserInfo>) {
|
||||
// userStore.updateUserInfo(options)
|
||||
ms.success(t('common.success'))
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
userStore.resetUserInfo()
|
||||
ms.success(t('common.success'))
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
function exportData(): void {
|
||||
const date = getCurrentDate()
|
||||
const data: string = localStorage.getItem('chatStorage') || '{}'
|
||||
const jsonString: string = JSON.stringify(JSON.parse(data), null, 2)
|
||||
const blob: Blob = new Blob([jsonString], { type: 'application/json' })
|
||||
const url: string = URL.createObjectURL(blob)
|
||||
const link: HTMLAnchorElement = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `chat-store_${date}.json`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
|
||||
function importData(event: Event): void {
|
||||
const target = event.target as HTMLInputElement
|
||||
if (!target || !target.files)
|
||||
return
|
||||
|
||||
const file: File = target.files[0]
|
||||
if (!file)
|
||||
return
|
||||
|
||||
const reader: FileReader = new FileReader()
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const data = JSON.parse(reader.result as string)
|
||||
localStorage.setItem('chatStorage', JSON.stringify(data))
|
||||
ms.success(t('common.success'))
|
||||
location.reload()
|
||||
}
|
||||
catch (error) {
|
||||
ms.error(t('common.invalidFileFormat'))
|
||||
}
|
||||
}
|
||||
reader.readAsText(file)
|
||||
}
|
||||
|
||||
function clearData(): void {
|
||||
localStorage.removeItem('chatStorage')
|
||||
location.reload()
|
||||
}
|
||||
|
||||
function handleImportButtonClick(): void {
|
||||
const fileInput = document.getElementById('fileInput') as HTMLElement
|
||||
if (fileInput)
|
||||
fileInput.click()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4 space-y-5 min-h-[200px]">
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.avatarLink') }}</span>
|
||||
<div class="flex-1">
|
||||
<NInput v-model:value="avatar" placeholder="" />
|
||||
</div>
|
||||
<NButton size="tiny" text type="primary" @click="updateUserInfo({ headImage })">
|
||||
{{ $t('common.save') }}
|
||||
</NButton>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.name') }}</span>
|
||||
<div class="w-[200px]">
|
||||
<NInput v-model:value="name" placeholder="" />
|
||||
</div>
|
||||
<NButton size="tiny" text type="primary" @click="updateUserInfo({ name })">
|
||||
{{ $t('common.save') }}
|
||||
</NButton>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.description') }}</span>
|
||||
<div class="flex-1">
|
||||
<NInput v-model:value="description" placeholder="" />
|
||||
</div>
|
||||
<NButton size="tiny" text type="primary" @click="updateUserInfo({ description })">
|
||||
{{ $t('common.save') }}
|
||||
</NButton>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center space-x-4"
|
||||
:class="isMobile && 'items-start'"
|
||||
>
|
||||
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.chatHistory') }}</span>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<NButton size="small" @click="exportData">
|
||||
<template #icon>
|
||||
<SvgIcon icon="ri:download-2-fill" />
|
||||
</template>
|
||||
{{ $t('common.export') }}
|
||||
</NButton>
|
||||
|
||||
<input id="fileInput" type="file" style="display:none" @change="importData">
|
||||
<NButton size="small" @click="handleImportButtonClick">
|
||||
<template #icon>
|
||||
<SvgIcon icon="ri:upload-2-fill" />
|
||||
</template>
|
||||
{{ $t('common.import') }}
|
||||
</NButton>
|
||||
|
||||
<NPopconfirm placement="bottom" @positive-click="clearData">
|
||||
<template #trigger>
|
||||
<NButton size="small">
|
||||
<template #icon>
|
||||
<SvgIcon icon="ri:close-circle-line" />
|
||||
</template>
|
||||
{{ $t('common.clear') }}
|
||||
</NButton>
|
||||
</template>
|
||||
{{ $t('chat.clearHistoryConfirm') }}
|
||||
</NPopconfirm>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.theme') }}</span>
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<template v-for="item of themeOptions" :key="item.key">
|
||||
<NButton
|
||||
size="small"
|
||||
:type="item.key === theme ? 'primary' : undefined"
|
||||
@click="appStore.setTheme(item.key)"
|
||||
>
|
||||
<template #icon>
|
||||
<SvgIcon :icon="item.icon" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.language') }}</span>
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<NSelect
|
||||
style="width: 140px"
|
||||
:value="language"
|
||||
:options="languageOptions"
|
||||
@update-value="value => appStore.setLanguage(value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.resetUserInfo') }}</span>
|
||||
<NButton size="small" @click="handleReset">
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template> -->
|
@ -1,62 +0,0 @@
|
||||
<script setup lang='ts'>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { NButton, NInput, NInputGroup, NSpin } from 'naive-ui'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { getReferralCode as getReferralCodeApi } from '@/api/system/user'
|
||||
// import { copyText } from '@/utils/format'
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const referralCode = ref<string>('')
|
||||
const url = ref<string>('')
|
||||
const copyButtonText = ref<string>('')
|
||||
|
||||
async function getReferralCode() {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data } = await getReferralCodeApi<User.GetReferralCodeResponse>()
|
||||
referralCode.value = data.referralCode
|
||||
url.value = `${getCurrentDomain()}/#/register?referralCode=${referralCode.value}`
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentDomain() {
|
||||
return `${location.protocol}//${location.hostname}${location.port === '' ? '' : (`:${location.port}`)}`
|
||||
}
|
||||
|
||||
function handleCopy() {
|
||||
const { copy } = useClipboard()
|
||||
// copyText({ text: urlCopy })
|
||||
copy(url.value)
|
||||
copyButtonText.value = '复制完成!'
|
||||
setInterval(() => {
|
||||
copyButtonText.value = '复制链接'
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getReferralCode()
|
||||
copyButtonText.value = '复制链接'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpin :show="loading">
|
||||
<div class="p-4 space-y-4">
|
||||
<h2 class="text-xl ">
|
||||
您的专属推荐链接
|
||||
</h2>
|
||||
<div>
|
||||
<NInputGroup>
|
||||
<NInput v-model:value="url" readonly type="text" />
|
||||
<NButton type="primary" ghost @click="handleCopy">
|
||||
{{ copyButtonText }}
|
||||
</NButton>
|
||||
</NInputGroup>
|
||||
</div>
|
||||
</div>
|
||||
</NSpin>
|
||||
</template>
|
@ -1,227 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import type { UploadFileInfo } from 'naive-ui'
|
||||
import { NAvatar, NButton, NInput, NUpload, useDialog, useMessage } from 'naive-ui'
|
||||
// import type { Language, Theme } from '@/store/modules/app/helper'
|
||||
import type { Theme } from '@/store/modules/app/helper'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { useAppStore, useAuthStore, useUserStore } from '@/store'
|
||||
import type { UserInfo } from '@/store/modules/user/helper'
|
||||
import { t } from '@/locales'
|
||||
import { UserUpdateInfo, logout } from '@/api'
|
||||
import { router } from '@/router'
|
||||
|
||||
// import defaultAvatar from '@/assets/userDefaultAvatar.png'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const ms = useMessage()
|
||||
const dialog = useDialog()
|
||||
|
||||
const theme = computed(() => appStore.theme)
|
||||
|
||||
const userInfo = computed(() => userStore.userInfo)
|
||||
|
||||
const avatar = ref(userInfo.value.headImage ?? '')
|
||||
|
||||
const name = ref(userInfo.value.name ?? '')
|
||||
|
||||
// const description = ref(userInfo.value.description ?? '')
|
||||
|
||||
// const language = computed({
|
||||
// get() {
|
||||
// return appStore.language
|
||||
// },
|
||||
// set(value: Language) {
|
||||
// appStore.setLanguage(value)
|
||||
// },
|
||||
// })
|
||||
|
||||
const themeOptions: { label: string; key: Theme; icon: string }[] = [
|
||||
{
|
||||
label: 'Auto',
|
||||
key: 'auto',
|
||||
icon: 'ri:contrast-line',
|
||||
},
|
||||
{
|
||||
label: 'Light',
|
||||
key: 'light',
|
||||
icon: 'ri:sun-foggy-line',
|
||||
},
|
||||
{
|
||||
label: 'Dark',
|
||||
key: 'dark',
|
||||
icon: 'ri:moon-foggy-line',
|
||||
},
|
||||
]
|
||||
|
||||
// const languageOptions: { label: string; key: Language; value: Language }[] = [
|
||||
// { label: '简体中文', key: 'zh-CN', value: 'zh-CN' },
|
||||
// { label: '繁體中文', key: 'zh-TW', value: 'zh-TW' },
|
||||
// { label: 'English', key: 'en-US', value: 'en-US' },
|
||||
// { label: '한국어', key: 'ko-KR', value: 'ko-KR' },
|
||||
// ]
|
||||
|
||||
function updateUserInfo(options: Partial<UserInfo>) {
|
||||
userStore.updateUserInfo({
|
||||
headImage: userInfo.value.headImage,
|
||||
name: options.name as string,
|
||||
})
|
||||
UserUpdateInfo(userInfo.value.headImage as string, options.name as string)
|
||||
ms.success(t('common.success'))
|
||||
}
|
||||
|
||||
// function uploadHeadImage(options: Partial<UserInfo>) {
|
||||
|
||||
// }
|
||||
|
||||
function onLogoutClick() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要退出登录',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
logoutApi()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function logoutApi() {
|
||||
await logout()
|
||||
userStore.resetUserInfo()
|
||||
authStore.removeToken()
|
||||
ms.success('您已经安全退出,期待与你再次相见!')
|
||||
router.push({ path: '/login' })
|
||||
}
|
||||
|
||||
const handleFinish = ({
|
||||
file,
|
||||
event,
|
||||
}: {
|
||||
file: UploadFileInfo
|
||||
event?: ProgressEvent
|
||||
}) => {
|
||||
const res = JSON.parse((event?.target as XMLHttpRequest).response)
|
||||
// {
|
||||
// "code": 0,
|
||||
// "data": {
|
||||
// "imageUrl": "/uploads/2023/5/12/c94306cfdf37fe7844753cd98fd57aaf.jpg"
|
||||
// },
|
||||
// "msg": "OK"
|
||||
// }
|
||||
const imageUrl = res.data.imageUrl
|
||||
userStore.updateUserInfo({
|
||||
headImage: imageUrl,
|
||||
name: userInfo.value.name,
|
||||
})
|
||||
avatar.value = imageUrl
|
||||
UserUpdateInfo(imageUrl, userInfo.value.name || '')
|
||||
|
||||
return file
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4 space-y-5 min-h-[200px]">
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">账号/邮箱</span>
|
||||
<div class="w-[200px]">
|
||||
<NInput v-model:value="userInfo.username" :disabled="true" readonly placeholder="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">密码</span>
|
||||
<div class="w-[200px]">
|
||||
<NButton @click="router.push({ path: '/resetPassword', query: { u: userInfo.username } })">
|
||||
重置密码
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.avatar') }}</span>
|
||||
<div class="w-[50px]">
|
||||
<NAvatar
|
||||
round
|
||||
:size="48"
|
||||
:src="avatar ? avatar : ''"
|
||||
/>
|
||||
</div>
|
||||
<NUpload
|
||||
action="/api/file/uploadImg"
|
||||
name="imgfile"
|
||||
:headers="{
|
||||
token: authStore.token as string,
|
||||
}"
|
||||
@finish="handleFinish"
|
||||
>
|
||||
<NButton size="tiny" text type="primary">
|
||||
上传文件
|
||||
</NButton>
|
||||
</NUpload>
|
||||
<!-- <NButton size="tiny" text type="primary" @click="uploadHeadImage({ avatar })">
|
||||
选择文件
|
||||
</NButton> -->
|
||||
</div>
|
||||
<!-- <div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.avatarLink') }}</span>
|
||||
<div class="flex-1">
|
||||
<NInput v-model:value="avatar" placeholder="" />
|
||||
</div>
|
||||
<NButton size="tiny" text type="primary" @click="updateUserInfo({ avatar })">
|
||||
{{ $t('common.save') }}
|
||||
</NButton>
|
||||
</div> -->
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.name') }}</span>
|
||||
<div class="w-[200px]">
|
||||
<NInput v-model:value="name" placeholder="" />
|
||||
</div>
|
||||
<NButton size="tiny" text type="primary" @click="updateUserInfo({ name })">
|
||||
{{ $t('common.save') }}
|
||||
</NButton>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.theme') }}</span>
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<template v-for="item of themeOptions" :key="item.key">
|
||||
<NButton
|
||||
size="small"
|
||||
:type="item.key === theme ? 'primary' : undefined"
|
||||
@click="appStore.setTheme(item.key)"
|
||||
>
|
||||
<template #icon>
|
||||
<SvgIcon :icon="item.icon" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.language') }}</span>
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<NSelect
|
||||
style="width: 140px"
|
||||
:value="language"
|
||||
:options="languageOptions"
|
||||
@update-value="value => appStore.setLanguage(value)"
|
||||
/>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]" />
|
||||
<NButton size="small" type="error" strong secondary @click="onLogoutClick">
|
||||
<template #icon>
|
||||
<SvgIcon icon="oi:account-logout" />
|
||||
</template>
|
||||
安全退出账号
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -1,80 +0,0 @@
|
||||
<script setup lang='ts'>
|
||||
import { computed, ref } from 'vue'
|
||||
import { NModal, NTabPane, NTabs } from 'naive-ui'
|
||||
import UserInfo from './UserInfo.vue'
|
||||
// import Advanced from './Advanced.vue'
|
||||
import About from './About.vue'
|
||||
import InviteUsers from './InviteUsers.vue'
|
||||
|
||||
// import { useAuthStore } from '@/store'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
interface Emit {
|
||||
(e: 'update:visible', visible: boolean): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
// const authStore = useAuthStore()
|
||||
|
||||
// const isChatGPTAPI = computed<boolean>(() => !!authStore.isChatGPTAPI)
|
||||
|
||||
const active = ref('General')
|
||||
|
||||
const show = computed({
|
||||
get() {
|
||||
return props.visible
|
||||
},
|
||||
set(visible: boolean) {
|
||||
emit('update:visible', visible)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal v-model:show="show" :auto-focus="false" preset="card" style="width: 95%; max-width: 640px">
|
||||
<div>
|
||||
<NTabs v-model:value="active" type="line" animated>
|
||||
<NTabPane name="General" tab="General">
|
||||
<template #tab>
|
||||
<SvgIcon class="text-lg" icon="ri:file-user-line" />
|
||||
<span class="ml-2">设置</span>
|
||||
</template>
|
||||
<div class="min-h-[100px]">
|
||||
<UserInfo />
|
||||
</div>
|
||||
</NTabPane>
|
||||
<!-- <NTabPane v-if="isChatGPTAPI" name="Advanced" tab="Advanced">
|
||||
<template #tab>
|
||||
<SvgIcon class="text-lg" icon="ri:equalizer-line" />
|
||||
<span class="ml-2">{{ $t('setting.advanced') }}</span>
|
||||
</template>
|
||||
<div class="min-h-[100px]">
|
||||
<Advanced />
|
||||
</div>
|
||||
</NTabPane> -->
|
||||
<NTabPane name="InviteUsers" tab="InviteUsers">
|
||||
<template #tab>
|
||||
<SvgIcon class="text-lg" icon="mdi:invite" />
|
||||
<span class="ml-2">邀请新用户</span>
|
||||
</template>
|
||||
<InviteUsers />
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane name="About" tab="about">
|
||||
<template #tab>
|
||||
<SvgIcon class="text-lg" icon="mdi:about-circle-outline" />
|
||||
<span class="ml-2">关于</span>
|
||||
</template>
|
||||
<About />
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</div>
|
||||
</NModal>
|
||||
</template>
|
@ -1,21 +1,52 @@
|
||||
<script setup lang='ts'>
|
||||
import { computed, useAttrs } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
|
||||
interface Props {
|
||||
icon?: string
|
||||
function compatibleName(inputString: string): string {
|
||||
// 使用正则表达式替换所有的冒号
|
||||
const resultString = inputString.replace(/:/g, '-')
|
||||
return resultString
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
export default defineComponent({
|
||||
name: 'SvgIcon',
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const symbolId = computed(() => `#${compatibleName(props.icon)}`)
|
||||
const svgClass = computed(() => {
|
||||
if (props.className)
|
||||
return `svg-icon ${props.className}`
|
||||
|
||||
const attrs = useAttrs()
|
||||
return 'svg-icon'
|
||||
})
|
||||
|
||||
const bindAttrs = computed<{ class: string; style: string }>(() => ({
|
||||
class: (attrs.class as string) || '',
|
||||
style: (attrs.style as string) || '',
|
||||
}))
|
||||
return {
|
||||
symbolId,
|
||||
svgClass,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Icon :icon="icon ?? ''" v-bind="bindAttrs" />
|
||||
<svg :class="svgClass" aria-hidden="true">
|
||||
<use class="svg-use" :href="symbolId" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.svg-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
21
src/components/common/SvgIconOnline/index.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<script setup lang='ts'>
|
||||
import { computed, useAttrs } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
|
||||
interface Props {
|
||||
icon?: string
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const attrs = useAttrs()
|
||||
|
||||
const bindAttrs = computed<{ class: string; style: string }>(() => ({
|
||||
class: (attrs.class as string) || '',
|
||||
style: (attrs.style as string) || '',
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Icon :icon="icon ?? ''" v-bind="bindAttrs" />
|
||||
</template>
|
@ -1,19 +1,23 @@
|
||||
import HoverButton from './HoverButton/index.vue'
|
||||
import SvgIcon from './SvgIcon/index.vue'
|
||||
import Setting from './Setting/index.vue'
|
||||
import Captcha from './Captcha/index.vue'
|
||||
import Verification from './Verification/index.vue'
|
||||
import ItemIcon from './ItemIcon/index.vue'
|
||||
import NaiveProvider from './NaiveProvider/index.vue'
|
||||
import RoundCardModal from './RoundCardModal/index.vue'
|
||||
import SvgIconOnline from './SvgIconOnline/index.vue'
|
||||
import JsonImportExport from './JsonImportExport/index.vue'
|
||||
import AppLoader from './AppLoader/index.vue'
|
||||
|
||||
export {
|
||||
Verification,
|
||||
HoverButton,
|
||||
SvgIcon,
|
||||
Setting,
|
||||
Captcha,
|
||||
ItemIcon,
|
||||
NaiveProvider,
|
||||
RoundCardModal,
|
||||
SvgIconOnline,
|
||||
JsonImportExport,
|
||||
AppLoader,
|
||||
}
|
||||
|
@ -59,10 +59,10 @@ onBeforeUnmount(() => {
|
||||
|
||||
<template>
|
||||
<div class="w-full text-center">
|
||||
<span class="text-3xl font-[600]">
|
||||
<span class="text-2xl sm:text-2xl md:text-3xl font-[600]">
|
||||
{{ currentDate.time }}
|
||||
</span>
|
||||
<div>
|
||||
<div class="hidden sm:hidden md:block">
|
||||
<span>
|
||||
{{ currentDate.date }}
|
||||
</span>
|
||||
|
@ -1,19 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { defineEmits, onMounted, ref } from 'vue'
|
||||
import { NAvatar, NCheckbox } from 'naive-ui'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { useModuleConfig } from '@/store/modules'
|
||||
import { useAuthStore } from '@/store'
|
||||
import { VisitMode } from '@/enums/auth'
|
||||
|
||||
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
|
||||
@ -22,8 +18,17 @@ withDefaults(defineProps<{
|
||||
textColor: 'white',
|
||||
})
|
||||
|
||||
const emits = defineEmits(['itemSearch'])
|
||||
|
||||
interface State {
|
||||
currentSearchEngine: DeskModule.SearchBox.SearchEngine
|
||||
searchEngineList: DeskModule.SearchBox.SearchEngine[]
|
||||
newWindowOpen: boolean
|
||||
}
|
||||
|
||||
const moduleConfigName = 'deskModuleSearchBox'
|
||||
const moduleConfig = useModuleConfig()
|
||||
const authStore = useAuthStore()
|
||||
const searchTerm = ref('')
|
||||
const isFocused = ref(false)
|
||||
const searchSelectListShow = ref(false)
|
||||
@ -62,6 +67,9 @@ const onBlur = (): void => {
|
||||
}
|
||||
|
||||
function handleEngineClick() {
|
||||
// 访客模式不允许修改
|
||||
if (authStore.visitMode === VisitMode.VISIT_MODE_PUBLIC)
|
||||
return
|
||||
searchSelectListShow.value = !searchSelectListShow.value
|
||||
}
|
||||
|
||||
@ -75,7 +83,7 @@ function handleSearchClick() {
|
||||
const keyword = searchTerm
|
||||
// 如果网址中存在 %s,则直接替换为关键字
|
||||
const fullUrl = replaceOrAppendKeywordToUrl(url, keyword.value)
|
||||
|
||||
handleClearSearchTerm()
|
||||
if (state.value.newWindowOpen)
|
||||
window.open(fullUrl)
|
||||
else
|
||||
@ -91,6 +99,15 @@ function replaceOrAppendKeywordToUrl(url: string, keyword: string) {
|
||||
return url + (keyword ? `${encodeURIComponent(keyword)}` : '')
|
||||
}
|
||||
|
||||
const handleItemSearch = () => {
|
||||
emits('itemSearch', searchTerm.value)
|
||||
}
|
||||
|
||||
function handleClearSearchTerm() {
|
||||
searchTerm.value = ''
|
||||
emits('itemSearch', searchTerm.value)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
moduleConfig.getValueByNameFromCloud<State>('deskModuleSearchBox').then(({ code, data }) => {
|
||||
if (code === 0)
|
||||
@ -108,9 +125,13 @@ onMounted(() => {
|
||||
<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" />
|
||||
<input v-model="searchTerm" :placeholder="$t('deskModule.searchBox.inputPlaceholder')" @focus="onFocus" @blur="onBlur" @input="handleItemSearch">
|
||||
|
||||
<div v-if="searchTerm !== ''" class="w-[25px] mr-[10px] flex justify-center cursor-pointer" @click="handleClearSearchTerm">
|
||||
<SvgIcon style="width: 20px;height: 20px;" icon="line-md:close-small" />
|
||||
</div>
|
||||
<div class="w-[25px] flex justify-center cursor-pointer" @click="handleSearchClick">
|
||||
<SvgIcon style="width: 20px;height: 20px;" icon="iconamoon:search-fill" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -138,7 +159,7 @@ onMounted(() => {
|
||||
<div class="mt-[10px]">
|
||||
<NCheckbox v-model:checked="state.newWindowOpen" @update-checked="moduleConfig.saveToCloud(moduleConfigName, state)">
|
||||
<span :style="{ color: textColor }">
|
||||
新窗口打开
|
||||
{{ $t('deskModule.searchBox.openWithNewOpen') }}
|
||||
</span>
|
||||
</NCheckbox>
|
||||
</div>
|
||||
|
4
src/enums/admin/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum AdminAuthRole {
|
||||
'admin' = 1, // 平台管理
|
||||
'regularUser' = 2, // 受限用户
|
||||
}
|
4
src/enums/auth/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum VisitMode {
|
||||
'VISIT_MODE_LOGIN' = 0, // 登录状态
|
||||
'VISIT_MODE_PUBLIC' = 1, // 公开状态
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { h } from 'vue'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { SvgIconOnline } from '@/components/common'
|
||||
|
||||
export const useIconRender = () => {
|
||||
interface IconConfig {
|
||||
@ -27,7 +27,7 @@ export const useIconRender = () => {
|
||||
if (!icon)
|
||||
window.console.warn('iconRender: icon is required')
|
||||
|
||||
return () => h(SvgIcon, { icon, style })
|
||||
return () => h(SvgIconOnline, { icon, style })
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { computed } from 'vue'
|
||||
import { enUS, koKR, zhCN, zhTW } from 'naive-ui'
|
||||
import { enUS, zhCN } from 'naive-ui'
|
||||
// import { enUS, koKR, zhCN, zhTW } from 'naive-ui'
|
||||
import { useAppStore } from '@/store'
|
||||
import { setLocale } from '@/locales'
|
||||
|
||||
@ -11,15 +12,15 @@ export function useLanguage() {
|
||||
case 'en-US':
|
||||
setLocale('en-US')
|
||||
return enUS
|
||||
case 'ko-KR':
|
||||
setLocale('ko-KR')
|
||||
return koKR
|
||||
// case 'ko-KR':
|
||||
// setLocale('ko-KR')
|
||||
// return koKR
|
||||
case 'zh-CN':
|
||||
setLocale('zh-CN')
|
||||
return zhCN
|
||||
case 'zh-TW':
|
||||
setLocale('zh-TW')
|
||||
return zhTW
|
||||
// case 'zh-TW':
|
||||
// setLocale('zh-TW')
|
||||
// return zhTW
|
||||
default:
|
||||
setLocale('zh-CN')
|
||||
return zhCN
|
||||
|
@ -1,10 +1,10 @@
|
||||
import type { App } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import enUS from './en-US'
|
||||
import koKR from './ko-KR'
|
||||
// import koKR from './ko-KR'
|
||||
import zhCN from './zh-CN'
|
||||
import zhTW from './zh-TW'
|
||||
import ruRU from './ru-RU'
|
||||
// import zhTW from './zh-TW'
|
||||
// import ruRU from './ru-RU'
|
||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
import type { Language } from '@/store/modules/app/helper'
|
||||
|
||||
@ -18,10 +18,10 @@ const i18n = createI18n({
|
||||
allowComposition: true,
|
||||
messages: {
|
||||
'en-US': enUS,
|
||||
'ko-KR': koKR,
|
||||
// 'ko-KR': koKR,
|
||||
'zh-CN': zhCN,
|
||||
'zh-TW': zhTW,
|
||||
'ru-RU': ruRU,
|
||||
// 'zh-TW': zhTW,
|
||||
// 'ru-RU': ruRU,
|
||||
},
|
||||
})
|
||||
|
||||
|