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