字節(jié)跳動小程序 【開發(fā)】 自學總結

準備
  • 微信小程序開發(fā)文檔 官網(wǎng) - https://developers.weixin.qq.com/miniprogram/dev/framework/
    由于字節(jié)跳動小程序很類似微信小程序,但是文檔的詳細程度卻差一些,所以需要微信小程序開發(fā)文檔做對照
開發(fā)階段

目錄結構同微信小程序類似,app.json 為全局設置。

  1. 認證注意事項
    企業(yè)開發(fā)者:適用于企業(yè),個體工商戶,政府組織,海外機構等其他機構。每個企業(yè)主體可以驗證【 10 個企業(yè)小程序 】。
    特別注意:認證主體后續(xù)需與支付主體、廣告結算主體保持一致

此外,如果在廣告中心開通了激勵廣告需要注意

1. 目前激勵視頻廣告僅支持抖音端,接入需判斷接入宿主及宿主版本, 在不支持的宿主及版本上需要將結果直接展示給用戶。
2. 展示廣告前向用戶說明激勵廣告規(guī)則,明確告知用戶看完視頻廣告后能獲得相應獎勵。
3. 一個頁面最多出現(xiàn)一個激勵視頻廣告。
4. 需要用戶主動操作點擊按鈕,才能創(chuàng)建和獲取激勵視頻廣告。
5. 需判斷廣告異常情況,如不支持激勵視頻的低版本用戶、廣告調用失敗等情況,應直接給予獎勵。
激勵廣告規(guī)范

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. 關于上下架
手動下架的小程序(小游戲),需要再次審核,審核通過后,方可再次上架
暫時沒有微信的 臨時下架功能,如 下架進行維護,再自己操作上架,而無需審核

全局設置
  1. 如增加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"
      }
    ]
  }
  1. 頁面跳轉分為標簽跳轉和程序跳轉
    標簽跳轉
<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調用失敗`);
  },
});
  1. 小程序對Javascript語法的 支持程度 - https://microapp.bytedance.com/dev/cn/mini-app/develop/framework/mini-app-runtime/javascript-support

  2. 模塊化,提取公共腳本
    導出

// common.js
function hello(name) {
  console.log(`Hello ${name} !`);
}
module.exports.hello = hello;

導入

var common = require("common.js");
Page({
  helloWorld: function() {
    common.hello("world");
  }
})
  1. 標簽有限,基本這三個就夠用了
    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>
  1. 布局單位與實際操作
    單位 rpx 據(jù)說可以根據(jù)屏幕比例變化
    | 設備 | rpx 換算 px (屏幕寬度/750) | px 換算 rpx (750/屏幕寬度) |
    | iPhone6 | 1rpx = 0.5px | 1px = 2rpx |
    建議:
    設計師可以用 iPhone6 作為視覺稿的標準。
    即使用PSD尺寸為750px的設計稿,然后1:1測量,單位使用rpx即可

  2. 圖片標簽新裁切屬性屬性,同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 即可

  1. 渲染轉譯符 增加屬性decode
    <view><text decode>?</text></view>

  2. 調試工具緩存有時候很大,通過開發(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ù),只有最后一個會生效????。。?!

  1. 返回頂部 按鈕怎么操作頁面
  • 針對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調用失敗`);
  }
});
  1. 網(wǎng)絡請求需要配置白名單 位置 - https://developer.toutiao.com/dev/cn/mini-app/develop/api/other/network-request-reference
    特別注意:線上環(huán)境,網(wǎng)絡請求僅支持 https 協(xié)議的 URL

  2. 小程序點擊,不支持事件處理函數(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ā)
  1. 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)一致了


修正cy值
  1. 小程序支持的canvas屬性 字節(jié)跳動小程序官網(wǎng) - https://microapp.bytedance.com/dev/cn/mini-app/develop/api/interface/canvas-draw/tt.createcanvascontext

  2. 小程序圖片預加載
    循環(huán)數(shù)組數(shù)據(jù)創(chuàng)建圖片,通過圖片的bindloadbinderror的變化,來判斷加載進度與是否加載完畢,
    暫時沒有發(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);
}
  1. 音頻
  • 分為背景音頻
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();
  1. 動畫 animate.css 需要修改下后綴名 如 .ttss
  <!--觸發(fā)動畫-->
  <view class="{{scene==2? 'bounce animated':' '}}"></view>
  1. 獲取 基礎庫版本 (' 論壇提交問題需要基礎庫版本號 ');
    所有參數(shù) - https://developer.toutiao.com/dev/cn/mini-app/develop/api/device/system-information/tt.getsysteminfo
//獲取基礎庫版本
tt.getSystemInfo({
      success(res) {
        console.log(res.SDKVersion);
      }
});
  1. 全局數(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/

參數(shù)的種類

19-2. 當頁面通過二維碼加載出錯時候,需要重定向

onPageNotFound(res) {
    //如果不是 tabbar 頁面
    tt.redirectTo({
      url: "pages/index/index",
    }); 
    //如果是 tabbar 頁面,請使用 tt.switchTab
    tt.switchTab({
      url: "pages/index/index",
    }); 
},
  1. 獲取自定義屬性
    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組件

  1. 導入ttml模板
<include src="../../resources/ttml/header.ttml" />
  1. 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('播放');
}
  1. 小程序輪播圖指示點顏色 ( 默認: 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>
  1. 修改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自身高度
  1. 背景圖 -- ( 圖片精靈可以放在這里 )
    樣式中使用本地背景圖,可以將背景圖文件與app.json同級,然后通過
background:url('resource/image/bell.png') no-repeat left top;

或者使用 base64、或者網(wǎng)絡圖片

  1. 下載遠程圖片至本機相冊
//--- 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é)議,不能加端口號,否則失效

白名單不要加端口號

  1. 組件 switch
    修改背景色 使用 屬性 color設置
    修改大小,使用transform:scale(xxx) 調整

  2. 定位為fixed的元素不要放在scroll-view中,真機不會生效(不像模擬器)

  3. 表單輸入框、文本域限制字數(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
  1. 真機與模擬器在include上渲染不一
    模擬器可以使用<include src="../../resource/template/xxx.ttml" />,引入模板文件;
    但是在真機上無法完成渲染,
    解決方法,將內容從include中拿出來

  2. 純文本中存在\r\n,需要渲染出換行效果,
    pre-wrap : 保留空白符序列,但是正常地進行換行。

<view style="white-space:pre-wrap">
      {{item.scoreTabData.description}}
</view>
  1. tt:for操作報錯Polymer::Attributes: couldn't decode Array as JSON:
    解決方法,
    修改循環(huán)單位名字 如: tt:for-item="seasonItem"

  2. 多重三目運算

matchInsetItem.status=='Uncertain'?'待定':(matchInsetItem.status=='Postponed'?'推遲':(matchInsetItem.status=='Played'?'結束':''))
  1. 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ā)事件

  2. 【優(yōu)化】
    tt:if 顯示隱藏 -- 渲染成本高 [ 初始化渲染使用,不頻繁顯示消失 ]
    通過樣式控制顯示隱藏 -- 渲染成本低 [ 頻繁切換顯示、消失使用 ]

  3. 隱藏loading,彈出toast,存在沖突
    loading 的實現(xiàn)基于 toast,所以hideLoading也會將 toast 隱藏。
    解決:

app.showPop('登錄成功',400);
setTimeout(()=>{
    app.hideLoadPop();
},800);
  1. 授權被拒,無法拉起再次授權
  • 常規(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 是否授權攝像頭
  1. 上傳圖片
    (第一步).需要拉起本地相冊,【需要用戶授權】
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只支持單張上傳

  1. 輸入框修改內容如何優(yōu)化
    通過bindinput修改數(shù)據(jù),但是不要使用setData,降低設置數(shù)據(jù)性能消耗,將其值存在變量中
    在bindblur時候,進行setData,將變量賦值到對應數(shù)據(jù)上去;
    此外bindblur時候setData需要時間,不是立即完成的,所以需要開啟延遲定時器,再去setData

  2. 版本更新
    要點: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:
      "當前客戶端版本過低,無法使用該功能,請升級客戶端或關閉后重啟更新。"
  });
}
  1. 生命周期
  • 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
  1. 跳轉返回上一步
    tt.redirectTo不能跳轉到 TabBar 頁面
    但是 redirectTo方法生成的頁面,可以返回到 TabBar 頁面
backBtnFn(){
    tt.navigateBack({
      delta: 1,
      success(res) {
        // console.log(`${res}`);
      },
      fail(res) {
        console.log(`navigateBack調用失敗`);
      }
    });
}
  1. 下拉刷新、上拉加載
    (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. 小程序分享
    (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ù)

小程序配置自定義參數(shù)

44-3. 分享內容定義的優(yōu)先級
今日頭條/頭條極速版:

| 場景 | 優(yōu)先級 |
| 端內分享 | 代碼指定 > 模板指定 > 平臺默認 |
| 端外分享 | 模板指定 > 平臺默認 |

在腳本中還是設置標題以及描述為空,這樣在開發(fā)者后臺通過切換激活的分享模板,可以修改模板內容
但是修改新的分享模板,仍需要在代碼中配置,怎么都會影響修改分享的效率

  1. 關于登錄授權流程
    先使用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
  1. 企業(yè)級賬號中 webview配置
    首先,開發(fā)設置 - webview域名 - 下載檢驗文件
    其次,將授權文件放置在需要配置域名的根路徑下,確??梢酝ㄟ^域名+檢驗文件,可以訪問文件內容即可

  2. 同時一次同時設置多個數(shù)據(jù),如果涉及緩動動畫與表單焦點聚焦,可能會引起渲染問題,
    解決方式,先設置有動畫的數(shù)據(jù),再添加一個演示定時器,設置獲取焦點的數(shù)據(jù)

  3. 浮層上有表單,如果設置點擊空白區(qū)域使浮層消失,那么點擊表單輸入(bindinput),會引起冒泡行為,造成浮層關閉。
    解決方法:
    第一步,通過設置表單的焦點數(shù)據(jù)(focus),以及丟失焦點事件(bindfocus),在點擊浮層之外的空白區(qū)域時候,判斷輸入框是否存在焦點狀態(tài)(focus數(shù)據(jù)情況),如果存在就不要做浮層消失,否則就叫浮層消失
    第二步,表單輸入框失去焦點事件中(bindfocus),將表單的焦點屬性數(shù)據(jù)(focus)重置,使浮層可以被正常關閉
    解決方法2:百度一下,未解決
    給表單元素添加catchtap事件,事件處理函數(shù)為空,但是效果是,表單無法輸入文字了,????

  4. scroll-view 如果希望其內部橫向無限滾動,只需要設置兩個樣式
    其一,scroll-view容器設置 width:100%;
    其二,scroll-view內部包裹一個滾動元素,為其設置 display:inline-block; white-space:nowrap;

  5. 頁面滾動,導航的吸頂效果
    準備:
    第一,判斷方法存在與否 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ā)事件的間隔頻率

  1. 組件配置
    第一,先配置【子組件】信息,新建對應的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

  1. 作用域 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";
  1. 兩個非TAB頁面之間跳轉,數(shù)據(jù)狀態(tài)不同步
    如:
    詳細頁通過路由(tt.navigateBack)返回列表頁,在列表頁觸發(fā)了onShow鉤子函數(shù),但是數(shù)據(jù)不會重新加載
    這樣會引起一個問題:
    如果詳細頁點擊取消關注,返回列表頁,不會重新渲染,因為數(shù)據(jù)有緩存,列表頁不會重新渲染
    解決方法:
    將關注列表內容放到localStorge內,如果有操作關注的東西,及時改變localStorage,來作為變化參照憑證

  2. 二級聯(lián)動pinker組件,TAB頁面之間跳轉,會出現(xiàn)問題
    表現(xiàn): 是聯(lián)動數(shù)據(jù)一級內容,為之前某二級列表內容,而非標題
    解決: 頁面,onshow生命周期 重置聯(lián)動pinker初始索引

  3. 轉發(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ù)

  4. 支付接入
    先獲取訂單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,可以查詢支付寶支付,能否拉起。

  1. 浮層上如果有一個textarea, 如果通過修改class類名方式,控制浮層的顯示與消失,那么textarea會不顯示文字,且無法獲得焦點,拉起手機鍵盤。
    解決方法,通過tt:if 來控制顯示層的顯示與隱藏,那么textarea就可以操作了

  2. 在小程序中,使用第三方客服拆件,用于聊天功能
    方法一,其核心是調用小程序官方組件<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

  1. 微信小程序遷移字節(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
  1. 小程序 時間對象 開發(fā)工具正常,但是iphone真機報錯
    new Date("2019-07-24 19:57") 應該寫成 new Date("2019/07/24 19:57")
    需要對時間字符串處理下,如 new Date(tm.replace(/-/g,'/'))

  2. 圖片預覽的輪播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ā)布器調起失敗');
      }
    }
  }
  1. 圖片安全性驗證
    上傳圖片時候,需要在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');
    ....
  }
});
  1. 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調用失敗~`);
  },
});
  1. 自定義導航
  • 配置 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
      });
    }
  }
});
  1. 關于視頻的上傳
    調用文件上傳接口, 進度條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("完成選擇");
  },
});
  1. 上傳視頻文件引起報錯,返回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

  1. 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;}  
  1. 小程序評級標準有官方認定,評級的頻率為
    平臺會在存在提審版本后的 7 天內再次安排評級,達到對應級別即可解鎖各特殊能力的申請渠道

  2. 關于審核被拒,獎勵金額相關
    由于現(xiàn)金獎勵被拒絕,可以通過話費獎勵的方式,來解決這個審核問題,但是值得注意的是,單次話費獎勵金額不得超過50元
    官方審核說明 - https://microapp.bytedance.com/docs/zh-CN/mini-app/operation/Industrynorms/motivate/

......占位,填坑........

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容