网站和推流制作完成

This commit is contained in:
2025-08-08 16:07:49 +08:00
parent 3f67c118de
commit 7894b155dd
87 changed files with 26936 additions and 73 deletions

364
EyeVue/src/views/read.vue Normal file
View File

@@ -0,0 +1,364 @@
<template>
<div class="flex h-screen w-full bg-gray-100 dark:bg-gray-900 p-6 overflow-y-auto">
<div class="max-w-7xl mx-auto flex w-full">
<!-- 左侧信息区域 -->
<div class="w-3/10 bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mr-6">
<h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4">内容统计</h2>
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<tbody class="bg-white dark:bg-gray-700">
<tr class="border-b border-gray-200 dark:border-gray-600">
<td class="px-4 py-2 font-medium text-gray-700 dark:text-gray-300">行数</td>
<td class="px-4 py-2 text-gray-900 dark:text-gray-200">{{ lineCount }}</td>
</tr>
<tr class="border-b border-gray-200 dark:border-gray-600">
<td class="px-4 py-2 font-medium text-gray-700 dark:text-gray-300">语言</td>
<td class="px-4 py-2 text-gray-900 dark:text-gray-200">{{ language }}</td>
</tr>
<tr class="border-b border-gray-200 dark:border-gray-600">
<td class="px-4 py-2 font-medium text-gray-700 dark:text-gray-300">字符数</td>
<td class="px-4 py-2 text-gray-900 dark:text-gray-200">{{ article?.content?.length || 0 }}</td>
</tr>
<tr>
<td class="px-4 py-2 font-medium text-gray-700 dark:text-gray-300">阅读数</td>
<td class="px-4 py-2 text-gray-900 dark:text-gray-200">{{ article?.read_count || 0 }}</td>
</tr>
<tr>
<td class="px-4 py-2 font-medium text-gray-700 dark:text-gray-300">标签数</td>
<td class="px-4 py-2 text-gray-900 dark:text-gray-200">{{ tagCount }}</td>
</tr>
</tbody>
</table>
<!-- 支持统计饼图 -->
<div v-if="article" class="mt-6">
<h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4">翻译支持比例</h2>
<div class="w-50">
<PieChart :data="supportData" />
</div>
</div>
</div>
<!-- 右侧阅读区域 -->
<div class="w-7/10 w-full bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 overflow-y-auto">
<div v-if="loading" class="text-center text-gray-500">加载中...</div>
<div v-else-if="error" class="text-center text-red-500">{{ error }}</div>
<div v-else class="prose dark:prose-invert max-w-none">
<!-- 标题 -->
<h1 class="text-3xl font-bold text-gray-800 dark:text-white mb-4">{{ article.title }}</h1>
<!-- 作者 -->
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">作者{{ article.author }}</p>
<a class="flex text-sm text-gray-500 dark:text-gray-400 mb-6" :href="`/edit?id=${article.id}`">编辑文章</a>
<hr />
<!-- 内容 -->
<div class="mb-6 whitespace-pre-line text-gray-700 dark:text-gray-300">
{{ article.content }}
</div>
<!-- 人工翻译 -->
<div v-if="article.transform_content_human" class="mb-4">
<h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300 mb-2">人工翻译内容</h2>
<div class="whitespace-pre-line bg-gray-50 dark:bg-gray-700 p-4 rounded border dark:border-gray-600">
{{ article.transform_content_human }}
</div>
<button
@click="handleLike('human')"
class="mt-2 flex items-center text-sm text-gray-500 dark:text-gray-400 hover:text-red-500 dark:hover:text-red-400 like-button"
>
<svg
:class="{ 'text-red-500 dark:text-red-400': humanLikeStatus }"
class="w-4 h-4 mr-1"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M2 10.5a1.5 1.5 0 113 0v6a1.5 1.5 0 01-3 0v-6zM6 10.333v5.43a2 2 0 001.106 1.79l.05.025A4 4 0 008.943 18h5.416a2 2 0 001.962-1.608l1.2-6A2 2 0 0015.583 8H12V4a2 2 0 00-2-2 1 1 0 00-1 1v.667a4 4 0 01-.8 2.4L6.8 7.933a4 4 0 00-.8 2.4z" />
</svg>
点赞 {{ humanLikeStatus ? '已' : '未' }}完成
</button>
</div>
<!-- 多个机器翻译 -->
<div v-for="(trans, index) in article.transform_content_machine" :key="index" class="mb-4">
<h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300 mb-2">AI 翻译内容 {{ index + 1 }}</h2>
<!-- 显示 Prompt -->
<p class="text-sm text-gray-500 dark:text-gray-400 mb-1">
提示词{{ article.support_machine_prompt[index] || '未提供' }}
</p>
<!-- 翻译内容 -->
<div class="whitespace-pre-line bg-gray-50 dark:bg-gray-700 p-4 rounded border dark:border-gray-600">
{{ trans }}
</div>
<!-- 点赞按钮 -->
<button
@click="handleLike(`machine-${index}`)"
class="mt-2 flex items-center text-sm text-gray-500 dark:text-gray-400 hover:text-red-500 dark:hover:text-red-400 like-button"
>
<svg
:class="{ 'text-red-500 dark:text-red-400': machineLikeStatus[index] }"
class="w-4 h-4 mr-1"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M2 10.5a1.5 1.5 0 113 0v6a1.5 1.5 0 01-3 0v-6zM6 10.333v5.43a2 2 0 001.106 1.79l.05.025A4 4 0 008.943 18h5.416a2 2 0 001.962-1.608l1.2-6A2 2 0 0015.583 8H12V4a2 2 0 00-2-2 1 1 0 00-1 1v.667a4 4 0 01-.8 2.4L6.8 7.933a4 4 0 00-.8 2.4z" />
</svg>
点赞 {{ machineLikeStatus[index] ? '已' : '未' }}完成
</button>
</div>
<!-- 标签 -->
<div v-if="article.tags && article.tags.length > 0" class="mt-6">
<h2 class="text-lg font-medium text-gray-700 dark:text-gray-300 mb-2">标签</h2>
<div class="flex flex-wrap gap-2">
<span v-for="tagId in article.tags" :key="tagId" class="inline-flex items-center bg-gray-200 dark:bg-gray-600 text-gray-800 dark:text-gray-200 px-3 py-1 rounded-full text-sm">
{{ tagTitles[tagId] || '加载中...' }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watchEffect } from 'vue'
import axios from 'axios'
import { useRoute } from 'vue-router'
import PieChart from '@/components/PieChart.vue'
import router from '@/router'
const route = useRoute()
// 文章数据
const article = ref<any>(null)
const loading = ref(true)
const error = ref<string | null>(null)
// 翻译支持统计
const supportData = ref({
labels: ['人工翻译'],
datasets: [
{
label: '翻译支持比例',
data: [0],
backgroundColor: [
'rgba(54, 162, 235, 0.7)',
'rgba(255, 99, 132, 0.7)',
'rgba(75, 192, 192, 0.7)',
'rgba(153, 102, 255, 0.7)'
],
borderColor: [
'rgba(54, 162, 235, 1)',
'rgba(255, 99, 132, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)'
],
borderWidth: 1
}
]
})
// 统计信息计算
const wordCount = ref(0)
const lineCount = ref(0)
const language = ref('未知')
const tagCount = ref(0)
// 标签相关
const tagTitles = ref<Record<string, string>>({}) // 保存 tagId -> title 映射
// 获取标签名称
const fetchTagTitle = async (tagId: string) => {
try {
const response = await axios.get('/api/tags/getTagById', {
params: { id: tagId }
})
tagTitles.value[tagId] = response.data.title
} catch (err) {
tagTitles.value[tagId] = '未知标签'
console.error(`获取标签 ${tagId} 失败`, err)
}
}
// 批量获取所有标签名
const fetchAllTagTitles = () => {
if (!article.value?.tags?.length) return
const uniqueTagIds = [...new Set(article.value.tags)] // 去重
uniqueTagIds.forEach(tagId => {
if (!tagTitles.value[tagId]) {
fetchTagTitle(tagId)
}
})
}
// 加载文章
const fetchArticle = async () => {
const id = route.query.id as string
if (!id) {
error.value = '缺少文章 ID'
loading.value = false
return
}
try {
const response = await axios.get('/api/page/getPageById', {
params: { id }
})
article.value = response.data
// 更新本地 read_count
if (article.value) {
article.value.read_count = (article.value.read_count || 0) + 1
}
// 初始化点赞状态
initLikeStatus()
// 更新统计数据
updateSupportData()
wordCount.value = article.value.content.trim().split(/\s+/).length
// 获取标签名称
fetchAllTagTitles()
} catch (err) {
error.value = '加载文章失败'
console.error(err)
} finally {
loading.value = false
}
}
// 翻译点赞状态
const humanLikeStatus = ref<boolean>(false)
const machineLikeStatus = ref<{ [index: number]: boolean }>({})
const initLikeStatus = () => {
// 人工翻译点赞状态
const humanKey = `like_human_${article.value.id}`
const storedHuman = localStorage.getItem(humanKey)
if (storedHuman) {
humanLikeStatus.value = JSON.parse(storedHuman).liked
}
// 机器翻译点赞状态
const machineLength = article.value?.transform_content_machine?.length || 0
for (let i = 0; i < machineLength; i++) {
const key = `like_machine_${article.value.id}_${i}`
const stored = localStorage.getItem(key)
if (stored) {
machineLikeStatus.value[i] = JSON.parse(stored).liked
}
}
}
// 更新饼图数据
const updateSupportData = () => {
if (!article.value) return
const labels = ['人工翻译']
const data = [article.value.support_human]
if (Array.isArray(article.value.transform_content_machine)) {
article.value.transform_content_machine.forEach((_, index) => {
labels.push(`AI翻译 ${index + 1}${article.value.support_machine_prompt[index] || '无提示词'}`)
data.push(article.value.support_machine[index])
})
}
supportData.value.labels = labels
supportData.value.datasets[0].data = data
}
const handleLike = async (type: 'human' | `machine-${number}`) => {
let needUpdate = false
if (type === 'human') {
if (humanLikeStatus.value) return
article.value.support_human += 1
humanLikeStatus.value = true
localStorage.setItem(`like_human_${article.value.id}`, JSON.stringify({ liked: true, timestamp: Date.now() }))
needUpdate = true
} else {
const match = type.match(/machine-(\d+)/)
if (!match) return
const index = parseInt(match[1], 10)
if (machineLikeStatus.value[index]) return
article.value.support_machine[index] += 1
machineLikeStatus.value[index] = true
localStorage.setItem(`like_machine_${article.value.id}_${index}`, JSON.stringify({ liked: true, timestamp: Date.now() }))
needUpdate = true
}
if (needUpdate) {
try {
await axios.post('/api/page/like', {
id: article.value.id,
human: article.value.support_human,
machine: article.value.support_machine
}, {
headers: { 'Content-Type': 'application/json' }
})
updateSupportData()
} catch (err) {
console.error('点赞失败:', err)
}
}
}
onMounted(() => {
if (window.innerWidth < 768) {
router.push(`/readM?id=${route.query.id}`)
}
fetchArticle()
})
watchEffect(() => {
if (article.value?.content) {
wordCount.value = article.value.content.trim().split(/\s+/).length
lineCount.value = article.value.content.split('\n').length
const text = article.value.content.trim()
if (/[\u4e00-\u9fa5]/.test(text)) {
language.value = '中文'
} else if (/[a-zA-Z]/.test(text)) {
language.value = '英文'
} else {
language.value = '未知'
}
}
updateSupportData()
tagCount.value = article.value?.tags?.length || 0
})
</script>
<style scoped>
.like-button {
transition: all 0.2s ease-in-out;
}
.like-button:hover {
transform: scale(1.05);
}
.like-button:active {
transform: scale(0.95);
}
.flex,
.w-4\/10,
.w-6\/10 {
background-color: transparent !important;
}
.whitespace-pre-line {
white-space: pre-line;
}
</style>