mirror of
https://github.com/m-xlsea/ruoyi-plus-soybean.git
synced 2025-09-23 23:39:47 +08:00
Compare commits
19 Commits
2504498eb5
...
9c51f75404
Author | SHA1 | Date | |
---|---|---|---|
9c51f75404 | |||
75cc0e4592 | |||
f9afd27f4b | |||
6dcc8c349a | |||
e230b0da81 | |||
12b25e0d58 | |||
e33f944a74 | |||
3d72f954ed | |||
1213531bef | |||
805c338141 | |||
3c0a52825d | |||
100e0ea55d | |||
257f1183fc | |||
7f2f3bd088 | |||
5ef1c5de98 | |||
90a14e338a | |||
4e625111ce | |||
8524ae7666 | |||
d6ae85d218 |
39
CHANGELOG.md
39
CHANGELOG.md
@ -1,5 +1,44 @@
|
||||
# 更新日志
|
||||
|
||||
## [v1.1.3](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v1.1.2...v1.1.3) (2025-08-16)
|
||||
|
||||
### 🐞 Bug 修复
|
||||
|
||||
- **hooks**:
|
||||
- 非安全环境下不使用流式下载 - by @m-xlsea [<samp>(f8983)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f8983557)
|
||||
- 修复oss下载时未转码问题 - by **AN** [<samp>(2d31d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/2d31d7dc)
|
||||
- **project**:
|
||||
- 关闭多租户功能后仍然遍历租户列表导致控制台报错的问题 - by **wang_rui** [<samp>(b96c4)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/b96c46ba)
|
||||
- 关闭多租户功能后仍然遍历租户列表导致控制台报错的问题 Merge pull request !25 from littleghost2016/dev - by **不寻俗** [<samp>(90276)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/9027632b)
|
||||
- **projects**:
|
||||
- 修复一级菜单隐藏失效问题 - by **AN** [<samp>(8fcc7)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/8fcc70d7)
|
||||
- 修复日期搜索条件清除问题 - by **AN** [<samp>(52318)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/52318c10)
|
||||
- 修复登录过期事件监听未被重置 - by @m-xlsea [<samp>(71037)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/71037439)
|
||||
- 修复用户新增时角色下拉包含超级管理员问题 - by **AN** [<samp>(a15b6)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/a15b683b)
|
||||
- 修复用户导入功能无法更新问题 - by **AN** [<samp>(4e983)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4e9839bd)
|
||||
- Fix the icon size in the image preview toolbar - by @m-xlsea [<samp>(4539f)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4539fe01)
|
||||
- 修复新增用户未查询角色列表问题 - by **AN** [<samp>(d6ae8)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/d6ae85d2)
|
||||
- **readme**:
|
||||
- update GitHub stars and forks links for gitee - by @soybeanjs [<samp>(923eb)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/923eb98a)
|
||||
|
||||
### 💅 重构
|
||||
|
||||
- **menu**:
|
||||
- 菜单管理中隐藏的菜单显示灰色 - by **NicholasLD** [<samp>(adca2)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/adca2e26)
|
||||
- 菜单管理中隐藏的菜单显示灰色 Merge pull request !24 from NicholasLD/N/A - by **不寻俗** [<samp>(4eb77)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4eb77eac)
|
||||
- **projects**:
|
||||
- 菜单列表新增禁用菜单样式 - by @m-xlsea [<samp>(e5383)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e538355f)
|
||||
|
||||
### 🏡 杂项
|
||||
|
||||
- **other**: update the ESLint validation configuration to support more file types. - by **Azir-11** [<samp>(8d7f9)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/8d7f91dc)
|
||||
- **readme**: remove DartNode sponsorship badge from README files - by @soybeanjs [<samp>(33ade)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/33ade539)
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/soybeanjs) [](https://github.com/m-xlsea) [](https://gitee.com/elio-an) [](https://github.com/Azir-11) [](https://github.com/NicholasLD)
|
||||
[wang_rui](mailto:wrr1996@163.com)
|
||||
|
||||
## [v1.1.2](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v1.1.1...v1.1.2) (2025-07-24)
|
||||
|
||||
### 🐞 Bug 修复
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
# 📢 重要通知
|
||||
|
||||
1.1.2 版本已经正式发布,工作流版本迎来首个版本(请切换 [flow](https://gitee.com/xlsea/ruoyi-plus-soybean/tree/flow/) 分支查看),但仍然建议:
|
||||
1.1.3 版本已经正式发布(工作流版本请切换 [flow](https://gitee.com/xlsea/ruoyi-plus-soybean/tree/flow/) 分支查看),但仍然建议:
|
||||
- 在生产环境使用前进行充分测试
|
||||
- 关注项目更新,及时获取最新版本
|
||||
- 积极反馈问题,帮助我们快速迭代
|
||||
@ -40,6 +40,11 @@
|
||||
|
||||
<p style="font-weight: bold; font-size: 24px;">后端需要替换代码生成模板与菜单 SQL,详细请看 <a href="#代码生成与菜单更新">代码生成与菜单更新</a></p>
|
||||
|
||||
# 💎 友情链接
|
||||
|
||||
- [Snail Job Pro](https://pro.snailjob.opensnail.com/home) - 灵活,可靠和快速的分布式任务重试和分布式任务调度平台
|
||||
- [AiZuDa - 爱组搭(飞龙工作流企业版)](https://naiveui.aizuda.com) - 像搭积木一样进行低代码甚至零代码快速构建应用
|
||||
|
||||
## 📋 项目概述
|
||||
|
||||
RuoYi-Plus-Soybean 是一个现代化的企业级多租户管理系统,它结合了 RuoYi-Vue-Plus 的强大后端功能和 Soybean Admin 的现代化前端特性,为开发者提供了完整的企业管理解决方案。
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { HttpProxy, ProxyOptions } from 'vite';
|
||||
import type { ProxyOptions } from 'vite';
|
||||
import { bgRed, bgYellow, green, lightBlue } from 'kolorist';
|
||||
import { consola } from 'consola';
|
||||
import { createServiceConfig } from '../../src/utils/service';
|
||||
@ -34,7 +34,7 @@ function createProxyItem(item: App.Service.ServiceConfigItem, enableLog: boolean
|
||||
target: item.baseURL,
|
||||
changeOrigin: true,
|
||||
ws: item.ws,
|
||||
configure: (_proxy: HttpProxy.Server, options: ProxyOptions) => {
|
||||
configure: (_proxy, options) => {
|
||||
_proxy.on('proxyReq', (_proxyReq, req, _res) => {
|
||||
if (!enableLog) return;
|
||||
|
||||
|
57
package.json
57
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ruoyi-vue-plus",
|
||||
"type": "module",
|
||||
"version": "1.1.2",
|
||||
"version": "1.1.3",
|
||||
"description": "结合了 RuoYi-Vue-Plus 的强大后端功能和 Soybean Admin 的现代化前端特性,为开发者提供了完整的企业管理解决方案。",
|
||||
"author": {
|
||||
"name": "xlsea",
|
||||
@ -66,11 +66,11 @@
|
||||
"@sa/tinymce": "workspace:*",
|
||||
"@sa/utils": "workspace:*",
|
||||
"@types/streamsaver": "^2.0.5",
|
||||
"@vueuse/core": "13.5.0",
|
||||
"@vueuse/core": "13.8.0",
|
||||
"clipboard": "2.0.11",
|
||||
"dayjs": "1.11.13",
|
||||
"dayjs": "1.11.14",
|
||||
"defu": "6.1.4",
|
||||
"echarts": "5.6.0",
|
||||
"echarts": "6.0.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"json5": "2.2.3",
|
||||
@ -80,46 +80,47 @@
|
||||
"pinia": "3.0.3",
|
||||
"streamsaver": "^2.0.6",
|
||||
"tailwind-merge": "3.3.1",
|
||||
"vue": "3.5.17",
|
||||
"vue": "3.5.20",
|
||||
"vue-advanced-cropper": "^2.8.9",
|
||||
"vue-draggable-plus": "0.6.0",
|
||||
"vue-i18n": "11.1.10",
|
||||
"vue-router": "4.5.1"
|
||||
"vue-i18n": "11.1.11",
|
||||
"vue-router": "4.5.1",
|
||||
"xlsx": "0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@elegant-router/vue": "0.3.8",
|
||||
"@iconify/json": "2.2.359",
|
||||
"@iconify/json": "2.2.378",
|
||||
"@sa/scripts": "workspace:*",
|
||||
"@sa/uno-preset": "workspace:*",
|
||||
"@soybeanjs/eslint-config": "1.7.1",
|
||||
"@types/node": "24.0.15",
|
||||
"@types/node": "24.3.0",
|
||||
"@types/nprogress": "0.2.3",
|
||||
"@unocss/eslint-config": "66.3.3",
|
||||
"@unocss/preset-icons": "66.3.3",
|
||||
"@unocss/preset-uno": "66.3.3",
|
||||
"@unocss/transformer-directives": "66.3.3",
|
||||
"@unocss/transformer-variant-group": "66.3.3",
|
||||
"@unocss/vite": "66.3.3",
|
||||
"@vitejs/plugin-vue": "6.0.0",
|
||||
"@vitejs/plugin-vue-jsx": "5.0.1",
|
||||
"@unocss/eslint-config": "66.4.2",
|
||||
"@unocss/preset-icons": "66.4.2",
|
||||
"@unocss/preset-uno": "66.4.2",
|
||||
"@unocss/transformer-directives": "66.4.2",
|
||||
"@unocss/transformer-variant-group": "66.4.2",
|
||||
"@unocss/vite": "66.4.2",
|
||||
"@vitejs/plugin-vue": "6.0.1",
|
||||
"@vitejs/plugin-vue-jsx": "5.1.0",
|
||||
"consola": "3.4.2",
|
||||
"eslint": "9.31.0",
|
||||
"eslint-plugin-vue": "10.3.0",
|
||||
"eslint": "9.34.0",
|
||||
"eslint-plugin-vue": "10.4.0",
|
||||
"kolorist": "1.8.0",
|
||||
"sass": "1.89.2",
|
||||
"simple-git-hooks": "2.13.0",
|
||||
"tsx": "4.20.3",
|
||||
"typescript": "5.8.3",
|
||||
"unplugin-icons": "22.1.0",
|
||||
"unplugin-vue-components": "28.8.0",
|
||||
"vite": "7.0.5",
|
||||
"sass": "1.91.0",
|
||||
"simple-git-hooks": "2.13.1",
|
||||
"tsx": "4.20.5",
|
||||
"typescript": "5.9.2",
|
||||
"unplugin-icons": "22.2.0",
|
||||
"unplugin-vue-components": "29.0.0",
|
||||
"vite": "7.1.3",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vite-plugin-progress": "0.0.7",
|
||||
"vite-plugin-static-copy": "^3.1.0",
|
||||
"vite-plugin-svg-icons": "2.0.1",
|
||||
"vite-plugin-vue-devtools": "7.7.7",
|
||||
"vite-plugin-vue-devtools": "8.0.1",
|
||||
"vue-eslint-parser": "10.2.0",
|
||||
"vue-tsc": "3.0.3"
|
||||
"vue-tsc": "3.0.6"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"commit-msg": "pnpm sa git-commit-verify",
|
||||
|
@ -11,7 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*",
|
||||
"axios": "1.10.0",
|
||||
"axios": "1.11.0",
|
||||
"axios-retry": "4.5.0",
|
||||
"qs": "6.14.0"
|
||||
},
|
||||
|
@ -14,14 +14,15 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@soybeanjs/changelog": "0.3.24",
|
||||
"bumpp": "10.2.0",
|
||||
"c12": "3.1.0",
|
||||
"bumpp": "10.2.3",
|
||||
"c12": "3.2.0",
|
||||
"cac": "6.7.14",
|
||||
"consola": "3.4.2",
|
||||
"enquirer": "2.4.1",
|
||||
"execa": "9.6.0",
|
||||
"kolorist": "1.8.0",
|
||||
"npm-check-updates": "18.0.1",
|
||||
"npm-check-updates": "18.0.3",
|
||||
"picomatch": "4.0.3",
|
||||
"rimraf": "6.0.1"
|
||||
}
|
||||
}
|
||||
|
1578
pnpm-lock.yaml
generated
1578
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, useAttrs } from 'vue';
|
||||
import type { TagProps } from 'naive-ui';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useDict } from '@/hooks/business/dict';
|
||||
import { isNotNull } from '@/utils/common';
|
||||
import { $t } from '@/locales';
|
||||
@ -28,7 +29,7 @@ const { transformDictData } = useDict(props.dictCode, props.immediate);
|
||||
|
||||
const dictTagData = computed<Api.System.DictData[]>(() => {
|
||||
if (props.dictData) {
|
||||
const dictData = props.dictData;
|
||||
const dictData = jsonClone(props.dictData);
|
||||
if (dictData.dictLabel?.startsWith(`dict.${dictData.dictType}.`)) {
|
||||
dictData.dictLabel = $t(dictData.dictLabel as App.I18n.I18nKey);
|
||||
}
|
||||
|
@ -1,61 +1,524 @@
|
||||
<!-- Copyright By https://github.com/Daymychen/art-design-pro/blob/main/src/components/core/views/login/LoginLeftView.vue -->
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { getPaletteColorByNumber } from '@sa/color';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
|
||||
defineOptions({ name: 'WaveBg' });
|
||||
|
||||
interface Props {
|
||||
/** Theme color */
|
||||
themeColor: string;
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
function toggleThemeScheme() {
|
||||
if (themeStore.darkMode) {
|
||||
themeStore.setThemeScheme('light');
|
||||
return;
|
||||
}
|
||||
themeStore.setThemeScheme('dark');
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const lightColor = computed(() => getPaletteColorByNumber(props.themeColor, 200));
|
||||
const darkColor = computed(() => getPaletteColorByNumber(props.themeColor, 500));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="absolute-lt z-1 size-full overflow-hidden">
|
||||
<div class="absolute -right-300px -top-900px lt-sm:(-right-100px -top-1170px)">
|
||||
<svg height="1337" width="1337">
|
||||
<defs>
|
||||
<path
|
||||
id="path-1"
|
||||
opacity="1"
|
||||
fill-rule="evenodd"
|
||||
d="M1337,668.5 C1337,1037.455193874239 1037.455193874239,1337 668.5,1337 C523.6725684305388,1337 337,1236 370.50000000000006,1094 C434.03835568300906,824.6732385973953 6.906089672974592e-14,892.6277623047779 0,668.5000000000001 C0,299.5448061257611 299.5448061257609,1.1368683772161603e-13 668.4999999999999,0 C1037.455193874239,0 1337,299.544806125761 1337,668.5Z"
|
||||
/>
|
||||
<linearGradient id="linearGradient-2" x1="0.79" y1="0.62" x2="0.21" y2="0.86">
|
||||
<stop offset="0" :stop-color="lightColor" stop-opacity="1" />
|
||||
<stop offset="1" :stop-color="darkColor" stop-opacity="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g opacity="1">
|
||||
<use xlink:href="#path-1" fill="url(#linearGradient-2)" fill-opacity="1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="absolute -bottom-400px -left-200px lt-sm:(-bottom-760px -left-100px)">
|
||||
<svg height="896" width="967.8852157128662">
|
||||
<defs>
|
||||
<path
|
||||
id="path-2"
|
||||
opacity="1"
|
||||
fill-rule="evenodd"
|
||||
d="M896,448 C1142.6325445712241,465.5747656464056 695.2579309733121,896 448,896 C200.74206902668806,896 5.684341886080802e-14,695.2579309733121 0,448.0000000000001 C0,200.74206902668806 200.74206902668791,5.684341886080802e-14 447.99999999999994,0 C695.2579309733121,0 475,418 896,448Z"
|
||||
/>
|
||||
<linearGradient id="linearGradient-3" x1="0.5" y1="0" x2="0.5" y2="1">
|
||||
<stop offset="0" :stop-color="darkColor" stop-opacity="1" />
|
||||
<stop offset="1" :stop-color="lightColor" stop-opacity="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g opacity="1">
|
||||
<use xlink:href="#path-2" fill="url(#linearGradient-3)" fill-opacity="1" />
|
||||
</g>
|
||||
</svg>
|
||||
<div class="wave-bg">
|
||||
<!-- 几何装饰元素 -->
|
||||
<div class="geometric-decorations">
|
||||
<!-- 基础几何形状 -->
|
||||
<div class="geo-element circle-outline animate-fade-in-up animate-delay-0s"></div>
|
||||
<div class="geo-element square-rotated animate-fade-in-left animate-delay-0s"></div>
|
||||
<div class="geo-element circle-small animate-fade-in-up animate-delay-0.3s"></div>
|
||||
|
||||
<div class="geo-element square-bottom-right animate-fade-in-right animate-delay-0s"></div>
|
||||
|
||||
<!-- 背景泡泡 -->
|
||||
<div class="geo-element bg-bubble animate-scale-in animate-delay-0.5s"></div>
|
||||
|
||||
<!-- 太阳/月亮 -->
|
||||
<div
|
||||
class="geo-element circle-top-right animate-fade-in-down animate-delay-0.5s"
|
||||
@click="toggleThemeScheme"
|
||||
></div>
|
||||
|
||||
<!-- 装饰点 -->
|
||||
<div class="geo-element dot dot-top-left animate-bounce-in animate-delay-0s"></div>
|
||||
<div class="geo-element dot dot-top-right animate-bounce-in animate-delay-0s"></div>
|
||||
<div class="geo-element dot dot-center-right animate-bounce-in animate-delay-0s"></div>
|
||||
|
||||
<!-- 叠加方块组 -->
|
||||
<div class="squares-group">
|
||||
<i class="geo-element square square-blue animate-fade-in-left-rotated-blue animate-delay-0.2s"></i>
|
||||
<i class="geo-element square square-pink animate-fade-in-left-rotated-pink animate-delay-0.4s"></i>
|
||||
<i class="geo-element square square-purple animate-fade-in-left-no-rotation animate-delay-0.6s"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
// 颜色变量定义
|
||||
$primary-light-7: rgb(var(--primary-50-color));
|
||||
$primary-light-8: rgb(var(--primary-100-color));
|
||||
$primary-light-9: rgb(var(--primary-200-color));
|
||||
$primary-base: rgb(var(--primary-color));
|
||||
$main-bg: rgb(var(--primary-50-color));
|
||||
|
||||
// 混合颜色函数
|
||||
$bg-mix-light-9: color-mix(in srgb, $primary-light-9 100%, $main-bg);
|
||||
$bg-mix-light-8: color-mix(in srgb, $primary-light-8 80%, $main-bg);
|
||||
$bg-mix-light-7: color-mix(in srgb, $primary-light-7 80%, $main-bg);
|
||||
|
||||
.wave-bg {
|
||||
.geometric-decorations {
|
||||
.geo-element {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
animation-fill-mode: forwards;
|
||||
animation-duration: 0.8s;
|
||||
animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
}
|
||||
|
||||
// 动画 mixin
|
||||
@mixin fadeAnimation($direction: '', $rotation: 0deg) {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
@if $direction == 'up' {
|
||||
transform: translateY(30px) rotate($rotation);
|
||||
} @else if $direction == 'down' {
|
||||
transform: translateY(-30px) rotate($rotation);
|
||||
} @else if $direction == 'left' {
|
||||
transform: translateX(-30px) rotate($rotation);
|
||||
} @else if $direction == 'right' {
|
||||
transform: translateX(30px) rotate($rotation);
|
||||
}
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
|
||||
@if $direction == 'up' or $direction == 'down' {
|
||||
transform: translateY(0) rotate($rotation);
|
||||
} @else {
|
||||
transform: translateX(0) rotate($rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 动画定义
|
||||
@keyframes fadeInUp {
|
||||
@include fadeAnimation('up');
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
@include fadeAnimation('down');
|
||||
}
|
||||
|
||||
@keyframes fadeInLeft {
|
||||
@include fadeAnimation('left');
|
||||
}
|
||||
|
||||
@keyframes fadeInLeftRotated {
|
||||
@include fadeAnimation('left', -25deg);
|
||||
}
|
||||
|
||||
@keyframes fadeInRight {
|
||||
@include fadeAnimation('right');
|
||||
}
|
||||
|
||||
@keyframes fadeInRightRotated {
|
||||
@include fadeAnimation('right', 45deg);
|
||||
}
|
||||
|
||||
@keyframes fadeInLeftRotatedBlue {
|
||||
@include fadeAnimation('left', -10deg);
|
||||
}
|
||||
|
||||
@keyframes fadeInLeftRotatedPink {
|
||||
@include fadeAnimation('left', 10deg);
|
||||
}
|
||||
|
||||
@keyframes fadeInLeftNoRotation {
|
||||
@include fadeAnimation('left');
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounceIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.3);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes lineGrow {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInLeft {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 动画类
|
||||
.animate-fade-in-up {
|
||||
animation-name: fadeInUp;
|
||||
}
|
||||
|
||||
.animate-fade-in-down {
|
||||
animation-name: fadeInDown;
|
||||
}
|
||||
|
||||
.animate-fade-in-left {
|
||||
animation-name: fadeInLeft;
|
||||
}
|
||||
|
||||
.animate-fade-in-right {
|
||||
animation-name: fadeInRight;
|
||||
}
|
||||
|
||||
.animate-scale-in {
|
||||
animation-name: scaleIn;
|
||||
animation-duration: 1.2s;
|
||||
}
|
||||
|
||||
.animate-bounce-in {
|
||||
animation-name: bounceIn;
|
||||
animation-duration: 0.6s;
|
||||
}
|
||||
|
||||
.animate-fade-in-left-rotated-blue {
|
||||
animation-name: fadeInLeftRotatedBlue;
|
||||
}
|
||||
|
||||
.animate-fade-in-left-rotated-pink {
|
||||
animation-name: fadeInLeftRotatedPink;
|
||||
}
|
||||
|
||||
.animate-fade-in-left-no-rotation {
|
||||
animation-name: fadeInLeftNoRotation;
|
||||
}
|
||||
|
||||
// 基础几何形状
|
||||
.circle-outline {
|
||||
top: 10%;
|
||||
left: 25%;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border: 2px solid $primary-light-8;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.square-rotated {
|
||||
top: 50%;
|
||||
left: 16%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-color: $bg-mix-light-8;
|
||||
|
||||
&.animate-fade-in-left {
|
||||
animation-name: fadeInLeftRotated;
|
||||
}
|
||||
}
|
||||
|
||||
.circle-small {
|
||||
bottom: 26%;
|
||||
left: 30%;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: $primary-light-8;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
// 太阳/月亮效果
|
||||
.circle-top-right {
|
||||
top: 3%;
|
||||
right: 3%;
|
||||
z-index: 100;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
cursor: pointer;
|
||||
background: $bg-mix-light-7;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: '';
|
||||
background: linear-gradient(to right, #fcbb04, #fffc00);
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
transition: all 0.5s;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 36px #fffc00;
|
||||
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.square-bottom-right {
|
||||
right: 10%;
|
||||
bottom: 10%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-color: $primary-light-8;
|
||||
|
||||
&.animate-fade-in-right {
|
||||
animation-name: fadeInRightRotated;
|
||||
}
|
||||
}
|
||||
|
||||
// 背景泡泡
|
||||
.bg-bubble {
|
||||
top: -120px;
|
||||
right: -120px;
|
||||
width: 360px;
|
||||
height: 360px;
|
||||
background-color: $bg-mix-light-8;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
// 装饰点
|
||||
.dot {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background-color: $primary-light-7;
|
||||
border-radius: 50%;
|
||||
|
||||
&.dot-top-left {
|
||||
top: 140px;
|
||||
left: 100px;
|
||||
}
|
||||
|
||||
&.dot-top-right {
|
||||
top: 140px;
|
||||
right: 120px;
|
||||
}
|
||||
|
||||
&.dot-center-right {
|
||||
top: 46%;
|
||||
right: 22%;
|
||||
background-color: $primary-light-8;
|
||||
}
|
||||
}
|
||||
|
||||
// 叠加方块组
|
||||
.squares-group {
|
||||
position: absolute;
|
||||
bottom: 18px;
|
||||
left: 20px;
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
pointer-events: none;
|
||||
|
||||
.square {
|
||||
position: absolute;
|
||||
display: block;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 24px rgb(64 87 167 / 12%);
|
||||
|
||||
&.square-blue {
|
||||
top: 12px;
|
||||
left: 30px;
|
||||
z-index: 2;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-color: rgb(from $primary-base r g b / 30%);
|
||||
}
|
||||
|
||||
&.square-pink {
|
||||
top: 30px;
|
||||
left: 48px;
|
||||
z-index: 1;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
background-color: rgb(from $primary-base r g b / 15%);
|
||||
}
|
||||
|
||||
&.square-purple {
|
||||
top: 66px;
|
||||
left: 86px;
|
||||
z-index: 3;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-color: rgb(from $primary-base r g b / 45%);
|
||||
}
|
||||
}
|
||||
|
||||
// 装饰线条
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 86px;
|
||||
left: 72px;
|
||||
width: 80px;
|
||||
height: 1px;
|
||||
content: '';
|
||||
background: linear-gradient(90deg, var(--el-color-primary-light-6), transparent);
|
||||
opacity: 0;
|
||||
transform: rotate(50deg);
|
||||
animation: lineGrow 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1600px) {
|
||||
width: 60vw;
|
||||
|
||||
.text-wrap {
|
||||
bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1280px) {
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
// 隐藏背景和其他内容,只保留 logo
|
||||
background: transparent;
|
||||
|
||||
.left-img,
|
||||
.text-wrap,
|
||||
.geometric-decorations {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
left: 25px;
|
||||
z-index: 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 暗色主题
|
||||
.dark .wave-bg {
|
||||
background-color: color-mix(in srgb, $primary-light-9 60%, #070707);
|
||||
|
||||
@media only screen and (max-width: 1280px) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.geometric-decorations {
|
||||
// 月亮效果
|
||||
.circle-top-right {
|
||||
background-color: $bg-mix-light-8;
|
||||
box-shadow: 0 0 25px #333 inset;
|
||||
transition: all 0.3s ease-in-out 0.1s;
|
||||
rotate: -48deg;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 15px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
content: '';
|
||||
background-color: $bg-mix-light-9;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
box-shadow: 0 40px 25px #ddd inset;
|
||||
|
||||
&::before {
|
||||
left: 18px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bg-bubble {
|
||||
background-color: $bg-mix-light-9;
|
||||
}
|
||||
|
||||
// 其他元素颜色调整
|
||||
.square-rotated {
|
||||
background-color: $bg-mix-light-9;
|
||||
}
|
||||
|
||||
.circle-small,
|
||||
.dot {
|
||||
background-color: $primary-light-8;
|
||||
}
|
||||
|
||||
.square-bottom-right {
|
||||
background-color: $primary-light-9;
|
||||
}
|
||||
|
||||
.dot.dot-top-right {
|
||||
background-color: $primary-light-8;
|
||||
}
|
||||
}
|
||||
|
||||
// 方块组暗色调整
|
||||
.squares-group {
|
||||
.square {
|
||||
box-shadow: none;
|
||||
|
||||
&.square-blue {
|
||||
background-color: rgb(from $primary-base r g b / 18%);
|
||||
}
|
||||
|
||||
&.square-pink {
|
||||
background-color: rgb(from $primary-base r g b / 10%);
|
||||
}
|
||||
|
||||
&.square-purple {
|
||||
background-color: rgb(from $primary-base r g b / 20%);
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
background: linear-gradient(90deg, $primary-light-8, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -58,8 +58,8 @@ export const themePageAnimationModeRecord: Record<UnionKey.ThemePageAnimateMode,
|
||||
export const themePageAnimationModeOptions = transformRecordToOption(themePageAnimationModeRecord);
|
||||
|
||||
export const resetCacheStrategyRecord: Record<UnionKey.ResetCacheStrategy, App.I18n.I18nKey> = {
|
||||
close: 'theme.layout.resetCacheStrategy.close',
|
||||
refresh: 'theme.layout.resetCacheStrategy.refresh'
|
||||
refresh: 'theme.layout.resetCacheStrategy.refresh',
|
||||
close: 'theme.layout.resetCacheStrategy.close'
|
||||
};
|
||||
|
||||
export const resetCacheStrategyOptions = transformRecordToOption(resetCacheStrategyRecord);
|
||||
|
@ -113,6 +113,10 @@ export function useDownload() {
|
||||
|
||||
const response = await fetch(fullUrl, requestOptions);
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(errorCodeRecord.default);
|
||||
}
|
||||
|
||||
await handleResponse(response);
|
||||
|
||||
const rawHeader = response.headers.get('Download-Filename');
|
||||
|
@ -6,6 +6,7 @@ import AppearanceSettings from './modules/appearance/index.vue';
|
||||
import LayoutSettings from './modules/layout/index.vue';
|
||||
import GeneralSettings from './modules/general/index.vue';
|
||||
import ConfigOperation from './modules/config-operation.vue';
|
||||
import PresetSettings from './modules/preset/index.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'ThemeDrawer'
|
||||
@ -33,6 +34,7 @@ const drawerWidth = computed(() => {
|
||||
<NTab name="appearance" :tab="$t('theme.tabs.appearance')"></NTab>
|
||||
<NTab name="layout" :tab="$t('theme.tabs.layout')"></NTab>
|
||||
<NTab name="general" :tab="$t('theme.tabs.general')"></NTab>
|
||||
<NTab name="preset" :tab="$t('theme.tabs.preset')"></NTab>
|
||||
</NTabs>
|
||||
|
||||
<div class="min-h-400px">
|
||||
@ -40,6 +42,7 @@ const drawerWidth = computed(() => {
|
||||
<AppearanceSettings v-if="activeTab === 'appearance'" />
|
||||
<LayoutSettings v-else-if="activeTab === 'layout'" />
|
||||
<GeneralSettings v-else-if="activeTab === 'general'" />
|
||||
<PresetSettings v-else-if="activeTab === 'preset'" />
|
||||
</KeepAlive>
|
||||
</div>
|
||||
|
||||
|
15
src/layouts/modules/theme-drawer/modules/preset/index.vue
Normal file
15
src/layouts/modules/theme-drawer/modules/preset/index.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import ThemePreset from './modules/theme-preset.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'PresetSettings'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-col-stretch gap-16px">
|
||||
<ThemePreset />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -0,0 +1,148 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'ThemePreset'
|
||||
});
|
||||
|
||||
type ThemePreset = Pick<
|
||||
App.Theme.ThemeSetting,
|
||||
| 'themeScheme'
|
||||
| 'grayscale'
|
||||
| 'colourWeakness'
|
||||
| 'recommendColor'
|
||||
| 'themeColor'
|
||||
| 'otherColor'
|
||||
| 'isInfoFollowPrimary'
|
||||
| 'resetCacheStrategy'
|
||||
| 'layout'
|
||||
| 'page'
|
||||
| 'header'
|
||||
| 'tab'
|
||||
| 'fixedHeaderAndTab'
|
||||
| 'sider'
|
||||
| 'footer'
|
||||
| 'watermark'
|
||||
| 'tokens'
|
||||
> & {
|
||||
name: string;
|
||||
desc: string;
|
||||
i18nkey?: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
const presetModules = import.meta.glob('@/theme/preset/*.json', { eager: true, import: 'default' });
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
// Extract preset data
|
||||
const presets = computed(() =>
|
||||
Object.entries(presetModules)
|
||||
.map(([path, presetData]) => {
|
||||
const fileName = path.split('/').pop()?.replace('.json', '') || '';
|
||||
return {
|
||||
id: fileName,
|
||||
...(presetData as ThemePreset)
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (a.name === 'default') return -1;
|
||||
if (b.name === 'default') return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
})
|
||||
);
|
||||
|
||||
const getPresetName = (preset: ThemePreset): string => {
|
||||
if (!preset.i18nkey) return preset.name;
|
||||
try {
|
||||
const key = `${preset.i18nkey}.name` as App.I18n.I18nKey;
|
||||
const translated = $t(key);
|
||||
return translated !== key ? translated : preset.name;
|
||||
} catch {
|
||||
return preset.name;
|
||||
}
|
||||
};
|
||||
|
||||
const getPresetDesc = (preset: ThemePreset): string => {
|
||||
if (!preset.i18nkey) return preset.desc;
|
||||
try {
|
||||
const key = `${preset.i18nkey}.desc` as App.I18n.I18nKey;
|
||||
const translated = $t(key);
|
||||
return translated !== key ? translated : preset.desc;
|
||||
} catch {
|
||||
return preset.desc;
|
||||
}
|
||||
};
|
||||
|
||||
const applyPreset = ({ themeScheme, grayscale, colourWeakness, layout, watermark, ...rest }: ThemePreset): void => {
|
||||
themeStore.setThemeScheme(themeScheme);
|
||||
themeStore.setGrayscale(grayscale);
|
||||
themeStore.setColourWeakness(colourWeakness);
|
||||
themeStore.setThemeLayout(layout.mode);
|
||||
themeStore.setWatermarkEnableUserName(watermark.enableUserName);
|
||||
themeStore.setWatermarkEnableTime(watermark.enableTime);
|
||||
|
||||
Object.assign(themeStore, {
|
||||
...rest,
|
||||
layout: { ...themeStore.layout, scrollMode: layout.scrollMode },
|
||||
page: { ...rest.page },
|
||||
header: { ...rest.header },
|
||||
tab: { ...rest.tab },
|
||||
sider: { ...rest.sider },
|
||||
footer: { ...rest.footer },
|
||||
watermark: { ...watermark },
|
||||
tokens: { ...rest.tokens }
|
||||
});
|
||||
|
||||
window.$message?.success($t('theme.appearance.preset.applySuccess'));
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.appearance.preset.title') }}</NDivider>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<div
|
||||
v-for="preset in presets"
|
||||
:key="preset.id"
|
||||
class="border border-primary/10 rounded-lg border-solid bg-white/5 p-3 backdrop-blur-10 transition-all duration-300 hover:(shadow-md -translate-y-0.5)"
|
||||
>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<div class="min-w-0 w-full flex flex-1 items-center justify-between gap-2">
|
||||
<h5 class="m-0 truncate text-sm text-primary font-600">
|
||||
{{ getPresetName(preset) }}
|
||||
</h5>
|
||||
<NBadge :value="`v${preset.version}`" type="info" size="small" class="flex-shrink-0 opacity-80" />
|
||||
</div>
|
||||
<NButton type="primary" size="tiny" ghost round class="ml-2 flex-shrink-0" @click="applyPreset(preset)">
|
||||
{{ $t('theme.appearance.preset.apply') }}
|
||||
</NButton>
|
||||
</div>
|
||||
|
||||
<p class="line-clamp-2 mb-3 text-xs text-gray-500 leading-4">{{ getPresetDesc(preset) }}</p>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex gap-1">
|
||||
<div
|
||||
v-for="(color, key) in { primary: preset.themeColor, ...preset.otherColor }"
|
||||
:key="key"
|
||||
class="h-3 w-3 cursor-pointer border border-white/30 rounded-full transition-transform hover:scale-110"
|
||||
:style="{ backgroundColor: color }"
|
||||
:class="{ 'ring-1 ring-primary/50': key === 'primary' }"
|
||||
:title="key"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="text-lg">
|
||||
{{ preset.themeScheme === 'dark' ? '🌙' : '☀️' }}
|
||||
</div>
|
||||
<div class="text-lg">
|
||||
{{ preset.grayscale ? '🎨' : '' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -86,7 +86,8 @@ const local: App.I18n.Schema = {
|
||||
tabs: {
|
||||
appearance: 'Appearance',
|
||||
layout: 'Layout',
|
||||
general: 'General'
|
||||
general: 'General',
|
||||
preset: 'Preset'
|
||||
},
|
||||
appearance: {
|
||||
themeSchema: {
|
||||
@ -107,7 +108,28 @@ const local: App.I18n.Schema = {
|
||||
followPrimary: 'Follow Primary'
|
||||
},
|
||||
recommendColor: 'Apply Recommended Color Algorithm',
|
||||
recommendColorDesc: 'The recommended color algorithm refers to'
|
||||
recommendColorDesc: 'The recommended color algorithm refers to',
|
||||
preset: {
|
||||
title: 'Theme Presets',
|
||||
apply: 'Apply',
|
||||
applySuccess: 'Preset applied successfully',
|
||||
default: {
|
||||
name: 'Default Preset',
|
||||
desc: 'Default theme preset with balanced settings'
|
||||
},
|
||||
dark: {
|
||||
name: 'Dark Preset',
|
||||
desc: 'Dark theme preset for night time usage'
|
||||
},
|
||||
compact: {
|
||||
name: 'Compact Preset',
|
||||
desc: 'Compact layout preset for small screens'
|
||||
},
|
||||
azir: {
|
||||
name: "Azir's Preset",
|
||||
desc: 'It is a cold and elegant preset that Azir likes'
|
||||
}
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
layoutMode: {
|
||||
@ -345,6 +367,8 @@ const local: App.I18n.Schema = {
|
||||
page: {
|
||||
login: {
|
||||
common: {
|
||||
title: 'Modern enterprise-level multi-tenant management system',
|
||||
subTitle: 'Provides developers with a complete enterprise management solution',
|
||||
loginOrRegister: 'Login / Register',
|
||||
register: 'Register',
|
||||
userNamePlaceholder: 'Please enter user name',
|
||||
|
@ -86,7 +86,8 @@ const local: App.I18n.Schema = {
|
||||
tabs: {
|
||||
appearance: '外观',
|
||||
layout: '布局',
|
||||
general: '通用'
|
||||
general: '通用',
|
||||
preset: '预设'
|
||||
},
|
||||
appearance: {
|
||||
themeSchema: {
|
||||
@ -107,7 +108,28 @@ const local: App.I18n.Schema = {
|
||||
followPrimary: '跟随主色'
|
||||
},
|
||||
recommendColor: '应用推荐算法的颜色',
|
||||
recommendColorDesc: '推荐颜色的算法参照'
|
||||
recommendColorDesc: '推荐颜色的算法参照',
|
||||
preset: {
|
||||
title: '主题预设',
|
||||
apply: '应用',
|
||||
applySuccess: '预设应用成功',
|
||||
default: {
|
||||
name: '默认预设',
|
||||
desc: 'Soybean 默认主题预设'
|
||||
},
|
||||
dark: {
|
||||
name: '暗色预设',
|
||||
desc: '适用于夜间使用的暗色主题预设'
|
||||
},
|
||||
compact: {
|
||||
name: '紧凑型',
|
||||
desc: '适用于小屏幕的紧凑布局预设'
|
||||
},
|
||||
azir: {
|
||||
name: 'Azir的预设',
|
||||
desc: '是 Azir 比较喜欢的莫兰迪色系冷淡风'
|
||||
}
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
layoutMode: {
|
||||
@ -342,6 +364,8 @@ const local: App.I18n.Schema = {
|
||||
page: {
|
||||
login: {
|
||||
common: {
|
||||
title: '现代化的企业级多租户管理系统',
|
||||
subTitle: '为开发者提供了完整的企业管理解决方案',
|
||||
loginOrRegister: '登录 / 注册',
|
||||
register: '注册',
|
||||
userNamePlaceholder: '请输入用户名',
|
||||
|
90
src/theme/preset/azir.json
Normal file
90
src/theme/preset/azir.json
Normal file
@ -0,0 +1,90 @@
|
||||
{
|
||||
"name": "Azir's Preset",
|
||||
"desc": "It is a cold and elegant preset that Azir likes",
|
||||
"i18nkey": "theme.appearance.preset.azir",
|
||||
"version": "1.0.0",
|
||||
"themeScheme": "light",
|
||||
"grayscale": false,
|
||||
"colourWeakness": false,
|
||||
"recommendColor": true,
|
||||
"themeColor": "#78a878",
|
||||
"otherColor": {
|
||||
"info": "#89b989",
|
||||
"success": "#99c299",
|
||||
"warning": "#d4bb9d",
|
||||
"error": "#c49a9a"
|
||||
},
|
||||
"isInfoFollowPrimary": true,
|
||||
"resetCacheStrategy": "refresh",
|
||||
"layout": {
|
||||
"mode": "vertical-mix",
|
||||
"scrollMode": "wrapper"
|
||||
},
|
||||
"page": {
|
||||
"animate": true,
|
||||
"animateMode": "zoom-fade"
|
||||
},
|
||||
"header": {
|
||||
"height": 64,
|
||||
"breadcrumb": {
|
||||
"visible": true,
|
||||
"showIcon": true
|
||||
},
|
||||
"multilingual": {
|
||||
"visible": true
|
||||
},
|
||||
"globalSearch": {
|
||||
"visible": true
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"visible": true,
|
||||
"cache": true,
|
||||
"height": 48,
|
||||
"mode": "chrome"
|
||||
},
|
||||
"fixedHeaderAndTab": true,
|
||||
"sider": {
|
||||
"inverted": false,
|
||||
"width": 220,
|
||||
"collapsedWidth": 64,
|
||||
"mixWidth": 90,
|
||||
"mixCollapsedWidth": 64,
|
||||
"mixChildMenuWidth": 200
|
||||
},
|
||||
"footer": {
|
||||
"visible": true,
|
||||
"fixed": true,
|
||||
"height": 56,
|
||||
"right": true
|
||||
},
|
||||
"watermark": {
|
||||
"visible": false,
|
||||
"text": "SoybeanAdmin",
|
||||
"enableUserName": false,
|
||||
"enableTime": true,
|
||||
"timeFormat": "YYYY-MM-DD HH:mm:ss"
|
||||
},
|
||||
"tokens": {
|
||||
"light": {
|
||||
"colors": {
|
||||
"container": "rgb(255, 255, 255)",
|
||||
"layout": "rgb(247, 250, 252)",
|
||||
"inverted": "rgb(0, 20, 40)",
|
||||
"base-text": "rgb(31, 31, 31)"
|
||||
},
|
||||
"boxShadow": {
|
||||
"header": "0 1px 2px rgb(0, 21, 41, 0.08)",
|
||||
"sider": "2px 0 8px 0 rgb(29, 35, 41, 0.05)",
|
||||
"tab": "0 1px 2px rgb(0, 21, 41, 0.08)"
|
||||
}
|
||||
},
|
||||
"dark": {
|
||||
"colors": {
|
||||
"container": "rgb(28, 28, 28)",
|
||||
"layout": "rgb(18, 18, 18)",
|
||||
"base-text": "rgb(224, 224, 224)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
90
src/theme/preset/compact.json
Normal file
90
src/theme/preset/compact.json
Normal file
@ -0,0 +1,90 @@
|
||||
{
|
||||
"name": "Compact Preset",
|
||||
"desc": "Compact layout preset for small screens",
|
||||
"i18nkey": "theme.appearance.preset.compact",
|
||||
"version": "1.0.0",
|
||||
"themeScheme": "light",
|
||||
"grayscale": false,
|
||||
"colourWeakness": false,
|
||||
"recommendColor": false,
|
||||
"themeColor": "#646cff",
|
||||
"otherColor": {
|
||||
"info": "#2080f0",
|
||||
"success": "#52c41a",
|
||||
"warning": "#faad14",
|
||||
"error": "#f5222d"
|
||||
},
|
||||
"isInfoFollowPrimary": true,
|
||||
"resetCacheStrategy": "close",
|
||||
"layout": {
|
||||
"mode": "vertical",
|
||||
"scrollMode": "content"
|
||||
},
|
||||
"page": {
|
||||
"animate": true,
|
||||
"animateMode": "fade-slide"
|
||||
},
|
||||
"header": {
|
||||
"height": 48,
|
||||
"breadcrumb": {
|
||||
"visible": true,
|
||||
"showIcon": true
|
||||
},
|
||||
"multilingual": {
|
||||
"visible": false
|
||||
},
|
||||
"globalSearch": {
|
||||
"visible": false
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"visible": true,
|
||||
"cache": true,
|
||||
"height": 36,
|
||||
"mode": "button"
|
||||
},
|
||||
"fixedHeaderAndTab": true,
|
||||
"sider": {
|
||||
"inverted": false,
|
||||
"width": 180,
|
||||
"collapsedWidth": 48,
|
||||
"mixWidth": 80,
|
||||
"mixCollapsedWidth": 48,
|
||||
"mixChildMenuWidth": 180
|
||||
},
|
||||
"footer": {
|
||||
"visible": false,
|
||||
"fixed": false,
|
||||
"height": 40,
|
||||
"right": true
|
||||
},
|
||||
"watermark": {
|
||||
"visible": false,
|
||||
"text": "SoybeanAdmin",
|
||||
"enableUserName": false,
|
||||
"enableTime": false,
|
||||
"timeFormat": "YYYY-MM-DD HH:mm"
|
||||
},
|
||||
"tokens": {
|
||||
"light": {
|
||||
"colors": {
|
||||
"container": "rgb(255, 255, 255)",
|
||||
"layout": "rgb(247, 250, 252)",
|
||||
"inverted": "rgb(0, 20, 40)",
|
||||
"base-text": "rgb(31, 31, 31)"
|
||||
},
|
||||
"boxShadow": {
|
||||
"header": "0 1px 2px rgb(0, 21, 41, 0.08)",
|
||||
"sider": "2px 0 8px 0 rgb(29, 35, 41, 0.05)",
|
||||
"tab": "0 1px 2px rgb(0, 21, 41, 0.08)"
|
||||
}
|
||||
},
|
||||
"dark": {
|
||||
"colors": {
|
||||
"container": "rgb(28, 28, 28)",
|
||||
"layout": "rgb(18, 18, 18)",
|
||||
"base-text": "rgb(224, 224, 224)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
90
src/theme/preset/dark.json
Normal file
90
src/theme/preset/dark.json
Normal file
@ -0,0 +1,90 @@
|
||||
{
|
||||
"name": "Dark Preset",
|
||||
"desc": "Dark theme preset for night time usage",
|
||||
"i18nkey": "theme.appearance.preset.dark",
|
||||
"version": "1.0.0",
|
||||
"themeScheme": "dark",
|
||||
"grayscale": false,
|
||||
"colourWeakness": false,
|
||||
"recommendColor": false,
|
||||
"themeColor": "#409eff",
|
||||
"otherColor": {
|
||||
"info": "#2080f0",
|
||||
"success": "#52c41a",
|
||||
"warning": "#faad14",
|
||||
"error": "#f5222d"
|
||||
},
|
||||
"isInfoFollowPrimary": true,
|
||||
"resetCacheStrategy": "close",
|
||||
"layout": {
|
||||
"mode": "vertical",
|
||||
"scrollMode": "content"
|
||||
},
|
||||
"page": {
|
||||
"animate": true,
|
||||
"animateMode": "fade-slide"
|
||||
},
|
||||
"header": {
|
||||
"height": 56,
|
||||
"breadcrumb": {
|
||||
"visible": true,
|
||||
"showIcon": true
|
||||
},
|
||||
"multilingual": {
|
||||
"visible": true
|
||||
},
|
||||
"globalSearch": {
|
||||
"visible": true
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"visible": true,
|
||||
"cache": true,
|
||||
"height": 44,
|
||||
"mode": "chrome"
|
||||
},
|
||||
"fixedHeaderAndTab": true,
|
||||
"sider": {
|
||||
"inverted": true,
|
||||
"width": 220,
|
||||
"collapsedWidth": 64,
|
||||
"mixWidth": 90,
|
||||
"mixCollapsedWidth": 64,
|
||||
"mixChildMenuWidth": 200
|
||||
},
|
||||
"footer": {
|
||||
"visible": true,
|
||||
"fixed": false,
|
||||
"height": 48,
|
||||
"right": true
|
||||
},
|
||||
"watermark": {
|
||||
"visible": false,
|
||||
"text": "SoybeanAdmin",
|
||||
"enableUserName": false,
|
||||
"enableTime": false,
|
||||
"timeFormat": "YYYY-MM-DD HH:mm"
|
||||
},
|
||||
"tokens": {
|
||||
"light": {
|
||||
"colors": {
|
||||
"container": "rgb(255, 255, 255)",
|
||||
"layout": "rgb(247, 250, 252)",
|
||||
"inverted": "rgb(0, 20, 40)",
|
||||
"base-text": "rgb(31, 31, 31)"
|
||||
},
|
||||
"boxShadow": {
|
||||
"header": "0 1px 2px rgb(0, 21, 41, 0.08)",
|
||||
"sider": "2px 0 8px 0 rgb(29, 35, 41, 0.05)",
|
||||
"tab": "0 1px 2px rgb(0, 21, 41, 0.08)"
|
||||
}
|
||||
},
|
||||
"dark": {
|
||||
"colors": {
|
||||
"container": "rgb(28, 28, 28)",
|
||||
"layout": "rgb(18, 18, 18)",
|
||||
"base-text": "rgb(224, 224, 224)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
90
src/theme/preset/default.json
Normal file
90
src/theme/preset/default.json
Normal file
@ -0,0 +1,90 @@
|
||||
{
|
||||
"name": "default",
|
||||
"desc": "Default theme preset with balanced settings",
|
||||
"i18nkey": "theme.appearance.preset.default",
|
||||
"version": "1.0.0",
|
||||
"themeScheme": "light",
|
||||
"grayscale": false,
|
||||
"colourWeakness": false,
|
||||
"recommendColor": false,
|
||||
"themeColor": "#646cff",
|
||||
"otherColor": {
|
||||
"info": "#2080f0",
|
||||
"success": "#52c41a",
|
||||
"warning": "#faad14",
|
||||
"error": "#f5222d"
|
||||
},
|
||||
"isInfoFollowPrimary": true,
|
||||
"resetCacheStrategy": "close",
|
||||
"layout": {
|
||||
"mode": "vertical",
|
||||
"scrollMode": "content"
|
||||
},
|
||||
"page": {
|
||||
"animate": true,
|
||||
"animateMode": "fade-slide"
|
||||
},
|
||||
"header": {
|
||||
"height": 56,
|
||||
"breadcrumb": {
|
||||
"visible": true,
|
||||
"showIcon": true
|
||||
},
|
||||
"multilingual": {
|
||||
"visible": true
|
||||
},
|
||||
"globalSearch": {
|
||||
"visible": true
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"visible": true,
|
||||
"cache": true,
|
||||
"height": 44,
|
||||
"mode": "chrome"
|
||||
},
|
||||
"fixedHeaderAndTab": true,
|
||||
"sider": {
|
||||
"inverted": false,
|
||||
"width": 220,
|
||||
"collapsedWidth": 64,
|
||||
"mixWidth": 90,
|
||||
"mixCollapsedWidth": 64,
|
||||
"mixChildMenuWidth": 200
|
||||
},
|
||||
"footer": {
|
||||
"visible": true,
|
||||
"fixed": false,
|
||||
"height": 48,
|
||||
"right": true
|
||||
},
|
||||
"watermark": {
|
||||
"visible": false,
|
||||
"text": "SoybeanAdmin",
|
||||
"enableUserName": false,
|
||||
"enableTime": false,
|
||||
"timeFormat": "YYYY-MM-DD HH:mm"
|
||||
},
|
||||
"tokens": {
|
||||
"light": {
|
||||
"colors": {
|
||||
"container": "rgb(255, 255, 255)",
|
||||
"layout": "rgb(247, 250, 252)",
|
||||
"inverted": "rgb(0, 20, 40)",
|
||||
"base-text": "rgb(31, 31, 31)"
|
||||
},
|
||||
"boxShadow": {
|
||||
"header": "0 1px 2px rgb(0, 21, 41, 0.08)",
|
||||
"sider": "2px 0 8px 0 rgb(29, 35, 41, 0.05)",
|
||||
"tab": "0 1px 2px rgb(0, 21, 41, 0.08)"
|
||||
}
|
||||
},
|
||||
"dark": {
|
||||
"colors": {
|
||||
"container": "rgb(28, 28, 28)",
|
||||
"layout": "rgb(18, 18, 18)",
|
||||
"base-text": "rgb(224, 224, 224)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
||||
error: '#CB2634'
|
||||
},
|
||||
isInfoFollowPrimary: true,
|
||||
resetCacheStrategy: 'close',
|
||||
resetCacheStrategy: 'refresh',
|
||||
layout: {
|
||||
mode: 'vertical',
|
||||
scrollMode: 'content'
|
||||
|
14
src/typings/app.d.ts
vendored
14
src/typings/app.d.ts
vendored
@ -406,6 +406,7 @@ declare namespace App {
|
||||
appearance: string;
|
||||
layout: string;
|
||||
general: string;
|
||||
preset: string;
|
||||
};
|
||||
appearance: {
|
||||
themeSchema: { title: string } & Record<UnionKey.ThemeScheme, string>;
|
||||
@ -417,6 +418,17 @@ declare namespace App {
|
||||
} & Theme.ThemeColor;
|
||||
recommendColor: string;
|
||||
recommendColorDesc: string;
|
||||
preset: {
|
||||
title: string;
|
||||
apply: string;
|
||||
applySuccess: string;
|
||||
[key: string]:
|
||||
| {
|
||||
name: string;
|
||||
desc: string;
|
||||
}
|
||||
| string;
|
||||
};
|
||||
};
|
||||
layout: {
|
||||
layoutMode: { title: string } & Record<UnionKey.ThemeLayoutMode, string> & {
|
||||
@ -517,6 +529,8 @@ declare namespace App {
|
||||
};
|
||||
login: {
|
||||
common: {
|
||||
title: string;
|
||||
subTitle: string;
|
||||
loginOrRegister: string;
|
||||
register: string;
|
||||
userNamePlaceholder: string;
|
||||
|
2
src/typings/components.d.ts
vendored
2
src/typings/components.d.ts
vendored
@ -68,6 +68,8 @@ declare module 'vue' {
|
||||
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
|
||||
NColorPicker: typeof import('naive-ui')['NColorPicker']
|
||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
NDescriptions: typeof import('naive-ui')['NDescriptions']
|
||||
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
|
||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||
NDivider: typeof import('naive-ui')['NDivider']
|
||||
NDrawer: typeof import('naive-ui')['NDrawer']
|
||||
|
67
src/utils/export.ts
Normal file
67
src/utils/export.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { utils, writeFile } from 'xlsx';
|
||||
import { isNotNull } from '@/utils/common';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
export interface ExportExcelProps<T> {
|
||||
columns: NaiveUI.TableColumn<T>[];
|
||||
data: T[];
|
||||
filename: string;
|
||||
ignoreKeys?: (keyof T | (string & {}))[];
|
||||
dicts?: Record<keyof T, string>;
|
||||
}
|
||||
|
||||
export function exportExcel<T>({
|
||||
columns,
|
||||
data,
|
||||
filename,
|
||||
dicts,
|
||||
ignoreKeys = ['index', 'operate']
|
||||
}: ExportExcelProps<T>) {
|
||||
const exportColumns = columns.filter(col => isTableColumnHasKey(col) && !ignoreKeys?.includes(col.key));
|
||||
|
||||
const excelList = data.map(item => exportColumns.map(col => getTableValue(col, item, dicts)));
|
||||
|
||||
const titleList = exportColumns.map(col => (isTableColumnHasTitle(col) && col.title) || null);
|
||||
|
||||
excelList.unshift(titleList);
|
||||
|
||||
const workBook = utils.book_new();
|
||||
|
||||
const workSheet = utils.aoa_to_sheet(excelList);
|
||||
|
||||
workSheet['!cols'] = exportColumns.map(item => ({
|
||||
width: Math.round(Number(item.width) / 10 || 20)
|
||||
}));
|
||||
|
||||
utils.book_append_sheet(workBook, workSheet, filename);
|
||||
|
||||
writeFile(workBook, `${filename}.xlsx`);
|
||||
}
|
||||
|
||||
function getTableValue<T>(col: NaiveUI.TableColumn<T>, item: T, dicts?: Record<keyof T, string>) {
|
||||
if (!isTableColumnHasKey(col)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { key } = col;
|
||||
|
||||
if (key === 'operate') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isNotNull(dicts?.[key as keyof T]) && isNotNull(item[key as keyof T])) {
|
||||
return $t(item[key as keyof T] as App.I18n.I18nKey);
|
||||
}
|
||||
|
||||
return item[key as keyof T];
|
||||
}
|
||||
|
||||
function isTableColumnHasKey<T>(column: NaiveUI.TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
|
||||
return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
|
||||
}
|
||||
|
||||
function isTableColumnHasTitle<T>(column: NaiveUI.TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> & {
|
||||
title: string;
|
||||
} {
|
||||
return Boolean((column as NaiveUI.TableColumnWithKey<T>).title);
|
||||
}
|
@ -39,45 +39,51 @@ const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative min-h-screen w-full flex flex-wrap">
|
||||
<div class="hidden min-h-screen w-50% bg-primary-100 lg:block dark:bg-primary-800">
|
||||
<div class="size-full flex-center">
|
||||
<img class="w-60% sm:w-80%" :src="loginBackground" />
|
||||
<!-- Copyright By https://github.com/Daymychen/art-design-pro/blob/main/src/components/core/views/login/LoginLeftView.vue -->
|
||||
<div class="box-border size-full flex">
|
||||
<div class="relative box-border hidden h-full w-65vw overflow-hidden bg-primary-50 xl:block dark:bg-primary-900">
|
||||
<div class="relative z-100 flex items-center pl-30px pt-30px">
|
||||
<SystemLogo class="text-32px text-primary" />
|
||||
<h3 class="ml-10px text-20px font-400">{{ $t('system.title') }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex-col-center px-24px py-32px lg:w-50%">
|
||||
<div class="mx-auto max-w-464px w-full">
|
||||
<header class="flex-y-center justify-between">
|
||||
<div class="flex-y-center gap-16px">
|
||||
<SystemLogo class="text-30px text-primary sm:text-42px" />
|
||||
<h3 class="text-24px text-primary font-500 sm:text-32px">{{ $t('system.title') }}</h3>
|
||||
</div>
|
||||
<div class="flex-y-center">
|
||||
<ThemeSchemaSwitch
|
||||
:theme-schema="themeStore.themeScheme"
|
||||
:show-tooltip="false"
|
||||
class="text-20px lt-sm:text-18px"
|
||||
@switch="themeStore.toggleThemeScheme"
|
||||
/>
|
||||
<LangSwitch
|
||||
v-if="themeStore.header.multilingual.visible"
|
||||
:lang="appStore.locale"
|
||||
:lang-options="appStore.localeOptions"
|
||||
:show-tooltip="false"
|
||||
class="text-20px lt-sm:text-18px"
|
||||
@change-lang="appStore.changeLocale"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
<main class="pt-24px">
|
||||
<div>
|
||||
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
|
||||
<component :is="activeModule.component" />
|
||||
</Transition>
|
||||
</div>
|
||||
</main>
|
||||
<div class="absolute inset-x-0 inset-b-10.5% inset-t-0 z-10 m-auto w-40%">
|
||||
<img class="size-full" :src="loginBackground" />
|
||||
</div>
|
||||
<div class="absolute bottom-80px w-full text-center">
|
||||
<h1 class="text-24px font-400">{{ $t('page.login.common.title') }}</h1>
|
||||
<p class="mt-8px text-14px color-gray-500">{{ $t('page.login.common.subTitle') }}</p>
|
||||
</div>
|
||||
<WaveBg />
|
||||
</div>
|
||||
<header class="relative h-full flex-1 xl:m-auto sm:!w-full">
|
||||
<div class="relative z-100 block flex items-center pl-30px pt-30px xl:hidden">
|
||||
<SystemLogo class="text-32px text-primary" />
|
||||
<h3 class="ml-10px text-20px font-400">{{ $t('system.title') }}</h3>
|
||||
</div>
|
||||
<div class="position-fixed right-30px top-24px z-100 flex items-center justify-end">
|
||||
<div class="ml-15px inline-block flex cursor-pointer select-none p-5px">
|
||||
<ThemeSchemaSwitch
|
||||
:theme-schema="themeStore.themeScheme"
|
||||
:show-tooltip="false"
|
||||
class="text-20px lt-sm:text-18px"
|
||||
@switch="themeStore.toggleThemeScheme"
|
||||
/>
|
||||
<LangSwitch
|
||||
v-if="themeStore.header.multilingual.visible"
|
||||
:lang="appStore.locale"
|
||||
:lang-options="appStore.localeOptions"
|
||||
:show-tooltip="false"
|
||||
class="text-20px lt-sm:text-18px"
|
||||
@change-lang="appStore.changeLocale"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<main class="absolute inset-0 m-auto h-630px max-w-450px w-full overflow-hidden rounded-5px bg-cover px-24px">
|
||||
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
|
||||
<component :is="activeModule.component" />
|
||||
</Transition>
|
||||
</main>
|
||||
</header>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -40,6 +40,10 @@ async function handleSubmit() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-5px text-32px text-black font-600 sm:text-30px dark:text-white">
|
||||
{{ $t('page.login.codeLogin.title') }}
|
||||
</div>
|
||||
<div class="pb-18px text-16px text-#858585">请输入您的手机号,我们将发送验证码到您的手机</div>
|
||||
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
|
||||
<NFormItem path="phone">
|
||||
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
||||
@ -52,15 +56,32 @@ async function handleSubmit() {
|
||||
</NButton>
|
||||
</div>
|
||||
</NFormItem>
|
||||
<NSpace vertical :size="18" class="w-full">
|
||||
<NButton type="primary" size="large" round block @click="handleSubmit">
|
||||
{{ $t('common.confirm') }}
|
||||
<NSpace vertical :size="20" class="w-full">
|
||||
<NButton type="primary" size="large" block @click="handleSubmit">
|
||||
{{ $t('page.login.codeLogin.title') }}
|
||||
</NButton>
|
||||
<NButton size="large" round block @click="toggleLoginModule('pwd-login')">
|
||||
<NButton size="large" block @click="toggleLoginModule('pwd-login')">
|
||||
{{ $t('page.login.common.back') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
:deep(.n-base-selection),
|
||||
:deep(.n-input) {
|
||||
--n-height: 42px !important;
|
||||
--n-font-size: 16px !important;
|
||||
--n-border-radius: 8px !important;
|
||||
}
|
||||
|
||||
:deep(.n-base-selection-label) {
|
||||
padding: 0 6px !important;
|
||||
}
|
||||
|
||||
:deep(.n-button) {
|
||||
--n-height: 42px !important;
|
||||
--n-font-size: 18px !important;
|
||||
--n-border-radius: 8px !important;
|
||||
}
|
||||
</style>
|
||||
|
@ -124,8 +124,8 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-12px text-24px text-black font-500 sm:text-30px dark:text-white">登录到您的账户</div>
|
||||
<div class="pb-24px text-18px text-#858585">欢迎回来!请输入您的账户信息</div>
|
||||
<div class="mb-5px text-32px text-black font-600 dark:text-white">登录到您的账户</div>
|
||||
<div class="pb-18px text-16px text-#858585">欢迎回来!请输入您的账户信息</div>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="model"
|
||||
@ -156,16 +156,16 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
|
||||
<NFormItem v-if="captchaEnabled" path="code">
|
||||
<div class="w-full flex-y-center gap-16px">
|
||||
<NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
|
||||
<NSpin :show="codeLoading" :size="28" class="h-52px">
|
||||
<NButton :focusable="false" class="login-code h-52px w-136px" @click="handleFetchCaptchaCode">
|
||||
<NSpin :show="codeLoading" :size="28" class="h-42px">
|
||||
<NButton :focusable="false" class="login-code h-42px w-136px" @click="handleFetchCaptchaCode">
|
||||
<img v-if="codeUrl" :src="codeUrl" />
|
||||
<NEmpty v-else :show-icon="false" description="暂无验证码" />
|
||||
</NButton>
|
||||
</NSpin>
|
||||
</div>
|
||||
</NFormItem>
|
||||
<NSpace vertical :size="16" class="mb-8px">
|
||||
<div class="mx-6px mb-10px flex-y-center justify-between">
|
||||
<NSpace vertical :size="12" class="mb-8px">
|
||||
<div class="mx-6px mb-8px flex-y-center justify-between">
|
||||
<NCheckbox v-model:checked="remberMe" size="large">{{ $t('page.login.pwdLogin.rememberMe') }}</NCheckbox>
|
||||
<NA type="primary" class="text-18px" @click="toggleLoginModule('reset-pwd')">
|
||||
{{ $t('page.login.pwdLogin.forgetPassword') }}
|
||||
@ -199,7 +199,7 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
|
||||
</NButton>
|
||||
</div>
|
||||
|
||||
<div class="mt-32px w-full text-center text-18px text-#858585">
|
||||
<div class="mt-24px w-full text-center text-18px text-#858585">
|
||||
您还没有账户?
|
||||
<NA type="primary" class="text-18px" @click="toggleLoginModule('register')">
|
||||
{{ $t('page.login.common.register') }}
|
||||
@ -216,13 +216,13 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
|
||||
}
|
||||
|
||||
img {
|
||||
height: 52px;
|
||||
height: 42px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-base-selection),
|
||||
:deep(.n-input) {
|
||||
--n-height: 52px !important;
|
||||
--n-height: 42px !important;
|
||||
--n-font-size: 16px !important;
|
||||
--n-border-radius: 8px !important;
|
||||
}
|
||||
@ -237,7 +237,7 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
|
||||
}
|
||||
|
||||
:deep(.n-button) {
|
||||
--n-height: 52px !important;
|
||||
--n-height: 42px !important;
|
||||
--n-font-size: 18px !important;
|
||||
--n-border-radius: 8px !important;
|
||||
}
|
||||
|
@ -104,8 +104,8 @@ handleFetchCaptchaCode();
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-12px text-24px text-black font-500 sm:text-30px dark:text-white">注册新账户</div>
|
||||
<div class="pb-24px text-18px text-#858585">欢迎注册!请输入您的账户信息</div>
|
||||
<div class="mb-5px text-32px text-black font-600 sm:text-30px dark:text-white">注册新账户</div>
|
||||
<div class="pb-18px text-16px text-#858585">欢迎注册!请输入您的账户信息</div>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="model"
|
||||
@ -147,14 +147,14 @@ handleFetchCaptchaCode();
|
||||
</NSpin>
|
||||
</div>
|
||||
</NFormItem>
|
||||
<NSpace vertical :size="18" class="w-full pt-6px">
|
||||
<NSpace vertical :size="18" class="w-full">
|
||||
<NButton type="primary" size="large" block :loading="registerLoading" @click="handleSubmit">
|
||||
{{ $t('page.login.common.register') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
|
||||
<div class="mt-32px w-full text-center text-18px text-#858585">
|
||||
<div class="mt-24px w-full text-center text-18px text-#858585">
|
||||
您已有账户?
|
||||
<NA type="primary" class="text-18px" @click="toggleLoginModule('pwd-login')">
|
||||
{{ $t('common.login') }}
|
||||
@ -177,7 +177,7 @@ handleFetchCaptchaCode();
|
||||
|
||||
:deep(.n-base-selection),
|
||||
:deep(.n-input) {
|
||||
--n-height: 52px !important;
|
||||
--n-height: 42px !important;
|
||||
--n-font-size: 16px !important;
|
||||
--n-border-radius: 8px !important;
|
||||
}
|
||||
@ -187,7 +187,7 @@ handleFetchCaptchaCode();
|
||||
}
|
||||
|
||||
:deep(.n-button) {
|
||||
--n-height: 52px !important;
|
||||
--n-height: 42px !important;
|
||||
--n-font-size: 18px !important;
|
||||
--n-border-radius: 8px !important;
|
||||
}
|
||||
|
@ -46,10 +46,10 @@ async function handleSubmit() {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-12px text-24px text-black font-500 sm:text-30px dark:text-white">
|
||||
<div class="mb-5px text-32px text-black font-600 sm:text-30px dark:text-white">
|
||||
{{ $t('page.login.resetPwd.title') }}
|
||||
</div>
|
||||
<div class="pb-24px text-18px text-#858585">请输入您的手机号,我们将发送验证码到您的手机</div>
|
||||
<div class="pb-18px text-16px text-#858585">请输入您的手机号,我们将发送验证码到您的手机</div>
|
||||
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
|
||||
<NFormItem path="phone">
|
||||
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
||||
@ -73,7 +73,7 @@ async function handleSubmit() {
|
||||
:placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NSpace vertical :size="18" class="w-full">
|
||||
<NSpace vertical :size="20" class="w-full">
|
||||
<NButton type="primary" size="large" block @click="handleSubmit">
|
||||
{{ $t('page.login.resetPwd.title') }}
|
||||
</NButton>
|
||||
@ -88,7 +88,7 @@ async function handleSubmit() {
|
||||
<style scoped>
|
||||
:deep(.n-base-selection),
|
||||
:deep(.n-input) {
|
||||
--n-height: 52px !important;
|
||||
--n-height: 42px !important;
|
||||
--n-font-size: 16px !important;
|
||||
--n-border-radius: 8px !important;
|
||||
}
|
||||
@ -98,7 +98,7 @@ async function handleSubmit() {
|
||||
}
|
||||
|
||||
:deep(.n-button) {
|
||||
--n-height: 52px !important;
|
||||
--n-height: 42px !important;
|
||||
--n-font-size: 18px !important;
|
||||
--n-border-radius: 8px !important;
|
||||
}
|
||||
|
Reference in New Issue
Block a user