From c95aa594cb4e56218c459e1c3792ad59be484b8d Mon Sep 17 00:00:00 2001 From: liuyun Date: Sun, 11 Oct 2026 11:34:41 +0800 Subject: [PATCH] feat: configurable login page + register/forgot-password pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 登录页配置化:src/config/login.ts(品牌/布局/字段/开关),split/card 两种布局 - JsonSchemaProperty 新增 prefixIcon - 注册 /register、忘记密码 /forgot-password 独立页面(blank 布局) - 登录/注册/忘记密码统一 blank 布局(新增 blank.vue) - 注册/重置成功后带 username 回填登录页 Co-Authored-By: Claude Opus 4.8 --- auto-imports.d.ts | 1 + components.d.ts | 8 +- src/config/login.ts | 61 ++++++ src/layouts/blank.vue | 12 ++ src/pages/forgot-password.vue | 165 +++++++++++++++ src/pages/login.vue | 61 ------ src/pages/login/index.vue | 374 ++++++++++++++++++++++++++++++++++ src/pages/register.vue | 179 ++++++++++++++++ src/types/crud.ts | 2 + stats.html | 2 +- typed-router.d.ts | 34 +++- 11 files changed, 829 insertions(+), 70 deletions(-) create mode 100644 src/config/login.ts create mode 100644 src/layouts/blank.vue create mode 100644 src/pages/forgot-password.vue delete mode 100644 src/pages/login.vue create mode 100644 src/pages/login/index.vue create mode 100644 src/pages/register.vue diff --git a/auto-imports.d.ts b/auto-imports.d.ts index fde48bf..2738704 100644 --- a/auto-imports.d.ts +++ b/auto-imports.d.ts @@ -7,6 +7,7 @@ export {} declare global { const EffectScope: typeof import('vue').EffectScope + const ElMessage: typeof import('element-plus/es').ElMessage const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate const asyncComputed: typeof import('@vueuse/core').asyncComputed const autoResetRef: typeof import('@vueuse/core').autoResetRef diff --git a/components.d.ts b/components.d.ts index 7c084f3..8723fde 100644 --- a/components.d.ts +++ b/components.d.ts @@ -16,6 +16,7 @@ declare module 'vue' { ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] ElButton: typeof import('element-plus/es')['ElButton'] ElCard: typeof import('element-plus/es')['ElCard'] + ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] ElDialog: typeof import('element-plus/es')['ElDialog'] @@ -28,6 +29,7 @@ declare module 'vue' { ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] + ElLink: typeof import('element-plus/es')['ElLink'] ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElOption: typeof import('element-plus/es')['ElOption'] @@ -41,8 +43,6 @@ declare module 'vue' { ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] - ElTabPane: typeof import('element-plus/es')['ElTabPane'] - ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] Icon: typeof import('./src/components/icon/icon.vue')['default'] @@ -73,6 +73,7 @@ declare global { const ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] const ElButton: typeof import('element-plus/es')['ElButton'] const ElCard: typeof import('element-plus/es')['ElCard'] + const ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] const ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] const ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] const ElDialog: typeof import('element-plus/es')['ElDialog'] @@ -85,6 +86,7 @@ declare global { const ElFormItem: typeof import('element-plus/es')['ElFormItem'] const ElInput: typeof import('element-plus/es')['ElInput'] const ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] + const ElLink: typeof import('element-plus/es')['ElLink'] const ElMenu: typeof import('element-plus/es')['ElMenu'] const ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] const ElOption: typeof import('element-plus/es')['ElOption'] @@ -98,8 +100,6 @@ declare global { const ElSwitch: typeof import('element-plus/es')['ElSwitch'] const ElTable: typeof import('element-plus/es')['ElTable'] const ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] - const ElTabPane: typeof import('element-plus/es')['ElTabPane'] - const ElTabs: typeof import('element-plus/es')['ElTabs'] const ElTag: typeof import('element-plus/es')['ElTag'] const ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] const Icon: typeof import('./src/components/icon/icon.vue')['default'] diff --git a/src/config/login.ts b/src/config/login.ts new file mode 100644 index 0000000..d29f93c --- /dev/null +++ b/src/config/login.ts @@ -0,0 +1,61 @@ +import type { JsonSchema } from '@/types/crud' +import logo from '@/assets/images/logo.png' + +/** 登录页布局:split=左右分栏(左品牌右表单)/ card=居中卡片。 */ +export type LoginLayout = 'split' | 'card' + +/** 登录页配置。默认值在此集中配置,fork 项目时改这里即可。 */ +export interface LoginConfig { + /** 系统名称。 */ + title: string + /** Logo 图片地址。 */ + logo?: string + /** 副标题/标语。 */ + subtitle?: string + /** 版权信息。 */ + copyright: string + /** 布局形态。 */ + layout: LoginLayout + /** 背景(图 URL 或 CSS 渐变/颜色)。 */ + background?: string + /** 登录字段(JSON Schema,字段名须与后端 DTO 一致)。 */ + fields: JsonSchema + /** 是否显示「记住我」。 */ + showRemember: boolean + /** 是否显示「忘记密码」。 */ + showForgetPassword: boolean + /** 是否显示「注册」。 */ + showRegister: boolean + /** 是否显示验证码(当前为 UI 占位,后端 DTO 暂无 captcha)。 */ + showCaptcha: boolean + /** 登录成功跳转路径。 */ + homePath: string +} + +export const defaultLoginConfig: LoginConfig = { + title: '投云系统管理平台', + logo, + subtitle: '通用系统管理脚手架', + copyright: '云南投云科技有限公司', + layout: 'split', + fields: { + type: 'object', + properties: { + tenantCode: { type: 'string', title: '租户编码', placeholder: '请输入租户编码', prefixIcon: 'mdi:domain' }, + username: { type: 'string', title: '用户名', placeholder: '请输入用户名', prefixIcon: 'mdi:account' }, + password: { + type: 'string', + title: '密码', + format: 'password', + placeholder: '请输入密码', + prefixIcon: 'mdi:lock', + }, + }, + required: ['tenantCode', 'username', 'password'], + }, + showRemember: true, + showForgetPassword: true, + showRegister: true, + showCaptcha: true, + homePath: '/', +} diff --git a/src/layouts/blank.vue b/src/layouts/blank.vue new file mode 100644 index 0000000..d7c8283 --- /dev/null +++ b/src/layouts/blank.vue @@ -0,0 +1,12 @@ + + + + + diff --git a/src/pages/forgot-password.vue b/src/pages/forgot-password.vue new file mode 100644 index 0000000..dfb9aec --- /dev/null +++ b/src/pages/forgot-password.vue @@ -0,0 +1,165 @@ + +meta: + layout: 'blank' + requiresAuth: false + + + + + + + diff --git a/src/pages/login.vue b/src/pages/login.vue deleted file mode 100644 index c4b05a9..0000000 --- a/src/pages/login.vue +++ /dev/null @@ -1,61 +0,0 @@ - -meta: - layout: false - requiresAuth: false - - - - - - - diff --git a/src/pages/login/index.vue b/src/pages/login/index.vue new file mode 100644 index 0000000..f820299 --- /dev/null +++ b/src/pages/login/index.vue @@ -0,0 +1,374 @@ + +meta: + layout: 'blank' + requiresAuth: false + + + + + + + diff --git a/src/pages/register.vue b/src/pages/register.vue new file mode 100644 index 0000000..d8facb5 --- /dev/null +++ b/src/pages/register.vue @@ -0,0 +1,179 @@ + +meta: + layout: 'blank' + requiresAuth: false + + + + + + + diff --git a/src/types/crud.ts b/src/types/crud.ts index e058144..d17a35a 100644 --- a/src/types/crud.ts +++ b/src/types/crud.ts @@ -24,6 +24,8 @@ export interface JsonSchemaProperty { minimum?: number maximum?: number placeholder?: string + /** 输入框前缀图标(iconify 图标名,如 mdi:account)。 */ + prefixIcon?: string /** 控件宽度(如 '160px');缺省时搜索行内默认 200px、弹窗表单默认 100%。 */ width?: string /** format=tree-select 时的树数据([{ label, value, children }])。 */ diff --git a/stats.html b/stats.html index d6cb0c9..23a772e 100644 --- a/stats.html +++ b/stats.html @@ -4930,7 +4930,7 @@ var drawChart = (function (exports) {