1,关于阿里播放器使用过的几种播放方式

url (source)

① 要在创建播放器前要拿到资源否则会报错

② 在有不同清晰度的资源时  直接调用 player.loadByUrl() 方法会报错 官网给出的例子:JSON.stringify({ 'FD': videoUrl.Fd, 'SD': videoUrl.Sd })   (首次可以播,但在切换时资源路径会出错,官网的demo只有单个视频,无法测试切换)

解决方法是 直接抛弃 player.loadByUrl()  ,每次销毁播放器,然后重新创建。

vid+playauth:

这种方式清晰度不需要自己设置,但坑比较多。在F5刷新后视频组件会丢失,只剩下封面,并且此类型必须额外写入一些配置,指定清晰度等等。

这种方式切换视频源不用player.loadByUrl() ,要用  player.replayByVidAndPlayAuth(this.vid, this.playauth)  方法

        // vid+playauth播放
        // vid: this.vid,
        // playauth: this.playauth,
        // encryptType: '1', // 播放加密视频
        // qualitySort: "asc", // 指定排序方式,只有使用Vid + PlayAuth播放方式时支持。
        // format: "m3u8", // 指定播放地址格式,只有使用vid的播放方式时支持可选值。
        // definition: "FD,LD,SD,HD,OD,2K,4K", // 此值是vid对应流清晰度的一个子集,仅H5模式支持。
        // defaultDefinition: 'SD', // 默认视频清晰度,此值是vid对应流的一个清晰度,仅H5模式支持。

2,需注意这几种事件的坑,在记录上报,播放时常要在 播放中playing 而不是 播放play 中执行,播放完毕后顺序播放可以用自带组件,这里是自己实现,但是有个bug是 ended播放完毕回调中 事件会执行两次!这里是设置了个开关来限制的。

3,配置中有个 skinLayout 可以设置一些播放组件,其中 setting 里自带倍速,字幕,音轨,清晰度,但是字幕和音轨这里用不到,还没法去掉。但是注释了setting又会导致 倍速和清晰度不见了。

这里官方提供了 components组件

components: [
          { name: 'RateComponent', type: AliPlayerComponent.RateComponent },
          { name: 'MemoryPlayComponent', type: AliPlayerComponent.MemoryPlayComponent },
          {
            name: 'QualityComponent',
            type: AliPlayerComponent.QualityComponent
          }],
 
RateComponent 为倍速
MemoryPlayComponent 历史记录
QualityComponent 是清晰度 ,这里有个坑,就是原本项目中引入的是   https://g.alicdn.com/de/prismplayer/2.8.1/aliplayer-min.js   (2.8.1版本的js和css)导致 一引入 清晰度组件就会报错,一只到不到原因,后来修改为 2.9.23版本的,报错消失。
 
4,倍速功能,因为有之前的坑,每次会销毁播放器重新创建,所以导致播放下一个视频时倍速会重置成1,官网还没有提供切换倍速抛出的事件,所以就捕获不到倍速的改变,也就无法在下一个时去设置倍速。只能手写一个倍速盒子点击时去对接阿里的方法。
 
音乐播放器的代码:
<template>
<!-- 容器 -->
<div class="container">
<!-- 音频播放器 -->
<transition name="el-zoom-in-top">
<div v-show="isShowPlayer" class="audioWrapper">
<div class="header">
<div class="title">正在播放 {{playingAudioName}}</div>
<img class="close" src="@/assets/image/close.png" alt="关闭播放器" @click="closePlayer">
</div>
<div class="audio-box" v-loading="playerLoading">
<img :src="play?pauseIcon:playIcon" alt="播放暂停" @click="playHandle()">
<div class="audio-progress">
<el-slider v-model="sliderVal" :format-tooltip="formatTooltip" :min="sliderMin" :max="sliderMax" @change="sliderChange"></el-slider>
<div id="aliPlayer"></div>
<div class="audio-info">
<div>
<span class="current-time">{{currentTime}}</span>
<span class="time-middle">/</span>
<em class="time-long">{{duration}}</em>
</div>
<div class="util-box">
<span @click="isShowRateList = !isShowRateList">倍速{{rateVal}}x</span>
<ul v-if="isShowRateList">
<li v-for="(t,i) in playBackRateList" :key="i" @click="changeRate(t)">{{t}}</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</transition>
</div>
</template> <script>
import { courseStore } from "@/store/course.js";
import { tokenStore } from "@/store/user.js";
export default {
layout: 'detail',
name: 'CourseCatalogue', // 校验动态路由参数的有效性
validate({ params, query }) { },
// 获取详情数据
async asyncData({ $axios, params }) {
return { }
}, // 处理数据
created() { }, mounted() {
// 初始化播放器
this.createPlayer() }, // 销毁定时器及阿里播放器
beforeDestroy() {
this.stopReportLearning()
if (this.player) {
this.player.dispose()
this.player = null
}
}, //
computed: { },
data() {
return {
store: courseStore(), // 课程商店
userStore: tokenStore(), // 用户商店 seeked: true, // 是否可续播
// 播放相关
canplay: false,// 是否可以播放
isShowPlayer: false,// 是否显示播放器
playerLoading: true, // 播放器loading
playingAudioName: '', // 正在播放的课程名称
player: null, // 播放器
timer: null, // 学习上报定时器
playDate: '', // 当前视频的时间戳
opener: true,
playerUrl: '/a.m3u8', // 播放的资源路径
sliderVal: 0, // 滑块当前时长。
sliderMin: 0,
sliderMax: 0, // 滑块的总时长。
currentTime: '00:00', // 当前播放时间
duration: '00:00', // 总时长
play: false, // 播放ing?暂停?
// 播放/暂停图标
playIcon: require('@/assets/image/play.png'),
pauseIcon: require('@/assets/image/pause.png'),
isShowRateList: false, // 是否展示倍速列表
rateVal: 1, // 当前倍速
playBackRateList: [0.25, 0.5, 0.75, '正常', 1.25, 1.5, 1.75, 2], // 倍速
}
},
methods: {
// 获取vid+playauth
getVid() { },// 初始化播放器
createPlayer() {
let prop = {
id: "aliPlayer",
source: this.playerUrl,
mediaType: "audio",
width: "100%",
height: "100%",
autoplay: false, // 自动播放
isLive: false, // 直播
rePlay: false, // 循环播放
playsinline: true, //H5是否内置播放
preload: true, //播放器自动加载
language: "zh-cn", // 语言
cover: '', //播放器默认封面图片,需要autoplay为’false’时,才生效
controlBarVisibility: "hover", //控制面板的实现 ‘click’ 点击出现、‘hover’ 浮动出现、‘always’ 一直在
useH5Prism: true //指定使用H5播放器
} this.player = new Aliplayer(prop, function (player) {
console.log("The player is created");
}) // 播放 (由暂停恢复为播放时触发)
this.player.on('play', () => {
this.play = true
}) // 暂停
this.player.on('pause', () => {
this.play = false
this.stopReportLearning()
}) // 播放ing (播放中,会触发多次)
this.player.on('playing', () => {
// console.log('播放中ing') // 学习记录上报
this.learningReport()
}) // 可以播放
this.player.on('canplay', () => {
this.playerLoading = false
const courseInfo = this.store.getCourseInfo
if (this.canplay && this.seeked && courseInfo.duration && courseInfo.newestLessonId === courseInfo.lessonId) {
this.seeked = false
this.player.seek(courseInfo.duration)
}
}) // 等待缓冲
this.player.on('waiting', () => {
this.playerLoading = true
}) // 时长发生变动时
this.player.on('timeupdate', () => {
this.updateTime()
}) // 播放出错
this.player.on('error', () => {
this.$message.error('播放出错')
this.stopReportLearning()
this.sliderVal = 0
this.play = false
this.playerLoading = true
this.isShowPlayer = false
this.store.setIsPlaying(false)
}) // 播放完毕
this.player.on("ended", () => {
if (this.opener) {
this.opener = false
console.log('播放完了!') // 停止学习上报
this.stopReportLearning()
this.playDate = '' // 调用子组件方法,自动播放下一个
const courseInfo = this.store.getCourseInfo
this.$refs.CourseCurriculum.createdPlayList(courseInfo.courseId, courseInfo.lessonId)
}
});
}, // 学习记录上报
learningReport() { // 先清空
this.stopReportLearning() const courseInfo = this.store.getCourseInfo
const lessonAudioVideo = courseInfo.lessonAudioVideo // 播放时长小于1时不进行上报
// if (curTime <= 1) {
// return
// } if (this.playDate === '') {
const nowDate = new Date
this.playDate = nowDate.getTime()
} // 学次唯一值
const userInfo = this.userStore.getUserInfo
const key = `${userInfo.userId}_${courseInfo.orderId}_${courseInfo.projectId}_${courseInfo.courseId}_${courseInfo.lessonId}_${this.playDate}`;
const sessionKey = this.$md5(key) this.timer = setInterval(() => {
const curTime = parseInt(this.player.getCurrentTime())
this.$axios.$post('/hadoop/learning/learningRecordSend', {
sessionKey: sessionKey,
orderId: courseInfo.orderId,
projectId: courseInfo.projectId,
courseId: courseInfo.courseId,
courseCatalogId: courseInfo.lessonId,
resourceType: lessonAudioVideo.type,
resourceId: lessonAudioVideo.audioId,
duration: curTime
}).then(() => { }).catch((err) => {
if (err.code === 10009) this.player.pause()
});
}, 5000)
}, // 停止上报学习记录
stopReportLearning() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
}, // 关闭播放器
closePlayer() {
this.isShowPlayer = false
this.store.setIsPlaying(false)
this.stopReportLearning()
this.play = false
this.playerLoading = true
this.player.pause()
}, // 播放/暂停
playHandle() {
this.play = !this.play
this.play ? this.player.play() : this.player.pause()
}, // 更新时间
updateTime() {
if (!Number.isFinite(this.player.getDuration())) {
this.currentTime = Number.MAX_SAFE_INTEGER;
this.currentTime = '00:00';
} else {
const total = this.formatTime(this.player.getDuration())
const current = this.formatTime(this.player.getCurrentTime())
this.duration = `${total.min}:${total.sec}`
this.currentTime = `${current.min}:${current.sec}`
this.sliderVal = Math.floor(this.player.getCurrentTime())
this.sliderMax = Math.floor(this.player.getDuration())
}
}, // 格式化毫秒,返回String型分秒对象
formatTime(time) {
// 如果没获取到
if (!time) return { min: '00', sec: '00' }
return {
min: Math.floor(time / 60).toString().padStart(2, '0'),
sec: Math.floor(time % 60).toString().padStart(2, '0')
}
}, // 格式化毫秒数,对应elm滑块组件
formatTooltip(val) {
const time = this.formatTime(val)
return `${time.min}:${time.sec}`
}, // 滑块松动后触发。更新当前时长,
// 时长发生变动会init里的方法进行更新数据
sliderChange(val) {
this.player.seek(this.sliderVal)
}, // 倍速改变时
changeRate(t) {
if (t === '正常') t = 1;
this.rateVal = t
this.player.setSpeed(t)
this.isShowRateList = false
},
}
}
</script>

视频播放代码:

<template>
<div class="wrap">
<!-- 视频区域 -->
<div class="video" :class="{'w1': sideBarFlag}"> <!-- 视频 -->
<div class="player" id="aliVideoPlayer"></div> </div> <!-- 侧边栏区域 --> </div>
</template> <script>
import { courseStore } from "@/store/course.js";
import { tokenStore } from "@/store/user.js";
export default {
layout: 'index',
async asyncData({ $axios, query }) {
const [chapterList] = await Promise.all([
// 获取视频资源
// $axios.$get(`/resources/resources/video/${lessonAudioVideo.videoId}`),
// 获取课程列表
$axios.$get('/learning/user/project/pc/catalog/v1', {
params: {
projectId: query.projectId,
courseId: query.courseId
}
}),
])
return {
// videoData,
chapterList
}
},
data() {
return {
store: courseStore(), // 课程商店
userStore: tokenStore(), // 用户商店
courseInfo: {},// 当前播放的课程相关信息
player: null, // 播放器
opener: true,
playerUrl: '', // 播放的资源url
vid: '', // vid + playauth
playauth: '',
lessonName: '', // 当前播放的课次名称
seeked: true, // 是否可续播
speed: 1, // 倍速
// 侧边栏
sideBarFlag: true,
// 目录/笔记
listFlag: true,
// 学习上报定时器
timer: null,
// 当前视频的时间戳
playDate: '',
}
}, beforeMount() {
this.courseInfo = this.store.getCourseInfo
// 获取视频资源url
// this.playerUrl = this.courseInfo.lessonAudioVideo.url
}, async mounted() {
// 获取视频vid+playauth
const videoInfo = await this.getVid()
this.playerUrl = JSON.stringify({ 'FD': videoInfo.Fd, 'SD': videoInfo.Sd })
// this.vid = videoInfo.VideoId
// this.playauth = videoInfo.PlayAuth
// 初始化播放器
this.createPlayer()
}, // 销毁定时器
beforeDestroy() {
this.stopReportLearning()
if (this.player) {
this.player.dispose()
this.player = null
}
}, methods: {
// 获取vid+playauth
getVid() {
return this.$axios.$get(`/file/sys/file/getVideoPlayUrl`, {
params: {
videoId: this.courseInfo.lessonAudioVideo.resourcesId
}
})
},
// 初始化播放器
createPlayer() {
let prop = {
id: "aliVideoPlayer", // 资源url播放
source: this.playerUrl, // vid+playauth播放
// vid: this.vid,
// playauth: this.playauth,
// encryptType: '1', // 播放加密视频
// qualitySort: "asc", // 指定排序方式,只有使用Vid + PlayAuth播放方式时支持。
// format: "m3u8", // 指定播放地址格式,只有使用vid的播放方式时支持可选值。
// definition: "FD,LD,SD,HD,OD,2K,4K", // 此值是vid对应流清晰度的一个子集,仅H5模式支持。
// defaultDefinition: 'SD', // 默认视频清晰度,此值是vid对应流的一个清晰度,仅H5模式支持。 mediaType: "video",
width: "100%",
height: "100%",
autoplay: true, // 自动播放
isLive: false, // 直播
rePlay: false, // 循环播放
playsinline: true, //H5是否内置播放
preload: true, //播放器自动加载
keyShortCuts: true, // 是否启用快捷键
language: "zh-cn", // 语言
cover: '', //播放器默认封面图片,需要autoplay为’false’时,才生效
controlBarVisibility: "hover", //控制面板的实现 ‘click’ 点击出现、‘hover’ 浮动出现、‘always’ 一直在
useH5Prism: true, //指定使用H5播放器
components: [
{ name: 'RateComponent', type: AliPlayerComponent.RateComponent },
{ name: 'MemoryPlayComponent', type: AliPlayerComponent.MemoryPlayComponent },
{
name: 'QualityComponent',
type: AliPlayerComponent.QualityComponent
}],
skinLayout: [
{ name: "bigPlayButton", align: "blabs", x: 30, y: 80 },
{ name: "H5Loading", align: "cc" },
{ name: "errorDisplay", align: "tlabs", x: 0, y: 0 },
{ name: "infoDisplay" },
{ name: "tooltip", align: "blabs", x: 0, y: 56 },
{ name: "thumbnail" },
{
name: "controlBar", align: "blabs", x: 0, y: 0,
children: [
{ name: "progress", align: "blabs", x: 0, y: 44 },
{ name: "playButton", align: "tl", x: 15, y: 12 },
{ name: "timeDisplay", align: "tl", x: 10, y: 7 },
{ name: "fullScreenButton", align: "tr", x: 10, y: 12 },
// { name: "subtitle", align: "tr", x: 15, y: 12 },
// { name: "setting", align: "tr", x: 15, y: 12 },
{ name: "volume", align: "tr", x: 5, y: 10 }
]
}
]
} this.player = new Aliplayer(prop, function (player) {
console.log("The player is created");
player.on('sourceloaded', function (params) {
var paramData = params.paramData
var desc = paramData.desc
var definition = paramData.definition
// 获取清晰度组件并调用清晰度组件的 setCurrentQuality 设置清晰度
player.getComponent('QualityComponent').setCurrentQuality(desc, definition)
}) // 是否有设置过倍速
if (this.speed) {
player.setSpeed(this.speed)
}
}) // 播放
this.player.on('play', () => {
// console.log('开始播放')
}) // 播放ing
this.player.on('playing', () => {
// console.log('播放中ing') this.speed = this.player.tag.playbackRate // 学习记录上报
this.learningReport()
}) // 可以播放
this.player.on('canplay', () => {
if (this.seeked && this.courseInfo.duration && this.courseInfo.newestLessonId === this.courseInfo.lessonId) {
this.seeked = false
this.player.seek(this.courseInfo.duration)
}
}) // 暂停
this.player.on('pause', () => {
// console.log('播放暂停') // 停止上报
this.stopReportLearning()
}) // 播放出错
this.player.on('error', () => {
this.$message.error('播放出错')
}) // 播放完毕
this.player.on("ended", (e) => {
if (this.opener) {
this.opener = false
console.log('播放完了!!!!!')
this.stopReportLearning()
this.playDate = '' // 调用子组件方法,自动播放下一个
this.$refs.videoList.createdPlayList(this.courseInfo.lessonId)
}
})
}, // 接受到子组件课次变化,播放新的视频
async lessonChange(doing) { this.courseInfo = this.store.getCourseInfo // 未告知要播放时,只展示课次名字
if (doing !== 'play') {
this.lessonName = this.courseInfo.lessonName
return
} // 检测播放源是否相同(目前不进行检测,否则导致点击相同资源时->无法重播)
// if (this.playerUrl !== this.courseInfo.lessonAudioVideo.url) { // 更改视频源 // TODO: url
// this.playerUrl = this.courseInfo.lessonAudioVideo.url
// this.player.loadByUrl(this.playerUrl) // TODO: vod
const videoUrl = await this.getVid()
// this.playerUrl = videoInfo.Sd
// this.player.loadByUrl(this.playerUrl)
this.player.dispose()
this.playerUrl = JSON.stringify({ 'FD': videoUrl.Fd, 'SD': videoUrl.Sd })
this.createPlayer() // TODO: vid+playauth
// this.vid = videoInfo.VideoId
// this.playauth = videoInfo.PlayAuth
// this.player.replayByVidAndPlayAuth(this.vid, this.playauth) // } else {
// this.player.play()
// } this.lessonName = this.courseInfo.lessonName
this.seeked = true
this.opener = true
}, // 停止上报学习记录,清楚定时器
stopReportLearning() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
}, // 学习记录上报
learningReport() {
// 先清空
this.stopReportLearning() const courseInfo = this.courseInfo
const lessonAudioVideo = this.courseInfo.lessonAudioVideo if (this.playDate === '') {
const nowDate = new Date
this.playDate = nowDate.getTime()
// console.log(this.playDate)
} // 学次唯一值
const userInfo = this.userStore.getUserInfo
const key = `${userInfo.userId}_${courseInfo.orderId}_${courseInfo.projectId}_${courseInfo.courseId}_${courseInfo.lessonId}_${this.playDate}`;
const sessionKey = this.$md5(key) this.timer = setInterval(() => {
const curTime = parseInt(this.player.getCurrentTime())
this.$axios.$post('/hadoop/learning/learningRecordSend', {
sessionKey: sessionKey,
orderId: courseInfo.orderId,
projectId: courseInfo.projectId,
courseId: courseInfo.courseId,
courseCatalogId: courseInfo.lessonId,
resourceType: lessonAudioVideo.type,
resourceId: lessonAudioVideo.videoId,
duration: curTime
}).then(() => { }).catch((err) => {
if (err.code === 10009) this.player.pause()
});
}, 5000)
}
}
}
</script>
 

阿里播放器Aliplayer遇到的所有坑的更多相关文章

  1. 阿里播放器踩坑记录 进度条重构 video loadByUrl失效解决方案

    如果本文对你有用,请爱心点个赞,提高排名,帮助更多的人.谢谢大家!❤ 如果解决不了,可以在文末进群交流. 文档地址:https://player.alicdn.com/aliplayer/index. ...

  2. vue+element-ui中引入阿里播放器

    1.在public文件下的index.html文件中插入以下代码: <link rel="stylesheet" href="https://g.alicdn.co ...

  3. 阿里云web播放器

    原文地址:https://help.aliyun.com/document_detail/51991.html?spm=5176.doc61109.6.703.ZTCYoi 一.概念说明 1. pla ...

  4. 如何利用阿里视频云开源组件,快速自定义你的H5播放器?

    摘要: Aliplayer希望提供一种方便.简单.灵活的机制,让客户能够扩展播放器的功能,并且Aliplayer提供一些组件的基本实现,用户可以基于这些开源的组件实现个性化功能,比如自定义UI和自己A ...

  5. 阿里云Prismplayer-Web播放器的使用

    Prismplayer是一套在线视频播放技术方案,同时支持Flash和Html5两种播放技术,可对播放器进行功能配置和皮肤定制.其在线使用文档地址为:https://help.aliyun.com/d ...

  6. 前端视频插件Aliplayer播放器简单使用(基于地址播放)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http ...

  7. RTSP播放器开发填坑之道

    好多开发者提到,在目前开源播放器如此泛滥的情况下,为什么还需要做自研框架的RTSP播放器,自研和开源播放器,到底好在哪些方面?以下大概聊聊我们的一点经验,感兴趣的,可以关注 github: 1. 低延 ...

  8. RTMP播放器开发填坑之道

    好多开发者提到,在目前开源播放器如此泛滥的情况下,为什么还需要做自研框架的RTMP播放器,自研和开源播放器,到底好在哪些方面?以下大概聊聊我们的一点经验,感兴趣的,可以关注 github: 1. 低延 ...

  9. Web播放器

    web视频播放器的使用及遇到的问题记录 TcPlayer播放器(腾讯Web超级播放器) https://cloud.tencent.com/document/product/881/20207 Ste ...

  10. iOS多播放器封装

    今年在做直播业务的时候遇到一些问题,就是在一个套播放器UI中需要多种不同的播放器(AVPlayer.IJKPlayer.AliPlayer)支持,根据ABTest开关来切换具体使用哪种播放器,并且还要 ...

随机推荐

  1. ionic+vue+capacitor系列笔记--03项目使用Native插件

    话不多说,直接上代码 下载依赖 npm install @capacitor/camera 添加权限配置代码到安卓文件夹里的 AndroidManifest.xml <uses-permissi ...

  2. ionic+vue+capacitor系列笔记--01项目初始化

    Ionic 是什么? Ionic 是一款接近原生的 Html5 移动 App 开发框架,只需要你会 HTML.CSS 和 JavaScript 就可以开发移动 App应用,使用最基础的 Web 技术创 ...

  3. Ubuntu安装Anaconda并且配置国内镜像教程

    前言 我们在学习 Python 的时候需要不同的 Python 版本,关系到电脑环境变量配置换来换去很是麻烦,所以这个时候我们需要一个虚拟的 Python 环境变量,我之前也装过 virtualenv ...

  4. 【单片机】nRF52832 实现停止蓝牙广播接口

    前言 有一个项目使用了 nRF52832 芯片作为主控,其中有用到蓝牙功能.在对蓝牙接口进一步封装的时候,发现 SDK 居然没有停止广播的接口,咨询了代理 FAE,对方也没有找到关闭广播的接口.后来通 ...

  5. 【Django drf】视图层大总结 ViewSetMixin源码分析 路由系统 action装饰器

    目录 九个视图子类 视图集 继承ModelViewSet类写五个接口 继承 ReadOnlyModelView编写2个只读接口 ViewSetMixin源码分析 查找as_view方法 setattr ...

  6. 洛谷 P3137 [USACO16FEB]Circular Barn S

    题目链接 本蒟蒻的第一篇题解,写得不好请指出,敬请谅解 题意: 有\(n\)头奶牛,分布在一些房间,某些房间可能有多头牛,要让这些牛按顺时针移动,求使每一个房间刚好有一个奶牛的最小花费 花费计算:如果 ...

  7. 第九周总结-MySQL、前端

    多表查询的两种方式 1.连表操作: 1.1: inner join:内连接,将两张表共同的部分连接在一起生成一张新表.凭借顺序是把后面的表拼在前面表的后面,如果颠倒位置结果不同. select * f ...

  8. 元数据库 information_schema.tables

    转  https://www.cnblogs.com/ssslinppp/p/6178636.html 1.information_schema数据库 对于mysql和Infobright等数据库,i ...

  9. 6、Collections工具类

    1.Collections工具类介绍 Collections 是一个操作 Set.List 和 Map 等集合的工具类 Collections 中提供了一系列静态的方法对集合元素进行排序.查询和修改等 ...

  10. P4_创建第一个小程序项目

    设置外观和代理 创建小程序项目 点击"加号"按钮 填写项目信息 项目创建完成 在模拟器上查看项目效果 在真机上预览项目效果 主界面的 5 个组成部分