feat: 配额优化

This commit is contained in:
2026-02-25 21:30:24 +08:00
parent 2e93211697
commit 79a5c1f3ed
29 changed files with 1225 additions and 489 deletions

View File

@@ -1,6 +1,14 @@
<script setup>
import { ref } from 'vue'
import { ref, onMounted } from 'vue'
import GradientButton from '@/components/GradientButton.vue'
import { usePointsConfigStore } from '@/stores/pointsConfig'
const pointsConfigStore = usePointsConfigStore()
// 加载积分配置
onMounted(() => {
pointsConfigStore.loadConfig()
})
const props = defineProps({
modelValue: {
@@ -65,6 +73,7 @@ function handleReset() {
/>
<a-button @click="handleReset">重置</a-button>
</a-space>
<p class="points-hint">每次分析将消耗积分消耗量与分析数量相关</p>
</a-form>
</section>
</template>
@@ -88,6 +97,13 @@ function handleReset() {
color: var(--color-text-secondary);
}
.points-hint {
margin-top: 12px;
font-size: 12px;
color: var(--color-text-secondary);
text-align: center;
}
:deep(.ant-slider) {
padding: 10px 0;
}

View File

@@ -6,7 +6,7 @@
* 试听优化添加缓存机制同一参数下第二次试听直接播放缓存无需重复调用API
*/
defineOptions({ name: 'DigitalVideoPage' })
import { ref, computed, onMounted, watch, onUnmounted, onActivated } from 'vue'
import { ref, computed, onMounted, watch, onUnmounted } from 'vue'
import { message } from 'ant-design-vue'
import { InboxOutlined, SoundOutlined, LoadingOutlined } from '@ant-design/icons-vue'
import { VoiceService } from '@/api/voice'
@@ -18,7 +18,9 @@ import { DEFAULT_VOICE_PROVIDER } from '@/config/voiceConfig'
// 导入 voiceStore 用于获取用户音色
import { useVoiceCopyStore } from '@/stores/voiceCopy'
import { usePointsConfigStore } from '@/stores/pointsConfig'
const voiceStore = useVoiceCopyStore()
const pointsConfigStore = usePointsConfigStore()
// 状态管理
const uploadedVideo = ref('')
@@ -36,6 +38,10 @@ const playingPreviewVoiceId = ref('') // 当前正在试听的音色ID
const isPlayingSynthesized = ref(false) // 是否正在播放已合成的音频
const pollingInterval = ref(null) // 轮询间隔ID
// 音频播放实例
let previewAudio = null
let previewObjectUrl = ''
// Upload Hook
const { upload } = useUpload()
@@ -760,6 +766,8 @@ const playAudioFromBase64 = (audioBase64, format = 'mp3', onEnded = null) => {
// 生命周期
onMounted(async () => {
// 加载积分配置
pointsConfigStore.loadConfig()
await voiceStore.refresh()
// 默认选择第一个音色
@@ -830,9 +838,6 @@ watch([ttsText, speechRate, instruction, emotion], () => {
console.log('试听参数已变化,清除缓存')
})
// 音频实例
let previewAudio = null
let previewObjectUrl = ''
</script>
<template>
@@ -1032,6 +1037,7 @@ let previewObjectUrl = ''
>
{{ isGenerating ? '生成中...' : '生成视频' }}
</a-button>
<p class="points-hint">生成视频将消耗积分,消耗量与视频时长相关</p>
</div>
<div v-else class="task-actions">
@@ -1440,6 +1446,13 @@ let previewObjectUrl = ''
gap: 12px;
}
.points-hint {
text-align: center;
font-size: 12px;
color: #94a3b8;
margin: 0;
}
.task-actions {
display: flex;
flex-direction: column;

View File

@@ -82,6 +82,14 @@
<!-- 搜索和操作区 -->
<div class="toolbar-actions">
<!-- 存储配额显示 -->
<div class="storage-quota">
<span class="storage-quota__label">存储空间</span>
<span class="storage-quota__value">{{ userStore.usedStorage.toFixed(2) }} / {{ userStore.totalStorage }} GB</span>
<div class="storage-quota__bar">
<div class="storage-quota__used" :style="{ width: `${Math.min((userStore.usedStorage / userStore.totalStorage) * 100, 100)}%` }"></div>
</div>
</div>
<a-input
v-model="searchKeyword"
placeholder="搜索文件名..."
@@ -273,6 +281,10 @@ import MaterialUploadModal from '@/components/material/MaterialUploadModal.vue';
import MaterialService, { MaterialGroupService } from '@/api/material';
import { useUpload } from '@/composables/useUpload';
import FullWidthLayout from '@/layouts/components/FullWidthLayout.vue';
import { useUserStore } from '@/stores/user';
// 用户状态(获取存储配额)
const userStore = useUserStore()
// 状态管理
const loading = ref(false)
@@ -530,15 +542,6 @@ const handleFileClick = (file) => {
}
}
const handleFileSelectChange = (fileId, checked) => {
const index = selectedFileIds.value.indexOf(fileId)
if (checked && index === -1) {
selectedFileIds.value.push(fileId)
} else if (!checked && index > -1) {
selectedFileIds.value.splice(index, 1)
}
}
const handleOpenUploadModal = () => {
uploadModalVisible.value = true
}
@@ -572,6 +575,8 @@ const handleFileUpload = async (filesWithCover, category, groupId) => {
message.success(`成功上传 ${filesWithCover.length} 个文件`)
uploadModalVisible.value = false
await loadFileList()
// 刷新存储配额
await userStore.fetchUserProfile()
// 混剪素材才刷新分组列表
if (activeCategory.value === 'MIX') {
await loadGroupList()
@@ -633,6 +638,9 @@ const handleBatchDelete = async () => {
totalFileCount.value = Math.max(0, totalFileCount.value - count)
selectedFileIds.value = []
// 刷新存储配额
await userStore.fetchUserProfile()
// 如果删除后当前页没有数据了,则加载上一页
if (fileList.value.length === 0 && pagination.current > 1) {
pagination.current = pagination.current - 1
@@ -727,8 +735,10 @@ watch(activeCategory, () => {
})
// 初始化
onMounted(() => {
loadGroupList()
onMounted(async () => {
// 刷新用户档案获取最新存储配额
await userStore.fetchUserProfile()
await loadGroupList()
})
</script>
@@ -987,6 +997,43 @@ onMounted(() => {
}
}
// 存储配额显示
.storage-quota {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
background: @bg-page;
border-radius: @radius-sm;
border: 1px solid @border-color;
&__label {
font-size: 12px;
color: @text-muted;
}
&__value {
font-size: 12px;
font-weight: 500;
color: @text-primary;
}
&__bar {
width: 80px;
height: 4px;
background: @border-color;
border-radius: 2px;
overflow: hidden;
}
&__used {
height: 100%;
background: linear-gradient(90deg, @primary-color, #818cf8);
border-radius: 2px;
transition: width 0.3s ease;
}
}
// 工具栏操作区
.toolbar-actions {
display: flex;

View File

@@ -6,6 +6,7 @@ import TikhubService, { InterfaceType, MethodType, ParamType } from '@/api/tikhu
import { rewriteStream } from '@/api/forecast'
import { getAgentList } from '@/api/agent'
import { useUserStore } from '@/stores/user'
import { usePointsConfigStore } from '@/stores/pointsConfig'
import { getVoiceText } from '@gold/hooks/web/useVoiceText'
import { copyToClipboard } from '@/utils/clipboard'
@@ -13,6 +14,7 @@ defineOptions({ name: 'ForecastView' })
// 状态管理
const userStore = useUserStore()
const pointsConfigStore = usePointsConfigStore()
const searchKeyword = ref('')
const isLoading = ref(false)
const isGenerating = ref(false)
@@ -368,6 +370,7 @@ async function handleSearch() {
// 初始化
onMounted(() => {
loadAgentList()
pointsConfigStore.loadConfig()
})
</script>
@@ -604,6 +607,7 @@ onMounted(() => {
<span>生成爆款</span>
</template>
</button>
<p class="points-hint">生成爆款文案将消耗积分</p>
<!-- 生成结果 -->
<Transition name="slide-up">
@@ -848,10 +852,6 @@ onMounted(() => {
to { transform: rotate(360deg); }
}
@keyframes spin {
to { transform: rotate(360deg); }
}
// 热点卡片
.topic-list {
display: flex;
@@ -1205,6 +1205,13 @@ onMounted(() => {
}
}
.points-hint {
text-align: center;
font-size: 12px;
color: #94a3b8;
margin: 8px 0 0;
}
// 结果区域
.result-block {
padding-top: 16px;