3
0

first commit

This commit is contained in:
2025-12-11 23:46:42 +08:00
commit 95ff3462fa
144 changed files with 17128 additions and 0 deletions

102
src/page/index/index.vue Executable file
View 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
View 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
View 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
View 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
View 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>

View 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>

View 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
View 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
View 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>

View 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>

View 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
View 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
View 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
View 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
View 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
View 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>

View 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
View 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
View 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>
&nbsp; &nbsp;
<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
View 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
View 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
View 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
View 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
View 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
View 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>