Wiki源代码剩余工时显示插件
由 Yang Sheng 于 2025-02-42,17:46 最后修改
显示最后作者
author | version | line-number | content |
---|---|---|---|
1 | = 效果展示: = | ||
2 | |||
3 | |||
4 | [[image:1737957143812-601.png||height="701" width="1082"]] | ||
5 | |||
6 | |||
7 | = 更新日志: = | ||
8 | |||
9 | ~/~/20250127 V1.1版本 | ||
10 | ~/~/更新说明: | ||
11 | ~/~/对请假时长小于8小时场景,进行更新。 | ||
12 | |||
13 | ~/~/20250126 V1.0版本 | ||
14 | ~/~/计算逻辑:查看.absolute中的打卡时间,计算当日工作时间,减去午休时间12:00-1:00,及当日正常8小时工作时间,计算当日剩余工时 | ||
15 | ~/~/当有外出公干逻辑时,取.ant-fullcalendar-content中的外出公干时间,与当日出勤时间合计后,减去午休及当日8小时工时,计算剩余工时 | ||
16 | ~/~/考虑请假情况,请假情况不做处理,直接计算为0剩余工时。 | ||
17 | ~/~/忽略上、下班忘刷卡提示 | ||
18 | ~/~/考虑外出公干中0:00-0:00情况,直接计算为0剩余工时。 | ||
19 | |||
20 | = = | ||
21 | |||
22 | = 部署方法: = | ||
23 | |||
24 | |||
25 | (% style="color:#d35400" %)**2025/02/01更新:** | ||
26 | |||
27 | (% style="color:#d35400" %)**目前微软应用商店已过审,插件设置为未公开,您无法从应用商店直接搜索到,但可以通过以下网址直接访问:** | ||
28 | |||
29 | [[Remaining_Work_Hours_for_AIO - Microsoft Edge Addons>>url:https://microsoftedge.microsoft.com/addons/detail/remaining_work_hours_for_/cccllmndpkncoikcianbcmbmhbpdokco]] | ||
30 | |||
31 | (% style="color:#d35400" %)**直接点击获取安装即可,安装后可参见下方<4.验证安装>进行验证。** | ||
32 | |||
33 | [[image:1738348729386-156.png||height="603" width="1083"]] | ||
34 | |||
35 | |||
36 | **以下方法适用于使用油猴插件场景,因微软插件需要审核,周期较长,油猴无需审核,实时更新,鉴于脚本刚刚试运行,可能有部分场景没有考虑到,故仍保留油猴渠道。** | ||
37 | |||
38 | === 1.在浏览器安装油猴插件 === | ||
39 | |||
40 | |||
41 | |||
42 | 网址如下: | ||
43 | [[https:~~/~~/microsoftedge.microsoft.com/addons/detail/%E7%AF%A1%E6%94%B9%E7%8C%B4/iikmkjmpaadaobahmlepeloendndfphd?refid=bingshortanswersdownload>>url:https://microsoftedge.microsoft.com/addons/detail/%E7%AF%A1%E6%94%B9%E7%8C%B4/iikmkjmpaadaobahmlepeloendndfphd?refid=bingshortanswersdownload||rel="noopener noreferrer" target="_blank"]] | ||
44 | |||
45 | |||
46 | 因为我已经安装了,下图是删除,没安装过的是安装。 | ||
47 | |||
48 | [[image:1737957191147-772.png||height="752" width="1078"]] | ||
49 | |||
50 | |||
51 | == 2.配置浏览器开发者属性。 == | ||
52 | |||
53 | |||
54 | 复制下面网址至浏览器: | ||
55 | |||
56 | edge:~/~/extensions/ | ||
57 | |||
58 | 如下图:红框全部勾选 | ||
59 | |||
60 | [[image:1737957191170-336.png||height="573" width="1083"]] | ||
61 | |||
62 | |||
63 | == 3.添加脚本 == | ||
64 | |||
65 | |||
66 | 访问如下网址,直接添加脚本: | ||
67 | |||
68 | [[https:~~/~~/update.greasyfork.org/scripts/525013/Remaining_Work_Hours_For_AIO.user.js>>url:https://update.greasyfork.org/scripts/525013/Remaining_Work_Hours_For_AIO.user.js||rel="noopener noreferrer" target="_blank"]] | ||
69 | |||
70 | 核对名称,版本,直接安装即可 | ||
71 | |||
72 | [[image:1737957191174-404.png||height="673" width="1084"]] | ||
73 | |||
74 | |||
75 | == 4.功能验证 == | ||
76 | |||
77 | (% style="color:#c0392b" %)**此插件只有在考勤日历界面才会启用** | ||
78 | |||
79 | 当停留在考勤日历界面时,检查脚本是否正常启用: | ||
80 | |||
81 | |||
82 | [[image:1737957191180-764.png]] | ||
83 | |||
84 | |||
85 | 打开浏览器地址栏后插件的图标,让篡改猴可视化。 | ||
86 | |||
87 | 确认脚本已经选中: | ||
88 | |||
89 | |||
90 | [[image:1737957191180-705.png]] | ||
91 | |||
92 | |||
93 | 且篡改猴图标有红色数字1,代表脚本正常运行。 | ||
94 | |||
95 | 至此便可正常显示剩余工时。 | ||
96 | |||
97 | |||
98 | {{velocity}} | ||
99 | #if($xcontext.user != "XWiki.XWikiGuest") | ||
100 | |||
101 | |||
102 | |||
103 | = 代码如下: = | ||
104 | |||
105 | |||
106 | {{code language="java" layout="LINENUMBERS"}} | ||
107 | // ==UserScript== | ||
108 | // @name Remaining_Work_Hours_For_AIO | ||
109 | // @namespace http://nicebro.fun/ | ||
110 | // @version V1.1 | ||
111 | // @description Remaining_Work_Hours_for_AIO | ||
112 | // @author flower | ||
113 | // @match https://eip.h3c.com/myCenter/kaoqin | ||
114 | // @grant none | ||
115 | // @license MIT | ||
116 | // @downloadURL https://update.greasyfork.org/scripts/525013/Remaining_Work_Hours_For_AIO.user.js | ||
117 | // @updateURL https://update.greasyfork.org/scripts/525013/Remaining_Work_Hours_For_AIO.meta.js | ||
118 | // ==/UserScript== | ||
119 | |||
120 | //20250127 V1.1版本 | ||
121 | //更新说明: | ||
122 | //对请假时长小于8小时场景,进行更新。 | ||
123 | |||
124 | //20250126 V1.0版本 | ||
125 | //计算逻辑:查看.absolute中的打卡时间,计算当日工作时间,减去午休时间12:00-1:00,及当日正常8小时工作时间,计算当日剩余工时 | ||
126 | //当有外出公干逻辑时,取.ant-fullcalendar-content中的外出公干时间,与当日出勤时间合计后,减去午休及当日8小时工时,计算剩余工时 | ||
127 | //考虑请假情况,请假情况不做处理,直接计算为0剩余工时。 | ||
128 | //忽略上、下班未打卡提示 | ||
129 | //考虑外出公干中0:00-0:00情况,直接计算为0剩余工时。 | ||
130 | |||
131 | (function() { | ||
132 | 'use strict'; | ||
133 | let isInsertDiv = false; | ||
134 | let totalMinutesWorkOvertime = 0; | ||
135 | |||
136 | const timeToMinutes = (timeStr) => { | ||
137 | const [hours, minutes] = timeStr.split(':').map(Number); | ||
138 | return isNaN(hours) || isNaN(minutes) ? 0 : hours * 60 + minutes; | ||
139 | }; | ||
140 | |||
141 | const delayInterval = 5000; | ||
142 | const scanInterval = 1000; | ||
143 | const standardWorkMinutes = 8 * 60; // 标准工作时间为8小时,单位为分钟 | ||
144 | const siestaMinutes = timeToMinutes('13:00') - timeToMinutes('12:00'); // 午休时间,单位为分钟 | ||
145 | |||
146 | const createDisplayElement = () => { | ||
147 | const fullscreenElement = document.querySelector('.ant-fullcalendar-fullscreen'); | ||
148 | if (fullscreenElement) { | ||
149 | const displayDiv = document.createElement('div'); | ||
150 | displayDiv.id = 'total-time-display'; | ||
151 | displayDiv.style.backgroundColor = '#f0f0f0'; | ||
152 | displayDiv.style.padding = '16px'; | ||
153 | displayDiv.style.textAlign = 'left'; | ||
154 | displayDiv.style.zIndex = '1000'; | ||
155 | displayDiv.style.fontSize = '16px'; | ||
156 | displayDiv.style.fontWeight = 'bold'; | ||
157 | displayDiv.style.color = '#333'; | ||
158 | displayDiv.textContent = "剩余工时-增值AIO试用: "; | ||
159 | fullscreenElement.parentNode.insertBefore(displayDiv, fullscreenElement); | ||
160 | } | ||
161 | }; | ||
162 | |||
163 | const updateDisplay = (totalMinutesWorkOvertime, totalHours) => { | ||
164 | const displayDiv = document.getElementById('total-time-display'); | ||
165 | if (displayDiv) { | ||
166 | displayDiv.textContent = `剩余工时-AIO试用: ${totalHours} 小时( ${totalMinutesWorkOvertime} 分钟 )`; | ||
167 | } else { | ||
168 | console.log("无法找到指定displayDiv"); | ||
169 | } | ||
170 | }; | ||
171 | |||
172 | const scanElements = () => { | ||
173 | try { | ||
174 | console.clear(); | ||
175 | totalMinutesWorkOvertime = 0; | ||
176 | |||
177 | const element1 = document.querySelector('.ant-calendar-picker'); | ||
178 | if (element1) { | ||
179 | const textContent = element1.textContent.trim(); | ||
180 | const elements2 = document.querySelectorAll('.ant-fullcalendar-cell'); | ||
181 | elements2.forEach((element2) => { | ||
182 | if (element2.title.includes(textContent)) { | ||
183 | let currentDate = 0; | ||
184 | const currentDateElements = element2.querySelector('.ant-fullcalendar-value'); | ||
185 | if (currentDateElements) { | ||
186 | const tmpDate = currentDateElements.textContent.trim(); | ||
187 | if (!isNaN(tmpDate)) { | ||
188 | currentDate = tmpDate; | ||
189 | } | ||
190 | } | ||
191 | |||
192 | console.log(`正在处理本月 ${currentDate} 日的数据...`); | ||
193 | |||
194 | let totalDailyMinutes = 0; | ||
195 | let hasLeave = false; // 用于判断是否有全日假期 | ||
196 | |||
197 | // 检查是否为休息日 | ||
198 | const absoluteElement = element2.querySelector('.absolute'); | ||
199 | const statusTextElement = element2.querySelector('.ant-fullcalendar-content'); | ||
200 | const hasTimePattern = /\d{1,2}:\d{2}/; // 时间格式匹配 | ||
201 | |||
202 | // 如果没有时间信息,视为休息日 | ||
203 | if (!absoluteElement && (!statusTextElement || !hasTimePattern.test(statusTextElement.textContent))) { | ||
204 | console.log(`本月 ${currentDate} 日为休息日,不计算剩余工时`); | ||
205 | return; // 当前日期为休息日,跳过计算 | ||
206 | } | ||
207 | |||
208 | // 处理外出公干和假期时间段 | ||
209 | if (statusTextElement) { | ||
210 | const statusText = statusTextElement.textContent.trim(); | ||
211 | const publicBusinessPattern = /外出公干((\d{1,2}:\d{2})-(\d{1,2}:\d{2}))/g; | ||
212 | const leavePattern = /[\u4e00-\u9fa5]*假((\d{1,2}:\d{2})-(\d{1,2}:\d{2}))/g; | ||
213 | let match; | ||
214 | |||
215 | // 处理外出公干 | ||
216 | while ((match = publicBusinessPattern.exec(statusText)) !== null) { | ||
217 | const startTime = timeToMinutes(match[1]); | ||
218 | const endTime = timeToMinutes(match[2]); | ||
219 | if (startTime && endTime && endTime > startTime) { | ||
220 | const duration = endTime - startTime; | ||
221 | totalDailyMinutes += duration; | ||
222 | console.log(`外出公干时间段:${match[1]} - ${match[2]},时长:${duration} 分钟`); | ||
223 | } | ||
224 | } | ||
225 | |||
226 | // 处理假期 | ||
227 | while ((match = leavePattern.exec(statusText)) !== null) { | ||
228 | const startTime = timeToMinutes(match[1]); | ||
229 | const endTime = timeToMinutes(match[2]); | ||
230 | if (startTime === 0 && endTime === 0) { | ||
231 | console.log(`全日假期:${match[1]} - ${match[2]}`); | ||
232 | hasLeave = true; | ||
233 | } else if (startTime && endTime && endTime > startTime) { | ||
234 | const duration = endTime - startTime; | ||
235 | totalDailyMinutes += duration; | ||
236 | console.log(`假期时间段:${match[1]} - ${match[2]},时长:${duration} 分钟`); | ||
237 | } | ||
238 | } | ||
239 | } | ||
240 | |||
241 | // 处理打卡时间段 | ||
242 | if (absoluteElement) { | ||
243 | const timeRange = absoluteElement.textContent.trim(); | ||
244 | const timePattern = /(\d{1,2}:\d{2})\s*-\s*(\d{1,2}:\d{2})/g; | ||
245 | let match; | ||
246 | while ((match = timePattern.exec(timeRange)) !== null) { | ||
247 | const startTime = timeToMinutes(match[1]); | ||
248 | const endTime = timeToMinutes(match[2]); | ||
249 | if (startTime && endTime && endTime > startTime) { | ||
250 | const duration = endTime - startTime; | ||
251 | totalDailyMinutes += duration; | ||
252 | console.log(`打卡时间段:${match[1]} - ${match[2]},时长:${duration} 分钟`); | ||
253 | } | ||
254 | } | ||
255 | } | ||
256 | |||
257 | console.log(`本月 ${currentDate} 日总工作时间(含午休):${totalDailyMinutes} 分钟`); | ||
258 | |||
259 | // 计算当天剩余工时 | ||
260 | if (totalDailyMinutes > 0) { | ||
261 | let dailyOvertimeMinutes = totalDailyMinutes - siestaMinutes - standardWorkMinutes; | ||
262 | console.log(`扣除标准工时和午休后,本月 ${currentDate} 日剩余工时:${dailyOvertimeMinutes} 分钟`); | ||
263 | |||
264 | if (dailyOvertimeMinutes > 0) { | ||
265 | totalMinutesWorkOvertime += dailyOvertimeMinutes; | ||
266 | } | ||
267 | } else if (hasLeave) { | ||
268 | console.log(`本月 ${currentDate} 日为全日假期,不计算剩余工时。`); | ||
269 | } | ||
270 | |||
271 | console.log(`本月 ${currentDate} 日处理完成。`); | ||
272 | } | ||
273 | }); | ||
274 | |||
275 | // 输出累计结果 | ||
276 | console.log(`总剩余分钟数: ${totalMinutesWorkOvertime}`); | ||
277 | const totalHours = (totalMinutesWorkOvertime / 60).toFixed(2); | ||
278 | console.log(`总剩余小时数: ${totalHours}`); | ||
279 | |||
280 | if (!isInsertDiv) { | ||
281 | createDisplayElement(); | ||
282 | isInsertDiv = true; | ||
283 | } | ||
284 | |||
285 | updateDisplay(totalMinutesWorkOvertime, totalHours); | ||
286 | } | ||
287 | } catch (error) { | ||
288 | console.error("An error occurred during the scan:", error); | ||
289 | } | ||
290 | }; | ||
291 | |||
292 | setTimeout(() => { | ||
293 | scanElements(); | ||
294 | setInterval(scanElements, scanInterval); | ||
295 | }, delayInterval); | ||
296 | })(); | ||
297 | {{/code}} | ||
298 | |||
299 | |||
300 | #end | ||
301 | {{/velocity}} | ||
302 | |||
303 | |||
304 |