主题
二次封装能力
使用文件系统
js
// file-storage.js
const isAlipay = uni.getSystemInfoSync().platform === 'alipay';
// 获取文件系统管理器
function getFileSystemManager() {
return isAlipay ? my.getFileSystemManager() : uni.getFileSystemManager();
}
// 获取用户数据路径
function getUserDataPath() {
return isAlipay ? my.env.USER_DATA_PATH : wx.env.USER_DATA_PATH;
}
// 写入文件
export function writeFile(fileName, data) {
const fs = getFileSystemManager();
const filePath = `${getUserDataPath()}/${fileName}`;
const content = typeof data === 'string' ? data : JSON.stringify(data);
return new Promise((resolve, reject) => {
fs.writeFile({
filePath,
data: content,
encoding: 'utf8',
success: resolve,
fail: reject,
});
});
}
// 读取文件
export function readFile(fileName) {
const fs = getFileSystemManager();
const filePath = `${getUserDataPath()}/${fileName}`;
return new Promise((resolve, reject) => {
fs.readFile({
filePath,
encoding: 'utf8',
success(res) {
try {
resolve(JSON.parse(res.data));
} catch (e) {
resolve(res.data);
}
},
fail: reject,
});
});
}
// 检查文件是否存在
export function access(fileName) {
const fs = getFileSystemManager();
const filePath = `${getUserDataPath()}/${fileName}`;
return new Promise((resolve) => {
fs.access({
path: filePath,
success: () => resolve(true),
fail: () => resolve(false),
});
});
}js
let res;
try {
// 获取页面数据
res = await this.$Req.getApp('/app/getPageInfo', params, undefined, undefined, 3000);
uni.stopPullDownRefresh();
if (uni.getStorageSync('homeStorage')) {
console.error('模拟获取页面接口失败');
throw new Error('获取页面接口失败');
}
// 对页面进行赋值
if (res.success) {
// 备份数据
res.source = 'interface';
await writeFile(this.fileName, res);
}
} catch (err) {
const isAccess = await access(this.fileName);
if (isAccess) {
res = await readFile(this.fileName);
res.source = 'storage';
}
} finally {
console.log(res, 'pageData');
if (res.success) {
this.pageInfo = res.data.page;
this.fileHost = res.data.fileHost;
this.propList = res.data.propList;
this.style = res.data.style;
}
await uni.vuex('vuexHomeSource', res.source);
}获取扫码参数
js
import MztUtils from '@/utils/mzt-utils';
class HandleFunc {
/**
* 获取微信、支付宝扫码参数并进行处理。
* 提取出 URL 后,会进一步解析出 query 参数对象,并通过回调返回。
*
* @param {Object} options - 小程序页面 onLoad(options) 中的参数对象。
* @param {Function} [callback] - 处理解析后参数的回调函数。
* @param {string} callback.url - 扫码跳转的完整 URL。
* @param {Object} callback.paramsObj - 从 URL 中解析出的参数对象(key-value)。
*/
static scanParams(options, callback) {
if (MztUtils.isEmpty(options)) {
return;
}
let url;
let paramsObj;
// #ifdef MP-ALIPAY
if (options.query && MztUtils.isNotEmpty(options.query.qrCode)) {
url = options.query.qrCode;
}
// #endif
// #ifdef MP-WEIXIN
if (MztUtils.isNotEmpty(options.q)) {
url = decodeURIComponent(options.q);
}
// #endif
if (MztUtils.isNotEmpty(url)) {
paramsObj = MztUtils.getQueryParams(url);
callback && callback(url, paramsObj);
}
}
/**
* 获取全局扫码参数
*/
static getLaunchOptions() {
let options;
try {
// #ifdef MP-ALIPAY
options = my.getLaunchOptionsSync();
// #endif
// #ifdef MP-WEIXIN
options = wx.getLaunchOptionsSync()?.query;
// #endif
return options;
} catch (e) {
console.log('获取扫码参数错误');
return null;
}
}
}
export default HandleFunc;js
onLoad();
{
// 获取全局扫码参数
let scan_options = HandleFunc.getLaunchOptions();
console.log(scan_options, 'scan_options');
try {
HandleFunc.scanParams(scan_options, (url, obj) => {
console.log(url, obj);
this.qrCodeId = obj.qrCodeId || '';
});
} catch (e) {
uni.$u.toast('获取扫码数据失败');
}
}判断是否开启相机权限
js
/**
* 判断是否开启系统摄像头权限
* @returns {Promise<unknown>}
*/
export function hasCameraAccess() {
return new Promise((resolve, reject) => {
// #ifdef MP-ALIPAY
my.getSystemInfo({
success: (res) => {
resolve(res.cameraAuthorized);
},
});
// #endif
// #ifdef MP-WEIXIN
uni.authorize({
scope: 'scope.camera',
success: () => {
resolve(true);
},
fail: () => {
resolve(false);
},
});
// #endif
});
}
/**
* 展示系统设置弹窗
*/
export function showCameraAuthGuide() {
uniApi.showCustomModal(
{
msg: '检测到您未开启相机权限,该服务需通过人脸核验确保账户安全。请开启相机使用权限',
confirmText: '去设置',
},
() => {
// #ifdef MP-WEIXIN
uni.openSetting({
success(settingRes) {
if (settingRes.authSetting['scope.camera']) {
console.log('用户已授权摄像头权限');
} else {
console.log('用户未授权摄像头权限');
}
},
});
// #endif
// #ifdef MP-ALIPAY
my.showAuthGuide({
authType: 'CAMERA',
success: (res) => {
console.log('用户已授权摄像头权限');
},
fail: (error) => {
console.log('用户未授权摄像头权限');
},
});
// #endif
}
);
}js
import {hasCameraAccess} from "@/utils/camera_permission.js";
// 判断是否有相机权限
const isCamerAccess = await hasCameraAccess();
if (!isCamerAccess))
{
// 开启提醒
showCameraAuthGuide();
}工具方法
js
let uniApi = {
/**
* 通用对话框
* @param options
* @param confirmback
* @param cancelCallback
*/
showCustomModal(options, confirmback, cancelCallback = null) {
uni.showModal({
title: options.title || '温馨提示',
content: options.msg || '',
confirmText: options.confirmText || '确认',
cancelText: options.cancelText || '取消',
showCancel: options.showCancel !== undefined ? options.showCancel : true,
success: (res) => {
if (res.confirm) {
confirmback && confirmback();
} else if (res.cancel) {
cancelCallback && cancelCallback();
}
},
});
},
// 通用跳转
router(url, type = 'navigateTo') {
uni[type]({ url });
},
/**
* 获取胶囊信息
* @returns {Object}
*/
getMenuButtonInfo() {
const menuButtonInfo = uni.getMenuButtonBoundingClientRect();
return {
width: menuButtonInfo.width,
top: menuButtonInfo.top,
height: menuButtonInfo.height,
};
},
/**
* 获取小程序当前版本号
* @returns {Promise<unknown>}
*/
getMiniVersion() {
return new Promise(async (resolve, reject) => {
let version = '';
const systemInfo = await getSystemInfo();
version = systemInfo.appVersion;
const accountInfo = uni.getAccountInfoSync();
if (accountInfo.miniProgram.version) {
version = accountInfo.miniProgram.version;
}
resolve(version);
});
},
/**
* 获取当前平台的版本
* @returns {Promise<unknown>}
*/
async getEnvVersion() {
return new Promise(async (resolve, reject) => {
let dict = {
release: '正式版',
trial: '体验版',
develop: '开发版',
};
try {
// #ifdef MP-WEIXIN
resolve({
v: __wxConfig.envVersion,
d: dict[__wxConfig.envVersion],
s: 'wechat',
});
// #endif
// #ifdef MP-ALIPAY
const res = await my.getRunScene();
resolve({
v: res.envVersion,
d: dict[res.envVersion],
s: 'alipay',
});
// #endif
} catch (error) {
reject(error);
}
});
},
};
export default uniApi;选择系统相册并上传
js
/**
* 选择系统相册
* @param {Function} callback
* @param {Object} params
*/
function selectSystemPhoto(callback, params) {
uni.chooseImage({
...params,
count: params?.count ?? 1,
sizeType: params?.sizeType ?? ['original', 'compressed'],
sourceType: params?.sourceType ?? ['album'],
success: function (res) {
callback && callback(res);
},
});
}vue
<template>
<div></div>
</template>
<script>
export default {
methods: {
uploadAvatar() {
selectSystemPhoto(async (res) => {
console.log(res, 'res');
this.avatarFilePath = res.tempFilePaths[0]; // 用于图片临时回显
let uploadRes = await uploadPhoto(this.avatarFilePath); // 上传
if (uploadRes.success) {
this.$toast.success('上传成功');
}
});
},
},
};
</script>js
export async function uploadPhoto(filePath) {
uni.showLoading({
title: '加载中',
});
const url = `${BASE_URL}/sys-file/upload`; // 后端实际接口
return new Promise((resolve, reject) => {
try {
uni.uploadFile({
url,
filePath: filePath,
name: 'file', // 后端接收file的字段
header: request.getHeaders(), // 请求头 token等等
success: async (uploadFileRes) => {
const { statusCode, data } = uploadFileRes;
if (statusCode !== 200) {
handleStatus(statusCode);
reject(Result.fail('上传失败'));
}
resolve(Result.ok(JSON.parse(data).data));
},
});
} catch (e) {
reject(Result.fail('上传失败'));
} finally {
uni.hideLoading();
}
});
}在线图片转本地缓存图片
js
/**
* 图片缓存
* @param url - https 地址
* @param cache - 缓存对象
* @param options - 配置项
* @return {Promise<Result>}
*/
function getCachedImage(url, cache, options = {}) {
const { forceUpdate = false } = options;
return new Promise((resolve, reject) => {
try {
// 缓存存在且 URL 一致,且非强制更新
if (cache && cache.url === url && !forceUpdate) {
console.log('使用缓存图片');
return resolve(Result.ok(cache));
}
// 下载图片
uni.downloadFile({
url,
success: (res) => {
if (res.statusCode !== 200) {
return reject(Result.fail('图片下载失败'));
}
const fs = uni.getFileSystemManager();
// 使用唯一文件名,避免删除旧文件
const savePath = `${wx.env.USER_DATA_PATH}/${Date.now()}_${Math.random().toString(36).slice(2)}.png`;
saveFile(res.tempFilePath, savePath, url, resolve, reject);
},
fail: (err) => {
console.error('下载头像失败', err);
reject(Result.fail('下载失败'));
},
});
} catch (e) {
reject(Result.fail(e.message || '获取缓存失败'));
}
});
}
// 保存文件封装
function saveFile(tempFilePath, savePath, url, resolve, reject) {
const fs = uni.getFileSystemManager();
fs.saveFile({
tempFilePath,
filePath: savePath,
success: (saveRes) => {
// 缓存对象的数据结构(path:本地缓存地址,url:真实图片地址)
const cacheData = { path: saveRes.savedFilePath, url };
resolve(Result.ok(cacheData));
},
fail: (err) => {
console.error('保存头像失败', err);
reject(Result.fail('保存头像失败'));
},
});
}js
import Request from '@/api/request';
import { handleErrorCode } from '@/utils/common';
import { isKit } from 'y-js-kit';
import { getCachedImage } from '@/utils/uni-api';
export default {
namespaced: true,
state: {
avatar_cache: null,
login_info: null,
user_info: null,
},
mutations: {
setLoginInfo(state, login_info) {
state.login_info = login_info;
},
setUserInfo(state, user_info) {
state.user_info = user_info;
},
setAvatarCache(state, cache) {
state.avatar_cache = cache;
},
},
actions: {
async updateUserInfo({ commit }, fieldsObj) {
try {
let res = await Request.post('/api_user/updateInfo', fieldsObj);
console.log(res, 'res');
if (res.ok) {
uni.$u.toast('修改成功!');
}
} catch (e) {
uni.$u.toast('修改用户信息失败,请稍后再试~');
}
},
setLoginInfo({ commit }, login_info) {
commit('setLoginInfo', login_info);
},
async setUserInfo({ commit, getters, dispatch }) {
if (!getters.isLogin) {
return;
}
let user_info = null;
try {
let res = await Request.get('/api_user/info', null);
if (res.ok) {
user_info = res.data;
const avatar = user_info?.appUser.avatar;
if (isKit.isNotEmpty(avatar)) {
// 缓存头像
await dispatch('setAvatarCache', avatar);
}
}
} catch (e) {
console.log(e);
handleErrorCode({ code: 0, msg: e.msg || '未知错误' }, false);
} finally {
commit('setUserInfo', user_info);
}
},
async setAvatarCache({ commit, getters }, url) {
if (!url) return null;
try {
const res = await getCachedImage(url, getters.avatarCache);
if (res.success && res.data) {
const cacheData = res.data;
commit('setAvatarCache', cacheData);
return cacheData;
}
} catch (e) {
console.error('缓存头像失败', e);
}
return null;
},
logout({ commit }) {
commit('setLoginInfo', null);
commit('setUserInfo', null);
commit('setAvatarCache', null);
},
},
getters: {
avatarCache: (state) => state.avatar_cache,
isLogin: (state) => !!state.login_info,
userInfo: (state) => state.user_info?.appUser || {},
accessToken: (state) => state.login_info?.access_token || '',
},
};js
function avatarImage() {
let url = '';
if (isKit.isNotEmpty(this.avatarCache)) {
url = this.avatarCache.path;
} else {
url = '/static/mine/avatar.png';
}
return url;
}获取系统键盘高度
js
/**
* 获取系统键盘高度
*/
function getKeyBoardHeight(changeCallBack) {
let safeBottom = 0;
let systemInfoRes = uni.getSystemInfoSync();
if (isNotEmpty(systemInfoRes) && isNotEmpty(systemInfoRes.safeAreaInsets) && isNotEmpty(systemInfoRes.safeAreaInsets.bottom)) {
safeBottom = systemInfoRes.safeAreaInsets.bottom;
}
uni.onKeyboardHeightChange((e) => changeCallBack({ ...e, keyboardHeight: e.height + safeBottom }));
return () => {
uni.offKeyboardHeightChange();
};
}图片百分比宽度计算相应高度
js
/**
* 计算正方形元素高度
* @param {number} columns 一行几个元素
* @param {number} margin 元素间距(rpx 或 px,根据实际单位)
* @param {number} containerWidth 容器宽度,可选,不传则自动取屏幕宽度
* @returns {string} 高度,单位与 containerWidth 保持一致
*/
function calcSquareItemHeight(columns = 3, margin = 15, containerWidth) {
if (!containerWidth) {
// 获取屏幕宽度(px)
containerWidth = uni.getSystemInfoSync().screenWidth;
}
// 总间距 = 左右边距 + 元素间距
const totalMargin = (columns - 1) * margin;
const width = (containerWidth - totalMargin) / columns;
return `${width}px`; // 返回字符串可直接绑定 style
}封装uni.$xxx
js
import Vue from "vue";
import mztRequest from "@/http/mzt-request";
import MztUtils from "@/utils/mzt-utils";
class PageDialog {
constructor() {
this.state = Vue.observable({
visible: false,
list: [],
data: {}
});
}
async checkShow(pageId) {
this.pageId = pageId;
// 初始化弹窗数组
this.state.list = await this.getList();
// 获取弹窗数据
this.state.data = this.isIncluded();
if (MztUtils.isNotEmpty(this.state.data)) {
this.setStatus(true);
} else {
this.setStatus(false);
}
}
isIncluded() {
if (this.state.list.length === 0) {
return null;
}
return (
this.state.list.find(item => {
const ids = Array.isArray(item.layoutId)
? item.layoutId
: String(item.layoutId || "").split(",");
return ids.includes(this.pageId);
}) || null
);
}
async getList() {
if (this.state.list.length > 0) {
return this.state.list;
}
try {
let res = await mztRequest.postApp("dialog/list");
if (!res.success) {
throw new Error('"请求错误"');
}
return res.data;
} catch (e) {
console.error("[弹窗error]" + e.message);
return [];
}
}
setStatus(status) {
this.state.visible = status;
}
limitRules() {}
}
export const pageDialog = new PageDialog();
export default {
install(Vue) {
Vue.prototype.$dialog = pageDialog;
if (!uni.$dialog) {
uni.$dialog = pageDialog;
}
}
};js
import Vue from "vue";
import PageDialog from "@/components/PageDialog/page-dialog";
Vue.use(PageDialog);vue
await uni.$dialog.checkShow(this.pageInfo.id);vue
<template>
<u-popup
:show="state.visible"
:round="10"
mode="center"
:closeOnClickOverlay="true"
@close=""
customStyle="background-color:transparent;height:auto"
:closeable="false"
>
<view class="image-dialog">
<Image mode="aspectFit" :src="getImageUrl" />
<u-icon
name="close-circle"
size="40"
color="#dcdfe6"
custom-style="margin-top:15px"
@click="closeDialog"
></u-icon>
</view>
</u-popup>
</template>
<script>
export default {
name: "PageDialog",
data() {
return {
state: uni.$dialog.state
};
},
computed: {
getImageUrl() {
const url = this.state.data.fileUrl;
return url ? url.replace(/http:\/\//, "https://") : url;
}
},
methods: {
closeDialog() {
uni.$dialog.setStatus(false);
}
}
};
</script>
<style scoped lang="scss">
.image-dialog {
display: flex;
flex-direction: column;
align-items: center;
Image {
height: 200px;
}
}
</style>