go-flow.vue 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774
  1. <template>
  2. <div class="form-container">
  3. <div>
  4. <!-- 返回按钮 -->
  5. <el-button class="back-btn" @click="handleBack"> 返回 </el-button>
  6. <!-- 进度条 -->
  7. <div class="progress-container">
  8. <el-steps :active="currentStep" style="max-width: 1500px" align-center>
  9. <el-step v-for="(item, index) in entryList" :key="index">
  10. <template #title>
  11. <div class="step-title-wrapper">
  12. <span class="step-title">{{ item.title }}</span>
  13. </div>
  14. </template>
  15. </el-step>
  16. </el-steps>
  17. </div>
  18. </div>
  19. <!-- 第一步:个人实名 - 身份证正反面上传 -->
  20. <div v-if="currentStep === 1" class="step1-content">
  21. <div class="form-content">
  22. <h3 class="section-title">身份证正反面</h3>
  23. <div class="id-card-upload-container">
  24. <!-- 正面 -->
  25. <div class="upload-item">
  26. <div class="upload-label">正面</div>
  27. <el-upload
  28. v-model:file-list="idCardFrontList"
  29. :http-request="handleHttpUpload"
  30. list-type="picture-card"
  31. :limit="1"
  32. :on-exceed="handleExceed"
  33. :on-success="(response, file) => handleUploadSuccess(response, file, 'ID_CARD', '身份证')"
  34. :on-preview="handlePictureCardPreview"
  35. :on-remove="handleRemove"
  36. accept="image/*"
  37. class="id-card-upload"
  38. :class="{ 'upload-complete': idCardFrontList.length > 0 }"
  39. >
  40. <template v-if="idCardFrontList.length === 0">
  41. <div class="upload-placeholder">
  42. <!-- <el-image src="/src/assets/images/idCard1.png" /> -->
  43. <span class="placeholder-text">身份证正面示例-带有国徽一面</span>
  44. </div>
  45. </template>
  46. </el-upload>
  47. </div>
  48. <!-- 反面 -->
  49. <div class="upload-item">
  50. <div class="upload-label">反面</div>
  51. <el-upload
  52. v-model:file-list="idCardBackList"
  53. :http-request="handleHttpUpload"
  54. list-type="picture-card"
  55. :limit="1"
  56. :on-exceed="handleExceed"
  57. :on-success="(response, file) => handleUploadSuccess(response, file, 'ID_CARD', '身份证')"
  58. :on-preview="handlePictureCardPreview"
  59. :on-remove="handleRemove"
  60. accept="image/*"
  61. class="id-card-upload"
  62. :class="{ 'upload-complete': idCardBackList.length > 0 }"
  63. >
  64. <template v-if="idCardBackList.length === 0">
  65. <div class="upload-placeholder">
  66. <span class="placeholder-text">
  67. <span>身份证反面示例-带有人像一面</span>
  68. </span>
  69. </div>
  70. </template>
  71. </el-upload>
  72. </div>
  73. </div>
  74. <!-- OCR 识别结果展示 -->
  75. <div class="ocr-result-container" v-if="isIdCardUploadComplete">
  76. <div class="ocr-result-item" v-if="isOcrProcessing">
  77. <span class="label">识别中:</span>
  78. <span class="value">正在识别身份证信息,请稍候...</span>
  79. </div>
  80. <template v-else>
  81. <div class="ocr-result-item" v-if="ocrResult.name">
  82. <span class="label">姓名:</span>
  83. <span class="value">{{ ocrResult.name }}</span>
  84. </div>
  85. <div class="ocr-result-item" v-if="ocrResult.idCard">
  86. <span class="label">身份证号:</span>
  87. <span class="value">{{ ocrResult.idCard }}</span>
  88. </div>
  89. <div class="ocr-result-tip" v-if="!ocrResult.name && !ocrResult.idCard">请等待身份证识别完成</div>
  90. </template>
  91. </div>
  92. </div>
  93. <!-- 按钮 -->
  94. <div class="form-actions">
  95. <el-button type="primary" size="large" @click="handleNextStep"> 下一步 </el-button>
  96. </div>
  97. </div>
  98. <!-- 第二步:填写信息 -->
  99. <div v-if="currentStep === 2">
  100. <!-- 表单内容 -->
  101. <div class="form-content step2-form">
  102. <el-form :model="step2Form" :rules="step2Rules" ref="step2FormRef" label-width="125px">
  103. <div class="form-row">
  104. <!-- 左列 -->
  105. <div class="form-col">
  106. <el-form-item label="店铺名称" prop="storeName">
  107. <el-input v-model="step2Form.storeName" placeholder="请输入店铺名称" maxlength="30" />
  108. </el-form-item>
  109. <el-form-item label="容纳人数" prop="storeCapacity">
  110. <el-input-number v-model="step2Form.storeCapacity" :min="1" :max="9999" />
  111. </el-form-item>
  112. <el-form-item label="门店面积" prop="storeArea">
  113. <el-radio-group v-model="step2Form.storeArea">
  114. <el-radio label="小于20平米" value="1"> 小于20平米 </el-radio>
  115. <el-radio label="20-50平米" value="2"> 20-50平米 </el-radio>
  116. <el-radio label="50-100平米" value="3"> 50-100平米 </el-radio>
  117. <el-radio label="100-300平米" value="4"> 100-300平米 </el-radio>
  118. <el-radio label="500-1000平米" value="5"> 500-1000平米 </el-radio>
  119. <el-radio label="大于1000平米" value="6"> 大于1000平米 </el-radio>
  120. </el-radio-group>
  121. </el-form-item>
  122. <el-form-item label="所在地区" prop="region">
  123. <el-cascader :props="areaProps" v-model="step2Form.region" style="width: 100%" />
  124. </el-form-item>
  125. <el-form-item label="详细地址" prop="storeDetailAddress">
  126. <el-input v-model="step2Form.storeDetailAddress" type="textarea" :rows="3" placeholder="请输入" maxlength="255" />
  127. </el-form-item>
  128. <el-form-item label="门店简介" prop="storeBlurb">
  129. <el-input v-model="step2Form.storeBlurb" type="textarea" :rows="3" placeholder="请输入" maxlength="300" />
  130. </el-form-item>
  131. <el-form-item label="经营板块" prop="businessSection">
  132. <el-radio-group v-model="step2Form.businessSection" @change="changeBusinessSection">
  133. <el-radio
  134. v-for="businessSection in businessSectionList"
  135. :value="businessSection.dictId"
  136. :key="businessSection.dictId"
  137. >
  138. {{ businessSection.dictDetail }}
  139. </el-radio>
  140. </el-radio-group>
  141. </el-form-item>
  142. <el-form-item label="标签" prop="storeTickets" v-if="showDisportLicence">
  143. <el-radio-group v-model="step2Form.storeTickets">
  144. <el-radio
  145. v-for="businessSection in businessLabelList"
  146. :value="businessSection.dictId"
  147. :key="businessSection.dictId"
  148. >
  149. {{ businessSection.dictDetail }}
  150. </el-radio>
  151. </el-radio-group>
  152. </el-form-item>
  153. <!-- 经营种类 -->
  154. <el-form-item label="经营种类" prop="businessTypeName">
  155. <el-input v-model="step2Form.businessTypeName" type="textarea" :rows="3" placeholder="请输入" maxlength="255" />
  156. </el-form-item>
  157. <!-- 经营类目 -->
  158. <el-form-item label="经营类目" prop="businessCategoryName">
  159. <el-input
  160. v-model="step2Form.businessCategoryName"
  161. type="textarea"
  162. :rows="3"
  163. placeholder="请输入"
  164. maxlength="255"
  165. />
  166. </el-form-item>
  167. </div>
  168. <!-- 右列 -->
  169. <div class="form-col">
  170. <el-form-item label="门店营业状态" prop="businessType">
  171. <el-radio-group v-model="step2Form.businessType">
  172. <el-radio label="正常营业"> 正常营业 </el-radio>
  173. <el-radio label="暂停营业"> 暂停营业 </el-radio>
  174. <el-radio label="筹建中"> 筹建中 </el-radio>
  175. </el-radio-group>
  176. </el-form-item>
  177. <el-form-item label="经度" prop="storePositionLongitude" v-show="latShow">
  178. <el-input disabled v-model="step2Form.storePositionLongitude" placeholder="请填写经度" clearable />
  179. </el-form-item>
  180. <el-form-item label="纬度" prop="storePositionLatitude" v-show="latShow">
  181. <el-input disabled v-model="step2Form.storePositionLatitude" placeholder="请填写纬度" clearable />
  182. </el-form-item>
  183. <el-form-item label="经纬度查询" prop="address">
  184. <el-select
  185. v-model="step2Form.address"
  186. filterable
  187. placeholder="请输入地址进行查询"
  188. remote
  189. reserve-keyword
  190. :remote-method="getLonAndLat"
  191. @change="selectAddress"
  192. >
  193. <el-option v-for="item in addressList" :key="item.id" :label="item.name" :value="item.location">
  194. <span style="float: left">{{ item.name }}</span>
  195. <span style="float: right; font-size: 13px; color: var(--el-text-color-secondary)">{{ item.district }}</span>
  196. </el-option>
  197. </el-select>
  198. </el-form-item>
  199. <!-- 店铺评价(三项绑定 storePj 数组,校验才能通过) -->
  200. <el-form-item label="店铺评价" prop="storePj" required>
  201. <div class="store-pj-inputs">
  202. <el-input v-model="step2Form.storePj[0]" placeholder="请输入" maxlength="2" clearable class="store-pj-input" />
  203. <el-input v-model="step2Form.storePj[1]" maxlength="2" placeholder="请输入" clearable class="store-pj-input" />
  204. <el-input v-model="step2Form.storePj[2]" placeholder="请输入" maxlength="2" clearable class="store-pj-input" />
  205. </div>
  206. </el-form-item>
  207. <el-form-item label="预订服务">
  208. <el-radio-group v-model="step2Form.appointment">
  209. <el-radio label="提供"> 提供 </el-radio>
  210. <el-radio label="不提供"> 不提供 </el-radio>
  211. </el-radio-group>
  212. </el-form-item>
  213. <el-form-item label="营业时间">
  214. <div style="color: #6c8ff8; cursor: pointer" @click="openBusinessHours">
  215. {{ hasBusinessHoursConfig ? "已添加" : "添加" }}
  216. </div>
  217. </el-form-item>
  218. <el-form-item label="营业执照" prop="businessLicenseAddress">
  219. <el-upload
  220. v-model:file-list="step2Form.businessLicenseAddress"
  221. :http-request="handleHttpUpload"
  222. list-type="picture-card"
  223. :limit="1"
  224. :on-exceed="handleExceed"
  225. :on-success="(response, file) => handleUploadSuccess(response, file, 'BUSINESS_LICENSE', '营业执照')"
  226. :on-preview="handlePictureCardPreview"
  227. >
  228. <el-icon><Plus /></el-icon>
  229. <template #tip>
  230. <div class="el-upload__tip">({{ step2Form.businessLicenseAddress.length }}/1)</div>
  231. </template>
  232. </el-upload>
  233. </el-form-item>
  234. <el-form-item label="其他资质证明" prop="disportLicenceImgList">
  235. <el-upload
  236. v-model:file-list="step2Form.disportLicenceImgList"
  237. :http-request="handleHttpUpload"
  238. :limit="20"
  239. list-type="picture-card"
  240. :on-exceed="handleExceed"
  241. :on-success="(response, file) => handleUploadSuccess(response, file, 'BUSINESS_LICENSE', '其他资质证明')"
  242. :on-preview="handlePictureCardPreview"
  243. >
  244. <el-icon><Plus /></el-icon>
  245. <template #tip>
  246. <div class="el-upload__tip">已上传 {{ step2Form.disportLicenceImgList.length }} 张(最多20张)</div>
  247. </template>
  248. </el-upload>
  249. </el-form-item>
  250. <el-form-item label="设置收款账号" @click="handleSetPayAccount" style="cursor: pointer">
  251. <div class="pay-account-trigger">
  252. <el-icon color="#666" class="pay-account-trigger__arrow">
  253. <ArrowRightBold />
  254. </el-icon>
  255. </div>
  256. </el-form-item>
  257. </div>
  258. </div>
  259. </el-form>
  260. </div>
  261. <!-- 按钮 -->
  262. <div class="form-actions">
  263. <el-button type="primary" size="large" @click="handleSubmit"> 提交 </el-button>
  264. </div>
  265. </div>
  266. </div>
  267. <!-- 营业时间弹窗(入驻:仅缓存,关闭按钮) -->
  268. <GoBusinessHours
  269. v-model="businessHoursDialogVisible"
  270. :cache-only="true"
  271. @success="hasBusinessHoursConfig = true"
  272. @has-config="(v: boolean) => (hasBusinessHoursConfig = v)"
  273. />
  274. <el-dialog
  275. v-model="payAccountDialogVisible"
  276. width="420px"
  277. align-center
  278. append-to-body
  279. destroy-on-close
  280. class="pay-account-setup-dialog"
  281. :show-close="true"
  282. @open="onPayAccountDialogOpen"
  283. >
  284. <template #header>
  285. <div class="pay-account-setup-dialog__title">设置收款账号</div>
  286. </template>
  287. <div class="pay-account-option-list">
  288. <button
  289. v-for="opt in payAccountOptions"
  290. :key="opt.value"
  291. type="button"
  292. class="pay-account-option"
  293. :class="{ 'is-active': payAccountTemp === opt.value }"
  294. @click="payAccountTemp = opt.value"
  295. >
  296. <span class="pay-account-option__icon" :class="`pay-account-option__icon--${opt.value}`" aria-hidden="true" />
  297. <span class="pay-account-option__label">{{ opt.label }}</span>
  298. <span class="pay-account-option__radio" :class="{ 'is-checked': payAccountTemp === opt.value }">
  299. <el-icon v-if="payAccountTemp === opt.value" class="pay-account-option__check"><Check /></el-icon>
  300. </span>
  301. </button>
  302. </div>
  303. <template #footer>
  304. <el-button type="primary" class="pay-account-setup-dialog__confirm" @click="confirmPayAccount"> 确定 </el-button>
  305. </template>
  306. </el-dialog>
  307. <!-- 图片预览 -->
  308. <el-image-viewer
  309. v-if="imageViewerVisible"
  310. :url-list="imageViewerUrlList"
  311. :initial-index="imageViewerInitialIndex"
  312. @close="imageViewerVisible = false"
  313. />
  314. </template>
  315. <script setup lang="ts">
  316. import { ref, reactive, watch, onMounted, computed, toRaw } from "vue";
  317. import { ElMessage, ElMessageBox, type FormInstance, type FormRules, UploadUserFile, UploadRequestOptions } from "element-plus";
  318. import { Plus, ArrowRightBold, Check } from "@element-plus/icons-vue";
  319. import { applyStore, getMerchantByPhone, getThirdLevelList, verifyIdInfo } from "@/api/modules/homeEntry";
  320. import { getInputPrompt, getDistrict, uploadImg, ocrRequestUrl, getAiapprovestoreInfo } from "@/api/modules/newLoginApi";
  321. import GoBusinessHours from "./go-businessHours.vue";
  322. import { localGet, localSet } from "@/utils/index";
  323. import { useAuthStore } from "@/stores/modules/auth";
  324. import { useRouter } from "vue-router";
  325. const authStore = useAuthStore();
  326. const router = useRouter();
  327. const userInfo = localGet("geeker-user")?.userInfo || {};
  328. const latShow = ref(false);
  329. const showDisportLicence = ref(false);
  330. // 图片预览相关
  331. const imageViewerVisible = ref(false);
  332. const imageViewerUrlList = ref<string[]>([]);
  333. const imageViewerInitialIndex = ref(0);
  334. // 营业时间
  335. const businessHoursDialogVisible = ref(false);
  336. const hasBusinessHoursConfig = ref(false);
  337. const openBusinessHours = () => {
  338. businessHoursDialogVisible.value = true;
  339. };
  340. /** 收款方式:与弹窗选项一致,默认支付宝(与设计稿一致) */
  341. type ReceivePayType = "alipay" | "wechat" | "bank";
  342. const payAccountOptions: { value: ReceivePayType; label: string }[] = [
  343. { value: "alipay", label: "支付宝支付" },
  344. { value: "wechat", label: "微信支付" }
  345. ];
  346. const payAccountDialogVisible = ref(false);
  347. const payAccountTemp = ref<ReceivePayType>("alipay");
  348. const entryList = ref([
  349. {
  350. title: "个人实名"
  351. },
  352. {
  353. title: "填写信息"
  354. },
  355. {
  356. title: "等待审核"
  357. },
  358. {
  359. title: "入驻成功"
  360. }
  361. ]);
  362. const businessLabelList = ref<any[]>([
  363. {
  364. dictId: 1,
  365. dictDetail: "装修公司"
  366. },
  367. {
  368. dictId: 0,
  369. dictDetail: "其他类型"
  370. }
  371. ]);
  372. const businessSectionList = ref<any[]>([
  373. {
  374. dictId: 1,
  375. dictDetail: "特色美食"
  376. },
  377. {
  378. dictId: 2,
  379. dictDetail: "休闲娱乐"
  380. },
  381. {
  382. dictId: 3,
  383. dictDetail: "生活服务"
  384. }
  385. ]);
  386. // 身份证正反面上传列表
  387. const idCardFrontList = ref<UploadUserFile[]>([]);
  388. const idCardBackList = ref<UploadUserFile[]>([]);
  389. // OCR 识别结果
  390. const ocrResult = ref<{
  391. name?: string;
  392. idCard?: string;
  393. }>({});
  394. // 食品经营许可证到期时间(从 OCR 结果中提取)
  395. const foodLicenceExpirationTime = ref<string>("");
  396. // 其他资质证明到期时间(从 OCR 结果中提取)
  397. const entertainmentLicenceExpirationTime = ref<string>("");
  398. // 是否正在识别中
  399. const isOcrProcessing = ref(false);
  400. // OCR识别状态:'success' | 'failed' | 'none'(未识别)
  401. const businessLicenseOcrStatus = ref<"success" | "failed" | "none">("none"); // 营业执照OCR状态
  402. const foodLicenseOcrStatus = ref<"success" | "failed" | "none">("none"); // 食品经营许可证OCR状态
  403. const entertainmentLicenseOcrStatus = ref<"success" | "failed" | "none">("none"); // 其他资质证明OCR状态
  404. // 日期格式转换函数:支持两种格式
  405. // 1. "20220508" -> "2022-05-08"
  406. // 2. "2024年01月14日" -> "2024-01-14"
  407. const formatDate = (dateStr: string): string => {
  408. if (!dateStr) {
  409. return "";
  410. }
  411. // 处理中文日期格式:2024年01月14日
  412. if (dateStr.includes("年") && dateStr.includes("月") && dateStr.includes("日")) {
  413. const match = dateStr.match(/(\d{4})年(\d{1,2})月(\d{1,2})日/);
  414. if (match) {
  415. const year = match[1];
  416. const month = match[2].padStart(2, "0"); // 补零,确保是两位数
  417. const day = match[3].padStart(2, "0"); // 补零,确保是两位数
  418. return `${year}-${month}-${day}`;
  419. }
  420. }
  421. // 处理8位数字格式:20220508
  422. if (dateStr.length === 8 && /^\d{8}$/.test(dateStr)) {
  423. const year = dateStr.substring(0, 4);
  424. const month = dateStr.substring(4, 6);
  425. const day = dateStr.substring(6, 8);
  426. return `${year}-${month}-${day}`;
  427. }
  428. return "";
  429. };
  430. // 计算是否已上传完成(正反面都上传完成)
  431. const isIdCardUploadComplete = computed(() => {
  432. return idCardFrontList.value.length > 0 && idCardBackList.value.length > 0;
  433. });
  434. // 下一步 - 验证身份证正反面是否已上传
  435. const handleNextStep = async () => {
  436. // 识别成功,进入下一步
  437. // try {
  438. // const res: any = await verifyIdInfo({
  439. // idCard: ocrResult.value.idCard,
  440. // name: ocrResult.value.name,
  441. // appType: 1
  442. // });
  443. // if (res.code === 200) {
  444. // ElMessage.success("身份证识别成功");
  445. // setStep(2);
  446. // } else {
  447. // ElMessage.error(res?.msg || "身份证识别失败");
  448. // idCardFrontList.value = [];
  449. // }
  450. // } catch (error: any) {
  451. // console.log(error);
  452. // }
  453. ElMessage.success("身份证识别成功");
  454. setStep(2);
  455. };
  456. const step2Rules: FormRules = {
  457. storeName: [{ required: true, message: "请输入店铺名称", trigger: "blur" }],
  458. storeCapacity: [{ required: true, message: "请输入容纳人数", trigger: "blur" }],
  459. storeArea: [{ required: true, message: "请选择门店面积", trigger: "change" }],
  460. storeBlurb: [{ required: true, message: "请输入门店简介", trigger: "change" }],
  461. storeDetailAddress: [{ required: true, message: "请输入详细地址", trigger: "blur" }],
  462. businessSection: [{ required: true, message: "请选择经营板块", trigger: "change" }],
  463. storeTickets: [
  464. {
  465. required: true,
  466. message: "请选择标签",
  467. trigger: "change",
  468. validator: (_rule: any, value: any, callback: (err?: Error) => void) => {
  469. // 仅当经营板块为「生活服务」时校验标签必选
  470. if (step2Form.businessSection != 3) {
  471. callback();
  472. return;
  473. }
  474. if (value !== "" && value !== undefined && value !== null) {
  475. callback();
  476. } else {
  477. callback(new Error("请选择标签"));
  478. }
  479. }
  480. }
  481. ],
  482. businessTypeName: [{ required: true, message: "请输入经营种类", trigger: "change" }],
  483. businessCategoryName: [{ required: true, message: "请选择经营类目", trigger: "change" }],
  484. address: [{ required: true, message: "请输入经纬度", trigger: "blur" }],
  485. businessLicenseAddress: [{ required: true, message: "请上传营业执照", trigger: "change" }],
  486. storePj: [
  487. {
  488. required: true,
  489. message: "请完整填写三项店铺评价",
  490. trigger: "blur",
  491. validator: (_rule: any, value: any, callback: (err?: Error) => void) => {
  492. const list = Array.isArray(value) ? value : [];
  493. const filled = list.length >= 3 && list.every(v => v != null && String(v).trim() !== "");
  494. if (filled) callback();
  495. else callback(new Error("请完整填写三项店铺评价"));
  496. }
  497. }
  498. ]
  499. // disportLicenceImgList: [{ required: true, message: "请上传其他资质证明", trigger: "change" }]
  500. };
  501. //地址集合
  502. const addressList = ref<any[]>([]);
  503. //查询地址名称
  504. const queryAddress = ref<string>("");
  505. const props = defineProps({
  506. currentStep: {
  507. type: Number,
  508. default: 1
  509. },
  510. storeApplicationStatus: {
  511. type: Number,
  512. default: 0
  513. }
  514. });
  515. const emit = defineEmits(["update:currentStep", "update:get-user-info"]);
  516. // 调用父组件的 getUserInfo 方法
  517. const callGetUserInfo = () => {
  518. emit("update:get-user-info");
  519. };
  520. // 内部步骤状态,和父组件同步
  521. const currentStep = ref<number>(props.currentStep || 1);
  522. const storeApplicationStatus = ref<number>(props.storeApplicationStatus || 0);
  523. watch(
  524. () => props.currentStep,
  525. val => {
  526. if (typeof val === "number") currentStep.value = val;
  527. }
  528. );
  529. watch(
  530. () => props.storeApplicationStatus,
  531. val => {
  532. if (typeof val === "number") storeApplicationStatus.value = val;
  533. }
  534. );
  535. const changeBusinessSection = () => {
  536. if (step2Form.businessSection == 3) {
  537. // 生活服务:显示标签,默认选「其他类型」(dictId=0)
  538. showDisportLicence.value = true;
  539. (step2Form as { storeTickets: string | number }).storeTickets = 0;
  540. } else {
  541. // 非生活服务:隐藏标签,不选任何类型
  542. showDisportLicence.value = false;
  543. step2Form.storeTickets = "";
  544. }
  545. };
  546. // 隐藏财务管理菜单的函数
  547. const hideFinancialManagementMenu = () => {
  548. const hideMenus = (menuList: any[]) => {
  549. menuList.forEach(menu => {
  550. if (menu.name && menu.name === "financialManagement") {
  551. menu.meta.isHide = true;
  552. }
  553. if (menu.children && menu.children.length > 0) {
  554. hideMenus(menu.children);
  555. }
  556. });
  557. };
  558. if (authStore.authMenuList && authStore.authMenuList.length > 0) {
  559. hideMenus(authStore.authMenuList);
  560. }
  561. };
  562. // 显示财务管理菜单的函数
  563. const showFinancialManagementMenu = () => {
  564. const showMenus = (menuList: any[]) => {
  565. menuList.forEach(menu => {
  566. if (menu.name && menu.name === "financialManagement") {
  567. menu.meta.isHide = false;
  568. }
  569. if (menu.children && menu.children.length > 0) {
  570. showMenus(menu.children);
  571. }
  572. });
  573. };
  574. if (authStore.authMenuList && authStore.authMenuList.length > 0) {
  575. showMenus(authStore.authMenuList);
  576. }
  577. };
  578. // 更新缓存中的 storeId
  579. const updateStoreIdInCache = async () => {
  580. try {
  581. const geekerUser = localGet("geeker-user");
  582. if (!geekerUser || !geekerUser.userInfo || !geekerUser.userInfo.phone) {
  583. console.error("用户信息不存在");
  584. return;
  585. }
  586. const phone = geekerUser.userInfo.phone;
  587. const res: any = await getMerchantByPhone({ phone });
  588. if (res && res.code == 200 && res.data && res.data.storeId) {
  589. geekerUser.userInfo.storeId = res.data.storeId;
  590. localSet("geeker-user", geekerUser);
  591. if (res.data.storeId) {
  592. localSet("createdId", res.data.storeId);
  593. }
  594. }
  595. } catch (error) {
  596. console.error("更新 storeId 缓存失败:", error);
  597. }
  598. };
  599. // 监听步骤和审核状态
  600. watch([() => currentStep.value, () => storeApplicationStatus.value], ([step, status]) => {
  601. if (step === 3 && (status === 0 || status === 2)) {
  602. updateStoreIdInCache();
  603. }
  604. if (status === 2) {
  605. hideFinancialManagementMenu();
  606. }
  607. if (status === 1) {
  608. showFinancialManagementMenu();
  609. }
  610. });
  611. // 监听菜单列表变化
  612. watch(
  613. () => authStore.authMenuList.length,
  614. newLength => {
  615. if (newLength > 0) {
  616. if (storeApplicationStatus.value === 2) {
  617. hideFinancialManagementMenu();
  618. }
  619. if (storeApplicationStatus.value === 1) {
  620. showFinancialManagementMenu();
  621. }
  622. }
  623. }
  624. );
  625. onMounted(() => {
  626. callGetUserInfo();
  627. if (currentStep.value === 3 && (storeApplicationStatus.value === 0 || storeApplicationStatus.value === 2)) {
  628. updateStoreIdInCache();
  629. }
  630. if (storeApplicationStatus.value === 2) {
  631. hideFinancialManagementMenu();
  632. } else if (storeApplicationStatus.value === 1) {
  633. showFinancialManagementMenu();
  634. }
  635. });
  636. const setStep = (val: number) => {
  637. currentStep.value = val;
  638. emit("update:currentStep", val);
  639. };
  640. // 第二步表单
  641. const step2FormRef = ref<FormInstance>();
  642. const step2Form = reactive({
  643. appointment: "",
  644. businessSecondMeal: 1,
  645. storeName: "",
  646. storeCapacity: 1,
  647. storeArea: "1",
  648. isChain: 0,
  649. storeDetailAddress: "",
  650. region: [],
  651. administrativeRegionProvinceAdcode: "",
  652. administrativeRegionCityAdcode: "",
  653. administrativeRegionDistrictAdcode: "",
  654. storeAddress: "",
  655. storeBlurb: "",
  656. businessSection: 1,
  657. storeTickets: "",
  658. businessSecondLevel: [] as string[],
  659. businessTypes: "" as string,
  660. businessTypesList: [] as string[],
  661. businessStatus: 0,
  662. storeStatus: 1,
  663. businessType: "正常营业",
  664. storePositionLongitude: "",
  665. storePositionLatitude: "",
  666. businessLicenseAddress: [] as UploadUserFile[],
  667. contractImageList: [] as UploadUserFile[],
  668. foodLicenceImgList: [] as UploadUserFile[],
  669. disportLicenceImgList: [] as UploadUserFile[],
  670. address: "",
  671. // 经营种类
  672. businessTypeName: "",
  673. // 经营类目
  674. businessCategoryName: "",
  675. // 店铺评价(三项,与 prop="storePj" 对应,校验规则会检查此数组)
  676. storePj: ["", "", ""] as string[],
  677. /** 设置收款账号(弹窗选择,提交时带给 saveStoreInfo,后端可按需接收) */
  678. receivePayType: "alipay" as ReceivePayType
  679. });
  680. const payAccountLabel = computed(() => {
  681. const v = step2Form.receivePayType;
  682. return payAccountOptions.find(o => o.value === v)?.label ?? "支付宝支付";
  683. });
  684. const handleSetPayAccount = () => {
  685. payAccountDialogVisible.value = true;
  686. };
  687. const onPayAccountDialogOpen = () => {
  688. payAccountTemp.value = step2Form.receivePayType;
  689. };
  690. const confirmPayAccount = () => {
  691. step2Form.receivePayType = payAccountTemp.value;
  692. payAccountDialogVisible.value = false;
  693. if (payAccountTemp.value === "alipay") {
  694. router.push({ path: "/businessInfo/zfbIndex" });
  695. } else if (payAccountTemp.value === "wechat") {
  696. router.push({ path: "/businessInfo/weChartIndex" });
  697. }
  698. };
  699. // 返回按钮
  700. const handleBack = () => {
  701. if (currentStep.value === 1) {
  702. setStep(0);
  703. } else if (currentStep.value === 2) {
  704. setStep(1);
  705. } else if (currentStep.value === 3) {
  706. setStep(2);
  707. }
  708. };
  709. // 地区选择
  710. const areaProps: any = {
  711. lazy: true,
  712. async lazyLoad(node, resolve) {
  713. const { level } = node;
  714. try {
  715. let param = { adCode: node.data.adCode ? node.data.adCode : "" };
  716. const response: any = await getDistrict(param as any);
  717. const nodes = (response?.data?.districts?.[0]?.districts || []).map((item: any) => ({
  718. value: item.adcode,
  719. adCode: item.adcode,
  720. label: item.name,
  721. leaf: level >= 2
  722. }));
  723. resolve(nodes);
  724. } catch (error) {
  725. resolve([]);
  726. }
  727. }
  728. };
  729. watch(
  730. () => step2Form.region,
  731. (newVal: any[]) => {
  732. if (newVal.length > 0) {
  733. step2Form.administrativeRegionProvinceAdcode = newVal[0];
  734. step2Form.administrativeRegionCityAdcode = newVal[1];
  735. step2Form.administrativeRegionDistrictAdcode = newVal[2];
  736. }
  737. }
  738. );
  739. // 二级分类列表
  740. const secondLevelList = ref<any[]>([]);
  741. // 三级分类列表
  742. const thirdLevelList = ref<any[]>([]);
  743. // 二级分类变化时,加载三级分类
  744. const changeBusinessSecondLevel = async (dictId: string | number | boolean | undefined) => {
  745. const dictIdStr = String(dictId || "");
  746. if (!dictIdStr) {
  747. thirdLevelList.value = [];
  748. step2Form.businessTypes = "";
  749. step2Form.businessTypesList = [];
  750. return;
  751. }
  752. // 清空三级分类(但不清空 businessTypes,因为这是用户刚选择的值)
  753. thirdLevelList.value = [];
  754. step2Form.businessTypesList = [];
  755. // 加载三级分类
  756. try {
  757. const res: any = await getThirdLevelList(dictIdStr);
  758. if (res && (res.code === 200 || res.code === "200") && res.data && Array.isArray(res.data) && res.data.length > 0) {
  759. thirdLevelList.value = res.data;
  760. } else {
  761. // 如果没有三级分类数据,确保清空
  762. thirdLevelList.value = [];
  763. }
  764. } catch (error) {
  765. console.error("获取三级分类失败:", error);
  766. // 如果没有三级分类,不显示错误,因为可能该二级分类下没有三级分类
  767. // 确保清空三级分类列表
  768. thirdLevelList.value = [];
  769. }
  770. };
  771. // 经纬度查询
  772. const getLonAndLat = async (keyword: string) => {
  773. if (keyword) {
  774. let param = {
  775. addressName: keyword
  776. };
  777. let res: any = await getInputPrompt(param as any);
  778. if (res.code == "200") {
  779. addressList.value = res?.data?.tips || [];
  780. } else {
  781. ElMessage.error("查询失败!");
  782. }
  783. } else {
  784. addressList.value = [];
  785. }
  786. };
  787. const selectAddress = async () => {
  788. if (!step2Form.address || typeof step2Form.address !== "string") {
  789. ElMessage.warning("地址格式不正确,请重新选择");
  790. return;
  791. }
  792. if (!step2Form.address.includes(",")) {
  793. ElMessage.warning("地址格式不正确,缺少经纬度信息");
  794. return;
  795. }
  796. let locationList = step2Form.address.split(",");
  797. if (locationList.length < 2) {
  798. ElMessage.warning("地址格式不正确,无法获取经纬度");
  799. return;
  800. }
  801. addressList.value.forEach((item: any) => {
  802. if (item.location == step2Form.address) {
  803. queryAddress.value = item.name;
  804. }
  805. });
  806. step2Form.storePositionLongitude = locationList[0]?.trim() || "";
  807. step2Form.storePositionLatitude = locationList[1]?.trim() || "";
  808. if (!step2Form.storePositionLongitude || !step2Form.storePositionLatitude) {
  809. ElMessage.warning("无法获取有效的经纬度信息");
  810. return;
  811. }
  812. latShow.value = true;
  813. };
  814. //文件上传
  815. const handleHttpUpload = async (options: UploadRequestOptions) => {
  816. let formData = new FormData();
  817. formData.append("file", options.file);
  818. try {
  819. const res: any = await uploadImg(formData);
  820. const fileUrl = res?.data?.fileUrl || res?.data?.[0] || res?.fileUrl;
  821. if (fileUrl) {
  822. options.onSuccess({ fileUrl });
  823. } else {
  824. throw new Error("上传失败:未获取到文件URL");
  825. }
  826. } catch (error) {
  827. options.onError(error as any);
  828. ElMessage.error("文件上传失败,请重试");
  829. }
  830. };
  831. // 自动调用 OCR 识别
  832. const autoOcrRecognition = async (ocrType: string, name: string) => {
  833. // 如果正在识别中,不重复调用
  834. if (isOcrProcessing.value) {
  835. return;
  836. }
  837. let imageUrls = "";
  838. let fileList: UploadUserFile[] = [];
  839. // 根据不同的 ocrType 获取对应的图片 URL
  840. if (ocrType === "ID_CARD") {
  841. // 身份证:需要检查正反面是否都已上传完成
  842. if (idCardFrontList.value.length === 0 || idCardBackList.value.length === 0) {
  843. return;
  844. }
  845. const frontFile = idCardFrontList.value[0];
  846. const backFile = idCardBackList.value[0];
  847. // 验证上传的文件是否成功
  848. if (frontFile.status !== "success" || !frontFile.url || backFile.status !== "success" || !backFile.url) {
  849. return;
  850. }
  851. // 获取身份证正反面的 URL
  852. const frontUrl = getFileUrls(idCardFrontList.value)[0] || "";
  853. const backUrl = getFileUrls(idCardBackList.value)[0] || "";
  854. if (!frontUrl || !backUrl) {
  855. return;
  856. }
  857. // 将正反面 URL 用逗号分隔
  858. imageUrls = `${frontUrl},${backUrl}`;
  859. } else if (ocrType === "BUSINESS_LICENSE") {
  860. // 营业执照或其他资质证明:检查是否已上传
  861. // 优先检查营业执照,如果没有再检查其他资质证明
  862. let fileList: UploadUserFile[] = [];
  863. if (name == "营业执照") {
  864. fileList = step2Form.businessLicenseAddress;
  865. } else if (name == "其他资质证明") {
  866. fileList = step2Form.disportLicenceImgList;
  867. } else {
  868. return;
  869. }
  870. const file = fileList[0];
  871. if (file.status !== "success" || !file.url) {
  872. return;
  873. }
  874. const fileUrl = getFileUrls(fileList)[0] || "";
  875. if (!fileUrl) {
  876. return;
  877. }
  878. imageUrls = fileUrl;
  879. } else if (ocrType === "FOOD_MANAGE_LICENSE") {
  880. // 食品经营许可证:检查是否已上传
  881. if (step2Form.foodLicenceImgList.length === 0) {
  882. return;
  883. }
  884. const file = step2Form.foodLicenceImgList[0];
  885. if (file.status !== "success" || !file.url) {
  886. return;
  887. }
  888. const fileUrl = getFileUrls(step2Form.foodLicenceImgList)[0] || "";
  889. if (!fileUrl) {
  890. return;
  891. }
  892. imageUrls = fileUrl;
  893. } else {
  894. // 其他类型不进行 OCR
  895. return;
  896. }
  897. let params = {
  898. imageUrls: imageUrls,
  899. ocrType: ocrType,
  900. storeId: userInfo.storeId,
  901. storeUserId: userInfo.id
  902. };
  903. try {
  904. isOcrProcessing.value = true;
  905. const res: any = await ocrRequestUrl(params);
  906. if (res && (res.code === 200 || res.code === "200")) {
  907. // 只有身份证类型才需要保存识别结果到 ocrResult
  908. if (ocrType === "ID_CARD" && res.data && Array.isArray(res.data) && res.data.length > 0) {
  909. // 从第一个元素获取正面或反面数据
  910. const firstItem = res.data[0];
  911. const secondItem = res.data[1];
  912. // 根据数据键值判断哪一个是正面(face)或反面(back)
  913. const faceData = firstItem?.face?.data || secondItem?.face?.data;
  914. const backData = firstItem?.back?.data || secondItem?.back?.data;
  915. // 提取姓名(从正面数据中获取)
  916. const name = faceData?.name || "";
  917. // 提取身份证号(优先从正面数据中获取,如果没有则从反面获取)
  918. let idCard = faceData?.idNumber || backData?.idNumber || "";
  919. // 如果从 data 中获取不到,尝试从 prism_keyValueInfo 中查找
  920. if (!idCard) {
  921. // 先从正面查找
  922. if (firstItem?.face?.prism_keyValueInfo) {
  923. const idNumberInfo = firstItem.face.prism_keyValueInfo.find((item: any) => item.key === "idNumber" && item.value);
  924. if (idNumberInfo) {
  925. idCard = idNumberInfo.value;
  926. }
  927. }
  928. // 如果正面没有,再从反面查找
  929. if (!idCard && secondItem?.face?.prism_keyValueInfo) {
  930. const idNumberInfo = secondItem.face.prism_keyValueInfo.find((item: any) => item.key === "idNumber" && item.value);
  931. if (idNumberInfo) {
  932. idCard = idNumberInfo.value;
  933. }
  934. }
  935. }
  936. ocrResult.value = {
  937. name: name,
  938. idCard: idCard
  939. };
  940. // 更新本地存储中的用户信息
  941. const geekerUser = localGet("geeker-user");
  942. if (geekerUser && geekerUser.userInfo) {
  943. if (ocrResult.value.name) {
  944. geekerUser.userInfo.name = ocrResult.value.name;
  945. }
  946. if (ocrResult.value.idCard) {
  947. geekerUser.userInfo.idCard = ocrResult.value.idCard;
  948. }
  949. localSet("geeker-user", geekerUser);
  950. }
  951. ElMessage.success("身份证识别成功");
  952. } else if (ocrType === "BUSINESS_LICENSE") {
  953. // 判断是营业执照还是其他资质证明
  954. const isBusinessLicense = step2Form.businessLicenseAddress.length > 0;
  955. // 提取 validToDate 字段(其他资质证明)
  956. if (!isBusinessLicense && res.data && Array.isArray(res.data) && res.data.length > 0) {
  957. const validToDate = res.data[0]?.validToDate || "";
  958. if (validToDate) {
  959. entertainmentLicenceExpirationTime.value = formatDate(validToDate);
  960. }
  961. }
  962. if (name == "营业执照") {
  963. businessLicenseOcrStatus.value = "success";
  964. ElMessage.success("营业执照识别成功");
  965. } else if (name == "其他资质证明") {
  966. entertainmentLicenseOcrStatus.value = "success";
  967. ElMessage.success("其他资质证明识别成功");
  968. }
  969. } else if (ocrType === "FOOD_MANAGE_LICENSE") {
  970. // 提取食品经营许可证的 validToDate 字段
  971. if (res.data && Array.isArray(res.data) && res.data.length > 0) {
  972. const validToDate = res.data[0]?.validToDate || "";
  973. if (validToDate) {
  974. foodLicenceExpirationTime.value = formatDate(validToDate);
  975. }
  976. }
  977. foodLicenseOcrStatus.value = "success";
  978. ElMessage.success("食品经营许可证识别成功");
  979. }
  980. } else {
  981. console.warn("OCR 识别失败:", res?.msg);
  982. if (name === "营业执照") {
  983. businessLicenseOcrStatus.value = "failed";
  984. step2Form.businessLicenseAddress = [];
  985. } else if (name === "其他资质证明") {
  986. entertainmentLicenseOcrStatus.value = "failed";
  987. step2Form.disportLicenceImgList = [];
  988. } else if (ocrType === "FOOD_MANAGE_LICENSE") {
  989. foodLicenseOcrStatus.value = "failed";
  990. step2Form.foodLicenceImgList = [];
  991. } else if (ocrType === "ID_CARD") {
  992. idCardFrontList.value = [];
  993. idCardBackList.value = [];
  994. }
  995. ElMessage.error(res?.msg || "识别失败,已清除该图片,请重新上传");
  996. }
  997. } catch (error) {
  998. if (name === "营业执照") {
  999. businessLicenseOcrStatus.value = "failed";
  1000. step2Form.businessLicenseAddress = [];
  1001. } else if (name === "其他资质证明") {
  1002. entertainmentLicenseOcrStatus.value = "failed";
  1003. step2Form.disportLicenceImgList = [];
  1004. } else if (ocrType === "FOOD_MANAGE_LICENSE") {
  1005. foodLicenseOcrStatus.value = "failed";
  1006. step2Form.foodLicenceImgList = [];
  1007. } else if (ocrType === "ID_CARD") {
  1008. idCardFrontList.value = [];
  1009. idCardBackList.value = [];
  1010. }
  1011. ElMessage.error("识别失败,已清除该图片,请重新上传");
  1012. } finally {
  1013. isOcrProcessing.value = false;
  1014. }
  1015. };
  1016. // 文件上传成功回调
  1017. const handleUploadSuccess = (response: any, uploadFile: UploadUserFile, ocrType: string, name: string) => {
  1018. if (response?.fileUrl) {
  1019. uploadFile.url = response.fileUrl;
  1020. }
  1021. // 其他资质证明不做 OCR,直接返回
  1022. if (name === "其他资质证明") {
  1023. return;
  1024. }
  1025. // 仅对身份证、营业执照、食品许可证做 OCR
  1026. if (ocrType && (ocrType === "ID_CARD" || ocrType === "BUSINESS_LICENSE" || ocrType === "FOOD_MANAGE_LICENSE")) {
  1027. if (name === "营业执照") {
  1028. businessLicenseOcrStatus.value = "none";
  1029. } else if (ocrType === "FOOD_MANAGE_LICENSE") {
  1030. foodLicenseOcrStatus.value = "none";
  1031. }
  1032. // 延迟一下,确保文件状态已更新,然后检查是否需要自动 OCR
  1033. setTimeout(() => {
  1034. autoOcrRecognition(ocrType, name);
  1035. }, 100);
  1036. }
  1037. };
  1038. // 图片预览处理函数
  1039. const handlePictureCardPreview = (file: UploadUserFile) => {
  1040. if (file.status === "uploading" && file.url) {
  1041. imageViewerUrlList.value = [file.url];
  1042. imageViewerInitialIndex.value = 0;
  1043. imageViewerVisible.value = true;
  1044. return;
  1045. }
  1046. let urlList: string[] = [];
  1047. let currentFileList: UploadUserFile[] = [];
  1048. // 判断是哪个上传组件的文件
  1049. if (idCardFrontList.value.some((f: UploadUserFile) => f.uid === file.uid)) {
  1050. currentFileList = idCardFrontList.value;
  1051. } else if (idCardBackList.value.some((f: UploadUserFile) => f.uid === file.uid)) {
  1052. currentFileList = idCardBackList.value;
  1053. } else if (step2Form.businessLicenseAddress.some((f: UploadUserFile) => f.uid === file.uid)) {
  1054. currentFileList = step2Form.businessLicenseAddress;
  1055. } else if (step2Form.contractImageList.some((f: UploadUserFile) => f.uid === file.uid)) {
  1056. currentFileList = step2Form.contractImageList;
  1057. } else if (step2Form.foodLicenceImgList.some((f: UploadUserFile) => f.uid === file.uid)) {
  1058. currentFileList = step2Form.foodLicenceImgList;
  1059. } else if (step2Form.disportLicenceImgList.some((f: UploadUserFile) => f.uid === file.uid)) {
  1060. currentFileList = step2Form.disportLicenceImgList;
  1061. }
  1062. urlList = currentFileList
  1063. .filter((item: UploadUserFile) => item.status === "success" && (item.url || (item.response as any)?.fileUrl))
  1064. .map((item: UploadUserFile) => item.url || (item.response as any)?.fileUrl);
  1065. const currentUrl = file.url || (file.response as any)?.fileUrl;
  1066. const currentIndex = urlList.findIndex((url: string) => url === currentUrl);
  1067. if (currentIndex < 0) {
  1068. ElMessage.warning("图片尚未上传完成,无法预览");
  1069. return;
  1070. }
  1071. imageViewerUrlList.value = urlList;
  1072. imageViewerInitialIndex.value = currentIndex;
  1073. imageViewerVisible.value = true;
  1074. };
  1075. // 文件移除处理
  1076. const handleRemove = (file: UploadUserFile) => {
  1077. // 文件移除时,清空 OCR 识别结果
  1078. ocrResult.value = {};
  1079. isOcrProcessing.value = false;
  1080. // 根据文件所属列表重置对应的OCR状态
  1081. if (step2Form.businessLicenseAddress.some((f: UploadUserFile) => f.uid === file.uid)) {
  1082. // 移除的是营业执照
  1083. businessLicenseOcrStatus.value = "none";
  1084. } else if (step2Form.foodLicenceImgList.some((f: UploadUserFile) => f.uid === file.uid)) {
  1085. // 移除的是食品经营许可证
  1086. foodLicenseOcrStatus.value = "none";
  1087. } else if (step2Form.disportLicenceImgList.some((f: UploadUserFile) => f.uid === file.uid)) {
  1088. // 移除的是其他资质证明
  1089. entertainmentLicenseOcrStatus.value = "none";
  1090. }
  1091. };
  1092. // 提取文件列表中的URL
  1093. const getFileUrls = (fileList: UploadUserFile[]): string[] => {
  1094. return fileList
  1095. .map((file: UploadUserFile) => {
  1096. const response = file.response as any;
  1097. return file.url || response?.fileUrl || "";
  1098. })
  1099. .filter((url: string) => url);
  1100. };
  1101. // 根据adcode获取地区详细信息
  1102. const getDistrictInfo = async (adcode: string) => {
  1103. try {
  1104. const response: any = await getDistrict({ adCode: adcode } as any);
  1105. const district = response?.data?.districts?.[0];
  1106. if (district) {
  1107. return {
  1108. citycode: district.citycode ? [district.citycode] : [],
  1109. adcode: district.adcode,
  1110. level: district.level,
  1111. center: district.center,
  1112. name: district.name,
  1113. districts: []
  1114. };
  1115. }
  1116. } catch (error) {
  1117. console.error("获取地区信息失败:", error);
  1118. }
  1119. return null;
  1120. };
  1121. // 构建whereAddress数组
  1122. const buildWhereAddress = async (regionCodes: string[]) => {
  1123. const whereAddress: any[] = [];
  1124. if (regionCodes && regionCodes.length > 0) {
  1125. for (const code of regionCodes) {
  1126. const districtInfo = await getDistrictInfo(code);
  1127. if (districtInfo) {
  1128. whereAddress.push(districtInfo);
  1129. }
  1130. }
  1131. }
  1132. return whereAddress;
  1133. };
  1134. // 店铺入驻成功后调用 AI 审核接口(参考商家端:后台调用,不阻塞、不向用户展示结果)
  1135. const handleAi = () => {
  1136. const businessLicenseUrls = getFileUrls(step2Form.businessLicenseAddress);
  1137. const contractImageUrls = getFileUrls(step2Form.contractImageList);
  1138. const foodLicenceUrls = getFileUrls(step2Form.foodLicenceImgList);
  1139. const disportLicenceUrls = getFileUrls(step2Form.disportLicenceImgList);
  1140. const licenseImages = [...businessLicenseUrls, ...contractImageUrls, ...foodLicenceUrls, ...disportLicenceUrls].filter(Boolean);
  1141. const params: any = {
  1142. business_scope: step2Form.storeBlurb || "",
  1143. contact_email: "",
  1144. contact_name: userInfo.name || "",
  1145. contact_phone: userInfo.phone || "",
  1146. license_images: licenseImages,
  1147. merchant_name: step2Form.storeName || "",
  1148. userId: userInfo.id || ""
  1149. };
  1150. getAiapprovestoreInfo(params).catch(aiError => {
  1151. console.error("AI店铺审核接口调用失败:", aiError);
  1152. });
  1153. };
  1154. // 提交
  1155. const handleSubmit = async () => {
  1156. if (!step2FormRef.value) return;
  1157. await step2FormRef.value.validate(async valid => {
  1158. if (valid) {
  1159. const businessLicenseUrls = getFileUrls(step2Form.businessLicenseAddress);
  1160. const contractImageUrls = getFileUrls(step2Form.contractImageList);
  1161. const foodLicenceUrls = getFileUrls(step2Form.foodLicenceImgList);
  1162. const disportLicenceUrls = getFileUrls(step2Form.disportLicenceImgList);
  1163. const whereAddress = await buildWhereAddress(step2Form.region);
  1164. const storePosition =
  1165. step2Form.storePositionLongitude && step2Form.storePositionLatitude
  1166. ? `${step2Form.storePositionLongitude},${step2Form.storePositionLatitude}`
  1167. : "";
  1168. const storePositionLatitude = parseFloat(step2Form.storePositionLatitude) || 0;
  1169. const storePositionLongitude = parseFloat(step2Form.storePositionLongitude) || 0;
  1170. let finalBusinessTypes: string[] = [];
  1171. if (thirdLevelList.value.length > 0) {
  1172. if (step2Form.businessTypes) {
  1173. finalBusinessTypes = [step2Form.businessTypes];
  1174. }
  1175. if (
  1176. Array.isArray(step2Form.businessSecondLevel) &&
  1177. step2Form.businessSecondLevel.length > 0 &&
  1178. finalBusinessTypes.length === 0
  1179. ) {
  1180. finalBusinessTypes = step2Form.businessSecondLevel;
  1181. }
  1182. } else {
  1183. if (Array.isArray(step2Form.businessSecondLevel) && step2Form.businessSecondLevel.length > 0) {
  1184. finalBusinessTypes = step2Form.businessSecondLevel;
  1185. } else if (step2Form.businessTypes) {
  1186. finalBusinessTypes = [step2Form.businessTypes];
  1187. } else if (step2Form.businessTypesList.length > 0) {
  1188. finalBusinessTypes = step2Form.businessTypesList;
  1189. }
  1190. }
  1191. const businessTypesListVal = finalBusinessTypes.length > 0 ? finalBusinessTypes[0] : "";
  1192. // const categoryListVal = step2Form.businessCategoryName ? [step2Form.businessCategoryName] : [];
  1193. // 与商家端 storeInfo saveStoreInfo 入参保持一致(storeInfoDto + 提交覆盖项)
  1194. const storeInfoDto: any = {
  1195. updatedTime: null,
  1196. storeTel: userInfo.phone,
  1197. storeName: step2Form.storeName,
  1198. storeCapacity: step2Form.storeCapacity,
  1199. storeArea: typeof step2Form.storeArea === "string" ? parseInt(step2Form.storeArea) : step2Form.storeArea,
  1200. isChain: step2Form.isChain,
  1201. storeAddress: step2Form.storeDetailAddress,
  1202. storeBlurb: step2Form.storeBlurb,
  1203. businessSection: String(step2Form.businessSection),
  1204. businessSectionName:
  1205. step2Form.businessSection == 1 ? "特色美食" : step2Form.businessSection == 2 ? "休闲娱乐" : "生活服务",
  1206. businessTypeName: step2Form.businessTypeName,
  1207. storeStatus: step2Form.businessType === "暂停营业" ? 0 : step2Form.businessType === "筹建中" ? 2 : 1,
  1208. queryAddress: queryAddress.value || "",
  1209. storePosition,
  1210. storePositionLatitude,
  1211. storePositionLongitude,
  1212. // businessTypesList: [businessTypesListVal],
  1213. // businessTypes: businessTypesListVal,
  1214. businessClassify: step2Form.businessCategoryName,
  1215. businessLicenseAddress: businessLicenseUrls,
  1216. businessLicenseUrl: businessLicenseUrls.join(","),
  1217. contractImageList: contractImageUrls,
  1218. foodLicenceUrl: foodLicenceUrls,
  1219. otherQualificationImages: disportLicenceUrls,
  1220. otherLicenses: disportLicenceUrls.join(","),
  1221. storeEvaluate: (step2Form.storePj || []).join(","),
  1222. evaluation1: step2Form.storePj[0],
  1223. evaluation2: step2Form.storePj[1],
  1224. evaluation3: step2Form.storePj[2],
  1225. userAccount: userInfo.id,
  1226. administrativeRegionProvinceAdcode: step2Form.administrativeRegionProvinceAdcode || whereAddress[0]?.adcode || "",
  1227. administrativeRegionProvinceName: whereAddress[0]?.name || "",
  1228. administrativeRegionCityAdcode: step2Form.administrativeRegionCityAdcode || whereAddress[1]?.adcode || "",
  1229. administrativeRegionCityName: whereAddress[1]?.name || "",
  1230. administrativeRegionDistrictAdcode: step2Form.administrativeRegionDistrictAdcode || whereAddress[2]?.adcode || "",
  1231. administrativeRegionDistrictName: whereAddress[2]?.name || "",
  1232. businessStatus: step2Form.businessStatus,
  1233. storeContact: userInfo.name || (localGet("smName") as string) || "",
  1234. idCard: localGet("idCard") || "",
  1235. bookingService: step2Form.appointment === "提供" ? 1 : 0
  1236. };
  1237. storeInfoDto.storeTickets = step2Form.storeTickets;
  1238. // 营业时间:从缓存读取并转为提交格式
  1239. const businessHoursRaw = localGet("geeker-entry-businessHours");
  1240. const businessHoursCopy = Array.isArray(businessHoursRaw)
  1241. ? businessHoursRaw
  1242. : businessHoursRaw && typeof businessHoursRaw === "object" && Array.isArray((businessHoursRaw as any).list)
  1243. ? (businessHoursRaw as any).list
  1244. : [];
  1245. const storeBusinessTime = businessHoursCopy.map((item: any) => {
  1246. const obj: any = {
  1247. businessType: item.businessType,
  1248. startTime: item.startTime,
  1249. endTime: item.endTime,
  1250. storeId: userInfo.storeId ?? null
  1251. };
  1252. if (item.businessType === 2) {
  1253. obj.essentialId = item.holidayId; // geeker-entry-businessHours 缓存里的 holidayId(getHolidayList 返回的 id)
  1254. }
  1255. if (item.businessType === 1) obj.businessDate = item.businessDate;
  1256. return obj;
  1257. });
  1258. const saveStoreInfoParams = {
  1259. ...storeInfoDto,
  1260. foodLicenceUrl: foodLicenceUrls.join(","),
  1261. mealsFlag: step2Form.businessSecondMeal === 1 ? 1 : 0,
  1262. createdUserId: userInfo.id || "",
  1263. storeBusinessTime: storeBusinessTime.length > 0 ? storeBusinessTime : undefined,
  1264. receivePayType: step2Form.receivePayType
  1265. };
  1266. ElMessageBox.confirm("确认提交入驻申请吗?", "提示", {
  1267. confirmButtonText: "确定",
  1268. cancelButtonText: "取消",
  1269. type: "warning"
  1270. })
  1271. .then(async () => {
  1272. try {
  1273. const res: any = await applyStore(saveStoreInfoParams);
  1274. if (res && res.code == 200) {
  1275. storeApplicationStatus.value = 0;
  1276. ElMessage.success(res.msg || "提交成功");
  1277. if (res.data?.id) {
  1278. localSet("createdId", res.data.id);
  1279. }
  1280. callGetUserInfo();
  1281. setStep(0);
  1282. handleAi();
  1283. } else {
  1284. ElMessage.error(res?.msg || "提交失败");
  1285. }
  1286. } catch (error) {
  1287. ElMessage.error("提交失败,请重试");
  1288. }
  1289. })
  1290. .catch(() => {});
  1291. } else {
  1292. ElMessage.error("请完善表单信息");
  1293. }
  1294. });
  1295. };
  1296. // 文件上传超出限制
  1297. const handleExceed = () => {
  1298. ElMessage.warning("文件数量超出限制");
  1299. };
  1300. </script>
  1301. <style>
  1302. .el-dialog__body {
  1303. height: 600px;
  1304. overflow: scroll;
  1305. }
  1306. </style>
  1307. <style scoped lang="scss">
  1308. // 店铺评价三个输入框纵向排列
  1309. .store-pj-inputs {
  1310. display: flex;
  1311. flex-direction: column;
  1312. gap: 12px;
  1313. width: 100%;
  1314. .store-pj-input {
  1315. width: 100%;
  1316. }
  1317. }
  1318. // 表单页面样式
  1319. .form-container {
  1320. min-height: calc(100vh - 100px);
  1321. padding: 30px;
  1322. background: #ffffff;
  1323. border-radius: 8px;
  1324. .back-btn {
  1325. margin-bottom: 30px;
  1326. color: #606266;
  1327. border-color: #dcdfe6;
  1328. }
  1329. .progress-container {
  1330. margin-bottom: 40px;
  1331. :deep(.el-step__head.is-process .el-step__icon) {
  1332. color: #909399;
  1333. border-color: #909399 !important;
  1334. }
  1335. :deep(.el-steps) {
  1336. .is-finish {
  1337. .el-step__icon {
  1338. color: #ffffff;
  1339. background-color: #6c8ff8 !important;
  1340. border-color: #6c8ff8 !important;
  1341. }
  1342. }
  1343. .el-step__head {
  1344. .el-step__icon {
  1345. width: 30px;
  1346. height: 30px;
  1347. font-size: 16px;
  1348. font-weight: 600;
  1349. }
  1350. }
  1351. .el-step__title {
  1352. .step-title-wrapper {
  1353. display: flex;
  1354. flex-direction: column;
  1355. gap: 8px;
  1356. align-items: center;
  1357. .step-title {
  1358. font-size: 16px;
  1359. font-weight: 600;
  1360. color: #6c8ff8;
  1361. }
  1362. }
  1363. }
  1364. }
  1365. }
  1366. // 第一步内容样式
  1367. .step1-content {
  1368. .form-content {
  1369. max-width: 800px;
  1370. margin: 0 auto 40px;
  1371. .section-title {
  1372. margin-bottom: 30px;
  1373. font-size: 18px;
  1374. font-weight: 600;
  1375. color: #303133;
  1376. text-align: center;
  1377. }
  1378. .id-card-upload-container {
  1379. display: flex;
  1380. gap: 40px;
  1381. align-items: flex-start;
  1382. justify-content: center;
  1383. :deep(.el-upload-list--picture-card) {
  1384. width: 100%;
  1385. }
  1386. .upload-item {
  1387. flex: 1;
  1388. max-width: 300px;
  1389. .upload-label {
  1390. margin-bottom: 12px;
  1391. font-size: 14px;
  1392. color: #606266;
  1393. text-align: center;
  1394. }
  1395. .id-card-upload {
  1396. width: 100%;
  1397. :deep(.el-upload) {
  1398. position: relative;
  1399. display: flex;
  1400. align-items: center;
  1401. justify-content: center;
  1402. width: 100%;
  1403. height: 200px;
  1404. cursor: pointer;
  1405. background-color: #f5f7fa;
  1406. border: 1px solid #dcdfe6;
  1407. border-radius: 4px;
  1408. transition: all 0.3s;
  1409. &:hover {
  1410. border-color: #6c8ff8;
  1411. }
  1412. }
  1413. // 当上传完成后隐藏上传按钮
  1414. &.upload-complete {
  1415. :deep(.el-upload) {
  1416. display: none !important;
  1417. }
  1418. }
  1419. :deep(.el-upload-list) {
  1420. .el-upload-list__item {
  1421. width: 100%;
  1422. height: 200px;
  1423. margin: 0;
  1424. }
  1425. }
  1426. .upload-placeholder {
  1427. display: flex;
  1428. align-items: center;
  1429. justify-content: center;
  1430. width: 100%;
  1431. height: 100%;
  1432. .placeholder-text {
  1433. font-size: 14px;
  1434. color: #909399;
  1435. }
  1436. }
  1437. }
  1438. }
  1439. }
  1440. // OCR 识别结果展示样式
  1441. .ocr-result-container {
  1442. width: 670px;
  1443. padding: 20px;
  1444. margin: 60px auto;
  1445. .ocr-result-item {
  1446. display: flex;
  1447. align-items: center;
  1448. margin-bottom: 16px;
  1449. font-size: 16px;
  1450. line-height: 1.5;
  1451. &:last-child {
  1452. margin-bottom: 0;
  1453. }
  1454. .label {
  1455. min-width: 100px;
  1456. font-weight: 600;
  1457. color: #606266;
  1458. }
  1459. .value {
  1460. flex: 1;
  1461. color: #303133;
  1462. word-break: break-all;
  1463. }
  1464. }
  1465. .ocr-result-tip {
  1466. padding: 10px 0;
  1467. font-size: 14px;
  1468. color: #909399;
  1469. text-align: center;
  1470. }
  1471. }
  1472. }
  1473. }
  1474. .form-content {
  1475. max-width: 800px;
  1476. margin: 0 auto;
  1477. &.step2-form {
  1478. max-width: 100%;
  1479. .form-row {
  1480. display: flex;
  1481. gap: 40px;
  1482. .form-col {
  1483. flex: 1;
  1484. }
  1485. }
  1486. }
  1487. }
  1488. .form-actions {
  1489. display: flex;
  1490. gap: 20px;
  1491. justify-content: center;
  1492. padding-top: 30px;
  1493. margin-top: 40px;
  1494. border-top: 1px solid #e4e7ed;
  1495. .el-button {
  1496. width: 200px;
  1497. height: 44px;
  1498. font-size: 16px;
  1499. font-weight: 500;
  1500. color: #ffffff;
  1501. background: #6c8ff8;
  1502. border: none;
  1503. border-radius: 4px;
  1504. outline: none;
  1505. }
  1506. }
  1507. .pay-account-trigger {
  1508. display: inline-flex;
  1509. gap: 10px;
  1510. align-items: center;
  1511. &__text {
  1512. font-size: 14px;
  1513. color: #303133;
  1514. }
  1515. &__arrow {
  1516. cursor: pointer;
  1517. }
  1518. }
  1519. }
  1520. </style>
  1521. <style lang="scss">
  1522. /* 弹窗挂到 body,需非 scoped;并抵消本页全局 .el-dialog__body { height: 600px } */
  1523. .pay-account-setup-dialog.el-dialog {
  1524. padding: 0;
  1525. overflow: hidden;
  1526. border-radius: 12px;
  1527. box-shadow: 0 8px 32px rgb(0 0 0 / 12%);
  1528. }
  1529. .pay-account-setup-dialog .el-dialog__header {
  1530. padding: 20px 24px 12px;
  1531. margin: 0;
  1532. }
  1533. .pay-account-setup-dialog .el-dialog__headerbtn {
  1534. top: 16px;
  1535. right: 16px;
  1536. font-size: 18px;
  1537. }
  1538. .pay-account-setup-dialog .el-dialog__body {
  1539. height: auto !important;
  1540. max-height: none !important;
  1541. padding: 4px 24px 8px !important;
  1542. overflow: visible !important;
  1543. }
  1544. .pay-account-setup-dialog .el-dialog__footer {
  1545. padding: 16px 24px 24px;
  1546. }
  1547. .pay-account-setup-dialog__title {
  1548. width: 100%;
  1549. font-size: 17px;
  1550. font-weight: 600;
  1551. color: #303133;
  1552. text-align: center;
  1553. }
  1554. .pay-account-option-list {
  1555. display: flex;
  1556. flex-direction: column;
  1557. }
  1558. .pay-account-option {
  1559. display: flex;
  1560. gap: 14px;
  1561. align-items: center;
  1562. width: 100%;
  1563. padding: 16px 0;
  1564. margin: 0;
  1565. font: inherit;
  1566. text-align: left;
  1567. cursor: pointer;
  1568. background: transparent;
  1569. border: none;
  1570. border-bottom: 1px solid #ebeef5;
  1571. outline: none;
  1572. }
  1573. .pay-account-option:last-of-type {
  1574. border-bottom: none;
  1575. }
  1576. .pay-account-option__icon {
  1577. flex-shrink: 0;
  1578. width: 36px;
  1579. height: 36px;
  1580. border-radius: 8px;
  1581. }
  1582. .pay-account-option__icon--alipay {
  1583. background: #1677ff;
  1584. background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23fff' d='M8.5 7.2h2.2v1.9H8.5V7.2zm0 3.1h2.2v5.4H8.5v-5.4zm3.8-3.1h4.6c1.4 0 2.4 1 2.4 2.3 0 1.1-.7 2-1.7 2.3l1.9 3.8h-2.5l-1.6-3.2h-1.4v3.2h-2.2V7.2zm2.2 3.6h2.1c.6 0 1-.4 1-.9 0-.5-.4-.9-1-.9h-2.1v1.8z'/%3E%3C/svg%3E");
  1585. background-repeat: no-repeat;
  1586. background-position: center;
  1587. background-size: 26px 26px;
  1588. }
  1589. .pay-account-option__icon--wechat {
  1590. background: #07c160;
  1591. background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23fff' d='M8.5 10.5c-.8 0-1.5-.7-1.5-1.5S7.7 7.5 8.5 7.5 10 8.2 10 9s-.7 1.5-1.5 1.5zm7 0c-.8 0-1.5-.7-1.5-1.5S14.7 7.5 15.5 7.5 17 8.2 17 9s-.7 1.5-1.5 1.5z'/%3E%3Cpath fill='%23fff' d='M9 14.5c-2.5 0-4.5-1.6-4.5-3.5 0-1 .5-1.9 1.3-2.6-.1-.3-.2-.7-.2-1C5.6 5.8 8.9 4 13 4s7.4 1.8 7.4 4c0 .4-.1.8-.2 1 .8.7 1.3 1.6 1.3 2.6 0 1.9-2 3.5-4.5 3.5-.6 0-1.2-.1-1.8-.3l-2 1.1.5-1.8c-.9-.5-1.6-1.2-2-2z'/%3E%3C/svg%3E");
  1592. background-repeat: no-repeat;
  1593. background-position: center;
  1594. background-size: 24px 24px;
  1595. }
  1596. .pay-account-option__icon--bank {
  1597. background-color: #3b7cff;
  1598. background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect x='3' y='7' width='18' height='11' rx='2' fill='%23fff' opacity='.95'/%3E%3Crect x='3' y='10' width='18' height='2' fill='%233b7cff'/%3E%3C/svg%3E");
  1599. background-repeat: no-repeat;
  1600. background-position: center;
  1601. background-size: 28px 22px;
  1602. }
  1603. .pay-account-option__label {
  1604. flex: 1;
  1605. font-size: 15px;
  1606. color: #303133;
  1607. }
  1608. .pay-account-option__radio {
  1609. display: flex;
  1610. flex-shrink: 0;
  1611. align-items: center;
  1612. justify-content: center;
  1613. width: 22px;
  1614. height: 22px;
  1615. border: 2px solid #dcdfe6;
  1616. border-radius: 50%;
  1617. transition:
  1618. border-color 0.2s,
  1619. background 0.2s;
  1620. }
  1621. .pay-account-option__radio.is-checked {
  1622. background: #7395ff;
  1623. border-color: #7395ff;
  1624. }
  1625. .pay-account-option__check {
  1626. font-size: 14px;
  1627. font-weight: 700;
  1628. color: #ffffff;
  1629. }
  1630. .pay-account-setup-dialog__confirm {
  1631. width: 100%;
  1632. height: 44px;
  1633. font-size: 16px;
  1634. font-weight: 500;
  1635. color: #ffffff;
  1636. background: #7395ff !important;
  1637. border-color: #7395ff !important;
  1638. border-radius: 8px;
  1639. }
  1640. .pay-account-setup-dialog__confirm:hover {
  1641. background: #5f82f0 !important;
  1642. border-color: #5f82f0 !important;
  1643. }
  1644. </style>