Merge pull request #425 from jamebal/dev

perf: 优化文件右键菜单
This commit is contained in:
jmal 2025-06-20 15:55:15 +08:00 committed by GitHub
commit 5eea0e51a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 825 additions and 102 deletions

View File

@ -52,7 +52,6 @@
"spark-md5": "^3.0.0",
"stompjs": "^2.3.3",
"tippy.js": "^6.3.7",
"v-contextmenu": "^2.9.0",
"v-viewer": "^1.5.1",
"vditor": "3.11.0",
"vue-aplayer": "^1.6.1",

View File

@ -10,7 +10,7 @@
<!-- 一级菜单 -->
<v-contextmenu-item
v-if="!item.child"
:divider="item.divider ? true : false"
:divider="!!item.divider"
@click="contextmenuClick(item.operation)"
>
<svg-icon
@ -28,7 +28,7 @@
<div v-for="itemSecond of item.child" :key="itemSecond.operation">
<v-contextmenu-item
v-if="!itemSecond.child"
:divider="itemSecond.divider ? true : false"
:divider="!!itemSecond.divider"
@click="contextmenuClick(itemSecond.operation)"
>
<svg-icon
@ -367,45 +367,7 @@
</div>
<!--右键菜单-->
<e-vue-contextmenu
ref="contextShow"
class="newFileMenu"
:class="menuTriangle"
@ctx-show="show"
@ctx-hide="hide"
>
<div class="popper-arrow"></div>
<ul v-for="(item, index) in menus" :key="item.label">
<li v-if="item.operation === 'unFavorite' || item.operation === 'favorite'"
class="menu-option"
@click="menusOperations(item.operation, $event)"
@mouseover.prevent.stop="
menuFavoriteOver(index, rowContextData.isFavorite)
"
@mouseleave.prevent.stop="
menuFavoriteLeave(index, rowContextData.isFavorite)
"
>
<div class="menuitem home-contextmenu">
<svg-icon :icon-class="item.iconClass" />
<div class="home-contextmenu-title">
<span class="menuitem text">{{ item.label }}</span>
</div>
</div>
</li>
<li v-else @click="menusOperations(item.operation, $event)">
<div class="menuitem home-contextmenu">
<svg-icon :icon-class="item.iconClass" />
<div class="home-contextmenu-title">
<span class="menuitem text">{{ item.label }}</span>
<span v-if="item.shortcut">
<kbd v-for="key in item.shortcut" :style="{fontSize: key === '⌘' ? '14px' : '12px'}">{{ key }}</kbd>
</span>
</div>
</div>
</li>
</ul>
</e-vue-contextmenu>
<file-contextmenu ref="contextShow" :menus="menus" @menusOperations="menusOperations" @show="show" @hide="hide" @contextmenu="contextmenu" />
<!--list布局-->
<div
@ -914,6 +876,7 @@ import ShareDialog from '@/components/ShareDialog/index.vue'
import DialogFileList from '@/components/ShowFile/DialogFileList.vue'
import FileClipboard from '@/components/ShowFile/FileClipboard.vue'
import TagDialog from '@/components/TagDialog/index.vue'
import FileContextmenu from '@/components/VContextmenu/file-contextmenu.vue'
import store from '@/store'
import { getElementToPageLeft } from '@/utils/dom'
@ -932,6 +895,7 @@ import { mapGetters, mapState } from 'vuex'
export default {
name: 'ShowFile',
components: {
FileContextmenu,
FileClipboard,
DialogFileList,
SearchOption,
@ -1018,19 +982,14 @@ export default {
default: function() {
return [
fileOperations.open,
{ iconClass: 'share', label: '分享', operation: 'share' },
{ iconClass: 'tag', label: '标签', operation: 'tag' },
fileOperations.download,
{ iconClass: 'share', label: '分享...', operation: 'share' },
{ iconClass: 'tag', label: '标签...' , operation: 'tag'},
{ iconClass: 'menu-favorite', label: '收藏', operation: 'favorite' },
{
iconClass: 'menu-details',
label: '详情',
shortcut: ["space"],
operation: 'details',
},
fileOperations.detail,
fileOperations.copyOnly,
fileOperations.rename,
fileOperations.copy,
fileOperations.copyOnly,
fileOperations.download,
fileOperations.remove,
]
},
@ -1044,10 +1003,10 @@ export default {
label: '取消选定',
operation: 'deselect',
},
{ iconClass: 'tag', label: '标签', operation: 'tag' },
fileOperations.copy,
fileOperations.copyOnly,
fileOperations.download,
{ iconClass: 'tag', label: '标签', operation: 'tag' },
fileOperations.copyOnly,
fileOperations.copy,
fileOperations.remove,
]
},
@ -1134,7 +1093,6 @@ export default {
finished: false,
newFolderLoading: false,
renameLoading: false,
menuTriangle: '', // 三角菜单
cellMouseIndex: -1,
editingIndex: -1,
titlePrefix: '',
@ -3563,7 +3521,7 @@ export default {
this.setMenusByPermission(row)
} else {
if (row.suffix && row.suffix === 'md' && this.queryFileType !== 'trash') {
this.menus.splice(2, 0, {
this.menus.splice(this.getIndexOfFileContextMenus(fileOperations.copyOnly.operation), 0, {
iconClass: 'menu-edit1',
label: '编辑',
operation: 'edit',
@ -3577,11 +3535,15 @@ export default {
)
}
if (!row.isFolder && this.queryFileType !== 'trash') {
this.menus.splice(-2, 0, fileOperations.duplicate)
// 创建副本
const copyIndex = this.getIndexOfFileContextMenus(fileOperations.copy.operation)
if (copyIndex > -1) {
this.menus.splice(copyIndex, 0, fileOperations.duplicate)
}
}
if (row.ossFolder) {
// 删除分享选项
let index = this.menus.findIndex(item => item.operation === 'share')
let index = this.getIndexOfFileContextMenus('share')
if (index > -1) {
this.menus.splice(index, 1)
}
@ -3590,6 +3552,26 @@ export default {
}
this.preliminaryRowData(row)
},
setFileContextMenusDivider() {
// 下载栏下面添加分隔符
const downloadIndex = this.getIndexOfFileContextMenus(fileOperations.download.operation)
if (downloadIndex > -1 && this.menus.length - 1 > downloadIndex && !this.menus[downloadIndex + 1].divider) {
this.menus.splice(downloadIndex + 1, 0, { divider: true, operation: 'divider' })
}
// 删除栏上面添加分隔符
const removeIndex = this.getIndexOfFileContextMenus(fileOperations.remove.operation)
if (removeIndex > 0 && !this.menus[removeIndex - 1].divider) {
this.menus.splice(removeIndex, 0, { divider: true, operation: 'divider' })
}
// 详情栏下面添加分隔符
const detailIndex = this.getIndexOfFileContextMenus(fileOperations.detail.operation)
if (detailIndex > -1 && this.menus.length - 1 > detailIndex && !this.menus[detailIndex + 1].divider) {
this.menus.splice(detailIndex + 1, 0, { divider: true, operation: 'divider' })
}
},
getIndexOfFileContextMenus(operation) {
return this.menus.findIndex(item => item.operation === operation)
},
setMenusCopyDownLoadLinks(row) {
if (row.isShare) {
// 获取this.menus中download的索引
@ -3617,22 +3599,22 @@ export default {
})
},
setMenusByPermission(file) {
const reservations = ['open', 'download']
const reservations = ['open', 'download', 'details']
// 删除this.menus中不要的菜单, 仅保留reservations中的菜单
this.menus = this.menus.filter(item =>
reservations.includes(item.operation)
)
if (file.operationPermissionList && file.operationPermissionList.length > 0) {
if (file.operationPermissionList.indexOf('PUT') > -1) {
this.menus.splice(this.menus.length - 1, 0, fileOperations.rename)
this.menus.splice(this.menus.length, 0, fileOperations.rename)
}
if (file.operationPermissionList.indexOf('UPLOAD') > -1) {
if (file.operationPermissionList.indexOf('DELETE') > -1) {
this.menus.splice(this.menus.length - 1, 0, fileOperations.copy)
this.menus.splice(this.menus.length - 1, 0, fileOperations.copyOnly)
this.menus.splice(this.menus.length, 0, fileOperations.copy)
this.menus.splice(this.menus.length, 0, fileOperations.copyOnly)
}
if (!file.isFolder) {
this.menus.splice(this.menus.length - 1, 0, fileOperations.duplicate)
this.menus.splice(this.menus.length, 0, fileOperations.duplicate)
}
}
if (file.operationPermissionList.indexOf('DELETE') > -1) {
@ -3643,6 +3625,7 @@ export default {
},
// 鼠标右击
rowContextmenu(row) {
this.$refs.homeContextmenu.hide()
if (this.selectFile) {
return
}
@ -3653,7 +3636,7 @@ export default {
) > -1
) {
this.menusIsMultiple = true
this.menus = this.multipleRightMenus
this.menus = JSON.parse(JSON.stringify(this.multipleRightMenus))
this.highlightFavorite(false, false)
} else {
this.$refs.fileListTable.clearSelection()
@ -3661,13 +3644,35 @@ export default {
this.menusIsMultiple = false
this.setMenus(row)
}
// 设置分割线
this.setFileContextMenusDivider()
event.preventDefault()
this.menuTriangle = ''
event.stopPropagation()
const e = {}
e.pageX = event.pageX + 5
e.pageY = event.pageY + 20
e.clientX = event.clientX + 50
e.clientY = event.clientY + 2
e.top = event.clientY
e.left = event.clientX
const dividerSize = this.menus.filter(item => item.divider).length
const containerWidth = 180 + 5
const containerHeight = (this.menus.length - dividerSize) * 35 + (dividerSize * 10) + 21
const distanceToBottom = document.documentElement.clientHeight - event.clientY
const distanceToRight = document.documentElement.clientWidth - event.clientX
if (distanceToBottom < containerHeight) {
e.top = event.clientY - containerHeight
}
if (distanceToRight < containerWidth) {
e.left = event.clientX - containerWidth
}
this.$refs.contextShow.showMenu(e)
this.cellMouseIndex = -1
},
@ -3701,6 +3706,11 @@ export default {
// this.$set(this.menus, 0, item_menu)
}
},
contextmenu(e) {
console.log('contextmenu', e)
e.preventDefault()
e.stopPropagation()
},
show() {
const that = this
this.contextmenuDisabled = true
@ -4257,7 +4267,7 @@ export default {
if (row.isFolder) {
url = fileConfig.packageDownloadUrl(row.id, row.name + '.zip', this.shareToken)
}
let clipboard = new Clipboard('.newFileMenu', {
let clipboard = new Clipboard('.file-contextmenu', {
text: function() {
return url
},

View File

@ -0,0 +1,180 @@
<template>
<ul
v-show="visible"
ref="contextmenu"
:class="contextmenuCls"
:style="style"
>
<slot />
</ul>
</template>
<script>
export default {
name: 'VContextmenu',
provide () {
return {
$$contextmenu: this,
}
},
props: {
eventType: {
type: String,
default: 'contextmenu',
},
theme: {
type: String,
default: 'default',
},
autoPlacement: {
type: Boolean,
default: true,
},
disabled: Boolean,
},
data () {
return {
visible: false,
references: [],
style: {
top: 0,
left: 0,
},
}
},
computed: {
clickOutsideHandler () {
return this.visible ? this.hide : () => {}
},
isClick () {
return this.eventType === 'click'
},
contextmenuCls () {
return [
'v-contextmenu',
`v-contextmenu--${this.theme}`,
]
},
},
watch: {
visible (value) {
if (value) {
this.$emit('show', this)
document.body.addEventListener('click', this.handleBodyClick)
} else {
this.$emit('hide', this)
document.body.removeEventListener('click', this.handleBodyClick)
}
},
},
mounted () {
document.body.appendChild(this.$el)
if (window.$$VContextmenu) {
window.$$VContextmenu[this.$contextmenuId] = this
} else {
window.$$VContextmenu = { [this.$contextmenuId]: this }
}
},
beforeDestroy () {
document.body.removeChild(this.$el)
delete window.$$VContextmenu[this.$contextmenuId]
this.references.forEach((ref) => {
ref.el.removeEventListener(this.eventType, this.handleReferenceContextmenu)
})
document.body.removeEventListener('click', this.handleBodyClick)
},
methods: {
addRef (ref) {
// FIXME: 如何处理 removeRef?
this.references.push(ref)
ref.el.addEventListener(this.eventType, this.handleReferenceContextmenu)
},
handleReferenceContextmenu (event) {
event.preventDefault()
if (this.disabled) return
const reference = this.references.find(ref => ref.el.contains(event.target))
this.$emit('contextmenu', reference ? reference.vnode : null)
const eventX = event.pageX
const eventY = event.pageY
this.show()
this.$nextTick(() => {
const contextmenuPosition = {
top: eventY,
left: eventX,
}
if (this.autoPlacement) {
const contextmenuWidth = this.$refs.contextmenu.clientWidth
const contextmenuHeight = this.$refs.contextmenu.clientHeight
if (contextmenuHeight + eventY >= window.innerHeight) {
contextmenuPosition.top -= contextmenuHeight
}
if (contextmenuWidth + eventX >= window.innerWidth) {
contextmenuPosition.left -= contextmenuWidth
}
}
this.style = {
top: `${contextmenuPosition.top}px`,
left: `${contextmenuPosition.left}px`,
}
})
},
handleBodyClick (event) {
const notOutside = this.$el.contains(event.target) || (
this.isClick && this.references.some(ref => ref.el.contains(event.target))
)
if (!notOutside) {
this.visible = false
}
},
show (position) {
Object.keys(window.$$VContextmenu)
.forEach((key) => {
if (key !== this.$contextmenuId) {
window.$$VContextmenu[key].hide()
}
})
if (position) {
this.style = {
top: `${position.top}px`,
left: `${position.left}px`,
}
}
this.visible = true
},
hide () {
this.visible = false
},
hideAll () {
Object.keys(window.$$VContextmenu)
.forEach((key) => {
window.$$VContextmenu[key].hide()
})
},
},
}
</script>

View File

@ -0,0 +1,33 @@
<template>
<li class="v-contextmenu-group">
<ul
:style="menusStyle"
class="v-contextmenu-group__menus"
>
<slot />
</ul>
</li>
</template>
<script>
export default {
name: 'VContextmenuGroup',
props: {
maxWidth: [Number, String],
},
computed: {
menusStyle () {
if (!this.maxWidth) return null
const style = {
'max-width': typeof this.maxWidth === 'number' ? `${this.maxWidth}px` : this.maxWidth,
'overflow-x': 'auto',
}
return style
},
},
}
</script>

View File

@ -0,0 +1,73 @@
<template>
<li
v-if="divider"
class="v-contextmenu-divider"
/>
<li
v-else
:class="classname"
@click="handleClick"
@mouseenter="handleMouseenter"
@mouseleave="handleMouseleave"
>
<slot />
</li>
</template>
<script>
export default {
name: 'VContextmenuItem',
inject: ['$$contextmenu'],
props: {
divider: Boolean,
disabled: Boolean,
autoHide: {
type: Boolean,
default: true,
},
},
data () {
return {
hover: false,
}
},
computed: {
classname () {
return {
'v-contextmenu-item': !this.divider,
'v-contextmenu-item--hover': this.hover,
'v-contextmenu-item--disabled': this.disabled,
}
},
},
methods: {
handleMouseenter (event) {
if (this.disabled) return
this.hover = true
this.$emit('mouseenter', this, event)
},
handleMouseleave (event) {
if (this.disabled) return
this.hover = false
this.$emit('mouseleave', this, event)
},
handleClick (event) {
if (this.disabled) return
this.$emit('click', this, event)
this.autoHide && this.$$contextmenu.hide()
},
},
}
</script>

View File

@ -0,0 +1,110 @@
<template>
<li
:class="classname"
@mouseenter="handleMouseenter"
@mouseleave="handleMouseleave"
>
<svg-icon v-if="iconClass" :icon-class="iconClass"></svg-icon>
<!-- <div class="v-contextmenu-submenu__title home-contextmenu">-->
<!-- <svg-icon v-if="iconClass" :icon-class="iconClass"></svg-icon>-->
<!-- <div class="home-contextmenu-title">-->
<!-- <span class="menuitem text">{{ title }}</span>-->
<!-- </div>-->
<!-- <span class="v-contextmenu-iconfont v-contextmenu-submenu__icon" />-->
<!-- </div>-->
<div class="v-contextmenu-submenu__title home-contextmenu-title">
<span class="menuitem text">{{ title }}</span>
<span class="v-contextmenu-iconfont v-contextmenu-submenu__icon" />
</div>
<ul
v-show="hover"
ref="submenu"
:class="submenuCls"
>
<slot />
</ul>
</li>
</template>
<script>
export default {
name: 'VContextmenuSubmenu',
props: {
title: String,
disabled: Boolean,
iconClass: {
type: String,
required: true
}
},
data () {
return {
hover: false,
submenuPlacement: [],
}
},
computed: {
classname () {
return {
'v-contextmenu-item': true,
'v-contextmenu-submenu': true,
'v-contextmenu-item--hover': this.hover,
'v-contextmenu-item--disabled': this.disabled,
}
},
submenuCls () {
return [
'v-contextmenu',
'v-contextmenu-sub',
...this.submenuPlacement,
]
},
},
methods: {
handleMouseenter (event) {
if (this.disabled) return
const { target } = event
const targetDimension = target.getBoundingClientRect()
this.hover = true
this.$emit('mouseenter', this, event)
this.$nextTick(() => {
const submenuWidth = this.$refs.submenu.clientWidth
const submenuHeight = this.$refs.submenu.clientHeight
const submenuPlacement = []
if (targetDimension.right + submenuWidth >= window.innerWidth) {
submenuPlacement.push('left')
} else {
submenuPlacement.push('right')
}
if (targetDimension.bottom + submenuHeight >= window.innerHeight) {
submenuPlacement.push('bottom')
} else {
submenuPlacement.push('top')
}
this.submenuPlacement = submenuPlacement
})
},
handleMouseleave (event) {
if (this.disabled) return
this.hover = false
this.$emit('mouseleave', this, event)
},
},
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,19 @@
/* eslint-disable no-param-reassign */
export default {
// 在组件 mount 前,设置 $contextmenuId,修复内存泄漏的问题
bind (el, binding, vnode) {
const node = vnode.context.$refs[binding.arg] || vnode.context.$refs[binding.value]
const contextmenu = Object.prototype.toString.call(node) === '[object Array]' ? node[0] : node
if (contextmenu) {
contextmenu.$contextmenuId = el.id || contextmenu._uid // eslint-disable-line no-underscore-dangle
}
},
// 之所以用 inserted 而不是 bind,是需要确保 contentmenu mounted 后才进行 addRef 操作
inserted (el, binding, vnode) {
const node = vnode.context.$refs[binding.arg] || vnode.context.$refs[binding.value]
const contextmenu = Object.prototype.toString.call(node) === '[object Array]' ? node[0] : node
if (contextmenu) {
contextmenu.addRef({ el, vnode })
}
},
}

View File

@ -0,0 +1,142 @@
<template>
<v-contextmenu ref="contextShow" class="file-contextmenu" :class="className" @show="show" @hide="hide" @contextmenu="contextmenu">
<div v-for="item of menus" :key="item.label">
<!-- 一级菜单 -->
<v-contextmenu-item v-if="!item.child" :divider="!!item.divider" @click="menusOperations(item.operation, $event)"
class="menuitem home-contextmenu">
<svg-icon v-if="item.iconClass" :icon-class="item.iconClass"></svg-icon>
<div class="home-contextmenu-title">
<span class="menuitem text">{{ item.label }}</span>
<span v-if="item.shortcut">
<kbd v-for="key in item.shortcut" :style="{fontSize: key === '⌘' ? '14px' : '12px'}">{{ key }}</kbd>
</span>
</div>
</v-contextmenu-item>
<v-contextmenu-submenu class="home-contextmenu" v-else :icon-class="item.iconClass" :title="item.label">
<!-- 二级菜单 -->
<div v-for="itemSecond of item.child" :key="itemSecond.operation">
<v-contextmenu-item
v-if="!itemSecond.child"
:divider="!!itemSecond.divider"
@click="menusOperations(itemSecond.operation, $event)"
>
<svg-icon
v-if="itemSecond.iconClass"
:icon-class="itemSecond.iconClass"
></svg-icon>
{{ itemSecond.label }}
</v-contextmenu-item>
<v-contextmenu-submenu
v-if="itemSecond.child"
:icon-class="itemSecond.iconClass"
:title="itemSecond.label"
>
</v-contextmenu-submenu>
</div>
</v-contextmenu-submenu>
</div>
</v-contextmenu>
</template>
<script>
export default {
name: 'file-contextmenu',
props: {
menus: {
type: Array,
default: () => []
},
className: {
type: String,
default: ''
}
},
data() {
return {
}
},
methods: {
menusOperations() {
this.$emit('menusOperations', ...arguments)
},
showMenu(e) {
this.$refs.contextShow.show(e)
},
hideMenu(e) {
this.$refs.contextShow.hide(e)
},
show() {
this.$emit('show')
},
hide() {
this.$emit('hide')
},
contextmenu() {
this.$emit('contextmenu')
}
}
}
</script>
<style scoped lang="scss">
@import "~@/styles/variables.scss";
>>> .v-contextmenu.v-contextmenu-sub {
border-radius: $dialogBorderRadius;
padding: 8px;
}
.file-contextmenu.v-contextmenu {
min-width: 180px;
border-radius: $dialogBorderRadius;
padding: 8px;
background-color: rgba(255, 255, 255, .75);
-webkit-backdrop-filter: blur(16px) saturate(180%);
backdrop-filter: blur(16px) saturate(180%);
.v-contextmenu-divider {
margin: 5px 0;
}
li {
cursor: pointer;
padding: 0;
font-size: 16px;
min-width: 136px;
line-height: 35px;
&:hover {
border-radius: 10px;
background-color: #409eff30;
color: #000;
}
}
li > .text {
font-weight: normal;
}
li > .svg-icon {
font-size: 20px;
cursor: pointer;
}
.v-contextmenu-item {
padding: 0 10px;
display: flex;
align-items: center;
justify-content: space-between;
>>> .svg-icon {
margin-right: 10px;
font-size: 20px;
}
}
>>> .home-contextmenu-title {
width: 100%;
display: flex;
justify-content: space-between;
}
}
</style>

View File

@ -0,0 +1,32 @@
import directive from './directive'
import Contextmenu from './components/Contextmenu.vue'
import ContextmenuItem from './components/ContextmenuItem.vue'
import ContextmenuSubmenu from './components/ContextmenuSubmenu.vue'
import ContextmenuGroup from './components/ContextmenuGroup.vue'
const install = (Vue) => {
Vue.directive('contextmenu', directive)
Vue.component(Contextmenu.name, Contextmenu)
Vue.component(ContextmenuItem.name, ContextmenuItem)
Vue.component(ContextmenuSubmenu.name, ContextmenuSubmenu)
Vue.component(ContextmenuGroup.name, ContextmenuGroup)
}
export {
directive,
Contextmenu,
ContextmenuItem,
ContextmenuSubmenu,
ContextmenuGroup,
}
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export default {
install,
}

View File

@ -0,0 +1,82 @@
.v-contextmenu
position: absolute
padding: padding-vertical 0
margin: 0
background-color: #fff
border: 1px solid border-color
border-radius: 4px
box-shadow: 2px 2px 8px 0px shadow-color
list-style: none
font-size: 14px
white-space: nowrap
cursor: pointer
z-index: 2800
-webkit-tap-highlight-color: transparent
.v-contextmenu-item
padding: padding-vertical contextmenu-item-padding-horizonal
line-height: 1
color: #333
&.v-contextmenu-item--hover
color: #fff
&.v-contextmenu-item--disabled
color: #ccc
cursor: not-allowed
.v-contextmenu-divider
height: 0
margin: padding-vertical 0
border-bottom: 1px solid border-color
.v-contextmenu-group__menus
padding: 0 5px
margin: 0
list-style: none
.v-contextmenu-item
display: inline-block
padding: padding-vertical (contextmenu-item-padding-horizonal - 5)
.v-contextmenu-submenu
position: relative
& > .v-contextmenu
position: absolute
&.left
left: 0
transform: translateX(-100%)
&.right
right: 0
transform: translateX(100%)
&.top
top: -(padding-vertical) - 1
&.bottom
bottom: -(padding-vertical) - 1
.v-contextmenu-submenu__title
margin-right: padding-vertical * 2
.v-contextmenu-submenu__icon
position: absolute
right: padding-vertical
&::before
content: "\e622"
.v-contextmenu--default
.v-contextmenu-item--hover
background-color: active-color
.v-contextmenu--bright
.v-contextmenu-item--hover
background-color: active-color-bright
.v-contextmenu--dark
.v-contextmenu-item--hover
background-color: active-color-dark

Binary file not shown.

View File

@ -0,0 +1,36 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2013-9-30: Created.
-->
<svg>
<metadata>
Created by iconfont
</metadata>
<defs>
<font id="iconfont" horiz-adv-x="1024" >
<font-face
font-family="iconfont"
font-weight="500"
font-stretch="normal"
units-per-em="1024"
ascent="896"
descent="-128"
/>
<missing-glyph />
<glyph glyph-name="x" unicode="x" horiz-adv-x="1001"
d="M281 543q-27 -1 -53 -1h-83q-18 0 -36.5 -6t-32.5 -18.5t-23 -32t-9 -45.5v-76h912v41q0 16 -0.5 30t-0.5 18q0 13 -5 29t-17 29.5t-31.5 22.5t-49.5 9h-133v-97h-438v97zM955 310v-52q0 -23 0.5 -52t0.5 -58t-10.5 -47.5t-26 -30t-33 -16t-31.5 -4.5q-14 -1 -29.5 -0.5
t-29.5 0.5h-32l-45 128h-439l-44 -128h-29h-34q-20 0 -45 1q-25 0 -41 9.5t-25.5 23t-13.5 29.5t-4 30v167h911zM163 247q-12 0 -21 -8.5t-9 -21.5t9 -21.5t21 -8.5q13 0 22 8.5t9 21.5t-9 21.5t-22 8.5zM316 123q-8 -26 -14 -48q-5 -19 -10.5 -37t-7.5 -25t-3 -15t1 -14.5
t9.5 -10.5t21.5 -4h37h67h81h80h64h36q23 0 34 12t2 38q-5 13 -9.5 30.5t-9.5 34.5q-5 19 -11 39h-368zM336 498v228q0 11 2.5 23t10 21.5t20.5 15.5t34 6h188q31 0 51.5 -14.5t20.5 -52.5v-227h-327z" />
<glyph glyph-name="arrow-right" unicode="&#58914;" d="M725.3399071604938 383.8342991012345a41.425224691358025 41.425224691358025 0 0 0-12.924670103703681-30.074713125925918l-343.82936493827134-325.2708642765434a41.425224691358025 41.425224691358025 0 0 0-56.91825872592596 60.23227670123452l312.0976428246912 295.1961511506175-312.0976428246914 295.4447024987654a41.50807514074074 41.50807514074074 0 0 0-1.6570089876543506 58.575267713580246c15.74158538271605 16.570089876543207 42.00517783703699 17.315743920987664 58.575267713580246 1.6570089876543506l343.8293649382716-325.6022660740737a41.425224691358025 41.425224691358025 0 0 0 12.924670103703713-30.15756357530862z" horiz-adv-x="1024" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,14 @@
@font-face
font-family: "v-contextmenu-iconfont"
src: url("./fonts/iconfont.eot")
src: url("./fonts/iconfont.eot#iefix") format("embedded-opentype"),
url("./fonts/iconfont.woff") format("woff"),
url("./fonts/iconfont.ttf") format("truetype"),
url("./fonts/iconfont.svg") format("svg")
.v-contextmenu-iconfont
font-family: "v-contextmenu-iconfont" !important
font-size: inherit
font-style: normal
-webkit-font-smoothing: antialiased
-moz-osx-font-smoothing: grayscale

View File

@ -0,0 +1 @@
@font-face{font-family:"v-contextmenu-iconfont";src:url("./fonts/iconfont.eot");src:url("./fonts/iconfont.eot#iefix") format("embedded-opentype"),url("./fonts/iconfont.woff") format("woff"),url("./fonts/iconfont.ttf") format("truetype"),url("./fonts/iconfont.svg") format("svg")}.v-contextmenu-iconfont{font-family:"v-contextmenu-iconfont" !important;font-size:inherit;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.v-contextmenu{position:absolute;padding:5px 0;margin:0;background-color:#fff;border:1px solid #e8e8e8;border-radius:4px;box-shadow:2px 2px 8px 0 rgba(150,150,150,0.2);list-style:none;font-size:14px;white-space:nowrap;cursor:pointer;z-index:2800;-webkit-tap-highlight-color:transparent}.v-contextmenu .v-contextmenu-item{padding:5px 14px;line-height:1;color:#333}.v-contextmenu .v-contextmenu-item.v-contextmenu-item--hover{color:#fff}.v-contextmenu .v-contextmenu-item.v-contextmenu-item--disabled{color:#ccc;cursor:not-allowed}.v-contextmenu .v-contextmenu-divider{height:0;margin:5px 0;border-bottom:1px solid #e8e8e8}.v-contextmenu .v-contextmenu-group__menus{padding:0 5px;margin:0;list-style:none}.v-contextmenu .v-contextmenu-group__menus .v-contextmenu-item{display:inline-block;padding:5px 9px}.v-contextmenu .v-contextmenu-submenu{position:relative}.v-contextmenu .v-contextmenu-submenu > .v-contextmenu{position:absolute}.v-contextmenu .v-contextmenu-submenu > .v-contextmenu.left{left:0;transform:translateX(-100%)}.v-contextmenu .v-contextmenu-submenu > .v-contextmenu.right{right:0;transform:translateX(100%)}.v-contextmenu .v-contextmenu-submenu > .v-contextmenu.top{top:-6px}.v-contextmenu .v-contextmenu-submenu > .v-contextmenu.bottom{bottom:-6px}.v-contextmenu .v-contextmenu-submenu .v-contextmenu-submenu__title{margin-right:10px}.v-contextmenu .v-contextmenu-submenu .v-contextmenu-submenu__icon{position:absolute;right:5px}.v-contextmenu .v-contextmenu-submenu .v-contextmenu-submenu__icon::before{content:"\e622"}.v-contextmenu--default .v-contextmenu-item--hover{background-color:#46a0fc}.v-contextmenu--bright .v-contextmenu-item--hover{background-color:#ef5350}.v-contextmenu--dark .v-contextmenu-item--hover{background-color:#2d3035}

View File

@ -0,0 +1,11 @@
active-color = #46a0fc
border-color = #e8e8e8
shadow-color = rgba(150, 150, 150, 0.2)
padding-vertical = 5px
contextmenu-item-padding-horizonal = 14px
active-color-bright = #ef5350
active-color-dark = #2d3035
@import "./iconfont.styl"
@import "./contextmenu.styl"

View File

@ -53,7 +53,7 @@ export default {
props: {
value: {
type: String,
default: 'sdfsd'
default: ''
}
},
data() {

View File

@ -427,9 +427,9 @@ export default {
$tagMenuBorderRadius: 8px;
>>> .ctx-menu-container {
border: 0 solid rgba(0, 0, 0, 0);
min-width: unset;
border-radius: $dialogBorderRadius;
border: 0 solid rgba(0, 0, 0, 0) !important;
min-width: unset !important;
border-radius: $dialogBorderRadius !important;
}
.tag-menu ul {
@ -446,7 +446,7 @@ $tagMenuBorderRadius: 8px;
min-width: 110px;
}
.tag-menu {
padding: 5px 5px;
padding: 3px;
}
.tag-menu li:hover {
cursor: pointer;

View File

@ -19,8 +19,8 @@ setFavicon()
import { Affix } from 'ant-design-vue';
Vue.use(Affix)
import contentmenu from 'v-contextmenu'
import 'v-contextmenu/dist/index.css'
import contentmenu from '@/components/VContextmenu'
import '@/components/VContextmenu/styles/index.css'
Vue.use(contentmenu)
import '@/icons' // icon

View File

@ -131,31 +131,6 @@
line-height: 35px;
}
>>> .ctx-menu-container {
border: 0 solid rgba(0, 0, 0, 0);
min-width: 160px;
border-radius: $dialogBorderRadius;
padding: 10px 10px;
box-shadow: 0 5px 12px #ccc;
.home-contextmenu {
padding: 0 10px;
display: flex;
align-items: center;
justify-content: space-between;
.svg-icon {
margin-right: 10px;
}
}
.home-contextmenu-title {
width: 100%;
display: flex;
justify-content: space-between;
}
}
.menu-triangle-bottom {
border: 0 solid rgba(0, 0, 0, 0);

View File

@ -40,9 +40,9 @@ body {
.el-popover.el-popper.upload-file {
border-radius: 16px;
padding: 10px 10px;
padding: 8px;
li {
padding: 0 10px;
padding: 0 8px;
}
}

View File

@ -3,9 +3,15 @@ export const fileOperations = {
remove: { iconClass: 'menu-remove', label: '删除', operation: 'remove', shortcut: ['Del'] },
duplicate: { iconClass: 'duplicate', label: '创建副本', operation: 'duplicate' },
download: { iconClass: 'menu-download', label: '下载', operation: 'download' },
copy: { iconClass: 'menu-copy', label: '移动或复制', operation: 'copy' },
copy: { iconClass: 'menu-copy', label: '移动或复制...', operation: 'copy' },
rename: { iconClass: 'menu-rename', label: '重命名', operation: 'rename', shortcut: ['F2'] },
copyOnly: { iconClass: 'menu-copy', label: '复制', operation: 'copyOnly', shortcut: [navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl', 'C'] },
detail: {
iconClass: 'menu-details',
label: '详情...',
shortcut: ["space"],
operation: 'details',
}
}
export const fileArrangements = [