uni-admin

什么是 uniCloud admin
基于 uni-app 和 uniCloud 的应用后台管理的开源框架
支持自动适配 PC 宽屏和手机各端

预置功能:

1.管理员账户的初始化、登录、以及管理员账号的修改密码功能。
⒉ 强大的基于 uni-idl 的用户管理功能。包含(注册、修改信息、停用启用、删除、根据字段排序、筛选、搜索)、角色管理和权限管理 3.顶部左上角有 logo 右上角有快捷链接等,这些都可以提供项目配置文件,轻松更换
4.左侧菜单分为两种类型:动态菜单、和静态菜单。
a.动态菜单:存储在数据表中,可以直接在菜单管理中进行增删改查,根据不同权限和角色的用户展示不同的菜单。
image

b.静态菜单:对所有用户可见,直接在项目根目录的admin.config.js中配置。  

image
5.框架支持开启 debug,当应用出现问题,右上角会显示这个 bug 图标。可展开查看报错内容,点击携带搜索参数跳转至搜索引擎查找问题

image

扩展插件:除预置功能外,还支持扩展插件。插件生态包括并不仅限于: cms 插件、banner 管理插件、日志管理插件、图表示例等,详见插件市场image

自动生成:快速生成用户端的增删改查页面。在 admin 端绝大部分功能是数据表的管理,如列表浏览、分页搜索、详情修改、新增删除,这些代码都无需自己开发。建好数据表的 schema 表结构,利用 schema2code 工具,即可自动生成该表的管理页面的代码。

imageimage

需要更换的地方:

image

1.创建项目

第一步:新建项目, 模版选择 uniCloud admin

image

第二步: 关联云服务空间, 在项目的根目录有个文件夹 uniCloud, 右键选择 '关联云服务空间....'

image

  1. 什么是云服务空间

    1. 一个云空间可以给多个应用共同使用的.(如一个网约车的用户端和司机端就应该共用同一个云空间,因为他们需要共用同一套数据)
    2. 一个云空间代表着一套独立的云开发资源, 每个云开发资源直接是相互独立的,互不影响(就像一个项目的某个独立的模块, 单独使用的数据库和后端接口)
  2. 什么是一云多端项目

    1. 这里的端指的是不同 app 对应的不同应用,而不是同一个应用对应的不同平台.
    2. 怎么理解上面这一句话: 一个系统对应的用户端(web), 乘客端(一个 app), 管理端(web), 司机端(一个 app), 指的是系统面向的不同人群
  3. 本地云函数和云端云函数

    1. 运行到浏览器才有这个按钮,运行到基座没有.image

    2. 本地云函数

      1. 开发时使用本地云函数
      2. image
      3. 为了解决云端云函数的开发遇到的麻烦, 所以可以直接访问 cloudfunctions 下的函数, 代码用的是本地的云函数,
        但是数据用的云服务空间的函数.
    3. 云端云函数

      1. 正式上线使用云端云函数
      2. image
      3. 写好的云函数,必须上传后,才能查看 每次修改调试都必须重新上传.
      4. 还有另外一个问题: 如果是多端项目都使用同一个云函数, 那么 两个项目下就都必须有这个云函数, 在上传的时候,就会相互覆盖,还会有文件的冗余和不好维护, 为了解决这个问题,就必须用绑定其他项目的服务空间的方式.
  4. 绑定其他项目的服务空间

      1. image
      2. image
      3. 一端修改, 另一端下载就可以同步了, 但是如果 a 端启用了'绑定其他项目的服务空间' (比如绑定 b 端的服务空间),那么 a 端的 uniCloud 目录就被关闭,不可打开,除非取消关联.

2.预置功能

没啥好说的, 就是一些用户管理\菜单管理\项目管理一些功能.

3.按照扩展插件

就是给admin 添加一些功能, 按照插件的说明,一步步操作就可以了.

4.自动生成表单(schema2code)

imageimage

uni-admin 发行:

uniapp 实战 -- 创建 uni-admin 项目,部署到 uniCloud 前端网页托管(免费云空间)-CSDN 博客

官网文档: uniCloud 发行 | uniCloud

image

发行后的地址: 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

image

常见错误:

1.提示未匹配到云函数[uni-upgrade-center]

首先看下云端是否有这个云函数, 如果没有就添加, 如果有还报错, 有三种解决办法:

  1. 运行到浏览器(只有运行到浏览器才能指定 本地和云端云函数) , 在运行窗口 点击 链接云端云函数image

  2. 将云端云函数下载到本地, 然后指定本地云函数

    1. image
  3. 使用其他项目云函数

    1. image

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 云函数的调用

云函数可以本地调好后上传部署, 参数固定写死,先测试下查询语句和逻辑是否正确

  1. 有坑的点: where 条件查询 和 页面的 上线发行 按钮一定要开启.
  2.  //下面是云函数中代码简写
     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 升级不能解决的?

  1. APK 更新

    1. 含义:APK(Android 应用程序包)更新是指替换整个 Android 应用安装包。在 uniapp 开发中,这意味着重新编译和发布一个包含所有代码、资源和配置更改的全新安装包。

    2. 场景: -

      1. 底层框架更新:当 uniapp 的底层框架(如安卓系统相关的原生插件更新,影响了整个应用的运行基础)发生重大变化时,需要 APK 更新。例如,Android 系统的原生功能接口发生改变,导致 uniapp 中调用的部分原生插件无法正常工作,就需要更新 APK 来集成更新后的原生插件代码。
      2. 大量资源更新:如果对应用的大量资源(如全新的界面设计,大量新的图片、音频、视频等素材)进行了替换或添加,APK 更新可以确保这些资源能够正确地安装到用户设备上。例如,一个电商应用进行了全新的品牌形象升级,涉及到众多商品图片、图标以及整体页面布局的改变,APK 更新能够更好地处理这些复杂的资源更新。 -
      3. 安全相关更新:涉及安全漏洞修复、加密算法更新等安全相关的更改,APK 更新可以确保所有安全机制在新的安装包中得到正确配置。例如,发现应用的登录认证部分存在安全风险,需要更新加密算法来加强安全性,这时就需要通过 APK 更新来让用户安装包含新安全措施的应用。
    3. WGT 更新无法解决的情况: -

      1. 安卓系统权限变更:如果应用需要新增或更改安卓系统权限(如需要访问用户的通讯录权限,之前没有此权限要求),WGT 更新无法添加这个权限,必须通过 APK 更新来在安装包的配置中声明新的权限。
      2. 原生代码库的替换或更新:当应用依赖的原生代码库(如地图导航相关的原生 SDK)需要完全替换或进行重大更新时,WGT 更新可能无法正确处理这些复杂的原生代码更新,而需要 APK 更新来重新整合新的原生代码库。
  2. WGT 更新

    1. 含义:WGT(Widget)更新是 uniapp 特有的一种更新方式,它主要是更新应用中的部分资源和代码,而不是整个应用安装包。WGT 文件本质上是一个包含更新内容的压缩包,通过特定的更新机制替换应用中原有的部分内容。

    2. 场景

      1. 小功能迭代:对于 uniapp 应用中的一些小功能改进,如修复一个小的业务逻辑错误、更新某个小模块的界面显示(如修改某个页面的按钮样式)等,WGT 更新可以快速、高效地将这些变化推送给用户。例如,一个新闻应用只是对新闻详情页的评论区样式进行了微调,就可以使用 WGT 更新。
      2. 热更新需求:当需要在不中断用户使用(即热更新)的情况下更新应用内容时,WGT 更新是很好的选择。例如,在一个在线教育应用中,老师正在直播授课,此时如果发现应用的一个小工具(如课堂提问的投票功能)出现小问题,通过 WGT 更新可以在不影响直播的情况下修复这个问题。
    3. APK 更新无法替代的优势

      1. 更新速度快、节省流量:WGT 更新只更新部分内容,相比于 APK 更新(需要下载整个安装包),更新文件体积通常较小,下载速度更快,对用户的流量消耗也更少。这在用户处于移动网络环境下,对更新文件大小比较敏感的场景下非常有优势。
      2. 更新过程相对平滑:WGT 更新不需要用户重新安装应用,避免了 APK 更新过程中可能出现的安装包下载失败、安装过程中断等问题,提供了相对更平滑的更新体验,减少了对用户正常使用应用的干扰。

升级遇到的问题

升级过程中进度条不显示进度

升级后打开应用还会弹出升级的弹窗

原因:

解决: 将管理后台->app 升级中心-> 升级包的版本号由 200 改成 2.0.0;

  • 开源

    Open Source, Open Mind, Open Sight, Open Future!

    407 引用 • 3574 回帖

相关帖子

回帖

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...