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