QT简介
Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt很容易扩展,并且允许真正地组件编程。
web通讯
利用Qt的Qt WebEngine和WebChannel模块与web进行通讯。前端需要引入官方提供的qwebchannel.js库进行与Qt客户端进行交互(异步式交互)官方链接
Qt<--->web交互原理
实例化QWebChannel后生成一个交互通道channel,然后获取通道对象let qtContext=channel.objects.webBridge;对qtContext做双向通信的事件处理,接受Client的消息:
qtContext.contentChanged.connect((response) => {
const { event, data } = response
responseLog({ event, data })
EE.emit(event, data)
})
发送消息到Client(参数格式为:事件名称,数据,回调事件名称):
qtContext.dataChanged(
JSON.stringify({ event, data, callbackEvent })
)
封装Promise
- 判断是否为Qt环境
export const isQtClient = (function () {
return navigator.userAgent.includes('QtWebEngine')
})()
- 数据异步回调
为了实现类ajax式的交互逻辑引入
eventemitter3作为桥梁:
import EventEmitter from 'eventemitter3'
export const EE = new EventEmitter()
接着定义接口fetchQt,其接收事件名event、数据data、回调事件名callbackEvent作为参数:
fetchQt: ({ event, data, callbackEvent }) => {
requestLog(event, data)
return qtContext.dataChanged(
JSON.stringify({ event, data, callbackEvent })
)
}
fetchQtCallback为自动注册回调函数的方法,其行为类似ajax:
fetchQtCallback: ({ event, data,callbackEvent = `${event}_callback` },config = {}) => {
const options = { ...conf, ...config }
return new Promise((resolve, reject) => {
let timer = null
const apiHandler = (data) => {
clearTimeout(timer)
timer = null
resolve(data)
}
EE.once(callbackEvent, apiHandler)
if (options.timeout) {
timer = setTimeout(() => {
EE.off(callbackEvent, apiHandler)
reject({ callbackEvent, msg: 'Timeout', time: options.timeout })
}, options.timeout)
}
qWebChannel.__apis__.fetchQt({ event, data, callbackEvent })
})
}
- 使用方式
// 只下发
export const qwebApi = async ({ event, data, callbackEvent }) => {
const channel = await qwebchannel()
return channel.fetchQt({ event, data, callbackEvent }, {})
}
// 下发、监听、返回值
export const fetchQwebApi = async ({ event, data, callbackEvent }, config = {}) => {
const channel = await qwebchannel()
return channel.fetchQtCallback({ event, data, callbackEvent }, config)
}
注意点
以上定义的数据对象以参数含义需要跟Qt端保持一致比如callbackEvent在Qt端收到此参数时在处理完事件后会发射一个事件名为callbackEvent的事件并返回数据
全部代码
utils.js
export const isQtClient = (function () {
return navigator.userAgent.includes('QtWebEngine')
})()
export function assert(condition, msg) {
if (!condition) console.error(msg || `[ASSERT]: ${condition} is a falsy value.`)
}
export const __DEV__ = process.env.NODE_ENV === 'development'
qwebchannel.js
import EventEmitter from 'eventemitter3'
import { QWebChannel } from './qwebchannel'
import { assert, isQtClient, __DEV__ } from './utils'
let qWebChannel = null
const conf = {
timeout: 10000,
}
// 页面标识符,可进行或操作-》表示qwebchannel事件往目标页面发送
export const PageType = {
Current: 0x0000,
Home: 0x0001,
CloudGames: 0x0002,
AndroidGames: 0x0004,
}
// 请求log
const requestLog = (event, data, ga) => {
try {
if (ga && Object.keys(ga).length) {
console.info(
`%cRequest:qwebApi for <${event}>(Ga-Data):`,
'color:green;font-size:18px;',
'\n',
ga
)
}
if (data) {
console.info(
`%cRequest:qwebApi for <${event}>(Req-Data):`,
'color:pink;font-size:18px;',
'\n',
data
)
} else {
console.info(`%cRequest:qwebApi for <${event}>`, 'color:pink;font-size:18px;')
}
} catch (error) {}
}
// 相应log
const responseLog = (response, isMock) => {
try {
const { event, data } = response
console.info(
`%cResponse:qwebApi for <${event}>${isMock ? ' [[MOCK-DATA]]' : ''}:`,
'color:green;font-size:18px;',
'\n',
data
)
} catch (error) {
console.error(
'%cqtContext.contentChanged(数据解析出错)',
'color:red;font-size:18px;',
'\n',
error
)
}
}
export const EE = new EventEmitter()
// console.log(EE)
export default function QWC() {
return new Promise((resolve, reject) => {
if (!__DEV__) {
assert(
window && window.qt && window.qt.webChannelTransport,
"'qt' or 'qt.webChannelTransport' should be initialized(injected) by QtWebEngine"
)
}
// 用于在浏览器端开发时,模拟 `Qt` 的注入行为
if (!isQtClient) {
window.qt = {
webChannelTransport: {
send() {
console.log(`QWebChannel simulator activated !`)
setTimeout(() => {
qWebChannel.__apis__ = {
fetchQt: ({ event, data }) => {
requestLog(event, data)
return Promise.resolve()
},
fetchQtCallback: (
{ event, data, callbackEvent = `${event}_callback` },
{ initial }
) => {
// initial表示初始数据(mock)
return qWebChannel.__apis__.fetchQt({ event, data }).then(() => {
responseLog({ event: callbackEvent, data: initial }, true)
EE.emit(callbackEvent, initial)
return initial
})
},
qtContext: null,
on: EE.on.bind(EE),
off: EE.off.bind(EE),
once: EE.off.bind(EE),
emit: EE.emit.bind(EE),
}
resolve(qWebChannel.__apis__)
}, 0)
},
},
}
}
if (!qWebChannel) {
qWebChannel = new QWebChannel(window.qt.webChannelTransport, (channel) => {
const qtContext = channel.objects.webBridge
qtContext.contentChanged.connect((response) => {
const { event, data } = response
responseLog({ event, data })
EE.emit(event, data)
})
qWebChannel.__apis__ = {
fetchQt: ({ event, data, page = PageType.Current, callbackEvent, ga = {} }) => {
const { path, module, ...extra } = ga
const gaTemp = {
path: path || window.location.pathname,
module,
extra: JSON.stringify(extra),
}
requestLog(event, data, gaTemp)
return qtContext.dataChanged(
JSON.stringify({ event, data, page, callbackEvent, ga: gaTemp })
)
},
fetchQtCallback: (
{ event, data, page = PageType.Current, callbackEvent = `${event}_callback`, ga },
config = {}
) => {
const options = { ...conf, ...config }
return new Promise((resolve, reject) => {
let timer = null
const apiHandler = (data) => {
clearTimeout(timer)
timer = null
resolve(data)
}
EE.once(callbackEvent, apiHandler)
if (options.timeout) {
timer = setTimeout(() => {
EE.off(callbackEvent, apiHandler)
reject({ callbackEvent, msg: 'Timeout', time: options.timeout })
}, options.timeout)
}
qWebChannel.__apis__.fetchQt({ event, data, page, callbackEvent, ga })
})
},
qtContext,
on: EE.on.bind(EE),
off: EE.off.bind(EE),
once: EE.off.bind(EE),
emit: EE.emit.bind(EE),
}
resolve(qWebChannel.__apis__)
})
} else {
resolve(qWebChannel.__apis__)
}
})
}
qwebApi.js
import qwebchannel, { EE } from '@/tool/qwebchannel'
export const on = EE.on.bind(EE)
export const off = EE.off.bind(EE)
export const once = EE.once.bind(EE)
export const emit = EE.emit.bind(EE)
// 只下发
export const qwebApi = async ({ event, data, page, callbackEvent, ga }) => {
const channel = await qwebchannel()
return channel.fetchQt({ event, data, page, callbackEvent, ga }, {})
}
// 下发、监听、返回值
export const fetchQwebApi = async ({ event, data, page, callbackEvent, ga }, config = {}) => {
const channel = await qwebchannel()
return channel.fetchQtCallback({ event, data, page, callbackEvent, ga }, config)
}
- 在Vue及React中应用时需等待channel建立完成,例如React:
import React from 'react'
import ReactDOM from 'react-dom'
import qwebchannel from '@/tool/qwebchannel'
qwebchannel().then(async (qwebApi) => {
ReactDOM.render(
<App/>,
document.getElementById('root')
)
})
- 具体组件中使用
import { qwebApi, fetchQwebApi } from '@/apis/qwebApi'
qwebApi({
event: 'openClientLoading',
data: null,
})z
const vmList = await fetchQwebApi(
{
event: 'getVmList',
data: { packageName: game.package_name},
},
{ initial: { installed: [], uninstalled: [], defaultEngines: '' } }
)
总结
以上就是Qtwebchannel的使用总结,让客户端与web的交互统一化