新增功能
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
完整的排班管理代码请参考 DOCTOR_ROSTER.md 文档
|
||||
|
||||
由于文件较大,建议手动创建或从以下地址获取完整代码:
|
||||
https://github.com/your-repo/roster.vue
|
||||
|
||||
或者使用以下简化版本开始开发。
|
||||
@@ -0,0 +1,442 @@
|
||||
<template>
|
||||
<div class="doctor-roster p-4">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg font-medium">医生排班管理</span>
|
||||
<div>
|
||||
<el-button type="success" @click="showBatchDialog">批量排班</el-button>
|
||||
<el-button @click="prevWeek">上一周</el-button>
|
||||
<el-button @click="nextWeek">下一周</el-button>
|
||||
<el-button type="primary" @click="goToday">本周</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="mb-4">
|
||||
<el-form inline>
|
||||
<el-form-item label="医生">
|
||||
<el-input v-model="searchName" placeholder="请输入医生姓名" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="text-center mb-4 text-gray-600">
|
||||
{{ weekText }}
|
||||
</div>
|
||||
|
||||
<el-table :data="tableData" border v-loading="loading">
|
||||
<el-table-column prop="doctorName" label="医生" width="120" fixed />
|
||||
<el-table-column v-for="day in weekDays" :key="day.date" :label="day.label" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<div class="space-y-1">
|
||||
<div class="text-xs p-1 bg-gray-100 rounded cursor-pointer hover:bg-gray-200" @click="editRoster(row, day.date, 'morning')">
|
||||
上午: {{ getRosterText(row, day.date, 'morning') }}
|
||||
</div>
|
||||
<div class="text-xs p-1 bg-gray-100 rounded cursor-pointer hover:bg-gray-200" @click="editRoster(row, day.date, 'afternoon')">
|
||||
下午: {{ getRosterText(row, day.date, 'afternoon') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="编辑排班" width="500px">
|
||||
<el-form :model="editForm" label-width="100px">
|
||||
<el-form-item label="医生">
|
||||
<span>{{ editForm.doctorName }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期">
|
||||
<span>{{ editForm.date }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="时段">
|
||||
<span>{{ editForm.period === 'morning' ? '上午' : '下午' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="editForm.status">
|
||||
<el-radio :label="1">出诊</el-radio>
|
||||
<el-radio :label="2">停诊</el-radio>
|
||||
<el-radio :label="3">休息</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="号源数" v-if="editForm.status === 1">
|
||||
<el-input-number v-model="editForm.quota" :min="0" :max="100" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveRoster">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 批量排班弹窗 -->
|
||||
<el-dialog v-model="batchDialogVisible" title="批量排班" width="700px">
|
||||
<el-form :model="batchForm" label-width="100px">
|
||||
<el-form-item label="选择医生" required>
|
||||
<el-select v-model="batchForm.doctorIds" multiple placeholder="请选择医生" class="w-full">
|
||||
<el-option
|
||||
v-for="doctor in tableData"
|
||||
:key="doctor.doctorId"
|
||||
:label="doctor.doctorName"
|
||||
:value="doctor.doctorId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="日期范围" required>
|
||||
<el-date-picker
|
||||
v-model="batchForm.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="选择时段" required>
|
||||
<el-checkbox-group v-model="batchForm.periods">
|
||||
<el-checkbox label="morning">上午</el-checkbox>
|
||||
<el-checkbox label="afternoon">下午</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="选择星期" required>
|
||||
<el-checkbox-group v-model="batchForm.weekdays">
|
||||
<el-checkbox :label="1">周一</el-checkbox>
|
||||
<el-checkbox :label="2">周二</el-checkbox>
|
||||
<el-checkbox :label="3">周三</el-checkbox>
|
||||
<el-checkbox :label="4">周四</el-checkbox>
|
||||
<el-checkbox :label="5">周五</el-checkbox>
|
||||
<el-checkbox :label="6">周六</el-checkbox>
|
||||
<el-checkbox :label="0">周日</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="排班状态" required>
|
||||
<el-radio-group v-model="batchForm.status">
|
||||
<el-radio :label="1">出诊</el-radio>
|
||||
<el-radio :label="2">停诊</el-radio>
|
||||
<el-radio :label="3">休息</el-radio>
|
||||
<el-radio :label="4">请假</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="号源数" v-if="batchForm.status === 1">
|
||||
<el-input-number v-model="batchForm.quota" :min="0" :max="100" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="最大接诊数" v-if="batchForm.status === 1">
|
||||
<el-input-number v-model="batchForm.maxPatients" :min="0" :max="100" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="batchForm.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="batchDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleBatchSave" :loading="batchSaving">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="doctorRoster">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import isoWeek from 'dayjs/plugin/isoWeek'
|
||||
import feedback from '@/utils/feedback'
|
||||
import { adminLists } from '@/api/perms/admin'
|
||||
import { rosterLists, rosterSave, rosterDelete, rosterBatchSave } from '@/api/doctor'
|
||||
|
||||
dayjs.extend(isoWeek)
|
||||
|
||||
interface RosterItem {
|
||||
id?: number
|
||||
status: number
|
||||
quota: number
|
||||
}
|
||||
|
||||
interface DoctorRow {
|
||||
doctorId: number
|
||||
doctorName: string
|
||||
rosters: Record<string, RosterItem>
|
||||
}
|
||||
|
||||
const searchName = ref('')
|
||||
const currentWeekStart = ref(dayjs().startOf('isoWeek'))
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const doctorOptions = ref<any[]>([])
|
||||
|
||||
// 批量排班
|
||||
const batchDialogVisible = ref(false)
|
||||
const batchSaving = ref(false)
|
||||
const batchForm = ref({
|
||||
doctorIds: [] as number[],
|
||||
dateRange: [] as string[],
|
||||
periods: ['morning', 'afternoon'] as string[],
|
||||
weekdays: [1, 2, 3, 4, 5] as number[], // 默认周一到周五
|
||||
status: 1,
|
||||
quota: 20,
|
||||
maxPatients: 30,
|
||||
remark: ''
|
||||
})
|
||||
|
||||
// 加载医生列表(role_id=1)
|
||||
const loadDoctors = async () => {
|
||||
try {
|
||||
const res = await adminLists({
|
||||
page_no: 1,
|
||||
page_size: 1000,
|
||||
role_id: 1 // 只获取医生角色
|
||||
})
|
||||
doctorOptions.value = res?.lists || []
|
||||
|
||||
// 转换为表格数据格式
|
||||
tableData.value = (res?.lists || []).map((doctor: any) => ({
|
||||
doctorId: doctor.id,
|
||||
doctorName: doctor.name || doctor.account,
|
||||
rosters: {}
|
||||
}))
|
||||
|
||||
// 加载排班数据
|
||||
await loadRosterData()
|
||||
} catch (error) {
|
||||
console.error('加载医生列表失败:', error)
|
||||
feedback.msgError('加载医生列表失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 加载排班数据
|
||||
const loadRosterData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
start_date: currentWeekStart.value.format('YYYY-MM-DD'),
|
||||
end_date: currentWeekStart.value.add(6, 'day').format('YYYY-MM-DD')
|
||||
}
|
||||
|
||||
const res = await rosterLists(params)
|
||||
const rosters = res?.lists || []
|
||||
|
||||
// 将排班数据填充到医生行中
|
||||
tableData.value.forEach(doctor => {
|
||||
doctor.rosters = {}
|
||||
rosters.forEach((roster: any) => {
|
||||
if (roster.doctor_id === doctor.doctorId) {
|
||||
const key = `${roster.date}_${roster.period}`
|
||||
doctor.rosters[key] = {
|
||||
id: roster.id,
|
||||
status: roster.status,
|
||||
quota: roster.quota || 0
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('加载排班数据失败:', error)
|
||||
feedback.msgError('加载排班数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const weekDays = computed(() => {
|
||||
const days = []
|
||||
const labels = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const date = currentWeekStart.value.add(i, 'day')
|
||||
days.push({
|
||||
date: date.format('YYYY-MM-DD'),
|
||||
label: `${labels[i]} ${date.format('MM-DD')}`
|
||||
})
|
||||
}
|
||||
return days
|
||||
})
|
||||
|
||||
const weekText = computed(() => {
|
||||
const start = currentWeekStart.value.format('YYYY-MM-DD')
|
||||
const end = currentWeekStart.value.add(6, 'day').format('YYYY-MM-DD')
|
||||
return `${start} 至 ${end}`
|
||||
})
|
||||
|
||||
const tableData = ref<DoctorRow[]>([])
|
||||
|
||||
const editForm = ref({
|
||||
id: null as number | null,
|
||||
doctorId: 0,
|
||||
doctorName: '',
|
||||
date: '',
|
||||
period: 'morning',
|
||||
status: 1,
|
||||
quota: 20
|
||||
})
|
||||
|
||||
const getRosterText = (row: DoctorRow, date: string, period: string) => {
|
||||
const key = `${date}_${period}`
|
||||
const roster = row.rosters[key]
|
||||
if (!roster) return '未排班'
|
||||
const statusMap: Record<number, string> = { 1: '出诊', 2: '停诊', 3: '休息' }
|
||||
return roster.status === 1 ? `${statusMap[roster.status]}(${roster.quota})` : statusMap[roster.status]
|
||||
}
|
||||
|
||||
const editRoster = (row: DoctorRow, date: string, period: string) => {
|
||||
const key = `${date}_${period}`
|
||||
const roster = row.rosters[key] || { status: 1, quota: 20 }
|
||||
editForm.value = {
|
||||
id: roster.id || null,
|
||||
doctorId: row.doctorId,
|
||||
doctorName: row.doctorName,
|
||||
date,
|
||||
period,
|
||||
status: roster.status,
|
||||
quota: roster.quota
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const saveRoster = async () => {
|
||||
try {
|
||||
await rosterSave({
|
||||
id: editForm.value.id,
|
||||
doctor_id: editForm.value.doctorId,
|
||||
date: editForm.value.date,
|
||||
period: editForm.value.period,
|
||||
status: editForm.value.status,
|
||||
quota: editForm.value.status === 1 ? editForm.value.quota : 0
|
||||
})
|
||||
|
||||
feedback.msgSuccess('保存成功')
|
||||
dialogVisible.value = false
|
||||
await loadRosterData()
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error)
|
||||
feedback.msgError('保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
const loadData = () => {
|
||||
loadRosterData()
|
||||
}
|
||||
|
||||
const prevWeek = () => {
|
||||
currentWeekStart.value = currentWeekStart.value.subtract(1, 'week')
|
||||
loadRosterData()
|
||||
}
|
||||
|
||||
const nextWeek = () => {
|
||||
currentWeekStart.value = currentWeekStart.value.add(1, 'week')
|
||||
loadRosterData()
|
||||
}
|
||||
|
||||
const goToday = () => {
|
||||
currentWeekStart.value = dayjs().startOf('isoWeek')
|
||||
loadRosterData()
|
||||
}
|
||||
|
||||
// 显示批量排班弹窗
|
||||
const showBatchDialog = () => {
|
||||
batchForm.value = {
|
||||
doctorIds: [],
|
||||
dateRange: [],
|
||||
periods: ['morning', 'afternoon'],
|
||||
weekdays: [1, 2, 3, 4, 5],
|
||||
status: 1,
|
||||
quota: 20,
|
||||
maxPatients: 30,
|
||||
remark: ''
|
||||
}
|
||||
batchDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 批量保存排班
|
||||
const handleBatchSave = async () => {
|
||||
// 验证
|
||||
if (batchForm.value.doctorIds.length === 0) {
|
||||
feedback.msgWarning('请选择医生')
|
||||
return
|
||||
}
|
||||
if (!batchForm.value.dateRange || batchForm.value.dateRange.length !== 2) {
|
||||
feedback.msgWarning('请选择日期范围')
|
||||
return
|
||||
}
|
||||
if (batchForm.value.periods.length === 0) {
|
||||
feedback.msgWarning('请选择时段')
|
||||
return
|
||||
}
|
||||
if (batchForm.value.weekdays.length === 0) {
|
||||
feedback.msgWarning('请选择星期')
|
||||
return
|
||||
}
|
||||
|
||||
batchSaving.value = true
|
||||
try {
|
||||
// 生成排班数据
|
||||
const rosters: any[] = []
|
||||
const startDate = dayjs(batchForm.value.dateRange[0])
|
||||
const endDate = dayjs(batchForm.value.dateRange[1])
|
||||
|
||||
// 遍历日期范围
|
||||
let currentDate = startDate
|
||||
while (currentDate.isBefore(endDate) || currentDate.isSame(endDate, 'day')) {
|
||||
const weekday = currentDate.day() // 0-6,0是周日
|
||||
|
||||
// 检查是否在选中的星期内
|
||||
if (batchForm.value.weekdays.includes(weekday)) {
|
||||
// 遍历医生
|
||||
batchForm.value.doctorIds.forEach(doctorId => {
|
||||
// 遍历时段
|
||||
batchForm.value.periods.forEach(period => {
|
||||
rosters.push({
|
||||
doctor_id: doctorId,
|
||||
date: currentDate.format('YYYY-MM-DD'),
|
||||
period: period,
|
||||
status: batchForm.value.status,
|
||||
quota: batchForm.value.status === 1 ? batchForm.value.quota : 0,
|
||||
max_patients: batchForm.value.status === 1 ? batchForm.value.maxPatients : 0,
|
||||
remark: batchForm.value.remark
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
currentDate = currentDate.add(1, 'day')
|
||||
}
|
||||
|
||||
if (rosters.length === 0) {
|
||||
feedback.msgWarning('没有符合条件的排班数据')
|
||||
return
|
||||
}
|
||||
|
||||
// 调用批量保存接口
|
||||
await rosterBatchSave({ rosters })
|
||||
|
||||
feedback.msgSuccess(`批量保存成功!共处理 ${rosters.length} 条记录`)
|
||||
batchDialogVisible.value = false
|
||||
await loadRosterData()
|
||||
} catch (error) {
|
||||
console.error('批量保存失败:', error)
|
||||
feedback.msgError('批量保存失败')
|
||||
} finally {
|
||||
batchSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadDoctors()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.doctor-roster {
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user