baseInfo.vue 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082
  1. <template>
  2. <div class="p-4">
  3. <!-- 企业基本信息 -->
  4. <el-card shadow="never" class="mb-4">
  5. <template #header>
  6. <div class="flex justify-between items-center">
  7. <span class="font-medium">企业基本信息 </span>
  8. <el-button v-if="!isViewMode" type="primary" style="float: right" @click="handleSave">保存</el-button>
  9. <!-- <span class="font-medium"
  10. >企业基本信息 / <span style="color: #ff0033">客户编号:{{ customerNumber }}</span></span
  11. > -->
  12. </div>
  13. </template>
  14. <el-form ref="formRef" :model="form" :rules="rules" label-width="140px">
  15. <el-row :gutter="20">
  16. <el-col :span="8">
  17. <el-form-item label="所属公司" prop="belongCompanyId">
  18. <el-select
  19. v-model="form.belongCompanyId"
  20. placeholder="请选择所属公司"
  21. class="w-full"
  22. filterable
  23. @change="handCompanyChange"
  24. :disabled="isViewMode"
  25. >
  26. <el-option v-for="item in companyList" :key="item.id" :label="`${item.companyCode} , ${item.companyName}`" :value="item.id" />
  27. </el-select>
  28. </el-form-item>
  29. </el-col>
  30. <el-col :span="8">
  31. <el-form-item label="客户名称" prop="customerName">
  32. <el-input v-model="form.customerName" placeholder="请输入客户名称" :disabled="isViewMode"> </el-input>
  33. </el-form-item>
  34. </el-col>
  35. <el-col :span="8">
  36. <el-form-item label="工商名称" prop="businessCustomerName">
  37. <el-input v-model="form.businessCustomerName" placeholder="请输入工商名称" @blur="selectBusinessBtn" :disabled="isViewMode"> </el-input>
  38. </el-form-item>
  39. </el-col>
  40. </el-row>
  41. <el-row :gutter="20">
  42. <el-col :span="8">
  43. <el-form-item label="企业简称" prop="shortName">
  44. <el-input v-model="form.shortName" placeholder="请输入企业简称" :disabled="isViewMode" />
  45. </el-form-item>
  46. </el-col>
  47. <el-col :span="8">
  48. <el-form-item label="开票类型" prop="invoiceTypeId">
  49. <el-select v-model="form.invoiceTypeId" placeholder="请选择开票类型" class="w-full" :disabled="isViewMode">
  50. <el-option
  51. v-for="item in invoiceTypeList"
  52. :key="item.id"
  53. :label="`${item.invoiceTypeNo} , ${item.invoiceTypeName}`"
  54. :value="item.id"
  55. />
  56. </el-select>
  57. </el-form-item>
  58. </el-col>
  59. <el-col :span="8">
  60. <el-form-item label="企业规模" prop="enterpriseScaleId">
  61. <el-select v-model="form.enterpriseScaleId" placeholder="请选择企业规模" class="w-full" filterable :disabled="isViewMode">
  62. <el-option
  63. v-for="item in enterpriseScaleList"
  64. :key="item.id"
  65. :label="`${item.enterpriseScaleCode} , ${item.enterpriseScaleName}`"
  66. :value="item.id"
  67. />
  68. </el-select>
  69. </el-form-item>
  70. </el-col>
  71. </el-row>
  72. <el-row :gutter="20">
  73. <el-col :span="8">
  74. <el-form-item label="客户类别" prop="customerTypeId">
  75. <el-select v-model="form.customerTypeId" placeholder="请选择客户类别" class="w-full" filterable :disabled="isViewMode">
  76. <el-option v-for="item in customerTypeList" :key="item.id" :label="`${item.typeCode} , ${item.typeName}`" :value="item.id" />
  77. </el-select>
  78. </el-form-item>
  79. </el-col>
  80. <el-col :span="8">
  81. <el-form-item label="行业类别" prop="industryCategoryId">
  82. <el-select v-model="form.industryCategoryId" placeholder="请选择行业类别" class="w-full" filterable :disabled="isViewMode">
  83. <el-option
  84. v-for="item in industryCategoryList"
  85. :key="item.id"
  86. :label="`${item.industryCode} , ${item.industryCategoryName}`"
  87. :value="item.id"
  88. />
  89. </el-select>
  90. </el-form-item>
  91. </el-col>
  92. <el-col :span="8">
  93. <el-form-item label="客户等级" prop="customerLevelId">
  94. <el-select v-model="form.customerLevelId" placeholder="请选择客户等级" class="w-full" filterable :disabled="isViewMode">
  95. <el-option v-for="item in customerLevelList" :key="item.id" :label="`${item.levelCode} , ${item.levelName}`" :value="item.id" />
  96. </el-select>
  97. </el-form-item>
  98. </el-col>
  99. </el-row>
  100. <el-row :gutter="20">
  101. <el-col :span="8">
  102. <el-form-item label="固定电话" prop="landline">
  103. <el-input v-model="form.landline" placeholder="请输入固定电话" :disabled="isViewMode" />
  104. </el-form-item>
  105. </el-col>
  106. <el-col :span="8">
  107. <el-form-item label="传真" prop="fax">
  108. <el-input v-model="form.fax" placeholder="请输入传真" :disabled="isViewMode" />
  109. </el-form-item>
  110. </el-col>
  111. <el-col :span="8">
  112. <el-form-item label="网址" prop="url">
  113. <el-input v-model="form.url" placeholder="请输入网址" :disabled="isViewMode" />
  114. </el-form-item>
  115. </el-col>
  116. </el-row>
  117. <el-row :gutter="20">
  118. <el-col :span="8">
  119. <el-form-item label="邮政编码" prop="postCode">
  120. <el-input v-model="form.postCode" placeholder="请输入邮政编码" :disabled="isViewMode" />
  121. </el-form-item>
  122. </el-col>
  123. <el-col :span="8">
  124. <el-form-item label="开始时间" prop="validityFromDate">
  125. <el-date-picker
  126. v-model="form.validityFromDate"
  127. type="date"
  128. placeholder="请选择开始时间"
  129. class="w-full"
  130. value-format="YYYY-MM-DD"
  131. style="width: 100%"
  132. :disabled="isViewMode"
  133. />
  134. </el-form-item>
  135. </el-col>
  136. <el-col :span="8">
  137. <el-form-item label="结束时间" prop="validityToDate">
  138. <el-date-picker
  139. v-model="form.validityToDate"
  140. type="date"
  141. placeholder="请选择结束时间"
  142. class="w-full"
  143. value-format="YYYY-MM-DD"
  144. style="width: 100%"
  145. :disabled-date="(time) => form.validityFromDate && time.getTime() < new Date(form.validityFromDate).getTime()"
  146. :disabled="isViewMode"
  147. />
  148. </el-form-item>
  149. </el-col>
  150. </el-row>
  151. <el-row :gutter="20">
  152. <!-- <el-col :span="8">
  153. <el-form-item label="发票抬头" prop="invoiceTop">
  154. <el-input v-model="form.invoiceTop" placeholder="请输入发票抬头" disabled />
  155. </el-form-item>
  156. </el-col> -->
  157. <el-col :span="8">
  158. <el-form-item label="详细地址" prop="address">
  159. <el-cascader
  160. v-model="codeArr"
  161. :options="regionData as any"
  162. placeholder="请选择"
  163. @change="handleChange"
  164. style="width: 100%"
  165. :disabled="isViewMode"
  166. ></el-cascader>
  167. </el-form-item>
  168. </el-col>
  169. <el-col :span="8">
  170. <el-form-item label-width="0">
  171. <el-input v-model="form.address" placeholder="请输入详细地址" :disabled="isViewMode" />
  172. </el-form-item>
  173. </el-col>
  174. </el-row>
  175. </el-form>
  176. </el-card>
  177. <!-- 工商信息 -->
  178. <el-card shadow="never" class="mb-4">
  179. <template #header>
  180. <div class="flex justify-between items-center">
  181. <span class="font-medium">工商信息</span>
  182. </div>
  183. </template>
  184. <el-form :model="businessInfo" label-width="140px">
  185. <el-row :gutter="20">
  186. <el-col :span="8">
  187. <el-form-item label="企业工商名称">
  188. <el-input v-model="businessInfo.businessCustomerName" disabled />
  189. </el-form-item>
  190. </el-col>
  191. <el-col :span="8">
  192. <el-form-item label="社会信用代码">
  193. <el-input v-model="businessInfo.socialCreditCode" disabled />
  194. </el-form-item>
  195. </el-col>
  196. <el-col :span="8">
  197. <el-form-item label="法人姓名">
  198. <el-input v-model="businessInfo.legalPersonName" disabled />
  199. </el-form-item>
  200. </el-col>
  201. </el-row>
  202. <el-row :gutter="20">
  203. <el-col :span="8">
  204. <el-form-item label="注册资本">
  205. <el-input v-model="businessInfo.registeredCapital" disabled />
  206. </el-form-item>
  207. </el-col>
  208. <el-col :span="8">
  209. <el-form-item label="登记机关">
  210. <el-input v-model="businessInfo.registrationAuthority" disabled />
  211. </el-form-item>
  212. </el-col>
  213. <el-col :span="8">
  214. <el-form-item label="成立日期">
  215. <el-input v-model="businessInfo.establishmentDate" disabled />
  216. </el-form-item>
  217. </el-col>
  218. </el-row>
  219. <el-row :gutter="20">
  220. <el-col :span="8">
  221. <el-form-item label="吊销日期">
  222. <el-input v-model="businessInfo.revocationDate" disabled />
  223. </el-form-item>
  224. </el-col>
  225. <el-col :span="8">
  226. <el-form-item label="登记状态">
  227. <el-input v-model="businessInfo.registrationStatus" disabled />
  228. </el-form-item>
  229. </el-col>
  230. <el-col :span="8">
  231. <el-form-item label="实缴资本">
  232. <el-input v-model="businessInfo.paidInCapital" disabled />
  233. </el-form-item>
  234. </el-col>
  235. </el-row>
  236. <el-row :gutter="20">
  237. <el-col :span="16">
  238. <el-form-item label="详细地址">
  239. <el-input v-model="businessInfo.businessAddress" disabled />
  240. </el-form-item>
  241. </el-col>
  242. </el-row>
  243. <el-row :gutter="20">
  244. <!-- <el-col :span="24">
  245. <el-form-item label="营业执照">
  246. <div
  247. v-if="!businessInfo.businessLicense"
  248. class="upload-box"
  249. @click="businessLicenseSelectorVisible = true"
  250. style="
  251. width: 360px;
  252. height: 200px;
  253. border: 2px dashed #d9d9d9;
  254. border-radius: 4px;
  255. display: flex;
  256. align-items: center;
  257. justify-content: center;
  258. cursor: pointer;
  259. transition: all 0.3s;
  260. "
  261. >
  262. <div style="text-align: center; color: #8c939d">
  263. <el-icon :size="40" style="margin-bottom: 8px">
  264. <Plus />
  265. </el-icon>
  266. <div style="font-size: 14px">点击上传</div>
  267. </div>
  268. </div>
  269. <div v-else style="position: relative; display: inline-block">
  270. <el-image
  271. :src="businessInfo.businessLicense"
  272. style="width: 360px; height: 200px"
  273. fit="contain"
  274. :preview-src-list="[businessInfo.businessLicense]"
  275. />
  276. <el-button
  277. type="danger"
  278. :icon="Delete"
  279. circle
  280. size="small"
  281. style="position: absolute; top: 5px; right: 5px"
  282. @click="businessInfo.businessLicense = ''"
  283. />
  284. </div>
  285. </el-form-item>
  286. </el-col> -->
  287. </el-row>
  288. </el-form>
  289. </el-card>
  290. <!-- 公司开票信息 -->
  291. <el-card shadow="never" class="mb-4">
  292. <template #header>
  293. <div class="flex justify-between items-center">
  294. <span class="font-medium">企业开票信息</span>
  295. <el-button v-if="!isViewMode" type="primary" @click="handleAddInvoice">新增</el-button>
  296. </div>
  297. </template>
  298. <el-table :data="invoiceList" border>
  299. <el-table-column type="index" label="序号" align="center" width="60" />
  300. <el-table-column label="是否主账号" align="center" prop="isPrimaryAccount" min-width="120">
  301. <template #default="{ row }">
  302. <span>{{ row.isPrimaryAccount === '0' ? '是' : '否' }}</span>
  303. </template>
  304. </el-table-column>
  305. <el-table-column label="开户行名称" align="center" prop="bankName" min-width="180" />
  306. <el-table-column label="银行账户" align="center" prop="bankAccount" min-width="180" />
  307. <el-table-column v-if="!isViewMode" label="操作" align="center" width="150" fixed="right">
  308. <template #default="{ row, $index }">
  309. <el-button link type="primary" @click="handleEditInvoice(row, $index)">编辑</el-button>
  310. <el-button link type="danger" @click="removeInvoice(row)">删除</el-button>
  311. </template>
  312. </el-table-column>
  313. </el-table>
  314. </el-card>
  315. <el-card shadow="never" class="mb-4">
  316. <template #header>
  317. <el-row :gutter="10" class="mb8" type="flex" justify="space-between" align="middle">
  318. <span style="font-size: 16px; font-weight: 500">销售信息</span>
  319. </el-row>
  320. </template>
  321. <el-form ref="salesFormRef" :model="form" label-width="140px">
  322. <el-row :gutter="20">
  323. <el-col :span="8">
  324. <el-form-item label="业务人员" prop="salesPersonId">
  325. <el-select
  326. v-model="form.salesPersonId"
  327. placeholder="请选择业务人员"
  328. class="w-full"
  329. filterable
  330. @change="handleSalesPersonChange"
  331. :disabled="isViewMode"
  332. >
  333. <el-option v-for="item in comStaffList" :key="item.staffId" :label="`${item.staffCode} , ${item.staffName}`" :value="item.staffId" />
  334. </el-select>
  335. </el-form-item>
  336. </el-col>
  337. <el-col :span="8">
  338. <el-form-item label="客服人员" prop="serviceStaffId">
  339. <el-select v-model="form.serviceStaffId" placeholder="请选择客服人员" class="w-full" filterable :disabled="isViewMode">
  340. <el-option v-for="item in comStaffList" :key="item.staffId" :label="`${item.staffCode} , ${item.staffName}`" :value="item.staffId" />
  341. </el-select>
  342. </el-form-item>
  343. </el-col>
  344. <el-col :span="8">
  345. <el-form-item label="所属部门" prop="belongingDepartmentId">
  346. <el-input v-model="deptName" placeholder="请选择所属部门" class="w-full" disabled />
  347. </el-form-item>
  348. </el-col>
  349. </el-row> </el-form
  350. ></el-card>
  351. <!-- 添加/编辑开票信息对话框 -->
  352. <add-invoice-dialog v-model="invoiceDialogVisible" :edit-data="currentInvoice" @confirm="handleInvoiceConfirm" />
  353. <!-- 营业执照选择器对话框 -->
  354. <FileSelector
  355. v-model="businessLicenseSelectorVisible"
  356. title="选择营业执照"
  357. :allowed-types="[1]"
  358. :multiple="false"
  359. :allow-upload="true"
  360. @confirm="handleBusinessLicenseSelected"
  361. />
  362. </div>
  363. </template>
  364. <script setup lang="ts" name="BaseInfo">
  365. import { getCustomerInfo, updateCustomerInfo } from '@/api/customer/customerFile/customerInfo';
  366. import type { CustomerInfoForm } from '@/api/customer/customerFile/customerInfo/types';
  367. import type { BusinessInfoForm } from '@/api/customer/customerFile/businessInfo/types';
  368. import type { InvoiceInfoForm } from '@/api/customer/customerFile/invoiceInfo/types';
  369. import { listInvoiceInfo, getInvoiceInfo, addInvoiceInfo, updateInvoiceInfo, delInvoiceInfo } from '@/api/customer/customerFile/invoiceInfo';
  370. import { listSettlementMethod } from '@/api/customer/settlementMethod';
  371. import { SettlementMethodVO } from '@/api/customer/settlementMethod/types';
  372. import { listCustomerLevel } from '@/api/customer/customerLevel';
  373. import { CustomerLevelVO } from '@/api/customer/customerLevel/types';
  374. import { listCustomerType } from '@/api/customer/customerType';
  375. import { CustomerTypeVO, CustomerTypeQuery } from '@/api/customer/customerType/types';
  376. import { getBusinessInfoBycustomerName } from '@/api/customer/customerFile/businessInfo';
  377. import { listCreditLevel } from '@/api/customer/creditLevel';
  378. import { CreditLevelVO, CreditLevelQuery } from '@/api/customer/creditLevel/types';
  379. import AddInvoiceDialog from '../components/addInvoiceDialog.vue';
  380. import { regionData } from 'element-china-area-data';
  381. import { listEnterpriseScale } from '@/api/customer/customerCategory/enterpriseScale';
  382. import { listIndustryCategory } from '@/api/customer/customerCategory/industryCategory';
  383. import { listInvoiceType } from '@/api/customer/invoiceType';
  384. import type { EnterpriseScaleVO } from '@/api/customer/customerCategory/enterpriseScale/types';
  385. import type { IndustryCategoryVO } from '@/api/customer/customerCategory/industryCategory/types';
  386. import type { InvoiceTypeVO } from '@/api/customer/invoiceType/types';
  387. import { listCompany } from '@/api/company/company';
  388. import { CompanyVO } from '@/api/company/company/types';
  389. import { listComStaff } from '@/api/company/comStaff';
  390. import { listErpStaff } from '@/api/erpData/erpStaff';
  391. import { ComStaffVO, ComStaffQuery } from '@/api/company/comStaff/types';
  392. import { ErpStaffVO } from '@/api/erpData/erpStaff/types';
  393. import { listDept, getDept } from '@/api/system/dept';
  394. import { DeptVO } from '@/api/system/dept/types';
  395. import { listErpDept, getErpDept } from '@/api/erpData/erpDept';
  396. import { ErpDeptVO } from '@/api/erpData/erpDept/types';
  397. import FileSelector from '@/components/FileSelector/index.vue';
  398. import { Plus, Delete } from '@element-plus/icons-vue';
  399. import { listComCurrency } from '@/api/company/comCurrency';
  400. import { listTaxrate } from '@/api/company/taxrate';
  401. import { ComCurrencyVO } from '@/api/company/comCurrency/types';
  402. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  403. const { order_check_way, customer_type, customer_level, customer_source, sell_channel, erp_is_enabled } = toRefs<any>(
  404. proxy?.useDict('order_check_way', 'customer_type', 'customer_level', 'customer_source', 'sell_channel', 'erp_is_enabled')
  405. );
  406. // 接收父组件传递的props
  407. const props = defineProps<{
  408. customerId?: string | number;
  409. customerNo?: string;
  410. }>();
  411. const route = useRoute();
  412. const router = useRouter();
  413. // 查看模式
  414. const isViewMode = computed(() => route.query.status === 'view');
  415. const unitPriceArr = ref([
  416. { label: '含税', value: 'True' },
  417. { label: '不含税', value: 'False' }
  418. ]);
  419. const formRef = ref<any>(null);
  420. const salesFormRef = ref<any>(null);
  421. const submitLoading = ref(false);
  422. const custId = ref<string | number>('');
  423. const customerNumber = ref('');
  424. const codeArr = ref([]);
  425. // 下拉框数据列表
  426. const enterpriseScaleList = ref<EnterpriseScaleVO[]>([]);
  427. const industryCategoryList = ref<IndustryCategoryVO[]>([]);
  428. const invoiceTypeList = ref<InvoiceTypeVO[]>([]);
  429. const companyList = ref<CompanyVO[]>([]);
  430. const settlementMethodList = ref<SettlementMethodVO[]>([]);
  431. const creditLevelList = ref<CreditLevelVO[]>([]);
  432. const customerLevelList = ref<CustomerLevelVO[]>([]);
  433. const customerTypeList = ref<CustomerTypeVO[]>([]);
  434. const comStaffList = ref<ErpStaffVO[]>([]);
  435. const comDeptList = ref<ErpDeptVO[]>([]);
  436. const currencyList = ref<ComCurrencyVO[]>([]);
  437. const taxrateList = ref<any[]>([]);
  438. // 企业基本信息(用于显示)
  439. const customerInfo = reactive<any>({
  440. customerNo: '',
  441. belongCompanyName: '',
  442. companyName: '',
  443. businessCustomerName: '',
  444. shortName: '',
  445. invoiceTypeName: '',
  446. enterpriseScale: '',
  447. customerTypeName: '',
  448. industryCategory: '',
  449. customerLevelName: '',
  450. landline: '',
  451. fax: '',
  452. url: '',
  453. postCode: '',
  454. validityFromDate: '',
  455. validityToDate: '',
  456. invoiceTop: '',
  457. provincialCityCounty: '',
  458. address: ''
  459. });
  460. // 企业基本信息表单(用于编辑)
  461. const form = reactive<CustomerInfoForm>({
  462. id: undefined,
  463. customerNo: '',
  464. belongCompanyId: undefined,
  465. companyName: '',
  466. businessCustomerName: '',
  467. shortName: '',
  468. invoiceTypeId: undefined,
  469. enterpriseScaleId: undefined,
  470. customerTypeId: undefined,
  471. industryCategoryId: undefined,
  472. customerLevelId: undefined,
  473. landline: '',
  474. fax: '',
  475. url: '',
  476. postCode: '',
  477. validityFromDate: undefined,
  478. validityToDate: undefined,
  479. invoiceTop: '',
  480. address: '',
  481. regProvincialNo: '',
  482. regCityNo: '',
  483. regCountyNo: '',
  484. provincialCityCounty: '',
  485. status: '0',
  486. remark: '',
  487. salesPersonId: undefined,
  488. serviceStaffId: undefined,
  489. belongingDepartmentId: undefined
  490. });
  491. // 工商信息(只读)
  492. const businessInfo = reactive<BusinessInfoForm>({
  493. businessCustomerName: '',
  494. socialCreditCode: '',
  495. legalPersonName: '',
  496. registeredCapital: '',
  497. registrationAuthority: '',
  498. establishmentDate: '',
  499. revocationDate: '',
  500. registrationStatus: '',
  501. paidInCapital: undefined,
  502. businessAddress: '',
  503. businessLicense: '',
  504. status: '0'
  505. });
  506. // 开票信息列表
  507. const invoiceList = ref<InvoiceInfoForm[]>([]);
  508. const invoiceDialogVisible = ref(false);
  509. const currentInvoice = ref<InvoiceInfoForm | undefined>(undefined);
  510. const currentInvoiceIndex = ref<number>(-1);
  511. // 营业执照上传相关
  512. const uploadRef = ref();
  513. const uploadLoading = ref(false);
  514. const businessLicenseSelectorVisible = ref(false);
  515. const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/common/upload';
  516. const uploadHeaders = ref({
  517. Authorization: 'Bearer ' + localStorage.getItem('token')
  518. });
  519. // 打开添加开票信息对话框
  520. const handleAddInvoice = () => {
  521. currentInvoice.value = {};
  522. currentInvoice.value.taxId = businessInfo.socialCreditCode;
  523. currentInvoice.value.address = businessInfo.businessAddress;
  524. currentInvoiceIndex.value = -1;
  525. invoiceDialogVisible.value = true;
  526. };
  527. // 更新工商信息
  528. const handleUpdate = () => {
  529. // 触发上传
  530. uploadRef.value?.$el.querySelector('input[type="file"]')?.click();
  531. };
  532. // 上传前验证
  533. const beforeUpload = (file: File) => {
  534. const isImage = file.type.startsWith('image/');
  535. const isPDF = file.type === 'application/pdf';
  536. const isLt10M = file.size / 1024 / 1024 < 10;
  537. if (!isImage && !isPDF) {
  538. ElMessage.error('只能上传图片或PDF文件!');
  539. return false;
  540. }
  541. if (!isLt10M) {
  542. ElMessage.error('文件大小不能超过 10MB!');
  543. return false;
  544. }
  545. uploadLoading.value = true;
  546. return true;
  547. };
  548. // 处理业务人员选择变化
  549. const handleSalesPersonChange = async (staffId: any) => {
  550. // 根据选中的业务人员ID,找到对应的部门ID
  551. const selectedStaff = comStaffList.value.find((staff) => staff.staffId === staffId);
  552. if (selectedStaff && selectedStaff.deptId) {
  553. // 确保 deptId 的类型一致
  554. form.belongingDepartmentId = String(selectedStaff.deptId);
  555. // 如果部门不在列表中,从API获取
  556. const deptExists = comDeptList.value.find((d) => String(d.deptId) === String(selectedStaff.deptId));
  557. if (!deptExists) {
  558. try {
  559. const res = await getDept(selectedStaff.deptId);
  560. if (res.data) {
  561. comDeptList.value.push(res.data as any);
  562. }
  563. } catch (error) {
  564. console.error('获取部门信息失败:', error);
  565. }
  566. }
  567. }
  568. };
  569. // 上传成功回调
  570. const handleUploadSuccess = (response: any) => {
  571. uploadLoading.value = false;
  572. if (response.code === 200) {
  573. businessInfo.businessLicense = response.data.url || response.data.fileName;
  574. ElMessage.success('上传成功');
  575. // TODO: 这里可以调用OCR识别接口,自动填充工商信息
  576. // 如果后端提供了OCR识别接口,可以在这里调用
  577. // recognizeBusinessLicense(response.data.url);
  578. } else {
  579. ElMessage.error(response.msg || '上传失败');
  580. }
  581. };
  582. // 上传失败回调
  583. const handleUploadError = () => {
  584. uploadLoading.value = false;
  585. ElMessage.error('上传失败,请重试');
  586. };
  587. // 预览营业执照
  588. const previewLicense = () => {
  589. if (businessInfo.businessLicense) {
  590. window.open(businessInfo.businessLicense, '_blank');
  591. }
  592. };
  593. // 营业执照选择处理
  594. const handleBusinessLicenseSelected = (files: any[]) => {
  595. if (files && files.length > 0) {
  596. const file = files[0]; // 取第一个文件
  597. if (file && (file.url || file.path)) {
  598. businessInfo.businessLicense = file.url || file.path;
  599. ElMessage.success('营业执照选择成功');
  600. } else {
  601. ElMessage.error('请选择有效的图片文件');
  602. }
  603. } else {
  604. ElMessage.error('请选择有效的图片文件');
  605. }
  606. };
  607. // 表单验证规则
  608. const rules = {
  609. belongCompanyId: [{ required: true, message: '请选择所属公司', trigger: 'change' }],
  610. customerName: [{ required: true, message: '请输入客户名称', trigger: 'blur' }],
  611. businessCustomerName: [
  612. { required: true, message: '请输入工商名称', trigger: 'blur' },
  613. {
  614. // 允许:中文 + 常见标点 (括号、中圆点、横杠)
  615. // 禁止:数字、字母、空格、@#$%等其他符号
  616. pattern: /^[\u4e00-\u9fa5()()·\-]+$/,
  617. message: '名称不能包含数字、字母或特殊符号,仅限中文及常用标点',
  618. trigger: 'blur'
  619. }
  620. ],
  621. shortName: [{ required: true, message: '请输入企业简称', trigger: 'blur' }],
  622. invoiceTypeId: [{ required: true, message: '请选择开票类型', trigger: 'change' }],
  623. enterpriseScaleId: [{ required: true, message: '请选择企业规模', trigger: 'change' }],
  624. customerTypeId: [{ required: true, message: '请选择客户类别', trigger: 'change' }],
  625. industryCategoryId: [{ required: true, message: '请选择行业类别', trigger: 'change' }],
  626. customerLevelId: [{ required: true, message: '请选择客户等级', trigger: 'change' }],
  627. address: [{ required: true, message: '请输入详细地址', trigger: 'blur' }]
  628. // salesPersonId: [{ required: true, message: '请选择业务人员', trigger: 'change' }],
  629. // serviceStaffId: [{ required: true, message: '请选择客服人员', trigger: 'change' }]
  630. };
  631. // 初始化
  632. onMounted(async () => {
  633. // 加载下拉框数据
  634. await loadEnterpriseScaleList();
  635. await loadIndustryCategoryList();
  636. await loadInvoiceTypeList();
  637. await loadCompanyList();
  638. await loadSettlementMethodList();
  639. await loadCreditLevelList();
  640. await loadCustomerLevelList();
  641. await loadCustomerTypeList();
  642. await loadComStaffList();
  643. await loadComDeptList();
  644. // 优先使用props传递的customerId,否则从路由获取
  645. const id = props.customerId || route.query.id;
  646. if (id) {
  647. custId.value = id as string;
  648. await loadCustomerData(id as any);
  649. }
  650. });
  651. const handCompanyChange = async (val) => {
  652. try {
  653. try {
  654. // 1. 处理清空情况
  655. if (!val) {
  656. form.companyName = '';
  657. return;
  658. }
  659. // 2. 在本地列表中查找完整对象
  660. const selectedCompany = companyList.value.find((item) => item.id === val);
  661. if (selectedCompany) {
  662. // 3. 赋值操作
  663. form.companyName = selectedCompany.companyName;
  664. } else {
  665. // 如果本地列表没找到(可能是数据不同步),可以选择清空或调用接口查询
  666. form.companyName = '';
  667. }
  668. } catch (error) {}
  669. } catch (error) {}
  670. };
  671. // 监听props变化
  672. watch(
  673. () => props.customerId,
  674. (newId) => {
  675. if (newId) {
  676. custId.value = newId;
  677. loadCustomerData(newId);
  678. }
  679. }
  680. );
  681. const selectBusinessBtn = async () => {
  682. try {
  683. // 验证基本信息表单
  684. await formRef.value.validateField('businessCustomerName');
  685. const res = await getBusinessInfoBycustomerName(form.businessCustomerName);
  686. const data = res.data;
  687. // 填充信息
  688. Object.assign(businessInfo, data);
  689. form.invoiceTop = data.businessCustomerName;
  690. form.businessCustomerName = data.businessCustomerName;
  691. } catch (error) {
  692. // ElMessage.error('查询工商信息失败');
  693. }
  694. };
  695. // 加载企业规模列表
  696. const loadEnterpriseScaleList = async () => {
  697. try {
  698. const res = await listEnterpriseScale({ dataSource: 'A10' } as any);
  699. enterpriseScaleList.value = res.rows || [];
  700. } catch (error) {
  701. console.error('加载企业规模列表失败:', error);
  702. }
  703. };
  704. // 加载行业类别列表
  705. const loadIndustryCategoryList = async () => {
  706. try {
  707. const res = await listIndustryCategory({ dataSource: 'A10' } as any);
  708. industryCategoryList.value = res.rows || [];
  709. } catch (error) {
  710. console.error('加载行业类别列表失败:', error);
  711. }
  712. };
  713. // 加载开票类型列表
  714. const loadInvoiceTypeList = async () => {
  715. try {
  716. const res = await listInvoiceType({ dataSource: 'A10' } as any);
  717. invoiceTypeList.value = res.rows || [];
  718. } catch (error) {
  719. console.error('加载开票类型列表失败:', error);
  720. }
  721. };
  722. // 加载公司列表
  723. const loadCompanyList = async () => {
  724. try {
  725. const query: any = { isShow: '0', dataSource: 'A10' };
  726. const res = await listCompany(query);
  727. companyList.value = res.rows || [];
  728. } catch (error) {
  729. console.error('加载公司列表失败:', error);
  730. }
  731. };
  732. // 加载结算方式列表
  733. const loadSettlementMethodList = async () => {
  734. try {
  735. const query: any = { isShow: '0' };
  736. const res = await listSettlementMethod(query);
  737. settlementMethodList.value = res.rows || [];
  738. } catch (error) {
  739. console.error('加载结算方式列表失败:', error);
  740. }
  741. };
  742. // 加载信用等级列表
  743. const loadCreditLevelList = async () => {
  744. try {
  745. const query: any = { dataSource: 'A10' };
  746. const res = await listCreditLevel(query);
  747. creditLevelList.value = res.rows || [];
  748. } catch (error) {
  749. console.error('加载信用等级列表失败:', error);
  750. }
  751. };
  752. // 加载客户等级列表
  753. const loadCustomerLevelList = async () => {
  754. try {
  755. const res = await listCustomerLevel({ dataSource: 'A10' } as any);
  756. customerLevelList.value = res.rows || [];
  757. } catch (error) {
  758. console.error('加载客户等级列表失败:', error);
  759. }
  760. };
  761. // 加载客户类别列表
  762. const loadCustomerTypeList = async () => {
  763. try {
  764. const res = await listCustomerType({ dataSource: 'A10' } as any);
  765. customerTypeList.value = res.rows || [];
  766. } catch (error) {
  767. console.error('加载客户类别列表失败:', error);
  768. }
  769. };
  770. // 加载员工列表
  771. const loadComStaffList = async () => {
  772. try {
  773. const query: any = {};
  774. const res = await listComStaff(query);
  775. comStaffList.value = res.rows || [];
  776. } catch (error) {
  777. console.error('加载员工列表失败:', error);
  778. }
  779. };
  780. // 加载部门列表
  781. const loadComDeptList = async () => {
  782. try {
  783. const res = await listDept();
  784. // 处理可能的不同返回结构
  785. comDeptList.value = res.rows || res.data || [];
  786. } catch (error) {
  787. console.error('加载部门列表失败:', error);
  788. }
  789. };
  790. // 加载客户数据
  791. const loadCustomerData = async (id: any) => {
  792. try {
  793. const res = await getCustomerInfo(id);
  794. const data = res.data;
  795. // 填充基本信息
  796. customerInfo.customerNo = data.customerNo || '';
  797. customerInfo.belongCompanyName = getCompanyName(data.belongCompanyId);
  798. customerInfo.companyName = data.companyName || '';
  799. customerInfo.businessCustomerName = data.businessCustomerName || '';
  800. customerInfo.shortName = data.shortName || '';
  801. customerInfo.invoiceTypeName = getInvoiceTypeName(data.invoiceTypeId);
  802. customerInfo.enterpriseScale = data.enterpriseScale || '';
  803. customerInfo.customerTypeName = getCustomerTypeName(data.customerTypeId);
  804. customerInfo.industryCategory = data.industryCategory || '';
  805. customerInfo.customerLevelName = getCustomerLevelName(data.customerLevelId);
  806. customerInfo.landline = data.landline || '';
  807. customerInfo.fax = data.fax || '';
  808. customerInfo.url = data.url || '';
  809. customerInfo.postCode = data.postCode || '';
  810. customerInfo.validityFromDate = data.validityFromDate || '';
  811. customerInfo.validityToDate = data.validityToDate || '';
  812. customerInfo.invoiceTop = data.invoiceTop || '';
  813. customerInfo.provincialCityCounty = data.provincialCityCounty || '';
  814. customerInfo.address = data.address || '';
  815. // 填充工商信息
  816. if (data.customerBusinessInfoVo) {
  817. Object.assign(businessInfo, data.customerBusinessInfoVo);
  818. }
  819. // 填充表单数据(用于编辑)
  820. Object.assign(form, data);
  821. customerNumber.value = data.customerNo || '';
  822. // 如果有省市区编码,回显到级联选择器
  823. if (data.regProvincialNo && data.regCityNo && data.regCountyNo) {
  824. codeArr.value = [data.regProvincialNo, data.regCityNo, data.regCountyNo] as any;
  825. }
  826. // 填充开票信息列表
  827. loadInvoiceList(id);
  828. } catch (error) {
  829. console.error('加载客户数据失败:', error);
  830. ElMessage.error('加载客户数据失败');
  831. }
  832. };
  833. // 格式化方法
  834. const getCompanyName = (id: string | number | undefined) => {
  835. const map: Record<string, string> = { '1': '公司A', '2': '公司B', '3': '公司C' };
  836. return map[String(id)] || '-';
  837. };
  838. const getInvoiceTypeName = (id: string | number | undefined) => {
  839. const map: Record<string, string> = {
  840. '1': '增值税专用发票',
  841. '2': '增值税普通发票',
  842. '3': '电子发票'
  843. };
  844. return map[String(id)] || '-';
  845. };
  846. const getCustomerTypeName = (id: string | number | undefined) => {
  847. const map: Record<string, string> = {
  848. '1': '重点客户',
  849. '2': '普通客户',
  850. '3': '潜在客户'
  851. };
  852. return map[String(id)] || '-';
  853. };
  854. const getCustomerLevelName = (id: string | number | undefined) => {
  855. const map: Record<string, string> = { '1': 'A级', '2': 'B级', '3': 'C级', '4': 'D级' };
  856. return map[String(id)] || '-';
  857. };
  858. // 部门名称(响应式)
  859. const deptName = ref('');
  860. // 处理区域选择变化
  861. const handleChange = (val: string[]) => {
  862. // 保存编码
  863. form.regProvincialNo = val[0];
  864. form.regCityNo = val[1];
  865. form.regCountyNo = val[2];
  866. // 根据编码获取名称
  867. const names: string[] = [];
  868. if (val[0]) {
  869. const province = regionData.find((item: any) => item.value === val[0]);
  870. if (province) {
  871. names.push(province.label);
  872. if (val[1] && province.children) {
  873. const city = province.children.find((item: any) => item.value === val[1]);
  874. if (city) {
  875. names.push(city.label);
  876. if (val[2] && city.children) {
  877. const county = city.children.find((item: any) => item.value === val[2]);
  878. if (county) {
  879. names.push(county.label);
  880. }
  881. }
  882. }
  883. }
  884. }
  885. }
  886. // 将省市区名称用斜杠连接
  887. form.provincialCityCounty = names.join('/');
  888. };
  889. // 编辑开票信息
  890. const handleEditInvoice = (row: InvoiceInfoForm, index: number) => {
  891. currentInvoice.value = { ...row };
  892. currentInvoiceIndex.value = index;
  893. invoiceDialogVisible.value = true;
  894. };
  895. // 确认添加/编辑开票信息
  896. const handleInvoiceConfirm = (data: InvoiceInfoForm) => {
  897. // if (currentInvoiceIndex.value >= 0) {
  898. // // 编辑
  899. // invoiceList.value[currentInvoiceIndex.value] = data;
  900. // } else {
  901. // // 新增
  902. // invoiceList.value.push(data);
  903. // }
  904. data.customerId = custId.value;
  905. if (data.id) {
  906. updateInvoiceInfo(data).then(() => {
  907. ElMessage.success('修改成功');
  908. // 刷新列表
  909. loadInvoiceList(custId.value);
  910. });
  911. } else {
  912. addInvoiceInfo(data).then(() => {
  913. ElMessage.success('保存成功');
  914. // 刷新列表
  915. loadInvoiceList(custId.value);
  916. });
  917. }
  918. };
  919. const loadInvoiceList = (customerId: string | number) => {
  920. const params = {
  921. customerId,
  922. pageNum: 1,
  923. pageSize: 50
  924. };
  925. listInvoiceInfo(params).then((res) => {
  926. invoiceList.value = res.rows;
  927. });
  928. };
  929. // 删除开票信息
  930. const removeInvoice = (data: any) => {
  931. ElMessageBox.confirm('确定要删除该开票信息吗?', '提示', {
  932. confirmButtonText: '确定',
  933. cancelButtonText: '取消',
  934. type: 'warning'
  935. })
  936. .then(() => {
  937. delInvoiceInfo(data.id).then(() => {
  938. loadInvoiceList(custId.value);
  939. });
  940. ElMessage.success('删除成功');
  941. })
  942. .catch(() => {});
  943. };
  944. // 保存按钮
  945. const handleSave = async () => {
  946. try {
  947. await formRef.value.validate();
  948. submitLoading.value = true;
  949. const submitData: CustomerInfoForm = {
  950. ...form,
  951. customerBusinessBo: businessInfo,
  952. customerInvoiceInfoBoList: invoiceList.value
  953. };
  954. await updateCustomerInfo(submitData);
  955. ElMessage.success('保存成功');
  956. // 重新加载数据
  957. await loadCustomerData(custId.value);
  958. } catch (error) {
  959. const isValidationError = error && typeof error === 'object' && 'fields' in error;
  960. if (isValidationError) {
  961. console.warn('表单验证未通过', error);
  962. return;
  963. }
  964. ElMessage.error('保存失败,请稍后重试或联系管理员');
  965. } finally {
  966. submitLoading.value = false;
  967. }
  968. };
  969. // 监听部门ID变化,自动加载部门名称
  970. watch(
  971. () => form.belongingDepartmentId,
  972. async (newDeptId) => {
  973. if (!newDeptId) {
  974. deptName.value = '';
  975. return;
  976. }
  977. // 先从列表中查找
  978. const dept = comDeptList.value.find((d) => String(d.deptId) === String(newDeptId));
  979. if (dept) {
  980. deptName.value = dept.deptName;
  981. return;
  982. }
  983. // 如果列表中没有,从API获取
  984. try {
  985. const res = await getDept(newDeptId);
  986. if (res.data) {
  987. deptName.value = res.data.deptName;
  988. comDeptList.value.push(res.data as any);
  989. }
  990. } catch (error) {
  991. console.error('获取部门信息失败:', error);
  992. deptName.value = String(newDeptId);
  993. }
  994. },
  995. { immediate: true }
  996. );
  997. </script>
  998. <style scoped>
  999. .upload-box:hover {
  1000. border-color: #409eff !important; /* 鼠标悬停时变蓝 */
  1001. }
  1002. </style>