This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-MVP/apps/web/src/shared/useChatWidth.ts
T
JCAI Main AgentandClaude Opus 5 3cfaa04098 M6(1/n): 工作台任务要素 —— 原型 1:1 平移的第一屏
样式整文件搬(README 平移规则 1):app.css 2767 行 + proto.vendor.css +
theme.css + console.css 原样搬,设计令牌与全部 jc-* / g-* 类名不动。

前端(按 §9 重排,渲染输出 1:1):
- shared/:Ico / ScrollArea / useChatWidth 自原型搬,去掉 "use client" 等 Next 专有件;
  失效的 react-hooks disable 指令按 §17 清掉(不改渲染)
- shared/typed-fetch.ts:服务端交互唯一通道,路由契约来自 @contracts/api;
  不引 Hono RPC 客户端(那要求前端 import 后端类型,违反 §5.1)
- app/query.ts:TanStack Query;缓存失效由订阅层驱动,组件不轮询
- features/desk-task:任务列表(一条 = 一个活动的一次运行,不是一整条工作流)/
  标题区 + 工作区四页签 / 对话区(气泡二元制 + 卡族 + **composer 锁**)
- @ui:ViewContainer 用**沙箱 iframe(sandbox 不含 allow-scripts)** —— 结构上无脚本执行权,
  不依赖净化算法的完备性;ViewBlocks 渲染十一种视图块
  feature 不得自行 dangerouslySetInnerHTML,eslint 有规则守着

后端:工作台路由 /api/desk/*(任务清单 / 任务详情 / 工单回传 / 自由输入 / 消息);
回传与后端校验**同 schema**;任务详情是服务端投影 —— 前端零过程状态,刷新即重建

已验证(真机):任务卡「A1 RFQ需求分析 · 序位 1/7 · awaiting-event」、
预算 2/40 步、1 张工单(LLM 步失败后转人兜底那张)、7 行带归侧标签的任务日志

358 个测试通过,门禁五道全绿;web 构建通过

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
2026-07-27 02:45:42 +08:00

52 lines
2.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 对话区/视图区可调比例:workspace 上的分隔柄拖拽写入 --jc-chat-w(CSS 变量,
// 各断点的对话区宽度均以它优先),localStorage 记忆,双击复位回默认配比。
// 任务工作台与消息模块共用同一 storage key —— 两处对话区宽度保持一致。
import { useCallback, useEffect, useState } from "react";
const MIN_W = 340;
// 上限随视口收敛:给 视图区(≥480) + 清单列 + 间距 留足空间,避免拖到中栏塌陷
const maxW = () => Math.max(MIN_W, Math.min(720, window.innerWidth - 830));
export function useChatWidth(storageKey = "jc-chat-w") {
const [w, setW] = useState<number | null>(null); // null = 未拖拽,走断点默认
useEffect(() => {
const v = Number(localStorage.getItem(storageKey));
if (v) setW(Math.min(maxW(), Math.max(MIN_W, v)));
}, [storageKey]);
const onSplitDown = useCallback((e: React.PointerEvent<HTMLElement>) => {
e.preventDefault();
const handle = e.currentTarget;
const chatEl = handle.parentElement?.querySelector<HTMLElement>(".session-chat");
const startX = e.clientX;
const startW = chatEl?.getBoundingClientRect().width ?? 440;
let cur = startW;
handle.classList.add("dragging");
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
const move = (ev: PointerEvent) => {
cur = Math.min(maxW(), Math.max(MIN_W, Math.round(startW + (startX - ev.clientX))));
setW(cur);
};
const up = () => {
window.removeEventListener("pointermove", move);
window.removeEventListener("pointerup", up);
handle.classList.remove("dragging");
document.body.style.cursor = "";
document.body.style.userSelect = "";
localStorage.setItem(storageKey, String(cur));
};
window.addEventListener("pointermove", move);
window.addEventListener("pointerup", up);
}, [storageKey]);
const onSplitReset = useCallback(() => {
setW(null);
localStorage.removeItem(storageKey);
}, [storageKey]);
const style = (w ? { "--jc-chat-w": `${w}px` } : undefined) as React.CSSProperties | undefined;
return { style, onSplitDown, onSplitReset };
}