diff --git a/src/api/api-models.ts b/src/api/api-models.ts index 1289d71..21a0e4d 100644 --- a/src/api/api-models.ts +++ b/src/api/api-models.ts @@ -358,7 +358,7 @@ export interface QAInfo { sender: UserBasicInfo target: UserBasicInfo question: { message: string; image?: string } - answer?: { message: string; image?: string } + answer?: { message: string; image?: string, createdAt: number } isReaded?: boolean isSenderRegisted: boolean isPublic: boolean diff --git a/src/components/QuestionItem.vue b/src/components/QuestionItem.vue index bdf0086..6a1d9b2 100644 --- a/src/components/QuestionItem.vue +++ b/src/components/QuestionItem.vue @@ -11,6 +11,21 @@ const useQA = useQuestionBox() const isViolation = props.item.reviewResult?.isApproved == false const showContent = ref(!isViolation) + +// 计算得分颜色的函数 +function getScoreColor(score: number | undefined): string { + if (score === undefined) { + return 'grey'; // 如果没有分数,返回灰色 + } + // 将分数限制在 0 到 100 之间 + const clampedScore = Math.max(0, Math.min(100, score)); + // 插值计算色相: 0 (红色) for score 0, 120 (绿色) for score 100 + const hue = 120 * (clampedScore / 100); // 反转插值逻辑 + // 固定饱和度和亮度 (可根据需要调整) + const saturation = 50; + const lightness = 45; // 稍暗以提高与白色文本的对比度 + return `hsl(${hue}, ${saturation}%, ${lightness}%)`; +} @@ -101,18 +116,18 @@ const showContent = ref(!isViolation) - + 得分: {{ item.reviewResult.saftyScore }} - 审查得分, 满分100, 越低代表消息越8行 + 审查得分, 满分100, 越低代表消息越安全, 越高越危险 @@ -139,18 +154,15 @@ const showContent = ref(!isViolation) - - - {{ item.question?.message }} - - - {{ item.question?.message }} - + + {{ item.question?.message }} diff --git a/src/store/useQuestionBox.ts b/src/store/useQuestionBox.ts index b9e7c8f..1215aff 100644 --- a/src/store/useQuestionBox.ts +++ b/src/store/useQuestionBox.ts @@ -23,7 +23,11 @@ export const useQuestionBox = defineStore('QuestionBox', () => { const recieveQuestions = ref([]) const sendQuestions = ref([]) - const trashQuestions = ref([]) + const trashQuestions = computed(() => { + return recieveQuestions.value.filter( + (q) => q.reviewResult && q.reviewResult.isApproved == false + ) + }) const tags = ref([]) const reviewing = ref(0) @@ -37,6 +41,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => { return false }*/ return ( + (!q.reviewResult || q.reviewResult.isApproved == true) && (q.isFavorite || !onlyFavorite.value) && (q.isPublic || !onlyPublic.value) && (!q.isReaded || !onlyUnread.value) && @@ -66,16 +71,9 @@ export const useQuestionBox = defineStore('QuestionBox', () => { if (data.data.questions.length > 0) { recieveQuestions.value = new List(data.data.questions) .OrderBy((d) => d.isReaded) - //.ThenByDescending(d => d.isFavorite) - .Where( - (d) => !d.reviewResult || d.reviewResult.isApproved == true - ) //只显示审核通过的 .ThenByDescending((d) => d.sendAt) .ToArray() reviewing.value = data.data.reviewCount - trashQuestions.value = data.data.questions.filter( - (d) => d.reviewResult && d.reviewResult.isApproved == false - ) const displayId = accountInfo.value?.settings.questionDisplay.currentQuestion @@ -378,6 +376,20 @@ export const useQuestionBox = defineStore('QuestionBox', () => { message.error('拉黑失败: ' + err) }) } + async function markAsNormal(question: QAInfo) { + await QueryGetAPI(QUESTION_API_URL + 'mark-as-normal', { + id: question.id + }) + .then((data) => { + if (data.code == 200) { + message.success('已标记为正常') + question.reviewResult!.isApproved = true + } + }) + .catch((err) => { + message.error('标记失败: ' + err) + }) + } async function setCurrentQuestion(item: QAInfo | undefined) { const isCurrent = displayQuestion.value?.id == item?.id if (!isCurrent) { @@ -433,6 +445,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => { favorite, setPublic, blacklist, + markAsNormal, setCurrentQuestion, getViolationString } diff --git a/src/store/useWebFetcher.ts b/src/store/useWebFetcher.ts index ae71cff..28cdebf 100644 --- a/src/store/useWebFetcher.ts +++ b/src/store/useWebFetcher.ts @@ -224,7 +224,11 @@ export const useWebFetcher = defineStore('WebFetcher', () => { skipNegotiation: true, transport: signalR.HttpTransportType.WebSockets }) - .withAutomaticReconnect([0, 2000, 10000, 30000]) // 自动重连策略 + .withAutomaticReconnect({ + nextRetryDelayInMilliseconds: retryContext => { + return retryContext.elapsedMilliseconds < 60 * 1000 ? 10 * 1000 : 30 * 1000; + } + }) // 自动重连策略 .withHubProtocol(new msgpack.MessagePackHubProtocol()) // 使用 MessagePack 协议 .build(); @@ -249,7 +253,7 @@ export const useWebFetcher = defineStore('WebFetcher', () => { console.error(prefix.value + `与服务器连接关闭: ${error?.message || '未知原因'}. 自动重连将处理.`); state.value = 'connecting'; // 标记为连接中,等待自动重连 signalRConnectionId.value = undefined; - // withAutomaticReconnect 会处理重连,这里不需要手动调用 reconnect + await connection.start(); } else if (disconnectedByServer) { console.log(prefix.value + `连接已被服务器关闭.`); //Stop(); // 服务器要求断开,则彻底停止 @@ -369,12 +373,6 @@ export const useWebFetcher = defineStore('WebFetcher', () => { * 定期将队列中的事件发送到服务器 */ async function sendEvents() { - if (updateCount % 60 == 0) { - // 每60秒更新一次连接信息 - if (signalRClient.value) { - await sendSelfInfo(signalRClient.value); - } - } updateCount++; // 确保 SignalR 已连接 if (!signalRClient.value || signalRClient.value.state !== signalR.HubConnectionState.Connected) { @@ -384,6 +382,12 @@ export const useWebFetcher = defineStore('WebFetcher', () => { if (events.length === 0) { return; } + if (updateCount % 60 == 0) { + // 每60秒更新一次连接信息 + if (signalRClient.value) { + await sendSelfInfo(signalRClient.value); + } + } // 批量处理事件,每次最多发送20条 const batchSize = 30; diff --git a/src/views/manage/AnalyzeView.vue b/src/views/manage/AnalyzeView.vue index 69f0963..0411213 100644 --- a/src/views/manage/AnalyzeView.vue +++ b/src/views/manage/AnalyzeView.vue @@ -645,7 +645,7 @@ const fetchAnalyzeData = async () => { message.error(`获取数据失败: ${data.message}`); } } catch (error) { - message.error('请求失败,请检查网络连接'); + message.error('获取数据出错:' + (error as Error).message); console.error('获取数据出错:', error); } finally { loading.value = false; diff --git a/src/views/manage/QuestionBoxManageView.vue b/src/views/manage/QuestionBoxManageView.vue index 314cfb6..c5380ce 100644 --- a/src/views/manage/QuestionBoxManageView.vue +++ b/src/views/manage/QuestionBoxManageView.vue @@ -1,15 +1,16 @@ - - - 启用提问箱 - - - - - 刷新 - - - 分享 - - - 前往提问页 - - - 预览OBS组件 - - - 2025.3.1 本站已支持内容审查, 可前往提问箱设置页进行开启 - - - - - 新功能还不稳定, 如果启用后遇到任何问题请向我反馈 - - - - - 提问页链接 - - - - - + + + - 复制 - - - - 使用国内镜像(访问更快) - - - - - - 还剩余 {{ useQB.reviewing }} 条 - - - - - - - + + + + 启用提问箱 + + + + + - 打开展示页 + 刷新 - - - - 在设置选项卡中添加或删除话题 - - - - - 只显示收藏 - - - 只显示公开 - - - 只显示未读 - - - - - - - - - - - - 设为已读 - - - 重设为未读 - - - - - - 收藏 - - - - - - - - 删除 - - - 确认删除这条提问? 删除后无法恢复 - - - - 拉黑 - - - - - - {{ item.answer ? '查看回复' : '回复' }} - - - - - - - - - - - + - - - - 发给 - - - {{ item.target.name }} - - - - - - - - - - - - - - - - - - - 回复 - - - - {{ item.answer.message }} - - - - - - - {{ item.question?.message }} - - - - - - - - - - - + - - - - - - - - - - 删除 - - - 确认删除这条提问? 删除后无法恢复 - - - - 拉黑 - - - 标记为正常 - - - - - - {{ item.answer ? '查看回复' : '回复' }} - - - - - - - - 设定 - - + + + - 允许未注册用户进行提问 - - - 内容审查 - - - 新 - - - { accountInfo.settings.questionBox.saftyLevel = tempSaftyLevel; saveSettings() }" - /> - - 标签 + 2025.3.1 本站已支持内容审查, 可前往提问箱设置页进行开启 - 类似于话题, 可以在投稿时选择 + 新功能还不稳定, 如果启用后遇到任何问题请向我反馈 - - - - 标签名称 - - + + + + + 提问页链接 + + + + + + 复制 + + + + 使用国内镜像(访问更快) + + + + + + + + 当前有 {{ useQB.reviewing }} 条提问正在等待审核。 + + + + + + + + + + + - 添加 - - - - - - - - - - + + - {{ item.name }} - - - - - - - - - - - - - 确定要{{ item.visiable ? '隐藏' : '显示' }}这个标签吗? - + 打开展示页 + + + + + 在设置选项卡中管理话题 + - {{ item.visiable ? '隐藏' : '显示' }} - - - + + + + + + 只看收藏 + + + 只看公开 + + + 只看未读 + + + + + + + + + + + + + + + + {{ item.isReaded ? '设为未读' : '设为已读' }} + + + - + + {{ item.isFavorite ? '取消收藏' : '收藏' }} + + + 拉黑提问者 + + + + + + + + + 删除 + + + 确认删除这条提问? 删除后无法恢复。 + + + + + + + {{ item.answer ? '查看/修改回复' : '回复' }} + + + + + + + + + + + + + + + + + + + + + 发给 + + {{ item.target.name }} + + + + + + + + + + + - 确定要删除这个标签吗? - + + + + + + {{ item.question?.message }} + + + + + + + + 对方的回复 + + + {{ item.answer.message }} + + + + + + + + + + + + + + + + + + + + + + + 垃圾站 - - - - 通知 - - 收到新提问时发送邮件 - - - 提问后收到回复时发送邮件 - + + + 这里存放的是被内容审查机制自动过滤的提问。您可以查看、删除或将其标记为正常提问。标记为正常后,提问将移至“我收到的”列表。 + + + + + + + + + { + useQB.markAsNormal(item); + }" + > + 标记为正常 + + + + 拉黑提问者 + + + + + + + + + + 彻底删除 + + + 确认彻底删除这条提问? 删除后无法恢复。 + + + + + + + + + + + + + + + + + 基础设定 + + + 允许未注册/匿名用户进行提问 + + + + + 内容审查等级 + + 新 + + + { if (accountInfo?.settings?.questionBox) { accountInfo.settings.questionBox.saftyLevel = tempSaftyLevel; saveQuestionBoxSettings();} }" + /> + + + + 标签/话题管理 + + + + + 用于对收到的提问进行分类,或让提问者选择相关话题。 + + + + + 新标签 + + + 添加 + + + + + + + + + + + {{ item.name }} + + + + + + + + + + + + + + + 确定要{{ item.visiable ? '隐藏' : '显示' }}这个标签吗? (隐藏后提问者无法选择) + + + {{ item.visiable ? '隐藏标签' : '显示标签' }} + + + + + + + + + + + + + 确定要删除这个标签吗? 删除后不可恢复。 + + + 删除标签 + + + + + + + + + 通知设置 + + + 收到新提问时发送邮件通知 + + + 我发送的提问收到回复时发送邮件通知 + + + + + - - - + + + + + + 正在加载账户信息... + + + + - - 回复 - - - - - useQB.setPublic(v)" + + 正在回复给: {{ useQB.currentQuestion.sender?.name || '匿名用户' }} + + {{ useQB.currentQuestion.question?.message }} + + + + + + useQB.setPublic(v)" + > + 公开这条提问和我的回复 (其他人可在你的提问页看到) + + + + + + + 取消 + + { await useQB.reply(useQB.currentQuestion?.id ?? -1, replyMessage); replyModalVisiable = false; }" > - 公开可见 - - - - - - {{ useQB.currentQuestion?.answer ? '修改' : '发送' }} - + {{ useQB.currentQuestion?.answer ? '修改回复' : '发送回复' }} + + + + + + - - 向我提问 - - - 提 问 箱 - - - {{ accountInfo?.name }} - - - - VTSURU.LIVE - - + + + + + + + + 向我提问 + + + {{ accountInfo?.name }} + + + + + + 提问箱 + + + VTSURU.LIVE + + + + + + + - + + + 分享链接 + + 默认 { 复制 - - 国内镜像 (访问更快) - - + + 国内 { 复制 - + + - 保存卡片 + 保存分享图 保存二维码 @@ -852,28 +1094,38 @@ onMounted(() => { + - - 操作显示的内容请前往 + + 👇下方是实时预览效果。管理展示内容请前往 展示管理页 - + + { :setting="setting" /> - - - + + + OBS 浏览器源链接 + + + + + 复制 + + + + 前往展示管理页 @@ -897,78 +1164,154 @@ onMounted(() => { + +.share-card-divider { + height: 1px; + background-color: rgba(255, 255, 255, 0.3); /* 半透明分割线 */ + margin: 10px 0; + width: 80%; /* 分割线宽度 */ +} + +.share-card-meta { + display: flex; + justify-content: space-between; /* 类型和站点分开 */ + align-items: center; +} + +.share-card-type { + font-size: 16px; + font-weight: 500; + opacity: 0.8; +} + +.share-card-site { + font-size: 12px; + font-weight: 500; + opacity: 0.7; +} + +/* 二维码区域 (右侧) */ +.share-card-qr { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; /* 防止二维码被压缩 */ + background-color: rgba(255, 255, 255, 0.9); /* 二维码背景色,轻微透明 */ + padding: 8px; /* 内边距 */ + border-radius: 8px; /* 圆角 */ + height: 108px; /* 容器高度 */ + width: 108px; /* 容器宽度 */ + align-self: center; /* 垂直居中 */ +} + +/* 二维码SVG样式 */ +.share-card-qr svg { + display: block; /* 移除底部空白 */ + width: 100%; + height: 100%; +} + +/* 响应式调整 (可选) */ +@media (max-width: 500px) { + .share-card-content { + flex-direction: column; /* 小屏幕时垂直排列 */ + align-items: center; + text-align: center; + padding: 15px; + } + .share-card-main { + padding-right: 0; + margin-bottom: 15px; + align-items: center; /* 内部元素居中 */ + } + .share-card-qr { + align-self: center; /* 确保二维码居中 */ + width: 100px; /* 略微减小二维码尺寸 */ + height: 100px; + } + .share-card-name { + font-size: 36px; + } + .share-card-divider { + width: 100%; /* 分割线占满 */ + } + .share-card-meta { + width: 100%; /* 占满宽度方便对齐 */ + } +} + +/* --- 其他样式微调 --- */ +.n-list-item { + margin-bottom: 10px; /* 列表项间距 */ +} +.n-card { + border-radius: 6px; /* 统一卡片圆角 */ +} + \ No newline at end of file