登录叶细胞

This commit is contained in:
仲么了
2023-12-22 10:22:22 +08:00
parent 0f7b59f55b
commit 3a465769bc
17 changed files with 596 additions and 257 deletions

BIN
src/assets/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

BIN
src/assets/bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
src/assets/login-header.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,198 +1,198 @@
<template>
<div class="layout-config-drawer">
<div class='layout-config-drawer'>
<el-drawer
:model-value="configStore.layout.showDrawer"
title="布局配置"
size="310px"
@close="onCloseDrawer"
:model-value='configStore.layout.showDrawer'
title='布局配置'
size='310px'
@close='onCloseDrawer'
>
<el-scrollbar class="layout-mode-style-scrollbar">
<el-form ref="formRef" :model="configStore.layout">
<div class="layout-mode-styles-box">
<el-divider border-style="dashed">全局</el-divider>
<div class="layout-mode-box-style">
<el-row class="layout-mode-box-style-row" :gutter="10">
<el-col :span="12">
<el-scrollbar class='layout-mode-style-scrollbar'>
<el-form ref='formRef' :model='configStore.layout'>
<div class='layout-mode-styles-box'>
<el-divider border-style='dashed'>全局</el-divider>
<div class='layout-mode-box-style'>
<el-row class='layout-mode-box-style-row' :gutter='10'>
<el-col :span='12'>
<div
@click="setLayoutMode('Default')"
class="layout-mode-style default"
class='layout-mode-style default'
:class="configStore.layout.layoutMode == 'Default' ? 'active' : ''"
>
<div class="layout-mode-style-box">
<div class="layout-mode-style-aside"></div>
<div class="layout-mode-style-container-box">
<div class="layout-mode-style-header"></div>
<div class="layout-mode-style-container"></div>
<div class='layout-mode-style-box'>
<div class='layout-mode-style-aside'></div>
<div class='layout-mode-style-container-box'>
<div class='layout-mode-style-header'></div>
<div class='layout-mode-style-container'></div>
</div>
</div>
<div class="layout-mode-style-name">默认</div>
<div class='layout-mode-style-name'>默认</div>
</div>
</el-col>
<el-col :span="12">
<el-col :span='12'>
<div
@click="setLayoutMode('Classic')"
class="layout-mode-style classic"
class='layout-mode-style classic'
:class="configStore.layout.layoutMode == 'Classic' ? 'active' : ''"
>
<div class="layout-mode-style-box">
<div class="layout-mode-style-aside"></div>
<div class="layout-mode-style-container-box">
<div class="layout-mode-style-header"></div>
<div class="layout-mode-style-container"></div>
<div class='layout-mode-style-box'>
<div class='layout-mode-style-aside'></div>
<div class='layout-mode-style-container-box'>
<div class='layout-mode-style-header'></div>
<div class='layout-mode-style-container'></div>
</div>
</div>
<div class="layout-mode-style-name">经典</div>
<div class='layout-mode-style-name'>经典</div>
</div>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-row :gutter='10'>
<el-col :span='12'>
<div
@click="setLayoutMode('Streamline')"
class="layout-mode-style streamline"
class='layout-mode-style streamline'
:class="configStore.layout.layoutMode == 'Streamline' ? 'active' : ''"
>
<div class="layout-mode-style-box">
<div class="layout-mode-style-container-box">
<div class="layout-mode-style-header"></div>
<div class="layout-mode-style-container"></div>
<div class='layout-mode-style-box'>
<div class='layout-mode-style-container-box'>
<div class='layout-mode-style-header'></div>
<div class='layout-mode-style-container'></div>
</div>
</div>
<div class="layout-mode-style-name">单栏</div>
<div class='layout-mode-style-name'>单栏</div>
</div>
</el-col>
<el-col :span="12">
<el-col :span='12'>
<div
@click="setLayoutMode('Double')"
class="layout-mode-style double"
class='layout-mode-style double'
:class="configStore.layout.layoutMode == 'Double' ? 'active' : ''"
>
<div class="layout-mode-style-box">
<div class="layout-mode-style-aside"></div>
<div class="layout-mode-style-container-box">
<div class="layout-mode-style-header"></div>
<div class="layout-mode-style-container"></div>
<div class='layout-mode-style-box'>
<div class='layout-mode-style-aside'></div>
<div class='layout-mode-style-container-box'>
<div class='layout-mode-style-header'></div>
<div class='layout-mode-style-container'></div>
</div>
</div>
<div class="layout-mode-style-name">双栏</div>
<div class='layout-mode-style-name'>双栏</div>
</div>
</el-col>
</el-row>
</div>
<el-divider border-style="dashed">全局</el-divider>
<div class="layout-config-global">
<el-divider border-style='dashed'>全局</el-divider>
<div class='layout-config-global'>
<el-form-item label="'后台页面切换动画">
<el-select
@change="onCommitState($event, 'mainAnimation')"
:model-value="configStore.layout.mainAnimation"
:model-value='configStore.layout.mainAnimation'
:placeholder="'layouts.Please select an animation name'"
>
<el-option label="slide-right" value="slide-right"></el-option>
<el-option label="slide-left" value="slide-left"></el-option>
<el-option label="el-fade-in-linear" value="el-fade-in-linear"></el-option>
<el-option label="el-fade-in" value="el-fade-in"></el-option>
<el-option label="el-zoom-in-center" value="el-zoom-in-center"></el-option>
<el-option label="el-zoom-in-top" value="el-zoom-in-top"></el-option>
<el-option label="el-zoom-in-bottom" value="el-zoom-in-bottom"></el-option>
<el-option label='slide-right' value='slide-right'></el-option>
<el-option label='slide-left' value='slide-left'></el-option>
<el-option label='el-fade-in-linear' value='el-fade-in-linear'></el-option>
<el-option label='el-fade-in' value='el-fade-in'></el-option>
<el-option label='el-zoom-in-center' value='el-zoom-in-center'></el-option>
<el-option label='el-zoom-in-top' value='el-zoom-in-top'></el-option>
<el-option label='el-zoom-in-bottom' value='el-zoom-in-bottom'></el-option>
</el-select>
</el-form-item>
</div>
<el-divider border-style="dashed">侧边栏</el-divider>
<div class="layout-config-aside">
<el-form-item label="侧边菜单栏背景色">
<el-divider border-style='dashed'>侧边栏</el-divider>
<div class='layout-config-aside'>
<el-form-item label='侧边菜单栏背景色'>
<el-color-picker
@change="onCommitColorState($event, 'menuBackground')"
:model-value="configStore.getColorVal('menuBackground')"
/>
</el-form-item>
<el-form-item label="侧边菜单文字颜色">
<el-form-item label='侧边菜单文字颜色'>
<el-color-picker
@change="onCommitColorState($event, 'menuColor')"
:model-value="configStore.getColorVal('menuColor')"
/>
</el-form-item>
<el-form-item label="侧边菜单激活项背景色">
<el-form-item label='侧边菜单激活项背景色'>
<el-color-picker
@change="onCommitColorState($event, 'menuActiveBackground')"
:model-value="configStore.getColorVal('menuActiveBackground')"
/>
</el-form-item>
<el-form-item label="侧边菜单激活项文字色">
<el-form-item label='侧边菜单激活项文字色'>
<el-color-picker
@change="onCommitColorState($event, 'menuActiveColor')"
:model-value="configStore.getColorVal('menuActiveColor')"
/>
</el-form-item>
<el-form-item label="显示侧边菜单顶栏(LOGO栏)">
<el-form-item label='显示侧边菜单顶栏(LOGO栏)'>
<el-switch
@change="onCommitState($event, 'menuShowTopBar')"
:model-value="configStore.layout.menuShowTopBar"
:model-value='configStore.layout.menuShowTopBar'
></el-switch>
</el-form-item>
<el-form-item label="侧边菜单顶栏背景色">
<el-form-item label='侧边菜单顶栏背景色'>
<el-color-picker
@change="onCommitColorState($event, 'menuTopBarBackground')"
:model-value="configStore.getColorVal('menuTopBarBackground')"
/>
</el-form-item>
<el-form-item label="侧边菜单宽度(展开时)">
<el-form-item label='侧边菜单宽度(展开时)'>
<el-input
@input="onCommitState($event, 'menuWidth')"
type="number"
:step="10"
:model-value="configStore.layout.menuWidth"
type='number'
:step='10'
:model-value='configStore.layout.menuWidth'
>
<template #append>px</template>
</el-input>
</el-form-item>
<el-form-item label="侧边菜单默认图标">
<el-form-item label='侧边菜单默认图标'>
<IconSelector
@change="onCommitMenuDefaultIcon($event, 'menuDefaultIcon')"
:model-value="configStore.layout.menuDefaultIcon"
:model-value='configStore.layout.menuDefaultIcon'
/>
</el-form-item>
<el-form-item label="侧边菜单水平折叠">
<el-form-item label='侧边菜单水平折叠'>
<el-switch
@change="onCommitState($event, 'menuCollapse')"
:model-value="configStore.layout.menuCollapse"
:model-value='configStore.layout.menuCollapse'
></el-switch>
</el-form-item>
<el-form-item label="侧边菜单手风琴">
<el-form-item label='侧边菜单手风琴'>
<el-switch
@change="onCommitState($event, 'menuUniqueOpened')"
:model-value="configStore.layout.menuUniqueOpened"
:model-value='configStore.layout.menuUniqueOpened'
></el-switch>
</el-form-item>
</div>
<el-divider border-style="dashed">顶栏</el-divider>
<div class="layout-config-aside">
<el-form-item label="顶栏背景色">
<el-divider border-style='dashed'>顶栏</el-divider>
<div class='layout-config-aside'>
<el-form-item label='顶栏背景色'>
<el-color-picker
@change="onCommitColorState($event, 'headerBarBackground')"
:model-value="configStore.getColorVal('headerBarBackground')"
/>
</el-form-item>
<el-form-item label="顶栏文字色">
<el-form-item label='顶栏文字色'>
<el-color-picker
@change="onCommitColorState($event, 'headerBarTabColor')"
:model-value="configStore.getColorVal('headerBarTabColor')"
/>
</el-form-item>
<el-form-item label="顶栏悬停时背景色">
<el-form-item label='顶栏悬停时背景色'>
<el-color-picker
@change="onCommitColorState($event, 'headerBarHoverBackground')"
:model-value="configStore.getColorVal('headerBarHoverBackground')"
/>
</el-form-item>
<el-form-item label="顶栏菜单激活项背景色">
<el-form-item label='顶栏菜单激活项背景色'>
<el-color-picker
@change="onCommitColorState($event, 'headerBarTabActiveBackground')"
:model-value="configStore.getColorVal('headerBarTabActiveBackground')"
/>
</el-form-item>
<el-form-item label="顶栏菜单激活项文字色">
<el-form-item label='顶栏菜单激活项文字色'>
<el-color-picker
@change="onCommitColorState($event, 'headerBarTabActiveColor')"
:model-value="configStore.getColorVal('headerBarTabActiveColor')"
@@ -200,14 +200,12 @@
</el-form-item>
</div>
<el-popconfirm
@confirm="restoreDefault"
:title="
'layouts.Are you sure you want to restore all configurations to the default values?'
"
@confirm='restoreDefault'
title='确定要恢复全部配置到默认值吗?'
>
<template #reference>
<div class="ba-center">
<el-button class="w80" type="info">恢复默认</el-button>
<div class='ba-center'>
<el-button class='w80' type='info'>恢复默认</el-button>
</div>
</template>
</el-popconfirm>
@@ -218,7 +216,7 @@
</div>
</template>
<script setup lang="ts">
<script setup lang='ts'>
import { useConfig } from '@/stores/config'
import { useNavTabs } from '@/stores/navTabs'
import { useRouter } from 'vue-router'
@@ -271,34 +269,42 @@ const restoreDefault = () => {
}
</script>
<style scoped lang="scss">
<style scoped lang='scss'>
.layout-config-drawer :deep(.el-input__inner) {
padding: 0 0 0 6px;
}
.layout-config-drawer :deep(.el-input-group__append) {
padding: 0 10px;
}
.layout-config-drawer :deep(.el-drawer__header) {
margin-bottom: 0 !important;
}
.layout-config-drawer :deep(.el-drawer__body) {
padding: 0;
}
.layout-mode-styles-box {
padding: 20px;
}
.layout-mode-box-style-row {
margin-bottom: 15px;
}
.layout-mode-style {
position: relative;
height: 100px;
border: 1px solid var(--el-border-color-light);
border-radius: var(--el-border-radius-small);
&:hover,
&.active {
border: 1px solid var(--el-color-primary);
}
.layout-mode-style-name {
position: absolute;
display: flex;
@@ -310,6 +316,7 @@ const restoreDefault = () => {
width: 50px;
border: 1px solid var(--el-color-primary-light-3);
}
.layout-mode-style-box {
display: flex;
align-items: center;
@@ -317,24 +324,29 @@ const restoreDefault = () => {
width: 100%;
height: 100%;
}
&.default {
display: flex;
align-items: center;
justify-content: center;
.layout-mode-style-aside {
width: 18%;
height: 90%;
background-color: var(--el-border-color-lighter);
}
.layout-mode-style-container-box {
width: 68%;
height: 90%;
margin-left: 4%;
.layout-mode-style-header {
width: 100%;
height: 10%;
background-color: var(--el-border-color-lighter);
}
.layout-mode-style-container {
width: 100%;
height: 85%;
@@ -343,23 +355,28 @@ const restoreDefault = () => {
}
}
}
&.classic {
display: flex;
align-items: center;
justify-content: center;
.layout-mode-style-aside {
width: 18%;
height: 100%;
background-color: var(--el-border-color-lighter);
}
.layout-mode-style-container-box {
width: 82%;
height: 100%;
.layout-mode-style-header {
width: 100%;
height: 10%;
background-color: var(--el-border-color);
}
.layout-mode-style-container {
width: 100%;
height: 90%;
@@ -367,18 +384,22 @@ const restoreDefault = () => {
}
}
}
&.streamline {
display: flex;
align-items: center;
justify-content: center;
.layout-mode-style-container-box {
width: 100%;
height: 100%;
.layout-mode-style-header {
width: 100%;
height: 10%;
background-color: var(--el-border-color);
}
.layout-mode-style-container {
width: 100%;
height: 90%;
@@ -386,23 +407,28 @@ const restoreDefault = () => {
}
}
}
&.double {
display: flex;
align-items: center;
justify-content: center;
.layout-mode-style-aside {
width: 18%;
height: 100%;
background-color: var(--el-border-color);
}
.layout-mode-style-container-box {
width: 82%;
height: 100%;
.layout-mode-style-header {
width: 100%;
height: 10%;
background-color: var(--el-border-color);
}
.layout-mode-style-container {
width: 100%;
height: 90%;
@@ -411,6 +437,7 @@ const restoreDefault = () => {
}
}
}
.w80 {
width: 90%;
}

View File

@@ -1,15 +1,5 @@
<template>
<div class="nav-menus" :class="configStore.layout.layoutMode">
<router-link class="h100" target="_blank" title="'Home'" to="/">
<div class="nav-menu-item">
<Icon
:color="configStore.getColorVal('headerBarTabColor')"
class="nav-menu-icon"
name="el-icon-Monitor"
size="18"
/>
</div>
</router-link>
<div @click="onFullScreen" class="nav-menu-item" :class="state.isFullScreen ? 'hover' : ''">
<Icon
:color="configStore.getColorVal('headerBarTabColor')"

View File

@@ -1,8 +1,8 @@
<template>
<component :is="config.layout.layoutMode"></component>
<component :is='config.layout.layoutMode'></component>
</template>
<script setup lang="ts">
<script setup lang='ts'>
import { reactive } from 'vue'
import { useConfig } from '@/stores/config'
import { useNavTabs } from '@/stores/navTabs'

View File

@@ -1,10 +1,12 @@
<template>
<el-main class="layout-main">
<el-scrollbar class="layout-main-scrollbar" :style="layoutMainScrollbarStyle()" ref="mainScrollbarRef">
<router-view v-slot="{ Component }">
<transition :name="config.layout.mainAnimation" mode="out-in">
<keep-alive :include="state.keepAliveComponentNameList">
<component :is="Component" :key="state.componentKey" />
<el-main class='layout-main'>
<el-scrollbar class='layout-main-scrollbar' :style='layoutMainScrollbarStyle()' ref='mainScrollbarRef'>
<router-view v-slot='{ Component }'>
<transition :name='config.layout.mainAnimation' mode='out-in'>
<keep-alive :include='state.keepAliveComponentNameList'>
<div class='default-main'>
<component :is='Component' :key='state.componentKey' />
</div>
</keep-alive>
</transition>
</router-view>
@@ -12,7 +14,7 @@
</el-main>
</template>
<script setup lang="ts">
<script setup lang='ts'>
import { ref, reactive, onMounted, watch, onBeforeMount, onUnmounted, nextTick, provide } from 'vue'
import { useRoute, type RouteLocationNormalized } from 'vue-router'
import { mainHeight as layoutMainScrollbarStyle } from '@/utils/layout'
@@ -22,7 +24,7 @@ import { useNavTabs } from '@/stores/navTabs'
import type { ScrollbarInstance } from 'element-plus'
defineOptions({
name: 'layout/main',
name: 'layout/main'
})
const { proxy } = useCurrentInstance()
@@ -37,10 +39,10 @@ const state: {
keepAliveComponentNameList: string[]
} = reactive({
componentKey: route.path,
keepAliveComponentNameList: [],
keepAliveComponentNameList: []
})
const addKeepAliveComponentName = function (keepAliveName: string | undefined) {
const addKeepAliveComponentName = function(keepAliveName: string | undefined) {
if (keepAliveName) {
let exist = state.keepAliveComponentNameList.find((name: string) => {
return name === keepAliveName
@@ -89,13 +91,14 @@ watch(
provide('mainScrollbarRef', mainScrollbarRef)
</script>
<style scoped lang="scss">
<style scoped lang='scss'>
.layout-container .layout-main {
padding: 0 !important;
overflow: hidden;
width: 100%;
height: 100%;
}
.layout-main-scrollbar {
width: 100%;
position: relative;

View File

@@ -1,75 +1,31 @@
import { createRouter, createWebHashHistory } from 'vue-router'
import staticRoutes from '@/router/static'
import { useConfig } from '@/stores/config'
import NProgress from 'nprogress'
import { loading } from '@/utils/loading'
const router = createRouter({
history: createWebHashHistory(),
routes: staticRoutes
})
// router.beforeEach((to, from, next) => {
// NProgress.configure({ showSpinner: false })
// NProgress.start()
// if (!window.existLoading) {
// loading.show()
// window.existLoading = true
// }
router.beforeEach((to, from, next) => {
NProgress.configure({ showSpinner: false })
NProgress.start()
if (!window.existLoading) {
loading.show()
window.existLoading = true
}
console.log(to)
next()
})
// // 按需动态加载页面的语言包-start
// let loadPath: string[] = []
// const config = useConfig()
// if (to.path in langAutoLoadMap) {
// loadPath.push(...langAutoLoadMap[to.path as keyof typeof langAutoLoadMap])
// }
// let prefix = ''
// if (isAdminApp(to.fullPath)) {
// prefix = './backend/' + config.lang.defaultLang
// // 去除 path 中的 /admin
// const adminPath = to.path.slice(to.path.indexOf(adminBaseRoutePath) + adminBaseRoutePath.length)
// if (adminPath) loadPath.push(prefix + adminPath + '.ts')
// } else {
// prefix = './frontend/' + config.lang.defaultLang
// loadPath.push(prefix + to.path + '.ts')
// }
// // 根据路由 name 加载的语言包
// if (to.name) {
// loadPath.push(prefix + '/' + to.name.toString() + '.ts')
// }
// if (!window.loadLangHandle.publicMessageLoaded) window.loadLangHandle.publicMessageLoaded = []
// const publicMessagePath = prefix + '.ts'
// if (!window.loadLangHandle.publicMessageLoaded.includes(publicMessagePath)) {
// loadPath.push(publicMessagePath)
// window.loadLangHandle.publicMessageLoaded.push(publicMessagePath)
// }
// // 去重
// loadPath = uniq(loadPath)
// for (const key in loadPath) {
// loadPath[key] = loadPath[key].replaceAll('${lang}', config.lang.defaultLang)
// if (loadPath[key] in window.loadLangHandle) {
// window.loadLangHandle[loadPath[key]]().then((res: { default: anyObj }) => {
// const pathName = loadPath[key].slice(
// loadPath[key].lastIndexOf(prefix) + (prefix.length + 1),
// loadPath[key].lastIndexOf('.')
// )
// mergeMessage(res.default, pathName)
// })
// }
// }
// // 动态加载语言包-end
// next()
// })
// // 路由加载后
// router.afterEach(() => {
// if (window.existLoading) {
// loading.hide()
// }
// NProgress.done()
// })
// 路由加载后
router.afterEach(() => {
if (window.existLoading) {
loading.hide()
}
NProgress.done()
})
export default router

View File

@@ -34,6 +34,14 @@ export const adminBaseRoute = {
*/
const staticRoutes: Array<RouteRecordRaw> = [
adminBaseRoute,
{
path: '/',
redirect: (to) => {
return {
name: 'adminMainLoading'
}
}
},
{
// 管理员登录页 - 不放在 adminBaseRoute.children 因为登录页不需要使用后台的布局
path: '/login',
@@ -43,7 +51,6 @@ const staticRoutes: Array<RouteRecordRaw> = [
title: pageTitle('login')
}
},
{
path: '/:path(.*)*',
redirect: '/404'
@@ -56,6 +63,21 @@ const staticRoutes: Array<RouteRecordRaw> = [
meta: {
title: pageTitle('notFound') // 页面不存在
}
},
{
// 后台找不到页面了-可能是路由未加载上
path: adminBaseRoutePath + ':path(.*)*',
redirect: (to) => {
return {
name: 'adminMainLoading',
params: {
to: JSON.stringify({
path: to.path,
query: to.query
})
}
}
}
}
]

54
src/styles/loading.scss Normal file
View File

@@ -0,0 +1,54 @@
.block-loading {
width: 100%;
height: 100%;
position: fixed;
z-index: 9990;
background-color: var(--ba-bg-color);
}
.block-loading .block-loading-box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.block-loading .block-loading-box-warp {
width: 80px;
height: 80px;
}
.block-loading .block-loading-box-warp .block-loading-box-item {
width: 33.333333%;
height: 33.333333%;
background: #409eff;
float: left;
animation: block-loading-animation 1.2s infinite ease;
border-radius: 1px;
}
.block-loading .block-loading-box-warp .block-loading-box-item:nth-child(7) {
animation-delay: 0s;
}
.block-loading .block-loading-box-warp .block-loading-box-item:nth-child(4),
.block-loading .block-loading-box-warp .block-loading-box-item:nth-child(8) {
animation-delay: 0.1s;
}
.block-loading .block-loading-box-warp .block-loading-box-item:nth-child(1),
.block-loading .block-loading-box-warp .block-loading-box-item:nth-child(5),
.block-loading .block-loading-box-warp .block-loading-box-item:nth-child(9) {
animation-delay: 0.2s;
}
.block-loading .block-loading-box-warp .block-loading-box-item:nth-child(2),
.block-loading .block-loading-box-warp .block-loading-box-item:nth-child(6) {
animation-delay: 0.3s;
}
.block-loading .block-loading-box-warp .block-loading-box-item:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes block-loading-animation {
0%,
70%,
100% {
transform: scale3D(1, 1, 1);
}
35% {
transform: scale3D(0, 0, 1);
}
}

34
src/utils/loading.ts Normal file
View File

@@ -0,0 +1,34 @@
import { nextTick } from 'vue'
import '@/styles/loading.scss'
export const loading = {
show: () => {
const bodys: Element = document.body
const div = document.createElement('div')
div.className = 'block-loading'
div.innerHTML = `
<div class="block-loading-box">
<div class="block-loading-box-warp">
<div class="block-loading-box-item"></div>
<div class="block-loading-box-item"></div>
<div class="block-loading-box-item"></div>
<div class="block-loading-box-item"></div>
<div class="block-loading-box-item"></div>
<div class="block-loading-box-item"></div>
<div class="block-loading-box-item"></div>
<div class="block-loading-box-item"></div>
<div class="block-loading-box-item"></div>
</div>
</div>
`
bodys.insertBefore(div, bodys.childNodes[0])
},
hide: () => {
nextTick(() => {
setTimeout(() => {
const el = document.querySelector('.block-loading')
el && el.parentNode?.removeChild(el)
}, 1000)
})
},
}

104
src/utils/pageBubble.ts Normal file
View File

@@ -0,0 +1,104 @@
// 页面气泡效果
const bubble: {
width: number
height: number
bubbleEl: any
canvas: any
ctx: any
circles: any[]
animate: boolean
requestId: any
} = {
width: 0,
height: 0,
bubbleEl: null,
canvas: null,
ctx: {},
circles: [],
animate: true,
requestId: null,
}
export const init = function (): void {
bubble.width = window.innerWidth
bubble.height = window.innerHeight
bubble.bubbleEl = document.getElementById('bubble')
bubble.bubbleEl.style.height = bubble.height + 'px'
bubble.canvas = document.getElementById('bubble-canvas')
bubble.canvas.width = bubble.width
bubble.canvas.height = bubble.height
bubble.ctx = bubble.canvas.getContext('2d')
// create particles
bubble.circles = []
for (let x = 0; x < bubble.width * 0.5; x++) {
const c = new Circle()
bubble.circles.push(c)
}
animate()
addListeners()
}
function scrollCheck() {
bubble.animate = document.body.scrollTop > bubble.height ? false : true
}
function resize() {
bubble.width = window.innerWidth
bubble.height = window.innerHeight
bubble.bubbleEl.style.height = bubble.height + 'px'
bubble.canvas.width = bubble.width
bubble.canvas.height = bubble.height
}
function animate() {
if (bubble.animate) {
bubble.ctx.clearRect(0, 0, bubble.width, bubble.height)
for (const i in bubble.circles) {
bubble.circles[i].draw()
}
}
bubble.requestId = requestAnimationFrame(animate)
}
class Circle {
pos: {
x: number
y: number
}
alpha: number
scale: number
velocity: number
draw: () => void
constructor() {
this.pos = {
x: Math.random() * bubble.width,
y: bubble.height + Math.random() * 100,
}
this.alpha = 0.1 + Math.random() * 0.3
this.scale = 0.1 + Math.random() * 0.3
this.velocity = Math.random()
this.draw = function () {
this.pos.y -= this.velocity
this.alpha -= 0.0005
bubble.ctx.beginPath()
bubble.ctx.arc(this.pos.x, this.pos.y, this.scale * 10, 0, 2 * Math.PI, false)
bubble.ctx.fillStyle = 'rgba(255,255,255,' + this.alpha + ')'
bubble.ctx.fill()
}
}
}
function addListeners() {
window.addEventListener('scroll', scrollCheck)
window.addEventListener('resize', resize)
}
export function removeListeners() {
window.removeEventListener('scroll', scrollCheck)
window.removeEventListener('resize', resize)
cancelAnimationFrame(bubble.requestId)
}

View File

@@ -22,14 +22,14 @@ export const routePush = async (to: RouteLocationRaw) => {
type: 'error'
})
} else if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
ElNotification({
message: 'utils.Navigation failed, it is at the navigation target position!',
type: 'warning'
})
// ElNotification({
// message: '已在目标页',
// type: 'warning'
// })
}
} catch (error) {
ElNotification({
message: 'utils.Navigation failed, invalid route!',
message: '导航失败,路由无效',
type: 'error'
})
console.error(error)

View File

@@ -1,78 +1,8 @@
<template>
<el-form :model="form" label-width="120px">
<el-form-item label="Activity name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="Activity zone">
<el-select v-model="form.region" placeholder="please select your zone">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="Activity time">
<el-col :span="11">
<el-date-picker
v-model="form.date1"
type="date"
placeholder="Pick a date"
style="width: 100%"
/>
</el-col>
<el-col :span="2" class="text-center">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<el-time-picker
v-model="form.date2"
placeholder="Pick a time"
style="width: 100%"
/>
</el-col>
</el-form-item>
<el-form-item label="Instant delivery">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="Activity type">
<el-checkbox-group v-model="form.type">
<el-checkbox label="Online activities" name="type" />
<el-checkbox label="Promotion activities" name="type" />
<el-checkbox label="Offline activities" name="type" />
<el-checkbox label="Simple brand exposure" name="type" />
</el-checkbox-group>
</el-form-item>
<el-form-item label="Resources">
<el-radio-group v-model="form.resource">
<el-radio label="Sponsor" />
<el-radio label="Venue" />
</el-radio-group>
</el-form-item>
<el-form-item label="Activity form">
<el-input v-model="form.desc" type="textarea" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">Create</el-button>
<el-button>Cancel</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
// do not use same name with ref
const form = reactive({
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: '',
})
const onSubmit = () => {
console.log('submit!')
}
</script>
123123
</template>
<script lang='ts' setup>
</script>

View File

@@ -1 +1,213 @@
<template>login</template>
<template>
<div>
<div @contextmenu.stop='' id='bubble' class='bubble'>
<canvas id='bubble-canvas' class='bubble-canvas'></canvas>
</div>
<div class='login'>
<div class='login-box'>
<div class='head'>
<img src='@/assets/login-header.png' alt='' />
</div>
<div class='form'>
<img class='profile-avatar' src='@/assets/avatar.png' alt='' />
<div class='content'>
<el-form @keyup.enter='onSubmit()' ref='formRef' size='large' :model='form'>
<el-form-item prop='username'>
<el-input
ref='usernameRef'
type='text'
clearable
v-model='form.username'
placeholder='请输入账号'
>
<template #prefix>
<Icon name='fa fa-user' class='form-item-icon' size='16'
color='var(--el-input-icon-color)' />
</template>
</el-input>
</el-form-item>
<el-form-item prop='password'>
<el-input
ref='passwordRef'
v-model='form.password'
type='password'
placeholder='请输入密码'
show-password
>
<template #prefix>
<Icon name='fa fa-unlock-alt' class='form-item-icon' size='16'
color='var(--el-input-icon-color)' />
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-button
:loading='state.submitLoading'
class='submit-button'
round
type='primary'
size='large'
@click='onSubmit()'
>
登录
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang='ts'>
import { onMounted, onBeforeUnmount, reactive, ref, nextTick } from 'vue'
import * as pageBubble from '@/utils/pageBubble'
import type { FormInstance, InputInstance } from 'element-plus'
let timer: number
const formRef = ref<FormInstance>()
const usernameRef = ref<InputInstance>()
const passwordRef = ref<InputInstance>()
const state = reactive({
showCaptcha: false,
submitLoading: false
})
const form = reactive({
username: '',
password: '',
})
const focusInput = () => {
if (form.username === '') {
usernameRef.value!.focus()
} else if (form.password === '') {
passwordRef.value!.focus()
}
}
onMounted(() => {
timer = window.setTimeout(() => {
pageBubble.init()
}, 1000)
})
onBeforeUnmount(() => {
clearTimeout(timer)
pageBubble.removeListeners()
})
const onSubmit = (captchaInfo = '') => {
state.submitLoading = true
setTimeout(() => {
state.submitLoading = false
}, 3000)
}
</script>
<style scoped lang='scss'>
.switch-language {
position: fixed;
top: 20px;
right: 20px;
z-index: 1;
}
.bubble {
overflow: hidden;
background: url(@/assets/bg.jpg) repeat;
}
.form-item-icon {
height: auto;
}
.login {
position: absolute;
top: 0;
display: flex;
width: 100vw;
height: 100vh;
align-items: center;
justify-content: center;
.login-box {
overflow: hidden;
width: 430px;
padding: 0;
background: var(--ba-bg-color-overlay);
margin-bottom: 80px;
}
.head {
background: #ccccff;
img {
display: block;
width: 430px;
margin: 0 auto;
user-select: none;
}
}
.form {
position: relative;
.profile-avatar {
display: block;
position: absolute;
height: 100px;
width: 100px;
border-radius: 50%;
border: 4px solid var(--ba-bg-color-overlay);
top: -50px;
right: calc(50% - 50px);
z-index: 2;
user-select: none;
}
.content {
padding: 100px 40px 40px 40px;
}
.submit-button {
width: 100%;
letter-spacing: 2px;
font-weight: 300;
margin-top: 15px;
--el-button-bg-color: var(--el-color-primary);
}
}
}
@media screen and (max-width: 720px) {
.login {
display: flex;
align-items: center;
justify-content: center;
.login-box {
width: 340px;
margin-top: 0;
}
}
}
.chang-lang :deep(.el-dropdown-menu__item) {
justify-content: center;
}
.content :deep(.el-input__prefix) {
display: flex;
align-items: center;
}
@media screen and (max-height: 800px) {
.login .login-box {
margin-bottom: 0;
}
}
</style>