统计查询接口

main
yanzai 3 days ago
parent 1ab1f6d267
commit 330ce24e4f

@ -382,5 +382,117 @@ class WorkMainController extends Controller
'AppointmentCount' => $appointmentCount
]);
}
//统计
public function AllCountTongJi()
{
$searchInfo = request('searchInfo');
// 基础查询
$baseQuery = DB::table('s_list')
->whereIn('list_status',[1,2,3])//0未预约 1预约 2预约后已经登记 3取消
->where(['s_list.is_del' => 0, 's_list.is_nullify' => 0]);
// 日期范围条件(如果有)
if (isset($searchInfo['dateRange']) && !empty($searchInfo['dateRange'])) {
$startDate = $searchInfo['dateRange'][0] . ' 00:00:00';
$endDate = $searchInfo['dateRange'][1] . ' 23:59:59';
$baseQuery->whereBetween('s_list.reservation_date', [$startDate, $endDate]);
}
//执行科室分布统计
$departmentCount = clone $baseQuery;
$departmentCount = $departmentCount
->select('s_department.department_name', DB::raw('count(*) as count'))
->leftJoin('s_department', 's_list.RISRAcceptDeptCode', '=', 's_department.department_number')
->groupBy('s_department.department_name')
->get();
//根据检查项目分类 统计
// 检查项目分类统计:克隆基础查询
$checkItemTypeCount = clone $baseQuery;
$checkItemTypeCount = $checkItemTypeCount
->join('s_check_item', 's_list.entrust_code', '=', 's_check_item.item_code')
->join('s_check_item_class', 's_check_item.item_class_id', '=', 's_check_item_class.id')
->select('s_check_item_class.id as class_id', 's_check_item_class.item_class_name as class_name', DB::raw('COUNT(*) as count'))
->groupBy('s_check_item_class.id', 's_check_item_class.item_class_name')
->get();
//时段统计
//分时间段统计 预约数量 预约率 空闲数量 空闲率 爽约数量 爽约率 爽约判断状态为1且超过预约时间)
//s_list 里的roster_id 关联s_source_roster_detail里的id,s_source_roster_detail_count里的roster_detail_id是s_source_roster_detail的id
//s_source_roster_detail_count里的max_total是该时间段最大预约人数,appointment_type_id是预约类型id,count是各个类型的最大可预约人数used_count是各个类型已预约人数
// ===== 时段统计:按小时,包含空闲,正确处理 max_total 和 appointment_count =====
// 步骤1对 s_source_roster_detail_count 按 roster_detail_id 去重(取任意一条 max_total
$uniqueCapacity = DB::table('s_source_roster_detail_count')
->select(
'roster_detail_id',
DB::raw('MAX(max_total) as total_max_capacity') // 所有行值相同MAX 即可
)
->groupBy('roster_detail_id');
// 步骤2主查询
$hourlyStats = DB::table('s_source_roster_detail as rd')
->joinSub($uniqueCapacity, 'capacity', function ($join) {
$join->on('rd.id', '=', 'capacity.roster_detail_id');
})
->leftJoin('s_list', function ($join) use ($searchInfo) {
$join->on('s_list.roster_id', '=', 'rd.id')
->where('s_list.is_del', '=', 0)
->where('s_list.is_nullify', '=', 0)
->whereIn('s_list.list_status', [1, 2, 3]);
// 限制 s_list 的预约日期范围
if (isset($searchInfo['dateRange']) && !empty($searchInfo['dateRange'])) {
$startDateTime = $searchInfo['dateRange'][0] . ' 00:00:00';
$endDateTime = $searchInfo['dateRange'][1] . ' 23:59:59';
$join->whereBetween('s_list.reservation_date', [$startDateTime, $endDateTime]);
}
});
// 筛选排班日期(基于 rd.date
if (isset($searchInfo['dateRange']) && !empty($searchInfo['dateRange'])) {
$startDate = $searchInfo['dateRange'][0];
$endDate = $searchInfo['dateRange'][1];
$hourlyStats->whereBetween('rd.date', [$startDate, $endDate]);
}
// 按小时分组统计
$hourlyStats = $hourlyStats
->select(
DB::raw('HOUR(rd.begin_time) as hour_start'),
DB::raw('COUNT(s_list.id) as appointment_count'),
DB::raw('SUM(CASE WHEN s_list.list_status = 1 AND s_list.reservation_date < NOW() THEN 1 ELSE 0 END) as no_show_count'),
DB::raw('SUM(capacity.total_max_capacity) as total_max_capacity')
)
->groupBy(DB::raw('HOUR(rd.begin_time)'))
->orderBy(DB::raw('HOUR(rd.begin_time)'))
->get();
// 步骤3构建最终结果数组
$finalTimeSlotStats = [];
foreach ($hourlyStats as $stat) {
$hour = (int)$stat->hour_start;
$appointmentCount = (int)$stat->appointment_count;
$noShowCount = (int)$stat->no_show_count;
$maxTotal = (int)$stat->total_max_capacity;
// 防止除零
$appointmentRate = $maxTotal > 0 ? round($appointmentCount / $maxTotal, 2) : 0;
$idleCount = max(0, $maxTotal - $appointmentCount);
$idleRate = $maxTotal > 0 ? round($idleCount / $maxTotal, 2) : 0;
$noShowRate = $appointmentCount > 0 ? round($noShowCount / $appointmentCount, 2) : 0;
$finalTimeSlotStats[] = [
'hour' => $hour,
'time_slot' => sprintf('%02d:00-%02d:00', $hour, $hour + 1),
'appointment_count' => $appointmentCount,
'appointment_rate' => $appointmentRate,
'idle_count' => $idleCount,
'idle_rate' => $idleRate,
'no_show_count' => $noShowCount,
'no_show_rate' => $noShowRate,
'max_total' => $maxTotal,
];
}
return \Yz::Return(true, '查询成功', ['department_count' => $departmentCount, 'check_item_type_count' => $checkItemTypeCount, 'time_slot_stats' => $finalTimeSlotStats]);
}
}

@ -104,6 +104,7 @@ Route::group(['middleware'=>['checktoken','log'],'prefix'=>'v1'],function () {
Route::post('admin/PlanTongJi','App\Http\Controllers\API\Admin\YeWu\PlanListController@TongJi');//计划统计
Route::post('admin/BlackListGetList','App\Http\Controllers\API\Admin\YeWu\BlackListController@GetList');
Route::post('admin/BlackListDelete','App\Http\Controllers\API\Admin\YeWu\BlackListController@Delete');
Route::post('admin/AllCountTongJi','App\Http\Controllers\API\Admin\YeWu\WorkMainController@AllCountTongJi');
});
//暂时不加权限

@ -369,4 +369,7 @@ export const adminBlackListDelete = (data = {}) => {
export const adminBlackListGetList = (data = {}) => {
return axios({ url: import.meta.env.VITE_APP_API + 'v1/admin/BlackListGetList', data: data })
}
export const AdminAllCountTongJi = (data = {}) => {
return axios({ url: import.meta.env.VITE_APP_API + 'v1/admin/AllCountTongJi', data: data })
}

@ -1,5 +1,5 @@
<template>
<div class="report-container">
<div class="report-container" v-loading="loading">
<el-page-header icon="" @back="goBack" content="医院预约报表中心">
<template #title>
<span></span>
@ -7,11 +7,11 @@
</el-page-header>
<el-row style="margin-top: 20px;margin-bottom: -30px;">
<el-form-item>
<el-date-picker style="margin-left: 8px; width: 300px"
<el-date-picker style="margin-left: 8px; width: 300px" v-model="searchInfo.dateRange"
type="daterange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间"
value-format="YYYY-MM-DD" />
</el-form-item>
<el-button style="margin-left: 10px;">搜索</el-button>
<el-button style="margin-left: 10px;" @click="GetTongJi"></el-button>
<!-- <el-button type="primary" @click="Add()" style="margin-left: 10px;">添加</el-button> -->
</el-row>
<el-tabs v-model="activeTab" type="card" style="margin-top: 20px" @tab-change="handleTabChange">
@ -43,13 +43,13 @@
<div class="card-header">预约时间段分布</div>
</template>
<el-table :data="techTableData" stripe height="300" style="width: 100%">
<el-table-column prop="type" label="时间段" />
<el-table-column prop="dept" label="预约数量" />
<el-table-column prop="patient" label="预约率" />
<el-table-column prop="scheduledTime" label="空闲数量" />
<el-table-column prop="status" label="空闲率" />
<el-table-column prop="shuangyueliang" label="爽约数量" />
<el-table-column prop="shuangyuelv" label="爽约率" />
<el-table-column prop="time_slot" label="时间段" />
<el-table-column prop="appointment_count" label="预约数量" />
<el-table-column prop="appointment_rate" label="预约率" />
<el-table-column prop="idle_count" label="空闲数量" />
<el-table-column prop="idle_rate" label="空闲率" />
<el-table-column prop="no_show_count" label="爽约数量" />
<el-table-column prop="no_show_rate" label="爽约率" />
</el-table>
</el-card>
</el-col>
@ -67,6 +67,9 @@
nextTick
} from 'vue'
import * as echarts from 'echarts'
import {
AdminAllCountTongJi
} from '@/api/api.js'
import {
ElMessage
} from 'element-plus'
@ -76,7 +79,8 @@
} from "vue-router"
const route = useRoute()
const router = useRouter()
let loading=ref(false);
let searchInfo=ref({})
const goBack = () => {
ElMessage.info('返回上一页')
}
@ -450,14 +454,32 @@
charts.forEach(chart => chart.resize())
})
}
const GetTongJi=()=>{
loading.value = true
AdminAllCountTongJi({
searchInfo:searchInfo.value
}).then(res => {
loading.value = false
if (res.status) {
techTableData.value=res.data.time_slot_stats
} else {
ElMessage.error(res.msg)
}
})
}
onMounted(() => {
const range = getDateRangeAroundToday(15)
searchInfo.value.dateRange=[range.start,range.end]
GetTongJi()
if (route.query.type) {
activeTab.value = route.query.type
}
//
initTabCharts(activeTab.value)
window.addEventListener('resize', handleResize)
})
onBeforeUnmount(() => {
@ -467,6 +489,32 @@
charts.forEach(chart => chart.dispose())
})
})
const getDateRangeAroundToday=(days = 15)=> {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
const todayStr = `${year}-${month}-${day}`;
// YYYY-MM-DD
function addDays(dateStr, delta) {
const d = new Date(dateStr);
d.setDate(d.getDate() + delta);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${dd}`;
}
const startDate = addDays(todayStr, -days);
const endDate = addDays(todayStr, days);
return {
start: startDate,
end: endDate
};
}
</script>
<style scoped>

Loading…
Cancel
Save