
查询和获取权限为一体的插件、获取权限状态、请求权限授权、AppTracking、通知权限、权限一体化
查询和获取权限为一体的插件、获取权限状态、请求权限授权、AppTracking、通知权限、权限一体化:https://ext.dcloud.net.cn/plugin?id=8674
查询和获取权限为一体的插件、获取权限状态、请求权限授权、AppTracking、通知权限、权限一体化:https://ext.dcloud.net.cn/plugin?id=8674
收起阅读 »
uniapp 实现nvue模拟顶部双吸顶+左右滑动切换选项卡(适用于视频播放详情,支持Android、iOS)
场景说明
在app中用nvue实现视频播放页面,页面分为顶部视频区、课程标题区、tab选项卡区
需求1:用自定义黑色view占位标题栏区域,自定义状态栏占位区和视频区固定在顶部;
需求2:课程标题区随着页面上下滑动而滚动
需求3:tab选项卡区滑动到视频区底部时,自动吸顶
需求4:tab的每个子选项卡滚动高度各自独立
需求5:隐藏页面滚动条,仅显示tab选项卡区滚动条

index.nvue
<template>
<!-- :show-scrollbar="false" 不显示页面的滚动条 -->
<!-- :bounce="true" 开启iOS回弹效果 -->
<list id="page" class="page" :bounce="true" :show-scrollbar="false" isSwiperList="true">
<!-- 课程封面/视频播放器 -->
<!-- 固定吸在顶部 -->
<header>
<view id="head">
<!-- 黑色状态栏 -->
<view class="status-bar" :style="'height:' + statusHeight + 'px'">
</view>
<!-- 封面区/视频区 -->
<view class="cover-box">
<!--
课程封面/视频播放器区
tab选项卡总高度:{{pageHeight}}
tab选项卡吸顶高度:{{_headHeight}}(iOS端生效)
-->
<video title="uniapp nvue顶部双吸顶+左右滑动切换选项卡" src="https://api.dogecloud.com/player/get.mp4?vcode=5ac682e6f8231991&userId=17&ext=.mp4" objectFit="fill"
poster="https://i.loli.net/2019/06/06/5cf8c5d9c57b510947.png" style="width:750rpx;height: 200px;"></video>
</view>
</view>
</header>
<!-- 课程标题 -->
<!-- 不固定,随着竖向滚动条滑动 -->
<cell>
<view id="intro-box" class="intro-box">
课程标题区
</view>
</cell>
<!-- 可横向滑动切换选项卡 -->
<!-- 滑到封面区/视频区底部位置,自动吸顶 -->
<cell>
<view class="tabs" :style="'height:' + pageHeight + 'px'">
<scroll-view ref="tabbar1" id="tab-bar" class="tab-bar" :scroll="false" :scroll-x="false"
:show-scrollbar="false" :scroll-into-view="scrollInto">
<view style="flex-direction: column;">
<view style="flex-direction: row;">
<view class="uni-tab-item" id="tab0" ref="tabitem0" data-id="0" data-current="0"
@click="ontabtap">
<text class="uni-tab-item-title"
:class="tabIndex==0 ? 'uni-tab-item-title-active' : ''">详情</text>
</view>
<view class="uni-tab-item" id="tab1" ref="tabitem1" data-id="1" data-current="1"
@click="ontabtap">
<text class="uni-tab-item-title"
:class="tabIndex==1 ? 'uni-tab-item-title-active' : ''">目录</text>
</view>
<view class="uni-tab-item" id="tab2" ref="tabitem2" data-id="2" data-current="2"
@click="ontabtap">
<text class="uni-tab-item-title"
:class="tabIndex==2 ? 'uni-tab-item-title-active' : ''">评论</text>
</view>
<view class="uni-tab-item" id="tab3" ref="tabitem3" data-id="3" data-current="3"
@click="ontabtap">
<text class="uni-tab-item-title"
:class="tabIndex==3 ? 'uni-tab-item-title-active' : ''">资料</text>
</view>
</view>
<view class="scroll-view-indicator">
<view ref="underline" class="scroll-view-underline"
:class="isTap ? 'scroll-view-animation':''"
:style="{left: indicatorLineLeft + 'px', width: indicatorLineWidth + 'px'}"></view>
</view>
</view>
</scroll-view>
<view class="tab-bar-line"></view>
<swiper class="tab-view" ref="swiper1" id="tab-bar-view" :current="tabIndex" :duration="300"
@change="onswiperchange" @transition="onswiperscroll" @animationfinish="animationfinish"
@onAnimationEnd="animationfinish">
<swiper-item class="swiper-item">
<!-- 详情 -->
<view class="uni-swiper-page">
<list ref="list0" class="list" :offset-accuracy="5" :bounce="true" isSwiperList="true">
<cell @click="onclick">
课程简介内容
</cell>
<cell class="loading"></cell>
</list>
</view>
</swiper-item>
<swiper-item class="swiper-item">
<!-- 目录 -->
<view class="uni-swiper-page">
<list ref="list1" class="list" :offset-accuracy="5" :bounce="true" isSwiperList="true">
<cell @click="onclick">
<view class="list-item" v-for="(item,index) in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49]">
<text>章节{{item}}</text>
</view>
</cell>
<cell class="loading"></cell>
</list>
</view>
</swiper-item>
<swiper-item class="swiper-item">
<!-- 评论 -->
<view class="uni-swiper-page">
<list ref="list2" class="list" :offset-accuracy="5" :bounce="true" isSwiperList="true">
<cell @click="onclick">
<view class="list-item" v-for="(item,index) in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49]">
<text>评论{{item}}</text>
</view>
</cell>
<cell class="loading"></cell>
</list>
</view>
</swiper-item>
<swiper-item class="swiper-item">
<!-- 资料 -->
<view class="uni-swiper-page">
<list ref="list3" class="list" :offset-accuracy="5" :bounce="true" isSwiperList="true">
<cell @click="onclick">
<view class="list-item" v-for="(item,index) in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49]">
<text>资料{{item}}</text>
</view>
</cell>
<cell class="loading"></cell>
</list>
</view>
</swiper-item>
</swiper>
</view>
</cell>
</list>
</template>
<script>
// #ifdef APP-PLUS
const dom = weex.requireModule('dom');
// #endif
// 缓存每页最多
const MAX_CACHE_DATA = 100;
// 缓存页签数量
const MAX_CACHE_PAGE = 3;
const TAB_PRELOAD_OFFSET = 1;
export default {
data() {
return {
tabIndex: 0,
cacheTab: [],
scrollInto: "",
indicatorLineLeft: 0,
indicatorLineWidth: 0,
isTap: false,
showTitleView: true,
statusHeight: 44,
pageHeight: 300,
refreshing: false,
refreshText: "",
refreshFlag: false
}
},
onLoad() {
},
onReady() {
let inf = uni.getSystemInfoSync();
this.statusHeight = inf.statusBarHeight; // 状态栏高度
this.pageHeight = inf.windowHeight - this.statusHeight - 200; // 页面高度
this._lastTabIndex = 0;
this.swiperWidth = 0;
this.tabbarWidth = 0;
this.tabListSize = {};
this._touchTabIndex = 0;
this._headHeight = 100;
this.selectorQuery();
// #ifdef APP-PLUS
plus.navigator.setStatusBarStyle("light"); //白色
// #endif
},
methods: {
setScrollRef(index, height) {
// Android不支持setSpecialEffects , 仅iOS机型生效
if (this.$refs['list' + index].setSpecialEffects) {
this.$refs['list' + index].setSpecialEffects({
id: "page",
headerHeight: height //设置iOS端Tabs距离顶部的吸顶距离
});
}
},
onclick(e) {
console.log("onclick");
},
loadData() {
// 首次激活时被调用
},
clear() {
// 释放数据时被调用,参考 swiper-list 缓存配置
},
ontap1(e) {
console.log("ontap1", e);
},
ontabtap(e) {
let index = e.target.dataset.current || e.currentTarget.dataset.current;
//let offsetIndex = this._touchTabIndex = Math.abs(index - this._lastTabIndex) > 1;
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-QQ
this.isTap = true;
var currentSize = this.tabListSize[index];
this.updateIndicator(currentSize.left, currentSize.width);
this._touchTabIndex = index;
// #endif
this.switchTab(index);
},
onswiperchange(e) {
// 注意:百度小程序会触发2次
// #ifndef APP-PLUS || H5 || MP-WEIXIN || MP-QQ
let index = e.target.current || e.detail.current;
this.switchTab(index);
// #endif
},
onswiperscroll(e) {
if (this.isTap) {
return;
}
var offsetX = e.detail.dx;
var preloadIndex = this._lastTabIndex;
if (offsetX > TAB_PRELOAD_OFFSET) {
preloadIndex++;
} else if (offsetX < -TAB_PRELOAD_OFFSET) {
preloadIndex--;
}
if (preloadIndex === this._lastTabIndex || preloadIndex < 0 || preloadIndex > 4 - 1) {
return;
}
// if (this.pageList[preloadIndex].dataList.length === 0) {
// this.loadTabData(preloadIndex);
// }
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-QQ
var percentage = Math.abs(this.swiperWidth / offsetX);
var currentSize = this.tabListSize[this._lastTabIndex];
var preloadSize = this.tabListSize[preloadIndex];
var lineL = currentSize.left + (preloadSize.left - currentSize.left) / percentage;
var lineW = currentSize.width + (preloadSize.width - currentSize.width) / percentage;
this.updateIndicator(lineL, lineW);
// #endif
},
animationfinish(e) {
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-QQ
let index = e.detail.current;
if (this._touchTabIndex === index) {
this.isTap = false;
}
this._lastTabIndex = index;
this.switchTab(index);
this.updateIndicator(this.tabListSize[index].left, this.tabListSize[index].width);
// #endif
},
selectorQuery() {
// #ifdef APP-NVUE
// uni.createSelectorQuery().in(this).select('#head').boundingClientRect().exec(rect => {
// // 获取封面区高度
// this._headHeight = rect[0].height;
// });
uni.createSelectorQuery().in(this).select('#intro-box').boundingClientRect().exec(rect => {
// 获取课程简介区的位置,设为iOS端Tabs距离顶部的吸顶距离
this._headHeight = rect[0].height - 1;
console.log('3 introHeight', this._headHeight);
});
// 查询 tabbar 宽度
uni.createSelectorQuery().in(this).select('#tab-bar').boundingClientRect().exec(rect => {
this.tabbarWidth = rect[0].width;
});
// 查询 tabview 宽度
uni.createSelectorQuery().in(this).select('#tab-bar-view').boundingClientRect().exec(rect => {
this.swiperWidth = rect[0].width;
});
// 因 nvue 暂不支持 class 查询
var queryTabSize = uni.createSelectorQuery().in(this);
queryTabSize.select('#tab0').boundingClientRect();
queryTabSize.select('#tab1').boundingClientRect();
queryTabSize.select('#tab2').boundingClientRect();
queryTabSize.select('#tab3').boundingClientRect();
queryTabSize.exec(rects => {
rects.forEach((rect) => {
this.tabListSize[rect.dataset.id] = rect;
})
this.updateIndicator(this.tabListSize[this.tabIndex].left, this.tabListSize[this.tabIndex]
.width);
this.switchTab(this.tabIndex);
});
// #endif
// #ifdef MP-WEIXIN || H5 || MP-QQ
uni.createSelectorQuery().in(this).select('.tab-view').fields({
dataset: true,
size: true,
}, (res) => {
this.swiperWidth = res.width;
}).exec();
uni.createSelectorQuery().in(this).selectAll('.uni-tab-item').boundingClientRect((rects) => {
rects.forEach((rect) => {
this.tabListSize[rect.dataset.id] = rect;
})
this.updateIndicator(this.tabListSize[this.tabIndex].left, this.tabListSize[this.tabIndex]
.width);
}).exec();
// #endif
},
updateIndicator(left, width) {
this.indicatorLineLeft = left;
this.indicatorLineWidth = width;
},
switchTab(index) {
// if (this.pageList[index].dataList.length === 0) {
// this.loadTabData(index);
// }
this.setScrollRef(index,this._headHeight);
// if (this.tabIndex === index) {
// return;
// }
// // 缓存 tabId
// if (this.pageList[this.tabIndex].dataList.length > MAX_CACHE_DATA) {
// let isExist = this.cacheTab.indexOf(this.tabIndex);
// if (isExist < 0) {
// this.cacheTab.push(this.tabIndex);
// }
// }
this.tabIndex = index;
// #ifdef APP-NVUE
this.scrollTabTo(index);
// #endif
// #ifndef APP-NVUE
this.scrollInto = "tab" + index;
// #endif
// 释放 tabId
// if (this.cacheTab.length > MAX_CACHE_PAGE) {
// let cacheIndex = this.cacheTab[0];
// this.clearTabData(cacheIndex);
// this.cacheTab.splice(0, 1);
// }
},
scrollTabTo(index) {
const el = this.$refs['tabitem' + index][0];
let offset = 0;
// TODO fix ios offset
if (index > 0) {
offset = this.tabbarWidth / 2 - this.tabListSize[index].width / 2;
if (this.tabListSize[index].right < this.tabbarWidth / 2) {
offset = this.tabListSize[0].width;
}
}
dom.scrollToElement(el, {
offset: -offset
});
},
loadTabData(index) {
// this.pageList[index].loadData();
},
clearTabData(index) {
// this.pageList[index].clear();
},
onrefresh(e) {
// this.refreshing = true;
// this.refreshText = "刷新中...";
// setTimeout(() => {
// this.refreshing = false;
// this.refreshFlag = false;
// this.refreshText = "已刷新";
// }, 2000)
},
onpullingdown(e) {
if (this.refreshing) {
return;
}
this.pulling = false;
if (Math.abs(e.pullingDistance) > Math.abs(e.viewHeight)) {
this.refreshFlag = true;
this.refreshText = "释放立即刷新";
} else {
this.refreshFlag = false;
this.refreshText = "下拉可以刷新";
}
}
}
}
</script>
<style>
/* #ifndef APP-PLUS */
page {
width: 100%;
min-height: 100%;
display: flex;
}
/* #endif */
.page {
flex: 1;
}
.cover-box {
width: 750rpx;
height: 200px;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: #ffffff;
}
.status-bar {
height: 44px;
color: #ffffff;
background-color: #000000;
}
.intro-box {
height: 160px;
flex-direction: row;
align-items: center;
justify-content: center;
/* background-color: #f4f4f4; */
}
.flexible-view {
background-color: #f823ff;
}
.page-head {
height: 200px;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: red;
}
.tabs {
flex-direction: column;
overflow: hidden;
background-color: #ffffff;
/* #ifdef MP-ALIPAY || MP-BAIDU */
height: 100vh;
/* #endif */
}
.tab-bar {
width: 750upx;
height: 84upx;
flex-direction: row;
/* #ifndef APP-PLUS */
white-space: nowrap;
/* #endif */
}
/* #ifndef APP-NVUE */
.tab-bar ::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.scroll-view-indicator {
position: relative;
height: 2px;
background-color: transparent;
}
.scroll-view-underline {
position: absolute;
top: 0;
bottom: 0;
width: 0;
background-color: #007AFF;
}
.scroll-view-animation {
transition-duration: 0.2s;
transition-property: left;
}
.tab-bar-line {
height: 1upx;
background-color: #cccccc;
}
.tab-view {
flex: 1;
}
.uni-tab-item {
/* #ifndef APP-PLUS */
display: inline-block;
/* #endif */
flex-wrap: nowrap;
padding-left: 25px;
padding-right: 25px;
}
.uni-tab-item-title {
color: #555;
font-size: 30upx;
height: 80upx;
line-height: 80upx;
flex-wrap: nowrap;
/* #ifndef APP-PLUS */
white-space: nowrap;
/* #endif */
}
.uni-tab-item-title-active {
color: #007AFF;
}
.swiper-item {
flex: 1;
flex-direction: column;
}
.swiper-page {
flex: 1;
flex-direction: row;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.refresh-view {
width: 750rpx;
height: 80px;
flex-direction: row;
align-items: center;
justify-content: center;
}
</style>
<style scoped>
.uni-swiper-page {
flex: 1;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.list {
flex: 1;
background-color: #ebebeb;
}
.list-item {
margin-left: 12px;
margin-right: 12px;
margin-top: 12px;
padding: 20px;
background-color: #fff;
border-radius: 5px;
}
.loading {
height: 20px;
}
</style>
pages.json
{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app",
"navigationStyle": "custom"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
}
场景说明
在app中用nvue实现视频播放页面,页面分为顶部视频区、课程标题区、tab选项卡区
需求1:用自定义黑色view占位标题栏区域,自定义状态栏占位区和视频区固定在顶部;
需求2:课程标题区随着页面上下滑动而滚动
需求3:tab选项卡区滑动到视频区底部时,自动吸顶
需求4:tab的每个子选项卡滚动高度各自独立
需求5:隐藏页面滚动条,仅显示tab选项卡区滚动条
index.nvue
<template>
<!-- :show-scrollbar="false" 不显示页面的滚动条 -->
<!-- :bounce="true" 开启iOS回弹效果 -->
<list id="page" class="page" :bounce="true" :show-scrollbar="false" isSwiperList="true">
<!-- 课程封面/视频播放器 -->
<!-- 固定吸在顶部 -->
<header>
<view id="head">
<!-- 黑色状态栏 -->
<view class="status-bar" :style="'height:' + statusHeight + 'px'">
</view>
<!-- 封面区/视频区 -->
<view class="cover-box">
<!--
课程封面/视频播放器区
tab选项卡总高度:{{pageHeight}}
tab选项卡吸顶高度:{{_headHeight}}(iOS端生效)
-->
<video title="uniapp nvue顶部双吸顶+左右滑动切换选项卡" src="https://api.dogecloud.com/player/get.mp4?vcode=5ac682e6f8231991&userId=17&ext=.mp4" objectFit="fill"
poster="https://i.loli.net/2019/06/06/5cf8c5d9c57b510947.png" style="width:750rpx;height: 200px;"></video>
</view>
</view>
</header>
<!-- 课程标题 -->
<!-- 不固定,随着竖向滚动条滑动 -->
<cell>
<view id="intro-box" class="intro-box">
课程标题区
</view>
</cell>
<!-- 可横向滑动切换选项卡 -->
<!-- 滑到封面区/视频区底部位置,自动吸顶 -->
<cell>
<view class="tabs" :style="'height:' + pageHeight + 'px'">
<scroll-view ref="tabbar1" id="tab-bar" class="tab-bar" :scroll="false" :scroll-x="false"
:show-scrollbar="false" :scroll-into-view="scrollInto">
<view style="flex-direction: column;">
<view style="flex-direction: row;">
<view class="uni-tab-item" id="tab0" ref="tabitem0" data-id="0" data-current="0"
@click="ontabtap">
<text class="uni-tab-item-title"
:class="tabIndex==0 ? 'uni-tab-item-title-active' : ''">详情</text>
</view>
<view class="uni-tab-item" id="tab1" ref="tabitem1" data-id="1" data-current="1"
@click="ontabtap">
<text class="uni-tab-item-title"
:class="tabIndex==1 ? 'uni-tab-item-title-active' : ''">目录</text>
</view>
<view class="uni-tab-item" id="tab2" ref="tabitem2" data-id="2" data-current="2"
@click="ontabtap">
<text class="uni-tab-item-title"
:class="tabIndex==2 ? 'uni-tab-item-title-active' : ''">评论</text>
</view>
<view class="uni-tab-item" id="tab3" ref="tabitem3" data-id="3" data-current="3"
@click="ontabtap">
<text class="uni-tab-item-title"
:class="tabIndex==3 ? 'uni-tab-item-title-active' : ''">资料</text>
</view>
</view>
<view class="scroll-view-indicator">
<view ref="underline" class="scroll-view-underline"
:class="isTap ? 'scroll-view-animation':''"
:style="{left: indicatorLineLeft + 'px', width: indicatorLineWidth + 'px'}"></view>
</view>
</view>
</scroll-view>
<view class="tab-bar-line"></view>
<swiper class="tab-view" ref="swiper1" id="tab-bar-view" :current="tabIndex" :duration="300"
@change="onswiperchange" @transition="onswiperscroll" @animationfinish="animationfinish"
@onAnimationEnd="animationfinish">
<swiper-item class="swiper-item">
<!-- 详情 -->
<view class="uni-swiper-page">
<list ref="list0" class="list" :offset-accuracy="5" :bounce="true" isSwiperList="true">
<cell @click="onclick">
课程简介内容
</cell>
<cell class="loading"></cell>
</list>
</view>
</swiper-item>
<swiper-item class="swiper-item">
<!-- 目录 -->
<view class="uni-swiper-page">
<list ref="list1" class="list" :offset-accuracy="5" :bounce="true" isSwiperList="true">
<cell @click="onclick">
<view class="list-item" v-for="(item,index) in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49]">
<text>章节{{item}}</text>
</view>
</cell>
<cell class="loading"></cell>
</list>
</view>
</swiper-item>
<swiper-item class="swiper-item">
<!-- 评论 -->
<view class="uni-swiper-page">
<list ref="list2" class="list" :offset-accuracy="5" :bounce="true" isSwiperList="true">
<cell @click="onclick">
<view class="list-item" v-for="(item,index) in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49]">
<text>评论{{item}}</text>
</view>
</cell>
<cell class="loading"></cell>
</list>
</view>
</swiper-item>
<swiper-item class="swiper-item">
<!-- 资料 -->
<view class="uni-swiper-page">
<list ref="list3" class="list" :offset-accuracy="5" :bounce="true" isSwiperList="true">
<cell @click="onclick">
<view class="list-item" v-for="(item,index) in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49]">
<text>资料{{item}}</text>
</view>
</cell>
<cell class="loading"></cell>
</list>
</view>
</swiper-item>
</swiper>
</view>
</cell>
</list>
</template>
<script>
// #ifdef APP-PLUS
const dom = weex.requireModule('dom');
// #endif
// 缓存每页最多
const MAX_CACHE_DATA = 100;
// 缓存页签数量
const MAX_CACHE_PAGE = 3;
const TAB_PRELOAD_OFFSET = 1;
export default {
data() {
return {
tabIndex: 0,
cacheTab: [],
scrollInto: "",
indicatorLineLeft: 0,
indicatorLineWidth: 0,
isTap: false,
showTitleView: true,
statusHeight: 44,
pageHeight: 300,
refreshing: false,
refreshText: "",
refreshFlag: false
}
},
onLoad() {
},
onReady() {
let inf = uni.getSystemInfoSync();
this.statusHeight = inf.statusBarHeight; // 状态栏高度
this.pageHeight = inf.windowHeight - this.statusHeight - 200; // 页面高度
this._lastTabIndex = 0;
this.swiperWidth = 0;
this.tabbarWidth = 0;
this.tabListSize = {};
this._touchTabIndex = 0;
this._headHeight = 100;
this.selectorQuery();
// #ifdef APP-PLUS
plus.navigator.setStatusBarStyle("light"); //白色
// #endif
},
methods: {
setScrollRef(index, height) {
// Android不支持setSpecialEffects , 仅iOS机型生效
if (this.$refs['list' + index].setSpecialEffects) {
this.$refs['list' + index].setSpecialEffects({
id: "page",
headerHeight: height //设置iOS端Tabs距离顶部的吸顶距离
});
}
},
onclick(e) {
console.log("onclick");
},
loadData() {
// 首次激活时被调用
},
clear() {
// 释放数据时被调用,参考 swiper-list 缓存配置
},
ontap1(e) {
console.log("ontap1", e);
},
ontabtap(e) {
let index = e.target.dataset.current || e.currentTarget.dataset.current;
//let offsetIndex = this._touchTabIndex = Math.abs(index - this._lastTabIndex) > 1;
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-QQ
this.isTap = true;
var currentSize = this.tabListSize[index];
this.updateIndicator(currentSize.left, currentSize.width);
this._touchTabIndex = index;
// #endif
this.switchTab(index);
},
onswiperchange(e) {
// 注意:百度小程序会触发2次
// #ifndef APP-PLUS || H5 || MP-WEIXIN || MP-QQ
let index = e.target.current || e.detail.current;
this.switchTab(index);
// #endif
},
onswiperscroll(e) {
if (this.isTap) {
return;
}
var offsetX = e.detail.dx;
var preloadIndex = this._lastTabIndex;
if (offsetX > TAB_PRELOAD_OFFSET) {
preloadIndex++;
} else if (offsetX < -TAB_PRELOAD_OFFSET) {
preloadIndex--;
}
if (preloadIndex === this._lastTabIndex || preloadIndex < 0 || preloadIndex > 4 - 1) {
return;
}
// if (this.pageList[preloadIndex].dataList.length === 0) {
// this.loadTabData(preloadIndex);
// }
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-QQ
var percentage = Math.abs(this.swiperWidth / offsetX);
var currentSize = this.tabListSize[this._lastTabIndex];
var preloadSize = this.tabListSize[preloadIndex];
var lineL = currentSize.left + (preloadSize.left - currentSize.left) / percentage;
var lineW = currentSize.width + (preloadSize.width - currentSize.width) / percentage;
this.updateIndicator(lineL, lineW);
// #endif
},
animationfinish(e) {
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-QQ
let index = e.detail.current;
if (this._touchTabIndex === index) {
this.isTap = false;
}
this._lastTabIndex = index;
this.switchTab(index);
this.updateIndicator(this.tabListSize[index].left, this.tabListSize[index].width);
// #endif
},
selectorQuery() {
// #ifdef APP-NVUE
// uni.createSelectorQuery().in(this).select('#head').boundingClientRect().exec(rect => {
// // 获取封面区高度
// this._headHeight = rect[0].height;
// });
uni.createSelectorQuery().in(this).select('#intro-box').boundingClientRect().exec(rect => {
// 获取课程简介区的位置,设为iOS端Tabs距离顶部的吸顶距离
this._headHeight = rect[0].height - 1;
console.log('3 introHeight', this._headHeight);
});
// 查询 tabbar 宽度
uni.createSelectorQuery().in(this).select('#tab-bar').boundingClientRect().exec(rect => {
this.tabbarWidth = rect[0].width;
});
// 查询 tabview 宽度
uni.createSelectorQuery().in(this).select('#tab-bar-view').boundingClientRect().exec(rect => {
this.swiperWidth = rect[0].width;
});
// 因 nvue 暂不支持 class 查询
var queryTabSize = uni.createSelectorQuery().in(this);
queryTabSize.select('#tab0').boundingClientRect();
queryTabSize.select('#tab1').boundingClientRect();
queryTabSize.select('#tab2').boundingClientRect();
queryTabSize.select('#tab3').boundingClientRect();
queryTabSize.exec(rects => {
rects.forEach((rect) => {
this.tabListSize[rect.dataset.id] = rect;
})
this.updateIndicator(this.tabListSize[this.tabIndex].left, this.tabListSize[this.tabIndex]
.width);
this.switchTab(this.tabIndex);
});
// #endif
// #ifdef MP-WEIXIN || H5 || MP-QQ
uni.createSelectorQuery().in(this).select('.tab-view').fields({
dataset: true,
size: true,
}, (res) => {
this.swiperWidth = res.width;
}).exec();
uni.createSelectorQuery().in(this).selectAll('.uni-tab-item').boundingClientRect((rects) => {
rects.forEach((rect) => {
this.tabListSize[rect.dataset.id] = rect;
})
this.updateIndicator(this.tabListSize[this.tabIndex].left, this.tabListSize[this.tabIndex]
.width);
}).exec();
// #endif
},
updateIndicator(left, width) {
this.indicatorLineLeft = left;
this.indicatorLineWidth = width;
},
switchTab(index) {
// if (this.pageList[index].dataList.length === 0) {
// this.loadTabData(index);
// }
this.setScrollRef(index,this._headHeight);
// if (this.tabIndex === index) {
// return;
// }
// // 缓存 tabId
// if (this.pageList[this.tabIndex].dataList.length > MAX_CACHE_DATA) {
// let isExist = this.cacheTab.indexOf(this.tabIndex);
// if (isExist < 0) {
// this.cacheTab.push(this.tabIndex);
// }
// }
this.tabIndex = index;
// #ifdef APP-NVUE
this.scrollTabTo(index);
// #endif
// #ifndef APP-NVUE
this.scrollInto = "tab" + index;
// #endif
// 释放 tabId
// if (this.cacheTab.length > MAX_CACHE_PAGE) {
// let cacheIndex = this.cacheTab[0];
// this.clearTabData(cacheIndex);
// this.cacheTab.splice(0, 1);
// }
},
scrollTabTo(index) {
const el = this.$refs['tabitem' + index][0];
let offset = 0;
// TODO fix ios offset
if (index > 0) {
offset = this.tabbarWidth / 2 - this.tabListSize[index].width / 2;
if (this.tabListSize[index].right < this.tabbarWidth / 2) {
offset = this.tabListSize[0].width;
}
}
dom.scrollToElement(el, {
offset: -offset
});
},
loadTabData(index) {
// this.pageList[index].loadData();
},
clearTabData(index) {
// this.pageList[index].clear();
},
onrefresh(e) {
// this.refreshing = true;
// this.refreshText = "刷新中...";
// setTimeout(() => {
// this.refreshing = false;
// this.refreshFlag = false;
// this.refreshText = "已刷新";
// }, 2000)
},
onpullingdown(e) {
if (this.refreshing) {
return;
}
this.pulling = false;
if (Math.abs(e.pullingDistance) > Math.abs(e.viewHeight)) {
this.refreshFlag = true;
this.refreshText = "释放立即刷新";
} else {
this.refreshFlag = false;
this.refreshText = "下拉可以刷新";
}
}
}
}
</script>
<style>
/* #ifndef APP-PLUS */
page {
width: 100%;
min-height: 100%;
display: flex;
}
/* #endif */
.page {
flex: 1;
}
.cover-box {
width: 750rpx;
height: 200px;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: #ffffff;
}
.status-bar {
height: 44px;
color: #ffffff;
background-color: #000000;
}
.intro-box {
height: 160px;
flex-direction: row;
align-items: center;
justify-content: center;
/* background-color: #f4f4f4; */
}
.flexible-view {
background-color: #f823ff;
}
.page-head {
height: 200px;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: red;
}
.tabs {
flex-direction: column;
overflow: hidden;
background-color: #ffffff;
/* #ifdef MP-ALIPAY || MP-BAIDU */
height: 100vh;
/* #endif */
}
.tab-bar {
width: 750upx;
height: 84upx;
flex-direction: row;
/* #ifndef APP-PLUS */
white-space: nowrap;
/* #endif */
}
/* #ifndef APP-NVUE */
.tab-bar ::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.scroll-view-indicator {
position: relative;
height: 2px;
background-color: transparent;
}
.scroll-view-underline {
position: absolute;
top: 0;
bottom: 0;
width: 0;
background-color: #007AFF;
}
.scroll-view-animation {
transition-duration: 0.2s;
transition-property: left;
}
.tab-bar-line {
height: 1upx;
background-color: #cccccc;
}
.tab-view {
flex: 1;
}
.uni-tab-item {
/* #ifndef APP-PLUS */
display: inline-block;
/* #endif */
flex-wrap: nowrap;
padding-left: 25px;
padding-right: 25px;
}
.uni-tab-item-title {
color: #555;
font-size: 30upx;
height: 80upx;
line-height: 80upx;
flex-wrap: nowrap;
/* #ifndef APP-PLUS */
white-space: nowrap;
/* #endif */
}
.uni-tab-item-title-active {
color: #007AFF;
}
.swiper-item {
flex: 1;
flex-direction: column;
}
.swiper-page {
flex: 1;
flex-direction: row;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.refresh-view {
width: 750rpx;
height: 80px;
flex-direction: row;
align-items: center;
justify-content: center;
}
</style>
<style scoped>
.uni-swiper-page {
flex: 1;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.list {
flex: 1;
background-color: #ebebeb;
}
.list-item {
margin-left: 12px;
margin-right: 12px;
margin-top: 12px;
padding: 20px;
background-color: #fff;
border-radius: 5px;
}
.loading {
height: 20px;
}
</style>
pages.json
{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app",
"navigationStyle": "custom"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
}
收起阅读 »

uni统计2.0 看不到数据原因可能是编译版本不对
文档里面HBuilderX 3.4.10 +起支持 指的是编译版本,不是更新了HBuilderX 3.4.10 + 就可以,要打包的时候看下控制台的编译版本是否是 3.4.10 +
参考解决: uni统计2.0接入记录及问题解决
文档里面HBuilderX 3.4.10 +起支持 指的是编译版本,不是更新了HBuilderX 3.4.10 + 就可以,要打包的时候看下控制台的编译版本是否是 3.4.10 +
参考解决: uni统计2.0接入记录及问题解决
收起阅读 »
小程序滚动到底部触发加载新数据的技巧
在手机端加载商品或者评论时经常会遇到页面滚动到底部加载更多数据的需求
我在开发过程中也经常遇到,每次都感觉很头疼。特别是在订单那种有tab切换,混杂着触底刷新,tab切换后又要重置参数,很多时候代码会写一大堆,过了几天TMD自己写的都看不下去了。
最关键的是这种需求在商城类的需求很多,首页有评论有购物车也有,复制粘贴固然可以解决这些问题,但是代码整洁和共用提纯上乱的一塌糊涂。
于是我尝试用一种模式去改变这种现状。
用一种类似于工厂设计的模式来做,一开始传入请求数据的函数request、初始的list,请求的参数pages,是否刷新fresh、以及请求结果的回调callBack,然后在onReachBottom每次触底都请求一次getData(),当你需要改变pages,然后重置getData(true),即可重置请求结果
下面的代码我最近在uni-app使用的一种
// 缓存请求的对象
let scrollPages={}
// 滚动获取数据,fresh:刷新,key:请求时唯一标识,pages参数,n:初始从第几页开始,requsetFun请求方法,setList:赋值函数,list赋值对象
let page='pageNum'// 页面切换的key
let size='pageSize'// 每页请求个数key
let startNumber=1 // 开始页码
let defaultPar={[size]: 10,isAll: false}
// 滚动到底部获取数据 list初始条数 request请求函数 回调callBack,是必填,n开始页码,getCode在请求完的结果里取值的key
export const scrollGetList =(list,{fresh = false,getCode,key,n,pages={},request},callBack) => {
let inPages={ ...defaultPar,...pages}
console.log('inPages',inPages)
// 没有key会用请求的函数做名字
key=key||request.name
// fresh 重置会重置之前所有的请求参数的记录
if (fresh) scrollPages={}
// 没有记录或者重置参数
if (!scrollPages[key]||fresh) {
inPages[page]=n||startNumber
scrollPages[key]={key, request,pages:inPages,}
}
if (scrollPages[key].pages.isAll && !fresh) {
toast(`已加载全部,共${list.length}条`)
return
};
uni.showLoading({
title: '加载中',
mask: true,
});
let pg= scrollPages[key].pages
delete pg.isAll
scrollPages[key].request(pg).then((data) => {
uni.hideLoading();
let midList=getCode?data[getCode]:data
// 每次请求完+1
let pages = pg[page] + 1;
scrollPages[key].pages={...scrollPages[key].pages,[page]:pages, isAll: midList.length<inPages[size]}
list=fresh ? midList : [...list, ...midList];
callBack(list)
}).catch(()=>{
uni.hideLoading();
})
};
在vue文件里
data:{
return {
list:[],
pages:{}
}
},
methods:{
// 获取数据
getData(fresh){
scrollGetList(this.list, {
fresh,
pages:{
...this.pages
},
request:getListFun//请求的函数
},(list)=>{
this.list=list
})
},
},
// 每次进入页面刷新
onShow() {
this.getData(true)
},
// 触底刷新
onReachBottom() {
this.getData()
},
// 下拉刷新
onPullDownRefresh() {
this.getData(true)
}
在手机端加载商品或者评论时经常会遇到页面滚动到底部加载更多数据的需求
我在开发过程中也经常遇到,每次都感觉很头疼。特别是在订单那种有tab切换,混杂着触底刷新,tab切换后又要重置参数,很多时候代码会写一大堆,过了几天TMD自己写的都看不下去了。
最关键的是这种需求在商城类的需求很多,首页有评论有购物车也有,复制粘贴固然可以解决这些问题,但是代码整洁和共用提纯上乱的一塌糊涂。
于是我尝试用一种模式去改变这种现状。
用一种类似于工厂设计的模式来做,一开始传入请求数据的函数request、初始的list,请求的参数pages,是否刷新fresh、以及请求结果的回调callBack,然后在onReachBottom每次触底都请求一次getData(),当你需要改变pages,然后重置getData(true),即可重置请求结果
下面的代码我最近在uni-app使用的一种
// 缓存请求的对象
let scrollPages={}
// 滚动获取数据,fresh:刷新,key:请求时唯一标识,pages参数,n:初始从第几页开始,requsetFun请求方法,setList:赋值函数,list赋值对象
let page='pageNum'// 页面切换的key
let size='pageSize'// 每页请求个数key
let startNumber=1 // 开始页码
let defaultPar={[size]: 10,isAll: false}
// 滚动到底部获取数据 list初始条数 request请求函数 回调callBack,是必填,n开始页码,getCode在请求完的结果里取值的key
export const scrollGetList =(list,{fresh = false,getCode,key,n,pages={},request},callBack) => {
let inPages={ ...defaultPar,...pages}
console.log('inPages',inPages)
// 没有key会用请求的函数做名字
key=key||request.name
// fresh 重置会重置之前所有的请求参数的记录
if (fresh) scrollPages={}
// 没有记录或者重置参数
if (!scrollPages[key]||fresh) {
inPages[page]=n||startNumber
scrollPages[key]={key, request,pages:inPages,}
}
if (scrollPages[key].pages.isAll && !fresh) {
toast(`已加载全部,共${list.length}条`)
return
};
uni.showLoading({
title: '加载中',
mask: true,
});
let pg= scrollPages[key].pages
delete pg.isAll
scrollPages[key].request(pg).then((data) => {
uni.hideLoading();
let midList=getCode?data[getCode]:data
// 每次请求完+1
let pages = pg[page] + 1;
scrollPages[key].pages={...scrollPages[key].pages,[page]:pages, isAll: midList.length<inPages[size]}
list=fresh ? midList : [...list, ...midList];
callBack(list)
}).catch(()=>{
uni.hideLoading();
})
};
在vue文件里
data:{
return {
list:[],
pages:{}
}
},
methods:{
// 获取数据
getData(fresh){
scrollGetList(this.list, {
fresh,
pages:{
...this.pages
},
request:getListFun//请求的函数
},(list)=>{
this.list=list
})
},
},
// 每次进入页面刷新
onShow() {
this.getData(true)
},
// 触底刷新
onReachBottom() {
this.getData()
},
// 下拉刷新
onPullDownRefresh() {
this.getData(true)
}
收起阅读 »

Some App Tech Support
一、软件介绍
名称:小秘书
类型:写作类应用
二、功能
小秘书是一款专门为写作而生的工具类app,兼具便签、备忘录、笔记本、日程规划等功能,虽然简单却功能齐全。
专门的写作分类让小秘书与传统的记事本、备忘录、博客区分开来,独立于生活、学习等其他琐事,专注写作这一领域。
【功能】
1.【灵感浮现】、【灵感收集】、【佳句摘抄】:让你将生活中突然出现的灵感和有用的知识分门别类记录下来
2、【写作之旅】:写文章
3、【写作安排】:有序规划你的写作事项,培养写作习惯
4、【查阅帮助】:写作的辅助工具
让你随时随地记录灵感,成立自己的灵感宝库;更便捷地开启创作,随手翻阅自己的作品和文章
三、Getting Support:
邮箱: royalugemma@yeah.net
或者评论留言
一、软件介绍
名称:小秘书
类型:写作类应用
二、功能
小秘书是一款专门为写作而生的工具类app,兼具便签、备忘录、笔记本、日程规划等功能,虽然简单却功能齐全。
专门的写作分类让小秘书与传统的记事本、备忘录、博客区分开来,独立于生活、学习等其他琐事,专注写作这一领域。
【功能】
1.【灵感浮现】、【灵感收集】、【佳句摘抄】:让你将生活中突然出现的灵感和有用的知识分门别类记录下来
2、【写作之旅】:写文章
3、【写作安排】:有序规划你的写作事项,培养写作习惯
4、【查阅帮助】:写作的辅助工具
让你随时随地记录灵感,成立自己的灵感宝库;更便捷地开启创作,随手翻阅自己的作品和文章
三、Getting Support:
邮箱: royalugemma@yeah.net
或者评论留言
收起阅读 »
微信小程序 @bindgetphonenumber 无效问题解决办法
问题的根源是编译出现的问题
不知道是不是前缀的bind原因: bindgetphonenumber = "getPhoneNumber" 解析成了 bindbindgetphonenumber = "getPhoneNumber" ,前缀多了一个bind,暂时的解决办法在小程序登录页面删除了错误代码,但是这样开发起来挺麻烦的,每次编译完都要删除一遍,希望有大佬给个解决方案,谢谢!
<button class="tologin data-v-3363990d" bindbindgetphonenumber="{{a}}" open-type="getPhoneNumber">一键登录</button>
已经解决!在uniapp里删除bind 直接用 @getphonenumber
<button class="tologin" @getphonenumber="tologin" open-type="getPhoneNumber">一键登录</button>
问题的根源是编译出现的问题
不知道是不是前缀的bind原因: bindgetphonenumber = "getPhoneNumber" 解析成了 bindbindgetphonenumber = "getPhoneNumber" ,前缀多了一个bind,暂时的解决办法在小程序登录页面删除了错误代码,但是这样开发起来挺麻烦的,每次编译完都要删除一遍,希望有大佬给个解决方案,谢谢!
<button class="tologin data-v-3363990d" bindbindgetphonenumber="{{a}}" open-type="getPhoneNumber">一键登录</button>
已经解决!在uniapp里删除bind 直接用 @getphonenumber
<button class="tologin" @getphonenumber="tologin" open-type="getPhoneNumber">一键登录</button>
收起阅读 »

DCloud账号注销流程
实名认证账号注销流程
如需要注销DCloud 账号,实名认证企业账号请使用注册账号的邮箱发送邮件到 service@dcloud.io 申请,邮件标题:注销DCloud账号。
前提条件
- 删除所有uniCloud服务空间
材料要求
企业实名认证账号
存续企业
- 法人手持营业执照照片
- 法人手持身份证照片
- 《DCloud账号注销申请》(内容自拟)加盖公司公章
已注销企业
- 在邮件中需要说明企业已经注销,我司会在工商核查企业状态,确认企业已注销的情况下,由DCloud操作注销账号。
未实名认证账号注销流程
未实名认证账号可在线注销,登录 开发者中心,进入“个人中心”-“账户信息”页面,点击“去注销”,按页面提示操作即可。
个人实名账号注销流程
个人认证账号可在线注销,登录 开发者中心,进入“个人中心”-“账户信息”页面,点击“去注销”,按页面提示操作即可。
输入验证信息后,需再次通过实人认证。通过实人认证后完成注销。
注意事项
- 注销账号后,该账号在 DCloud 平台的所有资产将被清除且清除后无法恢复,请务必谨慎申请。
- 如果使用了 DCloud 相关付费服务,需要在注销前完成相关账单结算,包括但不限于以下服务:
- uniCloud 相关服务
- uni-AD 相关服务
- 插件作者收益提现
实名认证账号注销流程
如需要注销DCloud 账号,实名认证企业账号请使用注册账号的邮箱发送邮件到 service@dcloud.io 申请,邮件标题:注销DCloud账号。
前提条件
- 删除所有uniCloud服务空间
材料要求
企业实名认证账号
存续企业
- 法人手持营业执照照片
- 法人手持身份证照片
- 《DCloud账号注销申请》(内容自拟)加盖公司公章
已注销企业
- 在邮件中需要说明企业已经注销,我司会在工商核查企业状态,确认企业已注销的情况下,由DCloud操作注销账号。
未实名认证账号注销流程
未实名认证账号可在线注销,登录 开发者中心,进入“个人中心”-“账户信息”页面,点击“去注销”,按页面提示操作即可。
个人实名账号注销流程
个人认证账号可在线注销,登录 开发者中心,进入“个人中心”-“账户信息”页面,点击“去注销”,按页面提示操作即可。
输入验证信息后,需再次通过实人认证。通过实人认证后完成注销。
注意事项
- 注销账号后,该账号在 DCloud 平台的所有资产将被清除且清除后无法恢复,请务必谨慎申请。
- 如果使用了 DCloud 相关付费服务,需要在注销前完成相关账单结算,包括但不限于以下服务:
- uniCloud 相关服务
- uni-AD 相关服务
- 插件作者收益提现

完善扫码中心识别区域的宽度自定义
在扫码组件中无论是自定义方式还是nuve中的组件都是,自定义扫码摄像头展示的宽高自定义,既然支持扫描条形码,我强烈建议对中心方块识别区进行宽高自定义以满足长条形码的扫码需求,否则在使用场景取需要是设备在较远的地方才能将条形码完全包含在中心识别区
在扫码组件中无论是自定义方式还是nuve中的组件都是,自定义扫码摄像头展示的宽高自定义,既然支持扫描条形码,我强烈建议对中心方块识别区进行宽高自定义以满足长条形码的扫码需求,否则在使用场景取需要是设备在较远的地方才能将条形码完全包含在中心识别区

uni小程序、插件开发|个人接单|欢迎咨询
uni小程序开发;
uni插件开发;
可以全栈开发,后端语言java。
前端蓝牙助手:https://www.zhihu.com/pin/1521169284718239744
后端轮子:https://zhuanlan.zhihu.com/p/530231461
QQ:421156722
欢迎咨询,合作共赢。
uni小程序开发;
uni插件开发;
可以全栈开发,后端语言java。
前端蓝牙助手:https://www.zhihu.com/pin/1521169284718239744
后端轮子:https://zhuanlan.zhihu.com/p/530231461
QQ:421156722
欢迎咨询,合作共赢。
收起阅读 »
app分发平台源码ios免签封装
app分发平台包含各种形式的多功能移动应用程序,如娱乐、购物、游戏等。这个阶段让客户可以浏览他们最喜欢的应用程序并随时下载。几乎每个可移植应用程序改进组织在分派应用程序时都关注这些阶段。这些阶段还为转移其应用程序以跟踪进展的组织或工程师提供了各种设备。从下载次数到应用程序出现的搜索次数,组织可以跟踪所有内容。因此,便携式应用程序分散在各种渠道上的基本原理被证明更为重要。
app分发平台源码及演示:s.appwin.top
一个非常易于使用、完全可定制且灵活的基于 Web 的 App Store,可最大限度地提高您的企业应用程序的采用率。为任何目的和用户知识水平做好准备,并提供指导安装体验。无需 MDM 解决方案即可在一处分发 iOS 和 Android 应用程序所需的一切。
分发方法的范围从为测试设备导出您的应用程序到将其上传到 App Store Connect。您可以使用TestFlight 将iOS、tvOS 和watchOS beta 版本分发给测试人员并收集反馈。如果您想将您的应用程序分发给已注册的设备、使用 TestFlight 或通过 App Store 的 beta 测试,您需要加入APP开发项目。为您创建一个 App Store Connect 帐户,您可以开始上传构建。
加入APP开发项目可以访问分发方法以及可以添加到应用程序的功能。一项功能授予您的应用访问 提供的应用服务的权限。
要使用任何分发方法,您首先要创建应用程序的存档。存档是您的应用程序的构建,包括 Xcode 存储在包中的调试信息。
在 Xcode 项目的主窗口中,从 Scheme 工具栏菜单中选择一个目标和一个仅构建设备或真实设备。如果目标是模拟器,则无法创建存档。然后,选择 Product > Archive 来构建目标,并创建将出现在 Archives 管理器中的存档。
您可以通过选择窗口 > 管理器直接打开档案管理器。如果您想确认您的应用已准备好提交到 TestFlight 或 App Store,但尚未提交,请选择您的存档,然后单击验证应用。Xcode 将对应用程序执行有限的自动初始验证并提供反馈。
选择分发方法和选项
您可以导出存档或将其上传到 App Store Connect。您导出应用程序以将其分发到 App Store 之外,然后将其上传以通过 TestFlight 或 App Store 分发它。
在档案管理器中,选择档案,然后单击分发应用程序。在下一张表中,根据您的应用平台选择分发方法。
要仅在注册设备上分发给有限数量的用户(例如,在您的组织内分发),请选择 Ad Hoc 或 Development。有关详细信息,请参阅将您的app分发到已注册的设备。要使用 TestFlight 或通过 App Store 分发,请选择 App Store Connect。
要在 App Store 之外分发由 Apple 公证或使用开发者 ID 签名的 macOS 应用程序,请选择开发者 ID。
根据您的分发方法选择您的分发选项。例如,如果您使用导出分发方法,您将选择应用程序精简和按需资源选项,因为您需要自己分发应用程序。如果您上传到 App Store Connect,您将选择是否包含位码或符号。
如果您选择 App Store Connect 或 Developer ID 作为您的分发方法,您还需要选择目标选项。您可以选择将您的构建上传到 App Store,或将您的构建导出到本地以便稍后上传。
分发测试版
当您向用户分发应用的 Beta 版或提供即将发布的版本的预览时,请使用基于应用平台的方法:
对于 iOS、tvOS 或 watchOS 应用,使用 TestFlight 将应用的 Beta 版分发给内部和外部测试人员。TestFlight 应用程序允许受邀用户安装、Beta 测试、提供反馈并获取应用程序的更新。Apple 会为您分发测试版,然后您只需在 App Store Connect 上管理构建和用户。要了解更多信息,请参阅TestFlight beta 测试概述,有关 Xcode 特定的步骤,请参阅使用 TestFlight 分发应用程序。
对于所有平台,向您的开发者帐户中注册的设备分发测试版。仅当您可以保留部分有限的开发设备进行 Beta 测试时,才选择此选项。要了解更多信息,请参阅将您的app分发到已注册的设备。
对于 macOS 应用程序,在通过 App Store 分发应用程序之前,将经过 Apple 公证的构建分发给测试人员。要了解更多信息,请参阅分发前对 macOS 软件进行公证。
在 App Store 上发布
在对最终版本进行 Beta 版测试后,将其提交给 App Review,然后在 App Store 上提供。
如果您使用 TestFlight 分发测试版,并输入了 App Store 发布所需的附加信息,只需将 App Store Connect 中显示的最后一个构建提交到 App Review。要了解更多信息,请参阅发布应用程序概述。
否则,请按照通过 App Store 分发应用程序中的所有步骤准备您的应用程序并将其上传到 App Store Connect,然后将其提交给 App Review。
在 App Store 之外分发
对于 macOS 应用程序,您可以导出经过公证的应用程序以在 App Store 之外分发,但您可能需要先禁用需要 Apple Developer Program 会员资格的功能,然后再将应用程序自己分发给用户。要了解更多信息,请参阅分发前对 macOS 软件进行公证。
分发业务应用程序
还有几个用于分发业务、定制或内部应用程序的选项。有关详细信息,请参阅找到接触用户的最佳方式。如果您加入Apple Developer Enterprise Program,请参阅开发和分发企业应用程序,了解企业特定的 Xcode 步骤以导出您的应用程序。
app分发平台包含各种形式的多功能移动应用程序,如娱乐、购物、游戏等。这个阶段让客户可以浏览他们最喜欢的应用程序并随时下载。几乎每个可移植应用程序改进组织在分派应用程序时都关注这些阶段。这些阶段还为转移其应用程序以跟踪进展的组织或工程师提供了各种设备。从下载次数到应用程序出现的搜索次数,组织可以跟踪所有内容。因此,便携式应用程序分散在各种渠道上的基本原理被证明更为重要。
app分发平台源码及演示:s.appwin.top
一个非常易于使用、完全可定制且灵活的基于 Web 的 App Store,可最大限度地提高您的企业应用程序的采用率。为任何目的和用户知识水平做好准备,并提供指导安装体验。无需 MDM 解决方案即可在一处分发 iOS 和 Android 应用程序所需的一切。
分发方法的范围从为测试设备导出您的应用程序到将其上传到 App Store Connect。您可以使用TestFlight 将iOS、tvOS 和watchOS beta 版本分发给测试人员并收集反馈。如果您想将您的应用程序分发给已注册的设备、使用 TestFlight 或通过 App Store 的 beta 测试,您需要加入APP开发项目。为您创建一个 App Store Connect 帐户,您可以开始上传构建。
加入APP开发项目可以访问分发方法以及可以添加到应用程序的功能。一项功能授予您的应用访问 提供的应用服务的权限。
要使用任何分发方法,您首先要创建应用程序的存档。存档是您的应用程序的构建,包括 Xcode 存储在包中的调试信息。
在 Xcode 项目的主窗口中,从 Scheme 工具栏菜单中选择一个目标和一个仅构建设备或真实设备。如果目标是模拟器,则无法创建存档。然后,选择 Product > Archive 来构建目标,并创建将出现在 Archives 管理器中的存档。
您可以通过选择窗口 > 管理器直接打开档案管理器。如果您想确认您的应用已准备好提交到 TestFlight 或 App Store,但尚未提交,请选择您的存档,然后单击验证应用。Xcode 将对应用程序执行有限的自动初始验证并提供反馈。
选择分发方法和选项
您可以导出存档或将其上传到 App Store Connect。您导出应用程序以将其分发到 App Store 之外,然后将其上传以通过 TestFlight 或 App Store 分发它。
在档案管理器中,选择档案,然后单击分发应用程序。在下一张表中,根据您的应用平台选择分发方法。
要仅在注册设备上分发给有限数量的用户(例如,在您的组织内分发),请选择 Ad Hoc 或 Development。有关详细信息,请参阅将您的app分发到已注册的设备。要使用 TestFlight 或通过 App Store 分发,请选择 App Store Connect。
要在 App Store 之外分发由 Apple 公证或使用开发者 ID 签名的 macOS 应用程序,请选择开发者 ID。
根据您的分发方法选择您的分发选项。例如,如果您使用导出分发方法,您将选择应用程序精简和按需资源选项,因为您需要自己分发应用程序。如果您上传到 App Store Connect,您将选择是否包含位码或符号。
如果您选择 App Store Connect 或 Developer ID 作为您的分发方法,您还需要选择目标选项。您可以选择将您的构建上传到 App Store,或将您的构建导出到本地以便稍后上传。
分发测试版
当您向用户分发应用的 Beta 版或提供即将发布的版本的预览时,请使用基于应用平台的方法:
对于 iOS、tvOS 或 watchOS 应用,使用 TestFlight 将应用的 Beta 版分发给内部和外部测试人员。TestFlight 应用程序允许受邀用户安装、Beta 测试、提供反馈并获取应用程序的更新。Apple 会为您分发测试版,然后您只需在 App Store Connect 上管理构建和用户。要了解更多信息,请参阅TestFlight beta 测试概述,有关 Xcode 特定的步骤,请参阅使用 TestFlight 分发应用程序。
对于所有平台,向您的开发者帐户中注册的设备分发测试版。仅当您可以保留部分有限的开发设备进行 Beta 测试时,才选择此选项。要了解更多信息,请参阅将您的app分发到已注册的设备。
对于 macOS 应用程序,在通过 App Store 分发应用程序之前,将经过 Apple 公证的构建分发给测试人员。要了解更多信息,请参阅分发前对 macOS 软件进行公证。
在 App Store 上发布
在对最终版本进行 Beta 版测试后,将其提交给 App Review,然后在 App Store 上提供。
如果您使用 TestFlight 分发测试版,并输入了 App Store 发布所需的附加信息,只需将 App Store Connect 中显示的最后一个构建提交到 App Review。要了解更多信息,请参阅发布应用程序概述。
否则,请按照通过 App Store 分发应用程序中的所有步骤准备您的应用程序并将其上传到 App Store Connect,然后将其提交给 App Review。
在 App Store 之外分发
对于 macOS 应用程序,您可以导出经过公证的应用程序以在 App Store 之外分发,但您可能需要先禁用需要 Apple Developer Program 会员资格的功能,然后再将应用程序自己分发给用户。要了解更多信息,请参阅分发前对 macOS 软件进行公证。
分发业务应用程序
还有几个用于分发业务、定制或内部应用程序的选项。有关详细信息,请参阅找到接触用户的最佳方式。如果您加入Apple Developer Enterprise Program,请参阅开发和分发企业应用程序,了解企业特定的 Xcode 步骤以导出您的应用程序。