schedules.ejs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. <!doctype html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1">
  6. <title><%= title %></title>
  7. <link rel="stylesheet" href="/styles.css">
  8. </head>
  9. <body>
  10. <%- include('partials/nav') %>
  11. <%
  12. const scheduleNextOccurrence = typeof nextOccurrence !== 'undefined' ? nextOccurrence : null;
  13. const scheduleThisWeek = typeof thisWeek !== 'undefined' && Array.isArray(thisWeek) ? thisWeek : [];
  14. const scheduleNextWeek = typeof nextWeek !== 'undefined' && Array.isArray(nextWeek) ? nextWeek : [];
  15. %>
  16. <main>
  17. <section class="hero tile-light compact page-intro">
  18. <p class="eyebrow">Schedule</p>
  19. <h1>定时计划</h1>
  20. <p class="lead">支持每天、工作日、法定工作日、自定义。</p>
  21. <div class="hero-actions compact-actions">
  22. <button class="button-primary" type="button" id="openScheduleDialog">添加计划</button>
  23. </div>
  24. </section>
  25. <% if (message) { %><div class="notice"><%= message %></div><% } %>
  26. <% if (error) { %><div class="notice notice-error"><%= error %></div><% } %>
  27. <section class="tile-parchment compact">
  28. <div class="section-heading dark-text">
  29. <h2>本周与下周计划</h2>
  30. <p>
  31. <% if (scheduleNextOccurrence) { %>
  32. 下一次:<%= actionLabel(scheduleNextOccurrence.action) %>,<%= scheduleNextOccurrence.at %>。
  33. <% } else { %>
  34. 暂无后续计划。
  35. <% } %>
  36. </p>
  37. </div>
  38. <div class="plan-columns">
  39. <%- include('partials/occurrences', { title: '本周计划', items: scheduleThisWeek }) %>
  40. <%- include('partials/occurrences', { title: '下周计划', items: scheduleNextWeek }) %>
  41. </div>
  42. </section>
  43. <section class="tile-light compact">
  44. <div class="card-grid">
  45. <% schedules.forEach((item) => { %>
  46. <article class="utility-card schedule-card <%= item.is_enabled ? '' : 'schedule-disabled' %>" data-schedule-card data-id="<%= item.id %>" data-name="<%= item.name %>" data-target-channel="<%= item.target_channel %>" data-action="<%= item.action %>" data-time="<%= item.time %>" data-repeat-type="<%= item.repeat_type %>" data-weekdays="<%= item.weekdays %>">
  47. <p class="eyebrow schedule-status <%= item.is_enabled ? 'schedule-status-enabled' : 'schedule-status-disabled' %>"><%= item.is_enabled ? '启用' : '停用' %></p>
  48. <div class="schedule-content">
  49. <h3><%= item.name %></h3>
  50. <p><%= item.time %> · <%= actionLabel(item.action) %> · <%= targetLabel(item.target_channel) %></p>
  51. <p><%= repeatLabel(item.repeat_type) %><% if (item.weekdays) { %>:<%= item.weekdays.split(',').map(weekdayLabel).join('、') %><% } %></p>
  52. </div>
  53. <div class="inline-actions">
  54. <button class="button-secondary small" type="button" data-edit-schedule>修改</button>
  55. <form method="post" action="/schedules/<%= item.id %>/toggle"><button class="button-secondary small"><%= item.is_enabled ? '停用' : '启用' %></button></form>
  56. <form method="post" action="/schedules/<%= item.id %>/delete"><button class="button-secondary small">删除</button></form>
  57. </div>
  58. </article>
  59. <% }) %>
  60. </div>
  61. </section>
  62. </main>
  63. <dialog class="modal" id="scheduleDialog">
  64. <div class="modal-header">
  65. <div>
  66. <p class="eyebrow">Schedule</p>
  67. <h2 id="scheduleDialogTitle">添加计划</h2>
  68. </div>
  69. <button class="modal-close" type="button" id="closeScheduleDialog" aria-label="关闭">×</button>
  70. </div>
  71. <form method="post" action="/schedules" class="form-grid modal-form" id="scheduleForm">
  72. <label>名称<input name="name" required placeholder="例如 工作日开灯"></label>
  73. <label>目标
  74. <select name="target_channel">
  75. <option value="0">全部灯</option>
  76. <option value="1">灯1</option>
  77. <option value="2">灯2</option>
  78. <option value="3">灯3</option>
  79. </select>
  80. </label>
  81. <label>动作
  82. <select name="action">
  83. <option value="open">开灯</option>
  84. <option value="close">关灯</option>
  85. </select>
  86. </label>
  87. <label>时间<input name="time" type="text" inputmode="numeric" pattern="([01][0-9]|2[0-3]):[0-5][0-9]" placeholder="24 小时制 HH:mm,例如 09:00、18:30" required></label>
  88. <label>重复
  89. <select name="repeat_type" id="repeatType">
  90. <option value="daily">每天</option>
  91. <option value="workday">工作日</option>
  92. <option value="holiday">法定工作日</option>
  93. <option value="custom">自定义</option>
  94. </select>
  95. </label>
  96. <div class="weekday-picker" id="weekdayPicker" hidden>
  97. <% [1,2,3,4,5,6,7].forEach((day) => { %>
  98. <label><input type="checkbox" name="weekdays" value="<%= day %>"> 周<%= weekdayLabel(day) %></label>
  99. <% }) %>
  100. </div>
  101. <div class="modal-actions">
  102. <button class="button-secondary" type="button" id="cancelScheduleDialog">取消</button>
  103. <button class="button-primary" id="scheduleSubmitButton">创建计划</button>
  104. </div>
  105. </form>
  106. </dialog>
  107. <script>
  108. const scheduleDialog = document.getElementById('scheduleDialog');
  109. const openScheduleDialog = document.getElementById('openScheduleDialog');
  110. const closeScheduleDialog = document.getElementById('closeScheduleDialog');
  111. const cancelScheduleDialog = document.getElementById('cancelScheduleDialog');
  112. const scheduleDialogTitle = document.getElementById('scheduleDialogTitle');
  113. const scheduleForm = document.getElementById('scheduleForm');
  114. const scheduleSubmitButton = document.getElementById('scheduleSubmitButton');
  115. const repeatType = document.getElementById('repeatType');
  116. const weekdayPicker = document.getElementById('weekdayPicker');
  117. function syncWeekdayPicker() {
  118. const isCustom = repeatType.value === 'custom';
  119. weekdayPicker.hidden = !isCustom;
  120. if (!isCustom) {
  121. weekdayPicker.querySelectorAll('input[type="checkbox"]').forEach((input) => {
  122. input.checked = false;
  123. });
  124. }
  125. }
  126. function openCreateScheduleDialog() {
  127. scheduleDialogTitle.textContent = '添加计划';
  128. scheduleSubmitButton.textContent = '创建计划';
  129. scheduleForm.action = '/schedules';
  130. scheduleForm.reset();
  131. syncWeekdayPicker();
  132. scheduleDialog.showModal();
  133. }
  134. function openEditScheduleDialog(card) {
  135. scheduleDialogTitle.textContent = '修改计划';
  136. scheduleSubmitButton.textContent = '保存修改';
  137. scheduleForm.action = `/schedules/${card.dataset.id}/update`;
  138. scheduleForm.elements.name.value = card.dataset.name || '';
  139. scheduleForm.elements.target_channel.value = card.dataset.targetChannel || '0';
  140. scheduleForm.elements.action.value = card.dataset.action || 'open';
  141. scheduleForm.elements.time.value = card.dataset.time || '';
  142. scheduleForm.elements.repeat_type.value = card.dataset.repeatType || 'daily';
  143. const weekdays = (card.dataset.weekdays || '').split(',').filter(Boolean);
  144. weekdayPicker.querySelectorAll('input[type="checkbox"]').forEach((input) => {
  145. input.checked = weekdays.includes(input.value);
  146. });
  147. syncWeekdayPicker();
  148. scheduleDialog.showModal();
  149. }
  150. openScheduleDialog.addEventListener('click', openCreateScheduleDialog);
  151. document.querySelectorAll('[data-edit-schedule]').forEach((button) => {
  152. button.addEventListener('click', () => openEditScheduleDialog(button.closest('[data-schedule-card]')));
  153. });
  154. closeScheduleDialog.addEventListener('click', () => scheduleDialog.close());
  155. cancelScheduleDialog.addEventListener('click', () => scheduleDialog.close());
  156. scheduleDialog.addEventListener('click', (event) => {
  157. if (event.target === scheduleDialog) scheduleDialog.close();
  158. });
  159. repeatType.addEventListener('change', syncWeekdayPicker);
  160. syncWeekdayPicker();
  161. </script>
  162. </body>
  163. </html>