mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-12 09:11:13 +08:00
feat: 截屏功能优化
This commit is contained in:
@@ -67,9 +67,11 @@ export function getMentionAnalysis(sessionId: string, filter?: TimeFilter): any
|
||||
|
||||
let whereClause = clause
|
||||
if (whereClause.includes('WHERE')) {
|
||||
whereClause += " AND COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND msg.content LIKE '%@%'"
|
||||
whereClause +=
|
||||
" AND COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND msg.content LIKE '%@%'"
|
||||
} else {
|
||||
whereClause = " WHERE COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND msg.content LIKE '%@%'"
|
||||
whereClause =
|
||||
" WHERE COALESCE(m.account_name, '') != '系统消息' AND msg.type = 0 AND msg.content IS NOT NULL AND msg.content LIKE '%@%'"
|
||||
}
|
||||
|
||||
const messages = db
|
||||
@@ -151,7 +153,7 @@ export function getMentionAnalysis(sessionId: string, filter?: TimeFilter): any
|
||||
}
|
||||
topMentioned.sort((a, b) => b.count - a.count)
|
||||
|
||||
// 6. 检测单向关注(舔狗检测)
|
||||
// 6. 检测单向关注
|
||||
// 条件:A @ B 的比例 >= 80%(即 B @ A / A @ B < 20%)
|
||||
const oneWay: any[] = []
|
||||
const processedPairs = new Set<string>()
|
||||
@@ -500,4 +502,3 @@ export function getLaughAnalysis(sessionId: string, filter?: TimeFilter, keyword
|
||||
groupLaughRate: Math.round((totalLaughs / totalMessages) * 10000) / 100,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ const subTabs = [
|
||||
{
|
||||
id: 'campus',
|
||||
label: '阵营9宫格',
|
||||
desc: '和朋友们聊天的时候产生的一个有趣的想法,群里偶尔会很认真的讨论某个话题,大家都聊的很认真,那么是不是可以让AI分析聊天记录,然后针对这个话题,让AI用 守序善良/绝对中立/守序邪恶/混乱邪恶这样的九宫格把群友划分到对应的格子里面',
|
||||
desc: '和朋友们聊天的时候产生的一个有趣的想法,群里偶尔会很认真的讨论某个话题,那么是不是可以让AI分析聊天记录,然后针对这个话题,让AI用 守序善良/绝对中立/守序邪恶/混乱邪恶 这样的九宫格把群友划分到对应的格子里面',
|
||||
icon: 'i-heroicons-squares-2x2',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import CaptureButton from '@/components/common/CaptureButton.vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -23,6 +24,10 @@ const props = withDefaults(
|
||||
// 控制弹窗
|
||||
const isOpen = ref(false)
|
||||
|
||||
// 截屏相关 ref
|
||||
const cardRef = ref<HTMLElement | null>(null)
|
||||
const modalBodyRef = ref<HTMLElement | null>(null)
|
||||
|
||||
// Top N 数据
|
||||
const topNData = computed(() => props.items.slice(0, props.topN))
|
||||
|
||||
@@ -34,30 +39,40 @@ const formattedCount = computed(() => props.countTemplate.replace('{count}', Str
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900">
|
||||
<div ref="cardRef" class="rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900">
|
||||
<div class="flex items-center justify-between border-b border-gray-200 px-5 py-3 dark:border-gray-800">
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white">{{ title }}</h3>
|
||||
<h3 class="font-semibold text-gray-900 whitespace-nowrap dark:text-white">{{ title }}</h3>
|
||||
<p v-if="description" class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ description }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="no-capture flex items-center gap-2">
|
||||
<!-- 自定义头部右侧内容 -->
|
||||
<slot name="headerRight" />
|
||||
|
||||
<!-- 卡片截屏按钮 -->
|
||||
<CaptureButton tooltip="截取列表" size="xs" type="element" :target-element="cardRef" />
|
||||
|
||||
<!-- 完整列表弹窗 -->
|
||||
<UModal v-model:open="isOpen" :ui="{ content: 'md:w-full max-w-4xl' }">
|
||||
<UButton v-if="showViewAll" icon="i-heroicons-list-bullet" variant="ghost">查看完整排行</UButton>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">{{ title }}</h3>
|
||||
<span class="text-sm text-gray-500">({{ formattedCount }})</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="max-h-[60vh] divide-y divide-gray-100 overflow-y-auto dark:divide-gray-800">
|
||||
<div v-for="(item, index) in items" :key="index" class="px-5 py-3">
|
||||
<slot name="item" :item="item" :index="index" />
|
||||
<UButton v-if="showViewAll" icon="i-heroicons-list-bullet" variant="ghost">完整排行</UButton>
|
||||
<template #content>
|
||||
<div ref="modalBodyRef" class="section-content flex flex-col">
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="flex w-full items-center justify-between border-b border-gray-200 px-6 py-4 dark:border-gray-700"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900 whitespace-nowrap dark:text-white">{{ title }}</h3>
|
||||
<span class="text-sm text-gray-500">({{ formattedCount }})</span>
|
||||
</div>
|
||||
<CaptureButton tooltip="截取完整列表" size="xs" type="element" :target-element="modalBodyRef" />
|
||||
</div>
|
||||
<!-- Body -->
|
||||
<div class="max-h-[60vh] divide-y divide-gray-100 overflow-y-auto dark:divide-gray-800">
|
||||
<div v-for="(item, index) in items" :key="index" class="px-5 py-3">
|
||||
<slot name="item" :item="item" :index="index" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import RankList from './RankList.vue'
|
||||
import type { RankItem } from './RankList.vue'
|
||||
import CaptureButton from '@/components/common/CaptureButton.vue'
|
||||
|
||||
interface Props {
|
||||
/** 完整的排行数据 */
|
||||
@@ -24,6 +25,10 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
// 控制弹窗
|
||||
const isOpen = ref(false)
|
||||
|
||||
// 截屏相关 ref
|
||||
const cardRef = ref<HTMLElement | null>(null)
|
||||
const modalBodyRef = ref<HTMLElement | null>(null)
|
||||
|
||||
// Top N 数据
|
||||
const topNData = computed(() => {
|
||||
return props.members.slice(0, props.topN)
|
||||
@@ -36,28 +41,40 @@ const showViewAll = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900">
|
||||
<div ref="cardRef" class="rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900">
|
||||
<div class="flex items-center justify-between border-b border-gray-200 px-5 py-3 dark:border-gray-800">
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white">{{ title }}</h3>
|
||||
<h3 class="font-semibold text-gray-900 whitespace-nowrap dark:text-white">{{ title }}</h3>
|
||||
<p v-if="description" class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ description }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 完整排行榜 Dialog -->
|
||||
<UModal v-model:open="isOpen" :ui="{ content: 'md:w-full max-w-3xl' }">
|
||||
<UButton v-if="showViewAll" icon="i-heroicons-list-bullet" variant="ghost">查看完整排行</UButton>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">{{ title }}</h3>
|
||||
<span class="text-sm text-gray-500">(共 {{ members.length }} 位成员)</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="max-h-[60vh] overflow-y-auto">
|
||||
<RankList :members="members" :unit="unit" />
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
<div class="no-capture flex items-center gap-1">
|
||||
<!-- 卡片截屏按钮 -->
|
||||
<CaptureButton tooltip="截取当前卡片" size="xs" type="element" :target-element="cardRef" />
|
||||
|
||||
<!-- 完整排行榜 Dialog -->
|
||||
<UModal v-model:open="isOpen" :ui="{ content: 'md:w-full max-w-3xl' }">
|
||||
<UButton v-if="showViewAll" icon="i-heroicons-list-bullet" variant="ghost">完整排行</UButton>
|
||||
<template #content>
|
||||
<div ref="modalBodyRef" class="section-content flex flex-col">
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="flex w-full items-center justify-between border-b border-gray-200 px-6 py-4 dark:border-gray-700"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900 whitespace-nowrap dark:text-white">{{ title }}</h3>
|
||||
<span class="text-sm text-gray-500">(共 {{ members.length }} 位成员)</span>
|
||||
</div>
|
||||
<CaptureButton tooltip="截取完整排行" size="xs" type="element" :target-element="modalBodyRef" />
|
||||
</div>
|
||||
<!-- Body -->
|
||||
<div class="max-h-[60vh] overflow-y-auto">
|
||||
<RankList :members="members" :unit="unit" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RankList :members="topNData" :unit="unit" />
|
||||
|
||||
@@ -64,7 +64,7 @@ async function handleCapture(event: Event) {
|
||||
:id="buttonId"
|
||||
icon="i-heroicons-camera"
|
||||
variant="ghost"
|
||||
color="neutral"
|
||||
color="primary"
|
||||
:size="size"
|
||||
:loading="isCapturing"
|
||||
@click="handleCapture"
|
||||
|
||||
@@ -25,7 +25,7 @@ function closeModal() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal v-model:open="isOpen" :ui="{ content: 'max-w-5xl' }">
|
||||
<UModal v-model:open="isOpen" :ui="{ content: 'max-w-5xl z-100' }">
|
||||
<template #content>
|
||||
<div class="flex flex-col">
|
||||
<!-- Header -->
|
||||
|
||||
@@ -139,7 +139,7 @@ export function useAIChat(
|
||||
messages.value.push({
|
||||
id: generateId('error'),
|
||||
role: 'assistant',
|
||||
content: '⚠️ 请先配置 AI 服务。点击左下角「设置」按钮前往「AI模型Tab」进行配置。',
|
||||
content: '⚠️ 请先配置 AI 服务。点击左下角「设置」按钮前往「模型配置Tab」进行配置。',
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
return
|
||||
|
||||
@@ -156,18 +156,25 @@ export function useScreenCapture() {
|
||||
watermark.textContent = '聊天分析实验室 · chatlab.fun'
|
||||
element.appendChild(watermark)
|
||||
|
||||
// 保存原始 display 状态并隐藏指定元素(使用 display: none 完全移除占位)
|
||||
const hiddenElements: { el: HTMLElement; originalDisplay: string }[] = []
|
||||
// 隐藏指定元素(使用临时 class 而不是 inline style,避免恢复问题)
|
||||
const hiddenElements: HTMLElement[] = []
|
||||
const HIDDEN_CLASS = '__capture-hidden__'
|
||||
|
||||
// 注入隐藏样式(如果不存在)
|
||||
let styleTag = document.getElementById('__capture-style__')
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement('style')
|
||||
styleTag.id = '__capture-style__'
|
||||
styleTag.textContent = `.${HIDDEN_CLASS} { display: none !important; }`
|
||||
document.head.appendChild(styleTag)
|
||||
}
|
||||
|
||||
// 隐藏带有 .no-capture class 的元素(通用排除规则)
|
||||
const noCaptureElements = element.querySelectorAll('.no-capture')
|
||||
noCaptureElements.forEach((el) => {
|
||||
const htmlEl = el as HTMLElement
|
||||
hiddenElements.push({
|
||||
el: htmlEl,
|
||||
originalDisplay: htmlEl.style.display,
|
||||
})
|
||||
htmlEl.style.display = 'none'
|
||||
hiddenElements.push(htmlEl)
|
||||
htmlEl.classList.add(HIDDEN_CLASS)
|
||||
})
|
||||
|
||||
// 隐藏用户指定的选择器元素
|
||||
@@ -176,26 +183,55 @@ export function useScreenCapture() {
|
||||
const elements = document.querySelectorAll(selector)
|
||||
elements.forEach((el) => {
|
||||
const htmlEl = el as HTMLElement
|
||||
hiddenElements.push({
|
||||
el: htmlEl,
|
||||
originalDisplay: htmlEl.style.display,
|
||||
})
|
||||
htmlEl.style.display = 'none'
|
||||
hiddenElements.push(htmlEl)
|
||||
htmlEl.classList.add(HIDDEN_CLASS)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 如果需要捕获完整内容,临时移除 overflow 限制
|
||||
const fullContent = options?.fullContent !== false
|
||||
const overflowElements: { el: HTMLElement; originalOverflow: string; originalHeight: string }[] = []
|
||||
const overflowElements: { el: HTMLElement; originalOverflow: string; originalHeight: string; originalMaxHeight: string }[] = []
|
||||
|
||||
if (fullContent) {
|
||||
// 找到所有有 overflow 限制的祖先元素和目标元素本身
|
||||
let node: HTMLElement | null = element
|
||||
while (node) {
|
||||
// 处理目标元素及其所有子元素的 overflow 和 max-height 限制
|
||||
const elementsWithOverflow = [element, ...Array.from(element.querySelectorAll('*'))] as HTMLElement[]
|
||||
for (const node of elementsWithOverflow) {
|
||||
const style = window.getComputedStyle(node)
|
||||
const overflow = style.overflow
|
||||
const overflowY = style.overflowY
|
||||
const maxHeight = style.maxHeight
|
||||
|
||||
if (
|
||||
overflow === 'hidden' ||
|
||||
overflow === 'auto' ||
|
||||
overflow === 'scroll' ||
|
||||
overflowY === 'hidden' ||
|
||||
overflowY === 'auto' ||
|
||||
overflowY === 'scroll' ||
|
||||
(maxHeight !== 'none' && maxHeight !== '0px')
|
||||
) {
|
||||
overflowElements.push({
|
||||
el: node,
|
||||
originalOverflow: node.style.overflow,
|
||||
originalHeight: node.style.height,
|
||||
originalMaxHeight: node.style.maxHeight,
|
||||
})
|
||||
node.style.overflow = 'visible'
|
||||
node.style.maxHeight = 'none'
|
||||
// 如果有固定高度,也需要临时移除
|
||||
if (style.height !== 'auto' && node.scrollHeight > node.clientHeight) {
|
||||
node.style.height = 'auto'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 也处理祖先元素
|
||||
let parent: HTMLElement | null = element.parentElement
|
||||
while (parent) {
|
||||
const style = window.getComputedStyle(parent)
|
||||
const overflow = style.overflow
|
||||
const overflowY = style.overflowY
|
||||
if (
|
||||
overflow === 'hidden' ||
|
||||
overflow === 'auto' ||
|
||||
@@ -205,17 +241,14 @@ export function useScreenCapture() {
|
||||
overflowY === 'scroll'
|
||||
) {
|
||||
overflowElements.push({
|
||||
el: node,
|
||||
originalOverflow: node.style.overflow,
|
||||
originalHeight: node.style.height,
|
||||
el: parent,
|
||||
originalOverflow: parent.style.overflow,
|
||||
originalHeight: parent.style.height,
|
||||
originalMaxHeight: parent.style.maxHeight,
|
||||
})
|
||||
node.style.overflow = 'visible'
|
||||
// 如果有固定高度,也需要临时移除
|
||||
if (style.height !== 'auto' && node.scrollHeight > node.clientHeight) {
|
||||
node.style.height = 'auto'
|
||||
}
|
||||
parent.style.overflow = 'visible'
|
||||
}
|
||||
node = node.parentElement
|
||||
parent = parent.parentElement
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,16 +265,14 @@ export function useScreenCapture() {
|
||||
canvas.style.border = 'none'
|
||||
})
|
||||
|
||||
// 修复 Markdown 标题元素在 @zumer/snapdom 中的黑色边框和额外高度问题
|
||||
// 修复 Markdown 标题元素在 @zumer/snapdom 中的黑色边框问题
|
||||
// 注意:不修改 background,以保留渐变文字等效果
|
||||
const headingElements: {
|
||||
el: HTMLElement
|
||||
originalStyles: {
|
||||
border: string
|
||||
outline: string
|
||||
boxShadow: string
|
||||
background: string
|
||||
margin: string
|
||||
padding: string
|
||||
}
|
||||
}[] = []
|
||||
const headings = element.querySelectorAll('h1, h2, h3, h4, h5, h6')
|
||||
@@ -253,18 +284,12 @@ export function useScreenCapture() {
|
||||
border: htmlEl.style.border,
|
||||
outline: htmlEl.style.outline,
|
||||
boxShadow: htmlEl.style.boxShadow,
|
||||
background: htmlEl.style.background,
|
||||
margin: htmlEl.style.margin,
|
||||
padding: htmlEl.style.padding,
|
||||
},
|
||||
})
|
||||
// 清除所有可能导致 snapdom 渲染问题的样式
|
||||
// 只清除边框相关样式,保留 background/margin/padding
|
||||
htmlEl.style.border = 'none'
|
||||
htmlEl.style.outline = 'none'
|
||||
htmlEl.style.boxShadow = 'none'
|
||||
htmlEl.style.background = 'transparent'
|
||||
htmlEl.style.margin = '0.5em 0'
|
||||
htmlEl.style.padding = '0'
|
||||
})
|
||||
|
||||
// 修复 Markdown 列表元素在 @zumer/snapdom 中的渲染问题
|
||||
@@ -408,9 +433,6 @@ export function useScreenCapture() {
|
||||
el.style.border = originalStyles.border
|
||||
el.style.outline = originalStyles.outline
|
||||
el.style.boxShadow = originalStyles.boxShadow
|
||||
el.style.background = originalStyles.background
|
||||
el.style.margin = originalStyles.margin
|
||||
el.style.padding = originalStyles.padding
|
||||
}
|
||||
// 恢复列表元素样式并移除手动添加的前缀(@zumer/snapdom bug workaround)
|
||||
for (const { el, originalStyles, addedPrefixes } of listElements) {
|
||||
@@ -426,13 +448,14 @@ export function useScreenCapture() {
|
||||
}
|
||||
}
|
||||
// 恢复 overflow 设置
|
||||
for (const { el, originalOverflow, originalHeight } of overflowElements) {
|
||||
for (const { el, originalOverflow, originalHeight, originalMaxHeight } of overflowElements) {
|
||||
el.style.overflow = originalOverflow
|
||||
el.style.height = originalHeight
|
||||
el.style.maxHeight = originalMaxHeight
|
||||
}
|
||||
// 恢复隐藏元素的 display
|
||||
for (const { el, originalDisplay } of hiddenElements) {
|
||||
el.style.display = originalDisplay
|
||||
// 恢复隐藏元素
|
||||
for (const el of hiddenElements) {
|
||||
el.classList.remove('__capture-hidden__')
|
||||
}
|
||||
isCapturing.value = false
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ const props = defineProps<{
|
||||
// 计算赛季标题
|
||||
const seasonTitle = computed(() => {
|
||||
if (props.selectedYear && props.selectedYear > 0) {
|
||||
return `${props.selectedYear} 赛季`
|
||||
return `${props.selectedYear} 赛季群榜单`
|
||||
}
|
||||
// 全部时间:显示年份范围
|
||||
if (props.availableYears && props.availableYears.length > 0) {
|
||||
@@ -37,11 +37,11 @@ const seasonTitle = computed(() => {
|
||||
const minYear = sorted[0]
|
||||
const maxYear = sorted[sorted.length - 1]
|
||||
if (minYear === maxYear) {
|
||||
return `${minYear} 赛季`
|
||||
return `${minYear} 赛季群榜单`
|
||||
}
|
||||
return `${minYear}-${maxYear} 赛季`
|
||||
return `${minYear}-${maxYear} 赛季群榜单`
|
||||
}
|
||||
return '全部赛季'
|
||||
return '全部赛季群榜单'
|
||||
})
|
||||
|
||||
// 锚点导航配置
|
||||
@@ -82,7 +82,7 @@ const memberRankData = computed<RankItem[]>(() => {
|
||||
>
|
||||
🏆 {{ seasonTitle }}
|
||||
</h1>
|
||||
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400">各榜单前三名请 @群主 领取奖品 🎁</p>
|
||||
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400">各榜单前三名请 @群主 领取奖励 🎁</p>
|
||||
</div>
|
||||
|
||||
<!-- 龙王排名 -->
|
||||
|
||||
@@ -97,8 +97,10 @@ watch(
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
<!-- 单向关注(舔狗检测) -->
|
||||
<!-- 单向关注 -->
|
||||
<!-- 有严重BUG,很不准,先隐藏 -->
|
||||
<SectionCard
|
||||
class="hidden"
|
||||
v-if="mentionAnalysis.oneWay.length > 0"
|
||||
title="🐕 单向关注检测"
|
||||
:description="`发现 ${mentionAnalysis.oneWay.length} 对单向关注关系(一方 @ 另一方占比 ≥80%)`"
|
||||
@@ -276,4 +278,3 @@ watch(
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
+2
-2
@@ -560,7 +560,7 @@ export interface MentionPair {
|
||||
}
|
||||
|
||||
/**
|
||||
* 单向关注(舔狗检测)
|
||||
* 单向关注
|
||||
*/
|
||||
export interface OneWayMention {
|
||||
fromMemberId: number
|
||||
@@ -606,7 +606,7 @@ export interface MentionAnalysis {
|
||||
topMentioners: MentionRankItem[]
|
||||
/** 被 @ 最多的人排行 */
|
||||
topMentioned: MentionRankItem[]
|
||||
/** 单向关注列表(舔狗检测) */
|
||||
/** 单向关注列表 */
|
||||
oneWay: OneWayMention[]
|
||||
/** 双向奔赴列表(CP检测) */
|
||||
twoWay: TwoWayMention[]
|
||||
|
||||
Reference in New Issue
Block a user