打卡工具
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. const { ipcRenderer } = require('electron');
  2. const { chromium } = require('playwright');
  3. let browser = null;
  4. let page = null;
  5. const loginForm = document.getElementById('loginForm');
  6. const attendanceButtons = document.getElementById('attendanceButtons');
  7. const userInfo = document.getElementById('userInfo');
  8. const statusDiv = document.getElementById('status');
  9. const usernameInput = document.getElementById('username');
  10. const passwordInput = document.getElementById('password');
  11. const rememberCheckbox = document.getElementById('remember');
  12. // User info elements
  13. const userNameSpan = document.getElementById('userName');
  14. const checkInTimeSpan = document.getElementById('checkInTime');
  15. const checkOutTimeSpan = document.getElementById('checkOutTime');
  16. // Load saved credentials
  17. async function loadSavedCredentials() {
  18. const credentials = await ipcRenderer.invoke('get-credentials');
  19. if (credentials) {
  20. usernameInput.value = credentials.username || '';
  21. passwordInput.value = credentials.password || '';
  22. rememberCheckbox.checked = true;
  23. }
  24. }
  25. loadSavedCredentials();
  26. async function initBrowser() {
  27. if (!browser) {
  28. browser = await chromium.launch({
  29. headless: true, // 使用无头模式
  30. slowMo: 50 // 保留较小的延迟以确保稳定性
  31. });
  32. }
  33. if (!page) {
  34. page = await browser.newPage();
  35. await page.setDefaultTimeout(60000);
  36. await page.setDefaultNavigationTimeout(60000);
  37. }
  38. }
  39. async function updateAttendanceStatus() {
  40. try {
  41. const userInfo = await page.evaluate(() => {
  42. const rows = document.querySelectorAll('#Table1 tbody tr');
  43. if (rows.length > 1) {
  44. const row = rows[1];
  45. return {
  46. name: row.children[1].textContent.trim(),
  47. checkInTime: row.children[2].children[0].children[0].children[0].children[2].children[1].textContent.trim(),
  48. checkOutTime: row.children[2].children[0].children[0].children[0].children[3].children[1].textContent.trim()
  49. };
  50. }
  51. return null;
  52. });
  53. if (userInfo) {
  54. userNameSpan.textContent = userInfo.name;
  55. checkInTimeSpan.textContent = userInfo.checkInTime;
  56. checkOutTimeSpan.textContent = userInfo.checkOutTime;
  57. // 更新按钮状态
  58. const checkInBtn = document.getElementById('checkInBtn');
  59. const checkOutBtn = document.getElementById('checkOutBtn');
  60. if (userInfo.checkInTime === '00:00' || userInfo.checkInTime === '') {
  61. checkInBtn.disabled = false;
  62. checkInBtn.textContent = '上班打卡';
  63. } else {
  64. checkInBtn.disabled = true;
  65. checkInBtn.textContent = '已打卡';
  66. }
  67. if (userInfo.checkOutTime === '00:00' && userInfo.checkInTime !== '00:00') {
  68. checkOutBtn.disabled = false;
  69. checkOutBtn.textContent = '下班打卡';
  70. } else {
  71. checkOutBtn.disabled = true;
  72. checkOutBtn.textContent = userInfo.checkOutTime === '00:00' ? '未到下班时间' : '已打卡';
  73. }
  74. }
  75. } catch (error) {
  76. console.error('获取考勤状态失败:', error);
  77. }
  78. }
  79. async function login() {
  80. try {
  81. await initBrowser();
  82. statusDiv.textContent = '正在登录...';
  83. // 导航到登录页面
  84. await page.goto('http://hy.zhushitrade.cn/groupware/index.asp', {
  85. waitUntil: 'networkidle',
  86. timeout: 60000
  87. });
  88. // 等待输入框出现
  89. await page.waitForSelector('#userId', { timeout: 60000 });
  90. await page.waitForSelector('#userPassword', { timeout: 60000 });
  91. // 清除现有输入
  92. await page.evaluate(() => {
  93. document.getElementById('userId').value = '';
  94. document.getElementById('userPassword').value = '';
  95. });
  96. // 输入凭据
  97. await page.fill('#userId', usernameInput.value);
  98. await page.fill('#userPassword', passwordInput.value);
  99. if (rememberCheckbox.checked) {
  100. await ipcRenderer.invoke('save-credentials', {
  101. username: usernameInput.value,
  102. password: passwordInput.value
  103. });
  104. }
  105. // 点击登录并等待响应
  106. const navigationPromise = page.waitForNavigation({
  107. waitUntil: 'networkidle',
  108. timeout: 60000
  109. });
  110. await page.click('#btn_login');
  111. await navigationPromise;
  112. // 验证登录是否成功
  113. const currentUrl = page.url();
  114. if (currentUrl.includes('index.asp')) {
  115. statusDiv.textContent = '登录失败: 可能是用户名或密码错误';
  116. return;
  117. }
  118. // 更新用户信息和考勤状态
  119. await updateAttendanceStatus();
  120. statusDiv.textContent = '登录成功';
  121. loginForm.style.display = 'none';
  122. userInfo.style.display = 'block';
  123. attendanceButtons.style.display = 'block';
  124. } catch (error) {
  125. console.error('登录错误:', error);
  126. statusDiv.textContent = '登录失败: ' + (error.message || '网络连接问题,请检查网络后重试');
  127. // 如果是超时错误,尝试重新初始化浏览器
  128. if (error.name === 'TimeoutError') {
  129. try {
  130. if (browser) {
  131. await browser.close();
  132. }
  133. browser = null;
  134. page = null;
  135. } catch (closeError) {
  136. console.error('关闭浏览器失败:', closeError);
  137. }
  138. }
  139. }
  140. }
  141. async function checkIn() {
  142. try {
  143. if (!page) {
  144. statusDiv.textContent = '请先登录';
  145. return;
  146. }
  147. statusDiv.textContent = '正在打卡...';
  148. await page.goto('http://hy.zhushitrade.cn/groupware/editCheckWork.asp?action=onDuty', {
  149. waitUntil: 'networkidle',
  150. timeout: 60000
  151. });
  152. await page.waitForTimeout(2000);
  153. await updateAttendanceStatus();
  154. statusDiv.textContent = '上班打卡成功';
  155. } catch (error) {
  156. console.error('打卡错误:', error);
  157. statusDiv.textContent = '打卡失败: ' + (error.message || '请检查网络连接');
  158. }
  159. }
  160. async function checkOut() {
  161. try {
  162. if (!page) {
  163. statusDiv.textContent = '请先登录';
  164. return;
  165. }
  166. statusDiv.textContent = '正在打卡...';
  167. await page.goto('http://hy.zhushitrade.cn/groupware/editcheckwork.asp?action=offDuty', {
  168. waitUntil: 'networkidle',
  169. timeout: 60000
  170. });
  171. await page.waitForTimeout(2000);
  172. await updateAttendanceStatus();
  173. statusDiv.textContent = '下班打卡成功';
  174. } catch (error) {
  175. console.error('打卡错误:', error);
  176. statusDiv.textContent = '打卡失败: ' + (error.message || '请检查网络连接');
  177. }
  178. }
  179. // 定时更新考勤状态
  180. setInterval(async () => {
  181. if (page && userInfo.style.display === 'block') {
  182. await updateAttendanceStatus();
  183. }
  184. }, 60000); // 每分钟更新一次
  185. // Event Listeners
  186. document.getElementById('loginBtn').addEventListener('click', login);
  187. document.getElementById('checkInBtn').addEventListener('click', checkIn);
  188. document.getElementById('checkOutBtn').addEventListener('click', checkOut);
  189. // Cleanup on window close
  190. window.addEventListener('beforeunload', async () => {
  191. if (browser) {
  192. await browser.close();
  193. }
  194. });