предисловие
В последнее время из-за потребностей бизнеса необходимо начать новый проект апплета, поэтому у меня есть эта статья о выборе. В этой статье кратко описывается построение базовой структуры, а также некоторые проблемы совместимости и меры предосторожности. Добро пожаловать, чтобы дать указатели после прочтения.
Выбор
Хоть это и небольшой программный проект, учитывая, что в будущем могут быть дополнительные расширения (такие как H5, возможность пока совсем небольшая, но ее надо учитывать), тэги страницы написаны на нативном html, то есть традиционном div span и другие теги.
Я выбрал Uniapp для Taro и Uniapp.Во-первых, я чувствую, что экосистема Uniapp будет богаче и будет больше решений, ведь Uniapp основан на Vue, а у отечественного Vue тоже больше последователей.
Таро мало знает о времени из-за временных отношений.Хотя он поддерживает Vue, большинство решений основано на React, и большая часть команды знакома с Vue.Учитывая несогласованность стека технологий, он в итоге выбрал Uniapp .
Uniapp
После того, как основная структура действительно хороша, я использую шаблон Uniapp (Vue3.0 + TS), который поставляется с Vue-cli, в качестве технологического стека на этот раз.
vue create -p dcloudio/uni-preset-vue#vue3 <project Name>
После завершения создания структура выглядит следующим образом.
Просто настройте каталог
- Здесь размещаются конкретные запросы API, которые также можно разделить по бизнес-модулю, разумеется.
- активы — это статические ресурсы, в которых хранятся изображения.
- компоненты общедоступный компонент.
- константы Константы в основном используются для хранения некоторых часто используемых констант, таких как типы товаров (1: физические объекты, 2: виртуальные объекты) и т. д.
- макет — это глобальный контейнер страницы.При разработке страниц самый внешний слой оборачивает слой макета, в основном для адаптации к различным моделям (таким как Liu Haiping из IphoneX), который можно адаптировать к безопасной области здесь, что будет обсуждаться позже.
- lib не помечен выше (необязательный), он используется для хранения некоторых структур, определенных Ts.
- Пакеты — это небольшой программный подпакет, который используется в качестве демонстрации для тестирования подпакетов.
- страницы Бизнес-страницы.
- Маршрутизация маршрутизатора.
- Здесь реализованы метод обслуживания общедоступных запросов, перехватчик запросов и т. д.
- статические статические ресурсы, забыли удалить, повторите вышеописанное, чтобы увидеть личные настройки именования.
- хранить модуль управления глобальным состоянием, Vuex.
- использует служебные функции.
Кратко объясните некоторые проблемы, которые могут возникнуть в мобильном терминале, например, общие компоненты, запросы и т. д. похожи, поэтому я не буду их повторять.
layout
Прежде всего, поговорим о смысле существования верстки.Он существует для адаптации к глобальной модели.При накрутке ему нужно только обратить внимание на бизнес-уровень, а адаптацию делать не надо.
Uniapp был очень дружелюбным и предоставил намgetSystemInfoSync
Этот метод используется для получения системной информации, этот метод возвращаетsafeArea
, в безопасной зоне под положительным направлением вертикального экрана, мы можем на этой основе поднять шумиху.
После этого проходимgetMenuButtonBoundingClientRect
Чтобы получить информацию о местонахождении капсулы в правом верхнем углу апплета WeChat, достаточно просто адаптироваться к безопасной зоне.
/**
* @feat < 获取 操作栏在视图上的top值 >
* @param {number} height 与小程序菜单按钮对齐的操作条高度
*/
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;
}
Здесь значение высоты по умолчанию равно 44, что можно определить по фактическому результату. Мы получаем информацию о высоте через эти API для интервалов между нашими элементами.
.eslintrc.js
Так как проект разрабатывается на основе ts в среде Node, необходимо добавить глобальные правила uni и wx, чтобы ts мог нормально работать.
module.exports = {
globals: {
uni: true,
wx: true,
}
}
postcss
Так как набросок дизайна в 2 раза больше изображения (ширина наброска дизайна 750px, фактический размер 375px), то для адаптации под апплет нужно использовать rpx.
const path = require("path");
module.exports = {
parser: require("postcss-comment"),
plugins: [
require("postcss-pxtorpx-pro")({
// 转化的单位
unit: "rpx",
// 单位精度
unitPrecision: 2,
// 需要转化的最小的pixel值,低于该值的px单位不做转化
minPixelValue: 1,
// 不处理的文件
exclude: /node_modules|components/gi,
// 默认设计稿按照750宽,2倍图的出
// 640 0.85
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));
}
return id;
},
}),
require("autoprefixer")({
remove: process.env.UNI_PLATFORM !== "h5",
}),
require("@dcloudio/vue-cli-plugin-uni/packages/postcss"),
],
};
assets/css/constant.scss
Здесь определяются некоторые общие переменные стиля. Если на более позднем этапе есть функция скиннинга, ее можно легко переключить. Конкретные правила могут быть заданы дизайном.
// 主题色
$main-color: #EE5A61; // 品牌色
$sub-color: #FFA264; // 辅助色
// 背景色
$page-background-color: #F2F4F5; // 页面背景色
// More ...
Компоненты камеры и аудиокомпоненты
сборка камеры
Давайте сначала поговорим о компоненте камеры, потому что он включает в себя такие операции, как запись видео. И Uniapp также предоставляетcreateCameraContext
, чтобы мы могли получить контекст камеры. Но этот API не совместим с H5 и App. Если вы хотите сделать мультиэнд с участием Н5, то это будет очень хлопотно, возможно потребуется настроить родную камеру (не вдаваться в подробности).
Этот компонент также не является полноэкранной камерой и сам по себе может управляться стилями. Итак, дана простая демонстрация.
<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">拍照</button>
<button @click="handleStartReord">开始录像</button>
<button @click="handleStopRecord">停止录像</button>
<button @click="handleSwitchDevicePosition">
切换摄像头朝向{{ cameraConfig.devicePosition }}
</button>
<button @click="handleSwitchFlashLight">
{{ cameraConfig.flash }}闪光灯
</button>
<div v-if="photoList.length > 0">
已拍出的照片
<div v-for="(item, index) in photoList" :key="index">
<img :src="item" alt="" />
</div>
</div>
<div v-if="videoSrc">
已录制的视频
<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];
}
},
// 开始录像
handleStartReord: () => {
carmareContext.startRecord({
success: (res: any) => {
console.log("handleStartReord-success", res);
uni.showToast({
title: "开始录像",
});
},
fail: (error: any) => {
console.error("handleStartReord-error", error);
},
});
},
// 停止录像
handleStopRecord: () => {
carmareContext.stopRecord({
success: (res: any) => {
console.log("handleStopRecord-success", res);
uni.showToast({
title: "停止录像",
});
videoSrc.value = res.tempVideoPath;
},
fail: (error: any) => {
console.error("handleStopRecord-error", error);
},
});
},
};
},
components: { LayoutMain },
});
</script>
Нажмите на реальную отладку машины в инструменте разработчика WeChat, эффект будет таким, как показано на рисунке:
аудио компоненты
Кроме того, Uniapp также предоставляетcreateInnerAudioContext()
, который создает и возвращает внутренний аудиоконтекстinnerAudioContext
объект.
API совместим с H5 и App. На стороне IOS этот компонент поддерживает меньше форматов, только форматы m4a, wav, mp3, aac, aiff и caf. Но это будет относительно больше на стороне Android. Он также дает краткий ответ Демо для отладки.
<template>
<LayoutMain>
<template v-slot:container>
<button @click="handleStartRecord">开始录音</button>
<button @click="handleEndRecord">结束录音</button>
<button @click="handlePlay">播放录音</button>
<button @click="handlePausePlay">暂停播放录音</button>
<button @click="handlePausePlay">暂停播放录音</button>
<button @click="handleEndPlay">结束播放录音</button>
<div>
操作记录
<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("开始录音");
});
current.onStop(function (res: any) {
operateRecordList.value.push("结束录音");
console.log("recorder stop" + JSON.stringify(res));
voicePath = res.tempFilePath;
});
current.onPause(function () {
operateRecordList.value.push("暂停录音");
});
});
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("开始播放");
});
current.onPause(() => {
operateRecordList.value.push("暂停播放");
});
current.onStop(() => {
operateRecordList.value.push("结束播放");
});
});
return {
operateRecordList,
handleStartRecord: () => {
recorderManager.start({
duration: 60000, //录音的时长,单位 ms,最大值 600000(10 分钟)
format: "mp3",
});
},
handleEndRecord: () => {
recorderManager.stop();
},
handlePlay: () => {
innerAudioContext.src = voicePath;
innerAudioContext.play();
},
handleEndPlay: () => {
innerAudioContext.stop();
},
handlePausePlay: () => {
innerAudioContext.pause();
},
};
},
};
</script>
Запись и воспроизведение не очень удобно отображать, поэтому скриншотов нет, заинтересованные друзья могут их скопировать и использовать сами.
яма столкнулась
layout
Упомянутые выше компоненты макета обнаруживаются, когда работают компьютеры разных коллег, и есть несколько отдельных случаев, когда макет не действует, то есть страница не оборачивает слой макета.
Зарегистрируйтесь глобально в 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);
// 全局注册组件
app.component("Layout", Layout);
app.mount("#app");
Используйте на странице (здесь макет не вступает в силу, однако мой компьютер вступает в силу):
<template>
<Layout>
<template v-slot:mains>
<div>分类页</div>
</template>
</Layout>
</template>
Это все еще тот случай, когда гарантируется, что среда такая же, плагины и все аспекты не затронуты.Пока неясно, что происходит.Я надеюсь, что кто-то может указать, что текущий план состоит в том, чтобыLayout
переименовать вLayoutMain
вступает в силу немедленно. (черный вопросительный знак?)
image
Ленивая загрузка компонента Image, который идет с Uniapp, не действует, этот момент проверен, и есть подозрение, что атрибут lazy-load является украшением QA Q.
Визуально мне приходится вручную реализовывать ленивую загрузку изображений.
О передаче компонента по значению
Предположим, у вас есть следующие компоненты:
<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>
Вы можете видеть, что hello имеет тип String, а значение по умолчанию — fff.
Но когда hello = undefined, hello будет отображать пустую строку "". "fff", если приветствие не передано.
наконец
Вы все это здесь видели, не ставите лайки и комментарии перед уходом?
Обратите внимание на паблик-аккаунт: передняя часть куков, сблизьте нас с вами (^▽^)