From 0b264ee9b326f0ee8aa7694f0f23df99b97351af Mon Sep 17 00:00:00 2001 From: Pine Date: Wed, 26 Aug 2026 12:00:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(admission):=20=E5=9B=AD=E5=8C=BA=E5=85=A5?= =?UTF-8?q?=E9=A9=BB=E7=94=B3=E8=AF=B7=20=E2=80=94=E2=80=94=20=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E9=A6=96=E9=A1=B5=E6=8C=89=E9=92=AE+?= =?UTF-8?q?=E8=A1=A8=E5=8D=95=20+=20=E7=BD=91=E7=AB=99=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 小程序: 首页首屏Hero(预约/测评后)加「申请入驻」; pages/park-apply 对照《入园申请表》全字段分区表单+资料上传; utils/parks - 网站: #/park-apply 7步入驻wizard(选园区→申请人→企业→项目→需求→上传→承诺提交); services/park; 首页入口 - 修复循环请求(getParks入useEffect一次); 输入框样式对齐event-detail --- miniprogram/src/app.config.js | 4 +- miniprogram/src/pages/index/index.jsx | 1 + .../src/pages/park-apply/index.config.js | 1 + miniprogram/src/pages/park-apply/index.jsx | 248 ++++++++++++++++++ miniprogram/src/pages/park-apply/index.scss | 58 ++++ miniprogram/src/utils/parks.js | 55 ++++ website/src/App.jsx | 2 + website/src/services/park.js | 63 +++++ website/src/styles/park.css | 30 +++ website/src/user/Home.jsx | 15 ++ website/src/user/ParkApply.jsx | 224 ++++++++++++++++ 11 files changed, 700 insertions(+), 1 deletion(-) create mode 100644 miniprogram/src/pages/park-apply/index.config.js create mode 100644 miniprogram/src/pages/park-apply/index.jsx create mode 100644 miniprogram/src/pages/park-apply/index.scss create mode 100644 miniprogram/src/utils/parks.js create mode 100644 website/src/services/park.js create mode 100644 website/src/styles/park.css create mode 100644 website/src/user/ParkApply.jsx diff --git a/miniprogram/src/app.config.js b/miniprogram/src/app.config.js index fd49e56..1d6c4b1 100644 --- a/miniprogram/src/app.config.js +++ b/miniprogram/src/app.config.js @@ -8,7 +8,9 @@ export default { 'pages/survey/index', 'pages/mine/index', 'pages/event-detail/index', - 'pages/scan/index' + 'pages/scan/index', + 'pages/park-apply/index', + 'pages/scan-login/index' ], window: { backgroundTextStyle: 'light', diff --git a/miniprogram/src/pages/index/index.jsx b/miniprogram/src/pages/index/index.jsx index 6dfe079..3698d0d 100644 --- a/miniprogram/src/pages/index/index.jsx +++ b/miniprogram/src/pages/index/index.jsx @@ -25,6 +25,7 @@ export default function Index() { + diff --git a/miniprogram/src/pages/park-apply/index.config.js b/miniprogram/src/pages/park-apply/index.config.js new file mode 100644 index 0000000..c2bc9c0 --- /dev/null +++ b/miniprogram/src/pages/park-apply/index.config.js @@ -0,0 +1 @@ +export default { navigationBarTitleText: '园区入驻申请' }; diff --git a/miniprogram/src/pages/park-apply/index.jsx b/miniprogram/src/pages/park-apply/index.jsx new file mode 100644 index 0000000..0adda17 --- /dev/null +++ b/miniprogram/src/pages/park-apply/index.jsx @@ -0,0 +1,248 @@ +import { View, Text, Input, Textarea, Button, Radio, RadioGroup, Checkbox, CheckboxGroup, Picker } from '@tarojs/components'; +import Taro from '@tarojs/taro'; +import { useState, useEffect } from 'react'; +import { phoneLogin, bindPhone, isAuthed, getUser } from '@/utils/auth'; +import { + getParks, submitParkApplication, + GENDER_OPTS, COMPANY_TYPE_OPTS, REGION_OPTS, SERVICE_OPTS, DOC_ITEMS, uploadParkDoc, +} from '@/utils/parks'; +import './index.scss'; + +const EMPTY = () => ({ + name: '', birth: '', gender: '', grad_school_major: '', ethnicity: '', + grad_time: '', id_card: '', contact_phone: '', + company_name: '', legal_person: '', legal_phone: '', registered_capital: '', + company_type: '', industry: '', honors: '', + emp_total: '', emp_grad: '', emp_layoff: '', emp_veteran: '', emp_migrant: '', + project_overview: '', + region: '', lease_period: '', services: [], agree: false, +}); + +export default function ParkApply() { + const [authed, setAuthed] = useState(isAuthed()); + const [gatePhone, setGatePhone] = useState(''); + const [gateCode, setGateCode] = useState(''); + const [gateErr, setGateErr] = useState(''); + const [busy, setBusy] = useState(false); + + const [parks, setParks] = useState([]); + const [tenantId, setTenantId] = useState(''); + const [form, setForm] = useState(EMPTY()); + const [docs, setDocs] = useState({}); + const [err, setErr] = useState(''); + const [done, setDone] = useState(false); + const [loading, setLoading] = useState(true); + + const up = (k, v) => setForm((f) => ({ ...f, [k]: v })); + + // 加载园区列表(仅挂载时一次;每次渲染都请求会无限循环) + useEffect(() => { + let alive = true; + (async () => { + try { + const t = await getParks(); + if (!alive) return; + setParks(t); + if (t[0]) setTenantId(t[0].id); + } catch (e) {} + finally { if (alive) setLoading(false); } + })(); + return () => { alive = false; }; + }, []); + + 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); + } catch (e) { setGateErr((e && e.message) || '登录失败'); } + finally { setBusy(false); } + }; + + // 选择并上传一个资料文件(图片/pdf/doc),存 url + 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 submit = async () => { + setErr(''); + if (!tenantId) { setErr('请选择意向园区'); return; } + if (!form.name || !form.contact_phone || !form.id_card) { setErr('请完整填写申请人信息(姓名/电话/身份证)'); return; } + if (!form.agree) { setErr('请勾选企业承诺'); return; } + const req = DOC_ITEMS.filter((d) => d.required); + if (req.some((d) => !docs[d.key])) { setErr('请上传必填资料(创业计划书 / 身份证 / 学历证明)'); return; } + setBusy(true); + try { + const r = await submitParkApplication({ tenant_id: tenantId, form, docs }); + setDone(!!r?.id || r?.ok); + setErr(''); + } catch (e) { setErr((e && e.message) || '提交失败,请稍后重试'); } + finally { setBusy(false); } + }; + + if (done) { + return ( + + + 申请已提交 + 园区将在收到材料后尽快审核。 + + + ); + } + + if (loading) return 加载中…; + + // 未登录/未绑手机 → 快捷门 + if (!authed) { + return ( + + + 园区入驻申请 + 请先用手机号登录(验证码 123456) + setGatePhone(e.detail.value)} /> + setGateCode(e.detail.value)} /> + {gateErr && {gateErr}} + + + + ); + } + + return ( + + + 园区入驻申请表 + 对照《入园申请表》逐项填写,附带的各项资料请上传 + + + + 一 · 选择意向园区 + p.name)} value={Math.max(0, parks.findIndex((p) => p.id === tenantId))} + onChange={(e) => setTenantId(parks[Number(e.detail.value)]?.id || '')}> + {parks.find((p) => p.id === tenantId)?.name || '请选择园区'} ▾ + + up('gender', GENDER_OPTS[Number(e.detail.value)])}> + 性别:{form.gender || '请选择'} ▾ + + + + + 一 · 申请人信息 + up('name', v)} /> + up('birth', v)} /> + 性别 + up('gender', e.detail.value)}> + {GENDER_OPTS.map((g) => {g})} + + + up('ethnicity', v)} /> + up('grad_school_major', v)} /> + up('grad_time', v)} /> + up('id_card', v)} /> + up('contact_phone', v)} /> + + + + 二 · 企业信息 + up('company_name', v)} /> + up('legal_person', v)} /> + up('legal_phone', v)} /> + up('registered_capital', v)} /> + 企业类型 + up('company_type', e.detail.value[0] || '')}> + {COMPANY_TYPE_OPTS.map((t) => {t})} + + + up('industry', v)} /> + up('honors', v)} /> + 企业人员情况 + + up('emp_total', v)} /> + up('emp_grad', v)} /> + up('emp_layoff', v)} /> + up('emp_veteran', v)} /> + up('emp_migrant', v)} /> + + + + + 三 · 项目概况 + 项目概况 / 商业模式 * +