初始化

This commit is contained in:
2023-04-27 16:44:20 +08:00
parent 481c3f5682
commit 4c646c1482
167 changed files with 36192 additions and 15 deletions
@@ -0,0 +1,50 @@
<template>
<div class="d2-contentmenu-list" @click="rowClick">
<div v-for="item in menulist" :key="item.value" :data-value="item.value" class="d2-contentmenu-item" flex="cross:center main:center">
<d2-icon v-if="item.icon" :name="item.icon"/>
<div class="d2-contentmenu-item-title" flex-box="1">
{{item.title}}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'd2-contextmenu-list',
props: {
menulist: {
type: Array,
default: () => []
}
},
methods: {
rowClick (event) {
let target = event.target
while (!target.dataset.value) {
target = target.parentNode
}
this.$emit('rowClick', target.dataset.value)
}
}
}
</script>
<style lang="scss">
.d2-contentmenu-list {
.d2-contentmenu-item {
padding: 8px 20px 8px 15px;
margin: 0;
font-size: 14px;
color: #606266;
cursor: pointer;
&:hover {
background: #ecf5ff;
color: #66b1ff;
}
.d2-contentmenu-item-title {
margin-left: 10px;
}
}
}
</style>
@@ -0,0 +1,68 @@
<template>
<div class="d2-contextmenu" v-show="flag" :style="style">
<slot/>
</div>
</template>
<script>
export default {
name: 'd2-contextmenu',
props: {
visible: {
type: Boolean,
default: false
},
x: {
type: Number,
default: 0
},
y: {
type: Number,
default: 0
}
},
computed: {
flag: {
get () {
if (this.visible) {
// 注册全局监听事件 [ 目前只考虑鼠标解除触发 ]
window.addEventListener('mousedown', this.watchContextmenu)
}
return this.visible
},
set (newVal) {
this.$emit('update:visible', newVal)
}
},
style () {
return {
left: this.x + 'px',
top: this.y + 'px',
display: this.visible ? 'block' : 'none '
}
}
},
methods: {
watchContextmenu (event) {
if (!this.$el.contains(event.target) || event.button !== 0) this.flag = false
window.removeEventListener('mousedown', this.watchContextmenu)
}
},
mounted () {
// 将菜单放置到body下
document.querySelector('body').appendChild(this.$el)
}
}
</script>
<style>
.d2-contextmenu {
position: absolute;
padding: 5px 0;
z-index: 2018;
background: #FFF;
border: 1px solid #cfd7e5;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
}
</style>
@@ -0,0 +1,24 @@
<template>
<el-tooltip effect="dark" :content="active ? '退出全屏' : '全屏'" placement="bottom">
<el-button class="d2-mr btn-text can-hover" type="text" @click="toggle">
<d2-icon v-if="active" name="compress"/>
<d2-icon v-else name="arrows-alt" style="font-size: 16px"/>
</el-button>
</el-tooltip>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
computed: {
...mapState('d2admin/fullscreen', [
'active'
])
},
methods: {
...mapActions('d2admin/fullscreen', [
'toggle'
])
}
}
</script>
@@ -0,0 +1,25 @@
<template>
<el-dropdown placement="bottom" size="small" @command="onChangeLocale">
<el-button class="d2-mr btn-text can-hover" type="text">
<d2-icon name="language" style="font-size: 16px;"/>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-for="language in $languages"
:key="language.value"
:command="language.value">
<d2-icon :name="$i18n.locale === language.value ? 'dot-circle-o' : 'circle-o'" class="d2-mr-5"/>
{{ language.label }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
import localeMixin from '@/locales/mixin.js'
export default {
mixins: [
localeMixin
]
}
</script>
@@ -0,0 +1,39 @@
<template>
<el-tooltip effect="dark" :content="tooltipContent" placement="bottom">
<el-button class="d2-ml-0 d2-mr btn-text can-hover" type="text" @click="handleClick">
<el-badge v-if="logLength > 0" :max="99" :value="logLengthError" :is-dot="logLengthError === 0">
<d2-icon :name="logLengthError === 0 ? 'dot-circle-o' : 'bug'" style="font-size: 20px"/>
</el-badge>
<d2-icon v-else name="dot-circle-o" style="font-size: 20px"/>
</el-button>
</el-tooltip>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
computed: {
...mapGetters('d2admin', {
logLength: 'log/length',
logLengthError: 'log/lengthError'
}),
tooltipContent () {
return this.logLength === 0
? '没有日志或异常'
: `${this.logLength} 条日志${this.logLengthError > 0
? ` | 包含 ${this.logLengthError} 个异常`
: ''}`
}
},
methods: {
...mapMutations('d2admin/log', [
'clean'
]),
handleClick () {
this.$router.push({
name: 'log'
})
}
}
}
</script>
@@ -0,0 +1,15 @@
<template>
<el-button class="d2-mr btn-text can-hover" type="text" @click="handleClick">
<d2-icon name="search" style="font-size: 18px;"/>
</el-button>
</template>
<script>
export default {
methods: {
handleClick () {
this.$emit('click')
}
}
}
</script>
@@ -0,0 +1,54 @@
<template>
<el-dropdown placement="bottom" size="small" @command="handleChange">
<el-button class="d2-mr btn-text can-hover" type="text">
<d2-icon name="font" style="font-size: 16px;"/>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item in options" :key="item.value" :command="item.value">
<d2-icon :name="iconName(item.value)" class="d2-mr-5"/>{{item.label}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
import { mapState, mapMutations, mapActions } from 'vuex'
export default {
name: 'd2-header-size',
data () {
return {
options: [
{ label: '默认', value: 'default' },
{ label: '中', value: 'medium' },
{ label: '小', value: 'small' },
{ label: '最小', value: 'mini' }
]
}
},
computed: {
...mapState('d2admin/size', [
'value'
])
},
methods: {
...mapMutations({
pageKeepAliveClean: 'd2admin/page/keepAliveClean'
}),
...mapActions({
sizeSet: 'd2admin/size/set'
}),
handleChange (value) {
this.sizeSet(value)
this.$notify({
title: '提示',
dangerouslyUseHTMLString: true,
message: '已更新页面内 <b>组件</b> 的 <b>默认尺寸</b><br/>例如按钮大小,<b>非字号</b>',
type: 'success'
})
},
iconName (name) {
return name === this.value ? 'dot-circle-o' : 'circle-o'
}
}
}
</script>
@@ -0,0 +1,53 @@
<template>
<el-table :data="list" v-bind="table">
<el-table-column prop="title" align="center" width="160"/>
<el-table-column label="预览" width="120">
<div slot-scope="scope" class="theme-preview" :style="{ backgroundImage: `url(${$baseUrl}${scope.row.preview})` }"/>
</el-table-column>
<el-table-column prop="address" align="center">
<template slot-scope="scope">
<el-button v-if="activeName === scope.row.name" type="success" icon="el-icon-check" round>已激活</el-button>
<el-button v-else round @click="handleSelectTheme(scope.row.name)">使用</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
name: 'd2-theme-list',
data () {
return {
table: {
showHeader: false,
border: true
}
}
},
computed: {
...mapState('d2admin/theme', [
'list',
'activeName'
])
},
methods: {
...mapActions('d2admin/theme', [
'set'
]),
handleSelectTheme (name) {
this.set(name)
}
}
}
</script>
<style lang="scss" scoped>
.theme-preview {
height: 50px;
width: 100px;
border-radius: 4px;
background-size: cover;
border: 1px solid $color-border-1;
}
</style>
@@ -0,0 +1,38 @@
<template>
<div>
<el-tooltip
effect="dark"
content="主题"
placement="bottom">
<el-button
class="d2-ml-0 d2-mr btn-text can-hover"
type="text"
@click="dialogVisible = true">
<d2-icon
name="diamond"
style="font-size: 16px"/>
</el-button>
</el-tooltip>
<el-dialog
title="主题"
width="600px"
:visible.sync="dialogVisible"
:append-to-body="true">
<d2-theme-list style="margin-top: -25px;"/>
</el-dialog>
</div>
</template>
<script>
import themeList from './components/d2-theme-list'
export default {
components: {
'd2-theme-list': themeList
},
data () {
return {
dialogVisible: false
}
}
}
</script>
@@ -0,0 +1,46 @@
/**
* @description 创建菜单
* @param {Function} h createElement
* @param {Object} menu 菜单项
*/
export function elMenuItem (h, menu) {
let icon = null
if (menu.icon) icon = <i class={ `fa fa-${menu.icon}` }/>
else if (menu.iconSvg) icon = <d2-icon-svg name={ menu.iconSvg }/>
else icon = <i class="fa fa-file-o"/>
return <el-menu-item
key={ menu.path }
index={ menu.path }>
{ icon }
<span slot="title">{ menu.title || '未命名菜单' }</span>
</el-menu-item>
}
/**
* @description 创建子菜单
* @param {Function} h createElement
* @param {Object} menu 菜单项
*/
export function elSubmenu (h, menu) {
let icon = null
if (menu.icon) icon = <i slot="title" class={ `fa fa-${menu.icon}` }/>
else if (menu.iconSvg) icon = <d2-icon-svg slot="title" name={ menu.iconSvg }/>
else icon = <i slot="title" class="fa fa-folder-o"/>
return <el-submenu
key={ menu.path }
index={ menu.path }>
{ icon }
<span slot="title">{ menu.title || '未命名菜单' }</span>
{ menu.children.map(child => createMenu.call(this, h, child)) }
</el-submenu>
}
/**
* @description 在组件中调用此方法渲染菜单项目
* @param {Function} h createElement
* @param {Object} menu 菜单项
*/
export function createMenu (h, menu) {
if (menu.children === undefined) return elMenuItem.call(this, h, menu)
return elSubmenu.call(this, h, menu)
}
@@ -0,0 +1,140 @@
import { throttle } from 'lodash'
import { mapState } from 'vuex'
import menuMixin from '../mixin/menu'
import { createMenu } from '../libs/util.menu'
export default {
name: 'd2-layout-header-aside-menu-header',
mixins: [
menuMixin
],
render (h) {
return <div
flex="cross:center"
class={ { 'd2-theme-header-menu': true, 'is-scrollable': this.isScroll } }
ref="page">
<div
ref="content"
class="d2-theme-header-menu__content"
flex-box="1"
flex>
<div
class="d2-theme-header-menu__scroll"
flex-box="0"
style={ { transform: `translateX(${this.currentTranslateX}px)` } }
ref="scroll">
<el-menu
mode="horizontal"
defaultActive={ this.active }
onSelect={ this.handleMenuSelect }>
{ this.header.map(menu => createMenu.call(this, h, menu)) }
</el-menu>
</div>
</div>
{
this.isScroll
? [
<div
class="d2-theme-header-menu__prev"
flex="main:center cross:center"
flex-box="0"
onClick={ () => this.scroll('left') }>
<i class="el-icon-arrow-left"></i>
</div>,
<div
class="d2-theme-header-menu__next"
flex="main:center cross:center"
flex-box="0"
onClick={ () => this.scroll('right') }>
<i class="el-icon-arrow-right"></i>
</div>
]
: []
}
</div>
},
computed: {
...mapState('d2admin/menu', [
'header'
])
},
data () {
return {
active: '',
isScroll: false,
scrollWidth: 0,
contentWidth: 0,
currentTranslateX: 0,
throttledCheckScroll: null
}
},
watch: {
'$route.matched': {
handler (val) {
this.active = val[val.length - 1].path
},
immediate: true
}
},
methods: {
scroll (direction) {
if (direction === 'left') {
// 向右滚动
this.currentTranslateX = 0
} else {
// 向左滚动
if (this.contentWidth * 2 - this.currentTranslateX <= this.scrollWidth) {
this.currentTranslateX -= this.contentWidth
} else {
this.currentTranslateX = this.contentWidth - this.scrollWidth
}
}
},
checkScroll () {
let contentWidth = this.$refs.content.clientWidth
let scrollWidth = this.$refs.scroll.clientWidth
if (this.isScroll) {
// 页面依旧允许滚动的情况,需要更新width
if (this.contentWidth - this.scrollWidth === this.currentTranslateX) {
// currentTranslateX 也需要相应变化【在右端到头的情况时】
this.currentTranslateX = contentWidth - scrollWidth
// 快速的滑动依旧存在判断和计算时对应的contentWidth变成正数,所以需要限制一下
if (this.currentTranslateX > 0) {
this.currentTranslateX = 0
}
}
// 更新元素数据
this.contentWidth = contentWidth
this.scrollWidth = scrollWidth
// 判断何时滚动消失: 当scroll > content
if (contentWidth > scrollWidth) {
this.isScroll = false
}
}
// 判断何时滚动出现: 当scroll < content
if (!this.isScroll && contentWidth < scrollWidth) {
this.isScroll = true
// 注意,当isScroll变为true,对应的元素盒子大小会发生变化
this.$nextTick(() => {
contentWidth = this.$refs.content.clientWidth
scrollWidth = this.$refs.scroll.clientWidth
this.contentWidth = contentWidth
this.scrollWidth = scrollWidth
this.currentTranslateX = 0
})
}
}
},
mounted () {
// 初始化判断
// 默认判断父元素和子元素的大小,以确定初始情况是否显示滚动
this.checkScroll()
// 全局窗口变化监听,判断父元素和子元素的大小,从而控制isScroll的开关
this.throttledCheckScroll = throttle(this.checkScroll, 300)
window.addEventListener('resize', this.throttledCheckScroll)
},
beforeDestroy () {
// 取消监听
window.removeEventListener('resize', this.throttledCheckScroll)
}
}
@@ -0,0 +1,17 @@
import util from '@/libs/util.js'
export default {
methods: {
handleMenuSelect (index, indexPath) {
if (/^d2-menu-empty-\d+$/.test(index) || index === undefined) {
this.$message.warning('临时菜单')
} else if (/^https:\/\/|http:\/\//.test(index)) {
util.open(index)
} else {
this.$router.push({
path: index
})
}
}
}
}
@@ -0,0 +1,104 @@
<template>
<div class="d2-panel-search-item" :class="hoverMode ? 'can-hover' : ''" flex>
<div class="d2-panel-search-item__icon" flex-box="0">
<div class="d2-panel-search-item__icon-box" flex="main:center cross:center">
<d2-icon v-if="item.icon" :name="item.icon"/>
<d2-icon-svg v-else-if="item.iconSvg" :name="item.iconSvg"/>
<d2-icon v-else name="file-o"/>
</div>
</div>
<div class="d2-panel-search-item__info" flex-box="1" flex="dir:top">
<div class="d2-panel-search-item__info-title" flex-box="1" flex="cross:center">
<span>{{item.title}}</span>
</div>
<div class="d2-panel-search-item__info-fullTitle" flex-box="0">
<span>{{item.fullTitle}}</span>
</div>
<div class="d2-panel-search-item__info-path" flex-box="0">
<span>{{item.path}}</span>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
item: {
default: () => ({})
},
hoverMode: {
default: false
}
}
}
</script>
<style lang="scss" scoped>
.d2-panel-search-item {
height: 64px;
margin: 0px -20px;
&.can-hover {
@extend %unable-select;
margin: 0px;
&:hover {
background-color: #F5F7FA;
.d2-panel-search-item__icon {
.d2-panel-search-item__icon-box {
i {
font-size: 24px;
color: $color-primary;
}
}
}
.d2-panel-search-item__info {
.d2-panel-search-item__info-title {
color: $color-text-main;
}
.d2-panel-search-item__info-fullTitle {
color: $color-text-normal;
}
.d2-panel-search-item__info-path {
color: $color-text-normal;
}
}
}
}
.d2-panel-search-item__icon {
width: 64px;
.d2-panel-search-item__icon-box {
height: 64px;
width: 64px;
border-right: 1px solid $color-border-3;
i {
font-size: 20px;
color: $color-text-sub;
}
svg {
height: 20px;
width: 20px;
}
}
}
.d2-panel-search-item__info {
margin-left: 10px;
.d2-panel-search-item__info-title {
font-size: 16px;
line-height: 16px;
font-weight: bold;
color: $color-text-normal;
}
.d2-panel-search-item__info-fullTitle {
font-size: 10px;
line-height: 14px;
color: $color-text-placehoder;
}
.d2-panel-search-item__info-path {
margin-bottom: 4px;
font-size: 10px;
line-height: 14px;
color: $color-text-placehoder;
}
}
}
</style>
@@ -0,0 +1,199 @@
<template>
<div class="panel-search" flex="dir:top">
<div class="panel-search__input-group" flex-box="0" flex="dir:top main:center cross:center" @click.self="handlePanelClick">
<d2-icon-svg class="panel-search__logo" name="d2-admin-text"/>
<el-autocomplete
class="panel-search__input"
ref="input"
v-model="searchText"
suffix-icon="el-icon-search"
placeholder="搜索页面"
:fetch-suggestions="querySearch"
:trigger-on-focus="false"
:clearable="true"
@keydown.esc.native="handleEsc"
@select="handleSelect">
<d2-panel-search-item slot-scope="{ item }" :item="item"/>
</el-autocomplete>
<div class="panel-search__tip">
您可以使用快捷键
<span class="panel-search__key">{{hotkey.open}}</span>
唤醒搜索面板,按
<span class="panel-search__key">{{hotkey.close}}</span>
关闭
</div>
</div>
<div v-if="resultsList.length > 0" class="panel-search__results-group" flex-box="1">
<el-card shadow="never">
<div class="panel-search__results-group-inner">
<d2-panel-search-item
v-for="(item, index) in resultsList"
:key="index"
:item="item"
:hover-mode="true"
@click.native="handleResultsGroupItemClick(item.path)"/>
</div>
</el-card>
</div>
</div>
</template>
<script>
import Fuse from 'fuse.js'
import { mapState } from 'vuex'
import mixin from '../mixin/menu'
export default {
mixins: [
mixin
],
components: {
'd2-panel-search-item': () => import('./components/panel-search-item/index.vue')
},
data () {
return {
searchText: '',
results: []
}
},
computed: {
...mapState('d2admin/search', [
'hotkey',
'pool'
]),
// 这份数据是展示在搜索面板下面的
resultsList () {
return (this.results.length === 0 && this.searchText === '') ? this.pool.map(e => ({
value: e.fullTitle,
...e
})) : this.results
},
// 根据 pool 更新 fuse 实例
fuse () {
return new Fuse(this.pool, {
shouldSort: true,
tokenize: true,
threshold: 0.6,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [
'fullTitle',
'path'
]
})
}
},
methods: {
/**
* @description 过滤选项 这个方法在每次输入框的值发生变化时会触发
*/
querySearch (queryString, callback) {
const results = this.fuse.search(queryString).map(e => e.item)
this.results = results
callback(results)
},
/**
* @description 聚焦输入框
*/
focus () {
this.input = ''
setTimeout(() => {
if (this.$refs.input) {
this.$refs.input.focus()
}
// 还原
this.searchText = ''
this.results = []
}, 500)
},
/**
* @description 接收用户在列表中选择项目的事件
*/
handleResultsGroupItemClick (path) {
// 如果用户选择的就是当前页面 就直接关闭搜索面板
if (path === this.$route.path) {
this.handleEsc()
return
}
// 用户选择的是其它页面
this.handleMenuSelect(path)
},
/**
* @description 接收用户在下拉菜单中选中事件
*/
async handleSelect ({ path }) {
// 如果用户选择的就是当前页面 就直接关闭搜索面板
if (path === this.$route.path) {
this.handleEsc()
return
}
// 用户选择的是其它页面
await this.$nextTick()
this.handleMenuSelect(path)
},
/**
* @augments 关闭输入框的下拉菜单
*/
closeSuggestion () {
if (this.$refs.input.activated) {
this.$refs.input.suggestions = []
this.$refs.input.activated = false
}
},
/**
* @augments 接收用户点击空白区域的关闭
*/
handlePanelClick () {
this.handleEsc()
},
/**
* @augments 接收用户触发的关闭
*/
async handleEsc () {
this.closeSuggestion()
await this.$nextTick()
this.$emit('close')
}
}
}
</script>
<style lang="scss" scoped>
.panel-search {
margin: 20px;
width: 100%;
.panel-search__input-group {
height: 240px;
.panel-search__logo {
width: 80px;
height: 80px;
margin-bottom: 20px;
}
.panel-search__input {
width: 500px;
}
.panel-search__tip {
@extend %unable-select;
margin-top: 20px;
margin-bottom: 40px;
font-size: 12px;
color: $color-text-sub;
.panel-search__key {
padding: 1px 5px;
margin: 0px 2px;
border-radius: 2px;
background-color: $color-text-normal;
color: $color-bg;
}
}
}
.panel-search__results-group {
overflow: auto;
margin-bottom: -20px;
.panel-search__results-group-inner {
margin: -20px;
}
}
}
</style>
@@ -0,0 +1,185 @@
<template>
<div class="d2-multiple-page-control-group" flex>
<div class="d2-multiple-page-control-content" flex-box="1">
<div class="d2-multiple-page-control-content-inner">
<d2-contextmenu
:visible.sync="contextmenuFlag"
:x="contentmenuX"
:y="contentmenuY">
<d2-contextmenu-list
:menulist="tagName === '/index' ? contextmenuListIndex : contextmenuList"
@rowClick="contextmenuClick"/>
</d2-contextmenu>
<el-tabs
class="d2-multiple-page-control d2-multiple-page-sort"
:value="current"
type="card"
@tab-click="handleClick"
@tab-remove="handleTabRemove"
@contextmenu.native="handleContextmenu">
<el-tab-pane
v-for="page in opened"
:key="page.fullPath"
:label="page.meta.title || '未命名'"
:name="page.fullPath"
:closable="isTabClosable(page)"/>
</el-tabs>
</div>
</div>
<div class="d2-multiple-page-control-btn" flex-box="0">
<el-dropdown
size="default"
split-button
@click="closeAll"
@command="command => handleControlItemClick(command)">
<d2-icon name="times-circle"/>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="left">
<d2-icon name="arrow-left" class="d2-mr-10"/>
关闭左侧
</el-dropdown-item>
<el-dropdown-item command="right">
<d2-icon name="arrow-right" class="d2-mr-10"/>
关闭右侧
</el-dropdown-item>
<el-dropdown-item command="other">
<d2-icon name="times" class="d2-mr-10"/>
关闭其它
</el-dropdown-item>
<el-dropdown-item command="all">
<d2-icon name="times-circle" class="d2-mr-10"/>
全部关闭
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
import Sortable from 'sortablejs'
export default {
components: {
D2Contextmenu: () => import('../contextmenu'),
D2ContextmenuList: () => import('../contextmenu/components/contentmenuList')
},
data () {
return {
contextmenuFlag: false,
contentmenuX: 0,
contentmenuY: 0,
contextmenuListIndex: [
{ icon: 'times-circle', title: '关闭全部', value: 'all' }
],
contextmenuList: [
{ icon: 'refresh', title: '刷新', value: 'refresh' },
{ icon: 'arrow-left', title: '关闭左侧', value: 'left' },
{ icon: 'arrow-right', title: '关闭右侧', value: 'right' },
{ icon: 'times', title: '关闭其它', value: 'other' },
{ icon: 'times-circle', title: '关闭全部', value: 'all' }
],
tagName: '/index'
}
},
computed: {
...mapState('d2admin/page', [
'opened',
'current'
])
},
methods: {
...mapActions('d2admin/page', [
'close',
'closeLeft',
'closeRight',
'closeOther',
'closeAll',
'openedSort'
]),
/**
* @description 计算某个标签页是否可关闭
* @param {Object} page 其中一个标签页
*/
isTabClosable (page) {
return page.name !== 'index'
},
/**
* @description 右键菜单功能点击
* @param {Object} event 事件
*/
handleContextmenu (event) {
let target = event.target
// fix https://github.com/d2-projects/d2-admin/issues/54
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
}
},
/**
* @description 右键菜单的 row-click 事件
* @param {String} command 事件类型
*/
contextmenuClick (command) {
this.handleControlItemClick(command, this.tagName)
},
/**
* @description 接收点击关闭控制上选项的事件
* @param {String} command 事件类型
* @param {String} tagName tab 名称
*/
handleControlItemClick (command, tagName = null) {
if (tagName) this.contextmenuFlag = false
const params = { pageSelect: tagName }
switch (command) {
case 'refresh': this.$router.push({ name: 'refresh' }); break
case 'left': this.closeLeft(params); break
case 'right': this.closeRight(params); break
case 'other': this.closeOther(params); break
case 'all': this.closeAll(); break
default: this.$message.error('无效的操作'); break
}
},
/**
* @description 接收点击 tab 标签的事件
* @param {object} tab 标签
* @param {object} event 事件
*/
handleClick (tab, event) {
// 找到点击的页面在 tag 列表里是哪个
const page = this.opened.find(page => page.fullPath === tab.name)
if (page) {
const { name, params, query } = page
this.$router.push({ name, params, query })
}
},
/**
* @description 点击 tab 上的删除按钮触发这里
* @param {String} tagName tab 名称
*/
handleTabRemove (tagName) {
this.close({ tagName })
}
},
mounted () {
const el = document.querySelectorAll('.d2-multiple-page-sort .el-tabs__nav')[0]
Sortable.create(el, {
onEnd: (evt) => {
const { oldIndex, newIndex } = evt
this.openedSort({ oldIndex, newIndex })
}
})
}
}
</script>
+3
View File
@@ -0,0 +1,3 @@
import layout from './layout'
export default layout
+155
View File
@@ -0,0 +1,155 @@
<template>
<div class="d2-layout-header-aside-group" :style="styleLayoutMainGroup" :class="{grayMode: grayActive}">
<!-- 半透明遮罩 -->
<div class="d2-layout-header-aside-mask"></div>
<!-- 主体内容 -->
<div class="d2-layout-header-aside-content" flex="dir:top">
<!-- 顶栏 -->
<div class="d2-theme-header" :style="{ opacity: this.searchActive ? 0.5 : 1 }" flex-box="0" flex>
<router-link
to="/index"
:class="{'logo-group': true, 'logo-transition': asideTransition}"
:style="{width: '65px'}"
flex-box="0">
<img :src="`${$baseUrl}image/theme/${themeActiveSetting.name}/logo/icon-only.png`">
</router-link>
<d2-menu-header flex-box="1"/>
<!-- 顶栏右侧 -->
<div v-if="actionBtnVisible" class="d2-header-right" flex-box="0">
<d2-header-search @click="handleSearchClick"/>
<!-- 如果你只想在开发环境显示这个按钮请添加 v-if="$env === 'development'" -->
<d2-header-log v-if="$env === 'development'"/>
<d2-header-fullscreen/>
<d2-header-theme/>
<d2-header-size/>
</div>
</div>
<!-- 下面 主体 -->
<div class="d2-theme-container" flex-box="1" flex>
<!-- 主体 侧边栏 -->
<div flex-box="0" :style="{width: '20px'}"></div>
<!-- 主体 -->
<div class="d2-theme-container-main" flex-box="1" flex>
<!-- 搜索 -->
<transition name="fade-scale">
<div v-if="searchActive" class="d2-theme-container-main-layer" flex>
<d2-panel-search ref="panelSearch" @close="searchPanelClose"/>
</div>
</transition>
<!-- 内容 -->
<transition name="fade-scale">
<div v-if="!searchActive" class="d2-theme-container-main-layer" flex="dir:top">
<!-- tab -->
<!-- <div class="d2-theme-container-main-header" flex-box="0">-->
<!-- <d2-tabs/>-->
<!-- </div>-->
<!-- 页面 -->
<div class="d2-theme-container-main-body" flex-box="1">
<transition :name="transitionActive ? 'fade-transverse' : ''">
<keep-alive :include="keepAlive">
<router-view :key="routerViewKey" />
</keep-alive>
</transition>
</div>
</div>
</transition>
</div>
</div>
</div>
</div>
</template>
<script>
import d2MenuHeader from './components/menu-header'
// import d2Tabs from './components/tabs'
import d2HeaderFullscreen from './components/header-fullscreen'
import d2HeaderSearch from './components/header-search'
import d2HeaderSize from './components/header-size'
import d2HeaderTheme from './components/header-theme'
import d2HeaderLog from './components/header-log'
import { mapState, mapGetters } from 'vuex'
import mixinSearch from './mixins/search'
export default {
name: 'd2-layout-header-aside',
mixins: [
mixinSearch
],
components: {
d2MenuHeader,
// d2Tabs,
d2HeaderFullscreen,
d2HeaderSearch,
d2HeaderSize,
d2HeaderTheme,
d2HeaderLog
},
data () {
return {
// [侧边栏宽度] 正常状态
asideWidth: '200px',
// [侧边栏宽度] 折叠状态
asideWidthCollapse: '65px',
// 窗口宽度
windowWidth: 375,
// 功能按钮显现
actionBtnVisible: true
}
},
mounted () {
// 窗口大小改变事件
window.onresize = () => {
return (() => {
this.windowWidth = window.innerWidth
this.actionBtnVisible = this.windowWidth >= 404
})()
}
},
watch: {
// 实时监听screenWidth的变化
// screenWidth: {
// handler: function (val, oldVal) {
// if (val < 1550) {
// console.log('屏幕宽度小于1550px')
// } else {
// console.log('屏幕宽度大于1550px')
// }
// },
// immediate: true
// }
},
computed: {
...mapState('d2admin', {
keepAlive: state => state.page.keepAlive,
grayActive: state => state.gray.active,
transitionActive: state => state.transition.active,
asideCollapse: state => state.menu.asideCollapse,
asideTransition: state => state.menu.asideTransition
}),
...mapGetters('d2admin', {
themeActiveSetting: 'theme/activeSetting'
}),
/**
* @description 用来实现带参路由的缓存
*/
routerViewKey () {
// 默认情况下 key 类似 __transition-n-/foo
// 这里的字符串操作是为了最终 key 的格式和原来相同 类似 __transition-n-__stamp-time-/foo
const stamp = this.$route.meta[`__stamp-${this.$route.path}`] || ''
return `${stamp ? `__stamp-${stamp}-` : ''}${this.$route.path}`
},
/**
* @description 最外层容器的背景图片样式
*/
styleLayoutMainGroup () {
return this.themeActiveSetting.backgroundImage
? { backgroundImage: `url('${this.$baseUrl}${this.themeActiveSetting.backgroundImage}')` }
: {}
}
}
}
</script>
<style lang="scss">
// 注册主题
@import '~@/assets/style/theme/register.scss';
</style>
+66
View File
@@ -0,0 +1,66 @@
import { mapState, mapMutations } from 'vuex'
import hotkeys from 'hotkeys-js'
export default {
components: {
'd2-panel-search': () => import('../components/panel-search')
},
mounted () {
// 绑定搜索功能快捷键 [ 打开 ]
hotkeys(this.searchHotkey.open, event => {
event.preventDefault()
this.searchPanelOpen()
})
// 绑定搜索功能快捷键 [ 关闭 ]
hotkeys(this.searchHotkey.close, event => {
event.preventDefault()
this.searchPanelClose()
})
},
beforeDestroy () {
hotkeys.unbind(this.searchHotkey.open)
hotkeys.unbind(this.searchHotkey.close)
},
computed: {
...mapState('d2admin', {
searchActive: state => state.search.active,
searchHotkey: state => state.search.hotkey
})
},
methods: {
...mapMutations({
searchToggle: 'd2admin/search/toggle',
searchSet: 'd2admin/search/set'
}),
/**
* 接收点击搜索按钮
*/
handleSearchClick () {
this.searchToggle()
if (this.searchActive) {
setTimeout(() => {
if (this.$refs.panelSearch) {
this.$refs.panelSearch.focus()
}
}, 500)
}
},
searchPanelOpen () {
if (!this.searchActive) {
this.searchSet(true)
setTimeout(() => {
if (this.$refs.panelSearch) {
this.$refs.panelSearch.focus()
}
}, 500)
}
},
// 关闭搜索面板
searchPanelClose () {
if (this.searchActive) {
this.searchSet(false)
}
}
}
}