【招生用户端】 新增# 1、排名榜:查看更多;2、封装查询组件的业务逻辑

main
kaeery 2025-03-06 20:59:23 +08:00
parent 5110a2c267
commit 8271f5586d
7 changed files with 301 additions and 62 deletions

View File

@ -20,6 +20,11 @@ export function rankListApi(params?: any) {
export function completedCustomerListApi(params?: any) {
return request.get({ url: '/control/transactionCustomerStatistics', params })
}
// 排名榜查看更多
export function rankMoreListApi(params?: any) {
return request.get({ url: '/control/topAll', params })
}
// 获取所有组织以及人员信息
export function allUserListApi(params?: any) {
return request.get({ url: '/organization/getAllChildOrgInfo', params })

View File

@ -1,9 +1,12 @@
<template>
<el-dialog v-model="dialogVisible" :title="parameter.title" :width="parameter.width">
<template #header>
<slot name="header" />
</template>
<slot />
<template #footer>
<el-button @click="handleCancel"></el-button>
<el-button type="primary" @click="handleConfirm"></el-button>
<el-button type="primary" v-if="showConfirmButton" @click="handleConfirm"></el-button>
</template>
</el-dialog>
</template>
@ -14,6 +17,12 @@ export interface IParams {
width?: string | number
data: { [key: string]: any }
}
defineProps({
showConfirmButton: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['handleCancel', 'handleConfirm'])
const dialogVisible = ref(false)

View File

@ -20,6 +20,8 @@ import { applyForEdit } from '@/api/finance/withdraw'
import { useCreateModal } from './useCreateModal'
import { toast, formatFileSize } from '@/utils/util'
import { postLists } from '@/api/account_center/postion'
import { organzationLists } from '@/api/account_center/organization'
import { allUserListApi } from '@/api/workbench'
export interface CategoryProp {
id: number
@ -312,7 +314,7 @@ export function useUploadMoreAction() {
previewPdf
}
}
// 岗位数据
export function usePositionData() {
const positionOptions = ref<any[]>([])
const postId = ref()
@ -330,3 +332,59 @@ export function usePositionData() {
fetchPostionData
}
}
// 工作台数据
export function useWorkbench(callback?: (params: number) => void) {
const organizationList = ref([])
const userList = ref<any[]>([])
const defaultProps = {
label: 'name',
value: 'id'
}
const CascaderProps = {
label: 'name',
value: 'id'
}
const fetchOrganizationList = async () => {
try {
const result = await organzationLists()
organizationList.value = result ?? []
if (result.length > 0) callback && callback(result[0].id)
} catch (error) {}
}
const fetchAllUserList = async () => {
try {
const result = await allUserListApi()
const renamedData = renameFields(result)
userList.value = renamedData
} catch (error) {}
}
const renameFields = (data: any[]): any[] => {
return data.map(item => {
const newItem = { ...item }
if (newItem.organizationVoList) {
newItem.children = renameFields(newItem.organizationVoList)
delete newItem.organizationVoList
}
if (newItem.userVos) {
newItem.userVos.forEach(item => {
item.name = item.username
})
newItem.children = newItem.children ? [...newItem.children, ...newItem.userVos] : newItem.userVos
delete newItem.userVos
}
if (!newItem.children.length) {
newItem.disabled = true
}
return newItem
})
}
return {
defaultProps,
CascaderProps,
organizationList,
userList,
fetchOrganizationList,
fetchAllUserList
}
}

View File

@ -1,7 +1,10 @@
<template>
<div class="flex flex-col gap-[16px] bg-white h-full">
<div class="flex-row-between-center px-[20px] pt-[20px]">
<span class="text-[20px] font-bold">{{ title }}</span>
<template v-if="slotTitle">
<slot name="title" />
</template>
<span v-else class="text-[20px] font-bold">{{ title }}</span>
<slot name="right" />
</div>
<div class="flex-1 px-[20px] pb-[20px]">
@ -17,5 +20,7 @@ defineProps({
default: ''
}
})
const slots = useSlots()
const slotTitle = computed(() => slots?.title)
</script>
<style scoped></style>

View File

@ -0,0 +1,162 @@
<template>
<ProDialog ref="proDialogRef" :showConfirmButton="false" @handle-cancel="handleCancel">
<template #header>
<div class="flex gap-[40px] flex-1 mb-[6px]">
<div class="item relative" v-for="item in positionOptions" :key="item.id" @click="postId = item.id">
<span :class="{ active: postId == item.id }">{{ item.name + '业绩排名榜' }}</span>
</div>
</div>
</template>
<div class="flex mb-[16px]">
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" :inline="true">
<el-form-item label="">
<el-tree-select
v-model="queryParams.organizationId"
:props="defaultProps"
default-expand-all
:data="organizationList"
:render-after-expand="false"
/>
</el-form-item>
<el-form-item label="">
<daterange-picker
type="datetimerange"
format="YYYY-MM-DD HH:mm:ss"
isDisabledDate
isDisabledHours
:shortcuts="shortcuts"
v-model:startTime="queryParams.createTimeStart"
v-model:endTime="queryParams.createTimeEnd"
/>
</el-form-item>
<el-form-item label="">
<el-cascader v-model="selectedUserId" :props="CascaderProps" :options="userList" @change="handleCascaderChange" />
</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>
</div>
<ProTable ref="proTableRef" :columns="columns" :tableData="tableData" :loading="loading" :maxHeight="530"></ProTable>
<div class="flex justify-end mt-[10px]">
<pagination v-model="pager" @change="fetchData" layout="total, prev, pager, next, jumper" class="mb-[10px]" />
</div>
</ProDialog>
</template>
<script setup lang="ts">
import ProDialog, { type IParams } from '@/components/ProDialog/index.vue'
import { usePositionData, useWorkbench } from '@/hooks/useCommon'
import { rankMoreListApi } from '@/api/workbench'
import { getCurDate, shortcuts } from '@/utils/util'
const { positionOptions, postId, fetchPostionData } = usePositionData()
const pager = ref({
page: 1,
size: 10,
count: 0
})
const proDialogRef = ref<InstanceType<typeof ProDialog>>()
const loading = ref(false)
const proTableRef = ref()
const tableData = ref<any[]>([])
const columns = reactive([
{ prop: 'rank', label: '排名' },
{ prop: 'username', label: '账号名称' },
{ prop: 'clueCount', label: '总数' }
])
const queryParams = ref()
const initialVal = ref()
const { defaultProps, CascaderProps, organizationList, userList, fetchOrganizationList, fetchAllUserList } = useWorkbench(id => {
initialVal.value = id
})
const selectedUserId = ref([])
const openDialog = async (params: IParams) => {
queryParams.value = params.data
await fetchPostionData()
fetchOrganizationList()
fetchAllUserList()
fetchData()
proDialogRef.value?.openDialog(params)
}
const fetchData = async () => {
loading.value = true
try {
const newParams = {
...queryParams.value,
post: postId.value ?? queryParams.value.post
}
const result = await rankMoreListApi(newParams)
tableData.value = result.lists ?? []
pager.value = {
page: result.pageNo,
size: result.pageSize,
count: result.count
}
} catch (error) {}
loading.value = false
}
const handleCancel = (callback: () => void) => {
callback()
}
watch(
() => postId.value,
(newVal, oldVal) => {
if (newVal !== oldVal) {
fetchData()
}
}
)
const resetPage = () => {
fetchData()
}
const resetParams = () => {
queryParams.value = {
organizationId: initialVal.value,
createTimeStart: getCurDate('start').toString(),
createTimeEnd: getCurDate('end').toString(),
userId: ''
}
selectedUserId.value = []
fetchData()
}
const handleCascaderChange = value => {
const lastVal = value[value.length - 1]
queryParams.value = {
...queryParams.value,
userId: lastVal.toString()
}
}
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.item {
position: relative;
cursor: pointer;
> span {
&.active {
position: relative;
color: var(--el-color-primary);
&::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 46px;
height: 3px;
border-radius: 3px;
background-color: var(--el-color-primary);
}
}
}
.active {
}
}
</style>

View File

@ -1,7 +1,17 @@
<template>
<div class="rank flex-1">
<card title="TOP5">
<template #title>
<div class="flex gap-[40px] title flex-1 mb-[6px]">
<div class="item relative" v-for="item in positionOptions" :key="item.id" @click="postId = item.id">
<span :class="{ active: postId == item.id }">{{ item.name + 'TOP5' }}</span>
</div>
</div>
</template>
<template #right>
<span class="text-primary cursor-pointer" @click="handleMore"></span>
</template>
<!-- <template #right>
<div class="positions">
<span
class="default"
@ -13,24 +23,26 @@
{{ item.name }}
</span>
</div>
</template>
<template v-if="tableData.length > 0">
<el-table :data="tableData" v-loading="loading">
</template> -->
<template v-if="!loading && tableData.length > 0">
<el-table :data="tableData" v-loading="loading" style="min-height: 300px">
<el-table-column prop="rank" label="排名" width="70" />
<el-table-column prop="username" label="负责人" width="180" />
<el-table-column prop="clueCount" label="总数" />
</el-table>
</template>
<template v-else>
<template v-if="!loading && tableData.length == 0">
<el-empty description="暂无数据" />
</template>
</card>
</div>
<rankListDialog ref="rankListDialogRef" />
</template>
<script setup lang="ts">
import { rankListApi } from '@/api/workbench'
import card from './card.vue'
import rankListDialog from './rank-list-dialog.vue'
import type { IForm } from '../index.vue'
import { usePositionData } from '@/hooks/useCommon'
@ -38,8 +50,10 @@ const { positionOptions, postId, fetchPostionData } = usePositionData()
const emit = defineEmits(['refresh'])
const rankListDialogRef = ref<InstanceType<typeof rankListDialog>>()
const tableData = ref([])
const loading = ref(false)
const queryParams = ref()
const fetchData = async (payload: IForm) => {
if (!positionOptions.value.length) await fetchPostionData()
if (tableData.value.length > 0) tableData.value = []
@ -50,12 +64,18 @@ const fetchData = async (payload: IForm) => {
post: postId.value
}
const result = await rankListApi(newPayload)
console.log(result)
tableData.value = result ?? []
queryParams.value = newPayload //
} catch (error) {}
loading.value = false
}
const handleMore = () => {
rankListDialogRef.value?.openDialog({
title: '排行榜',
width: 1100,
data: { ...queryParams.value }
})
}
watch(
() => postId.value,
(newVal, oldVal) => {
@ -71,6 +91,29 @@ defineExpose({
<style scoped lang="scss">
.rank {
min-height: 350px;
.item {
position: relative;
cursor: pointer;
> span {
&.active {
position: relative;
color: var(--el-color-primary);
&::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 46px;
height: 3px;
border-radius: 3px;
background-color: var(--el-color-primary);
}
}
}
.active {
}
}
.positions {
display: flex;
cursor: pointer;

View File

@ -31,20 +31,11 @@
</template>
<script setup lang="ts">
import { organzationLists } from '@/api/account_center/organization'
import { shortcuts } from '@/utils/util'
import type { PropType } from 'vue'
import type { IForm } from '../index.vue'
import { allUserListApi } from '@/api/workbench'
import { useWorkbench } from '@/hooks/useCommon'
import { shortcuts } from '@/utils/util'
const defaultProps = {
label: 'name',
value: 'id'
}
const CascaderProps = {
label: 'name',
value: 'id'
}
const props = defineProps({
queryParams: {
type: Object as PropType<IForm>,
@ -65,6 +56,14 @@ const localValue = computed({
emit('update:queryParams', newValue)
}
})
const { defaultProps, CascaderProps, organizationList, userList, fetchOrganizationList, fetchAllUserList } = useWorkbench(id => {
localValue.value = {
...localValue.value,
organizationId: id
}
emit('fectAllData', id)
})
const selectedUserId = ref([])
const resetParams = () => {
@ -74,48 +73,6 @@ const resetParams = () => {
const resetPage = () => {
emit('resetPage')
}
const organizationList = ref([])
const userList = ref<any[]>([])
const fetchOrganizationList = async () => {
try {
const result = await organzationLists()
organizationList.value = result ?? []
if (result.length > 0) {
localValue.value = {
...localValue.value,
organizationId: result[0].id
}
emit('fectAllData', result[0].id)
}
} catch (error) {}
}
const fetchAllUserList = async () => {
try {
const result = await allUserListApi()
const renamedData = renameFields(result)
userList.value = renamedData
} catch (error) {}
}
const renameFields = (data: any[]): any[] => {
return data.map(item => {
const newItem = { ...item }
if (newItem.organizationVoList) {
newItem.children = renameFields(newItem.organizationVoList)
delete newItem.organizationVoList
}
if (newItem.userVos) {
newItem.userVos.forEach(item => {
item.name = item.username
})
newItem.children = newItem.children ? [...newItem.children, ...newItem.userVos] : newItem.userVos
delete newItem.userVos
}
if (!newItem.children.length) {
newItem.disabled = true
}
return newItem
})
}
const handleCascaderChange = value => {
const lastVal = value[value.length - 1]
localValue.value = {