winaxHelper.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. const { ipcMain, BrowserWindow } = require('electron');
  2. const path = require('path');
  3. const fs = require('fs');
  4. let winax;
  5. try {
  6. winax = require('winax');
  7. console.log('✅ Winax loaded successfully');
  8. } catch (error) {
  9. console.warn('❌ Winax not available:', error.message);
  10. }
  11. class WPSHelper {
  12. constructor() {
  13. this.wpsApp = null;
  14. this.activeDocument = null;
  15. this.isConnected = false;
  16. this.documents = new Map();
  17. this.checkInterval = null;
  18. this.lastParagraphContent = '';
  19. this.startMonitoring();
  20. console.log('🔄 WPSHelper initialized');
  21. }
  22. connectToWPS() {
  23. if (!winax) {
  24. console.warn('⚠️ Winax not available, using fallback mode');
  25. this.setupFallbackMode();
  26. return false;
  27. }
  28. try {
  29. this.wpsApp = new winax.Object('KWPS.Application');
  30. this.wpsApp.Visible = true;
  31. this.isConnected = true;
  32. console.log('✅ Connected to WPS');
  33. this.setupEventListeners();
  34. this.startMonitoring();
  35. this.sendWPSStatus();
  36. return true;
  37. } catch (error) {
  38. console.error('❌ Failed to connect to WPS:', error);
  39. this.isConnected = false;
  40. this.setupFallbackMode();
  41. return false;
  42. }
  43. }
  44. setupEventListeners() {
  45. this.lastActiveDocument = null;
  46. this.lastDocumentCount = 0;
  47. }
  48. startMonitoring() {
  49. if (this.checkInterval) {
  50. clearInterval(this.checkInterval);
  51. }
  52. this.checkInterval = setInterval(() => {
  53. this.checkWPSStatus();
  54. this.updateDocumentsList();
  55. }, 2000);
  56. }
  57. checkWPSStatus() {
  58. try {
  59. if (this.wpsApp && this.isConnected) {
  60. try {
  61. const name = this.wpsApp.Name;
  62. this.isConnected = true;
  63. } catch (error) {
  64. this.isConnected = false;
  65. this.wpsApp = null;
  66. }
  67. } else {
  68. this.isConnected = false;
  69. }
  70. } catch (error) {
  71. this.isConnected = false;
  72. this.wpsApp = null;
  73. }
  74. }
  75. updateDocumentsList() {
  76. if (!this.isConnected || !this.wpsApp) {
  77. if (!this.isConnected) {
  78. this.simulateDocumentsList();
  79. }
  80. return;
  81. }
  82. try {
  83. const newDocs = new Map();
  84. let documents;
  85. try {
  86. documents = this.wpsApp.Documents;
  87. } catch (error) {
  88. return;
  89. }
  90. for (let i = 1; i <= documents.Count; i++) {
  91. try {
  92. const doc = documents.Item(i);
  93. const fullName = doc.FullName;
  94. const name = doc.Name;
  95. newDocs.set(fullName, {
  96. name: name,
  97. path: fullName,
  98. document: doc,
  99. isActive: doc === this.activeDocument
  100. });
  101. } catch (error) {
  102. console.warn('Error accessing document:', error);
  103. }
  104. }
  105. this.documents = newDocs;
  106. this.sendDocumentsList();
  107. } catch (error) {
  108. console.error('Error updating documents list:', error);
  109. }
  110. }
  111. getFullParagraphContent() {
  112. if (!this.isConnected || !this.wpsApp || !this.activeDocument) {
  113. return this.getFallbackParagraphContent();
  114. }
  115. try {
  116. const selection = this.wpsApp.Selection;
  117. if (!selection) {
  118. return this.getFallbackParagraphContent();
  119. }
  120. const originalStart = selection.Start;
  121. const originalEnd = selection.End;
  122. try {
  123. selection.Expand(5); // wdParagraph
  124. const text = selection.Text;
  125. const revisions = this.getRevisionsInSelection(selection);
  126. const comments = this.getCommentsInSelection(selection);
  127. selection.SetRange(originalStart, originalEnd);
  128. return {
  129. text: text.trim(),
  130. html: text.trim(),
  131. revisions: revisions,
  132. comments: comments,
  133. hasRevisions: revisions.length > 0,
  134. hasComments: comments.length > 0
  135. };
  136. } catch (error) {
  137. selection.SetRange(originalStart, originalEnd);
  138. return this.getFallbackParagraphContent();
  139. }
  140. } catch (error) {
  141. return this.getFallbackParagraphContent();
  142. }
  143. }
  144. getRevisionsInSelection(selection) {
  145. const revisions = [];
  146. if (!selection || !selection.Revisions) return revisions;
  147. try {
  148. const revisionsCount = selection.Revisions.Count;
  149. for (let i = 1; i <= revisionsCount; i++) {
  150. try {
  151. const revision = selection.Revisions.Item(i);
  152. revisions.push({
  153. type: 'insert',
  154. author: revision.Author || '未知作者',
  155. date: revision.Date || new Date(),
  156. text: revision.Range.Text || '',
  157. index: i
  158. });
  159. } catch (error) {
  160. console.warn('Error getting revision:', error);
  161. }
  162. }
  163. } catch (error) {
  164. console.warn('Could not access revisions:', error.message);
  165. }
  166. return revisions;
  167. }
  168. getCommentsInSelection(selection) {
  169. const comments = [];
  170. if (!selection || !selection.Comments) return comments;
  171. try {
  172. const commentsCount = selection.Comments.Count;
  173. for (let i = 1; i <= commentsCount; i++) {
  174. try {
  175. const comment = selection.Comments.Item(i);
  176. comments.push({
  177. author: comment.Author || '未知作者',
  178. date: comment.Date || new Date(),
  179. text: comment.Range.Text || '',
  180. index: i
  181. });
  182. } catch (error) {
  183. console.warn('Error getting comment:', error);
  184. }
  185. }
  186. } catch (error) {
  187. console.warn('Could not access comments:', error.message);
  188. }
  189. return comments;
  190. }
  191. getFallbackParagraphContent() {
  192. return {
  193. text: this.lastParagraphContent || '',
  194. html: this.lastParagraphContent || '',
  195. revisions: [],
  196. comments: [],
  197. hasRevisions: false,
  198. hasComments: false
  199. };
  200. }
  201. updateParagraphWithRevisions(newContent) {
  202. if (!this.isConnected || !this.wpsApp || !this.activeDocument) {
  203. return { success: false, error: 'WPS not connected' };
  204. }
  205. try {
  206. const selection = this.wpsApp.Selection;
  207. if (!selection) {
  208. return { success: false, error: 'No selection available' };
  209. }
  210. const originalStart = selection.Start;
  211. const originalEnd = selection.End;
  212. try {
  213. selection.Expand(5); // wdParagraph
  214. const originalText = selection.Text.trim();
  215. if (originalText === newContent.trim()) {
  216. return { success: true, unchanged: true };
  217. }
  218. selection.Text = newContent;
  219. this.lastParagraphContent = newContent;
  220. return {
  221. success: true,
  222. originalContent: originalText,
  223. newContent: newContent,
  224. hasChanges: true
  225. };
  226. } catch (error) {
  227. return { success: false, error: error.message };
  228. } finally {
  229. try {
  230. selection.SetRange(originalStart, originalStart);
  231. } catch (e) {}
  232. }
  233. } catch (error) {
  234. return { success: false, error: error.message };
  235. }
  236. }
  237. navigateParagraph(direction) {
  238. if (!this.isConnected || !this.wpsApp || !this.activeDocument) {
  239. return false;
  240. }
  241. try {
  242. const selection = this.wpsApp.Selection;
  243. if (!selection) {
  244. return false;
  245. }
  246. if (direction === 'prev') {
  247. selection.MoveUp(5, 1); // wdParagraph
  248. } else if (direction === 'next') {
  249. selection.MoveDown(5, 1); // wdParagraph
  250. }
  251. return true;
  252. } catch (error) {
  253. console.error('Error navigating paragraphs:', error);
  254. return false;
  255. }
  256. }
  257. openDocument(filePath) {
  258. if (!this.isConnected || !this.wpsApp) {
  259. console.log('WPS not connected, using fallback');
  260. return false;
  261. }
  262. try {
  263. if (!fs.existsSync(filePath)) {
  264. throw new Error('File does not exist: ' + filePath);
  265. }
  266. const doc = this.wpsApp.Documents.Open(filePath);
  267. this.activeDocument = doc;
  268. this.documents.set(filePath, {
  269. name: doc.Name,
  270. path: filePath,
  271. document: doc,
  272. isActive: true
  273. });
  274. this.sendDocumentsList();
  275. this.sendActiveDocumentChange();
  276. console.log('Document opened successfully:', path.basename(filePath));
  277. return true;
  278. } catch (error) {
  279. console.error('Error opening document:', error);
  280. return false;
  281. }
  282. }
  283. switchToDocument(filePath) {
  284. if (!this.isConnected || !this.wpsApp) {
  285. return false;
  286. }
  287. const docInfo = this.documents.get(filePath);
  288. if (docInfo && docInfo.document) {
  289. try {
  290. docInfo.document.Activate();
  291. this.activeDocument = docInfo.document;
  292. this.sendActiveDocumentChange();
  293. return true;
  294. } catch (error) {
  295. console.error('Error switching document:', error);
  296. }
  297. }
  298. return false;
  299. }
  300. handleRevision(action, revisionIndex) {
  301. if (!this.isConnected || !this.wpsApp || !this.activeDocument) {
  302. return { success: false, error: 'WPS not connected' };
  303. }
  304. try {
  305. if (revisionIndex !== null) {
  306. const revision = this.activeDocument.Revisions.Item(revisionIndex);
  307. if (action === 'accept') {
  308. revision.Accept();
  309. } else {
  310. revision.Reject();
  311. }
  312. } else {
  313. const revisions = this.activeDocument.Revisions;
  314. if (action === 'accept') {
  315. revisions.AcceptAll();
  316. } else {
  317. revisions.RejectAll();
  318. }
  319. }
  320. return { success: true };
  321. } catch (error) {
  322. return { success: false, error: error.message };
  323. }
  324. }
  325. addComment(commentText) {
  326. if (!this.isConnected || !this.wpsApp || !this.activeDocument) {
  327. return { success: false, error: 'WPS not connected' };
  328. }
  329. try {
  330. const selection = this.wpsApp.Selection;
  331. if (!selection) {
  332. return { success: false, error: 'No selection available' };
  333. }
  334. const comment = selection.Comments.Add(selection.Range);
  335. comment.Range.Text = commentText;
  336. return { success: true, comment: commentText };
  337. } catch (error) {
  338. return { success: false, error: error.message };
  339. }
  340. }
  341. setTrackRevisions(track) {
  342. if (!this.isConnected || !this.wpsApp) return false;
  343. try {
  344. this.wpsApp.ActiveDocument.TrackRevisions = track;
  345. return true;
  346. } catch (error) {
  347. console.warn('Could not set track revisions:', error.message);
  348. return false;
  349. }
  350. }
  351. setupFallbackMode() {
  352. console.log('Setting up fallback mode');
  353. this.isConnected = false;
  354. this.startMonitoring();
  355. setTimeout(() => {
  356. this.simulateDocumentsList();
  357. }, 2000);
  358. }
  359. simulateDocumentsList() {
  360. const mockDocuments = [
  361. {
  362. name: '示例文档.docx',
  363. path: 'C:\\Documents\\示例文档.docx',
  364. isActive: true
  365. }
  366. ];
  367. this.sendDocumentsList(mockDocuments);
  368. const sampleParagraph = `这是一个示例段落内容。在回退模式下,您可以查看界面功能,但实际的WPS文档操作需要安装WPS并确保Winax正常工作。
  369. 当前功能包括:
  370. • 文档列表显示
  371. • 段落内容显示
  372. • 基本的导航操作
  373. • 文件拖拽处理
  374. 要启用完整的WPS集成功能,请确保:
  375. 1. 已安装WPS Office
  376. 2. Winax模块正确安装
  377. 3. 应用程序有足够的权限访问COM组件`;
  378. this.sendFullParagraphContent({
  379. text: sampleParagraph,
  380. html: sampleParagraph,
  381. revisions: [],
  382. comments: [],
  383. hasRevisions: false,
  384. hasComments: false
  385. });
  386. }
  387. sendWPSStatus() {
  388. if (ipcMain) {
  389. const windows = BrowserWindow.getAllWindows();
  390. windows.forEach(window => {
  391. if (window && !window.isDestroyed()) {
  392. try {
  393. window.webContents.send('wps-status-changed', {
  394. connected: this.isConnected,
  395. hasActiveDocument: !!this.activeDocument,
  396. documentCount: this.documents.size,
  397. timestamp: new Date().toISOString()
  398. });
  399. } catch (error) {
  400. console.warn('Error sending WPS status:', error);
  401. }
  402. }
  403. });
  404. }
  405. }
  406. sendDocumentsList(documents = null) {
  407. if (ipcMain) {
  408. const docs = documents || Array.from(this.documents.values()).map(doc => ({
  409. name: doc.name,
  410. path: doc.path,
  411. isActive: doc.document === this.activeDocument
  412. }));
  413. const windows = BrowserWindow.getAllWindows();
  414. windows.forEach(window => {
  415. if (window && !window.isDestroyed()) {
  416. try {
  417. window.webContents.send('documents-list-changed', docs);
  418. } catch (error) {
  419. console.warn('Error sending documents list:', error);
  420. }
  421. }
  422. });
  423. }
  424. }
  425. sendActiveDocumentChange() {
  426. if (ipcMain && this.activeDocument) {
  427. const windows = BrowserWindow.getAllWindows();
  428. windows.forEach(window => {
  429. if (window && !window.isDestroyed()) {
  430. try {
  431. window.webContents.send('active-document-changed', {
  432. name: this.activeDocument.Name,
  433. path: this.activeDocument.FullName
  434. });
  435. } catch (error) {
  436. console.warn('Error sending active document change:', error);
  437. }
  438. }
  439. });
  440. }
  441. }
  442. sendFullParagraphContent(content) {
  443. if (ipcMain) {
  444. const windows = BrowserWindow.getAllWindows();
  445. windows.forEach(window => {
  446. if (window && !window.isDestroyed()) {
  447. try {
  448. window.webContents.send('full-paragraph-content-changed', content);
  449. } catch (error) {
  450. console.warn('Error sending full paragraph content:', error);
  451. }
  452. }
  453. });
  454. }
  455. }
  456. getStatus() {
  457. if (!this.isConnected) this.connectToWPS();
  458. return {
  459. connected: this.isConnected,
  460. activeDocument: this.activeDocument ? {
  461. name: this.activeDocument.Name,
  462. path: this.activeDocument.FullName
  463. } : null,
  464. documents: Array.from(this.documents.values()).map(doc => ({
  465. name: doc.name,
  466. path: doc.path,
  467. isActive: doc.document === this.activeDocument
  468. })),
  469. currentParagraph: this.lastParagraphContent,
  470. timestamp: new Date().toISOString()
  471. };
  472. }
  473. cleanup() {
  474. console.log('Cleaning up WPSHelper resources...');
  475. if (this.checkInterval) {
  476. clearInterval(this.checkInterval);
  477. this.checkInterval = null;
  478. }
  479. if (this.wpsApp) {
  480. try {
  481. console.log('WPS application reference cleaned');
  482. } catch (error) {
  483. console.warn('Error cleaning WPS application:', error);
  484. }
  485. this.wpsApp = null;
  486. }
  487. this.activeDocument = null;
  488. this.isConnected = false;
  489. this.documents.clear();
  490. console.log('WPSHelper cleanup completed');
  491. }
  492. }
  493. const wpsHelper = new WPSHelper();
  494. module.exports = { wpsHelper };