7547de82ec
- 任务卡/详情显示「✓可抢 / 条件不符(附原因)」徽标;原因展示在卡内 - 条件不符时禁用抢单按钮;未登录仅浏览、抢单/报名引导登录 - 竞标任务按钮显示「报名」;详情页同样按 can_accept 放行/禁用 - 首页「接单生态」CTA 指向 #/tasks 任务广场
90 lines
4.7 KiB
React
90 lines
4.7 KiB
React
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>
|
||
);
|
||
}
|