Compare commits

...
25 Commits
Author SHA1 Message Date
Hooray 20f012323e chore: v5存档 2026-03-31 02:11:01 +08:00
Hooray b661e0e834 chore: release v5.10.1 2026-03-31 01:59:14 +08:00
Hooray 5bf2664a04 chore: 更新依赖 2026-03-31 01:55:54 +08:00
Hooray 3fded8abac chore: types 2026-03-25 22:27:22 +08:00
Hooray d707522b6c fix: 修复 FaSelect options 数组为空报错 2026-03-25 22:12:18 +08:00
Hooray a177b8c6a6 chore: 更新依赖 2026-03-25 22:10:24 +08:00
Hooray ac9b5d5b3b chore: 更新依赖 2026-03-15 02:13:49 +08:00
Hooray f0582cf721 fix: 优化路由守卫逻辑,简化条件判断和重定向处理 2026-03-07 15:12:04 +08:00
Hooray 7be9641d46 chore: release v5.10.0 2026-03-07 12:01:43 +08:00
Hooray 7fb2864072 chore: 更新依赖 2026-03-07 12:00:37 +08:00
Hooray 658b51a747 fix: 修复 overscroll-behavior 属性行为在 chrome 144 变更引起的 bug
https://developer.chrome.com/release-notes/144?hl=zh-cn#respect_overscroll-behavior_on_non-scrollable_scroll_containers
2026-01-22 22:30:20 +08:00
Hooray b5832c3256 chore: release v5.9.0 2025-12-28 04:27:07 +08:00
Hooray ba6eb5480a chore: 更新依赖 2025-12-28 04:26:36 +08:00
Hooray cf73c14561 chore: 更新GitCode星标链接 2025-12-25 13:12:53 +08:00
Hooray e712a75a7f chore: action 2025-12-25 12:06:23 +08:00
Hooray 8ae890213e chore: 更新依赖 2025-12-02 01:47:08 +08:00
Hooray 7cc4f64274 style: 优化明暗切换过渡效果 2025-12-02 01:45:48 +08:00
Hooray 17f0af6542 docs: 更新readme 2025-11-30 06:36:29 +08:00
Hooray 1262fe9e1d refactor: 移除注册表单中的验证码字段 2025-11-10 15:39:15 +08:00
Hooray c9c69464f3 refactor: 移除 FaDrawer / FaModal 组件内置的 FaScrollArea 组件 2025-11-09 19:56:24 +08:00
Hooray 7bcdc1b4cd chore: 更新依赖 2025-11-09 19:30:05 +08:00
7thlayer 69665852a1 fix: 修复FaImagePreview组件更改src值后不重置加载状态问题 2025-11-05 02:11:30 +08:00
Hooray 09807dd115 chore: 更新依赖 2025-10-23 14:25:49 +08:00
hooray0905 4daeb67e19 chore: 更新 readme 2025-10-17 18:08:10 +08:00
Hooray 439d6e9f60 fix: 修复自定义内容不生效 2025-10-10 15:09:27 +08:00
47 changed files with 5097 additions and 4164 deletions
-87
View File
@@ -1,87 +0,0 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: latest
- run: npx changelogithub # or changelogithub@0.12 if ensure the stable result
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
upload-archive:
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Get Release
id: last_release
uses: joutvhu/get-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
latest: true
- name: Create Archive
uses: thedoctor0/zip-release@main
with:
type: zip
filename: fantastic-admin.${{ steps.last_release.outputs.tag_name }}.zip
exclusions: '/.git/* /.github/*'
- name: Upload Archive To Release
uses: xresloader/upload-to-github-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
release_id: ${{ steps.last_release.outputs.id }}
draft: false
file: fantastic-admin.${{ steps.last_release.outputs.tag_name }}.zip
upload-archive-example:
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
ref: example
- name: Get Release
id: last_release
uses: joutvhu/get-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
latest: true
- name: Create Archive
uses: thedoctor0/zip-release@main
with:
type: zip
filename: fantastic-admin.example.${{ steps.last_release.outputs.tag_name }}.zip
exclusions: '/.git/* /.github/* /public/.github/* /public/.nojekyll'
- name: Upload Archive To Release
uses: xresloader/upload-to-github-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
release_id: ${{ steps.last_release.outputs.id }}
draft: false
file: fantastic-admin.example.${{ steps.last_release.outputs.tag_name }}.zip
-23
View File
@@ -1,23 +0,0 @@
name: repo-sync
on:
# 每天 00:05 自动同步
schedule:
- cron: '5 0 * * *'
# 手动触发部署
workflow_dispatch:
jobs:
sync-to-gitee:
runs-on: ubuntu-latest
steps:
- name: Sync to Gitee
uses: wearerequired/git-mirror-action@master
env:
# 注意在 Settings->Secrets 配置 GITEE_RSA_PRIVATE_KEY
SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }}
with:
# 注意替换为你的 GitHub 源仓库地址
source-repo: git@github.com:fantastic-admin/basic.git
# 注意替换为你的 Gitee 目标仓库地址
destination-repo: git@gitee.com:fantastic-admin/basic.git
+9 -8
View File
@@ -1,5 +1,6 @@
{
"eslint.useFlatConfig": true,
"npm.packageManager": "pnpm",
"js/ts.tsdk.path": "node_modules/typescript/lib",
"prettier.enable": false,
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
@@ -7,12 +8,7 @@
"source.fixAll.stylelint": "explicit",
"source.organizeImports": "never"
},
"stylelint.validate": [
"css",
"postcss",
"scss",
"vue"
],
"eslint.useFlatConfig": true,
"eslint.validate": [
"javascript",
"javascriptreact",
@@ -25,5 +21,10 @@
"jsonc",
"yaml"
],
"typescript.tsdk": "node_modules/typescript/lib"
"stylelint.validate": [
"css",
"postcss",
"scss",
"vue"
]
}
+20 -20
View File
@@ -1,12 +1,12 @@
[中文](./README.md) | **English**
<p align="center">
<img src="https://fantastic-admin.hurui.me/logo.svg" width="200" height="200" />
<img src="https://fantastic-admin.hurui.me/logo.svg" width="200" height="200" alt="Fantastic-admin" />
</p>
<h1 align="center">Fantastic-admin</h1>
<p align="center">A <b>out-of-the-box</b> Vue3 back-office management system framework</p>
<p align="center">An <b>out-of-the-box</b> Vue3 management system framework</p>
<p align="center">
<a href="https://fantastic-admin.hurui.me" target="_blank">Official Website</a>
@@ -15,8 +15,8 @@
<p>
<p align="center">
<a href="###"><img src="https://img.shields.io/github/license/fantastic-admin/basic?label=License&style=flat-square" /></a>
<a href="https://github.com/fantastic-admin/basic/releases" target="_blank"><img src="https://img.shields.io/github/v/release/fantastic-admin/basic?label=Version&style=flat-square" /></a>
<a href="###"><img src="https://img.shields.io/github/license/fantastic-admin/basic?label=License&style=flat-square" alt="" /></a>
<a href="https://github.com/fantastic-admin/basic/releases" target="_blank"><img src="https://img.shields.io/github/v/release/fantastic-admin/basic?label=Version&style=flat-square" alt="" /></a>
</p>
## Features
@@ -48,14 +48,14 @@
<table>
<tr>
<td><img src="https://fantastic-admin.hurui.me/preview1.png" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview2.png" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview3.png" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview1.png" alt="" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview2.png" alt="" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview3.png" alt="" /></td>
</tr>
<tr>
<td><img src="https://fantastic-admin.hurui.me/preview4.png" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview5.png" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview6.png" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview4.png" alt="" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview5.png" alt="" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview6.png" alt="" /></td>
</tr>
</table>
@@ -98,14 +98,14 @@ If you think the Fantastic-admin framework is good, or you are already using it,
</th>
</tr>
<tr>
<td><img src="https://one-step-admin.hurui.me/preview1.png" /></td>
<td><img src="https://one-step-admin.hurui.me/preview2.png" /></td>
<td><img src="https://one-step-admin.hurui.me/preview3.png" /></td>
<td><img src="https://one-step-admin.hurui.me/preview1.png" alt="" /></td>
<td><img src="https://one-step-admin.hurui.me/preview2.png" alt="" /></td>
<td><img src="https://one-step-admin.hurui.me/preview3.png" alt="" /></td>
</tr>
<tr>
<td><img src="https://one-step-admin.hurui.me/preview4.png" /></td>
<td><img src="https://one-step-admin.hurui.me/preview5.png" /></td>
<td><img src="https://one-step-admin.hurui.me/preview6.png" /></td>
<td><img src="https://one-step-admin.hurui.me/preview4.png" alt="" /></td>
<td><img src="https://one-step-admin.hurui.me/preview5.png" alt="" /></td>
<td><img src="https://one-step-admin.hurui.me/preview6.png" alt="" /></td>
</tr>
</table>
@@ -121,9 +121,9 @@ If you think the Fantastic-admin framework is good, or you are already using it,
</th>
</tr>
<tr>
<td><img src="https://fantastic-mobile.hurui.me/preview1.png" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview2.png" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview3.png" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview4.png" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview1.png" alt="" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview2.png" alt="" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview3.png" alt="" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview4.png" alt="" /></td>
</tr>
</table>
+24 -22
View File
@@ -1,12 +1,12 @@
**中文** | [English](./README.EN.md)
<p align="center">
<img src="https://fantastic-admin.hurui.me/logo.svg" width="200" height="200" />
<img src="https://fantastic-admin.hurui.me/logo.svg" width="200" height="200" alt="Fantastic-admin" />
</p>
<h1 align="center">Fantastic-admin</h1>
<p align="center">一款<b>开箱即用</b>的 Vue3 中后台管理系统框架</p>
<p align="center">一款<b>开箱即用</b>的 Vue3 管理系统框架</p>
<p align="center">
<a href="https://fantastic-admin.hurui.me" target="_blank">官网</a>
@@ -15,8 +15,8 @@
<p>
<p align="center">
<a href="###"><img src="https://img.shields.io/github/license/fantastic-admin/basic?label=%E5%BC%80%E6%BA%90%E5%8D%8F%E8%AE%AE&style=flat-square" /></a>
<a href="https://github.com/fantastic-admin/basic/releases" target="_blank"><img src="https://img.shields.io/github/v/release/fantastic-admin/basic?label=%E5%BD%93%E5%89%8D%E7%89%88%E6%9C%AC&style=flat-square" /></a>
<a href="###"><img src="https://img.shields.io/github/license/fantastic-admin/basic?label=%E5%BC%80%E6%BA%90%E5%8D%8F%E8%AE%AE&style=flat-square" alt="" /></a>
<a href="https://github.com/fantastic-admin/basic/releases" target="_blank"><img src="https://img.shields.io/github/v/release/fantastic-admin/basic?label=%E5%BD%93%E5%89%8D%E7%89%88%E6%9C%AC&style=flat-square" alt="" /></a>
</p>
## 特点
@@ -48,25 +48,27 @@
<table>
<tr>
<td><img src="https://fantastic-admin.hurui.me/preview1.png" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview2.png" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview3.png" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview1.png" alt="" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview2.png" alt="" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview3.png" alt="" /></td>
</tr>
<tr>
<td><img src="https://fantastic-admin.hurui.me/preview4.png" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview5.png" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview6.png" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview4.png" alt="" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview5.png" alt="" /></td>
<td><img src="https://fantastic-admin.hurui.me/preview6.png" alt="" /></td>
</tr>
</table>
## 支持
如果觉得 Fantastic-admin 这个框架不错,或者已经在使用了,希望你可以在 **Github** 或者 **Gitee(码云)** 帮我点个 ⭐ ,这将对我是极大的鼓励。
如果觉得 Fantastic-admin 这个框架不错,或者已经在使用了,希望你可以在 **Github** / **Gitee** / **GitCode** 帮我点个 ⭐ ,这将对我是极大的鼓励。
[![star](https://img.shields.io/github/stars/fantastic-admin/basic?style=social)](https://github.com/fantastic-admin/basic)
[![star](https://gitee.com/fantastic-admin/basic/badge/star.svg?theme=dark)](https://gitee.com/fantastic-admin/basic)
[![star](https://atomgit.com/fantastic-admin/basic/star/badge.svg)](https://atomgit.com/fantastic-admin/basic)
<details>
<summary>Github Stars 曲线</summary>
@@ -96,18 +98,18 @@
</tr>
<tr>
<th colspan="3" align="center">
一款干啥都快人一步的 Vue 中后台管理系统框架
一款干啥都快人一步的 Vue 后台管理系统框架
</th>
</tr>
<tr>
<td><img src="https://one-step-admin.hurui.me/preview1.png" /></td>
<td><img src="https://one-step-admin.hurui.me/preview2.png" /></td>
<td><img src="https://one-step-admin.hurui.me/preview3.png" /></td>
<td><img src="https://one-step-admin.hurui.me/preview1.png" alt="" /></td>
<td><img src="https://one-step-admin.hurui.me/preview2.png" alt="" /></td>
<td><img src="https://one-step-admin.hurui.me/preview3.png" alt="" /></td>
</tr>
<tr>
<td><img src="https://one-step-admin.hurui.me/preview4.png" /></td>
<td><img src="https://one-step-admin.hurui.me/preview5.png" /></td>
<td><img src="https://one-step-admin.hurui.me/preview6.png" /></td>
<td><img src="https://one-step-admin.hurui.me/preview4.png" alt="" /></td>
<td><img src="https://one-step-admin.hurui.me/preview5.png" alt="" /></td>
<td><img src="https://one-step-admin.hurui.me/preview6.png" alt="" /></td>
</tr>
</table>
@@ -123,9 +125,9 @@
</th>
</tr>
<tr>
<td><img src="https://fantastic-mobile.hurui.me/preview1.png" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview2.png" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview3.png" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview4.png" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview1.png" alt="" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview2.png" alt="" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview3.png" alt="" /></td>
<td><img src="https://fantastic-mobile.hurui.me/preview4.png" alt="" /></td>
</tr>
</table>
+1
View File
@@ -10,6 +10,7 @@ export default antfu(
},
{
rules: {
'e18e/prefer-static-regex': 'off',
'eslint-comments/no-unlimited-disable': 'off',
'curly': ['error', 'all'],
'ts/no-unused-expressions': ['error', {
+62 -61
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"version": "5.8.0",
"packageManager": "pnpm@10.18.1",
"version": "5.10.1",
"packageManager": "pnpm@10.33.0",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
@@ -26,95 +26,96 @@
},
"dependencies": {
"@vee-validate/zod": "^4.15.1",
"@vueuse/components": "^13.9.0",
"@vueuse/core": "^13.9.0",
"@vueuse/integrations": "^13.9.0",
"axios": "^1.12.2",
"@vueuse/components": "^14.2.1",
"@vueuse/core": "^14.2.1",
"@vueuse/integrations": "^14.2.1",
"axios": "^1.14.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.18",
"dayjs": "^1.11.20",
"defu": "^6.1.4",
"disable-devtool": "^0.3.9",
"element-plus": "^2.11.4",
"element-plus": "^2.13.6",
"eruda": "^3.4.3",
"es-toolkit": "^1.39.10",
"filesize": "^11.0.13",
"hotkeys-js": "^3.13.15",
"lucide-vue-next": "^0.545.0",
"es-toolkit": "^1.45.1",
"filesize": "^11.0.15",
"hotkeys-js": "^4.0.2",
"lucide-vue-next": "^1.0.0",
"mitt": "^3.0.1",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"path-to-regexp": "^8.3.0",
"pinia": "^3.0.3",
"qs": "^6.14.0",
"reka-ui": "^2.5.1",
"path-to-regexp": "^8.4.0",
"pinia": "^3.0.4",
"qs": "^6.15.0",
"reka-ui": "^2.9.2",
"scule": "^1.3.0",
"tailwind-merge": "^3.3.1",
"ua-parser-js": "^2.0.5",
"tailwind-merge": "^3.5.0",
"ua-parser-js": "^2.0.9",
"vconsole": "^3.15.1",
"vee-validate": "^4.15.1",
"vue": "^3.5.22",
"vue-router": "^4.5.1",
"vue": "^3.5.31",
"vue-router": "^5.0.4",
"vue-sonner": "^2.0.9",
"zod": "^4.1.12"
"zod": "^4.3.6"
},
"devDependencies": {
"@antfu/eslint-config": "^5.4.1",
"@clack/prompts": "^0.11.0",
"@faker-js/faker": "^10.0.0",
"@iconify/json": "^2.2.392",
"@antfu/eslint-config": "^7.7.3",
"@clack/prompts": "^1.1.0",
"@faker-js/faker": "^10.4.0",
"@iconify/json": "^2.2.456",
"@iconify/vue": "^5.0.0",
"@stylistic/stylelint-config": "^3.0.1",
"@stylistic/stylelint-config": "^5.0.0",
"@types/nprogress": "^0.2.3",
"@types/path-browserify": "^1.0.3",
"@types/qs": "^6.14.0",
"@unocss/eslint-plugin": "^66.5.2",
"@unocss/preset-legacy-compat": "^66.5.2",
"@vitejs/plugin-legacy": "^7.2.1",
"@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue-jsx": "^5.1.1",
"@vue/tsconfig": "^0.8.1",
"autoprefixer": "^10.4.21",
"@types/qs": "^6.15.0",
"@unocss/eslint-plugin": "^66.6.7",
"@unocss/preset-legacy-compat": "^66.6.7",
"@unocss/reset": "^66.6.7",
"@vitejs/plugin-legacy": "^8.0.1",
"@vitejs/plugin-vue": "^6.0.5",
"@vitejs/plugin-vue-jsx": "^5.1.5",
"@vue/tsconfig": "^0.9.1",
"autoprefixer": "^10.4.27",
"boxen": "^8.0.1",
"bumpp": "^10.3.1",
"bumpp": "^11.0.1",
"cz-git": "^1.12.0",
"eslint": "^9.37.0",
"fs-extra": "^11.3.2",
"eslint": "^10.1.0",
"fs-extra": "^11.3.4",
"http-server": "^14.1.1",
"lint-staged": "^16.2.3",
"lint-staged": "^16.4.0",
"npm-run-all2": "^8.0.4",
"picocolors": "^1.1.1",
"plop": "^4.0.4",
"postcss": "^8.5.6",
"plop": "^4.0.5",
"postcss": "^8.5.8",
"postcss-nested": "^7.0.2",
"sass-embedded": "^1.93.2",
"sass-embedded": "^1.98.0",
"simple-git-hooks": "^2.13.1",
"stylelint": "^16.25.0",
"stylelint-config-recess-order": "^7.3.0",
"stylelint-config-standard-scss": "^16.0.0",
"stylelint": "^17.6.0",
"stylelint-config-recess-order": "^7.7.0",
"stylelint-config-standard-scss": "^17.0.0",
"stylelint-config-standard-vue": "^1.0.0",
"stylelint-scss": "^6.12.1",
"svgo": "^4.0.0",
"taze": "^19.7.0",
"tsx": "^4.20.6",
"typescript": "^5.9.3",
"unocss": "^66.5.2",
"unocss-preset-animations": "^1.2.1",
"unplugin-auto-import": "^20.2.0",
"unplugin-turbo-console": "^2.2.0",
"unplugin-vue-components": "^29.1.0",
"vite": "^7.1.9",
"stylelint-scss": "^7.0.0",
"svgo": "^4.0.1",
"taze": "^19.10.0",
"tsx": "^4.21.0",
"typescript": "^6.0.2",
"unocss": "^66.6.7",
"unocss-preset-animations": "^1.3.0",
"unplugin-auto-import": "^21.0.0",
"unplugin-turbo-console": "^2.3.0",
"unplugin-vue-components": "^32.0.0",
"vite": "^8.0.3",
"vite-plugin-app-loading": "^0.4.0",
"vite-plugin-archiver": "^0.2.0",
"vite-plugin-banner": "^0.8.1",
"vite-plugin-compression2": "^2.2.1",
"vite-plugin-compression2": "^2.5.3",
"vite-plugin-env-parse": "^1.0.15",
"vite-plugin-fake-server": "^2.2.0",
"vite-plugin-pages": "^0.33.1",
"vite-plugin-fake-server": "^2.2.3",
"vite-plugin-pages": "^0.33.3",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vue-devtools": "^8.0.2",
"vite-plugin-vue-meta-layouts": "^0.6.0",
"vue-tsc": "^3.1.1"
"vite-plugin-vue-devtools": "^8.1.1",
"vite-plugin-vue-meta-layouts": "^0.6.1",
"vue-tsc": "^3.2.6"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged",
+4682 -3708
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -59,6 +59,12 @@
scrollbar-width: thin;
}
.disable-color-scheme-transition-duration *,
.disable-color-scheme-transition-duration *::before,
.disable-color-scheme-transition-duration *::after {
transition-duration: 0ms !important;
}
html,
body {
height: 100%;
@@ -55,7 +55,7 @@ const onSubmit = form.handleSubmit((values) => {
<FormField v-slot="{ componentField, errors }" name="password">
<FormItem class="relative pb-6 space-y-0">
<FormControl>
<FaInput type="password" placeholder="请输入原密码" class="w-full" :class="errors.length && 'border-destructive'" v-bind="componentField" />
<FaInput type="password" placeholder="请输入原密码" class="w-full" :class="errors.length > 0 && 'border-destructive'" v-bind="componentField" />
</FormControl>
<Transition enter-active-class="transition-opacity" enter-from-class="opacity-0" leave-active-class="transition-opacity" leave-to-class="opacity-0">
<FormMessage class="absolute bottom-1 text-xs" />
@@ -65,7 +65,7 @@ const onSubmit = form.handleSubmit((values) => {
<FormField v-slot="{ componentField, errors }" name="newPassword">
<FormItem class="relative pb-6 space-y-0">
<FormControl>
<FaInput type="password" placeholder="请输入新密码" class="w-full" :class="errors.length && 'border-destructive'" v-bind="componentField" />
<FaInput type="password" placeholder="请输入新密码" class="w-full" :class="errors.length > 0 && 'border-destructive'" v-bind="componentField" />
</FormControl>
<Transition enter-active-class="transition-opacity" enter-from-class="opacity-0" leave-active-class="transition-opacity" leave-to-class="opacity-0">
<FormMessage class="absolute bottom-1 text-xs" />
@@ -75,7 +75,7 @@ const onSubmit = form.handleSubmit((values) => {
<FormField v-slot="{ componentField, errors }" name="checkPassword">
<FormItem class="relative pb-6 space-y-0">
<FormControl>
<FaInput type="password" placeholder="请确认新密码" class="w-full" :class="errors.length && 'border-destructive'" v-bind="componentField" />
<FaInput type="password" placeholder="请确认新密码" class="w-full" :class="errors.length > 0 && 'border-destructive'" v-bind="componentField" />
</FormControl>
<Transition enter-active-class="transition-opacity" enter-from-class="opacity-0" leave-active-class="transition-opacity" leave-to-class="opacity-0">
<FormMessage class="absolute bottom-1 text-xs" />
+2 -2
View File
@@ -83,7 +83,7 @@ function testAccount(account: string) {
<FormField v-slot="{ componentField, errors }" name="account">
<FormItem class="relative pb-6 space-y-0">
<FormControl>
<FaInput type="text" placeholder="请输入用户名" class="w-full" :class="errors.length && 'border-destructive'" v-bind="componentField" />
<FaInput type="text" placeholder="请输入用户名" class="w-full" :class="errors.length > 0 && 'border-destructive'" v-bind="componentField" />
</FormControl>
<Transition enter-active-class="transition-opacity" enter-from-class="opacity-0" leave-active-class="transition-opacity" leave-to-class="opacity-0">
<FormMessage class="absolute bottom-1 text-xs" />
@@ -93,7 +93,7 @@ function testAccount(account: string) {
<FormField v-slot="{ componentField, errors }" name="password">
<FormItem class="relative pb-6 space-y-0">
<FormControl>
<FaInput type="password" placeholder="请输入密码" class="w-full" :class="errors.length && 'border-destructive'" v-bind="componentField" />
<FaInput type="password" placeholder="请输入密码" class="w-full" :class="errors.length > 0 && 'border-destructive'" v-bind="componentField" />
</FormControl>
<Transition enter-active-class="transition-opacity" enter-from-class="opacity-0" leave-active-class="transition-opacity" leave-to-class="opacity-0">
<FormMessage class="absolute bottom-1 text-xs" />
+3 -5
View File
@@ -23,7 +23,6 @@ const form = useForm({
validationSchema: toTypedSchema(
z.object({
account: z.string().min(1, '请输入用户名'),
captcha: z.string().min(6, '请输入验证码'),
password: z.string().min(1, '请输入密码').min(6, '密码长度为6到18位').max(18, '密码长度为6到18位'),
checkPassword: z.string().min(1, '请再次输入密码'),
}).refine(data => data.password === data.checkPassword, {
@@ -33,7 +32,6 @@ const form = useForm({
),
initialValues: {
account: props.account ?? '',
captcha: '',
password: '',
checkPassword: '',
},
@@ -58,7 +56,7 @@ const onSubmit = form.handleSubmit((values) => {
<FormField v-slot="{ componentField, errors }" name="account">
<FormItem class="relative pb-6 space-y-0">
<FormControl>
<FaInput type="text" placeholder="请输入用户名" class="w-full" :class="errors.length && 'border-destructive'" v-bind="componentField" />
<FaInput type="text" placeholder="请输入用户名" class="w-full" :class="errors.length > 0 && 'border-destructive'" v-bind="componentField" />
</FormControl>
<Transition enter-active-class="transition-opacity" enter-from-class="opacity-0" leave-active-class="transition-opacity" leave-to-class="opacity-0">
<FormMessage class="absolute bottom-1 text-xs" />
@@ -68,7 +66,7 @@ const onSubmit = form.handleSubmit((values) => {
<FormField v-slot="{ componentField, value, errors }" name="password">
<FormItem class="relative pb-6 space-y-0">
<FormControl>
<FaInput type="password" placeholder="请输入密码" class="w-full" :class="errors.length && 'border-destructive'" v-bind="componentField" />
<FaInput type="password" placeholder="请输入密码" class="w-full" :class="errors.length > 0 && 'border-destructive'" v-bind="componentField" />
</FormControl>
<FormDescription>
<FaPasswordStrength :password="value" class="mt-2" />
@@ -81,7 +79,7 @@ const onSubmit = form.handleSubmit((values) => {
<FormField v-slot="{ componentField, errors }" name="checkPassword">
<FormItem class="relative pb-6 space-y-0">
<FormControl>
<FaInput type="password" placeholder="请再次输入密码" class="w-full" :class="errors.length && 'border-destructive'" v-bind="componentField" />
<FaInput type="password" placeholder="请再次输入密码" class="w-full" :class="errors.length > 0 && 'border-destructive'" v-bind="componentField" />
</FormControl>
<Transition enter-active-class="transition-opacity" enter-from-class="opacity-0" leave-active-class="transition-opacity" leave-to-class="opacity-0">
<FormMessage class="absolute bottom-1 text-xs" />
@@ -63,7 +63,7 @@ function handleSendCaptcha() {
<FormField v-slot="{ componentField, errors }" name="account">
<FormItem class="relative pb-6 space-y-0">
<FormControl>
<FaInput type="text" placeholder="请输入用户名" class="w-full" :class="errors.length && 'border-destructive'" v-bind="componentField" />
<FaInput type="text" placeholder="请输入用户名" class="w-full" :class="errors.length > 0 && 'border-destructive'" v-bind="componentField" />
</FormControl>
<Transition enter-active-class="transition-opacity" enter-from-class="opacity-0" leave-active-class="transition-opacity" leave-to-class="opacity-0">
<FormMessage class="absolute bottom-1 text-xs" />
@@ -88,7 +88,7 @@ function handleSendCaptcha() {
<FormField v-slot="{ componentField, errors }" name="newPassword">
<FormItem class="relative pb-6 space-y-0">
<FormControl>
<FaInput type="password" placeholder="请输入新密码" class="w-full" :class="errors.length && 'border-destructive'" v-bind="componentField" />
<FaInput type="password" placeholder="请输入新密码" class="w-full" :class="errors.length > 0 && 'border-destructive'" v-bind="componentField" />
</FormControl>
<Transition enter-active-class="transition-opacity" enter-from-class="opacity-0" leave-active-class="transition-opacity" leave-to-class="opacity-0">
<FormMessage class="absolute bottom-1 text-xs" />
+2 -2
View File
@@ -19,7 +19,7 @@ const { switchTo } = useMenu()
<component :is="useSlots('header-start')" />
<Logo class="title" />
<component :is="useSlots('header-after-logo')" />
<FaScrollArea :scrollbar="false" mask horizontal gradient-color="var(--g-header-bg)" class="menu-container h-full flex-1">
<FaScrollArea :scrollbar="false" mask horizontal gradient-color="var(--g-header-bg)" class="menu-container h-full flex-1 overscroll-contain">
<!-- 顶部模式 -->
<div class="menu h-full flex of-hidden transition-all">
<template v-for="(item, index) in menuStore.allMenus" :key="index">
@@ -29,7 +29,7 @@ const { switchTo } = useMenu()
}"
>
<div
v-if="item.children && item.children.length !== 0" class="group menu-item-container relative h-full w-full flex cursor-pointer items-center justify-between gap-1 rounded-lg px-3 text-[var(--g-header-menu-color)] transition-all hover-(bg-[var(--g-header-menu-hover-bg)] text-[var(--g-header-menu-hover-color)])" :class="{
v-if="item.children && item.children.length !== 0" class="group menu-item-container relative h-full w-full flex cursor-pointer items-center justify-between gap-1 rounded-lg px-3 text-[var(--g-header-menu-color)] transition-colors hover-(bg-[var(--g-header-menu-hover-bg)] text-[var(--g-header-menu-hover-color)])" :class="{
'text-[var(--g-header-menu-active-color)]! bg-[var(--g-header-menu-active-bg)]!': index === menuStore.actived,
}" :title="typeof item.meta?.title === 'function' ? item.meta?.title() : item.meta?.title" @click="switchTo(index)"
>
+2 -2
View File
@@ -38,7 +38,7 @@ onUnmounted(() => {
<component :is="useSlots('main-sidebar-top')" />
<Logo :show-title="false" class="sidebar-logo" />
<component :is="useSlots('main-sidebar-after-logo')" />
<FaScrollArea :scrollbar="false" mask gradient-color="var(--g-main-sidebar-bg)" class="menu flex-1">
<FaScrollArea :scrollbar="false" mask gradient-color="var(--g-main-sidebar-bg)" class="menu flex-1 overscroll-contain">
<!-- 侧边栏模式(含主导航) -->
<div class="w-full flex flex-col of-hidden py-1 transition-all -mt-2">
<template v-for="(item, index) in menuStore.allMenus" :key="index">
@@ -48,7 +48,7 @@ onUnmounted(() => {
}"
>
<div
v-if="item.children && item.children.length !== 0" class="group menu-item-container relative h-full w-full flex cursor-pointer items-center justify-between gap-1 rounded-lg py-4 text-[var(--g-main-sidebar-menu-color)] transition-all hover-(bg-[var(--g-main-sidebar-menu-hover-bg)] text-[var(--g-main-sidebar-menu-hover-color)]) px-2!" :class="{
v-if="item.children && item.children.length !== 0" class="group menu-item-container relative h-full w-full flex cursor-pointer items-center justify-between gap-1 rounded-lg py-4 text-[var(--g-main-sidebar-menu-color)] transition-colors hover-(bg-[var(--g-main-sidebar-menu-hover-bg)] text-[var(--g-main-sidebar-menu-hover-color)]) px-2!" :class="{
'text-[var(--g-main-sidebar-menu-active-color)]! bg-[var(--g-main-sidebar-menu-active-bg)]!': index === menuStore.actived,
}" :title="typeof item.meta?.title === 'function' ? item.meta?.title() : item.meta?.title" @click="switchTo(index)"
>
+1 -1
View File
@@ -170,7 +170,7 @@ provide(rootMenuInjectionKey, reactive({
<template>
<div
:class="cn('h-full w-full flex flex-col of-hidden transition-all-300', {
:class="cn('h-full w-full flex flex-col of-hidden', {
'flex-row w-auto': isMenuPopup && props.mode === 'horizontal',
'py-1': props.mode === 'vertical',
})"
+2 -2
View File
@@ -37,7 +37,7 @@ defineExpose({
<template>
<div
ref="itemRef" :class="cn('menu-item relative transition-all', {
ref="itemRef" :class="cn('menu-item relative', {
'active': isItemActive,
'py-1 px-2': (rootMenu.isMenuPopup && rootMenu.props.mode === 'vertical') || (rootMenu.isMenuPopup && level !== 0 && rootMenu.props.mode === 'horizontal') || !rootMenu.isMenuPopup,
'px-1 py-2': rootMenu.isMenuPopup && level === 0 && rootMenu.props.mode === 'horizontal',
@@ -52,7 +52,7 @@ defineExpose({
target: item.meta?.link ? '_blank' : '_self',
class: 'no-underline',
}),
}" :class="cn('group menu-item-container relative h-full w-full flex cursor-pointer items-center justify-between gap-1 rounded-lg px-4 py-3 text-[var(--g-sub-sidebar-menu-color)] transition-all hover-(bg-[var(--g-sub-sidebar-menu-hover-bg)] text-[var(--g-sub-sidebar-menu-hover-color)])', {
}" :class="cn('group menu-item-container relative h-full w-full flex cursor-pointer items-center justify-between gap-1 rounded-lg px-4 py-3 text-[var(--g-sub-sidebar-menu-color)] transition-colors hover-(bg-[var(--g-sub-sidebar-menu-hover-bg)] text-[var(--g-sub-sidebar-menu-hover-color)])', {
'text-[var(--g-sub-sidebar-menu-active-color)]! bg-[var(--g-sub-sidebar-menu-active-bg)]!': isItemActive,
'px-3': rootMenu.isMenuPopup && level === 0,
})" :title="typeof item.meta?.title === 'function' ? item.meta?.title() : item.meta?.title" v-on="{
+1 -1
View File
@@ -214,7 +214,7 @@ function handleMouseleave() {
'bg-[var(--g-sub-sidebar-bg)]': rootMenu.isMenuPopup,
'border shadow-xl fixed z-3000 w-[200px]': rootMenu.isMenuPopup,
'mx-1': rootMenu.isMenuPopup && (rootMenu.props.mode === 'vertical' || level !== 0),
'py-1': rootMenu.isMenuPopup,
'py-1 overscroll-contain': rootMenu.isMenuPopup,
})"
>
<template v-for="item in menu.children" :key="item.path ?? rootMenu.getUseId(item)">
+1 -1
View File
@@ -54,7 +54,7 @@ watch(() => menuStore.actived, (val, oldVal) => {
}"
/>
<component :is="useSlots('sub-sidebar-after-logo')" />
<FaScrollArea :scrollbar="false" mask gradient-color="var(--g-sub-sidebar-bg)" class="flex-1">
<FaScrollArea :scrollbar="false" mask gradient-color="var(--g-sub-sidebar-bg)" class="flex-1 overscroll-contain">
<TransitionGroup :name="transitionName">
<template v-for="(mainItem, mainIndex) in menuStore.allMenus" :key="mainIndex">
<div v-show="mainIndex === menuStore.actived">
@@ -173,7 +173,12 @@ onMounted(() => {
}
// 切换到最后一个标签页
case 'alt+0':
router.push(tabbarStore.list[tabbarStore.list.length - 1].fullPath)
{
const last = tabbarStore.list.at(-1)
if (last) {
router.push(last.fullPath)
}
}
break
}
}
@@ -41,7 +41,7 @@ function pathCompile(path: string) {
<template>
<Breadcrumb v-if="settingsStore.mode === 'pc' && settingsStore.settings.app.routeBaseOn !== 'filesystem'" class="breadcrumb whitespace-nowrap px-2">
<TransitionGroup name="breadcrumb">
<BreadcrumbItem v-for="(item, index) in breadcrumbList" :key="`${index}_${item.path}_${item.title}`" :to="index < breadcrumbList.length - 1 && item.path !== '' ? pathCompile(item.path) : ''">
<BreadcrumbItem v-for="(item, index) in breadcrumbList" :key="`${index}_${item.path}_${item.title}`" :to="Number(index) < breadcrumbList.length - 1 && item.path !== '' ? pathCompile(item.path) : ''">
{{ item.title }}
</BreadcrumbItem>
</TransitionGroup>
@@ -33,7 +33,7 @@ function toggleColorScheme(event: MouseEvent) {
]
document.documentElement.animate(
{
clipPath: settingsStore.currentColorScheme === 'light' ? clipPath : [...clipPath].reverse(),
clipPath: settingsStore.currentColorScheme === 'light' ? clipPath : clipPath.toReversed(),
},
{
duration: 500,
@@ -30,7 +30,7 @@ const searchInput = ref('')
const searchInputRef = useTemplateRef('searchInputRef')
const { focused: searchInputFocused } = useFocus(searchInputRef)
const sourceList = ref<listTypes[]>([])
const actived = ref(-1)
const actived = ref(0)
const searchResultRef = useTemplateRef('searchResultRef')
const searchResultItemRef = useTemplateRef<HTMLElement[]>('searchResultItemRef')
@@ -69,7 +69,7 @@ const resultList = computed(() => {
watch(() => isShow.value, (val) => {
if (val) {
searchInput.value = ''
actived.value = -1
actived.value = 0
// 当搜索显示的时候绑定上、下、回车快捷键,隐藏的时候再解绑。另外当 input 处于 focus 状态时,采用 vue 来绑定键盘事件
hotkeys('up', keyUp)
hotkeys('down', keyDown)
@@ -89,7 +89,7 @@ watch(() => isShow.value, (val) => {
}
})
watch(() => resultList.value, () => {
actived.value = -1
actived.value = 0
handleScroll()
})
@@ -158,15 +158,13 @@ function keyDown() {
}
}
function keyEnter() {
if (actived.value !== -1) {
searchResultItemRef.value?.find(item => Number.parseInt(item.dataset.index!) === actived.value)?.click()
}
searchResultItemRef.value?.find(item => Number.parseInt(item.dataset.index!) === actived.value)?.click()
}
function handleScroll() {
if (searchResultRef.value?.areaRef?.ref?.el?.viewportElement) {
const contentDom = searchResultRef.value.areaRef.ref.el.viewportElement
if (searchResultRef.value?.ref?.el?.viewportElement) {
const contentDom = searchResultRef.value.ref.el.viewportElement
let scrollTo = 0
if (actived.value !== -1) {
if (resultList.value.length > 0) {
scrollTo = contentDom.scrollTop
const activedOffsetTop = searchResultItemRef.value?.find(item => Number.parseInt(item.dataset.index!) === actived.value)?.offsetTop ?? 0
const activedClientHeight = searchResultItemRef.value?.find(item => Number.parseInt(item.dataset.index!) === actived.value)?.clientHeight ?? 0
@@ -197,7 +195,7 @@ function pageJump(path: listTypes['path'], link: listTypes['link']) {
</script>
<template>
<FaModal ref="searchResultRef" v-model="isShow" border :footer="settingsStore.mode === 'pc'" :closable="false" class="w-full lg-max-w-2xl" content-class="flex flex-col p-0 min-h-auto" header-class="p-0" footer-class="p-0" @opened="searchInputFocused = true">
<FaModal v-model="isShow" border :footer="settingsStore.mode === 'pc'" :closable="false" class="w-full lg-max-w-2xl" content-class="flex flex-col p-0 min-h-auto" header-class="p-0" footer-class="p-0" @opened="searchInputFocused = true">
<template #header>
<div class="h-12 flex flex-shrink-0 items-center">
<div class="h-full w-14 flex-center">
@@ -236,7 +234,7 @@ function pageJump(path: listTypes['path'], link: listTypes['link']) {
</div>
</div>
</template>
<div>
<FaScrollArea ref="searchResultRef">
<template v-if="resultList.length > 0">
<div v-for="(item, index) in resultList" ref="searchResultItemRef" :key="item.path" class="p-4" :data-index="index" @click="pageJump(item.path, item.link)" @mouseover="actived = index">
<a class="flex cursor-pointer items-center border rounded-lg" :class="{ '-mt-4': index !== 0, 'bg-accent': index === actived }">
@@ -268,6 +266,6 @@ function pageJump(path: listTypes['path'], link: listTypes['link']) {
</p>
</div>
</template>
</div>
</FaScrollArea>
</FaModal>
</template>
@@ -8,7 +8,7 @@ defineOptions({
</script>
<template>
<div class="toolbar-container flex items-center justify-between">
<div class="toolbar-container h-[var(--g-toolbar-height)] flex items-center justify-between bg-[var(--g-toolbar-bg)]">
<div class="left-side h-full flex items-center of-hidden pe-16 ps-2">
<LeftSide />
</div>
@@ -19,13 +19,7 @@ defineOptions({
</template>
<style scoped>
.toolbar-container {
height: var(--g-toolbar-height);
background-color: var(--g-toolbar-bg);
transition: background-color 0.3s;
.left-side {
mask-image: linear-gradient(to right, #000 0%, #000 calc(100% - 50px), transparent);
}
.left-side {
mask-image: linear-gradient(to right, #000 0%, #000 calc(100% - 50px), transparent);
}
</style>
+1 -1
View File
@@ -1,4 +1,4 @@
import type { RouteLocationRaw, Router } from 'vue-router'
import type { NavigationFailure, RouteLocationRaw, Router } from 'vue-router'
import pinia from '@/store'
function getId(router: Router) {
+12 -30
View File
@@ -4,7 +4,7 @@ import { asyncRoutes, asyncRoutesByFilesystem } from './routes'
import '@/assets/styles/nprogress.css'
function setupRoutes(router: Router) {
router.beforeEach(async (to, _from, next) => {
router.beforeEach(async (to) => {
const settingsStore = useSettingsStore()
const userStore = useUserStore()
const routeStore = useRouteStore()
@@ -17,27 +17,19 @@ function setupRoutes(router: Router) {
settingsStore.settings.menu.mode !== 'single' && menuStore.setActived(to.path)
// 如果已登录状态下,进入登录页会强制跳转到主页
if (to.name === 'login') {
next({
return {
path: settingsStore.settings.home.fullPath,
replace: true,
})
}
}
// 如果未开启主页,但进入的是主页,则会进入侧边栏导航第一个模块
else if (!settingsStore.settings.home.enable && to.fullPath === settingsStore.settings.home.fullPath) {
if (menuStore.sidebarMenus.length > 0) {
next({
return {
path: menuStore.sidebarMenusFirstDeepestPath,
replace: true,
})
}
}
// 如果侧边栏导航第一个模块均无法命中,则还是进入主页
else {
next()
}
}
// 正常访问页面
else {
next()
}
}
else {
@@ -84,24 +76,21 @@ function setupRoutes(router: Router) {
userStore.logout()
}
// 动态路由生成并注册后,重新进入当前路由
next({
return {
path: to.path,
query: to.query,
replace: true,
})
}
}
}
else {
if (to.name !== 'login') {
next({
return {
name: 'login',
query: {
redirect: to.fullPath !== settingsStore.settings.home.fullPath ? to.fullPath : undefined,
},
})
}
else {
next()
}
}
}
})
@@ -109,20 +98,14 @@ function setupRoutes(router: Router) {
// 当父级路由未配置重定向时,自动重定向到有访问权限的子路由
function setupRedirectAuthChildrenRoute(router: Router) {
router.beforeEach((to, _from, next) => {
router.beforeEach((to) => {
const { auth } = useAuth()
const currentRoute = router.getRoutes().find(route => route.path === (to.matched.at(-1)?.path ?? ''))
if (!currentRoute?.redirect) {
const findAuthRoute = currentRoute?.children?.find(route => route.meta?.menu !== false && auth(route.meta?.auth ?? ''))
if (findAuthRoute) {
next(findAuthRoute)
return findAuthRoute
}
else {
next()
}
}
else {
next()
}
})
}
@@ -130,12 +113,11 @@ function setupRedirectAuthChildrenRoute(router: Router) {
// 进度条
function setupProgress(router: Router) {
const { isLoading } = useNProgress()
router.beforeEach((_to, _from, next) => {
router.beforeEach(() => {
const settingsStore = useSettingsStore()
if (settingsStore.settings.app.enableProgress) {
isLoading.value = true
}
next()
})
router.afterEach(() => {
const settingsStore = useSettingsStore()
+2 -2
View File
@@ -73,9 +73,9 @@ const constantRoutesByFilesystem = generatedRoutes.filter((item) => {
return item.meta?.enabled !== false && item.meta?.constant === true
})
const asyncRoutesByFilesystem = setupLayouts(generatedRoutes.filter((item) => {
const asyncRoutesByFilesystem = [...setupLayouts(generatedRoutes.filter((item) => {
return item.meta?.enabled !== false && item.meta?.constant !== true && item.meta?.layout !== false
}))
}))]
export {
asyncRoutes,
+6
View File
@@ -12,6 +12,12 @@ export const useSettingsStore = defineStore(
const prefersColorScheme = window.matchMedia('(prefers-color-scheme: dark)')
watch(() => settings.value.app.colorScheme, (val) => {
document.documentElement.classList.add('disable-color-scheme-transition-duration')
requestAnimationFrame(() => {
requestAnimationFrame(() => {
document.documentElement.classList.remove('disable-color-scheme-transition-duration')
})
})
if (val === '') {
prefersColorScheme.addEventListener('change', updateTheme)
}
+86 -86
View File
@@ -6,92 +6,92 @@
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const createPinia: typeof import('pinia')['createPinia']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const defineStore: typeof import('pinia')['defineStore']
const effectScope: typeof import('vue')['effectScope']
const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const getCurrentWatcher: typeof import('vue')['getCurrentWatcher']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const isShallow: typeof import('vue')['isShallow']
const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']
const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const storeToRefs: typeof import('pinia')['storeToRefs']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useAuth: typeof import('../utils/composables/useAuth')['default']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useGlobalProperties: typeof import('../utils/composables/useGlobalProperties')['default']
const useId: typeof import('vue')['useId']
const useKeepAliveStore: typeof import('../store/modules/keepAlive')['useKeepAliveStore']
const useLink: typeof import('vue-router')['useLink']
const useMainPage: typeof import('../utils/composables/useMainPage')['default']
const useMenu: typeof import('../utils/composables/useMenu')['default']
const useMenuStore: typeof import('../store/modules/menu')['useMenuStore']
const useModel: typeof import('vue')['useModel']
const useRoute: typeof import('vue-router')['useRoute']
const useRouteStore: typeof import('../store/modules/route')['useRouteStore']
const useRouter: typeof import('vue-router')['useRouter']
const useSettingsStore: typeof import('../store/modules/settings')['useSettingsStore']
const useSlots: typeof import('vue')['useSlots']
const useTabbar: typeof import('../utils/composables/useTabbar')['default']
const useTabbarStore: typeof import('../store/modules/tabbar')['useTabbarStore']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const useUserStore: typeof import('../store/modules/user')['useUserStore']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
const EffectScope: typeof import('vue').EffectScope
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
const computed: typeof import('vue').computed
const createApp: typeof import('vue').createApp
const createPinia: typeof import('pinia').createPinia
const customRef: typeof import('vue').customRef
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
const defineComponent: typeof import('vue').defineComponent
const defineStore: typeof import('pinia').defineStore
const effectScope: typeof import('vue').effectScope
const getActivePinia: typeof import('pinia').getActivePinia
const getCurrentInstance: typeof import('vue').getCurrentInstance
const getCurrentScope: typeof import('vue').getCurrentScope
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
const h: typeof import('vue').h
const inject: typeof import('vue').inject
const isProxy: typeof import('vue').isProxy
const isReactive: typeof import('vue').isReactive
const isReadonly: typeof import('vue').isReadonly
const isRef: typeof import('vue').isRef
const isShallow: typeof import('vue').isShallow
const mapActions: typeof import('pinia').mapActions
const mapGetters: typeof import('pinia').mapGetters
const mapState: typeof import('pinia').mapState
const mapStores: typeof import('pinia').mapStores
const mapWritableState: typeof import('pinia').mapWritableState
const markRaw: typeof import('vue').markRaw
const nextTick: typeof import('vue').nextTick
const onActivated: typeof import('vue').onActivated
const onBeforeMount: typeof import('vue').onBeforeMount
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
const onDeactivated: typeof import('vue').onDeactivated
const onErrorCaptured: typeof import('vue').onErrorCaptured
const onMounted: typeof import('vue').onMounted
const onRenderTracked: typeof import('vue').onRenderTracked
const onRenderTriggered: typeof import('vue').onRenderTriggered
const onScopeDispose: typeof import('vue').onScopeDispose
const onServerPrefetch: typeof import('vue').onServerPrefetch
const onUnmounted: typeof import('vue').onUnmounted
const onUpdated: typeof import('vue').onUpdated
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
const provide: typeof import('vue').provide
const reactive: typeof import('vue').reactive
const readonly: typeof import('vue').readonly
const ref: typeof import('vue').ref
const resolveComponent: typeof import('vue').resolveComponent
const setActivePinia: typeof import('pinia').setActivePinia
const setMapStoreSuffix: typeof import('pinia').setMapStoreSuffix
const shallowReactive: typeof import('vue').shallowReactive
const shallowReadonly: typeof import('vue').shallowReadonly
const shallowRef: typeof import('vue').shallowRef
const storeToRefs: typeof import('pinia').storeToRefs
const toRaw: typeof import('vue').toRaw
const toRef: typeof import('vue').toRef
const toRefs: typeof import('vue').toRefs
const toValue: typeof import('vue').toValue
const triggerRef: typeof import('vue').triggerRef
const unref: typeof import('vue').unref
const useAttrs: typeof import('vue').useAttrs
const useAuth: typeof import('../utils/composables/useAuth').default
const useCssModule: typeof import('vue').useCssModule
const useCssVars: typeof import('vue').useCssVars
const useGlobalProperties: typeof import('../utils/composables/useGlobalProperties').default
const useId: typeof import('vue').useId
const useKeepAliveStore: typeof import('../store/modules/keepAlive').useKeepAliveStore
const useLink: typeof import('vue-router').useLink
const useMainPage: typeof import('../utils/composables/useMainPage').default
const useMenu: typeof import('../utils/composables/useMenu').default
const useMenuStore: typeof import('../store/modules/menu').useMenuStore
const useModel: typeof import('vue').useModel
const useRoute: typeof import('vue-router').useRoute
const useRouteStore: typeof import('../store/modules/route').useRouteStore
const useRouter: typeof import('vue-router').useRouter
const useSettingsStore: typeof import('../store/modules/settings').useSettingsStore
const useSlots: typeof import('vue').useSlots
const useTabbar: typeof import('../utils/composables/useTabbar').default
const useTabbarStore: typeof import('../store/modules/tabbar').useTabbarStore
const useTemplateRef: typeof import('vue').useTemplateRef
const useUserStore: typeof import('../store/modules/user').useUserStore
const watch: typeof import('vue').watch
const watchEffect: typeof import('vue').watchEffect
const watchPostEffect: typeof import('vue').watchPostEffect
const watchSyncEffect: typeof import('vue').watchSyncEffect
}
// for type re-export
declare global {
+53 -1
View File
@@ -1,8 +1,12 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
import { GlobalComponents } from 'vue'
export {}
/* prettier-ignore */
@@ -54,3 +58,51 @@ declare module 'vue' {
RouterView: typeof import('vue-router')['RouterView']
}
}
// For TSX support
declare global {
const AccountButton: typeof import('./../components/AccountButton/index.vue')['default']
const FaAuth: typeof import('./../ui/components/FaAuth/index.vue')['default']
const FaAvatar: typeof import('./../ui/components/FaAvatar/index.vue')['default']
const FaBackToTop: typeof import('./../ui/components/FaBackToTop/index.vue')['default']
const FaButton: typeof import('./../ui/components/FaButton/index.vue')['default']
const FaButtonGroup: typeof import('./../ui/components/FaButtonGroup/index.vue')['default']
const FaCard: typeof import('./../ui/components/FaCard/index.vue')['default']
const FaCheckbox: typeof import('./../ui/components/FaCheckbox/index.vue')['default']
const FaCollapsible: typeof import('./../ui/components/FaCollapsible/index.vue')['default']
const FaContextMenu: typeof import('./../ui/components/FaContextMenu/index.vue')['default']
const FaCopyright: typeof import('./../ui/components/FaCopyright/index.vue')['default']
const FaDivider: typeof import('./../ui/components/FaDivider/index.vue')['default']
const FaDrawer: typeof import('./../ui/components/FaDrawer/index.vue')['default']
const FaDropdown: typeof import('./../ui/components/FaDropdown/index.vue')['default']
const FaFileUpload: typeof import('./../ui/components/FaFileUpload/index.vue')['default']
const FaFixedActionBar: typeof import('./../ui/components/FaFixedActionBar/index.vue')['default']
const FaHoverCard: typeof import('./../ui/components/FaHoverCard/index.vue')['default']
const FaIcon: typeof import('./../ui/components/FaIcon/index.vue')['default']
const FaImagePreview: typeof import('./../ui/components/FaImagePreview/index.vue')['default']
const FaImageUpload: typeof import('./../ui/components/FaImageUpload/index.vue')['default']
const FaInput: typeof import('./../ui/components/FaInput/index.vue')['default']
const FaKbd: typeof import('./../ui/components/FaKbd/index.vue')['default']
const FaModal: typeof import('./../ui/components/FaModal/index.vue')['default']
const FaNotAllowed: typeof import('./../ui/components/FaNotAllowed/index.vue')['default']
const FaNotification: typeof import('./../ui/components/FaNotification/index.vue')['default']
const FaPageHeader: typeof import('./../ui/components/FaPageHeader/index.vue')['default']
const FaPageMain: typeof import('./../ui/components/FaPageMain/index.vue')['default']
const FaPasswordStrength: typeof import('./../ui/components/FaPasswordStrength/index.vue')['default']
const FaPinInput: typeof import('./../ui/components/FaPinInput/index.vue')['default']
const FaPopover: typeof import('./../ui/components/FaPopover/index.vue')['default']
const FaProgress: typeof import('./../ui/components/FaProgress/index.vue')['default']
const FaScrollArea: typeof import('./../ui/components/FaScrollArea/index.vue')['default']
const FaSearchBar: typeof import('./../ui/components/FaSearchBar/index.vue')['default']
const FaSelect: typeof import('./../ui/components/FaSelect/index.vue')['default']
const FaSlider: typeof import('./../ui/components/FaSlider/index.vue')['default']
const FaSmartFixedBlock: typeof import('./../ui/components/FaSmartFixedBlock/index.vue')['default']
const FaSwitch: typeof import('./../ui/components/FaSwitch/index.vue')['default']
const FaSystemInfo: typeof import('./../ui/components/FaSystemInfo/index.vue')['default']
const FaTabs: typeof import('./../ui/components/FaTabs/index.vue')['default']
const FaTextarea: typeof import('./../ui/components/FaTextarea/index.vue')['default']
const FaToast: typeof import('./../ui/components/FaToast/index.vue')['default']
const FaTooltip: typeof import('./../ui/components/FaTooltip/index.vue')['default']
const RouterLink: typeof import('vue-router')['RouterLink']
const RouterView: typeof import('vue-router')['RouterView']
}
+10 -10
View File
@@ -1,13 +1,5 @@
interface ImportMetaEnv {
// Auto generate by env-parse
/**
* 应用配置面板
*/
readonly VITE_APP_SETTING: boolean
/**
* 页面标题
*/
readonly VITE_APP_TITLE: string
/**
* 接口请求地址,会设置到 axios 的 baseURL 参数上
*/
@@ -21,11 +13,19 @@ interface ImportMetaEnv {
*/
readonly VITE_APP_DISABLE_DEVTOOL: boolean
/**
* 是否开启代理
* 应用配置面板
*/
readonly VITE_OPEN_PROXY: boolean
readonly VITE_APP_SETTING: boolean
/**
* 页面标题
*/
readonly VITE_APP_TITLE: string
/**
* 是否开启开发者工具
*/
readonly VITE_OPEN_DEVTOOLS: boolean
/**
* 是否开启代理
*/
readonly VITE_OPEN_PROXY: boolean
}
+2 -2
View File
@@ -12,9 +12,9 @@ const settingsStore = useSettingsStore()
<FaIcon name="i-ri:copyright-line" class="size-5" />
<span v-if="settingsStore.settings.copyright.dates" class="px-1">{{ settingsStore.settings.copyright.dates }}</span>
<template v-if="settingsStore.settings.copyright.company">
<a v-if="settingsStore.settings.copyright.website" :href="settingsStore.settings.copyright.website" target="_blank" rel="noopener" class="px-1 text-center no-underline transition hover-text-secondary-foreground">{{ settingsStore.settings.copyright.company }}</a>
<a v-if="settingsStore.settings.copyright.website" :href="settingsStore.settings.copyright.website" target="_blank" rel="noopener" class="px-1 text-center no-underline transition-colors hover-text-secondary-foreground">{{ settingsStore.settings.copyright.company }}</a>
<span v-else class="px-1">{{ settingsStore.settings.copyright.company }}</span>
</template>
<a v-if="settingsStore.settings.copyright.beian" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" class="px-1 text-center no-underline transition hover-text-secondary-foreground">{{ settingsStore.settings.copyright.beian }}</a>
<a v-if="settingsStore.settings.copyright.beian" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" class="px-1 text-center no-underline transition-colors hover-text-secondary-foreground">{{ settingsStore.settings.copyright.beian }}</a>
</footer>
</template>
+3 -2
View File
@@ -23,13 +23,14 @@ export interface DrawerProps {
confirmButtonLoading?: boolean
beforeClose?: (
action: 'confirm' | 'cancel' | 'close',
done: () => void
done: () => void,
) => void
header?: boolean
footer?: boolean
closeOnClickOverlay?: boolean
closeOnPressEscape?: boolean
destroyOnClose?: boolean
openAutoFocus?: boolean
contentClass?: HTMLAttributes['class']
headerClass?: HTMLAttributes['class']
footerClass?: HTMLAttributes['class']
@@ -93,7 +94,7 @@ export function useFaDrawer() {
}
// 监听 visible 和 options 变化,自动重新渲染
watch([visible, options], () => {
watch([visible, options as object], () => {
updateVNode()
}, {
immediate: true,
+14 -10
View File
@@ -38,6 +38,7 @@ const props = withDefaults(
closeOnClickOverlay: true,
closeOnPressEscape: true,
destroyOnClose: true,
openAutoFocus: false,
},
)
@@ -135,6 +136,13 @@ async function onCancel() {
}
}
function handleOpenAutoFocus(e: Event) {
if (!props.openAutoFocus) {
e.preventDefault()
e.stopPropagation()
}
}
function handleFocusOutside(e: Event) {
e.preventDefault()
e.stopPropagation()
@@ -179,7 +187,7 @@ function handleAnimationEnd() {
})"
:side="props.side"
:force-mount="forceMount"
@open-auto-focus="handleFocusOutside"
@open-auto-focus="handleOpenAutoFocus"
@close-auto-focus="handleFocusOutside"
@focus-outside="handleFocusOutside"
@pointer-down-outside="handleClickOutside"
@@ -209,15 +217,11 @@ function handleAnimationEnd() {
<SheetTitle />
<SheetDescription />
</VisuallyHidden>
<div class="m-0 flex-1 of-y-hidden">
<FaScrollArea v-if="!!slots.default" class="h-full">
<div class="p-4">
<slot />
</div>
<div v-show="props.loading" class="absolute inset-0 z-1000 size-full flex-center bg-popover/75">
<FaIcon name="i-line-md:loading-twotone-loop" class="size-10" />
</div>
</FaScrollArea>
<div v-if="!!slots.default" class="relative m-0 flex-1 overflow-y-auto p-4">
<slot />
</div>
<div v-show="props.loading" class="absolute inset-0 z-1000 size-full flex-center bg-popover/75">
<FaIcon name="i-line-md:loading-twotone-loop" class="size-10" />
</div>
<SheetFooter
v-if="footer" :class="cn('p-3 gap-y-2', props.footerClass, {
+7 -5
View File
@@ -80,7 +80,7 @@ function onSelectFile(files: FileList | File[] | null) {
if (!files) {
return
}
const selectedFiles = Array.from(files)
const selectedFiles = [...files]
// 数量限制
const remain = props.max > 0 ? props.max - fileList.value.length : selectedFiles.length
const filesToAdd: File[] = []
@@ -175,10 +175,12 @@ function removeFile(idx: number) {
@drop="handleDrop"
@click="fileInputRef?.click()"
>
<FaIcon name="i-icon-park-outline:upload" class="mb-2 text-2xl text-card-foreground/50" />
<div class="text-sm text-card-foreground/70">
将文件拖到此处,或<span class="cursor-pointer text-primary font-bold">点击上传</span>
</div>
<slot>
<FaIcon name="i-icon-park-outline:upload" class="mb-2 text-2xl text-card-foreground/50" />
<div class="text-sm text-card-foreground/70">
将文件拖到此处,或<span class="cursor-pointer text-primary font-bold">点击上传</span>
</div>
</slot>
<input ref="fileInputRef" type="file" :multiple="props.multiple" :disabled="props.disabled" class="hidden" @change="e => onSelectFile((e.target as HTMLInputElement).files)">
</button>
<div v-if="!props.hideTips && !props.disabled" class="flex flex-wrap gap-1 text-xs text-card-foreground/50 empty:hidden">
@@ -21,6 +21,12 @@ const isLoading = ref(true)
const isError = ref(false)
const isOpen = ref(false)
// 监听 src 变化,重置加载状态
watch(() => props.src, () => {
isLoading.value = true
isError.value = false
}, { immediate: false })
function handleLoad() {
isLoading.value = false
emits('load')
+1 -1
View File
@@ -54,7 +54,7 @@ function onSelectFile(e: Event) {
if (!input.files) {
return
}
const selectedFiles = Array.from(input.files)
const selectedFiles = [...input.files]
const remain = props.max === 0 ? selectedFiles.length : props.max - images.value.length
const filesToAdd: File[] = []
for (const file of selectedFiles.slice(0, remain)) {
+8 -2
View File
@@ -27,13 +27,14 @@ export interface ModalProps {
confirmButtonLoading?: boolean
beforeClose?: (
action: 'confirm' | 'cancel' | 'close',
done: () => void
done: () => void,
) => void
header?: boolean
footer?: boolean
closeOnClickOverlay?: boolean
closeOnPressEscape?: boolean
destroyOnClose?: boolean
openAutoFocus?: boolean
class?: HTMLAttributes['class']
headerClass?: HTMLAttributes['class']
contentClass?: HTMLAttributes['class']
@@ -101,7 +102,7 @@ export function useFaModal() {
}
// 监听 visible 和 options 变化,自动重新渲染
watch([visible, options], () => {
watch([visible, options as object], () => {
updateVNode()
}, {
immediate: true,
@@ -147,6 +148,7 @@ export function useFaModal() {
alignCenter: true,
closeOnClickOverlay: false,
destroyOnClose: true,
openAutoFocus: true,
contentClass: 'py-0 min-h-auto',
footerClass: 'p-4',
}
@@ -161,6 +163,7 @@ export function useFaModal() {
alignCenter: true,
closeOnClickOverlay: false,
destroyOnClose: true,
openAutoFocus: true,
contentClass: 'py-0 min-h-auto',
footerClass: 'p-4',
}
@@ -175,6 +178,7 @@ export function useFaModal() {
alignCenter: true,
closeOnClickOverlay: false,
destroyOnClose: true,
openAutoFocus: true,
contentClass: 'py-0 min-h-auto',
footerClass: 'p-4',
}
@@ -189,6 +193,7 @@ export function useFaModal() {
alignCenter: true,
closeOnClickOverlay: false,
destroyOnClose: true,
openAutoFocus: true,
contentClass: 'py-0 min-h-auto',
footerClass: 'p-4',
}
@@ -203,6 +208,7 @@ export function useFaModal() {
showCancelButton: true,
closeOnClickOverlay: false,
destroyOnClose: true,
openAutoFocus: true,
contentClass: 'py-0 min-h-auto',
footerClass: 'p-4',
}
+16 -15
View File
@@ -42,6 +42,7 @@ const props = withDefaults(
closeOnClickOverlay: true,
closeOnPressEscape: true,
destroyOnClose: true,
openAutoFocus: false,
},
)
@@ -55,13 +56,8 @@ const slots = defineSlots<{
const dialogContentRef = useTemplateRef('dialogContentRef')
const dialogHeaderRef = ref()
const dialogAreaRef = useTemplateRef('dialogAreaRef')
const dialogRef = ref()
defineExpose({
areaRef: dialogAreaRef,
})
const modalId = shallowRef(props.id ?? useId())
const isOpen = ref(props.modelValue)
const isMaximize = ref(props.maximize)
@@ -174,6 +170,13 @@ async function onCancel() {
}
}
function handleOpenAutoFocus(e: Event) {
if (!props.openAutoFocus) {
e.preventDefault()
e.stopPropagation()
}
}
function handleFocusOutside(e: Event) {
e.preventDefault()
e.stopPropagation()
@@ -222,13 +225,13 @@ function handleAnimationEnd() {
:maximize="isMaximize"
:maximizable="props.maximizable"
:force-mount="forceMount"
:class="cn('left-0 right-0 top-0 md:top-[5vh] flex flex-col p-0 gap-0 mx-auto h-[calc-size(auto,size)] min-h-full md:min-h-auto max-h-full md:max-h-[90vh] translate-x-0 translate-y-0', props.class, {
:class="cn('left-0 right-0 top-0 md:top-[5vh] flex flex-col p-0 gap-0 mx-auto h-[calc-size(auto,size)] min-h-full overflow-hidden md:min-h-auto max-h-full md:max-h-[90vh] translate-x-0 translate-y-0', props.class, {
'md:top-0 size-full max-w-full max-h-full md:max-h-full': isMaximize,
'md:top-1/2 md:-translate-y-1/2!': props.alignCenter,
'duration-0': isDragging,
'hidden': isClosed,
})"
@open-auto-focus="handleFocusOutside"
@open-auto-focus="handleOpenAutoFocus"
@close-auto-focus="handleFocusOutside"
@focus-outside="handleFocusOutside"
@pointer-down-outside="handleClickOutside"
@@ -273,14 +276,12 @@ function handleAnimationEnd() {
<DialogTitle />
<DialogDescription />
</VisuallyHidden>
<FaScrollArea v-if="!!slots.default" ref="dialogAreaRef" class="flex-1">
<div :class="cn('min-h-40 p-4', props.contentClass)">
<slot />
</div>
<div v-show="props.loading" class="absolute inset-0 z-1000 size-full flex-center bg-popover/75">
<FaIcon name="i-line-md:loading-twotone-loop" class="size-10" />
</div>
</FaScrollArea>
<div v-if="!!slots.default" :class="cn('relative flex-1 min-h-40 p-4 overflow-y-auto', props.contentClass)">
<slot />
</div>
<div v-show="props.loading" class="absolute inset-0 z-1000 size-full flex-center bg-popover/75">
<FaIcon name="i-line-md:loading-twotone-loop" class="size-10" />
</div>
<DialogFooter
v-if="footer" :class="cn('p-3 gap-y-2', props.footerClass, {
'md:justify-center': props.center,
@@ -80,11 +80,11 @@ const state = ref<State>({
function dispatch(action: Action) {
switch (action.type) {
case actionTypes.ADD_TOAST:
state.value.toasts = [action.toast, ...state.value.toasts].slice(0, TOAST_LIMIT)
state.value.toasts = [action.toast as any, ...state.value.toasts as any].slice(0, TOAST_LIMIT)
break
case actionTypes.UPDATE_TOAST:
state.value.toasts = state.value.toasts.map(t =>
state.value.toasts = state.value.toasts.map((t: any) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t,
)
break
@@ -101,7 +101,7 @@ function dispatch(action: Action) {
})
}
state.value.toasts = state.value.toasts.map(t =>
state.value.toasts = state.value.toasts.map((t: any) =>
t.id === toastId || toastId === undefined
? {
...t,
@@ -116,7 +116,7 @@ function dispatch(action: Action) {
if (action.toastId === undefined) {
state.value.toasts = []
}
else { state.value.toasts = state.value.toasts.filter(t => t.id !== action.toastId) }
else { state.value.toasts = state.value.toasts.filter((t: any) => t.id !== action.toastId) }
break
}
@@ -28,7 +28,7 @@ defineExpose({
<template>
<ScrollAreaRoot v-bind="delegatedProps" :class="cn('relative overflow-hidden', props.class)">
<ScrollAreaViewport ref="viewportRef" class="scroll-area-viewport h-full w-full overscroll-contain rounded-[inherit]" @scroll="onScroll" @wheel="onWheel">
<ScrollAreaViewport ref="viewportRef" class="scroll-area-viewport h-full w-full rounded-[inherit]" @scroll="onScroll" @wheel="onWheel">
<slot />
</ScrollAreaViewport>
<ScrollBar :class="{ 'opacity-0 pointer-events-none': !props.scrollbar }" />
+3 -3
View File
@@ -29,10 +29,10 @@ const value = defineModel<AcceptableValue>()
const selectedOption = computed({
get() {
return props.options.find(option => option.value === value.value) ?? props.options[0]
return props.options.length > 0 ? props.options.find(option => option.value === value.value) ?? props.options[0] : null
},
set(val) {
value.value = val.value
value.value = val?.value || null
},
})
</script>
@@ -40,7 +40,7 @@ const selectedOption = computed({
<template>
<Select v-model="value" :disabled>
<SelectTrigger :class="cn('w-[200px]', props.class)">
<SelectValue :placeholder="selectedOption.label" />
<SelectValue :placeholder="selectedOption?.label" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
+1 -1
View File
@@ -30,7 +30,7 @@ if (isSupprotColorMix) {
</script>
<template>
<ElConfigProvider :locale="zhCN" :button="{ autoInsertSpace: true }">
<ElConfigProvider :locale="zhCN as any" :button="{ autoInsertSpace: true }">
<slot />
</ElConfigProvider>
</template>
+1
View File
@@ -92,6 +92,7 @@ function open(url: string) {
[
{ label: 'Github', icon: 'i-simple-icons:github', handle: () => open('https://github.com/fantastic-admin/basic') },
{ label: 'Gitee', icon: 'i-simple-icons:gitee', handle: () => open('https://gitee.com/fantastic-admin/basic') },
{ label: 'GitCode', icon: 'i-simple-icons:gitcode', handle: () => open('https://atomgit.com/fantastic-admin/basic') },
],
]"
>
+1 -1
View File
@@ -3,7 +3,6 @@
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"#/*": ["./src/types/*"]
@@ -19,6 +18,7 @@
"strict": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": false,
"noUncheckedSideEffectImports": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"sourceMap": true,
-1
View File
@@ -1,7 +1,6 @@
{
// 此处配置是为了满足 shadcn-vue CLI 的配置要求
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
+15 -12
View File
@@ -131,16 +131,18 @@ export default function createVitePlugins(mode: string, isBuild = false) {
transform: (code, id) => {
if (/src\/main.ts$/.test(id)) {
if (viteEnv.VITE_APP_DEBUG_TOOL === 'eruda') {
code = code.concat(`
import eruda from 'eruda'
eruda.init()
`)
code = `
${code}
import eruda from 'eruda'
eruda.init()
`
}
else if (viteEnv.VITE_APP_DEBUG_TOOL === 'vconsole') {
code = code.concat(`
import VConsole from 'vconsole'
new VConsole()
`)
code = `
${code}
import VConsole from 'vconsole'
new VConsole()
`
}
return {
code,
@@ -156,10 +158,11 @@ export default function createVitePlugins(mode: string, isBuild = false) {
transform: (code, id) => {
if (/src\/main.ts$/.test(id)) {
if (viteEnv.VITE_APP_DISABLE_DEVTOOL) {
code = code.concat(`
import DisableDevtool from 'disable-devtool'
DisableDevtool()
`)
code = `
${code}
import DisableDevtool from 'disable-devtool'
DisableDevtool()
`
}
return {
code,