医生统计页面优化
This commit is contained in:
+274
-137
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<div class="statistics-page">
|
||||
<el-card shadow="never" class="!border-none">
|
||||
<!-- 筛选区域 -->
|
||||
<!-- 筛选区域卡片 -->
|
||||
<el-card class="mb-4" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">筛选条件</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :inline="true" :model="queryParams" class="filter-form">
|
||||
<el-form-item label="医生">
|
||||
<el-select
|
||||
@@ -48,123 +53,97 @@
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 统计表格 -->
|
||||
<el-card shadow="never" class="!border-none mt-4">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="statisticsList"
|
||||
border
|
||||
stripe
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: '600' }"
|
||||
>
|
||||
<el-table-column prop="doctor_name" label="医生姓名" width="120" fixed />
|
||||
|
||||
<el-table-column label="部门统计" min-width="250" fixed>
|
||||
<template #default="{ row }">
|
||||
<div class="dept-stats">
|
||||
<el-tag
|
||||
v-for="(item, index) in parseDeptStats(row.dept_stats)"
|
||||
:key="index"
|
||||
type="primary"
|
||||
size="small"
|
||||
class="stat-tag"
|
||||
>
|
||||
{{ item }}
|
||||
</el-tag>
|
||||
<span v-if="!row.dept_stats || row.dept_stats === '未分配'" class="text-gray-400">未分配</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="渠道统计" min-width="250">
|
||||
<template #default="{ row }">
|
||||
<div class="channel-stats">
|
||||
<el-tag
|
||||
v-for="(item, index) in parseChannelStats(row.channel_stats)"
|
||||
:key="index"
|
||||
type="success"
|
||||
size="small"
|
||||
class="stat-tag"
|
||||
>
|
||||
{{ item }}
|
||||
</el-tag>
|
||||
<span v-if="!row.channel_stats || row.channel_stats === '未设置'" class="text-gray-400">未设置</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="total_count" label="总诊单" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<span class="font-semibold text-blue-600">{{ row.total_count }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="diagnosis_count" label="诊单数" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip
|
||||
content="统计期内该医生挂号涉及的去重诊单数,用于计算成交率"
|
||||
placement="top"
|
||||
>
|
||||
<span class="text-gray-700">{{ row.diagnosis_count ?? 0 }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="deal_count" label="成交单" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip content="上述诊单中已开有效处方(未删除、未作废)的数量" placement="top">
|
||||
<el-tag type="primary" size="small">{{ row.deal_count ?? 0 }}</el-tag>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="deal_rate" label="成交率" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<span :class="getCompletionRateClass(Number(row.deal_rate ?? 0))">
|
||||
{{ row.deal_rate ?? 0 }}%
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="registered_count" label="已挂号" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info" size="small">{{ row.registered_count }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="completed_count" label="已完成" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="success" size="small">{{ row.completed_count }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="missed_count" label="已过号" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="warning" size="small">{{ row.missed_count }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="cancelled_count" label="已取消" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="danger" size="small">{{ row.cancelled_count }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="completion_rate" label="完成率" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<span :class="getCompletionRateClass(row.completion_rate)">
|
||||
{{ row.completion_rate }}%
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="time_range" label="统计时间" min-width="200" />
|
||||
|
||||
<template #empty>
|
||||
<el-empty description="暂无统计数据" />
|
||||
</template>
|
||||
</el-table>
|
||||
<!-- 统计数据卡片 -->
|
||||
<el-card v-loading="loading" class="mb-4" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">医生统计数据</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="statisticsList.length > 0">
|
||||
<el-table
|
||||
:data="statisticsList"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: '600' }"
|
||||
>
|
||||
<el-table-column prop="doctor_name" label="医生姓名" width="120" />
|
||||
|
||||
<!-- 部门和渠道状态明细 -->
|
||||
<el-table-column label="状态明细" min-width="700">
|
||||
<template #default="{ row }">
|
||||
<div class="status-overview">
|
||||
<!-- 部门状态 -->
|
||||
<div class="status-section">
|
||||
<h4 class="section-title">部门状态</h4>
|
||||
<div v-if="row.dept_status_details && row.dept_status_details.length > 0">
|
||||
<div class="dept-grid">
|
||||
<div
|
||||
v-for="(dept, index) in row.dept_status_details"
|
||||
:key="index"
|
||||
class="dept-item"
|
||||
>
|
||||
<div class="dept-header">
|
||||
<span class="dept-name">{{ dept.dept_name }}</span>
|
||||
<span class="dept-total">({{ getDeptTotalCount(dept) }})</span>
|
||||
</div>
|
||||
<div class="status-tags">
|
||||
<el-tag
|
||||
v-for="(count, status) in dept.statuses"
|
||||
:key="status"
|
||||
:type="getStatusType(status)"
|
||||
size="small"
|
||||
class="compact-tag"
|
||||
>
|
||||
{{ getStatusName(status) }}: {{ count }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="text-gray-400">暂无部门状态明细</span>
|
||||
</div>
|
||||
|
||||
<!-- 渠道状态 -->
|
||||
<div class="status-section">
|
||||
<h4 class="section-title">渠道状态</h4>
|
||||
<div v-if="row.channel_status_details && row.channel_status_details.length > 0">
|
||||
<div class="channel-grid">
|
||||
<div
|
||||
v-for="(channel, index) in row.channel_status_details"
|
||||
:key="index"
|
||||
class="channel-item"
|
||||
>
|
||||
<div class="channel-header">
|
||||
<span class="channel-name">{{ channel.channel_name }}</span>
|
||||
<span class="channel-total">({{ getChannelTotalCount(channel) }})</span>
|
||||
</div>
|
||||
<div class="status-tags">
|
||||
<el-tag
|
||||
v-for="(count, status) in channel.statuses"
|
||||
:key="status"
|
||||
:type="getStatusType(status)"
|
||||
size="small"
|
||||
class="compact-tag"
|
||||
>
|
||||
{{ getStatusName(status) }}: {{ count }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="text-gray-400">暂无渠道状态明细</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="time_range" label="统计时间" min-width="200" />
|
||||
</el-table>
|
||||
</div>
|
||||
<div v-else class="empty-data">
|
||||
<el-empty description="暂无统计数据" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -213,6 +192,17 @@ const getStatistics = async () => {
|
||||
|
||||
const res = await getDoctorStatistics(params)
|
||||
statisticsList.value = res.lists || []
|
||||
|
||||
// 为每个医生添加必要的属性
|
||||
statisticsList.value.forEach(item => {
|
||||
// 确保数据结构完整
|
||||
if (!item.dept_status_details) {
|
||||
item.dept_status_details = []
|
||||
}
|
||||
if (!item.channel_status_details) {
|
||||
item.channel_status_details = []
|
||||
}
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('获取统计数据错误:', error)
|
||||
ElMessage.error(error.msg || '获取统计数据失败')
|
||||
@@ -234,23 +224,38 @@ const handleReset = () => {
|
||||
getStatistics()
|
||||
}
|
||||
|
||||
const getCompletionRateClass = (rate: number) => {
|
||||
if (rate >= 80) return 'text-green-600 font-semibold'
|
||||
if (rate >= 60) return 'text-blue-600 font-semibold'
|
||||
if (rate >= 40) return 'text-orange-600 font-semibold'
|
||||
return 'text-red-600 font-semibold'
|
||||
// 计算部门总数量
|
||||
const getDeptTotalCount = (dept: any) => {
|
||||
if (!dept.statuses) return 0
|
||||
return Object.values(dept.statuses).reduce((total: number, count: number) => total + count, 0)
|
||||
}
|
||||
|
||||
// 解析部门统计字符串为数组
|
||||
const parseDeptStats = (stats: string) => {
|
||||
if (!stats || stats === '未分配') return []
|
||||
return stats.split(' ').filter(item => item.trim())
|
||||
// 计算渠道总数量
|
||||
const getChannelTotalCount = (channel: any) => {
|
||||
if (!channel.statuses) return 0
|
||||
return Object.values(channel.statuses).reduce((total: number, count: number) => total + count, 0)
|
||||
}
|
||||
|
||||
// 解析渠道统计字符串为数组
|
||||
const parseChannelStats = (stats: string) => {
|
||||
if (!stats || stats === '未设置') return []
|
||||
return stats.split(' ').filter(item => item.trim())
|
||||
// 获取状态名称
|
||||
const getStatusName = (status: string | number) => {
|
||||
const statusMap: Record<string | number, string> = {
|
||||
1: '已挂号',
|
||||
2: '已取消',
|
||||
3: '已完成',
|
||||
4: '已过号'
|
||||
}
|
||||
return statusMap[status] || `状态${status}`
|
||||
}
|
||||
|
||||
// 获取状态标签类型
|
||||
const getStatusType = (status: string | number) => {
|
||||
const typeMap: Record<string | number, string> = {
|
||||
1: 'info',
|
||||
2: 'danger',
|
||||
3: 'success',
|
||||
4: 'warning'
|
||||
}
|
||||
return typeMap[status] || 'default'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -262,23 +267,155 @@ onMounted(() => {
|
||||
<style scoped lang="scss">
|
||||
.statistics-page {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.filter-form {
|
||||
padding: 10px 0;
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dept-stats,
|
||||
.channel-stats {
|
||||
.status-overview {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.status-section {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 4px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.dept-grid, .channel-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.dept-item, .channel-item {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
border: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.dept-header, .channel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dept-name, .channel-name {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.dept-total, .channel-total {
|
||||
color: #606266;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-tag {
|
||||
font-weight: 500;
|
||||
.compact-tag {
|
||||
font-size: 11px !important;
|
||||
padding: 2px 8px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.empty-data {
|
||||
padding: 40px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background-color: #f5f7fa !important;
|
||||
font-weight: 600 !important;
|
||||
color: #606266 !important;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover) {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 卡片样式优化 */
|
||||
:deep(.el-card) {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1) !important;
|
||||
margin-bottom: 16px !important;
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
:deep(.el-card:hover) {
|
||||
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
/* 折叠面板样式优化 */
|
||||
:deep(.el-collapse-item__header) {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
padding: 10px 20px;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
:deep(.el-collapse-item__content) {
|
||||
padding: 10px 20px 15px !important;
|
||||
background-color: #f9f9f9;
|
||||
border-top: 1px solid #ebeef5;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
/* 表格单元格对齐 */
|
||||
:deep(.el-table__cell) {
|
||||
text-align: left !important;
|
||||
vertical-align: top !important;
|
||||
}
|
||||
|
||||
/* 确保折叠面板从顶部开始显示 */
|
||||
:deep(.el-table__row) {
|
||||
vertical-align: top !important;
|
||||
}
|
||||
|
||||
/* 去除默认的垂直居中样式 */
|
||||
:deep(.el-table__row > td) {
|
||||
vertical-align: top !important;
|
||||
}
|
||||
|
||||
.text-gray-400 {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user