go-flow.vue 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007
  1. <template>
  2. <div class="form-container">
  3. <div>
  4. <!-- 进度条 -->
  5. <el-button class="back-btn" @click="handleBack"> 返回 </el-button>
  6. <div class="progress-container">
  7. <el-steps :active="currentStep" style="max-width: 1500px" align-center>
  8. <el-step v-for="(item, index) in entryList" :key="index">
  9. <template #title>
  10. <div class="step-title-wrapper">
  11. <span class="step-title">{{ item.title }}</span>
  12. </div>
  13. </template>
  14. </el-step>
  15. </el-steps>
  16. </div>
  17. </div>
  18. <!-- 第一步:个人实名 -->
  19. <div v-if="currentStep === 1">
  20. <!-- 表单 -->
  21. <div class="form-content">
  22. <el-form :model="step1Form" :rules="step1Rules" ref="step1FormRef" label-width="120px">
  23. <el-form-item label="姓名" prop="name">
  24. <el-input v-model="step1Form.name" placeholder="请输入姓名" style="width: 400px" />
  25. </el-form-item>
  26. <el-form-item label="身份证号码" prop="idNumber">
  27. <el-input v-model="step1Form.idNumber" placeholder="请输入身份证号码" style="width: 400px" />
  28. </el-form-item>
  29. </el-form>
  30. </div>
  31. <!-- 按钮 -->
  32. <div class="form-actions">
  33. <el-button type="primary" size="large" @click="handleNextStep"> 下一步 </el-button>
  34. </div>
  35. </div>
  36. <!-- 第二步:填写信息 -->
  37. <div v-if="currentStep === 2">
  38. <!-- 表单内容 -->
  39. <div class="form-content step2-form">
  40. <el-form :model="step2Form" :rules="step2Rules" ref="step2FormRef" label-width="125px">
  41. <div class="form-row">
  42. <!-- 左列 -->
  43. <div class="form-col">
  44. <el-form-item label="店铺名称" prop="storeName">
  45. <el-input v-model="step2Form.storeName" placeholder="请输入店铺名称" />
  46. </el-form-item>
  47. <el-form-item label="容纳人数" prop="storeCapacity">
  48. <el-input-number v-model="step2Form.storeCapacity" :min="1" :max="9999" />
  49. </el-form-item>
  50. <el-form-item label="门店面积" prop="storeArea">
  51. <el-radio-group v-model="step2Form.storeArea">
  52. <el-radio label="小于20平米" value="1"> 小于20平米 </el-radio>
  53. <el-radio label="20-50平米" value="2"> 20-50平米 </el-radio>
  54. <el-radio label="50-100平米" value="3"> 50-100平米 </el-radio>
  55. <el-radio label="100-300平米" value="4"> 100-300平米 </el-radio>
  56. <el-radio label="500-1000平米" value="5"> 500-1000平米 </el-radio>
  57. <el-radio label="大于1000平米" value="6"> 大于1000平米 </el-radio>
  58. </el-radio-group>
  59. </el-form-item>
  60. <el-form-item label="所在地区" prop="region">
  61. <el-cascader :props="areaProps" v-model="step2Form.region" style="width: 100%" />
  62. </el-form-item>
  63. <el-form-item label="详细地址" prop="storeDetailAddress">
  64. <el-input v-model="step2Form.storeDetailAddress" type="textarea" :rows="3" placeholder="请输入" />
  65. </el-form-item>
  66. <el-form-item label="门店简介" prop="storeBlurb">
  67. <el-input v-model="step2Form.storeBlurb" type="textarea" :rows="3" placeholder="请输入" />
  68. </el-form-item>
  69. <el-form-item label="经营板块" prop="businessSection">
  70. <el-radio-group v-model="step2Form.businessSection" @change="changeBusinessSector">
  71. <el-radio
  72. v-for="businessSection in businessSectionList"
  73. :value="businessSection.value"
  74. :key="businessSection.value"
  75. >
  76. {{ businessSection.label }}
  77. </el-radio>
  78. </el-radio-group>
  79. </el-form-item>
  80. <el-form-item label="经营种类" prop="businessTypes">
  81. <el-checkbox-group v-model="step2Form.businessTypes">
  82. <el-checkbox
  83. v-for="businessType in businessTypeList"
  84. :key="businessType"
  85. :label="businessType.label"
  86. :value="businessType.value"
  87. />
  88. </el-checkbox-group>
  89. </el-form-item>
  90. </div>
  91. <!-- 右列 -->
  92. <div class="form-col">
  93. <el-form-item label="门店营业状态" prop="businessType">
  94. <el-radio-group v-model="step2Form.businessType">
  95. <el-radio label="正常营业"> 正常营业 </el-radio>
  96. <el-radio label="暂停营业"> 暂停营业 </el-radio>
  97. <el-radio label="筹建中"> 筹建中 </el-radio>
  98. </el-radio-group>
  99. </el-form-item>
  100. <el-form-item label="经度" prop="storePositionLongitude" v-show="latShow">
  101. <el-input disabled v-model="step2Form.storePositionLongitude" placeholder="请填写经度" clearable />
  102. </el-form-item>
  103. <el-form-item label="纬度" prop="storePositionLatitude" v-show="latShow">
  104. <el-input disabled v-model="step2Form.storePositionLatitude" placeholder="请填写纬度" clearable />
  105. </el-form-item>
  106. <el-form-item label="经纬度查询" prop="address">
  107. <el-select
  108. v-model="step2Form.address"
  109. filterable
  110. placeholder="请输入地址进行查询"
  111. remote
  112. reserve-keyword
  113. :remote-method="getLonAndLat"
  114. @change="selectAddress"
  115. >
  116. <el-option v-for="item in addressList" :key="item.id" :label="item.name" :value="item.location">
  117. <span style="float: left">{{ item.name }}</span>
  118. <span style="float: right; font-size: 13px; color: var(--el-text-color-secondary)">{{ item.district }}</span>
  119. </el-option>
  120. </el-select>
  121. </el-form-item>
  122. <el-form-item label="营业执照" prop="businessLicenseAddress">
  123. <el-upload
  124. v-model:file-list="step2Form.businessLicenseAddress"
  125. :http-request="handleHttpUpload"
  126. list-type="picture-card"
  127. :limit="1"
  128. :on-exceed="handleExceed"
  129. :on-success="handleUploadSuccess"
  130. :on-preview="handlePictureCardPreview"
  131. >
  132. <el-icon><Plus /></el-icon>
  133. <template #tip>
  134. <div class="el-upload__tip">({{ step2Form.businessLicenseAddress.length }}/1)</div>
  135. </template>
  136. </el-upload>
  137. </el-form-item>
  138. <el-form-item label="合同图片" prop="contractImageList">
  139. <el-upload
  140. v-model:file-list="step2Form.contractImageList"
  141. :http-request="handleHttpUpload"
  142. list-type="picture-card"
  143. :limit="20"
  144. :on-exceed="handleExceed"
  145. :on-success="handleUploadSuccess"
  146. :on-preview="handlePictureCardPreview"
  147. >
  148. <el-icon><Plus /></el-icon>
  149. <template #tip>
  150. <div class="el-upload__tip">({{ step2Form.contractImageList.length }}/20)</div>
  151. </template>
  152. </el-upload>
  153. </el-form-item>
  154. <el-form-item label="食品经营许可证" prop="foodLicenceImgList">
  155. <el-upload
  156. v-model:file-list="step2Form.foodLicenceImgList"
  157. :http-request="handleHttpUpload"
  158. list-type="picture-card"
  159. :limit="1"
  160. :on-exceed="handleExceed"
  161. :on-success="handleUploadSuccess"
  162. :on-preview="handlePictureCardPreview"
  163. >
  164. <el-icon><Plus /></el-icon>
  165. <template #tip>
  166. <div class="el-upload__tip">({{ step2Form.foodLicenceImgList.length }}/1)</div>
  167. </template>
  168. </el-upload>
  169. </el-form-item>
  170. </div>
  171. </div>
  172. </el-form>
  173. </div>
  174. <!-- 按钮 -->
  175. <div class="form-actions">
  176. <el-button type="primary" size="large" @click="handleSubmit"> 提交 </el-button>
  177. </div>
  178. </div>
  179. <!-- 第三步: 等待审核-->
  180. <div v-if="currentStep === 3">
  181. <div class="button-container">
  182. <el-button type="danger" size="large" class="register-btn-red" @click="changeRefuse" v-if="storeApplicationStatus == 2">
  183. 审核拒绝,重新入驻
  184. </el-button>
  185. <el-button type="primary" size="large" class="register-btn" v-if="storeApplicationStatus == 0"> 等待审核 </el-button>
  186. </div>
  187. </div>
  188. </div>
  189. <!-- 图片预览 -->
  190. <el-image-viewer
  191. v-if="imageViewerVisible"
  192. :url-list="imageViewerUrlList"
  193. :initial-index="imageViewerInitialIndex"
  194. @close="imageViewerVisible = false"
  195. />
  196. </template>
  197. <script setup lang="ts">
  198. import { ref, reactive, watch, onMounted } from "vue";
  199. import {
  200. ElMessage,
  201. ElMessageBox,
  202. type FormInstance,
  203. type FormRules,
  204. UploadProps,
  205. UploadUserFile,
  206. UploadRequestOptions
  207. } from "element-plus";
  208. import { Plus } from "@element-plus/icons-vue";
  209. import { verifyIdInfo, applyStore, getMerchantByPhone } from "@/api/modules/homeEntry";
  210. import { getBusinessSection, getBusinessSectionTypes, getInputPrompt, getDistrict, uploadImg } from "@/api/modules/newLoginApi";
  211. import { localGet, localSet } from "@/utils/index";
  212. import { useAuthStore } from "@/stores/modules/auth";
  213. const authStore = useAuthStore();
  214. const userInfo = localGet("geeker-user")?.userInfo || {};
  215. const latShow = ref(false);
  216. // 图片预览相关
  217. const imageViewerVisible = ref(false);
  218. const imageViewerUrlList = ref<string[]>([]);
  219. const imageViewerInitialIndex = ref(0);
  220. const entryList = ref([
  221. {
  222. title: "个人实名"
  223. },
  224. {
  225. title: "填写信息"
  226. },
  227. {
  228. title: "等待审核"
  229. },
  230. {
  231. title: "入驻成功"
  232. }
  233. ]);
  234. const step2Rules: FormRules = {
  235. storeName: [{ required: true, message: "请输入店铺名称", trigger: "blur" }],
  236. storeCapacity: [{ required: true, message: "请输入容纳人数", trigger: "blur" }],
  237. storeArea: [{ required: true, message: "请选择门店面积", trigger: "change" }],
  238. storeBlurb: [{ required: true, message: "请输入门店简介", trigger: "change" }],
  239. storeIntro: [{ required: true, message: "请输入门店简介", trigger: "blur" }],
  240. businessSection: [{ required: true, message: "请选择经营板块", trigger: "change" }],
  241. businessTypes: [{ required: true, message: "请选择经营种类", trigger: "change" }],
  242. address: [{ required: true, message: "请输入经纬度", trigger: "blur" }],
  243. businessLicenseAddress: [{ required: true, message: "请上传营业执照", trigger: "change" }],
  244. contractImageList: [{ required: true, message: "请上传合同图片", trigger: "change" }],
  245. foodLicenceImgList: [{ required: true, message: "请上传食品经营许可证", trigger: "change" }]
  246. };
  247. //地址集合
  248. const addressList = ref<any[]>([]);
  249. //查询地址名称
  250. const queryAddress = ref<string>("");
  251. const props = defineProps({
  252. currentStep: {
  253. type: Number,
  254. value: 0
  255. },
  256. storeApplicationStatus: {
  257. type: Number,
  258. value: 0
  259. }
  260. });
  261. const emit = defineEmits(["update:currentStep", "update:get-user-info"]);
  262. // 调用父组件的 getUserInfo 方法
  263. const callGetUserInfo = () => {
  264. emit("update:get-user-info");
  265. };
  266. // 内部步骤状态,和父组件同步
  267. const currentStep = ref<number>(props.currentStep || 0);
  268. const storeApplicationStatus = ref<number>(props.storeApplicationStatus || 0);
  269. console.log(storeApplicationStatus);
  270. watch(
  271. () => props.currentStep,
  272. val => {
  273. if (typeof val === "number") currentStep.value = val;
  274. }
  275. );
  276. watch(
  277. () => props.storeApplicationStatus,
  278. val => {
  279. if (typeof val === "number") storeApplicationStatus.value = val;
  280. }
  281. );
  282. // 隐藏财务管理菜单的函数
  283. const hideFinancialManagementMenu = () => {
  284. const hideMenus = (menuList: any[]) => {
  285. menuList.forEach(menu => {
  286. // 根据菜单名称判断是否需要隐藏财务管理
  287. if (menu.name && menu.name === "financialManagement") {
  288. menu.meta.isHide = true;
  289. }
  290. // 递归处理子菜单
  291. if (menu.children && menu.children.length > 0) {
  292. hideMenus(menu.children);
  293. }
  294. });
  295. };
  296. if (authStore.authMenuList && authStore.authMenuList.length > 0) {
  297. hideMenus(authStore.authMenuList);
  298. }
  299. };
  300. // 显示财务管理菜单的函数
  301. const showFinancialManagementMenu = () => {
  302. const showMenus = (menuList: any[]) => {
  303. menuList.forEach(menu => {
  304. // 根据菜单名称判断是否需要显示财务管理
  305. if (menu.name && menu.name === "financialManagement") {
  306. menu.meta.isHide = false;
  307. }
  308. // 递归处理子菜单
  309. if (menu.children && menu.children.length > 0) {
  310. showMenus(menu.children);
  311. }
  312. });
  313. };
  314. if (authStore.authMenuList && authStore.authMenuList.length > 0) {
  315. showMenus(authStore.authMenuList);
  316. }
  317. };
  318. // 更新缓存中的 storeId
  319. const updateStoreIdInCache = async () => {
  320. try {
  321. const geekerUser = localGet("geeker-user");
  322. if (!geekerUser || !geekerUser.userInfo || !geekerUser.userInfo.phone) {
  323. console.error("用户信息不存在");
  324. return;
  325. }
  326. const phone = geekerUser.userInfo.phone;
  327. const res: any = await getMerchantByPhone({ phone });
  328. if (res && res.code == 200 && res.data && res.data.storeId) {
  329. // 更新缓存中的 storeId
  330. geekerUser.userInfo.storeId = res.data.storeId;
  331. localSet("geeker-user", geekerUser);
  332. // 同时更新 createdId 缓存
  333. if (res.data.storeId) {
  334. localSet("createdId", res.data.storeId);
  335. }
  336. }
  337. } catch (error) {
  338. console.error("更新 storeId 缓存失败:", error);
  339. }
  340. };
  341. // 监听步骤和审核状态,如果是待审核或审核拒绝,则更新 storeId
  342. watch([() => currentStep.value, () => storeApplicationStatus.value], ([step, status]) => {
  343. if (step === 3 && (status === 0 || status === 2)) {
  344. updateStoreIdInCache();
  345. }
  346. // 如果是审核拒绝状态,隐藏财务管理菜单
  347. if (status === 2) {
  348. hideFinancialManagementMenu();
  349. }
  350. // 如果是审核通过状态,显示财务管理菜单
  351. if (status === 1) {
  352. showFinancialManagementMenu();
  353. }
  354. });
  355. // 监听菜单列表变化,根据审核状态显示或隐藏财务管理菜单
  356. watch(
  357. () => authStore.authMenuList.length,
  358. newLength => {
  359. if (newLength > 0) {
  360. // 如果审核状态是拒绝,隐藏财务管理菜单
  361. if (storeApplicationStatus.value === 2) {
  362. hideFinancialManagementMenu();
  363. }
  364. // 如果审核状态是通过,显示财务管理菜单
  365. if (storeApplicationStatus.value === 1) {
  366. showFinancialManagementMenu();
  367. }
  368. }
  369. }
  370. );
  371. onMounted(() => {
  372. getBusinessSectionList();
  373. getBusinessTypes(null);
  374. callGetUserInfo();
  375. // 如果当前已经是待审核或审核拒绝状态,则更新 storeId
  376. if (currentStep.value === 3 && (storeApplicationStatus.value === 0 || storeApplicationStatus.value === 2)) {
  377. updateStoreIdInCache();
  378. }
  379. // 根据审核状态显示或隐藏财务管理菜单
  380. if (storeApplicationStatus.value === 2) {
  381. hideFinancialManagementMenu();
  382. } else if (storeApplicationStatus.value === 1) {
  383. showFinancialManagementMenu();
  384. }
  385. });
  386. const changeRefuse = () => {
  387. currentStep.value = 2;
  388. };
  389. const setStep = (val: number) => {
  390. currentStep.value = val;
  391. emit("update:currentStep", val);
  392. };
  393. // 第一步表单
  394. const step1FormRef = ref<FormInstance>();
  395. const step1Form = reactive({
  396. name: "",
  397. idNumber: ""
  398. });
  399. const step1Rules: FormRules = {
  400. name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
  401. idNumber: [
  402. { required: true, message: "请输入身份证号码", trigger: "blur" },
  403. {
  404. pattern: /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/,
  405. message: "请输入正确的身份证号码",
  406. trigger: "blur"
  407. }
  408. ]
  409. };
  410. // 第二步表单
  411. const step2FormRef = ref<FormInstance>();
  412. const step2Form = reactive({
  413. storeName: "", //门店名称
  414. storeCapacity: 1, //容纳人数
  415. storeArea: "1", //门店面积
  416. isChain: 0, //是否连锁
  417. storeDetailAddress: "", //详细地址
  418. region: [],
  419. administrativeRegionProvinceAdcode: "", //省
  420. administrativeRegionCityAdcode: "", //市
  421. administrativeRegionDistrictAdcode: "", //区
  422. storeAddress: "", //门店地址(完整地址)
  423. storeBlurb: "", //门店简介
  424. businessSection: "1", //经营板块
  425. businessSectionName: "KTV",
  426. businessTypes: [], //经营种类
  427. businessTypesList: [], //经营种类集合
  428. businessStatus: 0, //营业状态
  429. storeStatus: 1, //门店状态
  430. businessType: "正常营业", //门店营业状态(用于表单显示)
  431. storePositionLongitude: "", //经度
  432. storePositionLatitude: "", //纬度
  433. businessLicenseAddress: [] as UploadUserFile[], //营业执照地址
  434. contractImageList: [] as UploadUserFile[], //合同图片集合
  435. foodLicenceImgList: [] as UploadUserFile[], //食品经营许可证
  436. address: ""
  437. });
  438. // 返回按钮
  439. const handleBack = () => {
  440. if (currentStep.value === 1) {
  441. setStep(0);
  442. } else if (currentStep.value === 2) {
  443. setStep(1);
  444. } else if (currentStep.value === 3) {
  445. setStep(2);
  446. }
  447. };
  448. // 地区选择
  449. const areaProps: any = {
  450. lazy: true,
  451. async lazyLoad(node, resolve) {
  452. const { level } = node;
  453. try {
  454. let param = { adCode: node.data.adCode ? node.data.adCode : "" };
  455. // 调用后台接口获取数据
  456. const response: any = await getDistrict(param as any);
  457. // 转换数据格式
  458. const nodes = (response?.data?.districts?.[0]?.districts || []).map((item: any) => ({
  459. value: item.adcode,
  460. adCode: item.adcode,
  461. label: item.name,
  462. leaf: level >= 2 // 假设最多三级,可以根据实际需求调整
  463. }));
  464. console.log(nodes);
  465. // 返回数据
  466. resolve(nodes);
  467. } catch (error) {
  468. resolve([]);
  469. }
  470. }
  471. };
  472. watch(
  473. () => step2Form.region,
  474. (newVal: any[]) => {
  475. if (newVal.length > 0) {
  476. step2Form.administrativeRegionProvinceAdcode = newVal[0];
  477. step2Form.administrativeRegionCityAdcode = newVal[1];
  478. step2Form.administrativeRegionDistrictAdcode = newVal[2];
  479. }
  480. }
  481. );
  482. //经营板块
  483. const businessSectionList = ref<any[]>([]);
  484. const getBusinessSectionList = async () => {
  485. let res: any = await getBusinessSection();
  486. let addData: any[] = [];
  487. (res?.data || []).forEach((element: any) => {
  488. addData.push({ value: element.dictId, label: element.dictDetail, parentId: element.parentId });
  489. });
  490. businessSectionList.value = addData;
  491. // 如果businessSection有默认值,自动设置对应的id和name
  492. if (step2Form.businessSection) {
  493. const selectedSection = addData.find((item: any) => item.value === step2Form.businessSection);
  494. if (selectedSection) {
  495. step2Form.businessSection = selectedSection.value;
  496. step2Form.businessSectionName = selectedSection.label;
  497. }
  498. }
  499. };
  500. const changeBusinessSector = async (val: any) => {
  501. // 根据选中的value从businessSectionList中查找对应的项
  502. const selectedSection = businessSectionList.value.find((item: any) => item.value === val);
  503. if (selectedSection) {
  504. step2Form.businessSection = selectedSection.value; // dictId
  505. step2Form.businessSectionName = selectedSection.label; // dictDetail
  506. } else {
  507. step2Form.businessSection = "";
  508. step2Form.businessSectionName = "";
  509. }
  510. // 清空之前选中的经营种类
  511. step2Form.businessTypesList = [];
  512. step2Form.businessTypes = [];
  513. getBusinessTypes(val);
  514. };
  515. //经营种类
  516. const businessTypeList = ref<any[]>([]);
  517. const getBusinessTypes = async (val: any) => {
  518. let res: any = await getBusinessSectionTypes({ parentId: val ? val : step2Form.businessSection });
  519. let addData1: any[] = [];
  520. (res?.data || []).forEach((element: any) => {
  521. addData1.push({ value: element.dictId, label: element.dictDetail });
  522. });
  523. businessTypeList.value = addData1;
  524. };
  525. // 经纬度查询
  526. const getLonAndLat = async (keyword: string) => {
  527. if (keyword) {
  528. console.log("地址查询", keyword);
  529. let param = {
  530. addressName: keyword
  531. };
  532. let res: any = await getInputPrompt(param as any);
  533. if (res.code == "200") {
  534. addressList.value = res?.data?.tips || [];
  535. console.log("res", res);
  536. } else {
  537. ElMessage.error("新增失败!");
  538. }
  539. } else {
  540. addressList.value = [];
  541. }
  542. };
  543. const selectAddress = async (param: any) => {
  544. // 安全检查:确保 address 存在且是字符串类型
  545. if (!step2Form.address || typeof step2Form.address !== "string") {
  546. ElMessage.warning("地址格式不正确,请重新选择");
  547. return;
  548. }
  549. // 检查是否包含逗号(经纬度格式应该是 "经度,纬度")
  550. if (!step2Form.address.includes(",")) {
  551. ElMessage.warning("地址格式不正确,缺少经纬度信息");
  552. return;
  553. }
  554. // 安全地分割地址字符串
  555. let locationList = step2Form.address.split(",");
  556. // 检查分割后的数组长度
  557. if (locationList.length < 2) {
  558. ElMessage.warning("地址格式不正确,无法获取经纬度");
  559. return;
  560. }
  561. // 查找对应的地址名称
  562. addressList.value.forEach((item: any) => {
  563. if (item.location == step2Form.address) {
  564. queryAddress.value = item.name;
  565. }
  566. });
  567. // 设置经纬度,并去除可能的空格
  568. step2Form.storePositionLongitude = locationList[0]?.trim() || "";
  569. step2Form.storePositionLatitude = locationList[1]?.trim() || "";
  570. // 验证经纬度是否为有效数字
  571. if (!step2Form.storePositionLongitude || !step2Form.storePositionLatitude) {
  572. ElMessage.warning("无法获取有效的经纬度信息");
  573. return;
  574. }
  575. latShow.value = true;
  576. };
  577. //文件上传
  578. const handleHttpUpload = async (options: UploadRequestOptions) => {
  579. let formData = new FormData();
  580. formData.append("file", options.file);
  581. try {
  582. const res: any = await uploadImg(formData);
  583. // 上传成功,将URL保存到文件对象中
  584. const fileUrl = res?.data?.fileUrl || res?.data?.[0] || res?.fileUrl;
  585. if (fileUrl) {
  586. // 调用 onSuccess 回调,传入响应数据
  587. options.onSuccess({ fileUrl });
  588. } else {
  589. throw new Error("上传失败:未获取到文件URL");
  590. }
  591. } catch (error) {
  592. options.onError(error as any);
  593. ElMessage.error("文件上传失败,请重试");
  594. }
  595. };
  596. // 文件上传成功回调
  597. const handleUploadSuccess = (response: any, uploadFile: UploadUserFile) => {
  598. // 将URL保存到文件对象中
  599. if (response?.fileUrl) {
  600. uploadFile.url = response.fileUrl;
  601. }
  602. };
  603. // 图片预览处理函数
  604. const handlePictureCardPreview = (file: UploadUserFile) => {
  605. // 如果文件正在上传中,允许预览(使用本地预览)
  606. if (file.status === "uploading" && file.url) {
  607. imageViewerUrlList.value = [file.url];
  608. imageViewerInitialIndex.value = 0;
  609. imageViewerVisible.value = true;
  610. return;
  611. }
  612. // 获取当前上传组件的所有图片 URL 列表(只包含已上传成功的图片)
  613. let urlList: string[] = [];
  614. let currentFileList: UploadUserFile[] = [];
  615. // 根据文件对象判断是哪个上传组件的文件
  616. if (step2Form.businessLicenseAddress.some((f: UploadUserFile) => f.uid === file.uid)) {
  617. currentFileList = step2Form.businessLicenseAddress;
  618. } else if (step2Form.contractImageList.some((f: UploadUserFile) => f.uid === file.uid)) {
  619. currentFileList = step2Form.contractImageList;
  620. } else if (step2Form.foodLicenceImgList.some((f: UploadUserFile) => f.uid === file.uid)) {
  621. currentFileList = step2Form.foodLicenceImgList;
  622. }
  623. // 获取所有已上传成功的图片 URL
  624. urlList = currentFileList
  625. .filter((item: UploadUserFile) => item.status === "success" && (item.url || (item.response as any)?.fileUrl))
  626. .map((item: UploadUserFile) => item.url || (item.response as any)?.fileUrl);
  627. // 找到当前点击的图片索引
  628. const currentUrl = file.url || (file.response as any)?.fileUrl;
  629. const currentIndex = urlList.findIndex((url: string) => url === currentUrl);
  630. if (currentIndex < 0) {
  631. ElMessage.warning("图片尚未上传完成,无法预览");
  632. return;
  633. }
  634. imageViewerUrlList.value = urlList;
  635. imageViewerInitialIndex.value = currentIndex;
  636. imageViewerVisible.value = true;
  637. };
  638. // 下一步
  639. const handleNextStep = async () => {
  640. if (!step1FormRef.value) return;
  641. await step1FormRef.value.validate(async valid => {
  642. if (valid) {
  643. const params = {
  644. name: step1Form.name,
  645. idCard: step1Form.idNumber,
  646. appType: 1
  647. };
  648. const res: any = await verifyIdInfo(params);
  649. if (res && res.code == 200) {
  650. ElMessage.success(res.msg);
  651. // 更新本地存储中的idCard
  652. try {
  653. const geekerUser = localGet("geeker-user");
  654. if (geekerUser && geekerUser.userInfo) {
  655. // 获取最新的用户信息
  656. const phone = geekerUser.userInfo.phone;
  657. if (phone) {
  658. const userRes: any = await getMerchantByPhone({ phone });
  659. if (userRes && userRes.code == 200 && userRes.data) {
  660. // 更新本地存储中的用户信息,特别是idCard
  661. geekerUser.userInfo.idCard = userRes.data.idCard || step1Form.idNumber;
  662. geekerUser.userInfo.name = userRes.data.name || step1Form.name;
  663. localSet("geeker-user", geekerUser);
  664. } else {
  665. // 如果获取失败,至少更新idCard字段
  666. geekerUser.userInfo.idCard = step1Form.idNumber;
  667. geekerUser.userInfo.name = step1Form.name;
  668. localSet("geeker-user", geekerUser);
  669. }
  670. } else {
  671. // 如果没有phone,直接更新idCard
  672. geekerUser.userInfo.idCard = step1Form.idNumber;
  673. geekerUser.userInfo.name = step1Form.name;
  674. localSet("geeker-user", geekerUser);
  675. }
  676. }
  677. } catch (error) {
  678. console.error("更新本地存储失败:", error);
  679. }
  680. setStep(2);
  681. }
  682. } else {
  683. ElMessage.error("请完善表单信息");
  684. }
  685. });
  686. };
  687. // 提取文件列表中的URL
  688. const getFileUrls = (fileList: UploadUserFile[]): string[] => {
  689. return fileList
  690. .map((file: UploadUserFile) => {
  691. // 优先使用 url,如果没有则使用 response 中的 fileUrl
  692. const response = file.response as any;
  693. return file.url || response?.fileUrl || "";
  694. })
  695. .filter((url: string) => url); // 过滤掉空字符串
  696. };
  697. // 根据adcode获取地区详细信息
  698. const getDistrictInfo = async (adcode: string) => {
  699. try {
  700. const response: any = await getDistrict({ adCode: adcode } as any);
  701. const district = response?.data?.districts?.[0];
  702. if (district) {
  703. return {
  704. citycode: district.citycode ? [district.citycode] : [],
  705. adcode: district.adcode,
  706. level: district.level,
  707. center: district.center,
  708. name: district.name,
  709. districts: []
  710. };
  711. }
  712. } catch (error) {
  713. console.error("获取地区信息失败:", error);
  714. }
  715. return null;
  716. };
  717. // 构建whereAddress数组
  718. const buildWhereAddress = async (regionCodes: string[]) => {
  719. const whereAddress: any[] = [];
  720. if (regionCodes && regionCodes.length > 0) {
  721. for (const code of regionCodes) {
  722. const districtInfo = await getDistrictInfo(code);
  723. if (districtInfo) {
  724. whereAddress.push(districtInfo);
  725. }
  726. }
  727. }
  728. return whereAddress;
  729. };
  730. // 提交
  731. const handleSubmit = async () => {
  732. if (!step2FormRef.value) return;
  733. await step2FormRef.value.validate(async valid => {
  734. if (valid) {
  735. // 提取上传图片的URL
  736. const businessLicenseUrls = getFileUrls(step2Form.businessLicenseAddress);
  737. const contractImageUrls = getFileUrls(step2Form.contractImageList);
  738. const foodLicenceUrls = getFileUrls(step2Form.foodLicenceImgList);
  739. // 构建whereAddress数组
  740. const whereAddress = await buildWhereAddress(step2Form.region);
  741. // 处理营业状态映射
  742. let storeStatus = 1; // 默认值
  743. if (step2Form.businessType === "正常营业") {
  744. storeStatus = 1;
  745. } else if (step2Form.businessType === "暂停营业") {
  746. storeStatus = 0;
  747. } else if (step2Form.businessType === "筹建中") {
  748. storeStatus = 2;
  749. }
  750. // 处理门店面积:转换为数字
  751. const storeAreaNum = typeof step2Form.storeArea === "string" ? parseInt(step2Form.storeArea) : step2Form.storeArea;
  752. // 构建地址对象
  753. const addressObj = {
  754. address: queryAddress.value || "",
  755. longitude: parseFloat(step2Form.storePositionLongitude) || 0,
  756. latitude: parseFloat(step2Form.storePositionLatitude) || 0
  757. };
  758. // 构建门店位置字符串
  759. const storePosition =
  760. step2Form.storePositionLongitude && step2Form.storePositionLatitude
  761. ? `${step2Form.storePositionLongitude},${step2Form.storePositionLatitude}`
  762. : "";
  763. // 构建完整地址(省市区+详细地址)
  764. let fullStoreAddress = "";
  765. if (whereAddress.length > 0) {
  766. const provinceName = whereAddress[0]?.name || "";
  767. const cityName = whereAddress[1]?.name || "";
  768. const districtName = whereAddress[2]?.name || "";
  769. fullStoreAddress = `${provinceName}${cityName}${districtName}`;
  770. }
  771. const params = {
  772. storeTel: userInfo.phone,
  773. storeName: step2Form.storeName,
  774. storeCapacity: step2Form.storeCapacity,
  775. storeArea: storeAreaNum,
  776. isChain: step2Form.isChain,
  777. storeDetailAddress: step2Form.storeDetailAddress,
  778. storeBlurb: step2Form.storeBlurb,
  779. businessSection: step2Form.businessSection,
  780. businessTypesList: step2Form.businessTypes.length > 0 ? step2Form.businessTypes : step2Form.businessTypesList,
  781. storeStatus: storeStatus,
  782. businessStatus: step2Form.businessStatus,
  783. address: addressObj,
  784. businessLicenseAddress: businessLicenseUrls,
  785. contractImageList: contractImageUrls,
  786. foodLicenceImgList: foodLicenceUrls,
  787. storeAddress: fullStoreAddress,
  788. whereAddress: whereAddress,
  789. updatedTime: null,
  790. queryAddress: queryAddress.value,
  791. storePosition: storePosition,
  792. storePositionLatitude: parseFloat(step2Form.storePositionLatitude) || 0,
  793. storePositionLongitude: parseFloat(step2Form.storePositionLongitude) || 0,
  794. businessSectionName: step2Form.businessSectionName,
  795. businessTypes: step2Form.businessTypes.length > 0 ? step2Form.businessTypes : step2Form.businessTypesList,
  796. foodLicenceUrl: foodLicenceUrls.length > 0 ? foodLicenceUrls[0] : "",
  797. userAccount: userInfo.id, // 需要从用户信息中获取
  798. administrativeRegionProvinceAdcode: step2Form.administrativeRegionProvinceAdcode,
  799. administrativeRegionCityAdcode: step2Form.administrativeRegionCityAdcode,
  800. administrativeRegionDistrictAdcode: step2Form.administrativeRegionDistrictAdcode,
  801. storeContact: step1Form.name,
  802. idCard: step1Form.idNumber
  803. };
  804. ElMessageBox.confirm("确认提交入驻申请吗?", "提示", {
  805. confirmButtonText: "确定",
  806. cancelButtonText: "取消",
  807. type: "warning"
  808. })
  809. .then(async () => {
  810. try {
  811. const res: any = await applyStore(params);
  812. if (res && res.code == 200) {
  813. // 重新提交后,状态应该变为等待审核(0)
  814. storeApplicationStatus.value = 0;
  815. setStep(3); // 跳转到等待审核步骤
  816. ElMessage.success(res.msg);
  817. // 通知父组件重新获取用户信息,更新状态
  818. callGetUserInfo();
  819. } else {
  820. ElMessage.error(res.msg || "提交失败");
  821. }
  822. } catch (error) {
  823. ElMessage.error("提交失败,请重试");
  824. }
  825. })
  826. .catch(() => {
  827. // 取消提交
  828. });
  829. } else {
  830. ElMessage.error("请完善表单信息");
  831. }
  832. });
  833. };
  834. // 文件上传超出限制
  835. const handleExceed = () => {
  836. ElMessage.warning("文件数量超出限制");
  837. };
  838. </script>
  839. <style scoped lang="scss">
  840. // 表单页面样式
  841. .form-container {
  842. min-height: calc(100vh - 100px);
  843. padding: 30px;
  844. background: #ffffff;
  845. border-radius: 8px;
  846. .back-btn {
  847. margin-bottom: 30px;
  848. color: #606266;
  849. border-color: #dcdfe6;
  850. }
  851. .progress-container {
  852. margin-bottom: 40px;
  853. :deep(.el-step__head.is-process .el-step__icon) {
  854. color: #909399;
  855. border-color: #909399 !important; /* 设置圆圈边框为灰色 */
  856. }
  857. :deep(.el-steps) {
  858. .el-step__head {
  859. .el-step__icon {
  860. width: 30px;
  861. height: 30px;
  862. font-size: 16px;
  863. font-weight: 600;
  864. }
  865. }
  866. .el-step__title {
  867. .step-title-wrapper {
  868. display: flex;
  869. flex-direction: column;
  870. gap: 8px;
  871. align-items: center;
  872. .step-title {
  873. font-size: 16px;
  874. font-weight: 600;
  875. color: #6c8ff8;
  876. }
  877. }
  878. }
  879. }
  880. }
  881. .form-content {
  882. max-width: 800px;
  883. margin: 0 auto;
  884. &.step2-form {
  885. max-width: 100%;
  886. .form-row {
  887. display: flex;
  888. gap: 40px;
  889. .form-col {
  890. flex: 1;
  891. }
  892. }
  893. }
  894. }
  895. .form-actions {
  896. display: flex;
  897. gap: 20px;
  898. justify-content: center;
  899. padding-top: 30px;
  900. margin-top: 40px;
  901. border-top: 1px solid #e4e7ed;
  902. .el-button {
  903. width: 200px;
  904. height: 44px;
  905. font-size: 16px;
  906. font-weight: 500;
  907. color: #ffffff;
  908. background: #6c8ff8;
  909. border: none;
  910. border-radius: 4px;
  911. outline: none;
  912. }
  913. }
  914. }
  915. .button-container {
  916. display: flex;
  917. align-items: center;
  918. justify-content: center;
  919. .register-btn {
  920. width: 200px;
  921. height: 44px;
  922. font-size: 16px;
  923. font-weight: 500;
  924. background: #6c8ff8;
  925. border: 0;
  926. border-radius: 4px;
  927. outline: none;
  928. }
  929. .register-btn-red {
  930. width: 200px;
  931. height: 44px;
  932. font-size: 16px;
  933. font-weight: 500;
  934. border: 0;
  935. border-radius: 4px;
  936. outline: none;
  937. }
  938. }
  939. </style>