uniapp对微信小程序异步加载分包组件的做法--vite插件方案
微信原生文档见 文档
社区有方案,是在style中写入
这里的方案是使用vite插件,在编译完成后,对目标插件进行代码注入 (仅适用vue3 + vite) , 目前运行在uniapp 微信小程序上
目录结构:
/project-config
componentPlaceholder
componentPlaceholder.js
vite.uni-component-placeholder.js
vite插件代码
/**
实现了 uni编译完成后 , 处理 componentPlaceholder
*/
var fs = require('fs');
var path = require('path');
class ProcessComponentPlaceholder{
constructor(){
this.destFolder = process.env.UNI_OUTPUT_DIR;
}
process(){
let dev_fold = process.env.UNI_INPUT_DIR;
let dir = dev_fold+"/project-config/componentPlaceHolder/"
let files = fs.readdirSync(dir, 'utf-8');
let fileMap = {}
files.some((f) => {
let p = dir + "/" + f;
let stat = fs.lstatSync(p);
if (!stat.isDirectory()) {
fileMap[f] = p;
}
})
this.processNodes(fileMap)
}
processNodes(fileMap){
console.log("处理异步组件引用componentPlaceHolder",fileMap)
for(let jsonFileName in fileMap){
let path = fileMap[jsonFileName];
if(!path.lastIndexOf(".js")<0)continue;
let obj = require(path);
// console.log("读componentPlaceHolder", obj)
this.processOneConfig(obj);
}
console.table("componentPlaceHolder处理完毕")
}
processOneConfig(config){
for(let f in config){ //某个配置文件
let weixinJSONFile = this.destFolder+f+".json";
fs.readFile(weixinJSONFile,'utf8',(err, data)=>{
if (err) {
return console.log('componentPlaceHolder文件读取失败,失败原因是:' + err)
}
let weixinJSON = JSON.parse(data);
// console.log("读componentPlaceHolder",weixinJSON);
//准备合并配置
let usingComponents = weixinJSON["usingComponents"]||{};
let componentPlaceholder = weixinJSON["componentPlaceholder"]||{};
let customConfig = config[f];
for(let tag in customConfig) {
let tagVal = customConfig[tag];
let path = tagVal.path;
let replace = tagVal.replace;
// console.log(weixinJSONFile+ " " +tag+" "+path )
if(!usingComponents[tag]){
usingComponents[tag]="../.."+path; //这里的双层目录有必要可能动态算相对层级,根据项目自身情况而定
}
if(!componentPlaceholder[tag]){
componentPlaceholder[tag] = replace;
}
}
weixinJSON.usingComponents = usingComponents;
weixinJSON.componentPlaceholder = componentPlaceholder;
fs.writeFileSync(weixinJSONFile, JSON.stringify(weixinJSON,null,4))
}) ;
}
}
}
export default (options)=> {
var name = 'vite-plugin-copy-uniapp_config';
return {
name: name,
enforce: 'post',
closeBundle:()=>{ //buildEnd之后运行
options.forEach(function(option) {
let processor = new ProcessComponentPlaceholder();
processor.process();
});
}
};
}
配置文件 componentPlaceholder.js 代码:
/**
配置:
{
"某个包的组件路径,不带.vue后缀":{
"组件名,一般为文件名不带.vue和路径":{
path:"引用某个包的组件路径,不带.vue后缀",
replace:"未加载完成时的替换组件,比如view或某个全局组件"
}
}
}
*/
module.exports = {
"/pages/index/index":{
"tabbar-me":{
path:"/package-my/pages/my/my",
replace:"view"
},
},
}
vite.config.js
import viteComponentPlaceHolder from "./project-config/vite.uni-component-placeholder.js"
plugins.push(viteComponentPlaceHolder([{}]))
export default defineConfig({
plugins
});
启动时会有日志
08:48:43.602 处理异步组件引用componentPlaceHolder {
08:48:43.609 'componentPlaceholder.js': '/project-config/componentPlaceHolder//componentPlaceholder.js'
08:48:43.610 }
微信原生文档见 文档
社区有方案,是在style中写入
这里的方案是使用vite插件,在编译完成后,对目标插件进行代码注入 (仅适用vue3 + vite) , 目前运行在uniapp 微信小程序上
目录结构:
/project-config
componentPlaceholder
componentPlaceholder.js
vite.uni-component-placeholder.js
vite插件代码
/**
实现了 uni编译完成后 , 处理 componentPlaceholder
*/
var fs = require('fs');
var path = require('path');
class ProcessComponentPlaceholder{
constructor(){
this.destFolder = process.env.UNI_OUTPUT_DIR;
}
process(){
let dev_fold = process.env.UNI_INPUT_DIR;
let dir = dev_fold+"/project-config/componentPlaceHolder/"
let files = fs.readdirSync(dir, 'utf-8');
let fileMap = {}
files.some((f) => {
let p = dir + "/" + f;
let stat = fs.lstatSync(p);
if (!stat.isDirectory()) {
fileMap[f] = p;
}
})
this.processNodes(fileMap)
}
processNodes(fileMap){
console.log("处理异步组件引用componentPlaceHolder",fileMap)
for(let jsonFileName in fileMap){
let path = fileMap[jsonFileName];
if(!path.lastIndexOf(".js")<0)continue;
let obj = require(path);
// console.log("读componentPlaceHolder", obj)
this.processOneConfig(obj);
}
console.table("componentPlaceHolder处理完毕")
}
processOneConfig(config){
for(let f in config){ //某个配置文件
let weixinJSONFile = this.destFolder+f+".json";
fs.readFile(weixinJSONFile,'utf8',(err, data)=>{
if (err) {
return console.log('componentPlaceHolder文件读取失败,失败原因是:' + err)
}
let weixinJSON = JSON.parse(data);
// console.log("读componentPlaceHolder",weixinJSON);
//准备合并配置
let usingComponents = weixinJSON["usingComponents"]||{};
let componentPlaceholder = weixinJSON["componentPlaceholder"]||{};
let customConfig = config[f];
for(let tag in customConfig) {
let tagVal = customConfig[tag];
let path = tagVal.path;
let replace = tagVal.replace;
// console.log(weixinJSONFile+ " " +tag+" "+path )
if(!usingComponents[tag]){
usingComponents[tag]="../.."+path; //这里的双层目录有必要可能动态算相对层级,根据项目自身情况而定
}
if(!componentPlaceholder[tag]){
componentPlaceholder[tag] = replace;
}
}
weixinJSON.usingComponents = usingComponents;
weixinJSON.componentPlaceholder = componentPlaceholder;
fs.writeFileSync(weixinJSONFile, JSON.stringify(weixinJSON,null,4))
}) ;
}
}
}
export default (options)=> {
var name = 'vite-plugin-copy-uniapp_config';
return {
name: name,
enforce: 'post',
closeBundle:()=>{ //buildEnd之后运行
options.forEach(function(option) {
let processor = new ProcessComponentPlaceholder();
processor.process();
});
}
};
}
配置文件 componentPlaceholder.js 代码:
/**
配置:
{
"某个包的组件路径,不带.vue后缀":{
"组件名,一般为文件名不带.vue和路径":{
path:"引用某个包的组件路径,不带.vue后缀",
replace:"未加载完成时的替换组件,比如view或某个全局组件"
}
}
}
*/
module.exports = {
"/pages/index/index":{
"tabbar-me":{
path:"/package-my/pages/my/my",
replace:"view"
},
},
}
vite.config.js
import viteComponentPlaceHolder from "./project-config/vite.uni-component-placeholder.js"
plugins.push(viteComponentPlaceHolder([{}]))
export default defineConfig({
plugins
});
启动时会有日志
08:48:43.602 处理异步组件引用componentPlaceHolder {
08:48:43.609 'componentPlaceholder.js': '/project-config/componentPlaceHolder//componentPlaceholder.js'
08:48:43.610 }
七分钟掌握 uni-app自定义基座,离线打包的生成方法
// what's uapp
const uapp = 'universal app'
uapp 是一款跨平台APP开发工具箱,所有积累都来自多年产品开发中的不断实践。开发者仅需写一套代码,就能横扫所有平台。
制作了一个七分钟视频,包含了android,ios 手机平台上,离线打包,自定义基座的制作方法,视频首秀,喜欢的小伙伴帮忙点赞支持
// what's uapp
const uapp = 'universal app'
uapp 是一款跨平台APP开发工具箱,所有积累都来自多年产品开发中的不断实践。开发者仅需写一套代码,就能横扫所有平台。
制作了一个七分钟视频,包含了android,ios 手机平台上,离线打包,自定义基座的制作方法,视频首秀,喜欢的小伙伴帮忙点赞支持
https://www.ixigua.com/7294197408232276495
收起阅读 »uni-app 开发中,监听 input 键盘事件获取不到按下按键值怎么办?
uniapp 开发 H5 时,无法监听按钮键盘事件的原因以及解决方法。
问题描述:
不少 uni-app 开发者在使用 input 组件时,监听 keyup 事件时,获取不到键盘的 keyCode。编写的代码如下:
<template>
<input @keyup="handleKeyUp">
</template>
但是在 handleKeyUp() 方法里获取不到键盘的编码,出现这个问题的原因是 uni-app 的内置组件 <input> 其实是封装过的,编译为 h5 时不是 html 原生的 input 元素,所以才无法监听原生的键盘事件。
解决方法参考这个:uni-app input 键盘事件获取按键值
uniapp 开发 H5 时,无法监听按钮键盘事件的原因以及解决方法。
问题描述:
不少 uni-app 开发者在使用 input 组件时,监听 keyup 事件时,获取不到键盘的 keyCode。编写的代码如下:
<template>
<input @keyup="handleKeyUp">
</template>
但是在 handleKeyUp() 方法里获取不到键盘的编码,出现这个问题的原因是 uni-app 的内置组件 <input> 其实是封装过的,编译为 h5 时不是 html 原生的 input 元素,所以才无法监听原生的键盘事件。
解决方法参考这个:uni-app input 键盘事件获取按键值
收起阅读 »uni-app通过SSE支持流式效果
uni-app支持SSE
因为uni.request没有办法支持SSE,为此尝试了各种方案,着了大急。
现将尝试的各种方案进行一个整理,若有疏漏请大家帮忙补充。
H5
因为Web端运行在浏览器内核上,SSE的支持是比较完备的,可以使用axios、@microsoft/fetch-event-source 等实现,各种案例也比较完善因此不再赘述。
微信小程序
微信小程序的SSE方案参考的是《微信小程序除了WebSocket其他思路实现流传输文字(打字机)效果》
因为我们是在uniapp中实现,所以在原文方案的基础上使用的uni相关的API来实现,考虑到要实现停止和兼容H5的的接口,最后引入了abort-controller 和 @uni-helper/uni-network来进行封装。
import type { UnCancelTokenListener, UnGenericAbortSignal, UnHeaders } from '@uni-helper/uni-network'
/**
* 二进制解析成文本
* @param data 二进制数据
* @returns 文本
*/
export function decodeArrayBuffer(data: ArrayBuffer | undefined) {
if (!data) {
return ''
}
return decodeUsingURIComponent(data)
}
/**
* URIComponent解码二进制流(不用引入额外包)
* @param data 二进制流
* @returns 文本
*/
function decodeUsingURIComponent(data: ArrayBuffer) {
const uint8Array = new Uint8Array(data)
let text = String.fromCharCode(...uint8Array)
try {
text = decodeURIComponent(escape(text))
} catch (e) {
console.error('decodeUsingURIComponent: Can not decodeURI ', text)
}
return text
}
type onStreamReceivedListener = (text: string) => void
export function fetchStreamChat(
params: { prompt: string; uuid: string },
signal?: UnGenericAbortSignal,
listener?: onStreamReceivedListener
) {
const onHeadersReceived = (response?: { headers?: UnHeaders }) => {
console.log('fetchStreamChat.onHeadersReceived: ', response?.headers)
}
const onChunkReceived = (response?: { data?: ArrayBuffer }) => {
const text = decodeArrayBuffer(response?.data)
listener?.(text)
}
return post<string>({
url: '/openai/completions/stream',
headers: {
Accept: 'text/event-stream',
'Content-Type': 'application/json',
token: 'your-token'
},
data: {
content: params.prompt,
scene: params.uuid,
source: 'gpt4',
},
responseType: 'arraybuffer',
enableChunked: true,
onHeadersReceived,
onChunkReceived,
signal: signal
})
}
特殊注意以上代码使用时对abort-controller的引入方式
import AbortController from 'abort-controller/dist/abort-controller'
let controller = new AbortController()
const onResponseListener = async (responseText: string) => {
console.log('==response==\n', responseText)
}
await fetchStreamChat({ prompt, uuid }, controller.signal, onResponseListener)
后端NGINX配置
# 注意这里只配置代理发送接口,不然其他接口也会受影响
location /openai/completions/stream {
# ...more config
proxy_set_header Transfer-Encoding "";
chunked_transfer_encoding on;
proxy_buffering off;
}
APP
App目前看到的方案最多,但是目前为止没有找到很合适的方案。有更好的方案请各位大佬补充Thanks♪(・ω・)ノ
1. plus.net.XMLHttpRequest
参考方案 《XMLHttpRequest模块管理网络请求》,具体代码如下
import type { UnCancelTokenListener, UnGenericAbortSignal, UnHeaders } from '@uni-helper/uni-network'
type onStreamReceivedListener = (text: string) => void
export class CanceledError extends Error {
constructor(message?: string) {
super(message ?? 'canceled')
}
}
export function fetchStreamChatForApp(
params: { prompt: string; uuid: string },
signal?: UnGenericAbortSignal,
listener?: onStreamReceivedListener
) {
return new Promise((resolve, reject) => {
// 梳理好请求数据
const token = 'your-token'
const data = JSON.stringify({
content: params.prompt,
scene: params.uuid,
source: 'gpt3.5',
})
// 处理资源释放
let onCanceled: UnCancelTokenListener
const done = () => {
signal?.removeEventListener?.('abort', onCanceled)
}
// 封装请求
// @ts-ignore
let xhr: plus.net.XMLHttpRequest | undefined
// @ts-ignore
xhr = new plus.net.XMLHttpRequest()
xhr.withCredentials = true
// 配置终止逻辑
if (signal) {
signal.addEventListener?.('abort', () => {
console.log('fetchStreamChatForApp signal abort')
xhr.abort()
})
}
let nLastIndex = 0
xhr.onreadystatechange = function () {
console.log(`onreadystatechange(${xhr.readyState}) → `)
if (xhr.readyState === 4) {
if (nLastIndex < xhr.responseText.length) {
const responseText = xhr.responseText as string
// 处理 HTTP 数据块
if (responseText) {
const textLen = responseText.length
const chunk = responseText.substring(nLastIndex)
nLastIndex = textLen
listener?.(chunk)
}
}
if (xhr.status === 200) {
resolve({ code: ResultCode.SUCCESS, msg: 'end' })
done()
} else {
reject(new Error(xhr.statusText))
done()
}
}
}
xhr.onprogress = function (event: any) {
const responseText = xhr.responseText
if (responseText) {
const textLen = responseText.length
const chunk = responseText.substring(nLastIndex)
nLastIndex = textLen
listener?.(chunk)
console.log('onprogress ', chunk)
}
}
xhr.onerror = function (error: any) {
console.error('Network Error:', error)
reject(error)
done()
}
// 配置请求
xhr.open('POST', 'https://your-site/api/openai/completions/stream')
xhr.setRequestHeader('Accept', 'text/event-stream')
xhr.setRequestHeader('token', token)
xhr.setRequestHeader('User-Agent', 'Mobile')
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.setRequestHeader('Host', 'mapi.lawvector.cn')
xhr.setRequestHeader('Connection', 'keep-alive')
// 处理终止逻辑
if (signal) {
onCanceled = cancel => {
console.log('fetchStreamChatForApp onCanceled ', cancel)
if (!xhr) {
return
}
reject(new CanceledError('canceled'))
xhr.abort()
xhr = undefined
}
// @ts-expect-error no types
signal?.aborted ? onCanceled() : signal?.addEventListener('abort', onCanceled)
}
xhr.send(data)
})
}
当前方案经验证,可以从流式接口获取到数据,但是流式效果不太好,而且从网上汇总来的信息来看,plus.net存在较多问题,比如《plus.net.XMLHttpRequest()在苹果端移动网络环境下不能使用》等。因此 不推荐 plus.net方案
2. event-source-polyfill
参考方案《OpenAI流式请求实现方案》、《react + ts + event-source-polyfill 实现方案》、《Vue中使用eventSource处理ChatGPT聊天SSE长连接获取数据》
实际App上运行发现报错 TypeError: XMLHttpRequest is not a constructor
哪位大佬可以解决上述问题请补充,不胜感激!
3. fetch-event-source
参考方案《js调用SSE客户端》、《fetch-event-source源码解析》、ChatGPT-SSE流式响应
经过验证发现单独引入@microsoft/fetch-event-source 会抛出异常
从ChatGPT-SSE流式响应分析应该是需要结合renderjs进行使用。
目前推荐使用该方案~
4. App原生语言插件
参考《EventSource (sse)等自定义网络请求 》
因为该插件目前仅支持Android,不推荐。
理论上,原生插件是一定能够解决这个问题,期待大佬们开发更完善的原生插件。
uni-app支持SSE
因为uni.request没有办法支持SSE,为此尝试了各种方案,着了大急。
现将尝试的各种方案进行一个整理,若有疏漏请大家帮忙补充。
H5
因为Web端运行在浏览器内核上,SSE的支持是比较完备的,可以使用axios、@microsoft/fetch-event-source 等实现,各种案例也比较完善因此不再赘述。
微信小程序
微信小程序的SSE方案参考的是《微信小程序除了WebSocket其他思路实现流传输文字(打字机)效果》
因为我们是在uniapp中实现,所以在原文方案的基础上使用的uni相关的API来实现,考虑到要实现停止和兼容H5的的接口,最后引入了abort-controller 和 @uni-helper/uni-network来进行封装。
import type { UnCancelTokenListener, UnGenericAbortSignal, UnHeaders } from '@uni-helper/uni-network'
/**
* 二进制解析成文本
* @param data 二进制数据
* @returns 文本
*/
export function decodeArrayBuffer(data: ArrayBuffer | undefined) {
if (!data) {
return ''
}
return decodeUsingURIComponent(data)
}
/**
* URIComponent解码二进制流(不用引入额外包)
* @param data 二进制流
* @returns 文本
*/
function decodeUsingURIComponent(data: ArrayBuffer) {
const uint8Array = new Uint8Array(data)
let text = String.fromCharCode(...uint8Array)
try {
text = decodeURIComponent(escape(text))
} catch (e) {
console.error('decodeUsingURIComponent: Can not decodeURI ', text)
}
return text
}
type onStreamReceivedListener = (text: string) => void
export function fetchStreamChat(
params: { prompt: string; uuid: string },
signal?: UnGenericAbortSignal,
listener?: onStreamReceivedListener
) {
const onHeadersReceived = (response?: { headers?: UnHeaders }) => {
console.log('fetchStreamChat.onHeadersReceived: ', response?.headers)
}
const onChunkReceived = (response?: { data?: ArrayBuffer }) => {
const text = decodeArrayBuffer(response?.data)
listener?.(text)
}
return post<string>({
url: '/openai/completions/stream',
headers: {
Accept: 'text/event-stream',
'Content-Type': 'application/json',
token: 'your-token'
},
data: {
content: params.prompt,
scene: params.uuid,
source: 'gpt4',
},
responseType: 'arraybuffer',
enableChunked: true,
onHeadersReceived,
onChunkReceived,
signal: signal
})
}
特殊注意以上代码使用时对abort-controller的引入方式
import AbortController from 'abort-controller/dist/abort-controller'
let controller = new AbortController()
const onResponseListener = async (responseText: string) => {
console.log('==response==\n', responseText)
}
await fetchStreamChat({ prompt, uuid }, controller.signal, onResponseListener)
后端NGINX配置
# 注意这里只配置代理发送接口,不然其他接口也会受影响
location /openai/completions/stream {
# ...more config
proxy_set_header Transfer-Encoding "";
chunked_transfer_encoding on;
proxy_buffering off;
}
APP
App目前看到的方案最多,但是目前为止没有找到很合适的方案。有更好的方案请各位大佬补充Thanks♪(・ω・)ノ
1. plus.net.XMLHttpRequest
参考方案 《XMLHttpRequest模块管理网络请求》,具体代码如下
import type { UnCancelTokenListener, UnGenericAbortSignal, UnHeaders } from '@uni-helper/uni-network'
type onStreamReceivedListener = (text: string) => void
export class CanceledError extends Error {
constructor(message?: string) {
super(message ?? 'canceled')
}
}
export function fetchStreamChatForApp(
params: { prompt: string; uuid: string },
signal?: UnGenericAbortSignal,
listener?: onStreamReceivedListener
) {
return new Promise((resolve, reject) => {
// 梳理好请求数据
const token = 'your-token'
const data = JSON.stringify({
content: params.prompt,
scene: params.uuid,
source: 'gpt3.5',
})
// 处理资源释放
let onCanceled: UnCancelTokenListener
const done = () => {
signal?.removeEventListener?.('abort', onCanceled)
}
// 封装请求
// @ts-ignore
let xhr: plus.net.XMLHttpRequest | undefined
// @ts-ignore
xhr = new plus.net.XMLHttpRequest()
xhr.withCredentials = true
// 配置终止逻辑
if (signal) {
signal.addEventListener?.('abort', () => {
console.log('fetchStreamChatForApp signal abort')
xhr.abort()
})
}
let nLastIndex = 0
xhr.onreadystatechange = function () {
console.log(`onreadystatechange(${xhr.readyState}) → `)
if (xhr.readyState === 4) {
if (nLastIndex < xhr.responseText.length) {
const responseText = xhr.responseText as string
// 处理 HTTP 数据块
if (responseText) {
const textLen = responseText.length
const chunk = responseText.substring(nLastIndex)
nLastIndex = textLen
listener?.(chunk)
}
}
if (xhr.status === 200) {
resolve({ code: ResultCode.SUCCESS, msg: 'end' })
done()
} else {
reject(new Error(xhr.statusText))
done()
}
}
}
xhr.onprogress = function (event: any) {
const responseText = xhr.responseText
if (responseText) {
const textLen = responseText.length
const chunk = responseText.substring(nLastIndex)
nLastIndex = textLen
listener?.(chunk)
console.log('onprogress ', chunk)
}
}
xhr.onerror = function (error: any) {
console.error('Network Error:', error)
reject(error)
done()
}
// 配置请求
xhr.open('POST', 'https://your-site/api/openai/completions/stream')
xhr.setRequestHeader('Accept', 'text/event-stream')
xhr.setRequestHeader('token', token)
xhr.setRequestHeader('User-Agent', 'Mobile')
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.setRequestHeader('Host', 'mapi.lawvector.cn')
xhr.setRequestHeader('Connection', 'keep-alive')
// 处理终止逻辑
if (signal) {
onCanceled = cancel => {
console.log('fetchStreamChatForApp onCanceled ', cancel)
if (!xhr) {
return
}
reject(new CanceledError('canceled'))
xhr.abort()
xhr = undefined
}
// @ts-expect-error no types
signal?.aborted ? onCanceled() : signal?.addEventListener('abort', onCanceled)
}
xhr.send(data)
})
}
当前方案经验证,可以从流式接口获取到数据,但是流式效果不太好,而且从网上汇总来的信息来看,plus.net存在较多问题,比如《plus.net.XMLHttpRequest()在苹果端移动网络环境下不能使用》等。因此 不推荐 plus.net方案
2. event-source-polyfill
参考方案《OpenAI流式请求实现方案》、《react + ts + event-source-polyfill 实现方案》、《Vue中使用eventSource处理ChatGPT聊天SSE长连接获取数据》
实际App上运行发现报错 TypeError: XMLHttpRequest is not a constructor
哪位大佬可以解决上述问题请补充,不胜感激!
3. fetch-event-source
参考方案《js调用SSE客户端》、《fetch-event-source源码解析》、ChatGPT-SSE流式响应
经过验证发现单独引入@microsoft/fetch-event-source 会抛出异常
从ChatGPT-SSE流式响应分析应该是需要结合renderjs进行使用。
目前推荐使用该方案~
4. App原生语言插件
参考《EventSource (sse)等自定义网络请求 》
因为该插件目前仅支持Android,不推荐。
理论上,原生插件是一定能够解决这个问题,期待大佬们开发更完善的原生插件。
还在手动上传小程序? uni-mini-ci帮你一键发布多平台uni-app小程序
背景
在没有CI
工具帮助的时候,我们使用uni-app
开发小程序通常会在小程序发版时先进行 build 构建,然后在小程序开发者工具中打开构建产物,然后进行上传代码的操作。这种人力运维的稳定性是不可控的,而且当我们一次要发布多个平台或者多个小程序时,这种人力运维的工作方式将浪费我们大量的时间。
为了解决这个问题,微信推出了miniprogram-ci,开发者可不打开小程序开发者工具,独立使用 miniprogram-ci 进行小程序代码的上传、预览等操作,随后支付宝、钉钉等其他小程序厂商也跟着推出了各自的CI
工具,这一举措将开发者从繁琐的上传流程中解放了出来。
uni-mini-ci是一款支持微信、钉钉、支付宝、企业微信等小程序平台的持续集成工具。它集成了多个平台的ci工具一次配置发布到多端,让开发者可以轻松地将应用程序发布到多个小程序平台上。
为什么不直接使用各自平台的CI
工具?
- 多个小程序平台
CI
工具的配置文件、配置项等有所差异,其能力也有所不同, uni-app
存在构建的步骤,使用各自小程序平台的CI
工具则需要在构建产物中各自创建配置文件。- 钉钉小程序的DingTalk Design CLI,不支持指定版本号,而
uni-mini-ci
支持(至于为什么支持,可以去看一下uni-mini-ci的文档)。
所以使用uni-mini-ci
可以让多平台的小程序持续集成统一化。
在哪些场景使用?
- Github Actions 小程序持续集成
- Jenkins 小程序持续集成
- GitLab CI/CD 小程序持续集成
- 本地上传多个小程序平台
借助Jenkins
等工具可以将开发者从构建、上传小程序的工作中,彻底解放出来,只需动动手指,即可实现小程序的上传。
示例与实践
Jenkins示例
这里是一个简单的Jenkins示例,其中关于.minicirc
的配置可见uni-mini-ci,示例中的变量都可以在jenkins中定义。
这里jenkins环境要全局安装
uni-mini-ci
## 安装依赖 npm install
检查配置文件是否存在
if [ -f .minicirc ]; then
如果文件存在,删除它
rm .minicirc
fi
将配置写入配置文件
echo '{
"dd": {
"appid": "'$miniAppId'",
"token": "'$token'",
"projectPath": "dist/build/mp-alipay",
"autoincrement": '$autoincrement'
},
"weixin": {
"appid": "'$appid'",
"privateKeyPath": "build/ci_keys/private.'$appid'.key",
"projectPath": "dist/build/mp-weixin",
"setting": {
"minifyJS": true,
"minifyWXML": true,
"minifyWXSS": true,
"minify": true
},
"version": "'$version'",
"desc": "'$desc'"
}' > .minicirc
构建钉钉并上传
npm run build:mp-dingtalk
minici --platform dd
构建微信并上传
npm run build:mp-weixin
minici --platform weixin
### Github Actions 示例
Github Actions与jenkins的实现基本一致,不过Github Actions是使用`.yml`作为配置文件的。
```yaml
name: Upload To Weixin Alipay
on:
push:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
upload:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Dependencies
run: |
npm i uni-mini-ci -g
yarn install
- shell: bash
env:
PRIVATE_KEY: ${{ secrets.MP_PRIVATE_KEY }}
APPID: ${{ secrets.MP_APPID }}
TOOLID: ${{ secrets.ALI_TOOL_ID }}
ALI_APPID: ${{ secrets.ALI_APPID }}
ALI_PRIVATE_KEY: ${{ secrets.ALI_PRIVATE_KEY }}
run: |
echo "$PRIVATE_KEY" > private.key
echo '{
"alipay": {
"appid": "'$ALI_APPID'",
"toolId":"'$TOOLID'",
"privateKey": "'$ALI_PRIVATE_KEY'",
"projectPath": "dist/build/mp-alipay",
"autoincrement":true
},
"weixin": {
"appid": "'$APPID'",
"privateKeyPath": "private.key",
"projectPath": "dist/build/mp-weixin",
"setting": {
"minifyJS": true,
"minifyWXML": true,
"minifyWXSS": true,
"minify": true
}
},
"version": "",
"desc": ""
}' > .minicirc
fi
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: "14.x"
- name: upload
run: |
npm run build:mp-alipay
minici --platform alipay
# 构建微信并上传
npm run build:mp-weixin
minici --platform weixin
总结
通过使用uni-mini-ci,我们简化了使用uni-app开发的小程序的发布流程。借助Github Actions、Jenkins、GitLab CI/CD等工具的能力,我们可以实现小程序的自动化构建和部署,提高开发效率,使开发者能够更专注于业务逻辑的开发。
相关文章
背景
在没有CI
工具帮助的时候,我们使用uni-app
开发小程序通常会在小程序发版时先进行 build 构建,然后在小程序开发者工具中打开构建产物,然后进行上传代码的操作。这种人力运维的稳定性是不可控的,而且当我们一次要发布多个平台或者多个小程序时,这种人力运维的工作方式将浪费我们大量的时间。
为了解决这个问题,微信推出了miniprogram-ci,开发者可不打开小程序开发者工具,独立使用 miniprogram-ci 进行小程序代码的上传、预览等操作,随后支付宝、钉钉等其他小程序厂商也跟着推出了各自的CI
工具,这一举措将开发者从繁琐的上传流程中解放了出来。
uni-mini-ci是一款支持微信、钉钉、支付宝、企业微信等小程序平台的持续集成工具。它集成了多个平台的ci工具一次配置发布到多端,让开发者可以轻松地将应用程序发布到多个小程序平台上。
为什么不直接使用各自平台的CI
工具?
- 多个小程序平台
CI
工具的配置文件、配置项等有所差异,其能力也有所不同, uni-app
存在构建的步骤,使用各自小程序平台的CI
工具则需要在构建产物中各自创建配置文件。- 钉钉小程序的DingTalk Design CLI,不支持指定版本号,而
uni-mini-ci
支持(至于为什么支持,可以去看一下uni-mini-ci的文档)。
所以使用uni-mini-ci
可以让多平台的小程序持续集成统一化。
在哪些场景使用?
- Github Actions 小程序持续集成
- Jenkins 小程序持续集成
- GitLab CI/CD 小程序持续集成
- 本地上传多个小程序平台
借助Jenkins
等工具可以将开发者从构建、上传小程序的工作中,彻底解放出来,只需动动手指,即可实现小程序的上传。
示例与实践
Jenkins示例
这里是一个简单的Jenkins示例,其中关于.minicirc
的配置可见uni-mini-ci,示例中的变量都可以在jenkins中定义。
这里jenkins环境要全局安装
uni-mini-ci
## 安装依赖 npm install
检查配置文件是否存在
if [ -f .minicirc ]; then
如果文件存在,删除它
rm .minicirc
fi
将配置写入配置文件
echo '{
"dd": {
"appid": "'$miniAppId'",
"token": "'$token'",
"projectPath": "dist/build/mp-alipay",
"autoincrement": '$autoincrement'
},
"weixin": {
"appid": "'$appid'",
"privateKeyPath": "build/ci_keys/private.'$appid'.key",
"projectPath": "dist/build/mp-weixin",
"setting": {
"minifyJS": true,
"minifyWXML": true,
"minifyWXSS": true,
"minify": true
},
"version": "'$version'",
"desc": "'$desc'"
}' > .minicirc
构建钉钉并上传
npm run build:mp-dingtalk
minici --platform dd
构建微信并上传
npm run build:mp-weixin
minici --platform weixin
### Github Actions 示例
Github Actions与jenkins的实现基本一致,不过Github Actions是使用`.yml`作为配置文件的。
```yaml
name: Upload To Weixin Alipay
on:
push:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
upload:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Dependencies
run: |
npm i uni-mini-ci -g
yarn install
- shell: bash
env:
PRIVATE_KEY: ${{ secrets.MP_PRIVATE_KEY }}
APPID: ${{ secrets.MP_APPID }}
TOOLID: ${{ secrets.ALI_TOOL_ID }}
ALI_APPID: ${{ secrets.ALI_APPID }}
ALI_PRIVATE_KEY: ${{ secrets.ALI_PRIVATE_KEY }}
run: |
echo "$PRIVATE_KEY" > private.key
echo '{
"alipay": {
"appid": "'$ALI_APPID'",
"toolId":"'$TOOLID'",
"privateKey": "'$ALI_PRIVATE_KEY'",
"projectPath": "dist/build/mp-alipay",
"autoincrement":true
},
"weixin": {
"appid": "'$APPID'",
"privateKeyPath": "private.key",
"projectPath": "dist/build/mp-weixin",
"setting": {
"minifyJS": true,
"minifyWXML": true,
"minifyWXSS": true,
"minify": true
}
},
"version": "",
"desc": ""
}' > .minicirc
fi
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: "14.x"
- name: upload
run: |
npm run build:mp-alipay
minici --platform alipay
# 构建微信并上传
npm run build:mp-weixin
minici --platform weixin
总结
通过使用uni-mini-ci,我们简化了使用uni-app开发的小程序的发布流程。借助Github Actions、Jenkins、GitLab CI/CD等工具的能力,我们可以实现小程序的自动化构建和部署,提高开发效率,使开发者能够更专注于业务逻辑的开发。
相关文章
支持多平台小程序的uni-app持续集成工具 - 掘金 (juejin.cn)
收起阅读 »uni.openLocation和VUE3有兼容问题
VUE3 使用 uni.openLocation 浏览器不会打开新页面。会在原页面调用地图组件遮盖,这样使用浏览器自带返回按钮导致路由变化,页面始终被地图组件遮盖。(地图组件无法被浏览器返回事件关闭)
VUE3 使用 uni.openLocation 浏览器不会打开新页面。会在原页面调用地图组件遮盖,这样使用浏览器自带返回按钮导致路由变化,页面始终被地图组件遮盖。(地图组件无法被浏览器返回事件关闭)
【解决】您的应用在运行时,未见向用户告知权限申请的目的,向用户索取(相机)等权限,不符合华为应用市场审核标准。
简约解决方式:
uni.showModal({
title: "温馨提示",
content: "将申请相机权限,用于扫码功能",
success: (res) => {
if (res.confirm) {
// 唤起相册、相机、扫码、地理位置之类的api,如:uni.scanCode
}
}
})
更好的解决办法(实现如下图功能):可能有bug,仅供参考安卓权限说明蒙层
↓↓↓ 各位大佬点点赞
2023年DCloud插件大赛获奖名单
获奖名单
本次插件大赛收到大量优质插件,尤其是uni-app x的相关生态大幅完善,常用插件基本都有了。
获奖名单公布如下:
奖项 | 分类 | 获奖作者 | 获奖插件 |
---|---|---|---|
特等奖 | uni-app x+uniCloud | lieft | 快亿商城,uni-app x(uvue+uts)和uniCloud云端一体完整原生商城项目源码 |
奖项 | 分类 | 获奖作者 | 获奖插件 |
---|---|---|---|
一等奖 | uni-app x | 同名自定义 | TMUI4.0 tmx-ui UVUE组件及UTS插件库 |
一等奖 | uni-app x | UxFrame | UxFrame 低代码高性能UI框架 |
一等奖 | uniCloud | 禾店科技 | 禾店短剧 |
奖项 | 分类 | 获奖作者 | 获奖插件 |
---|---|---|---|
二等奖 | uni-app x | FirstUI | FirstUI-unix |
二等奖 | UTS插件 | kux | kux-mlkit-scancode谷歌扫码 |
二等奖 | uni-app x | 康爱公社 | kux-request 简洁高效的uts 请求库 |
二等奖 | UTS插件 | 网易云信 | 【官方】网易云信RTC音视频通话点对点呼叫组件(uni-app x) |
二等奖 | UTS插件 | 865***@qq.com | 高德地图点标记导航离线地图-原生 |
二等奖 | UTS插件 | 码农朱哲 | svg组件 |
二等奖 | uni-app x | 陌上华年 | qrcode 二维码生成 |
二等奖 | uniCloud | 小猿敲代码 | 汇玩App(完整项目可直接上线,含试玩游戏,试玩应用,uniad结合变现的APP网赚平台) |
奖项 | 分类 | 获奖作者 | 获奖插件 |
---|---|---|---|
三等奖 | HBuilderX | 智谱AI | CodeGeeX: AI Code AutoComplete, Chat, Auto Comment |
三等奖 | HBuilderX | 猫猫猫猫 | GitHub Copilot |
三等奖 | uni-app x | abc***@163.com | t-uvue-ui 首个基于UNI-APP X开发的前端UI框架 |
三等奖 | uni-app x | GraceUI | uXui 一款基于 uni-app x 的、免费、开源的 UI 框架 |
三等奖 | uni-app x | 229***@qq.com | 考试答题模板(appx) |
三等奖 | uni-app x | 陌上年华 | 手写板-签名签字-lime-signature |
三等奖 | uni-app x | 陌上年华 | watermark 防盗水印 |
三等奖 | UTS插件 | JKX | 高德地图组件 |
三等奖 | UTS插件 | UxFrame | UxFrame 微信SDK 微信支付 微信登录 微信分享 微信企业客服 |
三等奖 | UTS插件 | UxFrame | UxFrame 实时音视频通话直播SDK 支持语音通话 视频通话 语音直播 视频直播 录屏直播 |
三等奖 | UTS插件 | 小飞007 | 腾讯直播推流 |
三等奖 | UTS插件 | 小飞007 | UTS实现操作SQLite加密数据库 |
三等奖 | UTS插件 | 珊瑚 | Android端的AES、MD5、RSA、SHA、SM2、SM3、SM4加密解密 |
三等奖 | UTS插件 | 木杉丶 | 原生安卓Android ble低功耗蓝牙插件 |
三等奖 | UTS插件 | 康爱公社 | kux-dayjs |
三等奖 | UTS插件 | 码农朱哲 | android无须权限,选择文件 |
三等奖 | UTS插件 | 865***@qq.com | 腾讯x5webview支持离线内核 |
三等奖 | UTS插件 | 狼人残风001 | 悬浮窗小窗口画中画自定义画中画界面 |
三等奖 | uniCloud | monkeyFree | 【用户端】【多行业适用】多端配置自定义官网 |
三等奖 | uniCloud | 一幅画 | 星之链社区-unicloud版 |
因很多插件值得推荐,所以增加了贡献奖的名额。
奖项设置
特等奖:
奖品:2万元插件包销 + 8888元现金红包 + 插件市场置顶推荐半个月 + HBuilderX预置 + HBuilderX超大鼠标垫 + DCloud奖牌
名额:1名
一等奖:
奖品:2万元插件包销 + 1部Apple设备(mac mini 或 ipad 2选1) + 1000元现金红包+插件市场置顶推荐半个月 + HBuilderX预置 + HBuilderX超大鼠标垫 + DCloud奖牌
名额:3名
mac mini配置为M2芯片、16G内存、256G硬盘。
ipad配置为10.9寸,256G。
其中1部ipad由支付宝提供,定向给予支持unicloud支付宝小程序云版的unicloud插件作者。
二等奖
奖品:2000元插件包销 + 极客外设1台 (HHKB 机械键盘 或 小米 34寸曲面显示器 2选1) + 500元现金红包 + 插件市场置顶推荐1个星期 + HBuilderX超大鼠标垫 + DCloud奖牌
名额:8名
三等奖
奖品:(200元uniCloud代金券 或 护眼套餐 2选1) + HBuilderX超大鼠标垫 + DCloud奖牌
名额:20名
护眼套餐包括2件奖品整包发送:
贡献奖
奖品:HBuilderX超大鼠标垫
名额:50名
注:红包需缴纳个人所得税或通过云账户发放。
“插件包销”,是指获奖插件通过插件市场销售,DCloud兜底包销。以1等奖的2万元包销为例,如果获奖插件在插件市场1年内销售额没有达到2万元,则由DCloud付差额给获奖者进行兜底。包销只针对付费插件,如免费插件获得二等奖及以上奖励,其中的包销奖励无效。
“插件包销”即不虚、也不遥远,去年插件大赛获包销奖励的插件,均提前完成包销。其价值就是实实在在的人民币。
“HBuilderX预置”,是在HBuilderX新建项目界面,可直接选择该项目模板。这为插件带来大量的流量。不适合预置的插件类型,无法领取此奖项。
除上述奖品外,
- 三等奖及以上获奖插件作者,都将进入DCloud VIP技术支持群,享受优先的技术支付、问题反馈。
- 所有获奖插件的集锦页面,还将通过HBuilderX工具、论坛、QQ微信群进行全量推广,给予优秀插件充分的曝光。
HBuilderX预置窗体界面如下:
奖牌照片如下:
历史信息
-
参赛起止时间:从23年10月24日起,到24年3月31日止。
-
鼓励的插件范围
-
uni-app x 前端插件,如ui库。现有插件可参考uvue组件
-
uts插件,扩展原生能力。现有插件可参考uts插件。除了插件市场的插件,uni-app x的源码仓库,也有大量插件源码可参考,因为uni-app x的很多组件和api就是用uts开发的,api仓库详见,组件仓库详见
-
uniCloud插件,优秀的云端一体插件。现有插件可参考云端一体项目模板
-
有助于开发者广告变现的运营类插件
-
HBuilder插件
奖品领取
请各位获奖作者尽快提交自己的邮寄地址,我们会陆续联系获奖人员发放奖品;
邮寄地址提交方式:登录ask社区,点击右上角个人头像,进入设置
界面,设置界面下方补充快递邮寄地址。
所有2选1
的奖品,插件作者可以发邮件到 service@dcloud.io,说明社区账号、获奖插件及希望选择的奖品。
已获奖的插件作者请继续升级迭代插件;
未获奖的今年还有机会,官方会继续为建设更好的uni-app x生态推出其他计划。不管是为了下次大赛获奖,还是为了把握uni-app x的新浪潮,或者在插件市场通过售卖插件变现,都是值得期待的好事。
获奖名单
本次插件大赛收到大量优质插件,尤其是uni-app x的相关生态大幅完善,常用插件基本都有了。
获奖名单公布如下:
奖项 | 分类 | 获奖作者 | 获奖插件 |
---|---|---|---|
特等奖 | uni-app x+uniCloud | lieft | 快亿商城,uni-app x(uvue+uts)和uniCloud云端一体完整原生商城项目源码 |
奖项 | 分类 | 获奖作者 | 获奖插件 |
---|---|---|---|
一等奖 | uni-app x | 同名自定义 | TMUI4.0 tmx-ui UVUE组件及UTS插件库 |
一等奖 | uni-app x | UxFrame | UxFrame 低代码高性能UI框架 |
一等奖 | uniCloud | 禾店科技 | 禾店短剧 |
奖项 | 分类 | 获奖作者 | 获奖插件 |
---|---|---|---|
二等奖 | uni-app x | FirstUI | FirstUI-unix |
二等奖 | UTS插件 | kux | kux-mlkit-scancode谷歌扫码 |
二等奖 | uni-app x | 康爱公社 | kux-request 简洁高效的uts 请求库 |
二等奖 | UTS插件 | 网易云信 | 【官方】网易云信RTC音视频通话点对点呼叫组件(uni-app x) |
二等奖 | UTS插件 | 865***@qq.com | 高德地图点标记导航离线地图-原生 |
二等奖 | UTS插件 | 码农朱哲 | svg组件 |
二等奖 | uni-app x | 陌上华年 | qrcode 二维码生成 |
二等奖 | uniCloud | 小猿敲代码 | 汇玩App(完整项目可直接上线,含试玩游戏,试玩应用,uniad结合变现的APP网赚平台) |
奖项 | 分类 | 获奖作者 | 获奖插件 |
---|---|---|---|
三等奖 | HBuilderX | 智谱AI | CodeGeeX: AI Code AutoComplete, Chat, Auto Comment |
三等奖 | HBuilderX | 猫猫猫猫 | GitHub Copilot |
三等奖 | uni-app x | abc***@163.com | t-uvue-ui 首个基于UNI-APP X开发的前端UI框架 |
三等奖 | uni-app x | GraceUI | uXui 一款基于 uni-app x 的、免费、开源的 UI 框架 |
三等奖 | uni-app x | 229***@qq.com | 考试答题模板(appx) |
三等奖 | uni-app x | 陌上年华 | 手写板-签名签字-lime-signature |
三等奖 | uni-app x | 陌上年华 | watermark 防盗水印 |
三等奖 | UTS插件 | JKX | 高德地图组件 |
三等奖 | UTS插件 | UxFrame | UxFrame 微信SDK 微信支付 微信登录 微信分享 微信企业客服 |
三等奖 | UTS插件 | UxFrame | UxFrame 实时音视频通话直播SDK 支持语音通话 视频通话 语音直播 视频直播 录屏直播 |
三等奖 | UTS插件 | 小飞007 | 腾讯直播推流 |
三等奖 | UTS插件 | 小飞007 | UTS实现操作SQLite加密数据库 |
三等奖 | UTS插件 | 珊瑚 | Android端的AES、MD5、RSA、SHA、SM2、SM3、SM4加密解密 |
三等奖 | UTS插件 | 木杉丶 | 原生安卓Android ble低功耗蓝牙插件 |
三等奖 | UTS插件 | 康爱公社 | kux-dayjs |
三等奖 | UTS插件 | 码农朱哲 | android无须权限,选择文件 |
三等奖 | UTS插件 | 865***@qq.com | 腾讯x5webview支持离线内核 |
三等奖 | UTS插件 | 狼人残风001 | 悬浮窗小窗口画中画自定义画中画界面 |
三等奖 | uniCloud | monkeyFree | 【用户端】【多行业适用】多端配置自定义官网 |
三等奖 | uniCloud | 一幅画 | 星之链社区-unicloud版 |
因很多插件值得推荐,所以增加了贡献奖的名额。
奖项设置
特等奖:
奖品:2万元插件包销 + 8888元现金红包 + 插件市场置顶推荐半个月 + HBuilderX预置 + HBuilderX超大鼠标垫 + DCloud奖牌
名额:1名
一等奖:
奖品:2万元插件包销 + 1部Apple设备(mac mini 或 ipad 2选1) + 1000元现金红包+插件市场置顶推荐半个月 + HBuilderX预置 + HBuilderX超大鼠标垫 + DCloud奖牌
名额:3名
mac mini配置为M2芯片、16G内存、256G硬盘。
ipad配置为10.9寸,256G。
其中1部ipad由支付宝提供,定向给予支持unicloud支付宝小程序云版的unicloud插件作者。
二等奖
奖品:2000元插件包销 + 极客外设1台 (HHKB 机械键盘 或 小米 34寸曲面显示器 2选1) + 500元现金红包 + 插件市场置顶推荐1个星期 + HBuilderX超大鼠标垫 + DCloud奖牌
名额:8名
三等奖
奖品:(200元uniCloud代金券 或 护眼套餐 2选1) + HBuilderX超大鼠标垫 + DCloud奖牌
名额:20名
护眼套餐包括2件奖品整包发送:
贡献奖
奖品:HBuilderX超大鼠标垫
名额:50名
注:红包需缴纳个人所得税或通过云账户发放。
“插件包销”,是指获奖插件通过插件市场销售,DCloud兜底包销。以1等奖的2万元包销为例,如果获奖插件在插件市场1年内销售额没有达到2万元,则由DCloud付差额给获奖者进行兜底。包销只针对付费插件,如免费插件获得二等奖及以上奖励,其中的包销奖励无效。
“插件包销”即不虚、也不遥远,去年插件大赛获包销奖励的插件,均提前完成包销。其价值就是实实在在的人民币。
“HBuilderX预置”,是在HBuilderX新建项目界面,可直接选择该项目模板。这为插件带来大量的流量。不适合预置的插件类型,无法领取此奖项。
除上述奖品外,
- 三等奖及以上获奖插件作者,都将进入DCloud VIP技术支持群,享受优先的技术支付、问题反馈。
- 所有获奖插件的集锦页面,还将通过HBuilderX工具、论坛、QQ微信群进行全量推广,给予优秀插件充分的曝光。
HBuilderX预置窗体界面如下:
奖牌照片如下:
历史信息
-
参赛起止时间:从23年10月24日起,到24年3月31日止。
-
鼓励的插件范围
-
uni-app x 前端插件,如ui库。现有插件可参考uvue组件
-
uts插件,扩展原生能力。现有插件可参考uts插件。除了插件市场的插件,uni-app x的源码仓库,也有大量插件源码可参考,因为uni-app x的很多组件和api就是用uts开发的,api仓库详见,组件仓库详见
-
uniCloud插件,优秀的云端一体插件。现有插件可参考云端一体项目模板
-
有助于开发者广告变现的运营类插件
-
HBuilder插件
奖品领取
请各位获奖作者尽快提交自己的邮寄地址,我们会陆续联系获奖人员发放奖品;
邮寄地址提交方式:登录ask社区,点击右上角个人头像,进入设置
界面,设置界面下方补充快递邮寄地址。
所有2选1
的奖品,插件作者可以发邮件到 service@dcloud.io,说明社区账号、获奖插件及希望选择的奖品。
已获奖的插件作者请继续升级迭代插件;
未获奖的今年还有机会,官方会继续为建设更好的uni-app x生态推出其他计划。不管是为了下次大赛获奖,还是为了把握uni-app x的新浪潮,或者在插件市场通过售卖插件变现,都是值得期待的好事。
十分钟使用vitepress+github action+gitee pages 搭建你的专属文档
介绍
VitePress 是 VuePress
的精神继承者。最初的 VuePress
基于Vue 2
和webpack
。在VitePress
内部使用了Vue 3
和Vite
,这使得VitePress在开发体验、生产性能、默认主题的精细化和更灵活的自定义API方面提供了显著的改进。本文将介绍使用VitePress
搭建uni-app路由库uni-mini-router的文档,并通过github action
实现自动化部署到github pages
与gitee pages
。
创建项目
安装
关于安装配置的问题,vitepress目前还没有稳定的版本,所以可能会有所变动,推荐还是看一下文档然后进行创建。
mkdir your-project npm init yarn add -D vitepress
安装向导
VitePress 附带一个命令行设置向导,可帮助您搭建基本项目的基架。安装后,通过运行以下命令启动向导:
npx vitepress init
您会看到几个简单的问题:
┌ Welcome to VitePress!
│
◇ Where should VitePress initialize the config?
│ ./docs
│
◇ Site title:
│ uni-mini-router(你的项目名称)
│
◇ Site description:
│ 一个基于vue3+typescript的uni-app路由库(你的项目介绍)
│
◇ Theme:
│ Default Theme
│
◇ Use TypeScript for config and theme files?
│ Yes
│
◇ Add VitePress npm scripts to package.json?
│ Yes
│
└ Done! Now run npm run docs:dev and start writing.
执行完本步骤后,将会向你的package.json
注入以下脚本:
{
...
"scripts": {
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs"
},
...
}
运行
npm run docs:dev
配置
在docs/.vitepress
文件夹中有一个 config.mts 文件,我们可以在这里配置文档项目,配置项参考配置。
import { defineConfig } from 'vitepress'
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "uni-mini-router",
description: "一个基于vue3+typescript的uni-app路由库",
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Home', link: '/' },
{ text: 'Examples', link: '/markdown-examples' }
],
sidebar: [
{
text: 'Examples',
items: [
{ text: 'Markdown Examples', link: '/markdown-examples' },
{ text: 'Runtime API Examples', link: '/api-examples' }
]
}
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
]
}
})
部署到 Github Pages + Gitee Pages
- 修改
config
的base
- 如果要部署到 https://<USERNAME>.github.io/,则可以省略 base,因为它默认为 “/”。
- 如果您正在部署到
https://<USERNAME>.github.io/<REPO>/
,例如,您的存储库位于github.com/<REPO>/
,然后将base
设置为/<REPO>/
// 示例
import { defineConfig } from 'vitepress'
export default defineConfig({
base: "/uni-mini-router/", // 这里为仓库名
title: "uni-mini-router",
})
- 创建 Github Action 部署Github Pages并同步至Gitee Pages
Github Pages
在国内的访问速度并不理想,而Gitee
则没有类似Github Action
的功能且标准版Gitee Pages
不支持自动部署,所以我们通过Github Action 将文档部署Github Pages并同步至Gitee Pages。
在项目根目录下创建.github
文件夹,.github
中创建workflows
文件夹并创建文件deploy.yml
name: Deploy VitePress site to Pages
on:
push:
tags:
- '*'
workflow_dispatch:
jobs:
deploy-and-sync:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: 'master'
- name: Install yarn
run: corepack enable
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'yarn'
- name: Install dependencies
run: yarn install
- name: Build Site
run: npm run docs:build
# 将文档产物提交到gh-pages分支
- name: Deploy for Gitee
uses: JamesIves/github-pages-deploy-action@v4.4.1
with:
branch: gh-pages
folder: docs/.vitepress/dist
# enable single-commit to reduce the repo size
single-commit: true
clean: true
- name: Sync to Gitee
uses: wearerequired/git-mirror-action@v1.2.0
env:
SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }}
with:
# GitHub 仓库地址
source-repo: git@github.com:Moonofweisheng/uni-mini-router.git
# Gitee 仓库地址
destination-repo: git@gitee.com:Moonofweisheng/uni-mini-router.git
- name: Build Gitee Pages
uses: yanglbme/gitee-pages-action@main
with:
# 替换为你的 Gitee 用户名
gitee-username: Moonofweisheng
# 注意在 Settings->Secrets 配置 GITEE_PASSWORD
gitee-password: ${{ secrets.GITEE_PASSWORD }}
# 注意替换为你的 Gitee 仓库,仓库名严格区分大小写,请准确填写,否则会出错
gitee-repo: Moonofweisheng/uni-mini-router
# 要部署的分支,默认是 master,若是其他分支,则需要指定(指定的分支必须存在)
branch: gh-pages
其中GITEE_RSA_PRIVATE_KEY
是私钥,生成步骤如下:
- 在 GitHub 项目的
Settings -> Pages
路径下配置Build and deployment
,source
选Deploy from a branch
,branch
选择gh-pages
,路径选择/root
。 - 在 Gitee 项目的
服务 -> Gitee Pages
中设置部署分支
为gh-pages
,部署目录则不用填。 - 在命令行终端或 Git Bash 使用命令 ssh-keygen 生成 SSH Key,连续三次回车。生成的 id_rsa 是私钥,id_rsa.pub 是公钥。
- 在 GitHub 项目的
Settings -> Secrets
路径下配置好命名为GITEE_RSA_PRIVATE_KEY
密钥。GITEE_RSA_PRIVATE_KEY
存放 id_rsa 私钥。 - 在 GitHub 的个人设置页面SSH and GPG keys 配置 SSH 公钥(即:id_rsa.pub),命名任意。
- 在 Gitee 的个人设置页面SSH 公钥 配置 SSH 公钥(即:id_rsa.pub),命名可任意。
- 在
GitHub
项目的Settings -> Secrets
路径下配置好命名为GITEE_RSA_PRIVATE_KEY
和GITEE_PASSWORD
的两个密钥。其中:GITEE_RSA_PRIVATE_KEY
存放 id_rsa 私钥;GITEE_PASSWORD
存放 Gitee 帐号的密码。 - 在 GitHub 项目的
Settings -> Actions -> General
路径下配置Fork pull request workflows from outside collaborators
为Require approval for first-time contributors who are new to GitHub
,将Workflow permissions
配置为Read and write permissions
。
至此我们就实现了通过Github Action
部署Github Pages
并同步至Gitee Pages
Algolia 搜索
vitepress
自带站内搜索功能有限,所以我推荐使用Algolia搜索,Algolia
提供了一套强大的搜索功能,包括全文搜索、过滤、排序、分页和高亮等。它还支持多语言搜索、拼写纠正和近义词处理,以提供更准确和相关的搜索结果。
申请 Docsearch
首先,我们打开Docsearch的申请地址,填写网站地址、邮箱和仓库地址等信息,这里注意,文档内容和代码要是开源且可以公开访问的。申请过后我们会收到三封邮件,按照指示即可完成申请。
配置文档
在文档的config
中增加themeConfig
,配置search
。
export default defineConfig({
base: "/uni-mini-router/",
title: `Uni Mini Router`,
...
themeConfig: {
search: {
provider: 'algolia',
options: {
appId: '你的appid',
apiKey: '你的APIkey,注意是搜索的key',
indexName: '你的indexName',
},
},
},
...
})
执行爬虫
我们这里使用Algolia
提供的爬虫进行爬取,访问地址,选中我们创建的index,然后点击右上角的【Restart crawling】执行。
当然即使我们不手动执行,Algolia
也会定期对我们的文档进行爬取的。
总结
通过使用VitePress
、GitHub Pages
和Gitee Pages
,以及GitHub Actions
,我们成功搭建了一个专属文档的自动化部署系统。VitePress
作为VuePress
的继任者,提供了更好的开发体验和性能表现。我们使用VitePress
创建了一个uni-app
路由库的文档,并通过GitHub Actions
实现了自动部署到GitHub Pages
和Gitee Pages
。
为了解决GitHub Pages
在国内访问速度不理想的问题,我们使用GitHub Actions
将文档部署到GitHub Pages
,并通过Gitee Pages
实现了同步部署。这样,无论用户在国内还是国外,都可以快速访问到我们的文档。
为了提供更好的搜索功能,我们申请了Algolia
的Docsearch
服务,并在配置文件中添加了搜索功能的相关配置。通过执行Algolia
提供的爬虫,我们可以定期更新文档的搜索索引,以提供准确和相关的搜索结果。
总的来说,通过使用VitePress
、GitHub Pages
和Gitee Pages
,以及GitHub Actions
和Algolia
,我们成功搭建了一个功能强大、美观专业的文档系统,为用户提供了更好的阅读和搜索体验。
链接
介绍
VitePress 是 VuePress
的精神继承者。最初的 VuePress
基于Vue 2
和webpack
。在VitePress
内部使用了Vue 3
和Vite
,这使得VitePress在开发体验、生产性能、默认主题的精细化和更灵活的自定义API方面提供了显著的改进。本文将介绍使用VitePress
搭建uni-app路由库uni-mini-router的文档,并通过github action
实现自动化部署到github pages
与gitee pages
。
创建项目
安装
关于安装配置的问题,vitepress目前还没有稳定的版本,所以可能会有所变动,推荐还是看一下文档然后进行创建。
mkdir your-project npm init yarn add -D vitepress
安装向导
VitePress 附带一个命令行设置向导,可帮助您搭建基本项目的基架。安装后,通过运行以下命令启动向导:
npx vitepress init
您会看到几个简单的问题:
┌ Welcome to VitePress!
│
◇ Where should VitePress initialize the config?
│ ./docs
│
◇ Site title:
│ uni-mini-router(你的项目名称)
│
◇ Site description:
│ 一个基于vue3+typescript的uni-app路由库(你的项目介绍)
│
◇ Theme:
│ Default Theme
│
◇ Use TypeScript for config and theme files?
│ Yes
│
◇ Add VitePress npm scripts to package.json?
│ Yes
│
└ Done! Now run npm run docs:dev and start writing.
执行完本步骤后,将会向你的package.json
注入以下脚本:
{
...
"scripts": {
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs"
},
...
}
运行
npm run docs:dev
配置
在docs/.vitepress
文件夹中有一个 config.mts 文件,我们可以在这里配置文档项目,配置项参考配置。
import { defineConfig } from 'vitepress'
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "uni-mini-router",
description: "一个基于vue3+typescript的uni-app路由库",
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Home', link: '/' },
{ text: 'Examples', link: '/markdown-examples' }
],
sidebar: [
{
text: 'Examples',
items: [
{ text: 'Markdown Examples', link: '/markdown-examples' },
{ text: 'Runtime API Examples', link: '/api-examples' }
]
}
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
]
}
})
部署到 Github Pages + Gitee Pages
- 修改
config
的base
- 如果要部署到 https://<USERNAME>.github.io/,则可以省略 base,因为它默认为 “/”。
- 如果您正在部署到
https://<USERNAME>.github.io/<REPO>/
,例如,您的存储库位于github.com/<REPO>/
,然后将base
设置为/<REPO>/
// 示例
import { defineConfig } from 'vitepress'
export default defineConfig({
base: "/uni-mini-router/", // 这里为仓库名
title: "uni-mini-router",
})
- 创建 Github Action 部署Github Pages并同步至Gitee Pages
Github Pages
在国内的访问速度并不理想,而Gitee
则没有类似Github Action
的功能且标准版Gitee Pages
不支持自动部署,所以我们通过Github Action 将文档部署Github Pages并同步至Gitee Pages。
在项目根目录下创建.github
文件夹,.github
中创建workflows
文件夹并创建文件deploy.yml
name: Deploy VitePress site to Pages
on:
push:
tags:
- '*'
workflow_dispatch:
jobs:
deploy-and-sync:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: 'master'
- name: Install yarn
run: corepack enable
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'yarn'
- name: Install dependencies
run: yarn install
- name: Build Site
run: npm run docs:build
# 将文档产物提交到gh-pages分支
- name: Deploy for Gitee
uses: JamesIves/github-pages-deploy-action@v4.4.1
with:
branch: gh-pages
folder: docs/.vitepress/dist
# enable single-commit to reduce the repo size
single-commit: true
clean: true
- name: Sync to Gitee
uses: wearerequired/git-mirror-action@v1.2.0
env:
SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }}
with:
# GitHub 仓库地址
source-repo: git@github.com:Moonofweisheng/uni-mini-router.git
# Gitee 仓库地址
destination-repo: git@gitee.com:Moonofweisheng/uni-mini-router.git
- name: Build Gitee Pages
uses: yanglbme/gitee-pages-action@main
with:
# 替换为你的 Gitee 用户名
gitee-username: Moonofweisheng
# 注意在 Settings->Secrets 配置 GITEE_PASSWORD
gitee-password: ${{ secrets.GITEE_PASSWORD }}
# 注意替换为你的 Gitee 仓库,仓库名严格区分大小写,请准确填写,否则会出错
gitee-repo: Moonofweisheng/uni-mini-router
# 要部署的分支,默认是 master,若是其他分支,则需要指定(指定的分支必须存在)
branch: gh-pages
其中GITEE_RSA_PRIVATE_KEY
是私钥,生成步骤如下:
- 在 GitHub 项目的
Settings -> Pages
路径下配置Build and deployment
,source
选Deploy from a branch
,branch
选择gh-pages
,路径选择/root
。 - 在 Gitee 项目的
服务 -> Gitee Pages
中设置部署分支
为gh-pages
,部署目录则不用填。 - 在命令行终端或 Git Bash 使用命令 ssh-keygen 生成 SSH Key,连续三次回车。生成的 id_rsa 是私钥,id_rsa.pub 是公钥。
- 在 GitHub 项目的
Settings -> Secrets
路径下配置好命名为GITEE_RSA_PRIVATE_KEY
密钥。GITEE_RSA_PRIVATE_KEY
存放 id_rsa 私钥。 - 在 GitHub 的个人设置页面SSH and GPG keys 配置 SSH 公钥(即:id_rsa.pub),命名任意。
- 在 Gitee 的个人设置页面SSH 公钥 配置 SSH 公钥(即:id_rsa.pub),命名可任意。
- 在
GitHub
项目的Settings -> Secrets
路径下配置好命名为GITEE_RSA_PRIVATE_KEY
和GITEE_PASSWORD
的两个密钥。其中:GITEE_RSA_PRIVATE_KEY
存放 id_rsa 私钥;GITEE_PASSWORD
存放 Gitee 帐号的密码。 - 在 GitHub 项目的
Settings -> Actions -> General
路径下配置Fork pull request workflows from outside collaborators
为Require approval for first-time contributors who are new to GitHub
,将Workflow permissions
配置为Read and write permissions
。
至此我们就实现了通过Github Action
部署Github Pages
并同步至Gitee Pages
Algolia 搜索
vitepress
自带站内搜索功能有限,所以我推荐使用Algolia搜索,Algolia
提供了一套强大的搜索功能,包括全文搜索、过滤、排序、分页和高亮等。它还支持多语言搜索、拼写纠正和近义词处理,以提供更准确和相关的搜索结果。
申请 Docsearch
首先,我们打开Docsearch的申请地址,填写网站地址、邮箱和仓库地址等信息,这里注意,文档内容和代码要是开源且可以公开访问的。申请过后我们会收到三封邮件,按照指示即可完成申请。
配置文档
在文档的config
中增加themeConfig
,配置search
。
export default defineConfig({
base: "/uni-mini-router/",
title: `Uni Mini Router`,
...
themeConfig: {
search: {
provider: 'algolia',
options: {
appId: '你的appid',
apiKey: '你的APIkey,注意是搜索的key',
indexName: '你的indexName',
},
},
},
...
})
执行爬虫
我们这里使用Algolia
提供的爬虫进行爬取,访问地址,选中我们创建的index,然后点击右上角的【Restart crawling】执行。
当然即使我们不手动执行,Algolia
也会定期对我们的文档进行爬取的。
总结
通过使用VitePress
、GitHub Pages
和Gitee Pages
,以及GitHub Actions
,我们成功搭建了一个专属文档的自动化部署系统。VitePress
作为VuePress
的继任者,提供了更好的开发体验和性能表现。我们使用VitePress
创建了一个uni-app
路由库的文档,并通过GitHub Actions
实现了自动部署到GitHub Pages
和Gitee Pages
。
为了解决GitHub Pages
在国内访问速度不理想的问题,我们使用GitHub Actions
将文档部署到GitHub Pages
,并通过Gitee Pages
实现了同步部署。这样,无论用户在国内还是国外,都可以快速访问到我们的文档。
为了提供更好的搜索功能,我们申请了Algolia
的Docsearch
服务,并在配置文件中添加了搜索功能的相关配置。通过执行Algolia
提供的爬虫,我们可以定期更新文档的搜索索引,以提供准确和相关的搜索结果。
总的来说,通过使用VitePress
、GitHub Pages
和Gitee Pages
,以及GitHub Actions
和Algolia
,我们成功搭建了一个功能强大、美观专业的文档系统,为用户提供了更好的阅读和搜索体验。
链接
收起阅读 »APP备案公钥、证书MD5指纹/签名MD5值获取最简单方法
本文只详细讲解android app获取方法,三个平台获取方法(android、windows、macOS):
一. Android手机平台:前提,您的应用已安装到手机;然后,android应用市场搜索下载安装 APP备案助手,此app可直接获取所有已安装app的公钥、证书MD5指纹/签名MD5值,示例:获取 抖音app公钥、MD5等信息,
APP备案助手 获取结果,与下面两种方式获取结果一致;
二. Windows平台,下载jadx-gui反编译工具,github下载地址: https://github.com/skylot/jadx/releases
下载上图这个zip,然后解压后,打开 jadx-gui.exe,
点击 Open file/打开文件,打开apk安装包文件,
点击APK signature, Modulus/模数 为公钥,十进制显示的;MD5 Fingerprint/MD5签名,APP备案填写时需要去掉空格,填写32位长度的十六进制数据。
三. macOS平台,下载jadx-gui反编译工具,下载方法
终端执行 brew install jadx
执行完毕后,终端再输入命令 jadx-gui,即可打开jadx,
点击 Open file/打开文件,打开apk安装包文件,
点击APK signature, Modulus/模数 为公钥,十进制显示的;MD5 Fingerprint/MD5签名,APP备案填写时需要去掉空格,填写32位长度的十六进制数据。
本文只详细讲解android app获取方法,三个平台获取方法(android、windows、macOS):
一. Android手机平台:前提,您的应用已安装到手机;然后,android应用市场搜索下载安装 APP备案助手,此app可直接获取所有已安装app的公钥、证书MD5指纹/签名MD5值,示例:获取 抖音app公钥、MD5等信息,
APP备案助手 获取结果,与下面两种方式获取结果一致;
二. Windows平台,下载jadx-gui反编译工具,github下载地址: https://github.com/skylot/jadx/releases
下载上图这个zip,然后解压后,打开 jadx-gui.exe,
点击 Open file/打开文件,打开apk安装包文件,
点击APK signature, Modulus/模数 为公钥,十进制显示的;MD5 Fingerprint/MD5签名,APP备案填写时需要去掉空格,填写32位长度的十六进制数据。
三. macOS平台,下载jadx-gui反编译工具,下载方法
终端执行 brew install jadx
执行完毕后,终端再输入命令 jadx-gui,即可打开jadx,
点击 Open file/打开文件,打开apk安装包文件,
点击APK signature, Modulus/模数 为公钥,十进制显示的;MD5 Fingerprint/MD5签名,APP备案填写时需要去掉空格,填写32位长度的十六进制数据。
收起阅读 »解决JSON.parse大数字转换精度丢失问题
因为js语言问题,数字大于16位后,会丢失16位以后的数字:比如声明 let n = 1234567890123456789;打印n之后会显示1234567890123456800;
同理JSON.parse将json字符串转为Object时,如果其中有大数字的值,也会发生精度丢失问题。
使用uni.request或uniCloud.httpclient.request 接收远程api返回数据时,如果返回数据里有大数字,并且直接设置了返回数据类型为json的话,则不能正常接收大数字。
此时就需要先接收为纯文本,也就是dataType = "text",再通过脚本匹配大数字后,给其加上字符串的引号,再进行JSON.parse的转换。这样就能正确接收大数字值了。
下面是转换函数实现:
// 解决大数字转换丢失问题
function jsonParse(text) {
text = text.replace(/([^"'\d])(\d{16,})/g, "$1\"$2\"")
return JSON.parse(text);
}
因为js语言问题,数字大于16位后,会丢失16位以后的数字:比如声明 let n = 1234567890123456789;打印n之后会显示1234567890123456800;
同理JSON.parse将json字符串转为Object时,如果其中有大数字的值,也会发生精度丢失问题。
使用uni.request或uniCloud.httpclient.request 接收远程api返回数据时,如果返回数据里有大数字,并且直接设置了返回数据类型为json的话,则不能正常接收大数字。
此时就需要先接收为纯文本,也就是dataType = "text",再通过脚本匹配大数字后,给其加上字符串的引号,再进行JSON.parse的转换。这样就能正确接收大数字值了。
下面是转换函数实现:
// 解决大数字转换丢失问题
function jsonParse(text) {
text = text.replace(/([^"'\d])(\d{16,})/g, "$1\"$2\"")
return JSON.parse(text);
}
收起阅读 »