|
@@ -1,106 +1,182 @@
|
|
|
<template>
|
|
|
<div class="tenant">
|
|
|
- <div style="padding: 20px; background: #fff; margin-bottom: 20px">
|
|
|
+ <!-- <div style="padding: 20px; background: #fff; margin-bottom: 20px">
|
|
|
<el-button type="primary" style="margin-left: 10px" @click="openModal()">添加客户</el-button>
|
|
|
- </div>
|
|
|
+ </div> -->
|
|
|
|
|
|
- <byTable :source="sourceList.data" :pagination="sourceList.pagination" :config="config" :loading="loading" :statConfig="statConfig"
|
|
|
- :selectConfig="selectConfig" highlight-current-row @moreSearch="moreSearch" @get-list="getList" ref="table">
|
|
|
- <template #isTop="{ item }">
|
|
|
- <div>
|
|
|
- <img style="cursor: pointer; width: 20px; transform: translateY(5px)" :src="'/img/isTop.png'" @click="deleteTop(item)"
|
|
|
- v-if="item.isTop === 1" />
|
|
|
- <img style="cursor: pointer; width: 20px; transform: translateY(5px)" :src="'/img/noTop.png'" @click="addTop(item)" v-else />
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <template #address="{ item }">
|
|
|
- <span>{{ item.countryName }}</span>
|
|
|
- <span v-if="item.provinceName"> ,{{ item.provinceName }}</span>
|
|
|
- <span v-if="item.cityName"> ,{{ item.cityName }}</span>
|
|
|
- </template>
|
|
|
- <template #name="{ item }">
|
|
|
- <div style="cursor: pointer; color: #409eff; word-break: break-all" @click="handleClickName(item)">
|
|
|
- {{ item.name }}
|
|
|
+ <div style="display:flex;height:calc(100vh - 140px);">
|
|
|
+ <div style="width:240px;height:100%;overflow:auto;background:#fff;padding:10px">
|
|
|
+ <div style="margin-bottom:10px">
|
|
|
+ <el-button type="primary" style="width:100%" @click="handleSearch()">全部客户</el-button>
|
|
|
</div>
|
|
|
- </template>
|
|
|
- <template #tags="{ item }">
|
|
|
- <div style="width: 100%">
|
|
|
- <el-tag style="margin-right: 8px" type="success" v-for="(tag, index) in item.tag" closable :key="index" @close="tagClose(tag, item)">
|
|
|
- {{ dictValueLabel(tag, customerTag) }}
|
|
|
- </el-tag>
|
|
|
- <template v-if="item.tag.length !== customerTag.length">
|
|
|
- <el-select v-if="item.addTagShow" v-model="addTag" style="width: 100%" @change="
|
|
|
+ <el-collapse v-model="activeNames">
|
|
|
+ <el-collapse-item title="客户跟进" name="1">
|
|
|
+ <template #title>
|
|
|
+ <div>
|
|
|
+ <img src="@/assets/images/custom-1.png" alt="" style="object-fit: contain;width: 20px;height: 20px;vertical-align: middle;" />
|
|
|
+ 客户跟进
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div style="padding-left:25px;padding-right:10px">
|
|
|
+ <div style="display:flex;justify-content:space-between;cursor:pointer" @click="handleSearchOne(1)">
|
|
|
+ <span>超7天未跟进</span> <span>{{leftDataFour[7]}}</span>
|
|
|
+ </div>
|
|
|
+ <div style="display:flex;justify-content:space-between;cursor:pointer;margin-top:5px" @click="handleSearchOne(2)">
|
|
|
+ <span>超30天未跟进</span> <span>{{leftDataFour[30]}}</span>
|
|
|
+ </div>
|
|
|
+ <div style="display:flex;justify-content:space-between;cursor:pointer;margin-top:5px" @click="handleSearchOne(3)">
|
|
|
+ <span>超半年未跟进</span> <span>{{leftDataFour[180]}}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-collapse-item>
|
|
|
+ <el-collapse-item title="客户来源" name="2">
|
|
|
+ <template #title>
|
|
|
+ <div>
|
|
|
+ <img src="@/assets/images/custom-2.png" alt="" style="object-fit: contain;width: 20px;height: 20px;vertical-align: middle;" />
|
|
|
+ 客户来源
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div style="padding-left:25px;padding-right:10px">
|
|
|
+ <div style="display:flex;justify-content:space-between;cursor:pointer" v-for="(item,index) in customerSource" :key="item.value"
|
|
|
+ :style="{marginTop:index!=0?'15px':'0px'}" @click="handleSearch(item.value,'source')">
|
|
|
+ <span>{{item.label}}</span> <span>{{showLeftData(item.value,'source')}}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-collapse-item>
|
|
|
+ <el-collapse-item title="客户类型" name="3">
|
|
|
+ <template #title>
|
|
|
+ <div>
|
|
|
+ <img src="@/assets/images/custom-4.png" alt="" style="object-fit: contain;width: 20px;height: 20px;vertical-align: middle;" />
|
|
|
+ 客户类型
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div style="padding-left:25px;padding-right:10px">
|
|
|
+ <div style="display:flex;justify-content:space-between;cursor:pointer" v-for="(item,index) in customerStatus" :key="item.value"
|
|
|
+ :style="{marginTop:index!=0?'15px':'0px'}" @click="handleSearch(item.value,'status')">
|
|
|
+ <span>{{item.label}}</span> <span>{{showLeftData(item.value,'status')}}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-collapse-item>
|
|
|
+ <el-collapse-item title="客户标签" name="4">
|
|
|
+ <template #title>
|
|
|
+ <div>
|
|
|
+ <img src="@/assets/images/custom-3.png" alt="" style="object-fit: contain;width: 20px;height: 20px;vertical-align: middle;" />
|
|
|
+ 客户标签
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div style="padding-left:25px;padding-right:10px">
|
|
|
+ <div style="display:flex;justify-content:space-between;cursor:pointer" v-for="(item,index) in customerTag" :key="item.value"
|
|
|
+ :style="{marginTop:index!=0?'15px':'0px'}" @click="handleSearch(item.value,'tag')">
|
|
|
+ <span>{{item.label}}</span> <span>{{showLeftData(item.value,'tag')}}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-collapse-item>
|
|
|
+ </el-collapse>
|
|
|
+ </div>
|
|
|
+ <div style="width:10px"></div>
|
|
|
+ <div style="width:calc(100% - 250px);height:100%;overflow:auto">
|
|
|
+ <byTable :source="sourceList.data" :pagination="sourceList.pagination" :config="config" :loading="loading" :statConfig="[]"
|
|
|
+ :selectConfig="selectConfig" highlight-current-row :action-list="[
|
|
|
+ {
|
|
|
+ text: '添加客户',
|
|
|
+ action: () => openModal(),
|
|
|
+ },
|
|
|
+ ]" @moreSearch="moreSearch" @get-list="getList" ref="table">
|
|
|
+ <template #isTop="{ item }">
|
|
|
+ <div>
|
|
|
+ <img style="cursor: pointer; width: 20px; transform: translateY(5px)" :src="'/img/isTop.png'" @click="deleteTop(item)"
|
|
|
+ v-if="item.isTop === 1" />
|
|
|
+ <img style="cursor: pointer; width: 20px; transform: translateY(5px)" :src="'/img/noTop.png'" @click="addTop(item)" v-else />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template #address="{ item }">
|
|
|
+ <span>{{ item.countryName }}</span>
|
|
|
+ <span v-if="item.provinceName"> ,{{ item.provinceName }}</span>
|
|
|
+ <span v-if="item.cityName"> ,{{ item.cityName }}</span>
|
|
|
+ </template>
|
|
|
+ <template #name="{ item }">
|
|
|
+ <div style="cursor: pointer; color: #409eff; word-break: break-all" @click="handleClickName(item)">
|
|
|
+ {{ item.name }}
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template #tags="{ item }">
|
|
|
+ <div style="width: 100%">
|
|
|
+ <el-tag style="margin-right: 8px" type="success" v-for="(tag, index) in item.tag" closable :key="index" @close="tagClose(tag, item)">
|
|
|
+ {{ dictValueLabel(tag, customerTag) }}
|
|
|
+ </el-tag>
|
|
|
+ <template v-if="item.tag.length !== customerTag.length">
|
|
|
+ <el-select v-if="item.addTagShow" v-model="addTag" style="width: 100%" @change="
|
|
|
(val) => {
|
|
|
return changeTag(val, item);
|
|
|
}
|
|
|
">
|
|
|
- <el-option v-for="tag in customerTag" :key="tag.value" :label="tag.label" :value="tag.value"
|
|
|
- :disabled="judgeTagSelect(item.tag, tag.value)" />
|
|
|
- </el-select>
|
|
|
- <el-tag style="cursor: pointer" type="success" @click="showSelect(item)" v-else>
|
|
|
- +
|
|
|
- </el-tag>
|
|
|
+ <el-option v-for="tag in customerTag" :key="tag.value" :label="tag.label" :value="tag.value"
|
|
|
+ :disabled="judgeTagSelect(item.tag, tag.value)" />
|
|
|
+ </el-select>
|
|
|
+ <el-tag style="cursor: pointer" type="success" @click="showSelect(item)" v-else>
|
|
|
+ +
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <template #follow="{ item }">
|
|
|
- <div :class="'getWidth' + item.id" style="width: 100%">
|
|
|
- <div style="width: 100%; display: flex">
|
|
|
- <template v-if="
|
|
|
+ <template #follow="{ item }">
|
|
|
+ <div :class="'getWidth' + item.id" style="width: 100%">
|
|
|
+ <div style="width: 100%; display: flex">
|
|
|
+ <template v-if="
|
|
|
item.customerFollowRecordsList &&
|
|
|
item.customerFollowRecordsList.length > 0
|
|
|
">
|
|
|
- <div :style="
|
|
|
+ <div :style="
|
|
|
index > 2
|
|
|
? 'line-height: 32px; margin-right: 8px; padding: 0 8px; background-color: #eeeeee; border-radius: 4px; cursor: pointer; display: none'
|
|
|
: 'line-height: 32px; margin-right: 8px; padding: 0 8px; background-color: #eeeeee; border-radius: 4px; cursor: pointer'
|
|
|
" v-for="(record, index) in item.customerFollowRecordsList" :key="record.id">
|
|
|
- <el-popover placement="bottom" :width="300" trigger="hover" @show="recordShow(record)">
|
|
|
- <template #reference>
|
|
|
- <div>
|
|
|
- <span v-if="record.date">{{
|
|
|
+ <el-popover placement="bottom" :width="300" trigger="hover" @show="recordShow(record)">
|
|
|
+ <template #reference>
|
|
|
+ <div>
|
|
|
+ <span v-if="record.date">{{
|
|
|
record.date.substr(0, 10)
|
|
|
}}</span>
|
|
|
- <el-icon style="margin-left: 8px; transform: translateY(2px)" @click="deleteFollow(record)">
|
|
|
- <DeleteFilled />
|
|
|
- </el-icon>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <template #default>
|
|
|
- <div style="width: 100%">
|
|
|
- <div style="color: #909399; margin: 8px 0">
|
|
|
- 跟进时间: {{ record.date }}
|
|
|
- </div>
|
|
|
- <div style="margin-top: 8px">
|
|
|
- 跟进人:
|
|
|
- {{ dictValueLabel(record.createUser, userList) }}
|
|
|
- </div>
|
|
|
- <!-- <div style="margin-top: 8px">
|
|
|
+ <el-icon style="margin-left: 8px; transform: translateY(2px)" @click="deleteFollow(record)">
|
|
|
+ <DeleteFilled />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template #default>
|
|
|
+ <div style="width: 100%">
|
|
|
+ <div style="color: #909399; margin: 8px 0">
|
|
|
+ 跟进时间: {{ record.date }}
|
|
|
+ </div>
|
|
|
+ <div style="margin-top: 8px">
|
|
|
+ 跟进人:
|
|
|
+ {{ dictValueLabel(record.createUser, userList) }}
|
|
|
+ </div>
|
|
|
+ <!-- <div style="margin-top: 8px">
|
|
|
跟进内容: {{ record.content }}
|
|
|
</div> -->
|
|
|
|
|
|
- <div v-if="record.type == '30'">
|
|
|
- <div style="word-wrap: break-word; margin: 8px 0" v-html="getStyle(record.content)" v-if="record.content"></div>
|
|
|
- <div v-else>跟进记录:</div>
|
|
|
- </div>
|
|
|
- <div v-else>
|
|
|
- <div style="word-wrap: break-word; margin: 8px 0">
|
|
|
- {{ getContent(record) }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div style="margin: 8px 0; display: flex" v-if="record.fileList && record.fileList.length > 0">
|
|
|
- <div style="width: 36px">附件:</div>
|
|
|
- <div style="width: calc(100% - 36px)">
|
|
|
- <div v-for="(file, index) in record.fileList" :key="index">
|
|
|
- <a style="color: #409eff; cursor: pointer" @click="openFile(file.fileUrl)">{{ file.fileName }}</a>
|
|
|
+ <div v-if="record.type == '30'">
|
|
|
+ <div style="word-wrap: break-word; margin: 8px 0" v-html="getStyle(record.content)" v-if="record.content"></div>
|
|
|
+ <div v-else>跟进记录:</div>
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <div style="word-wrap: break-word; margin: 8px 0">
|
|
|
+ {{ getContent(record) }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div style="margin: 8px 0; display: flex" v-if="record.fileList && record.fileList.length > 0">
|
|
|
+ <div style="width: 36px">附件:</div>
|
|
|
+ <div style="width: calc(100% - 36px)">
|
|
|
+ <div v-for="(file, index) in record.fileList" :key="index">
|
|
|
+ <a style="color: #409eff; cursor: pointer" @click="openFile(file.fileUrl)">{{ file.fileName }}</a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-popover>
|
|
|
- </div>
|
|
|
- <div style="
|
|
|
+ </template>
|
|
|
+ </el-popover>
|
|
|
+ </div>
|
|
|
+ <div style="
|
|
|
line-height: 32px;
|
|
|
margin-right: 8px;
|
|
|
padding: 0 8px;
|
|
@@ -108,13 +184,15 @@
|
|
|
border-radius: 4px;
|
|
|
cursor: pointer;
|
|
|
" @click="clickMore(item)" v-if="item.customerFollowRecordsList.length >= 3">
|
|
|
- 更多
|
|
|
+ 更多
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
</div>
|
|
|
- </template>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </byTable>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </byTable>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
<el-dialog :title="modalType == 'add' ? '新增' : '编辑'" v-if="dialogVisible" v-model="dialogVisible" width="800" v-loading="loadingOperation">
|
|
|
<byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="submit">
|
|
@@ -373,6 +451,7 @@ import useUserStore from "@/store/modules/user";
|
|
|
import selectCity from "@/components/selectCity/index.vue";
|
|
|
|
|
|
const { proxy } = getCurrentInstance();
|
|
|
+const activeNames = ref(["1", "2", "3", "4"]);
|
|
|
const loading = ref(false);
|
|
|
const loadingOperation = ref(false);
|
|
|
const submitLoading = ref(false);
|
|
@@ -1296,14 +1375,98 @@ const statisticalData = ref({
|
|
|
countAmount: 0,
|
|
|
customerList: [],
|
|
|
});
|
|
|
+const leftData = ref({});
|
|
|
+const leftDataFour = ref({
|
|
|
+ 0: 0,
|
|
|
+ 7: 0,
|
|
|
+ 180: 0,
|
|
|
+});
|
|
|
+
|
|
|
const obtainStatisticalData = () => {
|
|
|
proxy
|
|
|
- .post("/customer/sourceStatistics", sourceList.value.paginationTwo)
|
|
|
+ .post("/customer/sourceStatistics", { statisticsType: 1, type: null })
|
|
|
+ .then((res) => {
|
|
|
+ leftData.value.source = res;
|
|
|
+ });
|
|
|
+ proxy
|
|
|
+ .post("/customer/sourceStatistics", { statisticsType: 2, type: null })
|
|
|
+ .then((res) => {
|
|
|
+ leftData.value.status = res;
|
|
|
+ });
|
|
|
+ proxy
|
|
|
+ .post("/customer/sourceStatistics", { statisticsType: 4, type: null })
|
|
|
.then((res) => {
|
|
|
- statisticalData.value = res;
|
|
|
+ leftData.value.tag = res;
|
|
|
});
|
|
|
+ proxy.post("/customer/followStatistics", { type: null }).then((res) => {
|
|
|
+ leftDataFour.value = res;
|
|
|
+ });
|
|
|
};
|
|
|
obtainStatisticalData();
|
|
|
+const showLeftData = (dictKey, att) => {
|
|
|
+ const data = leftData.value[att];
|
|
|
+ if (data && data.customerList.length > 0) {
|
|
|
+ const current = data.customerList.find((x) => x[att] == dictKey);
|
|
|
+ if (current && current.count) {
|
|
|
+ return current.count;
|
|
|
+ } else {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+const handleSearch = (value, att) => {
|
|
|
+ sourceList.value.pagination = {
|
|
|
+ total: 0,
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ keyword: "",
|
|
|
+ type: "",
|
|
|
+ source: "",
|
|
|
+ status: "",
|
|
|
+ name: "",
|
|
|
+ countryId: "",
|
|
|
+ provinceId: "",
|
|
|
+ cityId: "",
|
|
|
+ customerCode: "",
|
|
|
+ userId: "",
|
|
|
+ tag: "",
|
|
|
+ tags: [],
|
|
|
+ };
|
|
|
+ if (value || att) {
|
|
|
+ sourceList.value.pagination[att] = value;
|
|
|
+ }
|
|
|
+ getList();
|
|
|
+};
|
|
|
+const handleSearchOne = (type) => {
|
|
|
+ sourceList.value.pagination = {
|
|
|
+ total: 0,
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ keyword: "",
|
|
|
+ type: "",
|
|
|
+ source: "",
|
|
|
+ status: "",
|
|
|
+ name: "",
|
|
|
+ countryId: "",
|
|
|
+ provinceId: "",
|
|
|
+ cityId: "",
|
|
|
+ customerCode: "",
|
|
|
+ userId: "",
|
|
|
+ tag: "",
|
|
|
+ tags: [],
|
|
|
+ };
|
|
|
+ if (type == 1) {
|
|
|
+ sourceList.value.pagination.beginDay = 7;
|
|
|
+ sourceList.value.pagination.endDay = 30;
|
|
|
+ } else if (type == 2) {
|
|
|
+ sourceList.value.pagination.beginDay = 30;
|
|
|
+ sourceList.value.pagination.endDay = 180;
|
|
|
+ } else {
|
|
|
+ sourceList.value.pagination.beginDay = 180;
|
|
|
+ sourceList.value.pagination.endDay = "";
|
|
|
+ }
|
|
|
+ getList();
|
|
|
+};
|
|
|
const getNum = (val) => {
|
|
|
let num = 0;
|
|
|
if (
|