Commit 559a85c1 by Performance System

1

parent e1d6e3c2
Pipeline #3236 passed with stage
in 31 seconds
......@@ -355,5 +355,3 @@ async def cleanup_old_history(
......@@ -484,6 +484,25 @@ export const historyApi = {
return apiClient.post('/api/history/cleanup', { keep_months: keepMonths })
},
// 检查指定月份数据是否存在
async checkMonthExists(month) {
try {
const response = await apiClient.get(`/api/history/${month}`)
return true
} catch (error) {
// 如果是404错误,说明数据不存在,返回false
if (error.message && error.message.includes('指定月份的历史记录不存在')) {
return false
}
if (error.response && error.response.status === 404) {
return false
}
// 其他错误继续抛出
console.error(`检查月份 ${month} 存在性时出错:`, error)
throw error
}
},
}
......
......@@ -801,6 +801,104 @@ export const useDataStore = defineStore('data', () => {
}
}
/**
* 手动保存当前月份的统计数据到历史记录
* @param {boolean} forceOverwrite - 是否强制覆盖已存在的数据
* @returns {Object} 保存结果 { success: boolean, message: string, isOverwrite: boolean }
*/
const saveCurrentMonthStatsManually = async (forceOverwrite = false) => {
try {
const currentDate = new Date()
const monthKey = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}`
console.log(`📊 手动保存 ${monthKey} 月份统计数据...`)
// 检查当前月份数据是否已存在
console.log(`🔍 检查月份 ${monthKey} 数据是否存在...`)
const monthExists = await historyApi.checkMonthExists(monthKey)
console.log(`📋 月份 ${monthKey} 存在状态: ${monthExists}`)
if (monthExists && !forceOverwrite) {
console.log(`⚠️ 月份 ${monthKey} 数据已存在,需要用户确认覆盖`)
return {
success: false,
message: `${monthKey} 月份的数据已存在,是否要覆盖?`,
isOverwrite: true,
monthKey
}
}
// 获取当前所有用户的统计数据
const currentStats = users.value
.filter(user => user.role === 'user')
.map(user => {
const userInstitutions = getInstitutionsByUserId(user.id)
return {
userId: user.id,
userName: user.name,
institutionCount: userInstitutions.length,
interactionScore: calculateInteractionScore(user.id),
performanceScore: calculatePerformanceScore(user.id),
institutions: userInstitutions.map(inst => ({
id: inst.id,
name: inst.name,
imageCount: inst.images ? inst.images.length : 0
}))
}
})
// 保存完整的机构和图片数据
const institutionsWithImages = institutions.value.map(inst => ({
id: inst.id,
institutionId: inst.institutionId,
name: inst.name,
ownerId: inst.ownerId,
images: inst.images || []
}))
const historyData = {
month: monthKey,
save_time: new Date().toISOString(),
total_users: currentStats.length,
total_institutions: institutions.value.length,
total_images: institutions.value.reduce((total, inst) => total + (inst.images ? inst.images.length : 0), 0),
user_stats: currentStats,
institutions_data: institutionsWithImages
}
// 保存到数据库
console.log(`💾 开始保存 ${monthKey} 月份数据到数据库...`)
console.log('📊 保存的数据概览:', {
month: monthKey,
totalUsers: historyData.total_users,
totalInstitutions: historyData.total_institutions,
totalImages: historyData.total_images,
userStatsCount: historyData.user_stats.length
})
await historyApi.save(historyData)
const message = monthExists ?
`✅ ${monthKey} 月份统计数据覆盖保存成功` :
`✅ ${monthKey} 月份统计数据保存成功`
console.log(message)
return {
success: true,
message,
isOverwrite: false,
monthKey
}
} catch (error) {
console.error('手动保存历史统计数据失败:', error)
return {
success: false,
message: `保存失败:${error.message || '未知错误'}`,
isOverwrite: false
}
}
}
// ========== 数据管理 ==========
/**
......@@ -1006,6 +1104,7 @@ export const useDataStore = defineStore('data', () => {
// 历史数据
saveCurrentMonthStats,
saveCurrentMonthStatsManually,
getHistoryStats,
getAvailableHistoryMonths,
getMonthStats,
......
......@@ -360,6 +360,15 @@
<p class="history-subtitle">查看历史月份各用户的绩效数据</p>
</div>
<div class="header-actions">
<el-button
type="success"
@click="handleManualSave"
:loading="manualSaveLoading"
>
<el-icon><DocumentAdd /></el-icon>
手动保存当前数据
</el-button>
<el-dropdown
@command="handleHistoryExportCommand"
:disabled="!selectedMonthData"
......@@ -941,6 +950,7 @@ import {
Calendar,
InfoFilled,
Document,
DocumentAdd,
Grid,
List,
Printer
......@@ -1013,6 +1023,7 @@ const selectedMonthData = ref(null)
const availableMonths = ref([])
const exportHistoryLoading = ref(false)
const manualSaveLoading = ref(false)
......@@ -2292,6 +2303,60 @@ const loadLastResetTime = async () => {
*/
/**
* 手动保存当前月份数据
*/
const handleManualSave = async () => {
try {
manualSaveLoading.value = true
const result = await dataStore.saveCurrentMonthStatsManually()
if (result.success) {
ElMessage.success(result.message)
// 刷新可用月份列表
await loadAvailableMonths()
// 注意:不立即加载刚保存的数据,避免时序问题
// 用户可以手动选择月份来查看数据
} else if (result.isOverwrite) {
// 需要用户确认是否覆盖
try {
await ElMessageBox.confirm(
result.message,
'数据已存在',
{
type: 'warning',
confirmButtonText: '覆盖保存',
cancelButtonText: '取消'
}
)
// 用户确认覆盖,重新保存
const overwriteResult = await dataStore.saveCurrentMonthStatsManually(true)
if (overwriteResult.success) {
ElMessage.success(overwriteResult.message)
// 刷新可用月份列表
await loadAvailableMonths()
// 注意:不立即加载刚保存的数据,避免时序问题
// 用户可以手动选择月份来查看数据
} else {
ElMessage.error(overwriteResult.message)
}
} catch {
// 用户取消覆盖
ElMessage.info('已取消保存')
}
} else {
ElMessage.error(result.message)
}
} catch (error) {
console.error('手动保存失败:', error)
ElMessage.error('保存失败,请重试')
} finally {
manualSaveLoading.value = false
}
}
/**
* 加载可用的历史月份
*/
const loadAvailableMonths = async () => {
......@@ -2682,7 +2747,7 @@ const iconComponents = {
z-index: 10;
}
.header-actions .el-button {
.header-actions .el-button:not(.el-button--success) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: 1px solid #667eea;
color: white;
......@@ -2693,13 +2758,36 @@ const iconComponents = {
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
}
.header-actions .el-button:hover {
.header-actions .el-button:not(.el-button--success):hover {
background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
border-color: #5a6fd8;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
}
/* 确保成功类型按钮显示正确的绿色 - 使用更高优先级 */
.header-actions .el-button.el-button--success {
background: #67c23a !important;
background-color: #67c23a !important;
background-image: none !important;
border-color: #67c23a !important;
color: white !important;
font-weight: 500;
padding: 10px 20px;
border-radius: 8px;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(103, 194, 58, 0.3) !important;
}
.header-actions .el-button.el-button--success:hover {
background: #5daf34 !important;
background-color: #5daf34 !important;
background-image: none !important;
border-color: #5daf34 !important;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(103, 194, 58, 0.4) !important;
}
.stats-section {
margin-bottom: 20px;
}
......
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手动保存功能测试</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
.test-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #fafafa;
}
.test-section h2 {
color: #555;
margin-top: 0;
}
.button {
background-color: #409EFF;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 5px;
font-size: 14px;
}
.button:hover {
background-color: #337ecc;
}
.button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.result {
margin-top: 15px;
padding: 10px;
border-radius: 4px;
font-family: monospace;
white-space: pre-wrap;
}
.success {
background-color: #f0f9ff;
border: 1px solid #67c23a;
color: #67c23a;
}
.error {
background-color: #fef0f0;
border: 1px solid #f56c6c;
color: #f56c6c;
}
.info {
background-color: #f4f4f5;
border: 1px solid #909399;
color: #606266;
}
.loading {
background-color: #fff7e6;
border: 1px solid #e6a23c;
color: #e6a23c;
}
</style>
</head>
<body>
<div class="container">
<h1>🧪 手动保存功能测试</h1>
<div class="test-section">
<h2>📋 测试说明</h2>
<p>这个页面用于测试绩效计分系统的手动保存功能。请按照以下步骤进行测试:</p>
<ol>
<li>确保后端服务正在运行(http://localhost:8000)</li>
<li>确保前端服务正在运行(http://localhost:4001)</li>
<li>点击下面的测试按钮来验证各个功能</li>
</ol>
</div>
<div class="test-section">
<h2>🔍 1. 检查当前月份数据是否存在</h2>
<button class="button" onclick="testCheckMonthExists()">检查 2025-09 月份数据</button>
<div id="checkResult" class="result info">点击按钮开始测试...</div>
</div>
<div class="test-section">
<h2>💾 2. 测试手动保存功能</h2>
<button class="button" onclick="testManualSave()">手动保存当前数据</button>
<div id="saveResult" class="result info">点击按钮开始测试...</div>
</div>
<div class="test-section">
<h2>📊 3. 验证保存的数据</h2>
<button class="button" onclick="testGetSavedData()">获取已保存的数据</button>
<div id="dataResult" class="result info">点击按钮开始测试...</div>
</div>
</div>
<script>
const API_BASE_URL = 'http://localhost:8000';
// 获取当前月份
function getCurrentMonth() {
const now = new Date();
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
}
// 显示结果
function showResult(elementId, message, type = 'info') {
const element = document.getElementById(elementId);
element.textContent = message;
element.className = `result ${type}`;
}
// 显示加载状态
function showLoading(elementId, message = '正在处理...') {
showResult(elementId, message, 'loading');
}
// 测试检查月份数据是否存在
async function testCheckMonthExists() {
const month = getCurrentMonth();
showLoading('checkResult', `正在检查 ${month} 月份数据...`);
try {
const response = await fetch(`${API_BASE_URL}/api/history/${month}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
if (response.ok) {
const data = await response.json();
showResult('checkResult', `✅ ${month} 月份数据存在\n响应: ${JSON.stringify(data, null, 2)}`, 'success');
} else if (response.status === 404) {
showResult('checkResult', `ℹ️ ${month} 月份数据不存在(这是正常的,可以进行保存)`, 'info');
} else {
const errorData = await response.json();
showResult('checkResult', `❌ 检查失败: ${errorData.detail || response.statusText}`, 'error');
}
} catch (error) {
showResult('checkResult', `❌ 网络错误: ${error.message}`, 'error');
}
}
// 测试手动保存功能
async function testManualSave() {
const month = getCurrentMonth();
showLoading('saveResult', `正在保存 ${month} 月份数据...`);
// 模拟保存数据
const historyData = {
month: month,
save_time: new Date().toISOString(),
total_users: 2,
total_institutions: 4,
total_images: 6,
user_stats: [
{
userId: 'test-user-1',
userName: '测试用户1',
institutionCount: 2,
interactionScore: 1.5,
performanceScore: 7.5,
institutions: []
},
{
userId: 'test-user-2',
userName: '测试用户2',
institutionCount: 2,
interactionScore: 2.0,
performanceScore: 10.0,
institutions: []
}
],
institutions_data: []
};
try {
const response = await fetch(`${API_BASE_URL}/api/history`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// 注意:实际使用时需要添加认证头
// 'Authorization': 'Bearer your-token-here'
},
body: JSON.stringify(historyData)
});
if (response.ok) {
const data = await response.json();
showResult('saveResult', `✅ 保存成功!\n响应: ${JSON.stringify(data, null, 2)}`, 'success');
} else {
const errorData = await response.json();
showResult('saveResult', `❌ 保存失败: ${errorData.detail || response.statusText}`, 'error');
}
} catch (error) {
showResult('saveResult', `❌ 网络错误: ${error.message}`, 'error');
}
}
// 测试获取保存的数据
async function testGetSavedData() {
const month = getCurrentMonth();
showLoading('dataResult', `正在获取 ${month} 月份数据...`);
try {
const response = await fetch(`${API_BASE_URL}/api/history/${month}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
if (response.ok) {
const data = await response.json();
showResult('dataResult', `✅ 数据获取成功!\n${JSON.stringify(data, null, 2)}`, 'success');
} else if (response.status === 404) {
showResult('dataResult', `ℹ️ ${month} 月份数据不存在,请先保存数据`, 'info');
} else {
const errorData = await response.json();
showResult('dataResult', `❌ 获取失败: ${errorData.detail || response.statusText}`, 'error');
}
} catch (error) {
showResult('dataResult', `❌ 网络错误: ${error.message}`, 'error');
}
}
// 页面加载完成后显示当前月份
document.addEventListener('DOMContentLoaded', function() {
const month = getCurrentMonth();
console.log(`当前测试月份: ${month}`);
});
</script>
</body>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment