準備
- 微信小程序開發(fā)文檔 官網(wǎng) -
https://developers.weixin.qq.com/miniprogram/dev/framework/
由于字節(jié)跳動小程序很類似微信小程序,但是文檔的詳細程度卻差一些,所以需要微信小程序開發(fā)文檔做對照
開發(fā)階段
目錄結構同微信小程序類似,app.json 為全局設置。
- 認證注意事項
企業(yè)開發(fā)者:適用于企業(yè),個體工商戶,政府組織,海外機構等其他機構。每個企業(yè)主體可以驗證【 10 個企業(yè)小程序 】。
特別注意:認證主體后續(xù)需與支付主體、廣告結算主體保持一致
此外,如果在廣告中心開通了激勵廣告需要注意
1. 目前激勵視頻廣告僅支持抖音端,接入需判斷接入宿主及宿主版本, 在不支持的宿主及版本上需要將結果直接展示給用戶。
2. 展示廣告前向用戶說明激勵廣告規(guī)則,明確告知用戶看完視頻廣告后能獲得相應獎勵。
3. 一個頁面最多出現(xiàn)一個激勵視頻廣告。
4. 需要用戶主動操作點擊按鈕,才能創(chuàng)建和獲取激勵視頻廣告。
5. 需判斷廣告異常情況,如不支持激勵視頻的低版本用戶、廣告調用失敗等情況,應直接給予獎勵。

0-1. 關于激勵廣告
//--app.js
globalData:{
'videoAd': null,
},
rewardedVideoAd(){
var _that = this;
var _videoAd = _that.globalData.videoAd;
//兼容
//目前只能在抖音使用該方法,今日頭條等宿主暫不支持
let version = tt.getSystemInfoSync().SDKVersion;
if(_that.compareVersion(version,'1.57.0')){
if(_videoAd==null){
//初始化
if(tt.createRewardedVideoAd){
_videoAd = tt.createRewardedVideoAd({
adUnitId: '申請的激勵廣告ID'
});
//廣告顯示成功,先解除綁定close事件的監(jiān)聽器,為后續(xù)添加準備
if(typeof _videoAd != 'undefined'){
_videoAd.offClose((res)=>{
console.log('廣告組件解綁');
});
};
// return _videoAd;
_that.globalData.videoAd = _videoAd;
}else{
tt.showModal({
title: "提示",
content:
"當前客戶端版本過低,無法使用該功能,請升級客戶端或關閉后重啟更新。"
});
// return null;
_that.globalData.videoAd = null;
}
};
}else{
tt.showModal({
title: '提示',
content: '當前版本過低,無法獲取激勵廣告功能,請升級到最新版本后重試。'
});
};
},
compareVersion(v1, v2){
v1 = v1.split('.')
v2 = v2.split('.')
const len = Math.max(v1.length, v2.length)
while(v1.length < len){
v1.push('0')
}
while(v2.length < len){
v2.push('0')
}
for(let i = 0; i < len; i++){
const num1 = parseInt(v1[i])
const num2 = parseInt(v2[i])
if (num1 > num2){
return true;
}else if(num1 < num2){
return false;
}
}
},
//需求頁面,調用廣告
//獲取激勵廣告唯一對象
var rewardedVideoAdObj = app.globalData.videoAd;
//用戶點擊觸發(fā)的激勵廣告
showRewardedVideoAd(){
//廣告
if(rewardedVideoAdObj){
//版本符合,看廣告
rewardedVideoAdObj.show().then((res) => {
rewardedVideoAdObj.offClose(res=>{});
rewardedVideoAdObj.onClose(res => {
clearTimeout(rewardedVideoAdObj.iTimer);
rewardedVideoAdObj.iTimer = setTimeout(function(){
if(res.isEnded) {
// console.log('給與獎勵');
//觀看完廣告 -> 獎勵流程
adAllow();
}else{
// console.log('廣告未觀看完畢');
}
},500);
});
})
.catch(err => {
console.log("廣告組件出現(xiàn)問題", err);
//發(fā)生錯誤看不了廣告 -> 獎勵流程
adAllow();
});
}else{
//版本低,看不了廣告 -> 獎勵流程
adAllow();
}
},
//觀看完激勵廣告的獎勵流程
adAllow(){
dosomething...
},
0-2. 關于插屏廣告
值得注意的是:
- 不能打斷用戶的完整操作過程,例:不能在快速的信息流下拉刷新過程中插入廣告
- 不能在用戶剛打開小程序時就插入廣告,即便不在首屏,也需要等待一會兒再進入廣告所在頁面
否則報錯,APP剛剛啟動,廣告不能顯示
//抖音插屏廣告
interstitialAd(){
//目前只能在抖音使用該方法,今日頭條等宿主暫不支持
//插屏廣告組件每次創(chuàng)建都會返回一個全新的實例,默認是隱藏的,需要調用 InterstitialAd.show 將其顯示
//基礎庫 1.70.0 開始支持本方法
let version = tt.getSystemInfoSync().SDKVersion;
if(app.compareVersion(version,'1.70.0')){
//創(chuàng)建插屏廣告
// console.log('創(chuàng)建插屏廣告');
let interstitialAd = tt.createInterstitialAd({
adUnitId: "19j5e8eiiae4h5fa17",
});
if(typeof interstitialAd != 'undefined'){
interstitialAd.load().then(() => {
interstitialAd.show();
}).catch((err) => {
console.log(err);
});
};
}else{
tt.showModal({
title: '提示',
content: '當前版本過低,無法獲取插屏廣告功能,請升級到最新版本后重試。'
});
};
}
0-3. 關于上下架
手動下架的小程序(小游戲),需要再次審核,審核通過后,方可再次上架
暫時沒有微信的 臨時下架功能,如 下架進行維護,再自己操作上架,而無需審核
全局設置
- 如增加Tab需要設置圖標,
圖片路徑,icon 大小限制為 40kb,建議尺寸為 81px * 81px,當 postion 為 top 時,此參數(shù)無效,不支持網(wǎng)絡圖片
"tabBar": {
"color":"#666666",
"selectedColor":"#2a76ff",
"backgroundColor":"#FFF",
"list": [
{
"pagePath": "pages/index/index",
"text": "標題1",
"iconPath":"pages/resource/images/icon1.png",
"selectedIconPath":"pages/resource/images/icon1_ac.png"
},
{
"pagePath": "pages/second/index",
"text": "標題2",
"iconPath":"pages/resource/images/icon3.png",
"selectedIconPath":"pages/resource/images/icon3_ac.png"
}
]
}
- 頁面跳轉分為標簽跳轉和程序跳轉
標簽跳轉
<navigator
url="navigate?title=navigate"
hover-class="navigator-hover"
>
...
</navigator>
navigator-hover 默認為 {background-color: rgba(0, 0, 0, 0.1); opacity: 0.7;},<navigator/>的子節(jié)點背景色應為透明色。
2-0-1. 刪除 navigator 默認點擊樣式
法1
.navigator-hover { background-color:rgba(0,0,0,0); opacity:1;}
法2
<navigator hover-class="none" url="..."></navigator>
程序跳轉
tt.navigateTo({
url: '/pages/match_expert/index?planid='+_planId, //絕對路徑
url: '../match_expert/index?planid='+_planId, //相對路徑
success(res) {
console.log(`${res}`);
},
fail(res) {
console.log(`navigateTo調用失敗`);
}
});
【注】: 不能跳轉到 TabBar 頁面
2-0. 跳轉到TabBar的方法
tt.switchTab({
url: '../my/index',
success(res){},
fail(err){
console.log(err);
console.log(`navigateTo調用失敗`);
}
});
2-1. 跳轉只要到達對應文件夾即可,不用指定到xxx.ttml
2-2. 獲取跳轉的get參數(shù) , 在被跳轉頁的js文件,生命周期onload參數(shù)中獲取
onLoad: function(option){
let pageId = option.detailId || "";
if(pageId){
//獲取信息
this.getMes(pageId);
}
}
2-3. 如果需要跳轉的頁面,在app.json中的tabBar內,則需要使用
tt.switchTab({
url: `pages/my`,
success(res){},
fail(err) {
console.log(`switchTab調用失敗`);
}
});
2-4. 返回上一頁,如果目標頁面為非TAB頁,則可以使用
tt.navigateBack({
delta: 1,
success(res) {
console.log(`${res}`);
},
fail(res) {
console.log(`navigateBack調用失敗`);
},
});
小程序對Javascript語法的 支持程度 -
https://microapp.bytedance.com/dev/cn/mini-app/develop/framework/mini-app-runtime/javascript-support模塊化,提取公共腳本
導出
// common.js
function hello(name) {
console.log(`Hello ${name} !`);
}
module.exports.hello = hello;
導入
var common = require("common.js");
Page({
helloWorld: function() {
common.hello("world");
}
})
- 標簽有限,基本這三個就夠用了
view - div text - span image - img
5-1. 如果渲染的文本內包含html標簽,則需要使用rich-text。官方連接文檔 - https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/component/basic-content/rich-text
如:
<rich-text nodes="{{someData}}"></rich-text>
布局單位與實際操作
單位 rpx 據(jù)說可以根據(jù)屏幕比例變化
| 設備 | rpx 換算 px (屏幕寬度/750) | px 換算 rpx (750/屏幕寬度) |
| iPhone6 | 1rpx = 0.5px | 1px = 2rpx |
建議:
設計師可以用 iPhone6 作為視覺稿的標準。
即使用PSD尺寸為750px的設計稿,然后1:1測量,單位使用rpx即可圖片標簽新裁切屬性屬性,同web中CSS3的object-fit
7-1. 如果希望圖片寬度100%,高度自適應,
則需要給圖片添加mode="widthFix"和樣式 width;100% 即可,
如果不設置mode="widthFix",高度樣式不設置,那么會圖片會走默認的系統(tǒng)預制高度
如果不設置mode="widthFix",高度樣式設置auto,那么高度為0
兩種都不是圖片高度自適應
<image src="../resource/images/banner.jpg" mode="widthFix" />
7-2. 如果希望固定寬高內,不鏤空,則需要圖片設置寬高尺寸后,使用 mode使用aspectFill 即可
渲染轉譯符 增加屬性decode
<view><text decode>?</text></view>調試工具緩存有時候很大,通過開發(fā)者工具清除緩存按鈕不好使,直接退出再重進,即可
9-1. setData給對象內屬性賦值
Page({
data: {
gameData:{
level:0, //評價等級
totalScore:0, //總分數(shù)
questionIndex:0 //當前題目
}
}
});
設置數(shù)據(jù)
this.setData({
['gameData.totalScore']: 1000
});
9-2. setData給對象內屬性賦值,且子對象為變量
data:{
totalMatchData:[ [],[],[],[],[] ]
}
//indexNum為數(shù)組索引,且為變量
var _mdata = 'totalMatchData['+indexNum+']';
_that.setData({
[_mdata]: xxx
});
BUG:這種方式如果設置多個動態(tài)數(shù)據(jù),只有最后一個會生效????。。?!
- 返回頂部 按鈕怎么操作頁面
- 針對scroll-view
可以使用重置這個屬性的值 scroll-into-view ,值為頁面上目標點的元素的id即可,注意這個元素必須是 scroll-view 包含結構內的
注:
如果元素的在底部觸發(fā)了bindscrolltolower觸底行為,通過scroll-into-view指向這個元素的ID,
會將scorll-view拉回到這個元素位置,但是其實這樣做,會多滾動一屏的高度,需要修正這個位置
所以,可以在這個元素上添加position:absolute定位元素到 負一屏的高度,如果還有偏差可以用padding做二次修正
補充:
- 對于<scroll-view> 必須要添加一個高度限制,否則效果不會生效
- 指定錨點元素的id (不能以數(shù)字開頭)
- 如果在<scroll-view>內動態(tài)添加循環(huán)元素,該組件會回到 scrollTop 為 0 的位置,需要將 scroll-view 組件的全部子元素包裹一層 view 可避免該問題 (官方提示)
- 錨點ID元素可以放置在循環(huán)元素內部
//--- ttml
<!-- scroll-view -->
<scroll-view
class="scrollViewBox"
scroll-y
scroll-with-animation
scroll-into-view="{{returnTopEle}}"
>
<view class="mainTopShow style2Box">
<!--陣容-->
<text id="topEleTar"></text>
<include src="../../resource/template/match_detail_2.ttml" />
</view>
</scroll-view>
//--- js
data: {
returnTopEle:"topEleTar"
},
returnFn(){
this.setData({
returnTopEle:"topEleTar"
});
}
- 針對非scroll-view
使用API
tt.pageScrollTo({
scrollTop: 0,
duration: 1000,
success(res) {
//console.log(`pageScrollTo調用成功`);
},
fail(res) {
console.log(`pageScrollTo調用失敗`);
}
});
網(wǎng)絡請求需要配置白名單 位置 -
https://developer.toutiao.com/dev/cn/mini-app/develop/api/other/network-request-reference
特別注意:線上環(huán)境,網(wǎng)絡請求僅支持 https 協(xié)議的 URL小程序點擊,不支持事件處理函數(shù)傳參,帶上參數(shù)會認定整體為 函數(shù)名,報錯未定義
解決通過自定義屬性
<view class="selectBtns pos1 ac" bindtap="selectClickFn" data-index="1">
<view class="selectBtn1Bg">
<text>羅納爾多</text>
</view>
</view>
事件處理函數(shù)默認參數(shù)為事件對象,通過其可以過得自定義屬性
selectClickFn(ev){
console.log(ev.currentTarget.dataset.index); //1
}
12-1. 阻止事件冒泡的行為要通過修改事件本身來做,不支持修飾符,不支持事件對象stopPropagation
如
觸摸點擊事件: bindtap --> catchtap 即可
小程序中存在冒泡的事件:

注:
事件綁定后沒有反應的情況,可能是以下情況
- text標簽內嵌套text標簽,在里面的text上綁定事件,無法觸發(fā)
- canvas在小程序(非小游戲)中,存在開發(fā)工具渲染正常,真機尺寸錯誤的情況
表現(xiàn) 以 iphone6 為基準繪制的頁面,在iphone7 plus上表現(xiàn) canvas變小,在小米5上表現(xiàn) canvas變大
因此需要對canvas繪圖尺寸進行二次修改
如:
tt.getSystemInfoSync().windowWidth -- 真機寬度
tt.getSystemInfoSync().windowHeight -- 真機高度
開發(fā)者工具中,如選擇iphone6為開發(fā)參照,則獲取設備寬高做參照
以iphone6為例
375 -- 模擬器上寬度
603 -- 模擬器上高度
在真機上,模擬器數(shù)據(jù)需要修正的比例值
寬度修正比例值 = 真機寬度 / 模擬器上寬度
高度修正比例值 = 真機高度 / 模擬器上高度
【實操】: iphoneX修正異常、如果修正后的高度大于寬度,需要調整高度為寬度的數(shù)值
再次測試,iPhone與小米真機表現(xiàn)一致了

小程序支持的canvas屬性 字節(jié)跳動小程序官網(wǎng) -
https://microapp.bytedance.com/dev/cn/mini-app/develop/api/interface/canvas-draw/tt.createcanvascontext小程序圖片預加載
循環(huán)數(shù)組數(shù)據(jù)創(chuàng)建圖片,通過圖片的bindload與binderror的變化,來判斷加載進度與是否加載完畢,
暫時沒有發(fā)現(xiàn)new Image()或類似 小游戲的tt.createImage的東西
<!-- ttml -->
<!--加載圖片-->
<view class="onlyLoadImgArr">
<view tt:if="{{allImgArr}}" tt:for="{{allImgArr}}" tt:for-index="idx" tt:key="*this">
<image bindload="bindloadFn" binderror="binderrorFn" src="{{item}}" >
</view>
</view>
<!--js-->
//圖片加載
bindloadFn(res){
let _that = this;
if(res.type=="load"){
loadNum+=1;
this.setData({
allImgLoad:Math.floor(loadNum/sumLoadNum*100)
});
if(loadNum==sumLoadNum){
this.setData({
allLoadText:'加載完畢'
});
setTimeout(function(){
_that.setData({
scene:2
});
},800);
}
}
},
binderrorFn(err){
console.log('圖片加載錯誤');
console.log(err);
}
- 音頻
- 分為背景音頻
const backgroundAudioManager = tt.getBackgroundAudioManager();
backgroundAudioManager.src = "https://xxx/0000-0001.mp3";
BackgroundAudioManager.play();
BackgroundAudioManager.pause();
BackgroundAudioManager.stop();
//音頻加載回調
BackgroundAudioManager.onWaiting(function callback)
//音頻播放中
paused --- boolean | 當前音頻是否處于暫停狀態(tài),只讀
- 普通音頻
const innerAudioContext = tt.createInnerAudioContext();
innerAudioContext.src = "https://someaudiourl";
innerAudioContext.volume = 0.5; //范圍 0~1。默認為 1 只讀
innerAudioContext.onPlay(() => {
console.log("開始播放回調");
});
innerAudioContext.pause();
InnerAudioContext.stop();
- 動畫 animate.css 需要修改下后綴名 如 .ttss
<!--觸發(fā)動畫-->
<view class="{{scene==2? 'bounce animated':' '}}"></view>
- 獲取 基礎庫版本 (' 論壇提交問題需要基礎庫版本號 ');
所有參數(shù) -https://developer.toutiao.com/dev/cn/mini-app/develop/api/device/system-information/tt.getsysteminfo
//獲取基礎庫版本
tt.getSystemInfo({
success(res) {
console.log(res.SDKVersion);
}
});
- 全局數(shù)據(jù)的獲取與設置
在app.js中
//獲取
onLaunch: function () {
console.log(this.globalData.some_data); // 10
},
//全局數(shù)據(jù)
globalData: {
some_data:10
},
...
//設置
this.globalData.some_data= 1000;
在非app.js中
const app = getApp()
//獲取
console.log(app.globalData.some_data);
//設置
app.globalData.some_data = 1000;
19-1. 關于onLaunch生命周期,有且僅加載一次,可以通過二維碼帶參數(shù),參數(shù)的獲取方式為
以自定義參數(shù)'channel'為例
if(options.query && options.query.channel){
_that.globalData.channel = options.query.channel;
};
返回參數(shù)的種類 - https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/api/foundation/lifecycle/tt-get-launch-options-sync/

19-2. 當頁面通過二維碼加載出錯時候,需要重定向
onPageNotFound(res) {
//如果不是 tabbar 頁面
tt.redirectTo({
url: "pages/index/index",
});
//如果是 tabbar 頁面,請使用 tt.switchTab
tt.switchTab({
url: "pages/index/index",
});
},
- 獲取自定義屬性
20-1. 在非組件標簽上掛載屬性時候,可以使用event.currentTarget.dataset.index來獲取;
//--ttml
<view class="nav" data-index="100" bindtap="navClickFn">
<text>按鈕1</text>
</view>
//--js
function navClickFn(event){
if(typeof event.currentTarget.dataset.index!= 'undefined'){
let _index = event.currentTarget.dataset.index;
console.log(_index); //100
}
}
20-2.獲取組件
區(qū)別與使用普通標簽,在組件上掛載data屬性,可以使用 event.target.dataset.index來獲取; 如: pinker組件
- 導入ttml模板
<include src="../../resources/ttml/header.ttml" />
- InnerAudioContext與BackgroundAudioManager沖突,音效會終止BackgroundAudioManager背景音
解決的方法:
將背景音頻(BackgroundAudioManager)用音效(InnerAudioContext)代替,
//背景音函數(shù)
let bgsrc = 'https://www.aaa.com/bgm1.mp3';
function createBGMObj(_src){
var bgmOb = tt.createInnerAudioContext();
bgmOb.src = _src;
bgmOb.autoplay = true;
bgmOb.loop = true;
//音頻加載中
bgmOb.onWaiting(function(res){ });
//音頻可播放
bgmOb.onCanplay(function(){
bgmOb.play();
});
return bgmOb;
};
const gameBGM = createBGMObj(bgsrc);
if(!gameBGM.paused){
this.setData({
bgmBtnStatue:true //音頻播放的UI類名
});
gameBGM.stop && gameBGM.stop();
console.log('暫停');
}else{
this.setData({
bgmBtnStatue:false //音頻暫停的UI類名
});
gameBGM.play && gameBGM.play();
console.log('播放');
}
- 小程序輪播圖指示點顏色 ( 默認: indicator-color ,選中:indicator-active-color)
更多參數(shù)配置 -https://developers.weixin.qq.com/miniprogram/dev/component/swiper.html
<swiper
indicator-dots="true"
indicator-color="rgba(255,255,255,0.5)"
indicator-active-color="#f60"
autoplay="true"
interval="3000"
duration="500"
>
<block tt:for="{{bannerResult}}">
<swiper-item>
<view class="swiper-item">
<image src="{{item.list_pic}}" mode="aspectFill" />
<text class="mes">{{item.title}}</text>
</view>
</swiper-item>
</block>
</swiper>
- 修改swiper容器高度
//--ttml
<view class="bannerBox">
<swiper
indicator-dots="true"
indicator-color="rgba(0,0,0,0.5)"
indicator-active-color="#f60"
>
...
//--ttss
.homePage .ourCup .bannerBox -- 包裹swiper容器高度
.homePage .ourCup .bannerBox tt-swiper -- swiper自身高度
- 背景圖 -- ( 圖片精靈可以放在這里 )
樣式中使用本地背景圖,可以將背景圖文件與app.json同級,然后通過
background:url('resource/image/bell.png') no-repeat left top;
或者使用 base64、或者網(wǎng)絡圖片
- 下載遠程圖片至本機相冊
//--- ttml
<button type="primary" bindtap="downloadImgFn">下載</button>
//--- js
downloadImgFn(){
var _src = 'https://www.xxx.com/upload/images/temp/3.d5aabd23.jpg'
tt.getImageInfo({
src: _src,
success(res) {
var _path = res.path
tt.saveImageToPhotosAlbum({
filePath: _path,
success(res) {
console.log(`saveImageToPhotosAlbum調用成功`);
},
fail(res) {
console.log(`saveImageToPhotosAlbum調用失敗`);
}
});
}
});
}
26-1. 方式2,通過 tt.downloadFile 下載網(wǎng)絡圖片至本地緩存,獲取tempFilePath,然后通過
tt.saveImageToPhotosAlbum,配置上參數(shù)tempFilePath,下載至本地相冊
這個過程中會詢問用戶相冊的權限,此外需要配置下載文件白名單
且白名單為https協(xié)議,不能加端口號,否則失效

組件 switch
修改背景色 使用 屬性 color設置
修改大小,使用transform:scale(xxx) 調整定位為fixed的元素不要放在scroll-view中,真機不會生效(不像模擬器)
表單輸入框、文本域限制字數(shù)
ttml
<view class="topArea">
<textarea
class="textareaBox"
placeholder="請輸入您的意見或建議"
placeholder-style="color:#c9c9c9;"
bindinput="textareaInputFn"
bindblur="textareaBlurFn"
value="{{txtValue1}}"
maxlength="{{limitNum1}}"
/>
<view class="textareaBtm">
<text>{{txtLength1}}/{{limitNum1}}</text>
</view>
</view>
js
data: {
txtValue1:'',
txtLength1:0,
limitNum1:100
},
textareaInputFn(event){
var txtObj = event.detail;
var _txt = txtObj.value;
var _txtLength = txtObj.cursor;
if(_txt.length == this.data.limitNum1){
commonTool.showPop('字數(shù)已經(jīng)達到上限');
}
//限制字數(shù)
var _txt2 = _txt.substring(0,this.data.limitNum1);
var _txtLength2 = _txt2.length;
//事件對象屬性修改
txtObj.value = _txt2;
txtObj.cursor = _txtLength2;
this.setData({
txtValue1: _txt2,
txtLength1: _txtLength2
});
},
textareaBlurFn(event){},
注:textarea不像input,無法通過setData來改變綁定其上的value數(shù)據(jù),真實限制字數(shù)通過maxlength屬性生效
29-1. event.detail.cursor 為光標位置
通過輸入框光標位置,也可以判斷字數(shù)達到上限與否
[注]: textarea 背景色BUG
Bug:當在 <textarea>外面包裹一個元素且設置有背景值顏色時,在 Android 手機上背景顏色是不能在<textarea>上生效的。此處可以通過直接在 <textarea> 上設置與包裹元素相同的背景顏色解決。
if(event.detail.cursor > 40){
...do something
}
29-2. 表單 input 的type 可以設置多種
| 值 | 說明 | 最低支持版本 |
|---|---|---|
| text | 文本輸入鍵盤 | 1.0.0 |
| number | 數(shù)字輸入鍵盤 | 1.0.0 |
| digit | 帶小數(shù)點的數(shù)字鍵盤 | 1.0.0 |
真機與模擬器在include上渲染不一
模擬器可以使用<include src="../../resource/template/xxx.ttml" />,引入模板文件;
但是在真機上無法完成渲染,
解決方法,將內容從include中拿出來純文本中存在
\r\n,需要渲染出換行效果,
pre-wrap : 保留空白符序列,但是正常地進行換行。
<view style="white-space:pre-wrap">
{{item.scoreTabData.description}}
</view>
tt:for操作報錯
Polymer::Attributes: couldn't decode Array as JSON:
解決方法,
修改循環(huán)單位名字 如: tt:for-item="seasonItem"多重三目運算
matchInsetItem.status=='Uncertain'?'待定':(matchInsetItem.status=='Postponed'?'推遲':(matchInsetItem.status=='Played'?'結束':''))
swiper與按鈕聯(lián)動
點擊按鈕,切換swiper的current,聯(lián)動觸發(fā)swiper其bindchange事件,
相反的,切換swiper,并不會觸發(fā)按鈕的bindtap事件,
所以關聯(lián)是單向的,最終會體現(xiàn)在swiper的bindchange上
因此,為了防止swiper中嵌套scroll-view后,縱向滑動scroll-view引起事件,觸發(fā)swiper橫向操作誤判
可以操作索引后,僅在swiper的bindchange上,開啟延遲定時器,觸發(fā)事件【優(yōu)化】
tt:if 顯示隱藏 -- 渲染成本高 [ 初始化渲染使用,不頻繁顯示消失 ]
通過樣式控制顯示隱藏 -- 渲染成本低 [ 頻繁切換顯示、消失使用 ]隱藏loading,彈出toast,存在沖突
loading 的實現(xiàn)基于 toast,所以hideLoading也會將 toast 隱藏。
解決:
app.showPop('登錄成功',400);
setTimeout(()=>{
app.hideLoadPop();
},800);
- 授權被拒,無法拉起再次授權
- 常規(guī)情況下,是主觀還是意外拒絕授權后,是無法再次拉起授權的。需要退出小程序,二次進入,才可重新打開授權
但是,可以通過openSetting,來拉起,( 設置頁面只包含用戶請求過的權限 ) - 通過一個自定義按鈕來觸發(fā)即可
tt.openSetting({
success:function(res){
... 內部邏輯
},
fail:function(err){
...
}
});
37-1. 授權被拒后,內部邏輯,可以根據(jù)用戶最新授權變化,再進行業(yè)務
具體的授權列表 - https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/api/open-interface/set/auth-setting/
tt.openSetting({
success:function(res){
if(res.authSetting['scope.userInfo']){
//再次登錄邏輯
}
},
fail:function(err){
...
}
});
注:
- 不能在初始頁面就進行用戶強制授權,
- 授權需要用戶點擊按鈕,彈出浮層,其中解釋需要用戶什么信息,以及提供兩個按鈕,確認授權、取消授權,在點擊確認授權后,在調用官方的使用用戶信息授權的API
- 在拉起授權API后,如果用戶點擊取消授權時候,需要單獨提供一個手動授權的按鈕,因為之前的授權API很長的一段時間會直接選擇禁止授權(出于防止用戶被騷擾的原因),如果用戶想選擇確認授權,需要通過一個按鈕點擊調用授權菜單,用戶自行去打開授權即可

- scope列表:
| scope | 對應接口 | 描述 |
|---|---|---|
| scope.userInfo | tt.getUserInfo | 是否授權用戶信息 |
| scope.userLocation | tt.getLocation,tt.openLocation | 是否授權地理位置 |
| scope.address | tt.chooseAddress | 是否授權通訊地址 |
| scope.record | tt.getRecorderManager.start | 是否授權錄音功能 |
| scope.album | tt.saveImageToPhotosAlbum,tt.saveVideoToPhotosAlbum | 是否授權保存到相冊 |
| scope.camera | tt.scanCode,tt.chooseImage,tt.chooseVideo | 是否授權攝像頭 |
- 上傳圖片
(第一步).需要拉起本地相冊,【需要用戶授權】
tt.chooseImage({
sourceType: ["album"],
count:1,
success(res) {
this.setData({
imagePath: res.tempFilePaths[0],
imageFile: res.tempFiles[0]
});
},
fail(res) {
console.log(`chooseImage調用失敗`);
}
});
(第二步).上傳文件
let fileTask = tt.uploadFile({
url: _baseUrl+'api/file',
filePath: (上一步的imagePath),
name: "file",
success(res) {
if(res.statusCode === 200){
//將用戶頭像在數(shù)據(jù)庫內修改
....
}
},
fail(err){
console.log(err);
console.log(`uploadFile調用失敗`);
}
});
38-1. 上傳圖片分為2步操作,選擇圖片部分可以一次選擇多張,但是上傳圖片目前API只支持單張上傳
輸入框修改內容如何優(yōu)化
通過bindinput修改數(shù)據(jù),但是不要使用setData,降低設置數(shù)據(jù)性能消耗,將其值存在變量中
在bindblur時候,進行setData,將變量賦值到對應數(shù)據(jù)上去;
此外bindblur時候setData需要時間,不是立即完成的,所以需要開啟延遲定時器,再去setData版本更新
要點:1.需要做低版本兼容 2.上一個版本需要有此代碼,此代碼才有反應,否則沒有返回
//獲取版本信息
if(tt.getUpdateManager){
var updateManager = tt.getUpdateManager();
updateManager.onCheckForUpdate(function(res) {
// 請求完新版本信息的回調
console.log("onCheckForUpdate", res.hasUpdate);
if (res.hasUpdate) {
tt.showToast({
title: "即將有更新請留意"
});
}
});
updateManager.onUpdateReady(() => {
tt.showModal({
title: "更新提示",
content: "新版本已經(jīng)準備好,是否立即使用?",
success: function(res) {
if (res.confirm) {
// 調用 applyUpdate 應用新版本并重啟
updateManager.applyUpdate();
} else {
tt.showToast({
icon: "none",
title: "小程序下一次「冷啟動」時會使用新版本"
});
}
}
});
});
updateManager.onUpdateFailed(() => {
tt.showToast({
title: "更新失敗,下次啟動繼續(xù)..."
});
});
}else{
tt.showModal({
title: "提示",
content:
"當前客戶端版本過低,無法使用該功能,請升級客戶端或關閉后重啟更新。"
});
}
- 生命周期
- navigateTo, redirectTo 只能打開非 tabBar 頁面。
- switchTab 只能打開 tabBar 頁面。
- reLaunch 可以打開任意頁面。
同級AB欄目之間的 TAB切換
A ~> B A.onHide(), B.onLoad(), B.onShow()
C ~> A C為A詳細頁 C.onUnload(), A.onShow()
一般情況,先onload,后續(xù)同級TAB切換為onShow ; TAB進去非TAB詳細頁則,觸發(fā)onLoad,onShow
- 跳轉返回上一步
tt.redirectTo不能跳轉到 TabBar 頁面
但是 redirectTo方法生成的頁面,可以返回到 TabBar 頁面
backBtnFn(){
tt.navigateBack({
delta: 1,
success(res) {
// console.log(`${res}`);
},
fail(res) {
console.log(`navigateBack調用失敗`);
}
});
}
- 下拉刷新、上拉加載
(1).配置app.json中window屬性,enablePullDownRefresh 與 onReachBottomDistance
"window":{
"navigationBarTextStyle":"black",
"navigationBarBackgroundColor": "#fff",
"backgroundTextStyle": "dark",
"navigationBarTitleText": "xxxxxxx",
"enablePullDownRefresh": true,
"onReachBottomDistance":50
},
(2).在頁面的js中配置
//下拉刷新
onPullDownRefresh(){
console.log('onPullDownRefresh');
tt.stopPullDownRefresh();
},
//上拉加載
onReachBottom(){
console.log('加載下一頁');
}
(3).不能使用scroll-view組件,需要使用普通view
(4).返回頂部,使用tt.pageScrollTo()
- 小程序分享
(1). 后臺配置 - (設置-基礎設置-分享設置),配置圖文之后提審,等待審核
(2). js頁面內配置 onShareAppMessage 事件處理函數(shù),使用上面分享信息
Page({
...
onShareAppMessage (option) {
// option.from === 'button'
return {
title: '這是要轉發(fā)的小程序標題',
desc: '這是默認的轉發(fā)文案,用戶可以直接發(fā)送,也可以在發(fā)布器內修改',
path: '/pages/index/index?from=sharebuttonabc&otherkey=othervalue', // ?后面的參數(shù)會在轉發(fā)頁面打開時傳入onLoad方法
imageUrl: 'https://e.com/e.png', // 支持本地或遠程圖片,默認是小程序 icon
templateId: '這是開發(fā)者后臺設置的分享素材模板id',
success () {
console.log('轉發(fā)發(fā)布器已調起,并不意味著用戶轉發(fā)成功,微頭條不提供這個時機的回調');
},
fail () {
console.log('轉發(fā)發(fā)布器調起失敗');
}
}
},
onLoad (query) {
if (query.from === 'sharebuttonabc') {
// do something...
}
}
});
44-2.小程序 通過自定義按鈕分享
(1). 按鈕的分享 - https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/api/open-interface/share/retweet
(2). <button open-type="share">轉發(fā)到微頭條</button>
44-3. 如果判斷是用戶點擊的詳情頁,還是通過分享過來的詳情頁
通過app.js內的launch,或者小程序內的onload 來獲取參數(shù)
注:小程序調試期間,怎么在launch中測試傳遞過來的參數(shù)?
官方說明 - https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/api/other/param-use/
開發(fā)者工具內,就可以配置初始參數(shù)

44-3. 分享內容定義的優(yōu)先級
今日頭條/頭條極速版:
| 場景 | 優(yōu)先級 |
| 端內分享 | 代碼指定 > 模板指定 > 平臺默認 |
| 端外分享 | 模板指定 > 平臺默認 |
在腳本中還是設置標題以及描述為空,這樣在開發(fā)者后臺通過切換激活的分享模板,可以修改模板內容
但是修改新的分享模板,仍需要在代碼中配置,怎么都會影響修改分享的效率
- 關于登錄授權流程
先使用tt.login獲取臨時登錄code憑證,在把code、appid、secret發(fā)送后臺,獲取openid(openid 是用戶的唯一標識)
后臺流程地址 -https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/log-in/code-2-session/
獲取openid
企業(yè)級賬號中 webview配置
首先,開發(fā)設置 - webview域名 - 下載檢驗文件
其次,將授權文件放置在需要配置域名的根路徑下,確??梢酝ㄟ^域名+檢驗文件,可以訪問文件內容即可同時一次同時設置多個數(shù)據(jù),如果涉及緩動動畫與表單焦點聚焦,可能會引起渲染問題,
解決方式,先設置有動畫的數(shù)據(jù),再添加一個演示定時器,設置獲取焦點的數(shù)據(jù)浮層上有表單,如果設置點擊空白區(qū)域使浮層消失,那么點擊表單輸入(bindinput),會引起冒泡行為,造成浮層關閉。
解決方法:
第一步,通過設置表單的焦點數(shù)據(jù)(focus),以及丟失焦點事件(bindfocus),在點擊浮層之外的空白區(qū)域時候,判斷輸入框是否存在焦點狀態(tài)(focus數(shù)據(jù)情況),如果存在就不要做浮層消失,否則就叫浮層消失
第二步,表單輸入框失去焦點事件中(bindfocus),將表單的焦點屬性數(shù)據(jù)(focus)重置,使浮層可以被正常關閉
解決方法2:百度一下,未解決
給表單元素添加catchtap事件,事件處理函數(shù)為空,但是效果是,表單無法輸入文字了,????scroll-view 如果希望其內部橫向無限滾動,只需要設置兩個樣式
其一,scroll-view容器設置 width:100%;
其二,scroll-view內部包裹一個滾動元素,為其設置 display:inline-block; white-space:nowrap;頁面滾動,導航的吸頂效果
準備:
第一,判斷方法存在與否 if(tt.createSelectorQuery){ ... }
說明:
一般新增的 api 在低版本基礎庫上是不存在的,貿然調用會導致錯誤。建議做如下判斷:
if (tt.navigateToMiniProgram) {
tt.navigateToMiniProgram();
} else {
tt.showModal({
title: "提示",
content:
"當前客戶端版本過低,無法使用該功能,請升級客戶端或關閉后重啟更新。",
});
}
第二,在一個web-view中綁定滾動事件 bindscroll="scrollFn"
第三,在滾動事件中,調用選擇元素的方法,判斷其距離頂部距離
然后改變數(shù)據(jù),渲染導航fixed類名存在與否
if(domQuery){
domQuery.select('#filterhead').boundingClientRect(rect=>{
// console.log(rect.top);
if(rect.top<=0){
if(!_isFilterFixed){
_that.setData({
"isFilterFixed":true
});
}
}else{
if(_isFilterFixed){
_that.setData({
"isFilterFixed":false
});
}
}
}).exec();
}
第四,注意盡量節(jié)流,添加判斷條件,除非必要,否則不要setData吸頂與否的類名
否則,由于滾動事件觸發(fā)頻率極快,因此即使設置的是相同的數(shù)據(jù),也會造成導航在狀態(tài)之間切換,因為setData也是需要消耗時間的,且這個時間可能要大于觸發(fā)事件的間隔頻率
- 組件配置
第一,先配置【子組件】信息,新建對應的ttml、ttss、js、json
組件json需要配置
{
"component": true
}
組件的js內要設置組件信息
Component({
properties: {
//通過父組件屬性,傳遞至子組件內的數(shù)據(jù)
diyattr: {
type: String,
value: "默認標題文案",
}
},
data:{
// 組件內部數(shù)據(jù)
defaultStates: { ... }
},
methods:{
// 自定義方法,可以將數(shù)據(jù)100通過自定義事件'myevent',傳遞給父組件
fn1(){
this.triggerEvent(
'myevent', '傳遞的數(shù)據(jù)', ‘配置的數(shù)據(jù)(bubbles、composed、capturePhase)’
)
}
}
});
鏈接:配置事件選項 - https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/framework/custom-component/correspondence-and-event-among-components
第二,配置【父組件】信息,新建對應的json
配置組件簡稱與路徑
{
"usingComponents": {
"my-component": "/components/test/index"
}
}
在ttml內使用組件
<my-component
diyattr="傳遞給子組件內的數(shù)據(jù)~"
bindmyevent="子組件自定義事件名myevent"
class="comp {{isShow?'compShow':''}}"
>
<view>通過slot傳遞到子組件內的數(shù)據(jù)</view>
</my-component>
如果配置了json,如果不寫東西,會報錯
所以得寫點什么
{ "component": false }
{ "usingComponents": {} }
在自定義事件bindmyevent的事件處理函數(shù)中,通過event的detail獲取傳遞的數(shù)據(jù)
51-1. 自定義組件無法使用全局樣式,則在組件js中配置
options: {
addGlobalClass: true
}
即可
51-2. 組件無法使用組件外樣式,在組件內樣式,只能使用當前組件上的類名
有些樣式不允許在組件內使用
Some selectors are not allowed in component ttss, including ID selectors, and attribute selectors
- 作用域 js
第一、導出js,聲明 文件夾 /resources/js/md5.js
...xxxx....
module.exports.hex_md5 = hex_md5;
第二、引入js,在/app.js中
let mdjs = require("/resources/js/md5.js");
App({
md5Fn(str){
return mdjs.hex_md5(str);
}
})
第三、外部頁面使用 js中
app.md5Fn('xxxx')
css作用域:將外部css引入app.ttss
/** app.ttss **/
@import "common.ttss";
兩個非TAB頁面之間跳轉,數(shù)據(jù)狀態(tài)不同步
如:
詳細頁通過路由(tt.navigateBack)返回列表頁,在列表頁觸發(fā)了onShow鉤子函數(shù),但是數(shù)據(jù)不會重新加載
這樣會引起一個問題:
如果詳細頁點擊取消關注,返回列表頁,不會重新渲染,因為數(shù)據(jù)有緩存,列表頁不會重新渲染
解決方法:
將關注列表內容放到localStorge內,如果有操作關注的東西,及時改變localStorage,來作為變化參照憑證二級聯(lián)動pinker組件,TAB頁面之間跳轉,會出現(xiàn)問題
表現(xiàn): 是聯(lián)動數(shù)據(jù)一級內容,為之前某二級列表內容,而非標題
解決: 頁面,onshow生命周期 重置聯(lián)動pinker初始索引轉發(fā)與分享
第一步,開發(fā)者后臺配置小程序分享,過審后,獲得分享ID
第二步,添加標簽
<button open-type="share">轉發(fā)到微頭條</button>
第三部,配置分享觸發(fā)生命周期函數(shù) 官方示例 -https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/api/open-interface/share/retweet-- 配置參數(shù),其中包括上面獲得的分享ID參數(shù)支付接入
先獲取訂單orderid、再獲取orderInfo(后臺返回),最后調用tt.pay
其中orderInfo里的 risk_info是字符串格式,里面是json,注意符號 ' 與 " 的使用。
risk_info: "{'ip':'xxx.xxx.xxx.xxx'}"
支付寶官方工具 - https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/api/open-interface/payment/mini-app-pay-plugin-reference/faq/:
通過orderInfo內的alipay_url,可以查詢支付寶支付,能否拉起。
浮層上如果有一個textarea, 如果通過修改class類名方式,控制浮層的顯示與消失,那么textarea會不顯示文字,且無法獲得焦點,拉起手機鍵盤。
解決方法,通過tt:if 來控制顯示層的顯示與隱藏,那么textarea就可以操作了在小程序中,使用第三方客服拆件,用于聊天功能
方法一,其核心是調用小程序官方組件<button type='default' open-type='contact'> 打開客服 </button>:
芝麻小客服 接入引導視頻 -https://ossxkf.oss-cn-beijing.aliyuncs.com/douyinjieru.mp4
芝麻小客服 官方鏈接地址 -https://www.kancloud.cn/wikizhima/wikixkf/1835308
客服按鈕配置 -https://www.kancloud.cn/wikizhima/wikixkf/1010958
方法二,其核心是使用webview
芝麻小客服 接入引導視頻 - https://ossxkf.oss-cn-beijing.aliyuncs.com/20.mp4
小程序需要企業(yè)賬號(需上傳校驗文件至服務器根目錄,完成校驗),且三方平臺必須是付費用戶才可以設置自定義域名(付費用戶包括標準版、專業(yè)版、企業(yè)版等付費版本)
配置自定義域名--官方連接 - https://xiaokefu.com.cn/yun/yunAdmin/yunDomain?parent_nav_label=yundomain&nav_label=yundomain&wechatapp_id=201539&channel_id=10740
自定義域名也可以傳遞參數(shù) - https://www.kancloud.cn/wikizhima/wikixkf/1109086
- 微信小程序遷移字節(jié)跳動小程序
提前申請一個字節(jié)小程序,獲取appid,開發(fā)者工具遷移按鈕,依照要求輸入微信小程序項目地址、字節(jié)小程序輸出地址、appid、小程序名即可進行遷移
后續(xù)工作:
手動進行的語法轉化
1. tt:for-items 修改為 tt:for ; tt:key修改為tt:for-index="idx";
2. navigator跳轉,在pages下的頁面需要在app.json中注冊,但是微信小程序好像在pages下二級文件夾文件無需配置,這個在字節(jié)跳動小程序不行
3. 組件不一樣,如pinker
小程序 時間對象 開發(fā)工具正常,但是iphone真機報錯
new Date("2019-07-24 19:57") 應該寫成 new Date("2019/07/24 19:57")
需要對時間字符串處理下,如 new Date(tm.replace(/-/g,'/'))圖片預覽的輪播API 地址 -
https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/api/media/picture/tt-preview-image
預覽一組圖片,可以理解為一個封裝好的 圖片pop banner
62.通過抖音掛載能力,可以抖音發(fā)布小視頻時候,掛載小程序。
這一功能需要提前申請

代碼內調用 ( 僅針對當前頁面有效,屬于二次修改分享內容,默認分享是開發(fā)者后臺首次申請的配置文案 )
onShareAppMessage (option) {
// option.from === 'button'
return {
title: app.globalData.shareMes.tit,
desc: app.globalData.shareMes.txt,
path: app.globalData.shareMes.path, // ?后面的參數(shù)會在轉發(fā)頁面打開時傳入onLoad方法
imageUrl: '', // 支持本地或遠程圖片,默認是小程序 icon
templateId: app.globalData.shareMes.templateId,
success(){
//console.log('轉發(fā)發(fā)布器已調起,并不意味著用戶轉發(fā)成功,微頭條不提供這個時機的回調');
},
fail(){
console.log('轉發(fā)發(fā)布器調起失敗');
}
}
}
- 圖片安全性驗證
上傳圖片時候,需要在tt.uploadFile時候操作
【注】:
- 今日頭條 客戶端,上傳圖片至服務器后,將服務器圖片發(fā)送至頭條進行驗證,需要壓縮體積至1.4MB以下,否則報錯 error3 圖片下載失敗
- 需要將頭條域名
https://developer.toutiao.com添加至白名單
流程圖
//-- app.js
globalData:{
imgCheckURL:'https://developer.toutiao.com/api/apps/censor/image',
accessToken: '',
accessTokenExpireTime: ''
},
/*
* ajaxFn
* options : {
* "url": xxx, "data": {}, "method": xxx, "dataType":xxx, "sucFn": xxx, "errFn": xxx
* }
*/
ajaxFn(options,isOutLink){
var _that = this;
tt.request({
url: isOutLink ? options.url : (_that.globalData.baseURL + '/' + options.url),
data: options.data || null,
header: {
"content-type": "application/json",
},
method: options.method || "GET",
dataType: "json", //返回數(shù)據(jù)的類型,支持 json、string
success(res){
options.sucFn && options.sucFn(res);
},
fail(res){
options.errFn && options.errFn(res);
}
});
},
/*token 是小程序級別 token,不要為每個用戶單獨分配一個 token,會導致 token 校驗失敗。建議每小時更新一次即可
* 檢查access token是否過期,過期則重新獲取token,否則返回當前token
* cbFn 里面參數(shù)返回access token
*/
checkTokenExpireFn(cbFn){
//時間戳 統(tǒng)一換算秒
let _that = this;
let nowTimeStamp = new Date().getTime()/1000;
let oldTimeStamp = _that.globalData.accessTokenExpireTime;
//一小時一換的話 3600 秒
if(oldTimeStamp!=''){
//檢查過期與否
if(nowTimeStamp - oldTimeStamp>=3600){
//過期了,重新獲取token
_that.getAccessTokenFn(function(res){
//設置token,以及過期時間戳
if(res.statusCode==200){
_that.globalData.accessToken = res.data.access_token;
_that.globalData.accessTokenExpireTime = nowTimeStamp;
//回調
cbFn && cbFn(res.data.access_token);
};
},function(err){
_that.showPop('獲取token失敗',1000,'fail');
console.log(err);
//回調
cbFn && cbFn('');
});
}else{
//沒過期
cbFn && cbFn(_that.globalData.accessToken);
}
}else{
//初次使用,直接去獲取token,等同過期
//過期了,重新獲取token
_that.getAccessTokenFn(function(res){
//設置token,以及過期時間戳
if(res.statusCode==200){
_that.globalData.accessToken = res.data.access_token;
_that.globalData.accessTokenExpireTime = nowTimeStamp;
//回調
cbFn && cbFn(res.data.access_token);
};
},function(err){
_that.showPop('獲取token失敗',1000,'fail');
console.log(err);
//回調
cbFn && cbFn('');
});
}
},
/*獲取access token*/
getAccessTokenFn(_sucFn,_errFn){
var _that = this;
_that.ajaxFn({
"url": 'https://developer.toutiao.com/api/apps/token?appid=xxxxxx&secret=xxxxxx&grant_type=client_credential',
"data": null,
"method":"GET",
"dataType":"json",
"sucFn":function(res){
_sucFn && _sucFn(res);
},
"errFn":function(err){
_errFn && _errFn(err);
}
},true);
},
/*檢測圖片安全性*/
checkImgSafeFn(_accessToken,_imgSrc,_sucFn,_errFn){
var _that = this;
_that.ajaxFn({
"url": _that.globalData.imgCheckURL,
"data": {
'app_id': 'xxxxxxxxxxxxxx',
'access_token': _accessToken,
'image': _imgSrc
},
"method":"POST",
"dataType":"json",
"sucFn":function(res){
_sucFn && _sucFn(res);
},
"errFn":function(err){
_errFn && _errFn(err);
}
},true);
},
/*圖片安全驗證-報錯相關*/
checkImgSafeErrFn(sensitiveArr){
let errObj = {
'porn':'圖片涉黃',
'cartoon_leader': '領導人漫畫',
'anniversary_flag': '特殊標志',
'sensitive_flag': '敏感旗幟',
'sensitive_text': '敏感文字',
'leader_recognition': '敏感人物',
'bloody': '圖片血腥',
'fandongtaibiao': '未準入臺標',
'plant_ppx': '圖片涉毒',
'high_risk_social_event': '社會事件',
'high_risk_boom': '爆炸',
'high_risk_money': '人民幣',
'high_risk_terrorist_uniform': '極端服飾',
'high_risk_sensitive_map': '敏感地圖',
'great_hall': '大會堂',
'cartoon_porn': '色情動漫',
'party_founding_memorial':'建黨紀念'
};
let errMes = [];
if(sensitiveArr.length>0){
sensitiveArr.forEach((item,idx)=>{
if(item.hit){
if(typeof errObj[item.model_name] != 'undefined'){
errMes.push(errObj[item.model_name]);
}
}
});
}
return errMes;
},
使用:
//檢測圖片是否包含違法違規(guī)內容
app.checkTokenExpireFn(function(_token){
if(typeof _token != 'undefined'){
//有token,可以驗證圖片
app.checkImgSafeFn(_token, 在線圖片src ,function(res){
//驗證圖片安全性
if(res.statusCode==200){
if(res.data.error==0){
//圖片審查調用成功
var errMes = app.checkImgSafeErrFn(res.data.predicts);
if(errMes.length>0){
//圖片存在違規(guī)信息
app.showPop('違規(guī):'+errMes.join('、'),1500,'fail');
}else{
//圖片健康=>通過
app.showPop("上傳成功",600);
}
}else{
//圖片審查調用失敗
app.showPop('圖片驗證接口錯誤',1500,'fail');
}
...
};
},function(err){
console.log(err);
})
}else{
//無token,無法驗證圖片
app.showPop('token獲取失敗',1500,'fail');
....
}
});
- pinker 市區(qū)縣
//--ttml
<picker
mode="multiSelector"
bindchange="bindMultiPickerChange"
bindcolumnchange="bindMultiPickerColumnChange"
value="{{multiIndex2}}"
range="{{multiArray2}}"
data-pinkergroupnum="2"
>
<view class="flexBox flexYC">
<view class="pickerBox">
<view class="picker">{{multiArray2[0][multiIndex2[0]]}}</view>
<view class="pickerBoxIco bgCover btmCorIco"></view>
</view>
<view class="pickerBox">
<view class="picker">{{multiArray2[1][multiIndex2[1]]}}</view>
<view class="pickerBoxIco bgCover btmCorIco"></view>
</view>
<view class="pickerBox">
<view class="picker">{{multiArray2[2][multiIndex2[2]]}}</view>
<view class="pickerBoxIco bgCover btmCorIco"></view>
</view>
</view>
</picker>
//-- 用于pinker展示的文案的數(shù)據(jù)multiArray 格式
//-- 省
[
"北京", "河北", ...
],
//-- 市
[
[ "北京" ],
[ "石家莊", "保定", ... ],
],
//-- 區(qū)
[
[
[ "東城區(qū)", "西城區(qū)",... ],
],
[
[ "長安區(qū)", "橋東區(qū)",... ],
[ "新市區(qū)", "涿州區(qū)",... ]
],
],
//--js (多個三級聯(lián)動pinker),通過pinker標簽dataset來判斷修改值
//pinker -- 城市pinker列表
let cityLists = []; // 用于存儲pinker的展示文案的結構
Page({
data: {
'multiData': [], //原始數(shù)據(jù)
'multiArray1': [ ], //用于pinker展示的文案
'multiIndex1': [0, 0], //用于切換pinker展示的文案索引
'multiArray2': [ ], //用于pinker展示的文案
'multiIndex2': [0, 0, 0], //用于切換pinker展示的文案索引
},
....
//地區(qū)pinker多選- 確定
bindMultiPickerChange(event) {
var _that = this;
if(typeof event.currentTarget.dataset.pinkergroupnum != 'undefined'){
var _pinkergroupnum = event.currentTarget.dataset.pinkergroupnum;
//獲取區(qū)域表索引
if(_pinkergroupnum == '1'){
_that.setData({
"multiIndex1": event.detail.value,
});
}else if(_pinkergroupnum == '2'){
_that.setData({
"multiIndex2": event.detail.value,
});
};
};
},
//地區(qū)pinker多選- 切換
bindMultiPickerColumnChange(event) {
if(typeof event.currentTarget.dataset.pinkergroupnum != 'undefined'){
var _pinkergroupnum = event.currentTarget.dataset.pinkergroupnum;
var _multiArray = [];
var _multiIndex = [];
//獲取區(qū)域表索引
if(_pinkergroupnum == '1'){
_multiArray = this.data.multiArray1;
_multiIndex = this.data.multiIndex1;
switch(event.detail.column){
case 0:
_multiIndex[0] = event.detail.value;
_multiIndex[1] = 0;
_multiArray[1] = cityLists[1][_multiIndex[0]];
break;
case 1:
_multiIndex[1] = event.detail.value;
break;
};
//設置修改
this.setData({
'multiArray1': _multiArray,
'multiIndex1': _multiIndex
});
}else if(_pinkergroupnum == '2'){
_multiArray = this.data.multiArray2;
_multiIndex = this.data.multiIndex2;
switch(event.detail.column){
case 0:
_multiIndex[0] = event.detail.value;
_multiIndex[1] = 0;
_multiArray[1] = cityLists[1][_multiIndex[0]];
_multiIndex[2] = 0;
_multiArray[2] = cityLists[2][_multiIndex[0]][_multiIndex[1]];
break;
case 1:
_multiIndex[1] = event.detail.value;
_multiIndex[2] = 0;
_multiArray[2] = cityLists[2][_multiIndex[0]][_multiIndex[1]];
break;
case 2:
_multiIndex[2] = event.detail.value;
break;
};
//設置修改
this.setData({
'multiArray2': _multiArray,
'multiIndex2': _multiIndex
});
};
};
},
64-2. 根據(jù)地理位置修改pinker的展示
tt.getLocation獲取經(jīng)緯度,然后調用后臺接口,返回省市區(qū)的ID,根據(jù)三個ID,從原始JSON獲取索引位置,然后修改pinker的索引位置
在這一步需要注意的是:
- 省市區(qū)不僅要修改索引,而且要修改pinker對應的展示列表(不是原始JSON)
//省市區(qū)對應id列表,索引位置
var _indexArr = [-1,-1,-1];
...
過濾原始數(shù)據(jù),找到省市區(qū)對應ID的索引,并依次賦值到_indexArr
...
//-- cityLists為原始JSON數(shù)據(jù)
if(_indexArr[0]!=-1){
//-- 省
_multiIndex2[0] = _indexArr[0];
_multiArray2[0] = cityLists[0];
if(_indexArr[1]!=-1){
//-- 市
_multiIndex2[1] = _indexArr[1];
_multiArray2[1] = cityLists[1][_indexArr[0]];
if(_indexArr[2]!=-1){
//-- 區(qū)
_multiIndex2[2] = _indexArr[2];
_multiArray2[2] = cityLists[2][_indexArr[0]][_indexArr[1]];
}
}
};
省市區(qū)數(shù)組格式 見上面 (用于pinker展示的文案的數(shù)據(jù)multiArray 格式)
- 獲取地理位置,授權失敗時候,需要注意給三級聯(lián)動pinker設置初始索引,否則授權失敗報錯
//--獲取用戶坐標
tt.getLocation({
success(res) {
_that.setData({
'userlongitude': res.longitude,
'userlatitude': res.latitude
});
},
fail(res) {
//顯示手動授權信息
_that.setData({
'multiIndex2': [0, 0, 0]
});
console.log(`getLocation調用失敗~`);
},
});
- 自定義導航
- 配置 spp.json - window
"window":{
"navigationStyle":"custom"
},
- 配置 app.js
onLaunch:function(){
//版本信息
let version = tt.getSystemInfoSync().SDKVersion;
let _that = this;
//獲取狀態(tài)欄高度
let _SystemInfo = tt.getSystemInfoSync();
if(_SystemInfo.statusBarHeight){
_that.globalData.sysBarHeight = _SystemInfo.statusBarHeight;
};
//獲取膠囊按鈕的空間信息
//基礎庫 1.25.0 開始支持本方法。
if(_that.compareVersion(version,'1.25.0')){
var _statusBarHeight = _that.globalData.sysBarHeight;
var ButtonBoundingRes = tt.getMenuButtonBoundingClientRect();
if(ButtonBoundingRes){
_that.globalData.ButtonBoundingHright = (Number(ButtonBoundingRes.top) - _statusBarHeight)*2 + Number(ButtonBoundingRes.height);
};
}else{
tt.showModal({
title: '提示',
content: '當前版本過低,無法獲取膠囊按鈕尺寸功能,請升級到最新版本后重試。'
});
};
},
globalData:{
'sysBarHeight':'',
'ButtonBoundingHright':'',
},
//對比版本號,返回布爾值,做兼容使用
compareVersion(v1, v2){
v1 = v1.split('.')
v2 = v2.split('.')
const len = Math.max(v1.length, v2.length)
while(v1.length < len){
v1.push('0')
}
while(v2.length < len){
v2.push('0')
}
for(let i = 0; i < len; i++){
const num1 = parseInt(v1[i])
const num2 = parseInt(v2[i])
if (num1 > num2){
return true;
}else if(num1 < num2){
return false;
}
}
},
- 引入 ttml 組件
<mypagehead
showLeftBtn="{{true}}"
headTit="發(fā)布爆料-編輯"
></mypagehead>
- 組件 ttml
<!-- 導航 -->
<view
class="topHead"
style="padding-top:{{sysBarHeightNum}}px"
>
<view
class="headInset flexBox flexXC flexYC"
style="height:{{ButtonBoundingHright}}px"
>
<!-- 后退按鈕 -->
<view
tt:if="{{showLeftBtn}}"
class="iconMenu"
>
<!-- 后退 -->
<view
bindtap="backFn"
class="iconTag bgCover iconBackWhite"
></view>
<!-- 返回主頁 -->
</view>
<!-- 標題 -->
<view class="tit">
<text>{{headTit}}</text>
</view>
</view>
</view>
- 組件 js
const app = getApp()
Component({
properties:{
showLeftBtn:{
type: Boolean,
value: false
},
headTit:{
type: String,
value: '缺省值標題'
}
},
data:{
// component internal data
'defaultStates': { },
'sysBarHeightNum': 0,
'ButtonBoundingHright': 0
},
methods:{
backFn(){
app.jumpBack(1);
}
},
lifetimes:{
//組件生命周期函數(shù),在組件實例進入頁面節(jié)點樹時執(zhí)行
attached(){
//獲取系統(tǒng)狀態(tài)欄高度
this.setData({
'sysBarHeightNum': app.globalData.sysBarHeight,
'ButtonBoundingHright': app.globalData.ButtonBoundingHright
});
}
}
});
- 關于視頻的上傳
調用文件上傳接口, 進度條100%后,后臺要立即返回數(shù)據(jù),否則會報錯 '掛起'
字節(jié)系不同平臺,不用手機 上傳的視頻格式是不一樣的 官方兼容表 -https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/component/media-component/video/
因此后續(xù)可能需要考慮 (不同手機上傳視頻的兼容問題,是否不用系統(tǒng)手機上傳的視頻,大家無法相互看到)
//--選擇視頻、報錯相關
tt.chooseVideo({
sourceType: ["album", "camera"],
compressed: true,
maxDuration: 180,
success: (res) => {
let { duration, width, height, size } = res;
_that.setData({
['upVideo.videoSrc']: res.tempFilePath,
['upVideo.description']: `視頻時長:${duration}s; 視頻大?。?{Math.floor(size/(1024*1024))}MB`,
['upVideo.videoDuration']: Math.ceil(duration)
});
//錯誤信息-大于180s
if(duration>180){
_that.setData({
['formValidation.videoErr']: '視頻時長超過3分鐘'
})
return false;
}
//單位字節(jié) B -> KB -> MB
if(size/(1024*1024)>180){
_that.setData({
['formValidation.videoErr']: '視頻時長超過180MB'
})
return false;
}
//沒有問題上傳視頻
_that.uploadVideoFn();
},
fail: (err) => {
app.showPop('選擇視頻失敗',600,'none');
_that.setData({
['formValidation.videoErr']: '上傳視頻不能為空'
});
console.log(err);
},
complete: (res) => {
console.log("完成選擇");
},
});
- 上傳視頻文件引起報錯,返回statusCode: 404
chooseVideo的配置參數(shù),如果使用壓縮compressed: true,
IOS系統(tǒng),可以被壓縮,但是設置為false, 則不會被壓縮,(與官方說的,無論設置什么都壓縮,不太一樣 iphone7 plus)
Android系統(tǒng),不管怎么設置都不會被壓縮
后臺IIS系統(tǒng),.net有個默認的配置限制最大28.6M,會造成 前端uploadFile直接報錯 statusCode 404,上傳進度為0
前端報錯,后端還不能捕獲到錯誤 官方提問的帖子 - https://forum.microapp.bytedance.com/mini-app/posts/608695821e04b37e5eaf9f2d
- scroll-view橫向布局時候,通過scroll-view包裹一個類名為navInset的view容器
在一般設備上沒有問題,但是在iphone 12 Pro Max上,會出現(xiàn)折行問題
- 問題的布局方式:
.navInset { display:inline-block; white-space:nowrap; }
.navInset .navItem { display:inline-block; padding:0 20rpx; margin-right:4rpx; white-space:nowrap;}
- 解決的布局方式:
.navInset { display:-webkit-flex; display:-moz-flex; display:-ms-flexbox; display:-webkit-box; display:-moz-box; display:flex; }
.navInset .navItem { display:inline-block; padding:0 20rpx; margin-right:4rpx; white-space:nowrap;}
小程序評級標準有官方認定,評級的頻率為
平臺會在存在提審版本后的 7 天內再次安排評級,達到對應級別即可解鎖各特殊能力的申請渠道關于審核被拒,獎勵金額相關
由于現(xiàn)金獎勵被拒絕,可以通過話費獎勵的方式,來解決這個審核問題,但是值得注意的是,單次話費獎勵金額不得超過50元
官方審核說明 -https://microapp.bytedance.com/docs/zh-CN/mini-app/operation/Industrynorms/motivate/
......占位,填坑........

