refactor(layout): update BasicLayout and LayoutHeader styling with ant design improvements
- Replace ant-design-vue message with vue-sonner toast notifications - Remove LayoutHeader component import from BasicLayout - Update CSS classes for better styling consistency - Adjust padding and spacing in layout components - Modify shadow and overflow properties in task management views - Add hidden class utility for element visibility control
This commit is contained in:
@@ -1,8 +1,5 @@
|
||||
<script setup>
|
||||
import LayoutHeader from './LayoutHeader.vue'
|
||||
|
||||
defineOptions({ name: 'BasicLayout' })
|
||||
|
||||
defineProps({
|
||||
title: { type: String, default: '' },
|
||||
showBack: { type: Boolean, default: false },
|
||||
@@ -13,19 +10,9 @@ const emit = defineEmits(['back'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col h-full overflow-hidden rounded-xl border bg-card">
|
||||
<LayoutHeader
|
||||
:title="title"
|
||||
:show-back="showBack"
|
||||
@back="emit('back')"
|
||||
>
|
||||
<template #extra>
|
||||
<slot name="extra"></slot>
|
||||
</template>
|
||||
</LayoutHeader>
|
||||
|
||||
<div class="flex flex-col h-full overflow-hidden">
|
||||
<!-- 页面内容 -->
|
||||
<div class="flex-1 flex flex-col overflow-x-hidden overflow-y-auto bg-card p-4">
|
||||
<div class="flex-1 flex flex-col overflow-x-hidden overflow-y-auto p-4">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -32,12 +32,10 @@ const handleBack = () => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex justify-between items-center gap-4 border-b bg-card shrink-0 min-h-14 transition-all"
|
||||
class="flex justify-between items-center gap-4 shrink-0 min-h-12"
|
||||
:class="{
|
||||
'border-border': !ghost,
|
||||
'border-transparent bg-transparent': ghost,
|
||||
'px-4 py-3': !padding,
|
||||
'dark:border-border dark:bg-card dark:text-foreground': !ghost
|
||||
'bg-transparent': ghost,
|
||||
'px-4 py-2': !padding
|
||||
}"
|
||||
:style="padding ? { padding } : {}"
|
||||
>
|
||||
@@ -53,9 +51,9 @@ const handleBack = () => {
|
||||
</Button>
|
||||
<div class="flex-1 min-w-0 flex items-center">
|
||||
<slot name="header">
|
||||
<h1 v-if="title" class="text-lg font-semibold text-foreground m-0 leading-snug tracking-tight">
|
||||
<span v-if="title" class="text-base font-medium text-foreground">
|
||||
{{ title }}
|
||||
</h1>
|
||||
</span>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { resolveId } from '@/utils/url'
|
||||
import { exportBenchmarkDataToExcel } from '@/utils/excel'
|
||||
import GlobalLoading from '@/components/GlobalLoading.vue'
|
||||
@@ -75,10 +75,10 @@ async function handleAnalyzeUser() {
|
||||
maxCursor.value = result.maxCursor
|
||||
hasMore.value = result.hasMore
|
||||
await saveTableDataToSession()
|
||||
message.success('分析完成')
|
||||
toast.success('分析完成')
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
message.error('请求失败,请稍后重试')
|
||||
toast.error('请求失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -86,17 +86,17 @@ async function handleAnalyzeUser() {
|
||||
|
||||
async function handleExportToExcel() {
|
||||
if (!data.value?.length) {
|
||||
message.warning('暂无数据可导出')
|
||||
toast.warning('暂无数据可导出')
|
||||
return
|
||||
}
|
||||
|
||||
if (!selectedRowKeys.value.length) {
|
||||
message.warning('请先选择要导出的行')
|
||||
toast.warning('请先选择要导出的行')
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedRowKeys.value.length > 20) {
|
||||
message.warning('最多只能导出20条数据,请重新选择')
|
||||
toast.warning('最多只能导出20条数据,请重新选择')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ async function handleExportToExcel() {
|
||||
globalLoadingText.value = '正在导出...'
|
||||
} catch (error) {
|
||||
console.error('获取语音转写失败:', error)
|
||||
message.warning('部分数据语音转写失败,将导出已有数据')
|
||||
toast.warning('部分数据语音转写失败,将导出已有数据')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,11 @@ async function handleExportToExcel() {
|
||||
platform: form.value.platform,
|
||||
formatTime
|
||||
})
|
||||
message[result.success ? 'success' : 'error'](result.message)
|
||||
if (result.success) {
|
||||
toast.success(result.message)
|
||||
} else {
|
||||
toast.error(result.message)
|
||||
}
|
||||
} finally {
|
||||
globalLoading.value = false
|
||||
globalLoadingText.value = ''
|
||||
@@ -186,10 +190,10 @@ async function handleLoadMore() {
|
||||
}
|
||||
|
||||
await saveTableDataToSession()
|
||||
message.success(`已加载 ${data.value.length} 条数据`)
|
||||
toast.success(`已加载 ${data.value.length} 条数据`)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
message.error('加载失败,请稍后重试')
|
||||
toast.error('加载失败,请稍后重试')
|
||||
} finally {
|
||||
loadingMore.value = false
|
||||
}
|
||||
@@ -206,15 +210,15 @@ function getSelectedVideoUrls() {
|
||||
// 打开创建风格任务弹窗
|
||||
function handleCreateAsyncTask() {
|
||||
if (!selectedRowKeys.value.length) {
|
||||
message.warning('请先选择要分析的视频')
|
||||
toast.warning('请先选择要分析的视频')
|
||||
return
|
||||
}
|
||||
if (selectedRowKeys.value.length > 20) {
|
||||
message.warning('最多只能选择20个视频')
|
||||
toast.warning('最多只能选择20个视频')
|
||||
return
|
||||
}
|
||||
if (!getSelectedVideoUrls().length) {
|
||||
message.warning('选中的视频没有有效的URL')
|
||||
toast.warning('选中的视频没有有效的URL')
|
||||
return
|
||||
}
|
||||
createTaskModalVisible.value = true
|
||||
@@ -256,19 +260,18 @@ defineOptions({ name: 'ContentStyleBenchmark' })
|
||||
/>
|
||||
|
||||
<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">
|
||||
<rect x="20" y="30" width="80" height="60" rx="4" stroke="currentColor" stroke-width="2" fill="none" opacity="0.3"/>
|
||||
<circle cx="40" cy="50" r="8" fill="currentColor" opacity="0.4"/>
|
||||
<rect x="54" y="47" width="40" height="6" rx="3" fill="currentColor" opacity="0.4"/>
|
||||
<rect x="54" y="60" width="32" height="6" rx="3" fill="currentColor" opacity="0.4"/>
|
||||
<line x1="32" y1="75" x2="88" y2="75" stroke="currentColor" stroke-width="2" opacity="0.3"/>
|
||||
<line x1="32" y1="82" x2="88" y2="82" stroke="currentColor" stroke-width="2" opacity="0.3"/>
|
||||
<line x1="32" y1="89" x2="72" y2="89" stroke="currentColor" stroke-width="2" opacity="0.3"/>
|
||||
</svg>
|
||||
</template>
|
||||
</a-empty>
|
||||
<div class="empty-content">
|
||||
<svg class="empty-icon" width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="20" y="30" width="80" height="60" rx="4" stroke="currentColor" stroke-width="2" fill="none" opacity="0.3"/>
|
||||
<circle cx="40" cy="50" r="8" fill="currentColor" opacity="0.4"/>
|
||||
<rect x="54" y="47" width="40" height="6" rx="3" fill="currentColor" opacity="0.4"/>
|
||||
<rect x="54" y="60" width="32" height="6" rx="3" fill="currentColor" opacity="0.4"/>
|
||||
<line x1="32" y1="75" x2="88" y2="75" stroke="currentColor" stroke-width="2" opacity="0.3"/>
|
||||
<line x1="32" y1="82" x2="88" y2="82" stroke="currentColor" stroke-width="2" opacity="0.3"/>
|
||||
<line x1="32" y1="89" x2="72" y2="89" stroke="currentColor" stroke-width="2" opacity="0.3"/>
|
||||
</svg>
|
||||
<p class="empty-text">暂无数据,请点击开始分析</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -322,12 +325,21 @@ defineOptions({ name: 'ContentStyleBenchmark' })
|
||||
min-height: 420px;
|
||||
}
|
||||
|
||||
.empty-state :deep(.ant-empty) {
|
||||
.empty-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.empty-state :deep(.ant-empty-description) {
|
||||
.empty-icon {
|
||||
color: var(--color-gray-400);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
color: var(--color-gray-600);
|
||||
font-size: var(--font-size-base);
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import GradientButton from '@/components/GradientButton.vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { usePointsConfigStore } from '@/stores/pointsConfig'
|
||||
|
||||
const pointsConfigStore = usePointsConfigStore()
|
||||
@@ -39,23 +43,31 @@ function handleReset() {
|
||||
|
||||
<template>
|
||||
<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>
|
||||
<div class="form-space-y-6">
|
||||
<!-- 平台选择 -->
|
||||
<div class="form-field">
|
||||
<Label class="form-label">平台</Label>
|
||||
<RadioGroup v-model="form.platform" class="radio-group">
|
||||
<div class="radio-item active">
|
||||
<RadioGroupItem value="抖音" id="douyin" class="hidden" />
|
||||
<label for="douyin" class="radio-label">抖音</label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<a-form-item label="主页/视频链接">
|
||||
<a-input
|
||||
v-model:value="form.url"
|
||||
<!-- 链接输入 -->
|
||||
<div class="form-field">
|
||||
<Label class="form-label">主页/视频链接</Label>
|
||||
<Input
|
||||
v-model="form.url"
|
||||
placeholder="粘贴抖音主页或视频链接"
|
||||
allow-clear
|
||||
size="large"
|
||||
class="form-input h-11"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<a-form-item label="分析数量">
|
||||
<!-- 分析数量 -->
|
||||
<div class="form-field">
|
||||
<Label class="form-label">分析数量</Label>
|
||||
<div class="slider-row">
|
||||
<span class="slider-num">{{ form.count }}</span>
|
||||
<input
|
||||
@@ -67,9 +79,10 @@ function handleReset() {
|
||||
/>
|
||||
</div>
|
||||
<div class="form-hint">建议 20-30 个内容</div>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<a-space>
|
||||
<!-- 操作按钮 -->
|
||||
<div class="button-row">
|
||||
<GradientButton
|
||||
text="开始分析"
|
||||
:loading="loading"
|
||||
@@ -77,10 +90,10 @@ function handleReset() {
|
||||
size="middle"
|
||||
@click="handleAnalyze"
|
||||
/>
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-space>
|
||||
<Button variant="outline" @click="handleReset">重置</Button>
|
||||
</div>
|
||||
<p class="points-hint">每次分析将消耗积分,消耗量与分析数量相关</p>
|
||||
</a-form>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -92,6 +105,60 @@ function handleReset() {
|
||||
border: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.form-space-y-6 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
.form-input {
|
||||
background: var(--color-gray-50);
|
||||
border-color: var(--color-gray-300);
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.radio-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 var(--space-4);
|
||||
height: 36px;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
color: var(--color-gray-600);
|
||||
background: var(--color-gray-50);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-base);
|
||||
cursor: pointer;
|
||||
transition: all var(--duration-fast);
|
||||
}
|
||||
|
||||
.radio-item.active {
|
||||
color: #fff;
|
||||
background: var(--color-primary-500);
|
||||
border-color: var(--color-primary-500);
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slider-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -152,6 +219,11 @@ function handleReset() {
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.points-hint {
|
||||
margin-top: var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
@@ -159,4 +231,3 @@ function handleReset() {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
import { reactive, computed, ref } from 'vue'
|
||||
import { formatTime } from '../utils/benchmarkUtils'
|
||||
import GradientButton from '@/components/GradientButton.vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import { Spinner } from '@/components/ui/spinner'
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
data: { type: Array, required: true },
|
||||
selectedRowKeys: { type: Array, required: true },
|
||||
loading: { type: Boolean, default: false },
|
||||
@@ -13,29 +24,74 @@ defineProps({
|
||||
|
||||
const emit = defineEmits(['update:selectedRowKeys', 'export', 'loadMore', 'createAsyncTask'])
|
||||
|
||||
const defaultColumns = [
|
||||
{ title: '封面', key: 'cover', dataIndex: 'cover', width: 100 },
|
||||
{ title: '描述', key: 'desc', dataIndex: 'desc', width: 200, ellipsis: true },
|
||||
{ title: '点赞', key: 'digg_count', dataIndex: 'digg_count', width: 80,
|
||||
sorter: (a, b) => (a.digg_count || 0) - (b.digg_count || 0), defaultSortOrder: 'descend' },
|
||||
{ title: '评论', key: 'comment_count', dataIndex: 'comment_count', width: 80,
|
||||
sorter: (a, b) => (a.comment_count || 0) - (b.comment_count || 0) },
|
||||
{ title: '分享', key: 'share_count', dataIndex: 'share_count', width: 80,
|
||||
sorter: (a, b) => (a.share_count || 0) - (b.share_count || 0) },
|
||||
{ title: '收藏', key: 'collect_count', dataIndex: 'collect_count', width: 80,
|
||||
sorter: (a, b) => (a.collect_count || 0) - (b.collect_count || 0) },
|
||||
{ title: '时长', key: 'duration_s', dataIndex: 'duration_s', width: 80,
|
||||
sorter: (a, b) => (a.duration_s || 0) - (b.duration_s || 0) },
|
||||
{ title: '创建时间', key: 'create_time', dataIndex: 'create_time', width: 140,
|
||||
sorter: (a, b) => (a.create_time || 0) - (b.create_time || 0) },
|
||||
// 列定义
|
||||
const columns = [
|
||||
{ key: 'cover', title: '封面', width: '100px' },
|
||||
{ key: 'desc', title: '描述', width: '200px' },
|
||||
{ key: 'digg_count', title: '点赞', width: '80px', sortable: true },
|
||||
{ key: 'comment_count', title: '评论', width: '80px', sortable: true },
|
||||
{ key: 'share_count', title: '分享', width: '80px', sortable: true },
|
||||
{ key: 'collect_count', title: '收藏', width: '80px', sortable: true },
|
||||
{ key: 'duration_s', title: '时长', width: '80px', sortable: true },
|
||||
{ key: 'create_time', title: '创建时间', width: '140px', sortable: true },
|
||||
]
|
||||
|
||||
const columns = reactive([...defaultColumns])
|
||||
// 排序状态
|
||||
const sortKey = ref('digg_count')
|
||||
const sortOrder = ref('desc')
|
||||
|
||||
function onSelectChange(selectedKeys) {
|
||||
emit('update:selectedRowKeys', selectedKeys)
|
||||
// 全选状态
|
||||
const isAllSelected = computed(() => {
|
||||
return props.data.length > 0 && props.selectedRowKeys.length === props.data.length
|
||||
})
|
||||
|
||||
// 切换排序
|
||||
function handleSort(key) {
|
||||
if (sortKey.value === key) {
|
||||
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
|
||||
} else {
|
||||
sortKey.value = key
|
||||
sortOrder.value = 'desc'
|
||||
}
|
||||
emit('update:selectedRowKeys', [])
|
||||
}
|
||||
|
||||
// 选择切换
|
||||
function handleSelectAll() {
|
||||
if (isAllSelected.value) {
|
||||
emit('update:selectedRowKeys', [])
|
||||
} else {
|
||||
emit('update:selectedRowKeys', props.data.map(item => String(item.id)))
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelect(id) {
|
||||
const key = String(id)
|
||||
const newKeys = [...props.selectedRowKeys]
|
||||
const index = newKeys.indexOf(key)
|
||||
if (index > -1) {
|
||||
newKeys.splice(index, 1)
|
||||
} else {
|
||||
newKeys.push(key)
|
||||
}
|
||||
emit('update:selectedRowKeys', newKeys)
|
||||
}
|
||||
|
||||
// 排序后的数据
|
||||
const sortedData = computed(() => {
|
||||
const data = [...props.data]
|
||||
const key = sortKey.value
|
||||
|
||||
if (!key) return data
|
||||
|
||||
return data.sort((a, b) => {
|
||||
const aVal = a[key] || 0
|
||||
const bVal = b[key] || 0
|
||||
return sortOrder.value === 'asc' ? aVal - bVal : bVal - aVal
|
||||
})
|
||||
})
|
||||
|
||||
// 格式化数字
|
||||
function formatNumber(value) {
|
||||
return value ? value.toLocaleString('zh-CN') : '0'
|
||||
}
|
||||
@@ -62,58 +118,105 @@ function formatNumber(value) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<a-table
|
||||
:dataSource="data"
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
: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'">
|
||||
<div class="cover-wrapper">
|
||||
<a v-if="record.cover && record.share_url" :href="record.share_url" target="_blank" class="cover-link">
|
||||
<img :src="record.cover" alt="cover" loading="lazy" class="cover-img" />
|
||||
<span v-if="record.is_top" class="top-tag">置顶</span>
|
||||
</a>
|
||||
<template v-else-if="record.cover">
|
||||
<img :src="record.cover" alt="cover" loading="lazy" class="cover-img" />
|
||||
<span v-if="record.is_top" class="top-tag">置顶</span>
|
||||
</template>
|
||||
<span v-else class="no-cover">无封面</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'desc'">
|
||||
<span :title="record.desc">{{ record.desc || '-' }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'play_count'">
|
||||
{{ record.play_count ? (record.play_count / 10000).toFixed(1) + 'w' : '0' }}
|
||||
</template>
|
||||
<template v-else-if="['digg_count', 'comment_count', 'share_count', 'collect_count'].includes(column.key)">
|
||||
{{ formatNumber(record[column.key]) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'create_time'">
|
||||
{{ formatTime(record.create_time) }}
|
||||
</template>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div v-if="hasMore" class="load-more-footer">
|
||||
<a-button
|
||||
:loading="loadingMore"
|
||||
@click="$emit('loadMore')"
|
||||
size="large"
|
||||
|
||||
<!-- 表格 -->
|
||||
<div class="table-wrapper">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-[40px]">
|
||||
<Checkbox
|
||||
:checked="isAllSelected"
|
||||
@update:checked="handleSelectAll"
|
||||
class="checkbox-small"
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
:class="[col.width && `w-[${col.width}]`, col.sortable && 'cursor-pointer']"
|
||||
@click="col.sortable && handleSort(col.key)"
|
||||
>
|
||||
<div class="flex items-center gap-1">
|
||||
{{ col.title }}
|
||||
<span v-if="col.sortable" class="sort-icon">
|
||||
<svg v-if="sortKey !== col.key" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M7 15l5 5 5-5M7 9l5-5 5 5"/>
|
||||
</svg>
|
||||
<svg v-else-if="sortOrder === 'asc'" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 19V5M5 12l7-7 7 7"/>
|
||||
</svg>
|
||||
<svg v-else width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 5v14M5 12l7 7 7-7"/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-if="loading">
|
||||
<TableCell :col-span="columns.length + 1" class="h-32 text-center">
|
||||
<Spinner class="mx-auto" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
v-else
|
||||
v-for="record in sortedData"
|
||||
:key="record.id"
|
||||
class="table-row"
|
||||
>
|
||||
加载更多内容
|
||||
</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>
|
||||
<TableCell class="w-[40px]">
|
||||
<Checkbox
|
||||
:checked="selectedRowKeys.includes(String(record.id))"
|
||||
@update:checked="handleSelect(record.id)"
|
||||
class="checkbox-small"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="cover-wrapper">
|
||||
<a v-if="record.cover && record.share_url" :href="record.share_url" target="_blank" class="cover-link">
|
||||
<img :src="record.cover" alt="cover" loading="lazy" class="cover-img" />
|
||||
<span v-if="record.is_top" class="top-tag">置顶</span>
|
||||
</a>
|
||||
<template v-else-if="record.cover">
|
||||
<img :src="record.cover" alt="cover" loading="lazy" class="cover-img" />
|
||||
<span v-if="record.is_top" class="top-tag">置顶</span>
|
||||
</template>
|
||||
<span v-else class="no-cover">无封面</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span :title="record.desc" class="truncate-text">{{ record.desc || '-' }}</span>
|
||||
</TableCell>
|
||||
<TableCell>{{ formatNumber(record.digg_count) }}</TableCell>
|
||||
<TableCell>{{ formatNumber(record.comment_count) }}</TableCell>
|
||||
<TableCell>{{ formatNumber(record.share_count) }}</TableCell>
|
||||
<TableCell>{{ formatNumber(record.collect_count) }}</TableCell>
|
||||
<TableCell>{{ record.duration_s ? `${record.duration_s}s` : '-' }}</TableCell>
|
||||
<TableCell>{{ formatTime(record.create_time) }}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<div v-if="hasMore" class="load-more-footer">
|
||||
<Button
|
||||
:disabled="loadingMore"
|
||||
size="lg"
|
||||
variant="outline"
|
||||
@click="$emit('loadMore')"
|
||||
class="load-more-btn"
|
||||
>
|
||||
<Spinner v-if="loadingMore" class="mr-2 h-4 w-4" />
|
||||
{{ loadingMore ? '加载中...' : '加载更多内容' }}
|
||||
</Button>
|
||||
<div class="load-more-hint">已加载 {{ data.length }} 条</div>
|
||||
</div>
|
||||
<div v-else-if="data.length > 0" class="no-more-hint">
|
||||
已加载全部内容
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -150,13 +253,32 @@ function formatNumber(value) {
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.batch-btn {
|
||||
font-weight: 600;
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
max-height: calc(100vh - 400px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.batch-btn:hover {
|
||||
box-shadow: var(--shadow-blue);
|
||||
filter: brightness(1.03);
|
||||
.table-wrapper::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.table-wrapper::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.table-wrapper::-webkit-scrollbar-thumb {
|
||||
background: var(--color-gray-300);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.table-row:hover {
|
||||
background: var(--color-gray-50);
|
||||
}
|
||||
|
||||
.checkbox-small {
|
||||
transform: scale(0.85);
|
||||
}
|
||||
|
||||
.cover-wrapper {
|
||||
@@ -199,42 +321,22 @@ function formatNumber(value) {
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.benchmark-table :deep(.ant-table-expand-icon-th),
|
||||
.benchmark-table :deep(.ant-table-row-expand-icon-cell) {
|
||||
width: 48px;
|
||||
min-width: 48px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.benchmark-table :deep(.ant-table) {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.benchmark-table :deep(.ant-table-wrapper),
|
||||
.benchmark-table :deep(.ant-spin-nested-loading),
|
||||
.benchmark-table :deep(.ant-spin-container),
|
||||
.benchmark-table :deep(.ant-table-container) {
|
||||
width: 100%;
|
||||
.truncate-text {
|
||||
display: block;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.benchmark-table :deep(.ant-table-body) {
|
||||
overflow-x: hidden !important;
|
||||
.sort-icon {
|
||||
opacity: 0.4;
|
||||
transition: opacity var(--duration-fast);
|
||||
}
|
||||
|
||||
.benchmark-table :deep(.ant-table-thead > tr > th),
|
||||
.benchmark-table :deep(.ant-table-tbody > tr > td) {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.benchmark-table :deep(.ant-table-row-expand-icon) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
.table-row:hover .sort-icon,
|
||||
[onclick]:hover .sort-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.load-more-footer,
|
||||
@@ -247,7 +349,7 @@ function formatNumber(value) {
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.load-more-footer .ant-btn {
|
||||
.load-more-btn {
|
||||
max-width: 200px;
|
||||
height: 40px;
|
||||
font-size: var(--font-size-base);
|
||||
@@ -268,4 +370,3 @@ function formatNumber(value) {
|
||||
padding: var(--space-3);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
<script setup>
|
||||
import { ref, watchEffect } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { BenchmarkTaskApi } from '@/api/benchmarkTask'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Spinner } from '@/components/ui/spinner'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -38,17 +51,17 @@ watchEffect(() => {
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!form.value.taskName.trim()) {
|
||||
message.warning('请输入任务名称')
|
||||
toast.warning('请输入任务名称')
|
||||
return
|
||||
}
|
||||
|
||||
if (!props.videoUrls?.length) {
|
||||
message.warning('没有选择视频')
|
||||
toast.warning('没有选择视频')
|
||||
return
|
||||
}
|
||||
|
||||
if (!form.value.promptName.trim()) {
|
||||
message.warning('请输入提示词名称')
|
||||
toast.warning('请输入提示词名称')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -64,7 +77,7 @@ async function handleSubmit() {
|
||||
const response = await BenchmarkTaskApi.createTask(payload)
|
||||
|
||||
if (response?.code === 0 || response?.data) {
|
||||
message.success('任务创建成功!请到任务中心查看进度')
|
||||
toast.success('任务创建成功!请到任务中心查看进度')
|
||||
emit('update:visible', false)
|
||||
emit('success', response.data)
|
||||
} else {
|
||||
@@ -72,7 +85,7 @@ async function handleSubmit() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建任务失败:', error)
|
||||
message.error(error?.message || '创建任务失败')
|
||||
toast.error(error?.message || '创建任务失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -84,52 +97,61 @@ function handleCancel() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-modal
|
||||
:open="visible"
|
||||
title="创建风格分析任务"
|
||||
:width="500"
|
||||
:maskClosable="false"
|
||||
:confirmLoading="loading"
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form :model="form" layout="vertical">
|
||||
<a-form-item label="任务名称" required>
|
||||
<a-input
|
||||
v-model:value="form.taskName"
|
||||
placeholder="请输入任务名称"
|
||||
:maxlength="100"
|
||||
/>
|
||||
</a-form-item>
|
||||
<Dialog :open="visible" @update:open="(v) => emit('update:visible', v)">
|
||||
<DialogContent class="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>创建风格分析任务</DialogTitle>
|
||||
<DialogDescription>
|
||||
将选中的视频创建为异步分析任务
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<a-form-item label="提示词名称" required>
|
||||
<a-input
|
||||
v-model:value="form.promptName"
|
||||
placeholder="任务完成后将保存为提示词"
|
||||
:maxlength="50"
|
||||
/>
|
||||
</a-form-item>
|
||||
<div class="space-y-4 py-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="taskName">任务名称 <span class="text-destructive">*</span></Label>
|
||||
<Input
|
||||
id="taskName"
|
||||
v-model="form.taskName"
|
||||
placeholder="请输入任务名称"
|
||||
:maxlength="100"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<a-form-item label="分类/标签">
|
||||
<a-input
|
||||
v-model:value="form.category"
|
||||
placeholder="可选:输入分类或标签"
|
||||
:maxlength="20"
|
||||
/>
|
||||
</a-form-item>
|
||||
<div class="space-y-2">
|
||||
<Label for="promptName">提示词名称 <span class="text-destructive">*</span></Label>
|
||||
<Input
|
||||
id="promptName"
|
||||
v-model="form.promptName"
|
||||
placeholder="任务完成后将保存为提示词"
|
||||
:maxlength="50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<a-form-item label="选中视频">
|
||||
<a-tag color="blue">{{ videoUrls.length }} 个视频</a-tag>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="space-y-2">
|
||||
<Label for="category">分类/标签</Label>
|
||||
<Input
|
||||
id="category"
|
||||
v-model="form.category"
|
||||
placeholder="可选:输入分类或标签"
|
||||
:maxlength="20"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
<a-button type="primary" :loading="loading" @click="handleSubmit">
|
||||
创建任务
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-modal>
|
||||
<div class="space-y-2">
|
||||
<Label>选中视频</Label>
|
||||
<div>
|
||||
<Badge variant="secondary">{{ videoUrls.length }} 个视频</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="handleCancel">取消</Button>
|
||||
<Button :disabled="loading" @click="handleSubmit">
|
||||
<Spinner v-if="loading" class="mr-2 h-4 w-4" />
|
||||
{{ loading ? '创建中...' : '创建任务' }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -407,18 +407,16 @@ onMounted(fetchList)
|
||||
padding: var(--space-4);
|
||||
background: var(--color-bg-card);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.task-page__content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
background: var(--color-bg-card);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.batch-actions {
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
v-for="item in NAV_ITEMS"
|
||||
:key="item.type"
|
||||
:value="item.type"
|
||||
class="h-10 px-4 gap-2 rounded-lg transition-all
|
||||
data-[state=active]:bg-primary data-[state=active]:text-white data-[state=active]:shadow-sm
|
||||
data-[state=inactive]:text-muted-foreground data-[state=inactive]:hover:text-foreground data-[state=inactive]:hover:bg-muted"
|
||||
class="h-10 px-4 gap-2 rounded-lg transition-all data-[state=active]:bg-foreground data-[state=active]:!text-white data-[state=active]:shadow-sm data-[state=inactive]:text-muted-foreground data-[state=inactive]:hover:text-foreground data-[state=inactive]:hover:bg-muted"
|
||||
>
|
||||
<Icon :icon="item.icon" class="size-4" />
|
||||
<span>{{ item.label }}</span>
|
||||
@@ -82,14 +80,12 @@ const currentComponent = computed(() => {
|
||||
.task-layout__header {
|
||||
flex-shrink: 0;
|
||||
padding: 0 var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background: var(--color-bg-card);
|
||||
}
|
||||
|
||||
.task-layout__content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
background: var(--color-bg-page);
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
:disabled="!hasDownloadableSelected"
|
||||
:loading="batchDownloading"
|
||||
size="sm"
|
||||
class="text-white"
|
||||
@click="handleBatchDownloadSelected"
|
||||
>
|
||||
<Icon icon="lucide:download" class="mr-1 size-4" />
|
||||
@@ -630,7 +631,6 @@ onMounted(fetchList)
|
||||
padding: var(--space-4);
|
||||
background: var(--color-bg-card);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.task-page__content {
|
||||
@@ -641,7 +641,6 @@ onMounted(fetchList)
|
||||
padding: var(--space-4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.batch-toolbar {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, computed } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
|
||||
import TikhubService, { InterfaceType, MethodType, ParamType } from '@/api/tikhub'
|
||||
import { rewriteStream } from '@/api/forecast'
|
||||
@@ -76,9 +78,9 @@ function handleSearchKeypress(event) {
|
||||
async function copyContent() {
|
||||
const success = await copyToClipboard(generatedContent.value)
|
||||
if (success) {
|
||||
message.success('已复制')
|
||||
toast.success('已复制')
|
||||
} else {
|
||||
message.error('复制失败')
|
||||
toast.error('复制失败')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +119,7 @@ async function analyzeVoice(audioUrl) {
|
||||
? `${topicDetails.copywriting}\n\n${transcript}`
|
||||
: transcript
|
||||
} else {
|
||||
message.warning('未获取到可用文案')
|
||||
toast.warning('未获取到可用文案')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('分析语音失败:', error)
|
||||
@@ -143,12 +145,12 @@ async function handleCreate(topic) {
|
||||
// 生成文案(流式)
|
||||
async function handleGenerate() {
|
||||
if (!topicDetails.copywriting?.trim()) {
|
||||
message.warning('请输入文案内容')
|
||||
toast.warning('请输入文案内容')
|
||||
return
|
||||
}
|
||||
|
||||
if (!selectedAgent.value?.id) {
|
||||
message.warning('请先选择文案风格')
|
||||
toast.warning('请先选择文案风格')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -197,7 +199,7 @@ async function handleGenerate() {
|
||||
completed = true
|
||||
clearTimeout(timeout)
|
||||
ctrl.abort()
|
||||
message.error(err?.message || '网络请求失败')
|
||||
toast.error(err?.message || '网络请求失败')
|
||||
reject(new Error(err?.message || '网络请求失败'))
|
||||
}
|
||||
},
|
||||
@@ -223,10 +225,10 @@ async function handleGenerate() {
|
||||
})
|
||||
|
||||
generatedContent.value = fullText.trim()
|
||||
message.success('文案生成成功')
|
||||
toast.success('文案生成成功')
|
||||
} catch (error) {
|
||||
console.error('生成文案失败:', error)
|
||||
message.error('生成文案失败')
|
||||
toast.error('生成文案失败')
|
||||
} finally {
|
||||
isGenerating.value = false
|
||||
}
|
||||
@@ -291,7 +293,7 @@ function processSearchResults(response, startId = 1) {
|
||||
async function handleSearch() {
|
||||
const keyword = searchKeyword.value.trim()
|
||||
if (!keyword) {
|
||||
message.warning('请输入搜索关键词')
|
||||
toast.warning('请输入搜索关键词')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -326,15 +328,15 @@ async function handleSearch() {
|
||||
const searchResults = processSearchResults(response)
|
||||
|
||||
if (searchResults.length === 0) {
|
||||
message.warning('未找到相关结果')
|
||||
toast.warning('未找到相关结果')
|
||||
return
|
||||
}
|
||||
|
||||
hotTopics.value = searchResults
|
||||
message.success(`找到 ${searchResults.length} 个结果`)
|
||||
toast.success(`找到 ${searchResults.length} 个结果`)
|
||||
} catch (error) {
|
||||
console.error('搜索失败:', error)
|
||||
message.error(error?.message || '搜索失败')
|
||||
toast.error(error?.message || '搜索失败')
|
||||
hotTopics.value = []
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -385,29 +387,44 @@ onMounted(() => {
|
||||
<div class="filters-row">
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">排序</span>
|
||||
<a-select v-model:value="searchParams.sort_type" size="small">
|
||||
<a-select-option value="0">综合</a-select-option>
|
||||
<a-select-option value="1">最多点赞</a-select-option>
|
||||
<a-select-option value="2">最新发布</a-select-option>
|
||||
</a-select>
|
||||
<Select v-model="searchParams.sort_type">
|
||||
<SelectTrigger class="h-[26px] w-[88px]">
|
||||
<SelectValue placeholder="综合" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">综合</SelectItem>
|
||||
<SelectItem value="1">最多点赞</SelectItem>
|
||||
<SelectItem value="2">最新发布</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">时间</span>
|
||||
<a-select v-model:value="searchParams.publish_time" size="small">
|
||||
<a-select-option value="0">不限</a-select-option>
|
||||
<a-select-option value="1">一天</a-select-option>
|
||||
<a-select-option value="7">一周</a-select-option>
|
||||
<a-select-option value="180">半年</a-select-option>
|
||||
</a-select>
|
||||
<Select v-model="searchParams.publish_time">
|
||||
<SelectTrigger class="h-[26px] w-[88px]">
|
||||
<SelectValue placeholder="不限" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">不限</SelectItem>
|
||||
<SelectItem value="1">一天</SelectItem>
|
||||
<SelectItem value="7">一周</SelectItem>
|
||||
<SelectItem value="180">半年</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">时长</span>
|
||||
<a-select v-model:value="searchParams.filter_duration" size="small">
|
||||
<a-select-option value="0">不限</a-select-option>
|
||||
<a-select-option value="0-1">1分钟内</a-select-option>
|
||||
<a-select-option value="1-5">1-5分钟</a-select-option>
|
||||
<a-select-option value="5-10000">5分钟+</a-select-option>
|
||||
</a-select>
|
||||
<Select v-model="searchParams.filter_duration">
|
||||
<SelectTrigger class="h-[26px] w-[88px]">
|
||||
<SelectValue placeholder="不限" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">不限</SelectItem>
|
||||
<SelectItem value="0-1">1分钟内</SelectItem>
|
||||
<SelectItem value="1-5">1-5分钟</SelectItem>
|
||||
<SelectItem value="5-10000">5分钟+</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -498,12 +515,12 @@ onMounted(() => {
|
||||
分析中
|
||||
</span>
|
||||
</div>
|
||||
<a-textarea
|
||||
v-model:value="topicDetails.copywriting"
|
||||
<Textarea
|
||||
v-model="topicDetails.copywriting"
|
||||
:rows="5"
|
||||
placeholder="输入文案或点击热点卡片自动提取..."
|
||||
:disabled="isAnalyzing"
|
||||
class="content-input"
|
||||
class="content-input min-h-[120px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -600,24 +617,18 @@ onMounted(() => {
|
||||
grid-template-columns: 1fr 400px;
|
||||
gap: var(--space-4);
|
||||
height: 100%;
|
||||
background: var(--color-gray-50);
|
||||
}
|
||||
|
||||
// 面板基础
|
||||
// 面板基础 - 更现代简洁
|
||||
.search-panel,
|
||||
.create-panel {
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: var(--radius-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: var(--space-4) var(--space-5);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
}.panel-header {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
@@ -709,7 +720,6 @@ onMounted(() => {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
padding: 0 var(--space-4) var(--space-3);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
@@ -723,23 +733,6 @@ onMounted(() => {
|
||||
color: var(--color-gray-400);
|
||||
}
|
||||
|
||||
:deep(.ant-select) {
|
||||
width: 88px;
|
||||
|
||||
.ant-select-selector {
|
||||
background: var(--color-gray-50) !important;
|
||||
border-color: var(--color-gray-300) !important;
|
||||
border-radius: var(--radius-base) !important;
|
||||
height: 26px !important;
|
||||
|
||||
.ant-select-selection-item {
|
||||
color: var(--color-gray-900) !important;
|
||||
font-size: var(--font-size-xs) !important;
|
||||
line-height: 24px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结果区域
|
||||
.results-area {
|
||||
flex: 1;
|
||||
@@ -812,19 +805,16 @@ onMounted(() => {
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3);
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all var(--duration-fast);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-gray-300);
|
||||
box-shadow: var(--shadow-md);
|
||||
background: var(--color-gray-50);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: var(--color-primary-500);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
background: rgba(59, 130, 246, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1008,42 +998,11 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.content-input {
|
||||
:deep(.ant-input) {
|
||||
background: var(--color-gray-50) !important;
|
||||
border-color: var(--color-gray-300) !important;
|
||||
color: var(--color-gray-900) !important;
|
||||
border-radius: var(--radius-base) !important;
|
||||
font-size: var(--font-size-sm) !important;
|
||||
line-height: 1.6 !important;
|
||||
|
||||
&::placeholder { color: var(--color-gray-400) !important; }
|
||||
&:focus {
|
||||
border-color: var(--color-primary-500) !important;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉选择器
|
||||
:deep(.ant-select) {
|
||||
.ant-select-selector {
|
||||
background: var(--color-gray-50) !important;
|
||||
border-color: var(--color-gray-300) !important;
|
||||
border-radius: var(--radius-base) !important;
|
||||
height: 36px !important;
|
||||
|
||||
.ant-select-selection-item,
|
||||
.ant-select-selection-placeholder {
|
||||
color: var(--color-gray-900) !important;
|
||||
font-size: var(--font-size-sm) !important;
|
||||
line-height: 34px !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.ant-select-focused .ant-select-selector {
|
||||
border-color: var(--color-primary-500) !important;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1) !important;
|
||||
}
|
||||
background: var(--color-gray-50);
|
||||
border-color: var(--color-gray-300);
|
||||
border-radius: var(--radius-base);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.agent-category {
|
||||
|
||||
Reference in New Issue
Block a user