<template> <div class="tenant"> <div style="padding: 20px; background: #fff; margin-bottom: 20px"> <el-button type="primary" style="margin-left: 10px" @click="openModal()">添加客户</el-button> </div> <div style="padding: 20px 20px 0 20px; background: #fff; margin-bottom: 20px"> <div style="display: flex"> <div style="font-size: 14px; cursor: pointer" class="by-dropdown"> <div class="by-dropdown-title"> <span>{{ dictValueLabel(sourceList.paginationTwo.statisticsType, statisticsType) }}</span> <el-icon style="margin-left: 5px; font-size: 16px"><CaretBottom /></el-icon> </div> <ul class="by-dropdown-lists"> <li v-for="item in statisticsType" :key="item.value" @click="searchItemSelect(item.value)" style="display: flex; align-items: center; justify-content: center"> {{ item.label }} </li> </ul> </div> </div> <div style="display: flex; width: 100%; margin: 10px 0 0 10px; flex-wrap: wrap"> <div style="padding: 20px; border-radius: 10px; width: 200px; background-color: #d1caff59; margin: 0 20px 20px 0"> <div style="margin-bottom: 10px; display: flex"> <div style="width: 8px; height: 8px; background-color: #5bacff; border-radius: 50px; margin-top: 6px"></div> <span style="padding-left: 8px">合计</span> </div> <div style="color: black; font-size: 20px; font-weight: 700">{{ statisticalData.countAmount }}</div> </div> <template v-if="sourceList.paginationTwo.statisticsType === 1"> <div style="padding: 20px; border-radius: 10px; width: 200px; background-color: #a2d8ff70; margin: 0 20px 20px 0" v-for="(item, index) in customerSource" :key="index"> <div style="margin-bottom: 10px; display: flex"> <div style="width: 8px; height: 8px; background-color: #5bacff; border-radius: 50px; margin-top: 6px"></div> <el-tooltip class="box-item" effect="light" placement="bottom"> <template #content> <div style="max-width: 400px; max-height: 50vh; word-break: break-all"> {{ item.label }} </div> </template> <div class="statistics-text" style="cursor: pointer"> {{ item.label }} </div> </el-tooltip> </div> <div style="color: black; font-size: 20px; font-weight: 700">{{ getNum(item.value) }}</div> </div> </template> <template v-else-if="sourceList.paginationTwo.statisticsType === 2"> <div style="padding: 20px; border-radius: 10px; width: 200px; background-color: #a2d8ff70; margin: 0 20px 20px 0" v-for="(item, index) in customerStatus" :key="index"> <div style="margin-bottom: 10px; display: flex"> <div style="width: 8px; height: 8px; background-color: #5bacff; border-radius: 50px; margin-top: 6px"></div> <el-tooltip class="box-item" effect="light" placement="bottom"> <template #content> <div style="max-width: 400px; max-height: 50vh; word-break: break-all"> {{ item.label }} </div> </template> <div class="statistics-text" style="cursor: pointer"> {{ item.label }} </div> </el-tooltip> </div> <div style="color: black; font-size: 20px; font-weight: 700">{{ getNum(item.value) }}</div> </div> </template> <template v-else-if="sourceList.paginationTwo.statisticsType === 3"> <div style="padding: 20px; border-radius: 10px; width: 200px; background-color: #a2d8ff70; margin: 0 20px 20px 0" v-for="(item, index) in userList" :key="index"> <div style="margin-bottom: 10px; display: flex"> <div style="width: 8px; height: 8px; background-color: #5bacff; border-radius: 50px; margin-top: 6px"></div> <el-tooltip class="box-item" effect="light" placement="bottom"> <template #content> <div style="max-width: 400px; max-height: 50vh; word-break: break-all"> {{ item.label }} </div> </template> <div class="statistics-text" style="cursor: pointer"> {{ item.label }} </div> </el-tooltip> </div> <div style="color: black; font-size: 20px; font-weight: 700">{{ getNum(item.value) }}</div> </div> </template> </div> </div> <byTable :source="sourceList.data" :pagination="sourceList.pagination" :config="config" :loading="loading" :selectConfig="selectConfig" highlight-current-row @moreSearch="moreSearch" @get-list="getList"> <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> </template> </div> </template> <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=" 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">{{ 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="word-wrap: break-word; margin: 8px 0" v-html="getStyle(record.content)" v-if="record.content"></div> <div v-else>跟进记录:</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> </template> </el-popover> </div> <div style="line-height: 32px; margin-right: 8px; padding: 0 8px; background-color: #eeeeee; border-radius: 4px; cursor: pointer" @click="clickMore(item)" v-if="item.customerFollowRecordsList.length >= 3"> 更多 </div> </template> </div> </div> </template> </byTable> <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"> <template #address> <el-row style="width: 100%"> <el-col :span="8"> <el-form-item prop="countryId"> <el-select v-model="formData.data.countryId" placeholder="国家" filterable @change="(val) => getCityData(val, '20', true)"> <el-option v-for="item in countryData" :label="item.chineseName" :value="item.id"> </el-option> </el-select> </el-form-item> </el-col> <el-col :span="8"> <el-form-item prop="provinceName"> <selectCity placeholder="省/洲" @change="(val) => getCityData(val, '30', true)" addressId="provinceId" addressName="provinceName" v-model="formData.data" :data="provinceData"> </selectCity> </el-form-item> </el-col> <el-col :span="8"> <el-form-item prop="cityName"> <selectCity placeholder="城市" addressId="cityId" addressName="cityName" v-model="formData.data" :data="cityData"></selectCity> </el-form-item> </el-col> </el-row> <el-row style="margin-top: 20px; width: 100%"> <el-col :span="24"> <el-form-item prop="address"> <el-input v-model="formData.data.address" type="textarea"> </el-input> </el-form-item> </el-col> </el-row> </template> <template #person> <div style="width: 100%"> <el-button type="primary" @click="clickAddPerson">添 加</el-button> <el-table :data="formData.data.customerUserList" style="width: 100%; margin-top: 16px"> <el-table-column label="联系人" width="160"> <template #default="{ row, $index }"> <div style="width: 100%"> <el-form-item :prop="'customerUserList.' + $index + '.name'" :rules="rules.name2" :inline-message="true"> <el-input v-model="row.name" placeholder="请输入联系人" /> </el-form-item> </div> </template> </el-table-column> <el-table-column label="电子邮箱"> <template #default="{ row, $index }"> <div style="width: 100%"> <el-form-item :prop="'customerUserList.' + $index + '.email'" :rules="rules.email" :inline-message="true"> <el-input v-model="row.email" placeholder="请输入电子邮箱" /> </el-form-item> </div> </template> </el-table-column> <el-table-column align="center" label="操作" width="120" fixed="right"> <template #default="{ row, $index }"> <el-button type="primary" link @click="clickInformationMore(row, $index)">更多</el-button> <el-button type="primary" link @click="clickDelete($index)">删除</el-button> </template> </el-table-column> </el-table> </div> </template> </byForm> <template #footer> <el-button @click="dialogVisible = false" size="large">取 消</el-button> <el-button type="primary" @click="submitForm()" size="large" :loading="submitLoading">确 定</el-button> </template> </el-dialog> <el-dialog title="更多联系方式" v-if="openPerson" v-model="openPerson" width="700"> <el-form :label-position="'top'" :model="formPerson.data" :rules="rulesPerson" ref="person"> <el-form-item label="联系人" prop="name"> <el-input v-model="formPerson.data.name" /> </el-form-item> <el-form-item label="电子邮箱" prop="email"> <el-input v-model="formPerson.data.email" /> </el-form-item> <el-form-item label="更多联系方式"> <div style="width: 100%"> <el-button type="primary" @click="clickAddMoreInformation">添 加</el-button> <el-table :data="formPerson.data.contact" style="width: 100%; margin-top: 16px"> <el-table-column label="类型" width="180"> <template #default="{ row, $index }"> <div style="width: 100%"> <el-form-item :prop="'contact.' + $index + '.type'" :rules="rulesPerson.type" :inline-message="true"> <el-select v-model="row.type" placeholder="请选择类型" style="width: 100%"> <el-option v-for="item in contactType" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-form-item> </div> </template> </el-table-column> <el-table-column label="联系号码"> <template #default="{ row, $index }"> <div style="width: 100%"> <el-form-item :prop="'contact.' + $index + '.contactNo'" :rules="rulesPerson.contactNo" :inline-message="true"> <el-input v-model="row.contactNo" placeholder="请输入联系号码" /> </el-form-item> </div> </template> </el-table-column> <el-table-column align="center" label="操作" width="120" fixed="right"> <template #default="{ $index }"> <el-button type="primary" link @click="clickInformationDelete($index)">删除</el-button> </template> </el-table-column> </el-table> </div> </el-form-item> </el-form> <template #footer> <el-button @click="openPerson = false" size="large">取 消</el-button> <el-button type="primary" @click="submitPerson()" size="large">确 定</el-button> </template> </el-dialog> <el-dialog title="添加跟进记录" v-if="openFollow" v-model="openFollow" width="500" destroy-on-close> <byForm :formConfig="formConfigAFollow" :formOption="formOption" v-model="formFollow.data" :rules="rulesFollow" ref="follow"> <template #fileSlot> <div style="width: 100%"> <el-upload v-model:fileList="fileList" action="https://winfaster.obs.cn-south-1.myhuaweicloud.com" :data="uploadData" multiple :before-upload="uploadFile" :on-preview="onPreviewFile"> <el-button type="primary">文件上传</el-button> </el-upload> </div> </template> </byForm> <template #footer> <el-button @click="openFollow = false" size="large">取 消</el-button> <el-button type="primary" @click="submitFollow()" size="large">确 定</el-button> </template> </el-dialog> <el-dialog title="跟进记录" v-if="openRecordMore" v-model="openRecordMore" width="800" destroy-on-close> <div> <div style="padding: 8px 0"> <el-button type="primary" @click="clickFollowUp(rowData)" plain>添加跟进记录</el-button> </div> <div style="padding-top: 16px"> <div v-infinite-scroll="infiniteScroll" class="infinite-scroll" :infinite-scroll-disabled="judgeTotal()"> <el-timeline> <el-timeline-item v-for="(record, index) in recordList" :key="index" :timestamp="record.date" hide-timestamp> <div> <div style="padding: 0 0 8px 0; display: flex; justify-content: space-between"> <span>{{ dictValueLabel(record.createUser, userList) }}</span> <span>{{ record.date }}</span> </div> <div style="word-wrap: break-word; margin: 8px 0" v-html="getStyle(record.content)" v-if="record.content"></div> <div style="margin: 8px 0" v-else>跟进记录:</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> </el-timeline-item> </el-timeline> </div> </div> </div> <template #footer> <el-button @click="openRecordMore = false" size="large">关 闭</el-button> </template> </el-dialog> <el-dialog title="高级检索" v-if="openSearch" v-model="openSearch" width="600"> <byForm :formConfig="formSearchConfig" :formOption="formOption" v-model="sourceList.pagination"> <template #address> <el-row style="width: 100%"> <el-col :span="8"> <el-form-item prop="countryId"> <el-select v-model="sourceList.pagination.countryId" placeholder="国家" clearable filterable @change="(val) => getCitySearchData(val, '20', true)"> <el-option v-for="item in countrySearchData" :label="item.chineseName" :value="item.id"> </el-option> </el-select> </el-form-item> </el-col> <el-col :span="8"> <el-form-item prop="provinceName"> <selectCity placeholder="省/洲" @change="(val) => getCitySearchData(val, '30', true)" addressId="provinceId" addressName="provinceName" v-model="sourceList.pagination" :data="provinceSearchData"> </selectCity> </el-form-item> </el-col> <el-col :span="8"> <el-form-item prop="cityName"> <selectCity placeholder="城市" addressId="cityId" addressName="cityName" v-model="sourceList.pagination" :data="citySearchData"></selectCity> </el-form-item> </el-col> </el-row> </template> <template #tags> <div style="width: 100%"> <el-tag style="margin-right: 8px" type="info" v-for="(tag, index) in sourceList.pagination.tags" closable :key="index" @close="tagSearchClose(tag)"> {{ dictValueLabel(tag, customerTag) }} </el-tag> <template v-if="sourceList.pagination.tags.length !== customerTag.length"> <el-select v-if="addTagSearchShow" v-model="addSearchTag" style="margin-top: 8px" @change="changeSearchTag"> <el-option v-for="tag in customerTag" :key="tag.value" :label="tag.label" :value="tag.value" :disabled="judgeTagSelect(sourceList.pagination.tags, tag.value)" /> </el-select> <el-tag style="cursor: pointer" type="info" @click="addTagSearchShow = true" v-else> + </el-tag> </template> </div> </template> </byForm> <template #footer> <el-button @click="cancelSearch()" size="large">取 消</el-button> <el-button type="primary" @click="submitSearch()" size="large">确 定</el-button> </template> </el-dialog> </div> </template> <script setup> import { ElMessage, ElMessageBox } from "element-plus"; import byTable from "@/components/byTable/index"; import byForm from "@/components/byForm/index"; import { computed, ref } from "vue"; import useUserStore from "@/store/modules/user"; import selectCity from "@/components/selectCity/index.vue"; const { proxy } = getCurrentInstance(); const loading = ref(false); const loadingOperation = ref(false); const submitLoading = ref(false); const openPerson = ref(false); const customerTag = ref([]); const customerSource = ref([]); const customerStatus = ref([]); const contactType = ref([]); const userList = ref([]); const fileList = ref([]); const uploadData = ref({}); const statisticsType = ref([ { label: "客户来源统计", value: 1, }, { label: "客户类型统计", value: 2, }, { label: "业务员统计", value: 3, }, ]); const sourceList = ref({ data: [], pagination: { total: 0, pageNum: 1, pageSize: 10, keyword: "", type: "", source: "", status: "", name: "", countryId: "", provinceId: "", cityId: "", customerCode: "", tag: "", tags: [], }, paginationTwo: { statisticsType: 1, type: 0, }, }); const selectConfig = computed(() => { return [ { label: "客户状态", prop: "type", data: [ { label: "公海", value: "0", }, { label: "私海", value: "1", }, ], }, { label: "客户来源", prop: "source", data: customerSource.value, }, { label: "客户类型", prop: "status", data: customerStatus.value, }, ]; }); const config = computed(() => { return [ { attrs: { label: "", slot: "isTop", fixed: "left", width: 60, align: "center", }, }, { attrs: { label: "客户名称", slot: "name", fixed: "left", width: 160, }, }, { attrs: { label: "所在城市", slot: "address", width: 160, }, }, { attrs: { label: "客户代码", prop: "customerCode", width: 120, }, }, { attrs: { label: "客户来源", prop: "source", width: 120, }, render(type) { return proxy.dictValueLabel(type, customerSource.value); }, }, { attrs: { label: "客户类型", prop: "status", width: 120, }, render(type) { return proxy.dictValueLabel(type, customerStatus.value); }, }, { attrs: { label: "客户标签", slot: "tags", width: 180, }, }, { attrs: { label: "业务员", prop: "userId", width: 140, }, render(type) { let data = userList.value.filter((item) => item.value == type); if (data && data.length > 0) { return data[0].label; } else { return ""; } }, }, { attrs: { label: "跟进", slot: "follow", "min-width": 440, }, }, { attrs: { label: "操作", width: 140, align: "center", fixed: "right", }, renderHTML(row) { return [ { attrs: { label: "跟进", type: "primary", text: true, }, el: "button", click() { clickFollowUp(row); }, }, { attrs: { label: "认领客户", type: "primary", text: true, }, el: "button", click() { ElMessageBox.confirm("是否确定认领该客户?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }).then(() => { proxy .post("/customer/CustomerAllocation", { id: row.id, userId: useUserStore().user.userId, }) .then(() => { ElMessage({ message: "认领成功", type: "success", }); getList(); obtainStatisticalData(); }); }); }, }, ]; }, }, ]; }); let modalType = ref("add"); let dialogVisible = ref(false); let openFollow = ref(false); let formData = reactive({ data: { countryId: "44", }, }); let formPerson = reactive({ data: {}, }); let formFollow = reactive({ data: {}, }); const formOption = reactive({ inline: true, labelWidth: 100, itemWidth: 100, rules: [], }); const formConfig = computed(() => { return [ { type: "input", prop: "name", label: "客户名称", required: true, itemWidth: 100, itemType: "text", }, { type: "slot", slotName: "address", prop: "countryId", label: "详细地址", }, { type: "input", prop: "customerCode", label: "客户代码", required: true, itemWidth: 100, itemType: "text", }, { type: "select", label: "客户来源", prop: "source", itemWidth: 50, data: customerSource.value, }, { type: "select", label: "客户类型", prop: "status", itemWidth: 50, data: customerStatus.value, }, { type: "select", label: "业务员", prop: "userId", itemWidth: 100, data: userList.value, clearable: true, }, { type: "select", label: "客户标签", prop: "tags", itemWidth: 100, multiple: true, data: customerTag.value, style: { width: "100%", }, }, { type: "slot", slotName: "person", label: "客户联系人", }, ]; }); let rules = ref({ name: [{ required: true, message: "请输入客户名称", trigger: "blur" }], name2: [{ required: true, message: "请输入联系人", trigger: "blur" }], email: [{ required: true, message: "请输入电子邮箱", trigger: "blur" }], countryId: [{ required: true, message: "请选择国家", trigger: "change" }], provinceId: [{ required: true, message: "请选择省/州", trigger: "change" }], cityId: [{ required: true, message: "请选择城市", trigger: "change" }], source: [{ required: true, message: "请选择客户来源", trigger: "change" }], status: [{ required: true, message: "请选择类型", trigger: "change" }], }); const formConfigAFollow = computed(() => { return [ { type: "date", itemType: "datetime", label: "跟进时间", prop: "date", itemWidth: 100, }, { type: "input", itemType: "textarea", label: "跟进内容", prop: "content", itemWidth: 100, }, { type: "slot", label: "上传附件", prop: "fileList", slotName: "fileSlot", }, ]; }); let rulesPerson = ref({ name: [{ required: true, message: "请输入联系人", trigger: "blur" }], email: [{ required: true, message: "请输入电子邮箱", trigger: "blur" }], type: [{ required: true, message: "请选择类型", trigger: "change" }], contactNo: [{ required: true, message: "请输入联系号码", trigger: "blur" }], }); let rulesFollow = ref({ date: [{ required: true, message: "请选择跟进时间", trigger: "change" }], content: [{ required: true, message: "请输入跟进内容", trigger: "blur" }], }); const submit = ref(null); const person = ref(null); const allocation = ref(null); const follow = ref(null); const getList = async (req) => { sourceList.value.pagination = { ...sourceList.value.pagination, ...req }; loading.value = true; proxy.post("/customer/page", sourceList.value.pagination).then((res) => { res.rows.forEach((x) => { x.addTagShow = false; if (x.tag) { x.tag = x.tag.split(","); } else { x.tag = []; } }); sourceList.value.data = res.rows; sourceList.value.pagination.total = res.total; setTimeout(() => { loading.value = false; }, 200); }); }; const openModal = () => { modalType.value = "add"; formData.data = { countryId: "44", tags: [], }; getCityData(formData.data.countryId, "20"); loadingOperation.value = false; dialogVisible.value = true; }; const countryData = ref([]); const provinceData = ref([]); const cityData = ref([]); const getCityData = (id, type, isChange) => { proxy.post("/customizeArea/list", { parentId: id }).then((res) => { if (type === "20") { provinceData.value = res; if (isChange) { formData.data.provinceId = ""; formData.data.provinceName = ""; formData.data.cityId = ""; formData.data.cityName = ""; } } else if (type === "30") { cityData.value = res; if (isChange) { formData.data.cityId = ""; formData.data.cityName = ""; } } else { countryData.value = res; } }); }; getCityData("0"); const clickAddPerson = () => { if (formData.data.customerUserList && formData.data.customerUserList.length > 0) { formData.data.customerUserList.push({ name: "", email: "", }); } else { formData.data.customerUserList = [ { name: "", email: "", }, ]; } }; const submitFollow = () => { follow.value.handleSubmit(() => { if (fileList.value && fileList.value.length > 0) { formFollow.data.fileList = fileList.value.map((item) => { return { id: item.raw.id, fileName: item.raw.fileName, fileUrl: item.raw.fileUrl, }; }); } else { formFollow.data.fileList = []; } proxy.post("/customerFollowRecords/" + modalType.value, formFollow.data).then( () => { ElMessage({ message: modalType.value == "add" ? "添加成功" : "编辑成功", type: "success", }); openFollow.value = false; getList(); }, (err) => { console.log(err); } ); }); }; const submitForm = () => { submit.value.handleSubmit(() => { if (formData.data.customerUserList && formData.data.customerUserList.length > 0) { formData.data.tag = formData.data.tags.join(","); submitLoading.value = true; proxy.post("/customer/" + modalType.value, formData.data).then( () => { ElMessage({ message: modalType.value == "add" ? "添加成功" : "编辑成功", type: "success", }); dialogVisible.value = false; submitLoading.value = false; getList(); obtainStatisticalData(); }, (err) => { console.log(err); submitLoading.value = false; } ); } else { ElMessage("请添加客户联系人"); } }); }; const getDict = () => { proxy.getDictOne(["customer_tag", "customer_source", "customer_status", "contact_type"]).then((res) => { customerTag.value = res["customer_tag"].map((x) => ({ label: x.dictValue, value: x.dictKey, })); customerSource.value = res["customer_source"].map((x) => ({ label: x.dictValue, value: x.dictKey, })); customerStatus.value = res["customer_status"].map((x) => ({ label: x.dictValue, value: x.dictKey, })); contactType.value = res["contact_type"].map((x) => ({ label: x.dictValue, value: x.dictKey, })); }); proxy .get("/tenantUser/list", { pageNum: 1, pageSize: 10000, tenantId: useUserStore().user.tenantId, }) .then((res) => { userList.value = res.rows.map((item) => { return { label: item.nickName, value: item.userId, }; }); }); }; getDict(); getList(); const handleClickName = (row) => { proxy.$router.push({ path: "/ERP/customer/portrait", query: { id: row.id, }, }); }; const deleteFollow = (data) => { ElMessageBox.confirm("是否确认删除该跟进?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }).then(() => { proxy .post("/customerFollowRecords/delete", { id: data.id, }) .then(() => { ElMessage({ message: "删除成功", type: "success", }); getList(); }); }); }; const addTag = ref(""); const judgeTagSelect = (data, val) => { if (data && data.length > 0) { if (data.includes(val)) { return true; } } return false; }; const changeTag = (val, item) => { let data = { id: item.id, tag: JSON.parse(JSON.stringify(item.tag)), }; data.tag.push(val); data.tag = data.tag.join(","); proxy.post("/customer/editTag", data).then(() => { ElMessage({ message: "添加成功", type: "success", }); item.addTagShow = false; addTag.value = ""; getList(); }); }; const tagClose = (val, item) => { let data = { id: item.id, tag: JSON.parse(JSON.stringify(item.tag)), }; data.tag = data.tag.filter((row) => row !== val); if (data.tag && data.tag.length > 0) { data.tag = data.tag.join(","); } else { data.tag = ""; } proxy.post("/customer/editTag", data).then(() => { ElMessage({ message: "添加成功", type: "success", }); item.addTagShow = false; addTag.value = ""; getList(); }); }; const showSelect = (item) => { item.addTagShow = true; }; const clickFollowUp = (item) => { formFollow.data = { customerId: item.id, fileList: [], }; fileList.value = []; modalType.value = "add"; openFollow.value = true; openRecordMore.value = false; }; const uploadFile = async (file) => { const res = await proxy.post("/fileInfo/getSing", { fileName: file.name }); uploadData.value = res.uploadBody; file.id = res.id; file.fileName = res.fileName; file.fileUrl = res.fileUrl; return true; }; const onPreviewFile = (file) => { window.open(file.raw.fileUrl, "_blank"); }; const getStyle = (val) => { if (val) { return "跟进记录: " + val.replace(/\n|\r\n/g, "<br>"); } else { return ""; } }; const recordShow = (item) => { if (!(item.fileList && item.fileList.length > 0)) { proxy.post("/fileInfo/getList", { businessIdList: [item.id] }).then((fileObj) => { item.fileList = fileObj[item.id] || []; }); } }; const openFile = (path) => { window.open(path, "_blank"); }; const recordList = ref([]); const rowData = ref({}); const openRecordMore = ref(false); const queryParams = ref({ total: 0, pageNum: 1, pageSize: 10, customerId: "", }); const clickMore = (item) => { if (openRecordMore.value === false) { queryParams.value.pageNum = 1; recordList.value = []; rowData.value = item; queryParams.value.customerId = item.id; } proxy.post("/customerFollowRecords/page", queryParams.value).then((res) => { recordList.value = recordList.value.concat(res.rows); queryParams.value.total = res.total; proxy.post("/fileInfo/getList", { businessIdList: res.rows.map((rows) => rows.id) }).then((fileObj) => { for (let i = 0; i < res.rows.length; i++) { recordList.value[parseInt(i + (queryParams.value.pageNum - 1) * queryParams.value.pageSize)].fileList = fileObj[recordList.value[parseInt(i + (queryParams.value.pageNum - 1) * queryParams.value.pageSize)].id] || []; } }); }); openRecordMore.value = true; }; const infiniteScroll = () => { queryParams.value.pageNum++; clickMore(); }; const judgeTotal = () => { if (queryParams.value.pageNum * queryParams.value.pageSize >= queryParams.value.total) { return true; } return false; }; const moreIndex = ref(0); const clickInformationMore = (item, index) => { moreIndex.value = index; if (item.contactJson) { item.contact = JSON.parse(item.contactJson); } else { item.contact = []; } formPerson.data = proxy.deepClone(item); openPerson.value = true; }; const clickDelete = (index) => { formData.data.customerUserList.splice(index, 1); }; const clickAddMoreInformation = () => { if (formPerson.data.contact && formPerson.data.contact.length > 0) { formPerson.data.contact.push({ type: "", contactNo: "", }); } else { formPerson.data.contact = [ { type: "", contactNo: "", }, ]; } }; const clickInformationDelete = (index) => { formPerson.data.contact.splice(index, 1); }; const submitPerson = () => { person.value.validate((valid) => { if (valid) { formPerson.data.contactJson = JSON.stringify(formPerson.data.contact); formData.data.customerUserList[moreIndex.value] = formPerson.data; openPerson.value = false; } }); }; const deleteTop = (item) => { proxy.post("/customerTop/delete", { customerId: item.id }).then(() => { item.isTop = 0; }); }; const addTop = (item) => { proxy.post("/customerTop/add", { customerId: item.id }).then(() => { item.isTop = 1; }); }; const searchItemSelect = (val) => { sourceList.value.paginationTwo.statisticsType = val; obtainStatisticalData(); }; const statisticalData = ref({ countAmount: 0, customerList: [], }); const obtainStatisticalData = () => { proxy.post("/customer/sourceStatistics", sourceList.value.paginationTwo).then((res) => { statisticalData.value = res; }); }; obtainStatisticalData(); const getNum = (val) => { let num = 0; if (statisticalData.value.customerList && statisticalData.value.customerList.length > 0) { statisticalData.value.customerList.map((item) => { if (sourceList.value.paginationTwo.statisticsType === 1) { if (item.source === val) { num = item.count; } } else if (sourceList.value.paginationTwo.statisticsType === 2) { if (item.status === val) { num = item.count; } } else if (sourceList.value.paginationTwo.statisticsType === 3) { if (item.userId === val) { num = item.count; } } }); } return num; }; const countrySearchData = ref([]); const provinceSearchData = ref([]); const citySearchData = ref([]); const getCitySearchData = (id, type, isChange) => { proxy.post("/customizeArea/list", { parentId: id }).then((res) => { if (type === "20") { provinceSearchData.value = res; if (isChange) { sourceList.value.pagination.provinceId = ""; sourceList.value.pagination.provinceName = ""; sourceList.value.pagination.cityId = ""; sourceList.value.pagination.cityName = ""; } } else if (type === "30") { citySearchData.value = res; if (isChange) { sourceList.value.pagination.cityId = ""; sourceList.value.pagination.cityName = ""; } } else { countrySearchData.value = res; } }); }; getCitySearchData("0"); const openSearch = ref(false); const formSearchConfig = computed(() => { return [ { type: "input", prop: "name", label: "客户名称", itemType: "text", }, { type: "slot", slotName: "address", label: "所在城市", }, { type: "input", prop: "customerCode", label: "客户代码", itemType: "text", }, { type: "select", label: "客户来源", prop: "source", itemWidth: 50, data: customerSource.value, clearable: true, }, { type: "select", label: "客户类型", prop: "status", itemWidth: 50, data: customerStatus.value, clearable: true, }, { type: "slot", slotName: "tags", label: "客户标签", }, ]; }); const addSearchTag = ref(""); const addTagSearchShow = ref(false); let copySearch = ref({}); const moreSearch = () => { if (sourceList.value.pagination.tag) { sourceList.value.pagination.tags = sourceList.value.pagination.tag.split(","); } else { sourceList.value.pagination.tags = []; } addTagSearchShow.value = false; copySearch.value = proxy.deepClone(sourceList.value.pagination); openSearch.value = true; }; const cancelSearch = () => { sourceList.value.pagination = copySearch.value; openSearch.value = false; }; const submitSearch = () => { if (sourceList.value.pagination.tags && sourceList.value.pagination.tags.length > 0) { sourceList.value.pagination.tag = sourceList.value.pagination.tags.join(","); } else { sourceList.value.pagination.tag = ""; } openSearch.value = false; sourceList.value.pagination.pageNum = 1; getList(); }; const tagSearchClose = (val) => { sourceList.value.pagination.tags = sourceList.value.pagination.tags.filter((item) => item !== val); }; const changeSearchTag = (val) => { sourceList.value.pagination.tags.push(val); addTagSearchShow.value = false; addSearchTag.value = ""; }; </script> <style lang="scss" scoped> .tenant { padding: 20px; } .infinite-scroll { max-height: calc(89vh - 94px - 70px - 58px - 16px); overflow-y: auto; &::-webkit-scrollbar { width: 0px; } } .by-dropdown { position: relative; text-align: left; height: 32px; z-index: 1010; padding: 0 10px; transition: all 0.5s ease; cursor: pointer; line-height: 32px; .by-dropdown-title { font-size: 14px; background-color: #fff; } ul { position: absolute; left: 0; top: 32px; padding: 0; margin: 0; z-index: 1200; display: none; white-space: nowrap; background-color: #fff; li { list-style: none; z-index: 1200; font-size: 12px; height: 30px; padding: 0 10px; } li:hover { background-color: #eff6ff; color: #0084ff; } } } .by-dropdown::before { display: block; width: 1px; content: " "; position: absolute; height: 14px; top: 8px; background-color: #ddd; right: 0; z-index: 1011; } .by-dropdown:hover { background: #ffffff; border-radius: 2px 2px 2px 2px; opacity: 1; ul { background: #ffffff; box-shadow: 0px 2px 16px 1px rgba(0, 0, 0, 0.06); border-radius: 2px 2px 2px 2px; opacity: 1; display: block; text-align: left; } } .by-dropdown-lists { max-height: 50vh; overflow-y: auto; line-height: 1; } .statistics-text { padding-left: 8px; width: 152px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } </style>