前言
公司项目做一个后台系统,在网上找了一圈模板,最后还是决定自己做。
线上demo : Ds-Admin admin 123456
github: GitHub - guanshundai/vue3-ts-admin
这里只展示部分代码, 感兴趣的去看源码吧
开始
npm init vue@latest
选 jsx、ts、pinia、router 其他看你自己
yarn add less less-loader
yarn add ant-design-vue
yarn add unplugin-vue-components //按需引入
vite.config.ts
import { fileURLToPath, URL } from 'url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
// https://vitejs.dev/config/
export default defineConfig({
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true
},
},
},
plugins: [vue(), vueJsx(),Components({
resolvers: [AntDesignVueResolver()],
}),],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
host: '0.0.0.0',
port: 9900,
open: true
}
})
main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist' //持久化状态管理
import 'ant-design-vue/dist/antd.less';
import '@/assets/base.less'
import defineComponents from '@/utils/defineComponents' //全局注册自定义组件
import App from './App.vue'
import router from './router'
import { useStore } from './stores/useStore';
const pinia = createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia.use(piniaPluginPersist))
app.use(defineComponents)
router.beforeEach((to) => {
const { token } = useStore(pinia)
if (to.path !== '/login' && !token) return '/login'
})
app.mount('#app')
App.vue
<template>
<a-spin :spinning="spinning">
<router-view v-if="isRefresh"></router-view>
</a-spin>
</template>
<script setup lang='ts'>
import { ref, provide, nextTick } from 'vue'
const isRefresh = ref<boolean>(true)
const spinning = ref<boolean>(false)
let timer: any = null
//刷新
const refresh = async () => {
isRefresh.value = false;
spinning.value = true
await nextTick()
isRefresh.value = true;
//loding效果
timer = setTimeout(() => {
spinning.value = false
timer = null
}, 500)
};
provide('refresh', refresh)
</script>
<style scoped lang='less'>
</style>
router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Layout from '../views/index.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
redirect: {name: 'option1'},
component: Layout,
children: [
{
path: 'option1',
name: 'option1',
component: () => import('@/views/Home/Option1.vue')
},
{
path: 'option2',
name: 'option2',
component: () => import('@/views/Home/Option2.vue')
},
]
},
{
path: '/nav2',
name: 'nav2',
redirect: { name: 'nav2Option1' },
component: Layout,
children: [
{
path: 'option1',
name: 'nav2Option1',
component: () => import('@/views/Nav2/option1.vue')
},
{
path: 'option2',
name: 'nav2Option2',
component: () => import('@/views/Nav2/option2.vue')
}
]
},
{
path: '/login',
name: 'login',
component: () => import('@/views/Login/index.vue')
},
]
})
export default router
view/index.vue
<template>
<a-layout style="min-height: 100vh">
<Sider :select="select" :sideKey="sideKey" />
<a-layout>
<Header @getKey="getKey" />
<Content />
<Footer />
</a-layout>
</a-layout>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { useStore } from '@/stores/useStore'
const { navKey } = useStore()
const select = ref<string>('nav1')
const sideKey = ref<string[]>(['1'])
onMounted(() => {
select.value = navKey.join('')
})
const getKey = (key: string, key2: string[]) => {
select.value = key
sideKey.value = key2
}
</script>
<style scoped lang="less">
.site-layout .site-layout-background {
background: #fff;
}
[data-theme='dark'] .site-layout .site-layout-background {
background: #141414;
}
</style>
Header.vue
<template>
<a-layout-header class="header flex-r-jb">
<a-menu v-model:selectedKeys="selectedKeys" @click="getKey" theme="white" mode="horizontal">
<a-menu-item key="nav1" @click="$router.push('/')">nav 1</a-menu-item>
<a-menu-item key="nav2" @click="$router.push('/nav2/option1')">nav 2</a-menu-item>
<a-menu-item key="nav3">nav 3</a-menu-item>
</a-menu>
<div class="flex-r-c">
<redo-outlined style="font-size: 22px;" @click="refresh" />
<fullscreen-outlined @click="fullScreen" style="font-size: 22px;margin-left: 15px;" v-if="!isFull" />
<fullscreen-exit-outlined @click="cancelScreen" style="font-size: 22px;margin-left: 15px;" v-else />
<a-popover :title="'Hello ' + username + ' !'" placement="bottomRight" autoAdjustOverflow>
<template #content>
<a>
<p>Profile</p>
</a>
<a>
<p>Settings</p>
</a>
<a @click="logOut">
<p>Log out</p>
</a>
</template>
<a-avatar shape="square" :src="logo" :size="42" style="margin-left: 15px;"></a-avatar>
</a-popover>
</div>
</a-layout-header>
</template>
<script setup lang='ts'>
import { ref, onMounted, inject } from 'vue'
import { FullscreenOutlined, FullscreenExitOutlined, RedoOutlined } from '@ant-design/icons-vue';
import logo from '@/assets/jingyu.png'
import { useRouter } from 'vue-router';
import { useStore } from '@/stores/useStore'
const router = useRouter()
const { navKey, changeNavKey, changeSideKey, username } = useStore()
const emit = defineEmits<{ (event: 'getKey', key: string, sideKey: string[]): void }>()
const selectedKeys = ref<string[]>(['nav1'])
const isFull = ref<boolean>(false)
const fresh = inject<any>('refresh')
onMounted(() => {
selectedKeys.value = navKey
})
const getKey = ({ key }: { key: string }) => {
emit('getKey', key, ['1'])
changeSideKey(['1'])
changeNavKey([key])
}
const refresh = () => {
fresh()
}
const fullScreen = () => {
isFull.value = true
document.documentElement.requestFullscreen();
}
const cancelScreen = () => {
isFull.value = false
document.exitFullscreen()
}
const logOut = () => {
sessionStorage.clear()
router.push('/login')
}
</script>
<style scoped lang='less'>
.header {
background: #fff;
padding: 0 15px;
}
.ant-layout-header {
:deep(.ant-menu-item) {
padding: 0 30px;
}
}
</style>
Sider.vue
<template>
<a-layout-sider v-model:collapsed="collapsed" collapsible>
<div class="flex-r-c" style="width: 100%; padding: 10px; color: #fff; font-size: 20px">
<div class="logo"></div>
<Transition name="slide-fade">
<span v-if="!collapsed">AutoFeed</span>
</Transition>
</div>
<a-menu v-model:selectedKeys="selectedKeys" v-model:openKeys="openKeys" theme="dark" mode="inline"
@click="getOpens">
<component :is="
select === 'nav1'
? 'List1'
: select === 'nav2'
? 'List2'
: select === 'nav3'
? 'List3'
: 'List1'
">
</component>
</a-menu>
</a-layout-sider>
</template>
<script setup lang="ts">
import { ref, watch, onMounted } from "vue";
import { useStore } from "@/stores/useStore";
const { sideKey, changeSideKey } = useStore();
const props = defineProps<{
select: string;
sideKey: string[];
}>();
const collapsed = ref<boolean>(false);
const selectedKeys = ref<string[]>(props.sideKey);
const openKeys = ref<string[]>(props.sideKey);
const select = ref<string>(props.select);
watch(
() => props.select,
() => (select.value = props.select)
);
watch(
() => props.sideKey,
() => {
selectedKeys.value = props.sideKey;
openKeys.value = props.sideKey;
}
);
onMounted(() => {
selectedKeys.value = sideKey;
openKeys.value = sideKey;
});
const getOpens = ({ keyPath }: { keyPath: string[] }) => {
openKeys.value = keyPath;
changeSideKey(keyPath);
};
</script>
<style scoped lang="less">
.logo {
width: 50px;
height: 50px;
margin-right: 10px;
background-image: url("@/assets/jingyu.png");
background-size: auto 100%;
background-repeat: no-repeat;
}
.slide-fade-enter-active {
transition: all 1s ease-out;
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateX(20px);
opacity: 0;
}
</style>
Content.vue
<template>
<a-layout-content class="content">
<router-view />
</a-layout-content>
</template>
<script setup lang='ts'>
</script>
<style scoped lang='less'>
.content {
margin: 10px 16px 0 16px;
height: 100%;
background-color: #fff;
overflow: auto;
padding: 15px;
}
</style>
自定义组件在defineComponents.ts中做了集中处理
import type { App } from "vue";
import Header from "@/components/Layout/Header.vue";
import Sider from "@/components/Layout/Sider.vue";
import Content from "@/components/Layout/Content.vue";
import Footer from "@/components/Layout/Footer.vue";
import List1 from "@/components/SideList/List1.vue";
import List2 from "@/components/SideList/List2.vue";
import List3 from "@/components/SideList/List3.vue";
export default (app: App) => {
app
.component("Header", Header)
.component("Sider", Sider)
.component("Content", Content)
.component("Footer", Footer)
.component('List1', List1)
.component('List2', List2)
.component('List3', List3)
};
main.js中use一下