xto-fronted 0.2.6 → 0.2.7
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/.env.development +4 -0
- package/.env.production +4 -0
- package/README.md +94 -196
- package/dist/{components/Layout/TopMenu.vue.d.ts → App.vue.d.ts} +1 -1
- package/dist/api/auth.d.ts +8 -10
- package/dist/api/system.d.ts +11 -12
- package/dist/api/user.d.ts +12 -3
- package/dist/components/Layout/Footer.vue.d.ts +1 -1
- package/dist/components/Layout/Header.vue.d.ts +3 -14
- package/dist/components/Layout/Sidebar.vue.d.ts +1 -1
- package/dist/components/Layout/Tabs.vue.d.ts +1 -1
- package/dist/components/Layout/index.vue.d.ts +1 -1
- package/dist/composables/useAuth.d.ts +4 -19
- package/dist/directives/permission.d.ts +0 -1
- package/dist/index-CWRs4WMN.js +372 -0
- package/dist/index-CpxpXTQX.js +1462 -0
- package/dist/index-Cu3Z2-PY.js +345 -0
- package/dist/index-DPEVEyik.js +475 -0
- package/dist/index-DYnXaqYf.js +142 -0
- package/dist/index.d.ts +12 -25
- package/dist/index.es.js +76 -1521
- package/dist/index.umd.js +1 -20
- package/dist/main.d.ts +0 -1
- package/dist/router/dynamicRoutes.d.ts +33 -17
- package/dist/router/index.d.ts +4 -26
- package/dist/router/layoutRoute.d.ts +18 -0
- package/dist/router/staticRoutes.d.ts +2 -18
- package/dist/setup.d.ts +17 -0
- package/dist/stores/app.d.ts +15 -9
- package/dist/stores/auth.d.ts +48 -62
- package/dist/stores/index.d.ts +3 -1
- package/dist/stores/menu.d.ts +29 -47
- package/dist/stores/user.d.ts +84 -64
- package/dist/style.css +1 -1
- package/dist/utils/auth.d.ts +10 -10
- package/dist/utils/permission.d.ts +10 -1
- package/dist/utils/request.d.ts +7 -23
- package/dist/{components/Layout/Breadcrumb.vue.d.ts → views/dashboard/index.vue.d.ts} +1 -1
- package/dist/{components/Error → views/error}/403.vue.d.ts +1 -1
- package/dist/{components/Error → views/error}/404.vue.d.ts +1 -1
- package/dist/views/login/index.vue.d.ts +4 -0
- package/dist/views/system/menu/index.vue.d.ts +4 -0
- package/dist/views/system/role/index.vue.d.ts +4 -0
- package/dist/views/system/user/index.vue.d.ts +4 -0
- package/dist/vite.svg +9 -9
- package/index.html +13 -0
- package/package.json +27 -31
- package/public/vite.svg +10 -0
- package/src/App.vue +20 -0
- package/src/api/auth.ts +26 -0
- package/src/api/system.ts +65 -0
- package/src/api/user.ts +46 -0
- package/src/assets/styles/_dark.scss +407 -0
- package/src/assets/styles/_reset.scss +126 -0
- package/src/assets/styles/_root.scss +140 -0
- package/src/assets/styles/_transition.scss +119 -0
- package/src/assets/styles/_variables.scss +45 -0
- package/src/assets/styles/index.scss +187 -0
- package/src/components/Layout/Footer.vue +17 -0
- package/src/components/Layout/Header.vue +390 -0
- package/src/components/Layout/Sidebar.vue +297 -0
- package/src/components/Layout/Tabs.vue +134 -0
- package/src/components/Layout/index.vue +62 -0
- package/src/composables/useAuth.ts +45 -0
- package/src/composables/useForm.ts +79 -0
- package/src/composables/useTable.ts +97 -0
- package/src/directives/permission.ts +38 -0
- package/src/enums/index.ts +63 -0
- package/src/env.d.ts +17 -0
- package/src/index.ts +48 -0
- package/src/main.ts +34 -0
- package/src/router/dynamicRoutes.ts +163 -0
- package/src/router/index.ts +81 -0
- package/src/router/layoutRoute.ts +45 -0
- package/src/router/staticRoutes.ts +43 -0
- package/src/setup.ts +54 -0
- package/src/stores/app.ts +163 -0
- package/src/stores/auth.ts +66 -0
- package/src/stores/index.ts +15 -0
- package/src/stores/menu.ts +80 -0
- package/src/stores/user.ts +73 -0
- package/src/style.css +11 -0
- package/src/types/api.d.ts +84 -0
- package/src/types/global.d.ts +45 -0
- package/src/types/router.d.ts +48 -0
- package/src/types/xto.d.ts +149 -0
- package/src/utils/auth.ts +62 -0
- package/src/utils/permission.ts +42 -0
- package/src/utils/request.ts +124 -0
- package/src/utils/storage.ts +63 -0
- package/src/views/dashboard/index.vue +284 -0
- package/src/views/error/403.vue +57 -0
- package/src/views/error/404.vue +57 -0
- package/src/views/login/index.vue +248 -0
- package/src/views/system/menu/index.vue +381 -0
- package/src/views/system/role/index.vue +304 -0
- package/src/views/system/user/index.vue +327 -0
- package/tsconfig.json +26 -0
- package/tsconfig.node.json +11 -0
- package/vite.config.ts +140 -0
- package/dist/api/menu.d.ts +0 -4
- package/dist/components/Login/index.vue.d.ts +0 -25
- package/dist/components/SettingDrawer/index.vue.d.ts +0 -19
- package/dist/composables/index.d.ts +0 -8
- package/dist/composables/useApp.d.ts +0 -65
- package/dist/composables/useMenu.d.ts +0 -34
- package/dist/config/index.d.ts +0 -31
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { useRoute, useRouter } from 'vue-router'
|
|
4
|
+
import { Tabs, TabPane } from '@xto/navigation'
|
|
5
|
+
|
|
6
|
+
const route = useRoute()
|
|
7
|
+
const router = useRouter()
|
|
8
|
+
|
|
9
|
+
// 标签页列表
|
|
10
|
+
const tabList = computed(() => {
|
|
11
|
+
const tabs: { name: string; title: string; path: string; affix: boolean }[] = []
|
|
12
|
+
|
|
13
|
+
// 添加固定标签
|
|
14
|
+
route.matched.forEach(r => {
|
|
15
|
+
if (r.meta?.affix) {
|
|
16
|
+
tabs.push({
|
|
17
|
+
name: r.name as string,
|
|
18
|
+
title: r.meta.title as string,
|
|
19
|
+
path: r.path,
|
|
20
|
+
affix: true
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return tabs
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// 当前激活的标签
|
|
29
|
+
const activeTab = computed(() => route.path)
|
|
30
|
+
|
|
31
|
+
// 点击标签
|
|
32
|
+
const handleClick = (path: string) => {
|
|
33
|
+
router.push(path)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 关闭标签
|
|
37
|
+
const handleClose = (path: string) => {
|
|
38
|
+
// 固定标签不能关闭
|
|
39
|
+
const tab = tabList.value.find(t => t.path === path)
|
|
40
|
+
if (tab?.affix) return
|
|
41
|
+
|
|
42
|
+
// 如果关闭的是当前标签,跳转到前一个标签
|
|
43
|
+
if (path === activeTab.value) {
|
|
44
|
+
const index = tabList.value.findIndex(t => t.path === path)
|
|
45
|
+
const nextTab = tabList.value[index - 1] || tabList.value[index + 1]
|
|
46
|
+
if (nextTab) {
|
|
47
|
+
router.push(nextTab.path)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<template>
|
|
54
|
+
<div class="tabs-wrapper">
|
|
55
|
+
<Tabs v-model="activeTab" type="card" @tab-click="handleClick">
|
|
56
|
+
<TabPane
|
|
57
|
+
v-for="tab in tabList"
|
|
58
|
+
:key="tab.path"
|
|
59
|
+
:name="tab.path"
|
|
60
|
+
:label="tab.title"
|
|
61
|
+
:closable="!tab.affix"
|
|
62
|
+
>
|
|
63
|
+
<template #label>
|
|
64
|
+
<span class="tab-label">
|
|
65
|
+
{{ tab.title }}
|
|
66
|
+
<span
|
|
67
|
+
v-if="!tab.affix"
|
|
68
|
+
class="tab-close"
|
|
69
|
+
@click.stop="handleClose(tab.path)"
|
|
70
|
+
>
|
|
71
|
+
✕
|
|
72
|
+
</span>
|
|
73
|
+
</span>
|
|
74
|
+
</template>
|
|
75
|
+
</TabPane>
|
|
76
|
+
</Tabs>
|
|
77
|
+
</div>
|
|
78
|
+
</template>
|
|
79
|
+
|
|
80
|
+
<style lang="scss" scoped>
|
|
81
|
+
.tabs-wrapper {
|
|
82
|
+
width: 100%;
|
|
83
|
+
height: 100%;
|
|
84
|
+
padding: 0 10px;
|
|
85
|
+
|
|
86
|
+
:deep(.t-tabs) {
|
|
87
|
+
height: 100%;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
:deep(.t-tabs__header) {
|
|
91
|
+
margin: 0;
|
|
92
|
+
border-bottom: none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
:deep(.t-tabs__nav) {
|
|
96
|
+
border: none;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
:deep(.t-tabs__item) {
|
|
100
|
+
height: 32px;
|
|
101
|
+
line-height: 32px;
|
|
102
|
+
border: 1px solid var(--color-border-lighter);
|
|
103
|
+
margin-right: 5px;
|
|
104
|
+
border-radius: var(--border-radius-base);
|
|
105
|
+
padding: 0 15px;
|
|
106
|
+
|
|
107
|
+
&.is-active {
|
|
108
|
+
background-color: var(--color-primary-light-9);
|
|
109
|
+
border-color: var(--color-primary);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.tab-label {
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
gap: 5px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.tab-close {
|
|
121
|
+
width: 14px;
|
|
122
|
+
height: 14px;
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
justify-content: center;
|
|
126
|
+
font-size: 10px;
|
|
127
|
+
border-radius: 50%;
|
|
128
|
+
|
|
129
|
+
&:hover {
|
|
130
|
+
background-color: var(--color-danger-light);
|
|
131
|
+
color: var(--color-danger);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
</style>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { useAppStore } from '@/stores/app'
|
|
4
|
+
import Sidebar from './Sidebar.vue'
|
|
5
|
+
import Header from './Header.vue'
|
|
6
|
+
|
|
7
|
+
const appStore = useAppStore()
|
|
8
|
+
|
|
9
|
+
const sidebarWidth = computed(() =>
|
|
10
|
+
appStore.isCollapsed ? '64px' : '210px'
|
|
11
|
+
)
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div class="layout">
|
|
16
|
+
<aside class="layout__aside" :style="{ width: sidebarWidth }">
|
|
17
|
+
<Sidebar />
|
|
18
|
+
</aside>
|
|
19
|
+
<div class="layout__main">
|
|
20
|
+
<Header class="layout__header" />
|
|
21
|
+
<main class="layout__content">
|
|
22
|
+
<router-view />
|
|
23
|
+
</main>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<style lang="scss" scoped>
|
|
29
|
+
.layout {
|
|
30
|
+
display: flex;
|
|
31
|
+
width: 100%;
|
|
32
|
+
height: 100%;
|
|
33
|
+
|
|
34
|
+
&__aside {
|
|
35
|
+
transition: width 0.3s;
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
flex-shrink: 0;
|
|
38
|
+
height: 100%;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&__main {
|
|
42
|
+
flex: 1;
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
height: 100%;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&__header {
|
|
50
|
+
height: 50px;
|
|
51
|
+
background-color: var(--bg-color);
|
|
52
|
+
border-bottom: 1px solid var(--color-border-lighter);
|
|
53
|
+
flex-shrink: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&__content {
|
|
57
|
+
flex: 1;
|
|
58
|
+
overflow: auto;
|
|
59
|
+
background-color: var(--bg-color-page);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 权限组合函数
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { computed } from 'vue'
|
|
6
|
+
import { useUserStore } from '@/stores/user'
|
|
7
|
+
|
|
8
|
+
export function useAuth() {
|
|
9
|
+
const userStore = useUserStore()
|
|
10
|
+
|
|
11
|
+
// 检查权限
|
|
12
|
+
const hasPermission = (permission: string | string[]): boolean => {
|
|
13
|
+
const permissions = userStore.permissions
|
|
14
|
+
if (permissions.includes('*')) return true
|
|
15
|
+
|
|
16
|
+
if (Array.isArray(permission)) {
|
|
17
|
+
return permission.some(p => permissions.includes(p))
|
|
18
|
+
}
|
|
19
|
+
return permissions.includes(permission)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 检查角色
|
|
23
|
+
const hasRole = (role: string | string[]): boolean => {
|
|
24
|
+
const roles = userStore.roles
|
|
25
|
+
if (roles.includes('admin')) return true
|
|
26
|
+
|
|
27
|
+
if (Array.isArray(role)) {
|
|
28
|
+
return role.some(r => roles.includes(r))
|
|
29
|
+
}
|
|
30
|
+
return roles.includes(role)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 是否是管理员
|
|
34
|
+
const isAdmin = computed(() => userStore.roles.includes('admin'))
|
|
35
|
+
|
|
36
|
+
// 是否已登录
|
|
37
|
+
const isLoggedIn = computed(() => userStore.isLoggedIn)
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
hasPermission,
|
|
41
|
+
hasRole,
|
|
42
|
+
isAdmin,
|
|
43
|
+
isLoggedIn
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表单组合函数
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ref, reactive } from 'vue'
|
|
6
|
+
|
|
7
|
+
export interface FormOptions<T = any> {
|
|
8
|
+
rules?: Record<string, any[]>
|
|
9
|
+
onSubmit?: (data: T) => Promise<void> | void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
13
|
+
initialValues: T,
|
|
14
|
+
options: FormOptions<T> = {}
|
|
15
|
+
) {
|
|
16
|
+
const { rules, onSubmit } = options
|
|
17
|
+
|
|
18
|
+
const formRef = ref()
|
|
19
|
+
const formData = reactive<T>({ ...initialValues } as T)
|
|
20
|
+
const loading = ref(false)
|
|
21
|
+
const visible = ref(false)
|
|
22
|
+
const isEdit = ref(false)
|
|
23
|
+
|
|
24
|
+
// 打开新增
|
|
25
|
+
const openAdd = () => {
|
|
26
|
+
resetForm()
|
|
27
|
+
isEdit.value = false
|
|
28
|
+
visible.value = true
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 打开编辑
|
|
32
|
+
const openEdit = (data: Partial<T>) => {
|
|
33
|
+
Object.assign(formData, data)
|
|
34
|
+
isEdit.value = true
|
|
35
|
+
visible.value = true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 关闭
|
|
39
|
+
const close = () => {
|
|
40
|
+
visible.value = false
|
|
41
|
+
resetForm()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 重置表单
|
|
45
|
+
const resetForm = () => {
|
|
46
|
+
Object.keys(initialValues).forEach(key => {
|
|
47
|
+
(formData as any)[key] = initialValues[key]
|
|
48
|
+
})
|
|
49
|
+
formRef.value?.resetFields()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 提交
|
|
53
|
+
const handleSubmit = async () => {
|
|
54
|
+
try {
|
|
55
|
+
await formRef.value?.validate()
|
|
56
|
+
loading.value = true
|
|
57
|
+
await onSubmit?.(formData as T)
|
|
58
|
+
close()
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(error)
|
|
61
|
+
} finally {
|
|
62
|
+
loading.value = false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
formRef,
|
|
68
|
+
formData,
|
|
69
|
+
rules,
|
|
70
|
+
loading,
|
|
71
|
+
visible,
|
|
72
|
+
isEdit,
|
|
73
|
+
openAdd,
|
|
74
|
+
openEdit,
|
|
75
|
+
close,
|
|
76
|
+
resetForm,
|
|
77
|
+
handleSubmit
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表格组合函数
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ref, reactive, computed } from 'vue'
|
|
6
|
+
|
|
7
|
+
export interface TableOptions<T = any> {
|
|
8
|
+
fetchData: (params: any) => Promise<{ list: T[]; total: number }>
|
|
9
|
+
defaultPageSize?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useTable<T = any>(options: TableOptions<T>) {
|
|
13
|
+
const { fetchData, defaultPageSize = 10 } = options
|
|
14
|
+
|
|
15
|
+
const loading = ref(false)
|
|
16
|
+
const data = ref<T[]>([]) as any
|
|
17
|
+
const total = ref(0)
|
|
18
|
+
const currentPage = ref(1)
|
|
19
|
+
const pageSize = ref(defaultPageSize)
|
|
20
|
+
|
|
21
|
+
const searchParams = reactive<Record<string, any>>({})
|
|
22
|
+
|
|
23
|
+
// 获取数据
|
|
24
|
+
const getData = async () => {
|
|
25
|
+
loading.value = true
|
|
26
|
+
try {
|
|
27
|
+
const params = {
|
|
28
|
+
...searchParams,
|
|
29
|
+
page: currentPage.value,
|
|
30
|
+
pageSize: pageSize.value
|
|
31
|
+
}
|
|
32
|
+
const result = await fetchData(params)
|
|
33
|
+
data.value = result.list
|
|
34
|
+
total.value = result.total
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(error)
|
|
37
|
+
} finally {
|
|
38
|
+
loading.value = false
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 搜索
|
|
43
|
+
const handleSearch = () => {
|
|
44
|
+
currentPage.value = 1
|
|
45
|
+
getData()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 重置
|
|
49
|
+
const handleReset = () => {
|
|
50
|
+
Object.keys(searchParams).forEach(key => {
|
|
51
|
+
searchParams[key] = undefined
|
|
52
|
+
})
|
|
53
|
+
currentPage.value = 1
|
|
54
|
+
getData()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 分页改变
|
|
58
|
+
const handlePageChange = (page: number) => {
|
|
59
|
+
currentPage.value = page
|
|
60
|
+
getData()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 每页条数改变
|
|
64
|
+
const handleSizeChange = (size: number) => {
|
|
65
|
+
pageSize.value = size
|
|
66
|
+
currentPage.value = 1
|
|
67
|
+
getData()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 刷新
|
|
71
|
+
const refresh = () => {
|
|
72
|
+
getData()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 分页配置
|
|
76
|
+
const pagination = computed(() => ({
|
|
77
|
+
current: currentPage.value,
|
|
78
|
+
pageSize: pageSize.value,
|
|
79
|
+
total: total.value
|
|
80
|
+
}))
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
loading,
|
|
84
|
+
data,
|
|
85
|
+
total,
|
|
86
|
+
currentPage,
|
|
87
|
+
pageSize,
|
|
88
|
+
searchParams,
|
|
89
|
+
pagination,
|
|
90
|
+
getData,
|
|
91
|
+
handleSearch,
|
|
92
|
+
handleReset,
|
|
93
|
+
handlePageChange,
|
|
94
|
+
handleSizeChange,
|
|
95
|
+
refresh
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 权限指令
|
|
3
|
+
* v-permission="['user:edit']" 或 v-permission="'user:edit'"
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Directive, DirectiveBinding } from 'vue'
|
|
7
|
+
import { useUserStore } from '@/stores/user'
|
|
8
|
+
|
|
9
|
+
const permission: Directive = {
|
|
10
|
+
mounted(el: HTMLElement, binding: DirectiveBinding<string | string[]>) {
|
|
11
|
+
const userStore = useUserStore()
|
|
12
|
+
const { value } = binding
|
|
13
|
+
|
|
14
|
+
if (!value) return
|
|
15
|
+
|
|
16
|
+
const permissions = userStore.permissions
|
|
17
|
+
|
|
18
|
+
// 判断是否有权限
|
|
19
|
+
let hasPermission = false
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
hasPermission = value.some(p => permissions.includes(p) || permissions.includes('*'))
|
|
22
|
+
} else {
|
|
23
|
+
hasPermission = permissions.includes(value) || permissions.includes('*')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 没有权限则移除元素
|
|
27
|
+
if (!hasPermission) {
|
|
28
|
+
el.parentNode?.removeChild(el)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default permission
|
|
34
|
+
|
|
35
|
+
// 注册指令
|
|
36
|
+
export function setupPermissionDirective(app: any) {
|
|
37
|
+
app.directive('permission', permission)
|
|
38
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 枚举常量
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 状态
|
|
6
|
+
export enum Status {
|
|
7
|
+
ENABLED = 1,
|
|
8
|
+
DISABLED = 0
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 性别
|
|
12
|
+
export enum Gender {
|
|
13
|
+
UNKNOWN = 0,
|
|
14
|
+
MALE = 1,
|
|
15
|
+
FEMALE = 2
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 菜单类型
|
|
19
|
+
export enum MenuType {
|
|
20
|
+
DIRECTORY = 0,
|
|
21
|
+
MENU = 1,
|
|
22
|
+
BUTTON = 2
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 状态文本映射
|
|
26
|
+
export const StatusText: Record<Status, string> = {
|
|
27
|
+
[Status.ENABLED]: '启用',
|
|
28
|
+
[Status.DISABLED]: '禁用'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 性别文本映射
|
|
32
|
+
export const GenderText: Record<Gender, string> = {
|
|
33
|
+
[Gender.UNKNOWN]: '未知',
|
|
34
|
+
[Gender.MALE]: '男',
|
|
35
|
+
[Gender.FEMALE]: '女'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 菜单类型文本映射
|
|
39
|
+
export const MenuTypeText: Record<MenuType, string> = {
|
|
40
|
+
[MenuType.DIRECTORY]: '目录',
|
|
41
|
+
[MenuType.MENU]: '菜单',
|
|
42
|
+
[MenuType.BUTTON]: '按钮'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 状态选项
|
|
46
|
+
export const StatusOptions = [
|
|
47
|
+
{ label: '启用', value: Status.ENABLED },
|
|
48
|
+
{ label: '禁用', value: Status.DISABLED }
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
// 性别选项
|
|
52
|
+
export const GenderOptions = [
|
|
53
|
+
{ label: '未知', value: Gender.UNKNOWN },
|
|
54
|
+
{ label: '男', value: Gender.MALE },
|
|
55
|
+
{ label: '女', value: Gender.FEMALE }
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
// 菜单类型选项
|
|
59
|
+
export const MenuTypeOptions = [
|
|
60
|
+
{ label: '目录', value: MenuType.DIRECTORY },
|
|
61
|
+
{ label: '菜单', value: MenuType.MENU },
|
|
62
|
+
{ label: '按钮', value: MenuType.BUTTON }
|
|
63
|
+
]
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
declare module '*.vue' {
|
|
4
|
+
import type { DefineComponent } from 'vue'
|
|
5
|
+
const component: DefineComponent<{}, {}, any>
|
|
6
|
+
export default component
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ImportMetaEnv {
|
|
10
|
+
readonly VITE_APP_TITLE: string
|
|
11
|
+
readonly VITE_API_BASE_URL: string
|
|
12
|
+
readonly VITE_USE_MOCK: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ImportMeta {
|
|
16
|
+
readonly env: ImportMetaEnv
|
|
17
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// 组件
|
|
2
|
+
export { default as Layout } from './components/Layout/index.vue'
|
|
3
|
+
export { default as Header } from './components/Layout/Header.vue'
|
|
4
|
+
export { default as Sidebar } from './components/Layout/Sidebar.vue'
|
|
5
|
+
export { default as Tabs } from './components/Layout/Tabs.vue'
|
|
6
|
+
export { default as Footer } from './components/Layout/Footer.vue'
|
|
7
|
+
|
|
8
|
+
// 错误页面组件
|
|
9
|
+
export { default as Login } from './views/login/index.vue'
|
|
10
|
+
export { default as NotFound } from './views/error/404.vue'
|
|
11
|
+
export { default as Forbidden } from './views/error/403.vue'
|
|
12
|
+
|
|
13
|
+
// 组合式函数
|
|
14
|
+
export * from './composables/useAuth'
|
|
15
|
+
export * from './composables/useForm'
|
|
16
|
+
export * from './composables/useTable'
|
|
17
|
+
|
|
18
|
+
// 工具函数
|
|
19
|
+
export * from './utils/auth'
|
|
20
|
+
export * from './utils/permission'
|
|
21
|
+
export * from './utils/request'
|
|
22
|
+
export * from './utils/storage'
|
|
23
|
+
|
|
24
|
+
// Store
|
|
25
|
+
export * from './stores/app'
|
|
26
|
+
export * from './stores/auth'
|
|
27
|
+
export * from './stores/menu'
|
|
28
|
+
export * from './stores/user'
|
|
29
|
+
|
|
30
|
+
// 路由
|
|
31
|
+
export { default as router, resetRouter } from './router'
|
|
32
|
+
export * from './router/staticRoutes'
|
|
33
|
+
export * from './router/dynamicRoutes'
|
|
34
|
+
export { createLayoutRoute, createRouter } from './router/layoutRoute'
|
|
35
|
+
|
|
36
|
+
// API
|
|
37
|
+
export * from './api/auth'
|
|
38
|
+
export * from './api/system'
|
|
39
|
+
export * from './api/user'
|
|
40
|
+
|
|
41
|
+
// 枚举
|
|
42
|
+
export * from './enums'
|
|
43
|
+
|
|
44
|
+
// 指令
|
|
45
|
+
export { default as permissionDirective } from './directives/permission'
|
|
46
|
+
|
|
47
|
+
// 应用初始化
|
|
48
|
+
export { createXtoApp } from './setup'
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createApp } from 'vue'
|
|
2
|
+
import { createPinia } from 'pinia'
|
|
3
|
+
import App from './App.vue'
|
|
4
|
+
import router from './router'
|
|
5
|
+
|
|
6
|
+
// 样式
|
|
7
|
+
import '@/assets/styles/index.scss'
|
|
8
|
+
|
|
9
|
+
// 组件库样式
|
|
10
|
+
import '@xto/base/es/style.css'
|
|
11
|
+
import '@xto/form/es/style.css'
|
|
12
|
+
import '@xto/data/es/style.css'
|
|
13
|
+
import '@xto/feedback/es/style.css'
|
|
14
|
+
import '@xto/navigation/es/style.css'
|
|
15
|
+
import '@xto/layout/es/style.css'
|
|
16
|
+
import '@xto/business/es/style.css'
|
|
17
|
+
|
|
18
|
+
// 全局注册内部组件(解决 resolveComponent 问题)
|
|
19
|
+
import { Loading } from '@xto/base'
|
|
20
|
+
import { TreeNode } from '@xto/data'
|
|
21
|
+
|
|
22
|
+
const app = createApp(App)
|
|
23
|
+
|
|
24
|
+
// 注册内部组件
|
|
25
|
+
app.component('x-loading', Loading)
|
|
26
|
+
app.component('TreeNode', TreeNode)
|
|
27
|
+
|
|
28
|
+
// Pinia
|
|
29
|
+
app.use(createPinia())
|
|
30
|
+
|
|
31
|
+
// Router
|
|
32
|
+
app.use(router)
|
|
33
|
+
|
|
34
|
+
app.mount('#app')
|