Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
1esse committed Jul 17, 2022
1 parent 72de2f1 commit f3cdb4d
Show file tree
Hide file tree
Showing 31 changed files with 757 additions and 498 deletions.
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
<img src="https://img.shields.io/badge/vue-3.2.37-brightgreen.svg" alt="vue">
</a>
<a href="https://github.com/vitejs/vite">
<img src="https://img.shields.io/badge/vite-2.9.13-brightgreen.svg" alt="vue">
<img src="https://img.shields.io/badge/vite-3.0.0-brightgreen.svg" alt="vue">
</a>
<a href="https://github.com/vuejs/pinia">
<img src="https://img.shields.io/badge/pinia-2.0.14-brightgreen.svg" alt="vue">
<img src="https://img.shields.io/badge/pinia-2.0.16-brightgreen.svg" alt="vue">
</a>
<a href="https://github.com/vuejs/router">
<img src="https://img.shields.io/badge/vueRouter-4.0.15-brightgreen.svg" alt="vue">
<img src="https://img.shields.io/badge/vueRouter-4.1.2-brightgreen.svg" alt="vue">
</a>
<a href="https://github.com/element-plus/element-plus">
<img src="https://img.shields.io/badge/elementplus-2.2.5-brightgreen.svg" alt="element-ui">
<img src="https://img.shields.io/badge/elementplus-2.2.9-brightgreen.svg" alt="element-ui">
</a>
<a href="https://github.com/1esse/vue-clownfish-admin/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
Expand All @@ -36,11 +36,22 @@
[ES2015+](http://es6.ruanyifeng.com/)[typescript](https://www.typescriptlang.org/zh/)[vue3](https://staging-cn.vuejs.org)[pinia](https://pinia.vuejs.org/)[vue-router](https://router.vuejs.org/zh/)[vite](https://cn.vitejs.dev/)[element-plus](https://github.com/element-plus/element-plus),了解这些技术会让你更容易入手此项目。此项目基于`vite`构建,并使用`vue3`作为开发技术,所以[只针对现代浏览器做开发](https://cn.vitejs.dev/guide/build.html#browser-compatibility),不支持低版本的浏览器(如ie),如有需要请自行添加`polyfill`进行适配。
+ [在线预览](https://1esse.github.io/vue-clownfish-admin-elem)

## ✨ 最新版本 v1.0.16
1. cookie增加SameSite=None;Secure设置
2. 优化侧边栏和标签栏滚动速度
3. 将vite版本更新至3.0,更新所有其他依赖包至最新版本
4. 运行项目成功后默认不自动打开浏览器窗口

## 🦑 优势
+ 几乎使用当前前端最新技术开发(vite, vue3, ts, pinia, csswg)
+ 布局组件解耦,可轻易切换,替代组件
+ 框架实现代码行数少,通俗易懂容易上手

## 🐟了解这些可能会有帮助
1. 定义在src/components下的组件,可在页面的template直接使用,不必导入(tsx除外)。
2. 本项目采用[vite官方推荐的css编写方式](https://www.vitejs.net/guide/features.html#css-pre-processors),可能是volar插件的原因,并没有对vue文件的postcss代码进行高亮,但如果关闭了编辑器的css语法检查,也可以使用原生css编写嵌套style,但可能会失去一些代码提示。如果你不喜欢用postcss-nesting,也可以下载其他css预处理器依赖如scss,less等,不会造成冲突。
3. 不需要对flex,grid等css样式进行多浏览器适配,框架已配置了自动适配。

## 🐳 主要功能
+ 根据路由配置自动生成侧边栏菜单(支持多层嵌套和外链)
+ 根据当前路由信息动态生成面包屑
Expand Down
6 changes: 3 additions & 3 deletions components.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'

export {}

declare module '@vue/runtime-core' {
export interface GlobalComponents {
Dialog: typeof import('./src/components/Dialog.vue')['default']
Expand All @@ -29,5 +31,3 @@ declare module '@vue/runtime-core' {
SvgIcon: typeof import('./src/components/SvgIcon.vue')['default']
}
}

export {}
3 changes: 3 additions & 0 deletions src/mock/api/user.ts → mock/api/user.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Stores } from '../../types/stores'
import { MockApi } from '../mockapi'

const users: ({ username: string, password: string } & Stores.user)[] = [
{
username: 'david',
Expand Down
15 changes: 10 additions & 5 deletions src/mock/index.ts → mock/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import Mock from 'mockjs'
import { mockNamespace } from '@/appConfig'
import { MockApi } from './mockapi'

interface GlobModule {
default: MockApi.obj[]
}

function collectApis(): MockApi.obj[] {
const mockApis = []
const apiModules = import.meta.globEager('./api/*.ts')
const apiModules = import.meta.glob('./api/*.ts', { eager: true })
if (mockNamespace) {
for (const [filePath, apiModule] of Object.entries(apiModules)) {
const apis: MockApi.obj[] = apiModule.default
const apis: MockApi.obj[] = (apiModule as GlobModule).default
apis.forEach(api => api.url = filePath.replace(/^.+([\/\\].*)\.ts$/, '$1') + api.url)
mockApis.push(...apiModule.default)
mockApis.push(...(apiModule as GlobModule).default)
}
} else {
for (const [, apiModule] of Object.entries(apiModules)) {
mockApis.push(...apiModule.default)
mockApis.push(...(apiModule as GlobModule).default)
}
}
return mockApis
Expand All @@ -30,7 +35,7 @@ function enableMock(timeout: string | number = '100-1000') {
const mockApis = collectApis()
for (const api of mockApis) {
Mock.mock(new RegExp(api.url), api.type || 'get', (options: MockApi.request) => {
if (options.body) {
if (typeof options.body === 'string' && options.body) {
options.body = JSON.parse(options.body)
}
return api.response instanceof Function ? api.response(options) : api.response
Expand Down
2 changes: 1 addition & 1 deletion src/mock/mockapi.d.ts → mock/mockapi.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare namespace MockApi {
export namespace MockApi {
type type = Lowercase<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'>
type responseFunc = (options: request) => response
interface request {
Expand Down
28 changes: 15 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"email": "572116385@qq.com",
"url": "https://1esse.github.io/vue-clownfish-admin/"
},
"version": "1.0.11",
"version": "1.0.16",
"private": "true",
"scripts": {
"dev": "vite",
Expand All @@ -15,31 +15,33 @@
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.4",
"@element-plus/icons-vue": "^2.0.6",
"axios": "^0.27.2",
"element-plus": "^2.2.5",
"element-plus": "^2.2.9",
"nprogress": "^0.2.0",
"overlayscrollbars": "^1.13.2",
"pinia": "^2.0.14",
"pathe": "^0.3.2",
"pinia": "^2.0.16",
"qs": "^6.11.0",
"vue": "^3.2.37",
"vue-router": "^4.0.15"
"vue-router": "^4.1.2"
},
"devDependencies": {
"@types/mockjs": "^1.0.6",
"@types/node": "^17.0.41",
"@types/node": "^18.0.5",
"@types/nprogress": "^0.2.0",
"@types/overlayscrollbars": "^1.12.1",
"@vitejs/plugin-vue": "^2.3.3",
"@vitejs/plugin-vue-jsx": "^1.3.10",
"@vitejs/plugin-vue": "^3.0.0",
"@vitejs/plugin-vue-jsx": "^2.0.0",
"autoprefixer": "^10.4.7",
"mockjs": "^1.1.0",
"postcss": "^8.4.14",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-nesting": "^10.1.8",
"typescript": "^4.7.3",
"unplugin-vue-components": "^0.19.6",
"vite": "^2.9.13",
"postcss-nesting": "^10.1.10",
"typescript": "^4.7.4",
"unplugin-vue-components": "^0.21.1",
"vite": "^3.0.0",
"vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^0.38.2"
"vue-tsc": "^0.38.7"
}
}
4 changes: 2 additions & 2 deletions src/appConfig.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { EnvType, SwitchType } from "./app"
import { EnvType, SwitchType } from "types/app"

/**
* app标题
*/
export const appTitle = 'ClownFish Admin'
export const appTitle = 'Clownfish Admin'

/**
* 使用mock代理api请求:on开,off关
Expand Down
2 changes: 1 addition & 1 deletion src/components/Dialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function shadowClick() {
display: flex;
flex-direction: column;
background-color: white;
transition: ease .3s;
transition: ease .2s;
transition-property: width, height;
}
Expand Down
34 changes: 24 additions & 10 deletions src/components/Scrollbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ const props = withDefaults(defineProps<{
direction?: 'horizontal' | 'vertical'
initOptions?: Options,
flex?: number
speed?: number
}>(), {
width: '100%',
height: '100%',
tag: 'div',
direction: 'vertical',
initOptions: undefined,
flex: 1
flex: 1,
speed: 1
})
const scrollbar = ref<OverlayScrollbars>()
Expand All @@ -37,15 +39,27 @@ defineExpose({
})
function listenWheel() {
scrollbarDom.value.addEventListener('wheel', (e: WheelEventInit): void => {
const viewport = scrollbar.value?.getElements().viewport
const scrollOffset = (viewport && (
props.direction === 'horizontal' ? viewport.scrollLeft : viewport.scrollTop) || 0) + ((e.deltaY || 0) / 4)
scrollbar.value?.scroll({
x: props.direction === 'horizontal' ? scrollOffset : 0,
y: props.direction === 'vertical' ? scrollOffset : 0
}, 30)
})
scrollbarDom.value.addEventListener('wheel', (e) => onWheel(e as WheelEvent))
}
function onWheel(e: WheelEvent): void {
const viewport = scrollbar.value?.getElements().viewport
const states = scrollbar.value?.getState()
if (
(states?.hasOverflow.x && props.direction === 'horizontal') ||
(states?.hasOverflow.y && props.direction === 'vertical')
) {
// 阻止浏览器默认滚动行为
e.preventDefault()
}
const scrollOffset = (viewport && (
props.direction === 'horizontal' ? viewport.scrollLeft : viewport.scrollTop) || 0) + ((e.deltaY || 0) / 4 * props.speed)
scrollbar.value?.scroll({
x: props.direction === 'horizontal' ? scrollOffset : 0,
y: props.direction === 'vertical' ? scrollOffset : 0
}, 30)
}
function getDirectionOptions(): Options {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Shadow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const emit = defineEmits<{
position: fixed;
top: -25vh;
left: -25vw;
z-index: 9999;
z-index: 999;
padding: 25vh 25vw;
background-color: v-bind('props.color');
}
Expand Down
2 changes: 1 addition & 1 deletion src/composables/isMobile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { onBeforeMount, onBeforeUnmount, onMounted, ref } from "vue"
import { onBeforeMount, onBeforeUnmount, ref } from "vue"

export default function () {
const WIDTH = 992
Expand Down
8 changes: 3 additions & 5 deletions src/layout/BreadCrumb.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import { onBeforeMount, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import type { RouteLocationMatched, RouteLocationNormalizedLoaded } from 'vue-router'
import { dashboardRoute } from '@/router'
import { resolve } from 'pathe'
const route = useRoute()
const routeMatched = ref<RouteLocationMatched[]>([])
Expand All @@ -18,11 +20,7 @@ function refreshBreadCrumb(route: RouteLocationNormalizedLoaded) {
routeMatched.value = route.matched.filter((item) => item.meta.breadcrumb !== false && !item.meta.hidden)
if (routeMatched.value.length === 0) return
if (routeMatched.value[0].path !== '/dashboard') {
routeMatched.value.unshift(<RouteLocationMatched>{
path: '/dashboard',
name: 'Dashboard',
meta: { title: '首页', icon: 'dashboard' }
})
routeMatched.value.unshift(<RouteLocationMatched>{ ...dashboardRoute.children?.[0], path: resolve('/', dashboardRoute.children?.[0].path as string) })
}
}
</script>
Expand Down
1 change: 1 addition & 0 deletions src/layout/HeadBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useRouter } from 'vue-router'
import { userStore } from '../stores/user'
import BreadCrumb from './BreadCrumb.vue'
import { Fold, ArrowRightBold } from '@element-plus/icons-vue'
import type { Layout } from 'types/layout'
const sidebarRelated = inject<Layout.SidebarRelated>('sidebarRelated')
const loading = inject<Layout.Loading>('loading')
Expand Down
3 changes: 2 additions & 1 deletion src/layout/SideBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { Component, Slots } from 'vue'
import type { RouteMeta, RouteRecordRaw } from 'vue-router'
import { ElMenu, ElMenuItem, ElSubMenu, ElIcon } from 'element-plus/es'
import SvgIcon from '../components/SvgIcon.vue'
import type { Layout } from 'types/layout'
const router = useRouter()
const route = useRoute()
Expand Down Expand Up @@ -101,7 +102,7 @@ function getOnlyChildPath(parentRoute: RouteRecordRaw): RouteRecordRaw {
</script>

<template>
<Scrollbar>
<Scrollbar :speed="3">
<ElMenu class="menu-vertical" mode="vertical" :collapse="sidebarRelated?.collapsed" :defaultActive="defaultActive"
:defaultOpeneds="defaultOpeneds" :collapseTransition="false">
<template v-for="route in routesList" key="index">
Expand Down
3 changes: 2 additions & 1 deletion src/layout/TabsBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Scrollbar from '@/components/Scrollbar.vue'
import MenuPanel from '@/components/MenuPanel.vue'
import type { ComponentPublicInstance } from 'vue'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import type { Layout } from 'types/layout'
const router = useRouter()
const route = useRoute()
Expand Down Expand Up @@ -114,7 +115,7 @@ function showTabMenu(e: MouseEvent, tab: RouteLocationNormalizedLoaded) {
</script>

<template>
<Scrollbar ref="scrollbarDom" height="2rem" direction="horizontal">
<Scrollbar ref="scrollbarDom" height="2rem" direction="horizontal" :speed="3">
<div class="tabs">
<RouterLink ref="tabDoms" v-for="tab in tabs" :key="tab.path" :to="tab.path" class="tab shadow-sm"
:class="{ active: tab.path === route.path }" @click.right.prevent="showTabMenu($event, tab)">
Expand Down
14 changes: 10 additions & 4 deletions src/layout/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SideBar from './SideBar.vue'
import TabsBar from './TabsBar.vue'
import isMobile from '@/composables/isMobile'
import Logo from '@/assets/logo.png'
import type { Layout } from 'types/layout'
const _isMobile = isMobile()
const sidebarRelated = reactive<Layout.SidebarRelated>({
Expand Down Expand Up @@ -53,12 +54,17 @@ provide('loading', loading)
<HeadBar></HeadBar>
<TabsBar :withIcons="true"></TabsBar>
</ElHeader>
<ElMain>
<ElMain id="content-window">
<RouterView v-slot="{ Component, route }">
<Transition name="fade-scale" mode="out-in">
<KeepAlive :include="getKeepAlivePages">
<component :is="Component" :key="route.path" />
</KeepAlive>
<!--
vite的hmr和keepalive组件冲突会导致vite热更新后路由失效,
https://github.com/vuejs/core/pull/5165
开发过程注释掉keepalive
-->
<!-- <KeepAlive :include="getKeepAlivePages"> -->
<component :is="Component" :key="route.path" />
<!-- </KeepAlive> -->
</Transition>
</RouterView>
</ElMain>
Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
import { mock, mockEnv } from './appConfig'
import enableMock from './mock'
import enableMock from '../mock'
import '@/styles/index.postcss' // 全局样式
import 'virtual:svg-icons-register'
import './permission'
import 'element-plus/theme-chalk/el-message.css' // message样式
import type { EnvType } from './app'
import { EnvType } from 'types/app'

mockEnv.includes(import.meta.env.MODE as EnvType) && mock === 'on' && enableMock()

Expand Down
16 changes: 15 additions & 1 deletion src/permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { ElMessage } from 'element-plus'
NProgress.configure({ showSpinner: false })

const whitelist: string[] = ['/login', '/404']
let scrollTimeout: NodeJS.Timeout | null = null
let contentWindowDom: HTMLElement | null = null

router.beforeEach(async (to, from, next) => {
NProgress.start()
Expand Down Expand Up @@ -37,6 +39,18 @@ router.beforeEach(async (to, from, next) => {
}
})

router.afterEach(() => {
router.afterEach((to, from) => {
NProgress.done()
scrollTimeout && clearTimeout(scrollTimeout)
if (from.path === '/') return
scrollTimeout = setTimeout(() => {
if (contentWindowDom) {
contentWindowDom.scrollTo({ top: 0, left: 0 })
return
}
contentWindowDom = document.querySelector('#content-window')
if (contentWindowDom) {
contentWindowDom.scrollTo({ top: 0, left: 0 })
}
}, 350)
})
Loading

0 comments on commit f3cdb4d

Please sign in to comment.