@duxweb/dvha-template 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -0
- package/bin/index.js +250 -0
- package/package.json +50 -0
- package/template/base/App.vue +13 -0
- package/template/base/README.md +154 -0
- package/template/base/index.html +13 -0
- package/template/base/main.ts +64 -0
- package/template/base/package.json +28 -0
- package/template/base/tsconfig.json +37 -0
- package/template/base/typings.d.ts +16 -0
- package/template/base/uno.config.ts +22 -0
- package/template/base/vite-env.d.ts +1 -0
- package/template/base/vite.config.ts +34 -0
- package/template/ui-configs/base/pages/404.vue +63 -0
- package/template/ui-configs/base/pages/home.vue +77 -0
- package/template/ui-configs/base/pages/layout.vue +79 -0
- package/template/ui-configs/base/pages/login.vue +161 -0
- package/template/ui-configs/base/pages/menu.vue +32 -0
- package/template/ui-configs/base.json +22 -0
- package/template/ui-configs/elementui/pages/404.vue +63 -0
- package/template/ui-configs/elementui/pages/home.vue +77 -0
- package/template/ui-configs/elementui/pages/layout.vue +79 -0
- package/template/ui-configs/elementui/pages/login.vue +123 -0
- package/template/ui-configs/elementui/pages/menu.vue +32 -0
- package/template/ui-configs/elementui.json +27 -0
- package/template/ui-configs/naiveui/pages/404.vue +63 -0
- package/template/ui-configs/naiveui/pages/home.vue +77 -0
- package/template/ui-configs/naiveui/pages/layout.vue +79 -0
- package/template/ui-configs/naiveui/pages/login.vue +146 -0
- package/template/ui-configs/naiveui/pages/menu.vue +32 -0
- package/template/ui-configs/naiveui.json +26 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="min-h-screen bg-gray-50 flex flex-col">
|
|
3
|
+
<!-- 头部 -->
|
|
4
|
+
<header class="bg-white shadow-sm border-b fixed w-full top-0 z-50">
|
|
5
|
+
<div class="flex items-center justify-between h-16 px-4">
|
|
6
|
+
<div class="flex items-center">
|
|
7
|
+
<button @click="toggleSidebar" class="p-2 rounded hover:bg-gray-100 lg:hidden">
|
|
8
|
+
<div class="i-tabler:menu-2 text-xl"></div>
|
|
9
|
+
</button>
|
|
10
|
+
<div class="flex items-center ml-4 lg:ml-0">
|
|
11
|
+
<div class="i-tabler:brand-vue text-2xl text-blue-600 mr-3"></div>
|
|
12
|
+
<h1 class="text-xl font-bold">{{ manage.config?.title || 'Dux Admin' }}</h1>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- 用户菜单 -->
|
|
17
|
+
<div class="relative">
|
|
18
|
+
<button @click="showUserMenu = !showUserMenu" class="flex items-center p-2 rounded hover:bg-gray-100">
|
|
19
|
+
<div class="i-tabler:user-circle text-xl mr-2"></div>
|
|
20
|
+
<span class="hidden md:block text-sm font-medium">{{ getUserName() }}</span>
|
|
21
|
+
<div class="i-tabler:chevron-down text-sm ml-1 hidden md:block"></div>
|
|
22
|
+
</button>
|
|
23
|
+
|
|
24
|
+
<div v-if="showUserMenu" class="absolute right-0 mt-2 w-40 bg-white rounded-md shadow-lg border z-50">
|
|
25
|
+
<div class="py-1">
|
|
26
|
+
<button @click="handleLogout" class="flex items-center w-full px-4 py-2 text-sm hover:bg-gray-100">
|
|
27
|
+
<div class="i-tabler:logout mr-3"></div>
|
|
28
|
+
退出登录
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</header>
|
|
35
|
+
|
|
36
|
+
<div class="flex pt-16 flex-1">
|
|
37
|
+
<!-- 侧边栏 -->
|
|
38
|
+
<Sidebar :sidebar-open="sidebarOpen" />
|
|
39
|
+
|
|
40
|
+
<!-- 遮罩层 -->
|
|
41
|
+
<div v-if="sidebarOpen" @click="toggleSidebar" class="fixed inset-0 z-30 bg-black bg-opacity-50 lg:hidden"></div>
|
|
42
|
+
|
|
43
|
+
<!-- 主内容 -->
|
|
44
|
+
<main class="flex-1">
|
|
45
|
+
<RouterView />
|
|
46
|
+
</main>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup lang="ts">
|
|
52
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
53
|
+
import { useManage, useGetAuth, useLogout } from '@duxweb/dvha-core'
|
|
54
|
+
import Sidebar from './menu.vue'
|
|
55
|
+
|
|
56
|
+
const manage = useManage()
|
|
57
|
+
const user = useGetAuth()
|
|
58
|
+
|
|
59
|
+
const { mutate: logout } = useLogout({
|
|
60
|
+
onSuccess: () => console.log('退出成功'),
|
|
61
|
+
onError: (error) => console.error('退出失败:', error)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const sidebarOpen = ref(false)
|
|
65
|
+
const showUserMenu = ref(false)
|
|
66
|
+
|
|
67
|
+
const getUserName = () => user?.info?.name || user?.info?.username || 'Admin'
|
|
68
|
+
const toggleSidebar = () => sidebarOpen.value = !sidebarOpen.value
|
|
69
|
+
const handleLogout = () => logout()
|
|
70
|
+
|
|
71
|
+
const closeDropdowns = (event: Event) => {
|
|
72
|
+
if (!(event.target as HTMLElement).closest('.relative')) {
|
|
73
|
+
showUserMenu.value = false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onMounted(() => document.addEventListener('click', closeDropdowns))
|
|
78
|
+
onUnmounted(() => document.removeEventListener('click', closeDropdowns))
|
|
79
|
+
</script>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, reactive } from 'vue'
|
|
3
|
+
import { ElMessage } from 'element-plus'
|
|
4
|
+
import { useRouter } from 'vue-router'
|
|
5
|
+
import { useLogin } from '@duxweb/dvha-core'
|
|
6
|
+
import type { FormInstance } from 'element-plus'
|
|
7
|
+
import { User, Lock } from '@element-plus/icons-vue'
|
|
8
|
+
import 'element-plus/dist/index.css'
|
|
9
|
+
|
|
10
|
+
const router = useRouter()
|
|
11
|
+
|
|
12
|
+
const formRef = ref<FormInstance>()
|
|
13
|
+
|
|
14
|
+
const formModel = reactive({
|
|
15
|
+
username: '',
|
|
16
|
+
password: '',
|
|
17
|
+
remember: false
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const rules = reactive({
|
|
21
|
+
username: [
|
|
22
|
+
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
|
23
|
+
],
|
|
24
|
+
password: [
|
|
25
|
+
{ required: true, message: '请输入密码', trigger: 'blur' }
|
|
26
|
+
]
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const { mutate, isLoading } = useLogin({
|
|
30
|
+
onSuccess: () => {
|
|
31
|
+
ElMessage.success('登录成功')
|
|
32
|
+
},
|
|
33
|
+
onError: (error) => {
|
|
34
|
+
console.log(error)
|
|
35
|
+
ElMessage.error(error?.message || '登录失败,请检查用户名和密码')
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const handleSubmit = () => {
|
|
40
|
+
if (!formRef.value) return
|
|
41
|
+
|
|
42
|
+
formRef.value.validate((valid) => {
|
|
43
|
+
if (valid) {
|
|
44
|
+
mutate({
|
|
45
|
+
username: formModel.username,
|
|
46
|
+
password: formModel.password,
|
|
47
|
+
code: "0000"
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<template>
|
|
55
|
+
<div class="flex justify-center items-center min-h-screen bg-gray-100">
|
|
56
|
+
<div class="w-400px p-40px rounded-8px bg-white shadow-sm">
|
|
57
|
+
<div class="text-center mb-40px">
|
|
58
|
+
<h1 class="m-0 mb-8px text-24px font-600 text-gray-800">系统登录</h1>
|
|
59
|
+
<p class="m-0 text-gray-500 text-14px">欢迎使用管理系统,请登录</p>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<el-form
|
|
63
|
+
ref="formRef"
|
|
64
|
+
:model="formModel"
|
|
65
|
+
:rules="rules"
|
|
66
|
+
label-position="top"
|
|
67
|
+
>
|
|
68
|
+
<el-form-item prop="username">
|
|
69
|
+
<el-input
|
|
70
|
+
v-model="formModel.username"
|
|
71
|
+
placeholder="请输入用户名"
|
|
72
|
+
>
|
|
73
|
+
<template #prefix>
|
|
74
|
+
<el-icon><User /></el-icon>
|
|
75
|
+
</template>
|
|
76
|
+
</el-input>
|
|
77
|
+
</el-form-item>
|
|
78
|
+
|
|
79
|
+
<el-form-item prop="password">
|
|
80
|
+
<el-input
|
|
81
|
+
v-model="formModel.password"
|
|
82
|
+
type="password"
|
|
83
|
+
placeholder="请输入密码"
|
|
84
|
+
show-password
|
|
85
|
+
>
|
|
86
|
+
<template #prefix>
|
|
87
|
+
<el-icon><Lock /></el-icon>
|
|
88
|
+
</template>
|
|
89
|
+
</el-input>
|
|
90
|
+
</el-form-item>
|
|
91
|
+
|
|
92
|
+
<div class="flex justify-between items-center mb-4">
|
|
93
|
+
<el-checkbox v-model="formModel.remember">
|
|
94
|
+
记住我
|
|
95
|
+
</el-checkbox>
|
|
96
|
+
<el-button link type="primary">
|
|
97
|
+
忘记密码?
|
|
98
|
+
</el-button>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<el-button
|
|
102
|
+
type="primary"
|
|
103
|
+
class="w-full"
|
|
104
|
+
:loading="isLoading"
|
|
105
|
+
@click="handleSubmit"
|
|
106
|
+
>
|
|
107
|
+
登录
|
|
108
|
+
</el-button>
|
|
109
|
+
|
|
110
|
+
<div class="flex justify-center items-center mt-6 text-14px text-gray-500">
|
|
111
|
+
<span>还没有账号?</span>
|
|
112
|
+
<el-button link type="primary" class="ml-2" @click="router.push('/register')">
|
|
113
|
+
立即注册
|
|
114
|
+
</el-button>
|
|
115
|
+
</div>
|
|
116
|
+
</el-form>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</template>
|
|
120
|
+
|
|
121
|
+
<style scoped>
|
|
122
|
+
/* 所有CSS样式已替换为UnoCSS原子类 */
|
|
123
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<aside
|
|
3
|
+
:class="sidebarOpen ? 'translate-x-0' : '-translate-x-full'"
|
|
4
|
+
class="fixed inset-y-0 left-0 z-40 w-64 bg-white shadow-lg border-r pt-16 transition-transform lg:translate-x-0 lg:static lg:inset-0"
|
|
5
|
+
>
|
|
6
|
+
<div class="h-full overflow-y-auto p-4">
|
|
7
|
+
<nav class="space-y-2">
|
|
8
|
+
<router-link
|
|
9
|
+
v-for="menu in menuList"
|
|
10
|
+
:key="menu.name"
|
|
11
|
+
:to="menu.path"
|
|
12
|
+
class="flex items-center px-3 py-2 text-sm font-medium rounded-md"
|
|
13
|
+
:class="active === menu.name ? 'text-blue-600 bg-blue-50' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'"
|
|
14
|
+
>
|
|
15
|
+
<div :class="[menu.icon, 'mr-3']"></div>
|
|
16
|
+
{{ menu.label }}
|
|
17
|
+
</router-link>
|
|
18
|
+
</nav>
|
|
19
|
+
</div>
|
|
20
|
+
</aside>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import { useMenu } from '@duxweb/dvha-core'
|
|
25
|
+
|
|
26
|
+
defineProps<{
|
|
27
|
+
sidebarOpen: boolean
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
const { data: menuList,active } = useMenu()
|
|
31
|
+
|
|
32
|
+
</script>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "elementui",
|
|
3
|
+
"display": "Vue 3 + Element Plus",
|
|
4
|
+
"description": "使用 Element Plus 的 Vue 3 项目",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"@duxweb/dvha-core": "latest",
|
|
7
|
+
"@iconify-json/tabler": "^1.2.18",
|
|
8
|
+
"@unocss/preset-icons": "^66.1.2",
|
|
9
|
+
"@vueuse/core": "^13.2.0",
|
|
10
|
+
"@vueuse/integrations": "^13.2.0",
|
|
11
|
+
"axios": "^1.9.0",
|
|
12
|
+
"element-plus": "^2.9.10",
|
|
13
|
+
"lodash-es": "^4.17.21",
|
|
14
|
+
"unocss": "^66.1.2",
|
|
15
|
+
"@unocss/reset": "^66.1.2"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {},
|
|
18
|
+
"imports": [
|
|
19
|
+
"import '@unocss/reset/tailwind.css'",
|
|
20
|
+
"import 'virtual:uno.css'",
|
|
21
|
+
"import 'element-plus/dist/index.css'"
|
|
22
|
+
],
|
|
23
|
+
"appUse": [
|
|
24
|
+
"import ElementPlus from 'element-plus'",
|
|
25
|
+
"app.use(ElementPlus)"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="min-h-screen bg-gray-50 flex items-center justify-center p-4">
|
|
3
|
+
<div class="max-w-md w-full text-center">
|
|
4
|
+
<!-- 404 图标 -->
|
|
5
|
+
<div class="mb-8">
|
|
6
|
+
<div class="text-8xl font-bold text-gray-300 mb-4">404</div>
|
|
7
|
+
<div class="i-tabler:face-sad text-6xl text-gray-400 mx-auto"></div>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<!-- 错误信息 -->
|
|
11
|
+
<div class="mb-8">
|
|
12
|
+
<h1 class="text-2xl font-bold text-gray-900 mb-2">页面不存在</h1>
|
|
13
|
+
<p class="text-gray-600">抱歉,您访问的页面无法找到</p>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- 操作按钮 -->
|
|
17
|
+
<div class="space-y-3 mb-8">
|
|
18
|
+
<button
|
|
19
|
+
@click="goHome"
|
|
20
|
+
class="w-full flex items-center justify-center px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium"
|
|
21
|
+
>
|
|
22
|
+
<div class="i-tabler:home mr-2"></div>
|
|
23
|
+
返回首页
|
|
24
|
+
</button>
|
|
25
|
+
|
|
26
|
+
<button
|
|
27
|
+
@click="goBack"
|
|
28
|
+
class="w-full flex items-center justify-center px-6 py-3 border text-gray-700 rounded-lg hover:bg-gray-50 font-medium"
|
|
29
|
+
>
|
|
30
|
+
<div class="i-tabler:arrow-left mr-2"></div>
|
|
31
|
+
返回上页
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<!-- 底部信息 -->
|
|
36
|
+
<p class="text-xs text-gray-400">
|
|
37
|
+
错误代码: {{ errorCode }} | {{ currentTime }}
|
|
38
|
+
</p>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<script setup lang="ts">
|
|
44
|
+
import { ref, computed } from 'vue'
|
|
45
|
+
|
|
46
|
+
const errorCode = ref('ERR_404_NOT_FOUND')
|
|
47
|
+
|
|
48
|
+
const currentTime = computed(() => {
|
|
49
|
+
return new Date().toLocaleString('zh-CN')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const goHome = () => {
|
|
53
|
+
window.location.href = '/'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const goBack = () => {
|
|
57
|
+
if (window.history.length > 1) {
|
|
58
|
+
window.history.back()
|
|
59
|
+
} else {
|
|
60
|
+
goHome()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="p-6">
|
|
3
|
+
<!-- 欢迎区域 -->
|
|
4
|
+
<div class="bg-white rounded-lg shadow-sm border p-8 mb-8">
|
|
5
|
+
<div class="text-center">
|
|
6
|
+
<div class="i-tabler:home text-5xl text-blue-600 mx-auto mb-4"></div>
|
|
7
|
+
<h1 class="text-3xl font-bold text-gray-900 mb-4">欢迎使用 DVHA</h1>
|
|
8
|
+
<p class="text-gray-600 max-w-2xl mx-auto">
|
|
9
|
+
基于 Vue 3 + UnoCSS + @duxweb/dvha-core 构建的现代化管理系统
|
|
10
|
+
</p>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<!-- @duxweb/dvha-core 特点展示 -->
|
|
15
|
+
<div class="mb-8">
|
|
16
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
17
|
+
<div class="bg-white rounded-lg shadow-sm border p-6">
|
|
18
|
+
<div class="i-tabler:brand-vue text-3xl text-green-600 mb-4"></div>
|
|
19
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-2">Vue 3 支持</h3>
|
|
20
|
+
<p class="text-gray-600 text-sm">基于最新的 Vue 3 Composition API,提供现代化的开发体验</p>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="bg-white rounded-lg shadow-sm border p-6">
|
|
24
|
+
<div class="i-tabler:brand-typescript text-3xl text-blue-600 mb-4"></div>
|
|
25
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-2">TypeScript</h3>
|
|
26
|
+
<p class="text-gray-600 text-sm">完整的 TypeScript 支持,提供类型安全和更好的开发体验</p>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="bg-white rounded-lg shadow-sm border p-6">
|
|
30
|
+
<div class="i-tabler:layout-dashboard text-3xl text-purple-600 mb-4"></div>
|
|
31
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-2">多管理端</h3>
|
|
32
|
+
<p class="text-gray-600 text-sm">支持多个独立的管理端,每个管理端可以有不同的配置</p>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="bg-white rounded-lg shadow-sm border p-6">
|
|
36
|
+
<div class="i-tabler:database text-3xl text-orange-600 mb-4"></div>
|
|
37
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-2">数据提供者</h3>
|
|
38
|
+
<p class="text-gray-600 text-sm">灵活的数据提供者模式,支持各种 API 和数据源</p>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="bg-white rounded-lg shadow-sm border p-6">
|
|
42
|
+
<div class="i-tabler:shield-check text-3xl text-red-600 mb-4"></div>
|
|
43
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-2">认证系统</h3>
|
|
44
|
+
<p class="text-gray-600 text-sm">内置完整的认证和权限管理系统,支持多种认证方式</p>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="bg-white rounded-lg shadow-sm border p-6">
|
|
48
|
+
<div class="i-tabler:route text-3xl text-indigo-600 mb-4"></div>
|
|
49
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-2">路由管理</h3>
|
|
50
|
+
<p class="text-gray-600 text-sm">智能的路由和菜单管理,支持动态路由和权限控制</p>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<!-- 开始使用 -->
|
|
56
|
+
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-lg border p-8 text-center">
|
|
57
|
+
<h3 class="text-xl font-bold text-gray-900 mb-4">开始构建您的管理系统</h3>
|
|
58
|
+
<p class="text-gray-600 mb-6">@duxweb/dvha-core 为您提供了构建现代化管理系统所需的所有工具</p>
|
|
59
|
+
<div class="flex justify-center space-x-4">
|
|
60
|
+
<a href="https://github.com/duxweb/dux-vue" target="_blank"
|
|
61
|
+
class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm font-medium">
|
|
62
|
+
<div class="i-tabler:brand-github mr-2"></div>
|
|
63
|
+
查看源码
|
|
64
|
+
</a>
|
|
65
|
+
<a href="#" class="inline-flex items-center px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 text-sm font-medium">
|
|
66
|
+
<div class="i-tabler:book mr-2"></div>
|
|
67
|
+
查看文档
|
|
68
|
+
</a>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</template>
|
|
73
|
+
|
|
74
|
+
<script setup lang="ts">
|
|
75
|
+
// 首页逻辑
|
|
76
|
+
console.log('首页已加载')
|
|
77
|
+
</script>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="min-h-screen bg-gray-50 flex flex-col">
|
|
3
|
+
<!-- 头部 -->
|
|
4
|
+
<header class="bg-white shadow-sm border-b fixed w-full top-0 z-50">
|
|
5
|
+
<div class="flex items-center justify-between h-16 px-4">
|
|
6
|
+
<div class="flex items-center">
|
|
7
|
+
<button @click="toggleSidebar" class="p-2 rounded hover:bg-gray-100 lg:hidden">
|
|
8
|
+
<div class="i-tabler:menu-2 text-xl"></div>
|
|
9
|
+
</button>
|
|
10
|
+
<div class="flex items-center ml-4 lg:ml-0">
|
|
11
|
+
<div class="i-tabler:brand-vue text-2xl text-blue-600 mr-3"></div>
|
|
12
|
+
<h1 class="text-xl font-bold">{{ manage.config?.title || 'Dux Admin' }}</h1>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- 用户菜单 -->
|
|
17
|
+
<div class="relative">
|
|
18
|
+
<button @click="showUserMenu = !showUserMenu" class="flex items-center p-2 rounded hover:bg-gray-100">
|
|
19
|
+
<div class="i-tabler:user-circle text-xl mr-2"></div>
|
|
20
|
+
<span class="hidden md:block text-sm font-medium">{{ getUserName() }}</span>
|
|
21
|
+
<div class="i-tabler:chevron-down text-sm ml-1 hidden md:block"></div>
|
|
22
|
+
</button>
|
|
23
|
+
|
|
24
|
+
<div v-if="showUserMenu" class="absolute right-0 mt-2 w-40 bg-white rounded-md shadow-lg border z-50">
|
|
25
|
+
<div class="py-1">
|
|
26
|
+
<button @click="handleLogout" class="flex items-center w-full px-4 py-2 text-sm hover:bg-gray-100">
|
|
27
|
+
<div class="i-tabler:logout mr-3"></div>
|
|
28
|
+
退出登录
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</header>
|
|
35
|
+
|
|
36
|
+
<div class="flex pt-16 flex-1">
|
|
37
|
+
<!-- 侧边栏 -->
|
|
38
|
+
<Sidebar :sidebar-open="sidebarOpen" />
|
|
39
|
+
|
|
40
|
+
<!-- 遮罩层 -->
|
|
41
|
+
<div v-if="sidebarOpen" @click="toggleSidebar" class="fixed inset-0 z-30 bg-black bg-opacity-50 lg:hidden"></div>
|
|
42
|
+
|
|
43
|
+
<!-- 主内容 -->
|
|
44
|
+
<main class="flex-1">
|
|
45
|
+
<RouterView />
|
|
46
|
+
</main>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup lang="ts">
|
|
52
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
53
|
+
import { useManage, useGetAuth, useLogout } from '@duxweb/dvha-core'
|
|
54
|
+
import Sidebar from './menu.vue'
|
|
55
|
+
|
|
56
|
+
const manage = useManage()
|
|
57
|
+
const user = useGetAuth()
|
|
58
|
+
|
|
59
|
+
const { mutate: logout } = useLogout({
|
|
60
|
+
onSuccess: () => console.log('退出成功'),
|
|
61
|
+
onError: (error) => console.error('退出失败:', error)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const sidebarOpen = ref(false)
|
|
65
|
+
const showUserMenu = ref(false)
|
|
66
|
+
|
|
67
|
+
const getUserName = () => user?.info?.name || user?.info?.username || 'Admin'
|
|
68
|
+
const toggleSidebar = () => sidebarOpen.value = !sidebarOpen.value
|
|
69
|
+
const handleLogout = () => logout()
|
|
70
|
+
|
|
71
|
+
const closeDropdowns = (event: Event) => {
|
|
72
|
+
if (!(event.target as HTMLElement).closest('.relative')) {
|
|
73
|
+
showUserMenu.value = false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onMounted(() => document.addEventListener('click', closeDropdowns))
|
|
78
|
+
onUnmounted(() => document.removeEventListener('click', closeDropdowns))
|
|
79
|
+
</script>
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="min-h-screen bg-gray-100 flex items-center justify-center p-4">
|
|
3
|
+
<div class="w-full max-w-md">
|
|
4
|
+
<!-- 登录卡片 -->
|
|
5
|
+
<n-card class="login-card">
|
|
6
|
+
<!-- 标题区域 -->
|
|
7
|
+
<div class="text-center mb-8">
|
|
8
|
+
<div class="i-tabler:shield-check text-4xl text-blue-600 mx-auto mb-4"></div>
|
|
9
|
+
<h1 class="text-2xl font-bold text-gray-900 mb-2">系统登录</h1>
|
|
10
|
+
<p class="text-gray-500 text-sm">欢迎使用管理系统,请登录</p>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<!-- 登录表单 -->
|
|
14
|
+
<n-form
|
|
15
|
+
ref="formRef"
|
|
16
|
+
:model="form"
|
|
17
|
+
:rules="rules"
|
|
18
|
+
@submit.prevent="handleLogin"
|
|
19
|
+
size="large"
|
|
20
|
+
>
|
|
21
|
+
<!-- 用户名 -->
|
|
22
|
+
<n-form-item path="username">
|
|
23
|
+
<n-input
|
|
24
|
+
v-model:value="form.username"
|
|
25
|
+
placeholder="请输入用户名"
|
|
26
|
+
clearable
|
|
27
|
+
>
|
|
28
|
+
<template #prefix>
|
|
29
|
+
<div class="i-tabler:user text-gray-400"></div>
|
|
30
|
+
</template>
|
|
31
|
+
</n-input>
|
|
32
|
+
</n-form-item>
|
|
33
|
+
|
|
34
|
+
<!-- 密码 -->
|
|
35
|
+
<n-form-item path="password">
|
|
36
|
+
<n-input
|
|
37
|
+
v-model:value="form.password"
|
|
38
|
+
type="password"
|
|
39
|
+
placeholder="请输入密码"
|
|
40
|
+
show-password-on="click"
|
|
41
|
+
clearable
|
|
42
|
+
>
|
|
43
|
+
<template #prefix>
|
|
44
|
+
<div class="i-tabler:lock text-gray-400"></div>
|
|
45
|
+
</template>
|
|
46
|
+
</n-input>
|
|
47
|
+
</n-form-item>
|
|
48
|
+
|
|
49
|
+
<!-- 记住我和忘记密码 -->
|
|
50
|
+
<div class="flex items-center justify-between mb-6">
|
|
51
|
+
<n-checkbox v-model:checked="form.remember">记住我</n-checkbox>
|
|
52
|
+
<n-button text type="primary" @click="handleForgotPassword">忘记密码?</n-button>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<!-- 登录按钮 -->
|
|
56
|
+
<n-form-item>
|
|
57
|
+
<n-button
|
|
58
|
+
type="primary"
|
|
59
|
+
:loading="loading"
|
|
60
|
+
@click="handleLogin"
|
|
61
|
+
block
|
|
62
|
+
size="large"
|
|
63
|
+
>
|
|
64
|
+
{{ loading ? '登录中...' : '登录' }}
|
|
65
|
+
</n-button>
|
|
66
|
+
</n-form-item>
|
|
67
|
+
</n-form>
|
|
68
|
+
|
|
69
|
+
<!-- 注册提示 -->
|
|
70
|
+
<div class="text-center">
|
|
71
|
+
<span class="text-sm text-gray-500">还没有账号?</span>
|
|
72
|
+
<n-button text type="primary" @click="handleRegister" class="ml-1">立即注册</n-button>
|
|
73
|
+
</div>
|
|
74
|
+
</n-card>
|
|
75
|
+
|
|
76
|
+
<!-- 版权信息 -->
|
|
77
|
+
<p class="mt-6 text-center text-xs text-gray-400">
|
|
78
|
+
© 2024 Dux Template. All rights reserved.
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</template>
|
|
83
|
+
|
|
84
|
+
<script setup lang="ts">
|
|
85
|
+
import { ref, reactive } from 'vue'
|
|
86
|
+
import { useMessage, type FormInst } from 'naive-ui'
|
|
87
|
+
import { useLogin } from '@duxweb/dvha-core'
|
|
88
|
+
|
|
89
|
+
const message = useMessage()
|
|
90
|
+
const formRef = ref<FormInst | null>(null)
|
|
91
|
+
|
|
92
|
+
const form = reactive({
|
|
93
|
+
username: '',
|
|
94
|
+
password: '',
|
|
95
|
+
remember: false
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const rules = {
|
|
99
|
+
username: [
|
|
100
|
+
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
|
101
|
+
],
|
|
102
|
+
password: [
|
|
103
|
+
{ required: true, message: '请输入密码', trigger: 'blur' },
|
|
104
|
+
{ min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const { mutate: login, isLoading: loading } = useLogin({
|
|
109
|
+
onSuccess: () => {
|
|
110
|
+
message.success('登录成功')
|
|
111
|
+
},
|
|
112
|
+
onError: (error) => {
|
|
113
|
+
console.error('登录失败:', error)
|
|
114
|
+
message.error(error?.message || '登录失败,请检查用户名和密码')
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const handleLogin = async () => {
|
|
119
|
+
if (!formRef.value) return
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await formRef.value.validate()
|
|
123
|
+
await login({
|
|
124
|
+
username: form.username,
|
|
125
|
+
password: form.password,
|
|
126
|
+
remember: form.remember
|
|
127
|
+
})
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('表单验证失败:', error)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const handleForgotPassword = () => {
|
|
134
|
+
message.info('忘记密码功能待开发')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const handleRegister = () => {
|
|
138
|
+
message.info('注册功能待开发')
|
|
139
|
+
}
|
|
140
|
+
</script>
|
|
141
|
+
|
|
142
|
+
<style scoped>
|
|
143
|
+
.login-card {
|
|
144
|
+
padding: 2rem;
|
|
145
|
+
}
|
|
146
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<aside
|
|
3
|
+
:class="sidebarOpen ? 'translate-x-0' : '-translate-x-full'"
|
|
4
|
+
class="fixed inset-y-0 left-0 z-40 w-64 bg-white shadow-lg border-r pt-16 transition-transform lg:translate-x-0 lg:static lg:inset-0"
|
|
5
|
+
>
|
|
6
|
+
<div class="h-full overflow-y-auto p-4">
|
|
7
|
+
<nav class="space-y-2">
|
|
8
|
+
<router-link
|
|
9
|
+
v-for="menu in menuList"
|
|
10
|
+
:key="menu.name"
|
|
11
|
+
:to="menu.path"
|
|
12
|
+
class="flex items-center px-3 py-2 text-sm font-medium rounded-md"
|
|
13
|
+
:class="active === menu.name ? 'text-blue-600 bg-blue-50' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'"
|
|
14
|
+
>
|
|
15
|
+
<div :class="[menu.icon, 'mr-3']"></div>
|
|
16
|
+
{{ menu.label }}
|
|
17
|
+
</router-link>
|
|
18
|
+
</nav>
|
|
19
|
+
</div>
|
|
20
|
+
</aside>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import { useMenu } from '@duxweb/dvha-core'
|
|
25
|
+
|
|
26
|
+
defineProps<{
|
|
27
|
+
sidebarOpen: boolean
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
const { data: menuList,active } = useMenu()
|
|
31
|
+
|
|
32
|
+
</script>
|