feat: add monitoring marked articles and retention updates
Frontend CI / Frontend (push) Successful in 7m47s
Backend CI / Backend (push) Successful in 19m26s

This commit is contained in:
2026-06-17 12:48:41 +08:00
parent 9ed857e159
commit 31c4dd9358
40 changed files with 2373 additions and 488 deletions
@@ -0,0 +1,472 @@
<script setup lang="ts">
import {
DeleteOutlined,
EditOutlined,
EyeOutlined,
PlusOutlined,
ReloadOutlined,
SearchOutlined,
} from '@ant-design/icons-vue'
import type { MonitoringMarkedArticle, MonitoringMarkedArticleRequest } from '@geo/shared-types'
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import type { TableColumnsType, TablePaginationConfig } from 'ant-design-vue'
import { message, Modal } from 'ant-design-vue'
import type { Dayjs } from 'dayjs'
import { computed, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import PageHero from '@/components/PageHero.vue'
import { monitoringApi } from '@/lib/api'
import { formatDateTime } from '@/lib/display'
import { formatError } from '@/lib/errors'
import { useCompanyStore } from '@/stores/company'
const { t } = useI18n()
const queryClient = useQueryClient()
const companyStore = useCompanyStore()
const page = ref(1)
const pageSize = ref(10)
const createdRange = ref<[Dayjs, Dayjs] | null>(null)
const draftFilters = reactive({
domain: '',
title: '',
})
const appliedFilters = reactive({
domain: '',
title: '',
created_from: '',
created_to: '',
})
const modalOpen = ref(false)
const editingArticle = ref<MonitoringMarkedArticle | null>(null)
const formState = reactive({
article_title: '',
original_url: '',
})
const listParams = computed(() => ({
page: page.value,
page_size: pageSize.value,
domain: appliedFilters.domain || undefined,
title: appliedFilters.title || undefined,
created_from: appliedFilters.created_from || undefined,
created_to: appliedFilters.created_to || undefined,
}))
const markedArticlesQuery = useQuery({
queryKey: computed(() => ['tracking', 'marked-articles', listParams.value]),
queryFn: () => monitoringApi.listMarkedArticles(listParams.value),
})
const markedArticles = computed(() => markedArticlesQuery.data.value?.items ?? [])
const pageInfo = computed(() => markedArticlesQuery.data.value?.page)
const total = computed(() => pageInfo.value?.total ?? 0)
const modalTitle = computed(() =>
editingArticle.value
? t('trackingMarkedArticles.editTitle')
: t('trackingMarkedArticles.createTitle'),
)
const columns = computed<TableColumnsType<MonitoringMarkedArticle>>(() => [
{
title: t('trackingMarkedArticles.columns.articleTitle'),
dataIndex: 'article_title',
key: 'article_title',
width: 320,
},
{
title: t('trackingMarkedArticles.columns.domain'),
dataIndex: 'registrable_domain',
key: 'registrable_domain',
width: 180,
},
{
title: t('trackingMarkedArticles.columns.url'),
dataIndex: 'original_url',
key: 'original_url',
ellipsis: true,
},
{
title: t('trackingMarkedArticles.columns.markedAt'),
dataIndex: 'marked_at',
key: 'marked_at',
width: 180,
},
{
title: t('trackingMarkedArticles.columns.expiresAt'),
dataIndex: 'expires_at',
key: 'expires_at',
width: 180,
},
{
title: t('common.actions'),
key: 'actions',
width: 140,
fixed: 'right',
align: 'right' as const,
},
])
const createMutation = useMutation({
mutationFn: (payload: MonitoringMarkedArticleRequest) =>
monitoringApi.createMarkedArticle(payload),
onSuccess: async () => {
message.success(t('trackingMarkedArticles.messages.saved'))
closeModal()
await invalidateMarkedArticles()
},
onError: (error) => message.error(formatError(error)),
})
const updateMutation = useMutation({
mutationFn: (payload: { id: number; body: MonitoringMarkedArticleRequest }) =>
monitoringApi.updateMarkedArticle(payload.id, payload.body),
onSuccess: async () => {
message.success(t('trackingMarkedArticles.messages.saved'))
closeModal()
await invalidateMarkedArticles()
},
onError: (error) => message.error(formatError(error)),
})
const deleteMutation = useMutation({
mutationFn: (id: number) => monitoringApi.deleteMarkedArticle(id),
onSuccess: async () => {
message.success(t('trackingMarkedArticles.messages.deleted'))
await invalidateMarkedArticles()
},
onError: (error) => message.error(formatError(error)),
})
const saving = computed(() => createMutation.isPending.value || updateMutation.isPending.value)
function applyFilters(): void {
page.value = 1
appliedFilters.domain = draftFilters.domain.trim()
appliedFilters.title = draftFilters.title.trim()
appliedFilters.created_from = createdRange.value?.[0]?.toISOString() ?? ''
appliedFilters.created_to = createdRange.value?.[1]?.toISOString() ?? ''
}
function resetFilters(): void {
draftFilters.domain = ''
draftFilters.title = ''
createdRange.value = null
applyFilters()
}
function openCreateModal(): void {
editingArticle.value = null
formState.article_title = ''
formState.original_url = ''
modalOpen.value = true
}
function openEditModal(record: MonitoringMarkedArticle): void {
editingArticle.value = record
formState.article_title = record.article_title
formState.original_url = record.original_url
modalOpen.value = true
}
function closeModal(): void {
modalOpen.value = false
editingArticle.value = null
formState.article_title = ''
formState.original_url = ''
}
async function submitForm(): Promise<void> {
const payload: MonitoringMarkedArticleRequest = {
article_title: formState.article_title.trim(),
original_url: formState.original_url.trim(),
brand_id: companyStore.currentBrandId ?? null,
}
if (!payload.article_title || !payload.original_url) {
message.warning(t('trackingMarkedArticles.messages.required'))
return
}
if (editingArticle.value) {
await updateMutation.mutateAsync({ id: editingArticle.value.id, body: payload })
return
}
await createMutation.mutateAsync(payload)
}
function confirmDelete(record: MonitoringMarkedArticle): void {
Modal.confirm({
title: t('trackingMarkedArticles.deleteConfirmTitle'),
content: record.article_title,
okText: t('common.delete'),
okType: 'danger',
cancelText: t('common.cancel'),
onOk: () => deleteMutation.mutateAsync(record.id),
})
}
function openURL(url: string): void {
const normalized = url.trim()
if (!normalized) {
return
}
window.open(normalized, '_blank', 'noopener,noreferrer')
}
function handleTableChange(pagination: TablePaginationConfig): void {
page.value = pagination.current ?? 1
pageSize.value = pagination.pageSize ?? pageSize.value
}
function renderTotal(count: number): string {
return t('trackingMarkedArticles.total', { count })
}
async function invalidateMarkedArticles(): Promise<void> {
await Promise.all([
queryClient.invalidateQueries({ queryKey: ['tracking', 'marked-articles'] }),
queryClient.invalidateQueries({ queryKey: ['tracking'] }),
])
}
</script>
<template>
<section class="marked-articles-page">
<PageHero
:title="t('trackingMarkedArticles.title')"
:description="t('trackingMarkedArticles.description')"
>
<template #actions>
<a-button @click="markedArticlesQuery.refetch()">
<template #icon><ReloadOutlined /></template>
{{ t('common.refresh') }}
</a-button>
<a-button type="primary" @click="openCreateModal">
<template #icon><PlusOutlined /></template>
{{ t('trackingMarkedArticles.createAction') }}
</a-button>
</template>
</PageHero>
<a-card class="marked-articles-card">
<div class="marked-articles-filters">
<a-input
v-model:value="draftFilters.domain"
allow-clear
class="marked-articles-filters__domain"
:placeholder="t('trackingMarkedArticles.filters.domain')"
@pressEnter="applyFilters"
>
<template #prefix><SearchOutlined /></template>
</a-input>
<a-input
v-model:value="draftFilters.title"
allow-clear
class="marked-articles-filters__title"
:placeholder="t('trackingMarkedArticles.filters.title')"
@pressEnter="applyFilters"
/>
<a-range-picker
v-model:value="createdRange"
allow-clear
show-time
class="marked-articles-filters__range"
format="YYYY-MM-DD HH:mm"
:placeholder="[
t('trackingMarkedArticles.filters.createdFrom'),
t('trackingMarkedArticles.filters.createdTo'),
]"
/>
<div class="marked-articles-filters__actions">
<a-button type="primary" @click="applyFilters">{{ t('common.search') }}</a-button>
<a-button @click="resetFilters">{{ t('common.reset') }}</a-button>
</div>
</div>
<a-table
row-key="id"
class="modern-table marked-articles-table"
:columns="columns"
:data-source="markedArticles"
:loading="markedArticlesQuery.isLoading.value"
:pagination="{
current: page,
pageSize,
total,
showSizeChanger: true,
showTotal: renderTotal,
}"
@change="handleTableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'article_title'">
<div class="marked-article-title">
<strong :title="record.article_title">{{ record.article_title }}</strong>
<span>{{ record.publish_platform }}</span>
</div>
</template>
<template v-else-if="column.key === 'registrable_domain'">
<a-tag color="blue">{{ record.registrable_domain }}</a-tag>
</template>
<template v-else-if="column.key === 'original_url'">
<a
:href="record.original_url"
target="_blank"
rel="noreferrer"
class="marked-article-url"
:title="record.original_url"
>
{{ record.original_url }}
</a>
</template>
<template v-else-if="column.key === 'marked_at'">
{{ formatDateTime(record.marked_at) }}
</template>
<template v-else-if="column.key === 'expires_at'">
{{ formatDateTime(record.expires_at) }}
</template>
<template v-else-if="column.key === 'actions'">
<div class="marked-article-actions">
<a-tooltip :title="t('trackingMarkedArticles.openOriginal')">
<a-button type="text" shape="circle" @click="openURL(record.original_url)">
<template #icon><EyeOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip :title="t('common.edit')">
<a-button type="text" shape="circle" @click="openEditModal(record)">
<template #icon><EditOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip :title="t('common.delete')">
<a-button
type="text"
danger
shape="circle"
:loading="deleteMutation.isPending.value"
@click="confirmDelete(record)"
>
<template #icon><DeleteOutlined /></template>
</a-button>
</a-tooltip>
</div>
</template>
</template>
</a-table>
</a-card>
<a-modal
v-model:open="modalOpen"
:title="modalTitle"
:confirm-loading="saving"
width="560px"
@ok="submitForm"
@cancel="closeModal"
>
<a-form layout="vertical" class="marked-article-form">
<a-form-item :label="t('trackingMarkedArticles.form.articleTitle')" required>
<a-input
v-model:value="formState.article_title"
:maxlength="160"
:placeholder="t('trackingMarkedArticles.form.articleTitlePlaceholder')"
/>
</a-form-item>
<a-form-item :label="t('trackingMarkedArticles.form.originalUrl')" required>
<a-input
v-model:value="formState.original_url"
:placeholder="t('trackingMarkedArticles.form.originalUrlPlaceholder')"
/>
</a-form-item>
</a-form>
</a-modal>
</section>
</template>
<style scoped>
.marked-articles-page {
display: flex;
flex-direction: column;
gap: 20px;
}
.marked-articles-card {
border: 1px solid #edf1f7;
border-radius: 12px;
}
.marked-articles-filters {
display: grid;
grid-template-columns: minmax(180px, 240px) minmax(220px, 1fr) minmax(280px, 360px) auto;
gap: 12px;
align-items: center;
margin-bottom: 18px;
}
.marked-articles-filters__actions {
display: inline-flex;
gap: 8px;
justify-content: flex-end;
}
.marked-articles-table :deep(.ant-table-cell) {
vertical-align: middle;
}
.marked-article-title {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.marked-article-title strong {
overflow: hidden;
color: #111827;
text-overflow: ellipsis;
white-space: nowrap;
}
.marked-article-title span {
color: #8a96aa;
font-size: 12px;
}
.marked-article-url {
display: inline-block;
max-width: 100%;
overflow: hidden;
color: #2563eb;
text-overflow: ellipsis;
vertical-align: middle;
white-space: nowrap;
}
.marked-article-actions {
display: inline-flex;
gap: 4px;
}
.marked-article-form {
padding-top: 8px;
}
@media (max-width: 1100px) {
.marked-articles-filters {
grid-template-columns: 1fr 1fr;
}
.marked-articles-filters__range,
.marked-articles-filters__actions {
width: 100%;
}
.marked-articles-filters__actions {
justify-content: flex-start;
}
}
@media (max-width: 720px) {
.marked-articles-filters {
grid-template-columns: 1fr;
}
}
</style>
@@ -1,12 +1,13 @@
<script setup lang="ts">
import { ArrowLeftOutlined, CopyOutlined } from '@ant-design/icons-vue'
import { ArrowLeftOutlined, BookOutlined, CopyOutlined } from '@ant-design/icons-vue'
import type {
MonitoringQuestionCitationAnalysisItem,
MonitoringQuestionDetailCitation,
MonitoringQuestionDetailPlatform,
MonitoringSourceItem,
} from '@geo/shared-types'
import { useQuery } from '@tanstack/vue-query'
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -22,6 +23,7 @@ import { useCompanyStore } from '@/stores/company'
const { t } = useI18n()
const route = useRoute()
const router = useRouter()
const queryClient = useQueryClient()
const companyStore = useCompanyStore()
const trackingMaxHistoryDays = 30
@@ -179,6 +181,7 @@ type TrackingAnalyzedContentCitation = {
citationCount: number
citationRate: number | null
citedURL: string | null
sourceType: string | null
}
type TrackingCitationSourceLookupItem = {
@@ -202,6 +205,20 @@ const shouldShowInlineContentCitations = computed(() => {
return activeInlineContentCitations.value.length > 0
})
const markCitationMutation = useMutation({
mutationFn: (citation: MonitoringQuestionDetailCitation) =>
monitoringApi.createMarkedArticle({
article_title: resolveExternalArticleTitle(citation),
original_url: citation.cited_url,
brand_id: brandId.value ?? null,
}),
onSuccess: async () => {
message.success(t('tracking.markedArticleSaved'))
await queryClient.invalidateQueries({ queryKey: ['tracking'] })
},
onError: (error) => message.error(formatError(error)),
})
watch(activePlatformId, () => {
clearCitationHighlight()
})
@@ -248,6 +265,13 @@ function openImitationCreate(
})
}
function markCitationAsExternal(citation: MonitoringQuestionDetailCitation): void {
if (!canMarkCitationAsExternal(citation, activePlatform.value?.ai_platform_id)) {
return
}
markCitationMutation.mutate(citation)
}
function formatPercent(value: number | null | undefined): string {
if (value === null || value === undefined) {
return '--'
@@ -311,6 +335,37 @@ function resolveCitationTitle(citation: MonitoringQuestionDetailCitation): strin
return citation.cited_title || citation.article_title || citation.cited_url
}
function resolveExternalArticleTitle(citation: MonitoringQuestionDetailCitation): string {
const title = String(citation.cited_title ?? citation.article_title ?? '').trim()
if (title) {
return title
}
return String(citation.site_name ?? citation.cited_url).trim()
}
function isManualMarkedCitation(
citation: MonitoringQuestionDetailCitation | null | undefined,
): boolean {
return citation?.source_type === 'manual_mark'
}
function canMarkCitationAsExternal(
citation: MonitoringQuestionDetailCitation | null | undefined,
platformId: string | null | undefined,
): boolean {
if (!citation || !supportsContentCitationDisplay(platformId)) {
return false
}
if (!String(citation.cited_url ?? '').trim()) {
return false
}
return (
!citation.article_id &&
citation.source_type !== 'published_article' &&
!isManualMarkedCitation(citation)
)
}
function isLinkedInlineCitationPlatform(platformId: string | null | undefined): boolean {
return platformId === 'yuanbao'
}
@@ -1118,6 +1173,7 @@ function analyzeInlineContentCitations(
citationCount: item.citationCount,
citationRate: item.citationCount / totalCitationCount,
citedURL: item.url,
sourceType: null,
}))
}
@@ -1151,6 +1207,7 @@ function analyzeInlineContentCitations(
citationCount: item.citationCount,
citationRate: item.citationCount / totalCitationCount,
citedURL: item.url,
sourceType: null,
}
})
}
@@ -1199,6 +1256,7 @@ function analyzeInlineContentCitations(
citationCount,
citationRate: citationCount / totalCitationCount,
citedURL,
sourceType: citation?.source_type ?? null,
},
]
})
@@ -1503,17 +1561,36 @@ const contentCitationColumns = computed(() => [
>
{{ resolveCitationTitle(citation) }}
</span>
<a-tooltip :title="t('tracking.imitationAction')">
<button
type="button"
class="tracking-question-imitation-trigger tracking-question-imitation-trigger--source"
@click.stop="
openImitationCreate(citation.cited_url, resolveCitationTitle(citation))
"
<div class="tracking-question-source-card__actions">
<a-tooltip
v-if="canMarkCitationAsExternal(citation, activePlatform?.ai_platform_id)"
:title="t('tracking.markAsExternalArticle')"
>
<CopyOutlined />
</button>
</a-tooltip>
<button
type="button"
class="tracking-question-imitation-trigger"
:disabled="markCitationMutation.isPending.value"
@click.stop="markCitationAsExternal(citation)"
@keydown.enter.stop.prevent="markCitationAsExternal(citation)"
>
<BookOutlined />
</button>
</a-tooltip>
<a-tooltip :title="t('tracking.imitationAction')">
<button
type="button"
class="tracking-question-imitation-trigger"
@click.stop="
openImitationCreate(citation.cited_url, resolveCitationTitle(citation))
"
@keydown.enter.stop.prevent="
openImitationCreate(citation.cited_url, resolveCitationTitle(citation))
"
>
<CopyOutlined />
</button>
</a-tooltip>
</div>
<a-tag
v-if="
citation.article_id &&
@@ -1524,6 +1601,16 @@ const contentCitationColumns = computed(() => [
>
{{ t('tracking.contentCitationBadge') }}
</a-tag>
<a-tag
v-else-if="
isManualMarkedCitation(citation) &&
supportsContentCitationDisplay(activePlatform?.ai_platform_id)
"
color="green"
class="tracking-question-source-card__badge"
>
{{ t('tracking.manualMarkedArticle') }}
</a-tag>
</div>
<div class="tracking-question-source-card__link">
{{ citation.cited_url }}
@@ -1627,16 +1714,20 @@ const contentCitationColumns = computed(() => [
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'contentName'">
<div class="cell-primary-stack">
<a
v-if="record.citedURL"
:href="record.citedURL"
target="_blank"
rel="noreferrer"
:title="record.contentName"
class="table-link"
>
{{ record.contentName }}
</a>
<template v-if="record.citedURL">
<a
:href="record.citedURL"
target="_blank"
rel="noreferrer"
:title="record.contentName"
class="table-link"
>
{{ record.contentName }}
</a>
<small v-if="record.sourceType === 'manual_mark'" class="meta-subtext">
{{ t('tracking.manualMarkedArticle') }}
</small>
</template>
<strong v-else class="table-text" :title="record.contentName">
{{ record.contentName }}
</strong>
@@ -2139,10 +2230,17 @@ const contentCitationColumns = computed(() => [
background: #eff6ff;
}
.tracking-question-imitation-trigger--source {
.tracking-question-imitation-trigger:disabled {
cursor: not-allowed;
opacity: 0.55;
}
.tracking-question-source-card__actions {
position: absolute;
top: 50%;
right: 16px;
display: inline-flex;
gap: 6px;
transform: translateY(calc(-50% + 1px));
}
@@ -2152,9 +2250,8 @@ const contentCitationColumns = computed(() => [
transform: translateY(0);
}
.tracking-question-source-card:hover .tracking-question-imitation-trigger--source,
.tracking-question-source-card:focus-within .tracking-question-imitation-trigger--source {
opacity: 1;
.tracking-question-source-card:hover .tracking-question-source-card__actions,
.tracking-question-source-card:focus-within .tracking-question-source-card__actions {
transform: translateY(-50%);
}
+58 -13
View File
@@ -1,12 +1,12 @@
<script setup lang="ts">
import {
EyeOutlined,
ReloadOutlined,
QuestionCircleOutlined,
ClockCircleOutlined,
EyeOutlined,
FileTextOutlined,
QuestionCircleOutlined,
ReloadOutlined,
RightOutlined,
SearchOutlined,
FileTextOutlined,
} from '@ant-design/icons-vue'
import type {
DesktopAccountInfo,
@@ -189,7 +189,10 @@ watch(
() => route.query.question_id,
(value) => {
const requestedQuestionId = parseNumericQuery(route.query.question_id)
if (missingRequestedQuestionId.value && missingRequestedQuestionId.value !== requestedQuestionId) {
if (
missingRequestedQuestionId.value &&
missingRequestedQuestionId.value !== requestedQuestionId
) {
missingRequestedQuestionId.value = null
}
if (value || selectedBrandId.value) {
@@ -691,9 +694,18 @@ watch([selectedBrandId, selectedQuestionId, selectedPlatformId, selectedBusiness
hotQuestionPage.value = 1
})
watch([selectedBrandId, selectedQuestionId, selectedPlatformId, selectedBusinessDate, selectedCitationWindowDays], () => {
citedArticlePage.value = 1
})
watch(
[
selectedBrandId,
selectedQuestionId,
selectedPlatformId,
selectedBusinessDate,
selectedCitationWindowDays,
],
() => {
citedArticlePage.value = 1
},
)
function openQuestion(question: MonitoringHotQuestion): void {
if (!selectedBrandId.value) {
@@ -718,10 +730,30 @@ function openQuestion(question: MonitoringHotQuestion): void {
}
function openCitedArticle(item: MonitoringCitedArticle): void {
if (item.source_type === 'manual_mark') {
openExternalURL(item.source_url)
return
}
if (!item.article_id) {
openExternalURL(item.source_url)
return
}
selectedArticleId.value = item.article_id
articleDrawerOpen.value = true
}
function citedArticleKey(item: MonitoringCitedArticle): string {
return `${item.source_type}:${item.source_id || item.source_url || item.article_id || item.article_title}`
}
function openExternalURL(url: string | null | undefined): void {
const normalized = String(url ?? '').trim()
if (!normalized) {
return
}
window.open(normalized, '_blank', 'noopener,noreferrer')
}
function formatPercent(value: number | null | undefined): string {
if (value === null || value === undefined) {
return '--'
@@ -1308,7 +1340,10 @@ function disableTrackingDate(current: dayjs.Dayjs): boolean {
<strong>{{ question.question_text }}</strong>
<div class="mention-rate-container">
<span class="mention-rate-label">{{ t('tracking.metrics.mentionRate') }}</span>
<span class="mention-rate-badge" :class="getMentionRateClass(question.mention_rate)">
<span
class="mention-rate-badge"
:class="getMentionRateClass(question.mention_rate)"
>
{{ formatPercent(question.mention_rate) }}
</span>
<div class="mention-rate-track">
@@ -1368,7 +1403,7 @@ function disableTrackingDate(current: dayjs.Dayjs): boolean {
<div v-if="citedArticles.length" class="article-list">
<div
v-for="item in citedArticles"
:key="item.article_id"
:key="citedArticleKey(item)"
class="article-list__item"
>
<div class="article-list__item-left">
@@ -1377,7 +1412,13 @@ function disableTrackingDate(current: dayjs.Dayjs): boolean {
</div>
<div class="article-list__item-content">
<strong>{{ item.article_title }}</strong>
<span class="article-platform-tag">{{ item.publish_platform }}</span>
<span class="article-platform-tag">
{{
item.source_type === 'manual_mark'
? t('tracking.manualMarkedArticle')
: item.publish_platform
}}
</span>
</div>
</div>
<div class="article-list__side">
@@ -1693,7 +1734,9 @@ function disableTrackingDate(current: dayjs.Dayjs): boolean {
.tracking-list-search.ant-input-affix-wrapper:focus {
background-color: #ffffff;
border-color: #1f5cff;
box-shadow: 0 0 0 3px rgba(31, 92, 255, 0.1), 0 4px 12px rgba(31, 92, 255, 0.05);
box-shadow:
0 0 0 3px rgba(31, 92, 255, 0.1),
0 4px 12px rgba(31, 92, 255, 0.05);
}
.tracking-list-search :deep(.ant-input) {
@@ -2035,7 +2078,9 @@ function disableTrackingDate(current: dayjs.Dayjs): boolean {
.detail-citation-list__item:hover {
background: #ffffff;
border-color: rgba(31, 92, 255, 0.25);
box-shadow: 0 10px 25px -5px rgba(31, 92, 255, 0.08), 0 8px 10px -6px rgba(31, 92, 255, 0.04);
box-shadow:
0 10px 25px -5px rgba(31, 92, 255, 0.08),
0 8px 10px -6px rgba(31, 92, 255, 0.04);
transform: translateY(-2px);
}