| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477 |
- <template>
- <div class="zfb-step-page">
- <header class="step-header">
- <el-steps :active="zfbStepActive" align-center finish-status="process" class="zfb-steps">
- <el-step title="第一步 完善商家信息" />
- <el-step title="第二步 完善结算信息" />
- <el-step title="第三步 提交审核" />
- </el-steps>
- </header>
- <div class="page-layout">
- <div class="main-column">
- <el-form
- ref="formRef"
- class="zfb-form"
- :model="form"
- :rules="rules"
- label-width="200px"
- require-asterisk-position="left"
- label-position="right"
- >
- <!-- 主体信息 -->
- <section id="zfb-subject" class="form-block anchor-section">
- <h2 class="section-head">
- <span class="section-bar" aria-hidden="true" />
- 主体信息
- </h2>
- <el-form-item label="主体类型" prop="subjectType">
- <el-radio-group v-model="form.subjectType" class="radio-stack" @change="onSubjectTypeChange">
- <el-radio v-for="opt in subjectTypeOptions" :key="opt.value" :value="opt.value">
- {{ opt.label }}
- </el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item prop="licenseUrl" v-if="form.subjectType === '01'">
- <template #label>
- <span class="label-with-icon"> 营业执照 </span>
- </template>
- <div class="license-upload-wrap">
- <el-upload
- v-model:file-list="form.licenseFileList"
- list-type="picture-card"
- :limit="1"
- accept=".jpg,.jpeg,.png,.bmp,.gif,image/jpeg,image/png,image/bmp,image/gif"
- :before-upload="beforeImageUpload"
- :http-request="opt => handleSingleUpload(opt, 'license')"
- :on-remove="(_f, fl) => onSingleRemove(fl, 'license')"
- >
- <el-icon><Plus /></el-icon>
- </el-upload>
- </div>
- <div class="upload-rules">
- <p>只支持.jpg、.png格式。</p>
- </div>
- </el-form-item>
- <el-form-item label="营业执照号" prop="creditCode" v-if="form.subjectType === '01'">
- <el-input
- v-model="form.creditCode"
- placeholder="请输入统一社会信用代码"
- clearable
- maxlength="32"
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item prop="merchantShortName" v-if="form.subjectType === '01'">
- <template #label>
- <span class="label-with-icon"> 商户简称 </span>
- </template>
- <el-input v-model="form.merchantShortName" placeholder="请输入商户简称" clearable style="max-width: 480px" />
- </el-form-item>
- <el-form-item prop="businessCategory" v-if="form.subjectType === '01'">
- <template #label>
- <span class="label-with-icon"> 经营类目 </span>
- </template>
- <el-cascader
- v-model="form.businessCategory"
- :options="categoryCascaderOptions"
- :props="categoryCascaderProps"
- placeholder="请选择经营类目"
- clearable
- filterable
- style="width: 480px"
- />
- </el-form-item>
- <!--
- <template v-if="form.subjectType === '02'">
- <el-form-item prop="institutionCertUrl">
- <template #label>事业单位法人证书</template>
- <div class="license-upload-wrap">
- <el-upload
- v-model:file-list="form.institutionCertFileList"
- list-type="picture-card"
- :limit="1"
- accept=".jpg,.jpeg,.png,image/jpeg,image/png"
- :before-upload="beforeImageUploadInstJpgPng"
- :http-request="opt => handleSingleUpload(opt, 'institutionCert')"
- :on-remove="(_f, fl) => onSingleRemove(fl, 'institutionCert')"
- >
- <el-icon><Plus /></el-icon>
- </el-upload>
- </div>
- <div class="upload-rules">
- <p>仅支持 jpg、png 格式。</p>
- </div>
- </el-form-item>
- <el-form-item label="事业单位法人证书编号" prop="institutionCertNumber">
- <el-input
- v-model="form.institutionCertNumber"
- placeholder="请输入证书编号"
- clearable
- maxlength="64"
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item prop="merchantShortName">
- <template #label>
- <span class="label-with-icon">
- 商户简称
- <el-tooltip placement="top" content="简称将展示给消费者,需与法人证书或品牌一致。">
- <el-icon class="info-icon" :size="16"><InfoFilled /></el-icon>
- </el-tooltip>
- </span>
- </template>
- <el-input
- v-model="form.merchantShortName"
- placeholder="请输入商户简称"
- clearable
- maxlength="64"
- show-word-limit
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item label="经营类目" prop="businessCategory">
- <el-cascader
- v-model="form.businessCategory"
- :options="categoryCascaderOptions"
- :props="categoryCascaderProps"
- placeholder="请选择经营类目"
- clearable
- filterable
- style="width: 480px"
- />
- </el-form-item>
- </template> -->
- <!-- <template v-if="form.subjectType === '03'">
- <el-form-item prop="otherOrgCertUrl">
- <template #label>民办非企业单位登记证书</template>
- <div class="license-upload-wrap">
- <el-upload
- v-model:file-list="form.otherOrgCertFileList"
- list-type="picture-card"
- :limit="1"
- accept=".jpg,.jpeg,.png,image/jpeg,image/png"
- :before-upload="beforeImageUploadInstJpgPng"
- :http-request="opt => handleSingleUpload(opt, 'otherOrgCert')"
- :on-remove="(_f, fl) => onSingleRemove(fl, 'otherOrgCert')"
- >
- <el-icon><Plus /></el-icon>
- </el-upload>
- </div>
- <div class="upload-rules">
- <p>只支持 jpg、png 格式。</p>
- </div>
- </el-form-item>
- <el-form-item label="民办非企业单位登记证书编号" prop="otherOrgCertNumber">
- <el-input
- v-model="form.otherOrgCertNumber"
- placeholder="请输入证书编号"
- clearable
- maxlength="64"
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item prop="merchantShortName">
- <template #label>
- <span class="label-with-icon">
- 商户简称
- <el-tooltip placement="top" content="简称将展示给消费者,需与登记证书或品牌一致。">
- <el-icon class="info-icon" :size="16"><InfoFilled /></el-icon>
- </el-tooltip>
- </span>
- </template>
- <el-input
- v-model="form.merchantShortName"
- placeholder="请输入商户简称"
- clearable
- maxlength="64"
- show-word-limit
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item label="经营类目" prop="businessCategory">
- <el-cascader
- v-model="form.businessCategory"
- :options="categoryCascaderOptions"
- :props="categoryCascaderProps"
- placeholder="请选择经营类目"
- clearable
- filterable
- style="width: 480px"
- />
- </el-form-item>
- </template> -->
- <!-- <template v-if="form.subjectType === '04'">
- <el-form-item prop="socialCertUrl">
- <template #label>社会团体法人登记证书</template>
- <div class="license-upload-wrap">
- <el-upload
- v-model:file-list="form.socialCertFileList"
- list-type="picture-card"
- :limit="1"
- accept=".jpg,.jpeg,.png,image/jpeg,image/png"
- :before-upload="beforeImageUploadInstJpgPng"
- :http-request="opt => handleSingleUpload(opt, 'socialCert')"
- :on-remove="(_f, fl) => onSingleRemove(fl, 'socialCert')"
- >
- <el-icon><Plus /></el-icon>
- </el-upload>
- </div>
- <div class="upload-rules">
- <p>只支持 jpg、png 格式。</p>
- </div>
- </el-form-item>
- <el-form-item label="社会团体法人登记证书编号" prop="socialCertNumber">
- <el-input
- v-model="form.socialCertNumber"
- placeholder="请输入证书编号"
- clearable
- maxlength="64"
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item prop="merchantShortName">
- <template #label>
- <span class="label-with-icon">
- 商户简称
- <el-tooltip placement="top" content="简称将展示给消费者,需与法人登记证书或品牌一致。">
- <el-icon class="info-icon" :size="16"><InfoFilled /></el-icon>
- </el-tooltip>
- </span>
- </template>
- <el-input
- v-model="form.merchantShortName"
- placeholder="请输入商户简称"
- clearable
- maxlength="64"
- show-word-limit
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item label="经营类目" prop="businessCategory">
- <el-cascader
- v-model="form.businessCategory"
- :options="categoryCascaderOptions"
- :props="categoryCascaderProps"
- placeholder="请选择经营类目"
- clearable
- filterable
- style="width: 480px"
- />
- </el-form-item>
- </template> -->
- <!-- <template v-if="form.subjectType === '05'">
- <el-form-item prop="govCertUrl">
- <template #label>
- <span class="label-wrap-multiline">党政机关批准设立文件/行政执法主体资格证</span>
- </template>
- <div class="license-upload-wrap">
- <el-upload
- v-model:file-list="form.govCertFileList"
- list-type="picture-card"
- :limit="1"
- accept=".jpg,.jpeg,.png,image/jpeg,image/png"
- :before-upload="beforeImageUploadInstJpgPng"
- :http-request="opt => handleSingleUpload(opt, 'govCert')"
- :on-remove="(_f, fl) => onSingleRemove(fl, 'govCert')"
- >
- <el-icon><Plus /></el-icon>
- </el-upload>
- </div>
- <div class="upload-rules">
- <p>只支持 jpg、png 格式。</p>
- </div>
- </el-form-item>
- <el-form-item prop="govCertNumber">
- <template #label>
- <span class="label-wrap-multiline">党政机关批准设立文件/行政执法主体资格证编号</span>
- </template>
- <el-input
- v-model="form.govCertNumber"
- placeholder="请输入编号"
- clearable
- maxlength="128"
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item prop="merchantShortName">
- <template #label>
- <span class="label-with-icon">
- 商户简称
- <el-tooltip placement="top" content="简称将展示给消费者,需与批准文件或主体名称一致。">
- <el-icon class="info-icon" :size="16"><InfoFilled /></el-icon>
- </el-tooltip>
- </span>
- </template>
- <el-input
- v-model="form.merchantShortName"
- placeholder="请输入商户简称"
- clearable
- maxlength="64"
- show-word-limit
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item label="经营类目" prop="businessCategory">
- <el-cascader
- v-model="form.businessCategory"
- :options="categoryCascaderOptions"
- :props="categoryCascaderProps"
- placeholder="请选择经营类目"
- clearable
- filterable
- style="width: 480px"
- />
- </el-form-item>
- </template> -->
- <!-- <template v-if="form.subjectType === '06'">
- <el-alert
- class="individual-merchant-alert"
- type="info"
- :closable="false"
- show-icon
- >
- 使用当面付服务时,选择个人类型会有收款额度限制
- </el-alert>
- <el-form-item prop="idFrontUrl">
- <template #label>个人身份证</template>
- <div class="id-upload-row id-upload-row--four">
- <div class="id-upload-cell">
- <span class="id-side-label">上传人像面</span>
- <el-upload
- v-model:file-list="form.idFrontFileList"
- list-type="picture-card"
- :limit="1"
- accept=".jpg,.jpeg,.png,image/jpeg,image/png"
- :before-upload="beforeImageUploadInstJpgPng"
- :http-request="opt => handleSingleUpload(opt, 'idFront')"
- :on-remove="(_f, fl) => onSingleRemove(fl, 'idFront')"
- >
- <el-icon><Plus /></el-icon>
- </el-upload>
- </div>
- <div class="id-upload-cell">
- <span class="id-side-label">上传国徽面</span>
- <el-upload
- v-model:file-list="form.idBackFileList"
- list-type="picture-card"
- :limit="1"
- accept=".jpg,.jpeg,.png,image/jpeg,image/png"
- :before-upload="beforeImageUploadInstJpgPng"
- :http-request="opt => handleSingleUpload(opt, 'idBack')"
- :on-remove="(_f, fl) => onSingleRemove(fl, 'idBack')"
- >
- <el-icon><Plus /></el-icon>
- </el-upload>
- </div>
- </div>
- <div class="upload-rules">
- <p>
- 说明:仅支持 jpg、png 格式。如果没有证件原件照片,可以上传证件照片复印件。
- </p>
- </div>
- </el-form-item>
- <el-form-item label="个人身份证号码" prop="idNumber">
- <el-input v-model="form.idNumber" placeholder="请输入身份证号码" clearable style="max-width: 480px" />
- </el-form-item>
- <el-form-item prop="merchantShortName">
- <template #label>
- <span class="label-with-icon">
- 商户简称
- <el-tooltip placement="top" content="简称将展示给消费者,需与身份证或品牌一致。">
- <el-icon class="info-icon" :size="16"><InfoFilled /></el-icon>
- </el-tooltip>
- </span>
- </template>
- <el-input
- v-model="form.merchantShortName"
- placeholder="请输入商户简称"
- clearable
- maxlength="64"
- show-word-limit
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item label="经营类目" prop="businessCategory">
- <el-cascader
- v-model="form.businessCategory"
- :options="categoryCascaderOptions"
- :props="categoryCascaderProps"
- placeholder="请选择经营类目"
- clearable
- filterable
- style="width: 480px"
- />
- </el-form-item>
- </template> -->
- <template v-if="form.subjectType === '07'">
- <el-form-item prop="licenseUrl">
- <template #label> 营业执照 </template>
- <div class="license-upload-wrap">
- <el-upload
- v-model:file-list="form.licenseFileList"
- list-type="picture-card"
- :limit="1"
- accept=".jpg,.jpeg,.png,image/jpeg,image/png"
- :before-upload="beforeImageUploadInstJpgPng"
- :http-request="opt => handleSingleUpload(opt, 'license')"
- :on-remove="(_f, fl) => onSingleRemove(fl, 'license')"
- >
- <el-icon><Plus /></el-icon>
- </el-upload>
- </div>
- <div class="upload-rules">
- <p>只支持 jpg、png 格式。</p>
- </div>
- </el-form-item>
- <el-form-item label="营业执照编号" prop="creditCode">
- <el-input
- v-model="form.creditCode"
- placeholder="请输入营业执照编号"
- clearable
- maxlength="32"
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item label="营业执照名称" prop="householdLicenseName">
- <el-input
- v-model="form.householdLicenseName"
- placeholder="请输入营业执照名称"
- clearable
- maxlength="128"
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item prop="merchantShortName">
- <template #label>
- <span class="label-with-icon">
- 商户简称
- <el-tooltip placement="top" content="简称将展示给消费者,需与营业执照或品牌一致。">
- <el-icon class="info-icon" :size="16"><InfoFilled /></el-icon>
- </el-tooltip>
- </span>
- </template>
- <el-input v-model="form.merchantShortName" placeholder="请输入商户简称" clearable style="max-width: 480px" />
- </el-form-item>
- <el-form-item label="经营类目" prop="businessCategory">
- <el-cascader
- v-model="form.businessCategory"
- :options="categoryCascaderOptions"
- :props="categoryCascaderProps"
- placeholder="请选择经营类目"
- clearable
- filterable
- style="width: 480px"
- />
- </el-form-item>
- </template>
- </section>
- <!-- 法人信息 -->
- <section id="zfb-legal" class="form-block anchor-section" v-if="form.subjectType === '01' || form.subjectType === '07'">
- <h2 class="section-head">
- <span class="section-bar" aria-hidden="true" />
- 法人信息
- </h2>
- <el-form-item label="法定代表人/负责人证件类型" prop="legalIdType">
- <el-select v-model="form.legalIdType" placeholder="请选择" clearable style="width: 260px">
- <el-option label="居民身份证" value="id_card" />
- <el-option label="护照" value="passport" />
- <el-option label="港澳居民来往内地通行证" value="hk_macau_pass" />
- <el-option label="台湾居民来往大陆通行证" value="taiwan_pass" />
- </el-select>
- </el-form-item>
- <el-form-item prop="idFrontUrl">
- <template #label> 上传证件照片 </template>
- <div class="id-upload-row">
- <div class="id-upload-cell">
- <div class="license-upload-wrap">
- <el-upload
- v-model:file-list="form.idFrontFileList"
- list-type="picture-card"
- :limit="1"
- accept=".jpg,.jpeg,.png,.bmp,.gif,image/jpeg,image/png,image/bmp,image/gif"
- :before-upload="beforeImageUpload"
- :http-request="opt => handleSingleUpload(opt, 'idFront')"
- :on-remove="(_f, fl) => onSingleRemove(fl, 'idFront')"
- >
- <div class="id-upload-trigger">
- <el-icon><Plus /></el-icon>
- <div class="id-side-label">上传证件正面</div>
- </div>
- </el-upload>
- </div>
- </div>
- <div class="id-upload-cell">
- <div class="license-upload-wrap">
- <el-upload
- v-model:file-list="form.idBackFileList"
- list-type="picture-card"
- :limit="1"
- accept=".jpg,.jpeg,.png,.bmp,.gif,image/jpeg,image/png,image/bmp,image/gif"
- :before-upload="beforeImageUpload"
- :http-request="opt => handleSingleUpload(opt, 'idBack')"
- :on-remove="(_f, fl) => onSingleRemove(fl, 'idBack')"
- >
- <div class="id-upload-trigger">
- <el-icon><Plus /></el-icon>
- <div class="id-side-label">上传证件反面</div>
- </div>
- </el-upload>
- </div>
- </div>
- </div>
- <div class="upload-rules">
- <p>说明:仅支持.jpg、.png格式,如果没有证件原件照片,可以上传证件照片复印件。</p>
- </div>
- </el-form-item>
- <el-form-item label="证件号" prop="idNumber">
- <el-input v-model="form.idNumber" placeholder="请输入证件号码" clearable style="max-width: 480px" />
- </el-form-item>
- <el-form-item label="法定代表人/负责人姓名" prop="legalName">
- <el-input v-model="form.legalName" placeholder="请输入姓名" clearable style="max-width: 480px" />
- </el-form-item>
- </section>
- <!-- 经营信息 -->
- <section id="zfb-business" class="form-block anchor-section">
- <h2 class="section-head">
- <span class="section-bar" aria-hidden="true" />
- 经营信息
- </h2>
- <el-form-item label="接入服务类型" prop="serviceTypes">
- <div class="field-stack">
- <el-checkbox-group v-model="form.serviceTypes" class="service-checks">
- <el-checkbox v-for="s in serviceTypeOptions" :key="s.value" :label="s.value">
- {{ s.label }}
- </el-checkbox>
- </el-checkbox-group>
- <p class="field-tip">
- 请勾选实际售卖商品/提供服务场景(至少一项),已便为你开通所需支付权限。建议只勾选目前必须的场景,以便尽快通过入驻审核,其他支付权限可在入驻后再根据需要发起申请
- </p>
- </div>
- </el-form-item>
- <el-form-item prop="storefrontUrl" v-if="form.serviceTypes.includes('当面付')">
- <template #label> 上传门头照 </template>
- <div class="license-upload-wrap">
- <el-upload
- v-model:file-list="form.storefrontFileList"
- list-type="picture-card"
- :limit="1"
- accept=".jpg,.jpeg,.png,.bmp,.gif,image/jpeg,image/png,image/bmp,image/gif"
- :before-upload="beforeImageUpload"
- :http-request="opt => handleSingleUpload(opt, 'storefront')"
- :on-remove="(_f, fl) => onSingleRemove(fl, 'storefront')"
- >
- <el-icon><Plus /></el-icon>
- </el-upload>
- </div>
- <div class="upload-rules">
- <p>说明:仅支持jpg、png格式,图片需清晰完整,最多上传3张。</p>
- </div>
- </el-form-item>
- <el-form-item prop="interiorUrl" v-if="form.serviceTypes.includes('当面付')">
- <template #label> 上传内景照 </template>
- <div class="license-upload-wrap">
- <el-upload
- v-model:file-list="form.interiorFileList"
- list-type="picture-card"
- :limit="1"
- accept=".jpg,.jpeg,.png,.bmp,.gif,image/jpeg,image/png,image/bmp,image/gif"
- :before-upload="beforeImageUpload"
- :http-request="opt => handleSingleUpload(opt, 'interior')"
- :on-remove="(_f, fl) => onSingleRemove(fl, 'interior')"
- >
- <el-icon><Plus /></el-icon>
- </el-upload>
- </div>
- <div class="upload-rules">
- <p>说明:仅支持jpg、png格式,图片需清晰完整,最多上传3张。</p>
- </div>
- </el-form-item>
- <el-form-item label="店铺地址" prop="region" v-if="form.serviceTypes.includes('当面付')">
- <div class="address-row">
- <el-cascader
- v-model="form.region"
- :options="regionCascaderOptions"
- placeholder="省 / 市"
- filterable
- clearable
- style="width: 320px"
- />
- <el-input
- v-model="form.detailAddress"
- class="address-detail"
- placeholder="街道、门牌号等"
- clearable
- maxlength="200"
- />
- </div>
- </el-form-item>
- <el-form-item label="APP名称" prop="appName" v-if="form.serviceTypes.includes('App支付')">
- <el-input
- v-model="form.appName"
- placeholder="请输入APP名称"
- clearable
- maxlength="64"
- show-word-limit
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item label="联系人姓名" prop="contactName">
- <el-input v-model="form.contactName" placeholder="请输入联系人姓名" clearable style="max-width: 480px" />
- </el-form-item>
- <el-form-item label="联系人手机号" prop="contactPhone">
- <el-input
- v-model="form.contactPhone"
- placeholder="请输入手机号"
- clearable
- maxlength="11"
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item label="联系人邮箱" prop="contactEmail">
- <el-input v-model="form.contactEmail" placeholder="请输入邮箱" clearable style="max-width: 480px" />
- </el-form-item>
- <el-form-item label="客服电话" prop="servicePhone">
- <el-input
- v-model="form.servicePhone"
- placeholder="请输入客服电话"
- clearable
- maxlength="20"
- style="max-width: 480px"
- />
- </el-form-item>
- <el-form-item prop="settlementAlipayAccount">
- <template #label> 签约支付宝账号 </template>
- <div class="field-stack">
- <el-input
- v-model="form.settlementAlipayAccount"
- placeholder="请输入用于收款的支付宝账号"
- clearable
- style="max-width: 480px"
- />
- <p class="field-tip">如商家无支付宝账号,请引导商家至支付宝注册。</p>
- </div>
- </el-form-item>
- </section>
- </el-form>
- <div class="footer-actions">
- <el-button type="primary" class="btn-primary-zfb" :loading="nextLoading" @click="onNext"> 下一步 </el-button>
- </div>
- </div>
- <aside class="anchor-column">
- <el-affix :offset="88">
- <div class="anchor-card">
- <el-anchor container=".el-main" :offset="72" :bound="48" direction="vertical">
- <el-anchor-link href="#zfb-subject" title="主体信息" />
- <el-anchor-link href="#zfb-legal" title="法人信息" />
- <el-anchor-link href="#zfb-business" title="经营信息" />
- </el-anchor>
- </div>
- </el-affix>
- </aside>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
- import { useRoute, useRouter } from "vue-router";
- import { ElMessage } from "element-plus";
- import type { FormInstance, FormRules } from "element-plus";
- import type { UploadRequestOptions, UploadUserFile } from "element-plus";
- import { Plus, InfoFilled } from "@element-plus/icons-vue";
- import { getOcrRequestByBase64 } from "@/api/modules/businessInfo";
- import {
- uploadBusinessInfoImageToOss,
- uploadBusinessInfoImageWithPageOcr,
- filterOutUploadUserFileByUid,
- failBusinessInfoUploadCleanup
- } from "@/utils/businessInfoImageUpload";
- import { localGet, localSet } from "@/utils/index";
- import cityJson from "@/assets/json/city.json";
- import categoryJson from "@/views/businessInfo/category.json";
- const ALIPAY_JSON_KEY = "alipayJson";
- const GEEKER_USER_KEY = "geeker-user";
- /** 同时勾选当面付 + App 支付时,APP 名称默认与进件 sites[0].SiteName 一致 */
- const DEFAULT_ALIPAY_APP_SITE_NAME = "应用2.0签约2025092468860692";
- /** 进件 externalId:16 位随机数字(0–9) */
- function randomAlipayExternalId16(): string {
- const buf = new Uint8Array(16);
- crypto.getRandomValues(buf);
- let s = "";
- for (let i = 0; i < 16; i++) {
- s += String(buf[i]! % 10);
- }
- return s;
- }
- const formRef = ref<FormInstance>();
- const nextLoading = ref(false);
- const router = useRouter();
- const route = useRoute();
- function resolveAlipayUploadStoreId(): string | number | null {
- const q = route.query.storeId;
- if (q !== undefined && q !== null && String(q).trim() !== "") {
- return Array.isArray(q) ? q[0]! : q;
- }
- const geeker = localGet(GEEKER_USER_KEY) as { userInfo?: { storeId?: string | number | null } } | null | undefined;
- const sid = geeker?.userInfo?.storeId ?? localGet("createdId");
- if (sid === undefined || sid === null || sid === "") return null;
- return sid;
- }
- /** 支付宝进件步骤:0 商家信息 / 1 结算信息 / 2 提交审核 */
- const zfbStepActive = ref(0);
- type CategoryJsonNode = {
- code?: string;
- name?: string;
- parentCode?: string;
- parentName?: string;
- level?: number;
- display?: boolean;
- status?: string;
- sortNum?: number;
- childrenNodes?: CategoryJsonNode[];
- };
- type CategoryCascaderNode = { value: string; label: string; children?: CategoryCascaderNode[] };
- /**
- * 二级经营类目:一级为 level=1(name/code),二级为其 childrenNodes 中 level=2 的 name/code
- */
- function buildCategoryCascaderOptions(root: unknown): CategoryCascaderNode[] {
- const list = (root as { data?: { childrenNodes?: CategoryJsonNode[] } })?.data?.childrenNodes;
- if (!Array.isArray(list)) return [];
- return list
- .filter(n => n && typeof n === "object" && n.level === 1 && n.code && n.name && n.status === "VALID" && n.display !== false)
- .sort((a, b) => (a.sortNum ?? 0) - (b.sortNum ?? 0))
- .map(parent => {
- const children = (parent.childrenNodes || [])
- .filter(
- ch =>
- ch && typeof ch === "object" && ch.level === 2 && ch.code && ch.name && ch.status === "VALID" && ch.display !== false
- )
- .sort((a, b) => (a.sortNum ?? 0) - (b.sortNum ?? 0))
- .map(ch => ({
- value: String(ch.code),
- label: String(ch.name)
- }));
- return {
- value: String(parent.code),
- label: String(parent.name),
- children
- };
- })
- .filter(p => p.children && p.children.length > 0);
- }
- const categoryCascaderOptions = buildCategoryCascaderOptions(categoryJson);
- const categoryCascaderProps = {
- value: "value",
- label: "label",
- children: "children",
- emitPath: true
- };
- const subjectTypeOptions = [
- { label: "企业", value: "01" },
- // { label: "事业单位", value: "02" },
- // { label: "民办非企业组织", value: "03" },
- // { label: "社会团体", value: "04" },
- // { label: "党政及国家机关", value: "05" },
- // { label: "个人商户", value: "06" },
- { label: "个体工商户", value: "07" }
- ];
- const serviceTypeOptions = [
- { label: "当面付", value: "当面付" },
- { label: "App支付", value: "App支付" }
- ];
- type CityJsonEntry = { name: string; adCode: string; cityCode: string };
- type CityJsonRoot = { cityList: { letter: string; list: CityJsonEntry[] }[] };
- const PROVINCE_ROWS: [string, string][] = [
- ["11", "北京市"],
- ["12", "天津市"],
- ["13", "河北省"],
- ["14", "山西省"],
- ["15", "内蒙古自治区"],
- ["21", "辽宁省"],
- ["22", "吉林省"],
- ["23", "黑龙江省"],
- ["31", "上海市"],
- ["32", "江苏省"],
- ["33", "浙江省"],
- ["34", "安徽省"],
- ["35", "福建省"],
- ["36", "江西省"],
- ["37", "山东省"],
- ["41", "河南省"],
- ["42", "湖北省"],
- ["43", "湖南省"],
- ["44", "广东省"],
- ["45", "广西壮族自治区"],
- ["46", "海南省"],
- ["50", "重庆市"],
- ["51", "四川省"],
- ["52", "贵州省"],
- ["53", "云南省"],
- ["54", "西藏自治区"],
- ["61", "陕西省"],
- ["62", "甘肃省"],
- ["63", "青海省"],
- ["64", "宁夏回族自治区"],
- ["65", "新疆维吾尔自治区"],
- ["71", "台湾省"],
- ["81", "香港特别行政区"],
- ["82", "澳门特别行政区"]
- ];
- const PROVINCE_BY_PREFIX = Object.fromEntries(PROVINCE_ROWS) as Record<string, string>;
- const PROVINCE_PREFIX_ORDER = PROVINCE_ROWS.map(([k]) => k);
- function buildProvinceCityOptions(): {
- label: string;
- value: string;
- cities: { label: string; value: string }[];
- }[] {
- const root = cityJson as CityJsonRoot;
- const flat = root.cityList.flatMap(g => g.list);
- const bucket = new Map<string, Map<string, CityJsonEntry>>();
- for (const c of flat) {
- const digits = String(c.adCode ?? "").replace(/\D/g, "");
- const code = digits.length >= 6 ? digits.slice(-6) : digits.padStart(6, "0");
- if (code.length !== 6) continue;
- const prefix = code.slice(0, 2);
- if (!PROVINCE_BY_PREFIX[prefix]) continue;
- if (!bucket.has(prefix)) bucket.set(prefix, new Map());
- bucket.get(prefix)!.set(c.adCode, c);
- }
- const out = [...bucket.entries()].map(([prefix, cityMap]) => ({
- value: prefix,
- label: PROVINCE_BY_PREFIX[prefix],
- cities: [...cityMap.values()]
- .sort((a, b) => a.name.localeCompare(b.name, "zh-CN"))
- .map(ci => ({ label: ci.name, value: ci.adCode }))
- }));
- out.sort((a, b) => {
- const ia = PROVINCE_PREFIX_ORDER.indexOf(a.value);
- const ib = PROVINCE_PREFIX_ORDER.indexOf(b.value);
- return (ia === -1 ? 999 : ia) - (ib === -1 ? 999 : ib);
- });
- return out;
- }
- const provinceCityOptions = buildProvinceCityOptions();
- const regionCascaderOptions = computed(() =>
- provinceCityOptions.map(p => ({
- value: p.value,
- label: p.label,
- children: p.cities.map(c => ({ value: c.value, label: c.label }))
- }))
- );
- type UploadKind =
- | "license"
- | "idFront"
- | "idBack"
- | "storefront"
- | "interior"
- | "institutionCert"
- | "otherOrgCert"
- | "socialCert"
- | "govCert";
- const form = reactive<{
- subjectType: string;
- licenseFileList: UploadUserFile[];
- licenseUrl: string;
- /** 营业执照上传接口返回的 image_id,供 alipayJson.certImage */
- licenseImageId: string;
- creditCode: string;
- householdLicenseName: string;
- merchantShortName: string;
- businessTermRange: [string, string] | undefined;
- institutionCertFileList: UploadUserFile[];
- institutionCertUrl: string;
- institutionCertNumber: string;
- otherOrgCertFileList: UploadUserFile[];
- otherOrgCertUrl: string;
- otherOrgCertNumber: string;
- socialCertFileList: UploadUserFile[];
- socialCertUrl: string;
- socialCertNumber: string;
- govCertFileList: UploadUserFile[];
- govCertUrl: string;
- govCertNumber: string;
- businessCategory: string[];
- legalIdType: string;
- idFrontFileList: UploadUserFile[];
- idFrontUrl: string;
- idBackFileList: UploadUserFile[];
- idBackUrl: string;
- idNumber: string;
- legalName: string;
- serviceTypes: string[];
- storefrontFileList: UploadUserFile[];
- storefrontUrl: string;
- /** 上传接口返回的 image_id,供 alipayJson.outDoorImages */
- storefrontImageId: string;
- interiorFileList: UploadUserFile[];
- interiorUrl: string;
- /** 上传接口返回的 image_id,供 alipayJson.inDoorImages */
- interiorImageId: string;
- institutionStorefrontFileList: UploadUserFile[];
- institutionStorefrontUrls: string[];
- institutionInteriorFileList: UploadUserFile[];
- institutionInteriorUrls: string[];
- region: string[];
- detailAddress: string;
- appName: string;
- contactName: string;
- contactPhone: string;
- contactEmail: string;
- servicePhone: string;
- settlementAlipayAccount: string;
- }>({
- subjectType: "01",
- licenseFileList: [],
- licenseUrl: "",
- licenseImageId: "",
- creditCode: "",
- householdLicenseName: "",
- merchantShortName: "",
- businessTermRange: undefined,
- institutionCertFileList: [],
- institutionCertUrl: "",
- institutionCertNumber: "",
- otherOrgCertFileList: [],
- otherOrgCertUrl: "",
- otherOrgCertNumber: "",
- socialCertFileList: [],
- socialCertUrl: "",
- socialCertNumber: "",
- govCertFileList: [],
- govCertUrl: "",
- govCertNumber: "",
- businessCategory: [],
- legalIdType: "",
- idFrontFileList: [],
- idFrontUrl: "",
- idBackFileList: [],
- idBackUrl: "",
- idNumber: "",
- legalName: "",
- serviceTypes: [],
- storefrontFileList: [],
- storefrontUrl: "",
- storefrontImageId: "",
- interiorFileList: [],
- interiorUrl: "",
- interiorImageId: "",
- institutionStorefrontFileList: [],
- institutionStorefrontUrls: [],
- institutionInteriorFileList: [],
- institutionInteriorUrls: [],
- region: [],
- detailAddress: "",
- appName: "",
- contactName: "",
- contactPhone: "",
- contactEmail: "",
- servicePhone: "",
- settlementAlipayAccount: ""
- });
- watch(
- () => form.serviceTypes.slice().sort().join("\0"),
- () => {
- const hasFace = form.serviceTypes.includes("当面付");
- const hasApp = form.serviceTypes.includes("App支付");
- if (hasFace && hasApp && !String(form.appName || "").trim()) {
- form.appName = DEFAULT_ALIPAY_APP_SITE_NAME;
- }
- }
- );
- /** 用户切换到个体工商户时清空本页字段,避免企业侧已填数据带入个体户(与初始 form 一致,不改 subjectType) */
- function clearZfbFormExceptSubjectType() {
- form.licenseFileList = [];
- form.licenseUrl = "";
- form.licenseImageId = "";
- form.creditCode = "";
- form.householdLicenseName = "";
- form.merchantShortName = "";
- form.businessTermRange = undefined;
- form.institutionCertFileList = [];
- form.institutionCertUrl = "";
- form.institutionCertNumber = "";
- form.otherOrgCertFileList = [];
- form.otherOrgCertUrl = "";
- form.otherOrgCertNumber = "";
- form.socialCertFileList = [];
- form.socialCertUrl = "";
- form.socialCertNumber = "";
- form.govCertFileList = [];
- form.govCertUrl = "";
- form.govCertNumber = "";
- form.businessCategory = [];
- form.legalIdType = "";
- form.idFrontFileList = [];
- form.idFrontUrl = "";
- form.idBackFileList = [];
- form.idBackUrl = "";
- form.idNumber = "";
- form.legalName = "";
- form.serviceTypes = [];
- form.storefrontFileList = [];
- form.storefrontUrl = "";
- form.storefrontImageId = "";
- form.interiorFileList = [];
- form.interiorUrl = "";
- form.interiorImageId = "";
- form.institutionStorefrontFileList = [];
- form.institutionStorefrontUrls = [];
- form.institutionInteriorFileList = [];
- form.institutionInteriorUrls = [];
- form.region = [];
- form.detailAddress = "";
- form.appName = "";
- form.contactName = "";
- form.contactPhone = "";
- form.contactEmail = "";
- form.servicePhone = "";
- form.settlementAlipayAccount = "";
- }
- function onSubjectTypeChange(val: string | number | boolean | undefined) {
- if (String(val ?? "") !== "07") return;
- clearZfbFormExceptSubjectType();
- nextTick(() => formRef.value?.clearValidate());
- }
- /** 商户端图片上传统一上限 20MB(若支付宝接口仍限 10MB,超大文件可能在服务端被拒) */
- const MAX_ALIPAY_IMAGE_BYTES = 20 * 1024 * 1024;
- /** 事业单位材料:仅 jpg / png(与页面说明一致) */
- function beforeImageUploadInstJpgPng(file: File) {
- const name = file.name?.toLowerCase() || "";
- const okExt = /\.(jpe?g|png)$/i.test(name);
- const okMime = ["image/jpeg", "image/png"].includes(file.type);
- if (!okExt && !okMime) {
- ElMessage.error("仅支持 jpg、png 格式");
- return false;
- }
- if (file.size > MAX_ALIPAY_IMAGE_BYTES) {
- ElMessage.error("文件大小不能超过 20M");
- return false;
- }
- return true;
- }
- function beforeImageUpload(file: File) {
- const name = file.name?.toLowerCase() || "";
- const okExt = /\.(jpe?g|png|bmp|gif)$/i.test(name);
- const okMime = ["image/jpeg", "image/png", "image/bmp", "image/gif"].includes(file.type);
- if (!okExt && !okMime) {
- ElMessage.error("图片只支持 JPG、BMP、PNG、GIF 格式");
- return false;
- }
- if (file.size > MAX_ALIPAY_IMAGE_BYTES) {
- ElMessage.error("文件大小不能超过 20M");
- return false;
- }
- return true;
- }
- /** 解析 image_type:扩展名 3~16 字符,jpeg 归一为 jpg */
- function resolveAlipayImageType(file: File): string | null {
- const name = file.name || "";
- const extMatch = /\.([a-zA-Z0-9]{1,16})$/i.exec(name);
- let ext = extMatch ? extMatch[1].toLowerCase() : "";
- if (ext === "jpeg") ext = "jpg";
- if (ext && ext.length >= 3 && ext.length <= 16) return ext;
- const mime = file.type;
- if (mime === "image/jpeg") return "jpg";
- if (mime === "image/png") return "png";
- if (mime === "image/gif") return "gif";
- if (mime === "image/bmp") return "bmp";
- return null;
- }
- /** 解析支付宝/通用上传返回;兼容 data 为 JSON 字符串(直付通 ant_merchant_expand_indirect_image_upload_response) */
- function parseMediaUploadResult(res: any): { fileUrl: string; mediaId: string; errMsg: string } {
- const errMsg = String(res?.msg ?? "").trim();
- let payload = res?.data;
- if (payload === undefined) payload = res;
- if (typeof payload === "string") {
- const s = payload.trim();
- if (/^https?:\/\//i.test(s)) return { fileUrl: s, mediaId: "", errMsg };
- if ((s.startsWith("{") || s.startsWith("[")) && s.length > 1) {
- try {
- payload = JSON.parse(s);
- } catch {
- if (s) return { fileUrl: "", mediaId: s, errMsg };
- return { fileUrl: "", mediaId: "", errMsg };
- }
- } else if (s) {
- return { fileUrl: "", mediaId: s, errMsg };
- } else {
- return { fileUrl: "", mediaId: "", errMsg };
- }
- }
- if (!payload || typeof payload !== "object") {
- return { fileUrl: "", mediaId: "", errMsg };
- }
- let body: any = payload;
- if ("data" in payload && payload.data !== undefined) {
- if (typeof payload.data === "string") {
- const inner = payload.data.trim();
- if (/^https?:\/\//i.test(inner)) return { fileUrl: inner, mediaId: "", errMsg };
- if ((inner.startsWith("{") || inner.startsWith("[")) && inner.length > 1) {
- try {
- body = JSON.parse(inner);
- } catch {
- if (inner) return { fileUrl: "", mediaId: inner, errMsg };
- body = payload;
- }
- } else if (inner) {
- return { fileUrl: "", mediaId: inner, errMsg };
- }
- } else if (payload.data && typeof payload.data === "object") {
- body = payload.data;
- }
- }
- if (body && typeof body === "object" && "data" in body && body.data !== undefined) {
- if (typeof body.data === "string") {
- const inner = body.data.trim();
- if (/^https?:\/\//i.test(inner)) return { fileUrl: inner, mediaId: "", errMsg };
- if ((inner.startsWith("{") || inner.startsWith("[")) && inner.length > 1) {
- try {
- body = JSON.parse(inner);
- } catch {
- if (inner) return { fileUrl: "", mediaId: inner, errMsg };
- }
- } else if (inner) {
- return { fileUrl: "", mediaId: inner, errMsg };
- }
- } else if (body.data && typeof body.data === "object") {
- body = body.data;
- }
- }
- const antUpload =
- body?.ant_merchant_expand_indirect_image_upload_response ?? body?.antMerchantExpandIndirectImageUploadResponse;
- const fileUrl = String(
- body?.url ?? body?.fileUrl ?? body?.imageUrl ?? body?.image_url ?? body?.picUrl ?? body?.ossUrl ?? body?.file_path ?? ""
- ).trim();
- const mediaId = String(
- antUpload?.image_id ??
- antUpload?.imageId ??
- body?.image_id ??
- body?.imageId ??
- body?.resource_id ??
- body?.resourceId ??
- body?.media_id ??
- body?.mediaId ??
- body?.outBizNo ??
- body?.pictureId ??
- ""
- ).trim();
- return { fileUrl, mediaId, errMsg };
- }
- /** 与支付宝上传返回常见的 object / 文件名一致 */
- const ALIPAY_UPLOAD_IMAGE_FILE_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.(jpe?g|png)$/i;
- function extractAlipayFilenameFromImageUrl(url: string): string {
- const s = String(url || "").trim();
- if (!s || !/^https?:\/\//i.test(s)) return "";
- try {
- const seg = decodeURIComponent(new URL(s).pathname.split("/").filter(Boolean).pop() || "");
- if (ALIPAY_UPLOAD_IMAGE_FILE_RE.test(seg) || /\.(jpe?g|png)$/i.test(seg)) return seg;
- } catch {
- /* ignore */
- }
- return "";
- }
- /** 深度遍历响应,取 image_id 等字段(兼容多层 data / 字段名差异) */
- function deepPickAlipayImageId(node: any, depth = 0): string {
- if (depth > 14 || node === null || node === undefined) return "";
- if (typeof node === "string") {
- const t = node.trim();
- if (ALIPAY_UPLOAD_IMAGE_FILE_RE.test(t)) return t;
- if (t.startsWith("{") && t.includes("image")) {
- try {
- return deepPickAlipayImageId(JSON.parse(t), depth + 1);
- } catch {
- return "";
- }
- }
- return "";
- }
- if (typeof node !== "object") return "";
- if (Array.isArray(node)) {
- for (const x of node) {
- const r = deepPickAlipayImageId(x, depth + 1);
- if (r) return r;
- }
- return "";
- }
- const keys = ["image_id", "imageId", "ImageId", "fileName", "file_name", "objectName", "object_name", "key", "picName"];
- for (const k of keys) {
- if (!(k in node) || node[k] == null) continue;
- const v = String(node[k]).trim();
- if (!v) continue;
- if (/^https?:\/\//i.test(v)) {
- const fromUrl = extractAlipayFilenameFromImageUrl(v);
- if (fromUrl) return fromUrl;
- } else {
- const base = v.split(/[/\\]/).pop() || v;
- if (base) return base.trim();
- }
- }
- for (const v of Object.values(node)) {
- const r = deepPickAlipayImageId(v, depth + 1);
- if (r) return r;
- }
- return "";
- }
- /** 上传成功后得到进件用图片名:扁平解析 → 深度查找 → 从返回的图片 URL 截取 */
- function resolveAlipayUploadImageId(res: any, fileUrl: string, mediaId: string): string {
- let id = String(mediaId || "").trim();
- if (id) return id.split(/[/\\]/).pop() || id;
- id = deepPickAlipayImageId(res);
- if (id) return id.split(/[/\\]/).pop() || id;
- return extractAlipayFilenameFromImageUrl(fileUrl);
- }
- function isAlipayUploadHttpSuccess(res: any): boolean {
- return res?.code === 200 || res?.code === "200";
- }
- /** 与 subjectInfo:营业执照 OCR 响应解析 */
- function pickBusinessLicenseOcrFields(res: any) {
- const raw = res?.data ?? res;
- let node: any = raw;
- if (node && typeof node === "object" && "data" in node && node.data !== undefined) {
- node = node.data;
- }
- const item = Array.isArray(node) ? node[0] : node;
- if (!item || typeof item !== "object") {
- return { creditCode: "", companyName: "", legalPerson: "" };
- }
- return {
- creditCode: String(item.creditCode ?? item.credit_code ?? "").trim(),
- companyName: String(item.companyName ?? item.company_name ?? "").trim(),
- legalPerson: String(item.legalPerson ?? item.legal_person ?? "").trim()
- };
- }
- /** 事业单位法人证书 OCR:证书编号、单位名称(回显商户简称) */
- function pickInstitutionCertOcrFields(res: any): { certNumber: string; orgName: string } {
- const base = pickBusinessLicenseOcrFields(res);
- const raw = res?.data ?? res;
- let node: any = raw;
- if (node && typeof node === "object" && "data" in node && node.data !== undefined) {
- node = node.data;
- }
- const item = Array.isArray(node) ? node[0] : node;
- let certNumber = base.creditCode;
- let orgName = base.companyName;
- if (item && typeof item === "object") {
- if (!certNumber) {
- certNumber = String(
- item.certNumber ??
- item.certificateNumber ??
- item.cert_no ??
- item.certificateNo ??
- item.registrationNumber ??
- item.registration_no ??
- ""
- ).trim();
- }
- if (!orgName) {
- orgName = String(item.companyName ?? item.company_name ?? item.orgName ?? item.unitName ?? item.name ?? "").trim();
- }
- }
- return { certNumber, orgName };
- }
- /** 身份证有效期 → YYYY-MM-DD(与 subjectInfo 一致) */
- function splitIdCardValidPeriod(raw: string): { begin: string; end: string } {
- const s = (raw || "").trim();
- if (!s) return { begin: "", end: "" };
- if (/长期|长期有效/.test(s)) return { begin: "", end: "长期" };
- const norm = s
- .replace(/年|月|日/g, ".")
- .replace(/\.+/g, ".")
- .trim();
- const parts = norm.split(/[-至~~]/);
- const normOne = (p: string) => {
- const m = p.trim().match(/(\d{4})\D*(\d{1,2})\D*(\d{1,2})/);
- if (m) return `${m[1]}-${m[2].padStart(2, "0")}-${m[3].padStart(2, "0")}`;
- return p.trim();
- };
- if (parts.length >= 2) {
- return { begin: normOne(parts[0]), end: normOne(parts[1]) };
- }
- return { begin: "", end: "" };
- }
- /** 与 subjectInfo:身份证 OCR 响应解析 */
- function pickIdCardPortraitOcrFields(res: any): {
- name: string;
- idNumber: string;
- idCardAddress: string;
- cardPeriodBegin: string;
- cardPeriodEnd: string;
- } {
- const raw = res?.data ?? res;
- let node: any = raw;
- if (node && typeof node === "object" && "data" in node && node.data !== undefined) {
- node = node.data;
- }
- const arr = Array.isArray(node) ? node : [node];
- const item = arr[0];
- const item2 = arr[1];
- if (!item || typeof item !== "object") {
- return { name: "", idNumber: "", idCardAddress: "", cardPeriodBegin: "", cardPeriodEnd: "" };
- }
- let name = String(item.name ?? item.userName ?? "").trim();
- let idNumber = String(item.idNumber ?? item.id_number ?? item.idCard ?? item.id_card ?? "").trim();
- const faceData = item.face?.data || item2?.face?.data;
- const backData = item.back?.data || item2?.back?.data;
- if (faceData && typeof faceData === "object") {
- if (!name) name = String(faceData.name ?? "").trim();
- if (!idNumber) idNumber = String(faceData.idNumber ?? faceData.id_number ?? "").trim();
- }
- let idCardAddress = "";
- if (backData && typeof backData === "object") {
- idCardAddress = String(backData.address ?? backData.registeredAddress ?? backData.issueAuthority ?? "").trim();
- }
- if (faceData && typeof faceData === "object" && !idCardAddress) {
- idCardAddress = String(faceData.address ?? "").trim();
- }
- const faceKv = item.face?.prism_keyValueInfo || item2?.face?.prism_keyValueInfo;
- if (Array.isArray(faceKv)) {
- for (const row of faceKv) {
- if (row?.key === "name" && row?.value && !name) name = String(row.value).trim();
- if (row?.key === "idNumber" && row?.value && !idNumber) idNumber = String(row.value).trim();
- if (row?.key === "address" && row?.value && !idCardAddress) {
- idCardAddress = String(row.value).trim();
- }
- }
- }
- const backKv = item.back?.prism_keyValueInfo || item2?.back?.prism_keyValueInfo;
- let validPeriodFromKv = "";
- if (Array.isArray(backKv)) {
- for (const row of backKv) {
- if (row?.key === "address" && row?.value && !idCardAddress) {
- idCardAddress = String(row.value).trim();
- }
- if (row?.value) {
- const keyRaw = String(row.key || "");
- if (/有效期限|有效期|失效日期|签发日期|validPeriod|validDate|valid_period/i.test(keyRaw)) {
- const v = String(row.value).trim();
- if (v && !validPeriodFromKv) validPeriodFromKv = v;
- }
- }
- }
- }
- let vp = String(
- (backData && (backData.validPeriod ?? backData.validDate)) ||
- (faceData && (faceData.validPeriod ?? faceData.validDate)) ||
- (item && (item.validPeriod ?? item.validDate)) ||
- (item2 && (item2.validPeriod ?? item2.validDate)) ||
- ""
- ).trim();
- if (!vp && validPeriodFromKv) vp = validPeriodFromKv;
- const { begin: cardPeriodBegin, end: cardPeriodEnd } = splitIdCardValidPeriod(vp);
- return { name, idNumber, idCardAddress, cardPeriodBegin, cardPeriodEnd };
- }
- function isOcrSuccess(res: any): boolean {
- return res?.code === 200 || res?.code === "200";
- }
- /** 上传成功后按类型调用 OCR 并写入表单(与 subjectInfo 请求体一致:imageFile + ocrType) */
- async function runOcrAfterZfbUpload(kind: UploadKind, file: File) {
- if (kind === "license") {
- const formData = new FormData();
- formData.append("imageFile", file, file.name || "license.jpg");
- formData.append("ocrType", "BUSINESS_LICENSE");
- const res: any = await getOcrRequestByBase64(formData);
- if (!isOcrSuccess(res)) {
- throw new Error(String(res?.msg || "营业执照识别未通过,请核对照片清晰度"));
- }
- const fields = pickBusinessLicenseOcrFields(res);
- if (fields.creditCode) form.creditCode = fields.creditCode;
- if (form.subjectType === "07") {
- if (fields.companyName) {
- form.householdLicenseName = fields.companyName;
- form.merchantShortName = fields.companyName;
- }
- } else {
- if (fields.companyName) form.merchantShortName = fields.companyName;
- if (fields.legalPerson) form.legalName = fields.legalPerson;
- }
- return;
- }
- if (kind === "institutionCert") {
- const formData = new FormData();
- formData.append("imageFile", file, file.name || "institution-cert.jpg");
- formData.append("ocrType", "BUSINESS_LICENSE");
- const res: any = await getOcrRequestByBase64(formData);
- if (!isOcrSuccess(res)) {
- throw new Error(String(res?.msg || "事业单位法人证书识别未通过,请核对照片清晰度"));
- }
- const f = pickInstitutionCertOcrFields(res);
- if (f.certNumber) form.institutionCertNumber = f.certNumber;
- if (f.orgName) form.merchantShortName = f.orgName;
- return;
- }
- if (kind === "idFront") {
- const formData = new FormData();
- formData.append("imageFile", file, file.name || "id-portrait.jpg");
- formData.append("ocrType", "ID_CARD");
- const res: any = await getOcrRequestByBase64(formData);
- if (!isOcrSuccess(res)) {
- throw new Error(String(res?.msg || "身份证人像面识别未通过,请核对照片清晰度"));
- }
- const f = pickIdCardPortraitOcrFields(res);
- if (f.name) form.legalName = f.name;
- if (f.idNumber) form.idNumber = f.idNumber;
- if (f.cardPeriodBegin && f.cardPeriodEnd) {
- form.businessTermRange = [f.cardPeriodBegin, f.cardPeriodEnd];
- }
- return;
- }
- if (kind === "idBack") {
- const formData = new FormData();
- formData.append("imageFile", file, file.name || "id-emblem.jpg");
- formData.append("ocrType", "ID_CARD");
- const res: any = await getOcrRequestByBase64(formData);
- if (!isOcrSuccess(res)) {
- throw new Error(String(res?.msg || "身份证国徽面识别未通过,请核对照片清晰度"));
- }
- const f = pickIdCardPortraitOcrFields(res);
- if (f.cardPeriodBegin && f.cardPeriodEnd) {
- form.businessTermRange = [f.cardPeriodBegin, f.cardPeriodEnd];
- } else if (!f.cardPeriodBegin && !f.cardPeriodEnd) {
- throw new Error("未识别到有效期限,请确认国徽面照片清晰完整");
- }
- }
- }
- function urlFieldFor(kind: UploadKind): keyof typeof form {
- const map: Record<UploadKind, keyof typeof form> = {
- license: "licenseUrl",
- idFront: "idFrontUrl",
- idBack: "idBackUrl",
- storefront: "storefrontUrl",
- interior: "interiorUrl",
- institutionCert: "institutionCertUrl",
- otherOrgCert: "otherOrgCertUrl",
- socialCert: "socialCertUrl",
- govCert: "govCertUrl"
- };
- return map[kind];
- }
- function fileListForUploadKind(kind: UploadKind): UploadUserFile[] {
- const map: Record<UploadKind, UploadUserFile[]> = {
- license: form.licenseFileList,
- idFront: form.idFrontFileList,
- idBack: form.idBackFileList,
- storefront: form.storefrontFileList,
- interior: form.interiorFileList,
- institutionCert: form.institutionCertFileList,
- otherOrgCert: form.otherOrgCertFileList,
- socialCert: form.socialCertFileList,
- govCert: form.govCertFileList
- };
- return map[kind] ?? [];
- }
- function setFileListForUploadKind(kind: UploadKind, list: UploadUserFile[]) {
- if (kind === "license") form.licenseFileList = list;
- else if (kind === "idFront") form.idFrontFileList = list;
- else if (kind === "idBack") form.idBackFileList = list;
- else if (kind === "storefront") form.storefrontFileList = list;
- else if (kind === "interior") form.interiorFileList = list;
- else if (kind === "institutionCert") form.institutionCertFileList = list;
- else if (kind === "otherOrgCert") form.otherOrgCertFileList = list;
- else if (kind === "socialCert") form.socialCertFileList = list;
- else if (kind === "govCert") form.govCertFileList = list;
- }
- async function handleSingleUpload(options: UploadRequestOptions, kind: UploadKind) {
- const uploadFileItem = options.file as UploadUserFile;
- const uid = uploadFileItem.uid;
- const raw = uploadFileItem.raw || uploadFileItem;
- const file = raw instanceof File ? raw : null;
- const field = urlFieldFor(kind);
- if (!file) {
- setFileListForUploadKind(kind, filterOutUploadUserFileByUid(fileListForUploadKind(kind), uid));
- options.onError(new Error("无效文件") as any);
- return;
- }
- uploadFileItem.status = "uploading";
- try {
- if (file.size > MAX_ALIPAY_IMAGE_BYTES) {
- setFileListForUploadKind(kind, filterOutUploadUserFileByUid(fileListForUploadKind(kind), uid));
- ElMessage.error("文件大小不能超过 20M");
- options.onError(new Error("文件大小不能超过 20M") as any);
- return;
- }
- const needOcr = kind === "license" || kind === "idFront" || kind === "idBack" || kind === "institutionCert";
- const { fileUrl } = needOcr
- ? await uploadBusinessInfoImageWithPageOcr(file, f => runOcrAfterZfbUpload(kind, f), {
- showUploadOverlay: true
- })
- : await uploadBusinessInfoImageToOss(file, { showUploadOverlay: true });
- const resolvedImageId = extractAlipayFilenameFromImageUrl(fileUrl) || fileUrl;
- if (needOcr) {
- nextTick(() => {
- const f = formRef.value;
- if (!f) return;
- if (kind === "license") {
- f.validateField("creditCode").catch(() => {});
- f.validateField("merchantShortName").catch(() => {});
- if (form.subjectType === "07") {
- f.validateField("householdLicenseName").catch(() => {});
- } else {
- f.validateField("legalName").catch(() => {});
- }
- } else if (kind === "institutionCert") {
- f.validateField("institutionCertNumber").catch(() => {});
- f.validateField("merchantShortName").catch(() => {});
- } else if (kind === "idFront") {
- f.validateField("legalName").catch(() => {});
- f.validateField("idNumber").catch(() => {});
- f.validateField("businessTermRange").catch(() => {});
- } else if (kind === "idBack") {
- f.validateField("businessTermRange").catch(() => {});
- }
- });
- }
- uploadFileItem.status = "success";
- uploadFileItem.url = fileUrl;
- uploadFileItem.response = { media_id: resolvedImageId, url: fileUrl };
- (form as any)[field] = fileUrl;
- if (kind === "storefront") {
- form.storefrontImageId = resolvedImageId;
- }
- if (kind === "interior") {
- form.interiorImageId = resolvedImageId;
- }
- if (kind === "license") {
- form.licenseImageId = resolvedImageId;
- }
- options.onSuccess({ url: fileUrl } as any);
- } catch (err) {
- failBusinessInfoUploadCleanup({
- fileList: fileListForUploadKind(kind),
- setFileList: list => setFileListForUploadKind(kind, list),
- uid,
- onClear: () => clearZfbUploadFieldsForKind(kind),
- error: err
- });
- options.onError(err as any);
- }
- formRef.value?.validateField(field as string).catch(() => {});
- }
- function clearZfbUploadFieldsForKind(kind: UploadKind) {
- const field = urlFieldFor(kind);
- (form as any)[field] = "";
- if (kind === "storefront") form.storefrontImageId = "";
- if (kind === "interior") form.interiorImageId = "";
- if (kind === "license") form.licenseImageId = "";
- }
- function onSingleRemove(fileList: UploadUserFile[], kind: UploadKind) {
- const field = urlFieldFor(kind);
- if (!fileList.length) {
- (form as any)[field] = "";
- if (kind === "storefront") form.storefrontImageId = "";
- if (kind === "interior") form.interiorImageId = "";
- if (kind === "license") form.licenseImageId = "";
- } else {
- const first = fileList[0] as UploadUserFile & {
- url?: string;
- response?: { url?: string; media_id?: string };
- };
- (form as any)[field] = first?.url || first?.response?.url || "";
- if (kind === "storefront") {
- form.storefrontImageId = String(first?.response?.media_id || "").trim();
- }
- if (kind === "interior") {
- form.interiorImageId = String(first?.response?.media_id || "").trim();
- }
- if (kind === "license") {
- form.licenseImageId = String(first?.response?.media_id || "").trim();
- }
- }
- formRef.value?.validateField(field as string).catch(() => {});
- }
- /** alipayJson.service 仅传接入类型的 value(当面付 / App支付),不把 face_to_face、app 等内部码传给接口 */
- function serviceValuesForAlipayPayload(selected: string[]): string[] {
- const codeToValue: Record<string, string> = {
- face_to_face: "当面付",
- app: "App支付"
- };
- const allowed = new Set(serviceTypeOptions.map(o => o.value));
- const out: string[] = [];
- const seen = new Set<string>();
- for (const s of selected) {
- const v = codeToValue[s] ?? (allowed.has(s) ? s : "");
- if (v && !seen.has(v)) {
- seen.add(v);
- out.push(v);
- }
- }
- return out;
- }
- /** 店铺地址写入 alipayJson;行政区划码由省前缀、市 adCode 推导(仅省/市二级时 DistrictCode 与 CityCode 相同) */
- function buildBusinessAddressForAlipayJson():
- | {
- address: string;
- districtCode: string;
- cityCode: string;
- provinceCode: string;
- }
- | undefined {
- const region = form.region;
- if (!Array.isArray(region) || region.length < 2) return undefined;
- const provPrefix = String(region[0] ?? "").replace(/\D/g, "");
- const cityAd = String(region[1] ?? "").replace(/\D/g, "");
- if (!provPrefix || cityAd.length < 6) return undefined;
- const prov = provinceCityOptions.find(p => p.value === region[0]);
- const provinceName = prov?.label ?? "";
- const cityName = prov?.cities.find(c => c.value === region[1])?.label ?? "";
- const detail = String(form.detailAddress || "").trim();
- const address = `${provinceName}${cityName}${detail}`.trim();
- const provinceCode = provPrefix.padEnd(6, "0").slice(0, 6);
- const cityCode = cityAd.padStart(6, "0").slice(0, 6);
- const districtCode = cityCode;
- return { address, districtCode, cityCode, provinceCode };
- }
- /** 进件用图片标识:支付宝 image_id 或 OSS 地址中的文件名(*.jpg / *.png) */
- function alipayImageIdFromUploadOnly(imageIdField: string): string {
- const s = String(imageIdField || "").trim();
- if (!s) return "";
- if (/^https?:\/\//i.test(s)) {
- return extractAlipayFilenameFromImageUrl(s) || "";
- }
- const lower = s.toLowerCase();
- if (lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".png")) return s;
- return "";
- }
- /** 进件接口要求数组:有图为单元素数组,无图为 [] */
- function alipayImageIdArrayForPayload(imageIdField: string): string[] {
- const id = alipayImageIdFromUploadOnly(imageIdField);
- return id ? [id] : [];
- }
- /** 从 localStorage 读取的 alipayJson 可能是对象或历史字符串 */
- function normalizeAlipayJsonFromStorage(raw: unknown): Record<string, unknown> | null {
- if (raw == null || raw === "") return null;
- if (typeof raw === "string") {
- try {
- const o = JSON.parse(raw) as unknown;
- return typeof o === "object" && o !== null && !Array.isArray(o) ? (o as Record<string, unknown>) : null;
- } catch {
- return null;
- }
- }
- if (typeof raw === "object" && !Array.isArray(raw)) return raw as Record<string, unknown>;
- return null;
- }
- function findCategoryPathByMcc(mcc: string): string[] | undefined {
- const m = String(mcc || "").trim();
- if (!m) return undefined;
- for (const p of categoryCascaderOptions) {
- const ch = p.children?.find(c => c.value === m);
- if (ch) return [p.value, ch.value];
- }
- return undefined;
- }
- function firstImageIdFromAlipayField(val: unknown): string {
- if (Array.isArray(val) && val.length > 0) return String(val[0] ?? "").trim();
- if (typeof val === "string") return val.trim();
- return "";
- }
- /** 将 businessAddress 反填为省市区 cascader + 详细地址(与 buildBusinessAddressForAlipayJson 对称) */
- function regionAndDetailFromBusinessAddress(ba: unknown): { region: string[]; detailAddress: string } | undefined {
- if (!ba || typeof ba !== "object") return undefined;
- const o = ba as Record<string, unknown>;
- const provinceCode = String(o.provinceCode ?? "").replace(/\D/g, "");
- const cityCode = String(o.cityCode ?? "").replace(/\D/g, "");
- const address = String(o.address ?? "").trim();
- if (provinceCode.length < 2 || cityCode.length < 6) return undefined;
- const prefix = provinceCode.slice(0, 2);
- const prov = provinceCityOptions.find(p => p.value === prefix);
- if (!prov) return undefined;
- const normCity = cityCode.padStart(6, "0").slice(0, 6);
- const city = prov.cities.find(c => {
- const ad = String(c.value ?? "").replace(/\D/g, "");
- const tail = ad.length >= 6 ? ad.slice(-6) : ad;
- return tail.padStart(6, "0") === normCity;
- });
- if (!city) return undefined;
- let detail = address;
- const prefixStr = `${prov.label}${city.label}`;
- if (address.startsWith(prefixStr)) detail = address.slice(prefixStr.length).trim();
- return { region: [prefix, city.value], detailAddress: detail };
- }
- /** alipayJson.service 为「当面付」「App支付」等;兼容旧内部码 */
- function serviceTypesFromAlipayService(val: unknown): string[] {
- const allowed = new Set(serviceTypeOptions.map(o => o.value));
- const legacy: Record<string, string> = {
- face_to_face: "当面付",
- app: "App支付"
- };
- const arr = Array.isArray(val) ? val : typeof val === "string" && val.trim() ? [val] : [];
- const out: string[] = [];
- const seen = new Set<string>();
- for (const item of arr) {
- let s = String(item ?? "").trim();
- if (s.toLowerCase() === "app 支付") s = "App支付";
- const mapped = legacy[s] ?? s;
- if (mapped && allowed.has(mapped) && !seen.has(mapped)) {
- seen.add(mapped);
- out.push(mapped);
- }
- }
- return out;
- }
- /** 进入本页时把缓存中的 alipayJson 写回第一步表单 */
- function applyAlipayJsonCacheToForm(data: Record<string, unknown>) {
- const st = String(data.merchantType ?? "").trim();
- if (st) form.subjectType = st;
- const name = String(data.name ?? data.aliasName ?? "").trim();
- if (name) form.merchantShortName = name;
- const mcc = String(data.mcc ?? "").trim();
- const path = findCategoryPathByMcc(mcc);
- if (path) form.businessCategory = path;
- const certNo = String(data.certNo ?? "").trim();
- if (certNo) form.creditCode = certNo;
- const certImage = String(data.certImage ?? "").trim();
- if (certImage) form.licenseImageId = certImage;
- const legalName = String(data.legalName ?? "").trim();
- if (legalName) form.legalName = legalName;
- const legalCertNo = String(data.legalCertNo ?? "").trim();
- if (legalCertNo) form.idNumber = legalCertNo;
- const certName = String(data.certName ?? "").trim();
- if (certName && form.subjectType === "07") form.householdLicenseName = certName;
- const alipayId = String(data.alipayLogonId ?? data.bindingAlipayLogonId ?? data.BindingAlipayLogonId ?? "").trim();
- if (alipayId) form.settlementAlipayAccount = alipayId;
- const ci = data.contactInfos;
- if (Array.isArray(ci) && ci.length > 0) {
- const c0 = ci[0] as Record<string, unknown>;
- const cn = String(c0?.name ?? "").trim();
- const cm = String(c0?.mobile ?? "").trim();
- if (cn) form.contactName = cn;
- if (cm) form.contactPhone = cm;
- }
- const svc = serviceTypesFromAlipayService(data.service);
- if (svc.length) form.serviceTypes = svc;
- const inId = firstImageIdFromAlipayField(data.inDoorImages);
- if (inId) form.interiorImageId = inId;
- const outId = firstImageIdFromAlipayField(data.outDoorImages);
- if (outId) form.storefrontImageId = outId;
- const ba = regionAndDetailFromBusinessAddress(data.businessAddress);
- if (ba) {
- form.region = ba.region;
- form.detailAddress = ba.detailAddress;
- }
- const sites = data.sites;
- if (Array.isArray(sites) && sites.length > 0) {
- const s0 = sites[0] as Record<string, unknown>;
- const sn = String(s0?.siteName ?? s0?.siteName ?? "").trim();
- if (sn) form.appName = sn;
- }
- }
- /** 下一步写入 localStorage,供后续支付宝进件等使用 */
- function buildAlipayJsonPayload() {
- const mcc =
- Array.isArray(form.businessCategory) && form.businessCategory.length >= 2
- ? form.businessCategory[1]
- : Array.isArray(form.businessCategory) && form.businessCategory.length === 1
- ? form.businessCategory[0]
- : "";
- const businessAddress = buildBusinessAddressForAlipayJson();
- const certName = form.subjectType === "07" ? String(form.householdLicenseName || "").trim() : "";
- const alipayLogonIdVal = String(form.settlementAlipayAccount || "").trim();
- const hasFaceToFace = form.serviceTypes.includes("当面付");
- const hasAppPay = form.serviceTypes.includes("App支付");
- const siteNameForPayload = String(form.appName || "").trim() || DEFAULT_ALIPAY_APP_SITE_NAME;
- return {
- type: 0,
- externalId: randomAlipayExternalId16(),
- name: form.merchantShortName,
- aliasName: form.merchantShortName,
- merchantType: form.subjectType,
- mcc,
- certNo: form.creditCode,
- certType: 201,
- certImage: alipayImageIdFromUploadOnly(form.licenseImageId),
- legalName: form.legalName,
- legalCertNo: form.idNumber,
- certName,
- alipayLogonId: alipayLogonIdVal,
- contactInfos: [
- {
- name: String(form.contactName || "").trim(),
- mobile: String(form.contactPhone || "").trim()
- }
- ],
- service: serviceValuesForAlipayPayload(form.serviceTypes),
- inDoorImages: alipayImageIdArrayForPayload(form.interiorImageId),
- outDoorImages: alipayImageIdArrayForPayload(form.storefrontImageId),
- ...(businessAddress ? { businessAddress } : {}),
- ...(hasFaceToFace && hasAppPay
- ? {
- sites: [{ siteType: "02", siteName: siteNameForPayload }]
- }
- : {})
- };
- }
- onMounted(async () => {
- const cached = normalizeAlipayJsonFromStorage(localGet(ALIPAY_JSON_KEY));
- if (cached) applyAlipayJsonCacheToForm(cached);
- await nextTick();
- formRef.value?.clearValidate();
- });
- const rules: FormRules = {
- subjectType: [{ required: true, message: "请选择主体类型", trigger: "change" }],
- serviceTypes: [
- {
- type: "array",
- required: true,
- min: 1,
- message: "请选择接入服务类型",
- trigger: "change"
- }
- ],
- licenseUrl: [
- {
- validator: (_r, _v, cb) => {
- if (form.subjectType !== "01" && form.subjectType !== "07") return cb();
- if (!String(form.licenseUrl || "").trim()) cb(new Error("请上传营业执照"));
- else cb();
- },
- trigger: "change"
- }
- ],
- creditCode: [
- {
- validator: (_r, _v, cb) => {
- if (form.subjectType !== "01" && form.subjectType !== "07") return cb();
- const v = String(form.creditCode || "").trim();
- if (!v) {
- cb(new Error(form.subjectType === "07" ? "请输入营业执照编号" : "请输入营业执照号"));
- } else if (v.length < 15 || v.length > 32) cb(new Error("长度一般为 18 位统一社会信用代码"));
- else cb();
- },
- trigger: "blur"
- }
- ],
- householdLicenseName: [
- {
- validator: (_r, _v, cb) => {
- if (form.subjectType !== "07") return cb();
- if (!String(form.householdLicenseName || "").trim()) cb(new Error("请输入营业执照名称"));
- else cb();
- },
- trigger: "blur"
- }
- ],
- merchantShortName: [
- {
- validator: (_r, _v, cb) => {
- if (!["01", "02", "03", "04", "05", "06", "07"].includes(form.subjectType)) return cb();
- if (!String(form.merchantShortName || "").trim()) cb(new Error("请输入商户简称"));
- else cb();
- },
- trigger: "blur"
- }
- ],
- institutionCertUrl: [
- {
- validator: (_r, _v, cb) => {
- if (form.subjectType !== "institution") return cb();
- if (!String(form.institutionCertUrl || "").trim()) cb(new Error("请上传事业单位法人证书"));
- else cb();
- },
- trigger: "change"
- }
- ],
- institutionCertNumber: [
- {
- validator: (_r, _v, cb) => {
- if (form.subjectType !== "institution") return cb();
- if (!String(form.institutionCertNumber || "").trim()) cb(new Error("请输入事业单位法人证书编号"));
- else cb();
- },
- trigger: "blur"
- }
- ],
- otherOrgCertUrl: [
- {
- validator: (_r, _v, cb) => {
- if (form.subjectType !== "other_org") return cb();
- if (!String(form.otherOrgCertUrl || "").trim()) cb(new Error("请上传民办非企业单位登记证书"));
- else cb();
- },
- trigger: "change"
- }
- ],
- otherOrgCertNumber: [
- {
- validator: (_r, _v, cb) => {
- if (form.subjectType !== "other_org") return cb();
- if (!String(form.otherOrgCertNumber || "").trim()) cb(new Error("请输入民办非企业单位登记证书编号"));
- else cb();
- },
- trigger: "blur"
- }
- ],
- socialCertUrl: [
- {
- validator: (_r, _v, cb) => {
- if (form.subjectType !== "social") return cb();
- if (!String(form.socialCertUrl || "").trim()) cb(new Error("请上传社会团体法人登记证书"));
- else cb();
- },
- trigger: "change"
- }
- ],
- socialCertNumber: [
- {
- validator: (_r, _v, cb) => {
- if (form.subjectType !== "social") return cb();
- if (!String(form.socialCertNumber || "").trim()) cb(new Error("请输入社会团体法人登记证书编号"));
- else cb();
- },
- trigger: "blur"
- }
- ],
- govCertUrl: [
- {
- validator: (_r, _v, cb) => {
- if (form.subjectType !== "gov") return cb();
- if (!String(form.govCertUrl || "").trim()) {
- cb(new Error("请上传党政机关批准设立文件/行政执法主体资格证"));
- } else cb();
- },
- trigger: "change"
- }
- ],
- govCertNumber: [
- {
- validator: (_r, _v, cb) => {
- if (form.subjectType !== "gov") return cb();
- if (!String(form.govCertNumber || "").trim()) {
- cb(new Error("请输入党政机关批准设立文件/行政执法主体资格证编号"));
- } else cb();
- },
- trigger: "blur"
- }
- ],
- businessCategory: [
- {
- validator: (_r, _v, cb) => {
- if (!["01", "02", "03", "04", "05", "06", "07"].includes(form.subjectType)) return cb();
- const bc = form.businessCategory;
- if (!Array.isArray(bc) || bc.length < 2 || !String(bc[0] || "").trim() || !String(bc[1] || "").trim()) {
- cb(new Error("请选择经营类目(需选择到二级分类)"));
- } else cb();
- },
- trigger: "change"
- }
- ],
- legalIdType: [
- {
- validator: (_r, _v, cb) => {
- if (!["01", "institution", "other_org", "social"].includes(form.subjectType)) return cb();
- if (!String(form.legalIdType || "").trim()) cb(new Error("请选择证件类型"));
- else cb();
- },
- trigger: "change"
- }
- ],
- idFrontUrl: [
- {
- validator: (_r, _v, cb) => {
- if (!["01", "institution", "other_org", "social", "individual_merchant"].includes(form.subjectType)) return cb();
- if (!String(form.idFrontUrl || "").trim()) {
- cb(new Error(form.subjectType === "individual_merchant" ? "请上传身份证人像面" : "请上传证件正面"));
- } else cb();
- },
- trigger: "change"
- }
- ],
- idBackUrl: [
- {
- validator: (_r, _v, cb) => {
- if (!["01", "institution", "other_org", "social", "individual_merchant"].includes(form.subjectType)) return cb();
- if (!String(form.idBackUrl || "").trim()) {
- cb(new Error(form.subjectType === "individual_merchant" ? "请上传身份证国徽面" : "请上传证件反面"));
- } else cb();
- },
- trigger: "change"
- }
- ],
- idNumber: [
- {
- validator: (_r, _v, cb) => {
- if (!["01", "institution", "other_org", "social", "individual_merchant"].includes(form.subjectType)) return cb();
- if (!String(form.idNumber || "").trim()) {
- cb(new Error(form.subjectType === "individual_merchant" ? "请输入个人身份证号码" : "请输入证件号"));
- } else cb();
- },
- trigger: "blur"
- }
- ],
- legalName: [
- {
- validator: (_r, _v, cb) => {
- if (!["01", "institution", "other_org", "social"].includes(form.subjectType)) return cb();
- if (!String(form.legalName || "").trim()) cb(new Error("请输入法定代表人/负责人姓名"));
- else cb();
- },
- trigger: "blur"
- }
- ],
- storefrontUrl: [{ required: true, message: "请上传门头照", trigger: "change" }],
- interiorUrl: [{ required: true, message: "请上传内景照", trigger: "change" }],
- region: [{ type: "array", required: true, min: 2, message: "请选择省、市", trigger: "change" }],
- detailAddress: [{ required: true, message: "请输入详细地址", trigger: "blur" }],
- contactName: [{ required: true, message: "请输入联系人姓名", trigger: "blur" }],
- contactPhone: [
- { required: true, message: "请输入联系人手机号", trigger: "blur" },
- { pattern: /^1\d{10}$/, message: "请输入有效手机号", trigger: "blur" }
- ],
- contactEmail: [
- { required: true, message: "请输入联系人邮箱", trigger: "blur" },
- { type: "email", message: "邮箱格式不正确", trigger: "blur" }
- ],
- servicePhone: [
- { required: true, message: "请输入客服电话", trigger: "blur" },
- { pattern: /^[\d\-+\s]{5,20}$/, message: "请输入有效电话号码", trigger: "blur" }
- ],
- settlementAlipayAccount: [{ required: true, message: "请输入签约支付宝账号", trigger: "blur" }]
- };
- async function onNext() {
- if (!formRef.value) return;
- nextLoading.value = true;
- try {
- localSet(ALIPAY_JSON_KEY, buildAlipayJsonPayload());
- await router.push({ path: "/businessInfo/zfbIndexTwo", query: { ...route.query } });
- } catch {
- ElMessage.warning("请完善必填信息");
- } finally {
- nextLoading.value = false;
- }
- }
- </script>
- <style scoped lang="scss">
- $zfb-blue: #1677ff;
- .zfb-step-page {
- box-sizing: border-box;
- min-height: 100%;
- padding: 24px 24px 48px;
- margin: 0 auto;
- background: #ffffff;
- }
- .step-header {
- margin-right: -24px;
- margin-bottom: 28px;
- margin-left: -24px;
- }
- .zfb-steps {
- width: 100%;
- margin: 0;
- }
- .step-header :deep(.zfb-steps.el-steps--horizontal) {
- .el-step__icon {
- width: 36px;
- height: 36px;
- font-size: 16px;
- font-weight: 600;
- background: #ffffff !important;
- }
- .el-step__head.is-process .el-step__icon {
- color: #ffffff !important;
- background: #1677ff !important;
- border-color: $zfb-blue !important;
- }
- .el-step__head.is-wait .el-step__icon {
- color: #c0c4cc !important;
- background: #ffffff !important;
- border: 2px solid #dcdfe6 !important;
- }
- .el-step__head.is-finish .el-step__icon {
- color: #ffffff !important;
- background: $zfb-blue !important;
- border-color: $zfb-blue !important;
- }
- .el-step__line {
- background-color: #e4e7ed !important;
- }
- .el-step__line-inner {
- border-width: 1px !important;
- }
- .el-step__title {
- font-size: 14px;
- line-height: 36px;
- color: #606266;
- }
- .el-step__head.is-process + .el-step__main .el-step__title {
- font-weight: 600;
- color: #303133;
- }
- .el-step__head.is-wait + .el-step__main .el-step__title {
- color: #c0c4cc;
- }
- .el-step__head.is-finish + .el-step__main .el-step__title {
- font-weight: 500;
- color: #606266;
- }
- }
- .page-layout {
- display: flex;
- gap: 32px;
- align-items: flex-start;
- }
- .main-column {
- flex: 1;
- min-width: 0;
- /* 滚到底时 max(scrollTop) 往往仍小于「经营信息」的锚点判定线,侧栏会一直停在前一节;底部留白增大可滚动距离 */
- padding-bottom: clamp(140px, 22vh, 400px);
- }
- .anchor-column {
- flex-shrink: 0;
- width: 160px;
- }
- .anchor-card {
- padding: 16px 12px;
- }
- :deep(.el-anchor__list) {
- border-left: 1px solid #ebeef5;
- }
- .anchor-title {
- margin-bottom: 12px;
- font-size: 13px;
- font-weight: 600;
- color: #303133;
- }
- .zfb-form {
- :deep(.el-form-item__label) {
- font-weight: 500;
- color: #606266;
- }
- }
- .form-block {
- padding-bottom: 8px;
- margin-bottom: 28px;
- scroll-margin-top: 72px;
- border-bottom: 1px solid #ebeef5;
- &:last-of-type {
- border-bottom: none;
- }
- }
- .section-head {
- display: flex;
- align-items: center;
- margin: 0 0 16px;
- font-size: 16px;
- font-weight: 600;
- color: #303133;
- }
- .section-bar {
- display: inline-block;
- flex-shrink: 0;
- width: 4px;
- height: 16px;
- margin-right: 8px;
- background: $zfb-blue;
- border-radius: 2px;
- }
- .radio-stack {
- display: flex;
- gap: 10px;
- align-items: flex-start;
- }
- .label-with-icon {
- display: inline-flex;
- gap: 4px;
- align-items: center;
- }
- .label-wrap-multiline {
- display: inline-block;
- max-width: 196px;
- line-height: 1.45;
- white-space: normal;
- vertical-align: middle;
- }
- .individual-merchant-alert {
- max-width: 410px;
- margin-bottom: 20px;
- margin-left: 200px;
- }
- .info-icon {
- color: #909399;
- cursor: help;
- }
- .license-upload-wrap {
- display: flex;
- flex-wrap: wrap;
- gap: 16px;
- align-items: flex-start;
- }
- .license-example {
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
- .license-example-label {
- font-size: 12px;
- color: #909399;
- }
- .license-example-thumb {
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 148px;
- height: 148px;
- overflow: hidden;
- background: #f5f7fa;
- border: 1px dashed #dcdfe6;
- border-radius: 6px;
- }
- .license-example-placeholder {
- font-size: 12px;
- color: #c0c4cc;
- }
- .id-example-thumb {
- position: relative;
- }
- .license-example-badge {
- position: absolute;
- top: 6px;
- right: 6px;
- padding: 2px 6px;
- font-size: 11px;
- color: #ffffff;
- background: rgb(0 0 0 / 45%);
- border-radius: 4px;
- }
- .id-upload-row {
- display: flex;
- flex-wrap: wrap;
- gap: 24px;
- }
- .id-upload-cell {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .id-upload-trigger {
- display: flex;
- flex-direction: column;
- gap: 6px;
- align-items: center;
- justify-content: center;
- line-height: 1.2;
- }
- .id-side-label {
- margin-top: 10px;
- font-size: 13px;
- color: #606266;
- }
- .id-upload-trigger .id-side-label {
- font-size: 12px;
- line-height: 1.3;
- text-align: center;
- }
- .upload-rules {
- margin-top: 12px;
- font-size: 12px;
- line-height: 1.65;
- color: #909399;
- p {
- margin: 0;
- }
- }
- .field-stack {
- width: 100%;
- }
- .field-tip {
- margin: 8px 0 0;
- font-size: 12px;
- line-height: 1.65;
- color: #909399;
- }
- .service-checks {
- display: flex;
- flex-wrap: wrap;
- gap: 12px 20px;
- }
- .address-row {
- display: flex;
- flex-wrap: wrap;
- gap: 12px;
- align-items: center;
- width: 100%;
- max-width: 720px;
- }
- .address-detail {
- flex: 1;
- min-width: 200px;
- }
- .footer-actions {
- display: flex;
- flex-wrap: wrap;
- gap: 16px;
- align-items: center;
- justify-content: center;
- padding-top: 24px;
- margin-top: 8px;
- border-top: 1px solid #ebeef5;
- }
- .btn-primary-zfb {
- min-width: 120px;
- background: $zfb-blue;
- border-color: $zfb-blue;
- &:hover {
- background: #4096ff;
- border-color: #4096ff;
- }
- }
- :deep(.el-upload--picture-card) {
- --el-upload-picture-card-size: 148px;
- }
- :deep(.el-upload-list--picture-card .el-upload-list__item) {
- width: 148px;
- height: 148px;
- }
- :deep(.el-form-item__content) {
- display: block;
- }
- :deep(.anchor-card .el-anchor) {
- /* 实际滚动在 layout 的 el-main 上,不设 container 时监听 window,高亮不会随滚动更新 */
- --el-anchor-color: #606266;
- --el-anchor-active-color: #{$zfb-blue};
- --el-anchor-marker-bg-color: #{$zfb-blue};
- background: transparent;
- }
- :deep(.anchor-card .el-anchor__link) {
- padding: 6px 0;
- font-size: 13px;
- }
- :deep(.anchor-card .el-anchor__link.is-active) {
- font-weight: 600;
- }
- </style>
|