diff --git a/.stylelintrc.cjs b/.stylelintrc.cjs index bc1219c9..e6b1be15 100644 --- a/.stylelintrc.cjs +++ b/.stylelintrc.cjs @@ -44,7 +44,7 @@ module.exports = { "at-rule-no-unknown": [ true, { - ignoreAtRules: ["apply", "use"], + ignoreAtRules: ["apply", "use", "forward"], }, ], }, diff --git a/README.md b/README.md index 57c992aa..502747d2 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ vue3-element-admin

vue3-element-admin

- - - + + + diff --git a/package.json b/package.json index d209bf11..4f82fd25 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue3-element-admin", - "version": "2.14.0", + "version": "2.15.0", "private": true, "type": "module", "scripts": { @@ -52,8 +52,7 @@ "animate.css": "^4.1.1", "axios": "^1.7.7", "codemirror": "^5.65.17", - "codemirror-editor-vue3": "^2.7.0", - "color": "^4.2.3", + "codemirror-editor-vue3": "^2.7.1", "echarts": "^5.5.1", "element-plus": "^2.8.1", "exceljs": "^4.4.0", @@ -73,7 +72,6 @@ "@commitlint/config-conventional": "^18.6.3", "@iconify-json/ep": "^1.2.0", "@types/codemirror": "^5.60.15", - "@types/color": "^3.0.6", "@types/lodash": "^4.17.7", "@types/node": "^20.16.5", "@types/nprogress": "^0.2.3", @@ -119,8 +117,7 @@ "vue-tsc": "^2.1.6" }, "engines": { - "node": "^18.0.0 || ^20.7.0", - "pnpm": ">=9" + "node": ">=18.0.0" }, "repository": "https://gitee.com/youlaiorg/vue3-element-admin.git", "author": "有来开源组织", diff --git a/src/api/generator.ts b/src/api/codegen.ts similarity index 98% rename from src/api/generator.ts rename to src/api/codegen.ts index a6888c7c..166aa222 100644 --- a/src/api/generator.ts +++ b/src/api/codegen.ts @@ -1,6 +1,6 @@ import request from "@/utils/request"; -const GENERATOR_BASE_URL = "/api/v1/generator"; +const GENERATOR_BASE_URL = "/api/v1/codegen"; class GeneratorAPI { /** 获取数据表分页列表 */ diff --git a/src/assets/icons/gitee.svg b/src/assets/icons/gitee.svg new file mode 100644 index 00000000..c799c2f3 --- /dev/null +++ b/src/assets/icons/gitee.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/qq.svg b/src/assets/icons/qq.svg new file mode 100644 index 00000000..a59086b4 --- /dev/null +++ b/src/assets/icons/qq.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/wechat.svg b/src/assets/icons/wechat.svg new file mode 100644 index 00000000..2fc58038 --- /dev/null +++ b/src/assets/icons/wechat.svg @@ -0,0 +1 @@ + diff --git a/src/assets/images/login-image.svg b/src/assets/images/login-image.svg new file mode 100644 index 00000000..f42e56b6 --- /dev/null +++ b/src/assets/images/login-image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/IconSelect/index.vue b/src/components/IconSelect/index.vue index 7e383086..e74aa44c 100644 --- a/src/components/IconSelect/index.vue +++ b/src/components/IconSelect/index.vue @@ -201,7 +201,7 @@ onClickOutside(iconSelectRef, () => (popoverVisible.value = false), { } .icon-item:hover { - border-color: #409eff; + border-color: #4080ff; scale: 1.2; } } diff --git a/src/lang/package/en.ts b/src/lang/package/en.ts index 7eac43b3..77e9e4f9 100644 --- a/src/lang/package/en.ts +++ b/src/lang/package/en.ts @@ -11,6 +11,8 @@ export default { login: "Login", captchaCode: "Verify Code", capsLock: "Caps Lock is On", + rememberMe: "Remember Me", + forgetPassword: "Forget Password", message: { username: { required: "Please enter Username", @@ -23,6 +25,7 @@ export default { required: "Please enter Verify Code", }, }, + otherLoginMethods: "Other login methods", }, // 导航栏国际化 navbar: { diff --git a/src/lang/package/zh-cn.ts b/src/lang/package/zh-cn.ts index 36764dc9..e8093cf1 100644 --- a/src/lang/package/zh-cn.ts +++ b/src/lang/package/zh-cn.ts @@ -11,6 +11,8 @@ export default { login: "登 录", captchaCode: "验证码", capsLock: "大写锁定已打开", + rememberMe: "记住我", + forgetPassword: "忘记密码", message: { username: { required: "请输入用户名", @@ -23,6 +25,7 @@ export default { required: "请输入验证码", }, }, + otherLoginMethods: "其他登录方式", }, // 导航栏国际化 navbar: { diff --git a/src/layout/components/Settings/components/ThemeColorPicker.vue b/src/layout/components/Settings/components/ThemeColorPicker.vue index 5a0d59cd..587c1ac5 100644 --- a/src/layout/components/Settings/components/ThemeColorPicker.vue +++ b/src/layout/components/Settings/components/ThemeColorPicker.vue @@ -15,7 +15,7 @@ const emit = defineEmits(["update:modelValue"]); // 定义颜色预设 const colorPresets = [ - "#409EFF", + "#4080FF", "#ff4500", "#ff8c00", "#90ee90", diff --git a/src/main.ts b/src/main.ts index ff813587..4ea367dc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,10 +10,10 @@ import "element-plus/theme-chalk/dark/css-vars.css"; import "@/styles/index.scss"; import "uno.css"; import "animate.css"; -import { InstallCodemirro } from "codemirror-editor-vue3"; +import { InstallCodeMirror } from "codemirror-editor-vue3"; const app = createApp(App); // 注册插件 app.use(setupPlugins); -app.use(InstallCodemirro); +app.use(InstallCodeMirror); app.mount("#app"); diff --git a/src/settings.ts b/src/settings.ts index e25fef2c..c4393497 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -18,7 +18,7 @@ const defaultSettings: AppSettings = { theme: mediaQueryList.matches ? ThemeEnum.DARK : ThemeEnum.LIGHT, size: SizeEnum.DEFAULT, language: LanguageEnum.ZH_CN, - themeColor: "#409EFF", + themeColor: "#4080FF", watermarkEnabled: false, watermarkContent: pkg.name, }; diff --git a/src/store/modules/settings.ts b/src/store/modules/settings.ts index a581e90f..9a2eee6a 100644 --- a/src/store/modules/settings.ts +++ b/src/store/modules/settings.ts @@ -1,68 +1,50 @@ import defaultSettings from "@/settings"; import { ThemeEnum } from "@/enums/ThemeEnum"; -import Color from "color"; +import { generateThemeColors, applyTheme, toggleDarkMode } from "@/utils/theme"; type SettingsValue = boolean | string; export const useSettingsStore = defineStore("setting", () => { - // 是否显示设置 + // 基本设置 const settingsVisible = ref(false); - // 是否显示标签视图 + // 标签 const tagsView = useStorage("tagsView", defaultSettings.tagsView); - // 是否显示侧边栏logo + // 侧边栏 Logo const sidebarLogo = useStorage( "sidebarLogo", defaultSettings.sidebarLogo ); - // 是否固定头部 + // 固定头部 const fixedHeader = useStorage( "fixedHeader", defaultSettings.fixedHeader ); - // 布局模式:left-左侧模式(默认) top-顶部模式 mix-混合模式 + // 布局 const layout = useStorage("layout", defaultSettings.layout); - // 主题颜色 - const themeColor = useStorage( - "themeColor", - defaultSettings.themeColor - ); - // 主题:light-亮色(默认) dark-暗色 - const theme = useStorage("theme", defaultSettings.theme); - // 是否开启水印 + // 水印 const watermarkEnabled = useStorage( "watermarkEnabled", defaultSettings.watermarkEnabled ); + // 主题 + const themeColor = useStorage( + "themeColor", + defaultSettings.themeColor + ); + const theme = useStorage("theme", defaultSettings.theme); + + // 监听主题变化 watch( [theme, themeColor], - ([newTheme, newThemeColor], [oldTheme, oldThemeColor]) => { - if (newTheme !== oldTheme) { - if (newTheme === ThemeEnum.DARK) { - document.documentElement.classList.add("dark"); - } else { - document.documentElement.classList.remove("dark"); - } - } - - if (newThemeColor !== oldThemeColor) { - const rootStyle = document.documentElement.style; - rootStyle.setProperty(`--el-color-primary`, newThemeColor); - rootStyle.setProperty(`--el-color-primary-dark-2`, newThemeColor); - - for (let i = 1; i < 10; i++) { - rootStyle.setProperty( - `--el-color-primary-light-${i}`, - `${Color(newThemeColor).alpha(1 - i * 0.1)}` - ); - } - } + ([newTheme, newThemeColor]) => { + toggleDarkMode(newTheme === ThemeEnum.DARK); + const colors = generateThemeColors(newThemeColor); + applyTheme(colors); }, - { - immediate: true, // 立即执行,确保在侦听器创建时执行一次 - } + { immediate: true } ); - + // 设置更改函数 const settingsMap: Record> = { fixedHeader, tagsView, @@ -79,31 +61,17 @@ export const useSettingsStore = defineStore("setting", () => { value: SettingsValue; }) { const setting = settingsMap[key]; - if (setting) { - setting.value = value; - } + if (setting) setting.value = value; } - /** - * 切换主题 - */ function changeTheme(val: string) { theme.value = val; } - /** - * 切换主题颜色 - * - * @param color 主题颜色 - * - */ function changeThemeColor(color: string) { themeColor.value = color; } - /** - * 切换布局 - */ function changeLayout(val: string) { layout.value = val; } diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 4eea0408..40bf52f2 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -69,7 +69,6 @@ export const useUserStore = defineStore("user", () => { // remove token function resetToken() { - console.log("resetToken"); return new Promise((resolve) => { localStorage.setItem(TOKEN_KEY, ""); resetRouter(); diff --git a/src/styles/login.scss b/src/styles/login.scss index ede34896..3f4f382c 100644 --- a/src/styles/login.scss +++ b/src/styles/login.scss @@ -8,25 +8,47 @@ background: url("@/assets/images/login-background-light.jpg") no-repeat center right; - .top-bar { - position: absolute; - top: 0; - left: 0; + .login-content { display: flex; - align-items: center; - justify-content: flex-end; width: 100%; - padding: 10px; - } + min-width: 400px; + max-width: 850px; + overflow: hidden; + background-color: #fff; + border-radius: 5px; + box-shadow: var(--el-box-shadow-light); - .login-card { - width: 400px; - background: transparent; - border: none; - border-radius: 4%; + @media (width <= 768px) { + flex-direction: column; + max-width: 100%; + height: 100vh; + border-radius: 0; + box-shadow: none; + } - @media (width <= 640px) { - width: 340px; + .login-image { + display: flex; + flex: 3; + align-items: center; + justify-content: center; + background: linear-gradient(60deg, #165dff, #6aa1ff); + + @media (width <= 768px) { + display: none; + } + } + + .login-box { + display: flex; + flex: 2; + flex-direction: column; + justify-content: center; + min-width: 400px; + padding: 30px; + + @media (width <= 768px) { + width: 100%; + } } .input-wrapper { @@ -43,13 +65,6 @@ } } - .icp-info { - position: absolute; - bottom: 4px; - font-size: 12px; - text-align: center; - } - .el-form-item { background: var(--el-input-bg-color); border: 1px solid var(--el-border-color); @@ -75,7 +90,14 @@ } } -html.dark .login-container { - background: url("@/assets/images/login-background-dark.jpg") no-repeat center - right; +html.dark { + .login-container { + background: url("@/assets/images/login-background-dark.jpg") no-repeat + center right; + + .login-content { + background: transparent; + box-shadow: var(--el-box-shadow); + } + } } diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 42f97182..7948a7c6 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -1,3 +1,27 @@ +@forward "element-plus/theme-chalk/src/common/var.scss" with ( + $colors: ( + "primary": ( + "base": #4080ff, + ), + "success": ( + "base": #23c343, + ), + "warning": ( + "base": #ff9a2e, + ), + "danger": ( + "base": #f76560, + ), + "info": ( + "base": #a9aeb8, + ), + ), + + $bg-color: ( + "page": #f5f8fd, + ) +); + /** 全局SCSS变量 */ :root { diff --git a/src/utils/theme.ts b/src/utils/theme.ts new file mode 100644 index 00000000..a62d01d1 --- /dev/null +++ b/src/utils/theme.ts @@ -0,0 +1,52 @@ +// 辅助函数:将十六进制颜色转换为 RGB +function hexToRgb(hex: string): [number, number, number] { + const bigint = parseInt(hex.slice(1), 16); + return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255]; +} + +// 辅助函数:将 RGB 转换为十六进制颜色 +function rgbToHex(r: number, g: number, b: number): string { + return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; +} + +// 辅助函数:调整颜色亮度 +function adjustBrightness(hex: string, factor: number): string { + const rgb = hexToRgb(hex); + const newRgb = rgb.map((val) => + Math.max(0, Math.min(255, Math.round(val + (255 - val) * factor))) + ) as [number, number, number]; + return rgbToHex(...newRgb); +} + +export function generateThemeColors(primary: string) { + const colors: Record = { + primary, + }; + + // 生成浅色变体 + for (let i = 1; i <= 9; i++) { + const factor = i * 0.1; + colors[`primary-light-${i}`] = adjustBrightness(primary, factor); + } + + // 生成深色变体 + colors["primary-dark-2"] = adjustBrightness(primary, -0.2); + + return colors; +} + +export function applyTheme(colors: Record) { + const el = document.documentElement; + + Object.entries(colors).forEach(([key, value]) => { + el.style.setProperty(`--el-color-${key}`, value); + }); +} + +export function toggleDarkMode(isDark: boolean) { + if (isDark) { + document.documentElement.classList.add("dark"); + } else { + document.documentElement.classList.remove("dark"); + } +} diff --git a/src/views/generator/index.vue b/src/views/codegen/index.vue similarity index 99% rename from src/views/generator/index.vue rename to src/views/codegen/index.vue index b1a6b1d2..98f48bd1 100644 --- a/src/views/generator/index.vue +++ b/src/views/codegen/index.vue @@ -430,7 +430,7 @@