更新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
+14
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 },
})
}
+13
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
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 },
})
}
+22 -2
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 },
})
}
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M16.477 3.004c.167.015.24.219.12.338l-8.32 8.32a.75.75 0 0 0-.195.34l-1 3.83a.75.75 0 0 0 .915.915l3.829-1a.751.751 0 0 0 .34-.196l8.438-8.438a.198.198 0 0 1 .339.12a45.723 45.723 0 0 1-.06 10.073c-.223 1.905-1.754 3.4-3.652 3.613a47.468 47.468 0 0 1-10.461 0c-1.899-.213-3.43-1.708-3.653-3.613a45.672 45.672 0 0 1 0-10.611C3.34 4.789 4.871 3.294 6.77 3.082a47.512 47.512 0 0 1 9.707-.078Z"/><path fill="currentColor" d="M17.823 4.237a.25.25 0 0 1 .354 0l1.414 1.415a.25.25 0 0 1 0 .353L11.298 14.3a.253.253 0 0 1-.114.065l-1.914.5a.25.25 0 0 1-.305-.305l.5-1.914a.25.25 0 0 1 .065-.114l8.293-8.294Z"/></svg>

After

Width:  |  Height:  |  Size: 722 B

+1
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

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

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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M15 17h2v-3h1v-2l-1-5H2l-1 5v2h1v6h9v-6h4v3zm-6 1H4v-4h5v4zM2 4h15v2H2z"/><path fill="currentColor" d="M20 18v-3h-2v3h-3v2h3v3h2v-3h3v-2z"/></svg>

After

Width:  |  Height:  |  Size: 260 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 48 48"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.99" d="m14 26l-9 9l9 9m-9-8.992h17.5M34 18l9 9l-9 9m9-8.992H25.5M4.5 24V7.5h39V15"/></svg>

After

Width:  |  Height:  |  Size: 278 B

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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M11 2a9 9 0 1 0 5.618 16.032l3.675 3.675a1 1 0 0 0 1.414-1.414l-3.675-3.675A9 9 0 0 0 11 2Zm-6 9a6 6 0 1 1 12 0a6 6 0 0 1-12 0Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 290 B

+1
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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-dasharray="16" stroke-dashoffset="16" stroke-linecap="round" stroke-width="2"><path d="M7 7L17 17"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.4s" values="16;0"/></path><path d="M17 7L7 17"><animate fill="freeze" attributeName="stroke-dashoffset" begin="0.4s" dur="0.4s" values="16;0"/></path></g></svg>

After

Width:  |  Height:  |  Size: 452 B

+1
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

+1
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

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

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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M5.85 17.1q1.275-.975 2.85-1.537T12 15q1.725 0 3.3.563t2.85 1.537q.875-1.025 1.363-2.325T20 12q0-3.325-2.337-5.663T12 4Q8.675 4 6.337 6.338T4 12q0 1.475.488 2.775T5.85 17.1ZM12 13q-1.475 0-2.488-1.012T8.5 9.5q0-1.475 1.013-2.488T12 6q1.475 0 2.488 1.013T15.5 9.5q0 1.475-1.012 2.488T12 13Zm0 9q-2.075 0-3.9-.788t-3.175-2.137q-1.35-1.35-2.137-3.175T2 12q0-2.075.788-3.9t2.137-3.175q1.35-1.35 3.175-2.137T12 2q2.075 0 3.9.788t3.175 2.137q1.35 1.35 2.138 3.175T22 12q0 2.075-.788 3.9t-2.137 3.175q-1.35 1.35-3.175 2.138T12 22Z"/></svg>

After

Width:  |  Height:  |  Size: 646 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M8 16h12V6H8v10Zm0 2q-.825 0-1.412-.587T6 16V4q0-.825.588-1.412T8 2h12q.825 0 1.413.588T22 4v12q0 .825-.587 1.413T20 18H8Zm-4 4q-.825 0-1.412-.587T2 20V7q0-.425.288-.712T3 6q.425 0 .713.288T4 7v13h13q.425 0 .713.288T18 21q0 .425-.288.713T17 22H4ZM8 4v12V4Z"/></svg>

After

Width:  |  Height:  |  Size: 379 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21H7Zm2-4h2V8H9v9Zm4 0h2V8h-2v9Z"/></svg>

After

Width:  |  Height:  |  Size: 230 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M3 20v-3q0-.825.588-1.412T5 15h1v-2q0-.825.588-1.412T8 11h3V9h-1q-.825 0-1.412-.587T8 7V4q0-.825.588-1.412T10 2h4q.825 0 1.413.588T16 4v3q0 .825-.587 1.413T14 9h-1v2h3q.825 0 1.413.588T18 13v2h1q.825 0 1.413.588T21 17v3q0 .825-.587 1.413T19 22h-4q-.825 0-1.412-.587T13 20v-3q0-.825.588-1.412T15 15h1v-2H8v2h1q.825 0 1.413.588T11 17v3q0 .825-.587 1.413T9 22H5q-.825 0-1.412-.587T3 20Zm7-13h4V4h-4v3ZM5 20h4v-3H5v3Zm10 0h4v-3h-4v3ZM12 7ZM9 17Zm6 0Z"/></svg>

After

Width:  |  Height:  |  Size: 569 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M12 18Zm-8 1v-1.8q0-.85.438-1.562T5.6 14.55q1.55-.775 3.15-1.162T12 13q.925 0 1.825.113t1.8.362l-1.675 1.7q-.5-.075-.975-.125T12 15q-1.4 0-2.775.338T6.5 16.35q-.225.125-.363.35T6 17.2v.8h6v2H5q-.425 0-.712-.288T4 19Zm10 1v-1.25q0-.4.163-.763t.437-.637l4.925-4.925q.225-.225.5-.325t.55-.1q.3 0 .575.113t.5.337l.925.925q.2.225.313.5t.112.55q0 .275-.1.563t-.325.512l-4.925 4.925q-.275.275-.637.425t-.763.15H15q-.425 0-.712-.288T14 20Zm7.5-5.575l-.925-.925l.925.925Zm-6 5.075h.95l3.025-3.05l-.45-.475l-.475-.45l-3.05 3.025v.95Zm3.525-3.525l-.475-.45l.925.925l-.45-.475ZM12 12q-1.65 0-2.825-1.175T8 8q0-1.65 1.175-2.825T12 4q1.65 0 2.825 1.175T16 8q0 1.65-1.175 2.825T12 12Zm0-2q.825 0 1.413-.587T14 8q0-.825-.587-1.412T12 6q-.825 0-1.412.588T10 8q0 .825.588 1.413T12 10Zm0-2Z"/></svg>

After

Width:  |  Height:  |  Size: 894 B

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

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

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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M12 17a2 2 0 0 1-2-2c0-1.11.89-2 2-2a2 2 0 0 1 2 2a2 2 0 0 1-2 2m6 3V10H6v10h12m0-12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10c0-1.11.89-2 2-2h1V6a5 5 0 0 1 5-5a5 5 0 0 1 5 5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3Z"/></svg>

After

Width:  |  Height:  |  Size: 349 B

+1
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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none"><path d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z"/><path fill="currentColor" d="M5 10a2 2 0 1 1 0 4a2 2 0 0 1 0-4Zm7 0a2 2 0 1 1 0 4a2 2 0 0 1 0-4Zm7 0a2 2 0 1 1 0 4a2 2 0 0 1 0-4Z"/></g></svg>

After

Width:  |  Height:  |  Size: 813 B

+1
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

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

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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="#888888" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M14 3v4a1 1 0 0 0 1 1h4"/><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2m-5-10v6"/><path d="M9.5 13.5L12 11l2.5 2.5"/></g></svg>

After

Width:  |  Height:  |  Size: 340 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M0 0h24v24H0z"/><path fill="currentColor" d="M18 3a3 3 0 0 1 2.995 2.824L21 6v12a3 3 0 0 1-2.824 2.995L18 21H6a3 3 0 0 1-2.995-2.824L3 18V6a3 3 0 0 1 2.824-2.995L6 3h12zm0 2H9v14h9a1 1 0 0 0 .993-.883L19 18V6a1 1 0 0 0-.883-.993L18 5zm-2.293 4.293a1 1 0 0 1 .083 1.32l-.083.094L14.415 12l1.292 1.293a1 1 0 0 1 .083 1.32l-.083.094a1 1 0 0 1-1.32.083l-.094-.083l-2-2a1 1 0 0 1-.083-1.32l.083-.094l2-2a1 1 0 0 1 1.414 0z"/></g></svg>

After

Width:  |  Height:  |  Size: 603 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M0 0h24v24H0z"/><path fill="currentColor" d="M18 3a3 3 0 0 1 2.995 2.824L21 6v12a3 3 0 0 1-2.824 2.995L18 21H6a3 3 0 0 1-2.995-2.824L3 18V6a3 3 0 0 1 2.824-2.995L6 3h12zm-3 2H6a1 1 0 0 0-.993.883L5 6v12a1 1 0 0 0 .883.993L6 19h9V5zM9.613 9.21l.094.083l2 2a1 1 0 0 1 .083 1.32l-.083.094l-2 2a1 1 0 0 1-1.497-1.32l.083-.094L9.585 12l-1.292-1.293a1 1 0 0 1-.083-1.32l.083-.094a1 1 0 0 1 1.32-.083z"/></g></svg>

After

Width:  |  Height:  |  Size: 580 B

+1
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

+1
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

@@ -26,7 +26,7 @@ onMounted(() => {
</script>
<template>
<div>
<div class="pt-10">
<div>
<div class="flex flex-col items-center justify-center">
<img :src="srcSvglogo" width="100" height="100" alt="">
@@ -40,7 +40,7 @@ onMounted(() => {
</div>
</div>
</div>
<NDivider />
<NDivider> </NDivider>
<div class="flex flex-col items-center justify-center text-base">
<div>
建议反馈<a href="https://github.com/hslr-s/sun-panel/issues" target="_blank" class="link">Github Issues</a>
+360
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('当前软件版本可能过旧,很有可能无法兼容该配置文件,请谨慎导入。推荐将软件更新到新版后再次导入')
// (暂时不做)此处可以判断,当前的配置文件是否存在的导入项目(不存在隐藏importItems里面的值)操作变量:importItems
// 通过了验证,打开弹窗
importRoundModalShow.value = !importRoundModalShow.value
// console.log(importObj.value.geticons())
}
}
catch (error) {
if (error instanceof ConfigVersionLowError) {
ms.error('配置文件版本过低,无法兼容')
console.log('配置文件版本过低')
}
else if (error instanceof FormatError) {
ms.error('格式不正确,无法导入')
console.log('格式不正确')
}
}
}
else {
ms.error('数据不正确')
}
}
// 开始导出
async function handleStartExport() {
loading.value = true
console.log('要导出的项目', checkedItems.value)
// 获取软件版本号
const exportResult = exportJson(version.value)
if (checkedItems.value.includes('icons')) {
console.log('export icons ...')
const iconGroups = await exportIcons()
exportResult.addIconsData(iconGroups)
console.log('export icons finish', iconGroups)
}
console.log('导出结果')
jsonData.value = exportResult.string()
exportResult.exportFile()
loading.value = false
exportRoundModalShow.value = false
// ms.success(t('common.success'))
}
// 开始导入
async function handleStartImport() {
loading.value = true
if (checkedItems.value.includes('icons')) {
console.log('export icons ...')
const errMsg = await importIcons()
if (errMsg !== null)
ms.success(`${t('common.failed')}:${errMsg}`)
}
loading.value = false
importRoundModalShow.value = false
ms.success(`${t('common.success')},请手动刷新页面`)
}
</script>
<template>
<div class="pt-2">
<NAlert type="info" :bordered="false">
<p>导入图标配置数据不会清空现有图标数据</p>
</NAlert>
<div class="flex justify-center m-[50px]">
<div class="m-[10px]">
<NUpload
accept=".sun-panel.json,.sunpanel.json"
directory-dnd
:default-upload="false"
:show-file-list="false"
@change="handleFileChange"
>
<NButton type="info" size="large" :loading="uploadLoading">
<template #icon>
<SvgIcon icon="fa6:solid-file-import" />
</template>
导入配置
</NButton>
</NUpload>
</div>
<div class="m-[10px]">
<NButton type="info" size="large" @click="exportRoundModalShow = !exportRoundModalShow">
<template #icon>
<SvgIcon icon="fa6:solid-file-export" />
</template>
导出配置
</NButton>
</div>
</div>
<div class="flex justify-center">
<a href="https://hslr-s.github.io/sun-panel-tool-page/#/" target="_blank">浏览器书签转换工具</a>
</div>
<!-- 调试模式 -->
<div v-if="debug">
<NButton @click="importCheck">
检查导入
</NButton>
<!-- <NButton @click="exportJsonS">
导出JSON
</NButton> -->
<NButton @click="jsonData = ''">
清空导入数据
</NButton>
<NInput
v-model:value="jsonData"
type="textarea"
placeholder="基本的 Textarea"
/>
<div v-if="jsonData">
<h2>JSON 数据</h2>
<pre>{{ jsonData }}</pre>
</div>
</div>
<RoundCardModal v-model:show="importRoundModalShow" style="max-width: 400px;" title="导入">
<div v-if="importWarning.length > 0">
<NAlert :title="$t('common.warning')" type="warning">
<div v-for="(text, index) in importWarning " :key="index">
{{ text }}
</div>
</NAlert>
</div>
<NDivider title-placement="left">
请选择要导入的配置数据
</NDivider>
<NSpace justify="center" style="margin-top: 20px;">
<NCheckboxGroup v-model:value="checkedItems">
<NCheckbox v-if="importItems.includes('icons')" value="icons" label="图标" />
<NCheckbox v-if="importItems.includes('style')" value="style" label="样式配置" />
</NCheckboxGroup>
</NSpace>
<NSpace justify="center">
<div class="mt-[50px]">
<NButton type="success" :disabled="checkedItems.length === 0" :loading="loading" @click="handleStartImport">
继续导入
</NButton>
</div>
</NSpace>
</RoundCardModal>
<RoundCardModal v-model:show="exportRoundModalShow" style="max-width: 400px;" title="导出">
<NDivider title-placement="left">
请选择要导出的配置数据
</NDivider>
<NSpace justify="center" style="margin-top: 20px;">
<NCheckboxGroup v-model:value="checkedItems">
<NCheckbox v-if="importItems.includes('icons')" value="icons" label="图标" />
<NCheckbox v-if="importItems.includes('style')" value="style" label="样式配置" />
</NCheckboxGroup>
</NSpace>
<NSpace justify="center">
<div class="mt-[50px]">
<NButton type="success" :disabled="checkedItems.length === 0" :loading="loading" @click="handleStartExport">
继续导出
</NButton>
</div>
</NSpace>
</RoundCardModal>
</div>
</template>
@@ -48,6 +48,7 @@ function handleAddGroup() {
function handleEditGroup(groupInfo: Panel.ItemIconGroup) {
editModalArg.value.show = true
editModalArg.value.model = groupInfo
editModalArg.value.editStatus = 2
}
function handleDragSort() {
@@ -122,8 +123,8 @@ onMounted(() => {
</script>
<template>
<div class="h-[500px]">
<div>
<div class="h-full">
<div class="p-2">
<NButton type="success" size="small" style="margin-right: 10px;" @click="handleAddGroup">
新增分组
</NButton>
@@ -137,7 +138,7 @@ onMounted(() => {
</NButton>
</div>
<div class=" overflow-auto w-full mt-[20px] bg-slate-200 rounded-xl" style="height:calc(100% - 50px)">
<div class=" overflow-auto w-full mt-[20px] bg-slate-200 rounded-xl" style="height:calc(100% - 65px)">
<VueDraggable
v-model="groups"
item-key="sort" :animation="300"
@@ -149,7 +150,7 @@ onMounted(() => {
<div class="flex" :class="sortStatus ? 'cursor-move' : ''">
<div class="flex items-center">
<span class="mr-[10px]">
<SvgIcon class="text-[20px]" icon="material-symbols:ad-group-outline" />
<SvgIcon class="text-[20px]" icon="material-symbols:ad-group-outline-rounded" />
<!-- <SvgIcon class="text-[20px]" :icon="item.icon" /> -->
</span>
<span>
@@ -178,7 +179,7 @@ onMounted(() => {
</VueDraggable>
</div>
<RoundCardModal v-model:show="editModalArg.show" type="small" :title="editModalArg.editStatus === 1 ? '添加' : '编辑'" style="width: 400px;">
<RoundCardModal v-model:show="editModalArg.show" size="small" type="small" :title="editModalArg.editStatus === 1 ? '添加' : '编辑'" style="width: 400px;">
<NForm ref="formRef" :model="editModalArg.model" :rules="editModalArg.rules">
<NFormItem path="title" label="分组名称">
<NInput v-model:value="editModalArg.model.title" type="text" :maxlength="20" show-count placeholder="请输入" />
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import type { UploadFileInfo } from 'naive-ui'
import { NButton, NCard, NColorPicker, NInput, NPopconfirm, NSelect, NSlider, NSwitch, NUpload, NUploadDragger, useMessage } from 'naive-ui'
import { NButton, NCard, NColorPicker, NGrid, NGridItem, NInput, NPopconfirm, NSelect, NSlider, NSwitch, NUpload, NUploadDragger, useMessage } from 'naive-ui'
import { useAuthStore, usePanelState } from '@/store'
import { set as setUserConfig } from '@/api/panel/userConfig'
import { PanelPanelConfigStyleEnum } from '@/enums/panel'
@@ -31,9 +31,9 @@ watch(panelState.panelConfig, () => {
panelState.recordState()//
setUserConfig({ panel: panelState.panelConfig }).then((res) => {
if (res.code === 0)
ms.success('配置已同步到云端')
ms.success('配置已保存')
else
ms.error(`配置同步到云端失败${res.msg}`)
ms.error(`配置保存失败${res.msg}`)
isSaveing.value = false
})
}, 1000)
@@ -68,16 +68,24 @@ function resetPanelConfig() {
</script>
<template>
<div class="bg-slate-200 rounded-[10px] p-[8px] h-[500px] overflow-auto">
<div class="bg-slate-200 rounded-[10px] p-[8px] overflow-auto">
<NCard style="border-radius:10px" size="small">
<div class="text-slate-500 mb-[5px]">
<div class="text-slate-500 mb-[5px] font-bold">
LOGO
</div>
<NInput v-model:value="panelState.panelConfig.logoText" type="text" show-count :maxlength="20" placeholder="请输入文字" />
<div>
<div>
文本内容
</div>
<div class="flex items-center mt-[5px]">
<NInput v-model:value="panelState.panelConfig.logoText" type="text" show-count :maxlength="20" placeholder="请输入文字" />
</div>
</div>
</NCard>
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
<div class="text-slate-500 mb-[5px]">
<div class="text-slate-500 mb-[5px] font-bold">
时钟
</div>
<div class="flex items-center mt-[5px]">
@@ -87,17 +95,21 @@ function resetPanelConfig() {
</NCard>
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
<div class="text-slate-500 mb-[5px]">
<div class="text-slate-500 mb-[5px] font-bold">
搜索框
</div>
<div class="flex items-center mt-[5px]">
<span class="mr-[10px]">显示</span>
<NSwitch v-model:value="panelState.panelConfig.searchBoxShow" />
</div>
<div v-if="panelState.panelConfig.searchBoxShow" class="flex items-center mt-[5px]">
<span class="mr-[10px]">允许搜索快捷图标</span>
<NSwitch v-model:value="panelState.panelConfig.searchBoxSearchIcon" />
</div>
</NCard>
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
<div class="text-slate-500 mb-[5px]">
<div class="text-slate-500 mb-[5px] font-bold">
图标
</div>
<div class="mt-[5px]">
@@ -149,7 +161,7 @@ function resetPanelConfig() {
</div>
</NCard>
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
<div class="text-slate-500 mb-[5px]">
<div class="text-slate-500 mb-[5px] font-bold">
壁纸
</div>
<NUpload
@@ -185,6 +197,27 @@ function resetPanelConfig() {
</div>
</NCard>
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
<div class="text-slate-500 mb-[5px] font-bold">
其他
</div>
<NGrid cols="2">
<NGridItem span="12 400:12">
<div class="flex items-center mt-[10px]">
<span class="mr-[10px]">上边距 (%)</span>
<NSlider v-model:value="panelState.panelConfig.marginTop" class="max-w-[200px]" :step="1" :max="50" />
</div>
</NGridItem>
<NGridItem span="12 400:6">
<div class="flex items-center mt-[10px]">
<span class="mr-[10px]">下边距 (%)</span>
<NSlider v-model:value="panelState.panelConfig.marginBottom" class="max-w-[200px]" :step="1" :max="50" />
</div>
</NGridItem>
</NGrid>
</NCard>
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
<NPopconfirm
@positive-click="resetPanelConfig"
@@ -0,0 +1,183 @@
<script setup lang="ts">
import { NButton, NButtonGroup, NCard, NEllipsis, NGrid, NGridItem, NImage, NImageGroup, NSpin, useDialog, useMessage } from 'naive-ui'
import { onMounted, ref } from 'vue'
import { deletes, getList } from '@/api/system/file'
import { set as savePanelConfig } from '@/api/panel/userConfig'
import { RoundCardModal, SvgIcon } from '@/components/common'
import { copyToClipboard, timeFormat } from '@/utils/cmn'
import { t } from '@/locales'
import { usePanelState } from '@/store'
interface InfoModalState {
title: string
show: boolean
fileInfo: File.Info | null
}
const imageList = ref<File.Info[]>([])
const ms = useMessage()
const dialog = useDialog()
const panelStore = usePanelState()
const loading = ref(false)
const infoModalState = ref<InfoModalState>({
show: false,
title: '',
fileInfo: null,
})
async function getFileList() {
loading.value = true
const { data } = await getList<Common.ListResponse<File.Info[]>>()
imageList.value = data.list
loading.value = false
}
async function copyImageUrl(text: string) {
const res = await copyToClipboard(text)
if (res)
ms.success(t('apps.uploadsFileManager.copySuccess'))
else
ms.error(t('apps.uploadsFileManager.copyFailed'))
}
function handleDelete(id: number) {
dialog.warning({
title: t('common.warning'),
content: t('apps.uploadsFileManager.deleteWarningText'),
positiveText: t('common.confirm'),
negativeText: t('common.cancel'),
onPositiveClick: () => {
deletesImges(id)
},
})
}
async function deletesImges(id: number) {
try {
const { code, msg } = await deletes([id])
if (code === 0) {
getFileList()
ms.success(t('common.success'))
}
else {
ms.error(`${t('common.failed')}:${msg}`)
}
}
catch (error) {
ms.error(t('common.failed'))
}
}
function handleInfoClick(fileInfo: File.Info) {
infoModalState.value.fileInfo = fileInfo
infoModalState.value.show = true
}
function handleSetWallpaper(imgSrc: string) {
panelStore.panelConfig.backgroundImageSrc = imgSrc
savePanelConfig({ panel: panelStore.panelConfig })
}
onMounted(() => {
getFileList()
})
</script>
<template>
<div class="bg-slate-200 p-2 h-full">
<NSpin v-show="loading" size="small" />
<div class="flex justify-center">
<NImageGroup>
<NGrid cols="2 300:2 600:4 900:6 1100:9" :x-gap="5" :y-gap="5">
<NGridItem v-for=" item, index in imageList" :key="index">
<NCard size="small" style="border-radius: 5px;" :bordered="true">
<template #cover>
<div class="card transparent-grid">
<NImage :lazy="true" style="object-fit: contain;height: 100%;" :src="item.src" />
</div>
</template>
<template #footer>
<span class="text-xs">
<NEllipsis>
{{ item.fileName }}
</NEllipsis>
</span>
<div class="flex justify-center mt-[10px]">
<NButtonGroup>
<NButton size="tiny" tertiary style="cursor: pointer;" :title="$t('apps.uploadsFileManager.copyLink')" @click="copyImageUrl(item.src)">
<template #icon>
<SvgIcon icon="ion-copy" />
</template>
</NButton>
<NButton size="tiny" tertiary style="cursor: pointer;" :title="timeFormat(item.createTime)" @click="handleInfoClick(item)">
<template #icon>
<SvgIcon icon="mdi-information-box-outline" />
</template>
</NButton>
<NButton size="tiny" tertiary style="cursor: pointer;" :title="$t('apps.uploadsFileManager.setWallpaper')" @click="handleSetWallpaper(item.src)">
<template #icon>
<SvgIcon icon="lucide:wallpaper" />
</template>
</NButton>
<NButton size="tiny" tertiary type="error" style="cursor: pointer;" :title="$t('common.delete')" @click="handleDelete(item.id as number)">
<template #icon>
<SvgIcon icon="material-symbols-delete" />
</template>
</NButton>
</NButtonGroup>
</div>
</template>
</NCard>
</NGridItem>
</NGrid>
</NImageGroup>
</div>
<RoundCardModal v-model:show="infoModalState.show" style="max-width: 300px;" size="small" :title="$t('apps.uploadsFileManager.infoTitle')">
<div>
<div>
<div class="mb-2">
<span class="text-slate-500">
{{ $t('apps.uploadsFileManager.fileName') }}
</span>
<div class="text-xs">
{{ infoModalState.fileInfo?.fileName }}
</div>
</div>
<div class="mb-2">
<span class="text-slate-500">
{{ $t('apps.uploadsFileManager.path') }}
</span>
<div class="text-xs">
{{ infoModalState.fileInfo?.src }}
</div>
</div>
<div class="mb-2">
<span class="text-slate-500">
{{ $t('apps.uploadsFileManager.uploadTime') }}
</span>
<div class="text-xs">
{{ timeFormat(infoModalState.fileInfo?.createTime) }}
</div>
</div>
</div>
</div>
</RoundCardModal>
</div>
</template>
<style scoped>
.card {
display: flex;
justify-content: center;
align-items: center;
height: 80px;
}
.transparent-grid {
background-image: linear-gradient(45deg, #f0f0f0 25%, transparent 25%, transparent 75%, #f0f0f0 75%),
linear-gradient(45deg, #f0f0f0 25%, transparent 25%, transparent 75%, #f0f0f0 75%);
background-size: 16px 16px;
background-position: 0 0, 8px 8px;
}
</style>
+201
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>
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { computed, defineEmits, defineProps, ref, watch } from 'vue'
import type { FormInst, FormRules } from 'naive-ui'
import { NButton, NForm, NFormItem, NInput, useMessage } from 'naive-ui'
import { NButton, NForm, NFormItem, NInput, NSelect, useMessage } from 'naive-ui'
import { edit as userManageEdit } from '@/api/panel/users'
import { RoundCardModal } from '@/components/common'
import { t } from '@/locales'
interface Props {
visible: boolean
@@ -30,37 +31,43 @@ const formInitValue = {
const model = ref<User.Info>(formInitValue)
const formRef = ref<FormInst | null>(null)
const roleOtions = ref([
{
label: t('common.role.regularUser'),
value: 2,
},
{
label: t('common.role.admin'),
value: 1,
},
])
const rules: FormRules = {
username: [
{
required: true,
trigger: 'blur',
message: '请输入账号且大于5个字符',
message: t('adminSettingUsers.formRules.usernameRequired'),
min: 5,
},
{
trigger: 'blur',
message: '请输入邮箱作为账号',
type: 'email',
},
],
role: {
required: true,
trigger: 'blur',
type: 'number',
message: '请选择角色',
},
status: {
required: true,
trigger: 'blur',
type: 'number',
message: '请选择账号状态',
message: t('adminSettingUsers.formRules.roleRequired'),
},
// status: {
// required: true,
// trigger: 'blur',
// type: 'number',
// message: '',
// },
password: {
trigger: 'blur',
min: 6,
max: 20,
message: '6-20个字符',
message: t('adminSettingUsers.formRules.passwordLimit'),
},
}
@@ -86,7 +93,7 @@ const add = async () => {
emit('done', res.data.id as number)
else if (res.code !== -1)
message.warning('操作失败')
message.warning(t('common.failed'))
}
const handleValidateButtonClick = (e: MouseEvent) => {
@@ -101,25 +108,32 @@ const handleValidateButtonClick = (e: MouseEvent) => {
</script>
<template>
<RoundCardModal v-model:show="show" size="small" preset="card" style="width: 400px" :title="`${userInfo?.id ? '编辑' : '添加'}用户`">
<RoundCardModal v-model:show="show" size="small" preset="card" style="width: 400px" :title="`${userInfo?.id ? $t('common.edit') : $t('common.add')}`">
<NForm ref="formRef" :model="model" :rules="rules">
<NFormItem path="username" label="账号">
<NInput v-model:value="model.username" type="text" placeholder="邮箱地址作为账号" />
<NFormItem path="username" :label="$t('common.username')">
<NInput v-model:value="model.username" type="text" :placeholder="$t('common.inputPlaceholder')" />
</NFormItem>
<NFormItem path="name" label="昵称">
<NInput v-model:value="model.name" type="text" placeholder="请输入昵称" />
<NFormItem path="name" :label="$t('common.nikeName')">
<NInput v-model:value="model.name" type="text" :placeholder="$t('common.inputPlaceholder')" />
</NFormItem>
<NFormItem path="password" label="密码">
<NInput v-model:value="model.password" :maxlength="20" type="password" :placeholder="`${userInfo?.id ? '请输入新密码,留空密码不变' : '请输入密码'}`" />
<NFormItem path="role" :label="$t('adminSettingUsers.role')">
<NSelect
v-model:value="model.role"
:options="roleOtions"
/>
</NFormItem>
<NFormItem path="password" :label="$t('common.password')">
<NInput v-model:value="model.password" :maxlength="20" type="password" :placeholder="`${userInfo?.id ? $t('adminSettingUsers.EditpasswordPlaceholder') : $t('adminSettingUsers.passwordPlaceholder')}`" />
</NFormItem>
</NForm>
<template #footer>
<div class="float-right">
<NButton type="success" size="small" @click="handleValidateButtonClick">
保存
{{ $t('common.save') }}
</NButton>
</div>
</template>
@@ -1,19 +1,22 @@
<script lang="ts" setup>
import { h, onMounted, reactive, ref } from 'vue'
import { NAlert, NButton, NDataTable, NDropdown, useDialog, useMessage } from 'naive-ui'
import { NAlert, NButton, NDataTable, NDropdown, NTag, useDialog, useMessage } from 'naive-ui'
import type { DataTableColumns, PaginationProps } from 'naive-ui'
import EditUser from './EditUser/index.vue'
import { deletes as usersDeletes, getList as usersGetList } from '@/api/panel/users'
import { getPublicVisitUser, setPublicVisitUser, deletes as usersDeletes, getList as usersGetList } from '@/api/panel/users'
import { SvgIcon } from '@/components/common'
import { useUserStore } from '@/store'
import { useAuthStore } from '@/store'
import { t } from '@/locales'
import { AdminAuthRole } from '@/enums/admin'
const message = useMessage()
const userStore = useUserStore()
const authStore = useAuthStore()
const tableIsLoading = ref<boolean>(false)
const editUserDialogShow = ref<boolean>(false)
const keyWord = ref<string>()
const editUserUserInfo = ref<User.Info>()
const dialog = useDialog()
const publicVisitUserId = ref<number | null>(null)
const createColumns = ({
update,
@@ -22,20 +25,38 @@ const createColumns = ({
}): DataTableColumns<User.Info> => {
return [
{
title: '账号',
title: t('common.username'),
key: 'username',
render(row: User.Info) {
if (row.username === userStore.userInfo.username)
return `${row.username} (当前账号)`
return row.username
let publicVisitHtml = ''
if (publicVisitUserId.value && publicVisitUserId.value === row.id)
publicVisitHtml = `[${t('adminSettingUsers.pblicText')}]-`
if (row.username === authStore.userInfo?.username)
return `${publicVisitHtml}${row.username} (${t('adminSettingUsers.currentUseUsername')})`
return publicVisitHtml + row.username
},
},
{
title: '昵称',
title: t('common.nikeName'),
key: 'name',
},
{
title: '操作',
title: t('adminSettingUsers.role'),
key: 'role',
render(row) {
switch (row.role) {
case AdminAuthRole.admin:
return h(NTag, { type: 'info' }, t('common.role.admin'))
case AdminAuthRole.regularUser:
return h(NTag, t('common.role.regularUser'))
default:
return '-'
}
},
},
{
title: t('common.action'),
key: '',
render(row) {
const btn = h(
@@ -59,17 +80,34 @@ const createColumns = ({
return h(NDropdown, {
trigger: 'click',
onSelect(key: string | number) {
console.log(key)
switch (key) {
case 'update':
update(row)
break
case 'publicMode':
//
if (publicVisitUserId.value && publicVisitUserId.value === row.id) {
setPublicVisitUser(null).then(({ code }) => {
if (code === 0)
publicVisitUserId.value = null
})
}
else {
//
setPublicVisitUser(row.id as number).then(({ code }) => {
if (code === 0)
publicVisitUserId.value = row.id as number
else if (code === 1111)
message.error('用户不存在,请刷新后再试')
})
}
break
case 'delete':
dialog.warning({
title: '警告',
content: `你确定删除${row.name}(${row.username})`,
positiveText: '确定',
negativeText: '取消',
title: t('common.warning'),
content: t('adminSettingUsers.deletePromptContent', { name: row.name, username: row.username }),
positiveText: t('common.confirm'),
negativeText: t('common.cancel'),
onPositiveClick: () => {
deletes([row.id as number])
},
@@ -82,11 +120,15 @@ const createColumns = ({
},
options: [
{
label: '修改信息',
label: t('common.edit'),
key: 'update',
},
{
label: '删除',
label: t('adminSettingUsers.setOrUnsetPublicMode'),
key: 'publicMode',
},
{
label: t('common.delete'),
key: 'delete',
},
],
@@ -120,7 +162,7 @@ const pagination = reactive({
getList(null)
},
prefix(item: PaginationProps) {
return `${item.itemCount} 位用户`
return t('adminSettingUsers.userCountText', { count: item.itemCount })
},
})
@@ -136,7 +178,7 @@ function handleAdd() {
function handelDone() {
editUserDialogShow.value = false
message.success('操作成功')
message.success(t('common.success'))
getList(null)
}
@@ -159,24 +201,27 @@ async function getList(page: number | null) {
async function deletes(ids: number[]) {
const { code } = await usersDeletes(ids)
if (code === 0) {
message.success('已删除')
message.success(t('common.deleteSuccess'))
getList(null)
}
}
onMounted(() => {
getPublicVisitUser<User.Info>().then(({ data }) => {
publicVisitUserId.value = data.id || null
})
getList(null)
})
</script>
<template>
<div class="h-[500px] overflow-auto">
<div class="overflow-auto pt-2">
<NAlert type="info" :bordered="false">
账号之间的数据不互通
{{ $t('adminSettingUsers.alertText') }}
</NAlert>
<div class="my-[10px]">
<NButton type="primary" size="small" ghost @click="handleAdd">
添加
{{ $t('common.add') }}
</NButton>
</div>
+15
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,
}
+46
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>
+2 -2
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>
@@ -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>
-55
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>
-225
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> -->
@@ -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>
-227
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>
-80
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>
+43 -12
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>
@@ -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>
+6 -2
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,
}
+2 -2
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>
+33 -12
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
View File
@@ -0,0 +1,4 @@
export enum AdminAuthRole {
'admin' = 1, // 平台管理
'regularUser' = 2, // 受限用户
}
+4
View File
@@ -0,0 +1,4 @@
export enum VisitMode {
'VISIT_MODE_LOGIN' = 0, // 登录状态
'VISIT_MODE_PUBLIC' = 1, // 公开状态
}
+2 -2
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 {
+8 -7
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
+6 -6
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,
},
})
-93
View File
@@ -1,93 +0,0 @@
export default {
common: {
add: '추가',
addSuccess: '추가 성공',
edit: '편집',
editSuccess: '편집 성공',
delete: '삭제',
deleteSuccess: '삭제 성공',
save: '저장',
saveSuccess: '저장 성공',
reset: '초기화',
action: '액션',
export: '내보내기',
exportSuccess: '내보내기 성공',
import: '가져오기',
importSuccess: '가져오기 성공',
clear: '비우기',
clearSuccess: '비우기 성공',
yes: '예',
no: '아니오',
confirm: '확인',
download: '다운로드',
noData: '데이터 없음',
wrong: '문제가 발생했습니다. 나중에 다시 시도하십시오.',
success: '성공',
failed: '실패',
verify: '검증',
unauthorizedTips: '인증되지 않았습니다. 먼저 확인하십시오.',
},
chat: {
newChatButton: '새로운 채팅',
placeholder: '무엇이든 물어보세요...(Shift + Enter = 줄바꿈, "/"를 눌러서 힌트를 보세요)',
placeholderMobile: '무엇이든 물어보세요...',
copy: '복사',
copied: '복사됨',
copyCode: '코드 복사',
clearChat: '채팅 비우기',
clearChatConfirm: '이 채팅을 비우시겠습니까?',
exportImage: '이미지 내보내기',
exportImageConfirm: '이 채팅을 png로 내보내시겠습니까?',
exportSuccess: '내보내기 성공',
exportFailed: '내보내기 실패',
usingContext: '컨텍스트 모드',
turnOnContext: '현재 모드에서는 이전 대화 기록을 포함하여 메시지를 보낼 수 있습니다.',
turnOffContext: '현재 모드에서는 이전 대화 기록을 포함하지 않고 메시지를 보낼 수 있습니다.',
deleteMessage: '메시지 삭제',
deleteMessageConfirm: '이 메시지를 삭제하시겠습니까?',
deleteHistoryConfirm: '이 기록을 삭제하시겠습니까?',
clearHistoryConfirm: '채팅 기록을 삭제하시겠습니까?',
preview: '미리보기',
showRawText: '원본 텍스트로 보기',
},
setting: {
setting: '설정',
general: '일반',
advanced: '고급',
config: '설정',
avatarLink: '아바타 링크',
name: '이름',
description: '설명',
role: '역할',
temperature: '온도',
top_p: 'Top_p',
resetUserInfo: '사용자 정보 초기화',
chatHistory: '채팅 기록',
theme: '테마',
language: '언어',
api: 'API',
reverseProxy: '리버스 프록시',
timeout: '타임아웃',
socks: 'Socks',
httpsProxy: 'HTTPS 프록시',
balance: 'API 잔액',
monthlyUsage: '월 사용량',
},
store: {
siderButton: '프롬프트 저장소',
local: '로컬',
online: '온라인',
title: '제목',
description: '설명',
clearStoreConfirm: '데이터를 삭제하시겠습니까?',
importPlaceholder: '여기에 JSON 데이터를 붙여넣으십시오',
addRepeatTitleTips: '제목 중복됨, 다시 입력하십시오',
addRepeatContentTips: '내용 중복됨: {msg}, 다시 입력하십시오',
editRepeatTitleTips: '제목 충돌, 수정하십시오',
editRepeatContentTips: '내용 충돌 {msg} , 수정하십시오',
importError: '키 값 불일치',
importRepeatTitle: '제목이 반복되어 건너뜀: {msg}',
importRepeatContent: '내용이 반복되어 건너뜀: {msg}',
onlineImportWarning: '참고: JSON 파일 소스를 확인하십시오!',
},
}
-94
View File
@@ -1,94 +0,0 @@
export default {
common: {
add: 'Добавить',
addSuccess: 'Добавлено успешно',
edit: 'Редактировать',
editSuccess: 'Изменено успешно',
delete: 'Удалить',
deleteSuccess: 'Удалено успешно',
save: 'Сохранить',
saveSuccess: 'Сохранено успешно',
reset: 'Сбросить',
action: 'Действие',
export: 'Экспортировать',
exportSuccess: 'Экспорт выполнен успешно',
import: 'Импортировать',
importSuccess: 'Импорт выполнен успешно',
clear: 'Очистить',
clearSuccess: 'Очищено успешно',
yes: 'Да',
no: 'Нет',
confirm: 'Подтвердить',
download: 'Загрузить',
noData: 'Нет данных',
wrong: 'Что-то пошло не так, пожалуйста, повторите попытку позже.',
success: 'Успех',
failed: 'Не удалось',
verify: 'Проверить',
unauthorizedTips: 'Не авторизован, сначала подтвердите свою личность.',
},
chat: {
newChatButton: 'Новый чат',
placeholder: 'Спросите меня о чем-нибудь ... (Shift + Enter = перенос строки, "/" для вызова подсказок)',
placeholderMobile: 'Спросите меня о чем-нибудь ...',
copy: 'Копировать',
copied: 'Скопировано',
copyCode: 'Копировать код',
clearChat: 'Очистить чат',
clearChatConfirm: 'Вы уверены, что хотите очистить этот чат?',
exportImage: 'Экспорт в изображение',
exportImageConfirm: 'Вы уверены, что хотите экспортировать этот чат в формате PNG?',
exportSuccess: 'Экспортировано успешно',
exportFailed: 'Не удалось выполнить экспорт',
usingContext: 'Режим контекста',
turnOnContext: 'В текущем режиме отправка сообщений будет включать предыдущие записи чата.',
turnOffContext: 'В текущем режиме отправка сообщений не будет включать предыдущие записи чата.',
deleteMessage: 'Удалить сообщение',
deleteMessageConfirm: 'Вы уверены, что хотите удалить это сообщение?',
deleteHistoryConfirm: 'Вы уверены, что хотите очистить эту историю?',
clearHistoryConfirm: 'Вы уверены, что хотите очистить историю чата?',
preview: 'Предварительный просмотр',
showRawText: 'Показать как обычный текст',
},
setting: {
setting: 'Настройки',
general: 'Общее',
advanced: 'Дополнительно',
config: 'Конфигурация',
avatarLink: 'Ссылка на аватар',
name: 'Имя',
description: 'Описание',
role: 'Роль',
temperature: 'Температура',
top_p: 'Top_p',
resetUserInfo: 'Сбросить информацию о пользователе',
chatHistory: 'История чата',
theme: 'Тема',
language: 'Язык',
api: 'API',
reverseProxy: 'Обратный прокси-сервер',
timeout: 'Время ожидания',
socks: 'Socks',
httpsProxy: 'HTTPS-прокси',
balance: 'Баланс API',
monthlyUsage: 'Ежемесячное использование',
},
store: {
siderButton: 'Хранилище подсказок',
local: 'Локальное',
online: 'Онлайн',
title: 'Название',
description: 'Описание',
clearStoreConfirm: 'Вы действительно хотите очистить данные?',
importPlaceholder: 'Пожалуйста, вставьте здесь JSON-данные',
addRepeatTitleTips: 'Дубликат названия, пожалуйста, введите другое название',
addRepeatContentTips: 'Дубликат содержимого: {msg}, пожалуйста, введите другой текст',
editRepeatTitleTips: 'Конфликт названий, пожалуйста, измените название',
editRepeatContentTips: 'Конфликт содержимого {msg}, пожалуйста, измените текст',
importError: 'Не совпадает ключ-значение',
importRepeatTitle: 'Название повторяющееся, пропускается: {msg}',
importRepeatContent: 'Содержание повторяющееся, пропускается: {msg}',
onlineImportWarning: 'Внимание! Проверьте источник JSON-файла!',
downloadError: 'Проверьте состояние сети и правильность JSON-файла',
},
}
+66 -43
View File
@@ -5,6 +5,7 @@ export default {
addSuccess: '添加成功',
edit: '编辑',
editSuccess: '编辑成功',
editFail: '编辑失败',
delete: '删除',
deleteSuccess: '删除成功',
save: '保存',
@@ -15,41 +16,30 @@ export default {
exportSuccess: '导出成功',
import: '导入',
importSuccess: '导入成功',
clear: '清空',
clearSuccess: '清空成功',
// clear: '清空',
// clearSuccess: '清空成功',
yes: '是',
no: '否',
confirm: '确定',
cancel: '取消',
warning: '警告',
download: '下载',
noData: '暂无数据',
wrong: '好像出错了,请稍后再试。',
// wrong: '好像出错了,请稍后再试。',
success: '操作成功',
failed: '操作失败',
verify: '验证',
unauthorizedTips: '未经授权,请先进行验证。',
},
chat: {
newChatButton: '新建对话',
placeholder: '来说点什么吧...Shift + Enter = 换行)',
placeholderMobile: '来说点什么...',
copy: '复制',
copied: '复制成功',
copyCode: '复制代码',
clearChat: '清空会话',
clearChatConfirm: '是否清空会话?',
exportImage: '保存会话到图片',
exportImageConfirm: '是否将会话保存为图片?',
exportSuccess: '保存成功',
exportFailed: '保存失败',
usingContext: '上下文模式',
turnOnContext: '当前模式下, 发送消息会携带之前的聊天记录',
turnOffContext: '当前模式下, 发送消息不会携带之前的聊天记录',
deleteMessage: '删除消息',
deleteMessageConfirm: '是否删除此消息?',
deleteHistoryConfirm: '确定删除此记录?',
clearHistoryConfirm: '确定清空聊天记录?',
preview: '预览',
showRawText: '显示原文',
inputPlaceholder: '请输入',
inputPlaceholderByText: '请输入{text}',
username: '账号',
nikeName: '昵称',
password: '密码',
serverError: '服务器错误',
role: {
regularUser: '普通',
admin: '管理',
},
},
setting: {
setting: '设置',
@@ -75,22 +65,55 @@ export default {
balance: 'API余额',
monthlyUsage: '本月使用量',
},
store: {
siderButton: '提示词商店',
local: '本地',
online: '在线',
title: '标题',
description: '描述',
clearStoreConfirm: '是否清空数据?',
importPlaceholder: '请粘贴 JSON 数据到此处',
addRepeatTitleTips: '标题重复,请重新输入',
addRepeatContentTips: '内容重复:{msg},请重新输入',
editRepeatTitleTips: '标题冲突,请重新修改',
editRepeatContentTips: '内容冲突{msg} ,请重新修改',
importError: '键值不匹配',
importRepeatTitle: '标题重复跳过:{msg}',
importRepeatContent: '内容重复跳过:{msg}',
onlineImportWarning: '注意:请检查 JSON 文件来源!',
downloadError: '请检查网络状态与 JSON 文件有效性',
login: {
loginButton: '登录',
usernamePlaceholder: '请输入账号',
passwordPlaceholder: '请输入密码',
welcomeMessage: '欢迎回来!',
},
settingUserInfo: {
updatePassword: '修改密码',
oldPassword: '旧密码',
newPassword: '新密码',
confirmPassword: '确认新密码',
confirmPasswordInconsistentMsg: '两次密码不一致',
logout: '退出登录',
confirmLogoutText: '你确定要退出登录吗?',
logoutSuccess: '您已经安全退出,期待与你再次相见!',
},
adminSettingUsers: {
passwordPlaceholder: '请输入密码',
EditpasswordPlaceholder: '请输入新密码,留空密码不变',
role: '角色',
formRules: {
usernameRequired: '请输入账号且大于5个字符',
roleRequired: '请选择角色',
passwordLimit: '6-20个字符',
},
alertText: '账号之间的数据不互通',
userCountText: '共{count}位用户',
deletePromptContent: '你确定删除{name}({username})',
currentUseUsername: '当前账号',
setOrUnsetPublicMode: '设置/取消公开访问',
pblicText: '公开',
},
deskModule: {
searchBox: {
openWithNewOpen: '新窗口打开',
inputPlaceholder: '请输入搜索内容',
},
},
apps: {
uploadsFileManager: {
copyLink: '复制链接',
infoTitle: '文件详情',
fileName: '原文件名',
path: '文件路径',
uploadTime: '上传时间',
deleteWarningText: '删除后无法恢复,你确定要继续吗?',
copySuccess: '链接复制成功,可以在图标地址栏',
copyFailed: '复制失败',
setWallpaper: '设置为壁纸',
},
},
}
-94
View File
@@ -1,94 +0,0 @@
export default {
common: {
add: '新增',
addSuccess: '新增成功',
edit: '編輯',
editSuccess: '編輯成功',
delete: '刪除',
deleteSuccess: '刪除成功',
save: '儲存',
saveSuccess: '儲存成功',
reset: '重設',
action: '操作',
export: '匯出',
exportSuccess: '匯出成功',
import: '匯入',
importSuccess: '匯入成功',
clear: '清除',
clearSuccess: '清除成功',
yes: '是',
no: '否',
confirm: '確認',
download: '下載',
noData: '目前無資料',
wrong: '發生錯誤,請稍後再試。',
success: '操作成功',
failed: '操作失敗',
verify: '驗證',
unauthorizedTips: '未經授權,請先進行驗證。',
},
chat: {
newChatButton: '新增對話',
placeholder: '來說點什麼...Shift + Enter = 換行,"/" 觸發提示詞)',
placeholderMobile: '來說點什麼...',
copy: '複製',
copied: '複製成功',
copyCode: '複製代碼',
clearChat: '清除對話',
clearChatConfirm: '是否清空對話?',
exportImage: '儲存對話為圖片',
exportImageConfirm: '是否將對話儲存為圖片?',
exportSuccess: '儲存成功',
exportFailed: '儲存失敗',
usingContext: '上下文模式',
turnOnContext: '啟用上下文模式,在此模式下,發送訊息會包含之前的聊天記錄。',
turnOffContext: '關閉上下文模式,在此模式下,發送訊息不會包含之前的聊天記錄。',
deleteMessage: '刪除訊息',
deleteMessageConfirm: '是否刪除此訊息?',
deleteHistoryConfirm: '確定刪除此紀錄?',
clearHistoryConfirm: '確定清除紀錄?',
preview: '預覽',
showRawText: '顯示原文',
},
setting: {
setting: '設定',
general: '總覽',
advanced: '進階',
config: '設定',
avatarLink: '頭貼連結',
name: '名稱',
description: '描述',
role: '角色設定',
temperature: 'Temperature',
top_p: 'Top_p',
resetUserInfo: '重設使用者資訊',
chatHistory: '紀錄',
theme: '主題',
language: '語言',
api: 'API',
reverseProxy: '反向代理',
timeout: '逾時',
socks: 'Socks',
httpsProxy: 'HTTPS Proxy',
balance: 'API Credit 餘額',
monthlyUsage: '本月使用量',
},
store: {
siderButton: '提示詞商店',
local: '本機',
online: '線上',
title: '標題',
description: '描述',
clearStoreConfirm: '是否清除資料?',
importPlaceholder: '請將 JSON 資料貼在此處',
addRepeatTitleTips: '標題重複,請重新輸入',
addRepeatContentTips: '內容重複:{msg},請重新輸入',
editRepeatTitleTips: '標題衝突,請重新修改',
editRepeatContentTips: '內容衝突{msg} ,請重新修改',
importError: '鍵值不符合',
importRepeatTitle: '因標題重複跳過:{msg}',
importRepeatContent: '因內容重複跳過:{msg}',
onlineImportWarning: '注意:請檢查 JSON 檔案來源!',
downloadError: '請檢查網路狀態與 JSON 檔案有效性',
},
}
+1
View File
@@ -4,6 +4,7 @@ import { setupI18n } from './locales'
import { setupAssets, setupScrollbarStyle } from './plugins'
import { setupStore } from './store'
import { setupRouter } from './router'
import 'virtual:svg-icons-register' // svg图标注册
async function bootstrap() {
const app = createApp(App)
-21
View File
@@ -11,26 +11,5 @@ export function setupPageGuard(router: Router) {
else
next()
// if (!authStore.session) {
// try {
// const data = await authStore.getSession()
// if (String(data.auth) === 'false' && authStore.token)
// authStore.removeToken()
// if (to.path === '/500')
// next({ name: 'Root' })
// else
// next()
// }
// catch (error) {
// if (to.path !== '/500')
// next({ name: '500' })
// else
// next()
// }
// }
// else {
// next()
// }
})
}
+1 -1
View File
@@ -4,7 +4,7 @@ const LOCAL_NAME = 'appSetting'
export type Theme = 'light' | 'dark' | 'auto'
export type Language = 'zh-CN' | 'zh-TW' | 'en-US' | 'ko-KR'
export type Language = 'zh-CN' | 'en-US'
export interface AppState {
siderCollapsed: boolean
+20 -11
View File
@@ -1,20 +1,29 @@
import type { AuthState } from './index'
import { ss } from '@/utils/storage'
const LOCAL_NAME = 'SECRET_TOKEN'
export function getToken() {
return ss.get(LOCAL_NAME)
// export function getToken() {
// return ss.get(LOCAL_NAME)
// }
// export function setToken(token: string) {
// return ss.set(LOCAL_NAME, token)
// }
// export function setUserInfo(userInfo: User.Info) {
// return ss.set(LOCAL_NAME, userInfo)
// }
// export function getUserInfo() {
// return ss.get(LOCAL_NAME)
// }
export function setStorage(state: AuthState) {
return ss.set(LOCAL_NAME, state)
}
export function setToken(token: string) {
return ss.set(LOCAL_NAME, token)
}
export function setUserInfo(userInfo: User.Info) {
return ss.set(LOCAL_NAME, userInfo)
}
export function getUserInfo() {
export function getStorage() {
return ss.get(LOCAL_NAME)
}
+29 -41
View File
@@ -1,63 +1,51 @@
import { defineStore } from 'pinia'
import { getToken, getUserInfo, removeToken, setToken } from './helper'
import { store } from '@/store'
import { fetchSession } from '@/api'
import { getStorage, removeToken as hRemoveToken, setStorage } from './helper'
import { VisitMode } from '@/enums/auth'
interface SessionResponse {
auth: boolean
model: 'ChatGPTAPI' | 'ChatGPTUnofficialProxyAPI'
}
// interface SessionResponse {
// auth: boolean
// }
export interface AuthState {
token: string | undefined
userInfo: User.Info | undefined
session: SessionResponse | null
token: string | null
userInfo: User.Info | null
// session: SessionResponse | null
visitMode: VisitMode
}
const defaultState: AuthState = {
token: null,
userInfo: null,
visitMode: VisitMode.VISIT_MODE_LOGIN,
}
export const useAuthStore = defineStore('auth-store', {
state: (): AuthState => ({
userInfo: getUserInfo(),
token: getToken(),
session: null,
}),
getters: {
isChatGPTAPI(state): boolean {
return state.session?.model === 'ChatGPTAPI'
},
},
state: (): AuthState => getStorage() || defaultState,
actions: {
async getSession() {
try {
const { data } = await fetchSession<SessionResponse>()
this.session = { ...data }
return Promise.resolve(data)
}
catch (error) {
return Promise.reject(error)
}
},
setToken(token: string) {
this.token = token
setToken(token)
this.saveStorage()
},
setUserInfo(userInfo: User.Info) {
this.userInfo = userInfo
this.setUserInfo(userInfo)
this.saveStorage()
},
setVisitMode(visitMode: VisitMode) {
this.visitMode = visitMode
this.saveStorage()
},
saveStorage() {
setStorage(this.$state)
},
// 清除所有的本地储存
removeToken() {
this.token = undefined
removeToken()
this.$state = defaultState
hRemoveToken()
},
},
})
export function useAuthStoreWithout() {
return useAuthStore(store)
}
})
-1
View File
@@ -1,6 +1,5 @@
export * from './app'
export * from './user'
export * from './settings'
export * from './auth'
export * from './admin'
export * from './notice'
+3
View File
@@ -16,6 +16,9 @@ export function defaultStatePanelConfig(): Panel.panelConfig {
logoImageSrc: '',
clockShowSecond: false,
searchBoxShow: false,
searchBoxSearchIcon: false,
marginBottom: 10,
marginTop: 10,
}
}
+2
View File
@@ -37,6 +37,8 @@ export const usePanelState = defineStore('panel', {
getUserConfig<Panel.userConfig>().then((res) => {
if (res.code === 0)
this.panelConfig = res.data.panel
else
this.resetPanelConfig() // 重置恢复默认
this.recordState()
})
},
-30
View File
@@ -1,30 +0,0 @@
import { ss } from '@/utils/storage'
const LOCAL_NAME = 'settingsStorage'
export interface SettingsState {
systemMessage: string
temperature: number
top_p: number
}
export function defaultSetting(): SettingsState {
return {
systemMessage: 'You are ChatGPT, a large language model trained by OpenAI. Follow the user\'s instructions carefully. Respond using markdown.',
temperature: 0.8,
top_p: 1,
}
}
export function getLocalState(): SettingsState {
const localSetting: SettingsState | undefined = ss.get(LOCAL_NAME)
return { ...defaultSetting(), ...localSetting }
}
export function setLocalState(setting: SettingsState): void {
ss.set(LOCAL_NAME, setting)
}
export function removeLocalState() {
ss.remove(LOCAL_NAME)
}
-22
View File
@@ -1,22 +0,0 @@
import { defineStore } from 'pinia'
import type { SettingsState } from './helper'
import { defaultSetting, getLocalState, removeLocalState, setLocalState } from './helper'
export const useSettingStore = defineStore('setting-store', {
state: (): SettingsState => getLocalState(),
actions: {
updateSetting(settings: Partial<SettingsState>) {
this.$state = { ...this.$state, ...settings }
this.recordState()
},
resetSetting() {
this.$state = defaultSetting()
removeLocalState()
},
recordState() {
setLocalState(this.$state)
},
},
})
+12
View File
@@ -0,0 +1,12 @@
declare namespace File {
interface Info extends Common.InfoBase {
src: string
userId: number
fileName: string
method: number
ext: string
}
}
+4
View File
@@ -8,6 +8,7 @@ declare namespace Panel {
icon: ItemIcon |null
title: string
url: string
sort?: number
lanUrl?: string
description?: string
openMethod: number
@@ -48,6 +49,9 @@ declare namespace Panel {
clockShowSecond?:boolean
clockColor?:string
searchBoxShow?:boolean
searchBoxSearchIcon?:boolean
marginTop?:number
marginBottom?:number
}
interface userConfig{
+83 -4
View File
@@ -2,12 +2,14 @@ import moment from 'moment'
import { h } from 'vue'
import type { NotificationReactive } from 'naive-ui'
import { NButton, createDiscreteApi } from 'naive-ui'
import { useNoticeStore, useUserStore } from '@/store'
import { getInfo as getUserInfo } from '@/api/system/user'
import { useAuthStore, useNoticeStore, useUserStore } from '@/store'
import { getAuthInfo } from '@/api/system/user'
import type { VisitMode } from '@/enums/auth'
import { getListByDisplayType as getListByDisplayTypeApi } from '@/api/notice'
const noticeStore = useNoticeStore()
const userStore = useUserStore()
const authStore = useAuthStore()
const { notification } = createDiscreteApi(['notification'])
@@ -102,9 +104,15 @@ export function getTitle(titile: string) {
//
export async function updateLocalUserInfo() {
const { data } = await getUserInfo<User.Info>()
interface Req {
user: User.Info
visitMode: VisitMode
}
userStore.updateUserInfo({ headImage: data.headImage, name: data.name })
const { data } = await getAuthInfo<Req>()
userStore.updateUserInfo({ headImage: data.user.headImage, name: data.user.name })
authStore.setUserInfo(data.user)
authStore.setVisitMode(data.visitMode)
}
export async function getNotice(displayType: number | number[]) {
@@ -123,6 +131,43 @@ export async function getNotice(displayType: number | number[]) {
}
}
// 权限受限暂时不用
// export async function getFaviconUrl(url: string, extName = 'ico'): Promise<string | null> {
// try {
// // 获取网址的域名
// const { protocol, host } = new URL(url)
// const domain = `${protocol}//${host}`
// // 构建 favicon URL
// const faviconUrl = `${domain}/favicon.${extName}`
// // 检查 favicon 是否存在,包含 CORS 头部
// const response = await fetch(faviconUrl, { method: 'HEAD', mode: 'cors' })
// // 如果请求成功,返回 favicon URL
// if (response.ok) {
// return faviconUrl
// }
// else {
// console.log('Favicon not found.')
// return null
// }
// }
// catch (error) {
// // 如果出现错误,返回 null,表示找不到 favicon
// console.error('Error:', error)
// return null
// }
// }
export function getFaviconUrl(url: string): string {
// 获取网址的域名
const { protocol, host } = new URL(url)
const domain = `${protocol}//${host}`
// 构建 favicon URL
return `${domain}/favicon.ico`
}
/**
* @description: 获取随机码
* @param {number} size
@@ -142,3 +187,37 @@ export function randomCode(size: number, seed?: Array<string>) {
}
return createPassword
}
// 复制文字到剪切板
export async function copyToClipboard(text: string): Promise<boolean> {
if (navigator.clipboard) {
// 使用 Clipboard API
try {
await navigator.clipboard.writeText(text)
return true
}
catch (err) {
console.error('copy fail', err)
return false
}
}
else {
// 兼容旧版浏览器
const textArea = document.createElement('textarea')
textArea.value = text
document.body.appendChild(textArea)
textArea.select()
try {
document.execCommand('copy')
return true
}
catch (err) {
console.error('copy fail', err)
return false
}
finally {
document.body.removeChild(textArea)
}
}
}
+186
View File
@@ -0,0 +1,186 @@
import CryptoJS from 'crypto-js'
import moment from 'moment'
const VERSION = 1 // 当前配置文件版本
const ALLOW_LOW_VERSION = 1 // 最小支持的配置文件版本号
const APPNAME = 'Sun-Panel-Config'
export class FormatError extends Error {
constructor(message: string) {
super(message)
this.name = 'FormatError'
}
}
export class ConfigVersionLowError extends Error {
constructor(message: string) {
super(message)
this.name = 'ConfigVersionLowError'
}
}
export interface JsonStructure {
version: number
appName: 'Sun-Panel-Config'
exportTime: string
appVersion: string
icons?: any
// styleConfig: Panel.panelConfig
md5: string
}
// 图标
export interface Icon {
title: string
sort: number
icon: Panel.ItemIcon | null
url: string
lanUrl: string
description: string
openMethod: number
}
// 图标组
export interface IconGroup {
title: string
sort: number
children: Icon[]
}
interface ExportJsonResult {
addIconsData(datas: IconGroup[]): ExportJsonResult
exportFile(): void
string(): string
}
// 导出数据
export function exportJson(appVersion?: string): ExportJsonResult {
const jsonData: JsonStructure = {
version: VERSION,
appName: APPNAME,
exportTime: moment().format('YYYY-MM-DD HH:mm:ss'),
appVersion: appVersion || '',
md5: '',
}
// MD5 生成函数
function generateMD5AndUpdate() {
jsonData.md5 = generateMD5(JSON.stringify(jsonData))
}
return {
// 添加图标信息
addIconsData(datas: IconGroup[]) {
jsonData.icons = datas
return this
},
// 导出json文件
exportFile() {
generateMD5AndUpdate()
const jsonString = JSON.stringify(jsonData)
if (jsonString) {
const blob = new Blob([jsonString], { type: 'application/json' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = `SunPanel-Data${moment().format('YYYYMMDDHHmm')}.sun-panel.json`
link.click()
}
},
// 返回字符串
string() {
generateMD5AndUpdate()
return JSON.stringify(jsonData)
},
}
}
export interface ImportJsonResult {
isPassCheckMd5: () => boolean
isPassCheckConfigVersionOld: () => boolean // 配置(数据)的版本是否过旧
isPassCheckConfigVersionNew: () => boolean // 配置(数据)的版本是否过新
isPassCheckConfigVersionBest: () => boolean // 验证程序的导入版本驱动是否为最佳 当配置文件和驱动版本相等的时候为最佳,否则不匹配过新或者过旧
jsonStruct: JsonStructure // 根据实际情况提供更具体的类型定义
hasProperty: (key: string) => boolean
geticons: () => IconGroup[] // 根据实际情况提供更具体的类型定义
}
// 导入json数据
export function importJsonString(jsonString: string): ImportJsonResult | null {
let data: any
try {
data = JSON.parse(jsonString)
}
catch (error) {
throw new FormatError('file format error')
return null
}
const jsonStruct = transformJson(data)
const md5 = generateMD5(jsonString)
if (!jsonStruct) {
throw new FormatError('file format error')
return null
}
if (data.version < ALLOW_LOW_VERSION)
throw new ConfigVersionLowError('')
return {
isPassCheckMd5: () => md5 === jsonStruct.md5,
isPassCheckConfigVersionOld: () => !(jsonStruct.version < VERSION),
isPassCheckConfigVersionNew: () => !(jsonStruct.version > VERSION),
isPassCheckConfigVersionBest: () => jsonStruct.version === VERSION,
jsonStruct,
hasProperty: (key: string): boolean => {
return key in jsonStruct
},
geticons: (): IconGroup[] => {
return jsonStruct.icons || []
},
}
}
function transformJson(jsonData: any): JsonStructure | null {
// 检查必须存在的键
const requiredKeys: Array<keyof JsonStructure> = ['version', 'appName', 'exportTime', 'appVersion', 'md5']
for (const key of requiredKeys) {
if (!(key in jsonData))
return null
}
// 使用类型断言将 JSON 数据转换为指定类型
const transformedData: JsonStructure = jsonData as JsonStructure
// 返回转换后的数据
return transformedData
}
function generateMD5(jsonString: string): string {
try {
// 解析 JSON 字符串
const data: any = JSON.parse(jsonString)
// 移除 md5 字段及其对应的值
removeMD5Field(data)
// 将修改后的 JSON 对象转换回字符串
const modifiedJsonString = JSON.stringify(data)
// 使用 crypto-js 计算 MD5 值
const md5 = CryptoJS.MD5(modifiedJsonString).toString()
return md5
}
catch (error) {
return ''
}
}
function removeMD5Field(obj: any): void {
for (const key in obj) {
if (key === 'md5') {
// 移除 md5 字段
delete obj[key]
return
}
}
}
@@ -1,26 +0,0 @@
<script setup lang='ts'>
import { defineAsyncComponent, ref } from 'vue'
import { HoverButton, SvgIcon } from '@/components/common'
const Setting = defineAsyncComponent(() => import('@/components/common/Setting/index.vue'))
const show = ref(false)
</script>
<template>
<footer class="flex items-center justify-between min-w-0 p-4 overflow-hidden border-t dark:border-neutral-800">
<div class="flex-1 flex-shrink-0 overflow-hidden">
<!-- <UserAvatar /> -->
</div>
<a href="https://gitee.com/hslr/sun-panel.git">Gitee</a>
<a href="https://github.com/hslr-s/sun-panel.git">Github</a>
<HoverButton @click="show = true">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:settings-4-line" />
</span>
</HoverButton>
<Setting v-if="show" v-model:visible="show" />
</footer>
</template>
-3
View File
@@ -1,3 +0,0 @@
import UserInfoFooter from './UserInfoFooter/index.vue'
export { UserInfoFooter }
+23 -23
View File
@@ -1,41 +1,41 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { NColorPicker } from 'naive-ui'
import { onMounted, ref } from 'vue'
import { getList } from '@/api/system/file'
// 获取背景颜色的 RGB 值(假设这里获取到了背景颜色的值)
const bgColor = ref('#000000') // 假设为蓝色
import UploadFileManager from '@/components/apps/UploadFileManager/index.vue'
// 计算颜色的明暗度
const calculateLuminance = (color: string) => {
const hex = color.replace(/^#/, '')
const r = parseInt(hex.substring(0, 2), 16)
const g = parseInt(hex.substring(2, 4), 16)
const b = parseInt(hex.substring(4, 6), 16)
return (0.299 * r + 0.587 * g + 0.114 * b) / 255
const imageList = ref<File.Info[]>([])
async function getFileList() {
const { data } = await getList<Common.ListResponse<File.Info[]>>()
console.log(data)
imageList.value = data.list
}
// 根据明暗度切换文本颜色
const textColor = computed(() => {
const luminance = calculateLuminance(bgColor.value)
return luminance > 0.5 ? 'black' : 'white'
onMounted(() => {
getFileList()
})
</script>
<template>
<div :style="{ backgroundColor: bgColor, color: textColor }">
Background Color Example
<div>
<UploadFileManager />
</div>
<NColorPicker v-model:value="bgColor" />
</template>
<style scoped>
/* 样式可以根据实际需求自定义 */
div {
width: 200px;
height: 100px;
.card {
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
height: 100px;
/* 或者其他容器高度 */
}
.transparent-grid {
background-image: linear-gradient(45deg, #e6e4e4 25%, transparent 25%, transparent 75%, #e6e4e4 75%),
linear-gradient(45deg, #e6e4e4 25%, transparent 25%, transparent 75%, #e6e4e4 75%);
background-size: 16px 16px;
background-position: 0 0, 8px 8px;
}
</style>
@@ -0,0 +1,190 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { NLayout, NLayoutContent, NLayoutSider, NSpace } from 'naive-ui'
import { useAuthStore } from '@/store'
import { AppLoader, RoundCardModal, SvgIcon } from '@/components/common'
interface App {
name: string
componentName: string
icon: string
auth?: number
}
const props = defineProps<{
visible: boolean
}>()
const emit = defineEmits<{
(e: 'update:visible', visible: boolean): void
}>()
const componentName = ref('UserInfo')
const collapsed = ref(false)
const screenWidth = ref(0)
const isSmallScreen = ref(false)
const defaultTitle = '系统应用 & 设置'
const title = ref('')
const height = ref('500px')
const apps = ref<App[]>([
{
name: '用户信息',
componentName: 'UserInfo',
icon: 'material-symbols-person-edit-outline-rounded',
},
{
name: '基本设置',
componentName: 'Style',
icon: 'ep-setting',
},
{
name: '分组管理',
componentName: 'ItemGroupManage',
icon: 'material-symbols-ad-group-outline-rounded',
},
{
name: '导入导出',
componentName: 'ImportExport',
icon: 'icon-park-outline-import-and-export',
},
{
name: '上传文件管理',
componentName: 'UploadFileManager',
icon: 'tabler:file-upload',
},
{
name: '关于',
componentName: 'About',
icon: 'lucide-info',
},
])
const authStore = useAuthStore()
const show = computed({
get: () => props.visible,
set: (visible: boolean) => {
emit('update:visible', visible)
},
})
function handleClickApp(item: App) {
componentName.value = item.componentName
if (isSmallScreen.value)
collapsed.value = true
}
function getScreenWidth() {
return window.innerWidth
}
function handleResize() {
screenWidth.value = getScreenWidth()
if (screenWidth.value < 640) {
collapsed.value = true
isSmallScreen.value = true
}
else {
collapsed.value = false
isSmallScreen.value = false
}
}
onMounted(() => {
const adminApp: App = {
name: '用户管理',
componentName: 'Users',
icon: 'lucide-users',
auth: 1,
}
// 初始化
if (authStore.userInfo?.role === 1)
apps.value.push(adminApp)
window.addEventListener('resize', handleResize)
handleResize()
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
</script>
<template>
<div>
<RoundCardModal
v-model:show="show"
style="max-width: 900px;"
title="应用列表"
size="small"
>
<template #header>
<div class="flex items-center select-none" @click="collapsed = !collapsed">
<div class="text-3xl cursor-pointer" style="color:var(--n-color-target)">
<SvgIcon class=" transition-all duration-500" :icon="collapsed ? 'tabler-layout-sidebar-right-collapse-filled' : 'tabler-layout-sidebar-left-collapse-filled'" />
</div>
<div class="ml-1">
{{ title === '' ? defaultTitle : title }}
</div>
</div>
</template>
<div class="w-full h-full">
<NSpace vertical size="large" style="height: 100%;width: 100%;">
<NLayout has-sider>
<NLayoutSider
v-model:collapsed="collapsed"
collapse-mode="width"
:collapsed-width="0"
:width="isSmallScreen ? '100%' : 240"
style="height: 100%;"
content-style="overflow: hidden"
>
<div
class="p-[5px] bg-slate-200 rounded-xl overflow-auto"
:style="{
width: isSmallScreen ? '100%' : '220px',
minWidth: '200px',
height,
}"
>
<div
v-for=" (item, index) in apps"
:key="index"
:style="{ color: componentName === item.componentName ? 'var(--n-color-target)' : '' }"
@click="handleClickApp(item)"
>
<div
class="bg-white p-[10px] rounded-lg mb-[5px] font-bold cursor-pointer flex items-center"
>
<div class="flex items-center justify-center">
<div class="text-lg">
<SvgIcon :icon="item.icon" />
</div>
<span class="ml-2">{{ item.name }}</span>
</div>
<!-- 更多按钮 -->
<!-- <div class="ml-auto">
<SvgIcon icon="mingcute-more-1-fill" />
</div> -->
</div>
</div>
</div>
</NLayoutSider>
<NLayoutContent :content-style="{ height }">
<div class="rounded-lg h-full overflow-auto transition-all duration-500 min-w-[300px] h-full" :class="(isSmallScreen && !collapsed) ? 'opacity-0' : 'opacity-100'">
<AppLoader :component-name="componentName" class="h-full" />
</div>
</NLayoutContent>
</NLayout>
</NSpace>
</div>
</RoundCardModal>
</div>
</template>
<style scoped>
.text-shadow {
text-shadow: 0px 0px 5px gray;
}
</style>
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { NButton, NColorPicker, NInput, NRadio, NUpload, useMessage } from 'naive-ui'
import type { UploadFileInfo } from 'naive-ui'
import { defineProps, ref, watch } from 'vue'
import { computed, defineProps } from 'vue'
import { ItemIcon } from '@/components/common'
import { useAuthStore } from '@/store'
@@ -13,7 +13,6 @@ const emit = defineEmits<{
}>()
const authStore = useAuthStore()
const ms = useMessage()
const checkedValueRef = ref<number | null>(props.itemIcon?.itemType || 1)
// 默认图标背景色
const defautSwatchesBackground = [
@@ -28,27 +27,39 @@ const defautSwatchesBackground = [
]
const initData: Panel.ItemIcon = {
itemType: 1,
itemType: 2,
backgroundColor: '#2a2a2a6b',
}
// const itemIconInfo = ref<Panel.ItemIcon>(props.itemIcon ?? { ...initData })
const itemIconInfo = ref<Panel.ItemIcon>({
...initData,
...props.itemIcon,
backgroundColor: props.itemIcon?.backgroundColor || initData.backgroundColor,
const itemIconInfo = computed({
get() {
const v = {
...initData,
...props.itemIcon,
backgroundColor: props.itemIcon?.backgroundColor || initData.backgroundColor,
}
return v
},
set() {
handleChange()
},
})
function handleIconTypeRadioChange(type: number) {
checkedValueRef.value = type
// checkedValueRef.value = type
itemIconInfo.value.itemType = type
emit('update:itemIcon', itemIconInfo.value)
handleChange()
}
function handleChange() {
emit('update:itemIcon', itemIconInfo.value || null)
}
function handleResetBackgroundColor() {
itemIconInfo.value.backgroundColor = initData.backgroundColor
handleChange()
}
const handleUploadFinish = ({
file,
event,
@@ -68,17 +79,13 @@ const handleUploadFinish = ({
return file
}
watch(itemIconInfo.value, () => {
handleChange()
})
</script>
<template>
<div>
<div class="mb-[10px]">
<NRadio
:checked="checkedValueRef === 1 "
:checked="itemIconInfo.itemType === 1 "
:value="1"
name="iconType"
@change="handleIconTypeRadioChange(1)"
@@ -87,7 +94,7 @@ watch(itemIconInfo.value, () => {
</NRadio>
<NRadio
:checked="checkedValueRef === 2"
:checked="itemIconInfo.itemType === 2"
:value="2"
name="iconType"
@change="handleIconTypeRadioChange(2)"
@@ -96,12 +103,12 @@ watch(itemIconInfo.value, () => {
</NRadio>
<NRadio
:checked="checkedValueRef === 3"
:checked="itemIconInfo.itemType === 3"
:value="3"
name="iconType"
@change="handleIconTypeRadioChange(3)"
>
图标
在线图标
</NRadio>
</div>
@@ -115,22 +122,22 @@ watch(itemIconInfo.value, () => {
<!-- 文字 -->
<div class="ml-[20px]">
<!-- <NImage :src="model.icon" preview-disabled /> -->
<div v-if="checkedValueRef === 1">
<div v-if="itemIconInfo.itemType === 1">
<NInput v-model:value="itemIconInfo.text" class="mb-[5px]" size="small" type="text" placeholder="请输入文字作为图标" @input="handleChange" />
</div>
<div v-if="checkedValueRef === 3">
<div v-if="itemIconInfo.itemType === 3">
<div>
<NInput v-model:value="itemIconInfo.text" class="mb-[5px]" size="small" type="text" placeholder="请输入图标名字" @input="handleChange" />
<NButton quaternary type="info">
<a target="_blank" href="https://icon-sets.iconify.design/">图标库</a>
<a target="_blank" href="https://icon-sets.iconify.design/">在线图标库</a>
</NButton>
</div>
</div>
<!-- 图片 -->
<div v-if="checkedValueRef === 2">
<div v-if="itemIconInfo.itemType === 2">
<NInput v-model:value="itemIconInfo.src" class="mb-[5px] w-full" size="small" type="text" placeholder="输入图标地址或上传" @input="handleChange" />
<NUpload
action="/api/file/uploadImg"
@@ -142,7 +149,7 @@ watch(itemIconInfo.value, () => {
@finish="handleUploadFinish"
>
<NButton size="small">
点击上传
本地上传
</NButton>
</NUpload>
</div>
@@ -164,7 +171,7 @@ watch(itemIconInfo.value, () => {
/>
</div>
<div v-if="itemIconInfo.backgroundColor !== initData.backgroundColor" class="w-auto text-slate-500 mr-[10px] cursor-pointer">
<NButton quaternary type="info" @click="itemIconInfo.backgroundColor = initData.backgroundColor">
<NButton quaternary type="info" @click="handleResetBackgroundColor">
恢复默认
</NButton>
</div>
+65 -42
View File
@@ -1,19 +1,22 @@
<script setup lang="ts">
import { computed, defineEmits, defineProps, onMounted, ref, watch } from 'vue'
import { computed, defineEmits, defineProps, ref, watch } from 'vue'
import type { FormInst, FormRules } from 'naive-ui'
import { NButton, NForm, NFormItem, NGrid, NGridItem, NInput, NModal, NSelect, useMessage } from 'naive-ui'
import { NButton, NForm, NFormItem, NGrid, NGridItem, NInput, NInputGroup, NModal, NSelect, useMessage } from 'naive-ui'
import IconEditor from './IconEditor.vue'
import { edit } from '@/api/panel/itemIcon'
import { edit, getSiteFavicon } from '@/api/panel/itemIcon'
import { getList as getGroupList } from '@/api/panel/itemIconGroup'
interface Props {
visible: boolean
itemInfo: Panel.Info | null
itemGroupId?: number
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const ms = useMessage()
const submitLoading = ref(false)
const getIconLoading = ref([false, false])
const itemIconGroupOptions = ref<{
label: string
value: number
@@ -25,7 +28,7 @@ const restoreDefault: Panel.Info = {
url: '',
lanUrl: '',
description: '',
openMethod: 1,
openMethod: 2,
}
interface Emit {
@@ -33,10 +36,7 @@ interface Emit {
(e: 'done', item: Panel.Info): void// 创建完成
}
const model = ref<Panel.Info>(props.itemInfo !== null ? { ...props.itemInfo } : { ...restoreDefault })
// const model = computed(()=>{
// return props.itemInfo !== null ? { ...props.itemInfo } : { ...restoreDefault }
// })
const model = ref<Panel.Info>(props.itemInfo ? { ...props.itemInfo } : { ...restoreDefault })
const formRef = ref<FormInst | null>(null)
const rules: FormRules = {
@@ -74,22 +74,6 @@ const options = [
},
]
// const urlProtocolOptions = [
// {
// default: true,
// label: 'http://',
// value: 'http://',
// },
// {
// label: 'https://',
// value: 'https://',
// },
// {
// label: '不使用',
// value: '',
// },
// ]
// 更新值父组件传来的值
const show = computed({
get: () => props.visible,
@@ -99,16 +83,24 @@ const show = computed({
})
async function editApi() {
const { code, data, msg } = await edit<Panel.ItemInfo>(model.value)
if (code === 0) {
show.value = false
model.value = { ...restoreDefault }
submitLoading.value = true
try {
const { code, data, msg } = await edit<Panel.ItemInfo>(model.value)
if (code === 0) {
show.value = false
model.value = { ...restoreDefault }
console.log('重置完成', model.value)
emit('done', data)
emit('done', data)
}
else {
ms.error(`保存失败:${msg}`)
}
}
else {
ms.error(`保存失败:${msg}`)
catch (error) {
ms.error('保存失败')
}
submitLoading.value = false
}
const handleValidateButtonClick = (e: MouseEvent) => {
@@ -119,8 +111,33 @@ const handleValidateButtonClick = (e: MouseEvent) => {
})
}
watch(() => props.itemInfo, (newValue) => {
model.value = newValue || { ...restoreDefault }
async function getIconByUrl(url: string, loadingIndex: number) {
getIconLoading.value[loadingIndex] = true
try {
const { code, data } = await getSiteFavicon<{ iconUrl: string }>(url)
if (code === 0) {
model.value.icon = {
itemType: 2,
src: data.iconUrl,
}
}
else {
ms.error('图标获取失败')
}
}
catch (error) {
ms.error('图标获取失败')
}
getIconLoading.value[loadingIndex] = false
}
watch(() => props.visible, (newValue) => {
if (newValue === true) {
model.value = props.itemInfo ? { ...props.itemInfo } : { ...restoreDefault }
if (props.itemGroupId)
model.value.itemIconGroupId = props.itemGroupId
}
getGroupListOptions()
})
@@ -132,8 +149,8 @@ function getGroupListOptions() {
for (let i = 0; i < data.list.length; i++) {
const element = data.list[i]
if (i === 0 && !model.value.itemIconGroupId) {
restoreDefault.itemIconGroupId = element.id
model.value.itemIconGroupId = element.id
restoreDefault.itemIconGroupId = element.id
}
itemIconGroupOptions.value.push({
@@ -147,14 +164,10 @@ function getGroupListOptions() {
}
})
}
onMounted(() => {
getGroupListOptions()
})
</script>
<template>
<NModal v-model:show="show" preset="card" style="width: 600px;border-radius: 1rem;" :title="itemInfo ? '修改项目' : '添加项目'">
<NModal v-model:show="show" preset="card" size="small" style="width: 600px;border-radius: 1rem;" :title="itemInfo ? '修改项目' : '添加项目'">
<div class="h-[600px] overflow-auto p-[5px]">
<NForm ref="formRef" :model="model" :rules="rules">
<NGrid cols="2" :x-gap="10" item-responsive>
@@ -175,10 +188,20 @@ onMounted(() => {
</NFormItem>
<NFormItem path="url" label="跳转地址">
<!-- <NSelect :style="{ width: '100px' }" :options="urlProtocolOptions" /> -->
<NInput v-model:value="model.url" type="text" :maxlength="1000" placeholder="http(s)://" />
<NInputGroup>
<NInput v-model:value="model.url" type="text" :maxlength="1000" placeholder="http(s)://" />
<NButton :disabled="!model.url" :loading="getIconLoading[0]" @click="getIconByUrl(model.url, 0)">
获取图标
</NButton>
</NInputGroup>
</NFormItem>
<NFormItem path="lanUrl" label="局域网跳转地址">
<NInput v-model:value="model.lanUrl" type="text" :maxlength="1000" placeholder="http(s)://(可以留空,切换到局域网模式,点击会使用该地址)" />
<NInputGroup>
<NInput v-model:value="model.lanUrl" type="text" :maxlength="1000" placeholder="http(s)://(可以留空,切换到局域网模式,点击会使用该地址)" />
<NButton :disabled="!model.lanUrl" :loading="getIconLoading[1]" @click="getIconByUrl(model.lanUrl || '', 1)">
获取图标
</NButton>
</NInputGroup>
</NFormItem>
<NFormItem path="description" label="描述信息">
<NInput v-model:value="model.description" type="text" show-count :maxlength="100" placeholder="请填写描述信息" />
@@ -190,7 +213,7 @@ onMounted(() => {
</div>
<template #footer>
<NButton type="success" style="float: right;" @click="handleValidateButtonClick">
<NButton type="success" :loading="submitLoading" style="float: right;" @click="handleValidateButtonClick">
确定
</NButton>
</template>
@@ -1,56 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue'
import { NTabPane, NTabs } from 'naive-ui'
import Style from './tabs/Style.vue'
import About from './tabs/About.vue'
import Users from './tabs/Users.vue'
import UserInfo from './tabs/UserInfo.vue'
import ItemGroupManage from './tabs/ItemGroupManage.vue'
import { RoundCardModal } from '@/components/common'
const props = defineProps<{
visible: boolean
}>()
const emit = defineEmits<{
(e: 'update:visible', visible: boolean): void
}>()
const show = computed({
get: () => props.visible,
set: (visible: boolean) => {
emit('update:visible', visible)
},
})
</script>
<template>
<div>
<RoundCardModal v-model:show="show" title="设置" style="max-height: 700px;max-width: 600px;">
<NTabs type="line" size="small" animated>
<NTabPane name="style" tab="样式">
<Style />
</NTabPane>
<NTabPane name="itemGroupManage" tab="分组管理">
<ItemGroupManage />
</NTabPane>
<NTabPane name="userInfo" tab="登录信息">
<UserInfo />
</NTabPane>
<NTabPane name="about" tab="关于">
<About />
</NTabPane>
<NTabPane name="password" tab="账号管理">
<Users />
</NTabPane>
</NTabs>
</RoundCardModal>
</div>
</template>
<style scoped>
.text-shadow {
text-shadow: 0px 0px 5px gray;
}
</style>
@@ -1,62 +0,0 @@
<script setup lang="ts">
import { NButton, NCard, useDialog, useMessage } from 'naive-ui'
import { useAuthStore, usePanelState, useUserStore } from '@/store'
import { logout } from '@/api'
import { router } from '@/router'
import { SvgIcon } from '@/components/common/'
const userStore = useUserStore()
const authStore = useAuthStore()
const panelState = usePanelState()
const ms = useMessage()
const dialog = useDialog()
async function logoutApi() {
await logout()
userStore.resetUserInfo()
authStore.removeToken()
panelState.removeState()
ms.success('您已经安全退出,期待与你再次相见!')
router.push({ path: '/login' })
}
function handleLogiut() {
dialog.warning({
title: '警告',
content: '你确定要退出登录',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
logoutApi()
},
})
}
</script>
<template>
<div class="bg-slate-200 rounded-[10px] p-[8px]">
<NCard style="border-radius:10px" size="small">
<div class="text-slate-500 mb-[5px]">
账号/邮箱
</div>
{{ userStore.userInfo.username }}
</NCard>
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
<div class="text-slate-500 mb-[5px]">
昵称
</div>
{{ userStore.userInfo.name }}
</NCard>
<NCard style="border-radius:10px" class="mt-[10px]" size="small">
<NButton size="small" quaternary type="error" @click="handleLogiut">
<template #icon>
<SvgIcon icon="tabler:logout" />
</template>
退出登录
</NButton>
</NCard>
</div>
</template>
+2 -2
View File
@@ -1,8 +1,8 @@
import Result from './Result/index.vue'
import EditItem from './EditItem/index.vue'
import Setting from './Setting/index.vue'
import AppIcon from './AppIcon/index.vue'
import AppStarter from './AppStarter/index.vue'
export {
Result, EditItem, Setting, AppIcon,
Result, EditItem, AppIcon, AppStarter,
}
+176 -97
View File
@@ -1,33 +1,29 @@
<script setup lang="ts">
import { VueDraggable } from 'vue-draggable-plus'
import { NBackTop, NButton, NButtonGroup, NDropdown, NModal, NSkeleton, NSpin, useDialog, useMessage } from 'naive-ui'
import { nextTick, onMounted, ref, watch } from 'vue'
import { AppIcon, EditItem, Setting } from './components'
import { nextTick, onMounted, ref } from 'vue'
import { AppIcon, AppStarter, EditItem } from './components'
import { Clock, SearchBox } from '@/components/deskModule'
import { SvgIcon } from '@/components/common'
import { deletes, getListByGroupId, saveSort } from '@/api/panel/itemIcon'
import { getList as getGroupList } from '@/api/panel/itemIconGroup'
import { getInfo } from '@/api/system/user'
import { usePanelState, useUserStore } from '@/store'
import { setTitle, updateLocalUserInfo } from '@/utils/cmn'
import { useAuthStore, usePanelState } from '@/store'
import { PanelPanelConfigStyleEnum, PanelStateNetworkModeEnum } from '@/enums'
import { setTitle } from '@/utils/cmn'
import { VisitMode } from '@/enums/auth'
import { router } from '@/router'
interface StateDragAppSort {
status: boolean
}
interface ItemGroup extends Panel.ItemIconGroup {
sortStatus?: boolean
hoverStatus: boolean
items?: Panel.ItemInfo[]
}
const stateDragAppSort = ref<StateDragAppSort>({
status: false,
})
const ms = useMessage()
const dialog = useDialog()
const panelState = usePanelState()
const userStore = useUserStore()
const authStore = useAuthStore()
const scrollContainerRef = ref<HTMLElement | undefined>(undefined)
@@ -44,15 +40,12 @@ const dropdownMenuX = ref(0)
const dropdownMenuY = ref(0)
const dropdownShow = ref(false)
const currentRightSelectItem = ref<Panel.ItemInfo | null>(null)
const currentAddItenIconGroupId = ref<number | undefined>()
const settingModalShow = ref(false)
const items = ref<ItemGroup[]>([])
function handleAddAppClick() {
editItemInfoData.value = null
editItemInfoShow.value = true
}
const filterItems = ref<ItemGroup[]>([])
function openPage(openMethod: number, url: string, title?: string) {
switch (openMethod) {
@@ -74,7 +67,12 @@ function openPage(openMethod: number, url: string, title?: string) {
}
}
function handleItemClick(item: Panel.ItemInfo) {
function handleItemClick(itemGroupIndex: number, item: Panel.ItemInfo) {
if (items.value[itemGroupIndex] && items.value[itemGroupIndex].sortStatus) {
handleEditItem(item)
return
}
let jumpUrl = ''
if (item)
@@ -96,16 +94,23 @@ function getList() {
items.value = data.list
for (let i = 0; i < data.list.length; i++) {
const element = data.list[i]
getListByGroupId<Common.ListResponse<Panel.ItemInfo[]>>(element.id).then((res) => {
if (res.code === 0)
items.value[i].items = res.data.list
})
if (element.id)
updateItemIconGroupByNet(i, element.id)
}
filterItems.value = items.value
// console.log(items)
})
}
function handleSelect(key: string | number) {
// 从后端获取组下面的图标
function updateItemIconGroupByNet(itemIconGroupIndex: number, itemIconGroupId: number) {
getListByGroupId<Common.ListResponse<Panel.ItemInfo[]>>(itemIconGroupId).then((res) => {
if (res.code === 0)
items.value[itemIconGroupIndex].items = res.data.list
})
}
function handleRightMenuSelect(key: string | number) {
dropdownShow.value = false
// console.log(currentRightSelectItem, key)
let jumpUrl = panelState.networkMode === PanelStateNetworkModeEnum.lan ? currentRightSelectItem.value?.lanUrl : currentRightSelectItem.value?.url
@@ -125,8 +130,7 @@ function handleSelect(key: string | number) {
break
case 'edit':
// 这里有个奇怪的问题,如果不使用{...}的方式 父组件的值会同步修改 标记一下
editItemInfoData.value = { ...currentRightSelectItem.value } as Panel.ItemInfo
editItemInfoShow.value = true
handleEditItem({ ...currentRightSelectItem.value } as Panel.ItemInfo)
break
case 'delete':
dialog.warning({
@@ -153,7 +157,10 @@ function handleSelect(key: string | number) {
}
}
function handleContextMenu(e: MouseEvent, item: Panel.ItemInfo) {
function handleContextMenu(e: MouseEvent, itemGroupIndex: number, item: Panel.ItemInfo) {
if (items.value[itemGroupIndex] && items.value[itemGroupIndex].sortStatus)
return
e.preventDefault()
currentRightSelectItem.value = item
dropdownShow.value = false
@@ -183,10 +190,10 @@ function handleChangeNetwork(mode: PanelStateNetworkModeEnum) {
}
// 结束拖拽
function handleEndDrag(event: any, itemIconGroup: Panel.ItemIconGroup) {
// console.log(event)
// console.log(items.value)
}
// function handleEndDrag(event: any, itemIconGroup: Panel.ItemIconGroup) {
// // console.log(event)
// // console.log(items.value)
// }
function handleSaveSort(itemGroup: ItemGroup) {
const saveItems: Common.SortItemRequest[] = []
@@ -201,9 +208,8 @@ function handleSaveSort(itemGroup: ItemGroup) {
saveSort({ itemIconGroupId: itemGroup.id as number, sortItems: saveItems }).then(({ code, msg }) => {
if (code === 0) {
//
ms.success('保存成功')
// sortStatus.value = false
itemGroup.sortStatus = false
}
else {
ms.error(`保存失败:${msg}`)
@@ -235,33 +241,24 @@ function getDropdownMenuOptions() {
})
}
dropdownMenuOptions.push({
label: '编辑',
key: 'edit',
}, {
label: '删除',
key: 'delete',
})
if (authStore.visitMode === VisitMode.VISIT_MODE_LOGIN) {
dropdownMenuOptions.push({
label: '编辑',
key: 'edit',
}, {
label: '删除',
key: 'delete',
})
}
return dropdownMenuOptions
}
watch(() => stateDragAppSort.value.status, (newvalue: boolean) => {
if (newvalue === false)
getList()
else
ms.warning('进入排序模式,记得点击保存再退出')
})
onMounted(() => {
// 更新用户信息
updateLocalUserInfo()
getList()
// 获取用户信息
getInfo<User.Info>().then((res) => {
if (res.code === 0)
userStore.updateUserInfo(res.data)
})
// 更新同步云端配置
panelState.updatePanelConfigByCloud()
@@ -269,6 +266,59 @@ onMounted(() => {
if (panelState.panelConfig.logoText)
setTitle(panelState.panelConfig.logoText)
})
// 前端搜索过滤
function itemFrontEndSearch(keyword?: string) {
keyword = keyword?.trim()
if (keyword !== '' && panelState.panelConfig.searchBoxSearchIcon) {
const filteredData = ref<ItemGroup[]>([])
for (let i = 0; i < items.value.length; i++) {
const element = items.value[i].items?.filter((item: Panel.ItemInfo) => {
return (
item.title.toLowerCase().includes(keyword?.toLowerCase() ?? '')
|| item.url.toLowerCase().includes(keyword?.toLowerCase() ?? '')
|| item.description?.toLowerCase().includes(keyword?.toLowerCase() ?? '')
)
})
if (element && element.length > 0)
filteredData.value.push({ items: element, hoverStatus: false })
}
filterItems.value = filteredData.value
}
else {
filterItems.value = items.value
}
}
function handleSetHoverStatus(groupIndex: number, hoverStatus: boolean) {
if (items.value[groupIndex])
items.value[groupIndex].hoverStatus = hoverStatus
}
function handleSetSortStatus(groupIndex: number, sortStatus: boolean) {
if (items.value[groupIndex])
items.value[groupIndex].sortStatus = sortStatus
// 并未保存排序重新更新数据
if (!sortStatus) {
// 单独更新组
if (items.value[groupIndex] && items.value[groupIndex].id)
updateItemIconGroupByNet(groupIndex, items.value[groupIndex].id as number)
}
}
function handleEditItem(item: Panel.ItemInfo) {
editItemInfoData.value = item
editItemInfoShow.value = true
currentAddItenIconGroupId.value = undefined
}
function handleAddItem(itemIconGroupId?: number) {
editItemInfoData.value = null
editItemInfoShow.value = true
if (itemIconGroupId)
currentAddItenIconGroupId.value = itemIconGroupId
}
</script>
<template>
@@ -283,12 +333,15 @@ onMounted(() => {
/>
<div class="mask" :style="{ backgroundColor: `rgba(0,0,0,${panelState.panelConfig.backgroundMaskNumber})` }" />
<div ref="scrollContainerRef" class="absolute w-full h-full overflow-auto">
<div class="p-2.5 max-w-[1200px] mx-auto mt-[10%]">
<div
class="p-2.5 xs:max-w-[95%] lg:max-w-[80%] mx-auto "
:style="{ marginTop: `${panelState.panelConfig.marginTop}%`, marginBottom: `${panelState.panelConfig.marginBottom}%` }"
>
<!-- -->
<div class="mx-[auto] w-[80%]">
<div class="flex mx-[auto] items-center justify-center text-white">
<div>
<span class="text-2xl md:text-5xl font-bold text-shadow">
<span class="text-2xl md:text-6xl font-bold text-shadow">
{{ panelState.panelConfig.logoText }}
</span>
</div>
@@ -300,7 +353,7 @@ onMounted(() => {
</div>
</div>
<div v-if="panelState.panelConfig.searchBoxShow" class="flex mt-[20px] mx-auto sm:w-full lg:w-[80%]">
<SearchBox />
<SearchBox @itemSearch="itemFrontEndSearch" />
</div>
</div>
@@ -308,14 +361,29 @@ onMounted(() => {
<div class="mt-[50px]">
<!-- 组纵向排列 -->
<div
v-for="(itemGroup, itemGroupIndex) in items"
:key="itemGroupIndex"
v-for="(itemGroup, itemGroupIndex) in filterItems" :key="itemGroupIndex"
class="mt-[50px]"
:class="stateDragAppSort.status ? 'shadow-2xl border shadow-[0_0_30px_10px_rgba(0,0,0,0.8)] p-[10px] rounded-2xl' : ''"
:class="itemGroup.sortStatus ? 'shadow-2xl border shadow-[0_0_30px_10px_rgba(0,0,0,0.3)] p-[10px] rounded-2xl' : ''"
@mouseenter="handleSetHoverStatus(itemGroupIndex, true)"
@mouseleave="handleSetHoverStatus(itemGroupIndex, false)"
>
<!-- 分组标题 -->
<div class="text-white text-xl font-extrabold mb-[20px] ml-[10px]">
{{ itemGroup.title }}
<div class="text-white text-xl font-extrabold mb-[20px] ml-[10px] flex items-center">
<span>
{{ itemGroup.title }}
</span>
<div
v-if="authStore.visitMode === VisitMode.VISIT_MODE_LOGIN"
class="ml-2 delay-100 transition-opacity flex"
:class="itemGroup.hoverStatus ? 'opacity-100' : 'opacity-0'"
>
<span class="mr-2 cursor-pointer" title="添加快捷图标" @click="handleAddItem(itemGroup.id)">
<SvgIcon class="text-white font-xl" icon="typcn:plus" />
</span>
<span class="mr-2 cursor-pointer " title="排序组快捷图标" @click="handleSetSortStatus(itemGroupIndex, !itemGroup.sortStatus)">
<SvgIcon class="text-white font-xl" icon="ri:drag-drop-line" />
</span>
</div>
</div>
<!-- 详情图标 -->
@@ -323,32 +391,31 @@ onMounted(() => {
<div v-if="itemGroup.items">
<VueDraggable
v-model="itemGroup.items" item-key="sort" :animation="300"
class="mx-auto mt-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:12 gap-5"
class="icon-info-box"
filter=".not-drag"
:disabled="!stateDragAppSort.status"
@end="(event) => handleEndDrag(event, itemGroup)"
:disabled="!itemGroup.sortStatus"
>
<div v-for="item, index in itemGroup.items" :key="index" :title="item.description" @contextmenu="(e) => handleContextMenu(e, item)">
<div v-for="item, index in itemGroup.items" :key="index" :title="item.description" @contextmenu="(e) => handleContextMenu(e, itemGroupIndex, item)">
<AppIcon
:class="stateDragAppSort.status ? 'cursor-move' : 'cursor-pointer'"
:class="itemGroup.sortStatus ? 'cursor-move' : 'cursor-pointer'"
:item-info="item"
:icon-text-color="panelState.panelConfig.iconTextColor"
:icon-text-info-hide-description="panelState.panelConfig.iconTextInfoHideDescription || false"
:icon-text-icon-hide-title="panelState.panelConfig.iconTextIconHideTitle || false"
:style="0"
@click="handleItemClick(item)"
@click="handleItemClick(itemGroupIndex, item)"
/>
</div>
<div v-if="itemGroup.items.length === 0" class="not-drag">
<AppIcon
:class="stateDragAppSort.status ? 'cursor-move' : 'cursor-pointer'"
:class="itemGroup.sortStatus ? 'cursor-move' : 'cursor-pointer'"
:item-info="{ icon: { itemType: 3, text: 'subway:add' }, title: '添加图标', url: '', openMethod: 0 }"
:icon-text-color="panelState.panelConfig.iconTextColor"
:icon-text-info-hide-description="panelState.panelConfig.iconTextInfoHideDescription || false"
:icon-text-icon-hide-title="panelState.panelConfig.iconTextIconHideTitle || false"
:style="0"
@click="handleAddAppClick"
@click="handleAddItem(itemGroup.id)"
/>
</div>
</VueDraggable>
@@ -360,32 +427,32 @@ onMounted(() => {
<div v-if="itemGroup.items">
<VueDraggable
v-model="itemGroup.items" item-key="id" :animation="300"
class="mx-auto mt-4 grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 lg:grid-cols-10 xl:12 gap-5"
class="icon-small-box"
filter=".not-drag"
:disabled="!stateDragAppSort.status"
:disabled="!itemGroup.sortStatus"
>
<div v-for="item, index in itemGroup.items" :key="index" :title="item.description" @contextmenu="(e) => handleContextMenu(e, item)">
<div v-for="item, index in itemGroup.items" :key="index" :title="item.description" @contextmenu="(e) => handleContextMenu(e, itemGroupIndex, item)">
<AppIcon
:class="stateDragAppSort.status ? 'cursor-move' : 'cursor-pointer'"
:class="itemGroup.sortStatus ? 'cursor-move' : 'cursor-pointer'"
:item-info="item"
:icon-text-color="panelState.panelConfig.iconTextColor"
:icon-text-info-hide-description="!panelState.panelConfig.iconTextInfoHideDescription"
:icon-text-icon-hide-title="panelState.panelConfig.iconTextIconHideTitle || false"
:style="1"
@click="handleItemClick(item)"
@click="handleItemClick(itemGroupIndex, item)"
/>
</div>
<div v-if="itemGroup.items.length === 0" class="not-drag">
<AppIcon
:class="stateDragAppSort.status ? 'cursor-move' : 'cursor-pointer'"
class="cursor-pointer"
:item-info="{ icon: { itemType: 3, text: 'subway:add' }, title: '添加图标', url: '', openMethod: 0 }"
:icon-text-color="panelState.panelConfig.iconTextColor"
:icon-text-info-hide-description="!panelState.panelConfig.iconTextInfoHideDescription"
:icon-text-icon-hide-title="panelState.panelConfig.iconTextIconHideTitle || false"
:style="1"
@click="handleAddAppClick"
@click="handleAddItem(itemGroup.id)"
/>
</div>
</vuedraggable>
@@ -393,7 +460,7 @@ onMounted(() => {
</div>
<!-- 编辑栏 -->
<div v-if="stateDragAppSort.status" class="flex mt-[10px]">
<div v-if="itemGroup.sortStatus" class="flex mt-[10px]">
<div>
<NButton color="#2a2a2a6b" @click="handleSaveSort(itemGroup)">
<template #icon>
@@ -413,29 +480,18 @@ onMounted(() => {
<!-- 右键菜单 -->
<NDropdown
placement="bottom-start" trigger="manual" :x="dropdownMenuX" :y="dropdownMenuY"
:options="getDropdownMenuOptions()" :show="dropdownShow" :on-clickoutside="onClickoutside" @select="handleSelect"
:options="getDropdownMenuOptions()" :show="dropdownShow" :on-clickoutside="onClickoutside" @select="handleRightMenuSelect"
/>
<!-- 悬浮按钮 -->
<div class="fixed-element shadow-[0_0_10px_2px_rgba(0,0,0,0.2)]">
<NButton v-if="stateDragAppSort.status" color="#2a2a2a6b" @click="stateDragAppSort.status = !stateDragAppSort.status">
<template #icon>
<SvgIcon class="text-white font-xl" icon="ri:drag-drop-line" />
</template>
</NButton>
<NButtonGroup v-if="!stateDragAppSort.status" vertical>
<NButton color="#2a2a2a6b" @click="handleAddAppClick">
<template #icon>
<SvgIcon class="text-white font-xl" icon="typcn:plus" />
</template>
</NButton>
<NButtonGroup vertical>
<NButton
v-if="panelState.networkMode === PanelStateNetworkModeEnum.lan" color="#2a2a2a6b"
title="当前:局域网模式,点击切换成互联网模式" @click="handleChangeNetwork(PanelStateNetworkModeEnum.wan)"
>
<template #icon>
<SvgIcon class="text-white font-xl" icon="material-symbols:lan-outline" />
<SvgIcon class="text-white font-xl" icon="material-symbols:lan-outline-rounded" />
</template>
</NButton>
@@ -448,15 +504,15 @@ onMounted(() => {
</template>
</NButton>
<NButton color="#2a2a2a6b" title="排序模式" @click="stateDragAppSort.status = !stateDragAppSort.status">
<NButton v-if="authStore.visitMode === VisitMode.VISIT_MODE_LOGIN" color="#2a2a2a6b" @click="settingModalShow = !settingModalShow">
<template #icon>
<SvgIcon class="text-white font-xl" icon="ri:drag-drop-line" />
<SvgIcon class="text-white font-xl" icon="majesticons-applications" />
</template>
</NButton>
<NButton color="#2a2a2a6b" @click="settingModalShow = !settingModalShow">
<NButton v-if="authStore.visitMode === VisitMode.VISIT_MODE_PUBLIC" color="#2a2a2a6b" title="登录" @click="router.push('/login')">
<template #icon>
<SvgIcon class="text-white font-xl" icon="ep:setting" />
<SvgIcon class="text-white font-xl" icon="material-symbols:account-circle" />
</template>
</NButton>
</NButtonGroup>
@@ -476,10 +532,11 @@ onMounted(() => {
</div>
</NBackTop>
<Setting v-model:visible="settingModalShow" />
<AppStarter v-model:visible="settingModalShow" />
<!-- <Setting v-model:visible="settingModalShow" /> -->
</div>
<EditItem v-model:visible="editItemInfoShow" :item-info="editItemInfoData" @done="handleEditSuccess" />
<EditItem v-model:visible="editItemInfoShow" :item-info="editItemInfoData" :item-group-id="currentAddItenIconGroupId" @done="handleEditSuccess" />
<!-- 弹窗 -->
<NModal
@@ -554,4 +611,26 @@ html {
bottom: 50px;
/* 距离屏幕左侧的距离 */
}
.icon-info-box {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 18px;
}
.icon-small-box {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(75px, 1fr));
gap: 18px;
}
@media (max-width: 500px) {
.icon-info-box{
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
}
</style>
+21 -14
View File
@@ -2,14 +2,16 @@
import { NButton, NCard, NForm, NFormItem, NGradientText, NInput, useMessage } from 'naive-ui'
import { ref } from 'vue'
import { login } from '@/api'
import { useAuthStore, useUserStore } from '@/store'
import { useAuthStore } from '@/store'
import { SvgIcon } from '@/components/common'
import { router } from '@/router'
import { Captcha, SvgIcon } from '@/components/common'
import { t } from '@/locales'
const userStore = useUserStore()
// const userStore = useUserStore()
const authStore = useAuthStore()
const ms = useMessage()
const isShowCaptcha = ref<boolean>(false)
const loading = ref(false)
// const isShowCaptcha = ref<boolean>(false)
// const isShowRegister = ref<boolean>(false)
const captchaRef = ref()
@@ -20,16 +22,21 @@ const form = ref<Login.LoginReqest>({
})
const loginPost = async () => {
loading.value = true
const res = await login<Login.LoginResponse>(form.value)
userStore.updateUserInfo(res.data)
if (res.code === 0) {
authStore.setToken(res.data.token)
ms.success(`Hi ${res.data.name},欢迎回来!`)
router.push({ path: '/' })
authStore.setUserInfo(res.data)
setTimeout(() => {
ms.success(`Hi ${res.data.name},${t('login.welcomeMessage')}`)
loading.value = false
router.push({ path: '/' })
}, 500)
}
else {
loading.value = false
captchaRef.value.refresh()
}
}
@@ -50,7 +57,7 @@ function handleSubmit() {
</div>
<NForm :model="form" label-width="100px" @keydown.enter="handleSubmit">
<NFormItem>
<NInput v-model:value="form.username" placeholder="请输入邮箱地址作为账号">
<NInput v-model:value="form.username" :placeholder="$t('login.usernamePlaceholder')">
<template #prefix>
<SvgIcon icon="ph:user-bold" />
</template>
@@ -58,22 +65,22 @@ function handleSubmit() {
</NFormItem>
<NFormItem>
<NInput v-model:value="form.password" type="password" placeholder="请输入密码">
<NInput v-model:value="form.password" type="password" :placeholder="$t('login.passwordPlaceholder')">
<template #prefix>
<SvgIcon icon="mdi:password-outline" />
</template>
</NInput>
</NFormItem>
<NFormItem v-if="isShowCaptcha">
<!-- <NFormItem v-if="isShowCaptcha">
<div class="w-[120px] h-[34px] mr-[20px] rounded border flex cursor-pointer">
<Captcha ref="captchaRef" src="/api/captcha/getImage" />
</div>
<NInput v-model:value="form.vcode" type="text" placeholder="请输入图像验证码" />
</NFormItem>
</NFormItem> -->
<NFormItem style="margin-top: 10px">
<NButton type="primary" block @click="handleSubmit">
登录
<NButton type="primary" block :loading="loading" @click="handleSubmit">
{{ $t('login.loginButton') }}
</NButton>
</NFormItem>