一键搞定UniApp WiFi连接!这个Vue 3 Hook让你少走弯路
2025年10月15日 08:05
在移动应用开发中,设备连接功能是常见的需求场景。无论是智能家居配网、设备绑定,还是网络配置,WiFi
之前的文章 微信小程序同声传译插件接入实战:语音识别功能完整实现指南介绍如何使用同声传译插件进行语音识别,这篇将会讲述同声传译的另一个功能语音合成。
微信小程序同声传译插件的语音合成(TTS)功能能将文字内容转换为语音播放,适用于内容朗读、语音提醒、无障碍阅读等场景。
const textToSpeechContent = ref("")
const textToSpeechStatus = ref(0) // 0 未播放 1 合成中 2 正在播放
function onTextToSpeech(text = "") {
// 如果正在播放,先停止
if(textToSpeechStatus.value > 0) {
uni.$emit("STOP_INNER_AUDIO_CONTEXT")
}
textToSpeechStatus.value = 1
uni.showLoading({
title: "语音合成中...",
mask: true,
})
// 处理文本内容
if(text.length) {
textToSpeechContent.value = text
}
// 分段处理长文本(微信限制每次最多200字)
let content = textToSpeechContent.value.slice(0, 200)
textToSpeechContent.value = textToSpeechContent.value.slice(200)
if(!content) {
uni.hideLoading()
return
}
// 调用合成接口
plugin.textToSpeech({
lang: "zh_CN",
tts: true,
content: content,
success: (res) => {
handleSpeechSuccess(res)
},
fail: (res) => {
handleSpeechFail(res)
}
})
}
function handleSpeechSuccess(res) {
uni.hideLoading()
// 创建音频上下文
innerAudioContext = uni.createInnerAudioContext()
innerAudioContext.src = res.filename
innerAudioContext.play()
textToSpeechStatus.value = 2
// 播放结束自动播下一段
innerAudioContext.onEnded(() => {
innerAudioContext = null
textToSpeechStatus.value = 0
onTextToSpeech() // 递归播放剩余内容
})
setupAudioControl()
}
function setupAudioControl() {
uni.$off("STOP_INNER_AUDIO_CONTEXT")
uni.$on("STOP_INNER_AUDIO_CONTEXT", (pause) => {
textToSpeechStatus.value = 0
if(pause) {
innerAudioContext?.pause()
} else {
innerAudioContext?.stop()
innerAudioContext = null
textToSpeechContent.value = ""
}
})
}
function handleSpeechFail(res) {
textToSpeechStatus.value = 0
uni.hideLoading()
toast("不支持合成的文字")
console.log("fail tts", res)
}
由于微信接口限制,单次合成最多200字,需要实现自动分段:
let content = textToSpeechContent.value.slice(0, 200)
textToSpeechContent.value = textToSpeechContent.value.slice(200)
通过状态值精确控制播放流程:
0
:未播放,可以开始新的合成1
:合成中,显示loading状态2
:播放中,可以暂停或停止利用递归实现长文本的自动连续播放:
innerAudioContext.onEnded(() => {
onTextToSpeech() // 播放结束继续合成下一段
})
export function useTextToSpeech() {
const plugin = requirePlugin('WechatSI')
let innerAudioContext = null
const textToSpeechContent = ref("")
const textToSpeechStatus = ref(0)
function onTextToSpeech(text = "") {
if(textToSpeechStatus.value > 0) {
uni.$emit("STOP_INNER_AUDIO_CONTEXT")
}
textToSpeechStatus.value = 1
uni.showLoading({
title: "语音合成中...",
mask: true,
})
if(text.length) {
textToSpeechContent.value = text
}
let content = textToSpeechContent.value.slice(0, 200)
textToSpeechContent.value = textToSpeechContent.value.slice(200)
if(!content) {
uni.hideLoading()
return
}
plugin.textToSpeech({
lang: "zh_CN",
tts: true,
content: content,
success: (res) => {
uni.hideLoading()
innerAudioContext = uni.createInnerAudioContext()
innerAudioContext.src = res.filename
innerAudioContext.play()
textToSpeechStatus.value = 2
innerAudioContext.onEnded(() => {
innerAudioContext = null
textToSpeechStatus.value = 0
onTextToSpeech()
})
uni.$off("STOP_INNER_AUDIO_CONTEXT")
uni.$on("STOP_INNER_AUDIO_CONTEXT", (pause) => {
textToSpeechStatus.value = 0
if(pause) {
innerAudioContext?.pause()
} else {
innerAudioContext?.stop()
innerAudioContext = null
textToSpeechContent.value = ""
}
})
},
fail: (res) => {
textToSpeechStatus.value = 0
uni.hideLoading()
toast("不支持合成的文字")
console.log("fail tts", res)
}
})
}
return {
onTextToSpeech,
textToSpeechContent,
textToSpeechStatus
}
}
在 Electron 应用开发中,我们经常需要在主进程和渲染进程之间进行通信。随着应用功能不断增加,IPC处理器的数量也会急剧增长。传统的手动注册方式会导致代码臃肿、难以维护。本文将介绍一种自动化的 IPC 处理器注册机制。
首先,我们来看核心的注册机制实现:
// project/electron/ipc/index.js
const path = require("path");
const { readdirSync } = require("fs");
const importSync = require("import-sync");
const getIcpMainHandler = () => {
let allHandler = {};
// 扫描 handlers 目录下的所有文件
const dirs = readdirSync(path.join(__dirname, "handlers"), "utf8");
for (const file of dirs) {
const filePath = path.join(__dirname, "handlers", file);
const handlersTemp = importSync(filePath);
let handlers = {}
// 分析每个导出的处理器
for (const key in handlersTemp) {
const handler = handlersTemp[key];
let handlerType = Object.prototype.toString.call(handler);
const match = handlerType.match(/^\[object (\w+)\]$/);
handlerType = match[1];
handlers[key] = {
key,
type: handlerType,
val: handler,
};
allHandler = {
...allHandler,
...handlers,
};
}
}
return allHandler;
}
module.exports.registerHandlerForIcpMain = () => {
const ipcMainHandlers = getIcpMainHandler();
// 只执行函数类型的处理器
for (const key in ipcMainHandlers) {
const handler = ipcMainHandlers[key];
if (handler.type.indexOf("Function") > -1) {
handler.val()
}
}
};
将不同功能的 IPC 处理器分类到不同的文件中:
// project/electron/ipc/handlers/file.js
const { ipcMain } = require("electron");
module.exports.fileHander = () => {
// 处理文件夹清理请求
ipcMain.handle("clear-folder", async (event, path) => {
// 具体的文件操作逻辑
console.log("Clearing folder:", path);
});
// 可以注册更多的文件相关处理器
ipcMain.handle("read-file", async (event, filePath) => {
// 文件读取逻辑
});
}
// project/electron/ipc/handlers/win.js
const { ipcMain, BrowserWindow } = require("electron");
module.exports.winHander = () => {
// 处理窗口打开请求
ipcMain.on('open-window', async (event) => {
// 创建新窗口的逻辑
const win = new BrowserWindow({ width: 800, height: 600 });
});
// 窗口管理相关处理器
ipcMain.on('close-window', async (event) => {
// 窗口关闭逻辑
});
}
在主进程启动时注册所有 IPC 处理器:
// project/electron/main.js
const { registerHandlerForIcpMain } = require("./ipc/index.js")
app.whenReady().then(() => {
// 自动注册所有 IPC 处理器
registerHandlerForIcpMain();
// ... 其他初始化逻辑
});
为了支持开发时的热重载,我们在 Vite 配置中动态扫描 IPC 文件:
// vite.config.js
import fs from "fs"
import path from "path"
// 递归扫描目录(支持子目录)
const scanDeep = (dir) => {
let results = []
const list = fs.readdirSync(dir, { withFileTypes: true })
for (const item of list) {
const fullPath = path.join(dir, item.name)
if (item.isDirectory()) {
results = results.concat(scanDeep(fullPath))
} else if (item.isFile() && [".js", ".cjs", ".mjs"].includes(path.extname(item.name))) {
results.push(fullPath)
}
}
return results
}
// 生成 Electron 配置
export const getElectronConfig = () => {
const electronDir = path.join(__dirname, "../electron")
const ipcEntries = scanDeep(path.join(electronDir, "ipc")).map((file) => ({
// 从项目根目录计算相对路径
entry: path.relative(process.cwd(), file)
}))
return [
// 主进程
{
entry: path.join(process.cwd(), "electron/main.js")
},
// 预加载脚本
{
entry: path.join(process.cwd(), "electron/preload/index.js"),
onstart(args) {
args.reload()
}
},
// 动态 IPC 入口
...ipcEntries
]
}
这种自动化的 IPC 处理器注册机制为 Electron 应用开发带来了显著的好处:
这种模式特别适合中大型 Electron 项目,能够有效管理复杂的进程间通信需求。