feat(mine): 小程序「我的」页对齐 Web 个人中心 + 我的报名/任务/园区/认证子页
- mine 页:我的服务宫格(报名/任务/园区/认证)+账号绑定状态卡(手机号/微信/小程序)+身份标签;pickUser 持久化 wxBound/wxMiniBound/account_type_label - 新增子页 my-bookings/my-tasks/my-park/my-cert(复用 getMyBookings/getMyTasks/getMyAdmission/getOpCertMine) - app.config 注册 4 子页;build:weapp 通过
This commit is contained in:
@@ -16,7 +16,11 @@ export default {
|
||||
'pages/content-detail/index',
|
||||
'pages/tasks/index',
|
||||
'pages/task-detail/index',
|
||||
'pages/scan-login/index'
|
||||
'pages/scan-login/index',
|
||||
'pages/my-bookings/index',
|
||||
'pages/my-tasks/index',
|
||||
'pages/my-park/index',
|
||||
'pages/my-cert/index'
|
||||
],
|
||||
window: {
|
||||
backgroundTextStyle: 'light',
|
||||
|
||||
@@ -41,6 +41,15 @@ const fmtTime = (iso) => {
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())} 周${week}`;
|
||||
};
|
||||
|
||||
/** 我的服务宫格项:label → 子页 */
|
||||
const SVC = [
|
||||
['报名', '/pages/my-bookings/index'],
|
||||
['任务', '/pages/my-tasks/index'],
|
||||
['园区', '/pages/my-park/index'],
|
||||
['认证', '/pages/my-cert/index'],
|
||||
];
|
||||
const maskPhone = (p) => (/^1\d{10}$/.test(p || '') ? p.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : (p || ''));
|
||||
|
||||
export default function Mine() {
|
||||
useTabIndex(4);
|
||||
const [user, setUser] = useState(getUser());
|
||||
@@ -328,6 +337,9 @@ export default function Mine() {
|
||||
<Text className={`phone-tag${user.phoneBound ? ' bound' : ''}`}>
|
||||
{user.phoneBound ? (user.phone ? `已绑定 ${user.phone}` : '已绑定手机') : '未绑定手机'}
|
||||
</Text>
|
||||
{user.account_type_label || user.role ? (
|
||||
<Text className="role-tag">{user.account_type_label || user.role}</Text>
|
||||
) : null}
|
||||
{nickDraft && nickDraft !== ((user && user.name) || '') && (
|
||||
<Button className="btn-ghost save-nick" onClick={onSaveNick}>保存昵称</Button>
|
||||
)}
|
||||
@@ -335,6 +347,30 @@ export default function Mine() {
|
||||
<Button className="btn-ghost logout-btn" onClick={onLogout}>退出</Button>
|
||||
</View>
|
||||
|
||||
{/* 我的服务宫格:报名 / 任务 / 园区 / 认证 */}
|
||||
<View className="mk-grid">
|
||||
{SVC.map(([label, url]) => (
|
||||
<View className="mk-grid-item" key={label} onClick={() => Taro.navigateTo({ url })}>
|
||||
<View className="mk-grid-ico"><Text>{label.slice(0, 1)}</Text></View>
|
||||
<Text className="mk-grid-label">我的{label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 账号绑定状态 */}
|
||||
<View className="card bind-state">
|
||||
<Text className="bind-title">账号绑定</Text>
|
||||
<View className="bind-line"><Text className="bind-key">手机号</Text>
|
||||
<Text className={`bind-val${user.phoneBound ? ' on' : ''}`}>{user.phoneBound ? maskPhone(user.phone) : '未绑定'}</Text>
|
||||
</View>
|
||||
<View className="bind-line"><Text className="bind-key">微信(开放平台)</Text>
|
||||
<Text className={`bind-val${user.wxBound ? ' on' : ''}`}>{user.wxBound ? '已绑定' : '未绑定'}</Text>
|
||||
</View>
|
||||
<View className="bind-line"><Text className="bind-key">小程序</Text>
|
||||
<Text className={`bind-val${user.wxMiniBound ? ' on' : ''}`}>{user.wxMiniBound ? '已绑定' : '未绑定'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 跨端口扫码登录:由电脑/网页登录页展示「小程序码」,微信扫码自动进入小程序授权页确认 */}
|
||||
<View className="card next-card">
|
||||
<Text className="next-lbl">登录其它端</Text>
|
||||
|
||||
@@ -378,3 +378,18 @@
|
||||
.save-nick { margin-top: 12rpx; align-self: flex-start; line-height: 2.2; padding: 0 28rpx; font-size: 24rpx; }
|
||||
|
||||
.scan-err { display:block; margin-top:12rpx; font-size:24rpx; color:#f0b8b8; text-align:center; }
|
||||
|
||||
/* ---- 我的服务宫格 ---- */
|
||||
.role-tag { display:inline-block; margin-top:10rpx; padding:2rpx 20rpx; border-radius:999px; font-size:24rpx; color:#1f8ce0; background:#eaf6ff; align-self:flex-start; }
|
||||
.mk-grid { display:flex; gap:16rpx; padding:0 24rpx; margin-top:24rpx; }
|
||||
.mk-grid-item { flex:1; background:#fff; border-radius:24rpx; padding:24rpx 8rpx; display:flex; flex-direction:column; align-items:center; gap:10rpx; box-shadow:0 8rpx 22rpx -12rpx rgba(13,47,78,.16); }
|
||||
.mk-grid-ico { width:72rpx; height:72rpx; border-radius:50%; display:flex; align-items:center; justify-content:center; background:#f2f8ff; color:#06acf1; font-size:30rpx; font-weight:700; }
|
||||
.mk-grid-label { font-size:24rpx; color:#23315c; font-weight:600; }
|
||||
|
||||
/* ---- 账号绑定状态 ---- */
|
||||
.bind-state { margin-top:24rpx; }
|
||||
.bind-line { display:flex; align-items:center; justify-content:space-between; padding:16rpx 2rpx; border-bottom:1px solid #f0f2f6; }
|
||||
.bind-line:last-child { border-bottom:none; }
|
||||
.bind-key { font-size:26rpx; color:#6a6a73; }
|
||||
.bind-val { font-size:26rpx; color:#98a0ab; }
|
||||
.bind-val.on { color:#00a06e; font-weight:600; }
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export default { navigationBarTitleText: '我的报名' };
|
||||
@@ -0,0 +1,92 @@
|
||||
import { View, Text, Button } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { isAuthed } from '@/utils/auth';
|
||||
import { getMyBookings, checkIn, parseIso } from '@/utils/booking';
|
||||
import './index.scss';
|
||||
|
||||
const PHASE_META = {
|
||||
pending: { text: '审核中', cls: 'pending' },
|
||||
rejected: { text: '未通过', cls: 'rejected' },
|
||||
done: { text: '已结束', cls: 'done' },
|
||||
upcoming: { text: '待参加', cls: 'upcoming' }
|
||||
};
|
||||
const phaseOf = (b) => {
|
||||
const audit = b.auditStatus !== undefined ? b.auditStatus : b.audit_status;
|
||||
const evStatus = b.event_status !== undefined ? b.event_status : b.eventStatus;
|
||||
if (audit === 'pending') return PHASE_META.pending;
|
||||
if (audit === 'rejected') return PHASE_META.rejected;
|
||||
if (evStatus === 'done') return PHASE_META.done;
|
||||
return PHASE_META.upcoming;
|
||||
};
|
||||
const fmtTime = (iso) => {
|
||||
if (!iso) return '';
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(+d)) return iso;
|
||||
const p = (n) => String(n).padStart(2, '0');
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
|
||||
};
|
||||
|
||||
export default function MyBookings() {
|
||||
const [bookings, setBookings] = useState([]);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [err, setErr] = useState('');
|
||||
const [busyId, setBusyId] = useState('');
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
const d = await getMyBookings();
|
||||
setBookings((d && d.list) || []);
|
||||
} catch (e) { setErr((e && e.message) || '加载失败'); }
|
||||
finally { setLoaded(true); }
|
||||
};
|
||||
useEffect(() => { load(); }, []);
|
||||
|
||||
const onCheckin = async (id) => {
|
||||
setBusyId(id); setErr('');
|
||||
try {
|
||||
const r = await checkIn(id);
|
||||
if (r.ok) Taro.showToast({ title: '签到成功', icon: 'success' });
|
||||
else Taro.showToast({ title: r.error || '签到失败', icon: 'none' });
|
||||
load();
|
||||
} catch (e) { Taro.showToast({ title: (e && e.message) || '签到失败', icon: 'none' }); }
|
||||
finally { setBusyId(''); }
|
||||
};
|
||||
const goActivities = () => Taro.switchTab({ url: '/pages/booking/index' });
|
||||
|
||||
return (
|
||||
<View className="page-in">
|
||||
<View className="mk-head">
|
||||
<Text className="mk-title">我的报名</Text>
|
||||
<Text className="mk-sub">你参加过的公益课 / 活动报名与审核状态。</Text>
|
||||
</View>
|
||||
{!isAuthed() && <View className="card"><Text className="muted">请先登录(我的 → 登录绑定)。</Text></View>}
|
||||
{err && <Text className="mk-err">{err}</Text>}
|
||||
{!loaded ? <Text className="muted">加载中…</Text>
|
||||
: bookings.length === 0 ? (
|
||||
<View className="card mk-empty">
|
||||
<Text className="muted">还没有报名记录。</Text>
|
||||
<Text className="link" onClick={goActivities}>去看活动 →</Text>
|
||||
</View>
|
||||
) : bookings.map((b) => {
|
||||
const p = phaseOf(b);
|
||||
const ended = b.event_status === 'done';
|
||||
const ready = b.can_checkin;
|
||||
return (
|
||||
<View className="mk-card" key={b.id}>
|
||||
<View className="mk-row">
|
||||
<Text className="mk-name">{b.eventTitle || b.want || '报名'}</Text>
|
||||
<Text className={`mk-phase ${p.cls}`}>{p.text}</Text>
|
||||
</View>
|
||||
{b.eventStart ? <Text className="mk-meta">场次 {fmtTime(b.eventStart)}</Text> : null}
|
||||
<Text className="mk-meta">报名于 {fmtTime(b.createdAt)}</Text>
|
||||
{b.checkinAt ? <Text className="mk-checked">已于 {fmtTime(b.checkinAt)} 签到</Text>
|
||||
: ended ? <Text className="mk-checked done">本场已结束</Text>
|
||||
: ready ? <Button className="btn-main mk-checkin" loading={busyId === b.id} onClick={() => onCheckin(b.id)}>活动签到</Button>
|
||||
: <Text className="mk-checked">未到签到时间,活动开始后即可签到</Text>}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
.mk-head { padding: 18px 20px 6px; }
|
||||
.mk-title { font-size: 24px; font-weight: 700; color: #0d2f4e; display: block; }
|
||||
.mk-sub { font-size: 13px; color: #8a8a93; margin-top: 4px; display: block; }
|
||||
.mk-err { color: #e0544c; font-size: 13px; padding: 0 20px; }
|
||||
.mk-card { background: #fff; border-radius: 18px; padding: 16px 18px; margin: 10px 20px; box-shadow: 0 6px 18px -8px rgba(13,47,78,.12); }
|
||||
.mk-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
||||
.mk-name { font-size: 16px; font-weight: 600; color: #12233b; flex: 1; }
|
||||
.mk-phase { flex: 0 0 auto; font-size: 12px; padding: 2px 12px; border-radius: 999px; }
|
||||
.mk-phase.upcoming { color: #1f8ce0; background: #eaf6ff; }
|
||||
.mk-phase.pending { color: #e6a400; background: #fff6e2; }
|
||||
.mk-phase.rejected { color: #98a0ab; background: #f2f2f6; }
|
||||
.mk-phase.done { color: #98a0ab; background: #f2f2f6; }
|
||||
.mk-meta { display: block; font-size: 12px; color: #98a0ab; margin-top: 6px; }
|
||||
.mk-checked { display: block; margin-top: 10px; font-size: 13px; color: #00a06e; }
|
||||
.mk-checked.done { color: #98a0ab; }
|
||||
.mk-checkin { margin-top: 12px; }
|
||||
.mk-empty { text-align: center; padding: 40px 20px; }
|
||||
@@ -0,0 +1 @@
|
||||
export default { navigationBarTitleText: '认证中心' };
|
||||
@@ -0,0 +1,59 @@
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { isAuthed } from '@/utils/auth';
|
||||
import { getOpCertMine } from '@/utils/profile';
|
||||
import './index.scss';
|
||||
|
||||
const CERT = {
|
||||
uncertified: ['未认证', 'muted'],
|
||||
pending: ['待提交', 'pending'],
|
||||
reviewing: ['审核中', 'pending'],
|
||||
certified: ['已认证', 'ok'],
|
||||
rejected: ['未通过', 'rejected'],
|
||||
};
|
||||
const badge = (s) => CERT[String(s || '').toLowerCase()] || [s || '未认证', 'muted'];
|
||||
|
||||
export default function MyCert() {
|
||||
const [r, setR] = useState({});
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [err, setErr] = useState('');
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
const d = await getOpCertMine();
|
||||
setR(d || {});
|
||||
} catch (e) { setErr((e && e.message) || '加载失败'); }
|
||||
finally { setLoaded(true); }
|
||||
};
|
||||
useEffect(() => { load(); }, []);
|
||||
|
||||
const status = r.certification_status || '';
|
||||
const cert = r.certification || {};
|
||||
const [txt, cls] = badge(status);
|
||||
const certified = String(status).toLowerCase() === 'certified';
|
||||
const goApply = () => Taro.navigateTo({ url: '/pages/cert-apply/index' });
|
||||
|
||||
return (
|
||||
<View className="page-in">
|
||||
<View className="mk-head">
|
||||
<Text className="mk-title">认证中心</Text>
|
||||
<Text className="mk-sub">你的 OPC 创业伙伴认证进度。</Text>
|
||||
</View>
|
||||
{!isAuthed() && <View className="card"><Text className="muted">请先登录(我的 → 登录绑定)。</Text></View>}
|
||||
{err && <Text className="mk-err">{err}</Text>}
|
||||
{!loaded ? <Text className="muted">加载中…</Text> : (
|
||||
<View className="mk-card">
|
||||
<View className="mk-row">
|
||||
<Text className="mk-name">OPC 创业伙伴认证</Text>
|
||||
<Text className={`mk-phase ${cls}`}>{txt}</Text>
|
||||
</View>
|
||||
{cert.cert_no ? <Text className="mk-meta">证书编号:{cert.cert_no}</Text> : null}
|
||||
{cert.certified_at ? <Text className="mk-meta">认证时间:{cert.certified_at}</Text> : null}
|
||||
<Text className="mk-meta">在平台完成指定指标后即可获得认证,享受政策 / 资源对接权益。</Text>
|
||||
{!certified && <Text className="link" onClick={goApply}>去认证 →</Text>}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
.mk-head { padding: 18px 20px 6px; }
|
||||
.mk-title { font-size: 24px; font-weight: 700; color: #0d2f4e; display: block; }
|
||||
.mk-sub { font-size: 13px; color: #8a8a93; margin-top: 4px; display: block; }
|
||||
.mk-err { color: #e0544c; font-size: 13px; padding: 0 20px; }
|
||||
.mk-card { background: #fff; border-radius: 18px; padding: 18px; margin: 12px 20px; box-shadow: 0 6px 18px -8px rgba(13,47,78,.12); }
|
||||
.mk-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
||||
.mk-name { font-size: 16px; font-weight: 600; color: #12233b; flex: 1; }
|
||||
.mk-phase { flex: 0 0 auto; font-size: 12px; padding: 2px 12px; border-radius: 999px; }
|
||||
.mk-phase.ok { color: #00a06e; background: #e6f8f0; }
|
||||
.mk-phase.pending { color: #e6a400; background: #fff6e2; }
|
||||
.mk-phase.rejected { color: #98a0ab; background: #f2f2f6; }
|
||||
.mk-phase.muted { color: #98a0ab; background: #f2f2f6; }
|
||||
.mk-meta { display: block; font-size: 13px; color: #98a0ab; margin-top: 8px; }
|
||||
@@ -0,0 +1 @@
|
||||
export default { navigationBarTitleText: '我的园区' };
|
||||
@@ -0,0 +1,63 @@
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { isAuthed } from '@/utils/auth';
|
||||
import { getMyAdmission } from '@/utils/parks';
|
||||
import './index.scss';
|
||||
|
||||
function statusText(s) {
|
||||
const k = String(s || '').toLowerCase();
|
||||
if (['approved', 'verified', 'admitted'].includes(k) || s === '已入驻' || s === '通过') return ['已入驻', 'ok'];
|
||||
if (['pending', 'reviewing'].includes(k) || s === '审核中') return ['审核中', 'pending'];
|
||||
if (['rejected'].includes(k) || s === '未通过') return ['未通过', 'rejected'];
|
||||
return [s || '待审核', 'pending'];
|
||||
}
|
||||
|
||||
export default function MyPark() {
|
||||
const [items, setItems] = useState([]);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [err, setErr] = useState('');
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
const r = await getMyAdmission();
|
||||
setItems(Array.isArray(r) ? r : []);
|
||||
} catch (e) { setErr((e && e.message) || '加载失败'); }
|
||||
finally { setLoaded(true); }
|
||||
};
|
||||
useEffect(() => { load(); }, []);
|
||||
|
||||
const goApply = () => Taro.navigateTo({ url: '/pages/park-apply/index' });
|
||||
const ad = items[0] || {};
|
||||
|
||||
return (
|
||||
<View className="page-in">
|
||||
<View className="mk-head">
|
||||
<Text className="mk-title">我的园区</Text>
|
||||
<Text className="mk-sub">查看你的园区入驻申请与所属园区。</Text>
|
||||
</View>
|
||||
{!isAuthed() && <View className="card"><Text className="muted">请先登录(我的 → 登录绑定)。</Text></View>}
|
||||
{err && <Text className="mk-err">{err}</Text>}
|
||||
{!loaded ? <Text className="muted">加载中…</Text>
|
||||
: items.length === 0 ? (
|
||||
<View className="card mk-empty">
|
||||
<Text className="muted">你还没有提交园区入驻申请。</Text>
|
||||
<Text className="link" onClick={goApply}>去申请入驻 →</Text>
|
||||
</View>
|
||||
) : (() => {
|
||||
const [txt, cls] = statusText(ad.status || ad.audit_status);
|
||||
const name = ad.tenant_name || ad.park_name || ad.company_name || ad.company || '昆明市大学生创业园';
|
||||
return (
|
||||
<View className="mk-card">
|
||||
<View className="mk-row">
|
||||
<Text className="mk-name">{name}</Text>
|
||||
<Text className={`mk-phase ${cls}`}>{txt}</Text>
|
||||
</View>
|
||||
{ad.region ? <Text className="mk-meta">区域:{ad.region}</Text> : null}
|
||||
{ad.created_at ? <Text className="mk-meta">申请时间:{ad.created_at}</Text> : null}
|
||||
</View>
|
||||
);
|
||||
})()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
.mk-head { padding: 18px 20px 6px; }
|
||||
.mk-title { font-size: 24px; font-weight: 700; color: #0d2f4e; display: block; }
|
||||
.mk-sub { font-size: 13px; color: #8a8a93; margin-top: 4px; display: block; }
|
||||
.mk-err { color: #e0544c; font-size: 13px; padding: 0 20px; }
|
||||
.mk-card { background: #fff; border-radius: 18px; padding: 18px; margin: 12px 20px; box-shadow: 0 6px 18px -8px rgba(13,47,78,.12); }
|
||||
.mk-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
||||
.mk-name { font-size: 16px; font-weight: 600; color: #12233b; flex: 1; }
|
||||
.mk-phase { flex: 0 0 auto; font-size: 12px; padding: 2px 12px; border-radius: 999px; }
|
||||
.mk-phase.ok { color: #00a06e; background: #e6f8f0; }
|
||||
.mk-phase.pending { color: #e6a400; background: #fff6e2; }
|
||||
.mk-phase.rejected { color: #98a0ab; background: #f2f2f6; }
|
||||
.mk-meta { display: block; font-size: 13px; color: #98a0ab; margin-top: 6px; }
|
||||
.mk-empty { text-align: center; padding: 40px 20px; }
|
||||
@@ -0,0 +1 @@
|
||||
export default { navigationBarTitleText: '我的任务' };
|
||||
@@ -0,0 +1,59 @@
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { isAuthed } from '@/utils/auth';
|
||||
import { getMyTasks } from '@/utils/tasks';
|
||||
import './index.scss';
|
||||
|
||||
const MODE = { grab: '抢单', bid: '竞标', assign: '指派', recommend: '推荐' };
|
||||
const money = (a, b) => (a || b) ? (a && b && a !== b ? `¥${a}–${b}` : `¥${a || b}`) : '面议';
|
||||
const itemsOf = (r) => (r && r.items) || (r && r.list) || (Array.isArray(r) ? r : []);
|
||||
|
||||
export default function MyTasks() {
|
||||
const [items, setItems] = useState([]);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [err, setErr] = useState('');
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
const r = await getMyTasks();
|
||||
setItems(itemsOf(r));
|
||||
} catch (e) {
|
||||
setErr((e && e.message) || '加载失败');
|
||||
} finally { setLoaded(true); }
|
||||
};
|
||||
useEffect(() => { load(); }, []);
|
||||
|
||||
const open = (id) => () => Taro.navigateTo({ url: `/pages/task-detail/index?id=${id}` });
|
||||
const goSquare = () => Taro.navigateTo({ url: '/pages/tasks/index' });
|
||||
|
||||
return (
|
||||
<View className="page-in">
|
||||
<View className="mk-head">
|
||||
<Text className="mk-title">我的任务</Text>
|
||||
<Text className="mk-sub">你承接 / 参与的任务,点击查看详情。</Text>
|
||||
</View>
|
||||
{!isAuthed() && <View className="card"><Text className="muted">请先登录(我的 → 登录绑定)。</Text></View>}
|
||||
{err && <Text className="mk-err">{err}</Text>}
|
||||
{!loaded ? <Text className="muted">加载中…</Text>
|
||||
: items.length === 0 ? (
|
||||
<View className="card mk-empty">
|
||||
<Text className="muted">还没有承接的任务。</Text>
|
||||
<Text className="link" onClick={goSquare}>去任务广场 →</Text>
|
||||
</View>
|
||||
) : items.map((t) => (
|
||||
<View className="mk-card" key={t.id} onClick={open(t.id)}>
|
||||
<View className="mk-row">
|
||||
<Text className="mk-tag">{t.category || '综合'}</Text>
|
||||
<Text className="mk-mode">{MODE[t.mode] || '抢单'}</Text>
|
||||
</View>
|
||||
<Text className="mk-name">{t.title}</Text>
|
||||
<View className="mk-meta">
|
||||
<Text className="mk-price">{money(t.budget_min, t.budget_max)}</Text>
|
||||
{t.deadline ? <Text className="mk-dot">截止 {t.deadline}</Text> : null}
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
.mk-head { padding: 18px 20px 6px; }
|
||||
.mk-title { font-size: 24px; font-weight: 700; color: #0d2f4e; display: block; }
|
||||
.mk-sub { font-size: 13px; color: #8a8a93; margin-top: 4px; display: block; }
|
||||
.mk-err { color: #e0544c; font-size: 13px; padding: 0 20px; }
|
||||
.mk-card { background: #fff; border-radius: 18px; padding: 16px 18px; margin: 10px 20px; box-shadow: 0 6px 18px -8px rgba(13,47,78,.12); }
|
||||
.mk-row { display: flex; gap: 8px; }
|
||||
.mk-tag { background: #eaf6ff; color: #1f8ce0; border-radius: 999px; padding: 2px 12px; font-size: 12px; }
|
||||
.mk-mode { background: #f2f2f6; color: #6a6a73; border-radius: 999px; padding: 2px 12px; font-size: 12px; }
|
||||
.mk-name { display: block; font-size: 16px; font-weight: 600; color: #12233b; margin: 10px 0 6px; }
|
||||
.mk-meta { display: flex; gap: 12px; }
|
||||
.mk-price { color: #06acf1; font-weight: 600; font-size: 14px; }
|
||||
.mk-dot { color: #98a0ab; font-size: 13px; }
|
||||
.mk-empty { text-align: center; padding: 40px 20px; }
|
||||
@@ -13,6 +13,10 @@ function pickUser(d) {
|
||||
avatar: d.avatar || '',
|
||||
phone: d.phone || '',
|
||||
phoneBound: !!d.phoneBound,
|
||||
wxBound: !!d.wxBound,
|
||||
wxMiniBound: !!d.wxMiniBound,
|
||||
account_type_label: d.account_type_label || d.role || '',
|
||||
role: d.role || '',
|
||||
status: d.status || '',
|
||||
topics: Array.isArray(d.topics) ? d.topics : [],
|
||||
source: d.source || ''
|
||||
|
||||
Reference in New Issue
Block a user