【线索管理】 新增# 线索管理

main
kaeery 2025-02-24 14:19:46 +08:00
parent 4221095b2c
commit 3793f8d8ab
17 changed files with 995 additions and 161 deletions

View File

@ -0,0 +1,46 @@
<template>
<el-drawer v-model="drawerVisible" :title="parameter.title" :size="parameter.width">
<slot />
<template #footer>
<el-button @click="handleCancel"></el-button>
<el-button type="primary" @click="handleConfirm"></el-button>
</template>
</el-drawer>
</template>
<script setup lang="ts">
export interface IParams {
title: string
width?: string | number
data: { [key: string]: any }
}
const emit = defineEmits(['handleCancel', 'handleConfirm'])
const drawerVisible = ref(false)
//
const parameter = ref<IParams>({
title: '',
width: '50%',
data: {}
})
const openDrawer = (params: IParams) => {
parameter.value = { ...parameter.value, ...params }
drawerVisible.value = true
}
const handleCancel = () => {
emit('handleCancel', closeDrawer)
}
const handleConfirm = () => {
emit('handleConfirm', closeDrawer)
}
const closeDrawer = () => {
drawerVisible.value = false
}
defineExpose({
openDrawer,
drawerVisible
})
</script>
<style scoped></style>

View File

@ -10,6 +10,7 @@ import directives from './directives'
// 表格组件 // 表格组件
import ProTable from '@/components/ProTable/index.vue' import ProTable from '@/components/ProTable/index.vue'
import ProDialog from '@/components/ProDialog/index.vue' import ProDialog from '@/components/ProDialog/index.vue'
import ProDrawer from '@/components/ProDrawer/index.vue'
const app = createApp(App) const app = createApp(App)
app.use(install) app.use(install)
@ -17,4 +18,5 @@ app.use(eventBus)
app.use(directives) app.use(directives)
app.component('ProTable', ProTable) app.component('ProTable', ProTable)
app.component('ProDialog', ProDialog) app.component('ProDialog', ProDialog)
app.component('ProDrawer', ProDrawer)
app.mount('#app') app.mount('#app')

View File

@ -29,6 +29,7 @@
--el-text-color-secondary: #999999; --el-text-color-secondary: #999999;
--el-text-color-placeholder: #a8abb2; --el-text-color-placeholder: #a8abb2;
--el-text-color-disabled: #c0c4cc; --el-text-color-disabled: #c0c4cc;
--el-text-color-black2: #979797;
--el-border-color: #dcdfe6; --el-border-color: #dcdfe6;
--el-border-color-light: #e4e7ed; --el-border-color-light: #e4e7ed;
--el-border-color-lighter: #ebeef5; --el-border-color-lighter: #ebeef5;

View File

@ -0,0 +1,172 @@
<template>
<div class="edit-popup">
<popup
ref="popupRef"
:title="popupTitle"
:async="true"
width="550px"
@confirm="handleSubmit"
@close="handleClose"
>
<el-form ref="formRef" :model="formData" label-width="84px" :rules="formRules">
<el-form-item label="上级部门" prop="pid" v-if="formData.pid !== 0">
<el-tree-select
class="flex-1"
v-model="formData.pid"
:data="optionsData.dept"
clearable
node-key="id"
:props="{
value: 'id',
label: 'name'
}"
check-strictly
:default-expand-all="true"
placeholder="请选择上级部门"
/>
</el-form-item>
<el-form-item label="部门名称" prop="name">
<el-input
v-model="formData.name"
placeholder="请输入部门名称"
clearable
:maxlength="100"
/>
</el-form-item>
<el-form-item label="负责人" prop="duty">
<el-input
v-model="formData.duty"
placeholder="请输入负责人姓名"
clearable
:maxlength="30"
/>
</el-form-item>
<el-form-item label="联系电话" prop="mobile">
<el-input v-model="formData.mobile" placeholder="请输入联系电话" clearable />
</el-form-item>
<el-form-item label="排序" prop="sort">
<div>
<el-input-number v-model="formData.sort" :min="0" :max="9999" />
<div class="form-tips">默认为0 数值越大越排前</div>
</div>
</el-form-item>
<el-form-item label="部门状态" prop="isStop">
<el-switch v-model="formData.isStop" :active-value="0" :inactive-value="1" />
</el-form-item>
</el-form>
</popup>
</div>
</template>
<script lang="ts" setup>
import type { FormInstance } from 'element-plus'
import { deptLists, deptEdit, deptAdd, deptDetail } from '@/api/org/department'
import Popup from '@/components/popup/index.vue'
import { useDictOptions } from '@/hooks/useDictOptions'
import feedback from '@/utils/feedback'
const emit = defineEmits(['success', 'close'])
const formRef = shallowRef<FormInstance>()
const popupRef = shallowRef<InstanceType<typeof Popup>>()
const mode = ref('add')
const popupTitle = computed(() => {
return mode.value == 'edit' ? '编辑部门' : '新增部门'
})
const formData = reactive({
id: '',
pid: '' as string | number,
name: '',
duty: '',
mobile: '',
sort: 0,
isStop: 0
})
const checkMobile = (rule: any, value: any, callback: any) => {
if (!value) {
return callback()
} else {
const reg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/
console.log(reg.test(value))
if (reg.test(value)) {
callback()
} else {
return callback(new Error('请输入正确的手机号'))
}
}
}
const formRules = {
pid: [
{
required: true,
message: '请选择上级部门',
trigger: ['change']
}
],
name: [
{
required: true,
message: '请输入部门名称',
trigger: ['blur']
}
],
duty: [
{
required: true,
message: '请输入负责人',
trigger: ['blur']
}
],
mobile: [
{
required: true,
message: '请输入联系电话',
trigger: ['blur']
}
]
}
const { optionsData } = useDictOptions<{
dept: any[]
}>({
dept: {
api: deptLists
}
})
const handleSubmit = async () => {
await formRef.value?.validate()
mode.value == 'edit' ? await deptEdit(formData) : await deptAdd(formData)
popupRef.value?.close()
feedback.msgSuccess('操作成功')
emit('success')
}
const open = (type = 'add') => {
mode.value = type
popupRef.value?.open()
}
const setFormData = (data: Record<any, any>) => {
for (const key in formData) {
if (data[key] != null && data[key] != undefined) {
//@ts-ignore
formData[key] = data[key]
}
}
}
const getDetail = async (row: Record<string, any>) => {
const data = await deptDetail({
id: row.id
})
setFormData(data)
}
const handleClose = () => {
emit('close')
}
defineExpose({
open,
setFormData,
getDetail
})
</script>

View File

@ -0,0 +1,166 @@
<template>
<div class="dept-lists">
<el-card class="!border-none" shadow="never">
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" :inline="true">
<el-form-item label="部门名称" prop="name">
<el-input
class="w-[280px]"
v-model="queryParams.name"
clearable
@keyup.enter="getLists"
/>
</el-form-item>
<el-form-item label="部门状态" prop="isStop">
<el-select class="w-[280px]" v-model="queryParams.isStop">
<el-option label="全部" value />
<el-option label="正常" value="0" />
<el-option label="停用" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getLists"></el-button>
<el-button @click="resetParams"></el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<div>
<el-button v-perms="['system:dept:add']" type="primary" @click="handleAdd()">
<template #icon>
<icon name="el-icon-Plus" />
</template>
新增
</el-button>
<el-button @click="handleExpand"> / </el-button>
</div>
<el-table
ref="tableRef"
class="mt-4"
size="large"
v-loading="loading"
:data="lists"
row-key="id"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column
label="部门名称"
prop="name"
min-width="150"
show-overflow-tooltip
/>
<el-table-column label="部门状态" prop="isStop" min-width="100">
<template #default="{ row }">
<el-tag class="ml-2" :type="row.isStop ? 'danger' : ''">
{{ row.isStop ? '停用' : '正常' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="排序" prop="sort" min-width="100" />
<el-table-column label="更新时间" prop="updateTime" min-width="180" />
<el-table-column label="操作" width="160" fixed="right">
<template #default="{ row }">
<el-button
v-perms="['system:dept:add']"
type="primary"
link
@click="handleAdd(row.id)"
>
新增
</el-button>
<el-button
v-perms="['system:dept:edit']"
type="primary"
link
@click="handleEdit(row)"
>
编辑
</el-button>
<el-button
v-if="row.pid !== 0"
v-perms="['system:dept:del']"
type="danger"
link
@click="handleDelete(row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
</div>
</template>
<script lang="ts" setup name="department">
import type { ElTable, FormInstance } from 'element-plus'
import EditPopup from './edit.vue'
import { deptDelete, deptLists } from '@/api/org/department'
import feedback from '@/utils/feedback'
const tableRef = shallowRef<InstanceType<typeof ElTable>>()
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
const formRef = shallowRef<FormInstance>()
let isExpand = false
const loading = ref(false)
const lists = ref<any[]>([])
const queryParams = reactive({
isStop: '',
name: ''
})
const showEdit = ref(false)
const getLists = async () => {
loading.value = true
lists.value = await deptLists(queryParams)
loading.value = false
}
const resetParams = () => {
formRef.value?.resetFields()
getLists()
}
const handleAdd = async (id?: number) => {
showEdit.value = true
await nextTick()
if (id) {
editRef.value?.setFormData({
pid: id
})
}
editRef.value?.open('add')
}
const handleEdit = async (data: any) => {
showEdit.value = true
await nextTick()
editRef.value?.open('edit')
editRef.value?.getDetail(data)
}
const handleDelete = async (id: number) => {
await feedback.confirm('确定要删除?')
await deptDelete({ id })
feedback.msgSuccess('删除成功')
getLists()
}
const handleExpand = () => {
isExpand = !isExpand
toggleExpand(lists.value, isExpand)
}
const toggleExpand = (children: any[], unfold = true) => {
for (const key in children) {
tableRef.value?.toggleRowExpansion(children[key], unfold)
if (children[key].children) {
toggleExpand(children[key].children!, unfold)
}
}
}
onMounted(async () => {
await getLists()
nextTick(() => {
handleExpand()
})
})
</script>

View File

@ -0,0 +1,120 @@
<template>
<div class="edit-popup">
<popup
ref="popupRef"
:title="popupTitle"
:async="true"
width="550px"
@confirm="handleSubmit"
@close="handleClose"
>
<el-form ref="formRef" :model="formData" label-width="84px" :rules="formRules">
<el-form-item label="岗位名称" prop="name">
<el-input
v-model="formData.name"
placeholder="请输入岗位名称"
clearable
:maxlength="100"
/>
</el-form-item>
<el-form-item label="岗位编码" prop="code">
<el-input v-model="formData.code" placeholder="请输入岗位编码" clearable />
</el-form-item>
<el-form-item label="排序" prop="sort">
<div>
<el-input-number v-model="formData.sort" :min="0" :max="9999" />
<div class="form-tips">默认为0 数值越大越排前</div>
</div>
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input
v-model="formData.remarks"
placeholder="请输入备注"
type="textarea"
:autosize="{ minRows: 4, maxRows: 6 }"
/>
</el-form-item>
<el-form-item label="岗位状态" prop="isStop">
<el-switch v-model="formData.isStop" :active-value="0" :inactive-value="1" />
</el-form-item>
</el-form>
</popup>
</div>
</template>
<script lang="ts" setup>
import type { FormInstance } from 'element-plus'
import { postEdit, postAdd, postDetail } from '@/api/org/post'
import Popup from '@/components/popup/index.vue'
import feedback from '@/utils/feedback'
const emit = defineEmits(['success', 'close'])
const formRef = shallowRef<FormInstance>()
const popupRef = shallowRef<InstanceType<typeof Popup>>()
const mode = ref('add')
const popupTitle = computed(() => {
return mode.value == 'edit' ? '编辑岗位' : '新增岗位'
})
const formData = reactive({
id: '',
name: '',
code: '',
sort: 0,
remarks: '',
isStop: 0
})
const formRules = {
code: [
{
required: true,
message: '请输入岗位编码',
trigger: ['blur']
}
],
name: [
{
required: true,
message: '请输入岗位名称',
trigger: ['blur']
}
]
}
const handleSubmit = async () => {
await formRef.value?.validate()
mode.value == 'edit' ? await postEdit(formData) : await postAdd(formData)
feedback.msgSuccess('操作成功')
popupRef.value?.close()
emit('success')
}
const open = (type = 'add') => {
mode.value = type
popupRef.value?.open()
}
const setFormData = (data: Record<any, any>) => {
for (const key in formData) {
if (data[key] != null && data[key] != undefined) {
//@ts-ignore
formData[key] = data[key]
}
}
}
const getDetail = async (row: Record<string, any>) => {
const data = await postDetail({
id: row.id
})
setFormData(data)
}
const handleClose = () => {
emit('close')
}
defineExpose({
open,
setFormData,
getDetail
})
</script>

View File

@ -0,0 +1,128 @@
<template>
<div class="post-lists">
<el-card class="!border-none" shadow="never">
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" :inline="true">
<el-form-item label="岗位编码">
<el-input
class="w-[280px]"
v-model="queryParams.code"
clearable
@keyup.enter="resetPage"
/>
</el-form-item>
<el-form-item label="岗位名称">
<el-input
class="w-[280px]"
v-model="queryParams.name"
clearable
@keyup.enter="resetPage"
/>
</el-form-item>
<el-form-item label="岗位状态">
<el-select class="w-[280px]" v-model="queryParams.isStop">
<el-option label="全部" value />
<el-option label="正常" :value="0" />
<el-option label="停用" :value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="resetPage"></el-button>
<el-button @click="resetParams"></el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<div>
<el-button v-perms="['system:post:add']" type="primary" @click="handleAdd()">
<template #icon>
<icon name="el-icon-Plus" />
</template>
新增
</el-button>
</div>
<el-table class="mt-4" size="large" v-loading="pager.loading" :data="pager.lists">
<el-table-column label="岗位编码" prop="code" min-width="100" />
<el-table-column label="岗位名称" prop="name" min-width="100" />
<el-table-column label="排序" prop="sort" min-width="100" />
<el-table-column
label="备注"
prop="remarks"
min-width="100"
show-overflow-tooltip
/>
<el-table-column label="添加时间" prop="createTime" min-width="180" />
<el-table-column label="岗位状态" prop="isStop" min-width="100">
<template #default="{ row }">
<el-tag class="ml-2" :type="row.isStop ? 'danger' : ''">
{{ row.isStop ? '停用' : '正常' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button
v-perms="['system:post:edit']"
type="primary"
link
@click="handleEdit(row)"
>
编辑
</el-button>
<el-button
v-perms="['system:post:del']"
type="danger"
link
@click="handleDelete(row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="flex justify-end mt-4">
<pagination v-model="pager" @change="getLists" />
</div>
</el-card>
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
</div>
</template>
<script lang="ts" setup name="post">
import { postDelete, postLists } from '@/api/org/post'
import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback'
import EditPopup from './edit.vue'
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
const showEdit = ref(false)
const queryParams = reactive({
code: '',
name: '',
isStop: ''
})
const { pager, getLists, resetPage, resetParams } = usePaging({
fetchFun: postLists,
params: queryParams
})
const handleAdd = async () => {
showEdit.value = true
await nextTick()
editRef.value?.open('add')
}
const handleEdit = async (data: any) => {
showEdit.value = true
await nextTick()
editRef.value?.open('edit')
editRef.value?.getDetail(data)
}
const handleDelete = async (id: number) => {
await feedback.confirm('确定要删除?')
await postDelete({ id })
feedback.msgSuccess('删除成功')
getLists()
}
getLists()
</script>

View File

@ -19,39 +19,41 @@
</el-form-item> </el-form-item>
<el-form-item label="岗位"> <el-form-item label="岗位">
<el-checkbox-group v-model="form.position"> <el-checkbox-group v-model="form.position">
<el-checkbox v-for="option in positionOptions" :key="option.value" :value="option.value" name="type"> <el-checkbox v-for="option in positionOptions" :key="option.value" :label="option.value" name="type">
{{ option.label }} {{ option.label }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
<el-form-item> <template v-if="form.position.includes(1)">
<template #label> <el-form-item>
<div class="flex items-center gap-[6px] cursor-pointer"> <template #label>
<span>有效数据流向</span> <div class="flex items-center gap-[6px] cursor-pointer">
<el-tooltip effect="dark" placement="top"> <span>有效数据流向</span>
<icon name="el-icon-QuestionFilled" /> <el-tooltip effect="dark" placement="top">
<template #content> <icon name="el-icon-QuestionFilled" />
<div>电销有效数据流向</div> <template #content>
<ul> <div>电销有效数据流向</div>
<li v-for="(tooltip, index) in channelTooltips" :key="index" class="flex items-center gap-[4px]"> <ul>
<span class="w-[6px] h-[6px] bg-white rounded-[8px]"></span> <li v-for="(tooltip, index) in channelTooltips" :key="index" class="flex items-center gap-[4px]">
{{ tooltip }} <span class="w-[6px] h-[6px] bg-white rounded-[8px]"></span>
</li> {{ tooltip }}
</ul> </li>
</template> </ul>
</el-tooltip> </template>
</div> </el-tooltip>
</template> </div>
<el-radio-group v-model="form.channel"> </template>
<el-radio v-for="option in channelOptions" :key="option.value" :label="option.value">{{ option.label }}</el-radio> <el-radio-group v-model="form.channel">
</el-radio-group> <el-radio v-for="option in channelOptions" :key="option.value" :label="option.value">{{ option.label }}</el-radio>
<template v-if="form.channel == 'specific'"> </el-radio-group>
<el-select class="mt-[10px]" v-model="form.teacher" placeholder="请选择指定招生老师"> <template v-if="form.channel == 'specific'">
<el-option label="Zone one" value="shanghai" /> <el-select class="mt-[10px]" v-model="form.teacher" placeholder="请选择指定招生老师">
<el-option label="Zone two" value="beijing" /> <el-option label="Zone one" value="shanghai" />
</el-select> <el-option label="Zone two" value="beijing" />
</template> </el-select>
</el-form-item> </template>
</el-form-item>
</template>
<el-form-item label="账号状态"> <el-form-item label="账号状态">
<el-radio-group v-model="form.accountStatus"> <el-radio-group v-model="form.accountStatus">
<el-radio v-for="option in accountStatusOptions" :key="option.value" :label="option.value">{{ option.label }}</el-radio> <el-radio v-for="option in accountStatusOptions" :key="option.value" :label="option.value">{{ option.label }}</el-radio>
@ -65,6 +67,7 @@
import ProDialog, { type IParams } from '@/components/ProDialog/index.vue' import ProDialog, { type IParams } from '@/components/ProDialog/index.vue'
const proDialogRef = ref<InstanceType<typeof ProDialog>>() const proDialogRef = ref<InstanceType<typeof ProDialog>>()
const emit = defineEmits(['fetchTableList'])
const data = ref([ const data = ref([
{ {
id: 1, id: 1,
@ -127,7 +130,7 @@ const form = ref({
organization: '', organization: '',
accountName: '', accountName: '',
mobile: '', mobile: '',
position: '', position: [],
channel: '', channel: '',
accountStatus: '', accountStatus: '',
teacher: '' teacher: ''
@ -140,6 +143,7 @@ const handleCancel = (callback: () => void) => {
} }
const handleConfirm = (callback: () => void) => { const handleConfirm = (callback: () => void) => {
callback() callback()
emit('fetchTableList')
} }
defineExpose({ defineExpose({
openDialog openDialog

View File

@ -1,125 +0,0 @@
<template>
<el-dialog v-model="dialogVisible" :title="parameter.title" :width="parameter.width">
<el-space direction="vertical" alignment="normal">
<el-form :model="searchForm" ref="userFormRef" :inline="true" label-width="auto" @submit.native.prevent>
<el-form-item label="账号名称">
<el-input class="ls-input" v-model="searchForm.accountName" placeholder="请输入账号名称" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary">搜索</el-button>
<el-button>重置</el-button>
</el-form-item>
</el-form>
<ProTable ref="proTableRef" :columns="columns" :tableData="tableData" :loading="loading" :maxHeight="530">
<template #radio="{ row }">
<el-radio v-model="radio" :label="row.id">&nbsp;</el-radio>
</template>
<template #accountName="{ row }">
<el-space>
<span>{{ row.accountName }}</span>
<span
v-if="isGroupLeader(row.groupLeader)"
class="px-[6px] text-green border border-solid border-green rounded-[4px] text-center text-xs"
>
组长
</span>
</el-space>
</template>
<template #accountStatus="{ row }">
<el-switch v-model="row.accountStatus" disabled :active-value="1" :inactive-value="0" />
</template>
</ProTable>
<!-- <pagination v-model="pager" @change="getLists" layout="total, prev, pager, next, jumper" class="mb-[10px]" /> -->
</el-space>
<template #footer>
<el-button @click="handleCancel"></el-button>
<el-button type="primary" @click="handleConfirm"></el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
interface IParams {
title: string
width?: string | number
data: { [key: string]: any }
}
const dialogVisible = ref(false)
//
const parameter = ref<IParams>({
title: '',
width: '50%',
data: {}
})
const openDialog = (params: IParams) => {
parameter.value = { ...parameter.value, ...params }
dialogVisible.value = true
}
const emit = defineEmits(['refreshList'])
defineExpose({
openDialog
})
const searchForm = ref({
accountName: ''
})
const loading = ref(false)
const proTableRef = ref()
const tableData = ref<any[]>([])
const columns = reactive([
{ prop: 'radio', label: '单选', width: 70 },
{ prop: 'accountName', label: '账号名称' },
{ prop: 'mobile', label: '联系电话' },
{ prop: 'organization', label: '所属组织' },
{ prop: 'position', label: '岗位' },
{ prop: 'accountStatus', label: '账号状态' }
])
const radio = ref()
const isGroupLeader = computed(() => (groupLeader: number) => groupLeader == 1)
watch(
() => dialogVisible.value,
val => {
if (val) {
loading.value = true
setTimeout(() => {
tableData.value = [
{
id: 1,
accountName: '张三',
mobile: '18138952909',
organization: '广州团队-A组',
position: '电销老师',
accountStatus: 1,
groupLeader: 1
},
{
id: 2,
accountName: '李四',
mobile: '18138952909',
organization: '广州团队-A组',
position: '电销老师',
accountStatus: 1,
groupLeader: 0
}
]
loading.value = false
//
if (tableData.value.length > 0) {
radio.value = tableData.value.find(item => item.groupLeader == 1)?.id
}
}, 500)
}
}
)
const handleCancel = () => {
dialogVisible.value = false
}
const handleConfirm = () => {
console.log(radio.value)
dialogVisible.value = false
emit('refreshList')
}
</script>
<style scoped></style>

View File

@ -9,15 +9,15 @@
<el-button type="danger" plain :icon="Delete" :disabled="isDisabled" @click="handleBatchDelete"></el-button> <el-button type="danger" plain :icon="Delete" :disabled="isDisabled" @click="handleBatchDelete"></el-button>
</el-space> </el-space>
<el-space> <el-space>
<el-input :placeholder="`请输入${placeholder}`"> <el-input :placeholder="`请输入${placeholder}`" v-model="searchForm.keyword" @input="handleInputChange">
<template #prepend> <template #prepend>
<el-select v-model="selectKey" :placeholder="placeholder" class="w-[100px]"> <el-select v-model="selectKey" :placeholder="placeholder" class="w-[100px]">
<el-option v-for="option in searchOptions" :key="option.field" :label="option.label" :value="option.field" /> <el-option v-for="option in searchOptions" :key="option.field" :label="option.label" :value="option.field" />
</el-select> </el-select>
</template> </template>
</el-input> </el-input>
<el-select placeholder="请选择账号状态"> <el-select placeholder="请选择账号状态" v-model="searchForm.accountStatus" clearable @change="handleSelectChange">
<el-option label="Restaurant" value="1" /> <el-option v-for="option in accountStatusOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select> </el-select>
</el-space> </el-space>
</div> </div>
@ -44,7 +44,7 @@
</template> </template>
</ProTable> </ProTable>
</div> </div>
<account-dialog ref="accountDialogRef" /> <account-dialog ref="accountDialogRef" @fetch-table-list="fetchTableList" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -52,6 +52,7 @@ import useHandleData from '@/hooks/useHandleData'
import feedback from '@/utils/feedback' import feedback from '@/utils/feedback'
import { Plus, Delete } from '@element-plus/icons-vue' import { Plus, Delete } from '@element-plus/icons-vue'
import accountDialog from '../components/account-list/account-dialog.vue' import accountDialog from '../components/account-list/account-dialog.vue'
import { useDebounceFn } from '@vueuse/core'
defineProps({ defineProps({
curOrganization: { curOrganization: {
@ -66,7 +67,10 @@ const searchOptions = shallowRef([
{ field: 'positionName', label: '岗位名称' } { field: 'positionName', label: '岗位名称' }
]) ])
const placeholder = computed(() => searchOptions.value.find(item => item.field == selectKey.value)?.label) const placeholder = computed(() => searchOptions.value.find(item => item.field == selectKey.value)?.label)
const accountStatusOptions = ref([
{ label: '启用', value: 1 },
{ label: '停用', value: 2 }
])
const loading = ref(false) const loading = ref(false)
const proTableRef = ref() const proTableRef = ref()
const tableData = ref<any[]>([]) const tableData = ref<any[]>([])
@ -158,5 +162,28 @@ const showAccountDialog = () => {
data: {} data: {}
}) })
} }
const searchForm = ref({
keyword: '',
accountStatus: ''
})
const handleInputChange = useDebounceFn(() => {
const formMap: Record<string, string> = {
accountName: '',
mobile: '',
position: ''
}
formMap[selectKey.value] = searchForm.value.keyword
}, 500)
const handleSelectChange = () => {
console.log('handleSelectChange')
}
watch(
() => selectKey.value,
(newVal, oldVal) => {
if (newVal != oldVal && searchForm.value.keyword !== '') {
searchForm.value.keyword = ''
}
}
)
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -0,0 +1,67 @@
<template>
<el-space direction="vertical" alignment="normal" :size="24" v-loading="loading">
<el-descriptions v-for="item in baseInfo" :key="item.title" :title="item.title" direction="vertical" :column="4" border>
<el-descriptions-item v-for="(itemy, indey) in item.data" :key="indey" :label="itemy.label">{{ itemy.value }}</el-descriptions-item>
</el-descriptions>
</el-space>
</template>
<script setup lang="ts">
const props = defineProps({
modelValue: {
type: String,
default: 'baseInfo'
}
})
const loading = ref(false)
watch(
() => props.modelValue,
() => {
console.log('基础信息')
loading.value = true
setTimeout(() => {
loading.value = false
}, 500)
},
{ immediate: true }
)
const baseInfo = ref([
{
title: '线索基本信息',
data: [
{ label: '线索提供者', value: '张三' },
{ label: '线索来源', value: '线下名单' },
{ label: '创建人', value: 'admin' },
{ label: '创建时间', value: '2025-02-12 11:30:00' },
{ label: '客户姓名', value: '韩梅梅' },
{ label: '联系电话', value: '18866668888' },
{
label: '基本情况',
value: '学生爸爸接电话学生高三毕业300多分家长不清楚学生收到录取通知家长说学生不读书了我让家长先问问学生对未来的规划先和学生沟通一下家长同意我们加他微信发专业资料给他看看可以在微信上问问学生具体情况。推荐3+2给家长发一下学校简介和专业资料。'
}
]
},
{
title: '电销信息',
data: [
{ label: '负责人', value: '张三' },
{ label: '所属组织', value: '广州团队-A组' },
{ label: '线索状态', value: '已添加' },
{ label: '跟进时间', value: '2025-02-12 11:30:00' }
]
},
{
title: '招生信息',
data: [
{ label: '负责人', value: '张三' },
{ label: '所属组织', value: '广州团队-A组' },
{ label: '领取时间', value: '2025-02-12 11:19:00' },
{ label: '状态', value: '账号已添加' },
{ label: '是否转化成功', value: '' },
{ label: '备注', value: '' },
{ label: '成交时间', value: '' }
]
}
])
</script>
<style scoped></style>

View File

@ -0,0 +1,83 @@
<template>
<el-timeline class="px-[6px]" v-loading="loading">
<el-timeline-item v-for="(item, index) in recordList" :key="index" type="primary" color="#0E66FB">
<el-space direction="vertical" alignment="normal">
<div class="text-primary">{{ item.date }}</div>
<div>
{{ item.user }}
<span class="text-black2">{{ item.step }}</span>
</div>
<div v-if="item.content" class="bg-primary-light-10 w-full p-[12px] rounded-[6px] flex flex-col gap-[6px]">
<span v-for="(value, key) in item.content" :key="key">{{ key }}{{ value }}</span>
</div>
</el-space>
</el-timeline-item>
</el-timeline>
</template>
<script setup lang="ts">
const props = defineProps({
modelValue: {
type: String,
default: 'clueRecord'
}
})
const loading = ref(false)
watch(
() => props.modelValue,
() => {
console.log('线索来源')
loading.value = true
setTimeout(() => {
loading.value = false
}, 500)
},
{ immediate: true }
)
const recordList = ref([
{
date: '2025-02-12 15:47:00',
user: '电销老师:张三',
step: '添加跟进',
content: {
客户姓名: '韩梅梅',
联系电话: '188866668888',
基本情况:
'学生爸爸接电话学生高三毕业300多分家长不清楚学生收到录取通知家长说学生不读书了我让家长先问问学生对未来的规划先和学生沟通一下家长同意我们加他微信发专业资料给他看看可以在微信上问问学生具体情况。推荐3'
}
},
{ date: '2025-02-12 15:47:00', user: '招生老师:王五', step: '领取线索', content: null },
{
date: '2025-02-12 15:47:00',
user: '招生老师:王五',
step: '添加进展',
content: {
状态: '账号已添加'
}
},
{
date: '2025-02-12 15:47:00',
user: '招生老师:王五',
step: '转化完成',
content: {
是否转化成功: '是',
备注: '已交部分定位金'
}
}
])
</script>
<style scoped lang="scss">
:deep(.el-space) {
width: 100%;
.el-space__item {
&:last-child {
width: 100%;
}
}
}
:deep(.el-timeline-item__tail) {
border-style: dashed;
border-color: var(--el-color-primary);
}
</style>

View File

@ -0,0 +1,38 @@
<template>
<div>
<search-form v-model="queryParams" @reset-page="resetPage" @reset-params="resetParams" />
<clue-list :loading="pager.loading" :tableData="pager.lists">
<template #operation="{ row }">
<el-button type="primary" link @click="handleDetail(row)"></el-button>
</template>
</clue-list>
</div>
<clue-detail ref="clueDetailRef" />
</template>
<script setup lang="ts">
import searchForm from './modules/search-form.vue'
import clueList from './modules/clue-list.vue'
import clueDetail from './modules/clue-detail.vue'
import { postLists } from '@/api/org/post'
import { usePaging } from '@/hooks/usePaging'
const queryParams = reactive({
name: '',
conversionStatus: ''
})
const { pager, getLists, resetPage, resetParams } = usePaging({
fetchFun: postLists,
params: queryParams
})
getLists()
const clueDetailRef = ref()
const handleDetail = () => {
clueDetailRef.value.openDrawer({
title: '线索详情'
})
}
</script>
<style scoped></style>

View File

@ -0,0 +1,29 @@
<template>
<ProDrawer ref="proDrawerRef">
<el-tabs v-model="activeTab">
<el-tab-pane v-for="tab in tabs" :key="tab.name" :label="tab.label" :name="tab.name">
<component v-if="tab.name == activeTab" :is="tab.component" v-model="activeTab" />
</el-tab-pane>
</el-tabs>
</ProDrawer>
</template>
<script setup lang="ts">
import ProDrawer, { type IParams } from '@/components/ProDrawer/index.vue'
import baseIfno from '../components/clue-detail/base-info.vue'
import clueRecord from '../components/clue-detail/clue-record.vue'
const proDrawerRef = ref<InstanceType<typeof ProDrawer>>()
const activeTab = ref('baseInfo')
const tabs = shallowRef([
{ label: '基础信息', name: 'baseInfo', component: baseIfno },
{ label: '线索来源', name: 'clueRecord', component: clueRecord }
])
const openDrawer = (params: IParams) => {
proDrawerRef.value?.openDrawer(params)
}
defineExpose({
openDrawer
})
</script>
<style scoped></style>

View File

@ -0,0 +1,34 @@
<template>
<el-card shadow="never" class="!border-none mt-4">
<ProTable ref="proTableRef" :columns="columns" :tableData="tableData" :loading="loading" :maxHeight="530">
<template #operation="{ row }">
<slot name="operation" :row="row" />
</template>
</ProTable>
</el-card>
</template>
<script setup lang="ts">
defineProps({
loading: {
type: Boolean,
default: false
},
tableData: {
type: Array,
default: () => []
}
})
const proTableRef = ref()
const columns = reactive([
{ prop: 'accountName', label: '学生名字', width: 180 },
{ prop: 'mobile', label: '联系电话', width: 180 },
{ prop: 'organization', label: '电销老师', width: 180 },
{ prop: 'position', label: '转化老师', width: 180 },
{ prop: 'position', label: '名单来源', width: 180 },
{ prop: 'position', label: '转化情况', width: 180 },
{ prop: 'accountStatus', label: '创建人', width: 180 },
{ prop: 'operation', label: '操作', fixed: 'right', width: 250 }
])
</script>
<style scoped></style>

View File

@ -0,0 +1,41 @@
<template>
<el-card shadow="never" class="!border-none">
<el-form ref="formRef" class="mb-[-16px]" :model="modelValue" :inline="true">
<el-form-item label="学生名字">
<el-input class="w-[280px]" placeholder="请输入" v-model="modelValue.name" clearable @keyup.enter="$emit('resetPage')" />
</el-form-item>
<el-form-item label="转化情况">
<el-select class="w-[280px]" v-model="modelValue.conversionStatus">
<el-option v-for="option in conversionOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="$emit('resetPage')"></el-button>
<el-button @click="$emit('resetParams')"></el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script setup lang="ts">
defineProps({
modelValue: {
type: Object,
default: () => ({
name: '',
conversionStatus: ''
})
}
})
defineEmits(['resetPage', 'resetParams'])
const conversionOptions = ref([
{ label: '有意向', value: 1 },
{ label: '待领取', value: 2 },
{ label: '转化中', value: 3 },
{ label: '已添加', value: 4 },
{ label: '异常待处理', value: 5 },
{ label: '已成交', value: 6 },
{ label: '已战败', value: 7 }
])
</script>
<style scoped></style>

View File

@ -35,7 +35,8 @@ module.exports = {
'fill-light': 'var(--el-fill-color-light)', 'fill-light': 'var(--el-fill-color-light)',
'fill-lighter': 'var(--el-fill-color-lighter)', 'fill-lighter': 'var(--el-fill-color-lighter)',
mask: 'var(--el-mask-color)', mask: 'var(--el-mask-color)',
green: 'var(--color-green)' green: 'var(--color-green)',
black2: 'var(--el-text-color-black2)'
}, },
fontFamily: { fontFamily: {
sans: ['PingFang SC', 'Arial', 'Hiragino Sans GB', 'Microsoft YaHei', 'sans-serif'] sans: ['PingFang SC', 'Arial', 'Hiragino Sans GB', 'Microsoft YaHei', 'sans-serif']