schedule.vue 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. <template>
  2. <div class="mod-demo__schedule">
  3. <div class="form-container">
  4. <el-form :inline="true" :model="state.dataForm" @keyup.enter="state.getDataList()">
  5. <el-form-item>
  6. <el-select v-model="state.dataForm.status" placeholder="排班确认状态" clearable>
  7. <el-option :label="item.dictLabel" :value="item.dictValue" v-for="item in state.getDictByKey('scheduleStatus')"></el-option>
  8. </el-select>
  9. </el-form-item>
  10. <el-form-item>
  11. <el-button @click="state.getDataList()">查询</el-button>
  12. </el-form-item>
  13. <el-form-item>
  14. <el-button v-if="state.hasPermission('emergency:schedule:save')" type="primary" @click="addHandle()">新增</el-button>
  15. </el-form-item>
  16. <el-form-item>
  17. <el-button v-if="state.hasPermission('emergency:schedule:delete')" type="danger" @click="customDeleteHandle()">删除</el-button>
  18. </el-form-item>
  19. <el-form-item>
  20. <el-button v-if="state.hasPermission('emergency:schedule:review')" type="danger" @click="reviewHandle()">审阅</el-button>
  21. </el-form-item>
  22. <el-form-item>
  23. <el-button v-if="state.hasPermission('emergency:inspection:export')" type="info" @click="state.exportHandle()">导出</el-button>
  24. </el-form-item>
  25. </el-form>
  26. </div>
  27. <!-- <el-calendar v-model="calendarValue" /> -->
  28. <el-calendar v-model="calendarValue">
  29. <template #date-cell="{ data }">
  30. <div class="calendar-cell" @dblclick="handleDayDblClick(data.day)">
  31. <div class="checkbox-status">
  32. <el-checkbox
  33. :label="formatMonthDay(data.day)"
  34. :checked="selectedDates.includes(data.day)"
  35. @change="(val) => handleDateToggle(data.day, val)"
  36. @click.stop
  37. />
  38. <el-tag
  39. v-if="getScheduleStatus(data.day) !== null"
  40. :type="getScheduleStatus(data.day) === 0 ? 'warning' : 'success'"
  41. size="small"
  42. class="status-tag"
  43. >
  44. {{ getScheduleStatus(data.day) === 0 ? '待确认' : '已确认' }}
  45. </el-tag>
  46. </div>
  47. <div class="employee-names">
  48. <div
  49. v-for="(name, index) in getScheduleNames(data.day)"
  50. :key="index"
  51. class="employee-name"
  52. @dblclick.stop
  53. >
  54. {{ name }}
  55. </div>
  56. </div>
  57. </div>
  58. </template>
  59. </el-calendar>
  60. <el-pagination :current-page="state.page" :page-sizes="[10, 20, 50, 100]" :page-size="state.limit" :total="state.total" layout="total, sizes, prev, pager, next, jumper" @size-change="state.pageSizeChangeHandle" @current-change="state.pageCurrentChangeHandle"> </el-pagination>
  61. <!-- 弹窗, 新增 / 修改 -->
  62. <add-or-update ref="addOrUpdateRef" @refreshDataList="refreshAfterAdd">确定</add-or-update>
  63. </div>
  64. </template>
  65. <script lang="ts" setup>
  66. import useView from "@/hooks/useView";
  67. import { reactive, ref, toRefs } from "vue";
  68. import baseService from "@/service/baseService";
  69. import { ElMessage, ElMessageBox } from "element-plus";
  70. import { IObject } from "@/types/interface";
  71. import AddOrUpdate from "./schedule-add-or-update.vue";
  72. const view = reactive({
  73. deleteIsBatch: true,
  74. getDataListURL: "/emergency/schedule/page",
  75. getDataListIsPage: true,
  76. exportURL: "/emergency/schedule/export",
  77. deleteURL: "/emergency/schedule",
  78. });
  79. const state = reactive({ ...useView(view), ...toRefs(view) });
  80. //日历
  81. const calendarValue = ref(new Date())
  82. const selectedDates = ref<string[]>([]);
  83. const formatMonthDay = (fullDate: string): string => {
  84. const parts = fullDate.split("-");
  85. return `${parts[1]}-${parts[2]}`;
  86. };
  87. const getScheduleByDate = (date: string) => {
  88. return state.dataList?.find(item => item.scheduleDate === date) || null;
  89. };
  90. const getScheduleStatus = (date: string): number | null => {
  91. const item = getScheduleByDate(date);
  92. return item?.status ?? null;
  93. };
  94. const getScheduleNames = (date: string): string[] => {
  95. const item = getScheduleByDate(date);
  96. return item?.employeeNames || [];
  97. };
  98. const addHandle = () => {
  99. if (!selectedDates.value || selectedDates.value.length === 0) {
  100. ElMessage({
  101. message: '请选择操作项',
  102. type: 'warning',
  103. duration: 500,
  104. });
  105. return;
  106. }
  107. const existingDates = selectedDates.value.filter(date =>
  108. state.dataList?.some(item => item.scheduleDate === date)
  109. );
  110. if (existingDates.length > 0) {
  111. ElMessageBox.confirm(
  112. `以下日期已有排班数据:\n${existingDates.join('、')}\n,是否继续新增?`,
  113. '提示',
  114. {
  115. confirmButtonText: '继续',
  116. cancelButtonText: '取消',
  117. type: 'warning',
  118. }
  119. )
  120. .then(() => {
  121. addOrUpdateHandle(undefined, undefined, selectedDates.value);
  122. })
  123. .catch(() => {
  124. // 用户取消,无需处理
  125. });
  126. } else {
  127. // 无冲突,直接新增
  128. addOrUpdateHandle(undefined, undefined, selectedDates.value);
  129. }
  130. };
  131. const refreshAfterAdd = async () => {
  132. selectedDates.value = [];
  133. state.getDataList();
  134. };
  135. const addOrUpdateRef = ref();
  136. const addOrUpdateHandle = (id?: number, date?: string, dates?: string[]) => {
  137. addOrUpdateRef.value.init(id, date, dates);
  138. };
  139. const handleDayDblClick = (day: string) => {
  140. const schedule = state.dataList?.find(item => item.scheduleDate === day);
  141. if (schedule && schedule.id) {
  142. // 已有排班记录,修改
  143. addOrUpdateHandle(schedule.id);
  144. } else {
  145. // 不存在,调用“新增”
  146. addOrUpdateHandle(undefined, day);
  147. }
  148. };
  149. const handleDateToggle = (day: string, checked: boolean) => {
  150. const index = selectedDates.value.indexOf(day);
  151. if (checked && index === -1) {
  152. selectedDates.value.push(day);
  153. } else if (!checked && index !== -1) {
  154. selectedDates.value.splice(index, 1);
  155. }
  156. };
  157. //删除
  158. const customDeleteHandle = () => {
  159. if (selectedDates.value.length === 0) {
  160. return ElMessage.warning("请先选择要删除的日期");
  161. }
  162. const idsToDelete = selectedDates.value
  163. .map(date => {
  164. const match = state.dataList.find(item => item.scheduleDate === date);
  165. return match?.id;
  166. })
  167. .filter(Boolean) as string[];
  168. if (idsToDelete.length === 0) {
  169. return ElMessage.warning("选中日期中暂无排班数据");
  170. }
  171. ElMessageBox.confirm(`确认要删除选中的 ${idsToDelete.length} 条排班记录吗?`, "提示", {
  172. confirmButtonText: "删除",
  173. cancelButtonText: "取消",
  174. type: "warning"
  175. }).then(() => {
  176. baseService.delete(state.deleteURL, idsToDelete);
  177. ElMessage.success({
  178. message: "删除成功",
  179. duration: 500,
  180. onClose: () => {
  181. state.getDataList();
  182. selectedDates.value = [];
  183. }
  184. });
  185. }).catch(() => {
  186. // 取消删除
  187. });
  188. };
  189. // 审阅
  190. const reviewHandle = (id?: string) => {
  191. if (selectedDates.value.length === 0) {
  192. return ElMessage({
  193. message: "请选择操作项",
  194. type: "warning",
  195. duration: 500
  196. });
  197. }
  198. const reviewData = selectedDates.value
  199. .map(date => {
  200. const item = state.dataList.find((i: any) => i.scheduleDate === date);
  201. return item?.id;
  202. })
  203. .filter(Boolean);
  204. if (reviewData.length === 0) {
  205. return ElMessage.warning("选中日期中暂无排班数据");
  206. }
  207. ElMessageBox.confirm("确定进行[审阅]操作?", "提示", {
  208. confirmButtonText: "确定",
  209. cancelButtonText: "取消",
  210. type: "warning"
  211. })
  212. .then(() => {
  213. baseService.put("/emergency/schedule/review", reviewData).then((res) => {
  214. ElMessage.success({
  215. message: "审阅成功",
  216. duration: 500,
  217. onClose: () => {
  218. state.getDataList();
  219. state.dataListSelections = [];
  220. }
  221. });
  222. });
  223. })
  224. .catch(() => {
  225. //
  226. });
  227. };
  228. </script>
  229. <style>
  230. .calendar-cell {
  231. width: 100%;
  232. height: 100%;
  233. display: flex;
  234. flex-direction: column;
  235. align-items: center;
  236. justify-content: flex-start;
  237. padding-top: 6px;
  238. box-sizing: border-box;
  239. padding: 4px;
  240. cursor: pointer;
  241. }
  242. .employee-name {
  243. user-select: none;
  244. -webkit-user-select: none;
  245. -moz-user-select: none;
  246. }
  247. .checkbox-status {
  248. display: flex;
  249. align-items: center;
  250. gap: 4px;
  251. margin-bottom: 2px;
  252. }
  253. .status-tag {
  254. font-size: 10px;
  255. padding: 0 4px;
  256. height: 20px;
  257. user-select: none;
  258. -webkit-user-select: none;
  259. -moz-user-select: none;
  260. }
  261. ::v-deep(.el-calendar-day) {
  262. display: flex !important;
  263. flex-direction: column;
  264. align-items: flex-start;
  265. justify-content: flex-start;
  266. padding: 4px;
  267. box-sizing: border-box;
  268. }
  269. </style>