c6def26b52
- API 基址支持 TARO_APP_API_BASE 覆盖;头像/入驻资料上传带 dir 业务目录(OSS 路径规范) - 资讯详情:真实点赞(计数)/微信分享(openType=share+useShareAppMessage)/留言;封面 3.35:1 裁切;顶部色条修复 - 发布者徽章(官方/园区/OPC 三款渐变胶囊);头像有则显示无则不留位 - 动作按钮纯图标化并入留言标题行;留言区胶囊输入+渐变发送键
501 lines
22 KiB
React
501 lines
22 KiB
React
import { View, Text, Button, Input, Image } from '@tarojs/components';
|
||
import Taro from '@tarojs/taro';
|
||
import { useState, useEffect } from 'react';
|
||
import { useTabIndex } from '@/utils/tab';
|
||
import { isAuthed, getUser, logout, wxLogin, phoneLogin, bindPhone, sendCode, wxPhoneBind, updateProfile, getMe } from '@/utils/auth';
|
||
import { getMyBookings, checkIn, parseIso, BK_STATUS_OPTIONS, BK_TOPIC_OPTIONS, BK_SOURCE_OPTIONS } from '@/utils/booking';
|
||
import { getMyAdmission } from '@/utils/parks';
|
||
import { API_BASE } from '@/utils/api';
|
||
import Skeleton from '@/components/skeleton';
|
||
import CountdownBox from '@/components/CountdownBox';
|
||
import './index.scss';
|
||
|
||
/* 报名标签:按 auditStatus + event_status 推断 待参加 / 审核中 / 已结束 / 未通过 */
|
||
const PHASE_META = {
|
||
pending: { text: '审核中', cls: 'pending' },
|
||
rejected: { text: '未通过', cls: 'rejected' },
|
||
done: { text: '已结束', cls: 'done' },
|
||
upcoming: { text: '待参加', cls: 'upcoming' }
|
||
};
|
||
const phaseOf = (b) => {
|
||
// 兼容 camelCase(后端新契约) 与 snake_case(旧数据)
|
||
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 PHASE_FILTERS = [
|
||
{ key: 'all', label: '全部' },
|
||
{ key: 'upcoming', label: '待参加' },
|
||
{ key: 'pending', label: '审核中' },
|
||
{ key: 'rejected', label: '未通过' },
|
||
{ key: 'done', label: '已结束' }
|
||
];
|
||
|
||
const fmtTime = (iso) => {
|
||
if (!iso) return '';
|
||
const d = new Date(iso);
|
||
const p = (n) => String(n).padStart(2, '0');
|
||
const week = ['日', '一', '二', '三', '四', '五', '六'][d.getDay()];
|
||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())} 周${week}`;
|
||
};
|
||
|
||
export default function Mine() {
|
||
useTabIndex(4);
|
||
const [user, setUser] = useState(getUser());
|
||
const [bookings, setBookings] = useState([]);
|
||
const [phase, setPhase] = useState('all'); // 报名状态筛选
|
||
const [admission, setAdmission] = useState([]); // 我的园区入驻记录
|
||
const [loaded, setLoaded] = useState(false);
|
||
const [busy, setBusy] = useState(false);
|
||
const [err, setErr] = useState('');
|
||
|
||
/* 绑定手机 */
|
||
const [showBind, setShowBind] = useState(false);
|
||
const [phone, setPhone] = useState('');
|
||
const [code, setCode] = useState('');
|
||
const [cd, setCd] = useState(0);
|
||
const [bindDebug, setBindDebug] = useState('');
|
||
|
||
/* 手机号登录 */
|
||
const [showPhone, setShowPhone] = useState(false);
|
||
const [pPhone, setPPhone] = useState('');
|
||
const [pCode, setPCode] = useState('');
|
||
const [pCd, setPCd] = useState(0);
|
||
const [pDebug, setPDebug] = useState('');
|
||
|
||
const refresh = async () => {
|
||
if (!isAuthed()) return;
|
||
try {
|
||
const me = await getMe();
|
||
if (me.ok && me.user) setUser(getUser());
|
||
} catch { /* ignore */ }
|
||
try {
|
||
const d = await getMyBookings();
|
||
setBookings((d && d.list) || []);
|
||
} catch { /* 保留上次列表,避免重进闪空 */ }
|
||
try {
|
||
const a = await getMyAdmission();
|
||
setAdmission(Array.isArray(a) ? a : []);
|
||
} catch { /* 保留上次 */ }
|
||
finally { setLoaded(true); }
|
||
};
|
||
Taro.useDidShow(() => { refresh(); });
|
||
|
||
/* 微信一键登录 */
|
||
const onWxLogin = async () => {
|
||
setErr('');
|
||
setBusy(true);
|
||
try {
|
||
let nickName = '', avatarUrl = '';
|
||
try {
|
||
const { userInfo } = await Taro.getUserProfile({ desc: '用于完善会员资料' });
|
||
nickName = (userInfo && userInfo.nickName) || '';
|
||
avatarUrl = (userInfo && userInfo.avatarUrl) || '';
|
||
} catch { /* 用户拒绝授权昵称,仍可用 code 登录 */ }
|
||
const { code: loginCode } = await Taro.login();
|
||
const data = await wxLogin({ code: loginCode, nickName, avatarUrl });
|
||
setUser(getUser());
|
||
refresh();
|
||
} catch (e) {
|
||
setErr((e && e.message) || '微信登录失败,请重试');
|
||
} finally {
|
||
setBusy(false);
|
||
}
|
||
};
|
||
|
||
const sendBind = async () => {
|
||
if (!/^1\d{10}$/.test(phone)) { setErr('请输入正确的 11 位手机号'); return; }
|
||
setErr(''); setBusy(true);
|
||
try {
|
||
const r = await sendCode(phone);
|
||
setBindDebug(`验证码已发送${r.debugCode ? ',演示验证码:' + r.debugCode : ''}`);
|
||
setCd(60);
|
||
const t = setInterval(() => setCd((v) => { if (v <= 1) { clearInterval(t); return 0; } return v - 1; }), 1000);
|
||
} catch (e) { setErr((e && e.message) || '发送失败'); }
|
||
finally { setBusy(false); }
|
||
};
|
||
|
||
const doBind = async () => {
|
||
if (!/^1\d{10}$/.test(phone)) { setErr('请输入正确的 11 位手机号'); return; }
|
||
if (!code) { setErr('请输入验证码'); return; }
|
||
setErr(''); setBusy(true);
|
||
try {
|
||
const r = await bindPhone(phone, code);
|
||
if (r.ok) {
|
||
setShowBind(false);
|
||
setPhone(''); setCode(''); setBindDebug('');
|
||
setUser(getUser());
|
||
Taro.showToast({ title: '手机绑定成功', icon: 'success' });
|
||
} else {
|
||
setErr(r.error || '绑定失败');
|
||
}
|
||
} catch (e) { setErr((e && e.message) || '绑定失败'); }
|
||
finally { setBusy(false); }
|
||
};
|
||
|
||
/* 微信官方一键绑定手机号(getPhoneNumber 回调) */
|
||
const onWxPhone = async (e) => {
|
||
const detail = (e.detail && e.detail) || {};
|
||
const pcode = detail.code;
|
||
if (!pcode) { Taro.showToast({ title: '已取消授权', icon: 'none' }); return; }
|
||
setBusy(true); setErr('');
|
||
try {
|
||
const r = await wxPhoneBind(pcode);
|
||
if (r.ok) {
|
||
setShowBind(false); setUser(getUser());
|
||
Taro.showToast({ title: '手机绑定成功', icon: 'success' });
|
||
} else {
|
||
Taro.showToast({ title: r.error || '绑定失败', icon: 'none' });
|
||
}
|
||
} catch (ex) { Taro.showToast({ title: (ex && ex.message) || '绑定失败', icon: 'none' }); }
|
||
finally { setBusy(false); }
|
||
};
|
||
|
||
const sendPhone = async () => {
|
||
if (!/^1\d{10}$/.test(pPhone)) { setErr('请输入正确的 11 位手机号'); return; }
|
||
setErr(''); setBusy(true);
|
||
try {
|
||
const r = await sendCode(pPhone);
|
||
setPDebug(`验证码已发送${r.debugCode ? ',演示验证码:' + r.debugCode : ''}`);
|
||
setPCd(60);
|
||
const t = setInterval(() => setPCd((v) => { if (v <= 1) { clearInterval(t); return 0; } return v - 1; }), 1000);
|
||
} catch (e) { setErr((e && e.message) || '发送失败'); }
|
||
finally { setBusy(false); }
|
||
};
|
||
|
||
const doPhoneLogin = async () => {
|
||
if (!/^1\d{10}$/.test(pPhone)) { setErr('请输入正确的 11 位手机号'); return; }
|
||
if (!pCode) { setErr('请输入验证码'); return; }
|
||
setErr(''); setBusy(true);
|
||
try {
|
||
await phoneLogin(pPhone, pCode);
|
||
setUser(getUser());
|
||
setShowPhone(false);
|
||
refresh();
|
||
} catch (e) { setErr((e && e.message) || '登录失败'); }
|
||
finally { setBusy(false); }
|
||
};
|
||
|
||
const onCheckin = async (id) => {
|
||
setErr('');
|
||
try {
|
||
const r = await checkIn(id);
|
||
if (r.ok) {
|
||
Taro.showToast({ title: '签到成功', icon: 'success' });
|
||
refresh();
|
||
} else {
|
||
Taro.showToast({ title: r.error || '签到失败', icon: 'none' });
|
||
}
|
||
} catch (e) {
|
||
Taro.showToast({ title: (e && e.message) || '签到失败', icon: 'none' });
|
||
}
|
||
};
|
||
|
||
/* 微信头像昵称填写能力:选头像→上传;填昵称→保存。确保登录后能渲染真实头像/昵称 */
|
||
const [nickDraft, setNickDraft] = useState(() => (getUser() || {}).name || '');
|
||
|
||
/* 登录/登出/刷新用户后,昵称草稿随 user.name 同步,避免登出残留或登录后不显示昵称 */
|
||
useEffect(() => { setNickDraft((user && user.name) || ''); }, [user]);
|
||
|
||
/* 报名资料(姓名/状态/主题/来源)——个人中心设置,报名自动带入 */
|
||
const [pName, setPName] = useState((user && user.name) || '');
|
||
const [pStatus, setPStatus] = useState((user && user.status) || '');
|
||
const [pTopics, setPTopics] = useState((user && user.topics) || []);
|
||
const [pSource, setPSource] = useState((user && user.source) || '');
|
||
const [saving, setSaving] = useState(false);
|
||
useEffect(() => {
|
||
setPName((user && user.name) || '');
|
||
setPStatus((user && user.status) || '');
|
||
setPTopics((user && user.topics) || []);
|
||
setPSource((user && user.source) || '');
|
||
}, [user]);
|
||
|
||
const savePrefs = async () => {
|
||
setErr('');
|
||
if (!pName.trim()) { setErr('请输入姓名'); return; }
|
||
setSaving(true);
|
||
try {
|
||
const r = await updateProfile({ name: pName.trim(), status: pStatus, topics: pTopics, source: pSource });
|
||
if (r.ok) { setUser(getUser()); Taro.showToast({ title: '已保存', icon: 'success' }); }
|
||
else { setErr(r.error || '保存失败'); }
|
||
} catch (e) { setErr((e && e.message) || '保存失败'); }
|
||
finally { setSaving(false); }
|
||
};
|
||
|
||
const onChooseAvatar = async (e) => {
|
||
const tempPath = e.detail && e.detail.avatarUrl;
|
||
if (!tempPath) return;
|
||
try {
|
||
const token = Taro.getStorageSync('pine_token') || '';
|
||
const up = await Taro.uploadFile({
|
||
url: `${API_BASE}/api/upload`,
|
||
filePath: tempPath,
|
||
name: 'file',
|
||
formData: { dir: 'avatar' },
|
||
header: { Authorization: `Bearer ${token}` }
|
||
});
|
||
const data = JSON.parse(up.data || '{}');
|
||
if (!data.ok) throw new Error(data.error || '上传失败');
|
||
await updateProfile({ avatar: data.url });
|
||
setUser(getUser());
|
||
Taro.showToast({ title: '头像已更新', icon: 'success' });
|
||
} catch (ex) {
|
||
Taro.showToast({ title: (ex && ex.message) || '头像更新失败', icon: 'none' });
|
||
}
|
||
};
|
||
|
||
const onSaveNick = async () => {
|
||
const v = (nickDraft || '').trim();
|
||
if (!v) { setNickDraft((getUser() || {}).name || ''); return; }
|
||
try {
|
||
await updateProfile({ name: v });
|
||
setUser(getUser());
|
||
Taro.showToast({ title: '昵称已更新', icon: 'success' });
|
||
} catch (ex) {
|
||
Taro.showToast({ title: (ex && ex.message) || '昵称更新失败', icon: 'none' });
|
||
}
|
||
};
|
||
|
||
const onLogout = () => {
|
||
logout();
|
||
setUser(null);
|
||
setBookings([]);
|
||
setShowBind(false); setShowPhone(false);
|
||
setPhone(''); setCode(''); setBindDebug('');
|
||
setErr('');
|
||
Taro.showToast({ title: '已退出登录', icon: 'none' });
|
||
};
|
||
|
||
/* ============ 未登录:登录入口 ============ */
|
||
if (!user || !isAuthed()) {
|
||
return (
|
||
<View className="mine page-in">
|
||
<View className="mine-head">
|
||
<Text className="mine-no">MY</Text>
|
||
<Text className="mine-title">我的</Text>
|
||
<Text className="mine-en">Account</Text>
|
||
</View>
|
||
<View className="mine-card login-card">
|
||
<Button className="btn-main wx-btn" loading={busy} onClick={onWxLogin}>微信一键登录</Button>
|
||
<Text className="muted mine-tip">登录后可绑定手机、查看报名场次与活动签到</Text>
|
||
<Text className="link" onClick={() => setShowPhone(!showPhone)}>{showPhone ? '收起手机号登录' : '或用手机号登录'}</Text>
|
||
</View>
|
||
{showPhone && (
|
||
<View className="card">
|
||
<View className="field">
|
||
<Text className="label">手机号</Text>
|
||
<Input value={pPhone} onInput={(e) => setPPhone(e.detail.value)} type="number" maxlength={11} placeholder="11 位手机号" className="input" />
|
||
</View>
|
||
<View className="field">
|
||
<Text className="label">验证码</Text>
|
||
<View className="row">
|
||
<Input value={pCode} onInput={(e) => setPCode(e.detail.value)} type="number" maxlength={6} placeholder="6 位验证码" className="input flex" />
|
||
<Button className="btn-ghost code-btn" disabled={pCd > 0} onClick={sendPhone}>{pCd > 0 ? `${pCd}s` : '获取验证码'}</Button>
|
||
</View>
|
||
</View>
|
||
{pDebug && <Text className="ok-text">{pDebug}</Text>}
|
||
<Button className="btn-main submit" loading={busy} onClick={doPhoneLogin}>{busy ? '登录中…' : '手机号登录'}</Button>
|
||
</View>
|
||
)}
|
||
{err && <Text className="err-text">{err}</Text>}
|
||
<View className="mk-foot">
|
||
<Text className="mk-foot-line">云南省首个由政府主办的免费大学生创业服务平台</Text>
|
||
<Text className="mk-foot-line">云南派音人工智能科技有限公司 && 云南汇浙青创科技有限公司</Text>
|
||
<Text className="mk-foot-line">像素字体 BubbledotICG-FinePos · CC BY 4.0</Text>
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
/* ============ 已登录:资料 + 我的报名 ============ */
|
||
return (
|
||
<View className="mine page-in">
|
||
<View className="mine-head">
|
||
<Text className="mine-no">MY</Text>
|
||
<Text className="mine-title">我的</Text>
|
||
<Text className="mine-en">Account</Text>
|
||
</View>
|
||
|
||
{/* 资料卡 */}
|
||
<View className="mine-card profile">
|
||
<Button className="avatar-btn" openType="chooseAvatar" onChooseAvatar={onChooseAvatar}>
|
||
{user.avatar ? (
|
||
<Image className="avatar" src={user.avatar} mode="aspectFill" />
|
||
) : (
|
||
<View className="avatar placeholder">
|
||
<Text>{(user.name || '我').slice(0, 1)}</Text>
|
||
</View>
|
||
)}
|
||
</Button>
|
||
<View className="profile-info">
|
||
<Input className="profile-name" type="nickname" value={nickDraft} onInput={(e) => setNickDraft(e.detail.value)} placeholder="点此填写昵称" />
|
||
<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>
|
||
)}
|
||
</View>
|
||
<Button className="btn-ghost logout-btn" onClick={onLogout}>退出</Button>
|
||
</View>
|
||
|
||
{/* 下一场活动倒计时(个人信息卡之后显示;取最早的待参加且未开始的报名场次) */}
|
||
{(() => {
|
||
const now = Date.now();
|
||
const upcoming = bookings
|
||
.filter((b) => b.event_status !== 'done' && !b.checkinAt && parseIso(b.eventStart) && parseIso(b.eventStart).getTime() > now)
|
||
.sort((a, b) => parseIso(a.eventStart).getTime() - parseIso(b.eventStart).getTime());
|
||
const nxt = upcoming[0];
|
||
if (!nxt) return null;
|
||
return (
|
||
<View className="card next-card">
|
||
<Text className="next-lbl">您参加的下一场活动</Text>
|
||
<Text className="next-title">{nxt.eventTitle}</Text>
|
||
{nxt.eventStart && <Text className="next-time">{fmtTime(nxt.eventStart)} · 敬请准时到场</Text>}
|
||
<CountdownBox target={nxt.eventStart} />
|
||
</View>
|
||
);
|
||
})()}
|
||
|
||
{/* 绑定手机 */}
|
||
{!user.phoneBound && (
|
||
<View className="card bind-card">
|
||
<View className="bind-head">
|
||
<Text className="bind-title">绑定手机</Text>
|
||
</View>
|
||
<Text className="bind-tip">绑定后用于接收活动通知与报名确认</Text>
|
||
{!showBind ? (
|
||
<Button className="btn-main" onClick={() => setShowBind(true)}>去绑定手机</Button>
|
||
) : (
|
||
<View className="bind-form">
|
||
<Button className="btn-main" openType="getPhoneNumber" onGetPhoneNumber={onWxPhone} loading={busy}>微信一键绑定手机号</Button>
|
||
<Text className="divider">或</Text>
|
||
<View className="field">
|
||
<Text className="label">手机号</Text>
|
||
<Input value={phone} onInput={(e) => setPhone(e.detail.value)} type="number" maxlength={11} placeholder="11 位手机号" className="input" />
|
||
</View>
|
||
<View className="field">
|
||
<Text className="label">验证码</Text>
|
||
<View className="row">
|
||
<Input value={code} onInput={(e) => setCode(e.detail.value)} type="number" maxlength={6} placeholder="6 位验证码" className="input flex" />
|
||
<Button className="btn-ghost code-btn" disabled={cd > 0} onClick={sendBind}>{cd > 0 ? `${cd}s` : '获取验证码'}</Button>
|
||
</View>
|
||
</View>
|
||
{bindDebug && <Text className="ok-text">{bindDebug}</Text>}
|
||
<Button className="btn-main submit" loading={busy} onClick={doBind}>{busy ? '提交中…' : '确认绑定'}</Button>
|
||
<Button className="btn-ghost bind-cancel" onClick={() => setShowBind(false)}>取消</Button>
|
||
</View>
|
||
)}
|
||
</View>
|
||
)}
|
||
|
||
{/* 我的报名 */}
|
||
<View className="mine-section-head">
|
||
<Text className="mine-no">BOOK</Text>
|
||
<Text className="mine-subtitle">我的报名({bookings.length})</Text>
|
||
</View>
|
||
<View className="phase-filters">
|
||
{PHASE_FILTERS.map((f) => (
|
||
<Text key={f.key} className={`phase-tab${phase === f.key ? ' on' : ''}`} onClick={() => setPhase(f.key)}>{f.label}</Text>
|
||
))}
|
||
</View>
|
||
{(() => {
|
||
const shown = phase === 'all' ? bookings : bookings.filter((b) => phaseOf(b).cls === phase);
|
||
if (!loaded) return <View className="card empty"><Skeleton.Rows n={3} gap={14} /></View>;
|
||
if (shown.length === 0) return (
|
||
<View className="card empty">
|
||
<Text className="muted">{bookings.length === 0 ? '还没有报名记录。' : '该状态下暂无报名。'}</Text>
|
||
<Text className="link" onClick={() => Taro.switchTab({ url: '/pages/booking/index' })}>去看活动 →</Text>
|
||
</View>
|
||
);
|
||
return (
|
||
<View className="bk-list">
|
||
{shown.map((b) => {
|
||
const p = phaseOf(b);
|
||
const ended = b.event_status === 'done';
|
||
const ready = b.can_checkin;
|
||
return (
|
||
<View className="bk-card" key={b.id}>
|
||
<View className="bk-head">
|
||
<Text className="bk-want">{b.eventTitle || b.want || '报名'}</Text>
|
||
<Text className={`bk-phase ${p.cls}`}>{p.text}</Text>
|
||
</View>
|
||
{b.eventStart && <Text className="bk-time">场次时间:{fmtTime(b.eventStart)}</Text>}
|
||
<Text className="bk-time">报名时间:{fmtTime(b.createdAt)}</Text>
|
||
<Text className="bk-extra">编号 {b.id} · {b.name || '—'}</Text>
|
||
{b.checkinAt ? (
|
||
<Text className="bk-checked">已于 {fmtTime(b.checkinAt)} 签到</Text>
|
||
) : ended ? (
|
||
<Text className="bk-checked done">本场已结束</Text>
|
||
) : ready ? (
|
||
<Button className="btn-main bk-checkin" onClick={() => onCheckin(b.id)}>活动签到</Button>
|
||
) : (
|
||
<Text className="bk-checked">未到签到时间,活动开始后即可签到</Text>
|
||
)}
|
||
</View>
|
||
);
|
||
})}
|
||
</View>
|
||
);
|
||
})()}
|
||
|
||
{/* 我的接单:扫码接单;任务广场移到 Web / 桌面端 */}
|
||
<View className="mine-section-head">
|
||
<Text className="mine-no">ACCEPT</Text>
|
||
<Text className="mine-subtitle">我的接单</Text>
|
||
</View>
|
||
<View className="card mk-act">
|
||
<Button className="btn-main mk-btn-sm" onClick={() => Taro.navigateTo({ url: '/pages/scan/index' })}>扫码接单</Button>
|
||
<Text className="muted mk-center-hint">任务广场请到 Web 端或桌面端查看</Text>
|
||
</View>
|
||
|
||
{/* 我的园区:未入驻 → 申请入驻 + OPC认证;已入驻 → 园区信息 + OPC认证 + 转园 */}
|
||
<View className="mine-section-head">
|
||
<Text className="mine-no">PARK</Text>
|
||
<Text className="mine-subtitle">我的园区</Text>
|
||
</View>
|
||
{(() => {
|
||
const ad = admission[0] || {};
|
||
const st = String(ad.status || ad.audit_status || '').toLowerCase();
|
||
const admitted = ['approved', 'verified', 'admitted'].includes(st) || ['已入驻', '通过', 'approved'].includes(ad.status);
|
||
if (!admitted) {
|
||
return (
|
||
<View className="card mk-actions">
|
||
<Text className="muted mk-center-hint">暂未入驻园区,可申请入驻并完成 OPC 认证。</Text>
|
||
<View className="mk-btnrow">
|
||
<Button className="btn-main mk-btn-sm" onClick={() => Taro.navigateTo({ url: '/pages/park-apply/index' })}>申请入驻</Button>
|
||
<Button className="btn-ghost mk-btn-sm" onClick={() => Taro.navigateTo({ url: '/pages/cert-apply/index' })}>OPC 认证</Button>
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|
||
const name = ad.tenant_name || ad.park_name || ad.company_name || ad.company || '已入驻园区';
|
||
return (
|
||
<View className="card mk-park">
|
||
<View className="mk-park-name">{name}</View>
|
||
{ad.region ? <Text className="mk-hint">区域:{ad.region}</Text> : null}
|
||
<View className="mk-btnrow">
|
||
<Button className="btn-ghost mk-btn-sm" onClick={() => Taro.navigateTo({ url: '/pages/cert-apply/index' })}>OPC 认证</Button>
|
||
<Button className="btn-ghost mk-btn-sm" onClick={() => Taro.navigateTo({ url: '/pages/park-transfer/index' })}>转园申请</Button>
|
||
</View>
|
||
</View>
|
||
);
|
||
})()}
|
||
|
||
{err && <Text className="err-text">{err}</Text>}
|
||
<View className="mk-foot">
|
||
<Text className="mk-foot-line">云南省首个由政府主办的免费大学生创业服务平台</Text>
|
||
<Text className="mk-foot-line">云南派音人工智能科技有限公司 · 云南汇浙青创科技有限公司</Text>
|
||
<Text className="mk-foot-line">像素字体 BubbledotICG-FinePos · CC BY 4.0</Text>
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|