UNI API

保存图片到系统相册

UNI

uni.saveImageToPhotosAlbum

语法

1
uni.saveImageToPhotosAlbum(OBJECT);

平台差异说明

AppH5微信小程序支付宝小程序百度小程序抖音小程序、飞书小程序QQ 小程序快手小程序京东小程序
x

OBJECT 参数说明

参数名类型必填说明
filePathString图片文件路径,可以是临时文件路径也可以是永久文件路径,不支持网络图片路径
successFunction接口调用成功的回调函数
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

success 返回参数说明

参数名类型说明
pathString保存到相册的图片路径,仅 App 3.0.5+ 支持
errMsgString调用结果

示例

1
2
3
4
5
6
7
8
9
10
11
12
uni.chooseImage({
count: 1,
sourceType: ["camera"],
success: function (res) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePaths[0],
success: function () {
console.log("save success");
}
});
}
});

PLUS

H5 端可以使用 plus api 来将图片保存到相册
plus.gallery.save

语法

1
plus.gallery.save(path, successCB, errorCB);

条件编译调用 HTML5+

在 uni-app 调用 HTML5+ 的扩展规范时,需要注意使用条件编译。否则运行到 h5、小程序等平台会出现 plus is not defined错误。

1
2
3
4
// #ifdef APP-PLUS
var appid = plus.runtime.appid;
console.log("应用的 appid 为:" + appid);
// #endif

说明

保存文件到系统相册中。 每次仅能保存一个文件,支持图片文件(jpg/jpeg、png、bmp 等格式)和视频文件(3gp、mov 等格式)。 若保存的文件类型当前系统不支持,则通过 errorCB 回调返回错误信息。

参数说明

参数名类型必填说明
pathString要保存到系统相册中的文件文件地址
succesCBFunction保存文件到系统相册中成功的回调函数
errorCBFunction保存文件到系统相册中失败的回调函数

平台支持

Android - 2.2+ (支持)
iOS - 4.3+ (支持)

示例

1
2
3
4
5
6
// 保存图片到相册中
function savePicture() {
plus.gallery.save("_doc/a.jpg", function () {
alert("保存图片到相册成功");
});
}

从相册中选择图片或视频文件

UNI

选择图片

从本地相册选择图片或使用相机拍照
uni.chooseImage

1
uni.chooseImage(OBJECT);

参数说明

OBJECT 参数说明

参数名类型必填说明平台差异说明
countNumber最多可以选择的图片张数,默认 9见下方说明
sizeTypeArrayoriginal 原图,compressed 压缩图,默认二者都有App、微信小程序、支付宝小程序、百度小程序
extensionArray根据文件拓展名过滤,每一项都不能是空字符串。默认不过滤。H5(HBuilder X2.9.9+)
sourceTypeArrayalbum 从相册选图,camera 使用相机,默认二者都有。如需直接开相机或直接选相册,请只使用一个选项
cropObject图像裁剪参数,设置后 sizeType 失效App 3.1.19+
successFunction成功则返回图片的本地文件路径列表 tempFilePaths
failFunction接口调用失败的回调函数小程序、App
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

crop 参数说明

参数名类型必填说明
qualityNumber取值范围为 1-100,数值越小,质量越低(仅对 jpg 格式有效)。默认值为 80。
widthNumber裁剪的宽度,单位为 px,用于计算裁剪宽高比。
heightNumber裁剪的高度,单位为 px,用于计算裁剪宽高比。
resizeBoolean是否将 width 和 height 作为裁剪保存图片真实的像素值。默认值为 true。注:设置为 false 时在裁剪编辑界面显示图片的像素值,设置为 true 时不显示

success 返回参数说明

参数类型说明
tempFilePathsArray图片的本地文件路径列表
tempFilesArray、Array图片的本地文件列表,每一项是一个 File 对象

File 对象结构如下

参数类型说明
pathString本地文件路径
sizeNumber本地文件大小,单位:B
nameString包含扩展名的文件名称,仅 H5 支持
typeString文件类型,仅 H5 支持

示例

1
2
3
4
5
6
7
8
uni.chooseImage({
count: 6, //默认9
sizeType: ["original", "compressed"], //可以指定是原图还是压缩图,默认二者都有
sourceType: ["album"], //从相册选择
success(res) {
console.log(res.tempFilePaths);
}
});

选择视频

拍摄视频或从手机相册中选视频,返回视频的临时文件路径
uni.chooseVideo

1
uni.chooseVideo(OBJECT);

参数说明

OBJECT 参数说明

参数名类型必填说明平台差异说明
sourceTypeArrayalbum 从相册选视频,camera 使用相机拍摄,默认为:[‘album’, ‘camera’]
extensionArray根据文件拓展名过滤,每一项都不能是空字符串。默认不过滤。H5(HBuilder X 2.9.9+)
compressedBoolean是否压缩所选的视频源文件,默认值为 true,需要压缩。微信小程序、百度小程序、抖音小程序、飞书小程序、京东小程序、App(HBuilder X 3.2.7+)
maxDurationNumber拍摄视频最长拍摄时间,单位秒。最长支持 60 秒。APP 平台 1.9.7+(iOS 支持,Android 取决于 ROM 的拍照组件是否实现此功能,如果没实现此功能则忽略此属性。) 微信小程序、百度小程序、京东小程序
cameraString‘front’、’back’,默认’back’APP、微信小程序、京东小程序
successFunction接口调用成功,返回视频文件的临时文件路径,详见返回参数说明。
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

success 返回参数说明

参数类型说明平台差异
tempFilePathString选定视频的临时文件路径
tempFileFile选定的视频文件仅 H5(2.6.15+)支持
durationNumber选定视频的时间长度,单位为 sAPP 2.1.0+、H5、微信小程序、京东小程序
sizeNumber选定视频的数据量大小APP 2.1.0+、H5、微信小程序、京东小程序
heightNumber返回选定视频的高APP 2.1.0+、H5、微信小程序、京东小程序
widthNumber返回选定视频的宽APP 2.1.0+、H5、微信小程序、京东小程序
nameString包含扩展名的文件名称仅 H5 支持

PLUS

plus.gallery.pick

语法

1
plus.gallery.pick(successCB, errorCB, options);

参数说明

参数名类型必填说明
optionsGalleryOptions设置选择文件的参数
succesCBFunction保存文件到系统相册中成功的回调函数
errorCBFunction保存文件到系统相册中失败的回调函数
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
interface GalleryOptions {
// 是否显示系统相册文件选择界面的动画
animation: Boolean;
// 确认按钮文字
confirmText: String;
// 是否支持编辑图片
crop: GalleryCropStyles;
// 选择文件保存的路径
editable: Boolean;
// 最多选择的图片数量
filename: String;
// 是否支持多选图片
filter: GalleryFilter;
// 是否检测权限
maximum: Number;
// 已选择的图片路径列表
multiple: Boolean;
// 是否使用系统相册文件选择界面
permissionAlert: Boolean;
// 超过最多选择图片数量事件
popover: PopPosition;
// 配置裁剪图片
selected: Array<String>;
// 相册中选择文件类型过滤器
system: Boolean;
// 相册选择界面弹出指示区域
onmaxed: Function;
}

interface GalleryCropStyles {
// 裁剪后保存图片的质量:取值范围为1-100,数值越小,质量越低(仅对jpg格式有效)。 默认值为80。
quality: Number;
// 裁剪的宽度: 单位为px,用于计算裁剪宽高比。 必须设置此值。
width: Number;
// 裁剪的高度: 单位为px,用于计算裁剪宽高比。 必须设置此值。
height: Number;
// 是否将图片保存为指定的宽高像素: true表示将width和height作为裁剪保存图片的像素值,false表示使用图片编辑操作的真实像素值。
// 默认值为true。设置为false时在裁剪编辑界面显示图片的像素值,设置为true时不显示。
resize: Boolean;
// 裁剪后的图片是否保存到相册中: true表示将裁剪后的图片保存到相册中,false表示不将裁剪后的图片保存到相册中。HBuilder3.3.0+版本支持。
// 默认值为false。
saveToAlbum: Boolean;
}

type GalleryFilter =
| "image" /* 仅可选择图片文件 */
| "video" /* 仅可选择视频文件 */
| "none"; /* 不过滤,可选择图片或视频文件 */

interface PopPosition {
// 指示区域距离容器顶部的距离: 弹出拍照或摄像窗口指示区域距离容器顶部的距离,单位支持像素值(如"100px")和百分比(如"50%"),如不写单位则为像素值值。
top: String;
// 指示区域距离容器左侧的距离: 弹出拍照或摄像窗口指示区域距离容器左侧的距离,单位支持像素值(如"100px")和百分比(如"50%"),如不写单位则为像素值。
left: String;
// 指示区域的宽度: 弹出拍照或摄像窗口指示区域的宽度,单位支持像素值(如"100px")和百分比(如"50%"),如不写单位则为像素值。
width: String;
// 指示区域的高度: 弹出拍照或摄像窗口指示区域的高度,单位支持像素值(如"100px")和百分比(如"50%"),如不写单位则为像素值。
height: String;
}

平台支持

Android - 2.2+ (支持)
iOS - 5.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
35
36
37
38
39
40
41
42
43
export function galleryF(list) {
if (!list.length) return;

const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i;
const VIDEO_REGEXP =
/\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i;

return list.map((url) => {
return {
url,
type: IMAGE_REGEXP.test(url)
? "image"
: VIDEO_REGEXP.test(url)
? "video"
: "",
name: url.slice(url.lastIndexOf("/") + 1)
};
});
}

export function gallery(options = {}) {
const { multiple = true, maximum = 9, filter = "none" } = options;

return new Promise((resolve, reject) => {
plus.gallery.pick(
(path) => {
resolve(galleryF(path.files));
},
(error = "取消选择") => {
reject(error);
},
{
multiple,
maximum,
filter,
permissionAlert: true,
onmaxed() {
plus.nativeUI.alert(`最多只能选择${9}张`);
}
}
);
});
}

获取父组件的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 获取父组件的参数
* 因为支付宝小程序不支持provide/inject的写法
this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx
这里默认值等于undefined有它的含义,因为最顶层元素(组件)的$parent就是undefined,意味着不传name
值(默认为undefined),就是查找最顶层的$parent
* @param {string|undefined} name 父组件的参数名
*/
export function $parent(name = undefined) {
let parent = this.$parent;
// 通过while历遍,这里主要是为了H5需要多层解析的问题
while (parent) {
// 父组件
if (parent.$options && parent.$options.name !== name) {
// 如果组件的name不相等,继续上一级寻找
parent = parent.$parent;
} else {
return parent;
}
}
return false;
}

获取目标元素的节点布局信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 节点布局信息
* @param selector <String> 此参数为元素节点,可以是id或者class,比如"#user-name",".box"
* @param all <Boolean> 是否返回全部节点信息,当页面有多个相同selector的元素时,all为true,会以数组形式返回所有节点的信息(结果为数组,数组元素为对象),否则只返回第一个节点的信息(结果为一个对象)
* @return res = {left: 0,right: 414,top: 323,height: 2597,bottom: 2920,width: 414}
*/
function uGetRect(selector, all) {
return new Promise((resolve) => {
uni
.createSelectorQuery()
.in(this)
[all ? "selectAll" : "select"](selector)
.boundingClientRect((rect) => {
if (all && Array.isArray(rect) && rect.length) {
resolve(rect);
}
if (!all && rect) {
resolve(rect);
}
})
.exec();
});
}

获取用户传递值的 px 值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const isNumber = (v) => /^[+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(v);

/**
* @description 用于获取用户传递值的px值 如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.upx2px进行转换
* @param {number|string} value 用户传递值的px值
* @param {boolean} unit
* @returns {number|string}
*/
function getPx(value, unit = false) {
if (isNumber(value)) {
return unit ? `${value}px` : Number(value);
}
// 如果带有rpx,先取出其数值部分,再转为px值
if (/(rpx|upx)$/.test(value)) {
return unit
? `${uni.upx2px(parseInt(value))}px`
: Number(uni.upx2px(parseInt(value)));
}
return unit ? `${parseInt(value)}px` : parseInt(value);
}

获取图片信息

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
/**
* 获取图片信息
* @param {Object} filepath
*/
function getImageInfo(filepath) {
return new Promise(function (resolve, reject) {
// #ifdef APP-PLUS
plus.io.getImageInfo({
src: filepath,
success: function (image) {
// console.log('orientation=' + image.orientation);
resolve(image);
},
fail: function (err) {
console.log("getImageInfoErr: " + JSON.stringify(err));
reject(err);
}
});
// #endif

// #ifdef H5 || MP-WEIXIN
uni.getImageInfo({
src: filepath,
success: function (image) {
// console.log('orientation=' + image.orientation);
resolve(image);
},
fail: function (err) {
console.log("getImageInfoErr: " + JSON.stringify(err));
reject(err);
}
});
// #endif
});
}

IO 操作

写入文件内容

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
/**
* 写入文件内容
* @param {String} filePath 文件路径,支持 a/b/file.txt
* @param {String} content 文件存在时是否重写
* @param {Number} [position] 写入日志的位置 0开始 默认结尾
* @returns {Promise}
*/
export function fileWriter(filePath, content, position) {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
// 请求本地系统文件对象 plus.io.PRIVATE_DOC:应用私有文档目录常量
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, function (fs) {
// fs.root是根目录操作对象DirectoryEntry
fs.root.getFile(
filePath,
{
create: true
},
function (fileEntry) {
fileEntry.createWriter(
function (writer) {
writer.seek(position == undefined ? writer.length : position);
writer.write(content);
// console.log("写入本地文件日志", writer.fileName);
// console.log(content);
resolve(writer);
},
function (e) {
console.error("本地文件日志写入异常", e);
reject();
}
);
}
);
});
// #endif
// #ifdef H5
resolve();
// #endif
});
}

读取文件内容

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
/**
* 读取文件内容
* @param {String} filePath 文件路径,支持 a/b/file.txt
* @returns {Promise}
*/
export function fileReader(filePath) {
return new Promise((resolve, reject) => {
// 请求本地系统文件对象 plus.io.PRIVATE_DOC 应用私有文档目录常量
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, function (fs) {
// fs.root是根目录操作对象DirectoryEntry
fs.root.getFile(filePath, {}, function (fileEntry) {
fileEntry.file(function (file) {
let fileReader = new plus.io.FileReader();
fileReader.readAsText(file, "utf-8");
fileReader.onloadend = function (evt) {
const { result: content, error } = evt?.target || {};
if (error) {
reject();
} else {
resolve({
...file,
content
});
}
};
});
});
});
});
}

读取目录文件列表

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
/**
* 读取目录文件列表
* @param {String} filePath 目录
* @returns {Promise}
*/
export function fileList(filePath) {
return new Promise((resolve, reject) => {
// 请求本地系统文件对象 plus.io.PRIVATE_DOC:应用私有文档目录常量
plus.io.resolveLocalFileSystemURL(
filePath, //指定的目录
(entry) => {
var directoryReader = entry.createReader(); //获取读取目录对象
directoryReader.readEntries(
(entries) => {
resolve(entries);
},
(err) => {
reject("读取失败");
console.error("读取本地目录", filePath, err);
}
);
},
(err) => {
reject("读取失败");
console.error("读取本地目录", filePath, err);
}
);
});
}

读取目录文件列表

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
/**
* 读取目录文件列表(递归)
* @param {String} path 目录
* @returns {Promise}
*/
export async function fileListDeep(path) {
return new Promise(async (resolve) => {
let realFilesObj = {};
await innerCollect(path);
async function innerCollect(path) {
let realFiles = [];
// 获取logs路径下的所有文件夹
let logFolders = await fileList(`${path}`);
for (let i = 0; i < logFolders.length; i++) {
const file = logFolders[i];
if (file.isDirectory) {
await innerCollect(file.toURL());
} else {
realFiles.push(file);
}
}
realFilesObj[path] = realFiles;
return;
}
resolve(realFilesObj);
});
}

压缩目录

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
/**
* 压缩目录
* @param {String} [targetPath] 目录
* @param {String} [name] 目录
* @returns {Promise}
*/
export function zipPath(targetPath = "_doc/", name) {
return new Promise((resolve, reject) => {
let zipfile = `_documents/${
name || targetPath.replace(/_/g, "").replace(/\//g, "_")
}${timeF("MMddhhmmssS")}.zip`;
plus.zip.compress(
targetPath,
zipfile,
() => {
resolve({
filePath: plus.io.convertLocalFileSystemURL(zipfile),
localFile: zipfile
});
},
(error) => {
reject(error);
}
);
});
}

删除单个文件或目录

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
/**
* 删除单个文件或目录
* @param {String} filePath 目录
* @returns {Promise}
*/
export function removeFile(filePath, singleFile = false) {
return new Promise((resolve, reject) => {
plus.io.resolveLocalFileSystemURL(
filePath,
(entry) => {
// 可通过entry对象操作test.html文件
/**
* entry.remove(succesCB, errorCB);
* 以下情况删除目录将会导致失败: 目录中存在文件; 删除根目录; 删除目录成功通过succesCB回调返回,失败则通过errorCB返回。
*/

/**
* entry.removeRecursively(succesCB, errorCB);
* 删除目录将会删除其下的所有文件及子目录 不能删除根目录,如果操作删除根目录将会删除目录下的文件及子目录,不会删除根目录自身。 删除目录成功通过succesCB回调返回,失败则通过errorCB返回。
*/
entry[!singleFile ? "removeRecursively" : "remove"](
() => {
console.log("success");
resolve();
},
() => {
console.log("error");
reject();
}
);
},
() => {
reject();
}
);
});
}

按条件删除文件

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
/**
* 按条件删除文件
* @param {Array} files 文件数组
* @param {Object} [options] 删除条件
* @param {Function} [options.filter] 文件处理函数,返回{ del: 是否直接删除, sort: (不直接删除)文件权重 }
* @param {Number} [options.retainMin] 至少保留的文件数量
* @param {Number} [options.sortMax] 条件权重的最大值
*/
export function removeFileByOptions(files, options) {
if (!options) {
return removeFiles(files);
} else {
const {
filter = (el) => ({ del: true }),
retainMin = 0,
sortMax = 0
} = options;
return files
.map((file) => {
// del=true 直接删除文件
// del=false保留文件,然后进行sort排序后删除sort小于sortMax的文件,但至少保留retainMin个文件
const { sort, del = false } = filter(file);
if (del) {
file.remove();
}
return { file, sort, del };
})
.filter((item) => !item.del)
.sort((a, b) => a.sort - b.sort)
.filter((item, index, self) => {
if (item.sort < sortMax && index < self.length - retainMin) {
item.file.remove();
return false;
}
return true;
});
}
}

删除多个文件或目录

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
/**
* 删除多个文件或目录
* @param {Array} [files] 文件数组
* @returns
*/
export function removeFiles(files = []) {
return new Promise((resolve, reject) => {
let filesLen = files?.length ?? 0;
if (!filesLen) {
resolve();
return;
}
for (let i = 0; i < filesLen; i++) {
const file = files[i];
file.removeRecursively(
() => {
filesLen--;
if (filesLen === 0) {
resolve();
}
},
(err) => {
console.error("remove failed", file.name, err);
reject(`删除失败:${err}`);
}
);
}
});
}

未完,待续。。。