xccjk / x-blog

学习笔记
18 stars 2 forks source link

微信小程序相关 #71

Open xccjk opened 2 years ago

xccjk commented 2 years ago

video标签背景色

想比很多人都想修改video的那个黑色背景吧,还是别做梦了,还不支持哈。

官方针对video背景色解读

xccjk commented 2 years ago

微信小程序 swiper禁止手动切换

给swiper设置catchtouchmove为true,设置swiper-item的catchtouchmove返回false

<swiper
  class="swiper"
  indicator-dots="{{false}}"
  circular
  autoplay="{{true}}"
  vertical="{{true}}"
  interval="{{3000}}" 
  duration="{{300}}"
  easing-function="easeInOutCubic"
  catchtouchmove="true"
>
  <block wx:for="{{list}}" wx:key="index">
    <swiper-item catchtouchmove="stopChange">
      <view class="swiper-item">{{item.name}}</view>
    </swiper-item>
  </block>
</swiper>

stopChange() {
  return false
}
xccjk commented 2 years ago

微信小程序多音频场景处理

背景:页面中有多条数据,每条数据都可以单独播放,不同时播放

核心逻辑就是每个音频单独初始化一个方法,每个音频播放的进度等信息都单独控制

通过wx.createInnerAudioContext来控制

// wxml
<view wx:for="{{list}}" wx:key="id">
  <view play="{{play}}" url="{{item.url}}" id="{{item.id}}">播放</view>
</view>

// js
Component({
  data: {
    play: false,
    list: [
      { id: 1, url: '' },
      { id: 2, url: '' },
      { id: 3, url: '' },
    ],
    id: null,
  },
  onLoad: function () {},
  onShow() {},
  onHide() {
    this.audioPause();
  },
  onUnload() {
    this.audioPause();
  },
  handleAudioPlay(e) {
    const { play, url, id } = e.detail;
    if (!this[`innerAudioContext${id}`]) {
      this[`innerAudioContext${id}`] = wx.createInnerAudioContext();
    }
    if (play) {
      this[`innerAudioContext${id}`].pause();
    } else {
      this[`innerAudioContext${id}`].autoplay = true;
      this[`innerAudioContext${id}`].src = url;
      this[`innerAudioContext${id}`].play();
    }

    this.setData({
      play: !play,
      id,
    });
    this.innerAudioContext.onEnded(() => {
      this.setData({
        play: false,
      });
    });
  },
  audioPause() {
    const { id } = this.data;
    this[`innerAudioContext${id}`].pause();
    this.setData({
      play: false,
    });
  },
});

当采用这个方法时,还有三种常见的场景需要考虑:

  1. 页面返回键退出当前页面
  2. 右上角退出小程序
  3. 手机home键将小程序切换到后台

为什么需要考虑这三种情况呢,因为一般小程序退出当前页面或者切换到后台,需要停止当前播放的音乐

当通过返回键退出当前页面(或者点击页面跳转到其它页面),一般直接销毁音频初始化方法即可:

handlerBack() {
  router.back()
  const { id } = this.data
  if (this[`innerAudioContext${id}`]) {
    this[`innerAudioContext${id}`]. destroy()
  }
}

右上角退出小程序

onUnload() {
  const { id } = this.data
  if (this[`innerAudioContext${id}`]) {
    this[`innerAudioContext${id}`]. pause()
  }
}

当按home键切换到后台时:

onHide() {
  const { id } = this.data
  if (this[`innerAudioContext${id}`]) {
   this[`innerAudioContext${id}`]. pause()
  }
}

当前音频是单独播放,只是针对每个音频初始化一个实例,假如需要同时播放多个音频,这种方式可能会导致内存不足导致的崩溃

xccjk commented 2 years ago

微信小程序多音频场景处理 - 背景音频

提到音频播放控制,不得不提背景音频这个方法wx.getBackgroundAudioManager

很不幸,这个有坑。当使用场景为单音频播放或者不需要记录每个音频播放的位置时,使用它是一个合适的选择。当需要记录每个音频的播放位置时,这个就会有些问题了。问题来源backgroundAudioManager.seek这个API

重要的事情说3遍:backgroundAudioManager.seek在模拟器上正常,但是在真机上不生效,每次都会重新播 重要的事情说3遍:backgroundAudioManager.seek在模拟器上正常,但是在真机上不生效,每次都会重新播 重要的事情说3遍:backgroundAudioManager.seek在模拟器上正常,但是在真机上不生效,每次都会重新播

Component({
  data: {
    play: false,
    list: [
      { id: 1, url: '' },
      { id: 2, url: '' },
      { id: 3, url: '' },
    ],
    duration: {},
    id: null,
  },
  onLoad() {},
  onShow() {},
  onHide() {},
  onUnload() { },
  play() {
    wx.nextTick(() => {
      const { duration = {} } = this.data;
      const currentTime = duration[id] || 0;
      backgroundAudioManager.title = '每日快讯';
      backgroundAudioManager.epname = '';
      backgroundAudioManager.singer = '';
      backgroundAudioManager.coverImgUrl = '';
      backgroundAudioManager.src = url;
      if (currentTime) {
        // 跳转到指定的进度
        backgroundAudioManager.seek(currentTime);
        backgroundAudioManager.play();
      }
    });
  },
  handleAudioPlay(e) {
    const { play, url, id } = e.detail;
    const backgroundAudioManager = wx.getBackgroundAudioManager();
    if (play) {
      backgroundAudioManager.pause();
      // 记录当前音频播放的进度
      wx.getBackgroundAudioPlayerState({
        success(res) {
          const { currentPosition } = res;
          that.setData({
            duration: {
              ...duration,
              [id]: currentPosition,
            },
          });
        },
      });
      // 点击的不是当前播放的音频
      if (this.data.id !== id) {
        this.play()
      }
    } else {
      this.play()
    }
    this.setData({
      play: !play,
      id,
    }),
  },
});
xccjk commented 2 years ago

自定义字体在Android端不生效的问题

xccjk commented 2 years ago

微信小程序警告 [Component] property received type-uncompatible value: expected but get null value. Used empty string instead

问题产生原因:子组件properties中定义的字段类型不满足导致相关警告

比如定义组件中src属性为String类型时,当src传入null时,就会有上述警告出现

// imagex
Component({
  options: {},
  externalClasses: ['class'],
  properties: {
    src: String,
  },
  data: {
    loaded: false,
  },
  lifetimes: {
    attached() {},
  },
  methods: {},
});

使用

<imagex src="{{null}}" />
xccjk commented 1 year ago

微信小程序-基于node实现自动打包上传代码

在平时的小程序开发过程中,可能会遇到下面这些小问题,虽然不影响开发过程,但是开发体验确会差一点,具体如下:

那么怎么避免重复的操作,特别是比较频繁的发布场景,可能每天需要多次的等待及上传操作。

对于问题1,相信很多人想的,就是每次输入命令时,微信开发者工具可以自动打开,并且可以打开当前指定的项目;对于问题2,每次输入构建发布命令时,先执行打包操作,然后自动执行上传操作,不需要人为等待打包结束已经人为点击发布按钮。

实现一个自动上传功能

miniprogram-ci

官方发布的cli命令,不需要开发者工具,进行小程序代码的上传、预览等操作等。网上这方面的文档挺多的,不细说。

采用这个方法的,需要提供小程序的秘钥与设置IP白名单,可能会存在一定的风险,比较适用于有独立的打包发布平台,在指定机器上进行打包操作

nodejs实现自动上传

其实,通过nodejs写的脚本或者shell之类的,可以快速实现自动上传的效果。核心原理就是通过child_process开启一个多进程,在执行完打包命令后,运行官方提供的命令行V2,常见的API如下:

// 登录
cli login
// 预览
cli preview
// 上传
cli upload
// 启动开发者工具
cli open

相比miniprogram-ci,使用脚本的方式实现时,会依赖开发者工具,当微信开发者工具未登录时,运行自动上传命令会报错,因此也添加了判断未登录时,会在命令行中生成二维码,扫码登录即可。

同时,采用child_process不依赖任何三方包,每个人的电脑只要安装node环境与微信开发者工具就行了

具体实现如下:

// upload.js
#!/usr/bin/env

const child = require('child_process');
const exec = child.execSync;

function getDays() {
  const date = new Date();

  const year = date.getFullYear();
  const month = date.getMonth() + 1;
  const strDate = date.getDate();
  const hours = date.getHours();
  const minutes = date.getMinutes();

  return `在${year}年${month}月${strDate}日${hours}点${minutes}分提交上传`;
}

function getName() {
  const arr = process.cwd().split('/');
  return arr[2];
}

const branch = child.execSync('git name-rev --name-only HEAD', { encoding: 'utf8' });

const config = {
  path: `/Applications/wechatwebdevtools.app/Contents/MacOS/cli`,
  projectPath: `${process.cwd()}/dist`,
  version: `1.2.1`,
  desc: `${getName()}${getDays()},发布分支${branch}`,
};

exec('npm run build', { stdio: 'inherit' });

child.exec(
  `${config.path} upload --project ${config.projectPath} -v ${config.version} -d ${config.desc}`,
  { stdio: 'inherit' },
  (err, res) => {
    if (res) {
      exec(`${config.path} login --qr-size small`, { stdio: 'inherit' });
      exec(
        `${config.path} upload --project ${config.projectPath} -v ${config.version} -d ${config.desc}`,
        { stdio: 'inherit' }
      );
    }
  }
);
// package.json
"scripts": {
  "upload": "node upload.js"
}

使用shell的方式实现自动上传

对于前端来说,平时写shell可能不是特别多,但是它真的可以解决非常多的问题,非常的便捷。

平时写shell,一般分两种,直接写shell,或者用三方包来写,比较知名的有shelljszx。对于shelljs,其实是对child_process做了一层封装,保证了多端语法一致性。而zx是Google出品的,语法更贴合前端。我们本次就是采用zx来实现。

// 全局安装zx
sudo npm install -g zx

具体实现如下:

// upload.mjs
#!/usr/bin/env zx

function getDays() {
  const date = new Date();

  const year = date.getFullYear();
  const month = date.getMonth() + 1;
  const strDate = date.getDate();
  const hours = date.getHours();
  const minutes = date.getMinutes();

  return `在${year}年${month}月${strDate}日${hours}点${minutes}分 提交上传`;
}

const branch = await $`git branch --show-current`;

const config = {
  path: `/Applications/wechatwebdevtools.app/Contents/MacOS/cli`,
  projectPath: `${process.cwd()}/dist`,
  version: `1.2.1`,
  desc: `${getDays()},发布分支${branch}`,
};

await $`npm run build`;

await $`${config.path} upload --project ${config.projectPath} -v ${config.version} -d ${config.desc}`;
// package.json
"scripts": {
  "upload": "zx upload.mjs"
}

自动打开开发者工具

// open.js
#!/usr/bin/env

const child = require('child_process');
const exec = child.execSync;

const path = '/Applications/wechatwebdevtools.app/Contents/MacOS/cli';
const projectPath = `${process.cwd()}/dist`;

child.exec('git branch --show-current', (err, res) => {
  console.log('当前分支:', res);
});

child.exec(`${path} open --project ${projectPath}`, (err, res) => {
  if (res) {
    exec(`${path} login --qr-size small`, { stdio: 'inherit' });
    exec(`${path} open --project ${projectPath}`, { stdio: 'inherit' });
    exec('npm run dev', { stdio: 'inherit' });
  } else {
    exec('npm run dev', { stdio: 'inherit' });
  }
});
// package.json
"scripts": {
  "open": "node open.js"
}
xccjk commented 8 months ago

微信小程序 图片预览,关闭预览会触发onShow生命周期

很多情况下在onShow生命周期进行接口请求,并且请求时设置了loading,在关闭预览时会出现loading动画

解决方案:

设置标识符,标记打开了预览弹窗

class App extends Component {
  constructor(props) {
    super(props);
    this.onShow = false
  }

  async componentDidShow() {
    if (this.onShow) {
      this.onShow = false;
      return;
    }
    await fn();
  }

  showModal = () => {
    this.onShow = true;
    wx.previewImage({});
  };
  ...
}

export default App;