add text review

This commit is contained in:
2025-03-01 00:18:46 +08:00
parent 3fd3a74f78
commit 300a38e851
7 changed files with 271 additions and 45 deletions

View File

@@ -104,8 +104,16 @@ export interface Setting_SendEmail {
recieveQA: boolean recieveQA: boolean
recieveQAReply: boolean recieveQAReply: boolean
} }
export enum SaftyLevels {
Disabled,
Low,
Medium,
High
}
export interface Setting_QuestionBox { export interface Setting_QuestionBox {
allowUnregistedUser: boolean allowUnregistedUser: boolean
saftyLevel: SaftyLevels
} }
export interface UserSetting { export interface UserSetting {
sendEmail: Setting_SendEmail sendEmail: Setting_SendEmail
@@ -326,6 +334,21 @@ export interface NotifactionInfo {
message: string message: string
type: LevelTypes type: LevelTypes
} }
//SENSITIVE_TERM, HATE, VIOLENCE, PORNOGRAPHY, POLITICS, ADVERTISING, AGGRESSION
export enum ViolationTypes {
SENSITIVE_TERM,
HATE,
VIOLENCE,
PORNOGRAPHY,
POLITICS,
ADVERTISING,
AGGRESSION,
}
export type QAReviewInfo = {
isApproved: boolean
saftyScore: number
violationType: ViolationTypes[]
}
export interface QAInfo { export interface QAInfo {
id: number id: number
sender: UserBasicInfo sender: UserBasicInfo
@@ -340,6 +363,7 @@ export interface QAInfo {
isAnonymous: boolean isAnonymous: boolean
tag?: string tag?: string
reviewResult?: QAReviewInfo
} }
export interface LotteryUserInfo { export interface LotteryUserInfo {
name: string name: string

View File

@@ -1,10 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { QAInfo } from '@/api/api-models' import { QAInfo } from '@/api/api-models'
import { NCard, NDivider, NFlex, NImage, NTag, NText, NTime, NTooltip } from 'naive-ui' import { useQuestionBox } from '@/store/useQuestionBox';
import { NButton, NCard, NDivider, NFlex, NImage, NTag, NText, NTime, NTooltip } from 'naive-ui'
import { ref } from 'vue';
const props = defineProps<{ const props = defineProps<{
item: QAInfo item: QAInfo
}>() }>()
const useQA = useQuestionBox()
const isViolation = props.item.reviewResult?.isApproved == false
const showContent = ref(!isViolation)
</script> </script>
<template> <template>
@@ -15,7 +21,7 @@ const props = defineProps<{
<NTag type="warning" size="tiny"> 未读 </NTag> <NTag type="warning" size="tiny"> 未读 </NTag>
<NDivider vertical /> <NDivider vertical />
</template> </template>
<NText :depth="item.isAnonymous ? 3 : 1" style="margin-top: 3px"> <NText :depth="item.isAnonymous ? 3 : 1" style="">
{{ item.isAnonymous ? '匿名用户' : item.sender?.name }} {{ item.isAnonymous ? '匿名用户' : item.sender?.name }}
</NText> </NText>
<NTag v-if="item.isSenderRegisted" size="small" type="info" :bordered="false" style="margin-left: 5px"> <NTag v-if="item.isSenderRegisted" size="small" type="info" :bordered="false" style="margin-left: 5px">
@@ -39,6 +45,25 @@ const props = defineProps<{
<NTime :time="item.sendAt" /> <NTime :time="item.sendAt" />
</NTooltip> </NTooltip>
</NText> </NText>
<template v-if="item.reviewResult && item.reviewResult.violationType?.length > 0">
<NDivider vertical />
<NFlex size="small">
<NTag v-for="v in item.reviewResult.violationType" size="small" type="error" :bordered="false">
{{ useQA.getViolationString(v) }}
</NTag>
</NFlex>
</template>
<template v-if="item.reviewResult && item.reviewResult.saftyScore">
<NDivider vertical />
<NTooltip>
<template #trigger>
<NTag size="small" :color="{ color: '#af2525', textColor: 'white', borderColor: 'white' }">
得分: {{ item.reviewResult.saftyScore }}
</NTag>
</template>
审查得分, 满分100, 越低代表消息越8行
</NTooltip>
</template>
</NFlex> </NFlex>
</template> </template>
<template #footer> <template #footer>
@@ -52,8 +77,13 @@ const props = defineProps<{
<br /> <br />
</template> </template>
<NText style=""> <NText :style="{ filter: showContent ? '' : 'blur(3.7px)', cursor: showContent ? '' : 'pointer' }">
{{ item.question?.message }} <NButton v-if="isViolation" @click="showContent = !showContent" size="small" text>
{{ item.question?.message }}
</NButton>
<template v-else>
{{ item.question?.message }}
</template>
</NText> </NText>
<template v-if="item.answer"> <template v-if="item.answer">

View File

@@ -10,6 +10,7 @@ import { GetNotifactions } from './data/notifactions'
import router from './router' import router from './router'
import { useAuthStore } from './store/useAuthStore' import { useAuthStore } from './store/useAuthStore'
import { useVTsuruHub } from './store/useVTsuruHub' import { useVTsuruHub } from './store/useVTsuruHub'
import { useNotificationStore } from './store/useNotificationStore'
const pinia = createPinia() const pinia = createPinia()
@@ -114,6 +115,8 @@ let isHaveNewVersion = false
const { notification } = createDiscreteApi(['notification']) const { notification } = createDiscreteApi(['notification'])
useNotificationStore().init()
function InitTTS() { function InitTTS() {
try { try {
const result = EasySpeech.detect() const result = EasySpeech.detect()

View File

@@ -0,0 +1,39 @@
import { QueryGetAPI } from '@/api/query'
import { NOTIFACTION_API_URL } from '@/data/constants'
import { defineStore } from 'pinia'
import { ref } from 'vue'
export type NotificationData = {
title: string
}
export const useNotificationStore = defineStore('notification', () => {
const unread = ref<NotificationData[]>([])
const all = ref<NotificationData[]>([])
const isInited = ref(false)
async function updateUnread() {
try {
const result = await QueryGetAPI<NotificationData[]>(
NOTIFACTION_API_URL + 'get-unread'
)
if (result.code == 200) {
unread.value = result.data
}
} catch {}
}
function init() {
if (isInited.value) {
return
}
setInterval(() => {
updateUnread()
}, 10 * 1000)
isInited.value = true
}
return {
init,
unread
}
})

View File

@@ -1,5 +1,5 @@
import { useAccount } from '@/api/account' import { useAccount } from '@/api/account'
import { QAInfo } from '@/api/api-models' import { QAInfo, ViolationTypes } from '@/api/api-models'
import { QueryGetAPI, QueryPostAPI } from '@/api/query' import { QueryGetAPI, QueryPostAPI } from '@/api/query'
import { ACCOUNT_API_URL, QUESTION_API_URL } from '@/data/constants' import { ACCOUNT_API_URL, QUESTION_API_URL } from '@/data/constants'
import { List } from 'linqts' import { List } from 'linqts'
@@ -12,6 +12,8 @@ export type QATagInfo = {
createAt: number createAt: number
visiable: boolean visiable: boolean
} }
//SENSITIVE_TERM, HATE, VIOLENCE, PORNOGRAPHY, POLITICS, ADVERTISING, AGGRESSION, EMOTIONAL
export const useQuestionBox = defineStore('QuestionBox', () => { export const useQuestionBox = defineStore('QuestionBox', () => {
const isLoading = ref(false) const isLoading = ref(false)
const isRepling = ref(false) const isRepling = ref(false)
@@ -21,7 +23,9 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
const recieveQuestions = ref<QAInfo[]>([]) const recieveQuestions = ref<QAInfo[]>([])
const sendQuestions = ref<QAInfo[]>([]) const sendQuestions = ref<QAInfo[]>([])
const trashQuestions = ref<QAInfo[]>([])
const tags = ref<QATagInfo[]>([]) const tags = ref<QATagInfo[]>([])
const reviewing = ref(0)
const onlyFavorite = ref(false) const onlyFavorite = ref(false)
const onlyPublic = ref(false) const onlyPublic = ref(false)
@@ -54,18 +58,31 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
async function GetRecieveQAInfo() { async function GetRecieveQAInfo() {
isLoading.value = true isLoading.value = true
await QueryGetAPI<QAInfo[]>(QUESTION_API_URL + 'get-recieve') await QueryGetAPI<{ questions: QAInfo[]; reviewCount: number }>(
QUESTION_API_URL + 'get-recieve'
)
.then((data) => { .then((data) => {
if (data.code == 200) { if (data.code == 200) {
if (data.data.length > 0) { if (data.data.questions.length > 0) {
recieveQuestions.value = new List(data.data) recieveQuestions.value = new List(data.data.questions)
.OrderBy((d) => d.isReaded) .OrderBy((d) => d.isReaded)
//.ThenByDescending(d => d.isFavorite) //.ThenByDescending(d => d.isFavorite)
.Where(
(d) => !d.reviewResult || d.reviewResult.isApproved == true
) //只显示审核通过的
.ThenByDescending((d) => d.sendAt) .ThenByDescending((d) => d.sendAt)
.ToArray() .ToArray()
const displayId = accountInfo.value?.settings.questionDisplay.currentQuestion 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
if (displayId && displayQuestion.value?.id != displayId) { if (displayId && displayQuestion.value?.id != displayId) {
displayQuestion.value = recieveQuestions.value.find((q) => q.id == displayId) displayQuestion.value = recieveQuestions.value.find(
(q) => q.id == displayId
)
} }
} }
//message.success('共收取 ' + data.data.length + ' 条提问') //message.success('共收取 ' + data.data.length + ' 条提问')
@@ -101,12 +118,14 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
} }
async function DelQA(id: number) { async function DelQA(id: number) {
await QueryGetAPI(QUESTION_API_URL + 'del', { await QueryGetAPI(QUESTION_API_URL + 'del', {
id: id, id: id
}) })
.then((data) => { .then((data) => {
if (data.code == 200) { if (data.code == 200) {
message.success('删除成功') message.success('删除成功')
recieveQuestions.value = recieveQuestions.value.filter((q) => q.id != id) recieveQuestions.value = recieveQuestions.value.filter(
(q) => q.id != id
)
} else { } else {
message.error(data.message) message.error(data.message)
} }
@@ -118,7 +137,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
async function GetTags() { async function GetTags() {
isLoading.value = true isLoading.value = true
await QueryGetAPI<QATagInfo[]>(QUESTION_API_URL + 'get-tags', { await QueryGetAPI<QATagInfo[]>(QUESTION_API_URL + 'get-tags', {
id: accountInfo.value?.id, id: accountInfo.value?.id
}) })
.then((data) => { .then((data) => {
if (data.code == 200) { if (data.code == 200) {
@@ -134,6 +153,25 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
isLoading.value = false isLoading.value = false
}) })
} }
function getViolationString(violation: ViolationTypes) {
//SENSITIVE_TERM, HATE, VIOLENCE, PORNOGRAPHY, POLITICS, ADVERTISING, AGGRESSION
switch (violation) {
case ViolationTypes.SENSITIVE_TERM:
return '敏感词'
case ViolationTypes.HATE:
return '辱骂'
case ViolationTypes.VIOLENCE:
return '暴力'
case ViolationTypes.PORNOGRAPHY:
return '色情'
case ViolationTypes.POLITICS:
return '政治'
case ViolationTypes.ADVERTISING:
return '广告'
case ViolationTypes.AGGRESSION:
return '攻击性'
}
}
async function addTag(tag: string) { async function addTag(tag: string) {
if (!tag) { if (!tag) {
message.warning('请输入标签') message.warning('请输入标签')
@@ -144,7 +182,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
return return
} }
await QueryGetAPI(QUESTION_API_URL + 'add-tag', { await QueryGetAPI(QUESTION_API_URL + 'add-tag', {
tag: tag, tag: tag
}) })
.then((data) => { .then((data) => {
if (data.code == 200) { if (data.code == 200) {
@@ -168,7 +206,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
return return
} }
await QueryGetAPI(QUESTION_API_URL + 'del-tag', { await QueryGetAPI(QUESTION_API_URL + 'del-tag', {
tag: tag, tag: tag
}) })
.then((data) => { .then((data) => {
if (data.code == 200) { if (data.code == 200) {
@@ -193,7 +231,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
} }
await QueryGetAPI(QUESTION_API_URL + 'update-tag-visiable', { await QueryGetAPI(QUESTION_API_URL + 'update-tag-visiable', {
tag: tag, tag: tag,
visiable: visiable, visiable: visiable
}) })
.then((data) => { .then((data) => {
if (data.code == 200) { if (data.code == 200) {
@@ -211,7 +249,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
isRepling.value = true isRepling.value = true
await QueryPostAPI<QAInfo>(QUESTION_API_URL + 'reply', { await QueryPostAPI<QAInfo>(QUESTION_API_URL + 'reply', {
Id: id, Id: id,
Message: msg, Message: msg
}) })
.then((data) => { .then((data) => {
if (data.code == 200) { if (data.code == 200) {
@@ -236,7 +274,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
async function read(question: QAInfo, read: boolean) { async function read(question: QAInfo, read: boolean) {
await QueryGetAPI(QUESTION_API_URL + 'read', { await QueryGetAPI(QUESTION_API_URL + 'read', {
id: question.id, id: question.id,
read: read ? 'true' : 'false', read: read ? 'true' : 'false'
}) })
.then((data) => { .then((data) => {
if (data.code == 200) { if (data.code == 200) {
@@ -255,7 +293,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
async function favorite(question: QAInfo, fav: boolean) { async function favorite(question: QAInfo, fav: boolean) {
await QueryGetAPI(QUESTION_API_URL + 'favorite', { await QueryGetAPI(QUESTION_API_URL + 'favorite', {
id: question.id, id: question.id,
favorite: fav, favorite: fav
}) })
.then((data) => { .then((data) => {
if (data.code == 200) { if (data.code == 200) {
@@ -272,7 +310,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
isChangingPublic.value = true isChangingPublic.value = true
await QueryGetAPI(QUESTION_API_URL + 'public', { await QueryGetAPI(QUESTION_API_URL + 'public', {
id: currentQuestion.value?.id, id: currentQuestion.value?.id,
public: pub, public: pub
}) })
.then((data) => { .then((data) => {
if (data.code == 200) { if (data.code == 200) {
@@ -291,12 +329,12 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
} }
async function blacklist(question: QAInfo) { async function blacklist(question: QAInfo) {
await QueryGetAPI(ACCOUNT_API_URL + 'black-list/add', { await QueryGetAPI(ACCOUNT_API_URL + 'black-list/add', {
id: question.sender.id, id: question.sender.id
}) })
.then(async (data) => { .then(async (data) => {
if (data.code == 200) { if (data.code == 200) {
await QueryGetAPI(QUESTION_API_URL + 'del', { await QueryGetAPI(QUESTION_API_URL + 'del', {
id: question.id, id: question.id
}).then((data) => { }).then((data) => {
if (data.code == 200) { if (data.code == 200) {
message.success('已拉黑 ' + question.sender.name) message.success('已拉黑 ' + question.sender.name)
@@ -325,8 +363,8 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
isCurrent || !item isCurrent || !item
? null ? null
: { : {
id: item.id, id: item.id
}, }
) )
if (data.code == 200) { if (data.code == 200) {
//message.success('设置成功') //message.success('设置成功')
@@ -347,6 +385,8 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
recieveQuestions, recieveQuestions,
recieveQuestionsFiltered, recieveQuestionsFiltered,
sendQuestions, sendQuestions,
trashQuestions,
reviewing,
tags, tags,
onlyFavorite, onlyFavorite,
onlyPublic, onlyPublic,
@@ -366,5 +406,6 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
setPublic, setPublic,
blacklist, blacklist,
setCurrentQuestion, setCurrentQuestion,
getViolationString
} }
}) })

View File

@@ -100,7 +100,7 @@ function getOptions() {
// 用于存储粉丝增量数据 // 用于存储粉丝增量数据
const fansIncreacement: { time: Date; count: number }[] = [] const fansIncreacement: { time: Date; count: number }[] = []
// 用于存储完整的时间序列数据,包括时间、粉丝数、是否变化 // 用于存储完整的时间序列数据,包括时间、粉丝数、是否变化
const completeTimeSeries: { time: Date; count: number; change: boolean }[] = [] const completeTimeSeries: { time: Date; count: number; change: boolean, exist: boolean }[] = []
let startTime = new Date(accountInfo.value?.createAt ?? Date.now()) let startTime = new Date(accountInfo.value?.createAt ?? Date.now())
startTime = startTime < statisticStartDate ? statisticStartDate : startTime // 确保开始时间不早于统计开始时间 startTime = startTime < statisticStartDate ? statisticStartDate : startTime // 确保开始时间不早于统计开始时间
@@ -126,6 +126,7 @@ function getOptions() {
time: currentTime, time: currentTime,
count: lastDayCount, count: lastDayCount,
change: false, change: false,
exist: false,
}) })
break break
} }
@@ -138,6 +139,7 @@ function getOptions() {
time: currentTime, time: currentTime,
count: lastDayCount, count: lastDayCount,
change: changed, change: changed,
exist: true,
}) })
break break
} }
@@ -274,7 +276,7 @@ function getOptions() {
let str = '' let str = ''
for (var i = 0; i < param.length; i++) { for (var i = 0; i < param.length; i++) {
const status = const status =
param[i].seriesName == '粉丝数' ? (completeTimeSeries[param[i].dataIndex].change ? '' : '(未获取)') : '' param[i].seriesName == '粉丝数' ? (completeTimeSeries[param[i].dataIndex].exist ? '' : '(未获取)') : ''
const statusHtml = status == '' ? '' : '&nbsp;<span style="color:gray">' + status + '</span>' const statusHtml = status == '' ? '' : '&nbsp;<span style="color:gray">' + status + '</span>'
str += param[i].marker + param[i].seriesName + '' + param[i].data + statusHtml + '<br>' str += param[i].marker + param[i].seriesName + '' + param[i].data + statusHtml + '<br>'
} }

View File

@@ -1,16 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { copyToClipboard, downloadImage } from '@/Utils' import { copyToClipboard, downloadImage } from '@/Utils'
import { DisableFunction, EnableFunction, SaveAccountSettings, SaveSetting, useAccount } from '@/api/account' import { DisableFunction, EnableFunction, SaveSetting, useAccount } from '@/api/account'
import { FunctionTypes, QAInfo, Setting_QuestionDisplay } from '@/api/api-models' import { FunctionTypes, QAInfo, Setting_QuestionDisplay } from '@/api/api-models'
import { QueryGetAPI } from '@/api/query' import { CURRENT_HOST } from '@/data/constants'
import { CURRENT_HOST, QUESTION_API_URL } from '@/data/constants'
import router from '@/router' import router from '@/router'
import { Heart, HeartOutline, SwapHorizontal } from '@vicons/ionicons5' import { Heart, HeartOutline, TrashBin } from '@vicons/ionicons5'
import QuestionItem from '@/components/QuestionItem.vue'
import QuestionItems from '@/components/QuestionItems.vue'
import { useQuestionBox } from '@/store/useQuestionBox'
import { Delete24Filled, Delete24Regular, Eye24Filled, EyeOff24Filled, Info24Filled } from '@vicons/fluent'
import { useAsyncQueue, useStorage } from '@vueuse/core'
// @ts-ignore // @ts-ignore
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import html2canvas from 'html2canvas' import html2canvas from 'html2canvas'
import { import {
NAffix,
NAlert, NAlert,
NButton, NButton,
NCard, NCard,
@@ -28,11 +31,10 @@ import {
NModal, NModal,
NPagination, NPagination,
NPopconfirm, NPopconfirm,
NScrollbar,
NSelect, NSelect,
NSlider,
NSpace, NSpace,
NSpin, NSpin,
NSplit,
NSwitch, NSwitch,
NTabPane, NTabPane,
NTabs, NTabs,
@@ -40,15 +42,11 @@ import {
NText, NText,
NTime, NTime,
NTooltip, NTooltip,
useMessage, useMessage
} from 'naive-ui' } from 'naive-ui'
import QrcodeVue from 'qrcode.vue' import QrcodeVue from 'qrcode.vue'
import { computed, onMounted, ref } from 'vue' import { computed, h, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import QuestionItem from '@/components/QuestionItems.vue'
import { Delete24Filled, Delete24Regular, Eye24Filled, EyeOff24Filled, Info24Filled } from '@vicons/fluent'
import { useQuestionBox } from '@/store/useQuestionBox'
import { useStorage } from '@vueuse/core'
import QuestionDisplayCard from './QuestionDisplayCard.vue' import QuestionDisplayCard from './QuestionDisplayCard.vue'
const accountInfo = useAccount() const accountInfo = useAccount()
@@ -96,6 +94,33 @@ const savedCardSize = useStorage<{ width: number; height: number }>('Settings.Qu
let isRevieveGetted = false let isRevieveGetted = false
let isSendGetted = false let isSendGetted = false
const tempSaftyLevel = ref(accountInfo.value?.settings?.questionBox?.saftyLevel)
const remarkLevel = {
0: () => h(NFlex, { align: 'center', justify: 'center', size: 3 }, () => [
'无',
h(NTooltip, null, { trigger: () => h(NIcon, { component: Info24Filled, color: '#c2e77f' }), default: () => '完全关闭内容审查机制,用户可自由提问,系统不会进行任何内容过滤' }),
]),
1: () => h(NFlex, { align: 'center', justify: 'center', size: 3 }, () => [
'宽松',
h(NTooltip, null, { trigger: () => h(NIcon, { component: Info24Filled, color: '#e1d776' }), default: () => '基础内容审查,仅过滤极端攻击性、暴力或违法内容,保留大部分用户提问 (得分 > 30)' }),
]),
2: () => h(NFlex, { align: 'center', justify: 'center', size: 3 }, () => [
'一般',
h(NTooltip, null, { trigger: () => h(NIcon, { component: Info24Filled, color: '#ef956d' }), default: () => '适度内容审查,就比较一般 (得分 > 60)' }),
]),
3: () => h(NFlex, { align: 'center', justify: 'center', size: 3, wrap: false }, () => [
'严格',
h(NTooltip, null, { trigger: () => h(NIcon, { component: Info24Filled, color: '#ea6262' }), default: () => '最高级别内容审查,禁止任何嘴臭 (得分 > 90)' }),
]),
}
const remarkLevelString: { [key: number]: string } = {
0: '无',
1: '宽松',
2: '一般',
3: '严格',
}
async function onTabChange(value: string) { async function onTabChange(value: string) {
return return
@@ -146,7 +171,7 @@ function saveQRCode() {
downloadImage(`https://api.qrserver.com/v1/create-qr-code/?data=${shareUrl.value}`, 'vtsuru-提问箱二维码.png') downloadImage(`https://api.qrserver.com/v1/create-qr-code/?data=${shareUrl.value}`, 'vtsuru-提问箱二维码.png')
} }
async function saveSettings() { async function saveSettings() {
useQB.isLoading = true //useQB.isLoading = true
await SaveSetting('QuestionBox', accountInfo.value.settings.questionBox) await SaveSetting('QuestionBox', accountInfo.value.settings.questionBox)
.then((msg) => { .then((msg) => {
if (msg) { if (msg) {
@@ -157,7 +182,7 @@ async function saveSettings() {
} }
}) })
.finally(() => { .finally(() => {
useQB.isLoading = false //useQB.isLoading = false
}) })
} }
@@ -204,8 +229,23 @@ onMounted(() => {
前往提问页 前往提问页
</NButton> </NButton>
<NButton @click="showOBSModal = true" type="primary" secondary> 预览OBS组件 </NButton> <NButton @click="showOBSModal = true" type="primary" secondary> 预览OBS组件 </NButton>
<NAlert type="success" style="max-width: 550px;" closable>
2025.3.1 本站已支持内容审查, 可前往提问箱设置页进行开启
<NTooltip>
<template #trigger>
<NIcon :component="Info24Filled" />
</template>
新功能还不稳定, 如果启用后遇到任何问题请向我反馈
</NTooltip>
</NAlert>
</NSpace> </NSpace>
<NDivider style="margin: 10px 0 10px 0" /> <NDivider style="margin: 10px 0 10px 0" />
<template v-if="useQB.reviewing > 0">
<NAlert type="warning" title="有提问正在审核中">
还剩余 {{ useQB.reviewing }}
</NAlert>
<NDivider style="margin: 10px 0 10px 0" />
</template>
<NSpin v-if="useQB.isLoading" show /> <NSpin v-if="useQB.isLoading" show />
<NTabs v-else animated @update:value="onTabChange" v-model:value="selectedTabItem"> <NTabs v-else animated @update:value="onTabChange" v-model:value="selectedTabItem">
<NTabPane tab="我收到的" name="0" display-directive="show:lazy"> <NTabPane tab="我收到的" name="0" display-directive="show:lazy">
@@ -227,7 +267,7 @@ onMounted(() => {
<NPagination v-model:page="pn" v-model:page-size="ps" :item-count="useQB.recieveQuestionsFiltered.length" <NPagination v-model:page="pn" v-model:page-size="ps" :item-count="useQB.recieveQuestionsFiltered.length"
show-quick-jumper show-size-picker :page-sizes="[20, 50, 100]" /> show-quick-jumper show-size-picker :page-sizes="[20, 50, 100]" />
<NDivider style="margin: 10px 0 10px 0" /> <NDivider style="margin: 10px 0 10px 0" />
<QuestionItem :questions="pagedQuestions"> <QuestionItems :questions="pagedQuestions">
<template #footer="{ item }"> <template #footer="{ item }">
<NSpace> <NSpace>
<NButton v-if="!item.isReaded" size="small" @click="useQB.read(item, true)" type="success"> <NButton v-if="!item.isReaded" size="small" @click="useQB.read(item, true)" type="success">
@@ -250,7 +290,7 @@ onMounted(() => {
删除 删除
</NButton> </NButton>
</template> </template>
确认删除这条提问 确认删除这条提问 删除后无法恢复
</NPopconfirm> </NPopconfirm>
<!-- <NTooltip> <!-- <NTooltip>
<template #trigger> <template #trigger>
@@ -266,7 +306,7 @@ onMounted(() => {
{{ item.answer ? '查看回复' : '回复' }} {{ item.answer ? '查看回复' : '回复' }}
</NButton> </NButton>
</template> </template>
</QuestionItem> </QuestionItems>
<NDivider style="margin: 10px 0 10px 0" /> <NDivider style="margin: 10px 0 10px 0" />
<NPagination v-model:page="pn" v-model:page-size="ps" :item-count="useQB.recieveQuestionsFiltered.length" <NPagination v-model:page="pn" v-model:page-size="ps" :item-count="useQB.recieveQuestionsFiltered.length"
show-quick-jumper show-size-picker :page-sizes="[20, 50, 100]" /> show-quick-jumper show-size-picker :page-sizes="[20, 50, 100]" />
@@ -315,13 +355,60 @@ onMounted(() => {
</NListItem> </NListItem>
</NList> </NList>
</NTabPane> </NTabPane>
<NTabPane tab="设置" name="2" display-directive="show:lazy"> <NTabPane tab="垃圾站" name="2" display-directive="show:lazy">
<template #prefix>
<NIcon :component="TrashBin" />
</template>
<NEmpty v-if="useQB.trashQuestions.length == 0" description="暂无被过滤的提问" />
<NList v-else>
<NListItem v-for="question in useQB.trashQuestions" :key="question.id">
<QuestionItem :item="question">
<template #footer="{ item }">
<NSpace>
<NPopconfirm @positive-click="useQB.DelQA(item.id)">
<template #trigger>
<NButton size="small" type="error">
<template #icon>
<NIcon :component="Delete24Filled" />
</template>
删除
</NButton>
</template>
确认删除这条提问 删除后无法恢复
</NPopconfirm>
<!-- <NTooltip>
<template #trigger>
<NButton size="small"> 举报 </NButton>
</template>
暂时还没写
</NTooltip> -->
<NButton size="small" @click="useQB.blacklist(item)" type="warning"> 拉黑 </NButton>
</NSpace>
</template>
<template #header-extra="{ item }">
<NButton @click="onOpenModal(item)" :type="item.isReaded ? 'default' : 'info'" :secondary="item.isReaded">
{{ item.answer ? '查看回复' : '回复' }}
</NButton>
</template>
</QuestionItem>
</NListItem>
</NList>
</NTabPane>
<NTabPane tab="设置" name="3" display-directive="show:lazy">
<NDivider> 设定 </NDivider> <NDivider> 设定 </NDivider>
<NSpin :show="useQB.isLoading"> <NSpin :show="useQB.isLoading">
<NCheckbox v-model:checked="accountInfo.settings.questionBox.allowUnregistedUser" <NCheckbox v-model:checked="accountInfo.settings.questionBox.allowUnregistedUser"
@update:checked="saveSettings"> @update:checked="saveSettings">
允许未注册用户进行提问 允许未注册用户进行提问
</NCheckbox> </NCheckbox>
<NDivider> 内容审查
<NDivider vertical />
<NTag type="success" :bordered="false" size="tiny"></NTag>
</NDivider>
<NSlider v-model:value="tempSaftyLevel"
@dragend="() => { accountInfo.settings.questionBox.saftyLevel = tempSaftyLevel; saveSettings() }"
:marks="remarkLevel" step="mark" :max="3" style="max-width: 80%; margin: 0 auto"
:format-tooltip="(v) => remarkLevelString[v]" />
<NDivider> <NDivider>
标签 标签
<NTooltip> <NTooltip>