
基于uniapp+yii2的全套OA办公系统,提供前后端源码,支持多端部署
简介
UNI办公系统主要为国内企业提供办公自动化信息服务,功能如:工作流、工作审批(报修申请、报销申请、领用申请、申购申请、请假申请、出差申请、加班申请、用工申请、接待申请、用车申请);个人中心(公告管理、消息管理、工作日志、我的日程、工作总结);知识管理(新闻动态、管理制度、下载中心等);日常管理(资产管理、车辆管理、档案管理、考勤管理、巡更巡检、合同管理、后勤采购、就餐统计、通讯录);人事管理(员工信息、奖惩管理、社保管理等)、考勤管理(班次管理、排班管理、我的排班、签到签退);巡更巡检(地点管理、巡更班次、巡更计划、我的排班、巡更记录);意见反馈、站点帮助、在线客服、一键换肤等功能模块。
系统前端基于uniapp开发,后端基于php开发;该系统可独立部署在自己的服务器,且支持单企业版和多企业版本;可后台根据用户角色权限控制手机端功能模块的显示;目前已适配H5、微信小程序、安卓和ios,如果在试用或购买后发现问题,欢迎您随时提出。
咨询
作者QQ:21931118 QQ群:1107210028
如您已下载本插件,可加入QQ群一起讨论,作者不承诺技术支持;
演示
- 后台演示账号:后台不开源,不免费提供后台账号及接口
- 手机端测试版:测试版自己加群下载,账密:18986860001/18986860001(请扫码H5或者安卓,苹果和小程序版不提供测试账号)
- 业务合作:加Q私聊
- 官方网站:unioa插件市场
手机端部分截图
后台部分截图
安装插件
已安装插件
工作流列表
工作流配置
流程状态
配置审核人员
支持指定人员审核和指定角色审核
工作流转
新增流程节点
待办工作
已办工作
如果审核失误,在下一级没有审核的情况下,可点击重审按钮进行重新审核,流程将重新转入申请状态。
审核工作
创建申请
查看进度
如果工作已有人员进行审核,进入工作流转后,用户不能编辑和删除该工作。
企业管理人员可自定义工作类型
班次管理
排班管理
我的排班
签到列表
新建打卡
插件市场 总有一款插件适合你!
开始使用
- 将项目导入HBuildX
- 修改配置文件config/index.conf.js内配置对应参数
- 运行即可体验
发行微信小程序
在HBuildx顶部菜单点击运行->运行到微信小程序
使用微信开发者工具上传版本并审核通过即可
注意:小程序需额外配置服务器域名
发行H5
在HBuildx顶部菜单点击发行->网站-H5手机版
在弹出的对话框中输入网站标题和域名即可,将编译后的资源部署到服务器(虚拟主机)
需注意HBx直接部署网页托管需要最新版,老版本没有这个选项,可以自己到web控制台进行托管。
点击发行,等待项目编译部署即可。
如果您不想购买服务器,那就来uniCloud白嫖一波吧~
发行ios版和安卓版
在HBuildx顶部菜单点击发行->原生App-云打包,
在弹出的对话框中选择证书文件和输入密码即可。
隐私、权限声明
-
本插件需要申请的系统权限列表:
1.定位权限 2.拍照权限 3.相册权限
-
本插件采集的数据、发送的服务器地址、以及数据用途说明:
无
-
本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:
无
简介
UNI办公系统主要为国内企业提供办公自动化信息服务,功能如:工作流、工作审批(报修申请、报销申请、领用申请、申购申请、请假申请、出差申请、加班申请、用工申请、接待申请、用车申请);个人中心(公告管理、消息管理、工作日志、我的日程、工作总结);知识管理(新闻动态、管理制度、下载中心等);日常管理(资产管理、车辆管理、档案管理、考勤管理、巡更巡检、合同管理、后勤采购、就餐统计、通讯录);人事管理(员工信息、奖惩管理、社保管理等)、考勤管理(班次管理、排班管理、我的排班、签到签退);巡更巡检(地点管理、巡更班次、巡更计划、我的排班、巡更记录);意见反馈、站点帮助、在线客服、一键换肤等功能模块。
系统前端基于uniapp开发,后端基于php开发;该系统可独立部署在自己的服务器,且支持单企业版和多企业版本;可后台根据用户角色权限控制手机端功能模块的显示;目前已适配H5、微信小程序、安卓和ios,如果在试用或购买后发现问题,欢迎您随时提出。
咨询
作者QQ:21931118 QQ群:1107210028
如您已下载本插件,可加入QQ群一起讨论,作者不承诺技术支持;
演示
- 后台演示账号:后台不开源,不免费提供后台账号及接口
- 手机端测试版:测试版自己加群下载,账密:18986860001/18986860001(请扫码H5或者安卓,苹果和小程序版不提供测试账号)
- 业务合作:加Q私聊
- 官方网站:unioa插件市场
手机端部分截图
后台部分截图
安装插件
已安装插件
工作流列表
工作流配置
流程状态
配置审核人员
支持指定人员审核和指定角色审核
工作流转
新增流程节点
待办工作
已办工作
如果审核失误,在下一级没有审核的情况下,可点击重审按钮进行重新审核,流程将重新转入申请状态。
审核工作
创建申请
查看进度
如果工作已有人员进行审核,进入工作流转后,用户不能编辑和删除该工作。
企业管理人员可自定义工作类型
班次管理
排班管理
我的排班
签到列表
新建打卡
插件市场 总有一款插件适合你!
开始使用
- 将项目导入HBuildX
- 修改配置文件config/index.conf.js内配置对应参数
- 运行即可体验
发行微信小程序
在HBuildx顶部菜单点击运行->运行到微信小程序
使用微信开发者工具上传版本并审核通过即可
注意:小程序需额外配置服务器域名
发行H5
在HBuildx顶部菜单点击发行->网站-H5手机版
在弹出的对话框中输入网站标题和域名即可,将编译后的资源部署到服务器(虚拟主机)
需注意HBx直接部署网页托管需要最新版,老版本没有这个选项,可以自己到web控制台进行托管。
点击发行,等待项目编译部署即可。
如果您不想购买服务器,那就来uniCloud白嫖一波吧~
发行ios版和安卓版
在HBuildx顶部菜单点击发行->原生App-云打包,
在弹出的对话框中选择证书文件和输入密码即可。
隐私、权限声明
-
本插件需要申请的系统权限列表:
1.定位权限 2.拍照权限 3.相册权限
-
本插件采集的数据、发送的服务器地址、以及数据用途说明:
无
-
本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:
无

个人发卡网源码内核
发卡网源码演示及下载地址:fakaysw.top
<?php
namespace app\admin\controller;
use controller\BasicAdmin;
use think\Db;
class App extends BasicAdmin
{
public function index()
{
if (!$this->request->isPost()) {
return $this->fetch();
}
$version = input('version/s');
if(empty($version)){
$this->error('请输入版本号');
}
$data = [
'platform' => input('platform/s'),
'version' => $version,
];
//判断是否已经存在相同的版本号
$res = Db::name('app_version')->where($data)->find();
if($res){
$this->error('已存在相同版本');
}
$platform = input('platform/s');
$url = '';
if ($platform == 'android') {
$package = getUploadFile('package', true, ['apk']);
if (!$package['status']) {
$this->error($package['msg']);
} else {
$url = $package['data']['file'];
}
} else {
$url = input('appstore_url/s');
}
$data = array_merge($data,[
'package' => $url,
'create_at' => time(),
'create_ip' => $_SERVER['REMOTE_ADDR'],
'remark' => input('remark/s')
]);
$res = Db::name('app_version')->insert($data);
if ($res) {
$this->success('新增版本成功');
} else {
$this->error('新增版本失败');
}
}
}
<?php
namespace app\admin\controller;
use controller\BasicAdmin;
use service\DataService;
use service\NodeService;
use service\ToolsService;
use think\Request;
use think\Session;
use think\Db;
use think\captcha\Captcha;
use app\common\util\Sms;
use service\LogService;
/**
- 系统权限管理控制器
- Class Auth
- @package app\admin\controller
- @author Anyon <zoujingli@qq.com>
-
@date 2017/02/15 18:13
*/
class Auth extends BasicAdmin
{/**
- 默认数据模型
- @var string
*/
public $table = 'SystemAuth';
/**
- 权限列表
*/
public function index()
{
$this->title = '系统权限管理';
return parent::_list($this->table);
}
/**
- 权限授权
- @return string
*/
public function apply()
{
$auth_id = $this->request->get('id', '0');
$method = 'apply' . strtolower($this->request->get('action', '0'));
if (method_exists($this, $method)) {
return $this->$method($auth_id);
}
$this->assign('title', '节点授权');
return $this->_form($this->table, 'apply');
}
/**
- 读取授权节点
- @param $auth_id
*/
protected function _apply_getnode($auth_id)
{
$nodes = NodeService::get();
$checked = Db::name('SystemAuthNode')->where(['auth' => $auth_id])->column('node');
foreach ($nodes as &$node) {
$node['checked'] = in_array($node['node'], $checked);
}
$all = $this->_apply_filter(ToolsService::arr2tree($nodes, 'node', 'pnode', 'sub'));
$this->success('获取节点成功!', '', $all);
}
/**
- 保存授权节点
- @param $auth_id
*/
protected function _apply_save($auth_id)
{
list($data, $post) = [[], $this->request->post()];
foreach (isset($post['nodes']) ? $post['nodes'] : [] as $node) {
$data[] = ['auth' => $auth_id, 'node' => $node];
}
Db::name('SystemAuthNode')->where(['auth' => $auth_id])->delete();
Db::name('SystemAuthNode')->insertAll($data);
LogService::write('系统权限', '节点授权成功');
$this->success('节点授权更新成功!', '');
}
/**
- 节点数据拼装
- @param array $nodes
- @param int $level
- @return array
*/
protected function _apply_filter($nodes, $level = 1)
{
foreach ($nodes as $key => &$node) {
if (!empty($node['sub']) && is_array($node['sub'])) {
$node['sub'] = $this->_apply_filter($node['sub'], $level + 1);
}
}
return $nodes;
}
/**
- 权限添加
*/
public function add()
{
return $this->_form($this->table, 'form');
}
/**
- 权限编辑
*/
public function edit()
{
return $this->_form($this->table, 'form');
}
/**
- 权限禁用
*/
public function forbid()
{
if (DataService::update($this->table)) {
LogService::write('系统权限', '权限禁用成功');
$this->success("权限禁用成功!", '');
}
$this->error("权限禁用失败,请稍候再试!");
}
/**
- 权限恢复
*/
public function resume()
{
if (DataService::update($this->table)) {
LogService::write('系统权限', '权限启用成功');
$this->success("权限启用成功!", '');
}
$this->error("权限启用失败,请稍候再试!");
}
/**
- 权限删除
*/
public function del()
{
if (DataService::update($this->table)) {
$id = $this->request->post('id');
Db::name('SystemAuthNode')->where(['auth' => $id])->delete();
LogService::write('系统权限', '权限删除成功');
$this->success("权限删除成功!", '');
}
$this->error("权限删除失败,请稍候再试!");
}
/**
-
谷歌令牌验证
*/
public function google()
{
if (!session('user')) {
$this->error('未登录');
}
if(sysconf('is_google_auth') == 0) {
$this->error('系统未开启谷歌身份验证', '@admin');
}
$google_auth = session('google_auth');
if($google_auth) {
$this->redirect('@admin');
}
$ga = new \Util\Verify\PHPGangsta_GoogleAuthenticator();
$google_token = Db::name('system_user')->where('id',session('user')['id'])->value('google_secret_key');
if (!$this->request->isPost()) {
if($google_token == '') {
$secret = $ga->createSecret();
$qrCodeUrl = $ga->getQRCodeGoogleUrl(Request::instance()->domain(), $secret);
session('google_secret_key', $secret);
$this->assign('secret', $secret);
$this->assign('qrCodeUrl', $qrCodeUrl);} $this->assign('action_type', $google_token == '' ? 0 : 1); $this->assign('google_token', $google_token); return view();
} else {
$action_type = input('action_type/d', 0);
$code = $this->request->post('code', '', 'trim');
if($code == '') {
$this->error("请输入验证码");
}
if($action_type == 0) {//首次绑定
$google_secret_key = session('google_secret_key');
if(!$google_secret_key) {
$this->error("绑定失败,请刷新页面重试");
}
$oneCode = $ga->getCode($google_secret_key);
if($code !== $oneCode) {
$this->error("验证码错误");
} else {
$re = Db::name('system_user')->where(['id'=>session('user')['id']])->update(['google_secret_key'=>$google_secret_key]);
if(FALSE !== $re) {
session('google_auth', $oneCode);
session('google_secret_key', null);
$this->success("绑定成功", '@admin');
} else {
$this->error("绑定失败,请售后重试");
}
}
} else {
$google_secret_key = Db::name('system_user')->where(['id'=>session('user')['id']])->value('google_secret_key');
if($google_secret_key == '') {
$this->error("您未绑定谷歌身份验证器");
}
$captcha = $this->request->post('captcha_code', '', 'trim');
$captchaClass = new Captcha();
if(!$captchaClass->check($captcha)) {
$this->error('图形验证码错误!');
}
$oneCode = $ga->getCode($google_secret_key);
if($code != $oneCode) {
$this->error("身份验证码错误");
} else {
session('google_auth', $oneCode);
LogService::write('系统权限', '绑定谷歌身份验证器成功');
$this->success("验证通过,正在进入系统...", '@admin');
}
}}
}
public function resetGoogle()
{
if (!session('user')) {
$this->error('未登录');
}
if(sysconf('is_google_auth') == 0) {
$this->error('系统未开启谷歌身份验证');
}
$mobile = session('user')['phone'];
$google_auth = session('google_auth');
if($google_auth) {
$this->redirect('@admin');
}
if (!$this->request->isPost()) {
if($mobile) {
$mobile = substr($mobile, 0, 3).'****'.substr($mobile, 7);
}
$this->assign('mobile', $mobile);
return view();
} else {
$code = input('sms_code');
if($code == '') {
$this->error('请输入验证码');
}
$sms=new Sms();
if(!$sms->verifyCode($mobile, $code,'google_auth')){
$this->error($sms->getError());
}
$re = Db::name('system_user')->where(['id'=>session('user')['id']])->update(['google_secret_key'=>'']);
if(FALSE !== $re) {
LogService::write('系统权限', '谷歌身份验证器验证通过');
$this->success('验证通过,进入下一步...','admin/auth/google');
} else {
$this->error("重置失败");
}
}
}
//生成绑定谷歌身份验证器二维码(测试,部署生产环境时删除!)
public function bindGoogle()
{
$id = input('id/d');
if(!$id) {
return false;
}
$ga = new \Util\Verify\PHPGangsta_GoogleAuthenticator();
$google_secret_key = Db::name('system_user')->where(['id'=>$id])->value('google_secret_key');
if(!$google_secret_key) {
return false;
}
$qrCodeUrl = $ga->getQRCodeGoogleUrl(Request::instance()->domain(), $google_secret_key);
echo '<img src="'.$qrCodeUrl.'"">';die;
}/**
- 发送短信验证码
*/
public function sendSmsCode()
{
if (!session('user')) {
$this->error('未登录');
}
$mobile=session('user')['phone'];
if(!is_mobile_number($mobile)){
$this->error('不是有效的号码!');
}
$sms=new Sms();
$res=$sms->sendCode($mobile, 'google_auth');
if($res===false){
$this->error($sms->getError());
}
$this->success('已发送验证码,请注意查收!!');
}
}
发卡网源码演示及下载地址:fakaysw.top
<?php
namespace app\admin\controller;
use controller\BasicAdmin;
use think\Db;
class App extends BasicAdmin
{
public function index()
{
if (!$this->request->isPost()) {
return $this->fetch();
}
$version = input('version/s');
if(empty($version)){
$this->error('请输入版本号');
}
$data = [
'platform' => input('platform/s'),
'version' => $version,
];
//判断是否已经存在相同的版本号
$res = Db::name('app_version')->where($data)->find();
if($res){
$this->error('已存在相同版本');
}
$platform = input('platform/s');
$url = '';
if ($platform == 'android') {
$package = getUploadFile('package', true, ['apk']);
if (!$package['status']) {
$this->error($package['msg']);
} else {
$url = $package['data']['file'];
}
} else {
$url = input('appstore_url/s');
}
$data = array_merge($data,[
'package' => $url,
'create_at' => time(),
'create_ip' => $_SERVER['REMOTE_ADDR'],
'remark' => input('remark/s')
]);
$res = Db::name('app_version')->insert($data);
if ($res) {
$this->success('新增版本成功');
} else {
$this->error('新增版本失败');
}
}
}
<?php
namespace app\admin\controller;
use controller\BasicAdmin;
use service\DataService;
use service\NodeService;
use service\ToolsService;
use think\Request;
use think\Session;
use think\Db;
use think\captcha\Captcha;
use app\common\util\Sms;
use service\LogService;
/**
- 系统权限管理控制器
- Class Auth
- @package app\admin\controller
- @author Anyon <zoujingli@qq.com>
-
@date 2017/02/15 18:13
*/
class Auth extends BasicAdmin
{/**
- 默认数据模型
- @var string
*/
public $table = 'SystemAuth';
/**
- 权限列表
*/
public function index()
{
$this->title = '系统权限管理';
return parent::_list($this->table);
}
/**
- 权限授权
- @return string
*/
public function apply()
{
$auth_id = $this->request->get('id', '0');
$method = 'apply' . strtolower($this->request->get('action', '0'));
if (method_exists($this, $method)) {
return $this->$method($auth_id);
}
$this->assign('title', '节点授权');
return $this->_form($this->table, 'apply');
}
/**
- 读取授权节点
- @param $auth_id
*/
protected function _apply_getnode($auth_id)
{
$nodes = NodeService::get();
$checked = Db::name('SystemAuthNode')->where(['auth' => $auth_id])->column('node');
foreach ($nodes as &$node) {
$node['checked'] = in_array($node['node'], $checked);
}
$all = $this->_apply_filter(ToolsService::arr2tree($nodes, 'node', 'pnode', 'sub'));
$this->success('获取节点成功!', '', $all);
}
/**
- 保存授权节点
- @param $auth_id
*/
protected function _apply_save($auth_id)
{
list($data, $post) = [[], $this->request->post()];
foreach (isset($post['nodes']) ? $post['nodes'] : [] as $node) {
$data[] = ['auth' => $auth_id, 'node' => $node];
}
Db::name('SystemAuthNode')->where(['auth' => $auth_id])->delete();
Db::name('SystemAuthNode')->insertAll($data);
LogService::write('系统权限', '节点授权成功');
$this->success('节点授权更新成功!', '');
}
/**
- 节点数据拼装
- @param array $nodes
- @param int $level
- @return array
*/
protected function _apply_filter($nodes, $level = 1)
{
foreach ($nodes as $key => &$node) {
if (!empty($node['sub']) && is_array($node['sub'])) {
$node['sub'] = $this->_apply_filter($node['sub'], $level + 1);
}
}
return $nodes;
}
/**
- 权限添加
*/
public function add()
{
return $this->_form($this->table, 'form');
}
/**
- 权限编辑
*/
public function edit()
{
return $this->_form($this->table, 'form');
}
/**
- 权限禁用
*/
public function forbid()
{
if (DataService::update($this->table)) {
LogService::write('系统权限', '权限禁用成功');
$this->success("权限禁用成功!", '');
}
$this->error("权限禁用失败,请稍候再试!");
}
/**
- 权限恢复
*/
public function resume()
{
if (DataService::update($this->table)) {
LogService::write('系统权限', '权限启用成功');
$this->success("权限启用成功!", '');
}
$this->error("权限启用失败,请稍候再试!");
}
/**
- 权限删除
*/
public function del()
{
if (DataService::update($this->table)) {
$id = $this->request->post('id');
Db::name('SystemAuthNode')->where(['auth' => $id])->delete();
LogService::write('系统权限', '权限删除成功');
$this->success("权限删除成功!", '');
}
$this->error("权限删除失败,请稍候再试!");
}
/**
-
谷歌令牌验证
*/
public function google()
{
if (!session('user')) {
$this->error('未登录');
}
if(sysconf('is_google_auth') == 0) {
$this->error('系统未开启谷歌身份验证', '@admin');
}
$google_auth = session('google_auth');
if($google_auth) {
$this->redirect('@admin');
}
$ga = new \Util\Verify\PHPGangsta_GoogleAuthenticator();
$google_token = Db::name('system_user')->where('id',session('user')['id'])->value('google_secret_key');
if (!$this->request->isPost()) {
if($google_token == '') {
$secret = $ga->createSecret();
$qrCodeUrl = $ga->getQRCodeGoogleUrl(Request::instance()->domain(), $secret);
session('google_secret_key', $secret);
$this->assign('secret', $secret);
$this->assign('qrCodeUrl', $qrCodeUrl);} $this->assign('action_type', $google_token == '' ? 0 : 1); $this->assign('google_token', $google_token); return view();
} else {
$action_type = input('action_type/d', 0);
$code = $this->request->post('code', '', 'trim');
if($code == '') {
$this->error("请输入验证码");
}
if($action_type == 0) {//首次绑定
$google_secret_key = session('google_secret_key');
if(!$google_secret_key) {
$this->error("绑定失败,请刷新页面重试");
}
$oneCode = $ga->getCode($google_secret_key);
if($code !== $oneCode) {
$this->error("验证码错误");
} else {
$re = Db::name('system_user')->where(['id'=>session('user')['id']])->update(['google_secret_key'=>$google_secret_key]);
if(FALSE !== $re) {
session('google_auth', $oneCode);
session('google_secret_key', null);
$this->success("绑定成功", '@admin');
} else {
$this->error("绑定失败,请售后重试");
}
}
} else {
$google_secret_key = Db::name('system_user')->where(['id'=>session('user')['id']])->value('google_secret_key');
if($google_secret_key == '') {
$this->error("您未绑定谷歌身份验证器");
}
$captcha = $this->request->post('captcha_code', '', 'trim');
$captchaClass = new Captcha();
if(!$captchaClass->check($captcha)) {
$this->error('图形验证码错误!');
}
$oneCode = $ga->getCode($google_secret_key);
if($code != $oneCode) {
$this->error("身份验证码错误");
} else {
session('google_auth', $oneCode);
LogService::write('系统权限', '绑定谷歌身份验证器成功');
$this->success("验证通过,正在进入系统...", '@admin');
}
}}
}
public function resetGoogle()
{
if (!session('user')) {
$this->error('未登录');
}
if(sysconf('is_google_auth') == 0) {
$this->error('系统未开启谷歌身份验证');
}
$mobile = session('user')['phone'];
$google_auth = session('google_auth');
if($google_auth) {
$this->redirect('@admin');
}
if (!$this->request->isPost()) {
if($mobile) {
$mobile = substr($mobile, 0, 3).'****'.substr($mobile, 7);
}
$this->assign('mobile', $mobile);
return view();
} else {
$code = input('sms_code');
if($code == '') {
$this->error('请输入验证码');
}
$sms=new Sms();
if(!$sms->verifyCode($mobile, $code,'google_auth')){
$this->error($sms->getError());
}
$re = Db::name('system_user')->where(['id'=>session('user')['id']])->update(['google_secret_key'=>'']);
if(FALSE !== $re) {
LogService::write('系统权限', '谷歌身份验证器验证通过');
$this->success('验证通过,进入下一步...','admin/auth/google');
} else {
$this->error("重置失败");
}
}
}
//生成绑定谷歌身份验证器二维码(测试,部署生产环境时删除!)
public function bindGoogle()
{
$id = input('id/d');
if(!$id) {
return false;
}
$ga = new \Util\Verify\PHPGangsta_GoogleAuthenticator();
$google_secret_key = Db::name('system_user')->where(['id'=>$id])->value('google_secret_key');
if(!$google_secret_key) {
return false;
}
$qrCodeUrl = $ga->getQRCodeGoogleUrl(Request::instance()->domain(), $google_secret_key);
echo '<img src="'.$qrCodeUrl.'"">';die;
}/**
- 发送短信验证码
*/
public function sendSmsCode()
{
if (!session('user')) {
$this->error('未登录');
}
$mobile=session('user')['phone'];
if(!is_mobile_number($mobile)){
$this->error('不是有效的号码!');
}
$sms=new Sms();
$res=$sms->sendCode($mobile, 'google_auth');
if($res===false){
$this->error($sms->getError());
}
$this->success('已发送验证码,请注意查收!!');
}
}

UNIAPP前端工程师7000-10000
岗位职责:
1.根据工作安排高效、高质地完成代码编写,确保符合规范的前端代码规范;
2.负责公司项目的前端修改调试和开发工作,优化用户交互体验;
3.与设计团队紧密配合,能够实现实现设计师的设计想法;
4.与后端开发团队紧密配合,确保代码有效对接,优化页面前端性能。
任职要求:
1.大专及以上学历,有成熟微信小程序案例,微信公众平台开发项目优先;
2.熟悉各种Web客户端,尤其是主流Web浏览器的开发模式和特性;
3.精通HTML5/JavaScript/CSS3等Web开发技术,对性能优化有经验;
4.熟练微信小程序开发流程以及小程序相关API使用,熟悉微信平台接口及微信小程序功能研发;
5、熟练能用微信小程序的wmpf框架
6、熟练uniapp框架者;
7.具备工程化的前端思维,具备较好的问题分析与解决能力;
8.良好的代码编写习惯,有进取心、求知欲强烈、对工作充满热情!
工作地点:成都市郫都区创客公园
联系方式:可电子邮箱495881410@qq.com
岗位职责:
1.根据工作安排高效、高质地完成代码编写,确保符合规范的前端代码规范;
2.负责公司项目的前端修改调试和开发工作,优化用户交互体验;
3.与设计团队紧密配合,能够实现实现设计师的设计想法;
4.与后端开发团队紧密配合,确保代码有效对接,优化页面前端性能。
任职要求:
1.大专及以上学历,有成熟微信小程序案例,微信公众平台开发项目优先;
2.熟悉各种Web客户端,尤其是主流Web浏览器的开发模式和特性;
3.精通HTML5/JavaScript/CSS3等Web开发技术,对性能优化有经验;
4.熟练微信小程序开发流程以及小程序相关API使用,熟悉微信平台接口及微信小程序功能研发;
5、熟练能用微信小程序的wmpf框架
6、熟练uniapp框架者;
7.具备工程化的前端思维,具备较好的问题分析与解决能力;
8.良好的代码编写习惯,有进取心、求知欲强烈、对工作充满热情!
工作地点:成都市郫都区创客公园
联系方式:可电子邮箱495881410@qq.com

uniapp 显示 svga
注意hbuilder编译要v3
在manifest.json 文件下设置
"app-plus" : {
"compilerVersion" : 3,
}
具体内容在附件
注意hbuilder编译要v3
在manifest.json 文件下设置
"app-plus" : {
"compilerVersion" : 3,
}
具体内容在附件

uniCloud云数据库增删改查
一个H5页面,让你看懂数据库增删改查。
写在最前面
我是自学前端的小白,所以只能演示基本的操作,关于复杂的连表增删改查,还没有研究过。 就以这个demo
数据库为原型,完成查询、新增、修改、删除的基本操作示例。
1、查询,①在网站初始化时需要调用查询;②用户查询电户号时,需要调用查询;③新增或删除电户信息时,需要比对数据,也需要调用查询。
2、新增,分为①在demo
下,再新增_id
,并添加字段和值;②在指定_id
下指定位置新增。
3、更新,分为①覆盖式更新;②在同一个_id
下指定位置更新数据。
4、删除,分为①指定_id
全部删除;②在指定_id
下,指定位置删除数据。
在云函数写好之后,还需页面调用处理返回的数据,文末有示例。
一、准备云数据库
- 云数据库有两个,'initial'和'demo',后面有展开数据演示。
- 'initial',用于初始化页面。
- 'demo',用于增删改查。
二、编写云函数
- uniCloud数据库,允许在网页端直接使用
uniCloud.callFunction
函数调用数据库的数据。- 但是,建议使用云函数调用,这样数据更安全。</font>
- 云函数的主流是路由——自动匹配对应的云函数去处理数据,我还不会,所以就写了五个云函数,在页面要用哪个就调用哪个。
2.1 云函数默认目录。
uniCloud/cloudfunctions/云函数名称/云函数名称.js
2.2 uniCloud数据库操作基于mongoDB,可以查询相关文档。
三、获取数据的云函数
await db.collection('demo').field({}).get();
3.1 初始化页面获取数据的方法
- 初始化页面,需要调用的云数据库,名称
initial
,展开如下:
{
"_id":"603a0b27c9e7be00013b5e33",
"ArrList":["沙沟村","大东沟村","罗家坡村","小山岔村","渡口村","七家洼村"]
}
---------------华丽的---------分割线-------------
{
"_id":"603a120b20be4e000120b8f6",
"groupArr": {
"0": ["沙沟一变","沙沟二变","沙沟三变","同心一变","双明一变","双明二变"],
"1": ["一变","二变","三变"],
"2": ["一变","二变","三变","四变"],
"3": ["一变","二变","三变","四变","五变"]
--------后面-------还有------内容-------省略------
}
}
- 初始化云函数,名称也叫
initial
,代码如下: - get()获取的数据存放在data数组中,第一个
_id
的数据,就是data[0],第二个_id
的数据,就是data[1]
'use strict';
const db = uniCloud.database();
exports.main = async (event, context) => {
let [str] = event;
if(str ==='initial'){
let res = await db.collection('initial').field({_id:0,ArrList:1,'groupArr.0':1}).get();
//get()获取的数据存放在data数组中,第一个`_id`数据,就是data[0]
let cunList = res.data[0].ArrList;
//get()获取的数据存放在data数组中,第二个`_id`数据,就是data[1]
let zuList = res.data[1].groupArr[0];
//code的数字,只是为了前端if判断时好用,并没有什么实际意义。
return {
code:888,
msg:'初始化数据成功',
cunList,
zuList
}
}else{
return{
code:400,
msg:'您请求的数据格式有误'
}
}
}
- 前端页面调用的代码,在
onLoad
生命周期内,将查询的数据赋值给两个数组,再渲染到页面中(两个列表中)。 - 页面端调用的代码
this.$api.HandleUnicloud(cloudName, mydata){res=>{}}
,如何而来,文末有详解
export default {
data() {
return {
cun_list: [],
zu_list: []
}
}
onLoad() {
let mydata = ['initial'];
let cloudName = 'initial';
this.$api.HandleUnicloud(cloudName, mydata).then(res => {
this.cun_list = res.cunList;
this.zu_list = res.zuList;
});
}
}
3.2 <font color="red">★重点★</font> 查询返回指定字段的数据
-
示例
demo
数据库,由六部分组成,每一个_id
独立为一个单元。 -
数据库展开
{
"_id":"603eeaa40daee300011f18f4",
"villages": {
"name": "沙沟村",
"id": 0,
"groupArr": ["沙沟一变","沙沟二变","沙沟三变","同心一变","双明一变","双明二变"],
"group-1": [{"3102568889": "翟**"},{"3105360550": "李**"},{"3105360619": "马**"},..省略..],
"group-2": [{"3078734642": "刘**"},..省略..],
"group-3": [],
"group-4": [],
"group-6": []
}
}
------------华丽的-----------分割线------------------
{
"_id":"603eeab4e857bd0001690475",
"villages": {
"name": "大东沟村",
"id": 1,
"groupArr": ["一变","二变","三变"]
}
}
------------华丽的-----------分割线----------------
{
"_id":"603eeab4e857bd0001690475",
"villages": {
"name": "XXX村",
"id": 2,
"groupArr": ["一变","二变","三变","四变"]
}
}
-------后面----还有-----数据-----不再-----展示-----
field({})
,可以返回指定字段的内容,必须使用get()
方法才能获取到数据。1
表示显示,没有标注的数据不显示。但是_id
是默认显示的。field({'ArrList':1,_id:0})
,不显示_id
,查询关于ArrList
的信息,field({'villages.group-2':1})
<font color="red">查询 villages 数组或对象内 group-2 的内容。- 与变量相结合的查询方式,有二种:
const groupNum = "group-3";
field({[`villages.${groupNum}`]:1})
const groupNum = "group-4";
field({['villages.'+ groupNum]:true})
'use strict';
const db = uniCloud.database();
exports.main = async (event, context) => {
//event==mydata: { cunIndex: 0, groupName: 'group-1', userObj: { username: '', usernum: '' }, pageNum: 0, rowsNum: 10 }
let {cunIndex,groupName,userObj,pageNum,rowsNum} = event;
let resData = await db.collection('demo')
.field({_id:0,[`villages.${groupName}`]:1})
.get();
//resList等于当前村下的,当前变电组的,全部用户的信息
let resList = resData.data[`${cunIndex}`].villages[`${groupName}`];
//如果当前村的变电组不存在,或者变电组内没有数据时,调用
if(!resList||resList.length===0){
return {
code:400,
msg:'该村变电组内还未收录用户信息',
}
}
//这一段为翻页时调用
//请求的数据条数(rowsNum默认10条)
//pageNum当前点击的页码(由于是H5的原因默认是1)
let pageStarNum = (pageNum - 1) * rowsNum;
let pageEndNum = pageStarNum + rowsNum;
let pagesTotal = resList.length;
//pages表示返回的数据,在页面中总共要显示多少页
let pages = parseInt(pagesTotal/rowsNum);
//前端页面点击页码后,显示从第几条数据到第几条数据的列表
let resArr = resList.slice(pageStarNum,pageEndNum);
//resObj用于存放,数据库查找到的电户信息
let resObj = {};
//遍历当前变电组内所有的用户信息,当遍历的姓名与查询的姓名一致时
//把结果追加给resObj,使用合并对象的方法Object.assign(resObj,item)也行
resList.forEach(item=>{
if(Object.values(item)==userObj.username){
resObj = {...item}
}
})
//当用户没有输入姓名,直接查询,会获取全部列表
if(userObj.username===''){
return {
code:200,
msg:'当前查询的是:该变电组内所有用户的信息',
resArr,
pages
}
}
//当输入的姓名,查询不到时调用
//返回当前村、变电组,全部列表数据
//pages是总页数
if(Object.keys(resObj).length===0){
return {
code:404,
msg:'抱歉!该变电组内未收录此用户的信息',
resArr,
pages
}
}
//当输入的姓名,数据库中有时调用
//msg的信息可以使用模板字符串
return {
code:200,
msg:`「 ${userObj.username} 」的电户号,查询结果如下:`,
resObj
}
}
四、新增数据的云函数
4.1 在原数据外,新增一个_id,add()
- 新增单一数据,用大括号包裹数据
{"name": "张三"}
。 - 新增多条数据,用中括号包裹数据
[{"name": "张三"},{"name": "李四"},{"name": "王五"}]
。
const db = uniCloud.database();
exports.main = async (event, context) => {
let resData = await db.collection('demo')
.add([{"name": "张三"},{"name": "李四"},{"name": "王五"}]);
let resList = await db.collection('demo').get();
return {
code:200,
msg:'添加数据成功',
resList
}
};//数据返回结果
{
"code": 200,
"msg": "添加数据成功",
"resList": {
"data": [{
"_id": "60210b53ef338d00016d2404",
....此处 .... 省略 ....一万字....
},
{
"_id": "6038d46620be4e00011e5cf4",
"name": "张三"
},
{
"_id": "6038d46620be4e00011e5cf5",
"name": "李四"
},
{
"_id": "6038d46620be4e00011e5cf6",
"name": "王五"
}
}]
}
4.2 在指定_id内,指定位置新增。
-
doc('_id').update();
方法可用于更改,也可用于新增。 -
普通数组添加、删除数据,官方文档中有示例,这里演示的是,数据为对象的数组
"group-1":[{key:value},{key:vlaue}]
如何新增。 -
update(),需紧随
doc()
之后,doc()
内写指定数据的_id
,因为一个集合内可能有多个_id
-
在
update()
内引用变量,需要使用[`${变量名}`]
-
在数组尾部新增对象,可以在中括号内写模板字符串
dbCmd.push([{...addUserObj}])
'use strict';
const db = uniCloud.database();
const dbCmd = db.command;
exports.main = async (event, context) => {
let {cunIndex,groupName,userObj} = event;
if(userObj.username==''||userObj.usernum==''){
return{
code:400,
msg:'新增数据不能为空'
}
}
let resData = await db.collection('demo').field({_id: 1,[`villages.${groupName}`]: 1}).get();
//用于获取_id,存放与data[{_id:***},{_id:***},{_id:***},***]中
let id = resData.data[`${cunIndex}`]['_id'];
let groupArr = resData.data[`${cunIndex}`].villages[`${groupName}`];
let isTrue = true;
//先查询,用户要新增的电户号码,在数据库中,有没有,有就赋值false
if (groupArr) {
groupArr.forEach(val=>{
if(Object.keys(val)==userObj.usernum){
isTrue = false;
}
});
//如果数据库中没有该电户号码,就在对应村、变电组的尾部新增该数据
if(isTrue){
let addUserObj = {[`${userObj.usernum}`]:userObj.username};
let resSucc = await db.collection('demo')
.doc(`${id}`)
.update({
villages: {
[`${groupName}`]: dbCmd.push([{...addUserObj}])
}
})
return {
code: 200,
msg: '数据添加成功',
resSucc
}
//当数据库中有该电户号码时,就提示已经存在
}else{
return {
code: 400,
msg: `抱歉!电户号「 ${userObj.usernum} 」已经存在`
}
}
}
//如果该村还未创建该变电组,就新建变电组,之后再新增用户信息
if (!groupArr) {
let resSucc = await db.collection('demo')
.doc(`${id}`)
.update({
villages: {
[`${groupName}`]: dbCmd.set([{
[`${userObj.usernum}`]: userObj.username
}])
}
})
return {
code: 200,
msg: '创建变电组成功,数据添加成功',
resSucc
}
}
}
五、更新数据的云函数
5.1 覆盖式更新
- 覆盖式更新的意义不大,会将原数据彻底清除,一般情况下用不到。
update({'villages':dbCmd.set({key:value})})
将villages做为字段,后面跟一个对象
const db = uniCloud.database();
const dbCmd = db.command;
exports.main = async (event, context) => {
let resSucc = await db.collection('demo')
.doc("600bfa4d7f06f30001b47259")
.update({
'villages':dbCmd.set({
"6640059601": "张三"
})
}
);
let resObj = await db.collection('demo').get();
return {
code:200,
resObj,
resSucc
}
}
//返回的数据结果 villages下只剩下一个对象了
"villages": {"6640059601":"张三"}
5.2 指定_id
下,指定位置更新。
- 指定位置更新,同上面的update()方法一致,这里省略。
六、删除数据的云函数
6.1 删除指定_id的数据库的全部数据
-
collection.doc(_id).remove()
-
doc()
内只能写字符串或者数字,可以使用ES6模板字符串doc(`${id}`)
// 删除指定_id的数据
const db = uniCloud.database();
exports.main = async (event, context) => {
const id = '60388f7eb6ce210001cbbf14';
await db.collection('demo')
.doc(`${id}`)
.remove();
let resData = await db.collection('demo').get();
return resData;
};
6.2 删除指定位置的数据
- 在update()内使用remove(),方法挂载在command下,不能用于数组,删除后,数组原索引位置会变成null。但是可以用于对象,删除key就等于删除了该对象。doc(`${id}`).update({villages:{name:dbCmd.remove()}})
- 更新数组,可以先获取数组,再修改数组,最后把新数组更新到原位置即可。update({villages:{[`${groupName}`]:[...groupArr]}})
'use strict';
const db = uniCloud.database();
exports.main = async (event, context) => {
let {cunIndex,groupName,userObj} = event;
let resData = await db.collection('demo').field({_id: 1,[`villages.${groupName}`]: 1}).get();
let id = resData.data[`${cunIndex}`]['_id'];
let groupArr = resData.data[`${cunIndex}`].villages[`${groupName}`];
let isTrue = false;
var index = 0;
if (!groupArr) {
return {
code: 400,
msg: '抱歉!该村还未创建此变电组数据',
resSucc
}
};
groupArr.forEach((val,i)=>{
if(Object.keys(val)==userObj.usernum){
isTrue = true;
index = i
}
});
//先查询数据在数组中的索引,使用splice,删除数据,把获得的新数组,更新到原位置处
if(isTrue){
groupArr.splice([`${index}`],1);
let resData = await db.collection('demo')
.doc(`${id}`)
.update({
villages:{
[`${groupName}`]:[...groupArr]
}
})
return {
code: 200,
msg: `「 ${userObj.usernum} 」数据删除成功`,
index,
groupArr,
resData
}
};
return {
code: 400,
msg: '数据删除失败,原因:电户号错误'
}
}
七、处理云函数返回数据的工具函数
- 工具函数的路径
common/api/index.js
- HandleUnicloud有两个参数,用于
uniCloud.callFunction({name,data})
传参。 cloudName
表示,需要调用哪个云函数。mydata
表示,用户向云函数传递的数据。
const HandleUnicloud = (cloudName,mydata)=>{
return new Promise((reslove,reject)=>{
uniCloud.callFunction({
name:cloudName,
data:mydata
}).then(res=>{
if(res.result){
reslove(res.result)
}else{
reject(res.result)
}
}).catch(res=>{
console.log(res);
})
});
};
export default{
HandleUnicloud
}
八、全局注册处理云函数数据的工具函数
- 先引入
import api from './common/api'
- 再注册
Vue.prototype.$api = api
import Vue from 'vue'
import App from './App'
import api from './common/api'
Vue.config.productionTip = false
Vue.prototype.$api = api
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
九、页面调用的方法
this.$api.HandleUnicloud()
addData() {
//用户传给云函数的数据,赋值给mydata
let mydata = this.mydata;
//当前需要调用的云函数名称
let cloudName = 'UpdateAdd';
//用于检查姓名和电户号码的正则函数checkName()和checkNum()
if (!this.checkName(this.mydata.userObj.username) || !this.checkNum(this.mydata.userObj.usernum)) {
this.tip = '抱歉,姓名和电户号,数据有误!';
return;
}
//有确定和取消按键的模态框
uni.showModal({
title: '请选择',
content: `您确定要添加${mydata.userObj.username}吗?`,
cancelText:'取消添加',
confirmText:'确定添加',
//这里应当使用箭头函数,否则影响this.$api和this.tip的使用
success: res=> {
if (res.confirm) {
this.$api.HandleUnicloud(cloudName, mydata).then(v => {
this.tip = v.msg;
uni.showLoading({
title: this.tip
});
setTimeout(() => {
uni.hideLoading();
}, 1000);
});
} else if (res.cancel) {
this.tip = '选择了取消!'
}
}
});
}
十、写在最后
非常感谢,DCloud公司提供如此强大的生态系统,让我们去学习进步的效率大大的提升。
最后附上H5白~嫖建站的最后一关,跨域配置问题,将前端网页托管——参数配置——默认网址,复制粘贴到跨域配置——新增域名中,即可手机,PC端都能正常访问了。
目前uniCloud</font>提供了,免费的阿里云空间,</font>完全可以满足个人用户的建站使用。
只需要在https://unicloud.dcloud.net.cn/login申请账号就可以获得:
1、一个免费的云数据库,用于保存JSON格式的数据
2、一个免费的存储云函数的空间,用于保存可以在云端调用修改云数据库数据的函数,空间环境为nodejs,操作基于mongoDB,有所改动。
3、一个免费的云存储,用于保存图片、视频、文件等,并能够提供对应的访问地址
4、一个免费的网页托管,自动生成可访问的网址,只需处理跨域配置即可。
个人博客地址
一个H5页面,让你看懂数据库增删改查。
写在最前面
我是自学前端的小白,所以只能演示基本的操作,关于复杂的连表增删改查,还没有研究过。 就以这个demo
数据库为原型,完成查询、新增、修改、删除的基本操作示例。
1、查询,①在网站初始化时需要调用查询;②用户查询电户号时,需要调用查询;③新增或删除电户信息时,需要比对数据,也需要调用查询。
2、新增,分为①在demo
下,再新增_id
,并添加字段和值;②在指定_id
下指定位置新增。
3、更新,分为①覆盖式更新;②在同一个_id
下指定位置更新数据。
4、删除,分为①指定_id
全部删除;②在指定_id
下,指定位置删除数据。
在云函数写好之后,还需页面调用处理返回的数据,文末有示例。
一、准备云数据库
- 云数据库有两个,'initial'和'demo',后面有展开数据演示。
- 'initial',用于初始化页面。
- 'demo',用于增删改查。
二、编写云函数
- uniCloud数据库,允许在网页端直接使用
uniCloud.callFunction
函数调用数据库的数据。- 但是,建议使用云函数调用,这样数据更安全。</font>
- 云函数的主流是路由——自动匹配对应的云函数去处理数据,我还不会,所以就写了五个云函数,在页面要用哪个就调用哪个。
2.1 云函数默认目录。
uniCloud/cloudfunctions/云函数名称/云函数名称.js
2.2 uniCloud数据库操作基于mongoDB,可以查询相关文档。
三、获取数据的云函数
await db.collection('demo').field({}).get();
3.1 初始化页面获取数据的方法
- 初始化页面,需要调用的云数据库,名称
initial
,展开如下:
{
"_id":"603a0b27c9e7be00013b5e33",
"ArrList":["沙沟村","大东沟村","罗家坡村","小山岔村","渡口村","七家洼村"]
}
---------------华丽的---------分割线-------------
{
"_id":"603a120b20be4e000120b8f6",
"groupArr": {
"0": ["沙沟一变","沙沟二变","沙沟三变","同心一变","双明一变","双明二变"],
"1": ["一变","二变","三变"],
"2": ["一变","二变","三变","四变"],
"3": ["一变","二变","三变","四变","五变"]
--------后面-------还有------内容-------省略------
}
}
- 初始化云函数,名称也叫
initial
,代码如下: - get()获取的数据存放在data数组中,第一个
_id
的数据,就是data[0],第二个_id
的数据,就是data[1]
'use strict';
const db = uniCloud.database();
exports.main = async (event, context) => {
let [str] = event;
if(str ==='initial'){
let res = await db.collection('initial').field({_id:0,ArrList:1,'groupArr.0':1}).get();
//get()获取的数据存放在data数组中,第一个`_id`数据,就是data[0]
let cunList = res.data[0].ArrList;
//get()获取的数据存放在data数组中,第二个`_id`数据,就是data[1]
let zuList = res.data[1].groupArr[0];
//code的数字,只是为了前端if判断时好用,并没有什么实际意义。
return {
code:888,
msg:'初始化数据成功',
cunList,
zuList
}
}else{
return{
code:400,
msg:'您请求的数据格式有误'
}
}
}
- 前端页面调用的代码,在
onLoad
生命周期内,将查询的数据赋值给两个数组,再渲染到页面中(两个列表中)。 - 页面端调用的代码
this.$api.HandleUnicloud(cloudName, mydata){res=>{}}
,如何而来,文末有详解
export default {
data() {
return {
cun_list: [],
zu_list: []
}
}
onLoad() {
let mydata = ['initial'];
let cloudName = 'initial';
this.$api.HandleUnicloud(cloudName, mydata).then(res => {
this.cun_list = res.cunList;
this.zu_list = res.zuList;
});
}
}
3.2 <font color="red">★重点★</font> 查询返回指定字段的数据
-
示例
demo
数据库,由六部分组成,每一个_id
独立为一个单元。 -
数据库展开
{
"_id":"603eeaa40daee300011f18f4",
"villages": {
"name": "沙沟村",
"id": 0,
"groupArr": ["沙沟一变","沙沟二变","沙沟三变","同心一变","双明一变","双明二变"],
"group-1": [{"3102568889": "翟**"},{"3105360550": "李**"},{"3105360619": "马**"},..省略..],
"group-2": [{"3078734642": "刘**"},..省略..],
"group-3": [],
"group-4": [],
"group-6": []
}
}
------------华丽的-----------分割线------------------
{
"_id":"603eeab4e857bd0001690475",
"villages": {
"name": "大东沟村",
"id": 1,
"groupArr": ["一变","二变","三变"]
}
}
------------华丽的-----------分割线----------------
{
"_id":"603eeab4e857bd0001690475",
"villages": {
"name": "XXX村",
"id": 2,
"groupArr": ["一变","二变","三变","四变"]
}
}
-------后面----还有-----数据-----不再-----展示-----
field({})
,可以返回指定字段的内容,必须使用get()
方法才能获取到数据。1
表示显示,没有标注的数据不显示。但是_id
是默认显示的。field({'ArrList':1,_id:0})
,不显示_id
,查询关于ArrList
的信息,field({'villages.group-2':1})
<font color="red">查询 villages 数组或对象内 group-2 的内容。- 与变量相结合的查询方式,有二种:
const groupNum = "group-3";
field({[`villages.${groupNum}`]:1})
const groupNum = "group-4";
field({['villages.'+ groupNum]:true})
'use strict';
const db = uniCloud.database();
exports.main = async (event, context) => {
//event==mydata: { cunIndex: 0, groupName: 'group-1', userObj: { username: '', usernum: '' }, pageNum: 0, rowsNum: 10 }
let {cunIndex,groupName,userObj,pageNum,rowsNum} = event;
let resData = await db.collection('demo')
.field({_id:0,[`villages.${groupName}`]:1})
.get();
//resList等于当前村下的,当前变电组的,全部用户的信息
let resList = resData.data[`${cunIndex}`].villages[`${groupName}`];
//如果当前村的变电组不存在,或者变电组内没有数据时,调用
if(!resList||resList.length===0){
return {
code:400,
msg:'该村变电组内还未收录用户信息',
}
}
//这一段为翻页时调用
//请求的数据条数(rowsNum默认10条)
//pageNum当前点击的页码(由于是H5的原因默认是1)
let pageStarNum = (pageNum - 1) * rowsNum;
let pageEndNum = pageStarNum + rowsNum;
let pagesTotal = resList.length;
//pages表示返回的数据,在页面中总共要显示多少页
let pages = parseInt(pagesTotal/rowsNum);
//前端页面点击页码后,显示从第几条数据到第几条数据的列表
let resArr = resList.slice(pageStarNum,pageEndNum);
//resObj用于存放,数据库查找到的电户信息
let resObj = {};
//遍历当前变电组内所有的用户信息,当遍历的姓名与查询的姓名一致时
//把结果追加给resObj,使用合并对象的方法Object.assign(resObj,item)也行
resList.forEach(item=>{
if(Object.values(item)==userObj.username){
resObj = {...item}
}
})
//当用户没有输入姓名,直接查询,会获取全部列表
if(userObj.username===''){
return {
code:200,
msg:'当前查询的是:该变电组内所有用户的信息',
resArr,
pages
}
}
//当输入的姓名,查询不到时调用
//返回当前村、变电组,全部列表数据
//pages是总页数
if(Object.keys(resObj).length===0){
return {
code:404,
msg:'抱歉!该变电组内未收录此用户的信息',
resArr,
pages
}
}
//当输入的姓名,数据库中有时调用
//msg的信息可以使用模板字符串
return {
code:200,
msg:`「 ${userObj.username} 」的电户号,查询结果如下:`,
resObj
}
}
四、新增数据的云函数
4.1 在原数据外,新增一个_id,add()
- 新增单一数据,用大括号包裹数据
{"name": "张三"}
。 - 新增多条数据,用中括号包裹数据
[{"name": "张三"},{"name": "李四"},{"name": "王五"}]
。
const db = uniCloud.database();
exports.main = async (event, context) => {
let resData = await db.collection('demo')
.add([{"name": "张三"},{"name": "李四"},{"name": "王五"}]);
let resList = await db.collection('demo').get();
return {
code:200,
msg:'添加数据成功',
resList
}
};//数据返回结果
{
"code": 200,
"msg": "添加数据成功",
"resList": {
"data": [{
"_id": "60210b53ef338d00016d2404",
....此处 .... 省略 ....一万字....
},
{
"_id": "6038d46620be4e00011e5cf4",
"name": "张三"
},
{
"_id": "6038d46620be4e00011e5cf5",
"name": "李四"
},
{
"_id": "6038d46620be4e00011e5cf6",
"name": "王五"
}
}]
}
4.2 在指定_id内,指定位置新增。
-
doc('_id').update();
方法可用于更改,也可用于新增。 -
普通数组添加、删除数据,官方文档中有示例,这里演示的是,数据为对象的数组
"group-1":[{key:value},{key:vlaue}]
如何新增。 -
update(),需紧随
doc()
之后,doc()
内写指定数据的_id
,因为一个集合内可能有多个_id
-
在
update()
内引用变量,需要使用[`${变量名}`]
-
在数组尾部新增对象,可以在中括号内写模板字符串
dbCmd.push([{...addUserObj}])
'use strict';
const db = uniCloud.database();
const dbCmd = db.command;
exports.main = async (event, context) => {
let {cunIndex,groupName,userObj} = event;
if(userObj.username==''||userObj.usernum==''){
return{
code:400,
msg:'新增数据不能为空'
}
}
let resData = await db.collection('demo').field({_id: 1,[`villages.${groupName}`]: 1}).get();
//用于获取_id,存放与data[{_id:***},{_id:***},{_id:***},***]中
let id = resData.data[`${cunIndex}`]['_id'];
let groupArr = resData.data[`${cunIndex}`].villages[`${groupName}`];
let isTrue = true;
//先查询,用户要新增的电户号码,在数据库中,有没有,有就赋值false
if (groupArr) {
groupArr.forEach(val=>{
if(Object.keys(val)==userObj.usernum){
isTrue = false;
}
});
//如果数据库中没有该电户号码,就在对应村、变电组的尾部新增该数据
if(isTrue){
let addUserObj = {[`${userObj.usernum}`]:userObj.username};
let resSucc = await db.collection('demo')
.doc(`${id}`)
.update({
villages: {
[`${groupName}`]: dbCmd.push([{...addUserObj}])
}
})
return {
code: 200,
msg: '数据添加成功',
resSucc
}
//当数据库中有该电户号码时,就提示已经存在
}else{
return {
code: 400,
msg: `抱歉!电户号「 ${userObj.usernum} 」已经存在`
}
}
}
//如果该村还未创建该变电组,就新建变电组,之后再新增用户信息
if (!groupArr) {
let resSucc = await db.collection('demo')
.doc(`${id}`)
.update({
villages: {
[`${groupName}`]: dbCmd.set([{
[`${userObj.usernum}`]: userObj.username
}])
}
})
return {
code: 200,
msg: '创建变电组成功,数据添加成功',
resSucc
}
}
}
五、更新数据的云函数
5.1 覆盖式更新
- 覆盖式更新的意义不大,会将原数据彻底清除,一般情况下用不到。
update({'villages':dbCmd.set({key:value})})
将villages做为字段,后面跟一个对象
const db = uniCloud.database();
const dbCmd = db.command;
exports.main = async (event, context) => {
let resSucc = await db.collection('demo')
.doc("600bfa4d7f06f30001b47259")
.update({
'villages':dbCmd.set({
"6640059601": "张三"
})
}
);
let resObj = await db.collection('demo').get();
return {
code:200,
resObj,
resSucc
}
}
//返回的数据结果 villages下只剩下一个对象了
"villages": {"6640059601":"张三"}
5.2 指定_id
下,指定位置更新。
- 指定位置更新,同上面的update()方法一致,这里省略。
六、删除数据的云函数
6.1 删除指定_id的数据库的全部数据
-
collection.doc(_id).remove()
-
doc()
内只能写字符串或者数字,可以使用ES6模板字符串doc(`${id}`)
// 删除指定_id的数据
const db = uniCloud.database();
exports.main = async (event, context) => {
const id = '60388f7eb6ce210001cbbf14';
await db.collection('demo')
.doc(`${id}`)
.remove();
let resData = await db.collection('demo').get();
return resData;
};
6.2 删除指定位置的数据
- 在update()内使用remove(),方法挂载在command下,不能用于数组,删除后,数组原索引位置会变成null。但是可以用于对象,删除key就等于删除了该对象。doc(`${id}`).update({villages:{name:dbCmd.remove()}})
- 更新数组,可以先获取数组,再修改数组,最后把新数组更新到原位置即可。update({villages:{[`${groupName}`]:[...groupArr]}})
'use strict';
const db = uniCloud.database();
exports.main = async (event, context) => {
let {cunIndex,groupName,userObj} = event;
let resData = await db.collection('demo').field({_id: 1,[`villages.${groupName}`]: 1}).get();
let id = resData.data[`${cunIndex}`]['_id'];
let groupArr = resData.data[`${cunIndex}`].villages[`${groupName}`];
let isTrue = false;
var index = 0;
if (!groupArr) {
return {
code: 400,
msg: '抱歉!该村还未创建此变电组数据',
resSucc
}
};
groupArr.forEach((val,i)=>{
if(Object.keys(val)==userObj.usernum){
isTrue = true;
index = i
}
});
//先查询数据在数组中的索引,使用splice,删除数据,把获得的新数组,更新到原位置处
if(isTrue){
groupArr.splice([`${index}`],1);
let resData = await db.collection('demo')
.doc(`${id}`)
.update({
villages:{
[`${groupName}`]:[...groupArr]
}
})
return {
code: 200,
msg: `「 ${userObj.usernum} 」数据删除成功`,
index,
groupArr,
resData
}
};
return {
code: 400,
msg: '数据删除失败,原因:电户号错误'
}
}
七、处理云函数返回数据的工具函数
- 工具函数的路径
common/api/index.js
- HandleUnicloud有两个参数,用于
uniCloud.callFunction({name,data})
传参。 cloudName
表示,需要调用哪个云函数。mydata
表示,用户向云函数传递的数据。
const HandleUnicloud = (cloudName,mydata)=>{
return new Promise((reslove,reject)=>{
uniCloud.callFunction({
name:cloudName,
data:mydata
}).then(res=>{
if(res.result){
reslove(res.result)
}else{
reject(res.result)
}
}).catch(res=>{
console.log(res);
})
});
};
export default{
HandleUnicloud
}
八、全局注册处理云函数数据的工具函数
- 先引入
import api from './common/api'
- 再注册
Vue.prototype.$api = api
import Vue from 'vue'
import App from './App'
import api from './common/api'
Vue.config.productionTip = false
Vue.prototype.$api = api
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
九、页面调用的方法
this.$api.HandleUnicloud()
addData() {
//用户传给云函数的数据,赋值给mydata
let mydata = this.mydata;
//当前需要调用的云函数名称
let cloudName = 'UpdateAdd';
//用于检查姓名和电户号码的正则函数checkName()和checkNum()
if (!this.checkName(this.mydata.userObj.username) || !this.checkNum(this.mydata.userObj.usernum)) {
this.tip = '抱歉,姓名和电户号,数据有误!';
return;
}
//有确定和取消按键的模态框
uni.showModal({
title: '请选择',
content: `您确定要添加${mydata.userObj.username}吗?`,
cancelText:'取消添加',
confirmText:'确定添加',
//这里应当使用箭头函数,否则影响this.$api和this.tip的使用
success: res=> {
if (res.confirm) {
this.$api.HandleUnicloud(cloudName, mydata).then(v => {
this.tip = v.msg;
uni.showLoading({
title: this.tip
});
setTimeout(() => {
uni.hideLoading();
}, 1000);
});
} else if (res.cancel) {
this.tip = '选择了取消!'
}
}
});
}
十、写在最后
非常感谢,DCloud公司提供如此强大的生态系统,让我们去学习进步的效率大大的提升。
最后附上H5白~嫖建站的最后一关,跨域配置问题,将前端网页托管——参数配置——默认网址,复制粘贴到跨域配置——新增域名中,即可手机,PC端都能正常访问了。
目前uniCloud</font>提供了,免费的阿里云空间,</font>完全可以满足个人用户的建站使用。
只需要在https://unicloud.dcloud.net.cn/login申请账号就可以获得:
1、一个免费的云数据库,用于保存JSON格式的数据
2、一个免费的存储云函数的空间,用于保存可以在云端调用修改云数据库数据的函数,空间环境为nodejs,操作基于mongoDB,有所改动。
3、一个免费的云存储,用于保存图片、视频、文件等,并能够提供对应的访问地址
4、一个免费的网页托管,自动生成可访问的网址,只需处理跨域配置即可。
个人博客地址

【分享】uniCloud云函数结合nodemailer发送邮件的方法
网上找的node发邮件,用到最多的是nodemailer,于是利用百度出来的代码,直接运行在uniCloud,本地运行发送成功,但是云端一直报错。
仔细看了报错内容,提到Thread,所以猜测代码里新建了线程去发邮件。
为了找到如何用当前线程发,第一时间想到的是nodemailer的官方文档:https://nodemailer.com/about/
结合官方文档,写了个可以在uniCloud云端正常发邮件,并且亲测成功的代码:
'use strict';
const nodemailer = require('nodemailer') // 记得在当前文件夹执行npm install nodemailer后才能使用
exports.main = async (event, context) => {
let transporter = nodemailer.createTransport({
host: 'smtp.126.com',
secureConnection: true,
port: 465,
secure: true,
auth: {
user: 'yourname@126.com',
pass: 'SMTP授权码'
}
})
const info = await transporter.sendMail({
from: '"邮箱昵称"<yourname@126.com>',
to: 'receiver@163.com',
subject: '主题',
html: '<h1>HTML代码</h1>',
text: '文本'
})
if (info.messageId) {
return {code: 0, msg: '发送成功'}
} else {
return {code: 1, msg: '发送失败', info}
}
}
代码里最关键的是await transporter.sendMail这行,这样写才是在当前线程发
百度上找到的大部分人是这么写的:
transporter.sendMail({
from: '"邮箱昵称"<yourname@126.com>',
to: 'receiver@163.com',
subject: '主题',
html: '<h1>HTML代码</h1>',
text: '文本'
}, (error, info) => {
if (error) {
return console.log(error);
}
console.log(info)
})
这样写的话会新建线程,这在普通的node环境当然没错,但是uniCloud却报了关于线程的错误,猜测是uniCloud不允许多线程
网上找的node发邮件,用到最多的是nodemailer,于是利用百度出来的代码,直接运行在uniCloud,本地运行发送成功,但是云端一直报错。
仔细看了报错内容,提到Thread,所以猜测代码里新建了线程去发邮件。
为了找到如何用当前线程发,第一时间想到的是nodemailer的官方文档:https://nodemailer.com/about/
结合官方文档,写了个可以在uniCloud云端正常发邮件,并且亲测成功的代码:
'use strict';
const nodemailer = require('nodemailer') // 记得在当前文件夹执行npm install nodemailer后才能使用
exports.main = async (event, context) => {
let transporter = nodemailer.createTransport({
host: 'smtp.126.com',
secureConnection: true,
port: 465,
secure: true,
auth: {
user: 'yourname@126.com',
pass: 'SMTP授权码'
}
})
const info = await transporter.sendMail({
from: '"邮箱昵称"<yourname@126.com>',
to: 'receiver@163.com',
subject: '主题',
html: '<h1>HTML代码</h1>',
text: '文本'
})
if (info.messageId) {
return {code: 0, msg: '发送成功'}
} else {
return {code: 1, msg: '发送失败', info}
}
}
代码里最关键的是await transporter.sendMail这行,这样写才是在当前线程发
百度上找到的大部分人是这么写的:
transporter.sendMail({
from: '"邮箱昵称"<yourname@126.com>',
to: 'receiver@163.com',
subject: '主题',
html: '<h1>HTML代码</h1>',
text: '文本'
}, (error, info) => {
if (error) {
return console.log(error);
}
console.log(info)
})
这样写的话会新建线程,这在普通的node环境当然没错,但是uniCloud却报了关于线程的错误,猜测是uniCloud不允许多线程
原文出处:https://coding3.com/archives/uniCloud-nodemailer.html
收起阅读 »
红包雨--简单实现,没有过度的修饰
<template>
<view class="home">
<view class="content">
<view @animationend="runend(index)" @animationstart="runstart(index)" v-for="(item,index) in packStyle" :key="index" class="envelope" :style="item">
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
packStyle: []
};
},
onLoad() {
// 随即生成红包
this.createRedPack();
},
mounted() {
},
onReady() {
},
methods: {
runstart(key){
if(key===0){
console.log('监听动画开始');
}
},
runend(key){
if(key===this.packStyle.length-1){
console.log('监听动画结束---下一波红包开始');
this.createRedPack()
}
},
createRedPack(){
// 随机生成30个红包
var initNumber = 0;
for (var i = 0; i < 60; i++) {
let lefts = (Math.floor(Math.random()*5+5)); // 随机边距
let delay = Math.floor(Math.random()*5+2); // 延迟时间
initNumber+=lefts; // 确保唯一,不让红包出现重叠现象
this.packStyle.push({
'left': initNumber+'%',
'top': lefts+'px',
'animation-delay': delay/2+'s'
})
}
}
}
};
</script>
<style>
.home {
width: 100%;
height: 100%;
}
.content {
position: relative;
height: 100%;
background: #fff;
overflow: hidden;
}
.envelope {
position: fixed;
opacity: 0;
animation: drops 1.2s cubic-bezier(.22,.22,.39,.26) 1;
width: 60px;
height: 60px;
background: url(../../static/hongbaotu.png) no-repeat;
/* background-color: #007AFF; */
background-size: 60px 60px;
}
@keyframes drops {
0% {
opacity: 0;
}
20% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
transform: translate3d(10px, 100vh, -10px);
}
}
</style>
<template>
<view class="home">
<view class="content">
<view @animationend="runend(index)" @animationstart="runstart(index)" v-for="(item,index) in packStyle" :key="index" class="envelope" :style="item">
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
packStyle: []
};
},
onLoad() {
// 随即生成红包
this.createRedPack();
},
mounted() {
},
onReady() {
},
methods: {
runstart(key){
if(key===0){
console.log('监听动画开始');
}
},
runend(key){
if(key===this.packStyle.length-1){
console.log('监听动画结束---下一波红包开始');
this.createRedPack()
}
},
createRedPack(){
// 随机生成30个红包
var initNumber = 0;
for (var i = 0; i < 60; i++) {
let lefts = (Math.floor(Math.random()*5+5)); // 随机边距
let delay = Math.floor(Math.random()*5+2); // 延迟时间
initNumber+=lefts; // 确保唯一,不让红包出现重叠现象
this.packStyle.push({
'left': initNumber+'%',
'top': lefts+'px',
'animation-delay': delay/2+'s'
})
}
}
}
};
</script>
<style>
.home {
width: 100%;
height: 100%;
}
.content {
position: relative;
height: 100%;
background: #fff;
overflow: hidden;
}
.envelope {
position: fixed;
opacity: 0;
animation: drops 1.2s cubic-bezier(.22,.22,.39,.26) 1;
width: 60px;
height: 60px;
background: url(../../static/hongbaotu.png) no-repeat;
/* background-color: #007AFF; */
background-size: 60px 60px;
}
@keyframes drops {
0% {
opacity: 0;
}
20% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
transform: translate3d(10px, 100vh, -10px);
}
}
</style>
收起阅读 »

安卓离线打包小米推送Register mipush appId or appKey is null or empty的解决方法
安卓离线打包,小米离线推送,打开页面,提示Register mipush appId or appKey is null or empty

问了官方人员,见这个帖子。https://ask.dcloud.net.cn/question/118126
我们分3步来分析可能会出什么问题。
第一步、引入lib:aps-release.aar和aps-unipush-release.aar
第二步、build配置key
第三步、工程的xml引入这个key
第一步,类库没有引入。
因为我的华为测试还好的,可以离线推送,所以我首先排除掉这个问题,我尝试把这两个类库去掉,安装好app,会提示push库不存在。
所以,排除掉这个问题。
第二步、build配置key
这步,似乎也没啥好看的,按照官方的离线打包文档,就是这么写的。
第三步、工程的xml引入这个key
后来读了push的安卓源代码。
发现是这步的问题,meta-data的位置放错了,要放到application后面,但奇怪的是华为的也在前面,不知道为什么能识别。
如果大家觉得受益匪浅的话,记得打赏一下哈。有问题加我QQ:13040
安卓离线打包,小米离线推送,打开页面,提示Register mipush appId or appKey is null or empty
问了官方人员,见这个帖子。https://ask.dcloud.net.cn/question/118126
我们分3步来分析可能会出什么问题。
第一步、引入lib:aps-release.aar和aps-unipush-release.aar
第二步、build配置key
第三步、工程的xml引入这个key
第一步,类库没有引入。
因为我的华为测试还好的,可以离线推送,所以我首先排除掉这个问题,我尝试把这两个类库去掉,安装好app,会提示push库不存在。
所以,排除掉这个问题。
第二步、build配置key
这步,似乎也没啥好看的,按照官方的离线打包文档,就是这么写的。
第三步、工程的xml引入这个key
后来读了push的安卓源代码。
发现是这步的问题,meta-data的位置放错了,要放到application后面,但奇怪的是华为的也在前面,不知道为什么能识别。
如果大家觉得受益匪浅的话,记得打赏一下哈。有问题加我QQ:13040

个推解读Android 12首个开发者预览版
引言
近日,Google发布了新系统 Android 12 的首个开发者预览版。根据谷歌官方消息,最终版本的Android 12预计于今年下半年正式上线。此次发布预览版的目的主要是帮助开发者提前了解Android 12的新变化,为后续进行应用适配提前做准备。
作为陪伴开发者多年的重要伙伴,个推一直密切关注和跟进行业发展趋势。在Android12首个开发者预览版发布后,个推快速对新系统的有关更新展开了调研。本文对Android 12预览版的部分新功能、新特性进行了解读,希望能帮助广大开发者对新系统有个快速了解。
Android 12行为变更:面向所有应用
用户体验升级
沉浸式手势导航改进
从Android 10 开始,Android 系统就已支持手势导航,致力于给用户带来沉浸式的全新体验。在之前版本的系统下,用户启动安卓的沉浸模式,默认需要的手势操作是:从屏幕侧方滑入,退出沉浸模式,然后再返回上一个界面。最新发布的Android 12首个开发者预览版则对手势导航模式进行了简化:用户不再需要滑动两次,只需一次滑动手势,即可退出全屏沉浸模式并返回上个界面,更加高效和便利。
隐私/安全保护增强
MAC 地址权限限制
为进一步保护用户隐私和数据安全,Android 11引入了“单次授权”“权限自动重置”“分区存储”等功能。Android 12则更进一步限制了所有非系统应用程序对设备MAC地址的访问,无论目标API级别如何。
相关API返回空值或占位符值,具体取决于应用程序的目标SDK版本:
➀ target = 12 ,返回 null
➁ target <12 ,返回 02:00:00:00:00:00
未被信任的触摸事件将被阻止
为了保护系统安全和更好的用户体验,Android 12将会阻止某些窗口的触摸。
例如:
➀ 申请了 SYSTEM_ALERT_WINDOW 权限的 windows
➁ Toast messages
应用不能关闭系统对话框
ACTION_CLOSE_SYSTEM_DIALOGS intent 在 Android 12 被废弃。
➀ target = 12, 将抛出 SecurityException 异常
➁ target < 12 ,该 intent 不会执行,会在 logcat 打印警告日志:
E ActivityTaskManager Permission Denial:
android.intent.action.CLOSE_SYSTEM_DIALOGS broadcast from
com.package.name requires android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS,
dropping broadcast.
Android 12行为变更:针对Target = 12的应用
自定义通知栏
之前,开发者能自定义整个通知栏区域的布局和样式,这就导致了不同设备的兼容适配问题以及用户的浏览不适应问题。
Android 12更改了完全自定义通知的外观。对于 target = 12 的应用,在通知栏的消息展示均使用统一的模板。上面应用名字显示和折叠按钮都是相同的、固定的,下面折叠和展开状态呈现的区域是可自定义的:
折叠和展开的样式:
折叠状态
展开状态
若APP中存在自定义Notification.Style,亦或是使用了Notification.Builder中 setCustomContentView(RemoteViews), setCustomBigContentView(RemoteViews)和setCustomHeadsUpContentView(RemoteViews)方法,可能会受此影响。
隐私/安全
WebView 中的SameSite cookie行为
Android的WebView组件基于Chromium来提高安全性和隐私性,去年,Chromium对第三方Cookie的处理方式进行了更改,并已面向众多Chrome用户推出。从Android 12开始,这些更改将应用于WebView。
SameSitecookie的属性控制它是否可以与任何请求一起发送,还是只能与相同站点的请求一起发送。Android 12中的WebView基本版本(版本89.0.4385.0)改进了第三方Cookie的默认处理,将有助于防止意外的跨站点共享。
ADB backup 限制
Android 12 限制了 adb backup 命令行的默认行为 (该命令行是用来备份恢复数据的),对应用程序数据adb backup有依赖的开发者可以在清单文件中设置 android:debuggable 为 true。
组件需要添加 exported 配置
target=12时,使用的activity 、service或者广播有用到 intent filters ,则需声明 android:exported 属性。不配置的话,在安卓 12 设备上将不能安装,logcat 也会打印错误日志:
Targeting S+ (version 10000 and above) requires that an explicit value for
android:exported be defined when intent filters are present
Pending intents 必须声明意图
使用 PendingIntent 需要声明 PendingIntent.FLAG_MUTABLE 或者 PendingIntent.FLAG_IMMUTABLE flag,否则系统会抛出异常 IllegalArgumentException。
性能
前台服务启动限制
以 Android 12 为目标的应用程序,无法在后台运行时启动前台服务,应用程序在后台运行时,可考虑使用 WorkManager 执行任务。
ForegroundService通知延迟
前台服务启动后必须调用startForeground() 来显示前台通知,如果应用在5s内未调用 startForeground(),则系统将停止服务并声明此应用为 ANR。
在Android 12中,限制时间由5s改为了10s。这样一来,对于部分APP来讲,将会有更充分的处理时间。
通知跳转
services 或者 broadcast receivers 中创建的通知将不能调用 startActivity() !!!
logcat 会打印:
Indirect notification activity start (trampoline) from PACKAGE_NAME, \this should be avoided for performance reasons.
总结
以上,是个推对Android 12首个开发者预览版本中几个重要更新点的解读。
除了以上内容外,Android 12 预览版还在视频、音频和图片处理方面做了很多有趣的更新。比如,通过手机的振动马达增加对触觉耦合音频效果的支持,可以帮助游戏类APP提升玩家体验;引入了兼容媒体转码功能,可以让那些不支持 HEVC的应用,也能将文件高效转码为 AVC 格式;同时还引入了对 AV1 图像文件格式(AVIF)的支持,使得开发者可以同样的文件大小,收获比 JPEG 图像更高的图像质量……感兴趣的开发者可以进入Android 12官网进一步详细了解。
https://developer.android.google.cn/about/versions/12
后续,个推还将在持续打磨开发者服务和SDK产品的同时,密切跟进移动开发领域的相关动态,为开发者升级产品功能、迭代服务体验提供有效建议。
也欢迎更多的开发者和我们一起交流和探索Android及移动开发新技术,共同建设更好的安卓开发生态。
*本文图片来源于Android官网
引言
近日,Google发布了新系统 Android 12 的首个开发者预览版。根据谷歌官方消息,最终版本的Android 12预计于今年下半年正式上线。此次发布预览版的目的主要是帮助开发者提前了解Android 12的新变化,为后续进行应用适配提前做准备。
作为陪伴开发者多年的重要伙伴,个推一直密切关注和跟进行业发展趋势。在Android12首个开发者预览版发布后,个推快速对新系统的有关更新展开了调研。本文对Android 12预览版的部分新功能、新特性进行了解读,希望能帮助广大开发者对新系统有个快速了解。
Android 12行为变更:面向所有应用
用户体验升级
沉浸式手势导航改进
从Android 10 开始,Android 系统就已支持手势导航,致力于给用户带来沉浸式的全新体验。在之前版本的系统下,用户启动安卓的沉浸模式,默认需要的手势操作是:从屏幕侧方滑入,退出沉浸模式,然后再返回上一个界面。最新发布的Android 12首个开发者预览版则对手势导航模式进行了简化:用户不再需要滑动两次,只需一次滑动手势,即可退出全屏沉浸模式并返回上个界面,更加高效和便利。
隐私/安全保护增强
MAC 地址权限限制
为进一步保护用户隐私和数据安全,Android 11引入了“单次授权”“权限自动重置”“分区存储”等功能。Android 12则更进一步限制了所有非系统应用程序对设备MAC地址的访问,无论目标API级别如何。
相关API返回空值或占位符值,具体取决于应用程序的目标SDK版本:
➀ target = 12 ,返回 null
➁ target <12 ,返回 02:00:00:00:00:00
未被信任的触摸事件将被阻止
为了保护系统安全和更好的用户体验,Android 12将会阻止某些窗口的触摸。
例如:
➀ 申请了 SYSTEM_ALERT_WINDOW 权限的 windows
➁ Toast messages
应用不能关闭系统对话框
ACTION_CLOSE_SYSTEM_DIALOGS intent 在 Android 12 被废弃。
➀ target = 12, 将抛出 SecurityException 异常
➁ target < 12 ,该 intent 不会执行,会在 logcat 打印警告日志:
E ActivityTaskManager Permission Denial:
android.intent.action.CLOSE_SYSTEM_DIALOGS broadcast from
com.package.name requires android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS,
dropping broadcast.
Android 12行为变更:针对Target = 12的应用
自定义通知栏
之前,开发者能自定义整个通知栏区域的布局和样式,这就导致了不同设备的兼容适配问题以及用户的浏览不适应问题。
Android 12更改了完全自定义通知的外观。对于 target = 12 的应用,在通知栏的消息展示均使用统一的模板。上面应用名字显示和折叠按钮都是相同的、固定的,下面折叠和展开状态呈现的区域是可自定义的:
折叠和展开的样式:
折叠状态
展开状态
若APP中存在自定义Notification.Style,亦或是使用了Notification.Builder中 setCustomContentView(RemoteViews), setCustomBigContentView(RemoteViews)和setCustomHeadsUpContentView(RemoteViews)方法,可能会受此影响。
隐私/安全
WebView 中的SameSite cookie行为
Android的WebView组件基于Chromium来提高安全性和隐私性,去年,Chromium对第三方Cookie的处理方式进行了更改,并已面向众多Chrome用户推出。从Android 12开始,这些更改将应用于WebView。
SameSitecookie的属性控制它是否可以与任何请求一起发送,还是只能与相同站点的请求一起发送。Android 12中的WebView基本版本(版本89.0.4385.0)改进了第三方Cookie的默认处理,将有助于防止意外的跨站点共享。
ADB backup 限制
Android 12 限制了 adb backup 命令行的默认行为 (该命令行是用来备份恢复数据的),对应用程序数据adb backup有依赖的开发者可以在清单文件中设置 android:debuggable 为 true。
组件需要添加 exported 配置
target=12时,使用的activity 、service或者广播有用到 intent filters ,则需声明 android:exported 属性。不配置的话,在安卓 12 设备上将不能安装,logcat 也会打印错误日志:
Targeting S+ (version 10000 and above) requires that an explicit value for
android:exported be defined when intent filters are present
Pending intents 必须声明意图
使用 PendingIntent 需要声明 PendingIntent.FLAG_MUTABLE 或者 PendingIntent.FLAG_IMMUTABLE flag,否则系统会抛出异常 IllegalArgumentException。
性能
前台服务启动限制
以 Android 12 为目标的应用程序,无法在后台运行时启动前台服务,应用程序在后台运行时,可考虑使用 WorkManager 执行任务。
ForegroundService通知延迟
前台服务启动后必须调用startForeground() 来显示前台通知,如果应用在5s内未调用 startForeground(),则系统将停止服务并声明此应用为 ANR。
在Android 12中,限制时间由5s改为了10s。这样一来,对于部分APP来讲,将会有更充分的处理时间。
通知跳转
services 或者 broadcast receivers 中创建的通知将不能调用 startActivity() !!!
logcat 会打印:
Indirect notification activity start (trampoline) from PACKAGE_NAME, \this should be avoided for performance reasons.
总结
以上,是个推对Android 12首个开发者预览版本中几个重要更新点的解读。
除了以上内容外,Android 12 预览版还在视频、音频和图片处理方面做了很多有趣的更新。比如,通过手机的振动马达增加对触觉耦合音频效果的支持,可以帮助游戏类APP提升玩家体验;引入了兼容媒体转码功能,可以让那些不支持 HEVC的应用,也能将文件高效转码为 AVC 格式;同时还引入了对 AV1 图像文件格式(AVIF)的支持,使得开发者可以同样的文件大小,收获比 JPEG 图像更高的图像质量……感兴趣的开发者可以进入Android 12官网进一步详细了解。
https://developer.android.google.cn/about/versions/12
后续,个推还将在持续打磨开发者服务和SDK产品的同时,密切跟进移动开发领域的相关动态,为开发者升级产品功能、迭代服务体验提供有效建议。
也欢迎更多的开发者和我们一起交流和探索Android及移动开发新技术,共同建设更好的安卓开发生态。
*本文图片来源于Android官网
收起阅读 »
uniapp升级v3之后,手机上的复选框样式没有了,h5还是正常
uniapp升级v3之后,手机上的复选框样式没有了,h5还是正常
因为升级v3 之前,web上是通过 .uni-checkbox-input 来修改的复选框样式,app是通过 .wx-checkbox-input 来修改的复选框样式
升级之后.wx-checkbox-input就失效了,统一使用.uni-checkbox-input了
uniapp升级v3之后,手机上的复选框样式没有了,h5还是正常
因为升级v3 之前,web上是通过 .uni-checkbox-input 来修改的复选框样式,app是通过 .wx-checkbox-input 来修改的复选框样式
升级之后.wx-checkbox-input就失效了,统一使用.uni-checkbox-input了
收起阅读 »
mui ajax ios wkwebview请求的问题
之前项目jquery+mui写的,脏乱差,现在进行改版,采用vue+mui,去掉沉重的html拼接,开始上手顺的一塌糊涂,后面打包发布真机,android的一切照旧,ios的ajax访问不到数据.心凉了一截,后面是所有代码放在mui.plusready里面,但是html里面却又不能实现vue的渲染,愁啊;调试都不方便;
后面又是百度又是啥的终于琢磨出一个办法,也不知道对大家有没有用,我这里在下面贴出来;
如下代码:
//业务页面js
mui.init();
mui.plusReady(function() {
my_packer_info.initPackageList(); //要写,否者会出现第一次打开页面没数据,因为当时plus还没被初始化,这里只要调一下vue里面的初始化数据的方法即可
});
var my_packer_info = new Vue({
el: "#my_packer_info",
data:{},
methods: {
//初始化包裹列表
initPackageList: function() {
//;
var req_data = {
PlatForm: this.PlatForm,
Shop: this.ClientInfo.ClientInfo.Shop,
Page: 1,
PageCount: 100,
}
let that = this;
//自己在mui.ajax外面封装了一层的方法,同时也要改一下mui.js里面的ajax模块的xhr的创建
AxiosPost('GetBillCode', req_data, (res) => {
if (res.State) {
....
} else {
mui.toast("系統繁忙,請稍後重試:" + res.MsgText);
}
}, (err) => {
mui.toast("系統繁忙,請稍後重試:" + err);
});
}
},
created: function() {
this.initPackageList();
}
});
//mui.js里面 $.ajax模块的修改,不是很完善,大家可以自行加点判断,我这里是从ajax下面有WKWebView提醒哪里直接拷贝过来用的
xhr: function(protocol) {
if(location.protocol === 'file:' && $.os.ios && window.webkit && window.webkit.messageHandlers && !(xhr instanceof plus.net.XMLHttpRequest)){
return plus.net.XMLHttpRequest;
}
else{
return new window.XMLHttpRequest();
}
}
之前项目jquery+mui写的,脏乱差,现在进行改版,采用vue+mui,去掉沉重的html拼接,开始上手顺的一塌糊涂,后面打包发布真机,android的一切照旧,ios的ajax访问不到数据.心凉了一截,后面是所有代码放在mui.plusready里面,但是html里面却又不能实现vue的渲染,愁啊;调试都不方便;
后面又是百度又是啥的终于琢磨出一个办法,也不知道对大家有没有用,我这里在下面贴出来;
如下代码:
//业务页面js
mui.init();
mui.plusReady(function() {
my_packer_info.initPackageList(); //要写,否者会出现第一次打开页面没数据,因为当时plus还没被初始化,这里只要调一下vue里面的初始化数据的方法即可
});
var my_packer_info = new Vue({
el: "#my_packer_info",
data:{},
methods: {
//初始化包裹列表
initPackageList: function() {
//;
var req_data = {
PlatForm: this.PlatForm,
Shop: this.ClientInfo.ClientInfo.Shop,
Page: 1,
PageCount: 100,
}
let that = this;
//自己在mui.ajax外面封装了一层的方法,同时也要改一下mui.js里面的ajax模块的xhr的创建
AxiosPost('GetBillCode', req_data, (res) => {
if (res.State) {
....
} else {
mui.toast("系統繁忙,請稍後重試:" + res.MsgText);
}
}, (err) => {
mui.toast("系統繁忙,請稍後重試:" + err);
});
}
},
created: function() {
this.initPackageList();
}
});
//mui.js里面 $.ajax模块的修改,不是很完善,大家可以自行加点判断,我这里是从ajax下面有WKWebView提醒哪里直接拷贝过来用的
xhr: function(protocol) {
if(location.protocol === 'file:' && $.os.ios && window.webkit && window.webkit.messageHandlers && !(xhr instanceof plus.net.XMLHttpRequest)){
return plus.net.XMLHttpRequest;
}
else{
return new window.XMLHttpRequest();
}
}
收起阅读 »

关于前端弹窗无法覆盖原生导航栏及tabbar的一个解决方案
直接简单的先说一下我的需求, 首页加载的时候有弹窗,可能会多个,但是需求不能一次性全部弹出需要队列弹出,就是关闭一个之后再弹出下一个。这边使用到了vuex来存储全局弹窗队列 最下方附带有效果视频
这边是再App.vue中监听一下vuex > state中的弹窗队列
watch: {
ALERT_LIST(newVal, oldVal) {
if (newVal.length >= 1 && oldVal.length == 0) { //该条件存在则证明是第一个弹窗,需要先跳转页面
this.myRouter({ //这边是跳转一个新页面
type: "navigateTo",
url: "/pages/public/alert/alert",
animationType:"fade-in",
animationDuration:300
})
}
}
}
///pages/public/alert/alert跳转的新页面中的page.json一定要这样设置,这样这个页面就把下面的页面给覆盖住包括tabbar与到导航栏,并且这个页面是透明的你可以再页面的style中自己给背景透明色
"style": {
"navigationStyle": "custom",
"app-plus": {
"animationType": "fade-in",
"animationDuration": "200",
"bounce": "none",
"backgroundColor": "rgba(0,0,0,0)" // 背景透明
}
}
在这个新页面中监听(也是这个队列)
watch: {
ALERT_LIST(newVal, oldVal) {
if (newVal.length == 0) { //如果是最后一个弹窗的情况下就返回也就是关闭这个弹窗容器
uni.navigateBack({})
}
}
},
这个就是你要显示的弹窗因为考虑到后期弹窗多的话我就做成了组件形式
说明:ALERT_LIST[0]就是拿这个队列中的第一项 name:就是你在往队列中添加新弹窗的时候定义的要与下面代码中的v-if后面对应的值一致这样在队列往前走的时候他就能显示对应的弹窗
<!-- 隐私的弹出 -->
<protocol-alert @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'protoco'"></protocol-alert>
<!-- //版本更新的弹出 -->
<versionup-date @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'version'" :versionInfo="ALERT_LIST[0].data"></versionup-date>
<!-- //淘宝客猜你想找的弹出 -->
<copy-search @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'copysearch'"></copy-search>
注:在弹窗中如果有按钮出发了关闭弹窗要将队列中的这一项删除掉 (代码只是提供了一个大概的思路具体写法看你自己)
直接简单的先说一下我的需求, 首页加载的时候有弹窗,可能会多个,但是需求不能一次性全部弹出需要队列弹出,就是关闭一个之后再弹出下一个。这边使用到了vuex来存储全局弹窗队列 最下方附带有效果视频
这边是再App.vue中监听一下vuex > state中的弹窗队列
watch: {
ALERT_LIST(newVal, oldVal) {
if (newVal.length >= 1 && oldVal.length == 0) { //该条件存在则证明是第一个弹窗,需要先跳转页面
this.myRouter({ //这边是跳转一个新页面
type: "navigateTo",
url: "/pages/public/alert/alert",
animationType:"fade-in",
animationDuration:300
})
}
}
}
///pages/public/alert/alert跳转的新页面中的page.json一定要这样设置,这样这个页面就把下面的页面给覆盖住包括tabbar与到导航栏,并且这个页面是透明的你可以再页面的style中自己给背景透明色
"style": {
"navigationStyle": "custom",
"app-plus": {
"animationType": "fade-in",
"animationDuration": "200",
"bounce": "none",
"backgroundColor": "rgba(0,0,0,0)" // 背景透明
}
}
在这个新页面中监听(也是这个队列)
watch: {
ALERT_LIST(newVal, oldVal) {
if (newVal.length == 0) { //如果是最后一个弹窗的情况下就返回也就是关闭这个弹窗容器
uni.navigateBack({})
}
}
},
这个就是你要显示的弹窗因为考虑到后期弹窗多的话我就做成了组件形式
说明:ALERT_LIST[0]就是拿这个队列中的第一项 name:就是你在往队列中添加新弹窗的时候定义的要与下面代码中的v-if后面对应的值一致这样在队列往前走的时候他就能显示对应的弹窗
<!-- 隐私的弹出 -->
<protocol-alert @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'protoco'"></protocol-alert>
<!-- //版本更新的弹出 -->
<versionup-date @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'version'" :versionInfo="ALERT_LIST[0].data"></versionup-date>
<!-- //淘宝客猜你想找的弹出 -->
<copy-search @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'copysearch'"></copy-search>
注:在弹窗中如果有按钮出发了关闭弹窗要将队列中的这一项删除掉 (代码只是提供了一个大概的思路具体写法看你自己)
收起阅读 »