2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 06:47:20 +08:00

feat: 优化文件管理支持内联新建

This commit is contained in:
2026-01-13 03:32:17 +08:00
parent f1eccd6e7e
commit 7d9bb1b191
2 changed files with 129 additions and 40 deletions

View File

@@ -70,6 +70,12 @@ const inlineRenameItem = ref<any>(null)
const inlineRenameName = ref('')
const inlineRenameInputRef = ref<HTMLInputElement | null>(null)
// 内联新建状态
const inlineCreateActive = ref(false)
const inlineCreateIsDir = ref(false)
const inlineCreateName = ref('')
const inlineCreateInputRef = ref<HTMLInputElement | null>(null)
// 设置内联重命名输入框 ref 的函数
const setInlineRenameRef = (el: any) => {
if (el) {
@@ -77,6 +83,78 @@ const setInlineRenameRef = (el: any) => {
}
}
// 设置内联新建输入框 ref 的函数
const setInlineCreateRef = (el: any) => {
if (el) {
inlineCreateInputRef.value = el
}
}
// 启动内联新建
const startInlineCreate = (isDir: boolean) => {
// 取消正在进行的重命名
cancelInlineRename()
inlineCreateActive.value = true
inlineCreateIsDir.value = isDir
inlineCreateName.value = ''
// 等待 DOM 更新后聚焦输入框
nextTick(() => {
if (inlineCreateInputRef.value) {
inlineCreateInputRef.value.focus()
}
})
}
// 取消内联新建
const cancelInlineCreate = () => {
inlineCreateActive.value = false
inlineCreateIsDir.value = false
inlineCreateName.value = ''
}
// 提交内联新建
const submitInlineCreate = () => {
if (!inlineCreateActive.value) return
const name = inlineCreateName.value.trim()
// 如果名称为空,直接取消
if (!name) {
cancelInlineCreate()
return
}
// 验证名称
if (!checkName(name)) {
window.$message.error($gettext('Invalid name'))
return
}
const fullPath = path.value + '/' + name
useRequest(file.create(fullPath, inlineCreateIsDir.value))
.onSuccess(() => {
window.$bus.emit('file:refresh')
window.$message.success($gettext('Created successfully'))
})
.onComplete(() => {
cancelInlineCreate()
})
}
// 处理内联新建键盘事件
const handleInlineCreateKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
submitInlineCreate()
} else if (event.key === 'Escape') {
event.preventDefault()
cancelInlineCreate()
}
}
const unCompressModal = ref(false)
const unCompressModel = ref({
path: '',
@@ -1136,6 +1214,7 @@ onMounted(() => {
window.$bus.on('file:refresh', refresh)
window.$bus.on('file:keyboard-pause', pauseKeyboard)
window.$bus.on('file:keyboard-resume', resumeKeyboard)
window.$bus.on('file:inline-create', startInlineCreate)
// 添加全局鼠标事件监听
document.addEventListener('mousemove', onSelectionMove)
@@ -1157,6 +1236,7 @@ onUnmounted(() => {
window.$bus.off('file:refresh', refresh)
window.$bus.off('file:keyboard-pause', pauseKeyboard)
window.$bus.off('file:keyboard-resume', resumeKeyboard)
window.$bus.off('file:inline-create', startInlineCreate)
document.removeEventListener('mousemove', onSelectionMove)
document.removeEventListener('mouseup', onSelectionEnd)
document.removeEventListener('keydown', handleKeyDown)
@@ -1206,6 +1286,53 @@ onUnmounted(() => {
<div class="list-col col-actions">{{ $gettext('Actions') }}</div>
</div>
<!-- 内联新建项 -->
<div v-if="inlineCreateActive" class="file-item inline-create-item">
<!-- 图标视图 -->
<template v-if="fileStore.viewType === 'grid'">
<div class="icon-wrapper">
<the-icon
:icon="inlineCreateIsDir ? 'mdi:folder' : 'mdi:file-document-outline'"
:size="48"
:color="inlineCreateIsDir ? themeVars.warningColor : themeVars.textColor3"
/>
</div>
<input
:ref="setInlineCreateRef"
v-model="inlineCreateName"
class="inline-rename-input"
:placeholder="inlineCreateIsDir ? $gettext('Folder name') : $gettext('File name')"
@blur="submitInlineCreate"
@keydown="handleInlineCreateKeydown"
@click.stop
/>
</template>
<!-- 列表视图 -->
<template v-else>
<div class="list-col col-name">
<the-icon
:icon="inlineCreateIsDir ? 'mdi:folder' : 'mdi:file-document-outline'"
:size="20"
:color="inlineCreateIsDir ? themeVars.warningColor : themeVars.textColor3"
/>
<input
:ref="setInlineCreateRef"
v-model="inlineCreateName"
class="inline-rename-input list-mode"
:placeholder="inlineCreateIsDir ? $gettext('Folder name') : $gettext('File name')"
@blur="submitInlineCreate"
@keydown="handleInlineCreateKeydown"
@click.stop
/>
</div>
<div class="list-col col-size">-</div>
<div class="list-col col-mode">-</div>
<div class="list-col col-owner">-</div>
<div class="list-col col-modify">-</div>
<div class="list-col col-actions">-</div>
</template>
</div>
<!-- 文件/文件夹列表 -->
<div
v-for="item in data"

View File

@@ -21,11 +21,6 @@ const permission = defineModel<boolean>('permission', { type: Boolean, required:
const terminalModal = ref(false)
const upload = ref(false)
const create = ref(false)
const createModel = ref({
dir: false,
path: ''
})
const download = ref(false)
const downloadModel = ref({
path: '',
@@ -33,23 +28,8 @@ const downloadModel = ref({
})
const showCreate = (value: string) => {
createModel.value.dir = value !== 'file'
createModel.value.path = ''
create.value = true
}
const handleCreate = () => {
if (!checkName(createModel.value.path)) {
window.$message.error($gettext('Invalid name'))
return
}
const fullPath = path.value + '/' + createModel.value.path
useRequest(file.create(fullPath, createModel.value.dir)).onSuccess(() => {
create.value = false
window.$bus.emit('file:refresh')
window.$message.success($gettext('Created successfully'))
})
// 触发内联新建事件
window.$bus.emit('file:inline-create', value !== 'file')
}
const handleDownload = () => {
@@ -299,24 +279,6 @@ const handleSortSelect = (key: string) => {
</n-flex>
</div>
</n-flex>
<n-modal
v-model:show="create"
preset="card"
:title="$gettext('New')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-space vertical>
<n-form :model="createModel">
<n-form-item :label="$gettext('Name')">
<n-input v-model:value="createModel.path" />
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">{{ $gettext('Submit') }}</n-button>
</n-space>
</n-modal>
<n-modal
v-model:show="download"
preset="card"