9 | */
10 |
11 | /** 清除内外边距 **/
12 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* structural elements 结构元素 */
13 | dl, dt, dd, ul, ol, li, /* list elements 列表元素 */
14 | pre, /* text formatting elements 文本格式元素 */
15 | form, fieldset, legend, button, input, textarea, /* form elements 表单元素 */
16 | th, td /* table elements 表格元素 */ {
17 | margin: 0;
18 | padding: 0;
19 | }
20 |
21 | /** 设置默认字体 **/
22 | body,
23 | button, input, select, textarea /* for ie */ {
24 | font: 12px/1.5 tahoma, arial, \5b8b\4f53, sans-serif;
25 | }
26 | h1, h2, h3, h4, h5, h6 { font-size: 100%; }
27 | address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */
28 | code, kbd, pre, samp { font-family: courier new, courier, monospace; } /* 统一等宽字体 */
29 | small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */
30 |
31 | /** 重置列表元素 **/
32 | ul, ol { list-style: none; }
33 |
34 | /** 重置文本格式元素 **/
35 | a { text-decoration: none; }
36 | a:hover { text-decoration: underline; }
37 |
38 |
39 | /** 重置表单元素 **/
40 | legend { color: #000; } /* for ie6 */
41 | fieldset, img { border: 0; } /* img 搭车:让链接里的 img 无边框 */
42 | button, input, select, textarea { font-size: 100%; } /* 使得表单元素在 ie 下能继承字体大小 */
43 | /* 注:optgroup 无法扶正 */
44 |
45 | /** 重置表格元素 **/
46 | table { border-collapse: collapse; border-spacing: 0; }
47 |
48 | /* 清除浮动 */
49 | .ks-clear:after, .clear:after {
50 | content: '\20';
51 | display: block;
52 | height: 0;
53 | clear: both;
54 | }
55 | .ks-clear, .clear {
56 | *zoom: 1;
57 | }
58 |
59 | .main {
60 | padding: 30px 100px;
61 | width: 960px;
62 | margin: 0 auto;
63 | }
64 | .main h1{font-size:36px; color:#333; text-align:left;margin-bottom:30px; border-bottom: 1px solid #eee;}
65 |
66 | .helps{margin-top:40px;}
67 | .helps pre{
68 | padding:20px;
69 | margin:10px 0;
70 | border:solid 1px #e7e1cd;
71 | background-color: #fffdef;
72 | overflow: auto;
73 | }
74 |
75 | .icon_lists{
76 | width: 100% !important;
77 |
78 | }
79 |
80 | .icon_lists li{
81 | float:left;
82 | width: 100px;
83 | height:180px;
84 | text-align: center;
85 | list-style: none !important;
86 | }
87 | .icon_lists .icon{
88 | font-size: 42px;
89 | line-height: 100px;
90 | margin: 10px 0;
91 | color:#333;
92 | -webkit-transition: font-size 0.25s ease-out 0s;
93 | -moz-transition: font-size 0.25s ease-out 0s;
94 | transition: font-size 0.25s ease-out 0s;
95 |
96 | }
97 | .icon_lists .icon:hover{
98 | font-size: 100px;
99 | }
100 |
101 |
102 |
103 | .markdown {
104 | color: #666;
105 | font-size: 14px;
106 | line-height: 1.8;
107 | }
108 |
109 | .highlight {
110 | line-height: 1.5;
111 | }
112 |
113 | .markdown img {
114 | vertical-align: middle;
115 | max-width: 100%;
116 | }
117 |
118 | .markdown h1 {
119 | color: #404040;
120 | font-weight: 500;
121 | line-height: 40px;
122 | margin-bottom: 24px;
123 | }
124 |
125 | .markdown h2,
126 | .markdown h3,
127 | .markdown h4,
128 | .markdown h5,
129 | .markdown h6 {
130 | color: #404040;
131 | margin: 1.6em 0 0.6em 0;
132 | font-weight: 500;
133 | clear: both;
134 | }
135 |
136 | .markdown h1 {
137 | font-size: 28px;
138 | }
139 |
140 | .markdown h2 {
141 | font-size: 22px;
142 | }
143 |
144 | .markdown h3 {
145 | font-size: 16px;
146 | }
147 |
148 | .markdown h4 {
149 | font-size: 14px;
150 | }
151 |
152 | .markdown h5 {
153 | font-size: 12px;
154 | }
155 |
156 | .markdown h6 {
157 | font-size: 12px;
158 | }
159 |
160 | .markdown hr {
161 | height: 1px;
162 | border: 0;
163 | background: #e9e9e9;
164 | margin: 16px 0;
165 | clear: both;
166 | }
167 |
168 | .markdown p,
169 | .markdown pre {
170 | margin: 1em 0;
171 | }
172 |
173 | .markdown > p,
174 | .markdown > blockquote,
175 | .markdown > .highlight,
176 | .markdown > ol,
177 | .markdown > ul {
178 | width: 80%;
179 | }
180 |
181 | .markdown ul > li {
182 | list-style: circle;
183 | }
184 |
185 | .markdown > ul li,
186 | .markdown blockquote ul > li {
187 | margin-left: 20px;
188 | padding-left: 4px;
189 | }
190 |
191 | .markdown > ul li p,
192 | .markdown > ol li p {
193 | margin: 0.6em 0;
194 | }
195 |
196 | .markdown ol > li {
197 | list-style: decimal;
198 | }
199 |
200 | .markdown > ol li,
201 | .markdown blockquote ol > li {
202 | margin-left: 20px;
203 | padding-left: 4px;
204 | }
205 |
206 | .markdown code {
207 | margin: 0 3px;
208 | padding: 0 5px;
209 | background: #eee;
210 | border-radius: 3px;
211 | }
212 |
213 | .markdown pre {
214 | border-radius: 6px;
215 | background: #f7f7f7;
216 | padding: 20px;
217 | }
218 |
219 | .markdown pre code {
220 | border: none;
221 | background: #f7f7f7;
222 | margin: 0;
223 | }
224 |
225 | .markdown strong,
226 | .markdown b {
227 | font-weight: 600;
228 | }
229 |
230 | .markdown > table {
231 | border-collapse: collapse;
232 | border-spacing: 0px;
233 | empty-cells: show;
234 | border: 1px solid #e9e9e9;
235 | width: 95%;
236 | margin-bottom: 24px;
237 | }
238 |
239 | .markdown > table th {
240 | white-space: nowrap;
241 | color: #333;
242 | font-weight: 600;
243 |
244 | }
245 |
246 | .markdown > table th,
247 | .markdown > table td {
248 | border: 1px solid #e9e9e9;
249 | padding: 8px 16px;
250 | text-align: left;
251 | }
252 |
253 | .markdown > table th {
254 | background: #F7F7F7;
255 | }
256 |
257 | .markdown blockquote {
258 | font-size: 90%;
259 | color: #999;
260 | border-left: 4px solid #e9e9e9;
261 | padding-left: 0.8em;
262 | margin: 1em 0;
263 | font-style: italic;
264 | }
265 |
266 | .markdown blockquote p {
267 | margin: 0;
268 | }
269 |
270 | .markdown .anchor {
271 | opacity: 0;
272 | transition: opacity 0.3s ease;
273 | margin-left: 8px;
274 | }
275 |
276 | .markdown .waiting {
277 | color: #ccc;
278 | }
279 |
280 | .markdown h1:hover .anchor,
281 | .markdown h2:hover .anchor,
282 | .markdown h3:hover .anchor,
283 | .markdown h4:hover .anchor,
284 | .markdown h5:hover .anchor,
285 | .markdown h6:hover .anchor {
286 | opacity: 1;
287 | display: inline-block;
288 | }
289 |
290 | .markdown > br,
291 | .markdown > p > br {
292 | clear: both;
293 | }
294 |
295 |
296 | .hljs {
297 | display: block;
298 | background: white;
299 | padding: 0.5em;
300 | color: #333333;
301 | overflow-x: auto;
302 | }
303 |
304 | .hljs-comment,
305 | .hljs-meta {
306 | color: #969896;
307 | }
308 |
309 | .hljs-string,
310 | .hljs-variable,
311 | .hljs-template-variable,
312 | .hljs-strong,
313 | .hljs-emphasis,
314 | .hljs-quote {
315 | color: #df5000;
316 | }
317 |
318 | .hljs-keyword,
319 | .hljs-selector-tag,
320 | .hljs-type {
321 | color: #a71d5d;
322 | }
323 |
324 | .hljs-literal,
325 | .hljs-symbol,
326 | .hljs-bullet,
327 | .hljs-attribute {
328 | color: #0086b3;
329 | }
330 |
331 | .hljs-section,
332 | .hljs-name {
333 | color: #63a35c;
334 | }
335 |
336 | .hljs-tag {
337 | color: #333333;
338 | }
339 |
340 | .hljs-title,
341 | .hljs-attr,
342 | .hljs-selector-id,
343 | .hljs-selector-class,
344 | .hljs-selector-attr,
345 | .hljs-selector-pseudo {
346 | color: #795da3;
347 | }
348 |
349 | .hljs-addition {
350 | color: #55a532;
351 | background-color: #eaffea;
352 | }
353 |
354 | .hljs-deletion {
355 | color: #bd2c00;
356 | background-color: #ffecec;
357 | }
358 |
359 | .hljs-link {
360 | text-decoration: underline;
361 | }
362 |
363 | pre{
364 | background: #fff;
365 | }
366 |
367 |
368 |
369 |
370 |
371 |
--------------------------------------------------------------------------------
/src/lib/capture/iconfont/iconfont.css:
--------------------------------------------------------------------------------
1 |
2 | @font-face {font-family: "iconfont";
3 | src: url('iconfont.eot?t=1538684099545'); /* IE9*/
4 | src: url('iconfont.eot?t=1538684099545#iefix') format('embedded-opentype'), /* IE6-IE8 */
5 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAWQAAsAAAAACCQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8nVDMY21hcAAAAYAAAABrAAABsu3anlxnbHlmAAAB7AAAAZYAAAG869Cas2hlYWQAAAOEAAAALgAAADYS2G+AaGhlYQAAA7QAAAAcAAAAJAfeA4ZobXR4AAAD0AAAAA4AAAAUFAAAAGxvY2EAAAPgAAAADAAAAAwBEAFgbWF4cAAAA+wAAAAfAAAAIAESAENuYW1lAAAEDAAAAUUAAAJtPlT+fXBvc3QAAAVUAAAAPAAAAE0pDHeHeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeqb+7xdzwv4EhhrmBoQEozAiSAwDxJgzseJztkbsNgDAMRF8+IBSxBw0dC2SgVIzLAC5ZIdgxBUNw0bPsU+LiAkxAUnYlQzgJmJq6YfiJMvxM1bmwEImyySHtvnqHb/8q6D0/1id9Ybtnfq2j1nfKlp5jacvh2I9IczQ77sshP1MOG7MAeJwdj0FrE1EUhe95z8zLvOnM9GVC0pSZmEybmSKYTdsZRIhKXRgQLfgTpIiLoJtCdZFqf4HQnRsLhazabNxamD/gyoIb/QkK3ehqUu/4Npdzz8c795AkfvKSR0gRZUSIrQgtS/WtJN/eSirVRbs/Qr49RO0eMt75MJWjTJrEymreZEJ+9hq4vVV+vLOBRrgkHmXiJBujZhbj1XM76Oi3JjJ444SW1ufMbxp/kuZC3t2Y+Mb4x8xinB375p2a6U5gT1BU+8X9iW3rjp5Jj0j8v/WpmJNNAVGA1ggpEg8DWF3kyIb4IR7qSOnywgnr4kn5RddDR+xoFYnD8oKHZr8errLBlNhhir8E0XUpL8Uv6rEYQnlQa+18jTtzdRZxknJKq80hI8jTzW/T6VnjtCg+NWf7R3Mp50fZ81t6ZVnovb8vXzzA9f6BH5xNi+L1h8pkZD1Worli71qPd39XedzjvTihZaJBnA6SEdo1ZF0oWApftb347tZ6HgwOdezqxav60jN15fRd5yf+uDd6bnmlBf0D1bBUzQAAeJxjYGRgYADi+Y6Xrsbz23xl4GZhAIHrd6YeRtD//7IwMIO4HAxMIAoAaF8MWwAAeJxjYGRgYG7438AQw8IAAkCSkQEVsAIARwsCbnicY2FgYGBBwwABBAAVAAAAAAAAAFYAggC6AN54nGNgZGBgYGUwZ2BmAAEmIOYCQgaG/2A+AwAOdQFWAHicZY9NTsMwEIVf+gekEqqoYIfkBWIBKP0Rq25YVGr3XXTfpk6bKokjx63UA3AejsAJOALcgDvwSCebNpbH37x5Y08A3OAHHo7fLfeRPVwyO3INF7gXrlN/EG6QX4SbaONVuEX9TdjHM6bCbXRheYPXuGL2hHdhDx18CNdwjU/hOvUv4Qb5W7iJO/wKt9Dx6sI+5l5XuI1HL/bHVi+cXqnlQcWhySKTOb+CmV7vkoWt0uqca1vEJlODoF9JU51pW91T7NdD5yIVWZOqCas6SYzKrdnq0AUb5/JRrxeJHoQm5Vhj/rbGAo5xBYUlDowxQhhkiMro6DtVZvSvsUPCXntWPc3ndFsU1P9zhQEC9M9cU7qy0nk6T4E9XxtSdXQrbsuelDSRXs1JErJCXta2VELqATZlV44RelzRiT8oZ0j/AAlabsgAAAB4nGNgYoAALgbsgJWRiZGZkYWRlZGNga0iM7EqMZMtvTQxLymToyojPy+9KiOTLaU0MyMxn4EBAMN8Cyw=') format('woff'),
6 | url('iconfont.ttf?t=1538684099545') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
7 | url('iconfont.svg?t=1538684099545#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family:"iconfont" !important;
12 | font-size:16px;
13 | font-style:normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-xiazai:before { content: "\e627"; }
19 |
20 | .icon-guanbi:before { content: "\e66c"; }
21 |
22 | .icon-zhongzhi:before { content: "\e633"; }
23 |
24 | .icon-duihao:before { content: "\eeda"; }
25 |
26 |
--------------------------------------------------------------------------------
/src/lib/capture/iconfont/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/lib/capture/iconfont/iconfont.eot
--------------------------------------------------------------------------------
/src/lib/capture/iconfont/iconfont.js:
--------------------------------------------------------------------------------
1 | (function(window){var svgSprite='';var script=function(){var scripts=document.getElementsByTagName("script");return scripts[scripts.length-1]}();var shouldInjectCss=script.getAttribute("data-injectcss");var ready=function(fn){if(document.addEventListener){if(~["complete","loaded","interactive"].indexOf(document.readyState)){setTimeout(fn,0)}else{var loadFn=function(){document.removeEventListener("DOMContentLoaded",loadFn,false);fn()};document.addEventListener("DOMContentLoaded",loadFn,false)}}else if(document.attachEvent){IEContentLoaded(window,fn)}function IEContentLoaded(w,fn){var d=w.document,done=false,init=function(){if(!done){done=true;fn()}};var polling=function(){try{d.documentElement.doScroll("left")}catch(e){setTimeout(polling,50);return}init()};polling();d.onreadystatechange=function(){if(d.readyState=="complete"){d.onreadystatechange=null;init()}}}};var before=function(el,target){target.parentNode.insertBefore(el,target)};var prepend=function(el,target){if(target.firstChild){before(el,target.firstChild)}else{target.appendChild(el)}};function appendSvg(){var div,svg;div=document.createElement("div");div.innerHTML=svgSprite;svgSprite=null;svg=div.getElementsByTagName("svg")[0];if(svg){svg.setAttribute("aria-hidden","true");svg.style.position="absolute";svg.style.width=0;svg.style.height=0;svg.style.overflow="hidden";prepend(svg,document.body)}}if(shouldInjectCss&&!window.__iconfont__svg__cssinject__){window.__iconfont__svg__cssinject__=true;try{document.write("")}catch(e){console&&console.log(e)}}ready(appendSvg)})(window)
--------------------------------------------------------------------------------
/src/lib/capture/iconfont/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
39 |
--------------------------------------------------------------------------------
/src/lib/capture/iconfont/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/lib/capture/iconfont/iconfont.ttf
--------------------------------------------------------------------------------
/src/lib/capture/iconfont/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/lib/capture/iconfont/iconfont.woff
--------------------------------------------------------------------------------
/src/lib/capture/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 截屏
6 |
7 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | 200*200
83 |
84 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/src/lib/capture/js/capture.js:
--------------------------------------------------------------------------------
1 | const { desktopCapturer, remote } = require('electron');
2 | const screen = remote.screen;
3 | const { width, height } = screen.getPrimaryDisplay().workAreaSize;
4 |
5 | const { Draw } = require(`${__dirname}/js/draw.js`);
6 |
7 | desktopCapturer
8 | .getSources({
9 | types: ['screen'],
10 | thumbnailSize: {
11 | width,
12 | height,
13 | },
14 | })
15 | .then((sources) => {
16 | const screenImgUrl = sources[0].thumbnail.toDataURL();
17 |
18 | const bg = document.querySelector('#bg');
19 | const rect = document.querySelector('.rect');
20 | const sizeInfo = document.querySelector('.size-info');
21 | const toolbar = document.querySelector('.toolbar');
22 | const draw = new Draw(screenImgUrl, bg, width, height, rect, sizeInfo, toolbar);
23 | document.addEventListener('mousedown', draw.startRect.bind(draw));
24 | document.addEventListener('mousemove', draw.drawingRect.bind(draw));
25 | document.addEventListener('mouseup', draw.endRect.bind(draw));
26 | })
27 | .catch((err) => console.log('err', err));
28 |
--------------------------------------------------------------------------------
/src/lib/capture/js/draw.js:
--------------------------------------------------------------------------------
1 | const { ipcRenderer, clipboard, nativeImage, remote } = require('electron');
2 |
3 | // 进程间转发
4 | // ipcRenderer.on('paste-from-clipboard', (e, arg)=>{
5 | // ipcRenderer.send('paste-from-clipboard-capwin', arg)
6 | // })
7 |
8 | class Draw {
9 | constructor(screenImgUrl, bg, screenWidth, screenHeight, rect, sizeInfo, toolbar) {
10 | this.screenImgUrl = screenImgUrl;
11 | this.screenWidth = screenWidth;
12 | this.screenHeight = screenHeight;
13 |
14 | this.$bgDOM = bg;
15 | //背景图数据存在canvas里面
16 | this.$bgCanvas;
17 | this.$bgCtx;
18 | this.initFullScreenCanvas();
19 |
20 | this.$rectDOM = rect;
21 | this.$rectCtx = this.$rectDOM.getContext('2d');
22 |
23 | this.$sizeInfoDom = sizeInfo;
24 | this.$toolbarDom = toolbar;
25 |
26 | //存储位置,矩形宽高,是否可画等meta信息
27 | this.selectRectMeta = {
28 | x: 0, //canvas最终的left
29 | y: 0, //canvas最终的top
30 | startX: 0, //鼠标一开始点那个点,e.pageX
31 | startY: 0, //鼠标一开始点那个点,e.pageY
32 | w: 0, //向量,宽,为负说明在startX的左边
33 | h: 0, //向量,高,为负说明在startY的左边
34 | drawing: false, //是否可画,mousedown为true,mouseup为false
35 | dragging: false, //是否可拖拽
36 | RGBAData: null, //矩阵图信息
37 | base64Data: null, //base64编码的二进制图片数据
38 | };
39 |
40 | //绑定this到原型链上,方便使用
41 | this.getMouseMeta = this.getMouseMeta.bind(this);
42 | this.setSizeInfo = this.setSizeInfo.bind(this);
43 | this.setToolBar = this.setToolBar.bind(this);
44 | this.destroy = this.destroy.bind(this);
45 | this.done = this.done.bind(this);
46 | this.sendMsg = this.sendMsg.bind(this);
47 | this.handleClose = this.handleClose.bind(this);
48 | this.handlePaste = this.handlePaste.bind(this);
49 |
50 | //初始化toolbar事件
51 | this.initToolBarEvent();
52 | }
53 |
54 | //记录屏幕快照,并赋值给背景
55 | async initFullScreenCanvas() {
56 | //用backgroundSize限定,否则append图片会伸缩
57 | this.$bgDOM.style.backgroundImage = `url(${this.screenImgUrl})`;
58 | this.$bgDOM.style.backgroundSize = `${this.screenWidth}px ${this.screenHeight}px`;
59 |
60 | //创建新的canvas上下文作为存储,方便取出里面的rgba信息
61 | this.$bgCanvas = document.createElement('canvas');
62 | this.$bgCtx = this.$bgCanvas.getContext('2d');
63 | // 拿到原图后,再加截屏遮罩
64 | const mask = document.querySelector('#mask');
65 | mask.style.backgroundColor = 'rgba(0, 0, 0, 0.3)';
66 |
67 | //新建一个图片,用来放进canvas存图片数据
68 | const imgCanvas = await new Promise((resolve) => {
69 | const img = new Image();
70 | img.src = this.screenImgUrl;
71 | if (img.complete) {
72 | resolve(img);
73 | } else {
74 | img.onload = () => resolve(img);
75 | }
76 | }).catch((err) => console.log(err));
77 |
78 | this.$bgCanvas.width = width;
79 | this.$bgCanvas.height = height;
80 | this.$bgCtx.drawImage(imgCanvas, 0, 0);
81 | }
82 |
83 | //开始按下,对应mousedown事件
84 | startRect(e) {
85 | console.log('startRect');
86 | this.drawing = true;
87 |
88 | //鼠标按下的定点坐标
89 | this.selectRectMeta.startX = e.pageX;
90 | this.selectRectMeta.startY = e.pageY;
91 | }
92 |
93 | //正在画矩形选区,对应mousemove
94 | drawingRect(e) {
95 | if (!this.drawing) return;
96 |
97 | this.getMouseMeta(e);
98 |
99 | //宽高需赋值绝对值为正
100 | this.$rectDOM.width = Math.abs(this.selectRectMeta.w);
101 | this.$rectDOM.height = Math.abs(this.selectRectMeta.h);
102 |
103 | this.$rectDOM.style.left = `${this.selectRectMeta.x}px`;
104 | this.$rectDOM.style.top = `${this.selectRectMeta.y}px`;
105 |
106 | //没有拉伸距离会报错
107 | if (!this.selectRectMeta.w || !this.selectRectMeta.h) return;
108 | //获取矩形坐标在整个fullscreen的位置,生成RGBAData传入回矩形选区
109 | this.selectRectMeta.RGBAData = this.$bgCtx.getImageData(
110 | this.selectRectMeta.x,
111 | this.selectRectMeta.y,
112 | Math.abs(this.selectRectMeta.w),
113 | Math.abs(this.selectRectMeta.h)
114 | );
115 | this.$rectCtx.putImageData(this.selectRectMeta.RGBAData, 0, 0);
116 |
117 | this.$rectCtx.fillStyle = 'white';
118 | this.$rectCtx.strokeStyle = 'black';
119 | this.$rectCtx.lineWidth = 2;
120 |
121 | this.$rectCtx.strokeRect(0, 0, Math.abs(this.selectRectMeta.w), Math.abs(this.selectRectMeta.h));
122 |
123 | this.$rectDOM.style.display = 'block';
124 |
125 | //尺寸信息
126 | this.setSizeInfo();
127 | }
128 |
129 | getMouseMeta(e) {
130 | // 计算坐标差值(宽高)
131 | this.selectRectMeta.w = e.pageX - this.selectRectMeta.startX;
132 | this.selectRectMeta.h = e.pageY - this.selectRectMeta.startY;
133 |
134 | //计算真正的x,y坐标,根据距离在鼠标定点的左右来判断,即大于0
135 | if (this.selectRectMeta.w > 0) {
136 | this.selectRectMeta.x = this.selectRectMeta.startX;
137 | } else {
138 | this.selectRectMeta.x = e.pageX;
139 | }
140 |
141 | if (this.selectRectMeta.h > 0) {
142 | this.selectRectMeta.y = this.selectRectMeta.startY;
143 | } else {
144 | this.selectRectMeta.y = e.pageY;
145 | }
146 | }
147 |
148 | //画完,对应mouseup事件
149 | endRect(e) {
150 | this.drawing = false;
151 |
152 | //转成base64
153 | this.selectRectMeta.base64Data = this.RGBA2ImageData(this.selectRectMeta.RGBAData);
154 |
155 | //画完显示工具条
156 | this.setToolBar();
157 | }
158 |
159 | //设置size-info,就是取宽高
160 | setSizeInfo() {
161 | this.$sizeInfoDom.style.display = 'block';
162 | this.$sizeInfoDom.style.left = `${this.selectRectMeta.x}px`;
163 | this.$sizeInfoDom.style.top = `${this.selectRectMeta.y - 25}px`;
164 | this.$sizeInfoDom.innerHTML = `${Math.abs(this.selectRectMeta.w)}*${Math.abs(this.selectRectMeta.h)}`;
165 | }
166 |
167 | //设置工具栏
168 | setToolBar() {
169 | this.$toolbarDom.style.display = 'block';
170 | this.$toolbarDom.style.left = `${this.selectRectMeta.x + Math.abs(this.selectRectMeta.w) - 100}px`;
171 | this.$toolbarDom.style.top = `${this.selectRectMeta.y + Math.abs(this.selectRectMeta.h)}px`;
172 | }
173 |
174 | //初始化工具栏事件
175 | initToolBarEvent() {
176 | this.$toolbarDom.querySelector('#js-tool-close').addEventListener('click', (e) => {
177 | console.log('close');
178 | this.destroy();
179 | });
180 |
181 | this.$toolbarDom.querySelector('#js-tool-ok').addEventListener('click', (e) => {
182 | console.log('ok');
183 | this.done();
184 | });
185 | }
186 |
187 | //关闭按钮
188 | destroy(data) {
189 | // this.sendMsg('close', data)
190 | this.handleClose(data);
191 | }
192 |
193 | //check按钮的动作
194 | done(e) {
195 | //写入图片到剪贴板
196 | const dataUrl = this.selectRectMeta.base64Data;
197 | const img = nativeImage.createFromDataURL(dataUrl);
198 | clipboard.writeImage(img);
199 |
200 | // 进程间转发 成功后,粘贴到输入框
201 | // this.sendMsg('paste', dataUrl)
202 |
203 | this.handlePaste(dataUrl);
204 | console.log('paste11');
205 |
206 | this.destroy({
207 | base64: dataUrl,
208 | });
209 | }
210 |
211 | handleClose(msg) {
212 | ipcRenderer.send('clip-page', {
213 | type: 'close',
214 | msg,
215 | });
216 | }
217 |
218 | handlePaste(msg) {
219 | const id = remote.getGlobal('sharedObject').mainId;
220 | ipcRenderer.sendTo(id, 'paste-pic-from-clipboard', msg);
221 | }
222 |
223 | sendMsg(type, msg) {
224 | ipcRenderer.send('clip-page', {
225 | type,
226 | msg,
227 | });
228 | }
229 |
230 | // 矩阵图转base64格式,原理是插入canvas里面,通过canvas转成图片
231 | RGBA2ImageData(RGBAImg) {
232 | const width = RGBAImg.width;
233 | const height = RGBAImg.height;
234 | const canvas = document.createElement('canvas');
235 | canvas.width = width;
236 | canvas.height = height;
237 | const ctx = canvas.getContext('2d');
238 | const imgData = ctx.createImageData(width, height);
239 | imgData.data.set(RGBAImg.data);
240 | ctx.putImageData(imgData, 0, 0);
241 | return canvas.toDataURL();
242 | }
243 | }
244 |
245 | exports.Draw = Draw;
246 |
--------------------------------------------------------------------------------
/src/lib/capture/main.js:
--------------------------------------------------------------------------------
1 | const { BrowserWindow, globalShortcut, ipcMain, app } = require('electron');
2 |
3 | const path = require('path');
4 | let capWin;
5 |
6 | //注册快捷键
7 | function createShortcut() {
8 | globalShortcut.register('CmdOrCtrl+Shift+A', captureScreen);
9 | // Esc 与 其他应用的快捷键冲突
10 | globalShortcut.register('CmdOrCtrl+Esc', () => {
11 | if (capWin) {
12 | capWin.close();
13 | capWin = null;
14 | }
15 | });
16 | ipcMain.on('capture-screen', captureScreen);
17 | }
18 |
19 | function createCaptureWindow() {
20 | // 创建浏览器窗口,只允许创建一个
21 | if (capWin) return console.log('只能有一个CaptureWindow');
22 | const { screen } = require('electron');
23 | const { width, height } = screen.getPrimaryDisplay().workAreaSize;
24 |
25 | capWin = new BrowserWindow({
26 | // window 使用 fullscreen, mac 设置为 undefined, 不可为 false
27 | fullscreen: process.platform !== 'darwin' || undefined,
28 | width,
29 | height,
30 | x: 0,
31 | y: 0,
32 | transparent: true,
33 | frame: false,
34 | movable: false,
35 | skipTaskbar: true,
36 | autoHideMenuBar: true,
37 | resizable: false,
38 | enableLargerThanScreen: true, // mac
39 | hasShadow: false,
40 | webPreferences: {
41 | nodeIntegration: true,
42 | contextIsolation: false,
43 | enableRemoteModule: true,
44 | },
45 | });
46 |
47 | app.dock.hide();
48 | capWin.setAlwaysOnTop(true, 'screen-saver');
49 | capWin.setVisibleOnAllWorkspaces(true);
50 | capWin.setSimpleFullScreen(true);
51 | // capWin.setFullScreenable(false) // mac
52 | capWin.loadFile(path.join(__dirname, './index.html'));
53 |
54 | // 打开开发者工具
55 | // capWin.webContents.openDevTools();
56 |
57 | capWin.on('closed', () => {
58 | capWin = null;
59 | });
60 | }
61 |
62 | function captureScreen() {
63 | createCaptureWindow();
64 | }
65 |
66 | ipcMain.on('clip-page', (event, { type, msg }) => {
67 | if (type === 'close') {
68 | if (capWin) {
69 | // todo 为了解决全屏问题
70 | capWin.setSimpleFullScreen(false);
71 | // 解决图片自动粘贴问题
72 | setTimeout(function () {
73 | capWin.close();
74 | capWin = null;
75 | }, 0);
76 | }
77 |
78 | // 进程间转发
79 | // }else if(type === 'paste'){
80 | // console.log('paste22')
81 | // event.sender.send('paste-from-clipboard', msg);
82 | }
83 | });
84 |
85 | exports.createShortcut = createShortcut;
86 |
--------------------------------------------------------------------------------
/src/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
84 |
85 | x
86 |
87 |

88 |
账号:
89 |
密码:密码错误
90 |
登录
91 |
92 |
93 |
131 |
132 |
--------------------------------------------------------------------------------
/src/main/crash-reporter.js:
--------------------------------------------------------------------------------
1 | const { crashReporter } = require('electron');
2 |
3 | function init() {
4 | crashReporter.start({
5 | productName: 'spiderchat',
6 | globalExtra: {
7 | companyName: 'spiderT',
8 | },
9 | submitURL: 'http://127.0.0.1:9999/crash',
10 | });
11 | }
12 | module.exports = {
13 | init,
14 | };
15 |
--------------------------------------------------------------------------------
/src/main/download.js:
--------------------------------------------------------------------------------
1 | const { session, app } = require('electron');
2 | const fs = require('fs');
3 | const path = require('path');
4 |
5 | module.exports = (win) =>
6 | session.defaultSession.on('will-download', async (event, item) => {
7 | const fileName = item.getFilename();
8 | const url = item.getURL();
9 | const startTime = item.getStartTime();
10 | const initialState = item.getState();
11 | const downloadPath = app.getPath('downloads');
12 |
13 | let fileNum = 0;
14 | let savePath = path.join(downloadPath, fileName);
15 |
16 | // savePath基础信息
17 | const ext = path.extname(savePath);
18 | const name = path.basename(savePath, ext);
19 | const dir = path.dirname(savePath);
20 |
21 | // 文件名自增逻辑
22 | while (fs.existsSync(savePath)) {
23 | fileNum += 1;
24 | savePath = path.format({
25 | dir,
26 | ext,
27 | name: `${name}(${fileNum})`,
28 | });
29 | }
30 |
31 | // 设置下载目录,阻止系统dialog的出现
32 | item.setSavePath(savePath);
33 |
34 | // 通知渲染进程,有一个新的下载任务
35 | win.webContents.send('new-download-item', {
36 | savePath,
37 | url,
38 | startTime,
39 | state: initialState,
40 | paused: item.isPaused(),
41 | totalBytes: item.getTotalBytes(),
42 | receivedBytes: item.getReceivedBytes(),
43 | });
44 |
45 | // 下载任务更新
46 | item.on('updated', (e, state) => {
47 | win.webContents.send('download-item-updated', {
48 | startTime,
49 | state,
50 | totalBytes: item.getTotalBytes(),
51 | receivedBytes: item.getReceivedBytes(),
52 | paused: item.isPaused(),
53 | });
54 | });
55 |
56 | // 下载任务完成
57 | item.on('done', (e, state) => {
58 | win.webContents.send('download-item-done', {
59 | startTime,
60 | state,
61 | });
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/src/main/ipc.js:
--------------------------------------------------------------------------------
1 | const { ipcMain, dialog, BrowserWindow, app, Notification } = require('electron');
2 | const fs = require('fs');
3 | const path = require('path');
4 | const mineType = require('mime-types');
5 | let unread = 0;
6 |
7 | const { createLoginWindow, close, send } = require('./windows');
8 | let picWin, settingWin;
9 |
10 | module.exports = function () {
11 | // 打开对话框事件dialog
12 | ipcMain.handle('open-directory-dialog', async (event) => {
13 | const res = new Promise((resolve, reject) =>
14 | resolve(
15 | dialog
16 | .showOpenDialog({
17 | // properties String -包含对话框应用的功能。支持以下值:
18 | // openFile - 允许选择文件
19 | // openDirectory - 允许选择文件夹
20 | // multiSelections-允许多选。
21 | // showHiddenFiles-显示对话框中的隐藏文件。
22 | // createDirectory macOS -允许你通过对话框的形式创建新的目录。
23 | // noResolveAliases macOS -禁用自动别名 (symlink) 路径解析。 选定的别名现在将返回别名路径而不是其目标路径。
24 | // treatPackageAsDirectory macOS -将包 (如 .app 文件夹) 视为目录而不是文件。
25 | properties: ['openFile', 'openDirectory'],
26 | filters: [
27 | {
28 | name: 'Images',
29 | extensions: ['jpg', 'jpeg', 'png', 'gif'],
30 | },
31 | // { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },
32 | // { name: 'Custom File Type', extensions: ['as'] },
33 | // { name: 'All Files', extensions: ['*'] }
34 | ],
35 | })
36 | .then((result) => {
37 | // console.log('result', result)
38 | // result { canceled: false, filePaths: [ '/Users/Desktop/11.jpg' ] }
39 | const { canceled, filePaths } = result;
40 | if (canceled) {
41 | return { event: 'canceled' };
42 | }
43 | const filePath = filePaths[0];
44 | let data = fs.readFileSync(filePath);
45 | data = new Buffer.from(data).toString('base64');
46 | const base64 = 'data:' + mineType.lookup(filePath) + ';base64,' + data;
47 | return { event: 'send', data: base64 };
48 | })
49 | .catch((err) => console.log(err))
50 | )
51 | );
52 | return res;
53 | });
54 |
55 | // 图片预览
56 | ipcMain.on('create-pic-window', (event, arg) => {
57 | picWin = new BrowserWindow({
58 | width: 800,
59 | height: 600,
60 | resizable: false,
61 | webPreferences: {
62 | nodeIntegration: true,
63 | contextIsolation: false,
64 | enableRemoteModule: true,
65 | },
66 | });
67 | global.sharedObject.picId = picWin.webContents.id;
68 |
69 | picWin.loadURL(path.join('file:', __dirname, '../pic.html'));
70 |
71 | picWin.webContents.on('did-finish-load', function () {
72 | picWin.webContents.send('pic-url', arg);
73 | });
74 |
75 | picWin.show();
76 |
77 | // picWin.webContents.openDevTools();
78 |
79 | picWin.on('closed', () => {
80 | picWin.destroy();
81 | });
82 | });
83 |
84 | // 收到消息 msg-receive
85 | ipcMain.handle('msg-receive', async (event, arg) => {
86 | console.log('handle msg-receive');
87 | unread += 1;
88 | app.setBadgeCount(unread);
89 | const res = new Promise((resolve, reject) => {
90 | const notification = new Notification({
91 | title: arg.title,
92 | body: arg.body,
93 | hasReply: true,
94 | });
95 | notification.show();
96 | notification.on('reply', (e, reply) => {
97 | resolve({ event: 'reply', text: reply });
98 | });
99 | notification.on('close', (e) => {
100 | resolve({ event: 'close' });
101 | });
102 | });
103 | return res;
104 | });
105 |
106 | // 打开设置页
107 | ipcMain.on('open-settings', () => {
108 | settingWin = new BrowserWindow({
109 | width: 400,
110 | height: 300,
111 | resizable: false,
112 | titleBarStyle: 'hiddenInset',
113 | webPreferences: {
114 | nodeIntegration: true,
115 | contextIsolation: false,
116 | enableRemoteModule: true,
117 | },
118 | });
119 |
120 | settingWin.loadURL(path.join('file:', __dirname, '../setting.html'));
121 |
122 | settingWin.show();
123 |
124 | settingWin.on('closed', () => {
125 | settingWin.destroy();
126 | });
127 | });
128 |
129 | // 退出登录
130 | ipcMain.on('login-out', () => {
131 | close();
132 | settingWin.destroy();
133 | createLoginWindow();
134 | });
135 |
136 | // 清除未读数
137 | app.on('browser-window-focus', () => {
138 | app.setBadgeCount(0);
139 | unread = 0;
140 | send('browser-window-focus');
141 | });
142 | };
143 |
--------------------------------------------------------------------------------
/src/main/main.js:
--------------------------------------------------------------------------------
1 | const { app, globalShortcut } = require('electron');
2 | const setAppMenu = require('./menus');
3 | const setTray = require('./tray');
4 | const { createLoginWindow, createWindow, show, close } = require('./windows');
5 | const path = require('path');
6 | const handleIPC = require('./ipc');
7 | const handleDownload = require('./download');
8 | const isDev = require('electron-is-dev');
9 |
10 | // 开机自启动
11 | app.setLoginItemSettings({
12 | openAtLogin: true,
13 | });
14 |
15 | app.whenReady().then(() => {
16 | setAppMenu();
17 | if (process.platform === 'darwin') {
18 | app.dock.setIcon(path.join(__dirname, '../resources/images/zhizhuxia_big.png'));
19 | }
20 | });
21 |
22 | const gotTheLock = app.requestSingleInstanceLock();
23 |
24 | if (!gotTheLock) {
25 | app.quit();
26 | } else {
27 | app.on('second-instance', show);
28 |
29 | app.on('will-finish-launching', () => {
30 | // 自动更新
31 | if (!isDev) {
32 | require('./updater.js');
33 | }
34 | require('./crash-reporter').init();
35 | });
36 |
37 | app.on('ready', () => {
38 | createLoginWindow();
39 | const win = createWindow();
40 | setTray();
41 | handleIPC();
42 | handleDownload(win);
43 | // 模拟crash
44 | // process.crash();
45 | });
46 |
47 | app.on('activate', show);
48 |
49 | app.on('before-quit', close);
50 |
51 | app.on('will-quit', () => {
52 | // Unregister all shortcuts.
53 | globalShortcut.unregisterAll();
54 | });
55 |
56 | app.on('open-url', (event, url) => {
57 | event.preventDefault();
58 | console.log('open-url');
59 | });
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/menus/about.js:
--------------------------------------------------------------------------------
1 | const openAboutWindow = require('about-window').default;
2 | const path = require('path');
3 |
4 | const aboutWindow = () => openAboutWindow({
5 | icon_path: path.join(__dirname, '../../resources/images/zhizhuxia_big.png'),
6 | package_json_dir: path.resolve(__dirname + '../../../../'),
7 | copyright: 'Copyright (c) 2020 TT',
8 | })
9 |
10 | module.exports = aboutWindow;
--------------------------------------------------------------------------------
/src/main/menus/file-menu.js:
--------------------------------------------------------------------------------
1 | const submenu = [{
2 | label: '发起会话...',
3 | accelerator: 'CommandOrControl+N',
4 | },
5 | {
6 | label: '置顶/取消置顶',
7 | accelerator: 'up+CommandOrControl+T',
8 | },
9 | {
10 | label: '静音/取消静音',
11 | accelerator: 'up+CommandOrControl+M',
12 | },
13 | {
14 | type: 'separator'
15 | },
16 | {
17 | label: '备份与恢复',
18 | },
19 | {
20 | label: '关闭',
21 | accelerator: 'CommandOrControl+W',
22 | },
23 | ]
24 |
25 | const fileMenu = {
26 | label: '&文件',
27 | submenu,
28 | };
29 |
30 | module.exports = fileMenu;
31 |
--------------------------------------------------------------------------------
/src/main/menus/index.js:
--------------------------------------------------------------------------------
1 | const { Menu, app } = require('electron');
2 | const fileMenu = require('./file-menu');
3 | const macAppMenu = require('./mac-app-menu');
4 | const isMac = process.platform === 'darwin';
5 | function createMenuTemplate() {
6 | const windowMenu = {
7 | label: '窗口',
8 | submenu: [
9 | { role: 'minimize' },
10 | { role: 'zoom' },
11 | { type: 'separator' },
12 | { role: 'front' },
13 | { type: 'separator' },
14 | { role: 'window' }
15 | ]
16 | };
17 |
18 | const editMenu = {
19 | label: '编辑',
20 | submenu: [
21 | { role: 'undo' },
22 | { role: 'redo' },
23 | { type: 'separator' },
24 | { role: 'cut' },
25 | { role: 'copy' },
26 | { role: 'paste' },
27 | ...(isMac ? [
28 | { role: 'pasteAndMatchStyle' },
29 | { role: 'delete' },
30 | { role: 'selectAll' },
31 | { type: 'separator' },
32 | {
33 | label: 'Speech',
34 | submenu: [
35 | { role: 'startspeaking' },
36 | { role: 'stopspeaking' }
37 | ]
38 | }
39 | ] : [
40 | { role: 'delete' },
41 | { type: 'separator' },
42 | { role: 'selectAll' }
43 | ])
44 | ]
45 | };
46 |
47 | const viewMenu = {
48 | label: '显示',
49 | submenu: [
50 | { role: 'resetzoom' },
51 | { role: 'zoomin' },
52 | { role: 'zoomout' },
53 | { type: 'separator' },
54 | { role: 'togglefullscreen' }
55 | ]
56 | }
57 |
58 | const helpMenu = {
59 | role: 'help',
60 | submenu: [
61 | {
62 | label: 'Learn More',
63 | click: async () => {
64 | const { shell } = require('electron')
65 | await shell.openExternal('https://electronjs.org')
66 | }
67 | },
68 | {
69 | role: 'toggledevtools'
70 | }
71 | ]
72 | }
73 |
74 | return [
75 | macAppMenu,
76 | fileMenu,
77 | editMenu,
78 | viewMenu,
79 | windowMenu,
80 | helpMenu
81 | ].filter(menu => menu !== null)
82 |
83 | }
84 |
85 | function setAppMenu() {
86 | const menuTemplate = createMenuTemplate();
87 | const appMenu = Menu.buildFromTemplate(menuTemplate);
88 | app.applicationMenu = appMenu;
89 | }
90 |
91 | module.exports = setAppMenu;
--------------------------------------------------------------------------------
/src/main/menus/mac-app-menu.js:
--------------------------------------------------------------------------------
1 | const { app } = require('electron');
2 | const aboutWindow = require('./about');
3 |
4 | const name = app.getName();
5 | const macAppMenu = {
6 | label: name,
7 | submenu: [
8 | {
9 | label: '关于' + name,
10 | click: aboutWindow
11 | },
12 | { type: 'separator' },
13 | { role: 'hide' },
14 | { role: 'hideothers' },
15 | { role: 'unhide' },
16 | { type: 'separator' },
17 | { role: 'quit' },
18 | ]
19 | }
20 |
21 |
22 | module.exports = macAppMenu;
--------------------------------------------------------------------------------
/src/main/tray/index.js:
--------------------------------------------------------------------------------
1 | const {
2 | app,
3 | Menu,
4 | Tray
5 | } = require('electron');
6 | const {
7 | show
8 | } = require('../windows');
9 | const path = require('path');
10 |
11 | let tray;
12 |
13 | function setTray() {
14 | tray = new Tray(path.resolve(__dirname, '../../resources/images/zhizhuxia_small.png'));
15 | const contextMenu = Menu.buildFromTemplate([{
16 | label: '显示',
17 | click: show
18 | },
19 | {
20 | label: '退出',
21 | click: app.quit
22 | }
23 | ])
24 | tray.setContextMenu(contextMenu)
25 | tray.setToolTip('spiderChat')
26 | }
27 |
28 | module.exports = setTray;
--------------------------------------------------------------------------------
/src/main/updater.js:
--------------------------------------------------------------------------------
1 | const { autoUpdater, app, dialog } = require('electron');
2 |
3 | if (process.platform == 'darwin') {
4 | autoUpdater.setFeedURL('http://127.0.0.1:9999/darwin?version=' + app.getVersion());
5 | } else {
6 | autoUpdater.setFeedURL('http://127.0.0.1:9999/win32?version=' + app.getVersion());
7 | }
8 |
9 | autoUpdater.checkForUpdates();
10 | // 定时轮训( 30s), 或者服务端推送
11 | setInterval(() => autoUpdater.checkForUpdates(), 30000);
12 |
13 | autoUpdater.on('update-available', () => {
14 | console.log('update-available');
15 | });
16 |
17 | autoUpdater.on('update-downloaded', (e, notes, version) => {
18 | // 提醒用户更新
19 | app.whenReady().then(() => {
20 | const clickId = dialog.showMessageBoxSync({
21 | type: 'info',
22 | title: '升级提示',
23 | message: '新版本:主题升级,自动匹配系统深色主题',
24 | buttons: ['马上升级', '手动重启'],
25 | cancelId: 1,
26 | });
27 | if (clickId === 0) {
28 | autoUpdater.quitAndInstall();
29 | app.quit();
30 | }
31 | });
32 | });
33 |
34 | autoUpdater.on('error', (err) => {
35 | console.log('error', err);
36 | });
37 |
--------------------------------------------------------------------------------
/src/main/windows/index.js:
--------------------------------------------------------------------------------
1 | const { BrowserWindow, ipcMain, nativeTheme } = require('electron');
2 | const isDev = require('electron-is-dev');
3 | const path = require('path');
4 |
5 | let win,
6 | loginWin,
7 | willQuitApp = false;
8 | const { createShortcut } = require('../../lib/capture/main');
9 |
10 | process.on('unhandledRejection', (reason, p) => {
11 | console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
12 | });
13 |
14 | function createLoginWindow() {
15 | loginWin = new BrowserWindow({
16 | width: 300,
17 | height: 400,
18 | frame: false,
19 | webPreferences: {
20 | nodeIntegration: true,
21 | contextIsolation: false,
22 | enableRemoteModule: true,
23 | },
24 | resizable: false,
25 | });
26 |
27 | willQuitApp = false;
28 | loginWin.loadFile(path.join(__dirname, '../../login.html'));
29 | }
30 |
31 | // 登录
32 | ipcMain.on('login-error', (event, arg) => {
33 | console.log('login-error');
34 | // // todo 未生效???
35 | // loginWin.flashFrame(true);
36 | });
37 |
38 | ipcMain.on('login-success', (event, arg) => {
39 | console.log('login-success');
40 | createWindow();
41 | loginWin.close();
42 | win.setSize(900, 700);
43 | win.center();
44 | });
45 |
46 | ipcMain.on('close-login', (event, arg) => {
47 | console.log('close-login');
48 | loginWin && loginWin.close();
49 | close();
50 | });
51 |
52 | function createWindow() {
53 | // 创建浏览器窗口
54 | win = new BrowserWindow({
55 | width: 0,
56 | height: 0,
57 | webPreferences: {
58 | nodeIntegration: true,
59 | contextIsolation: false,
60 | enableRemoteModule: true,
61 | },
62 | minWidth: 800,
63 | minHeight: 600,
64 | titleBarStyle: 'hiddenInset',
65 | show: false, // 先隐藏
66 | icon: path.join(__dirname, '../../resources/images/zhizhuxia.png'),
67 | backgroundColor: '#f3f3f3', // 优化白屏,设置窗口底色
68 | });
69 |
70 | global.sharedObject = {
71 | mainId: win.webContents.id,
72 | };
73 |
74 | // 初始化截图
75 | createShortcut();
76 |
77 | win.on('ready-to-show', () => win.show()); // 初始化后显示
78 |
79 | win.on('close', (e) => {
80 | console.log('close', willQuitApp);
81 | if (willQuitApp) {
82 | win = null;
83 | } else {
84 | e.preventDefault();
85 | win.hide();
86 | }
87 | });
88 |
89 | if (isDev) {
90 | win.loadURL('http://localhost:9200');
91 | } else {
92 | win.loadFile(path.join(__dirname, '../../../build/index.html'));
93 | }
94 |
95 | // // 打开开发者工具
96 | // win.webContents.openDevTools()
97 |
98 | // 进程间转发
99 | // ipcMain.on('paste-from-clipboard-capwin', (event, arg) => {
100 | // console.log('paste-from-clipboard-capwin')
101 | // win.webContents.send('paste-from-clipboard-mainwin', arg)
102 | // })
103 |
104 | nativeTheme.on('updated', function (e) {
105 | const darkMode = nativeTheme.shouldUseDarkColors;
106 | console.log('updateddarkMode', darkMode);
107 | send('change-mode', darkMode);
108 | });
109 |
110 | return win;
111 | }
112 |
113 | function show() {
114 | win && win.show();
115 | }
116 |
117 | function close() {
118 | willQuitApp = true;
119 | win && win.close();
120 | }
121 |
122 | function send(channel, ...args) {
123 | win && win.webContents.send(channel, ...args);
124 | }
125 |
126 | module.exports = {
127 | createLoginWindow,
128 | createWindow,
129 | show,
130 | close,
131 | send,
132 | };
133 |
--------------------------------------------------------------------------------
/src/pic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | spiderChat
8 |
9 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
保存成功
102 |
103 |
104 |
105 |
185 |
186 |
--------------------------------------------------------------------------------
/src/renderer/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { ipcRenderer, remote } from 'electron';
3 |
4 | import User from './container/User/index.jsx';
5 | import Chat from './container/Chat/index.jsx';
6 |
7 | export default function App() {
8 | useEffect(() => {
9 | ipcRenderer.on('download-item-done', (e) => {
10 | console.log('下载完成✅');
11 |
12 | const id = remote.getGlobal('sharedObject').picId;
13 | ipcRenderer.sendTo(id, 'download-item-done');
14 | });
15 | }, []);
16 |
17 | return (
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/src/renderer/components/EmojiPackage/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './index.scss';
3 |
4 | export default function EmojiPackage(props) {
5 | const emojiArr = [
6 | '😊',
7 | '😢',
8 | '😄',
9 | '🔥',
10 | '👌',
11 | '👀',
12 | '🐦',
13 | '😯',
14 | '👎',
15 | '🤮',
16 | '🀄️',
17 | '😔',
18 | '😁',
19 | '👿',
20 | '🐢',
21 | '🐑',
22 | '🐎',
23 | '🐷',
24 | '😍',
25 | '❤️',
26 | '🌹',
27 | '💩',
28 | '👼',
29 | '🍦',
30 | '🍰',
31 | '🐻',
32 | '🍞',
33 | '🐼',
34 | '🐟',
35 | '🐬',
36 | '⛽️',
37 | '🏠',
38 | '🚗',
39 | '😼',
40 | '🚴',
41 | '🏃',
42 | '😯',
43 | '🐶',
44 | '👸',
45 | '🧙',
46 | '🌧️',
47 | '🌞',
48 | ];
49 |
50 | function sendEmoji(e, item) {
51 | e.stopPropagation();
52 | props.sendEmoji(item);
53 | }
54 |
55 | document.body.addEventListener(
56 | 'click',
57 | (e) => {
58 | if (e.target.matches('.emoji-wrap *')) {
59 | return;
60 | }
61 | props.handleLeave();
62 | },
63 | false
64 | );
65 |
66 | return (
67 |
68 | {emojiArr.map((item, index) => (
69 | sendEmoji(e, item)}>
70 | {item}
71 |
72 | ))}
73 |
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/src/renderer/components/EmojiPackage/index.scss:
--------------------------------------------------------------------------------
1 | .emoji-wrap{
2 | position: absolute;
3 | background-color: #fff;
4 | top: -260px;
5 | left: -82px;
6 | width: 224px;
7 | height: 256px;
8 | border-radius: 6px;
9 | box-shadow: 2px 2px 8px #ccc;
10 | padding: 10px;
11 | box-sizing: border-box;
12 | &::before{
13 | position: absolute;
14 | display: inline-block;
15 | top: 246px;
16 | left: 92px;
17 | width: 0;
18 | height: 0px;
19 | content: '';
20 | border-style: solid;
21 | border-width: 8px;
22 | border-color: #fff #fff transparent transparent;
23 | transform: rotate(135deg);
24 | box-shadow: 2px -2px 2px #ccc;
25 | }
26 | .emoji-item{
27 | display: inline-block;
28 | width: 34px;
29 | height: 34px;
30 | line-height: 34px;
31 | text-align: center;
32 | font-size: 14px;
33 | &:hover{
34 | font-size: 22px;
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/src/renderer/components/Message/Image.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Image.scss';
3 | import { ipcRenderer } from 'electron';
4 |
5 | function handleZoom(src) {
6 | ipcRenderer.send('create-pic-window', src);
7 | }
8 |
9 | const Image = (props) => {
10 | const content = props.content;
11 | return (
12 |
13 |
handleZoom(content)} />
14 |
15 | );
16 | };
17 |
18 | export default Image;
19 |
--------------------------------------------------------------------------------
/src/renderer/components/Message/Image.scss:
--------------------------------------------------------------------------------
1 | .msg-text{
2 | img{
3 | max-height: 70px;
4 | width: auto;
5 | }
6 | }
--------------------------------------------------------------------------------
/src/renderer/components/Message/Text.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Text.scss';
3 |
4 | const Text = (props) => {
5 | return (
6 |
7 | {props.content}
8 |
9 | );
10 | };
11 |
12 | export default Text;
--------------------------------------------------------------------------------
/src/renderer/components/Message/Text.scss:
--------------------------------------------------------------------------------
1 | .msg-text {
2 | background: #fff;
3 | border-radius: 5px;
4 | padding: 5px 10px;
5 | }
--------------------------------------------------------------------------------
/src/renderer/components/Message/Tip.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Tip.scss';
3 |
4 | const Tip = (props) => {
5 | return (
6 |
7 | {props.content}
8 |
9 | );
10 | };
11 |
12 | export default Tip;
--------------------------------------------------------------------------------
/src/renderer/components/Message/Tip.scss:
--------------------------------------------------------------------------------
1 | .msg-tip{
2 | text-align: center;
3 | color: #333;
4 | }
--------------------------------------------------------------------------------
/src/renderer/components/Message/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Text from './Text';
3 | import Tip from './Tip';
4 | import Image from './Image.jsx';
5 | import { USER_NAME } from '../../constants';
6 | import './index.scss';
7 |
8 | const AvatarRender = (msg) => {
9 | return (
10 |
14 | );
15 | };
16 |
17 | const MsgRender = (msg) => {
18 | let content;
19 | switch (msg.type) {
20 | // 文本
21 | case 1:
22 | content = ;
23 | break;
24 | // 系统
25 | case 2:
26 | content = ;
27 | break;
28 | // 图片
29 | case 3:
30 | content = ;
31 | break;
32 | }
33 |
34 | return (
35 |
36 | {msg.type !== 2 ? AvatarRender(msg) : null}
37 | {content}
38 |
39 | );
40 | };
41 |
42 | export default MsgRender;
43 |
--------------------------------------------------------------------------------
/src/renderer/components/Message/index.scss:
--------------------------------------------------------------------------------
1 | .chat-avatar{
2 | display: inline-block;
3 | background-color: #fff;
4 | width: 32px;
5 | height: 32px;
6 | border-radius: 2px;
7 | }
8 |
9 | .msg-item{
10 | margin: 18px 0;
11 | .msg-text{
12 | vertical-align: top;
13 | padding: 8px;
14 | display: inline-block;
15 | max-width: 300px;
16 | }
17 | &.left .msg-text{
18 | position: relative;
19 | margin-left: 10px;
20 | &::before{
21 | display: block;
22 | content: '';
23 | width: 0;
24 | height: 0;
25 | border-style:solid;
26 | border-width: 6px;
27 | border-color: transparent #fff transparent transparent;
28 | position: absolute;
29 | left: -12px;
30 | top: 8px;
31 | }
32 | }
33 |
34 | &.right{
35 | img, .msg-text{
36 | float:right;
37 | margin-right: 10px;
38 | position: relative;
39 | }
40 | .msg-text{
41 | background-color: rgb(158, 231, 101);
42 | &::before{
43 | display: block;
44 | content: '';
45 | width: 0;
46 | height: 0;
47 | border-style:solid;
48 | border-width: 6px;
49 | border-color: transparent transparent transparent rgb(158, 231, 101);
50 | position: absolute;
51 | right: -12px;
52 | top: 6px;
53 | }
54 | }
55 | }
56 | }
57 |
58 | .clearfix:after{
59 | content: "020";
60 | display: block;
61 | height: 0;
62 | clear: both;
63 | visibility: hidden;
64 | }
65 |
66 | .clearfix {
67 | zoom: 1;
68 | }
--------------------------------------------------------------------------------
/src/renderer/constants.js:
--------------------------------------------------------------------------------
1 | const USER_NAME = 'ivy';
2 | const SPIDER_MAN = 'zhizhuxia';
3 |
4 | export { USER_NAME, SPIDER_MAN };
5 |
--------------------------------------------------------------------------------
/src/renderer/container/Chat/Chatlist.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useContext } from 'react';
2 | import { MyContext } from '../../context-manager';
3 | import { SPIDER_MAN } from '../../constants';
4 |
5 | import './index.scss';
6 |
7 | const users = [
8 | {
9 | name: '蜘蛛侠',
10 | avatar: 'zhizhuxia',
11 | },
12 | {
13 | name: '小丑',
14 | avatar: 'xiaochou',
15 | },
16 | {
17 | name: '蚁人',
18 | avatar: 'yiren',
19 | },
20 | {
21 | name: '超人',
22 | avatar: 'chaoren',
23 | },
24 | {
25 | name: '蝙蝠侠',
26 | avatar: 'bianfuxia',
27 | },
28 | {
29 | name: '钢铁侠',
30 | avatar: 'gangtiexia',
31 | },
32 | {
33 | name: '金刚狼',
34 | avatar: 'jinganglang',
35 | },
36 | {
37 | name: '雷神',
38 | avatar: 'leishen',
39 | },
40 | {
41 | name: '绿灯侠',
42 | avatar: 'lvdengxia',
43 | },
44 | {
45 | name: '绿巨人',
46 | avatar: 'lvjuren',
47 | },
48 | {
49 | name: '美国队长',
50 | avatar: 'meiguoduichang',
51 | },
52 | {
53 | name: '圣诞老人',
54 | avatar: 'ren',
55 | },
56 | {
57 | name: '闪电侠',
58 | avatar: 'shandianxia',
59 | },
60 | {
61 | name: '死侍',
62 | avatar: 'sishi',
63 | },
64 | ];
65 |
66 | export default function Chatlist(props) {
67 | console.log('props', props);
68 |
69 | useEffect(() => {
70 | fetchData();
71 | }, []);
72 |
73 | const { setMsgData } = useContext(MyContext);
74 | function renderCurMsg(user) {
75 | const msgData = props.msgData;
76 | if (msgData && msgData.length) {
77 | const last = msgData[msgData.length - 1];
78 | return last.type === 3 ? '[图片]' : last.content;
79 | } else {
80 | return `您已添加了${user.name},现在可以开始聊天了。`;
81 | }
82 | }
83 |
84 | function ListItem(user) {
85 | return (
86 |
87 |
88 |
})
89 |
90 |
91 |
{user.name}
92 | {user.avatar === SPIDER_MAN ? (
93 |
{renderCurMsg(user)}
94 | ) : (
95 |
{`您已添加了${user.name},现在可以开始聊天了。`}
96 | )}
97 |
98 |
2021/06/25
99 |
100 | );
101 | }
102 |
103 | function fetchData() {
104 | fetch('http://127.0.0.1:1234/allmsgs')
105 | .then((response) => response.json())
106 | .then((response) => {
107 | setMsgData(response || []);
108 | })
109 | .catch((error) => console.log(error));
110 | }
111 |
112 | function getMsg(user) {
113 | if (user.avatar !== SPIDER_MAN) {
114 | return;
115 | }
116 | fetchData();
117 | }
118 | return {users.map((item) => ListItem(item))}
;
119 | }
120 |
--------------------------------------------------------------------------------
/src/renderer/container/Chat/Messages.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useContext } from 'react';
2 | import { ipcRenderer, remote } from 'electron';
3 | import MsgRender from '../../components/Message';
4 | import EmojiPackage from '../../components/EmojiPackage';
5 | import ContentEditable from 'react-contenteditable';
6 | import { msgBody } from '../../utils';
7 | import { USER_NAME, SPIDER_MAN } from '../../constants';
8 | import { MyContext } from '../../context-manager';
9 | import './index.scss';
10 |
11 | let unread = 0;
12 | const app = remote.app;
13 |
14 | const TO_ID = SPIDER_MAN;
15 | const FROM_ID = USER_NAME;
16 | ipcRenderer.setMaxListeners(100);
17 |
18 | const socket = new WebSocket('ws://localhost:8080/ws');
19 | socket.onopen = (event) => {
20 | console.log('onopen');
21 | };
22 | socket.onclose = (event) => {
23 | console.log('onclose');
24 | };
25 |
26 | export default function Messages(props) {
27 | const { msgData = [] } = props;
28 | const { setMsgData } = useContext(MyContext);
29 | const [isShowEmoji, toggleShowEmoji] = useState(false);
30 | const msgBox = React.createRef();
31 | const contentEditable = React.createRef();
32 | const [editHtml, setHtml] = useState('');
33 |
34 | useEffect(() => {
35 | setTimeout(() => scrollToView(), 300);
36 | // 'paste-pic-from-clipboard'
37 | ipcRenderer.on('paste-pic-from-clipboard', (e, arg) => {
38 | console.log('paste-pic-from-clipboard');
39 | handlePasteFromIpc(arg);
40 | });
41 |
42 | // 清除未读数
43 | ipcRenderer.on('browser-window-focus', () => {
44 | unread = 0;
45 | })
46 | }, [])
47 |
48 | socket.onmessage = (event) => {
49 | const msg = JSON.parse(event.data);
50 | if (msg.fromId !== TO_ID) {
51 | return
52 | }
53 |
54 | handleMsg(msg.type, msg.content, 'receive');
55 |
56 | if (document.hidden) {
57 | createNativeNotification(msg);
58 | // createHtmlNotification(msg)
59 |
60 | unread += 1;
61 | app.setBadgeCount(unread);
62 | // handleNativeNoti(msg);
63 | }
64 | };
65 |
66 | function createHtmlNotification(msg) {
67 | // h5通知
68 | const option = {
69 | title: '蜘蛛侠',
70 | body: msg.content,
71 | };
72 |
73 | // 渲染进程的Notification
74 | const chatNotication = new Notification(option.title, option);
75 | chatNotication.onclick = () => {
76 | console.log('clickchatNotication')
77 | }
78 | }
79 |
80 | async function createNativeNotification(msg) {
81 | // 发给主进程,展示Notification
82 | const res = await ipcRenderer.invoke('msg-receive', { title: '蜘蛛侠', body: msg.content });
83 | console.log('res', res);
84 | res.event === 'reply' ? onSend(res.text) : onClose();
85 | }
86 |
87 | function handleNativeNoti(msg) {
88 | const notification = new remote.Notification({
89 | title: '蜘蛛侠',
90 | body: msg.content,
91 | hasReply: true,
92 | });
93 | notification.show();
94 | notification.on('reply', (e, reply) => {
95 | onSend(reply)
96 | });
97 | notification.on('close', (e) => {
98 | onClose()
99 | });
100 | }
101 |
102 | function onClose() { }
103 |
104 | function onSend(data) {
105 | handleMsg(1, data);
106 | // todo 解决 收到消息,聊天区域没有及时渲染
107 | document.getElementsByClassName('list-item')[0].click();
108 | }
109 |
110 | function handleMsg(msgType, data, type) {
111 | console.log('handleMsg', msgType, data, type)
112 | if (!data) {
113 | return
114 | }
115 | let msg;
116 | if (type === 'receive') {
117 | msg = msgBody(msgType, data, TO_ID, FROM_ID);
118 | } else {
119 | msg = msgBody(msgType, data, FROM_ID, TO_ID);
120 | socket.send(JSON.stringify(msg));
121 | addMsg(msg)
122 | }
123 | setMsgData(pre => [...pre, msg]);
124 | // todo 解决 收到消息,聊天区域没有及时渲染
125 | setHtml(editHtml + ' ');
126 | scrollToView();
127 | }
128 |
129 | function addMsg(data) {
130 | fetch('http://127.0.0.1:1234/addmsg', {
131 | body: JSON.stringify(data),
132 | method: 'POST',
133 | headers: {
134 | 'content-type': 'application/json'
135 | },
136 | })
137 | .then((response) => console.log(response))
138 | .catch((error) => console.log(error))
139 | }
140 |
141 | function captureScreen() {
142 | ipcRenderer.send('capture-screen');
143 | }
144 |
145 | function getSrcFromImg(str) {
146 | const imgReg = /|\/>)/gi; //匹配图片中的img标签
147 | const srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i; // 匹配图片中的src
148 | const arr = str.match(imgReg); //筛选出所有的img
149 | const src = arr[0].match(srcReg);
150 | return src[1];
151 | }
152 |
153 | // 聊天内容向上滚动到可见区域
154 | function scrollToView() {
155 | const msgBoxEle = document.getElementById('msg-box');
156 | const msgChildNodes = msgBoxEle.childNodes;
157 | const len = msgChildNodes.length;
158 | // 因为加了一个‘’dom
159 | const lastChildEle = msgChildNodes[len - 2];
160 | lastChildEle && setTimeout(function () {
161 | lastChildEle.scrollIntoView();
162 | }, 100);
163 | }
164 |
165 | function handleKeyDown(e) {
166 | if (e.keyCode === 13 || e.which === 13) {
167 | e.preventDefault();
168 |
169 | if (!editHtml || !editHtml.trim() || !window.WebSocket) {
170 | return;
171 | }
172 | if (socket.readyState === WebSocket.OPEN) {
173 | // 判断发送文本,还是图片
174 | if (editHtml.includes('
`;
217 | setHtml(eleHtml);
218 | }
219 |
220 | function handlePaste(e) {
221 | const cbd = e.clipboardData;
222 | if (!(e.clipboardData && e.clipboardData.items)) {
223 | return;
224 | }
225 | for (let i = 0; i < cbd.items.length; i++) {
226 | const item = cbd.items[i];
227 | if (item.kind == 'file') {
228 | const blob = item.getAsFile();
229 | if (blob.size === 0) {
230 | return;
231 | }
232 | const reader = new FileReader();
233 | const imgs = new Image();
234 | imgs.file = blob;
235 | reader.onload = (e) => {
236 | const imgPath = e.target.result;
237 | imgs.src = imgPath;
238 | const eleHtml = `${editHtml}
`;
239 | setHtml(eleHtml);
240 | };
241 | reader.readAsDataURL(blob);
242 | }
243 | }
244 | }
245 |
246 | // 进程间转发
247 | // ipcRenderer.on('paste-from-clipboard-mainwin', (e, arg) => {
248 | // console.log('paste-from-clipboard-mainwin');
249 | // handlePasteFromIpc(arg);
250 | // });
251 |
252 |
253 |
254 | return (
255 |
256 |
蜘蛛侠
257 |
258 |
259 | {msgData.length ? msgData.map((item) => MsgRender(item)) : null}
260 |
261 |
262 |
263 | {isShowEmoji &&
}
264 |
265 |
266 |
267 |
268 | {/* // 以下功能暂时未开发 */}
269 | {/*
270 |
271 | */}
272 |
273 |
283 |
284 |
285 | );
286 | }
287 |
--------------------------------------------------------------------------------
/src/renderer/container/Chat/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import './index.scss';
3 | import Chatlist from './Chatlist';
4 | import Messages from './Messages.jsx';
5 | import { MyContext } from '../../context-manager';
6 | import { ipcRenderer } from 'electron';
7 |
8 | function addDarkMode() {
9 | document.getElementsByTagName('body')[0].classList.add("dark-mode");
10 | }
11 |
12 | function removeDarkMode() {
13 | document.getElementsByTagName('body')[0].classList.remove("dark-mode");
14 | }
15 |
16 | if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
17 | addDarkMode();
18 | } else {
19 | removeDarkMode();
20 | }
21 |
22 | ipcRenderer.on('change-mode', (e, arg) => {
23 | console.log('change-mode')
24 | if (arg) {
25 | addDarkMode();
26 | } else {
27 | removeDarkMode();
28 | }
29 | })
30 |
31 | function Chat() {
32 | const [msgData, setMsgData] = useState([]);
33 | return (
34 |
35 |
36 |
37 |
38 |
39 |
40 | +
41 |
42 |
43 |
44 |
45 |
46 |
47 | );
48 | }
49 |
50 | export default Chat;
--------------------------------------------------------------------------------
/src/renderer/container/Chat/index.scss:
--------------------------------------------------------------------------------
1 | .right-wrap {
2 | float: left;
3 | width: calc(100% - 70px);
4 | height: 100%;
5 | }
6 | .list-wrap {
7 | margin-top: 50px;
8 | border-right: 1px solid #eee;
9 | }
10 | .chat-wrap {
11 | width: 260px;
12 | float: left;
13 | height: 100%;
14 | overflow: scroll;
15 | box-sizing: border-box;
16 | .search {
17 | box-sizing: border-box;
18 | padding: 14px 10px;
19 | width: 260px;
20 | height: 50px;
21 | position: fixed;
22 | background-color: #fff;
23 | z-index: 99;
24 | input {
25 | width: calc(100% - 84px);
26 | background-color: #eee;
27 | border: none;
28 | height: 22px;
29 | line-height: 22px;
30 | font-size: 12px;
31 | border-radius: 4px;
32 | padding-left: 26px;
33 | }
34 | .icon {
35 | display: block;
36 | width: 20px;
37 | height: 20px;
38 | background: url('../../../resources/images/icons/sousuo.png') no-repeat;
39 | position: absolute;
40 | top: 18px;
41 | left: 14px;
42 | }
43 | .plus {
44 | display: inline-block;
45 | margin-left: 10px;
46 | vertical-align: middle;
47 | border-radius: 2px;
48 | width: 24px;
49 | height: 20px;
50 | border: 1px solid #ddd;
51 | color: #999;
52 | font-size: 20px;
53 | line-height: 16px;
54 | text-align: center;
55 | }
56 | }
57 | .list-item {
58 | -webkit-user-select: none;
59 | height: 66px;
60 | padding: 8px 0;
61 | box-sizing: border-box;
62 | border-bottom: 1px solid #ddd;
63 | position: relative;
64 | &:first-child {
65 | background-color: #ddd;
66 | }
67 | padding-left: 10px;
68 | }
69 | .avatar {
70 | float: left;
71 | border: 1px solid #ddd;
72 | width: 44px;
73 | height: 44px;
74 | overflow: hidden;
75 | background: #fff;
76 | img {
77 | width: 44px;
78 | height: 44px;
79 | }
80 | }
81 | .info {
82 | float: left;
83 | margin-left: 10px;
84 | line-height: 22px;
85 | .name {
86 | font-weight: 500;
87 | font-size: 14px;
88 | margin: 0;
89 | width: 160px;
90 | overflow: hidden;
91 | text-overflow: ellipsis;
92 | white-space: nowrap;
93 | }
94 | .message {
95 | font-size: 12px;
96 | color: #888;
97 | margin: 0;
98 | width: 180px;
99 | overflow: hidden;
100 | text-overflow: ellipsis;
101 | white-space: nowrap;
102 | }
103 | .clearfix:after {
104 | content: '020';
105 | display: block;
106 | height: 0;
107 | clear: both;
108 | visibility: hidden;
109 | }
110 |
111 | .clearfix {
112 | zoom: 1;
113 | }
114 | }
115 | .time {
116 | position: absolute;
117 | right: 10px;
118 | top: 8px;
119 | color: #888;
120 | font-size: 12px;
121 | }
122 | }
123 |
124 | .chat-container {
125 | background-color: rgb(243, 243, 243);
126 | float: left;
127 | width: calc(100% - 260px);
128 | height: 100%;
129 | .head {
130 | height: 50px;
131 | margin-left: 20px;
132 | line-height: 50px;
133 | -webkit-app-region: drag;
134 | -webkit-user-select: none;
135 | }
136 | .message-wrap {
137 | height: 400px;
138 | border-top: 1px solid #ddd;
139 | border-bottom: 1px solid #ddd;
140 | padding: 20px;
141 | box-sizing: border-box;
142 | overflow-y: scroll;
143 | overflow-x: hidden;
144 | }
145 | .edit-wrap {
146 | padding: 6px 8px;
147 | box-sizing: border-box;
148 | position: relative;
149 | .edit-tool {
150 | height: 40px;
151 | .face,
152 | .file,
153 | .screenshot,
154 | .messages,
155 | .phone,
156 | .video {
157 | display: inline-block;
158 | width: 24px;
159 | height: 24px;
160 | background-image: url('../../../resources/images/icons/xiaolian.png');
161 | background-size: 100% 100%;
162 | margin-right: 6px;
163 | }
164 | .file {
165 | background-image: url('../../../resources/images/icons/wenjian.png');
166 | }
167 | .screenshot {
168 | background-image: url('../../../resources/images/icons/jianqie.png');
169 | }
170 | .messages {
171 | background-image: url('../../../resources/images/icons/xiaoxi.png');
172 | }
173 | .phone {
174 | background-image: url('../../../resources/images/icons/dianhua.png');
175 | float: right;
176 | }
177 | .video {
178 | background-image: url('../../../resources/images/icons/shipin.png');
179 | float: right;
180 | }
181 | }
182 | .edit-box {
183 | resize: none;
184 | border: none;
185 | display: inline-block;
186 | background-color: transparent;
187 | width: 100%;
188 | height: 160px;
189 | background-color: rgb(243, 243, 243);
190 | }
191 | }
192 | }
193 |
194 | .edit-div {
195 | display: inline-block;
196 | background-color: rgb(243, 243, 243);
197 | width: 100%;
198 | height: 160px;
199 | text-align: left;
200 | resize: none;
201 | outline: none;
202 | overflow-y: scroll;
203 | padding: 10px;
204 | font-size: 14px;
205 | img {
206 | max-width: 100px;
207 | }
208 | }
209 |
210 | .dark-mode {
211 | .right-wrap,
212 | .chat-wrap .search,
213 | .chat-container,
214 | .user-wrap,
215 | .edit-div {
216 | background-color: rgba(0, 0, 0, 0.8);
217 | }
218 | .chat-wrap .search input {
219 | background-color: rgb(44, 44, 44);
220 | }
221 | .list-item {
222 | background-color: rgba(0, 0, 0, 0.9);
223 | border-bottom: none;
224 | &:first-child {
225 | background-color: rgba(0, 0, 0, 0.1);
226 | }
227 | }
228 | .edit-div,
229 | .chat-container .head,
230 | .list-item .name {
231 | color: #fff;
232 | }
233 | .chat-container,
234 | .msg-text {
235 | color: #000;
236 | }
237 | .avatar,
238 | .message-wrap,
239 | .list-wrap,
240 | .search {
241 | border: none;
242 | }
243 | }
244 |
245 | .font-size-10 {
246 | .chat-wrap .info {
247 | .name,
248 | .message,
249 | .time {
250 | font-size: 10px;
251 | }
252 | }
253 | .chat-container {
254 | .head {
255 | font-size: 12px;
256 | }
257 | .msg-text,
258 | .edit-div {
259 | font-size: 10px;
260 | }
261 | }
262 | }
263 |
264 | .font-size-12 {
265 | .chat-wrap .info {
266 | .name,
267 | .message,
268 | .time {
269 | font-size: 12px;
270 | }
271 | }
272 | .chat-container {
273 | .head {
274 | font-size: 14px;
275 | }
276 | .msg-text,
277 | .edit-div {
278 | font-size: 12px;
279 | }
280 | }
281 | }
282 |
283 | .font-size-14 {
284 | .chat-wrap .info {
285 | .name,
286 | .message,
287 | .time {
288 | font-size: 14px;
289 | }
290 | }
291 | .chat-container {
292 | .head {
293 | font-size: 16px;
294 | }
295 | .msg-text,
296 | .edit-div {
297 | font-size: 14px;
298 | }
299 | }
300 | }
301 |
302 | .font-size-16 {
303 | .chat-wrap .info {
304 | .name,
305 | .message,
306 | .time {
307 | font-size: 16px;
308 | }
309 | }
310 | .chat-container {
311 | .head {
312 | font-size: 18px;
313 | }
314 | .msg-text,
315 | .edit-div {
316 | font-size: 16px;
317 | }
318 | }
319 | }
320 |
321 | .font-size-18 {
322 | .chat-wrap .info {
323 | .name,
324 | .message,
325 | .time {
326 | font-size: 18px;
327 | }
328 | }
329 | .chat-container {
330 | .head {
331 | font-size: 20px;
332 | }
333 | .msg-text,
334 | .edit-div {
335 | font-size: 18px;
336 | }
337 | }
338 | }
339 |
--------------------------------------------------------------------------------
/src/renderer/container/User/Setting.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './index.scss';
3 | import { ipcRenderer } from 'electron';
4 |
5 | const Setting = (props) => {
6 | function openSettings() {
7 | props.handleSetting();
8 | ipcRenderer.send('open-settings');
9 | }
10 |
11 | return (
12 |
16 | );
17 | };
18 |
19 | export default Setting;
20 |
--------------------------------------------------------------------------------
/src/renderer/container/User/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Setting from './Setting.jsx';
3 | import './index.scss';
4 |
5 | function User() {
6 | const [visible, setVisible] = useState(false);
7 | function handleSetting() {
8 | setVisible(!visible);
9 | }
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {visible &&
}
19 |
20 | );
21 | }
22 |
23 | export default User;
24 |
--------------------------------------------------------------------------------
/src/renderer/container/User/index.scss:
--------------------------------------------------------------------------------
1 | .wrap {
2 | height: 100%;
3 | }
4 |
5 | .user-wrap {
6 | -webkit-app-region: drag;
7 | background-color: #000;
8 | width: 70px;
9 | height: 100%;
10 | text-align: center;
11 | float: left;
12 | position: relative;
13 | .avatar {
14 | display: block;
15 | width: 44px;
16 | margin-left: 10px;
17 | height: 44px;
18 | margin-top: 60px;
19 | background-image: url('../../../resources/images/users/user.png');
20 | background-size: contain;
21 | }
22 | .chat,
23 | .contacts,
24 | .collect,
25 | .phone,
26 | .setting {
27 | display: block;
28 | width: 26px;
29 | height: 26px;
30 | margin-left: 18px;
31 | background-image: url('../../../resources/images/icons/bubble.png');
32 | background-size: 100% 100%;
33 | margin-top: 30px;
34 | &.active {
35 | background-image: url('../../../resources/images/icons/bubble_green.png');
36 | }
37 | }
38 | .contacts {
39 | background-image: url('../../../resources/images/icons/lianxiren.png');
40 | &.active {
41 | background-image: url('../../../resources/images/icons/lianxiren_green.png');
42 | }
43 | }
44 | .collect {
45 | background-image: url('../../../resources/images/icons/lifangti.png');
46 | &.active {
47 | background-image: url('../../../resources/images/icons/lifangti_green.png');
48 | }
49 | }
50 | .phone {
51 | background-image: url('../../../resources/images/icons/shouji.png');
52 | position: absolute;
53 | bottom: 90px;
54 | left: 50%;
55 | margin-left: -18px;
56 | }
57 | .setting {
58 | width: 20px;
59 | height: 20px;
60 | background-image: url('../../../resources/images/icons/santiaogang.png');
61 | position: absolute;
62 | bottom: 30px;
63 | left: 50%;
64 | margin-left: -14px;
65 | }
66 | }
67 |
68 | .setting-wrap {
69 | width: 120px;
70 | box-sizing: border-box;
71 | height: 80px;
72 | background-color: #333;
73 | color: #555;
74 | position: fixed;
75 | left: 70px;
76 | bottom: 10px;
77 | z-index: 999;
78 | text-align: left;
79 | .setting-item {
80 | line-height: 40px;
81 | height: 40px;
82 | padding-left: 10px;
83 | &:hover {
84 | background-color: #aaa;
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/renderer/context-manager.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const MyContext = React.createContext();
4 |
--------------------------------------------------------------------------------
/src/renderer/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | spider
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/renderer/index.jsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import App from './App.jsx';
4 |
5 | import { ipcRenderer } from 'electron';
6 |
7 | ipcRenderer.on('change-fontsize', (e, arg) => {
8 | const fonsize = 10 + arg * 0.08;
9 | const cls = ['font-size-10', 'font-size-12', 'font-size-14', 'font-size-16', 'font-size-18'];
10 | console.log('change-fontsize', arg, fonsize);
11 | document.getElementsByTagName('body')[0].classList.remove(...cls);
12 | document.getElementsByTagName('body')[0].classList.add(`font-size-${fonsize}`);
13 | })
14 |
15 | ReactDOM.render(, document.getElementById('appRoot'));
--------------------------------------------------------------------------------
/src/renderer/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * type是消息类型,1文本,2系统话术,3图片
3 | * content 是消息内容
4 | * fromId 消息发送方
5 | * toId 消息接收方
6 | */
7 | export function msgBody(type, content, fromId, toId, id) {
8 | return {
9 | type,
10 | content,
11 | fromId,
12 | toId,
13 | id: id || new Date().getTime(),
14 | }
15 | }
--------------------------------------------------------------------------------
/src/resources/icns/spider.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/icns/spider.icns
--------------------------------------------------------------------------------
/src/resources/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/favicon.ico
--------------------------------------------------------------------------------
/src/resources/images/icons/bubble.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/bubble.png
--------------------------------------------------------------------------------
/src/resources/images/icons/bubble_green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/bubble_green.png
--------------------------------------------------------------------------------
/src/resources/images/icons/dianhua.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/dianhua.png
--------------------------------------------------------------------------------
/src/resources/images/icons/download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/download.png
--------------------------------------------------------------------------------
/src/resources/images/icons/jianqie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/jianqie.png
--------------------------------------------------------------------------------
/src/resources/images/icons/lianxiren.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/lianxiren.png
--------------------------------------------------------------------------------
/src/resources/images/icons/lianxiren_green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/lianxiren_green.png
--------------------------------------------------------------------------------
/src/resources/images/icons/lifangti.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/lifangti.png
--------------------------------------------------------------------------------
/src/resources/images/icons/lifangti_green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/lifangti_green.png
--------------------------------------------------------------------------------
/src/resources/images/icons/refresh-line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/refresh-line.png
--------------------------------------------------------------------------------
/src/resources/images/icons/ren.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/ren.png
--------------------------------------------------------------------------------
/src/resources/images/icons/rotateleft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/rotateleft.png
--------------------------------------------------------------------------------
/src/resources/images/icons/rotateright.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/rotateright.png
--------------------------------------------------------------------------------
/src/resources/images/icons/santiaogang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/santiaogang.png
--------------------------------------------------------------------------------
/src/resources/images/icons/shipin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/shipin.png
--------------------------------------------------------------------------------
/src/resources/images/icons/shouji.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/shouji.png
--------------------------------------------------------------------------------
/src/resources/images/icons/sousuo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/sousuo.png
--------------------------------------------------------------------------------
/src/resources/images/icons/wenjian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/wenjian.png
--------------------------------------------------------------------------------
/src/resources/images/icons/xiaolian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/xiaolian.png
--------------------------------------------------------------------------------
/src/resources/images/icons/xiaoxi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/xiaoxi.png
--------------------------------------------------------------------------------
/src/resources/images/icons/zoomin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/zoomin.png
--------------------------------------------------------------------------------
/src/resources/images/icons/zoomout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/zoomout.png
--------------------------------------------------------------------------------
/src/resources/images/users/bianfuxia-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/bianfuxia-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/bianfuxia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/bianfuxia.png
--------------------------------------------------------------------------------
/src/resources/images/users/chaoren-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/chaoren-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/chaoren.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/chaoren.png
--------------------------------------------------------------------------------
/src/resources/images/users/gangtiexia-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/gangtiexia-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/gangtiexia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/gangtiexia.png
--------------------------------------------------------------------------------
/src/resources/images/users/jinganglang-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/jinganglang-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/jinganglang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/jinganglang.png
--------------------------------------------------------------------------------
/src/resources/images/users/leishen-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/leishen-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/leishen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/leishen.png
--------------------------------------------------------------------------------
/src/resources/images/users/lvdengxia-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/lvdengxia-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/lvdengxia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/lvdengxia.png
--------------------------------------------------------------------------------
/src/resources/images/users/lvjuren-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/lvjuren-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/lvjuren.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/lvjuren.png
--------------------------------------------------------------------------------
/src/resources/images/users/meiguoduichang-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/meiguoduichang-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/meiguoduichang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/meiguoduichang.png
--------------------------------------------------------------------------------
/src/resources/images/users/ren-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/ren-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/ren.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/ren.png
--------------------------------------------------------------------------------
/src/resources/images/users/shandianxia-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/shandianxia-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/shandianxia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/shandianxia.png
--------------------------------------------------------------------------------
/src/resources/images/users/sishi-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/sishi-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/sishi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/sishi.png
--------------------------------------------------------------------------------
/src/resources/images/users/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/user.png
--------------------------------------------------------------------------------
/src/resources/images/users/xiaochou-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/xiaochou-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/xiaochou.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/xiaochou.png
--------------------------------------------------------------------------------
/src/resources/images/users/yiren-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/yiren-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/yiren.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/yiren.png
--------------------------------------------------------------------------------
/src/resources/images/users/zhizhuxia-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/zhizhuxia-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/zhizhuxia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/zhizhuxia.png
--------------------------------------------------------------------------------
/src/resources/images/users/zhizhuxia_1-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/zhizhuxia_1-big.png
--------------------------------------------------------------------------------
/src/resources/images/users/zhizhuxia_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/zhizhuxia_1.png
--------------------------------------------------------------------------------
/src/resources/images/zhizhuxia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/zhizhuxia.png
--------------------------------------------------------------------------------
/src/resources/images/zhizhuxia_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/zhizhuxia_big.png
--------------------------------------------------------------------------------
/src/resources/images/zhizhuxia_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/zhizhuxia_small.png
--------------------------------------------------------------------------------
/src/setting.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
92 |
93 |
94 |
账号信息
95 |
96 |

97 |
Ivy
98 |
99 |
100 |
101 |
102 |
113 |
120 |
小大
121 |
122 |
123 |
124 |

125 |
126 |
退出登录
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
170 |
171 |
--------------------------------------------------------------------------------
/todo.md:
--------------------------------------------------------------------------------
1 | # todo list
2 |
3 | 1. 文件拖放 发送图片
4 | 2. 优化截屏——页面变得模糊??
5 | 3. API升级
6 | 4. 从9升级到13后,crashreport收集不到数据(9.0.3 版本可用)
7 | 5. remote废弃
8 |
--------------------------------------------------------------------------------
/updater-server/index.js:
--------------------------------------------------------------------------------
1 | const Koa = require('koa');
2 | const app = new Koa();
3 | const Router = require('koa-router');
4 | const serve = require('koa-static-server');
5 | const router = new Router();
6 | const compareVersions = require('compare-versions');
7 | const multer = require('koa-multer');
8 |
9 | const upload = multer({ dest: 'crash/' });
10 |
11 | router.post('/crash', upload.single('upload_file_minidump'), (ctx) => {
12 | console.log('file', ctx.req.file);
13 | console.log('body', ctx.req.body);
14 | ctx.body = '上传成功';
15 | // todo 存DB
16 | });
17 |
18 | function getNewVersion(version) {
19 | if (!version) return null;
20 | const maxVersion = {
21 | name: '1.0.1',
22 | pub_date: '2021-06-24T12:26:53+1:00',
23 | notes: '新增功能: 自定义下载路径',
24 | url: `http://127.0.0.1:9999/public/spiderchat-1.0.1-mac.zip`,
25 | };
26 | if (compareVersions.compare(maxVersion.name, version, '>')) {
27 | return maxVersion;
28 | }
29 | return null;
30 | }
31 |
32 | router.get('/darwin', (ctx, next) => {
33 | const { version } = ctx.query;
34 | const newVersion = getNewVersion(version);
35 | if (newVersion) {
36 | ctx.body = newVersion;
37 | } else {
38 | ctx.status = 204;
39 | }
40 | });
41 | app.use(
42 | serve({
43 | rootDir: 'public',
44 | rootPath: '/public',
45 | })
46 | );
47 | app.use(router.routes()).use(router.allowedMethods());
48 |
49 | app.listen(9999);
50 |
--------------------------------------------------------------------------------
/updater-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "updater-server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@koa/multer": "^3.0.0",
14 | "compare-versions": "^3.5.1",
15 | "koa": "^2.11.0",
16 | "koa-multer": "^1.0.2",
17 | "koa-router": "^8.0.7",
18 | "koa-static-server": "^1.4.0",
19 | "minidump": "^0.19.0",
20 | "multer": "^1.3.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/updater-server/parsedump.js:
--------------------------------------------------------------------------------
1 | const minidump = require('minidump');
2 | const fs = require('fs');
3 |
4 | // symbolpath
5 | minidump.addSymbolPath('/Users/tangting/tt/soft/electron-v9/breakpad_symbols/');
6 |
7 | minidump.walkStack('./crash/c643b202bb5f8203b23d34037cc8690e',(err, res)=>{
8 | fs.writeFileSync('./error/error.txt', res);
9 | })
--------------------------------------------------------------------------------
/updater-server/public/spiderchat-1.0.1-mac.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/updater-server/public/spiderchat-1.0.1-mac.zip
--------------------------------------------------------------------------------
/upload/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/upload/default.png
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const allEnvConfigs = require( './webpack_configs/index.js' );
2 |
3 | const DEFAULT_ENV = 'dev';
4 |
5 | module.exports = function( cliEnv ) {
6 | let cliEnvConfig = allEnvConfigs[ cliEnv ];
7 |
8 | if( !cliEnvConfig ) {
9 | let avaibleEnvs = Object.keys( allEnvConfigs ).join(' ');
10 | console.warn(`
11 | Provided environment "${cliEnv}" was not found.
12 | Please use one of the following ones: ${avaibleEnvs}
13 | `);
14 | }
15 |
16 | return cliEnvConfig || allEnvConfigs[ DEFAULT_ENV ];
17 | }
18 |
--------------------------------------------------------------------------------
/webpack_configs/absulotePath.js:
--------------------------------------------------------------------------------
1 | const path = require( 'path' );
2 |
3 | module.exports = {
4 | root: path.resolve(__dirname, '../'),
5 | dist: path.resolve(__dirname, '../dist'),
6 | build: path.resolve(__dirname, '../build'),
7 | src: path.resolve(__dirname, '../src')
8 | };
--------------------------------------------------------------------------------
/webpack_configs/index.js:
--------------------------------------------------------------------------------
1 | const devConfig = require( './webpack.dev.js' );
2 | const prodConfig = require( './webpack.prod.js' );
3 |
4 | module.exports = {
5 | dev: devConfig,
6 | prod: prodConfig
7 | };
--------------------------------------------------------------------------------
/webpack_configs/webpack.common.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin');
4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
5 | const AbsulotePath = require('./absulotePath');
6 |
7 | var appConfig = {
8 | entry: {
9 | // app: ['babel-polyfill', './src/renderer/index.jsx'],
10 | app: ['./src/renderer/index.jsx'],
11 | },
12 | plugins: [
13 | new CleanWebpackPlugin(),
14 | new HtmlWebpackPlugin({
15 | template: 'index.html',
16 | }),
17 | new MiniCssExtractPlugin({
18 | filename: 'styles/[name].[hash:8].css',
19 | }),
20 | ],
21 | optimization: {
22 | splitChunks: {
23 | cacheGroups: {
24 | commons: {
25 | test: /[\\/]node_modules[\\/]/,
26 | name: 'vendors',
27 | chunks: 'all',
28 | reuseExistingChunk: true,
29 | },
30 | },
31 | },
32 | },
33 | target: 'electron-renderer',
34 | output: {
35 | filename: 'scripts/[name].[hash:8].js',
36 | path: AbsulotePath.build,
37 | },
38 | module: {
39 | rules: [
40 | {
41 | test: /.js$/,
42 | exclude: /node_modules/,
43 | loader: 'babel-loader',
44 | options: {
45 | presets: [['env', { modules: false }], 'stage-2', 'react']
46 | },
47 | },
48 | {
49 | test: /.jsx$/,
50 | exclude: /node_modules/,
51 | loader: 'babel-loader',
52 | options: {
53 | presets: ['react'],
54 | },
55 | },
56 | {
57 | test: /\.html$/,
58 | loader: 'html-loader',
59 | },
60 | {
61 | test: /.css$/,
62 | use: [
63 | {
64 | loader: MiniCssExtractPlugin.loader,
65 | options: {
66 | publicPath: '../'
67 | },
68 | },
69 | 'css-loader',
70 | 'postcss-loader',
71 | ],
72 | },
73 | {
74 | test: /.(png|svg|jpg|jpeg|gif)$/,
75 | loader: 'file-loader',
76 | options: {
77 | esModule: false, // 这里设置为false
78 | name: '[name].[ext]',
79 | outputPath: 'images/',
80 | },
81 | },
82 | {
83 | test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
84 | loader: 'file-loader',
85 | options: {
86 | name: '[name].[ext]',
87 | publicPath: '../iconfont/',
88 | outputPath: 'iconfont/',
89 | },
90 | },
91 | ],
92 | },
93 | };
94 |
95 | module.exports = appConfig;
96 |
--------------------------------------------------------------------------------
/webpack_configs/webpack.dev.js:
--------------------------------------------------------------------------------
1 | // utils
2 | const merge = require( 'webpack-merge' );
3 | // configs
4 | const commons = require( './webpack.common.js' );
5 |
6 | /* --------------------------- main ---------------------------- */
7 | module.exports = [ merge( commons, {
8 | mode: 'development',
9 | devtool: 'inline-source-map',
10 | devServer: {
11 | contentBase: './build',
12 | disableHostCheck: true,
13 | port: 9200
14 | }
15 | } )
16 | ];
--------------------------------------------------------------------------------
/webpack_configs/webpack.prod.js:
--------------------------------------------------------------------------------
1 | // utils
2 | const webpack = require( 'webpack' );
3 | const merge = require( 'webpack-merge' );
4 | // plugins
5 | const UglifyJSPlugin = require( 'uglifyjs-webpack-plugin' );
6 | // configs
7 | const commons = require( './webpack.common.js' );
8 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
9 |
10 | /* --------------------------- main ---------------------------- */
11 | module.exports = [ merge( commons, {
12 | mode: 'production',
13 | devtool: 'cheap-module-source-map',
14 | plugins: [
15 | new UglifyJSPlugin( {
16 | sourceMap: true
17 | } ),
18 | new webpack.DefinePlugin( {
19 | 'process.env.NODE_ENV': JSON.stringify( 'production' )
20 | } )
21 | ]
22 | } )
23 | ];
--------------------------------------------------------------------------------
/ws-server/client.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | WebSocket Chat
6 |
7 |
47 |
48 |
126 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/ws-server/index.js:
--------------------------------------------------------------------------------
1 | const WebSocket = require('ws');
2 |
3 | const server = new WebSocket.Server({ port: 8080 });
4 |
5 | server.on('open', () => {
6 | console.log('connected');
7 | });
8 |
9 | server.on('close', () => {
10 | console.log('disconnected');
11 | });
12 |
13 | server.on('connection', (ws, req, client) => {
14 | ws.on('message', (message) => {
15 | console.log('received: %s', message);
16 |
17 | // 广播消息给所有客户端
18 | server.clients.forEach((client) => {
19 | if (client.readyState === WebSocket.OPEN) {
20 | client.send(message);
21 | }
22 | });
23 | });
24 | });
25 |
--------------------------------------------------------------------------------