git init

1
2
git init // 在当前目录新建一个Git代码库
git init [project-name] // 新建一个目录,将其初始化为Git代码库

git clone

1
git clone [url] // 克隆一个项目和它的整个代码历史

git add

阅读全文 »

前提条件

Android4.4+

操作步骤

  1. 在 APP 中启用 WebView 调试,开启调试后,Chrome DevTools 才能对 WebView 进行远程调试;
    1
    WebView.setWebContentsDebuggingEnabled(true);
  2. 通过访问 chrome://inspect/#devices 访问已启用调试的 WebView 列表;
  3. 调试 Webview 与远程调试普通页面相同,远程调试普通页面也就是在安卓手机中安装 Chrome 浏览器,使用 USB 连接 PC,然后在 PC 的 Chrome 浏览器中打开 chrome://inspect/#devices 即可。

情境

有些 h5 项目有长按保存图片的需求,可以使用 html2canvas 来实现相对应的功能,
但是在项目中发现有的地方模糊不清,比如网页中有一张二维码的图片模糊,无法识别,

解决

后来发现,图片之类的不要当作背景图片,也就是直接放在 标签中即可,
解决了图片模糊的问题

分类常见问题处理方案
按钮类快速点击,造成数据重复提交增加 loading,防止重复点击
按钮类禁用按钮点击时只是样式禁用,仍可点击使用 disabled 属性禁用
1、重要业务增加 flag
2、建议后端增加防止重复提交
兼容类低版本手机不支持 css3 语法
position: fixed; left 和 top 值
添加适配内核如:–webkit
兼容类低版本手机不支持 ES6 语法ES6 语法使用时注意是否使用 babel 转换
兼容类部分手机不识别 font-weight: 500/600 (小米)换成 font-weight: bold
倒计时倒计时结束不刷新数据增加刷新
倒计时倒计时结束前后端刷新数据频率不一致导致前端频繁刷新(频繁刷新多次后,数据正常显示)倒计时结束后延迟刷新
倒计时在页面切换或者变化时未重置数据在页面切换或者变化时重置倒计时以及相关状态
文案展示类值为数值类型 0,显示默认值([*/–])值为 0 特殊判断,根据具体业务需求展示
数值计算当数值在进行减法或除法计算时,出现浮点数toFixed
样式类移动端 line-height 不居中line-height: normal;
display: flex;
align-item: center;
样式类移动端元素固定定位时,内部元素动态控制展示,在页面滚动时部分手机出现元素错位(元素跟随屏幕滚动)先将元素隐藏,内部元素动态判断加载出来之后在将固定定位元素展示出来
表单类表单未添加校验(如:必填,正则等)在一次开发写表单的时候顺手添加表单校验
表单类输入框输入过长输入框添加长度限制
图片图片过大图片在蓝湖/Figma 上下载后使用 TinyPNG 对图片进行二次压缩
图片当页面图片过多时导致页面卡顿图片懒加载、将图片换成背景图加载
分页加载连续点击导致加载数据异常问题,连续点击时,点击加载按钮状态还没变成加载中的时再次触发在接口请求中添加 loading 阻止请求
分页加载有多个 tab 切换时,分页数据异步返回导致数据错乱在接口返回中添加是否是当前数据判断
活动类异常情况全部活动状态判断考虑全部活动状态(有时候产品并不考虑全部),例如活动未开始时的异常情况
活动类异常情况活动默认展示【活动已结束】在接口请求数据回来之前展示 loading
时间格式化处理后台返回时间戳时间轴数据比当前月小一个月
getUTCMonth() 方法可返回一个表示月份的数字(按照世界时 UTC)。(一月份为 0, 二月份为 1, 以此类推)

项目目录

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
27
28
29
30
31
32
33
34
|-- public 静态资源目录(图片、文字等)
|-- images 全局共用的img图片目录
|-- js 存放JavaScript脚本
|-- css 全局公用的样式表
|-- index.html 首页入口文件(可添加meta 统计代码等)
|-- dist 打包项目
|-- [package] 项目包名
|-- node_modules npm 加载的项目依赖
|-- src
|-- assets
|-- config 项目配置文件(包括OSS地址等公共用常量配置文件)
|-- less.js css 中使用的全局变量,如服务器上的图片 OSS_PATH
|-- images 项目图片
|-- utils js文件(全局api,公共方法,组件注册)
|-- components 公共组件
|-- phjtWebTools 项目公共组件,方法库
|-- views 开发项目位置
|-- base
|-- [package]
|-- assets
|-- config 项目配置文件
|-- css 当前项目公共样式文件
|-- utils 当前项目公共js文件,如 vuex | Vue.observable 的注入
|-- views 开发主体页面
|-- router vue路由的目录
|-- store Vuex
|-- App.vue 项目文件入口
|-- main.js 项目的核心文件
|-- templates
|-- [package].html
|-- envs 根据具体项目的不同公共配置
|-- package.json 项目的配置信息(比如名称、版本、许可证等元数据)
|-- vue.config.js 项目配置文件
|-- README.md

修改 webpack 配置

代码如下
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// vue.config.js

const path = require("path");
require("babel-polyfill");
const fs = require("fs");
const CompressionWebpackPlugin = require("compression-webpack-plugin");
const argv = require("yargs").argv;

function resolve(dir) {
return path.join(__dirname, dir);
}
const PRODUCTION = process.env.NODE_ENV === "production";

// 0开发 1正式环境 2测试服务器
const serverType = PRODUCTION ? (argv["test_server"] ? 2 : 1) : 0;

// APP 版本号(影响打包路径)
const APP_VERSION = (process.env.VUE_APP_VERSION = process.env.npm_package_appVersion);

// H5 版本号(影响打包文件名)
const H5_VERSION = (process.env.VUE_APP_H5_VERSION = process.env.npm_package_version);

// H5 包名称(只有生产环境和单项目开发环境才有值)
process.env.VUE_APP_NAME = "";
process.env.VUE_APP_VERSION_CODE = "";

// 生产环境使用CDN加速域名,开发和测试环境使用OSS域名
process.env.VUE_APP_HOST = serverType == 1 ? process.env.VUE_APP_CDN_HOST : process.env.VUE_APP_OSS_HOST;

let lessVars = require("./src/assets/config/less");
lessVars["TEST_SERVICE"] = process.env.VUE_APP_TEST_SERVICE = serverType; // css 中使用的全局变量,如服务器上的图片 OSS_PATH

console.log(">>>>>>>> 运行环境 ", ["开发环境", "正式环境", "测试服务器"][serverType]);

// 获取所有多入口文件
const getPages = (arr = [], toName) => {
const dirs = fs.readdirSync("./src/views");
let pages = {};
for (const name of dirs) {
if (arr.length > 0 && !arr.includes(name)) continue;
let temPath = `templates/${name}.html`;
let entryPath = `src/views/${name}/main.js`;
let newName = name;
if (arr.length == 1 && toName) {
newName = toName;
}
pages[newName] = {
entry: entryPath, // 入口
template: fs.existsSync(temPath) ? temPath : "public/index.html",
filename: PRODUCTION ? `index.html` : `${newName}.html`,
title: newName,
// 在这个页面中包含的块,默认情况下会包含,提取出来的通用 chunk 和 vendor chunk。
chunks: ["chunk-vendors", "chunk-common", newName]
};
}
return pages;
};
let proPathName = PRODUCTION ? argv["test_server"] || process.argv[3] : "";
let proNewName = (argv["test_server"] ? process.argv[5] : process.argv[4]) || proPathName;

console.log(process.argv);
let devPathName = "";
let page = {};
if (!PRODUCTION) {
if (argv["project"] && argv["project"] !== true) {
devPathName = String(argv["project"]).trim().split(",");
console.log(">>>>>>>> 运行项目 ", devPathName);
page = getPages(devPathName);
if (devPathName.length == 1) {
process.env.VUE_APP_NAME = devPathName[0];
process.env.VUE_APP_VERSION_CODE = require(`./src/views/${devPathName[0]}/assets/config/index`).versionCode;
}
} else {
console.log(">>>>>>>> 运行项目 ", "全部");
page = getPages();
}
} else if (proPathName) {
proPathName = proPathName.replace(/[^a-zA-Z|_\d]/gi, "");
proNewName = proNewName.replace(/[^a-zA-Z|_\d]/gi, "");
console.log(">>>>>>>> 打包项目 ", proPathName, proNewName);
process.env.VUE_APP_NAME = proNewName;
process.env.VUE_APP_VERSION_CODE = require(`./src/views/${proPathName}/assets/config/index`).versionCode;
page = getPages([proPathName], proNewName);
} else {
throw new Error("未写项目包名");
}

let cssOptions = PRODUCTION
? {
extract: {
filename: `css/[name]-${H5_VERSION}-[hash:8].css`,
chunkFilename: `css/[name]-${H5_VERSION}-[hash:8].css`
}
}
: {};
module.exports = {
publicPath: !PRODUCTION ? "/m" : "./",
outputDir: "./dist/" + proNewName,
filenameHashing: true,
pages: page,
productionSourceMap: false, // 生产环境 sourceMap
devServer: {
openPage: `${proNewName || ""}.html`,
open: true, // 项目构建成功之后,自动弹出页面
host: "0.0.0.0", // 主机名,也可以127.0.0.0 || 做真机测试时候0.0.0.0
port: 8082, // 端口号,默认8080
https: false, // 协议
hotOnly: false, // 没啥效果,热模块,webpack已经做好了
proxy: {
"/api": {
// target: "https://test-mall-web.peogoo.com",
target: "http://10.20.39.122:8082",
changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
pathRewrite: {
"^/api": ""
}
}
// "/foo": {
// target: "<other_url>"
// }
} // 配置多个代理
},
// 配置less全局变量
css: {
loaderOptions: {
less: {
globalVars: lessVars
}
},
...cssOptions
},
configureWebpack: (config) => {
// ES6转为ES5
if (PRODUCTION && proPathName) {
config.entry[proNewName] = ["babel-polyfill", ...config.entry[proNewName]];
config.output.filename = `js/[name]-${H5_VERSION}-[hash:8].js`;
config.output.chunkFilename = `js/[name]-${H5_VERSION}-[hash:8].js`;
}

let plugins = [];
config.plugins.push(
new CompressionWebpackPlugin({
// 正在匹配需要压缩的文件后缀
test: /\.(js|css|svg|woff|ttf|json|html)$/,
// 大于10kb的会压缩
threshold: 10240
// 其余配置查看compression-webpack-plugin
})
);
// 生成环境且非测试服务器需要移除console和debugger
if (serverType == 1) {
console.log(`>>>>>>>> 移除console和debugger`);
config.plugins = [...config.plugins, ...plugins];
config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true; // 删除console
config.optimization.minimizer[0].options.terserOptions.compress.drop_debugger = true; // 删除deubgger
}
},
chainWebpack: (config) => {
//忽略的打包文件
config.externals({
"animate.css": "animate.css",
vue: "Vue",
"vue-router": "VueRouter",
vuex: "Vuex",
axios: "axios",
moment: "moment",
vant: "vant",
"vue-clipboard2": "vue-clipboard2"
});

// 别名
config.resolve.alias
.set("@", resolve("src"))
.set("@assets", resolve("src/assets"))
.set("@views", resolve("src/views"))
.set("@components", resolve("src/components"))
.set("@phjtWebTools", resolve("src/phjtWebTools"))
.set("@utils", resolve("src/assets/utils"));

config.module
.rule("svg")
.use("file-loader")
.loader("file-loader")
.options({ name: `img/[name]-${H5_VERSION}-[hash:8].[ext]` });

// 打包文件格式
config.module
.rule("images")
.test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
.use("url-loader")
.loader("url-loader")
.tap((options) => {
options.fallback.options.name = `img/[name]-${H5_VERSION}-[hash:8].[ext]`;
options.limit = 5120; //这是字节(Byte)限制,1KB = 1024Byte。
return options;
});

// 为了补删除换行而加的配置
config.module
.rule("vue")
.use("vue-loader")
.loader("vue-loader")
.tap((options) => {
options.compilerOptions.preserveWhitespace = true;
return options;
});
}
};

package.json

阅读全文 »

背景

项目中经常使用别人维护的模块,在 git 中使用子模块的功能能够大大提高开发效率。

使用子模块后,不必负责子模块的维护,只需要在必要的时候同步更新子模块即可。

本文主要讲解子模块相关的基础命令,详细使用请参考 man page。

子模块的添加

添加子模块非常简单,命令如下:

1
git submodule add <url> <path>
阅读全文 »

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import Vue from "vue";
import LoadingComponent from "./loading.vue";
// 使用 Vue.extend构造组件子类
const LoadingContructor = Vue.extend(LoadingComponent);

// 定义一个名为loading的指令
Vue.directive("loading", {
/**
* 只调用一次,在指令第一次绑定到元素时调用,可以在这里做一些初始化的设置
* @param {*} el 指令要绑定的元素
* @param {*} binding 指令传入的信息,包括 {name:'指令名称', value: '指令绑定的值',arg: '指令参数 v-bind:text 对应 text'}
*/
bind(el, binding) {
const instance = new LoadingContructor({
el: document.createElement("div"),
data: {}
});
el.appendChild(instance.$el);
el.instance = instance;
Vue.nextTick(() => {
el.instance.visible = binding.value;
});
},
/**
* 所在组件的 VNode 更新时调用
* @param {*} el
* @param {*} binding
*/
update(el, binding) {
// 通过对比值的变化判断loading是否显示
if (binding.oldValue !== binding.value) {
el.instance.visible = binding.value;
}
},
/**
* 只调用一次,在 指令与元素解绑时调用
* @param {*} el
*/
unbind(el) {
const mask = el.instance.$el;
if (mask.parentNode) {
mask.parentNode.removeChild(mask);
}
el.instance.$destroy();
el.instance = undefined;
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<van-loading v-if="visible" />
</template>
<script>
import { Loading } from "vant";
export default {
data() {
return {
visible: false
};
},
components: {
[Loading.name]: Loading
}
};
</script>

fsfilesystem的缩写,该模块提供本地文件的读写能力,基本上是 POSIX 文件操作命令的简单包装。但是,这个模块几乎对所有操作提供异步和同步两种操作方式,供开发者选择。

readFile(),readFileSync()

readFile方法用于异步读取数据。

1
2
3
4
fs.readFile("./image.png", function (err, buffer) {
if (err) throw err;
process(buffer);
});

readFile方法的第一个参数是文件的路径,可以是绝对路径,也可以是相对路径。注意,如果是相对路径,是相对于当前进程所在的路径(process.cwd()),而不是相对于当前脚本所在的路径。

阅读全文 »

概述

Express 是目前最流行的基于 Node.js 的 Web 开发框架,可以快速地搭建一个完整功能的网站。

Express 上手非常简单,首先新建一个项目目录,假定叫做 hello-world。

1
mkdir hello-world

进入该目录,新建一个 package.json 文件,内容如下。

阅读全文 »