|
|
|
@ -50,6 +50,13 @@ import VConsole from "vconsole"; |
|
|
|
const vConsole = new VConsole(); |
|
|
|
|
|
|
|
const isMobile = ref(null); |
|
|
|
//免费的Token的到期时间 |
|
|
|
const freeTokenExpireTime = ref("2026-06-30"); |
|
|
|
// Token清除时间范围 |
|
|
|
const tokenClearTimeRange = ref({ |
|
|
|
startTime: '2025-07-01', |
|
|
|
endTime: '2025-12-31' |
|
|
|
}); |
|
|
|
|
|
|
|
// 获取 AiEmotion 组件的 ref |
|
|
|
const aiEmotionRef = ref(null); |
|
|
|
@ -66,6 +73,125 @@ const deepNineStore = useDeepNineStore(); |
|
|
|
// 音频管理 |
|
|
|
const emotionAudioStore = useEmotionAudioStore(); |
|
|
|
const audioStore = useAudioStore(); |
|
|
|
// 控制Token变动记录弹窗显示 |
|
|
|
const tokenRecordVisible = ref(false); |
|
|
|
|
|
|
|
const tokenRecordList = ref([ |
|
|
|
]); |
|
|
|
import axios from 'axios' |
|
|
|
|
|
|
|
// 获取Token变动记录接口 |
|
|
|
const getTokenChangeLogs = async () => { |
|
|
|
try { |
|
|
|
const APIurl = import.meta.env.VITE_APP_API_BASE_URL |
|
|
|
const res = await axios.post( |
|
|
|
`${APIurl}/api/aiEmotion/client/viewTokenChangeLogs`, |
|
|
|
{ |
|
|
|
token: localStorage.getItem('localToken') |
|
|
|
} |
|
|
|
) |
|
|
|
if (res.data.code === 200) { |
|
|
|
// 接口字段映射到前端表格格式 |
|
|
|
const records = res.data.data.map((item, index) => ({ |
|
|
|
id: index + 1, |
|
|
|
change: item.count, |
|
|
|
type: item.token_type === 1 ? '付费' : '免费', |
|
|
|
time: item.created_at, |
|
|
|
remark: item.reason |
|
|
|
})) |
|
|
|
tokenRecordList.value = records |
|
|
|
} else { |
|
|
|
ElMessage.error(res.data.msg || '获取Token流水失败') |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
ElMessage.error(error.message || '网络异常') |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 获取免费Token过期时间和清除时间范围接口 |
|
|
|
const getTokenExpireInfo = async () => { |
|
|
|
try { |
|
|
|
const APIurl = import.meta.env.VITE_APP_API_BASE_URL |
|
|
|
const res = await axios.post( |
|
|
|
`${APIurl}/api/aiEmotion/client/getCleanUpFreeTokenTime`, |
|
|
|
{ |
|
|
|
token: localStorage.getItem('localToken') |
|
|
|
} |
|
|
|
) |
|
|
|
if (res.data.code === 200) { |
|
|
|
// 更新过期时间(只取年月日) |
|
|
|
if (res.data.data.expiredTime) { |
|
|
|
freeTokenExpireTime.value = res.data.data.expiredTime.split(' ')[0] |
|
|
|
} |
|
|
|
// 更新清除时间范围 |
|
|
|
if (res.data.data.startTime && res.data.data.endTime) { |
|
|
|
tokenClearTimeRange.value = { |
|
|
|
startTime: res.data.data.startTime, |
|
|
|
endTime: res.data.data.endTime |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('获取Token过期信息失败', error) |
|
|
|
} |
|
|
|
} |
|
|
|
// Token清除提示 |
|
|
|
const showTokenClearDialog = ref(false) |
|
|
|
// 当前要显示的清除文案 |
|
|
|
const currentClearText = ref('') |
|
|
|
// 点击查看Token变动记录 |
|
|
|
const showTokenRecord = async () => { |
|
|
|
await getTokenChangeLogs() |
|
|
|
tokenRecordVisible.value = true |
|
|
|
}; |
|
|
|
const APIurl = import.meta.env.VITE_APP_API_BASE_URL |
|
|
|
// 清除弹窗提醒接口 |
|
|
|
const clearPopupReminder = async () => { |
|
|
|
try { |
|
|
|
const res = await axios.post( |
|
|
|
`${APIurl}/api/aiEmotion/client/clearPopupReminder`, |
|
|
|
{ |
|
|
|
token: localStorage.getItem('localToken') |
|
|
|
} |
|
|
|
) |
|
|
|
console.log('clearPopupReminder 接口返回:', res.data) |
|
|
|
if (res.data.code === 200) { |
|
|
|
// 接口返回 data 为 true 时,显示清除提示弹窗 |
|
|
|
if (res.data.data === true) { |
|
|
|
console.log('设置 showTokenClearDialog = true') |
|
|
|
showTokenClearDialog.value = true |
|
|
|
} |
|
|
|
} else { |
|
|
|
ElMessage.error(res.data.msg || '获取清除提醒状态失败') |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
ElMessage.error(error.message || '网络异常') |
|
|
|
} |
|
|
|
} |
|
|
|
//判断是否需要显示Token清除提示 |
|
|
|
const checkTokenClearTip = () => { |
|
|
|
// 1. 先判断是否已经提示过(本地缓存标记) |
|
|
|
const hasShowClearTip = localStorage.getItem('hasShowTokenClearTip') |
|
|
|
if (hasShowClearTip === '1') return |
|
|
|
|
|
|
|
// 格式化时间显示 |
|
|
|
const formatDate = (dateStr) => { |
|
|
|
const date = new Date(dateStr) |
|
|
|
return `${date.getFullYear()} 年 ${String(date.getMonth() + 1).padStart(2, '0')} 月 ${String(date.getDate()).padStart(2, '0')} 日` |
|
|
|
} |
|
|
|
|
|
|
|
const startTime = formatDate(tokenClearTimeRange.value.startTime) |
|
|
|
const endTime = formatDate(tokenClearTimeRange.value.endTime) |
|
|
|
|
|
|
|
currentClearText.value = `【通知】${startTime} - ${endTime} 期间获得的免费 Token 已统一清除,过期未使用额度不予保留、不累计、不顺延。` |
|
|
|
showTokenClearDialog.value = true |
|
|
|
} |
|
|
|
|
|
|
|
// 关闭Token清除弹窗,并标记已提示 |
|
|
|
const closeTokenClearDialog = () => { |
|
|
|
showTokenClearDialog.value = false |
|
|
|
localStorage.setItem('hasShowTokenClearTip', '1') |
|
|
|
} |
|
|
|
// 根据当前页面类型获取对应的音频store |
|
|
|
const getCurrentAudioStore = () => { |
|
|
|
return activeTab.value === "AiEmotion" ? emotionAudioStore : audioStore; |
|
|
|
@ -166,13 +292,19 @@ const getSelectedOptionImage = () => { |
|
|
|
}; |
|
|
|
|
|
|
|
// 点击外部关闭下拉菜单 |
|
|
|
onMounted(() => { |
|
|
|
onMounted(async () => { |
|
|
|
document.addEventListener("click", (e) => { |
|
|
|
const container = document.querySelector(".custom-select-container"); |
|
|
|
if (container && !container.contains(e.target) && isDropdownOpen.value) { |
|
|
|
isDropdownOpen.value = false; |
|
|
|
} |
|
|
|
}); |
|
|
|
// 获取Token过期信息 |
|
|
|
getTokenExpireInfo() |
|
|
|
// 调用清除弹窗提醒接口 |
|
|
|
await clearPopupReminder() |
|
|
|
// 检查是否需要显示Token清除提示 |
|
|
|
checkTokenClearTip() |
|
|
|
}); |
|
|
|
|
|
|
|
// 手机端选择器变化处理(保留原函数以兼容其他地方可能的调用) |
|
|
|
@ -271,6 +403,12 @@ const ensureAIchat = () => { |
|
|
|
|
|
|
|
// 获取次数 |
|
|
|
const UserCount = computed(() => chatStore.UserCount); |
|
|
|
//总的Token |
|
|
|
const totalToken = computed(() => chatStore.UserCount); |
|
|
|
//付费的Token |
|
|
|
const paidToken = computed(() => chatStore.PaidCount); |
|
|
|
//免费的Token |
|
|
|
const freeToken = computed(() => chatStore.FreeCount); |
|
|
|
|
|
|
|
const getCount = () => { |
|
|
|
console.log("点击了获取次数的按钮"); |
|
|
|
@ -1838,6 +1976,20 @@ onUnmounted(() => { |
|
|
|
<div>点击查看详情</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="token-info-row"> |
|
|
|
<div class="token-info-left"> |
|
|
|
<div class="token-total1">Token总数:{{ totalToken }}</div> |
|
|
|
<div class="token-paid">永久Token:{{ paidToken }}</div> |
|
|
|
<div class="token-free"> |
|
|
|
免费Token:{{ freeToken }} |
|
|
|
(到期时间:{{ freeTokenExpireTime }}) |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="token-info-right" @click="showTokenRecord"> |
|
|
|
查看Token变动记录 |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="changeLevel"> |
|
|
|
<div class="changeLevelTitle">兑换Token</div> |
|
|
|
<div class="changeLevelContent"> |
|
|
|
@ -1886,19 +2038,28 @@ onUnmounted(() => { |
|
|
|
/> |
|
|
|
</div> |
|
|
|
<div class="changeContent"> |
|
|
|
<div class="changeJwcode">精网号:{{ userInfo.jwcode }}</div> |
|
|
|
</div> |
|
|
|
<div class="changeJwcode">{{ userInfo.jwcode }}</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="changeLevel"> |
|
|
|
<div class="changeLevelTitle"> |
|
|
|
兑换Token |
|
|
|
<div class="changeRule" @click="openTokenRuleDialog"> |
|
|
|
(兑换规则:{{ changeRule.gold }}金币={{ changeRule.token }}Token) |
|
|
|
兑换规则:{{ changeRule.gold }}金币={{ changeRule.token }}Token |
|
|
|
<div>点击查看详情</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
<div class="changeLevel"> |
|
|
|
<div class="token-record"> |
|
|
|
<div class="token-row-top"> |
|
|
|
<div class="token-total-mobile">Token总数:{{ totalToken }}</div> |
|
|
|
<div class="token-change" @click="showTokenRecord">查看Token变动记录</div> |
|
|
|
</div> |
|
|
|
<div class="token-paid-mobile">付费Token:{{ paidToken }}</div> |
|
|
|
<div class="token-free-mobile">免费Token:{{ freeToken }} |
|
|
|
<br> |
|
|
|
(到期时间:{{ freeTokenExpireTime }}) |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="changeLevelContent"> |
|
|
|
<div |
|
|
|
class="changeLevelItems" |
|
|
|
@ -1931,6 +2092,49 @@ onUnmounted(() => { |
|
|
|
</div> |
|
|
|
<div class="changeBtn" @click="changeToken">立即兑换</div> |
|
|
|
</el-dialog> |
|
|
|
<!-- Token变动记录弹窗 --> |
|
|
|
<el-dialog |
|
|
|
v-model="tokenRecordVisible" |
|
|
|
:width="isMobile ? '90%' : '60%'" |
|
|
|
:show-close="true" |
|
|
|
> |
|
|
|
<!-- 顶部统计区 --> |
|
|
|
<div class="token-record-header"> |
|
|
|
<div class="token-total">Token总数:{{ totalToken }}</div> |
|
|
|
<div class="token-row"> |
|
|
|
<div class="token-free">免费Token:{{ freeToken }} |
|
|
|
<br> |
|
|
|
(到期时间:{{ freeTokenExpireTime }})</div> |
|
|
|
<div class="token-permanent">永久Token:{{ paidToken }}</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 表格区 --> |
|
|
|
<div class="token-record-table"> |
|
|
|
<table> |
|
|
|
<thead> |
|
|
|
<tr> |
|
|
|
<th>序号</th> |
|
|
|
<th>数量变化</th> |
|
|
|
<th>Token类型</th> |
|
|
|
<th>操作时间</th> |
|
|
|
<th>备注</th> |
|
|
|
</tr> |
|
|
|
</thead> |
|
|
|
<tbody> |
|
|
|
<tr v-for="(item, index) in tokenRecordList" :key="item.id"> |
|
|
|
<td>{{ index + 1 }}</td> |
|
|
|
<td :class="{ 'token-plus': item.change > 0, 'token-minus': item.change < 0 }"> |
|
|
|
{{ item.change > 0 ? '+' : '' }}{{ item.change }} |
|
|
|
</td> |
|
|
|
<td>{{ item.type }}</td> |
|
|
|
<td>{{ item.time }}</td> |
|
|
|
<td>{{ item.remark }}</td> |
|
|
|
</tr> |
|
|
|
</tbody> |
|
|
|
</table> |
|
|
|
</div> |
|
|
|
</el-dialog> |
|
|
|
|
|
|
|
<el-dialog |
|
|
|
v-model="rechargeDialogVisible" |
|
|
|
@ -2002,6 +2206,19 @@ onUnmounted(() => { |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
</el-dialog> |
|
|
|
<!-- 免费Token清除提示弹窗 --> |
|
|
|
<el-dialog v-model="showTokenClearDialog" :width="isMobile ? '80%' : '40%'" :show-close="false" align-center> |
|
|
|
<div class="tokenClearDialogTitle">通知</div> |
|
|
|
<div class="tokenClearDialogContent"> |
|
|
|
{{ currentClearText }} |
|
|
|
</div> |
|
|
|
<div class="tokenClearDialogFooter"> |
|
|
|
<button class="tokenClearDialogBtn" @click="closeTokenClearDialog"> |
|
|
|
我已知晓 |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
</el-dialog> |
|
|
|
|
|
|
|
|
|
|
|
<!-- Token规则提示框 --> |
|
|
|
<div |
|
|
|
@ -2144,6 +2361,44 @@ onUnmounted(() => { |
|
|
|
padding: 6px 10px; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* Token清除弹窗样式 */ |
|
|
|
.tokenClearDialogTitle { |
|
|
|
font-size: 1.7rem; |
|
|
|
color: #4e86fe; |
|
|
|
display: flex; |
|
|
|
justify-content: center; |
|
|
|
align-items: center; |
|
|
|
letter-spacing: 10px; |
|
|
|
} |
|
|
|
|
|
|
|
.tokenClearDialogContent { |
|
|
|
padding: 20px; |
|
|
|
font-size: 1.2rem; |
|
|
|
line-height: 1.8; |
|
|
|
color: #333; |
|
|
|
} |
|
|
|
|
|
|
|
.tokenClearDialogFooter { |
|
|
|
display: flex; |
|
|
|
justify-content: center; |
|
|
|
padding: 0 20px 20px; |
|
|
|
} |
|
|
|
|
|
|
|
.tokenClearDialogBtn { |
|
|
|
color: white; |
|
|
|
background-color: #4e86fe; |
|
|
|
padding: 10px 30px; |
|
|
|
border-radius: 13px; |
|
|
|
cursor: pointer; |
|
|
|
border: none; |
|
|
|
font-size: 1rem; |
|
|
|
transition: all 0.3s ease; |
|
|
|
} |
|
|
|
|
|
|
|
.tokenClearDialogBtn:hover { |
|
|
|
background-color: #3a73e6; |
|
|
|
} |
|
|
|
</style> |
|
|
|
|
|
|
|
<style scoped> |
|
|
|
@ -2724,6 +2979,190 @@ body { |
|
|
|
transform: translateY(-2px); |
|
|
|
} |
|
|
|
|
|
|
|
.token-info-row { |
|
|
|
display: flex; |
|
|
|
width: 100%; |
|
|
|
margin-bottom: 30px; |
|
|
|
justify-content: center; |
|
|
|
gap: 20px; |
|
|
|
} |
|
|
|
|
|
|
|
.token-info-left { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
justify-content: center; |
|
|
|
gap: 6px; |
|
|
|
background-color: #f8f8f8; |
|
|
|
border-radius: 5px; |
|
|
|
padding: 10px 20px; |
|
|
|
width: 40%; |
|
|
|
font-weight: bold; |
|
|
|
} |
|
|
|
|
|
|
|
.token-info-right { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
justify-content: center; |
|
|
|
align-items: center; |
|
|
|
white-space: nowrap; |
|
|
|
gap: 6px; |
|
|
|
background-color: #f8f8f8; |
|
|
|
border-radius: 5px; |
|
|
|
padding: 10px 20px; |
|
|
|
color: #4e86fe; |
|
|
|
min-width: 40%; |
|
|
|
transition: all 0.3s ease; |
|
|
|
cursor: pointer; |
|
|
|
} |
|
|
|
|
|
|
|
.token-info-right:hover { |
|
|
|
background-color: #e8f0ff; |
|
|
|
box-shadow: 0 2px 8px rgba(78, 134, 254, 0.2); |
|
|
|
transform: translateY(-2px); |
|
|
|
} |
|
|
|
|
|
|
|
/* Token变动记录弹窗样式 */ |
|
|
|
.token-record-header { |
|
|
|
background-color: #f8f8f8; |
|
|
|
border-radius: 8px; |
|
|
|
padding: 15px 20px; |
|
|
|
margin-bottom: 20px; |
|
|
|
} |
|
|
|
|
|
|
|
.token-total { |
|
|
|
font-size: 20px; |
|
|
|
color: #20303d; |
|
|
|
font-weight: 500; |
|
|
|
text-align: center; |
|
|
|
margin-bottom: 10px; |
|
|
|
} |
|
|
|
|
|
|
|
.token-row { |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
font-size: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
.token-total1 { |
|
|
|
color: #20303d; |
|
|
|
} |
|
|
|
|
|
|
|
.token-paid { |
|
|
|
color: #20303d; |
|
|
|
} |
|
|
|
|
|
|
|
.token-free { |
|
|
|
color: #20303d; |
|
|
|
} |
|
|
|
|
|
|
|
.token-permanent { |
|
|
|
color: #20303d; |
|
|
|
} |
|
|
|
|
|
|
|
.token-record-table { |
|
|
|
width: 100%; |
|
|
|
max-height: 300px; |
|
|
|
overflow-x: auto; |
|
|
|
overflow-y: auto; |
|
|
|
} |
|
|
|
|
|
|
|
.token-record-table table { |
|
|
|
width: 100%; |
|
|
|
border-collapse: collapse; |
|
|
|
} |
|
|
|
|
|
|
|
.token-record-table th, |
|
|
|
.token-record-table td { |
|
|
|
padding: 12px 8px; |
|
|
|
text-align: center; |
|
|
|
border-bottom: 1px solid #eee; |
|
|
|
} |
|
|
|
|
|
|
|
.token-record-table th { |
|
|
|
background-color: #f8f8f8; |
|
|
|
font-weight: 500; |
|
|
|
} |
|
|
|
|
|
|
|
.token-plus { |
|
|
|
color: #00b42a; |
|
|
|
} |
|
|
|
|
|
|
|
.token-minus { |
|
|
|
color: #ff4d4f; |
|
|
|
} |
|
|
|
|
|
|
|
/* 移动端适配 */ |
|
|
|
@media (max-width: 768px) { |
|
|
|
.changeMsg { |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
margin-bottom: 16px; |
|
|
|
gap: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.changeInfo { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
width: auto; |
|
|
|
padding: 0; |
|
|
|
margin: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.changeImgClass { |
|
|
|
width: 40px !important; |
|
|
|
height: 40px !important; |
|
|
|
border-radius: 4px !important; |
|
|
|
} |
|
|
|
|
|
|
|
.changeLevelTitle { |
|
|
|
align-items: left; |
|
|
|
width: auto; |
|
|
|
padding: 0; |
|
|
|
margin: 0 !important; |
|
|
|
} |
|
|
|
|
|
|
|
.changeRule { |
|
|
|
margin: 0 !important; |
|
|
|
padding: 0 !important; |
|
|
|
align-items: center !important; |
|
|
|
} |
|
|
|
|
|
|
|
.token-record { |
|
|
|
background: #f5f5f5 !important; |
|
|
|
padding: 16px !important; |
|
|
|
border-radius: 4px !important; |
|
|
|
margin-bottom: 20px !important; |
|
|
|
} |
|
|
|
|
|
|
|
.token-row-top { |
|
|
|
display: flex !important; |
|
|
|
justify-content: space-between !important; |
|
|
|
align-items: center !important; |
|
|
|
margin-bottom: 5px !important; |
|
|
|
} |
|
|
|
|
|
|
|
.token-total-mobile { |
|
|
|
font-size: 16px !important; |
|
|
|
color: #222 !important; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.token-change { |
|
|
|
font-size: 16px !important; |
|
|
|
color: #4e86fe !important; |
|
|
|
font-weight: bold; |
|
|
|
} |
|
|
|
|
|
|
|
.token-paid-mobile, |
|
|
|
.token-free-mobile { |
|
|
|
font-size: 16px !important; |
|
|
|
line-height: 2 !important; |
|
|
|
color: #222 !important; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.changeLevel { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
|