【招生用户端】 修复# 工作台:切换条件查询时,线索转客户统计以及成交客户统计没反应

main
kaeery 2025-03-07 21:39:18 +08:00
parent 6e1a9e0ce9
commit 6bdb7a2730
16 changed files with 304 additions and 170 deletions

View File

@ -24,3 +24,7 @@ export function subAccountDetail(params: Record<string, any>) {
export function subAccountDelete(params: Record<string, any>) {
return request.post({ url: '/user/sonDel', params })
}
// 修改账号状态
export function subAccountUpdateStatus(params: Record<string, any>) {
return request.post({ url: '/user/changingAccountStatus', params })
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -96,9 +96,13 @@ const setFormData = (data: Record<any, any>) => {
}
const getDetail = async (row: Record<string, any>) => {
console.log(row)
const data = await organzationDetail({
id: row.id
})
console.log(data, '==')
setFormData(data)
}

View File

@ -113,8 +113,9 @@ const handleEdit = async (data: any) => {
}
const handleDelete = async (id: number) => {
const ids = [id]
await feedback.confirm('确定要删除?')
await organzationDelete({ id })
await organzationDelete({ ids })
feedback.msgSuccess('删除成功')
getLists()
}

View File

@ -0,0 +1,19 @@
<template>
<div class="flex flex-col items-center justify-center flex-1">
<div class="flex flex-col gap-[6px]">
<img :src="emptyStatusImg" style="width: 95px; height: 75px" />
<span>您还没有分组</span>
</div>
<span class="mt-[6px] mb-[16px] text-[14px] text-[#999]">请新建分组后进行新建账号</span>
<div>
<el-button type="primary" @click="$emit('handleOrganization', 'add')">新建分组</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import emptyStatusImg from '@/assets/images/emptyStatus.png'
defineEmits(['handleOrganization'])
</script>
<style scoped></style>

View File

@ -2,9 +2,9 @@
<div>
<div class="flex gap-[6px] items-center mb-[2px]">
<el-icon color="#E6A23C"><WarningFilled /></el-icon>
<span>此操作不可逆确定删除组织?</span>
<span>此操作不可逆确定删除{{ name }}组织?</span>
</div>
<span class="text-[14px] text-[#999] ml-[20px]">此组织下的所有成员也会被删除</span>
<!-- <span class="text-[14px] text-[#999] ml-[20px]">此组织下的所有成员也会被删除</span> -->
</div>
</template>
@ -15,6 +15,12 @@ export default defineComponent({
components: {
WarningFilled
},
props: {
name: {
type: String,
default: ''
}
},
setup() {
return {
WalletFilled

View File

@ -46,6 +46,7 @@ export interface Tree {
id: number
label: string
children?: Tree[]
name?: string
}
const props = defineProps({
data: {
@ -72,7 +73,7 @@ const treeRef = ref<InstanceType<typeof ElTree>>()
const primaryAccountId = computed(() => (props.data && props.data.length > 0 ? props.data[0]?.id : 0))
const filterNode = (value: string, data: Tree) => {
if (!value) return true
return data.label.includes(value)
return data.name?.includes(value)
}
watch(filterText, val => {
treeRef.value!.filter(val)

View File

@ -2,8 +2,21 @@
<div class="flex flex-col h-full">
<account-number :accoutnInfo="accoutnInfo" />
<div class="flex flex-1 bg-white overflow-x-auto">
<organization @set-selected-node="setSelectedNode" :curSelectedNode="selectedNode" @fetch-table-list="fetchTableList" />
<account-list ref="accountListRef" :curOrganization="selectedNode" @refresh-sub-account-number="fetchSubAccountNumber" />
<organization
ref="organizationRef"
@set-selected-node="setSelectedNode"
:curSelectedNode="selectedNode"
@fetch-table-list="fetchTableList"
@get-organization-list="getOrganizationList"
/>
<!-- v-if="organzationList[0]?.children?.length > 0"-->
<account-list
ref="accountListRef"
:curOrganization="selectedNode"
@refresh-sub-account-number="fetchSubAccountNumber"
@fetch-organization-list="fetchOrganizationList"
/>
<!-- <account-empty v-else /> -->
</div>
</div>
</template>
@ -12,11 +25,14 @@
import accountNumber, { type IAccountInfo } from './modules/account-number.vue'
import organization from './modules/organization.vue'
import accountList from './modules/account-list.vue'
import accountEmpty from './modules/account-empty.vue'
import type { Tree } from './components/organization/organization-tree.vue'
import { subAccountNumber } from '@/api/account_center/sub_account'
import { StatusEnum } from '@/enums'
import feedback from '@/utils/feedback'
const organizationRef = ref<InstanceType<typeof organization>>()
const organzationList = ref<Tree[]>([])
const selectedNode = ref()
const setSelectedNode = (data: Tree) => {
selectedNode.value = data
@ -42,5 +58,13 @@ const fetchSubAccountNumber = async () => {
} catch (error) {}
}
fetchSubAccountNumber()
const getOrganizationList = (data: Tree[]) => {
organzationList.value = data
}
const fetchOrganizationList = async () => {
organizationRef.value?.fetchOrganizationList()
fetchTableList()
}
</script>
<style scoped></style>

View File

@ -0,0 +1,26 @@
<template>
<div class="flex items-center flex-1 justify-center">
<div class="w-[25%] gap-[20px]" v-for="(step, index) in steps" :key="step.label">
<div class="flex flex-col items-center gap-[16px]">
<div class="flex items-center gap-[20px]">
<img :src="step.imagePath" style="width: 100%; height: 100%" :alt="step.label" />
<el-icon v-if="index < steps.length - 1" :size="24" color="#999"><ArrowRightBold /></el-icon>
</div>
<div class="flex items-center gap-[6px]">
<span class="font-bold text-[20px] text-primary">0{{ index + 1 }}</span>
<span class="text-[16px]">{{ step.label }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import stepOneImg from '@/assets/images/step-one.png'
const steps = shallowRef([
{ label: '新建组织', imagePath: stepOneImg },
{ label: '新建分组', imagePath: stepOneImg },
{ label: '新建账号', imagePath: stepOneImg }
])
</script>
<style scoped></style>

View File

@ -1,61 +1,67 @@
<template>
<div class="flex flex-col flex-1">
<div class="h-[50px] flex items-center border-b-solid-light2 px-[20px] text-[20px] font-bold">
{{ curOrganization.name }}
</div>
<div class="px-[20px] py-[16px] flex justify-between">
<el-space>
<el-button type="primary" :icon="Plus" :disabled="isDisabledAdd" @click="showAccountDialog"></el-button>
<el-button type="danger" plain :icon="Delete" :disabled="isDisabled" @click="handleBatchDelete"></el-button>
</el-space>
<el-space>
<el-input :placeholder="`请输入${placeholder}`" v-model="searchForm.keyword" clearable @input="handleInputChange">
<template #prepend>
<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-select>
</template>
</el-input>
<el-select placeholder="请选择岗位名称" v-model="searchForm.postId" clearable @change="handleSelectChange">
<el-option v-for="option in positionOptions" :key="option.id" :label="option.name" :value="option.id" />
</el-select>
<el-select placeholder="请选择账号状态" v-model="searchForm.isDisable" clearable @change="handleSelectChange">
<el-option v-for="option in accountStatusOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
</el-space>
</div>
<ProTable ref="proTableRef" :columns="columns" :tableData="tableData" :loading="loading" :maxHeight="530">
<template #username="{ row }">
<template v-if="showEmptyComponent">
<empty-content @handle-organization="handleOrganization" />
</template>
<template v-else>
<div class="h-[50px] flex items-center border-b-solid-light2 px-[20px] text-[20px] font-bold">
{{ curOrganization.name }}
</div>
<div class="px-[20px] py-[16px] flex justify-between">
<el-space>
<div class="w-[50px] h-[50px] bg-primary rounded-[6px] text-white flex-row-center-center">{{ row.username }}</div>
<span>{{ row.username }}</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-button type="primary" :icon="Plus" :disabled="isDisabledAdd" @click="showAccountDialog"></el-button>
<el-button type="danger" plain :icon="Delete" :disabled="isDisabled" @click="handleBatchDelete"></el-button>
</el-space>
</template>
<template #isDisable="{ row }">
<el-switch
v-model="row.isDisable"
:active-value="isDisabledEnum.NO"
:inactive-value="isDisabledEnum.YES"
:before-change="() => handleStatusChange(row)"
/>
</template>
<template #operation="{ row }">
<el-button link type="primary" @click="handleEdit(row)"></el-button>
<el-button link type="primary" @click="setGroupLeader(row)">{{ btnText(row.groupLeader) }}</el-button>
<el-button link type="primary" @click="handleDelete(row)"></el-button>
</template>
</ProTable>
<div class="flex justify-end bg-white mt-[20px]">
<pagination v-model="pager" @change="fetchTableList" />
</div>
<el-space>
<el-input :placeholder="`请输入${placeholder}`" v-model="searchForm.keyword" clearable @input="handleInputChange">
<template #prepend>
<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-select>
</template>
</el-input>
<el-select placeholder="请选择岗位名称" v-model="searchForm.postId" clearable @change="handleSelectChange">
<el-option v-for="option in positionOptions" :key="option.id" :label="option.name" :value="option.id" />
</el-select>
<el-select placeholder="请选择账号状态" v-model="searchForm.isDisable" clearable @change="handleSelectChange">
<el-option v-for="option in accountStatusOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
</el-space>
</div>
<ProTable ref="proTableRef" :columns="columns" :tableData="tableData" :loading="loading" :maxHeight="530">
<template #username="{ row }">
<el-space>
<div class="w-[50px] h-[50px] bg-primary rounded-[6px] text-white flex-row-center-center">{{ row.username }}</div>
<span>{{ row.username }}</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 #isDisable="{ row }">
<el-switch
v-model="row.isDisable"
:active-value="isDisabledEnum.NO"
:inactive-value="isDisabledEnum.YES"
:before-change="() => handleStatusChange(row)"
/>
</template>
<template #operation="{ row }">
<el-button link type="primary" @click="handleEdit(row)"></el-button>
<el-button link type="primary" @click="setGroupLeader(row)">{{ btnText(row.groupLeader) }}</el-button>
<el-button link type="primary" @click="handleDelete(row)"></el-button>
</template>
</ProTable>
<div class="flex justify-end bg-white mt-[20px]">
<pagination v-model="pager" @change="fetchTableList" />
</div>
</template>
</div>
<account-dialog ref="accountDialogRef" @confirm-after="confirmAfter" />
<edit-popup v-if="showEdit" ref="editRef" @success="fetchOrganizationList" @close="showEdit = false" />
</template>
<script setup lang="ts">
@ -63,8 +69,11 @@ import useHandleData from '@/hooks/useHandleData'
import feedback from '@/utils/feedback'
import { Plus, Delete } from '@element-plus/icons-vue'
import accountDialog from '../components/account-list/account-dialog.vue'
import emptyContent from '../components/account-list/empty-content.vue'
import editPopup from '@/views/account_center/organization/edit.vue'
import { useDebounceFn } from '@vueuse/core'
import { subAccountDelete, subAccountEdit, subAccountList } from '@/api/account_center/sub_account'
import { subAccountDelete, subAccountEdit, subAccountList, subAccountUpdateStatus } from '@/api/account_center/sub_account'
import { StatusEnum, groupLeaderEnum, isDisabledEnum } from '@/enums'
import { omit } from 'lodash-es'
@ -74,7 +83,7 @@ const props = defineProps({
default: () => ({})
}
})
const emit = defineEmits(['refreshSubAccountNumber'])
const emit = defineEmits(['refreshSubAccountNumber', 'fetchOrganizationList'])
const pager = ref({
page: 1,
@ -107,8 +116,17 @@ const columns = reactive([
const isGroupLeader = computed(() => (groupLeader: number) => groupLeader === groupLeaderEnum.YES)
const btnText = computed(() => (groupLeader: number) => isGroupLeader.value(groupLeader) ? '取消设为组长' : '设置组长')
const isDisabledAdd = computed(() => props.curOrganization.status == StatusEnum.Stop)
const showEmptyComponent = computed(() => {
const { ancestors } = props.curOrganization
const ancestorArray = ancestors ? ancestors.split(',') : []
const level = ancestorArray.length - 1
return level < 2 && !props.curOrganization?.children
})
const fetchTableList = async (nodeId?: number) => {
console.log('fetchTableList')
loading.value = true
tableData.value = []
try {
const newParams = omit(searchForm.value, ['keyword'])
const params = {
@ -122,8 +140,20 @@ const fetchTableList = async (nodeId?: number) => {
}
const handleStatusChange = row => {
console.log(row)
return new Promise(resolve => {
resolve(true)
const { id, isDisable } = row
return new Promise(async resolve => {
try {
const params = {
id,
isDisable: isDisable == isDisabledEnum.YES ? isDisabledEnum.NO : isDisabledEnum.YES
}
const msg = isDisable == isDisabledEnum.YES ? '启用' : '停用'
await feedback.confirm(`确定${msg}该账号?`)
await subAccountUpdateStatus(params)
feedback.msgSuccess(`${msg}成功`)
resolve(true)
fetchTableList()
} catch (error) {}
})
}
//
@ -204,6 +234,18 @@ const handleInputChange = useDebounceFn(() => {
const handleSelectChange = () => {
fetchTableList()
}
const showEdit = ref(false)
const editRef = ref<InstanceType<typeof editPopup>>()
const handleOrganization = async (mode: 'add' | 'edit') => {
const { id } = props.curOrganization
showEdit.value = true
await nextTick()
if (id) editRef.value?.setFormData({ pid: id })
editRef.value?.open(mode)
}
const fetchOrganizationList = () => {
emit('fetchOrganizationList', props.curOrganization)
}
const positionOptions = ref<any[]>([])
onMounted(() => {

View File

@ -1,5 +1,5 @@
<template>
<div class="flex flex-col w-[240px] border-r-solid-light2">
<div class="flex flex-col w-[240px] min-w-[240px] border-r-solid-light2">
<structure @handle-organization="handleOrganization" />
<organization-tree
ref="organizationTreeRef"
@ -33,7 +33,7 @@ defineProps({
default: () => ({})
}
})
const emit = defineEmits(['setSelectedNode', 'fetchTableList'])
const emit = defineEmits(['setSelectedNode', 'fetchTableList', 'getOrganizationList'])
const data = ref<Tree[]>([])
const loading = ref(false)
const organizationTreeRef = ref<InstanceType<typeof organizationTree>>()
@ -72,15 +72,17 @@ const handleOrganization = async (mode: 'add' | 'edit', data?: any) => {
mode == 'edit' && editRef.value?.getDetail(data)
}
const handleDelete = (data?: Tree) => {
const props = { name: data?.name }
ElMessageBox({
title: '温馨提示',
showCancelButton: true,
message: () => {
return h(MessageBoxContent)
return h(MessageBoxContent, props)
},
callback: (value: string) => {
if (value == 'confirm') {
organzationDelete({ id: data?.id }).then(() => {
const ids = [data?.id]
organzationDelete({ ids }).then(() => {
feedback.msgSuccess('删除成功')
fetchOrganizationList()
})
@ -89,6 +91,9 @@ const handleDelete = (data?: Tree) => {
}
})
}
defineExpose({
fetchOrganizationList
})
</script>
<style scoped lang="scss"></style>

View File

@ -1,7 +1,7 @@
<template>
<div class="flex-1 w-full chart-card">
<card title="线索转客户统计">
<v-charts ref="chartRef" v-loading="loading" style="height: 350px" :autoresize="true" :option="option" />
<v-charts ref="chartRef" v-loading="loading" style="height: 350px" :autoresize="true" />
</card>
</div>
</template>
@ -19,7 +19,7 @@ function createBarSeries(data, name, field) {
}
}
function createLineSeries(data, name) {
function createLineSeries(data, name, field) {
return {
name,
type: 'line',
@ -28,7 +28,7 @@ function createLineSeries(data, name) {
return value as number
}
},
data: data.map(item => item.client),
data: data.map(item => item[field]),
yAxisIndex: 1
}
}
@ -36,48 +36,6 @@ const loading = ref(false)
const data = ref([])
const xAxisData = ref([])
const series = [
createBarSeries(data.value, '线索数', 'clueNumber'),
createBarSeries(data.value, '线索转客户数', 'client'),
createLineSeries(data.value, '线索转客户率')
]
const option = ref({
color: ['#0E66FB', '#FAC858', '#96B2D9'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
toolbox: {},
legend: {},
xAxis: [
{
type: 'category',
data: xAxisData.value
}
],
yAxis: [
{
type: 'value',
axisLabel: {
formatter: '{value}'
}
},
{
type: 'value',
axisLabel: {
formatter: '{value}'
}
}
],
series
})
const chartRef = ref()
const fetchData = async (payload: IForm) => {
loading.value = true
@ -94,30 +52,71 @@ const fetchData = async (payload: IForm) => {
}
})
xAxisData.value = result.map((item: any) => item.organizationName)
if (series[2].data.length === 0) {
chartRef.value.setOption({
title: {
text: '暂无数据',
x: 'center',
y: 'center'
},
xAxis: [
{
type: 'category',
data: [],
splitLine: {
show: false
},
axisLine: {
show: false
}
chartRef.value.setOption({
color: ['#0E66FB', '#FAC858', '#96B2D9'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
],
legend: {
show: false
}
})
}
},
toolbox: {},
legend: {},
title: {
text: ''
},
xAxis: [
{
type: 'category',
data: xAxisData.value
}
],
yAxis: [
{
type: 'value',
axisLabel: {
formatter: '{value}'
}
},
{
type: 'value',
axisLabel: {
formatter: '{value}'
}
}
],
series: [
createBarSeries(data.value, '线索数', 'clueNumber'),
createBarSeries(data.value, '线索转客户数', 'client'),
createLineSeries(data.value, '线索转客户率', 'rate')
]
})
} else {
chartRef.value.setOption({
legend: {},
title: {
text: '暂无数据',
x: 'center',
y: 'center'
},
xAxis: [
{
type: 'category',
data: [],
splitLine: {
show: false
},
axisLine: {
show: false
}
}
],
yAxis: [],
series: [createBarSeries([], '', ''), createBarSeries([], '', ''), createLineSeries([], '', '')]
})
}
} catch (error) {}
loading.value = false

View File

@ -1,7 +1,7 @@
<template>
<div class="flex-1 w-full chart-card">
<card title="成交客户统计">
<v-charts style="height: 350px" ref="chartRef" v-loading="loading" :autoresize="true" :option="option" />
<v-charts style="height: 350px" ref="chartRef" v-loading="loading" :autoresize="true" />
</card>
</div>
</template>
@ -21,38 +21,6 @@ function createBarSeries(data, name, field) {
const chartRef = ref()
const data = ref<any[]>([])
const xAxisData = ref([])
const series = [createBarSeries(data.value, '客户数', 'clientCount'), createBarSeries(data.value, '成交客户数', 'transactionClient')]
const option = ref({
color: ['#0E66FB', '#96B2D9'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
toolbox: {},
legend: {},
xAxis: [
{
type: 'category',
data: xAxisData.value
}
],
yAxis: [
{
type: 'value',
axisLabel: {
formatter: '{value}'
}
}
],
series
})
const loading = ref(false)
const fetchData = async (payload: IForm) => {
@ -68,8 +36,7 @@ const fetchData = async (payload: IForm) => {
}
})
xAxisData.value = result.map((item: any) => item.organizationName)
const allZero = data.value.every(item => item.clientCount == 0 && item.transactionClient == 0)
if (allZero) {
if (!result.length) {
chartRef.value.setOption({
title: {
text: '暂无数据',
@ -90,7 +57,41 @@ const fetchData = async (payload: IForm) => {
],
legend: {
show: false
}
},
series: [createBarSeries([], '', ''), createBarSeries([], '', '')]
})
} else {
chartRef.value.setOption({
color: ['#0E66FB', '#96B2D9'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
toolbox: {},
legend: {},
title: {
text: ''
},
xAxis: [
{
type: 'category',
data: xAxisData.value
}
],
yAxis: [
{
type: 'value',
axisLabel: {
formatter: '{value}'
}
}
],
series: [createBarSeries(data.value, '客户数', 'clientCount'), createBarSeries(data.value, '成交客户数', 'transactionClient')]
})
}
} catch (error) {}

View File

@ -22,7 +22,8 @@ const loading = ref(false)
const dataOverview = ref<any[]>([])
const dataMap: Record<string, string> = {
followUpRecord: '新增跟进记录(个)',
newCustomer: '新增客户(个)',
unclaimedQuantity: '未领取(个)',
// newCustomer: '',
transactionClient: '成交客户(个)',
convertingClient: '转化中客户(个)',
exceptionPending: '异常待处理(个)',

View File

@ -8,6 +8,7 @@
default-expand-all
:data="organizationList"
:render-after-expand="false"
check-strictly
/>
<div>
<daterange-picker