mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
add text review
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
39
src/store/useNotificationStore.ts
Normal file
39
src/store/useNotificationStore.ts
Normal 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
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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 == '' ? '' : ' <span style="color:gray">' + status + '</span>'
|
const statusHtml = status == '' ? '' : ' <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>'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user