mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2025-12-08 13:49:37 +00:00
feat: 添加一言卡片及其设置功能,支持动态内容刷新
This commit is contained in:
parent
4627605178
commit
7d90e6ee33
184
src/components/HitokotoCard.vue
Normal file
184
src/components/HitokotoCard.vue
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
<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'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HitokotoCard',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
enabled: false,
|
||||||
|
refreshInterval: 60,
|
||||||
|
kvConfig: {
|
||||||
|
sources: ['hitokoto'],
|
||||||
|
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 : ['hitokoto'],
|
||||||
|
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/sentence', {
|
||||||
|
headers: {
|
||||||
|
'X-User-Token': 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
|
||||||
|
const hasSensitiveWord = this.kvConfig.sensitiveWords.some(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>
|
||||||
154
src/components/HitokotoSettings.vue
Normal file
154
src/components/HitokotoSettings.vue
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<setting-group title="一言设置" icon="mdi-comment-quote">
|
||||||
|
<setting-item setting-key="hitokoto.enabled" />
|
||||||
|
<setting-item setting-key="hitokoto.refreshInterval" />
|
||||||
|
</setting-group>
|
||||||
|
|
||||||
|
<setting-group title="数据源配置" icon="mdi-cloud-sync" class="mt-4">
|
||||||
|
<div class="text-caption text-grey px-4 pt-2 pb-2">以下配置将同步到云端,对所有连接此班级的设备生效。</div>
|
||||||
|
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title class="mb-2">启用数据源</v-list-item-title>
|
||||||
|
<div class="d-flex flex-wrap gap-2">
|
||||||
|
<v-checkbox
|
||||||
|
v-model="kvConfig.sources"
|
||||||
|
label="一言 (Hitokoto)"
|
||||||
|
value="hitokoto"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
class="mr-4"
|
||||||
|
:disabled="loading"
|
||||||
|
@update:model-value="saveKvSettings"
|
||||||
|
/>
|
||||||
|
<v-checkbox
|
||||||
|
v-model="kvConfig.sources"
|
||||||
|
label="诏预 (Zhaoyu)"
|
||||||
|
value="zhaoyu"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
class="mr-4"
|
||||||
|
:disabled="loading"
|
||||||
|
@update:model-value="saveKvSettings"
|
||||||
|
/>
|
||||||
|
<v-checkbox
|
||||||
|
v-model="kvConfig.sources"
|
||||||
|
label="今日诗词 (Jinrishici)"
|
||||||
|
value="jinrishici"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
:disabled="loading"
|
||||||
|
@update:model-value="saveKvSettings"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<v-list-item v-if="kvConfig.sources.includes('jinrishici')">
|
||||||
|
<v-text-field
|
||||||
|
v-model="kvConfig.jinrishiciToken"
|
||||||
|
label="今日诗词 Token"
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
:disabled="loading"
|
||||||
|
hint="留空则自动获取,也可以手动输入已有 Token"
|
||||||
|
persistent-hint
|
||||||
|
class="mt-2"
|
||||||
|
@change="saveKvSettings"
|
||||||
|
/>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<v-list-item>
|
||||||
|
<v-textarea
|
||||||
|
v-model="kvConfig.sensitiveWords"
|
||||||
|
:disabled="loading"
|
||||||
|
label="敏感词过滤 (用逗号分隔)"
|
||||||
|
variant="outlined"
|
||||||
|
rows="3"
|
||||||
|
auto-grow
|
||||||
|
hide-details
|
||||||
|
class="mt-2 mb-2"
|
||||||
|
@change="saveKvSettings"
|
||||||
|
/>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<div v-if="loading" class="text-center pb-4">
|
||||||
|
<v-progress-circular indeterminate size="24" color="primary" />
|
||||||
|
<span class="ml-2 text-caption">正在同步配置...</span>
|
||||||
|
</div>
|
||||||
|
</setting-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SettingGroup from './settings/SettingGroup.vue'
|
||||||
|
import SettingItem from './settings/SettingItem.vue'
|
||||||
|
import dataProvider from '@/utils/dataProvider'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HitokotoSettings',
|
||||||
|
components: {
|
||||||
|
SettingGroup,
|
||||||
|
SettingItem
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
kvConfig: {
|
||||||
|
sources: ['hitokoto'],
|
||||||
|
sensitiveWords: '',
|
||||||
|
jinrishiciToken: null
|
||||||
|
},
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadKvSettings()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadKvSettings() {
|
||||||
|
this.loading = true
|
||||||
|
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 : ['hitokoto'],
|
||||||
|
sensitiveWords: data.sensitiveWords || '',
|
||||||
|
jinrishiciToken: data.jinrishiciToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load sentence-info', e)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async saveKvSettings() {
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
// Check if jinrishici is enabled and token is missing
|
||||||
|
if (this.kvConfig.sources.includes('jinrishici') && !this.kvConfig.jinrishiciToken) {
|
||||||
|
try {
|
||||||
|
const tokenRes = await axios.get('https://v2.jinrishici.com/token')
|
||||||
|
if (tokenRes.data.status === 'success') {
|
||||||
|
this.kvConfig.jinrishiciToken = tokenRes.data.data
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to get jinrishici token', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await dataProvider.saveData('sentence-info', this.kvConfig)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to save sentence-info', e)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -4,15 +4,21 @@
|
|||||||
<div
|
<div
|
||||||
v-for="item in sortedItems"
|
v-for="item in sortedItems"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
|
ref="items"
|
||||||
|
:data-key="item.key"
|
||||||
:style="{
|
:style="{
|
||||||
'grid-row-end': `span ${item.rowSpan}`,
|
|
||||||
order: item.order,
|
order: item.order,
|
||||||
}"
|
}"
|
||||||
class="grid-item"
|
class="grid-item"
|
||||||
>
|
>
|
||||||
|
<!-- 一言卡片 -->
|
||||||
|
<div v-if="item.type === 'hitokoto'" style="height: 100%">
|
||||||
|
<hitokoto-card />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 出勤卡片 -->
|
<!-- 出勤卡片 -->
|
||||||
<v-card
|
<v-card
|
||||||
v-if="item.type === 'attendance'"
|
v-else-if="item.type === 'attendance'"
|
||||||
:class="{ 'glow-highlight': highlightedCards[item.key], 'cursor-not-allowed': isEditingDisabled, 'cursor-pointer': !isEditingDisabled }"
|
:class="{ 'glow-highlight': highlightedCards[item.key], 'cursor-not-allowed': isEditingDisabled, 'cursor-pointer': !isEditingDisabled }"
|
||||||
border
|
border
|
||||||
class="glow-track"
|
class="glow-track"
|
||||||
@ -178,8 +184,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import HitokotoCard from "@/components/HitokotoCard.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "HomeworkGrid",
|
name: "HomeworkGrid",
|
||||||
|
components: {
|
||||||
|
HitokotoCard,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
sortedItems: {
|
sortedItems: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@ -212,7 +223,76 @@ export default {
|
|||||||
return this.$vuetify.display.mobile;
|
return this.$vuetify.display.mobile;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
|
this.resizeAllGridItems();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Observe the grid container for width changes
|
||||||
|
if (this.$refs.gridContainer) {
|
||||||
|
this.resizeObserver.observe(this.$refs.gridContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial resize
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.resizeAllGridItems();
|
||||||
|
// Observe all items
|
||||||
|
if (this.$refs.items) {
|
||||||
|
this.$refs.items.forEach(item => {
|
||||||
|
// Observe the content inside the grid item
|
||||||
|
if (item.firstElementChild) {
|
||||||
|
this.resizeObserver.observe(item.firstElementChild);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updated() {
|
||||||
|
// When items change, re-observe new items
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.resizeAllGridItems();
|
||||||
|
if (this.$refs.items) {
|
||||||
|
this.$refs.items.forEach(item => {
|
||||||
|
if (item.firstElementChild) {
|
||||||
|
this.resizeObserver.observe(item.firstElementChild);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
if (this.resizeObserver) {
|
||||||
|
this.resizeObserver.disconnect();
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
resizeGridItem(item) {
|
||||||
|
const grid = this.$refs.gridContainer;
|
||||||
|
if (!grid) return;
|
||||||
|
|
||||||
|
const rowHeight = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-auto-rows'));
|
||||||
|
const rowGap = parseInt(window.getComputedStyle(grid).getPropertyValue('gap'));
|
||||||
|
|
||||||
|
// Find the content element (v-card or div)
|
||||||
|
const content = item.firstElementChild;
|
||||||
|
if (!content) return;
|
||||||
|
|
||||||
|
// Calculate required span
|
||||||
|
// We use scrollHeight to get the full height of content
|
||||||
|
// Add a small buffer to prevent scrollbars
|
||||||
|
const contentHeight = content.getBoundingClientRect().height;
|
||||||
|
|
||||||
|
// Formula: span = ceil((contentHeight + gap) / (rowHeight + gap))
|
||||||
|
const rowSpan = Math.ceil((contentHeight + rowGap) / (rowHeight + rowGap));
|
||||||
|
|
||||||
|
item.style.gridRowEnd = `span ${rowSpan}`;
|
||||||
|
},
|
||||||
|
resizeAllGridItems() {
|
||||||
|
const items = this.$refs.items;
|
||||||
|
if (items) {
|
||||||
|
items.forEach(item => this.resizeGridItem(item));
|
||||||
|
}
|
||||||
|
},
|
||||||
handleCardClick(type, key) {
|
handleCardClick(type, key) {
|
||||||
if (this.isEditingDisabled) {
|
if (this.isEditingDisabled) {
|
||||||
this.$emit('disabled-click');
|
this.$emit('disabled-click');
|
||||||
|
|||||||
@ -331,6 +331,7 @@ import AttendanceSidebar from "@/components/attendance/AttendanceSidebar.vue";
|
|||||||
import AttendanceManagementDialog from "@/components/attendance/AttendanceManagementDialog.vue";
|
import AttendanceManagementDialog from "@/components/attendance/AttendanceManagementDialog.vue";
|
||||||
import HomeworkGrid from "@/components/home/HomeworkGrid.vue";
|
import HomeworkGrid from "@/components/home/HomeworkGrid.vue";
|
||||||
import HomeActions from "@/components/home/HomeActions.vue";
|
import HomeActions from "@/components/home/HomeActions.vue";
|
||||||
|
import HitokotoCard from "@/components/HitokotoCard.vue";
|
||||||
import dataProvider from "@/utils/dataProvider";
|
import dataProvider from "@/utils/dataProvider";
|
||||||
import {
|
import {
|
||||||
getSetting,
|
getSetting,
|
||||||
@ -550,28 +551,46 @@ export default {
|
|||||||
const subjectData = this.state.boardData.homework[subjectKey];
|
const subjectData = this.state.boardData.homework[subjectKey];
|
||||||
|
|
||||||
if (subjectData && subjectData.content) {
|
if (subjectData && subjectData.content) {
|
||||||
|
const lineCount = subjectData.content.split("\n").filter((line) => line.trim()).length;
|
||||||
|
// Estimate height in pixels: title(64) + padding(32) + lines * line-height(24) + extra
|
||||||
|
const estimatedHeight = 100 + lineCount * 24;
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
key: subjectKey,
|
key: subjectKey,
|
||||||
name: subjectKey,
|
name: subjectKey,
|
||||||
type: 'homework',
|
type: 'homework',
|
||||||
content: subjectData.content,
|
content: subjectData.content,
|
||||||
order: subject.order,
|
order: subject.order,
|
||||||
rowSpan: Math.ceil((subjectData.content.split("\n").filter((line) => line.trim()).length + 1) * 0.8),
|
rowSpan: estimatedHeight, // Used for sorting only
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加一言卡片
|
||||||
|
if (getSetting("hitokoto.enabled")) {
|
||||||
|
items.push({
|
||||||
|
key: "hitokoto-card",
|
||||||
|
name: "一言",
|
||||||
|
type: "hitokoto",
|
||||||
|
order: 9998,
|
||||||
|
rowSpan: 150, // Default estimated height
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 添加自定义卡片
|
// 添加自定义卡片
|
||||||
for (const key in this.state.boardData.homework) {
|
for (const key in this.state.boardData.homework) {
|
||||||
if (key.startsWith('custom-')) {
|
if (key.startsWith('custom-')) {
|
||||||
const card = this.state.boardData.homework[key];
|
const card = this.state.boardData.homework[key];
|
||||||
|
const lineCount = card.content.split("\n").filter((line) => line.trim()).length;
|
||||||
|
const estimatedHeight = 100 + lineCount * 24;
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
key: key,
|
key: key,
|
||||||
name: card.name,
|
name: card.name,
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
content: card.content,
|
content: card.content,
|
||||||
order: 9999, // Put at the end
|
order: 9999, // Put at the end
|
||||||
rowSpan: Math.ceil((card.content.split("\n").filter((line) => line.trim()).length + 1) * 0.8),
|
rowSpan: estimatedHeight, // Used for sorting only
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -181,6 +181,10 @@
|
|||||||
/>
|
/>
|
||||||
</v-tabs-window-item>
|
</v-tabs-window-item>
|
||||||
|
|
||||||
|
<v-tabs-window-item value="hitokoto">
|
||||||
|
<hitokoto-settings border />
|
||||||
|
</v-tabs-window-item>
|
||||||
|
|
||||||
<v-tabs-window-item value="randomPicker">
|
<v-tabs-window-item value="randomPicker">
|
||||||
<random-picker-card :is-mobile="isMobile" border/>
|
<random-picker-card :is-mobile="isMobile" border/>
|
||||||
</v-tabs-window-item>
|
</v-tabs-window-item>
|
||||||
@ -272,6 +276,7 @@ import RandomPickerCard from "@/components/settings/cards/RandomPickerCard.vue";
|
|||||||
import HomeworkTemplateCard from "@/components/settings/cards/HomeworkTemplateCard.vue";
|
import HomeworkTemplateCard from "@/components/settings/cards/HomeworkTemplateCard.vue";
|
||||||
import SubjectManagementCard from "@/components/settings/cards/SubjectManagementCard.vue";
|
import SubjectManagementCard from "@/components/settings/cards/SubjectManagementCard.vue";
|
||||||
import KvDatabaseCard from "@/components/settings/cards/KvDatabaseCard.vue";
|
import KvDatabaseCard from "@/components/settings/cards/KvDatabaseCard.vue";
|
||||||
|
import HitokotoSettings from "@/components/HitokotoSettings.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Settings",
|
name: "Settings",
|
||||||
@ -293,6 +298,7 @@ export default {
|
|||||||
HomeworkTemplateCard,
|
HomeworkTemplateCard,
|
||||||
SubjectManagementCard,
|
SubjectManagementCard,
|
||||||
KvDatabaseCard,
|
KvDatabaseCard,
|
||||||
|
HitokotoSettings,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const {mobile} = useDisplay();
|
const {mobile} = useDisplay();
|
||||||
@ -414,6 +420,11 @@ export default {
|
|||||||
icon: "mdi-theme-light-dark",
|
icon: "mdi-theme-light-dark",
|
||||||
value: "theme",
|
value: "theme",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "一言",
|
||||||
|
icon: "mdi-comment-quote",
|
||||||
|
value: "hitokoto",
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "随机点名",
|
title: "随机点名",
|
||||||
|
|||||||
@ -146,6 +146,8 @@
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
grid-auto-flow: dense;
|
grid-auto-flow: dense;
|
||||||
|
grid-auto-rows: 1px;
|
||||||
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-item {
|
.grid-item {
|
||||||
|
|||||||
@ -102,6 +102,20 @@ const settingsDefinitions = {
|
|||||||
description: "空科目的显示方式",
|
description: "空科目的显示方式",
|
||||||
icon: "mdi-card-outline",
|
icon: "mdi-card-outline",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 一言设置
|
||||||
|
"hitokoto.enabled": {
|
||||||
|
type: "boolean",
|
||||||
|
default: false,
|
||||||
|
description: "启用一言",
|
||||||
|
icon: "mdi-comment-quote",
|
||||||
|
},
|
||||||
|
"hitokoto.refreshInterval": {
|
||||||
|
type: "number",
|
||||||
|
default: 300,
|
||||||
|
description: "刷新时间(秒,0为不自动刷新)",
|
||||||
|
icon: "mdi-timer-refresh",
|
||||||
|
},
|
||||||
"display.dynamicSort": {
|
"display.dynamicSort": {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
default: true,
|
default: true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user