Files
qiming/qiming-mobile/subpackages/pages/chat-conversation-component/components/file-tree/file-tree-node.uvue

236 lines
5.0 KiB
Plaintext

<template>
<view
v-if="node.type === 'folder'"
class="folder-item"
:style="`margin-left: ${level * 16}rpx`"
>
<view
class="folder-header"
@tap="handleToggleFolder"
>
<!-- 文件夹展开/收起图标 -->
<text class="folder-caret iconfont icon-a-Chevronright"
:class="{ expanded: isExpanded }"></text>
<view class="folder-name">{{ node.name }}</view>
</view>
<view v-if="isExpanded && node.children && !!node.children.length" class="file-list">
<file-tree-node
v-for="child in node.children"
:key="child.id"
:node="child"
:level="level + 1"
:expanded-folders="expandedFolders"
:selected-file-id="selectedFileId"
@toggle-folder="handleChildToggleFolder"
@select-file="handleChildSelectFile"
/>
</view>
</view>
<view
v-else
class="file-item"
:class="{ 'active-file': isSelected, 'hidden-file': node.name.startsWith('.') }"
:style="`margin-left: ${level * 8}rpx`"
@tap.stop="handleSelectFile"
>
<view class="file-icon-wrapper">
<image
class="file-icon"
:src="fileIcon"
mode="aspectFit"
alt=""
@error="handleImageError"
/>
</view>
<view class="file-name">{{ node.name }}</view>
</view>
</template>
<script setup lang="uts">
import { computed } from 'vue'
import type { FileNode } from '@/types/interfaces/agent'
import FileTreeNode from './file-tree-node.uvue'
import { getFileIcon } from './fileIconHelper.uts'
import iconDefault from '@/static/filetree/icon_default.svg'
interface Props {
node: FileNode
level: number
expandedFolders: Set<string>
selectedFileId: string
}
const props = defineProps<Props>()
const emit = defineEmits<{
toggleFolder: [folderId: string]
selectFile: [fileNode: FileNode]
}>()
/**
* 是否展开
*/
const isExpanded = computed(() => {
return props.expandedFolders.has(props.node.id)
})
/**
* 是否选中
*/
const isSelected = computed(() => {
return props.selectedFileId === props.node.id
})
/**
* 文件图标路径
*/
const fileIcon = computed<string>(() => {
if (props.node?.type === 'file' || props.node?.isLink) {
return getFileIcon(props.node.name)
}
return iconDefault
})
/**
* 图片加载错误处理
*/
const handleImageError = (e: any) => {
console.error('Image load error:', fileIcon.value, e)
}
/**
* 切换文件夹展开/收起
*/
const handleToggleFolder = () => {
if (props.node.type === 'folder') {
emit('toggleFolder', props.node.id)
}
}
/**
* 选择文件
*/
const handleSelectFile = () => {
if (props.node.type === 'file' && !props.node.name.startsWith('.')) {
emit('selectFile', props.node)
}
}
/**
* 处理子组件的切换文件夹事件
*/
const handleChildToggleFolder = (folderId: string) => {
emit('toggleFolder', folderId)
}
/**
* 处理子组件的选择文件事件
*/
const handleChildSelectFile = (fileNode: FileNode) => {
emit('selectFile', fileNode)
}
</script>
<style scoped lang="scss">
@import '@/static/iconfont/iconfont.css';
.folder-item {
padding: 0rpx 0 0 32rpx;
white-space: nowrap;
display: block;
.folder-header {
display: flex;
align-items: center;
flex-direction: row;
padding: 16rpx 0;
cursor: pointer;
user-select: none;
min-height: 40rpx;
white-space: nowrap;
.folder-caret {
font-size: 18rpx;
color: #5e5e5b;
margin-right: 12rpx;
transition: transform 0.2s;
flex-shrink: 0;
// display: inline-block;
// width: 18rpx;
// text-align: center;
// line-height: 1;
&.expanded {
transform: rotate(90deg);
}
}
// .folder-icon {
// font-size: 28rpx;
// color: #8b9dc3;
// margin-right: 6rpx;
// display: inline-block;
// line-height: 1;
// }
.folder-name {
font-size: 26rpx;
color: #5e5e5b;
line-height: 1.4;
white-space: nowrap;
flex-shrink: 0;
}
}
.file-list {
margin-left: 0;
}
}
.file-item {
display: flex;
align-items: center;
flex-direction: row;
padding: 16rpx 32rpx;
color: #5e5e5b;
cursor: pointer;
user-select: none;
min-height: 40rpx;
white-space: nowrap;
&.active-file {
background-color: #f0f0f0;
border-radius: 4rpx;
}
&.hidden-file {
opacity: 0.6;
}
.file-icon-wrapper {
width: 28rpx;
height: 28rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12rpx;
flex-shrink: 0;
.file-icon {
width: 28rpx;
height: 28rpx;
display: block;
color: #5e5e5b;
}
}
.file-name {
font-size: 26rpx;
color: #5e5e5b;
line-height: 1.4;
white-space: nowrap;
flex-shrink: 0;
}
}
</style>