Files
akmon/uni_modules/ak-ai-news/components/AINewsDemo.vue
2026-01-20 08:04:15 +08:00

1150 lines
28 KiB
Vue
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
filepath: h:\blews\akmon\uni_modules\ak-ai-news\components\AINewsDemo.vue
AI News System Demo Component
-->
<template>
<view class="ai-news-demo">
<view class="demo-header">
<text class="demo-title">🤖 AI新闻系统演示</text>
<text class="demo-subtitle">多语言AI驱动的新闻处理平台</text>
</view>
<!-- 功能选择标签 -->
<view class="tab-container">
<view
v-for="(tab, index) in tabs"
:key="tab.key"
class="tab-item"
:class="{ active: activeTab === tab.key }"
@click="switchTab(tab.key)"
>
<text class="tab-text">{{ tab.icon }} {{ tab.label }}</text>
</view>
</view>
<!-- 翻译服务演示 -->
<view v-if="activeTab === 'translation'" class="demo-section">
<view class="section-header">
<text class="section-title">🔄 翻译服务</text>
</view>
<view class="input-group">
<textarea
v-model="translationInput"
placeholder="请输入要翻译的文本..."
class="text-input"
:maxlength="500"
/>
</view>
<view class="language-selector">
<picker
:value="sourceLanguageIndex"
:range="languages"
range-key="label"
@change="onSourceLanguageChange"
>
<view class="picker-text">源语言: {{ languages[sourceLanguageIndex].label }}</view>
</picker>
<text class="arrow"></text>
<picker
:value="targetLanguageIndex"
:range="languages"
range-key="label"
@change="onTargetLanguageChange"
>
<view class="picker-text">目标语言: {{ languages[targetLanguageIndex].label }}</view>
</picker>
</view>
<button
class="action-button"
:disabled="!translationInput || isTranslating"
@click="performTranslation"
>
{{ isTranslating ? '翻译中...' : '开始翻译' }}
</button>
<view v-if="translationResult" class="result-box">
<text class="result-label">翻译结果:</text>
<text class="result-text">{{ translationResult.translatedText }}</text>
<view class="result-meta">
<text class="meta-item">质量分数: {{ translationResult.qualityScore.toFixed(2) }}</text>
<text class="meta-item">提供商: {{ translationResult.provider }}</text>
<text class="meta-item">成本: ${{ translationResult.costUSD.toFixed(4) }}</text>
</view>
</view>
</view>
<!-- 内容分析演示 -->
<view v-if="activeTab === 'analysis'" class="demo-section">
<view class="section-header">
<text class="section-title">🔍 内容分析</text>
</view>
<view class="input-group">
<textarea
v-model="analysisInput"
placeholder="请输入要分析的新闻内容..."
class="text-input large"
:maxlength="1000"
/>
</view>
<button
class="action-button"
:disabled="!analysisInput || isAnalyzing"
@click="performAnalysis"
>
{{ isAnalyzing ? '分析中...' : '开始分析' }}
</button>
<view v-if="analysisResult" class="analysis-results">
<view class="analysis-item">
<text class="analysis-label">📊 情感分析:</text>
<text class="analysis-value" :class="getSentimentClass(analysisResult.sentimentLabel)">
{{ getSentimentText(analysisResult.sentimentLabel) }} ({{ analysisResult.sentimentScore.toFixed(2) }})
</text>
</view>
<view class="analysis-item">
<text class="analysis-label">📚 可读性:</text>
<progress class="progress-bar" :percent="analysisResult.readabilityScore * 100" />
<text class="progress-text">{{ (analysisResult.readabilityScore * 100).toFixed(1) }}%</text>
</view>
<view class="analysis-item">
<text class="analysis-label">🛡 可信度:</text>
<progress class="progress-bar" :percent="analysisResult.credibilityScore * 100" />
<text class="progress-text">{{ (analysisResult.credibilityScore * 100).toFixed(1) }}%</text>
</view>
<view class="analysis-item">
<text class="analysis-label">🏷 关键词:</text>
<view class="keyword-tags">
<text v-for="keyword in analysisResult.keywords.slice(0, 5)" :key="keyword" class="keyword-tag">
{{ keyword }}
</text>
</view>
</view>
<view v-if="analysisResult.categories.length > 0" class="analysis-item">
<text class="analysis-label">📂 分类:</text>
<view class="category-list">
<view v-for="category in analysisResult.categories" :key="category.categoryId" class="category-item">
<text class="category-name">{{ category.categoryName }}</text>
<text class="category-confidence">{{ (category.confidence * 100).toFixed(1) }}%</text>
</view>
</view>
</view>
<view v-if="analysisResult.summary" class="analysis-item">
<text class="analysis-label">📝 摘要:</text>
<text class="summary-text">{{ analysisResult.summary }}</text>
</view>
</view>
</view>
<!-- 智能对话演示 -->
<view v-if="activeTab === 'chat'" class="demo-section">
<view class="section-header">
<text class="section-title">💬 智能对话</text>
</view>
<view class="chat-container">
<scroll-view
class="chat-messages"
scroll-y="true"
:scroll-top="scrollTop"
scroll-with-animation="true"
>
<view v-for="(message, index) in chatMessages" :key="index" class="message-item">
<view class="message" :class="message.type">
<text class="message-sender">{{ getMessageSender(message.type) }}</text>
<text class="message-content">{{ message.content }}</text>
<text class="message-time">{{ formatTime(message.timestamp) }}</text>
</view>
</view>
</scroll-view>
<view class="chat-input-container">
<input
v-model="chatInput"
placeholder="问问AI关于新闻的任何问题..."
class="chat-input"
@confirm="sendChatMessage"
/>
<button
class="send-button"
:disabled="!chatInput || isChatting"
@click="sendChatMessage"
>
发送
</button>
</view>
</view>
</view>
<!-- 推荐系统演示 -->
<view v-if="activeTab === 'recommendation'" class="demo-section">
<view class="section-header">
<text class="section-title">🎯 智能推荐</text>
</view>
<view class="recommendation-controls">
<button class="action-button" @click="generateRecommendations">
{{ isRecommending ? '生成中...' : '生成推荐' }}
</button>
<button class="action-button secondary" @click="getTrendingNews">
获取热门新闻
</button>
</view>
<view v-if="recommendations.length > 0" class="recommendation-list">
<view v-for="(rec, index) in recommendations" :key="rec.contentId" class="recommendation-item">
<view class="rec-header">
<text class="rec-title">{{ getMockNewsTitle(rec.contentId) }}</text>
<text class="rec-score">{{ (rec.score * 100).toFixed(0) }}%</text>
</view>
<text class="rec-reason">{{ rec.reason }}</text>
<view class="rec-meta">
<text class="rec-algorithm">{{ rec.algorithm }}</text>
<text class="rec-type">{{ getRecommendationTypeText(rec.recommendationType) }}</text>
</view>
</view>
</view>
</view>
<!-- 系统状态 -->
<view class="system-status">
<text class="status-title">📊 系统状态</text>
<view class="status-grid">
<view class="status-item">
<text class="status-label">总请求数</text>
<text class="status-value">{{ systemStats.totalRequests }}</text>
</view>
<view class="status-item">
<text class="status-label">成功率</text>
<text class="status-value">{{ getSuccessRate() }}%</text>
</view>
<view class="status-item">
<text class="status-label">总成本</text>
<text class="status-value">${{ systemStats.totalCost.toFixed(4) }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick } from 'vue'
import {
AIServiceManager,
type AIServiceConfig,
type TranslationResult,
type ContentAnalysisResult,
type ChatMessage,
type RecommendationResult
} from '../index.uts'
// 响应式数据
const activeTab = ref('translation')
const isTranslating = ref(false)
const isAnalyzing = ref(false)
const isChatting = ref(false)
const isRecommending = ref(false)
// 翻译相关
const translationInput = ref('')
const translationResult = ref<TranslationResult | null>(null)
const sourceLanguageIndex = ref(0)
const targetLanguageIndex = ref(1)
// 分析相关
const analysisInput = ref('')
const analysisResult = ref<ContentAnalysisResult | null>(null)
// 聊天相关
const chatInput = ref('')
const chatMessages = ref<Array<ChatMessage & { type: string }>>([])
const scrollTop = ref(0)
const currentSessionId = ref('')
// 推荐相关
const recommendations = ref<RecommendationResult[]>([])
// 系统统计
const systemStats = reactive({
totalRequests: 0,
successfulRequests: 0,
totalCost: 0
})
// AI服务管理器
let serviceManager: AIServiceManager | null = null
// 常量定义
const tabs = [
{ key: 'translation', label: '翻译', icon: '🔄' },
{ key: 'analysis', label: '分析', icon: '🔍' },
{ key: 'chat', label: '对话', icon: '💬' },
{ key: 'recommendation', label: '推荐', icon: '🎯' }
]
const languages = [
{ code: 'zh-CN', label: '中文' },
{ code: 'en', label: 'English' },
{ code: 'ja', label: '日本語' },
{ code: 'ko', label: '한국어' },
{ code: 'es', label: 'Español' },
{ code: 'fr', label: 'Français' }
]
// 组件挂载时初始化
onMounted(async () => {
await initializeAIServices()
await initializeChat()
})
// 初始化AI服务
async function initializeAIServices() {
try {
const config: AIServiceConfig = {
openai: {
apiKey: 'demo-key', // 在实际使用中应该从配置文件读取
model: 'gpt-3.5-turbo',
maxTokens: 2000,
temperature: 0.7
},
costLimits: {
dailyUSD: 50,
monthlyUSD: 500,
perRequestUSD: 2
}
}
serviceManager = new AIServiceManager(config)
await serviceManager.initialize()
console.log('AI服务初始化成功')
} catch (error) {
console.error('AI服务初始化失败:', error)
uni.showToast({
title: '服务初始化失败',
icon: 'error'
})
}
}
// 初始化聊天会话
async function initializeChat() {
if (!serviceManager) return
try {
const chatService = serviceManager.getChatService()
const sessionResponse = await chatService.createChatSession('demo-user', 'zh-CN')
if (sessionResponse.success && sessionResponse.data) {
currentSessionId.value = sessionResponse.data.id
// 添加欢迎消息
chatMessages.value.push({
id: 'welcome',
sessionId: sessionResponse.data.id,
type: 'assistant',
content: '您好我是AI新闻助手可以帮您了解最新新闻、分析新闻内容、翻译文本等。请问有什么可以帮您的吗',
language: 'zh-CN',
timestamp: Date.now()
})
}
} catch (error) {
console.error('聊天初始化失败:', error)
}
}
// 切换标签
function switchTab(tabKey: string) {
activeTab.value = tabKey
}
// 语言选择处理
function onSourceLanguageChange(e: any) {
sourceLanguageIndex.value = e.detail.value
}
function onTargetLanguageChange(e: any) {
targetLanguageIndex.value = e.detail.value
}
// 执行翻译
async function performTranslation() {
if (!serviceManager || !translationInput.value) return
isTranslating.value = true
translationResult.value = null
try {
const translationService = serviceManager.getTranslationService()
const sourceLanguage = languages[sourceLanguageIndex.value].code
const targetLanguage = languages[targetLanguageIndex.value].code
const result = await translationService.translateText(
translationInput.value,
targetLanguage,
sourceLanguage,
{
provider: 'openai',
culturalAdaptation: true
}
)
if (result.success && result.data) {
translationResult.value = result.data
updateSystemStats(true, result.costUSD || 0)
uni.showToast({
title: '翻译完成',
icon: 'success'
})
} else {
throw new Error(result.error || '翻译失败')
}
} catch (error) {
console.error('翻译失败:', error)
updateSystemStats(false, 0)
uni.showToast({
title: '翻译失败',
icon: 'error'
})
} finally {
isTranslating.value = false
}
}
// 执行内容分析
async function performAnalysis() {
if (!serviceManager || !analysisInput.value) return
isAnalyzing.value = true
analysisResult.value = null
try {
const analysisService = serviceManager.getAnalysisService()
const result = await analysisService.analyzeContent(analysisInput.value, {
types: ['sentiment', 'entities', 'topics', 'categories', 'readability', 'credibility', 'summary', 'keywords'],
language: 'zh-CN',
includeScores: true
})
if (result.success && result.data) {
analysisResult.value = result.data
updateSystemStats(true, result.costUSD || 0)
uni.showToast({
title: '分析完成',
icon: 'success'
})
} else {
throw new Error(result.error || '分析失败')
}
} catch (error) {
console.error('分析失败:', error)
updateSystemStats(false, 0)
uni.showToast({
title: '分析失败',
icon: 'error'
})
} finally {
isAnalyzing.value = false
}
}
// 发送聊天消息
async function sendChatMessage() {
if (!serviceManager || !chatInput.value || !currentSessionId.value) return
const userMessage = chatInput.value
chatInput.value = ''
isChatting.value = true
// 添加用户消息
chatMessages.value.push({
id: `user-${Date.now()}`,
sessionId: currentSessionId.value,
type: 'user',
content: userMessage,
language: 'zh-CN',
timestamp: Date.now()
})
scrollToBottom()
try {
const chatService = serviceManager.getChatService()
const response = await chatService.sendMessage(currentSessionId.value, userMessage)
if (response.success && response.data) {
chatMessages.value.push(response.data)
updateSystemStats(true, response.costUSD || 0)
scrollToBottom()
} else {
throw new Error(response.error || '发送失败')
}
} catch (error) {
console.error('聊天失败:', error)
updateSystemStats(false, 0)
// 添加错误消息
chatMessages.value.push({
id: `error-${Date.now()}`,
sessionId: currentSessionId.value,
type: 'error',
content: '抱歉,我现在无法回复您的消息,请稍后再试。',
language: 'zh-CN',
timestamp: Date.now()
})
scrollToBottom()
} finally {
isChatting.value = false
}
}
// 生成推荐
async function generateRecommendations() {
if (!serviceManager) return
isRecommending.value = true
recommendations.value = []
try {
const recommendationService = serviceManager.getRecommendationService()
// 模拟可用新闻内容
const mockNews = [
{ id: 'news1', title: 'AI技术新突破', categoryId: 'technology' },
{ id: 'news2', title: '经济政策分析', categoryId: 'economy' },
{ id: 'news3', title: '环保新举措', categoryId: 'environment' },
{ id: 'news4', title: '体育赛事报道', categoryId: 'sports' },
{ id: 'news5', title: '文化艺术活动', categoryId: 'culture' }
]
const result = await recommendationService.getPersonalizedRecommendations(
'demo-user',
mockNews as any,
{
algorithm: 'hybrid',
maxResults: 5,
diversityWeight: 0.3,
freshnessWeight: 0.4,
personalizedWeight: 0.3
}
)
if (result.success && result.data) {
recommendations.value = result.data
updateSystemStats(true, 0)
uni.showToast({
title: '推荐生成完成',
icon: 'success'
})
} else {
throw new Error(result.error || '推荐生成失败')
}
} catch (error) {
console.error('推荐失败:', error)
updateSystemStats(false, 0)
uni.showToast({
title: '推荐失败',
icon: 'error'
})
} finally {
isRecommending.value = false
}
}
// 获取热门新闻
async function getTrendingNews() {
if (!serviceManager) return
isRecommending.value = true
try {
const recommendationService = serviceManager.getRecommendationService()
// 模拟新闻数据
const mockNews = [
{ id: 'trend1', title: '今日热点:科技创新', viewCount: 5000, likeCount: 300 },
{ id: 'trend2', title: '经济动态:市场分析', viewCount: 3500, likeCount: 200 },
{ id: 'trend3', title: '社会新闻:民生关注', viewCount: 4200, likeCount: 250 }
]
const result = await recommendationService.getTrendingRecommendations(
mockNews as any,
24,
5
)
if (result.success && result.data) {
recommendations.value = result.data
updateSystemStats(true, 0)
uni.showToast({
title: '热门新闻获取成功',
icon: 'success'
})
}
} catch (error) {
console.error('获取热门新闻失败:', error)
updateSystemStats(false, 0)
} finally {
isRecommending.value = false
}
}
// 工具函数
function getSentimentClass(sentiment: string): string {
switch (sentiment) {
case 'positive': return 'sentiment-positive'
case 'negative': return 'sentiment-negative'
default: return 'sentiment-neutral'
}
}
function getSentimentText(sentiment: string): string {
switch (sentiment) {
case 'positive': return '积极'
case 'negative': return '消极'
default: return '中性'
}
}
function getMessageSender(type: string): string {
switch (type) {
case 'user': return '我'
case 'assistant': return 'AI助手'
case 'system': return '系统'
default: return '错误'
}
}
function formatTime(timestamp: number): string {
const date = new Date(timestamp)
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
}
function getMockNewsTitle(contentId: string): string {
const titles: Record<string, string> = {
'news1': 'AI技术突破新一代智能系统发布',
'news2': '经济分析:数字化转型推动增长',
'news3': '环保新政:绿色发展战略实施',
'news4': '体育快报:国际赛事精彩回顾',
'news5': '文化动态:传统艺术现代传承',
'trend1': '今日热点:科技创新引领未来',
'trend2': '经济动态:全球市场深度分析',
'trend3': '社会新闻:民生议题广泛关注'
}
return titles[contentId] || `新闻标题 ${contentId}`
}
function getRecommendationTypeText(type: string): string {
switch (type) {
case 'personalized': return '个性化'
case 'trending': return '热门'
case 'similar': return '相似'
case 'latest': return '最新'
default: return type
}
}
function getSuccessRate(): string {
if (systemStats.totalRequests === 0) return '0'
return ((systemStats.successfulRequests / systemStats.totalRequests) * 100).toFixed(1)
}
function updateSystemStats(success: boolean, cost: number) {
systemStats.totalRequests++
if (success) {
systemStats.successfulRequests++
}
systemStats.totalCost += cost
}
async function scrollToBottom() {
await nextTick()
scrollTop.value = 999999
}
</script>
<style lang="scss" scoped>
.ai-news-demo {
padding: 20rpx;
background-color: #f8f9fa;
min-height: 100vh;
}
.demo-header {
text-align: center;
margin-bottom: 30rpx;
.demo-title {
font-size: 48rpx;
font-weight: bold;
color: #2c3e50;
display: block;
margin-bottom: 10rpx;
}
.demo-subtitle {
font-size: 28rpx;
color: #7f8c8d;
display: block;
}
}
.tab-container {
display: flex;
background-color: white;
border-radius: 16rpx;
padding: 8rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
}
.tab-item {
flex: 1;
text-align: center;
padding: 20rpx;
border-radius: 12rpx;
transition: all 0.3s ease;
&.active {
background-color: #3498db;
.tab-text {
color: white;
font-weight: bold;
}
}
.tab-text {
font-size: 26rpx;
color: #7f8c8d;
}
}
.demo-section {
background-color: white;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
}
.section-header {
margin-bottom: 30rpx;
.section-title {
font-size: 36rpx;
font-weight: bold;
color: #2c3e50;
}
}
.input-group {
margin-bottom: 30rpx;
}
.text-input {
width: 100%;
min-height: 120rpx;
padding: 20rpx;
border: 2rpx solid #e0e6ed;
border-radius: 12rpx;
font-size: 28rpx;
background-color: #f8f9fa;
&.large {
min-height: 200rpx;
}
&:focus {
border-color: #3498db;
background-color: white;
}
}
.language-selector {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 30rpx;
.picker-text {
padding: 20rpx;
background-color: #f8f9fa;
border-radius: 12rpx;
font-size: 26rpx;
color: #2c3e50;
min-width: 200rpx;
text-align: center;
}
.arrow {
font-size: 32rpx;
color: #3498db;
margin: 0 20rpx;
}
}
.action-button {
width: 100%;
padding: 24rpx;
background-color: #3498db;
color: white;
border: none;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
&:disabled {
background-color: #bdc3c7;
}
&.secondary {
background-color: #95a5a6;
}
}
.result-box {
padding: 24rpx;
background-color: #f8f9fa;
border-radius: 12rpx;
border-left: 8rpx solid #3498db;
.result-label {
font-size: 28rpx;
font-weight: bold;
color: #2c3e50;
display: block;
margin-bottom: 16rpx;
}
.result-text {
font-size: 30rpx;
color: #2c3e50;
line-height: 1.6;
display: block;
margin-bottom: 20rpx;
}
.result-meta {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
.meta-item {
font-size: 24rpx;
color: #7f8c8d;
background-color: white;
padding: 8rpx 16rpx;
border-radius: 8rpx;
}
}
}
.analysis-results {
.analysis-item {
margin-bottom: 30rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #ecf0f1;
&:last-child {
border-bottom: none;
margin-bottom: 0;
}
}
.analysis-label {
font-size: 28rpx;
font-weight: bold;
color: #2c3e50;
display: block;
margin-bottom: 16rpx;
}
.analysis-value {
font-size: 30rpx;
font-weight: bold;
&.sentiment-positive {
color: #27ae60;
}
&.sentiment-negative {
color: #e74c3c;
}
&.sentiment-neutral {
color: #f39c12;
}
}
.progress-bar {
margin: 10rpx 0;
}
.progress-text {
font-size: 24rpx;
color: #7f8c8d;
margin-left: 10rpx;
}
}
.keyword-tags {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
.keyword-tag {
background-color: #3498db;
color: white;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
}
}
.category-list {
.category-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12rpx 0;
.category-name {
font-size: 26rpx;
color: #2c3e50;
}
.category-confidence {
font-size: 24rpx;
color: #7f8c8d;
}
}
}
.summary-text {
font-size: 26rpx;
color: #2c3e50;
line-height: 1.6;
background-color: #f8f9fa;
padding: 20rpx;
border-radius: 8rpx;
display: block;
}
.chat-container {
height: 600rpx;
display: flex;
flex-direction: column;
}
.chat-messages {
flex: 1;
padding: 20rpx;
background-color: #f8f9fa;
border-radius: 12rpx;
margin-bottom: 20rpx;
}
.message-item {
margin-bottom: 20rpx;
.message {
max-width: 80%;
padding: 16rpx 20rpx;
border-radius: 20rpx;
&.user {
background-color: #3498db;
color: white;
margin-left: auto;
text-align: right;
}
&.assistant {
background-color: white;
color: #2c3e50;
border: 2rpx solid #ecf0f1;
}
&.error {
background-color: #e74c3c;
color: white;
}
.message-sender {
font-size: 20rpx;
opacity: 0.8;
display: block;
margin-bottom: 8rpx;
}
.message-content {
font-size: 26rpx;
line-height: 1.5;
display: block;
margin-bottom: 8rpx;
}
.message-time {
font-size: 20rpx;
opacity: 0.6;
}
}
}
.chat-input-container {
display: flex;
gap: 16rpx;
.chat-input {
flex: 1;
padding: 20rpx;
border: 2rpx solid #e0e6ed;
border-radius: 12rpx;
font-size: 26rpx;
}
.send-button {
padding: 20rpx 30rpx;
background-color: #3498db;
color: white;
border: none;
border-radius: 12rpx;
font-size: 26rpx;
&:disabled {
background-color: #bdc3c7;
}
}
}
.recommendation-controls {
display: flex;
gap: 20rpx;
margin-bottom: 30rpx;
.action-button {
flex: 1;
margin-bottom: 0;
}
}
.recommendation-list {
.recommendation-item {
background-color: #f8f9fa;
padding: 24rpx;
border-radius: 12rpx;
margin-bottom: 20rpx;
border-left: 8rpx solid #3498db;
.rec-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
.rec-title {
font-size: 28rpx;
font-weight: bold;
color: #2c3e50;
flex: 1;
}
.rec-score {
font-size: 24rpx;
font-weight: bold;
color: #3498db;
background-color: white;
padding: 4rpx 12rpx;
border-radius: 12rpx;
}
}
.rec-reason {
font-size: 24rpx;
color: #7f8c8d;
margin-bottom: 12rpx;
display: block;
}
.rec-meta {
display: flex;
gap: 16rpx;
.rec-algorithm,
.rec-type {
font-size: 20rpx;
color: #95a5a6;
background-color: white;
padding: 4rpx 8rpx;
border-radius: 8rpx;
}
}
}
}
.system-status {
background-color: white;
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
.status-title {
font-size: 32rpx;
font-weight: bold;
color: #2c3e50;
margin-bottom: 24rpx;
display: block;
}
.status-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
.status-item {
text-align: center;
padding: 20rpx;
background-color: #f8f9fa;
border-radius: 12rpx;
.status-label {
font-size: 24rpx;
color: #7f8c8d;
display: block;
margin-bottom: 8rpx;
}
.status-value {
font-size: 32rpx;
font-weight: bold;
color: #2c3e50;
display: block;
}
}
}
}
</style>