样式修改
This commit is contained in:
1 parent
fc5eaa2941
commit
03329387b3
6 files changed
+1242
-1173
No files matched your search
@@ -104,9 +104,9 @@ export default function StrategyRoutes() {
|
|||||||
path="plan-task/plan-task-fx"
|
path="plan-task/plan-task-fx"
|
||||||
element={
|
element={
|
||||||
<TaskAnalyzes
|
<TaskAnalyzes
|
||||||
title="æµ è¯²å§ŸFX"
|
title="任务分析"
|
||||||
category="ç‘™å‹«åžæµ 诲姟"
|
category="规划任务"
|
||||||
subCategory="ç‘™å‹«åžæµ 诲姟"
|
subCategory="规划任务"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { ChevronRight, FileText } from 'lucide-react';
|
import { FileText } from 'lucide-react';
|
||||||
import { Pagination } from 'tdesign-react';
|
import { Pagination } from 'tdesign-react';
|
||||||
import { useTheme } from '@/contexts/ThemeContext';
|
import { useTheme } from '@/contexts/ThemeContext';
|
||||||
import { TaskListItem } from './TaskManagement';
|
import { TaskListItem } from './TaskManagement';
|
||||||
@@ -11,8 +11,8 @@ interface KanbanColumnProps {
|
|||||||
color: string;
|
color: string;
|
||||||
tasks: TaskListItem[];
|
tasks: TaskListItem[];
|
||||||
taskType: string;
|
taskType: string;
|
||||||
TaskDataType?: string;
|
taskDataType?: string;
|
||||||
pageIndexSearch: (taskType: string, pageIndex: number) => void;
|
pageIndexSearch: (taskType: string, pageIndex: number) => void | Promise<void>;
|
||||||
handleStartAnalysis: (task: TaskListItem) => void;
|
handleStartAnalysis: (task: TaskListItem) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ export const KanbanColumn: React.FC<KanbanColumnProps> = ({
|
|||||||
color,
|
color,
|
||||||
tasks,
|
tasks,
|
||||||
taskType,
|
taskType,
|
||||||
TaskDataType = 'task',
|
taskDataType = 'task',
|
||||||
pageIndexSearch,
|
pageIndexSearch,
|
||||||
handleStartAnalysis,
|
handleStartAnalysis,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -30,94 +30,79 @@ export const KanbanColumn: React.FC<KanbanColumnProps> = ({
|
|||||||
const isDark = theme === 'dark';
|
const isDark = theme === 'dark';
|
||||||
const [pageNum, setPageNum] = useState(1);
|
const [pageNum, setPageNum] = useState(1);
|
||||||
|
|
||||||
const pageIndexChange = async (value: number) => {
|
const handlePageChange = async (value: number) => {
|
||||||
setPageNum(value);
|
setPageNum(value);
|
||||||
await pageIndexSearch(taskType, value);
|
await pageIndexSearch(taskType, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const columnClass = isDark
|
||||||
|
? 'border border-[rgba(67,89,124,0.46)] bg-[linear-gradient(180deg,rgba(18,36,67,0.96),rgba(12,28,54,0.96))] shadow-[0_18px_40px_rgba(3,10,22,0.18)]'
|
||||||
|
: 'border border-[#E8E8E8] bg-[linear-gradient(180deg,#FFFFFF_0%,#FFF9F9_100%)] shadow-[0_14px_28px_rgba(245,34,45,0.06)]';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<section className={`flex h-full flex-col rounded-[28px] p-4 ${columnClass}`}>
|
||||||
className={`theme-transition flex h-full min-h-0 flex-col rounded-[28px] border p-4 md:p-5 ${
|
<div className="flex items-center justify-between gap-4">
|
||||||
isDark
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
? 'theme-card border-[rgba(110,136,176,0.24)] shadow-[0_18px_50px_rgba(2,8,20,0.24)]'
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
: 'border-slate-200 bg-white shadow-sm'
|
<span className={`h-2.5 w-2.5 rounded-full ${color}`} />
|
||||||
}`}
|
<h2
|
||||||
>
|
className={`whitespace-nowrap text-[15px] font-semibold leading-none ${
|
||||||
<div className="mb-4 flex items-start justify-between gap-3">
|
isDark ? 'text-white' : 'text-[#222222]'
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<div className={`mt-1 h-2.5 w-2.5 rounded-full ${color}`} />
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<h2 className={`text-base font-semibold ${isDark ? 'text-slate-50' : 'text-slate-900'}`}>{title}</h2>
|
|
||||||
<span
|
|
||||||
className={`rounded-full px-2 py-0.5 text-xs ${
|
|
||||||
isDark ? 'bg-[rgba(20,47,86,0.92)] text-slate-300' : 'bg-slate-100 text-slate-600'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{count}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className={`mt-1 text-xs ${isDark ? 'text-slate-400' : 'text-slate-500'}`}>
|
|
||||||
按时间倒序展示,支持分页切换。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div
|
|
||||||
className={`hidden rounded-xl p-2 lg:flex ${
|
|
||||||
isDark ? 'bg-[rgba(10,30,54,0.82)] text-slate-300' : 'bg-slate-50 text-slate-500'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<ChevronRight className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<Pagination
|
|
||||||
className="sentiment-pagination"
|
|
||||||
total={count}
|
|
||||||
size="small"
|
|
||||||
theme="simple"
|
|
||||||
current={pageNum}
|
|
||||||
pageSize={10}
|
|
||||||
maxPageBtn={3}
|
|
||||||
showPageSize={false}
|
|
||||||
totalContent={false}
|
|
||||||
onCurrentChange={(page) => pageIndexChange(Number(page))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="scrollbar-thin flex-1 space-y-3 overflow-y-auto pr-1">
|
|
||||||
{tasks.length === 0 ? (
|
|
||||||
<div
|
|
||||||
className={`flex h-full min-h-[260px] flex-col items-center justify-center rounded-2xl border border-dashed ${
|
|
||||||
isDark
|
|
||||||
? 'border-[rgba(110,136,176,0.24)] bg-[rgba(9,25,46,0.38)]'
|
|
||||||
: 'border-slate-200 bg-slate-50'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`mb-3 flex h-12 w-12 items-center justify-center rounded-2xl ${
|
|
||||||
isDark ? 'bg-[rgba(13,42,78,0.92)] text-slate-300' : 'bg-white text-slate-500'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FileText className="h-5 w-5" />
|
{title}
|
||||||
</div>
|
</h2>
|
||||||
<div className={`text-sm ${isDark ? 'text-slate-300' : 'text-slate-600'}`}>暂无任务</div>
|
<span
|
||||||
<div className={`mt-1 text-xs ${isDark ? 'text-slate-500' : 'text-slate-400'}`}>
|
className={`inline-flex rounded-full px-2 py-0.5 text-xs ${
|
||||||
可以尝试调整筛选条件或切换数据模式。
|
isDark ? 'bg-[rgba(77,141,255,0.14)] text-[#9CCBFF]' : 'bg-[#FFF1F0] text-[#CF1322]'
|
||||||
</div>
|
}`}
|
||||||
|
>
|
||||||
|
{count}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Pagination
|
||||||
|
className="sentiment-pagination shrink-0"
|
||||||
|
total={count}
|
||||||
|
size="small"
|
||||||
|
theme="simple"
|
||||||
|
current={pageNum}
|
||||||
|
pageSize={10}
|
||||||
|
maxPageBtn={3}
|
||||||
|
showPageSize={false}
|
||||||
|
totalContent={false}
|
||||||
|
onCurrentChange={(page) => handlePageChange(Number(page))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className={`mt-2 text-xs leading-6 ${isDark ? 'text-slate-400' : 'text-[#8C8C8C]'}`}>
|
||||||
|
按时间倒序展示,支持分页切换。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-4 flex-1 space-y-4">
|
||||||
|
{tasks.length === 0 ? (
|
||||||
|
<div
|
||||||
|
className={`flex h-full min-h-[220px] flex-col items-center justify-center rounded-[22px] border border-dashed ${
|
||||||
|
isDark
|
||||||
|
? 'border-[rgba(87,111,149,0.42)] bg-[rgba(12,24,44,0.46)] text-slate-400'
|
||||||
|
: 'border-[#F3C4C8] bg-[#FFF7F7] text-[#A6A6A6]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<FileText className="h-8 w-8 opacity-70" />
|
||||||
|
<p className="mt-3 text-sm">暂无任务</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
tasks.map((task) => (
|
tasks.map((task) => (
|
||||||
<TaskCard
|
<TaskCard
|
||||||
key={task.id}
|
key={task.id}
|
||||||
task={task}
|
task={task}
|
||||||
TaskType={TaskDataType}
|
taskDataType={taskDataType}
|
||||||
handleStartAnalysis={handleStartAnalysis}
|
handleStartAnalysis={handleStartAnalysis}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
+399
-315
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { ArrowRight, Bot, Clock3, Layers, Search, Sparkles, Users, X } from 'lucide-react';
|
import { ArrowRight, Layers, Plus, Search, Users, X } from 'lucide-react';
|
||||||
import { DateRangePicker, DateRangeValue, Loading } from 'tdesign-react';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { DateRangePicker, DateRangePickerProps, DateRangeValue, Loading } from 'tdesign-react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useTheme } from '@/contexts/ThemeContext';
|
import { useTheme } from '@/contexts/ThemeContext';
|
||||||
import { useChatContext } from '@/contexts/ChatContext';
|
import { useChatContext } from '@/contexts/ChatContext';
|
||||||
@@ -15,158 +15,156 @@ interface TaskListPageProps {
|
|||||||
subCategory: string;
|
subCategory: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaskType = 'single' | 'consultation' | 'scene' | 'multi-agent' | 'task-search';
|
type ColumnType = 'unanalyzed' | 'running' | 'completed';
|
||||||
type ColumnType = 'unanalyzed' | 'analyzing' | 'completed';
|
|
||||||
|
|
||||||
const service = requestCrudService;
|
const service = requestCrudService;
|
||||||
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
|
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
|
||||||
const ENABLE_SENTIMENT_DEMO_BY_DEFAULT = import.meta.env.DEV;
|
const ENABLE_SENTIMENT_DEMO_BY_DEFAULT = import.meta.env.DEV;
|
||||||
|
|
||||||
const buildMockTask = (
|
const buildMockTask = (
|
||||||
id: string,
|
id: number,
|
||||||
name: string,
|
taskName: string,
|
||||||
overview: string,
|
overview: string,
|
||||||
column: ColumnType,
|
column: ColumnType,
|
||||||
type: TaskType,
|
taskType: number,
|
||||||
priority: 1 | 2 | 3,
|
taskLevel: number,
|
||||||
progress: number,
|
taskDirection: string,
|
||||||
createTime: string,
|
createTime: string,
|
||||||
updateTime: string,
|
updateTime: string,
|
||||||
owner: string,
|
|
||||||
): TaskListItem => ({
|
): TaskListItem => ({
|
||||||
id,
|
createBy: 0,
|
||||||
taskName: name,
|
|
||||||
overview,
|
|
||||||
column,
|
|
||||||
type,
|
|
||||||
priority,
|
|
||||||
progress,
|
|
||||||
createTime,
|
createTime,
|
||||||
updateTime,
|
deptId: null,
|
||||||
owner,
|
id,
|
||||||
creator: owner,
|
isTop: 0,
|
||||||
executor: owner,
|
issuingDept: '',
|
||||||
|
keywords: null,
|
||||||
|
overview,
|
||||||
|
planTaskTop: 0,
|
||||||
|
platform: null,
|
||||||
|
ptaskId: null,
|
||||||
|
recommendedBasis: null,
|
||||||
scene: JSON.stringify({
|
scene: JSON.stringify({
|
||||||
taskJson: JSON.stringify({
|
taskJson: JSON.stringify({
|
||||||
task_title: name,
|
task_title: taskName,
|
||||||
task_goal: overview,
|
task_goal: overview,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
sourceType: 0,
|
||||||
|
step: null,
|
||||||
|
taskContent: overview,
|
||||||
|
taskDirection,
|
||||||
|
taskEndDate: '',
|
||||||
|
taskLevel,
|
||||||
|
taskName,
|
||||||
|
taskNo: `TASK-${id}`,
|
||||||
|
taskStartDate: '',
|
||||||
|
taskStatus: 0,
|
||||||
|
taskSuperiorFlag: 0,
|
||||||
|
taskTimeLimit: null,
|
||||||
|
taskType,
|
||||||
|
column,
|
||||||
|
updateBy: 0,
|
||||||
|
updateTime,
|
||||||
|
endCount: 0,
|
||||||
|
sumAll: 0,
|
||||||
|
progress: 0,
|
||||||
|
taskStatusData: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const MOCK_TASKS: Record<ColumnType, TaskListItem[]> = {
|
const MOCK_TASKS: Record<ColumnType, TaskListItem[]> = {
|
||||||
unanalyzed: [
|
unanalyzed: [
|
||||||
buildMockTask(
|
buildMockTask(
|
||||||
'demo-u-1',
|
1001,
|
||||||
'新能源价格舆情波动分析',
|
'新能源价格舆情波动分析',
|
||||||
'聚焦近 7 日新能源整车与动力电池价格讨论热度,识别社媒与媒体端异常扩散信号。',
|
'聚焦近 7 日新能源整车与动力电池价格讨论热度,识别社媒与媒体端异常扩散信号。',
|
||||||
'unanalyzed',
|
'unanalyzed',
|
||||||
'single',
|
1,
|
||||||
2,
|
2,
|
||||||
0,
|
|
||||||
'2026-06-05 09:10:00',
|
|
||||||
'2026-06-05 09:10:00',
|
|
||||||
'王晓楠',
|
'王晓楠',
|
||||||
|
'2026-06-05 09:10:00',
|
||||||
|
'2026-06-05 09:10:00',
|
||||||
),
|
),
|
||||||
buildMockTask(
|
buildMockTask(
|
||||||
'demo-u-2',
|
1002,
|
||||||
'重点项目负面反馈聚类',
|
'重点项目负面反馈聚类',
|
||||||
'从任务评论、审批意见和业务群消息中抽取高频情绪主题,辅助判断风险聚焦点。',
|
'从任务评论、审批意见和业务群消息中抽取高频情绪主题,辅助判断风险聚焦点。',
|
||||||
'unanalyzed',
|
'unanalyzed',
|
||||||
'consultation',
|
|
||||||
3,
|
3,
|
||||||
0,
|
3,
|
||||||
'2026-06-05 08:40:00',
|
|
||||||
'2026-06-05 08:40:00',
|
|
||||||
'刘志诚',
|
'刘志诚',
|
||||||
|
'2026-06-05 08:40:00',
|
||||||
|
'2026-06-05 08:40:00',
|
||||||
),
|
),
|
||||||
buildMockTask(
|
buildMockTask(
|
||||||
'demo-u-3',
|
1003,
|
||||||
'竞品发布会用户情绪追踪',
|
'竞品发布会用户情绪追踪',
|
||||||
'围绕发布会直播、短视频评论与论坛帖子,建立情绪转折时间线并识别主要触发事件。',
|
'围绕发布会直播、短视频评论与论坛帖子,建立情绪转折时间线并识别主要触发事件。',
|
||||||
'unanalyzed',
|
'unanalyzed',
|
||||||
'scene',
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
'2026-06-04 18:20:00',
|
|
||||||
'2026-06-04 18:20:00',
|
|
||||||
'陈思雨',
|
|
||||||
),
|
|
||||||
buildMockTask(
|
|
||||||
'demo-u-4',
|
|
||||||
'区域渠道满意度扫描',
|
|
||||||
'基于周报、工单和售后回访文本,发现华东区域渠道服务满意度下行趋势。',
|
|
||||||
'unanalyzed',
|
|
||||||
'task-search',
|
|
||||||
2,
|
2,
|
||||||
0,
|
1,
|
||||||
'2026-06-04 15:35:00',
|
'陈思雨',
|
||||||
'2026-06-04 15:35:00',
|
'2026-06-04 18:20:00',
|
||||||
'赵明宇',
|
'2026-06-04 18:20:00',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
analyzing: [
|
running: [
|
||||||
buildMockTask(
|
buildMockTask(
|
||||||
'demo-a-1',
|
2001,
|
||||||
'新车上市口碑监测会商',
|
'新车上市口碑监测会商',
|
||||||
'正在联合多个智能体汇总平台评价、媒体测评与用户问答,生成阶段性情绪画像。',
|
'正在联合多个智能体汇总平台评价、媒体测评与用户问答,生成阶段性情绪画像。',
|
||||||
'analyzing',
|
'running',
|
||||||
'multi-agent',
|
4,
|
||||||
3,
|
3,
|
||||||
56,
|
'张雨桐',
|
||||||
'2026-06-05 07:50:00',
|
'2026-06-05 07:50:00',
|
||||||
'2026-06-05 10:05:00',
|
'2026-06-05 10:05:00',
|
||||||
'张雨桐',
|
|
||||||
),
|
),
|
||||||
buildMockTask(
|
buildMockTask(
|
||||||
'demo-a-2',
|
2002,
|
||||||
'投诉热点归因拆解',
|
'投诉热点归因拆解',
|
||||||
'拆分售后投诉中关于续航、交付和门店服务的负面占比,分析主要归因与蔓延路径。',
|
'拆分售后投诉中关于续航、交付和门店服务的负面占比,分析主要归因与蔓延路径。',
|
||||||
'analyzing',
|
'running',
|
||||||
'consultation',
|
3,
|
||||||
2,
|
2,
|
||||||
78,
|
'何嘉成',
|
||||||
'2026-06-04 20:10:00',
|
'2026-06-04 20:10:00',
|
||||||
'2026-06-05 09:48:00',
|
'2026-06-05 09:48:00',
|
||||||
'何嘉成',
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
completed: [
|
completed: [
|
||||||
buildMockTask(
|
buildMockTask(
|
||||||
'demo-c-1',
|
3001,
|
||||||
'车展传播效果情绪复盘',
|
'车展传播效果情绪复盘',
|
||||||
'已输出传播节点热度变化、核心评论主题以及 KOL 内容对情绪曲线的影响分析。',
|
'已输出传播节点热度变化、核心评论主题以及 KOL 内容对情绪曲线的影响分析。',
|
||||||
'completed',
|
'completed',
|
||||||
'single',
|
|
||||||
1,
|
1,
|
||||||
100,
|
1,
|
||||||
|
'孙怡宁',
|
||||||
'2026-06-03 14:20:00',
|
'2026-06-03 14:20:00',
|
||||||
'2026-06-04 17:30:00',
|
'2026-06-04 17:30:00',
|
||||||
'孙怡宁',
|
|
||||||
),
|
),
|
||||||
buildMockTask(
|
buildMockTask(
|
||||||
'demo-c-2',
|
3002,
|
||||||
'智能驾驶争议议题扫描',
|
'智能驾驶争议议题扫描',
|
||||||
'已完成争议议题聚类、情绪倾向分层和外部平台扩散链路分析,可直接查看结论。',
|
'已完成争议议题聚类、情绪倾向分层和外部平台扩散链路分析,可直接查看结论。',
|
||||||
'completed',
|
'completed',
|
||||||
'scene',
|
2,
|
||||||
3,
|
3,
|
||||||
100,
|
'李承泽',
|
||||||
'2026-06-03 09:00:00',
|
'2026-06-03 09:00:00',
|
||||||
'2026-06-04 11:45:00',
|
'2026-06-04 11:45:00',
|
||||||
'李承泽',
|
|
||||||
),
|
),
|
||||||
buildMockTask(
|
buildMockTask(
|
||||||
'demo-c-3',
|
3003,
|
||||||
'品牌联名活动舆情总结',
|
'品牌联名活动舆情总结',
|
||||||
'已完成用户态度分布统计与正负面声音对比,附带评论样本摘要和建议动作。',
|
'已完成用户态度分布统计与正负面声音对比,附带评论样本摘要和建议动作。',
|
||||||
'completed',
|
'completed',
|
||||||
'task-search',
|
5,
|
||||||
2,
|
2,
|
||||||
100,
|
'周清越',
|
||||||
'2026-06-02 16:50:00',
|
'2026-06-02 16:50:00',
|
||||||
'2026-06-03 19:12:00',
|
'2026-06-03 19:12:00',
|
||||||
'周清越',
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -176,28 +174,27 @@ const paginateTasks = (tasks: TaskListItem[], pageIndex: number, pageSize: numbe
|
|||||||
return tasks.slice(start, start + pageSize);
|
return tasks.slice(start, start + pageSize);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeRangeValue = (range: DateRangeValue) => {
|
||||||
|
if (!Array.isArray(range) || range.length < 2) {
|
||||||
|
return { startTime: '', endTime: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
startTime: dayjs(range[0]).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
endTime: dayjs(range[1]).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategory }) => {
|
const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategory }) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const { setStartConfig } = useChatContext();
|
const { setStartConfig } = useChatContext();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isDark = theme === 'dark';
|
const isDark = theme === 'dark';
|
||||||
const shellClass = isDark
|
|
||||||
? 'strategy-data min-h-full bg-[radial-gradient(circle_at_top_left,rgba(27,94,168,0.28),transparent_32%),linear-gradient(180deg,#081322_0%,#0d1d33_42%,#0a1628_100%)] text-slate-100'
|
|
||||||
: 'strategy-data min-h-full bg-[#f8fafc] text-slate-900';
|
|
||||||
const metricCardClass = isDark
|
|
||||||
? 'border border-[rgba(110,136,176,0.24)] bg-[linear-gradient(180deg,rgba(12,32,58,0.96),rgba(9,22,39,0.96))] shadow-[0_14px_42px_rgba(2,8,20,0.30)]'
|
|
||||||
: 'border border-slate-200 bg-white shadow-sm';
|
|
||||||
const inputClass = isDark
|
|
||||||
? 'border-[rgba(110,136,176,0.3)] bg-[rgba(8,23,42,0.92)] text-slate-100 placeholder:text-slate-500'
|
|
||||||
: 'border-slate-200 bg-white text-slate-900 placeholder:text-slate-400';
|
|
||||||
const panelGlowClass = isDark
|
|
||||||
? 'before:pointer-events-none before:absolute before:inset-x-8 before:top-0 before:h-px before:bg-[linear-gradient(90deg,transparent,rgba(96,165,250,0.45),transparent)]'
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [showAnalysisModal, setShowAnalysisModal] = useState(false);
|
|
||||||
const [selectedTaskForAnalysis, setSelectedTaskForAnalysis] = useState<TaskListItem | null>(null);
|
|
||||||
const [useDemoData, setUseDemoData] = useState(ENABLE_SENTIMENT_DEMO_BY_DEFAULT);
|
const [useDemoData, setUseDemoData] = useState(ENABLE_SENTIMENT_DEMO_BY_DEFAULT);
|
||||||
|
const [selectedTask, setSelectedTask] = useState<TaskListItem | null>(null);
|
||||||
|
const [showAnalysisModal, setShowAnalysisModal] = useState(false);
|
||||||
const [unanalyzedTotal, setUnanalyzedTotal] = useState(0);
|
const [unanalyzedTotal, setUnanalyzedTotal] = useState(0);
|
||||||
const [runningTotal, setRunningTotal] = useState(0);
|
const [runningTotal, setRunningTotal] = useState(0);
|
||||||
const [completedTotal, setCompletedTotal] = useState(0);
|
const [completedTotal, setCompletedTotal] = useState(0);
|
||||||
@@ -207,378 +204,465 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
|
|||||||
const [unanalyzedLoading, setUnanalyzedLoading] = useState(false);
|
const [unanalyzedLoading, setUnanalyzedLoading] = useState(false);
|
||||||
const [runningLoading, setRunningLoading] = useState(false);
|
const [runningLoading, setRunningLoading] = useState(false);
|
||||||
const [completedLoading, setCompletedLoading] = useState(false);
|
const [completedLoading, setCompletedLoading] = useState(false);
|
||||||
const [dateRange, setDateRange] = useState<DateRangeValue>([
|
const [range, setRange] = useState<DateRangeValue>([]);
|
||||||
dayjs().startOf('month').format('YYYY-MM-DD'),
|
|
||||||
dayjs().endOf('day').format('YYYY-MM-DD'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const pageSizeRef = useRef(10);
|
const pageSizeRef = useRef(10);
|
||||||
|
|
||||||
|
const presets = useMemo<DateRangePickerProps['presets']>(
|
||||||
|
() => ({
|
||||||
|
最近7天: [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()],
|
||||||
|
最近3天: [dayjs().subtract(2, 'day').toDate(), dayjs().toDate()],
|
||||||
|
今天: [dayjs().toDate(), dayjs().toDate()],
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const filteredMock = useMemo(() => {
|
const filteredMock = useMemo(() => {
|
||||||
const keyword = searchQuery.trim().toLowerCase();
|
const keyword = searchQuery.trim().toLowerCase();
|
||||||
const filterItems = (items: TaskListItem[]) => {
|
const filterList = (list: TaskListItem[]) => {
|
||||||
if (!keyword) return items;
|
if (!keyword) return list;
|
||||||
return items.filter((item) => {
|
return list.filter((item) =>
|
||||||
const text = `${item.taskName} ${item.overview} ${item.owner || ''}`.toLowerCase();
|
`${item.taskName} ${item.overview} ${item.taskDirection}`.toLowerCase().includes(keyword),
|
||||||
return text.includes(keyword);
|
);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
unanalyzed: filterItems(MOCK_TASKS.unanalyzed),
|
unanalyzed: filterList(MOCK_TASKS.unanalyzed),
|
||||||
analyzing: filterItems(MOCK_TASKS.analyzing),
|
running: filterList(MOCK_TASKS.running),
|
||||||
completed: filterItems(MOCK_TASKS.completed),
|
completed: filterList(MOCK_TASKS.completed),
|
||||||
};
|
};
|
||||||
}, [searchQuery]);
|
}, [searchQuery]);
|
||||||
|
|
||||||
const applyMockColumn = (column: ColumnType, pageIndex = 1) => {
|
const applyMockColumn = (column: ColumnType, pageIndex = 1) => {
|
||||||
const source = filteredMock[column];
|
const source = filteredMock[column];
|
||||||
const pageItems = paginateTasks(source, pageIndex, pageSizeRef.current);
|
const pageList = paginateTasks(source, pageIndex, pageSizeRef.current);
|
||||||
|
|
||||||
if (column === 'unanalyzed') {
|
if (column === 'unanalyzed') {
|
||||||
setUnanalyzedTotal(source.length);
|
setUnanalyzedTotal(source.length);
|
||||||
setUnanalyzedList(pageItems);
|
setUnanalyzedList(pageList);
|
||||||
setUnanalyzedLoading(false);
|
setUnanalyzedLoading(false);
|
||||||
} else if (column === 'analyzing') {
|
return;
|
||||||
setRunningTotal(source.length);
|
|
||||||
setRunningList(pageItems);
|
|
||||||
setRunningLoading(false);
|
|
||||||
} else {
|
|
||||||
setCompletedTotal(source.length);
|
|
||||||
setCompletedList(pageItems);
|
|
||||||
setCompletedLoading(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (column === 'running') {
|
||||||
|
setRunningTotal(source.length);
|
||||||
|
setRunningList(pageList);
|
||||||
|
setRunningLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCompletedTotal(source.length);
|
||||||
|
setCompletedList(pageList);
|
||||||
|
setCompletedLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const taskListSearch = async (
|
const taskListSearch = async (
|
||||||
taskStatus: ColumnType,
|
column: ColumnType,
|
||||||
pageIndex = 1,
|
pageIndex = 1,
|
||||||
pageSize = pageSizeRef.current,
|
pageSize = pageSizeRef.current,
|
||||||
) => {
|
) => {
|
||||||
pageSizeRef.current = pageSize;
|
pageSizeRef.current = pageSize;
|
||||||
|
|
||||||
const setLoadingByType = (loading: boolean) => {
|
if (column === 'unanalyzed') setUnanalyzedLoading(true);
|
||||||
if (taskStatus === 'unanalyzed') setUnanalyzedLoading(loading);
|
if (column === 'running') setRunningLoading(true);
|
||||||
if (taskStatus === 'analyzing') setRunningLoading(loading);
|
if (column === 'completed') setCompletedLoading(true);
|
||||||
if (taskStatus === 'completed') setCompletedLoading(loading);
|
|
||||||
};
|
|
||||||
|
|
||||||
setLoadingByType(true);
|
|
||||||
|
|
||||||
if (useDemoData) {
|
if (useDemoData) {
|
||||||
applyMockColumn(taskStatus, pageIndex);
|
applyMockColumn(column, pageIndex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const taskStatusMap: Record<ColumnType, number[]> = {
|
||||||
|
unanalyzed: [0, 10],
|
||||||
|
running: [20],
|
||||||
|
completed: [25, 30, 40, 60, 70, 80, 90, 100, 110],
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [startTime, endTime] = Array.isArray(dateRange) ? dateRange : ['', ''];
|
const { startTime, endTime } = normalizeRangeValue(range);
|
||||||
const payload = {
|
const response = await service.post(`${zncmUrl}/three/cop-three-tasks`, {
|
||||||
|
content: searchQuery.trim(),
|
||||||
pageNum: pageIndex,
|
pageNum: pageIndex,
|
||||||
pageSize,
|
pageSize,
|
||||||
taskStatus,
|
taskStatus: taskStatusMap[column],
|
||||||
taskCategoryName: category,
|
|
||||||
taskTypeName: subCategory,
|
|
||||||
searchKey: searchQuery.trim() || undefined,
|
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
};
|
taskDirection: '',
|
||||||
|
});
|
||||||
|
|
||||||
const response = await service.post(`${zncmUrl}/three/cop-three-tasks`, payload);
|
const result = response?.data ?? response ?? {};
|
||||||
const rows = response?.rows || response?.data?.rows || [];
|
const records = Array.isArray(result.records) ? result.records : [];
|
||||||
const total = response?.total || response?.data?.total || 0;
|
const total = Number(result.total ?? records.length);
|
||||||
const mappedList = rows.map((item: TaskListItem) => ({
|
const mapped = records.map((item: TaskListItem) => ({
|
||||||
...item,
|
...item,
|
||||||
column: taskStatus === 'analyzing' ? 'running' : taskStatus,
|
column,
|
||||||
|
taskLevel: item.taskLevel || 1,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (taskStatus === 'unanalyzed') {
|
if (column === 'unanalyzed') {
|
||||||
setUnanalyzedList(mappedList);
|
setUnanalyzedList(mapped);
|
||||||
setUnanalyzedTotal(total);
|
setUnanalyzedTotal(total);
|
||||||
} else if (taskStatus === 'analyzing') {
|
} else if (column === 'running') {
|
||||||
setRunningList(mappedList);
|
setRunningList(mapped);
|
||||||
setRunningTotal(total);
|
setRunningTotal(total);
|
||||||
} else {
|
} else {
|
||||||
setCompletedList(mappedList);
|
setCompletedList(mapped);
|
||||||
setCompletedTotal(total);
|
setCompletedTotal(total);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('sentiment task list fetch failed', error);
|
console.error('sentiment task list fetch failed', error);
|
||||||
applyMockColumn(taskStatus, pageIndex);
|
|
||||||
setUseDemoData(true);
|
setUseDemoData(true);
|
||||||
|
applyMockColumn(column, pageIndex);
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingByType(false);
|
if (column === 'unanalyzed') setUnanalyzedLoading(false);
|
||||||
|
if (column === 'running') setRunningLoading(false);
|
||||||
|
if (column === 'completed') setCompletedLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshAllColumns = async () => {
|
const refreshColumns = () => {
|
||||||
await Promise.all([
|
taskListSearch('unanalyzed', 1);
|
||||||
taskListSearch('unanalyzed', 1),
|
taskListSearch('running', 1);
|
||||||
taskListSearch('analyzing', 1),
|
taskListSearch('completed', 1);
|
||||||
taskListSearch('completed', 1),
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refreshAllColumns();
|
refreshColumns();
|
||||||
}, [useDemoData]);
|
}, [useDemoData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = window.setTimeout(() => {
|
const timer = window.setTimeout(() => {
|
||||||
refreshAllColumns();
|
refreshColumns();
|
||||||
}, useDemoData ? 120 : 260);
|
}, 200);
|
||||||
|
|
||||||
return () => window.clearTimeout(timer);
|
return () => window.clearTimeout(timer);
|
||||||
}, [searchQuery, dateRange, useDemoData]);
|
}, [searchQuery, range]);
|
||||||
|
|
||||||
const metrics = [
|
|
||||||
{
|
|
||||||
label: '全部任务',
|
|
||||||
value: unanalyzedTotal + runningTotal + completedTotal,
|
|
||||||
icon: Layers,
|
|
||||||
accent: 'from-[#4D8DFF] to-[#76C2FF]',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '未分析',
|
|
||||||
value: unanalyzedTotal,
|
|
||||||
icon: Clock3,
|
|
||||||
accent: 'from-[#38BDF8] to-[#7DD3FC]',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '分析中',
|
|
||||||
value: runningTotal,
|
|
||||||
icon: Sparkles,
|
|
||||||
accent: 'from-[#2DD4BF] to-[#67E8F9]',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '已完成',
|
|
||||||
value: completedTotal,
|
|
||||||
icon: Bot,
|
|
||||||
accent: 'from-[#60A5FA] to-[#A78BFA]',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleStartAnalysis = (task: TaskListItem) => {
|
const handleStartAnalysis = (task: TaskListItem) => {
|
||||||
setSelectedTaskForAnalysis(task);
|
setSelectedTask(task);
|
||||||
setShowAnalysisModal(true);
|
setShowAnalysisModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewTaskDetail = () => {
|
const handleOpenTaskDetail = () => {
|
||||||
if (!selectedTaskForAnalysis) return;
|
if (!selectedTask) return;
|
||||||
|
|
||||||
setStartConfig({
|
setStartConfig({
|
||||||
task_title: selectedTaskForAnalysis.taskName,
|
task_title: selectedTask.taskName,
|
||||||
task_goal: selectedTaskForAnalysis.overview,
|
task_goal: selectedTask.overview,
|
||||||
task_context: selectedTaskForAnalysis.overview,
|
task_context: selectedTask.overview,
|
||||||
llm_preset_id: 'BAILING',
|
llm_preset_id: 'BAILING',
|
||||||
consistency_mode: 'soft',
|
consistency_mode: 'soft',
|
||||||
phase_config: { diverge_rounds: 3 },
|
phase_config: { diverge_rounds: 3 },
|
||||||
});
|
});
|
||||||
|
|
||||||
setShowAnalysisModal(false);
|
setShowAnalysisModal(false);
|
||||||
navigate('/qa-center/mope-chat');
|
navigate('/qa-center/mope-chat');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMultiAgentDiscussion = () => {
|
const handleOpenRoundtable = () => {
|
||||||
if (!selectedTaskForAnalysis) return;
|
if (!selectedTask) return;
|
||||||
|
|
||||||
setStartConfig({
|
setStartConfig({
|
||||||
task_title: selectedTaskForAnalysis.taskName,
|
task_title: selectedTask.taskName,
|
||||||
task_goal: selectedTaskForAnalysis.overview,
|
task_goal: selectedTask.overview,
|
||||||
task_context: selectedTaskForAnalysis.overview,
|
task_context: selectedTask.overview,
|
||||||
llm_preset_id: 'BAILING',
|
llm_preset_id: 'BAILING',
|
||||||
consistency_mode: 'soft',
|
consistency_mode: 'soft',
|
||||||
phase_config: { diverge_rounds: 3 },
|
phase_config: { diverge_rounds: 3 },
|
||||||
});
|
});
|
||||||
|
|
||||||
setShowAnalysisModal(false);
|
setShowAnalysisModal(false);
|
||||||
navigate('/page/workspace/roundtable/planning');
|
navigate('/page/workspace/roundtable/planning');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const shellClass = isDark
|
||||||
|
? 'strategy-data h-full overflow-y-auto bg-[linear-gradient(180deg,#081322_0%,#0d1d33_42%,#0a1628_100%)]'
|
||||||
|
: 'strategy-data h-full overflow-y-auto bg-[linear-gradient(180deg,#FFFDFD_0%,#FFF7F7_46%,#FAFAFA_100%)]';
|
||||||
|
|
||||||
|
const toolbarClass = isDark
|
||||||
|
? 'border border-[rgba(67,89,124,0.46)] bg-[rgba(10,23,42,0.94)] text-white shadow-[0_16px_36px_rgba(3,10,22,0.2)]'
|
||||||
|
: 'border border-[#E8E8E8] bg-white text-[#222222] shadow-[0_14px_30px_rgba(245,34,45,0.06)]';
|
||||||
|
|
||||||
|
const metricClass = isDark
|
||||||
|
? 'border border-[rgba(67,89,124,0.46)] bg-[rgba(12,29,54,0.94)]'
|
||||||
|
: 'border border-[#E8E8E8] bg-white';
|
||||||
|
|
||||||
|
const inputClass = isDark
|
||||||
|
? 'border-[rgba(67,89,124,0.46)] bg-[rgba(8,19,35,0.9)] text-white placeholder:text-slate-500'
|
||||||
|
: 'border-[#E8E8E8] bg-white text-[#222222] placeholder:text-[#A6A6A6]';
|
||||||
|
|
||||||
|
const totalCount = unanalyzedTotal + runningTotal + completedTotal;
|
||||||
|
const metrics = [
|
||||||
|
{ label: '全部任务', value: totalCount },
|
||||||
|
{ label: '未分析', value: unanalyzedTotal },
|
||||||
|
{ label: '分析中', value: runningTotal },
|
||||||
|
{ label: '已完成', value: completedTotal },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={shellClass}>
|
<div className={shellClass}>
|
||||||
<div className={`relative mx-auto flex h-full max-w-[1680px] flex-col gap-5 px-6 py-6 lg:px-8 ${panelGlowClass}`}>
|
<div className="mx-auto flex min-h-full max-w-[1680px] flex-col gap-4 px-6 py-6 lg:px-8">
|
||||||
<div className="flex flex-col gap-4 xl:flex-row xl:items-end xl:justify-between">
|
<section className={`rounded-[28px] p-5 ${toolbarClass}`}>
|
||||||
<div className="space-y-3">
|
<div className="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="space-y-3">
|
||||||
<div className="flex h-11 w-11 items-center justify-center rounded-2xl border border-[rgba(96,165,250,0.28)] bg-[linear-gradient(180deg,rgba(22,119,255,0.28),rgba(14,63,135,0.18))] shadow-[0_10px_30px_rgba(14,63,135,0.24)]">
|
<div className="flex items-center gap-2 text-xs">
|
||||||
<Bot className="h-5 w-5 text-[#7CC9FF]" />
|
<span
|
||||||
|
className={`rounded-full px-3 py-1 ${
|
||||||
|
isDark ? 'bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : 'bg-[#FFF1F0] text-[#CF1322]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{category}
|
||||||
|
</span>
|
||||||
|
<ArrowRight className={`h-3.5 w-3.5 ${isDark ? 'text-slate-500' : 'text-[#A6A6A6]'}`} />
|
||||||
|
<span
|
||||||
|
className={`rounded-full px-3 py-1 ${
|
||||||
|
isDark ? 'bg-[rgba(34,197,94,0.14)] text-[#89E8B8]' : 'bg-[#FFF6E8] text-[#D46B08]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{subCategory}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<h1 className="text-[26px] font-semibold tracking-[0.02em] text-slate-50">{title}</h1>
|
<h1 className={`text-[28px] font-semibold ${isDark ? 'text-white' : 'text-[#222222]'}`}>
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
{useDemoData ? (
|
{useDemoData ? (
|
||||||
<span className="rounded-full border border-[rgba(125,211,252,0.28)] bg-[rgba(56,189,248,0.14)] px-2.5 py-1 text-xs text-[#8EDCFF]">
|
<span
|
||||||
|
className={`rounded-full px-3 py-1 text-xs ${
|
||||||
|
isDark ? 'bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : 'bg-[#FFF1F0] text-[#CF1322]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
演示数据
|
演示数据
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<p className={`mt-1 text-sm ${isDark ? 'text-slate-300/80' : 'text-slate-500'}`}>
|
<p className={`mt-2 text-sm ${isDark ? 'text-slate-300' : 'text-[#575757]'}`}>
|
||||||
面向规划任务的情绪与倾向分析看板,支持快速检索、会商启动与结果回看。
|
面向规划任务的情绪与倾向分析看板,支持快速检索、会商启动与结果回看。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs ${isDark ? 'border-[rgba(110,136,176,0.28)] bg-[rgba(9,25,46,0.72)] text-slate-300' : 'border-slate-200 bg-white text-slate-600'}`}>
|
|
||||||
<span>{category}</span>
|
|
||||||
<ArrowRight className="h-3.5 w-3.5 opacity-70" />
|
|
||||||
<span>{subCategory}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-3 xl:items-end">
|
<div className="flex flex-col gap-3 xl:items-end">
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<label className={`flex h-11 min-w-[320px] items-center gap-3 rounded-2xl border px-4 ${inputClass}`}>
|
<label
|
||||||
<Search className="h-4 w-4 text-[#7CC9FF]" />
|
className={`flex h-11 min-w-[320px] items-center gap-3 rounded-xl border px-4 ${inputClass}`}
|
||||||
<input
|
>
|
||||||
value={searchQuery}
|
<Search className={`h-4 w-4 ${isDark ? 'text-slate-400' : 'text-[#A6A6A6]'}`} />
|
||||||
onChange={(event) => setSearchQuery(event.target.value)}
|
<input
|
||||||
className="w-full bg-transparent text-sm outline-none"
|
value={searchQuery}
|
||||||
placeholder="搜索任务名称、摘要或负责人"
|
onChange={(event) => setSearchQuery(event.target.value)}
|
||||||
|
className="w-full bg-transparent text-sm outline-none"
|
||||||
|
placeholder="搜索任务名称"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<DateRangePicker
|
||||||
|
className="sentiment-date-range"
|
||||||
|
value={range}
|
||||||
|
presets={presets}
|
||||||
|
enableTimePicker
|
||||||
|
clearable
|
||||||
|
popupProps={{ overlayClassName: 'sentiment-date-range-popup' }}
|
||||||
|
onChange={(value) => setRange(value)}
|
||||||
/>
|
/>
|
||||||
</label>
|
</div>
|
||||||
<DateRangePicker
|
|
||||||
className="sentiment-date-range"
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
value={dateRange}
|
<button
|
||||||
enableTimePicker={false}
|
type="button"
|
||||||
clearable={false}
|
onClick={() => setUseDemoData(true)}
|
||||||
popupProps={{ overlayClassName: 'sentiment-date-range-popup' }}
|
className={`rounded-xl px-4 py-2 text-sm transition ${
|
||||||
rangeInputProps={{
|
useDemoData
|
||||||
inputProps: [
|
? isDark
|
||||||
{ inputClass: 'sentiment-date-range-input' },
|
? 'bg-[#1677FF] text-white'
|
||||||
{ inputClass: 'sentiment-date-range-input' },
|
: 'bg-[#F5222D] text-white'
|
||||||
],
|
: isDark
|
||||||
}}
|
? 'bg-[rgba(8,19,35,0.9)] text-slate-300'
|
||||||
onChange={(value) => setDateRange(value)}
|
: 'bg-white text-[#575757] border border-[#E8E8E8]'
|
||||||
/>
|
}`}
|
||||||
</div>
|
>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
演示数据
|
||||||
<button
|
</button>
|
||||||
type="button"
|
<button
|
||||||
onClick={() => setUseDemoData(true)}
|
type="button"
|
||||||
className={`rounded-xl px-4 py-2 text-sm transition ${useDemoData ? 'bg-[#1677FF] text-white shadow-[0_8px_22px_rgba(22,119,255,0.28)]' : isDark ? 'bg-[rgba(10,30,54,0.9)] text-slate-300 hover:bg-[rgba(13,42,78,0.95)]' : 'bg-white text-slate-700 hover:bg-slate-50'}`}
|
onClick={() => setUseDemoData(false)}
|
||||||
>
|
className={`rounded-xl px-4 py-2 text-sm transition ${
|
||||||
演示数据
|
!useDemoData
|
||||||
</button>
|
? isDark
|
||||||
<button
|
? 'bg-[#0EA5E9] text-white'
|
||||||
type="button"
|
: 'bg-[#CF1322] text-white'
|
||||||
onClick={() => setUseDemoData(false)}
|
: isDark
|
||||||
className={`rounded-xl px-4 py-2 text-sm transition ${!useDemoData ? 'bg-[#0EA5E9] text-white shadow-[0_8px_22px_rgba(14,165,233,0.24)]' : isDark ? 'bg-[rgba(10,30,54,0.9)] text-slate-300 hover:bg-[rgba(13,42,78,0.95)]' : 'bg-white text-slate-700 hover:bg-slate-50'}`}
|
? 'bg-[rgba(8,19,35,0.9)] text-slate-300'
|
||||||
>
|
: 'bg-white text-[#575757] border border-[#E8E8E8]'
|
||||||
真实数据
|
}`}
|
||||||
</button>
|
>
|
||||||
|
真实数据
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`inline-flex items-center gap-2 rounded-xl px-4 py-2 text-sm ${
|
||||||
|
isDark ? 'bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : 'bg-[#FFF1F0] text-[#CF1322]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
新建任务
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
|
<section className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||||
{metrics.map(({ label, value, icon: Icon, accent }) => (
|
{metrics.map((item, index) => (
|
||||||
<div key={label} className={`rounded-3xl p-5 ${metricCardClass}`}>
|
<div key={item.label} className={`rounded-[24px] p-5 ${metricClass}`}>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className={`text-sm ${isDark ? 'text-slate-300/78' : 'text-slate-500'}`}>{label}</p>
|
<p className={`text-sm ${isDark ? 'text-slate-400' : 'text-[#8C8C8C]'}`}>{item.label}</p>
|
||||||
<p className="mt-3 text-3xl font-semibold tracking-[0.03em] text-white">{value}</p>
|
<p
|
||||||
|
className={`mt-3 text-[36px] font-semibold ${
|
||||||
|
isDark
|
||||||
|
? index === 0
|
||||||
|
? 'text-white'
|
||||||
|
: 'text-slate-100'
|
||||||
|
: index === 0
|
||||||
|
? 'text-[#222222]'
|
||||||
|
: 'text-[#F5222D]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{item.value}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={`flex h-12 w-12 items-center justify-center rounded-2xl bg-gradient-to-br ${accent} shadow-[inset_0_1px_0_rgba(255,255,255,0.18)]`}>
|
<div
|
||||||
<Icon className="h-5 w-5 text-white" />
|
className={`flex h-12 w-12 items-center justify-center rounded-2xl ${
|
||||||
|
isDark
|
||||||
|
? 'bg-[rgba(77,141,255,0.18)] text-[#9CCBFF]'
|
||||||
|
: 'bg-[#FFF1F0] text-[#CF1322]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Layers className="h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</section>
|
||||||
|
|
||||||
<div className="grid min-h-0 flex-1 grid-cols-1 gap-4 xl:grid-cols-3">
|
<section className="grid grid-cols-1 items-stretch gap-4 xl:grid-cols-3">
|
||||||
<div className="min-h-[420px]">
|
<div className="h-full">
|
||||||
<Loading loading={unanalyzedLoading} showOverlay={false}>
|
<Loading className="block h-full" loading={unanalyzedLoading} showOverlay={false}>
|
||||||
<KanbanColumn
|
<div className="h-full">
|
||||||
title="未分析"
|
<KanbanColumn
|
||||||
count={unanalyzedTotal}
|
title="未分析"
|
||||||
color="bg-[#38BDF8]"
|
count={unanalyzedTotal}
|
||||||
tasks={unanalyzedList}
|
color={isDark ? 'bg-[#7CC9FF]' : 'bg-[#F5222D]'}
|
||||||
taskType="unanalyzed"
|
tasks={unanalyzedList}
|
||||||
TaskDataType="sentiment"
|
taskType="unanalyzed"
|
||||||
pageIndexSearch={(taskType, pageIndex) => taskListSearch(taskType as ColumnType, pageIndex)}
|
taskDataType="sentiment"
|
||||||
handleStartAnalysis={handleStartAnalysis}
|
pageIndexSearch={taskListSearch}
|
||||||
/>
|
handleStartAnalysis={handleStartAnalysis}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Loading>
|
</Loading>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-h-[420px]">
|
|
||||||
<Loading loading={runningLoading} showOverlay={false}>
|
<div className="h-full">
|
||||||
<KanbanColumn
|
<Loading className="block h-full" loading={runningLoading} showOverlay={false}>
|
||||||
title="分析中"
|
<div className="h-full">
|
||||||
count={runningTotal}
|
<KanbanColumn
|
||||||
color="bg-[#2DD4BF]"
|
title="分析中"
|
||||||
tasks={runningList}
|
count={runningTotal}
|
||||||
taskType="analyzing"
|
color={isDark ? 'bg-[#5EEAD4]' : 'bg-[#1677FF]'}
|
||||||
TaskDataType="sentiment"
|
tasks={runningList}
|
||||||
pageIndexSearch={(taskType, pageIndex) => taskListSearch(taskType as ColumnType, pageIndex)}
|
taskType="running"
|
||||||
handleStartAnalysis={handleStartAnalysis}
|
taskDataType="sentiment"
|
||||||
/>
|
pageIndexSearch={taskListSearch}
|
||||||
|
handleStartAnalysis={handleStartAnalysis}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Loading>
|
</Loading>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-h-[420px]">
|
|
||||||
<Loading loading={completedLoading} showOverlay={false}>
|
<div className="h-full">
|
||||||
<KanbanColumn
|
<Loading className="block h-full" loading={completedLoading} showOverlay={false}>
|
||||||
title="已完成"
|
<div className="h-full">
|
||||||
count={completedTotal}
|
<KanbanColumn
|
||||||
color="bg-[#60A5FA]"
|
title="已完成"
|
||||||
tasks={completedList}
|
count={completedTotal}
|
||||||
taskType="completed"
|
color={isDark ? 'bg-[#67E8A5]' : 'bg-[#52C41A]'}
|
||||||
TaskDataType="sentiment"
|
tasks={completedList}
|
||||||
pageIndexSearch={(taskType, pageIndex) => taskListSearch(taskType as ColumnType, pageIndex)}
|
taskType="completed"
|
||||||
handleStartAnalysis={handleStartAnalysis}
|
taskDataType="sentiment"
|
||||||
/>
|
pageIndexSearch={taskListSearch}
|
||||||
|
handleStartAnalysis={handleStartAnalysis}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Loading>
|
</Loading>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showAnalysisModal && selectedTaskForAnalysis ? (
|
{showAnalysisModal && selectedTask ? (
|
||||||
<div className="fixed inset-0 z-[2000] flex items-center justify-center bg-[rgba(3,10,22,0.62)] px-4 backdrop-blur-sm">
|
<div className="fixed inset-0 z-[2000] flex items-center justify-center bg-black/45 px-4">
|
||||||
<div className={`w-full max-w-[560px] rounded-[28px] border p-6 ${isDark ? 'border-[rgba(110,136,176,0.26)] bg-[linear-gradient(180deg,rgba(10,29,54,0.98),rgba(8,20,37,0.98))] text-slate-100 shadow-[0_28px_80px_rgba(2,8,20,0.42)]' : 'border-slate-200 bg-white text-slate-900 shadow-xl'}`}>
|
<div
|
||||||
|
className={`w-full max-w-[560px] rounded-[28px] border p-6 ${
|
||||||
|
isDark
|
||||||
|
? 'border-[rgba(67,89,124,0.46)] bg-[rgba(10,23,42,0.98)] text-white'
|
||||||
|
: 'border-[#E8E8E8] bg-white text-[#222222] shadow-[0_18px_36px_rgba(0,0,0,0.12)]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-semibold">选择分析模式</h3>
|
<h3 className="text-xl font-semibold">选择分析模式</h3>
|
||||||
<p className={`mt-2 text-sm ${isDark ? 'text-slate-300/78' : 'text-slate-500'}`}>
|
<p className={`mt-2 text-sm ${isDark ? 'text-slate-300' : 'text-[#575757]'}`}>
|
||||||
当前任务:{selectedTaskForAnalysis.taskName}
|
为任务“{selectedTask.taskName}”选择分析方式
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowAnalysisModal(false)}
|
onClick={() => setShowAnalysisModal(false)}
|
||||||
className={`rounded-xl p-2 transition ${isDark ? 'hover:bg-[rgba(15,41,74,0.88)]' : 'hover:bg-slate-100'}`}
|
className={`rounded-xl p-2 ${isDark ? 'hover:bg-[rgba(21,42,72,0.9)]' : 'hover:bg-[#FFF1F0]'}`}
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 grid gap-3">
|
<div className="mt-6 space-y-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleViewTaskDetail}
|
onClick={handleOpenTaskDetail}
|
||||||
className={`group flex items-center justify-between rounded-2xl border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(110,136,176,0.24)] bg-[rgba(10,30,54,0.9)] hover:border-[rgba(96,165,250,0.48)] hover:bg-[rgba(12,38,69,0.96)]' : 'border-slate-200 bg-slate-50 hover:border-sky-200 hover:bg-sky-50'}`}
|
className={`flex w-full items-center justify-between rounded-[22px] border px-5 py-4 text-left transition ${
|
||||||
|
isDark
|
||||||
|
? 'border-[rgba(67,89,124,0.46)] bg-[rgba(15,33,60,0.92)] hover:border-[rgba(110,168,255,0.46)]'
|
||||||
|
: 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-4">
|
<div>
|
||||||
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-[linear-gradient(180deg,#1677FF,#0EA5E9)]">
|
<div className="font-medium">查看任务详情</div>
|
||||||
<Layers className="h-5 w-5 text-white" />
|
<div className={`mt-1 text-sm ${isDark ? 'text-slate-300' : 'text-[#575757]'}`}>
|
||||||
</div>
|
进入单任务分析视图,查看步骤、结论和上下文信息。
|
||||||
<div>
|
|
||||||
<div className="font-medium">查看任务详情</div>
|
|
||||||
<div className={`mt-1 text-sm ${isDark ? 'text-slate-300/72' : 'text-slate-500'}`}>进入单任务分析视图,查看步骤、结论和上下文信息。</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ArrowRight className="h-4 w-4 opacity-70 transition group-hover:translate-x-0.5" />
|
<ArrowRight className="h-4 w-4 opacity-70" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleMultiAgentDiscussion}
|
onClick={handleOpenRoundtable}
|
||||||
className={`group flex items-center justify-between rounded-2xl border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(110,136,176,0.24)] bg-[rgba(10,30,54,0.9)] hover:border-[rgba(45,212,191,0.48)] hover:bg-[rgba(10,42,58,0.96)]' : 'border-slate-200 bg-slate-50 hover:border-emerald-200 hover:bg-emerald-50'}`}
|
className={`flex w-full items-center justify-between rounded-[22px] border px-5 py-4 text-left transition ${
|
||||||
|
isDark
|
||||||
|
? 'border-[rgba(67,89,124,0.46)] bg-[rgba(15,33,60,0.92)] hover:border-[rgba(103,232,165,0.42)]'
|
||||||
|
: 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-[linear-gradient(180deg,#14B8A6,#22C55E)]">
|
<Users className={`mt-0.5 h-5 w-5 ${isDark ? 'text-[#89E8B8]' : 'text-[#CF1322]'}`} />
|
||||||
<Users className="h-5 w-5 text-white" />
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">多智能体会商</div>
|
<div className="font-medium">多智能体会商</div>
|
||||||
<div className={`mt-1 text-sm ${isDark ? 'text-slate-300/72' : 'text-slate-500'}`}>启动会商模式,由多个智能体并行分析情绪走势与影响因素。</div>
|
<div className={`mt-1 text-sm ${isDark ? 'text-slate-300' : 'text-[#575757]'}`}>
|
||||||
|
启动会商模式,由多个智能体并行分析情绪走势与影响因素。
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ArrowRight className="h-4 w-4 opacity-70 transition group-hover:translate-x-0.5" />
|
<ArrowRight className="h-4 w-4 opacity-70" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+580
-574
File diff suppressed because it is too large.
Load diff
@@ -1,20 +1,54 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { Bot, Eye, Layers, Play, User, Users, Workflow } from 'lucide-react';
|
import { Bot, Eye, Layers, Play, User, Users, Workflow } from 'lucide-react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useTheme } from '@/contexts/ThemeContext';
|
import { useTheme } from '@/contexts/ThemeContext';
|
||||||
import { useChatContext } from '@/contexts/ChatContext';
|
import { useChatContext } from '@/contexts/ChatContext';
|
||||||
import { TaskListItem } from './TaskManagement';
|
import { TaskListItem } from './TaskManagement';
|
||||||
|
|
||||||
interface TaskCardProps {
|
interface TaskCardProps {
|
||||||
TaskType?: string;
|
|
||||||
task: TaskListItem;
|
task: TaskListItem;
|
||||||
|
taskDataType?: string;
|
||||||
handleStartAnalysis: (task: TaskListItem) => void;
|
handleStartAnalysis: (task: TaskListItem) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTaskTypeConfig = (taskType?: number) => {
|
||||||
|
switch (taskType) {
|
||||||
|
case 2:
|
||||||
|
return { label: '场景', icon: Workflow };
|
||||||
|
case 3:
|
||||||
|
return { label: '会商', icon: Users };
|
||||||
|
case 4:
|
||||||
|
return { label: '多智能体', icon: Layers };
|
||||||
|
case 5:
|
||||||
|
return { label: '检索', icon: Layers };
|
||||||
|
case 1:
|
||||||
|
default:
|
||||||
|
return { label: '单任务', icon: Bot };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTaskLevelLabel = (taskLevel?: number) => {
|
||||||
|
switch (taskLevel) {
|
||||||
|
case 2:
|
||||||
|
return '紧急';
|
||||||
|
case 3:
|
||||||
|
return '加急';
|
||||||
|
case 1:
|
||||||
|
default:
|
||||||
|
return '一般';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getColumnLabel = (column?: string) => {
|
||||||
|
if (column === 'running' || column === 'analyzing') return '分析中';
|
||||||
|
if (column === 'completed') return '已完成';
|
||||||
|
return '待分析';
|
||||||
|
};
|
||||||
|
|
||||||
export const TaskCard: React.FC<TaskCardProps> = ({
|
export const TaskCard: React.FC<TaskCardProps> = ({
|
||||||
task,
|
task,
|
||||||
TaskType = 'task',
|
taskDataType = 'task',
|
||||||
handleStartAnalysis,
|
handleStartAnalysis,
|
||||||
}) => {
|
}) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
@@ -22,209 +56,159 @@ export const TaskCard: React.FC<TaskCardProps> = ({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isDark = theme === 'dark';
|
const isDark = theme === 'dark';
|
||||||
|
|
||||||
const sceneDataChange = (currentTask: TaskListItem) => {
|
const typeConfig = getTaskTypeConfig(task.taskType);
|
||||||
if (!currentTask.scene) return;
|
const TypeIcon = typeConfig.icon;
|
||||||
|
const isCompleted = task.column === 'completed';
|
||||||
|
const isRunning = task.column === 'running' || task.column === 'analyzing';
|
||||||
|
|
||||||
|
const openConversation = () => {
|
||||||
|
try {
|
||||||
|
const sceneItem = task.scene ? JSON.parse(task.scene) : {};
|
||||||
|
const taskJson = sceneItem?.taskJson ? JSON.parse(sceneItem.taskJson) : {};
|
||||||
|
|
||||||
const sceneItem = JSON.parse(currentTask.scene);
|
|
||||||
if (sceneItem.taskJson) {
|
|
||||||
sceneItem.taskJson = JSON.parse(sceneItem.taskJson);
|
|
||||||
setStartConfig({
|
setStartConfig({
|
||||||
...sceneItem.taskJson,
|
...taskJson,
|
||||||
|
task_title: task.taskName,
|
||||||
|
task_goal: task.overview,
|
||||||
|
task_context: task.overview,
|
||||||
|
llm_preset_id: 'BAILING',
|
||||||
|
consistency_mode: 'soft',
|
||||||
|
phase_config: { diverge_rounds: 3 },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
setStartConfig({
|
||||||
|
task_title: task.taskName,
|
||||||
|
task_goal: task.overview,
|
||||||
|
task_context: task.overview,
|
||||||
llm_preset_id: 'BAILING',
|
llm_preset_id: 'BAILING',
|
||||||
consistency_mode: 'soft',
|
consistency_mode: 'soft',
|
||||||
phase_config: { diverge_rounds: 3 },
|
phase_config: { diverge_rounds: 3 },
|
||||||
task_context: currentTask.overview,
|
|
||||||
});
|
});
|
||||||
navigate('/qa-center/mope-chat');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navigate('/qa-center/mope-chat');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTaskClick = (currentTask: TaskListItem) => {
|
const handleCardClick = () => {
|
||||||
if (TaskType === 'sentiment') {
|
if (taskDataType === 'sentiment') {
|
||||||
sceneDataChange(currentTask);
|
openConversation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTask.column === 'completed') {
|
if (isCompleted) {
|
||||||
navigate('/strategy-result');
|
navigate('/strategy-result');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTask.column === 'running' || currentTask.column === 'analyzing') {
|
if (isRunning) {
|
||||||
sceneDataChange(currentTask);
|
openConversation();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStartChange = (currentTask: TaskListItem) => {
|
const handleActionClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
handleStartAnalysis(currentTask);
|
event.stopPropagation();
|
||||||
};
|
|
||||||
|
|
||||||
const getTypeConfig = (type: string) => {
|
if (isCompleted) {
|
||||||
const common = isDark ? 'border border-white/10 text-slate-200' : 'border border-slate-200 text-slate-700';
|
handleCardClick();
|
||||||
switch (type) {
|
return;
|
||||||
case 'single':
|
|
||||||
return {
|
|
||||||
label: '单任务',
|
|
||||||
icon: Layers,
|
|
||||||
className: `${common} ${isDark ? 'bg-[rgba(22,119,255,0.14)]' : 'bg-sky-50'}`,
|
|
||||||
};
|
|
||||||
case 'consultation':
|
|
||||||
return {
|
|
||||||
label: '会商',
|
|
||||||
icon: Users,
|
|
||||||
className: `${common} ${isDark ? 'bg-[rgba(20,184,166,0.14)]' : 'bg-emerald-50'}`,
|
|
||||||
};
|
|
||||||
case 'scene':
|
|
||||||
return {
|
|
||||||
label: '场景',
|
|
||||||
icon: Workflow,
|
|
||||||
className: `${common} ${isDark ? 'bg-[rgba(168,85,247,0.14)]' : 'bg-violet-50'}`,
|
|
||||||
};
|
|
||||||
case 'multi-agent':
|
|
||||||
return {
|
|
||||||
label: '多智能体',
|
|
||||||
icon: Bot,
|
|
||||||
className: `${common} ${isDark ? 'bg-[rgba(99,102,241,0.16)]' : 'bg-indigo-50'}`,
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
label: '检索',
|
|
||||||
icon: Layers,
|
|
||||||
className: `${common} ${isDark ? 'bg-[rgba(59,130,246,0.12)]' : 'bg-slate-100'}`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleStartAnalysis(task);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLevel = (priority: number = 1) => {
|
const panelClass = isDark
|
||||||
if (priority >= 3) return '加急';
|
? 'border border-[rgba(67,89,124,0.46)] bg-[rgba(19,39,72,0.9)] shadow-[0_14px_34px_rgba(3,10,22,0.2)] hover:border-[rgba(110,168,255,0.52)]'
|
||||||
if (priority === 2) return '紧急';
|
: 'border border-[#E8E8E8] bg-white shadow-[0_10px_24px_rgba(245,34,45,0.06)] hover:border-[#F6B0B4] hover:shadow-[0_16px_32px_rgba(245,34,45,0.12)]';
|
||||||
return '一般';
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusConfig = (column: string) => {
|
const typeBadgeClass = isDark
|
||||||
switch (column) {
|
? 'bg-[rgba(77,141,255,0.14)] text-[#A9D2FF] border border-[rgba(108,161,255,0.24)]'
|
||||||
case 'completed':
|
: 'bg-[#FFF1F0] text-[#CF1322] border border-[#FFD6D6]';
|
||||||
return {
|
|
||||||
label: '已完成',
|
|
||||||
className: isDark
|
|
||||||
? 'bg-[rgba(34,197,94,0.16)] text-emerald-300 border border-emerald-400/20'
|
|
||||||
: 'bg-emerald-50 text-emerald-600 border border-emerald-100',
|
|
||||||
};
|
|
||||||
case 'running':
|
|
||||||
case 'analyzing':
|
|
||||||
return {
|
|
||||||
label: '分析中',
|
|
||||||
className: isDark
|
|
||||||
? 'bg-[rgba(14,165,233,0.16)] text-sky-300 border border-sky-400/20'
|
|
||||||
: 'bg-sky-50 text-sky-600 border border-sky-100',
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
label: '待分析',
|
|
||||||
className: isDark
|
|
||||||
? 'bg-[rgba(96,165,250,0.14)] text-blue-200 border border-blue-300/15'
|
|
||||||
: 'bg-blue-50 text-blue-600 border border-blue-100',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const typeConfig = getTypeConfig(task.type || 'single');
|
const levelClass = isDark
|
||||||
const statusConfig = getStatusConfig(task.column || 'unanalyzed');
|
? 'text-slate-400'
|
||||||
const TypeIcon = typeConfig.icon;
|
: 'text-[#8C8C8C]';
|
||||||
const progressValue = Math.max(0, Math.min(task.progress || 0, 100));
|
|
||||||
|
const statusClass =
|
||||||
|
task.column === 'completed'
|
||||||
|
? isDark
|
||||||
|
? 'bg-[rgba(18,184,134,0.16)] text-[#59E2B1] border border-[rgba(89,226,177,0.2)]'
|
||||||
|
: 'bg-[#EEFCF7] text-[#0F9D6C] border border-[#B7F0D5]'
|
||||||
|
: task.column === 'running' || task.column === 'analyzing'
|
||||||
|
? isDark
|
||||||
|
? 'bg-[rgba(34,197,94,0.12)] text-[#67E8A5] border border-[rgba(103,232,165,0.2)]'
|
||||||
|
: 'bg-[#EAF4FF] text-[#1677FF] border border-[#CDE2FF]'
|
||||||
|
: isDark
|
||||||
|
? 'bg-[rgba(96,165,250,0.12)] text-[#8FD1FF] border border-[rgba(143,209,255,0.2)]'
|
||||||
|
: 'bg-[#FFF1F0] text-[#F5222D] border border-[#FFD6D6]';
|
||||||
|
|
||||||
|
const actionClass = isCompleted
|
||||||
|
? isDark
|
||||||
|
? 'bg-[rgba(12,48,82,0.95)] text-white hover:bg-[rgba(15,60,104,0.98)]'
|
||||||
|
: 'bg-[#FFF1F0] text-[#CF1322] hover:bg-[#FFE4E6]'
|
||||||
|
: isDark
|
||||||
|
? 'bg-[linear-gradient(90deg,#1677FF,#22A8F0)] text-white hover:brightness-110'
|
||||||
|
: 'bg-[linear-gradient(90deg,#F5222D,#FF6B73)] text-white hover:brightness-105';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<article
|
||||||
className={`theme-card theme-card-hover theme-transition rounded-[24px] border p-4 ${
|
className={`group rounded-[24px] p-4 transition-all duration-200 ${panelClass}`}
|
||||||
isDark
|
onClick={handleCardClick}
|
||||||
? 'border-[rgba(110,136,176,0.2)] shadow-[0_12px_30px_rgba(2,8,20,0.20)]'
|
|
||||||
: 'border-slate-200 bg-white shadow-sm'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className={`inline-flex items-center gap-2 rounded-full px-3 py-1 text-xs ${typeConfig.className}`}>
|
<span
|
||||||
|
className={`inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium ${typeBadgeClass}`}
|
||||||
|
>
|
||||||
<TypeIcon className="h-3.5 w-3.5" />
|
<TypeIcon className="h-3.5 w-3.5" />
|
||||||
<span>{typeConfig.label}</span>
|
{typeConfig.label}
|
||||||
</div>
|
|
||||||
<span className={`inline-flex items-center rounded-full px-2.5 py-1 text-xs ${statusConfig.className}`}>
|
|
||||||
{statusConfig.label}
|
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
className={`inline-flex items-center rounded-full px-3 py-1 text-xs font-medium ${statusClass}`}
|
||||||
|
>
|
||||||
|
{getColumnLabel(task.column)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3
|
||||||
|
className={`mt-5 text-[16px] font-semibold leading-7 ${
|
||||||
|
isDark ? 'text-white' : 'text-[#222222]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{task.taskName}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className={`mt-3 min-h-[56px] text-sm leading-7 ${
|
||||||
|
isDark ? 'text-slate-300' : 'text-[#575757]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{task.overview || '暂无任务摘要'}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-4 flex items-center justify-between gap-3 text-sm">
|
||||||
|
<div className={`flex items-center gap-2 ${isDark ? 'text-slate-300' : 'text-[#575757]'}`}>
|
||||||
|
<User className="h-4 w-4" />
|
||||||
|
<span>{task.taskDirection || '未分配'}</span>
|
||||||
|
</div>
|
||||||
|
<div className={levelClass}>优先级:{getTaskLevelLabel(task.taskLevel)}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`mt-4 grid grid-cols-2 gap-3 text-xs ${
|
||||||
|
isDark ? 'text-slate-400' : 'text-[#8C8C8C]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div>创建于 {format(new Date(task.createTime), 'MM-dd HH:mm')}</div>
|
||||||
|
<div className="text-right">更新于 {format(new Date(task.updateTime), 'MM-dd HH:mm')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleTaskClick(task)}
|
onClick={handleActionClick}
|
||||||
className="mt-4 block w-full text-left"
|
className={`mt-5 flex h-12 w-full items-center justify-center gap-2 rounded-full text-sm font-medium transition ${actionClass}`}
|
||||||
>
|
>
|
||||||
<div className={`line-clamp-2 text-[15px] font-semibold leading-6 ${isDark ? 'text-slate-50' : 'text-slate-900'}`}>
|
{isCompleted ? <Eye className="h-4 w-4" /> : <Play className="h-4 w-4" />}
|
||||||
{task.taskName}
|
{isCompleted ? '查看结果' : '开始分析'}
|
||||||
</div>
|
|
||||||
<p className={`mt-2 line-clamp-3 text-sm leading-6 ${isDark ? 'text-slate-300/78' : 'text-slate-500'}`}>
|
|
||||||
{task.overview || '暂无任务摘要'}
|
|
||||||
</p>
|
|
||||||
</button>
|
</button>
|
||||||
|
</article>
|
||||||
<div className="mt-4 space-y-3">
|
|
||||||
<div className="flex items-center justify-between text-xs">
|
|
||||||
<div className={`flex items-center gap-1.5 ${isDark ? 'text-slate-400' : 'text-slate-500'}`}>
|
|
||||||
<User className="h-3.5 w-3.5" />
|
|
||||||
<span>{task.owner || task.creator || '未分配'}</span>
|
|
||||||
</div>
|
|
||||||
<div className={`${isDark ? 'text-slate-500' : 'text-slate-400'}`}>优先级:{getLevel(task.priority)}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className="mb-1.5 flex items-center justify-between text-xs">
|
|
||||||
<span className={isDark ? 'text-slate-400' : 'text-slate-500'}>分析进度</span>
|
|
||||||
<span className={isDark ? 'text-slate-300' : 'text-slate-700'}>{progressValue}%</span>
|
|
||||||
</div>
|
|
||||||
<div className={`h-2 overflow-hidden rounded-full ${isDark ? 'bg-[rgba(255,255,255,0.08)]' : 'bg-slate-100'}`}>
|
|
||||||
<div
|
|
||||||
className="h-full rounded-full bg-[linear-gradient(90deg,#1677FF,#5EEAD4)]"
|
|
||||||
style={{ width: `${progressValue}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between text-xs">
|
|
||||||
<span className={isDark ? 'text-slate-500' : 'text-slate-400'}>
|
|
||||||
创建于 {task.createTime ? format(new Date(task.createTime), 'MM-dd HH:mm') : '--'}
|
|
||||||
</span>
|
|
||||||
<span className={isDark ? 'text-slate-500' : 'text-slate-400'}>
|
|
||||||
更新于 {task.updateTime ? format(new Date(task.updateTime), 'MM-dd HH:mm') : '--'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-4 flex gap-2">
|
|
||||||
{task.column === 'completed' ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleTaskClick(task)}
|
|
||||||
className={`flex-1 rounded-xl px-3 py-2 text-sm transition ${
|
|
||||||
isDark
|
|
||||||
? 'bg-[rgba(11,40,71,0.95)] text-slate-100 hover:bg-[rgba(15,49,87,0.98)]'
|
|
||||||
: 'bg-slate-900 text-white hover:bg-slate-800'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span className="inline-flex items-center gap-2">
|
|
||||||
<Eye className="h-4 w-4" />
|
|
||||||
查看结果
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleStartChange(task)}
|
|
||||||
className="flex-1 rounded-xl bg-[linear-gradient(90deg,#1677FF,#0EA5E9)] px-3 py-2 text-sm text-white shadow-[0_10px_24px_rgba(22,119,255,0.28)] transition hover:brightness-110"
|
|
||||||
>
|
|
||||||
<span className="inline-flex items-center gap-2">
|
|
||||||
<Play className="h-4 w-4" />
|
|
||||||
开始分析
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
+46
-36
@@ -77,28 +77,16 @@
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.strategy-data::-webkit-scrollbar,
|
|
||||||
.strategy-data *::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.strategy-data::-webkit-scrollbar-thumb,
|
|
||||||
.strategy-data *::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba(203, 213, 225, 0.75);
|
|
||||||
border-radius: 999px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.strategy-data::-webkit-scrollbar-track,
|
|
||||||
.strategy-data *::-webkit-scrollbar-track {
|
|
||||||
background: rgba(241, 245, 249, 0.9);
|
|
||||||
border-radius: 999px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .strategy-data .scrollbar-thin {
|
.dark .strategy-data .scrollbar-thin {
|
||||||
scrollbar-color: rgba(148, 163, 184, 0.45) transparent;
|
scrollbar-color: rgba(148, 163, 184, 0.45) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .strategy-data::-webkit-scrollbar,
|
||||||
|
.dark .strategy-data *::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.dark .strategy-data,
|
.dark .strategy-data,
|
||||||
.dark .strategy-data * {
|
.dark .strategy-data * {
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
@@ -262,27 +250,39 @@
|
|||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sentiment-date-range-popup .t-date-picker__panel,
|
html.dark .sentiment-date-range-popup .t-date-picker__panel,
|
||||||
.sentiment-date-range-popup .t-date-range-picker__panel,
|
html.dark .sentiment-date-range-popup .t-date-range-picker__panel,
|
||||||
.sentiment-date-range-popup .t-date-picker__panel-body,
|
html.dark .sentiment-date-range-popup .t-date-picker__panel-body,
|
||||||
.sentiment-date-range-popup .t-date-picker__panel-content,
|
html.dark .sentiment-date-range-popup .t-date-picker__panel-content,
|
||||||
.sentiment-date-range-popup .t-date-picker__table,
|
html.dark .sentiment-date-range-popup .t-date-picker__table,
|
||||||
.sentiment-date-range-popup .t-time-picker__panel {
|
html.dark .sentiment-date-range-popup .t-time-picker__panel,
|
||||||
|
html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__panel,
|
||||||
|
html[theme-mode='dark'] .sentiment-date-range-popup .t-date-range-picker__panel,
|
||||||
|
html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__panel-body,
|
||||||
|
html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__panel-content,
|
||||||
|
html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__table,
|
||||||
|
html[theme-mode='dark'] .sentiment-date-range-popup .t-time-picker__panel {
|
||||||
background: #0d1d33 !important;
|
background: #0d1d33 !important;
|
||||||
color: #e6f4ff !important;
|
color: #e6f4ff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sentiment-date-range-popup .t-popup__content {
|
html.dark .sentiment-date-range-popup .t-popup__content,
|
||||||
|
html[theme-mode='dark'] .sentiment-date-range-popup .t-popup__content {
|
||||||
background: #0d1d33 !important;
|
background: #0d1d33 !important;
|
||||||
border: 1px solid rgba(110, 136, 176, 0.24);
|
border: 1px solid rgba(110, 136, 176, 0.24);
|
||||||
box-shadow: 0 24px 60px rgba(2, 8, 20, 0.42);
|
box-shadow: 0 24px 60px rgba(2, 8, 20, 0.42);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sentiment-date-range-popup .t-date-picker__cell,
|
html.dark .sentiment-date-range-popup .t-date-picker__cell,
|
||||||
.sentiment-date-range-popup .t-date-picker__cell-inner,
|
html.dark .sentiment-date-range-popup .t-date-picker__cell-inner,
|
||||||
.sentiment-date-range-popup .t-date-picker__header,
|
html.dark .sentiment-date-range-popup .t-date-picker__header,
|
||||||
.sentiment-date-range-popup .t-date-picker__text,
|
html.dark .sentiment-date-range-popup .t-date-picker__text,
|
||||||
.sentiment-date-range-popup .t-button {
|
html.dark .sentiment-date-range-popup .t-button,
|
||||||
|
html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__cell,
|
||||||
|
html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__cell-inner,
|
||||||
|
html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__header,
|
||||||
|
html[theme-mode='dark'] .sentiment-date-range-popup .t-date-picker__text,
|
||||||
|
html[theme-mode='dark'] .sentiment-date-range-popup .t-button {
|
||||||
color: #e6f4ff !important;
|
color: #e6f4ff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +296,7 @@
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.strategy-data .dark-theme:hover {
|
.dark .strategy-data .dark-theme:hover {
|
||||||
background: #0062d966;
|
background: #0062d966;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
border-color: #0062d9 !important;
|
border-color: #0062d9 !important;
|
||||||
@@ -307,8 +307,8 @@
|
|||||||
border-color: var(--card-border);
|
border-color: var(--card-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.strategy-data .dark-theme.task-active,
|
.dark .strategy-data .dark-theme.task-active,
|
||||||
.strategy-data .dark-theme .task-active {
|
.dark .strategy-data .dark-theme .task-active {
|
||||||
border-color: #0062d9 !important;
|
border-color: #0062d9 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,20 +351,20 @@
|
|||||||
background: linear-gradient(180deg, #f5f5f5, #fff 17.89% 100%);
|
background: linear-gradient(180deg, #f5f5f5, #fff 17.89% 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.strategy-data .dark-action-bg {
|
.dark .strategy-data .dark-action-bg {
|
||||||
background: rgba(3, 38, 80, 0.6) !important;
|
background: rgba(3, 38, 80, 0.6) !important;
|
||||||
border: 1px solid rgba(43, 143, 254, 0.6) !important;
|
border: 1px solid rgba(43, 143, 254, 0.6) !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
box-shadow: 0 1px 16px rgba(0, 0, 0, 0.08) !important;
|
box-shadow: 0 1px 16px rgba(0, 0, 0, 0.08) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.strategy-data .dark-action-item {
|
.dark .strategy-data .dark-action-item {
|
||||||
background: #0062d91a;
|
background: #0062d91a;
|
||||||
border: 1px solid #2b8ffe99;
|
border: 1px solid #2b8ffe99;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.strategy-data .dark-action-item:hover {
|
.dark .strategy-data .dark-action-item:hover {
|
||||||
background: #0062d966;
|
background: #0062d966;
|
||||||
border-color: #0062d9;
|
border-color: #0062d9;
|
||||||
}
|
}
|
||||||
@@ -373,6 +373,16 @@
|
|||||||
.strategy-data .t-loading,
|
.strategy-data .t-loading,
|
||||||
.strategy-data .t-loading__parent {
|
.strategy-data .t-loading__parent {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.strategy-data .t-loading {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.strategy-data .t-loading__parent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.strategy-data .t-loading--full {
|
.strategy-data .t-loading--full {
|
||||||
|
|||||||
Reference in new issue
Block a user