iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >如何使用electron实现一个截屏工具
  • 246
分享到

如何使用electron实现一个截屏工具

2024-04-02 19:04:59 246人浏览 安东尼
摘要

这篇文章将为大家详细讲解有关如何使用electron实现一个截屏工具,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。思路electron 提供了截取屏幕的 api,可以轻松

这篇文章将为大家详细讲解有关如何使用electron实现一个截屏工具,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

思路

electron 提供了截取屏幕的 api,可以轻松的获取每个屏幕(存在外接显示器的情况)和每个窗口的图像信息。

  • 把图片截取出来,然后创建一个全屏的窗口盖住整个屏幕,将截取的图片绘制在窗口上,然后再覆盖一层黑色半透明的元素,看起来就像屏幕定住了一样;

  • 在窗口上增加交互制作选区的效果;

  • 点击确定,利用 canvas 对应选区的位置截取图片内容,写入剪贴板和保存图片。

搭建项目

首先创建 package.JSON 填写项目的必要信息, 注意 main 为入口文件。

{
 "name": "electorn-capture-screen",
 "version": "1.0.0",
 "main": "main.js",
 "repository": "https://GitHub.com/chrisbing/electorn-capture-screen.git",
 "author": "Chris",
 "license": "MIT",
 "scripts": {
 "start": "electron ."
 },
 "dependencies": {
 "electron": "^3.0.2"
 }
}

创建 main.js , 代码来自 electron 官方文档

const { app, BrowserWindow, ipcMain, globalShortcut } = require('electron')
const os = require('os')

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the javascript object is garbage collected.
let win

function createWindow() {
 
 // 创建浏览器窗口。
 win = new BrowserWindow({ width: 800, height: 600 })

 // 然后加载应用的 index.html。
 win.loadFile('index.html')

 // 打开开发者工具
 win.WEBContents.openDevTools()

 // 当 window 被关闭,这个事件会被触发。
 win.on('closed', () => {
  // 取消引用 window 对象,如果你的应用支持多窗口的话,
  // 通常会把多个 window 对象存放在一个数组里面,
  // 与此同时,你应该删除相应的元素。
  win = null
 })
}

// Electron 会在初始化后并准备
// 创建浏览器窗口时,调用这个函数。
// 部分 API 在 ready 事件触发后才能使用。
app.on('ready', createWindow)

// 当全部窗口关闭时退出。
app.on('window-all-closed', () => {
 // 在 MacOS 上,除非用户用 Cmd + Q 确定地退出,
 // 否则绝大部分应用及其菜单栏会保持激活。
 if (process.platfORM !== 'darwin') {
  app.quit()
 }
})

app.on('activate', () => {
 // 在macOS上,当单击dock图标并且没有其他窗口打开时,
 // 通常在应用程序中重新创建一个窗口。
 if (win === null) {
  createWindow()
 }
})

创建 index.html , html 中放了一个按钮, 用来触发截屏操作

<!DOCTYPE html>
<html>
<head>
 <meta charset="UTF-8">
 <title>Hello World!</title>
</head>
<body>
<button id="js-capture">Capture Screen</button>
<script>
 const { ipcRenderer } = require('electron')

 document.getElementById('js-capture').addEventListener('click', ()=>{
  ipcRenderer.send('capture-screen')
 })

</script>
</body>
</html>

这样一个简单的 electron 项目就完成了, 执行 yarn start 或者 npm start 即可看到一个窗口, 窗口中有一个按钮

如何使用electron实现一个截屏工具

触发截屏

截屏是一个相对独立的功能, 并且有可能会有全局快捷键以及菜单触发等脱离窗口的情况, 所以截屏的触发应该放在 main 进程中来实现

在 renderer 进程中可以通过 ipc 通讯来完成, 在页面的代码中使用 ipcRenderer 发送事件, 而在 main 中使用 ipcMain 接收事件

// index.html
	const { ipcRenderer } = require('electron')

	document.getElementById('js-capture').addEventListener('click', ()=>{
		ipcRenderer.send('capture-screen')
	})

在 main 进程中接收 capture-screen 事件

// main.js

// 接收事件
ipcMain.on('capture-screen', captureScreen)

同时加入全局快捷键触发和取消截屏

// main.js

// 注册全局快捷键
// globalShortcut 需要在 app ready 之后
globalShortcut.reGISter('CmdOrCtrl+Shift+A', captureScreen)
globalShortcut.register('Esc', () => {
 if (captureWin) {
  captureWin.close()
  captureWin = null
 }
})

通过快捷键和事件来触发截屏方法 captureScreen , 接下来实现这个方法来创建一个截屏窗口

创建截屏窗口

截屏窗口是要创建一个全屏的窗口, 并且把屏幕图片绘制在窗口上, 再通过鼠标拖拽等交互操作选出特定区域的图像.

第一步是要创建窗口

// main.js
let captureWin = null

const captureScreen = (e, args) => {
 if (captureWin) {
  return
 }
 const { screen } = require('electron')
 let { width, height } = screen.getPrimaryDisplay().bounds
 captureWin = new BrowserWindow({
  // window 使用 fullscreen, mac 设置为 undefined, 不可为 false
  fullscreen: os.platform() === 'win32' || undefined, // win
  width,
  height,
  x: 0,
  y: 0,
  transparent: true,
  frame: false,
  skipTaskbar: true,
  autoHideMenuBar: true,
  movable: false,
  resizable: false,
  enableLargerThanScreen: true, // mac
  hasshadow: false,
 })
 captureWin.setAlwaysOnTop(true, 'screen-saver') // mac
 captureWin.setVisibleOnAllWorkspaces(true) // mac
 captureWin.setFullScreenable(false) // mac

 captureWin.loadFile(path.join(__dirname, 'capture.html'))

 // 调试用
 // captureWin.openDevTools()

 captureWin.on('closed', () => {
  captureWin = null
 })

}

窗口需要覆盖全屏, 并且完全置顶, 在 windows 下可以使用 fullscreen 来保证全屏, Mac 下 fullscreen 会把窗口移到单独桌面, 所以采用了另外的办法, 代码注释上标注了不同系统的相关选项, 具体内容可以查看文档

注意这里窗口加载了另外一个 html 文件, 这个文件用来负责截屏和裁剪的一些交互工作

capture.html

首先 html 结构

// capture.html

<div id="js-bg" class="bg"></div>
<div id="js-mask" class="mask"></div>
<canvas id="js-canvas" class="image-canvas"></canvas>
<div id="js-size-info" class="size-info"></div>
<div id="js-toolbar" class="toolbar">
 <div class="iconfont icon-zhongzhi" id="js-tool-reset"></div>
 <div class="iconfont icon-xiazai" id="js-tool-save"></div>
 <div class="iconfont icon-guanbi" id="js-tool-close"></div>
 <div class="iconfont icon-duihao" id="js-tool-ok"></div>
</div>
<script src="capture-renderer.js"></script>

Bg : 截屏图片 Mask : 一层灰色遮罩 Canvas : 绘制选中的图片区域和边框 Size info : 标识截取范围的尺寸 Toolbar : 操作按钮, 用来取消和保存等 capture-renderer.js : js 代码

@import "./assets/iconfont/iconfont.CSS";

html, body, div {
 margin: 0;
 padding: 0;
 box-sizing: border-box;
}

.mask {
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 background: rgba(0, 0, 0, 0.6);
}

.bg {
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
}

.image-canvas {
 position: absolute;
 display: none;
 z-index: 1;
}

.size-info {
 position: absolute;
 color: #ffffff;
 font-size: 12px;
 background: rgba(40, 40, 40, 0.8);
 padding: 5px 10px;
 border-radius: 2px;
 font-family: Arial Consolas sans-serif;
 display: none;
 z-index: 2;
}

.toolbar {
 position: absolute;
 color: #343434;
 font-size: 12px;
 background: #f5f5f5;
 padding: 5px 10px;
 border-radius: 4px;
 font-family: Arial Consolas sans-serif;
 display: none;
 box-shadow: 0 0 20px rgba(0, 0, 0, 0.4);
 z-index: 2;
 align-items: center;
}

.toolbar .iconfont {
 font-size: 24px;
 padding: 2px 5px;
}

各个元素基本为 absolute 定位, 由 js 控制位置 按钮使用了 iconfont , 所有涉及到的资源文件和完整项目可以到 GitHub - chrisbing/electorn-capture-screen: electron capture screen 中下载

截图交互

如何使用electron实现一个截屏工具

完成的功能有截取指定区域图片, 拖拽移动和改变选区尺寸, 实时尺寸显示和工具条

获取屏幕截图

// capture-renderer.js

const { ipcRenderer, clipboard, nativeImage, remote, desktopCapturer, screen } = require('electron')
const Event = require('events')
const fs = require('fs')

const { bounds: { width, height }, scaleFactor } = screen.getPrimaryDisplay()
const $canvas = document.getElementById('js-canvas')
const $bg = document.getElementById('js-bg')
const $sizeInfo = document.getElementById('js-size-info')
const $toolbar = document.getElementById('js-toolbar')

const $btnClose = document.getElementById('js-tool-close')
const $btnOk = document.getElementById('js-tool-ok')
const $btnSave = document.getElementById('js-tool-save')
const $btnReset = document.getElementById('js-tool-reset')

console.time('capture')
desktopCapturer.getSources({
 types: ['screen'],
 thumbnailSize: {
  width: width * scaleFactor,
  height: height * scaleFactor,
 }
}, (error, sources) => {
 console.timeEnd('capture')
 let imgSrc = sources[0].thumbnail.toDataURL()

 let capture = new CaptureRenderer($canvas, $bg, imgSrc, scaleFactor)
})

screen.getPrimaryDisplay() 可以获取主屏幕的大小和缩放比例, 缩放比例在高分屏中适用, 在高分屏中屏幕的物理尺寸和窗口尺寸并不一致, 一般会有2倍3倍等缩放倍数, 所以为了获取到高清的屏幕截图, 需要在屏幕尺寸基础上乘以缩放倍数

desktopCapturer 获取屏幕截图的图片信息, 获取的是一个数组, 包含了每一个屏幕的信息, 这里呢暂时只处理了第一个屏幕的信息

获取了截图信息后创建 CaptureRenderer 进行交互处理

CaptureRenderer

// capture-renderer.js
class CaptureRenderer extends Event {

 constructor($canvas, $bg, imageSrc, scaleFactor) {
  super()
 		 // ...

  this.init().then(() => {
   console.log('init')
  })
 }

 async init() {
  this.$bg.style.backgroundImage = `url(${this.imageSrc})`
  this.$bg.style.backgroundSize = `${width}px ${height}px`
  let canvas = document.createElement('canvas')
  let ctx = canvas.getContext('2d')
  let img = await new Promise(resolve => {
   let img = new Image()
   img.src = this.imageSrc
   if (img.complete) {
    resolve(img)
   } else {
    img.onload = () => resolve(img)
   }
  })

  canvas.width = img.width
  canvas.height = img.height
  ctx.drawImage(img, 0, 0)
  this.bGCtx = ctx
		 // ...
 }
	 
 // ...

 onMouseDrag(e) {
		 // ...
		 this.selectRect = {x, y, w, h, r, b}
  this.drawRect()
  this.emit('dragging', this.selectRect)
  // ...
 }

 drawRect() {
  if (!this.selectRect) {
   this.$canvas.style.display = 'none'
   return
  }
  const { x, y, w, h } = this.selectRect

  const scaleFactor = this.scaleFactor
  let margin = 7
  let radius = 5
  this.$canvas.style.left = `${x - margin}px`
  this.$canvas.style.top = `${y - margin}px`
  this.$canvas.style.width = `${w + margin * 2}px`
  this.$canvas.style.height = `${h + margin * 2}px`
  this.$canvas.style.display = 'block'
  this.$canvas.width = (w + margin * 2) * scaleFactor
  this.$canvas.height = (h + margin * 2) * scaleFactor

  if (w && h) {
   let imageData = this.bgCtx.getImageData(x * scaleFactor, y * scaleFactor, w * scaleFactor, h * scaleFactor)
   this.ctx.putImageData(imageData, margin * scaleFactor, margin * scaleFactor)
  }
  this.ctx.fillStyle = '#ffffff'
  this.ctx.strokeStyle = '#67bade'
  this.ctx.lineWidth = 2 * this.scaleFactor

  this.ctx.strokeRect(margin * scaleFactor, margin * scaleFactor, w * scaleFactor, h * scaleFactor)
  this.drawAnchors(w, h, margin, scaleFactor, radius)
 }

 drawAnchors(w, h, margin, scaleFactor, radius) {
  // ...
 }

 onMouseMove(e) {
  // ...
  document.body.style.cursor = 'move'
  // ...
 }

 onMouseUp(e) {
  this.emit('end-dragging')
  this.drawRect()
 }

 getImageUrl() {
  const { x, y, w, h } = this.selectRect
  if (w && h) {
   let imageData = this.bgCtx.getImageData(x * scaleFactor, y * scaleFactor, w * scaleFactor, h * scaleFactor)
   let canvas = document.createElement('canvas')
   let ctx = canvas.getContext('2d')
   ctx.putImageData(imageData, 0, 0)
   return canvas.toDataURL()
  }
  return ''
 }

 reset() {
  // ...
 }
}

代码有点长, 由于篇幅的原因, 这里只列出了关键部分, 完整代码请到 GitHub - chrisbing/electorn-capture-screen: electron capture screen 上查看

初始化时保存一份绘制了全部图片的 canvas , 用来后续取选区部分图片用

绘制过程中从 通过 canvas 中的 getImageData 获取图片内容 然后通过 putImageData 绘制到显示 canvas 中

附加内容

在 CaptureRenderer 类中处理了图片的选取. 还需要工具条和尺寸信息

这一部分代码和图片选取关系不是很大, 所以在外部单独处理, 通过 CaptureRenderer 传出的事件和一些属性即可完成交互

// capture-renderer.js

let onDrag = (selectRect) => {
 $toolbar.style.display = 'none'
 $sizeInfo.style.display = 'block'
 $sizeInfo.innerText = `${selectRect.w} * ${selectRect.h}`
 if (selectRect.y > 35) {
  $sizeInfo.style.top = `${selectRect.y - 30}px`
 } else {
  $sizeInfo.style.top = `${selectRect.y + 10}px`
 }
 $sizeInfo.style.left = `${selectRect.x}px`
}
capture.on('start-dragging', onDrag)
capture.on('dragging', onDrag)

let onDragEnd = () => {
 if (capture.selectRect) {
  const { x, r, b, y } = capture.selectRect
  $toolbar.style.display = 'flex'
  $toolbar.style.top = `${b + 15}px`
  $toolbar.style.right = `${window.screen.width - r}px`
 }
}
capture.on('end-dragging', onDragEnd)

capture.on('reset', () => {
 $toolbar.style.display = 'none'
 $sizeInfo.style.display = 'none'
})

移动过程中计算尺寸, 并且实时计算位置, 移动过程中隐藏工具条

重置选区时隐藏工具条和尺寸标识

保存剪贴板

// capture-renderer.js

const audio = new Audio()
audio.src = './assets/audio/capture.mp3'

let selectCapture = () => {
 if (!capture.selectRect) {
  return
 }
 let url = capture.getImageUrl()
 remote.getCurrentWindow().hide()

 audio.play()
 audio.onended = () => {
  window.close()
 }
 clipboard.writeImage(nativeImage.createFromDataURL(url))
 ipcRenderer.send('capture-screen', {
  type: 'complete',
  url,
 })
}

$btnOk.addEventListener('click', selectCapture)

通过 nativeImage.createFromDataURL 创建图片写入剪贴板, 通知 main 进程截图完毕, 并附带图片的 base64 url, 然后关闭窗口

保存到文件

// capture-renderer.js
$btnSave.addEventListener(‘click', () => {
 let url = capture.getImageUrl()

 remote.getCurrentWindow().hide()
 remote.dialog.showSaveDialog({
  filters: [{
   name: ‘Images',
   extensions: [‘png', ‘jpg', ‘gif']
  }]
 }, function (path) {
  if (path) {
   fs.writeFile(path, new Buffer(url.replace(‘data:image/png;base64,', ‘'), ‘base64'), function () {
    ipcRenderer.send(‘capture-screen', {
     type: ‘complete',
     url,
     path,
    })
    window.close()
   })
  } else {
   ipcRenderer.send(‘capture-screen', {
    type: ‘cancel',
    url,
   })
   window.close()
  }
 })
})

利用 remote.dialog.showSaveDialog 选择保存文件名, 然后通过 fs 模块写入文件

最终整体目录结构

├── index.html
├── lib // 截图核心代码
│  ├── assets // font 和 声音资源
│  ├── capture-main.js // main 中截图部分代码
│  ├── capture-renderer.js // 截图交互代码
│  └── capture.html // 截图 html
├── main.js 
└── package.json

关于“如何使用electron实现一个截屏工具”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

--结束END--

本文标题: 如何使用electron实现一个截屏工具

本文链接: https://www.lsjlt.com/news/74837.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何使用electron实现一个截屏工具
    这篇文章将为大家详细讲解有关如何使用electron实现一个截屏工具,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。思路electron 提供了截取屏幕的 API,可以轻松...
    99+
    2024-04-02
  • python如何实现的截屏工具
    这篇文章主要介绍python如何实现的截屏工具,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!python的数据类型有哪些python的数据类型:1. 数字类型,包括int(整型)、long(长整型)和float(浮点...
    99+
    2023-06-14
  • Linux中如何使用scrot截屏工具
    今天就跟大家聊聊有关Linux中如何使用scrot截屏工具,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。  在Linux中安装Scrot  在 Debian,Ubuntu 或 Lin...
    99+
    2023-06-13
  • 一个用python3写的简单截屏工具初步
    需求1.有30~50台左右的无盘PC机windows7、windows10系统 需要5秒一截图(屏幕桌面操作)。需求2.要每年每月每日每时每人每账号截图。 第一步创建目录结构 # -*- coding: cp936 -*- from ...
    99+
    2023-01-31
    简单 工具
  • 利用Python实现一个简易的截图工具
    这是工作期间同事想要个截完图之后可以显示并且永远前置的截图小工具(即不会被其他程序覆盖)直接上代码: # # -*- coding: utf-8 -*- import tkinter...
    99+
    2024-04-02
  • 如何使用Bash工具截屏Linux系统配置
    这篇文章主要介绍了如何使用Bash工具截屏Linux系统配置,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。使用 ScreenFetch 和 Neofetch 与其他人轻松分享...
    99+
    2023-06-16
  • 怎么用Python实现一个简易的截图工具
    这篇文章主要讲解了“怎么用Python实现一个简易的截图工具”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么用Python实现一个简易的截图工具”吧!代码:# # -*...
    99+
    2023-07-02
  • Java如何实现截图小工具
    这篇文章主要介绍“Java如何实现截图小工具”,在日常操作中,相信很多人在Java如何实现截图小工具问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java如何实现截图小工具”的疑惑有所帮助!接下来,请跟着小编...
    99+
    2023-06-30
  • 使用Java怎么实现一个网页截屏功能
    使用Java怎么实现一个网页截屏功能?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。具体如下:package awtDemo;import j...
    99+
    2023-05-30
    java
  • C++如何实现截图截屏
    这篇文章主要介绍了C++如何实现截图截屏,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1、截图工具1.1 键盘截图(PrtScn键)如何使用Microsoft Windows...
    99+
    2023-06-21
  • 通过C#编写一个简易的Windows截屏增强工具
    目录前言功能实现原理代码C#PInvoke定义注册键盘钩子键盘消息处理函数保存图片半年前我开源了 DreamScene2 一个小而快并且功能强大的 Windows...
    99+
    2024-04-02
  • Java实现屏幕截图工具的代码分享
    目录效果展示程序结构核心代码效果展示 程序运行后的截图: 先测试一下功能: 截图过程对界面的捕捉: 双击保存后的显示界面: 后续的步骤就自己去尝试吧,这里给出了简单的测试过程。...
    99+
    2024-04-02
  • win7系统下使用print screen键加画图工具实现屏幕截图
    截图大家应该都会,我们在QQ上聊天的时候,经常会截图给好友看,往往自己电脑上都会保存一些搞笑幽默的图片,到时候在QQ群里,或者论坛贴吧里发给别人欣赏,像笔者电脑的大部分图片都是在上网的时候,遇见搞笑,有意思的图片,就使用...
    99+
    2023-06-05
    win7 画图工具 屏幕截图 画图 print screen 系统 工具
  • Java实现屏幕截图工具的代码怎么写
    这篇文章主要讲解了“Java实现屏幕截图工具的代码怎么写”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java实现屏幕截图工具的代码怎么写”吧!效果展示程序运行后的截图:先测试一下功能:截图...
    99+
    2023-06-30
  • 如何利用Python实现一个论文降重工具
    前言 时值毕业季,有不少小伙伴深受论文查重的困扰。因此我便想到做一个简单的自动去重的工具,先看看效果,我们再对原理或是代码实现做进一步的分析。 首先需要输入appid以及key,这些...
    99+
    2024-04-02
  • Android中怎么实现一个截屏功能
    这篇文章给大家介绍Android中怎么实现一个截屏功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。1:build.gradlecompileSdkVersion 21   &n...
    99+
    2023-06-20
  • 如何利用JavaScript差集实现一个对比小工具
    本篇内容介绍了“如何利用JavaScript差集实现一个对比小工具”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!前言在工作中需要每周统计人员...
    99+
    2023-06-20
  • java如何实现一个扫描包的工具类
    小编给大家分享一下java如何实现一个扫描包的工具类,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!前言在很多的实际场景中,我们需要得到某个包名下面所有的类,比如我...
    99+
    2023-05-31
    java
  • 如何使用Node.js写一个命令行工具
    这篇文章给大家分享的是有关如何使用Node.js写一个命令行工具的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。具体如下:操作系统需要为Linux1. 目标在命令行输入自己写的命令...
    99+
    2024-04-02
  • 用python实现一个文件搜索工具
    目录前言步骤操作如下:完整代码:总结前言 经常使用电脑自带的搜索很慢很卡,今天做一个搜索工具,可以搜索到隐藏的文件,而且速度也很快 步骤 导入模块 import os 检测一下输入的...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作