From 35e87889dbd91c4ab5301578e9c0b3945b26c381 Mon Sep 17 00:00:00 2001 From: Pine Date: Wed, 26 Aug 2026 19:37:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(c=E7=AB=AF):=20=E8=A1=A5=E9=BD=90=20OPC?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E7=94=B3=E8=AF=B7=20+=20=E8=BD=AC=E5=9B=AD?= =?UTF-8?q?=E7=94=B3=E8=AF=B7=E5=8F=91=E8=B5=B7=E9=A1=B5(=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F+=E7=BD=91=E7=AB=99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 小程序:新增 pages/cert-apply(OPC认证)、pages/park-transfer(转园);utils/profile.js 封装 /opc/certification/apply|mine、/opc/park-transfer/apply|mine;app.config 注册;首页/我的加入口 - 网站:新增 services/opcProfile.js、user/CertApply.jsx、user/ParkTransfer.jsx;App.jsx 路由 + Home.jsx 申请区入口 - 登录门复用 auth(phoneLogin/bindPhone/isAuthed);重复提交/已认证前端禁用;build:weapp + website build 通过 - 至此 申请入驻/OPC认证/转园 三条流:C端发起 → 园区端|平台端审核 → 回填用户 全链路打通 Co-Authored-By: Claude --- miniprogram/src/app.config.js | 2 + .../src/pages/cert-apply/index.config.js | 1 + miniprogram/src/pages/cert-apply/index.jsx | 184 ++++++++++++++++++ miniprogram/src/pages/cert-apply/index.scss | 35 ++++ miniprogram/src/pages/index/index.jsx | 2 + miniprogram/src/pages/mine/index.jsx | 18 ++ .../src/pages/park-transfer/index.config.js | 1 + miniprogram/src/pages/park-transfer/index.jsx | 133 +++++++++++++ .../src/pages/park-transfer/index.scss | 37 ++++ miniprogram/src/utils/profile.js | 27 +++ website/src/App.jsx | 4 + website/src/services/opcProfile.js | 47 +++++ website/src/user/CertApply.jsx | 120 ++++++++++++ website/src/user/Home.jsx | 30 +++ website/src/user/ParkTransfer.jsx | 94 +++++++++ 15 files changed, 735 insertions(+) create mode 100644 miniprogram/src/pages/cert-apply/index.config.js create mode 100644 miniprogram/src/pages/cert-apply/index.jsx create mode 100644 miniprogram/src/pages/cert-apply/index.scss create mode 100644 miniprogram/src/pages/park-transfer/index.config.js create mode 100644 miniprogram/src/pages/park-transfer/index.jsx create mode 100644 miniprogram/src/pages/park-transfer/index.scss create mode 100644 miniprogram/src/utils/profile.js create mode 100644 website/src/services/opcProfile.js create mode 100644 website/src/user/CertApply.jsx create mode 100644 website/src/user/ParkTransfer.jsx diff --git a/miniprogram/src/app.config.js b/miniprogram/src/app.config.js index 1d6c4b1..9af9480 100644 --- a/miniprogram/src/app.config.js +++ b/miniprogram/src/app.config.js @@ -10,6 +10,8 @@ export default { 'pages/event-detail/index', 'pages/scan/index', 'pages/park-apply/index', + 'pages/cert-apply/index', + 'pages/park-transfer/index', 'pages/scan-login/index' ], window: { diff --git a/miniprogram/src/pages/cert-apply/index.config.js b/miniprogram/src/pages/cert-apply/index.config.js new file mode 100644 index 0000000..7590d70 --- /dev/null +++ b/miniprogram/src/pages/cert-apply/index.config.js @@ -0,0 +1 @@ +export default { navigationBarTitleText: 'OPC 认证' }; diff --git a/miniprogram/src/pages/cert-apply/index.jsx b/miniprogram/src/pages/cert-apply/index.jsx new file mode 100644 index 0000000..a3f9880 --- /dev/null +++ b/miniprogram/src/pages/cert-apply/index.jsx @@ -0,0 +1,184 @@ +import { View, Text, Input, Textarea, Button, Radio, RadioGroup } from '@tarojs/components'; +import Taro from '@tarojs/taro'; +import { useState, useEffect } from 'react'; +import { phoneLogin, bindPhone, isAuthed } from '@/utils/auth'; +import { submitOpCert, getOpCertMine } from '@/utils/profile'; +import { uploadParkDoc } from '@/utils/parks'; +import './index.scss'; + +const EMPTY = () => ({ real_name: '', gender: '', address: '', industry: '', ability: '', phone: '' }); +const GENDER_OPTS = ['男', '女']; + +/** OPC 认证申请(C端):提交认证资料 → 平台运营方审核。 */ +export default function CertApply() { + const [authed, setAuthed] = useState(isAuthed()); + const [gatePhone, setGatePhone] = useState(''); + const [gateCode, setGateCode] = useState(''); + const [gateErr, setGateErr] = useState(''); + const [busy, setBusy] = useState(false); + + const [form, setForm] = useState(EMPTY()); + const [docs, setDocs] = useState({}); + const [mine, setMine] = useState(null); // {certification_status, certification} + const [err, setErr] = useState(''); + const [done, setDone] = useState(false); + const [loaded, setLoaded] = useState(false); + + const up = (k, v) => setForm((f) => ({ ...f, [k]: v })); + + const reloadMine = async () => { + try { + const m = await getOpCertMine(); + setMine(m); + } catch { /* ignore */ } + finally { setLoaded(true); } + }; + useEffect(() => { if (isAuthed()) reloadMine(); else setLoaded(true); }, []); + + const gateSmsLogin = async () => { + setGateErr(''); + if (!/^1\d{10}$/.test(gatePhone)) { setGateErr('请输入正确的 11 位手机号'); return; } + if (!gateCode) { setGateErr('请输入验证码'); return; } + setBusy(true); + try { + if (authed) { + const b = await bindPhone(gatePhone, gateCode); + if (!b.ok) { setGateErr(b.error || '绑定失败'); return; } + } else { + await phoneLogin(gatePhone, gateCode); + } + setAuthed(true); + reloadMine(); + } catch (e) { setGateErr((e && e.message) || '登录失败'); } + finally { setBusy(false); } + }; + + const pickAndUpload = async (key) => { + let filePath = null; + try { + const res = await Taro.chooseMessageFile({ count: 1, type: 'file' }); + filePath = res.tempFiles && res.tempFiles[0] && res.tempFiles[0].path; + } catch (e) { + try { + const img = await Taro.chooseImage({ count: 1, sizeType: ['compressed'] }); + filePath = img.tempFilePaths && img.tempFilePaths[0]; + } catch (_) { return; } + } + if (!filePath) return; + setBusy(true); setErr(''); + try { + const url = await uploadParkDoc(filePath); + setDocs((d) => ({ ...d, [key]: url })); + } catch (e) { setErr((e && e.message) || '文件上传失败'); } + finally { setBusy(false); } + }; + + const statusText = (s) => ({ uncertified: '未认证', pending: '审核中', reviewing: '审核中', certified: '已认证', rejected: '未通过' }[s] || s || '未认证'); + + const submit = async () => { + setErr(''); + if (!form.real_name || !form.phone) { setErr('请填写真实姓名与联系电话'); return; } + const payload = { ...form, docs_json: JSON.stringify(docs) }; + setBusy(true); + try { + await submitOpCert(payload); + setDone(true); + await reloadMine(); + } catch (e) { setErr((e && e.message) || '提交失败,请稍后重试'); } + finally { setBusy(false); } + }; + + if (done) { + return ( + + + 认证申请已提交 + 平台将对您的资料进行审核,请留意认证状态。 + + + ); + } + + if (!loaded) return 加载中…; + + // 未登录/未绑手机 → 快捷门 + if (!authed) { + return ( + + + OPC 认证申请 + 请先用手机号登录(验证码 123456) + setGatePhone(e.detail.value)} /> + setGateCode(e.detail.value)} /> + {gateErr && {gateErr}} + + + + ); + } + + const st = mine?.certification_status || 'uncertified'; + const disabled = st === 'certified' || st === 'pending' || st === 'reviewing'; + + return ( + + + OPC 认证申请 + 当前认证状态:{statusText(st)} + + + + 一 · 基本信息 + up('real_name', v)} /> + 性别 + up('gender', e.detail.value)}> + {GENDER_OPTS.map((g) => {g})} + + + up('address', v)} /> + up('industry', v)} /> + up('phone', v)} /> + + + + 二 · 能力与成果 + 专业技能 / 代表作品 / 成果 +