|
|
@@ -0,0 +1,528 @@
|
|
|
+<!--
|
|
|
+ * @Author: wanglj 471442253@qq.com
|
|
|
+ * @Date: 2022-12-15 15:38:21
|
|
|
+ * @LastEditors: niezch@dashoo.cn
|
|
|
+ * @LastEditTime: 2023-03-24 17:52:18
|
|
|
+ * @Description: file content
|
|
|
+ * @FilePath: \opms_frontend\src\views\customer\followsell.vue
|
|
|
+-->
|
|
|
+<template>
|
|
|
+ <div class="follow-container">
|
|
|
+ <el-row :gutter="10" style="margin-bottom: 10px">
|
|
|
+ <!-- <el-col :span="4">
|
|
|
+ <el-input v-model="queryForm.custName" placeholder="客户" />
|
|
|
+ </el-col> -->
|
|
|
+
|
|
|
+ <!-- <el-col :span="4">
|
|
|
+ <el-input v-model.number="queryForm.daysBeforeToday" placeholder="查询天数">
|
|
|
+ <template slot="append">天</template>
|
|
|
+ </el-input>
|
|
|
+ </el-col> -->
|
|
|
+ <el-col :span="4">
|
|
|
+ <el-select v-model="queryForm.followType" clearable placeholder="跟进类型" style="width: 100%">
|
|
|
+ <el-option v-for="item in FollowTypeOptions" :key="item.key" :label="item.value" :value="item.key" />
|
|
|
+ </el-select>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <!-- <el-select v-model="queryForm.targetType" clearable placeholder="跟进对象类型" style="width: 100%">
|
|
|
+ <el-option v-for="item in targetTypeOptions" :key="item.key" :label="item.value" :value="item.key" />
|
|
|
+ </el-select> -->
|
|
|
+ <el-input v-model="queryForm.targetName" class="input-with-select" clearable placeholder="跟进对象">
|
|
|
+ <el-select v-model="queryForm.targetType" clearable placeholder="跟进对象类型" slot="prepend">
|
|
|
+ <el-option v-for="item in targetTypeOptions" :key="item.key" :label="item.value" :value="item.key" />
|
|
|
+ </el-select>
|
|
|
+ </el-input>
|
|
|
+ </el-col>
|
|
|
+ <!-- <el-col :span="4">
|
|
|
+ <el-input v-model.number="queryForm.targetName" placeholder="跟进对象" />
|
|
|
+ </el-col> -->
|
|
|
+ <el-col :span="4">
|
|
|
+ <el-input v-model="queryForm.createdName" clearable placeholder="跟进人" />
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="queryForm.date"
|
|
|
+ end-placeholder="跟进结束时间"
|
|
|
+ range-separator="至"
|
|
|
+ start-placeholder="跟进开始时间"
|
|
|
+ style="width: 100%"
|
|
|
+ type="daterange" />
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="4">
|
|
|
+ <el-button icon="el-icon-plus" type="primary" @click="search">查询</el-button>
|
|
|
+ <!-- <el-button @click="addFollowUp">添加</el-button> -->
|
|
|
+ <el-button icon="el-icon-refresh-right" @click="reset">重置</el-button>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <div class="follow">
|
|
|
+ <ul
|
|
|
+ v-infinite-scroll="load"
|
|
|
+ class="records infinite-list"
|
|
|
+ :infinite-scroll-disabled="loadFlag"
|
|
|
+ :infinite-scroll-immediate-check="false">
|
|
|
+ <li v-for="(date, index) in records" :key="index">
|
|
|
+ <div class="date">
|
|
|
+ <h2>{{ date.followDay.split('-')[2] }}</h2>
|
|
|
+ <h3>{{ date.followDay.split('-').splice(0, 2).join('.') }}</h3>
|
|
|
+ </div>
|
|
|
+ <ul class="content">
|
|
|
+ <li v-for="(item, idx) in date.followupList" :key="idx" @click="showComment(item.id)">
|
|
|
+ <!-- <el-avatar class="user-avatar"
|
|
|
+ :src="avatar" />-->
|
|
|
+ <vab-icon class="user-avatar" icon="account-circle-fill" />
|
|
|
+ <div class="text">
|
|
|
+ <p class="action">
|
|
|
+ <span>{{ item.createdName }} 跟进({{ formatType(item.followType) }})</span>
|
|
|
+ <span>
|
|
|
+ <vab-icon icon="time-line" />
|
|
|
+ {{ item.followDate }}
|
|
|
+ </span>
|
|
|
+ </p>
|
|
|
+ <p class="content">
|
|
|
+ <span>{{ item.followContent }}</span>
|
|
|
+ </p>
|
|
|
+ <div class="footer">
|
|
|
+ <p>
|
|
|
+ 来自{{ selectDictLabel(targetTypeOptions, item.targetType) }}:
|
|
|
+ <span @click="jumpTo(item)">{{ item.targetName }}</span>
|
|
|
+ </p>
|
|
|
+ <div>
|
|
|
+ <el-button size="mini" @click="showDetail(item)">
|
|
|
+ <vab-icon icon="arrow-right-circle-fill" />
|
|
|
+ 详情
|
|
|
+ </el-button>
|
|
|
+ <el-button size="mini" @click="showComment(item.id)">
|
|
|
+ <!-- <vab-icon icon="chat-3-fill" /> -->
|
|
|
+ 评论({{ item.commentNumber }})
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ <div class="comment">
|
|
|
+ <ul>
|
|
|
+ <li v-for="item in comments" :key="item.id">
|
|
|
+ <vab-icon class="user-avatar" icon="account-circle-fill" />
|
|
|
+ <div class="text">
|
|
|
+ <p>{{ item.createdName }}</p>
|
|
|
+ <p>{{ item.content }}</p>
|
|
|
+ <p>{{ item.createdTime }}</p>
|
|
|
+ </div>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ <div class="form">
|
|
|
+ <el-input v-model="comment" maxlength="300" resize="none" :rows="5" show-word-limit type="textarea" />
|
|
|
+ <el-button size="mini" type="primary" @click="handleComment">评论</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 跟进详情 -->
|
|
|
+ <FollowDetail ref="followDetail" />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+ import to from 'await-to-js'
|
|
|
+ import api from '@/api/customer/follow'
|
|
|
+ import FollowDetail from './components/FollowDetail.vue'
|
|
|
+
|
|
|
+ export default {
|
|
|
+ name: 'FollowSell',
|
|
|
+ components: {
|
|
|
+ FollowDetail,
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ listLoading: false,
|
|
|
+ queryForm: {
|
|
|
+ custId: '',
|
|
|
+ custName: '',
|
|
|
+ targetType: '',
|
|
|
+ targetName: '',
|
|
|
+ createdName: '',
|
|
|
+ targetId: '',
|
|
|
+ managerId: '',
|
|
|
+ daysBeforeToday: 20,
|
|
|
+ date: [new Date(this.getLastMonth(new Date())), new Date()],
|
|
|
+ followType: '',
|
|
|
+ },
|
|
|
+ loadFlag: true,
|
|
|
+ records: [],
|
|
|
+ followId: '',
|
|
|
+ comment: '',
|
|
|
+ comments: [],
|
|
|
+ visible: false,
|
|
|
+ form: {
|
|
|
+ id: '',
|
|
|
+ followType: '',
|
|
|
+ followDate: '',
|
|
|
+ followContent: '',
|
|
|
+ targetId: '',
|
|
|
+ targetType: '',
|
|
|
+ targetName: '',
|
|
|
+ custId: '',
|
|
|
+ custName: '',
|
|
|
+ contactsId: 0,
|
|
|
+ contactsName: '',
|
|
|
+ reminders: '',
|
|
|
+ nextTime: '',
|
|
|
+ remark: '',
|
|
|
+ createdBy: '',
|
|
|
+ createdName: '',
|
|
|
+ createdTime: '',
|
|
|
+ updatedBy: '',
|
|
|
+ updatedName: '',
|
|
|
+ updatedTime: '',
|
|
|
+ deletedTime: '',
|
|
|
+ },
|
|
|
+ targetTypeOptions: [],
|
|
|
+ FollowTypeOptions: [],
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.fetchData()
|
|
|
+ this.getOptions()
|
|
|
+ this.getFollowOptions()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ getLastMonth(date) {
|
|
|
+ const now = new Date(date)
|
|
|
+ const year = now.getFullYear()
|
|
|
+ const month = now.getMonth() + 1
|
|
|
+ const day = now.getDate()
|
|
|
+ const nowMonthDay = new Date(year, month, 0).getDate() // 当前月的总天数
|
|
|
+ if (month - 1 <= 0) return year - 1 + '-' + 12 + '-' + day // 如果是1月,年数往前推一年
|
|
|
+ const lastMonthDay = new Date(year, parseInt(month) - 1, 0).getDate()
|
|
|
+ if (lastMonthDay >= day) return year + '-' + (month - 1) + '-' + day
|
|
|
+ if (day < nowMonthDay) return year + '-' + (month - 1) + '-' + (lastMonthDay - (nowMonthDay - day)) // 1个月前所在月的总天数小于现在的天日期
|
|
|
+ return year + '-' + (month - 1) + '-' + lastMonthDay // 当前天日期小于当前月总天数
|
|
|
+ },
|
|
|
+ getOptions() {
|
|
|
+ Promise.all([this.getDicts('follow_target_type')])
|
|
|
+ .then(([targetType]) => {
|
|
|
+ this.targetTypeOptions = targetType.data.values || []
|
|
|
+ })
|
|
|
+ .catch((err) => console.log(err))
|
|
|
+ },
|
|
|
+ //跟进类型
|
|
|
+ getFollowOptions() {
|
|
|
+ Promise.all([this.getDicts('follow_type')])
|
|
|
+ .then(([targetType]) => {
|
|
|
+ this.FollowTypeOptions = targetType.data.values || []
|
|
|
+ })
|
|
|
+ .catch((err) => console.log(err))
|
|
|
+ },
|
|
|
+ search() {
|
|
|
+ // this.queryForm.daysBeforeToday = 20
|
|
|
+ this.fetchData()
|
|
|
+ },
|
|
|
+ async fetchData() {
|
|
|
+ let params = { ...this.queryForm }
|
|
|
+ let _this = this
|
|
|
+
|
|
|
+ let CreateOn = []
|
|
|
+ if (!_this.queryForm.date) {
|
|
|
+ _this.queryForm.date = []
|
|
|
+ }
|
|
|
+ // 解析时间
|
|
|
+ if (_this.queryForm.date.length === 2) {
|
|
|
+ _this.queryForm.date[1].setHours(23)
|
|
|
+ _this.queryForm.date[1].setMinutes(59)
|
|
|
+ _this.queryForm.date[1].setSeconds(59)
|
|
|
+ CreateOn.push(_this.formatDateTime(_this.queryForm.date[0]))
|
|
|
+ CreateOn.push(_this.formatDateTime(_this.queryForm.date[1]))
|
|
|
+ }
|
|
|
+ params['CreateOn'] = CreateOn.join(',')
|
|
|
+ params['Sell'] = '1'
|
|
|
+ const [err, res] = await to(api.getListByDay(params))
|
|
|
+ if (err) return console.log(err, 'err')
|
|
|
+ this.records = res.data.list || []
|
|
|
+ await this.$nextTick()
|
|
|
+ this.loadFlag = false
|
|
|
+ if (this.records.length) {
|
|
|
+ if (!this.followId) this.showComment(this.records[0].followupList[0].id)
|
|
|
+ } else {
|
|
|
+ this.comments = []
|
|
|
+ this.followId = ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+ load() {
|
|
|
+ this.queryForm.daysBeforeToday += 20
|
|
|
+ this.fetchData()
|
|
|
+ },
|
|
|
+ reset() {
|
|
|
+ this.queryForm = {
|
|
|
+ custId: '',
|
|
|
+ custName: '',
|
|
|
+ targetType: '',
|
|
|
+ targetName: '',
|
|
|
+ createdName: '',
|
|
|
+ targetId: '',
|
|
|
+ managerId: '',
|
|
|
+ daysBeforeToday: 20,
|
|
|
+ date: [new Date(this.getLastMonth(new Date())), new Date()],
|
|
|
+ followType: '',
|
|
|
+ }
|
|
|
+ this.fetchData()
|
|
|
+ },
|
|
|
+ jumpTo(row) {
|
|
|
+ let pageName = ''
|
|
|
+ switch (row.targetType) {
|
|
|
+ case '10':
|
|
|
+ pageName = 'CustomerDetail'
|
|
|
+ break
|
|
|
+ case '20':
|
|
|
+ pageName = 'BusinessDetail'
|
|
|
+ break
|
|
|
+ case '30':
|
|
|
+ pageName = 'ContractDetail'
|
|
|
+ break
|
|
|
+ case '40':
|
|
|
+ pageName = ''
|
|
|
+ break
|
|
|
+ case '50':
|
|
|
+ pageName = 'DistributorDetail'
|
|
|
+ break
|
|
|
+ }
|
|
|
+ if (pageName) {
|
|
|
+ this.$router.push({
|
|
|
+ name: pageName,
|
|
|
+ query: {
|
|
|
+ id: row.targetId,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ formatDateTime(date) {
|
|
|
+ var y = date.getFullYear()
|
|
|
+ var m = date.getMonth() + 1
|
|
|
+ m = m < 10 ? '0' + m : m
|
|
|
+ var d = date.getDate()
|
|
|
+ d = d < 10 ? '0' + d : d
|
|
|
+ var h = date.getHours()
|
|
|
+ var minute = date.getMinutes()
|
|
|
+ minute = minute < 10 ? '0' + minute : minute
|
|
|
+ return y + '-' + m + '-' + d + ' ' + h + ':' + minute
|
|
|
+ },
|
|
|
+ // 详情
|
|
|
+ showDetail(row) {
|
|
|
+ this.$refs.followDetail.init({ ...row })
|
|
|
+ },
|
|
|
+ async showComment(id) {
|
|
|
+ this.followId = id
|
|
|
+ const [err, res] = await to(api.getComment({ followId: id + '' }))
|
|
|
+ if (err) return
|
|
|
+ this.comments = res.data.list || []
|
|
|
+ },
|
|
|
+ formatType(val) {
|
|
|
+ let str = ''
|
|
|
+ if (val == 10) str = '电话'
|
|
|
+ else if (val == 20) str = '邮件'
|
|
|
+ else if (val == 30) str = '拜访'
|
|
|
+ return str
|
|
|
+ },
|
|
|
+ // 评论
|
|
|
+ async handleComment() {
|
|
|
+ let str = ''
|
|
|
+ if (!this.followId) str = '请选择跟进记录'
|
|
|
+ else if (!this.comment) str = '请输入评论'
|
|
|
+ if (str) return this.$message.warning(str)
|
|
|
+ let params = {
|
|
|
+ followId: this.followId + '',
|
|
|
+ content: this.comment,
|
|
|
+ remark: '',
|
|
|
+ }
|
|
|
+ const [err, res] = await to(api.addComment(params))
|
|
|
+ if (err) return
|
|
|
+ this.$message.success(res.msg)
|
|
|
+ this.showComment(this.followId)
|
|
|
+ this.comment = ''
|
|
|
+ this.fetchData()
|
|
|
+ },
|
|
|
+ addFollowUp() {
|
|
|
+ let params = {
|
|
|
+ followType: '20',
|
|
|
+ followDate: '2022-10-29',
|
|
|
+ followContent: '跟进内容',
|
|
|
+ targetId: 1,
|
|
|
+ targetType: '20',
|
|
|
+ targetName: 'dashoo',
|
|
|
+ custId: 26,
|
|
|
+ custName: '测试客户1348',
|
|
|
+ contactsId: 55,
|
|
|
+ contactsName: 'wanglj',
|
|
|
+ reminders: '',
|
|
|
+ nextTime: this.parseTime(new Date()),
|
|
|
+ remark: '',
|
|
|
+ }
|
|
|
+ api.addFollowUp(params)
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+ $base: '.follow';
|
|
|
+ ::v-deep .el-input-group__prepend {
|
|
|
+ width: 35%;
|
|
|
+ }
|
|
|
+ .follow {
|
|
|
+ height: calc(100vh - 240px);
|
|
|
+ display: flex;
|
|
|
+
|
|
|
+ .comment {
|
|
|
+ width: 300px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid rgb(215, 232, 244);
|
|
|
+
|
|
|
+ ul {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 10px;
|
|
|
+
|
|
|
+ li {
|
|
|
+ display: flex;
|
|
|
+ border-bottom: 1px solid #e3e5e7;
|
|
|
+
|
|
|
+ .text {
|
|
|
+ flex: 1;
|
|
|
+ padding: 0 10px;
|
|
|
+
|
|
|
+ p {
|
|
|
+ font-weight: 500;
|
|
|
+ margin: 0;
|
|
|
+ line-height: 32px;
|
|
|
+ }
|
|
|
+
|
|
|
+ p:first-child {
|
|
|
+ line-height: 30px;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ p:last-child {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #9499a0;
|
|
|
+ text-align: right;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .user-avatar {
|
|
|
+ font-size: 30px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .form {
|
|
|
+ padding: 4px;
|
|
|
+ text-align: right;
|
|
|
+
|
|
|
+ .el-textarea {
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .records {
|
|
|
+ flex: 1;
|
|
|
+ margin: 0;
|
|
|
+ height: 100%;
|
|
|
+ padding: 10px 20px;
|
|
|
+ list-style: none;
|
|
|
+ overflow: auto;
|
|
|
+
|
|
|
+ > li {
|
|
|
+ display: flex;
|
|
|
+
|
|
|
+ + li {
|
|
|
+ margin-top: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .date {
|
|
|
+ width: 100px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ h2,
|
|
|
+ h3 {
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ h2 {
|
|
|
+ font-size: 26px;
|
|
|
+ line-height: 32px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .content {
|
|
|
+ flex: 1;
|
|
|
+ list-style: none;
|
|
|
+
|
|
|
+ li {
|
|
|
+ display: flex;
|
|
|
+ cursor: pointer;
|
|
|
+ border: 1px solid rgb(215, 232, 244);
|
|
|
+ background: rgb(247, 251, 254);
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 8px;
|
|
|
+
|
|
|
+ + li {
|
|
|
+ margin-top: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .user-avatar {
|
|
|
+ font-size: 40px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .text {
|
|
|
+ flex: 1;
|
|
|
+ padding-left: 20px;
|
|
|
+ padding-right: 10px;
|
|
|
+
|
|
|
+ p {
|
|
|
+ font-weight: 500;
|
|
|
+ margin: 0;
|
|
|
+ line-height: 32px;
|
|
|
+
|
|
|
+ span {
|
|
|
+ color: #1d66dc;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .action {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ span:first-child {
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .content {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ span {
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|