Files
training/website/src/user/TaskDetail.jsx
T
Pine 7547de82ec feat(web): 任务广场/详情透出 can_accept(可接/原因) + 按角色放行抢单
- 任务卡/详情显示「✓可抢 / 条件不符(附原因)」徽标;原因展示在卡内
- 条件不符时禁用抢单按钮;未登录仅浏览、抢单/报名引导登录
- 竞标任务按钮显示「报名」;详情页同样按 can_accept 放行/禁用
- 首页「接单生态」CTA 指向 #/tasks 任务广场
2026-08-27 19:09:35 +08:00

90 lines
4.7 KiB
React
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.
import React from 'react';
import { ContentTemplate } from '@/components/templates';
import { Button, Icon } from '@/components/atoms';
import { isAuthed } from '@/services/auth';
import { getTask, grabTask } from '@/services/tasks';
import '@/styles/tasks.css';
const MODE = { grab: '抢单', bid: '投标', assign: '指派', recommend: '推荐' };
const money = (a, b) => (a || b) ? (a && b && a !== b ? `¥${a}${b}` : `¥${a || b}`) : '面议';
const getId = () => new URLSearchParams((window.location.hash.split('?')[1] || '')).get('id');
export default function TaskDetail() {
const [task, setTask] = React.useState(null);
const [err, setErr] = React.useState('');
const [loaded, setLoaded] = React.useState(false);
const [busy, setBusy] = React.useState(false);
React.useEffect(() => {
getId() && getTask(getId()).then((t) => { setTask(t || null); setLoaded(true); });
}, []);
const reasons = Array.isArray(task && task.accept_reasons) ? task.accept_reasons : [];
const can = task && task.can_accept; // true/false/undefined
const onGrab = async () => {
if (!isAuthed()) { setErr('抢单需先登录(OPC 端身份)。'); return; }
setBusy(true); setErr('');
const r = await grabTask(task.id);
setBusy(false);
if (r.ok) setErr('抢单成功,可在「我的接单」查看。');
else setErr(r.error || '抢单失败');
};
if (!loaded) return <ContentTemplate><div className="tk-page"><p className="tk-empty">加载中</p></div></ContentTemplate>;
if (!task) return <ContentTemplate><div className="tk-page"><p className="tk-empty">任务不存在或已下线</p></div></ContentTemplate>;
return (
<ContentTemplate>
<section className="tk-page">
<a className="tk-back" href="#/tasks"><Icon name="arrow-left" size="sm" />返回任务广场</a>
<article className="tk-detail">
<div className="tk-detail-head">
<div className="tk-cat-row">
<span className="tk-cat">{task.category || '综合'}</span>
<span className="tk-mode">{MODE[task.mode] || '抢单'}</span>
{task.exclusive && <span className="tk-excl">独占</span>}
<span className="tk-status">{task.status === 'published' ? '待接单' : task.status}</span>
{can === true && <span className="tk-can"> 你可抢</span>}
{can === false && <span className="tk-nocan" title={reasons.join('')}>条件不符</span>}
</div>
<h1 className="tk-detail-title">{task.title}</h1>
{task.task_code && <span className="tk-code">短码 {task.task_code}</span>}
</div>
<div className="tk-detail-grid">
<div className="tk-detail-main">
<h2 className="tk-sec">任务说明</h2>
<p className="tk-full-desc">{task.description}</p>
{task.tags && <div className="tk-tags">{String(task.tags).split(',').filter(Boolean).map((x) => <span className="tk-tag" key={x}>{x}</span>)}</div>}
</div>
<aside className="tk-detail-side">
<dl className="tk-facts">
<div className="tk-fact"><dt>预算</dt><dd className="money">{money(task.budget_min, task.budget_max)}</dd></div>
<div className="tk-fact"><dt>分类</dt><dd>{task.category}{task.sub_category ? ` · ${task.sub_category}` : ''}</dd></div>
<div className="tk-fact"><dt>接单模式</dt><dd>{MODE[task.mode] || task.mode}</dd></div>
<div className="tk-fact"><dt>截止</dt><dd>{task.deadline || '面议'}</dd></div>
<div className="tk-fact"><dt>交付时限</dt><dd>{task.delivery_days ? `${task.delivery_days}` : '面议'}</dd></div>
<div className="tk-fact"><dt>接单人数</dt><dd>{task.headcount ? `${task.headcount}` : '不限'}</dd></div>
{task.publisher_name ? <div className="tk-fact"><dt>发包方</dt><dd>{task.publisher_name}</dd></div> : null}
</dl>
<div className="tk-detail-actions">
{can === false ? (
<>
<Button variant="ghost" disabled>暂不可抢</Button>
<p className="tk-note tk-why">{reasons.join('')}</p>
</>
) : (
<Button variant="cta" onClick={onGrab} loading={busy}>{task.mode === 'bid' ? '报名' : '抢单'}</Button>
)}
{err && <p className="tk-err">{err}</p>}
{!isAuthed() && <p className="tk-note">抢单/报名需登录<a href="#/profile">去登录 / 注册</a></p>}
</div>
</aside>
</div>
</article>
</section>
</ContentTemplate>
);
}