go-flow.vue 49 KB

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