diff --git a/console/src/api/modules/pages.ts b/console/src/api/modules/pages.ts index 59f9cc1..57f3a82 100644 --- a/console/src/api/modules/pages.ts +++ b/console/src/api/modules/pages.ts @@ -1,6 +1,7 @@ // 个人主页(OPC 公开名片页)API(/pages/*,0052) // 复用 hall 同款 getApiUrl/getApiToken 凭据出口。 import { getApiUrl, getApiToken } from "../config"; +import type { HallService, TalentProfile } from "./hall"; function authHeaders(): Record { const t = getApiToken(); @@ -35,13 +36,14 @@ export interface UserBadge { } export interface UserPageRender { + /** null 表示该用户未自定义个人主页(渲染平台默认名片)。 */ page: { pageType: "html" | "embed"; htmlContent: string; embedUrl: string; capabilities: PageCapability[]; published: boolean; - }; + } | null; profile: { nickname: string; avatar: string; @@ -54,6 +56,11 @@ export interface UserPageRender { text: string; }; ownerIsSelf: boolean; + /** page 为 null 时的默认名片数据(人才档案 + 已上架服务)。 */ + default?: { + talent: TalentProfile | null; + services: HallService[]; + }; } export interface MyPage { diff --git a/console/src/pages/portal/Opc/HallTalents.tsx b/console/src/pages/portal/Opc/HallTalents.tsx index 2a48c3e..81bfd6a 100644 --- a/console/src/pages/portal/Opc/HallTalents.tsx +++ b/console/src/pages/portal/Opc/HallTalents.tsx @@ -1,9 +1,8 @@ -// 人才大厅:OPC + 求职者档案列表 + 详情抽屉 +// 人才大厅:OPC + 求职者档案列表,点击卡片直接跳转该用户个人主页(公开名片) import { useCallback, useEffect, useState } from "react"; -import { Avatar, Button, Descriptions, Drawer, Empty, Input, Segmented, Tag, message } from "antd"; +import { Avatar, Empty, Input, Segmented, Tag } from "antd"; import { hallApi, type TalentProfile } from "../../../api/modules/hall"; import { RequireRole } from "../../../auth/guards"; -import { useStartChat } from "../../../hooks/useStartChat"; import { useOsRoute } from "../../../os/osRouteStore"; import styles from "./OpcList.module.less"; @@ -11,11 +10,9 @@ export function HallTalentsView() { const [items, setItems] = useState([]); const [type, setType] = useState("all"); const [q, setQ] = useState(""); - const [detail, setDetail] = useState(null); - const startChat = useStartChat(); const openApp = useOsRoute((s) => s.openApp); - /** 打开该用户的个人主页(公开名片窗口)。 */ + /** 点击人才 → 直接打开其个人主页(公开名片窗口)。 */ const openHome = (uid: string) => openApp("core.user-home", `/user/${encodeURIComponent(uid)}`); const load = useCallback(() => { @@ -23,14 +20,6 @@ export function HallTalentsView() { }, [type, q]); useEffect(load, [load]); - const open = async (uid: string) => { - try { - setDetail(await hallApi.talentDetail(uid)); - } catch (err) { - message.error(err instanceof Error ? err.message : "加载失败"); - } - }; - return ( <>
@@ -44,10 +33,10 @@ export function HallTalentsView() { ) : (
{items.map((t) => ( -
open(t.userId)}> +
openHome(t.userId)}>
- {(t.displayName || "·").slice(0, 1)} + {(t.displayName || "·").slice(0, 1)}
{t.displayName} @@ -72,41 +61,6 @@ export function HallTalentsView() { ))}
)} - - setDetail(null)} title={detail?.displayName} width={440}> - {detail && ( - <> - - {detail.talentType === "seeker" ? "求职者" : "OPC"} - {detail.region || "云南"} - {detail.workYears ?? 0} 年 - {detail.casesCount ?? 0} 例 - {detail.opcCertified && OPC 认证} - -

{detail.bio}

- {(detail.services ?? []).length > 0 && ( - <> -

TA 的服务

- {detail.services!.map((s) => ( -
- {s.title} - {s.price ? `¥${s.price}` : "面议"} -
- ))} - - )} - {!detail.contactOk &&

该人才暂未开放联系方式,可通过沟通发起连接。

} -
- - -
- - )} -
); } diff --git a/console/src/pages/portal/Opc/UserHome.module.less b/console/src/pages/portal/Opc/UserHome.module.less index bb2c672..37bf1a5 100644 --- a/console/src/pages/portal/Opc/UserHome.module.less +++ b/console/src/pages/portal/Opc/UserHome.module.less @@ -89,6 +89,139 @@ background: #fff; } +/* ── 平台默认名片(用户未自定义主页时)────────── */ +.defaultBody { + flex: 1; + min-height: 0; + overflow-y: auto; + padding: 16px 18px 28px; + background: var(--color-bg-layout, #f5f5f5); + scrollbar-width: none; + -ms-overflow-style: none; + + &::-webkit-scrollbar { + display: none; + } +} + +.profileCard { + background: #fff; + border: 1px solid rgba(0, 0, 0, 0.06); + border-radius: 12px; + padding: 16px; +} + +.profileHeader { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; +} + +.profileHeadline { + font-size: 15px; + font-weight: 600; + color: #1a1b1c; + line-height: 1.4; +} + +.profileMeta { + margin-top: 4px; + font-size: 12px; + color: #8a8f98; +} + +.profileBio { + margin: 12px 0 0; + font-size: 13px; + color: #3a3f45; + line-height: 1.7; + white-space: pre-wrap; +} + +.tagRow { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 12px; +} + +.section { + margin-top: 18px; +} + +.sectionTitle { + font-size: 14px; + font-weight: 600; + color: #1a1b1c; + margin-bottom: 10px; +} + +.serviceGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 12px; +} + +.serviceCard { + background: #fff; + border: 1px solid rgba(0, 0, 0, 0.06); + border-radius: 12px; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.serviceCover { + width: 100%; + height: 96px; + object-fit: cover; + background: #eef0f3; +} + +.serviceTitle { + font-size: 13px; + font-weight: 600; + color: #1a1b1c; + padding: 10px 12px 0; +} + +.serviceTags { + padding: 4px 12px 0; + font-size: 11px; + color: #3b6fd4; +} + +.serviceDesc { + padding: 6px 12px 0; + font-size: 12px; + color: #6b7280; + line-height: 1.6; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + flex: 1; +} + +.serviceFooter { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 12px; +} + +.servicePrice { + font-size: 14px; + font-weight: 600; + color: #fa541c; +} + +.serviceMeta { + font-size: 11px; + color: #8a8f98; +} + /* ── 底部说明条 ──────────────────────────────── */ .noticeConnected, .noticeWarn { diff --git a/console/src/pages/portal/Opc/UserHomePage.tsx b/console/src/pages/portal/Opc/UserHomePage.tsx index 6ee3e9a..d8d1abd 100644 --- a/console/src/pages/portal/Opc/UserHomePage.tsx +++ b/console/src/pages/portal/Opc/UserHomePage.tsx @@ -4,10 +4,11 @@ // 「更多选项 + 返回」标准胶囊按钮,底部展示能力接入说明或数据核实提醒。 import { useCallback, useEffect, useRef, useState } from "react"; import { useParams } from "react-router-dom"; -import { Avatar, Button, Dropdown, Empty, Input, Modal, Segmented, Spin, Switch, message } from "antd"; +import { Avatar, Button, Dropdown, Empty, Input, Modal, Segmented, Spin, Switch, Tag, message } from "antd"; import type { MenuProps } from "antd"; -import { Edit3, RefreshCw, X } from "lucide-react"; +import { Edit3, MessageCircle, RefreshCw, X } from "lucide-react"; import { pagesApi, type PageCapability, type UserPageRender } from "../../../api/modules/pages"; +import { useStartChat } from "../../../hooks/useStartChat"; import { useOsWindows } from "../../../os/osWindowStore"; import styles from "./UserHome.module.less"; @@ -25,6 +26,7 @@ const CAP_PRESETS: { key: string; name: string; desc: string }[] = [ export default function UserHomePage() { const { uid = "" } = useParams(); const close = useOsWindows((s) => s.close); + const startChat = useStartChat(); const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); @@ -65,7 +67,7 @@ export default function UserHomePage() { const pushIdentity = useCallback(() => { const d = dataRef.current; const frame = iframeRef.current?.contentWindow; - if (!d || !frame) return; + if (!d || !frame || !d.page) return; frame.postMessage( { __opcBridge: true, @@ -102,15 +104,14 @@ export default function UserHomePage() { close(HOME_ROUTE_ID); }, [close]); - /** 打开编辑弹窗,回填当前页(仅本人)。 */ + /** 打开编辑弹窗,回填当前页(仅本人;无页时按新建处理)。 */ const openEditor = useCallback(() => { const p = dataRef.current?.page; - if (!p) return; - setEditType(p.pageType); - setEditHtml(p.htmlContent || ""); - setEditUrl(p.embedUrl || ""); - setEditCaps((p.capabilities || []).map((c) => c.key)); - setEditPublished(p.published); + setEditType(p?.pageType ?? "html"); + setEditHtml(p?.htmlContent || ""); + setEditUrl(p?.embedUrl || ""); + setEditCaps((p?.capabilities || []).map((c) => c.key)); + setEditPublished(p?.published ?? false); setEditOpen(true); }, []); @@ -167,8 +168,10 @@ export default function UserHomePage() { ); } - const { profile, page, notice } = data; - const isHtml = page.pageType === "html"; + const { profile, notice } = data; + const page = data.page; + const def = data.default; + const isHtml = page?.pageType === "html"; // 安全沙箱: // - html 模式:不含 allow-same-origin,脚本运行在独特源,无法触达父页 DOM/存储; // 平台能力经 postMessage 桥提供。 @@ -206,25 +209,83 @@ export default function UserHomePage() {
{data.ownerIsSelf && ( )}
- {/* 内容区:iframe(自写 HTML 沙箱 / 第三方网页嵌入) */} -
-