Files
GitCodeStatic/web/static/app.js
2025-12-31 16:23:40 +08:00

523 lines
20 KiB
JavaScript

const { createApp } = Vue;
const { ElMessage, ElMessageBox } = ElementPlus;
const API_BASE = '/api/v1';
createApp({
data() {
return {
activeTab: 'repos',
repos: [],
reposLoading: false,
statsLoading: false,
addRepoVisible: false,
switchBranchVisible: false,
addRepoForm: {
urls: '',
branch: 'main',
username: '',
password: ''
},
switchBranchForm: {
branch: '',
repoId: null,
repoUrl: '',
currentBranch: ''
},
branches: [],
branchesLoading: false,
statsForm: {
repo_id: null,
branch: 'main',
constraint_type: 'commit_limit',
from: '',
to: '',
limit: 100
},
statsDateRange: null,
statsFormBranches: [],
statsFormBranchesLoading: false,
selectedStatsResult: null,
tasks: [],
tasksLoading: false,
caches: [],
cachesLoading: false
};
},
mounted() {
this.loadRepos();
this.loadCaches();
},
watch: {
activeTab(newTab) {
if (newTab === 'tasks') {
this.loadTasks();
} else if (newTab === 'caches') {
this.loadCaches();
}
}
},
methods: {
async loadRepos() {
this.reposLoading = true;
try {
const response = await axios.get(`${API_BASE}/repos`);
if (response.data.code === 0) {
this.repos = response.data.data.repositories || [];
} else {
ElMessage.error(response.data.message || '加载仓库列表失败');
}
} catch (error) {
ElMessage.error('网络请求失败: ' + error.message);
} finally {
this.reposLoading = false;
}
},
showAddRepoDialog() {
this.addRepoForm = {
urls: '',
branch: 'main',
username: '',
password: ''
};
this.addRepoVisible = true;
},
async onRepoChange() {
this.statsForm.branch = 'main';
this.statsFormBranches = [];
if (this.statsForm.repo_id) {
await this.loadStatsFormBranches();
}
},
async loadStatsFormBranches() {
if (!this.statsForm.repo_id) return;
this.statsFormBranchesLoading = true;
try {
const response = await axios.get(`${API_BASE}/repos/${this.statsForm.repo_id}/branches`);
if (response.data.code === 0) {
this.statsFormBranches = response.data.data.branches || ['main'];
// 如果当前分支不在列表中,添加进去
if (this.statsForm.branch && !this.statsFormBranches.includes(this.statsForm.branch)) {
this.statsFormBranches.unshift(this.statsForm.branch);
}
} else {
this.statsFormBranches = ['main', 'master', 'develop'];
}
} catch (error) {
this.statsFormBranches = ['main', 'master', 'develop'];
} finally {
this.statsFormBranchesLoading = false;
}
},
getRepoDisplayName(repo) {
const url = repo.url;
const parts = url.split('/');
return parts[parts.length - 1].replace('.git', '');
},
async addRepos() {
const urls = this.addRepoForm.urls.split('\n')
.map(u => u.trim())
.filter(u => u);
if (urls.length === 0) {
ElMessage.warning('请输入至少一个仓库URL');
return;
}
const repos = urls.map(url => ({
url,
branch: this.addRepoForm.branch || 'main'
}));
const requestData = { repos };
// 如果提供了认证信息
if (this.addRepoForm.username && this.addRepoForm.password) {
requestData.username = this.addRepoForm.username;
requestData.password = this.addRepoForm.password;
}
try {
const response = await axios.post(`${API_BASE}/repos/batch`, requestData);
if (response.data.code === 0) {
const result = response.data.data;
ElMessage.success(`成功添加 ${result.success_count} 个仓库`);
if (result.failure_count > 0) {
ElMessage.warning(`${result.failure_count} 个仓库添加失败`);
}
this.addRepoVisible = false;
this.loadRepos();
} else {
ElMessage.error(response.data.message || '添加仓库失败');
}
} catch (error) {
ElMessage.error('网络请求失败: ' + error.message);
}
},
async switchBranch(repo) {
this.switchBranchForm = {
branch: '',
repoId: repo.id,
repoUrl: repo.url,
currentBranch: repo.current_branch || '未知'
};
this.branches = [];
this.switchBranchVisible = true;
// 获取分支列表
await this.loadBranches(repo.id);
},
async loadBranches(repoId) {
this.branchesLoading = true;
try {
const response = await axios.get(`${API_BASE}/repos/${repoId}/branches`);
if (response.data.code === 0) {
this.branches = response.data.data.branches || [];
} else {
ElMessage.warning('获取分支列表失败: ' + (response.data.message || ''));
this.branches = [];
}
} catch (error) {
console.warn('无法获取分支列表:', error.message);
this.branches = [];
} finally {
this.branchesLoading = false;
}
},
async confirmSwitchBranch() {
if (!this.switchBranchForm.branch) {
ElMessage.warning('请输入分支名称');
return;
}
try {
const response = await axios.post(
`${API_BASE}/repos/${this.switchBranchForm.repoId}/switch-branch`,
{ branch: this.switchBranchForm.branch }
);
if (response.data.code === 0) {
ElMessage.success('切换分支任务已提交');
this.switchBranchVisible = false;
this.loadRepos();
} else {
ElMessage.error(response.data.message || '切换分支失败');
}
} catch (error) {
ElMessage.error('网络请求失败: ' + error.message);
}
},
async updateRepo(repoId) {
try {
const response = await axios.post(`${API_BASE}/repos/${repoId}/update`);
if (response.data.code === 0) {
ElMessage.success('更新任务已提交');
this.loadRepos();
} else {
ElMessage.error(response.data.message || '更新仓库失败');
}
} catch (error) {
ElMessage.error('网络请求失败: ' + error.message);
}
},
async resetRepo(repoId) {
try {
await ElMessageBox.confirm('确定要重置该仓库吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
const response = await axios.post(`${API_BASE}/repos/${repoId}/reset`);
if (response.data.code === 0) {
ElMessage.success('重置任务已提交');
this.loadRepos();
} else {
ElMessage.error(response.data.message || '重置仓库失败');
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('网络请求失败: ' + error.message);
}
}
},
async deleteRepo(repoId) {
try {
await ElMessageBox.confirm('确定要删除该仓库吗?此操作不可恢复!', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
const response = await axios.delete(`${API_BASE}/repos/${repoId}`);
if (response.data.code === 0) {
ElMessage.success('删除成功');
this.loadRepos();
} else {
ElMessage.error(response.data.message || '删除仓库失败');
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('网络请求失败: ' + error.message);
}
}
},
async calculateStats() {
if (!this.statsForm.repo_id) {
ElMessage.warning('请选择仓库');
return;
}
if (!this.statsForm.branch) {
ElMessage.warning('请输入分支名称');
return;
}
const constraint = {
type: this.statsForm.constraint_type
};
if (this.statsForm.constraint_type === 'date_range') {
if (!this.statsDateRange || this.statsDateRange.length !== 2) {
ElMessage.warning('请选择日期范围');
return;
}
constraint.from = this.statsDateRange[0];
constraint.to = this.statsDateRange[1];
} else {
constraint.limit = this.statsForm.limit;
}
try {
const response = await axios.post(`${API_BASE}/stats/calculate`, {
repo_id: this.statsForm.repo_id,
branch: this.statsForm.branch,
constraint
});
if (response.data.code === 0) {
ElMessage.success('统计任务已提交,请稍后查看结果');
// 3秒后刷新缓存列表
setTimeout(() => {
this.loadCaches();
}, 3000);
} else {
ElMessage.error(response.data.message || '提交统计任务失败');
}
} catch (error) {
ElMessage.error('网络请求失败: ' + error.message);
}
},
async loadTasks() {
this.tasksLoading = true;
try {
const response = await axios.get(`${API_BASE}/tasks`);
if (response.data.code === 0) {
this.tasks = response.data.data.tasks || [];
} else {
ElMessage.error(response.data.message || '加载任务列表失败');
}
} catch (error) {
ElMessage.error('网络请求失败: ' + error.message);
} finally {
this.tasksLoading = false;
}
},
async loadCaches() {
this.cachesLoading = true;
try {
const response = await axios.get(`${API_BASE}/stats/caches`);
if (response.data.code === 0) {
this.caches = response.data.data.caches || [];
} else {
ElMessage.error(response.data.message || '加载统计缓存列表失败');
}
} catch (error) {
ElMessage.error('网络请求失败: ' + error.message);
} finally {
this.cachesLoading = false;
}
},
async clearAllCaches() {
try {
await ElMessageBox.confirm('确定要清空所有统计缓存吗?此操作不可恢复!', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
const response = await axios.delete(`${API_BASE}/stats/caches/clear`);
if (response.data.code === 0) {
ElMessage.success('所有统计缓存已清除');
this.loadCaches();
} else {
ElMessage.error(response.data.message || '清除缓存失败');
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('网络请求失败: ' + error.message);
}
}
},
async clearAllTasks() {
try {
await ElMessageBox.confirm('确定要清空所有任务记录吗?包括正在执行的任务!', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
const response = await axios.delete(`${API_BASE}/tasks/clear`);
if (response.data.code === 0) {
ElMessage.success('所有任务记录已清除');
this.loadTasks();
} else {
ElMessage.error(response.data.message || '清除任务失败');
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('网络请求失败: ' + error.message);
}
}
},
async clearCompletedTasks() {
try {
await ElMessageBox.confirm('确定要清除所有已完成的任务记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'info'
});
const response = await axios.delete(`${API_BASE}/tasks/clear-completed`);
if (response.data.code === 0) {
ElMessage.success('已完成的任务记录已清除');
this.loadTasks();
} else {
ElMessage.error(response.data.message || '清除已完成任务失败');
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('网络请求失败: ' + error.message);
}
}
},
async viewStatsCache(cache) {
this.statsLoading = true;
try {
const params = {
repo_id: cache.repo_id,
branch: cache.branch,
constraint_type: cache.constraint_type
};
// 根据缓存的constraint_value添加参数
if (cache.constraint_type === 'date_range' && cache.constraint_value) {
try {
const constraint = JSON.parse(cache.constraint_value);
if (constraint.from) params.from = constraint.from;
if (constraint.to) params.to = constraint.to;
} catch (e) {
console.error('Failed to parse constraint_value:', e);
}
} else if (cache.constraint_type === 'commit_limit' && cache.constraint_value) {
try {
const constraint = JSON.parse(cache.constraint_value);
if (constraint.limit) params.limit = constraint.limit;
} catch (e) {
params.limit = 100; // 默认值
}
}
const response = await axios.get(`${API_BASE}/stats/result`, { params });
if (response.data.code === 0) {
// 适配后端返回的数据结构
const data = response.data.data;
const stats = data.statistics;
this.selectedStatsResult = {
summary: {
total_commits: stats.summary.total_commits || 0,
total_contributors: stats.summary.total_contributors || 0,
total_additions: stats.by_contributor.reduce((sum, c) => sum + (c.additions || 0), 0),
total_deletions: stats.by_contributor.reduce((sum, c) => sum + (c.deletions || 0), 0)
},
date_range: stats.summary.date_range || { from: '未指定', to: '未指定' },
contributors: stats.by_contributor.map(c => ({
name: c.author,
email: c.email,
commit_count: c.commits,
additions: c.additions,
deletions: c.deletions,
first_commit_date: c.first_commit_date || '-',
last_commit_date: c.last_commit_date || '-'
}))
};
ElMessage.success('查看统计结果成功');
} else {
ElMessage.error(response.data.message || '查询统计结果失败');
this.selectedStatsResult = null;
}
} catch (error) {
ElMessage.error('网络请求失败: ' + error.message);
this.selectedStatsResult = null;
} finally {
this.statsLoading = false;
}
},
getRepoStatusType(status) {
const statusMap = {
'pending': 'info',
'cloning': 'warning',
'ready': 'success',
'error': 'danger'
};
return statusMap[status] || 'info';
},
getTaskStatusType(status) {
const statusMap = {
'pending': 'info',
'running': 'warning',
'completed': 'success',
'failed': 'danger'
};
return statusMap[status] || 'info';
},
getConstraintText(cache) {
if (!cache.constraint_value) return '未指定';
try {
const constraint = JSON.parse(cache.constraint_value);
if (cache.constraint_type === 'date_range') {
return `${constraint.from || ''} ~ ${constraint.to || ''}`;
} else if (cache.constraint_type === 'commit_limit') {
return `最近 ${constraint.limit || 100} 次提交`;
}
} catch (e) {
return '解析失败';
}
return '未知';
},
getRepoName(repoId) {
const repo = this.repos.find(r => r.id === repoId);
if (repo) {
const url = repo.url;
const parts = url.split('/');
return parts[parts.length - 1].replace('.git', '');
}
return `仓库 #${repoId}`;
},
formatDate(dateStr) {
if (!dateStr) return '-';
const date = new Date(dateStr);
return date.toLocaleString('zh-CN');
},
formatFileSize(bytes) {
if (!bytes) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
}
}
}).use(ElementPlus).mount('#app');