electron 总结

前言

有一个web项目需要用客户端来包装一下 项目的主要业务都在服务器上 所以项目的大多数功能都用url 地址来访问; 客户端登陆界面在本地 打包客户端的本地登陆界面 做为登陆入口;

electron 开发中 有一个概念需要理解 我暂且叫主进程吗 main,  这个进程的控制 在项目的 配置 package.json  的main 字段中定义;

main.js 作为electron 控制的后端入口, 基本的主要控制都是在这个文件里面定义  eletron 控制系统 的接口很多也在这里面定义,详细可以查看官方API 文档;

{
"name": "linksame",
"version": "1.0.4",
"description": "邻盛管家客户端",
"main": "main.js", // 主进程控制定义
"scripts": {
"start": "electron .",
"dist": "electron-builder -wm",
"build": "electron-builder",
"test": "echo \"Error: no test specified\" && exit 1"
},

模块

使用到的nodejs 模块:


"electron-main-notification": "^1.0.1", // 右下角消息提醒 组件
"electron-notify": "^0.1.0",// 消息提醒组件
"electron-packager": "^10.1.2",// 项目打包 组件 打包为独立的绿色运行程序
"electron-squirrel-startup": "^1.0.0",// 升级包工具 用处不详
"electron-updater": "^2.20.1",// 一个不错的升级包 制作组件 我们用它做的升级包 提醒
"gulp": "^3.9.1", // 工具链工具 本地开发
"nedb": "^1.8.0",// 一个数据库组件 对象性质 nodejs
"request": "^2.83.0" // 一个请求库

项目用到 eletron 主要的模块 说明

const electron = require('electron') //
//import { autoUpdater } from "electron-updater"
const autoUpdater=require('electron-updater').autoUpdater // 升级包检测 不同于官方默认的那个
autoUpdater.autoDownload = false // 配置取消自动下载
// Module to control application life.
const app = electron.app // 项目基础控制
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow // 开窗口控制
const BrowserView = electron.BrowserView // 窗口控制另外一种方式
const dialog = electron.dialog // 系统弹出地址框
const Tray = electron.Tray // 右下角任务栏
const Menu = electron.Menu //右下角任务栏 菜单控制
const Notification = electron.Notification // 消息提醒
const window = electron.window
const ipcMain = require('electron').ipcMain // 重点 主进程通信专用 发通道信息 监听通道
const ipcRenderer = require('electron').ipcRenderer // 重点 子窗口 通信专用 可以在子窗口中 和 主进程通信
//const storage = require('electron-json-storage')
const {shell} = require('electron') // electron 基础功能接口 例如 打开浏览器网页 ; 打开文件 运行程序等等;
const notify = require('electron-main-notification') // 也是一个消息对话框
const {session} = require('electron') // seesion cookie 控制
const fs = require('fs'); // nodejs 模块 文件文本操作等等 io
const nedb = require('nedb'); // 数据库 一个nodejs 中可以用的数据库

通信

const ipcMain = require('electron').ipcMain // 重点 主进程通信专用 发通道信息 监听通道

const ipcRenderer = require('electron').ipcRenderer // 重点 子窗口 通信专用 可以在子窗口中 和 主进程通信

// 例如 主穿裤中监听 界面发来的命令 主进程中执行相应操作

ipcMain.on('window-min',function(){

mainWindow.minimize();

})

// 主进程发起送消息 其他页面会收到数据domianObj

mainWindow.webContents.send('islogin',domainObj)

本地数据库

打包

npm run build

升级包制作

参考 :https://www.electron.build/auto-update

const autoUpdater=require('electron-updater').autoUpdater // 升级包检查模块 包含检查升级包 下载 安装等接口封装 都在里面

autoUpdater.setFeedURL('http://www.linksame.com/release/');

升级包检测 比较

app.getVersion() // 系统当前版本

// 检查升级包接口 返回 Promise 对象

let checkinfo=autoUpdater.checkForUpdates();

//console.log('checkinfo:',checkinfo)

checkinfo.then(function(data){

console.log('datav:::',data.versionInfo.version)

console.log('datav2:::',data)

// 返回的data 为网络上版本最新版信息 可获取系统版本号 和 最新版本好比较 确定要部要升级

if(data.versionInfo.version!=app.getVersion()){

updateHandle();

//const UpdateInfo=require('electron-updater').UpdateInfo

//console.log('UpdateInfo:',)

}

})

Promise 应用 对象

异步变成 新规范 一些功能中用到了 例如 autoUpdater 升级管理 模块的的一些接口 就是 该类型

参考

升级参考

main.js 参考

项目组控制 介绍说明


if (require('electron-squirrel-startup')) return;
const electron = require('electron')
//import { autoUpdater } from "electron-updater"
const autoUpdater=require('electron-updater').autoUpdater
autoUpdater.autoDownload = false // 配置取消自动下载
// Module to control application life.
const app = electron.app
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow
const BrowserView = electron.BrowserView
const dialog = electron.dialog
const Tray = electron.Tray
const Menu = electron.Menu
const Notification = electron.Notification
const window = electron.window
const ipcMain = require('electron').ipcMain
const ipcRenderer = require('electron').ipcRenderer
//const storage = require('electron-json-storage')
const {shell} = require('electron')
const notify = require('electron-main-notification')
const {session} = require('electron')
const fs = require('fs');
const nedb = require('nedb'); // 数据库 const path = require('path')
const url = require('url') const icologo=__dirname+'\\ioc\\ls.ico'
// 域名
const domain="http://www.linksame.com";
const loginpath=domain+"/index.php?app=Core&m=Pcdlogin&network=1&ip=0"
const request = require('request')
// 判断网络配置
const options = {
       method: 'post',
url:domain+'/release/network.json',
form: "content",
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}; const message={
error:'check version error',
checking:'check updateing ......',
updateAva:'find a New Version,downloading ......',
updateNotAva:'now it New best',
};
const os = require('os'); // init obj
let mainWindow
let tray = null
const app_data=app.getPath('userData')
console.log('app_data',app_data) // 设置共享运行目录
global.linksame = {
runpath: app_data
} // 实例化连接对象(不带参数默认为内存数据库) const db = new nedb({
filename: path.join(app_data,'\\Cache\\downloadfile.db'),
autoload: true
});
let date=new Date(); const initobj={
width:1024,
height:760,
} function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 712,
height: 588,
icon:icologo,
title:'邻盛企业管家',
frame: false,
transparent: true,
webPreferences:{
nodeIntegration:true,
nodeIntegrationInWorker:true,
}
}) //Menu.setApplicationMenu(null); const mainindex = path.join('file://', __dirname, 'index.html')
mainWindow.loadURL(mainindex) mainWindow.once('ready-to-show', () => {
mainWindow.show()
}) // Open the DevTools.
// mainWindow.webContents.openDevTools() // Emitted when the window is closed.
mainWindow.on('closed', function() { mainWindow = null
}) //捕获新打开窗口事件 定制新窗口
mainWindow.webContents.on('new-window', (event, url) => {
event.preventDefault()
const win = new BrowserWindow({
width: 1024,
height: 760,
icon:__dirname+'./ioc/ls.ico',
title:'邻盛企业管家',
frame: false,
transparent: true,
backgroundColor:'#4385F4', }) win.once('ready-to-show', () => win.show())
win.loadURL(url)
//event.newGuest = win console.log('windowID:',win.id) ipcMain.on('sub-close',function(d){
console.log('d',d)
win.hide()
}) }) // download mainWindow.webContents.session.on('will-download', (event, item, webContents) => {
// Set the save path, making Electron not to prompt a save dialog. item.on('updated', (event, state) => { if (state === 'interrupted') {
console.log('Download is interrupted but can be resumed')
} else if (state === 'progressing') {
// set loading progressBar
mainWindow.setProgressBar(item.getReceivedBytes() / item.getTotalBytes()); if (item.isPaused()) {
console.log('Download is paused')
} else { if(Math.ceil(item.getReceivedBytes()/1024/1024)===1){
let title='稍等 正在下载';
let body= item.getSavePath();
let ico=getico(item.getSavePath());
notifly(title,body,ico)
} let download = `Received bytes: ${Math.ceil(item.getReceivedBytes()/1024/1024)} M / ${Math.ceil(item.getTotalBytes()/1024/1024)}M` console.log(`Received bytes: ${Math.ceil(item.getReceivedBytes()/1024/1024)} M / ${Math.ceil(item.getTotalBytes()/1024/1024)}M`)
}
}
})
item.once('done', (event, state) => { if (state === 'completed') {
const filepath=item.getSavePath();
var arr = filepath.split('\\');
let filename=arr[arr.length-1]; let title=filename+' 下载完成!';
let body= item.getSavePath()+' 打开';
let ico=getico(item.getSavePath()); //notifly(title,body,ico)
notiflyclick(title,body,function(){ shell.openItem(filepath) })
console.log('Download successfully') db.insert({
name: filename,
path:item.getSavePath(),
datetime:date.toLocaleDateString(),
sizes:Math.ceil(item.getTotalBytes()/1024/1024),
}, (err, ret) => {
console.log('insert successfully',err,ret)
}); } else {
console.log(`Download failed: ${state}`)
}
}) }) // 任务栏图标菜单 A
tray = new Tray(icologo)
const contextMenu = Menu.buildFromTemplate([{
label: '帮助中心',
type: 'normal',
icon: __dirname+'\\ioc\\help.png',
click: function() {
shell.openExternal('http://help.linksame.com/')
}
},
{ label: '官网', type: 'normal', icon: __dirname+'\\ioc\\web.png',click:function(){
shell.openExternal('http://www.linksame.com')
}},
{ label: '移动端', type: 'normal', icon:__dirname+'\\ioc\\phone.png',click:function(){
//shell.openExternal('http://www.linksame.com/index.php?app=Core&m=V7&a=download') const modalPath = path.join('file://', __dirname, 'qcode.html')
//let win = new BrowserWindow({ width: 705, height: 250,resizable:false,autoHideMenuBar:true,type: 'desktop', icon: './ioc/download2.png' })
let win = new BrowserWindow({
title:'移动设备软件下载',
width: 250,
height: 250,
autoHideMenuBar:true,
type: 'desktop',
icon:__dirname+'\\ioc\\phone.png',
resizable:false,
maximizable:false, })
//win.setApplicationMenu(null);
win.on('close', function() { win = null })
win.loadURL(modalPath)
win.show() } },
{
label: '下载管理',
type: 'normal',
icon:__dirname+'\\ioc\\down.png',
click: function() {
const modalPath = path.join('file://', __dirname, 'download.html')
//let win = new BrowserWindow({ width: 705, height: 250,resizable:false,autoHideMenuBar:true,type: 'desktop', icon: './ioc/download2.png' })
let win = new BrowserWindow({
width: 705,
height: 250,
autoHideMenuBar:true,
type: 'desktop',
icon: __dirname+'\\ioc\\download2.png',
resizable:false,
maximizable:false,
})
//win.setApplicationMenu(null);
win.on('close', function() { win = null })
win.loadURL(modalPath)
win.show()
}
},
{
label: '设置',
type: 'submenu',
icon:__dirname+'\\ioc\\setting.png',
submenu: [
{ label: '开机启动', type: 'normal', icon:__dirname+'\\ioc\\sysset.png' },
{ label: '更新缓存', type: 'normal', icon:__dirname+'\\ioc\\clear-2.png',click:function(){
let cachepath=app_data+'/Cache'
let dir=fs.readdir(cachepath,(err,file)=>{ for(v in file){
console.log('file',v)
let rmnum=shell.moveItemToTrash(path.join(cachepath,file[v]))
console.log('remove',rmnum)
}
notifly('缓存清理','缓存清理完成!',__dirname+'\\ioc\\ok.png') })
}}, ]
},
{ label: '升级', type: 'normal', icon: __dirname+'\\ioc\\upgrate.png',click:function(){
updateHandle();
} },
{ label: '注销', type: 'normal', icon: __dirname+'\\ioc\\zx.png', role: 'close',click:function(){ // console.log('siht',console.log(ses.getUserAgent()))
// 查询与指定 url 相关的所有 cookies. /*session.defaultSession.cookies.remove('http://www.linksame.com','sns_shell',function(cookies) {
console.log('remove~~~~')
});*/
session.defaultSession.cookies.get({url:domain}, function(error, cookies) { let domainObj=cookies
for (var i in domainObj){
console.log(i,':',domainObj[i])
session.defaultSession.cookies.remove(domain,domainObj[i].name, function(data) {
console.log('remove',data);
}); }
//console.log('ddd',domainObj)
}); let newobj=session.defaultSession.cookies.get({ url : domain }, function(error, cookies) {
console.log('login out coockie:',newobj)
}); app.quit();
//session.cookies.remove("http://www.linksame.com", name, callback) }},
{ label: '退出', type: 'normal', icon:__dirname+'\\ioc\\loginout.png', role: 'close',click:function(){
const dopt={
type:'question',
title:'你确定要退出吗?',
buttons:['确定','取消'],
defaultId:1,
message:'退出后 会关闭邻盛企业管家。',
//icon:'./ioc/ls.ico',
noLink:true,
}
dialog.showMessageBox(dopt,function(e){
console.log('e:',e)
if(e==0){
app.quit();
}else{
console.log('nat close')
}
//
}) }}
])
tray.setToolTip('邻盛企业管家 你的企业好帮手!')
tray.setContextMenu(contextMenu) } // This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow) // Quit when all windows are closed.
app.on('window-all-closed', function() {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
}) app.on('activate', function() {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
}) //登录窗口最小化
ipcMain.on('window-min',function(){
mainWindow.minimize();
}) //重新设置大小
ipcMain.on('window-reset',function(){
console.log(initobj.width, initobj.height) mainWindow.setSize(initobj.width, initobj.height)
mainWindow.center()
}) //登录窗口最大化
ipcMain.on('window-max',function(data){ console.log('data',data) if(mainWindow.isMaximized()){
mainWindow.restore();
}else{
mainWindow.maximize();
}
}) ipcMain.on('window-close',function(){
mainWindow.close();
}) // 检查登陆
ipcMain.on('islogin',function(){
session.defaultSession.cookies.get({url:domain,name:'sns_username'}, function(error, cookies) {
let domainObj=cookies
// console.log('ddd',domainObj[0].value)
mainWindow.webContents.send('islogin',domainObj)
});
})
// 检查网络
ipcMain.on('testNetwork',function(){
request(options, function (err, res, body) {
if (err) {
console.log(err)
}else {
mainWindow.webContents.send('testNetwork',body)
}
})
})
// 注销登陆
ipcMain.on('loginout',function(){
session.defaultSession.cookies.get({url:domain}, function(error, cookies) { let domainObj=cookies
for (var i in domainObj){
console.log(i,':',domainObj[i])
session.defaultSession.cookies.remove(domain,domainObj[i].name, function(data) {
console.log('remove',data);
}); }
//console.log('ddd',domainObj)
});
//app.quit();
//mainWindow.loadURL(loginpath)
}) // 消息通知 函数
function notifly(title,body,ico){
const opt = {
icon: ico,
title: title,
body: body,
}
const m = new Notification(opt);
m.show() } // 可点击事件的通知
function notiflyclick(title,body,callback){
notify(title, { body: body }, () => {
console.log('The notification got clicked on!')
callback()
})
} //自动获取图标
function getico(path){
let str = path.substring(path.lastIndexOf(".")+1);
switch (str) {
case 'doc':
return __dirname+'\\ioc/format/doc.png';
break;
case 'docx':
return __dirname+'\\ioc/format/doc.png';
break;
case 'xls':
return __dirname+'\\ioc/format/excel.png';
break;
case 'xlsx':
return __dirname+'\\ioc/format/excel.png';
break;
case 'csv':
return __dirname+'\\ioc/format/excel.png';
break;
case 'exe':
return __dirname+'\\ioc/format/exe.png';
break;
case 'html':
return __dirname+'\\ioc/format/file_html.png';
break;
case 'htm':
return __dirname+'\\ioc/format/file_html.png';
break;
case 'pptx':
return __dirname+'\\ioc/format/ppt.png';
break;
case 'ppx':
return __dirname+'\\ioc/format/ppt.png';
break;
case 'rar':
return __dirname+'\\ioc/format/rar.png';
break;
case 'zip':
return __dirname+'\\ioc/format/zip.png';
break;
case 'gz':
return __dirname+'\\ioc/format/zip.png';
break;
case 'tar':
return __dirname+'\\ioc/format/zip.png';
break;
case 'pdf':
return __dirname+'\\ioc/format/pdf.png';
break;
case 'png':
return __dirname+'\\ioc/format/png.png';
break;
case 'jpg':
return __dirname+'\\ioc/format/jpg.png';
break;
case 'gif':
return __dirname+'\\ioc/format/gif.png';
break;
default:
return __dirname+'\\ioc/format/file.png';
break;
}
}
//================ 升级 autoUpdater.setFeedURL('http://www.linksame.com/release/');
// 检测更新,在你想要检查更新的时候执行,renderer事件触发后的操作自行编写
function updateHandle(){ autoUpdater.on('error', function(error){
sendUpdateMessage(message.error)
});
autoUpdater.on('checking-for-update', function(info) {
console.log('checking for update:::',info)
sendUpdateMessage(message.checking)
});
autoUpdater.on('update-available', function(info) {
console.log('update-available:::',info)
upwin.webContents.send('checkinfo', info)
sendUpdateMessage(message.updateAva)
});
autoUpdater.on('update-not-available', function(info) {
sendUpdateMessage(message.updateNotAva)
}); // 更新下载进度事件
autoUpdater.on('download-progress', function(progressObj) {
console.log('downloading:',progressObj)
upwin.webContents.send('downloadProgress', progressObj)
})
autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
ipcMain.on('isUpdateNow', (e, arg) => {
//some code here to handle event
autoUpdater.quitAndInstall();
})
console.log(releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate)
//upwin.webContents.send('isUpdateNow')
}); //执行自动更新检查
//autoUpdater.checkForUpdates();
ipcMain.on("checkForUpdate",()=>{
//执行自动更新检查 appUpdater.autoDownload = false
autoUpdater.checkForUpdates();
//console.log('checkinfo:',checkinfo) })
//执行下载
ipcMain.on("download",()=>{
autoUpdater.downloadUpdate()
})
console.log('now check updateing ~~~~')
// autoUpdater.checkForUpdates(); //open upgroud dialog
const modalPath = path.join('file://', __dirname, 'upgroud.html')
//let win = new BrowserWindow({ width: 705, height: 250,resizable:false,autoHideMenuBar:true,type: 'desktop', icon: './ioc/download2.png' })
let upwin = new BrowserWindow({
width: 705,
height: 250,
autoHideMenuBar:true,
type: 'desktop',
icon: __dirname+'\\ioc\\upgrate2.png',
resizable:false,
maximizable:false,
})
//win.setApplicationMenu(null);
upwin.on('close', function() { upwin.webContents.send('close') //ipcMain.removeAllListeners(["checkForUpdate", "download", "isUpdateNow"])
upwin = null
//ipcRenderer.removeAll(["checkForUpdate", "download", "isUpdateNow"]);
})
upwin.loadURL(modalPath)
upwin.show()
} // 通过main进程发送事件给renderer进程,提示更新信息
// mainWindow = new BrowserWindow()
function sendUpdateMessage(text){
console.log('text:',text)
//mainWindow.webContents.send('message', text)
} // 第一次运行软件 判断网络
function initview(){ }
// 自动随机检查升级包
function checkUp(){
let checkinfo=autoUpdater.checkForUpdates();
//console.log('checkinfo:',checkinfo)
checkinfo.then(function(data){
console.log('data:::',data.versionInfo.version)
if(data.versionInfo.version){
updateHandle();
}
}) }
//checkUp();
//随机时间执行检查升级
let randoms=Math.round(Math.random()*9+1)*60000;
console.log('randoms=',randoms)
setTimeout(function(){
checkUp();
},randoms)
// hi i'm a vision 1.0.2 hahah

electron 开发拆坑总结的更多相关文章

  1. Electron开发跨平台桌面程序入门教程

    最近一直在学习 Electron 开发桌面应用程序,在尝试了 java swing 和 FXjava 后,感叹还是 Electron 开发桌面应用上手最快.我会在这一篇文章中实现一个HelloWord ...

  2. 使用electron开发桌面级小程序自动部署系统

    那一天我二十一岁,在我一生的黄金时代,我有好多奢望.我想爱,想吃,还想在一瞬间变成天上半明半暗的云,后来我才知道,生活就是个缓慢受锤的过程,人一天天老下去,奢望也一天天消逝,最后变得像挨了锤的牛一样. ...

  3. Electron-使用Electron开发第一个应用

    使用Electron开发第一个应用 Electron 应用的目录结构如下: app/ ├── package.json ├── main.js └── index.html 新建一个app文件夹 将这 ...

  4. Electron开发环境部署

    Electron开发环境部署 安装node.js 可以从node.js官方网站上获取安装包,并进行安装,安装完可以通过 ndoe -v 指令进行版本查看. 本文的开发环境为node.js 4.4.5. ...

  5. 个推 Spark实践教你绕过开发那些“坑”

    Spark作为一个开源数据处理框架,它在数据计算过程中把中间数据直接缓存到内存里,能大大提高处理速度,特别是复杂的迭代计算.Spark主要包括SparkSQL,SparkStreaming,Spark ...

  6. 【Electron】Electron开发入门

    Electron简介: Electron提供了丰富的本地(操作系统)的API,使你能够使用纯JavaScript来创建桌面应用程序,并且跨平台(win,mac,linux等各种PC端平台).与其它各种 ...

  7. Android项目开发填坑记-Fragment的onBackPressed

    Github版 CSDN版 知识背景 Fragment在当前的Android开发中,有两种引用方式,一个是 Android 3.0 时加入的,一个是supportV4包中的.这里简称为Fragment ...

  8. Android项目开发填坑记-Fragment的onAttach

    背景 现在Android开发多使用一个Activity管理多个Fragment进行开发,不免需要两者相互传递数据,一般是给Fragment添加回调接口,让Activity继承并实现. 回调接口一般都写 ...

  9. Android项目开发填坑记-so文件引发的攻坚战

    故事的最初 我负责的项目A要求有播放在线视频的功能,当时从别人的聊天记录的一瞥中发现百度有相关的SDK,当时找到的是Baidu-T5Player-SDK-Android-1.4s,项目中Demo的so ...

随机推荐

  1. Day12装饰器

    1.装饰器 什么是装饰器:装饰器指的是为被装饰对象添加新功能的工具 装饰器本身可以是任意调用对象 被装饰对象本身也可以是任意可调用对象 2.为何要用装饰器: 开放封闭原则: ①对修改源代码和调用方式是 ...

  2. PyCharm学习笔记(二) 调试配置

    选择PyCharm编译器 注意工程默认使用的解释器可能是Pycharm自带的,而不是单独安装的.

  3. cs229_part3

    接下来就是最最最重要的一个有监督学习算法了. 支持向量机 问题背景 样本集表示: \[(x,y)\in D, x\in R^n, y\in \{-1,+1\}\] 回到之前的逻辑回归模型中: 逻辑回归 ...

  4. Ubuntu 16.04上thunderbird配置163邮箱出现“配置无法被验证-请查看用户名或密码是否正确?”

    在Ubuntu 16.04 上用thunderbird配置163免费邮箱时出现的提示信息如图1: 图1 提示信息 网上有不少方法都说是将接收和发出的主机名分别改为 imap.ym.163.com 和 ...

  5. 算法学习记录-图——最小生成树之prim算法

    一个连通图的生成树是一个极小的连通子图,它包含图中全部的顶点(n个顶点),但只有n-1条边. 最小生成树:构造连通网的最小代价(最小权值)生成树. prim算法在严蔚敏树上有解释,但是都是数学语言,很 ...

  6. 通过 PC 远程控制 Android 的应用 -- 可以将手机屏幕投射显示到电脑上

    测试结果中的部分测试图:Mobizen手机界面: 电脑界面: 主界面 视频 全屏视频 WebKey手机界面: 电脑界面: AirMore手机界面: 电脑界面:主界面 镜像 全屏镜像 Airdroid手 ...

  7. CodeIgniter实现读写分离

    http://pengbotao.cn/codeigniter-mysql-proxy.html

  8. CodeForces 321C Ciel the Commander

    Ciel the Commander Time Limit: 1000ms Memory Limit: 262144KB This problem will be judged on CodeForc ...

  9. ubuntu 终端查看图片(eog)

    远程登陆服务器的话,是没有办法直接查看图片的,这时我们需要进入图片所在目录,然后通过终端命令查看图片. 想要查看图片,需要通过ssh -X登陆,然后在终端输入命令: eog 图片名

  10. JSF框架整理

    JSP体系结构: JSF主要优势之一就是它既是Java web 应用程序的用户界面标准又是严格遵循 模型-视图-控制器(MVC)设计模式的框架. 用户界面代码(视图)和应用程序数据和逻辑(模型)的清晰 ...