index.vue 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780
  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 #address>
  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="openFollow"
  443. v-model="openFollow"
  444. width="500"
  445. destroy-on-close
  446. >
  447. <byForm
  448. :formConfig="formConfigAFollow"
  449. :formOption="formOption"
  450. v-model="formFollow.data"
  451. :rules="rulesFollow"
  452. ref="follow"
  453. >
  454. <template #fileSlot>
  455. <div style="width: 100%">
  456. <el-upload
  457. v-model:fileList="fileList"
  458. action="https://winfaster.obs.cn-south-1.myhuaweicloud.com"
  459. :data="uploadData"
  460. multiple
  461. :before-upload="uploadFile"
  462. :on-preview="onPreviewFile"
  463. >
  464. <el-button type="primary">文件上传</el-button>
  465. </el-upload>
  466. </div>
  467. </template>
  468. </byForm>
  469. <template #footer>
  470. <el-button @click="openFollow = false" size="large">取 消</el-button>
  471. <el-button type="primary" @click="submitFollow()" size="large"
  472. >确 定</el-button
  473. >
  474. </template>
  475. </el-dialog>
  476. <el-dialog
  477. title="跟进记录"
  478. v-if="openRecordMore"
  479. v-model="openRecordMore"
  480. width="800"
  481. destroy-on-close
  482. >
  483. <div>
  484. <div style="padding: 8px 0">
  485. <el-button type="primary" @click="clickFollowUp(rowData)" plain
  486. >添加跟进记录</el-button
  487. >
  488. </div>
  489. <div style="padding-top: 16px">
  490. <div
  491. v-infinite-scroll="infiniteScroll"
  492. class="infinite-scroll"
  493. :infinite-scroll-disabled="judgeTotal()"
  494. >
  495. <el-timeline>
  496. <el-timeline-item
  497. v-for="(record, index) in recordList"
  498. :key="index"
  499. :timestamp="record.date"
  500. hide-timestamp
  501. >
  502. <div>
  503. <div
  504. style="
  505. padding: 0 0 8px 0;
  506. display: flex;
  507. justify-content: space-between;
  508. "
  509. >
  510. <span>{{
  511. dictValueLabel(record.createUser, userList)
  512. }}</span>
  513. <span>{{ record.date }}</span>
  514. </div>
  515. <div v-if="record.type == '30'">
  516. <div
  517. style="word-wrap: break-word; margin: 8px 0"
  518. v-html="getStyle(record.content)"
  519. v-if="record.content"
  520. ></div>
  521. <div v-else>跟进记录:</div>
  522. </div>
  523. <div v-else>
  524. <div style="word-wrap: break-word; margin: 8px 0">
  525. <!-- {{ getContent(record) }} -->
  526. <span v-if="record.type === '10'"
  527. >跟进记录:报价单总金额
  528. </span>
  529. <span v-if="record.type === '20'"
  530. >跟进记录:合同总金额
  531. </span>
  532. <span>{{ moneyFormat(record.amount, 2) }}</span>
  533. <span
  534. v-if="record.type === '10' && record.code"
  535. :class="{ 'code-class': isHave }"
  536. @click="isHaveOne ? handlePushRoute(record) : () => {}"
  537. >({{ record.code }})</span
  538. >
  539. <span
  540. v-if="record.type === '20' && record.contractCode"
  541. :class="{ 'code-class': isHaveOne }"
  542. @click="isHave ? handlePushRoute(record) : () => {}"
  543. >({{ record.contractCode }})</span
  544. >
  545. </div>
  546. </div>
  547. <div
  548. style="margin: 8px 0; display: flex"
  549. v-if="record.fileList && record.fileList.length > 0"
  550. >
  551. <div style="width: 36px">附件:</div>
  552. <div style="width: calc(100% - 36px)">
  553. <div
  554. v-for="(file, index) in record.fileList"
  555. :key="index"
  556. >
  557. <a
  558. style="color: #409eff; cursor: pointer"
  559. @click="openFile(file.fileUrl)"
  560. >{{ file.fileName }}</a
  561. >
  562. </div>
  563. </div>
  564. </div>
  565. </div>
  566. </el-timeline-item>
  567. </el-timeline>
  568. </div>
  569. </div>
  570. </div>
  571. <template #footer>
  572. <el-button @click="openRecordMore = false" size="large"
  573. >关 闭</el-button
  574. >
  575. </template>
  576. </el-dialog>
  577. <el-dialog
  578. title="高级检索"
  579. v-if="openSearch"
  580. v-model="openSearch"
  581. width="600"
  582. :before-close="cancelSearch"
  583. >
  584. <byForm
  585. :formConfig="formSearchConfig"
  586. :formOption="formOption"
  587. v-model="sourceList.pagination"
  588. >
  589. <template #address>
  590. <el-row style="width: 100%">
  591. <el-col :span="8">
  592. <el-form-item prop="countryId">
  593. <el-select
  594. v-model="sourceList.pagination.countryId"
  595. placeholder="国家"
  596. clearable
  597. filterable
  598. @change="(val) => getCitySearchData(val, '20', true)"
  599. >
  600. <el-option
  601. v-for="item in countrySearchData"
  602. :label="item.chineseName"
  603. :value="item.id"
  604. >
  605. </el-option>
  606. </el-select>
  607. </el-form-item>
  608. </el-col>
  609. <el-col :span="8">
  610. <el-form-item prop="provinceName">
  611. <selectCity
  612. placeholder="省/洲"
  613. @change="(val) => getCitySearchData(val, '30', true)"
  614. addressId="provinceId"
  615. addressName="provinceName"
  616. v-model="sourceList.pagination"
  617. :data="provinceSearchData"
  618. >
  619. </selectCity>
  620. </el-form-item>
  621. </el-col>
  622. <el-col :span="8">
  623. <el-form-item prop="cityName">
  624. <selectCity
  625. placeholder="城市"
  626. addressId="cityId"
  627. addressName="cityName"
  628. v-model="sourceList.pagination"
  629. :data="citySearchData"
  630. ></selectCity>
  631. </el-form-item>
  632. </el-col>
  633. </el-row>
  634. </template>
  635. <template #tags>
  636. <div style="width: 100%">
  637. <el-tag
  638. style="margin-right: 8px"
  639. type="info"
  640. v-for="(tag, index) in sourceList.pagination.tags"
  641. closable
  642. :key="index"
  643. @close="tagSearchClose(tag)"
  644. >
  645. {{ dictValueLabel(tag, customerTag) }}
  646. </el-tag>
  647. <template
  648. v-if="sourceList.pagination.tags.length !== customerTag.length"
  649. >
  650. <el-select
  651. v-if="addTagSearchShow"
  652. v-model="addSearchTag"
  653. style="margin-top: 8px"
  654. @change="changeSearchTag"
  655. >
  656. <el-option
  657. v-for="tag in customerTag"
  658. :key="tag.value"
  659. :label="tag.label"
  660. :value="tag.value"
  661. :disabled="
  662. judgeTagSelect(sourceList.pagination.tags, tag.value)
  663. "
  664. />
  665. </el-select>
  666. <el-tag
  667. style="cursor: pointer"
  668. type="info"
  669. @click="addTagSearchShow = true"
  670. v-else
  671. >
  672. +
  673. </el-tag>
  674. </template>
  675. </div>
  676. </template>
  677. </byForm>
  678. <template #footer>
  679. <el-button @click="cancelSearch()" size="large">取 消</el-button>
  680. <el-button type="primary" @click="submitSearch()" size="large"
  681. >确 定</el-button
  682. >
  683. </template>
  684. </el-dialog>
  685. </div>
  686. </template>
  687. <script setup>
  688. import { ElMessage, ElMessageBox } from "element-plus";
  689. import byTable from "@/components/byTable/index";
  690. import byForm from "@/components/byForm/index";
  691. import { computed, ref } from "vue";
  692. import useUserStore from "@/store/modules/user";
  693. import selectCity from "@/components/selectCity/index.vue";
  694. const { proxy } = getCurrentInstance();
  695. const loading = ref(false);
  696. const loadingOperation = ref(false);
  697. const submitLoading = ref(false);
  698. const openPerson = ref(false);
  699. const customerTag = ref([]);
  700. const customerSource = ref([]);
  701. const customerStatus = ref([]);
  702. const contactType = ref([]);
  703. const userList = ref([]);
  704. const fileList = ref([]);
  705. const uploadData = ref({});
  706. const statisticsType = ref([
  707. {
  708. label: "客户来源统计",
  709. value: 1,
  710. },
  711. {
  712. label: "客户类型统计",
  713. value: 2,
  714. },
  715. ]);
  716. const sourceList = ref({
  717. data: [],
  718. pagination: {
  719. total: 0,
  720. pageNum: 1,
  721. pageSize: 10,
  722. keyword: "",
  723. type: 0,
  724. source: "",
  725. status: "",
  726. name: "",
  727. countryId: "",
  728. provinceId: "",
  729. cityId: "",
  730. customerCode: "",
  731. tag: "",
  732. tags: [],
  733. },
  734. paginationTwo: {
  735. statisticsType: 1,
  736. type: 0,
  737. },
  738. });
  739. const statConfig = computed(() => [
  740. {
  741. label: "客户来源统计",
  742. data: [
  743. {
  744. label: "合计",
  745. num: statisticalData.value.countAmount,
  746. type: 2,
  747. },
  748. ...customerSource.value
  749. .filter((a) => getNum(a.value) > 0)
  750. .map((b) => {
  751. let num = getNum(b.value);
  752. return {
  753. label: b.label,
  754. num,
  755. type: 1,
  756. };
  757. }),
  758. ],
  759. },
  760. {
  761. label: "客户类型统计",
  762. data: [
  763. {
  764. label: "合计",
  765. num: statisticalData.value.countAmount,
  766. type: 2,
  767. },
  768. ...customerStatus.value
  769. .filter((a) => getNum(a.value) > 0)
  770. .map((b) => {
  771. let num = getNum(b.value);
  772. return {
  773. label: b.label,
  774. num,
  775. type: 1,
  776. };
  777. }),
  778. ],
  779. },
  780. ]);
  781. const selectConfig = computed(() => {
  782. return [
  783. {
  784. label: "客户来源",
  785. prop: "source",
  786. data: customerSource.value,
  787. },
  788. {
  789. label: "客户类型",
  790. prop: "status",
  791. data: customerStatus.value,
  792. },
  793. ];
  794. });
  795. const config = computed(() => {
  796. return [
  797. {
  798. attrs: {
  799. label: "",
  800. slot: "isTop",
  801. fixed: "left",
  802. width: 60,
  803. align: "center",
  804. },
  805. },
  806. {
  807. attrs: {
  808. label: "客户名称",
  809. slot: "name",
  810. fixed: "left",
  811. width: 160,
  812. },
  813. },
  814. {
  815. attrs: {
  816. label: "所在城市",
  817. slot: "address",
  818. width: 160,
  819. },
  820. },
  821. {
  822. attrs: {
  823. label: "客户代码",
  824. prop: "customerCode",
  825. width: 120,
  826. },
  827. },
  828. {
  829. attrs: {
  830. label: "客户来源",
  831. prop: "source",
  832. width: 120,
  833. },
  834. render(type) {
  835. return proxy.dictValueLabel(type, customerSource.value);
  836. },
  837. },
  838. {
  839. attrs: {
  840. label: "客户类型",
  841. prop: "status",
  842. width: 120,
  843. },
  844. render(type) {
  845. return proxy.dictValueLabel(type, customerStatus.value);
  846. },
  847. },
  848. {
  849. attrs: {
  850. label: "客户标签",
  851. slot: "tags",
  852. width: 180,
  853. },
  854. },
  855. {
  856. attrs: {
  857. label: "业务员",
  858. prop: "userId",
  859. width: 140,
  860. },
  861. render(type) {
  862. let data = userList.value.filter((item) => item.value == type);
  863. if (data && data.length > 0) {
  864. return data[0].label;
  865. } else {
  866. return "";
  867. }
  868. },
  869. },
  870. {
  871. attrs: {
  872. label: "跟进",
  873. slot: "follow",
  874. "min-width": 440,
  875. },
  876. },
  877. {
  878. attrs: {
  879. label: "操作",
  880. width: 140,
  881. align: "center",
  882. fixed: "right",
  883. },
  884. renderHTML(row) {
  885. return [
  886. {
  887. attrs: {
  888. label: "跟进",
  889. type: "primary",
  890. text: true,
  891. },
  892. el: "button",
  893. click() {
  894. clickFollowUp(row);
  895. },
  896. },
  897. {
  898. attrs: {
  899. label: "认领客户",
  900. type: "primary",
  901. text: true,
  902. },
  903. el: "button",
  904. click() {
  905. ElMessageBox.confirm("是否确定认领该客户?", "提示", {
  906. confirmButtonText: "确定",
  907. cancelButtonText: "取消",
  908. type: "warning",
  909. }).then(() => {
  910. proxy
  911. .post("/customer/CustomerAllocation", {
  912. id: row.id,
  913. userId: useUserStore().user.userId,
  914. })
  915. .then(() => {
  916. ElMessage({
  917. message: "认领成功",
  918. type: "success",
  919. });
  920. getList();
  921. obtainStatisticalData();
  922. });
  923. });
  924. },
  925. },
  926. ];
  927. },
  928. },
  929. ];
  930. });
  931. let modalType = ref("add");
  932. let dialogVisible = ref(false);
  933. let openFollow = ref(false);
  934. let formData = reactive({
  935. data: {
  936. countryId: "44",
  937. },
  938. });
  939. let formPerson = reactive({
  940. data: {},
  941. });
  942. let formFollow = reactive({
  943. data: {},
  944. });
  945. const formOption = reactive({
  946. inline: true,
  947. labelWidth: 100,
  948. itemWidth: 100,
  949. rules: [],
  950. });
  951. const formConfig = computed(() => {
  952. return [
  953. {
  954. type: "input",
  955. prop: "name",
  956. label: "客户名称",
  957. required: true,
  958. itemWidth: 100,
  959. itemType: "text",
  960. },
  961. {
  962. type: "slot",
  963. slotName: "address",
  964. prop: "countryId",
  965. label: "详细地址",
  966. },
  967. {
  968. type: "input",
  969. prop: "customerCode",
  970. label: "客户代码",
  971. required: true,
  972. itemWidth: 100,
  973. itemType: "text",
  974. },
  975. {
  976. type: "input",
  977. prop: "dutyParagraph",
  978. label: "客户税号",
  979. required: true,
  980. itemWidth: 100,
  981. itemType: "text",
  982. },
  983. {
  984. type: "select",
  985. label: "客户来源",
  986. prop: "source",
  987. itemWidth: 50,
  988. data: customerSource.value,
  989. },
  990. {
  991. type: "select",
  992. label: "客户类型",
  993. prop: "status",
  994. itemWidth: 50,
  995. data: customerStatus.value,
  996. },
  997. {
  998. type: "select",
  999. label: "业务员",
  1000. prop: "userId",
  1001. itemWidth: 100,
  1002. data: userList.value,
  1003. clearable: true,
  1004. },
  1005. {
  1006. type: "select",
  1007. label: "客户标签",
  1008. prop: "tags",
  1009. itemWidth: 100,
  1010. multiple: true,
  1011. data: customerTag.value,
  1012. style: {
  1013. width: "100%",
  1014. },
  1015. },
  1016. {
  1017. type: "slot",
  1018. slotName: "person",
  1019. label: "客户联系人",
  1020. },
  1021. ];
  1022. });
  1023. let rules = ref({
  1024. name: [{ required: true, message: "请输入客户名称", trigger: "blur" }],
  1025. name2: [{ required: true, message: "请输入联系人", trigger: "blur" }],
  1026. email: [{ required: true, message: "请输入电子邮箱", trigger: "blur" }],
  1027. countryId: [{ required: true, message: "请选择国家", trigger: "change" }],
  1028. provinceId: [{ required: true, message: "请选择省/州", trigger: "change" }],
  1029. cityId: [{ required: true, message: "请选择城市", trigger: "change" }],
  1030. source: [{ required: true, message: "请选择客户来源", trigger: "change" }],
  1031. status: [{ required: true, message: "请选择类型", trigger: "change" }],
  1032. });
  1033. const formConfigAFollow = computed(() => {
  1034. return [
  1035. {
  1036. type: "date",
  1037. itemType: "datetime",
  1038. label: "跟进时间",
  1039. prop: "date",
  1040. itemWidth: 100,
  1041. },
  1042. {
  1043. type: "input",
  1044. itemType: "textarea",
  1045. label: "跟进内容",
  1046. prop: "content",
  1047. itemWidth: 100,
  1048. },
  1049. {
  1050. type: "slot",
  1051. label: "上传附件",
  1052. prop: "fileList",
  1053. slotName: "fileSlot",
  1054. },
  1055. ];
  1056. });
  1057. let rulesPerson = ref({
  1058. name: [{ required: true, message: "请输入联系人", trigger: "blur" }],
  1059. email: [{ required: true, message: "请输入电子邮箱", trigger: "blur" }],
  1060. type: [{ required: true, message: "请选择类型", trigger: "change" }],
  1061. contactNo: [{ required: true, message: "请输入联系号码", trigger: "blur" }],
  1062. });
  1063. let rulesFollow = ref({
  1064. date: [{ required: true, message: "请选择跟进时间", trigger: "change" }],
  1065. content: [{ required: true, message: "请输入跟进内容", trigger: "blur" }],
  1066. });
  1067. const submit = ref(null);
  1068. const person = ref(null);
  1069. const allocation = ref(null);
  1070. const follow = ref(null);
  1071. const getList = async (req) => {
  1072. sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
  1073. loading.value = true;
  1074. proxy.post("/customer/page", sourceList.value.pagination).then((res) => {
  1075. res.rows.forEach((x) => {
  1076. x.addTagShow = false;
  1077. if (x.tag) {
  1078. x.tag = x.tag.split(",");
  1079. } else {
  1080. x.tag = [];
  1081. }
  1082. });
  1083. sourceList.value.data = res.rows;
  1084. sourceList.value.pagination.total = res.total;
  1085. setTimeout(() => {
  1086. loading.value = false;
  1087. }, 200);
  1088. });
  1089. };
  1090. const openModal = () => {
  1091. modalType.value = "add";
  1092. formData.data = {
  1093. countryId: "44",
  1094. tags: [],
  1095. customerUserList: [
  1096. {
  1097. name: "",
  1098. email: "",
  1099. },
  1100. ],
  1101. };
  1102. getCityData(formData.data.countryId, "20");
  1103. loadingOperation.value = false;
  1104. dialogVisible.value = true;
  1105. };
  1106. const countryData = ref([]);
  1107. const provinceData = ref([]);
  1108. const cityData = ref([]);
  1109. const getCityData = (id, type, isChange) => {
  1110. proxy.post("/customizeArea/list", { parentId: id }).then((res) => {
  1111. if (type === "20") {
  1112. provinceData.value = res;
  1113. if (isChange) {
  1114. formData.data.provinceId = "";
  1115. formData.data.provinceName = "";
  1116. formData.data.cityId = "";
  1117. formData.data.cityName = "";
  1118. }
  1119. } else if (type === "30") {
  1120. cityData.value = res;
  1121. if (isChange) {
  1122. formData.data.cityId = "";
  1123. formData.data.cityName = "";
  1124. }
  1125. } else {
  1126. countryData.value = res;
  1127. }
  1128. });
  1129. };
  1130. getCityData("0");
  1131. const clickAddPerson = () => {
  1132. if (
  1133. formData.data.customerUserList &&
  1134. formData.data.customerUserList.length > 0
  1135. ) {
  1136. formData.data.customerUserList.push({
  1137. name: "",
  1138. email: "",
  1139. });
  1140. } else {
  1141. formData.data.customerUserList = [
  1142. {
  1143. name: "",
  1144. email: "",
  1145. },
  1146. ];
  1147. }
  1148. };
  1149. const submitFollow = () => {
  1150. follow.value.handleSubmit(() => {
  1151. if (fileList.value && fileList.value.length > 0) {
  1152. formFollow.data.fileList = fileList.value.map((item) => {
  1153. return {
  1154. id: item.raw.id,
  1155. fileName: item.raw.fileName,
  1156. fileUrl: item.raw.fileUrl,
  1157. };
  1158. });
  1159. } else {
  1160. formFollow.data.fileList = [];
  1161. }
  1162. proxy
  1163. .post("/customerFollowRecords/" + modalType.value, formFollow.data)
  1164. .then(
  1165. () => {
  1166. ElMessage({
  1167. message: modalType.value == "add" ? "添加成功" : "编辑成功",
  1168. type: "success",
  1169. });
  1170. openFollow.value = false;
  1171. getList();
  1172. },
  1173. (err) => {
  1174. console.log(err);
  1175. }
  1176. );
  1177. });
  1178. };
  1179. const submitForm = () => {
  1180. submit.value.handleSubmit(() => {
  1181. if (
  1182. formData.data.customerUserList &&
  1183. formData.data.customerUserList.length > 0
  1184. ) {
  1185. formData.data.tag = formData.data.tags.join(",");
  1186. submitLoading.value = true;
  1187. proxy.post("/customer/" + modalType.value, formData.data).then(
  1188. () => {
  1189. ElMessage({
  1190. message: modalType.value == "add" ? "添加成功" : "编辑成功",
  1191. type: "success",
  1192. });
  1193. dialogVisible.value = false;
  1194. submitLoading.value = false;
  1195. getList();
  1196. obtainStatisticalData();
  1197. },
  1198. (err) => {
  1199. console.log(err);
  1200. submitLoading.value = false;
  1201. }
  1202. );
  1203. } else {
  1204. ElMessage("请添加客户联系人");
  1205. }
  1206. });
  1207. };
  1208. const getDict = () => {
  1209. proxy
  1210. .getDictOne([
  1211. "customer_tag",
  1212. "customer_source",
  1213. "customer_status",
  1214. "contact_type",
  1215. ])
  1216. .then((res) => {
  1217. customerTag.value = res["customer_tag"].map((x) => ({
  1218. label: x.dictValue,
  1219. value: x.dictKey,
  1220. }));
  1221. customerSource.value = res["customer_source"].map((x) => ({
  1222. label: x.dictValue,
  1223. value: x.dictKey,
  1224. }));
  1225. customerStatus.value = res["customer_status"].map((x) => ({
  1226. label: x.dictValue,
  1227. value: x.dictKey,
  1228. }));
  1229. contactType.value = res["contact_type"].map((x) => ({
  1230. label: x.dictValue,
  1231. value: x.dictKey,
  1232. }));
  1233. });
  1234. proxy
  1235. .get("/tenantUser/list", {
  1236. pageNum: 1,
  1237. pageSize: 10000,
  1238. tenantId: useUserStore().user.tenantId,
  1239. })
  1240. .then((res) => {
  1241. userList.value = res.rows.map((item) => {
  1242. return {
  1243. label: item.nickName,
  1244. value: item.userId,
  1245. };
  1246. });
  1247. });
  1248. };
  1249. getDict();
  1250. getList();
  1251. const handleClickName = (row) => {
  1252. proxy.$router.push({
  1253. path: "/ERP/customer/portrait",
  1254. query: {
  1255. id: row.id,
  1256. },
  1257. });
  1258. };
  1259. const deleteFollow = (data) => {
  1260. ElMessageBox.confirm("是否确认删除该跟进?", "提示", {
  1261. confirmButtonText: "确定",
  1262. cancelButtonText: "取消",
  1263. type: "warning",
  1264. }).then(() => {
  1265. proxy
  1266. .post("/customerFollowRecords/delete", {
  1267. id: data.id,
  1268. })
  1269. .then(() => {
  1270. ElMessage({
  1271. message: "删除成功",
  1272. type: "success",
  1273. });
  1274. getList();
  1275. });
  1276. });
  1277. };
  1278. const addTag = ref("");
  1279. const judgeTagSelect = (data, val) => {
  1280. if (data && data.length > 0) {
  1281. if (data.includes(val)) {
  1282. return true;
  1283. }
  1284. }
  1285. return false;
  1286. };
  1287. const changeTag = (val, item) => {
  1288. let data = {
  1289. id: item.id,
  1290. tag: JSON.parse(JSON.stringify(item.tag)),
  1291. };
  1292. data.tag.push(val);
  1293. data.tag = data.tag.join(",");
  1294. proxy.post("/customer/editTag", data).then(() => {
  1295. ElMessage({
  1296. message: "添加成功",
  1297. type: "success",
  1298. });
  1299. item.addTagShow = false;
  1300. addTag.value = "";
  1301. getList();
  1302. });
  1303. };
  1304. const tagClose = (val, item) => {
  1305. let data = {
  1306. id: item.id,
  1307. tag: JSON.parse(JSON.stringify(item.tag)),
  1308. };
  1309. data.tag = data.tag.filter((row) => row !== val);
  1310. if (data.tag && data.tag.length > 0) {
  1311. data.tag = data.tag.join(",");
  1312. } else {
  1313. data.tag = "";
  1314. }
  1315. proxy.post("/customer/editTag", data).then(() => {
  1316. ElMessage({
  1317. message: "添加成功",
  1318. type: "success",
  1319. });
  1320. item.addTagShow = false;
  1321. addTag.value = "";
  1322. getList();
  1323. });
  1324. };
  1325. const showSelect = (item) => {
  1326. item.addTagShow = true;
  1327. };
  1328. const clickFollowUp = (item) => {
  1329. formFollow.data = {
  1330. customerId: item.id,
  1331. fileList: [],
  1332. };
  1333. fileList.value = [];
  1334. modalType.value = "add";
  1335. openFollow.value = true;
  1336. openRecordMore.value = false;
  1337. };
  1338. const uploadFile = async (file) => {
  1339. const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
  1340. uploadData.value = res.uploadBody;
  1341. file.id = res.id;
  1342. file.fileName = res.fileName;
  1343. file.fileUrl = res.fileUrl;
  1344. return true;
  1345. };
  1346. const onPreviewFile = (file) => {
  1347. window.open(file.raw.fileUrl, "_blank");
  1348. };
  1349. const getStyle = (val) => {
  1350. if (val) {
  1351. return "跟进记录: " + val.replace(/\n|\r\n/g, "<br>");
  1352. } else {
  1353. return "";
  1354. }
  1355. };
  1356. const getContent = (item) => {
  1357. if (item.type === "10") {
  1358. return "跟进记录: " + "报价单总金额 " + proxy.moneyFormat(item.amount, 2);
  1359. } else if (item.type === "20") {
  1360. return (
  1361. "跟进记录: " +
  1362. "合同总金额 " +
  1363. proxy.moneyFormat(item.amount, 2) +
  1364. ` (${item.contractCode}) `
  1365. );
  1366. }
  1367. };
  1368. const recordShow = (item) => {
  1369. if (
  1370. !(item.fileList && item.fileList.length > 0) &&
  1371. JSON.stringify(item.fileList) !== "[]"
  1372. ) {
  1373. proxy
  1374. .post("/fileInfo/getList", { businessIdList: [item.id] })
  1375. .then((fileObj) => {
  1376. item.fileList = fileObj[item.id] || [];
  1377. });
  1378. }
  1379. };
  1380. const openFile = (path) => {
  1381. window.open(path, "_blank");
  1382. };
  1383. const recordList = ref([]);
  1384. const rowData = ref({});
  1385. const openRecordMore = ref(false);
  1386. const queryParams = ref({
  1387. total: 0,
  1388. pageNum: 1,
  1389. pageSize: 10,
  1390. customerId: "",
  1391. });
  1392. const clickMore = (item) => {
  1393. if (openRecordMore.value === false) {
  1394. queryParams.value.pageNum = 1;
  1395. recordList.value = [];
  1396. rowData.value = item;
  1397. queryParams.value.customerId = item.id;
  1398. }
  1399. proxy.post("/customerFollowRecords/page", queryParams.value).then((res) => {
  1400. recordList.value = recordList.value.concat(res.rows);
  1401. queryParams.value.total = res.total;
  1402. proxy
  1403. .post("/fileInfo/getList", {
  1404. businessIdList: res.rows.map((rows) => rows.id),
  1405. })
  1406. .then((fileObj) => {
  1407. for (let i = 0; i < res.rows.length; i++) {
  1408. recordList.value[
  1409. parseInt(
  1410. i + (queryParams.value.pageNum - 1) * queryParams.value.pageSize
  1411. )
  1412. ].fileList =
  1413. fileObj[
  1414. recordList.value[
  1415. parseInt(
  1416. i +
  1417. (queryParams.value.pageNum - 1) * queryParams.value.pageSize
  1418. )
  1419. ].id
  1420. ] || [];
  1421. }
  1422. });
  1423. });
  1424. openRecordMore.value = true;
  1425. };
  1426. const infiniteScroll = () => {
  1427. queryParams.value.pageNum++;
  1428. clickMore();
  1429. };
  1430. const judgeTotal = () => {
  1431. if (
  1432. queryParams.value.pageNum * queryParams.value.pageSize >=
  1433. queryParams.value.total
  1434. ) {
  1435. return true;
  1436. }
  1437. return false;
  1438. };
  1439. const moreIndex = ref(0);
  1440. const clickInformationMore = (item, index) => {
  1441. moreIndex.value = index;
  1442. if (item.contactJson) {
  1443. item.contact = JSON.parse(item.contactJson);
  1444. } else {
  1445. item.contact = [];
  1446. }
  1447. formPerson.data = proxy.deepClone(item);
  1448. openPerson.value = true;
  1449. };
  1450. const clickDelete = (index) => {
  1451. formData.data.customerUserList.splice(index, 1);
  1452. };
  1453. const clickAddMoreInformation = () => {
  1454. if (formPerson.data.contact && formPerson.data.contact.length > 0) {
  1455. formPerson.data.contact.push({
  1456. type: "",
  1457. contactNo: "",
  1458. });
  1459. } else {
  1460. formPerson.data.contact = [
  1461. {
  1462. type: "",
  1463. contactNo: "",
  1464. },
  1465. ];
  1466. }
  1467. };
  1468. const clickInformationDelete = (index) => {
  1469. formPerson.data.contact.splice(index, 1);
  1470. };
  1471. const submitPerson = () => {
  1472. person.value.validate((valid) => {
  1473. if (valid) {
  1474. formPerson.data.contactJson = JSON.stringify(formPerson.data.contact);
  1475. formData.data.customerUserList[moreIndex.value] = formPerson.data;
  1476. openPerson.value = false;
  1477. }
  1478. });
  1479. };
  1480. const deleteTop = (item) => {
  1481. proxy.post("/customerTop/delete", { customerId: item.id }).then(() => {
  1482. item.isTop = 0;
  1483. });
  1484. };
  1485. const addTop = (item) => {
  1486. proxy.post("/customerTop/add", { customerId: item.id }).then(() => {
  1487. item.isTop = 1;
  1488. });
  1489. };
  1490. const statisticalData = ref({
  1491. countAmount: 0,
  1492. customerList: [],
  1493. });
  1494. const obtainStatisticalData = () => {
  1495. proxy
  1496. .post("/customer/sourceStatistics", sourceList.value.paginationTwo)
  1497. .then((res) => {
  1498. statisticalData.value = res;
  1499. });
  1500. };
  1501. obtainStatisticalData();
  1502. const getNum = (val) => {
  1503. let num = 0;
  1504. if (
  1505. statisticalData.value.customerList &&
  1506. statisticalData.value.customerList.length > 0
  1507. ) {
  1508. statisticalData.value.customerList.map((item) => {
  1509. if (sourceList.value.paginationTwo.statisticsType === 1) {
  1510. if (item.source === val) {
  1511. num = item.count;
  1512. }
  1513. } else if (sourceList.value.paginationTwo.statisticsType === 2) {
  1514. if (item.status === val) {
  1515. num = item.count;
  1516. }
  1517. } else if (sourceList.value.paginationTwo.statisticsType === 3) {
  1518. if (item.userId === val) {
  1519. num = item.count;
  1520. }
  1521. }
  1522. });
  1523. }
  1524. return num;
  1525. };
  1526. const countrySearchData = ref([]);
  1527. const provinceSearchData = ref([]);
  1528. const citySearchData = ref([]);
  1529. const getCitySearchData = (id, type, isChange) => {
  1530. proxy.post("/customizeArea/list", { parentId: id }).then((res) => {
  1531. if (type === "20") {
  1532. provinceSearchData.value = res;
  1533. if (isChange) {
  1534. sourceList.value.pagination.provinceId = "";
  1535. sourceList.value.pagination.provinceName = "";
  1536. sourceList.value.pagination.cityId = "";
  1537. sourceList.value.pagination.cityName = "";
  1538. }
  1539. } else if (type === "30") {
  1540. citySearchData.value = res;
  1541. if (isChange) {
  1542. sourceList.value.pagination.cityId = "";
  1543. sourceList.value.pagination.cityName = "";
  1544. }
  1545. } else {
  1546. countrySearchData.value = res;
  1547. }
  1548. });
  1549. };
  1550. getCitySearchData("0");
  1551. const openSearch = ref(false);
  1552. const formSearchConfig = computed(() => {
  1553. return [
  1554. {
  1555. type: "input",
  1556. prop: "name",
  1557. label: "客户名称",
  1558. itemType: "text",
  1559. },
  1560. {
  1561. type: "slot",
  1562. slotName: "address",
  1563. label: "所在城市",
  1564. },
  1565. {
  1566. type: "input",
  1567. prop: "customerCode",
  1568. label: "客户代码",
  1569. itemType: "text",
  1570. },
  1571. {
  1572. type: "select",
  1573. label: "客户来源",
  1574. prop: "source",
  1575. itemWidth: 50,
  1576. data: customerSource.value,
  1577. clearable: true,
  1578. },
  1579. {
  1580. type: "select",
  1581. label: "客户类型",
  1582. prop: "status",
  1583. itemWidth: 50,
  1584. data: customerStatus.value,
  1585. clearable: true,
  1586. },
  1587. {
  1588. type: "slot",
  1589. slotName: "tags",
  1590. label: "客户标签",
  1591. },
  1592. ];
  1593. });
  1594. const addSearchTag = ref("");
  1595. const addTagSearchShow = ref(false);
  1596. let copySearch = ref({});
  1597. const moreSearch = () => {
  1598. if (sourceList.value.pagination.tag) {
  1599. sourceList.value.pagination.tags =
  1600. sourceList.value.pagination.tag.split(",");
  1601. } else {
  1602. sourceList.value.pagination.tags = [];
  1603. }
  1604. addTagSearchShow.value = false;
  1605. copySearch.value = proxy.deepClone(sourceList.value.pagination);
  1606. openSearch.value = true;
  1607. };
  1608. const cancelSearch = () => {
  1609. sourceList.value.pagination = copySearch.value;
  1610. openSearch.value = false;
  1611. };
  1612. const submitSearch = () => {
  1613. if (
  1614. sourceList.value.pagination.tags &&
  1615. sourceList.value.pagination.tags.length > 0
  1616. ) {
  1617. sourceList.value.pagination.tag =
  1618. sourceList.value.pagination.tags.join(",");
  1619. } else {
  1620. sourceList.value.pagination.tag = "";
  1621. }
  1622. openSearch.value = false;
  1623. sourceList.value.pagination.keyword = "";
  1624. sourceList.value.pagination.pageNum = 1;
  1625. getList();
  1626. };
  1627. const tagSearchClose = (val) => {
  1628. sourceList.value.pagination.tags = sourceList.value.pagination.tags.filter(
  1629. (item) => item !== val
  1630. );
  1631. };
  1632. const changeSearchTag = (val) => {
  1633. sourceList.value.pagination.tags.push(val);
  1634. addTagSearchShow.value = false;
  1635. addSearchTag.value = "";
  1636. };
  1637. // 判断当前用户有无销售合同页面权限
  1638. const isHave = ref(false);
  1639. if (
  1640. useUserStore().permissions &&
  1641. useUserStore().permissions.includes("contract")
  1642. ) {
  1643. isHave.value = true;
  1644. }
  1645. // 判断当前用户有无报价单页面权限
  1646. const isHaveOne = ref(false);
  1647. if (
  1648. useUserStore().permissions &&
  1649. useUserStore().permissions.includes("priceSheet")
  1650. ) {
  1651. isHaveOne.value = true;
  1652. }
  1653. const handlePushRoute = (row) => {
  1654. if (row.type === "10") {
  1655. proxy.$router.push({
  1656. name: "PriceSheet",
  1657. query: {
  1658. code: row.code,
  1659. },
  1660. });
  1661. } else if (row.type === "20") {
  1662. proxy.$router.push({
  1663. name: "Contract",
  1664. query: {
  1665. code: row.contractCode,
  1666. },
  1667. });
  1668. }
  1669. };
  1670. const table = ref(null);
  1671. onMounted(() => {
  1672. watch(
  1673. () => table.value.statSelectVal,
  1674. (val) => {
  1675. sourceList.value.paginationTwo.statisticsType = val + 1;
  1676. obtainStatisticalData();
  1677. }
  1678. );
  1679. });
  1680. </script>
  1681. <style lang="scss" scoped>
  1682. .code-class {
  1683. cursor: pointer;
  1684. color: #409eff;
  1685. }
  1686. .tenant {
  1687. padding: 20px;
  1688. }
  1689. .infinite-scroll {
  1690. max-height: calc(89vh - 94px - 70px - 58px - 16px);
  1691. overflow-y: auto;
  1692. &::-webkit-scrollbar {
  1693. width: 0px;
  1694. }
  1695. }
  1696. .by-dropdown {
  1697. position: relative;
  1698. text-align: left;
  1699. height: 32px;
  1700. z-index: 1010;
  1701. padding: 0 10px;
  1702. transition: all 0.5s ease;
  1703. cursor: pointer;
  1704. line-height: 32px;
  1705. .by-dropdown-title {
  1706. font-size: 14px;
  1707. background-color: #fff;
  1708. }
  1709. ul {
  1710. position: absolute;
  1711. left: 0;
  1712. top: 32px;
  1713. padding: 0;
  1714. margin: 0;
  1715. z-index: 1200;
  1716. display: none;
  1717. white-space: nowrap;
  1718. background-color: #fff;
  1719. li {
  1720. list-style: none;
  1721. z-index: 1200;
  1722. font-size: 12px;
  1723. height: 30px;
  1724. padding: 0 10px;
  1725. }
  1726. li:hover {
  1727. background-color: #eff6ff;
  1728. color: #0084ff;
  1729. }
  1730. }
  1731. }
  1732. .by-dropdown::before {
  1733. display: block;
  1734. width: 1px;
  1735. content: " ";
  1736. position: absolute;
  1737. height: 14px;
  1738. top: 8px;
  1739. background-color: #ddd;
  1740. right: 0;
  1741. z-index: 1011;
  1742. }
  1743. .by-dropdown:hover {
  1744. background: #ffffff;
  1745. border-radius: 2px 2px 2px 2px;
  1746. opacity: 1;
  1747. ul {
  1748. background: #ffffff;
  1749. box-shadow: 0px 2px 16px 1px rgba(0, 0, 0, 0.06);
  1750. border-radius: 2px 2px 2px 2px;
  1751. opacity: 1;
  1752. display: block;
  1753. text-align: left;
  1754. }
  1755. }
  1756. .by-dropdown-lists {
  1757. max-height: 50vh;
  1758. overflow-y: auto;
  1759. line-height: 1;
  1760. }
  1761. .statistics-text {
  1762. padding-left: 8px;
  1763. width: 152px;
  1764. overflow: hidden;
  1765. text-overflow: ellipsis;
  1766. white-space: nowrap;
  1767. }
  1768. </style>