index.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  1. <template>
  2. <div class="tenant">
  3. <div class="content">
  4. <byTable
  5. :source="sourceList.data"
  6. :pagination="sourceList.pagination"
  7. :config="config"
  8. :loading="loading"
  9. :selectConfig="selectConfig"
  10. highlight-current-row
  11. :action-list="[
  12. {
  13. text: '添加客户',
  14. action: () => openModal(),
  15. },
  16. ]"
  17. @get-list="getList">
  18. <template #isTop="{ item }">
  19. <div>
  20. <img style="cursor: pointer; width: 20px; transform: translateY(5px)" :src="'/img/isTop.png'" @click="deleteTop(item)" v-if="item.isTop === 1" />
  21. <img style="cursor: pointer; width: 20px; transform: translateY(5px)" :src="'/img/noTop.png'" @click="addTop(item)" v-else />
  22. </div>
  23. </template>
  24. <template #address="{ item }">
  25. <span>{{ item.countryName }}</span>
  26. <span v-if="item.provinceName"> ,{{ item.provinceName }}</span>
  27. <span v-if="item.cityName"> ,{{ item.cityName }}</span>
  28. </template>
  29. <template #name="{ item }">
  30. <div style="cursor: pointer; color: #409eff" @click="handleClickName(item)">
  31. {{ item.name }}
  32. </div>
  33. </template>
  34. <template #tags="{ item }">
  35. <div style="width: 100%">
  36. <el-tag style="margin-right: 8px" type="success" v-for="(tag, index) in item.tag" closable :key="index" @close="tagClose(tag, item)">
  37. {{ dictValueLabel(tag, customerTag) }}
  38. </el-tag>
  39. <template v-if="item.tag.length !== customerTag.length">
  40. <el-select
  41. v-if="item.addTagShow"
  42. v-model="addTag"
  43. style="width: 100%"
  44. @change="
  45. (val) => {
  46. return changeTag(val, item);
  47. }
  48. ">
  49. <el-option v-for="tag in customerTag" :key="tag.value" :label="tag.label" :value="tag.value" :disabled="judgeTagSelect(item.tag, tag.value)" />
  50. </el-select>
  51. <el-tag style="cursor: pointer" type="success" @click="showSelect(item)" v-else> + </el-tag>
  52. </template>
  53. </div>
  54. </template>
  55. <template #follow="{ item }">
  56. <div :class="'getWidth' + item.id" style="width: 100%">
  57. <div style="width: 100%; display: flex">
  58. <template v-if="item.customerFollowRecordsList && item.customerFollowRecordsList.length > 0">
  59. <div
  60. :style="
  61. index > 2
  62. ? 'line-height: 32px; margin-right: 8px; padding: 0 8px; background-color: #eeeeee; border-radius: 4px; cursor: pointer; display: none'
  63. : 'line-height: 32px; margin-right: 8px; padding: 0 8px; background-color: #eeeeee; border-radius: 4px; cursor: pointer'
  64. "
  65. v-for="(record, index) in item.customerFollowRecordsList"
  66. :key="record.id">
  67. <el-popover placement="bottom" :width="300" trigger="hover" @show="recordShow(record)">
  68. <template #reference>
  69. <div>
  70. <span v-if="record.date">{{ record.date.substr(0, 10) }}</span>
  71. <el-icon style="margin-left: 8px; transform: translateY(2px)" @click="deleteFollow(record)"><DeleteFilled /></el-icon>
  72. </div>
  73. </template>
  74. <template #default>
  75. <div style="width: 100%">
  76. <div style="color: #909399; margin: 8px 0">跟进时间: {{ record.date }}</div>
  77. <div style="word-wrap: break-word; margin: 8px 0" v-html="getStyle(record.content)" v-if="record.content"></div>
  78. <div v-else>跟进记录:</div>
  79. <div style="margin: 8px 0; display: flex" v-if="record.fileList && record.fileList.length > 0">
  80. <div style="width: 36px">附件:</div>
  81. <div style="width: calc(100% - 36px)">
  82. <div v-for="(file, index) in record.fileList" :key="index">
  83. <a style="color: #409eff; cursor: pointer" @click="openFile(file.fileUrl)">{{ file.fileName }}</a>
  84. </div>
  85. </div>
  86. </div>
  87. </div>
  88. </template>
  89. </el-popover>
  90. </div>
  91. <div
  92. style="line-height: 32px; margin-right: 8px; padding: 0 8px; background-color: #eeeeee; border-radius: 4px; cursor: pointer"
  93. @click="clickMore(item)"
  94. v-if="item.customerFollowRecordsList.length >= 3">
  95. 更多
  96. </div>
  97. </template>
  98. </div>
  99. </div>
  100. </template>
  101. </byTable>
  102. </div>
  103. <el-dialog :title="modalType == 'add' ? '新增' : '编辑'" v-if="dialogVisible" v-model="dialogVisible" width="800" v-loading="loadingOperation">
  104. <byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="submit">
  105. <template #address>
  106. <el-row style="width: 100%">
  107. <el-col :span="8">
  108. <el-form-item prop="countryId">
  109. <el-select v-model="formData.data.countryId" placeholder="国家" @change="(val) => getCityData(val, '20', true)">
  110. <el-option v-for="item in countryData" :label="item.chineseName" :value="item.id"> </el-option>
  111. </el-select>
  112. </el-form-item>
  113. </el-col>
  114. <el-col :span="8">
  115. <el-form-item prop="provinceId">
  116. <el-select v-model="formData.data.provinceId" placeholder="省/洲" @change="(val) => getCityData(val, '30', true)">
  117. <el-option v-for="item in provinceData" :label="item.name" :value="item.id"> </el-option>
  118. </el-select>
  119. </el-form-item>
  120. </el-col>
  121. <el-col :span="8">
  122. <el-form-item prop="cityId">
  123. <el-select v-model="formData.data.cityId" placeholder="城市">
  124. <el-option v-for="item in cityData" :label="item.name" :value="item.id"> </el-option>
  125. </el-select>
  126. </el-form-item>
  127. </el-col>
  128. </el-row>
  129. <el-row style="margin-top: 20px; width: 100%">
  130. <el-col :span="24">
  131. <el-form-item prop="address">
  132. <el-input v-model="formData.data.address" type="textarea"> </el-input>
  133. </el-form-item>
  134. </el-col>
  135. </el-row>
  136. </template>
  137. <template #person>
  138. <div style="width: 100%">
  139. <el-button type="primary" @click="clickAddPerson">添 加</el-button>
  140. <el-table :data="formData.data.customerUserList" style="width: 100%; margin-top: 16px">
  141. <el-table-column label="联系人" width="160">
  142. <template #default="{ row, $index }">
  143. <div style="width: 100%">
  144. <el-form-item :prop="'customerUserList.' + $index + '.name'" :rules="rules.name2" :inline-message="true">
  145. <el-input v-model="row.name" placeholder="请输入联系人" />
  146. </el-form-item>
  147. </div>
  148. </template>
  149. </el-table-column>
  150. <el-table-column label="电子邮箱">
  151. <template #default="{ row, $index }">
  152. <div style="width: 100%">
  153. <el-form-item :prop="'customerUserList.' + $index + '.email'" :rules="rules.email" :inline-message="true">
  154. <el-input v-model="row.email" placeholder="请输入电子邮箱" />
  155. </el-form-item>
  156. </div>
  157. </template>
  158. </el-table-column>
  159. <el-table-column align="center" label="操作" width="120" fixed="right">
  160. <template #default="{ row, $index }">
  161. <el-button type="primary" link @click="clickInformationMore(row, $index)">更多</el-button>
  162. <el-button type="primary" link @click="clickDelete($index)">删除</el-button>
  163. </template>
  164. </el-table-column>
  165. </el-table>
  166. </div>
  167. </template>
  168. </byForm>
  169. <template #footer>
  170. <el-button @click="dialogVisible = false" size="large">取 消</el-button>
  171. <el-button type="primary" @click="submitForm()" size="large" :loading="submitLoading">确 定</el-button>
  172. </template>
  173. </el-dialog>
  174. <el-dialog title="更多联系方式" v-if="openPerson" v-model="openPerson" width="700">
  175. <el-form :label-position="'top'" :model="formPerson.data" :rules="rulesPerson" ref="person">
  176. <el-form-item label="联系人" prop="name">
  177. <el-input v-model="formPerson.data.name" />
  178. </el-form-item>
  179. <el-form-item label="电子邮箱" prop="email">
  180. <el-input v-model="formPerson.data.email" />
  181. </el-form-item>
  182. <el-form-item label="更多联系方式">
  183. <div style="width: 100%">
  184. <el-button type="primary" @click="clickAddMoreInformation">添 加</el-button>
  185. <el-table :data="formPerson.data.contact" style="width: 100%; margin-top: 16px">
  186. <el-table-column label="类型" width="180">
  187. <template #default="{ row, $index }">
  188. <div style="width: 100%">
  189. <el-form-item :prop="'contact.' + $index + '.type'" :rules="rulesPerson.type" :inline-message="true">
  190. <el-select v-model="row.type" placeholder="请选择类型" style="width: 100%">
  191. <el-option v-for="item in contactType" :key="item.value" :label="item.label" :value="item.value" />
  192. </el-select>
  193. </el-form-item>
  194. </div>
  195. </template>
  196. </el-table-column>
  197. <el-table-column label="联系号码">
  198. <template #default="{ row, $index }">
  199. <div style="width: 100%">
  200. <el-form-item :prop="'contact.' + $index + '.contactNo'" :rules="rulesPerson.contactNo" :inline-message="true">
  201. <el-input v-model="row.contactNo" placeholder="请输入联系号码" />
  202. </el-form-item>
  203. </div>
  204. </template>
  205. </el-table-column>
  206. <el-table-column align="center" label="操作" width="120" fixed="right">
  207. <template #default="{ $index }">
  208. <el-button type="primary" link @click="clickInformationDelete($index)">删除</el-button>
  209. </template>
  210. </el-table-column>
  211. </el-table>
  212. </div>
  213. </el-form-item>
  214. </el-form>
  215. <template #footer>
  216. <el-button @click="openPerson = false" size="large">取 消</el-button>
  217. <el-button type="primary" @click="submitPerson()" size="large">确 定</el-button>
  218. </template>
  219. </el-dialog>
  220. <el-dialog title="添加跟进记录" v-if="openFollow" v-model="openFollow" width="500" destroy-on-close>
  221. <byForm :formConfig="formConfigAFollow" :formOption="formOption" v-model="formFollow.data" :rules="rulesFollow" ref="follow">
  222. <template #fileSlot>
  223. <div style="width: 100%">
  224. <el-upload
  225. v-model:fileList="fileList"
  226. action="https://winfaster.obs.cn-south-1.myhuaweicloud.com"
  227. :data="uploadData"
  228. multiple
  229. :before-upload="uploadFile"
  230. :on-preview="onPreviewFile">
  231. <el-button type="primary">文件上传</el-button>
  232. </el-upload>
  233. </div>
  234. </template>
  235. </byForm>
  236. <template #footer>
  237. <el-button @click="openFollow = false" size="large">取 消</el-button>
  238. <el-button type="primary" @click="submitFollow()" size="large">确 定</el-button>
  239. </template>
  240. </el-dialog>
  241. <el-dialog title="跟进记录" v-if="openRecordMore" v-model="openRecordMore" width="800" destroy-on-close>
  242. <div>
  243. <div style="padding: 8px 0">
  244. <el-button type="primary" @click="clickFollowUp(rowData)" plain>添加跟进记录</el-button>
  245. </div>
  246. <div style="padding-top: 16px">
  247. <div v-infinite-scroll="infiniteScroll" class="infinite-scroll" :infinite-scroll-disabled="judgeTotal()">
  248. <el-timeline>
  249. <el-timeline-item v-for="(record, index) in recordList" :key="index" :timestamp="record.date" hide-timestamp>
  250. <div>
  251. <div style="padding: 0 0 8px 0; display: flex; justify-content: space-between">
  252. <span>{{ dictValueLabel(record.createUser, userList) }}</span>
  253. <span>{{ record.date }}</span>
  254. </div>
  255. <div style="word-wrap: break-word; margin: 8px 0" v-html="getStyle(record.content)" v-if="record.content"></div>
  256. <div style="margin: 8px 0" v-else>跟进记录:</div>
  257. <div style="margin: 8px 0; display: flex" v-if="record.fileList && record.fileList.length > 0">
  258. <div style="width: 36px">附件:</div>
  259. <div style="width: calc(100% - 36px)">
  260. <div v-for="(file, index) in record.fileList" :key="index">
  261. <a style="color: #409eff; cursor: pointer" @click="openFile(file.fileUrl)">{{ file.fileName }}</a>
  262. </div>
  263. </div>
  264. </div>
  265. </div>
  266. </el-timeline-item>
  267. </el-timeline>
  268. </div>
  269. </div>
  270. </div>
  271. <template #footer>
  272. <el-button @click="openRecordMore = false" size="large">关 闭</el-button>
  273. </template>
  274. </el-dialog>
  275. </div>
  276. </template>
  277. <script setup>
  278. import { ElMessage, ElMessageBox } from "element-plus";
  279. import byTable from "@/components/byTable/index";
  280. import byForm from "@/components/byForm/index";
  281. import { computed, ref } from "vue";
  282. import useUserStore from "@/store/modules/user";
  283. const { proxy } = getCurrentInstance();
  284. const loading = ref(false);
  285. const loadingOperation = ref(false);
  286. const submitLoading = ref(false);
  287. const openPerson = ref(false);
  288. const customerTag = ref([]);
  289. const customerSource = ref([]);
  290. const customerStatus = ref([]);
  291. const contactType = ref([]);
  292. const userList = ref([]);
  293. const fileList = ref([]);
  294. const uploadData = ref({});
  295. const sourceList = ref({
  296. data: [],
  297. pagination: {
  298. total: 0,
  299. pageNum: 1,
  300. pageSize: 10,
  301. status: "",
  302. source: "",
  303. type: "",
  304. },
  305. });
  306. const selectConfig = computed(() => {
  307. return [
  308. {
  309. label: "客户状态",
  310. prop: "type",
  311. data: [
  312. {
  313. label: "公海",
  314. value: "0",
  315. },
  316. {
  317. label: "私海",
  318. value: "1",
  319. },
  320. ],
  321. },
  322. {
  323. label: "客户来源",
  324. prop: "source",
  325. data: customerSource.value,
  326. },
  327. {
  328. label: "客户类型",
  329. prop: "status",
  330. data: customerStatus.value,
  331. },
  332. ];
  333. });
  334. const config = computed(() => {
  335. return [
  336. {
  337. attrs: {
  338. label: "",
  339. slot: "isTop",
  340. fixed: "left",
  341. width: 60,
  342. align: "center",
  343. },
  344. },
  345. {
  346. attrs: {
  347. label: "客户名称",
  348. prop: "name",
  349. slot: "name",
  350. fixed: "left",
  351. width: 160,
  352. },
  353. },
  354. {
  355. attrs: {
  356. label: "所在城市",
  357. slot: "address",
  358. width: 160,
  359. },
  360. },
  361. {
  362. attrs: {
  363. label: "客户代码",
  364. prop: "customerCode",
  365. width: 120,
  366. },
  367. },
  368. {
  369. attrs: {
  370. label: "客户来源",
  371. prop: "source",
  372. width: 120,
  373. },
  374. render(type) {
  375. return proxy.dictValueLabel(type, customerSource.value);
  376. },
  377. },
  378. {
  379. attrs: {
  380. label: "客户类型",
  381. prop: "status",
  382. width: 120,
  383. },
  384. render(type) {
  385. return proxy.dictValueLabel(type, customerStatus.value);
  386. },
  387. },
  388. {
  389. attrs: {
  390. label: "客户标签",
  391. slot: "tags",
  392. width: 180,
  393. },
  394. },
  395. {
  396. attrs: {
  397. label: "业务员",
  398. prop: "userId",
  399. width: 140,
  400. },
  401. render(type) {
  402. let data = userList.value.filter((item) => item.value == type);
  403. if (data && data.length > 0) {
  404. return data[0].label;
  405. } else {
  406. return "";
  407. }
  408. },
  409. },
  410. {
  411. attrs: {
  412. label: "跟进",
  413. slot: "follow",
  414. "min-width": 440,
  415. },
  416. },
  417. {
  418. attrs: {
  419. label: "操作",
  420. width: 140,
  421. align: "center",
  422. fixed: "right",
  423. },
  424. renderHTML(row) {
  425. return [
  426. {
  427. attrs: {
  428. label: "跟进",
  429. type: "primary",
  430. text: true,
  431. },
  432. el: "button",
  433. click() {
  434. clickFollowUp(row);
  435. },
  436. },
  437. {
  438. attrs: {
  439. label: "退回公海",
  440. type: "primary",
  441. text: true,
  442. },
  443. el: "button",
  444. click() {
  445. ElMessageBox.confirm("是否确定将该客户退回公海?", "提示", {
  446. confirmButtonText: "确定",
  447. cancelButtonText: "取消",
  448. type: "warning",
  449. }).then(() => {
  450. proxy
  451. .post("/customer/CustomerAllocation", {
  452. id: row.id,
  453. userId: "",
  454. })
  455. .then(() => {
  456. ElMessage({
  457. message: "退回成功",
  458. type: "success",
  459. });
  460. getList();
  461. });
  462. });
  463. },
  464. },
  465. ];
  466. },
  467. },
  468. ];
  469. });
  470. let modalType = ref("add");
  471. let dialogVisible = ref(false);
  472. let openFollow = ref(false);
  473. let formData = reactive({
  474. data: {
  475. countryId: "China",
  476. },
  477. });
  478. let formPerson = reactive({
  479. data: {},
  480. });
  481. let formAllocation = reactive({
  482. data: {},
  483. });
  484. let formFollow = reactive({
  485. data: {},
  486. });
  487. const formOption = reactive({
  488. inline: true,
  489. labelWidth: 100,
  490. itemWidth: 100,
  491. rules: [],
  492. });
  493. const formConfig = computed(() => {
  494. return [
  495. {
  496. type: "input",
  497. prop: "name",
  498. label: "客户名称",
  499. required: true,
  500. itemWidth: 100,
  501. itemType: "text",
  502. },
  503. {
  504. type: "slot",
  505. slotName: "address",
  506. prop: "countryId",
  507. label: "详细地址",
  508. },
  509. {
  510. type: "input",
  511. prop: "customerCode",
  512. label: "客户代码",
  513. required: true,
  514. itemWidth: 100,
  515. itemType: "text",
  516. },
  517. {
  518. type: "select",
  519. label: "客户来源",
  520. prop: "source",
  521. itemWidth: 50,
  522. data: customerSource.value,
  523. },
  524. {
  525. type: "select",
  526. label: "客户类型",
  527. prop: "status",
  528. itemWidth: 50,
  529. data: customerStatus.value,
  530. },
  531. {
  532. type: "select",
  533. label: "业务员",
  534. prop: "userId",
  535. itemWidth: 100,
  536. data: userList.value,
  537. disabled: true,
  538. },
  539. {
  540. type: "select",
  541. label: "客户标签",
  542. prop: "tags",
  543. itemWidth: 100,
  544. multiple: true,
  545. data: customerTag.value,
  546. style: {
  547. width: "100%",
  548. },
  549. },
  550. {
  551. type: "slot",
  552. slotName: "person",
  553. label: "客户联系人",
  554. },
  555. ];
  556. });
  557. let rules = ref({
  558. name: [{ required: true, message: "请输入客户名称", trigger: "blur" }],
  559. name2: [{ required: true, message: "请输入联系人", trigger: "blur" }],
  560. email: [{ required: true, message: "请输入电子邮箱", trigger: "blur" }],
  561. countryId: [{ required: true, message: "请选择国家", trigger: "change" }],
  562. provinceId: [{ required: true, message: "请选择省/州", trigger: "change" }],
  563. cityId: [{ required: true, message: "请选择城市", trigger: "change" }],
  564. source: [{ required: true, message: "请选择客户来源", trigger: "change" }],
  565. status: [{ required: true, message: "请选择类型", trigger: "change" }],
  566. });
  567. const formConfigAFollow = computed(() => {
  568. return [
  569. {
  570. type: "date",
  571. itemType: "datetime",
  572. label: "跟进时间",
  573. prop: "date",
  574. itemWidth: 100,
  575. },
  576. {
  577. type: "input",
  578. itemType: "textarea",
  579. label: "跟进内容",
  580. prop: "content",
  581. itemWidth: 100,
  582. },
  583. {
  584. type: "slot",
  585. label: "上传附件",
  586. prop: "fileList",
  587. slotName: "fileSlot",
  588. },
  589. ];
  590. });
  591. let rulesPerson = ref({
  592. name: [{ required: true, message: "请输入联系人", trigger: "blur" }],
  593. email: [{ required: true, message: "请输入电子邮箱", trigger: "blur" }],
  594. type: [{ required: true, message: "请选择类型", trigger: "change" }],
  595. contactNo: [{ required: true, message: "请输入联系号码", trigger: "blur" }],
  596. });
  597. let rulesFollow = ref({
  598. date: [{ required: true, message: "请选择跟进时间", trigger: "change" }],
  599. content: [{ required: true, message: "请输入跟进内容", trigger: "blur" }],
  600. });
  601. const submit = ref(null);
  602. const person = ref(null);
  603. const allocation = ref(null);
  604. const follow = ref(null);
  605. const getList = async (req) => {
  606. sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
  607. loading.value = true;
  608. proxy.post("customer/privateSeaPage", sourceList.value.pagination).then((res) => {
  609. res.rows.forEach((x) => {
  610. x.addTagShow = false;
  611. if (x.tag) {
  612. x.tag = x.tag.split(",");
  613. } else {
  614. x.tag = [];
  615. }
  616. });
  617. sourceList.value.data = res.rows;
  618. sourceList.value.pagination.total = res.total;
  619. setTimeout(() => {
  620. loading.value = false;
  621. }, 200);
  622. });
  623. };
  624. const openModal = () => {
  625. modalType.value = "add";
  626. formData.data = {
  627. countryId: "China",
  628. userId: useUserStore().user.userId,
  629. tags: [],
  630. };
  631. getCityData(formData.data.countryId, "20");
  632. loadingOperation.value = false;
  633. dialogVisible.value = true;
  634. };
  635. const countryData = ref([]);
  636. const provinceData = ref([]);
  637. const cityData = ref([]);
  638. const getCityData = (id, type, isChange) => {
  639. proxy.post("/areaInfo/list", { parentId: id }).then((res) => {
  640. if (type === "20") {
  641. provinceData.value = res;
  642. if (isChange) {
  643. formData.data.provinceId = "";
  644. formData.data.cityId = "";
  645. }
  646. } else if (type === "30") {
  647. cityData.value = res;
  648. if (isChange) {
  649. formData.data.cityId = "";
  650. }
  651. } else {
  652. countryData.value = res;
  653. }
  654. });
  655. };
  656. getCityData("0");
  657. const clickAddPerson = () => {
  658. if (formData.data.customerUserList && formData.data.customerUserList.length > 0) {
  659. formData.data.customerUserList.push({
  660. name: "",
  661. email: "",
  662. });
  663. } else {
  664. formData.data.customerUserList = [
  665. {
  666. name: "",
  667. email: "",
  668. },
  669. ];
  670. }
  671. };
  672. const submitFollow = () => {
  673. follow.value.handleSubmit(() => {
  674. if (fileList.value && fileList.value.length > 0) {
  675. formFollow.data.fileList = fileList.value.map((item) => {
  676. return {
  677. id: item.raw.id,
  678. fileName: item.raw.fileName,
  679. fileUrl: item.raw.fileUrl,
  680. };
  681. });
  682. } else {
  683. formFollow.data.fileList = [];
  684. }
  685. proxy.post("/customerFollowRecords/" + modalType.value, formFollow.data).then(
  686. () => {
  687. ElMessage({
  688. message: modalType.value == "add" ? "添加成功" : "编辑成功",
  689. type: "success",
  690. });
  691. openFollow.value = false;
  692. getList();
  693. },
  694. (err) => {
  695. console.log(err);
  696. }
  697. );
  698. });
  699. };
  700. const submitForm = () => {
  701. submit.value.handleSubmit(() => {
  702. if (formData.data.customerUserList && formData.data.customerUserList.length > 0) {
  703. formData.data.tag = formData.data.tags.join(",");
  704. submitLoading.value = true;
  705. proxy.post("/customer/" + modalType.value, formData.data).then(
  706. () => {
  707. ElMessage({
  708. message: modalType.value == "add" ? "添加成功" : "编辑成功",
  709. type: "success",
  710. });
  711. dialogVisible.value = false;
  712. submitLoading.value = false;
  713. getList();
  714. },
  715. (err) => {
  716. console.log(err);
  717. submitLoading.value = false;
  718. }
  719. );
  720. } else {
  721. ElMessage("请添加客户联系人");
  722. }
  723. });
  724. };
  725. const getDict = () => {
  726. proxy.getDictOne(["customer_tag", "customer_source", "customer_status", "contact_type"]).then((res) => {
  727. customerTag.value = res["customer_tag"].map((x) => ({
  728. label: x.dictValue,
  729. value: x.dictKey,
  730. }));
  731. customerSource.value = res["customer_source"].map((x) => ({
  732. label: x.dictValue,
  733. value: x.dictKey,
  734. }));
  735. customerStatus.value = res["customer_status"].map((x) => ({
  736. label: x.dictValue,
  737. value: x.dictKey,
  738. }));
  739. contactType.value = res["contact_type"].map((x) => ({
  740. label: x.dictValue,
  741. value: x.dictKey,
  742. }));
  743. });
  744. proxy
  745. .get("/tenantUser/list", {
  746. pageNum: 1,
  747. pageSize: 10000,
  748. tenantId: useUserStore().user.tenantId,
  749. })
  750. .then((res) => {
  751. userList.value = res.rows.map((item) => {
  752. return {
  753. label: item.nickName,
  754. value: item.userId,
  755. };
  756. });
  757. });
  758. };
  759. getDict();
  760. getList();
  761. const handleClickName = (row) => {
  762. proxy.$router.push({
  763. path: "/customer/customer/portrait",
  764. query: {
  765. id: row.id,
  766. },
  767. });
  768. };
  769. const deleteFollow = (data) => {
  770. ElMessageBox.confirm("是否确认删除该跟进?", "提示", {
  771. confirmButtonText: "确定",
  772. cancelButtonText: "取消",
  773. type: "warning",
  774. }).then(() => {
  775. proxy
  776. .post("/customerFollowRecords/delete", {
  777. id: data.id,
  778. })
  779. .then(() => {
  780. ElMessage({
  781. message: "删除成功",
  782. type: "success",
  783. });
  784. getList();
  785. });
  786. });
  787. };
  788. const addTag = ref("");
  789. const judgeTagSelect = (data, val) => {
  790. if (data && data.length > 0) {
  791. if (data.includes(val)) {
  792. return true;
  793. }
  794. }
  795. return false;
  796. };
  797. const changeTag = (val, item) => {
  798. let data = {
  799. id: item.id,
  800. tag: JSON.parse(JSON.stringify(item.tag)),
  801. };
  802. data.tag.push(val);
  803. data.tag = data.tag.join(",");
  804. proxy.post("/customer/editTag", data).then(() => {
  805. ElMessage({
  806. message: "添加成功",
  807. type: "success",
  808. });
  809. item.addTagShow = false;
  810. addTag.value = "";
  811. getList();
  812. });
  813. };
  814. const tagClose = (val, item) => {
  815. let data = {
  816. id: item.id,
  817. tag: JSON.parse(JSON.stringify(item.tag)),
  818. };
  819. data.tag = data.tag.filter((row) => row !== val);
  820. if (data.tag && data.tag.length > 0) {
  821. data.tag = data.tag.join(",");
  822. } else {
  823. data.tag = "";
  824. }
  825. proxy.post("/customer/editTag", data).then(() => {
  826. ElMessage({
  827. message: "添加成功",
  828. type: "success",
  829. });
  830. item.addTagShow = false;
  831. addTag.value = "";
  832. getList();
  833. });
  834. };
  835. const showSelect = (item) => {
  836. item.addTagShow = true;
  837. };
  838. const clickFollowUp = (item) => {
  839. formFollow.data = {
  840. customerId: item.id,
  841. fileList: [],
  842. };
  843. fileList.value = [];
  844. modalType.value = "add";
  845. openFollow.value = true;
  846. openRecordMore.value = false;
  847. };
  848. const uploadFile = async (file) => {
  849. const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
  850. uploadData.value = res.uploadBody;
  851. file.id = res.id;
  852. file.fileName = res.fileName;
  853. file.fileUrl = res.fileUrl;
  854. return true;
  855. };
  856. const onPreviewFile = (file) => {
  857. window.open(file.raw.fileUrl, "_blank");
  858. };
  859. const getStyle = (val) => {
  860. if (val) {
  861. return "跟进记录: " + val.replace(/\n|\r\n/g, "<br>");
  862. } else {
  863. return "";
  864. }
  865. };
  866. const recordShow = (item) => {
  867. if (!(item.fileList && item.fileList.length > 0)) {
  868. proxy.post("/fileInfo/getList", { businessIdList: [item.id] }).then((fileObj) => {
  869. item.fileList = fileObj[item.id] || [];
  870. });
  871. }
  872. };
  873. const openFile = (path) => {
  874. window.open(path, "_blank");
  875. };
  876. const recordList = ref([]);
  877. const rowData = ref({});
  878. const openRecordMore = ref(false);
  879. const queryParams = ref({
  880. total: 0,
  881. pageNum: 1,
  882. pageSize: 10,
  883. customerId: "",
  884. });
  885. const clickMore = (item) => {
  886. if (openRecordMore.value === false) {
  887. queryParams.value.pageNum = 1;
  888. recordList.value = [];
  889. rowData.value = item;
  890. queryParams.value.customerId = item.id;
  891. }
  892. proxy.post("/customerFollowRecords/page", queryParams.value).then((res) => {
  893. recordList.value = recordList.value.concat(res.rows);
  894. queryParams.value.total = res.total;
  895. proxy.post("/fileInfo/getList", { businessIdList: res.rows.map((rows) => rows.id) }).then((fileObj) => {
  896. for (let i = 0; i < res.rows.length; i++) {
  897. recordList.value[parseInt(i + (queryParams.value.pageNum - 1) * queryParams.value.pageSize)].fileList =
  898. fileObj[recordList.value[parseInt(i + (queryParams.value.pageNum - 1) * queryParams.value.pageSize)].id] || [];
  899. }
  900. });
  901. });
  902. openRecordMore.value = true;
  903. };
  904. const infiniteScroll = () => {
  905. queryParams.value.pageNum++;
  906. clickMore();
  907. };
  908. const judgeTotal = () => {
  909. if (queryParams.value.pageNum * queryParams.value.pageSize >= queryParams.value.total) {
  910. return true;
  911. }
  912. return false;
  913. };
  914. const moreIndex = ref(0);
  915. const clickInformationMore = (item, index) => {
  916. moreIndex.value = index;
  917. if (item.contactJson) {
  918. item.contact = JSON.parse(item.contactJson);
  919. } else {
  920. item.contact = [];
  921. }
  922. formPerson.data = proxy.deepClone(item);
  923. openPerson.value = true;
  924. };
  925. const clickDelete = (index) => {
  926. formData.data.customerUserList.splice(index, 1);
  927. };
  928. const clickAddMoreInformation = () => {
  929. if (formPerson.data.contact && formPerson.data.contact.length > 0) {
  930. formPerson.data.contact.push({
  931. type: "",
  932. contactNo: "",
  933. });
  934. } else {
  935. formPerson.data.contact = [
  936. {
  937. type: "",
  938. contactNo: "",
  939. },
  940. ];
  941. }
  942. };
  943. const clickInformationDelete = (index) => {
  944. formPerson.data.contact.splice(index, 1);
  945. };
  946. const submitPerson = () => {
  947. person.value.validate((valid) => {
  948. if (valid) {
  949. formPerson.data.contactJson = JSON.stringify(formPerson.data.contact);
  950. formData.data.customerUserList[moreIndex.value] = formPerson.data;
  951. openPerson.value = false;
  952. }
  953. });
  954. };
  955. const deleteTop = (item) => {
  956. proxy.post("/customerTop/delete", { customerId: item.id }).then(() => {
  957. item.isTop = 0;
  958. });
  959. };
  960. const addTop = (item) => {
  961. proxy.post("/customerTop/add", { customerId: item.id }).then(() => {
  962. item.isTop = 1;
  963. });
  964. };
  965. </script>
  966. <style lang="scss" scoped>
  967. .tenant {
  968. padding: 20px;
  969. }
  970. .infinite-scroll {
  971. max-height: calc(89vh - 94px - 70px - 58px - 16px);
  972. overflow-y: auto;
  973. &::-webkit-scrollbar {
  974. width: 0px;
  975. }
  976. }
  977. </style>