更新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的位置
This commit is contained in:
Sun 2023-12-27 17:35:41 +08:00
parent a62a4f5d62
commit c9e05a5624
130 changed files with 5326 additions and 2837 deletions

1
.gitignore vendored
View File

@ -27,6 +27,7 @@ coverage
*.njsproj
*.sln
*.sw?
.pnpm-store
# Environment variables files
/service/.env

View File

@ -2,7 +2,7 @@
"prettier.enable": false,
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"javascript",

View File

@ -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
View 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.

View File

@ -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)

View File

@ -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() {

View File

@ -1,4 +1,4 @@
> 开源不易,如果觉得我的项目有帮到你,欢迎给我打赏或者请我喝个奶茶☕,打赏不准超过你工资的一半。你的支持就是我的动力,谢谢。
> 开源不易,如果觉得我的项目有帮到你,欢迎给我打赏或者请我喝个奶茶☕(如果可以备注下您的昵称或者名字),打赏不准超过你工资的一半。你的支持就是我的动力,谢谢。
| | |
| ------------ | ------------ |

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"`
}

View File

@ -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) {

View 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
}
}

View File

@ -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) {

View File

@ -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")
}

View File

@ -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(&param, 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)
}

View File

@ -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)
}

View File

@ -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))))

View File

@ -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)

View File

@ -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

View File

@ -1 +1 @@
3|1.1.0
7|1.2.0

View File

@ -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

View File

@ -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=

View File

@ -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) // 务必退出

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View 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
}

View File

@ -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"`

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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 },
})
}

View File

@ -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
View 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 },
})
}

View File

@ -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 },
})
}

View 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

View 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

View 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

View 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

View 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

View 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="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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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="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

View 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

View 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="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

View 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 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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-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

View 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-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

View 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

View 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

View File

@ -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>

View 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('当前软件版本可能过旧,很有可能无法兼容该配置文件,请谨慎导入。推荐将软件更新到新版后再次导入')
// importItemsimportItems
// ,
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>

View File

@ -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="请输入" />

View File

@ -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"

View 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>

View 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>

View File

@ -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>

View File

@ -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>

View 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,
}

View 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>

View File

@ -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>

View 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>

View File

@ -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>

View File

@ -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> -->

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View File

@ -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,
}

View File

@ -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>

View File

@ -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
View File

@ -0,0 +1,4 @@
export enum AdminAuthRole {
'admin' = 1, // 平台管理
'regularUser' = 2, // 受限用户
}

4
src/enums/auth/index.ts Normal file
View File

@ -0,0 +1,4 @@
export enum VisitMode {
'VISIT_MODE_LOGIN' = 0, // 登录状态
'VISIT_MODE_PUBLIC' = 1, // 公开状态
}

View File

@ -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 {

View File

@ -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

View File

@ -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,
},
})

Some files were not shown because too many files have changed in this diff Show More