656 lines
18 KiB
Vue

<template>
<div :class="{ 'has-logo': showLogo }">
<el-dialog class="dialog-cm" width="500px" title="修改标签" :close-on-click-modal="false" :visible.sync="dialogTagVisible" append-to-body>
<el-form ref="managerForm" size="medium" :model="form" label-position="left" :rules="rules" label-width="95px">
<el-form-item label="标签名称:" prop="name">
<el-input v-model="form.name" placeholder="请输入标签名称"/>
</el-form-item>
<el-form-item label="标签颜色:" prop="color">
<div class="form-tag-color">
<svg-icon class="tag-color-icon" :style="{ color: tagColors.hex }" icon-class="tag2"></svg-icon>
<sketch-picker v-model="tagColors"/>
</div>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button round size="small" @click="dialogTagVisible = false">取 消</el-button>
<el-button round size="small" native-type="submit" type="primary" :loading="tagUpdateLoading"
@click.native.prevent="updateTag">保 存
</el-button>
</div>
</el-dialog>
<div class="scrollbar-head">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:unique-opened="false"
:active-text-color="variables.menuActiveText"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item
v-for="route in routes"
:key="route.path"
:item="route"
:base-path="route.path"
/>
</el-menu>
</el-scrollbar>
</div>
<!--标签菜单-->
<e-vue-contextmenu ref="contextShow" @ctx-show="show" @ctx-hide="hide">
<div ref="contextShow" class="tag-menu">
<ul>
<li v-for="(item, index) in tagMenus" :key="item.label" @click="tagMenusOperation(item.operation)">
<label class="menuitem">
<svg-icon :icon-class="item.iconClass" />
<span class="menuitem text">{{ item.label }}</span>
</label>
</li>
</ul>
</div>
</e-vue-contextmenu>
<div v-if="showTag && tagList.length > 0" class="scrollbar-tag" :class="{ collapse: isCollapse }">标签</div>
<div class="scroll-decoration-top"></div>
<el-scrollbar class="tag-list" wrap-class="scrollbar-wrapper">
<div v-if="isCollapse">
<ul class="infinite-list">
<el-tooltip class="item"
v-if="showTag"
effect="dark"
:content="tag.name"
placement="right"
v-for="tag in tagList"
:key="tag.tagId"
>
<li class="infinite-list-item collapse" :class="{ 'highlight': selectedTagId === tag.id }" @click="tagClick(tag)">
<svg-icon :style="{ color: tag.color, fontSize: '14px' }" icon-class="tag2"></svg-icon>
</li>
</el-tooltip>
</ul>
</div>
<div v-else class="tag-drop-container">
<ul class="infinite-list">
<li v-for="tag in tagList" v-if="showTag" :key="tag.id"
class="infinite-list-item expand"
:class="{ 'highlight': selectedTagId === tag.id }"
@click="tagClick(tag)"
>
<div class="tag-name-icon">
<svg-icon :style="{ color: tag.color, fontSize: '14px' }" icon-class="tag2"></svg-icon>
<div class="tag-name">{{ tag.name }}</div>
</div>
<el-button round slot="reference" class="btn-more" size="mini" icon="el-icon-more" @click.stop.prevent="clickTagMore(tag, $event)"></el-button>
</li>
</ul>
</div>
</el-scrollbar>
<div class="scroll-decoration-bottom"></div>
<div class="scrollbar-footer">
<div class="quota-space">
<el-progress
v-show="percentage > 0"
:class="{ collapse: isCollapse }"
:percentage="percentage"
:format="progressFormat"
:color="customColors"
></el-progress>
</div>
<div class="webdav">
<div
:class="{ normal: true, collapse: isCollapse }"
@mousemove="showCopyBtn = true"
@mouseleave="showCopyBtn = false"
>
<svg-icon class="webdav-icon" icon-class="disk-drive"></svg-icon>
<div class="wedav-text">WebDAV</div>
<el-tooltip placement="right" v-if="showCopyBtn">
<div slot="content">{{ $t('app.clickCopyWebDAVAddress') }}<br />{{ webdavUrl }}</div>
<svg-icon
class="copy-btn"
icon-class="menu-fuzhi"
@click="copyWebDAVLink('.copy-btn')"
:data-clipboard-text="webdavUrl"
></svg-icon>
</el-tooltip>
</div>
</div>
</div>
</div>
</template>
<script>
import UploadImageInput from '@/components/input/UploadImageInput.vue'
import Sortable from 'sortablejs'
import { mapGetters, mapState } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'
import {formatSize} from "@/utils/number";
import fileConfig from "@/utils/file-config";
import Clipboard from "clipboard";
import tagApi from '@/api/tag'
import AppLink from './Link'
import { Sketch } from 'vue-color'
export default {
components: { UploadImageInput, SidebarItem, Logo, AppLink , 'sketch-picker': Sketch },
data() {
const checkTagName = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入标签名称'));
} else if (/[\[\]\/\\"<>?*]/gi.test(value)) {
callback(new Error('标签名称不能包含这些字符:<,>,|,*,?,,/,[,]'));
} else {
callback();
}
};
return {
percentage: 0,
customColors: [
{color: '#f56c6c', percentage: 150},
{color: '#f56c6c', percentage: 100},
{color: '#e6a23c', percentage: 80},
{color: '#5cb87a', percentage: 60},
{color: '#1989fa', percentage: 40},
{color: '#6f7ad3', percentage: 20}
],
showCopyBtn: false,
takeUpSpace: 0,
tagList: [],
showTag: false,
tagMenus: [
{ iconClass: 'menu-rename', label: '修改', operation: 'update' },
{ iconClass: 'menu-remove', label: '删除', operation: 'delete' }
],
contextTag: {},
tagMenusHideTime: 0,
dialogTagVisible: false,
form: {
tagId: '',
name: '',
},
tagColors: {
hex: '#f56c6c',
},
rules: {
name: [
{ validator: checkTagName, trigger: 'blur' },
]
},
tagUpdateLoading: false,
selectedTagId: null,
}
},
computed: {
...mapState(['message']),
...mapGetters([
'sidebar',
'name',
'userInfo'
]),
webdavUrl() {
return fileConfig.webDAVUrl(this.name)
},
routes() {
let routes = []
if(this.$route.meta.menuType === 0){
this.$router.options.routes.forEach(route => {
if(route.menuType === 0){
routes.push(route)
}
})
} else {
this.$router.options.routes.forEach(route => {
if(route.menuType === 2){
routes.push(route)
}
})
}
return routes
},
activeMenu() {
const route = this.$route
const { meta, path } = route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
showLogo() {
return this.$store.state.settings.sidebarLogo
},
variables() {
return variables
},
isCollapse() {
return !this.sidebar.opened
}
},
watch: {
isCollapse(val){
},
$route(to) {
this.showTag = to.meta !== undefined && to.meta.menuType !== undefined && to.meta.menuType === 0
this.setSelectedTagFromUrl()
},
message(msg) {
if (msg.event === 'msg/file/change') {
this.onmessage(msg.data)
if (msg.data.url === 'updateTags') {
this.setTagListData(msg.data.body)
}
}
},
},
mounted() {
this.getTagList()
// 阻止默认行为
document.body.ondrop = function (event) {
event.preventDefault();
event.stopPropagation();
};
this.tagDrop()
this.setSelectedTagFromUrl()
},
methods: {
getTagList() {
if (this.$route.meta.menuType === 0) {
this.showTag = true
} else {
this.showTag = false
return
}
tagApi.tagList({userId: this.$store.state.user.userId}).then(res => {
this.setTagListData(res.data)
})
},
// tag拖拽
tagDrop() {
const tagList = document.querySelector('.tag-drop-container > .infinite-list')
if (!tagList) return
const _this = this
Sortable.create(tagList, {
animation: 150,
ghostClass: 'drop-background-class',
onEnd({ newIndex, oldIndex }) {
const currRow = _this.tagList.splice(oldIndex, 1)[0]
_this.tagList.splice(newIndex, 0, currRow)
let tagIdList = []
_this.tagList.forEach(tag => {
tagIdList.push(tag.id)
})
tagApi.setTagSort(tagIdList).then(()=>{
_this.$message.success('标签顺序已更新');
}).catch(() => {
})
}
})
},
setTagListData(tagList) {
this.tagList = tagList
this.showTag = this.tagList.length > 0
},
onmessage(msg) {
const takeUpSpace = msg.space
if (takeUpSpace) {
this.takeUpSpace = takeUpSpace
const space = takeUpSpace/1024/1024/1024
const percentage = Number((space/this.userInfo.quota * 100).toFixed(1))
this.percentage = percentage > 100 ? 100 : percentage
}
},
copyWebDAVLink(className) {
let clipboard = new Clipboard(className)
clipboard.on('success', e => {
this.$message({message: '复制成功', type: 'success', duration: 1000})
// 释放内存
clipboard.destroy()
})
clipboard.on('error', e => {
// 不支持复制
this.$message({message: '该浏览器不支持自动复制', type: 'warning', duration: 1000})
clipboard.destroy()
})
},
tagClick(tag) {
this.$router.push(`/tag?tagId=${tag.id}`)
this.selectedTagId = tag.id
},
setSelectedTagFromUrl() {
// 从 URL 中获取 tagId 参数
const tagId = this.$route.query.tagId
if (this.$route.path.startsWith("/tag") && tagId) {
this.selectedTagId = tagId
} else {
this.selectedTagId = null
}
},
clickTagMore(tag, event) {
this.contextTag = tag
event.stopPropagation()
if (new Date().getTime() - this.tagMenusHideTime < 10) {
return
}
const e = {}
e.pageX = event.pageX + 5
e.pageY = event.pageY + 2
e.clientX = event.clientX + 5
e.clientY = event.clientY + 2
this.$refs.contextShow.showMenu(event)
},
tagMenusOperation(operation) {
if (operation === 'update') {
this.$refs.contextShow.hideMenu()
this.form.name = this.contextTag.name
this.form.tagId = this.contextTag.id
this.tagColors.hex = this.contextTag.color
this.dialogTagVisible = true
} else if (operation === 'delete') {
this.$refs.contextShow.hideMenu()
this.$confirm('此操作将永久删除该标签, 是否继续?', `删除标签: ${this.contextTag.name}`, {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
tagApi.deleteFileTag({ tagId: this.contextTag.id }).then(() => {
this.getTagList()
this.$message({
type: 'success',
message: '删除成功!'
})
})
}).catch(() => {
})
}
},
updateTag() {
this.$refs.managerForm.validate((valid) => {
if (valid) {
this.tagUpdateLoading = true
this.form.color = this.tagColors.hex
tagApi.updateFileTag(this.form).then(() => {
this.dialogTagVisible = false
this.tagUpdateLoading = false
this.$message({
type: 'success',
message: '修改成功'
})
}).catch(() => {
this.tagUpdateLoading = false
})
}
})
},
show() {
},
hide() {
this.tagMenusHideTime = new Date().getTime()
},
progressFormat() {
if (this.takeUpSpace === 0) {
this.takeUpSpace = this.userInfo.takeUpSpace
}
const spaceBytes = this.takeUpSpace
const quotaBytes = this.userInfo.quota * 1024 * 1024 * 1024
if(spaceBytes && quotaBytes > 0){
const percentage = Number((spaceBytes/quotaBytes * 100).toFixed(5))
this.percentage = percentage > 100 ? 100 : percentage
if(this.isCollapse){
return `${formatSize(this.takeUpSpace)}`
} else {
return `${formatSize(this.takeUpSpace)}/${formatSize(quotaBytes)}`
}
}
}
}
}
</script>
<style lang="scss" scoped>
@import "src/styles/element-ui";
$tagMenuBorderRadius: 8px;
>>> .ctx-menu-container {
border: 0 solid rgba(0, 0, 0, 0) !important;
min-width: unset !important;
border-radius: $dialogBorderRadius !important;
}
.tag-menu ul {
list-style: none;
padding-inline-start: 0;
margin-top: 0;
margin-bottom: 0;
padding: 0 5px;
}
.tag-menu li {
cursor: pointer;
margin: 0;
padding: 0;
min-width: 110px;
}
.tag-menu {
padding: 3px;
}
.tag-menu li:hover {
cursor: pointer;
border-radius: $tagMenuBorderRadius;
background-color: #409eff30;
}
.tag-menu li > .menuitem {
cursor: pointer;
line-height: 28px;
margin-left: 10px;
}
.tag-menu li > .menuitem > .text {
cursor: pointer;
font-weight: normal;
}
.tag-menu li > .menuitem > .svg-icon {
cursor: pointer;
margin-right: 8px !important;
}
.sidebar-container.has-logo {
display: flex;
flex-direction: column;
height: 100%;
.scrollbar-tag {
height: 30px;
line-height: 30px;
padding-left: 20px;
font-size: 14px;
font-weight: 900;
color: #a4a4a4;
}
.scrollbar-tag.collapse {
padding-left: unset;
text-align: center;
}
.tag-list {
overflow-y: auto;
flex: 1;
.infinite-list {
padding: 0;
margin: 0;
list-style: none;
}
.infinite-list-item {
height: 30px;
line-height: 30px;
padding: 0 5px 0 5px;
font-size: 14px;
color: #606266;
cursor: pointer;
margin: 2px 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.tag-name-icon {
display: flex;
justify-content: flex-start;
align-items: center;
.tag-name {
width: 74px;
white-space: nowrap; /* 强制单行显示 */
overflow: hidden; /* 隐藏溢出的内容 */
text-overflow: ellipsis; /* 添加省略号 */
}
}
.btn-more {
display: none;
padding: 2px 5px;
background: #dadada;
border: 0;
}
.svg-icon {
margin: 2px 10px !important;
}
.el-button:hover {
color: #409EFF;
border-color: #c6e2ff;
background-color: #ecf5ff;
}
&:hover {
background-color: #dadada !important;
border-radius: $tagMenuBorderRadius;
.btn-more {
display: block;
}
}
}
.infinite-list-item.highlight {
background-color: #dadada;
border-radius: $tagMenuBorderRadius;
}
.infinite-list-item.expand {
display: flex;
justify-content: space-between;
align-items: center;
border-radius: $tagMenuBorderRadius;
}
.infinite-list-item.collapse {
padding-left: unset;
text-align: center;
.svg-icon {
margin-right: unset !important;
}
}
}
.scrollbar-footer {
margin-top: 15px;
}
}
.scrollbar-head {
.el-scrollbar {
padding-bottom: 15px;
}
>>> .submenu-title-noDropdown {
padding-left: 15px !important;
padding-right: 10px !important;
}
>>> .el-submenu__title {
padding-left: 15px !important;
padding-right: 10px !important;
.el-submenu__icon-arrow {
right: 15px;
}
}
>>> .nest-menu {
.el-menu-item {
padding-left: 25px !important;
padding-right: 20px !important;
min-width: unset;
}
}
}
.quota-space {
font-size: 14px;
height: 32px;
line-height: 32px;
>>> .el-progress-bar {
padding-right: 40px;
margin-left: 20px;
}
>>> .el-progress__text {
margin-left: 20px;
font-size: 12px !important;
}
.collapse {
>>> .el-progress-bar {
padding-right: 4px;
margin-left: 2px;
}
>>> .el-progress__text {
margin-left: 5px;
display: block;
}
}
}
.webdav {
.normal {
font-size: 1.5rem;
height: 46px;
display: flex;
line-height: 46px;
padding-left: 20px;
.webdav-icon {
margin-top: 12px;
margin-right: 0 !important;
}
.copy-btn {
margin-top: 10px;
margin-right: 0 !important;
margin-left: 16px;
}
.copy-btn:hover {
cursor: pointer;
}
.wedav-text {
font-size: 12px;
margin-left: 20px;
cursor: default;
}
}
.collapse {
padding-left: 15px;
.wedav-text {
display: none;
}
.copy-btn {
display: none;
}
}
}
.drop-background-class {
background-color: #409EFFD1;
border-radius: 4px;
}
.form-tag-color {
display: flex;
.svg-icon {
font-size: 36px;
margin-right: 20px;
}
}
</style>