feat: Multi-user system with PostgreSQL - WIP temporary save

This commit is contained in:
TIANHE
2026-01-14 05:29:55 +08:00
parent 996e3b38fe
commit 61a5e5e6aa
68 changed files with 91057 additions and 1920 deletions
+90 -22
View File
@@ -331,9 +331,21 @@
>
{{ $t('dashboard.analysis.modal.history.viewResult') }}
</a-button>
<!-- <span style="color: #999; font-size: 12px; margin-left: 12px;">
{{ formatTime(item.createtime) }}
</span> -->
<a-popconfirm
:title="$t('dashboard.analysis.modal.history.deleteConfirm')"
:ok-text="$t('common.confirm')"
:cancel-text="$t('common.cancel')"
@confirm="deleteHistoryItem(item)"
>
<a-button
type="link"
size="small"
icon="delete"
style="color: #ff4d4f;"
>
{{ $t('dashboard.analysis.modal.history.delete') }}
</a-button>
</a-popconfirm>
</div>
</div>
</template>
@@ -357,7 +369,7 @@
<script>
import { mapGetters, mapState } from 'vuex'
import { getUserInfo } from '@/api/login'
import { getWatchlist, addWatchlist, removeWatchlist, getWatchlistPrices, multiAnalysis, getAnalysisTaskStatus, getAnalysisHistoryList, getConfig, getMarketTypes, searchSymbols, getHotSymbols } from '@/api/market'
import { getWatchlist, addWatchlist, removeWatchlist, getWatchlistPrices, multiAnalysis, getAnalysisTaskStatus, getAnalysisHistoryList, deleteAnalysisTask, getConfig, getMarketTypes, searchSymbols, getHotSymbols } from '@/api/market'
import MetaverseAnalysis from './components/index'
import { DEFAULT_AI_MODEL_MAP, mergeModelMaps, modelMapToOptions } from '@/config/aiModels'
@@ -456,6 +468,8 @@ export default {
// Local-only mode: load watchlist immediately (userId defaults to 1).
// This also prevents a blank watchlist if user info fetch is slow/fails.
this.loadWatchlist()
// Check for any pending/running analysis tasks from previous session
this.checkPendingTasks()
},
mounted () {
// 启动自选股价格定时刷新
@@ -549,29 +563,35 @@ export default {
return
}
// Reset state
this.analyzing = true
this.currentTaskId = null
const [market, symbol] = this.selectedSymbol.split(':')
// 获取当前语言设置
const language = this.$store.getters.lang || 'zh-CN'
const useMultiAgent = this.$store.getters.useMultiAgent !== false
try {
// 调用后端API创建分析任务(异步)
// 可以从配置或用户设置中获取 use_multi_agent,默认使用多智能体模式
const useMultiAgent = this.$store.getters.useMultiAgent !== false // 默认启用多智能体模式
// Call API to create analysis task
const res = await multiAnalysis({
userid: this.userId,
market: market,
symbol: symbol,
language: language,
use_multi_agent: useMultiAgent,
model: this.selectedModel // 传递选中的模型
model: this.selectedModel,
timeframe: '1D'
})
if (res && res.code === 1 && res.data) {
// 检查是否是缓存数据(直接返回分析结果)
if (res.data.overview || res.data.fundamental || res.data.technical) {
// 这是缓存数据,直接显示结果
if (res.data.task_id) {
// Task created, start polling for results
// Frontend will show simulation animation while backend processes
this.currentTaskId = Number(res.data.task_id) || null
this.$message.info(this.$t('dashboard.analysis.message.taskCreated') || 'Analysis task created, processing...')
this.startTaskStatusPolling()
} else if (res.data.overview || res.data.fundamental || res.data.technical) {
// Direct result (legacy support)
this.analysisResults = {
overview: res.data.overview || null,
fundamental: res.data.fundamental || null,
@@ -584,15 +604,8 @@ export default {
risk_debate: res.data.risk_debate || null,
final_decision: res.data.final_decision || null
}
this.$message.success(this.$t('dashboard.analysis.message.analysisCompleteCache'))
this.$message.success(this.$t('dashboard.analysis.message.analysisComplete'))
this.analyzing = false
} else if (res.data.task_id) {
// 这是新任务,需要轮询
this.currentTaskId = Number(res.data.task_id) || null
this.$message.info(this.$t('dashboard.analysis.message.taskCreated'))
// 开始轮询任务状态
this.startTaskStatusPolling()
} else {
throw new Error('返回数据格式错误')
}
@@ -600,7 +613,7 @@ export default {
throw new Error(res?.msg || '创建分析任务失败')
}
} catch (error) {
// 如果错误信息包含QDT相关提示,直接显示;否则显示默认错误
console.error('Analysis failed:', error)
const errorMsg = error?.response?.data?.msg || error?.message || this.$t('dashboard.analysis.message.analysisFailed')
this.$message.error(errorMsg)
this.analyzing = false
@@ -680,6 +693,47 @@ export default {
this.historyLoading = false
}
},
// Check for pending/processing tasks when user returns to page
async checkPendingTasks () {
try {
const res = await getAnalysisHistoryList({
userid: this.userId,
page: 1,
pagesize: 5
})
if (res && res.code === 1 && res.data && res.data.list) {
// Find the most recent pending task
const pendingTask = res.data.list.find(t => t.status === 'pending')
if (pendingTask) {
// There's a pending task - show notification and start polling
this.currentTaskId = pendingTask.id
this.selectedSymbol = `${pendingTask.market}:${pendingTask.symbol}`
this.analyzing = true
this.$message.info(this.$t('dashboard.analysis.message.resumingAnalysis') || 'Resuming analysis in progress...')
this.startTaskStatusPolling()
} else {
// Check if most recent task just completed (within last 30 seconds)
const recentCompleted = res.data.list.find(t => {
if (t.status !== 'completed') return false
const completedAt = t.completetime
if (!completedAt) return false
const now = Math.floor(Date.now() / 1000)
return (now - completedAt) < 30 // Within 30 seconds
})
if (recentCompleted) {
// Show result of recently completed task
this.viewHistoryResult(recentCompleted)
}
}
}
} catch (error) {
// Silent fail - not critical
console.warn('Failed to check pending tasks:', error)
}
},
// 查看历史分析结果
async viewHistoryResult (task) {
if (task.status !== 'completed') {
@@ -718,6 +772,20 @@ export default {
this.$message.error(this.$t('dashboard.analysis.message.analysisFailed'))
}
},
// Delete history item
async deleteHistoryItem (item) {
try {
const res = await deleteAnalysisTask({ task_id: item.id })
if (res && res.code === 1) {
this.$message.success(this.$t('dashboard.analysis.message.deleteSuccess'))
this.loadHistoryList()
} else {
this.$message.error(res?.msg || this.$t('dashboard.analysis.message.deleteFailed'))
}
} catch (error) {
this.$message.error(this.$t('dashboard.analysis.message.deleteFailed'))
}
},
// 格式化时间
formatTime (timestamp) {
if (!timestamp) return '-'