Dongsj的博客

Talk is cheap, show me the code.


  • 首页

  • 标签

  • 分类

  • 归档

  • 搜索

Ionic/Cordova 项目在 Android 内出现图片或网页不能显示的问题

发表于 2019-07-04 | 分类于 Ionic

问题描述

今天在 Android 中跑 Apk 时出现了网络图片不能显示的问题,打开 Chrome 进行浏览器远程调试。
经检查 cordova-plugin-whitelist 已正常安装并设置白名单,接口可以访问(接口为 https)但是图片不可以访问,提示 net::ERR_CLEARTEXT_NOT_PERMITTED。

问题原因

原因是因为在 Android 6.0 开始默认关闭了 http 请求,需要在 AnroidManifest.xml 中的 application 设置为 true。

设置 Config.xml 解决问题

最后结局方案为设置 Config.xml 添加如下配置

1
2
3
4
5
<platform name="android">
<edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application">
<application android:usesCleartextTraffic="true" />
</edit-config>
</platform>

如果修改后 build 提示 Android resource compilation failed,则需要修改 widget 的 xmlns:android 值,示例如下:

1
2
3
<widget id="..." version="1.2.6" xmlns:android="http://schemas.android.com/apk/res/android">
...
</widget>

Egret 基础知识笔记

发表于 2019-02-12 | 分类于 Egret

Egret 显示对象(DisplayObject)

核心的显示

  • 显示容器(DisplayObjectContainer)

    • 精灵(Splite)
      带有矢量绘制功能的显示容器

    • 舞台(Stage)
      Egret 的根容器,每个项目有且只有一个 Stage

    • 添加/删除显示对象(addChild/removeChild)

  • 矢量图(Shape)
    • 绘制矩形、圆、直线
    • 绘制圆弧、拱形、扇形
    • 绘制不规则进度条
  • 文本(TextField)
    仅为简单文本,可使用 HtmlTextParser 转换 html 样式文本

  • 位图(Bitmap)

    • 从硬盘读取纹理资源
    • 位图加载纹理资源
    • 展示位图
    • 九宫格
      可在 default.res.json 内直接选中资源设置

    • 填充模式(fillMode)

    • 纹理集
      • 合并纹理集
      • 加载纹理集中的特定纹理
    • 混合模式
    • 滤镜
  • 位图文本(BitmapText)
    • 如何制作字体文件(Texture merger 工具)
    • 如何创建位图文本

属性

  • 基本属性
    alpha:透明度
    width:宽度
    height:高度
    rotation:旋转角度
    scaleX:横向缩放
    scaleY:纵向缩放
    skewX:横向斜切
    skewY:纵向斜切
    visible:是否可见
    x:X轴坐标值
    y:Y轴坐标值
    anchorOffsetX:对象绝对锚点X
    anchorOffsetY:对象绝对锚点Y

  • 遮罩(Mask)
    遮罩的作用是指定一个显示对象的可见区域,所有显示对象都具备遮罩功能。

资源加载(RES)

  • 资源配置文件(*.res.json)
  • Wing 提供设计/源码模式编辑
  • 添加资源到资源配置文件
  • json 的字段意义
  • RES 工作过程
    加载资源配置文件(loadConfig()) => 加载资源组(loadGroup())=>从内存读取资源(RES.getRES()/RES.getRESAsync()/RES.getRESByUrl()),销毁资源(RES.destroyRES())
  • 利用 group 分场景加载资源

事件机制

事件

用户交互/网络请求。。。

事件监听器

响应事件的函数

添加事件监听器

DisplayObject.addEventListenner(事件名,事件监听器函数,指针);

移除事件监听器

DisplayObject.removeEventListenner(事件名,事件监听器函数,指针);

常用事件

  • egert.Event
  • egret.TouchEvent
  • egert.TimerEvent

事件流

先捕获再冒泡,addEventListenner 第四个参数 useCapture 决定是捕获还是冒泡时触发

事件对象

事件监听器的参数e

自定义事件

  • 派发
    DisplayObject.dispatchEvent(new Event(自定义事件名));
  • 响应
    DisplayObject.addEventListenner(自定义事件名,事件监听器函数,指针);

声音

分类

  • 背景音乐
  • 游戏音效

声音播放相关类

  • Sound
    声音类,主要是用加载并播放文件
  • SounChannel
    声音控制类,每个 Sound 均需要分配给一个 SoundChannel ,可对声音进行更精细的控制

解决方案

  • 使用 egret.Sound 和事件监听的方式加载播放(egret.Event.COMPLETE监听成功,egret.IOErrorEvent.IO_ERROR监听错误)
    1
    2
    3
    4
    5
    6
    7
    let s:egret.Sound = new egret.Sound();
    let bgMusic = s;
    s.load(SRC);
    s.addEventListener(egret.Event.COMPLETE,()=>{
    let channel:egret.SoundChannel = this.bgMusic.play();
    },this);
    s.addEventListener(egret.IOErrorEvent.IO_ERROR,()=>{},this);
  • 使用 RES 模块加载(加载时间可能会过长,不推荐)
    1
    2
    RES.loadConfig();
    RES.loadGroup();
  • 使用 URLLoader 类
    1
    2
    3
    4
    5
    6
    7
    let loader:egret.URLLoader = new egret.URLLoader();
    loader.addEventListener(egret.Event.COMPLETE,(event:egret.Event)=>{
    let sound:egret.Sound = loader.data;
    sound.play();
    },this);
    loader.dataFormat = egret.URLLoaderDataFormat.SOUND;
    loader.load(new egret.URLReqest(SRC));

EUI

EUI扩展库类图

默认项目基本加载过程

Skin(.EXML) + UIComponent(.JS),通过 eui.IThemeAdapter 加载default.thm.json 的配置进行关联,其中图片素材通过 eui.IAssetAdapter 关联,

控件

  • 文本
  • 图片
  • 按钮
  • 复选框
  • 单选框
  • 状态切换按钮
  • 滑动选择器
  • 进度条
  • 输入文本

容器

  • 简单容器
  • 层叠容器
  • 面板容器
  • 滚动控制容器

数据集合

  • 数据容器(结合数组集合和列表使用)
  • 数组集合
  • 列表
  • 选项卡
  • 自定义展示器

数据容器的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 设置数据源 eui.ArrayCollection
let arr:Array<any> = [];
for(let i = 0;i < 10;i++){
arr.push({name:`label:i`});
}
let euiArr:eui.ArrayCollection = new eui.ArrayCollection(arr);

// 创建数据容器
let dataGroup:eui.DataGroup = new eui.DataGroup();
dataGroup.dataProvider = euiArr;
// 展示方法1:设置展示类,必须继承自 eui.ItemRenderer,并在 dataChange 方法中渲染数据
dataGroup.itemRenderer = LabelRender;
// 展示方法2:设置数据展示皮肤,可在 EXML 文件中通过 {} 绑定 data 中的数据
dataGroup.itemRendererSkinName = SRC;
this.addChild(dataGroup);
// 展示方法3:在图形化界面中设置条目皮肤

利用 nginx + 阿里云部署 Angular/Vue/React 等 SPA 应用

发表于 2018-10-29 | 分类于 工具链开发

背景

随着 Angular/Vue/React 等 SPA 框架的普及,前后端分离已经深入人心,而 SPA 应用的部署和简单的静态网页的部署又有着微妙的区别,本文就来介绍结合 nginx 和阿里云来使用的一套带预生产环境的 SPA 应用部署方案。

SPA 在 html5 mode 下的域名解析

当 SPA 应用开启 html5 mode 的情况下,指定 url 下的全部请求都会访问入口文件(一般情况下是 index.html),然后 SPA 应用会根据 url 再去决定访问的实际页面。如使用 express 做服务器可以理解为:

1
2
3
app.use("/**", function (req, res) {
res.sendfile(staticPath+"/index.html");
});

具体实现可参考我的另一篇文章 使用 Express 实现一个简单的 SPA 静态资源服务器

使用 nginx 来解析 SPA 应用

与简单的静态网页的部署不同,我们需要将指定 url 下的全部请求都返回 index.html 的内容,其实 nginx 配置的实现方式有很多,这里我们来使用一个讨巧的处理方法,那就是将 404 指向 index.html,实现 nginx 配置如下:

1
2
3
4
5
6
7
8
9
10
server {
listen 80;
server_name dongsj.cn;
index index.html;
root /opt/static/spa-website/;
location / {
index index.html;
try_files $uri $uri/ /index.html =404;
}
}

使用 rsync 上传代码到服务器

部署代码有多种方式,因 SPA 应用的打包和 npm 依赖安装等众多因素,我选择了 rsync 直击上传代码到服务器而不是使用 docker 进行部署。
安装和命令可参考rsync官方网站: https://www.samba.org/ftp/rsync/rsync.html
我们可以直接将同步命令添加至 package.json:

1
2
3
"scripts": {
"rsync": "rsync -azP ./public/ server:/opt/static/spa-website/"
}

分别部署 develop 和 master 分支

实际开发过程中,我们一般都会将开发代码和线上代码分别放在两个分支中,此处以 develop 和 master 分支为例,在服务器分别建立两个目录来存储 SPA 应用打包后的代码,并配置 package.json 的命令如下:

1
2
3
4
"scripts": {
"rsync:develop": "rsync -azP ./public/ server:/opt/static/develop/spa-website/",
"rsync:master": "rsync -azP ./public/ server:/opt/static/master/spa-website/",
}

并应用以下 nginx 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server {
listen 80;
server_name develop.dongsj.cn;
index index.html;
root /opt/static/develop/spa-website/;
location / {
index index.html;
try_files $uri $uri/ /index.html =404;
}
}
server {
listen 80;
server_name master.dongsj.cn;
index index.html;
root /opt/static/master/spa-website/;
location / {
index index.html;
try_files $uri $uri/ /index.html =404;
}
}

配置 nginx 缓存时间和代码压缩

对于 SPA 应用,我们可以配置一定时间的静态文件缓存(如js/css/img等),但 index.html 的缓存时间要配置的尽可能短。添加 nginx 配置如下:

1
2
3
4
5
6
7
8
9
map $sent_http_content_type $expires {
default off;
text/html -10d;
text/css 1d;
application/javascript 1d;
~image/ 1d;
}
# 顺手再配置下gzip代码压缩
gzip_types text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;

至此,使用 nginx 部署的 spa 应用和命令已基本完成。

使用阿里云的全站加速来实现预生产环境

在实际工作过程中,我们除了开发环境和生产环境外,往往还需要一个预生产环境来验证代码,假设各环境和对应域名如下:

  • 开发环境:develop.dongsj.cn
  • 预生产环境:master.dongsj.cn
  • 生产环境:www.dongsj.cn
    开发环境和预生产环境可以直接使用上文的 nginx 配置,当然生产环境也可以选择手动进行部署,本文我们选择使用阿里云的全站加速来实现预生产环境到生产环境的资源同步。
    首先我们进入阿里云的全站加速控制台添加一个加速域名,加速域名填写生产环境的域名(www.dongsj.cn),源站信息选择源站域名并填写预生产环境的域名(master.dongsj.cn)。
    添加加速域名
    注意,加速区域如果选择中国大陆,则国外ip(包含翻墙的情况)均无法访问。
    确定后阿里云会进入生成CNAME的配置阶段
    等待阿里云生成CNAME
    阿里云CNAME生成完成后,我们要在域名解析为生产环境的域名(www.dongsj.cn)添加域名解析:
    阿里云生成CNAME完成
    配置域名解析
    配置成功后阿里云的全站加速控制台的该域名加速状态就可变为已完成了。
    域名解析配置成功
    最后我们要关闭阿里云的动态加速并添加缓存配置,既修改动态同步预生产和生产环境资源为手动同步:
    关闭动态加速
    添加缓存配置
    至此,阿里云全站加速配置就完成了,当我们更新预生产环境资源时,生产环境不会受到任何影响,而在预生产环境验证完成后,可以进入全站加速的刷新预热来刷新缓存同步生产环境的资源。
    刷新缓存

【抛砖引玉】使用 ES6 的类继承加速 SPA 开发

发表于 2018-08-28 | 分类于 工具链开发

【抛砖引玉】使用 ES6 的类继承加速 SPA 开发

背景

随着 ES6 和 SPA 框架的普及,前端开发已经在从以往的页面为主体向组件为主体进行改变,而组件的重用可以使用类继承来加速 SPA 开发。下面就是一个简单的继承实现的例子。

初始化项目

本文使用 angular6.0 + ng-zorro1.4 作为范例,首先初始化项目 ng-extents-components:

1
2
3
ng new ng-extents-component
cd ng-extents-component
ng add ng-zorro-antd

初始化完成后执行 ng serve 即可访问 http://localhost:4200 如图:

新增 LoginPageComponent 和 RegisterPageComponent 并配置路由

ng-zorro 为我们提供了基于 Schematics 的模板,我们可以直接选择适应的模板新建 LoginPageComponent 和 RegisterPageComponent :

1
2
ng g ng-zorro-antd:form-register -p app --styleext='scss' --name=register-page
ng g ng-zorro-antd:form-normal-login -p app --styleext='less' --name=login-page

新建完成后修改 src/app/app.module.ts 的 imports 字段,添加响应式表单支持并配置路由:

1
2
3
4
5
6
7
...
ReactiveFormsModule,
RouterModule.forRoot([
{path:'login',component:LoginPageComponent},
{path:'register',component:RegisterPageComponent}
]),
...

随后修改 src/app/app.module.ts 添加 router-outlet:

1
2
3
4
5
<!-- NG-ZORRO -->
<!--<a href="https://github.com/NG-ZORRO/ng-zorro-antd" target="_blank" style="display: flex;align-items: center;justify-content: center;height: 100%;width: 100%;">-->
<!--<img height="300" src="https://img.alicdn.com/tfs/TB1NvvIwTtYBeNjy1XdXXXXyVXa-89-131.svg">-->
<!--</a>-->
<router-outlet></router-outlet>

完成后即可访问 http://localhost:4200/login 和 http://localhost:4200/register 如图所示:


使 LoginPageComponent 继承基类 TablePageBasicComponent

LoginPageComponent 和 RegisterPageComponent 实际其本质均为响应式表单,登录和注册操作的本质均为提交表单,我们完全可以为两者抽象一个基类 TablePageBasicComponent 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import {Component} from '@angular/core';
import {FormBuilder, FormGroup} from "@angular/forms";
import {HttpClient} from "@angular/common/http";

@Component({
template: ''
})
export class TablePageBasicComponent {
validateForm: FormGroup; // 存放实例化后的 FormGroup
submitUrl: string = ''; // 存放 submit 方法的 url ,子类可以对其重写

constructor(public fb: FormBuilder, public http: HttpClient) { // 基类的构造函数,将所有的类依赖以 public 的方式注入,以供子类访问
}

ngOnInit(): void { // 基类 ngOnInit 方法
console.log('TablePageBasicComponent.ngOnInit()');
}

submitForm(): void { // 基类 submitForm 方法
console.log('TablePageBasicComponent.submitForm()');
console.log(this.validateForm.value);
this.http.post(this.submitUrl,this.validateForm.value).subscribe(resp => {
console.log(resp)
});
}
}

我们在子类使用时,只需要修改 submitUrl 即可对提交的 url 进行修改,但因为使用了响应式表单,我们还需在各个子类内重写 ngOnInit(): void。

下面使 LoginPageComponent 继承自 TablePageBasicComponent,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Component } from '@angular/core';
import {TablePageBasicComponent} from "../basic-components/table-page.basic.component";
import {Validators} from "@angular/forms";

@Component({
selector: 'app-login-page',
templateUrl: './login-page.component.html',
styleUrls: ['./login-page.component.less']
})
export class LoginPageComponent extends TablePageBasicComponent{
submitUrl='http://yapi.youhujia.com/mock/42/demo/login';

ngOnInit(): void {
super.ngOnInit();
console.log('LoginPageComponent.ngOnInit()');
this.validateForm = this.fb.group({
userName: [ null, [ Validators.required ] ],
password: [ null, [ Validators.required ] ],
remember: [ true ]
});
}
}

点击登录按钮后,我们可以看到以下输出:

1
2
3
4
table-page.basic.component.ts:16 TablePageBasicComponent.ngOnInit()
login-page.component.ts:15 LoginPageComponent.ngOnInit()
table-page.basic.component.ts:20 TablePageBasicComponent.submitForm()
table-page.basic.component.ts:21 {userName: "18000000000", password: "test", remember: true}

同样我们可以使 RegisterPageComponent 继承自 TablePageBasicComponent ,此处不详细实现,大家可以访问 Github 查看该项目

使用继承的价值

  • 抽象同类功能,比较有代表性的就是后台项目的列表、详情等页面
    为各类页面分别抽象基类,并可使同类页面使用统一模板,重写基类配置快速实现同类页面
  • 可快速统一修改基类的同一方法
    设想现项目有20个详情页,PM 飘过来跟你说当页面保存的时候给我加一个提示框让用户选择是否会到列表页,我们此时就可以修改基类的代码统一进行修改了
  • 可通过重写属性减少重复代码
    我们的 team 内最常用的就是通过 getDataService/sendDataService 属性来控制页面的获取/提交数据,可参考我之前的文章 从零实现 SPA 框架快速同步配置生成接口(angular2 + Easy-mock)

项目组内后台项正在使用的基类及项目结构

我们项目组内大量使用了各类组件基类的继承以简化各个组件的实现,下面仅作展现供参考讨论,不进行详细的展开。

项目组内正在使用的基类和对应功能

  • BasicComponent
    公共基类,管理所有基类共有的依赖、输入参数、事件等
  • PageBasicComponent
    页面基类,继承自 BasicComponent,管理所有页面的面包屑导航、弹框处理等
  • TablePageBasicComponent
    列表页基类,继承自 PageBasicComponent,管理所有列表页的数据加载、按钮控制等
  • CreatePageBasicComponent
    新建页基类,继承自 PageBasicComponent,管理所有新建页的数据提交、按钮控制等
  • DetailPageBasicComponent
    详情页基类,继承自 CreatePageBasicComponent,管理所有详情页的状态切换、数据提交、按钮控制等
  • TableViewBasicComponent
    列表组件基类,继承自 BasicComponent,通常嵌入到 TablePageBasicComponent,提供输入参数/数据接口/各类事件来管理业务数据的展示和事件触发
  • FormViewBasicComponent
    表单组件基类,继承自 BasicComponent,通常嵌入到 CreatePageBasicComponent 或 DetailPageBasicComponent,提供输入参数/数据接口/各类事件来管理业务数据的展示和事件触发
  • SelectorViewBasicComponent
    选择器组件基类,继承自 BasicComponent,通常嵌入到 FormViewBasicComponent,提供输入参数/数据接口/各类事件来管理业务数据的展示和事件触发

对应的项目结构

  • entites
    存放所有后端接口 Service ,参考我之前的文章 从零实现 SPA 框架快速同步配置生成接口(angular2 + Easy-mock)
  • pipes
    存放所有自定义 pipes
  • services
    存放所有自定义 services
  • basics
    存放所有基类
  • layouts
    存放所有业务无关的布局组件
  • views
    存放所有业务相关的 views 组件,通常继承自 TableViewBasicComponent/FormViewBasicComponent/SelectorViewBasicComponent
  • pages
    存放所有关联后端数据的 pages 组件,通常继承自 PageBasicComponent 的各子类

结语

本文只是抛砖引玉简单的实现了一个类继承的项目,而实际开发过程中还是需要结合需求对项目进行梳理。感兴趣的同学可在我的博客进行留言我会定期进行回复。

Xampp Access forbidden Error 403 处理

发表于 2018-08-28 | 分类于 mediawiki

背景

近期在研究搭建mediawiki,进入 /phpmyadmin 时提示了 Error 403 ,记录下修改过程

修改 /opt/lampp/etc/extra/httpd-xampp.conf 允许非 localhost 访问

打开配置文件 /opt/lampp/etc/extra/httpd-xampp.conf,查找 <Directory "/opt/lampp/phpmyadmin">,修改其内配置如下

1
2
3
4
5
6
7
8
# vi /opt/lampp/etc/extra/httpd-xampp.conf
...
AllowOverride AuthConfig Limit
# Require local
Allow from all
Require all granted
# ErrorDocument 403 /error/XAMPP_FORBIDDEN.html.var
...

完成后 lampp restart 即可

12…4
Dongsj

Dongsj

一个喜欢晒饲养员的程序猿

17 日志
7 分类
34 标签
GitHub E-Mail
© 2018 — 2019 Dongsj
Hosted by Coding Pages
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4