feat: 优化

This commit is contained in:
2026-01-27 00:39:12 +08:00
parent b76367afed
commit bf12e70339
10 changed files with 896 additions and 47 deletions

View File

@@ -430,10 +430,11 @@ onBeforeUnmount(() => {
:deep(.ant-input-affix-wrapper) {
border-radius: 12px !important;
background: #f8fafc !important;
border: 1.5px solid #e2e8f0 !important;
border: 1.5px solid transparent !important;
box-shadow: none !important;
transition: all 0.2s ease !important;
height: 48px !important;
box-sizing: border-box !important;
}
:deep(.ant-input-affix-wrapper:hover) {
@@ -445,6 +446,7 @@ onBeforeUnmount(() => {
border-color: #3B82F6 !important;
background: #ffffff !important;
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1) !important;
margin: 0 !important;
}
:deep(.ant-input) {
@@ -467,24 +469,29 @@ onBeforeUnmount(() => {
font-weight: 500;
color: #334155;
font-size: 14px;
margin-bottom: 8px;
display: block;
}
:deep(.ant-input-affix-wrapper:focus-within) {
outline: none;
}
:deep(.ant-form-item) {
margin-bottom: 28px;
}
:deep(.ant-form-item-control) {
min-height: 48px;
position: relative;
}
/* 表单验证错误状态 */
:deep(.ant-form-item-has-error .ant-input-affix-wrapper) {
border-color: #ef4444 !important;
background: #ffffff !important;
}
:deep(.ant-form-item-explain-error) {
color: #ef4444;
font-size: 13px;
margin-top: 4px;
}
/* ========== 验证码输入 ========== */
.code-input {
display: flex;

View File

@@ -25,6 +25,7 @@ const {
saveTableDataToSession,
loadTableDataFromSession,
processApiResponse,
appendData,
clearData,
} = useBenchmarkData()
@@ -51,6 +52,10 @@ const batchPromptTextCount = ref(0)
const savePromptModalVisible = ref(false)
const savePromptContent = ref('')
const maxCursor = ref(0)
const hasMore = ref(false)
const loadingMore = ref(false)
async function handleAnalyzeUser() {
const sec_user_id = resolveId(form.value.url, {
queryKeys: ['user'],
@@ -61,6 +66,10 @@ async function handleAnalyzeUser() {
}
loading.value = true
maxCursor.value = 0
hasMore.value = false
await clearData()
const req = await TikhubService.postTikHup({
type: InterfaceType.DOUYIN_WEB_USER_POST_VIDEOS,
methodType: MethodType.GET,
@@ -75,7 +84,9 @@ async function handleAnalyzeUser() {
try {
const resp = await req
processApiResponse(resp, form.value.platform)
const result = processApiResponse(resp, form.value.platform)
maxCursor.value = result.maxCursor
hasMore.value = result.hasMore
await saveTableDataToSession()
message.success('分析完成')
} catch (err) {
@@ -164,9 +175,64 @@ async function handleBatchAnalyze() {
async function handleResetForm() {
form.value = { platform: '抖音', url: '', count: 20, sort_type: 0 }
maxCursor.value = 0
hasMore.value = false
await clearData()
}
async function handleLoadMore() {
const sec_user_id = resolveId(form.value.url, {
queryKeys: ['user'],
pathPatterns: ['/user/:id'],
})
if (!sec_user_id) {
return
}
loadingMore.value = true
const req = await TikhubService.postTikHup({
type: InterfaceType.DOUYIN_WEB_USER_POST_VIDEOS,
methodType: MethodType.GET,
urlParams: {
sec_user_id,
max_cursor: maxCursor.value,
type: 'tik-app',
sort_type: form.value.sort_type,
count: form.value.count || 20,
},
})
try {
const resp = await req
if (form.value.platform === '抖音') {
const { mapFromDouyin } = await import('./utils/benchmarkUtils')
const awemeList = resp?.data?.aweme_list || []
const newItems = mapFromDouyin(awemeList)
appendData(newItems)
maxCursor.value = resp?.data?.max_cursor || 0
hasMore.value = resp?.data?.has_more || !(awemeList.length < (resp?.data?.count || 20))
} else {
const { mapFromXhs } = await import('./utils/benchmarkUtils')
const notes = resp?.data?.notes || resp?.data?.data || []
const newItems = mapFromXhs(notes)
appendData(newItems)
maxCursor.value = resp?.data?.max_cursor || 0
hasMore.value = resp?.data?.has_more || false
}
await saveTableDataToSession()
message.success(`已加载 ${data.value.length} 条数据`)
} catch (err) {
console.error(err)
message.error('加载失败,请稍后重试')
} finally {
loadingMore.value = false
}
}
function handleCopyBatchPrompt(prompt) {
if (!prompt?.trim()) {
message.warning('没有提示词可复制')
@@ -224,11 +290,14 @@ defineOptions({ name: 'ContentStyleBenchmark' })
:data="data"
v-model:selectedRowKeys="selectedRowKeys"
:loading="loading"
:loading-more="loadingMore"
:has-more="hasMore"
@export="handleExportToExcel"
@batch-analyze="handleBatchAnalyze"
@load-more="handleLoadMore"
/>
<section v-if="!data.length && !loading" class="card results-card empty-state">
<section v-if="!data.length" class="card results-card empty-state">
<a-empty description="暂无数据,请点击开始分析">
<template #image>
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">

View File

@@ -30,29 +30,31 @@ function handleReset() {
</script>
<template>
<section class="card">
<section class="form-container">
<a-form :model="form" layout="vertical">
<a-form-item label="平台">
<a-radio-group v-model:value="form.platform" button-style="solid">
<a-radio-button value="抖音">抖音</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item label="主页/视频链接">
<a-input
class="search-input"
v-model:value="form.url"
placeholder="粘贴抖音主页或视频链接,或点击下方示例试一试"
placeholder="粘贴抖音主页或视频链接"
allow-clear
size="large"
/>
</a-form-item>
<a-form-item label="最大数量建议保持默认值20">
<a-form-item label="分析数量">
<div class="slider-row">
<a-slider v-model:value="form.count" :min="1" :max="100" :tooltip-open="true" style="flex:1" />
<a-input-number v-model:value="form.count" :min="1" :max="100" style="width:96px; margin-left:12px;" />
<a-slider v-model:value="form.count" :min="1" :max="40" :tooltip-open="true" style="flex:1" />
<a-input-number v-model:value="form.count" :min="1" :max="40" style="width:96px; margin-left:12px;" />
</div>
<div class="form-hint">数量越大越全面但分析时间更长建议 2030</div>
<div class="form-hint">建议 20-30 个内容</div>
</a-form-item>
<a-space>
<GradientButton
text="开始分析"
@@ -61,18 +63,17 @@ function handleReset() {
size="middle"
@click="handleAnalyze"
/>
<a-button @click="handleReset">清空</a-button>
<a-button @click="handleReset">重置</a-button>
</a-space>
</a-form>
</section>
</template>
<style scoped>
.card {
.form-container {
background: var(--color-surface);
border-radius: 8px;
padding: 16px;
box-shadow: var(--shadow-inset-card);
border: 1px solid var(--color-border);
}
@@ -87,7 +88,6 @@ function handleReset() {
color: var(--color-text-secondary);
}
:deep(.ant-slider) {
padding: 10px 0;
}
@@ -115,30 +115,5 @@ function handleReset() {
:deep(.ant-slider-handle:active::after) {
box-shadow: 0 0 0 3px var(--color-primary);
}
.search-input {
flex: 1;
padding: 8px 12px;
font-size: 14px;
color: var(--color-text);
border: 1px solid var(--color-border);
border-radius: 6px;
transition: all 0.2s;
}
.search-input:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(0, 176, 48, 0.1);
}
.search-input:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.search-input::placeholder {
color: var(--color-text-secondary);
opacity: 0.6;
}
</style>

View File

@@ -16,12 +16,21 @@ import GradientButton from '@/components/GradientButton.vue'
type: Boolean,
default: false,
},
loadingMore: {
type: Boolean,
default: false,
},
hasMore: {
type: Boolean,
default: false,
},
})
const emit = defineEmits([
'update:selectedRowKeys',
'export',
'batchAnalyze',
'loadMore',
])
const defaultColumns = [
@@ -78,6 +87,7 @@ function onSelectChange(selectedKeys) {
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
:rowKey="(record) => String(record.id)"
:loading="loading"
:scroll="{ y: 'calc(100vh - 400px)' }"
class="benchmark-table">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'cover'">
@@ -115,6 +125,21 @@ function onSelectChange(selectedKeys) {
<span v-else>-</span>
</template>
</template>
<template #footer>
<div v-if="hasMore" class="load-more-footer">
<a-button
:loading="loadingMore"
@click="$emit('loadMore')"
size="large"
>
加载更多内容
</a-button>
<div class="load-more-hint">已加载 {{ data.length }} </div>
</div>
<div v-else-if="data.length > 0" class="no-more-hint">
已加载全部内容
</div>
</template>
</a-table>
</section>
</template>
@@ -187,5 +212,32 @@ function onSelectChange(selectedKeys) {
cursor: pointer;
user-select: none;
}
.load-more-footer {
padding: 16px;
text-align: center;
border-top: 1px solid var(--color-border);
}
.load-more-footer .ant-btn {
max-width: 200px;
height: 40px;
font-size: 14px;
border-radius: 8px;
}
.load-more-hint {
margin-top: 8px;
font-size: 12px;
color: var(--color-text-secondary);
}
.no-more-hint {
padding: 12px;
text-align: center;
font-size: 12px;
color: var(--color-text-secondary);
border-top: 1px solid var(--color-border);
}
</style>

View File

@@ -40,12 +40,28 @@ export function useBenchmarkData() {
console.log('抖音返回的原始数据:', awemeList[0])
data.value = mapFromDouyin(awemeList)
console.log('映射后的第一条数据:', data.value[0])
return {
maxCursor: resp?.data?.max_cursor || 0,
hasMore: resp?.data?.has_more || !(resp?.data?.aweme_list?.length < (resp?.data?.count || 20)),
count: data.value.length,
}
} else {
const notes = resp?.data?.notes || resp?.data?.data || []
data.value = mapFromXhs(notes)
return {
maxCursor: resp?.data?.max_cursor || 0,
hasMore: resp?.data?.has_more || false,
count: data.value.length,
}
}
}
function appendData(newItems) {
data.value = [...data.value, ...newItems]
}
async function clearData() {
data.value = []
selectedRowKeys.value = []
@@ -60,6 +76,7 @@ export function useBenchmarkData() {
saveTableDataToSession,
loadTableDataFromSession,
processApiResponse,
appendData,
clearData,
}
}

View File

@@ -449,7 +449,6 @@ onMounted(() => {
}
.filter-button {
min-width: 80px;
@media (max-width: 767px) {
min-width: auto;