Quellcode durchsuchen

开发剩余积分

zkn vor 3 Wochen
Ursprung
Commit
88ac6a71dd

+ 20 - 0
shudao-vue-frontend/src/views/Chat.pointsBalance.test.js

@@ -0,0 +1,20 @@
+import { readFileSync } from 'node:fs'
+import { dirname, resolve } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { describe, expect, it } from 'vitest'
+
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = dirname(__filename)
+const chatSource = readFileSync(resolve(__dirname, 'Chat.vue'), 'utf8')
+
+describe('Chat points balance card', () => {
+  it('renders the remaining points card from the points balance service', () => {
+    expect(chatSource).toContain("import { getBalance as getPointsBalance } from '@/services/pointsService.js'")
+    expect(chatSource).toContain('class="points-display"')
+    expect(chatSource).toContain('剩余积分')
+    expect(chatSource).toContain('{{ userPointsBalance }}')
+    expect(chatSource).toContain('const userPointsBalance = ref(0)')
+    expect(chatSource).toContain('userPointsBalance.value = await getPointsBalance()')
+    expect(chatSource).toContain('fetchPointsBalance()')
+  })
+})

+ 64 - 3
shudao-vue-frontend/src/views/Chat.vue

@@ -63,9 +63,17 @@
       <!-- 右侧AI问答区域 -->
       <div class="main-chat" :class="{ 'sidebar-open': webSearchSidebarVisible }">
       <!-- 聊天头部 -->
-      <div class="chat-header" v-if="currentMode !== 'exam-workshop' && currentQuestion">
-        <div class="question-title-card">
-          <h2>{{ currentQuestion }}</h2>
+      <div class="chat-header" v-if="currentMode !== 'exam-workshop'">
+        <div class="header-left">
+          <div class="question-title-card" v-if="currentQuestion">
+            <h2>{{ currentQuestion }}</h2>
+          </div>
+        </div>
+        <div class="header-right">
+          <div class="points-display">
+            <span class="points-label">剩余积分</span>
+            <span class="points-value">{{ userPointsBalance }}</span>
+          </div>
         </div>
       </div>
 
@@ -772,6 +780,7 @@ import StatusAvatar from '@/components/StatusAvatar.vue'
 import { createSSEConnection, closeSSEConnection } from '@/utils/sse'
 import { getApiPrefix } from '@/utils/apiConfig'
 import { synthesizeSpeechToObjectUrl } from '@/services/speechService'
+import { getBalance as getPointsBalance } from '@/services/pointsService.js'
 import { Document } from '@element-plus/icons-vue'
 
 // 导入发送按钮图标
@@ -1072,6 +1081,7 @@ const expandedOnlineSearchResults = ref({}) // 记录每个消息的联网搜索
 
 // 网络搜索结果数据(新增)
 const webSearchSidebarVisible = ref(false) // 网络搜索侧边栏显示状态
+const userPointsBalance = ref(0)
 
 // AI写作侧边栏状态
 const aiWritingSidebarVisible = ref(false)
@@ -1089,6 +1099,15 @@ const aiWritingSidebarWidth = ref(AI_WRITING_SIDEBAR_SIZE.default)
 const aiWritingSidebarResizing = ref(false)
 let aiWritingSidebarResizeFrame = null
 
+const fetchPointsBalance = async () => {
+  try {
+    userPointsBalance.value = await getPointsBalance()
+  } catch (error) {
+    console.error('获取积分余额失败:', error)
+    userPointsBalance.value = 0
+  }
+}
+
 const aiWritingToolbarConfig = {
   excludeKeys: [
     'group-video',
@@ -5586,6 +5605,8 @@ onMounted(async () => {
     // 1. 首先获取历史记录列表(最高优先级)
     await getHistoryRecordList()
     console.log('✅ AI问答历史记录加载完成')
+
+    await fetchPointsBalance()
     
     // 2. 测试TTS服务连接
     try {
@@ -5976,7 +5997,25 @@ onActivated(async () => {
 /* 聊天头部 */
 .chat-header {
   background: transparent;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  flex-shrink: 0;
+  min-height: 42px;
+  box-sizing: border-box;
   padding: 20px 0px 0px 18px; /* 从30px改为20px,向上提升10px */
+
+  .header-left {
+    flex: 1;
+    min-width: 0;
+  }
+
+  .header-right {
+    display: flex;
+    align-items: center;
+    flex-shrink: 0;
+    padding-right: 24px;
+  }
   
   .default-title {
     margin: 0;
@@ -6019,6 +6058,28 @@ onActivated(async () => {
       box-shadow: 0 4px 12px rgba(91, 141, 239, 0.15);
     }
   }
+
+  .points-display {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 8px 16px;
+    background: rgba(255, 255, 255, 0.9);
+    border-radius: 20px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+    white-space: nowrap;
+
+    .points-label {
+      font-size: 14px;
+      color: #6b7280;
+    }
+
+    .points-value {
+      font-size: 16px;
+      font-weight: 600;
+      color: #3b82f6;
+    }
+  }
 }
 
 /* 聊天内容区域 */