git-proxy / src /views /RepoView.vue
github-actions[bot]
Update from GitHub Actions
15ff6c7
raw
history blame
6.94 kB
<script setup lang="ts">
import { type TableProps } from 'tdesign-vue-next';
import { computed, ref, watch, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { MessagePlugin } from 'tdesign-vue-next';
import { useAccountStore } from '../stores/accountStorage';
import { repoApi, type RepoContent, type Account } from '../services/repoApi';
import { FolderIcon, FileIcon, CodeIcon, ImageIcon } from 'tdesign-icons-vue-next';
import RepoHeader from '../components/RepoHeader.vue';
const router = useRouter();
const route = useRoute();
// Local state
const loading = ref(false);
const allFiles = ref<RepoContent[]>([]);
const selectedAccount = ref<number>(0);
const currentPath = ref('');
const store = useAccountStore();
// Sort files with directories first then files
const sortedFiles = computed(() => {
// First separate directories and files
const dirs = allFiles.value.filter(item => item.type === 'dir');
const files = allFiles.value.filter(item => item.type === 'file');
// Sort directories and files alphabetically by name
dirs.sort((a, b) => a.name.localeCompare(b.name));
files.sort((a, b) => a.name.localeCompare(b.name));
// Combine with directories first
return [...dirs, ...files];
});
// Helper function to determine file icon based on file extension
const getFileIcon = (filename: string) => {
const extension = filename.split('.').pop()?.toLowerCase();
if (!extension) return FileIcon;
const codeExtensions = ['js', 'ts', 'py', 'java', 'c', 'cpp', 'cs', 'go', 'php', 'html', 'css', 'vue', 'jsx', 'tsx'];
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp'];
const textExtensions = ['txt', 'md', 'json', 'xml', 'csv', 'yml', 'yaml'];
if (codeExtensions.includes(extension)) return CodeIcon;
if (imageExtensions.includes(extension)) return ImageIcon;
// if (textExtensions.includes(extension)) return TextIcon;
// if (extension === 'pdf') return PdfIcon;
return FileIcon;
};
const columns = ref<TableProps['columns']>([
{
colKey: 'name',
title: '文件名',
width: 300,
cell: (h, { row }) => {
const IconComponent = row.type === 'dir' ? FolderIcon : getFileIcon(row.name);
return h('div', { class: 'flex items-center gap-2' }, [
h(IconComponent, {
class: row.type === 'dir' ? 'text-blue-500' : 'text-gray-600',
style: { fontSize: '1.25rem' }
}),
h('span', row.name)
]);
}
},
{ colKey: 'type', title: '类型', width: 100 },
{
colKey: 'size',
title: '大小',
width: 100,
cell: (h, { row }) => {
if (row.type === 'dir') return '-';
// Format file size
const size = Number(row.size);
if (size < 1024) return `${size} B`;
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
}
},
{ colKey: 'sha', title: 'SHA', width: 200 }
]);
const handleRowClick = (e: any) => {
const item: RepoContent = e.row;
if (item.type === 'dir') {
currentPath.value = item.path;
fetchRepo();
} else if (item.type === 'file') {
router.push({
path: '/content',
query: {
id: selectedAccount.value,
path: item.path
}
});
}
};
const fetchRepo = async () => {
if (!selectedAccount.value) return;
loading.value = true;
try {
const account = store.accounts.find(acc => acc.id === selectedAccount.value);
if (!account) {
MessagePlugin.error(`未找到账户信息${selectedAccount.value}`);
return;
}
const result = await repoApi.getContents(account, currentPath.value);
allFiles.value = Array.isArray(result) ? result : [result];
} catch (error) {
MessagePlugin.error('获取仓库内容失败');
} finally {
loading.value = false;
}
};
const handleBreadcrumbClick = (path: string) => {
currentPath.value = path;
fetchRepo();
};
const handleRootClick = () => {
currentPath.value = '';
fetchRepo();
};
const handleAccountChange = (val: number) => {
selectedAccount.value = val;
currentPath.value = ''; // Reset path
router.push({
path: '/repo',
query: {
id: selectedAccount.value,
}
});
}
// Watch route query params and sync with local state
watch(() => route.query, async (query) => {
// Only respond to query changes when on the repo route
if (route.path !== '/repo') return;
await store.fetchAccounts();
const { id, path } = query;
if (id) {
selectedAccount.value = Number(id);
currentPath.value = path ? path as string : '';
}
else {
if (store.accounts.length > 0) {
selectedAccount.value = store.accounts[0].id;
}
currentPath.value = '';
}
// Fetch data after updating both account and path
await fetchRepo();
}, { immediate: true });
onMounted(async () => {
});
const handleNewFile = () => {
if (!selectedAccount.value) {
MessagePlugin.error('请先选择仓库');
return;
}
let path = currentPath.value;
router.push({
path: '/content',
query: {
id: selectedAccount.value,
path: path,
newFile: 1,
}
});
};
</script>
<template>
<div class="repository-browser w-full flex flex-col p-3 md:p-5 gap-3 md:gap-5 bg-gray-50">
<RepoHeader :selected-account="selectedAccount" :current-path="currentPath" :accounts="store.accounts"
@path-click="handleBreadcrumbClick" @root-click="handleRootClick"
@update:selected-account="handleAccountChange">
<div class="flex gap-2 items-center">
<t-button theme="primary" @click="handleNewFile" class="flex items-center gap-1">
<template #icon>
<FileIcon />
</template>
新建文件
</t-button>
</div>
</RepoHeader>
<div class="content-section flex-1 bg-white rounded-lg shadow-sm">
<t-table :data="sortedFiles" :loading="loading" :columns="columns" row-key="sha" hover stripe size="medium"
class="min-w-full" @row-click="handleRowClick" row-class-name="hover:bg-blue-50 cursor-pointer" />
</div>
</div>
</template>
<style scoped>
.repository-browser {
min-height: 600px;
}
/* Add subtle hover animations */
.t-table__row:hover {
transition: all 0.2s ease;
}
/* Additional responsive styles */
@media (max-width: 640px) {
.t-table {
font-size: 0.875rem;
}
}
</style>