登录叶细胞

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

View File

@@ -14,6 +14,7 @@
"element-plus": "^2.4.4", "element-plus": "^2.4.4",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"nprogress": "^0.2.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1", "pinia-plugin-persistedstate": "^3.2.1",
"screenfull": "^6.0.2", "screenfull": "^6.0.2",

6
pnpm-lock.yaml generated
View File

@@ -9,6 +9,7 @@ specifiers:
element-plus: ^2.4.4 element-plus: ^2.4.4
lodash-es: ^4.17.21 lodash-es: ^4.17.21
mitt: ^3.0.1 mitt: ^3.0.1
nprogress: ^0.2.0
pinia: ^2.1.7 pinia: ^2.1.7
pinia-plugin-persistedstate: ^3.2.1 pinia-plugin-persistedstate: ^3.2.1
sass: ^1.69.5 sass: ^1.69.5
@@ -25,6 +26,7 @@ dependencies:
element-plus: 2.4.4_vue@3.3.13 element-plus: 2.4.4_vue@3.3.13
lodash-es: 4.17.21 lodash-es: 4.17.21
mitt: 3.0.1 mitt: 3.0.1
nprogress: 0.2.0
pinia: 2.1.7_dembj2eby4ermcojoe7jay3m6m pinia: 2.1.7_dembj2eby4ermcojoe7jay3m6m
pinia-plugin-persistedstate: 3.2.1_pinia@2.1.7 pinia-plugin-persistedstate: 3.2.1_pinia@2.1.7
screenfull: 6.0.2 screenfull: 6.0.2
@@ -860,6 +862,10 @@ packages:
resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==}
dev: false dev: false
/nprogress/0.2.0:
resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==}
dev: false
/path-browserify/1.0.1: /path-browserify/1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
dev: true dev: true

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

View File

@@ -1,15 +1,5 @@
<template> <template>
<div class="nav-menus" :class="configStore.layout.layoutMode"> <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' : ''"> <div @click="onFullScreen" class="nav-menu-item" :class="state.isFullScreen ? 'hover' : ''">
<Icon <Icon
:color="configStore.getColorVal('headerBarTabColor')" :color="configStore.getColorVal('headerBarTabColor')"

View File

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

View File

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

View File

@@ -1,75 +1,31 @@
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import staticRoutes from '@/router/static' import staticRoutes from '@/router/static'
import { useConfig } from '@/stores/config'
import NProgress from 'nprogress'
import { loading } from '@/utils/loading'
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes: staticRoutes routes: staticRoutes
}) })
// router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
// NProgress.configure({ showSpinner: false }) NProgress.configure({ showSpinner: false })
// NProgress.start() NProgress.start()
// if (!window.existLoading) { if (!window.existLoading) {
// loading.show() loading.show()
// window.existLoading = true window.existLoading = true
// } }
console.log(to)
next()
})
// // 按需动态加载页面的语言包-start // 路由加载后
// let loadPath: string[] = [] router.afterEach(() => {
// const config = useConfig() if (window.existLoading) {
// if (to.path in langAutoLoadMap) { loading.hide()
// loadPath.push(...langAutoLoadMap[to.path as keyof typeof langAutoLoadMap]) }
// } NProgress.done()
// 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()
// })
export default router export default router

View File

@@ -34,6 +34,14 @@ export const adminBaseRoute = {
*/ */
const staticRoutes: Array<RouteRecordRaw> = [ const staticRoutes: Array<RouteRecordRaw> = [
adminBaseRoute, adminBaseRoute,
{
path: '/',
redirect: (to) => {
return {
name: 'adminMainLoading'
}
}
},
{ {
// 管理员登录页 - 不放在 adminBaseRoute.children 因为登录页不需要使用后台的布局 // 管理员登录页 - 不放在 adminBaseRoute.children 因为登录页不需要使用后台的布局
path: '/login', path: '/login',
@@ -43,7 +51,6 @@ const staticRoutes: Array<RouteRecordRaw> = [
title: pageTitle('login') title: pageTitle('login')
} }
}, },
{ {
path: '/:path(.*)*', path: '/:path(.*)*',
redirect: '/404' redirect: '/404'
@@ -56,6 +63,21 @@ const staticRoutes: Array<RouteRecordRaw> = [
meta: { meta: {
title: pageTitle('notFound') // 页面不存在 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' type: 'error'
}) })
} else if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { } else if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
ElNotification({ // ElNotification({
message: 'utils.Navigation failed, it is at the navigation target position!', // message: '已在目标页',
type: 'warning' // type: 'warning'
}) // })
} }
} catch (error) { } catch (error) {
ElNotification({ ElNotification({
message: 'utils.Navigation failed, invalid route!', message: '导航失败,路由无效',
type: 'error' type: 'error'
}) })
console.error(error) console.error(error)

View File

@@ -1,78 +1,8 @@
<template> <template>
<el-form :model="form" label-width="120px"> 123123
<el-form-item label="Activity name"> </template>
<el-input v-model="form.name" />
</el-form-item> <script lang='ts' setup>
<el-form-item label="Activity zone">
<el-select v-model="form.region" placeholder="please select your zone"> </script>
<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>

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>