From 2dae55741e8110f476e7e80ae7cda5a85903d54e Mon Sep 17 00:00:00 2001 From: Pine Date: Thu, 27 Aug 2026 19:43:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E4=BC=81=E4=B8=9A=E7=AB=AF?= =?UTF-8?q?=E9=97=A8=E6=88=B7(=E7=94=B2=E6=96=B9/service)=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E5=8F=91=E5=B8=83=E4=BB=BB=E5=8A=A1/=E7=AB=9E?= =?UTF-8?q?=E6=A0=87=E8=AF=84=E5=AE=A1/=E9=AA=8C=E6=94=B6=E7=BB=93?= =?UTF-8?q?=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - services/enterprise.js:/enterprise/tasks* (发标/提交发布/竞标列表/评标中标/验收) - platform/Enterprise.jsx:service 角色门户(登录墙+角色校验) · 发标表单:模式/可见性/预算/名额/竞标报名·中标数/接单条件JSON/托管方式 + C端可见/公有/独占 · 我的任务列表 + 竞标查看/评标中标 + 验收通过(结算)/驳回 + 重新上架 - 路由 #/enterprise;浅色 token 样式 --- website/src/App.jsx | 2 + website/src/platform/Enterprise.jsx | 194 ++++++++++++++++++++++++++++ website/src/services/enterprise.js | 49 +++++++ website/src/styles/enterprise.css | 34 +++++ 4 files changed, 279 insertions(+) create mode 100644 website/src/platform/Enterprise.jsx create mode 100644 website/src/services/enterprise.js create mode 100644 website/src/styles/enterprise.css diff --git a/website/src/App.jsx b/website/src/App.jsx index 692ced8..82399f1 100644 --- a/website/src/App.jsx +++ b/website/src/App.jsx @@ -33,6 +33,7 @@ import Content from './user/Content'; import ContentDetail from './user/ContentDetail'; import Tasks from './user/Tasks'; import TaskDetail from './user/TaskDetail'; +import Enterprise from './platform/Enterprise'; /* 公开路由(无需登录) */ const PUBLIC_ROUTES = [ @@ -50,6 +51,7 @@ const PUBLIC_ROUTES = [ { path: 'content-detail', Page: ContentDetail }, { path: 'tasks', Page: Tasks }, { path: 'task-detail', Page: TaskDetail }, + { path: 'enterprise', Page: Enterprise }, { path: 'profile', Page: Profile } ]; diff --git a/website/src/platform/Enterprise.jsx b/website/src/platform/Enterprise.jsx new file mode 100644 index 0000000..1c1cd97 --- /dev/null +++ b/website/src/platform/Enterprise.jsx @@ -0,0 +1,194 @@ +import React from 'react'; +import { ContentTemplate } from '@/components/templates'; +import { SectionHead, Button, Icon } from '@/components/atoms'; +import AuthGate from '@/components/AuthGate'; +import { isAuthed, getUser } from '@/services/auth'; +import { listTasks, createTask, submitTask, listBids, winBid, reviewTask } from '@/services/enterprise'; +import '@/styles/enterprise.css'; + +const MODE = { grab: '抢单', bid: '竞标', assign: '指派', recommend: '推荐' }; +const MODE_OPT = [ + ['grab', '抢单(可设名额)'], + ['bid', '竞标(报名/中标数)'], + ['assign', '指派(给OPC或园区)'], +]; +const VIS_OPT = [['public', '公开'], ['assigned_only', '仅被指派'], ['c_visible', 'C端可见']]; + +const EMPTY = () => ({ + title: '', category: '', sub_category: '', description: '', tags: '', + mode: 'grab', budget_min: '0', budget_max: '0', deadline: '', delivery_days: '0', + headcount: '0', exclusive: false, publisher_name: '', publisher_role: 'enterprise', + visibility: 'public', c_visible: true, park_public: false, assign_type: '', + assign_park_id: '', assign_opc_id: '', bid_quota: '0', win_quota: '0', + eligibility: '{}', settle_mode: 'operator_escrow', +}); + +export default function Enterprise() { + const [authed, setAuthed] = React.useState(isAuthed()); + const [items, setItems] = React.useState([]); + const [loaded, setLoaded] = React.useState(false); + const [err, setErr] = React.useState(''); + const [editing, setEditing] = React.useState(false); + const [form, setForm] = React.useState(EMPTY()); + const [busy, setBusy] = React.useState(false); + const [bids, setBids] = React.useState({}); + + const refresh = async () => { + try { setItems(await listTasks()); } catch { setItems([]); } + finally { setLoaded(true); setErr(''); } + }; + React.useEffect(() => { if (authed) refresh(); }, [authed]); + + const role = (getUser() || {}).role; + + const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.type === 'checkbox' ? e.target.checked : e.target.value })); + const setSel = (k, v) => setForm((f) => ({ ...f, [k]: v })); + + const save = async () => { + setBusy(true); setErr(''); + const body = { + ...form, + budget_min: Number(form.budget_min) || 0, budget_max: Number(form.budget_max) || 0, + delivery_days: Number(form.delivery_days) || 0, headcount: Number(form.headcount) || 0, + bid_quota: Number(form.bid_quota) || 0, win_quota: Number(form.win_quota) || 0, + exclusive: !!form.exclusive, + }; + try { + const t = await createTask(body); + setEditing(false); setForm(EMPTY()); setErr(''); + if (t && t.id) await submitTask(t.id); + await refresh(); + } catch (e) { setErr((e && e.message) || '发布失败'); } + finally { setBusy(false); } + }; + + const openBids = async (t) => { + const list = await listBids(t.id); + setBids((b) => ({ ...b, [t.id]: list })); + }; + const doWin = async (t, bidId) => { + try { await winBid(t.id, bidId); await refresh(); } + catch (e) { setErr((e && e.message) || '评标失败'); } + }; + const doReview = async (t, action) => { + try { await reviewTask(t.id, action); await refresh(); } + catch (e) { setErr((e && e.message) || '验收失败'); } + }; + + if (!authed) { + return ( + +
+ { setAuthed(true); refresh(); }} /> +
+
+ ); + } + if (role !== 'service') { + return ( + +
+

企业任务中心

+

当前账号非企业(service)身份,无法使用企业发标/评审功能。

+
+
+ ); + } + + return ( + +
+
+ +

发布任务(抢单 / 竞标 / 指派),查看竞标并评审中标,履约后验收结算。资金由运营平台托管。

+
+ + {editing ? ( +
+

发新任务

+
+ + + + +
+ +
+
+ +
+ + + + + + + + +
+