1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2025-07-02 09:19:23 +00:00
This commit is contained in:
SunWuyuan 2025-03-15 16:09:06 +08:00
parent 49c93ecd08
commit 6b3ee0074e
No known key found for this signature in database
GPG Key ID: A6A54CF66F56BB64
6 changed files with 200 additions and 186 deletions

View File

@ -2,7 +2,7 @@
<v-snackbar
v-model="snackbar"
:color="colors[message?.type] || colors.info"
:timeout="4000"
:timeout="2000"
location="bottom"
class="global-snackbar"
multi-line
@ -21,7 +21,7 @@
</template>
<script>
import { defineComponent, ref, onBeforeUnmount } from 'vue';
import { defineComponent, ref, onBeforeUnmount, nextTick } from 'vue';
import messageService from '@/utils/message';
export default defineComponent({
@ -44,8 +44,12 @@ export default defineComponent({
info: 'info'
};
const unsubscribe = messageService?.onSnackbar?.((msg) => {
const unsubscribe = messageService?.onSnackbar?.(async (msg) => {
if (!msg) return;
if (snackbar.value) {
snackbar.value = false;
await nextTick();
}
message.value = msg;
snackbar.value = true;
});

View File

@ -1,49 +1,37 @@
<template>
<v-navigation-drawer v-model="drawer" location="right" temporary width="400">
<v-toolbar color="primary">
<v-toolbar-title>
消息记录
<v-chip v-if="unreadCount" color="error" size="x-small" class="ml-2">
{{ unreadCount }}
</v-chip>
</v-toolbar-title>
<v-toolbar-title>消息记录</v-toolbar-title>
<template #append>
<v-btn icon="mdi-delete" variant="text" color="white" @click="clear" />
<v-btn icon="mdi-delete" variant="text" @click="clearMessages" />
</template>
</v-toolbar>
<v-list class="pa-4">
<template v-if="visibleMessages.length">
<v-slide-y-transition group>
<v-list-item
v-for="msg in visibleMessages"
:key="msg.id"
:active="!msg.read"
class="mb-3"
rounded
@click="markAsRead(msg.id)"
>
<template #prepend>
<v-icon :icon="icons[msg.type]" :color="colors[msg.type]" size="20" />
</template>
<v-list>
<v-list-item v-for="msg in messages" :key="msg.id" rounded>
<template #prepend>
<v-icon :icon="icons[msg.type]" :color="colors[msg.type]" size="20" />
</template>
<div class="d-flex flex-column flex-grow-1 px-3">
<v-list-item-title>{{ msg.title }}</v-list-item-title>
<v-list-item-subtitle v-if="msg.content">{{ msg.content }}</v-list-item-subtitle>
<span class="text-caption text-grey">{{ new Date(msg.timestamp).toLocaleTimeString() }}</span>
</div>
<v-list-item-title>{{ msg.title }}</v-list-item-title>
<v-list-item-subtitle v-if="msg.content">{{
msg.content
}}</v-list-item-subtitle>
<span class="text-caption text-grey">
{{ new Date(msg.timestamp).toLocaleTimeString() }}
</span>
<template #append>
<v-btn icon="mdi-delete" variant="text" size="small" @click.stop="deleteMessage(msg.id)" />
</template>
</v-list-item>
</v-slide-y-transition>
<template #append>
<v-btn
icon="mdi-delete"
variant="text"
size="small"
@click="deleteMessage(msg.id)"
/>
</template>
</v-list-item>
<v-btn v-if="hasMore" variant="tonal" block class="mt-4" @click="loadMore">
加载更多
</v-btn>
</template>
<v-list-item v-else>
<v-list-item v-if="!messages.length">
<template #prepend>
<v-icon icon="mdi-inbox" color="grey" />
</template>
@ -54,58 +42,43 @@
</template>
<script>
import { defineComponent, ref, computed, onBeforeUnmount } from 'vue';
import messageService from '@/utils/message';
import { defineComponent, ref } from "vue";
import messageService from "@/utils/message";
export default defineComponent({
name: 'MessageLog',
name: "MessageLog",
setup() {
const drawer = ref(false);
const messages = ref([]);
const unreadCount = ref(0);
const page = ref(1);
const PAGE_SIZE = 20;
const visibleMessages = computed(() => messages.value.slice(0, page.value * PAGE_SIZE));
const hasMore = computed(() => visibleMessages.value.length < messages.value.length);
const icons = {
success: 'mdi-check-circle',
error: 'mdi-alert-circle',
warning: 'mdi-alert',
info: 'mdi-information'
success: "mdi-check-circle",
error: "mdi-alert-circle",
warning: "mdi-alert",
info: "mdi-information",
};
const colors = {
success: 'success',
error: 'error',
warning: 'warning',
info: 'primary'
success: "success",
error: "error",
warning: "warning",
info: "primary",
};
const unsubscribe = messageService?.onLog?.(msgs => {
if (!msgs) return;
messages.value = msgs.reverse();
unreadCount.value = messageService.getUnreadCount();
messageService.onLog((msgs) => {
if (msgs) {
messages.value = msgs;
}
});
onBeforeUnmount(() => unsubscribe?.());
return {
drawer,
unreadCount,
visibleMessages,
hasMore,
messages,
icons,
colors,
loadMore: () => page.value++,
markAsRead: id => messageService?.markAsRead?.(id),
deleteMessage: id => messageService?.deleteMessage?.(id),
clear: () => {
messageService?.clearMessages?.();
messages.value = [];
}
deleteMessage: (id) => messageService.deleteMessage(id),
clearMessages: () => messageService.clearMessages(),
};
}
},
});
</script>

View File

@ -275,23 +275,6 @@
<v-divider class="my-2" />
<v-list-item>
<template #prepend>
<v-icon icon="mdi-database" class="mr-3" />
</template>
<v-list-item-title>禁用消息日志记录</v-list-item-title>
<v-list-item-subtitle>关闭保存消息到本地存储的功能</v-list-item-subtitle>
<template #append>
<v-switch
v-model="settings.developer.disableMessageLog"
density="comfortable"
hide-details
/>
</template>
</v-list-item>
<v-divider class="my-2" />
<v-expand-transition>
<div v-if="settings.developer.showDebugConfig">
<v-divider class="my-2" />
@ -404,8 +387,7 @@ export default {
},
developer: {
enabled: getSetting('developer.enabled'),
showDebugConfig: getSetting('developer.showDebugConfig'),
disableMessageLog: getSetting('developer.disableMessageLog')
showDebugConfig: getSetting('developer.showDebugConfig')
},
message: {
showSidebar: getSetting('message.showSidebar'),

104
src/utils/db.js Normal file
View File

@ -0,0 +1,104 @@
const DB_NAME = 'homeworkboard';
const DB_VERSION = 1;
const LOG_STORE = 'message_logs';
class LogDB {
constructor() {
this.db = null;
this.ready = this.initDB();
}
async initDB() {
try {
this.db = await new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(LOG_STORE)) {
const store = db.createObjectStore(LOG_STORE, {
keyPath: 'id'
});
// 只保留时间和类型索引
store.createIndex('timestamp', 'timestamp');
store.createIndex('type', 'type');
}
};
});
return true;
} catch (error) {
console.error('初始化日志数据库失败:', error);
return false;
}
}
async ensureDB() {
if (!this.db) {
await this.ready;
}
if (!this.db) {
throw new Error('数据库未初始化');
}
}
async addLog(message) {
await this.ensureDB();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([LOG_STORE], 'readwrite');
const store = transaction.objectStore(LOG_STORE);
const request = store.add(message);
transaction.oncomplete = () => resolve(request.result);
transaction.onerror = () => reject(transaction.error);
});
}
async getLogs(limit = 20) {
await this.ensureDB();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([LOG_STORE], 'readonly');
const store = transaction.objectStore(LOG_STORE);
const index = store.index('timestamp');
const request = index.getAll(null, limit);
request.onsuccess = () => resolve(request.result.reverse());
request.onerror = () => reject(request.error);
});
}
async deleteLog(messageId) {
await this.ensureDB();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([LOG_STORE], 'readwrite');
const store = transaction.objectStore(LOG_STORE);
const request = store.delete(messageId);
transaction.oncomplete = () => resolve(true);
transaction.onerror = () => reject(transaction.error);
});
}
async clearLogs() {
await this.ensureDB();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([LOG_STORE], 'readwrite');
const store = transaction.objectStore(LOG_STORE);
const request = store.clear();
transaction.oncomplete = () => resolve(true);
transaction.onerror = () => reject(transaction.error);
});
}
}
export default new LogDB();

View File

@ -1,3 +1,6 @@
import logDB from './db';
import { getSetting } from './settings';
const messages = [];
let snackbarCallback = null;
let logCallback = null;
@ -15,79 +18,27 @@ const defaultOptions = {
addToLog: true
};
const STORAGE_KEY = 'classworks_messages';
const MAX_MESSAGES = 100; // 最大消息数量
const MAX_STORAGE_SIZE = 1024 * 1024; // 1MB 存储限制
// 加载保存的消息
function loadStoredMessages() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
messages.push(...JSON.parse(stored));
}
} catch (error) {
console.error('加载消息历史失败:', error);
}
}
// 清理旧消息
function cleanOldMessages() {
if (messages.length > MAX_MESSAGES) {
messages.splice(MAX_MESSAGES);
}
}
// 检查存储大小
function checkStorageSize(data) {
try {
const size = new Blob([data]).size;
return size <= MAX_STORAGE_SIZE;
} catch (error) {
console.error('检查存储大小失败:', error);
return false;
}
}
// 保存消息到localStorage
function saveMessages() {
try {
cleanOldMessages();
const data = JSON.stringify(messages);
if (!checkStorageSize(data)) {
// 如果数据太大,删除一半的旧消息
messages.splice(Math.floor(messages.length / 2));
return saveMessages();
}
localStorage.setItem(STORAGE_KEY, data);
} catch (error) {
if (error.name === 'QuotaExceededError') {
// 如果存储空间不足,清理一些旧消息再试
messages.splice(Math.floor(messages.length / 2));
return saveMessages();
}
console.error('保存消息历史失败:', error);
}
}
function createMessage(type, title, content = '', options = {}) {
async function createMessage(type, title, content = '', options = {}) {
const msgOptions = { ...defaultOptions, ...options };
const message = {
id: Date.now() + Math.random(),
type,
title,
content: content.substring(0, 500), // 限制内容长度
timestamp: new Date(),
read: false
content: content.substring(0, 500),
timestamp: new Date()
};
if (msgOptions.addToLog) {
messages.unshift(message); // 新消息添加到开头
cleanOldMessages();
saveMessages();
logCallback?.(messages);
try {
await logDB.addLog(message);
messages.unshift(message);
while (messages.length > getSetting('message.maxActiveMessages')) {
messages.pop();
}
logCallback?.(messages);
} catch (error) {
console.error('保存日志失败:', error);
}
}
if (msgOptions.showSnackbar) {
@ -97,7 +48,6 @@ function createMessage(type, title, content = '', options = {}) {
return message;
}
// 添加防抖函数实现
function debounce(fn, delay) {
let timer = null;
return function (...args) {
@ -119,35 +69,37 @@ export default {
},
onSnackbar: (callback) => { snackbarCallback = callback; },
onLog: (callback) => { logCallback = callback; },
getMessages: () => [...messages],
clearMessages: () => {
messages.length = 0;
getMessages: async () => {
try {
localStorage.removeItem(STORAGE_KEY);
return await logDB.getLogs();
} catch (error) {
console.error('清除消息历史失败:', error);
console.error('获取日志失败:', error);
return [...messages];
}
},
clearMessages: async () => {
try {
await logDB.clearLogs();
messages.length = 0;
logCallback?.(messages);
} catch (error) {
console.error('清除日志失败:', error);
}
},
MessageType,
markAsRead: (messageId) => {
const message = messages.find(m => m.id === messageId);
if (message) {
message.read = true;
saveMessages();
markAsRead: () => {}, // 移除标记已读功能
deleteMessage: async (messageId) => {
try {
await logDB.deleteLog(messageId);
const index = messages.findIndex(m => m.id === messageId);
if (index !== -1) {
messages.splice(index, 1);
}
logCallback?.(messages);
} catch (error) {
console.error('删除消息失败:', error);
}
},
deleteMessage: (messageId) => {
const index = messages.findIndex(m => m.id === messageId);
if (index !== -1) {
messages.splice(index, 1);
saveMessages();
logCallback?.(messages);
}
},
getUnreadCount: () => messages.filter(m => !m.read).length,
initialize: () => {
loadStoredMessages();
},
debounce, // 导出防抖函数
getUnreadCount: () => 0, // 移除未读计数
debounce,
};

View File

@ -110,11 +110,11 @@ const settingsDefinitions = {
default: false,
description: "是否显示调试配置",
},
// 新增的配置项禁止将消息日志记录到localStorage
"developer.disableMessageLog": {
"developer.disableMessageLog": { // 添加新的设置项
type: "boolean",
default: false,
description: "是否禁用将消息日志记录到localStorage",
description: "禁用消息日志记录",
requireDeveloper: true,
},
// 消息设置
@ -263,8 +263,7 @@ function logSettingsChange(key, oldValue, newValue) {
const shouldLog =
settingsCache['developer.enabled'] &&
settingsCache['developer.showDebugConfig'] &&
!settingsCache['developer.disableMessageLog'];
settingsCache['developer.showDebugConfig'];
if (shouldLog) {
console.log(`[Settings] ${key}:`, {