Files
shanxia2.0/web-admin/src/views/crm/clue/form.vue
T
2026-08-21 16:50:47 +08:00

359 lines
12 KiB
Vue

<template>
<el-dialog
v-model="dialog.visible"
:title="dialog.title"
width="1160px"
class="dialog-form-layout"
align-center
destroy-on-close
:close-on-click-modal="false"
@close="reset"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="right"
label-width="100px"
class="crm-dialog-form"
scroll-to-error
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
>
<el-divider content-position="left">基本信息</el-divider>
<div class="form-grid-4">
<el-form-item label="线索姓名" prop="clueName">
<el-input v-model="form.clueName" placeholder="请输入线索姓名" maxlength="100" />
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input v-model="form.mobile" placeholder="请输入手机号" maxlength="11" />
</el-form-item>
<el-form-item label="职位" prop="position">
<el-input v-model="form.position" placeholder="请输入职位" maxlength="50" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" maxlength="100" />
</el-form-item>
<el-form-item label="公司名称" prop="company">
<el-input v-model="form.company" placeholder="请输入公司名称" maxlength="200" />
</el-form-item>
<el-form-item label="客户类型" prop="customerType">
<el-select v-model="form.customerType" placeholder="请选择客户类型" clearable>
<el-option
v-for="opt in dictStore.getOptions('crm_customer_type')"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<el-form-item label="客户网站" prop="website" class="form-field-span-2">
<el-input v-model="form.website" placeholder="请输入客户网站" maxlength="500" />
</el-form-item>
</div>
<el-divider content-position="left">详细信息</el-divider>
<div class="form-grid-4">
<el-form-item label="线索来源" prop="source">
<el-select v-model="form.source" placeholder="请选择线索来源" clearable>
<el-option
v-for="opt in dictStore.getOptions('crm_clue_source')"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<el-form-item label="线索等级" prop="level">
<el-select v-model="form.level" placeholder="请选择线索等级" clearable>
<el-option
v-for="opt in dictStore.getOptions('crm_clue_level')"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<el-form-item label="所属行业" prop="industry">
<el-select v-model="form.industry" placeholder="请选择所属行业" clearable>
<el-option
v-for="opt in dictStore.getOptions('crm_customer_industry')"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<el-form-item label="所在园区" prop="parkArea">
<DictSelect
v-model="form.parkArea"
dict-type="crm_customer_park"
placeholder="请选择或输入所在园区"
/>
</el-form-item>
<el-form-item label="省份" prop="province">
<el-input v-model="form.province" placeholder="请输入省份" maxlength="50" />
</el-form-item>
<el-form-item label="城市" prop="city">
<el-input v-model="form.city" placeholder="请输入城市" maxlength="50" />
</el-form-item>
<el-form-item label="区县" prop="district">
<el-input v-model="form.district" placeholder="请输入区县" maxlength="50" />
</el-form-item>
<el-form-item label="详细地址" class="form-field-span-2">
<el-input v-model="form.address" placeholder="请输入详细地址" maxlength="255" />
</el-form-item>
</div>
<el-divider content-position="left">需求信息</el-divider>
<div class="form-grid-4">
<el-form-item label="意向产品" class="form-field-span-2">
<el-input
v-model="form.intentProducts"
placeholder="请输入意向产品,多个用逗号分隔"
maxlength="500"
/>
</el-form-item>
<el-form-item label="需求描述" class="form-field-span-2">
<el-input
v-model="form.requirement"
placeholder="请输入需求描述"
type="textarea"
:rows="2"
maxlength="500"
/>
</el-form-item>
</div>
<el-divider content-position="left">分配信息</el-divider>
<div class="form-grid-4">
<el-form-item label="负责人" prop="ownerUserId">
<el-select v-model="form.ownerUserId" placeholder="请选择负责人" clearable filterable>
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickName"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item label="下次联系时间" prop="nextTime">
<el-date-picker
v-model="form.nextTime"
type="datetime"
placeholder="请选择下次联系时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" placeholder="请输入备注" maxlength="500" />
</el-form-item>
</div>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button class="btn-ghost" @click="cancel">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button class="btn-main" type="primary" :loading="submitting" @click="submitForm">
<el-icon><Check /></el-icon>确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { Close, Check } from '@element-plus/icons-vue'
import { getClue, addClue, updateClue, checkDuplicateClue } from '@/api/crm/clue'
import type { ClueForm as ClueFormType } from '@/api/crm/clue/types'
import { fetchUserOptions } from '@/api/admin'
import { useDictStore } from '@/store/modules/dict'
import DictSelect from '@/components/DictSelect/index.vue'
import { useUserStore } from '@/store/modules/user'
const dictStore = useDictStore()
const userStore = useUserStore()
const emit = defineEmits(['success'])
const form = ref<ClueFormType>({
id: undefined,
clueName: '',
mobile: '',
company: '',
customerType: '',
email: undefined,
position: '',
source: '',
level: 'unrated',
industry: '',
parkArea: '',
province: '',
city: '',
district: '',
address: '',
website: undefined,
intentProducts: '',
requirement: '',
ownerUserId: undefined,
nextTime: undefined,
remark: undefined
})
const rules = ref({
clueName: [{ required: true, message: '线索姓名不能为空', trigger: 'blur' }],
mobile: [
{ required: true, message: '手机号不能为空', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
company: [{ required: true, message: '公司名称不能为空', trigger: 'blur' }],
customerType: [{ required: true, message: '客户类型不能为空', trigger: 'change' }],
source: [{ required: true, message: '线索来源不能为空', trigger: 'change' }],
ownerUserId: [{ required: true, message: '负责人不能为空', trigger: 'change' }],
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }]
})
const userList = ref<any[]>([])
const dialog = reactive({ visible: false, title: '' })
const submitting = ref(false)
const formRef = ref()
const resolveDefaultOwnerUserId = () => {
const info = userStore.info
const userId = info?.userId ?? info?.id
return userId != null && userId !== '' ? String(userId) : undefined
}
const applyDefaultOwnerUserId = async () => {
const defaultOwnerUserId = resolveDefaultOwnerUserId()
if (!defaultOwnerUserId) return
await nextTick()
form.value.ownerUserId = defaultOwnerUserId
}
const open = async (id?: string | number) => {
reset()
dialog.visible = true
await loadUserList()
if (id) {
dialog.title = '修改线索'
try {
const res = await getClue(id)
const data = (res as any)?.data ?? res ?? {}
Object.assign(form.value, data)
form.value.nextTime = data.nextTime || undefined
form.value.ownerUserId = data.ownerUserId || undefined
} catch (error) {
console.error(error)
dialog.visible = false
return
}
} else {
dialog.title = '新增线索'
await applyDefaultOwnerUserId()
}
}
const loadUserList = async () => {
try {
const res = await fetchUserOptions()
const options = (res as any)?.data ?? res ?? []
userList.value = (Array.isArray(options) ? options : []).map((item: any) => ({
id: item.value,
nickName: item.label
}))
} catch (error) {
console.error(error)
userList.value = []
}
}
const submitForm = () => {
formRef.value?.validate(async (valid: boolean) => {
if (!valid) return
submitting.value = true
try {
const dup = await checkDuplicateClue({
mobile: form.value.mobile,
company: form.value.company,
excludeId: form.value.id
})
if (dup?.isDuplicate && dup?.duplicates?.length) {
const names = dup.duplicates
.slice(0, 3)
.map((item: any) => item.clueName || item.company || item.mobile)
.join('、')
try {
await ElMessageBox.confirm(
`检测到可能重复的线索:${names}。是否仍要继续保存?`,
'重复提示',
{ type: 'warning', confirmButtonText: '继续保存', cancelButtonText: '取消' }
)
} catch {
return
}
}
if (form.value.id) {
await updateClue(form.value)
ElMessage.success('修改成功')
} else {
await addClue(form.value)
ElMessage.success('新增成功')
}
dialog.visible = false
emit('success')
} catch (error) {
console.error(error)
} finally {
submitting.value = false
}
})
}
const cancel = () => {
dialog.visible = false
reset()
}
const reset = () => {
form.value = {
id: undefined,
clueName: '',
mobile: '',
company: '',
customerType: '',
email: undefined,
position: '',
source: '',
level: 'unrated',
industry: '',
parkArea: '',
province: '',
city: '',
district: '',
address: '',
website: undefined,
intentProducts: '',
requirement: '',
ownerUserId: undefined,
nextTime: undefined,
remark: undefined
}
submitting.value = false
formRef.value?.resetFields()
}
defineExpose({ open })
</script>
<style scoped lang="scss">
.form-grid-4 .form-field-span-2 {
grid-column: span 2;
}
</style>