feat(web): 任务广场/详情透出 can_accept(可接/原因) + 按角色放行抢单
- 任务卡/详情显示「✓可抢 / 条件不符(附原因)」徽标;原因展示在卡内 - 条件不符时禁用抢单按钮;未登录仅浏览、抢单/报名引导登录 - 竞标任务按钮显示「报名」;详情页同样按 can_accept 放行/禁用 - 首页「接单生态」CTA 指向 #/tasks 任务广场
This commit is contained in:
@@ -20,6 +20,9 @@
|
||||
.tk-cat { font-size: 11px; font-weight: 700; color: var(--primary); background: var(--primary-soft); border-radius: 999px; padding: 3px 10px; }
|
||||
.tk-mode { font-size: 11px; font-weight: 600; color: var(--on-surface); background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 3px 10px; }
|
||||
.tk-excl { font-size: 11px; font-weight: 600; color: var(--warning); background: var(--warning-soft); border-radius: 999px; padding: 3px 10px; }
|
||||
.tk-can { font-size: 11px; font-weight: 700; color: var(--success); background: var(--success-soft); border-radius: 999px; padding: 3px 10px; }
|
||||
.tk-nocan { font-size: 11px; font-weight: 700; color: var(--muted); background: var(--panel-2); border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px; }
|
||||
.tk-why { margin-top: 10px; font-size: 12px; color: var(--muted); line-height: 1.5; }
|
||||
.tk-title { font-size: 17px; font-weight: 700; color: var(--heading); line-height: 1.4; }
|
||||
.tk-desc { margin-top: 8px; font-size: 13.5px; color: var(--text-soft); line-height: 1.65; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.tk-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
||||
|
||||
@@ -165,7 +165,7 @@ export default function Home() {
|
||||
<h2 className="pt-h">我们开放<span className="gr">接单生态</span></h2>
|
||||
<p className="pt-p">把你的技能、经验、兴趣,变成能交付的东西——一段录课、一个模板、一次咨询、一份陪跑。到生态里接下属于自己的单。</p>
|
||||
<p className="pt-p dim">先来交流沙龙,聊聊你那门手艺怎么变现。</p>
|
||||
<div className="mk-cta"><button className="btn-dark2" onClick={() => { location.hash = '#/events'; }}>了解接单生态 <Icon name="arrow-right" size="sm" /></button></div>
|
||||
<div className="mk-cta"><button className="btn-dark2" onClick={() => { location.hash = '#/tasks'; }}>去任务广场接单 <Icon name="arrow-right" size="sm" /></button></div>
|
||||
</div>
|
||||
<div className="pt-panel tint-cream"><div className="pt-window"><div className="pt-window-in">
|
||||
<div className="pt-window-head"><span className="pt-win-title">待接单</span><span className="pt-live"><i />Live</span></div>
|
||||
|
||||
@@ -19,6 +19,9 @@ export default function TaskDetail() {
|
||||
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('');
|
||||
@@ -42,6 +45,8 @@ export default function TaskDetail() {
|
||||
<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>}
|
||||
@@ -64,9 +69,16 @@ export default function TaskDetail() {
|
||||
{task.publisher_name ? <div className="tk-fact"><dt>发包方</dt><dd>{task.publisher_name}</dd></div> : null}
|
||||
</dl>
|
||||
<div className="tk-detail-actions">
|
||||
<Button variant="cta" onClick={onGrab} loading={busy}>抢单</Button>
|
||||
{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>}
|
||||
{!isAuthed() && <p className="tk-note">抢单/报名需登录,<a href="#/profile">去登录 / 注册</a>。</p>}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@@ -14,12 +14,16 @@ const money = (a, b) => {
|
||||
};
|
||||
|
||||
function TaskCard({ t, onGrab, busy, mine }) {
|
||||
const can = t.can_accept; // true/false/undefined(未登录)
|
||||
const reasons = Array.isArray(t.accept_reasons) ? t.accept_reasons : [];
|
||||
return (
|
||||
<a className="tk-card" href={`#/task-detail?id=${t.id}`}>
|
||||
<div className="tk-card-top">
|
||||
<span className="tk-cat">{t.category || '综合'}</span>
|
||||
<span className="tk-mode">{MODE[t.mode] || '抢单'}</span>
|
||||
{t.mode === 'bid' && <span className="tk-mode">竞标</span>}
|
||||
{t.exclusive && <span className="tk-excl">独占</span>}
|
||||
{can === true && <span className="tk-can">✓ 可抢</span>}
|
||||
{can === false && <span className="tk-nocan" title={reasons.join(';')}>条件不符</span>}
|
||||
</div>
|
||||
<h3 className="tk-title">{t.title}</h3>
|
||||
<p className="tk-desc">{t.description}</p>
|
||||
@@ -29,10 +33,14 @@ function TaskCard({ t, onGrab, busy, mine }) {
|
||||
{t.deadline && <span><Icon name="calendar" size="sm" />{t.deadline}</span>}
|
||||
{t.delivery_days ? <span><Icon name="clock" size="sm" />{t.delivery_days} 天交付</span> : null}
|
||||
</div>
|
||||
{can === false && <p className="tk-why">{reasons.join(';')}</p>}
|
||||
<div className="tk-actions">
|
||||
<Button variant={mine ? 'dark' : 'ghost'} loading={busy} onClick={(e) => { e.preventDefault(); e.stopPropagation(); onGrab(t); }}>
|
||||
{mine ? '查看详情' : '抢单'}
|
||||
</Button>
|
||||
{!mine && can !== false && (
|
||||
<Button variant="ghost" loading={busy} onClick={(e) => { e.preventDefault(); e.stopPropagation(); onGrab(t); }}>
|
||||
{t.mode === 'bid' ? '报名' : '抢单'}
|
||||
</Button>
|
||||
)}
|
||||
{mine && !t.can_accept && <Button variant="dark" onClick={(e) => { e.preventDefault(); e.stopPropagation(); }}>我的接单</Button>}
|
||||
<span className="tk-code" title="任务短码">{t.task_code}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user