feat: 新增租户切换组件和css目录优化
This commit is contained in:
@@ -19,7 +19,5 @@ VITE_MOCK_DEV_SERVER=false
|
||||
# 多租户功能开关
|
||||
# ============================================
|
||||
# 是否启用多租户功能(默认:false)
|
||||
# true: 启用多租户,显示租户切换器,发送 tenant-id 请求头
|
||||
# false: 禁用多租户,隐藏租户相关UI,不发送 tenant-id 请求头
|
||||
# 注意:前端开关需要与后端配置(youlai.tenant.enabled)保持一致
|
||||
VITE_APP_TENANT_ENABLED=false
|
||||
VITE_APP_TENANT_ENABLED=true
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import request from "@/utils/request";
|
||||
import type { MenuTypeEnum } from "@/enums/business";
|
||||
|
||||
const MENU_BASE_URL = "/api/v1/menus";
|
||||
|
||||
const MenuAPI = {
|
||||
@@ -42,7 +44,6 @@ export interface MenuQuery {
|
||||
/** 搜索关键字 */
|
||||
keywords?: string;
|
||||
}
|
||||
import type { MenuTypeEnum } from "@/enums/system/menu-enum";
|
||||
export interface MenuVO {
|
||||
/** 子菜单 */
|
||||
children?: MenuVO[];
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComponentSize } from "@/enums/settings/layout-enum";
|
||||
import { ComponentSize } from "@/enums/settings";
|
||||
import { useAppStore } from "@/store/modules/app-store";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
35
src/components/TenantSwitcher/index.vue
Normal file
35
src/components/TenantSwitcher/index.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<el-select
|
||||
v-if="tenantList.length > 0"
|
||||
v-model="currentTenantIdRef"
|
||||
placeholder="选择租户"
|
||||
style="width: 180px"
|
||||
@change="onChange"
|
||||
>
|
||||
<el-option v-for="item in tenantList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useTenantStoreHook } from "@/store/modules/tenant-store";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "change", tenantId: number): void;
|
||||
}>();
|
||||
|
||||
const tenantStore = useTenantStoreHook();
|
||||
|
||||
const tenantList = computed(() => tenantStore.tenantList);
|
||||
|
||||
const currentTenantIdRef = computed<number | null>({
|
||||
get: () => tenantStore.currentTenantId,
|
||||
set: (val) => {
|
||||
tenantStore.currentTenantId = val;
|
||||
},
|
||||
});
|
||||
|
||||
function onChange(tenantId: number) {
|
||||
emit("change", tenantId);
|
||||
}
|
||||
</script>
|
||||
@@ -20,7 +20,7 @@
|
||||
<script setup lang="ts">
|
||||
import { type RouteLocationNormalized } from "vue-router";
|
||||
import { useSettingsStore, useTagsViewStore } from "@/store";
|
||||
import variables from "@/styles/variables.scss";
|
||||
import variables from "@/styles/variables.module.scss";
|
||||
import Error404 from "@/views/error/404.vue";
|
||||
|
||||
const { cachedViews } = toRefs(useTagsViewStore());
|
||||
|
||||
@@ -33,7 +33,7 @@ import { SidebarColor } from "@/enums/settings";
|
||||
import { useSettingsStore, useAppStore } from "@/store";
|
||||
import { isExternal } from "@/utils/index";
|
||||
import MenuItem from "./components/MenuItem.vue";
|
||||
import variables from "@/styles/variables.scss";
|
||||
import variables from "@/styles/variables.module.scss";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
|
||||
@@ -39,7 +39,7 @@ defineOptions({
|
||||
|
||||
import { LocationQueryRaw, RouteRecordRaw } from "vue-router";
|
||||
import { usePermissionStore, useAppStore, useSettingsStore } from "@/store";
|
||||
import variables from "@/styles/variables.scss";
|
||||
import variables from "@/styles/variables.module.scss";
|
||||
import { SidebarColor } from "@/enums/settings";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<!-- 租户选择(如果启用多租户) -->
|
||||
<div v-if="showTenantSelect" class="navbar-actions__item">
|
||||
<TenantSelect />
|
||||
<TenantSwitcher @change="handleTenantChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -83,7 +83,7 @@ import Fullscreen from "@/components/Fullscreen/index.vue";
|
||||
import SizeSelect from "@/components/SizeSelect/index.vue";
|
||||
import LangSelect from "@/components/LangSelect/index.vue";
|
||||
import Notification from "@/components/Notification/index.vue";
|
||||
import TenantSelect from "@/components/TenantSelect/index.vue";
|
||||
import TenantSwitcher from "@/components/TenantSwitcher/index.vue";
|
||||
import { useTenantStoreHook } from "@/store/modules/tenant-store";
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -113,6 +113,18 @@ const showTenantSelect = computed(() => {
|
||||
return true;
|
||||
});
|
||||
|
||||
function handleTenantChange(tenantId: number) {
|
||||
tenantStore
|
||||
.switchTenant(tenantId)
|
||||
.then(() => {
|
||||
ElMessage.success("切换租户成功");
|
||||
window.location.reload();
|
||||
})
|
||||
.catch((error: any) => {
|
||||
ElMessage.error(error?.message || "切换租户失败");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开个人中心页面
|
||||
*/
|
||||
|
||||
@@ -14,7 +14,7 @@ import LeftLayout from "@/layouts/modes/left/index.vue";
|
||||
import TopLayout from "@/layouts/modes/top/index.vue";
|
||||
import MixLayout from "@/layouts/modes/mix/index.vue";
|
||||
import Settings from "./components/Settings/index.vue";
|
||||
import { LayoutMode } from "@/enums/settings/layout-enum";
|
||||
import { LayoutMode } from "@/enums/settings";
|
||||
import { defaultSettings } from "@/settings";
|
||||
|
||||
const { currentLayout } = useLayout();
|
||||
|
||||
@@ -69,7 +69,7 @@ import TagsView from "../../components/TagsView/index.vue";
|
||||
import AppMain from "../../components/AppMain/index.vue";
|
||||
import MenuItem from "../../components/Menu/components/MenuItem.vue";
|
||||
import Hamburger from "@/components/Hamburger/index.vue";
|
||||
import variables from "@/styles/variables.scss";
|
||||
import variables from "@/styles/variables.module.scss";
|
||||
import { isExternal } from "@/utils/index";
|
||||
import { useAppStore, usePermissionStore } from "@/store";
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import App from "./App.vue";
|
||||
// ===== 样式导入 =====
|
||||
import "element-plus/theme-chalk/dark/css-vars.css";
|
||||
import "vxe-table/lib/style.css";
|
||||
import "@/styles/dark/css-vars.css";
|
||||
import "@/styles/index.scss";
|
||||
import "uno.css";
|
||||
import "animate.css";
|
||||
|
||||
@@ -24,7 +24,7 @@ export const defaultSettings: AppSettings = {
|
||||
size: ComponentSize.DEFAULT,
|
||||
// 语言
|
||||
language: LanguageEnum.ZH_CN,
|
||||
// 主题颜色 - 修改此值时需同步修改 src/styles/variables.scss
|
||||
// 主题颜色 - 修改此值时需同步修改 src/styles/element-plus-vars.scss
|
||||
themeColor: "#4080FF",
|
||||
// 是否显示水印
|
||||
showWatermark: false,
|
||||
@@ -52,7 +52,7 @@ export const authConfig = {
|
||||
} as const;
|
||||
|
||||
// 主题色预设 - 经典配色方案
|
||||
// 注意:修改默认主题色时,需要同步修改 src/styles/variables.scss 中的 primary.base 值
|
||||
// 注意:修改默认主题色时,需要同步修改 src/styles/element-plus-vars.scss 中的 primary.base 值
|
||||
export const themeColorPresets = [
|
||||
"#4080FF", // Arco Design 蓝 - 现代感强
|
||||
"#1890FF", // Ant Design 蓝 - 经典商务
|
||||
|
||||
@@ -108,35 +108,38 @@ export const useTenantStore = defineStore("tenant", () => {
|
||||
*
|
||||
* @param tenantId 目标租户ID
|
||||
*/
|
||||
function switchTenant(tenantId: number) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
TenantAPI.switchTenant(tenantId)
|
||||
.then((tenantInfo) => {
|
||||
// 后端返回切换后的租户信息
|
||||
if (tenantInfo) {
|
||||
setCurrentTenant(tenantInfo);
|
||||
} else {
|
||||
// 如果后端未返回,从租户列表中找到对应的租户信息
|
||||
const tenant = tenantList.value.find((t) => t.id === tenantId);
|
||||
if (tenant) {
|
||||
setCurrentTenant(tenant);
|
||||
async function switchTenant(tenantId: number): Promise<void> {
|
||||
try {
|
||||
// 调用后端切换接口
|
||||
const tenantInfo = await TenantAPI.switchTenant(tenantId);
|
||||
|
||||
// 后端返回切换后的租户信息
|
||||
if (tenantInfo) {
|
||||
setCurrentTenant(tenantInfo);
|
||||
} else {
|
||||
// 如果后端未返回,从租户列表中找到对应的租户信息
|
||||
const tenant = tenantList.value.find((t) => t.id === tenantId);
|
||||
if (tenant) {
|
||||
setCurrentTenant(tenant);
|
||||
} else {
|
||||
// 如果列表中没有,重新获取租户信息
|
||||
try {
|
||||
const info = await TenantAPI.getCurrentTenant();
|
||||
if (info) {
|
||||
setCurrentTenant(info);
|
||||
} else {
|
||||
// 如果列表中没有,重新获取租户信息
|
||||
TenantAPI.getCurrentTenant()
|
||||
.then((info) => {
|
||||
if (info) {
|
||||
setCurrentTenant(info);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
throw new Error("无法获取租户信息");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取租户信息失败:", error);
|
||||
throw new Error("切换租户后无法获取租户信息");
|
||||
}
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("切换租户失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,6 +153,13 @@ export const useTenantStore = defineStore("tenant", () => {
|
||||
localStorage.removeItem(STORAGE_KEYS.TENANT_INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置租户列表
|
||||
*/
|
||||
function setTenantList(list: TenantInfo[]) {
|
||||
tenantList.value = list || [];
|
||||
}
|
||||
|
||||
// 恢复本地租户信息
|
||||
restoreTenant();
|
||||
|
||||
@@ -159,6 +169,7 @@ export const useTenantStore = defineStore("tenant", () => {
|
||||
tenantList,
|
||||
loadTenant,
|
||||
fetchTenantList,
|
||||
setTenantList,
|
||||
setCurrentTenant,
|
||||
switchTenant,
|
||||
clearTenant,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
/* 暗黑模式通过 CSS 自定义变量,官方链接:https://element-plus.org/zh-CN/guide/dark-mode.html#%E9%80%9A%E8%BF%87-css */
|
||||
html.dark {
|
||||
.el-table {
|
||||
/* 自定义表格选中高亮时当前行的背景颜色 */
|
||||
--el-table-current-row-bg-color: var(--el-fill-color-light);
|
||||
}
|
||||
}
|
||||
30
src/styles/element-plus-vars.scss
Normal file
30
src/styles/element-plus-vars.scss
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Element Plus 变量覆盖
|
||||
*
|
||||
* 此文件用于覆盖 Element Plus 的默认主题变量
|
||||
* 需要在 element-plus.scss 中导入,而不是在 variables.scss 中
|
||||
*/
|
||||
@forward "element-plus/theme-chalk/src/common/var.scss" with (
|
||||
$colors: (
|
||||
"primary": (
|
||||
// 默认主题色 - 修改此值时需同步修改 src/settings.ts 中的 themeColor
|
||||
"base": #4080ff,
|
||||
),
|
||||
"success": (
|
||||
"base": #23c343,
|
||||
),
|
||||
"warning": (
|
||||
"base": #ff9a2e,
|
||||
),
|
||||
"danger": (
|
||||
"base": #f76560,
|
||||
),
|
||||
"info": (
|
||||
"base": #a9aeb8,
|
||||
),
|
||||
),
|
||||
|
||||
$bg-color: (
|
||||
"page": #f5f8fd,
|
||||
)
|
||||
);
|
||||
@@ -1,3 +1,6 @@
|
||||
// Element Plus 变量覆盖(必须在最前面)
|
||||
@use "./element-plus-vars";
|
||||
|
||||
$border: 1px solid var(--el-border-color-light);
|
||||
|
||||
/* el-dialog */
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// 基础变量与主题色
|
||||
@use "./variables";
|
||||
|
||||
// 基础重置与组件细化样式
|
||||
// 1. 基础重置(补充 UnoCSS 预设未覆盖的全局样式)
|
||||
@use "./reset";
|
||||
@use "./element-plus";
|
||||
|
||||
// Vxe Table 主题覆写(CSS 变量 + 自定义样式)
|
||||
// 2. 项目自定义主题变量(CSS 变量 / SCSS 变量 / JS 导出)
|
||||
@use "./variables" as *;
|
||||
|
||||
// 3. UI 框架适配:Element Plus & Vxe Table
|
||||
@use "./element-plus";
|
||||
@use "./vxe-table";
|
||||
|
||||
// 业务通用样式
|
||||
// 4. 业务通用样式
|
||||
@use "./common";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// 全局基础重置:补充 UnoCSS 预设未覆盖的项目级样式
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -36,11 +38,6 @@ body {
|
||||
text-rendering: optimizelegibility;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
display: inline-block;
|
||||
|
||||
16
src/styles/variables.module.scss
Normal file
16
src/styles/variables.module.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
/* stylelint-disable property-no-unknown */
|
||||
|
||||
// 通过 SCSS 变量导出给 JS/TS 使用的模块文件
|
||||
// 注意:依赖 src/styles/variables.scss 中定义的 SCSS 变量
|
||||
|
||||
:export {
|
||||
sidebar-width: $sidebar-width;
|
||||
navbar-height: $navbar-height;
|
||||
tags-view-height: $tags-view-height;
|
||||
menu-background: $menu-background;
|
||||
menu-text: $menu-text;
|
||||
menu-active-text: $menu-active-text;
|
||||
menu-hover: $menu-hover;
|
||||
}
|
||||
|
||||
/* stylelint-enable property-no-unknown */
|
||||
@@ -1,29 +1,9 @@
|
||||
@forward "element-plus/theme-chalk/src/common/var.scss" with (
|
||||
$colors: (
|
||||
"primary": (
|
||||
// 默认主题色 - 修改此值时需同步修改 src/settings.ts 中的 themeColor
|
||||
"base": #4080ff,
|
||||
),
|
||||
"success": (
|
||||
"base": #23c343,
|
||||
),
|
||||
"warning": (
|
||||
"base": #ff9a2e,
|
||||
),
|
||||
"danger": (
|
||||
"base": #f76560,
|
||||
),
|
||||
"info": (
|
||||
"base": #a9aeb8,
|
||||
),
|
||||
),
|
||||
|
||||
$bg-color: (
|
||||
"page": #f5f8fd,
|
||||
)
|
||||
);
|
||||
|
||||
/** 全局SCSS变量 */
|
||||
/**
|
||||
* 项目自定义主题变量(CSS 变量 / SCSS 变量 / JS 导出)
|
||||
* 与 Element Plus 主题变量覆盖(element-plus-vars.scss)职责分离
|
||||
*
|
||||
* 注意:此文件以下划线开头,是 Sass partial,不会被单独编译,只能被其他文件导入
|
||||
*/
|
||||
|
||||
:root {
|
||||
--menu-background: #fff; // 菜单背景色
|
||||
@@ -56,6 +36,11 @@ html.dark {
|
||||
--sidebar-logo-background: rgb(0 0 0 / 20%);
|
||||
--sidebar-logo-text-color: #fff;
|
||||
|
||||
.el-table {
|
||||
/* 自定义表格选中高亮时当前行的背景颜色(暗黑模式) */
|
||||
--el-table-current-row-bg-color: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
/** WangEditor Dark */
|
||||
/* Textarea - css vars */
|
||||
--w-e-textarea-bg-color: var(--el-bg-color); /* 深色背景 */
|
||||
@@ -92,7 +77,7 @@ $sidebar-width-collapsed: 54px; // 侧边栏收缩宽度
|
||||
$navbar-height: 50px; // 导航栏高度
|
||||
$tags-view-height: 34px; // TagsView 高度
|
||||
|
||||
/* 供 JS/TS 侧按需读取的变量导出(保持与原 module 一致) */
|
||||
/* 供 JS/TS 侧按需读取的变量导出 */
|
||||
/* stylelint-disable property-no-unknown */
|
||||
:export {
|
||||
sidebar-width: $sidebar-width;
|
||||
|
||||
@@ -4,7 +4,6 @@ import { ApiCodeEnum } from "@/enums/api";
|
||||
import { AuthStorage, redirectToLogin } from "@/utils/auth";
|
||||
import { useTokenRefresh } from "@/composables/auth/useTokenRefresh";
|
||||
import { authConfig } from "@/settings";
|
||||
import { useTenantStoreHook } from "@/store/modules/tenant-store";
|
||||
|
||||
// 初始化token刷新组合式函数
|
||||
const { refreshTokenAndRetry } = useTokenRefresh();
|
||||
@@ -20,7 +19,7 @@ const httpRequest = axios.create({
|
||||
});
|
||||
|
||||
/**
|
||||
* 请求拦截器 - 添加 Authorization 头和租户ID
|
||||
* 请求拦截器 - 添加 Authorization 头
|
||||
*/
|
||||
httpRequest.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
@@ -33,19 +32,6 @@ httpRequest.interceptors.request.use(
|
||||
delete config.headers.Authorization;
|
||||
}
|
||||
|
||||
// 添加租户ID到请求头(如果存在)
|
||||
// 注意:只有在登录成功后,tenantStore 才会初始化,所以这里需要 try-catch
|
||||
try {
|
||||
const tenantStore = useTenantStoreHook();
|
||||
const tenantId = tenantStore.currentTenantId;
|
||||
if (tenantId) {
|
||||
config.headers["tenant-id"] = String(tenantId);
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果租户 store 未初始化(如登录前),忽略错误
|
||||
// 这是正常的,因为多租户功能是可选的,未启用时不会初始化 tenantStore
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
|
||||
@@ -97,16 +97,7 @@
|
||||
>
|
||||
<div class="tenant-select-content">
|
||||
<p class="tenant-select-tip">检测到你的账号属于多个租户,请选择登录租户:</p>
|
||||
<el-radio-group v-model="selectedTenantId" class="tenant-radio-group">
|
||||
<el-radio
|
||||
v-for="tenant in pendingTenants"
|
||||
:key="tenant.id"
|
||||
:label="tenant.id"
|
||||
class="tenant-radio"
|
||||
>
|
||||
{{ tenant.name }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<TenantSwitcher @change="(id: number) => (selectedTenantId = id)" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="tenantDialogVisible = false">取消</el-button>
|
||||
@@ -146,12 +137,15 @@ import AuthAPI from "@/api/auth";
|
||||
import type { LoginRequest } from "@/types/api";
|
||||
import router from "@/router";
|
||||
import { useUserStore } from "@/store";
|
||||
import { useTenantStoreHook } from "@/store/modules/tenant-store";
|
||||
import CommonWrapper from "@/components/CommonWrapper/index.vue";
|
||||
import TenantSwitcher from "@/components/TenantSwitcher/index.vue";
|
||||
import { AuthStorage } from "@/utils/auth";
|
||||
import { ApiCodeEnum } from "@/enums";
|
||||
|
||||
const { t } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
const tenantStore = useTenantStoreHook();
|
||||
const route = useRoute();
|
||||
|
||||
onMounted(() => getCaptcha());
|
||||
@@ -219,9 +213,6 @@ function getCaptcha() {
|
||||
.finally(() => (codeLoading.value = false));
|
||||
}
|
||||
|
||||
// 待选择的租户列表
|
||||
const pendingTenants = ref<Array<{ id: number; name: string }>>([]);
|
||||
|
||||
/**
|
||||
* 登录提交
|
||||
*/
|
||||
@@ -243,7 +234,8 @@ async function handleLoginSubmit() {
|
||||
// 检查是否是 choose_tenant 响应
|
||||
if (error?.code === ApiCodeEnum.CHOOSE_TENANT && error?.data?.tenants) {
|
||||
// 需要选择租户
|
||||
pendingTenants.value = error.data.tenants;
|
||||
tenantStore.setTenantList(error.data.tenants);
|
||||
selectedTenantId.value = error.data.tenants[0]?.id || null;
|
||||
tenantDialogVisible.value = true;
|
||||
return; // 等待用户选择租户
|
||||
}
|
||||
@@ -304,12 +296,12 @@ function toOtherForm(type: "register" | "resetPwd") {
|
||||
.auth-panel-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.auth-panel-form__title {
|
||||
margin: 0 0 0.75rem;
|
||||
font-size: 1.25rem;
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -317,7 +309,7 @@ function toOtherForm(type: "register" | "resetPwd") {
|
||||
.divider-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 24px 0;
|
||||
margin: 16px 0;
|
||||
|
||||
.divider-line {
|
||||
flex: 1;
|
||||
@@ -342,33 +334,5 @@ function toOtherForm(type: "register" | "resetPwd") {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.tenant-radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
|
||||
.tenant-radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
:deep(.el-radio__input.is-checked) {
|
||||
+ .el-radio__label {
|
||||
font-weight: 500;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -123,9 +123,7 @@ onBeforeUnmount(() => {
|
||||
height: 100%;
|
||||
padding: clamp(1rem, 3vw, 2rem);
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(circle at 20% 20%, rgba(64, 128, 255, 0.18), transparent 55%),
|
||||
radial-gradient(circle at 80% 80%, rgba(22, 93, 255, 0.16), transparent 50%);
|
||||
background-color: #f5f7ff;
|
||||
|
||||
&::before {
|
||||
position: fixed;
|
||||
@@ -201,13 +199,11 @@ onBeforeUnmount(() => {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: clamp(1.5rem, 3vw, 3rem);
|
||||
color: rgba(20, 40, 80, 0.95);
|
||||
text-shadow: 0 4px 16px rgba(15, 60, 110, 0.12);
|
||||
color: var(--el-text-color-primary);
|
||||
animation: featureFade 0.8s ease-out;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: rgba(236, 242, 255, 0.92);
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +268,7 @@ onBeforeUnmount(() => {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.7;
|
||||
color: rgba(35, 40, 65, 0.85);
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: rgba(220, 230, 255, 0.75);
|
||||
@@ -292,8 +288,8 @@ onBeforeUnmount(() => {
|
||||
align-items: flex-start;
|
||||
padding: 0.75rem 1rem;
|
||||
font-weight: 500;
|
||||
color: rgba(32, 37, 60, 0.9);
|
||||
background: rgba(255, 255, 255, 0.55);
|
||||
color: var(--el-text-color-primary);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid rgba(64, 128, 255, 0.08);
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(6px);
|
||||
@@ -321,11 +317,12 @@ onBeforeUnmount(() => {
|
||||
.auth-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
gap: 1rem;
|
||||
align-self: center;
|
||||
justify-content: flex-start;
|
||||
justify-self: end;
|
||||
width: min(520px, 100%);
|
||||
padding: clamp(2rem, 3vw, 2.75rem);
|
||||
width: min(420px, 100%);
|
||||
padding: clamp(1.5rem, 3vw, 2rem);
|
||||
margin-inline: auto;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border: 1px solid rgba(22, 93, 255, 0.1);
|
||||
@@ -359,11 +356,11 @@ onBeforeUnmount(() => {
|
||||
|
||||
.auth-panel__brand {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 1.25rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 0.875rem;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid rgba(22, 93, 255, 0.06);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@@ -449,7 +446,7 @@ onBeforeUnmount(() => {
|
||||
margin-inline: auto;
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
@@ -472,8 +469,8 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.auth-panel__footer {
|
||||
padding-top: 1.25rem;
|
||||
margin-top: 0.25rem;
|
||||
padding-top: 0.875rem;
|
||||
margin-top: 0.125rem;
|
||||
font-size: 0.875rem;
|
||||
text-align: center;
|
||||
border-top: 1px solid rgba(22, 93, 255, 0.06);
|
||||
|
||||
Reference in New Issue
Block a user