
uni-app 获取当前连接的WiFi名
getWifiName: function() {
var wifiManager, wifiInfo;
var Context = plus.android.importClass("android.content.Context");
var WifiManager = plus.android.importClass("android.net.wifi.WifiManager");
var WifiInfo = plus.android.importClass("android.net.wifi.WifiInfo");
wifiManager = plus.android.runtimeMainActivity().getSystemService(Context.WIFI_SERVICE);
wifiInfo = wifiManager.getConnectionInfo();
var ssid = wifiInfo.getSSID() || '';
if (ssid.length == 0) {
return null;
}
//一些手机上获取SSID是有值的,但是实际IP为空,真实为未连接
var i = parseInt(wifiInfo.getIpAddress());
var ipStr = (i & 0xFF) + "." +
((i >> 8) & 0xFF) + "." +
((i >> 16) & 0xFF) + "." +
(i >> 24 & 0xFF);
if (ipStr == "0.0.0.0") {
console.log("NULL");
return null;
}
if (ssid != "<unknown ssid>" && ssid.toUpperCase() != "0X") {
console.log(ssid.replace(/\"/g, ""));
return ssid.replace(/\"/g, "");
}
console.log("NULL");
return null;
},
getWifiName: function() {
var wifiManager, wifiInfo;
var Context = plus.android.importClass("android.content.Context");
var WifiManager = plus.android.importClass("android.net.wifi.WifiManager");
var WifiInfo = plus.android.importClass("android.net.wifi.WifiInfo");
wifiManager = plus.android.runtimeMainActivity().getSystemService(Context.WIFI_SERVICE);
wifiInfo = wifiManager.getConnectionInfo();
var ssid = wifiInfo.getSSID() || '';
if (ssid.length == 0) {
return null;
}
//一些手机上获取SSID是有值的,但是实际IP为空,真实为未连接
var i = parseInt(wifiInfo.getIpAddress());
var ipStr = (i & 0xFF) + "." +
((i >> 8) & 0xFF) + "." +
((i >> 16) & 0xFF) + "." +
(i >> 24 & 0xFF);
if (ipStr == "0.0.0.0") {
console.log("NULL");
return null;
}
if (ssid != "<unknown ssid>" && ssid.toUpperCase() != "0X") {
console.log(ssid.replace(/\"/g, ""));
return ssid.replace(/\"/g, "");
}
console.log("NULL");
return null;
},
收起阅读 »

HbuilderX 从命令行启动指定文件夹
3.29 更新
-
今天看HB更新了 多了个
cli
命令;原本以为是path 结果不是 -
我是这样用的 :
alias cli="/Applications/HBuilderX.app/Contents/MacOS/cli" -
用法就变成这样:

额 感觉还是差点意思(小声)
命令行 指定HbuilderX 启动指定目录(历史内容)
- 在VsCode 中 可以用code 加目录 快捷启动 编辑器
- 我看了下 如果在 HbuilderX中 可以通过设置别名 达到这个效果 以Mac (zsh)为例
#首先打开 .zshrc
#设置别名
alias hbd='hbd(){open $1 -a /Applications/HBuilderX.app}; hbd'
source 后 就可以如下操作了
cd 某个目录
git pull
hbd .
windows10 下
#首先打开 $PROFILE
function hbd([string]$_path){
{hbuilder.exe的路径} $_path
}
- 嗯 符合我的想法
3.29 更新
-
今天看HB更新了 多了个
cli
命令;原本以为是path 结果不是 -
我是这样用的 :
alias cli="/Applications/HBuilderX.app/Contents/MacOS/cli" -
用法就变成这样:
额 感觉还是差点意思(小声)
命令行 指定HbuilderX 启动指定目录(历史内容)
- 在VsCode 中 可以用code 加目录 快捷启动 编辑器
- 我看了下 如果在 HbuilderX中 可以通过设置别名 达到这个效果 以Mac (zsh)为例
#首先打开 .zshrc
#设置别名
alias hbd='hbd(){open $1 -a /Applications/HBuilderX.app}; hbd'
source 后 就可以如下操作了
cd 某个目录
git pull
hbd .
windows10 下
#首先打开 $PROFILE
function hbd([string]$_path){
{hbuilder.exe的路径} $_path
}
- 嗯 符合我的想法

vue3.x+electron聊天程序|Electron跨平台聊天室
前言
随着Electron快速更新迭代及Vue3生态圈愈来愈完善,二者结合开发桌面端程序必然再一次受到开发者热捧。
上一次有给大家分享一个vue3开发移动端短视频实例,今天带来的是最新开发的一款electron+vue3桌面版搭建TIM/QQ聊天应用程序实战项目ElectronQchat。
vue3-electron-qchat 支持同时新开多个窗口、换肤等功能。
框架技术
- 编码+技术:vscode | vue3.0+vuex4+vue-router@4
- 跨端框架:electron11.2.3
- UI组件库:ant-design-vue (蚂蚁桌面端vue3组件库)
- 弹窗组件:v3layer(vue3自定义弹窗组件)
- 滚动条组件:v3scroll(vue3自定义滚动条组件)
- 打包工具:vue-cli-plugin-electron-builder
- 按需引入:babel-plugin-import^1.13.3
项目结构
electron主进程入口
vue3+electron搭建的项目,根目录下有一个background.js,即主进程入口配置。
/**
* 主进程入口配置
*/
import { app, BrowserWindow, globalShortcut } from 'electron'
// import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import Windows from './module/windows'
const isDevelopment = process.env.NODE_ENV !== 'production'
async function createWindow() {
let window = new Windows()
window.listen()
window.createWin({isMainWin: true})
window.createTray()
}
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
app.on('ready', async () => {
createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
electron创建多窗体|父子modal窗口模式
项目支持同时新开多个窗口及modal窗口模式。只需简单调用公共函数createWin即可快速生成一个新窗口。
// 换肤窗口
const handleSkinWin = () => {
createWin({
title: '换肤',
route: '/skin',
width: 720,
height: 475,
resize: false,
})
}
// 朋友圈窗口
const handleFZoneWin = () => {
createWin({
title: '朋友圈',
route: '/fzone',
width: 550,
height: 700,
resize: false,
})
}
支持如下参数配置:
export const winConfig = {
id: null, // 窗口唯一id
background: '#fff', // 背景色
route: '', // 路由地址url
title: '', // 标题
data: null, // 传入数据参数
width: '', // 窗口宽度
height: '', // 窗口高度
minWidth: '', // 窗口最小宽度
minHeight: '', // 窗口最小高度
x: '', // 窗口相对于屏幕左侧坐标
y: '', // 窗口相对于屏幕顶端坐标
resize: true, // 是否支持缩放
maximize: false, // 最大化窗口
isMultiWin: false, // 是否支持多开窗口(为true则会支持创建多个窗口)
isMainWin: false, // 是否主窗口(为true则会替代之前主窗口)
parent: '', // 父窗口(传入父窗口id)
modal: false, // 模态窗口(需设置parent和modal选项)
alwaysOnTop: false, // 是否置顶窗口
}
由于篇幅原因,大家如果对实现过程感兴趣,可以去看看下面这篇文章。
electron无边框窗口|自定义顶部菜单
项目整体采用无边框模式,开启frame:false即可。顶部导航栏及右上角按钮就需要自定义实现了。
设置-webkit-app-region:drag即可自定义拖拽区域,不过拖拽区域鼠标右键会有系统菜单。
可通过如下方法,在创建窗口的时候快速给屏蔽掉。
win.hookWindowMessage(278, () => {
win.setEnabled(false)
setTimeout(() => {
win.setEnabled(true)
}, 100)
return true
})
出于篇幅缘故,大家如果对具体如何实现导航栏菜单感兴趣,可以去看看这篇分享。
electron模仿QQ托盘图标/闪烁
electron提供了Tray函数来创建系统托盘图标,大家可以通过定时器来实现闪烁效果。
需提前准备好两张大小一致的图标文件,一个透明背景就行。然后通过定时器来轮流显示图标。
// 创建系统托盘图标
let tray = null
let flashTimer = null
let trayIco1 = path.join(__dirname, '../static/tray.ico')
let trayIco2 = path.join(__dirname, '../static/tray-empty.ico')
createTray() {
const trayMenu = Menu.buildFromTemplate([
{
label: '我在线上', icon: path.join(__dirname, '../static/icon-online.png'),
click: () => {...}
},
{
label: '忙碌', icon: path.join(__dirname, '../static/icon-busy.png'),
click: () => {...}
},
{
label: '隐身', icon: path.join(__dirname, '../static/icon-invisible.png'),
click: () => {...}
},
{
label: '离开', icon: path.join(__dirname, '../static/icon-offline.png'),
click: () => {...}
},
{type: 'separator'},
{
label: '关闭所有声音', click: () => {...},
},
{
label: '关闭头像闪动', click: () => {
this.flashTray(false)
}
},
{type: 'separator'},
{
label: '打开主窗口', click: () => {
try {
for(let i in this.winLs) {
let win = this.getWin(i)
if(!win) return
if(win.isMinimized()) win.restore()
win.show()
}
} catch (error) {
console.log(error)
}
}
},
{
label: '退出', click: () => {
try {
for(let i in this.winLs) {
let win = this.getWin(i)
if(win) win.webContents.send('win-logout')
}
app.quit()
} catch (error) {
console.log(error)
}
}
},
])
this.tray = new Tray(this.trayIco1)
this.tray.setContextMenu(trayMenu)
this.tray.setToolTip(app.name)
this.tray.on('double-click', () => {
// ...
})
}
// 托盘图标闪烁
flashTray(flash) {
let hasIco = false
if(flash) {
if(this.flashTimer) return
this.flashTimer = setInterval(() => {
this.tray.setImage(hasIco ? this.trayIco1 : this.trayIco2)
hasIco = !hasIco
}, 500)
}else {
if(this.flashTimer) {
clearInterval(this.flashTimer)
this.flashTimer = null
}
this.tray.setImage(this.trayIco1)
}
}
// 销毁托盘图标
destoryTray() {
this.flashTray(false)
this.tray.destroy()
this.tray = null
}
electron快速打包配置
vue3和electron创建的项目,目录下会有一个vue.config.js配置文件。里面可以进行一些vue3配置及electron-builder打包配置。
/**
* @Desc vue3项目配置文件
* @Create andy by 2021-02 Q:282310962 wx:xy190310
*/
const path = require('path')
module.exports = {
// 基本路径
// publicPath: '/',
// 输出文件目录
// outputDir: 'dist',
// assetsDir: '',
// 环境配置
devServer: {
// host: 'localhost',
// port: 8080,
// 是否开启https
https: false,
// 编译完是否打开网页
open: false,
// 代理配置
// proxy: {
// '^/api': {
// target: '<url>',
// ws: true,
// changeOrigin: true
// },
// '^/foo': {
// target: '<other_url>'
// }
// }
},
// webpack配置
chainWebpack: config => {
// 配置路径别名
config.resolve.alias
.set('@', path.join(__dirname, 'src'))
.set('@assets', path.join(__dirname, 'src/assets'))
.set('@components', path.join(__dirname, 'src/components'))
.set('@module', path.join(__dirname, 'src/module'))
.set('@plugins', path.join(__dirname, 'src/plugins'))
.set('@layouts', path.join(__dirname, 'src/layouts'))
.set('@views', path.join(__dirname, 'src/views'))
},
// 插件配置
pluginOptions: {
electronBuilder: {
// 配置后可以在渲染进程使用ipcRenderer
nodeIntegration: true,
// 项目打包参数配置
builderOptions: {
"productName": "electron-qchat", //项目名称 打包生成exe的前缀名
"appId": "com.example.electronqchat", //包名
"compression": "maximum", //store|normal|maximum 打包压缩情况(store速度较快)
"artifactName": "${productName}-${version}-${platform}-${arch}.${ext}", //打包后安装包名称
// "directories": {
// "output": "build", //输出文件夹(默认dist_electron)
// },
"asar": false, //asar打包
// 拷贝静态资源目录到指定位置
"extraResources": [
{
"from": "./static",
"to": "static"
},
],
"nsis": {
"oneClick": false, //一键安装
"allowToChangeInstallationDirectory": true, //允许修改安装目录
"perMachine": true, //是否开启安装时权限设置(此电脑或当前用户)
"artifactName": "${productName}-${version}-${platform}-${arch}-setup.${ext}", //打包后安装包名称
"deleteAppDataOnUninstall": true, //卸载时删除数据
"createDesktopShortcut": true, //创建桌面图标
"createStartMenuShortcut": true, //创建开始菜单图标
"shortcutName": "ElectronQChat", //桌面快捷键图标名称
},
"win": {
"icon": "./static/shortcut.ico", //图标路径
}
}
}
}
}
注意事项
1、项目路径最好不能含有中文,否则打包会失败!
2、最好不要使用getCurrentInstance来操作router或store,否则打包会报错!
3、在渲染进程中使用ipcRenderer或remote模块,记得在创建窗体的时候配置nodeIntegration: true和enableRemoteModule: true
否则会出现如下错误提示:Uncaught TypeError: fs.existsSync is not a function
4、如果打包后图标或外部dll无效,记得在打包的时候配置静态资源转移extraResources
// 拷贝静态资源目录到指定位置
"extraResources": [
{
"from": "./static",
"to": "static"
},
]
ok,以上就是vue3+electron开发客户端聊天软件的一些分享,希望对大家有些帮助!
链接:https://juejin.cn/post/6933871014937886727/
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
前言
随着Electron快速更新迭代及Vue3生态圈愈来愈完善,二者结合开发桌面端程序必然再一次受到开发者热捧。
上一次有给大家分享一个vue3开发移动端短视频实例,今天带来的是最新开发的一款electron+vue3桌面版搭建TIM/QQ聊天应用程序实战项目ElectronQchat。
vue3-electron-qchat 支持同时新开多个窗口、换肤等功能。
框架技术
- 编码+技术:vscode | vue3.0+vuex4+vue-router@4
- 跨端框架:electron11.2.3
- UI组件库:ant-design-vue (蚂蚁桌面端vue3组件库)
- 弹窗组件:v3layer(vue3自定义弹窗组件)
- 滚动条组件:v3scroll(vue3自定义滚动条组件)
- 打包工具:vue-cli-plugin-electron-builder
- 按需引入:babel-plugin-import^1.13.3
项目结构
electron主进程入口
vue3+electron搭建的项目,根目录下有一个background.js,即主进程入口配置。
/**
* 主进程入口配置
*/
import { app, BrowserWindow, globalShortcut } from 'electron'
// import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import Windows from './module/windows'
const isDevelopment = process.env.NODE_ENV !== 'production'
async function createWindow() {
let window = new Windows()
window.listen()
window.createWin({isMainWin: true})
window.createTray()
}
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
app.on('ready', async () => {
createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
electron创建多窗体|父子modal窗口模式
项目支持同时新开多个窗口及modal窗口模式。只需简单调用公共函数createWin即可快速生成一个新窗口。
// 换肤窗口
const handleSkinWin = () => {
createWin({
title: '换肤',
route: '/skin',
width: 720,
height: 475,
resize: false,
})
}
// 朋友圈窗口
const handleFZoneWin = () => {
createWin({
title: '朋友圈',
route: '/fzone',
width: 550,
height: 700,
resize: false,
})
}
支持如下参数配置:
export const winConfig = {
id: null, // 窗口唯一id
background: '#fff', // 背景色
route: '', // 路由地址url
title: '', // 标题
data: null, // 传入数据参数
width: '', // 窗口宽度
height: '', // 窗口高度
minWidth: '', // 窗口最小宽度
minHeight: '', // 窗口最小高度
x: '', // 窗口相对于屏幕左侧坐标
y: '', // 窗口相对于屏幕顶端坐标
resize: true, // 是否支持缩放
maximize: false, // 最大化窗口
isMultiWin: false, // 是否支持多开窗口(为true则会支持创建多个窗口)
isMainWin: false, // 是否主窗口(为true则会替代之前主窗口)
parent: '', // 父窗口(传入父窗口id)
modal: false, // 模态窗口(需设置parent和modal选项)
alwaysOnTop: false, // 是否置顶窗口
}
由于篇幅原因,大家如果对实现过程感兴趣,可以去看看下面这篇文章。
electron无边框窗口|自定义顶部菜单
项目整体采用无边框模式,开启frame:false即可。顶部导航栏及右上角按钮就需要自定义实现了。
设置-webkit-app-region:drag即可自定义拖拽区域,不过拖拽区域鼠标右键会有系统菜单。
可通过如下方法,在创建窗口的时候快速给屏蔽掉。
win.hookWindowMessage(278, () => {
win.setEnabled(false)
setTimeout(() => {
win.setEnabled(true)
}, 100)
return true
})
出于篇幅缘故,大家如果对具体如何实现导航栏菜单感兴趣,可以去看看这篇分享。
electron模仿QQ托盘图标/闪烁
electron提供了Tray函数来创建系统托盘图标,大家可以通过定时器来实现闪烁效果。
需提前准备好两张大小一致的图标文件,一个透明背景就行。然后通过定时器来轮流显示图标。
// 创建系统托盘图标
let tray = null
let flashTimer = null
let trayIco1 = path.join(__dirname, '../static/tray.ico')
let trayIco2 = path.join(__dirname, '../static/tray-empty.ico')
createTray() {
const trayMenu = Menu.buildFromTemplate([
{
label: '我在线上', icon: path.join(__dirname, '../static/icon-online.png'),
click: () => {...}
},
{
label: '忙碌', icon: path.join(__dirname, '../static/icon-busy.png'),
click: () => {...}
},
{
label: '隐身', icon: path.join(__dirname, '../static/icon-invisible.png'),
click: () => {...}
},
{
label: '离开', icon: path.join(__dirname, '../static/icon-offline.png'),
click: () => {...}
},
{type: 'separator'},
{
label: '关闭所有声音', click: () => {...},
},
{
label: '关闭头像闪动', click: () => {
this.flashTray(false)
}
},
{type: 'separator'},
{
label: '打开主窗口', click: () => {
try {
for(let i in this.winLs) {
let win = this.getWin(i)
if(!win) return
if(win.isMinimized()) win.restore()
win.show()
}
} catch (error) {
console.log(error)
}
}
},
{
label: '退出', click: () => {
try {
for(let i in this.winLs) {
let win = this.getWin(i)
if(win) win.webContents.send('win-logout')
}
app.quit()
} catch (error) {
console.log(error)
}
}
},
])
this.tray = new Tray(this.trayIco1)
this.tray.setContextMenu(trayMenu)
this.tray.setToolTip(app.name)
this.tray.on('double-click', () => {
// ...
})
}
// 托盘图标闪烁
flashTray(flash) {
let hasIco = false
if(flash) {
if(this.flashTimer) return
this.flashTimer = setInterval(() => {
this.tray.setImage(hasIco ? this.trayIco1 : this.trayIco2)
hasIco = !hasIco
}, 500)
}else {
if(this.flashTimer) {
clearInterval(this.flashTimer)
this.flashTimer = null
}
this.tray.setImage(this.trayIco1)
}
}
// 销毁托盘图标
destoryTray() {
this.flashTray(false)
this.tray.destroy()
this.tray = null
}
electron快速打包配置
vue3和electron创建的项目,目录下会有一个vue.config.js配置文件。里面可以进行一些vue3配置及electron-builder打包配置。
/**
* @Desc vue3项目配置文件
* @Create andy by 2021-02 Q:282310962 wx:xy190310
*/
const path = require('path')
module.exports = {
// 基本路径
// publicPath: '/',
// 输出文件目录
// outputDir: 'dist',
// assetsDir: '',
// 环境配置
devServer: {
// host: 'localhost',
// port: 8080,
// 是否开启https
https: false,
// 编译完是否打开网页
open: false,
// 代理配置
// proxy: {
// '^/api': {
// target: '<url>',
// ws: true,
// changeOrigin: true
// },
// '^/foo': {
// target: '<other_url>'
// }
// }
},
// webpack配置
chainWebpack: config => {
// 配置路径别名
config.resolve.alias
.set('@', path.join(__dirname, 'src'))
.set('@assets', path.join(__dirname, 'src/assets'))
.set('@components', path.join(__dirname, 'src/components'))
.set('@module', path.join(__dirname, 'src/module'))
.set('@plugins', path.join(__dirname, 'src/plugins'))
.set('@layouts', path.join(__dirname, 'src/layouts'))
.set('@views', path.join(__dirname, 'src/views'))
},
// 插件配置
pluginOptions: {
electronBuilder: {
// 配置后可以在渲染进程使用ipcRenderer
nodeIntegration: true,
// 项目打包参数配置
builderOptions: {
"productName": "electron-qchat", //项目名称 打包生成exe的前缀名
"appId": "com.example.electronqchat", //包名
"compression": "maximum", //store|normal|maximum 打包压缩情况(store速度较快)
"artifactName": "${productName}-${version}-${platform}-${arch}.${ext}", //打包后安装包名称
// "directories": {
// "output": "build", //输出文件夹(默认dist_electron)
// },
"asar": false, //asar打包
// 拷贝静态资源目录到指定位置
"extraResources": [
{
"from": "./static",
"to": "static"
},
],
"nsis": {
"oneClick": false, //一键安装
"allowToChangeInstallationDirectory": true, //允许修改安装目录
"perMachine": true, //是否开启安装时权限设置(此电脑或当前用户)
"artifactName": "${productName}-${version}-${platform}-${arch}-setup.${ext}", //打包后安装包名称
"deleteAppDataOnUninstall": true, //卸载时删除数据
"createDesktopShortcut": true, //创建桌面图标
"createStartMenuShortcut": true, //创建开始菜单图标
"shortcutName": "ElectronQChat", //桌面快捷键图标名称
},
"win": {
"icon": "./static/shortcut.ico", //图标路径
}
}
}
}
}
注意事项
1、项目路径最好不能含有中文,否则打包会失败!
2、最好不要使用getCurrentInstance来操作router或store,否则打包会报错!
3、在渲染进程中使用ipcRenderer或remote模块,记得在创建窗体的时候配置nodeIntegration: true和enableRemoteModule: true
否则会出现如下错误提示:Uncaught TypeError: fs.existsSync is not a function
4、如果打包后图标或外部dll无效,记得在打包的时候配置静态资源转移extraResources
// 拷贝静态资源目录到指定位置
"extraResources": [
{
"from": "./static",
"to": "static"
},
]
ok,以上就是vue3+electron开发客户端聊天软件的一些分享,希望对大家有些帮助!
链接:https://juejin.cn/post/6933871014937886727/
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

招聘UNI-APP开发(全职)
1、三年以上 uniapp/vue前端研发经验,参与过比较复杂的前端交互场景;
2、精通uniapp/vue,对Web标准和标签语义化有深入理解;
3、有良好的代码习惯,要求结构清晰,命名规范逻辑性强,代码冗余低,有用户需求分析能力。
4、具备良好的团队协作精神,能利用自身技术能力提升团队整体研发效率,提高团队影响力;
公司运营自有B2B平台项目,不是外包公司。项目有发展前景,欢迎联系
1、三年以上 uniapp/vue前端研发经验,参与过比较复杂的前端交互场景;
2、精通uniapp/vue,对Web标准和标签语义化有深入理解;
3、有良好的代码习惯,要求结构清晰,命名规范逻辑性强,代码冗余低,有用户需求分析能力。
4、具备良好的团队协作精神,能利用自身技术能力提升团队整体研发效率,提高团队影响力;
公司运营自有B2B平台项目,不是外包公司。项目有发展前景,欢迎联系
收起阅读 »
uni组件之——webview与APP之间的通讯
首先,让我们来谈谈应用的业务场景。大伙的业务场景是什么样我不知道,我来谈谈我踩坑时的业务场景。
老板:我们有个移动端的网页项目,但是为了唬客户,我们需要把这个网页打包成ios端和安卓端的app,页面中有一个需要调用摄像头的拍照签到功能。
我内心活动:ok,uni + webview没跑了,但是webview是不能直接调用手机的摄像头的,他的逻辑大概是,点击网页的签到按钮触发一个事件,事件给app发送一个我要使用手机摄像头的需求,然后再uni中收到了信号,去调起手机的摄像头。
所以,搞明白了业务场景,不管三七二十一,先上案例,看看最简单的demo咋实现。
html页面:
首先模拟一个网页,我们就用一个静态的html网页就好。webview要访问本地静态页面的话,静态页面是需要放在static目录底下的,在这我们命名为index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>网页实时向APP发送消息</title>
<!-- uni 的 SDK,必须引用。 -->
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
<!-- 微信 JS-SDK 如果不需要兼容小程序,则无需引用此 JS 文件。 -->
<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
</head>
<body>
<script type="text/javascript">
// 在引用依赖的文件后,需要在 HTML 中监听 UniAppJSBridgeReady 事件触发后,才能安全调用 uni 的 API。
document.addEventListener('UniAppJSBridgeReady', function() {
uni.getEnv(function(res) {
console.log('当前环境:' + JSON.stringify(res));
});
//向APP发送消息
uni.postMessage({
data: {
action: 'message'
}
});
//接收APP发送过来的消息
handleMessage(evt) {
console.log('接收到的消息:' + JSON.stringify(evt.detail.data));
}
});
</script>
</body>
</html>
uni页面:
这里为index.nvue页面(注意这个nvue,跟vue页面是有差别的,后面就是踩坑点之一)
<template>
<view class="webview-box">
<web-view ref="webview" class="webview" src="../../static/index.html" @onPostMessage="handleMessage"></web-view>
</view>
</template>
<script>
export default {
data() {
return {
wv: null
}
},
onLoad() {
const self = this;
// #ifdef APP-PLUS
let currentWebview = getCurrentPages()[0];
setTimeout(function() {
self.wv = currentWebview.$getAppWebview();
//调用下面函数
self.handlePostMessage("消息");
}, 1000);
// #endif
},
mouted(){
},
methods: {
// 接收h5页面发来的键值判断需要执行的操作
handleMessage(evt) {
console.log("postMessage: ", evt)
},
// 获取到对应webview(关键)通过evalJs(注意大小写,如果不知道evalJ是什么,可自行百度) 执行网页的函数,可对其进行传参,完成与网页的通讯
handlePostMessage(res) {
this.$refs.webview.evalJs(`handleMessage(${res})`);
}
}
}
</script>
<style>
.webview-box {
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
}
.webview {
flex: 1;
}
</style>
好了,是不是觉得很简单,事实上也很简单,但是鄙人非出脑瘫,踩了两个巨坑。两个都是在uni的页面的,如果大伙也踩到的话,那恭喜了,看到了我。
第一,我用的是nvue页面,nvue获取webview窗口的方式是与普通vue获取webview的方式不一样的,这一步非常关键,如果获取不正确就算你以下写的东西是正确的也没有效果,具体是什么原理我也没深究,以后懂了再更好了,先实现业务。具体获取方法看案例,自己console一下看看要获取的是哪个webview,因为如果大伙的项目是有自定义导航栏获取其他nuve组件的话也算是一个webview。
第二个坑就是,一定要用在uni的webview组件上使用@onPostMessage
这个方法进行消息的监听,我当时也不知道犯了什么病,我用的是message,老半天调不出。message只有在特定的情况下才会接收到网页的消息,比如你执行了返回或着其他的特定场景操作的时候,具体可以看官网给出的。踩坑记录就到这啦,希望能帮助到大伙。
- 参考资料:[1] dcloud社区
首先,让我们来谈谈应用的业务场景。大伙的业务场景是什么样我不知道,我来谈谈我踩坑时的业务场景。
老板:我们有个移动端的网页项目,但是为了唬客户,我们需要把这个网页打包成ios端和安卓端的app,页面中有一个需要调用摄像头的拍照签到功能。
我内心活动:ok,uni + webview没跑了,但是webview是不能直接调用手机的摄像头的,他的逻辑大概是,点击网页的签到按钮触发一个事件,事件给app发送一个我要使用手机摄像头的需求,然后再uni中收到了信号,去调起手机的摄像头。
所以,搞明白了业务场景,不管三七二十一,先上案例,看看最简单的demo咋实现。
html页面:
首先模拟一个网页,我们就用一个静态的html网页就好。webview要访问本地静态页面的话,静态页面是需要放在static目录底下的,在这我们命名为index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>网页实时向APP发送消息</title>
<!-- uni 的 SDK,必须引用。 -->
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
<!-- 微信 JS-SDK 如果不需要兼容小程序,则无需引用此 JS 文件。 -->
<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
</head>
<body>
<script type="text/javascript">
// 在引用依赖的文件后,需要在 HTML 中监听 UniAppJSBridgeReady 事件触发后,才能安全调用 uni 的 API。
document.addEventListener('UniAppJSBridgeReady', function() {
uni.getEnv(function(res) {
console.log('当前环境:' + JSON.stringify(res));
});
//向APP发送消息
uni.postMessage({
data: {
action: 'message'
}
});
//接收APP发送过来的消息
handleMessage(evt) {
console.log('接收到的消息:' + JSON.stringify(evt.detail.data));
}
});
</script>
</body>
</html>
uni页面:
这里为index.nvue页面(注意这个nvue,跟vue页面是有差别的,后面就是踩坑点之一)
<template>
<view class="webview-box">
<web-view ref="webview" class="webview" src="../../static/index.html" @onPostMessage="handleMessage"></web-view>
</view>
</template>
<script>
export default {
data() {
return {
wv: null
}
},
onLoad() {
const self = this;
// #ifdef APP-PLUS
let currentWebview = getCurrentPages()[0];
setTimeout(function() {
self.wv = currentWebview.$getAppWebview();
//调用下面函数
self.handlePostMessage("消息");
}, 1000);
// #endif
},
mouted(){
},
methods: {
// 接收h5页面发来的键值判断需要执行的操作
handleMessage(evt) {
console.log("postMessage: ", evt)
},
// 获取到对应webview(关键)通过evalJs(注意大小写,如果不知道evalJ是什么,可自行百度) 执行网页的函数,可对其进行传参,完成与网页的通讯
handlePostMessage(res) {
this.$refs.webview.evalJs(`handleMessage(${res})`);
}
}
}
</script>
<style>
.webview-box {
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
}
.webview {
flex: 1;
}
</style>
好了,是不是觉得很简单,事实上也很简单,但是鄙人非出脑瘫,踩了两个巨坑。两个都是在uni的页面的,如果大伙也踩到的话,那恭喜了,看到了我。
第一,我用的是nvue页面,nvue获取webview窗口的方式是与普通vue获取webview的方式不一样的,这一步非常关键,如果获取不正确就算你以下写的东西是正确的也没有效果,具体是什么原理我也没深究,以后懂了再更好了,先实现业务。具体获取方法看案例,自己console一下看看要获取的是哪个webview,因为如果大伙的项目是有自定义导航栏获取其他nuve组件的话也算是一个webview。
第二个坑就是,一定要用在uni的webview组件上使用@onPostMessage
这个方法进行消息的监听,我当时也不知道犯了什么病,我用的是message,老半天调不出。message只有在特定的情况下才会接收到网页的消息,比如你执行了返回或着其他的特定场景操作的时候,具体可以看官网给出的。踩坑记录就到这啦,希望能帮助到大伙。
- 参考资料:[1] dcloud社区

HBuilderX android安卓手机 wifi无线调试
HBuilderX的安装包中有自带的adb 不需要自己重新下载
我的HBuilderX是最新的稳定版 目录是:
HBuilderX\plugins\launcher\tools\adbs
手机和电脑连接同一局域网(连同一个 WiFi)
运行cmd 切换到HBuilderX\plugins\launcher\tools\adbs
手机数据线连上电脑(打开允许usb调试),输入命令:adb tcpip <端口号>,<端口号> 可任意取,只要没被占用,如 6666
adb tcpip 6666 成功提示:restarting in TCP mode port :6666
查看手机的ip,输入命令 adb connect 192.168.2.102:666 此处192.168.2.102 替换为自己的ip
成功提示:connected to 192.168.2.102:6666
adb devices 可查看监听的设备
HBuilderX的安装包中有自带的adb 不需要自己重新下载
我的HBuilderX是最新的稳定版 目录是:
HBuilderX\plugins\launcher\tools\adbs
手机和电脑连接同一局域网(连同一个 WiFi)
运行cmd 切换到HBuilderX\plugins\launcher\tools\adbs
手机数据线连上电脑(打开允许usb调试),输入命令:adb tcpip <端口号>,<端口号> 可任意取,只要没被占用,如 6666
adb tcpip 6666 成功提示:restarting in TCP mode port :6666
查看手机的ip,输入命令 adb connect 192.168.2.102:666 此处192.168.2.102 替换为自己的ip
成功提示:connected to 192.168.2.102:6666
adb devices 可查看监听的设备
收起阅读 »
组件卸载之后取消未完成的请求
近期使用uni-app开发的项目中出现一个问题,在发起请求后如果用户直接点击的后退按钮,那么之前页面的请求还会继续加载,并且成功的时候依然会执行success的逻辑。
比如先从A页面跳转到B页面,然后在B页面查询数据,正常情况是查询成功后跳转到一个新的C页面,如果在请求成功之前用户不想去做后面的操作了,直接点击了浏览器的回退按钮,那么回退到A页面之后B页面的请求依然会继续执行,并且请求成功以后success的逻辑也会执行,会导致从A页面直接跳转的C页面。
最好的解决办法可能是①在离开一个页面后可以直接清空该页面组件的相关逻辑,并且停止页面内的请求?我找了一下没有看到这样的配置或者文档,所以不确定是否能够实现,并且如果有的页面逻辑是②:B页面同时发起两个请求task1,task2,在task1请求成功时会跳转页面,而task2的请求只负责传递数据,不做页面交互,那么在task1请求成功后页面跳转,B页面组件卸载直接停止task2请求的话可能也不合适。所以目前比较适合且可以实现的方式可能是在某些情况下手动停止请求。
关于uni.request停止请求的方法可以查看uni.request
直接使用uni.request的abort方法可以满足上方需求,但是写起来会比较麻烦,每一次请求都需要去保存对应的requestTask,在需要的时候再调用requestTask的abort方法。所以为了简化多请求的取消操作/写法,所以封装了一个方法。
我们的项目之前已经封装了uni.request方法,提供了自己的一些默认参数、请求头、返回数据处理以及错误提示等逻辑,这次在已有的基础上多加了取消请求的相关逻辑。这里我的思路是在发起请求时将当前requestTask作为一个对象的值存起来,对应的键可以是随机数或者guid格式的字符串,在请求成功或者失败后从对象中删除该键;当需要取消请求时把对象内的所有requestTask全部调用一次abort方法,以此取消请求。考虑到②的情况,所以暴露出去的request方法可以提供一个参数用于标识该请求是否可以被取消。而如果是希望实现①的情况的话,也可以监听路由变化,或者使用路由守卫,在页面变更时调用统一的取消请求方法。
import Vue from 'vue';
import { guid } from './utils.js';
let canCancelRequestObj = {};
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌过期)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const errortext = codeMessage[response.status] || response.statusText;
//校验不通过,则
const error = new Error(errortext);
error.name = response.status;
error.response = response;
throw error;
}
/**
- Requests a URL, returning a promise.
- @param {object} [options] The options we want to pass to "request"
-
@return {object} An object containing either "data" or "err"
*/
export default function request(options,canCancel=false) {
// const defaultOptions = {
// credentials: 'include',
// };
let requestId = guid();
console.log('canCancelRequestObj:',canCancelRequestObj);
if(!options.url){
console.log('无请求url')
return
}
const newOptions = {
// ...defaultOptions,
...options,
success(res) {
delete canCancelRequestObj[requestId];
// console.log('response:'+JSON.stringify(res));
if(res.statusCode == '401' || (res.statusCode != '200'&&!res.data)){
uni.hideLoading();
let strResult='';
//清空登陆信息,跳转到登陆页
console.log('登陆信息失效')
if(res.data){
strResult = res.data;
}else{
strResult = res.errMsg;
}
let jsondata = {
result: strResult
};
Vue.prototype && Vue.prototype.$bridge && Vue.prototype.$bridge.callhandler&&Vue.prototype.$bridge.callhandler('jsCallNative', {
action: 'goLogin',
data: JSON.stringify(jsondata)
}, (data) => {}); return } options.success&&options.success(res.data)
},
fail(error) {
if(error&&error.errMsg&&error.errMsg.includes('fail abort')){
console.log(error)
//主动取消的请求
return
}else{
options.fail&&options.fail(error)
}
}
};
let Authorization = window.sessionStorage.getItem('ACCESS_TOKEN');
newOptions.header = {
Authorization:Authorization,
from:'PSM_ANDROID',
timestamp:new Date().getTime(),
};
switch (uni.getSystemInfoSync().platform) {
case 'android':
newOptions.header.from = 'PSM_ANDROID';
break;
case 'ios':
newOptions.header.from = 'PSM_APPLE';
break;
default:
newOptions.header.from = 'PSM_APPLE';break;
}
if (newOptions.method === 'POST' || newOptions.method === 'PUT' || newOptions.method === 'DELETE') {
newOptions.header = {
// Accept: 'application/json',
'content-type': 'application/json;',
...newOptions.header,
};
}
console.log(JSON.stringify(newOptions));
let requestTask = uni.request(newOptions);
if(canCancel){
canCancelRequestObj[requestId] = requestTask;
}
return requestTask;
}
export function cancelRequest (){
console.log(canCancelRequestObj);
if(!canCancelRequestObj||typeof canCancelRequestObj != 'object'){
return
}
Object.keys(canCancelRequestObj).forEach(item=>canCancelRequestObj[item].abort());
}
近期使用uni-app开发的项目中出现一个问题,在发起请求后如果用户直接点击的后退按钮,那么之前页面的请求还会继续加载,并且成功的时候依然会执行success的逻辑。
比如先从A页面跳转到B页面,然后在B页面查询数据,正常情况是查询成功后跳转到一个新的C页面,如果在请求成功之前用户不想去做后面的操作了,直接点击了浏览器的回退按钮,那么回退到A页面之后B页面的请求依然会继续执行,并且请求成功以后success的逻辑也会执行,会导致从A页面直接跳转的C页面。
最好的解决办法可能是①在离开一个页面后可以直接清空该页面组件的相关逻辑,并且停止页面内的请求?我找了一下没有看到这样的配置或者文档,所以不确定是否能够实现,并且如果有的页面逻辑是②:B页面同时发起两个请求task1,task2,在task1请求成功时会跳转页面,而task2的请求只负责传递数据,不做页面交互,那么在task1请求成功后页面跳转,B页面组件卸载直接停止task2请求的话可能也不合适。所以目前比较适合且可以实现的方式可能是在某些情况下手动停止请求。
关于uni.request停止请求的方法可以查看uni.request
直接使用uni.request的abort方法可以满足上方需求,但是写起来会比较麻烦,每一次请求都需要去保存对应的requestTask,在需要的时候再调用requestTask的abort方法。所以为了简化多请求的取消操作/写法,所以封装了一个方法。
我们的项目之前已经封装了uni.request方法,提供了自己的一些默认参数、请求头、返回数据处理以及错误提示等逻辑,这次在已有的基础上多加了取消请求的相关逻辑。这里我的思路是在发起请求时将当前requestTask作为一个对象的值存起来,对应的键可以是随机数或者guid格式的字符串,在请求成功或者失败后从对象中删除该键;当需要取消请求时把对象内的所有requestTask全部调用一次abort方法,以此取消请求。考虑到②的情况,所以暴露出去的request方法可以提供一个参数用于标识该请求是否可以被取消。而如果是希望实现①的情况的话,也可以监听路由变化,或者使用路由守卫,在页面变更时调用统一的取消请求方法。
import Vue from 'vue';
import { guid } from './utils.js';
let canCancelRequestObj = {};
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌过期)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const errortext = codeMessage[response.status] || response.statusText;
//校验不通过,则
const error = new Error(errortext);
error.name = response.status;
error.response = response;
throw error;
}
/**
- Requests a URL, returning a promise.
- @param {object} [options] The options we want to pass to "request"
-
@return {object} An object containing either "data" or "err"
*/
export default function request(options,canCancel=false) {
// const defaultOptions = {
// credentials: 'include',
// };
let requestId = guid();
console.log('canCancelRequestObj:',canCancelRequestObj);
if(!options.url){
console.log('无请求url')
return
}
const newOptions = {
// ...defaultOptions,
...options,
success(res) {
delete canCancelRequestObj[requestId];
// console.log('response:'+JSON.stringify(res));
if(res.statusCode == '401' || (res.statusCode != '200'&&!res.data)){
uni.hideLoading();
let strResult='';
//清空登陆信息,跳转到登陆页
console.log('登陆信息失效')
if(res.data){
strResult = res.data;
}else{
strResult = res.errMsg;
}
let jsondata = {
result: strResult
};
Vue.prototype && Vue.prototype.$bridge && Vue.prototype.$bridge.callhandler&&Vue.prototype.$bridge.callhandler('jsCallNative', {
action: 'goLogin',
data: JSON.stringify(jsondata)
}, (data) => {}); return } options.success&&options.success(res.data)
},
fail(error) {
if(error&&error.errMsg&&error.errMsg.includes('fail abort')){
console.log(error)
//主动取消的请求
return
}else{
options.fail&&options.fail(error)
}
}
};
let Authorization = window.sessionStorage.getItem('ACCESS_TOKEN');
newOptions.header = {
Authorization:Authorization,
from:'PSM_ANDROID',
timestamp:new Date().getTime(),
};
switch (uni.getSystemInfoSync().platform) {
case 'android':
newOptions.header.from = 'PSM_ANDROID';
break;
case 'ios':
newOptions.header.from = 'PSM_APPLE';
break;
default:
newOptions.header.from = 'PSM_APPLE';break;
}
if (newOptions.method === 'POST' || newOptions.method === 'PUT' || newOptions.method === 'DELETE') {
newOptions.header = {
// Accept: 'application/json',
'content-type': 'application/json;',
...newOptions.header,
};
}
console.log(JSON.stringify(newOptions));
let requestTask = uni.request(newOptions);
if(canCancel){
canCancelRequestObj[requestId] = requestTask;
}
return requestTask;
}
export function cancelRequest (){
console.log(canCancelRequestObj);
if(!canCancelRequestObj||typeof canCancelRequestObj != 'object'){
return
}
Object.keys(canCancelRequestObj).forEach(item=>canCancelRequestObj[item].abort());
}

基于uniCloud/静态托管实现苹果IEP证书类型的ipa下载安装服务
uniCloud 是 DCloud 联合阿里云、腾讯云,为开发者提供的基于 serverless 模式和 js 编程的云开发平台。
uniCloud
的 web控制台地址:https://unicloud.dcloud.net.cn
Apple Developer Enterprise Program
可让大型组织开发内部专属的 app 并向其员工部署。此计划仅适用于需要使用安全的内部系统或经由移动设备管理解决方案,向员工私密地直接分发的特例情况。详情:https://developer.apple.com/cn/programs/enterprise/
原 IEP
(iOS Developer Enterprise Program),已统一为 Apple Developer Enterprise Program
步骤如下
第1步
开通uniCloud,详情 https://unicloud.dcloud.net.cn/
第2步
在 uniCloud web控制台左侧栏 "前端网页托管" 上传5个文件,目录结构如下
根目录 >
| app_name_v3.0.0.ipa
| manifest.plist
| index.html
| icon_58.png
| icon_512.png
文件目录介绍
app_name_v3.0.0.ipa 是使用`IEP`证书的应用安装包
manifest.plist 是应用的描述文件
index.html 是用户安装应用页面
icon_58.png 是应用显示的图标,分辨率 58x58,名字可自定义
icon_512.png 是应用的最大图标,分辨率 512x512,名字可自定义
manifest.plist
配置
- 新建文件 manifest.plist 文件并粘贴下面
新闻模板
的 manifest.plist 配置,.plist 是扩展名 - 替换 manifest.plist 文件内容为你的应用信息
- 在 manifest.plist 搜索应用名称
新闻模板
- 在 manifest.plist 搜索应用下载地址
https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa
- 在 manifest.plist 搜索icon_58.png图标
https://static-xxxx.bspapp.com/download/icon_58.png
- 在 manifest.plist 搜索icon_512.png图标
https://static-xxxx.bspapp.com/download/icon_512.png
- 在 manifest.plist 搜索应用名称
注意:
https://static-xxxx.bspapp.com/
是前端网页托管的默认域名,腾讯和阿里的不同,可绑定自己的域名manifest.plist
可配置不同设备的.ipa,例如 iPad, iPhone,示例中共用了一个.ipa- 安装包是有有效期的,过期后需要更新证书重新打包
新闻模板
的 manifest.plist
配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
</dict>
<dict>
<key>kind</key>
<string>display-image</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/icon_58.png</string>
</dict>
<dict>
<key>kind</key>
<string>full-size-image</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/icon_512.png</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>io.dcloud.new.template</string>
<key>bundle-version</key>
<string>2.4.3</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>新闻模板</string>
</dict>
<key>thinned-assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPhone6,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone6,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone7,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone8,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone8,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPod7,1</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array/>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPhone8,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone11,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone11,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone11,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,3</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array>
<string>iPhone11,6</string>
<string>iPhone11,2</string>
<string>iPhone11,4</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPad5,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,7</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,9</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad5,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad5,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,7</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,7</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,8</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,12</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,8</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad5,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,11</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,8</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,2</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array>
<string>iPad8,1</string>
<string>iPad8,8</string>
<string>iPad8,7</string>
<string>iPad8,6</string>
<string>iPad8,5</string>
<string>iPad8,4</string>
<string>iPad8,3</string>
<string>iPad8,2</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPad3,6</string>
<string>iPad3,4</string>
<string>iPad3,2</string>
<string>iPad3,5</string>
<string>iPad3,3</string>
<string>iPad3,1</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPhone11,8</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array>
<string>iPhone11,8</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPhone10,2</string>
<string>iPhone7,1</string>
<string>iPhone10,6</string>
<string>iPhone10,5</string>
<string>iPhone8,2</string>
<string>iPhone9,4</string>
<string>iPhone10,3</string>
<string>iPhone9,2</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPhone5,2</string>
<string>iPod5,1</string>
<string>iPhone5,3</string>
<string>iPhone4,1</string>
<string>iPhone5,1</string>
<string>iPhone5,4</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPad7,4</string>
<string>iPad4,5</string>
<string>iPad7,5</string>
<string>iPad4,6</string>
<string>iPad6,3</string>
<string>iPad5,1</string>
<string>iPad6,11</string>
<string>iPad7,6</string>
<string>iPad5,2</string>
<string>iPad6,4</string>
<string>iPad4,7</string>
<string>iPad5,3</string>
<string>iPad4,1</string>
<string>iPad4,8</string>
<string>iPad6,12</string>
<string>iPad7,1</string>
<string>iPad5,4</string>
<string>iPad4,9</string>
<string>iPad4,2</string>
<string>iPad7,2</string>
<string>iPad4,3</string>
<string>iPad6,7</string>
<string>iPad7,3</string>
<string>iPad4,4</string>
<string>iPad6,8</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPhone10,1</string>
<string>iPhone8,1</string>
<string>iPhone9,3</string>
<string>iPhone6,2</string>
<string>iPhone9,1</string>
<string>iPhone7,2</string>
<string>iPod7,1</string>
<string>iPhone10,4</string>
<string>iPhone8,4</string>
<string>iPhone6,1</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPad2,3</string>
<string>iPad2,2</string>
<string>iPad2,1</string>
<string>iPad2,7</string>
<string>iPad2,6</string>
<string>iPad2,5</string>
<string>iPad2,4</string>
</array>
</dict>
</array>
</dict>
</array>
</dict>
</plist>
第3步
index.html 内容
需要替换 a
标签的 href
属性,用户点击 a 标签后将弹出下载确认框
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello news</title>
<meta name="author" content="dcloud">
<meta name="description" content="hello news">
<meta name="keywords" content="hello news,dcloud">
<meta name="viewport" content="width=device-width,user-scalable=no">
</head>
<body>
<!-- https://static-xxx.bspapp.com/download/manifest.plist 替换为你的前端网页托管中的 manifest.plist 文件目录 -->
<a href="itms-services://?action=download-manifest&url=https://static-xxx.bspapp.com/download/manifest.plist">下载 iOS hello news</a>
</body>
</html>
第4步
使用手机浏览器访问你的前端网页托管地址,示例: https://static-xxx.bspapp.com/
uniCloud 是 DCloud 联合阿里云、腾讯云,为开发者提供的基于 serverless 模式和 js 编程的云开发平台。
uniCloud
的 web控制台地址:https://unicloud.dcloud.net.cn
Apple Developer Enterprise Program
可让大型组织开发内部专属的 app 并向其员工部署。此计划仅适用于需要使用安全的内部系统或经由移动设备管理解决方案,向员工私密地直接分发的特例情况。详情:https://developer.apple.com/cn/programs/enterprise/
原 IEP
(iOS Developer Enterprise Program),已统一为 Apple Developer Enterprise Program
步骤如下
第1步
开通uniCloud,详情 https://unicloud.dcloud.net.cn/
第2步
在 uniCloud web控制台左侧栏 "前端网页托管" 上传5个文件,目录结构如下
根目录 >
| app_name_v3.0.0.ipa
| manifest.plist
| index.html
| icon_58.png
| icon_512.png
文件目录介绍
app_name_v3.0.0.ipa 是使用`IEP`证书的应用安装包
manifest.plist 是应用的描述文件
index.html 是用户安装应用页面
icon_58.png 是应用显示的图标,分辨率 58x58,名字可自定义
icon_512.png 是应用的最大图标,分辨率 512x512,名字可自定义
manifest.plist
配置
- 新建文件 manifest.plist 文件并粘贴下面
新闻模板
的 manifest.plist 配置,.plist 是扩展名 - 替换 manifest.plist 文件内容为你的应用信息
- 在 manifest.plist 搜索应用名称
新闻模板
- 在 manifest.plist 搜索应用下载地址
https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa
- 在 manifest.plist 搜索icon_58.png图标
https://static-xxxx.bspapp.com/download/icon_58.png
- 在 manifest.plist 搜索icon_512.png图标
https://static-xxxx.bspapp.com/download/icon_512.png
- 在 manifest.plist 搜索应用名称
注意:
https://static-xxxx.bspapp.com/
是前端网页托管的默认域名,腾讯和阿里的不同,可绑定自己的域名manifest.plist
可配置不同设备的.ipa,例如 iPad, iPhone,示例中共用了一个.ipa- 安装包是有有效期的,过期后需要更新证书重新打包
新闻模板
的 manifest.plist
配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
</dict>
<dict>
<key>kind</key>
<string>display-image</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/icon_58.png</string>
</dict>
<dict>
<key>kind</key>
<string>full-size-image</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/icon_512.png</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>io.dcloud.new.template</string>
<key>bundle-version</key>
<string>2.4.3</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>新闻模板</string>
</dict>
<key>thinned-assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPhone6,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone6,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone7,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone8,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone8,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPod7,1</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array/>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPhone8,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone11,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone11,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone11,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,3</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array>
<string>iPhone11,6</string>
<string>iPhone11,2</string>
<string>iPhone11,4</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPad5,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,7</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,9</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad5,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad5,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,7</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,7</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,8</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,12</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,8</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad5,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,11</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,8</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,2</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array>
<string>iPad8,1</string>
<string>iPad8,8</string>
<string>iPad8,7</string>
<string>iPad8,6</string>
<string>iPad8,5</string>
<string>iPad8,4</string>
<string>iPad8,3</string>
<string>iPad8,2</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPad3,6</string>
<string>iPad3,4</string>
<string>iPad3,2</string>
<string>iPad3,5</string>
<string>iPad3,3</string>
<string>iPad3,1</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPhone11,8</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array>
<string>iPhone11,8</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPhone10,2</string>
<string>iPhone7,1</string>
<string>iPhone10,6</string>
<string>iPhone10,5</string>
<string>iPhone8,2</string>
<string>iPhone9,4</string>
<string>iPhone10,3</string>
<string>iPhone9,2</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPhone5,2</string>
<string>iPod5,1</string>
<string>iPhone5,3</string>
<string>iPhone4,1</string>
<string>iPhone5,1</string>
<string>iPhone5,4</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPad7,4</string>
<string>iPad4,5</string>
<string>iPad7,5</string>
<string>iPad4,6</string>
<string>iPad6,3</string>
<string>iPad5,1</string>
<string>iPad6,11</string>
<string>iPad7,6</string>
<string>iPad5,2</string>
<string>iPad6,4</string>
<string>iPad4,7</string>
<string>iPad5,3</string>
<string>iPad4,1</string>
<string>iPad4,8</string>
<string>iPad6,12</string>
<string>iPad7,1</string>
<string>iPad5,4</string>
<string>iPad4,9</string>
<string>iPad4,2</string>
<string>iPad7,2</string>
<string>iPad4,3</string>
<string>iPad6,7</string>
<string>iPad7,3</string>
<string>iPad4,4</string>
<string>iPad6,8</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPhone10,1</string>
<string>iPhone8,1</string>
<string>iPhone9,3</string>
<string>iPhone6,2</string>
<string>iPhone9,1</string>
<string>iPhone7,2</string>
<string>iPod7,1</string>
<string>iPhone10,4</string>
<string>iPhone8,4</string>
<string>iPhone6,1</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPad2,3</string>
<string>iPad2,2</string>
<string>iPad2,1</string>
<string>iPad2,7</string>
<string>iPad2,6</string>
<string>iPad2,5</string>
<string>iPad2,4</string>
</array>
</dict>
</array>
</dict>
</array>
</dict>
</plist>
第3步
index.html 内容
需要替换 a
标签的 href
属性,用户点击 a 标签后将弹出下载确认框
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello news</title>
<meta name="author" content="dcloud">
<meta name="description" content="hello news">
<meta name="keywords" content="hello news,dcloud">
<meta name="viewport" content="width=device-width,user-scalable=no">
</head>
<body>
<!-- https://static-xxx.bspapp.com/download/manifest.plist 替换为你的前端网页托管中的 manifest.plist 文件目录 -->
<a href="itms-services://?action=download-manifest&url=https://static-xxx.bspapp.com/download/manifest.plist">下载 iOS hello news</a>
</body>
</html>
第4步
使用手机浏览器访问你的前端网页托管地址,示例: https://static-xxx.bspapp.com/

万岳在线教育系统UNI-APP开源版
如果您是初学者,uni-app如何用于开发一定是一个颇为头疼且无从下手的问题。如果能有一个开源项目作为参考,一定能让您的学习事半功倍。我们万岳科技的在线教育系统UNI-APP开源版就是通过uni-app开发的开源系统的前端部分,uni-app的各种核心技术都有应用。有它一定能让您的学习轻松不少。
- <a target="_blank" href="http://doc.sdwanyue.com/wanyue_open_uniapp/2100689">部署文档</a>
系统演示
Web版地址
- 首页地址: <a target="_blank" href="https://demo.sdwanyue.com">https://demo.sdwanyue.com</a>
- 学生端地址: <a target="_blank" href="https://demo.sdwanyue.com">https://demo.sdwanyue.com</a> 账号:13800000000 密码:123456
- 教师端地址: <a target="_blank" href="https://demo.sdwanyue.com/teacher">https://demo.sdwanyue.com/teacher</a> 账号:13866666666 密码:123456
- 后台地址: <a target="_blank" href="https://demo.sdwanyue.com/admin">https://demo.sdwanyue.com/admin</a> 账号: demo 密码: 123456
- 仓库地址: <a target="_blank" href="https://gitee.com/WanYueKeJi/wanyue_education_web">点击此处</a>
项目介绍
万岳在线教育经过对教育市场的长期调研,综合当下各大线上教培机构对于教育平台的功能需求,着重于用户体验,自主研发了一套集知识付费、直播授课、网校建设等功能为一体的万岳在线教育系统,满足用户对于公开课、大班课、小班课、职业培训等多种线上教学活动的场景需求。
- 所有使用到的框架或者组件都是基于开源项目,代码保证100%开源。
- 系统功能通用,无论是个人还是企业都可以利用该系统快速搭建一个属于自己的在线教育平台。
系统前端采用uni-app+socket.io+WebRtc核心技术, 接口采用PhalApi框架配合TP5.1框架ThinkCMF,系统功能如下:
技术亮点
UNI-APP端:
1.一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序
2.依托Dcloud公司强大的生态圈, 开发者无论是拿来直接用还是自己修改后使用都
十分的方便, 网上资料/文档齐全,无需担心bug解决不了.
3.支持视频直播、聊天等即时通讯功能, 开源项目有很多,可是带聊天室和直播的寥寥可数.
4.本项目已内置好socket.io组件和声网WebRtc插件, 开发者可直接拿来调试学习.
无需再从基本的websocket写起.
5.项目占用空间小,全部加起来不到2MB,占用开发者磁盘空间极少.
6.代码中做了多端适配, 小程序端、H5端、安卓、IOS端样式都做到了样式兼容.
7.vue/nvue混合开发, 保证了样式美观的同时, 规避了nvue样式的兼容问题.
8.配置方便, 无需安装, 下载之后使用Hbuilder编辑器即可运行查看.
后端:
1.后台应用ThinkCMF快速生成现代化表单.
2.PHPExcel数据导出,导出表格更加美观,可视.
3.支持微信/支付宝支付,支付接入更加快捷,简单.
4.后台多任务窗口化操作界面.
5.内置强大灵活的权限管理.
6.内置组合数据,系统配置,管理碎片化数据.
7.客户端完善的交互效果和动画.
8.高频数据缓存.
9.内置PhalApi接口框架,前后端分离更方便.
10.WebSocket长连接减少CPU及内存使用及网络堵塞,减少请求响应时长.
11.支持队列降低流量高峰,解除耦合,高可用.
12.无需安装, clone下来即可直接使用, 完全100%真开源.
注:关于PC端源码请看本公司另一个开源项目, 万岳在线教育系统Web版,
此两个项目是互通的
功能展示
主要功能介绍
讲师PC端
-
登录页面,可通过手机号码及验证码或密码进行登录,支持qq、微信帐号登录
-
首页功能,展示老师头像、名称,我的直播课数量,我的课程数量,常用功能以及常见问题
-
直播课堂,
1.语音大班课
语音大班课内,讲师可进入课程根据视频和语音对学生进行讲解2.视频大班课
视频大班课讲师可以进行开启摄像头、上传ppt图片课件、屏幕共享操作 -
账号,展示头像、昵称等账号信息,可编辑头像、昵称
学生UNI-APP端
-
首页,
1.搜索 可根据课程、老师名称关键字查询搜索
2.轮播图 后台添加的轮播图展示
3.直播课堂 直播课展示列表, 直播分为三种类型(语音+视频、语音+音频、语音+ppt+摄像头直播)
4.精选内容 内容展示列表,包含封面、标题、内容类型(视频、音频、图文)
-
精选内容,
1.详情页 由上往下依次展示封面图、课程标题,学习人数、主讲老师与辅导老师、课程类型(分为密码、免费、付费三种类型)等
2.查看详情 上部为视频、音频播放区域,视频、音频支持进度条滑动查看,视频可全屏观看,播放区域下方为内容标题、简介、发布时间等信息和详细内容展示(支持图文)
-
直播课堂,
1.语音大班课
2.视频大班课
-
后台主要功能介绍
-
设置
1.网站信息 网站的相关信息及SEO设置
2.私密设置 配置系统的重要参数(包登录配置、直播配置、云存储设置)
3.幻灯片管理 首页轮播banner图管理配置
4.上传设置 上传信息相关配置
5.文件存储 文件存储信息相关配置
-
用户管理
1.管理组 编辑后台管理员身份及权限等相关配置
2.用户列表 平台所有注册用户信息列表,可进行编辑、设置讲师资格、禁用、删除等操作
3.教师列表 后台将普通用户设置为讲师的用户显示在此处
-
插件中心
1.插件列表 查看编辑应用插件信息
-
内容管理
1.文章管理 网站相关文章配置
2.分类管理 文章分类管理
3.页面管理 联系我们、关于我们等单页内容配置
-
订单管理
1.展示用户购买课程后展示的会员名称、购买价格、订单号、需要线下邮寄教材的收货信息与支付方式与状态
-
知识管理
1.学级分类 登录页的学习阶段分类,可在后台自行添加
2.付费内容列表 展示添加的所有需要付费/免费的文章内容,可查看平台所有内容信息。 -
大班课列表
1.语音大班课
2.视频大班课
页面展示
直播课页面
功能对比
开源版使用须知
-
允许用于个人学习、教学案例
-
开源版不适合商用,商用请购买商业版
-
禁止将本项目的代码和资源进行任何形式的出售,产生的一切任何后果责任由侵权者自负
商业合作
- 如果你想使用功能更完善的教育系统,请联系QQ客服: 2415408120获取专业版
- 如果您想基于教育系统进行定制开发,我们提供有偿定制服务支持!
- 其他合作模式不限,欢迎来撩!
- 官网地址:http://www.sdwanyue.com
如果您是初学者,uni-app如何用于开发一定是一个颇为头疼且无从下手的问题。如果能有一个开源项目作为参考,一定能让您的学习事半功倍。我们万岳科技的在线教育系统UNI-APP开源版就是通过uni-app开发的开源系统的前端部分,uni-app的各种核心技术都有应用。有它一定能让您的学习轻松不少。
- <a target="_blank" href="http://doc.sdwanyue.com/wanyue_open_uniapp/2100689">部署文档</a>
系统演示
Web版地址
- 首页地址: <a target="_blank" href="https://demo.sdwanyue.com">https://demo.sdwanyue.com</a>
- 学生端地址: <a target="_blank" href="https://demo.sdwanyue.com">https://demo.sdwanyue.com</a> 账号:13800000000 密码:123456
- 教师端地址: <a target="_blank" href="https://demo.sdwanyue.com/teacher">https://demo.sdwanyue.com/teacher</a> 账号:13866666666 密码:123456
- 后台地址: <a target="_blank" href="https://demo.sdwanyue.com/admin">https://demo.sdwanyue.com/admin</a> 账号: demo 密码: 123456
- 仓库地址: <a target="_blank" href="https://gitee.com/WanYueKeJi/wanyue_education_web">点击此处</a>
项目介绍
万岳在线教育经过对教育市场的长期调研,综合当下各大线上教培机构对于教育平台的功能需求,着重于用户体验,自主研发了一套集知识付费、直播授课、网校建设等功能为一体的万岳在线教育系统,满足用户对于公开课、大班课、小班课、职业培训等多种线上教学活动的场景需求。
- 所有使用到的框架或者组件都是基于开源项目,代码保证100%开源。
- 系统功能通用,无论是个人还是企业都可以利用该系统快速搭建一个属于自己的在线教育平台。
系统前端采用uni-app+socket.io+WebRtc核心技术, 接口采用PhalApi框架配合TP5.1框架ThinkCMF,系统功能如下:
技术亮点
UNI-APP端:
1.一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序
2.依托Dcloud公司强大的生态圈, 开发者无论是拿来直接用还是自己修改后使用都
十分的方便, 网上资料/文档齐全,无需担心bug解决不了.
3.支持视频直播、聊天等即时通讯功能, 开源项目有很多,可是带聊天室和直播的寥寥可数.
4.本项目已内置好socket.io组件和声网WebRtc插件, 开发者可直接拿来调试学习.
无需再从基本的websocket写起.
5.项目占用空间小,全部加起来不到2MB,占用开发者磁盘空间极少.
6.代码中做了多端适配, 小程序端、H5端、安卓、IOS端样式都做到了样式兼容.
7.vue/nvue混合开发, 保证了样式美观的同时, 规避了nvue样式的兼容问题.
8.配置方便, 无需安装, 下载之后使用Hbuilder编辑器即可运行查看.
后端:
1.后台应用ThinkCMF快速生成现代化表单.
2.PHPExcel数据导出,导出表格更加美观,可视.
3.支持微信/支付宝支付,支付接入更加快捷,简单.
4.后台多任务窗口化操作界面.
5.内置强大灵活的权限管理.
6.内置组合数据,系统配置,管理碎片化数据.
7.客户端完善的交互效果和动画.
8.高频数据缓存.
9.内置PhalApi接口框架,前后端分离更方便.
10.WebSocket长连接减少CPU及内存使用及网络堵塞,减少请求响应时长.
11.支持队列降低流量高峰,解除耦合,高可用.
12.无需安装, clone下来即可直接使用, 完全100%真开源.
注:关于PC端源码请看本公司另一个开源项目, 万岳在线教育系统Web版,
此两个项目是互通的
功能展示
主要功能介绍
讲师PC端
-
登录页面,可通过手机号码及验证码或密码进行登录,支持qq、微信帐号登录
-
首页功能,展示老师头像、名称,我的直播课数量,我的课程数量,常用功能以及常见问题
-
直播课堂,
1.语音大班课
语音大班课内,讲师可进入课程根据视频和语音对学生进行讲解2.视频大班课
视频大班课讲师可以进行开启摄像头、上传ppt图片课件、屏幕共享操作 -
账号,展示头像、昵称等账号信息,可编辑头像、昵称
学生UNI-APP端
-
首页,
1.搜索 可根据课程、老师名称关键字查询搜索
2.轮播图 后台添加的轮播图展示
3.直播课堂 直播课展示列表, 直播分为三种类型(语音+视频、语音+音频、语音+ppt+摄像头直播)
4.精选内容 内容展示列表,包含封面、标题、内容类型(视频、音频、图文)
-
精选内容,
1.详情页 由上往下依次展示封面图、课程标题,学习人数、主讲老师与辅导老师、课程类型(分为密码、免费、付费三种类型)等
2.查看详情 上部为视频、音频播放区域,视频、音频支持进度条滑动查看,视频可全屏观看,播放区域下方为内容标题、简介、发布时间等信息和详细内容展示(支持图文)
-
直播课堂,
1.语音大班课
2.视频大班课
-
后台主要功能介绍
-
设置
1.网站信息 网站的相关信息及SEO设置
2.私密设置 配置系统的重要参数(包登录配置、直播配置、云存储设置)
3.幻灯片管理 首页轮播banner图管理配置
4.上传设置 上传信息相关配置
5.文件存储 文件存储信息相关配置
-
用户管理
1.管理组 编辑后台管理员身份及权限等相关配置
2.用户列表 平台所有注册用户信息列表,可进行编辑、设置讲师资格、禁用、删除等操作
3.教师列表 后台将普通用户设置为讲师的用户显示在此处
-
插件中心
1.插件列表 查看编辑应用插件信息
-
内容管理
1.文章管理 网站相关文章配置
2.分类管理 文章分类管理
3.页面管理 联系我们、关于我们等单页内容配置
-
订单管理
1.展示用户购买课程后展示的会员名称、购买价格、订单号、需要线下邮寄教材的收货信息与支付方式与状态
-
知识管理
1.学级分类 登录页的学习阶段分类,可在后台自行添加
2.付费内容列表 展示添加的所有需要付费/免费的文章内容,可查看平台所有内容信息。 -
大班课列表
1.语音大班课
2.视频大班课
页面展示
直播课页面
功能对比
开源版使用须知
-
允许用于个人学习、教学案例
-
开源版不适合商用,商用请购买商业版
-
禁止将本项目的代码和资源进行任何形式的出售,产生的一切任何后果责任由侵权者自负
商业合作
- 如果你想使用功能更完善的教育系统,请联系QQ客服: 2415408120获取专业版
- 如果您想基于教育系统进行定制开发,我们提供有偿定制服务支持!
- 其他合作模式不限,欢迎来撩!
- 官网地址:http://www.sdwanyue.com

可获取MD5签名的JDK版本 分享
目前部分 应用服务(比如一键登录)需要提供 android应用的MD5证书签名
但是最新版本的JDK 已经不支持MD5了,仅支持sha1,sha256两种签名。
分享一下可以获取MD5签名的旧版本JDK
链接:https://pan.baidu.com/s/1U4SWoYY6Gn4B4PjSJwy9Gg
提取码:tm85
感谢 热心开发者 @759416651@qq.com 的分享
目前部分 应用服务(比如一键登录)需要提供 android应用的MD5证书签名
但是最新版本的JDK 已经不支持MD5了,仅支持sha1,sha256两种签名。
分享一下可以获取MD5签名的旧版本JDK
链接:https://pan.baidu.com/s/1U4SWoYY6Gn4B4PjSJwy9Gg
提取码:tm85
感谢 热心开发者 @759416651@qq.com 的分享
收起阅读 »
关于解决live-pusher只推麦克风
在uniapp中有这么一个问题是 enable-camera 在并不管用。
会出现会出现如下错误
别着急,你可以参考以下代码试一下会发现查看视频流的时候没有画面只有声音。
export default {
data() {
return {
url: 'rtmp://92926.livepush.myqcloud.com/live/123',
enableCamera: false,
context: null
};
},
onReady() {
this.context = uni.createLivePusherContext('livePusher', this);
},
methods: {
EnableCamera() {
this.enableCamera = false;
},
startLive() {
this.context.start({
success: a => {
console.log('livePusher.start:' JSON.stringify(a));
}
});
},
stopLive() {
this.context.stop({
success: a => {
console.log(JSON.stringify(a));
}
});
}
}
};
但是到了这一步你以为就完成了吗?不!你会发现还是跟之前一样申请了摄像头跟麦克风。而我的需求是只需要推麦克风;
别着急这也能实现。看图
如图所示只需加上enable-camera="false"就能解决了!!!
如果你有其它问题可联系QQ1437448122一起交流
在uniapp中有这么一个问题是 enable-camera 在并不管用。
会出现会出现如下错误
别着急,你可以参考以下代码试一下会发现查看视频流的时候没有画面只有声音。
export default {
data() {
return {
url: 'rtmp://92926.livepush.myqcloud.com/live/123',
enableCamera: false,
context: null
};
},
onReady() {
this.context = uni.createLivePusherContext('livePusher', this);
},
methods: {
EnableCamera() {
this.enableCamera = false;
},
startLive() {
this.context.start({
success: a => {
console.log('livePusher.start:' JSON.stringify(a));
}
});
},
stopLive() {
this.context.stop({
success: a => {
console.log(JSON.stringify(a));
}
});
}
}
};
但是到了这一步你以为就完成了吗?不!你会发现还是跟之前一样申请了摄像头跟麦克风。而我的需求是只需要推麦克风;
别着急这也能实现。看图
如图所示只需加上enable-camera="false"就能解决了!!!
如果你有其它问题可联系QQ1437448122一起交流
收起阅读 »
mui ajax 封装成Promise <仅供参考> 可以优雅的使用 async/await
import mui from '@/assets/mui/js/mui'
const { ajax } = mui
function promiseAjax (url, type, params, options = {}) {
return new Promise((res, rej) => {
try {
ajax(url, {
data: params,
dataType: options.dataType || 'json',//服务器返回json格式数据
type: type,//HTTP请求类型
timeout: options.timeout || 10000,//超时时间设置为10秒;
headers: options.headers || { 'Content-Type': 'application/json' },
success: function (data) {
res(data)
},
error: function (xhr, type, errorThrown) {
rej(errorThrown)
},
})
} catch (e) {
rej(e)
}
})
}
export default {
get (url, params, options) {
return promiseAjax(url, 'get', params, options)
},
post (url, params, options) {
return promiseAjax(url, 'post', params, options)
},
}
import mui from '@/assets/mui/js/mui'
const { ajax } = mui
function promiseAjax (url, type, params, options = {}) {
return new Promise((res, rej) => {
try {
ajax(url, {
data: params,
dataType: options.dataType || 'json',//服务器返回json格式数据
type: type,//HTTP请求类型
timeout: options.timeout || 10000,//超时时间设置为10秒;
headers: options.headers || { 'Content-Type': 'application/json' },
success: function (data) {
res(data)
},
error: function (xhr, type, errorThrown) {
rej(errorThrown)
},
})
} catch (e) {
rej(e)
}
})
}
export default {
get (url, params, options) {
return promiseAjax(url, 'get', params, options)
},
post (url, params, options) {
return promiseAjax(url, 'post', params, options)
},
}
收起阅读 »