preface

Recently, because of business needs, to start a new small program project, so there is this selection of the article, this will be a brief description of the basic framework construction and part of the compatibility problems and matters needing attention, welcome to read after guidance.

The selection

Although it is a small program project, the page tag is written in native HTML, that is, the traditional div SPAN tag, considering the possibility of additional extensions (H5, etc., is actually quite a small possibility, but should be considered).

Taro Taro and Uniapp, I choose Uniapp. First of all, the ecosystem of the two is richer and there are more solutions. After all, Uniapp is based on Vue, and There are many followers of Vue in China.

Taro does not know much about Vue due to time constraints. Although he supports Vue, most of the solutions are based on React. However, most of the team members are familiar with Vue and consider the inconsistency of technology stack, so they finally choose Uniapp.

Uniapp

After really good main framework, I use vue-CLI own Uniapp template (Vue3.0 + TS) as the technology stack.

vue create -p dcloudio/uni-preset-vue#vue3 <project Name>
Copy the code

After the establishment, the structure is as follows

Simply adjust the directory

  • API specific requests are put here, which can be broken down by business module, needless to say.
  • Assets Static resources, storing pictures.
  • Components Public components.
  • Constant is used to store the most commonly used constants such as item type (1: physical, 2: virtual).
  • Layout is a global page container, which is wrapped with a layer of layout. It is mainly designed to adapt to various models (such as IphoneX bangs).
  • Lib, which I have not marked above (optional), is used to store some structure defined by Ts.
  • 例 句 : Packages are used as demos for testing.
  • Pages Indicates the service page.
  • The router routing.
  • Serve public request methods, request interceptors and so on are implemented here.
  • Static static resources, forget to delete, see personal naming preferences.
  • Store Global status management module, Vuex.
  • Utils utility functions.

A brief introduction to some of the problems encountered in mobile terminals, such as common components, requests, etc., no longer verbose.

layout

First of all, let’s talk about the meaning of layout. It exists for global adaptation. It only needs to pay attention to the business level, and there is no need to do adaptation.

Uniapp is very friendly and provides a method called getSystemInfoSync to get system information. This method returns a safeArea, a safeArea under portrait direction, which we can build on.

After we get WeChat applet by getMenuButtonBoundingClientRect location information in the top right corner of the capsule, simple to fit the safety area.

/ * * *@feat < get the top value of the action bar on the view > *@param {number}  Height The height of the action bar aligned with the applet menu button */
export function getBarClientTop(height: number) :number {
  let top: number;
  const { safeArea } = uni.getSystemInfoSync();
  const safeAreaInsets = safeArea ? safeArea.top : 0;

  // #ifdef MP
  if (height > 0) {
    const menuBtnRect =
      uni.getMenuButtonBoundingClientRect &&
      uni.getMenuButtonBoundingClientRect();

    top = menuBtnRect.height / 2 + menuBtnRect.top - height / 2;
  } else {
    top = safeAreaInsets;
  }
  // #endif

  // #ifdef H5 || APP-PLUS
  top = safeAreaInsets;
  // #endif

  return top;
}
Copy the code

The default height is 44, depending on the actual result. We get height information from these apis for the spacing of our elements.

.eslintrc.js

Since the project is based on TS in the Node environment, some UNI and WX global rules need to be added in order for TS to work properly.

module.exports = {
  globals: {
    uni: true.wx: true,}}Copy the code

postcss

Since the design is a double image (the width of the design is 750px, the actual size is 375px), and RPX is needed to fit the applet.

const path = require("path");
module.exports = {
  parser: require("postcss-comment"),
  plugins: [
    require("postcss-pxtorpx-pro") ({// The conversion unit
      unit: "rpx".// Unit precision
      unitPrecision: 2.// The minimum pixel value that needs to be converted, the px unit below this value will not be converted
      minPixelValue: 1.// Unprocessed files
      exclude: /node_modules|components/gi.// The default design is 750 width, 2 times the output
      / / 0.85 640
      transform: (x) = > x * 2,}).require("postcss-import") ({resolve(id) {
        if (id.startsWith("~ @ /")) {
          return path.resolve(process.env.UNI_INPUT_DIR, id.substr(3));
        } else if (id.startsWith("@ /")) {
          return path.resolve(process.env.UNI_INPUT_DIR, id.substr(2));
        } else if (id.startsWith("/") && !id.startsWith("/ /")) {
          return path.resolve(process.env.UNI_INPUT_DIR, id.substr(1));
        }
        returnid; }}),require("autoprefixer") ({remove: process.env.UNI_PLATFORM ! = ="h5",}).require("@dcloudio/vue-cli-plugin-uni/packages/postcss")]};Copy the code

assets/css/constant.scss

Here, define some common style variables, if the later skin function can be easily switched, specific rules can be given by the design.

// Theme color $main-color: #EE5A61; // $sub-color: #FFA264; // Background color $page-background-color: #F2F4F5; // Page background color // More...Copy the code

Camera assembly && Audio assembly

The camera component

Let’s start with the camera component because it involves recording video and so on. Uniapp also provides createCameraContext so that we can get the Camera context. However, the API is not compatible with H5 and APPS. If you want to do a lot of things involving the H5 this is very troublesome, may need to tune the native camera (not redundant).

The component is also not a full-screen camera and can be styled itself. So, here’s a simple Demo.

<template>
  <LayoutMain>
    <template v-slot:mains>
      <div class="carmare-wrapper">
        <camera
          :device-position="cameraConfig.devicePosition"
          :flash="cameraConfig.flash"
          binderror="error"
          @error="handleOnError"
          style="width: 100%; height: 300px"
        ></camera>

        <button @click="handleTakePhoto">Taking pictures</button>
        <button @click="handleStartReord">Start the video</button>
        <button @click="handleStopRecord">Stop the video</button>
        <button @click="handleSwitchDevicePosition">Switch the camera toward the {{cameraConfig. DevicePosition}}</button>
        <button @click="handleSwitchFlashLight">{{cameraconfig. flash}} Flash</button>

        <div v-if="photoList.length > 0">Photos that have been taken<div v-for="(item, index) in photoList" :key="index">
            <img :src="item" alt="" />
          </div>
        </div>

        <div v-if="videoSrc">Recorded video<video :src="videoSrc" style="width: 100px; height: 100px"></video>
        </div>
      </div>
    </template>
  </LayoutMain></template> <script lang="ts"> import { onReady } from "@dcloudio/uni-app"; import { defineComponent, reactive, ref, Ref } from "vue"; import LayoutMain from "@/layout/layoutMain.vue"; export default defineComponent({ setup() { let carmareContext: any; let videoSrc: Ref<string> = ref(""); let currentFlashLightStatus = 0; const statusList = ["off", "on", "auto"]; const cameraConfig: any = reactive({ devicePosition: "back", flash: statusList[currentFlashLightStatus], }); onReady(() => { carmareContext = uni.createCameraContext(); }); const photoList: Ref<string[]> = ref([]); return { cameraConfig, photoList, videoSrc, handleOnError: (error: any) => { console.error("handleOnError-eerror", error); }, handleTakePhoto: () => { carmareContext.takePhoto({ quality: "high", success: (res: any) => { console.info("res", res); photoList.value.push(res.tempImagePath); }}); }, handleSwitchDevicePosition: () => { console.info("cameraConfig.devicePosition"); cameraConfig.devicePosition = cameraConfig.devicePosition === "back" ? "front" : "back"; }, handleSwitchFlashLight: () => { const lastStatus = statusList.length - 1; console.info("333handleSwitchFlashLight"); if (currentFlashLightStatus < lastStatus) { cameraConfig.flash = statusList[(currentFlashLightStatus += 1)]; } else { currentFlashLightStatus = 0; cameraConfig.flash = statusList[currentFlashLightStatus]; }}, / / video handleStartReord: () = > {carmareContext. StartRecord ({success: (res: any) => { console.log("handleStartReord-success", res); Uni. ShowToast ({title: "Start recording ",}); }, fail: (error: any) => { console.error("handleStartReord-error", error); }}); } / / stop video handleStopRecord: () = > {carmareContext. StopRecord ({success: (res: any) => { console.log("handleStopRecord-success", res); Uni.showtoast ({title: "Stop recording ",}); videoSrc.value = res.tempVideoPath; }, fail: (error: any) => { console.error("handleStopRecord-error", error); }}); }}; }, components: { LayoutMain }, }); </script>Copy the code

Click the real machine debugging on the wechat developer tool, and the result is shown in the picture:

Audio components

Similarly, Uniapp provides createInnerAudioContext(), which creates and returns the innerAudioContext object of the innerAudioContext.

This API is compatible with H5 and App. On IOS, this component supports only M4A, WAV, MP3, AAC, AIFF, AND CAF formats. But there are more on Android. Also give a brief answer to the Demo for debugging.

<template>
  <LayoutMain>
    <template v-slot:container>
      <button @click="handleStartRecord">Start the recording</button>
      <button @click="handleEndRecord">End of the tape</button>
      <button @click="handlePlay">Play the tape</button>
      <button @click="handlePausePlay">Pause the Recording</button>
      <button @click="handlePausePlay">Pause the Recording</button>
      <button @click="handleEndPlay">End Playing a Recording</button>

      <div>Operation records<div v-for="(item, index) in operateRecordList" :key="index">
          {{ item }}
        </div>
      </div>
    </template>
  </LayoutMain>
</template>

<script lang="ts">
import { ref, onMounted, Ref } from "vue";

export default {
  data() {
    return {};
  },
  setup() {
    const operateRecordList: Ref<string[]> = ref([]);

    // let getRecorderManager;
    let recorderManager: any;
    let innerAudioContext: any;
    let voicePath: string;

    onMounted(() = > {
      const current = (recorderManager = uni.getRecorderManager());

      operateRecordList.value.push("prending");

      current.onError(function (e: unknown) {
        uni.showToast({
          title: "getRecorderManager.onError"});console.error("getRecorderManager.onError", e);
      });

      current.onStart(function () {
        operateRecordList.value.push("Start recording.");
      });
      current.onStop(function (res: any) {
        operateRecordList.value.push("End of the recording.");

        console.log("recorder stop" + JSON.stringify(res));
        voicePath = res.tempFilePath;
      });
      current.onPause(function () {
        operateRecordList.value.push("Pause recording");
      });
    });

    onMounted(() = > {
      const current = (innerAudioContext = uni.createInnerAudioContext());

      current.obeyMuteSwitch = false;

      uni.setInnerAudioOption({
        obeyMuteSwitch: false}); current.onError((res) = > {
        console.error("innerAudioContext-onError", res);
      });

      current.onPlay(() = > {
        operateRecordList.value.push("Start playing");
      });
      current.onPause(() = > {
        operateRecordList.value.push("Pause play");
      });
      current.onStop(() = > {
        operateRecordList.value.push("End of play");
      });
    });

    return {
      operateRecordList,
      handleStartRecord: () = > {
        recorderManager.start({
          duration: 60000.// Recording duration in ms, maximum 600000 (10 minutes)
          format: "mp3"}); },handleEndRecord: () = > {
        recorderManager.stop();
      },
      handlePlay: () = > {
        innerAudioContext.src = voicePath;
        innerAudioContext.play();
      },
      handleEndPlay: () = > {
        innerAudioContext.stop();
      },
      handlePausePlay: () = >{ innerAudioContext.pause(); }}; }};</script>


Copy the code

Recording and playing are not easy to show, so there are no screenshots, interested friends can copy to use.

In the pit of

layout

When the layout components mentioned above are running on the computers of different colleagues, it is found that a few of them have layout that does not take effect, that is, the page does not wrap the layout layer.

Register globally in main.ts:

import { createApp } from "vue";
import Layout from "@/layout/layoutMain.vue";
import store from "@/store/index";
import App from "./App.vue";
const app = createApp(App);
app.use(store);
// Globally register components
app.component("Layout", Layout);
app.mount("#app");
Copy the code

Used in a page (layout doesn’t work here, but on my computer) :

<template>
  <Layout>
    <template v-slot:mains>
      <div>Category pages</div>
    </template>
  </Layout>
</template>
Copy the code

This is still the case when the environment is the same and plug-ins and other aspects are not affected. It is not clear how this will work, but the current solution is to rename the Layout to LayoutMain. (Black question mark?)

image

The lazy loading of the Uniapp Image component does not take effect, which is suspected to be A decoration after testing.

See their own manual image lazy loading.

About component passing values

Assume the following components:

<template>
    <div>{{ hello }}</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue";
import { Props } from "./interface";

export default defineComponent({
    props: {
        hello: {
          type: String as PropType<Props["hello"] >,default: 'fff',}}})</script>
Copy the code

You can see that Hello is of type String and the default value is FFF.

But when hello = undefined, hello will display an empty string “”. If hello does not pass, it will be “FFF”.

The last

Are you gonna leave here without liking it?

Attention to the public number: biscuit front, close the distance between you and me (^▽^)