W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
解釋: 本模版適用于快速搭建版權(quán)長視頻小程序內(nèi)與長視頻相關(guān)聯(lián)的短視頻詳情頁,例如:搭建長視頻的片花、預(yù)告、切片短視頻的詳情頁。模版在保證用戶交互體驗(yàn)的基礎(chǔ)上,提供了視頻播放器、長視頻信息區(qū)、活動(dòng)運(yùn)營位、百青藤廣告位、推薦短視頻區(qū)等模塊,旨在提升短視頻落地頁用戶體驗(yàn)。同時(shí),開發(fā)者可配合使用加入書架、預(yù)約和引導(dǎo)關(guān)注組件,實(shí)現(xiàn)興趣激發(fā),充分發(fā)揮短視頻帶長視頻消費(fèi)的種草價(jià)值,拉動(dòng)二次重訪。
npm i @smt-ui-template/page-video
.
├── project.swan.json
├── app.json
├── app.js
├── pages
└── @smt-ui-template-page-video // 模板文件
{
"pages": [
...
"pages/@smt-ui-template-page-video/pages/index/index",
"pages/@smt-ui-template-page-video/pages/comment-detail/index
]
}
模板包含 2 個(gè)頁面,分別是:短視頻詳情頁、評(píng)論詳情頁。
頁面路徑:pages/index
短視頻詳情頁包含導(dǎo)航欄、引導(dǎo)關(guān)注組件、視頻播放器(含自動(dòng)連播邏輯)、短視頻標(biāo)題及輔助信息區(qū)、長視頻信息區(qū)、活動(dòng)運(yùn)營位、百青藤廣告區(qū)、推薦短視頻區(qū)和一站式互動(dòng)區(qū)等模塊,分為默認(rèn)配置模塊和可選配置模塊。開發(fā)者可根據(jù)自身業(yè)務(wù)形態(tài)選擇合適的模塊進(jìn)行自定義配置。
Tips:默認(rèn)配置為導(dǎo)航欄、視頻播放器、短視頻標(biāo)題及輔助信息區(qū)、長視頻信息區(qū)一站式互動(dòng)區(qū)等模塊。
Page({
...
attr: {
// 請求的 url,請?zhí)鎿Q為真實(shí)的請求地址,該值僅做為示例,值為 defaultData 為默認(rèn)配置示例,其他值為全配置示例
url: '/index',
// onLoad參數(shù)
options: {}
},
onLoad(options) {
this.getPageData();
...
},
getPageData() {
const url = this.attr.url;
// 模擬請求,請進(jìn)行替換
getIndexData({
url
}).then(
res => {
res.playVideoList.forEach(item => {
// 格式化播放數(shù)量
item.playNum = this.formatPlayNum(item.playNum);
item.nextInfo = false;
// 過濾出長視頻
if (item.type === 1) {
this.attr.feedList.push(item);
}
});
let {
showFeed,
feedMore,
feedShowList,
toolbarConfig
} = this.data;
let feedList = this.attr.feedList;
if (!feedList.length) {
showFeed = false;
}
// feed 少于5條不展示查看更多
if (feedList.length <= 5) {
feedMore = false;
feedShowList = feedList;
} else {
// 多于5條時(shí),先展示前5條
feedShowList = feedList.slice(0, 5);
}
toolbarConfig.title = res.longVideoInfo.name;
res.longVideoInfo = this.formatVideoInfo(res.longVideoInfo);
this.setData({
playVideoList: res.playVideoList,
feedShowList: feedShowList,
longVideoInfo: res.longVideoInfo,
operateInfo: res.operateInfo,
feedMore: feedMore,
toolbarConfig: toolbarConfig,
showFeed: showFeed,
commentParam: {
// 文章的唯一標(biāo)識(shí)
snid: this.attr.options.snid,
path: `/@smt-ui-template-page-video/pages/index/index?snid=${this.attr.options.snid}`,
title: res.longVideoInfo.name
},
loading: false,
// code 0: 正常獲取數(shù)據(jù) 99999: 無網(wǎng)絡(luò) 其他: 服務(wù)異常
statusType: res.code === 99999 ? 'noNetwork' : res.code !== 0 ? 'warning' : ''
});
}
);
}
})
Page({
...
attr: {
// 頁面展示時(shí)是否顯示關(guān)注引導(dǎo)tip
showFavorite: true
},
onShow() {
if (this.attr.showFavorite) {
// 頁面展示時(shí)顯示關(guān)注引導(dǎo)
this.showFavoriteGuide();
}
}
})
Tips:
1、當(dāng)前用戶退出小程序后,即刪除已觀看記錄,下次進(jìn)入小程序短視頻落地頁,重新執(zhí)行去重邏輯。
2、蒙層展示下一條播放的視頻信息并提供重播、觀看正片、立即播放等功能。
<!-- 視頻組件-->
<video
id="myVideo"
class="video-header-player"
src="{{playVideoList[playIndex] && playVideoList[playIndex].src}}"
title="{{playVideoList[playIndex].title}}"
autoplay="true"
objectFit="?ll"
direction="true"
muted="true"
show-mute-btn="true"
show-center-play-btn="false"
bindtimeupdate="videoTimeUpdateHandler"
bindplay="videoPlayHandler"
bindended="videoEndedHandler"
>
</video>
<!-- 蒙層 -->
<view class="video-header-cover" s-if="{{!isPlaying && nextIndex != playIndex}}">
<view class="video-header-cover-title" s-if="{{!timer}}">接下來播放</view>
<view class="video-header-cover-title" s-else><view class="video-header-cover-title-remaining">{{remainingTime}}s</view>后播放</view>
<view class="video-header-cover-content">
<image class="video-header-cover-content-left" src="{{playVideoList[nextIndex].poster}}" mode="scaleToFill"></image>
<view class="video-header-cover-content-right">
<view class="video-header-cover-content-right-text c-line-clamp2">{{playVideoList[nextIndex].title}}</view>
<view class="video-header-cover-content-right-num">{{playVideoList[nextIndex].playNum}}次播放</view>
<view class="video-header-cover-content-right-button" bindtap="playVideo">立即播放</view>
</view>
</view>
<view class="video-header-cover-buttons">
<view class="video-header-cover-buttons-item" bindtap="replayVideo">
<image class="video-header-cover-buttons-item-img" src="../../common/images/replay.png" mode="aspectFit"></image>
重播
</view>
<view class="video-header-cover-buttons-item" bindtap="navigateTo" data-path="{{longVideoInfo.path}}">
<image class="video-header-cover-buttons-item-img" src="../../common/images/play.png" mode="aspectFit"></image>
觀看正片
</view>
</view>
</view>
<view class="video-header-cover replay" s-if="{{!isPlaying && nextIndex == playIndex}}" bindtap="replayVideo">
<view class="video-header-cover-replay">
<image class="video-header-cover-replay-icon" src="../../common/images/replay.png" mode="scaleToFill"></image>
</view>
重播
<
<!-- 下一條視頻提示 -->
<view class="video-header-tips c-line-clamp1" s-if="{{isMonitoring && isPlaying}}">即將播放:{{playVideoList[nextIndex].title}}</view>
Page({
...
/**
* 播放下一條視頻
*/
playVideo() {
this.setData({
isMonitoring: false,
isPlaying: true,
playIndex: this.data.nextIndex
});
},
/**
* 重播
*/
replayVideo() {
this.attr.videoContext.play();
this.setData({
isMonitoring: false,
isPlaying: true
});
},
/**
* 監(jiān)聽播放開始事件
*/
videoPlayHandler() {
const {
timer,
playVideoList,
playIndex
} = this.data;
// 開始播放清除倒計(jì)時(shí)器
if (timer) {
clearInterval(timer);
this.setData({
timer: null
});
}
if (!playVideoList[playIndex].nextInfo) {
playVideoList[playIndex].nextInfo = true;
// 根據(jù)已播列表獲取下一條視頻 index
for (let i = playIndex + 1; i < playVideoList.length; i++) {
if (this.attr.playedList.indexOf(playVideoList[i].id) === -1) {
this.setData({
nextIndex: i
});
break;
}
}
}
this.setData({
isMonitoring: false
});
},
/**
* 監(jiān)聽播放結(jié)束事件
*/
videoEndedHandler() {
// 短視頻去重
if (this.data.playVideoList[this.data.playIndex].type === 1) {
this.attr.playedList.push(this.data.playVideoList[this.data.playIndex].id);
}
// 沒有可播放的視頻
if (this.data.nextIndex === this.data.playIndex) {
// 存在長視頻落地頁時(shí)跳轉(zhuǎn)長視頻落地頁
if (this.data.longVideoInfo.path) {
swan.navigateTo({
url: this.data.longVideoInfo.path
});
} else {
// 顯示重播按鈕
this.setData({
playIndex: this.data.nextIndex,
isPlaying: false
});
this.attr.videoContext.stop();
return;
}
}
// 播下一條視頻時(shí)頁面不在頂部
if (this.attr.scrollTop !== 0) {
// 取消自動(dòng)連播
this.attr.videoContext.stop();
this.setData({
isPlaying: false
});
// 開啟計(jì)時(shí)器
this.onTimer();
} else {
// 不被打斷則直接播放下一條視頻
this.setData({
playIndex: this.data.nextIndex
});
}
},
/**
* 監(jiān)聽播放進(jìn)度變化
* @param {*} e 事件對(duì)象
*/
videoTimeUpdateHandler(e) {
if (this.data.nextIndex === this.data.playIndex) {
return;
}
const {
duration,
currentTime
} = e.detail;
// 剩余5s 時(shí)進(jìn)行自動(dòng)播放提示
if (duration !== 0 && currentTime !== 0 && duration - currentTime <= 5) {
this.setData({
isMonitoring: true
});
}
}
})
<!-- 短視頻標(biāo)題及輔助信息區(qū) -->
<view class="video-content-introduction">
<view class="video-content-introduction-title">
{{playVideoList[playIndex].title}}
<image
s-if="{{playVideoList[playIndex].time || playVideoList[playIndex].introduction}}"
class="video-content-introduction-title-switch {{introSwitch ? 'off': '' }}"
mode="scaleToFill"
src="../../common/images/arrow.png" bindtap="introductionSwitch">
</image>
</view>
<view class="video-content-introduction-other">
<view class="video-content-introduction-other-num">{{playVideoList[playIndex].playNum}}次播放</view>
|
<image class="video-content-introduction-other-img video-img" mode="scaleToFill"
s-if="{{playVideoList[playIndex].authorImage}}" src="{{playVideoList[nextIndex].autorImage}}"></image>
<view class="video-content-introduction-other-name c-line-clamp1">{{playVideoList[playIndex].authorName}}</view>
</view>
<view class="video-content-introduction-more"
s-if="{{introSwitch && (playVideoList[playIndex].time || playVideoList[playIndex].introduction)}}">
<view class="video-content-introduction-more-time" s-if="{{playVideoList[playIndex].time}}">
發(fā)布時(shí)間:{{playVideoList[playIndex].time}}
</view>
<view class="video-content-introduction-more-text c-line-clamp3"
s-if="{{playVideoList[playIndex].introduction}}">{{playVideoList[playIndex].introduction}}
</view>
</view>
</view>
Page({
...
/**
* 展開、收起簡介信息
*/
introductionSwitch() {
this.setData({
introSwitch: !this.data.introSwitch
});
}
})
<!-- 長視頻信息區(qū) -->
<view class="video-content-detail">
<view class="video-content-detail-left" bindtap="navigateTo" data-path="{{longVideoInfo.path}}">
<image class="video-content-detail-left-img video-img" src="{{longVideoInfo.poster}}" mode="scaleToFill">
</image>
<view class="video-content-detail-left-play">
</view>
<image class="video-content-detail-left-play-icon" src="../../common/images/play3.png" mode="scaleToFill">
</image>
</view>
<view class="video-content-detail-right">
<view class="video-content-detail-right-name">
{{longVideoInfo.name}}
</view>
<view class="video-content-detail-right-tags c-line-clamp2">
{{longVideoInfo.info}}
</view>
<view class="video-content-detail-right-buttons">
<!-- 立即觀看按鈕 -->
<view class="video-content-detail-right-buttons-primary" s-if="{{longVideoInfo.path}}" bindtap="navigateTo"
data-path="{{longVideoInfo.path}}">觀看正片</view>
<!-- 加入書架按鈕 -->
<view class="video-content-detail-right-buttons-normal" bindtap="insertBookshelf"
s-if="{{longVideoInfo.path || longVideoInfo.bookInfo}}">加入書架</view>
<!-- 預(yù)約觀看按鈕 -->
<form s-if="{{!longVideoInfo.path || longVideoInfo.subscribeId}}" report-submit="true"
report-type="subscribe" template-id="BD2305" subscribe-id="1235" bindsubmit="formSubmit">
<button class="video-content-detail-right-buttons-normal" formType="submit" type="primary">預(yù)約觀看</button>
</form>
</view>
</view>
</view>
<!-- 運(yùn)營位 -->
<view class="video-content-operational video-img" s-if="{{operateInfo.path}}" style="background-image: url({{operateInfo.img}});" bindtap="navigateTo" data-path="{{operateInfo.path}}">
</view>
<!-- 廣告位 若使用可取消注釋-->
<!-- <view class="video-ad">
<view class="video-ad-container">
<ad appid="f71feede"
apid="7182325"
type="feed"
>
</ad>
</view>
<view class="video-divider wrapper" s-if="{{!showFeed}}"></view>
</view> -->
<!-- 短視頻列表 -->
<view s-for="item in feedShowList" class="video-feed-item" bindtap="navigateTo" data-path="{{item.path}}">
<view class="video-feed-item-left">
<view class="video-feed-item-left-title c-line-clamp2">
{{item.title}}
</view>
<view class="video-feed-item-left-num">
{{item.playNum}}次播放
</view>
</view>
<view class="video-feed-item-right">
<image class="video-feed-item-right-img video-img" src="{{item.poster}}" mode="scaleToFill"></image>
<view class="video-feed-item-right-time">
<image class="video-feed-item-right-time-icon" src="../../common/images/play2.png" mode="scaleToFill">
</image>
{{item.duration}}
</view>
</view>
</view>
<view class="video-feed-more" bindtap="feedMoerHandler">
<view>
{{feedMore ? '查看更多': '沒有更多了'}}
<image class="video-feed-more-icon" s-if="{{feedMore}}" src="../../common/images/arrow.png" mode="scaleToFill"></image>
</view>
</view>
</view>
Page({
...
/**
* 查看更多短視頻
*/
feedMoerHandler() {
let {
feedMore,
feedShowList
} = this.data;
const start = feedShowList.length;
let end = start + 10;
// 結(jié)束位超出,展示剩余的視頻
if (end > this.attr.feedList.length) {
end = start + this.attr.feedList.length - feedShowList.length;
feedMore = false;
}
feedShowList.push(...this.attr.feedList.slice(start, end));
this.setData({
feedShowList: feedShowList,
feedMore: feedMore
});
}
})
Tips:
1、使用點(diǎn)贊、評(píng)論功能時(shí)需要進(jìn)行登入。
2、從其他頁面跳轉(zhuǎn)到本模板時(shí),snid 文章 id 需要在加在跳轉(zhuǎn)到本頁面的路徑上,跳轉(zhuǎn)本頁面的路徑如:@smt-ui-template-page-video/pages/index/index?snid=10070000311753961
<!-- 評(píng)論列表組件 -->
<comment-list
class="video-comment"
is-page-scroll="false"
comment-param="{{commentParam}}"
detail-path="{{detailPath}}"
toolbar-config="{{toolbarConfig}}"
bindunlogin="triggerLogin">
</comment-list>
頁面路徑:pages/comment-detail
展示評(píng)論詳情。
<comment-detail
comment-param="{{commentParam}}"
srid="{{srid}}"
bindunlogin="triggerLogin"
></comment-detail>
{
"navigationBarTitleText": "評(píng)論詳情",
"usingSwanComponents": {
"comment-detail": "dynamicLib://myDynamicLib/comment-detail"
}
}
import {
login
} from '../../utils';
Page({
data: {
srid: '',
commentParam: {}
},
onLoad(options) {
if (options.srid) {
this.setData({
srid: options.srid
});
}
const param = getApp().globalData.commentParam;
if (param && Object.keys(param).length) {
this.setData({
'commentParam': param
});
} else {
this.setData({
commentParam: {
snid: '10070000311753961',
path: '/pages/comment/index?snid=10070000311753961',
title: '測試文章標(biāo)題'
}
});
const isLoginResult = swan.isLoginSync();
// 當(dāng)前用戶已登錄的條件下,利用登錄 code 獲取 openid
// 當(dāng)前用戶未登錄的條件下,不強(qiáng)制其登錄
if (isLoginResult && isLoginResult.isLogin) {
this.triggerLogin();
}
}
},
triggerLogin(e) {
login().then(res => {
const commentParam = this.data.commentParam;
this.setData({
commentParam: {
...commentParam,
openid: res.data.openid
}
}, () => {
getApp().globalData.commentParam = this.data.commentParam;
});
});
}
});
對(duì)模板使用到的字段進(jìn)行說明,此部分的數(shù)據(jù)在使用模板時(shí)需從 server 獲取。模板作為示例進(jìn)行了 mock,開發(fā)者可參考數(shù)據(jù)格式進(jìn)行開發(fā)。
{
code: 0, // 接口信息
longVideoInfo: { // 長視頻信息
poster: '../../common/images/poster1.png', // 長視頻封面圖
name: '延禧攻略', // 長視頻名稱
path: '/longVideo', // 長視頻詳情頁路徑,已完結(jié)、更新中的資源必須填寫
type: '電視劇', // 資源類型,如:電視劇、電影、綜藝、動(dòng)漫等
year: '2018年', // 發(fā)行時(shí)間
area: '內(nèi)地', // 發(fā)行地區(qū)
tags: '劇情愛情古裝', // 資源標(biāo)簽,建議1-3個(gè)
update: '共52集', // 更新狀態(tài),如共X集、更新?X集、即將上映、已上映、更新至X期
time: '單集片長45分鐘' // 單集片長,電視劇/綜藝等可直接填寫單集??X分鐘,電影可填寫??X分鐘
},
playVideoList: [{ // 連播列表
id: 1, // 視頻 id
src: 'https://b.bdstatic.com/miniapp/development_tool/Smartprogram.mp4', // 視頻地址
poster: '../../common/images/poster2.png', // 視頻封面
title: '聶遠(yuǎn)年輕照片跟現(xiàn)在幾乎是判若兩人終于明白為什么現(xiàn)在才火了聶遠(yuǎn)年輕照片跟現(xiàn)在幾乎是判若兩人終于明白為什么現(xiàn)在才火了聶遠(yuǎn)年輕照片跟現(xiàn)在幾乎是判若兩人終于明白為什么現(xiàn)在才火了', // 視頻標(biāo)題
playNum: 6334000, // 視頻播放次數(shù)
authorName: '我是作者1', // 視頻作者
authorImage: '../../common/images/author.png', // 視頻作者頭像
duration: '03:20', // 視頻時(shí)長
time: '2020年3月10日', // 視頻上傳時(shí)間
introduction: '這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這這是簡介內(nèi)容這', // 視頻簡介
type: 1, // 視頻類型,短視頻為 1、長視頻為2
path: '/dasda'
}, {
id: 2,
src: 'https://b.bdstatic.com/miniapp/development_tool/2020-6/1591258607615/a332aa39e1ff.mp4',
poster: '../../common/images/poster3.png',
title: '賀涵強(qiáng)勢向子君表白,子君無所君無所適從',
playNum: 110011000001,
authorName: '2我是作者as大大說大廈的撒旦的撒',
authorImage: '../../common/images/author2.png',
duration: '103:20',
time: '2020年3月10日',
introduction: '這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這這是簡介內(nèi)容這',
type: 1,
path: '/dasda'
}, {
id: 3,
src: 'https://b.bdstatic.com/swan-temp/940fe716b0eaad38f47b209d61657490.mp4',
poster: '../../common/images/poster2.png',
title: '《前半生》大結(jié)局,賀涵離職和子君遠(yuǎn)走,唐晶不原諒',
playNum: 1000000,
authorName: '我是作者',
authorImage: '../../common/images/author3.png',
duration: '03:20',
time: '2020年3月10日',
introduction: '這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這這是簡介內(nèi)容這',
type: 1,
path: '/dasda'
},
{
id: 4,
src: 'https://b.bdstatic.com/miniapp/development_tool/Smartprogram.mp4',
poster: '../../common/images/poster3.png',
title: '4聶遠(yuǎn)年輕照片跟現(xiàn)在',
playNum: 6334000,
authorName: '我是作者',
authorImage: 'https://b.bdstatic.com/miniapp/images/demo-dog.png',
duration: '03:20',
time: '2020年3月10日',
introduction: '這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這這是簡介內(nèi)容這',
type: 2,
path: '/dasda'
},
{
id: 5,
src: 'https://b.bdstatic.com/miniapp/development_tool/2020-6/1591258607615/a332aa39e1ff.mp4',
poster: '../../common/images/poster2.png',
title: '5聶遠(yuǎn)年輕照片跟現(xiàn)在幾乎是判若兩人終于明白為什么現(xiàn)在才火了',
playNum: 1010000,
authorName: '我是作者',
authorImage: '../../common/images/author3.png',
duration: '03:20',
time: '2020年3月10日',
introduction: '這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這是簡介內(nèi)容這這是簡介內(nèi)容這',
type: 1,
path: '/dasda'
}
],
operateInfo: { // 運(yùn)營位信息
img: '../../common/images/operational.png', // 運(yùn)營位封面
path: '/dsadassdsa' // 運(yùn)營位落地頁地址
}
}
字段名 | 類型 | 說明 |
---|---|---|
code | Number | 接口信息,code 0 : 正常獲取數(shù)據(jù);99999 : 無網(wǎng)絡(luò);其他: 服務(wù)異常 |
longVideoInfo | Object | 長視頻信息,對(duì)應(yīng)模板長視頻信息區(qū)部分 |
playVideoList | Array | 連播列表,對(duì)應(yīng)視頻播放區(qū)、短視頻標(biāo)題及輔助信息區(qū)和短視頻列表區(qū)部分 |
operateInfo | Object | 運(yùn)營位信息,對(duì)應(yīng)模板運(yùn)營位部分 |
字段名 | 類型 | 說明 |
---|---|---|
poster | String | 長視頻封面圖 |
name | String | 長視頻名稱 |
path | String | 長視頻詳情頁路徑,已完結(jié)、更新中的資源必須填寫 |
type | String | 資源類型,如:電視劇、電影、綜藝、動(dòng)漫等 |
year | String | 發(fā)行時(shí)間 |
area | String | 發(fā)行地區(qū) |
tags | String | 資源標(biāo)簽,建議1-3個(gè) |
update | String | 更新狀態(tài),如共X集、更新?X集、即將上映、已上映、更新至X期 |
time | String | 單集片長,電視劇/綜藝等可直接填寫單集??X分鐘,電影可填寫??X分鐘 |
字段名 | 類型 | 說明 |
---|---|---|
id | Number | 視頻 id |
src | String | 視頻地址 |
poster | String | 視頻封面 |
title | String | 視頻標(biāo)題 |
playNum | Number | 視頻播放次數(shù) |
authorName | String | 視頻作者 |
authorImage | String | 視頻作者頭像 |
duration | String | 視頻時(shí)長 |
time | String | 視頻上傳時(shí)間 |
introduction | String | 單視頻簡介 |
type | Number | 視頻類型,短視頻為 1、長視頻為2 |
path | String | 視頻落地頁地址 |
字段名 | 類型 | 說明 |
---|---|---|
img | String | 運(yùn)營位封面地址 |
path | String | 運(yùn)營位落地頁地址 |
名稱 | 版本號(hào) |
---|---|
@smt-ui/component | ^1.1.41 |
@smt-ui/content-component | ^0.3.3 |
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: