crm 客户添加自定义列 跟进添加图片粘贴上传
This commit is contained in:
@@ -70,15 +70,21 @@
|
||||
<el-card v-if="form.type == '1'" class="info-card" shadow="never" style="margin-top: 16px">
|
||||
<div class="info-top">
|
||||
<span class="edit-title">上传跟进截图/聊天截图</span>
|
||||
<el-upload
|
||||
class="upload-demo"
|
||||
:action="actionImageUplode"
|
||||
:headers="uploadHeaders"
|
||||
:on-success="handleUploadSuccess"
|
||||
:show-file-list="false"
|
||||
>
|
||||
<img :src="imageUrl" class="avatar" />
|
||||
</el-upload>
|
||||
<div class="upload-area" @paste="handlePaste" @click="triggerFileInput">
|
||||
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
|
||||
<div v-else class="upload-placeholder">
|
||||
<i class="el-icon-upload"></i>
|
||||
<p>点击上传或直接粘贴截图</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 隐藏的文件输入框,用于点击上传 -->
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style="display: none"
|
||||
@change="handleFileChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card v-if="form.type == '1'" class="info-card" shadow="never" style="margin-top: 16px">
|
||||
@@ -383,6 +389,72 @@
|
||||
// 如需同步到 form.picture
|
||||
form.value.picture = response.url || (response.data && response.data.file_name) || ''
|
||||
}
|
||||
|
||||
// 处理粘贴上传
|
||||
const handlePaste = async (event: ClipboardEvent) => {
|
||||
event.preventDefault()
|
||||
const items = event.clipboardData?.items
|
||||
if (!items) return
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i]
|
||||
if (item.type.indexOf('image') !== -1) {
|
||||
const file = item.getAsFile()
|
||||
if (file) {
|
||||
await uploadImageFile(file)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 触发文件选择
|
||||
const triggerFileInput = () => {
|
||||
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement
|
||||
if (fileInput) {
|
||||
fileInput.click()
|
||||
}
|
||||
}
|
||||
|
||||
// 处理文件选择
|
||||
const handleFileChange = async (event: Event) => {
|
||||
const target = event.target as HTMLInputElement
|
||||
const file = target.files?.[0]
|
||||
if (file) {
|
||||
await uploadImageFile(file)
|
||||
}
|
||||
// 清空input值,允许重复选择同一文件
|
||||
target.value = ''
|
||||
}
|
||||
|
||||
// 上传图片文件
|
||||
const uploadImageFile = async (file: File) => {
|
||||
try {
|
||||
// 创建FormData
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
// 使用fetch上传到服务器
|
||||
const response = await fetch(actionImageUplode, {
|
||||
method: 'POST',
|
||||
headers: uploadHeaders.value,
|
||||
body: formData
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (response.ok) {
|
||||
// 上传成功,调用原有的成功处理逻辑
|
||||
handleUploadSuccess(result)
|
||||
ElMessage.success('图片上传成功')
|
||||
} else {
|
||||
ElMessage.error(result.message || '图片上传失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error)
|
||||
ElMessage.error('图片上传失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -529,16 +601,75 @@
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
.remind-btn:hover {
|
||||
background: #3471ff;
|
||||
color: #fff;
|
||||
}
|
||||
.remind-btn.active {
|
||||
background: #3471ff;
|
||||
color: #fff;
|
||||
border: 1px solid #3471ff;
|
||||
}
|
||||
.confirm-btn {
|
||||
.custom-date {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.custom-date .el-date-picker {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* 上传区域样式 */
|
||||
.upload-area {
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
/* border: 2px dashed #d9d9d9; */
|
||||
/* border-radius: 8px; */
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.upload-area:hover {
|
||||
border-color: #3471ff;
|
||||
background-color: #f4f7fe;
|
||||
}
|
||||
|
||||
.upload-area .avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.upload-placeholder i {
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.upload-placeholder p {
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 原有的上传样式保留 */
|
||||
.upload-demo {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.upload-demo .avatar {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -96,14 +96,41 @@
|
||||
<el-tab-pane label="已成交" name="1"></el-tab-pane>
|
||||
<el-tab-pane label="已丢失" name="2"></el-tab-pane>
|
||||
</el-tabs>
|
||||
<div>
|
||||
<el-form-item label="" label-width="70px">
|
||||
<el-cascader :options="options" clearable v-model="shots" />
|
||||
<!-- <el-select v-model="selectValue" placeholder="排序方式" style="width: 120px">
|
||||
<el-option v-for="v in sortList" :key="v.value" :label="v.label" :value="v.value">
|
||||
</el-option>
|
||||
</el-select> -->
|
||||
<div style="width: 240px; display: flex">
|
||||
<el-form-item label="" class="customer-sort">
|
||||
<el-cascader :options="options" clearable v-model="shots" placeholder="排序方式" />
|
||||
</el-form-item>
|
||||
<el-popover
|
||||
:visible="columnPopoverVisible"
|
||||
placement="bottom-end"
|
||||
:width="260"
|
||||
trigger="click"
|
||||
>
|
||||
<template #reference>
|
||||
<div
|
||||
class="btn"
|
||||
style="margin-left: 20px"
|
||||
title="自定义列"
|
||||
@click="columnPopoverVisible = !columnPopoverVisible"
|
||||
>
|
||||
<i class="iconfont-sys notice-btn"></i>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-checkbox-group v-model="selectedColumnKeys">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px">
|
||||
<el-checkbox v-for="col in allColumns" :key="col.key" :label="col.key">
|
||||
{{ col.label }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
<div style="display: flex; justify-content: end; margin-top: 10px">
|
||||
<div>
|
||||
<el-link type="primary" :underline="false" @click="saveCols">保存</el-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -122,20 +149,47 @@
|
||||
>
|
||||
<template #default>
|
||||
<el-table-column
|
||||
v-if="isColVisible('code')"
|
||||
prop="code"
|
||||
label="编号"
|
||||
align="center"
|
||||
width="170px"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="contacts" label="客户名称" align="center" />
|
||||
<el-table-column prop="magnitude_txt" label="客户商机" align="center" />
|
||||
<el-table-column prop="channel_name" label="渠道" align="center" />
|
||||
<el-table-column prop="country_name" label="国家" align="center" />
|
||||
<el-table-column
|
||||
v-if="isColVisible('contacts')"
|
||||
prop="contacts"
|
||||
label="客户名称"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="isColVisible('magnitude_txt')"
|
||||
prop="magnitude_txt"
|
||||
label="客户商机"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="isColVisible('channel_name')"
|
||||
prop="channel_name"
|
||||
label="渠道"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="isColVisible('country_name')"
|
||||
prop="country_name"
|
||||
label="国家"
|
||||
align="center"
|
||||
/>
|
||||
<!-- <el-table-column prop="phone" label="电话" align="center" /> -->
|
||||
<!-- <el-table-column prop="WhatsApp" label="WhatsApp" align="center" /> -->
|
||||
<!-- <el-table-column prop="email" label="邮箱" align="center" /> -->
|
||||
<el-table-column prop="follow" label="联系方式" width="240px" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
v-if="isColVisible('follow')"
|
||||
prop="follow"
|
||||
label="联系方式"
|
||||
width="240px"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="scope">
|
||||
<div
|
||||
>WhatsApp:{{
|
||||
@@ -146,7 +200,12 @@
|
||||
<div>邮箱:{{ scope.row.email == '' ? ' --' : '【' + scope.row.email + ' 】' }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="grade_txt" label="等级" align="center" />
|
||||
<el-table-column
|
||||
v-if="isColVisible('grade_txt')"
|
||||
prop="grade_txt"
|
||||
label="等级"
|
||||
align="center"
|
||||
/>
|
||||
<!-- <el-table-column prop="follow" label="状态" align="center">
|
||||
<template #default="scope">
|
||||
<span :class="statusClass(scope.row.follow)">
|
||||
@@ -154,11 +213,32 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
<el-table-column prop="follow_last" label="上次跟进"></el-table-column>
|
||||
<el-table-column prop="follow_next" label="下次跟进"></el-table-column>
|
||||
<el-table-column prop="enter_user_name" label="录单人"></el-table-column>
|
||||
<el-table-column prop="describe" label="备注" align="center" />
|
||||
<el-table-column prop="created_at" label="创建时间"></el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColVisible('follow_last')"
|
||||
prop="follow_last"
|
||||
label="上次跟进"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColVisible('follow_next')"
|
||||
prop="follow_next"
|
||||
label="下次跟进"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColVisible('enter_user_name')"
|
||||
prop="enter_user_name"
|
||||
label="录单人"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColVisible('describe')"
|
||||
prop="describe"
|
||||
label="备注"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="isColVisible('created_at')"
|
||||
prop="created_at"
|
||||
label="创建时间"
|
||||
></el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="200px" align="center">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
@@ -303,6 +383,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { CrmService } from '@/api/crmApi'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useUploadHeaders } from '@/composables/useUploadHeaders'
|
||||
@@ -320,6 +401,51 @@
|
||||
const reportFileList = ref<any[]>([])
|
||||
const { uploadHeaders } = useUploadHeaders()
|
||||
const reportUploadAction = `${import.meta.env.VITE_API_URL}/ticket/upload`
|
||||
const columnPopoverVisible = ref(false) // 控制自定义列popover的显示状态
|
||||
|
||||
// 自定义列配置
|
||||
type ColumnOption = { key: string; label: string }
|
||||
const COLUMN_STORAGE_KEY = 'crmpc_customer_table_columns'
|
||||
const allColumns = ref<ColumnOption[]>([
|
||||
{ key: 'code', label: '编号' },
|
||||
{ key: 'contacts', label: '客户名称' },
|
||||
{ key: 'magnitude_txt', label: '客户商机' },
|
||||
{ key: 'channel_name', label: '渠道' },
|
||||
{ key: 'country_name', label: '国家' },
|
||||
{ key: 'phone', label: '电话' },
|
||||
{ key: 'email', label: '邮箱' },
|
||||
{ key: 'grade_txt', label: '等级' },
|
||||
{ key: 'follow_last', label: '上次跟进' },
|
||||
{ key: 'follow_next', label: '下次跟进' },
|
||||
{ key: 'enter_user_name', label: '录单人' },
|
||||
{ key: 'describe', label: '备注' },
|
||||
{ key: 'created_at', label: '创建时间' }
|
||||
])
|
||||
const defaultColumnKeys = allColumns.value.map((c) => c.key)
|
||||
const selectedColumnKeys = ref<string[]>([])
|
||||
const loadCols = () => {
|
||||
try {
|
||||
const saved = localStorage.getItem(COLUMN_STORAGE_KEY)
|
||||
selectedColumnKeys.value = saved ? JSON.parse(saved) : defaultColumnKeys
|
||||
} catch (_) {
|
||||
selectedColumnKeys.value = defaultColumnKeys
|
||||
}
|
||||
}
|
||||
const saveCols = () => {
|
||||
if (selectedColumnKeys.value.length === 0) {
|
||||
ElMessage.warning('至少选择一列显示')
|
||||
return
|
||||
}
|
||||
localStorage.setItem(COLUMN_STORAGE_KEY, JSON.stringify(selectedColumnKeys.value))
|
||||
ElMessage.success('保存成功')
|
||||
columnPopoverVisible.value = false // 隐藏popover
|
||||
}
|
||||
// const resetCols = () => {
|
||||
// selectedColumnKeys.value = [...defaultColumnKeys]
|
||||
// saveCols()
|
||||
// }
|
||||
const isColVisible = (key: string) => selectedColumnKeys.value.includes(key)
|
||||
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
@@ -382,7 +508,28 @@
|
||||
getarea()
|
||||
gettableList()
|
||||
tagGrouplistfn()
|
||||
loadCols()
|
||||
|
||||
// 添加全局点击事件监听,点击其他地方时隐藏popover
|
||||
document.addEventListener('click', handleGlobalClick)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除全局点击事件监听
|
||||
document.removeEventListener('click', handleGlobalClick)
|
||||
})
|
||||
|
||||
// 处理全局点击事件
|
||||
const handleGlobalClick = (event: Event) => {
|
||||
const target = event.target as HTMLElement
|
||||
const popover = document.querySelector('.el-popover')
|
||||
const referenceBtn = document.querySelector('.btn[title="自定义列"]')
|
||||
|
||||
// 如果点击的不是popover内部元素,也不是触发按钮,则隐藏popover
|
||||
if (popover && !popover.contains(target) && referenceBtn && !referenceBtn.contains(target)) {
|
||||
columnPopoverVisible.value = false
|
||||
}
|
||||
}
|
||||
// tabs 切换联动
|
||||
watch([activeTab, searchValue, selectValue, country_id, shots], () => {
|
||||
console.log('tabs 联动' + shots.value)
|
||||
@@ -732,4 +879,36 @@
|
||||
color: #3471ff !important; /* 文字颜色 */
|
||||
background-color: #fff !important; /* 背景色 */
|
||||
}
|
||||
.btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-left: 10px;
|
||||
color: var(--art-gray-700);
|
||||
cursor: pointer;
|
||||
background-color: rgba(var(--art-gray-200-rgb), 0.8);
|
||||
border-radius: 6px;
|
||||
transition: color 0.3s;
|
||||
transition: all 0.3s;
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
color: var(--art-gray-700);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(var(--art-gray-300-rgb), 0.75);
|
||||
|
||||
i {
|
||||
color: var(--art-gray-800);
|
||||
}
|
||||
}
|
||||
}
|
||||
.customer-sort {
|
||||
width: 150px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user