Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@

### 修复

- Organization 子页(公告 / 架构 / 员工 / 技能 / 治理 / 知识 / 任务 / 反思 / 设置 / 智能体 / 工作台)去掉居中窄列 `max-width`,与知识库、设置等 Dashboard 系统页同宽;`PageShell` 在 `fill` 时滚动内容区,避免被固定标题/路径 Tab 裁切。路径 Tab 改到标题下方两行,页面主体更接近 openXYOS OpenApp(Ant Design 控件 vs 原 Tailwind 仍保留)。Chat 仍是 FreeOS 原生。
- Organization 任务详情把原来的「评论列表 + 右侧空白详情」收成一块嵌入式讨论区:左侧任务信息 / 子任务 / 状态,右侧消息流 + 发送框(头像、气泡、智能体备注),仍走宿主 `/api/org-module/tasks/:id/comments`,不嵌入 FreeOS Chat。

> 下面若干「启动即拉起本机 openXYOS」条目记录的是 0.0.1 边车时代问题。默认路径已被 [0.0.2] 与本文件 Unreleased 的 Phase 5 取代:安装与首屏不再捆绑或自动拉起 Node。

- Windows 安装预配不再把 Node 工作目录选到残留的嵌套 `openxyos\\openxyos`:顶层已有 `backend-dist/server.js` 与 `dist/index.html` 时必须用 live 根。嵌套 cwd 会让 `node backend-dist/server.js` 立刻退出且 stdout/stderr 为空,安装空等 90s 后以退出码 12 失败。`start-sidecar.ps1` / 预配 / 桌面 Go / Python 启动路径统一按此选择 cwd;每次启动截断 `start.log`;Shell.Application 若未刷新日志则改走 explorer / Start-Process,Node fail-fast 不再伪装成 livez 超时。
Expand Down
15 changes: 15 additions & 0 deletions dashboard/src/layouts/PageShell.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,21 @@
margin: -4px 0 12px;
}

/**
* When `fill` or mobile path-tabs pin the chrome, scroll the remaining
* body so tall pages are not clipped by overflow:hidden on the card.
*/
.fillBody {
flex: 1;
min-height: 0;
min-width: 0;
width: 100%;
overflow-x: hidden;
overflow-y: auto;
display: flex;
flex-direction: column;
}

/**
* Pin Ant Design Tabs nav; only the active pane scrolls.
* Use inside PageShell with `fill` (desktop).
Expand Down
11 changes: 11 additions & 0 deletions dashboard/src/layouts/PageShell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,15 @@ describe("PageShell path tabs", () => {
container.querySelector("div[class*='pathTabsBelowTitle']"),
).toBeTruthy();
});

it("scrolls the body when fill pins the title chrome", () => {
render(
<PageShell title="组织 / 公告" fill>
<div data-testid="tall-body">body</div>
</PageShell>,
);
const body = screen.getByTestId("page-shell-scroll-body");
expect(body).toContainElement(screen.getByTestId("tall-body"));
expect(body.className).toMatch(/fillBody/);
});
});
13 changes: 11 additions & 2 deletions dashboard/src/layouts/PageShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ interface PageShellProps {
pathTabsPlacement?: "title-row" | "below-title";
/** Render agent picker below the title row, outside the scrollable content card. */
agentScoped?: boolean;
/** When true, the content area does not scroll; children fill remaining height. */
/**
* Pin title / path-tab chrome and let the remaining body scroll.
* Children that manage their own height (FillTabs) still fill this region.
*/
fill?: boolean;
children: React.ReactNode;
}
Expand Down Expand Up @@ -241,7 +244,13 @@ function PageShell({
<PathTabsBar pathTabs={pathTabs} compact />
</div>
)}
{children}
{pinBody ? (
<div className={styles.fillBody} data-testid="page-shell-scroll-body">
{children}
</div>
) : (
children
)}
</div>
</div>
);
Expand Down
84 changes: 84 additions & 0 deletions dashboard/src/org-ui/layout/orgPage.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Shared Organization page body. Hosted under FreeOS PageShell and in the
* export-standalone shell — stretch to the content column instead of a
* centered card (openXYOS OpenApp used max-w-* inside a full-viewport app;
* Dashboard already insets by sidebar + PageShell padding).
*/
.page {
box-sizing: border-box;
width: 100%;
max-width: none;
min-width: 0;
margin: 0;
padding: 0 0 8px;
display: flex;
flex-direction: column;
gap: 16px;
}

.header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 12px;
}

.titleRow {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}

.title {
margin: 0;
font-size: 20px;
font-weight: 650;
line-height: 1.3;
}

.subtitle {
margin: 4px 0 0;
color: var(--fn-text-secondary, #667085);
font-size: 13px;
line-height: 1.5;
}

.empty {
text-align: center;
padding: 64px 16px;
color: var(--fn-text-secondary, #667085);
}

.emptyIcon {
display: flex;
justify-content: center;
margin-bottom: 12px;
opacity: 0.35;
}

.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 10px;
}

.stat {
border: 1px solid var(--fn-border, #e5e7eb);
border-radius: 10px;
padding: 12px 14px;
background: var(--fn-bg-container, #fff);
}

.statValue {
font-size: 22px;
font-weight: 650;
line-height: 1.2;
}

.statLabel {
margin-top: 4px;
font-size: 12px;
color: var(--fn-text-secondary, #667085);
}
38 changes: 38 additions & 0 deletions dashboard/src/org-ui/layout/orgPage.module.css.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { readdirSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";

const here = path.dirname(fileURLToPath(import.meta.url));
const pagesRoot = path.join(here, "..", "pages");

function walkCss(dir: string): string[] {
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) return walkCss(full);
return entry.name.endsWith(".module.css") ? [full] : [];
});
}

describe("org-ui page layout", () => {
it("keeps the shared page shell full-bleed", () => {
const css = readFileSync(path.join(here, "orgPage.module.css"), "utf8");
expect(css).toMatch(/width:\s*100%/);
expect(css).toMatch(/max-width:\s*none/);
expect(css).not.toMatch(/max-width:\s*\d+px/);
expect(css).not.toMatch(/margin:\s*0\s+auto/);
});

it("does not re-impose a centered max-width column on page wrappers", () => {
const files = walkCss(pagesRoot);
expect(files.length).toBeGreaterThan(5);
for (const file of files) {
const css = readFileSync(file, "utf8");
const pageBlock = css.match(/\.page\s*\{[^}]*\}/);
expect(pageBlock, file).toBeTruthy();
const block = pageBlock?.[0] ?? "";
expect(block, file).not.toMatch(/max-width:\s*\d+px/);
expect(block, file).not.toMatch(/margin:\s*0\s+auto/);
}
});
});
8 changes: 1 addition & 7 deletions dashboard/src/org-ui/pages/agents/AgentsPage.module.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
.page {
max-width: 960px;
margin: 0 auto;
padding: 8px 4px 32px;
display: flex;
flex-direction: column;
gap: 16px;
composes: page from "../../layout/orgPage.module.css";
}

.header {
Expand All @@ -31,7 +26,6 @@
margin: 4px 0 0;
color: var(--fn-text-secondary, #667085);
font-size: 13px;
max-width: 680px;
line-height: 1.5;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
.page {
max-width: 880px;
margin: 0 auto;
padding: 8px 4px 32px;
display: flex;
flex-direction: column;
gap: 16px;
composes: page from "../../layout/orgPage.module.css";
}

.header {
Expand Down Expand Up @@ -36,7 +31,36 @@
}

.card {
position: relative;
cursor: pointer;
border: 1px solid var(--fn-border, #e5e7eb);
border-radius: 10px;
padding: 16px;
background: var(--fn-bg-container, #fff);
transition:
box-shadow 0.15s ease,
border-color 0.15s ease;
}

.card:hover {
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.08);
}

.cardPinned {
border-color: color-mix(in srgb, var(--fn-color-brand, #1677ff) 30%, #e5e7eb);
box-shadow: 0 0 0 1px
color-mix(in srgb, var(--fn-color-brand, #1677ff) 12%, transparent);
}

.cardExpired {
opacity: 0.65;
}

.pinBadge {
position: absolute;
top: -8px;
left: -6px;
color: var(--fn-color-brand, #1677ff);
}

.cardTitle {
Expand Down Expand Up @@ -64,17 +88,25 @@
margin-top: 8px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
font-size: 12px;
color: var(--fn-text-secondary, #667085);
}

.empty {
text-align: center;
padding: 48px 12px;
padding: 64px 12px;
color: var(--fn-text-secondary, #667085);
}

.emptyIcon {
display: flex;
justify-content: center;
margin-bottom: 12px;
opacity: 0.35;
}

.detailBody {
white-space: pre-wrap;
line-height: 1.6;
Expand Down
23 changes: 12 additions & 11 deletions dashboard/src/org-ui/pages/announcements/AnnouncementPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
Tag,
Typography,
} from "antd";
import { Megaphone, Pin, Plus, Search } from "lucide-react";
import { Eye, Megaphone, Pin, Plus, Search } from "lucide-react";
import type {
Announcement,
OrgAnnouncementsClient,
Expand Down Expand Up @@ -199,7 +199,7 @@ export function AnnouncementPage({
<Megaphone size={22} />
<h1 className={styles.title}>{labels.title}</h1>
{unreadCount > 0 ? (
<Tag color="red">
<Tag color="red" style={{ borderRadius: 999 }}>
{unreadCount} {labels.unread}
</Tag>
) : null}
Expand Down Expand Up @@ -260,6 +260,9 @@ export function AnnouncementPage({
<div className={styles.empty}>{labels.loading}</div>
) : items.length === 0 ? (
<div className={styles.empty} data-testid="org-announcements-empty">
<div className={styles.emptyIcon} aria-hidden>
<Megaphone size={48} />
</div>
{labels.empty}
</div>
) : (
Expand All @@ -271,24 +274,21 @@ export function AnnouncementPage({
return (
<div
key={row.id}
className={styles.card}
className={`${styles.card} ${
row.is_pinned === 1 ? styles.cardPinned : ""
} ${expired ? styles.cardExpired : ""}`}
data-testid={`org-announcement-${row.id}`}
onClick={() => void openDetail(row)}
role="button"
tabIndex={0}
onKeyDown={(event) => {
if (event.key === "Enter") void openDetail(row);
}}
style={{
border: "1px solid var(--fn-border, #e5e7eb)",
borderRadius: 10,
padding: 14,
background: "var(--fn-bg-container, #fff)",
opacity: expired ? 0.65 : 1,
}}
>
{row.is_pinned === 1 ? (
<Pin size={14} className={styles.pinBadge} />
) : null}
<div className={styles.cardTitle}>
{row.is_pinned === 1 ? <Pin size={14} /> : null}
<Tag color={TYPE_COLOR[row.type] || "blue"}>
{typeLabel(row.type, locale)}
</Tag>
Expand Down Expand Up @@ -338,6 +338,7 @@ export function AnnouncementPage({
<span>{row.creator_name || labels.system}</span>
<span>{format(row.published_at)}</span>
<span>
<Eye size={11} style={{ marginRight: 4 }} />
{row.read_percent ?? 0}% ({row.read_count ?? 0}/
{row.total_users ?? 1})
</span>
Expand Down
Loading
Loading