index.vue 46 KB

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