From 787de5aa882b449e2b2a4ce6132be8c75cd2ff57 Mon Sep 17 00:00:00 2001 From: VAME Date: Thu, 9 Jul 2026 23:46:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(front-end):=20=E4=BE=9D=E6=8D=AE=E3=80=8A?= =?UTF-8?q?=E8=AF=A6=E7=BB=86=E8=AE=BE=E8=AE=A1=E3=80=8B=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=AF=B9=E5=89=8D=E7=AB=AF=20UI=20=E8=BF=9B=E8=A1=8C=E5=85=A8?= =?UTF-8?q?=E9=9D=A2=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 设计系统:品牌主色统一为品红渐变 #c2185b→#e91e63,新增品牌令牌与呼吸/入场动画 - 登录页重建:内部员工/外部账号切换、账号/手机验证码 Tab、SRM 存量提示 - 注册页重建为多步向导:企业信息→联系人→AI 审核,OCR 状态、密码复杂度校验与强度条 - 小滨 AI 助手重建:品牌渐变悬浮球+呼吸光环,角色感知主动提醒(紧急/AI完成/待办/风险/建议) - 新增公开门户首页 /portal:Banner 轮播 + 三列公告区 + 免责声明 - 各角色主题色对齐文档:供应商=红、评标专家=蓝、招标专员=绿 Co-authored-by: Cursor --- .gitignore | 15 + front-end/package-lock.json | 16 - front-end/src/app/App.tsx | 2 + .../src/app/components/XiaobinAssistant.tsx | 234 +++++-- .../src/app/components/shared/common.tsx | 2 +- front-end/src/app/pages/ApprovalCenter.tsx | 2 +- front-end/src/app/pages/Delegation.tsx | 2 +- front-end/src/app/pages/ExceptionDecision.tsx | 2 +- front-end/src/app/pages/LoginPage.tsx | 291 ++++++--- front-end/src/app/pages/MergeBidding.tsx | 2 +- front-end/src/app/pages/PortalHome.tsx | 224 +++++++ front-end/src/app/pages/ProjectDetail.tsx | 2 +- front-end/src/app/pages/RegisterPage.tsx | 616 ++++++++++++++++-- front-end/src/app/pages/Workbench.tsx | 10 +- front-end/src/app/roles.ts | 18 +- front-end/src/styles/theme.css | 107 ++- 16 files changed, 1301 insertions(+), 244 deletions(-) create mode 100644 .gitignore create mode 100644 front-end/src/app/pages/PortalHome.tsx diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..086069a --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# 依赖 +node_modules/ +**/node_modules/ + +# 构建产物 +dist/ +**/dist/ +.vite/ +**/.vite/ + +# 系统 / 编辑器 +.DS_Store +*.log +.idea/ +.vscode/ diff --git a/front-end/package-lock.json b/front-end/package-lock.json index 0236c84..4a847d0 100644 --- a/front-end/package-lock.json +++ b/front-end/package-lock.json @@ -5667,22 +5667,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "extraneous": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } } } } diff --git a/front-end/src/app/App.tsx b/front-end/src/app/App.tsx index abf9785..e7d9a4e 100644 --- a/front-end/src/app/App.tsx +++ b/front-end/src/app/App.tsx @@ -4,6 +4,7 @@ import { RoleProvider } from "./role-context"; import { AppLayout } from "./components/layout/AppLayout"; import { LoginPage } from "./pages/LoginPage"; import { RegisterPage } from "./pages/RegisterPage"; +import { PortalHome } from "./pages/PortalHome"; import { Workbench } from "./pages/Workbench"; import { Delegation } from "./pages/Delegation"; import { ProjectList } from "./pages/ProjectList"; @@ -39,6 +40,7 @@ export default function App() { } /> + } /> } /> }> } /> diff --git a/front-end/src/app/components/XiaobinAssistant.tsx b/front-end/src/app/components/XiaobinAssistant.tsx index 07a3d83..dfac591 100644 --- a/front-end/src/app/components/XiaobinAssistant.tsx +++ b/front-end/src/app/components/XiaobinAssistant.tsx @@ -1,20 +1,63 @@ import { useState, useRef, useEffect } from "react"; -import { Sparkles, X, Send, Bot, Lock } from "lucide-react"; +import { useNavigate } from "react-router"; +import { X, Send, Minus, Lock, ArrowRight } from "lucide-react"; import { Button } from "./ui/button"; import { cn } from "./ui/utils"; import { useRole } from "../role-context"; +import type { RoleId } from "../roles"; interface Msg { role: "bot" | "user"; text: string; } -const quickReplies = [ - "如何发起招标委托?", - "邀请招标和公开招标有什么区别?", - "在线评标怎么打分?", - "供应商如何入库?", -]; +type ProactiveType = "urgent" | "ai" | "todo" | "risk" | "suggestion"; + +interface Proactive { + type: ProactiveType; + title: string; + content: string; + to?: string; + actionLabel?: string; +} + +const TYPE_STYLE: Record = { + urgent: { bg: "bg-[#fff3e0]", border: "border-[#ffcc80]", title: "text-[#e65100]", badge: "🔥" }, + ai: { bg: "bg-[#e8f5e9]", border: "border-[#a5d6a7]", title: "text-[#2e7d32]", badge: "✅" }, + todo: { bg: "bg-[#e3f2fd]", border: "border-[#90caf9]", title: "text-[#0d47a1]", badge: "📋" }, + risk: { bg: "bg-[#ffebee]", border: "border-[#ef9a9a]", title: "text-[#c62828]", badge: "⚠️" }, + suggestion: { bg: "bg-[#f5f5f5]", border: "border-[#e0e0e0]", title: "text-[#455a64]", badge: "💡" }, +}; + +// 各角色的主动提醒(最多 3~4 条,按优先级) +const PROACTIVE: Partial> = { + specialist: [ + { type: "urgent", title: "BID-2026-005 今日需开标", content: "“2026年度防腐保温工程框架”已到开标时间,请立即组织评标", to: "/app/projects", actionLabel: "去处理" }, + { type: "ai", title: "AI 已完成:寻源报告生成", content: "“年产5万吨聚丙烯项目”已自动寻源,匹配到 12 家潜在供应商", to: "/app/projects", actionLabel: "查看" }, + { type: "todo", title: "您有 3 个委托单待审核", content: "来自技术部、采购部的委托申请已提交,请及时审核确认", to: "/app/delegation", actionLabel: "去审核" }, + ], + supplier: [ + { type: "urgent", title: "报名即将截止", content: "“年产5万吨聚丙烯项目”报名将于 7月15日截止,请尽快完成报名", to: "/app/bidding", actionLabel: "去报名" }, + { type: "todo", title: "2 笔保证金待缴纳", content: "有 2 个项目需缴纳投标保证金,逾期将影响投标资格", to: "/app/bidding", actionLabel: "去缴纳" }, + { type: "ai", title: "开标结果已公布", content: "“数字化交付平台”项目已完成开标,点击查看评标结果", to: "/app/bidding", actionLabel: "查看" }, + ], + expert: [ + { type: "urgent", title: "您收到催化剂采购项目的评标邀请", content: "请查看详情,接受邀请或申请回避(剩余不足 24 小时)", to: "/app/expert-tasks", actionLabel: "去查看" }, + { type: "todo", title: "《评标纪律承诺书》待签署", content: "进入评审前需先签署当前项目的评标纪律承诺书", to: "/app/expert-tasks", actionLabel: "去签署" }, + { type: "suggestion", title: "3 个评审项目待完成", content: "建议优先处理即将截止的评审任务,避免影响评审资格", to: "/app/expert-tasks", actionLabel: "查看" }, + ], + approver: [ + { type: "todo", title: "待审批事项 2 项", content: "有 2 个定标 / 立项审批等待您处理", to: "/app/approvals", actionLabel: "去审批" }, + ], + committee: [ + { type: "risk", title: "异常决策待处理", content: "检测到 1 个项目触发异常上报,需领导小组决策", to: "/app/exceptions", actionLabel: "去处理" }, + ], + auditor: [ + { type: "risk", title: "围串标风险提示", content: "AI 检测到 1 组投标文件相似度偏高,建议核查", to: "/app/collusion", actionLabel: "去核查" }, + ], +}; + +const quickReplies = ["如何发起招标委托?", "公开招标和邀请招标的区别?", "在线评标怎么打分?", "供应商如何入库?"]; const knowledge: Record = { 委托: @@ -26,17 +69,20 @@ const knowledge: Record = { 入库: "在「供应商管理」页点击「发起入库流程」,或在短名单管理中对供应商点击「发起入库」,系统将自动同步工商风险信息并进入审批。", 合并: - "您好,当系统中存在同类型未完成立项的委托时,系统会自动推送合并建议。您可以在立项阶段的「合并招标建议」卡片中查看并确认合并。需要我为您演示具体步骤吗?", + "当系统中存在同类型未完成立项的委托时,系统会自动推送合并建议。您可以在立项阶段的「合并招标建议」卡片中查看并确认合并。", 上传: - "进入「投标中心」→ 选择项目 → 在「上传投标文件」区域上传文件,系统会自动加密并上链存证。开标前任何人都无法查看,只有您本人可查看和修改,投标截止前还可撤回重传。", + "进入「投标中心」→ 选择项目 → 在「上传投标文件」区域上传文件,系统会自动加密并上链存证。开标前任何人都无法查看,投标截止前还可撤回重传。", }; const dataKeywords = ["报价", "投标价", "多少钱", "金额", "中标", "得分", "分数", "名单", "谁"]; export function XiaobinAssistant() { - const { sensitive } = useRole(); + const { sensitive, role, roleId } = useRole(); + const navigate = useNavigate(); const [open, setOpen] = useState(false); + const [showChat, setShowChat] = useState(false); const [input, setInput] = useState(""); + const [proactive, setProactive] = useState(PROACTIVE[roleId] ?? []); const [msgs, setMsgs] = useState([ { role: "bot", @@ -45,14 +91,21 @@ export function XiaobinAssistant() { ]); const endRef = useRef(null); + // 切换角色时刷新主动提醒 useEffect(() => { - endRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [msgs, open]); + setProactive(PROACTIVE[roleId] ?? []); + }, [roleId]); + + useEffect(() => { + if (showChat) endRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [msgs, showChat]); + + const gradient = `linear-gradient(135deg, ${role.bannerFrom}, ${role.bannerTo})`; + const unread = proactive.length; function answer(q: string) { - // 流程敏感期:拒答数据类问题 if (sensitive && dataKeywords.some((k) => q.includes(k))) { - return "当前为教学模式(流程敏感期),数据类问题暂无法回答。暂未到查看时间,请等待系统通知。我可以继续为您讲解系统操作哦~"; + return "当前为流程敏感期(如开标前),数据类问题暂无法回答。请等待系统通知。我可以继续为您讲解系统操作哦~"; } const key = Object.keys(knowledge).find((k) => q.includes(k)); return key @@ -63,80 +116,141 @@ export function XiaobinAssistant() { function send(text: string) { const q = text.trim(); if (!q) return; + setShowChat(true); setMsgs((m) => [...m, { role: "user", text: q }]); setInput(""); - setTimeout(() => { - setMsgs((m) => [...m, { role: "bot", text: answer(q) }]); - }, 500); + setTimeout(() => setMsgs((m) => [...m, { role: "bot", text: answer(q) }]), 500); + } + + function handleAction(p: Proactive) { + if (p.to) navigate(p.to); + setOpen(false); } return ( <> + {/* 悬浮球 + 呼吸光环 */} {!open && ( )} {open && ( -
-
+
+ {/* 头部 */} +
-
- -
+
-
智能助手 · 小滨
-
- 在线 · 操作教学 & 业务咨询 +
小滨 AI 助手
+
+ 在线 · 已为您分析今日待办
- +
+ + +
{sensitive && (
- 当前为教学模式,数据类问题暂无法回答 + 当前为流程敏感期,数据类问题暂无法回答
)}
- {msgs.map((m, i) => ( -
-
- {m.text} + {!showChat ? ( + <> + {proactive.length === 0 && ( +
+ 🎉 暂无待办,继续保持! +
+ )} + {proactive.map((p, i) => { + const st = TYPE_STYLE[p.type]; + return ( +
+
+
+ {st.badge} {p.title} +
+ +
+

{p.content}

+ {p.to && ( + + )} +
+ ); + })} +
+

猜你想问:

+
+ {quickReplies.map((q) => ( + + ))} +
-
- ))} - {msgs.length <= 1 && ( -
-

猜你想问:

- {quickReplies.map((q) => ( - + + ) : ( + <> + + {msgs.map((m, i) => ( +
+
+ {m.text} +
+
))} -
+
+ )} -
@@ -145,9 +259,9 @@ export function XiaobinAssistant() { onChange={(e) => setInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && send(input)} placeholder="输入您的问题…" - className="h-9 flex-1 rounded-lg border bg-input-background px-3 text-sm outline-none focus:border-primary" + className="h-9 flex-1 rounded-full border bg-input-background px-3.5 text-sm outline-none focus:border-primary" /> -
diff --git a/front-end/src/app/components/shared/common.tsx b/front-end/src/app/components/shared/common.tsx index a8278f3..a48e2fc 100644 --- a/front-end/src/app/components/shared/common.tsx +++ b/front-end/src/app/components/shared/common.tsx @@ -8,7 +8,7 @@ export function AiBadge({ label = "AI 生成", className }: { label?: string; cl return ( diff --git a/front-end/src/app/pages/ApprovalCenter.tsx b/front-end/src/app/pages/ApprovalCenter.tsx index 70f633f..2afe304 100644 --- a/front-end/src/app/pages/ApprovalCenter.tsx +++ b/front-end/src/app/pages/ApprovalCenter.tsx @@ -96,7 +96,7 @@ function ApprovalDetail({ item, onBack }: { item: ApprovalItem; onBack: () => vo
{/* AI 审批摘要 */} - +

AI 审批摘要

diff --git a/front-end/src/app/pages/Delegation.tsx b/front-end/src/app/pages/Delegation.tsx index b886d77..36f3eca 100644 --- a/front-end/src/app/pages/Delegation.tsx +++ b/front-end/src/app/pages/Delegation.tsx @@ -165,7 +165,7 @@ export function Delegation() {
- +

AI 解析提示

diff --git a/front-end/src/app/pages/ExceptionDecision.tsx b/front-end/src/app/pages/ExceptionDecision.tsx index 31347fb..506d2dc 100644 --- a/front-end/src/app/pages/ExceptionDecision.tsx +++ b/front-end/src/app/pages/ExceptionDecision.tsx @@ -106,7 +106,7 @@ function Detail({ item, onBack }: { item: Exception; onBack: () => void }) {

问题描述

{item.desc}

-
+
AI 分析摘要
diff --git a/front-end/src/app/pages/LoginPage.tsx b/front-end/src/app/pages/LoginPage.tsx index 499a1f6..221c936 100644 --- a/front-end/src/app/pages/LoginPage.tsx +++ b/front-end/src/app/pages/LoginPage.tsx @@ -1,119 +1,246 @@ import { useState } from "react"; import { useNavigate, Link } from "react-router"; -import { Hexagon, User, Lock, ShieldCheck, Sparkles, ArrowRight } from "lucide-react"; +import { Hexagon, User, Lock, Smartphone, ShieldCheck, ArrowRight, Eye, EyeOff, Info } from "lucide-react"; +import { toast } from "sonner"; import { Button } from "../components/ui/button"; +import { cn } from "../components/ui/utils"; + +type Mode = "employee" | "vendor"; +type Tab = "account" | "mobile"; export function LoginPage() { const navigate = useNavigate(); + const [mode, setMode] = useState("vendor"); + const [tab, setTab] = useState("account"); const [account, setAccount] = useState("zhangwei"); const [pwd, setPwd] = useState("123456"); + const [showPwd, setShowPwd] = useState(false); + const [phone, setPhone] = useState(""); + const [code, setCode] = useState(""); + const [remember, setRemember] = useState(true); + const [countdown, setCountdown] = useState(0); + + function sendCode() { + if (!/^1[3-9]\d{9}$/.test(phone)) { + toast.error("请输入正确的手机号"); + return; + } + toast.success("验证码已发送,请注意查收"); + setCountdown(60); + const timer = setInterval(() => { + setCountdown((c) => { + if (c <= 1) { + clearInterval(timer); + return 0; + } + return c - 1; + }); + }, 1000); + } + + function submit(e: React.FormEvent) { + e.preventDefault(); + if (mode === "employee") { + toast.info("正在跳转至集团 IAM 统一身份认证…"); + setTimeout(() => navigate("/app"), 800); + return; + } + navigate("/app"); + } return ( -
+
{/* 左侧品牌区 */} -
-
+
+
滨化集团智慧招标平台
-
Binhua Smart Tendering Platform
+
Smart Tendering Platform
-
+
-
- AI 原生 · 全流程线上闭环 -
-

- 智慧招标,AI 赋能 +

+ 智慧招标平台

-

- 从需求提报到合同归档,角色化工作台与智能助手让复杂招标流程直观、高效,实现「事找人」的主动式智能体验。 +

+ AI 原生 · 全流程线上闭环 · 主动式智能 +

+

+ 覆盖公开招标 · 邀请招标 · 竞争性谈判 · 单一来源

{[ { k: "全流程", v: "9 大阶段闭环" }, - { k: "AI 预填", v: "委托效率 +60%" }, + { k: "AI 寻源", v: "委托效率 +60%" }, { k: "链上存证", v: "投标哈希上链" }, ].map((s) => (
{s.k}
-
{s.v}
+
{s.v}
))}
-
© 2026 滨化集团股份有限公司 · 版权所有
+
© 2026 滨化集团 | 智慧招标平台 V1.0
{/* 装饰 */} -
-
+
+
+
+
{/* 右侧表单区 */}
-
-
+
+
滨化智慧招标平台
-

账号登录

-

欢迎回来,请登录您的工作账号

+
+

欢迎登录

+

登录您的账户,开始智能招采之旅

+
-
{ - e.preventDefault(); - navigate("/app"); - }} - > -
- -
- - setAccount(e.target.value)} - className="h-11 w-full rounded-lg border bg-input-background pl-10 pr-3 text-sm outline-none focus:border-primary" - placeholder="请输入账号" - /> -
-
-
- -
- - setPwd(e.target.value)} - className="h-11 w-full rounded-lg border bg-input-background pl-10 pr-3 text-sm outline-none focus:border-primary" - placeholder="请输入密码" - /> -
-
-
-
- - -
-
- 8 K 2 7 -
-
+ {/* 角色切换开关: 内部员工 / 外部账号 */} +
+ {( + [ + { id: "employee" as Mode, label: "内部员工" }, + { id: "vendor" as Mode, label: "外部账号" }, + ] + ).map((m) => ( + + ))} +
+

+ {mode === "employee" ? "当前:内部员工模式(走 IAM 集团统一认证)" : "当前:外部账号模式(走平台账号 / SRM 认证)"} +

+ + {/* Tab: 账号登录 / 手机验证码 */} +
+ {( + [ + { id: "account" as Tab, label: "账号登录" }, + { id: "mobile" as Tab, label: "手机验证码" }, + ] + ).map((t) => ( + + ))} +
+ + + {tab === "account" ? ( + <> +
+ +
+ + setAccount(e.target.value)} + className="h-11 w-full rounded-lg border bg-input-background pl-10 pr-3 text-sm outline-none transition-colors focus:border-primary" + placeholder="请输入您的账号" + /> +
+
+
+ +
+ + setPwd(e.target.value)} + className="h-11 w-full rounded-lg border bg-input-background pl-10 pr-10 text-sm outline-none transition-colors focus:border-primary" + placeholder="请输入您的密码" + /> + +
+
+ + ) : ( + <> +
+ +
+ + setPhone(e.target.value)} + className="h-11 w-full rounded-lg border bg-input-background pl-10 pr-3 text-sm outline-none transition-colors focus:border-primary" + placeholder="请输入11位手机号" + /> +
+
+
+ +
+
+ + setCode(e.target.value)} + className="h-11 w-full rounded-lg border bg-input-background pl-10 pr-3 text-sm outline-none transition-colors focus:border-primary" + placeholder="6 位验证码" + /> +
+ +
+
+ + )}
-
-
- 还不是平台供应商? - - 立即注册 +
+ 还没有账号? + {mode === "employee" ? ( + 员工账号由 HR 系统统一同步 + ) : ( + + 立即注册 + + )} +
+ + {/* SRM 温馨提示 */} + {mode === "vendor" && ( +
+ + 原 SRM 注册用户,可直接使用 SRM 账号密码登录,无需重复注册。 +
+ )} + +
+ + 浏览平台首页与公开招标公告 →
diff --git a/front-end/src/app/pages/MergeBidding.tsx b/front-end/src/app/pages/MergeBidding.tsx index 7d244c7..542186a 100644 --- a/front-end/src/app/pages/MergeBidding.tsx +++ b/front-end/src/app/pages/MergeBidding.tsx @@ -22,7 +22,7 @@ export function MergeBidding() { {/* 提示横幅 */} -
+

系统检测到 {mergeCandidates.length} 个可合并的招标委托(采购品类均为「通用设备」),合并后可降低成本、缩短周期。 diff --git a/front-end/src/app/pages/PortalHome.tsx b/front-end/src/app/pages/PortalHome.tsx new file mode 100644 index 0000000..a58afd9 --- /dev/null +++ b/front-end/src/app/pages/PortalHome.tsx @@ -0,0 +1,224 @@ +import { useEffect, useState } from "react"; +import { Link, useNavigate } from "react-router"; +import { Hexagon, ArrowRight, Sparkles, Bot } from "lucide-react"; +import { cn } from "../components/ui/utils"; + +const NAV = ["首页", "招标公告", "定标公告", "企业新闻"]; + +const BANNERS = [ + { + title: "AI 赋能的智慧招标", + subtitle: "全流程线上闭环,主动智能寻源,自动风险评估", + tags: ["智能招标委托", "供应商 AI 寻源", "自动化评标辅助"], + }, + { + title: "一次注册,双平台通用", + subtitle: "自动同步 SRM,建立统一供应商身份,秒级 AI 审核即时生效", + tags: ["营业执照 OCR", "工商实时验真", "SRM 无感登录"], + }, + { + title: "阳光招采,全程存证", + subtitle: "投标文件加密上链,评审操作哈希存证,围串标智能预警", + tags: ["链上存证", "盲评脱敏", "AI 雷同检测"], + }, +]; + +const TENDERS = [ + { title: "年产5万吨聚丙烯项目设备采购", meta: "2026-07-01 | 货物", status: "报名中", color: "#2e7d32", dot: "#4caf50" }, + { title: "数字化交付平台开发服务", meta: "2026-06-29 | 服务", status: "即将截止", color: "#e65100", dot: "#ff9800" }, + { title: "2026年度防腐保温工程框架", meta: "2026-06-28 | 工程", status: "已截止", color: "#9e9e9e", dot: "#bdbdbd" }, +]; + +const AWARDS = [ + { title: "催化剂采购项目(包1)", meta: "2026-07-01 | 中标方:滨化催化剂有限公司" }, + { title: "厂区安防监控升级项目", meta: "2026-06-30 | 中标方:海康威视" }, + { title: "办公楼装修工程", meta: "2026-06-28 | 中标方:滨州建工集团" }, +]; + +const NEWS = [ + { title: "滨化集团获评“省级智能工厂”", meta: "2026-07-01 | 集团动态" }, + { title: "2026 年供应商大会圆满举办", meta: "2026-06-25 | 合作伙伴" }, + { title: "智慧招标平台正式上线运营", meta: "2026-06-01 | 平台公告" }, +]; + +function AnnouncementCard({ + title, + tag, + headerColor, + items, + moreBg, + moreText, +}: { + title: string; + tag: string; + headerColor: string; + items: { title: string; meta: string; status?: string; color?: string; dot?: string }[]; + moreBg: string; + moreText: string; +}) { + return ( +

+
+
{title}
+
{tag}
+
+
+ {items.map((it) => ( +
+
+ {it.title} + {it.status && ( + + + {it.status} + + )} +
+
{it.meta}
+
+ ))} +
+
+ +
+
+ ); +} + +export function PortalHome() { + const navigate = useNavigate(); + const [banner, setBanner] = useState(0); + const [activeNav, setActiveNav] = useState(0); + + useEffect(() => { + const t = setInterval(() => setBanner((b) => (b + 1) % BANNERS.length), 5000); + return () => clearInterval(t); + }, []); + + const b = BANNERS[banner]; + + return ( +
+ {/* 顶部导航 */} +
+
+ + + 智慧招标平台 + Smart Tendering Platform + + +
+ + +
+
+
+ +
+ {/* Banner 轮播 */} +
+
+
+ AI 原生 · 全流程线上闭环 +
+

+ {b.title} +

+

{b.subtitle}

+
+ {b.tags.map((t) => ( + • {t} + ))} +
+ +
+ {/* 指示器 */} +
+ {BANNERS.map((_, i) => ( +
+
+ + {/* 公告区 */} +
+

+ 📢 最新公告 +

+
+ + + +
+
+ + {/* 免责声明 */} +
+
免责声明
+

+ 本平台所有招标信息均由滨化集团或其下属公司发布,信息真实有效。供应商应对投标行为负责,本平台保留对违规行为的处理权。 +

+

隐私政策 | 服务协议 | 法律声明 | © 2026 滨化集团 | 鲁ICP备XXXXXXXX号

+
+
+ + {/* AI 助手浮标 */} + +
+ ); +} diff --git a/front-end/src/app/pages/ProjectDetail.tsx b/front-end/src/app/pages/ProjectDetail.tsx index d303820..d1a3b81 100644 --- a/front-end/src/app/pages/ProjectDetail.tsx +++ b/front-end/src/app/pages/ProjectDetail.tsx @@ -123,7 +123,7 @@ export function ProjectDetail() { { t: "推荐评标办法", d: "综合评估法:技术 60 分 / 商务 20 分 / 价格 20 分,技术权重较高。" }, { t: "推荐供应商短名单", d: "基于合格供方库与历史业绩,推荐浙江中控、和利时、西门子等 3 家。" }, ].map((c) => ( -
+
{c.t}
diff --git a/front-end/src/app/pages/RegisterPage.tsx b/front-end/src/app/pages/RegisterPage.tsx index cfdd965..595284c 100644 --- a/front-end/src/app/pages/RegisterPage.tsx +++ b/front-end/src/app/pages/RegisterPage.tsx @@ -1,81 +1,591 @@ +import { useMemo, useState } from "react"; import { useNavigate, Link } from "react-router"; -import { Hexagon, ArrowLeft, CheckCircle2 } from "lucide-react"; +import { + Hexagon, + ArrowLeft, + ArrowRight, + UploadCloud, + CheckCircle2, + Loader2, + XCircle, + Building2, + UserRound, + ShieldCheck, + X, + Eye, + EyeOff, + Info, +} from "lucide-react"; import { toast } from "sonner"; import { Button } from "../components/ui/button"; +import { cn } from "../components/ui/utils"; -const fields = [ - { label: "企业全称", placeholder: "请输入营业执照上的企业全称", col: 2 }, - { label: "统一社会信用代码", placeholder: "18 位信用代码" }, - { label: "企业类型", placeholder: "如:有限责任公司" }, - { label: "法定代表人", placeholder: "请输入法人姓名" }, - { label: "注册资本(万元)", placeholder: "请输入注册资本" }, - { label: "联系人", placeholder: "请输入联系人姓名" }, - { label: "联系电话", placeholder: "请输入手机号码" }, - { label: "电子邮箱", placeholder: "用于接收平台通知" }, - { label: "经营类别", placeholder: "如:自动化控制 / 环保工程" }, - { label: "企业地址", placeholder: "请输入详细注册地址", col: 2 }, +type Registrant = "supplier" | "expert"; +type AuditState = "idle" | "auditing" | "success" | "rejected"; + +const SUPPLIER_STEPS = ["企业信息", "联系人", "提交 / 自动审核"]; +const EXPERT_STEPS = ["基本信息", "专业资质 / 收款", "提交审核"]; + +interface PwdRule { + label: string; + test: (v: string) => boolean; +} + +const PWD_RULES: PwdRule[] = [ + { label: "长度 8~20 位", test: (v) => v.length >= 8 && v.length <= 20 }, + { label: "包含大写字母(A-Z)与小写字母(a-z)", test: (v) => /[A-Z]/.test(v) && /[a-z]/.test(v) }, + { label: "包含数字(0-9)与特殊字符(!@#$%^&*)", test: (v) => /\d/.test(v) && /[!@#$%^&*()_+\-=]/.test(v) }, + { label: "禁止连续/重复字符(如 12345678、aaaa)", test: (v) => !/(.)\1{3,}/.test(v) && !/0123|1234|2345|3456|4567|5678|6789/.test(v) }, + { label: "非常见弱口令(password、admin123 等)", test: (v) => !["password", "admin123", "12345678", "qwerty"].includes(v.toLowerCase()) }, ]; +function Field({ + label, + placeholder, + required, + hint, + className, + type = "text", + readOnly, +}: { + label: string; + placeholder?: string; + required?: boolean; + hint?: string; + className?: string; + type?: string; + readOnly?: boolean; +}) { + return ( +
+ + + {hint &&

{hint}

} +
+ ); +} + +function StepBar({ steps, current }: { steps: string[]; current: number }) { + return ( +
+ {steps.map((s, i) => ( +
+
+
+ {i < current ? : i + 1} +
+ {s} +
+ {i < steps.length - 1 && ( +
+ )} +
+ ))} +
+ ); +} + +function Notice({ items }: { items: string[] }) { + return ( +
+
+ 注册须知 +
+
    + {items.map((t, i) => ( +
  1. + {i + 1}. {t} +
  2. + ))} +
+
+ ); +} + export function RegisterPage() { const navigate = useNavigate(); + const [registrant, setRegistrant] = useState("supplier"); + const [step, setStep] = useState(0); + const [ocr, setOcr] = useState<"none" | "scanning" | "done">("none"); + const [audit, setAudit] = useState("idle"); + const [products, setProducts] = useState(["工业阀门", "管道配件"]); + const [productInput, setProductInput] = useState(""); + const [pwd, setPwd] = useState(""); + const [confirmPwd, setConfirmPwd] = useState(""); + const [showPwd, setShowPwd] = useState(false); + const [smsCountdown, setSmsCountdown] = useState(0); + + const steps = registrant === "supplier" ? SUPPLIER_STEPS : EXPERT_STEPS; + + const passedRules = useMemo(() => PWD_RULES.map((r) => r.test(pwd)), [pwd]); + const passedCount = passedRules.filter(Boolean).length; + const strength: 0 | 1 | 2 = passedCount >= 5 && pwd.length >= 12 ? 2 : passedCount >= 4 ? 1 : 0; + + function switchRegistrant(r: Registrant) { + setRegistrant(r); + setStep(0); + setAudit("idle"); + setOcr("none"); + } + + function triggerOcr() { + setOcr("scanning"); + setTimeout(() => { + setOcr("done"); + toast.success("营业执照识别完成,已自动回填企业信息"); + }, 1500); + } + + function addProduct() { + const v = productInput.trim(); + if (v && products.length < 20 && !products.includes(v)) { + setProducts((p) => [...p, v]); + } + setProductInput(""); + } + + function sendSms() { + toast.success("验证码已发送"); + setSmsCountdown(60); + const t = setInterval(() => { + setSmsCountdown((c) => { + if (c <= 1) { + clearInterval(t); + return 0; + } + return c - 1; + }); + }, 1000); + } + + function submitAudit() { + setStep(steps.length - 1); + setAudit("auditing"); + setTimeout(() => { + setAudit("success"); + }, 3800); + } + + const isLastFormStep = step === steps.length - 2; + return ( -
+
-
- - 滨化集团智慧招标平台 · 供应商注册 +
+
+ +
+ 滨化集团智慧招标平台 · 新用户注册
返回登录
-
-
-

供应商入驻注册

-

- 完善企业信息提交后,平台将自动同步工商风险信息并进入资质审核,预计 1-3 个工作日完成。 -

+
+ {/* 注册对象选择 */} +
+ {( + [ + { id: "supplier" as Registrant, label: "供应商注册", icon: Building2 }, + { id: "expert" as Registrant, label: "外部专家注册", icon: UserRound }, + ] + ).map((r) => { + const Icon = r.icon; + return ( + + ); + })}
-
-

企业基本信息

-
{ - e.preventDefault(); - toast.success("注册申请已提交,平台正在进行资质审核"); - setTimeout(() => navigate("/"), 1200); - }} - > - {fields.map((f) => ( -
- - + +
+ AI 即时审核 · 秒级通过 +
+
+ + {/* ============ 供应商注册 ============ */} + {registrant === "supplier" && ( + <> + {step === 0 && ( +
+
+

供应商注册

+

请填写企业真实信息,上传营业执照,系统将自动识别并回填

+
+ + {/* 营业执照上传 */} +
+ +
上传营业执照(JPG/PNG/PDF,≤10MB)
+
支持拖拽或点击上传,AI 将自动识别企业信息并填充表单
+
+ + {ocr === "scanning" && ( +
+ 正在识别营业执照... +
+ )} + {ocr === "done" && ( +
+ 已识别:营业执照信息提取成功(统一信用代码:91370100XXXXXXXXXX) +
+ )} + +
+ + + + + + +
+ +