245 lines
7.4 KiB
Vue
245 lines
7.4 KiB
Vue
<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>
|