index.vue 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927
  1. <template>
  2. <div class="tenant">
  3. <div style="padding: 20px; background: #fff; margin-bottom: 20px">
  4. <el-button type="primary" style="margin-left: 10px" @click="openModal()"
  5. >添加客户</el-button
  6. >
  7. </div>
  8. <byTable
  9. :source="sourceList.data"
  10. :pagination="sourceList.pagination"
  11. :config="config"
  12. :loading="loading"
  13. :selectConfig="selectConfig"
  14. :statConfig="statConfig"
  15. highlight-current-row
  16. @moreSearch="moreSearch"
  17. @get-list="getList"
  18. ref="table"
  19. >
  20. <template #isTop="{ item }">
  21. <div>
  22. <img
  23. style="cursor: pointer; width: 20px; transform: translateY(5px)"
  24. :src="'/img/isTop.png'"
  25. @click="deleteTop(item)"
  26. v-if="item.isTop === 1"
  27. />
  28. <img
  29. style="cursor: pointer; width: 20px; transform: translateY(5px)"
  30. :src="'/img/noTop.png'"
  31. @click="addTop(item)"
  32. v-else
  33. />
  34. </div>
  35. </template>
  36. <template #address="{ item }">
  37. <span>{{ item.countryName }}</span>
  38. <span v-if="item.provinceName"> ,{{ item.provinceName }}</span>
  39. <span v-if="item.cityName"> ,{{ item.cityName }}</span>
  40. </template>
  41. <template #name="{ item }">
  42. <div
  43. style="cursor: pointer; color: #409eff; word-break: break-all"
  44. @click="handleClickName(item)"
  45. >
  46. {{ item.name }}
  47. </div>
  48. </template>
  49. <template #tags="{ item }">
  50. <div style="width: 100%">
  51. <el-tag
  52. style="margin-right: 8px"
  53. type="success"
  54. v-for="(tag, index) in item.tag"
  55. closable
  56. :key="index"
  57. @close="tagClose(tag, item)"
  58. >
  59. {{ dictValueLabel(tag, customerTag) }}
  60. </el-tag>
  61. <template v-if="item.tag.length !== customerTag.length">
  62. <el-select
  63. v-if="item.addTagShow"
  64. v-model="addTag"
  65. style="width: 100%"
  66. @change="
  67. (val) => {
  68. return changeTag(val, item);
  69. }
  70. "
  71. >
  72. <el-option
  73. v-for="tag in customerTag"
  74. :key="tag.value"
  75. :label="tag.label"
  76. :value="tag.value"
  77. :disabled="judgeTagSelect(item.tag, tag.value)"
  78. />
  79. </el-select>
  80. <el-tag
  81. style="cursor: pointer"
  82. type="success"
  83. @click="showSelect(item)"
  84. v-else
  85. >
  86. +
  87. </el-tag>
  88. </template>
  89. </div>
  90. </template>
  91. <template #follow="{ item }">
  92. <div :class="'getWidth' + item.id" style="width: 100%">
  93. <div style="width: 100%; display: flex">
  94. <template
  95. v-if="
  96. item.customerFollowRecordsList &&
  97. item.customerFollowRecordsList.length > 0
  98. "
  99. >
  100. <div
  101. :style="
  102. index > 2
  103. ? 'line-height: 32px; margin-right: 8px; padding: 0 8px; background-color: #eeeeee; border-radius: 4px; cursor: pointer; display: none'
  104. : 'line-height: 32px; margin-right: 8px; padding: 0 8px; background-color: #eeeeee; border-radius: 4px; cursor: pointer'
  105. "
  106. v-for="(record, index) in item.customerFollowRecordsList"
  107. :key="record.id"
  108. >
  109. <el-popover
  110. placement="bottom"
  111. :width="300"
  112. trigger="hover"
  113. @show="recordShow(record)"
  114. >
  115. <template #reference>
  116. <div>
  117. <span v-if="record.date">{{
  118. record.date.substr(0, 10)
  119. }}</span>
  120. <el-icon
  121. style="margin-left: 8px; transform: translateY(2px)"
  122. @click="deleteFollow(record)"
  123. ><DeleteFilled
  124. /></el-icon>
  125. </div>
  126. </template>
  127. <template #default>
  128. <div style="width: 100%">
  129. <div style="color: #909399; margin: 8px 0">
  130. 跟进时间: {{ record.date }}
  131. </div>
  132. <div style="margin: 8px 0">
  133. 跟进人:
  134. {{ dictValueLabel(record.createUser, userList) }}
  135. </div>
  136. <div v-if="record.type == '30'">
  137. <div
  138. style="word-wrap: break-word; margin: 8px 0"
  139. v-html="getStyle(record.content)"
  140. v-if="record.content"
  141. ></div>
  142. <div v-else>跟进记录:</div>
  143. </div>
  144. <div v-else>
  145. <div style="word-wrap: break-word; margin: 8px 0">
  146. <!-- {{ getContent(record) }} -->
  147. <span v-if="record.type === '10'"
  148. >跟进记录:报价单总金额
  149. </span>
  150. <span v-if="record.type === '20'"
  151. >跟进记录:合同总金额
  152. </span>
  153. <span>{{ moneyFormat(record.amount, 2) }}</span>
  154. <span
  155. v-if="record.type === '10' && record.code"
  156. :class="{ 'code-class': isHave }"
  157. @click="
  158. isHaveOne ? handlePushRoute(record) : () => {}
  159. "
  160. >({{ record.code }})</span
  161. >
  162. <span
  163. v-if="record.type === '20' && record.contractCode"
  164. :class="{ 'code-class': isHaveOne }"
  165. @click="isHave ? handlePushRoute(record) : () => {}"
  166. >({{ record.contractCode }})</span
  167. >
  168. </div>
  169. </div>
  170. <div
  171. style="margin: 8px 0; display: flex"
  172. v-if="record.fileList && record.fileList.length > 0"
  173. >
  174. <div style="width: 36px">附件:</div>
  175. <div style="width: calc(100% - 36px)">
  176. <div
  177. v-for="(file, index) in record.fileList"
  178. :key="index"
  179. >
  180. <a
  181. style="color: #409eff; cursor: pointer"
  182. @click="openFile(file.fileUrl)"
  183. >{{ file.fileName }}</a
  184. >
  185. </div>
  186. </div>
  187. </div>
  188. </div>
  189. </template>
  190. </el-popover>
  191. </div>
  192. <div
  193. style="
  194. line-height: 32px;
  195. margin-right: 8px;
  196. padding: 0 8px;
  197. background-color: #eeeeee;
  198. border-radius: 4px;
  199. cursor: pointer;
  200. "
  201. @click="clickMore(item)"
  202. v-if="item.customerFollowRecordsList.length >= 3"
  203. >
  204. 更多
  205. </div>
  206. </template>
  207. </div>
  208. </div>
  209. </template>
  210. </byTable>
  211. <el-dialog
  212. :title="modalType == 'add' ? '新增' : '编辑'"
  213. v-if="dialogVisible"
  214. v-model="dialogVisible"
  215. width="800"
  216. v-loading="loadingOperation"
  217. >
  218. <byForm
  219. :formConfig="formConfig"
  220. :formOption="formOption"
  221. v-model="formData.data"
  222. :rules="rules"
  223. ref="submit"
  224. >
  225. <template #allAddress>
  226. <el-row style="width: 100%">
  227. <el-col :span="8">
  228. <el-form-item prop="countryId">
  229. <el-select
  230. v-model="formData.data.countryId"
  231. placeholder="国家"
  232. filterable
  233. @change="(val) => getCityData(val, '20', true)"
  234. >
  235. <el-option
  236. v-for="item in countryData"
  237. :label="item.chineseName"
  238. :value="item.id"
  239. >
  240. </el-option>
  241. </el-select>
  242. </el-form-item>
  243. </el-col>
  244. <el-col :span="8">
  245. <el-form-item prop="provinceName">
  246. <selectCity
  247. placeholder="省/洲"
  248. @change="(val) => getCityData(val, '30', true)"
  249. addressId="provinceId"
  250. addressName="provinceName"
  251. v-model="formData.data"
  252. :data="provinceData"
  253. >
  254. </selectCity>
  255. </el-form-item>
  256. </el-col>
  257. <el-col :span="8">
  258. <el-form-item prop="cityName">
  259. <selectCity
  260. placeholder="城市"
  261. addressId="cityId"
  262. addressName="cityName"
  263. v-model="formData.data"
  264. :data="cityData"
  265. ></selectCity>
  266. </el-form-item>
  267. </el-col>
  268. </el-row>
  269. <el-row style="margin-top: 20px; width: 100%">
  270. <el-col :span="24">
  271. <el-form-item prop="address">
  272. <el-input v-model="formData.data.address" type="textarea">
  273. </el-input>
  274. </el-form-item>
  275. </el-col>
  276. </el-row>
  277. </template>
  278. <template #person>
  279. <div style="width: 100%">
  280. <el-button type="primary" @click="clickAddPerson">添 加</el-button>
  281. <el-table
  282. :data="formData.data.customerUserList"
  283. style="width: 100%; margin-top: 16px"
  284. >
  285. <el-table-column label="联系人" width="160">
  286. <template #default="{ row, $index }">
  287. <div style="width: 100%">
  288. <el-form-item
  289. :prop="'customerUserList.' + $index + '.name'"
  290. :rules="rules.name2"
  291. :inline-message="true"
  292. >
  293. <el-input v-model="row.name" placeholder="请输入联系人" />
  294. </el-form-item>
  295. </div>
  296. </template>
  297. </el-table-column>
  298. <el-table-column label="电子邮箱">
  299. <template #default="{ row, $index }">
  300. <div style="width: 100%">
  301. <el-form-item
  302. :prop="'customerUserList.' + $index + '.email'"
  303. :rules="rules.email"
  304. :inline-message="true"
  305. >
  306. <el-input
  307. v-model="row.email"
  308. placeholder="请输入电子邮箱"
  309. />
  310. </el-form-item>
  311. </div>
  312. </template>
  313. </el-table-column>
  314. <el-table-column
  315. align="center"
  316. label="操作"
  317. width="120"
  318. fixed="right"
  319. >
  320. <template #default="{ row, $index }">
  321. <el-button
  322. type="primary"
  323. link
  324. @click="clickInformationMore(row, $index)"
  325. >更多</el-button
  326. >
  327. <el-button type="primary" link @click="clickDelete($index)"
  328. >删除</el-button
  329. >
  330. </template>
  331. </el-table-column>
  332. </el-table>
  333. </div>
  334. </template>
  335. </byForm>
  336. <template #footer>
  337. <el-button @click="dialogVisible = false" size="large">取 消</el-button>
  338. <el-button
  339. type="primary"
  340. @click="submitForm()"
  341. size="large"
  342. :loading="submitLoading"
  343. >确 定</el-button
  344. >
  345. </template>
  346. </el-dialog>
  347. <el-dialog
  348. title="更多联系方式"
  349. v-if="openPerson"
  350. v-model="openPerson"
  351. width="700"
  352. >
  353. <el-form
  354. :label-position="'top'"
  355. :model="formPerson.data"
  356. :rules="rulesPerson"
  357. ref="person"
  358. >
  359. <el-form-item label="联系人" prop="name">
  360. <el-input v-model="formPerson.data.name" />
  361. </el-form-item>
  362. <el-form-item label="电子邮箱" prop="email">
  363. <el-input v-model="formPerson.data.email" />
  364. </el-form-item>
  365. <el-form-item label="更多联系方式">
  366. <div style="width: 100%">
  367. <el-button type="primary" @click="clickAddMoreInformation"
  368. >添 加</el-button
  369. >
  370. <el-table
  371. :data="formPerson.data.contact"
  372. style="width: 100%; margin-top: 16px"
  373. >
  374. <el-table-column label="类型" width="180">
  375. <template #default="{ row, $index }">
  376. <div style="width: 100%">
  377. <el-form-item
  378. :prop="'contact.' + $index + '.type'"
  379. :rules="rulesPerson.type"
  380. :inline-message="true"
  381. >
  382. <el-select
  383. v-model="row.type"
  384. placeholder="请选择类型"
  385. style="width: 100%"
  386. >
  387. <el-option
  388. v-for="item in contactType"
  389. :key="item.value"
  390. :label="item.label"
  391. :value="item.value"
  392. />
  393. </el-select>
  394. </el-form-item>
  395. </div>
  396. </template>
  397. </el-table-column>
  398. <el-table-column label="联系号码">
  399. <template #default="{ row, $index }">
  400. <div style="width: 100%">
  401. <el-form-item
  402. :prop="'contact.' + $index + '.contactNo'"
  403. :rules="rulesPerson.contactNo"
  404. :inline-message="true"
  405. >
  406. <el-input
  407. v-model="row.contactNo"
  408. placeholder="请输入联系号码"
  409. />
  410. </el-form-item>
  411. </div>
  412. </template>
  413. </el-table-column>
  414. <el-table-column
  415. align="center"
  416. label="操作"
  417. width="120"
  418. fixed="right"
  419. >
  420. <template #default="{ $index }">
  421. <el-button
  422. type="primary"
  423. link
  424. @click="clickInformationDelete($index)"
  425. >删除</el-button
  426. >
  427. </template>
  428. </el-table-column>
  429. </el-table>
  430. </div>
  431. </el-form-item>
  432. </el-form>
  433. <template #footer>
  434. <el-button @click="openPerson = false" size="large">取 消</el-button>
  435. <el-button type="primary" @click="submitPerson()" size="large"
  436. >确 定</el-button
  437. >
  438. </template>
  439. </el-dialog>
  440. <el-dialog
  441. title="分配"
  442. v-if="openAllocation"
  443. v-model="openAllocation"
  444. width="300"
  445. >
  446. <byForm
  447. :formConfig="formConfigAllocation"
  448. :formOption="formOption"
  449. v-model="formAllocation.data"
  450. :rules="rulesAllocation"
  451. ref="allocation"
  452. >
  453. </byForm>
  454. <template #footer>
  455. <el-button @click="openAllocation = false" size="large"
  456. >取 消</el-button
  457. >
  458. <el-button type="primary" @click="submitAllocation()" size="large"
  459. >确 定</el-button
  460. >
  461. </template>
  462. </el-dialog>
  463. <el-dialog
  464. title="添加跟进记录"
  465. v-if="openFollow"
  466. v-model="openFollow"
  467. width="500"
  468. destroy-on-close
  469. >
  470. <byForm
  471. :formConfig="formConfigAFollow"
  472. :formOption="formOption"
  473. v-model="formFollow.data"
  474. :rules="rulesFollow"
  475. ref="follow"
  476. >
  477. <template #fileSlot>
  478. <div style="width: 100%">
  479. <el-upload
  480. v-model:fileList="fileList"
  481. action="https://winfaster.obs.cn-south-1.myhuaweicloud.com"
  482. :data="uploadData"
  483. multiple
  484. :before-upload="uploadFile"
  485. :on-preview="onPreviewFile"
  486. >
  487. <el-button type="primary">文件上传</el-button>
  488. </el-upload>
  489. </div>
  490. </template>
  491. </byForm>
  492. <template #footer>
  493. <el-button @click="openFollow = false" size="large">取 消</el-button>
  494. <el-button type="primary" @click="submitFollow()" size="large"
  495. >确 定</el-button
  496. >
  497. </template>
  498. </el-dialog>
  499. <el-dialog
  500. title="跟进记录"
  501. v-if="openRecordMore"
  502. v-model="openRecordMore"
  503. width="800"
  504. destroy-on-close
  505. >
  506. <div>
  507. <div style="padding: 8px 0">
  508. <el-button type="primary" @click="clickFollowUp(rowData)" plain
  509. >添加跟进记录</el-button
  510. >
  511. </div>
  512. <div style="padding-top: 16px">
  513. <div
  514. v-infinite-scroll="infiniteScroll"
  515. class="infinite-scroll"
  516. :infinite-scroll-disabled="judgeTotal()"
  517. >
  518. <el-timeline>
  519. <el-timeline-item
  520. v-for="(record, index) in recordList"
  521. :key="index"
  522. :timestamp="record.date"
  523. hide-timestamp
  524. >
  525. <div>
  526. <div
  527. style="
  528. padding: 0 0 8px 0;
  529. display: flex;
  530. justify-content: space-between;
  531. "
  532. >
  533. <span>{{
  534. dictValueLabel(record.createUser, userList)
  535. }}</span>
  536. <span>{{ record.date }}</span>
  537. </div>
  538. <div v-if="record.type == '30'">
  539. <div
  540. style="word-wrap: break-word; margin: 8px 0"
  541. v-html="getStyle(record.content)"
  542. v-if="record.content"
  543. ></div>
  544. <div v-else>跟进记录:</div>
  545. </div>
  546. <div v-else>
  547. <div style="word-wrap: break-word; margin: 8px 0">
  548. <!-- {{ getContent(record) }} -->
  549. <span v-if="record.type === '10'"
  550. >跟进记录:报价单总金额
  551. </span>
  552. <span v-if="record.type === '20'"
  553. >跟进记录:合同总金额
  554. </span>
  555. <span>{{ moneyFormat(record.amount, 2) }}</span>
  556. <span
  557. v-if="record.type === '10' && record.code"
  558. :class="{ 'code-class': isHave }"
  559. @click="isHaveOne ? handlePushRoute(record) : () => {}"
  560. >({{ record.code }})</span
  561. >
  562. <span
  563. v-if="record.type === '20' && record.contractCode"
  564. :class="{ 'code-class': isHaveOne }"
  565. @click="isHave ? handlePushRoute(record) : () => {}"
  566. >({{ record.contractCode }})</span
  567. >
  568. </div>
  569. </div>
  570. <div
  571. style="margin: 8px 0; display: flex"
  572. v-if="record.fileList && record.fileList.length > 0"
  573. >
  574. <div style="width: 36px">附件:</div>
  575. <div style="width: calc(100% - 36px)">
  576. <div
  577. v-for="(file, index) in record.fileList"
  578. :key="index"
  579. >
  580. <a
  581. style="color: #409eff; cursor: pointer"
  582. @click="openFile(file.fileUrl)"
  583. >{{ file.fileName }}</a
  584. >
  585. </div>
  586. </div>
  587. </div>
  588. </div>
  589. </el-timeline-item>
  590. </el-timeline>
  591. </div>
  592. </div>
  593. </div>
  594. <template #footer>
  595. <el-button @click="openRecordMore = false" size="large"
  596. >关 闭</el-button
  597. >
  598. </template>
  599. </el-dialog>
  600. <el-dialog
  601. title="高级检索"
  602. v-if="openSearch"
  603. v-model="openSearch"
  604. width="600"
  605. :before-close="cancelSearch"
  606. >
  607. <byForm
  608. :formConfig="formSearchConfig"
  609. :formOption="formOption"
  610. v-model="sourceList.pagination"
  611. >
  612. <template #address>
  613. <el-row style="width: 100%">
  614. <el-col :span="8">
  615. <el-form-item prop="countryId">
  616. <el-select
  617. v-model="sourceList.pagination.countryId"
  618. placeholder="国家"
  619. clearable
  620. filterable
  621. @change="(val) => getCitySearchData(val, '20', true)"
  622. >
  623. <el-option
  624. v-for="item in countrySearchData"
  625. :label="item.chineseName"
  626. :value="item.id"
  627. >
  628. </el-option>
  629. </el-select>
  630. </el-form-item>
  631. </el-col>
  632. <el-col :span="8">
  633. <el-form-item prop="provinceName">
  634. <selectCity
  635. placeholder="省/洲"
  636. @change="(val) => getCitySearchData(val, '30', true)"
  637. addressId="provinceId"
  638. addressName="provinceName"
  639. v-model="sourceList.pagination"
  640. :data="provinceSearchData"
  641. >
  642. </selectCity>
  643. </el-form-item>
  644. </el-col>
  645. <el-col :span="8">
  646. <el-form-item prop="cityName">
  647. <selectCity
  648. placeholder="城市"
  649. addressId="cityId"
  650. addressName="cityName"
  651. v-model="sourceList.pagination"
  652. :data="citySearchData"
  653. ></selectCity>
  654. </el-form-item>
  655. </el-col>
  656. </el-row>
  657. </template>
  658. <template #tags>
  659. <div style="width: 100%">
  660. <el-tag
  661. style="margin-right: 8px"
  662. type="info"
  663. v-for="(tag, index) in sourceList.pagination.tags"
  664. closable
  665. :key="index"
  666. @close="tagSearchClose(tag)"
  667. >
  668. {{ dictValueLabel(tag, customerTag) }}
  669. </el-tag>
  670. <template
  671. v-if="sourceList.pagination.tags.length !== customerTag.length"
  672. >
  673. <el-select
  674. v-if="addTagSearchShow"
  675. v-model="addSearchTag"
  676. style="margin-top: 8px"
  677. @change="changeSearchTag"
  678. >
  679. <el-option
  680. v-for="tag in customerTag"
  681. :key="tag.value"
  682. :label="tag.label"
  683. :value="tag.value"
  684. :disabled="
  685. judgeTagSelect(sourceList.pagination.tags, tag.value)
  686. "
  687. />
  688. </el-select>
  689. <el-tag
  690. style="cursor: pointer"
  691. type="info"
  692. @click="addTagSearchShow = true"
  693. v-else
  694. >
  695. +
  696. </el-tag>
  697. </template>
  698. </div>
  699. </template>
  700. </byForm>
  701. <template #footer>
  702. <el-button @click="cancelSearch()" size="large">取 消</el-button>
  703. <el-button type="primary" @click="submitSearch()" size="large"
  704. >确 定</el-button
  705. >
  706. </template>
  707. </el-dialog>
  708. </div>
  709. </template>
  710. <script setup>
  711. import { ElMessage, ElMessageBox } from "element-plus";
  712. import byTable from "@/components/byTable/index";
  713. import byForm from "@/components/byForm/index";
  714. import { computed, ref } from "vue";
  715. import useUserStore from "@/store/modules/user";
  716. import selectCity from "@/components/selectCity/index.vue";
  717. const { proxy } = getCurrentInstance();
  718. const loading = ref(false);
  719. const loadingOperation = ref(false);
  720. const submitLoading = ref(false);
  721. const openPerson = ref(false);
  722. const openAllocation = ref(false);
  723. const customerTag = ref([]);
  724. const customerSource = ref([]);
  725. const customerStatus = ref([]);
  726. const contactType = ref([]);
  727. const userList = ref([]);
  728. const fileList = ref([]);
  729. const uploadData = ref({});
  730. const statisticsType = ref([
  731. {
  732. label: "客户来源统计",
  733. value: 1,
  734. },
  735. {
  736. label: "客户类型统计",
  737. value: 2,
  738. },
  739. {
  740. label: "业务员统计",
  741. value: 3,
  742. },
  743. ]);
  744. const sourceList = ref({
  745. data: [],
  746. pagination: {
  747. total: 0,
  748. pageNum: 1,
  749. pageSize: 10,
  750. keyword: "",
  751. type: "",
  752. source: "",
  753. status: "",
  754. name: "",
  755. countryId: "",
  756. provinceId: "",
  757. cityId: "",
  758. customerCode: "",
  759. userId: "",
  760. tag: "",
  761. tags: [],
  762. },
  763. paginationTwo: {
  764. statisticsType: 1,
  765. type: null,
  766. },
  767. });
  768. const statConfig = computed(() => [
  769. {
  770. label: "客户来源统计",
  771. data: [
  772. {
  773. label: "合计",
  774. num: statisticalData.value.countAmount,
  775. type: 2,
  776. },
  777. ...customerSource.value
  778. .filter((a) => getNum(a.value) > 0)
  779. .map((b) => {
  780. let num = getNum(b.value);
  781. return {
  782. label: b.label,
  783. num,
  784. type: 1,
  785. };
  786. }),
  787. ],
  788. },
  789. {
  790. label: "客户类型统计",
  791. data: [
  792. {
  793. label: "合计",
  794. num: statisticalData.value.countAmount,
  795. type: 2,
  796. },
  797. ...customerStatus.value
  798. .filter((a) => getNum(a.value) > 0)
  799. .map((b) => {
  800. let num = getNum(b.value);
  801. return {
  802. label: b.label,
  803. num,
  804. type: 1,
  805. };
  806. }),
  807. ],
  808. },
  809. {
  810. label: "业务员统计",
  811. data: [
  812. {
  813. label: "合计",
  814. num: statisticalData.value.countAmount,
  815. type: 2,
  816. },
  817. ...userList.value
  818. .filter((a) => getNum(a.value) > 0)
  819. .map((b) => {
  820. let num = getNum(b.value);
  821. return {
  822. label: b.label,
  823. num,
  824. type: 1,
  825. };
  826. }),
  827. ],
  828. },
  829. ]);
  830. const selectConfig = computed(() => {
  831. return [
  832. {
  833. label: "客户状态",
  834. prop: "type",
  835. data: [
  836. {
  837. label: "公海",
  838. value: "0",
  839. },
  840. {
  841. label: "私海",
  842. value: "1",
  843. },
  844. ],
  845. },
  846. {
  847. label: "客户来源",
  848. prop: "source",
  849. data: customerSource.value,
  850. },
  851. {
  852. label: "客户类型",
  853. prop: "status",
  854. data: customerStatus.value,
  855. },
  856. ];
  857. });
  858. const config = computed(() => {
  859. return [
  860. {
  861. attrs: {
  862. label: "",
  863. slot: "isTop",
  864. fixed: "left",
  865. width: 60,
  866. align: "center",
  867. },
  868. },
  869. {
  870. attrs: {
  871. label: "客户名称",
  872. slot: "name",
  873. fixed: "left",
  874. width: 160,
  875. },
  876. },
  877. {
  878. attrs: {
  879. label: "所在城市",
  880. slot: "address",
  881. width: 160,
  882. },
  883. },
  884. {
  885. attrs: {
  886. label: "客户代码",
  887. prop: "customerCode",
  888. width: 120,
  889. },
  890. },
  891. {
  892. attrs: {
  893. label: "客户来源",
  894. prop: "source",
  895. width: 120,
  896. },
  897. render(type) {
  898. return proxy.dictValueLabel(type, customerSource.value);
  899. },
  900. },
  901. {
  902. attrs: {
  903. label: "客户类型",
  904. prop: "status",
  905. width: 120,
  906. },
  907. render(type) {
  908. return proxy.dictValueLabel(type, customerStatus.value);
  909. },
  910. },
  911. {
  912. attrs: {
  913. label: "客户标签",
  914. slot: "tags",
  915. width: 180,
  916. },
  917. },
  918. {
  919. attrs: {
  920. label: "业务员",
  921. prop: "userId",
  922. width: 140,
  923. },
  924. render(type) {
  925. let data = userList.value.filter((item) => item.value == type);
  926. if (data && data.length > 0) {
  927. return data[0].label;
  928. } else {
  929. return "";
  930. }
  931. },
  932. },
  933. {
  934. attrs: {
  935. label: "跟进",
  936. slot: "follow",
  937. "min-width": 440,
  938. },
  939. },
  940. {
  941. attrs: {
  942. label: "操作",
  943. width: 190,
  944. align: "center",
  945. fixed: "right",
  946. },
  947. renderHTML(row) {
  948. return [
  949. {
  950. attrs: {
  951. label: "分配",
  952. type: "primary",
  953. text: true,
  954. },
  955. el: "button",
  956. click() {
  957. formAllocation.data = {
  958. id: row.id,
  959. userId: row.userId,
  960. };
  961. openAllocation.value = true;
  962. },
  963. },
  964. {
  965. attrs: {
  966. label: "跟进",
  967. type: "primary",
  968. text: true,
  969. },
  970. el: "button",
  971. click() {
  972. clickFollowUp(row);
  973. },
  974. },
  975. {
  976. attrs: {
  977. label: "修改",
  978. type: "primary",
  979. text: true,
  980. },
  981. el: "button",
  982. click() {
  983. update(row);
  984. },
  985. },
  986. {
  987. attrs: {
  988. label: "删除",
  989. type: "primary",
  990. text: true,
  991. },
  992. el: "button",
  993. click() {
  994. ElMessageBox.confirm(
  995. "此操作将永久删除该数据, 是否继续?",
  996. "提示",
  997. {
  998. confirmButtonText: "确定",
  999. cancelButtonText: "取消",
  1000. type: "warning",
  1001. }
  1002. ).then(() => {
  1003. proxy
  1004. .post("/customer/delete", {
  1005. id: row.id,
  1006. })
  1007. .then(() => {
  1008. ElMessage({
  1009. message: "删除成功",
  1010. type: "success",
  1011. });
  1012. getList();
  1013. obtainStatisticalData();
  1014. });
  1015. });
  1016. },
  1017. },
  1018. ];
  1019. },
  1020. },
  1021. ];
  1022. });
  1023. const modalType = ref("add");
  1024. const dialogVisible = ref(false);
  1025. const formData = reactive({
  1026. data: {
  1027. countryId: "44",
  1028. },
  1029. });
  1030. const openFollow = ref(false);
  1031. const formPerson = reactive({
  1032. data: {},
  1033. });
  1034. const formAllocation = reactive({
  1035. data: {},
  1036. });
  1037. const formFollow = reactive({
  1038. data: {},
  1039. });
  1040. const formOption = reactive({
  1041. inline: true,
  1042. labelWidth: 100,
  1043. itemWidth: 100,
  1044. rules: [],
  1045. });
  1046. const formConfig = computed(() => {
  1047. return [
  1048. {
  1049. type: "input",
  1050. prop: "name",
  1051. label: "客户名称",
  1052. required: true,
  1053. itemWidth: 100,
  1054. itemType: "text",
  1055. },
  1056. {
  1057. type: "slot",
  1058. slotName: "allAddress",
  1059. label: "详细地址",
  1060. },
  1061. {
  1062. type: "input",
  1063. prop: "customerCode",
  1064. label: "客户代码",
  1065. required: true,
  1066. itemWidth: 100,
  1067. itemType: "text",
  1068. },
  1069. {
  1070. type: "input",
  1071. prop: "dutyParagraph",
  1072. label: "客户税号",
  1073. required: true,
  1074. itemWidth: 100,
  1075. itemType: "text",
  1076. },
  1077. {
  1078. type: "select",
  1079. label: "客户来源",
  1080. prop: "source",
  1081. itemWidth: 50,
  1082. data: customerSource.value,
  1083. },
  1084. {
  1085. type: "select",
  1086. label: "客户类型",
  1087. prop: "status",
  1088. itemWidth: 50,
  1089. data: customerStatus.value,
  1090. },
  1091. {
  1092. type: "select",
  1093. label: "业务员",
  1094. prop: "userId",
  1095. itemWidth: 100,
  1096. data: userList.value,
  1097. clearable: true,
  1098. },
  1099. {
  1100. type: "select",
  1101. label: "客户标签",
  1102. prop: "tags",
  1103. itemWidth: 100,
  1104. multiple: true,
  1105. data: customerTag.value,
  1106. style: {
  1107. width: "100%",
  1108. },
  1109. },
  1110. {
  1111. type: "slot",
  1112. slotName: "person",
  1113. label: "客户联系人",
  1114. },
  1115. ];
  1116. });
  1117. const rules = ref({
  1118. name: [{ required: true, message: "请输入客户名称", trigger: "blur" }],
  1119. name2: [{ required: true, message: "请输入联系人", trigger: "blur" }],
  1120. email: [{ required: true, message: "请输入电子邮箱", trigger: "blur" }],
  1121. countryId: [{ required: true, message: "请选择国家", trigger: "change" }],
  1122. source: [{ required: true, message: "请选择客户来源", trigger: "change" }],
  1123. status: [{ required: true, message: "请选择类型", trigger: "change" }],
  1124. });
  1125. const formConfigAllocation = computed(() => {
  1126. return [
  1127. {
  1128. type: "select",
  1129. label: "业务员",
  1130. prop: "userId",
  1131. itemWidth: 100,
  1132. data: userList.value,
  1133. clearable: true,
  1134. },
  1135. ];
  1136. });
  1137. const rulesAllocation = ref({
  1138. userId: [{ required: true, message: "请选择业务员", trigger: "change" }],
  1139. });
  1140. const formConfigAFollow = computed(() => {
  1141. return [
  1142. {
  1143. type: "date",
  1144. itemType: "datetime",
  1145. label: "跟进时间",
  1146. prop: "date",
  1147. itemWidth: 100,
  1148. },
  1149. {
  1150. type: "input",
  1151. itemType: "textarea",
  1152. label: "跟进内容",
  1153. prop: "content",
  1154. itemWidth: 100,
  1155. },
  1156. {
  1157. type: "slot",
  1158. label: "上传附件",
  1159. prop: "fileList",
  1160. slotName: "fileSlot",
  1161. },
  1162. ];
  1163. });
  1164. const rulesPerson = ref({
  1165. name: [{ required: true, message: "请输入联系人", trigger: "blur" }],
  1166. email: [{ required: true, message: "请输入电子邮箱", trigger: "blur" }],
  1167. type: [{ required: true, message: "请选择类型", trigger: "change" }],
  1168. contactNo: [{ required: true, message: "请输入联系号码", trigger: "blur" }],
  1169. });
  1170. const rulesFollow = ref({
  1171. date: [{ required: true, message: "请选择跟进时间", trigger: "change" }],
  1172. content: [{ required: true, message: "请输入跟进内容", trigger: "blur" }],
  1173. });
  1174. const submit = ref(null);
  1175. const person = ref(null);
  1176. const allocation = ref(null);
  1177. const follow = ref(null);
  1178. const getList = async (req) => {
  1179. sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
  1180. loading.value = true;
  1181. proxy.post("/customer/page", sourceList.value.pagination).then((res) => {
  1182. res.rows.forEach((x) => {
  1183. x.addTagShow = false;
  1184. if (x.tag) {
  1185. x.tag = x.tag.split(",");
  1186. } else {
  1187. x.tag = [];
  1188. }
  1189. });
  1190. sourceList.value.data = res.rows;
  1191. sourceList.value.pagination.total = res.total;
  1192. setTimeout(() => {
  1193. loading.value = false;
  1194. }, 200);
  1195. });
  1196. };
  1197. const countryData = ref([]);
  1198. const provinceData = ref([]);
  1199. const cityData = ref([]);
  1200. const getCityData = (id, type, isChange) => {
  1201. proxy.post("/customizeArea/list", { parentId: id }).then((res) => {
  1202. if (type === "20") {
  1203. provinceData.value = res;
  1204. if (isChange) {
  1205. formData.data.provinceId = "";
  1206. formData.data.provinceName = "";
  1207. formData.data.cityId = "";
  1208. formData.data.cityName = "";
  1209. }
  1210. } else if (type === "30") {
  1211. cityData.value = res;
  1212. if (isChange) {
  1213. formData.data.cityId = "";
  1214. formData.data.cityName = "";
  1215. }
  1216. } else {
  1217. countryData.value = res;
  1218. }
  1219. });
  1220. };
  1221. getCityData("0");
  1222. const openModal = () => {
  1223. modalType.value = "add";
  1224. formData.data = {
  1225. countryId: "44",
  1226. tags: [],
  1227. customerUserList: [
  1228. {
  1229. name: "",
  1230. email: "",
  1231. },
  1232. ],
  1233. };
  1234. getCityData(formData.data.countryId, "20");
  1235. loadingOperation.value = false;
  1236. dialogVisible.value = true;
  1237. };
  1238. const clickAddPerson = () => {
  1239. if (
  1240. formData.data.customerUserList &&
  1241. formData.data.customerUserList.length > 0
  1242. ) {
  1243. formData.data.customerUserList.push({
  1244. name: "",
  1245. email: "",
  1246. });
  1247. } else {
  1248. formData.data.customerUserList = [
  1249. {
  1250. name: "",
  1251. email: "",
  1252. },
  1253. ];
  1254. }
  1255. };
  1256. const submitAllocation = () => {
  1257. allocation.value.handleSubmit(() => {
  1258. proxy.post("/customer/CustomerAllocation", formAllocation.data).then(() => {
  1259. ElMessage({
  1260. message: "分配成功",
  1261. type: "success",
  1262. });
  1263. openAllocation.value = false;
  1264. getList();
  1265. obtainStatisticalData();
  1266. });
  1267. });
  1268. };
  1269. const submitFollow = () => {
  1270. follow.value.handleSubmit(() => {
  1271. if (fileList.value && fileList.value.length > 0) {
  1272. formFollow.data.fileList = fileList.value.map((item) => {
  1273. return {
  1274. id: item.raw.id,
  1275. fileName: item.raw.fileName,
  1276. fileUrl: item.raw.fileUrl,
  1277. };
  1278. });
  1279. } else {
  1280. formFollow.data.fileList = [];
  1281. }
  1282. proxy
  1283. .post("/customerFollowRecords/" + modalType.value, formFollow.data)
  1284. .then(
  1285. () => {
  1286. ElMessage({
  1287. message: modalType.value == "add" ? "添加成功" : "编辑成功",
  1288. type: "success",
  1289. });
  1290. openFollow.value = false;
  1291. getList();
  1292. },
  1293. (err) => {
  1294. console.log(err);
  1295. }
  1296. );
  1297. });
  1298. };
  1299. const submitForm = () => {
  1300. submit.value.handleSubmit(() => {
  1301. if (
  1302. formData.data.customerUserList &&
  1303. formData.data.customerUserList.length > 0
  1304. ) {
  1305. formData.data.tag = formData.data.tags.join(",");
  1306. submitLoading.value = true;
  1307. proxy.post("/customer/" + modalType.value, formData.data).then(
  1308. () => {
  1309. ElMessage({
  1310. message: modalType.value == "add" ? "添加成功" : "编辑成功",
  1311. type: "success",
  1312. });
  1313. dialogVisible.value = false;
  1314. submitLoading.value = false;
  1315. getList();
  1316. obtainStatisticalData();
  1317. },
  1318. (err) => {
  1319. console.log(err);
  1320. submitLoading.value = false;
  1321. }
  1322. );
  1323. } else {
  1324. ElMessage("请添加客户联系人");
  1325. }
  1326. });
  1327. };
  1328. const getDict = () => {
  1329. proxy
  1330. .getDictOne([
  1331. "customer_tag",
  1332. "customer_source",
  1333. "customer_status",
  1334. "contact_type",
  1335. ])
  1336. .then((res) => {
  1337. customerTag.value = res["customer_tag"].map((x) => ({
  1338. label: x.dictValue,
  1339. value: x.dictKey,
  1340. }));
  1341. customerSource.value = res["customer_source"].map((x) => ({
  1342. label: x.dictValue,
  1343. value: x.dictKey,
  1344. }));
  1345. customerStatus.value = res["customer_status"].map((x) => ({
  1346. label: x.dictValue,
  1347. value: x.dictKey,
  1348. }));
  1349. contactType.value = res["contact_type"].map((x) => ({
  1350. label: x.dictValue,
  1351. value: x.dictKey,
  1352. }));
  1353. });
  1354. proxy
  1355. .get("/tenantUser/list", {
  1356. pageNum: 1,
  1357. pageSize: 10000,
  1358. tenantId: useUserStore().user.tenantId,
  1359. })
  1360. .then((res) => {
  1361. userList.value = res.rows.map((item) => {
  1362. return {
  1363. label: item.nickName,
  1364. value: item.userId,
  1365. };
  1366. });
  1367. });
  1368. };
  1369. getDict();
  1370. getList();
  1371. const handleClickName = (row) => {
  1372. proxy.$router.push({
  1373. path: "/ERP/customer/portrait",
  1374. query: {
  1375. id: row.id,
  1376. },
  1377. });
  1378. };
  1379. const deleteFollow = (data) => {
  1380. ElMessageBox.confirm("是否确认删除该跟进?", "提示", {
  1381. confirmButtonText: "确定",
  1382. cancelButtonText: "取消",
  1383. type: "warning",
  1384. }).then(() => {
  1385. proxy
  1386. .post("/customerFollowRecords/delete", {
  1387. id: data.id,
  1388. })
  1389. .then(() => {
  1390. ElMessage({
  1391. message: "删除成功",
  1392. type: "success",
  1393. });
  1394. getList();
  1395. });
  1396. });
  1397. };
  1398. const addTag = ref("");
  1399. const judgeTagSelect = (data, val) => {
  1400. if (data && data.length > 0) {
  1401. if (data.includes(val)) {
  1402. return true;
  1403. }
  1404. }
  1405. return false;
  1406. };
  1407. const changeTag = (val, item) => {
  1408. let data = {
  1409. id: item.id,
  1410. tag: JSON.parse(JSON.stringify(item.tag)),
  1411. };
  1412. data.tag.push(val);
  1413. data.tag = data.tag.join(",");
  1414. proxy.post("/customer/editTag", data).then(() => {
  1415. ElMessage({
  1416. message: "添加成功",
  1417. type: "success",
  1418. });
  1419. item.addTagShow = false;
  1420. addTag.value = "";
  1421. getList();
  1422. });
  1423. };
  1424. const tagClose = (val, item) => {
  1425. let data = {
  1426. id: item.id,
  1427. tag: JSON.parse(JSON.stringify(item.tag)),
  1428. };
  1429. data.tag = data.tag.filter((row) => row !== val);
  1430. if (data.tag && data.tag.length > 0) {
  1431. data.tag = data.tag.join(",");
  1432. } else {
  1433. data.tag = "";
  1434. }
  1435. proxy.post("/customer/editTag", data).then(() => {
  1436. ElMessage({
  1437. message: "添加成功",
  1438. type: "success",
  1439. });
  1440. item.addTagShow = false;
  1441. addTag.value = "";
  1442. getList();
  1443. });
  1444. };
  1445. const showSelect = (item) => {
  1446. item.addTagShow = true;
  1447. };
  1448. const clickFollowUp = (item) => {
  1449. formFollow.data = {
  1450. customerId: item.id,
  1451. fileList: [],
  1452. };
  1453. fileList.value = [];
  1454. modalType.value = "add";
  1455. openFollow.value = true;
  1456. openRecordMore.value = false;
  1457. };
  1458. const uploadFile = async (file) => {
  1459. const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
  1460. uploadData.value = res.uploadBody;
  1461. file.id = res.id;
  1462. file.fileName = res.fileName;
  1463. file.fileUrl = res.fileUrl;
  1464. return true;
  1465. };
  1466. const onPreviewFile = (file) => {
  1467. window.open(file.raw.fileUrl, "_blank");
  1468. };
  1469. const getStyle = (val) => {
  1470. if (val) {
  1471. return "跟进记录: " + val.replace(/\n|\r\n/g, "<br>");
  1472. } else {
  1473. return "";
  1474. }
  1475. };
  1476. const getContent = (item) => {
  1477. if (item.type === "10") {
  1478. return "跟进记录: " + "报价单总金额 " + proxy.moneyFormat(item.amount, 2);
  1479. } else if (item.type === "20") {
  1480. return (
  1481. "跟进记录: " +
  1482. "合同总金额 " +
  1483. proxy.moneyFormat(item.amount, 2) +
  1484. ` (${item.contractCode}) `
  1485. );
  1486. }
  1487. };
  1488. const recordShow = (item) => {
  1489. if (
  1490. !(item.fileList && item.fileList.length > 0) &&
  1491. JSON.stringify(item.fileList) !== "[]"
  1492. ) {
  1493. proxy
  1494. .post("/fileInfo/getList", { businessIdList: [item.id] })
  1495. .then((fileObj) => {
  1496. item.fileList = fileObj[item.id] || [];
  1497. });
  1498. }
  1499. };
  1500. const openFile = (path) => {
  1501. window.open(path, "_blank");
  1502. };
  1503. const recordList = ref([]);
  1504. const rowData = ref({});
  1505. const openRecordMore = ref(false);
  1506. const queryParams = ref({
  1507. total: 0,
  1508. pageNum: 1,
  1509. pageSize: 10,
  1510. customerId: "",
  1511. });
  1512. const clickMore = (item) => {
  1513. if (openRecordMore.value === false) {
  1514. queryParams.value.pageNum = 1;
  1515. recordList.value = [];
  1516. rowData.value = item;
  1517. queryParams.value.customerId = item.id;
  1518. }
  1519. proxy.post("/customerFollowRecords/page", queryParams.value).then((res) => {
  1520. recordList.value = recordList.value.concat(res.rows);
  1521. queryParams.value.total = res.total;
  1522. proxy
  1523. .post("/fileInfo/getList", {
  1524. businessIdList: res.rows.map((rows) => rows.id),
  1525. })
  1526. .then((fileObj) => {
  1527. for (let i = 0; i < res.rows.length; i++) {
  1528. recordList.value[
  1529. parseInt(
  1530. i + (queryParams.value.pageNum - 1) * queryParams.value.pageSize
  1531. )
  1532. ].fileList =
  1533. fileObj[
  1534. recordList.value[
  1535. parseInt(
  1536. i +
  1537. (queryParams.value.pageNum - 1) * queryParams.value.pageSize
  1538. )
  1539. ].id
  1540. ] || [];
  1541. }
  1542. });
  1543. });
  1544. openRecordMore.value = true;
  1545. };
  1546. const infiniteScroll = () => {
  1547. queryParams.value.pageNum++;
  1548. clickMore();
  1549. };
  1550. const judgeTotal = () => {
  1551. if (
  1552. queryParams.value.pageNum * queryParams.value.pageSize >=
  1553. queryParams.value.total
  1554. ) {
  1555. return true;
  1556. }
  1557. return false;
  1558. };
  1559. const moreIndex = ref(0);
  1560. const clickInformationMore = (item, index) => {
  1561. moreIndex.value = index;
  1562. if (item.contactJson) {
  1563. item.contact = JSON.parse(item.contactJson);
  1564. } else {
  1565. item.contact = [];
  1566. }
  1567. formPerson.data = proxy.deepClone(item);
  1568. openPerson.value = true;
  1569. };
  1570. const clickDelete = (index) => {
  1571. formData.data.customerUserList.splice(index, 1);
  1572. };
  1573. const clickAddMoreInformation = () => {
  1574. if (formPerson.data.contact && formPerson.data.contact.length > 0) {
  1575. formPerson.data.contact.push({
  1576. type: "",
  1577. contactNo: "",
  1578. });
  1579. } else {
  1580. formPerson.data.contact = [
  1581. {
  1582. type: "",
  1583. contactNo: "",
  1584. },
  1585. ];
  1586. }
  1587. };
  1588. const clickInformationDelete = (index) => {
  1589. formPerson.data.contact.splice(index, 1);
  1590. };
  1591. const submitPerson = () => {
  1592. person.value.validate((valid) => {
  1593. if (valid) {
  1594. formPerson.data.contactJson = JSON.stringify(formPerson.data.contact);
  1595. formData.data.customerUserList[moreIndex.value] = formPerson.data;
  1596. openPerson.value = false;
  1597. }
  1598. });
  1599. };
  1600. const deleteTop = (item) => {
  1601. proxy.post("/customerTop/delete", { customerId: item.id }).then(() => {
  1602. item.isTop = 0;
  1603. });
  1604. };
  1605. const addTop = (item) => {
  1606. proxy.post("/customerTop/add", { customerId: item.id }).then(() => {
  1607. item.isTop = 1;
  1608. });
  1609. };
  1610. const statisticalData = ref({
  1611. countAmount: 0,
  1612. customerList: [],
  1613. });
  1614. const obtainStatisticalData = () => {
  1615. proxy
  1616. .post("/customer/sourceStatistics", sourceList.value.paginationTwo)
  1617. .then((res) => {
  1618. statisticalData.value = res;
  1619. });
  1620. };
  1621. obtainStatisticalData();
  1622. const getNum = (val) => {
  1623. let num = 0;
  1624. if (
  1625. statisticalData.value.customerList &&
  1626. statisticalData.value.customerList.length > 0
  1627. ) {
  1628. statisticalData.value.customerList.map((item) => {
  1629. if (sourceList.value.paginationTwo.statisticsType === 1) {
  1630. if (item.source === val) {
  1631. num = item.count;
  1632. }
  1633. } else if (sourceList.value.paginationTwo.statisticsType === 2) {
  1634. if (item.status === val) {
  1635. num = item.count;
  1636. }
  1637. } else if (sourceList.value.paginationTwo.statisticsType === 3) {
  1638. if (item.userId === val) {
  1639. num = item.count;
  1640. }
  1641. }
  1642. });
  1643. }
  1644. return num;
  1645. };
  1646. const update = (row) => {
  1647. modalType.value = "edit";
  1648. loadingOperation.value = true;
  1649. proxy.post("/customer/detail", { id: row.id }).then((res) => {
  1650. if (res.tag) {
  1651. res.tags = res.tag.split(",");
  1652. } else {
  1653. res.tags = [];
  1654. }
  1655. formData.data = res;
  1656. getCityData(formData.data.countryId, "20");
  1657. if (formData.data.provinceId) {
  1658. getCityData(formData.data.provinceId, "30");
  1659. }
  1660. loadingOperation.value = false;
  1661. dialogVisible.value = true;
  1662. });
  1663. };
  1664. const countrySearchData = ref([]);
  1665. const provinceSearchData = ref([]);
  1666. const citySearchData = ref([]);
  1667. const getCitySearchData = (id, type, isChange) => {
  1668. proxy.post("/customizeArea/list", { parentId: id }).then((res) => {
  1669. if (type === "20") {
  1670. provinceSearchData.value = res;
  1671. if (isChange) {
  1672. sourceList.value.pagination.provinceId = "";
  1673. sourceList.value.pagination.provinceName = "";
  1674. sourceList.value.pagination.cityId = "";
  1675. sourceList.value.pagination.cityName = "";
  1676. }
  1677. } else if (type === "30") {
  1678. citySearchData.value = res;
  1679. if (isChange) {
  1680. sourceList.value.pagination.cityId = "";
  1681. sourceList.value.pagination.cityName = "";
  1682. }
  1683. } else {
  1684. countrySearchData.value = res;
  1685. }
  1686. });
  1687. };
  1688. getCitySearchData("0");
  1689. const openSearch = ref(false);
  1690. const formSearchConfig = computed(() => {
  1691. return [
  1692. {
  1693. type: "input",
  1694. prop: "name",
  1695. label: "客户名称",
  1696. itemType: "text",
  1697. },
  1698. {
  1699. type: "slot",
  1700. slotName: "address",
  1701. label: "所在城市",
  1702. },
  1703. {
  1704. type: "input",
  1705. prop: "customerCode",
  1706. label: "客户代码",
  1707. itemType: "text",
  1708. },
  1709. {
  1710. type: "select",
  1711. label: "客户来源",
  1712. prop: "source",
  1713. itemWidth: 50,
  1714. data: customerSource.value,
  1715. clearable: true,
  1716. },
  1717. {
  1718. type: "select",
  1719. label: "客户类型",
  1720. prop: "status",
  1721. itemWidth: 50,
  1722. data: customerStatus.value,
  1723. clearable: true,
  1724. },
  1725. {
  1726. type: "select",
  1727. label: "业务员",
  1728. prop: "userId",
  1729. data: userList.value,
  1730. clearable: true,
  1731. },
  1732. {
  1733. type: "slot",
  1734. slotName: "tags",
  1735. label: "客户标签",
  1736. },
  1737. ];
  1738. });
  1739. const addSearchTag = ref("");
  1740. const addTagSearchShow = ref(false);
  1741. let copySearch = ref({});
  1742. const moreSearch = () => {
  1743. if (sourceList.value.pagination.tag) {
  1744. sourceList.value.pagination.tags =
  1745. sourceList.value.pagination.tag.split(",");
  1746. } else {
  1747. sourceList.value.pagination.tags = [];
  1748. }
  1749. addTagSearchShow.value = false;
  1750. copySearch.value = proxy.deepClone(sourceList.value.pagination);
  1751. openSearch.value = true;
  1752. };
  1753. const cancelSearch = () => {
  1754. sourceList.value.pagination = copySearch.value;
  1755. openSearch.value = false;
  1756. };
  1757. const submitSearch = () => {
  1758. if (
  1759. sourceList.value.pagination.tags &&
  1760. sourceList.value.pagination.tags.length > 0
  1761. ) {
  1762. sourceList.value.pagination.tag =
  1763. sourceList.value.pagination.tags.join(",");
  1764. } else {
  1765. sourceList.value.pagination.tag = "";
  1766. }
  1767. openSearch.value = false;
  1768. sourceList.value.pagination.keyword = "";
  1769. sourceList.value.pagination.pageNum = 1;
  1770. getList();
  1771. };
  1772. const tagSearchClose = (val) => {
  1773. sourceList.value.pagination.tags = sourceList.value.pagination.tags.filter(
  1774. (item) => item !== val
  1775. );
  1776. };
  1777. const changeSearchTag = (val) => {
  1778. sourceList.value.pagination.tags.push(val);
  1779. addTagSearchShow.value = false;
  1780. addSearchTag.value = "";
  1781. };
  1782. // 判断当前用户有无销售合同页面权限
  1783. const isHave = ref(false);
  1784. if (
  1785. useUserStore().permissions &&
  1786. useUserStore().permissions.includes("contract")
  1787. ) {
  1788. isHave.value = true;
  1789. }
  1790. // 判断当前用户有无报价单页面权限
  1791. const isHaveOne = ref(false);
  1792. if (
  1793. useUserStore().permissions &&
  1794. useUserStore().permissions.includes("priceSheet")
  1795. ) {
  1796. isHaveOne.value = true;
  1797. }
  1798. const handlePushRoute = (row) => {
  1799. if (row.type === "10") {
  1800. proxy.$router.push({
  1801. name: "PriceSheet",
  1802. query: {
  1803. code: row.code,
  1804. },
  1805. });
  1806. } else if (row.type === "20") {
  1807. proxy.$router.push({
  1808. name: "Contract",
  1809. query: {
  1810. code: row.contractCode,
  1811. },
  1812. });
  1813. }
  1814. };
  1815. const table = ref(null);
  1816. onMounted(() => {
  1817. watch(
  1818. () => table.value.statSelectVal,
  1819. (val) => {
  1820. sourceList.value.paginationTwo.statisticsType = val + 1;
  1821. obtainStatisticalData();
  1822. }
  1823. );
  1824. });
  1825. </script>
  1826. <style lang="scss" scoped>
  1827. .code-class {
  1828. cursor: pointer;
  1829. color: #409eff;
  1830. }
  1831. .tenant {
  1832. padding: 20px;
  1833. }
  1834. .infinite-scroll {
  1835. max-height: calc(89vh - 94px - 70px - 58px - 16px);
  1836. overflow-y: auto;
  1837. &::-webkit-scrollbar {
  1838. width: 0px;
  1839. }
  1840. }
  1841. .by-dropdown {
  1842. position: relative;
  1843. text-align: left;
  1844. height: 32px;
  1845. z-index: 1010;
  1846. padding: 0 10px;
  1847. transition: all 0.5s ease;
  1848. cursor: pointer;
  1849. line-height: 32px;
  1850. .by-dropdown-title {
  1851. font-size: 14px;
  1852. background-color: #fff;
  1853. }
  1854. ul {
  1855. position: absolute;
  1856. left: 0;
  1857. top: 32px;
  1858. padding: 0;
  1859. margin: 0;
  1860. z-index: 1200;
  1861. display: none;
  1862. white-space: nowrap;
  1863. background-color: #fff;
  1864. li {
  1865. list-style: none;
  1866. z-index: 1200;
  1867. font-size: 12px;
  1868. height: 30px;
  1869. padding: 0 10px;
  1870. }
  1871. li:hover {
  1872. background-color: #eff6ff;
  1873. color: #0084ff;
  1874. }
  1875. }
  1876. }
  1877. .by-dropdown::before {
  1878. display: block;
  1879. width: 1px;
  1880. content: " ";
  1881. position: absolute;
  1882. height: 14px;
  1883. top: 8px;
  1884. background-color: #ddd;
  1885. right: 0;
  1886. z-index: 1011;
  1887. }
  1888. .by-dropdown:hover {
  1889. background: #ffffff;
  1890. border-radius: 2px 2px 2px 2px;
  1891. opacity: 1;
  1892. ul {
  1893. background: #ffffff;
  1894. box-shadow: 0px 2px 16px 1px rgba(0, 0, 0, 0.06);
  1895. border-radius: 2px 2px 2px 2px;
  1896. opacity: 1;
  1897. display: block;
  1898. text-align: left;
  1899. }
  1900. }
  1901. .by-dropdown-lists {
  1902. max-height: 50vh;
  1903. overflow-y: auto;
  1904. line-height: 1;
  1905. }
  1906. .statistics-text {
  1907. padding-left: 8px;
  1908. width: 152px;
  1909. overflow: hidden;
  1910. text-overflow: ellipsis;
  1911. white-space: nowrap;
  1912. }
  1913. </style>