Merge branch 'dhk-1' into 'master'

Dhk 1

See merge request knowledge-base-bj/deerflow-server!48
This commit is contained in:
董宏凯 committed 2026-05-23 15:05:37 +00:00
commit f049936ec4
151 files changed
+3041 -38957

No files matched your search

+492
View File
@@ -0,0 +1,492 @@
# 多智能体圆桌 · Step 1 · 任务理解智能体接口与实现
> Step 1(`/page/roundtable/planning` 的"任务意图与澄清"页)对应 `RoundtablePlanningPage.tsx` 里 `currentStep === 1` 的渲染分支。当前它是纯前端 mock —— 用户发消息后 `setTimeout(1500)` 拼一段固定话术回。本文档定义一个**内置任务理解智能体 + 2 个轻量接口**,让 step 1 跑起来真正的对话,为 step 2 输入更高质量的"任务目标 + 约束"。
>
> 整体风格直接参照 `multi-agent-api.md` / `multi-agent-backend-dev.md`。
---
## 1. 目标与边界
### ✅ 要做的事
- 内置一个智能体:**任务理解智能体**(`roundtable-intent`),跟 6 个 roundtable 席位同等地位
- 提供 2 个接口:`/api/intent/init` 和 `/api/intent/stream`
- 复用 step 2 的 SSE 协议风格(前端只需 1 个新 helper)
- 让用户跟智能体多轮澄清后,把最终"目标 + 约束"传给 step 2 的 `runOrchestration`
### ❌ 不做的事
- 不重新设计澄清协议(直接用 `ask_clarification` 工具 + ClarificationMiddleware 现成机制,跟 step 2 leader 一样)
- 不做"自动跳过"按钮的真实业务逻辑(保留前端按钮形态即可)
- 不做时间线(timelineEntries)的后端持久化(前端临时状态够用)
- 不做"评分/置信度"等假指标
- 不做单独的"任务理解专员模型选择"——直接用全局 `DEFAULT_MODEL`
---
## 2. 数据流概览
```
[Step 1 入口]
↓
前端:POST /api/intent/init
← {agent_id: "roundtable-intent", thread_id: "..."}
用户输入第一句话:"帮我规划一个生产系统安全改造方案"
↓
前端:POST /api/intent/stream (SSE)
← (流式 AI 文本) "好的,预算大致是多少?"
← {status: "asking", content: "..."} ← 智能体反问中,需要用户继续
用户回:"500 万左右"
↓
前端再次 POST /api/intent/stream
← (流式) "明白,还有什么时间约束吗?"
← {status: "asking", ...}
...对话进行几轮 ...
用户:可以了
↓
前端再 POST /api/intent/stream
← (流式) "好,我理解的任务是: <汇总>"
← {status: "done", content: "<最终汇总文本>", summary: {...}} ← 信息充分,可进入 step 2
[Step 1 完成 → Step 2 init]
前端把 summary.objective + summary.constraints 拼成 step 2 的 buildInitialIntent()
```
---
## 3. 接口设计
跟 step 2 完全同构。`/api/intent/init` 简化版(只建 1 个 thread + 1 个 agent,没有"协调"概念);`/api/intent/stream` 跟 `/api/multi-agent/run/stream` 的 special 分支几乎一样。
### 3.1 `POST /api/intent/init`
**请求体**
```json
{
"model": "deepseek-chat" // 可选,默认全局 DEFAULT_MODEL
}
```
**响应 200**
```json
{
"status": "success",
"agent_id": "roundtable-intent",
"thread_id": "<langgraph_thread_id>"
}
```
**行为**:
1. 鉴权(透传 `Authorization`)
2. 调上游 `POST /api/threads` 建 1 个 thread
3. 直接返回 `agent_id = "roundtable-intent"`(**这是内置 agent,不需要每次新建**)
**与 step 2 `/init` 的区别**:
- 没有"协调智能体"概念,只有 1 个 agent
- 没有 6 个 sub 席位,只有 1 个 thread
- 不需要传 `agent_names` 数组
- `agent_id` 是固定字符串 `"roundtable-intent"`,不带 timestamp 后缀(智能体本身是内置、长期、复用的)
### 3.2 `POST /api/intent/stream`
**请求体**
```json
{
"thread_id": "<from /init>",
"message": "用户这一轮要说的话",
"model": "deepseek-chat" // 可选
}
```
**响应**:`text/event-stream`,SSE 流式。
**SSE 帧组成**:
1. 透传上游 LangGraph 的所有帧(`event: metadata` / `event: messages` / `event: values`)—— 前端用现有的 `extractAiTextDelta` 解析 AI 文本增量
2. 流末追加一个**状态帧**(跟 step 2 同样模式):
```json
{
"status": "asking" | "done" | "error",
"content": "<本轮 AI 文本>",
"summary": {
"objective": "...", // 仅 status=done 时有: 任务目标
"constraints": ["...", ...], // 仅 status=done 时有: 约束清单
"assumptions": ["...", ...] // 仅 status=done 时有: 关键假设
}
}
```
**`status` 取值**:
- `"asking"` —— 智能体还在追问,前端应该把焦点放回输入框
- `"done"` —— 智能体宣布"信息已充分",前端就可以亮起"进入下一步"按钮,并拿 `summary` 拼 step 2 的初始 intent
- `"error"` —— 上游异常,`content` 里是错误信息
**判定 done/asking 的简单策略**(后端实现):
- 智能体在 SOUL 里被告知:信息已充分时**必须**调 `present_intent_summary` 工具(或直接在 AIMessage 里用一个固定标记 `[INTENT_READY]`),后端识别后发 `done` 帧
- 否则一律是 `asking`
> 上面"summary 工具"是新增的内置工具吗?不一定。最省事的做法是**让模型用 JSON 块输出**,后端用正则提取:
>
> ```
> [INTENT_READY]
> ```json
> {"objective": "...", "constraints": ["..."], "assumptions": ["..."]}
> ```
> ```
>
> 后端流末扫一下最末 AIMessage 里有没有这个标记,有就 parse JSON 出来填到 summary,发 `done`;没有就发 `asking`。
---
## 4. 内置智能体配置
**位置**:`offline-backend-20260512/backend/.deer-flow/agents/roundtable-intent/`
### 4.1 `config.yaml`
```yaml
id: roundtable-intent
name: 任务理解智能体
description: 帮助用户明确任务目标、约束、关键假设,为后续多智能体圆桌研讨准备结构化输入。
```
### 4.2 `SOUL.md`
```markdown
# 任务理解智能体
## 角色定位
你是多智能体圆桌研讨的"任务理解专员"。你的工作是在用户和 6 个圆桌席位之间架一座桥:
通过 2-5 轮简短追问,把用户模糊的需求转化成结构化的「任务目标 + 约束 + 关键假设」。
## 核心职责
- 用清晰、简短的问题引导用户补全关键信息(优先级: 目标 > 硬约束 > 软偏好)
- 不要追问已经清楚的事
- 不要做方案,不要给具体技术建议——那是后面 6 个席位的工作
- 信息足够后,立即停止追问,产出结构化汇总
## 信息充分性判定
当满足以下条件时,认为信息已充分:
1. 明确的任务目标(至少能写出一句 "用户希望实现 X")
2. 至少 1 条硬约束(预算/时间/合规/范围之一)
3. 没有明显的歧义点
## 信息汇总产出
信息充分时,你必须在回复末尾输出**精确的标记 + JSON 块**(给后端识别用):
```
[INTENT_READY]
```json
{
"objective": "用一句话写出用户最终要做的事",
"constraints": ["预算 ≤ XXX 万", "时间 ≤ XXX 天", ...],
"assumptions": ["假设 1", "假设 2", ...]
}
```
```
输出前可以先用自然语言总结一遍给用户看(让用户能 review 内容),再附上述标记 + JSON。
## 输出风格
- 一次回复不超过 3 句话
- 提问要具体,不要"还有什么补充吗"这种空问
- 用 emoji 简短点缀(❓ 表示问题, ✅ 表示明确, 📋 表示汇总)
## 边界
- 不调用 agent_orchestration(这是 step 2 的协调智能体的事)
- 不写文件、不做计算、不联网
- 用户消息歧义大时,优先调 ask_clarification 工具引导
```
### 4.3 注册方式
跟现有 6 个 `roundtable-*` 一样,通过 `_sync_legacy_agents()`(`app/gateway/routers/agents.py`)在下次任意 `/api/agents` 请求时自动 upsert 进 `agents` 表,`user_id IS NULL` 标记为内置。**零代码改动**。
---
## 5. 后端实现要点
### 5.1 新建 router 文件
**位置**:`offline-backend-20260512/backend/app/gateway/routers/intent.py`
```python
"""任务理解智能体接口 - Step 1 的极简包装。
跟 multi_agent.py 同构但简化:
- 没有 broadcast(单 thread,无需广播)
- 没有派活逻辑
- 没有 leader/special 分支,只有一种调用方式
- 流末状态帧解析 [INTENT_READY] 标记决定 status=done/asking
"""
from fastapi import APIRouter, Body, HTTPException, Request
from fastapi.responses import StreamingResponse
import httpx, json, re, asyncio, logging
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/intent", tags=["intent"])
# 复用 multi_agent.py 里已有的工具函数
from app.gateway.routers.multi_agent import (
_loopback_base, _auth_headers,
_create_thread, _stream_upstream, _parse_last_messages,
_flatten_content, _run_payload,
)
INTENT_AGENT_ID = "roundtable-intent"
_INTENT_READY_PATTERN = re.compile(
r"\[INTENT_READY\]\s*```json\s*(\{.*?\})\s*```",
re.DOTALL,
)
@router.post("/init")
async def init_intent(request: Request, payload: dict = Body(...)) -> dict:
base = _loopback_base(request)
headers = _auth_headers(request)
async with httpx.AsyncClient(timeout=None) as client:
thread_id = await _create_thread(client, base, headers)
return {
"status": "success",
"agent_id": INTENT_AGENT_ID,
"thread_id": thread_id,
}
@router.post("/stream")
async def stream_intent(request: Request, payload: dict = Body(...)) -> StreamingResponse:
thread_id = str(payload.get("thread_id") or "").strip()
message = str(payload.get("message") or "").strip()
model_name = str(payload.get("model") or "deepseek-chat")
if not thread_id or not message:
raise HTTPException(status_code=400, detail="thread_id and message required")
base = _loopback_base(request)
headers = _auth_headers(request)
async def gen():
async with httpx.AsyncClient(timeout=None) as client:
try:
body = _run_payload(
agent_name=INTENT_AGENT_ID,
model_name=model_name,
thread_id=thread_id,
new_message=message,
excluded_tools=["web_search", "present_files",
"view_image", "agent_orchestration"],
skill_stop_names=[],
)
captured = []
async for line, done in _stream_upstream(client, base, headers, thread_id, body):
if line is not None:
yield (line if line.endswith("\n") else line + "\n")
elif done is not None:
captured = done
messages = _parse_last_messages(captured)
last_ai = {}
for m in reversed(messages):
if isinstance(m, dict) and (m.get("type") or "").lower() in ("ai", "aimessage", "aimessagechunk"):
last_ai = m
break
content = _flatten_content(last_ai.get("content", ""))
# 检测 [INTENT_READY] 标记
match = _INTENT_READY_PATTERN.search(content)
if match:
try:
summary = json.loads(match.group(1))
except json.JSONDecodeError:
summary = None
frame = {"status": "done", "content": content, "summary": summary}
else:
frame = {"status": "asking", "content": content}
yield f"data: {json.dumps(frame, ensure_ascii=False)}\n\n"
except HTTPException as exc:
yield f"data: {json.dumps({'status':'error','content':f'[{exc.status_code}] {exc.detail}'}, ensure_ascii=False)}\n\n"
except Exception as exc:
logger.exception("intent stream failure")
yield f"data: {json.dumps({'status':'error','content':f'unexpected: {exc!r}'}, ensure_ascii=False)}\n\n"
return StreamingResponse(
gen(),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no", "Connection": "keep-alive"},
)
```
### 5.2 在 `app/gateway/app.py` 注册
```python
from app.gateway.routers import intent
app.include_router(intent.router)
```
---
## 6. 前端实现要点
### 6.1 新增 API helper
**位置**:`frontend-web/src/roundtable-planning/api/intent.ts`
```ts
import { apiFetch } from "@/core/api/fetch-client";
import { getBackendBaseURL } from "@/core/config";
export interface IntentInitResponse {
status: "success";
agent_id: string;
thread_id: string;
}
export async function initIntent(model?: string): Promise<IntentInitResponse> {
const res = await apiFetch(`${getBackendBaseURL()}/api/intent/init`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model }),
});
if (!res.ok) throw new Error(`init intent failed (${res.status}): ${await res.text()}`);
return res.json();
}
export interface IntentSummary {
objective: string;
constraints: string[];
assumptions?: string[];
}
export interface StreamIntentRequest {
threadId: string;
message: string;
model?: string;
signal?: AbortSignal;
onTextDelta?: (text: string, id: string) => void;
}
export interface StreamIntentResult {
status: "asking" | "done" | "error";
content: string;
summary: IntentSummary | null;
}
export async function streamIntent(req: StreamIntentRequest): Promise<StreamIntentResult> {
// 实现:reader 循环跟 streamMultiAgent 几乎一样,只是状态帧解析改成
// {status, content, summary?} 格式。
// ...(参考 multi-agent.ts 的 streamMultiAgent 改写,简化 80%)
}
```
### 6.2 改造 `RoundtablePlanningPage.tsx` 的 step1 渲染
替换 `handleSendMessage`:
```ts
const [intentThreadId, setIntentThreadId] = useState<string | null>(null);
const [intentReady, setIntentReady] = useState<IntentSummary | null>(null);
useEffect(() => {
if (currentStep === 1 && !intentThreadId) {
initIntent(DEFAULT_MODEL).then(r => setIntentThreadId(r.thread_id));
}
}, [currentStep]);
const handleSendMessage = async () => {
if (!inputText.trim() || !intentThreadId) return;
const userText = inputText.trim();
// (push user msg to interactiveMessages 略)
setInputText("");
// 占位 agent 气泡
const agentMsgId = pushAgentPlaceholder("任务理解智能体");
const res = await streamIntent({
threadId: intentThreadId,
message: userText,
model: DEFAULT_MODEL,
onTextDelta: (delta) => appendToAgentMsg(agentMsgId, delta),
});
finalizeAgentMsg(agentMsgId, res.content);
if (res.status === "done" && res.summary) {
setIntentReady(res.summary);
triggerToast("✅ 任务理解完成,可以进入圆桌研讨");
}
};
```
### 6.3 "进入 step 2"按钮的联动
- 当前页面已有"完成澄清/进入下一步"按钮(应该是路由到 `currentStep === 2`)
- 改造:只有 `intentReady !== null` 时该按钮才 enabled
- 点击后:把 `intentReady.objective + intentReady.constraints` 写入 `buildInitialIntent()` 的源(替换现在按 `interactiveMessages` 找最后一条 user msg + budgetLimit 拼接的逻辑)
```ts
const buildInitialIntent = useCallback(() => {
if (intentReady) {
return `${intentReady.objective}\n\n约束条件:\n${intentReady.constraints.map(c => `- ${c}`).join("\n")}\n\n请基于上述目标与约束,在圆桌内调度合适的子智能体完成研讨。`;
}
// fallback: 旧逻辑保留兜底
const lastUserMessage = [...interactiveMessages].reverse().find(m => m.sender === "user");
const userIntent = lastUserMessage?.content ?? "请围绕给定任务展开圆桌研讨";
const constraints = `预算硬约束:${budgetLimit} 万元`;
return `${userIntent}\n\n约束条件:${constraints}\n\n请基于上述目标与约束,在圆桌内调度合适的子智能体完成研讨。`;
}, [interactiveMessages, budgetLimit, intentReady]);
```
---
## 7. 实施清单
| 优先级 | 项 | 文件 |
|---|---|---|
| P0 | 内置 agent 目录 | `backend/.deer-flow/agents/roundtable-intent/{config.yaml,SOUL.md}` |
| P0 | 新 router | `backend/app/gateway/routers/intent.py` |
| P0 | 注册 router | `backend/app/gateway/app.py`(一行 `include_router`) |
| P0 | 前端 helper | `frontend-web/src/roundtable-planning/api/intent.ts` |
| P0 | 前端页面改造 | `frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx`(handleSendMessage + buildInitialIntent + 入口 useEffect) |
| P1 | step 1 进入下一步按钮 disabled 状态联动 `intentReady` | 同上 |
| P2 | 单测 | `backend/tests/test_intent.py`(init 返回 agent_id 固定值, stream 识别 INTENT_READY 标记) |
| P3 | 失败兜底(INTENT_READY 标记被模型遗漏时的"用户手动确认进入下一步"按钮) | 前端 |
---
## 8. 与 step 2 的衔接
```
step 1 输出: intentReady.objective + intentReady.constraints
↓
step 2 入口: buildInitialIntent() 拼成 leader 的 new_message
↓
step 2 leader 不再需要主动反问澄清(因为 step1 已经清完了),
除非模型遇到完全意料之外的歧义
↓
研讨更聚焦,减少 step2 阶段的 clarification 轮次
```
副产物:协调智能体的 SOUL 可以稍微调整(参考 `multi_agent.py:soul = "#核心定位..."` 那段),加一句"用户已经经过任务理解智能体澄清,task 描述里包含目标+约束,请直接开始派活而不是再反问",进一步减少 step2 的反问。
---
## 9. 路径速查
| 资源 | 位置 |
|---|---|
| Step 1 渲染 | `RoundtablePlanningPage.tsx` 内 `currentStep === 1` 分支(约 line 1418+) |
| 新增前端 helper | `frontend-web/src/roundtable-planning/api/intent.ts` |
| 新增后端 router | `offline-backend-20260512/backend/app/gateway/routers/intent.py` |
| 内置 agent | `offline-backend-20260512/backend/.deer-flow/agents/roundtable-intent/` |
| 路由注册 | `offline-backend-20260512/backend/app/gateway/app.py` |
| 参照实现 | `multi_agent.py`(接口模式、SSE 包装、状态帧) |
---
## 10. 验收清单
- [ ] 进入 step 1 自动调 `/api/intent/init`,拿到 thread_id
- [ ] 用户每次发消息后,任务理解智能体真实流式输出(不是 setTimeout)
- [ ] 智能体能在 2-5 轮对话后自动停止追问,发 `status: done` 帧并附 summary
- [ ] step 2 的初始 intent 用 step1 的 summary 拼接,不再依赖 `budgetLimit` 这种页面状态
- [ ] 离开/重新进入 step 1 时,thread_id 状态干净(不会带着上次的对话)
- [ ] 任何接口失败都不让 step 1 卡死(容错的"用旧 mock 兜底"或者明确错误提示)
+264
View File
@@ -0,0 +1,264 @@
# 多智能体圆桌 · Step 3 · 接口与对接说明
> Step 3(`/page/roundtable/planning` 的"最终方案展示"页)对应 `frontend-web/src/roundtable-planning/components/HighFidelityReport.tsx`。当前页面是产品早期的"展厅效果",绝大多数内容是硬编码 mock。本文档梳理**真正需要的数据**、**用哪些接口**、**前后端怎么对接**。
---
## 1. 改造方向(在做什么,不做什么)
### ✅ 保留并对接真实数据
| 区块 | 现状 | 改造后 |
|---|---|---|
| 标题栏 + 生成时间 | 硬编码"2026-05-21 11:01:12" | 用本地 `Date.now()` 或 init/最后一次 leader 完成时刻 |
| "已达成共识"徽章 | 静态显示 | step2 自然收敛(`leaderRes.dispatched.length === 0`)后才显示 |
| **总控最终结论** | 完全是 mock 文本 | 取最后一次 `leaderRes.content`(已经在 step2 拿到了) |
| **各席位最终交付(折叠卡片)** | 当前 step3 没渲染 | 取每个 sub 线程的最后一条 AIMessage 文本 |
| **生成的报告文件清单** | 5 张假"档案" | 真实列出 `outputs/` 目录里的所有产出(markdown / pdf / csv 等) |
| **文件下载** | 假 toast | 用现有 `GET /api/threads/{tid}/artifacts/{path}` |
| "重新起草新规划" | 调用 `setCurrentStep(1)` 重置 | **保留**,含义就是回到 step1 重新开始 |
### ❌ 不做(直接删 / 不接口)
| 区块 | 原因 |
|---|---|
| 4 张 metric 卡片(设计全要素通过率 99.4% / Tier III+ / 终审共识度 42 轮研商 / 90.3分)| 全是按 `budgetLimit` 编出来的"展厅指标",没有真实数据源 |
| 三阶段推进路线图(0-24h / 24-60h / 60-72h 三张卡)| 假设业务必然是 72 小时交付,写死场景,不通用 |
| "采纳理由与最终裁定判词"段 | 整段都是模板话术,没接口能算出"90.3 分总评" |
| "12 轮极限对抗研商" 数据 | 当前没记录"轮次"语义,临时就用 leader 实际轮数代替即可,不值得为它出接口 |
| "签发起草成果并投产运行" 按钮 | 无对应业务动作(不是要部署、不是要审批),删掉 |
| "执行摘要" 段写死的 3 个 bullet | 模板话术,跟实际任务无关 |
最终 step3 页面只保留四块:**头部状态 + 总控结论 + 席位交付列表 + 文件下载清单**,再加底部"重新起草"。
---
## 2. 数据来源全景
```
┌───────────────────────────────────────────────────────────┐
│ step2 全程结束时,前端手里已经有的本地状态 │
├───────────────────────────────────────────────────────────┤
│ coordinatorName: "roundtable-coordinator-xxx" │
│ threadIds: { [agent_name]: thread_id, ... } │ ← /init 返回
│ step2RoundtableDialogues: [...所有气泡, 含每个 sub 最终 │
│ 交付的 content...] │
│ leaderRes.content (最后一次 leader 的 final text) │
└───────────────────────────────────────────────────────────┘
后端权威数据(需要时从后端拉):
- 每个 sub 线程的完整 state → GET /api/threads/{tid}/state
- 单个 outputs 文件内容/下载 → GET /api/threads/{tid}/artifacts/{path}
- outputs 目录文件清单 ❗暂无 → 新增接口(见 §3)
```
**关键判断**:席位最终交付文本 + 总控结论文本 **已经在 step2 内存里**,无需重新调接口。文件清单是**目前唯一缺失**的数据源。
---
## 3. 接口需求
### 3.1 新增(**必需**):列出某 thread 的产出文件
```
GET /api/multi-agent/sessions/artifacts?thread_id=<sub_thread_id>
```
或者更通用、放进 artifacts 路由下:
```
GET /api/threads/{thread_id}/artifacts
```
**为什么需要**:现有 artifacts endpoint 只能"按已知路径下载文件",没有"列出目录"功能。Step 3 需要知道 sub-agent 写到 `/mnt/user-data/outputs/` 下哪些文件,才能在前端列出可下载清单。
**响应**:
```json
{
"thread_id": "<sub_thread_id>",
"files": [
{
"path": "mnt/user-data/outputs/方案设计报告_生产系统安全改造技术方案.md",
"name": "方案设计报告_生产系统安全改造技术方案.md",
"size_bytes": 12480,
"mime_type": "text/markdown",
"modified_at": "2026-05-23T14:32:30.123Z"
},
...
]
}
```
**实现建议**:
- 列 `backend/.deer-flow/users/{user_id}/threads/{thread_id}/user-data/outputs/` 下的文件
- 鉴权复用 `@require_permission("threads", "read", owner_check=True)`
- 路径要做 traversal 保护(虚拟路径仅限 `mnt/user-data/outputs/`)
### 3.2 新增(**可选,体感优化**):一次性聚合 step3 数据
```
POST /api/multi-agent/sessions/summary
Body: {
"main_agent_name": "roundtable-coordinator-xxx",
"thread_ids": { "<agent_name>": "<thread_id>", ... }
}
```
**为什么可选**:不加这个接口的话,前端需要为每个席位发一次 `GET /state` + 一次 `GET /artifacts`(共 7-13 次 round-trip)。加了这个接口,**一次返回所有数据**。
**响应**:
```json
{
"main_agent_name": "roundtable-coordinator-xxx",
"coordinator": {
"thread_id": "...",
"final_text": "...", // 最后一条 AIMessage content
"dispatch_rounds": 3 // leader 派活的轮数(可选展示)
},
"seats": [
{
"agent_name": "roundtable-intelligence",
"thread_id": "...",
"display_name": "情报收集",
"final_text": "...", // 最后一条 AIMessage content
"files": [ // 该席位写到 outputs/ 的文件
{ "path": "...", "name": "...", "size_bytes": ..., "mime_type": "..." }
]
},
...
]
}
```
**取舍**:
- 优点:1 次请求 vs 13 次;体验更顺
- 缺点:等于把 3.1 + N 次 state 读放到一个 endpoint,实现稍复杂
- 推荐:**先做 3.1,3.2 暂缓**;前端用现有 endpoint 串起来够用
### 3.3 复用(**无需改动**)
| Endpoint | 用途 |
|---|---|
| `GET /api/threads/{tid}/state` | 取席位 / 总控线程的完整状态,提取最后一条 AIMessage content |
| `GET /api/threads/{tid}/artifacts/{path}` | 点击文件名预览或下载(已有 `?download=true` 支持) |
> Note: 这两个 endpoint 已经在 `app.gateway.routers.threads` / `app.gateway.routers.artifacts` 里实现,前端 `fetch-client` 也有现成调用方式。
---
## 4. 前后端对接流程
### 4.1 进入 step3 时
```ts
// 触发条件:runOrchestration 自然结束(leaderRes.dispatched.length === 0
// 且无 clarification),用户点"达成共识生成全案成果"按钮 / 自动跳转。
// 前端手里已有:threadIds, coordinatorName, 最后的 leaderRes
// 1. 总控结论文本 — 已在内存,直接用
const consensusText = lastLeaderRes.content;
// 2. 各席位交付 — 已在 step2RoundtableDialogues 里,过滤"子智能体交付"的卡片
const seatDeliveries = step2RoundtableDialogues
.filter(d => d.confidence === "子智能体交付")
.map(d => ({ sender: d.sender, content: d.content }));
// 3. 文件清单 — 调新接口
const filesPerSeat = await Promise.all(
Object.entries(threadIds)
.filter(([name]) => name !== coordinatorName)
.map(async ([name, tid]) => ({
agentName: name,
files: (await fetch(`/api/threads/${tid}/artifacts`).then(r => r.json())).files,
})),
);
```
### 4.2 用户点击文件时
```ts
// 在线预览(mime 是 markdown/text/pdf 自动 inline)
window.open(`/api/threads/${threadId}/artifacts/${encodeURIComponent(filePath)}`);
// 强制下载
window.open(`/api/threads/${threadId}/artifacts/${encodeURIComponent(filePath)}?download=true`);
```
### 4.3 "重新起草新规划" 按钮
```ts
// 跟现在一样,纯前端:清掉 dialogues + 跳 step1
setCurrentStep(1);
setInteractiveMessages(...); // 已有逻辑
setStep2RoundtableDialogues([]);
// 不需要调任何接口
```
---
## 5. 改造前端的具体文件清单
| 文件 | 改动 |
|---|---|
| `frontend-web/src/roundtable-planning/components/HighFidelityReport.tsx` | **大改**。删除四张 metric 卡 / 三阶段路线图 / 论证要件 / 裁定判词;新增结论卡 + 席位折叠列表 + 文件清单 |
| `frontend-web/src/roundtable-planning/api/multi-agent.ts` | 新增 `listSessionArtifacts(threadId)` helper(封装 §3.1 接口) |
| `frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx` | 把 `threadIds` / `coordinatorName` / `lastLeaderContent` 传进 `HighFidelityReport` 的 props(目前没传) |
---
## 6. 改造后端的具体文件清单
| 文件 | 改动 |
|---|---|
| `offline-backend-20260512/backend/app/gateway/routers/artifacts.py` | 新增 `GET /api/threads/{tid}/artifacts`(不带 path,列目录) |
| `offline-backend-20260512/backend/CLAUDE.md` | 在 artifacts 路由表里增加新条目说明 |
| `offline-backend-20260512/backend/tests/test_artifacts_list.py` | 列目录新接口的单测(覆盖:空目录返回 [];traversal 路径返回 403;不存在 thread 返回 404)|
---
## 7. 实施优先级
| 优先级 | 项 | 备注 |
|---|---|---|
| P0 | 新增 `GET /api/threads/{tid}/artifacts` 列文件接口 | 必需,没它前端无法列文件 |
| P0 | step3 大改:删 mock 区块、加结论/席位/文件三块 | 主要价值改造 |
| P1 | 前端 helper `listSessionArtifacts` | 封装 fetch + 类型 |
| P2 | 单测 `test_artifacts_list.py` | 上线后跟进 |
| P3 | `POST /api/multi-agent/sessions/summary` 聚合接口 | 13 次→1 次,体感优化,**非必需** |
---
## 8. 显式不做的事 ❌
1. **不做"PDF 导出"**:后端没有现成 PDF 渲染管线,要做就是单独的项目。当前页面那个 `导出方案 PDF` 按钮直接删掉,或者保留按钮但点击只触发 `window.print()`(让浏览器自己生成 PDF)。
2. **不做"安全分享"**:项目里已有 `/api/thread-shares`(per-thread 分享)和 `/api/public/shares`(公开页面),但那是针对单个 thread 的,不是多 thread 会话的。要做"多 thread 整合分享"是新功能,远超 step3 的范围。
3. **不做"指标雷达图"**(什么 90.3 分总评):没有真实数据源,强行加只会变成下一个 mock。
4. **不做"签发投产"**:纯展示按钮,不接业务系统,删。
5. **不做"轮次/对抗次数"显示**:除非前端记一下 `dispatch_count`,否则后端没现成统计。如果真要展示,用现成的"派活了几次"代替"对抗 N 轮"。
---
## 9. 验收清单
step3 改造完成后应该能:
- [ ] 看到当前 session 的总控最终结论,markdown 渲染
- [ ] 看到 6 个席位各自的最终交付内容(默认折叠,点击展开)
- [ ] 看到所有 sub-agent 写到 outputs 的文件列表,含文件名、大小、类型 icon
- [ ] 点击文件名能在线预览(markdown / pdf / 图片)
- [ ] 点击下载按钮能下载(带正确文件名)
- [ ] 点"重新起草新规划"能回到 step1 且 step2 内存清空
- [ ] 页面不再包含任何来自 `budgetLimit` 的"假指标"
---
## 10. 路径速查
| 资源 | 位置 |
|---|---|
| Step 3 组件 | `frontend-web/src/roundtable-planning/components/HighFidelityReport.tsx` |
| 父页面 (state 持有者) | `frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx` |
| 前端 API helper | `frontend-web/src/roundtable-planning/api/multi-agent.ts` |
| 后端 artifacts 路由 | `offline-backend-20260512/backend/app/gateway/routers/artifacts.py` |
| 后端 multi-agent 路由 | `offline-backend-20260512/backend/app/gateway/routers/multi_agent.py` |
| 物理 outputs 目录 | `backend/.deer-flow/users/{user_id}/threads/{thread_id}/user-data/outputs/` |
+169 -169
View File
@@ -397,53 +397,53 @@ function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
);
}
const SidebarGroupLabel = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "div";
return (
<Comp
ref={ref}
data-slot="sidebar-group-label"
data-sidebar="group-label"
className={cn(
"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className,
)}
{...props}
/>
);
});
SidebarGroupLabel.displayName = "SidebarGroupLabel";
const SidebarGroupLabel = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "div";
const SidebarGroupAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
ref={ref}
data-slot="sidebar-group-action"
data-sidebar="group-action"
className={cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 md:after:hidden",
"group-data-[collapsible=icon]:hidden",
className,
)}
{...props}
/>
);
});
SidebarGroupAction.displayName = "SidebarGroupAction";
return (
<Comp
ref={ref}
data-slot="sidebar-group-label"
data-sidebar="group-label"
className={cn(
"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className,
)}
{...props}
/>
);
});
SidebarGroupLabel.displayName = "SidebarGroupLabel";
const SidebarGroupAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
ref={ref}
data-slot="sidebar-group-action"
data-sidebar="group-action"
className={cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 md:after:hidden",
"group-data-[collapsible=icon]:hidden",
className,
)}
{...props}
/>
);
});
SidebarGroupAction.displayName = "SidebarGroupAction";
function SidebarGroupContent({
className,
@@ -503,99 +503,99 @@ const sidebarMenuButtonVariants = cva(
},
);
const SidebarMenuButton = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & {
asChild?: boolean;
isActive?: boolean;
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
} & VariantProps<typeof sidebarMenuButtonVariants>
>(
(
{
asChild = false,
isActive = false,
variant = "default",
size = "default",
tooltip,
className,
...props
},
ref,
) => {
const Comp = asChild ? Slot : "button";
const { isMobile, state } = useSidebar();
const button = (
<Comp
ref={ref}
data-slot="sidebar-menu-button"
data-sidebar="menu-button"
data-size={size}
data-active={isActive}
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props}
/>
);
if (!tooltip) {
return button;
}
if (typeof tooltip === "string") {
tooltip = {
children: tooltip,
};
}
return (
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent
side="right"
align="center"
hidden={state !== "collapsed" || isMobile}
{...tooltip}
/>
</Tooltip>
);
},
);
SidebarMenuButton.displayName = "SidebarMenuButton";
const SidebarMenuButton = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & {
asChild?: boolean;
isActive?: boolean;
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
} & VariantProps<typeof sidebarMenuButtonVariants>
>(
(
{
asChild = false,
isActive = false,
variant = "default",
size = "default",
tooltip,
className,
...props
},
ref,
) => {
const Comp = asChild ? Slot : "button";
const { isMobile, state } = useSidebar();
const SidebarMenuAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & {
asChild?: boolean;
showOnHover?: boolean;
}
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
ref={ref}
data-slot="sidebar-menu-action"
data-sidebar="menu-action"
className={cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 md:after:hidden",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
showOnHover &&
"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
className,
)}
{...props}
/>
);
});
SidebarMenuAction.displayName = "SidebarMenuAction";
const button = (
<Comp
ref={ref}
data-slot="sidebar-menu-button"
data-sidebar="menu-button"
data-size={size}
data-active={isActive}
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props}
/>
);
if (!tooltip) {
return button;
}
if (typeof tooltip === "string") {
tooltip = {
children: tooltip,
};
}
return (
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent
side="right"
align="center"
hidden={state !== "collapsed" || isMobile}
{...tooltip}
/>
</Tooltip>
);
},
);
SidebarMenuButton.displayName = "SidebarMenuButton";
const SidebarMenuAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & {
asChild?: boolean;
showOnHover?: boolean;
}
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
ref={ref}
data-slot="sidebar-menu-action"
data-sidebar="menu-action"
className={cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 md:after:hidden",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
showOnHover &&
"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
className,
)}
{...props}
/>
);
});
SidebarMenuAction.displayName = "SidebarMenuAction";
function SidebarMenuBadge({
className,
@@ -686,37 +686,37 @@ function SidebarMenuSubItem({
);
}
const SidebarMenuSubButton = React.forwardRef<
HTMLAnchorElement,
React.ComponentProps<"a"> & {
asChild?: boolean;
size?: "sm" | "md";
isActive?: boolean;
}
>(({ asChild = false, size = "md", isActive = false, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a";
return (
<Comp
ref={ref}
data-slot="sidebar-menu-sub-button"
data-sidebar="menu-sub-button"
data-size={size}
data-active={isActive}
className={cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
size === "sm" && "text-xs",
size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden",
className,
)}
{...props}
/>
);
});
SidebarMenuSubButton.displayName = "SidebarMenuSubButton";
const SidebarMenuSubButton = React.forwardRef<
HTMLAnchorElement,
React.ComponentProps<"a"> & {
asChild?: boolean;
size?: "sm" | "md";
isActive?: boolean;
}
>(({ asChild = false, size = "md", isActive = false, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a";
return (
<Comp
ref={ref}
data-slot="sidebar-menu-sub-button"
data-sidebar="menu-sub-button"
data-size={size}
data-active={isActive}
className={cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
size === "sm" && "text-xs",
size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden",
className,
)}
{...props}
/>
);
});
SidebarMenuSubButton.displayName = "SidebarMenuSubButton";
export {
Sidebar,
@@ -0,0 +1,69 @@
import { apiFetch } from "@/core/api/fetch-client";
import { getBackendBaseURL } from "@/core/config";
function filenameFromContentDisposition(header: string | null): string | null {
if (!header) return null;
const star = /filename\*=(?:UTF-8'')?([^;]+)/i.exec(header);
if (star) {
const raw = star[1].replace(/["']/g, "").trim();
try {
return decodeURIComponent(raw);
} catch {
return raw;
}
}
const plain = /filename="?([^";\n]+)"?/i.exec(header);
return plain ? plain[1].trim() : null;
}
function sanitizeFallbackFilename(title: string | undefined): string {
const base = (title?.trim() || "document").replace(/[<>:"/\\|?*\x00-\x1f]+/g, "_");
return base.toLowerCase().endsWith(".docx") ? base : `${base}.docx`;
}
async function downloadBlobResponse(response: Response, fallbackFilename: string) {
if (!response.ok) {
const error = await response
.json()
.catch(() => ({ detail: "导出 Word 失败" }));
const detail = error.detail;
if (typeof detail === "string") throw new Error(detail);
throw new Error("导出 Word 失败");
}
const blob = await response.blob();
const filename =
filenameFromContentDisposition(response.headers.get("Content-Disposition")) ??
fallbackFilename;
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = filename;
anchor.rel = "noopener";
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
window.setTimeout(() => URL.revokeObjectURL(url), 30_000);
}
/** 调用后端导出 AI 写作草稿为 Word,仅传正文 markdown,不传目录/大纲。 */
export async function downloadAIWritingDocx(options: {
title?: string;
draftMarkdown: string;
}): Promise<void> {
const draftMarkdown = options.draftMarkdown.trim();
if (!draftMarkdown) {
throw new Error("文档内容为空");
}
const response = await apiFetch(`${getBackendBaseURL()}/api/ai-writing/export/docx`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: options.title?.trim() || undefined,
draft_markdown: draftMarkdown,
}),
});
await downloadBlobResponse(response, sanitizeFallbackFilename(options.title));
}
@@ -25,7 +25,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { CanvasModelSelector } from "../selectors/CanvasModelSelector";
import { exportMarkdownToDocx } from "../../utils/export-docx";
import { downloadAIWritingDocx } from "../../api/writing-export";
interface ToolbarButtonProps {
onClick: () => void;
@@ -82,9 +82,12 @@ export function WritingToolbar({ editor, markdown, title }: WritingToolbarProps)
if (!canExport) return;
setExporting(true);
try {
await exportMarkdownToDocx(markdown ?? "", title);
await downloadAIWritingDocx({
title,
draftMarkdown: markdown ?? "",
});
} catch (err) {
console.error("[export-docx] failed", err);
console.error("[writing-export] failed", err);
toast.error("导出 Word 失败,请稍后重试");
} finally {
setExporting(false);
@@ -3,10 +3,27 @@ import { getBackendBaseURL } from "@/core/config";
export type ThreadIdMap = Record<string, string>;
export interface SeatReadyEvent {
agentName: string;
threadId: string;
displayName: string;
description: string;
}
export interface CoordinatorReadyEvent {
mainAgentName: string;
threadId: string;
}
export interface InitMultiAgentRequest {
agentNames: string[];
mainAgentName: string;
model?: string;
/** Fires the moment each sub-agent's thread is ready (real completion order). */
onSeatReady?: (event: SeatReadyEvent) => void;
/** Fires when the coordinator's thread + agent have been created. */
onCoordinatorReady?: (event: CoordinatorReadyEvent) => void;
signal?: AbortSignal;
}
export interface InitMultiAgentResponse {
@@ -15,6 +32,12 @@ export interface InitMultiAgentResponse {
thread_ids: ThreadIdMap;
}
/**
* Open the /api/multi-agent/init SSE stream. Fires per-seat / coordinator
* callbacks as events arrive so the UI can light up roles individually, and
* resolves with the final {main_agent_name, thread_ids} once init_done lands.
* If the server emits an "error" frame mid-stream, the promise rejects.
*/
export async function initMultiAgent(
payload: InitMultiAgentRequest,
): Promise<InitMultiAgentResponse> {
@@ -26,18 +49,114 @@ export async function initMultiAgent(
main_agent_name: payload.mainAgentName,
model: payload.model,
}),
signal: payload.signal,
});
if (!res.ok) {
if (!res.ok || !res.body) {
const text = await res.text().catch(() => "");
throw new Error(`init multi-agent failed (${res.status}): ${text}`);
}
return (await res.json()) as InitMultiAgentResponse;
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let mainAgentName = "";
let threadIds: ThreadIdMap | null = null;
let aborted = false;
let errorDetail: string | null = null;
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const rawLine of lines) {
const line = rawLine.trimEnd();
if (!line.startsWith("data:")) continue;
const data = line.slice(5).trim();
if (!data) continue;
let parsed: unknown;
try {
parsed = JSON.parse(data);
} catch {
continue;
}
if (!parsed || typeof parsed !== "object") continue;
const obj = parsed as Record<string, unknown>;
const event = typeof obj.event === "string" ? obj.event : "";
switch (event) {
case "seat_ready": {
payload.onSeatReady?.({
agentName: String(obj.agent_name ?? ""),
threadId: String(obj.thread_id ?? ""),
displayName: String(obj.display_name ?? ""),
description: String(obj.description ?? ""),
});
break;
}
case "coordinator_ready": {
payload.onCoordinatorReady?.({
mainAgentName: String(obj.main_agent_name ?? ""),
threadId: String(obj.thread_id ?? ""),
});
break;
}
case "init_done": {
mainAgentName = String(obj.main_agent_name ?? "");
threadIds = (obj.thread_ids ?? {}) as ThreadIdMap;
break;
}
case "error": {
errorDetail = String(obj.detail ?? "unknown init error");
break;
}
default:
// Unknown event — ignore for forward-compat.
break;
}
}
}
} catch (err) {
aborted = (err as Error)?.name === "AbortError" || payload.signal?.aborted === true;
if (!aborted) throw err;
}
if (errorDetail) {
throw new Error(`init multi-agent failed: ${errorDetail}`);
}
if (aborted) {
throw new DOMException("init aborted", "AbortError");
}
if (!threadIds || !mainAgentName) {
throw new Error("init multi-agent stream ended without init_done");
}
return {
status: "success",
main_agent_name: mainAgentName,
thread_ids: threadIds,
};
}
export type StreamAgentType = "leader" | "special";
export type LeaderDispatch = [string, string];
/**
* High-level phases the SSE stream goes through. Used by the UI to show
* "what the backend is doing" while the user waits — most of the time before
* a first token is upstream/LLM latency, which feels like a stall otherwise.
*
* - `connecting` — fetch sent, no frames yet
* - `connected` — got the `event: metadata` frame (run_id assigned)
* - `streaming` — first AI text chunk arrived; tokens flowing
* - `tool_calling` — model emitted a tool_call chunk (e.g. agent_orchestration,
* present_files); usually means the run is about to interrupt
* - `tool_result` — a tool result message came back; brief, may go back to streaming
*/
export type StreamPhase = "connecting" | "connected" | "streaming" | "tool_calling" | "tool_result";
export interface StreamMultiAgentRequest {
agentType: StreamAgentType;
dAgentThreadId: ThreadIdMap;
@@ -54,6 +173,25 @@ export interface StreamMultiAgentRequest {
* `messageId` is stable per AI message so callers can group deltas.
*/
onTextDelta?: (text: string, messageId: string) => void;
/**
* Fires whenever the inferred SSE phase changes. The UI can use this to
* show progressive status messages during the inevitable first-token wait.
* `name` is the tool name when phase is `tool_calling` (or empty otherwise).
*/
onPhaseChange?: (phase: StreamPhase, name?: string) => void;
}
export interface ClarificationPayload {
/** Raw question text from the model's ask_clarification args. */
question: string;
/** Optional category hint from the model (e.g. "missing_info"). */
clarificationType: string;
/** Free-text reason the model needs clarification. */
context: string;
/** Suggested answer chips (may be empty). */
options: unknown[];
/** Whether the user is allowed to type a custom answer. */
allowCustom: boolean;
}
export interface StreamMultiAgentResult {
@@ -65,6 +203,14 @@ export interface StreamMultiAgentResult {
content: string;
/** Echoed agent_name of the agent that produced this run. */
agentName: string | null;
/**
* Populated when the leader called ask_clarification — the run ended without
* dispatching, and the model is asking the user a question instead. When
* present, callers should render `content` (or `clarification.question` as
* a fallback) as a question and collect a user reply rather than treating
* an empty `dispatched` as consensus.
*/
clarification: ClarificationPayload | null;
}
/**
@@ -76,6 +222,11 @@ function isFinalStatusFrame(value: unknown): value is {
status: LeaderDispatch[] | string;
content?: string;
agent_name?: string;
question?: string;
clarification_type?: string;
clarification_context?: string;
options?: unknown[];
allow_custom?: boolean;
} {
if (!value || typeof value !== "object") return false;
if (!("status" in value) || !("content" in value)) return false;
@@ -125,6 +276,44 @@ function extractAiTextDelta(parsed: unknown): { text: string; id: string } | nul
return { text, id };
}
/**
* Trigger a browser download for a text blob. No-op outside the browser.
* Used by streamMultiAgent's dump-to-file debug mode.
*/
function downloadTextBlob(filename: string, text: string): void {
if (typeof window === "undefined" || typeof document === "undefined") return;
try {
const blob = new Blob([text], { type: "text/plain;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.style.display = "none";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Defer revoke so the browser has a chance to start the download.
setTimeout(() => URL.revokeObjectURL(url), 60_000);
} catch (err) {
// Downloads must never break the actual stream — just log and continue.
console.warn("[stream] downloadTextBlob failed:", err);
}
}
/**
* Whether streamMultiAgent should auto-download the raw SSE transcript to a
* .txt when each call ends. Default ON for now because we're debugging the
* "sub-agent stream looks empty" issue and the browser console truncates
* large message logs. Toggle from devtools:
* window.__MULTI_AGENT_DUMP__ = false
*/
function isDumpEnabled(): boolean {
if (typeof window === "undefined") return false;
const flag = (window as unknown as { __MULTI_AGENT_DUMP__?: boolean }).__MULTI_AGENT_DUMP__;
// Default: enabled. Only disabled if explicitly set to false.
return flag !== false;
}
/**
* Open an SSE connection to /api/multi-agent/run/stream and resolve once the
* stream completes. The final frame (`{status: ...}`) is interpreted; every
@@ -168,66 +357,184 @@ export async function streamMultiAgent(
typeof window !== "undefined" &&
(window as unknown as { __MULTI_AGENT_DEBUG__?: boolean }).__MULTI_AGENT_DEBUG__ !== false;
const result: StreamMultiAgentResult = { dispatched: [], message: null, content: "", agentName: null };
// Raw SSE transcript buffer. Every raw line read from the stream (event:,
// data:, blank framing lines, anything) is appended verbatim so the dumped
// .txt is byte-for-byte what the frontend received off the socket. This is
// independent of parsing: even malformed/non-JSON frames end up in the file.
const dumpEnabled = isDumpEnabled();
const transcript: string[] = dumpEnabled
? [
`# multi-agent stream transcript`,
`# agent_type: ${req.agentType}`,
`# agent_name: ${req.agentName}`,
`# started_at: ${new Date().toISOString()}`,
`# new_message: ${req.newMessage.replace(/\n/g, " ⏎ ")}`,
`# model: ${req.model ?? "(default)"}`,
`# ---- raw SSE follows ----`,
"",
]
: [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const result: StreamMultiAgentResult = {
dispatched: [],
message: null,
content: "",
agentName: null,
clarification: null,
};
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
// Inferred SSE phase + a tiny helper so we only fire onPhaseChange when the
// value actually changes (the reader loop sees many frames per phase).
let currentPhase: StreamPhase = "connecting";
req.onPhaseChange?.("connecting");
const setPhase = (next: StreamPhase, name?: string) => {
if (next === currentPhase && !name) return;
currentPhase = next;
req.onPhaseChange?.(next, name);
};
for (const rawLine of lines) {
const line = rawLine.trimEnd();
if (!line) continue;
if (line.startsWith("event:")) {
lastEvent = line.slice(6).trim();
continue;
}
if (!line.startsWith("data:")) continue;
const payload = line.slice(5).trim();
if (!payload) continue;
// Wrap the read loop so we always flush the transcript to disk, even on
// abort / network failure. Otherwise an interrupted run would lose the very
// evidence we want to inspect.
const finishDump = (reason: string) => {
if (!dumpEnabled) return;
transcript.push("");
transcript.push(`# ---- stream ended (${reason}) ----`);
transcript.push(`# ended_at: ${new Date().toISOString()}`);
transcript.push(`# frames_parsed: ${frameSeq}`);
transcript.push(`# final_content_length: ${result.content.length}`);
transcript.push(`# dispatched_count: ${result.dispatched.length}`);
const tsSafe = new Date().toISOString().replace(/[:.]/g, "-");
const nameSafe = req.agentName.replace(/[^a-zA-Z0-9_-]/g, "_");
const filename = `multi-agent_${req.agentType}_${nameSafe}_${tsSafe}.txt`;
downloadTextBlob(filename, transcript.join("\n"));
};
let parsed: unknown;
try {
parsed = JSON.parse(payload);
} catch {
if (debugEnabled) console.debug(debugLabel, `bad JSON in event=${lastEvent}:`, payload.slice(0, 200));
continue;
}
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
buffer += chunk;
frameSeq += 1;
if (debugEnabled) console.debug(debugLabel, `frame#${frameSeq} event=${lastEvent}`, parsed);
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
if (isFinalStatusFrame(parsed)) {
if (Array.isArray(parsed.status)) {
result.dispatched = parsed.status;
} else {
result.message = parsed.status;
for (const rawLine of lines) {
// Record EVERY raw line — including blank framing lines and event:
// headers — so the dumped transcript matches what the socket sent.
if (dumpEnabled) transcript.push(rawLine);
const line = rawLine.trimEnd();
if (!line) continue;
if (line.startsWith("event:")) {
lastEvent = line.slice(6).trim();
// First metadata frame = run accepted by upstream. Move phase out
// of "connecting" so the UI can switch from "调度中" to "已连接".
if (lastEvent === "metadata" && currentPhase === "connecting") {
setPhase("connected");
}
continue;
}
if (typeof parsed.content === "string") {
result.content = parsed.content;
}
if (typeof parsed.agent_name === "string") {
result.agentName = parsed.agent_name;
}
continue;
}
if (!line.startsWith("data:")) continue;
const payload = line.slice(5).trim();
if (!payload) continue;
const delta = extractAiTextDelta(parsed);
if (delta) {
if (debugEnabled) console.debug(debugLabel, `→ delta (${delta.text.length} chars)`);
req.onTextDelta?.(delta.text, delta.id);
}
let parsed: unknown;
try {
parsed = JSON.parse(payload);
} catch {
if (debugEnabled) console.debug(debugLabel, `bad JSON in event=${lastEvent}:`, payload.slice(0, 200));
continue;
}
req.onUpstreamFrame?.(parsed);
frameSeq += 1;
if (debugEnabled) console.debug(debugLabel, `frame#${frameSeq} event=${lastEvent}`, parsed);
// Phase inference from LangGraph messages-tuple frames. We only flip
// to tool_calling/tool_result on chunks that clearly indicate them;
// everything else with non-empty AI text means we're streaming.
if (lastEvent === "messages" && Array.isArray(parsed) && parsed.length > 0) {
const chunk = parsed[0];
if (chunk && typeof chunk === "object") {
const chunkObj = chunk as Record<string, unknown>;
const chunkType = String(chunkObj.type ?? "").toLowerCase();
const toolCalls = chunkObj.tool_calls;
const toolCallChunks = chunkObj.tool_call_chunks;
const content = chunkObj.content;
const hasToolCall =
(Array.isArray(toolCalls) && toolCalls.length > 0) ||
(Array.isArray(toolCallChunks) && toolCallChunks.length > 0);
if (chunkType === "tool") {
setPhase("tool_result");
} else if (hasToolCall) {
// Pick the most informative name we can find in either field.
const firstCall = Array.isArray(toolCalls) && toolCalls.length > 0
? (toolCalls[0] as Record<string, unknown>)
: Array.isArray(toolCallChunks) && toolCallChunks.length > 0
? (toolCallChunks[0] as Record<string, unknown>)
: null;
const toolName = firstCall && typeof firstCall.name === "string" ? firstCall.name : "";
setPhase("tool_calling", toolName);
} else if (
(chunkType === "ai" || chunkType === "aimessagechunk" || chunkType === "aimessage") &&
((typeof content === "string" && content.length > 0) || (Array.isArray(content) && content.length > 0))
) {
setPhase("streaming");
}
}
}
if (isFinalStatusFrame(parsed)) {
if (Array.isArray(parsed.status)) {
result.dispatched = parsed.status;
} else {
result.message = parsed.status;
// "clarification" status carries the leader's ask_clarification
// payload. Extract the structured fields so the page can render
// a question (or chip choices) instead of mistaking the run for
// a consensus.
if (parsed.status === "clarification") {
result.clarification = {
question: typeof parsed.question === "string" ? parsed.question : "",
clarificationType:
typeof parsed.clarification_type === "string" ? parsed.clarification_type : "",
context:
typeof parsed.clarification_context === "string" ? parsed.clarification_context : "",
options: Array.isArray(parsed.options) ? parsed.options : [],
allowCustom: parsed.allow_custom !== false,
};
}
}
if (typeof parsed.content === "string") {
result.content = parsed.content;
}
if (typeof parsed.agent_name === "string") {
result.agentName = parsed.agent_name;
}
continue;
}
const delta = extractAiTextDelta(parsed);
if (delta) {
if (debugEnabled) console.debug(debugLabel, `→ delta (${delta.text.length} chars)`);
req.onTextDelta?.(delta.text, delta.id);
}
req.onUpstreamFrame?.(parsed);
}
}
}
if (debugEnabled) {
console.debug(debugLabel, `stream ended after ${frameSeq} frames; final content length=${result.content.length}`);
if (debugEnabled) {
console.debug(debugLabel, `stream ended after ${frameSeq} frames; final content length=${result.content.length}`);
}
finishDump("normal");
return result;
} catch (err) {
// Flush whatever we got so the user can still inspect the transcript on
// abort / network drop. Then rethrow so callers' own handling runs.
const aborted = (err as Error)?.name === "AbortError" || req.signal?.aborted === true;
finishDump(aborted ? "aborted" : `error: ${(err as Error)?.message ?? String(err)}`);
throw err;
}
return result;
}
@@ -1,4 +1,6 @@
import React, { useState, useEffect, useRef, useCallback } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { HighFidelityReport } from "@/roundtable-planning/components/HighFidelityReport";
import { useTheme } from "@/contexts/ThemeContext";
import { cn } from "@/lib/utils";
@@ -6,7 +8,9 @@ import { useModels } from "@/core/models/hooks";
import {
initMultiAgent,
streamMultiAgent,
type ClarificationPayload,
type LeaderDispatch,
type StreamPhase,
type ThreadIdMap,
} from "@/roundtable-planning/api/multi-agent";
import {
@@ -39,7 +43,8 @@ import {
Cpu,
CornerDownRight,
Star,
ShieldCheck
ShieldCheck,
Loader2,
} from "lucide-react";
// --- Custom Crisp SVGs to Match High Fidelity Avatars ---
@@ -306,6 +311,11 @@ export default function RoundtablePlanningPage() {
const [isInitializing, setIsInitializing] = useState<boolean>(false);
const [initError, setInitError] = useState<string | null>(null);
const [isOrchestrating, setIsOrchestrating] = useState<boolean>(false);
// When the coordinator calls ask_clarification, the leader run ends with
// this payload populated; the page renders a question card and only
// resumes orchestration once the user replies.
const [pendingClarification, setPendingClarification] = useState<ClarificationPayload | null>(null);
const [clarificationDraft, setClarificationDraft] = useState<string>("");
const orchestrationCancelRef = useRef<boolean>(false);
const step2InitRef = useRef<boolean>(false);
// Tracks every AbortController active in this Step 2 visit so the "停止" /
@@ -368,6 +378,38 @@ export default function RoundtablePlanningPage() {
]);
}, []);
// Translate an inferred SSE phase into a human-readable confidence label
// for the dialogue bubble. We keep the bubble's existing in-progress
// confidence ("调度中" / "正在输出…") when phase doesn't add information,
// and switch to a specific label when something specific is happening.
const phaseLabel = useCallback((phase: StreamPhase, agentType: "leader" | "special", toolName?: string): string => {
switch (phase) {
case "connecting":
return "调度中";
case "connected":
return "已连接,等待响应";
case "tool_calling":
if (toolName === "agent_orchestration") return "正在派活";
if (toolName === "ask_clarification") return "正在提问";
if (toolName === "present_files") return "正在交付文件";
return toolName ? `调用工具:${toolName}` : "调用工具";
case "tool_result":
return "处理工具结果";
case "streaming":
return agentType === "leader" ? "正在派活说明" : "正在输出";
default:
return agentType === "leader" ? "调度中" : "正在输出…";
}
}, []);
// Update only the confidence field of a streaming card by id. Used by the
// phase callback so we can change the label without touching content.
const setDialogueConfidence = useCallback((id: number, confidence: string) => {
setStep2RoundtableDialogues(prev =>
prev.map(d => (d.id === id ? { ...d, confidence } : d)),
);
}, []);
// Streaming variant: append a placeholder card and return its id so subsequent
// text deltas can be appended in-place via appendToDialogue.
const appendStreamingDialogue = useCallback((card: {
@@ -501,8 +543,10 @@ export default function RoundtablePlanningPage() {
safetyCounter += 1;
highlightSpeaker(2);
// Leader bubble — created BEFORE the call so users see "正在输出…" the
// moment the request is in flight, then text fills in as deltas arrive.
// Leader bubble — created BEFORE the call so users see "调度中" the
// moment the request is in flight, then phase changes flip the
// confidence label so the user sees what the backend is actually
// doing during the ~1-2s first-token wait.
const leaderBubbleId = appendStreamingDialogue({
sender: "总控协调",
avatarType: "blue",
@@ -523,6 +567,9 @@ export default function RoundtablePlanningPage() {
model: DEFAULT_MODEL,
signal: leaderAborter.signal,
onTextDelta: (text) => appendToDialogue(leaderBubbleId, text),
onPhaseChange: (phase, toolName) => {
setDialogueConfidence(leaderBubbleId, phaseLabel(phase, "leader", toolName));
},
});
} catch (err) {
activeAbortersRef.current.delete(leaderAborter);
@@ -536,6 +583,18 @@ export default function RoundtablePlanningPage() {
if (orchestrationCancelRef.current) break;
// Clarification branch — leader asked the user a question. Stop the
// auto loop, surface the question card, and wait for the user to
// reply before another leader cycle.
if (leaderRes.clarification) {
finalizeDialogue(leaderBubbleId, leaderRes.content, "等待用户澄清");
highlightSpeaker(null);
setPendingClarification(leaderRes.clarification);
setClarificationDraft("");
triggerToast("❓ 总控需要补充信息,请回答下方问题后继续");
break;
}
finalizeDialogue(
leaderBubbleId,
leaderRes.content,
@@ -549,52 +608,59 @@ export default function RoundtablePlanningPage() {
break;
}
// Dispatch all sub-agents in parallel; per-seat model preference
// overrides the default if the user picked one in the drawer. Each
// sub-agent gets its own streaming bubble that fills independently.
await Promise.allSettled(
leaderRes.dispatched.map(async ([subName, task]) => {
if (orchestrationCancelRef.current) return;
const meta = AGENT_NAME_TO_ROLE[subName.toLowerCase()];
// Add this speaker to the concurrent active set so its avatar
// lights up alongside any other sub-agents streaming in parallel.
if (meta) markSpeakerStreaming(meta.speakerId);
const perSeatModel = meta ? agentConfigs[meta.speakerId]?.model : undefined;
// Dispatch sub-agents sequentially (one at a time). Running them in
// parallel meant N concurrent SSE streams each triggering a full-list
// setState on every token delta, which made the browser stutter badly
// when the leader 派活给多个席位。改为顺序后,同一时刻只有一个 SSE 在跑。
for (const [subName, task] of leaderRes.dispatched) {
if (orchestrationCancelRef.current) break;
const meta = AGENT_NAME_TO_ROLE[subName.toLowerCase()];
if (meta) markSpeakerStreaming(meta.speakerId);
const perSeatModel = meta ? agentConfigs[meta.speakerId]?.model : undefined;
const subBubbleId = appendStreamingDialogue({
sender: meta?.displayName ?? subName,
avatarType: meta?.avatarType ?? "blue",
initialContent: "",
confidence: "正在输出…",
const subBubbleId = appendStreamingDialogue({
sender: meta?.displayName ?? subName,
avatarType: meta?.avatarType ?? "blue",
initialContent: "",
confidence: "调度中",
});
const subAborter = new AbortController();
activeAbortersRef.current.add(subAborter);
let aborted = false;
try {
const specRes = await streamMultiAgent({
agentType: "special",
dAgentThreadId: ids,
agentName: subName,
newMessage: task,
model: perSeatModel || DEFAULT_MODEL,
signal: subAborter.signal,
onTextDelta: (text) => appendToDialogue(subBubbleId, text),
onPhaseChange: (phase, toolName) => {
setDialogueConfidence(subBubbleId, phaseLabel(phase, "special", toolName));
},
});
const subAborter = new AbortController();
activeAbortersRef.current.add(subAborter);
try {
const specRes = await streamMultiAgent({
agentType: "special",
dAgentThreadId: ids,
agentName: subName,
newMessage: task,
model: perSeatModel || DEFAULT_MODEL,
signal: subAborter.signal,
onTextDelta: (text) => appendToDialogue(subBubbleId, text),
});
if (orchestrationCancelRef.current) return;
if (orchestrationCancelRef.current) {
finalizeDialogue(subBubbleId, specRes.content || "(已停止)", "已停止");
aborted = true;
} else {
finalizeDialogue(subBubbleId, specRes.content, "子智能体交付");
} catch (err) {
if (subAborter.signal.aborted || (err as Error).name === "AbortError") {
finalizeDialogue(subBubbleId, "(已停止)", "已停止");
} else {
console.error(`[special:${subName}] error:`, err);
finalizeDialogue(subBubbleId, `(错误) ${(err as Error).message}`, "调用失败");
}
} finally {
activeAbortersRef.current.delete(subAborter);
if (meta) markSpeakerIdle(meta.speakerId);
}
}),
);
} catch (err) {
if (subAborter.signal.aborted || (err as Error).name === "AbortError") {
finalizeDialogue(subBubbleId, "(已停止)", "已停止");
aborted = true;
} else {
console.error(`[special:${subName}] error:`, err);
finalizeDialogue(subBubbleId, `(错误) ${(err as Error).message}`, "调用失败");
}
} finally {
activeAbortersRef.current.delete(subAborter);
if (meta) markSpeakerIdle(meta.speakerId);
}
if (aborted) break;
}
if (orchestrationCancelRef.current) break;
@@ -635,6 +701,12 @@ export default function RoundtablePlanningPage() {
abortAllInFlight();
step2InitRef.current = false;
setIsOrchestrating(false);
setPendingClarification(null);
setClarificationDraft("");
// Re-entering step 2 should start with the discussion nominally
// "running" so the suspend button comes back; the prior visit might
// have ended in a paused state.
setIsRoundTableRunning(true);
return;
}
@@ -654,6 +726,23 @@ export default function RoundtablePlanningPage() {
agentNames: ROUNDTABLE_AGENT_IDS,
mainAgentName: coord,
model: DEFAULT_MODEL,
// As each sub-agent's thread comes online, flip every role that
// shares the agentId to "在线". Some seats reuse the same agent
// (e.g. seat 8 reuses roundtable-summary) so we match on agentId
// rather than a single id.
onSeatReady: ({ agentName }) => {
if (cancelled) return;
const id = agentName.toLowerCase();
setRoles(prev =>
prev.map(r => (r.agentId === id ? { ...r, status: "在线" } : r)),
);
},
// Coordinator readiness is a no-op for the role list (the
// coordinator isn't rendered as a seat), but we keep the hook in
// case we want to drop a "系统" toast later.
onCoordinatorReady: () => {
if (cancelled) return;
},
});
if (cancelled) return;
setThreadIds(initRes.thread_ids);
@@ -851,11 +940,15 @@ export default function RoundtablePlanningPage() {
};
// Step 2 role statuses transition when entering roundtable!
// On Step 2 entry we set every agent seat to "正在上线" (spinner state);
// the /init SSE stream then flips each one to "在线" as its thread/agent
// actually finishes on the backend. Seat 1 (human commander) stays online
// throughout — it's not part of the bring-up.
useEffect(() => {
if (currentStep === 1) {
setRoles(makeRoles("等待激活", { 2: "在线" }));
} else if (currentStep === 2) {
setRoles(makeRoles("在线", { 8: "等待陈述" }));
setRoles(makeRoles("正在上线", { 1: "在线" }));
} else {
setRoles(makeRoles("已完成"));
}
@@ -887,6 +980,36 @@ export default function RoundtablePlanningPage() {
void runOrchestration(`用户人工干预新指令:${userText}\n\n请结合当前讨论状态,决定是否继续派活或直接给出更新后的结论。`, threadIds, coordinatorName);
};
// Submit the user's answer to the coordinator's clarification question and
// resume orchestration. The reply is fed back as the next leader prompt on
// the same coordinator thread (no re-init needed).
const submitClarification = (rawAnswer: string) => {
const answer = rawAnswer.trim();
if (!answer) return;
if (!threadIds || !coordinatorName) {
triggerToast("⚠️ 圆桌尚未就绪,无法回复澄清");
return;
}
if (isOrchestrating) {
triggerToast("⏳ 当前轮次仍在进行中,请稍候");
return;
}
const question = pendingClarification?.question || "(澄清问题)";
appendDialogue({
sender: "用户 (澄清回复)",
avatarType: "human",
content: `🗣 回复总控提问:${answer}`,
confidence: "澄清回复",
});
setPendingClarification(null);
setClarificationDraft("");
void runOrchestration(
`用户已回复你刚才的澄清问题。\n问题:${question}\n用户回答:${answer}\n请基于该回答继续派活或给出结论。`,
threadIds,
coordinatorName,
);
};
return (
<div
className={cn(
@@ -1182,13 +1305,18 @@ export default function RoundtablePlanningPage() {
{roles.map((role) => {
const isActive = role.status === "在线" || role.status === "研讨中" || role.status === "已完成";
const isOngoing = role.status === "研讨中";
const isBootingUp = role.status === "正在上线";
return (
<div
<div
id={`role-item-${role.id}`}
key={role.id}
key={role.id}
className={`flex items-center justify-between p-2 rounded-xl transition ${
isActive ? "bg-slate-50 border border-slate-100" : "opacity-60"
isActive
? "bg-slate-50 border border-slate-100"
: isBootingUp
? "bg-amber-50/60 border border-amber-100"
: "opacity-60"
}`}
>
<div className="flex items-center gap-2.5">
@@ -1229,18 +1357,22 @@ export default function RoundtablePlanningPage() {
</span>
) : role.status === "已完成" ? (
<Check className="h-3.5 w-3.5 text-emerald-500 font-bold" />
) : isBootingUp ? (
<Loader2 className="h-3 w-3 text-amber-500 animate-spin" />
) : (
<span className="h-1.5 w-1.5 rounded-full bg-slate-300" />
)}
<span className={`text-[10px] font-semibold ${
isOngoing
? "text-[var(--brand-900)]"
isOngoing
? "text-[var(--brand-900)]"
: role.status === "在线"
? "text-emerald-600 font-bold"
: role.status === "已完成"
? "text-emerald-600"
: "text-slate-400"
: isBootingUp
? "text-amber-600"
: "text-slate-400"
}`}>
{role.status}
</span>
@@ -1608,23 +1740,33 @@ export default function RoundtablePlanningPage() {
>极速</button>
</div>
{isRoundTableRunning ? (
<button
{/* Suspend / resume button visibility:
- Show "挂起协商" only while a run is actually in flight
(isOrchestrating + still nominally running). When the
discussion finishes naturally there's nothing to
suspend, so the button hides; it'll come back the
next time orchestration starts (user "干预研审" or
clarification reply).
- Show "恢复会商" any time the user has explicitly paused
(!isRoundTableRunning) regardless of orchestration
state, so they can always recover from a manual pause. */}
{isRoundTableRunning && isOrchestrating ? (
<button
onClick={() => { setIsRoundTableRunning(false); triggerToast("⏸️ 会商模拟暂挂!智能体挂起..."); }}
className="bg-amber-100 hover:bg-amber-200 text-amber-800 text-[10px] font-bold px-2.5 py-1 rounded-xl flex items-center gap-1 transition border border-amber-200/40 shadow-sm"
>
<Pause className="w-3.5 h-3.5" />
挂起协商
</button>
) : (
<button
) : !isRoundTableRunning ? (
<button
onClick={() => { setIsRoundTableRunning(true); triggerToast("▶️ 恢复大模型并行博弈与推理决策"); }}
className="bg-emerald-100 hover:bg-emerald-200 text-emerald-800 text-[10px] font-bold px-2.5 py-1 rounded-xl flex items-center gap-1 transition border border-emerald-200/40 shadow-sm"
>
<Play className="w-3.5 h-3.5 fill-emerald-800" />
恢复会商
</button>
)}
) : null}
</div>
</div>
@@ -1995,17 +2137,165 @@ export default function RoundtablePlanningPage() {
</div>
<span className="text-[10px] text-slate-400 font-mono font-medium">{msg.time}</span>
</div>
<p className="text-xs text-slate-700 leading-relaxed font-medium whitespace-pre-wrap break-words">
{msg.content}
<div className="text-xs text-slate-700 leading-relaxed font-medium break-words roundtable-md">
{/* Pre-streaming hint — when the bubble is created but
no AI token has arrived yet, show a small spinner
+ the current phase label so the user can see the
backend is doing something (调度中 → 已连接 →
正在派活/调用工具 → 流式输出). */}
{msg.streaming && !msg.content && (
<div className="flex items-center gap-2 py-1 text-[11px] text-slate-500 italic">
<Loader2 className="w-3 h-3 animate-spin text-primary" />
<span>{msg.confidence}</span>
</div>
)}
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
// Compact spacing for chat bubbles — react-markdown's
// default block margins are too tall for this UI.
p: ({ children }) => <p className="my-1 whitespace-pre-wrap">{children}</p>,
ul: ({ children }) => <ul className="list-disc pl-5 my-1 space-y-0.5">{children}</ul>,
ol: ({ children }) => <ol className="list-decimal pl-5 my-1 space-y-0.5">{children}</ol>,
li: ({ children }) => <li className="leading-snug">{children}</li>,
h1: ({ children }) => <h1 className="text-sm font-bold mt-2 mb-1">{children}</h1>,
h2: ({ children }) => <h2 className="text-sm font-bold mt-2 mb-1">{children}</h2>,
h3: ({ children }) => <h3 className="text-xs font-bold mt-1.5 mb-0.5">{children}</h3>,
h4: ({ children }) => <h4 className="text-xs font-bold mt-1 mb-0.5">{children}</h4>,
strong: ({ children }) => <strong className="font-bold text-slate-900">{children}</strong>,
em: ({ children }) => <em className="italic">{children}</em>,
code: ({ children, className }) => {
const isBlock = /language-/.test(className ?? "");
if (isBlock) {
return (
<pre className="bg-slate-100 text-slate-800 rounded-md p-2 my-1 overflow-x-auto text-[11px] font-mono">
<code>{children}</code>
</pre>
);
}
return (
<code className="bg-slate-100 text-slate-800 rounded px-1 py-0.5 text-[11px] font-mono">
{children}
</code>
);
},
blockquote: ({ children }) => (
<blockquote className="border-l-2 border-slate-300 pl-2 my-1 text-slate-600 italic">
{children}
</blockquote>
),
a: ({ children, href }) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="text-primary underline hover:opacity-80"
>
{children}
</a>
),
table: ({ children }) => (
<table className="my-1 border-collapse text-[11px]">{children}</table>
),
th: ({ children }) => (
<th className="border border-slate-300 px-1.5 py-0.5 bg-slate-50 font-bold text-left">
{children}
</th>
),
td: ({ children }) => (
<td className="border border-slate-200 px-1.5 py-0.5">{children}</td>
),
hr: () => <hr className="my-2 border-slate-200" />,
}}
>
{msg.content ?? ""}
</ReactMarkdown>
{msg.streaming && (
<span className="inline-block w-1 h-3 ml-0.5 bg-primary align-middle animate-pulse" />
)}
</p>
</div>
</div>
</div>
))}
</div>
{/* Clarification card — shown only when the coordinator asked
the user a question via ask_clarification. The auto-loop is
paused; replying resumes orchestration on the same threads. */}
{pendingClarification && (
<div className="shrink-0 px-4 pt-3 bg-amber-50 border-t-2 border-amber-300">
<div className="flex items-start gap-3">
<HelpCircle className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
<div className="flex-1 space-y-2">
<div className="flex items-center gap-2">
<span className="text-xs font-extrabold text-amber-900">
总控协调需要澄清
</span>
{pendingClarification.clarificationType && (
<span className="text-[9px] font-mono font-black uppercase text-amber-900 bg-amber-200 border border-amber-300 px-1.5 py-0.2 rounded">
{pendingClarification.clarificationType}
</span>
)}
</div>
{pendingClarification.context && (
<p className="text-[11px] text-amber-800 italic">
{pendingClarification.context}
</p>
)}
<p className="text-xs text-slate-800 leading-relaxed font-medium whitespace-pre-wrap break-words">
{pendingClarification.question}
</p>
{Array.isArray(pendingClarification.options) &&
pendingClarification.options.length > 0 && (
<div className="flex flex-wrap gap-2 pt-1">
{pendingClarification.options.map((opt, idx) => {
const label =
typeof opt === "string"
? opt
: opt && typeof opt === "object" && "label" in (opt as object)
? String((opt as { label: unknown }).label ?? "")
: String(opt ?? "");
if (!label) return null;
return (
<button
key={`${idx}-${label}`}
type="button"
onClick={() => submitClarification(label)}
className="text-[11px] font-bold px-3 py-1.5 rounded-full bg-white border border-amber-300 text-amber-900 hover:bg-amber-100 transition active:scale-95 cursor-pointer"
>
{label}
</button>
);
})}
</div>
)}
{pendingClarification.allowCustom !== false && (
<div className="flex items-center gap-2 pt-1">
<input
type="text"
value={clarificationDraft}
onChange={(e) => setClarificationDraft(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") submitClarification(clarificationDraft);
}}
placeholder="输入你的回答…"
className="flex-1 bg-white border border-amber-300 rounded-xl px-3 py-2 text-xs focus:outline-none focus:border-amber-500 focus:ring-1 focus:ring-amber-200 font-medium"
/>
<button
type="button"
onClick={() => submitClarification(clarificationDraft)}
disabled={!clarificationDraft.trim()}
className="bg-amber-600 hover:bg-amber-700 disabled:bg-slate-300 disabled:cursor-not-allowed text-white text-xs font-bold px-4 py-2 rounded-xl transition shrink-0 cursor-pointer active:scale-95 shadow-md shadow-amber-500/10"
>
回复
</button>
</div>
)}
</div>
</div>
</div>
)}
{/* Console control / Custom injection bar */}
<div className="shrink-0 p-4 bg-slate-50 border-t border-slate-100 flex items-center gap-3">
<input
File diff suppressed because it is too large. Load diff
@@ -21,6 +21,7 @@ import asyncio
import json
import logging
import re
import time
from typing import Any, AsyncIterator
import httpx
@@ -127,14 +128,68 @@ async def _broadcast(
_TOOL_CALL_PATTERN = re.compile(r'\.py\s+"([^"]*)"\s+"([^"]*)"')
# Mirrors the upstream agent-id validator in the LangGraph runtime. Reject early
# so callers get a clear 400 here instead of a stream that aborts with HTTP 422
# from the loopback /api/threads/.../runs/stream endpoint.
_AGENT_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]+$")
def _extract_name_task(text: str) -> tuple[str, str]:
match = _TOOL_CALL_PATTERN.search(text)
def _validate_agent_id(value: str, field: str) -> str:
if not _AGENT_ID_PATTERN.match(value):
raise HTTPException(
status_code=400,
detail=(
f"{field} '{value}' is not a valid agent id. "
f"Must match ^[A-Za-z0-9_-]+$ (use the hyphen-case id, not the display name)."
),
)
return value
def _extract_dispatch(tool_call: dict[str, Any]) -> tuple[str, str]:
"""Return ``(agent_name, task)`` parsed from a leader tool call.
Two formats are accepted:
1. **Structured** — ``{"name": "agent_orchestration", "args": {"agent_name": ..., "task": ...}}``.
This is what most LLMs emit when told to "use the agent_orchestration skill",
and the form the roundtable coordinator's SOUL prompt elicits.
2. **Legacy bash argv** — ``{"name": "bash", "args": {"command": '... .py "X" "Y" ...'}}``.
Kept as a fallback so older skills that dispatch through bash still work.
"""
name = (tool_call.get("name") or "").lower()
args = tool_call.get("args")
# Format 1: structured agent_orchestration call.
if name == "agent_orchestration" and isinstance(args, dict):
agent_name = str(args.get("agent_name") or "").strip()
task = str(args.get("task") or "").strip()
if agent_name and task:
return _strip_display_suffix(agent_name), task
# Format 2: legacy bash + .py "X" "Y".
match = _TOOL_CALL_PATTERN.search(str(args))
if match:
return match.group(1), match.group(2)
return _strip_display_suffix(match.group(1)), match.group(2)
return "", ""
def _strip_display_suffix(agent_name: str) -> str:
"""Tolerate models that paste the human-readable suffix into agent_name.
The coordinator SOUL lists each seat as ``agent_name: roundtable-x(中文):...``
so weaker models sometimes echo the entire ``roundtable-x(中文)`` label
back as the dispatch target. Strip everything from the first non-id
character onward so we recover the bare hyphen-case id; if what's left
still violates the id pattern we hand it back untouched and let the
upstream validator reject it.
"""
head = re.split(r"[^A-Za-z0-9_-]", agent_name, maxsplit=1)[0]
return head or agent_name
def _flatten_content(content: Any) -> str:
if isinstance(content, str):
return content
@@ -195,7 +250,22 @@ def _run_payload(
@router.post("/init")
async def init_multi_agent(request: Request, payload: dict = Body(...)) -> dict[str, Any]:
async def init_multi_agent(request: Request, payload: dict = Body(...)) -> StreamingResponse:
"""Streaming /init.
Emits text/event-stream frames as each seat/coordinator becomes ready, so
the frontend can visualize the parallel bring-up rather than blocking on
one bulk JSON. Frames:
data: {"event":"seat_ready","agent_name":"...","thread_id":"...","display_name":"...","description":"..."}
data: {"event":"coordinator_ready","main_agent_name":"...","thread_id":"..."}
data: {"event":"init_done","main_agent_name":"...","thread_ids":{...}}
data: {"event":"error","detail":"..."} # on failure
Validation errors (missing agent_names, bad id pattern) still raise
HTTPException synchronously so the connection fails fast with a real 4xx
instead of a 200 SSE that just carries an error frame.
"""
agent_names_in = payload.get("agent_names") or []
agent_names: list[str] = [str(name).lower() for name in agent_names_in]
main_agent_name: str = str(payload.get("main_agent_name") or "main-agent").lower()
@@ -204,61 +274,112 @@ async def init_multi_agent(request: Request, payload: dict = Body(...)) -> dict[
if not agent_names:
raise HTTPException(status_code=400, detail="agent_names is required")
for name in agent_names:
_validate_agent_id(name, "agent_names entry")
_validate_agent_id(main_agent_name, "main_agent_name")
base = _loopback_base(request)
headers = _auth_headers(request)
async with httpx.AsyncClient(timeout=None) as client:
# Optimization A: fetch every sub-agent's metadata and create its
# thread concurrently. With 6 seats this turns ~12 sequential
# loopback round-trips into one wave of 12 parallel ones.
async def _prepare_sub_agent(sub_id: str) -> tuple[str, str, str]:
info = await _get_agent(client, base, headers, sub_id)
sub_name = str(info["name"]).lower()
thread_id = await _create_thread(client, base, headers)
return sub_name, thread_id, info.get("description", "") or ""
def _frame(payload: dict[str, Any]) -> str:
return f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
prepared = await asyncio.gather(*(_prepare_sub_agent(sid) for sid in agent_names))
async def stream_generator() -> AsyncIterator[str]:
async with httpx.AsyncClient(timeout=None) as client:
try:
# Per-seat prepare task — same body as before, but we no longer
# await them with asyncio.gather. Instead, asyncio.as_completed
# surfaces them in REAL completion order so we can emit a
# seat_ready frame the moment each finishes.
async def _prepare_sub_agent(sub_id: str) -> tuple[str, str, str, str]:
info = await _get_agent(client, base, headers, sub_id)
display_name = str(info.get("name") or sub_id)
description = info.get("description", "") or ""
thread_id = await _create_thread(client, base, headers)
return sub_id.lower(), thread_id, display_name, description
agent_threads: dict[str, str] = {sub_name: thread_id for sub_name, thread_id, _ in prepared}
descriptions: list[str] = [
f"agent_name:{sub_name},{description}" for sub_name, _, description in prepared
]
tasks = [asyncio.create_task(_prepare_sub_agent(sid)) for sid in agent_names]
prepared: list[tuple[str, str, str, str]] = []
try:
for fut in asyncio.as_completed(tasks):
sid, thread_id, display_name, description = await fut
prepared.append((sid, thread_id, display_name, description))
yield _frame({
"event": "seat_ready",
"agent_name": sid,
"thread_id": thread_id,
"display_name": display_name,
"description": description,
})
except Exception:
# Cancel any still-pending sibling so we don't leak tasks
# past the AsyncClient close.
for t in tasks:
if not t.done():
t.cancel()
raise
soul = (
"#核心定位:\n"
"用途:根据用户的任务目标,调用其他智能体干活,只负责协调汇总,"
"当你觉得已有信息支持回答用户问题时候,停止调度,并输出最后的结果\n"
"#调度方法:\n"
"通过使用agent_orchestration技能调用其他智能体干活,使用技能时传入agent_name和需要做的活\n"
"#可调度智能体:\n"
f"{descriptions}"
)
agent_threads: dict[str, str] = {sid: thread_id for sid, thread_id, _, _ in prepared}
descriptions: list[str] = [
f"- agent_name: {sid}({display_name}):{description}"
for sid, _, display_name, description in prepared
]
agent_threads[main_agent_name] = await _create_thread(client, base, headers)
ok, result = await _create_agent(
client,
base,
headers,
name=main_agent_name,
description="你是总控控制协调智能体,负责协调其他智能体干活",
model=model,
skills=["agent_orchestration"],
soul=soul,
agent_id=main_agent_name,
)
if not ok and isinstance(result, str) and "exists" not in result:
raise HTTPException(status_code=500, detail=f"create coordinator agent failed: {result}")
soul = (
"#核心定位:\n"
"用途:根据用户的任务目标,调用其他智能体干活,只负责协调汇总,"
"当你觉得已有信息支持回答用户问题时候,停止调度,并输出最后的结果\n"
"#调度方法:\n"
"通过使用agent_orchestration技能调用其他智能体干活,使用技能时传入agent_name和需要做的活。"
"注意:agent_name 必须严格使用下方列出的英文 id(例如 roundtable-intelligence),"
"不要把括号里的中文显示名拼进 agent_name 里。\n"
"#可调度智能体:\n"
+ "\n".join(descriptions)
)
# Optimization C: the sub-agent SOULs already declare "你只在被点到时
# 发言,由总控协调智能体派活", so the old intro broadcast (one human
# message per thread, 6 × GET-state + 6 × POST-state) is redundant.
# Skipping it removes the slowest stage of init entirely.
coord_thread = await _create_thread(client, base, headers)
agent_threads[main_agent_name] = coord_thread
ok, result = await _create_agent(
client,
base,
headers,
name=main_agent_name,
description="你是总控控制协调智能体,负责协调其他智能体干活",
model=model,
skills=["agent_orchestration"],
soul=soul,
agent_id=main_agent_name,
)
if not ok and isinstance(result, str) and "exists" not in result:
yield _frame({"event": "error", "detail": f"create coordinator agent failed: {result}"})
return
return {
"status": "success",
"main_agent_name": main_agent_name,
"thread_ids": agent_threads,
}
yield _frame({
"event": "coordinator_ready",
"main_agent_name": main_agent_name,
"thread_id": coord_thread,
})
yield _frame({
"event": "init_done",
"main_agent_name": main_agent_name,
"thread_ids": agent_threads,
})
except HTTPException as exc:
logger.warning("multi-agent init HTTPException: %s %s", exc.status_code, exc.detail)
yield _frame({"event": "error", "detail": f"[{exc.status_code}] {exc.detail}"})
except Exception as exc:
logger.exception("multi-agent init unexpected failure")
yield _frame({"event": "error", "detail": f"unexpected: {exc!r}"})
return StreamingResponse(
stream_generator(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
"Connection": "keep-alive",
},
)
# ---------------------------------------------------------------------------
@@ -277,37 +398,112 @@ async def _stream_upstream(
url = f"{base}/api/threads/{thread_id}/runs/stream"
captured: list[str] = []
t_send = time.perf_counter()
async with client.stream("POST", url, json=body, headers=headers) as resp:
t_headers = time.perf_counter()
if resp.status_code != 200:
body_text = (await resp.aread()).decode("utf-8", "replace")[:1000]
raise HTTPException(
status_code=502,
detail=f"upstream {url} returned HTTP {resp.status_code}: {body_text}",
)
agent_for_log = (body.get("context") or {}).get("agent_name") or thread_id
logger.info(
"[upstream-timing] %s headers_in=%.3fs",
agent_for_log,
t_headers - t_send,
)
first_line = True
async for line in resp.aiter_lines():
if not line:
continue
if first_line:
first_line = False
logger.info(
"[upstream-timing] %s first_line=%.3fs (LangGraph runtime startup + LLM first byte)",
agent_for_log,
time.perf_counter() - t_headers,
)
yield line, None
if line.startswith("data:"):
captured.append(line)
yield None, captured
def _parse_last_message(captured: list[str]) -> dict[str, Any]:
if len(captured) < 2:
raise HTTPException(
status_code=502,
detail=f"upstream produced no usable data lines (got {len(captured)})",
)
raw = captured[-2][5:].strip()
try:
parsed = json.loads(raw)
except json.JSONDecodeError as exc:
raise HTTPException(status_code=502, detail=f"failed to parse upstream frame: {exc}") from exc
messages = parsed.get("messages") or []
if not messages:
return {}
return messages[-1] or {}
def _parse_last_messages(captured: list[str]) -> list[dict[str, Any]]:
"""Return the full ``messages`` list from the most recent ``values`` snapshot.
The upstream sends the final ``values`` frame second-to-last (the very last
line is the ``end`` event with ``null``). We tolerate trailing non-JSON
frames (e.g. ``data: null``) by scanning backwards for the first frame that
successfully parses as a state object with a ``messages`` key.
"""
if not captured:
raise HTTPException(status_code=502, detail="upstream produced no data lines")
for raw_line in reversed(captured):
raw = raw_line[5:].strip() if raw_line.startswith("data:") else raw_line.strip()
if not raw or raw == "null":
continue
try:
parsed = json.loads(raw)
except json.JSONDecodeError:
continue
if isinstance(parsed, dict) and "messages" in parsed:
messages = parsed.get("messages") or []
if isinstance(messages, list):
return messages
raise HTTPException(status_code=502, detail="no parseable values frame in upstream stream")
def _find_trailing_ai_text(messages: list[dict[str, Any]], *, exclude_text: str = "") -> str:
"""Return the text content of the latest AIMessage with no tool_calls.
Used by the clarification branch: ClarificationMiddleware ends the run
after emitting the structured ToolMessage, but the model often produces a
richer human-readable follow-up AIMessage right before the interrupt.
Prefer that text over the brief preamble that accompanied the
ask_clarification tool_call. When the only trailing AIMessage matches the
preamble (``exclude_text``), return empty so the caller can fall back to
the question itself.
"""
for msg in reversed(messages):
if not isinstance(msg, dict):
continue
if (msg.get("type") or "").lower() not in ("ai", "aimessage", "aimessagechunk"):
continue
if msg.get("tool_calls"):
continue
text = _flatten_content(msg.get("content", "")).strip()
if not text:
continue
if exclude_text and text == exclude_text.strip():
continue
return text
return ""
def _find_dispatch_message(messages: list[dict[str, Any]]) -> dict[str, Any]:
"""Locate the latest AIMessage that carries dispatch tool calls.
The very last message can be a skill-stop ToolMessage injected by
``SkillStopMiddleware`` when it intercepts the dispatch, or a clarification
ToolMessage. Either way, the actionable tool_calls live on the AIMessage
*before* it, so we walk backwards until we hit one with ``tool_calls``.
Returns the latest AIMessage when none of them carry tool_calls so the
caller can still extract its text content.
"""
last_ai: dict[str, Any] = {}
for msg in reversed(messages):
if not isinstance(msg, dict):
continue
if (msg.get("type") or "").lower() in ("ai", "aimessage", "aimessagechunk"):
if not last_ai:
last_ai = msg
if msg.get("tool_calls"):
return msg
return last_ai
async def _leader_run(
@@ -320,48 +516,144 @@ async def _leader_run(
skill_stop_names: list[str],
model_name: str,
) -> AsyncIterator[str | dict[str, Any]]:
await _broadcast(client, base, headers, "总控智能体收到用户消息:" + new_message, agent_threads, [agent_name])
# Fine-grained timing for the leader path. The user reported the
# coordinator "stalls before output" — we log each major await so we can
# see whether the wait is in pre-broadcast scheduling, upstream stream
# setup, first-token latency, or post-stream parsing.
t0 = time.perf_counter()
pre_broadcast_task = asyncio.create_task(
_broadcast(client, base, headers, "总控智能体收到用户消息:" + new_message, agent_threads, [agent_name])
)
thread_id = agent_threads[agent_name]
effective_stop_names = list({"agent_orchestration", *skill_stop_names})
body = _run_payload(
agent_name=agent_name,
model_name=model_name,
thread_id=thread_id,
new_message=new_message,
excluded_tools=["web_search"],
skill_stop_names=skill_stop_names,
skill_stop_names=effective_stop_names,
)
t_payload_built = time.perf_counter()
logger.info(
"[leader-timing] %s payload_built=%.3fs (broadcast running in background)",
agent_name,
t_payload_built - t0,
)
captured: list[str] = []
first_line_ts: float | None = None
line_count = 0
async for line, done in _stream_upstream(client, base, headers, thread_id, body):
if line is not None:
if first_line_ts is None:
first_line_ts = time.perf_counter()
logger.info(
"[leader-timing] %s first_upstream_line=%.3fs since_entry=%.3fs",
agent_name,
first_line_ts - t_payload_built,
first_line_ts - t0,
)
line_count += 1
yield line
elif done is not None:
captured = done
t_stream_done = time.perf_counter()
logger.info(
"[leader-timing] %s upstream_stream_complete=%.3fs lines=%d since_entry=%.3fs",
agent_name,
t_stream_done - (first_line_ts or t_payload_built),
line_count,
t_stream_done - t0,
)
messages = _parse_last_messages(captured)
# Pre-broadcast must finish before we yield the final status frame on any
# branch — the seats that get dispatched next rely on having seen the
# original user message. The upstream LLM stream usually takes several
# seconds, so the broadcast has typically already completed and this
# await is effectively free.
t_before_broadcast_await = time.perf_counter()
try:
await pre_broadcast_task
except Exception as exc:
logger.warning("pre-broadcast failed: %s", exc)
t_after_broadcast_await = time.perf_counter()
logger.info(
"[leader-timing] %s pre_broadcast_await=%.3fs (>0 means broadcast was still in flight after LLM finished)",
agent_name,
t_after_broadcast_await - t_before_broadcast_await,
)
dispatch_msg = _find_dispatch_message(messages)
leader_text = _flatten_content(dispatch_msg.get("content", ""))
tool_calls = dispatch_msg.get("tool_calls") or []
# Clarification branch: the coordinator called ask_clarification. The
# ClarificationMiddleware intercepts with Command(goto=END) and stashes
# the structured question in the tool_call args; sometimes a richer
# follow-up AIMessage is also produced after the ToolMessage. Emit a
# dedicated frame so the frontend can render it as a question instead of
# treating an empty dispatch as "consensus reached".
clarification_call = next(
(tc for tc in tool_calls if (tc.get("name") or "").lower() == "ask_clarification"),
None,
)
if clarification_call is not None:
cargs = clarification_call.get("args") or {}
question = str(cargs.get("question") or "").strip()
follow_up = _find_trailing_ai_text(messages, exclude_text=leader_text)
yield {
"status": "clarification",
"content": follow_up or question or leader_text,
"question": question,
"clarification_type": str(cargs.get("clarification_type") or ""),
"clarification_context": str(cargs.get("context") or ""),
"options": cargs.get("options") or [],
"allow_custom": bool(cargs.get("allow_custom", True)),
"agent_name": agent_name,
}
return
last_msg = _parse_last_message(captured)
leader_text = _flatten_content(last_msg.get("content", ""))
tool_calls = last_msg.get("tool_calls") or []
if not tool_calls:
yield {"status": [], "content": leader_text, "agent_name": agent_name}
return
# Run all dispatch broadcasts concurrently — they target disjoint thread
# sets and we don't care about ordering, so serializing them just
# multiplied latency before the frontend could start a sub-agent run.
dispatched: list[list[str]] = []
dispatch_broadcasts: list[asyncio.Task[None]] = []
for tool_call in tool_calls:
try:
sub_name, task_text = _extract_name_task(str(tool_call.get("args", "")))
sub_name, task_text = _extract_dispatch(tool_call)
if not sub_name:
continue
await _broadcast(
client,
base,
headers,
f"总控智能体给{sub_name}派活,内容为:" + task_text,
agent_threads,
[agent_name, sub_name],
dispatch_broadcasts.append(
asyncio.create_task(
_broadcast(
client,
base,
headers,
f"总控智能体给{sub_name}派活,内容为:" + task_text,
agent_threads,
[agent_name, sub_name],
)
)
)
dispatched.append([sub_name, task_text])
except Exception as exc:
logger.warning("dispatch failed: %s", exc)
if dispatch_broadcasts:
t_before_dispatch = time.perf_counter()
await asyncio.gather(*dispatch_broadcasts, return_exceptions=True)
logger.info(
"[leader-timing] %s dispatch_broadcasts_await=%.3fs count=%d (gates how soon the next sub agent can start)",
agent_name,
time.perf_counter() - t_before_dispatch,
len(dispatch_broadcasts),
)
yield {"status": dispatched, "content": leader_text, "agent_name": agent_name}
@@ -375,38 +667,90 @@ async def _special_run(
skill_stop_names: list[str],
model_name: str,
) -> AsyncIterator[str | dict[str, Any]]:
# Same instrumentation as _leader_run so all 6 seats are diagnosable.
t0 = time.perf_counter()
thread_id = agent_threads[agent_name]
body = _run_payload(
agent_name=agent_name,
model_name=model_name,
thread_id=thread_id,
new_message=new_message,
excluded_tools=["web_search", "ask_clarification", "present_files", "view_image"],
excluded_tools=["web_search", "ask_clarification", "present_files", "view_image", "agent_orchestration"],
skill_stop_names=skill_stop_names,
)
t_payload_built = time.perf_counter()
logger.info(
"[special-timing] %s payload_built=%.3fs",
agent_name,
t_payload_built - t0,
)
captured: list[str] = []
first_line_ts: float | None = None
line_count = 0
async for line, done in _stream_upstream(client, base, headers, thread_id, body):
if line is not None:
if first_line_ts is None:
first_line_ts = time.perf_counter()
logger.info(
"[special-timing] %s first_upstream_line=%.3fs since_entry=%.3fs",
agent_name,
first_line_ts - t_payload_built,
first_line_ts - t0,
)
line_count += 1
yield line
elif done is not None:
captured = done
t_stream_done = time.perf_counter()
logger.info(
"[special-timing] %s upstream_stream_complete=%.3fs lines=%d since_entry=%.3fs",
agent_name,
t_stream_done - (first_line_ts or t_payload_built),
line_count,
t_stream_done - t0,
)
last_msg = _parse_last_message(captured)
messages = _parse_last_messages(captured)
# Sub-agent's final deliverable is the latest AIMessage's text content.
last_msg: dict[str, Any] = {}
for msg in reversed(messages):
if isinstance(msg, dict) and (msg.get("type") or "").lower() in ("ai", "aimessage", "aimessagechunk"):
last_msg = msg
break
content = _flatten_content(last_msg.get("content", ""))
await _broadcast(
client,
base,
headers,
f"子智能体{agent_name}完成{new_message}工作,交付内容为:" + content,
agent_threads,
[agent_name],
# Kick the "子智能体完成" broadcast off in the background and yield the
# status frame first — the next leader cycle that depends on this
# broadcast having landed only starts after a fresh /run/stream request
# from the frontend, which is many seconds away (full leader LLM call).
# Awaiting after the yield ensures the task finishes before the AsyncClient
# closes; the SSE connection stays open a bit longer but the frontend has
# already consumed the result.
t_before_post = time.perf_counter()
post_broadcast_task = asyncio.create_task(
_broadcast(
client,
base,
headers,
f"子智能体{agent_name}完成{new_message}工作,交付内容为:" + content,
agent_threads,
[agent_name],
)
)
yield {
"status": f"子智能体{agent_name}完成{new_message}工作",
"content": content,
"agent_name": agent_name,
}
try:
await post_broadcast_task
except Exception as exc:
logger.warning("post-broadcast failed: %s", exc)
logger.info(
"[special-timing] %s post_broadcast_total=%.3fs (broadcast time after yield — keeps SSE open this long)",
agent_name,
time.perf_counter() - t_before_post,
)
@router.post("/run/stream")
@@ -423,6 +767,9 @@ async def run_multi_agent_stream(request: Request, payload: dict = Body(...)) ->
status_code=400,
detail=f"agent_name '{agent_name}' is missing from d_agent_thread_id",
)
_validate_agent_id(agent_name, "agent_name")
for key in agent_threads:
_validate_agent_id(key, "d_agent_thread_id key")
base = _loopback_base(request)
headers = _auth_headers(request)
+2 -5
View File
@@ -132,11 +132,8 @@ agents_api:
enabled: true
database:
# TODO: 临时切到 SQLite,等 192.168.0.123 MySQL 恢复后改回 backend: mysql
backend: sqlite
sqlite_dir: .deer-flow/data
# backend: mysql
# mysql_url: $MYSQL_DATABASE_URL
backend: mysql
mysql_url: $MYSQL_DATABASE_URL
pool_size: 10
echo_sql: false
@@ -78,21 +78,35 @@ class SkillStopMiddleware(AgentMiddleware[SkillStopMiddlewareState]):
"""Return the matched skill name if this tool call invokes one of the
configured target skills, otherwise ``None``.
Matching today is bash-based: the lead agent reaches into a skill by
running ``python3 scripts/agent_orchestration.py "<skill-name>" "<task>"``,
so we only look at ``bash`` calls and extract the skill name from the
argv-style quoted form.
Two invocation patterns are recognized:
1. **Structured tool call** — the model emits a tool call whose ``name``
literally matches one of the configured skill names (e.g. the
multi-agent leader calling
``agent_orchestration({"agent_name": "...", "task": "..."})``).
This is the natural form most LLMs produce when told to "use a
skill", and it's the form that drives the roundtable dispatch.
2. **Bash + argv** — the legacy form where the lead agent reaches into
a skill by running ``python3 scripts/<skill-name>.py "<arg1>" "<arg2>"``.
Kept for backward compatibility with skills that use bash for
dispatch.
"""
name = tool_call.get("name") or ""
skill_name = ""
lowered_targets = {s.lower() for s in self._skill_names}
# Pattern 1: structured tool call whose tool name IS the skill name.
if name and (name in self._skill_names or name.lower() in lowered_targets):
logger.info("SkillStopMiddleware: matched structured skill call name=%s", name)
return name
# Pattern 2: bash invocation with argv-style skill name.
if name == "bash" and "args" in tool_call:
skill_name, _ = extract_name_task(str(tool_call["args"]))
if not skill_name:
return None
lowered_targets = {s.lower() for s in self._skill_names}
if skill_name in self._skill_names or skill_name.lower() in lowered_targets:
logger.info("SkillStopMiddleware: matched skill_name=%s", skill_name)
return skill_name
if skill_name and (skill_name in self._skill_names or skill_name.lower() in lowered_targets):
logger.info("SkillStopMiddleware: matched bash skill call name=%s", skill_name)
return skill_name
return None
def _handle_skill_stop(self, request: ToolCallRequest, skill_name: str) -> Command:
@@ -1,3 +1,4 @@
from .agent_orchestration_tool import agent_orchestration_tool
from .clarification_tool import ask_clarification_tool
from .memory_tool import memory_tool
from .present_file_tool import present_file_tool
@@ -10,6 +11,7 @@ __all__ = [
"setup_agent",
"present_file_tool",
"ask_clarification_tool",
"agent_orchestration_tool",
"memory_tool",
"scheduled_task_tool",
"view_image_tool",
@@ -0,0 +1,46 @@
"""Placeholder tool that lets the roundtable leader agent dispatch sub-agents.
Background
----------
The multi-agent ``/api/multi-agent/run/stream`` endpoint (``app.gateway.routers.multi_agent``)
drives a *leader* agent whose only job is to delegate work to seat-specific sub-agents.
Its SOUL.md tells the model to "use the agent_orchestration skill, passing agent_name
and task". With no real tool registered under that name, the model used to emit
``{"name": "agent_orchestration", "args": {"agent_name": ..., "task": ...}}`` calls
that the runtime rejected with "agent_orchestration is not a valid tool", driving it
into an unproductive retry loop (a single leader call could exceed 10 000 SSE frames).
This module exposes a no-op tool with the exact signature the leader naturally
emits. The actual dispatch is handled by ``SkillStopMiddleware`` which intercepts
the call, halts the run, and lets ``multi_agent.py`` parse the AIMessage's
``tool_calls`` to extract ``(agent_name, task)`` pairs for the frontend.
"""
from langchain.tools import tool
@tool("agent_orchestration", parse_docstring=True)
def agent_orchestration_tool(agent_name: str, task: str) -> str:
"""Delegate a task to a named sub-agent in the multi-agent roundtable.
Use this tool ONLY when you are the roundtable coordinator and need to
dispatch work to one of the configured seat agents. Each call delegates
ONE task to ONE agent. Emit multiple calls in the same turn to dispatch
several seats in a single round of the discussion.
The call is intercepted by the runtime — you do not get a real result
back synchronously. The orchestrator will collect every dispatched
``(agent_name, task)`` pair, run the corresponding sub-agents, then
re-prompt you with a synthesis instruction once they finish.
Args:
agent_name: Name of the sub-agent to dispatch. MUST exactly match one of
the names listed in your SOUL prompt's ``可调度智能体`` section.
task: The instruction to hand to that sub-agent. Be specific — the
sub-agent only sees this string (plus its own SOUL prompt), not the
broader conversation context.
"""
# Real handling is in SkillStopMiddleware + the multi_agent router. This
# body only runs as a fallback if the middleware is not configured; it
# returns a placeholder so the model does not see a "not a valid tool" error.
return f"dispatched:{agent_name}"
@@ -6,7 +6,7 @@ from deerflow.config import get_app_config
from deerflow.config.app_config import AppConfig
from deerflow.reflection import resolve_variable
from deerflow.sandbox.security import is_host_bash_allowed
from deerflow.tools.builtins import ask_clarification_tool, memory_tool, present_file_tool, scheduled_task_tool, task_tool, view_image_tool
from deerflow.tools.builtins import agent_orchestration_tool, ask_clarification_tool, memory_tool, present_file_tool, scheduled_task_tool, task_tool, view_image_tool
from deerflow.tools.builtins.tool_search import reset_deferred_registry
logger = logging.getLogger(__name__)
@@ -14,6 +14,7 @@ logger = logging.getLogger(__name__)
BUILTIN_TOOLS = [
present_file_tool,
ask_clarification_tool,
agent_orchestration_tool,
]
SUBAGENT_TOOLS = [
-10
View File
@@ -1,10 +0,0 @@
window.environment = {
b1ConsumerUrl: "http://127.0.0.1:8001/consumer", // http://10.106.71.194:8013/
// b1ConsumerUrl: "http://localhost:8004/consumer", // http://10.106.71.194:8013/
aImagePath: "https://present.a.sit.4.cn/gene", // https://present.a.uat.4.cn present.a.sit.4.cn
g6ImgUrl: "/src/assets",
mapUrl: "https://present.a.sit.4.cn/tiles/{z}/{x}/{y}.jpg",
b1NodeServerUrl: "https://ch1.b.uat.4.cn",
b5ReplyUrl:"https://pg.b.uat.4.cn"
};
@@ -1,134 +0,0 @@
//! moment.js locale configuration
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var symbolMap = {
'1': '١',
'2': '٢',
'3': '٣',
'4': '٤',
'5': '٥',
'6': '٦',
'7': '٧',
'8': '٨',
'9': '٩',
'0': '٠'
}, numberMap = {
'١': '1',
'٢': '2',
'٣': '3',
'٤': '4',
'٥': '5',
'٦': '6',
'٧': '7',
'٨': '8',
'٩': '9',
'٠': '0'
}, pluralForm = function (n) {
return n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5;
}, plurals = {
s : ['أقل من ثانية', 'ثانية واحدة', ['ثانيتان', 'ثانيتين'], '%d ثوان', '%d ثانية', '%d ثانية'],
m : ['أقل من دقيقة', 'دقيقة واحدة', ['دقيقتان', 'دقيقتين'], '%d دقائق', '%d دقيقة', '%d دقيقة'],
h : ['أقل من ساعة', 'ساعة واحدة', ['ساعتان', 'ساعتين'], '%d ساعات', '%d ساعة', '%d ساعة'],
d : ['أقل من يوم', 'يوم واحد', ['يومان', 'يومين'], '%d أيام', '%d يومًا', '%d يوم'],
M : ['أقل من شهر', 'شهر واحد', ['شهران', 'شهرين'], '%d أشهر', '%d شهرا', '%d شهر'],
y : ['أقل من عام', 'عام واحد', ['عامان', 'عامين'], '%d أعوام', '%d عامًا', '%d عام']
}, pluralize = function (u) {
return function (number, withoutSuffix, string, isFuture) {
var f = pluralForm(number),
str = plurals[u][pluralForm(number)];
if (f === 2) {
str = str[withoutSuffix ? 0 : 1];
}
return str.replace(/%d/i, number);
};
}, months = [
'يناير',
'فبراير',
'مارس',
'أبريل',
'مايو',
'يونيو',
'يوليو',
'أغسطس',
'سبتمبر',
'أكتوبر',
'نوفمبر',
'ديسمبر'
];
var ar = moment.defineLocale('ar', {
months : months,
monthsShort : months,
weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'),
weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'D/\u200FM/\u200FYYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd D MMMM YYYY HH:mm'
},
meridiemParse: /ص|م/,
isPM : function (input) {
return 'م' === input;
},
meridiem : function (hour, minute, isLower) {
if (hour < 12) {
return 'ص';
} else {
return 'م';
}
},
calendar : {
sameDay: '[اليوم عند الساعة] LT',
nextDay: '[غدًا عند الساعة] LT',
nextWeek: 'dddd [عند الساعة] LT',
lastDay: '[أمس عند الساعة] LT',
lastWeek: 'dddd [عند الساعة] LT',
sameElse: 'L'
},
relativeTime : {
future : 'بعد %s',
past : 'منذ %s',
s : pluralize('s'),
ss : pluralize('s'),
m : pluralize('m'),
mm : pluralize('m'),
h : pluralize('h'),
hh : pluralize('h'),
d : pluralize('d'),
dd : pluralize('d'),
M : pluralize('M'),
MM : pluralize('M'),
y : pluralize('y'),
yy : pluralize('y')
},
preparse: function (string) {
return string.replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) {
return numberMap[match];
}).replace(/،/g, ',');
},
postformat: function (string) {
return string.replace(/\d/g, function (match) {
return symbolMap[match];
}).replace(/,/g, '،');
},
week : {
dow : 6, // Saturday is the first day of the week.
doy : 12 // The week that contains Jan 12th is the first week of the year.
}
});
return ar;
})));
@@ -1,67 +0,0 @@
//! moment.js locale configuration
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var sq = moment.defineLocale('sq', {
months : 'Janar_Shkurt_Mars_Prill_Maj_Qershor_Korrik_Gusht_Shtator_Tetor_Nëntor_Dhjetor'.split('_'),
monthsShort : 'Jan_Shk_Mar_Pri_Maj_Qer_Kor_Gus_Sht_Tet_Nën_Dhj'.split('_'),
weekdays : 'E Diel_E Hënë_E Martë_E Mërkurë_E Enjte_E Premte_E Shtunë'.split('_'),
weekdaysShort : 'Die_Hën_Mar_Mër_Enj_Pre_Sht'.split('_'),
weekdaysMin : 'D_H_Ma_Më_E_P_Sh'.split('_'),
weekdaysParseExact : true,
meridiemParse: /PD|MD/,
isPM: function (input) {
return input.charAt(0) === 'M';
},
meridiem : function (hours, minutes, isLower) {
return hours < 12 ? 'PD' : 'MD';
},
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd, D MMMM YYYY HH:mm'
},
calendar : {
sameDay : '[Sot në] LT',
nextDay : '[Nesër në] LT',
nextWeek : 'dddd [në] LT',
lastDay : '[Dje në] LT',
lastWeek : 'dddd [e kaluar në] LT',
sameElse : 'L'
},
relativeTime : {
future : 'në %s',
past : '%s më parë',
s : 'disa sekonda',
ss : '%d sekonda',
m : 'një minutë',
mm : '%d minuta',
h : 'një orë',
hh : '%d orë',
d : 'një ditë',
dd : '%d ditë',
M : 'një muaj',
MM : '%d muaj',
y : 'një vit',
yy : '%d vite'
},
dayOfMonthOrdinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return sq;
})));
@@ -1,93 +0,0 @@
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var suffixes = {
1: '\'inci',
5: '\'inci',
8: '\'inci',
70: '\'inci',
80: '\'inci',
2: '\'nci',
7: '\'nci',
20: '\'nci',
50: '\'nci',
3: '\'üncü',
4: '\'üncü',
100: '\'üncü',
6: '\'ncı',
9: '\'uncu',
10: '\'uncu',
30: '\'uncu',
60: '\'ıncı',
90: '\'ıncı'
};
var tr = moment.defineLocale('tr', {
months : 'Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık'.split('_'),
monthsShort : 'Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara'.split('_'),
weekdays : 'Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi'.split('_'),
weekdaysShort : 'Paz_Pts_Sal_Çar_Per_Cum_Cts'.split('_'),
weekdaysMin : 'Pz_Pt_Sa_Ça_Pe_Cu_Ct'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd, D MMMM YYYY HH:mm'
},
calendar : {
sameDay : '[bugün saat] LT',
nextDay : '[yarın saat] LT',
nextWeek : '[gelecek] dddd [saat] LT',
lastDay : '[dün] LT',
lastWeek : '[geçen] dddd [saat] LT',
sameElse : 'L'
},
relativeTime : {
future : '%s sonra',
past : '%s önce',
s : 'birkaç saniye',
ss : '%d saniye',
m : 'bir dakika',
mm : '%d dakika',
h : 'bir saat',
hh : '%d saat',
d : 'bir gün',
dd : '%d gün',
M : 'bir ay',
MM : '%d ay',
y : 'bir yıl',
yy : '%d yıl'
},
ordinal: function (number, period) {
switch (period) {
case 'd':
case 'D':
case 'Do':
case 'DD':
return number;
default:
if (number === 0) { // special case for zero
return number + '\'ıncı';
}
var a = number % 10,
b = number % 100 - a,
c = number >= 100 ? 100 : null;
return number + (suffixes[a] || suffixes[b] || suffixes[c]);
}
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 7 // The week that contains Jan 7th is the first week of the year.
}
});
return tr;
})));
@@ -1 +0,0 @@
!function(e,a){"object"==typeof exports&&"undefined"!=typeof module?module.exports=a():"function"==typeof define&&define.amd?define(a):e.moment=a()}(this,function(){"use strict";var e,n;function l(){return e.apply(null,arguments)}function _(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function i(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function o(e){return void 0===e}function m(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function u(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function M(e,a){var t,s=[];for(t=0;t<e.length;++t)s.push(a(e[t],t));return s}function h(e,a){return Object.prototype.hasOwnProperty.call(e,a)}function L(e,a){for(var t in a)h(a,t)&&(e[t]=a[t]);return h(a,"toString")&&(e.toString=a.toString),h(a,"valueOf")&&(e.valueOf=a.valueOf),e}function c(e,a,t,s){return Sa(e,a,t,s,!0).utc()}function Y(e){return null==e._pf&&(e._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],meridiem:null,rfc2822:!1,weekdayMismatch:!1}),e._pf}function y(e){if(null==e._isValid){var a=Y(e),t=n.call(a.parsedDateParts,function(e){return null!=e}),s=!isNaN(e._d.getTime())&&a.overflow<0&&!a.empty&&!a.invalidMonth&&!a.invalidWeekday&&!a.weekdayMismatch&&!a.nullInput&&!a.invalidFormat&&!a.userInvalidated&&(!a.meridiem||a.meridiem&&t);if(e._strict&&(s=s&&0===a.charsLeftOver&&0===a.unusedTokens.length&&void 0===a.bigHour),null!=Object.isFrozen&&Object.isFrozen(e))return s;e._isValid=s}return e._isValid}function f(e){var a=c(NaN);return null!=e?L(Y(a),e):Y(a).userInvalidated=!0,a}n=Array.prototype.some?Array.prototype.some:function(e){for(var a=Object(this),t=a.length>>>0,s=0;s<t;s++)if(s in a&&e.call(this,a[s],s,a))return!0;return!1};var d=l.momentProperties=[];function k(e,a){var t,s,n;if(o(a._isAMomentObject)||(e._isAMomentObject=a._isAMomentObject),o(a._i)||(e._i=a._i),o(a._f)||(e._f=a._f),o(a._l)||(e._l=a._l),o(a._strict)||(e._strict=a._strict),o(a._tzm)||(e._tzm=a._tzm),o(a._isUTC)||(e._isUTC=a._isUTC),o(a._offset)||(e._offset=a._offset),o(a._pf)||(e._pf=Y(a)),o(a._locale)||(e._locale=a._locale),0<d.length)for(t=0;t<d.length;t++)o(n=a[s=d[t]])||(e[s]=n);return e}var a=!1;function p(e){k(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===a&&(a=!0,l.updateOffset(this),a=!1)}function D(e){return e instanceof p||null!=e&&null!=e._isAMomentObject}function T(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function g(e){var a=+e,t=0;return 0!==a&&isFinite(a)&&(t=T(a)),t}function r(e,a,t){var s,n=Math.min(e.length,a.length),d=Math.abs(e.length-a.length),r=0;for(s=0;s<n;s++)(t&&e[s]!==a[s]||!t&&g(e[s])!==g(a[s]))&&r++;return r+d}function w(e){!1===l.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+e)}function t(n,d){var r=!0;return L(function(){if(null!=l.deprecationHandler&&l.deprecationHandler(null,n),r){for(var e,a=[],t=0;t<arguments.length;t++){if(e="","object"==typeof arguments[t]){for(var s in e+="\n["+t+"] ",arguments[0])e+=s+": "+arguments[0][s]+", ";e=e.slice(0,-2)}else e=arguments[t];a.push(e)}w(n+"\nArguments: "+Array.prototype.slice.call(a).join("")+"\n"+(new Error).stack),r=!1}return d.apply(this,arguments)},d)}var s,v={};function S(e,a){null!=l.deprecationHandler&&l.deprecationHandler(e,a),v[e]||(w(a),v[e]=!0)}function H(e){return e instanceof Function||"[object Function]"===Object.prototype.toString.call(e)}function b(e,a){var t,s=L({},e);for(t in a)h(a,t)&&(i(e[t])&&i(a[t])?(s[t]={},L(s[t],e[t]),L(s[t],a[t])):null!=a[t]?s[t]=a[t]:delete s[t]);for(t in e)h(e,t)&&!h(a,t)&&i(e[t])&&(s[t]=L({},s[t]));return s}function j(e){null!=e&&this.set(e)}l.suppressDeprecationWarnings=!1,l.deprecationHandler=null,s=Object.keys?Object.keys:function(e){var a,t=[];for(a in e)h(e,a)&&t.push(a);return t};var x={};function O(e,a){var t=e.toLowerCase();x[t]=x[t+"s"]=x[a]=e}function P(e){return"string"==typeof e?x[e]||x[e.toLowerCase()]:void 0}function W(e){var a,t,s={};for(t in e)h(e,t)&&(a=P(t))&&(s[a]=e[t]);return s}var A={};function E(e,a){A[e]=a}function F(e,a,t){var s=""+Math.abs(e),n=a-s.length;return(0<=e?t?"+":"":"-")+Math.pow(10,Math.max(0,n)).toString().substr(1)+s}var z=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,J=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,N={},R={};function C(e,a,t,s){var n=s;"string"==typeof s&&(n=function(){return this[s]()}),e&&(R[e]=n),a&&(R[a[0]]=function(){return F(n.apply(this,arguments),a[1],a[2])}),t&&(R[t]=function(){return this.localeData().ordinal(n.apply(this,arguments),e)})}function I(e,a){return e.isValid()?(a=U(a,e.localeData()),N[a]=N[a]||function(s){var e,n,a,d=s.match(z);for(e=0,n=d.lengtLine truncated
-1
View File
@@ -1 +0,0 @@
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var e,i;function c(){return e.apply(null,arguments)}function o(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function u(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function l(e){return void 0===e}function h(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function d(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function f(e,t){var n,s=[];for(n=0;n<e.length;++n)s.push(t(e[n],n));return s}function m(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function _(e,t){for(var n in t)m(t,n)&&(e[n]=t[n]);return m(t,"toString")&&(e.toString=t.toString),m(t,"valueOf")&&(e.valueOf=t.valueOf),e}function y(e,t,n,s){return Tt(e,t,n,s,!0).utc()}function g(e){return null==e._pf&&(e._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],meridiem:null,rfc2822:!1,weekdayMismatch:!1}),e._pf}function v(e){if(null==e._isValid){var t=g(e),n=i.call(t.parsedDateParts,function(e){return null!=e}),s=!isNaN(e._d.getTime())&&t.overflow<0&&!t.empty&&!t.invalidMonth&&!t.invalidWeekday&&!t.weekdayMismatch&&!t.nullInput&&!t.invalidFormat&&!t.userInvalidated&&(!t.meridiem||t.meridiem&&n);if(e._strict&&(s=s&&0===t.charsLeftOver&&0===t.unusedTokens.length&&void 0===t.bigHour),null!=Object.isFrozen&&Object.isFrozen(e))return s;e._isValid=s}return e._isValid}function p(e){var t=y(NaN);return null!=e?_(g(t),e):g(t).userInvalidated=!0,t}i=Array.prototype.some?Array.prototype.some:function(e){for(var t=Object(this),n=t.length>>>0,s=0;s<n;s++)if(s in t&&e.call(this,t[s],s,t))return!0;return!1};var r=c.momentProperties=[];function w(e,t){var n,s,i;if(l(t._isAMomentObject)||(e._isAMomentObject=t._isAMomentObject),l(t._i)||(e._i=t._i),l(t._f)||(e._f=t._f),l(t._l)||(e._l=t._l),l(t._strict)||(e._strict=t._strict),l(t._tzm)||(e._tzm=t._tzm),l(t._isUTC)||(e._isUTC=t._isUTC),l(t._offset)||(e._offset=t._offset),l(t._pf)||(e._pf=g(t)),l(t._locale)||(e._locale=t._locale),0<r.length)for(n=0;n<r.length;n++)l(i=t[s=r[n]])||(e[s]=i);return e}var t=!1;function M(e){w(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===t&&(t=!0,c.updateOffset(this),t=!1)}function k(e){return e instanceof M||null!=e&&null!=e._isAMomentObject}function S(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function D(e){var t=+e,n=0;return 0!==t&&isFinite(t)&&(n=S(t)),n}function a(e,t,n){var s,i=Math.min(e.length,t.length),r=Math.abs(e.length-t.length),a=0;for(s=0;s<i;s++)(n&&e[s]!==t[s]||!n&&D(e[s])!==D(t[s]))&&a++;return a+r}function Y(e){!1===c.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+e)}function n(i,r){var a=!0;return _(function(){if(null!=c.deprecationHandler&&c.deprecationHandler(null,i),a){for(var e,t=[],n=0;n<arguments.length;n++){if(e="","object"==typeof arguments[n]){for(var s in e+="\n["+n+"] ",arguments[0])e+=s+": "+arguments[0][s]+", ";e=e.slice(0,-2)}else e=arguments[n];t.push(e)}Y(i+"\nArguments: "+Array.prototype.slice.call(t).join("")+"\n"+(new Error).stack),a=!1}return r.apply(this,arguments)},r)}var s,O={};function T(e,t){null!=c.deprecationHandler&&c.deprecationHandler(e,t),O[e]||(Y(t),O[e]=!0)}function b(e){return e instanceof Function||"[object Function]"===Object.prototype.toString.call(e)}function x(e,t){var n,s=_({},e);for(n in t)m(t,n)&&(u(e[n])&&u(t[n])?(s[n]={},_(s[n],e[n]),_(s[n],t[n])):null!=t[n]?s[n]=t[n]:delete s[n]);for(n in e)m(e,n)&&!m(t,n)&&u(e[n])&&(s[n]=_({},s[n]));return s}function P(e){null!=e&&this.set(e)}c.suppressDeprecationWarnings=!1,c.deprecationHandler=null,s=Object.keys?Object.keys:function(e){var t,n=[];for(t in e)m(e,t)&&n.push(t);return n};var W={};function C(e,t){var n=e.toLowerCase();W[n]=W[n+"s"]=W[t]=e}function H(e){return"string"==typeof e?W[e]||W[e.toLowerCase()]:void 0}function R(e){var t,n,s={};for(n in e)m(e,n)&&(t=H(n))&&(s[t]=e[n]);return s}var U={};function F(e,t){U[e]=t}function L(e,t,n){var s=""+Math.abs(e),i=t-s.length;return(0<=e?n?"+":"":"-")+Math.pow(10,Math.max(0,i)).toString().substr(1)+s}var N=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,G=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,V={},E={};function I(e,t,n,s){var i=s;"string"==typeof s&&(i=function(){return this[s]()}),e&&(E[e]=i),t&&(E[t[0]]=function(){return L(i.apply(this,arguments),t[1],t[2])}),n&&(E[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),e)})}function A(e,t){return e.isValid()?(t=j(t,e.localeData()),V[t]=V[t]||function(s){var e,i,t,r=s.match(N);for(e=0,i=r.lengtLine truncated
@@ -1 +0,0 @@
!function(e,a){"object"==typeof exports&&"undefined"!=typeof module?module.exports=a():"function"==typeof define&&define.amd?define(a):e.moment=a()}(this,function(){"use strict";var e,n;function l(){return e.apply(null,arguments)}function _(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function i(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function o(e){return void 0===e}function m(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function u(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function M(e,a){var t,s=[];for(t=0;t<e.length;++t)s.push(a(e[t],t));return s}function h(e,a){return Object.prototype.hasOwnProperty.call(e,a)}function L(e,a){for(var t in a)h(a,t)&&(e[t]=a[t]);return h(a,"toString")&&(e.toString=a.toString),h(a,"valueOf")&&(e.valueOf=a.valueOf),e}function c(e,a,t,s){return Sa(e,a,t,s,!0).utc()}function Y(e){return null==e._pf&&(e._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],meridiem:null,rfc2822:!1,weekdayMismatch:!1}),e._pf}function y(e){if(null==e._isValid){var a=Y(e),t=n.call(a.parsedDateParts,function(e){return null!=e}),s=!isNaN(e._d.getTime())&&a.overflow<0&&!a.empty&&!a.invalidMonth&&!a.invalidWeekday&&!a.weekdayMismatch&&!a.nullInput&&!a.invalidFormat&&!a.userInvalidated&&(!a.meridiem||a.meridiem&&t);if(e._strict&&(s=s&&0===a.charsLeftOver&&0===a.unusedTokens.length&&void 0===a.bigHour),null!=Object.isFrozen&&Object.isFrozen(e))return s;e._isValid=s}return e._isValid}function f(e){var a=c(NaN);return null!=e?L(Y(a),e):Y(a).userInvalidated=!0,a}n=Array.prototype.some?Array.prototype.some:function(e){for(var a=Object(this),t=a.length>>>0,s=0;s<t;s++)if(s in a&&e.call(this,a[s],s,a))return!0;return!1};var d=l.momentProperties=[];function k(e,a){var t,s,n;if(o(a._isAMomentObject)||(e._isAMomentObject=a._isAMomentObject),o(a._i)||(e._i=a._i),o(a._f)||(e._f=a._f),o(a._l)||(e._l=a._l),o(a._strict)||(e._strict=a._strict),o(a._tzm)||(e._tzm=a._tzm),o(a._isUTC)||(e._isUTC=a._isUTC),o(a._offset)||(e._offset=a._offset),o(a._pf)||(e._pf=Y(a)),o(a._locale)||(e._locale=a._locale),0<d.length)for(t=0;t<d.length;t++)o(n=a[s=d[t]])||(e[s]=n);return e}var a=!1;function p(e){k(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===a&&(a=!0,l.updateOffset(this),a=!1)}function D(e){return e instanceof p||null!=e&&null!=e._isAMomentObject}function T(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function g(e){var a=+e,t=0;return 0!==a&&isFinite(a)&&(t=T(a)),t}function r(e,a,t){var s,n=Math.min(e.length,a.length),d=Math.abs(e.length-a.length),r=0;for(s=0;s<n;s++)(t&&e[s]!==a[s]||!t&&g(e[s])!==g(a[s]))&&r++;return r+d}function w(e){!1===l.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+e)}function t(n,d){var r=!0;return L(function(){if(null!=l.deprecationHandler&&l.deprecationHandler(null,n),r){for(var e,a=[],t=0;t<arguments.length;t++){if(e="","object"==typeof arguments[t]){for(var s in e+="\n["+t+"] ",arguments[0])e+=s+": "+arguments[0][s]+", ";e=e.slice(0,-2)}else e=arguments[t];a.push(e)}w(n+"\nArguments: "+Array.prototype.slice.call(a).join("")+"\n"+(new Error).stack),r=!1}return d.apply(this,arguments)},d)}var s,v={};function S(e,a){null!=l.deprecationHandler&&l.deprecationHandler(e,a),v[e]||(w(a),v[e]=!0)}function H(e){return e instanceof Function||"[object Function]"===Object.prototype.toString.call(e)}function b(e,a){var t,s=L({},e);for(t in a)h(a,t)&&(i(e[t])&&i(a[t])?(s[t]={},L(s[t],e[t]),L(s[t],a[t])):null!=a[t]?s[t]=a[t]:delete s[t]);for(t in e)h(e,t)&&!h(a,t)&&i(e[t])&&(s[t]=L({},s[t]));return s}function j(e){null!=e&&this.set(e)}l.suppressDeprecationWarnings=!1,l.deprecationHandler=null,s=Object.keys?Object.keys:function(e){var a,t=[];for(a in e)h(e,a)&&t.push(a);return t};var x={};function O(e,a){var t=e.toLowerCase();x[t]=x[t+"s"]=x[a]=e}function P(e){return"string"==typeof e?x[e]||x[e.toLowerCase()]:void 0}function W(e){var a,t,s={};for(t in e)h(e,t)&&(a=P(t))&&(s[a]=e[t]);return s}var A={};function E(e,a){A[e]=a}function F(e,a,t){var s=""+Math.abs(e),n=a-s.length;return(0<=e?t?"+":"":"-")+Math.pow(10,Math.max(0,n)).toString().substr(1)+s}var z=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,J=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,N={},R={};function C(e,a,t,s){var n=s;"string"==typeof s&&(n=function(){return this[s]()}),e&&(R[e]=n),a&&(R[a[0]]=function(){return F(n.apply(this,arguments),a[1],a[2])}),t&&(R[t]=function(){return this.localeData().ordinal(n.apply(this,arguments),e)})}function I(e,a){return e.isValid()?(a=U(a,e.localeData()),N[a]=N[a]||function(s){var e,n,a,d=s.match(z);for(e=0,n=d.lengtLine truncated
@@ -1,2 +0,0 @@
.vis-time-axis{position:relative;overflow:hidden}.vis-time-axis.vis-foreground{top:0;left:0;width:100%}.vis-time-axis.vis-background{position:absolute;top:0;left:0;width:100%;height:100%}.vis-time-axis .vis-text{position:absolute;color:#4d4d4d;padding:3px;overflow:hidden;box-sizing:border-box;white-space:nowrap}.vis-time-axis .vis-text.vis-measure{position:absolute;padding-left:0;padding-right:0;margin-left:0;margin-right:0;visibility:hidden}.vis-time-axis .vis-grid.vis-vertical{position:absolute;border-left:1px solid}.vis-time-axis .vis-grid.vis-vertical-rtl{position:absolute;border-right:1px solid}.vis-time-axis .vis-grid.vis-minor{border-color:#e5e5e5}.vis-time-axis .vis-grid.vis-major{border-color:#bfbfbf}.vis .overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-active{box-shadow:0 0 10px #86d5f8}.vis-custom-time{background-color:#6e94ff;width:2px;cursor:move;z-index:1}.vis-custom-time>.vis-custom-time-marker{background-color:inherit;color:#fff;font-size:12px;white-space:nowrap;padding:3px 5px;top:0;cursor:auto;z-index:inherit}.vis-current-time{background-color:#ff7f6e;width:2px;z-index:1;pointer-events:none}.vis-rolling-mode-btn{height:40px;width:40px;position:absolute;top:7px;right:20px;border-radius:50%;font-size:28px;cursor:pointer;opacity:.8;color:#fff;font-weight:700;text-align:center;background:#3876c2}.vis-rolling-mode-btn:before{content:"\26F6"}.vis-rolling-mode-btn:hover{opacity:1}.vis-panel{position:absolute;padding:0;margin:0;box-sizing:border-box}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right,.vis-panel.vis-top{border:1px #bfbfbf}.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right{border-top-style:solid;border-bottom-style:solid;overflow:hidden}.vis-left.vis-panel.vis-vertical-scroll,.vis-right.vis-panel.vis-vertical-scroll{height:100%;overflow-x:hidden;overflow-y:scroll}.vis-left.vis-panel.vis-vertical-scroll{direction:rtl}.vis-left.vis-panel.vis-vertical-scroll .vis-content,.vis-right.vis-panel.vis-vertical-scroll{direction:ltr}.vis-right.vis-panel.vis-vertical-scroll .vis-content{direction:rtl}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-top{border-left-style:solid;border-right-style:solid}.vis-background{overflow:hidden}.vis-panel>.vis-content{position:relative}.vis-panel .vis-shadow{position:absolute;width:100%;height:1px;box-shadow:0 0 10px rgba(0,0,0,.8)}.vis-panel .vis-shadow.vis-top{top:-1px;left:0}.vis-panel .vis-shadow.vis-bottom{bottom:-1px;left:0}.vis-graph-group0{fill:#4f81bd;fill-opacity:0;stroke-width:2px;stroke:#4f81bd}.vis-graph-group1{fill:#f79646;fill-opacity:0;stroke-width:2px;stroke:#f79646}.vis-graph-group2{fill:#8c51cf;fill-opacity:0;stroke-width:2px;stroke:#8c51cf}.vis-graph-group3{fill:#75c841;fill-opacity:0;stroke-width:2px;stroke:#75c841}.vis-graph-group4{fill:#ff0100;fill-opacity:0;stroke-width:2px;stroke:#ff0100}.vis-graph-group5{fill:#37d8e6;fill-opacity:0;stroke-width:2px;stroke:#37d8e6}.vis-graph-group6{fill:#042662;fill-opacity:0;stroke-width:2px;stroke:#042662}.vis-graph-group7{fill:#00ff26;fill-opacity:0;stroke-width:2px;stroke:#00ff26}.vis-graph-group8{fill:#f0f;fill-opacity:0;stroke-width:2px;stroke:#f0f}.vis-graph-group9{fill:#8f3938;fill-opacity:0;stroke-width:2px;stroke:#8f3938}.vis-timeline .vis-fill{fill-opacity:.1;stroke:none}.vis-timeline .vis-bar{fill-opacity:.5;stroke-width:1px}.vis-timeline .vis-point{stroke-width:2px;fill-opacity:1}.vis-timeline .vis-legend-background{stroke-width:1px;fill-opacity:.9;fill:#fff;stroke:#c2c2c2}.vis-timeline .vis-outline{stroke-width:1px;fill-opacity:1;fill:#fff;stroke:#e5e5e5}.vis-timeline .vis-icon-fill{fill-opacity:.3;stroke:none}.vis-timeline{position:relative;border:1px solid #bfbfbf;overflow:hidden;padding:0;margin:0;box-sizing:border-box}.vis-loading-screen{width:100%;height:100%;position:absolute;top:0;left:0}.vis [class*=span]{min-height:0;width:auto}.vis-item{position:absolute;color:#1a1a1a;border-color:#97b0f8;border-width:1px;background-color:#d5ddf6;display:inline-block;z-index:1}.vis-item.vis-selected{border-color:#ffc200;background-color:#fff785;z-index:2}.vis-editable.vis-selected{cursor:move}.vis-item.vis-point.vis-selected{background-color:#fff785}.vis-item.vis-box{text-align:center;border-style:solid;border-radius:2px}.vis-item.vis-point{background:none}.vis-item.vis-dot{position:absolute;padding:0;border-width:4px;border-style:solid;border-radius:4px}.vis-item.vis-range{border-style:solid;border-radius:2px;box-sizing:border-box}.vis-item.vis-background{border:none;background-color:rgba(213,221,246,.4);box-sizing:border-box;padding:0;margin:0}.vis-item .vis-item-overflow{position:relative;width:100%;height:100%;padding:0;margin:0;overflow:hidden}.vis-item-visible-frame{white-space:nowrap}.vis-item.vis-range .vis-item-content{position:relative;display:inline-block}.vis-item.vis-background .vis-item-content{position:absolute;display:inline-block}.vis-item.vis-line{padding:0;position:absolute;width:0;border-Line truncated
/*# sourceMappingURL=vis-timeline-graph2d.min.css.map */
@@ -1,34 +0,0 @@
/**
* vis-timeline and vis-graph2d
* https://visjs.github.io/vis-timeline/
*
* Create a fully customizable, interactive timeline with items and ranges.
*
* @version 7.5.1
* @date 2022-03-01T18:26:09.239Z
*
* @copyright (c) 2011-2017 Almende B.V, http://almende.com
* @copyright (c) 2017-2019 visjs contributors, https://github.com/visjs
*
* @license
* vis.js is dual licensed under both
*
* 1. The Apache 2.0 License
* http://www.apache.org/licenses/LICENSE-2.0
*
* and
*
* 2. The MIT License
* http://opensource.org/licenses/MIT
*
* vis.js may be distributed under either license.
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).vis=t.vis||{})}(this,(function(t){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function n(t){throw new Error('Could not dynamically require "'+t+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}var o={exports:{}};!function(t,e){t.exports=function(){var e,i;function o(){return e.apply(null,arguments)}function r(t){e=t}function s(t){return t instanceof Array||"[object Array]"===Object.prototype.toString.call(t)}function a(t){return null!=t&&"[object Object]"===Object.prototype.toString.call(t)}function l(t,e){return Object.prototype.hasOwnProperty.call(t,e)}function h(t){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(t).length;var e;for(e in t)if(l(t,e))return!1;return!0}function u(t){return void 0===t}function d(t){return"number"==typeof t||"[object Number]"===Object.prototype.toString.call(t)}function c(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function p(t,e){var i,n=[];for(i=0;i<t.length;++i)n.push(e(t[i],i));return n}function f(t,e){for(var i in e)l(e,i)&&(t[i]=e[i]);return l(e,"toString")&&(t.toString=e.toString),l(e,"valueOf")&&(t.valueOf=e.valueOf),t}function m(t,e,i,n){return Xi(t,e,i,n,!0).utc()}function v(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidEra:null,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],era:null,meridiem:null,rfc2822:!1,weekdayMismatch:!1}}function g(t){return null==t._pf&&(t._pf=v()),t._pf}function y(t){if(null==t._isValid){var e=g(t),n=i.call(e.parsedDateParts,(function(t){return null!=t})),o=!isNaN(t._d.getTime())&&e.overflow<0&&!e.empty&&!e.invalidEra&&!e.invalidMonth&&!e.invalidWeekday&&!e.weekdayMismatch&&!e.nullInput&&!e.invalidFormat&&!e.userInvalidated&&(!e.meridiem||e.meridiem&&n);if(t._strict&&(o=o&&0===e.charsLeftOver&&0===e.unusedTokens.length&&void 0===e.bigHour),null!=Object.isFrozen&&Object.isFrozen(t))return o;t._isValid=o}return t._isValid}function b(t){var e=m(NaN);return null!=t?f(g(e),t):g(e).userInvalidated=!0,e}i=Array.prototype.some?Array.prototype.some:function(t){var e,i=Object(this),n=i.length>>>0;for(e=0;e<n;e++)if(e in i&&t.call(this,i[e],e,i))return!0;return!1};var _=o.momentProperties=[],w=!1;function k(t,e){var i,n,o;if(u(e._isAMomentObject)||(t._isAMomentObject=e._isAMomentObject),u(e._i)||(t._i=e._i),u(e._f)||(t._f=e._f),u(e._l)||(t._l=e._l),u(e._strict)||(t._strict=e._strict),u(e._tzm)||(t._tzm=e._tzm),u(e._isUTC)||(t._isUTC=e._isUTC),u(e._offset)||(t._offset=e._offset),u(e._pf)||(t._pf=g(e)),u(e._locale)||(t._locale=e._locale),_.length>0)for(i=0;i<_.length;i++)u(o=e[n=_[i]])||(t[n]=o);return t}function x(t){k(this,t),this._d=new Date(null!=t._d?t._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===w&&(w=!0,o.updateOffset(this),w=!1)}function D(t){return t instanceof x||null!=t&&null!=t._isAMomentObject}function S(t){!1===o.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function C(t,e){var i=!0;return f((function(){if(null!=o.deprecationHandler&&o.deprecationHandler(null,t),i){var n,r,s,a=[];for(r=0;r<arguments.length;r++){if(n="","object"==typeof arguments[r]){for(s in n+="\n["+r+"] ",arguments[0])l(arguments[0],s)&&(n+=s+": "+arguments[0][s]+", ");n=n.slice(0,-2)}else n=arguments[r];a.push(n)}S(t+"\nArguments: "+Array.prototype.slice.call(a).join("")+"\n"+(new Error).stack),i=!1}return e.apply(this,arguments)}),e)}var T,M={};function O(t,e){null!=o.deprecationHandler&&o.deprecationHandler(t,e),M[t]||(S(e),M[t]=!0)}function E(t){return"undefined"!=typeof Function&&t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function P(t){var e,i;for(i in t)l(t,i)&&(E(e=t[i])?this[i]=e:this["_"+i]=e);this._config=t,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)}function A(t,e){var i,n=f({},t);for(i in e)l(e,i)&&(a(t[i])&&a(e[i])?(n[i]={},f(n[i],t[i]),f(n[i],e[i])):null!=e[i]?n[i]=e[i]:delete n[i]);for(i in t)l(t,i)&&!l(e,i)&&a(t[i])&&(n[i]=f({},n[i]));return n}function I(t){null!=t&&this.set(t)}o.suppressDeprecationWarnings=!1,o.deprecationHandler=null,T=Object.keys?Object.keys:function(t){var e,i=[];for(e in t)l(t,e)&&i.push(e);return i};var L={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"};function N(t,e,i){var n=this._calendar[t]||this._calendar.sameElse;return E(n)?n.call(e,i):n}function F(t,e,i){var n=""+Math.abs(t),o=e-n.Line truncated
o.version="2.29.1",r(qi),o.fn=hr,o.min=Ji,o.max=Qi,o.now=tn,o.utc=m,o.unix=ur,o.months=gr,o.isDate=c,o.locale=fi,o.invalid=b,o.duration=Mn,o.isMoment=D,o.weekdays=br,o.parseZone=dr,o.localeData=gi,o.isDuration=an,o.monthsShort=yr,o.weekdaysMin=wr,o.defineLocale=mi,o.updateLocale=vi,o.locales=yi,o.weekdaysShort=_r,o.normalizeUnits=ot,o.relativeTimeRounding=os,o.relativeTimeThreshold=rs,o.calendarFormat=zn,o.prototype=hr,o.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},o}()}(o);var r=o.exports;!function(t){function e(t,e,i,n){var o={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[t+" Tage",t+" Tagen"],w:["eine Woche","einer Woche"],M:["ein Monat","einem Monat"],MM:[t+" Monate",t+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[t+" Jahre",t+" Jahren"]};return e?o[i][0]:o[i][1]}t.defineLocale("de",{months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Feb._März_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},calendar:{sameDay:"[heute um] LT [Uhr]",sameElse:"L",nextDay:"[morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",ss:"%d Sekunden",m:e,mm:"%d Minuten",h:e,hh:"%d Stunden",d:e,dd:e,w:e,ww:"%d Wochen",M:e,MM:e,y:e,yy:e},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}(o.exports),function(t){var e="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),i="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_"),n=[/^ene/i,/^feb/i,/^mar/i,/^abr/i,/^may/i,/^jun/i,/^jul/i,/^ago/i,/^sep/i,/^oct/i,/^nov/i,/^dic/i],o=/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i;t.defineLocale("es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(t,n){return t?/-MMM-/.test(n)?i[t.month()]:e[t.month()]:e},monthsRegex:o,monthsShortRegex:o,monthsStrictRegex:/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,monthsShortStrictRegex:/^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,monthsParse:n,longMonthsParse:n,shortMonthsParse:n,weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_sá".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY H:mm",LLLL:"dddd, D [de] MMMM [de] YYYY H:mm"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[mañana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",w:"una semana",ww:"%d semanas",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4},invalidDate:"Fecha inválida"})}(o.exports),function(t){var e=/(janv\.?|févr\.?|mars|avr\.?|mai|juin|juil\.?|août|sept\.?|oct\.?|nov\.?|déc\.?|janvier|février|mars|avril|mai|juin|juillet|août|septembre|octobre|novembre|décembre)/i,i=[/^janv/i,/^févr/i,/^mars/i,/^avr/i,/^mai/i,/^juin/i,/^juil/i,/^août/i,/^sept/i,/^oct/i,/^nov/i,/^déc/i];t.defineLocale("fr",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),monthsRegex:e,monthsShortRegex:e,monthsStrictRegex:/^(janvier|février|mars|avril|mai|juin|juillet|août|septembre|octobre|novembre|décembre)/i,monthsShortStrictRegex:/(janv\.?|févr\.?|mars|avr\.?|mai|juin|juil\.?|août|sept\.?|oct\.?|nov\.?|déc\.?)/i,monthsParse:i,longMonthsParse:i,shortMonthsParse:i,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_saLine truncated
/*! Hammer.JS - v2.0.17-rc - 2019-12-16
* http://naver.github.io/egjs
*
* Forked By Naver egjs
* Copyright (c) hammerjs
* Licensed under the MIT license */function Gf(){return(Gf=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var i=arguments[e];for(var n in i)Object.prototype.hasOwnProperty.call(i,n)&&(t[n]=i[n])}return t}).apply(this,arguments)}function Bf(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.__proto__=e}function Wf(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}var Vf,Uf="function"!=typeof Object.assign?function(t){if(null==t)throw new TypeError("Cannot convert undefined or null to object");for(var e=Object(t),i=1;i<arguments.length;i++){var n=arguments[i];if(null!=n)for(var o in n)n.hasOwnProperty(o)&&(e[o]=n[o])}return e}:Object.assign,Xf=["","webkit","Moz","MS","ms","o"],qf="undefined"==typeof document?{style:{}}:document.createElement("div"),Zf=Math.round,$f=Math.abs,Kf=Date.now;function Jf(t,e){for(var i,n,o=e[0].toUpperCase()+e.slice(1),r=0;r<Xf.length;){if((n=(i=Xf[r])?i+o:e)in t)return n;r++}}Vf="undefined"==typeof window?{}:window;var Qf=Jf(qf.style,"touchAction"),tm=void 0!==Qf;var em="compute",im="auto",nm="manipulation",om="none",rm="pan-x",sm="pan-y",am=function(){if(!tm)return!1;var t={},e=Vf.CSS&&Vf.CSS.supports;return["auto","manipulation","pan-y","pan-x","pan-x pan-y","none"].forEach((function(i){return t[i]=!e||Vf.CSS.supports("touch-action",i)})),t}(),lm="ontouchstart"in Vf,hm=void 0!==Jf(Vf,"PointerEvent"),um=lm&&/mobile|tablet|ip(ad|hone|od)|android/i.test(navigator.userAgent),dm="touch",cm="mouse",pm=16,fm=24,mm=["x","y"],vm=["clientX","clientY"];function gm(t,e,i){var n;if(t)if(t.forEach)t.forEach(e,i);else if(void 0!==t.length)for(n=0;n<t.length;)e.call(i,t[n],n,t),n++;else for(n in t)t.hasOwnProperty(n)&&e.call(i,t[n],n,t)}function ym(t,e){return"function"==typeof t?t.apply(e&&e[0]||void 0,e):t}function bm(t,e){return t.indexOf(e)>-1}var _m=function(){function t(t,e){this.manager=t,this.set(e)}var e=t.prototype;return e.set=function(t){t===em&&(t=this.compute()),tm&&this.manager.element.style&&am[t]&&(this.manager.element.style[Qf]=t),this.actions=t.toLowerCase().trim()},e.update=function(){this.set(this.manager.options.touchAction)},e.compute=function(){var t=[];return gm(this.manager.recognizers,(function(e){ym(e.options.enable,[e])&&(t=t.concat(e.getTouchAction()))})),function(t){if(bm(t,om))return om;var e=bm(t,rm),i=bm(t,sm);return e&&i?om:e||i?e?rm:sm:bm(t,nm)?nm:im}(t.join(" "))},e.preventDefaults=function(t){var e=t.srcEvent,i=t.offsetDirection;if(this.manager.session.prevented)e.preventDefault();else{var n=this.actions,o=bm(n,om)&&!am.none,r=bm(n,sm)&&!am["pan-y"],s=bm(n,rm)&&!am["pan-x"];if(o){var a=1===t.pointers.length,l=t.distance<2,h=t.deltaTime<250;if(a&&l&&h)return}if(!s||!r)return o||r&&6&i||s&&i&fm?this.preventSrc(e):void 0}},e.preventSrc=function(t){this.manager.session.prevented=!0,t.preventDefault()},t}();function wm(t,e){for(;t;){if(t===e)return!0;t=t.parentNode}return!1}function km(t){var e=t.length;if(1===e)return{x:Zf(t[0].clientX),y:Zf(t[0].clientY)};for(var i=0,n=0,o=0;o<e;)i+=t[o].clientX,n+=t[o].clientY,o++;return{x:Zf(i/e),y:Zf(n/e)}}function xm(t){for(var e=[],i=0;i<t.pointers.length;)e[i]={clientX:Zf(t.pointers[i].clientX),clientY:Zf(t.pointers[i].clientY)},i++;return{timeStamp:Kf(),pointers:e,center:km(e),deltaX:t.deltaX,deltaY:t.deltaY}}function Dm(t,e,i){i||(i=mm);var n=e[i[0]]-t[i[0]],o=e[i[1]]-t[i[1]];return Math.sqrt(n*n+o*o)}function Sm(t,e,i){i||(i=mm);var n=e[i[0]]-t[i[0]],o=e[i[1]]-t[i[1]];return 180*Math.atan2(o,n)/Math.PI}function Cm(t,e){return t===e?1:$f(t)>=$f(e)?t<0?2:4:e<0?8:pm}function Tm(t,e,i){return{x:e/t||0,y:i/t||0}}function Mm(t,e){var i=t.session,n=e.pointers,o=n.length;i.firstInput||(i.firstInput=xm(e)),o>1&&!i.firstMultiple?i.firstMultiple=xm(e):1===o&&(i.firstMultiple=!1);var r=i.firstInput,s=i.firstMultiple,a=s?s.center:r.center,l=e.center=km(n);e.timeStamp=Kf(),e.deltaTime=e.timeStamp-r.timeStamp,e.angle=Sm(a,l),e.distance=Dm(a,l),function(t,e){var i=e.center,n=t.offsetDelta||{},o=t.prevDelta||{},r=t.prevInput||{};1!==e.eventType&&4!==r.eventType||(o=t.prevDelta={x:r.deltaX||0,y:r.deltaY||0},n=t.offsetDelta={x:i.x,y:i.y}),e.deltaX=o.x+(i.x-n.x),e.deltaY=o.y+(i.y-n.y)}(i,e),e.offsetDirection=Cm(e.deltaX,e.deltaY);var h,u,d=Tm(e.deltaTime,e.deltaX,e.deltaY);e.overallVelocityX=d.x,e.overallVelocityY=d.y,e.overallVelocity=$f(d.x)>$f(d.y)?d.x:d.y,e.scale=s?(h=s.pointers,Dm((u=n)[0],u[1],vm)/Dm(h[0],h[1],vm)):1,e.rotation=s?function(t,e){return Sm(e[1],e[0],vm)+Sm(t[1],t[0],vm)}(s.pointers,n):0,e.maxPointers=i.prevInput?e.pointers.length>i.prevInput.maxPointers?e.pointers.length:i.prevInput.maxPointers:e.pointers.length,function(t,e){var i,n,o,r,s=t.lastInterval||e,a=e.timeStamp-s.timeStamp;if(8!==e.eventType&&(a>25||void 0===s.velocity)){var l=e.deltaX-s.deltaX,h=e.deltaY-s.deltaY,u=Tm(a,l,h);n=u.x,o=u.y,i=$f(u.x)>$f(u.y)?u.x:u.y,r=Cm(l,h),t.lastInterval=e}else i=s.velocity,n=s.velocityX,o=s.velocityY,r=s.direction;e.velocityLine truncated
//# sourceMappingURL=vis-timeline-graph2d.min.js.map
File diff suppressed because it is too large. Load diff
-47
View File
@@ -1,47 +0,0 @@
/**
* vis.js
* https://github.com/almende/vis
*
* A dynamic, browser-based visualization library.
*
* @version 4.20.1-SNAPSHOT
* @date 2017-10-12
*
* @license
* Copyright (C) 2011-2017 Almende B.V, http://almende.com
*
* Vis.js is dual licensed under both
*
* * The Apache 2.0 License
* http://www.apache.org/licenses/LICENSE-2.0
*
* and
*
* * The MIT License
* http://opensource.org/licenses/MIT
*
* Vis.js may be distributed under either license.
*/
"use strict";!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.vis=e():t.vis=e()}(this,function(){return function(t){function e(o){if(i[o])return i[o].exports;var n=i[o]={i:o,l:!1,exports:{}};return t[o].call(n.exports,n,n.exports,e),n.l=!0,n.exports}var i={};return e.m=t,e.c=i,e.d=function(t,i,o){e.o(t,i)||Object.defineProperty(t,i,{configurable:!1,enumerable:!0,get:o})},e.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(i,"a",i),i},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=123)}([function(t,e,i){e.__esModule=!0,e.default=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}},function(t,e,i){e.__esModule=!0;var o=i(169),n=function(t){return t&&t.__esModule?t:{default:t}}(o);e.default=function(){function t(t,e){for(var i=0;i<e.length;i++){var o=e[i];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),(0,n.default)(t,o.key,o)}}return function(e,i,o){return i&&t(e.prototype,i),o&&t(e,o),e}}()},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}function n(t,e,i,o){var n=!1;!0===o&&(n=null===e[i]&&void 0!==t[i]),n?delete t[i]:t[i]=e[i]}var s=i(77),r=o(s),a=i(29),h=o(a),d=i(8),l=o(d),u=i(6),c=o(u),p=i(9),f=i(157);e.isNumber=function(t){return t instanceof Number||"number"==typeof t},e.recursiveDOMDelete=function(t){if(t)for(;!0===t.hasChildNodes();)e.recursiveDOMDelete(t.firstChild),t.removeChild(t.firstChild)},e.giveRange=function(t,e,i,o){if(e==t)return.5;var n=1/(e-t);return Math.max(0,(o-t)*n)},e.isString=function(t){return t instanceof String||"string"==typeof t},e.isDate=function(t){if(t instanceof Date)return!0;if(e.isString(t)){if(m.exec(t))return!0;if(!isNaN(Date.parse(t)))return!0}return!1},e.randomUUID=function(){return f.v4()},e.assignAllKeys=function(t,e){for(var i in t)t.hasOwnProperty(i)&&"object"!==(0,c.default)(t[i])&&(t[i]=e)},e.fillIfDefined=function(t,i){var o=arguments.length>2&&void 0!==arguments[2]&&arguments[2];for(var s in t)void 0!==i[s]&&(null===i[s]||"object"!==(0,c.default)(i[s])?n(t,i,s,o):"object"===(0,c.default)(t[s])&&e.fillIfDefined(t[s],i[s],o))},e.extend=function(t,e){for(var i=1;i<arguments.length;i++){var o=arguments[i];for(var n in o)o.hasOwnProperty(n)&&(t[n]=o[n])}return t},e.selectiveExtend=function(t,e,i){if(!Array.isArray(t))throw new Error("Array with property names expected as first argument");for(var o=2;o<arguments.length;o++)for(var n=arguments[o],s=0;s<t.length;s++){var r=t[s];n&&n.hasOwnProperty(r)&&(e[r]=n[r])}return e},e.selectiveDeepExtend=function(t,i,o){var s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(Array.isArray(o))throw new TypeError("Arrays are not supported by deepExtend");for(var r=0;r<t.length;r++){var a=t[r];if(o.hasOwnProperty(a))if(o[a]&&o[a].constructor===Object)void 0===i[a]&&(i[a]={}),i[a].constructor===Object?e.deepExtend(i[a],o[a],!1,s):n(i,o,a,s);else{if(Array.isArray(o[a]))throw new TypeError("Arrays are not supported by deepExtend");n(i,o,a,s)}}return i},e.selectiveNotDeepExtend=function(t,i,o){var s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(Array.isArray(o))throw new TypeError("Arrays are not supported by deepExtend");for(var r in o)if(o.hasOwnProperty(r)&&-1===t.indexOf(r))if(o[r]&&o[r].constructor===Object)void 0===i[r]&&(i[r]={}),i[r].constructor===Object?e.deepExtend(i[r],o[r]):n(i,o,r,s);else if(Array.isArray(o[r])){i[r]=[];for(var a=0;a<o[r].length;a++)i[r].push(o[r][a])}else n(i,o,r,s);return i},e.deepExtend=function(t,i){var o=arguments.length>2&&void 0!==arguments[2]&&arguments[2],s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];for(var r in i)if(i.hasOwnProperty(r)||!0===o)if(i[r]&&i[r].constructor===Object)void 0===t[r]&&(t[r]={}),t[r].constructor===Object?e.deepExtend(t[r],i[r],o):n(t,i,r,s);else if(Array.isArray(i[r])){t[r]=[];for(var a=0;a<i[r].length;a++)t[r].push(i[r][a])}else n(t,i,r,s);return t},e.equalArray=function(t,e){if(t.length!=e.length)return!1;for(var i=0,o=t.length;i<o;i++)if(t[i]!=e[i])return!1;return!0},e.convert=function(t,i){var o;if(void 0!==t){if(null===t)return null;if(!i)return t;if("string"!=typeof i&&!(i instanceof String))throw new Error("Type must be a string");switch(i){case"boolean":case"Boolean":return Boolean(t);case"number":case"Number":return e.isString(t)&&!isNaN(Date.parse(t))?p(t).valueOf():Number(t.valueOf());case"string":case"String":return String(t);case"Date":if(e.isNumber(t))return new Date(t);if(t instanceof Date)return new Date(t.valueOf());if(p.isMoment(t))return new Date(t.valueOf());if(e.isString(t))return o=m.exec(t),o?new Date(Number(o[1])):p(new Date(t)).toDate();throw new Error("Cannot convert object of type "+e.getType(t)+" to type Date");case"Moment":if(e.isNumber(t))return p(t);if(t instanceof Date)return p(t.valueOf());if(p.isMoment(t))return p(t);ifLine truncated
;r=void 0!==n.indexMatch?" in "+t.printLocation(n.path,e,"")+'Perhaps it was incomplete? Did you mean: "'+n.indexMatch+'"?\n\n':s.distance<=4&&n.distance>s.distance?" in "+t.printLocation(n.path,e,"")+"Perhaps it was misplaced? Matching option found at: "+t.printLocation(s.path,s.closestMatch,""):n.distance<=8?'. Did you mean "'+n.closestMatch+'"?'+t.printLocation(n.path,e):". Did you mean one of these: "+t.print((0,d.default)(i))+t.printLocation(o,e),console.log('%cUnknown option detected: "'+e+'"'+r,g),m=!0}},{key:"findInOptions",value:function(e,i,o){var n=arguments.length>3&&void 0!==arguments[3]&&arguments[3],s=1e9,r="",a=[],h=e.toLowerCase(),d=void 0;for(var l in i){var u=void 0;if(void 0!==i[l].__type__&&!0===n){var c=t.findInOptions(e,i[l],f.copyAndExtendArray(o,l));s>c.distance&&(r=c.closestMatch,a=c.path,s=c.distance,d=c.indexMatch)}else-1!==l.toLowerCase().indexOf(h)&&(d=l),u=t.levenshteinDistance(e,l),s>u&&(r=l,a=f.copyArray(o),s=u)}return{closestMatch:r,path:a,distance:s,indexMatch:d}}},{key:"printLocation",value:function(t,e){for(var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"Problem value found at: \n",o="\n\n"+i+"options = {\n",n=0;n<t.length;n++){for(var s=0;s<n+1;s++)o+=" ";o+=t[n]+": {\n"}for(var r=0;r<t.length+1;r++)o+=" ";o+=e+"\n";for(var a=0;a<t.length+1;a++){for(var h=0;h<t.length-a;h++)o+=" ";o+="}\n"}return o+"\n\n"}},{key:"print",value:function(t){return(0,s.default)(t).replace(/(\")|(\[)|(\])|(,"__type__")/g,"").replace(/(\,)/g,", ")}},{key:"levenshteinDistance",value:function(t,e){if(0===t.length)return e.length;if(0===e.length)return t.length;var i,o=[];for(i=0;i<=e.length;i++)o[i]=[i];var n;for(n=0;n<=t.length;n++)o[0][n]=n;for(i=1;i<=e.length;i++)for(n=1;n<=t.length;n++)e.charAt(i-1)==t.charAt(n-1)?o[i][n]=o[i-1][n-1]:o[i][n]=Math.min(o[i-1][n-1]+1,Math.min(o[i][n-1]+1,o[i-1][n]+1));return o[e.length][t.length]}}]),t}();e.default=y,e.printStyle=g},function(t,e,i){function o(t,e){this.options=null,this.props=null}var n=i(2);o.prototype.setOptions=function(t){t&&n.extend(this.options,t)},o.prototype.redraw=function(){return!1},o.prototype.destroy=function(){},o.prototype._isResized=function(){var t=this.props._previousWidth!==this.props.width||this.props._previousHeight!==this.props.height;return this.props._previousWidth=this.props.width,this.props._previousHeight=this.props.height,t},t.exports=o},function(t,e,i){var o=i(18),n=i(7),s=i(80),r=i(26),a=function(t,e,i){var h,d,l,u=t&a.F,c=t&a.G,p=t&a.S,f=t&a.P,m=t&a.B,v=t&a.W,g=c?n:n[e]||(n[e]={}),y=g.prototype,b=c?o:p?o[e]:(o[e]||{}).prototype;c&&(i=e);for(h in i)(d=!u&&b&&void 0!==b[h])&&h in g||(l=d?b[h]:i[h],g[h]=c&&"function"!=typeof b[h]?i[h]:m&&d?s(l,o):v&&b[h]==l?function(t){var e=function(e,i,o){if(this instanceof t){switch(arguments.length){case 0:return new t;case 1:return new t(e);case 2:return new t(e,i)}return new t(e,i,o)}return t.apply(this,arguments)};return e.prototype=t.prototype,e}(l):f&&"function"==typeof l?s(Function.call,l):l,f&&((g.virtual||(g.virtual={}))[h]=l,t&a.R&&y&&!y[h]&&r(y,h,l)))};a.F=1,a.G=2,a.S=4,a.P=8,a.B=16,a.W=32,a.U=64,a.R=128,t.exports=a},function(t,e){var i=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=i)},function(t,e,i){t.exports={default:i(160),__esModule:!0}},function(t,e,i){var o=i(27),n=i(81),s=i(53),r=Object.defineProperty;e.f=i(21)?Object.defineProperty:function(t,e,i){if(o(t),e=s(e,!0),o(i),n)try{return r(t,e,i)}catch(t){}if("get"in i||"set"in i)throw TypeError("Accessors not supported!");return"value"in i&&(t[e]=i.value),t}},function(t,e,i){t.exports=!i(28)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,e){var i={}.hasOwnProperty;t.exports=function(t,e){return i.call(t,e)}},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(6),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=function(){function t(e,i,o){(0,a.default)(this,t),this.body=i,this.labelModule=o,this.setOptions(e),this.top=void 0,this.left=void 0,this.height=void 0,this.width=void 0,this.radius=void 0,this.margin=void 0,this.refreshNeeded=!0,this.boundingBox={top:0,left:0,right:0,bottom:0}}return(0,d.default)(t,[{key:"setOptions",value:function(t){this.options=t}},{key:"_setMargins",value:function(t){this.margin={},this.options.margin&&("object"==(0,s.default)(this.options.margin)?(this.margin.top=this.options.margin.top,this.margin.right=this.options.margin.right,this.margin.bottom=this.options.margin.bottom,this.margin.left=this.options.margin.left):(this.margin.top=this.options.margin,this.margin.right=this.options.margin,this.margin.bottom=this.options.margin,this.margin.left=this.options.margin)),t.adjustSizes(this.margin)}},{key:"_distanceToBorder",value:function(t,e){var i=this.options.borderWidth;return this.resize(t),Math.min(Math.abs(this.width/2/Math.cos(e)),Math.abs(thiLine truncated
;var s=this.options.showMinorLabels&&"none"!==this.options.orientation.axis,r=this.options.showMajorLabels&&"none"!==this.options.orientation.axis;t.minorLabelHeight=s?t.minorCharHeight:0,t.majorLabelHeight=r?t.majorCharHeight:0,t.height=t.minorLabelHeight+t.majorLabelHeight,t.width=e.offsetWidth,t.minorLineHeight=this.body.domProps.root.height-t.majorLabelHeight-("top"==this.options.orientation.axis?this.body.domProps.bottom.height:this.body.domProps.top.height),t.minorLineWidth=1,t.majorLineHeight=t.minorLineHeight+t.majorLabelHeight,t.majorLineWidth=1;var a=e.nextSibling,h=i.nextSibling;return e.parentNode&&e.parentNode.removeChild(e),i.parentNode&&i.parentNode.removeChild(i),e.style.height=this.props.height+"px",this._repaintLabels(),a?o.insertBefore(e,a):o.appendChild(e),h?this.body.dom.backgroundVertical.insertBefore(i,h):this.body.dom.backgroundVertical.appendChild(i),this._isResized()||n},o.prototype._repaintLabels=function(){var t=this.options.orientation.axis,e=r.convert(this.body.range.start,"Number"),i=r.convert(this.body.range.end,"Number"),o=this.body.util.toTime((this.props.minorCharWidth||10)*this.options.maxMinorChars).valueOf(),n=o-d.getHiddenDurationBefore(this.options.moment,this.body.hiddenDates,this.body.range,o);n-=this.body.util.toTime(0).valueOf();var s=new h(new Date(e),new Date(i),n,this.body.hiddenDates,this.options);s.setMoment(this.options.moment),this.options.format&&s.setFormat(this.options.format),this.options.timeAxis&&s.setScale(this.options.timeAxis),this.step=s;var a=this.dom;a.redundant.lines=a.lines,a.redundant.majorTexts=a.majorTexts,a.redundant.minorTexts=a.minorTexts,a.lines=[],a.majorTexts=[],a.minorTexts=[];var l,c,p,f,m,v,g,y,b,_=0,w=void 0,x=0;for(s.start(),l=s.getCurrent(),p=this.body.util.toScreen(l);s.hasNext()&&x<1e3;){switch(x++,f=s.isMajor(),b=s.getClassName(),y=s.getLabelMinor(),l,c=p,s.next(),l=s.getCurrent(),s.isMajor(),p=this.body.util.toScreen(l),v=_,_=p-c,s.scale){case"week":m=!0;break;default:m=_>=.4*v}if(this.options.showMinorLabels&&m){var k=this._repaintMinorText(c,y,t,b);k.style.width=_+"px"}f&&this.options.showMajorLabels?(c>0&&(void 0==w&&(w=c),k=this._repaintMajorText(c,s.getLabelMajor(),t,b)),g=this._repaintMajorLine(c,_,t,b)):m?g=this._repaintMinorLine(c,_,t,b):g&&(g.style.width=parseInt(g.style.width)+_+"px")}if(1e3!==x||u||(console.warn("Something is wrong with the Timeline scale. Limited drawing of grid lines to 1000 lines."),u=!0),this.options.showMajorLabels){var S=this.body.util.toTime(0),D=s.getLabelMajor(S),M=D.length*(this.props.majorCharWidth||10)+10;(void 0==w||M<w)&&this._repaintMajorText(0,D,t,b)}r.forEach(this.dom.redundant,function(t){for(;t.length;){var e=t.pop();e&&e.parentNode&&e.parentNode.removeChild(e)}})},o.prototype._repaintMinorText=function(t,e,i,o){var n=this.dom.redundant.minorTexts.shift();if(!n){var s=document.createTextNode("");n=document.createElement("div"),n.appendChild(s),this.dom.foreground.appendChild(n)}return this.dom.minorTexts.push(n),n.innerHTML=e,n.style.top="top"==i?this.props.majorLabelHeight+"px":"0",this.options.rtl?(n.style.left="",n.style.right=t+"px"):n.style.left=t+"px",n.className="vis-text vis-minor "+o,n},o.prototype._repaintMajorText=function(t,e,i,o){var n=this.dom.redundant.majorTexts.shift();if(!n){var s=document.createElement("div");n=document.createElement("div"),n.appendChild(s),this.dom.foreground.appendChild(n)}return n.childNodes[0].innerHTML=e,n.className="vis-text vis-major "+o,n.style.top="top"==i?"0":this.props.minorLabelHeight+"px",this.options.rtl?(n.style.left="",n.style.right=t+"px"):n.style.left=t+"px",this.dom.majorTexts.push(n),n},o.prototype._repaintMinorLine=function(t,e,i,o){var n=this.dom.redundant.lines.shift();n||(n=document.createElement("div"),this.dom.background.appendChild(n)),this.dom.lines.push(n);var s=this.props;return n.style.top="top"==i?s.majorLabelHeight+"px":this.body.domProps.top.height+"px",n.style.height=s.minorLineHeight+"px",this.options.rtl?(n.style.left="",n.style.right=t-s.minorLineWidth/2+"px",n.className="vis-grid vis-vertical-rtl vis-minor "+o):(n.style.left=t-s.minorLineWidth/2+"px",n.className="vis-grid vis-vertical vis-minor "+o),n.style.width=e+"px",n},o.prototype._repaintMajorLine=function(t,e,i,o){var n=this.dom.redundant.lines.shift();n||(n=document.createElement("div"),this.dom.background.appendChild(n)),this.dom.lines.push(n);var s=this.props;return n.style.top="top"==i?"0":this.body.domProps.top.height+"px",this.options.rtl?(n.style.left="",n.style.right=t-s.majorLineWidth/2+"px",n.className="vis-grid vis-vertical-rtl vis-major "+o):(n.style.left=t-s.majorLineWidth/2+"px",n.className="vis-grid vis-vertical vis-major "+o),n.style.height=s.majorLineHeight+"px",n.style.width=e+"px",n},o.prototype._calculateCharSize=function(){this.dom.measureCharMinor||(this.dom.measureCharMinor=document.createElement("DIV"),this.dom.measureCharMinor.className="vis-text vis-minor vis-measure",this.dom.measureCharMinor.style.position="absolute",thiLine truncated
;var d=m.snapAwayFromHidden(this.body.hiddenDates,a,i,!0),l=m.snapAwayFromHidden(this.body.hiddenDates,h,-i,!0);d==a&&l==h||(a=d,h=l);var u={animation:!1,byUser:!0,event:o};this.setRange(a,h,u),this.startToFront=!1,this.endToFront=!0},n.prototype.move=function(t){var e=this.end-this.start,i=this.start+e*t,o=this.end+e*t;this.start=i,this.end=o},n.prototype.moveTo=function(t){var e=(this.start+this.end)/2,i=e-t,o=this.start-i,n=this.end-i,s={animation:!1,byUser:!0,event:null};this.setRange(o,n,s)},t.exports=n},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}function n(){}var s=i(19),r=o(s),a=i(6),h=o(a),d=i(44),l=i(10),u=i(37),c=i(2),p=i(45),f=i(97),m=i(36),v=i(46);d(n.prototype),n.prototype._create=function(t){function e(t){this.isActive()&&this.emit("mousewheel",t);var e=0,i=0;if("detail"in t&&(i=-1*t.detail),"wheelDelta"in t&&(i=t.wheelDelta),"wheelDeltaY"in t&&(i=t.wheelDeltaY),"wheelDeltaX"in t&&(e=-1*t.wheelDeltaX),"axis"in t&&t.axis===t.HORIZONTAL_AXIS&&(e=-1*i,i=0),"deltaY"in t&&(i=-1*t.deltaY),"deltaX"in t&&(e=t.deltaX),this.options.zoomKey&&!t[this.options.zoomKey])if(t.preventDefault(),this.options.verticalScroll&&Math.abs(i)>=Math.abs(e)){var o=this.props.scrollTop,n=o+i;this.isActive()&&(this._setScrollTop(n),this._redraw(),this.emit("scroll",t))}else if(this.options.horizontalScroll){var s=Math.abs(e)>=Math.abs(i)?e:i,r=s/120*(this.range.end-this.range.start)/20,a=this.range.start+r,h=this.range.end+r,d={animation:!1,byUser:!0,event:t};this.range.setRange(a,h,d)}}function i(t){if(s.options.verticalScroll&&(t.preventDefault(),s.isActive())){var e=-t.target.scrollTop;s._setScrollTop(e),s._redraw(),s.emit("scrollSide",t)}}function o(t){if(t.preventDefault&&t.preventDefault(),!(!t.target.className.indexOf("vis")>-1||a))return t.dataTransfer.dropEffect="move",a=!0,!1}function n(t){t.preventDefault&&t.preventDefault(),t.stopPropagation&&t.stopPropagation();try{var e=JSON.parse(t.dataTransfer.getData("text"));if(!e||!e.content)return}catch(t){return!1}return a=!1,t.center={x:t.clientX,y:t.clientY},"item"!==e.target?s.itemSet._onAddItem(t):s.itemSet._onDropObjectOnItem(t),s.emit("drop",s.getEventProperties(t)),!1}this.dom={},this.dom.container=t,this.dom.root=document.createElement("div"),this.dom.background=document.createElement("div"),this.dom.backgroundVertical=document.createElement("div"),this.dom.backgroundHorizontal=document.createElement("div"),this.dom.centerContainer=document.createElement("div"),this.dom.leftContainer=document.createElement("div"),this.dom.rightContainer=document.createElement("div"),this.dom.center=document.createElement("div"),this.dom.left=document.createElement("div"),this.dom.right=document.createElement("div"),this.dom.top=document.createElement("div"),this.dom.bottom=document.createElement("div"),this.dom.shadowTop=document.createElement("div"),this.dom.shadowBottom=document.createElement("div"),this.dom.shadowTopLeft=document.createElement("div"),this.dom.shadowBottomLeft=document.createElement("div"),this.dom.shadowTopRight=document.createElement("div"),this.dom.shadowBottomRight=document.createElement("div"),this.dom.rollingModeBtn=document.createElement("div"),this.dom.root.className="vis-timeline",this.dom.background.className="vis-panel vis-background",this.dom.backgroundVertical.className="vis-panel vis-background vis-vertical",this.dom.backgroundHorizontal.className="vis-panel vis-background vis-horizontal",this.dom.centerContainer.className="vis-panel vis-center",this.dom.leftContainer.className="vis-panel vis-left",this.dom.rightContainer.className="vis-panel vis-right",this.dom.top.className="vis-panel vis-top",this.dom.bottom.className="vis-panel vis-bottom",this.dom.left.className="vis-content",this.dom.center.className="vis-content",this.dom.right.className="vis-content",this.dom.shadowTop.className="vis-shadow vis-top",this.dom.shadowBottom.className="vis-shadow vis-bottom",this.dom.shadowTopLeft.className="vis-shadow vis-top",this.dom.shadowBottomLeft.className="vis-shadow vis-bottom",this.dom.shadowTopRight.className="vis-shadow vis-top",this.dom.shadowBottomRight.className="vis-shadow vis-bottom",this.dom.rollingModeBtn.className="vis-rolling-mode-btn",this.dom.root.appendChild(this.dom.background),this.dom.root.appendChild(this.dom.backgroundVertical),this.dom.root.appendChild(this.dom.backgroundHorizontal),this.dom.root.appendChild(this.dom.centerContainer),this.dom.root.appendChild(this.dom.leftContainer),this.dom.root.appendChild(this.dom.rightContainer),this.dom.root.appendChild(this.dom.top),this.dom.root.appendChild(this.dom.bottom),this.dom.root.appendChild(this.dom.bottom),this.dom.root.appendChild(this.dom.rollingModeBtn),this.dom.centerContainer.appendChild(this.dom.center),this.dom.leftContainer.appendChild(this.dom.left),this.dom.rightContainer.appendChild(this.dom.right),this.dom.centerContainer.appendChild(this.dom.shadowTop),this.dom.centerContainer.appendChild(this.dom.shadowBottom),this.dom.leftContainer.appendCLine truncated
h.push(t(r.date()-1));break;case"week":h.push("vis-week"+r.format("w")),h.push(i(r)),h.push(t(r.week()));break;case"month":h.push("vis-"+r.format("MMMM").toLowerCase()),h.push(o(r)),h.push(t(r.month()));break;case"year":h.push("vis-year"+r.year()),h.push(function(t){return t.isSame(new Date,"year")?" vis-current-year":""}(r)),h.push(t(r.year()))}return h.filter(String).join(" ")},t.exports=o},function(t,e,i){function o(t,e){this.body=t,this.defaultOptions={rtl:!1,showCurrentTime:!0,moment:r,locales:a,locale:"en"},this.options=n.extend({},this.defaultOptions),this.offset=0,this._create(),this.setOptions(e)}var n=i(2),s=i(16),r=i(9),a=i(98);o.prototype=new s,o.prototype._create=function(){var t=document.createElement("div");t.className="vis-current-time",t.style.position="absolute",t.style.top="0px",t.style.height="100%",this.bar=t},o.prototype.destroy=function(){this.options.showCurrentTime=!1,this.redraw(),this.body=null},o.prototype.setOptions=function(t){t&&n.selectiveExtend(["rtl","showCurrentTime","moment","locale","locales"],this.options,t)},o.prototype.redraw=function(){if(this.options.showCurrentTime){var t=this.body.dom.backgroundVertical;this.bar.parentNode!=t&&(this.bar.parentNode&&this.bar.parentNode.removeChild(this.bar),t.appendChild(this.bar),this.start());var e=this.options.moment((new Date).valueOf()+this.offset),i=this.body.util.toScreen(e),o=this.options.locales[this.options.locale];o||(this.warned||(console.log("WARNING: options.locales['"+this.options.locale+"'] not found. See http://visjs.org/docs/timeline/#Localization"),this.warned=!0),o=this.options.locales.en);var n=o.current+" "+o.time+": "+e.format("dddd, MMMM Do YYYY, H:mm:ss");n=n.charAt(0).toUpperCase()+n.substring(1),this.options.rtl?this.bar.style.right=i+"px":this.bar.style.left=i+"px",this.bar.title=n}else this.bar.parentNode&&this.bar.parentNode.removeChild(this.bar),this.stop();return!1},o.prototype.start=function(){function t(){e.stop();var i=e.body.range.conversion(e.body.domProps.center.width).scale,o=1/i/10;o<30&&(o=30),o>1e3&&(o=1e3),e.redraw(),e.body.emitter.emit("currentTimeTick"),e.currentTimeTimer=setTimeout(t,o)}var e=this;t()},o.prototype.stop=function(){void 0!==this.currentTimeTimer&&(clearTimeout(this.currentTimeTimer),delete this.currentTimeTimer)},o.prototype.setCurrentTime=function(t){var e=n.convert(t,"Date").valueOf(),i=(new Date).valueOf();this.offset=e-i,this.redraw()},o.prototype.getCurrentTime=function(){return new Date((new Date).valueOf()+this.offset)},t.exports=o},function(t,e,i){function o(t,e,i){if(this.groupId=t,this.subgroups={},this.subgroupStack={},this.subgroupStackAll=!1,this.doInnerStack=!1,this.subgroupIndex=0,this.subgroupOrderer=e&&e.subgroupOrder,this.itemSet=i,this.isVisible=null,this.stackDirty=!0,e&&e.nestedGroups&&(this.nestedGroups=e.nestedGroups,0==e.showNested?this.showNested=!1:this.showNested=!0),e&&e.subgroupStack)if("boolean"==typeof e.subgroupStack)this.doInnerStack=e.subgroupStack,this.subgroupStackAll=e.subgroupStack;else for(var o in e.subgroupStack)this.subgroupStack[o]=e.subgroupStack[o],this.doInnerStack=this.doInnerStack||e.subgroupStack[o];this.nestedInGroup=null,this.dom={},this.props={label:{width:0,height:0}},this.className=null,this.items={},this.visibleItems=[],this.itemsInRange=[],this.orderedItems={byStart:[],byEnd:[]},this.checkRangedItems=!1;var n=this;this.itemSet.body.emitter.on("checkRangedItems",function(){n.checkRangedItems=!0}),this._create(),this.setData(e)}var n=i(8),s=function(t){return t&&t.__esModule?t:{default:t}}(n),r=i(2),a=i(100);o.prototype._create=function(){var t=document.createElement("div");this.itemSet.options.groupEditable.order?t.className="vis-label draggable":t.className="vis-label",this.dom.label=t;var e=document.createElement("div");e.className="vis-inner",t.appendChild(e),this.dom.inner=e;var i=document.createElement("div");i.className="vis-group",i["timeline-group"]=this,this.dom.foreground=i,this.dom.background=document.createElement("div"),this.dom.background.className="vis-group",this.dom.axis=document.createElement("div"),this.dom.axis.className="vis-group",this.dom.marker=document.createElement("div"),this.dom.marker.style.visibility="hidden",this.dom.marker.style.position="absolute",this.dom.marker.innerHTML="",this.dom.background.appendChild(this.dom.marker)},o.prototype.setData=function(t){var e,i;if(this.itemSet.options&&this.itemSet.options.groupTemplate?(i=this.itemSet.options.groupTemplate.bind(this),e=i(t,this.dom.inner)):e=t&&t.content,e instanceof Element){for(this.dom.inner.appendChild(e);this.dom.inner.firstChild;)this.dom.inner.removeChild(this.dom.inner.firstChild);this.dom.inner.appendChild(e)}else e instanceof Object?i(t,this.dom.inner):this.dom.inner.innerHTML=void 0!==e&&null!==e?e:this.groupId||"";if(this.dom.label.title=t&&t.title||"",this.dom.inner.firstChild?r.removeClassName(this.dom.inner,"vis-hidden"):r.addClassName(this.dom.inner,"vis-hidden"),t&&t.nestedGroups){this.nestedGroups&&this.nestedGroLine truncated
styles:e.styles||t.options.drawPoints.styles,size:e.size||t.options.drawPoints.size,className:e.className||t.className}}function s(t,e){var i=void 0;return t.options&&t.options.drawPoints&&t.options.drawPoints.onRender&&"function"==typeof t.options.drawPoints.onRender&&(i=t.options.drawPoints.onRender),e.group.options&&e.group.options.drawPoints&&e.group.options.drawPoints.onRender&&"function"==typeof e.group.options.drawPoints.onRender&&(i=e.group.options.drawPoints.onRender),i}var r=i(6),a=function(t){return t&&t.__esModule?t:{default:t}}(r),h=i(14);o.draw=function(t,e,i,o){o=o||0;for(var r=s(i,e),d=0;d<t.length;d++)if(r){var l=r(t[d],e);!0!==l&&"object"!==(void 0===l?"undefined":(0,a.default)(l))||h.drawPoint(t[d].screen_x+o,t[d].screen_y,n(e,l),i.svgElements,i.svg,t[d].label)}else h.drawPoint(t[d].screen_x+o,t[d].screen_y,n(e),i.svgElements,i.svg,t[d].label)},o.drawIcon=function(t,e,i,o,s,r){var a=.5*s,d=h.getSVGElement("rect",r.svgElements,r.svg);d.setAttributeNS(null,"x",e),d.setAttributeNS(null,"y",i-a),d.setAttributeNS(null,"width",o),d.setAttributeNS(null,"height",2*a),d.setAttributeNS(null,"class","vis-outline"),h.drawPoint(e+.5*o,i,n(t),r.svgElements,r.svg)},t.exports=o},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(23),m=o(f),v=function(t){function e(t,i,o){(0,a.default)(this,e);var n=(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o));return n.labelOffset=0,n.selected=!1,n}return(0,p.default)(e,t),(0,d.default)(e,[{key:"setOptions",value:function(t,e,i){this.options=t,void 0===e&&void 0===i||this.setImages(e,i)}},{key:"setImages",value:function(t,e){e&&this.selected?(this.imageObj=e,this.imageObjAlt=t):(this.imageObj=t,this.imageObjAlt=e)}},{key:"switchImages",value:function(t){var e=t&&!this.selected||!t&&this.selected;if(this.selected=t,void 0!==this.imageObjAlt&&e){var i=this.imageObj;this.imageObj=this.imageObjAlt,this.imageObjAlt=i}}},{key:"_resizeImage",value:function(){var t,e;if(!1===this.options.shapeProperties.useImageSize){var i=1,o=1;this.imageObj.width&&this.imageObj.height&&(this.imageObj.width>this.imageObj.height?i=this.imageObj.width/this.imageObj.height:o=this.imageObj.height/this.imageObj.width),t=2*this.options.size*i,e=2*this.options.size*o}else t=this.imageObj.width,e=this.imageObj.height;this.width=t,this.height=e,this.radius=.5*this.width}},{key:"_drawRawCircle",value:function(t,e,i,o){this.initContextForDraw(t,o),t.circle(e,i,o.size),this.performFill(t,o)}},{key:"_drawImageAtPosition",value:function(t,e){if(0!=this.imageObj.width){t.globalAlpha=1,this.enableShadow(t,e);var i=1;!0===this.options.shapeProperties.interpolation&&(i=this.imageObj.width/this.width/this.body.view.scale),this.imageObj.drawImageAtPosition(t,i,this.left,this.top,this.width,this.height),this.disableShadow(t,e)}}},{key:"_drawImageLabel",value:function(t,e,i,o,n){var s,r=0;if(void 0!==this.height){r=.5*this.height;var a=this.labelModule.getTextSize(t,o,n);a.lineCount>=1&&(r+=a.height/2)}s=i+r,this.options.label&&(this.labelOffset=r),this.labelModule.draw(t,e,s,o,n,"hanging")}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(19),s=o(n),r=i(6),a=o(r),h=i(29),d=o(h),l=i(0),u=o(l),c=i(1),p=o(c),f=i(2),m=i(117).default,v=i(48).default,g=i(215).default,y=i(217).default,b=i(218).default,_=i(219).default,w=function(){function t(e,i,o,n){if((0,u.default)(this,t),void 0===i)throw new Error("No body provided");this.options=f.bridgeObject(o),this.globalOptions=o,this.defaultOptions=n,this.body=i,this.id=void 0,this.fromId=void 0,this.toId=void 0,this.selected=!1,this.hover=!1,this.labelDirty=!0,this.baseWidth=this.options.width,this.baseFontSize=this.options.font.size,this.from=void 0,this.to=void 0,this.edgeType=void 0,this.connected=!1,this.labelModule=new m(this.body,this.options,!0),this.setOptions(e)}return(0,p.default)(t,[{key:"setOptions",value:function(e){if(e){t.parseOptions(this.options,e,!0,this.globalOptions),void 0!==e.id&&(this.id=e.id),void 0!==e.from&&(this.fromId=e.from),void 0!==e.to&&(this.toId=e.to),void 0!==e.title&&(this.title=e.title),void 0!==e.value&&(e.value=parseFloat(e.value));var i=[e,this.options,this.defaultOptions];this.chooser=v.choosify("edge",i),this.updateLabelModule(e);var o=this.updateEdgeType();return this._setInteractionWidths(),this.connect(),void 0===e.hidden&&void 0===e.physics||(o=!0),o}}},{key:"getFormattingValues",value:function(){var t=!0===this.options.arrows.to||!0===this.options.arrows.to.enabled,e=!0===this.options.arrows.from||!0===this.options.arrows.from.enabled,i=!0===this.options.arrows.middle||!0===this.options.arrows.middle.enabled,o=this.options.color.inherit,n={toArrow:t,toArrowScale:this.options.arrows.to.scaleFactor,toArrowType:this.options.arrows.to.type,middleArrow:i,middleArrowScalLine truncated
o.prototype.getArmLength=function(){return this.armLength},o.prototype.getCameraLocation=function(){return this.cameraLocation},o.prototype.getCameraRotation=function(){return this.cameraRotation},o.prototype.calculateCameraOrientation=function(){this.cameraLocation.x=this.armLocation.x-this.armLength*Math.sin(this.armRotation.horizontal)*Math.cos(this.armRotation.vertical),this.cameraLocation.y=this.armLocation.y-this.armLength*Math.cos(this.armRotation.horizontal)*Math.cos(this.armRotation.vertical),this.cameraLocation.z=this.armLocation.z+this.armLength*Math.sin(this.armRotation.vertical),this.cameraRotation.x=Math.PI/2-this.armRotation.vertical,this.cameraRotation.y=0,this.cameraRotation.z=-this.armRotation.horizontal;var t=this.cameraRotation.x,e=this.cameraRotation.z,i=this.cameraOffset.x,o=this.cameraOffset.y,n=Math.sin,s=Math.cos;this.cameraLocation.x=this.cameraLocation.x+i*s(e)+o*-n(e)*s(t),this.cameraLocation.y=this.cameraLocation.y+i*n(e)+o*s(e)*s(t),this.cameraLocation.z=this.cameraLocation.z+o*n(t)},t.exports=o},function(t,e,i){function o(t,e,i){this.dataGroup=t,this.column=e,this.graph=i,this.index=void 0,this.value=void 0,this.values=t.getDistinctValues(this.column),this.values.length>0&&this.selectValue(0),this.dataPoints=[],this.loaded=!1,this.onLoadCallback=void 0,i.animationPreload?(this.loaded=!1,this.loadInBackground()):this.loaded=!0}var n=i(12);o.prototype.isLoaded=function(){return this.loaded},o.prototype.getLoadedProgress=function(){for(var t=this.values.length,e=0;this.dataPoints[e];)e++;return Math.round(e/t*100)},o.prototype.getLabel=function(){return this.graph.filterLabel},o.prototype.getColumn=function(){return this.column},o.prototype.getSelectedValue=function(){if(void 0!==this.index)return this.values[this.index]},o.prototype.getValues=function(){return this.values},o.prototype.getValue=function(t){if(t>=this.values.length)throw new Error("Index out of range");return this.values[t]},o.prototype._getDataPoints=function(t){if(void 0===t&&(t=this.index),void 0===t)return[];var e;if(this.dataPoints[t])e=this.dataPoints[t];else{var i={};i.column=this.column,i.value=this.values[t];var o=new n(this.dataGroup.getDataSet(),{filter:function(t){return t[i.column]==i.value}}).get();e=this.dataGroup._getDataPoints(o),this.dataPoints[t]=e}return e},o.prototype.setOnLoadCallback=function(t){this.onLoadCallback=t},o.prototype.selectValue=function(t){if(t>=this.values.length)throw new Error("Index out of range");this.index=t,this.value=this.values[t]},o.prototype.loadInBackground=function(t){void 0===t&&(t=0);var e=this.graph.frame;if(t<this.values.length){void 0===e.progress&&(e.progress=document.createElement("DIV"),e.progress.style.position="absolute",e.progress.style.color="gray",e.appendChild(e.progress));var i=this.getLoadedProgress();e.progress.innerHTML="Loading animation... "+i+"%",e.progress.style.bottom="60px",e.progress.style.left="10px";var o=this;setTimeout(function(){o.loadInBackground(t+1)},10),this.loaded=!1}else this.loaded=!0,void 0!==e.progress&&(e.removeChild(e.progress),e.progress=void 0),this.onLoadCallback&&this.onLoadCallback()},t.exports=o},function(t,e,i){function o(t){this.active=!1,this.dom={container:t},this.dom.overlay=document.createElement("div"),this.dom.overlay.className="vis-overlay",this.dom.container.appendChild(this.dom.overlay),this.hammer=a(this.dom.overlay),this.hammer.on("tap",this._onTapOverlay.bind(this));var e=this;["tap","doubletap","press","pinch","pan","panstart","panmove","panend"].forEach(function(t){e.hammer.on(t,function(t){t.stopPropagation()})}),document&&document.body&&(this.onClick=function(i){n(i.target,t)||e.deactivate()},document.body.addEventListener("click",this.onClick)),void 0!==this.keycharm&&this.keycharm.destroy(),this.keycharm=s(),this.escListener=this.deactivate.bind(this)}function n(t,e){for(;t;){if(t===e)return!0;t=t.parentNode}return!1}var s=i(35),r=i(44),a=i(10),h=i(2);r(o.prototype),o.current=null,o.prototype.destroy=function(){this.deactivate(),this.dom.overlay.parentNode.removeChild(this.dom.overlay),this.onClick&&document.body.removeEventListener("click",this.onClick),this.hammer.destroy(),this.hammer=null},o.prototype.activate=function(){o.current&&o.current.deactivate(),o.current=this,this.active=!0,this.dom.overlay.style.display="none",h.addClassName(this.dom.container,"vis-active"),this.emit("change"),this.emit("activate"),this.keycharm.bind("esc",this.escListener)},o.prototype.deactivate=function(){this.active=!1,this.dom.overlay.style.display="",h.removeClassName(this.dom.container,"vis-active"),this.keycharm.unbind("esc",this.escListener),this.emit("change"),this.emit("deactivate")},o.prototype._onTapOverlay=function(t){this.activate(),t.stopPropagation()},t.exports=o},function(t,e,i){e.en={current:"current",time:"time"},e.en_EN=e.en,e.en_US=e.en,e.it={current:"attuale",time:"tempo"},e.it_IT=e.it,e.it_CH=e.it,e.nl={current:"huidige",time:"tijd"},e.nl_NL=e.nl,e.nl_BE=e.nl,e.de={current:"Aktuelle",time:"ZeiLine truncated
var i=this.options.multiselect?this.getSelection():[];if((t.srcEvent&&t.srcEvent.shiftKey||!1)&&this.options.multiselect){var o=this.itemsData.get(e.id).group,s=void 0;this.options.multiselectPerGroup&&i.length>0&&(s=this.itemsData.get(i[0]).group),this.options.multiselectPerGroup&&void 0!=s&&s!=o||i.push(e.id);var r=n._getItemRange(this.itemsData.get(i,this.itemOptions));if(!this.options.multiselectPerGroup||s==o){i=[];for(var a in this.items)if(this.items.hasOwnProperty(a)){var h=this.items[a],d=h.data.start,l=void 0!==h.data.end?h.data.end:d;!(d>=r.min&&l<=r.max)||this.options.multiselectPerGroup&&s!=this.itemsData.get(h.id).group||h instanceof _||i.push(h.id)}}}else{var u=i.indexOf(e.id);-1==u?i.push(e.id):i.splice(u,1)}this.setSelection(i),this.body.emitter.emit("select",{items:this.getSelection(),event:t})}}},n._getItemRange=function(t){var e=null,i=null;return t.forEach(function(t){(null==i||t.start<i)&&(i=t.start),void 0!=t.end?(null==e||t.end>e)&&(e=t.end):(null==e||t.start>e)&&(e=t.start)}),{min:i,max:e}},n.prototype.itemFromElement=function(t){for(var e=t;e;){if(e.hasOwnProperty("timeline-item"))return e["timeline-item"];e=e.parentNode}return null},n.prototype.itemFromTarget=function(t){return this.itemFromElement(t.target)},n.prototype.itemFromRelatedTarget=function(t){return this.itemFromElement(t.relatedTarget)},n.prototype.groupFromTarget=function(t){var e=t.center?t.center.y:t.clientY,i=this.groupIds;i.length<=0&&this.groupsData&&(i=this.groupsData.getIds({order:this.options.groupOrder}));for(var o=0;o<i.length;o++){var n=i[o],s=this.groups[n],r=s.dom.foreground,a=l.getAbsoluteTop(r);if(e>a&&e<a+r.offsetHeight)return s;if("top"===this.options.orientation.item){if(o===this.groupIds.length-1&&e>a)return s}else if(0===o&&e<a+r.offset)return s}return null},n.itemSetFromTarget=function(t){for(var e=t.target;e;){if(e.hasOwnProperty("timeline-itemset"))return e["timeline-itemset"];e=e.parentNode}return null},n.prototype._cloneItemData=function(t,e){var i=l.extend({},t);return e||(e=this.itemsData.getDataSet()._options.type),void 0!=i.start&&(i.start=l.convert(i.start,e&&e.start||"Date")),void 0!=i.end&&(i.end=l.convert(i.end,e&&e.end||"Date")),i},t.exports=n},function(t,e,i){e.orderByStart=function(t){t.sort(function(t,e){return t.data.start-e.data.start})},e.orderByEnd=function(t){t.sort(function(t,e){return("end"in t.data?t.data.end:t.data.start)-("end"in e.data?e.data.end:e.data.start)})},e.stack=function(t,i,o){if(o)for(var n=0;n<t.length;n++)t[n].top=null;for(var n=0;n<t.length;n++){var s=t[n];if(s.stack&&null===s.top){s.top=i.axis;do{for(var r=null,a=0,h=t.length;a<h;a++){var d=t[a];if(null!==d.top&&d!==s&&d.stack&&e.collision(s,d,i.item,d.options.rtl)){r=d;break}}null!=r&&(s.top=r.top+r.height+i.item.vertical)}while(r)}}},e.substack=function(t,i,o){for(var n=0;n<t.length;n++)t[n].top=null;var s=o.height;for(n=0;n<t.length;n++){var r=t[n];if(r.stack&&null===r.top){r.top=r.baseTop;do{for(var a=null,h=0,d=t.length;h<d;h++){var l=t[h];if(null!==l.top&&l!==r&&e.collision(r,l,i.item,l.options.rtl)){a=l;break}}null!=a&&(r.top=a.top+a.height+i.item.vertical),r.top+r.height>s&&(s=r.top+r.height)}while(a)}}o.height=s-o.top+.5*i.item.vertical},e.nostack=function(t,i,o,n){for(var s=0;s<t.length;s++)if(void 0==t[s].data.subgroup)t[s].top=i.item.vertical;else if(void 0!==t[s].data.subgroup&&n){var r=0;for(var a in o)o.hasOwnProperty(a)&&1==o[a].visible&&o[a].index<o[t[s].data.subgroup].index&&(r+=o[a].height,o[t[s].data.subgroup].top=r);t[s].top=r+.5*i.item.vertical}n||e.stackSubgroups(t,i,o)},e.stackSubgroups=function(t,i,o){for(var n in o)if(o.hasOwnProperty(n)){o[n].top=0;do{var s=null;for(var r in o)if(null!==o[r].top&&r!==n&&o[n].index>o[r].index&&e.collisionByTimes(o[n],o[r])){s=o[r];break}null!=s&&(o[n].top=s.top+s.height)}while(s)}for(var a=0;a<t.length;a++)void 0!==t[a].data.subgroup&&(t[a].top=o[t[a].data.subgroup].top+.5*i.item.vertical)},e.stackSubgroupsWithInnerStack=function(t,i,o){var n=!1,s=[];for(var r in o)o[r].hasOwnProperty("index")?s[o[r].index]=r:s.push(r);for(var a=0;a<s.length;a++)if(r=s[a],o.hasOwnProperty(r)){n=n||o[r].stack,o[r].top=0;for(var h in o)o[h].visible&&o[r].index>o[h].index&&(o[r].top+=o[h].height);for(var d=t[r],l=0;l<d.length;l++)void 0!==d[l].data.subgroup&&(d[l].top=o[d[l].data.subgroup].top+.5*i.item.vertical,o[r].stack&&(d[l].baseTop=d[l].top));n&&o[r].stack&&e.substack(t[r],i,o[r])}},e.collision=function(t,e,i,o){return o?t.right-i.horizontal+.001<e.right+e.width&&t.right+t.width+i.horizontal-.001>e.right&&t.top-i.vertical+.001<e.top+e.height&&t.top+t.height+i.vertical-.001>e.top:t.left-i.horizontal+.001<e.left+e.width&&t.left+t.width+i.horizontal-.001>e.left&&t.top-i.vertical+.001<e.top+e.height&&t.top+t.height+i.vertical-.001>e.top},e.collisionByTimes=function(t,e){return t.start<=e.start&&t.end>=e.start&&t.top<e.top+e.height&&t.top+t.height>e.top||e.start<=t.start&&e.end>=t.start&&e.top<t.top+t.height&&e.top+e.height>t.top}},function(t,e,i){function o(t,e,iLine truncated
f[n]=d[n];var m=i[l].length-a[l]--;i[l][m]=f}for(l in this.groups)this.groups.hasOwnProperty(l)&&(i.hasOwnProperty(l)||(i[l]=new Array(0)));for(l in i)if(i.hasOwnProperty(l))if(0==i[l].length)this.groups.hasOwnProperty(l)&&this._removeGroup(l);else{var v=void 0;void 0!=this.groupsData&&(v=this.groupsData.get(l)),void 0==v&&(v={id:l,content:this.options.defaultGroup+l}),this._updateGroup(v,l),this.groups[l].setItems(i[l])}this.forceGraphUpdate=!0,this.body.emitter.emit("_change",{queue:!0})}},o.prototype.redraw=function(){var t=!1;this.props.width=this.dom.frame.offsetWidth,this.props.height=this.body.domProps.centerContainer.height-this.body.domProps.border.top-this.body.domProps.border.bottom,t=this._isResized()||t;var e=this.body.range.end-this.body.range.start,i=e!=this.lastVisibleInterval;if(this.lastVisibleInterval=e,1==t&&(this.svg.style.width=r.option.asSize(3*this.props.width),this.svg.style.left=r.option.asSize(-this.props.width),-1==(this.options.height+"").indexOf("%")&&1!=this.updateSVGheightOnResize||(this.updateSVGheight=!0)),1==this.updateSVGheight?(this.options.graphHeight!=this.props.height+"px"&&(this.options.graphHeight=this.props.height+"px",this.svg.style.height=this.props.height+"px"),this.updateSVGheight=!1):this.svg.style.height=(""+this.options.graphHeight).replace("px","")+"px",1==t||1==i||1==this.abortedGraphUpdate||1==this.forceGraphUpdate)t=this._updateGraph()||t,this.forceGraphUpdate=!1;else if(0!=this.lastStart){var o=this.body.range.start-this.lastStart,n=this.body.range.end-this.body.range.start;if(0!=this.props.width){var s=this.props.width/n,a=o*s;this.svg.style.left=-this.props.width-a+"px"}}return this.legendLeft.redraw(),this.legendRight.redraw(),t},o.prototype._getSortedGroupIds=function(){var t=[];for(var e in this.groups)if(this.groups.hasOwnProperty(e)){var i=this.groups[e];1!=i.visible||void 0!==this.options.groups.visibility[e]&&1!=this.options.groups.visibility[e]||t.push({id:e,zIndex:i.options.zIndex})}r.insertSort(t,function(t,e){var i=t.zIndex,o=e.zIndex;return void 0===i&&(i=0),void 0===o&&(o=0),i==o?0:i<o?-1:1});for(var o=new Array(t.length),n=0;n<t.length;n++)o[n]=t[n].id;return o},o.prototype._updateGraph=function(){if(a.prepareElements(this.svgElements),0!=this.props.width&&null!=this.itemsData){var t,e,i={},o=this.body.util.toGlobalTime(-this.body.domProps.root.width),n=this.body.util.toGlobalTime(2*this.body.domProps.root.width),s=this._getSortedGroupIds();if(s.length>0){var r={};for(this._getRelevantData(s,r,o,n),this._applySampling(s,r),e=0;e<s.length;e++)this._convertXcoordinates(r[s[e]]);if(this._getYRanges(s,r,i),1==this._updateYAxis(s,i))return a.cleanupElements(this.svgElements),this.abortedGraphUpdate=!0,!0;this.abortedGraphUpdate=!1;var h=void 0;for(e=0;e<s.length;e++)t=this.groups[s[e]],!0===this.options.stack&&"line"===this.options.style&&(void 0!=t.options.excludeFromStacking&&t.options.excludeFromStacking||(void 0!=h&&(this._stack(r[t.id],r[h.id]),1==t.options.shaded.enabled&&"group"!==t.options.shaded.orientation&&("top"==t.options.shaded.orientation&&"group"!==h.options.shaded.orientation?(h.options.shaded.orientation="group",h.options.shaded.groupId=t.id):(t.options.shaded.orientation="group",t.options.shaded.groupId=h.id))),h=t)),this._convertYcoordinates(r[s[e]],t);var d={};for(e=0;e<s.length;e++)if(t=this.groups[s[e]],"line"===t.options.style&&1==t.options.shaded.enabled){var l=r[s[e]];if(null==l||0==l.length)continue;if(d.hasOwnProperty(s[e])||(d[s[e]]=m.calcPath(l,t)),"group"===t.options.shaded.orientation){var u=t.options.shaded.groupId;if(-1===s.indexOf(u)){console.log(t.id+": Unknown shading group target given:"+u);continue}d.hasOwnProperty(u)||(d[u]=m.calcPath(r[u],this.groups[u])),m.drawShading(d[s[e]],t,d[u],this.framework)}else m.drawShading(d[s[e]],t,void 0,this.framework)}for(f.draw(s,r,this.framework),e=0;e<s.length;e++)if(t=this.groups[s[e]],r[s[e]].length>0)switch(t.options.style){case"line":d.hasOwnProperty(s[e])||(d[s[e]]=m.calcPath(r[s[e]],t)),m.draw(d[s[e]],t,this.framework);case"point":case"points":"point"!=t.options.style&&"points"!=t.options.style&&1!=t.options.drawPoints.enabled||v.draw(r[s[e]],t,this.framework)}}}return a.cleanupElements(this.svgElements),!1},o.prototype._stack=function(t,e){var i,o,n,s,r;i=0;for(var a=0;a<t.length;a++){s=void 0,r=void 0;for(var h=i;h<e.length;h++){if(e[h].x===t[a].x){s=e[h],r=e[h],i=h;break}if(e[h].x>t[a].x){r=e[h],s=0==h?r:e[h-1],i=h;break}}void 0===r&&(s=e[e.length-1],r=e[e.length-1]),o=r.x-s.x,n=r.y-s.y,t[a].y=0==o?t[a].orginalY+r.y:t[a].orginalY+n/o*(t[a].x-s.x)+s.y}},o.prototype._getRelevantData=function(t,e,i,o){var n,s,a,h;if(t.length>0)for(s=0;s<t.length;s++){n=this.groups[t[s]];var d=n.getItems();if(1==n.options.sort){var l=function(t,e){return t.getTime()==e.getTime()?0:t<e?-1:1},u=Math.max(0,r.binarySearchValue(d,i,"x","before",l)),c=Math.min(d.length,r.binarySearchValue(d,o,"x","after",l)+1);c<=0&&(c=d.length);var p=new Array(c-u);for(a=u;a<c;a++)h=n.itemsData[a],pLine truncated
d=Math.sqrt(Math.pow(o.screen_x-n.screen_x,2)+Math.pow(o.screen_y-n.screen_y,2)),l=Math.sqrt(Math.pow(n.screen_x-s.screen_x,2)+Math.pow(n.screen_y-s.screen_y,2)),u=Math.sqrt(Math.pow(s.screen_x-r.screen_x,2)+Math.pow(s.screen_y-r.screen_y,2)),v=Math.pow(u,i),y=Math.pow(u,2*i),g=Math.pow(l,i),b=Math.pow(l,2*i),w=Math.pow(d,i),_=Math.pow(d,2*i),c=2*_+3*w*g+b,p=2*y+3*v*g+b,f=3*w*(w+g),f>0&&(f=1/f),m=3*v*(v+g),m>0&&(m=1/m),a={screen_x:(-b*o.screen_x+c*n.screen_x+_*s.screen_x)*f,screen_y:(-b*o.screen_y+c*n.screen_y+_*s.screen_y)*f},h={screen_x:(y*n.screen_x+p*s.screen_x-b*r.screen_x)*m,screen_y:(y*n.screen_y+p*s.screen_y-b*r.screen_y)*m},0==a.screen_x&&0==a.screen_y&&(a=n),0==h.screen_x&&0==h.screen_y&&(h=s),x.push([a.screen_x,a.screen_y]),x.push([h.screen_x,h.screen_y]),x.push([s.screen_x,s.screen_y]);return x},o._linear=function(t){for(var e=[],i=0;i<t.length;i++)e.push([t[i].screen_x,t[i].screen_y]);return e},t.exports=o},function(t,e,i){function o(t,e,i,o){this.body=t,this.defaultOptions={enabled:!1,icons:!0,iconSize:20,iconSpacing:6,left:{visible:!0,position:"top-left"},right:{visible:!0,position:"top-right"}},this.side=i,this.options=r.extend({},this.defaultOptions),this.linegraphOptions=o,this.svgElements={},this.dom={},this.groups={},this.amountOfGroups=0,this._create(),this.framework={svg:this.svg,svgElements:this.svgElements,options:this.options,groups:this.groups},this.setOptions(e)}var n=i(8),s=function(t){return t&&t.__esModule?t:{default:t}}(n),r=i(2),a=i(14),h=i(16);o.prototype=new h,o.prototype.clear=function(){this.groups={},this.amountOfGroups=0},o.prototype.addGroup=function(t,e){1!=e.options.excludeFromLegend&&(this.groups.hasOwnProperty(t)||(this.groups[t]=e),this.amountOfGroups+=1)},o.prototype.updateGroup=function(t,e){this.groups[t]=e},o.prototype.removeGroup=function(t){this.groups.hasOwnProperty(t)&&(delete this.groups[t],this.amountOfGroups-=1)},o.prototype._create=function(){this.dom.frame=document.createElement("div"),this.dom.frame.className="vis-legend",this.dom.frame.style.position="absolute",this.dom.frame.style.top="10px",this.dom.frame.style.display="block",this.dom.textArea=document.createElement("div"),this.dom.textArea.className="vis-legend-text",this.dom.textArea.style.position="relative",this.dom.textArea.style.top="0px",this.svg=document.createElementNS("http://www.w3.org/2000/svg","svg"),this.svg.style.position="absolute",this.svg.style.top="0px",this.svg.style.width=this.options.iconSize+5+"px",this.svg.style.height="100%",this.dom.frame.appendChild(this.svg),this.dom.frame.appendChild(this.dom.textArea)},o.prototype.hide=function(){this.dom.frame.parentNode&&this.dom.frame.parentNode.removeChild(this.dom.frame)},o.prototype.show=function(){this.dom.frame.parentNode||this.body.dom.center.appendChild(this.dom.frame)},o.prototype.setOptions=function(t){var e=["enabled","orientation","icons","left","right"];r.selectiveDeepExtend(e,this.options,t)},o.prototype.redraw=function(){var t=0,e=(0,s.default)(this.groups);e.sort(function(t,e){return t<e?-1:1});for(var i=0;i<e.length;i++){var o=e[i];1!=this.groups[o].visible||void 0!==this.linegraphOptions.visibility[o]&&1!=this.linegraphOptions.visibility[o]||t++}if(0==this.options[this.side].visible||0==this.amountOfGroups||0==this.options.enabled||0==t)this.hide();else{if(this.show(),"top-left"==this.options[this.side].position||"bottom-left"==this.options[this.side].position?(this.dom.frame.style.left="4px",this.dom.frame.style.textAlign="left",this.dom.textArea.style.textAlign="left",this.dom.textArea.style.left=this.options.iconSize+15+"px",this.dom.textArea.style.right="",this.svg.style.left="0px",this.svg.style.right=""):(this.dom.frame.style.right="4px",this.dom.frame.style.textAlign="right",this.dom.textArea.style.textAlign="right",this.dom.textArea.style.right=this.options.iconSize+15+"px",this.dom.textArea.style.left="",this.svg.style.right="0px",this.svg.style.left=""),"top-left"==this.options[this.side].position||"top-right"==this.options[this.side].position)this.dom.frame.style.top=4-Number(this.body.dom.center.style.top.replace("px",""))+"px",this.dom.frame.style.bottom="";else{var n=this.body.domProps.center.height-this.body.domProps.centerContainer.height;this.dom.frame.style.bottom=4+n+Number(this.body.dom.center.style.top.replace("px",""))+"px",this.dom.frame.style.top=""}0==this.options.icons?(this.dom.frame.style.width=this.dom.textArea.offsetWidth+10+"px",this.dom.textArea.style.right="",this.dom.textArea.style.left="",this.svg.style.width="0px"):(this.dom.frame.style.width=this.options.iconSize+15+this.dom.textArea.offsetWidth+10+"px",this.drawLegendIcons());var r="";for(i=0;i<e.length;i++)o=e[i],1!=this.groups[o].visible||void 0!==this.linegraphOptions.visibility[o]&&1!=this.linegraphOptions.visibility[o]||(r+=this.groups[o].content+"<br />");this.dom.textArea.innerHTML=r,this.dom.textArea.style.lineHeight=.75*this.options.iconSize+this.options.iconSpacing+"px"}},o.prototype.drawLegendIcons=function(){if(thLine truncated
value:function(t,e,i,o,n){this.enableShadow(t,e),t.beginPath(),t.arc(i,o,n,0,2*Math.PI,!1),t.stroke(),this.disableShadow(t,e)}},{key:"getDistanceToEdge",value:function(t,e,i,o,n,r,a,h){var d=0;if(this.from!=this.to)d=this._getDistanceToEdge(t,e,i,o,n,r,a);else{var l=this._getCircleData(void 0),u=(0,s.default)(l,3),c=u[0],p=u[1],f=u[2],m=c-n,v=p-r;d=Math.abs(Math.sqrt(m*m+v*v)-f)}return d}},{key:"_getDistanceToLine",value:function(t,e,i,o,n,s){var r=i-t,a=o-e,h=r*r+a*a,d=((n-t)*r+(s-e)*a)/h;d>1?d=1:d<0&&(d=0);var l=t+d*r,u=e+d*a,c=l-n,p=u-s;return Math.sqrt(c*c+p*p)}},{key:"getArrowData",value:function(t,e,i,o,n,r){var a=void 0,h=void 0,d=void 0,l=void 0,u=void 0,c=void 0,p=void 0,f=r.width;if("from"===e?(d=this.from,l=this.to,u=.1,c=r.fromArrowScale,p=r.fromArrowType):"to"===e?(d=this.to,l=this.from,u=-.1,c=r.toArrowScale,p=r.toArrowType):(d=this.to,l=this.from,c=r.middleArrowScale,p=r.middleArrowType),d!=l)if("middle"!==e)if(!0===this.options.smooth.enabled){h=this.findBorderPosition(d,t,{via:i});var m=this.getPoint(Math.max(0,Math.min(1,h.t+u)),i);a=Math.atan2(h.y-m.y,h.x-m.x)}else a=Math.atan2(d.y-l.y,d.x-l.x),h=this.findBorderPosition(d,t);else a=Math.atan2(d.y-l.y,d.x-l.x),h=this.getPoint(.5,i);else{var v=this._getCircleData(t),g=(0,s.default)(v,3),y=g[0],b=g[1],_=g[2];"from"===e?(h=this.findBorderPosition(this.from,t,{x:y,y:b,low:.25,high:.6,direction:-1}),a=-2*h.t*Math.PI+1.5*Math.PI+.1*Math.PI):"to"===e?(h=this.findBorderPosition(this.from,t,{x:y,y:b,low:.6,high:1,direction:1}),a=-2*h.t*Math.PI+1.5*Math.PI-1.1*Math.PI):(h=this._pointOnCircle(y,b,_,.175),a=3.9269908169872414)}"middle"===e&&c<0&&(f*=-1);var w=15*c+3*f;return{point:h,core:{x:h.x-.9*w*Math.cos(a),y:h.y-.9*w*Math.sin(a)},angle:a,length:w,type:p}}},{key:"drawArrowHead",value:function(t,e,i,o,n){t.strokeStyle=this.getColor(t,e,i,o),t.fillStyle=t.strokeStyle,t.lineWidth=e.width,u.draw(t,n),this.enableShadow(t,e),t.fill(),this.disableShadow(t,e)}},{key:"enableShadow",value:function(t,e){!0===e.shadow&&(t.shadowColor=e.shadowColor,t.shadowBlur=e.shadowSize,t.shadowOffsetX=e.shadowX,t.shadowOffsetY=e.shadowY)}},{key:"disableShadow",value:function(t,e){!0===e.shadow&&(t.shadowColor="rgba(0,0,0,0)",t.shadowBlur=0,t.shadowOffsetX=0,t.shadowOffsetY=0)}}]),t}();e.default=c},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(4),a=o(r),h=i(5),d=o(h),l=i(0),u=o(l),c=i(1),p=o(c),f=function(){function t(){(0,u.default)(this,t)}return(0,p.default)(t,null,[{key:"transform",value:function(t,e){t instanceof Array||(t=[t]);for(var i=e.point.x,o=e.point.y,n=e.angle,s=e.length,r=0;r<t.length;++r){var a=t[r],h=a.x*Math.cos(n)-a.y*Math.sin(n),d=a.x*Math.sin(n)+a.y*Math.cos(n);a.x=i+s*h,a.y=o+s*d}}},{key:"drawPath",value:function(t,e){t.beginPath(),t.moveTo(e[0].x,e[0].y);for(var i=1;i<e.length;++i)t.lineTo(e[i].x,e[i].y);t.closePath()}}]),t}(),m=function(t){function e(){return(0,u.default)(this,e),(0,a.default)(this,(e.__proto__||(0,s.default)(e)).apply(this,arguments))}return(0,d.default)(e,t),(0,p.default)(e,null,[{key:"draw",value:function(t,e){var i=[{x:0,y:0},{x:-1,y:.3},{x:-.9,y:0},{x:-1,y:-.3}];f.transform(i,e),f.drawPath(t,i)}}]),e}(f),v=function(){function t(){(0,u.default)(this,t)}return(0,p.default)(t,null,[{key:"draw",value:function(t,e){var i={x:-.4,y:0};f.transform(i,e),t.circle(i.x,i.y,.4*e.length)}}]),t}(),g=function(){function t(){(0,u.default)(this,t)}return(0,p.default)(t,null,[{key:"draw",value:function(t,e){var i=[{x:0,y:.5},{x:0,y:-.5},{x:-.15,y:-.5},{x:-.15,y:.5}];f.transform(i,e),f.drawPath(t,i)}}]),t}(),y=function(){function t(){(0,u.default)(this,t)}return(0,p.default)(t,null,[{key:"draw",value:function(t,e){var i;switch(e.type&&(i=e.type.toLowerCase()),i){case"circle":v.draw(t,e);break;case"bar":g.draw(t,e);break;case"arrow":default:m.draw(t,e)}}}]),t}();e.default=y},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=function(){function t(e,i,o){(0,s.default)(this,t),this.body=e,this.physicsBody=i,this.barnesHutTree,this.setOptions(o),this.randomSeed=5}return(0,a.default)(t,[{key:"setOptions",value:function(t){this.options=t,this.thetaInversed=1/this.options.theta,this.overlapAvoidanceFactor=1-Math.max(0,Math.min(1,this.options.avoidOverlap))}},{key:"seededRandom",value:function(){var t=1e4*Math.sin(this.randomSeed++);return t-Math.floor(t)}},{key:"solve",value:function(){if(0!==this.options.gravitationalConstant&&this.physicsBody.physicsNodeIndices.length>0){var t=void 0,e=this.body.nodes,i=this.physicsBody.physicsNodeIndices,o=i.length,n=this._formBarnesHutTree(e,i);this.barnesHutTree=n;for(var s=0;s<o;s++)t=e[i[s]],t.options.mass>0&&this._getForceContributions(n.root,t)}}},{key:"_getForceContributions",value:function(t,e){this._getForceContribution(t.children.NW,e),this._getForceContribution(t.children.NE,e),this._getForceContribution(t.children.SW,Line truncated
;for(var a in arguments[0])n+=a+": "+arguments[0][a]+", ";n=n.slice(0,-2)}else n=arguments[r];s.push(n)}x(t+"\nArguments: "+Array.prototype.slice.call(s).join("")+"\n"+(new Error).stack),o=!1}return i.apply(this,arguments)},i)}function S(t,i){null!=e.deprecationHandler&&e.deprecationHandler(t,i),Po[t]||(x(i),Po[t]=!0)}function D(t){return t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function M(t){var e,i;for(i in t)e=t[i],D(e)?this[i]=e:this["_"+i]=e;this._config=t,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)}function C(t,e){var i,n=l({},t);for(i in e)d(e,i)&&(o(t[i])&&o(e[i])?(n[i]={},l(n[i],t[i]),l(n[i],e[i])):null!=e[i]?n[i]=e[i]:delete n[i]);for(i in t)d(t,i)&&!d(e,i)&&o(t[i])&&(n[i]=l({},n[i]));return n}function O(t){null!=t&&this.set(t)}function E(t,e,i){var o=this._calendar[t]||this._calendar.sameElse;return D(o)?o.call(e,i):o}function T(t){var e=this._longDateFormat[t],i=this._longDateFormat[t.toUpperCase()];return e||!i?e:(this._longDateFormat[t]=i.replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t])}function P(){return this._invalidDate}function I(t){return this._ordinal.replace("%d",t)}function N(t,e,i,o){var n=this._relativeTime[i];return D(n)?n(t,e,i,o):n.replace(/%d/i,t)}function R(t,e){var i=this._relativeTime[t>0?"future":"past"];return D(i)?i(e):i.replace(/%s/i,e)}function A(t,e){var i=t.toLowerCase();Lo[i]=Lo[i+"s"]=Lo[e]=t}function z(t){return"string"==typeof t?Lo[t]||Lo[t.toLowerCase()]:void 0}function L(t){var e,i,o={};for(i in t)d(t,i)&&(e=z(i))&&(o[e]=t[i]);return o}function F(t,e){Fo[t]=e}function B(t){var e=[];for(var i in t)e.push({unit:i,priority:Fo[i]});return e.sort(function(t,e){return t.priority-e.priority}),e}function j(t,e,i){var o=""+Math.abs(t),n=e-o.length;return(t>=0?i?"+":"":"-")+Math.pow(10,Math.max(0,n)).toString().substr(1)+o}function H(t,e,i,o){var n=o;"string"==typeof o&&(n=function(){return this[o]()}),t&&(Wo[t]=n),e&&(Wo[e[0]]=function(){return j(n.apply(this,arguments),e[1],e[2])}),i&&(Wo[i]=function(){return this.localeData().ordinal(n.apply(this,arguments),t)})}function W(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function Y(t){var e,i,o=t.match(Bo);for(e=0,i=o.length;e<i;e++)Wo[o[e]]?o[e]=Wo[o[e]]:o[e]=W(o[e]);return function(e){var n,s="";for(n=0;n<i;n++)s+=D(o[n])?o[n].call(e,t):o[n];return s}}function G(t,e){return t.isValid()?(e=V(e,t.localeData()),Ho[e]=Ho[e]||Y(e),Ho[e](t)):t.localeData().invalidDate()}function V(t,e){function i(t){return e.longDateFormat(t)||t}var o=5;for(jo.lastIndex=0;o>=0&&jo.test(t);)t=t.replace(jo,i),jo.lastIndex=0,o-=1;return t}function U(t,e,i){an[t]=D(e)?e:function(t,o){return t&&i?i:e}}function q(t,e){return d(an,t)?an[t](e._strict,e._locale):new RegExp(X(t))}function X(t){return Z(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,i,o,n){return e||i||o||n}))}function Z(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function K(t,e){var i,o=e;for("string"==typeof t&&(t=[t]),r(e)&&(o=function(t,i){i[e]=_(t)}),i=0;i<t.length;i++)hn[t[i]]=o}function J(t,e){K(t,function(t,i,o,n){o._w=o._w||{},e(t,o._w,o,n)})}function $(t,e,i){null!=e&&d(hn,t)&&hn[t](e,i._a,i,t)}function Q(t){return tt(t)?366:365}function tt(t){return t%4==0&&t%100!=0||t%400==0}function et(){return tt(this.year())}function it(t,i){return function(o){return null!=o?(nt(this,t,o),e.updateOffset(this,i),this):ot(this,t)}}function ot(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function nt(t,e,i){t.isValid()&&!isNaN(i)&&("FullYear"===e&&tt(t.year())?t._d["set"+(t._isUTC?"UTC":"")+e](i,t.month(),ht(i,t.month())):t._d["set"+(t._isUTC?"UTC":"")+e](i))}function st(t){return t=z(t),D(this[t])?this[t]():this}function rt(t,e){if("object"==typeof t){t=L(t);for(var i=B(t),o=0;o<i.length;o++)this[i[o].unit](t[i[o].unit])}else if(t=z(t),D(this[t]))return this[t](e);return this}function at(t,e){return(t%e+e)%e}function ht(t,e){if(isNaN(t)||isNaN(e))return NaN;var i=at(e,12);return t+=(e-i)/12,1===i?tt(t)?29:28:31-i%7%2}function dt(t,e){return t?i(this._months)?this._months[t.month()]:this._months[(this._months.isFormat||_n).test(e)?"format":"standalone"][t.month()]:i(this._months)?this._months:this._months.standalone}function lt(t,e){return t?i(this._monthsShort)?this._monthsShort[t.month()]:this._monthsShort[_n.test(e)?"format":"standalone"][t.month()]:i(this._monthsShort)?this._monthsShort:this._monthsShort.standalone}function ut(t,e,i){var o,n,s,r=t.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],o=0;o<12;++o)s=u([2e3,o]),this._shortMonthsParse[o]=this.monthsShort(s,"").toLocaleLowerCase(),this._longMonthsParse[o]=this.months(s,"").toLocaleLowerCase();return i?"MMM"===e?(n=yn.call(this._shortMonthsParse,r),-1!==n?n:null):(n=yn.call(this._longMonthsParse,r),-1!==n?n:null):"MMMLine truncated
r-=ho(co(n)),o=b(a/12),a%=12,h.days=r,h.months=a,h.years=o,this}function uo(t){return 4800*t/146097}function co(t){return 146097*t/4800}function po(t){if(!this.isValid())return NaN;var e,i,o=this._milliseconds;if("month"===(t=z(t))||"year"===t)return e=this._days+o/864e5,i=this._months+uo(e),"month"===t?i:i/12;switch(e=this._days+Math.round(co(this._months)),t){case"week":return e/7+o/6048e5;case"day":return e+o/864e5;case"hour":return 24*e+o/36e5;case"minute":return 1440*e+o/6e4;case"second":return 86400*e+o/1e3;case"millisecond":return Math.floor(864e5*e)+o;default:throw new Error("Unknown unit "+t)}}function fo(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*_(this._months/12):NaN}function mo(t){return function(){return this.as(t)}}function vo(){return Ke(this)}function go(t){return t=z(t),this.isValid()?this[t+"s"]():NaN}function yo(t){return function(){return this.isValid()?this._data[t]:NaN}}function bo(){return b(this.days()/7)}function _o(t,e,i,o,n){return n.relativeTime(e||1,!!i,t,o)}function wo(t,e,i){var o=Ke(t).abs(),n=Ds(o.as("s")),s=Ds(o.as("m")),r=Ds(o.as("h")),a=Ds(o.as("d")),h=Ds(o.as("M")),d=Ds(o.as("y")),l=n<=Ms.ss&&["s",n]||n<Ms.s&&["ss",n]||s<=1&&["m"]||s<Ms.m&&["mm",s]||r<=1&&["h"]||r<Ms.h&&["hh",r]||a<=1&&["d"]||a<Ms.d&&["dd",a]||h<=1&&["M"]||h<Ms.M&&["MM",h]||d<=1&&["y"]||["yy",d];return l[2]=e,l[3]=+t>0,l[4]=i,_o.apply(null,l)}function xo(t){return void 0===t?Ds:"function"==typeof t&&(Ds=t,!0)}function ko(t,e){return void 0!==Ms[t]&&(void 0===e?Ms[t]:(Ms[t]=e,"s"===t&&(Ms.ss=e-1),!0))}function So(t){if(!this.isValid())return this.localeData().invalidDate();var e=this.localeData(),i=wo(this,!t,e);return t&&(i=e.pastFuture(+this,i)),e.postformat(i)}function Do(t){return(t>0)-(t<0)||+t}function Mo(){if(!this.isValid())return this.localeData().invalidDate();var t,e,i,o=Cs(this._milliseconds)/1e3,n=Cs(this._days),s=Cs(this._months);t=b(o/60),e=b(t/60),o%=60,t%=60,i=b(s/12),s%=12;var r=i,a=s,h=n,d=e,l=t,u=o?o.toFixed(3).replace(/\.?0+$/,""):"",c=this.asSeconds();if(!c)return"P0D";var p=c<0?"-":"",f=Do(this._months)!==Do(c)?"-":"",m=Do(this._days)!==Do(c)?"-":"",v=Do(this._milliseconds)!==Do(c)?"-":"";return p+"P"+(r?f+r+"Y":"")+(a?f+a+"M":"")+(h?m+h+"D":"")+(d||l||u?"T":"")+(d?v+d+"H":"")+(l?v+l+"M":"")+(u?v+u+"S":"")}var Co,Oo;Oo=Array.prototype.some?Array.prototype.some:function(t){for(var e=Object(this),i=e.length>>>0,o=0;o<i;o++)if(o in e&&t.call(this,e[o],o,e))return!0;return!1};var Eo=e.momentProperties=[],To=!1,Po={};e.suppressDeprecationWarnings=!1,e.deprecationHandler=null;var Io;Io=Object.keys?Object.keys:function(t){var e,i=[];for(e in t)d(t,e)&&i.push(e);return i};var No={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Ro={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Ao=/\d{1,2}/,zo={future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Lo={},Fo={},Bo=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,jo=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Ho={},Wo={},Yo=/\d/,Go=/\d\d/,Vo=/\d{3}/,Uo=/\d{4}/,qo=/[+-]?\d{6}/,Xo=/\d\d?/,Zo=/\d\d\d\d?/,Ko=/\d\d\d\d\d\d?/,Jo=/\d{1,3}/,$o=/\d{1,4}/,Qo=/[+-]?\d{1,6}/,tn=/\d+/,en=/[+-]?\d+/,on=/Z|[+-]\d\d:?\d\d/gi,nn=/Z|[+-]\d\d(?::?\d\d)?/gi,sn=/[+-]?\d+(\.\d{1,3})?/,rn=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,an={},hn={},dn=0,ln=1,un=2,cn=3,pn=4,fn=5,mn=6,vn=7,gn=8;H("Y",0,0,function(){var t=this.year();return t<=9999?""+t:"+"+t}),H(0,["YY",2],0,function(){return this.year()%100}),H(0,["YYYY",4],0,"year"),H(0,["YYYYY",5],0,"year"),H(0,["YYYYYY",6,!0],0,"year"),A("year","y"),F("year",1),U("Y",en),U("YY",Xo,Go),U("YYYY",$o,Uo),U("YYYYY",Qo,qo),U("YYYYYY",Qo,qo),K(["YYYYY","YYYYYY"],dn),K("YYYY",function(t,i){i[dn]=2===t.length?e.parseTwoDigitYear(t):_(t)}),K("YY",function(t,i){i[dn]=e.parseTwoDigitYear(t)}),K("Y",function(t,e){e[dn]=parseInt(t,10)}),e.parseTwoDigitYear=function(t){return _(t)+(_(t)>68?1900:2e3)};var yn,bn=it("FullYear",!0);yn=Array.prototype.indexOf?Array.prototype.indexOf:function(t){var e;for(e=0;e<this.length;++e)if(this[e]===t)return e;return-1},H("M",["MM",2],"Mo",function(){return this.month()+1}),H("MMM",0,0,function(t){return this.localeData().monthsShort(this,t)}),H("MMMM",0,0,function(t){return this.localeData().months(this,t)}),A("month","M"),F("month",8),U("M",Xo),U("MM",Xo,Go),U("MMM",function(t,e){return e.monthsShortRegex(t)}),U("MMMM",function(t,e){return e.monthsRegex(t)}),K(["M","MM"],function(t,e){e[ln]=_(t)-1}),K(["MMM","MMMM"],function(t,e,i,o){var nLine truncated
;Math.cos(2*o)>0?(t.textAlign="center",t.textBaseline="top",s.y+=n):Math.sin(2*o)<0?(t.textAlign="right",t.textBaseline="middle"):(t.textAlign="left",t.textBaseline="middle"),t.fillStyle=this.axisColor,t.fillText(i,s.x,s.y)},o.prototype.drawAxisLabelY=function(t,e,i,o,n){void 0===n&&(n=0);var s=this._convert3Dto2D(e);Math.cos(2*o)<0?(t.textAlign="center",t.textBaseline="top",s.y+=n):Math.sin(2*o)>0?(t.textAlign="right",t.textBaseline="middle"):(t.textAlign="left",t.textBaseline="middle"),t.fillStyle=this.axisColor,t.fillText(i,s.x,s.y)},o.prototype.drawAxisLabelZ=function(t,e,i,o){void 0===o&&(o=0);var n=this._convert3Dto2D(e);t.textAlign="right",t.textBaseline="middle",t.fillStyle=this.axisColor,t.fillText(i,n.x-o,n.y)},o.prototype._line3d=function(t,e,i,o){var n=this._convert3Dto2D(e),s=this._convert3Dto2D(i);this._line(t,n,s,o)},o.prototype._redrawAxis=function(){var t,e,i,o,n,s,r,a,h,d,c,f=this._getContext();f.font=24/this.camera.getArmLength()+"px arial";var m,v=.025/this.scale.x,g=.025/this.scale.y,y=5/this.camera.getArmLength(),b=this.camera.getArmRotation().horizontal,_=new u(Math.cos(b),Math.sin(b)),w=this.xRange,x=this.yRange,k=this.zRange;for(f.lineWidth=1,o=void 0===this.defaultXStep,i=new p(w.min,w.max,this.xStep,o),i.start(!0);!i.end();){var S=i.getCurrent();if(this.showGrid?(t=new l(S,x.min,k.min),e=new l(S,x.max,k.min),this._line3d(f,t,e,this.gridColor)):this.showXAxis&&(t=new l(S,x.min,k.min),e=new l(S,x.min+v,k.min),this._line3d(f,t,e,this.axisColor),t=new l(S,x.max,k.min),e=new l(S,x.max-v,k.min),this._line3d(f,t,e,this.axisColor)),this.showXAxis){r=_.x>0?x.min:x.max,m=new l(S,r,k.min);var D=" "+this.xValueLabel(S)+" ";this.drawAxisLabelX(f,m,D,b,y)}i.next()}for(f.lineWidth=1,o=void 0===this.defaultYStep,i=new p(x.min,x.max,this.yStep,o),i.start(!0);!i.end();){var M=i.getCurrent();if(this.showGrid?(t=new l(w.min,M,k.min),e=new l(w.max,M,k.min),this._line3d(f,t,e,this.gridColor)):this.showYAxis&&(t=new l(w.min,M,k.min),e=new l(w.min+g,M,k.min),this._line3d(f,t,e,this.axisColor),t=new l(w.max,M,k.min),e=new l(w.max-g,M,k.min),this._line3d(f,t,e,this.axisColor)),this.showYAxis){s=_.y>0?w.min:w.max,m=new l(s,M,k.min);var C=" "+this.yValueLabel(M)+" ";this.drawAxisLabelY(f,m,C,b,y)}i.next()}if(this.showZAxis){for(f.lineWidth=1,o=void 0===this.defaultZStep,i=new p(k.min,k.max,this.zStep,o),i.start(!0),s=_.x>0?w.min:w.max,r=_.y<0?x.min:x.max;!i.end();){var O=i.getCurrent(),E=new l(s,r,O),T=this._convert3Dto2D(E);e=new u(T.x-y,T.y),this._line(f,T,e,this.axisColor);var P=this.zValueLabel(O)+" ";this.drawAxisLabelZ(f,E,P,5),i.next()}f.lineWidth=1,t=new l(s,r,k.min),e=new l(s,r,k.max),this._line3d(f,t,e,this.axisColor)}if(this.showXAxis){var I,N;f.lineWidth=1,I=new l(w.min,x.min,k.min),N=new l(w.max,x.min,k.min),this._line3d(f,I,N,this.axisColor),I=new l(w.min,x.max,k.min),N=new l(w.max,x.max,k.min),this._line3d(f,I,N,this.axisColor)}this.showYAxis&&(f.lineWidth=1,t=new l(w.min,x.min,k.min),e=new l(w.min,x.max,k.min),this._line3d(f,t,e,this.axisColor),t=new l(w.max,x.min,k.min),e=new l(w.max,x.max,k.min),this._line3d(f,t,e,this.axisColor));var R=this.xLabel;R.length>0&&this.showXAxis&&(c=.1/this.scale.y,s=(w.max+3*w.min)/4,r=_.x>0?x.min-c:x.max+c,n=new l(s,r,k.min),this.drawAxisLabelX(f,n,R,b));var A=this.yLabel;A.length>0&&this.showYAxis&&(d=.1/this.scale.x,s=_.y>0?w.min-d:w.max+d,r=(x.max+3*x.min)/4,n=new l(s,r,k.min),this.drawAxisLabelY(f,n,A,b));var z=this.zLabel;z.length>0&&this.showZAxis&&(h=30,s=_.x>0?w.min:w.max,r=_.y<0?x.min:x.max,a=(k.max+3*k.min)/4,n=new l(s,r,a),this.drawAxisLabelZ(f,n,z,h))},o.prototype._hsv2rgb=function(t,e,i){var o,n,s,r,a,h;switch(r=i*e,a=Math.floor(t/60),h=r*(1-Math.abs(t/60%2-1)),a){case 0:o=r,n=h,s=0;break;case 1:o=h,n=r,s=0;break;case 2:o=0,n=r,s=h;break;case 3:o=0,n=h,s=r;break;case 4:o=h,n=0,s=r;break;case 5:o=r,n=0,s=h;break;default:o=0,n=0,s=0}return"RGB("+parseInt(255*o)+","+parseInt(255*n)+","+parseInt(255*s)+")"},o.prototype._getStrokeWidth=function(t){return void 0!==t?this.showPerspective?1/-t.trans.z*this.dataColor.strokeWidth:-this.eye.z/this.camera.getArmLength()*this.dataColor.strokeWidth:this.dataColor.strokeWidth},o.prototype._redrawBar=function(t,e,i,o,n,s){var r,a=this,h=e.point,d=this.zRange.min,u=[{point:new l(h.x-i,h.y-o,h.z)},{point:new l(h.x+i,h.y-o,h.z)},{point:new l(h.x+i,h.y+o,h.z)},{point:new l(h.x-i,h.y+o,h.z)}],c=[{point:new l(h.x-i,h.y-o,d)},{point:new l(h.x+i,h.y-o,d)},{point:new l(h.x+i,h.y+o,d)},{point:new l(h.x-i,h.y+o,d)}];u.forEach(function(t){t.screen=a._convert3Dto2D(t.point)}),c.forEach(function(t){t.screen=a._convert3Dto2D(t.point)});var p=[{corners:u,center:l.avg(c[0].point,c[2].point)},{corners:[u[0],u[1],c[1],c[0]],center:l.avg(c[1].point,c[0].point)},{corners:[u[1],u[2],c[2],c[1]],center:l.avg(c[2].point,c[1].point)},{corners:[u[2],u[3],c[3],c[2]],center:l.avg(c[3].point,c[2].point)},{corners:[u[3],u[0],c[0],c[3]],center:l.avg(c[0].point,c[3].point)}];e.surfaces=p;for(var f=0;f<p.length;f++){r=p[f];var m=this._conveLine truncated
function tt(t){return t&ve?"cancel":t&fe?"end":t&pe?"move":t&ce?"start":""}function et(t){return t==Lt?"down":t==zt?"up":t==Rt?"left":t==At?"right":""}function it(t,e){var i=e.manager;return i?i.get(t):t}function ot(){Q.apply(this,arguments)}function nt(){ot.apply(this,arguments),this.pX=null,this.pY=null}function st(){ot.apply(this,arguments)}function rt(){Q.apply(this,arguments),this._timer=null,this._input=null}function at(){ot.apply(this,arguments)}function ht(){ot.apply(this,arguments)}function dt(){Q.apply(this,arguments),this.pTime=!1,this.pCenter=!1,this._timer=null,this._input=null,this.count=0}function lt(t,e){return e=e||{},e.recognizers=m(e.recognizers,lt.defaults.preset),new ut(t,e)}function ut(t,e){this.options=ft({},lt.defaults,e||{}),this.options.inputTarget=this.options.inputTarget||t,this.handlers={},this.session={},this.recognizers=[],this.oldCssProps={},this.element=t,this.input=O(this),this.touchAction=new J(this,this.options.touchAction),ct(this,!0),l(this.options.recognizers,function(t){var e=this.add(new t[0](t[1]));t[2]&&e.recognizeWith(t[2]),t[3]&&e.requireFailure(t[3])},this)}function ct(t,e){var i=t.element;if(i.style){var o;l(t.options.cssProps,function(n,s){o=S(i.style,s),e?(t.oldCssProps[o]=i.style[o],i.style[o]=n):i.style[o]=t.oldCssProps[o]||""}),e||(t.oldCssProps={})}}function pt(t,e){var i=s.createEvent("Event");i.initEvent(t,!0,!0),i.gesture=e,e.target.dispatchEvent(i)}var ft,mt=["","webkit","Moz","MS","ms","o"],vt=s.createElement("div"),gt="function",yt=Math.round,bt=Math.abs,_t=Date.now;ft="function"!=typeof Object.assign?function(t){if(t===a||null===t)throw new TypeError("Cannot convert undefined or null to object");for(var e=Object(t),i=1;i<arguments.length;i++){var o=arguments[i];if(o!==a&&null!==o)for(var n in o)o.hasOwnProperty(n)&&(e[n]=o[n])}return e}:Object.assign;var wt=u(function(t,e,i){for(var o=Object.keys(e),n=0;n<o.length;)(!i||i&&t[o[n]]===a)&&(t[o[n]]=e[o[n]]),n++;return t},"extend","Use `assign`."),xt=u(function(t,e){return wt(t,e,!0)},"merge","Use `assign`."),kt=1,St=/mobile|tablet|ip(ad|hone|od)|android/i,Dt="ontouchstart"in n,Mt=S(n,"PointerEvent")!==a,Ct=Dt&&St.test(navigator.userAgent),Ot=25,Et=1,Tt=2,Pt=4,It=8,Nt=1,Rt=2,At=4,zt=8,Lt=16,Ft=Rt|At,Bt=zt|Lt,jt=Ft|Bt,Ht=["x","y"],Wt=["clientX","clientY"];C.prototype={handler:function(){},init:function(){this.evEl&&v(this.element,this.evEl,this.domHandler),this.evTarget&&v(this.target,this.evTarget,this.domHandler),this.evWin&&v(M(this.element),this.evWin,this.domHandler)},destroy:function(){this.evEl&&g(this.element,this.evEl,this.domHandler),this.evTarget&&g(this.target,this.evTarget,this.domHandler),this.evWin&&g(M(this.element),this.evWin,this.domHandler)}};var Yt={mousedown:Et,mousemove:Tt,mouseup:Pt},Gt="mousedown",Vt="mousemove mouseup";c(H,C,{handler:function(t){var e=Yt[t.type];e&Et&&0===t.button&&(this.pressed=!0),e&Tt&&1!==t.which&&(e=Pt),this.pressed&&(e&Pt&&(this.pressed=!1),this.callback(this.manager,e,{pointers:[t],changedPointers:[t],pointerType:"mouse",srcEvent:t}))}});var Ut={pointerdown:Et,pointermove:Tt,pointerup:Pt,pointercancel:It,pointerout:It},qt={2:"touch",3:"pen",4:"mouse",5:"kinect"},Xt="pointerdown",Zt="pointermove pointerup pointercancel";n.MSPointerEvent&&!n.PointerEvent&&(Xt="MSPointerDown",Zt="MSPointerMove MSPointerUp MSPointerCancel"),c(W,C,{handler:function(t){var e=this.store,i=!1,o=t.type.toLowerCase().replace("ms",""),n=Ut[o],s=qt[t.pointerType]||t.pointerType,r="touch"==s,a=w(e,t.pointerId,"pointerId");n&Et&&(0===t.button||r)?a<0&&(e.push(t),a=e.length-1):n&(Pt|It)&&(i=!0),a<0||(e[a]=t,this.callback(this.manager,n,{pointers:e,changedPointers:[t],pointerType:s,srcEvent:t}),i&&e.splice(a,1))}});var Kt={touchstart:Et,touchmove:Tt,touchend:Pt,touchcancel:It},Jt="touchstart",$t="touchstart touchmove touchend touchcancel";c(Y,C,{handler:function(t){var e=Kt[t.type];if(e===Et&&(this.started=!0),this.started){var i=G.call(this,t,e);e&(Pt|It)&&i[0].length-i[1].length==0&&(this.started=!1),this.callback(this.manager,e,{pointers:i[0],changedPointers:i[1],pointerType:"touch",srcEvent:t})}}});var Qt={touchstart:Et,touchmove:Tt,touchend:Pt,touchcancel:It},te="touchstart touchmove touchend touchcancel";c(V,C,{handler:function(t){var e=Qt[t.type],i=U.call(this,t,e);i&&this.callback(this.manager,e,{pointers:i[0],changedPointers:i[1],pointerType:"touch",srcEvent:t})}});var ee=2500,ie=25;c(q,C,{handler:function(t,e,i){var o="touch"==i.pointerType,n="mouse"==i.pointerType;if(!(n&&i.sourceCapabilities&&i.sourceCapabilities.firesTouchEvents)){if(o)X.call(this,e,i);else if(n&&K.call(this,i))return;this.callback(t,e,i)}},destroy:function(){this.touch.destroy(),this.mouse.destroy()}});var oe=S(vt.style,"touchAction"),ne=oe!==a,se="auto",re="manipulation",ae="none",he="pan-x",de="pan-y",le=function(){if(!ne)return!1;var t={},e=n.CSS&&n.CSS.supports;return["auto","manipulation","pan-y","pan-x","pan-x pan-y","none"].forEach(function(i){t[i]=!e||n.CSS.supports("touch-action",i)}),t}();J.protoLine truncated
;this.pixelRatio=(window.devicePixelRatio||1)/(t.webkitBackingStorePixelRatio||t.mozBackingStorePixelRatio||t.msBackingStorePixelRatio||t.oBackingStorePixelRatio||t.backingStorePixelRatio||1),this.colorPickerCanvas.getContext("2d").setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0)}else{var e=document.createElement("DIV");e.style.color="red",e.style.fontWeight="bold",e.style.padding="10px",e.innerHTML="Error: your browser does not support HTML canvas",this.colorPickerCanvas.appendChild(e)}this.colorPickerDiv.className="vis-color",this.opacityDiv=document.createElement("div"),this.opacityDiv.className="vis-opacity",this.brightnessDiv=document.createElement("div"),this.brightnessDiv.className="vis-brightness",this.arrowDiv=document.createElement("div"),this.arrowDiv.className="vis-arrow",this.opacityRange=document.createElement("input");try{this.opacityRange.type="range",this.opacityRange.min="0",this.opacityRange.max="100"}catch(t){}this.opacityRange.value="100",this.opacityRange.className="vis-range",this.brightnessRange=document.createElement("input");try{this.brightnessRange.type="range",this.brightnessRange.min="0",this.brightnessRange.max="100"}catch(t){}this.brightnessRange.value="100",this.brightnessRange.className="vis-range",this.opacityDiv.appendChild(this.opacityRange),this.brightnessDiv.appendChild(this.brightnessRange);var i=this;this.opacityRange.onchange=function(){i._setOpacity(this.value)},this.opacityRange.oninput=function(){i._setOpacity(this.value)},this.brightnessRange.onchange=function(){i._setBrightness(this.value)},this.brightnessRange.oninput=function(){i._setBrightness(this.value)},this.brightnessLabel=document.createElement("div"),this.brightnessLabel.className="vis-label vis-brightness",this.brightnessLabel.innerHTML="brightness:",this.opacityLabel=document.createElement("div"),this.opacityLabel.className="vis-label vis-opacity",this.opacityLabel.innerHTML="opacity:",this.newColorDiv=document.createElement("div"),this.newColorDiv.className="vis-new-color",this.newColorDiv.innerHTML="new",this.initialColorDiv=document.createElement("div"),this.initialColorDiv.className="vis-initial-color",this.initialColorDiv.innerHTML="initial",this.cancelButton=document.createElement("div"),this.cancelButton.className="vis-button vis-cancel",this.cancelButton.innerHTML="cancel",this.cancelButton.onclick=this._hide.bind(this,!1),this.applyButton=document.createElement("div"),this.applyButton.className="vis-button vis-apply",this.applyButton.innerHTML="apply",this.applyButton.onclick=this._apply.bind(this),this.saveButton=document.createElement("div"),this.saveButton.className="vis-button vis-save",this.saveButton.innerHTML="save",this.saveButton.onclick=this._save.bind(this),this.loadButton=document.createElement("div"),this.loadButton.className="vis-button vis-load",this.loadButton.innerHTML="load last",this.loadButton.onclick=this._loadLast.bind(this),this.frame.appendChild(this.colorPickerDiv),this.frame.appendChild(this.arrowDiv),this.frame.appendChild(this.brightnessLabel),this.frame.appendChild(this.brightnessDiv),this.frame.appendChild(this.opacityLabel),this.frame.appendChild(this.opacityDiv),this.frame.appendChild(this.newColorDiv),this.frame.appendChild(this.initialColorDiv),this.frame.appendChild(this.cancelButton),this.frame.appendChild(this.applyButton),this.frame.appendChild(this.saveButton),this.frame.appendChild(this.loadButton)}},{key:"_bindHammer",value:function(){var t=this;this.drag={},this.pinch={},this.hammer=new l(this.colorPickerCanvas),this.hammer.get("pinch").set({enable:!0}),u.onTouch(this.hammer,function(e){t._moveSelector(e)}),this.hammer.on("tap",function(e){t._moveSelector(e)}),this.hammer.on("panstart",function(e){t._moveSelector(e)}),this.hammer.on("panmove",function(e){t._moveSelector(e)}),this.hammer.on("panend",function(e){t._moveSelector(e)})}},{key:"_generateHueCircle",value:function(){if(!1===this.generated){var t=this.colorPickerCanvas.getContext("2d");void 0===this.pixelRation&&(this.pixelRatio=(window.devicePixelRatio||1)/(t.webkitBackingStorePixelRatio||t.mozBackingStorePixelRatio||t.msBackingStorePixelRatio||t.oBackingStorePixelRatio||t.backingStorePixelRatio||1)),t.setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0);var e=this.colorPickerCanvas.clientWidth,i=this.colorPickerCanvas.clientHeight;t.clearRect(0,0,e,i);var o=void 0,n=void 0,s=void 0,r=void 0;this.centerCoordinates={x:.5*e,y:.5*i},this.r=.49*e;var a=2*Math.PI/360,h=1/this.r,d=void 0;for(s=0;s<360;s++)for(r=0;r<this.r;r++)o=this.centerCoordinates.x+r*Math.sin(a*s),n=this.centerCoordinates.y+r*Math.cos(a*s),d=c.HSVToRGB(s*(1/360),r*h,1),t.fillStyle="rgb("+d.r+","+d.g+","+d.b+")",t.fillRect(o-.5,n-.5,2,2);t.strokeStyle="rgba(0,0,0,1)",t.circle(this.centerCoordinates.x,this.centerCoordinates.y,this.r),t.stroke(),this.hueCircle=t.getImageData(0,0,e,i)}this.generated=!0}},{key:"_moveSelector",value:function(t){var e=this.colorPickerDiv.getBoundingClientRect(),i=t.center.x-e.left,o=t.center.y-Line truncated
function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=i(2),d=function(){function t(){(0,s.default)(this,t),this.clear(),this.defaultIndex=0,this.groupsArray=[],this.groupIndex=0,this.defaultGroups=[{border:"#2B7CE9",background:"#97C2FC",highlight:{border:"#2B7CE9",background:"#D2E5FF"},hover:{border:"#2B7CE9",background:"#D2E5FF"}},{border:"#FFA500",background:"#FFFF00",highlight:{border:"#FFA500",background:"#FFFFA3"},hover:{border:"#FFA500",background:"#FFFFA3"}},{border:"#FA0A10",background:"#FB7E81",highlight:{border:"#FA0A10",background:"#FFAFB1"},hover:{border:"#FA0A10",background:"#FFAFB1"}},{border:"#41A906",background:"#7BE141",highlight:{border:"#41A906",background:"#A1EC76"},hover:{border:"#41A906",background:"#A1EC76"}},{border:"#E129F0",background:"#EB7DF4",highlight:{border:"#E129F0",background:"#F0B3F5"},hover:{border:"#E129F0",background:"#F0B3F5"}},{border:"#7C29F0",background:"#AD85E4",highlight:{border:"#7C29F0",background:"#D3BDF0"},hover:{border:"#7C29F0",background:"#D3BDF0"}},{border:"#C37F00",background:"#FFA807",highlight:{border:"#C37F00",background:"#FFCA66"},hover:{border:"#C37F00",background:"#FFCA66"}},{border:"#4220FB",background:"#6E6EFD",highlight:{border:"#4220FB",background:"#9B9BFD"},hover:{border:"#4220FB",background:"#9B9BFD"}},{border:"#FD5A77",background:"#FFC0CB",highlight:{border:"#FD5A77",background:"#FFD1D9"},hover:{border:"#FD5A77",background:"#FFD1D9"}},{border:"#4AD63A",background:"#C2FABC",highlight:{border:"#4AD63A",background:"#E6FFE3"},hover:{border:"#4AD63A",background:"#E6FFE3"}},{border:"#990000",background:"#EE0000",highlight:{border:"#BB0000",background:"#FF3333"},hover:{border:"#BB0000",background:"#FF3333"}},{border:"#FF6000",background:"#FF6000",highlight:{border:"#FF6000",background:"#FF6000"},hover:{border:"#FF6000",background:"#FF6000"}},{border:"#97C2FC",background:"#2B7CE9",highlight:{border:"#D2E5FF",background:"#2B7CE9"},hover:{border:"#D2E5FF",background:"#2B7CE9"}},{border:"#399605",background:"#255C03",highlight:{border:"#399605",background:"#255C03"},hover:{border:"#399605",background:"#255C03"}},{border:"#B70054",background:"#FF007E",highlight:{border:"#B70054",background:"#FF007E"},hover:{border:"#B70054",background:"#FF007E"}},{border:"#AD85E4",background:"#7C29F0",highlight:{border:"#D3BDF0",background:"#7C29F0"},hover:{border:"#D3BDF0",background:"#7C29F0"}},{border:"#4557FA",background:"#000EA1",highlight:{border:"#6E6EFD",background:"#000EA1"},hover:{border:"#6E6EFD",background:"#000EA1"}},{border:"#FFC0CB",background:"#FD5A77",highlight:{border:"#FFD1D9",background:"#FD5A77"},hover:{border:"#FFD1D9",background:"#FD5A77"}},{border:"#C2FABC",background:"#74D66A",highlight:{border:"#E6FFE3",background:"#74D66A"},hover:{border:"#E6FFE3",background:"#74D66A"}},{border:"#EE0000",background:"#990000",highlight:{border:"#FF3333",background:"#BB0000"},hover:{border:"#FF3333",background:"#BB0000"}}],this.options={},this.defaultOptions={useDefaultGroups:!0},h.extend(this.options,this.defaultOptions)}return(0,a.default)(t,[{key:"setOptions",value:function(t){var e=["useDefaultGroups"];if(void 0!==t)for(var i in t)if(t.hasOwnProperty(i)&&-1===e.indexOf(i)){var o=t[i];this.add(i,o)}}},{key:"clear",value:function(){this.groups={},this.groupsArray=[]}},{key:"get",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=this.groups[t];if(void 0===i&&e)if(!1===this.options.useDefaultGroups&&this.groupsArray.length>0){var o=this.groupIndex%this.groupsArray.length;this.groupIndex++,i={},i.color=this.groups[this.groupsArray[o]],this.groups[t]=i}else{var n=this.defaultIndex%this.defaultGroups.length;this.defaultIndex++,i={},i.color=this.defaultGroups[n],this.groups[t]=i}return i}},{key:"add",value:function(t,e){return this.groups[t]=e,this.groupsArray.push(t),e}}]),t}();e.default=d},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=i(2),d=i(11),l=i(12),u=i(47).default,c=function(){function t(e,i,o,n){var r=this;if((0,s.default)(this,t),this.body=e,this.images=i,this.groups=o,this.layoutEngine=n,this.body.functions.createNode=this.create.bind(this),this.nodesListeners={add:function(t,e){r.add(e.items)},update:function(t,e){r.update(e.items,e.data,e.oldData)},remove:function(t,e){r.remove(e.items)}},this.defaultOptions={borderWidth:1,borderWidthSelected:2,brokenImage:void 0,color:{border:"#2B7CE9",background:"#97C2FC",highlight:{border:"#2B7CE9",background:"#D2E5FF"},hover:{border:"#2B7CE9",background:"#D2E5FF"}},fixed:{x:!1,y:!1},font:{color:"#343434",size:14,face:"arial",background:"none",strokeWidth:0,strokeColor:"#ffffff",align:"center",vadjust:0,multi:!1,bold:{mod:"bold"},boldital:{mod:"bold italic"},ital:{mod:"italic"},mono:{mod:"",size:15,face:"monospace",vadjust:2}},group:void 0,hidden:!1,icon:{face:"FontAwesome",code:void 0,size:50,color:"#Line truncated
}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(24),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"draw",value:function(t,e,i,o,n,s){this._drawShape(t,"square",2,e,i,o,n,s)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(24),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"draw",value:function(t,e,i,o,n,s){this._drawShape(t,"hexagon",4,e,i,o,n,s)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(24),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"draw",value:function(t,e,i,o,n,s){this._drawShape(t,"star",4,e,i,o,n,s)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(23),m=o(f),v=function(t){function e(t,i,o){(0,a.default)(this,e);var n=(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o));return n._setMargins(o),n}return(0,p.default)(e,t),(0,d.default)(e,[{key:"resize",value:function(t,e,i){this.needsRefresh(e,i)&&(this.textSize=this.labelModule.getTextSize(t,e,i),this.width=this.textSize.width+this.margin.right+this.margin.left,this.height=this.textSize.height+this.margin.top+this.margin.bottom,this.radius=.5*this.width)}},{key:"draw",value:function(t,e,i,o,n,s){this.resize(t,o,n),this.left=e-this.width/2,this.top=i-this.height/2,this.enableShadow(t,s),this.labelModule.draw(t,this.left+this.textSize.width/2+this.margin.left,this.top+this.textSize.height/2+this.margin.top,o,n),this.disableShadow(t,s),this.updateBoundingBox(e,i,t,o,n)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(24),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"draw",value:function(t,e,i,o,n,s){this._drawShape(t,"triangle",3,e,i,o,n,s)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(24),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"draw",value:function(t,e,i,o,n,s){this._drawShape(t,"triangleDown",3,e,i,o,n,s)}},{key:"distanceToBorder",value:function(t,e){return this._distanceToBorder(t,e)}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=i(2),d=i(11),l=i(12),u=i(74).default,c=function(){function t(e,i,o){var n=this;(0,s.default)(this,t),this.body=e,this.images=i,this.groups=o,this.body.functions.createEdge=this.create.bind(this),this.edgesListeners={add:function(t,e){n.add(e.items)},update:function(t,e){n.update(e.items)},remove:function(t,e){n.remove(e.items)}},this.options={},this.defaultOptions={arrows:{to:{enabled:!1,scaleFactor:1,type:"arrow"},middle:{enabled:!1,scaleFactor:1,type:"arrow"},from:{enabled:!1,scaleFactor:1,type:"arrow"}},arrowStrikethrough:!0,color:{color:"#848484",highlight:"#848484",hover:"#848484",inherit:"from",opacity:1},dashes:!1,font:{color:"#343434",size:14,face:"arial",background:"none",strokeWidth:2,strokeColor:"#ffffff",align:"horizontal",multi:!1,vadjust:0,bold:{mod:"bold"},boldital:{mod:"bold italic"},ital:{mod:"italic"},mono:{mod:"",size:15,face:"courier nLine truncated
;s=i<p?-Math.pow(.05*i,2)+Math.pow(.05*p,2):0,0===i?i=.01:s/=i,o=t*s,n=e*s,c[r.id].x-=o,c[r.id].y-=n,c[a.id].x+=o,c[a.id].y+=n}}}]),t}();e.default=h},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=function(){function t(e,i,o){(0,s.default)(this,t),this.body=e,this.physicsBody=i,this.setOptions(o)}return(0,a.default)(t,[{key:"setOptions",value:function(t){this.options=t}},{key:"solve",value:function(){for(var t=void 0,e=void 0,i=this.physicsBody.physicsEdgeIndices,o=this.body.edges,n=void 0,s=void 0,r=void 0,a=0;a<i.length;a++)e=o[i[a]],!0===e.connected&&e.toId!==e.fromId&&void 0!==this.body.nodes[e.toId]&&void 0!==this.body.nodes[e.fromId]&&(void 0!==e.edgeType.via?(t=void 0===e.options.length?this.options.springLength:e.options.length,n=e.to,s=e.edgeType.via,r=e.from,this._calculateSpringForce(n,s,.5*t),this._calculateSpringForce(s,r,.5*t)):(t=void 0===e.options.length?1.5*this.options.springLength:e.options.length,this._calculateSpringForce(e.from,e.to,t)))}},{key:"_calculateSpringForce",value:function(t,e,i){var o=t.x-e.x,n=t.y-e.y,s=Math.max(Math.sqrt(o*o+n*n),.01),r=this.options.springConstant*(i-s)/s,a=o*r,h=n*r;void 0!==this.physicsBody.forces[t.id]&&(this.physicsBody.forces[t.id].x+=a,this.physicsBody.forces[t.id].y+=h),void 0!==this.physicsBody.forces[e.id]&&(this.physicsBody.forces[e.id].x-=a,this.physicsBody.forces[e.id].y-=h)}}]),t}();e.default=h},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=function(){function t(e,i,o){(0,s.default)(this,t),this.body=e,this.physicsBody=i,this.setOptions(o)}return(0,a.default)(t,[{key:"setOptions",value:function(t){this.options=t}},{key:"solve",value:function(){for(var t,e,i,o,n,s,r,a,h=this.body.edges,d=this.physicsBody.physicsEdgeIndices,l=this.physicsBody.physicsNodeIndices,u=this.physicsBody.forces,c=0;c<l.length;c++){var p=l[c];u[p].springFx=0,u[p].springFy=0}for(var f=0;f<d.length;f++)e=h[d[f]],!0===e.connected&&(t=void 0===e.options.length?this.options.springLength:e.options.length,i=e.from.x-e.to.x,o=e.from.y-e.to.y,a=Math.sqrt(i*i+o*o),a=0===a?.01:a,r=this.options.springConstant*(t-a)/a,n=i*r,s=o*r,e.to.level!=e.from.level?(void 0!==u[e.toId]&&(u[e.toId].springFx-=n,u[e.toId].springFy-=s),void 0!==u[e.fromId]&&(u[e.fromId].springFx+=n,u[e.fromId].springFy+=s)):(void 0!==u[e.toId]&&(u[e.toId].x-=.5*n,u[e.toId].y-=.5*s),void 0!==u[e.fromId]&&(u[e.fromId].x+=.5*n,u[e.fromId].y+=.5*s)));r=1;for(var m,v,g=0;g<l.length;g++){var y=l[g];m=Math.min(r,Math.max(-r,u[y].springFx)),v=Math.min(r,Math.max(-r,u[y].springFy)),u[y].x+=m,u[y].y+=v}for(var b=0,_=0,w=0;w<l.length;w++){var x=l[w];b+=u[x].x,_+=u[x].y}for(var k=b/l.length,S=_/l.length,D=0;D<l.length;D++){var M=l[D];u[M].x-=k,u[M].y-=S}}}]),t}();e.default=h},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(120),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"_calculateForces",value:function(t,e,i,o,n){0===t&&(t=.1*Math.random(),e=t),this.overlapAvoidanceFactor<1&&o.shape.radius&&(t=Math.max(.1+this.overlapAvoidanceFactor*o.shape.radius,t-o.shape.radius));var s=o.edges.length+1,r=this.options.gravitationalConstant*n.mass*o.options.mass*s/Math.pow(t,2),a=e*r,h=i*r;this.physicsBody.forces[o.id].x+=a,this.physicsBody.forces[o.id].y+=h}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(3),s=o(n),r=i(0),a=o(r),h=i(1),d=o(h),l=i(4),u=o(l),c=i(5),p=o(c),f=i(121),m=o(f),v=function(t){function e(t,i,o){return(0,a.default)(this,e),(0,u.default)(this,(e.__proto__||(0,s.default)(e)).call(this,t,i,o))}return(0,p.default)(e,t),(0,d.default)(e,[{key:"_calculateForces",value:function(t,e,i,o,n){if(t>0){var s=n.edges.length+1,r=this.options.centralGravity*s*n.options.mass;o[n.id].x=e*r,o[n.id].y=i*r}}}]),e}(m.default);e.default=v},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(8),s=o(n),r=i(6),a=o(r),h=i(0),d=o(h),l=i(1),u=o(l),c=i(2),p=i(76).default,f=i(228).default,m=i(74).default,v=i(47).default,g=function(){function t(e){var i=this;(0,d.default)(this,t),this.body=e,this.clusteredNodes={},this.clusteredEdges={},this.options={},this.defaultOptions={},c.extend(this.options,this.defaultOptions),this.body.emitter.on("_resetData",function(){i.clusteredNodes={},i.clusteredEdges={}})}return(0,u.default)(t,[{key:"clusterByHubsize",value:function(t,e){void 0===t?t=this._getHubSize():"object"===(void 0===t?"undefined":(0,a.default)(t))&&(e=this._checkOptions(t),t=this._getHubSiLine truncated
this.frame.canvas.width!==r&&(this.frame.canvas.width=r,i=!0),this.frame.canvas.height!==a&&(this.frame.canvas.height=a,i=!0)}return!0===i&&(this.body.emitter.emit("resize",{width:Math.round(this.frame.canvas.width/this.pixelRatio),height:Math.round(this.frame.canvas.height/this.pixelRatio),oldWidth:Math.round(o/this.pixelRatio),oldHeight:Math.round(n/this.pixelRatio)}),this._setCameraState()),this.initialized=!0,i}},{key:"getContext",value:function(){return this.frame.canvas.getContext("2d")}},{key:"_determinePixelRatio",value:function(){var t=this.getContext();if(void 0===t)throw new Error("Could not get canvax context");var e=1;return"undefined"!=typeof window&&(e=window.devicePixelRatio||1),e/(t.webkitBackingStorePixelRatio||t.mozBackingStorePixelRatio||t.msBackingStorePixelRatio||t.oBackingStorePixelRatio||t.backingStorePixelRatio||1)}},{key:"_setPixelRatio",value:function(){this.pixelRatio=this._determinePixelRatio()}},{key:"setTransform",value:function(){var t=this.getContext();if(void 0===t)throw new Error("Could not get canvax context");t.setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0)}},{key:"_XconvertDOMtoCanvas",value:function(t){return(t-this.body.view.translation.x)/this.body.view.scale}},{key:"_XconvertCanvasToDOM",value:function(t){return t*this.body.view.scale+this.body.view.translation.x}},{key:"_YconvertDOMtoCanvas",value:function(t){return(t-this.body.view.translation.y)/this.body.view.scale}},{key:"_YconvertCanvasToDOM",value:function(t){return t*this.body.view.scale+this.body.view.translation.y}},{key:"canvasToDOM",value:function(t){return{x:this._XconvertCanvasToDOM(t.x),y:this._YconvertCanvasToDOM(t.y)}}},{key:"DOMtoCanvas",value:function(t){return{x:this._XconvertDOMtoCanvas(t.x),y:this._YconvertDOMtoCanvas(t.y)}}}]),t}();e.default=u},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(0),s=o(n),r=i(1),a=o(r),h=i(2),d=i(76).default,l=function(){function t(e,i){var o=this;(0,s.default)(this,t),this.body=e,this.canvas=i,this.animationSpeed=1/this.renderRefreshRate,this.animationEasingFunction="easeInOutQuint",this.easingTime=0,this.sourceScale=0,this.targetScale=0,this.sourceTranslation=0,this.targetTranslation=0,this.lockedOnNodeId=void 0,this.lockedOnNodeOffset=void 0,this.touchTime=0,this.viewFunction=void 0,this.body.emitter.on("fit",this.fit.bind(this)),this.body.emitter.on("animationFinished",function(){o.body.emitter.emit("_stopRendering")}),this.body.emitter.on("unlockNode",this.releaseNode.bind(this))}return(0,a.default)(t,[{key:"setOptions",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.options=t}},{key:"fit",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{nodes:[]},e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=void 0,o=void 0;if(void 0!==t.nodes&&0!==t.nodes.length||(t.nodes=this.body.nodeIndices),!0===e){var n=0;for(var s in this.body.nodes)if(this.body.nodes.hasOwnProperty(s)){var r=this.body.nodes[s];!0===r.predefinedPosition&&(n+=1)}if(n>.5*this.body.nodeIndices.length)return void this.fit(t,!1);i=d.getRange(this.body.nodes,t.nodes);o=12.662/(this.body.nodeIndices.length+7.4147)+.0964822;o*=Math.min(this.canvas.frame.canvas.clientWidth/600,this.canvas.frame.canvas.clientHeight/600)}else{this.body.emitter.emit("_resizeNodes"),i=d.getRange(this.body.nodes,t.nodes);var a=1.1*Math.abs(i.maxX-i.minX),h=1.1*Math.abs(i.maxY-i.minY),l=this.canvas.frame.canvas.clientWidth/a,u=this.canvas.frame.canvas.clientHeight/h;o=l<=u?l:u}o>1?o=1:0===o&&(o=1);var c=d.findCenter(i),p={position:c,scale:o,animation:t.animation};this.moveTo(p)}},{key:"focus",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(void 0!==this.body.nodes[t]){var i={x:this.body.nodes[t].x,y:this.body.nodes[t].y};e.position=i,e.lockedOnNode=t,this.moveTo(e)}else console.log("Node: "+t+" cannot be found.")}},{key:"moveTo",value:function(t){if(void 0===t)return void(t={});void 0===t.offset&&(t.offset={x:0,y:0}),void 0===t.offset.x&&(t.offset.x=0),void 0===t.offset.y&&(t.offset.y=0),void 0===t.scale&&(t.scale=this.body.view.scale),void 0===t.position&&(t.position=this.getViewPosition()),void 0===t.animation&&(t.animation={duration:0}),!1===t.animation&&(t.animation={duration:0}),!0===t.animation&&(t.animation={}),void 0===t.animation.duration&&(t.animation.duration=1e3),void 0===t.animation.easingFunction&&(t.animation.easingFunction="easeInOutQuad"),this.animateView(t)}},{key:"animateView",value:function(t){if(void 0!==t){this.animationEasingFunction=t.animation.easingFunction,this.releaseNode(),!0===t.locked&&(this.lockedOnNodeId=t.lockedOnNode,this.lockedOnNodeOffset=t.offset),0!=this.easingTime&&this._transitionRedraw(!0),this.sourceScale=this.body.view.scale,this.sourceTranslation=this.body.view.translation,this.targetScale=t.scale,this.body.view.scale=this.targetScale;var e=this.canvas.DOMtoCanvas({x:.5*this.canvas.frame.cLine truncated
;if((e.unselectAll||void 0===e.unselectAll)&&this.unselectAll(),t.nodes)for(i=0;i<t.nodes.length;i++){o=t.nodes[i];var n=this.body.nodes[o];if(!n)throw new RangeError('Node with id "'+o+'" not found');this.selectObject(n,e.highlightEdges)}if(t.edges)for(i=0;i<t.edges.length;i++){o=t.edges[i];var s=this.body.edges[o];if(!s)throw new RangeError('Edge with id "'+o+'" not found');this.selectObject(s)}this.body.emitter.emit("_requestRedraw")}},{key:"selectNodes",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(!t||void 0===t.length)throw"Selection must be an array with ids";this.setSelection({nodes:t},{highlightEdges:e})}},{key:"selectEdges",value:function(t){if(!t||void 0===t.length)throw"Selection must be an array with ids";this.setSelection({edges:t})}},{key:"updateSelection",value:function(){for(var t in this.selectionObj.nodes)this.selectionObj.nodes.hasOwnProperty(t)&&(this.body.nodes.hasOwnProperty(t)||delete this.selectionObj.nodes[t]);for(var e in this.selectionObj.edges)this.selectionObj.edges.hasOwnProperty(e)&&(this.body.edges.hasOwnProperty(e)||delete this.selectionObj.edges[e])}},{key:"getClickedItems",value:function(t){for(var e=this.canvas.DOMtoCanvas(t),i=[],o=this.body.nodeIndices,n=this.body.nodes,s=o.length-1;s>=0;s--){var r=n[o[s]],a=r.getItemsOnPoint(e);i.push.apply(i,a)}for(var h=this.body.edgeIndices,d=this.body.edges,l=h.length-1;l>=0;l--){var u=d[h[l]],c=u.getItemsOnPoint(e);i.push.apply(i,c)}return i}}]),t}();e.default=u},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=i(30),s=o(n),r=i(6),a=o(r),h=i(8),d=o(h),l=i(0),u=o(l),c=i(1),p=o(c),f=i(2),m=i(76).default,v=i(236),g=v.HorizontalStrategy,y=v.VerticalStrategy,b=function(){function t(){(0,u.default)(this,t),this.childrenReference={},this.parentReference={},this.trees={},this.distributionOrdering={},this.levels={},this.distributionIndex={},this.isTree=!1,this.treeIndex=-1}return(0,p.default)(t,[{key:"addRelation",value:function(t,e){void 0===this.childrenReference[t]&&(this.childrenReference[t]=[]),this.childrenReference[t].push(e),void 0===this.parentReference[e]&&(this.parentReference[e]=[]),this.parentReference[e].push(t)}},{key:"checkIfTree",value:function(){for(var t in this.parentReference)if(this.parentReference[t].length>1)return void(this.isTree=!1);this.isTree=!0}},{key:"numTrees",value:function(){return this.treeIndex+1}},{key:"setTreeIndex",value:function(t,e){void 0!==e&&void 0===this.trees[t.id]&&(this.trees[t.id]=e,this.treeIndex=Math.max(e,this.treeIndex))}},{key:"ensureLevel",value:function(t){void 0===this.levels[t]&&(this.levels[t]=0)}},{key:"getMaxLevel",value:function(t){var e=this,i={};return function t(o){if(void 0!==i[o])return i[o];var n=e.levels[o];if(e.childrenReference[o]){var s=e.childrenReference[o];if(s.length>0)for(var r=0;r<s.length;r++)n=Math.max(n,t(s[r]))}return i[o]=n,n}(t)}},{key:"levelDownstream",value:function(t,e){void 0===this.levels[e.id]&&(void 0===this.levels[t.id]&&(this.levels[t.id]=0),this.levels[e.id]=this.levels[t.id]+1)}},{key:"setMinLevelToZero",value:function(t){var e=1e9;for(var i in t)t.hasOwnProperty(i)&&void 0!==this.levels[i]&&(e=Math.min(this.levels[i],e));for(var o in t)t.hasOwnProperty(o)&&void 0!==this.levels[o]&&(this.levels[o]-=e)}},{key:"getTreeSize",value:function(t,e){var i=1e9,o=-1e9,n=1e9,s=-1e9;for(var r in this.trees)if(this.trees.hasOwnProperty(r)&&this.trees[r]===e){var a=t[r];i=Math.min(a.x,i),o=Math.max(a.x,o),n=Math.min(a.y,n),s=Math.max(a.y,s)}return{min_x:i,max_x:o,min_y:n,max_y:s}}},{key:"hasSameParent",value:function(t,e){var i=this.parentReference[t.id],o=this.parentReference[e.id];if(void 0===i||void 0===o)return!1;for(var n=0;n<i.length;n++)for(var s=0;s<o.length;s++)if(i[n]==o[s])return!0;return!1}},{key:"inSameSubNetwork",value:function(t,e){return this.trees[t.id]===this.trees[e.id]}},{key:"getLevels",value:function(){return(0,d.default)(this.distributionOrdering)}},{key:"addToOrdering",value:function(t,e){void 0===this.distributionOrdering[e]&&(this.distributionOrdering[e]=[]);var i=!1,o=this.distributionOrdering[e];for(var n in o)if(o[n]===t){i=!0;break}i||(this.distributionOrdering[e].push(t),this.distributionIndex[t.id]=this.distributionOrdering[e].length-1)}}]),t}(),_=function(){function t(e){(0,u.default)(this,t),this.body=e,this.initialRandomSeed=Math.round(1e6*Math.random()),this.randomSeed=this.initialRandomSeed,this.setPhysics=!1,this.options={},this.optionsBackup={physics:{}},this.defaultOptions={randomSeed:void 0,improvedLayout:!0,hierarchical:{enabled:!1,levelSeparation:150,nodeSpacing:100,treeSpacing:200,blockShifting:!0,edgeMinimization:!0,parentCentralization:!0,direction:"UD",sortMethod:"hubsize"}},f.extend(this.options,this.defaultOptions),this.bindEventListeners()}return(0,p.default)(t,[{key:"bindEventListeners",value:function(){var t=this;this.body.emitter.on("_dataChanged",function(){t.setupHierarchicalLayout()}),this.boLine truncated
;if(2!==o.length)throw new Error("The function for delete does not support two arguments (data, callback)");o(s,function(e){null!==e&&void 0!==e&&"delete"===t.inMode?(t.body.data.edges.getDataSet().remove(e.edges),t.body.data.nodes.getDataSet().remove(e.nodes),t.body.emitter.emit("startSimulation"),t.showManipulatorToolbar()):(t.body.emitter.emit("startSimulation"),t.showManipulatorToolbar())})}else this.body.data.edges.getDataSet().remove(i),this.body.data.nodes.getDataSet().remove(e),this.body.emitter.emit("startSimulation"),this.showManipulatorToolbar()}},{key:"_setup",value:function(){!0===this.options.enabled?(this.guiEnabled=!0,this._createWrappers(),!1===this.editMode?this._createEditButton():this.showManipulatorToolbar()):(this._removeManipulationDOM(),this.guiEnabled=!1)}},{key:"_createWrappers",value:function(){void 0===this.manipulationDiv&&(this.manipulationDiv=document.createElement("div"),this.manipulationDiv.className="vis-manipulation",!0===this.editMode?this.manipulationDiv.style.display="block":this.manipulationDiv.style.display="none",this.canvas.frame.appendChild(this.manipulationDiv)),void 0===this.editModeDiv&&(this.editModeDiv=document.createElement("div"),this.editModeDiv.className="vis-edit-mode",!0===this.editMode?this.editModeDiv.style.display="none":this.editModeDiv.style.display="block",this.canvas.frame.appendChild(this.editModeDiv)),void 0===this.closeDiv&&(this.closeDiv=document.createElement("div"),this.closeDiv.className="vis-close",this.closeDiv.style.display=this.manipulationDiv.style.display,this.canvas.frame.appendChild(this.closeDiv))}},{key:"_getNewTargetNode",value:function(t,e){var i=f.deepExtend({},this.options.controlNodeStyle);i.id="targetNode"+f.randomUUID(),i.hidden=!1,i.physics=!1,i.x=t,i.y=e;var o=this.body.functions.createNode(i);return o.shape.boundingBox={left:t,right:t,top:e,bottom:e},o}},{key:"_createEditButton",value:function(){this._clean(),this.manipulationDOM={},f.recursiveDOMDelete(this.editModeDiv);var t=this.options.locales[this.options.locale],e=this._createButton("editMode","vis-button vis-edit vis-edit-mode",t.edit||this.options.locales.en.edit);this.editModeDiv.appendChild(e),this._bindHammerToDiv(e,this.toggleEditMode.bind(this))}},{key:"_clean",value:function(){this.inMode=!1,!0===this.guiEnabled&&(f.recursiveDOMDelete(this.editModeDiv),f.recursiveDOMDelete(this.manipulationDiv),this._cleanManipulatorHammers()),this._cleanupTemporaryNodesAndEdges(),this._unbindTemporaryUIs(),this._unbindTemporaryEvents(),this.body.emitter.emit("restorePhysics")}},{key:"_cleanManipulatorHammers",value:function(){if(0!=this.manipulationHammers.length){for(var t=0;t<this.manipulationHammers.length;t++)this.manipulationHammers[t].destroy();this.manipulationHammers=[]}}},{key:"_removeManipulationDOM",value:function(){this._clean(),f.recursiveDOMDelete(this.manipulationDiv),f.recursiveDOMDelete(this.editModeDiv),f.recursiveDOMDelete(this.closeDiv),this.manipulationDiv&&this.canvas.frame.removeChild(this.manipulationDiv),this.editModeDiv&&this.canvas.frame.removeChild(this.editModeDiv),this.closeDiv&&this.canvas.frame.removeChild(this.closeDiv),this.manipulationDiv=void 0,this.editModeDiv=void 0,this.closeDiv=void 0}},{key:"_createSeperator",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;this.manipulationDOM["seperatorLineDiv"+t]=document.createElement("div"),this.manipulationDOM["seperatorLineDiv"+t].className="vis-separator-line",this.manipulationDiv.appendChild(this.manipulationDOM["seperatorLineDiv"+t])}},{key:"_createAddNodeButton",value:function(t){var e=this._createButton("addNode","vis-button vis-add",t.addNode||this.options.locales.en.addNode);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.addNodeMode.bind(this))}},{key:"_createAddEdgeButton",value:function(t){var e=this._createButton("addEdge","vis-button vis-connect",t.addEdge||this.options.locales.en.addEdge);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.addEdgeMode.bind(this))}},{key:"_createEditNodeButton",value:function(t){var e=this._createButton("editNode","vis-button vis-edit",t.editNode||this.options.locales.en.editNode);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.editNode.bind(this))}},{key:"_createEditEdgeButton",value:function(t){var e=this._createButton("editEdge","vis-button vis-edit",t.editEdge||this.options.locales.en.editEdge);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.editEdgeMode.bind(this))}},{key:"_createDeleteButton",value:function(t){var e;e=this.options.rtl?"vis-button vis-delete-rtl":"vis-button vis-delete";var i=this._createButton("delete",e,t.del||this.options.locales.en.del);this.manipulationDiv.appendChild(i),this._bindHammerToDiv(i,this.deleteSelected.bind(this))}},{key:"_createBackButton",value:function(t){var e=this._createButton("back","vis-button vis-back",t.back||this.options.locales.en.back);this.manipulationDiv.appendChild(e),this._bindHammerToDiv(e,this.shLine truncated
//# sourceMappingURL=vis.map
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

-24
View File
@@ -1,24 +0,0 @@
<template>
<router-view />
</template>
<script setup>
// import autofit from '../src/assets/js/autofit'
document.documentElement.setAttribute('theme-mode', 'dark');
// if (typeof autofit !== 'undefined') {
// autofit.init();
// console.log('autofit初始化');
// }
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
}
</style>
-21
View File
@@ -1,21 +0,0 @@
import request from './request'
export const getUserList = () => {
return request.get('/users')
}
export const getUserById = (id) => {
return request.get(`/users/${id}`)
}
export const createUser = (data) => {
return request.post('/users', data)
}
export const updateUser = (id, data) => {
return request.put(`/users/${id}`, data)
}
export const deleteUser = (id) => {
return request.delete(`/users/${id}`)
}
@@ -1,7 +0,0 @@
export default {
0: "待签收",
10: "待分析",
20: "CEH中",
25: "CEH完成",
23: '已终止',
};
@@ -1,12 +0,0 @@
export const deepCopy = (data) => {
return JSON.parse(JSON.stringify(data));
}
export const uniqueById = (array) => {
return array.reduce((acc, item) => {
if (!acc.find(x => x.id === item.id)) {
acc.push(item);
}
return acc;
}, []);
};
@@ -1,4 +0,0 @@
export default {
0: "本系统创建",
1: "来自于B3",
};
@@ -1,5 +0,0 @@
export default {
1: "一般",
2: "紧急",
3: "加急",
};
@@ -1,16 +0,0 @@
export default {
0: "待签收",
10: "待分析",
20: "分析中",
25: "分析完成已提交",
30: "已分析待审核",
40: "分析审核通过",
50: "分析审核未通过",
60: "行动已下发待签收",
70: "行动已签收待策划",
80: "行动策划已提交",
90: "作战方案已提交",
100: "作战方案待审核",
110: "作战方案已审核",
120: "作战方案未通过",
};
@@ -1,36 +0,0 @@
<template>
<div class="AIChatExtract">
<div class="box">
AIChatExtract
</div>
</div>
</template>
<script>
export default {
data() {
return {
};
},
methods: {
},
watch: {
},
};
</script>
<style lang="less">
.AIChatExtract {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
z-index: 1001;
background-color: rgba(0, 0, 0, .2);
.box {
color: #fff;
}
}
</style>
@@ -1,702 +0,0 @@
<template>
<div class="action-overview">
<t-loading :loading="loading" show-overlay>
<t-layout>
<t-aside width="568px"></t-aside>
<t-layout>
<div class="overview-head"></div>
<div class="overview-body">
<div v-if="!loading" ref="graphRef" class="graph-container"></div>
</div>
</t-layout>
<t-aside width="562px">
<div class="graph-map-content">
<div class="collapse-content collapse-content-right flex-center-center cursor-pointer"
v-if="expandRightShow" @click="expandRightChange">
<img src="@/assets/map-icon/expand-logo.svg" alt="" />
</div>
<div class="expand-content" :class="{ 'expand-hidden': expandRightShow }">
<div class="graph-map">
<div class="list-content">
<div class="expand-logo cursor-pointer" @click="expandRightChange">
<img src="../assets/map-icon/expand-logo.svg" alt="" />
</div>
<div class="list-item" v-for="(item, index) in graphList" :key="index">
<div class="item-logo">
<img :src="item.imgUrl" alt="" />
</div>
<div class="item-title">{{ item.title }}</div>
</div>
</div>
</div>
</div>
</div>
</t-aside>
</t-layout>
</t-loading>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, watch } from 'vue';
import { Graph, Quadratic, register, ExtensionCategory, Circle } from '@antv/g6'
import { requestAxiosService } from '@/service/request-axios-service';
import { deepCopy, uniqueById } from '@/common/chatData';
const props = defineProps({
msg: String,
actionData: Object,
actionId: [String, Number, Object, Boolean, Array]
})
const count = ref(0);
let graph = null;
const graphRef = ref(null);
let treeMapData = {
nodes: [],
edges: [],
}
const service = requestAxiosService;
const loading = ref(false);
import xdLogo from "@/assets/map-icon/xd-logo.svg"
import xdLogoPng from "@/assets/map-icon/xd-logo.png"
import bcLogo from "@/assets/map-icon/boci-logo.svg"
import bcLogoPng from "@/assets/map-icon/boci-logo.png"
import characterLogo from "@/assets/map-icon/character-logo.svg"
import characterLogoPng from "@/assets/map-icon/character-logo.png"
import userSpreadLogo from "@/assets/map-icon/user-spread-logo.svg"
import userSpreadLogoPng from "@/assets/map-icon/user-spread-logo.png"
import audienceLogo from "@/assets/map-icon/audience-logo.svg"
import audienceLogoPng from "@/assets/map-icon/audience-logo.png"
import strategyLogo from "@/assets/map-icon/strategy-logo.png"
const graphList = [
{ imgUrl: xdLogo, title: 'XD' },
{ imgUrl: bcLogo, title: '波次' },
{ imgUrl: characterLogo, title: '人设' },
{ imgUrl: userSpreadLogo, title: '关键传播节点' },
{ imgUrl: audienceLogo, title: '受众群体' },
// { imgUrl: `${environment['g6ImgUrl'] || '/src/assets'}/map-icon/user-logo.svg`, title: '受众目标' },
]
const expandRightShow = ref(false);
onMounted(() => {
// initGraph()
})
const expandRightChange = () => {
expandRightShow.value = !expandRightShow.value;
}
// 查询关键传播节点-未完成
const copRelationingSearch = async (actionId) => {
const parmy = {
actionId,
}
loading.value = true;
const res = await service.get(`${environment['b1ConsumerUrl']}/cop/copRelationing`, parmy);
loading.value = false;
console.log(res);
treeMapData.nodes = [];
treeMapData.edges = [];
let result = res.data;
treeMapData.nodes.push({
id: actionId,
data: {
type: 'action',
bgColor: '#cf1322',
name: '行动'
},
size: 30
});
result?.designs?.forEach((item) => {
treeMapData.nodes.push({
id: item.designId,
data: { type: 'audience', bgColor: '#1569ef', name: item.targetAudience, item: deepCopy(item) },
size: 25
});
treeMapData.edges.push({
source: actionId, target: item.designId, data: { type: 'audience', lineColor: '#23D0FF', name: item.targetAudience }
});
item.designAccount.forEach((aItem) => {
//添加受众目标
// treeMapData.nodes.push({
// id: aItem.id,
// data: { type: 'userList', bgColor: '#1eb07f', name: aItem.accountName, item: deepCopy(aItem) },
// size: 25
// });
// treeMapData.edges.push({
// source: item.designId, target: aItem.id, data: { type: 'userList', lineColor: '#8263FF', name: aItem.accountName }
// });
});
// if (item.strategy) {
// item.strategy.forEach((cItem) => {
// try {
// cItem.extendInfo = JSON.parse(cItem.extendInfo);
// console.log('策略详细信息', cItem.extendInfo)
// } catch (error) {
// console.log('error',error)
// // cItem.namme
// }
// treeMapData.nodes.push({
// id: cItem.id,
// data: { type: 'strategy', bgColor: '#ac77e8', name: cItem.topic, item: deepCopy(cItem) },
// size: 25
// });
// treeMapData.edges.push({
// source: item.designId, target: cItem.id, data: { type: 'strategy', lineColor: '#ac77e8', name: cItem.topic }
// });
// })
// }
});
result?.pathway?.forEach((item) => {
treeMapData.nodes.push({
id: item.pathwayId,
data: { type: 'userSpread', bgColor: '#1eb07f', name: item.accountName, item: deepCopy(item) },
size: 25
});
treeMapData.edges.push({
source: actionId, target: item.pathwayId, data: { type: 'userSpread', lineColor: '#B0DC00', name: item.accountName }
});
// if (item.strategy) {
// item.strategy.forEach((aItem) => {
// treeMapData.nodes.push({
// id: aItem.id,
// data: { type: 'strategy', bgColor: '#ac77e8', name: aItem.topic, item: deepCopy(aItem) },
// size: 25
// });
// treeMapData.edges.push({
// source: item.pathwayId, target: aItem.id, data: { type: 'strategy', lineColor: '#ac77e8', name: aItem.topic }
// });
// })
// }
})
// await graph.value?.clear();
setTimeout(async () => {
await initGraph();
}, 10)
}
// 查询关键传播节点关系图-已完成
const copRelationSearch = async (actionId) => {
console.log('actionData', props.actionData);
const parmy = {
actionId,
}
loading.value = true;
const res = await service.get(`${environment['b1ConsumerUrl']}/cop/copRelations`, parmy);
loading.value = false;
console.log('查询关系图', res);
let result = res.data;
treeMapData.nodes = [];
treeMapData.edges = [];
treeMapData.nodes.push({
id: actionId,
data: {
type: 'action',
bgColor: '#cf1322',
name: '行动'
},
size: 30
})
result.bocis.forEach((item) => {
treeMapData.nodes.push({
id: item.bociId,
data: { type: 'boci', bgColor: '#fff6ea', name: item.bociName, item: deepCopy(item) },
size: 25
})
treeMapData.edges.push({
source: actionId, target: item.bociId, data: { type: 'action', lineColor: '#3683FF', name: item.bociName }
})
item.strategies.forEach((aItem) => {
// treeMapData.nodes.push({
// id: aItem.strategyId,
// data: { type: 'strategy', bgColor: '#ac77e8', name: aItem.targetAudience, item: deepCopy(aItem) },
// size: 25
// })
// treeMapData.edges.push({
// source: item.bociId, target: aItem.strategyId, data: { type: 'strategy', lineColor: '#ac77e8', name: aItem.targetAudience }
// })
aItem.personas.forEach((bItem, bIndex) => {
treeMapData.nodes.push({
id: 'persona' + bIndex + bItem.persona,
data: { type: 'character', bgColor: '#fff6ea', name: bItem.persona, item: deepCopy(bItem) },
size: 25
})
treeMapData.edges.push({
source: item.bociId, target: 'persona' + bIndex + bItem.persona, data: { type: 'character', lineColor: '#8263FF', name: bItem.persona }
})
bItem.pathwayRelations.forEach((cItem) => {
treeMapData.nodes.push({
id: cItem.pathwayId,
data: { type: 'userSpread', bgColor: '#1eb07f', name: cItem.pathwayAccountName, item: deepCopy(cItem) },
size: 25
})
treeMapData.edges.push({
source: 'persona' + bIndex + bItem.persona, target: cItem.pathwayId, data: { type: 'userSpread', lineColor: '#B0DC00', name: cItem.pathwayAccountName }
})
})
bItem.designRelations.forEach((dItem) => {
treeMapData.nodes.push({
id: dItem.designId,
data: { type: 'audience', bgColor: '#1569ef', name: dItem.targetAudience, item: deepCopy(dItem) },
size: 25
})
treeMapData.edges.push({
source: 'persona' + bIndex + bItem.persona, target: dItem.designId, data: { type: 'audience', lineColor: '#23D0FF', name: dItem.targetAudience }
})
})
})
})
});
treeMapData.nodes = uniqueById(treeMapData.nodes)
console.log('查询关系处理', treeMapData);
// await graph.value?.clear();
setTimeout(async () => {
await initGraph();
}, 10)
}
watch(() => props.actionData, (newVal, oldVal) => {
console.log('count changed from-------------', oldVal, 'to', newVal);
if (newVal) {
if (newVal.actionStatus === 25) {
copRelationSearch(newVal.id);
} else if (newVal.actionStatus === 20) {
copRelationingSearch(newVal.id)
}
}
}, { immediate: true });
// 渲染关系图
const initGraph = () => {
console.log('environment', environment)
// 自定义边
class AntLine extends Quadratic {
onCreate() {
const shape = this.shapeMap.key;
shape.animate([{ lineDashOffset: 20 }, { lineDashOffset: 0 }], { // 添加动画
duration: 500,
iterations: Infinity,
})
}
}
register(ExtensionCategory.EDGE, 'ant-line', AntLine);
// 定义自定义节点
class CustomImageCircleNode extends Circle {
render(attributes = this.parsedAttributes, container) {
super.render(attributes, container); // 渲染圆形节点
const [width, height] = this.getSize(attributes);
// 获取节点数据中的图片源
const { type, platform } = attributes;
// console.log('attributes', attributes);
// 如果有图片源,则添加图片元素
if (type === 'action') { // 当节点类型为行动
// console.log('imgUrl', imgUrl, width);
this.upsert(
'node-image',
'image',
{
x: -width / 2, // 图片的x坐标,使其居中
y: -height / 2, // 图片的y坐标,使其居中
width: width, // 图片宽度略小于圆形节点
height: height, // 图片高度略小于圆形节点
src: xdLogoPng,
},
container
);
this.upsert('subtitle', 'text', {
x: 0,
y: 0, // 节点中心
text: '行动',
fontSize: 16,
fill: '#fff',
textAlign: 'center',
textBaseline: 'middle',
fontWeight: 'bold' // 设置字体加粗
}, container)
} else if (type === 'boci') { // 节点类型为波次
// this.upsert(
// 'node-circle',
// 'rect',
// {
// x: -width / 2, // 图片的x坐标,使其居中
// y: -height / 2, // 图片的y坐标,使其居中
// width: width - 2, // 图片宽度略小于圆形节点
// height: height - 2, // 图片高度略小于圆形节点
// fill: 'transparent',
// stroke: '#fc5',
// lineWidth: 5,
// radius: 50,
// labelBackground: true,
// },
// container
// );
this.upsert(
'node-image',
'image',
{
x: -width / 2, // 图片的x坐标,使其居中
y: -height / 2, // 图片的y坐标,使其居中
width: width, // 图片宽度略小于圆形节点
height: height, // 图片高度略小于圆形节点
src: bcLogoPng,
},
container
);
this.upsert('subtitle', 'text', {
x: 10,
y: 0, // 节点中心
text: '波次',
fontSize: 16,
fill: '#fff',
textAlign: 'center',
textBaseline: 'middle',
fontWeight: 'bold' // 设置字体加粗
}, container)
} else if (type === 'character') { // 节点类型为人设
this.upsert(
'node-image',
'image',
{
x: -width / 2, // 图片的x坐标,使其居中
y: -height / 2, // 图片的y坐标,使其居中
width: width, // 图片宽度略小于圆形节点
height: height, // 图片高度略小于圆形节点
src: characterLogoPng,
},
container
);
} else if (type === 'userSpread') { // 节点类型为关键传播节点
this.upsert(
'node-image',
'image',
{
x: -width / 2, // 图片的x坐标,使其居中
y: -height / 2, // 图片的y坐标,使其居中
width: width, // 图片宽度略小于圆形节点
height: height, // 图片高度略小于圆形节点
src: userSpreadLogoPng,
},
container
);
} else if (type === 'audience') { // 节点类型为受众群体
this.upsert(
'node-image',
'image',
{
x: -width / 2, // 图片的x坐标,使其居中
y: -height / 2, // 图片的y坐标,使其居中
width: width, // 图片宽度略小于圆形节点
height: height, // 图片高度略小于圆形节点
src: audienceLogoPng,
},
container
);
} else if (type === 'userList') { // 节点类型为受众目标
this.upsert(
'node-image',
'image',
{
x: -width / 2, // 图片的x坐标,使其居中
y: -height / 2, // 图片的y坐标,使其居中
width: width, // 图片宽度略小于圆形节点
height: height, // 图片高度略小于圆形节点
src: userSpreadLogoPng,
},
container
);
} else if (type === 'strategy') {
this.upsert(
'node-image',
'image',
{
x: -width / 2, // 图片的x坐标,使其居中
y: -height / 2, // 图片的y坐标,使其居中
width: width, // 图片宽度略小于圆形节点
height: height, // 图片高度略小于圆形节点
src: strategyLogo,
},
container
);
}
}
}
// 2. 注册自定义节点
register(ExtensionCategory.NODE, 'custom-image-circle', CustomImageCircleNode);
const data = {
nodes: [
{ id: 'node1', data: { type: 'action', bgColor: '#cf1322', name: '行动1' }, size: 30 },
{ id: 'node2', data: { type: 'boci', bgColor: '#fff6ea', name: '波次1' }, size: 25 },
{ id: 'node3', data: { type: 'boci', bgColor: '#fff6ea', name: '波次2' }, size: 25 },
{ id: 'node4', data: { type: 'boci', bgColor: '#fff6ea', name: '波次3' }, size: 25 },
{ id: 'node5', data: { type: 'character', bgColor: '#fff6ea', name: '人设01' }, size: 25 },
{ id: 'node6', data: { type: 'character', bgColor: '#fff6ea', name: '人设02' }, size: 20 },
{ id: 'node7', data: { type: 'character', bgColor: '#fff6ea', name: '人设03' }, size: 25 },
{ id: 'node8', data: { type: 'character', bgColor: '#fff6ea', name: '人设04' }, size: 20 },
{ id: 'node9', data: { type: 'character', lineColor: '#fff6ea', name: '人设05' }, size: 25 },
{ id: 'node10', data: { type: 'character', bgColor: '#fff6ea', name: '人设06' }, size: 20 },
{ id: 'node11', data: { type: 'userSpread', platform: 'facebook', bgColor: '#1eb07f', name: '关键传播节点01' }, size: 20 },
{ id: 'node12', data: { type: 'userSpread', platform: 'facebook', bgColor: '#1eb07f', name: '关键传播节点01' }, size: 20 },
{ id: 'node13', data: { type: 'userSpread', platform: 'facebook', bgColor: '#1eb07f', name: '关键传播节点01' }, size: 20 },
{ id: 'node14', data: { type: 'userSpread', platform: 'facebook', bgColor: '#1eb07f', name: '关键传播节点01' }, size: 20 },
{ id: 'node15', data: { type: 'userSpread', platform: 'facebook', bgColor: '#1eb07f', name: '关键传播节点01' }, size: 20 },
{ id: 'node16', data: { type: 'userSpread', platform: 'facebook', bgColor: '#1eb07f', name: '关键传播节点01' }, size: 20 },
{ id: 'node17', data: { type: 'userSpread', platform: 'facebook', bgColor: '#1eb07f', name: '关键传播节点01' }, size: 20 },
{ id: 'node18', data: { type: 'audience', platform: 'facebook', bgColor: '#1569ef', name: '关键传播节点01' }, size: 20 },
{ id: 'node19', data: { type: 'audience', platform: 'facebook', bgColor: '#1569ef', name: '关键传播节点01' }, size: 20 },
{ id: 'node20', data: { type: 'audience', platform: 'facebook', bgColor: '#1569ef', name: '关键传播节点01' }, size: 20 },
{ id: 'node21', data: { type: 'audience', platform: 'facebook', bgColor: '#1569ef', name: '关键传播节点01' }, size: 20 },
{ id: 'node22', data: { type: 'audience', platform: 'facebook', bgColor: '#1569ef', name: '关键传播节点01' }, size: 20 },
{ id: 'node23', data: { type: 'audience', platform: 'facebook', bgColor: '#1569ef', name: '关键传播节点01' }, size: 20 },
{ id: 'node24', data: { type: 'audience', platform: 'facebook', bgColor: '#1569ef', name: '关键传播节点01' }, size: 20 },
],
edges: [
{ source: 'node1', target: 'node2', data: { type: 'action', lineColor: '#cf1322', name: '行动1' } },
{ source: 'node2', target: 'node5', data: { type: 'boci', lineColor: '#dd8717', name: '波次1' } },
{ source: 'node5', target: 'node11', data: { type: 'character', lineColor: '#fff6ea', name: '人设01' } },
{ source: 'node11', target: 'node18', data: { type: 'userSpread', platform: 'facebook', lineColor: '#1eb07f', name: '关键传播节点01' } },
{ source: 'node2', target: 'node6', data: { type: 'audience', platform: 'facebook', lineColor: '#1569ef', name: '关键传播节点01' } },
{ source: 'node6', target: 'node12', data: { type: 'character', lineColor: '#fff6ea', name: '人设01' } },
{ source: 'node12', target: 'node19', data: { type: 'userSpread', platform: 'facebook', lineColor: '#1eb07f', name: '关键传播节点01' } },
{ source: 'node1', target: 'node3', data: { type: 'action', lineColor: '#cf1322', name: '行动1' } },
{ source: 'node3', target: 'node7', data: { type: 'boci', lineColor: '#dd8717', name: '波次1' } },
{ source: 'node7', target: 'node13', data: { type: 'character', lineColor: '#fff6ea', name: '人设01' } },
{ source: 'node13', target: 'node20', data: { type: 'userSpread', platform: 'facebook', lineColor: '#1eb07f', name: '关键传播节点01' } },
{ source: 'node3', target: 'node8', data: { type: 'audience', platform: 'facebook', lineColor: '#1569ef', name: '关键传播节点01' } },
{ source: 'node8', target: 'node14', data: { type: 'character', lineColor: '#fff6ea', name: '人设01' } },
{ source: 'node14', target: 'node21', data: { type: 'userSpread', platform: 'facebook', lineColor: '#1eb07f', name: '关键传播节点01' } },
{ source: 'node1', target: 'node4', data: { type: 'action', lineColor: '#cf1322', name: '行动1' } },
{ source: 'node4', target: 'node9', data: { type: 'boci', lineColor: '#dd8717', name: '波次1' } },
{ source: 'node9', target: 'node15', data: { type: 'character', lineColor: '#fff6ea', name: '人设01' } },
{ source: 'node15', target: 'node22', data: { type: 'userSpread', platform: 'facebook', lineColor: '#1eb07f', name: '关键传播节点01' } },
{ source: 'node4', target: 'node10', data: { type: 'audience', platform: 'facebook', lineColor: '#1569ef', name: '关键传播节点01' } },
{ source: 'node10', target: 'node16', data: { type: 'character', lineColor: '#fff6ea', name: '人设01' } },
{ source: 'node16', target: 'node23', data: { type: 'userSpread', platform: 'facebook', lineColor: '#1eb07f', name: '关键传播节点01' } },
{ source: 'node10', target: 'node17', data: { type: 'character', lineColor: '#fff6ea', name: '人设01' } },
{ source: 'node17', target: 'node24', data: { type: 'audience', platform: 'facebook', lineColor: '#1569ef', name: '关键传播节点01' } },
]
}
// const size = (node) => Math.max(...node.style.size);
graph = new Graph({
container: graphRef.value,
// width: 800,
// height: 600,
data: treeMapData,
modes: {
default: ['drag-canvas', 'zoom-canvas', 'drag-node']
},
node: {
type: 'custom-image-circle', // 使用自定义节点类型
style: {
label: true,
labelText: (d) => d.data?.name,
labelFill: '#fff',
labelBackground: false,
// fill: (d) => d.data.bgColor || '#fc5',
fill: 'transparent',
type: (d) => d.data.type,
platform: (d) => d.data.platform,
size: (d) => {
if (d.data.type === 'action') {
return [120, 60]
} else if (d.data.type === 'boci') {
return [130, 40]
} else {
return [60, 70]
}
}
}
},
edge: {
type: 'ant-line',
style: {
label: false,
// labelText: (d) => d.data?.type,
labelBackground: true,
stroke: (d) => d.data?.lineColor || '#fc5', // 边的颜色,可以根据需要调整
lineWidth: 2.5, // 边的宽度,可以根据需要调整
lineDash: [10, 10],
endArrow: true, // 添加箭头
arrowSize: 6, // 箭头大小
},
},
layout: {
// type: 'radial',
// unitRadius: 220,
// linkDistance: 200,
// type: 'd3-force',
// link: 200,
// // collide: { radius: (node) => size(node) },
// // manyBody: { strength: (node) => -4 * size(node) }
animation: false,
// preventOverlap: true
type: 'd3-force',
preventOverlap: true,
nodeSize: 100,
link: {
distance: 200,
strength: 2
},
collide: { radius: 100 },
manyBody: {
strangth: (d) => {
if (d.isLeaf) {
return -50;
}
return -10;
}
}
},
autoFit: 'view',
behaviors: ['drag-canvas', 'drag-element', 'zoom-canvas',]
})
graph.render();
graph.on('node:click', (event) => {
const nodeItem = event.item; // 节点实例
console.log(event.target.id);
const model = graph.getNodeData(event.target.id);
console.log('节点数据:', model.data);
// const model = nodeItem.getModel(); // 数据模型
// console.log('点击了节点:', model.id);
});
// window.addPanel((gui) => {
// const config = {
// lineDash: 10,
// }
// gui.add(config,'lineDash',1,20,1).onChange((lineDash) => {
// graph.setEdge({
// type: 'ant-line',
// style: {
// lineDash: [lineDash,lineDash]
// }
// })
// })
// graph.draw()
// })
}
</script>
<style scoped lang="less">
.action-overview {
z-index: 500;
color: #fff;
position: relative;
.overview-head {
width: 100%;
height: 115px;
}
.overview-body {
// width: 100%;
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: calc(100vh - 115px);
.graph-container {
width: 100%;
height: 100%;
}
}
.graph-map-content {
z-index: 500;
position: fixed;
bottom: 100px;
right: 20px;
.graph-map {
width: 202px;
background: rgba(3, 38, 80, 0.8);
border: 1px solid;
border-color: rgba(43, 143, 254, 0.6);
z-index: 500;
border-radius: 4px;
padding: 10px 15px;
padding-top: 30px;
.list-content {
position: relative;
.expand-logo {
width: 20px;
height: 11px;
position: absolute;
top: -27px;
right: -8px;
img {
width: 100%;
}
}
.list-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
.item-logo {
width: 62px;
height: 28px;
img {
height: 28px;
}
}
.item-title {
flex: 1;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: 18px;
}
}
}
}
}
.collapse-content {
width: 30px;
height: 40px;
background: #032650cc;
border: 1px solid;
border-color: #2b8ffe99;
border-radius: 4px 0 0 4px;
z-index: 500;
position: absolute;
top: 0;
img {
width: 16px;
}
}
.collapse-content-right {
right: 0;
}
.expand-hidden {
width: 0px !important;
border: 0px !important;
overflow: hidden;
}
.expand-content {
width: 100%;
transition: all 0.5s ease;
float: right;
}
.expand-content-right {
float: right;
}
}
</style>
@@ -1,448 +0,0 @@
<template>
<div class="action-planning">
<!-- 任务详情 -->
<div class="task-details" v-if="flag">
<div class="task-title">
{{ taskData.taskName }}
</div>
<div class="task-content">
<span class="item-label">任务时间:</span>
<span class="item-value">{{
dayjs(taskData.createTime).format("YYYY-MM-DD HH:mm:ss")
}}</span>
<span class="item-tab tab-yellow" style="margin-left: 10px">{{
taskLevel[taskData.taskLevel]
}}</span>
</div>
<div class="task-footer" :class="{ 'text-ellipsis ellipsis3': !allShow }">
<span class="item-label">任务简介:</span> {{ taskData.overview }}
</div>
<div class="allShow" @click="allShow = !allShow">
{{ allShow ? "收起" : "查看全文" }}
</div>
</div>
<!-- 任务行动 -->
<div class="title-content-item margin-bottom-10">任务行动</div>
<div class="action-content">
<div class="action-list">
<div
class="list-item"
:class="{ 'item-active': item.id === actionId }"
v-for="(item, index) in actionList"
:key="index"
@click="actionDataChange(item)"
>
<div :class="'action-status-logo ' + handleBg(item.actionStatus)">
{{ actionStatus[item.actionStatus] }}
</div>
<!-- <div class="item-title">
{{ item.title }}
</div> -->
<div class="item-content text-ellipsis ellipsis2">
{{ item.actionName }}
</div>
<div class="item-footer">
<span class="item-label">行动时间:</span>
<span class="item-value">{{
dayjs(item.createTime).format("YYYY-MM-DD HH:mm:ss")
}}</span>
</div>
</div>
</div>
</div>
<t-dialog
v-model:visible="visible"
header="行动详情"
width="700"
:confirm-on-enter="true"
:on-cancel="handleCancel"
:on-close="handleCancel"
:on-confirm="handleOk"
>
<t-space direction="vertical" style="width: 100%">
<div class="action-details">
<div class="item-head">桃色与军纪舆论裂变攻台军形象</div>
<div class="item-card">
支撑性目的:利用桃色新闻与军纪涣散议题,瓦解台军社会形象与民众支持基础,引发线上网红、大V等对台军……
</div>
<!-- 顶部标签页 -->
<TabsContent
:tabIndex="tabIndex"
:tabsList="tabsList"
:tabStyle="{
textAlign: 'left',
borderBottom: '1px solid rgba(0, 98, 217, 0.4)',
}"
@tabsDataC="tabDataChange"
/>
<div class="narrate-list" v-if="tabIndex === 'narrate'">
<div
class="item-card"
v-for="(item, index) in narrateList"
:key="index"
>
<span class="item-index">{{ index + 1 }}</span
>. {{ item.title }}
</div>
</div>
<div class="behavior-list">
<div
class="item-card"
v-for="(item, index) in behaviorList"
:key="index"
>
<div class="item-title">行为体:{{ item.actor }}</div>
<div class="item-content">预期行为:{{ item.behavior }}</div>
</div>
</div>
</div>
</t-space>
</t-dialog>
</div>
</template>
<script setup>
import { ref, reactive, watch, onUnmounted } from "vue";
import TabsContent from "./views/TabsContent.vue";
import taskStatus from "@/common/taskStatus";
import taskLevel from "@/common/taskLevel";
import sourceTypeDict from "@/common/sourceType";
import actionStatus from "@/common/actionStatus";
import dayjs from "dayjs";
import { requestAxiosService } from "@/service/request-axios-service";
import { useStore } from "vuex";
const flag = ref(false);
const props = defineProps({
msg: String,
taskData: Object,
});
const allShow = ref(false);
const actionList = ref([
{
title: "行动01",
content: "军购透明化对比民生缺口,激青年声讨",
actionStatus: "CEH完成",
actionLogo: "action-completed",
},
{
title: "行动01",
content: "军购透明化对比民生缺口,激青年声讨",
actionStatus: "CEH中",
actionLogo: "action-uncompleted",
},
{
title: "行动01",
content: "军购透明化对比民生缺口,激青年声讨",
actionStatus: "待分析",
actionLogo: "action-pendinganalysis",
},
{
title: "行动01",
content: "军购透明化对比民生缺口,激青年声讨",
actionStatus: "已终止",
actionLogo: "action-failed",
},
{
title: "行动01",
content: "军购透明化对比民生缺口,激青年声讨",
actionStatus: "CEH完成",
actionLogo: "action-completed",
},
]); // 行动列表
const visible = ref(false); // 对话框
const tabIndex = ref("narrate"); // narrate 叙事主题 behavior 预期行为
const tabsList = ref([
{ label: "叙事主题", value: "narrate" },
{ label: "预期行为", value: "behavior" },
]);
const narrateList = ref([
{ title: "台军桃色丑闻频发暴露军纪涣散" },
{ title: "飞行事故频发揭穿台军装备老旧与训练造假" },
]);
const behaviorList = ref([
{
actor: "台军各军种单位",
behavior:
"通过社交媒体或媒体渠道揭露上级贪腐、性丑闻等(如陆军109旅贩毒案后内部爆料)",
},
]);
const actionId = ref(null);
const service = requestAxiosService;
// 获取 Vuex store
const store = useStore();
const emit = defineEmits(["actionDataC"]); // 定义子组件发送给父组件的函数
const actionShowChange = (item) => {
// visible.value = true;
};
const handleOk = () => {
visible.value = false;
};
const handleCancel = () => {
visible.value = false;
};
// 接收tabs页切换
const tabDataChange = (value) => {
tabIndex.value = value;
console.log("tabs", tabIndex.value);
};
const handleBg = (d) => {
const colorList = {
10: "action-pendinganalysis",
20: "action-uncompleted",
25: "action-completed",
23: "action-failed",
};
return colorList[d] || "action-completed";
};
// 查询行动略表
const taskActionSearch = async (taskId) => {
const parmy = {
taskId,
};
const res = await service.get(
`${environment["b1ConsumerUrl"]}/cop/copTaskActions`,
parmy
);
console.log("查询行动列表", res);
let result = res.data;
actionList.value = result.actions;
};
// 查询行动概览
const actionSummarySearch = async () => {
const parmy = {
actionId: actionId.value,
};
const res = await service.get(
`${environment["b1ConsumerUrl"]}/cop/copActionSummary`,
parmy
);
console.log("行动概览", res);
let result = res.data;
store.commit("setActionOverview", result);
};
// cop/queryOverview
const queryOverview = async () => {
const res = await service.get(
`${environment["b1ConsumerUrl"]}/cop/queryOverview`,
""
);
console.log("概览", res);
};
const actionDataChange = async (item) => {
console.log(item);
actionId.value = item.id;
store.commit("setActionOverview", null);
if (item.actionStatus === 25) {
await actionSummarySearch();
}
emit("actionDataC", item);
};
onUnmounted(() => {
store.commit("setActionOverview", null);
});
watch(
props.taskData,
(newVal, oldVal) => {
// console.log('count changed from', oldVal, 'to', newVal);
if (newVal) {
queryOverview();
taskActionSearch(newVal.id);
}
},
{ immediate: true }
);
</script>
<style scoped lang="less">
.action-planning {
z-index: 500;
color: #fff;
padding: 10px;
box-sizing: border-box;
// 任务详情样式
.task-details {
padding: 10px;
.task-title {
font-size: 20px;
font-weight: 500;
font-family: "Noto Sans S Chinese";
}
.task-content {
margin: 5px 0;
font-family: "Noto Sans S Chinese";
}
.task-footer {
font-family: "Noto Sans S Chinese";
}
}
// 行动列表样式
.action-content {
.action-list {
.list-item {
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
padding: 10px;
margin-bottom: 15px;
position: relative;
cursor: pointer;
overflow: hidden;
&:hover {
background: rgba(0, 98, 217, 0.4);
border-color: #0062d9;
}
.item-title {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #ffffff;
font-size: var(--titleSize);
margin-bottom: 5px;
}
.item-content {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSize);
margin-bottom: 5px;
}
.item-footer {
font-family: Noto Sans S Chinese;
font-weight: 300;
color: rgba(255, 255, 255, 0.8);
font-size: 15px;
}
.action-status-logo {
width: 132px;
height: 30px;
transform: rotate(45deg);
position: absolute;
top: 12px;
text-align: center;
line-height: 30px;
right: -36px;
}
.action-completed {
background: linear-gradient(
134.57deg,
#05b89f 14.74%,
#007f73 84.95%
);
}
.action-uncompleted {
background: linear-gradient(
134.57deg,
#d09a14 14.74%,
#a76e12 84.95%
);
}
.action-pendinganalysis {
background: linear-gradient(
134.57deg,
#117dff 14.74%,
#0057b8 84.95%
);
}
.action-failed {
background: linear-gradient(
134.57deg,
#838383 14.74%,
#5e5e5e 84.95%
);
}
}
.item-active {
background: rgba(0, 98, 217, 0.4);
border-color: #0062d9;
}
}
}
.item-label {
font-family: "Noto Sans S Chinese";
font-size: 15px;
margin-right: 5px;
}
.item-value {
font-family: "DDIN";
}
.item-tab {
font-size: var(--fontSizes);
font-family: "Noto Sans S Chinese";
padding: 2px 10px;
border: 1px solid;
border-radius: 4px;
}
}
// 详情弹窗页面
.action-details {
.item-head {
font-size: var(--fontSize);
font-family: "Noto Sans S Chinese";
padding: 10px;
}
.item-card {
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
padding: 10px;
margin-top: 10px;
margin-bottom: 15px;
.item-index {
font-family: "Noto Sans S Chinese";
font-weight: 500;
}
.item-title {
// font-size: var(--fontSize);
font-family: "Noto Sans S Chinese";
// font-weight: 500;
margin-bottom: 5px;
}
}
}
.allShow {
margin-top: 10px;
max-width: 100px;
padding: 5px;
border-radius: 3px;
cursor: pointer;
line-height: 20px;
border: 1px solid #2b8ffe99 !important;
text-align: center;
}
</style>
@@ -1,242 +0,0 @@
<template>
<div class="performance-evaluation">
<!-- 六度量样式 -->
<div class="six-metrics">
<t-row :gutter="16">
<t-col :span="4">
<div class="item-left">
<div class="metrices-item item-completed" style="margin-left: 50px;">
<div class="item-value">
108<i>个</i>
</div>
<div class="item-label">
大V转发
</div>
</div>
<div class="metrices-item item-completed" style="margin-left: 10px;">
<div class="item-value">
387<i>家</i>
</div>
<div class="item-label">
媒体报道
</div>
</div>
<div class="metrices-item item-completed" style="margin-left: 50px;">
<div class="item-value">
16<i>个</i>
</div>
<div class="item-label">
传播平台
</div>
</div>
</div>
</t-col>
<t-col :span="3">
<div class="item-center">
<div class="level-content">
<div class="metrices-item item-center-bg">
<div class="level-text">
五级
</div>
<div class="level-list">
<span class="list-item" v-for="(item, index) in levelList" :class="{ 'item-active': item.checked }"
:key="index">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" style="">
<path fill="currentColor"
d="M313.6 924.48a70.4 70.4 0 0 1-74.152-5.365 70.4 70.4 0 0 1-27.992-68.875l37.888-220.928L88.96 472.96a70.4 70.4 0 0 1 3.788-104.225A70.4 70.4 0 0 1 128 352.896l221.76-32.256 99.2-200.96a70.4 70.4 0 0 1 100.246-28.595 70.4 70.4 0 0 1 25.962 28.595l99.2 200.96 221.824 32.256a70.4 70.4 0 0 1 39.04 120.064L774.72 629.376l37.888 220.928a70.4 70.4 0 0 1-102.144 74.24L512 820.096l-198.4 104.32z">
</path>
</svg>
</span>
</div>
<div class="item-label">
评估等级
</div>
</div>
</div>
</div>
</t-col>
<t-col :span="5">
<div class="item-right">
<div class="metrices-item item-incomplete">
<div class="item-value">
0<i>个</i>
</div>
<div class="item-label">
线下活动
</div>
</div>
<div class="metrices-item item-completed" style="margin-left: 50px;">
<div class="item-value">
6<i>个</i>
</div>
<div class="item-label">
官方回应
</div>
</div>
<div class="metrices-item item-completed">
<div class="item-value">
5<i>个</i>
</div>
<div class="item-label" style="padding-left: 5px;">
关键节点
</div>
</div>
</div>
</t-col>
</t-row>
</div>
<!-- tab标签页 -->
<TabsContent :tabIndex="tabIndex" :tabsList="tabsList" @taskDetailsC="taskDetailsChange"/>
</div>
</template>
<script setup>
import { ref } from 'vue';
import TabsContent from '../views/TabsContent.vue';
defineProps({
msg: String,
})
const levelList = ref([
{ title: 1, checked: true },
{ title: 1, checked: true },
{ title: 1, checked: true },
{ title: 1, checked: true },
{ title: 1, checked: true },
{ title: 1, checked: false },
])
const tabIndex = ref('cognition'); // cognition 认知影响 spread 传播效果 action 行动脉络
const tabsList = ref([
{ label: '认知影响', value: 'cognition' },
{ label: '传播效果', value: 'spread' },
{ label: '行动脉络', value: 'action' },
])
// 标签页切换
const taskDetailsChange = (value) => {
tabIndex.value = value;
}
</script>
<style scoped lang="less">
.performance-evaluation {
z-index: 500;
color: #fff;
padding: 10px;
.six-metrics {
width: 100%;
padding-top: 15px;
margin-bottom: 25px;
.item-left {
width: 100%;
height: 100%;
}
.item-center {
width: 100%;
height: 100%;
position: relative;
.level-content {
padding-left: 15px;
position: absolute;
top: 50px;
left: 50%;
transform: translateX(-50%);
}
}
.item-right {
width: 100%;
height: 100%;
}
.metrices-item {
width: 100px;
height: 62px;
padding-top: 7px;
text-align: center;
}
.item-value {
color: #01e1e9;
font-size: var(--titleSize);
font-family: 'DDINBold';
font-weight: 700;
line-height: var(--fontSize);
i {
font-size: 12px;
font-style: normal;
color: #fff;
font-weight: 300;
margin-left: 5px;
}
}
.item-label {
font-family: Noto Sans S Chinese;
}
.level-text {
font-size: 24px;
font-family: Noto Sans S Chinese;
font-weight: 700;
padding-top: 5px;
animation: levelBreath 2s ease-in-out infinite;
}
.level-list {
width: 120px;
margin-top: 25px;
.list-item {
svg {
width: 20px;
color: #05727a;
}
}
.item-active {
svg {
color: #00ffff !important;
}
}
}
.item-completed {
background: url('../../assets/icon/tj-blue.png') no-repeat;
background-size: cover;
}
.item-incomplete {
background: url('../../assets/icon/tj-grey.png') no-repeat;
background-size: cover;
}
.item-center-bg {
background: url('../../assets/icon/cen-top.png') no-repeat;
background-size: cover;
}
}
}
// 级别动画
@keyframes levelBreath {
0% {
text-shadow: 0 0 0 #f90;
}
50% {
text-shadow: 0 0 25px #f90;
}
100% {
text-shadow: 0 0 0 #f90;
}
}
</style>
@@ -1,43 +0,0 @@
<script setup>
import { ref } from 'vue'
defineProps({
msg: String,
})
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>
@@ -1,549 +0,0 @@
<template>
<!-- @click="getChatInfo()" -->
<div class="frame">
<div class="opentiong">
<div class="isShowBtn" @click="getChatInfo()">
<img src="@/assets/icon/刷新.svg" alt="" />
</div>
<div class="isShowBtn" @click="quanpingModel = !quanpingModel">
<img src="@/assets/icon/流式输出.svg" alt="" />
</div>
<div class="isShowBtn" @click="isShow = !isShow">
<img src="@/assets/map-icon/expand-logo.svg" alt="" />
</div>
</div>
<div class="huakuai" v-if="isShow" @mousedown.prevent="startSplitting" @mouseup.prevent="endSplitting">
</div>
<div class="InfoChatView" ref="chatList"
:style="isShow ? { width: currentWidth + 'px' } : { width: '0px', border: 'none' }">
<t-collapse :default-value="[1]" @change="handlePanelChange" default-expand-all>
<t-collapse-panel header="思考过程"> <t-collapse default-expand-all>
<template v-for="(item, index) in chatList">
<t-collapse-panel header="思考过程" v-if="item.type === 'show_text'">
<CodeData :markdown="item.text"></CodeData>
</t-collapse-panel>
<t-collapse-panel header="分析报告" v-if="item.type === 'summary'">
<CodeData :markdown="item.text"></CodeData>
</t-collapse-panel>
<template v-if="item.type === 'google_search' && item.data['q']">
<div class="search" v-if="index === chatList.length - 1">
<t-loading text="搜索中..." size="small"></t-loading>{{
item.data['q'] }}
</div>
</template>
<t-collapse-panel :header="chatList[index - 1].data['q'] || '思考过程'"
v-if="item.type === 'google_search' && item.data['result']">
<div class="resultList">
<div class="item" @click="openDetails(item)"
v-for="(item, i) in JSON.parse(item.data['result']).organic">
<div class="eventTitle text-ellipsis ellipsis1">
<span class="num"> {{ i + 1 }} </span> {{ item.title }}
</div>
<div class="eventContent text-ellipsis ellipsis3">
{{ item.content }}
</div>
</div>
</div>
</t-collapse-panel>
</template>
</t-collapse></t-collapse-panel>
</t-collapse>
<div class="summary">
<CodeData :markdown="summary"></CodeData>
</div>
<t-loading v-if="loading" :loading="loading" show-overlay></t-loading>
</div>
</div>
<t-dialog v-model:visible="detailModel" header="详情" :zIndex="2000" width="1000" :footer='false'>
<t-space direction=" vertical" style="width: 100%">
<div class="detailModel">
<div class="title">
{{ detailData.title }}
</div>
<!-- <a class="link" target="_blank"> 原文链接</a> -->
<t-link hover="color"
:href="'http://chlib.b.uat.4.cn/knowlibrary/#/knowledge/mid-detail?recUuid=' + detailData.recUuid"
target="_blank">
<template #suffix-icon>
<jump-icon />
</template>
跳转链接
</t-link>
<div class="content">
<CodeData :markdown="detailData.content" />
</div>
</div>
</t-space>
</t-dialog>
<t-dialog v-model:visible="quanpingModel" header="详情" width="1600" :footer='false'>
<t-space direction=" vertical" style="width: 100%">
<div style="display: flex;flex-direction: column;gap: 10px;">
<t-collapse :default-value="[1]" @change="handlePanelChange" default-expand-all>
<t-collapse-panel header="思考过程"> <t-collapse default-expand-all>
<template v-for="(item, index) in chatList">
<t-collapse-panel header="思考过程" v-if="item.type === 'show_text'">
<CodeData :markdown="item.text"></CodeData>
</t-collapse-panel>
<t-collapse-panel header="分析报告" v-if="item.type === 'summary'">
<CodeData :markdown="item.text"></CodeData>
</t-collapse-panel>
<template v-if="item.type === 'google_search' && item.data['q']">
<div class="search" v-if="index === chatList.length - 1">
<t-loading text="搜索中..." size="small"></t-loading>{{
item.data['q'] }}
</div>
</template>
<t-collapse-panel :header="chatList[index - 1].data['q'] || '思考过程'"
v-if="item.type === 'google_search' && item.data['result']">
<div class="resultList">
<div class="item" @click="openDetails(item)"
v-for="(item, i) in JSON.parse(item.data['result']).organic">
<div class="eventTitle text-ellipsis ellipsis1">
<span class="num"> {{ i + 1 }} </span> {{ item.title }}
</div>
<div class="eventContent text-ellipsis ellipsis3">
{{ item.content }}
</div>
</div>
</div>
</t-collapse-panel>
</template>
</t-collapse></t-collapse-panel>
</t-collapse>
<div class="summary">
<CodeData :markdown="summary"></CodeData>
</div>
<t-loading v-if="loading" :loading="loading" show-overlay></t-loading>
</div>
</t-space>
</t-dialog>
</template>
<script>
import { requestAxiosService } from "@/service/request-axios-service"; // 引入发送异步请求方法
import CodeData from '../markdown/CodeData.vue'
import { MessagePlugin } from 'tdesign-vue-next';
import { LinkIcon, JumpIcon } from 'tdesign-icons-vue-next';
export default {
data() {
return {
rawData: [],
chatList: [],
quanpingModel: false,
loading: false,
isShow: true,
service: requestAxiosService,
abortController: null,
MessagePlugin: MessagePlugin,
isResizing: false,
currentWidth: 320,
startX: 0, //开始的X坐标
startWidth: 0, //开始的宽度
detailModel: false,
detailData: {},
summary: '', //总结
};
},
props: ['prompt', 'generate', 'taskId', 'taskChatList', 'taskSummary'],
components: { CodeData, JumpIcon },
computed: {
},
methods: {
openDetails(d) {
console.log(d);
this.detailData = d
this.detailModel = true
},
startSplitting(e) {
this.isResizing = true
this.startX = e.pageX;
this.startWidth = this.currentWidth
console.log(document);
document.addEventListener('mousemove', this.handleMouseMove);
document.addEventListener('mouseup', this.endResizing);
},
handleMouseMove(e) {
if (!this.isResizing) return;
const deltaX = this.startX - e.pageX;
const newWidth = this.startWidth + deltaX;
// 设置宽度限制
const minWidth = 320;
const maxWidth = 800;
this.currentWidth = Math.max(minWidth, Math.min(newWidth, maxWidth));
console.log(this.currentWidth);
},
endResizing() {
this.isResizing = false;
document.removeEventListener('mousemove', this.handleMouseMove);
document.removeEventListener('mouseup', this.endResizing);
},
dataHandl() {
console.log(this.rawData);
console.log(this.chatList);
this.service
.post(
`${environment["b1ConsumerUrl"]}/cop/batchEnemyElement`,
{
"data": [JSON.stringify(this.chatList), this.summary, ...this.rawData],
"taskId": this.taskId,
}
)
.then((res) => {
console.log("热点事件", res);
this.$emit('upDataInfo')
});
},
async getChatInfo() {
if (this.loading) {
this.MessagePlugin.warning('正在生成中,请稍后!')
return
}
this.loading = true
this.chatList = []
this.summary = ''
this.abortController = new AbortController();
try {
const response = await fetch(`https://ch1.b.uat.4.cn/qq_b1_chat/generate_qq`, {
// const response = await fetch(`http://localhost:8095/generate_qq`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt: this.prompt
}),
// signal: this.abortController.signal
});
// console.log(response);
if (!response.ok) {
const errorData = await response.json().catch(() => ({ detail: 'Unknown error' }));
throw new Error(errorData.detail || `HTTP error ${response.status}`);
}
if (!response.body) {
throw new Error('Response body is null');
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
console.log(done, value);
if (done) {
this.loading = false
this.dataHandl()
break;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
const lastLine = lines.pop();
buffer = lastLine || '';
console.log(lines);
for (let line of lines) {
if (!line.startsWith('data: ')) continue;
this.rawData.push(line.slice(6))
try {
// 解析SSE数据
// line = line.replace(/'/g, '"').trim();
const parsedData = JSON.parse(line.slice(6));
const { event, data } = parsedData;
// 处理不同类型的事件
switch (event) {
case 'task_created': {
// taskIdRef.current = data.task_id;
break;
}
case 'tool_call': {
this.handleToolCall(data);
break;
}
case 'message': {
// 处理消息事件
break;
}
case 'done': {
break;
}
case 'references': {
break;
}
default:
console.log(`未知事件类型: ${event}`, data);
}
} catch (error) {
console.error('JSON解析错误:', error);
console.log('原始数据:', line.slice(6));
}
}
}
} catch (err) {
this.loading = false
console.error('JSON解析错误:', err);
console.log('接口中断');
setTimeout(() => {
this.MessagePlugin.warning('数据异常中断,开始重试!')
this.getChatInfo()
}, 1000)
}
},
handleToolCall(d) {
console.log(d);
switch (d.tool_name) {
case 'show_text': {
const chatText = d.tool_input.text || '';
console.log(chatText);
if (chatText.includes('<think>')) {
// 添加新的思考过程
const thinkContent = chatText.replace('<think>', '').replace('</think>', '');
this.chatList.push({
text: thinkContent,
type: d.tool_name,
});
} else {
this.summary += chatText;
// 更新总结回答
}
break;
}
case 'google_search': {
this.chatList.push({
type: d.tool_name,
data: d.tool_input
})
}
}
this.$nextTick(() => {
this.scrollToBottom()
})
},
scrollToBottom() {
const container = this.$refs.chatList
// 平滑滚动
if (container && container.scrollTo) {
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth'
})
}
}
},
watch: {
generate(v) {
if (v) {
this.getChatInfo()
}
},
taskChatList(v) {
console.log(v);
if (v) {
this.chatList = v
}
},
taskSummary(d) {
if (d) {
this.summary = d
}
}
},
mounted() {
// this.getChatInfo()
},
};
</script>
<style lang="less" scoped>
.detailModel {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
.title {
color: #fff;
font-size: 32px;
line-height: 40px;
font-weight: 700;
text-align: center;
}
.link {
color: #fff;
text-align: center;
}
.content {
color: #fff;
font-size: 14px;
}
}
.huakuai {
width: 8px;
height: 50px;
background-color: #075176;
border: 1px solid #05244f;
border-radius: 3px;
position: absolute;
left: 0;
top: 50%;
z-index: 1001;
cursor: pointer;
}
:deep(.t-collapse-panel__body) {
background: rgba(3, 38, 80, 0.8);
}
:deep(.t-collapse) {
background-color: transparent;
}
.hidden {
height: 0 !important;
padding: 0 !important;
border: 0 !important;
}
.frame {
display: flex;
flex-direction: column;
gap: 10px;
top: 230px;
position: absolute;
align-items: end;
.opentiong {
display: flex;
align-items: center;
gap: 10px;
}
.isShowBtn {
cursor: pointer;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
background: #032650cc;
border: 1px solid;
border-color: #2b8ffe99;
border-radius: 4px 0 0 4px;
}
}
.InfoChatView {
z-index: 1000;
display: flex;
gap: 10px;
flex-direction: column;
// transition: width 0.3s ease;
overflow-y: auto;
width: 319px;
padding: 20px 0;
height: calc(100vh - 360px);
background: rgba(3, 38, 80, 0.8);
border: 1px solid;
border-color: rgba(43, 143, 254, 0.6);
border-radius: 4px;
backdrop-filter: blur(10px);
// 滚动条样式
&::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f8f8f8;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #aaa;
}
}
.chatItem {
.search {
color: #fff;
}
.think {
padding: 10px;
border: 1px solid;
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
}
}
.resultList {
display: flex;
flex-direction: column;
gap: 10px;
.item {
cursor: pointer;
.eventTitle {
color: #fff;
font-size: 20px;
font-weight: 600;
.num {
width: 16px;
height: 16px;
border-radius: 50%;
font-size: 14px;
display: inline-block;
text-align: center;
background-color: var(--bg-color);
}
}
.eventContent {
color: #fff;
}
}
}
.summary {
padding: 10px;
}
</style>
@@ -1,206 +0,0 @@
<template>
<div class="Overview">
<div class="title">
<span>ZZ任务</span>
<img src="@/assets/map-icon/down-logo.svg" alt="" />
</div>
<div class="content">
<div class="taskName">
任务名称任务名称任务名称任务名称任务名称任务名称任务名称任务名称任务名称任务名称任务名称任务名称任务名称任务名称任务名称
</div>
</div>
<div class="task-details">
<div class="task-line-bg task-line-top"></div>
<div class="" style="margin-top: 5px;">
<!-- {{ taskData.overview }} -->
</div>
<div class="task-line-bg task-line-bottom"></div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
};
},
methods: {
},
watch: {
},
};
</script>
<style lang="less">
.Overview {
position: fixed;
top: 60px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
.title {
width: 160px;
height: 30px;
line-height: 33px;
margin: auto;
background: url(/src/assets/map-icon/task-content-logo.svg) no-repeat;
background-size: cover;
color: #ffffff;
text-align: center;
}
.content {
.taskName {
background: linear-gradient(90.02deg,
rgba(3, 38, 80, 0) 0%,
rgba(25, 84, 154, 0.8) 32.88%,
rgba(21, 76, 141, 0.8) 67.83%,
rgba(3, 38, 80, 0) 99.46%);
margin-top: 10px;
font-family: Noto Sans S Chinese;
line-height: 30px;
font-weight: 300;
padding-top: 6px;
color: #ffffff;
font-size: var(--titleSize);
text-align: center;
padding-bottom: 10px;
position: relative;
}
}
// 顶部任务信息样式
.task-content {
width: 701px;
position: absolute;
top: 60px;
left: 50%;
transform: translateX(-50%);
z-index: 550;
.task-title {
width: 160px;
height: 30px;
line-height: 33px;
margin: auto;
background: url(/src/assets/map-icon/task-content-logo.svg) no-repeat;
background-size: cover;
color: #ffffff;
text-align: center;
img {
width: 12px;
margin-left: 10px;
}
}
.task-details {
width: 701px;
// height: 75px;
background: linear-gradient(90.02deg,
rgba(3, 38, 80, 0) 0%,
rgba(25, 84, 154, 0.8) 32.88%,
rgba(21, 76, 141, 0.8) 67.83%,
rgba(3, 38, 80, 0) 99.46%);
margin-top: 10px;
font-family: Noto Sans S Chinese;
line-height: 30px;
font-weight: 300;
padding-top: 6px;
color: #ffffff;
font-size: var(--titleSize);
text-align: center;
padding-bottom: 10px;
position: relative;
.task-line-bg {
width: 100%;
height: 2px;
background: linear-gradient(90deg,
rgba(33, 99, 171, 0) -0.82%,
#0079fe 48.95%,
rgba(33, 99, 171, 0) 100%);
position: absolute;
}
.task-line-top {
top: 0;
}
.task-line-bottom {
bottom: 0;
}
}
}
// 顶部行动信息样式
.action-content {
width: 1000px;
position: absolute;
top: 60px;
left: 50%;
transform: translateX(-50%);
z-index: 550;
.action-title {
width: 160px;
height: 30px;
line-height: 33px;
margin: auto;
background: url(/src/assets/map-icon/task-content-logo.svg) no-repeat;
background-size: cover;
color: #ffffff;
text-align: center;
img {
width: 12px;
margin-left: 10px;
}
}
.action-list {
width: 1000px;
margin-top: 20px;
display: flex;
justify-content: space-between;
.action-item {
width: 255px;
padding: 0 10px;
background: linear-gradient(90.07deg, rgba(3, 38, 80, 0) 0%, rgba(15, 63, 120, 0.34) 11.02%, rgba(25, 84, 154, 0.64) 27.47%, rgba(14, 92, 185, 0.64) 50.34%, rgba(21, 76, 141, 0.64) 75.5%, rgba(13, 58, 113, 0.34) 89.24%, rgba(3, 38, 80, 0) 99.43%);
text-align: center;
.action-line {
width: 100%;
height: 2px;
background: linear-gradient(90deg, rgba(33, 99, 171, 0) -0.82%, rgba(0, 121, 254, 0.8) 48.95%, rgba(33, 99, 171, 0) 100%);
}
.item-title {
font-family: Noto Sans S Chinese;
color: #ffffff;
font-size: 18px;
margin-top: 10px;
.item-label {
font-family: DS-Digital;
font-weight: 700;
color: rgba(255, 255, 255, 0.8);
font-size: 30px;
margin-left: 10px;
margin-right: 5px;
}
.item-value {
font-family: Noto Sans S Chinese;
color: #ffffff;
font-size: 16px;
}
}
}
}
}
}
</style>
@@ -1,132 +0,0 @@
<template>
<div class="setiment-analysis">
<div class="setiment-head">
<!-- 任务筛选 -->
<div class="margin-bottom-10">
<t-row :gutter="16">
<t-col :span="7">
<div>
<t-input v-model="inputValue" placeholder="请输入内容搜索" @enter="onEnter" />
</div>
</t-col>
<t-col :span="5">
<t-select>
<t-option key="待分析" label="待分析" value="待分析" />
<t-option key="分析中" label="分析中" value="分析中" />
<t-option key="已完成" label="已完成" value="已完成" />
</t-select>
</t-col>
</t-row>
</div>
<div>
<t-row :gutter="16">
<t-col :span="8">
<div>
<t-date-range-picker v-model="range" :presets="presets" enable-time-picker />
</div>
</t-col>
<t-col :span="4">
<div class="tabs-box">
<span class="sortBtn" :class="{ 'sort-active': taskRadioValue === 'all' }"
@click="taskChange('all')">全部</span>
<span class="sortBtn" :class="{ 'sort-active': taskRadioValue === 'me' }"
@click="taskChange('me')">我的</span>
</div>
</t-col>
</t-row>
</div>
</div>
<div class="setiment-body">
<TaskList :taskList="taskList" @taskDetailsC="taskDataChange" />
</div>
</div>
</template>
<script setup>
import { ref, defineEmits, onMounted } from 'vue';
import TaskList from './TaskAnalysis/TaskDataList.vue';
import { requestAxiosService } from '../service/request-axios-service'; // 引入发送异步请求方法
import dayjs from 'dayjs';
defineProps({
msg: String,
})
const inputValue = ref(''); // 输入款内容
const taskStatus = ref('a'); // 任务状态
const taskRadioValue = ref('all') // 任务类型选择
const presets = ref({
最近7天: [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()],
最近3天: [dayjs().subtract(2, 'day').toDate(), dayjs().toDate()],
今天: [dayjs().toDate(), dayjs().toDate()],
});
const taskList = ref([
{ title: '任务01', taskStatus: '已完成', taskLogo: 'task-completed' },
{ title: '任务01', taskStatus: '分析中', taskLogo: 'task-uncompleted' },
{ title: '任务01', taskStatus: '待分析', taskLogo: 'task-pendinganalysis' },
{ title: '任务01', taskStatus: '未通过', taskLogo: 'task-failed' },
{ title: '任务01', taskStatus: '已完成', taskLogo: 'task-completed' },
])
const range = ref([]); // 日期时间筛选框
const service = requestAxiosService;
const emit = defineEmits(['taskDetailsC']) // 定义子组件发送给父组件的函数
onMounted(() => {
taskSearch();
})
// 发送请求
const taskSearch = () => {
const parmy = {
orderBy: 'createTime',
orderByType: 'desc',
keyword: '',
pageNo: 1,
pageSize: 10,
taskStatus: '25,30,40,60,70,80,90,100,110'
}
service.get(`${environment['b1ConsumerUrl']}/taskAnalyseSearch/getTaskSearch`, parmy).then((res) => {
console.log('任务检索', res);
})
}
// 回车搜索
const onEnter = () => {
}
// 任务类型选择
const taskChange = (value) => {
taskRadioValue.value = value
}
const taskDataChange = (value) => { // 任务点击
console.log(value)
emit('taskDetailsC', value); // 给父组件传参
}
</script>
<style scoped lang="less">
.setiment-analysis {
width: 100%;
height: 100%;
z-index: 500;
color: #fff;
display: flex;
flex-direction: column;
/* 垂直排列子元素 */
.setiment-head {
padding: 10px;
}
.setiment-body {
padding: 10px;
// flex-grow: 1; /* 让 div 填充剩余空间 */
height: calc(100vh - 225px);
box-sizing: border-box;
overflow-y: auto;
}
}
.sortBtn {
padding: 8px 10px;
}
</style>
@@ -1,359 +0,0 @@
<template>
<div class="assessment-content">
<t-loading :loading="loading" show-overlay>
<div class="item-card margin-bottom-15">
<div class="item-title">
<img src="/src/assets/icon/zf-logo.png" alt="" />
上级意图领会
</div>
<div class="item-content">
{{ situationData['上级意图领会'] || '暂无数据' }}
</div>
</div>
<t-row :gutter="16">
<t-col :span="6">
<div class="item-card" v-if="situationData['口径指导与限制']">
<div class="item-title" style="margin-bottom: 10px;">
<img src="/src/assets/icon/theme-logo.png" alt="" />
口径指导与限制
</div>
<div class="item-border-card tab-red">
<div class="item-blue-card">
{{ situationData['口径指导与限制']['关键口径总结'] || '暂无数据' }}
</div>
<div class="item-title color-blue margin-top-15">
观点分布
</div>
<div class="viewpoint-item" v-for="(item, index) in consistencyList" :key="index">
<span :class="'item-tag ' + item.color">{{ item.type }}</span>
{{ item.title }}
</div>
</div>
</div>
</t-col>
<t-col :span="6">
<div class="item-card" v-if="situationData['可用资源与力量']">
<div class="item-title" style="margin-bottom: 10px;">
<img src="/src/assets/icon/theme-logo.png" alt="" />
可用资源与力量
</div>
<div class="item-border-card tab-orange">
<t-row :gutter="16">
<t-col :span="6">
<div class="assessment-item">
<span class="font-weight-bold">我方账号:</span>{{ situationData['可用资源与力量']['我方账号情况'] || '暂无数据' }}
</div>
</t-col>
<t-col :span="6">
<div ref="userPieRef" class="item-pie"></div>
</t-col>
</t-row>
</div>
<div class="item-border-card tab-orange">
<t-row :gutter="16">
<t-col :span="6">
<div class="assessment-item">
<span class="font-weight-bold">媒资数量不足:</span>{{ situationData['可用资源与力量']['媒资数量情况'] || '暂无数据' }}
</div>
</t-col>
<t-col :span="6">
<div ref="mediaPieRef" class="item-pie"></div>
</t-col>
</t-row>
</div>
<div class="item-border-card tab-orange">
<t-row :gutter="16">
<t-col :span="7">
<div class="assessment-item">
<span class="font-weight-bold">目标触达率较高:</span>{{ situationData['可用资源与力量']['目标触达率情况'] || '暂无数据' }}
</div>
</t-col>
<t-col :span="5">
<div class="assessment-item">
<span class="item-tag tag-lightblue" style="margin-right: 10px;">CBW1触达:</span>XXX、XXX
</div>
<div class="assessment-item">
<span class="item-tag tag-green" style="margin-right: 10px;">CBW2触达:</span>XXX、XXX
</div>
</t-col>
</t-row>
</div>
</div>
</t-col>
</t-row>
</t-loading>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import store from '@/store';
import * as echarts from 'echarts';
import { requestAxiosService } from '@/service/request-axios-service';
defineProps({
msg: String,
})
const consistencyList = ref([
{ title: '通过“坚决反对”和“强烈谴责”的表述,坚决反对美对台军售', type: '外交部', color: 'tag-lightblue' },
{ title: '以“要求停止武装台湾”划清红线,结合争取和平统一前景', type: '国台办', color: 'tag-green' },
{ title: '以“警告台独分裂势力”传递不承诺放弃使用武力的信号', type: '东部战区', color: 'tag-blue' },
]);
const userPieRef = ref(null);
const mediaPieRef = ref(null);
const situationData = ref({})
const loading = ref(false);
const taskData = store.state.taskData;
const service = requestAxiosService;
onMounted(() => {
ownSituationSearch();
})
// 查询我情判定
const ownSituationSearch = async () => {
const parmy = {
query: taskData.overview || ''
}
loading.value = true;
const res = await service.get(`${environment['b1ConsumerUrl']}/cop/copOwnSituation`, parmy);
loading.value = false;
console.log('我情判定', res);
let result = res.data;
situationData.value = result;
consistencyList.value[0].title = result['口径指导与限制']['核心口径']['外交部'] || '暂无数据';
consistencyList.value[1].title = result['口径指导与限制']['核心口径']['国台办'] || '暂无数据';
consistencyList.value[2].title = result['口径指导与限制']['核心口径']['东部战区'] || '暂无数据';
setTimeout(() => {
userPieChart();
mediaPieChart();
}, 100);
}
// 我方账号饼状图
const userPieChart = () => {
let chartPie = echarts.init(userPieRef.value);
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'right',
top: 'center',
data: ['文本', '图片', '音频', '视频'],
textStyle: {
color: '#fff'
}
},
series: [
{
name: '数据类型',
type: 'pie',
radius: '75%',
center: ['40%', '50%'],
label: {
show: false
},
labelLine: {
show: false // 隐藏标签线
},
data: [
{ value: 134, name: '文本' },
{ value: 42, name: '图片' },
{ value: 33, name: '音频' },
{ value: 14, name: '视频' }
],
}
]
};
chartPie.setOption(option)
}
// 媒资饼状图
const mediaPieChart = () => {
let chartPie = echarts.init(mediaPieRef.value);
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'right',
top: 'center',
data: ['政治学家人设', '地缘政治', '政治评论员', '绿营支持者'],
textStyle: {
color: '#fff'
}
},
series: [
{
name: '数据类型',
type: 'pie',
radius: '75%',
center: ['40%', '50%'],
label: {
show: false
},
labelLine: {
show: false // 隐藏标签线
},
data: [
{ value: 134, name: '政治学家人设' },
{ value: 149, name: '地缘政治' },
{ value: 112, name: '政治评论员' },
{ value: 124, name: '绿营支持者' }
],
}
]
};
chartPie.setOption(option)
}
</script>
<style scoped lang="less">
.assessment-content {
z-index: 500;
color: #fff;
padding: 15px 10px;
}
.item-card {
padding: 15px;
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
.item-title {
font-size: var(--fontSize);
font-family: 'Noto Sans S Chinese';
font-weight: 700;
margin-bottom: 5px;
display: flex;
align-items: center;
img {
width: var(--fontSize);
margin-right: 5px;
}
}
.item-content {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
}
.item-border-card {
border-left: 5px solid;
border-radius: 5px;
padding: 15px;
margin-bottom: 15px;
.item-title {
font-size: var(--fontSize);
font-family: 'Noto Sans S Chinese';
font-weight: 700;
margin-bottom: 5px;
display: flex;
align-items: center;
img {
width: var(--fontSize);
margin-right: 5px;
}
}
.item-content {
padding: 10px;
}
.ul li {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
}
.item-blue-card {
border: 1px solid;
border-radius: 5px;
border-color: rgba(85, 166, 254, 0.1);
padding: 15px;
margin-bottom: 15px;
background: rgba(0, 98, 217, 0.3);
}
.viewpoint-item {
padding: 10px;
border-radius: 4px;
background: rgba(0, 140, 119, 0.1);
margin-bottom: 10px;
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
.assessment-item {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
.item-tag {
display: inline-block;
color: #fff;
font-size: var(--fontSizes);
padding: 2px 10px;
border-radius: 4px;
margin: 5px;
}
.item-pie {
width: 230px;
height: 100px;
}
.tag-lightblue {
background: #169bd5;
}
.tag-green {
background: #008080;
}
.tag-blue {
background: #015478;
}
.color-blue {
color: #0062d9;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.color-green {
color: #1eb07f;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.color-orange {
color: #d97b00;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.tab-red {
background: rgba(207, 19, 34, 0.2);
}
.tab-orange {
border-color: #d97b00 !important;
background: #a6895f80;
.item-blue-card {
background: rgb(8 67 137);
}
}
</style>
@@ -1,358 +0,0 @@
<template>
<t-loading :loading="loading" show-overlay>
<div class="battlefield-environment">
<div class="margin-bottom-15">
<t-row :gutter="16">
<t-col :span="6">
<div class="item-card">
<div class="item-title" style="margin-bottom: 10px;">
<img src="/src/assets/icon/theme-logo.png" alt="" />
法理环境分析
</div>
<div class="item-border-card tab-blue" v-if="battlefieldEnvironmentData['法理环境分析']">
<div class="item-blue-card">
{{ battlefieldEnvironmentData['法理环境分析']['总结'] }}
</div>
<div class="analysis-content">
<ul>
<template v-for="(value, key) in battlefieldEnvironmentData['法理环境分析']" :key="key">
<li v-if="key !== '总结'">
{{ key }}: {{ value }}
</li>
</template>
</ul>
</div>
</div>
<t-empty v-if="!battlefieldEnvironmentData['法理环境分析']"/>
</div>
</t-col>
<t-col :span="6">
<div class="item-card" v-if="battlefieldEnvironmentData['人文环境分析']">
<div class="item-title" style="margin-bottom: 10px;">
<img src="/src/assets/icon/theme-logo.png" alt="" />
人文环境分析
</div>
<div class="item-border-card tab-blue">
<div class="item-blue-card">
{{ battlefieldEnvironmentData['人文环境分析']['总结'] }}
</div>
<div class="analysis-content">
<ul>
<template v-for="(value, key) in battlefieldEnvironmentData['人文环境分析']" :key="key">
<li v-if="key !== '总结'">
{{ key }}: {{ value }}
</li>
</template>
</ul>
</div>
</div>
</div>
<t-empty v-if="!battlefieldEnvironmentData['人文环境分析']"/>
</t-col>
</t-row>
</div>
<div class="margin-bottom-15">
<t-row :gutter="16">
<t-col :span="6">
<div class="item-card" v-if="battlefieldEnvironmentData['信息环境分析']">
<div class="item-title" style="margin-bottom: 10px;">
<img src="/src/assets/icon/theme-logo.png" alt="" />
信息环境分析
</div>
<div class="item-border-card tab-blue">
<div class="item-blue-card">
{{ battlefieldEnvironmentData['信息环境分析']['总结'] }}
</div>
<!-- <div class="analysis-content">
<div ref="informationPieRef" class="item-pie"></div>
</div> -->
<div class="analysis-content">
<ul>
<template v-for="(value, key) in battlefieldEnvironmentData['信息环境分析']" :key="key">
<li v-if="key !== '总结'">
{{ key }}: {{ value }}
</li>
</template>
</ul>
</div>
</div>
</div>
<t-empty v-if="!battlefieldEnvironmentData['信息环境分析']"/>
</t-col>
<t-col :span="6">
<div class="item-card" v-if="battlefieldEnvironmentData['国际舆论分析']">
<div class="item-title" style="margin-bottom: 10px;">
<img src="/src/assets/icon/theme-logo.png" alt="" />
国际舆论分析
</div>
<div class="item-border-card tab-blue">
<div class="item-blue-card">
{{ battlefieldEnvironmentData['国际舆论分析']['总结'] }}
</div>
<div class="analysis-content">
<ul>
<template v-for="(value, key) in battlefieldEnvironmentData['国际舆论分析']" :key="key">
<li v-if="key !== '总结'">
{{ key }}: {{ value }}
</li>
</template>
</ul>
</div>
</div>
</div>
<t-empty v-if="!battlefieldEnvironmentData['国际舆论分析']"/>
</t-col>
</t-row>
</div>
</div>
</t-loading>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import * as echarts from 'echarts';
import { requestAxiosService } from '@/service/request-axios-service';
import store from '@/store';
defineProps({
msg: String,
})
const legalList = ref([
{ title: '美国军售违背《联合国宪章》尊重主权原则,以及中美三个联合公报(特别是“八·一七”公报),该公报明确要求美国逐步减少对台军售。' },
{ title: '美国试图以《台湾关系法》和“六项保证”为军售辩护[[19][27]],但国际社会多数国家承认一个中国原则' },
{ title: '我国通过《反分裂国家法》等国内法,将台湾问题定位为内政,任何外部军售均属违法' },
])
const humList = ref([
{ title: '赖当局将海马斯军售包装为“守护民主自由”,鼓吹“同岛一命”的分裂文化。' },
{ title: '台军演习(如“云台操演”)融入海马斯元素,试图塑造“台湾主体意识”。' },
{ title: '我国通过《反分裂国家法》等国内法,将台湾问题定位为内政,任何外部军售均属违法' },
])
const opinionList = ref([
{ title: '赖当局将海马斯军售包装为“守护民主自由”,鼓吹“同岛一命”的分裂文化。' },
{ title: '台军演习(如“云台操演”)融入海马斯元素,试图塑造“台湾主体意识”。' },
{ title: '我国通过《反分裂国家法》等国内法,将台湾问题定位为内政,任何外部军售均属违法' },
]);
const loading = ref(false);
const taskData = store.state.taskData;
const service = requestAxiosService;
const battlefieldEnvironmentData = ref({});
onMounted(() => {
copBattlefieldEnvironmentSearch();
// informationPieChart();
})
// 战场环境
const copBattlefieldEnvironmentSearch = async () => {
const parmy = {
query: taskData.overview || ''
}
loading.value = true;
const res = await service.get(`${environment['b1ConsumerUrl']}/cop/copBattlefieldEnvironment`, parmy);
loading.value = false;
console.log('战场环境', res);
let result = res.data;
battlefieldEnvironmentData.value = result;
}
const informationPieRef = ref(null);
// 信息分析饼状图
const informationPieChart = () => {
let chartPie = echarts.init(informationPieRef.value);
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'right',
top: 'center',
data: ['facebook', 'ppt', 'youtube', 'threads', '伊莉论坛'],
textStyle: {
color: '#fff'
}
},
series: [
{
name: '数据类型',
type: 'pie',
radius: '75%',
center: ['40%', '50%'],
label: {
show: false
},
labelLine: {
show: false // 隐藏标签线
},
data: [
{ value: 65, name: 'facebook' },
{ value: 14, name: 'ppt' },
{ value: 12, name: 'youtube' },
{ value: 10, name: 'threads' },
{ value: 9, name: '伊莉论坛' },
],
}
]
};
chartPie.setOption(option)
}
</script>
<style scoped lang="less">
.battlefield-environment {
z-index: 500;
color: #fff;
padding: 15px 10px;
}
.item-card {
padding: 15px;
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
.item-title {
font-size: var(--fontSize);
font-family: 'Noto Sans S Chinese';
font-weight: 700;
margin-bottom: 5px;
display: flex;
align-items: center;
img {
width: var(--fontSize);
margin-right: 5px;
}
}
.item-content {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
}
.item-border-card {
border-left: 5px solid;
border-radius: 5px;
padding: 15px;
margin-bottom: 15px;
.item-title {
font-size: var(--fontSize);
font-family: 'Noto Sans S Chinese';
font-weight: 700;
margin-bottom: 5px;
display: flex;
align-items: center;
img {
width: var(--fontSize);
margin-right: 5px;
}
}
.item-content {
padding: 10px;
}
.ul li {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
}
.analysis-content {
padding: 10px 25px;
.ul li {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
margin-bottom: 5px;
}
}
.item-blue-card {
border: 1px solid;
border-radius: 5px;
border-color: rgba(85, 166, 254, 0.1);
padding: 15px;
margin-bottom: 15px;
background: rgba(0, 98, 217, 0.3);
}
.viewpoint-item {
padding: 10px;
border-radius: 4px;
background: rgba(0, 140, 119, 0.1);
margin-bottom: 10px;
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
.assessment-item {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
.item-tag {
display: inline-block;
color: #fff;
font-size: var(--fontSizes);
padding: 2px 10px;
border-radius: 4px;
margin: 5px;
}
.item-pie {
width: 435px;
height: 200px;
}
.tag-lightblue {
background: #169bd5;
}
.tag-green {
background: #008080;
}
.tag-blue {
background: #015478;
}
.color-blue {
color: #0062d9;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.color-green {
color: #1eb07f;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.color-orange {
color: #d97b00;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.tab-red {
background: rgba(207, 19, 34, 0.2);
}
.tab-orange {
border-color: #d97b00 !important;
background: #a6895f80;
.item-blue-card {
background: rgb(8 67 137);
}
}
</style>
@@ -1,544 +0,0 @@
<template>
<div class="determination-content">
<t-loading :loading="loading" show-overlay>
<t-row :gutter="16">
<t-col :span="7">
<t-dialog
v-model:visible="GJZQModel"
attach="body"
header="关键战情详情"
width="1000"
:confirm-on-enter="true"
:on-confirm="addModelConfig"
>
<div>
<div
style="
font-size: 26px;
font-weight: 800px;
color: #fff;
margin-bottom: 20px;
text-align: center;
"
>
{{
GJZQData["矛盾名称"] ||
GJZQData["优势名称"] ||
GJZQData["战机名称"]
}}
</div>
<div
style="
font-size: 18px;
font-weight: 600px;
color: #fff;
margin-bottom: 10px;
"
>
简介描述:
{{
GJZQData["矛盾简介"] ||
GJZQData["优势简介"] ||
GJZQData["战机简介"]
}}
</div>
<div class="item-content">
<div
class="item-label"
style="
font-size: 18px;
font-weight: 600px;
color: #fff;
margin-bottom: 10px;
"
>
关联方:<span
class="item-tag tag-lightblue"
v-for="(values, keys) in GJZQData['关联方']"
:key="keys"
>{{ keys }}:{{ values }}</span
>
</div>
</div>
<div class="item-content flex-between-center">
<span
class="item-value"
style="
font-size: 18px;
font-weight: 600px;
color: #fff;
margin-bottom: 10px;
"
>战场:{{ GJZQData["战场"] }}</span
>
</div>
<div
v-if="GJZQData['总结']"
style="
font-size: 18px;
font-weight: 600px;
color: #fff;
margin-bottom: 10px;
"
>
总结: {{ GJZQData["总结"] }}
</div>
<div
v-if="GJZQData['分析依据']"
style="
font-size: 18px;
font-weight: 600px;
color: #fff;
margin-bottom: 10px;
"
>
分析依据: {{ GJZQData["分析依据"] }}
</div>
</div>
</t-dialog>
<t-dialog
v-model:visible="modelStatus"
attach="body"
header="决心建议"
width="1000"
:confirm-on-enter="true"
:on-confirm="addModelConfig"
>
<CodeData :markdown="handleData['md']" />
</t-dialog>
<div v-if="determinationData['关键战情']">
<div class="title-content-item margin-bottom-10">关键战情</div>
<template v-for="(value, key) in determinationData['关键战情']">
<div
class="item-card"
v-for="(v, i) in value"
@click="
GJZQData = v;
GJZQModel = true;
"
>
<div class="item-title">
<span
class="item-tag tab-green"
:class="{
'tab-green': key === '核心矛盾',
'tab-yellow': key === '战机',
'tab-yellow': key === '优势',
}"
>{{ key }}{{ i + 1 }}</span
>
{{ v["优势名称"] || v["战机名称"] || v["矛盾名称"] }}
</div>
<!-- <div class="item-content">
<span class="item-label">
{{ key }}名称:
{{ v['矛盾名称'] || v['优势名称'] || v['战机名称'] }}
</span>
</div>
<div class="item-content">
<span class="item-label">
{{ key }}简介:
{{ v['矛盾简介'] || v['优势简介'] || v['战机简介'] }}
</span>
</div> -->
<!-- <div class="item-content">
<div class="item-label">
关联方:<span class="item-tag tag-lightblue" v-for="(values, keys) in v['关联方']" :key="keys">{{ keys
}}:{{
values
}}</span>
</div>
</div>
<div class="item-content flex-between-center">
<span class="item-value">{{ v['战场'] }}</span>
</div> -->
</div>
</template>
</div>
<div v-else>
<t-empty />
</div>
</t-col>
<t-col :span="5">
<div v-if="determinationData['决心建议']">
<div
class="title-content-item margin-bottom-10 cursor-pointer"
@click="
modelStatus = true;
handleData = determinationData;
"
>
决心建议
</div>
<div class="item-card">
<div class="item-title">本级任务</div>
<div class="item-content">
<div class="title">
{{ determinationData["决心建议"]["任务标题"] }}
</div>
{{ determinationData["决心建议"]["任务简介"] }}
<AddTaskDialog
:taskSuperiorFlag="1"
:taskId="taskData.id"
:overview="determinationData['决心建议']['任务简介']"
:taskName="determinationData['决心建议']['任务标题']"
>
<div class="btn" v-if="taskData.ifCreateTask !== 0">
下发任务
</div>
</AddTaskDialog>
</div>
</div>
<div class="item-card">
<div class="item-title">作战目的</div>
<div class="item-content">
<ul>
<li
v-for="(item, index) in determinationData['决心建议'][
'作战目的'
]"
:key="index"
>
{{ index + 1 }}.<span class="font-weight-bold">{{
item
}}</span>
</li>
</ul>
</div>
</div>
<!-- <div class="item-card">
<div class="item-title">
实施要点
</div>
<div class="item-content">
<ul>
<li v-for="(item, index) in determinationData['实施要点']" :key="index">{{ index + 1 }}.<span
class="font-weight-bold">{{ item }}</span></li>
</ul>
</div>
</div> -->
<div class="title-content-item margin-bottom-10">打击目标</div>
<div class="item-card">
<t-dialog
v-model:visible="DJTergetModel"
attach="body"
header="打击目标详情"
width="600"
:confirm-on-enter="true"
:on-confirm="addModelConfig"
>
<div>
<div
style="
font-size: 18px;
font-weight: 600px;
color: #fff;
margin-bottom: 10px;
"
>
目标名称: {{ DJTergetData["目标名称"] }}
</div>
<div
style="
font-size: 18px;
font-weight: 600px;
color: #fff;
margin-bottom: 10px;
"
>
目标描述: {{ DJTergetData["目标描述"] }}
</div>
<div
style="
font-size: 18px;
font-weight: 600px;
color: #fff;
margin-bottom: 10px;
"
>
选择理由: {{ DJTergetData["选择理由"] }}
</div>
<div
style="
font-size: 18px;
font-weight: 600px;
color: #fff;
margin-bottom: 10px;
"
>
推荐依据: {{ DJTergetData["推荐依据"] }}
</div>
</div>
</t-dialog>
<div class="item-content">
<div class="title">
<!-- <span class="item-tag tag-yellow" :key="index">{{ item['优先级'] }}</span> {{ item['目标名称'] }} -->
<span
class="item-tag tag-yellow"
@click="
DJTergetData = item;
DJTergetModel = true;
"
v-for="(item, index) in determinationData['决心建议'][
'打击目标'
]"
:key="index"
>
{{ item["目标名称"] }}</span
>
</div>
<!-- <div>
目标描述: {{ item['目标描述'] }}
</div>
<div>
选择理由: {{ item['选择理由'] }}
</div> -->
<!-- <div>
目标类型: {{ item['目标类型'] }}
</div>
<div>
选择理由: {{ item['选择理由'] }}
</div> -->
</div>
</div>
<div class="title-content-item margin-bottom-10">决心建议总结</div>
<div class="item-card">
<div class="item-content">
{{ determinationData["决心建议总结"] || "暂无数据" }}
</div>
</div>
<!-- <div class="title-content-item margin-bottom-10">叙事建议</div>
<div class="item-card">
<div class="item-title">
叙事反转
</div>
<div class="item-content">
{{ determinationData['叙事反转'] }}
</div>
</div>
<div class="item-card">
<div class="item-title">
数据反制
</div>
<div class="item-content">
{{ determinationData['数据反制'] }}
</div>
</div>
<div class="item-card">
<div class="item-title">
群体分化
</div>
<div class="item-content">
{{ determinationData['群体分化'] }}
</div>
</div> -->
<!-- <div class="item-card">
<div class="item-title">
总结
</div>
<div class="item-content">
{{ determinationData['决心建议总结'] }}
</div>
</div> -->
<!-- <t-radio-group variant="primary-filled" :value="taskRadioValue" @change="onChange">
<t-radio-button value="a">任务</t-radio-button>
<t-radio-button value="b">原则与限制</t-radio-button>
<t-radio-button value="c">兵力使用</t-radio-button>
<t-radio-button value="d">终止条件</t-radio-button>
</t-radio-group> -->
</div>
</t-col>
</t-row>
</t-loading>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from "vue";
import { requestAxiosService } from "@/service/request-axios-service";
import AddTaskDialog from "@/components/dialog/addTaskDialog.vue";
import store from "@/store";
import CodeData from "../markdown/CodeData.vue";
import { MessagePlugin } from "tdesign-vue-next";
const props = defineProps({
msg: String,
updateKey: Number,
});
watch(
() => props.updateKey,
(v) => {
copDecisionRecommendationSearch(true);
}
);
const addModelConfig = () => {};
const taskRadioValue = ref("a");
const loading = ref(false);
const taskData = store.state.taskData;
const service = requestAxiosService;
const determinationData = ref({});
const DJTergetModel = ref(false);
const DJTergetData = ref({});
const GJZQModel = ref(false);
const GJZQData = ref({});
const handleData = ref({});
const modelStatus = ref(false);
// 决心建议
const copDecisionRecommendationSearch = async (status = false) => {
// wxq让去掉query: taskData.overview || "",
const parmy = {
taskId: taskData.id,
refresh: status,
};
loading.value = true;
try {
const res = await service.get(
`${environment["b1ConsumerUrl"]}/cop/copDecisionRecommendation`,
parmy
);
console.log("决心建议", res);
let result = res.data;
determinationData.value = result;
loading.value = false;
} catch (error) {
MessagePlugin.error("决心建议数据获取失败!");
loading.value = false;
} finally {
loading.value = false;
}
};
onMounted(() => {
copDecisionRecommendationSearch();
});
const onChange = (value) => {
taskRadioValue.value = value;
};
</script>
<style scoped lang="less">
.determination-content {
z-index: 500;
color: #fff;
padding: 10px;
position: relative;
.item-card {
width: 100%;
background: rgba(0, 238, 255, 0.2);
border-radius: 4px;
padding: 10px 15px;
margin-bottom: 15px;
box-sizing: border-box;
.item-title {
font-size: var(--fontSize);
font-family: "Noto Sans S Chinese";
font-weight: 500;
margin-bottom: 10px;
}
.item-content {
font-size: var(--fontSizes);
font-family: "Noto Sans S Chinese";
overflow: hidden;
.title {
font-size: 18px;
font-weight: 700;
}
.item-value {
text-decoration: underline;
}
ul {
margin: 0 25px;
li {
margin-bottom: 10px;
}
}
}
}
}
.item-tag {
font-size: var(--fontSizes);
cursor: pointer;
display: inline-block;
white-space: nowrap;
padding: 2px 10px;
border-radius: 4px;
margin: 5px;
}
// 标签样式
// 蓝色标签
.tag-blue {
border-color: #aad2ff !important;
color: #0062d9 !important;
background-color: #eaf4ff !important;
}
// 黄色标签
.tag-yellow {
border-color: #d95e00 !important;
color: #fff !important;
background-color: #dd8717;
}
// 红色标签
.tag-red {
border-color: #fcb5b9 !important;
color: #fff !important;
background-color: #cf1322;
}
// 绿色标签
.tag-green {
border-color: #baf1dd !important;
color: #ffffff !important;
background-color: #1eb07f;
}
// 紫色标签
.tag-purple {
border-color: #cec0fc !important;
color: #fff !important;
background-color: #6353cc;
}
// 浅蓝色标签
.tag-lightblue {
border-color: #c1ebfb !important;
color: #ffffff !important;
background-color: #2f9acc;
}
// 灰色标签
.tag-gray {
border-color: #d6d7db !important;
background-color: #ffffff;
color: #575a61;
}
.btn {
padding: 0 5px;
background-color: #0e597f;
width: 100px;
text-align: center;
cursor: pointer;
line-height: 26px;
border-radius: 5px;
float: right;
}
</style>
@@ -1,424 +0,0 @@
<template>
<div class="enemy-assessmen">
<t-loading :loading="loading" show-overlay>
<div class="item-card margin-bottom-15">
<div class="item-title">
<img src="/src/assets/icon/zf-logo.png" alt="" />
对手核心企图
</div>
<div class="item-cotnent">
{{ enemyData['对手核心企图'] || '暂无数据'}}
</div>
</div>
<t-row :gutter="16">
<t-col :span="6">
<div class="item-card">
<div class="item-title" style="margin-bottom: 15px;">
<img src="/src/assets/icon/theme-logo.png" alt="" />
关键叙事分析
</div>
<div v-if="enemyData['关键叙事分析']" class="item-border-card tab-blue">
<div class="item-blue-card">
{{ enemyData['关键叙事分析']['叙事总结'] }}
</div>
<div class="item-title color-blue">
叙事内容
</div>
<div class="narrate-item">
<CodeData :markdown="enemyData['关键叙事分析']['叙事内容']"/>
</div>
<!-- <t-row :gutter="16">
<t-col :span="6">
<div class="narrate-item">
<ul>
<li>海马斯可覆盖大陆沿海</li>
<li>打了就跑的移动迷宫</li>
</ul>
</div>
</t-col>
<t-col :span="6">
<div class="narrate-item">
<ul>
<li>打了就跑的移动迷宫</li>
<li>让解放军吃火力墙</li>
</ul>
</div>
</t-col>
</t-row> -->
<div class="item-title color-blue margin-top-15">
观点分布
</div>
<t-row :gutter="16">
<t-col :span="6">
<div id="chart-pie" ref="chartRef">
</div>
</t-col>
<t-col :span="6">
<div class="viewpoint-item" v-for="(item, index) in opinionPieList" :key="index">
<div>{{ index + 1 }}.{{ item.name }}</div>
<div class="item-footer">观点占比:{{ item['data']['占比'] }}</div>
</div>
</t-col>
</t-row>
</div>
<t-empty v-if="!enemyData['关键叙事分析']"/>
</div>
</t-col>
<t-col :span="6">
<div v-if="enemyData['对手强弱点分析']" class="item-card">
<div class="item-title" style="margin-bottom: 15px;">
<img src="/src/assets/icon/theme-logo.png" alt="" />
对手强弱点分析
</div>
<div class="item-border-card tab-orange">
<div class="item-blue-card">
{{ enemyData['对手强弱点分析']['强弱点总结'] }}
</div>
<div class="item-title color-blue margin-top-15">
对手弱点
</div>
<div class="task-list-content">
<!-- <ul>
<li class="" v-for="(item, index) in weakPoints" :key="index">{{ item.title }}</li>
</ul> -->
<CodeData :markdown="enemyData['对手强弱点分析']['对手强点']"/>
</div>
<div class="item-title color-blue margin-top-15">
对手强点
</div>
<div class="task-list-content">
<!-- <ul>
<li class="" v-for="(item, index) in strongPoints" :key="index">{{ item.title }}</li>
</ul> -->
<CodeData :markdown="enemyData['对手强弱点分析']['对手弱点']"/>
</div>
</div>
</div>
</t-col>
</t-row>
</t-loading>
</div>
</template>
<script setup>
import { ref,reactive, onMounted, computed } from 'vue';
import { useStore } from 'vuex';
import * as echarts from 'echarts';
import { requestAxiosService } from '@/service/request-axios-service';
import CodeData from '../markdown/CodeData.vue';
const props = defineProps({
msg: String,
taskData: Object
})
const opinionPieList = ref([
{
value: 1500,
name: '支持我方观点',
itemStyle: {
color: '#7acaa4'
}
},
{
value: 735,
name: '中立观点',
itemStyle: {
color: '#c8cdd5'
}
},
{
value: 1500,
name: '反对我方观点',
itemStyle: {
color: '#f5a444'
}
}
]);
let colors = ['#7acaa4','#c8cdd5','#f5a444'];
const chartRef = ref(null);
const weakPoints = ref([
{ title: '俄乌战场经验可鉴,海马斯弱点已验证' },
{ title: '岛内对美售武器疑虑重重,存在认知裂痕' },
{ title: '台军虽可获得111套“海马斯”,但缺乏独立的侦察、指挥、数据链和电子战支撑体系,战时效能将大打折扣。' },
])
const strongPoints = ref([
{ title: '美方持续炒作“海马斯”威胁论' },
{ title: '“台独”势力将其视为“护身符”,加剧冒险' },
])
const enemyData = ref({});
// 获取 Vuex store
const store = useStore();
const taskData = store.state.taskData;
const service = requestAxiosService;
const loading = ref(false);
onMounted(() => {
enemySituationSearch();
})
// 查询敌情判定
const enemySituationSearch = async () => {
const parmy = {
query: taskData.overview || ''
}
loading.value = true;
const res = await service.get(`${environment['b1ConsumerUrl']}/cop/copEnemySituation`, parmy);
loading.value = false;
console.log('查询敌情判定', res);
let result = res.data;
enemyData.value = result;
opinionPieList.value = [];
if (result['关键叙事分析']) {
let pieList = result['关键叙事分析']['观点分布'] || [];
pieList.forEach((item, index) => {
opinionPieList.value.push({
value: Number(item['占比'].replace(/%/g, '')),
name: item['观点倾向'],
data: item,
itemStyle: {
color: colors[index]
}
})
})
}
setTimeout(() => {
initPieChart();
}, 100)
}
const initPieChart = () => {
let chartPie = echarts.init(chartRef.value);
const option = {
title: {
text: '',
left: 'center',
top: 'center'
},
legend: {
show: false // 隐藏图例
},
tooltip: {
show: true,
appendTo: 'body',
formatter: function (params) {
// console.log('params数据',params);
let text = ``;
text = `
<div class="pint-pie__tooltip">
<div>${params.data.name}</div>
<div>
<span class="">${params.data.value}</span>
</div>
</div>
`;
return text;
}
},
series: [
{
name: '访问来源',
type: 'pie',
radius: ['50%', '85%'], // 设置圆环的内径和外径
padAngle: 2,
// avoidLabelOverlap: false,
itemStyle: {
borderColor: '#fff',
// borderRadius: 4,
borderWidth: 0
},
label: {
show: false
},
labelLine: {
show: false // 隐藏标签线
},
emphasis: {
disabled: true,
label: {
show: false,
fontSize: '20',
fontWeight: 'bold'
}
},
data: opinionPieList.value
},
{
name: '外边框',
type: 'pie',
radius: ['90%', '90%'],
center: ['50%', '50%'],
emphasis: {
disabled: true
},
label: {
show: false
},
labelLine: {
show: false // 隐藏标签线
},
tooltip: {
show: false
},
data: [
{
value: 0,
name: '',
itemStyle: {
borderWidth: 1,
borderColor: '#e4e9f4'
}
}
]
}
]
}
chartPie.setOption(option)
}
</script>
<style scoped lang="less">
.judgment-conclusion {
z-index: 500;
color: #fff;
// 态势概览样式
.overview-content {
padding: 15px 0;
}
// 敌情判定样式
.enemy-assessmen {
padding: 15px 0;
}
}
.item-card {
padding: 15px;
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
.item-title {
font-size: var(--fontSize);
font-family: 'Noto Sans S Chinese';
font-weight: 700;
margin-bottom: 5px;
display: flex;
align-items: center;
img {
width: var(--fontSize);
margin-right: 5px;
}
}
.item-content {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
}
.item-border-card {
min-height: 145px;
border-left: 5px solid;
border-radius: 5px;
padding: 15px;
margin-bottom: 15px;
.item-title {
font-size: var(--fontSize);
font-family: 'Noto Sans S Chinese';
font-weight: 700;
margin-bottom: 5px;
display: flex;
align-items: center;
img {
width: var(--fontSize);
margin-right: 5px;
}
}
.item-content {
padding: 10px;
}
.ul li {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
}
.item-blue-card {
border: 1px solid;
border-radius: 5px;
border-color: rgba(85, 166, 254, 0.1);
padding: 15px;
margin-bottom: 15px;
background: rgba(0, 98, 217, 0.3);
}
.narrate-item {
padding: 10px 25px;
background: rgba(0, 238, 255, 0.2);
border-radius: 4px;
.ul {
li {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
}
}
.task-list-content {
padding: 10px 25px;
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
#chart-pie {
width: 150px;
height: 150px;
margin: 0 auto;
}
.viewpoint-item {
padding: 10px;
border-radius: 4px;
background: rgba(0, 140, 119, 0.1);
margin-bottom: 10px;
.item-footer {
color: #7acaa4;
}
}
.color-blue {
color: #0062d9;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.color-green {
color: #1eb07f;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.color-orange {
color: #d97b00;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.tab-orange {
border-color: #d97b00 !important;
background: #a6895f80;
.item-blue-card {
background: rgb(8 67 137);
}
}
</style>
@@ -1,488 +0,0 @@
<template>
<div class="handle-content">
<t-loading :loading="loading" show-overlay>
<t-row :gutter="16">
<t-col :span="7">
<div class="title-content-item margin-bottom-10">关键事态</div>
<CommonInfoDialog
:modelData="GJSTModelData"
:modelStatus="GJSTModelStatus"
@updateStatus="(v) => (GJSTModelStatus = v)"
@config="() => (GJSTModelStatus = false)"
/>
<!-- @click="openGJSTModel(value)" -->
<div class="item-card" v-for="value in handleData['关键事态']">
<div class="item-title">
<!-- {{ value }} -->
<span class="item-tag tab-green">{{ value["类型"] }}</span>
{{ value["标题"] }}
</div>
<div class="item-content flex-between-center">
{{ value["内容"] }}
<!-- <span class="item-value">海马斯失灵 俄乌战场</span>
<span class="item-label">
关联方:<span class="item-tag tag-lightblue">俄军</span><span class="item-tag tag-purple">美国政府</span><span
class="item-tag tag-green">美军</span>
</span> -->
<!-- <span class="item-label">
来源:<span class="item-value">{{ value.siteName }}</span>
</span> -->
</div>
<span class="item-label">
依据:<span class="item-value">{{ value["依据"] }}</span>
</span>
</div>
<!-- <div class="addEvent" @click="openAddModel">
+
</div> -->
<t-dialog
v-model:visible="addModelStatus"
attach="body"
header="添加关键事态"
width="600"
:confirm-on-enter="true"
:on-confirm="addModelConfig"
>
<div class="row" v-for="row in addRule">
<div class="label">{{ row.label }}:</div>
<div class="content">
<t-input v-model="row.value" v-if="row.key !== 'direction'" />
<t-select v-else>
<t-option key="U方向" label="U方向" value="U方向" />
<t-option key="W方向" label="W方向" value="W方向" />
</t-select>
</div>
</div>
</t-dialog>
</t-col>
<t-col :span="5">
<t-dialog
v-model:visible="modelStatus"
attach="body"
header="过程"
width="1000"
:confirm-on-enter="true"
:on-confirm="addModelConfig"
>
<CodeData :markdown="handleData['md']" />
</t-dialog>
<div v-if="handleData['处置建议']" class="suggestion-content">
<div
class="title-content-item margin-bottom-10"
@click="modelStatus = true"
>
处置建议总结
</div>
<div class="item-card">
<!-- <div class="item-title">
处置建议总结
</div> -->
<div class="item-content">
{{ handleData["处置建议总结"] }}
</div>
</div>
<div class="title-content-item margin-bottom-10">目标</div>
<div class="item-card">
<div class="item-content">
{{ handleData["目标"] }}
</div>
</div>
<!-- <div class="item-card">
<div class="item-title">
建议执行要点
</div>
<div class="item-content">
<ul>
<li v-for="(item, key, index) in handleData['建议执行要点']" :key="index">
{{ key }}:
<span class="font-weight-bold" v-if="!Array.isArray(item)">{{ item }}</span>
<ul v-else>
<li v-for="(v, i) in item">{{ i + 1 }}.
<span class="font-weight-bold">{{ v }}</span>
</li>
</ul>
</li>
</ul>
</div>
</div> -->
<div>
<div class="title-content-item margin-bottom-10">建议</div>
<t-swiper
class="tdesign-demo-block--swiper"
direction="vertical"
style="width: 480px"
:navigation="{ showSlideBtn: 'never', placement: 'outside' }"
:height="500"
:autoplay="false"
trigger="click"
>
<t-swiper-item
style="height: 500px; overflow-y: auto"
v-for="(item, index) in handleData['处置建议']"
:key="index"
>
<div class="item-card">
<div class="item-title">口径基调</div>
<div class="item-content">
{{ item["口径基调"].toString() }}
</div>
</div>
<t-row :gutter="16">
<t-col :span="6">
<div class="item-card">
<div class="item-title">处置主体</div>
<div class="item-content">
<div class="margin-bottom-10">
主责单位:
<span class="item-tag tag-lightblue">{{
item["处置主体"]["主责单位"]
}}</span>
</div>
<div class="margin-bottom-10">
配合单位:
<span
class="item-tag tag-lightblue"
v-for="(aItem, aIndex) in item['处置主体'][
'配合单位'
]"
:key="aIndex"
>{{ aItem }}</span
>
</div>
</div>
</div>
</t-col>
<t-col :span="6">
<div class="item-card">
<div class="item-title">处置对象</div>
<div class="item-content">
<div>
<div>
关联对象:
<span class="item-tag tag-yellow">{{
item["处置对象"]["关联对象"]
}}</span>
</div>
<div>
核心对象:
<span class="item-tag tag-yellow">{{
item["处置对象"]["核心对象"]
}}</span>
</div>
</div>
</div>
</div>
</t-col>
</t-row>
<div class="item-card" v-if="item['处置方式与步骤']">
<div class="item-title">处置方式与步骤</div>
<div class="item-content">
<CodeData :markdown="item['处置方式与步骤']" />
<!-- <div>{{ item['处置方式与步骤'] }}</div> -->
</div>
</div>
<div class="item-card">
<div class="item-title">建议内容</div>
<div class="item-content">
{{ item["建议内容"] }}
</div>
</div>
<div class="item-card">
<div class="item-title">立场与原则</div>
<div class="item-content">
{{ item["立场与原则"] }}
</div>
</div>
<!-- <div class="item-card">
<div class="item-title">
预期效果
</div>
<div class="item-content">
{{ item['预期效果'] }}
</div>
</div> -->
</t-swiper-item>
</t-swiper>
<!-- <div class="item-card">
<div class="item-title">
风险提示
</div>
<div class="item-content">
{{ item['风险提示'] }}
</div>
</div> -->
</div>
</div>
<div v-else class="padding-10">
<t-empty />
</div>
</t-col>
</t-row>
</t-loading>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from "vue";
import CodeData from "../markdown/CodeData.vue";
import { requestAxiosService } from "@/service/request-axios-service";
import CommonInfoDialog from "@/components/dialog/commonInfoDialog.vue";
import store from "@/store";
import dayjs from "dayjs";
import { MessagePlugin } from "tdesign-vue-next";
const props = defineProps({
msg: String,
taskId: Number,
updateKey: Number,
});
watch(
() => props.updateKey,
(v) => {
console.log(v);
getCZJY(true);
getGJSTData(true);
}
);
const modelStatus = ref(false);
const GJSTModelData = ref({});
const GJSTModelStatus = ref(false);
const openGJSTModel = (d) => {
console.log(d);
GJSTModelStatus.value = true;
GJSTModelData.value = d;
console.log(GJSTModelStatus.value);
};
const addModelStatus = ref(false);
const openAddModel = () => {
addModelStatus.value = true;
};
const addModelConfig = async () => {
console.log("搜索");
let params = {
taskId: props.taskId,
eventId: "",
};
addRule.value.forEach((i) => {
params[i.key] = i.value;
});
console.log(params);
const res = await service.post(
`${environment["b1ConsumerUrl"]}/cop/saveCopKeyEvents`,
params
);
console.log(res);
MessagePlugin.info(res.msg);
if (res.state === "201") {
addModelStatus.value = false;
getGJSTData();
}
};
const addRule = ref([
{ label: "标题", key: "title", value: "" },
{ label: "内容", key: "content", value: "" },
{ label: "方向", key: "direction", value: "" },
// { label: '表菩提', key: "eventId", value: '' },
{ label: "热度", key: "importNum", value: "" },
{ label: "纬度", key: "latitude", value: "" },
{ label: "经度", key: "longitude", value: "" },
{ label: "网站", key: "siteName", value: "" },
{ label: "来源", key: "sourceType", value: "" },
// { label: '', key: "taskId", value: 3059 },
]);
const loading = ref(false);
const taskData = store.state.taskData;
const service = requestAxiosService;
const handleData = ref({});
const GJSTData = ref([]);
onMounted(() => {
getGJSTData(); //获取关键事态
getCZJY(); //获取处置建议
});
//处置建议
const getCZJY = async (status = false) => {
const parmy = {
taskId: props.taskId,
refresh: status,
};
loading.value = true;
const res = await service.get(
`${environment["b1ConsumerUrl"]}/cop/queryHandlingSuggestions`,
parmy
);
loading.value = false;
console.log(res);
handleData.value = res.data.data || res.data;
console.log(handleData.value);
};
// 关键事态
const getGJSTData = async (status = false) => {
const parmy = {
taskId: props.taskId,
refresh: status,
};
loading.value = true;
const res = await service.get(
`${environment["b1ConsumerUrl"]}/cop/copSuggestionKeyEvents`,
parmy
);
loading.value = false;
console.log("关键事态", res);
let result = res.data;
GJSTData.value = result;
getCZJY();
};
</script>
<style scoped lang="less">
.handle-content {
z-index: 500;
color: #fff;
padding: 10px;
.item-card {
width: 100%;
background: rgba(0, 238, 255, 0.2);
border-radius: 4px;
padding: 10px 15px;
margin-bottom: 15px;
box-sizing: border-box;
.item-title {
font-size: var(--fontSize);
font-family: "Noto Sans S Chinese";
font-weight: 500;
margin-bottom: 10px;
}
.item-content {
font-size: var(--fontSizes);
font-family: "Noto Sans S Chinese";
.item-value {
text-decoration: underline;
}
ul {
margin: 0 25px;
li {
margin-bottom: 10px;
}
}
}
}
.addEvent {
font-size: 30px;
font-weight: 700;
text-align: center;
line-height: 80px;
border: 2px solid #575a61;
border-radius: 5px;
cursor: pointer;
}
.suggestion-content {
width: 100%;
height: 710px;
overflow-y: auto;
}
}
.item-tag {
font-size: var(--fontSizes);
display: inline-block;
// white-space: nowrap;
padding: 2px 10px;
border-radius: 4px;
margin: 5px;
}
// 标签样式
// 蓝色标签
.tag-blue {
border-color: #aad2ff !important;
color: #0062d9 !important;
background-color: #eaf4ff !important;
}
// 黄色标签
.tag-yellow {
border-color: #d95e00 !important;
color: #fff !important;
background-color: #dd8717;
}
// 红色标签
.tag-red {
border-color: #fcb5b9 !important;
color: #fff !important;
background-color: #cf1322;
}
// 绿色标签
.tag-green {
border-color: #baf1dd !important;
color: #ffffff !important;
background-color: #1eb07f;
}
// 紫色标签
.tag-purple {
border-color: #cec0fc !important;
color: #fff !important;
background-color: #6353cc;
}
// 浅蓝色标签
.tag-lightblue {
border-color: #c1ebfb !important;
color: #ffffff !important;
background-color: #2f9acc;
}
// 灰色标签
.tag-gray {
border-color: #d6d7db !important;
background-color: #ffffff;
color: #575a61;
}
.row {
display: flex;
.label {
width: 80px;
}
.content {
flex: 1;
flex-shrink: 0;
}
}
.btn {
padding: 0 5px;
background-color: #0e597f;
width: 100px;
text-align: center;
cursor: pointer;
line-height: 26px;
border-radius: 5px;
float: right;
}
</style>
@@ -1,223 +0,0 @@
<template>
<div class="judgment-conclusion">
<t-tabs v-model="tabIndex">
<t-tab-panel :value="1" label="总体战略态势" :destroy-on-hide="false">
<StrategyOverview/>
</t-tab-panel>
<t-tab-panel :value="2" label="敌情判定" :destroy-on-hide="false">
<EnemySituation/>
</t-tab-panel>
<t-tab-panel :value="3" label="我情判定" :destroy-on-hide="false">
<AssessmentContent/>
</t-tab-panel>
<t-tab-panel :value="4" label="战场环境" :destroy-on-hide="false">
<BattlefieldEnvironment/>
</t-tab-panel>
</t-tabs>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
import AssessmentContent from './AssessmentContent.vue';
import BattlefieldEnvironment from './BattlefieldEnvironment.vue';
import StrategyOverview from './StrategyOverview.vue';
import EnemySituation from './EnemySituation.vue';
import { useStore } from 'vuex';
import * as echarts from 'echarts';
import { requestAxiosService } from '@/service/request-axios-service';
const props = defineProps({
msg: String,
taskData: Object
})
const tabIndex = ref(1);
const opinionPieList = ref([
{
value: 1500,
name: '支持我方观点',
itemStyle: {
color: '#7acaa4'
}
},
{
value: 735,
name: '中立观点',
itemStyle: {
color: '#c8cdd5'
}
},
{
value: 1500,
name: '反对我方观点',
itemStyle: {
color: '#f5a444'
}
}
]);
const chartRef = ref(null);
const weakPoints = ref([
{ title: '俄乌战场经验可鉴,海马斯弱点已验证' },
{ title: '岛内对美售武器疑虑重重,存在认知裂痕' },
{ title: '台军虽可获得111套“海马斯”,但缺乏独立的侦察、指挥、数据链和电子战支撑体系,战时效能将大打折扣。' },
])
const strongPoints = ref([
{ title: '美方持续炒作“海马斯”威胁论' },
{ title: '“台独”势力将其视为“护身符”,加剧冒险' },
])
// 获取 Vuex store
const store = useStore();
const taskData = computed(() => store.state.taskData);
const service = requestAxiosService;
onMounted(() => {
})
</script>
<style scoped lang="less">
.judgment-conclusion {
z-index: 500;
color: #fff;
// 态势概览样式
.overview-content {
padding: 15px 0;
}
// 敌情判定样式
.enemy-assessmen {
padding: 15px 0;
}
}
.item-card {
padding: 15px;
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
.item-title {
font-size: var(--fontSize);
font-family: 'Noto Sans S Chinese';
font-weight: 700;
margin-bottom: 5px;
display: flex;
align-items: center;
img {
width: var(--fontSize);
margin-right: 5px;
}
}
.item-content {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
}
.item-border-card {
min-height: 145px;
border-left: 5px solid;
border-radius: 5px;
padding: 15px;
margin-bottom: 15px;
.item-title {
font-size: var(--fontSize);
font-family: 'Noto Sans S Chinese';
font-weight: 700;
margin-bottom: 5px;
display: flex;
align-items: center;
img {
width: var(--fontSize);
margin-right: 5px;
}
}
.item-content {
padding: 10px;
}
.ul li {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
}
.item-blue-card {
border: 1px solid;
border-radius: 5px;
border-color: rgba(85, 166, 254, 0.1);
padding: 15px;
margin-bottom: 15px;
background: rgba(0, 98, 217, 0.3);
}
.narrate-item {
padding: 10px 25px;
background: rgba(0, 238, 255, 0.2);
border-radius: 4px;
.ul {
li {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
}
}
.task-list-content {
padding: 10px 25px;
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
#chart-pie {
width: 150px;
height: 150px;
margin: 0 auto;
}
.viewpoint-item {
padding: 10px;
border-radius: 4px;
background: rgba(0, 140, 119, 0.1);
margin-bottom: 10px;
.item-footer {
color: #7acaa4;
}
}
.color-blue {
color: #0062d9;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.color-green {
color: #1eb07f;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.color-orange {
color: #d97b00;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.tab-orange {
border-color: #d97b00 !important;
background: #a6895f80;
.item-blue-card {
background: rgb(8 67 137);
}
}
</style>
@@ -1,270 +0,0 @@
<template>
<div class="overview-content">
<div class="margin-bottom-15">
<t-row :gutter="16">
<t-col :span="6">
<div class="item-card">
<div class="item-title">
<img src="/src/assets/icon/完成.svg" alt="" />
当前态势定性
</div>
<div class="item-cotnent">
{{ strategyData['当前态势定性'] || '暂无数据'}}
</div>
</div>
</t-col>
<t-col :span="6">
<div class="item-card">
<div class="item-title">
<img src="/src/assets/icon/zf-logo.png" alt="" />
核心矛盾
</div>
<div class="item-cotnent">
{{ strategyData['核心矛盾'] || '暂无数据' }}
</div>
</div>
</t-col>
</t-row>
</div>
<div class="item-card">
<div class="item-title" style="margin-bottom: 15px;">
<img src="/src/assets/icon/theme-logo.png" alt="" />
战略机遇与威胁(SWOT)
</div>
<t-row :gutter="16">
<t-col :span="6" v-for="(item, index) in overviewList" :key="index">
<div class="item-border-card" :class="item.color">
<div class="item-title">
{{ item.title }}
</div>
<div class="item-content">
<ul>
<t-empty v-if="item.list.length===0"/>
<li v-for="(aItem, aIndex) in item.list" :key="aIndex">
{{ aItem.title }}
</li>
</ul>
</div>
</div>
</t-col>
</t-row>
</div>
</div>
</template>
<script setup>
import { ref,reactive, onMounted, computed } from 'vue';
import { useStore } from 'vuex';
import * as echarts from 'echarts';
import { requestAxiosService } from '@/service/request-axios-service';
const props = defineProps({
msg: String,
taskData: Object
})
const overviewList = reactive([
{ title: '机遇', color: 'tab-blue', list: [] },
{ title: '威胁', color: 'tab-yellow', list: [] },
{ title: '优势', color: 'tab-green', list: [] },
{ title: '劣势', color: 'tab-red', list: [] },
]);
const opinionPieList = ref([
{
value: 1500,
name: '支持我方观点',
itemStyle: {
color: '#7acaa4'
}
},
{
value: 735,
name: '中立观点',
itemStyle: {
color: '#c8cdd5'
}
},
{
value: 1500,
name: '反对我方观点',
itemStyle: {
color: '#f5a444'
}
}
]);
const chartRef = ref(null);
// 获取 Vuex store
const store = useStore();
const taskData = store.state.taskData;
const service = requestAxiosService;
const strategyData = ref({})
onMounted(() => {
strategicSituationSearch();
})
// 总体战略态势
const strategicSituationSearch = async () => {
console.log(store.state.taskData,'任务信息')
const parmy = {
query: taskData.overview || ''
}
const res = await service.get(`${environment['b1ConsumerUrl']}/cop/copStrategicSituation`, parmy);
console.log('查询结果', res);
let result = res.data['总体战略态势分析'];
strategyData.value = result
if (result['战略机遇与威胁']) {
overviewList[0].list = [{title: result['战略机遇与威胁']['机遇']}];
overviewList[1].list = [{title: result['战略机遇与威胁']['威胁']}];
overviewList[2].list = [{title: result['战略机遇与威胁']['优势']}];
overviewList[3].list = [{title: result['战略机遇与威胁']['劣势']}];
}
}
</script>
<style scoped lang="less">
.judgment-conclusion {
z-index: 500;
color: #fff;
// 态势概览样式
.overview-content {
padding: 15px 0;
}
// 敌情判定样式
.enemy-assessmen {
padding: 15px 0;
}
}
.item-card {
padding: 15px;
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
.item-title {
font-size: var(--fontSize);
font-family: 'Noto Sans S Chinese';
font-weight: 700;
margin-bottom: 5px;
display: flex;
align-items: center;
img {
width: var(--fontSize);
margin-right: 5px;
}
}
.item-content {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
}
.item-border-card {
min-height: 145px;
border-left: 5px solid;
border-radius: 5px;
padding: 15px;
margin-bottom: 15px;
.item-title {
font-size: var(--fontSize);
font-family: 'Noto Sans S Chinese';
font-weight: 700;
margin-bottom: 5px;
display: flex;
align-items: center;
img {
width: var(--fontSize);
margin-right: 5px;
}
}
.item-content {
padding: 10px;
}
.ul li {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
}
.item-blue-card {
border: 1px solid;
border-radius: 5px;
border-color: rgba(85, 166, 254, 0.1);
padding: 15px;
margin-bottom: 15px;
background: rgba(0, 98, 217, 0.3);
}
.narrate-item {
padding: 10px 25px;
background: rgba(0, 238, 255, 0.2);
border-radius: 4px;
.ul {
li {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
}
}
.task-list-content {
padding: 10px 25px;
font-family: 'Noto Sans S Chinese';
font-size: 15px;
}
#chart-pie {
width: 150px;
height: 150px;
margin: 0 auto;
}
.viewpoint-item {
padding: 10px;
border-radius: 4px;
background: rgba(0, 140, 119, 0.1);
margin-bottom: 10px;
.item-footer {
color: #7acaa4;
}
}
.color-blue {
color: #0062d9;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.color-green {
color: #1eb07f;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.color-orange {
color: #d97b00;
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.tab-orange {
border-color: #d97b00 !important;
background: #a6895f80;
.item-blue-card {
background: rgb(8 67 137);
}
}
</style>
@@ -1,67 +0,0 @@
<template>
<div class="suggestion-content">
<div
class="btn"
style="position: absolute; right: 50px"
@click="updateData"
>
刷新
</div>
<t-tabs v-model="tabIndex">
<t-tab-panel :value="1" label="处置建议" :destroy-on-hide="false">
<HandleContent :taskId="taskData.id" :updateKey="handleUpdateKey" />
</t-tab-panel>
<t-tab-panel :value="2" label="决心建议" :destroy-on-hide="false">
<DeterminationContent :updateKey="determinationUpdateKey" />
</t-tab-panel>
</t-tabs>
</div>
</template>
<script setup>
import { ref } from "vue";
import HandleContent from "./HandleContent.vue";
import DeterminationContent from "./DeterminationContent.vue";
const props = defineProps({
msg: String,
taskData: Object,
});
const handleUpdateKey = ref(0);
const determinationUpdateKey = ref(0);
const updateData = () => {
tabIndex.value == 1
? handleUpdateKey.value++
: determinationUpdateKey.value++;
// updateKey.value++;
console.log(
tabIndex.value,
handleUpdateKey.value,
determinationUpdateKey.value
);
};
const tabIndex = ref(1);
</script>
<style scoped lang="less">
.suggestion-content {
z-index: 500;
color: #fff;
padding: 10px;
position: relative;
.btn {
padding: 0 5px;
background-color: #0e597f;
width: 100px;
text-align: center;
cursor: pointer;
line-height: 26px;
border-radius: 5px;
float: right;
cursor: pointer;
z-index: 9999;
}
}
</style>
@@ -1,548 +0,0 @@
<template>
<div class="situation-assessment">
<!-- 顶部tab页 -->
<div class="tabs-list">
<div class="tabs-item" :class="{ 'item-active': tabIndex === 1 }" @click="tabChange(1)">
优劣势分析
</div>
<div class="tabs-item" :class="{ 'item-active': tabIndex === 2 }" @click="tabChange(2)">
条件与限制
</div>
<div class="tabs-item" :class="{ 'item-active': tabIndex === 3 }" @click="tabChange(3)">
风险及应对
</div>
</div>
<!-- 内容区域 -->
<t-loading :loading="loading" show-overlay>
<!-- 优劣势分析 -->
<div v-if="tabIndex === 1" class="advantage-analysis">
<div class="title-content-item margin-bottom-10">民意RZ优劣势</div>
<!-- Echarts-图 -->
<!-- <t-row :gutter="16">
<t-col :span="6">
<div ref="chartRef" class="pie-charts"></div>
</t-col>
<t-col :span="6">
<div class="viewpoint-item" v-for="(item, index) in opinionPieList" :key="index">
<div>{{ index + 1 }}.{{ item.name }}</div>
<div class="item-footer">观点占比:{{ item.value }}%</div>
</div>
</t-col>
</t-row> -->
<t-dialog v-model:visible="YLModel" attach="body" header="详情" width="800" :confirm-on-enter="true"
:on-confirm="addModelConfig">
<div>
<div style="font-size: 26px;font-weight: 800px;color: #fff;margin-bottom: 20px;text-align: center;">
{{ YLData.title }}
</div>
<div style="font-size: 18px;font-weight: 600px;color: #fff;margin-bottom: 10px;">
{{ YLData.content }}
</div>
</div>
</t-dialog>
<!-- 优势,劣势 -->
<div class="advantage-content" v-if="advantageList.length > 0">
<div class="item-left advantage-bg">优势</div>
<div class="item-right">
<div class="list-content">
<div class="list-item" v-for="(item, index) in advantageList" :key="index"
@click="YLData = item; YLModel = true">
<div class="item-title" style="font-weight: 700;">{{ item.title }}</div>
<!-- <div class="item-title" style="font-weight: 300;margin-top: 10px;">{{ item.content }}</div> -->
</div>
</div>
</div>
</div>
<t-empty v-if="advantageList.length === 0" />
<div class="advantage-content" v-if="disadvantageList.length > 0">
<div class="item-left disadvantage-bg">劣势</div>
<div class="item-right">
<div class="list-content">
<div class="list-item" v-for="(item, index) in disadvantageList" :key="index"
@click="YLData = item; YLModel = true">
<div class="item-title" style="font-weight: 700;">{{ item.title }}</div>
<!-- <div class="item-title" style="font-weight: 300;margin-top: 10px;">{{ item.content }}</div> -->
</div>
</div>
</div>
</div>
<t-empty v-if="disadvantageList.length === 0" />
<!-- ZZ力量优劣势 -->
<div class="title-content-item margin-bottom-10">ZZ力量优劣势</div>
<div class="zz-content">
<div class="zz-list-content">
<div class="item-content" v-for="(value, key) in zzList" :key="key">
<div class="item-title">
{{ key }}
</div>
<div class="item-footer">
<template v-if="Array.isArray(value)">
<div class="row" style="margin: 5px 0;" v-for="item in value">
<div class="title" style="color: #fff;">
{{ item['维度'] }}:{{ item['判定'] }}
</div>
<div class="content" style="border-bottom: 1px solid #777;">描述:{{ item['描述'] }}</div>
</div>
</template>
<template v-else-if="isObject(value)">
<div class="row" style="margin: 5px 0;" v-for="(v, k) in value">
{{ k }}:{{ v }}
</div>
</template>
<template v-else>
{{ value }}
</template>
</div>
</div>
</div>
</div>
</div>
<!-- 条件与限制 -->
<div v-if="tabIndex === 2" class="conditions-constraints">
<t-space style="width: 100%" v-if="legalData" direction="vertical">
<t-collapse :expand-icon-placement="'right'" :defaultExpandAll="true" @change="handlePanelChange">
<t-collapse-panel :value="key" v-for="(value, key) in legalData" :key="key">
<template #header>
<div class="title-content-item">{{ key }}</div>
</template>
<template #default>
<div class="padding-10" v-for="(item, index) in value" :key="index">
{{ index + 1 }}.{{ item }}
</div>
</template>
</t-collapse-panel>
</t-collapse>
</t-space>
<div v-if="!legalData" class="padding-10">
<t-empty />
</div>
</div>
<!-- 风险及应对 -->
<div v-if="tabIndex === 3" class="risks-responses">
<t-space style="width: 100%" direction="vertical">
<t-collapse :expand-icon-placement="'right'" :defaultExpandAll="true" @change="handlePanelChange">
<t-collapse-panel :value="key" v-for="(value, key) in riskData" :key="key">
<template #header>
<div class="title-content-item">{{ key }}</div>
</template>
<template #default>
<div class="padding-10" v-for="(item, index) in value" :key="index">
{{ index + 1 }}.{{ item }}
</div>
</template>
</t-collapse-panel>
<!-- <t-collapse-panel value="0">
<template #header>
<div class="title-content-item">风险分析</div>
</template>
<template #default>
这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。
</template>
</t-collapse-panel>
<t-collapse-panel value="1">
<template #header>
<div class="title-content-item">应对方案</div>
</template>
<template #default>
这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。
</template>
</t-collapse-panel> -->
</t-collapse>
</t-space>
</div>
</t-loading>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from "vue";
import * as echarts from 'echarts';
import { requestAxiosService } from "@/service/request-axios-service";
import { index } from "d3";
const isObject = (value) => {
return value !== null && Object.prototype.toString.call(value) === '[object Object]';
}
const addModelConfig = () => {
}
const YLModel = ref(false)
const YLData = ref({})
const props = defineProps({
msg: String,
taskId: Number
});
const tabIndex = ref(1); // 1 优劣势分析 2 条件与劣势 3 风险及应对
const advantageList = ref([
// 优势
// { title: "内容内容内容内容内容内容内容内容内容内容" },
// { title: "内容内容内容内容内容内容内容内容内容内容" },
// { title: "内容内容内容内容内容内容内容内容内容内容" },
]);
const disadvantageList = ref([
//劣势
// { title: "内容内容内容内容内容内容内容内容内容内容" },
// { title: "内容内容内容内容内容内容内容内容内容内容" },
// { title: "内容内容内容内容内容内容内容内容内容内容" },
]);
const zzList = ref({});
const opinionPieList = ref([
{
value: 1500,
name: '支持我方观点',
itemStyle: {
color: '#7acaa4'
}
},
{
value: 735,
name: '中立观点',
itemStyle: {
color: '#c8cdd5'
}
},
{
value: 1500,
name: '反对我方观点',
itemStyle: {
color: '#f5a444'
}
}
]);
let colors = ['#7acaa4', '#c8cdd5', '#f5a444'];
const opinionPieLists = ref([
{
value: 1500,
name: '支持我方观点',
itemStyle: {
color: '#7acaa4'
}
},
{
value: 735,
name: '中立观点',
itemStyle: {
color: '#c8cdd5'
}
},
]);
const strengthsData = ref(null) // 优劣势分析
const legalData = ref(null); // 条件与劣势
const riskData = ref(null); // 风险及建议
const chartRef = ref(null);
const service = requestAxiosService;
const loading = ref(false);
onMounted(() => {
})
const initPieChart = () => {
let chartPie = echarts.init(chartRef.value);
const option = {
title: {
text: '',
left: 'center',
top: 'center'
},
legend: {
show: false // 隐藏图例
},
tooltip: {
show: true,
appendTo: 'body',
formatter: function (params) {
// console.log('params数据',params);
let text = ``;
text = `
<div class="pint-pie__tooltip">
<div>${params.data.name}</div>
<div>
<span class="">${params.data.value}</span>
</div>
</div>
`;
return text;
}
},
series: [
{
name: '访问来源',
type: 'pie',
radius: ['50%', '85%'], // 设置圆环的内径和外径
// padAngle: 2,
// avoidLabelOverlap: false,
itemStyle: {
borderColor: '#fff',
// borderRadius: 4,
borderWidth: 0
},
label: {
show: false
},
labelLine: {
show: false // 隐藏标签线
},
emphasis: {
disabled: true,
label: {
show: false,
fontSize: '20',
fontWeight: 'bold'
}
},
data: opinionPieList.value
},
{
name: '外边框',
type: 'pie',
radius: ['90%', '90%'],
center: ['50%', '50%'],
emphasis: {
disabled: true
},
label: {
show: false
},
labelLine: {
show: false // 隐藏标签线
},
tooltip: {
show: false
},
data: [
{
value: 0,
name: '',
itemStyle: {
borderWidth: 1,
borderColor: '#e4e9f4'
}
}
]
}
]
}
chartPie.setOption(option)
}
const tabChange = async (value) => {
// tabs标签页点击
tabIndex.value = value;
await querySituationEvaluation(props.taskId);
};
const handlePanelChange = (value) => {
console.log(value);
};
const querySituationEvaluation = async (taskId) => {
const parmy = {
taskId,
type: tabIndex.value
}
loading.value = true;
const res = await service.get(`${environment['b1ConsumerUrl']}/cop/querySituationEvaluation`, parmy);
console.log('情况判断', res);
loading.value = false;
let result = res.data;
if (tabIndex.value === 1) {
strengthsData.value = result;
advantageList.value = result['民意认知优劣势']['优势'];
disadvantageList.value = result['民意认知优劣势']['劣势'];
zzList.value = result['作战力量优劣势'];
opinionPieList.value = [];
for (let key in result['民意认知优劣势']['观点占比']) {
opinionPieList.value.push({
value: Number(result['民意认知优劣势']['观点占比'][key].replace(/%/g, '')),
name: key,
itemStyle: {
color: '#7acaa4'
}
})
}
opinionPieList.value.forEach((item, index) => {
item.itemStyle.color = colors[index];
});
console.log('观点占比', opinionPieList.value)
setTimeout(() => {
initPieChart();
}, 100)
} else if (tabIndex.value === 2) {
console.log(result);
legalData.value = result;
} else if (tabIndex.value === 3) {
riskData.value = result;
}
}
watch(() => props.taskId, (newVal, oldVal) => {
// console.log('count changed from', oldVal, 'to', newVal);
if (newVal) {
// getTaskActorDetailSearch(newVal);
querySituationEvaluation(newVal);
}
}, { immediate: true });
</script>
<style scoped lang="less">
.situation-assessment {
z-index: 500;
height: calc(100vh - 180px);
overflow-y: auto;
color: #fff;
padding: 10px;
.advantage-analysis {
padding: 0 12px;
.pie-charts {
width: 100%;
height: 150px;
background: rgba(1, 12, 28, 0.15);
}
.advantage-content {
padding: 10px;
display: flex;
align-items: stretch;
/* 使子元素在垂直方向上伸展 */
justify-content: space-between;
.advantage-bg {
background: rgba(0, 238, 255, 0.2);
}
.disadvantage-bg {
background: rgba(245, 34, 45, 0.3);
}
.item-left {
padding: 2px;
border-radius: 4px;
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #ffffff;
font-size: var(--fontSize);
text-align: center;
display: flex;
align-items: center;
}
.item-right {
flex-grow: 1;
.list-content {
.list-item {
font-family: Noto Sans S Chinese;
color: #ffffff;
font-size: var(--fontSize);
margin-bottom: 10px;
margin-left: 10px;
line-height: var(--fontSize);
background: rgba(0, 98, 217, 0.1);
padding: 10px 15px;
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 2px;
&:hover {
background: rgba(0, 98, 217, 0.3);
border-color: rgba(85, 166, 254, 0.1);
}
}
}
}
}
.zz-content {
padding: 10px;
.zz-list-content {
.item-content {
width: 100%;
background: rgba(1, 12, 28, 0.15);
border-radius: 4px;
padding: 10px 20px;
margin-bottom: 10px;
&:hover {
background: rgba(0, 98, 217, 0.3);
border-color: rgba(85, 166, 254, 0.1);
}
.item-title {
line-height: 36px;
font-family: "Noto Sans S Chinese";
color: #ffffff;
font-size: var(--titleSize);
}
.item-footer {
line-height: 28px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: rgba(255, 255, 255, 0.8);
}
}
}
}
}
.conditions-constraints {
padding: 0 12px;
}
.viewpoint-item {
padding: 10px;
border-radius: 4px;
background: rgba(0, 140, 119, 0.1);
margin-bottom: 10px;
.item-footer {
color: #7acaa4;
}
}
}
// 折叠面板整体样式
:deep(.t-collapse) {
font: var(--td-font-body-medium);
color: var(--td-text-color-primary);
box-sizing: border-box;
margin: 0;
padding: 0;
list-style: none;
border: none !important;
border-bottom: 0;
background: #24242400 !important;
}
// 折叠面板标题样式
:deep(.t-collapse-panel__wrapper .t-collapse-panel__header) {
padding: var(--td-comp-paddingTB-m) var(--td-comp-paddingLR-l);
border-bottom: none !important;
font: var(--td-font-title-small);
display: flex;
align-items: center;
}
// 折叠面板背景样式
:deep(.t-collapse-panel__wrapper .t-collapse-panel__body) {
border-bottom: none !important;
background: #24242400 !important;
overflow: inherit;
}
//折叠面板内容样式
:deep(.t-collapse-panel__wrapper .t-collapse-panel__content) {
padding: var(--td-comp-paddingTB-m) var(--td-comp-paddingLR-l) var(--td-comp-paddingTB-m) calc(var(--td-comp-paddingLR-l) + var(--td-comp-margin-xxl));
color: var(--td-text-color-primary) !important;
font-family: "Arial Negreta", "Arial Normal", "Arial", sans-serif;
// font-weight: 700;
font-style: normal;
// font-size: var(--titleSize);
}
</style>
@@ -1,35 +0,0 @@
<template>
<div class="task-analysis">
<TaskDataSearch v-if="!taskDetailShow" @taskDetailsC="taskDataChange"/>
<AnalysisData v-if="taskDetailShow"/>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue';
import TaskDataSearch from './TaskAnalysis/TaskDataSearch.vue';
import AnalysisData from './TaskAnalysis/AnalysisData.vue';
import dayjs from 'dayjs';
defineProps({
msg: String,
taskDetailShow: Boolean
})
const taskData = ref({})
const emit = defineEmits(['taskDetailsC']) // 定义子组件发送给父组件的函数
const taskDataChange = (value) => { // 任务点击
taskData.value = value;
emit('taskDetailsC', value); // 给父组件传参
}
</script>
<style scoped lang="less">
.task-analysis {
width: 100%;
height: 100%;
z-index: 500;
color: #fff;
padding: 10px;
}
</style>
@@ -1,719 +0,0 @@
<template>
<div class="task-analysis">
<!-- 任务详情 -->
<div>
<div class="task-list-item task-detail cursor-pointer">
<div class="task-status-logo" :class="handleBg(taskData.taskStatus)">{{ taskData.taskStatus < 30 ?
taskStatus[taskData.taskStatus] : '已完成' }}</div>
<div class="item-title">{{ taskData.taskName }}</div>
<div class="tab-list">
<span class="item-tab tab-green">{{ taskData.taskDirection }}</span>
<span class="item-tab tab-lightblue">{{ taskLevel[taskData.taskLevel] }}</span>
</div>
<div class="item-content text-ellipsis ellipsis3">
{{ taskData.overview }}
</div>
<div class="item-footer flex-content">
<span class="item-label">
{{ sourceTypeDict[taskData.sourceType] }}
</span>
<span class="item-date">
{{ dayjs(taskData.createTime).format('YYYY-MM-DD HH:mm:ss') }}
</span>
</div>
</div>
<!-- 分析结果 -->
<div class="title-content-item margin-bottom-10 flex-content">
<span>分析结果</span>
<span class="task-back" v-if="aimData" @click="treeMapChange">
<img src="../../assets/icon/tree-map-logo.svg" style="margin-right: 5px;" alt="" />
逻辑图
</span>
</div>
<t-loading :loading="taskLoading" show-overlay>
<div class="analysis-content">
<!-- 顶部tab页 -->
<!-- <div class="tabs-list">
<div class="tabs-item" :class="{ 'item-active': tabIndex === 0 }" @click="tabChange(0)">
支撑性目的1
</div>
<div class="tabs-item" :class="{ 'item-active': tabIndex === 1 }" @click="tabChange(1)">
支撑性目的2
</div>
</div> -->
<t-tabs v-model="tabIndex" @change="tabChange" theme="card">
<t-tab-panel v-for="(item, index) of taskDataList" :value="index" :label="'支持性目的' + (index + 1)"
:destroy-on-hide="false" :key="index">
</t-tab-panel>
</t-tabs>
<t-loading :loading="actorLoading" show-overlay>
<!-- 目的详情 -->
<div class="aim-item-card" @click="chatShowChange">
<div v-if="aimData" class="item-content">
<span class="aim-logo">目的</span><span style="margin-right: 50px;"></span>{{ aimData.zzPurpose }}
</div>
<t-empty v-if="!aimData" />
</div>
<!-- 行为体列表 -->
<div class="action-content">
<t-empty v-if="actorDataList?.length === 0" />
<div class="task-list-item" v-for="(item, index) in actorDataList" :key="index"
@click="actionShowChange(item)">
<div class="item-content text-ellipsis ellipsis3">
<span class="item-tab tab-blue">行为体</span> {{ item.actorMap[0].actorName }}
</div>
<div class="item-footer flex-content">
<!-- <span>
{{ item.deptName }}
</span> -->
<!-- <span>
<span class="item-value">2025-05-10 ~ 2025-05-25</span>
</span> -->
</div>
</div>
</div>
</t-loading>
</div>
</t-loading>
</div>
</div>
<t-dialog v-model:visible="visibles" header="详情" width="700" :confirm-on-enter="true" :on-cancel="handleCancels"
:on-close="handleCancels" :on-confirm="handleOks">
<t-space direction="vertical" style="width: 100%;">
<div class="action-details">
<!-- <div class="item-head">
桃色与军纪舆论裂变攻台军形象
</div>
<div class="item-card">
支撑性目的:利用桃色新闻与军纪涣散议题,瓦解台军社会形象与民众支持基础,引发线上网红、大V等对台军……
</div> -->
<!-- 顶部标签页 -->
<!-- <TabsContent :tabIndex="tabIndexs" :tabsList="tabsList"
:tabStyle="{ 'textAlign': 'left', 'borderBottom': '1px solid rgba(0, 98, 217, 0.4)' }"
@tabsDataC="tabDataChange" /> -->
<div class="tab-content">
<template v-for="(item, index) in tabsList" :key="index">
<div class="item-tab" :class="{ 'item-active': tabIndexs === item.value }"
@click="tabDataChange(item.value)">
{{ item.label }}
</div>
</template>
</div>
<div class="narrate-list" v-if="tabIndexs === 'narrate'">
<div class="item-card" v-for="(item, index) in narrateList" :key="index">
<div class="item-title">
<span class="item-index">{{ index + 1 }}</span>. {{ item.narration }}
</div>
</div>
</div>
<div class="narrate-list" v-if="tabIndexs !== 'narrate'">
<div class="item-card" v-for="(item, index) in behaviorList" :key="index">
<div class="item-content">
预期行为:{{ item.behavior }}
</div>
<div class="item-line"></div>
<div class="item-content" v-for="(aItem, aIndex) in item.factorList" :key="aIndex">
驱动因素:{{ aItem.drivingFactor }}
</div>
</div>
</div>
</div>
</t-space>
</t-dialog>
<t-dialog v-model:visible="treeMapShow" header="详情" width="1400" attach="body" :confirm-on-enter="true"
:on-cancel="handleTreeMapCancel" :on-close="handleCancels" :on-confirm="handleTreeMapOk">
<t-space direction="vertical" style="width: 100%;">
<div>
<TreeDataMap v-if="treeMapShow" :taskId="taskData.id" :taskData="taskData" />
</div>
</t-space>
</t-dialog>
<t-drawer v-model:visible="visible" attach="body" size="75%" :footer="false" :closeBtn="false"
:on-confirm="handleCancel" :close-btn="true">
<template #header>
<div class="drawer-head" @click="handleCancel">
<arrow-left-icon :stroke-width="2.5" fillColor="#fff" size="25px" />
<span>返回</span>
<span style="margin-top: -2px;">|</span>
<span style="margin-left: 10px;">智能参谋</span>
</div>
</template>
<div>
<IntelligentHelperDrawer />
</div>
</t-drawer>
</template>
<script setup>
import { ref, reactive, defineEmits, watch } from 'vue';
import TaskList from './TaskDataList.vue';
import TabsContent from '../views/TabsContent.vue';
import { ArrowLeftIcon } from 'tdesign-icons-vue-next';
import IntelligentHelperDrawer from '../intelligent-helper-drawer/IntelligentHelperDrawer.vue';
import taskStatus from '@/common/taskStatus';
import taskLevel from '@/common/taskLevel';
import sourceTypeDict from '@/common/sourceType';
import TreeDataMap from '@/components/TreeDataMap.vue';
import { requestAxiosService } from '@/service/request-axios-service';
import dayjs from 'dayjs';
const props = defineProps({
msg: String,
taskData: Object,
})
const inputValue = ref(''); // 输入款内容
const taskRadioValue = ref('all') // 任务类型选择
const presets = ref({
最近7天: [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()],
最近3天: [dayjs().subtract(2, 'day').toDate(), dayjs().toDate()],
今天: [dayjs().toDate(), dayjs().toDate()],
});
const taskList = ref([
{ title: '任务01', taskStatus: '已完成', taskLogo: 'task-completed' },
{ title: '任务01', taskStatus: '分析中', taskLogo: 'task-uncompleted' },
{ title: '任务01', taskStatus: '待分析', taskLogo: 'task-pendinganalysis' },
{ title: '任务01', taskStatus: '未通过', taskLogo: 'task-failed' },
{ title: '任务01', taskStatus: '已完成', taskLogo: 'task-completed' },
]);
const actionList = ref([
{ title: '行动01', taskStatus: '已完成', taskLogo: 'task-completed' },
{ title: '行动01', taskStatus: '分析中', taskLogo: 'task-uncompleted' },
{ title: '行动01', taskStatus: '待分析', taskLogo: 'task-pendinganalysis' },
{ title: '行动01', taskStatus: '未通过', taskLogo: 'task-failed' },
{ title: '行动01', taskStatus: '已完成', taskLogo: 'task-completed' },
]);
const taskDataList = ref([]); // 任务信息列表
const taskLoading = ref(false);
const aimData = ref(null) // 目的信息
const actorDataList = ref([]); // 行为体信息
const actorLoading = ref(false);
const range = ref([]); // 日期时间筛选框
const taskDetailShow = ref(false);
const tabIndex = ref(0) // tab页切换
const emit = defineEmits(['taskDetailsC']) // 定义子组件发送给父组件的函数
const visible = ref(false);
const visibles = ref(false); // 对话框
const treeMapShow = ref(false) // 逻辑图
const tabIndexs = ref('narrate'); // narrate 叙事主题 behavior 预期行为
const tabsList = ref([
{ label: '叙事主题', value: 'narrate' },
{ label: '预期行为', value: 'behavior' },
])
const narrateList = ref([
{ title: '台军桃色丑闻频发暴露军纪涣散' },
{ title: '飞行事故频发揭穿台军装备老旧与训练造假' },
])
const behaviorList = ref([
{ actor: '台军各军种单位', behavior: '通过社交媒体或媒体渠道揭露上级贪腐、性丑闻等(如陆军109旅贩毒案后内部爆料)' },
]);
const service = requestAxiosService;
const handleBg = d => {
const colorList = {
20: 'uncompleted',
10: 'pendinganalysis',
50: 'failed',
}
return colorList[d] || 'completed'
}
// 查询zz目的
const getTaskActorDetailSearch = async (taskId) => {
const parmy = {
taskId,
}
taskLoading.value = true;
const res = await service.get(`${environment['b1ConsumerUrl']}/cop/findZzPurposeByTaskId`, parmy);
taskLoading.value = false;
console.log(res);
taskDataList.value = res.data;
if (taskDataList.value?.length > 0) {
await tabChange(0);
}
}
// 查询zz目的下的行为体等信息
const findActionDataByZzIdSearch = async (purposeId) => {
const parmy = {
purposeId,
}
actorLoading.value = true;
const res = await service.get(`${environment['b1ConsumerUrl']}/cop/findActionDataByZzId`, parmy);
actorLoading.value = false;
console.log(res);
actorDataList.value = res.data;
}
watch(props.taskData, (newVal, oldVal) => {
// console.log('count changed from', oldVal, 'to', newVal);
if (newVal) {
getTaskActorDetailSearch(newVal.id);
}
}, { immediate: true });
const tabDataChange = (value) => {
tabIndexs.value = value;
}
const actionShowChange = (item) => {
console.log('这是什么', item);
narrateList.value = item.narrationMap;
behaviorList.value = [];
item.expectedBehaviorMap.forEach((aItem, index) => {
behaviorList.value.push({
behavior: aItem.behavior,
factorList: [item.drivingFactorMap[index]],
})
})
visibles.value = true;
}
const handleOks = () => {
visibles.value = false;
}
const handleCancels = () => {
visibles.value = false;
}
const chatShowChange = () => {
visible.value = true;
}
const handleOk = () => {
visible.value = false;
};
const handleCancel = () => {
visible.value = false;
}
// 逻辑图
const treeMapChange = () => {
treeMapShow.value = true;
}
const handleTreeMapOk = () => {
treeMapShow.value = false;
};
const handleTreeMapCancel = () => {
treeMapShow.value = false;
}
// 回车搜索
const onEnter = () => {
}
// tab页切换
const tabChange = async (value) => {
console.log('点击了', value);
aimData.value = taskDataList.value[value];
console.log(aimData);
await findActionDataByZzIdSearch(aimData.value.id);
tabIndex.value = value;
}
// 任务类型选择
const taskChange = (value) => {
taskRadioValue.value = value
}
</script>
<style scoped lang="less">
.task-analysis {
width: 100%;
height: 100%;
z-index: 500;
color: #fff;
padding: 15px;
display: flex;
flex-direction: column;
overflow-y: auto;
.task-detail {
padding: 15px 20px !important;
margin: 0 !important;
margin-bottom: 10px !important;
}
// 任务信息
.task-information {
width: 100%;
height: 267px;
background: url('../assets/icon/task-data-bg.png') no-repeat;
background-size: cover;
margin-bottom: 25px;
display: flex;
justify-content: space-around;
.list-item {
width: 115px;
height: 100%;
.item-head {
width: 95px;
height: 95px;
border: 3px solid #ec9004;
border-radius: 50%;
margin: 0 auto;
margin-top: 30px;
position: relative;
.item-logo {
width: 81px;
height: 81px;
background: url('../assets/icon/map-icon-logo.png');
background-position: -500px -79px;
position: absolute;
left: 50%;
top: 5px;
transform: translate(-50%);
font-size: var(--titleSize);
color: #7fcdff;
line-height: 81px;
text-align: center;
}
}
.item-body {
padding: 10px 0;
.item-content {
margin-bottom: 10px;
font-size: var(--fontSize);
display: flex;
align-items: center;
.item-point {
height: 10px;
width: 10px;
margin-right: 10px;
border-radius: 5px;
display: inline-block;
}
.item-title {
color: #73c7ec;
margin-right: 20px;
font-family: Noto Sans S Chinese;
}
.item-value {
font-family: 'DDIN';
}
}
}
}
}
.aim-item-card {
background: rgba(0, 98, 217, 0.4);
border: 1px solid;
border-color: #0062d9;
border-radius: 4px;
margin: 15px;
padding: 10px;
position: relative;
.item-content {
line-height: 26px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: 18px;
.aim-logo {
background: rgba(160, 224, 249, 0.28);
border-radius: 4px 0px 12px 0px;
padding: 5px 10px;
line-height: 16px;
font-size: 16px;
position: absolute;
top: 0;
left: 0;
}
}
}
.task-back {
// background: linear-gradient(102.76deg, #2b8ffe 0%, #0062d9 100%);
background: #00368c;
border: 1px solid;
border-color: #0056c6;
border-radius: 4px;
padding: 5px 10px;
line-height: var(--fontSize);
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSize);
display: flex;
align-items: center;
cursor: pointer;
}
/* 垂直排列子元素 */
.task-body {
padding: 10px;
// flex-grow: 1; /* 让 div 填充剩余空间 */
height: calc(100vh - 225px);
box-sizing: border-box;
overflow-y: auto;
}
.task-list-item {
padding: 10px;
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
margin: 15px;
position: relative;
cursor: pointer;
overflow: hidden;
&:hover {
background: rgba(0, 98, 217, 0.4);
border: 1px solid;
border-color: #0062d9;
}
.task-status-logo {
width: 132px;
height: 30px;
transform: rotate(45deg);
position: absolute;
top: 12px;
text-align: center;
line-height: 30px;
right: -36px;
}
.completed {
background: linear-gradient(134.57deg, #05b89f 14.74%, #007f73 84.95%);
}
.uncompleted {
background: linear-gradient(134.57deg, #d09a14 14.74%, #a76e12 84.95%);
}
.pendinganalysis {
background: linear-gradient(134.57deg, #117dff 14.74%, #0057b8 84.95%);
}
.failed {
background: linear-gradient(134.57deg, #838383 14.74%, #5e5e5e 84.95%);
}
.item-title {
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #ffffff;
font-size: var(--titleSize);
line-height: 24px;
margin-bottom: 10px;
}
.tab-list {
margin-bottom: 10px;
.item-tab {
display: inline-block;
line-height: var(--fontSizes);
padding: 5px 10px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSizes);
border: 1px solid;
border-radius: 4px;
margin-right: 5px;
}
}
.item-content {
line-height: 29px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSize);
margin-bottom: 10px;
}
.item-footer {
font-size: var(--fontSizes);
color: rgba(255, 255, 255, 0.8);
.item-label {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: rgba(255, 255, 255, 0.8);
font-size: var(--fontSize);
}
.item-date {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: rgba(255, 255, 255, 0.8);
font-size: var(--fontSize);
}
}
}
}
// 详情弹窗页面
.action-details {
.item-head {
font-size: var(--fontSize);
font-family: 'Noto Sans S Chinese';
padding: 10px;
}
.item-card {
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
padding: 10px;
margin-top: 10px;
margin-bottom: 15px;
.item-index {
font-family: 'Noto Sans S Chinese';
font-weight: 500;
}
.item-title {
// font-size: var(--fontSize);
font-family: 'Noto Sans S Chinese';
// font-weight: 500;
margin-bottom: 5px;
}
}
}
.narrate-list {
.item-card {
background: rgba(1, 12, 28, 0.15);
border-radius: 4px;
padding: 15px;
margin: 10px 0;
.item-index {
font-family: Noto Sans S Chinese;
font-weight: 700;
color: #ffffff;
font-size: var(--fontSize);
}
.item-title {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #ffffff;
font-size: var(--fontSize);
}
.item-content {
line-height: 23px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: rgba(255, 255, 255, 0.8);
font-size: var(--fontSizes);
}
.item-line {
width: 100%;
height: 1px;
background: rgba(85, 159, 254, 0.25);
border-radius: 4px;
margin: 10px 0;
}
}
}
.tab-content {
width: 100%;
border-bottom: 1px solid rgba(85, 159, 254, 0.4);
.item-tab {
padding: 10px 25px;
background: rgba(3, 38, 80, 0.48);
border: 1px solid;
border-color: #0e3668;
border-radius: 4px 4px 0px 0px;
font-family: Noto Sans S Chinese;
color: rgba(255, 255, 255, 0.8);
font-size: var(--fontSize);
display: inline-block;
cursor: pointer;
}
.item-active {
border: 0;
background: linear-gradient(99.41deg, #2b8ffe 0%, #0062d9 100%);
border-radius: 4px 4px 0px 0px;
color: #FFFFFF;
}
}
.item-label {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
margin-right: 5px;
}
.item-value {
font-family: 'DDIN';
}
.item-tab {
font-size: var(--fontSizes);
line-height: var(--fontSize);
padding: 5px 10px;
margin-right: 5px;
border: 1px solid;
border-radius: 4px;
}
.sortBtn {
font-size: var(--fontSizes);
padding: 8px 10px;
}
:deep(.t-tabs__nav--card) {
font-size: var(--fontSizes);
background: transparent;
padding: 10px 15px;
}
:deep(.t-tabs__nav--card.t-tabs__nav-item:not(:first-of-type)) {
border: 0;
}
:deep(.t-tabs__nav--card.t-tabs__nav-item.t-is-active) {
background: linear-gradient(103.13deg, #2b8ffe 0%, #0062d9 100%);
border-radius: 24px;
color: #FFFFFF;
position: relative;
&::after {
width: 77px;
height: 2px;
background: url('../../assets/map-icon/tab-line-logo.svg') no-repeat;
background-size: cover;
content: '';
display: block;
position: absolute;
bottom: 0;
}
}
.action-content {
height: calc(100vh - 680px);
overflow-y: auto;
}
</style>
@@ -1,142 +0,0 @@
<template>
<div class="task-list">
<div v-for="(item, index) in taskList" :key="index">
<div class="task-list-item cursor-pointer" @click="taskDataChange(item)">
<div class="task-status-logo" :class="item.taskLogo">{{ item.taskStatus }}</div>
<div class="item-title">{{ item.title }}</div>
<div class="tab-list">
<span class="item-tab tab-green">W方向</span>
<span class="item-tab tab-lightblue">一般</span>
</div>
<div class="item-content">
利用退役飞官言论构建“T军离心力”叙事加强对T方向相关舆论热度。利用退役飞官言论构
</div>
<div class="item-footer flex-content">
<span class="item-label">
来源信息
</span>
<span class="item-date">
2025-05-15 10:16
</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue'
defineProps({
taskList: Array,
})
const emit = defineEmits(['taskDetailsC']) // 定义子组件发送给父组件的函数
const count = ref(0)
const taskDataChange = (item) => {
emit('taskDetailsC', item)
}
</script>
<style scoped lang="less">
// 任务列表样式
.task-list {
width: 100%;
.task-list-item {
padding: 10px;
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
margin-bottom: 15px;
position: relative;
overflow: hidden;
&:hover {
background: rgba(0, 98, 217, 0.4);
border: 1px solid;
border-color: #0062d9;
}
.task-status-logo {
width: 132px;
height: 30px;
transform: rotate(45deg);
position: absolute;
top: 12px;
text-align: center;
line-height: 30px;
right: -36px;
}
.task-completed {
background: linear-gradient(134.57deg, #05b89f 14.74%, #007f73 84.95%);
}
.task-uncompleted {
background: linear-gradient(134.57deg, #d09a14 14.74%, #a76e12 84.95%);
}
.task-pendinganalysis {
background: linear-gradient(134.57deg, #117dff 14.74%, #0057b8 84.95%);
}
.task-failed {
background: linear-gradient(134.57deg, #838383 14.74%, #5e5e5e 84.95%);
}
.item-title {
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #ffffff;
font-size: var(--titleSize);
line-height: 24px;
margin-bottom: 10px;
}
.tab-list {
margin-bottom: 10px;
.item-tab {
display: inline-block;
line-height: var(--fontSizes);
padding: 5px 10px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSizes);
border: 1px solid;
border-radius: 4px;
margin-right: 5px;
}
}
.item-content {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSize);
margin-bottom: 10px;
}
.item-footer {
.item-label {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: rgba(255, 255, 255, 0.8);
font-size: var(--fontSize);
}
.item-date {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: rgba(255, 255, 255, 0.8);
font-size: var(--fontSize);
}
}
}
}
</style>
@@ -1,367 +0,0 @@
<template>
<div class="task-analysis">
<!-- 任务信息 -->
<div class="task-information">
<div class="list-item">
<div class="item-head">
<div class="item-logo">
W方向
</div>
</div>
<div class="item-body">
<div class="item-content">
<span class="item-point" style="background-color: rgb(236, 144, 4);"></span>
<span class="item-title">筹划中:</span>
<span class="item-value" style="color: rgb(236, 144, 4);">615</span>
</div>
<div class="item-content">
<span class="item-point" style="background-color: rgb(82, 220, 171);"></span>
<span class="item-title">投送中:</span>
<span class="item-value" style="color: rgb(82, 220, 171);">20</span>
</div>
<div class="item-content">
<span class="item-point" style="background-color: rgb(43, 143, 254);"></span>
<span class="item-title">已完成:</span>
<span class="item-value" style="color: rgb(43, 143, 254);">5</span>
</div>
</div>
</div>
<div class="list-item">
<div class="item-head">
<div class="item-logo">
u方向
</div>
</div>
<div class="item-body">
<div class="item-content">
<span class="item-point" style="background-color: rgb(236, 144, 4);"></span>
<span class="item-title">筹划中:</span>
<span class="item-value" style="color: rgb(236, 144, 4);">615</span>
</div>
<div class="item-content">
<span class="item-point" style="background-color: rgb(82, 220, 171);"></span>
<span class="item-title">投送中:</span>
<span class="item-value" style="color: rgb(82, 220, 171);">20</span>
</div>
<div class="item-content">
<span class="item-point" style="background-color: rgb(43, 143, 254);"></span>
<span class="item-title">已完成:</span>
<span class="item-value" style="color: rgb(43, 143, 254);">5</span>
</div>
</div>
</div>
</div>
<!-- 任务列表 -->
<div>
<div class="task-head">
<!-- 任务筛选 -->
<div class="margin-bottom-10">
<t-row :gutter="16">
<t-col :span="7">
<div>
<t-input v-model="inputValue" placeholder="请输入内容搜索" @enter="onEnter" />
</div>
</t-col>
<t-col :span="5">
<t-select>
<t-option key="待分析" label="待分析" value="待分析" />
<t-option key="分析中" label="分析中" value="分析中" />
<t-option key="已完成" label="已完成" value="已完成" />
</t-select>
</t-col>
</t-row>
</div>
<div>
<t-row :gutter="16">
<t-col :span="8">
<div>
<t-date-range-picker v-model="range" :presets="presets" enable-time-picker />
</div>
</t-col>
<t-col :span="4">
<div class="tabs-box">
<span class="sortBtn" :class="{ 'sort-active': taskRadioValue === 'all' }"
@click="taskChange('all')">全部</span>
<span class="sortBtn" :class="{ 'sort-active': taskRadioValue === 'me' }"
@click="taskChange('me')">我的</span>
</div>
</t-col>
</t-row>
</div>
</div>
<!-- 任务列表 -->
<div class="task-body">
<TaskList :taskList="taskList" @taskDetailsC="taskDataChange" />
</div>
</div>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue';
import TaskList from './TaskDataList.vue';
import dayjs from 'dayjs';
defineProps({
msg: String,
})
const inputValue = ref(''); // 输入款内容
const taskStatus = ref('a'); // 任务状态
const taskRadioValue = ref('all') // 任务类型选择
const presets = ref({
最近7天: [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()],
最近3天: [dayjs().subtract(2, 'day').toDate(), dayjs().toDate()],
今天: [dayjs().toDate(), dayjs().toDate()],
});
const taskList = ref([
{ title: '任务01', taskStatus: '已完成', taskLogo: 'task-completed' },
{ title: '任务01', taskStatus: '分析中', taskLogo: 'task-uncompleted' },
{ title: '任务01', taskStatus: '待分析', taskLogo: 'task-pendinganalysis' },
{ title: '任务01', taskStatus: '未通过', taskLogo: 'task-failed' },
{ title: '任务01', taskStatus: '已完成', taskLogo: 'task-completed' },
]);
const actionList = ref([
{ title: '行动01', taskStatus: '已完成', taskLogo: 'task-completed' },
{ title: '行动01', taskStatus: '分析中', taskLogo: 'task-uncompleted' },
{ title: '行动01', taskStatus: '待分析', taskLogo: 'task-pendinganalysis' },
{ title: '行动01', taskStatus: '未通过', taskLogo: 'task-failed' },
{ title: '行动01', taskStatus: '已完成', taskLogo: 'task-completed' },
])
const range = ref([]); // 日期时间筛选框
const taskDetailShow = ref(false);
const taskData = ref({})
const tabIndex = ref(0) // tab页切换
const emit = defineEmits(['taskDetailsC']) // 定义子组件发送给父组件的函数
// 回车搜索
const onEnter = () => {
}
// tab页切换
const tabChange = (value) => {
tabIndex.value = value;
}
// 任务类型选择
const taskChange = (value) => {
taskRadioValue.value = value
}
const taskDataChange = (value) => { // 任务点击
taskDetailShow.value = true;
taskData.value = value;
emit('taskDetailsC', value); // 给父组件传参
}
</script>
<style scoped lang="less">
.task-analysis {
width: 100%;
height: 100%;
z-index: 500;
color: #fff;
padding: 15px;
display: flex;
flex-direction: column;
// 任务信息
.task-information {
width: 100%;
height: 260px;
background: url('../../assets/icon/task-data-bg.png') no-repeat;
background-size: cover;
margin-bottom: 25px;
display: flex;
justify-content: space-around;
.list-item {
width: 115px;
height: 100%;
.item-head {
width: 95px;
height: 95px;
border: 3px solid #ec9004;
border-radius: 50%;
margin: 0 auto;
margin-top: 30px;
position: relative;
.item-logo {
width: 81px;
height: 81px;
background: url('../../assets/icon/map-icon-logo.png');
background-position: -500px -79px;
position: absolute;
left: 50%;
top: 5px;
transform: translate(-50%);
font-size: var(--titleSize);
color: #7fcdff;
line-height: 81px;
text-align: center;
}
}
.item-body {
padding: 10px 0;
.item-content {
margin-bottom: 10px;
font-size: var(--fontSize);
display: flex;
align-items: center;
.item-point {
height: 10px;
width: 10px;
margin-right: 10px;
border-radius: 5px;
display: inline-block;
}
.item-title {
color: #73c7ec;
margin-right: 20px;
font-family: Noto Sans S Chinese;
}
.item-value {
font-family: 'DDIN';
}
}
}
}
}
/* 垂直排列子元素 */
.task-body {
padding: 10px;
// flex-grow: 1; /* 让 div 填充剩余空间 */
height: calc(100vh - 225px);
box-sizing: border-box;
overflow-y: auto;
}
.task-list-item {
padding: 10px;
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
margin: 15px;
position: relative;
overflow: hidden;
&:hover {
background: rgba(0, 98, 217, 0.4);
border: 1px solid;
border-color: #0062d9;
}
.task-status-logo {
width: 132px;
height: 30px;
transform: rotate(45deg);
position: absolute;
top: 12px;
text-align: center;
line-height: 30px;
right: -36px;
}
.task-completed {
background: linear-gradient(134.57deg, #05b89f 14.74%, #007f73 84.95%);
}
.task-uncompleted {
background: linear-gradient(134.57deg, #d09a14 14.74%, #a76e12 84.95%);
}
.task-pendinganalysis {
background: linear-gradient(134.57deg, #117dff 14.74%, #0057b8 84.95%);
}
.task-failed {
background: linear-gradient(134.57deg, #838383 14.74%, #5e5e5e 84.95%);
}
.item-title {
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #ffffff;
font-size: var(--titleSize);
line-height: 24px;
margin-bottom: 10px;
}
.tab-list {
margin-bottom: 10px;
.item-tab {
display: inline-block;
line-height: var(--fontSizes);
padding: 5px 10px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSizes);
border: 1px solid;
border-radius: 4px;
margin-right: 5px;
}
}
.item-content {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSize);
margin-bottom: 10px;
}
.item-footer {
font-size: var(--fontSizes);
color: rgba(255, 255, 255, 0.8);
.item-label {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: rgba(255, 255, 255, 0.8);
font-size: var(--fontSize);
}
.item-date {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: rgba(255, 255, 255, 0.8);
font-size: var(--fontSize);
}
}
}
}
.item-label {
font-family: 'Noto Sans S Chinese';
font-size: 15px;
margin-right: 5px;
}
.item-value {
font-family: 'DDIN';
}
.item-tab {
font-size: var(--fontSizes);
line-height: var(--fontSize);
padding: 2px 10px;
margin-right: 5px;
border: 1px solid;
border-radius: 4px;
}
.sortBtn {
font-size: var(--fontSizes);
padding: 8px 10px;
}
</style>
@@ -1,335 +0,0 @@
<template>
<div class="task-details">
<!-- 顶部tab页 -->
<div class="tabs-list">
<div class="tabs-item" :class="{ 'item-active': tabIndex === 'enemy' }" @click="tabChange('enemy')">
敌情
</div>
<div class="tabs-item" :class="{ 'item-active': tabIndex === 'friendly' }" @click="tabChange('friendly')">
我情
</div>
<div class="tabs-item" :class="{ 'item-active': tabIndex === 'zz' }" @click="tabChange('zz')">
Z场环境
</div>
</div>
<!-- 敌情 -->
<div class="enemy-content" v-if="tabIndex === 'enemy'">
<EnemySituation :taskId="taskId" :taskDirection="taskDirection" />
</div>
<!-- 我情 -->
<div class="friendly-content" v-if="tabIndex === 'friendly'">
<OurSituation :taskId="taskId" />
</div>
<div class="enemy-content" v-if="tabIndex === 'zz'">
<BattlefieldEnvironment :taskId="taskId" />
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import BattlefieldEnvironment from "./TaskDetails/BattlefieldEnvironment.vue";
import OurSituation from "./TaskDetails/OurSituation.vue";
import EnemySituation from "./TaskDetails/EnemySituation.vue";
import { requestAxiosService } from "@/service/request-axios-service"; // 引入发送异步请求方法
const props = defineProps({
msg: String,
taskId: Number, taskDirection: String
});
const tabIndex = ref("enemy"); // enemy 敌情 friendly 我情 zz战场环境
const pieChartData = [
{ value: 335, name: "直接访问" },
{ value: 310, name: "邮件营销" },
{ value: 234, name: "联盟广告" },
{ value: 135, name: "视频广告" },
{ value: 1548, name: "搜索引擎" },
];
const chartTitle = "访问来源";
const state = reactive({
thinkIndex: 0,
thinkList: [],
chatTask: '',
})
const tabChange = (value) => {
tabIndex.value = value;
};
onMounted(async () => {
// await taskGenerateChat();
})
const taskGenerateChat = async () => {
state.thinkIndex = 0;
state.thinkList = [];
state.chatTask = '';
let scoure = new AbortController();
const response = await fetch(`http://ch1.b.uat.4.cn/qq_b1_chat/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt: '赖清德',
}),
signal: scoure.signal,
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ detail: 'Unknown error' }));
throw new Error(errorData.detail || `HTTP error ${response.status}`);
}
if (!response.body) {
throw new Error('Response body is null');
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
const lastLine = lines.pop();
buffer = lastLine || '';
for (let line of lines) {
if (!line.startsWith('data: ')) continue;
try {
// 解析SSE数据
// line = line.replace(/'/g, '"').trim();
const parsedData = JSON.parse(line.slice(6));
const { event, data } = parsedData;
// 处理不同类型的事件
switch (event) {
case 'task_created': {
// taskIdRef.current = data.task_id;
break;
}
case 'tool_call': {
handleToolCall(data);
console.log('任务分析', state.thinkList, state.chatTask);
break;
}
case 'message': {
// 处理消息事件
break;
}
case 'done': {
break;
}
case 'references': {
break;
}
default:
console.log(`未知事件类型: ${event}`, data);
}
} catch (error) {
console.error('JSON解析错误:', error);
console.log('原始数据:', line.slice(6));
}
}
}
}
// 处理工具调用事件
const handleToolCall = (toolData) => {
// 生产环境中可以移除或使用条件日志
// console.log('工具调用:', toolData?.tool_name);
if (state.thinkList.length > 0) {
state.thinkList[state.thinkIndex].thinkLoading = false;
state.thinkIndex = state.thinkList.length - 1;
}
switch (toolData.tool_name) {
case 'show_text': {
const chatText = toolData.tool_input.text || '';
if (chatText.includes('<think>')) {
// 添加新的思考过程
const thinkContent = chatText.replace('<think>', '').replace('</think>', '');
state.thinkList.push({
thinkTitle: thinkContent,
thinkLoading: false,
thinkSearch: null,
searchData: null,
searchResult: null,
list: [],
});
} else {
// 更新总结回答
state.chatTask += chatText;
}
break;
}
case 'google_search': {
if (toolData.tool_input.result) {
state.thinkList[state.thinkIndex].thinkLoading = false;
try {
let result = JSON.parse(toolData.tool_input.result);
let urlMap = new Map();
result.organic = result.organic.filter((item) => {
let url = item.title;
// 如果urlMap中还没有这个url,则添加到Map中并返回true
if (!urlMap.has(item.title)) {
urlMap.set(item.title, true);
return true;
}
return false;
});
console.log('---google_search---', result);
// 更新思考过程的搜索标题和参考列表
if (state.thinkList[state.thinkIndex]) {
state.thinkList[state.thinkIndex].searchTitle = result.query || '';
state.thinkList[state.thinkIndex].list = (result.organic || []).map((item) => ({
title: item.title || '',
content: item.content || item.summary,
url: item.recUuid || ''
}));
}
} catch (error) {
console.error('本地搜索结果JSON解析错误:', error);
// 生产环境中可以移除原始数据日志
// console.error('原始数据:', toolData.tool_input.result);
}
} else if (toolData.tool_input.q) {
state.thinkList[state.thinkIndex].thinkLoading = true;
state.thinkList[state.thinkIndex].thinkSearch = toolData.tool_input.q;
}
break;
}
case 'getTaskLikeTop': {
if (toolData.tool_input.result) {
state.thinkList[state.thinkIndex].thinkLoading = false;
try {
let result = JSON.parse(toolData.tool_input.result);
console.log('---getTaskLikeTop---', result);
// 更新思考过程的搜索标题和参考列表
if (state.thinkList[state.thinkIndex]) {
state.thinkList[state.thinkIndex].searchTitle = result.query || '';
state.thinkList[state.thinkIndex].list = result.data;
}
} catch (error) {
console.error('本地搜索结果JSON解析错误:', error);
// 生产环境中可以移除原始数据日志
// console.error('原始数据:', toolData.tool_input.result);
}
} else if (toolData.tool_input.keyword) {
state.thinkList[state.thinkIndex].thinkLoading = true;
state.thinkList[state.thinkIndex].thinkSearch = toolData.tool_input.keyword;
}
break;
}
case 'scrape_and_extract_info': {
console.log('toolData.tool_input.info_to_extract', toolData.tool_input);
if (toolData.tool_input.info_to_extract) {
state.thinkList[state.thinkIndex].searchData = toolData.tool_input;
} else if (toolData.tool_input.result) {
state.thinkList[state.thinkIndex].searchResult = JSON.parse(toolData.tool_input.result);
}
break;
}
case 'getTaskinfo': {
console.log('toolData.tool_input.info_to_extract', toolData.tool_input)
if (toolData.tool_input.info_to_extract) {
state.thinkList[state.thinkIndex].searchData = toolData.tool_input;
} else if (toolData.tool_input.result) {
state.thinkList[state.thinkIndex].searchResult = toolData.tool_input.result;
}
break;
}
default:
// console.log(`未知工具类型: ${toolData.tool_name}`);
break;
}
};
</script>
<style scoped lang="less">
.task-details {
z-index: 500;
color: #fff;
padding: 10px;
width: 100%;
.t-collapse {
background-color: transparent;
border: 0;
.t-collapse-panel__content {
padding: 0;
}
}
}
// 敌情样式
.enemy-content {
padding: 10px;
box-sizing: border-box;
}
.tab-content {
.item-tab {
display: inline-block;
line-height: var(--fontSize);
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSize);
background: rgba(0, 98, 217, 0.3);
border-radius: 4px;
padding: 5px 10px;
margin: 5px;
cursor: pointer;
.item-count {
font-family: "DDIN";
}
&:hover {
background: #2b86fe;
}
}
.item-active {
background: #2b86fe;
}
}
//我情样式
.friendly-content {
padding: 10px;
box-sizing: border-box;
}
:deep(.t-swiper__arrow-right) {
width: 20px;
height: 28px;
background: rgba(255, 255, 255, 0.15);
border-radius: 4px;
right: -28px;
}
:deep(.t-swiper__arrow-left) {
width: 20px;
height: 28px;
background: rgba(255, 255, 255, 0.15);
border-radius: 4px;
left: -28px;
}
:deep(.t-tabs__nav-item) {
.t-size-m {
height: 100% !important;
line-height: 18px !important;
}
}
.tabs-item {
width: 33%;
}
</style>
@@ -1,356 +0,0 @@
<template>
<div class="battlefield-environment">
<div class="title-content-item margin-bottom-10">信息环境</div>
<div ref="platformPieRef" class="pie-content"></div>
<div class="legendList">
<div class="item" v-for="(item, index) in infoData">
<div class="kuai" :style="{ backgroundColor: colorList[index % colorList.length] }"></div>
<span class="name">{{ item.name }}</span>
<span>{{ item.value }}</span>
<span>条</span>
</div>
</div>
<t-tabs v-model="informationTabIndex" @change="informationTabChange">
<t-tab-panel v-for="(item, index) in informationList" :key="index" :value="index" :label="item.title"
:destroy-on-hide="false">
<div class="environment-content">
<ul>
<li v-for="(items, indexs) in item.content" :key="indexs" v-html="processedText(items)"
@click="renwenHandel($event)">
</li>
</ul>
</div>
</t-tab-panel>
</t-tabs>
<div class="title-content-item margin-bottom-10">人文环境</div>
<t-tabs v-model="humanisticTabIndex">
<t-tab-panel v-for="(item, index) in humanisticList" :key="index" :value="index" :label="item.title"
:destroy-on-hide="false">
<div class="environment-content">
<ul>
<li v-for="(items, indexs) in item.content" :key="indexs" v-html="processedText(items)"
@click="renwenHandel($event)">
</li>
</ul>
</div>
</t-tab-panel>
</t-tabs>
<t-dialog v-model:visible="docDetailsStatus" attach="body" header="详情" width="1200" :confirm-on-enter="true"
:on-confirm="() => docDetailsStatus = false">
<DocDetails :docData="docData" />
</t-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import * as echarts from "echarts";
import { requestAxiosService } from "@/service/request-axios-service"; // 引入发送异步请求方法
import DocDetails from "../details-dialog/DocDetails.vue";
const service = requestAxiosService;
const props = defineProps({
msg: String,
taskId: Number,
});
const docDetailsStatus = ref(false)
const docData = ref({})
const platformPieRef = ref(null);
const informationTabIndex = ref(0);
const informationList = ref([
]);
const humanisticTabIndex = ref(1);
const humanisticList = ref([]);
const infoData = ref(null)
const colorList = ['#587bf3', '#b6d634', '#575a7b', '#ffa753', '#0cb3ee', '#ffd10a', '#7acaa4', '#c8cdd5', '#f40']
// tab页切换
const informationTabChange = async (value) => {
console.log('点击了', value);
}
onMounted(() => {
platformPieChange();
getRenWen(1) //信息环境
getRenWen(2) //人文环境
});
const informationListDocs = ref([])
const humanisticListDocs = ref([])
//处理[[数字]]
const processedText = (text) => {
return text.replace(/\[\[(\d+)\]\]/g,
`<span class="num-dot">$1</span>`
);
};
// 获取点击的数字
const renwenHandel = (e) => {
if (e.target.classList.contains('num-dot')) {
let num = e.target.innerText
console.log(num);
docDetailsStatus.value = true
docData.value = humanisticListDocs.value[num]
console.log(humanisticListDocs.value);
console.log(docData.value);
}
};
const getRenWen = (type) => {
service
.get(`${environment["b1ConsumerUrl"]}/cop/queryCombatEnvironment`, {
taskId: props.taskId,
// taskId: 3064,
type: type
}).then(res => {
if (type == 1) {
console.log("信息环境", res.data);
informationListDocs.value = res.data.docs
var list = formattedData(res.data.data)
//删除type项
const deleteIndex = list.findIndex(item => item.title == 'type');
if (deleteIndex > -1) {
const [deletedItem] = list.splice(deleteIndex, 1);
list = [...list];
}
//把概况移到首位
const index = list.findIndex(item => item.title === '概览');
if (index > -1) {
const [item] = list.splice(index, 1);
list.unshift(item);
}
informationList.value = list
} else {
console.log("人文环境", res.data);
humanisticListDocs.value = res.data.docs
var list = formattedData(res.data.data)
//删除type项
const deleteIndex = list.findIndex(item => item.title == 'type');
if (deleteIndex > -1) {
const [deletedItem] = list.splice(deleteIndex, 1);
list = [...list];
}
//把概况移到首位
const index = list.findIndex(item => item.title === '概览');
if (index > -1) {
const [item] = list.splice(index, 1);
list.unshift(item);
}
humanisticList.value = list
}
// xushiText.value = res.data
console.log('informationListDocs', informationListDocs.value)
})
}
const processString = (input) => {
// 使用正则表达式提取数字
const match = input.match(/\d+/);
const number = match ? parseInt(match[0], 10) : 0;
// 处理字符串,去除数字及其周围的括号
const processedString = input.replace(/\平台((\d+)个帖子\)/, '').trim();
return {
name: processedString,
value: Number(number) || 0
};
}
const platformPieChange = async () => {
let data = []
await service
.get(
`${environment['b1ConsumerUrl']}/eventAnalysis/informationEnvironment?taskId=${props.taskId}`
).then(res => {
console.log(res);
const result = res.data.split('\n');
let arr = result[1].replaceAll(' ', '').split(',')
arr.forEach(i => {
data.push(processString(i))
});
console.log(data);
})
let chartPie = echarts.init(platformPieRef.value);
infoData.value = data
let count = 0
data.forEach(i => {
count += i.value
})
const option = {
color: colorList,
// 标题
title: {
left: 'center'
},
// 提示框
tooltip: {
trigger: 'item',
formatter: '{a}<br/>{b}: {c} ({d}%)' // 显示系列名、数据名、值和百分比
},
graphic: [{
type: 'text',
left: '80px',
top: 'center',
style: {
text: '账号总数 \n' + count,
fontSize: 16,
textAlign: 'center',
fill: '#fff',
width: '50%',
height: '50%'
}
}],
// 系列配置
series: [
{
name: '数据类型', // 系列名称
type: 'pie', // 类型为饼图
radius: ['50%', '60%'], // 环形内外半径(控制环形宽度)
center: ['30%', '50%'], // 图表中心位置
data: data,
// 设置扇形之间的间隔(通过边框模拟)
itemStyle: {
borderRadius: 0, // 扇形圆角(可选)
borderColor: 'rgba(10, 36, 64, 0.8)', // 分隔线颜色(白色)
borderWidth: 5 // 分隔线宽度(控制间隔大小)
},
// 分隔线配置
splitLine: {
length: 15, // 分隔线长度(从外圆到标签的长度)
lineStyle: { // 分隔线样式
color: 'rgba(255, 255, 255, 0.5)', // 透明度
width: 2
}
},
// 标签配置
label: {
show: false,
formatter: '{b}: {c} ({d}%)', // 标签格式(名称、值、百分比)
position: 'outside', // 标签位置(外部)
color: '#fff' // 标签颜色
},
}
]
};
chartPie.setOption(option);
};
const formattedData = (list) => {
return Object.entries(list).map(([title, content]) => ({
title,
content
}));
}
</script>
<style>
.num-dot {
border-radius: 50%;
display: inline-block;
height: 20px;
width: 20px;
font-size: 12px;
background-color: rgba(0, 12, 28, 0.2);
text-align: center;
line-height: 20px;
cursor: pointer;
}
</style>
<style scoped lang="less">
.battlefield-environment {
position: relative;
z-index: 500;
color: #fff;
padding: 10px;
height: calc(100vh - 200px);
overflow-y: auto;
.pie-content {
width: 100%;
height: 300px;
background: url("@/assets/icon/账号环形图.png");
background-repeat: no-repeat;
background-size: 134px;
background-position: 43px 83px;
}
.environment-content {
line-height: 26px;
font-family: Noto Sans S Chinese;
color: #ffffff;
font-size: var(--fontSize);
padding: 10px;
.item-title {
font-size: var(--fontSize);
font-family: Noto Sans S Chinese;
color: #ffffff;
font-weight: 700;
}
ul {
margin: 10px 25px;
li {
margin-bottom: 5px;
.item-label {
font-size: var(--fontSize);
font-family: Noto Sans S Chinese;
color: #ffffff;
font-weight: 500;
.item-value {
font-size: var(--fontSize);
font-family: Noto Sans S Chinese;
color: #ffffff;
font-weight: 300;
}
}
}
}
}
}
.legendList {
position: absolute;
right: 10px;
top: 100px;
display: flex;
gap: 14px;
height: 200px;
overflow-y: auto;
flex-direction: column;
.kuai {
width: 6px;
height: 6px;
}
.item {
// width: 114px;
height: 22px;
// background: linear-gradient(90deg, rgba(47, 100, 173, 0.4) 0%, rgba(0, 82, 119, 0.04) 100%);
display: flex;
gap: 10px;
align-items: center;
justify-content: space-between;
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
padding: 0 5px;
img {
width: 8px;
height: 8px;
}
.name {
flex: 1;
flex-shrink: 0;
}
}
}
</style>
@@ -1,773 +0,0 @@
<template>
<t-space class="body" direction="vertical">
<t-dialog v-model:visible="targetModelStatus" attach="body" header="任务重要目标" width="1200" :confirm-on-enter="true"
:on-confirm="() => targetModelStatus = false">
<!-- <div class="targetModel">
<div class="title">
<div class="item">
<span>人物</span>
</div>
<div class="item">
<span>组织</span>
</div>
</div>
<div class="contentBox">
<div class="human">
<div v-for="(item, index) in targetList" :key="index">
<div class="targetItem">
<div class="img">
<img :src="item.src" alt="" />
</div>
<div class="info">
<div class="name ">
{{ item.name }}
</div>
<div class="reason ">
{{ item.reason }}
</div>
</div>
</div>
</div>
</div>
<div class="organization">
<div v-for="(item, index) in organization" :key="index">
<div class="targetItem">
<div class="img">
<img :src="item.src" alt="" />
</div>
<div class="info">
<div class="name ">
{{ item.name }}
</div>
<div class="reason ">
{{ item.reason }}
</div>
</div>
</div>
</div>
</div>
</div>
</div> -->
<div class="zl">
<div class="fliter">
<div class="item">
<div class="label">
类型:
</div>
<div class="content">
<t-select v-model="fliterType">
<t-option key="human" label="人物" value="human" />
<t-option key="organization" label="组织" value="organization"></t-option>
</t-select>
</div>
</div>
<div class="item">
<div class="label">
排序:
</div>
<div class="content">
<t-select v-model="fliterSort" @change="sortChange">
<t-option key="2" label="任务相关性" value="2" />
<t-option key="3" label="目标重要性" value="3"></t-option>
<t-option key="7" label="传播可控度" value="7" />
</t-select>
</div>
</div>
</div>
<div class="contentBox">
<div v-for="(item, index) in fliterType === 'human' ? targetList : organization" :key="index">
<div class="targetItem">
<div class="img">
<img :src="item.src" alt="" />
</div>
<div class="info">
<div class="name ">
{{ item.name }}
</div>
<div class="reason ">
{{ item.reason }}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- organization -->
</t-dialog>
<t-collapse v-model="enemyCurrentItem" :expand-icon-placement="'right'" @change="enemyCurrentItemChange">
<t-collapse-panel value="0" :expandIcon="false">
<template #header>
<div class="title-content-item margin-bottom-10" @click.stop="noChange">
<span>
任务重要目标
</span>
<div class="more" @click.stop="openTargetMmdeol">
更多>>
</div>
</div>
</template>
<template #default>
<div class="task-target margin-bottom-10">
<t-loading :loading="targetLoading" show-overlay>
<div class="target-list">
<t-swiper class="tdesign-demo-block--swiper" :duration="300" :interval="2000"
:navigation="{ placement: 'outside' }">
<t-swiper-item v-for="(item, index) in targetList" :key="index">
<div class="item-content flex-between-center" @click="targetListChange(item)">
<div class="item-left">
<img :src="item.src" alt=""
onerror="this.src = 'https://present.a.uat.4.cn/gene/image/img/schema_entity/human@basic_human'" />
</div>
<div class="item-right">
<div class="item-title text-ellipsis ellipsis1">
{{ item.name }}
</div>
<div class="item-footer text-ellipsis ellipsis3">
{{ item.reason }}
</div>
</div>
</div>
</t-swiper-item>
</t-swiper>
</div>
</t-loading>
</div>
</template>
</t-collapse-panel>
<CommonInfoDialog :modelData="modelData" :modelStatus="modelStatus" @updateStatus="v => modelStatus = v"
@config="() => modelStatus = false" />
<t-collapse-panel value="1">
<template #header>
<div class="title-content-item margin-bottom-10">任务关键事件</div>
</template>
<template #default>
<div class="task-event">
<!-- 任务类型 -->
<div class="tab-content">
<template v-for="(item, index) in tabList">
<span class="item-tab" :class="{ 'item-active': tabListIndex == index }" v-if="item.count > 0"
:key="index" @click="tabListIndex = index">
{{ item.topic
}}<span class="item-count">({{ item.count }})</span>
</span></template>
</div>
<!-- 任务事件 -->
<div class="event-list">
<t-loading :loading="eventLoading" show-overlay>
<template v-if="tabList.length > 0">
<div class="event-item" v-for="(item, index) in tabList[tabListIndex].events" :key="index"
@click="openModel(item)">
<div class="item-time">
{{ item.date }}
</div>
<div class="item-content">
<span class="item-tab" v-if="item.type" :class="{
'tab-green': item.type === '机会',
'tab-red': item.type === '风险',
}">{{ item.type }}</span>
{{ item.title }}
</div>
</div>
<t-empty v-if="!tabList[tabListIndex] || tabList[tabListIndex].events.length === 0" />
</template>
</t-loading>
</div>
</div>
</template>
</t-collapse-panel>
<t-collapse-panel value="2">
<template #header>
<div class="title-content-item margin-bottom-10">相关对手叙事</div>
</template>
<template #default>
<div class="narrate-content">
<div class="narrate-list">
<t-loading :loading="narrateLoading" show-overlay>
<div class="item-content" v-for="(item, index) in narrateList" :key="index"
@click="openXushiModel(item)">
<div class="item-title">
{{ item.topic }}
</div>
<div class="item-footer">
<!-- <span class="item-tab tab-blue" v-for="(aItem, aIndex) in item.tabList" :key="aIndex">{{ aItem
}}</span> -->
<span class="item-tab tab-blue">{{ item.tabList[0]
}}</span>
</div>
</div>
<t-empty v-if="narrateList.length === 0" />
</t-loading>
</div>
</div>
</template>
</t-collapse-panel>
</t-collapse>
<t-dialog v-model:visible="xushiModelStatus" attach="body" header="叙事" width="1000" :confirm-on-enter="true"
:on-confirm="() => xushiModelStatus = false">
<div class="xushiContent">
<template>
</template>
<div class="title-content-item margin-bottom-10">
意图
</div>
<div class="title">
{{ xushiItem['intention'] || '暂无' }}
</div>
<div class="title-content-item margin-bottom-10">
摘要
</div>
<div class="yitu"> {{ xushiItem['summary'] || '暂无' }}</div>
<div class="title-content-item margin-bottom-10">
话题
</div>
<div class="content">
{{ xushiItem['topic'] || '暂无' }}
</div>
<div class="title-content-item margin-bottom-10">
叙事主体
</div>
<div class="content">
{{ xushiItem['entity'] || '暂无' }}
</div>
<div class="title-content-item margin-bottom-10">
话题类型
</div>
<div class="tag" v-if="xushiItem['topicType']">
<div class="item" v-for="i in xushiItem['topicType'].split('、')">
{{ i }}
</div>
</div>
</div>
</t-dialog>
</t-space>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { requestAxiosService } from "@/service/request-axios-service"; // 引入发送异步请求方法
import { useStore } from 'vuex';
import CommonInfoDialog from "@/components/dialog/CommonInfoDialog.vue"
const service = requestAxiosService;
const props = defineProps({
msg: String,
taskId: Number,
taskDirection: String
});
const xushiModelStatus = ref(false)
const xushiItem = ref({})
const xushiText = ref('');
const openXushiModel = d => {
xushiItem.value = d
console.log(d);
xushiModelStatus.value = true
}
const targetModelStatus = ref(false)
const openTargetMmdeol = () => {
targetModelStatus.value = true
}
const modelData = ref({})
const modelStatus = ref(false)
const openModel = d => {
console.log(d);
modelStatus.value = true
modelData.value = d
}
const targetLoading = ref(false);
const eventLoading = ref(false);
const narrateLoading = ref(false);
const store = useStore();
onMounted(() => {
getTargetData();
getEventData();
getNarrateData();
});
const organization = ref([])
const fliterType = ref('human')
const fliterSort = ref('2')
const getTargetData = () => {
if (props.taskId) {
const parmy = {
taskId: props.taskId,
sortType: fliterSort.value
};
targetLoading.value = true;
service
.get(
`${environment["b1ConsumerUrl"]}/eventAnalysis/targetSummaryList`,
parmy
)
.then((res) => {
console.log("任务检索", res);
targetLoading.value = false
let result = res.data;
result.human.forEach((item) => {
item.src =
environment["aImagePath"] +
(item.image_path
? item.image_path?.replace(".", "")
: item.image_path?.replace(".", ""));
});
result.organization.forEach((item) => {
item.src =
environment["aImagePath"] +
(item.image_path
? item.image_path?.replace(".", "")
: item.image_path?.replace(".", ""));
});
targetList.value = result.human;
organization.value = result.organization
store.commit('setTargetList', [...result.human, ...result.organization]);
});
}
};
const sortChange = d => {
console.log(d);
console.log(fliterSort.value);
getTargetData()
}
const tabList = ref([]);
const tabListIndex = ref(0)
// 获取关键事件
const getEventData = () => {
if (props.taskId) {
const parmy = {
taskId: props.taskId,
direction: props.taskDirection,
// discriminant: "热点",
};
eventLoading.value = true;
service
.get(
`${environment["b1ConsumerUrl"]}/cop/copKeyEvents`,
parmy
)
.then((res) => {
eventLoading.value = false;
console.log("热点事件", res);
if (res.data.length > 0) {
let result = res.data;
tabList.value = result;
}
});
}
};
const getNarrateData = () => {
if (props.taskId) {
const parmy = {
taskId: props.taskId,
};
narrateLoading.value = true;
service
.get(
`${environment["b1ConsumerUrl"]}/eventAnalysis/opponentNarrative`,
parmy
)
.then((res) => {
console.log("叙事", res);
narrateLoading.value = false;
let result = res.data;
narrateList.value = result;
narrateList.value.forEach((item) => {
item.tabList = item.topicType.split('、');
})
console.log(narrateList.value)
});
}
};
const count = ref(0);
const noChange = () => { };
const targetList = ref([]);
const enemyCurrentItem = ref(["0", "1", "2"]); //折叠面板展开索引
const enemyCurrentItemChange = (value) => {
console.log(value);
};
const narrateList = ref([]);
const targetListChange = (item) => {
console.log('点击了', item);
store.commit('setTargetData', item)
}
</script>
<style scoped lang="less">
.zl {
.fliter {
width: 100%;
display: flex;
padding: 10px 0;
.item {
width: 50%;
display: flex;
align-items: center;
}
}
.contentBox {
height: 60vh;
overflow-y: auto;
}
}
.targetModel {
.title {
font-size: 24px;
font-weight: 700;
display: flex;
color: #fff;
.item {
width: 50%;
}
}
.contentBox {
display: flex;
.human,
.organization {
width: 50%;
height: 65vh;
overflow-y: auto;
}
}
}
.targetItem {
border-bottom: 1px solid #777;
display: flex;
gap: 10px;
align-items: center;
.img {
width: 80px;
height: 80px;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.info {
flex: 1;
flex-shrink: 0;
.name {
line-height: 36px;
font-family: Noto Sans S Chinese;
color: #ffffff;
font-size: var(--titleSize);
}
.reason {
height: 81px;
line-height: 26px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSize);
}
}
}
.body {
width: 100%;
height: calc(100vh - 200px);
overflow-y: auto;
}
.t-collapse {
background-color: transparent;
border: 0;
.t-collapse-panel__content {
padding: 0;
}
}
:deep(.t-collapse-panel__wrapper .t-collapse-panel__content) {
padding: 0;
}
.title-content-item {
width: 100%;
display: flex;
justify-content: space-between;
.more {
font-size: 14px;
text-align: right;
width: 100px;
}
}
:deep(.t-collapse-panel__wrapper .t-collapse-panel__body) {
background-color: transparent;
border-bottom: 0px;
}
:deep(.t-collapse-panel__wrapper .t-collapse-panel__header) {
padding: 0;
border-bottom: 0px;
}
.task-target {
.target-list {
padding: 0 30px;
.item-content {
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
padding: 10px;
.item-left {
width: 70px;
height: 70px;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
vertical-align: middle;
object-fit: cover;
}
}
.item-right {
flex: 1;
margin-left: 10px;
.item-title {
line-height: 36px;
font-family: Noto Sans S Chinese;
color: #ffffff;
font-size: var(--titleSize);
}
.item-footer {
height: 81px;
line-height: 26px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSize);
}
}
}
}
}
// 任务事件
.task-event {
.tab-content {
.item-tab {
display: inline-block;
line-height: var(--fontSize);
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSize);
background: rgba(0, 98, 217, 0.3);
border-radius: 4px;
padding: 5px 10px;
margin: 5px;
cursor: pointer;
.item-count {
font-family: "DDIN";
}
&:hover {
background: #2b86fe;
}
}
.item-active {
background: #2b86fe;
}
}
// 事件样式
.event-list {
padding: 10px 15px;
box-sizing: border-box;
.event-item {
margin-bottom: 10px;
padding-left: 25px;
position: relative;
&::after {
content: "";
display: block;
width: 12px;
height: 12px;
background: #ffffff;
border: 2px solid;
border-color: #45c3f3;
border-radius: 50px;
position: absolute;
top: 0px;
left: 0px;
}
&::before {
content: "";
display: block;
width: 1px;
height: 100%;
background: #2c66a7;
position: absolute;
top: var(--fontSize);
left: 6px;
}
.item-time {
line-height: 20px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: rgba(255, 255, 255, 0.6);
margin-bottom: 10px;
}
.item-tab {
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSizes);
line-height: var(--fontSize);
padding: 5px 10px;
border: 1px solid;
border-radius: 4px;
margin-right: 5px;
}
.item-content {
line-height: 30px;
font-family: Noto Sans S Chinese;
color: #ffffff;
font-size: var(--fontSize);
}
}
}
}
// 对手叙事样式
.narrate-content {
padding: 10px;
.narrate-list {
.item-content {
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
padding: 10px;
margin-bottom: 15px;
&:hover {
background: rgba(0, 98, 217, 0.2);
border-color: rgba(85, 166, 254, 0.1);
}
.item-title {
line-height: 26px;
font-family: Noto Sans S Chinese;
color: #ffffff;
font-size: var(--fontSize);
margin-bottom: 10px;
}
.item-footer {
.item-tab {
line-height: var(--fontSize);
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSizes);
padding: 5px 10px;
border: 1px solid;
border-radius: 4px;
display: inline-block;
margin-right: 10px;
margin-bottom: 10px;
}
}
}
}
}
.xushiContent {
color: #fff;
font-size: 16px;
h2 {
color: #fff;
margin: 20px 0 10px;
}
.title {
font-size: 18px;
font-weight: 700;
}
.yitu {
margin: 0px 0;
}
.tag {
display: flex;
flex-wrap: wrap;
gap: 10px;
.item {
line-height: var(--fontSize);
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSizes);
padding: 5px 10px;
border: 1px solid;
border-radius: 4px;
display: inline-block;
background: rgba(0, 121, 254, 0.1);
border-color: #0079fe !important;
}
}
.content {
padding: 0px 0;
color: #fff;
}
}
</style>
@@ -1,548 +0,0 @@
<template>
<t-space class="body" direction="vertical">
<t-collapse
v-model="currentItem"
:defaultExpandAll="true"
:expand-icon-placement="'right'"
@change="handlePanelChange"
>
<t-collapse-panel value="0">
<template #header>
<div class="title-content-item">
<div>我方发声口径与叙事</div>
</div>
</template>
<template #default>
<div style="text-align: right; margin-top: 10px">
<t-radio-group
variant="primary-filled"
size="small"
default-value="selfSpeech"
@change="friendlyTopTagChange"
>
<t-radio-button value="selfSpeech">总书记</t-radio-button>
<t-radio-button value="selfConsistents">口径</t-radio-button>
<t-radio-button value="xushi">叙事</t-radio-button>
</t-radio-group>
</div>
<div class="target-list">
<CommonInfoDialog
:modelData="infoModelData"
:modelStatus="infoModelStatus"
:remarkRule="{
trendSpeechTime: '时间',
trendConsistentTime: '时间',
}"
@updateStatus="(v) => (infoModelStatus = v)"
@config="() => (infoModelStatus = false)"
/>
<template v-if="friendlyTopTagFlag !== 'xushi'">
<t-loading :loading="kjLoading" show-overlay>
<t-empty v-if="xuAndKJ[friendlyTopTagFlag]?.length === 0" />
<div
v-for="(value, vIndex) in xuAndKJ[friendlyTopTagFlag]"
:key="vIndex"
@click="openInfoModel(value)"
class="target-list-story-item"
>
<!-- flex-between-center -->
<div
class="target-list-story-item-top text-ellipsis ellipsis1"
>
<div>
{{
value.trendConsistentTitleMini ||
value.trendConsistentTitle ||
value.trendSpeechTitleMini ||
value.trendSpeechTitle
}}
</div>
</div>
<div class="item-line"></div>
<div
class="target-list-story-item-content text-ellipsis ellipsis3"
>
<div>
{{
value.trendConsistentContent ||
value.trendConsistentLike ||
value.trendSpeechContent ||
value.trendSpeechLike
}}
</div>
</div>
<div
class="target-list-story-item-bottom flex-between-baseline"
>
<div v-if="value.xwt">发生主体:{{ value.xwt }}</div>
<div>
发生时间:{{
value.trendConsistentTime || value.trendSpeechTime
}}
</div>
</div>
</div>
</t-loading>
</template>
<!-- </t-swiper-item>
</t-swiper> -->
</div>
<div class="target-list" v-if="friendlyTopTagFlag == 'xushi'">
<!-- <Empty /> -->
<!-- {{ xushiText }} -->
<t-loading :loading="narrateLoading" show-overlay>
<t-empty v-if="!xushiText" />
<div
class="target-list-story-item"
v-for="value in xushiText"
@click="openXushiModel(value)"
>
<!-- <CodeData :markdown="xushiText" /> -->
{{ value["标题"] }}
</div>
</t-loading>
<t-dialog
v-model:visible="xushiModelStatus"
attach="body"
header="叙事"
width="600"
:confirm-on-enter="true"
:on-confirm="() => (xushiModelStatus = false)"
>
<div class="xushiContent">
<div class="title-content-item margin-bottom-10">标题</div>
<div class="title" v-if="xushiItem['标题']">
{{ xushiItem["标题"] }}
</div>
<div class="title-content-item margin-bottom-10">意图</div>
<div class="yitu" v-if="xushiItem['意图']">
{{ xushiItem["意图"] }}
</div>
<div class="title-content-item margin-bottom-10">标签</div>
<div class="tag" v-if="xushiItem['标签']">
<div class="item" v-for="i in xushiItem['标签']">
{{ i }}
</div>
</div>
<div class="title-content-item margin-bottom-10">内容</div>
<div class="content" v-if="xushiItem['内容']">
{{ xushiItem["内容"] }}
</div>
</div>
</t-dialog>
</div>
</template>
</t-collapse-panel>
<t-collapse-panel value="1">
<template #header>
<div class="title-content-item">我方zz资源与力量</div>
</template>
<template #default>
<div style="text-align: right; margin-top: 10px">
<t-radio-group
variant="primary-filled"
size="small"
default-value="ziyuan"
@change="resourcesAndPowerChange"
>
<t-radio-button value="ziyuan">资源</t-radio-button>
<t-radio-button value="liliang">力量</t-radio-button>
</t-radio-group>
</div>
<div class="resourcesAndPower flex-between-center">
<!-- <div class="resourcesAndPower-left">
<div class="resourcesAndPower-left-tag" @click="resourcesAndPowerChange('ziyuan')" :class="{
'resourcesAndPower-left-tag-on':
resourcesAndPowerFlag == 'ziyuan',
}">
资源
</div>
<div class="resourcesAndPower-left-tag" @click="resourcesAndPowerChange('liliang')" :class="{
'resourcesAndPower-left-tag-on':
resourcesAndPowerFlag == 'liliang',
}">
力量
</div>
</div> -->
<div class="resourcesAndPower-right">
<div v-if="resourcesAndPowerFlag == 'ziyuan'">
<!-- <PieChart :taskId="taskId" /> -->
<Resources :taskId="taskId" />
</div>
<div v-if="resourcesAndPowerFlag == 'liliang'">
<Power />
</div>
</div>
</div>
</template>
</t-collapse-panel>
<t-collapse-panel value="2">
<template #header>
<div class="title-content-item">上级要求</div>
</template>
<template #default>
<!-- <CodeData :markdown="upYaopqiuText" /> -->
<div style="cursor: pointer" @click="openReqModel()">
{{ upYaopqiuText.slice(0, 100) }}
</div>
</template>
</t-collapse-panel>
<t-collapse-panel value="3">
<template #header>
<div class="title-content-item">相关方案预案</div>
</template>
<template #default>
<div
class="planItem"
v-for="i in planList"
:key="i"
@click="openPlanModel(i)"
>
{{ i.trendPlanName }}
</div>
</template>
</t-collapse-panel>
</t-collapse>
<t-dialog
v-model:visible="planModelStatus"
attach="body"
header="相关方案预案"
width="1000"
:confirm-on-enter="true"
:on-confirm="() => (planModelStatus = false)"
>
<div class="planModel">
<CodeData :markdown="planModelData.trendPlanGoal" />
</div>
</t-dialog>
<t-dialog
v-model:visible="reqModelStatus"
attach="body"
header="上级要求"
width="1000"
:confirm-on-enter="true"
:on-confirm="() => (reqModelStatus = false)"
>
<div class="planModel">
<CodeData :markdown="upYaopqiuText" />
</div>
</t-dialog>
</t-space>
</template>
<script setup>
import { ref, onMounted, reactive } from "vue";
import PieChart from "../views/PieChart.vue";
import Power from "./Power.vue";
import CodeData from "../markdown/CodeData.vue";
import CommonInfoDialog from "@/components/dialog/CommonInfoDialog.vue";
import { requestAxiosService } from "@/service/request-axios-service"; // 引入发送异步请求方法
import Resources from "./component/Resources.vue";
const service = requestAxiosService;
import Empty from "../shared/Empty.vue";
import axios from "@/utils/request";
const props = defineProps({
msg: String,
taskId: Number,
});
onMounted(() => {
getKouJing();
getPlan();
upYaopqiu();
getXushi();
});
const infoModelStatus = ref(false);
const infoModelData = ref({});
const openInfoModel = (d) => {
console.log(d);
infoModelData.value = {
title: d.trendSpeechTitle || d.trendConsistentTitle,
content: d.trendSpeechContent || d.trendConsistentContent,
...d,
};
infoModelStatus.value = true;
};
const xushiModelStatus = ref(false);
const xushiItem = ref({});
const xushiText = ref("");
const narrateLoading = ref(false);
const reqModelStatus = ref(false);
const reqModelData = ref({});
const openReqModel = (d) => {
reqModelStatus.value = true;
reqModelData.value = d;
};
const planModelStatus = ref(false);
const planModelData = ref({});
const openPlanModel = (d) => {
planModelStatus.value = true;
planModelData.value = d;
};
//我情内容
const friendlyTopTagFlag = ref("selfSpeech");
const friendlyTopTagChange = (flag) => {
friendlyTopTagFlag.value = flag;
};
const upYaopqiuText = ref("");
const openXushiModel = (d) => {
xushiItem.value = d;
xushiModelStatus.value = true;
};
//获取叙事
const getXushi = () => {
narrateLoading.value = true;
service
.get(`${environment["b1ConsumerUrl"]}/cop/querySelfNarrative`, {
taskId: props.taskId,
// taskId: 3038
})
.then((res) => {
console.log(res);
narrateLoading.value = false;
xushiText.value = res.data;
});
};
//获取上级要求
const upYaopqiu = () => {
service
.get(`${environment["b1ConsumerUrl"]}/cop/querySuperiorRequirements`, {
taskId: props.taskId,
})
.then((res) => {
console.log(res);
upYaopqiuText.value = res.data.replaceAll("\\n", "\n");
});
};
const xuAndKJ = ref({ selfSpeech: [], selfConsistents: [] }); // 口径接口
const kjLoading = ref(false);
const getKouJing = () => {
console.log({ taskId: props.taskId });
kjLoading.value = true;
service
.get(`${environment["b1ConsumerUrl"]}/cop/querySelfSpeechConsistents`, {
taskId: props.taskId,
})
.then((res) => {
kjLoading.value = false;
console.log("口径接口", res);
xuAndKJ.value = res.data;
});
};
const planList = ref([]);
const getPlan = () => {
service
.get(`${environment["b1ConsumerUrl"]}/trend/getTrendPlan`, {
taskId: props.taskId,
})
.then((res) => {
console.log(res.data);
planList.value = res.data;
});
};
const resourcesAndPowerFlag = ref("ziyuan");
const resourcesAndPowerChange = (flag) => {
resourcesAndPowerFlag.value = flag;
};
const currentItem = ref(["0"]); //折叠面板展开索引
const handlePanelChange = (value) => {
console.log(value);
};
</script>
<style scoped lang="less">
.planItem {
padding: 10px;
}
.body {
width: 100%;
height: calc(100vh - 200px);
overflow-y: auto;
}
.t-collapse {
background-color: transparent;
border: 0;
.t-collapse-panel__content {
padding: 0;
}
}
:deep(.t-collapse-panel__wrapper .t-collapse-panel__body) {
background-color: transparent;
border-bottom: 0px;
}
:deep(.t-collapse-panel__wrapper .t-collapse-panel__header) {
border-bottom: 0px;
padding: 0;
}
:deep(.t-collapse-panel__wrapper .t-collapse-panel__content) {
padding: 0;
}
.title-content-item {
width: 100%;
display: flex;
justify-content: space-between;
.title-content-item-tab {
font-size: 13px;
color: #f2f2f2;
display: flex;
width: 120px;
height: 20px;
font-weight: 400;
.title-content-item-tab-item {
width: 33%;
line-height: 20px;
text-align: center;
background-color: rgba(28, 51, 60, 1);
cursor: pointer;
}
.title-content-item-tab-item-on {
background-color: rgb(11, 110, 227);
}
}
}
.target-list {
// padding: 0 10px;
:deep(.t-swiper--outside .t-swiper__navigation) {
display: none;
}
.target-list-story-item {
width: 100%;
background-color: rgba(0, 0, 0, 0.196078431372549);
border: none;
border-radius: 7px;
padding: 10px 15px;
margin-top: 10px;
cursor: pointer;
.target-list-story-item-top {
font-weight: 500;
font-size: var(--fontSize);
color: #ffffff;
// margin-bottom: 10px;
}
.target-list-story-item-content {
font-size: var(--fontSizes);
color: #ffffff;
margin-bottom: 10px;
}
.target-list-story-item-bottom {
color: #7f7f7f;
font-size: var(--fontSizes);
}
}
}
.resourcesAndPower {
width: 100%;
margin-top: 5px;
.resourcesAndPower-left {
display: flex;
flex-wrap: wrap;
align-items: center;
width: 80px;
.resourcesAndPower-left-tag {
width: 100%;
background-color: rgba(2, 23, 33, 1);
border-radius: 11px;
font-weight: 700;
font-style: normal;
font-size: var(--fontSize);
color: #f2f2f2;
line-height: 57px;
text-align: center;
margin-bottom: 10px;
cursor: pointer;
}
.resourcesAndPower-left-tag-on {
background-color: #2b86fe;
}
}
.resourcesAndPower-right {
flex-grow: 1;
background-color: rgba(11, 34, 55, 0.670588235294118);
padding: 10px;
}
}
.xushiContent {
color: #fff;
font-size: 16px;
h2 {
color: #fff;
margin: 20px 0 10px;
}
.title {
font-size: 18px;
font-weight: 700;
}
.yitu {
margin: 0px 0;
}
.tag {
display: flex;
gap: 10px;
.item {
line-height: var(--fontSize);
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSizes);
padding: 5px 10px;
border: 1px solid;
border-radius: 4px;
display: inline-block;
background: rgba(0, 121, 254, 0.1);
border-color: #0079fe !important;
}
}
.content {
padding: 0px 0;
color: #fff;
}
}
.switch {
display: flex;
.item {
width: 50px;
}
}
</style>
@@ -1,94 +0,0 @@
<template>
<div class="power">
<div class="title">
正在执行中{{ tabData.taskProgressCount }}项任务,当前空闲{{ tabData.freeGroupCount }}组力量。
</div>
<t-empty v-if="tabData.groups.length === 0"></t-empty>
<t-tabs v-else :default-value="1">
<t-tab-panel v-for="(item, index) in tabData.groups" :key="index" :value="index + 1" :label="item.name">
<div class="content">
<div v-if="item.progressTask">
<div class="padding-10">
{{ item.progressTask?.length || 0 }}项活跃任务
</div>
<div class="list-item">
<div class="item-title" v-for="(aItem, aIndex) in item.progressTask" :key="aIndex">
{{ aIndex + 1 }}. {{ aItem.name }}
</div>
</div>
</div>
<div v-else>
<t-empty />
</div>
</div>
</t-tab-panel>
</t-tabs>
</div>
</template>
<script>
import { requestAxiosService } from "@/service/request-axios-service"; // 引入发送异步请求方法
export default {
data() {
return {
tabData: {
freeGroupCount: 0,
taskProgressCount: 0,
groups: [],
loading: false
},
}
},
methods: {
getPower() {
let url = environment['b1ConsumerUrl'] + '/cop/copOwnPower';
this.loading = true;
requestAxiosService.get(url, {
// accessToken: localStorage.getItem('accessToken')
accessToken: '123'
}).then(res => {
this.loading = false;
console.log('我方力量', res);
let result = res.data;
if (result) {
this.tabData = result;
}
})
}
},
mounted() {
this.getPower()
}
}
</script>
<style scoped lang='less'>
/* Your styles here */
.power {
width: 370px;
.title {
font-size: 16px;
font-weight: 600;
color: #fff;
}
.content {
.list-item {
width: 100%;
height: 145px;
padding: 10px;
overflow-y: auto;
.item-title {
margin-bottom: 10px;
background: rgba(0, 98, 217, 0.1);
padding: 10px 15px;
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
}
}
}
}
</style>
@@ -1,514 +0,0 @@
<template>
<div class="Resources">
<div class="tab">
<div :class="['item', tabId == k ? 'active' : '']" v-for="(v, k) in tabList" @click="tabChange(k)">
<div class="text">
{{ v }}
</div>
<div class="info">
<img src="@/assets/icon/资源-优.png" alt="">
资源
{{ '优' }}
</div>
</div>
</div>
<div class="content">
<template v-if="tabId == '1'">
<div class="label">
任务{{ tabList[tabId] }}总数{{ 134 }}条,相似任务中平均使用{{ tabList[tabId] }}{{ 134 }}条。
</div>
<div class="MTChart chart" ref="MTChart"></div>
<div class="legendList">
<div class="item" v-for="(item, index) in MTData">
<img :src="`src/assets/icon/媒资图例${index + 1}.png`" alt="">
<span>{{ item.name }}</span>
<span>{{ item.value }}</span>
<span>条</span>
</div>
</div>
</template>
<template v-if="tabId == '2'">
<div class="label">
任务{{ tabList[tabId] }}总数{{ accountAndCWB.totalCount }}条。
</div>
<div class="AccountChart chart" ref="AccountChart"></div>
<div class="legendList">
<div class="item" v-for="(item, index) in accountData">
<div class="kuai" :style="{ backgroundColor: accountColorList[index] }"></div>
<span>{{ item.name }}</span>
<span>{{ item.value }}</span>
<span>条</span>
</div>
</div>
</template>
<template v-if="tabId == '3'">
<div class="label">
任务相关目标总计{{ accountAndCWB.targetCount }}个,其中{{ accountAndCWB.cbwTargetCount }}个可通过CBW触达,涉及CBW{{
accountAndCWB.cbwCount
}}张。
</div>
<div class="cbw">
<div class="item" v-for="(v, k, index) in accountAndCWB.cwb">
<div class="title">
{{ k }}
</div>
<div class="cbwContent">
<div class="row" v-for="i in v">
{{ i.username }}
</div>
</div>
</div>
</div>
</template>
<template v-if="tabId == '4'">
<div class="cbw">
<div class="item" v-for="(v, k, index) in b3DataRule">
<div class="title">
{{ v }}
</div>
<div class="cbwContent">
{{ b3Data[k] }}
</div>
</div>
</div>
</template>
</div>
</div>
</template>
<script>
import * as echarts from "echarts";
import { requestAxiosService } from "@/service/request-axios-service"; // 引入发送异步请求方法
export default {
props: ['taskId'],
data() {
return {
tabId: '1',
tabList: {
1: '相关媒资',
2: '相关账号',
3: 'CBW情况',
4: '资源通道'
},
axios: requestAxiosService,
MTColorList: ['#27c7ff', '#00ffff', '#f18d1e', '#bbf9ff'],
MTData: [ // 数据
{ value: 335, name: '文本' },
{ value: 310, name: '图片' },
{ value: 274, name: '音频' },
{ value: 235, name: '视频' },
],
accountAndCWB: null,
accountColorList: ['#00c7ff', '#e6a12d', '#49f5d2', '#f5e74f'],
accountData: [
],
b3Data: {},
b3DataRule: {
total: '实时通道总量',
using: '使用中',
backup: '备用中',
error: '异常中',
creating: '创建中',
retired: '临期通道',
}
};
},
methods: {
tabChange(d) {
this.tabId = d
if (d == '1') {
this.$nextTick(() => {
this.setMTChart()
})
}
if (d == '2') {
this.$nextTick(() => {
this.setAccountChart()
})
}
if (d == '4') {
this.$nextTick(() => {
this.getB3Data()
})
}
},
getB3Data() {
this.axios
.get(`${environment["b1ConsumerUrl"]}/cop/copChannelOverview`).then(res => {
console.log(res.data);
if (res.data) {
this.b3Data = res.data
}
})
},
getAccountAndCBWData() {
// requestAxiosService
// .get(`${environment["b1ConsumerUrl"]}/cop/copOwnAccount`, {
// taskId: this.taskId,
// }).then(res => {
// console.log(res.data);
// if (res.data) {
// this.accountData = res.data.personas.map(i => {
// return {
// value: i.personaAccountCount,
// name: i.persona.split(',')[0]
// }
// })
// this.setAccountChart()
// }
// })
let res = {
"state": "200",
"msg": "操作成功!",
"data": {
"cbwTargetCount": 0,
"cbw": {},
"cbwCount": 0,
"totalCount": 740,
"personas": [
{
"persona": "中东地缘经济分析师,精通能源供应链与区域冲突联动机制",
"personaAccountCount": 161
},
{
"persona": "中国海权战略研究员,关注海峡航运安全与能源进口路径风险",
"personaAccountCount": 162
},
{
"persona": "跨领域危机应对专家,结合军事、能源与外交案例分析台海局势",
"personaAccountCount": 417
}
],
"targetCount": 84
}
}
this.accountAndCWB = res.data
this.accountData = res.data.personas.map(i => {
return {
value: i.personaAccountCount,
name: i.persona.split(',')[0]
}
})
},
setAccountChart() {
const dom = this.$refs.AccountChart
let chart = echarts.init(dom);
let count = 0
this.accountData.forEach(i => {
count += i.value
})
chart.setOption({
color: this.accountColorList,
// 标题
title: {
left: 'center'
},
// 提示框
tooltip: {
trigger: 'item',
formatter: '{a}<br/>{b}: {c} ({d}%)' // 显示系列名、数据名、值和百分比
},
// 图例
legend: {
show: false,
orient: 'vertical', // 垂直排列
right: 20, // 图例位置
top: 20,
height: 300,
data: ['资源A', '资源B', '资源C', '资源D', '资源E'], // 图例名称
textStyle: {
color: '#fff'
},
itemGap: 30,
backgroundColor: {
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 0,
colorStops: [
{ offset: 0, color: 'rgba(47, 100, 173, 0.4)' },
{ offset: 1, color: 'rgba(0, 82, 119, 0.04)' }
]
},
},
graphic: [{
type: 'text',
left: '40px',
top: 'center',
style: {
text: '账号总数 \n' + count,
fontSize: 16,
textAlign: 'center',
fill: '#fff',
width: '50%',
height: '50%'
}
}],
// 系列配置
series: [
{
name: this.tabList[this.tabId], // 系列名称
type: 'pie', // 类型为饼图
radius: ['50%', '60%'], // 环形内外半径(控制环形宽度)
center: ['20%', '50%'], // 图表中心位置
data: this.accountData,
// 设置扇形之间的间隔(通过边框模拟)
itemStyle: {
borderRadius: 0, // 扇形圆角(可选)
borderColor: 'rgba(10, 36, 64, 0.8)', // 分隔线颜色(白色)
borderWidth: 5 // 分隔线宽度(控制间隔大小)
},
// 分隔线配置
splitLine: {
length: 15, // 分隔线长度(从外圆到标签的长度)
lineStyle: { // 分隔线样式
color: 'rgba(255, 255, 255, 0.5)', // 透明度
width: 2
}
},
// 标签配置
label: {
show: false,
formatter: '{b}: {c} ({d}%)', // 标签格式(名称、值、百分比)
position: 'outside', // 标签位置(外部)
color: '#fff' // 标签颜色
},
}
]
})
},
//设置媒体
setMTChart() {
const dom = this.$refs.MTChart
let chart = echarts.init(dom);
chart.setOption({
color: this.MTColorList,
// 标题
title: {
left: 'center'
},
// 提示框
tooltip: {
trigger: 'item',
formatter: '{a}<br/>{b}: {c} ({d}%)' // 显示系列名、数据名、值和百分比
},
// 图例
legend: {
show: false,
orient: 'vertical', // 垂直排列
right: 20, // 图例位置
top: 20,
height: 300,
data: ['资源A', '资源B', '资源C', '资源D', '资源E'], // 图例名称
textStyle: {
color: '#fff'
},
itemGap: 30,
backgroundColor: {
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 0,
colorStops: [
{ offset: 0, color: 'rgba(47, 100, 173, 0.4)' },
{ offset: 1, color: 'rgba(0, 82, 119, 0.04)' }
]
},
},
// 系列配置
series: [
{
name: this.tabList[this.tabId], // 系列名称
type: 'pie', // 类型为饼图
radius: ['50%', '60%'], // 环形内外半径(控制环形宽度)
center: ['32%', '50%'], // 图表中心位置
data: this.MTData,
// 设置扇形之间的间隔(通过边框模拟)
itemStyle: {
borderRadius: 0, // 扇形圆角(可选)
borderColor: 'rgba(10, 36, 64, 0.8)', // 分隔线颜色(白色)
borderWidth: 5 // 分隔线宽度(控制间隔大小)
},
// 分隔线配置
splitLine: {
length: 15, // 分隔线长度(从外圆到标签的长度)
lineStyle: { // 分隔线样式
color: 'rgba(255, 255, 255, 0.5)', // 透明度
width: 2
}
},
// 标签配置
label: {
show: false,
formatter: '{b}: {c} ({d}%)', // 标签格式(名称、值、百分比)
position: 'outside', // 标签位置(外部)
color: '#fff' // 标签颜色
},
}
]
})
},
},
watch: {
},
mounted() {
this.setMTChart()
this.getAccountAndCBWData()
}
};
</script>
<style lang="less">
.Resources {
.tab {
display: flex;
justify-content: center;
gap: 10px;
.item {
display: flex;
flex-direction: column;
padding: 10px 0 0;
cursor: pointer;
width: 80px;
height: 59px;
background: rgba(3, 38, 80, 0.6);
border: 1px solid;
border-color: rgba(43, 143, 254, 0.6);
border-radius: 4px;
.text {
text-align: center;
color: #ffffff;
font-size: 16px;
}
.info {
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.8);
font-size: 16px;
text-align: center;
img {
width: 16px;
height: 16px;
}
}
}
.active {
background: url('@/assets/icon/资源tab选中.png');
background-size: 114px 63px;
background-repeat: no-repeat;
border: none;
height: 63px;
}
}
.content {
position: relative;
width: 368px;
height: 300px;
.label {}
.chart {
width: 368px;
height: 250px;
}
.MTChart {
background: url("@/assets/icon/媒资环形图.png");
background-repeat: no-repeat;
background-size: 200px;
background-position: 20px 50px;
}
.AccountChart {
background: url("@/assets/icon/账号环形图.png");
background-repeat: no-repeat;
background-size: 118px;
background-position: 15px 65px;
}
.legendList {
position: absolute;
right: 10px;
top: 100px;
display: flex;
gap: 14px;
flex-direction: column;
.kuai {
width: 6px;
height: 6px;
}
.item {
// width: 114px;
height: 22px;
background: linear-gradient(90deg, rgba(47, 100, 173, 0.4) 0%, rgba(0, 82, 119, 0.04) 100%);
display: flex;
gap: 10px;
align-items: center;
justify-content: space-between;
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
padding: 0 5px;
img {
width: 8px;
height: 8px;
}
}
}
.cbw {
height: 250px;
overflow-y: auto;
.item {
width: 368px;
background: rgba(0, 12, 28, 0.15);
border: 1px solid;
border-color: rgba(0, 98, 217, 0.3);
border-radius: 4px;
.title {
background: url("@/assets/icon/CBWBG.png");
width: 368px;
height: 26px;
font-weight: 500;
color: #ffffff;
font-size: 16px;
padding: 0 0 0 16px;
line-height: 26px;
background-position-x: -5px;
}
.cbwContent {
padding: 14px 16px;
font-weight: 300;
color: #ffffff;
font-size: 16px;
}
}
}
}
}
</style>
@@ -1,20 +0,0 @@
<template>
<div class="task-analysis">task-analysis</div>
</template>
<script setup>
import { ref } from 'vue'
defineProps({
msg: String,
})
const count = ref(0)
</script>
<style scoped>
.task-analysis{
z-index: 500;
color: #fff;
}
</style>
@@ -1,337 +0,0 @@
<template>
<div class="step-task-fullmap">
<div ref="graphRef" class="graph-container"></div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import {
Graph,
treeToGraphData,
ExtensionCategory,
register,
Rect,
} from "@antv/g6";
import { requestAxiosService } from "@/service/request-axios-service";
import treemapCardBg from "@/assets/icon/treemap-card-bg.png";
const props = defineProps({
msg: String,
taskData: Object,
taskId: {
default: "",
},
});
let graph = null;
const graphRef = ref(null);
const count = ref(0);
let treemapData = {};
const mouseUpShow = ref(false);
const service = requestAxiosService;
const grupMapSeach = () => {
class taskImageRectNode extends Rect {
render(attributes = this.parsedAttributes, container) {
super.render(attributes, container); // 渲染圆形节点
const [width, height] = this.getSize(attributes);
const { contentTitle } = attributes;
this.upsert(
"node-image",
"image",
{
x: -width / 2, // 图片的x坐标,使其居中
y: -height / 2, // 图片的y坐标,使其居中
width: width, // 图片宽度略小于圆形节点
text: contentTitle,
height: height, // 图片高度略小于圆形节点
src: treemapCardBg,
},
container
);
}
}
register(ExtensionCategory.NODE, "custom-image-rect", taskImageRectNode);
const graphData = treeToGraphData(treemapData);
graphData.nodes.forEach((item) => {
item.src = treemapCardBg;
item.data.src = treemapCardBg;
// if (item.data.isExpanded === 1) {
// item.style = {
// collapsed: true
// };
// }
});
console.log("---------graphData", graphData);
console.log("这是什么", graphData);
graph = new Graph({
container: graphRef.value,
autoFit: "view",
data: graphData,
node: {
type: "custom-image-rect",
style: {
size: [400, 170],
labelText: (d) => d.data.contentText,
labelPlacement: "middle",
labelOffsetY: 10,
labelFill: "#FFF",
labelStorke: "#fcc",
labelWordWrap: true,
labelMaxLines: 3,
labelFontSize: 20,
labelLineHeight: 30,
labelZIndex: 100,
labelMaxWidth: "90%",
contentTitle: (d) => d.data.contentText,
// subtitle: (d) => d.data.contentText,
src: treemapCardBg,
badges: (d) => {
return [
{
text: d.data.headerBarText,
color: "#fff",
fontSize: 20,
padding: [10, 15],
backgroundFill: "",
backgroundRadius: 0,
offsetY: 15,
placement: "top",
// backgroundWidth: 400,
wordWrap: true,
maxLines: 1,
minWidth: 300,
},
];
},
halo: (d) => (d.id === "node0" ? true : false),
ports: [{ placement: "right" }, { placement: "left" }],
},
},
edge: {
type: "polyline",
style: {
lineWidth: 3,
radius: 20,
stroke: "#8b9baf",
endArrow: true,
labelFill: "#8b9baf",
labelFontWeight: 600,
labelBackground: true,
labelBackgroundFill: "#f8f8f8",
labelBackgroundOpacity: 1,
labelBackgroundLineWidth: 3,
labelBackgroundStroke: "#8b9baf",
labelPadding: [1, 10],
labelBackgroundRadius: 4,
lineDashOffset: 100,
router: {
type: "orth",
// padding:100
},
},
animation: {
enter: "fade", // 使用渐变动画
exit: "fade", // 使用渐变动画
},
},
layout: {
type: "compact-box",
direction: "LR",
// getHeight: function getHeight() { //:增大 getWidth() / getHeight() 匹配节点大小
// return 100;
// }
getWidth: function getWidth() {
return 400;
},
getVGap: function getVGap(d) {
// 增大垂直间距
// console.log('这是什么',d)
const { data } = d;
const height =
50 +
11 * (data.contentText.length / 20 + data.bottomText.length / 20);
return height;
// return 100;
},
getHGap: function getHGap() {
// 增大水平间距
return 50;
},
},
behaviors: [
"zoom-canvas",
"drag-element",
"drag-canvas",
{
type: "collapse-expand",
key: "collapse-expand-1",
trigger: "click", // 修改触发方式为单击
animation: true, // 启用动画效果
onCollapse: (id) => {
// 收起
console.log("这是什么", id);
},
onExpand: (id) => {
// 展开
console.log(id);
},
},
],
});
// // 节点点击事件
// graph.value.on('node:click', (e) => {
// // const nodeData = this.graph.getNodeData('actor4979');
// // console.log('这是什么', e, nodeData);
// // this.graph.collapseElement('actor4979', { animation: false });
// // graph.layout();
// });
graph.render();
// console.log('这是什么',this.treemapData.length)
// if (treemapData.length > 30) {
// setTimeout(() => {
// makeGraphZoom(0.6);
// }, 500);
// }
};
const makeGraphZoom = (index) => {
graph.zoomTo(index);
};
const taskDataSearch = async (taskId) => {
const parmy = {
taskId,
};
const res = await service.get(
`${environment["b1ConsumerUrl"]}/taskAnalyseSearch/getTaskPurposeDetail`,
parmy
);
console.log("逻辑图", res);
let result = res.data;
result.forEach((item) => {
item.childrenList = item.childrenList.filter(
(aItem) => aItem.childrenList.length > 0
);
});
taskDataChange(result);
treemapData = {
id: props.taskData.id,
data: {
id: props.taskData.id,
nodeName: "node-task",
contentText: props.taskData.overview,
bottomText: "",
headerBarText: props.taskData.taskName || "",
collapseText: "展开",
type: "",
isExpanded: 1,
},
children: result,
};
console.log("treemapData", treemapData);
grupMapSeach();
console.log(result);
};
const setLevelsAndFilter = (result, level = 1) => {
result.forEach((item) => {
item.level = level;
if (item.childrenList) {
// 递归设置子节点的 level 并过滤
item.childrenList = setLevelsAndFilter(
item.childrenList,
level + 1
).filter((child) => child.level === 5);
}
});
return result;
};
const taskDataChange = (list) => {
const DTO_TYPES = [
{
key: "purposeDto",
nodeName: "node-aim",
headerBarText: "ZZ目的",
contentKey: "zzPurpose",
nodeId: "aim",
},
{
key: "actorDto",
nodeName: "node-actor",
headerBarText: "行为体",
contentKey: "actorName",
bottomTextKey: "recReason",
nodeId: "actor",
},
{
key: "expectedBehaviorDto",
nodeName: "node-behavior",
headerBarText: "预期行为",
contentKey: "behavior",
nodeId: "behavior",
},
{
key: "drivingFactorDto",
nodeName: "node-factor",
headerBarText: "驱动因素",
contentKey: "drivingFactor",
nodeId: "factor",
},
{
key: "narrationDto",
nodeName: "node-narrate",
headerBarText: "叙事",
contentKey: "narration",
bottomTextKey: "recReason",
nodeId: "narrate",
},
];
const transformItem = (item, dtoType) => {
const dto = item[dtoType.key];
if (dto) {
item.id = dtoType.nodeId + dto.id;
item.data = {
id: dto.id,
nodeName: dtoType.nodeName,
contentText: dto[dtoType.contentKey],
bottomText: dtoType.bottomTextKey ? dto[dtoType.bottomTextKey] : "",
headerBarText: dtoType.headerBarText || "",
collapseText: "展开",
type: "",
isExpanded: dto.isExpanded,
};
}
};
list.forEach((item) => {
DTO_TYPES.forEach((dtoType) => {
transformItem(item, dtoType);
});
if (item.childrenList) {
item.children = item.childrenList;
taskDataChange(item.children);
}
});
};
onMounted(() => {
taskDataSearch(props.taskId);
});
</script>
<style scoped lang="less">
.step-task-fullmap {
width: 100%;
height: 700px;
color: #fff;
.graph-container {
width: 100%;
height: 100%;
}
}
</style>
@@ -1,78 +0,0 @@
<template>
<div class="DocDetails">
<div class="title">
{{ docData.m_title }}
</div>
<div class="info">
<div class="source">
{{ docData.source1 }}
</div>
<div class="date">
{{ docData.m_publish }}
</div>
</div>
<div class="content">
{{ docData.m_content }}
</div>
</div>
</template>
<script>
import { Handler } from 'leaflet';
export default {
props: {
docData: {
default: {}
}
},
data() {
return {
};
},
methods: {
},
watch: {
docData: {
handler(newV, oldV) {
console.log(newV);
},
deep: true
}
},
};
</script>
<style lang="less">
.DocDetails {
color: #fff;
.title {
font-weight: 700;
font-size: 18px;
}
.info {
display: flex;
gap: 10px;
margin: 10px 0;
.source {
border: 1px solid;
border-color: #d95e00 !important;
color: #dd8717 !important;
background-color: #fff6ea !important;
padding: 0 5px;
}
}
.content {
padding: 10px;
background-color: rgba(0, 140, 119, 0.1);
border-radius: 5px;
}
}
</style>
@@ -1,310 +0,0 @@
<template>
<div class="addTask">
<div @click="addModelStatus = true" v-if="taskSuperiorFlag !== null">
<slot>
<div class="addBtn">
+ 创建任务
</div>
</slot>
</div>
<t-dialog v-model:visible="addModelStatus" attach="body" header="创建任务" width="600" :confirm-on-enter="true"
:on-confirm="confirm">
<div class="frame">
<div class="row">
<div class="label">
<div class="text">
任务名称
</div>
<div class="required">
*
</div>
</div>
<div class="content"><t-input v-model="modelData.taskName" /></div>
</div>
<div class="row">
<div class="label">
<div class="text">
任务简介
</div>
<div class="required">
*
</div>
</div>
<div class="content">
<div>
<span class="JS">
!
</span> 任务描述对描述COH(CH)质量影响重大,请认真、完整填写
</div>
<t-textarea :autosize="{ minRows: 4, maxRows: 7 }" v-model="modelData.overview"
placeholder="这一步需要完整准确填写任务描述,您填写的内容对任务分析至关重要,填写的要素完整度和任务要求会贯穿整个COH(CH)过程,直接影响智能COH(CH)的质量" />
</div>
</div>
<div class="row">
<div class="label">
<div class="text">
任务等级
</div>
<div class="required">
*
</div>
</div>
<div class="content">
<t-select v-model="modelData.taskLevel">
<t-option v-for="(v, k) in taskLevel" :key="k" :label="v" :value="k" />
</t-select>
</div>
</div>
<div class="row">
<div class="label">
<div class="text">
下发单位
</div>
</div>
<div class="content"><t-input v-model="modelData.issuingDept" /></div>
</div>
<div class="row">
<div class="label">
<div class="text">
任务方向
</div>
<div class="required">
*
</div>
</div>
<div class="content"> <t-select v-model="modelData.taskDirection">
<t-option v-for="i in taskDirectionList" :key="i.value" :label="i.label" :value="i.value" />
</t-select></div>
</div>
<div class="row">
<div class="label">
<div class="text">
任务代号
</div>
</div>
<div class="content"><t-input v-model="modelData.taskNo" /></div>
</div>
<div class="row">
<div class="label">
<div class="text">
任务类型
</div>
<div class="required">
*
</div>
</div>
<div class="content"> <t-select v-model="modelData.taskType">
<t-option v-for="i in taskTypeList" :key="i.value" :label="i.label" :value="i.value" />
</t-select></div>
</div>
<div class="row">
<div class="label">
<div class="text">
任务时间
</div>
<div class="required">
*
</div>
</div>
<div class="content">
<t-date-range-picker v-model="date" :presets="presets" enable-time-picker allow-input clearable
@change="onChange" style="width: 100%;" />
</div>
</div>
</div>
</t-dialog>
</div>
</template>
<script>
import taskLevel from '@/common/taskLevel';
import dayjs from 'dayjs'
import { MessagePlugin } from 'tdesign-vue-next';
import { requestAxiosService } from '@/service/request-axios-service';
export default {
data() {
return {
addModelStatus: false,
taskLevel,
taskDirectionList: [
{ label: 'W方向', value: 'W方向' },
{ label: 'U方向', value: 'U方向' },
],
presets: {
最近7天: [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()],
最近3天: [dayjs().subtract(2, 'day').toDate(), dayjs().toDate()],
今天: [dayjs().toDate(), dayjs().toDate()],
},
taskTypeList: [
{ label: '塑势', value: 0 },
{ label: 'DJ', value: 1 },
],
modelData: {
"issuingDept": "",
"overview": "",
"taskDirection": "W方向",
"taskEndDate": "",
"taskLevel": '1',
"taskName": "",
"taskNo": "",
"taskStartDate": "",
"taskStatus": 0,
"taskSuperiorFlag": 0,
"taskType": 1,
},
date: []
};
},
props: {
url: {
Type: String,
default: ''
},
taskSuperiorFlag: {
default: null
},
taskId: {
default: null
},
overview: {
default: ''
},
taskName: {
default: ''
}
},
watch: {
addModelStatus: {
handler(v) {
console.log(v);
//重置
if (v) {
this.modelData = {
"issuingDept": "",
"overview": this.overview,
"taskDirection": "W方向",
"taskEndDate": "",
"taskLevel": '1',
"taskName": this.taskName,
"taskNo": "",
"taskStartDate": "",
"taskStatus": 0,
"taskSuperiorFlag": 0,
"taskType": 1,
}
}
}
}
},
methods: {
onChange(d) {
console.log(d);
},
async confirm() {
console.log(this.modelData);
if (!this.modelData.taskName) {
MessagePlugin.warning('请填写任务名称后重试!')
return false
}
if (!this.modelData.overview) {
MessagePlugin.warning('请填写任务简介后重试!')
return false
}
if (this.date.length == 0) {
MessagePlugin.warning('请选择任务时间后重试!')
return false
}
let params = { ...this.modelData }
params.taskStartDate = dayjs(this.date[0]).toISOString()
params.taskEndDate = dayjs(this.date[1]).toISOString()
console.log(params);
params.taskSuperiorFlag = this.taskSuperiorFlag
params.pTaskId = this.taskId
const res = await requestAxiosService.post(`${environment['b1ConsumerUrl']}/cop/saveSuperiorTask`, params);
console.log(res);
MessagePlugin.warning(res.msg)
if (res.state == 201) {
this.addModelStatus = false
this.$emit('success')
}
}
},
};
</script>
<style lang="less">
.addTask {
.addBtn {
text-align: center;
color: #fff;
line-height: 40px;
border-radius: 5px;
border: 1px solid #2b8ffe99;
margin: 10px 20px;
cursor: pointer;
&:hover {
background: linear-gradient(123.69deg, #2b8ffe, #0062d9) !important;
}
}
}
.frame {
display: flex;
flex-direction: column;
.row {
display: flex;
align-items: center;
min-height: 50px;
.label {
width: 100px;
display: flex;
line-height: 20px;
font-size: 16px;
.text {
color: #fff;
}
.required {
color: red;
font-size: 20px;
}
}
.content {
flex: 1;
flex-shrink: 0;
color: #fff;
.t-textarea__inner {
border-color: var(--td-brand-color);
;
}
.JS {
color: red;
display: inline-block;
border: 1px solid red;
width: 20px;
height: 20px;
border-radius: 50px;
text-align: center;
line-height: 20px;
}
}
}
}
</style>
@@ -1,137 +0,0 @@
<template>
<t-dialog
v-model:visible="modelVisible"
attach="body"
:header="modelTitle"
width="1000"
:cancelBtn="null"
:confirm-on-enter="true"
:on-confirm="modelConfig"
>
<div class="GJSTModel">
<div class="title">
{{ modelData.title }}
</div>
<div class="remark">
<template v-for="(label, key) in remarkRule">
<div class="item" v-if="modelData[key]">
{{ label }}:{{ modelData[key] }}
</div>
</template>
</div>
<div
class="abstract"
style="color: #aaa"
v-if="modelData.trendSpeechLike || modelData.trendConsistentLike"
>
摘要:{{ modelData.trendSpeechLike || modelData.trendConsistentLike }}
</div>
<div class="content">
{{ modelData.content }}
</div>
<div class="liyou" style="color: #aaa" v-if="modelData.recommendReason">
推荐理由:{{ modelData.recommendReason }}
</div>
<div
class="liyou"
style="color: #aaa"
v-if="modelData.narrativeDirection"
>
叙事方向:{{ modelData.narrativeDirection }}
</div>
<div
class="liyou"
style="color: #aaa"
v-if="modelData.narrativeConstraints"
>
叙事限制:{{ modelData.narrativeConstraints }}
</div>
</div>
</t-dialog>
</template>
<script>
export default {
data() {
return {
modelVisible: false,
};
},
props: {
modelTitle: {
default: "详情",
},
remarkRule: {
default: {
direction: "方向",
importNum: "热度",
latitude: "纬度",
longitude: "经度",
siteName: "网站",
sourceType: "来源",
type: "类型",
date: "时间",
},
},
modelData: {
default: {},
},
modelStatus: {
default: false,
},
},
methods: {
modelConfig() {
this.$emit("config");
},
},
watch: {
modelStatus(v) {
console.log(v);
this.modelVisible = v;
},
modelVisible(v) {
this.$emit("updateStatus", v);
},
},
mounted() {
console.log(this.modelStatus);
},
};
</script>
<style lang="less">
.CommonInfoDialog {
}
.GJSTModel {
color: #fff;
.title {
text-align: center;
font-size: 24px;
}
.remark {
color: #aaa;
margin: 10px;
display: flex;
justify-content: center;
gap: 10px;
.item {
}
}
.content {
font-size: 16px;
padding-bottom: 20px;
}
}
.abstract {
color: rgb(170, 170, 170);
font-size: 18px;
margin-bottom: 10px;
}
</style>
File diff suppressed because it is too large. Load diff
@@ -1,176 +0,0 @@
<template>
<div class="Audience" @click.stop>
<!-- {{ data.designName }} -->
<div class="head">
<img src="@/assets/icon/群组..svg" alt="">
<div class="JJ">
<div class="title">
<div class="tag">
<img src="@/assets/icon/群体.svg" alt="">
<span>
群体
</span>
</div>
<div class="name"> {{ data.designName }}</div>
</div>
<div class="info">
共涉及10个账号
</div>
</div>
</div>
<div class="content">
<t-tabs v-model="tabValue">
<t-tab-panel :value="1" label="群体概述" :destroy-on-hide="false">
<p style="padding: 25px">选项卡1的内容,使用 t-tab-panel 渲染</p>
</t-tab-panel>
<t-tab-panel :value="2" label="成员账号" :destroy-on-hide="false">
<!-- <template #panel>
<p style="padding: 25px">选项卡2的内容,使用 t-tab-panel 渲染</p>
</template> -->
<div class="userList">
<div class="item">
<div class="head">
<img src="@/assets/icon/群组..svg" alt="">
<div class="JJ">
<div class="title">
<div class="name"> {{ data.designName }}</div>
</div>
<div class="info">
<div class="tag">
<img src="@/assets/icon/粉丝量.svg" alt="">
<span>
粉丝量 805
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</t-tab-panel>
</t-tabs>
</div>
</div>
</template>
<script>
export default {
props: ['data'],
data() {
return {
tabValue: 1
};
},
methods: {
},
watch: {
},
};
</script>
<style lang="less">
.Audience {
width: 391px;
height: 476px;
background: rgba(3, 38, 80, 0.8);
border: 1px solid;
border-color: rgba(43, 143, 254, 0.6);
border-radius: 4px;
backdrop-filter: blur(15px);
padding: 16px 28px;
.head {
display: flex;
gap: 17px;
height: 60px;
img {
width: 60px;
height: 60px;
}
.JJ {
display: flex;
flex-direction: column;
justify-content: space-between;
.title {
display: flex;
align-items: center;
gap: 10px;
.tag {
width: 56px;
height: 22px;
background: rgba(82, 221, 171, 0.5);
border: 1px solid;
border-color: #52ddab;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
img {
width: 14px;
height: 14px;
}
}
.name {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #ffffff;
font-size: 18px;
}
}
.info {
opacity: 80%;
line-height: 28px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: 16px;
}
}
}
.content {
margin-top: 24px;
.t-tabs__nav-scroll {
justify-content: center;
}
.userList {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px 0;
.tag {
width: 126px;
height: 24px;
background: rgba(255, 153, 0, 0.3);
border: 1px solid;
border-color: rgba(255, 153, 0, 0.85);
border-radius: 2px;
display: flex;
align-items: center;
padding: 0 6px;
img {
width: 16px;
height: 16px;
}
}
}
}
}
</style>
@@ -1,195 +0,0 @@
<template>
<div class="strategy-container" @click.stop>
<div class="strategy-main">
<div class="strategy-main-title">
{{ "ZF策略" }}
</div>
<div>
<div v-for="(strategy, index) in strategyInfoDict" class="strate-list">
<div class="strate-item" v-if="strategy.isVisible">
<img :src="`src/assets/strategy-logo/${strategy.key}.svg`" alt="" class="strate-item-logo" />
<span class="strate-item-key">{{ strategy.key }}:</span>
<span class="strate-item-value text-ellipsis" :class="{
' ellipsis2':
strategy.name == '信息主题' || strategy.name == '人设',
}">
{{ strategy?.value }}
</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
const strategyInfoDict = ref([
{
key: "信息主题",
value: "",
isVisible: true,
},
{
key: "信息数量",
value: "",
isVisible: true,
},
{
key: "人设",
value: "",
isVisible: true,
},
{
key: "人设数量",
value: "",
isVisible: true,
},
{
key: "平台",
value: "",
isVisible: true,
},
{
key: "模态",
value: "",
isVisible: true,
},
{
key: "语种",
value: "",
isVisible: true,
},
{
key: "账号类型",
value: "",
isVisible: true,
},
{
key: "信息发布时间段",
value: "",
isVisible: false,
},
]);
const props = defineProps({
msg: String,
data: Object,
});
onMounted(() => {
console.log({ data: props });
if (props.data) {
const data = props.data;
strategyInfoDict.value.forEach((it) => {
switch (it.key) {
case "信息主题": {
it["value"] = data?.extendInfo?.informationTopic;
break;
}
case "信息数量": {
it["value"] = "";
break;
}
case "人设": {
it["value"] = data?.personaJson ? data?.personaJson[0]?.persona : "";
break;
}
case "人设数量": {
it["value"] = data?.personaJson ? data?.personaJson[0]?.size : "";
break;
}
case "平台": {
it["value"] = data?.extendInfo?.platformData;
break;
}
case "模态": {
it["value"] = "";
break;
}
case "语种": {
it["value"] = "";
break;
}
case "账号类型": {
it["value"] = "";
break;
}
}
});
}
});
</script>
<style scoped>
.strategy-container {
z-index: 600;
color: #fff;
position: absolute;
left: 55%;
backdrop-filter: blur(15px);
.strategy-main {
width: 316px;
height: 460px;
background: rgba(3, 38, 80, 0.8);
border: 1px solid;
border-color: rgba(43, 143, 254, 0.6);
border-radius: 4px;
padding: 0 10px;
.strategy-main-title {
width: 141px;
height: 32px;
background: rgba(3, 38, 80, 1);
border: 1px solid;
border-color: rgba(43, 143, 254, 0.6);
border-radius: 100px;
backdrop-filter: blur(15px);
position: relative;
top: -16px;
left: 25%;
text-align: center;
line-height: 32px;
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #ffffff;
font-size: 16px;
}
}
}
.strate-item {
display: flex;
align-items: top;
justify-content: left;
margin: 20px 5px;
.strate-item-logo {
width: 16px;
height: 25px;
margin-right: 10px;
}
.strate-item-key {
width: 80px;
height: 16px;
line-height: 26px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #aaceff;
font-size: 16px;
margin-right: 10px;
text-align: left;
}
.strate-item-value {
width: 148px;
max-height: 50px;
line-height: 26px;
font-family: Noto Sans S Chinese;
color: #ffffff;
font-size: 16px;
}
}
</style>
@@ -1,365 +0,0 @@
<template>
<div class="yqxg-container" @click.stop>
<div class="yqxg-title">预期效果</div>
<div class="yqxg-content-top-one">
<div class="yqxg-left-top">
<div class="yqxg-left-top-count">
{{ data["撬动关键节点"] }}
<span class="yqxg-left-top-count-a">个</span>
</div>
<div class="yqxg-left-top-title">撬动关键节点</div>
</div>
<div class="yqxg-center">
<div class="yqxg-center-count">
{{ numberToChinese(data["六度量等级"]) }} 级
</div>
<div class="yqxg-center-title">六度量等级</div>
</div>
<div class="yqxg-right-top">
<div class="yqxg-right-top-count">
{{ data["引发主流媒体报道"] }}
<span class="yqxg-right-top-count-a">个</span>
</div>
<div class="yqxg-right-top-title">引发主流媒体报道</div>
</div>
</div>
<div class="yqxg-content-top-two">
<div class="yqxg-left-bottom">
<div class="yqxg-left-bottom-flag">{{ data["引发官方回应"] }}</div>
<div class="yqxg-left-bottom-title">引发官方回应</div>
</div>
<div class="yqxg-right-bottom">
<div class="yqxg-right-bottom-flag">{{ data["引发线下活动"] }}</div>
<div class="yqxg-right-bottom-title">引发线下活动</div>
</div>
</div>
<div class="yqxg-content-top-three">
<div class="yqxg-content-top-three-content">
<span class="yqxg-conetnt-top-three-tag" :style="tagColor('未推演')">
{{ "未推演" }}
</span>
{{ data["预期效果"] }}
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, watch } from "vue";
const props = defineProps({
msg: String,
data: Object,
});
onMounted(() => { });
const tagColor = (status = "未推演") => {
const colorList = [
{
name: "已推演",
backgroundColor: "rgba(0, 140, 119, 0.5)",
borderColor: "#008c77",
},
{
name: "推演中",
backgroundColor: "rgba(255, 153, 0, 0.5)",
borderColor: "#ff9900",
},
{
name: "未推演",
backgroundColor: "rgba(0, 121, 254, 0.5)",
borderColor: "#0079fe",
},
];
const item = colorList.find((it) => it.name === status);
// 添加默认样式处理
if (!item) {
console.warn(`找不到对应状态的样式: ${status}`);
return {
background: "rgba(200, 200, 200, 0.5)",
"border-color": "#cccccc",
};
}
return {
background: item.backgroundColor,
"border-color": item.borderColor,
};
};
const numberToChinese = (num) => {
const map = {
1: "一",
2: "二",
3: "三",
4: "四",
5: "五",
6: "六",
};
return map[num] || num;
};
</script>
<style scoped>
.yqxg-container {
position: absolute;
right: 80px;
width: 536px;
height: 346px;
background: rgba(3, 38, 80, 1);
border: 1px solid;
border-color: rgba(43, 143, 254, 0.6);
border-radius: 4px;
z-index: 600;
.yqxg-title {
position: relative;
top: -16px;
left: 155px;
width: 227px;
height: 32px;
background: rgba(3, 38, 80, 0.8);
border: 1px solid;
border-color: rgba(43, 143, 254, 0.6);
border-radius: 100px;
backdrop-filter: blur(15px);
text-align: center;
line-height: 30px;
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #ffffff;
font-size: 16px;
}
}
.yqxg-content-top-one {
display: flex;
justify-content: center;
position: absolute;
width: 100%;
top: 30px;
}
.yqxg-left-top {
width: 125px;
height: 52px;
background: url("../../assets/6duliang/六度量四周bg.svg") no-repeat;
background-size: cover;
margin: 0 5px;
position: relative;
top: 30px;
.yqxg-left-top-count {
height: 37px;
line-height: 62px;
text-shadow: 0px 2px 4px rgba(67, 233, 233, 0.4);
font-family: DS-Digital;
font-weight: 700;
color: #ffffff;
font-size: 38px;
text-align: center;
position: relative;
top: -30px;
left: 5px;
}
.yqxg-left-top-count-a {
width: 16px;
height: 16px;
line-height: 28px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: 16px;
}
.yqxg-left-top-title {
height: 14px;
line-height: 28px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: 14px;
text-align: center;
position: relative;
top: -11px;
}
}
.yqxg-center {
width: 213px;
height: 111px;
background: url("../../assets/6duliang/六度量中心bg.svg") no-repeat;
background-size: cover;
.yqxg-center-count {
height: 38px;
line-height: normal;
text-shadow: 0px 0px 12px rgba(232, 233, 67, 0.62);
font-family: Noto Sans S Chinese;
font-weight: 700;
color: #ffffff;
font-size: 38px;
letter-spacing: 0px;
text-align: center;
position: relative;
top: 17px;
}
.yqxg-center-title {
height: 16px;
line-height: normal;
font-family: Noto Sans S Chinese;
color: #ffffff;
font-size: 16px;
text-align: center;
position: relative;
top: 70px;
}
}
.yqxg-right-top {
width: 125px;
height: 52px;
background: url("../../assets/6duliang/六度量四周bg.svg") no-repeat;
background-size: cover;
margin: 0 5px;
position: relative;
top: 44px;
.yqxg-right-top-count {
line-height: 62px;
text-shadow: 0px 2px 4px rgba(67, 233, 233, 0.4);
font-family: DS-Digital;
font-weight: 700;
color: #ffffff;
font-size: 38px;
text-align: center;
position: relative;
top: -30px;
}
.yqxg-right-top-count-a {
width: 16px;
height: 16px;
line-height: 28px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: 16px;
}
.yqxg-right-top-title {
height: 14px;
line-height: 28px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: 14px;
text-align: center;
top: -36px;
position: relative;
}
}
.yqxg-content-top-two {
padding: 0 55px;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
top: 120px;
.yqxg-left-bottom {
width: 125px;
height: 52px;
background: url("../../assets/6duliang/六度量四周bg.svg") no-repeat;
background-size: cover;
.yqxg-left-bottom-flag {
height: 20px;
line-height: 28px;
text-shadow: 0px 2px 4px rgba(67, 233, 233, 0.4);
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #ffffff;
font-size: 20px;
text-align: center;
position: relative;
top: -10px;
}
.yqxg-left-bottom-title {
height: 14px;
line-height: 28px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: 14px;
text-align: center;
position: relative;
top: 5px;
}
}
.yqxg-right-bottom {
width: 125px;
height: 52px;
background: url("../../assets/6duliang/六度量四周bg.svg") no-repeat;
background-size: cover;
.yqxg-right-bottom-flag {
height: 20px;
line-height: 28px;
text-shadow: 0px 2px 4px rgba(67, 233, 233, 0.4);
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #ffffff;
font-size: 20px;
text-align: center;
position: relative;
top: -10px;
}
.yqxg-right-bottom-title {
height: 14px;
line-height: 28px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: 14px;
text-align: center;
position: relative;
top: 5px;
}
}
}
.yqxg-content-top-three {
width: 488px;
height: 79px;
background: rgba(1, 12, 28, 0.15);
border-radius: 4px;
margin: 19px 23px;
padding: 5px;
position: relative;
top: 120px;
.yqxg-conetnt-top-three-tag {
padding: 3px;
height: 22px;
border: 1px solid;
border-radius: 2px;
}
.yqxg-content-top-three-content {
width: 456px;
height: 44px;
line-height: 28px;
font-family: Noto Sans S Chinese;
color: #ffffff;
font-size: 16px;
}
}
</style>
@@ -1,65 +0,0 @@
<template>
<Teleport to="body">
<div class="G6Modal" v-if="modalType" ref="G6Modal" @click="closeModal()">
<component :is="componentRule[modalType]" :data="data" />
</div>
</Teleport>
</template>
<script>
import Yqxg from "./components/Yqxg.vue"
import Strategy from "./components/Strategy.vue"
import Audience from './components/Audience.vue'
export default {
props: ['modalType', 'data'],
data() {
return {
componentRule: {
'xgfx': 'Yqxg',
'CL': 'Strategy',
'audience': 'Audience'
}
};
},
emits: ['updateModalType',],
components: { Yqxg, Strategy, Audience },
watch: {
modalType: {
handler(v) {
console.log(v);
if (v) {
this.openModal(v)
}
}
}
},
methods: {
openModal(t) {
},
closeModal() {
this.$emit('updateModalType', '')
}
},
mounted() {
console.log(this.$refs.G6Modal);
}
};
</script>
<style lang="less" scoped>
.G6Modal {
z-index: 99999;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, .4);
display: flex;
align-items: center;
justify-content: center;
}
</style>
@@ -1,114 +0,0 @@
<template>
<div class="analysis-tool">
<div class="title-content-item margin-bottom-10">
分析工具
</div>
<div class="tool-content">
<div class="list-item" v-for="(item, index) in toolList" :key="index">
{{ item.title }}
</div>
</div>
<div class="title-content-item margin-bottom-10">
分析结果
</div>
<div class="analysis-content">
<div class="list-item" v-for="(item, index) in list" :key="index">
<div class="item-title">
<span class="item-tag tab-lightblue">战机</span>
{{ item.title }}
</div>
<div class="item-footer">
<span class="item-value">参考依据</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
defineProps({
msg: String,
})
const count = ref(0);
const toolList = ref([
{ title: '战机抽取', checked: false },
{ title: '威胁分析抽取', checked: false },
{ title: '整体态势分析', checked: false },
{ title: '对手叙事抽取', checked: false },
{ title: '口径分析', checked: false },
{ title: '目标分析', checked: false },
]);
const list = ref([
{ title: '舆论揭露民进党政客鼓吹战争却将家人送往海外的双标行为,认为其是战争贩子,削弱了台湾当局的执政合法性。', type: '战机' },
{ title: '舆论揭露民进党政客鼓吹战争却将家人送往海外的双标行为,认为其是战争贩子,削弱了台湾当局的执政合法性。', type: '战机' },
{ title: '舆论揭露民进党政客鼓吹战争却将家人送往海外的双标行为,认为其是战争贩子,削弱了台湾当局的执政合法性。', type: '战机' },
{ title: '舆论揭露民进党政客鼓吹战争却将家人送往海外的双标行为,认为其是战争贩子,削弱了台湾当局的执政合法性。', type: '战机' },
{ title: '舆论揭露民进党政客鼓吹战争却将家人送往海外的双标行为,认为其是战争贩子,削弱了台湾当局的执政合法性。', type: '战机' },
])
</script>
<style scoped lang="less">
.analysis-tool {
z-index: 500;
color: #fff;
padding: 10px;
height: calc(100vh - 200px);
overflow-y: auto;
.tool-content {
padding: 10px;
.list-item {
width: 29%;
display: inline-block;
padding: 15px 15px;
font-size: 20px;
font-family: 'Noto Sans S Chinese';
font-weight: 700;
background: #02416a;
border-radius: 5px;
margin: 10px;
text-align: center;
}
}
.analysis-content {
.list-item {
padding: 10px;
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
margin: 15px;
.item-title {
font-size: var(--fontSize);
font-family: 'Noto Sans S Chinese';
}
.item-footer {
font-size: var(--fontSizes);
text-align: right;
.item-value {
color: #45c3f3;
text-decoration: underline;
cursor: pointer;
}
}
}
}
.item-tag {
display: inline-block;
padding: 2px 5px;
margin: 5px;
font-size: var(--fontSizes);
border-radius: 4px;
border: 1px solid;
}
}
</style>
@@ -1,67 +0,0 @@
<template>
<div class="chat-content">
<div class="chat-list-content">
<ChatList :chatList="chatList"/>
</div>
<div class="chat-input-content">
<t-textarea v-model="chatInputValue" placeholder="请输入内容"
name="description" :autosize="{ minRows: 2, maxRows: 5 }" />
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import ChatList from './ChatList.vue';
import chatData from '../../assets/json/chatData.json'
defineProps({
msg: String,
})
const chatList = ref([]);
const chatInputValue = ref('');
onMounted(() => {
chatList.value = chatData;
console.log('chatList',chatList.value)
})
</script>
<style scoped lang="less">
.chat-content {
z-index: 500;
color: #fff;
padding: 10px;
background: rgba(0, 98, 217, 0.1);
border-radius: 4px;
box-shadow: 0 1px var(--fontSize) #8b8b8b0f inset;
position: relative;
box-sizing: border-box;
.chat-list-content {
height: calc(100vh - 335px);
overflow-y: auto;
margin-bottom: 60px;
}
.chat-input-content {
width: 100%;
background-color: rgba(0, 98, 217, 0.2);
padding: 10px;
border: 1px solid;
border-color: #0062d9;
border-radius: 10px;
box-shadow: 0 3px 6px #8b8b8b29;
overflow: hidden;
}
}
:deep(.t-textarea__inner) {
border: 0;
&:focus {
box-shadow: none;
}
}
</style>
@@ -1,373 +0,0 @@
<template>
<div class="chat-list">
<t-loading size="small" :loading="loading" show-overlay>
<div class="chat-list-content">
<div class="chat-list-items" v-for="(item,index) in chatList" :key="index">
<div class="chat-list-item">
<div class="user-item-container">
<div class="user-item">
<div class="user-item-content">
<div class="user-content-title">{{ item.userTitle }} </div>
</div>
<div class="chat-user-logo" style="margin-left: 10px">
<img src="../../assets/icon/user-logo.svg" alt="" />
</div>
</div>
</div>
<div class="chat-item-container">
<div class="chat-item">
<div class="chat-logo-red" style="margin-right: 10px">
<img src="../../assets/icon/chat-logo.svg" alt="" />
</div>
<div class="chat-item-contents">
<div class="chat-item-content">
<span v-if="item.loading">正在思考<span class="chat-blink">...</span></span>
<div v-if="item.thinkTitle" class="chat-think-title">
<div class="chat-think-head flex-between-center" @click="chatThinkChange(index)">
<span>
<check-circle-icon :stroke-width="2.5" strokeColor="#45c3f3" size="20px"/>
<span nz-icon nzType="check-circle" nzTheme="outline"
style="color: var(--color-main); margin-right: 10px"></span>
<span>已深度思考</span>
</span>
<span nz-icon nzType="down" nzTheme="outline"></span>
<chevron-down-icon :stroke-width="2.5" strokeColor="#fff" size="20px"/>
</div>
<div class="chat-think-body" :class="{ 'chat-think-hidden': item.thinkShow }">
<CodeData :markdown="item.thinkTitle"/>
</div>
</div>
<div class="chat-content-title" v-if="chatIndex !== index || !editShow">
<CodeData :markdown="item.chatTitle"/>
</div>
<div class="answer-title" v-if="item.chatStop" @click="taskStop"> 停止生成 </div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</t-loading>
</div>
</template>
<script setup>
import { ref } from 'vue'
import CodeData from '../markdown/CodeData.vue'
import { CheckCircleIcon, ChevronDownIcon } from 'tdesign-icons-vue-next';
defineProps({
chatList: Array,
})
const loading = ref(false);
const chatIndex = ref(0);
const editShow = ref(false);
const chatThinkChange = (index) => {
}
const taskStop = () => {
}
const materialsListChange = () => {
}
</script>
<style scoped lang="less">
.chat-list {
width: 100%;
padding: 10px;
.chat-list-item {
width: 100%;
&::after {
content: '';
display: block;
clear: both;
}
.user-item-container,
.chat-item-container {
width: 100%;
}
.user-item {
display: flex;
justify-content: flex-end;
margin-bottom: 30px;
position: relative;
font-size: var(--fontSize);
line-height: 26px;
font-family: 'Noto Sans S Chinese';
.user-item-logo {
width: 45px;
height: 43px;
background-position: -706px -23px;
}
.user-item-content {
max-width: 80%;
// background: #d7e9f0;
background: var(--user-item-bg);
padding: 10px 15px;
border-radius: 8px 0px 8px 8px;
}
}
.chat-item {
min-height: 60px;
display: flex;
margin-bottom: 30px;
font-size: var(--fontSize);
line-height: 26px;
font-family: 'Noto Sans S Chinese';
// justify-content: flex-end;
// margin: 10px 0px;
.chat-item-contents {
width: 80%;
}
.chat-item-content {
// border: 1px solid #ddd;
// padding: 6px;
// border-radius: 5px;
background: var(--chat-item-bg);
border-radius: 0px 8px 8px 8px;
box-shadow: 0px 3px 12px rgba(0, 0, 0, 0.07);
padding: 10px 20px;
}
.chat-item-logo {
width: 45px;
height: 43px;
background-position: -758px -23px;
}
.chat-option-content {
padding: 5px 15px;
background: #ffffff;
border-radius: 8px;
box-shadow: 0px 3px 12px rgba(0, 0, 0, 0.07);
margin-top: 10px;
float: right;
}
}
}
}
// 引用样式调整
.chat-content {
max-height: 500px;
overflow-y: auto;
padding-right: 10px;
.chat-header {
margin-bottom: 5px;
.chat-head-title {
font-size: 20px;
font-weight: bold;
font-family: 'Noto Sans S Chinese';
margin-bottom: 5px;
}
.chat-head-subtitle {
font-size: var(--fontSize);
color: #222222;
}
}
.chat-link {
color: #ffffff;
background: var(--color-main);
display: inline-block;
padding: 2px 10px;
border-radius: 5px;
cursor: pointer;
}
}
.chat-think-title {
padding: 10px;
box-sizing: border-box;
font-family: 'Noto Sans S Chinese';
background-color: var(--theme-area-bg);
border-radius: 5px;
border: 1px solid var(--think-border-color);
margin-bottom: 10px;
.chat-think-head {
margin-bottom: 5px;
cursor: pointer;
}
.chat-think-body {
max-height: 10000px;
/* 设置一个足够大的值 */
padding: 0 10px;
overflow-y: auto;
font-size: var(--fontSize);
color: var(--think-font-color);
transition: max-height 0.5s ease;
/* 过渡效果 */
}
.chat-think-hidden {
max-height: 0;
overflow: hidden;
}
}
.chat-content-clear {
text-align: center;
margin-top: 50px;
span {
font-size: var(--fontSizes);
}
&:hover {
color: #575a61;
}
}
.cloud-content {
cursor: pointer;
text-align: center;
}
.chat-save {
color: green !important;
}
.user-content {
border: 1px solid #dddddd;
border-radius: 5px;
padding: 10px;
box-sizing: border-box;
.user-head {
width: 100%;
}
.user-body {
max-height: 400px;
overflow-y: auto;
}
}
.knowledge-title {
font-size: var(--fontSizes);
margin-top: 10px;
color: #98a2b3;
}
.blue-pxnew {
background-image: url('src/assets/icon/pxnew-blue.png');
}
.red-pxnew {
background-image: url('src/assets/icon/pxnew-red.png');
}
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.chat-blink {
/* -webkit-animation: blink 1s steps(5, start) infinite; */
animation: blink 1s infinite;
/* content: "▋"; */
margin-left: 0.25rem;
vertical-align: baseline;
}
.chat-link-title {
width: 100%;
color: #537ac0;
font-size: var(--fontSize);
cursor: pointer;
}
.edit-history {
width: 100%;
padding: 0px 5px;
border: 2px solid var(--bg-color);
border-radius: 5px;
}
.answer-title {
color: var(--color-main) !important;
font-size: var(--fontSizes) !important;
cursor: pointer;
}
[contenteditable='true']:focus {
outline: none;
/* 取消默认的焦点样式 */
/* 设置边框颜色 */
}
.flex-content {
display: flex;
justify-content: space-between;
}
.font-color {
color: var(--color-main);
}
.item-tag {
padding: 2px;
border: 1px solid;
}
.tag-yellow {
border-color: #fda !important;
color: #d97b00 !important;
background-color: #fff6ea !important;
}
.extraction-content {
padding: 5px 10px;
color: var(--color-main) !important;
background: #ffffff;
border: 1px solid var(--color-main);
border-radius: 5px;
margin-top: 15px;
display: inline-block;
cursor: pointer;
}
.edit-user {
cursor: pointer;
color: var(--color-main);
position: absolute;
bottom: -20px;
right: 56px;
}
.edit-user-concal {
cursor: pointer;
color: var(--color-main);
position: absolute;
bottom: -20px;
right: 86px;
}
</style>
@@ -1,30 +0,0 @@
<template>
<div class="inteligent-helper">
<t-tabs v-model="tabIndex">
<t-tab-panel :value="1" label="交互式分析" :destroy-on-hide="false">
<InteractiveAnalysis/>
</t-tab-panel>
<t-tab-panel :value="2" label="分析素材" :destroy-on-hide="false">
</t-tab-panel>
</t-tabs>
</div>
</template>
<script setup>
import { ref } from 'vue';
import InteractiveAnalysis from './InteractiveAnalysis.vue';
defineProps({
msg: String,
})
const tabIndex = ref(1)
</script>
<style scoped>
.inteligent-helper {
z-index: 500;
color: #fff;
padding: 10px;
}
</style>
@@ -1,37 +0,0 @@
<template>
<div class="interactive-analysis">
<t-row :gutter="16">
<t-col :span="8">
<div>
<ChatContent/>
</div>
</t-col>
<t-col :span="4">
<div>
<AnalysisTool/>
</div>
</t-col>
</t-row>
</div>
</template>
<script setup>
import { ref } from 'vue';
import ChatContent from './ChatContent.vue';
import AnalysisTool from './AnalysisTool.vue';
defineProps({
msg: String,
})
const count = ref(0)
</script>
<style scoped>
.interactive-analysis{
z-index: 500;
color: #fff;
padding: 10px;
}
</style>
@@ -1,194 +0,0 @@
<template>
<div class="markdown-renderer markdown-body" v-html="renderedMarkdown"></div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import MarkdownIt from 'markdown-it'
import hljs from "highlight.js";
import "highlight.js/styles/github-dark.css";
const props = defineProps({
markdown: {
type: String,
default: "",
},
});
const md = new MarkdownIt({
html: true, // 启用 HTML 解析
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return (
'<pre class="hljs"><code>' +
hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
"</code></pre>"
);
} catch (__) {}
}
return (
'<pre class="hljs"><code>' + md.utils.escapeHtml(str) + "</code></pre>"
);
},
breaks: true,
linkify: true,
});
const renderedMarkdown = computed(() => {
return md.render(props.markdown);
});
</script>
<style scoped lang="less">
/* 覆盖 github-markdown-css 的一些样式,使其更美观 */
:deep(.markdown-body) {
font-size: var(--fontSize);
line-height: 1.6;
}
:deep(.markdown-body h1),
:deep(.markdown-body h2),
:deep(.markdown-body h3),
:deep(.markdown-body h4),
:deep(.markdown-body h5),
:deep(.markdown-body h6) {
margin-top: 24px;
margin-bottom: var(--fontSize);
font-weight: 600;
line-height: 1.25;
}
:deep(.markdown-body h1) {
font-size: 2em;
border-bottom: 1px solid #eaecef;
padding-bottom: 0.3em;
}
:deep(.markdown-body h2) {
font-size: 1.5em;
border-bottom: 1px solid #eaecef;
padding-bottom: 0.3em;
}
:deep(.markdown-body h3) {
font-size: 1.25em;
}
:deep(.markdown-body p) {
margin-top: 0;
margin-bottom: var(--fontSize);
}
:deep(.markdown-body ul),
:deep(.markdown-body ol) {
margin-top: 0;
margin-bottom: var(--fontSize);
padding-left: 2em;
}
:deep(.markdown-body li) {
margin-top: 0.25em;
}
:deep(.markdown-body pre) {
margin-top: 0;
margin-bottom: var(--fontSize);
padding: var(--fontSize);
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f6f8fa;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
:deep(.markdown-body code) {
padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
background-color: rgba(27, 31, 35, 0.05);
border-radius: 3px;
}
:deep(.markdown-body pre code) {
padding: 0;
background-color: transparent;
border: 0;
}
:deep(.markdown-body a) {
color: #0366d6;
text-decoration: none;
transition: color 0.2s;
}
:deep(.markdown-body a:hover) {
text-decoration: underline;
color: #0353a4;
}
:deep(.markdown-body blockquote) {
padding: 0 1em;
color: #6a737d;
border-left: 0.25em solid #dfe2e5;
margin: 0 0 var(--fontSize) 0;
background-color: #f6f8fa;
border-radius: 0 6px 6px 0;
}
:deep(.markdown-body img) {
max-width: 100%;
box-sizing: content-box;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
:deep(.markdown-body table) {
display: block;
width: 100%;
overflow: auto;
margin-bottom: var(--fontSize);
border-spacing: 0;
border-collapse: collapse;
}
:deep(.markdown-body table th) {
font-weight: 600;
background-color: #f6f8fa;
}
:deep(.markdown-body table th),
:deep(.markdown-body table td) {
padding: 6px 13px;
border: 1px solid #dfe2e5;
}
:deep(.markdown-body table tr) {
background-color: #fff;
border-top: 1px solid #c6cbd1;
}
:deep(.markdown-body table tr:nth-child(2n)) {
background-color: #f6f8fa;
}
:deep(.markdown-body .task-list-item) {
list-style-type: none;
}
:deep(.markdown-body .task-list-item input[type="checkbox"]) {
margin: 0 0.2em 0.25em -1.6em;
vertical-align: middle;
}
:deep(.markdown-body) {
hr {
height: 2px;
background: #559ffe;
border-color: #559ffe;
}
}
</style>
@@ -1,71 +0,0 @@
<template>
<div class="scrollingloading" ref="targetElement">
</div>
</template>
<script>
export default {
data() {
return {
isIntersecting: false,
targetElement: null,
observer: null
}
},
props: {
onState: {
type: Boolean,
default: false
},
},
methods: {
initObserver() {
this.targetElement = this.$refs.targetElement
this.observer = new IntersectionObserver(this.handleIntersection, {
threshold: 1 // 当元素尺寸进入视口时触发,0-1代表0%-100%
});
if (this.targetElement) {
this.observer.observe(this.targetElement);
}
},
stopObserver() {
if (this.targetElement && this.observer) {
this.observer.unobserve(this.targetElement);
}
},
handleIntersection(entries, observer) {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.isIntersecting = true;
console.log(this.onState);
if (this.onState) {
this.$emit('onChange')
}
console.log('元素出现在了视图中,开始触发事件');
} else {
this.isIntersecting = false;
console.log('元素在视图中不可见');
}
});
}
},
mounted() {
this.initObserver();
console.log('初始化');
},
beforeDestroy() {
this.stopObserver();
},
}
</script>
<style lang="less">
.scrollingloading {
width: 100%;
height: 10px;
}
</style>
@@ -1,391 +0,0 @@
<template>
<div class="searchTaskList" :style="{ 'height': height }">
<div class="filter">
<t-input style="flex: 1;" v-model="keyword" placeholder="请输入内容搜索" @enter="resetSearch" />
<t-select style="width: 200px" v-if="searchType === 'task'" v-model="selectTaskStatus"
@change="resetSearch">
<t-option v-for="(item, index) in statusList" :label="item.label" :value="item.value" :key="index" />
</t-select>
<t-date-range-picker :presets="presets" style="width: 240px;" @change="resetSearch" v-model="time"
enable-time-picker />
<div class="tabs-box" v-if="searchType === 'task'">
<span class="sortBtn" :class="{ 'sort-active': range === 'all' }" @click="switchRane('all')">全部</span>
<span class="sortBtn" :class="{ 'sort-active': range === 'me' }" @click="switchRane('me')">我的</span>
</div>
</div>
<AddTaskDialog :taskSuperiorFlag="taskSuperiorFlag" @success="addTaskSuccess" />
<div class="list">
<template v-for="(item, index) in taskList" :key="index">
<div class="item cursor-pointer" @click="selectTask(item)">
<div class="task-status-logo" :class="handleBg(item.taskStatus)">{{ handleStatus(item.taskStatus) }}
</div>
<div class="item-title">{{ item.taskName }}</div>
<div class="tab-list">
<span class="item-tab tab-green">{{ item.taskDirection }}</span>
<span class="item-tab tab-lightblue">{{ taskLevel[item.taskLevel] }}</span>
<span v-if="searchType === 'task'" class="item-tab"
style="background-color:skyblue;border-color: goldenrod;">{{ item.ifCreateTask === 0 ?
'已创建任务分析' : '未创建任务分析'
}}</span>
</div>
<div class="item-content text-ellipsis ellipsis3">
{{ item.taskContent }}
</div>
<div class="item-footer flex-content">
</div>
<div class="item-footer flex-content">
<span class="item-label">
{{ sourceType[item.sourceType] }}
</span>
<span v-if="item['sumAll']" class="item-label">完成度 {{ item['endCount'] }}/{{
item['sumAll'] }}</span>
<span class="item-date">
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
</span>
</div>
</div>
</template>
<scrollingloading :onState="isScrolling" @onChange="scrollingloadingChange()" />
</div>
<t-back-top container=".list" size="small" theme="light" :visible-height="100" :offset="[20, 20]"
style="position: absolute; right: 20px; bottom: 0"></t-back-top>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, watch } from 'vue';
import axios from "@/utils/request"
import taskStatus from '@/common/taskStatus';
import taskLevel from '@/common/taskLevel';
import sourceType from "@/common/sourceType"
import dayjs from 'dayjs'
import scrollingloading from "@/components/scrollingloading/index.vue"
import AddTaskDialog from "@/components/dialog/addTaskDialog.vue"
import { useRoute } from 'vue-router';
const route = useRoute()
const time = ref([])
const keyword = ref('')
const taskList = ref([])
const emit = defineEmits(['selectTask', 'updateRange'])
const isScrolling = ref(false) //是否开起滚动加载
const presets = ref({
最近7天: [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()],
最近3天: [dayjs().subtract(2, 'day').toDate(), dayjs().toDate()],
今天: [dayjs().toDate(), dayjs().toDate()],
});
const statusList = ref([
{ label: "分析中", value: "20", bgClass: 'pendinganalysis' },
{ label: "已完成", value: "25,30,40,60,70,80,90,100,110", bgClass: 'completed' },
{ label: "未通过", value: "50,120", bgClass: 'failed' },
])
const props = defineProps({
range: String,
height: {
default: '100%'
},
searchType: {
default: 'task'
},
taskDirection: {
default: 'all'
},
taskSuperiorFlag: {
default: null
}
})
const addTaskSuccess = () => {
resetSearch()
}
//滚动加载事件
const scrollingloadingChange = () => {
console.log('开始加载数据');
pageNum.value++
if (props.searchType === 'task') {
getListData();
} else if (props.searchType === 'action') {
taskActionSearch();
}
}
const resetSearch = () => {
pageNum.value = 1
taskList.value = []
if (props.searchType !== 'task') {
taskActionSearch();
} else {
getListData();
}
}
// 行动任务搜索
const taskActionSearch = async () => {
console.log(props.taskDirection);
isScrolling.value = false
const params = {
pageNo: pageNum.value,
pageSize: pageSize.value,
orderBy: 'createTime',
orderByType: 'desc',
type: 'task',
keyword: keyword.value,
taskDirection: props.taskDirection == 'all' ? null : props.taskDirection
}
console.log(params);
let urlCount = environment['b1ConsumerUrl'] + '/taskActionSearch/getActionSearchCount';
await axios.get(urlCount, { params }).then(res => {
console.log('res', res);
total.value = res.data;
})
let url = environment['b1ConsumerUrl'] + '/taskActionSearch/getActionSearch';
axios.get(url, { params }).then(res => {
console.log('res', res);
let result = res.data;
taskList.value = [...taskList.value, ...result];
if (total.value > taskList.value.length) {
isScrolling.value = true
}
})
}
const switchRane = (d) => {
emit('updateRange', d)
};
watch(() => props.range, (newRange) => {
resetSearch()
});
watch(() => props.taskDirection, (newRange) => {
console.log(newRange);
resetSearch()
});
const handleStatus = d => {
return searchStatus(d).label
}
const handleBg = d => {
return searchStatus(d).bgClass
}
const searchStatus = d => {
let obj
statusList.value.forEach(i => {
let arr = i.value.split(',').map(Number)
if (arr.includes(d)) {
obj = i
}
})
return obj
}
const selectTaskStatus = ref('20,25,30,40,60,70,80,90,100,110,50,120')
const pageNum = ref(1)
const pageSize = ref(10)
const total = ref(0)
const url = ref('')
const getListData = () => {
isScrolling.value = false
let params = {
content: keyword.value,
endTime: time.value[1],
pageNum: pageNum.value,
pageSize: pageSize.value,
startTime: time.value[0],
taskStatus: selectTaskStatus.value,
taskDirection: props.taskDirection == 'all' ? null : props.taskDirection
}
let allUrl = props.range === 'all' ? url.value : '/taskAnalyseSearch/cop-my-tasks';
allUrl = environment['b1ConsumerUrl'] + allUrl
axios.get(allUrl, { params }).then(res => {
total.value = res.data.total
taskList.value = [...taskList.value, ...res.data.records]
if (total.value > taskList.value.length) {
isScrolling.value = true
}
})
}
const selectTask = d => {
console.log(d);
emit('selectTask', d)
}
onMounted(() => {
console.log(route.path);
if (route.path.indexOf('/action/task') == -1) {
selectTaskStatus.value = '0,10,20,25,30,40,60,70,80,90,100,110,50,120'
url.value = '/taskAnalyseSearch/cop-task-three-list'
statusList.value = [
{
label: '全部',
value: '0,10,20,25,30,40,60,70,80,90,100,110,50,120'
},
{ label: "待分析", value: "0,10", bgClass: 'uncompleted' },
...statusList.value
]
} else {
selectTaskStatus.value = '20,25,30,40,60,70,80,90,100,110,50,120'
url.value = '/taskAnalyseSearch/cop-task-list'
statusList.value = [
{
label: '全部',
value: '20,25,30,40,60,70,80,90,100,110,50,120'
},
...statusList.value
]
}
resetSearch()
})
</script>
<style scoped lang='less'>
/* Your styles here */
.searchTaskList {
width: 100%;
padding: 20px 0;
display: flex;
flex-direction: column;
position: relative;
width: 450px;
.filter {
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 0 20px;
.tabs-box {
display: flex;
gap: 10px;
.sortBtn {
padding: 0 10px;
border: 1px solid #2b8ffe99;
color: #fff;
line-height: 32px;
cursor: pointer;
border-radius: 3px;
margin: 0;
}
.action {
background-color: linear-gradient(123.69deg, #2b8ffe, #0062d9) !important;
}
}
}
.list {
height: calc(100% - 76px);
padding: 20px;
overflow-y: auto;
.item {
padding: 10px;
background: rgba(0, 98, 217, 0.1);
border: 1px solid;
border-color: rgba(85, 166, 254, 0.1);
border-radius: 4px;
margin-bottom: 15px;
position: relative;
overflow: hidden;
&:hover {
background: rgba(0, 98, 217, 0.4);
border: 1px solid;
border-color: #0062d9;
}
.task-status-logo {
width: 132px;
height: 30px;
transform: rotate(45deg);
position: absolute;
top: 12px;
color: #fff;
text-align: center;
line-height: 30px;
right: -36px;
}
.completed {
background: linear-gradient(134.57deg, #05b89f 14.74%, #007f73 84.95%);
}
.uncompleted {
background: linear-gradient(134.57deg, #d09a14 14.74%, #a76e12 84.95%);
}
.pendinganalysis {
background: linear-gradient(134.57deg, #117dff 14.74%, #0057b8 84.95%);
}
.failed {
background: linear-gradient(134.57deg, #838383 14.74%, #5e5e5e 84.95%);
}
.item-title {
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #ffffff;
font-size: var(--titleSize);
line-height: 24px;
margin-bottom: 10px;
}
.tab-list {
margin-bottom: 10px;
.item-tab {
display: inline-block;
line-height: var(--fontSizes);
padding: 5px 10px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSizes);
border: 1px solid;
border-radius: 4px;
margin-right: 5px;
}
}
.item-content {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSize);
margin-bottom: 10px;
}
.item-footer {
.item-label {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: rgba(255, 255, 255, 0.8);
font-size: var(--fontSize);
}
.item-date {
line-height: 24px;
font-family: Noto Sans S Chinese;
font-weight: 300;
color: rgba(255, 255, 255, 0.8);
font-size: var(--fontSize);
}
}
}
}
}
</style>
@@ -1,22 +0,0 @@
<template>
<t-space direction="vertical" class="flex-center">
<t-empty />
</t-space>
</template>
<script setup>
import { ref } from "vue";
defineProps({
msg: String,
});
const count = ref(0);
</script>
<style scoped>
.task-analysis {
z-index: 500;
color: #fff;
}
</style>
@@ -1,279 +0,0 @@
<template>
<t-tabs :default-value="1" @change="tabChange">
<t-tab-panel :value="1">
<template #label>
<div class="tabLabel">
<!-- <div style="width: 40px;height: 40px;"><img style="width: 100%;" src="/src/assets/icon/u8647.png" alt=""
srcset=""></div> -->
<div>
<div>相关资源</div>
</div>
</div>
<div class="biao" style="color: red;background-image: url('/src/assets/icon/u8649.svg');">差</div>
</template>
<div>
任务相关媒资总数134条,相似任务中平均使用媒资80条。</div>
<div ref="resourcePieRef" class="item-pie"></div>
</t-tab-panel>
<t-tab-panel :value="2">
<template #label>
<div class="tabLabel">
<!-- <div style="width: 40px;height: 40px;"><img style="width: 100%;" src="/src/assets/icon/u8653.png" alt=""
srcset=""></div> -->
<div>
<div>相关账号</div>
</div>
</div>
<div class="biao" style="color: #4B7902;background-image: url('/src/assets/icon/u8654.svg');">良</div>
</template>
<div>任务相关账号总共{{ accountAndCWB.totalCount }}个。</div>
<div ref="accountPieRef" class="item-pie"></div>
</t-tab-panel>
<t-tab-panel :value="3">
<template #label>
<div class="tabLabel">
<!-- <div style="width: 40px;height: 40px;"><img style="width: 100%;" src="/src/assets/icon/u8658.png" alt=""
srcset=""></div> -->
<div>
<div>CWB情况</div>
</div>
</div>
<div class="biao" style="color: #7B4D12;background-image: url('/src/assets/icon/u8659.svg');">优</div>
</template>
<div>
任务相关目标总计{{ accountAndCWB.targetCount }}个,其中{{ accountAndCWB.cbwTargetCount }}个可通过CBW触达,涉及CBW{{
accountAndCWB.cbwCount
}}张。
</div>
<div class="cbw-item" v-for="(v, k, index) in accountAndCWB.cwb">
<div class="cbw-item-title" :style="{ 'background': colorList[index] }">{{ k }}</div>
<div class="cbw-item-content">
<div class="item" v-for="i in v">
{{ i.username }}
</div>
</div>
</div>
</t-tab-panel>
</t-tabs>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import * as echarts from "echarts";
import { requestAxiosService } from "@/service/request-axios-service"; // 引入发送异步请求方法
import { Background } from "@antv/g6";
const service = requestAxiosService;
const props = defineProps({
msg: String,
taskId: Number
});
onMounted(() => {
resourcePieChart();
getAccountAndCWBData()
});
const colorList = [
'#169bd5',
'#008080',
'#7b4d12', '#169bd5',
'#008080',
'#7b4d12', '#169bd5',
'#008080',
'#7b4d12', '#169bd5',
'#008080',
'#7b4d12',
]
const cbwList = {
传播网id1: [{ username: '名字' }, { username: '名字' }],
传播网id2: [{ username: '名字' }, { username: '名字' }],
传播网id3: [{ username: '名字' }, { username: '名字' }],
传播网id4: [{ username: '名字' }, { username: '名字' }]
}
;
const description = ref("任务相关媒资总数134条,相似任务中平均使用媒资80条。");
const resourcePieRef = ref(null);
const accountPieRef = ref(null);
const tabChange = (value) => {
console.log(value);
nextTick(() => {
if (value === 1) {
resourcePieChart();
} else if (value === 2) {
description.value =
"";
accountPieChart();
} else {
description.value =
"任务相关目标总计85个,其中32个可通过CBW触达,涉及CBW3张。";
}
});
};
// 信息分析饼状图
const resourcePieChart = () => {
if (resourcePieRef.value) {
let chartPie = echarts.init(resourcePieRef.value);
const option = {
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b}: {c} ({d}%)",
},
legend: {
orient: "vertical",
left: "right",
top: "center",
data: ['文本', '图片', '音频', '视频'],
textStyle: {
color: "#fff",
},
},
series: [
{
name: "数据类型",
type: "pie",
radius: "75%",
center: ["25%", "40%"],
label: {
show: false,
},
labelLine: {
show: false, // 隐藏标签线
},
data: [{
name: '文本',
value: 134
}, {
name: '图片',
value: 42
}, {
name: '音频',
value: 33
}, {
name: '视频',
value: 14
}],
},
],
};
chartPie.setOption(option);
}
};
const accountAndCWB = ref({})
const getAccountAndCWBData = () => {
service
.get(`${environment["b1ConsumerUrl"]}/cop/copOwnAccount`, {
taskId: props.taskId,
}).then(res => {
accountAndCWB.value = res.data;
accountPieChart()
})
}
const accountPieChart = () => {
if (accountPieRef.value) {
let data = accountAndCWB.value.personas.map(i => {
return {
value: i.personaAccountCount,
name: i.persona.split(',')[0]
}
})
console.log(data);
let chartPie = echarts.init(accountPieRef.value);
const option = {
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b}: {c} ({d}%)",
},
legend: {
orient: "vertical",
left: "right",
top: "center",
data: data.map(i => i.name),
textStyle: {
color: "#fff",
},
},
series: [
{
name: "数据类型",
type: "pie",
radius: "75%",
center: ["25%", "40%"],
label: {
show: false,
},
labelLine: {
show: false, // 隐藏标签线
},
data: data,
},
],
};
chartPie.setOption(option);
}
};
const count = ref(0);
</script>
<style scoped>
.task-analysis {
z-index: 500;
color: #fff;
}
.tabLabel {
display: flex;
justify-content: space-between;
position: relative;
}
.biao {
position: absolute;
top: 0;
left: -20px;
width: 30px;
line-height: var(--fontSize);
font-size: 12px;
font-weight: 400;
-webkit-transform: rotate(324deg);
-moz-transform: rotate(324deg);
-ms-transform: rotate(324deg);
transform: rotate(324deg);
}
.item-pie {
width: 335px;
height: 200px;
}
.cbw-item {
display: flex;
width: 335px;
line-height: 27px;
}
.cbw-item-title {
line-height: 27px;
padding: 0 10px;
border-radius: 5px;
margin: 2px 0;
}
:deep(.t-tabs__nav-item.t-size-m) {
line-height: 1;
}
:deep(.t-tabs__nav-item-wrapper) {
color: #fff;
text-align: center;
}
:deep(.t-tabs__nav-item.t-is-active) {
background-color: rgba(22, 155, 213, 1);
border-radius: 8px;
}
.cbw-item-content {
display: flex;
gap: 5px;
}
</style>
@@ -1,32 +0,0 @@
<template>
<div class="tabs-list" :style="tabStyle">
<template v-for="(item, index) in tabsList" :key="index">
<div class="tabs-item" :class="{ 'item-active': tabIndex === item.value }" @click="tabsDataChange(item.value)">
{{ item.label }}
</div>
</template>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue'
defineProps({
tabsList: Array,
tabIndex: [String, Number, Object, Boolean, Array],
tabStyle: {}
})
const emit = defineEmits(['tabsDataC']) // 定义子组件发送给父组件的函数
const count = ref(0)
const tabsDataChange = (value) => {
console.log('点击了', value)
emit('tabsDataC', value)
}
</script>
<style scoped>
.task-analysis {
z-index: 500;
color: #fff;
}
</style>
-17
View File
@@ -1,17 +0,0 @@
import { createApp } from "vue";
import TDesign from "tdesign-vue-next";
import "tdesign-vue-next/es/style/index.css";
import "leaflet/dist/leaflet.css";
import "./style.css";
import "./style/theme.less";
import "./style/tdesign-style.less";
import "./assets/markdown-css/github-markdown.css";
import App from "./App.vue";
import store from "./store";
import router from "./router";
const app = createApp(App);
app.use(store);
app.use(router);
app.use(TDesign);
app.mount("#app");
@@ -1,91 +0,0 @@
import { path } from "d3";
import { createRouter, createWebHashHistory } from "vue-router";
const routes = [
{
path: "/",
redirect: "/situation-overview",
},
// {
// path: "/test",
// component: () => import("@/views/situation-overview.vue"),
// },
{
path: "/situation-overview",
component: () => import("@/views/situation-overview/index.vue"),
redirect: "/situation-overview/action",
children: [
{
path: "/situation-overview/monitoring",
name: "监测模式",
},
{
path: "action",
name: "行动模式",
redirect: "/situation-overview/action/sentiment",
children: [
{
path: "sentiment",
component: () => import("@/views/sentiment/index.vue"),
},
{
path: "task",
component: () => import("@/views/task/index.vue"),
},
{
path: "action",
component: () => import("@/views/action/index.vue"),
},
{
path: "reply",
component: () => import("@/views/reply/index.vue"),
},
{
path: "control",
component: () => import("@/views/control/index.vue"),
},
{
path: "homeoverview",
component: () => import("@/views/homeOverview/index.vue"),
},
],
},
{
path: "query",
name: "查询模式",
},
],
},
{
path: "/dashboard",
name: "Dashboard",
component: () => import("@/views/Dashboard.vue"),
},
{
path: "/chart",
name: "Chart",
component: () => import("../views/Chart.vue"),
},
{
path: "/map",
name: "Map",
component: () => import("../views/Map.vue"),
},
{
path: "/graph",
name: "Graph",
component: () => import("../views/Graph.vue"),
},
{
path: "/GroupContent",
name: "GroupContent",
component: () => import("../views/GroupContent.vue"),
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router;
@@ -1,47 +0,0 @@
import service from "../utils/request";
export const requestAxiosService = {
get: async (endpoint, params = {}) => {
try {
const response = await service.get(endpoint, { params });
return response;
} catch (error) {
throw new Error(`GET request failed: ${error.message}`);
}
},
post: async (endpoint, data = {}) => {
try {
const response = await service.post(endpoint, data);
return response;
} catch (error) {
throw new Error(`POST request failed: ${error.message}`);
}
},
put: async (endpoint, data = {}) => {
try {
const response = await service.put(endpoint, data);
return response;
} catch (error) {
throw new Error(`PUT request failed: ${error.message}`);
}
},
update: async (endpoint, data = {}) => {
try {
const response = await service.patch(endpoint, data);
return response;
} catch (error) {
throw new Error(`patch request failed: ${error.message}`);
}
},
delete: async (endpoint) => {
try {
const response = await service.delete(endpoint);
return response;
} catch (error) {
throw new Error(`DELETE request failed: ${error.message}`);
}
}
}
@@ -1,141 +0,0 @@
import { environment } from '@/environments/environment';
import { requestCrudService } from '@/service/request-axios-service';
export interface GetOneParam {
fields?: string[]; // 指定返回的字段 id,name
join?: string[]; // 指定关联表,如 ['user||id,name', 'relation||id']
cache?: 0 | 1; // 0直接DB,1缓存,默认1缓存
}
export interface GetAllParam {
fields?: string[] | string; // 指定返回的字段: 多个字段如 'id', 'name'
filter?: string[]; // 指定过滤条件,如 ['name||$eq||batman', 'id||$in||1,2,3', 'field||$isnull', 'relation.id||$eq||1']
or?: string[]; // 同filter
join?: string[]; // 指定关联表,如 ['user||id,name', 'relation||id']
cache?: 0 | 1; // 0直接DB,1缓存,默认1缓存
page?: number; // 页码(会被转换成offset)
offset?: number; // 偏移量
limit?: number; // 每页数量
sort?: string[]; // 排序,如 '['name,ASC', 'id,DESC]'
s?: string;
joinTaskAction?: boolean; //是否连行动表查询
joinTaskActionBoci?: boolean; //策略分析列表使用
}
export interface GetAllResponse {
data: any[];
page: number;
pageCount: number;
count: number;
total: number;
errMsg?: string;
}
let deepCopy = (str: any) => {
return JSON.parse(JSON.stringify(str));
}
export class RequestCrudService {
/**
* 获取单个对象
* @param url 地址
* @param id ID
* @param params 参数
* @returns 单个对象
*/
async getOne(url: string, id: number, params?: GetOneParam): Promise<any> {
const getUrl = `${url}/${id}`;
const res = await requestCrudService.get(getUrl, params);
if (res) return res;
// this.notification.create('error', getUrl, `${res.data}`);
return {};
}
/**
* 获取多个对象
* @param url 地址
* @param params 参数
* @returns 多个对象数组
*/
async getAll(url: string, params: GetAllParam | null = null): Promise<GetAllResponse> {
const formattedParams = deepCopy(params || {});
// 分页默认值
if (!params || !params.limit) formattedParams.limit = 999;
if (!params || !params.page) formattedParams.page = 1;
// 计算offset
if (params && formattedParams.page) {
formattedParams.offset = (formattedParams.page - 1) * formattedParams.limit!;
delete formattedParams.page;
}
const res = await requestCrudService.get(url, formattedParams);
if (res.status === 200) return res.data;
// this.notification.create('error', url, `${res.data}`);
return {
count: 0,
data: [],
page: 1,
pageCount: 1,
total: 0
};
}
/**
* 创建单个对象
* @param url 地址
* @param data 单个对象
* @returns 被创建的单个对象
*/
async createOne(url: string, data: any) {
const res = await requestCrudService.post(url, data);
if (res.status === 201) return res.data;
// this.notification.create('error', url, `${res.data}`);
return {};
}
/**
* 创建多个对象
* @param url 地址
* @param data 多个对象
* @returns 被创建的多个对象
*/
async createMulti(url: string, data: any[]) {
const res = await requestCrudService.post(url, {
bulk: data
});
if (res.status === 201) return res.data;
// this.notification.create('error', url, `${res.data}`);
return [];
}
/**
* 更新单个对象
* @param url 地址
* @param id ID
* @param data 单个对象部分键值对
* @returns 被修改的单个对象
*/
async updateOne(url: string, id: number, data: any) {
const res = await requestCrudService.update(`${url}/${id}`, data);
if (res.status === 200) return res.data;
// this.notification.create('error', url, `${res.data}`);
return {};
}
/**
* 删除单个对象
* @param url 地址
* @param id ID
* @returns 操作结果
*/
async deleteOne(url: string, id: number) {
const res = await requestCrudService.delete(`${url}/${id}`);
if (res.status === 200) return res.data;
// this.notification.create('error', url, `${res.data}`);
return 'error';
}
// get 请求
async simpleGet(url: string, params?: any): Promise<any> {
const res = await requestCrudService.get(url, params);
if (res.status === 200) return res.data;
return res.data;
}
// post 请求
async simplePost(url: string, data: any) {
const res = await requestCrudService.post(url, data);
if (res.status === 200) return res.data;
return res.data;
}
}
-43
View File
@@ -1,43 +0,0 @@
import { createStore } from 'vuex'
export default createStore({
state: {
count: 0,
user: null,
taskData: null,
targetList: [],
targetData: null,
actionOverview: null,
},
getters: {
doubleCount: (state) => state.count * 2
},
mutations: {
increment(state) {
state.count++
},
setUser(state, user) {
state.user = user
},
setTaskData(state, value) {
state.taskData = value;
},
setTargetList(state,value) {
state.targetList = value
},
setTargetData(state,value) {
state.targetData = value
},
setActionOverview(state,value) {
state.actionOverview = value;
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
},
modules: {}
})
Whitespace-only changes.
@@ -1,134 +0,0 @@
:root {
--td-brand-color-8: '#0062d9';
}
.t-input {
border-color: var(--td-brand-color);
}
.t-range-input {
border-color: var(--td-brand-color);
}
.t-layout, .t-layout__direction-vertical {
background: none;
}
.t-layout__sider {
background: none;
}
// 对话框样式
.t-dialog {
background: #0f2844 !important;
border: 1px solid;
border-color: #2b8ffe99;
padding: 20px 25px;
border-radius: 4px;
}
.t-dialog__header {
border-radius: 4px 4px 0 0;
padding: 20px 25px;
padding-top: 0;
box-shadow: none !important;
position: relative;
font-weight: 500;
color: #fff;
font-size: 25px;
background: url('../assets/icon/title-header-logo.svg') no-repeat bottom center !important;
}
.t-dialog__header .t-dialog__header-content {
justify-content: center;
}
.t-button--variant-base {
color: #FFFFFF;
background: rgba(3, 38, 80, 0.8);
border-color: rgba(43, 143, 254, 0.6);
}
.t-button--variant-base.t-button--theme-primary {
background: linear-gradient(102.76deg, #2b8ffe 0%, #0062d9 100%);
}
.t-drawer__close-btn {
display: none;
}
// 抽屉样式
.t-drawer__content-wrapper {
background: #021e3e;
}
.t-drawer__header {
background: linear-gradient(90deg, #0062d9 0%, rgba(0, 98, 217, 0) 100%);
border-bottom: 2px solid;
border-color: rgba(255, 255, 255, 0.2);
}
.t-drawer__body {
background: #021e3e;
}
// 标签页
.t-tabs {
background-color: transparent;
}
.t-tabs__nav-item.t-is-active {
font-size: var(--fontSize);
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #0062d9;
}
.t-tabs__nav-item {
font-size: var(--fontSize);
}
.t-tabs__bar {
background-color: #0062d9;
}
.t-tabs__nav-container.t-is-top::after {
background: rgba(85, 159, 254, 0.2);
}
.t-tabs__nav-item:not(.t-is-disabled):not(.t-is-active):hover .t-tabs__nav-item-wrapper{
background: rgba(255, 255, 255, 0.1);
}
.t-tabs__btn {
background: rgba(3, 38, 80, 0.8);
border-color: #053e59 !important;
}
.t-tabs__btn:hover {
background: rgba(3, 38, 80, 0.8);
}
.t-tabs__nav-item.t-size-m {
line-height: 18px;
height:auto;
}
.t-tabs__nav--card.t-tabs__nav-item:not(.t-is-disabled):not(.t-is-active):hover {
background:linear-gradient(103.13deg,#2b8ffe 0%,#0062d9 100%);
border-radius:24px;
}
// 单选框样式
.t-radio-group.t-radio-group--filled{
background-color: #08527b;
border-color: #08527b;
}
.t-radio-group.t-radio-group--filled .t-radio-button.t-is-checked {
background-color: #07324c;
}
.markdown-body{
background: transparent !important;
color: #FFFFFF !important;
}
-676
View File
@@ -1,676 +0,0 @@
:root {
--color-main: #0062d9;
--bg-color: #012044;
--fontSize: 18px;
--titleSize: 20px;
--fontSizes: 16px;
--chatSize: calc(18 * var(--px-to-vw));
--router-bg-color: #f8ccce;
--three-color: #fbd8da;
--task-button-bgcolor: #feeded;
--tag-border-color: #006efe;
--antd-wave-shadow-color: #ce2d39;
--border-color: linear-gradient(180deg, #f86c73, #ce1322);
--theme-img: url("/assets/icon/layout-bg-theme-red.png");
--them-icon: url("/assets/icon/pxcook-red.png");
--ant-modal-header: url("/assets/overiew-logo/ant-modal-header-bg.svg");
--theme-area-bg: rgba(3, 38, 80, 0.6);
--theme-area-border-color: rgba(43, 143, 254, 0.6);
--theme-box-shadow: 0px 8px var(--fontSize) rgba(139, 139, 139, 0.06);
--theme-font-color: #ffffff;
--theme-font-text: rgba(255, 255, 255, 0.6);
--theme-font-details: rgba(255, 255, 255, 0.8);
--theme-card-bg: rgba(0, 98, 217, 0.2);
--theme-card-border: rgba(85, 166, 254, 0.1);
--theme-card-active: rgba(0, 98, 217, 0.4);
--theme-active-border: #0062d9;
--theme-border-color: #559ffe;
--index-color: #ffffff;
--extract-content-bg: rgba(3, 38, 80, 0.6);
--logo-color: #45c3f3;
--user-item-bg: rgba(0, 98, 217, 0.3);
--chat-item-bg: rgba(0, 98, 217, 0.2);
--chat-content-bg: rgba(0, 98, 217, 0.1);
--think-content-bg: rgba(0, 98, 217, 0.2);
--think-border-color: #0062d9;
--think-logo-color: #45c3f3;
--think-font-color: rgba(255, 255, 255, 0.6);
--chat-source-color: #0079fe;
--chat-source-bg: rgba(0, 98, 217, 0.2);
--extraction-content-bg: #00368c;
--extraction-content-border: #0056c6;
--extraction-content-color: rgba(255, 255, 255, 0.8);
--chat-input-border: #0062d9;
--list-item-bg: var(--chat-item-bg);
--list-item-border: var(--theme-card-border);
--list-item-active: var(--theme-card-active);
--list-item-active-border: var(--theme-active-border);
--drawer-bg-color: #021e3e;
--table-border-color: #1592e6;
--table-bg-color: #01316d;
--time-range-th-bg-color: #012b6e;
--time-range-th-font-color: #ffffff;
--time-range-border-color: #006efe;
--time-range-font-color: #2b86fe;
--time-range-icon-color: #022043;
--time-range-tag-bg-color: #02387a;
--grap-container-bg: var(--chat-item-bg);
--full-screen: var(--bg-color);
--xwt-linear-gradient: linear-gradient(165.07deg, #0066cc 0%, #003366 100%);
--card-border: rgba(43, 143, 254, 0.6);
--item-borer-line: rgba(85, 159, 254, 0.2);
--gray-color: rgba(255, 255, 255, 0.6);
--item-index: var(--color-main);
--item-active-color: #0062d9;
--item-expand-color: #002c57;
--item-expand-active: #002d61;
--tag-bg-color: rgba(69, 195, 243, 0.5);
--theme-option-color: #023064;
--tag-border-bg: rgba(43, 143, 254, 0.6);
--file-tag-color: rgba(43, 143, 254, 0.6);
--ant-title-color: rgba(255, 255, 255, 0.8);
/* --heightVh: 920px; */
}
@font-face {
font-family: DDIN;
font-weight: normal;
src: url("../assets/font/D-Din/D-DIN.ttf");
unicode-range: U+0030-0039;
}
@font-face {
font-family: DDINBold;
font-weight: bold;
src: url("../assets/font/D-Din/D-DIN-Bold.ttf");
unicode-range: U+0030-0039;
}
@font-face {
font-family: Douyu;
src: url("../assets/font/DouyuFont/douyuFont-2.otf") format("opentype");
}
@font-face {
font-family: Noto Sans S Chinese;
font-weight: 100;
src: url("../assets/font/NotoSansHans/NotoSansHans-Thin-Windows.otf")
format("opentype");
}
@font-face {
font-family: Noto Sans S Chinese;
font-weight: 300;
src: url("../assets/font/NotoSansHans/NotoSansHans-Light.otf")
format("opentype");
}
@font-face {
font-family: Noto Sans S Chinese;
font-weight: 400;
src: url("../assets/font/NotoSansHans/NotoSansHans-Regular.otf")
format("opentype");
}
@font-face {
font-family: Noto Sans S Chinese;
font-weight: 500;
src: url("../assets/font/NotoSansHans/NotoSansHans-Medium.otf")
format("opentype");
}
@font-face {
font-family: Noto Sans S Chinese;
font-weight: 700;
src: url("../assets/font/NotoSansHans/NotoSansHans-Bold.otf")
format("opentype");
}
@font-face {
font-family: Noto Sans S Chinese;
font-weight: 900;
src: url("../assets/font/NotoSansHans/NotoSansHans-Black.otf")
format("opentype");
}
@font-face {
font-family: DS-Digital;
font-weight: 300;
src: url("../assets/font/DS-Digital/DS-DIGI-1.ttf") format("opentype");
}
@font-face {
font-family: DS-Digital;
font-weight: 400;
src: url("../assets/font/DS-Digital/DS-DIGIB-2.ttf") format("opentype");
}
@font-face {
font-family: DS-Digital;
font-weight: 500;
src: url("../assets/font/DS-Digital/DS-DIGII-3.ttf") format("opentype");
}
@font-face {
font-family: DS-Digital;
font-weight: 700;
src: url("../assets/font/DS-Digital/DS-DIGIT-4.ttf") format("opentype");
}
// flex 布局
.flex-center-center {
display: flex;
justify-content: center;
align-items: center;
}
.flex-start-center {
display: flex;
justify-content: flex-start;
align-items: center;
}
.flex-start-baseline {
display: flex;
justify-content: flex-start;
align-items: baseline;
}
.flex-start-start {
display: flex;
justify-content: flex-start;
align-items: start;
}
.flex-end-center {
display: flex;
justify-content: flex-end;
align-items: center;
}
.flex-end-start {
display: flex;
justify-content: flex-end;
align-items: start;
}
.flex-between-center {
display: flex;
justify-content: space-between;
align-items: center;
}
.flex-between-baseline {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.flex-between-start {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.flex-content {
display: flex;
justify-content: space-between;
}
.font-weight-bold {
font-family: "Noto Sans S Chinese";
font-weight: 700;
}
.margin-bottom-5 {
margin-bottom: 5px;
}
.margin-bottom-10 {
margin-bottom: 10px;
}
.margin-bottom-15 {
margin-bottom: 15px;
}
.margin-top-5 {
margin-top: 5px;
}
.margin-top-10 {
margin-top: 10px;
}
.margin-top-15 {
margin-top: 15px;
}
.padding-10 {
padding: 10px;
}
.cursor-pointer {
cursor: pointer;
}
// 字体设置
span,
p,
div,
li {
font-family: "Noto Sans S Chinese";
}
.markdown-body table tr {
background: transparent !important;
}
// 单选框样式
.tabs-box {
.sortBtn {
font-size: var(--fontSize);
line-height: var(--fontSize);
color: #fff;
display: inline-block;
padding: 5px 10px;
border-radius: 4px;
border: 1px solid #2b8ffe99 !important;
margin: 0 5px;
cursor: pointer;
}
.sort-active {
background: linear-gradient(123.69deg, #2b8ffe, #0062d9) !important;
}
}
// tab颜色
.tab-green {
background: rgba(0, 140, 119, 0.5);
border-color: #008c77 !important;
}
.tab-blue {
background: rgba(0, 121, 254, 0.1);
border-color: #0079fe !important;
}
.tab-lightblue {
background: rgba(69, 195, 243, 0.5);
border-color: #45c3f3 !important;
}
.tab-yellow {
background: #ff990080;
border-color: #f90 !important;
}
.tab-red {
background: rgba(207, 19, 34, 0.5);
border-color: #cf1322 !important;
}
// tab标签页
.tabs-list {
text-align: center;
.tabs-item {
background: rgba(0, 12, 28, 0.2);
color: rgba(255, 255, 255, 0.6);
display: inline-block;
font-size: var(--fontSize);
font-family: "Noto Sans S Chinese";
padding: 5px 15px;
cursor: pointer;
&:hover {
color: #ffffff;
background: rgba(255, 255, 255, 0.1);
border-bottom: 1.5px solid #ffffff;
}
}
.item-active {
color: #ffffff;
background: rgba(255, 255, 255, 0.1);
border-bottom: 1.5px solid #ffffff;
}
}
// 标题
.title-content-item {
line-height: 22px;
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #ffffff;
font-size: var(--titleSize);
border-bottom: 1px solid rgba(0, 98, 217, 0.4);
padding: 10px 15px;
position: relative;
&::after {
content: "";
display: block;
width: 4px;
height: var(--titleSize);
background: linear-gradient(167.47deg, #a2e1f9 0%, #45c3f3 100%);
border-radius: 2px 0px 2px 0px;
position: absolute;
top: 13px;
left: 0px;
}
}
.drawer-head {
font-size: 20px;
font-family: Noto Sans S Chinese;
font-weight: 300;
display: flex;
align-items: center;
cursor: pointer;
span {
margin-right: 5px;
}
}
.chat-source {
color: #80b7ff;
background-color: rgba(85, 159, 254, 0.3);
border-radius: 5px;
padding: 1px 5px;
text-align: center;
width: auto;
height: auto;
font-size: var(--fontSizes);
display: inline-block;
margin: 5px;
cursor: pointer;
}
.markdown-body hr {
height: 1.5px !important;
background: #559ffe !important;
border-color: #559ffe !important;
}
// 文字省略号
.text-ellipsis {
display: -webkit-box;
-webkit-box-orient: vertical;
/* 限制显示的行数 */
overflow: hidden;
text-overflow: ellipsis;
}
.ellipsis1 {
-webkit-line-clamp: 1;
}
.ellipsis2 {
-webkit-line-clamp: 2;
}
.ellipsis3 {
-webkit-line-clamp: 3;
}
.ellipsis4 {
-webkit-line-clamp: 4;
}
.ellipsis5 {
-webkit-line-clamp: 5;
}
// 逻辑图样式
.treemap-darkblue {
width: 100%;
// border: 2px solid;
// height: 100%;
color: lightblue;
padding: 0px;
user-select: none;
// padding: 10px;
border: 1px solid;
border-radius: 4px;
background-color: var(--chat-item-bg);
box-sizing: border-box;
box-shadow: 0px 0px 9px rgba(252, 131, 131, 0.27);
.treemap-title {
border-radius: 0;
}
.treemap-body {
color: #ffffff;
}
.node-red {
background: linear-gradient(
90deg,
rgba(245, 34, 45, 0.5) 0%,
rgba(138, 0, 8, 0.3) 100%
);
box-shadow: 0px 1px var(--fontSize) rgba(0, 0, 0, 0.08);
}
.node-orange {
background: rgba(255, 153, 0, 0.5);
border-bottom: 1px solid;
border-color: #ff9900;
}
.node-yellow {
background: rgba(255, 196, 0, 0.5);
border-bottom: 1px solid;
border-color: #ffc400;
}
.node-lightblue {
background: rgba(69, 195, 243, 0.5);
border-bottom: 1px solid;
border-color: #45c3f3;
}
.node-blue {
background: rgba(0, 121, 254, 0.5);
border-bottom: 1px solid;
border-color: #0079fe;
}
.node-purple {
background: rgba(125, 80, 255, 0.5);
border-bottom: 1px solid;
border-color: #7d50ff;
}
}
.pulse-content {
width: 34px;
height: 40px;
background: url("../assets/map-icon/map-point.svg") no-repeat;
background-size: cover;
display: flex;
align-items: center;
justify-content: center;
.user-logo {
width: 26px;
height: 26px;
margin-top: -5px;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
}
.overiew-content {
width: 400px;
padding: 10px;
box-sizing: border-box;
color: #ffffff;
border: 1px solid #2b8ffe99;
border-radius: 5px;
background-color: rgba(3, 38, 80, 0.8);
position: absolute;
top: -117px;
left: 310px;
z-index: 500;
.font-weight-bold {
font-size: 17px;
font-family: "Noto Sans S Chinese";
font-weight: 700;
}
.overiew-head {
padding: 10px 0;
display: flex;
align-items: center;
justify-content: space-between;
.user-logo {
width: 60px;
height: 60px;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
vertical-align: middle;
object-fit: cover;
}
}
.user-right {
flex: 1;
margin-left: 10px;
.user-title {
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #fff;
font-size: 18px;
line-height: 24px;
}
.user-text {
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #fff;
font-size: 16px;
line-height: 26px;
margin-top: 4px;
}
}
}
.overiew-body {
padding: 10px 0;
.typeLabel {
margin: 5px;
line-height: 28px;
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #55a6fe;
font-size: 16px;
display: flex;
align-items: center;
.details-logo {
width: 20px;
height: 14px;
background: url("../assets/icon/make-logo.svg") no-repeat;
background-size: cover;
margin-right: 5px;
}
}
.text-content {
font-family: Noto Sans S Chinese;
font-size: 16px;
padding: 0 10px;
}
}
.item-line {
border-bottom: 1px solid #2b8ffe99;
}
&::after {
content: "";
width: 200px;
height: 80px;
display: block;
background: url("../assets/map-icon/line-logo.png") no-repeat;
z-index: 1000;
position: absolute;
top: 75px;
left: -112px;
}
.text-content {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
word-break: break-all; /* 自动换行 */
white-space: normal; /* 允许正常换行 */
}
}
.item-line {
height: 1px;
background: rgba(85, 159, 254, 0.25);
margin: 10px 0;
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: -115px;
right: -305px;
z-index: 600;
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: transparent;
box-shadow: none;
}
.leaflet-popup-tip {
background: transparent !important;
}
.leaflet-left {
z-index: 100;
}
// 滚动条样式
// 滚动条整体样式
::-webkit-scrollbar {
/*滚动条整体样式*/
width: 6px !important;
height: 6px;
}
::-webkit-scrollbar-thumbk {
/*滚动条里面小方块*/
background: #ddd;
background: transparent;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
/*滚动条里面小方块*/
background: #555;
border-radius: 3px;
}
::-webkit-scrollbar-track {
/*滚动条里面轨道*/
background: transparent;
border-radius: 3px;
}
.t-dialog__body {
height: 70vh;
overflow-y: auto;
}
.t-dialog__ctx .t-dialog__position.t-dialog--top {
padding-top: 10vh;
}
@@ -1,34 +0,0 @@
import axios from "axios";
const request = axios.create({
baseURL: "/",
timeout: 10000000,
});
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
// if (token) {
// config.headers.authorization = `Bearer admin`
// }
config.headers.Authorization = `Bearer admin`;
console.log(config.headers.authorization);
return config;
},
(error) => {
return Promise.reject(error);
}
);
request.interceptors.response.use(
(response) => {
return response.data;
},
(error) => {
console.error("Request error:", error);
return Promise.reject(error);
}
);
export default request;
-128
View File
@@ -1,128 +0,0 @@
<template>
<div class="chart">
<t-layout>
<t-header>
<h1>图表演示</h1>
<nav>
<t-menu :value="activeMenu" @click="handleMenuClick" theme="light" mode="horizontal">
<t-menu-item value="/">首页</t-menu-item>
<t-menu-item value="/dashboard">仪表盘</t-menu-item>
<t-menu-item value="/chart">图表</t-menu-item>
<t-menu-item value="/map">地图</t-menu-item>
<t-menu-item value="/graph">关系图</t-menu-item>
</t-menu>
</nav>
</t-header>
<t-content>
<t-grid :columns="2" :gutter="20">
<t-grid-item>
<t-card title="柱状图">
<div ref="barChartRef" class="chart-item"></div>
</t-card>
</t-grid-item>
<t-grid-item>
<t-card title="折线图">
<div ref="lineChartRef" class="chart-item"></div>
</t-card>
</t-grid-item>
<t-grid-item>
<t-card title="饼图">
<div ref="pieChartRef" class="chart-item"></div>
</t-card>
</t-grid-item>
<t-grid-item>
<t-card title="散点图">
<div ref="scatterChartRef" class="chart-item"></div>
</t-card>
</t-grid-item>
</t-grid>
</t-content>
</t-layout>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import * as echarts from 'echarts'
const router = useRouter()
const activeMenu = ref('/chart')
const barChartRef = ref(null)
const lineChartRef = ref(null)
const pieChartRef = ref(null)
const scatterChartRef = ref(null)
const handleMenuClick = (value) => {
activeMenu.value = value
router.push(value)
}
onMounted(() => {
initBarChart()
initLineChart()
initPieChart()
initScatterChart()
})
const initBarChart = () => {
const chart = echarts.init(barChartRef.value)
chart.setOption({
xAxis: { data: ['A', 'B', 'C', 'D', 'E'] },
yAxis: {},
series: [{
data: [10, 20, 30, 40, 50],
type: 'bar'
}]
})
}
const initLineChart = () => {
const chart = echarts.init(lineChartRef.value)
chart.setOption({
xAxis: { type: 'category', data: ['1月', '2月', '3月', '4月', '5月'] },
yAxis: {},
series: [{
data: [120, 200, 150, 300, 250],
type: 'line'
}]
})
}
const initPieChart = () => {
const chart = echarts.init(pieChartRef.value)
chart.setOption({
series: [{
type: 'pie',
data: [
{ value: 30, name: 'A' },
{ value: 20, name: 'B' },
{ value: 50, name: 'C' }
]
}]
})
}
const initScatterChart = () => {
const chart = echarts.init(scatterChartRef.value)
chart.setOption({
xAxis: {},
yAxis: {},
series: [{
type: 'scatter',
data: [[10, 20], [20, 30], [30, 40], [40, 50], [50, 60]]
}]
})
}
</script>
<style scoped>
.chart {
padding: 20px;
}
.chart-item {
width: 100%;
height: 300px;
}
</style>
@@ -1,130 +0,0 @@
<template>
<div class="dashboard">
<t-layout>
<t-header>
<h1>仪表盘</h1>
<nav>
<t-menu :value="activeMenu" @click="handleMenuClick" theme="light" mode="horizontal">
<t-menu-item value="/">首页</t-menu-item>
<t-menu-item value="/dashboard">仪表盘</t-menu-item>
<t-menu-item value="/chart">图表</t-menu-item>
<t-menu-item value="/map">地图</t-menu-item>
<t-menu-item value="/graph">关系图</t-menu-item>
</t-menu>
</nav>
</t-header>
<t-content>
<t-grid :columns="3" :gutter="20">
<t-grid-item>
<t-card title="用户统计">
<div class="stat-item">
<t-statistic :value="1234" :precision="0" />
<p>总用户数</p>
</div>
</t-card>
</t-grid-item>
<t-grid-item>
<t-card title="数据统计">
<div class="stat-item">
<t-statistic :value="5678" :precision="0" />
<p>数据总量</p>
</div>
</t-card>
</t-grid-item>
<t-grid-item>
<t-card title="访问统计">
<div class="stat-item">
<t-statistic :value="9876" :precision="0" />
<p>总访问量</p>
</div>
</t-card>
</t-grid-item>
</t-grid>
<t-card title="近期趋势" class="chart-card">
<div ref="chartRef" class="chart"></div>
</t-card>
<t-card title="用户列表" class="table-card">
<t-table :data="userList">
<t-table-column prop="name" label="姓名" />
<t-table-column prop="age" label="年龄" />
<t-table-column prop="email" label="邮箱" />
<t-table-column prop="status" label="状态">
<template #default="{ row }">
<t-tag :theme="row.status === 'active' ? 'success' : 'default'">{{ row.status }}</t-tag>
</template>
</t-table-column>
</t-table>
</t-card>
</t-content>
</t-layout>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import * as echarts from 'echarts'
const router = useRouter()
const activeMenu = ref('/dashboard')
const chartRef = ref(null)
const userList = ref([
{ name: '张三', age: 25, email: 'zhangsan@example.com', status: 'active' },
{ name: '李四', age: 30, email: 'lisi@example.com', status: 'active' },
{ name: '王五', age: 28, email: 'wangwu@example.com', status: 'inactive' }
])
const handleMenuClick = (value) => {
activeMenu.value = value
router.push(value)
}
onMounted(() => {
initChart()
})
const initChart = () => {
const chart = echarts.init(chartRef.value)
const option = {
title: { text: '访问趋势' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: { type: 'value' },
series: [{
data: [120, 200, 300, 400, 500, 600],
type: 'line',
smooth: true
}]
}
chart.setOption(option)
}
</script>
<style scoped>
.dashboard {
padding: 20px;
}
.stat-item {
text-align: center;
}
.chart-card {
margin-top: 20px;
}
.table-card {
margin-top: 20px;
}
.chart {
width: 100%;
height: 400px;
}
</style>
@@ -1,91 +0,0 @@
<template>
<div class="graph">
<t-layout>
<t-header>
<h1>关系图演示</h1>
<nav>
<t-menu :value="activeMenu" @click="handleMenuClick" theme="light" mode="horizontal">
<t-menu-item value="/">首页</t-menu-item>
<t-menu-item value="/dashboard">仪表盘</t-menu-item>
<t-menu-item value="/chart">图表</t-menu-item>
<t-menu-item value="/map">地图</t-menu-item>
<t-menu-item value="/graph">关系图</t-menu-item>
</t-menu>
</nav>
</t-header>
<t-content>
<t-card title="@antv/g6 关系图">
<div ref="graphRef" class="graph-container"></div>
</t-card>
</t-content>
</t-layout>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { Graph } from '@antv/g6'
const router = useRouter()
const activeMenu = ref('/graph')
const graphRef = ref(null)
const handleMenuClick = (value) => {
activeMenu.value = value
router.push(value)
}
onMounted(() => {
initGraph()
})
const initGraph = () => {
const data = {
nodes: [
{ id: 'node1', label: 'Vue 3', size: 30 },
{ id: 'node2', label: 'TDesign', size: 25 },
{ id: 'node3', label: 'ECharts', size: 25 },
{ id: 'node4', label: 'D3.js', size: 25 },
{ id: 'node5', label: 'Leaflet', size: 25 },
{ id: 'node6', label: 'Vuex', size: 20 },
{ id: 'node7', label: 'Axios', size: 20 }
],
edges: [
{ source: 'node1', target: 'node2' },
{ source: 'node1', target: 'node3' },
{ source: 'node1', target: 'node4' },
{ source: 'node1', target: 'node5' },
{ source: 'node1', target: 'node6' },
{ source: 'node1', target: 'node7' }
]
}
const graph = new Graph({
container: graphRef.value,
width: 800,
height: 600,
data,
modes: {
default: ['drag-canvas', 'zoom-canvas', 'drag-node']
},
layout: {
type: 'force',
preventOverlap: true
}
})
graph.render()
}
</script>
<style scoped>
.graph {
padding: 20px;
}
.graph-container {
width: 800px;
height: 600px;
}
</style>
@@ -1,32 +0,0 @@
<template>
<div class="home">
打开群聊
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const activeMenu = ref('/')
const handleMenuClick = (value) => {
activeMenu.value = value
router.push(value)
}
onMounted(async () => {
window.location.href = 'workbench://#XDKZ#open#/dialog/b3Dialog?dialog=chatDialog';
// setTimeout(() => {
// window.opener = null;
// const myWindow = window.open('', '_self'); // 这句是关键,假装是自己打开的自己
// myWindow.close();
// console.log('关闭页面')
// },2000);
})
</script>
<style scoped>
</style>
-97
View File
@@ -1,97 +0,0 @@
<template>
<div class="home">
<t-layout>
<t-header>
<h1>Vue3 技术栈演示</h1>
<nav>
<t-menu :value="activeMenu" @click="handleMenuClick" theme="light" mode="horizontal">
<t-menu-item value="/">首页</t-menu-item>
<t-menu-item value="/dashboard">仪表盘</t-menu-item>
<t-menu-item value="/chart">图表</t-menu-item>
<t-menu-item value="/map">地图</t-menu-item>
<t-menu-item value="/graph">关系图</t-menu-item>
</t-menu>
</nav>
</t-header>
<t-content>
<div class="hero">
<h2>欢迎使用 Vue3 技术栈</h2>
<p>本项目集成了以下技术:</p>
<t-grid :columns="3" :gutter="20">
<t-grid-item>
<t-card title="UI 组件">
<p>TDesign</p>
</t-card>
</t-grid-item>
<t-grid-item>
<t-card title="数据可视化">
<p>ECharts</p>
<p>D3.js</p>
<p>@antv/g6</p>
</t-card>
</t-grid-item>
<t-grid-item>
<t-card title="地图">
<p>Leaflet</p>
</t-card>
</t-grid-item>
<t-grid-item>
<t-card title="网络请求">
<p>Axios</p>
</t-card>
</t-grid-item>
<t-grid-item>
<t-card title="状态管理">
<p>Vuex</p>
</t-card>
</t-grid-item>
<t-grid-item>
<t-card title="构建工具">
<p>Vite</p>
<p>Less</p>
</t-card>
</t-grid-item>
</t-grid>
</div>
</t-content>
</t-layout>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const activeMenu = ref('/')
const handleMenuClick = (value) => {
activeMenu.value = value
router.push(value)
}
onMounted(() => {
activeMenu.value = router.currentRoute.value.path
})
</script>
<style scoped>
.home {
padding: 20px;
}
.hero {
margin-top: 40px;
text-align: center;
}
.hero h2 {
font-size: 28px;
margin-bottom: 20px;
}
.hero p {
font-size: var(--titleSize);
margin-bottom: 40px;
}
</style>
-71
View File
@@ -1,71 +0,0 @@
<template>
<div class="map">
<t-layout>
<t-header>
<h1>地图演示</h1>
<nav>
<t-menu :value="activeMenu" @click="handleMenuClick" theme="light" mode="horizontal">
<t-menu-item value="/">首页</t-menu-item>
<t-menu-item value="/dashboard">仪表盘</t-menu-item>
<t-menu-item value="/chart">图表</t-menu-item>
<t-menu-item value="/map">地图</t-menu-item>
<t-menu-item value="/graph">关系图</t-menu-item>
</t-menu>
</nav>
</t-header>
<t-content>
<t-card title="Leaflet 地图">
<div ref="mapRef" class="map-container"></div>
</t-card>
</t-content>
</t-layout>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import L from 'leaflet'
const router = useRouter()
const activeMenu = ref('/map')
const mapRef = ref(null);
const markerGroup = ref(null);
const handleMenuClick = (value) => {
activeMenu.value = value
router.push(value)
}
onMounted(() => {
initMap()
})
const initMap = () => {
const map = L.map(mapRef.value).setView([39.9042, 116.4074], 13)
L.tileLayer('http://ch1.b.uat.4.cn/jiansuo/mapnew/root/images0_11/{z}/{x}/{y}.png', { // http://ch1.b.uat.4.cn/jiansuo/mapnew/root/images0_11/{z}/{x}/{y}.png https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
attribution: '&copy; OpenStreetMap contributors'
}).addTo(map)
L.marker([39.9042, 116.4074]).addTo(map)
.bindPopup('北京市中心')
.openPopup()
L.circle([39.9042, 116.4074], { radius: 500 }).addTo(map)
.bindPopup('北京市中心区域')
markerGroup.value = L.layerGroup().addTo(map);
console.log('地图markerGroup', markerGroup.value)
}
</script>
<style scoped>
.map {
padding: 20px;
}
.map-container {
width: 100%;
height: 600px;
}
</style>
@@ -1,206 +0,0 @@
<template>
<div class="actionDeatil">
<div class="header">
<div class="title">
行动详情
</div>
<div class="close" @click="close()">
X
</div>
</div>
<div class="content">
<div class="title">
行动名称
</div>
<div class="tabHead">
<div class="item" :class="{ 'active': tabId == 1 }" @click="tabId = 1">
叙事主题
</div>
<div class="item" :class="{ 'active': tabId == 2 }" @click="tabId = 2">
预期行为
</div>
</div>
<div class="tabContent">
<template v-if="tabId == 1">
<div class="xushi" v-for="(item, index) in xushiList">
{{ index + 1 }}.
{{ item.text }}
</div>
</template>
<template v-if="tabId == 2">
<div class="item">
<div class="label">
行为体
</div>
<div class="text">
行为体内容内容内容内容内容内容内容内容内容内容内容内容
</div>
</div>
<div class="item">
<div class="label">
预期行为
</div>
<div class="text">
预期行为内容内容内容内容内容内容内容内容内容内容内容内容
</div>
</div>
</template>
<div class="Analysis">
原因分析
</div>
</div>
<div class="tagget">
<span>
支撑性目的:
</span>
利用桃色新闻与军纪涣散议题利用桃色新闻与军纪涣散议题利用桃色新闻与军纪涣散议题,瓦解台军社会形象与民众支持基础,引发线上网红、大V等对台军……利用桃色新闻与军纪涣散议题利用桃色新闻与军纪涣散议题利用桃色新闻与军纪涣散议题,瓦解台军社会形象与民众支持基础,引发线上网红、大V等对台军……
</div>
</div>
</div>
</template>
<script>
export default {
props: ['activeData'],
data() {
return {
tabId: 1,
xushiList: [
{
text: '叙事内容'
}, {
text: '叙事内容'
},
]
};
},
methods: {
close() {
console.log('1');
this.$emit('close')
}
},
watch: {
},
};
</script>
<style lang="less">
.actionDeatil {
width: 400px;
height: 400px;
background-color: #032650;
position: fixed;
top: 80px;
z-index: 1000;
right: 10px;
border: 1px solid;
border-color: #2b8ffe99;
border-radius: 4px 4px 4px 4px;
.header {
display: flex;
justify-content: space-between;
padding: 0 10px;
height: 50px;
align-items: center;
.title {
color: #fff;
}
.close {
color: #fff;
cursor: pointer;
}
}
.content {
padding: 0 10px;
.title {
font-weight: 700;
font-size: 24px;
color: #fff;
padding: 0 0 10px 20px;
}
.tabHead {
display: flex;
gap: 10px;
padding: 0 10px;
.item {
padding: 0 20px;
color: #aaaaaa;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
background-color: rgba(0, 98, 217, 0.1);
cursor: pointer;
}
.active {
color: #fff;
background-color: #0668de;
}
}
.tabContent {
background-color: rgba(0, 98, 217, 0.1);
height: 180px;
overflow-y: auto;
padding: 10px;
.xushi {
color: #fff;
}
.item {
color: #fff;
display: flex;
gap: 10px;
.label {
border: 1px solid #0668de;
width: 80px;
height: 26px;
border-radius: 5px;
text-align: center;
}
.text {
flex: 1;
}
}
.Analysis {
text-align: right;
color: blue;
text-decoration: underline;
cursor: pointer;
}
}
.tagget {
margin-top: 10px;
padding: 5px;
height: 70px;
overflow: hidden;
color: #fff;
background-color: #092845;
span {
font-weight: 700;
}
}
}
}
</style>
@@ -1,260 +0,0 @@
<template>
<div class="bociDetail">
<div class="header">
<div class="title">
波次详情
</div>
<div class="close">
X
</div>
</div>
<div class="boci">
<div class="name">
<div>
波次1
</div>
<div>
波次名称1
</div>
</div>
<div class="bociLayout">
<div class="timeLine">
<div class="item" v-for="item in timeLine">
{{ item }}
</div>
</div>
<div class="row" v-for="item in timeArr"
:style="{ left: getPercentageInRange(item[0]) + '%', width: getPercentageInRange(item[1]) - getPercentageInRange(item[0]) + '%' }">
</div>
</div>
</div>
<div class="bociConent">
<div class="head">
<span>
波次信息主题
</span>
<span>
修改
</span>
</div>
<div class="info">
<div class="row">
<div class="item">
<div class="label">
信息主题:
</div>
<div class="content">
渴望被官方和主流舆论认可为两岸议题的权威声音理
</div>
</div>
</div>
<div class="row">
<div class="item">
<div class="label">
信息数量:
</div>
<div class="content">
8878
</div>
</div>
<div class="item">
<div class="label">
人设数量:
</div>
<div class="content">
123
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
timeArr: [['2026-04-18', '2026-04-29'], ['2026-03-18', '2026-04-16'], ['2026-03-20', '2026-04-21']],
timeLine: []
};
},
methods: {
getPercentageInRange(targetDate) {
// 1. 数据预处理:展平数组并转为时间戳
const timestamps = this.timeLine.flat(Infinity).map(dateStr => new Date(dateStr).getTime());
// 2. 确定范围的边界
const minTimestamp = Math.min(...timestamps); // 最早时间 (0%)
const maxTimestamp = Math.max(...timestamps); // 最晚时间 (100%)
// 3. 计算总时长差值
const totalDuration = maxTimestamp - minTimestamp;
// 边界情况处理:如果最大最小时间相同,避免除以0
if (totalDuration === 0) {
return 0;
}
// 4. 计算目标时间
const targetTimestamp = new Date(targetDate).getTime();
// 5. 计算百分比公式:(目标 - 最小) / (最大 - 最小)
const percentage = ((targetTimestamp - minTimestamp) / totalDuration) * 100;
// 6. 返回保留两位小数的结果
console.log(parseFloat(percentage.toFixed(2)));
return parseFloat(percentage.toFixed(2));
},
getDateRangeResult(dateRanges) {
// 1. 将二维数组展平并时间戳
// 使用 flat(Infinity) 展平多维数组,map 将日期字符串转为时间戳数值
const timestamps = dateRanges.flat(Infinity).map(dateStr => new Date(dateStr).getTime());
// 2. 获取最小时间戳和最大时间戳
const minTimestamp = Math.min(...timestamps);
const maxTimestamp = Math.max(...timestamps);
// 3. 计算中间时间戳 (取平均值)
const midTimestamp = (minTimestamp + maxTimestamp) / 2;
// 4. 格式化函数:将时间戳转为 'YYYY-MM-DD' 格式
const formatDate = (timestamp) => {
const date = new Date(timestamp);
// toISOString() 返回 UTC 时间格式 (YYYY-MM-DDTHH:mm:ss.sssZ),我们只需要日期部分
return date.toISOString().split('T')[0];
};
// 5. 返回结果数组
return [
formatDate(minTimestamp),
formatDate(midTimestamp),
formatDate(maxTimestamp)
];
}
},
watch: {
},
mounted() {
this.timeLine = this.getDateRangeResult(this.timeArr)
console.log(this.timeLine);
}
};
</script>
<style lang="less">
.bociDetail {
width: 400px;
height: 400px;
background-color: #032650;
position: fixed;
bottom: 20px;
z-index: 1000;
right: 10px;
border: 1px solid;
border-color: #2b8ffe99;
border-radius: 4px 4px 4px 4px;
.header {
display: flex;
justify-content: space-between;
padding: 0 10px;
height: 50px;
align-items: center;
.title {
color: #fff;
}
.close {
color: #fff;
cursor: pointer;
}
}
.boci {
display: flex;
padding: 0 10px;
.name {
display: flex;
flex-direction: column;
justify-content: center;
color: #fff;
width: 100px;
height: 80px;
div {
text-align: center;
}
}
.bociLayout {
flex: 1;
flex-shrink: 0;
background-color: rgba(0, 98, 217, 0.1);
position: relative;
.timeLine {
color: #fff;
display: flex;
justify-content: space-between;
.item {
font-size: 12px;
}
}
.row {
height: 5px;
background-color: red;
position: relative;
margin-top: 10px;
cursor: pointer;
border-radius: 5px;
}
}
}
.bociConent {
background-color: rgba(0, 98, 217, 0.1);
height: 100px;
margin: 10px;
border-radius: 5px;
color: #fff;
.head {}
.info {
.row {
display: flex;
justify-content: space-between;
.item {
display: flex;
.label {
font-size: 12px;
}
.content {
flex: 1;
flex-shrink: 0;
}
}
}
}
}
}
</style>
@@ -1,280 +0,0 @@
<template>
<div class="sentiment">
<div class="ts-type-block type-block-left">
<div class="collapse-content collapse-content-left flex-center-center cursor-pointer" v-if="expandShow"
@click="expandChange">
<img src="@/assets/map-icon/expand-logo.svg" alt="" />
</div>
<div class="expand-content" :class="{ 'expand-hidden': expandShow }">
<div class="cHotTopic">
<!-- 标题 -->
<div class="title-handle flex-between-center">
<div class="flex-content cursor-pointer" @click="expandChange">
<div class="collapse-icon">
<img src="@/assets/map-icon/expand-logo.svg" alt="" />
</div>
<div class="title">当前任务</div>
</div>
<div v-if="taskId" class="task-back" @click="taskBackChange">
<img src="@/assets/map-icon/back-logo.svg" alt="" />
返回任务
</div>
</div>
<!-- 输入框 -->
<div class="title-content">
<!-- 行动策划 -->
<template v-if="!taskId">
<!-- <taskStatistics :range="range" /> -->
<searchTaskList height="calc(100vh - 150px)" searchType="action" :range="range" :taskDirection="tabValue"
@updateRange="updateRange" @selectTask="selectTask" />
</template>
<template v-else>
<ActionPlanning :taskData="taskData" @actionDataC="actionDataChange" />
</template>
</div>
</div>
</div>
</div>
<!-- 关系图 -->
<div class="box-overview">
<G6NetworkGraph v-if="taskData && actionId" :actionData="actionData" :actionId="actionId" :taskData="taskData"
:taskId="taskData.id" :showActionCount="showActionCount" />
</div>
<!-- <actionDetail v-if="actionDetailStatus" :activeData="actionDetailData" @close="actionDetailClose" /> -->
<!-- <bociDetail v-if="bociDetailStatus" /> -->
</div>
</template>
<script setup>
import { ref, reactive } from "vue";
import ActionPlanning from "@/components/ActionPlanning.vue";
import ActionOverview from "@/components/ActionOverview.vue";
import taskStatistics from "../task/components/taskStatistics.vue";
import searchTaskList from "@/components/searchTaskList/index.vue";
import G6NetworkGraph from "@/components/graph/G6NetworkGraph.vue";
import actionDetail from "./components/actionDetail.vue";
import bociDetail from "./components/bociDetail.vue";
const tabValue = ref("all");
const taskId = ref("");
const taskDetailShow = ref(false); // 是否显示任务详情
const taskData = ref({});
const range = ref("all");
const actionData = ref({});
const actionId = ref(null);
const expandShow = ref(false);
const actionDetailStatus = ref(false);
const actionDetailData = ref({});
const bociDetailStatus = ref(false);
const showActionCount = ref(true);
const updateRange = (d) => {
range.value = d;
};
// 任务类型切换
const tabChange = (value) => {
tabValue.value = value;
};
const selectTask = (d) => {
console.log(d, d.id);
taskDetailShow.value = true;
taskId.value = d.id;
taskData.value = d;
actionId.value = null
};
const taskBackChange = () => {
taskId.value = null;
taskData.value = null;
};
const actionDetailClose = () => {
console.log("关闭");
actionDetailStatus.value = false;
};
const actionDataChange = (info) => {
console.log(info);
actionDetailStatus.value = true;
actionDetailData.value = info;
showActionCount.value = false;
actionData.value = info;
actionId.value = info.id;
};
const expandChange = () => {
expandShow.value = !expandShow.value;
};
</script>
<style scoped lang="less">
/* Your styles here */
// 详情样式
.sentiment {
height: 100%;
display: flex;
justify-content: space-between;
.ts-type-block {
// inset: var(--fontSize) auto 8px 124px;
width: 436px;
height: 90.3%;
overflow: hidden;
z-index: 1000;
// display: flex;
// flex-direction: column;
box-sizing: border-box;
transition: all 0.5s ease;
.cHotTopic {
width: 436px;
height: 100%;
background: #032650cc;
border: 1px solid;
border-color: #2b8ffe99;
border-radius: 4px 4px 4px 4px;
overflow: hidden;
box-sizing: border-box;
// display: flex;
// flex-direction: column;
transition: transform 0.5s ease;
.title-handle {
width: 102%;
height: 40px;
padding-right: var(--fontSize);
line-height: 22px;
font-family: Noto Sans S Chinese;
font-weight: 550;
color: #fff;
font-size: var(--titleSize);
padding-left: var(--fontSize);
box-sizing: border-box;
border-bottom: 1px solid rgba(43, 143, 254, 0.6);
letter-spacing: 1px;
background: linear-gradient(89.65deg,
#0062d9 0.03%,
#0062d900 99.97%) !important;
// .collapse-icon {
// margin-left: auto;
// margin-right: 5px;
// cursor: pointer;
// position: absolute;
// top: 12px;
// left: 8px;
// z-index: 1000;
// }
.title {
margin-left: 12px;
font-family: Noto Sans S Chinese;
font-weight: 550;
}
.tab-list {
.tab-list-item {
font-size: var(--fontSizes);
line-height: var(--fontSizes);
display: inline-block;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
}
.item-active {
background: linear-gradient(104.44deg, #2b8ffe, #0062d9);
}
}
}
}
.title-content {
flex: 1;
height: calc(100vh - 130px);
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
overflow-y: auto;
}
.title-content::-webkit-scrollbar {
display: none;
}
.task-back {
background: linear-gradient(102.76deg, #2b8ffe 0%, #0062d9 100%);
border-radius: 4px;
padding: 5px 10px;
line-height: var(--fontSize);
font-family: Noto Sans S Chinese;
font-weight: 300;
color: #ffffff;
font-size: var(--fontSize);
display: flex;
align-items: center;
cursor: pointer;
img {
width: var(--fontSize);
margin-right: 5px;
}
}
}
// 关系图样式
.box-overview {
width: 100%;
height: calc(100% - 62px);
background: rgba(2, 22, 47, 0.8);
// background: url('../../assets/map-icon/relation-bg.png') no-repeat;
background-size: cover;
z-index: 500;
position: fixed;
top: 62px;
left: 0;
}
.collapse-content {
width: 30px;
height: 40px;
background: #032650cc;
border: 1px solid;
border-color: #2b8ffe99;
border-radius: 4px 0 0 4px;
z-index: 500;
position: absolute;
top: 0;
img {
width: 16px;
}
}
.collapse-content-right {
right: 0;
}
.collapse-content-left {
left: 0;
}
.expand-hidden {
width: 0px !important;
border: 0px !important;
}
.expand-content {
width: 100%;
transition: all 0.5s ease;
overflow: hidden;
}
.expand-content-right {
float: right;
}
}
</style>
@@ -1,228 +0,0 @@
<template>
<div class="zxtp_wrap">
<div :id="conId" style="height: 100%; width: 100%"></div>
</div>
</template>
<script setup>
import { Rect, Circle, Diamond, Ellipse, Hexagon, register, Graph, ExtensionCategory } from '@antv/g6';
import {onMounted} from "vue";
import BottomIconImg from "@/assets/xdzxtp_bottom_icon.png"
const shapes = {
rect: Rect,
circle: Circle,
diamond: Diamond,
ellipse: Ellipse,
hexagon: Hexagon
}
Object.keys(shapes).forEach(key=>{
class IconNode extends shapes[key] {
get data() {
return this.context.graph.getNodeData(this.id).data;
}
get nodeData(){
return this.context.graph.getNodeData(this.id);
}
getCustomIconStyle(attributes) {
const [width, height] = this.getSize(attributes);
const { imgSrc } = this.data;
return {
x: -10,
y: -10,
width: 20,
height: 20,
src: imgSrc,
z: 100
};
}
drawCustomIconShape(attributes, container) {
const iconStyle = this.getCustomIconStyle(attributes);
this.upsert('custom-icon', 'image', iconStyle, container);
}
getSubtitleStyle(attributes) {
const [width, height] = this.getSize(attributes);
const { subTitle, fillColor } = this.nodeData;
return {
x: 0,
y: 0, // 放在主标题下方
text: subTitle || '',
fontSize: 14,
fontWeight: 'bold', // 加粗字体
fill: fillColor || '#fff',
textAlign: 'center',
textBaseline: 'middle',
};
}
// 绘制副标题
drawSubtitleShape(attributes, container) {
const subtitleStyle = this.getSubtitleStyle(attributes);
this.upsert('subtitle', 'text', subtitleStyle, container);
}
// 绘制额外的图标
drawBottomIconShape(attributes, container) {
const { isShowBottom } = this.nodeData;
const [width, height] = this.getSize(attributes);
const style = {
x: 0,
y: 0,
width: 20,
height: 20,
src: BottomIconImg,
z: 101,
opacity: isShowBottom ? 1 : 0 // 控制是否显示底部图标
}
this.upsert('bottom_icon', 'image', style, container);
}
render(attributes, container) {
// 渲染基础矩形
super.render(attributes, container);
// 添加图标
const { shape } = this.nodeData;
if(['rect', 'diamond'].includes(shape)){
this.drawSubtitleShape(attributes, container);
}else{
this.drawCustomIconShape(attributes, container);
}
this.drawBottomIconShape(attributes, container)
}
}
register(ExtensionCategory.NODE, `custom_xdzxtp_${key}`, IconNode);
})
const conId = 'rwxq_xdzxtp_con';
let graph;
const draw = async (params={})=>{
if(!graph){
graph = new Graph({
container: conId, // 通过 DOM ID 指定容器
padding: 30,
autoResize: true,
// 视口配置
zoom: 1,
autoFit: 'center',
// 节点配置
node: {
type: (e)=>{
if(e.shape){
return `custom_xdzxtp_${e.shape}`
}
return 'custom_xdzxtp_rect'
},
style: {
badge: (e)=>{
return !!e.isShowBadge
}, // 是否显示徽标
badges: [
{ text: '!', placement: 'left-top', offsetX: 6, offsetY: 0, fill: "#fff", backgroundFill: "#f59a23", backgroundRadius: 0 },
],
size: (e)=>{
if(e.shape === 'ellipse'){
return [60, 40]
}else if(e.shape === 'diamond'){
return [50, 50]
}else if(e.shape === 'circle'){
return [40, 40]
}else if(e.shape === 'rect'){
return [50, 45]
}else if(e.shape === 'hexagon'){
return [50, 50]
}
},
label: (e)=>{
return !!e.showLabel
}, // 是否显示节点标签
labelText: (e)=>{
return e.label
}, // 标签文字内容
labelFill: '#fff',
fill: (e)=>{
return e.fillColor || '#535353'
},
fillOpacity: 0.5,
stroke: (e)=>{
return e.fillColor || '#535353'
},
lineWidth: 1,
},
},
// 边配置
edge: {
type: "quadratic",
style: {
stroke: (e)=>{
if(e.lineColor) return e.lineColor;
return '#959899'
},
lineDash: (e)=>{
if(e.isDash) return 5;
return 0
}, // 虚线样式
lineDashOffset: 5, // 虚线偏移量
lineWidth: 1.5,
endArrow: true,
enableTooltip: false,
// 线条标签
//label: false, // 开启边标签展示
// labelText: 'labelText', // 边标签文字
// labelPlacement: 'center', // 边标签相对于边的位置
// labelFill: '#fff', // 边标签文字颜色
// labelOffsetY: 20, // 边标签在y轴方向上的偏移量
},
},
// 布局配置
layout: {
type: 'force',
preventOverlap: true,
linkDistance: 100,
},
// 交互行为
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element'],
plugins: [
{
type: 'tooltip',
getContent: (e, items) => {
let target = items[0]
let type = e.targetType
if (type==='node'){
let result = `<h4>${type==='node'?'节点详情':''}</h4>`;
result += `<p>${target.label}</p>`;
return result;
}
},
},
],
});
}
return graph;
}
defineExpose({
draw,
graph,
/*findNodeById(id){
const nodes = graph.getNodeData();
return nodes ? nodes.find(i => i.id === id) : null;
},*/
async updateNode(nodeDataArr){
graph.updateNodeData(nodeDataArr)
await graph.draw()
}
})
</script>
<style lang="less">
.zxtp_wrap{
height: 100%;
width: 100%;
}
</style>
Loaded 100 of 151 files, more files were not shown because too many files have changed in this diff. Show more