first commit
This commit is contained in:
102
src/page/index/index.vue
Executable file
102
src/page/index/index.vue
Executable file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="avue-contail"
|
||||
:class="{'avue--collapse':isCollapse,}">
|
||||
<div class="avue-layout"
|
||||
:class="{'avue-layout--horizontal':isHorizontal}">
|
||||
<div class="avue-sidebar"
|
||||
v-show="validSidebar">
|
||||
<!-- 左侧导航栏 -->
|
||||
<logo />
|
||||
<sidebar />
|
||||
</div>
|
||||
<div class="avue-main">
|
||||
<!-- 顶部导航栏 -->
|
||||
<top ref="top" />
|
||||
<!-- 顶部标签卡 -->
|
||||
<tags />
|
||||
<search class="avue-view"
|
||||
v-show="isSearch"></search>
|
||||
<!-- 主体视图层 -->
|
||||
<div id="avue-view"
|
||||
v-show="!isSearch"
|
||||
v-if="isRefresh">
|
||||
<router-view #="{ Component }">
|
||||
<keep-alive :include="$store.getters.tagsKeep">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</div>
|
||||
<div class="avue-footer">
|
||||
<p class="copyright">© 2018-2021 Avue designed by smallwei</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<wechat></wechat>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import index from '@/mixins/index'
|
||||
import wechat from './wechat.vue'
|
||||
import { validatenull } from 'utils/validate'
|
||||
import { mapGetters } from "vuex";
|
||||
import tags from "./tags.vue";
|
||||
import search from "./search.vue";
|
||||
import logo from "./logo.vue";
|
||||
import top from "./top/index.vue";
|
||||
import sidebar from "./sidebar/index.vue";
|
||||
export default {
|
||||
mixins: [index],
|
||||
components: {
|
||||
top,
|
||||
logo,
|
||||
tags,
|
||||
search,
|
||||
sidebar,
|
||||
wechat
|
||||
},
|
||||
name: "index",
|
||||
provide () {
|
||||
return {
|
||||
index: this
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["isHorizontal", "isRefresh", "isLock", "isCollapse", "isSearch", "menu", "setting",]),
|
||||
validSidebar () {
|
||||
return !((this.$route.meta || {}).menu == false || (this.$route.query || {}).menu == 'false')
|
||||
}
|
||||
},
|
||||
props: [],
|
||||
methods: {
|
||||
//打开菜单
|
||||
openMenu (item = {}) {
|
||||
this.$store.dispatch("GetMenu", item.parentId).then(data => {
|
||||
if (data.length !== 0) {
|
||||
this.$router.$avueRouter.formatRoutes(data, true);
|
||||
}
|
||||
//当点击顶部菜单做的事件
|
||||
if (!validatenull(item)) {
|
||||
let itemActive = {},
|
||||
childItemActive = 0;
|
||||
//vue-router路由
|
||||
if (item.path) {
|
||||
itemActive = item;
|
||||
} else {
|
||||
if (this.menu[childItemActive].length == 0) {
|
||||
itemActive = this.menu[childItemActive];
|
||||
} else {
|
||||
itemActive = this.menu[childItemActive].children[childItemActive];
|
||||
}
|
||||
}
|
||||
this.$store.commit('SET_MENUID', item);
|
||||
this.$router.push({
|
||||
path: itemActive.path
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
7
src/page/index/layout.vue
Executable file
7
src/page/index/layout.vue
Executable file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<router-view #="{ Component }">
|
||||
<keep-alive :include="$store.getters.tagsKeep">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</template>
|
||||
32
src/page/index/logo.vue
Executable file
32
src/page/index/logo.vue
Executable file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="avue-logo">
|
||||
<transition name="fade">
|
||||
<span v-if="getScreen(isCollapse)"
|
||||
class="avue-logo_subtitle"
|
||||
key="0">
|
||||
{{website.logo}}
|
||||
</span>
|
||||
</transition>
|
||||
<transition-group name="fade">
|
||||
<template v-if="getScreen(!isCollapse)">
|
||||
<span class="avue-logo_title"
|
||||
key="1">{{website.indexTitle}} </span>
|
||||
</template>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
name: "logo",
|
||||
data () {
|
||||
return {};
|
||||
},
|
||||
created () { },
|
||||
computed: {
|
||||
...mapGetters(["isCollapse"])
|
||||
},
|
||||
methods: {}
|
||||
};
|
||||
</script>
|
||||
177
src/page/index/search.vue
Executable file
177
src/page/index/search.vue
Executable file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div class="avue-searchs"
|
||||
@click.self="handleEsc">
|
||||
<div class="avue-searchs__title">Avue菜单搜索</div>
|
||||
<div class="avue-searchs__content">
|
||||
<div class="avue-searchs__form">
|
||||
<el-input :placeholder="$t('search')"
|
||||
v-model="value"
|
||||
@keydown.esc="handleEsc">
|
||||
<template #append>
|
||||
<el-button icon="el-icon-search"></el-button>
|
||||
</template>
|
||||
|
||||
</el-input>
|
||||
<p>
|
||||
<el-tag>你可以使用快捷键esc 关闭</el-tag>
|
||||
</p>
|
||||
</div>
|
||||
<div class="avue-searchs__list">
|
||||
<el-scrollbar class="avue-searchs__scrollbar">
|
||||
<div class="avue-searchs__item"
|
||||
v-for="(item,index) in menus"
|
||||
:key="index"
|
||||
@click="handleSelect(item)">
|
||||
<i :class="[item[iconKey],'avue-searchs__item-icon']"></i>
|
||||
<span class="avue-searchs__item-title">{{item[labelKey]}}</span>
|
||||
<div class="avue-searchs__item-path">
|
||||
{{item[pathKey]}}
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
value: "",
|
||||
menus: [],
|
||||
menuList: []
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getMenuList();
|
||||
},
|
||||
watch: {
|
||||
value () {
|
||||
this.querySearch();
|
||||
},
|
||||
menu () {
|
||||
this.getMenuList();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labelKey () {
|
||||
return this.website.menu.label
|
||||
},
|
||||
pathKey () {
|
||||
return this.website.menu.path
|
||||
},
|
||||
iconKey () {
|
||||
return this.website.menu.icon
|
||||
},
|
||||
childrenKey () {
|
||||
return (
|
||||
this.website.menu.children
|
||||
);
|
||||
},
|
||||
...mapGetters(["menu"])
|
||||
},
|
||||
methods: {
|
||||
handleEsc () {
|
||||
this.$store.commit('SET_IS_SEARCH', false)
|
||||
},
|
||||
getMenuList () {
|
||||
const findMenu = list => {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const ele = Object.assign({}, list[i]);
|
||||
if (this.validatenull(ele[this.childrenKey])) {
|
||||
this.menuList.push(ele);
|
||||
} else {
|
||||
findMenu(ele[this.childrenKey]);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.menuList = [];
|
||||
findMenu(this.menu);
|
||||
this.menus = this.menuList;
|
||||
},
|
||||
querySearch () {
|
||||
var restaurants = this.menuList;
|
||||
var queryString = this.value
|
||||
this.menus = queryString
|
||||
? this.menuList.filter(this.createFilter(queryString))
|
||||
: restaurants;
|
||||
},
|
||||
createFilter (queryString) {
|
||||
return restaurant => {
|
||||
return (
|
||||
restaurant[this.labelKey].toLowerCase().indexOf(queryString.toLowerCase()) ===
|
||||
0
|
||||
);
|
||||
};
|
||||
},
|
||||
handleSelect (item) {
|
||||
this.value = "";
|
||||
this.$router.push({
|
||||
path: item[this.pathKey],
|
||||
query: item.query
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.avue-searchs {
|
||||
padding-top: 50px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
z-index: 1024;
|
||||
&__title {
|
||||
margin-bottom: 60px;
|
||||
text-align: center;
|
||||
font-size: 42px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
text-indent: 2px;
|
||||
}
|
||||
&__form {
|
||||
margin: 0 auto 50px auto;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
p {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
&__scrollbar {
|
||||
height: 400px;
|
||||
}
|
||||
&__list {
|
||||
box-sizing: border-box;
|
||||
padding: 20px 30px;
|
||||
margin: 0 auto;
|
||||
width: 70%;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ebeef5;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
color: #303133;
|
||||
transition: 0.3s;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
&__item {
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px dashed #eee;
|
||||
&-icon {
|
||||
margin-right: 5px;
|
||||
font-size: 18px;
|
||||
}
|
||||
&-title {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
&-path {
|
||||
line-height: 30px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
166
src/page/index/setting.vue
Executable file
166
src/page/index/setting.vue
Executable file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<el-button @click="show=true"
|
||||
class="setting-icon"
|
||||
type="primary"
|
||||
icon="el-icon-setting"
|
||||
circle></el-button>
|
||||
<el-drawer append-to-body
|
||||
:with-header="false"
|
||||
v-model="show"
|
||||
size="30%">
|
||||
<div class="setting">
|
||||
<h5>导航模式</h5>
|
||||
<div class="setting-checkbox">
|
||||
<el-tooltip class="item"
|
||||
effect="dark"
|
||||
content="侧边菜单布局"
|
||||
placement="top">
|
||||
<div @click="setting.sidebar='vertical'"
|
||||
class="setting-checkbox-item setting-checkbox-item--side">
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item"
|
||||
effect="dark"
|
||||
content="顶部菜单布局"
|
||||
placement="top">
|
||||
<div @click="setting.sidebar='horizontal'"
|
||||
class="setting-checkbox-item setting-checkbox-item--top">
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<h5>页面布局</h5>
|
||||
<div class="setting-checkbox">
|
||||
<div class="setting-item"
|
||||
v-for="(item,index) in list1"
|
||||
:key="index">
|
||||
{{item.label}}:
|
||||
<el-switch v-model="setting[item.value]">
|
||||
</el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<h5>功能调试</h5>
|
||||
<div class="setting-checkbox">
|
||||
<div class="setting-item"
|
||||
v-for="(item,index) in list2"
|
||||
:key="index">
|
||||
{{item.label}}:
|
||||
<el-switch v-model="setting[item.value]">
|
||||
</el-switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
show: false,
|
||||
list1: [{
|
||||
label: '导航标签',
|
||||
value: 'tag'
|
||||
}, {
|
||||
label: '菜单折叠',
|
||||
value: 'collapse'
|
||||
}, {
|
||||
label: '菜单搜索',
|
||||
value: 'search'
|
||||
}, {
|
||||
label: '屏幕全屏',
|
||||
value: 'fullscren'
|
||||
}, {
|
||||
label: '主题选择',
|
||||
value: 'theme'
|
||||
}, {
|
||||
label: '顶部菜单',
|
||||
value: 'menu'
|
||||
}],
|
||||
list2: [{
|
||||
label: '日志调试',
|
||||
value: 'debug'
|
||||
}, {
|
||||
label: '屏幕锁定',
|
||||
value: 'lock'
|
||||
}]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["isHorizontal", 'setting']),
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" >
|
||||
.setting {
|
||||
&-icon {
|
||||
color: #666;
|
||||
position: fixed;
|
||||
bottom: 200px;
|
||||
right: 20px;
|
||||
z-index: 2048;
|
||||
}
|
||||
&-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
&-checkbox {
|
||||
&--check {
|
||||
position: absolute;
|
||||
color: var(--el-color-primary);
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
&-item {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 44px;
|
||||
height: 36px;
|
||||
margin-right: 16px;
|
||||
overflow: hidden;
|
||||
background-color: #f0f2f5;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 2.5px 0 rgba(0, 0, 0, 0.18);
|
||||
cursor: pointer;
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 33%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
content: "";
|
||||
}
|
||||
&:after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 25%;
|
||||
background-color: #fff;
|
||||
content: "";
|
||||
}
|
||||
&--side {
|
||||
&:before {
|
||||
z-index: 1;
|
||||
background-color: #001529;
|
||||
content: "";
|
||||
}
|
||||
&:after {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
&--top {
|
||||
&:after {
|
||||
background-color: #001529;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
39
src/page/index/sidebar/index.vue
Executable file
39
src/page/index/sidebar/index.vue
Executable file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<el-scrollbar class="avue-menu">
|
||||
<div v-if="menu&&menu.length==0&&!isHorizontal"
|
||||
class="avue-sidebar--tip">{{$t('menuTip')}}</div>
|
||||
<el-menu unique-opened
|
||||
:default-active="activeMenu"
|
||||
:mode="setting.sidebar"
|
||||
:collapse="getScreen(isCollapse)">
|
||||
<sidebar-item :menu="menu"></sidebar-item>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import sidebarItem from "./sidebarItem.vue";
|
||||
export default {
|
||||
name: "sidebar",
|
||||
components: { sidebarItem },
|
||||
inject: ["index"],
|
||||
created () {
|
||||
this.index.openMenu()
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["isHorizontal", "setting", "menu", "tag", "isCollapse", "menuId"]),
|
||||
activeMenu () {
|
||||
const route = this.$route;
|
||||
const { meta, path } = route;
|
||||
if (meta.activeMenu) {
|
||||
return meta.activeMenu;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
||||
89
src/page/index/sidebar/sidebarItem.vue
Executable file
89
src/page/index/sidebar/sidebarItem.vue
Executable file
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<template v-for="item in menu">
|
||||
<el-menu-item v-if="validatenull(item[childrenKey]) && validRoles(item)"
|
||||
:index="getPath(item)"
|
||||
@click="open(item)"
|
||||
:key="item[labelKey]">
|
||||
<i :class="item[iconKey]"></i>
|
||||
<template #title>
|
||||
<span :alt="item[pathKey]">{{getTitle(item)}}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-sub-menu v-else-if="!validatenull(item[childrenKey])&&validRoles(item)"
|
||||
:index="getPath(item)"
|
||||
:key="item[labelKey]">
|
||||
<template #title>
|
||||
<i :class="item[iconKey]"></i>
|
||||
<span>{{getTitle(item)}}</span>
|
||||
</template>
|
||||
<template v-for="(child,cindex) in item[childrenKey]"
|
||||
:key="child[labelKey]">
|
||||
<el-menu-item :index="getPath(child)"
|
||||
@click="open(child)"
|
||||
v-if="validatenull(child[childrenKey])">
|
||||
<i :class="child[iconKey]"></i>
|
||||
<template #title>
|
||||
<span>{{getTitle(child)}}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<sidebar-item v-else
|
||||
:menu="[child]"
|
||||
:key="cindex"></sidebar-item>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { validatenull } from 'utils/validate'
|
||||
import website from '@/config/website'
|
||||
export default {
|
||||
name: "sidebarItem",
|
||||
data () {
|
||||
return {
|
||||
props: website.menu
|
||||
}
|
||||
},
|
||||
props: {
|
||||
menu: Array
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["roles"]),
|
||||
labelKey () {
|
||||
return this.props.label
|
||||
},
|
||||
pathKey () {
|
||||
return this.props.path
|
||||
},
|
||||
queryKey () {
|
||||
return this.props.query
|
||||
},
|
||||
iconKey () {
|
||||
return this.props.icon
|
||||
},
|
||||
childrenKey () {
|
||||
return this.props.children
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validatenull,
|
||||
getPath (item) {
|
||||
return item[this.pathKey]
|
||||
},
|
||||
getTitle (item) {
|
||||
return this.$router.$avueRouter.generateTitle(item, this.props);
|
||||
},
|
||||
validRoles (item) {
|
||||
item.meta = item.meta || {};
|
||||
return item.meta.roles ? item.meta.roles.includes(this.roles) : true;
|
||||
},
|
||||
open (item) {
|
||||
this.$router.push({
|
||||
path: item[this.pathKey],
|
||||
query: item[this.queryKey]
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
169
src/page/index/tags.vue
Executable file
169
src/page/index/tags.vue
Executable file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div class="avue-tags"
|
||||
v-if="setting.tag"
|
||||
@click="contextmenuFlag=false">
|
||||
<!-- tag盒子 -->
|
||||
<div v-if="contextmenuFlag"
|
||||
class="avue-tags__contentmenu"
|
||||
:style="{left:contentmenuX+'px',top:contentmenuY+'px'}">
|
||||
<div class="item"
|
||||
@click="closeOthersTags">{{$t('tagsView.closeOthers')}}</div>
|
||||
<div class="item"
|
||||
@click="closeAllTags">{{$t('tagsView.closeAll')}}</div>
|
||||
</div>
|
||||
<div class="avue-tags__box">
|
||||
<el-tabs v-model="active"
|
||||
type="card"
|
||||
@contextmenu="handleContextmenu"
|
||||
:closable="tagLen!==1"
|
||||
@tab-click="openTag"
|
||||
@edit="menuTag">
|
||||
<el-tab-pane v-for="(item,index) in tagList"
|
||||
:key="index"
|
||||
:label="generateTitle(item)"
|
||||
:name="item.fullPath">
|
||||
<template #label>
|
||||
<span>
|
||||
{{generateTitle(item)}}
|
||||
<i class="el-icon-refresh"
|
||||
:class="{'turn':refresh}"
|
||||
@click="handleRefresh"
|
||||
v-if="active==item.fullPath"></i>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
<el-dropdown class="avue-tags__menu">
|
||||
<el-button type="primary">
|
||||
{{$t('tagsView.menu')}}
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="openSearch">{{$t('tagsView.search')}}</el-dropdown-item>
|
||||
<el-dropdown-item @click="closeOthersTags">{{$t('tagsView.closeOthers')}}</el-dropdown-item>
|
||||
<el-dropdown-item @click="closeAllTags">{{$t('tagsView.closeAll')}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
name: "tags",
|
||||
data () {
|
||||
return {
|
||||
refresh: false,
|
||||
active: "",
|
||||
contentmenuX: "",
|
||||
contentmenuY: "",
|
||||
contextmenuFlag: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
tag: {
|
||||
handler (val) {
|
||||
this.active = val.fullPath;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
contextmenuFlag () {
|
||||
window.addEventListener("mousedown", this.watchContextmenu);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["tagWel", "tagList", "tag", "setting"]),
|
||||
tagLen () {
|
||||
return this.tagList.length || 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openSearch () {
|
||||
this.$store.commit('SET_IS_SEARCH', true)
|
||||
},
|
||||
handleRefresh () {
|
||||
this.refresh = true;
|
||||
this.$store.commit('SET_IS_REFRESH', false);
|
||||
setTimeout(() => {
|
||||
this.$store.commit('SET_IS_REFRESH', true);
|
||||
}, 100)
|
||||
setTimeout(() => {
|
||||
this.refresh = false;
|
||||
}, 500)
|
||||
},
|
||||
generateTitle (item) {
|
||||
return this.$router.$avueRouter.generateTitle({
|
||||
...item,
|
||||
...{
|
||||
label: item.name
|
||||
}
|
||||
});
|
||||
},
|
||||
watchContextmenu (event) {
|
||||
if (!this.$el.contains(event.target) || event.button !== 0) {
|
||||
this.contextmenuFlag = false;
|
||||
}
|
||||
window.removeEventListener("mousedown", this.watchContextmenu);
|
||||
},
|
||||
handleContextmenu (event) {
|
||||
let target = event.target;
|
||||
let flag = false;
|
||||
if (target.className.indexOf("el-tabs__item") > -1) flag = true;
|
||||
else if (target.parentNode.className.indexOf("el-tabs__item") > -1) {
|
||||
target = target.parentNode;
|
||||
flag = true;
|
||||
}
|
||||
if (flag) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.contentmenuX = event.clientX;
|
||||
this.contentmenuY = event.clientY;
|
||||
this.tagName = target.getAttribute("aria-controls").slice(5);
|
||||
this.contextmenuFlag = true;
|
||||
}
|
||||
},
|
||||
menuTag (value, action) {
|
||||
if (action === "remove") {
|
||||
let { tag, key } = this.findTag(value);
|
||||
this.$store.commit("DEL_TAG", tag);
|
||||
if (tag.fullPath === this.tag.fullPath) {
|
||||
tag = this.tagList[key - 1]
|
||||
this.$router.push({
|
||||
path: tag.path,
|
||||
query: tag.query
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
openTag (item) {
|
||||
let value = item.props.name
|
||||
let { tag } = this.findTag(value)
|
||||
this.$router.push({
|
||||
path: tag.path,
|
||||
query: tag.query
|
||||
})
|
||||
},
|
||||
findTag (fullPath) {
|
||||
let tag = this.tagList.find(item => item.fullPath === fullPath);
|
||||
let key = this.tagList.findIndex(item => item.fullPath === fullPath);
|
||||
return { tag, key }
|
||||
},
|
||||
closeOthersTags () {
|
||||
this.contextmenuFlag = false;
|
||||
this.$store.commit('DEL_TAG_OTHER')
|
||||
},
|
||||
closeAllTags () {
|
||||
this.contextmenuFlag = false;
|
||||
this.$store.commit('DEL_ALL_TAG')
|
||||
this.$router.push(this.tagWel);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
131
src/page/index/top/index.vue
Executable file
131
src/page/index/top/index.vue
Executable file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div class="avue-top">
|
||||
<div class="top-bar__left">
|
||||
<div class="avue-breadcrumb"
|
||||
:class="[{ 'avue-breadcrumb--active': isCollapse }]"
|
||||
v-if="setting.collapse&&!isHorizontal">
|
||||
<i class="icon-navicon"
|
||||
@click="setCollapse"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="top-bar__title">
|
||||
<top-menu ref="topMenu"
|
||||
v-if="setting.menu"></top-menu>
|
||||
<top-search class="top-bar__item"
|
||||
v-if="setting.search"></top-search>
|
||||
</div>
|
||||
<div class="top-bar__right">
|
||||
<div v-if="setting.color"
|
||||
class="top-bar__item">
|
||||
<top-color></top-color>
|
||||
</div>
|
||||
<div v-if="setting.lock"
|
||||
class="top-bar__item">
|
||||
<top-lock></top-lock>
|
||||
</div>
|
||||
<div v-if="setting.theme"
|
||||
class="top-bar__item">
|
||||
<top-theme></top-theme>
|
||||
</div>
|
||||
<div class="top-bar__item">
|
||||
<top-lang></top-lang>
|
||||
</div>
|
||||
<div class="top-bar__item"
|
||||
v-if="setting.fullscren">
|
||||
<top-full></top-full>
|
||||
</div>
|
||||
<div class="top-bar__item"
|
||||
v-if="setting.debug">
|
||||
<top-logs></top-logs>
|
||||
</div>
|
||||
<div class="top-user">
|
||||
<img class="top-bar__img"
|
||||
:src="userInfo.avatar">
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link">
|
||||
{{userInfo.username}}
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<router-link to="/">{{$t('navbar.dashboard')}}</router-link>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<router-link to="/info/index">{{$t('navbar.userinfo')}}</router-link>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<router-link to="/info/setting">{{$t('navbar.setting')}}</router-link>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout"
|
||||
divided>{{$t('navbar.logOut')}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<top-setting></top-setting>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import topLock from "./top-lock.vue";
|
||||
import topMenu from "./top-menu.vue";
|
||||
import topSearch from "./top-search.vue";
|
||||
import topTheme from "./top-theme.vue";
|
||||
import topLogs from "./top-logs.vue";
|
||||
import topColor from "./top-color.vue";
|
||||
import topLang from "./top-lang.vue";
|
||||
import topFull from "./top-full.vue";
|
||||
import topSetting from "../setting.vue";
|
||||
export default {
|
||||
components: {
|
||||
topLock,
|
||||
topMenu,
|
||||
topSearch,
|
||||
topTheme,
|
||||
topLogs,
|
||||
topColor,
|
||||
topLang,
|
||||
topFull,
|
||||
topSetting
|
||||
},
|
||||
name: "top",
|
||||
data () {
|
||||
return {};
|
||||
},
|
||||
filters: {},
|
||||
created () { },
|
||||
computed: {
|
||||
...mapGetters([
|
||||
"setting",
|
||||
"userInfo",
|
||||
"tagWel",
|
||||
"tagList",
|
||||
"isCollapse",
|
||||
"tag",
|
||||
"logsLen",
|
||||
"logsFlag",
|
||||
"isHorizontal"
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
setCollapse () {
|
||||
this.$store.commit("SET_COLLAPSE");
|
||||
},
|
||||
logout () {
|
||||
this.$confirm(this.$t("logoutTip"), this.$t("tip"), {
|
||||
confirmButtonText: this.$t("submitText"),
|
||||
cancelButtonText: this.$t("cancelText"),
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$store.dispatch("LogOut").then(() => {
|
||||
this.$router.push({ path: "/login" });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
70
src/page/index/top/top-color.vue
Normal file
70
src/page/index/top/top-color.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<el-color-picker size="small"
|
||||
class="theme-picker"
|
||||
popper-class="theme-picker-dropdown"
|
||||
v-model="themeVal"></el-color-picker>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex"; // default color
|
||||
export default {
|
||||
name: "topColor",
|
||||
data () {
|
||||
return {
|
||||
themeVal: '#409EFF'
|
||||
};
|
||||
},
|
||||
created () {
|
||||
this.themeVal = this.colorName;
|
||||
},
|
||||
watch: {
|
||||
themeVal (val, oldVal) {
|
||||
this.$store.commit("SET_COLOR_NAME", val);
|
||||
this.updateTheme(val, oldVal);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["colorName"])
|
||||
},
|
||||
methods: {
|
||||
hexToRgb (str) {
|
||||
let hexs = '';
|
||||
str = str.replace('#', '');
|
||||
hexs = str.match(/../g);
|
||||
for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16);
|
||||
return hexs;
|
||||
},
|
||||
// r 代表红色 | g 代表绿色 | b 代表蓝色
|
||||
rgbToHex (r, g, b) {
|
||||
let hexs = [r.toString(16), g.toString(16), b.toString(16)];
|
||||
for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`;
|
||||
return `#${hexs.join('')}`;
|
||||
},
|
||||
|
||||
getDarkColor (color, level) {
|
||||
let rgb = this.hexToRgb(color);
|
||||
for (let i = 0; i < 3; i++) rgb[i] = Math.floor(rgb[i] * (1 - level));
|
||||
return this.rgbToHex(rgb[0], rgb[1], rgb[2]);
|
||||
},
|
||||
|
||||
// color 颜色值字符串 | level 加深的程度,限0-1之间
|
||||
getLightColor (color, level) {
|
||||
let rgb = this.hexToRgb(color);
|
||||
for (let i = 0; i < 3; i++) rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]);
|
||||
return this.rgbToHex(rgb[0], rgb[1], rgb[2]);
|
||||
},
|
||||
|
||||
updateTheme (e) {
|
||||
if (!e) return
|
||||
// e就是选择了的颜色
|
||||
const pre = "--el-color-primary";
|
||||
const el = document.documentElement;
|
||||
el.style.setProperty(pre, e);
|
||||
// 这里是覆盖原有颜色的核心代码
|
||||
for (let i = 1; i < 10; i += 1) {
|
||||
document.documentElement.style.setProperty(`${pre}-light-${i}`, `${this.getLightColor(e, i / 10)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
24
src/page/index/top/top-full.vue
Normal file
24
src/page/index/top/top-full.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<i :class="isFullScren?'icon-tuichuquanping':'icon-quanping'"
|
||||
@click="handleScreen"></i>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { fullscreenToggel, listenfullscreen } from "utils/util";
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters(["isFullScren"])
|
||||
},
|
||||
mounted () {
|
||||
listenfullscreen(this.setScreen);
|
||||
},
|
||||
methods: {
|
||||
setScreen () {
|
||||
this.$store.commit("SET_FULLSCREN");
|
||||
},
|
||||
handleScreen () {
|
||||
fullscreenToggel();
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
45
src/page/index/top/top-lang.vue
Executable file
45
src/page/index/top/top-lang.vue
Executable file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<el-dropdown trigger="click"
|
||||
@command="handleSetLanguage">
|
||||
<i class="icon-zhongyingwen"></i>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item :disabled="language==='zh-cn'"
|
||||
command="zh-cn">中文</el-dropdown-item>
|
||||
<el-dropdown-item :disabled="language==='en'"
|
||||
command="en">English</el-dropdown-item>
|
||||
<el-dropdown-item :disabled="language==='ja'"
|
||||
command="ja">日文</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
name: "top-lang",
|
||||
data () {
|
||||
return {};
|
||||
},
|
||||
created () { },
|
||||
mounted () { },
|
||||
computed: {
|
||||
...mapGetters(["language", "tag"])
|
||||
},
|
||||
props: [],
|
||||
methods: {
|
||||
handleSetLanguage (lang) {
|
||||
this.$i18n.locale = lang;
|
||||
this.$store.commit("SET_LANGUAGE", lang);
|
||||
let tag = this.tag;
|
||||
let title = this.$router.$avueRouter.generateTitle(tag);
|
||||
//根据当前的标签也获取label的值动态设置浏览器标题
|
||||
this.$router.$avueRouter.setTitle(title);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
75
src/page/index/top/top-lock.vue
Executable file
75
src/page/index/top/top-lock.vue
Executable file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<span v-if="text"
|
||||
@click="handleLock">{{text }}</span>
|
||||
<i v-else
|
||||
class="icon-suoping"
|
||||
@click="handleLock"></i>
|
||||
<el-dialog title="设置锁屏密码"
|
||||
v-model="box"
|
||||
width="30%"
|
||||
append-to-body>
|
||||
<el-form :model="form"
|
||||
ref="form"
|
||||
label-width="80px">
|
||||
<el-form-item label="锁屏密码"
|
||||
prop="passwd"
|
||||
:rules="[{ required: true, message: '锁屏密码不能为空'}]">
|
||||
<el-input v-model="form.passwd"
|
||||
placeholder="请输入锁屏密码">
|
||||
<template #append>
|
||||
<el-button @click="handleSetLock"
|
||||
icon="el-icon-lock"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { validatenull } from "utils/validate";
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
name: "top-lock",
|
||||
data () {
|
||||
return {
|
||||
box: false,
|
||||
form: {
|
||||
passwd: ""
|
||||
}
|
||||
};
|
||||
},
|
||||
created () { },
|
||||
mounted () { },
|
||||
computed: {
|
||||
...mapGetters(["lockPasswd"]),
|
||||
},
|
||||
props: {
|
||||
text: String
|
||||
},
|
||||
methods: {
|
||||
handleSetLock () {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
this.$store.commit("SET_LOCK_PASSWD", this.form.passwd);
|
||||
this.handleLock();
|
||||
}
|
||||
});
|
||||
},
|
||||
handleLock () {
|
||||
if (validatenull(this.lockPasswd)) {
|
||||
this.box = true;
|
||||
return;
|
||||
}
|
||||
this.$store.commit("SET_LOCK");
|
||||
setTimeout(() => {
|
||||
this.$router.push({ path: "/lock" });
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
components: {}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
107
src/page/index/top/top-logs.vue
Executable file
107
src/page/index/top/top-logs.vue
Executable file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<span @click="logsFlag?'':handleOpen()">
|
||||
<el-badge :value="logsFlag?'':logsLen"
|
||||
:max="99">
|
||||
<i class="icon-rizhi1"></i>
|
||||
</el-badge>
|
||||
<el-dialog title="日志"
|
||||
v-model="box"
|
||||
width="60%"
|
||||
append-to-body>
|
||||
<el-button type="primary"
|
||||
icon="el-icon-upload"
|
||||
@click="send">上传服务器</el-button>
|
||||
<el-button type="danger"
|
||||
icon="el-icon-delete"
|
||||
@click="clear">清空本地日志</el-button>
|
||||
<el-table :data="logsList">
|
||||
<el-table-column prop="type"
|
||||
label="类型"
|
||||
width="50px">
|
||||
</el-table-column>
|
||||
<el-table-column prop="url"
|
||||
label="地址"
|
||||
show-overflow-tooltip
|
||||
width="180">
|
||||
</el-table-column>
|
||||
<el-table-column prop="message"
|
||||
show-overflow-tooltip
|
||||
label="内容">
|
||||
</el-table-column>
|
||||
<el-table-column prop="stack"
|
||||
show-overflow-tooltip
|
||||
label="错误堆栈">
|
||||
</el-table-column>
|
||||
<el-table-column prop="time"
|
||||
label="时间">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
name: "top-logs",
|
||||
data () {
|
||||
return {
|
||||
box: false
|
||||
};
|
||||
},
|
||||
created () { },
|
||||
mounted () { },
|
||||
computed: {
|
||||
...mapGetters(['logsList', "logsFlag", "logsLen"])
|
||||
},
|
||||
props: [],
|
||||
methods: {
|
||||
handleOpen () {
|
||||
this.box = true;
|
||||
},
|
||||
send () {
|
||||
this.$confirm("确定上传本地日志到服务器?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
this.$store.dispatch("SendLogs").then(() => {
|
||||
this.box = false;
|
||||
this.$message({
|
||||
type: "success",
|
||||
message: "发送成功!"
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => { });
|
||||
},
|
||||
clear () {
|
||||
this.$confirm("确定清空本地日志记录?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
this.$store.commit("CLEAR_LOGS");
|
||||
this.box = false;
|
||||
this.$message({
|
||||
type: "success",
|
||||
message: "清空成功!"
|
||||
});
|
||||
})
|
||||
.catch(() => { });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.code {
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
margin: 1em 0px;
|
||||
}
|
||||
</style>
|
||||
50
src/page/index/top/top-menu.vue
Executable file
50
src/page/index/top/top-menu.vue
Executable file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<el-menu class="top-menu"
|
||||
:default-active="activeIndex"
|
||||
mode="horizontal"
|
||||
text-color="#333">
|
||||
<template v-for="(item,index) in items"
|
||||
:key="index">
|
||||
<el-menu-item :index="item.parentId+''"
|
||||
@click="openMenu(item)">
|
||||
<template #title>
|
||||
<i class="icon-caidan"></i>
|
||||
<span>{{generateTitle(item)}}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
name: "top-menu",
|
||||
data () {
|
||||
return {
|
||||
activeIndex: "0",
|
||||
items: []
|
||||
};
|
||||
},
|
||||
inject: ["index"],
|
||||
created () {
|
||||
this.getMenu();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["tagCurrent", "menu"])
|
||||
},
|
||||
methods: {
|
||||
openMenu (item) {
|
||||
this.index.openMenu(item)
|
||||
},
|
||||
getMenu () {
|
||||
this.$store.dispatch("GetTopMenu").then(res => {
|
||||
this.items = res;
|
||||
});
|
||||
},
|
||||
generateTitle (item) {
|
||||
return this.$router.$avueRouter.generateTitle(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
122
src/page/index/top/top-search.vue
Executable file
122
src/page/index/top/top-search.vue
Executable file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<el-autocomplete class="top-search"
|
||||
popper-class="my-autocomplete"
|
||||
v-model="value"
|
||||
:fetch-suggestions="querySearch"
|
||||
:placeholder="$t('search')"
|
||||
@select="handleSelect">
|
||||
|
||||
<template #="{ item }">
|
||||
<i :class="[item[iconKey],'icon']"></i>
|
||||
<div class="name">{{ item[labelKey] }}</div>
|
||||
<div class="addr">{{ item[pathKey] }}</div>
|
||||
</template>
|
||||
</el-autocomplete>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
value: "",
|
||||
menuList: []
|
||||
};
|
||||
},
|
||||
created () {
|
||||
this.getMenuList();
|
||||
},
|
||||
|
||||
watch: {
|
||||
menu () {
|
||||
this.getMenuList();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labelKey () {
|
||||
return this.website.menu.label
|
||||
},
|
||||
pathKey () {
|
||||
return this.website.menu.path
|
||||
},
|
||||
iconKey () {
|
||||
return this.website.menu.icon
|
||||
},
|
||||
childrenKey () {
|
||||
return (
|
||||
this.website.menu.children
|
||||
);
|
||||
},
|
||||
...mapGetters(["menu"])
|
||||
},
|
||||
methods: {
|
||||
getMenuList () {
|
||||
const findMenu = list => {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const ele = Object.assign({}, list[i]);
|
||||
if (this.validatenull(ele[this.childrenKey])) {
|
||||
this.menuList.push(ele);
|
||||
} else {
|
||||
findMenu(ele[this.childrenKey]);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.menuList = [];
|
||||
findMenu(this.menu);
|
||||
},
|
||||
querySearch (queryString, cb) {
|
||||
var restaurants = this.menuList;
|
||||
var results = queryString
|
||||
? restaurants.filter(this.createFilter(queryString))
|
||||
: restaurants;
|
||||
// 调用 callback 返回建议列表的数据
|
||||
cb(results);
|
||||
},
|
||||
createFilter (queryString) {
|
||||
return restaurant => {
|
||||
return (
|
||||
restaurant[this.labelKey].toLowerCase().indexOf(queryString.toLowerCase()) ===
|
||||
0
|
||||
);
|
||||
};
|
||||
},
|
||||
handleSelect (item) {
|
||||
this.value = "";
|
||||
this.$router.push({
|
||||
path: item[this.pathKey],
|
||||
query: item.query
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.my-autocomplete {
|
||||
li {
|
||||
line-height: normal !important;
|
||||
padding: 7px !important;
|
||||
.icon {
|
||||
margin-right: 5px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.name {
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.addr {
|
||||
padding-top: 5px;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
color: #b4b4b4;
|
||||
}
|
||||
|
||||
.highlighted .addr {
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
124
src/page/index/top/top-theme.vue
Normal file
124
src/page/index/top/top-theme.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog title="选择"
|
||||
v-model="box"
|
||||
width="50%">
|
||||
<el-radio-group v-model="text"
|
||||
class="list">
|
||||
<el-row :span="24">
|
||||
<el-col v-for="(item,index) in list"
|
||||
:key="index"
|
||||
:md="4"
|
||||
:xs="12"
|
||||
:sm="4">
|
||||
<el-radio :label="item.value">{{item.name}}</el-radio>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-radio-group>
|
||||
</el-dialog>
|
||||
|
||||
<span>
|
||||
<i class="icon-zhuti"
|
||||
@click="open"></i>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { setTheme } from "utils/util";
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
box: false,
|
||||
text: "",
|
||||
list: [
|
||||
{
|
||||
name: "默认主题",
|
||||
value: "default"
|
||||
},
|
||||
{
|
||||
name: "白色主题",
|
||||
value: "theme-white"
|
||||
},
|
||||
{
|
||||
name: "黑色主题",
|
||||
value: "theme-dark"
|
||||
},
|
||||
{
|
||||
name: "hey主题",
|
||||
value: "theme-hey"
|
||||
},
|
||||
{
|
||||
name: "炫彩主题",
|
||||
value: "theme-star"
|
||||
},
|
||||
{
|
||||
name: "vip尊贵主题",
|
||||
value: "theme-vip"
|
||||
},
|
||||
{
|
||||
name: "智能工厂主题",
|
||||
value: "theme-bule"
|
||||
},
|
||||
{
|
||||
name: "iview主题",
|
||||
value: "theme-iview"
|
||||
},
|
||||
{
|
||||
name: "cool主题",
|
||||
value: "theme-cool"
|
||||
},
|
||||
{
|
||||
name: "d2主题",
|
||||
value: "theme-d2"
|
||||
},
|
||||
{
|
||||
name: "renren主题",
|
||||
value: "theme-renren"
|
||||
}, {
|
||||
name: "beautiful主题",
|
||||
value: "theme-beautiful"
|
||||
}, {
|
||||
name: "gin主题",
|
||||
value: "theme-gin"
|
||||
}, {
|
||||
name: "Mac OS主题",
|
||||
value: "mac-os"
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
text: function (val) {
|
||||
this.$store.commit("SET_THEME_NAME", val);
|
||||
setTheme(val);
|
||||
if (this.$store.getters.isMacOs) {
|
||||
this.$router.push(this.tagWel);
|
||||
setTimeout(() => location.reload())
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["themeName", "tagWel"])
|
||||
},
|
||||
mounted () {
|
||||
this.text = this.themeName;
|
||||
if (!this.text) {
|
||||
this.text = "";
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open () {
|
||||
this.box = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
61
src/page/index/wechat.vue
Normal file
61
src/page/index/wechat.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<el-dialog center
|
||||
:show-close="false"
|
||||
:close-on-press-escape="false"
|
||||
:close-on-click-modal="false"
|
||||
append-to-body
|
||||
v-model="dialogVisible"
|
||||
title="人机识别"
|
||||
width="400px">
|
||||
<center>
|
||||
<span>
|
||||
扫码下方二维码,回复<b>【模板验证码】</b><br />
|
||||
<span style="color:red">获得「模板验证码 + 交流群(一起摸🐟)」</span>
|
||||
</span>
|
||||
<br />
|
||||
<br />
|
||||
<img width="200"
|
||||
src="https://avuejs.com/images/icon/wechat.png" />
|
||||
<br />
|
||||
<br />
|
||||
<el-input v-model="value"
|
||||
placeholder="请输入验证码"></el-input>
|
||||
</center>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary"
|
||||
@click="submit">确 认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
value: '',
|
||||
dialogVisible: false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (window.localStorage.getItem('avue-cli_lock')) {
|
||||
return
|
||||
}
|
||||
//this.dialogVisible = true
|
||||
},
|
||||
methods: {
|
||||
submit () {
|
||||
if (this.value == '') {
|
||||
this.$message.error('验证码不能为空')
|
||||
return
|
||||
} else if (this.value != 'avue-cli') {
|
||||
this.$message.error('验证码不正确')
|
||||
return
|
||||
}
|
||||
this.dialogVisible = false
|
||||
window.localStorage.setItem('avue-cli_lock', true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
105
src/page/lock/index.vue
Executable file
105
src/page/lock/index.vue
Executable file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="login-container"
|
||||
@keyup.enter="handleLogin">
|
||||
<div class="login-time">
|
||||
{{time}}
|
||||
</div>
|
||||
<div class="login-weaper">
|
||||
<div class="login-left animate__animated animate__fadeInLeft">
|
||||
<img class="img"
|
||||
src="/img/logo.png"
|
||||
alt="">
|
||||
<p class="title">{{ $t('login.info') }}</p>
|
||||
</div>
|
||||
<div class="login-border animate__animated animate__fadeInRight">
|
||||
<div class="login-main">
|
||||
<div class="lock-form animate__animated animate__bounceInDown">
|
||||
<div class="animate__animated"
|
||||
:class="{'shake':passwdError,'animate__bounceOut':pass}">
|
||||
<h3 style="color:#333">{{userInfo.username}}</h3>
|
||||
<el-input placeholder="请输入登录密码"
|
||||
type="password"
|
||||
class="input-with-select animated"
|
||||
v-model="passwd"
|
||||
@keyup.enter="handleLogin">
|
||||
<template #append>
|
||||
<i class="icon-bofangqi-suoping"
|
||||
@click="handleLogin"></i>
|
||||
|
||||
<i class="icon-tuichu"
|
||||
@click="handleLogout"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters, mapState } from "vuex";
|
||||
export default {
|
||||
name: "lock",
|
||||
data () {
|
||||
return {
|
||||
time: "",
|
||||
passwd: "",
|
||||
passwdError: false,
|
||||
pass: false
|
||||
};
|
||||
},
|
||||
created () {
|
||||
this.getTime();
|
||||
setInterval(() => {
|
||||
this.getTime();
|
||||
}, 1000);
|
||||
},
|
||||
mounted () { },
|
||||
computed: {
|
||||
...mapGetters(["userInfo", "tag", "lockPasswd"])
|
||||
},
|
||||
props: [],
|
||||
methods: {
|
||||
getTime () {
|
||||
this.time = this.$dayjs().format('YYYY年MM月DD日 HH:mm:ss')
|
||||
},
|
||||
handleLogout () {
|
||||
this.$confirm("是否退出系统, 是否继续?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$store.dispatch("LogOut").then(() => {
|
||||
this.$router.push({ path: "/login" });
|
||||
});
|
||||
});
|
||||
},
|
||||
handleLogin () {
|
||||
if (this.passwd != this.lockPasswd) {
|
||||
this.passwd = "";
|
||||
this.$message({
|
||||
message: "解锁密码错误,请重新输入",
|
||||
type: "error"
|
||||
});
|
||||
this.passwdError = true;
|
||||
setTimeout(() => {
|
||||
this.passwdError = false;
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
this.pass = true;
|
||||
setTimeout(() => {
|
||||
this.$store.commit("CLEAR_LOCK");
|
||||
this.$router.push({
|
||||
path: this.tag.path
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
components: {}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
20
src/page/login/authredirect.vue
Executable file
20
src/page/login/authredirect.vue
Executable file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'authredirect',
|
||||
created () {
|
||||
window.close()
|
||||
const params = this.$route.query
|
||||
const state = params.state
|
||||
const code = params.code
|
||||
window.opener.location.href = `${window.location.origin}/#/login?state=${state}&code=${code}`
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
</style>
|
||||
132
src/page/login/codelogin.vue
Executable file
132
src/page/login/codelogin.vue
Executable file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<el-form class="login-form"
|
||||
status-icon
|
||||
:rules="loginRules"
|
||||
ref="loginForm"
|
||||
:model="loginForm"
|
||||
label-width="0">
|
||||
<el-form-item prop="phone">
|
||||
<el-input @keyup.enter="handleLogin"
|
||||
v-model="loginForm.phone"
|
||||
auto-complete="off"
|
||||
:placeholder="$t('login.phone')">
|
||||
<template #prefix>
|
||||
<i class="icon-shouji"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<el-input @keyup.enter="handleLogin"
|
||||
v-model="loginForm.code"
|
||||
auto-complete="off"
|
||||
:placeholder="$t('login.code')">
|
||||
<template #prefix>
|
||||
<i class="icon-yanzhengma"></i>
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<span @click="handleSend"
|
||||
class="msg-text"
|
||||
:class="[{display:msgKey}]">{{msgText}}</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary"
|
||||
@click.prevent="handleLogin"
|
||||
class="login-submit">{{$t('login.submit')}}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isvalidatemobile } from "utils/validate";
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
name: "codelogin",
|
||||
data () {
|
||||
const validatePhone = (rule, value, callback) => {
|
||||
if (isvalidatemobile(value)[0]) {
|
||||
callback(new Error(isvalidatemobile(value)[1]));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validateCode = (rule, value, callback) => {
|
||||
if (value.length != 4) {
|
||||
callback(new Error("请输入4位数的验证码"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
msgText: "",
|
||||
msgTime: "",
|
||||
msgKey: false,
|
||||
loginForm: {
|
||||
phone: "",
|
||||
code: ""
|
||||
},
|
||||
loginRules: {
|
||||
phone: [{ required: true, trigger: "blur", validator: validatePhone }],
|
||||
code: [{ required: true, trigger: "blur", validator: validateCode }]
|
||||
}
|
||||
};
|
||||
},
|
||||
created () {
|
||||
this.msgText = this.config.MSGINIT;
|
||||
this.msgTime = this.config.MSGTIME;
|
||||
},
|
||||
mounted () { },
|
||||
computed: {
|
||||
...mapGetters(["tagWel"]),
|
||||
config () {
|
||||
return {
|
||||
MSGINIT: this.$t("login.msgText"),
|
||||
MSGSCUCCESS: this.$t("login.msgSuccess"),
|
||||
MSGTIME: 60
|
||||
};
|
||||
}
|
||||
},
|
||||
props: [],
|
||||
methods: {
|
||||
handleSend () {
|
||||
if (this.msgKey) return;
|
||||
this.msgText = this.msgTime + this.config.MSGSCUCCESS;
|
||||
this.msgKey = true;
|
||||
const time = setInterval(() => {
|
||||
this.msgTime--;
|
||||
this.msgText = this.msgTime + this.config.MSGSCUCCESS;
|
||||
if (this.msgTime == 0) {
|
||||
this.msgTime = this.config.MSGTIME;
|
||||
this.msgText = this.config.MSGINIT;
|
||||
this.msgKey = false;
|
||||
clearInterval(time);
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
handleLogin () {
|
||||
this.$refs.loginForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.$store.dispatch("LoginByPhone", this.loginForm).then(() => {
|
||||
this.$router.push(this.tagWel);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.msg-text {
|
||||
display: block;
|
||||
width: 60px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.msg-text.display {
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
42
src/page/login/facelogin.vue
Executable file
42
src/page/login/facelogin.vue
Executable file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<basic-video ref="video"
|
||||
:width="350">
|
||||
</basic-video>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import basicVideo from '@/components/basic-video/main.vue'
|
||||
export default {
|
||||
components: {
|
||||
basicVideo
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loginForm: {
|
||||
username: "admin",
|
||||
password: "123456",
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
created () {
|
||||
setTimeout(() => {
|
||||
this.handleLogin()
|
||||
}, 6000)
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["tagWel"])
|
||||
},
|
||||
methods: {
|
||||
handleLogin () {
|
||||
this.$store.dispatch("LoginByUsername", this.loginForm).then(() => {
|
||||
this.$router.push(this.tagWel);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
90
src/page/login/index.vue
Executable file
90
src/page/login/index.vue
Executable file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="login-container"
|
||||
@keyup.enter="handleLogin">
|
||||
<div class="login-time">
|
||||
{{time}}
|
||||
</div>
|
||||
<div class="login-weaper">
|
||||
<div class="login-left animate__animated animate__fadeInLeft">
|
||||
<img class="img"
|
||||
src="/img/logo.png"
|
||||
alt="">
|
||||
<p class="title">{{ $t('login.info') }}</p>
|
||||
</div>
|
||||
<div class="login-border animate__animated animate__fadeInRight">
|
||||
<div class="login-main">
|
||||
<p class="login-title">
|
||||
{{ $t('login.title') }}{{website.title}}
|
||||
<top-lang></top-lang>
|
||||
</p>
|
||||
<userLogin v-if="activeName==='user'"></userLogin>
|
||||
<codeLogin v-else-if="activeName==='code'"></codeLogin>
|
||||
<faceLogin v-else-if="activeName==='face'"></faceLogin>
|
||||
<div class="login-menu">
|
||||
<a href="#"
|
||||
@click.stop="activeName='user'">{{ $t('login.userLogin') }}</a>
|
||||
<a href="#"
|
||||
@click.stop="activeName='code'">{{ $t('login.phoneLogin') }}</a>
|
||||
<a href="#"
|
||||
@click.stop="activeName='face'">{{ $t('login.faceLogin') }}</a>
|
||||
</div>
|
||||
<thirdLogin></thirdLogin>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import userLogin from "./userlogin.vue";
|
||||
import codeLogin from "./codelogin.vue";
|
||||
import thirdLogin from "./thirdlogin.vue";
|
||||
import faceLogin from "./facelogin.vue";
|
||||
import { validatenull } from "@/utils/validate";
|
||||
import topLang from "@/page/index/top/top-lang.vue";
|
||||
export default {
|
||||
name: "login",
|
||||
components: {
|
||||
userLogin,
|
||||
codeLogin,
|
||||
thirdLogin,
|
||||
faceLogin,
|
||||
topLang
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
time: "",
|
||||
activeName: "user"
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route () {
|
||||
const params = this.$route.query;
|
||||
this.socialForm = params
|
||||
if (!validatenull(this.socialForm.state)) {
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: `${this.socialForm.state === "WX" ? "微信" : "QQ"
|
||||
}登录中,请稍后。。。`,
|
||||
spinner: "el-icon-loading"
|
||||
});
|
||||
setTimeout(() => {
|
||||
loading.close();
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getTime();
|
||||
setInterval(() => {
|
||||
this.getTime();
|
||||
}, 1000);
|
||||
},
|
||||
mounted () { },
|
||||
props: [],
|
||||
methods: {
|
||||
getTime () {
|
||||
this.time = this.$dayjs().format('YYYY年MM月DD日 HH:mm:ss')
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
69
src/page/login/thirdlogin.vue
Executable file
69
src/page/login/thirdlogin.vue
Executable file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="third">
|
||||
<div class="box">
|
||||
<i class="iconfont icon-QQ"
|
||||
@click="handleClick('qq')"
|
||||
style="color:#53a4d8"></i>
|
||||
<i class="iconfont icon-weixinicon2x"
|
||||
@click="handleClick('wx')"
|
||||
style="color:#71c252"></i>
|
||||
<i class="iconfont icon-weibo"
|
||||
@click="handleClick('wb')"
|
||||
style="color:#c73420"></i>
|
||||
<i class="iconfont icon-zhifubao"
|
||||
@click="handleClick('zfb')"
|
||||
style="color:#439fe2"></i>
|
||||
<i class="iconfont icon-huaban88"
|
||||
@click="handleClick('github')"
|
||||
style="color:#666"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { openWindow } from "utils/util";
|
||||
|
||||
export default {
|
||||
name: "thirdLogin",
|
||||
methods: {
|
||||
handleClick (thirdpart) {
|
||||
let appid, client_id, redirect_uri, url;
|
||||
redirect_uri = encodeURIComponent(
|
||||
window.location.origin + "/#/authredirect"
|
||||
);
|
||||
if (thirdpart === "wx") {
|
||||
appid = "xxxx";
|
||||
url =
|
||||
"https://open.weixin.qq.com/connect/qrconnect?appid=" +
|
||||
appid +
|
||||
"&redirect_uri=" +
|
||||
redirect_uri +
|
||||
"&state=WX&response_type=code&scope=snsapi_login#wechat_redirect";
|
||||
} else if (thirdpart === "qq") {
|
||||
client_id = "xxxx";
|
||||
url =
|
||||
"https://graph.qq.com/oauth2.0/authorize?response_type=code&state=QQ&client_id=" +
|
||||
client_id +
|
||||
"&redirect_uri=" +
|
||||
redirect_uri;
|
||||
}
|
||||
openWindow(url, thirdpart, 540, 540);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.third {
|
||||
padding: 10px 0;
|
||||
.box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
i {
|
||||
font-size: 36px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
137
src/page/login/userlogin.vue
Executable file
137
src/page/login/userlogin.vue
Executable file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<el-form class="login-form"
|
||||
status-icon
|
||||
:rules="loginRules"
|
||||
ref="loginForm"
|
||||
:model="loginForm"
|
||||
label-width="0">
|
||||
<el-form-item prop="username">
|
||||
<el-input @keyup.enter="handleLogin"
|
||||
v-model="loginForm.username"
|
||||
auto-complete="off"
|
||||
:placeholder="$t('login.username')">
|
||||
<template #prefix>
|
||||
<i class="icon-yonghu"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input @keyup.enter="handleLogin"
|
||||
type="password"
|
||||
show-password
|
||||
v-model="loginForm.password"
|
||||
auto-complete="off"
|
||||
:placeholder="$t('login.password')">
|
||||
<template #prefix>
|
||||
<i class="icon-mima"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<el-input @keyup.enter="handleLogin"
|
||||
:maxlength="code.len"
|
||||
v-model="loginForm.code"
|
||||
auto-complete="off"
|
||||
:placeholder="$t('login.code')">
|
||||
<template #prefix>
|
||||
<i class="icon-yanzhengma"></i>
|
||||
</template>
|
||||
<template #append>
|
||||
<div class="login-code">
|
||||
<span class="login-code-img"
|
||||
@click="refreshCode"
|
||||
v-if="code.type == 'text'">{{code.value}}</span>
|
||||
<img :src="code.src"
|
||||
class="login-code-img"
|
||||
@click="refreshCode"
|
||||
v-else />
|
||||
<!-- <i class="icon-shuaxin login-code-icon" @click="refreshCode"></i> -->
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary"
|
||||
@click.prevent="handleLogin"
|
||||
class="login-submit">{{$t('login.submit')}}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { randomLenNum } from "utils/util";
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
name: "userlogin",
|
||||
data () {
|
||||
const validateCode = (rule, value, callback) => {
|
||||
if (this.code.value != value) {
|
||||
this.loginForm.code = "";
|
||||
this.refreshCode();
|
||||
callback(new Error("请输入正确的验证码"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
loginForm: {
|
||||
username: "admin",
|
||||
password: "123456",
|
||||
code: "",
|
||||
redomStr: ""
|
||||
},
|
||||
checked: false,
|
||||
code: {
|
||||
src: "",
|
||||
value: "",
|
||||
len: 4,
|
||||
type: "text"
|
||||
},
|
||||
loginRules: {
|
||||
username: [
|
||||
{ required: true, message: "请输入用户名", trigger: "blur" }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||
{ min: 6, message: "密码长度最少为6位", trigger: "blur" }
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: "请输入验证码", trigger: "blur" },
|
||||
{ min: 4, max: 4, message: "验证码长度为4位", trigger: "blur" },
|
||||
{ required: true, trigger: "blur", validator: validateCode }
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
created () {
|
||||
this.refreshCode();
|
||||
},
|
||||
mounted () { },
|
||||
computed: {
|
||||
...mapGetters(["tagWel"])
|
||||
},
|
||||
props: [],
|
||||
methods: {
|
||||
refreshCode () {
|
||||
this.loginForm.redomStr = randomLenNum(this.code.len, true);
|
||||
this.code.type == "text"
|
||||
? (this.code.value = randomLenNum(this.code.len))
|
||||
: (this.code.src = `/${this.loginForm.redomStr}`);
|
||||
this.loginForm.code = this.code.value;
|
||||
},
|
||||
handleLogin () {
|
||||
this.$refs.loginForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.$store.dispatch("LoginByUsername", this.loginForm).then(() => {
|
||||
this.$router.push(this.tagWel);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
Reference in New Issue
Block a user