feat(admin): art-design-pro integration with local session auth
P1b Task 9: apps/admin based on art-design-pro v3.0.2 (tag, commit d224dd4; source recorded in apps/admin/SOURCE.md), trimmed to layout/login/i18n/request/table-form core. Login wired to real POST /v1/auth/password (single-token session), Bearer header on all requests, 401 interceptor logout+redirect, request-driven sliding renewal (refresh at ~80% TTL, single-flight), best-effort POST /v1/auth/logout on user-menu logout. One protected stub page (相册管理). Dev port 5176 strictPort + /v1 proxy to 8080. Admin keeps its own eslint9 flat config; root eslint ignores apps/admin/**. vitest unit tests for renewal pacing + Playwright e2e (guard redirect, localized wrong-password error, login lands on stub, token persists across reload, logout clears session). Co-Authored-By: Claude Code <noreply@anthropic.com>
@@ -0,0 +1,20 @@
|
||||
# 【@photo/admin 环境变量样例】复制为 .env 使用(.env 不入库,仓库约定)。
|
||||
# 所有键均有代码兜底,缺省也能构建运行;dev 端口(5176)与 /v1 代理在 vite.config.ts 固定。
|
||||
|
||||
# 版本号(注入 __APP_VERSION__,用于 localStorage 版本化键前缀)
|
||||
VITE_VERSION = 0.1.0
|
||||
|
||||
# 应用部署基础路径(如部署在子目录 /admin,则设置为 /admin/)
|
||||
VITE_BASE_URL = /
|
||||
|
||||
# 权限模式(模板遗留键;@photo/admin 裁剪后仅前端路由表模式)
|
||||
VITE_ACCESS_MODE = frontend
|
||||
|
||||
# 跨域请求时是否携带 Cookie(开启前需确保后端支持)
|
||||
VITE_WITH_CREDENTIALS = false
|
||||
|
||||
# 是否打开路由信息
|
||||
VITE_OPEN_ROUTE_INFO = false
|
||||
|
||||
# 锁屏加密密钥
|
||||
VITE_LOCK_ENCRYPT_KEY = change-me-lock-key
|
||||
@@ -0,0 +1,2 @@
|
||||
*.html linguist-detectable=false
|
||||
*.vue linguist-detectable=true
|
||||
@@ -0,0 +1,12 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.cursorrules
|
||||
|
||||
# Auto-generated files(unplugin 产物已提交入库:turbo typecheck 可独立运行,
|
||||
# 不必先跑一次 vite dev/build 生成声明;.auto-import.json 仅 eslint 全局用,不入库)
|
||||
.auto-import.json
|
||||
test-results/
|
||||
playwright-report/
|
||||
@@ -0,0 +1,3 @@
|
||||
/node_modules/*
|
||||
/dist/*
|
||||
/src/main.ts
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
"vueIndentScriptAndStyle": true,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "as-needed",
|
||||
"bracketSpacing": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSameLine": false,
|
||||
"jsxSingleQuote": false,
|
||||
"arrowParens": "always",
|
||||
"insertPragma": false,
|
||||
"requirePragma": false,
|
||||
"proseWrap": "never",
|
||||
"htmlWhitespaceSensitivity": "strict",
|
||||
"endOfLine": "auto",
|
||||
"rangeStart": 0
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"vue.volar",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"stylelint.vscode-stylelint",
|
||||
"lokalise.i18n-ally",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"volar.inlayHints.eventArgumentInInlineHandlers": true,
|
||||
"css.lint.unknownAtRules": "ignore",
|
||||
"i18n-ally.localesPaths": ["src/locales/langs", "src/locales"],
|
||||
"i18n-ally.enabledParsers": ["json"],
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.displayLanguage": "zh",
|
||||
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"i18n-ally.namespace": true
|
||||
}
|
||||
@@ -0,0 +1,650 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Released]
|
||||
|
||||
### v3.0.1 - 2025-11-15
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed repeated API calls issue when accessing non-existent interfaces during route registration
|
||||
- Fixed one-click cleanup script build failure issue
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Enhanced route configuration validation mechanism, automatically detects and prompts path configuration errors for non-first-level menus
|
||||
- Optimized top progress bar ghosting issue
|
||||
- Optimized vite pre-build configuration
|
||||
- Unified border radius and border consistency
|
||||
- Redesigned lock screen page, improved visual experience
|
||||
- Optimized visual experience issue caused by logout menu disappearing prematurely
|
||||
|
||||
#### Added
|
||||
|
||||
- ArtForm and ArtSearchBar component labels support custom render functions, enabling complex labels with tooltips
|
||||
- Menu management form key fields added Tooltip hints, lowering user configuration threshold
|
||||
- Iconify added offline icon loading mode
|
||||
- Logout added redirect property for redirecting to corresponding page after re-login
|
||||
- Logout and re-entry preserves user tabs, identified by userId
|
||||
- Dual menu added collapse button
|
||||
- Enhanced menu icons
|
||||
- Multi-tab pages added icon display
|
||||
- Added WebSocket connection example
|
||||
- Lock screen password error triggers input box shake animation
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> This version requires re-login
|
||||
|
||||
---
|
||||
|
||||
### v3.0.0 - 2025-11-09 🎉
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Style system refactored: Sass fully migrated to Tailwind CSS, improving development efficiency and style consistency
|
||||
- Icon solution upgraded: Iconfont replaced with Iconify, supporting richer icon libraries and on-demand loading
|
||||
- Route registration refactored: Comprehensive refactoring of route registration system with object-oriented design, improving code maintainability, testability and extensibility
|
||||
- Refactored ArtTextScroll component, improving performance and maintainability
|
||||
- Refactored color system, unified UI visual specifications, improving interface consistency
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Build optimization: Complete package size reduced by 1.3 MB, significantly improving loading performance
|
||||
- Architecture optimization: Optimized directory structure with clearer responsibility division, reducing user learning curve and onboarding difficulty
|
||||
- Comment optimization: Unified module comment standards, improved component descriptions, feature explanations and usage examples, reducing user understanding cost and onboarding difficulty
|
||||
- Performance improvement: Optimized core code logic, improving system runtime efficiency
|
||||
- Menu optimization: Refined menu styles, optimized interaction experience and visual presentation
|
||||
- Responsive optimization: Optimized ArtForm and ArtSearchBar grid layout, adapting to various screen sizes
|
||||
- Configuration management optimization: Added setting.ts configuration file, supporting one-click copy and reset of system default settings
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed ArtForm and ArtSearchBar custom component rendering issues
|
||||
|
||||
#### Added
|
||||
|
||||
- ArtForm and ArtSearchBar added render property, supporting custom component rendering
|
||||
- Festival feature enhancement: Fireworks configuration supports cross-date range settings and custom playback counts
|
||||
|
||||
#### Upgraded
|
||||
|
||||
- Dependency updates: Upgraded core dependencies to latest stable versions
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> This upgrade involves fundamental refactoring of the style system (Sass → Tailwind CSS) and icon library (Iconfont → Iconify), which is a breaking change. It is recommended to use v3.0 for new projects. Upgrading existing projects is not recommended.
|
||||
|
||||
---
|
||||
|
||||
### v2.6.1 - 2025-10-19
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed repeated API calls issue when accessing invalid addresses for user info and menu endpoints
|
||||
- Fixed ElButton circle mode styles
|
||||
- Fixed ElSelect keyboard selection issue
|
||||
- Fixed static routes with parameters redirecting to login page issue
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Upgraded some dependencies to be compatible with tailwindcss
|
||||
- Optimized external link menu click selection state
|
||||
- Added theme color switching feature to authorization pages
|
||||
|
||||
---
|
||||
|
||||
### v2.6.0 - 2025-10-16
|
||||
|
||||
#### Added
|
||||
|
||||
- Extended registration and password reset page top component support
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized streamlined version menu data structure, improved data consistency
|
||||
- Optimized local development environment network request proxy configuration
|
||||
- Optimized ElTree component default styles
|
||||
- Added VsCode recommended plugin related configuration
|
||||
- Optimized ElDropdown component click trigger mode interaction styles
|
||||
- Optimized menu filtering logic
|
||||
- Optimized page switching animation, improved loading speed
|
||||
- Optimized dark mode text color
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed static route custom home page path first visit redirecting to login page issue
|
||||
- Fixed logout briefly jumping to 500 page issue
|
||||
- Fixed v2.5.9 version home route redirect configuration failure issue
|
||||
- Fixed v2.5.9 auto-import mechanism causing build exception
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> This version requires re-login
|
||||
|
||||
---
|
||||
|
||||
### v2.5.9 - 2025-10-12
|
||||
|
||||
#### Code Optimized
|
||||
|
||||
- Optimized views file directory, file names, and code
|
||||
- Added global configuration for useTable pagination request fields (tableConfig.ts)
|
||||
- Optimized routing configuration to modular structure
|
||||
- Menu API now uses apifox mock data (set VITE_ACCESS_MODE to backend mode in .env)
|
||||
|
||||
---
|
||||
|
||||
### v2.5.8 - 2025-09-29
|
||||
|
||||
#### Upgraded
|
||||
|
||||
- Upgraded core libraries including vue, vite, element-plus
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed rich text editor fullscreen top bar z-index issue
|
||||
- Fixed table column sorting component text overflow issue
|
||||
- Fixed statistics card condition judgment
|
||||
- Fixed infinite redirect when root path / and HOME_PAGE_PATH are both /
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized el-tag styles
|
||||
- Optimized top progress bar color
|
||||
- Optimized custom theme configuration
|
||||
- Optimized ElementPlus custom theme issues
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> Due to dependency upgrades, Node version needs to be upgraded to v20.19.0 or above
|
||||
|
||||
---
|
||||
|
||||
### v2.5.7 - 2025-09-14
|
||||
|
||||
#### Added
|
||||
|
||||
- Added ArtForm component
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed theme switching flicker issue in new Chrome versions
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized form label height alignment issue
|
||||
- First screen startup performance optimization
|
||||
|
||||
---
|
||||
|
||||
### v2.5.6 - 2025-09-06
|
||||
|
||||
#### Added
|
||||
|
||||
- ArtTableHeader added search button to control top search bar visibility
|
||||
- Added permission demonstration examples
|
||||
- Added global error handling framework
|
||||
|
||||
#### Optimized
|
||||
|
||||
- useTable type inference optimization, no need to manually pass types for type hints
|
||||
- useTable removeColumn supports multiple data deletion
|
||||
- useTable auto-recognizes response body with custom configuration (src/utils/table/tableConfig.ts)
|
||||
- useTable empty data browser warning optimization
|
||||
- API request code optimization, api.d.ts type optimization
|
||||
- Optimized ArtTable top button wrapping adaptive issue
|
||||
- ArtTable pagination component selection style optimization
|
||||
- ArtTable empty state height fills by default
|
||||
- ArtButtonMore component added icon and color configuration
|
||||
- ArtSearchBar label doesn't occupy space when empty
|
||||
- Table operation column drag prohibits fixed column dragging
|
||||
- Role management page API integration and code optimization
|
||||
- Menu management page optimization
|
||||
- Optimized settings center scroll following issue
|
||||
- First-level route external link component validation logic optimization
|
||||
- Optimized map bottom-right drag issue
|
||||
- Optimized dark mode page refresh white background issue
|
||||
- Optimized left menu collapse button spacing issue
|
||||
- Mobile displays left menu logo
|
||||
- Network request added showSuccessMessage to configure success message display
|
||||
- Global components use async loading strategy to improve first screen loading performance
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed bug where batch deleting entire page data doesn't return to previous page
|
||||
- Fixed dynamic route parameter issues
|
||||
- Fixed dynamic route configuration first-level route iframe page fullscreen issue
|
||||
|
||||
---
|
||||
|
||||
### v2.5.5 - 2025-08-17
|
||||
|
||||
#### Added
|
||||
|
||||
- Added ArtSearchBar component examples
|
||||
- useTable added excludeParams to exclude certain parameters from requests
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Refactored ArtSearchBar component, supports more components and form validation
|
||||
|
||||
#### Optimized
|
||||
|
||||
- useTable column configuration: supports dynamic update capability
|
||||
- Optimized color picker border radius
|
||||
- Unified el-radio and el-checkbox sizes
|
||||
- art-stats-card added decimal places and separator configuration
|
||||
- Route configuration example optimization
|
||||
- Advanced table added custom data fetching example
|
||||
- Optimized path alias type issues
|
||||
- Local development CORS configuration optimization
|
||||
- useTable property and method naming optimization
|
||||
- Login page UI upgrade
|
||||
- 403, 404, 500 page UI upgrade
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed multiple rich text editor icon inconsistency issue
|
||||
- Fixed useTable deleting last entire page data not returning to previous page issue
|
||||
- Fixed echarts chart data initialization and update browser error
|
||||
- Network request supports logout on HTTP status code 401
|
||||
- Optimized network request logout multiple prompt issue
|
||||
|
||||
#### Removed
|
||||
|
||||
- Removed art-chart-empty component
|
||||
|
||||
---
|
||||
|
||||
### v2.5.4 - 2025-07-27
|
||||
|
||||
#### Added
|
||||
|
||||
- Network request headers support custom configuration
|
||||
- Expand row supports formatter rendering
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Route registration added component validation
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed user info API timing issue causing route registration menu rendering error
|
||||
- Fixed dynamic route validation issue causing iframe not to display
|
||||
- Fixed reset file syntax error
|
||||
- Fixed ArtTable data type error
|
||||
- Fixed map scroll wheel zoom issue
|
||||
|
||||
---
|
||||
|
||||
### v2.5.3 - 2025-07-20
|
||||
|
||||
#### Added
|
||||
|
||||
- Added table left-right layout example
|
||||
|
||||
#### Refactored
|
||||
|
||||
- ArtTable component refactored
|
||||
|
||||
#### Upgraded
|
||||
|
||||
- Element Plus upgraded to v2.10.2
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized useTable pagination parameter issue
|
||||
- Optimized network request example: initialization parameters, pagination carrying parameters
|
||||
- Optimized search date range parameter handling
|
||||
- Optimized el-date-picker component border radius
|
||||
- Optimized el-select component hover style
|
||||
- Search component and pagination component height reduced
|
||||
- Optimized login page slider animation interval
|
||||
- Optimized menu without submenu display issue
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed ArtTable switching page size executing two requests bug
|
||||
|
||||
---
|
||||
|
||||
### v2.5.2 - 2025-07-13
|
||||
|
||||
#### Added
|
||||
|
||||
- Added one-click cleanup script for quick development environment preparation
|
||||
- useTable hooks support custom pagination field name mapping
|
||||
- Horizontal menu, mixed menu, double column menu support badge display
|
||||
- Quick entry supports configuration file mode
|
||||
- Top bar function supports configuration file mode
|
||||
- Support custom home page path
|
||||
- Route supports redirect and other attributes configuration
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Global event bus mittBus type safety optimization
|
||||
- Optimized mobile settings container width style
|
||||
- Optimized login page verification slider text centering effect
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed table no data header not displaying issue
|
||||
- Fixed v2.5.0 top progress bar not displaying issue
|
||||
- Fixed left menu mask abnormal display issue
|
||||
- Fixed hiding all submenus still showing parent menu issue
|
||||
- Fixed stylelint causing login page slider style abnormality
|
||||
- Fixed old mobile device loading positioning issue
|
||||
|
||||
---
|
||||
|
||||
### v2.5.1 - 2025-07-08
|
||||
|
||||
#### Optimized
|
||||
|
||||
- el-card and el-table background color consistent with system
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed first login system loading closing early bug
|
||||
- Fixed v2.5.0 version causing fullscreen page style z-index too low bug
|
||||
- Fixed v2.5.0 version causing table expand row collapse bug
|
||||
|
||||
---
|
||||
|
||||
### v2.5.0 - 2025-07-06 🎉
|
||||
|
||||
#### Added
|
||||
|
||||
- Added useTable hooks table encapsulation, supports data fetching, transformation, response adaptation, intelligent caching (based on LRU algorithm), error handling, column configuration and slots, pagination control, refresh strategies and other core functions, comprehensively improving development efficiency and user experience
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Refactored ArtTable, ArtTableHeader, ArtNotification components
|
||||
|
||||
#### Upgraded
|
||||
|
||||
- Echarts version upgraded to 5.6.0
|
||||
|
||||
#### Removed
|
||||
|
||||
- Removed CountTo plugin, replaced with ArtCountTo component
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed menu management search directly modifying pinia data issue
|
||||
- Fixed route guard loading flicker issue
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> Recommended upgrade for more efficient and intelligent table development experience
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.9 - 2025-07-02
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Menu layout and top navigation code refactored
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized mobile menu scroll user experience
|
||||
- Optimized top menu style issues
|
||||
- Top menu width adaptive, can display more content, mixed menu supports mouse scroll
|
||||
- asyncRoutes route configuration auth_mark field changed to authMark
|
||||
- Removed duplicate components.d.ts file, components.d.ts and auto-imports.d.ts ignored from commits
|
||||
- Optimized i18n language file loading method, changed from async to sync mode
|
||||
- Optimized el-pagination size inconsistency issue
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed mobile lock screen page some browsers unable to unlock bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.8 - 2025-06-26
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed v2.4.2.7 version accessing / path showing 404 issue
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.7 - 2025-06-25
|
||||
|
||||
#### Added
|
||||
|
||||
- Route supports fullscreen mode configuration
|
||||
- Route supports auto-jump to first valid route of menu
|
||||
- Dynamic route added removeAllDynamicRoutes method for completely clearing all dynamic routes
|
||||
- Permission custom directive optimization, added role permission directive v-roles for controlling element visibility
|
||||
- Added tab operation examples
|
||||
|
||||
#### Optimized
|
||||
|
||||
- CORS request carrying cookie configuration from environment variables, disabled by default
|
||||
- Some optimizations for SEO and accessibility
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed login page drag component ArtDragVerify width and color abnormal bug
|
||||
- Fixed iframe page mixed mode and double column mode abnormal bug
|
||||
- Optimized lock screen page being penetrated by el-loading bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.6 - 2025-06-23
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Refactored form-related components in components/core/forms folder, improved maintainability and consistency
|
||||
- Refactored ArtBreadcrumb breadcrumb navigation component, optimized logic structure and styles
|
||||
- Refactored ArtFireworksEffect fireworks effect component, significantly improved rendering performance and animation smoothness
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized ArtChatWindow and ArtFastEnter component code, improved readability and performance
|
||||
|
||||
#### Added
|
||||
|
||||
- README documentation added official website link for easy access to project documentation
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.5 - 2025-06-22
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Refactored chart components, optimized code structure and maintainability
|
||||
- Fine-tuned chart animations and theme color schemes, improved visual consistency
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.4 - 2025-06-18
|
||||
|
||||
#### Refactored
|
||||
|
||||
- ArtMenuRight component refactored
|
||||
- Components under components/core/cards refactored, code optimized
|
||||
|
||||
#### Optimized
|
||||
|
||||
- ArtWatermark added type annotations
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.3 - 2025-06-18
|
||||
|
||||
#### Refactored
|
||||
|
||||
- ArtResultPage component refactored
|
||||
|
||||
#### Optimized
|
||||
|
||||
- ArtTextScroll component code optimization
|
||||
- ArtException component added type hints
|
||||
- ArtCutterImg component style optimization, added type definitions
|
||||
- ArtVideoPlayer component added type definitions
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.2 - 2025-06-16
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Back to top component refactored
|
||||
- Icon selector component refactored
|
||||
|
||||
#### Changed
|
||||
|
||||
- System Logo component property changes
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.1 - 2025-06-16
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Banner component refactored and optimized
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed mixed menu first menu nested menu jump bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2 - 2025-06-14
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Refactored network request module, enhanced error handling, type safety and multi-language support
|
||||
|
||||
#### Added
|
||||
|
||||
- Route configuration added activePath active menu path property
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Guide and column settings multi-language improvement
|
||||
- Removed invalid code from user list and menu management pages
|
||||
- Updated technical support link
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed mobile search bar unable to scroll, iPad page scroll abnormal issue
|
||||
- Fixed el-dialog enabling draggable property causing custom animation failure issue
|
||||
- Fixed v2.3.0 local storage refactoring causing login, registration page multi-language settings unable to persist issue
|
||||
- Fixed table fixed column not working bug
|
||||
- Fixed infinite redirect when root path / and HOME_PAGE_PATH are both /
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> This version requires re-login
|
||||
|
||||
---
|
||||
|
||||
### v2.4.1.1 - 2025-06-07
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized role management page code
|
||||
- el-dialog visual effect optimization, supports line configuration
|
||||
- System theme mode changed from Light to follow system mode
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed menu management collapse bug
|
||||
- Fixed table empty data height infinitely growing bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.1 - 2025-06-07
|
||||
|
||||
#### Added
|
||||
|
||||
- Prohibit using developer tools to crack lock screen when locked
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Improved menu operation responsiveness
|
||||
- Page entrance animation time reduced by 0.04s
|
||||
- Echarts chart performance optimization, added visible area initialization, memory leak protection, debounce handling
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed Echarts chart component not displaying in dialog bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.0 - 2025-06-06 🎉
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Global TypeScript type system refactored, improved type accuracy and maintainability
|
||||
- Refactored utils toolkit, unified utility method structure, enhanced readability and reusability
|
||||
|
||||
#### Added
|
||||
|
||||
- utils added form validation and Cookie operation related utility functions
|
||||
- Page components added defineOptions for explicit component naming
|
||||
- HTTP request added token expiration auto-handling logic, improved security and user experience
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Deleted unused utility modules and invalid resources, streamlined project size
|
||||
- Optimized views page structure, removed redundant page files
|
||||
- Exception page multi-language support, improved internationalization experience
|
||||
- Image resources uniformly converted to webp format, overall resource size reduced by about 50%
|
||||
- Build output reduced by about 1MB, improved loading efficiency
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> This version requires re-login
|
||||
|
||||
---
|
||||
|
||||
## Version Guidelines
|
||||
|
||||
### Version Number Rules
|
||||
|
||||
This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html):
|
||||
|
||||
- **Major**: Incompatible API changes
|
||||
- **Minor**: Backwards-compatible functionality additions
|
||||
- **Patch**: Backwards-compatible bug fixes
|
||||
|
||||
### Change Types
|
||||
|
||||
- **Added**: New features
|
||||
- **Changed**: Changes to existing functionality
|
||||
- **Deprecated**: Soon-to-be removed features
|
||||
- **Removed**: Removed features
|
||||
- **Fixed**: Bug fixes
|
||||
- **Security**: Security-related fixes
|
||||
- **Optimized**: Performance or experience optimizations
|
||||
- **Refactored**: Code refactoring
|
||||
- **Upgraded**: Dependency upgrades
|
||||
|
||||
### Important Notice Markers
|
||||
|
||||
- 🎉 **Major Update**: Versions with important new features
|
||||
- ⚠️ **Breaking Changes**: Updates requiring special attention for incompatibility
|
||||
- 🔒 **Security Update**: Versions fixing security vulnerabilities
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
If you want to contribute to the project, please check [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
[MIT License](./LICENSE)
|
||||
|
||||
---
|
||||
|
||||
**Note**: This changelog starts recording from version v2.4.0. For earlier version history, please check Git commit records.
|
||||
@@ -0,0 +1,654 @@
|
||||
# 更新日志
|
||||
|
||||
所有重要的项目变更都将记录在此文件中。
|
||||
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
||||
|
||||
## [已发布]
|
||||
|
||||
### v3.0.1 - 2025-11-15
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复路由注册时不存在接口的重复请求问题
|
||||
- 修复一键精简脚本打包失败的问题
|
||||
|
||||
#### 优化
|
||||
|
||||
- 完善路由配置验证机制,自动检测并提示非一级菜单的路径配置错误
|
||||
- 优化顶部进度条残影问题
|
||||
- 优化 vite 预构建配置
|
||||
- 优化圆角、边框统一性
|
||||
- 重新设计锁屏页面,提升视觉体验
|
||||
- 优化退出登录菜单提前消失造成的视觉体验不好问题
|
||||
|
||||
#### 新增
|
||||
|
||||
- ArtForm 和 ArtSearchBar 组件的 label 支持自定义渲染函数,可实现带 tooltip 等复杂标签
|
||||
- 菜单管理表单关键字段新增 Tooltip 提示,降低用户配置门槛
|
||||
- iconify 新增离线图标加载模式
|
||||
- 退出登录新增 redirect 属性,用于重新登录后跳转到对应页面
|
||||
- 退出登录重新进入系统保留用户标签页,根据 userId 进行身份识别
|
||||
- 双列菜单新增折叠按钮
|
||||
- 完善菜单图标
|
||||
- 多标签页增加图标显示
|
||||
- 新增 WebSocket 连接示例
|
||||
- 锁屏密码错误时增加输入框抖动动效
|
||||
|
||||
#### ⚠️ 重要提示
|
||||
|
||||
> 此版本需要重新登录
|
||||
|
||||
---
|
||||
|
||||
### v3.0.0 - 2025-11-09 🎉
|
||||
|
||||
#### 重构
|
||||
|
||||
- 样式系统重构:Sass 全面迁移至 Tailwind CSS,提升开发效率与样式一致性
|
||||
- 图标方案升级:Iconfont 替换为 Iconify,支持更丰富的图标库与按需加载
|
||||
- 路由注册重构:全面重构路由注册系统,引入面向对象设计,提高代码的可维护性、可测试性和扩展能力
|
||||
- 重构 ArtTextScroll 组件,提升性能与可维护性
|
||||
- 重构颜色体系,统一 UI 视觉规范,提升界面一致性
|
||||
|
||||
#### 优化
|
||||
|
||||
- 构建优化:完整包体积减少 1.3 MB,显著提升加载性能
|
||||
- 架构优化:优化目录结构,职责划分更清晰,降低用户学习成本与上手难度
|
||||
- 注释优化:统一模块注释规范,完善每一个组件介绍、功能说明与使用示例,降低用户理解成本与上手难度
|
||||
- 性能提升:优化核心代码逻辑,提升系统运行效率
|
||||
- 菜单优化:细化菜单样式,优化交互体验与视觉呈现
|
||||
- 响应式优化:优化 ArtForm、ArtSearchBar 栅格布局,适配多种屏幕尺寸
|
||||
- 配置管理优化:新增 setting.ts 配置文件,支持一键复制与重置系统默认设置
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复 ArtForm、ArtSearchBar 自定义组件渲染异常
|
||||
|
||||
#### 新增
|
||||
|
||||
- ArtForm、ArtSearchBar 新增 render 属性,支持自定义组件渲染
|
||||
- 节日功能增强:礼花配置支持跨日期范围设置与自定义播放次数
|
||||
|
||||
#### 升级
|
||||
|
||||
- 依赖更新:升级核心依赖至最新稳定版本
|
||||
|
||||
#### ⚠️ 重要提示
|
||||
|
||||
> 本次升级涉及样式系统(Sass → Tailwind CSS)与图标库(Iconfont → Iconify)的底层重构,属于破坏性更新。建议新项目直接使用 v3.0,旧版本项目不建议升级。
|
||||
|
||||
---
|
||||
|
||||
### v2.6.1 - 2025-10-19
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复获取用户信息、获取菜单接口访问无效地址重复调用问题
|
||||
- 修复 ElButton circle 模式样式
|
||||
- 修复 ElSelect 无法通过键盘选择问题
|
||||
- 修复带参数静态路由跳转登录页面问题
|
||||
|
||||
#### 优化
|
||||
|
||||
- 升级部分依赖兼容 tailwindcss
|
||||
- 优化外部链接菜单点击选中状态
|
||||
- 授权页增加主题色切换功能
|
||||
|
||||
---
|
||||
|
||||
### v2.6.0 - 2025-10-16
|
||||
|
||||
#### 新增
|
||||
|
||||
- 扩展注册、密码重置页面顶部组件支持
|
||||
|
||||
#### 优化
|
||||
|
||||
- 优化精简版本菜单数据结构,提升数据一致性
|
||||
- 优化本地开发环境网络请求代理配置
|
||||
- 优化 ElTree 组件默认样式
|
||||
- 新增 VsCode 推荐插件相关配置
|
||||
- 优化 ElDropdown 组件点击触发模式下的交互样式
|
||||
- 优化菜单过滤逻辑
|
||||
- 优化页面切换动画,提升加载速度
|
||||
- 优化暗黑模式文字颜色
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复静态路由自定义首页路径首次访问跳转登录页问题
|
||||
- 修复退出登录时短暂跳转至 500 页的问题
|
||||
- 修复 v2.5.9 版本首页路由跳转配置失效问题
|
||||
- 修复 v2.5.9 自动导包机制导致的构建异常
|
||||
|
||||
#### ⚠️ 重要提示
|
||||
|
||||
> 此版本需要重新登录
|
||||
|
||||
---
|
||||
|
||||
### v2.5.9 - 2025-10-12
|
||||
|
||||
#### 代码优化
|
||||
|
||||
- views 文件目录、文件名、代码优化
|
||||
- useTable 分页请求字段增加全局配置 tableConfig.ts
|
||||
- 优化路由配置为模块化结构
|
||||
- 获取菜单接口使用 apifox mock 数据(需在 .env 中将 VITE_ACCESS_MODE 设为 backend 模式)
|
||||
|
||||
---
|
||||
|
||||
### v2.5.8 - 2025-09-29
|
||||
|
||||
#### 升级
|
||||
|
||||
- vue、vite、element-plus 等核心库升级
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复富文本编辑器全屏顶栏层级问题
|
||||
- 修复表格列排序组件文字溢出问题
|
||||
- 修复统计卡片条件判断
|
||||
- 修复根路径 / 与 HOME_PAGE_PATH 同为 / 时出现的无限重定向
|
||||
|
||||
#### 优化
|
||||
|
||||
- 优化 el-tag 样式
|
||||
- 优化顶部进度条颜色
|
||||
- 优化自定义主题配置
|
||||
- 优化 ElementPlus 自定义主题问题
|
||||
|
||||
#### ⚠️ 重要提示
|
||||
|
||||
> 由于项目依赖升级,node 版本需要升级到 v20.19.0 或以上
|
||||
|
||||
---
|
||||
|
||||
### v2.5.7 - 2025-09-14
|
||||
|
||||
#### 新增
|
||||
|
||||
- 新增 ArtForm 组件
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复新版本谷歌浏览器切换主题闪烁问题
|
||||
|
||||
#### 优化
|
||||
|
||||
- 优化表单 label 高度没有对齐问题
|
||||
- 首屏启动性能优化
|
||||
|
||||
---
|
||||
|
||||
### v2.5.6 - 2025-09-06
|
||||
|
||||
#### 新增
|
||||
|
||||
- ArtTableHeader 新增搜索按钮,用于控制顶部搜索栏的显示与隐藏
|
||||
- 新增权限演示示例
|
||||
- 添加全局错误处理基础框架
|
||||
|
||||
#### 优化
|
||||
|
||||
- useTable 类型推导优化,不需要手动传递类型即可实现类型提示
|
||||
- useTable removeColumn 支持多数据删除
|
||||
- useTable 自动识别响应体支持自定义配置 (src/utils/table/tableConfig.ts)
|
||||
- useTable 空数据浏览器警告优化
|
||||
- api 接口请求代码优化、api.d.ts 类型优化
|
||||
- 优化 ArtTable 顶部按钮换行无法自适应问题(示例:功能示例 / 左右布局表格)
|
||||
- ArtTable 分页组件选中样式优化
|
||||
- ArtTable 空状态高度默认撑满
|
||||
- ArtButtonMore 组件新增图标、颜色配置
|
||||
- ArtSearchBar label 为空时不占空间
|
||||
- 表格操作栏拖拽禁止固定列拖拽
|
||||
- 角色管理页面接口对接、代码优化
|
||||
- 菜单管理页面优化
|
||||
- 优化设置中心滚动页面跟随滚动问题
|
||||
- 一级路由是外链时,component 校验逻辑优化
|
||||
- 优化地图右下角拖动问题
|
||||
- 优化暗黑模式刷新页面白色背景问题
|
||||
- 优化左侧菜单折叠按钮间距问题
|
||||
- 移动端显示左侧菜单 logo
|
||||
- 网络请求新增 showSuccessMessage,用于配置是否显示成功消息
|
||||
- 全局组件采用异步加载策略,提升首屏加载性能
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复批量删除整页数据没有返回上一页的bug
|
||||
- 修复动态路由参数导致的问题
|
||||
- 修复动态路由配置一级路由是 iframe 页面时,全屏问题
|
||||
|
||||
---
|
||||
|
||||
### v2.5.5 - 2025-08-17
|
||||
|
||||
#### 新增
|
||||
|
||||
- 新增 ArtSearchBar 组件示例
|
||||
- useTable 新增 excludeParams,用于排除某些参数不参与请求
|
||||
|
||||
#### 重构
|
||||
|
||||
- 重构 ArtSearchBar 组件,支持更多组件、表单校验等能力
|
||||
|
||||
#### 优化
|
||||
|
||||
- useTable 列配置:支持动态更新能力
|
||||
- 优化颜色选择器圆角
|
||||
- el-radio、el-checkbox 统一大小
|
||||
- art-stats-card 新增小数位、分隔符配置
|
||||
- 路由配置示例优化
|
||||
- 高级表格新增自定义获取数据示例(等待其他请求完成后执行 useTable 数据获取)
|
||||
- 优化路径别名类型问题
|
||||
- 本地开发跨域配置优化
|
||||
- useTable 属性、方法命名优化
|
||||
- 登录页UI升级
|
||||
- 403、404、500 页面UI升级
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复多个富文本编辑器图标不统一问题
|
||||
- 修复 useTable 删除最后一整页数据没有返回上一页的问题
|
||||
- 修复 echarts 图表数据初始化、更新数据浏览器报错
|
||||
- 网络请求支持 http 状态码为 401 时退出登录
|
||||
- 优化网络请求退出登录多次提示问题
|
||||
|
||||
#### 移除
|
||||
|
||||
- 删除 art-chart-empty 组件
|
||||
|
||||
---
|
||||
|
||||
### v2.5.4 - 2025-07-27
|
||||
|
||||
#### 新增
|
||||
|
||||
- 网络请求 headers 支持自定义配置
|
||||
- 展开行支持 formatter 渲染
|
||||
|
||||
#### 优化
|
||||
|
||||
- 路由注册新增 component 校验
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复获取用户信息接口时序问题导致路由注册菜单渲染错误bug
|
||||
- 修复动态路由校验问题导致的 iframe 不显示bug
|
||||
- 修复 reset 文件语法错误
|
||||
- 修复 ArtTable 数据类型错误
|
||||
- 修复地图滚轮滚动放大问题
|
||||
|
||||
---
|
||||
|
||||
### v2.5.3 - 2025-07-20
|
||||
|
||||
#### 新增
|
||||
|
||||
- 新增表格左右布局示例
|
||||
|
||||
#### 重构
|
||||
|
||||
- ArtTable 组件重构
|
||||
|
||||
#### 升级
|
||||
|
||||
- Element Plus 升级到 v2.10.2
|
||||
|
||||
#### 优化
|
||||
|
||||
- 优化 useTable 分页参数问题
|
||||
- 优化网络请求示例:初始化参数、分页携带参数问题
|
||||
- 优化搜索日期范围参数处理
|
||||
- 优化 el-date-picker 组件圆角问题
|
||||
- 优化 el-select 组件 hover 样式
|
||||
- 搜索组件、分页组件高度降低
|
||||
- 优化登录页面滑块动画间隔时长
|
||||
- 优化菜单没有子菜单显示的问题
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复 ArtTable 切换分页大小时执行两次请求bug
|
||||
|
||||
---
|
||||
|
||||
### v2.5.2 - 2025-07-13
|
||||
|
||||
#### 新增
|
||||
|
||||
- 新增一键精简脚本,快速准备开发环境
|
||||
- useTable hooks 支持分页字段名自定义映射
|
||||
- 水平菜单、混合菜单、双列菜单支持徽章显示
|
||||
- 快速入口支持配置文件模式
|
||||
- 顶栏功能支持配置文件模式
|
||||
- 支持自定义首页路径
|
||||
- 路由支持配置 redirect 等属性
|
||||
|
||||
#### 优化
|
||||
|
||||
- 全局事件总线 mittBus 类型安全优化
|
||||
- 优化移动端设置中容器宽度样式
|
||||
- 优化登录页验证滑块文字居中效果
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复表格无数据时表头不显示问题
|
||||
- 修复 v2.5.0 顶部进度条不显示问题
|
||||
- 修复左侧菜单遮罩异常显示问题
|
||||
- 修复隐藏所有子菜单时仍显示父级菜单的问题
|
||||
- 修复 stylelint 导致的登录页滑块样式异常
|
||||
- 修复老旧移动端设备 loading 定位问题
|
||||
|
||||
---
|
||||
|
||||
### v2.5.1 - 2025-07-08
|
||||
|
||||
#### 优化
|
||||
|
||||
- el-card、el-table 背景色跟系统保持一致
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复首次登录系统时 loading 提前关闭bug
|
||||
- 修复 v2.5.0 版本引起的全屏页样式层级过低bug
|
||||
- 修复 v2.5.0 版本引起的表格展开行折叠bug
|
||||
|
||||
---
|
||||
|
||||
### v2.5.0 - 2025-07-06 🎉
|
||||
|
||||
#### 新增
|
||||
|
||||
- 新增 useTable hooks 表格封装,支持数据获取、转换、响应适配、智能缓存(基于 LRU 算法)、错误处理、列配置与插槽、分页控制、刷新策略等核心功能,全面提升开发效率与用户体验
|
||||
|
||||
#### 重构
|
||||
|
||||
- 重构 ArtTable、ArtTableHeader、ArtNotification 组件
|
||||
|
||||
#### 升级
|
||||
|
||||
- Echarts 版本升级到 5.6.0
|
||||
|
||||
#### 移除
|
||||
|
||||
- 移除 CountTo 插件,替换为 ArtCountTo 组件
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复菜单管理搜索直接修改 pinia 数据的问题
|
||||
- 修复路由守卫 loading 闪烁问题
|
||||
|
||||
#### ⚠️ 重要提示
|
||||
|
||||
> 建议升级,带来更高效、更智能的表格开发体验
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.9 - 2025-07-02
|
||||
|
||||
#### 重构
|
||||
|
||||
- 菜单布局、顶部导航代码重构
|
||||
|
||||
#### 优化
|
||||
|
||||
- 优化移动端菜单滚动用户体验
|
||||
- 优化顶部菜单样式问题
|
||||
- 顶部菜单宽度自适应,可显示更多内容,混合菜单支持鼠标滚动
|
||||
- asyncRoutes 路由配置 auth_mark 字段改为 authMark
|
||||
- 去除重复的 components.d.ts 文件,components.d.ts、auto-imports.d.ts 忽略提交
|
||||
- 优化国际化语言文件加载方式,异步改成同步模式
|
||||
- 优化 el-pagination 大小不一致问题
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复移动端锁屏页部分浏览器无法解锁bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.8 - 2025-06-26
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复 v2.4.2.7 版本访问 / 路径时显示 404 的问题
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.7 - 2025-06-25
|
||||
|
||||
#### 新增
|
||||
|
||||
- 路由支持配置全屏模式
|
||||
- 路由支持自动跳转到菜单的第一个有效路由
|
||||
- 动态路由新增 removeAllDynamicRoutes 方法,可用于彻底清除所有动态路由
|
||||
- 权限自定义指令优化、新增角色权限指令 v-roles、可用于控制元素的显示与隐藏
|
||||
- 新增标签页操作示例
|
||||
|
||||
#### 优化
|
||||
|
||||
- 跨域请求携带 cookie 配置从环境变量中获取,默认关闭
|
||||
- 针对SEO、可访问性做一些优化
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复登录页面拖拽组件 ArtDragVerify 宽度、颜色异常bug
|
||||
- 修复 iframe 页面混合模式、双列模式异常bug
|
||||
- 优化锁屏页面被 el-loading 穿透bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.6 - 2025-06-23
|
||||
|
||||
#### 重构
|
||||
|
||||
- 重构 components/core/forms 文件夹下的表单相关组件,提升可维护性与一致性
|
||||
- 重构 ArtBreadcrumb 面包屑导航组件,优化逻辑结构与样式
|
||||
- 重构 ArtFireworksEffect 烟花效果组件,显著提升渲染性能与动画流畅度
|
||||
|
||||
#### 优化
|
||||
|
||||
- 优化 ArtChatWindow 与 ArtFastEnter 组件代码,提升可读性与性能
|
||||
|
||||
#### 新增
|
||||
|
||||
- README 文档新增官方网站链接,便于用户查看项目文档
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.5 - 2025-06-22
|
||||
|
||||
#### 重构
|
||||
|
||||
- 重构图表组件,优化代码结构与可维护性
|
||||
- 精细调整图表动画与主题配色方案,提升视觉一致性
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.4 - 2025-06-18
|
||||
|
||||
#### 重构
|
||||
|
||||
- ArtMenuRight 组件重构
|
||||
- components/core/cards 下面的组件重构,代码优化
|
||||
|
||||
#### 优化
|
||||
|
||||
- ArtWatermark 增加类型注释
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.3 - 2025-06-18
|
||||
|
||||
#### 重构
|
||||
|
||||
- ArtResultPage 组件重构
|
||||
|
||||
#### 优化
|
||||
|
||||
- ArtTextScroll 组件代码优化
|
||||
- ArtException 组件增加类型提示
|
||||
- ArtCutterImg 组件样式优化、增加类型定义
|
||||
- ArtVideoPlayer 组件增加类型定义
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.2 - 2025-06-16
|
||||
|
||||
#### 重构
|
||||
|
||||
- 返回顶部组件重构
|
||||
- 图标选择器组件重构
|
||||
|
||||
#### 修改
|
||||
|
||||
- 系统Logo组件属性变更
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.1 - 2025-06-16
|
||||
|
||||
#### 重构
|
||||
|
||||
- 横幅组件重构以及优化
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复混合菜单下第一个菜单是嵌套菜单跳转bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2 - 2025-06-14
|
||||
|
||||
#### 重构
|
||||
|
||||
- 重构网络请求模块,增强错误处理、类型安全与多语言支持
|
||||
|
||||
#### 新增
|
||||
|
||||
- 路由配置新增 activePath 激活菜单路径属性
|
||||
|
||||
#### 优化
|
||||
|
||||
- 引导、列设置多语言完善
|
||||
- 去除用户列表、菜单管理页面无效代码
|
||||
- 更新技术支持链接
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复移动端搜索栏无法滚动、iPad端页面滚动异常问题
|
||||
- 修复 el-dialog 启用 draggable 属性后,自定义动画失效的问题
|
||||
- 修复 2.3.0 版本本地存储重构后,导致登录、注册等页面多语言设置无法持久化的问题
|
||||
- 修复表格固定列不起作用bug
|
||||
- 修复根路径 / 与 HOME_PAGE_PATH 同为 / 时出现的无限重定向
|
||||
|
||||
#### ⚠️ 重要提示
|
||||
|
||||
> 此版本需要重新登录
|
||||
|
||||
---
|
||||
|
||||
### v2.4.1.1 - 2025-06-07
|
||||
|
||||
#### 优化
|
||||
|
||||
- 优化角色管理页面代码
|
||||
- el-dialog视觉效果优化,支持配置线条
|
||||
- 系统主题模式从Light改成跟随系统模式
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复菜单管理折叠 bug
|
||||
- 修复表格数据为空高度无限变大bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.1 - 2025-06-07
|
||||
|
||||
#### 新增
|
||||
|
||||
- 锁屏状态下禁止使用开发者工具破解锁屏
|
||||
|
||||
#### 优化
|
||||
|
||||
- 提升菜单操作跟手感
|
||||
- 页面入场动画时间减少0.04s
|
||||
- Echarts 图表性能优化,新增可视区域初始化、内存泄漏防护、防抖处理
|
||||
|
||||
#### 修复
|
||||
|
||||
- 修复 Echarts 图表组件在弹窗中不显示的 bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.0 - 2025-06-06 🎉
|
||||
|
||||
#### 重构
|
||||
|
||||
- 全局 TypeScript 类型体系重构,提升类型准确性与可维护性
|
||||
- 重构 utils 工具包,统一工具方法结构,增强可读性与复用性
|
||||
|
||||
#### 新增
|
||||
|
||||
- utils 新增表单验证与 Cookie 操作相关工具函数
|
||||
- 页面组件增加 defineOptions,明确组件命名
|
||||
- HTTP 请求增加 token 过期自动处理逻辑,提升安全性与用户体验
|
||||
|
||||
#### 优化
|
||||
|
||||
- 删除未使用的工具模块与无效资源,精简项目体积
|
||||
- 优化 views 页面结构,移除冗余页面文件
|
||||
- 异常页面多语言支持, 提升国际化体验
|
||||
- 图片资源统一转换为 webp 格式,整体资源体积减少约 50%
|
||||
- 打包产物减少约 1MB,提高加载效率
|
||||
|
||||
#### ⚠️ 重要提示
|
||||
|
||||
> 此版本需要重新登录
|
||||
|
||||
---
|
||||
|
||||
## 版本说明
|
||||
|
||||
### 版本号规则
|
||||
|
||||
本项目遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/) 规范:
|
||||
|
||||
- **主版本号(Major)**:当你做了不兼容的 API 修改
|
||||
- **次版本号(Minor)**:当你做了向下兼容的功能性新增
|
||||
- **修订号(Patch)**:当你做了向下兼容的问题修正
|
||||
|
||||
### 变更类型
|
||||
|
||||
- **新增(Added)**:新功能
|
||||
- **修改(Changed)**:对现有功能的变更
|
||||
- **弃用(Deprecated)**:即将移除的功能
|
||||
- **移除(Removed)**:已移除的功能
|
||||
- **修复(Fixed)**:Bug 修复
|
||||
- **安全(Security)**:安全相关的修复
|
||||
- **优化(Optimized)**:性能优化或体验优化
|
||||
- **重构(Refactored)**:代码重构
|
||||
- **升级(Upgraded)**:依赖升级
|
||||
|
||||
### 重要提示标记
|
||||
|
||||
- 🎉 **重大更新**:包含重要新特性的版本
|
||||
- ⚠️ **破坏性变更**:需要特别注意的不兼容更新
|
||||
- 🔒 **安全更新**:修复安全漏洞的版本
|
||||
|
||||
---
|
||||
|
||||
## 贡献指南
|
||||
|
||||
如果你想为项目做出贡献,请查看 [CONTRIBUTING.md](./CONTRIBUTING.md)。
|
||||
|
||||
## 许可证
|
||||
|
||||
[MIT License](./LICENSE)
|
||||
|
||||
---
|
||||
|
||||
**注意**:本更新日志从 v2.4.0 版本开始记录。更早的版本历史请查看 Git 提交记录。
|
||||
|
||||
---
|
||||
|
||||
[English Version](./CHANGELOG.en.md)
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 SuperManTT
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,104 @@
|
||||
<img src="https://www.qiniu.lingchen.kim/github-cover-light6.webp" />
|
||||
|
||||
<br />
|
||||
<h1 align="center">Art Design Pro</h1>
|
||||
<p align="center">A backend system template that combines design aesthetics with efficient development, helping you quickly build professional-grade applications</p>
|
||||
<div align="center">English | <a href="./README.zh-CN.md">简体中文</a></div>
|
||||
|
||||
<br />
|
||||
<div align="center">
|
||||
|
||||
[](./LICENSE) [](https://github.com/Daymychen/art-design-pro/stargazers) [](https://github.com/Daymychen/art-design-pro/network/members)
|
||||
|
||||
</div>
|
||||
<br />
|
||||
|
||||
## What makes this project special?
|
||||
|
||||
**Interface Design**: Modern UI design with smooth interactions, focusing on user experience and visual design
|
||||
|
||||
**Quick Start**: Clean architecture + comprehensive documentation, easy for backend developers to use
|
||||
|
||||
**Rich Components**: Built-in high-quality components for data display, forms, and more to meet different business scenarios
|
||||
|
||||
**Smooth Interactions**: Button clicks, theme switching, page transitions, chart animations - experience comparable to commercial products
|
||||
|
||||
**Efficient Development**: Built-in practical APIs like useTable and ArtForm to significantly improve development efficiency
|
||||
|
||||
**Clean Scripts**: Built-in one-click cleanup script to quickly remove demo data and get a ready-to-develop base project
|
||||
|
||||
## Tech Stack
|
||||
|
||||
Development Framework: Vue3, TypeScript, Vite, Element-Plus, Tailwind CSS
|
||||
|
||||
Code Standards: Eslint, Prettier, Stylelint, Husky, Lint-staged, cz-git
|
||||
|
||||
## Preview
|
||||
|
||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c1.webp" alt="Light Theme"/></kbd>
|
||||
|
||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c2.webp" alt="Light Theme"/></kbd>
|
||||
|
||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c4.webp" alt="Dark Theme"/></kbd>
|
||||
|
||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c5.webp" alt="Dark Theme"/></kbd>
|
||||
|
||||
## Quick Access
|
||||
|
||||
[Live Demo](https://www.artd.pro) | [Official Documentation](https://www.artd.pro/docs) | [Changelog](./CHANGELOG.en.md)
|
||||
|
||||
## Installation & Setup
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# If pnpm install fails, try using the command below
|
||||
pnpm install --ignore-scripts
|
||||
|
||||
# Start local development environment
|
||||
pnpm dev
|
||||
|
||||
# Build for production
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Clean Version
|
||||
|
||||
The project includes a cleanup script to quickly remove demo data and provide developers with a ready-to-develop base project
|
||||
|
||||
```bash
|
||||
pnpm clean:dev
|
||||
```
|
||||
|
||||
## Technical Support
|
||||
|
||||
QQ Group: <a href="https://qm.qq.com/cgi-bin/qm/qr?k=Gg6yzZLFaNgmRhK0T5Qcjf7-XcAFWWXm&jump_from=webapi&authKey=YpRKVJQyFKYbGTiKw0GJ/YQXnNF+GdXNZC5beQQqnGZTvuLlXoMO7nw5fNXvmVhA">1038930070</a> (Click the link to join the group chat)
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
Supports modern mainstream browsers including Chrome, Safari, Firefox, and more.
|
||||
|
||||
## Contributing
|
||||
|
||||
We sincerely welcome and appreciate the support of every contributor! Whether you have new ideas, feature suggestions, or code optimizations, you can participate in the following ways:
|
||||
|
||||
Submit Pull Requests: Share your code and help the project grow.
|
||||
|
||||
Create GitHub Issues: Provide bug feedback or new feature suggestions to help us improve together.
|
||||
|
||||
Every contribution you make takes this project one step further! Come join our open source community!
|
||||
|
||||
## Continuous Optimization & Extension
|
||||
|
||||
The project maintains active updates, supports the latest frontend tech stack, is compatible with mainstream frameworks, and ensures long-term stability and extensibility. Community-driven feedback mechanisms allow your needs to be quickly integrated into project iterations.
|
||||
|
||||
## Donation
|
||||
|
||||
If you feel this project has reduced your development costs and solved problems in your work/life, you can support us through the following ways:
|
||||
|
||||
<img src="https://www.qiniu.lingchen.kim/%E7%BB%84%202%402x%202.png" alt="Donation QR Code"/>
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://www.star-history.com/#Daymychen/art-design-pro&Date)
|
||||
@@ -0,0 +1,104 @@
|
||||
<img src="https://www.qiniu.lingchen.kim/github-cover-light6.webp" />
|
||||
|
||||
<br />
|
||||
<h1 align="center">Art Design Pro</h1>
|
||||
<p align="center">一款兼具设计美学与高效开发的后台系统模版,助你快速构建专业级应用</p>
|
||||
<div align="center">简体中文 | <a href="./README.md">English</a></div>
|
||||
|
||||
<br />
|
||||
<div align="center">
|
||||
|
||||
[](./LICENSE) [](https://github.com/Daymychen/art-design-pro/stargazers) [](https://github.com/Daymychen/art-design-pro/network/members)
|
||||
|
||||
</div>
|
||||
<br />
|
||||
|
||||
## 这个项目有什么特别的呢?
|
||||
|
||||
**界面设计**:现代化 UI 设计,流畅交互,以用户体验与视觉设计为核心
|
||||
|
||||
**极速上手**:简洁架构 + 完整文档,后端开发者也能轻松使用
|
||||
|
||||
**丰富组件**:内置数据展示、表单等多种高质量组件,满足不同业务场景的需求
|
||||
|
||||
**丝滑交互**:按钮点击、主题切换、页面过渡、图表动画,体验媲美商业产品
|
||||
|
||||
**高效开发**:内置 useTable、ArtForm 等实用 API,显著提升开发效率
|
||||
|
||||
**精简脚本**:内置一键清理脚本,可快速清理演示数据,立即得到可开发的基础项目
|
||||
|
||||
## 技术栈
|
||||
|
||||
开发框架:Vue3、TypeScript、Vite、Element-Plus、Tailwind CSS
|
||||
|
||||
代码规范:Eslint、Prettier、Stylelint、Husky、Lint-staged、cz-git
|
||||
|
||||
## 预览
|
||||
|
||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c1.webp" alt="浅色主题"/></kbd>
|
||||
|
||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c2.webp" alt="浅色主题"/></kbd>
|
||||
|
||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c4.webp" alt="暗黑主题"/></kbd>
|
||||
|
||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c5.webp" alt="暗黑主题"/></kbd>
|
||||
|
||||
## 快速访问
|
||||
|
||||
[演示地址](https://www.artd.pro) | [官方文档](https://www.artd.pro/docs) | [更新日志](./CHANGELOG.md)
|
||||
|
||||
## 安装运行
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
pnpm install
|
||||
|
||||
# 如果 pnpm install 安装失败,尝试使用下面的命令安装依赖
|
||||
pnpm install --ignore-scripts
|
||||
|
||||
# 本地开发环境启动
|
||||
pnpm dev
|
||||
|
||||
# 生产环境打包
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 精简版本
|
||||
|
||||
项目内置精简脚本,可快速移除项目中的演示数据,让开发者获得一个可快速开发的基础项目
|
||||
|
||||
```bash
|
||||
pnpm clean:dev
|
||||
```
|
||||
|
||||
## 技术支持
|
||||
|
||||
QQ群:<a href="https://qm.qq.com/cgi-bin/qm/qr?k=Gg6yzZLFaNgmRhK0T5Qcjf7-XcAFWWXm&jump_from=webapi&authKey=YpRKVJQyFKYbGTiKw0GJ/YQXnNF+GdXNZC5beQQqnGZTvuLlXoMO7nw5fNXvmVhA">1038930070</a>(点击链接加入群聊)
|
||||
|
||||
## 兼容性
|
||||
|
||||
支持 Chrome、Safari、Firefox 等现代主流浏览器。
|
||||
|
||||
## 贡献
|
||||
|
||||
我们真诚欢迎并感谢每一位贡献者的支持!无论您有新想法、功能建议还是代码优化,都可以通过以下方式参与:
|
||||
|
||||
提交 Pull Request:分享您的代码,助力项目成长。
|
||||
|
||||
创建 GitHub Issue:提出 bug 反馈或新功能建议,让我们一起完善。
|
||||
|
||||
您的每一点贡献都让这个项目更进一步!快来加入我们的开源社区吧!
|
||||
|
||||
## 持续优化与扩展
|
||||
|
||||
项目保持活跃更新,支持最新前端技术栈,兼容主流框架,确保长期稳定性和扩展性。社区驱动的反馈机制,让你的需求快速融入项目迭代。
|
||||
|
||||
## 捐赠
|
||||
|
||||
如果你觉得这个项目为你减少了开发成本、化解了工作 / 生活里的难题,可以通过以下方式支持一下~
|
||||
|
||||
<img src="https://www.qiniu.lingchen.kim/%E7%BB%84%202%402x%202.png" alt="捐赠二维码"/>
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://www.star-history.com/#Daymychen/art-design-pro&Date)
|
||||
@@ -0,0 +1,10 @@
|
||||
# Template source
|
||||
|
||||
- Template: [art-design-pro](https://github.com/Daymychen/art-design-pro)
|
||||
- Tag: `v3.0.2` (commit `d224dd4504e23cdf230b242a7f78eb089efdbee5`, 2026-03-15)
|
||||
- Cloned shallow at the tag and copied here minus `.git` / template CI files.
|
||||
|
||||
Trimmed for @photo/admin (P1b Task 9): demo views + route modules, `src/mock`,
|
||||
apifox mock wiring, unused demo deps (echarts/xlsx/xgplayer/wangeditor/...).
|
||||
Kept: layout, login, i18n, request layer, table/form core components.
|
||||
Upstream changes are re-applied by hand — this directory is not a git subtree.
|
||||
@@ -0,0 +1,24 @@
|
||||
// e2e 全局夹具:幂等种子管理员账号(复用 services/api 官方路径)。
|
||||
// `pnpm --filter @photo/api-cn seed` 读仓库根 .env 的 ADMIN_ACCOUNT/ADMIN_PASSWORD;
|
||||
// 账号已存在则跳过且绝不改写密码(seed-admin.ts 语义),重复跑 e2e 安全。
|
||||
import { execSync } from 'node:child_process'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const repoRoot = fileURLToPath(new URL('../../..', import.meta.url))
|
||||
const envFile = join(repoRoot, '.env')
|
||||
|
||||
export default function globalSetup(): void {
|
||||
if (!existsSync(envFile)) {
|
||||
throw new Error(
|
||||
`[admin e2e] 仓库根 .env 不存在(${envFile}):请参考 .env.example 配置开发环境`
|
||||
)
|
||||
}
|
||||
// shell:true 兼容 Windows(pnpm 是 .cmd)
|
||||
execSync('pnpm --filter @photo/api-cn seed', {
|
||||
cwd: dirname(envFile),
|
||||
stdio: 'inherit',
|
||||
shell: true
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// e2e 辅助:从仓库根 .env 读种子管理员凭据(global-setup 已跑过幂等 seed)。
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const repoRoot = fileURLToPath(new URL('../../..', import.meta.url))
|
||||
|
||||
export interface AdminCredentials {
|
||||
account: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export function adminCredentials(): AdminCredentials {
|
||||
const envPath = join(repoRoot, '.env')
|
||||
if (!existsSync(envPath)) {
|
||||
throw new Error(`[admin e2e] 仓库根 .env 不存在(${envPath})`)
|
||||
}
|
||||
const vars = new Map<string, string>()
|
||||
for (const line of readFileSync(envPath, 'utf-8').split(/\r?\n/)) {
|
||||
const match = /^([A-Z0-9_]+)=(.*)$/.exec(line.trim())
|
||||
if (match) vars.set(match[1], match[2])
|
||||
}
|
||||
const account = vars.get('ADMIN_ACCOUNT') ?? 'admin'
|
||||
const password = vars.get('ADMIN_PASSWORD') ?? 'admin-secret-change-me'
|
||||
return { account, password }
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// 登录/会话 e2e:真实后端(8080)+ admin dev(5176,/v1 代理)。
|
||||
// 覆盖:路由守卫重定向、错误密码本地化报错、正确登录落到相册管理 stub、
|
||||
// 令牌跨刷新持久(pinia persist → localStorage)、用户菜单登出(尽力吊销会话)。
|
||||
// 注意:后端登录限速 15 分钟窗口 5 次失败——本文件只做一次错误密码尝试,
|
||||
// 短时间内反复全量重跑可能触发 AUTH_RATE_LIMITED(属预期行为,等待窗口过去即可)。
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { adminCredentials } from './helpers'
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// 每个用例从干净会话开始(pinia persist 落在 localStorage)
|
||||
await page.goto('/')
|
||||
await page.evaluate(() => localStorage.clear())
|
||||
})
|
||||
|
||||
test('未登录访问受保护路由 → 重定向登录页并携带 redirect', async ({ page }) => {
|
||||
await page.goto('/#/albums/manage')
|
||||
await expect(page.getByText('欢迎回来')).toBeVisible()
|
||||
await expect(page).toHaveURL(/\/auth\/login\?redirect=/)
|
||||
})
|
||||
|
||||
test('错误密码 → 本地化错误提示(AUTH_INVALID_CREDENTIALS)', async ({ page }) => {
|
||||
const { account } = adminCredentials()
|
||||
await page.goto('/#/auth/login')
|
||||
await page.getByPlaceholder('请输入账号').fill(account)
|
||||
await page.getByPlaceholder('请输入密码', { exact: false }).fill('wrong-password-e2e')
|
||||
await page.getByRole('button', { name: '登录' }).click()
|
||||
await expect(page.getByText('账号或密码错误')).toBeVisible()
|
||||
// 仍停留在登录页
|
||||
await expect(page).toHaveURL(/\/auth\/login/)
|
||||
})
|
||||
|
||||
test('正确登录 → 落到相册管理占位页', async ({ page }) => {
|
||||
const { account, password } = adminCredentials()
|
||||
await page.goto('/#/auth/login')
|
||||
await page.getByPlaceholder('请输入账号').fill(account)
|
||||
await page.getByPlaceholder('请输入密码', { exact: false }).fill(password)
|
||||
await page.getByRole('button', { name: '登录' }).click()
|
||||
|
||||
// 首页 '/' 重定向到菜单第一个有效路径(/albums/manage)
|
||||
await expect(page).toHaveURL(/\/albums\/manage/)
|
||||
await expect(page.getByText('相册管理').first()).toBeVisible()
|
||||
await expect(page.getByText('相册管理功能建设中').first()).toBeVisible()
|
||||
})
|
||||
|
||||
test('登录后刷新页面 → 会话令牌持久,受保护路由仍可达', async ({ page }) => {
|
||||
const { account, password } = adminCredentials()
|
||||
await page.goto('/#/auth/login')
|
||||
await page.getByPlaceholder('请输入账号').fill(account)
|
||||
await page.getByPlaceholder('请输入密码', { exact: false }).fill(password)
|
||||
await page.getByRole('button', { name: '登录' }).click()
|
||||
await expect(page).toHaveURL(/\/albums\/manage/)
|
||||
|
||||
// 会话令牌已持久化(pinia-plugin-persistedstate → localStorage,键含版本前缀)
|
||||
const storage = await page.evaluate(() => JSON.stringify(localStorage))
|
||||
expect(storage).toContain('sessionToken')
|
||||
|
||||
// 刷新后不弹回登录页:动态路由重建,受保护页仍加载
|
||||
await page.reload()
|
||||
await expect(page).toHaveURL(/\/albums\/manage/)
|
||||
await expect(page.getByText('相册管理功能建设中').first()).toBeVisible()
|
||||
})
|
||||
|
||||
test('用户菜单登出 → 尽力吊销会话并回到登录页', async ({ page }) => {
|
||||
const { account, password } = adminCredentials()
|
||||
await page.goto('/#/auth/login')
|
||||
await page.getByPlaceholder('请输入账号').fill(account)
|
||||
await page.getByPlaceholder('请输入密码', { exact: false }).fill(password)
|
||||
await page.getByRole('button', { name: '登录' }).click()
|
||||
await expect(page).toHaveURL(/\/albums\/manage/)
|
||||
|
||||
// 头像 hover 弹出用户菜单 → 退出登录 → 确认
|
||||
await page.getByAltText('avatar').hover()
|
||||
await page.getByText('退出登录').click()
|
||||
await page.getByRole('button', { name: '确定' }).click()
|
||||
|
||||
// 用 heading 断言:页面还残留登录成功的 ElNotification(「欢迎回来, ...」文案)
|
||||
await expect(page.getByRole('heading', { name: '欢迎回来' })).toBeVisible({ timeout: 10_000 })
|
||||
await expect(page).toHaveURL(/\/auth\/login/)
|
||||
// 本地会话已清空(user store 持久化键以 -user 结尾;字段保留但令牌为空串)
|
||||
const storedToken = await page.evaluate(() => {
|
||||
const entry = Object.entries(localStorage).find(([key]) => key.endsWith('-user'))
|
||||
return entry ? String(JSON.parse(entry[1]).sessionToken ?? '') : '__missing__'
|
||||
})
|
||||
expect(storedToken).toBe('')
|
||||
})
|
||||
@@ -0,0 +1,83 @@
|
||||
// 从 URL 和路径模块中导入必要的功能
|
||||
import fs from 'fs'
|
||||
import path, { dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
// 从 ESLint 插件中导入推荐配置
|
||||
import pluginJs from '@eslint/js'
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import globals from 'globals'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
// 使用 import.meta.url 获取当前模块的路径
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
// 读取 .auto-import.json 文件的内容,并将其解析为 JSON 对象
|
||||
const autoImportConfig = JSON.parse(
|
||||
fs.readFileSync(path.resolve(__dirname, '.auto-import.json'), 'utf-8')
|
||||
)
|
||||
|
||||
export default [
|
||||
// 指定文件匹配规则
|
||||
{
|
||||
files: ['**/*.{js,mjs,cjs,ts,vue}']
|
||||
},
|
||||
// 指定全局变量和环境
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node
|
||||
}
|
||||
}
|
||||
},
|
||||
// 扩展配置
|
||||
pluginJs.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
...pluginVue.configs['flat/essential'],
|
||||
// 自定义规则
|
||||
{
|
||||
// 针对所有 JavaScript、TypeScript 和 Vue 文件应用以下配置
|
||||
files: ['**/*.{js,mjs,cjs,ts,vue}'],
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
// 合并从 autoImportConfig 中读取的全局变量配置
|
||||
...autoImportConfig.globals,
|
||||
// TypeScript 全局命名空间
|
||||
Api: 'readonly'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
quotes: ['error', 'single'], // 使用单引号
|
||||
semi: ['error', 'never'], // 语句末尾不加分号
|
||||
'no-var': 'error', // 要求使用 let 或 const 而不是 var
|
||||
'@typescript-eslint/no-explicit-any': 'off', // 禁用 any 检查
|
||||
'vue/multi-word-component-names': 'off', // 禁用对 Vue 组件名称的多词要求检查
|
||||
'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允许多个空行
|
||||
'no-unexpected-multiline': 'error' // 禁止空余的多行
|
||||
}
|
||||
},
|
||||
// vue 规则
|
||||
{
|
||||
files: ['**/*.vue'],
|
||||
languageOptions: {
|
||||
parserOptions: { parser: tseslint.parser }
|
||||
}
|
||||
},
|
||||
// 忽略文件
|
||||
{
|
||||
ignores: [
|
||||
'node_modules',
|
||||
'dist',
|
||||
'public',
|
||||
'.vscode/**',
|
||||
'src/assets/**',
|
||||
'src/utils/console.ts'
|
||||
]
|
||||
},
|
||||
// prettier 配置
|
||||
eslintPluginPrettierRecommended
|
||||
]
|
||||
@@ -0,0 +1,47 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Photo Live Admin</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Art Design Pro - A modern admin dashboard template built with Vue 3, TypeScript, and Element Plus."
|
||||
/>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="src/assets/images/favicon.ico" />
|
||||
|
||||
<style>
|
||||
/* 防止页面刷新时白屏的初始样式 */
|
||||
html {
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
background-color: #070707;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 初始化 html class 主题属性
|
||||
;(function () {
|
||||
try {
|
||||
if (typeof Storage === 'undefined' || !window.localStorage) {
|
||||
return
|
||||
}
|
||||
|
||||
const themeType = localStorage.getItem('sys-theme')
|
||||
if (themeType === 'dark') {
|
||||
document.documentElement.classList.add('dark')
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to apply initial theme:', e)
|
||||
}
|
||||
})()
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "@photo/admin",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Staff admin (art-design-pro v3.0.2 template, trimmed): local session auth against /v1/auth/*; template source recorded in SOURCE.md",
|
||||
"//": "Template art-design-pro v3.0.2 (https://github.com/Daymychen/art-design-pro, tag v3.0.2, commit d224dd4). Trim: demo views/routes, src/mock, apifox wiring, echarts/xlsx/xgplayer/wangeditor/highlight.js/file-saver/qrcode.vue/vue-img-cutter + stylelint/husky/commitizen toolchain.",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:e2e": "playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"@iconify/vue": "^5.0.0",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@vue/reactivity": "3.5.22",
|
||||
"@vueuse/core": "13.9.0",
|
||||
"axios": "1.12.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"element-plus": "2.11.4",
|
||||
"mitt": "^3.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"ohash": "^2.0.11",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.3.0",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"vue": "3.5.22",
|
||||
"vue-draggable-plus": "^0.6.0",
|
||||
"vue-i18n": "^9.14.0",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.1",
|
||||
"@playwright/test": "^1.48.0",
|
||||
"@types/node": "^24.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||
"@typescript-eslint/parser": "^8.3.0",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vue/compiler-sfc": "^3.0.5",
|
||||
"eslint": "^9.9.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"globals": "^15.9.0",
|
||||
"prettier": "^3.5.3",
|
||||
"sass": "^1.81.0",
|
||||
"terser": "^5.36.0",
|
||||
"typescript": "~5.6.3",
|
||||
"typescript-eslint": "^8.9.0",
|
||||
"unplugin-auto-import": "^20.2.0",
|
||||
"unplugin-element-plus": "^0.10.0",
|
||||
"unplugin-vue-components": "^29.1.0",
|
||||
"vite": "^7.1.5",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-vue-devtools": "^7.7.6",
|
||||
"vitest": "^4.1.11",
|
||||
"vue-demi": "^0.14.9",
|
||||
"vue-tsc": "~2.1.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
// 同 apps/viewer 约定:webServer 先等 API(8080,仓库根 `pnpm dev:api` 构建+启动,
|
||||
// 已在跑的实例直接复用),再等 admin dev(5176,/v1 代理 → 8080)。
|
||||
// globalSetup 幂等种子管理员(services/api seed:ADMIN_ACCOUNT/ADMIN_PASSWORD,
|
||||
// 账号已存在则跳过且不改写密码)。compose 基础设施需预先 `docker compose up -d`。
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
timeout: 60_000,
|
||||
// dev server 首次按需编译 + 动态路由初始化较慢,放宽断言超时(默认 5s 偏紧)
|
||||
expect: { timeout: 20_000 },
|
||||
fullyParallel: true,
|
||||
reporter: [['list']],
|
||||
use: {
|
||||
baseURL: 'http://127.0.0.1:5176',
|
||||
trace: 'on-first-retry'
|
||||
},
|
||||
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
|
||||
globalSetup: './e2e/global-setup.ts',
|
||||
webServer: [
|
||||
{
|
||||
command: 'pnpm dev:api',
|
||||
cwd: '../..',
|
||||
url: 'http://127.0.0.1:8080/v1/health',
|
||||
reuseExistingServer: true,
|
||||
timeout: 180_000 // 首次含 tsc 全量构建
|
||||
},
|
||||
{
|
||||
command: 'pnpm run dev',
|
||||
url: 'http://127.0.0.1:5176',
|
||||
reuseExistingServer: true,
|
||||
timeout: 120_000 // 首次含依赖预构建
|
||||
}
|
||||
]
|
||||
})
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<ElConfigProvider
|
||||
size="default"
|
||||
:locale="locales[language]"
|
||||
:z-index="3000"
|
||||
:card="{
|
||||
shadow: 'never'
|
||||
}"
|
||||
>
|
||||
<RouterView></RouterView>
|
||||
</ElConfigProvider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useUserStore } from './store/modules/user'
|
||||
import zh from 'element-plus/es/locale/lang/zh-cn'
|
||||
import en from 'element-plus/es/locale/lang/en'
|
||||
import { toggleTransition } from './utils/ui/animation'
|
||||
import { checkStorageCompatibility } from './utils/storage'
|
||||
import { initializeTheme } from './hooks/core/useTheme'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { language } = storeToRefs(userStore)
|
||||
|
||||
const locales = {
|
||||
zh: zh,
|
||||
en: en
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
toggleTransition(true)
|
||||
initializeTheme()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
checkStorageCompatibility()
|
||||
toggleTransition(false)
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 认证接口(对齐 @photo/contracts 的 /v1/auth/* 端点)
|
||||
*
|
||||
* - 登录:POST /v1/auth/password {account, password} → {sessionToken, expiresAt}
|
||||
* - 续期:POST /v1/auth/refresh(Bearer)→ {expiresAt}(单令牌滑动续期,令牌本身不变)
|
||||
* - 注销:POST /v1/auth/logout(Bearer)→ 204 无响应体
|
||||
*
|
||||
* 用户信息无后端端点(契约未定义 /users/me),由本地拼装:
|
||||
* 登录时把账号写入 user store,fetchGetUserInfo 直接读 store,
|
||||
* 避免路由守卫为拿用户信息打一个不存在的接口。
|
||||
*/
|
||||
import request from '@/utils/http'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
/**
|
||||
* 账号密码登录
|
||||
* @param params 登录参数(account + password)
|
||||
* @returns 会话令牌与过期时间
|
||||
*/
|
||||
export function fetchLogin(params: Api.Auth.LoginParams) {
|
||||
return request.post<Api.Auth.LoginResponse>({
|
||||
url: '/v1/auth/password',
|
||||
data: params,
|
||||
// 凭据错误也是 401,但不能触发「会话过期→登出」分支,标记豁免
|
||||
skipUnauthorizedLogout: true,
|
||||
showErrorMessage: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 滑动续期(需 Authorization: Bearer)
|
||||
* @returns 新的过期时间(令牌不变)
|
||||
*/
|
||||
export function fetchRefreshSession() {
|
||||
return request.post<Api.Auth.RefreshResponse>({
|
||||
url: '/v1/auth/refresh',
|
||||
showErrorMessage: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销会话(需 Authorization: Bearer),204 无响应体
|
||||
* 尽力而为:失败也继续本地登出
|
||||
*/
|
||||
export async function fetchLogout(): Promise<void> {
|
||||
await request.del<void>({ url: '/v1/auth/logout', showErrorMessage: false })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息(本地)
|
||||
* 模板守卫在动态路由初始化时调用;后端无对应端点,从 user store
|
||||
* 读取登录时保存的账号拼装,角色约定为 R_ADMIN。
|
||||
*/
|
||||
export async function fetchGetUserInfo(): Promise<Api.Auth.UserInfo> {
|
||||
const userStore = useUserStore()
|
||||
const account = userStore.info?.userName || 'admin'
|
||||
return {
|
||||
userId: 0,
|
||||
userName: account,
|
||||
email: '',
|
||||
buttons: [],
|
||||
roles: ['R_ADMIN']
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 954 B |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 726 B |
|
After Width: | Height: | Size: 944 B |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 810 B |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 514 B |
|
After Width: | Height: | Size: 409 B |
|
After Width: | Height: | Size: 431 B |
|
After Width: | Height: | Size: 439 B |
|
After Width: | Height: | Size: 292 B |
|
After Width: | Height: | Size: 286 B |
|
After Width: | Height: | Size: 293 B |
|
After Width: | Height: | Size: 448 B |
|
After Width: | Height: | Size: 416 B |
|
After Width: | Height: | Size: 509 B |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 400 300" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="94" y="34" width="212" height="233"><path d="M306 34H94v233h212V34Z" fill="#fff"/></mask><g mask="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M234.427 155.64h38.36V69.6h-38.36v86.04ZM113.326 155.64h121.1V69.6h-121.1v86.04Z" fill="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M130.126 155.354h104.2v-72.95h-104.2v72.95ZM236.369 71.05s0 3.3 1.65 5.05c2.33 2.52 7.38-.2 7.38-.2s-1.75 5.15-1.55 10.19c.29 8.24 6.99 9.51 10 4.75 4.56 4.85 8.94-.29 9.52-2.62 4.27 4.76 9.32-.87 9.32-.87v-6.3l-23.99-12.13-12.33 2.13Z" fill="#C7DEFF"/><path fill-rule="evenodd" clip-rule="evenodd" d="M234.429 155.641h-121.1l-15.93 32.11h121.1l15.93-32.11Z" fill="#fff"/><path d="M234.427 69.6h38.46v86.04M113.326 146.52V69.6h121.1M234.429 155.641l-15.93 32.11h-121.1l15.93-32.11h111.39" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M226.37 159.715H116.82l-12.04 23.86H215l11.37-23.86Z" fill="#006EFF"/><path fill-rule="evenodd" clip-rule="evenodd" d="m288.807 187.751-15.92-32.11h-38.46l16.02 32.11h38.36Z" fill="#fff"/><path d="m238.607 163.981 11.84 23.77h38.36l-15.92-32.11h-38.46" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M207.336 223.734c-3.69-13.77-15.44-23.86-29.33-23.86h-8.65s-27.09 14.94-27.09 33.27c0 18.34 25.44 33.18 25.44 33.18h10.4c13.79-.1 25.44-10.19 29.13-23.87 1.75-12.51 0-18.62.1-18.72Z" fill="#C7DEFF"/><path fill-rule="evenodd" clip-rule="evenodd" d="M243.459 240.421c3.98 0 7.28-3.3 7.28-7.27 0-3.98-3.3-7.28-7.28-7.28h-31.08c-3.98 0-7.28 3.3-7.28 7.28 0 3.97 3.3 7.27 7.28 7.27h31.08Z" fill="#C7DEFF"/><path d="M210.342 223.737c-4.08-13.87-16.9-23.96-32.05-23.96H168.972s-29.62 14.94-29.62 33.37 27.87 33.37 27.87 33.37h11.27c15.05-.1 27.77-10.19 31.75-23.96" stroke="#071F4D"/><path d="M212.379 240.421c-3.98 0-7.28-3.3-7.28-7.27m0 0c0-3.98 3.3-7.28 7.28-7.28" stroke="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M168.781 199.777c-18.45 0-33.41 14.94-33.41 33.37s14.96 33.37 33.41 33.37c18.45 0 33.4-14.94 33.4-33.37s-14.95-33.37-33.4-33.37Z" fill="#006EFF"/><path d="M168.781 199.777c-18.45 0-33.41 14.94-33.41 33.37s14.96 33.37 33.41 33.37c18.45 0 33.4-14.94 33.4-33.37s-14.95-33.37-33.4-33.37Z" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M168.775 209.38c-13.14 0-23.79 10.64-23.79 23.77 0 13.12 10.65 23.76 23.79 23.76 13.14 0 23.8-10.64 23.8-23.76 0-13.13-10.66-23.77-23.8-23.77Z" fill="#00E4E5"/><path d="M162.174 223.736a17.48 17.48 0 0 1 14.76-8.05M159.455 231.982c.1-1.36.29-2.62.68-3.88" stroke="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M173.535 209.87c-1.55-.3-3.11-.49-4.76-.49-13.11 0-23.79 10.67-23.79 23.77 0 13.09 10.68 23.76 23.79 23.76 1.65 0 3.21-.19 4.76-.48-10.88-2.23-19.03-11.84-19.03-23.28 0-11.45 8.15-21.05 19.03-23.28Z" fill="#071F4D"/><path d="M219.957 225.774h23.6c4.08 0 7.38 3.3 7.38 7.37m0 0c0 4.08-3.3 7.37-7.38 7.37h-20.1M212.091 225.774h3.3" stroke="#071F4D"/><path d="m248.894 34.485-.19 18.24c0 4.07-.39 5.23-2.14 6.79-8.15 6.88-10.97 9.02-9.22 12.9 1.45 3.2 6.79 2.23 9.61-1.55-.39 4.56-5.24 15.32-.58 18.04 4.37 2.52 6.89-3.49 6.89-3.49s.49 3.49 4.47 3.49c3.69 0 5.24-4.75 5.24-4.75s2.14 3.49 6.22 1.35c3.11-1.55 5.44-7.08 5.44-26.67v-24.35" fill="#fff"/><path d="m248.894 34.485-.19 18.24c0 4.07-.39 5.23-2.14 6.79-8.15 6.88-10.97 9.02-9.22 12.9 1.45 3.2 6.79 2.23 9.61-1.55-.39 4.56-5.24 15.32-.58 18.04 4.37 2.52 6.89-3.49 6.89-3.49s.49 3.49 4.47 3.49c3.69 0 5.24-4.75 5.24-4.75s2.14 3.49 6.22 1.35c3.11-1.55 5.44-7.08 5.44-26.67v-24.35" stroke="#071F4D"/><path d="M255.307 75.71s-.39 5.43-2.04 9.6l2.04-9.6Z" fill="#fff"/><path d="M255.307 75.71s-.39 5.43-2.04 9.6" stroke="#071F4D"/><path d="M264.921 75.323s-.68 5.24-2.04 8.63l2.04-8.63Z" fill="#fff"/><path d="M264.921 75.323s-.68 5.24-2.04 8.63M147.801 34.485v34.92M121.775 34.485v34.92M102.546 204.724v13.97M102.546 222.379v.87M102.546 197.934v3.49M115.268 206.955v26.29M115.268 239.451v5.34M244.43 197.643v11.93M244.43 213.939v3.49M270.359 201.232v33.76M115.369 47.774h-13.6M94.486 47.774h3.4M241.516 47.774h-84.1M280.168 47.774h25.35" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="m282.497 183.575-12.04-23.86h-27.29l11.36 23.86h27.97Z" fill="#00E4E5"/><path d="M234.427 134.88V69.6M234.427 140.412v7.66" stroke="#071F4D"/><path d="M220.831 228.684h16.99M240.934 228.684h2.43" stroke="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="m223.842 187.462 21.46-.2-10.97-20.66-10.49 20.86Z" fill="#071F4D"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg
|
||||
viewBox="0 0 400 300"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><mask id="a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="47" y="38" width="307" height="224"><path d="M353.3 38H47.5v223.8h305.8V38Z" fill="#fff"/></mask><g mask="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M299.2 200.6H61.6v5.1h240.3l-2.7-5.1Z" fill="#C7DEFF"/><path d="m308.9 185.8-6.5 20H183.7M332.3 127.6h10.6l-5 16.7-14.8-.1-7.2 21.1M328.8 127.4l13.6-39.6M307.6 166 337 84.7H180.6l-9.8 26.9h-10.5M296.6 196l4.3-11.8M157.2 149.2l6.4-17.7" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M324.8 93.1H188.5l-34.8 95.8h136.4l34.7-95.8ZM169.9 166.2l5-13.6-5 13.6Z" fill="#fff"/><path d="m169.9 166.2 5-13.6" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M324.8 93.1H188.5l-4 11.7h135.8l4.5-11.7Z" fill="#006EFF"/><path fill-rule="evenodd" clip-rule="evenodd" d="M102.6 159.5h38.3l2.7 36.6h-38.4c-10.1 0-20.9-8.2-20.9-18.3 0-10.1 8.2-18.3 18.3-18.3Z" fill="#DEEBFC"/><path fill-rule="evenodd" clip-rule="evenodd" d="M84.3 174.102c2.5 3.4 10 5 17.9 2.8 16.6-6.5 23.8-3.9 23.8-3.9s.5-3.4 1.3-5c-5.8-3-15.4.3-26.1 3.1-10.7 2.8-15.8-2.5-15.8-2.5-.4 0-1.1 2.8-1.1 5.5Z" fill="#fff"/><path d="M96.5 194.2c-7.2-3.3-12.2-10.5-12.2-19m0 0c0-11.5 9.3-20.8 20.8-20.8h29.4" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M140.3 195.1c-8.4-2.7-14.5-10.6-14.5-19.8l14.5 19.8Zm-14.5-19.8c0-11.5 9.3-20.8 20.8-20.8l-20.8 20.8Zm20.8-20.8c11.5 0 20.8 9.3 20.8 20.8l-20.8-20.8Zm20.8 20.8c0 8.4-5 15.6-12.1 18.9l12.1-18.9Z" fill="#fff"/><path d="M140.3 195.1c-8.4-2.7-14.5-10.6-14.5-19.8m0 0c0-11.5 9.3-20.8 20.8-20.8m0 0c11.5 0 20.8 9.3 20.8 20.8m0 0c0 8.4-5 15.6-12.1 18.9" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M161.5 177.2c0-7.7-6.3-14-14-14s-14 6.3-14 14c0 5.8 3.5 10.8 8.6 12.9.1 0 5.8 1.6 10.7 0 5.3-1.7 8.7-7.1 8.7-12.9Z" fill="#00E4E5"/><path d="M140.5 190.1c-5.8-2.4-9.9-8.2-9.9-14.9 0-8.9 7.2-16.1 16.1-16.1 8.9 0 16.1 7.2 16.1 16.1 0 6.8-4.2 12.5-10.1 14.9M88.4 170.604c2.9 1.3 7.7 2.6 13.6.3 14.7-5.7 22.3-4.3 24.6-3.5M84.5 174.599s5.9 6.5 19 1.7c9.2-3.4 15.3-3.9 18.8-3.8" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M340.6 112.3h-55.2l-2.7 6.2H338l2.6-6.2Z" fill="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M236.8 117.9c-16.13 0-29.2 13.07-29.2 29.2s13.07 29.2 29.2 29.2 29.2-13.07 29.2-29.2-13.07-29.2-29.2-29.2Z" fill="#00E4E5"/><path d="M265 123.3c13.1 13.1 13.1 34.4 0 47.6M306 205.9h19.2M61.7 205.9h32.9M181.2 196.2h115.2M47.5 205.9h10v-9.7h73.8" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M146.7 179.2c-2.49 0-4.5 2.01-4.5 4.5s2.01 4.5 4.5 4.5 4.5-2.01 4.5-4.5-2.01-4.5-4.5-4.5Z" fill="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M169.5 196.2c3.9 0 7.1 3.2 7.1 7.1 0 3.9-3.2 7.1-7.1 7.1H144c-2.1 0-3.9 1.7-3.9 3.9v1c0 2.1 1.7 3.9 3.9 3.9h48c5.1 0 9.2 4.1 9.2 9.2s-4.1 9.3-9.2 9.2h-33.8c-2.3 0-4.1 1.8-4.1 4.1s1.8 4.1 4.1 4.1h4.2c4.4 0 8 3.6 8 8s-3.6 8-8 8H111c-3.7 0-6.8-3-6.8-6.8 0-3.7 3-6.8 6.8-6.8h.3c2.3 0 4.1-1.8 4.1-4.1s-1.8-4.1-4.1-4.1H79c-4.5 0-8.1-3.6-8.1-8.1s3.6-8.1 8.1-8.1h37.7c2.1 0 3.9-1.7 3.9-3.9 0-2.1-1.7-3.9-3.9-3.9h-7.9c-4.4 0-7.9-3.5-7.9-7.9s3.5-7.9 7.9-7.9h30.4c2.2 0 3.9-1.8 3.9-3.9V187c0-1.9 1.6-3.5 3.5-3.5s3.5 1.6 3.5 3.5v5.3c0 2.2 1.8 3.9 3.9 3.9h15.5Z" fill="#006EFF"/><path d="m227.8 138.5 18.7 18.7M227.8 157.2l18.7-18.7" stroke="#fff" stroke-width="6"/><path fill-rule="evenodd" clip-rule="evenodd" d="M194.8 96.9c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8ZM202.9 96.9c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8Z" fill="#fff"/><path d="m291.7 184.3-1.6 4.6h-121M298.1 166.7l22.5-61.9" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="m193 134.1 2.2-5.1h-19.4l-2.3 5.1H193ZM313.2 123.5l2.2-5.1h-24.5l-2.3 5.1h24.6Z" fill="#DEEBFC"/><path d="m164.5 159.2 19.8-54.6" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M199.6 119.8h-53.2l-4.4 9.3h53.2l4.4-9.3Z" fill="#00E4E5"/><path d="M151.3 129.1H142l4.4-9.3h16.9" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M353.3 169.4h-67.4l-4.8 12.2h67.3l4.9-12.2Z" fill="#006EFF"/><path d="M332.4 169.4h20.9l-4.9 12.2h-39.7M242.7 235.5v-4.8c0-3.8 3.1-7 7-7h20.2c3.8 0 7 3.1 7 7" stroke="#071F4D"/><path d="M261.1 235.5v-4.8c0-3.8 3.1-7 7-7h13.7c3.8 0 7 3.1 7 7v4.8M242.6 230.7h13.7M235.2 237.7h63.3M224 237.7h6.7" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M324.1 141.3H335l3.3-10.7h-10.2l-4 10.7Z" fill="#C7DEFF"/><path fill-rule="evenodd" clip-rule="evenodd" d="M288.3 230.4c0-3.6-2.9-6.5-6.5-6.5h-14.2c-3.6 0-6.5 2.9-6.5 6.5v5.3h27.2v-5.3Z" fill="#071F4D"/><path d="M80.4 228.5H83M87.7 228.5h19.2M146.3 195.8v2c0 3.6-2.9 6.6-6.6 6.6H138M133.4 204.3h1.5M154 249.9h9.4" stroke="#DEEBFC"/><path d="m299.4 141.9 5.1-13.9" stroke="#071F4D"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 400 300" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="44" y="42" width="312" height="217"><path d="M355.3 42H44v216.9h311.3V42Z" fill="#fff"/></mask><g mask="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M288.2 248.4h25.1v-30h-25.1v30Z" fill="#C7DEFF"/><path fill-rule="evenodd" clip-rule="evenodd" d="M304.498 238.199c-1.5-3.9-5.9-15.4-4-21.6-2.9.8-3.3.1-5-.1-1.7-.1 0 10.7 2.2 16.4 1.7 4.5 2.1 11.1 2.1 13.6h5.4c.2-1.9.3-5.5-.7-8.3Z" fill="#fff"/><path d="M311.5 214.7v-1.6c0-.7-.6-1.3-1.3-1.3h-22.8c-.7 0-1.3.6-1.3 1.3v1.6" fill="#fff"/><path d="M311.5 214.7v-1.6c0-.7-.6-1.3-1.3-1.3h-22.8c-.7 0-1.3.6-1.3 1.3v1.6M290.2 214.7h21.4c1 0 1.8.8 1.8 1.8v29" stroke="#071F4D" stroke-width="1.096"/><path d="M284.3 245.6v-29c0-1 .8-1.8 1.8-1.8h1.6" fill="#fff"/><path d="M284.3 245.6v-29c0-1 .8-1.8 1.8-1.8h1.6" stroke="#071F4D" stroke-width="1.096"/><path d="M295.402 216.5c-.9 4.2-.4 9.7 2.8 17.5 2.4 5.9 1.9 10.2 1.8 12.3M300.502 216.5c-.9 4.2-.4 9.7 2.8 17.5 2.4 5.9 1.9 10.2 1.8 12.3" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="m331 258.4-.3-5.2H88.5l-1.2 5.2H331Z" fill="#C7DEFF"/><path d="M252.9 248.7H331M216.6 258.4H331M47.1 139.3l-2.6 1.5 42.7 117.6h129.2v-6.6" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="m247.2 248.6-40.4-111.3H50.5l40.3 111.3h156.4Z" fill="#fff"/><path d="m247.2 248.6-40.4-111.3H50.5l40.3 111.3h156.4Z" stroke="#071F4D"/><path d="m203.2 153.2 32.2 88.7H97.8l-32.3-88.7" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M72.2 146.9c-.77 0-1.4.63-1.4 1.4 0 .77.63 1.4 1.4 1.4.77 0 1.4-.63 1.4-1.4 0-.77-.63-1.4-1.4-1.4ZM79.3 146.9c-.77 0-1.4.63-1.4 1.4 0 .77.63 1.4 1.4 1.4.77 0 1.4-.63 1.4-1.4 0-.77-.63-1.4-1.4-1.4Z" fill="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M263.5 171.2h80.3v-63.7h-80.3v63.7Z" fill="#C7DEFF"/><path fill-rule="evenodd" clip-rule="evenodd" d="M290 143.9h-45.6l12.5 51.3H290v-51.3Z" fill="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M286 117.4h-29.3v77.8h92.9v-67.6l-55.9.6-7.7-10.8Z" fill="#00E4E5"/><path d="m332.6 127.6-38.9.6-7.7-10.8h-11.7M308.9 195.2h45.9M250.3 195.2h28.5M287.3 195.2h12.3" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M130.5 211.4H186v-44h-55.5v44Z" fill="#C7DEFF"/><path fill-rule="evenodd" clip-rule="evenodd" d="M148.7 192.5h-31.6l8.7 35.5h22.9v-35.5Z" fill="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M145.9 174.2h-20.2V228h64.1v-46.7l-38.6.4-5.3-7.5Z" fill="#006EFF"/><path d="m179 181.3-27.8.4-5.3-7.5h-7.7M176.2 201.7h19.2M163.2 210.7H195M172.1 228h-54.2M184.8 228h8.1M174.9 228h5.4" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="m293.2 155.7-6.4 6.3 15.3 15.3 22.7-22.6-6.4-6.4-16.3 16.3-8.9-8.9Z" fill="#fff"/><path d="M57.2 258.4h283.6M345.9 258.4h8.1M55.4 258.4h220.5M160.1 118.8l-1.2 2.7M156.7 127c-.3.8-.7 1.8-1.1 2.8M222 68.5c-1 .2-1.9.5-2.9.8M214.1 70.7c-5.8 1.9-11.3 4.4-16.5 7.4M195.4 79.5c-.9.5-1.7 1.1-2.5 1.6M314.2 98.5c-.6-.8-1.3-1.5-2-2.3M308.9 92.8c-4-4-8.3-7.6-13-10.8M293.9 80.7c-.8-.5-1.7-1.1-2.5-1.6" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M251.296 71.203c-3.6-1.5-18.5-2.9-21.8-1.9-1 5.8 4.9 13.5 4.9 13.5s6-9.9 16.9-11.6Z" fill="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M251.3 42.704c-6.5 6.7-7.8 13-8.8 19.3 24.4-1.1 36.3 13 42.8 20 3.2-9.1 7.8-23 7.2-29-7.1-6.4-20-11.7-41.2-10.3Z" fill="#C7DEFF"/><path d="M230 69.3c36.2-3.8 52 21.1 52 21.1s11.4-28.2 10.5-37.4c-7.3-6.5-23.3-12-45.6-10.1-9 6.3-15.6 18.7-16.9 26.4Z" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M161.604 70.7c-6 8.4-9.9 21.9-8.8 33.8 8.4 5.3 32.3 10.5 43.6 11.5 6.1-7.9 15.9-26 15.9-26s-32-4.8-50.7-19.3Z" fill="#C7DEFF"/><path d="M193.103 119.5c4.8-2.7 19.2-29.5 19.2-29.5s-35.8-5.4-53.7-21.8c-9.3 6.1-16.4 24.3-15 40.1 10.6 6.7 45.8 13.3 49.5 11.2Z" stroke="#071F4D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M189.5 111.6c-3 5.2-5.7 7.2-9.8 6.6 12.2 2.6 13.5 1.2 15.6-1.1 2.2-2.4 4.2-6.6 4.2-6.6s-3.1 2.5-10 1.1Z" fill="#071F4D"/><path d="M331 251.8v6.6M77 165.4l-2.7-6.7h7.8M222.8 228.9l2.8 6.6h-7.9" stroke="#071F4D"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,292 @@
|
||||
// 全局样式
|
||||
// 顶部进度条颜色
|
||||
#nprogress .bar {
|
||||
z-index: 2400;
|
||||
background-color: color-mix(in srgb, var(--theme-color) 70%, white);
|
||||
}
|
||||
|
||||
#nprogress .peg {
|
||||
box-shadow:
|
||||
0 0 10px var(--theme-color),
|
||||
0 0 5px var(--theme-color) !important;
|
||||
}
|
||||
|
||||
#nprogress .spinner-icon {
|
||||
border-top-color: var(--theme-color) !important;
|
||||
border-left-color: var(--theme-color) !important;
|
||||
}
|
||||
|
||||
// 处理移动端组件兼容性
|
||||
@media screen and (max-width: 640px) {
|
||||
* {
|
||||
cursor: default !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 背景滤镜
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
--tw-backdrop-blur: ;
|
||||
--tw-backdrop-brightness: ;
|
||||
--tw-backdrop-contrast: ;
|
||||
--tw-backdrop-grayscale: ;
|
||||
--tw-backdrop-hue-rotate: ;
|
||||
--tw-backdrop-invert: ;
|
||||
--tw-backdrop-opacity: ;
|
||||
--tw-backdrop-saturate: ;
|
||||
--tw-backdrop-sepia: ;
|
||||
}
|
||||
|
||||
// 色弱模式
|
||||
.color-weak {
|
||||
filter: invert(80%);
|
||||
-webkit-filter: invert(80%);
|
||||
}
|
||||
|
||||
#noop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// 语言切换选中样式
|
||||
.langDropDownStyle {
|
||||
// 选中项背景颜色
|
||||
.is-selected {
|
||||
background-color: var(--art-el-active-color) !important;
|
||||
}
|
||||
|
||||
// 语言切换按钮菜单样式优化
|
||||
.lang-btn-item {
|
||||
.el-dropdown-menu__item {
|
||||
padding-left: 13px !important;
|
||||
padding-right: 6px !important;
|
||||
margin-bottom: 3px !important;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
.el-dropdown-menu__item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-txt {
|
||||
min-width: 60px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 盒子默认边框
|
||||
.page-content {
|
||||
border: 1px solid var(--art-card-border) !important;
|
||||
}
|
||||
|
||||
@mixin art-card-base($border-color, $shadow: none, $radius-diff: 4px) {
|
||||
background: var(--default-box-color);
|
||||
border: 1px solid #{$border-color} !important;
|
||||
border-radius: calc(var(--custom-radius) + #{$radius-diff}) !important;
|
||||
box-shadow: #{$shadow} !important;
|
||||
|
||||
--el-card-border-color: var(--default-border) !important;
|
||||
}
|
||||
|
||||
.art-card,
|
||||
.art-card-sm,
|
||||
.art-card-xs {
|
||||
border: 1px solid var(--art-card-border);
|
||||
}
|
||||
|
||||
// 盒子边框
|
||||
[data-box-mode='border-mode'] {
|
||||
.page-content,
|
||||
.art-table-card {
|
||||
border: 1px solid var(--art-card-border) !important;
|
||||
}
|
||||
|
||||
.art-card {
|
||||
@include art-card-base(var(--art-card-border), none, 4px);
|
||||
}
|
||||
|
||||
.art-card-sm {
|
||||
@include art-card-base(var(--art-card-border), none, 0px);
|
||||
}
|
||||
|
||||
.art-card-xs {
|
||||
@include art-card-base(var(--art-card-border), none, -4px);
|
||||
}
|
||||
}
|
||||
|
||||
// 盒子阴影
|
||||
[data-box-mode='shadow-mode'] {
|
||||
.page-content,
|
||||
.art-table-card {
|
||||
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.04) !important;
|
||||
border: 1px solid var(--art-gray-200) !important;
|
||||
}
|
||||
|
||||
.layout-sidebar {
|
||||
border-right: 1px solid var(--art-card-border) !important;
|
||||
}
|
||||
|
||||
.art-card {
|
||||
@include art-card-base(
|
||||
var(--art-gray-200),
|
||||
(0 1px 3px 0 rgba(0, 0, 0, 0.03), 0 1px 2px -1px rgba(0, 0, 0, 0.08)),
|
||||
4px
|
||||
);
|
||||
}
|
||||
|
||||
.art-card-sm {
|
||||
@include art-card-base(
|
||||
var(--art-gray-200),
|
||||
(0 1px 3px 0 rgba(0, 0, 0, 0.03), 0 1px 2px -1px rgba(0, 0, 0, 0.08)),
|
||||
2px
|
||||
);
|
||||
}
|
||||
|
||||
.art-card-xs {
|
||||
@include art-card-base(
|
||||
var(--art-gray-200),
|
||||
(0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 1px -1px rgba(0, 0, 0, 0.08)),
|
||||
-4px
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 元素全屏
|
||||
.el-full-screen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100vw !important;
|
||||
height: 100% !important;
|
||||
z-index: 2300;
|
||||
margin-top: 0;
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--default-box-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// 表格卡片
|
||||
.art-table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 12px;
|
||||
border-radius: calc(var(--custom-radius) / 2 + 2px) !important;
|
||||
|
||||
.el-card__body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// 容器全高
|
||||
.art-full-height {
|
||||
height: var(--art-full-height);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@media (max-width: 640px) {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// 徽章样式
|
||||
.art-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 20px;
|
||||
bottom: 0;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin: auto;
|
||||
background: #ff3860;
|
||||
border-radius: 50%;
|
||||
animation: breathe 1.5s ease-in-out infinite;
|
||||
|
||||
&.art-badge-horizontal {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.art-badge-mixed {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.art-badge-dual {
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
bottom: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// 文字徽章样式
|
||||
.art-text-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 12px;
|
||||
bottom: 0;
|
||||
min-width: 20px;
|
||||
height: 18px;
|
||||
line-height: 17px;
|
||||
padding: 0 5px;
|
||||
margin: auto;
|
||||
font-size: 10px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
background: #fd4e4e;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@keyframes breathe {
|
||||
0% {
|
||||
opacity: 0.7;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.7;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 修复老机型 loading 定位问题
|
||||
.art-loading-fix {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.art-loading-fix .el-loading-spinner {
|
||||
position: static !important;
|
||||
top: auto !important;
|
||||
left: auto !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
// 去除移动端点击背景色
|
||||
@media screen and (max-width: 1180px) {
|
||||
* {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 深色主题
|
||||
* 单页面移除深色主题 document.getElementsByTagName("html")[0].removeAttribute('class')
|
||||
*/
|
||||
|
||||
$font-color: rgba(#ffffff, 0.85);
|
||||
|
||||
/* 覆盖element-plus默认深色背景色 */
|
||||
html.dark {
|
||||
// element-plus
|
||||
--el-bg-color: var(--default-box-color);
|
||||
--el-text-color-regular: #{$font-color};
|
||||
|
||||
// 富文本编辑器
|
||||
// 工具栏背景颜色
|
||||
--w-e-toolbar-bg-color: #18191c;
|
||||
// 输入区域背景颜色
|
||||
--w-e-textarea-bg-color: #090909;
|
||||
// 工具栏文字颜色
|
||||
--w-e-toolbar-color: var(--art-gray-600);
|
||||
// 选中菜单颜色
|
||||
--w-e-toolbar-active-bg-color: #25262b;
|
||||
// 弹窗边框颜色
|
||||
--w-e-toolbar-border-color: var(--default-border-dashed);
|
||||
// 分割线颜色
|
||||
--w-e-textarea-border-color: var(--default-border-dashed);
|
||||
// 链接输入框边框颜色
|
||||
--w-e-modal-button-border-color: var(--default-border-dashed);
|
||||
// 表格头颜色
|
||||
--w-e-textarea-slight-bg-color: #090909;
|
||||
// 按钮背景颜色
|
||||
--w-e-modal-button-bg-color: #090909;
|
||||
// hover toolbar 背景颜色
|
||||
--w-e-toolbar-active-color: var(--art-gray-800);
|
||||
}
|
||||
|
||||
.dark {
|
||||
.page-content .article-list .item .left .outer > div {
|
||||
border-right-color: var(--dark-border-color) !important;
|
||||
}
|
||||
|
||||
// 富文本编辑器
|
||||
.editor-wrapper {
|
||||
*:not(pre code *) {
|
||||
color: inherit !important;
|
||||
}
|
||||
}
|
||||
// 分隔线
|
||||
.w-e-bar-divider {
|
||||
background-color: var(--art-gray-300) !important;
|
||||
}
|
||||
|
||||
.w-e-select-list,
|
||||
.w-e-drop-panel,
|
||||
.w-e-bar-item-group .w-e-bar-item-menus-container,
|
||||
.w-e-text-container [data-slate-editor] pre > code {
|
||||
border: 1px solid var(--default-border) !important;
|
||||
}
|
||||
|
||||
// 下拉选择框
|
||||
.w-e-select-list {
|
||||
background-color: var(--default-box-color) !important;
|
||||
}
|
||||
|
||||
/* 下拉选择框 hover 样式调整 */
|
||||
.w-e-select-list ul li:hover,
|
||||
/* 工具栏 hover 按钮背景颜色 */
|
||||
.w-e-bar-item button:hover {
|
||||
background-color: #090909 !important;
|
||||
}
|
||||
|
||||
/* 代码块 */
|
||||
.w-e-text-container [data-slate-editor] pre > code {
|
||||
background-color: #25262b !important;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
/* 引用 */
|
||||
.w-e-text-container [data-slate-editor] blockquote {
|
||||
border-left: 4px solid var(--default-border-dashed) !important;
|
||||
background-color: var(--art-color);
|
||||
}
|
||||
|
||||
.editor-wrapper {
|
||||
.w-e-text-container [data-slate-editor] .table-container th:last-of-type {
|
||||
border-right: 1px solid var(--default-border-dashed) !important;
|
||||
}
|
||||
|
||||
.w-e-modal {
|
||||
background-color: var(--art-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// 导入暗黑主题
|
||||
@use 'element-plus/theme-chalk/src/dark/css-vars.scss' as *;
|
||||
@@ -0,0 +1,34 @@
|
||||
// https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/common/var.scss
|
||||
// 自定义Element 亮色主题
|
||||
|
||||
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
|
||||
$colors: (
|
||||
'white': #ffffff,
|
||||
'black': #000000,
|
||||
'success': (
|
||||
'base': #13deb9
|
||||
),
|
||||
'warning': (
|
||||
'base': #ffae1f
|
||||
),
|
||||
'danger': (
|
||||
'base': #ff4d4f
|
||||
),
|
||||
'error': (
|
||||
'base': #fa896b
|
||||
)
|
||||
),
|
||||
$button: (
|
||||
'hover-bg-color': var(--el-color-primary-light-9),
|
||||
'hover-border-color': var(--el-color-primary),
|
||||
'border-color': var(--el-color-primary),
|
||||
'text-color': var(--el-color-primary)
|
||||
),
|
||||
$messagebox: (
|
||||
'border-radius': '12px'
|
||||
),
|
||||
$popover: (
|
||||
'padding': '14px',
|
||||
'border-radius': '10px'
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,519 @@
|
||||
// 优化 Element Plus 组件库默认样式
|
||||
|
||||
:root {
|
||||
// 系统主色
|
||||
--main-color: var(--el-color-primary);
|
||||
--el-color-white: white !important;
|
||||
--el-color-black: white !important;
|
||||
// 输入框边框颜色
|
||||
// --el-border-color: #E4E4E7 !important; // DCDFE6
|
||||
// 按钮粗度
|
||||
--el-font-weight-primary: 400 !important;
|
||||
|
||||
--el-component-custom-height: 36px !important;
|
||||
|
||||
--el-component-size: var(--el-component-custom-height) !important;
|
||||
|
||||
// 边框、按钮圆角...
|
||||
--el-border-radius-base: calc(var(--custom-radius) / 3 + 2px) !important;
|
||||
|
||||
--el-border-radius-small: calc(var(--custom-radius) / 3 + 4px) !important;
|
||||
--el-messagebox-border-radius: calc(var(--custom-radius) / 3 + 4px) !important;
|
||||
--el-popover-border-radius: calc(var(--custom-radius) / 3 + 4px) !important;
|
||||
|
||||
.region .el-radio-button__original-radio:checked + .el-radio-button__inner {
|
||||
color: var(--theme-color);
|
||||
}
|
||||
}
|
||||
|
||||
// 优化 el-form-item 标签高度
|
||||
.el-form-item__label {
|
||||
height: var(--el-component-custom-height) !important;
|
||||
line-height: var(--el-component-custom-height) !important;
|
||||
}
|
||||
|
||||
// 日期选择器
|
||||
.el-date-range-picker {
|
||||
--el-datepicker-inrange-bg-color: var(--art-gray-200) !important;
|
||||
}
|
||||
|
||||
// el-card 背景色跟系统背景色保持一致
|
||||
html.dark .el-card {
|
||||
--el-card-bg-color: var(--default-box-color) !important;
|
||||
}
|
||||
|
||||
// 修改 el-pagination 大小
|
||||
.el-pagination--default {
|
||||
& {
|
||||
--el-pagination-button-width: 32px !important;
|
||||
--el-pagination-button-height: var(--el-pagination-button-width) !important;
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
& {
|
||||
--el-pagination-button-width: 28px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-select--default .el-select__wrapper {
|
||||
min-height: var(--el-pagination-button-width) !important;
|
||||
}
|
||||
|
||||
.el-pagination__jump .el-input {
|
||||
height: var(--el-pagination-button-width) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-pager li {
|
||||
padding: 0 10px !important;
|
||||
// border: 1px solid red !important;
|
||||
}
|
||||
|
||||
// 优化菜单折叠展开动画(提升动画流畅度)
|
||||
.el-menu.el-menu--inline {
|
||||
transition: max-height 0.26s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
}
|
||||
|
||||
// 优化菜单 item hover 动画(提升鼠标跟手感)
|
||||
.el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
transition: background-color 0s !important;
|
||||
}
|
||||
|
||||
// -------------------------------- 修改 el-size=default 组件默认高度 start --------------------------------
|
||||
// 修改 el-button 高度
|
||||
.el-button--default {
|
||||
height: var(--el-component-custom-height) !important;
|
||||
}
|
||||
|
||||
// circle 按钮宽度优化
|
||||
.el-button--default.is-circle {
|
||||
width: var(--el-component-custom-height) !important;
|
||||
}
|
||||
|
||||
// 修改 el-select 高度
|
||||
.el-select--default {
|
||||
.el-select__wrapper {
|
||||
min-height: var(--el-component-custom-height) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 修改 el-checkbox-button 高度
|
||||
.el-checkbox-button--default .el-checkbox-button__inner,
|
||||
// 修改 el-radio-button 高度
|
||||
.el-radio-button--default .el-radio-button__inner {
|
||||
padding: 10px 15px !important;
|
||||
}
|
||||
// -------------------------------- 修改 el-size=default 组件默认高度 end --------------------------------
|
||||
|
||||
.el-pagination.is-background .btn-next,
|
||||
.el-pagination.is-background .btn-prev,
|
||||
.el-pagination.is-background .el-pager li {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.el-popover {
|
||||
min-width: 80px;
|
||||
border-radius: var(--el-border-radius-small) !important;
|
||||
}
|
||||
|
||||
.el-dialog {
|
||||
border-radius: 100px !important;
|
||||
border-radius: calc(var(--custom-radius) / 1.2 + 2px) !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
.el-dialog__title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 25px 0 !important;
|
||||
position: relative; // 为了兼容 el-pagination 样式,需要设置 relative,不然会影响 el-pagination 的样式,比如 el-pagination__jump--small 会被影响,导致 el-pagination__jump--small 按钮无法点击,详见 URL_ADDRESS.com/element-plus/element-plus/issues/5684#issuecomment-1176299275;
|
||||
}
|
||||
|
||||
.el-dialog.el-dialog-border {
|
||||
.el-dialog__body {
|
||||
// 上边框
|
||||
&::before,
|
||||
// 下边框
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
width: calc(100% + 32px);
|
||||
height: 1px;
|
||||
background-color: var(--art-gray-300);
|
||||
}
|
||||
|
||||
&::before {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// el-message 样式优化
|
||||
.el-message {
|
||||
background-color: var(--default-box-color) !important;
|
||||
border: 0 !important;
|
||||
box-shadow:
|
||||
0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||
0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.05) !important;
|
||||
|
||||
p {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
// 修改 el-dropdown 样式
|
||||
.el-dropdown-menu {
|
||||
padding: 6px !important;
|
||||
border-radius: 10px !important;
|
||||
border: none !important;
|
||||
|
||||
.el-dropdown-menu__item {
|
||||
padding: 6px 16px !important;
|
||||
border-radius: 6px !important;
|
||||
|
||||
&:hover:not(.is-disabled) {
|
||||
color: var(--art-gray-900) !important;
|
||||
background-color: var(--art-el-active-color) !important;
|
||||
}
|
||||
|
||||
&:focus:not(.is-disabled) {
|
||||
color: var(--art-gray-900) !important;
|
||||
background-color: var(--art-gray-200) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏 select、dropdown 的三角
|
||||
.el-select__popper,
|
||||
.el-dropdown__popper {
|
||||
margin-top: -6px !important;
|
||||
|
||||
.el-popper__arrow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.el-dropdown-selfdefine:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
// 处理移动端组件兼容性
|
||||
@media screen and (max-width: 640px) {
|
||||
.el-message-box,
|
||||
.el-dialog {
|
||||
width: calc(100% - 24px) !important;
|
||||
}
|
||||
|
||||
.el-date-picker.has-sidebar.has-time {
|
||||
width: calc(100% - 24px);
|
||||
left: 12px !important;
|
||||
}
|
||||
|
||||
.el-picker-panel *[slot='sidebar'],
|
||||
.el-picker-panel__sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-picker-panel *[slot='sidebar'] + .el-picker-panel__body,
|
||||
.el-picker-panel__sidebar + .el-picker-panel__body {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 修改el-button样式
|
||||
.el-button {
|
||||
&.el-button--text {
|
||||
background-color: transparent !important;
|
||||
padding: 0 !important;
|
||||
|
||||
span {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 修改el-tag样式
|
||||
.el-tag {
|
||||
font-weight: 500;
|
||||
transition: all 0s !important;
|
||||
|
||||
&.el-tag--default {
|
||||
height: 26px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-checkbox-group {
|
||||
&.el-table-filter__checkbox-group label.el-checkbox {
|
||||
height: 17px !important;
|
||||
|
||||
.el-checkbox__label {
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-radio--default {
|
||||
// 优化单选按钮大小
|
||||
.el-radio__input {
|
||||
.el-radio__inner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
&::after {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-checkbox {
|
||||
.el-checkbox__inner {
|
||||
border-radius: 2px !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 优化复选框样式
|
||||
.el-checkbox--default {
|
||||
.el-checkbox__inner {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
border-radius: 4px !important;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
height: 4px !important;
|
||||
top: 5px !important;
|
||||
background-color: #fff !important;
|
||||
transform: scale(0.6) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.is-checked {
|
||||
.el-checkbox__inner {
|
||||
&::after {
|
||||
width: 3px;
|
||||
height: 8px;
|
||||
margin: auto;
|
||||
border: 2px solid var(--el-checkbox-checked-icon-color);
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
transform: translate(-45%, -60%) rotate(45deg) scale(0.86) !important;
|
||||
transform-origin: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-notification .el-notification__icon {
|
||||
font-size: 22px !important;
|
||||
}
|
||||
|
||||
// 修改 el-message-box 样式
|
||||
.el-message-box__headerbtn .el-message-box__close,
|
||||
.el-dialog__headerbtn .el-dialog__close {
|
||||
top: 7px;
|
||||
right: 7px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--art-hover-color) !important;
|
||||
color: var(--art-gray-900) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-message-box {
|
||||
padding: 25px 20px !important;
|
||||
}
|
||||
|
||||
.el-message-box__title {
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
.el-table__column-filter-trigger i {
|
||||
color: var(--theme-color) !important;
|
||||
margin: -3px 0 0 2px;
|
||||
}
|
||||
|
||||
// 去除 el-dropdown 鼠标放上去出现的边框
|
||||
.el-tooltip__trigger:focus-visible {
|
||||
outline: unset;
|
||||
}
|
||||
|
||||
// ipad 表单右侧按钮优化
|
||||
@media screen and (max-width: 1180px) {
|
||||
.el-table-fixed-column--right {
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.login-out-dialog {
|
||||
padding: 30px 20px !important;
|
||||
border-radius: 10px !important;
|
||||
}
|
||||
|
||||
// 修改 dialog 动画
|
||||
.dialog-fade-enter-active {
|
||||
.el-dialog:not(.is-draggable) {
|
||||
animation: dialog-open 0.3s cubic-bezier(0.32, 0.14, 0.15, 0.86);
|
||||
|
||||
// 修复 el-dialog 动画后宽度不自适应问题
|
||||
.el-select__selected-item {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-fade-leave-active {
|
||||
animation: fade-out 0.2s linear;
|
||||
|
||||
.el-dialog:not(.is-draggable) {
|
||||
animation: dialog-close 0.5s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dialog-open {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.2);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dialog-close {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// 遮罩层动画
|
||||
@keyframes fade-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 修改 el-select 样式
|
||||
.el-select__popper:not(.el-tree-select__popper) {
|
||||
.el-select-dropdown__list {
|
||||
padding: 5px !important;
|
||||
|
||||
.el-select-dropdown__item {
|
||||
height: 34px !important;
|
||||
line-height: 34px !important;
|
||||
border-radius: 6px !important;
|
||||
|
||||
&.is-selected {
|
||||
color: var(--art-gray-900) !important;
|
||||
font-weight: 400 !important;
|
||||
background-color: var(--art-el-active-color) !important;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--art-hover-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-select-dropdown__item:hover ~ .is-selected,
|
||||
.el-select-dropdown__item.is-selected:has(~ .el-select-dropdown__item:hover) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 修改 el-tree-select 样式
|
||||
.el-tree-select__popper {
|
||||
.el-select-dropdown__list {
|
||||
padding: 5px !important;
|
||||
|
||||
.el-tree-node {
|
||||
.el-tree-node__content {
|
||||
height: 36px !important;
|
||||
border-radius: 6px !important;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--art-gray-200) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 实现水波纹在文字下面效果
|
||||
.el-button > span {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
// 优化颜色选择器圆角
|
||||
.el-color-picker__color {
|
||||
border-radius: 2px !important;
|
||||
}
|
||||
|
||||
// 优化日期时间选择器底部圆角
|
||||
.el-picker-panel {
|
||||
.el-picker-panel__footer {
|
||||
border-radius: 0 0 var(--el-border-radius-base) var(--el-border-radius-base);
|
||||
}
|
||||
}
|
||||
|
||||
// 优化树型菜单样式
|
||||
.el-tree-node__content {
|
||||
border-radius: 4px;
|
||||
margin-bottom: 4px;
|
||||
padding: 1px 0;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--art-hover-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
|
||||
background-color: var(--art-gray-300) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏折叠菜单弹窗 hover 出现的边框
|
||||
.menu-left-popper:focus-within,
|
||||
.horizontal-menu-popper:focus-within {
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
// 数字输入组件右侧按钮高度跟随自定义组件高度
|
||||
.el-input-number--default.is-controls-right {
|
||||
.el-input-number__decrease,
|
||||
.el-input-number__increase {
|
||||
height: calc((var(--el-component-size) / 2)) !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// sass 混合宏(函数)
|
||||
|
||||
/**
|
||||
* 溢出省略号
|
||||
* @param {Number} 行数
|
||||
*/
|
||||
@mixin ellipsis($rowCount: 1) {
|
||||
@if $rowCount <=1 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
} @else {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: $rowCount;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制用户能否选中文本
|
||||
* @param {String} 类型
|
||||
*/
|
||||
@mixin userSelect($value: none) {
|
||||
user-select: $value;
|
||||
-moz-user-select: $value;
|
||||
-ms-user-select: $value;
|
||||
-webkit-user-select: $value;
|
||||
}
|
||||
|
||||
// 绝对定位居中
|
||||
@mixin absoluteCenter() {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* css3动画
|
||||
*
|
||||
*/
|
||||
@mixin animation(
|
||||
$from: (
|
||||
width: 0px
|
||||
),
|
||||
$to: (
|
||||
width: 100px
|
||||
),
|
||||
$name: mymove,
|
||||
$animate: mymove 2s 1 linear infinite
|
||||
) {
|
||||
-webkit-animation: $animate;
|
||||
-o-animation: $animate;
|
||||
animation: $animate;
|
||||
|
||||
@keyframes #{$name} {
|
||||
from {
|
||||
@each $key, $value in $from {
|
||||
#{$key}: #{$value};
|
||||
}
|
||||
}
|
||||
|
||||
to {
|
||||
@each $key, $value in $to {
|
||||
#{$key}: #{$value};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes #{$name} {
|
||||
from {
|
||||
@each $key, $value in $from {
|
||||
$key: $value;
|
||||
}
|
||||
}
|
||||
|
||||
to {
|
||||
@each $key, $value in $to {
|
||||
$key: $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 圆形盒子
|
||||
@mixin circle($size: 11px, $bg: #fff) {
|
||||
border-radius: 50%;
|
||||
width: $size;
|
||||
height: $size;
|
||||
line-height: $size;
|
||||
text-align: center;
|
||||
background: $bg;
|
||||
}
|
||||
|
||||
// placeholder
|
||||
@mixin placeholder($color: #bbb) {
|
||||
// Firefox
|
||||
&::-moz-placeholder {
|
||||
color: $color;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// Internet Explorer 10+
|
||||
&:-ms-input-placeholder {
|
||||
color: $color;
|
||||
}
|
||||
|
||||
// Safari and Chrome
|
||||
&::-webkit-input-placeholder {
|
||||
color: $color;
|
||||
}
|
||||
|
||||
&:placeholder-shown {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
//背景透明,文字不透明。兼容IE8
|
||||
@mixin betterTransparentize($color, $alpha) {
|
||||
$c: rgba($color, $alpha);
|
||||
$ie_c: ie_hex_str($c);
|
||||
background: rgba($color, 1);
|
||||
background: $c;
|
||||
background: transparent \9;
|
||||
zoom: 1;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#{$ie_c}, endColorstr=#{$ie_c});
|
||||
-ms-filter: 'progid:DXImageTransform.Microsoft.gradient(startColorstr=#{$ie_c}, endColorstr=#{$ie_c})';
|
||||
}
|
||||
|
||||
//添加浏览器前缀
|
||||
@mixin browserPrefix($propertyName, $value) {
|
||||
@each $prefix in -webkit-, -moz-, -ms-, -o-, '' {
|
||||
#{$prefix}#{$propertyName}: $value;
|
||||
}
|
||||
}
|
||||
|
||||
// 边框
|
||||
@mixin border($color: red) {
|
||||
border: 1px solid $color;
|
||||
}
|
||||
|
||||
// 背景滤镜
|
||||
@mixin backdropBlur() {
|
||||
--tw-backdrop-blur: blur(30px);
|
||||
-webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
|
||||
var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
|
||||
var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
|
||||
var(--tw-backdrop-sepia);
|
||||
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
|
||||
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
|
||||
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
/*滚动条*/
|
||||
/*滚动条整体部分,必须要设置*/
|
||||
::-webkit-scrollbar {
|
||||
width: 8px !important;
|
||||
height: 0 !important;
|
||||
}
|
||||
|
||||
/*滚动条的轨道*/
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: var(--art-gray-200);
|
||||
}
|
||||
|
||||
/*滚动条的滑块按钮*/
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 5px;
|
||||
background-color: #cccccc !important;
|
||||
transition: all 0.2s;
|
||||
-webkit-transition: all 0.2s;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #b0abab !important;
|
||||
}
|
||||
|
||||
/*滚动条的上下两端的按钮*/
|
||||
::-webkit-scrollbar-button {
|
||||
height: 0px;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.dark {
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: var(--default-bg-color);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--art-gray-300) !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
@use 'sass:map';
|
||||
|
||||
// === 变量区域 ===
|
||||
$transition: (
|
||||
// 动画持续时间
|
||||
duration: 0.25s,
|
||||
// 滑动动画的移动距离
|
||||
distance: 15px,
|
||||
// 默认缓动函数
|
||||
easing: cubic-bezier(0.25, 0.1, 0.25, 1),
|
||||
// 淡入淡出专用的缓动函数
|
||||
fade-easing: cubic-bezier(0.4, 0, 0.6, 1)
|
||||
);
|
||||
|
||||
// 抽取配置值函数,提高可复用性
|
||||
@function transition-config($key) {
|
||||
@return map.get($transition, $key);
|
||||
}
|
||||
|
||||
// 变量简写
|
||||
$duration: transition-config('duration');
|
||||
$distance: transition-config('distance');
|
||||
$easing: transition-config('easing');
|
||||
$fade-easing: transition-config('fade-easing');
|
||||
|
||||
// === 动画类 ===
|
||||
|
||||
// 淡入淡出动画
|
||||
.fade {
|
||||
&-enter-active,
|
||||
&-leave-active {
|
||||
transition: opacity $duration $fade-easing;
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&-enter-to,
|
||||
&-leave-from {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 滑动动画通用样式
|
||||
@mixin slide-transition($direction) {
|
||||
$distance-x: 0;
|
||||
$distance-y: 0;
|
||||
|
||||
@if $direction == 'left' {
|
||||
$distance-x: -$distance;
|
||||
} @else if $direction == 'right' {
|
||||
$distance-x: $distance;
|
||||
} @else if $direction == 'top' {
|
||||
$distance-y: -$distance;
|
||||
} @else if $direction == 'bottom' {
|
||||
$distance-y: $distance;
|
||||
}
|
||||
|
||||
&-enter-active {
|
||||
transition:
|
||||
opacity $duration $easing,
|
||||
transform $duration $easing;
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
|
||||
&-leave-active {
|
||||
transition:
|
||||
opacity calc($duration * 0.7) $easing,
|
||||
transform calc($duration * 0.7) $easing;
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
|
||||
&-enter-from {
|
||||
opacity: 0;
|
||||
transform: translate3d($distance-x, $distance-y, 0);
|
||||
}
|
||||
|
||||
&-enter-to {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
transform: translate3d(-$distance-x, -$distance-y, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 滑动动画方向类
|
||||
.slide-left {
|
||||
@include slide-transition('left');
|
||||
}
|
||||
.slide-right {
|
||||
@include slide-transition('right');
|
||||
}
|
||||
.slide-top {
|
||||
@include slide-transition('top');
|
||||
}
|
||||
.slide-bottom {
|
||||
@include slide-transition('bottom');
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
@import 'tailwindcss';
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
/* ==================== Light Mode Variables ==================== */
|
||||
:root {
|
||||
/* Base Colors */
|
||||
--art-color: #ffffff;
|
||||
--theme-color: var(--main-color);
|
||||
|
||||
/* Theme Colors - OKLCH Format */
|
||||
--art-primary: oklch(0.7 0.23 260);
|
||||
--art-secondary: oklch(0.72 0.19 231.6);
|
||||
--art-error: oklch(0.73 0.15 25.3);
|
||||
--art-info: oklch(0.58 0.03 254.1);
|
||||
--art-success: oklch(0.78 0.17 166.1);
|
||||
--art-warning: oklch(0.78 0.14 75.5);
|
||||
--art-danger: oklch(0.68 0.22 25.3);
|
||||
|
||||
/* Gray Scale - Light Mode */
|
||||
--art-gray-100: #f9fafb;
|
||||
--art-gray-200: #f2f4f5;
|
||||
--art-gray-300: #e6eaeb;
|
||||
--art-gray-400: #dbdfe1;
|
||||
--art-gray-500: #949eb7;
|
||||
--art-gray-600: #7987a1;
|
||||
--art-gray-700: #4d5875;
|
||||
--art-gray-800: #383853;
|
||||
--art-gray-900: #323251;
|
||||
|
||||
/* Border Colors */
|
||||
--art-card-border: rgba(0, 0, 0, 0.08);
|
||||
|
||||
--default-border: #e2e8ee;
|
||||
--default-border-dashed: #dbdfe9;
|
||||
|
||||
/* Background Colors */
|
||||
--default-bg-color: #fafbfc;
|
||||
--default-box-color: #ffffff;
|
||||
|
||||
/* Hover Color */
|
||||
--art-hover-color: #edeff0;
|
||||
|
||||
/* Active Color */
|
||||
--art-active-color: #f2f4f5;
|
||||
|
||||
/* Element Component Active Color */
|
||||
--art-el-active-color: #f2f4f5;
|
||||
}
|
||||
|
||||
/* ==================== Dark Mode Variables ==================== */
|
||||
.dark {
|
||||
/* Base Colors */
|
||||
--art-color: #000000;
|
||||
|
||||
/* Gray Scale - Dark Mode */
|
||||
--art-gray-100: #110f0f;
|
||||
--art-gray-200: #17171c;
|
||||
--art-gray-300: #393946;
|
||||
--art-gray-400: #505062;
|
||||
--art-gray-500: #73738c;
|
||||
--art-gray-600: #8f8fa3;
|
||||
--art-gray-700: #ababba;
|
||||
--art-gray-800: #c7c7d1;
|
||||
--art-gray-900: #e3e3e8;
|
||||
|
||||
/* Border Colors */
|
||||
--art-card-border: rgba(255, 255, 255, 0.08);
|
||||
|
||||
--default-border: rgba(255, 255, 255, 0.1);
|
||||
--default-border-dashed: #363843;
|
||||
|
||||
/* Background Colors */
|
||||
--default-bg-color: #070707;
|
||||
--default-box-color: #161618;
|
||||
|
||||
/* Hover Color */
|
||||
--art-hover-color: #252530;
|
||||
|
||||
/* Active Color */
|
||||
--art-active-color: #202226;
|
||||
|
||||
/* Element Component Active Color */
|
||||
--art-el-active-color: #2e2e38;
|
||||
}
|
||||
|
||||
/* ==================== Tailwind Theme Configuration ==================== */
|
||||
@theme {
|
||||
/* Box Color (Light: white / Dark: black) */
|
||||
--color-box: var(--default-box-color);
|
||||
|
||||
/* System Theme Color */
|
||||
--color-theme: var(--theme-color);
|
||||
|
||||
/* Hover Color */
|
||||
--color-hover-color: var(--art-hover-color);
|
||||
|
||||
/* Active Color */
|
||||
--color-active-color: var(--art-active-color);
|
||||
|
||||
/* Active Color */
|
||||
--color-el-active-color: var(--art-active-color);
|
||||
|
||||
/* ElementPlus Theme Colors */
|
||||
--color-primary: var(--art-primary);
|
||||
--color-secondary: var(--art-secondary);
|
||||
--color-error: var(--art-error);
|
||||
--color-info: var(--art-info);
|
||||
--color-success: var(--art-success);
|
||||
--color-warning: var(--art-warning);
|
||||
--color-danger: var(--art-danger);
|
||||
|
||||
/* Gray Scale Colors (Auto-adapts to dark mode) */
|
||||
--color-g-100: var(--art-gray-100);
|
||||
--color-g-200: var(--art-gray-200);
|
||||
--color-g-300: var(--art-gray-300);
|
||||
--color-g-400: var(--art-gray-400);
|
||||
--color-g-500: var(--art-gray-500);
|
||||
--color-g-600: var(--art-gray-600);
|
||||
--color-g-700: var(--art-gray-700);
|
||||
--color-g-800: var(--art-gray-800);
|
||||
--color-g-900: var(--art-gray-900);
|
||||
}
|
||||
|
||||
/* ==================== Custom Border Radius Utilities ==================== */
|
||||
@utility rounded-custom-xs {
|
||||
border-radius: calc(var(--custom-radius) / 2);
|
||||
}
|
||||
|
||||
@utility rounded-custom-sm {
|
||||
border-radius: calc(var(--custom-radius) / 2 + 2px);
|
||||
}
|
||||
|
||||
/* ==================== Custom Utility Classes ==================== */
|
||||
@layer utilities {
|
||||
/* Flexbox Layout Utilities */
|
||||
.flex-c {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.flex-b {
|
||||
@apply flex justify-between;
|
||||
}
|
||||
|
||||
.flex-cc {
|
||||
@apply flex items-center justify-center;
|
||||
}
|
||||
|
||||
.flex-cb {
|
||||
@apply flex items-center justify-between;
|
||||
}
|
||||
|
||||
/* Transition Utilities */
|
||||
.tad-200 {
|
||||
@apply transition-all duration-200;
|
||||
}
|
||||
|
||||
.tad-300 {
|
||||
@apply transition-all duration-300;
|
||||
}
|
||||
|
||||
/* Border Utilities */
|
||||
.border-full-d {
|
||||
@apply border border-[var(--default-border)];
|
||||
}
|
||||
|
||||
.border-b-d {
|
||||
@apply border-b border-[var(--default-border)];
|
||||
}
|
||||
|
||||
.border-t-d {
|
||||
@apply border-t border-[var(--default-border)];
|
||||
}
|
||||
|
||||
.border-l-d {
|
||||
@apply border-l border-[var(--default-border)];
|
||||
}
|
||||
|
||||
.border-r-d {
|
||||
@apply border-r border-[var(--default-border)];
|
||||
}
|
||||
|
||||
/* Cursor Utilities */
|
||||
.c-p {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== Custom Component Classes ==================== */
|
||||
@layer components {
|
||||
/* Art Card Header Component */
|
||||
.art-card-header {
|
||||
@apply flex justify-between pr-6 pb-1;
|
||||
|
||||
.title {
|
||||
h4 {
|
||||
@apply text-lg font-medium text-g-900;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply mt-1 text-sm text-g-600;
|
||||
|
||||
span {
|
||||
@apply ml-2 font-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// 定义基础变量
|
||||
$bg-animation-color-light: #000;
|
||||
$bg-animation-color-dark: #fff;
|
||||
$bg-animation-duration: 0.5s;
|
||||
|
||||
html {
|
||||
--bg-animation-color: $bg-animation-color-light;
|
||||
|
||||
&.dark {
|
||||
--bg-animation-color: $bg-animation-color-dark;
|
||||
}
|
||||
|
||||
// View transition styles
|
||||
&::view-transition-old(*) {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
&::view-transition-new(*) {
|
||||
animation: clip $bg-animation-duration ease-in both;
|
||||
}
|
||||
|
||||
&::view-transition-old(root) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&::view-transition-new(root) {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
&::view-transition-old(*) {
|
||||
animation: clip $bg-animation-duration ease-in reverse both;
|
||||
}
|
||||
|
||||
&::view-transition-new(*) {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
&::view-transition-old(root) {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
&::view-transition-new(root) {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 定义动画
|
||||
@keyframes clip {
|
||||
from {
|
||||
clip-path: circle(0% at var(--x) var(--y));
|
||||
}
|
||||
|
||||
to {
|
||||
clip-path: circle(var(--r) at var(--x) var(--y));
|
||||
}
|
||||
}
|
||||
|
||||
// body 相关样式
|
||||
body {
|
||||
background-color: var(--bg-animation-color);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// 主题切换过渡优化,优化除视觉上的不适感
|
||||
.theme-change {
|
||||
* {
|
||||
transition: 0s !important;
|
||||
}
|
||||
|
||||
.el-switch__core,
|
||||
.el-switch__action {
|
||||
transition: all 0.3s !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
|
||||
color: #a6accd;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-section,
|
||||
.hljs-selector-class,
|
||||
.hljs-template-variable,
|
||||
.hljs-deletion {
|
||||
color: #aed07e !important;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #6f747d;
|
||||
}
|
||||
|
||||
.hljs-doctag,
|
||||
.hljs-keyword,
|
||||
.hljs-formula {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.hljs-section,
|
||||
.hljs-name,
|
||||
.hljs-selector-tag,
|
||||
.hljs-deletion,
|
||||
.hljs-subst {
|
||||
color: #c86068;
|
||||
}
|
||||
|
||||
.hljs-literal {
|
||||
color: #56b6c2;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-regexp,
|
||||
.hljs-addition,
|
||||
.hljs-attribute,
|
||||
.hljs-meta-string {
|
||||
color: #abb2bf;
|
||||
}
|
||||
|
||||
.hljs-attribute {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.hljs-function {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.hljs-type {
|
||||
color: #f07178;
|
||||
}
|
||||
|
||||
.hljs-title {
|
||||
color: #82aaff !important;
|
||||
}
|
||||
|
||||
.hljs-built_in,
|
||||
.hljs-class {
|
||||
color: #82aaff;
|
||||
}
|
||||
|
||||
// 括号
|
||||
.hljs-params {
|
||||
color: #a6accd;
|
||||
}
|
||||
|
||||
.hljs-attr,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-number {
|
||||
color: #de7e61;
|
||||
}
|
||||
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-link,
|
||||
.hljs-meta,
|
||||
.hljs-selector-id {
|
||||
color: #61aeee;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// 重置默认样式
|
||||
@use './core/reset.scss';
|
||||
|
||||
// 应用全局样式
|
||||
@use './core/app.scss';
|
||||
|
||||
// Element Plus 样式优化
|
||||
@use './core/el-ui.scss';
|
||||
|
||||
// Element Plus 暗黑主题
|
||||
@use './core/el-dark.scss';
|
||||
|
||||
// 暗黑主题样式优化
|
||||
@use './core/dark.scss';
|
||||
|
||||
// 路由切换动画
|
||||
@use './core/router-transition';
|
||||
|
||||
// 主题切换过渡优化
|
||||
@use './core/theme-change.scss';
|
||||
|
||||
// 主题切换圆形扩散动画
|
||||
@use './core/theme-animation.scss';
|
||||
@@ -0,0 +1,32 @@
|
||||
// 自定义四点旋转SVG
|
||||
export const fourDotsSpinnerSvg = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
|
||||
<style>
|
||||
.spinner {
|
||||
transform-origin: 20px 20px;
|
||||
animation: rotate 1.6s linear infinite;
|
||||
}
|
||||
.dot {
|
||||
fill: var(--theme-color);
|
||||
animation: fade 1.6s infinite;
|
||||
}
|
||||
.dot:nth-child(1) { animation-delay: 0s; }
|
||||
.dot:nth-child(2) { animation-delay: 0.5s; }
|
||||
.dot:nth-child(3) { animation-delay: 1s; }
|
||||
.dot:nth-child(4) { animation-delay: 1.5s; }
|
||||
@keyframes rotate {
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes fade {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
</style>
|
||||
<g class="spinner">
|
||||
<circle class="dot" cx="20" cy="8" r="4"/>
|
||||
<circle class="dot" cx="32" cy="20" r="4"/>
|
||||
<circle class="dot" cx="20" cy="32" r="4"/>
|
||||
<circle class="dot" cx="8" cy="20" r="4"/>
|
||||
</g>
|
||||
</svg>
|
||||
`
|
||||
@@ -0,0 +1,343 @@
|
||||
<!-- 基础横幅组件 -->
|
||||
<template>
|
||||
<div
|
||||
class="art-card basic-banner"
|
||||
:class="[{ 'has-decoration': decoration }, boxStyle]"
|
||||
:style="{ height }"
|
||||
@click="emit('click')"
|
||||
>
|
||||
<!-- 流星效果 -->
|
||||
<div v-if="meteorConfig?.enabled && isDark" class="basic-banner__meteors">
|
||||
<span
|
||||
v-for="(meteor, index) in meteors"
|
||||
:key="index"
|
||||
class="meteor"
|
||||
:style="{
|
||||
top: '-60px',
|
||||
left: `${meteor.x}%`,
|
||||
animationDuration: `${meteor.speed}s`,
|
||||
animationDelay: `${meteor.delay}s`
|
||||
}"
|
||||
></span>
|
||||
</div>
|
||||
|
||||
<div class="basic-banner__content">
|
||||
<!-- title slot -->
|
||||
<slot name="title">
|
||||
<p v-if="title" class="basic-banner__title" :style="{ color: titleColor }">{{ title }}</p>
|
||||
</slot>
|
||||
|
||||
<!-- subtitle slot -->
|
||||
<slot name="subtitle">
|
||||
<p v-if="subtitle" class="basic-banner__subtitle" :style="{ color: subtitleColor }">{{
|
||||
subtitle
|
||||
}}</p>
|
||||
</slot>
|
||||
|
||||
<!-- button slot -->
|
||||
<slot name="button">
|
||||
<div
|
||||
v-if="buttonConfig?.show"
|
||||
class="basic-banner__button"
|
||||
:style="{
|
||||
backgroundColor: buttonColor,
|
||||
color: buttonTextColor,
|
||||
borderRadius: buttonRadius
|
||||
}"
|
||||
@click.stop="emit('buttonClick')"
|
||||
>
|
||||
{{ buttonConfig?.text }}
|
||||
</div>
|
||||
</slot>
|
||||
|
||||
<!-- default slot -->
|
||||
<slot></slot>
|
||||
|
||||
<!-- background image -->
|
||||
<img
|
||||
v-if="imageConfig.src"
|
||||
class="basic-banner__background-image"
|
||||
:src="imageConfig.src"
|
||||
:style="{ width: imageConfig.width, bottom: imageConfig.bottom, right: imageConfig.right }"
|
||||
loading="lazy"
|
||||
alt="背景图片"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { useSettingStore } from '@/store/modules/setting'
|
||||
const settingStore = useSettingStore()
|
||||
const { isDark } = storeToRefs(settingStore)
|
||||
|
||||
defineOptions({ name: 'ArtBasicBanner' })
|
||||
|
||||
// 流星对象接口定义
|
||||
interface Meteor {
|
||||
/** 流星的水平位置(百分比) */
|
||||
x: number
|
||||
/** 流星划过的速度 */
|
||||
speed: number
|
||||
/** 流星出现的延迟时间 */
|
||||
delay: number
|
||||
}
|
||||
|
||||
// 按钮配置接口定义
|
||||
interface ButtonConfig {
|
||||
/** 是否启用按钮 */
|
||||
show: boolean
|
||||
/** 按钮文本 */
|
||||
text: string
|
||||
/** 按钮背景色 */
|
||||
color?: string
|
||||
/** 按钮文字颜色 */
|
||||
textColor?: string
|
||||
/** 按钮圆角大小 */
|
||||
radius?: string
|
||||
}
|
||||
|
||||
// 流星效果配置接口定义
|
||||
interface MeteorConfig {
|
||||
/** 是否启用流星效果 */
|
||||
enabled: boolean
|
||||
/** 流星数量 */
|
||||
count?: number
|
||||
}
|
||||
|
||||
// 背景图片配置接口定义
|
||||
interface ImageConfig {
|
||||
/** 图片源地址 */
|
||||
src: string
|
||||
/** 图片宽度 */
|
||||
width?: string
|
||||
/** 距底部距离 */
|
||||
bottom?: string
|
||||
/** 距右侧距离 */
|
||||
right?: string // 距右侧距离
|
||||
}
|
||||
|
||||
// 组件属性接口定义
|
||||
interface Props {
|
||||
/** 横幅高度 */
|
||||
height?: string
|
||||
/** 标题文本 */
|
||||
title?: string
|
||||
/** 副标题文本 */
|
||||
subtitle?: string
|
||||
/** 盒子样式 */
|
||||
boxStyle?: string
|
||||
/** 是否显示装饰效果 */
|
||||
decoration?: boolean
|
||||
/** 按钮配置 */
|
||||
buttonConfig?: ButtonConfig
|
||||
/** 流星配置 */
|
||||
meteorConfig?: MeteorConfig
|
||||
/** 图片配置 */
|
||||
imageConfig?: ImageConfig
|
||||
/** 标题颜色 */
|
||||
titleColor?: string
|
||||
/** 副标题颜色 */
|
||||
subtitleColor?: string
|
||||
}
|
||||
|
||||
// 组件属性默认值设置
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
height: '11rem',
|
||||
titleColor: 'white',
|
||||
subtitleColor: 'white',
|
||||
boxStyle: '!bg-theme/60',
|
||||
decoration: true,
|
||||
buttonConfig: () => ({
|
||||
show: true,
|
||||
text: '查看',
|
||||
color: '#fff',
|
||||
textColor: '#333',
|
||||
radius: '6px'
|
||||
}),
|
||||
meteorConfig: () => ({ enabled: false, count: 10 }),
|
||||
imageConfig: () => ({ src: '', width: '12rem', bottom: '-3rem', right: '0' })
|
||||
})
|
||||
|
||||
// 定义组件事件
|
||||
const emit = defineEmits<{
|
||||
(e: 'click'): void // 整体点击事件
|
||||
(e: 'buttonClick'): void // 按钮点击事件
|
||||
}>()
|
||||
|
||||
// 计算按钮样式属性
|
||||
const buttonColor = computed(() => props.buttonConfig?.color ?? '#fff')
|
||||
const buttonTextColor = computed(() => props.buttonConfig?.textColor ?? '#333')
|
||||
const buttonRadius = computed(() => props.buttonConfig?.radius ?? '6px')
|
||||
|
||||
// 流星数据初始化
|
||||
const meteors = ref<Meteor[]>([])
|
||||
onMounted(() => {
|
||||
if (props.meteorConfig?.enabled) {
|
||||
meteors.value = generateMeteors(props.meteorConfig?.count ?? 10)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 生成流星数据数组
|
||||
* @param count 流星数量
|
||||
* @returns 流星数据数组
|
||||
*/
|
||||
function generateMeteors(count: number): Meteor[] {
|
||||
// 计算每个流星的区域宽度
|
||||
const segmentWidth = 100 / count
|
||||
return Array.from({ length: count }, (_, index) => {
|
||||
// 计算流星起始位置
|
||||
const segmentStart = index * segmentWidth
|
||||
// 在区域内随机生成x坐标
|
||||
const x = segmentStart + Math.random() * segmentWidth
|
||||
// 随机决定流星速度快慢
|
||||
const isSlow = Math.random() > 0.5
|
||||
return {
|
||||
x,
|
||||
speed: isSlow ? 5 + Math.random() * 3 : 2 + Math.random() * 2,
|
||||
delay: Math.random() * 5
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.basic-banner {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 0 2rem;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
border-radius: calc(var(--custom-radius) + 2px) !important;
|
||||
|
||||
&__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
margin: 0 0 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&__button {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
min-width: 80px;
|
||||
height: var(--el-component-custom-height);
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
line-height: var(--el-component-custom-height);
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
&__background-image {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -3rem;
|
||||
z-index: 0;
|
||||
width: 12rem;
|
||||
}
|
||||
|
||||
&.has-decoration::after {
|
||||
position: absolute;
|
||||
right: -10%;
|
||||
bottom: -20%;
|
||||
width: 60%;
|
||||
height: 140%;
|
||||
content: '';
|
||||
background: rgb(255 255 255 / 10%);
|
||||
border-radius: 30%;
|
||||
transform: rotate(-20deg);
|
||||
}
|
||||
|
||||
&__meteors {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
|
||||
.meteor {
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 60px;
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
rgb(255 255 255 / 40%),
|
||||
rgb(255 255 255 / 10%),
|
||||
transparent
|
||||
);
|
||||
opacity: 0;
|
||||
transform-origin: top left;
|
||||
animation-name: meteor-fall;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
content: '';
|
||||
background: rgb(255 255 255 / 50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes meteor-fall {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translate(0, -60px) rotate(-45deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translate(400px, 340px) rotate(-45deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 640px) {
|
||||
.basic-banner {
|
||||
box-sizing: border-box;
|
||||
justify-content: flex-start;
|
||||
padding: 16px;
|
||||
|
||||
&__title {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
&__background-image {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.has-decoration::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,114 @@
|
||||
<!-- 卡片横幅组件 -->
|
||||
<template>
|
||||
<div class="art-card-sm flex-c flex-col pb-6" :style="{ height: height }">
|
||||
<div class="flex-c flex-col gap-4 text-center">
|
||||
<div class="w-45">
|
||||
<img :src="image" :alt="title" class="w-full h-full object-contain" />
|
||||
</div>
|
||||
<div class="box-border px-4">
|
||||
<p class="mb-2 text-lg font-semibold text-g-800">{{ title }}</p>
|
||||
<p class="m-0 text-sm text-g-600">{{ description }}</p>
|
||||
</div>
|
||||
<div class="flex-c gap-3">
|
||||
<div
|
||||
v-if="cancelButton?.show"
|
||||
class="inline-block h-9 px-3 text-sm/9 c-p select-none rounded-md border border-g-300"
|
||||
:style="{
|
||||
backgroundColor: cancelButton?.color,
|
||||
color: cancelButton?.textColor
|
||||
}"
|
||||
@click="handleCancel"
|
||||
>
|
||||
{{ cancelButton?.text }}
|
||||
</div>
|
||||
<div
|
||||
v-if="button?.show"
|
||||
class="inline-block h-9 px-3 text-sm/9 c-p select-none rounded-md"
|
||||
:style="{ backgroundColor: button?.color, color: button?.textColor }"
|
||||
@click="handleClick"
|
||||
>
|
||||
{{ button?.text }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 导入默认图标
|
||||
import defaultIcon from '@imgs/3d/icon1.webp'
|
||||
|
||||
defineOptions({ name: 'ArtCardBanner' })
|
||||
|
||||
// 定义卡片横幅组件的属性接口
|
||||
interface CardBannerProps {
|
||||
/** 高度 */
|
||||
height?: string
|
||||
/** 图片路径 */
|
||||
image?: string
|
||||
/** 标题文本 */
|
||||
title: string
|
||||
/** 描述文本 */
|
||||
description: string
|
||||
/** 主按钮配置 */
|
||||
button?: {
|
||||
/** 是否显示 */
|
||||
show?: boolean
|
||||
/** 按钮文本 */
|
||||
text?: string
|
||||
/** 背景颜色 */
|
||||
color?: string
|
||||
/** 文字颜色 */
|
||||
textColor?: string
|
||||
}
|
||||
/** 取消按钮配置 */
|
||||
cancelButton?: {
|
||||
/** 是否显示 */
|
||||
show?: boolean
|
||||
/** 按钮文本 */
|
||||
text?: string
|
||||
/** 背景颜色 */
|
||||
color?: string
|
||||
/** 文字颜色 */
|
||||
textColor?: string
|
||||
}
|
||||
}
|
||||
|
||||
// 定义组件属性默认值
|
||||
withDefaults(defineProps<CardBannerProps>(), {
|
||||
height: '24rem',
|
||||
image: defaultIcon,
|
||||
title: '',
|
||||
description: '',
|
||||
// 主按钮默认配置
|
||||
button: () => ({
|
||||
show: true,
|
||||
text: '查看详情',
|
||||
color: 'var(--theme-color)',
|
||||
textColor: '#fff'
|
||||
}),
|
||||
// 取消按钮默认配置
|
||||
cancelButton: () => ({
|
||||
show: false,
|
||||
text: '取消',
|
||||
color: '#f5f5f5',
|
||||
textColor: '#666'
|
||||
})
|
||||
})
|
||||
|
||||
// 定义组件事件
|
||||
const emit = defineEmits<{
|
||||
(e: 'click'): void // 主按钮点击事件
|
||||
(e: 'cancel'): void // 取消按钮点击事件
|
||||
}>()
|
||||
|
||||
// 主按钮点击处理函数
|
||||
const handleClick = () => {
|
||||
emit('click')
|
||||
}
|
||||
|
||||
// 取消按钮点击处理函数
|
||||
const handleCancel = () => {
|
||||
emit('cancel')
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,40 @@
|
||||
<!-- 返回顶部按钮 -->
|
||||
<template>
|
||||
<Transition
|
||||
enter-active-class="tad-300 ease-out"
|
||||
leave-active-class="tad-200 ease-in"
|
||||
enter-from-class="opacity-0 translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-2"
|
||||
>
|
||||
<div
|
||||
v-show="showButton"
|
||||
class="fixed right-10 bottom-15 size-9.5 flex-cc c-p border border-g-300 rounded-md tad-300 hover:bg-g-200"
|
||||
@click="scrollToTop"
|
||||
>
|
||||
<ArtSvgIcon icon="ri:arrow-up-wide-line" class="text-g-500 text-lg" />
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useCommon } from '@/hooks/core/useCommon'
|
||||
|
||||
defineOptions({ name: 'ArtBackToTop' })
|
||||
|
||||
const { scrollToTop } = useCommon()
|
||||
|
||||
const showButton = ref(false)
|
||||
const scrollThreshold = 300
|
||||
|
||||
onMounted(() => {
|
||||
const scrollContainer = document.getElementById('app-main')
|
||||
if (scrollContainer) {
|
||||
const { y } = useScroll(scrollContainer)
|
||||
watch(y, (newY: number) => {
|
||||
showButton.value = newY > scrollThreshold
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,21 @@
|
||||
<!-- 系统logo -->
|
||||
<template>
|
||||
<div class="flex-cc">
|
||||
<img :style="logoStyle" src="@imgs/common/logo.webp" alt="logo" class="w-full h-full" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'ArtLogo' })
|
||||
|
||||
interface Props {
|
||||
/** logo 大小 */
|
||||
size?: number | string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 36
|
||||
})
|
||||
|
||||
const logoStyle = computed(() => ({ width: `${props.size}px` }))
|
||||
</script>
|
||||
@@ -0,0 +1,24 @@
|
||||
<!-- 图标组件 -->
|
||||
<template>
|
||||
<Icon v-if="icon" :icon="icon" v-bind="bindAttrs" class="art-svg-icon inline" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
|
||||
defineOptions({ name: 'ArtSvgIcon', inheritAttrs: false })
|
||||
|
||||
interface Props {
|
||||
/** Iconify icon name */
|
||||
icon?: string
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const attrs = useAttrs()
|
||||
|
||||
const bindAttrs = computed<{ class: string; style: string }>(() => ({
|
||||
class: (attrs.class as string) || '',
|
||||
style: (attrs.style as string) || ''
|
||||
}))
|
||||
</script>
|
||||
@@ -0,0 +1,74 @@
|
||||
<!-- 数据列表卡片 -->
|
||||
<template>
|
||||
<div class="art-card p-5">
|
||||
<div class="pb-3.5">
|
||||
<p class="text-lg font-medium">{{ title }}</p>
|
||||
<p class="text-sm text-g-600">{{ subtitle }}</p>
|
||||
</div>
|
||||
<ElScrollbar :style="{ height: maxHeight }">
|
||||
<div v-for="(item, index) in list" :key="index" class="flex-c py-3">
|
||||
<div v-if="item.icon" class="flex-cc mr-3 size-10 rounded-lg" :class="item.class">
|
||||
<ArtSvgIcon :icon="item.icon" class="text-xl" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="mb-1 text-sm">{{ item.title }}</div>
|
||||
<div class="text-xs text-g-500">{{ item.status }}</div>
|
||||
</div>
|
||||
<div class="ml-3 text-xs text-g-500">{{ item.time }}</div>
|
||||
</div>
|
||||
</ElScrollbar>
|
||||
<ElButton
|
||||
class="mt-[25px] w-full text-center"
|
||||
v-if="showMoreButton"
|
||||
v-ripple
|
||||
@click="handleMore"
|
||||
>查看更多</ElButton
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'ArtDataListCard' })
|
||||
|
||||
interface Props {
|
||||
/** 数据列表 */
|
||||
list: Activity[]
|
||||
/** 标题 */
|
||||
title: string
|
||||
/** 副标题 */
|
||||
subtitle?: string
|
||||
/** 最大显示数量 */
|
||||
maxCount?: number
|
||||
/** 是否显示更多按钮 */
|
||||
showMoreButton?: boolean
|
||||
}
|
||||
|
||||
interface Activity {
|
||||
/** 标题 */
|
||||
title: string
|
||||
/** 状态 */
|
||||
status: string
|
||||
/** 时间 */
|
||||
time: string
|
||||
/** 样式类名 */
|
||||
class: string
|
||||
/** 图标 */
|
||||
icon: string
|
||||
}
|
||||
|
||||
const ITEM_HEIGHT = 66
|
||||
const DEFAULT_MAX_COUNT = 5
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
maxCount: DEFAULT_MAX_COUNT
|
||||
})
|
||||
|
||||
const maxHeight = computed(() => `${ITEM_HEIGHT * props.maxCount}px`)
|
||||
|
||||
const emit = defineEmits<{
|
||||
/** 点击更多按钮事件 */
|
||||
(e: 'more'): void
|
||||
}>()
|
||||
|
||||
const handleMore = () => emit('more')
|
||||
</script>
|
||||