feat: 添加直链功能

- 新增直链相关 API 和工具函数
- 实现直链对话框组件
- 在文件上下文菜单中添加直链相关选项
- 优化文件上下文菜单样式和布局
This commit is contained in:
jmal 2025-06-27 17:22:33 +08:00
parent 0d07b963b7
commit b8b0471f48
10 changed files with 238 additions and 15 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

40
src/api/direct-file.js Normal file
View File

@ -0,0 +1,40 @@
import request from '@/utils/request'
export default {
/**
* 创建直链
* @param params
* @returns {*}
*/
createDirectLink: function(params) {
return request({
url: 'direct-link/create',
method: 'post',
params
})
},
/**
* 重置直链
* @param params
* @returns {*}
*/
resetDirectLink: function(params) {
return request({
url: 'direct-link/reset',
method: 'put',
params
})
},
/**
* 重置直链
* @param params
* @returns {*}
*/
resetAllDirectLink: function(params) {
return request({
url: 'direct-link/reset-all',
method: 'delete',
params
})
},
}

View File

@ -0,0 +1,118 @@
<template>
<div>
<!--直链-->
<el-dialog :title="'直链 - ' + filename" :style="{ fontWeight: 600 }" :visible.sync="dialogVisible" @close="dialogClose">
<div>
<div class="direct-file-content">
<el-input placeholder="" v-model="directUrl" readonly></el-input>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button round size="small" type="danger" @click="resetAllDirectLink" :loading="resetAllLoading">重置所有直链</el-button>
<el-button round size="small" @click="resetDirectLink" :loading="resetLoading">重置直链</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import directFileApi from '@/api/direct-file'
import fileConfig from '@/utils/file-config'
import { copyText } from '@/utils/copy-text'
export default {
name: "DirectLinkDialog",
components: {
},
props: {
status: {
type: Boolean,
default: false
},
file: {
type: Object,
default: function () {
return {}
}
},
},
data() {
return {
dialogVisible: false,
filename: '',
directUrl: '',
resetLoading: false,
resetAllLoading: false
}
},
computed: {
},
watch: {
status(visible) {
if (visible) {
this.dialogVisible = true
this.filename = this.file.name
}
}
},
methods: {
dialogClose() {
this.$emit('update:status', this.dialogVisible)
},
getDirectLink(file, isCopy) {
directFileApi.createDirectLink({ fileId: file.id }).then(res => {
this.directUrl = fileConfig.directFileUrl(res.data, file)
if (isCopy) {
copyText(this.directUrl)
}
})
},
resetDirectLink() {
this.resetLoading = true
this.$confirm('确定要重置该直链吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
directFileApi.resetDirectLink({ fileId: this.file.id }).then(res => {
this.directUrl = fileConfig.directFileUrl(res.data, this.file)
this.resetLoading = false
this.$message.success('重置成功')
}).catch(() => {
this.resetLoading = false
}
)
}).catch(() => {
this.resetLoading = false
}
)
},
resetAllDirectLink() {
this.resetAllLoading = true
this.$confirm('确定要重置所有直链吗? 此用户下的所有直链都将失效', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
directFileApi.resetAllDirectLink({ fileId: this.file.id }).then(res => {
this.directUrl = fileConfig.directFileUrl(res.data, this.file)
this.resetAllLoading = false
this.$message.success('重置所有直链成功')
}).catch(() => {
this.resetAllLoading = false
}
)
}).catch(() => {
this.resetAllLoading = false
}
)
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -5,7 +5,7 @@
import { Icon } from 'ant-design-vue'
const IconFont = Icon.createFromIconfontCN({
scriptUrl: document.location.protocol + '/resource/font_2024484_3m5puxhkyzz.js'
scriptUrl: document.location.protocol + '/resource/font_2024484_0fh6xrzocbtk.js'
})
// 把此段代码贴到iconfont项目页面的控制台中, 以获取图标列表
@ -15,7 +15,7 @@ const IconFont = Icon.createFromIconfontCN({
// console.log(iconNames.slice(80,160))
export const iconFonts = {
list: ['surge', 'dwg', 'epub', 'huanyuan', 'huishouzhan', 'glb', 'gengxinjindu', 'duigou', 'bars-solid', 'sort-amount-down-solid', 'sort-amount-up-alt-solid', 'tag2', 'guazai', 'duplicate', 'play1', "qrcode", "arrow1_left", "wulianwang-","zuji","disk-drive", "menu-fuzhi", "iso", "zuijinhuoyue","key", "shouquan", "sort", "grid", "list", "emoji-a", "emoji-bizui", "emoji-baiyan", "emoji-aixin", "emoji-dajing", "emoji-ziya", "emoji-daxiao", "emoji-esi", "emoji-fadai", "emoji-fankun", "emoji-ganga", "emoji-fennu", "emoji-hanyan", "emoji-jingkong", "emoji-haochi", "emoji-emo", "emoji-jingsong", "emoji-jingya", "emoji-kaixin", "emoji-lengku", "emoji-danao", "emoji-liukoushui", "emoji-liulei", "emoji-mengbi", "mianwubiaoqing", "nanguo", "shuizhuo", "taoyan", "tanchi", "siliao", "tiaopi", "xiaochulei", "wuliao", "xingxingyan", "xieyan", "xiasi", "xiaolian", "ku", "shengqi", "yousiliao", "en", "bushufu", "bianbian", "fankun1", "feiwen", "ganmao", "huaixiao", "liuhan", "outu", "keshui", "renzhe", "santiaoxian", "guaiwu", "shoushang", "tianshi", "shuai", "xianwen", "xiaodiaodaya", "xiong", "yiwen", "yun", "liubixie", "shimo", "dianzan", "biti", "fendou", "huqi", "heng", "kulou", "leng", "taoyan1", "shuixing", "aini", "aixin1", "zhadan", "xinsui",
list: ['a-editlink_nor', 'link', 'copy-link', 'surge', 'dwg', 'epub', 'huanyuan', 'huishouzhan', 'glb', 'gengxinjindu', 'duigou', 'bars-solid', 'sort-amount-down-solid', 'sort-amount-up-alt-solid', 'tag2', 'guazai', 'duplicate', 'play1', "qrcode", "arrow1_left", "wulianwang-","zuji","disk-drive", "menu-fuzhi", "iso", "zuijinhuoyue","key", "shouquan", "sort", "grid", "list", "emoji-a", "emoji-bizui", "emoji-baiyan", "emoji-aixin", "emoji-dajing", "emoji-ziya", "emoji-daxiao", "emoji-esi", "emoji-fadai", "emoji-fankun", "emoji-ganga", "emoji-fennu", "emoji-hanyan", "emoji-jingkong", "emoji-haochi", "emoji-emo", "emoji-jingsong", "emoji-jingya", "emoji-kaixin", "emoji-lengku", "emoji-danao", "emoji-liukoushui", "emoji-liulei", "emoji-mengbi", "mianwubiaoqing", "nanguo", "shuizhuo", "taoyan", "tanchi", "siliao", "tiaopi", "xiaochulei", "wuliao", "xingxingyan", "xieyan", "xiasi", "xiaolian", "ku", "shengqi", "yousiliao", "en", "bushufu", "bianbian", "fankun1", "feiwen", "ganmao", "huaixiao", "liuhan", "outu", "keshui", "renzhe", "santiaoxian", "guaiwu", "shoushang", "tianshi", "shuai", "xianwen", "xiaodiaodaya", "xiong", "yiwen", "yun", "liubixie", "shimo", "dianzan", "biti", "fendou", "huqi", "heng", "kulou", "leng", "taoyan1", "shuixing", "aini", "aixin1", "zhadan", "xinsui",
"maren", "zhutou", "qie", "youling", "apple-fill", "attachment", "android-fill", "apple", "download", "arrow-up-circle", "arrow-right-circle", "arrow-down", "ashbin", "add", "file_music", "menu", "role", "jiaoseguanli", "yemianshezhi", "drop_down", "yuandian", "tag", "service-directory", "fabu", "wailian", "bianji-", "WEBSITE", "wangzhanguanli", "Up", "up", "fenlei", "guanlizhuanjiawenzhang", "pifu", "tuichudenglu", "shezhi", "wangzhan", "huaban", "yunshezhi", "UserSettings", "blogger", "biaoqian", "myself", "guidang", "quxiaoyulan1-copy", "yulan-copy", "quxiaoyulan1", "yulan"]
}

View File

@ -775,6 +775,7 @@
@onSuccess="shareSuccess"
@onCancelShare="onCancelShare"
></share-dialog>
<direct-link-dialog ref="directLinkDialog" :status.sync="directLinkDialogVisible" :file="rowContextData"/>
<el-dialog
class="new-text-file-dialog"
:title="newCreateFileDialogTitle"
@ -858,6 +859,7 @@
import api from '@/api/file-api'
import BreadcrumbFilePath from '@/components/Breadcrumb/BreadcrumbFilePath'
import ButtonUpload from '@/components/button/ButtonUpload'
import DirectLinkDialog from '@/components/DirectLinkDialog/index.vue'
import EmptyFile from '@/components/EmptyFile'
import FileTree from '@/components/FileTree'
@ -881,7 +883,7 @@ import store from '@/store'
import { getElementToPageLeft } from '@/utils/dom'
import fileConfig from '@/utils/file-config'
import { fileOperations } from '@/utils/file-operations'
import { directLinkSubMenus, fileOperations } from '@/utils/file-operations'
import { suffix } from '@/utils/file-type'
import { formatExif, formatVideo } from '@/utils/media'
import { formatSize, formatTime } from '@/utils/number'
@ -895,6 +897,7 @@ import { mapGetters, mapState } from 'vuex'
export default {
name: 'ShowFile',
components: {
DirectLinkDialog,
FileContextmenu,
FileClipboard,
DialogFileList,
@ -982,6 +985,7 @@ export default {
default: function() {
return [
fileOperations.open,
{ iconClass: 'link', label: '直链', operation: 'directLink', child: directLinkSubMenus },
fileOperations.download,
{ iconClass: 'share', label: '分享...', operation: 'share' },
{ iconClass: 'tag', label: '标签...' , operation: 'tag'},
@ -1122,6 +1126,7 @@ export default {
allChecked: false,
summaries: '',
shareDialogVisible: false,
directLinkDialogVisible: false,
shareDialogObject: {},
tagDialogVisible: false,
tagDialogObjectList: [],
@ -3542,11 +3547,11 @@ export default {
}
}
if (row.ossFolder) {
// 删除分享选项
let index = this.getIndexOfFileContextMenus('share')
if (index > -1) {
this.menus.splice(index, 1)
}
const reservations = ['open', 'tag']
// 删除this.menus中不要的菜单, 仅保留reservations中的菜单
this.menus = this.menus.filter(item =>
reservations.includes(item.operation)
)
}
this.setMenusCopyDownLoadLinks(row)
}
@ -3656,20 +3661,20 @@ export default {
e.clientX = event.clientX + 50
e.clientY = event.clientY + 2
e.top = event.clientY
e.left = event.clientX
e.left = event.clientX + 2
const dividerSize = this.menus.filter(item => item.divider).length
const containerWidth = 180 + 5
const containerHeight = (this.menus.length - dividerSize) * 35 + (dividerSize * 10) + 21
const containerWidth = 180 + 10
const containerHeight = (this.menus.length - dividerSize) * 35 + (dividerSize * 11) + 18 + 10
const distanceToBottom = document.documentElement.clientHeight - event.clientY
const distanceToRight = document.documentElement.clientWidth - event.clientX
if (distanceToBottom < containerHeight) {
e.top = event.clientY - containerHeight
e.top = event.clientY - (containerHeight - distanceToBottom)
}
if (distanceToRight < containerWidth) {
e.left = event.clientX - containerWidth
e.left = event.clientX - containerWidth + 8
}
@ -3896,6 +3901,15 @@ export default {
// 返回原处
this.restoreFile()
break
case 'manageDirectLink':
// 管理直链
this.directLinkDialogVisible = true
this.$refs.directLinkDialog.getDirectLink(this.rowContextData)
break
case 'copyDirectLink':
// 复制直链
this.$refs.directLinkDialog.getDirectLink(this.rowContextData, true)
break
}
this.$refs.contextShow.hideMenu()
},
@ -4769,6 +4783,9 @@ export default {
if (this.shareDialogVisible) {
return true
}
if (this.directLinkDialogVisible) {
return true
}
if (this.tagDialogVisible) {
return true
}

View File

@ -124,7 +124,7 @@ export default {
padding: 0 10px;
display: flex;
align-items: center;
justify-content: space-between;
justify-content: flex-start;
>>> .svg-icon {
margin-right: 10px;

36
src/utils/copy-text.js Normal file
View File

@ -0,0 +1,36 @@
export async function copyText(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
try {
await navigator.clipboard.writeText(text);
this.$message.success('复制成功');
} catch (err) {
fallbackCopyText(text);
}
} else {
fallbackCopyText(text);
}
}
export function fallbackCopyText(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', ''); // 防止手机端弹出键盘
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
// 兼容 Safari 和 Chrome 的选中文本方式
textarea.select();
textarea.setSelectionRange(0, textarea.value.length); // 额外设置范围,兼容 Safari
try {
const successful = document.execCommand('copy');
if (successful) {
Vue.prototype.$message.success('复制成功');
} else {
Vue.prototype.$message.error('复制失败');
}
} catch (err) {
console.error('execCommand 复制失败:', err);
Vue.prototype.$message.error('复制失败');
}
document.body.removeChild(textarea);
}

View File

@ -143,4 +143,11 @@ export default {
}
return null;
},
directFileUrl: function(mark, file) {
if (file.isFolder) {
return window.location.origin + `${this.baseUrl}/direct-file/${mark}/${file.name}.zip`
} else {
return window.location.origin + `${this.baseUrl}/direct-file/${mark}/${file.name}`
}
}
}

View File

@ -14,6 +14,11 @@ export const fileOperations = {
}
}
export const directLinkSubMenus = [
{ label: '管理...', operation: 'manageDirectLink', iconClass: 'a-editlink_nor'},
{ label: '复制', operation: 'copyDirectLink', iconClass: 'copy-link'},
]
export const fileArrangements = [
{ label: '名称', operation: 'orderName', orderProp: 'name'},
{ label: '大小', operation: 'orderSize', orderProp: 'size'},