1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2026-02-04 07:53:11 +00:00
Classworks/src/components/HitokotoCard.vue
Sunwuyuan 61d8392d59
feat: Enhance exam management features and add sensitive word filtering
- Implement global sensitive word filtering in HitokotoCard component.
- Add a test button for Jinrishici API in HitokotoSettings component.
- Introduce ConciseExamCard and ExamScheduleCard components for better exam display.
- Add functionality to create and manage exam configurations in examschedule.vue.
- Implement upcoming exam notifications in the main index page.
- Create a new exam store for managing exam data and fetching exam details.
- Add RelativeTimeDisplay component for displaying relative time in a user-friendly format.
2025-12-13 20:42:12 +08:00

196 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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.

<template>
<v-card
class="hitokoto-card"
elevation="2"
border
rounded="xl"
:loading="loading"
height="100%"
@click="fetchSentence"
>
<v-card-text class="pa-6 d-flex flex-column justify-center" style="height: 100%">
<div class="text-h6 font-weight-medium mb-4 serif-font" style="white-space: pre-wrap; line-height: 1.6;">
{{ sentence }}
</div>
<div class="text-subtitle-2 text-medium-emphasis text-right serif-font">
<span v-if="author" class="mr-2">{{ author }}</span>
<span v-if="origin">{{ origin }}</span>
</div>
</v-card-text>
</v-card>
</template>
<script>
import { SettingsManager, watchSettings } from '@/utils/settings'
import dataProvider from '@/utils/dataProvider'
import axios from 'axios'
import { Base64 } from 'js-base64'
// 全局敏感词列表,强制生效。
const GLOBAL_SENSITIVE_WORDS_ENCODED = [
'6IO4',
'5Lmz',
'6JCd6I6J',
'5rer',
'5aW4',
]
// 解码敏感词列表
const GLOBAL_SENSITIVE_WORDS = GLOBAL_SENSITIVE_WORDS_ENCODED.map(word => Base64.decode(word))
export default {
name: 'HitokotoCard',
data() {
return {
enabled: false,
refreshInterval: 60,
kvConfig: {
sources: ['zhaoyu'],
sensitiveWords: []
},
sentence: '',
author: '',
origin: '',
loading: false,
timer: null,
unwatch: null
}
},
async mounted() {
this.loadLocalSettings()
await this.loadKvSettings()
this.fetchSentence()
this.startTimer()
this.unwatch = watchSettings(() => {
this.loadLocalSettings()
this.startTimer()
})
},
beforeUnmount() {
this.stopTimer()
if (this.unwatch) {
this.unwatch()
}
},
methods: {
loadLocalSettings() {
this.enabled = SettingsManager.getSetting('hitokoto.enabled')
this.refreshInterval = SettingsManager.getSetting('hitokoto.refreshInterval')
},
async loadKvSettings() {
try {
const res = await dataProvider.loadData('sentence-info')
let data = res
if (res && res.data) {
data = res.data
}
if (data) {
this.kvConfig = {
sources: Array.isArray(data.sources) && data.sources.length > 0 ? data.sources : ['zhaoyu'],
sensitiveWords: data.sensitiveWords ? data.sensitiveWords.split(/[,]/).map(w => w.trim()).filter(w => w) : [],
jinrishiciToken: data.jinrishiciToken
}
}
} catch (e) {
console.error('Failed to load sentence-info', e)
}
},
startTimer() {
if (this.timer) clearInterval(this.timer)
if (this.refreshInterval > 0) {
this.timer = setInterval(this.fetchSentence, this.refreshInterval * 1000)
}
},
stopTimer() {
if (this.timer) clearInterval(this.timer)
},
async fetchSentence() {
if (this.loading) return
this.loading = true
try {
// Pick random source
const sources = this.kvConfig.sources
const source = sources[Math.floor(Math.random() * sources.length)]
let data = null
let content = ''
let author = ''
let origin = ''
if (source === 'hitokoto') {
const res = await axios.get('https://v1.hitokoto.cn/')
data = res.data
content = data.hitokoto
author = data.from_who
origin = data.from
} else if (source === 'zhaoyu') {
const res = await axios.get('https://hub.saintic.com/openservice/sentence/all.json')
if (res.data.success) {
data = res.data.data
content = data.sentence || data.content || data.name
author = data.author
origin = data.name || data.origin
}
} else if (source === 'jinrishici') {
if (this.kvConfig.jinrishiciToken) {
const res = await axios.get('https://v2.jinrishici.com/one.json?client=npm-sdk/1.0&X-User-Token='+encodeURIComponent(this.kvConfig.jinrishiciToken), {
})
if (res.data.status === 'success') {
data = res.data.data
content = data.content
author = data.origin.author
origin = data.origin.title
}
} else {
// Token missing, maybe retry with another source or just fail silently
// For now, let's just log it. The settings page should handle token generation.
console.warn('Jinrishici token missing. Please enable it in settings to generate a token.')
// Retry to pick another source to avoid empty card
this.loading = false
return this.fetchSentence()
}
}
if (content) {
// Sensitive word check (global + KV)
const combinedWords = [...GLOBAL_SENSITIVE_WORDS, ...this.kvConfig.sensitiveWords]
const hasSensitiveWord = combinedWords.some(word => word && content.includes(word))
if (hasSensitiveWord) {
// Retry
this.loading = false
return this.fetchSentence()
}
this.sentence = content
this.author = author || ''
this.origin = origin || '未知'
}
} catch (e) {
console.error('Failed to fetch sentence', e)
this.sentence = '获取失败'
this.author = ''
this.origin = ''
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped>
.hitokoto-card {
cursor: pointer;
transition: all 0.3s ease;
}
.hitokoto-card:hover {
transform: translateY(-2px);
}
.serif-font {
font-family: "Noto Serif SC", "Source Han Serif SC", "Source Han Serif", source-han-serif-sc, "Songti SC", "SimSun", "Hiragino Sans GB", system-ui, serif;
}
</style>