follow.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. <!--
  2. * @Author: wanglj 471442253@qq.com
  3. * @Date: 2022-12-15 15:38:21
  4. * @LastEditors: niezch@dashoo.cn
  5. * @LastEditTime: 2023-04-03 13:49:41
  6. * @Description: file content
  7. * @FilePath: \opms_frontend\src\views\customer\follow.vue
  8. -->
  9. <template>
  10. <div class="follow-container">
  11. <el-row :gutter="10" style="margin-bottom: 10px">
  12. <el-col :span="3">
  13. <el-input v-model="queryForm.custName" placeholder="客户" @keyup.enter.native="search" />
  14. </el-col>
  15. <!-- <el-col :span="4">
  16. <el-input v-model="queryForm.custName" placeholder="客户" />
  17. </el-col> -->
  18. <!-- <el-col :span="4">
  19. <el-input v-model.number="queryForm.daysBeforeToday" placeholder="查询天数">
  20. <template slot="append">天</template>
  21. </el-input>
  22. </el-col> -->
  23. <el-col :span="3">
  24. <el-select v-model="queryForm.followType" clearable placeholder="跟进方式" style="width: 100%">
  25. <el-option v-for="item in FollowTypeOptions" :key="item.key" :label="item.value" :value="item.key" />
  26. </el-select>
  27. </el-col>
  28. <el-col :span="5">
  29. <!-- <el-select v-model="queryForm.targetType" clearable placeholder="跟进对象类型" style="width: 100%">
  30. <el-option v-for="item in targetTypeOptions" :key="item.key" :label="item.value" :value="item.key" />
  31. </el-select> -->
  32. <el-input
  33. v-model="queryForm.targetName"
  34. class="input-with-select"
  35. clearable
  36. placeholder="跟进对象"
  37. @keyup.enter.native="search">
  38. <el-select v-model="queryForm.targetType" clearable placeholder="跟进对象类型" slot="prepend">
  39. <el-option v-for="item in targetTypeOptions" :key="item.key" :label="item.value" :value="item.key" />
  40. </el-select>
  41. </el-input>
  42. </el-col>
  43. <!-- <el-col :span="4">
  44. <el-input v-model.number="queryForm.targetName" placeholder="跟进对象" />
  45. </el-col> -->
  46. <el-col :span="3">
  47. <el-input v-model="queryForm.createdName" placeholder="跟进人" @keyup.enter.native="search" />
  48. </el-col>
  49. <el-col :span="6">
  50. <el-date-picker
  51. v-model="queryForm.date"
  52. end-placeholder="跟进结束时间"
  53. range-separator="至"
  54. start-placeholder="跟进开始时间"
  55. style="width: 100%"
  56. type="daterange"
  57. value-format="yyyy-MM-dd" />
  58. </el-col>
  59. <el-col :span="4">
  60. <el-button icon="el-icon-plus" type="primary" @click="search">查询</el-button>
  61. <!-- <el-button @click="addFollowUp">添加</el-button> -->
  62. <el-button icon="el-icon-refresh-right" @click="reset">重置</el-button>
  63. </el-col>
  64. </el-row>
  65. <div class="follow" :style="{ height: $baseTableHeight(0) }">
  66. <ul
  67. v-infinite-scroll="load"
  68. class="records infinite-list"
  69. :infinite-scroll-disabled="loadFlag"
  70. :infinite-scroll-immediate-check="false">
  71. <li v-for="(date, index) in records" :key="index">
  72. <div class="date">
  73. <h2>{{ date.followDay.split('-')[2] }}</h2>
  74. <h3>{{ date.followDay.split('-').splice(0, 2).join('.') }}</h3>
  75. </div>
  76. <ul class="content">
  77. <li v-for="(item, idx) in date.followupList" :key="idx" @click="showComment(item.id)">
  78. <!-- <el-avatar class="user-avatar"
  79. :src="avatar" />-->
  80. <vab-icon class="user-avatar" icon="account-circle-fill" />
  81. <div class="text">
  82. <p class="action">
  83. <span>{{ item.createdName }} 跟进({{ formatType(item.followType) }})</span>
  84. <span>
  85. <vab-icon icon="time-line" />
  86. {{ item.followDate }}
  87. </span>
  88. </p>
  89. <p class="content">
  90. <span>{{ item.followContent }}</span>
  91. </p>
  92. <div class="footer">
  93. <p>
  94. 来自{{ selectDictLabel(targetTypeOptions, item.targetType) }}:
  95. <span @click="jumpTo(item)">{{ item.targetName }}</span>
  96. </p>
  97. <div>
  98. <el-button size="mini" @click="showDetail(item)">
  99. <vab-icon icon="arrow-right-circle-fill" />
  100. 详情
  101. </el-button>
  102. <el-button size="mini" @click="showComment(item.id)">
  103. <!-- <vab-icon icon="chat-3-fill" /> -->
  104. 评论({{ item.commentNumber }})
  105. </el-button>
  106. </div>
  107. </div>
  108. </div>
  109. </li>
  110. </ul>
  111. </li>
  112. </ul>
  113. <div class="comment">
  114. <ul>
  115. <li v-for="item in comments" :key="item.id">
  116. <vab-icon class="user-avatar" icon="account-circle-fill" />
  117. <div class="text">
  118. <p>{{ item.createdName }}</p>
  119. <p>{{ item.content }}</p>
  120. <p>{{ item.createdTime }}</p>
  121. </div>
  122. </li>
  123. </ul>
  124. <div class="form">
  125. <el-input v-model="comment" maxlength="300" resize="none" :rows="5" show-word-limit type="textarea" />
  126. <el-button size="mini" type="primary" @click="handleComment">评论</el-button>
  127. </div>
  128. </div>
  129. </div>
  130. <!-- 跟进详情 -->
  131. <FollowDetail ref="followDetail" />
  132. </div>
  133. </template>
  134. <script>
  135. import to from 'await-to-js'
  136. import api from '@/api/customer/follow'
  137. import FollowDetail from './components/FollowDetail.vue'
  138. export default {
  139. name: 'Follow',
  140. components: {
  141. FollowDetail,
  142. },
  143. data() {
  144. return {
  145. listLoading: false,
  146. queryForm: {
  147. custId: '',
  148. custName: '',
  149. targetType: '',
  150. targetName: '',
  151. createdName: '',
  152. targetId: '',
  153. managerId: '',
  154. daysBeforeToday: 20,
  155. date: [],
  156. followType: '',
  157. },
  158. loadFlag: true,
  159. records: [],
  160. followId: '',
  161. comment: '',
  162. comments: [],
  163. visible: false,
  164. form: {
  165. id: '',
  166. followType: '',
  167. followDate: '',
  168. followContent: '',
  169. targetId: '',
  170. targetType: '',
  171. targetName: '',
  172. custId: '',
  173. custName: '',
  174. contactsId: 0,
  175. contactsName: '',
  176. reminders: '',
  177. nextTime: '',
  178. remark: '',
  179. createdBy: '',
  180. createdName: '',
  181. createdTime: '',
  182. updatedBy: '',
  183. updatedName: '',
  184. updatedTime: '',
  185. deletedTime: '',
  186. },
  187. targetTypeOptions: [],
  188. FollowTypeOptions: [],
  189. }
  190. },
  191. mounted() {
  192. this.fetchData()
  193. this.getOptions()
  194. this.getFollowOptions()
  195. },
  196. methods: {
  197. getOptions() {
  198. Promise.all([this.getDicts('follow_target_type')])
  199. .then(([targetType]) => {
  200. this.targetTypeOptions = targetType.data.values || []
  201. })
  202. .catch((err) => console.log(err))
  203. },
  204. //跟进类型
  205. getFollowOptions() {
  206. Promise.all([this.getDicts('plat_follow_method')])
  207. .then(([targetType]) => {
  208. this.FollowTypeOptions = targetType.data.values || []
  209. })
  210. .catch((err) => console.log(err))
  211. },
  212. search() {
  213. // this.queryForm.daysBeforeToday = 20
  214. this.fetchData()
  215. },
  216. async fetchData() {
  217. let params = { ...this.queryForm }
  218. if (this.queryForm.date && this.queryForm.date.length === 2) {
  219. params.beginTime = this.queryForm.date[0]
  220. params.endTime = this.queryForm.date[1]
  221. params.daysBeforeToday = undefined
  222. } else {
  223. params.daysBeforeToday = 20
  224. }
  225. const [err, res] = await to(api.getListByDay(params))
  226. if (err) return console.log(err, 'err')
  227. this.records = res.data.list || []
  228. await this.$nextTick()
  229. this.loadFlag = false
  230. if (this.records.length) {
  231. if (!this.followId) this.showComment(this.records[0].followupList[0].id)
  232. } else {
  233. this.comments = []
  234. this.followId = ''
  235. }
  236. },
  237. load() {
  238. this.queryForm.daysBeforeToday += 20
  239. this.fetchData()
  240. },
  241. reset() {
  242. this.queryForm = {
  243. custId: '',
  244. custName: '',
  245. targetType: '',
  246. targetName: '',
  247. createdName: '',
  248. targetId: '',
  249. managerId: '',
  250. daysBeforeToday: 20,
  251. date: [],
  252. followType: '',
  253. }
  254. this.fetchData()
  255. },
  256. jumpTo(row) {
  257. let pageName = ''
  258. switch (row.targetType) {
  259. case '10':
  260. pageName = 'CustomerDetail'
  261. break
  262. case '20':
  263. pageName = 'BusinessDetail'
  264. break
  265. case '30':
  266. pageName = 'ContractDetail'
  267. break
  268. case '40':
  269. pageName = ''
  270. break
  271. case '50':
  272. pageName = 'DistributorDetail'
  273. break
  274. }
  275. if (pageName) {
  276. this.$router.push({
  277. name: pageName,
  278. query: {
  279. id: row.targetId,
  280. },
  281. })
  282. }
  283. },
  284. // 详情
  285. showDetail(row) {
  286. this.$refs.followDetail.init({ ...row })
  287. },
  288. async showComment(id) {
  289. this.followId = id
  290. const [err, res] = await to(api.getComment({ followId: id + '' }))
  291. if (err) return
  292. this.comments = res.data.list || []
  293. },
  294. formatType(val) {
  295. let str = ''
  296. if (val == 10) str = '电话'
  297. else if (val == 20) str = '邮件'
  298. else if (val == 30) str = '拜访'
  299. return str
  300. },
  301. // 评论
  302. async handleComment() {
  303. let str = ''
  304. if (!this.followId) str = '请选择跟进记录'
  305. else if (!this.comment) str = '请输入评论'
  306. if (str) return this.$message.warning(str)
  307. let params = {
  308. followId: this.followId + '',
  309. content: this.comment,
  310. remark: '',
  311. }
  312. const [err, res] = await to(api.addComment(params))
  313. if (err) return
  314. this.$message.success(res.msg)
  315. this.showComment(this.followId)
  316. this.comment = ''
  317. this.fetchData()
  318. },
  319. addFollowUp() {
  320. let params = {
  321. followType: '20',
  322. followDate: '2022-10-29',
  323. followContent: '跟进内容',
  324. targetId: 1,
  325. targetType: '20',
  326. targetName: 'dashoo',
  327. custId: 26,
  328. custName: '测试客户1348',
  329. contactsId: 55,
  330. contactsName: 'wanglj',
  331. reminders: '',
  332. nextTime: this.parseTime(new Date()),
  333. remark: '',
  334. }
  335. api.addFollowUp(params)
  336. },
  337. },
  338. }
  339. </script>
  340. <style lang="scss" scoped>
  341. $base: '.follow';
  342. ::v-deep .el-input-group__prepend {
  343. width: 40%;
  344. }
  345. .follow {
  346. display: flex;
  347. .comment {
  348. width: 300px;
  349. display: flex;
  350. flex-direction: column;
  351. border-radius: 4px;
  352. border: 1px solid rgb(215, 232, 244);
  353. ul {
  354. flex: 1;
  355. overflow-y: auto;
  356. padding: 10px;
  357. li {
  358. display: flex;
  359. border-bottom: 1px solid #e3e5e7;
  360. .text {
  361. flex: 1;
  362. padding: 0 10px;
  363. p {
  364. font-weight: 500;
  365. margin: 0;
  366. line-height: 32px;
  367. }
  368. p:first-child {
  369. line-height: 30px;
  370. font-weight: bold;
  371. }
  372. p:last-child {
  373. font-size: 12px;
  374. color: #9499a0;
  375. text-align: right;
  376. }
  377. }
  378. }
  379. .user-avatar {
  380. font-size: 30px;
  381. }
  382. }
  383. .form {
  384. padding: 4px;
  385. text-align: right;
  386. .el-textarea {
  387. margin-bottom: 4px;
  388. }
  389. }
  390. }
  391. .records {
  392. flex: 1;
  393. margin: 0;
  394. height: 100%;
  395. padding: 10px 20px;
  396. list-style: none;
  397. overflow: auto;
  398. > li {
  399. display: flex;
  400. + li {
  401. margin-top: 10px;
  402. }
  403. }
  404. .date {
  405. width: 100px;
  406. display: flex;
  407. flex-direction: column;
  408. align-items: center;
  409. h2,
  410. h3 {
  411. margin: 0;
  412. }
  413. h2 {
  414. font-size: 26px;
  415. line-height: 32px;
  416. }
  417. }
  418. .content {
  419. flex: 1;
  420. list-style: none;
  421. li {
  422. display: flex;
  423. cursor: pointer;
  424. border: 1px solid rgb(215, 232, 244);
  425. background: rgb(247, 251, 254);
  426. border-radius: 4px;
  427. padding: 8px;
  428. + li {
  429. margin-top: 10px;
  430. }
  431. }
  432. .user-avatar {
  433. font-size: 40px;
  434. }
  435. .text {
  436. flex: 1;
  437. padding-left: 20px;
  438. padding-right: 10px;
  439. p {
  440. font-weight: 500;
  441. margin: 0;
  442. line-height: 32px;
  443. span {
  444. color: #1d66dc;
  445. }
  446. }
  447. .action {
  448. display: flex;
  449. justify-content: space-between;
  450. span:first-child {
  451. font-weight: bold;
  452. color: #333;
  453. }
  454. }
  455. .content {
  456. display: flex;
  457. justify-content: space-between;
  458. span {
  459. font-weight: bold;
  460. color: #333;
  461. }
  462. }
  463. .footer {
  464. display: flex;
  465. justify-content: space-between;
  466. align-items: center;
  467. }
  468. }
  469. }
  470. }
  471. }
  472. </style>