Compare commits

...
14 Commits
27 changed files with 2719 additions and 2340 deletions
+13
View File
@@ -21,3 +21,16 @@ jobs:
source-repo: git@github.com:fantastic-admin/basic.git
# 注意替换为你的 Gitee 目标仓库地址
destination-repo: git@gitee.com:fantastic-admin/basic.git
sync-to-atomgit:
runs-on: ubuntu-latest
steps:
- name: Sync to AtomGit
uses: wearerequired/git-mirror-action@master
env:
# 注意在 Settings->Secrets 配置 ATOMGIT_RSA_PRIVATE_KEY
SSH_PRIVATE_KEY: ${{ secrets.ATOMGIT_RSA_PRIVATE_KEY }}
with:
# 注意替换为你的 GitHub 源仓库地址
source-repo: git@github.com:fantastic-admin/basic.git
# 注意替换为你的 AtomGit 目标仓库地址
destination-repo: git@gitcode.com:fantastic-admin/basic.git
+1 -1
View File
@@ -6,7 +6,7 @@
<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>
+5 -3
View File
@@ -6,7 +6,7 @@
<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>
@@ -61,12 +61,14 @@
## 支持
如果觉得 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,7 +98,7 @@
</tr>
<tr>
<th colspan="3" align="center">
一款干啥都快人一步的 Vue 中后台管理系统框架
一款干啥都快人一步的 Vue 后台管理系统框架
</th>
</tr>
<tr>
+48 -48
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"version": "5.8.0",
"packageManager": "pnpm@10.18.1",
"version": "5.9.0",
"packageManager": "pnpm@10.26.2",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
@@ -26,95 +26,95 @@
},
"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.1.0",
"@vueuse/core": "^14.1.0",
"@vueuse/integrations": "^14.1.0",
"axios": "^1.13.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.18",
"dayjs": "^1.11.19",
"defu": "^6.1.4",
"disable-devtool": "^0.3.9",
"element-plus": "^2.11.4",
"element-plus": "^2.13.0",
"eruda": "^3.4.3",
"es-toolkit": "^1.39.10",
"es-toolkit": "^1.43.0",
"filesize": "^11.0.13",
"hotkeys-js": "^3.13.15",
"lucide-vue-next": "^0.545.0",
"hotkeys-js": "^4.0.0",
"lucide-vue-next": "^0.562.0",
"mitt": "^3.0.1",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"path-to-regexp": "^8.3.0",
"pinia": "^3.0.3",
"pinia": "^3.0.4",
"qs": "^6.14.0",
"reka-ui": "^2.5.1",
"reka-ui": "^2.7.0",
"scule": "^1.3.0",
"tailwind-merge": "^3.3.1",
"ua-parser-js": "^2.0.5",
"tailwind-merge": "^3.4.0",
"ua-parser-js": "^2.0.7",
"vconsole": "^3.15.1",
"vee-validate": "^4.15.1",
"vue": "^3.5.22",
"vue-router": "^4.5.1",
"vue": "^3.5.26",
"vue-router": "^4.6.4",
"vue-sonner": "^2.0.9",
"zod": "^4.1.12"
"zod": "^4.2.1"
},
"devDependencies": {
"@antfu/eslint-config": "^5.4.1",
"@antfu/eslint-config": "^6.7.3",
"@clack/prompts": "^0.11.0",
"@faker-js/faker": "^10.0.0",
"@iconify/json": "^2.2.392",
"@faker-js/faker": "^10.1.0",
"@iconify/json": "^2.2.417",
"@iconify/vue": "^5.0.0",
"@stylistic/stylelint-config": "^3.0.1",
"@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",
"@unocss/eslint-plugin": "^66.5.11",
"@unocss/preset-legacy-compat": "^66.5.11",
"@vitejs/plugin-legacy": "^7.2.1",
"@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue-jsx": "^5.1.1",
"@vitejs/plugin-vue": "^6.0.3",
"@vitejs/plugin-vue-jsx": "^5.1.3",
"@vue/tsconfig": "^0.8.1",
"autoprefixer": "^10.4.21",
"autoprefixer": "^10.4.23",
"boxen": "^8.0.1",
"bumpp": "^10.3.1",
"bumpp": "^10.3.2",
"cz-git": "^1.12.0",
"eslint": "^9.37.0",
"fs-extra": "^11.3.2",
"eslint": "^9.39.2",
"fs-extra": "^11.3.3",
"http-server": "^14.1.1",
"lint-staged": "^16.2.3",
"lint-staged": "^16.2.7",
"npm-run-all2": "^8.0.4",
"picocolors": "^1.1.1",
"plop": "^4.0.4",
"postcss": "^8.5.6",
"postcss-nested": "^7.0.2",
"sass-embedded": "^1.93.2",
"sass-embedded": "^1.97.1",
"simple-git-hooks": "^2.13.1",
"stylelint": "^16.25.0",
"stylelint-config-recess-order": "^7.3.0",
"stylelint": "^16.26.1",
"stylelint-config-recess-order": "^7.4.0",
"stylelint-config-standard-scss": "^16.0.0",
"stylelint-config-standard-vue": "^1.0.0",
"stylelint-scss": "^6.12.1",
"stylelint-scss": "^6.13.0",
"svgo": "^4.0.0",
"taze": "^19.7.0",
"tsx": "^4.20.6",
"taze": "^19.9.2",
"tsx": "^4.21.0",
"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",
"unocss": "^66.5.11",
"unocss-preset-animations": "^1.3.0",
"unplugin-auto-import": "^20.3.0",
"unplugin-turbo-console": "^2.3.0",
"unplugin-vue-components": "^30.0.0",
"vite": "^7.3.0",
"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.4.0",
"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.2",
"vite-plugin-pages": "^0.33.2",
"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.0.5",
"vite-plugin-vue-meta-layouts": "^0.6.1",
"vue-tsc": "^3.2.1"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged",
+2425 -2136
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%;
@@ -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: '',
},
+1 -1
View File
@@ -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)"
>
+1 -1
View File
@@ -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="{
@@ -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>
@@ -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) {
+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']
}
+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>
+2 -1
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']
+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, {
+6 -4
View 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')
+7 -1
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']
@@ -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,
+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') },
],
]"
>