Files
nursing-home/frontend/src/views/admin/Schedules.vue
王子琦 438eb0b635 add
2026-01-20 13:56:37 +08:00

245 lines
7.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="page-card">
<div style="display:flex; justify-content: space-between; align-items:center;">
<h3>排班管理</h3>
<el-button type="primary" @click="openCreate">新增排班</el-button>
</div>
<div class="schedule-toolbar">
<el-date-picker v-model="monthPicker" type="month" placeholder="选择月份" @change="handleMonthChange" />
<el-button @click="jumpToday">回到今天</el-button>
</div>
<el-row :gutter="16">
<el-col :span="16">
<el-calendar v-model="calendarDate" @input="handleDateChange">
<template slot="dateCell" slot-scope="{ data }">
<div class="calendar-cell">
<div class="cell-date">{{ data.day.split('-')[2] }}</div>
<div class="cell-badges">
<span v-for="item in getDaySchedules(data.day).slice(0, 2)" :key="item.id" class="badge">
{{ formatBadge(item) }}
</span>
<span v-if="getDaySchedules(data.day).length > 2" class="more">
+{{ getDaySchedules(data.day).length - 2 }}
</span>
</div>
</div>
</template>
</el-calendar>
</el-col>
<el-col :span="8">
<div class="schedule-panel">
<div class="schedule-title">当天排班{{ selectedDate }}</div>
<el-table :data="daySchedules" stripe>
<el-table-column label="护工" width="120">
<template slot-scope="scope">
{{ nurseName(scope.row.nurseId) }}
</template>
</el-table-column>
<el-table-column prop="shift" label="班次" width="90" />
<el-table-column prop="task" label="任务" />
<el-table-column label="操作" width="160" class-name="action-col">
<template slot-scope="scope">
<div class="action-inline">
<el-button size="mini" @click="openEdit(scope.row)">编辑</el-button>
<el-button size="mini" type="danger" @click="remove(scope.row)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
</el-col>
</el-row>
<el-dialog title="排班信息" :visible.sync="showDialog">
<el-form :model="form" label-width="120px">
<el-form-item label="护工">
<el-select v-model="form.nurseId" placeholder="选择护工">
<el-option v-for="nurse in nurses" :key="nurse.id" :label="nurse.name" :value="nurse.id" />
</el-select>
</el-form-item>
<el-form-item label="日期"><el-date-picker v-model="form.date" type="date"/></el-form-item>
<el-form-item label="班次">
<el-select v-model="form.shift" placeholder="选择班次">
<el-option label="早班" value="早班" />
<el-option label="中班" value="中班" />
<el-option label="晚班" value="晚班" />
<el-option label="夜班" value="夜班" />
</el-select>
</el-form-item>
<el-form-item label="任务"><el-input v-model="form.task"/></el-form-item>
</el-form>
<span slot="footer">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" @click="save">保存</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { schedulesByRange, scheduleCreate, scheduleUpdate, scheduleDelete, adminUsers } from "../../api";
import { formatDate } from "../../utils/date";
export default {
data() {
const today = new Date();
return {
calendarDate: today,
monthPicker: today,
selectedDate: formatDate(today),
daySchedules: [],
scheduleMap: {},
nurses: [],
nurseMap: {},
showDialog: false,
form: { id: null, nurseId: "", date: "", shift: "", task: "" }
};
},
created() {
this.loadMonth(this.monthPicker);
this.loadNurses();
},
methods: {
async loadNurses() {
try {
const res = await adminUsers("NURSE");
this.nurses = res.data.data;
const map = {};
this.nurses.forEach((nurse) => {
map[nurse.id] = nurse.name;
});
this.nurseMap = map;
} catch (e) {
this.$message.error(e.message || "加载护工失败");
}
},
nurseName(id) {
return this.nurseMap[id] || `护工${id}`;
},
formatBadge(item) {
const shift = item.shift || "班次";
const name = this.nurseName(item.nurseId);
return `${shift}·${name}`;
},
handleMonthChange() {
if (!this.monthPicker) return;
const firstDay = new Date(this.monthPicker.getFullYear(), this.monthPicker.getMonth(), 1);
this.calendarDate = firstDay;
this.loadMonth(this.monthPicker);
},
handleDateChange(date) {
this.selectedDate = formatDate(date);
this.daySchedules = this.getDaySchedules(this.selectedDate);
},
getDaySchedules(dayStr) {
return this.scheduleMap[dayStr] || [];
},
async loadMonth(date) {
const year = date.getFullYear();
const month = date.getMonth();
const start = new Date(year, month, 1);
const end = new Date(year, month + 1, 0);
try {
const res = await schedulesByRange(formatDate(start), formatDate(end));
const map = {};
res.data.data.forEach((item) => {
if (!map[item.date]) {
map[item.date] = [];
}
map[item.date].push(item);
});
this.scheduleMap = map;
this.daySchedules = this.getDaySchedules(this.selectedDate);
} catch (e) {
this.$message.error(e.message || "加载失败");
}
},
openCreate() {
this.form = { id: null, nurseId: "", date: new Date(this.selectedDate), shift: "", task: "" };
this.showDialog = true;
},
openEdit(row) {
this.form = { ...row, date: new Date(row.date) };
this.showDialog = true;
},
async save() {
try {
const payload = { ...this.form, date: formatDate(this.form.date) };
if (payload.id) {
await scheduleUpdate(payload);
} else {
await scheduleCreate(payload);
}
this.showDialog = false;
this.loadMonth(this.monthPicker);
} catch (e) {
this.$message.error(e.message || "保存失败");
}
},
async remove(row) {
try {
await scheduleDelete(row.id);
this.loadMonth(this.monthPicker);
} catch (e) {
this.$message.error(e.message || "删除失败");
}
},
jumpToday() {
const today = new Date();
this.monthPicker = today;
this.calendarDate = today;
this.selectedDate = formatDate(today);
this.loadMonth(today);
}
}
};
</script>
<style scoped>
.schedule-toolbar {
margin: 12px 0 16px;
display: flex;
gap: 12px;
align-items: center;
}
.schedule-panel {
background: #f6fbf7;
border-radius: 8px;
padding: 12px;
border: 1px solid #d9efe3;
}
.schedule-title {
font-weight: 600;
margin-bottom: 10px;
}
.action-inline {
display: flex;
gap: 6px;
flex-wrap: nowrap;
}
.calendar-cell {
min-height: 58px;
}
.cell-date {
font-size: 12px;
color: #6b7b6f;
}
.cell-badges {
margin-top: 4px;
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.badge {
background: #e1f3ea;
color: #2f7d59;
padding: 2px 6px;
border-radius: 6px;
font-size: 11px;
}
.more {
color: #7b8b7f;
font-size: 11px;
}
</style>