add
This commit is contained in:
@@ -162,6 +162,14 @@ public class AdminController {
|
||||
return ApiResponse.success(scheduleMapper.listByDate(java.time.LocalDate.parse(date)));
|
||||
}
|
||||
|
||||
@GetMapping("/schedules/range")
|
||||
public ApiResponse<List<Schedule>> listSchedulesRange(@RequestParam String start, @RequestParam String end) {
|
||||
return ApiResponse.success(scheduleMapper.listByDateRange(
|
||||
java.time.LocalDate.parse(start),
|
||||
java.time.LocalDate.parse(end)
|
||||
));
|
||||
}
|
||||
|
||||
@PostMapping("/schedules")
|
||||
public ApiResponse<Schedule> createSchedule(@RequestBody ScheduleRequest request) {
|
||||
Schedule schedule = new Schedule();
|
||||
|
||||
@@ -40,6 +40,16 @@ public class NurseController {
|
||||
return ApiResponse.success(scheduleMapper.listByNurseAndDate(nurseId, java.time.LocalDate.parse(date)));
|
||||
}
|
||||
|
||||
@GetMapping("/schedules/range")
|
||||
public ApiResponse<List<Schedule>> listSchedulesRange(@RequestParam String start, @RequestParam String end) {
|
||||
Long nurseId = Long.valueOf(StpUtil.getLoginId().toString());
|
||||
return ApiResponse.success(scheduleMapper.listByNurseAndDateRange(
|
||||
nurseId,
|
||||
java.time.LocalDate.parse(start),
|
||||
java.time.LocalDate.parse(end)
|
||||
));
|
||||
}
|
||||
|
||||
@PostMapping("/care-records")
|
||||
public ApiResponse<CareRecord> createCare(@RequestBody CareRecordRequest request) {
|
||||
CareRecord record = new CareRecord();
|
||||
|
||||
@@ -14,6 +14,12 @@ public interface ScheduleMapper {
|
||||
@Select("SELECT * FROM schedule WHERE date = #{date} ORDER BY id DESC")
|
||||
List<Schedule> listByDate(LocalDate date);
|
||||
|
||||
@Select("SELECT * FROM schedule WHERE date BETWEEN #{start} AND #{end} ORDER BY date ASC")
|
||||
List<Schedule> listByDateRange(@Param("start") LocalDate start, @Param("end") LocalDate end);
|
||||
|
||||
@Select("SELECT * FROM schedule WHERE nurse_id = #{nurseId} AND date BETWEEN #{start} AND #{end} ORDER BY date ASC")
|
||||
List<Schedule> listByNurseAndDateRange(@Param("nurseId") Long nurseId, @Param("start") LocalDate start, @Param("end") LocalDate end);
|
||||
|
||||
@Insert("INSERT INTO schedule(nurse_id, date, shift, task, created_at, updated_at) VALUES(#{nurseId}, #{date}, #{shift}, #{task}, NOW(), NOW())")
|
||||
@Options(useGeneratedKeys = true, keyProperty = "id")
|
||||
int insert(Schedule schedule);
|
||||
|
||||
@@ -17,6 +17,7 @@ export const eldersUpdate = (payload) => http.put("/admin/elders", payload);
|
||||
export const eldersDelete = (id) => http.delete(`/admin/elders/${id}`);
|
||||
|
||||
export const schedulesByDate = (date) => http.get("/admin/schedules", { params: { date } });
|
||||
export const schedulesByRange = (start, end) => http.get("/admin/schedules/range", { params: { start, end } });
|
||||
export const scheduleCreate = (payload) => http.post("/admin/schedules", payload);
|
||||
export const scheduleUpdate = (payload) => http.put("/admin/schedules", payload);
|
||||
export const scheduleDelete = (id) => http.delete(`/admin/schedules/${id}`);
|
||||
@@ -31,6 +32,7 @@ export const noticeCreate = (payload) => http.post("/admin/notices", payload);
|
||||
export const noticeList = (role, userId) => http.get("/admin/notices", { params: { role, userId } });
|
||||
|
||||
export const nurseSchedules = (date) => http.get("/nurse/schedules", { params: { date } });
|
||||
export const nurseSchedulesRange = (start, end) => http.get("/nurse/schedules/range", { params: { start, end } });
|
||||
export const nurseCareCreate = (payload) => http.post("/nurse/care-records", payload);
|
||||
export const nurseCareList = (elderId) => http.get("/nurse/care-records", { params: { elderId } });
|
||||
export const nurseHealthCreate = (payload) => http.post("/nurse/health-records", payload);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Vue from "vue";
|
||||
import Vue from "vue";
|
||||
import ElementUI from "element-ui";
|
||||
import "element-ui/lib/theme-chalk/index.css";
|
||||
import "./assets/theme.css";
|
||||
|
||||
@@ -1,108 +1,244 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="page-card">
|
||||
<h3>排班管理</h3>
|
||||
<div style="margin: 12px 0; display:flex; gap: 12px;">
|
||||
<el-date-picker v-model="date" type="date" placeholder="选择日期" />
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="showCreate = true">新增排班</el-button>
|
||||
<div style="display:flex; justify-content: space-between; align-items:center;">
|
||||
<h3>排班管理</h3>
|
||||
<el-button type="primary" @click="openCreate">新增排班</el-button>
|
||||
</div>
|
||||
<el-table :data="schedules" stripe>
|
||||
<el-table-column prop="id" label="编号" width="80" />
|
||||
<el-table-column prop="nurseId" label="护工ID" width="100" />
|
||||
<el-table-column prop="date" label="日期" />
|
||||
<el-table-column prop="shift" label="班次" />
|
||||
<el-table-column prop="task" label="任务" />
|
||||
<el-table-column label="操作" width="200">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" @click="edit(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="danger" @click="remove(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<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="showCreate">
|
||||
<el-dialog title="排班信息" :visible.sync="showDialog">
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="护工ID"><el-input v-model="form.nurseId"/></el-form-item>
|
||||
<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-input v-model="form.shift"/></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="showCreate = false">取消</el-button>
|
||||
<el-button type="primary" @click="create">保存</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="编辑排班" :visible.sync="showEdit">
|
||||
<el-form :model="editForm" label-width="120px">
|
||||
<el-form-item label="护工ID"><el-input v-model="editForm.nurseId"/></el-form-item>
|
||||
<el-form-item label="日期"><el-date-picker v-model="editForm.date" type="date"/></el-form-item>
|
||||
<el-form-item label="班次"><el-input v-model="editForm.shift"/></el-form-item>
|
||||
<el-form-item label="任务"><el-input v-model="editForm.task"/></el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer">
|
||||
<el-button @click="showEdit = false">取消</el-button>
|
||||
<el-button type="primary" @click="update">保存</el-button>
|
||||
<el-button @click="showDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { schedulesByDate, scheduleCreate, scheduleUpdate, scheduleDelete } from "../../api";
|
||||
import { schedulesByRange, scheduleCreate, scheduleUpdate, scheduleDelete, adminUsers } from "../../api";
|
||||
import { formatDate } from "../../utils/date";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const today = new Date();
|
||||
return {
|
||||
date: "",
|
||||
schedules: [],
|
||||
showCreate: false,
|
||||
showEdit: false,
|
||||
form: { nurseId: "", date: "", shift: "", task: "" },
|
||||
editForm: {}
|
||||
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 load() {
|
||||
if (!this.date) return;
|
||||
async loadNurses() {
|
||||
try {
|
||||
const res = await schedulesByDate(formatDate(this.date));
|
||||
this.schedules = res.data.data;
|
||||
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 || "load failed");
|
||||
this.$message.error(e.message || "加载护工失败");
|
||||
}
|
||||
},
|
||||
async create() {
|
||||
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 {
|
||||
await scheduleCreate({ ...this.form, date: formatDate(this.form.date) });
|
||||
this.showCreate = false;
|
||||
this.load();
|
||||
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 || "create failed");
|
||||
this.$message.error(e.message || "加载失败");
|
||||
}
|
||||
},
|
||||
edit(row) {
|
||||
this.editForm = { ...row };
|
||||
this.showEdit = true;
|
||||
openCreate() {
|
||||
this.form = { id: null, nurseId: "", date: new Date(this.selectedDate), shift: "", task: "" };
|
||||
this.showDialog = true;
|
||||
},
|
||||
async update() {
|
||||
openEdit(row) {
|
||||
this.form = { ...row, date: new Date(row.date) };
|
||||
this.showDialog = true;
|
||||
},
|
||||
async save() {
|
||||
try {
|
||||
await scheduleUpdate({ ...this.editForm, date: formatDate(this.editForm.date) });
|
||||
this.showEdit = false;
|
||||
this.load();
|
||||
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 || "update failed");
|
||||
this.$message.error(e.message || "保存失败");
|
||||
}
|
||||
},
|
||||
async remove(row) {
|
||||
try {
|
||||
await scheduleDelete(row.id);
|
||||
this.load();
|
||||
this.loadMonth(this.monthPicker);
|
||||
} catch (e) {
|
||||
this.$message.error(e.message || "delete failed");
|
||||
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>
|
||||
|
||||
@@ -1,36 +1,145 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="page-card">
|
||||
<h3>我的排班</h3>
|
||||
<div style="margin-bottom: 12px; display:flex; gap: 12px;">
|
||||
<el-date-picker v-model="date" type="date" placeholder="选择日期" />
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<div style="display:flex; justify-content: space-between; align-items:center;">
|
||||
<h3>我的排班</h3>
|
||||
<el-button @click="jumpToday">回到今天</el-button>
|
||||
</div>
|
||||
<el-table :data="items" stripe>
|
||||
<el-table-column prop="date" label="日期" />
|
||||
<el-table-column prop="shift" label="班次" />
|
||||
<el-table-column prop="task" label="任务" />
|
||||
</el-table>
|
||||
<div class="schedule-toolbar">
|
||||
<el-date-picker v-model="monthPicker" type="month" placeholder="选择月份" @change="handleMonthChange" />
|
||||
</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">
|
||||
{{ item.shift || "班次" }}
|
||||
</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 prop="shift" label="班次" width="100" />
|
||||
<el-table-column prop="task" label="任务" />
|
||||
</el-table>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { nurseSchedules } from "../../api";
|
||||
import { nurseSchedulesRange } from "../../api";
|
||||
import { formatDate } from "../../utils/date";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return { date: "", items: [] };
|
||||
const today = new Date();
|
||||
return {
|
||||
calendarDate: today,
|
||||
monthPicker: today,
|
||||
selectedDate: formatDate(today),
|
||||
daySchedules: [],
|
||||
scheduleMap: {}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.loadMonth(this.monthPicker);
|
||||
},
|
||||
methods: {
|
||||
async load() {
|
||||
if (!this.date) return;
|
||||
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 nurseSchedules(formatDate(this.date));
|
||||
this.items = res.data.data;
|
||||
const res = await nurseSchedulesRange(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 || "load failed");
|
||||
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;
|
||||
}
|
||||
.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>
|
||||
|
||||
Reference in New Issue
Block a user