什么是 uniCloud admin
基于 uni-app 和 uniCloud 的应用后台管理的开源框架
支持自动适配 PC 宽屏和手机各端
预置功能:
1.管理员账户的初始化、登录、以及管理员账号的修改密码功能。
⒉ 强大的基于 uni-idl 的用户管理功能。包含(注册、修改信息、停用启用、删除、根据字段排序、筛选、搜索)、角色管理和权限管理 3.顶部左上角有 logo 右上角有快捷链接等,这些都可以提供项目配置文件,轻松更换
4.左侧菜单分为两种类型:动态菜单、和静态菜单。
a.动态菜单:存储在数据表中,可以直接在菜单管理中进行增删改查,根据不同权限和角色的用户展示不同的菜单。
b.静态菜单:对所有用户可见,直接在项目根目录的admin.config.js中配置。
5.框架支持开启 debug,当应用出现问题,右上角会显示这个 bug 图标。可展开查看报错内容,点击携带搜索参数跳转至搜索引擎查找问题
扩展插件:除预置功能外,还支持扩展插件。插件生态包括并不仅限于: cms 插件、banner 管理插件、日志管理插件、图表示例等,详见插件市场
自动生成:快速生成用户端的增删改查页面。在 admin 端绝大部分功能是数据表的管理,如列表浏览、分页搜索、详情修改、新增删除,这些代码都无需自己开发。建好数据表的 schema 表结构,利用 schema2code 工具,即可自动生成该表的管理页面的代码。
需要更换的地方:
1.创建项目
第一步:新建项目, 模版选择 uniCloud admin
第二步: 关联云服务空间, 在项目的根目录有个文件夹 uniCloud, 右键选择 '关联云服务空间....'
-
什么是云服务空间
- 一个云空间可以给多个应用共同使用的.(如一个网约车的用户端和司机端就应该共用同一个云空间,因为他们需要共用同一套数据)
- 一个云空间代表着一套独立的云开发资源, 每个云开发资源直接是相互独立的,互不影响(就像一个项目的某个独立的模块, 单独使用的数据库和后端接口)
-
什么是一云多端项目
- 这里的端指的是不同 app 对应的不同应用,而不是同一个应用对应的不同平台.
- 怎么理解上面这一句话: 一个系统对应的用户端(web), 乘客端(一个 app), 管理端(web), 司机端(一个 app), 指的是系统面向的不同人群
-
本地云函数和云端云函数
-
运行到浏览器才有这个按钮,运行到基座没有.
-
本地云函数
- 开发时使用本地云函数
-
- 为了解决云端云函数的开发遇到的麻烦, 所以可以直接访问 cloudfunctions 下的函数, 代码用的是本地的云函数,
但是数据用的云服务空间的函数.
-
云端云函数
- 正式上线使用云端云函数
-
- 写好的云函数,必须上传后,才能查看 每次修改调试都必须重新上传.
- 还有另外一个问题: 如果是多端项目都使用同一个云函数, 那么 两个项目下就都必须有这个云函数, 在上传的时候,就会相互覆盖,还会有文件的冗余和不好维护, 为了解决这个问题,就必须用绑定其他项目的服务空间的方式.
-
-
绑定其他项目的服务空间
-
-
-
- 一端修改, 另一端下载就可以同步了, 但是如果 a 端启用了'绑定其他项目的服务空间' (比如绑定 b 端的服务空间),那么 a 端的 uniCloud 目录就被关闭,不可打开,除非取消关联.
-
2.预置功能
没啥好说的, 就是一些用户管理\菜单管理\项目管理一些功能.
3.按照扩展插件
就是给admin 添加一些功能, 按照插件的说明,一步步操作就可以了.
4.自动生成表单(schema2code)
uni-admin 发行:
uniapp 实战 -- 创建 uni-admin 项目,部署到 uniCloud 前端网页托管(免费云空间)-CSDN 博客
官网文档: uniCloud 发行 | uniCloud
发行后的地址: https://env-00jxho64a4i1-static.normal.cloudstatic.cn/admin/
本项目的 uniCloud 使用的默认服务空间 spaceId 为:env-00jxho64a4i1
绑定安全域名: 发布到 web 端需要在 uniCloud 后台操作,绑定安全域名,否则会因为跨域问题而无法访问。教程参考:https://uniapp.dcloud.net.cn/uniCloud/publish.html#useinh5 其实就是把后台发布的域名填这里: env-00jxho64a4i1-static.normal.cloudstatic.cn
常见错误:
1.提示未匹配到云函数[uni-upgrade-center]
首先看下云端是否有这个云函数, 如果没有就添加, 如果有还报错, 有三种解决办法:
-
运行到浏览器(只有运行到浏览器才能指定 本地和云端云函数) , 在运行窗口 点击 链接云端云函数
-
将云端云函数下载到本地, 然后指定本地云函数
-
-
使用其他项目云函数
-
2.版本升级踩得坑
代码解析:
首先安装 uni-upgrade-center-app 插件
同步云函数(这些都可以看这个链接:[uni-upgrade-center 升级中心详细流程-CSDN博客](https://blog.csdn.net/tuoni123/article/details/125462996))
下面说代码是怎么运行的.
2.1.pages.json 需要添加页面
"pages": [
//不能放在第一位置, 前面必须有代码
.......
,{
"path": "uni_modules/uni-upgrade-center-app/pages/upgrade-popup",
"style": {
"disableScroll": true,
"app-plus": {
"backgroundColorTop": "transparent",
"background": "transparent",
"titleNView": false,
"scrollIndicator": false,
"popGesture": "none",
"animationType": "fade-in",
"animationDuration": 200
}
}
}],
2.2.推荐在 app.vue 中引入 checkUpdate
<script>
import checkUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update';
export default {
onLaunch: function() {
// #ifdef APP-PLUS
// App平台检测升级,服务端代码是通过uniCloud的云函数实现的,详情可参考:https://ext.dcloud.net.cn/plugin?id=4542
if (plus.runtime.appid !== 'HBuilder') { // 真机运行(使用标准基座运行)不需要检查更新,真机运行时appid固定为'HBuilder',这是调试基座的appid
checkUpdate()
}
// #endif
},
}
</script>
/uni_modules/uni-upgrade-center-app/utils/check-update 下的简写代码
import callCheckVersion from './call-check-version'
export default function() {
// #ifdef APP-PLUS
return new Promise((resolve, reject) => {
callCheckVersion().then(async (e) => {
if (code > 0) {
.......
}
}).catch(err => {
// TODO 云函数报错处理
console.error(err.message)
reject(err)
})
});
}
//call-check-version下的代码简写
export default function() {
// #ifdef APP-PLUS
return new Promise((resolve, reject) => {
plus.runtime.getProperty(plus.runtime.appid, function(widgetInfo) {
console.log(plus.runtime)
console.log(widgetInfo);
const data = {
action: 'checkVersion', //这里是定义查询的类型, 在云函数里
// plus.runtime.appid 这里的appid,如果是自定义基座,那么就是manifest.json 中的AppId,
// 如果是 标准基座或是其他 这里的appid就固定是HBuilder
appid: plus.runtime.appid,
// plus.runtime.version 这里的version 是自定义基座里manifest.json 的应用版本名称1.0.0
appVersion: plus.runtime.version,
// widgetInfo.version 这里的version 与基座无关 就是manifest.json 的应用版本名称1.0.0
wgtVersion: widgetInfo.version
}
console.log("data: ",data);
uniCloud.callFunction({
name: 'uni-upgrade-center', //调用云函数
data, //传给云函数的data数据
success: (e) => {
console.log("e: ", e);
resolve(e)
},
fail: (error) => {
reject(error)
}
})
})
})
// #endif
// #ifndef APP-PLUS 如果不是app if not default
return new Promise((resolve, reject) => {
reject({
message: '请在App中使用'
})
})
// #endif
}
新版/uni_modules/uni-upgrade-center-app/utils/check-update.ts 是.ts 代码
if (code > 0) {
// 腾讯云和阿里云下载链接不同,需要处理一下,阿里云会原样返回
const tcbRes = await uniCloud.getTempFileURL({ fileList: [url] });
if (typeof tcbRes.fileList[0].tempFileURL !== 'undefined') uniUpgradeCenterResult.url = tcbRes.fileList[0].tempFileURL;
我解释下这段代码的作用: 我们在uni-admin 后台系统->app升级中心->发布新版本时候会自动生成一个
下载路径:cloud://...-00jxho....apk. 这段代码的作用就是根据这个路径生成一个临时的真实的下载路径
{
"fileList": [
{
"fileID": "cloud://...-00jx/.....apk",
"tempFileURL": "https://....-00jxho.....normal.cloudstatic.cn/....自助平台2.0.0_1735029066360_0.apk?expire_at=1735030883&er_sign=bc9dd6bf53bd61759ffdd4869018c629"
}
]
}
2.3 云函数的调用
云函数可以本地调好后上传部署, 参数固定写死,先测试下查询语句和逻辑是否正确
- 有坑的点: where 条件查询 和 页面的 上线发行 按钮一定要开启.
-
//下面是云函数中代码简写 const checkVersion = require('./checkVersion') exports.main = async (event, context) => { //这里的event 就是 上面call-check-version 代码里的传参data const db = uniCloud.database() const appListDBName = 'opendb-app-list' const appVersionDBName = 'opendb-app-versions' let params = event.data || event.params; switch (event.action) { case 'checkVersion': //版本检查走的是这里, 其他不用看 res = await checkVersion(event, context) break; case 'deleteFile': res = await uniCloud.deleteFile({ fileList: params.fileList }) break; case 'setNewAppData': params.value.create_date = Date.now() res = await db.collection(appListDBName).doc(params.id).set(params.value) break; case 'getAppInfo': let dbAppList try { dbAppList = db.collection(appListDBName) } catch (e) {} if (!dbAppList) return fail; const dbAppListRecord = await dbAppList.where({ appid: params.appid }).get() if (dbAppListRecord && dbAppListRecord.data.length) return Object.assign({}, success, dbAppListRecord.data[0]) //返回数据给客户端 return fail break; case 'getAppVersionInfo': let dbVersionList try { dbVersionList = db.collection(appVersionDBName) } catch (e) {} if (!dbVersionList) return fail; const dbVersionListrecord = await dbVersionList.where({ appid: params.appid, platform: params.platform, type: "native_app", stable_publish: true }) .orderBy('create_date', 'desc') .get(); if (dbVersionListrecord && dbVersionListrecord.data && dbVersionListrecord.data.length > 0) return Object.assign({}, dbVersionListrecord.data[0], success) return fail break; } //返回数据给客户端 return res }; module.exports = async (event, context) => { /** * 检测升级 使用说明 * 上传包: * 1. 根据传参,先检测传参是否完整,appid appVersion wgtVersion 必传 * 2. 先从数据库取出所有该平台(从上下文读取平台信息,默认 Andriod)的所有线上发行更新 * 3. 再从所有线上发行更新中取出版本最大的一版。如果可以,尽量先检测wgt的线上发行版更新 * 4. 使用上步取出的版本包的版本号 和传参 appVersion、wgtVersion 来检测是否有更新,必须同时大于这两项,否则返回暂无更新 * 5. 如果库中 wgt包 版本大于传参 appVersion,但是不满足 min_uni_version < appVersion,则不会使用wgt更新,会接着判断库中 app包version 是否大于 appVersion * 6. is_uniapp_x 为了区分 App 类型,uni-app x 项目安卓端没有 wgt 升级 */ let { appid, appVersion, wgtVersion, is_uniapp_x = false } = event; const platform_Android = 'Android'; const platform_iOS = 'iOS'; const package_app = 'native_app'; const package_wgt = 'wgt'; const app_version_db_name = 'opendb-app-versions' //这里是查询的表名 let platform = platform_Android; // 云函数URL化请求 if (event.headers) { let body; try { if (event.httpMethod.toLocaleLowerCase() === 'get') { body = event.queryStringParameters; } else { body = JSON.parse(event.body); } } catch (e) { return { code: 500, msg: '请求错误' }; } appid = body.appid; appVersion = body.appVersion; wgtVersion = body.wgtVersion; platform = /iPhone|iPad/.test(event.headers) ? platform_iOS : platform_Android; } else if (context.OS) { platform = context.OS === 'android' ? platform_Android : context.OS === 'ios' ? platform_iOS : platform_Android; } if (appid && appVersion && wgtVersion && platform) { //这里的查询很重要,就是根据appid 和平台 查出当前应用的所以版本 const collection = uniCloud.database().collection(app_version_db_name); const record = await collection .where({ appid, platform: [platform], // 这里platform存的是数组, 所以需要加[] stable_publish: 'true' // 这里对应uni-admin 发布新版页面的上线发行按钮 一定要勾选. true 必须加引号 }) .orderBy('create_date', 'desc') .get(); console.log("查询结果:"); console.log(record); if (record && record.data && record.data.length > 0) { const appVersionInDb = record.data.find(item => item.type === package_app) || {}; const wgtVersionInDb = record.data.find(item => item.type === package_wgt) || {}; const hasAppPackage = !!Object.keys(appVersionInDb).length; const hasWgtPackage = !!Object.keys(wgtVersionInDb).length; // 取两个版本中版本号最大的包,版本一样,使用wgt包 let stablePublishDb = (() => { // uni-app x 项目安卓端没有 wgt 升级 if (is_uniapp_x === true && platform === platform_Android) { if (hasAppPackage) return appVersionInDb return {} } if (hasAppPackage && hasWgtPackage) { if (compare(wgtVersionInDb.version, appVersionInDb.version) >= 0) return wgtVersionInDb else { return appVersionInDb } } else if (hasAppPackage) { return appVersionInDb } else if (hasWgtPackage) { return wgtVersionInDb } return {} })(); if (Object.keys(stablePublishDb).length) { const { version, min_uni_version } = stablePublishDb; console.log('库版本',version); console.log('当前应用版本',appVersion); console.log(compare(version, appVersion)); // 库中的version必须满足同时大于appVersion和wgtVersion才行,因为上次更新可能是wgt更新 const appUpdate = compare(version, appVersion) === 1; // app包可用更新 const wgtUpdate = compare(version, wgtVersion) === 1; // wgt包可用更新 if (appUpdate && wgtUpdate) { // 判断是否可用wgt更新 if (min_uni_version && compare(min_uni_version, appVersion) < 1) { return { code: 101, message: 'wgt更新', ...stablePublishDb }; } else if (hasAppPackage && compare(appVersionInDb.version, appVersion) === 1) { return { code: 102, message: '整包更新', ...appVersionInDb }; } } } return { code: 0, message: '当前版本已经是最新的,不需要更新' }; } return { code: -101, message: '暂无更新或检查appid是否填写正确' }; } return { code: -102, message: '请检查传参是否填写正确' }; }; /** * 对比版本号,如需要,请自行修改判断规则 * 支持比对 ("3.0.0.0.0.1.0.1", "3.0.0.0.0.1") ("3.0.0.1", "3.0") ("3.1.1", "3.1.1.1") 之类的 * @param {Object} v1 * @param {Object} v2 * v1 > v2 return 1 * v1 < v2 return -1 * v1 == v2 return 0 */ function compare(v1 = '0', v2 = '0') { v1 = String(v1).split('.') v2 = String(v2).split('.') const minVersionLens = Math.min(v1.length, v2.length); let result = 0; for (let i = 0; i < minVersionLens; i++) { const curV1 = Number(v1[i]) const curV2 = Number(v2[i]) if (curV1 > curV2) { result = 1 break; } else if (curV1 < curV2) { result = -1 break; } } if (result === 0 && (v1.length !== v2.length)) { const v1BiggerThenv2 = v1.length > v2.length; const maxLensVersion = v1BiggerThenv2 ? v1 : v2; for (let i = minVersionLens; i < maxLensVersion.length; i++) { const curVersion = Number(maxLensVersion[i]) if (curVersion > 0) { v1BiggerThenv2 ? result = 1 : result = -1 break; } } } return result; }
其他需要注意的地方: 开发的 app 的云函数必须绑定 uni-admin 的云函数(绑定其他项目),
uniapp 开发中,apk 或者 wgt 更新有啥区别, 分别对应什么场景, 哪些是只能 apk 升级,wgt 升级不能解决的?
-
APK 更新
-
含义:APK(Android 应用程序包)更新是指替换整个 Android 应用安装包。在 uniapp 开发中,这意味着重新编译和发布一个包含所有代码、资源和配置更改的全新安装包。
-
场景: -
- 底层框架更新:当 uniapp 的底层框架(如安卓系统相关的原生插件更新,影响了整个应用的运行基础)发生重大变化时,需要 APK 更新。例如,Android 系统的原生功能接口发生改变,导致 uniapp 中调用的部分原生插件无法正常工作,就需要更新 APK 来集成更新后的原生插件代码。
- 大量资源更新:如果对应用的大量资源(如全新的界面设计,大量新的图片、音频、视频等素材)进行了替换或添加,APK 更新可以确保这些资源能够正确地安装到用户设备上。例如,一个电商应用进行了全新的品牌形象升级,涉及到众多商品图片、图标以及整体页面布局的改变,APK 更新能够更好地处理这些复杂的资源更新。 -
- 安全相关更新:涉及安全漏洞修复、加密算法更新等安全相关的更改,APK 更新可以确保所有安全机制在新的安装包中得到正确配置。例如,发现应用的登录认证部分存在安全风险,需要更新加密算法来加强安全性,这时就需要通过 APK 更新来让用户安装包含新安全措施的应用。
-
WGT 更新无法解决的情况: -
- 安卓系统权限变更:如果应用需要新增或更改安卓系统权限(如需要访问用户的通讯录权限,之前没有此权限要求),WGT 更新无法添加这个权限,必须通过 APK 更新来在安装包的配置中声明新的权限。
- 原生代码库的替换或更新:当应用依赖的原生代码库(如地图导航相关的原生 SDK)需要完全替换或进行重大更新时,WGT 更新可能无法正确处理这些复杂的原生代码更新,而需要 APK 更新来重新整合新的原生代码库。
-
-
WGT 更新
-
含义:WGT(Widget)更新是 uniapp 特有的一种更新方式,它主要是更新应用中的部分资源和代码,而不是整个应用安装包。WGT 文件本质上是一个包含更新内容的压缩包,通过特定的更新机制替换应用中原有的部分内容。
-
场景:
- 小功能迭代:对于 uniapp 应用中的一些小功能改进,如修复一个小的业务逻辑错误、更新某个小模块的界面显示(如修改某个页面的按钮样式)等,WGT 更新可以快速、高效地将这些变化推送给用户。例如,一个新闻应用只是对新闻详情页的评论区样式进行了微调,就可以使用 WGT 更新。
- 热更新需求:当需要在不中断用户使用(即热更新)的情况下更新应用内容时,WGT 更新是很好的选择。例如,在一个在线教育应用中,老师正在直播授课,此时如果发现应用的一个小工具(如课堂提问的投票功能)出现小问题,通过 WGT 更新可以在不影响直播的情况下修复这个问题。
-
APK 更新无法替代的优势:
- 更新速度快、节省流量:WGT 更新只更新部分内容,相比于 APK 更新(需要下载整个安装包),更新文件体积通常较小,下载速度更快,对用户的流量消耗也更少。这在用户处于移动网络环境下,对更新文件大小比较敏感的场景下非常有优势。
- 更新过程相对平滑:WGT 更新不需要用户重新安装应用,避免了 APK 更新过程中可能出现的安装包下载失败、安装过程中断等问题,提供了相对更平滑的更新体验,减少了对用户正常使用应用的干扰。
-
升级遇到的问题
升级过程中进度条不显示进度
升级后打开应用还会弹出升级的弹窗
原因:
解决: 将管理后台->app 升级中心-> 升级包的版本号由 200 改成 2.0.0;
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于