Parcourir la source

新增视频模块,包含视频列表和视频详情页面,优化API请求,添加视频播放功能,更新样式和图标,提升用户体验。

yangwenlan il y a 1 an
Parent
commit
86d4844565

+ 69 - 2
api/api.js

@@ -1,4 +1,4 @@
-import { requestAll, requestLogin } from '../utils/request'
+import { requestAll, requestLogin, post, postWithCommonParams } from '../utils/request'
 
 
 export const userLogin = (data) => requestLogin(data)
@@ -94,4 +94,71 @@ export const addBrowsingHistory = (data) => {
 // 获取阅读记录
 export const getBrowsingHistory = () => {
     return requestAll('/user/browsing-history', {}, 'GET', addTokenToHeader())
-}
+}
+
+// 获取短剧列表
+export function getVideoList(params = {}) {
+  const { page = 1, size = 10, tag_id = 2245, filter_id = "", next_id = "" } = params;
+  
+  const requestData = {
+    tag_id: tag_id,
+    filter_id: filter_id,
+    next_id: next_id,
+    count: size,
+    uuid: "128eaeb697b2e02aa4badbc02f49c182", // 固定值,实际应用中可能需要动态生成
+    gh_id: "gh_acc9a32b2122" // 固定值
+  };
+  
+  return post("https://applet.xiaoduer.cn/cashvideoapi/cashvideo/getvideolist", requestData)
+    .then(data => {
+
+     return data;
+      // 处理返回数据的格式转换,确保与页面期望的数据结构一致
+      if (data) {
+        return data.map(video => ({
+          id: video.id,
+          title: video.title,
+          cover: video.cover_image,
+          playCount: video.views || '0'
+        }));
+      }
+      return [];
+    });
+}
+
+// 获取视频详情
+export function getVideoDetail(params = {}) {
+  if (!params.id) {
+    return Promise.reject(new Error('缺少必要参数: id'));
+  }
+
+  return postWithCommonParams('https://applet.xiaoduer.cn/cashvideoapi/cashvideo/getvideoinfo', params);
+}
+
+// 获取真实视频播放地址
+export function getRealVideoUrl(id, tagId) {
+  if (!id || !tagId) {
+    return Promise.reject(new Error('缺少必要参数: id 或 tagId'));
+  }
+
+  const url = `https://applet.xiaoduer.cn/coral/cashvideo/getOasVideoUrl?id=${id}&tagId=${tagId}`;
+  return postWithCommonParams(url, {}, 'GET');
+}
+
+// 获取相关视频列表
+export function getRelatedVideos(params = {}) {
+  const { tag_id = 2250, count = 10, filter_id = "" } = params;
+  
+  const requestData = {
+    tag_id,
+    count,
+    filter_id,
+    uuid: "128eaeb697b2e02aa4badbc02f49c182",
+    gh_id: "gh_acc9a32b2122"
+  };
+  
+  return post("https://applet.xiaoduer.cn/cashvideoapi/cashvideo/getrandvideolistpro", requestData);
+}
+
+// 导出postWithCommonParams
+export { postWithCommonParams };

+ 8 - 1
app.json

@@ -8,7 +8,8 @@
     "pages/mine/mine",
     "pages/book/list",
     "pages/search/search",
-    "components/charge-dialog/charge-dialog"
+    "pages/video/video",
+    "pages/video/detail"
   ],
   "window": {
     "navigationStyle": "custom",
@@ -57,6 +58,12 @@
         "selectedIconPath": "assets/images/tabs/tab-category_active.png"
       },
       {
+        "pagePath": "pages/video/video",
+        "text": "短剧",
+        "iconPath": "assets/images/tabs/tab-video.png",
+        "selectedIconPath": "assets/images/tabs/tab-video_active.png"
+      },
+      {
         "pagePath": "pages/mine/mine",
         "text": "我的",
         "iconPath": "assets/images/tabs/tab-mine.png",

BIN
assets/images/icon-video-play.png


BIN
assets/images/icons/icon-back-black.png


BIN
assets/images/icons/icon-change.png


BIN
assets/images/icons/icon-wechat.png


BIN
assets/images/tabs/tab-bookshelf.png


BIN
assets/images/tabs/tab-bookshelf_active.png


BIN
assets/images/tabs/tab-category.png


BIN
assets/images/tabs/tab-category_active.png


BIN
assets/images/tabs/tab-mine.png


BIN
assets/images/tabs/tab-mine_active.png


BIN
assets/images/tabs/tab-store.png


BIN
assets/images/tabs/tab-store_active.png


BIN
assets/images/tabs/tab-video.png


BIN
assets/images/tabs/tab-video_active.png


+ 21 - 0
config/config.js

@@ -0,0 +1,21 @@
+const appInfoObj = {
+    wxa7a33088566e1292: {
+        appid: 'wxa7a33088566e1292',
+        ghid: 'gh_6e35b17c5636',
+        appName: '试读空间',
+    },
+  };
+  
+  // @config 确定使用的小程序appid
+  const appid = 'wxa7a33088566e1292';
+  export const apple = {
+    ...appInfoObj[appid],
+  };
+  
+  // 线上域名信息
+  export const https = {
+    dev_apiHttps: 'https://hyperion-novel-qa-app.mokamrp.com',
+    test_apiHttps: 'https://test-iaa-app.mokamrp.com',
+    apiHttps: 'https://iaa-app.mokamrp.com'
+  };
+  

+ 409 - 0
pages/video/detail.js

@@ -0,0 +1,409 @@
+// pages/video/detail.js
+import { getVideoDetail, getRealVideoUrl, postWithCommonParams, getRelatedVideos } from '../../api/api'
+import { rnd } from '../../utils/util'
+
+Page({
+  data: {
+    videoId: '',
+    videoInfo: null,
+    videoDetail:null,
+    episodeList: [],
+    currentEpisodeIndex: 0,
+    currentEpisode: null, // 当前播放的剧集
+    isPlaying: false,
+    isLoading: true,
+    title: '',
+    scrollHeight: 0,
+    windowHeight: 0,
+    realVideoUrl: '', // 存储真实的视频播放地址
+    videoStatus: 0, // 0 未播放 1 播放中 2 暂停
+    duration: 0, // 视频总时长
+    currentTime: 0, // 当前播放时间
+    relatedVideos: []
+  },
+  
+  onLoad: function (options) {
+    const videoId = parseInt(options.id);
+    const videoTitle = options.title ? decodeURIComponent(options.title) : '';
+    
+    console.log('加载视频详情页,ID:', videoId, '标题:', videoTitle);
+    
+    // 获取系统信息
+    this.getSystemInfo();
+    
+    // 如果有传递标题,则直接使用
+    if (videoTitle) {
+      this.setData({
+        title: videoTitle,
+        videoId: videoId,
+        isLoading: true
+      });
+    } else {
+      this.setData({
+        videoId: videoId,
+        isLoading: true
+      });
+    }
+    
+    this.loadVideoDetail(videoId);
+  },
+  
+  // 获取系统信息
+  getSystemInfo() {
+    const systemInfo = wx.getSystemInfoSync();
+    this.setData({
+      windowHeight: systemInfo.windowHeight
+    });
+  },
+  
+  // 加载视频详情
+  async loadVideoDetail(id) {
+    try {
+      wx.showLoading({
+        title: '加载中...',
+        mask: true
+      });
+      
+      // 构建请求参数
+      const params = {
+        id,
+        tag_id: 2072
+      };
+      
+      const videoDetail = await getVideoDetail(params);
+      console.log('视频详情数据:', videoDetail);
+      
+      if (videoDetail) {
+        // 处理视频信息
+        const formattedVideoInfo = {
+          id: videoDetail.id,
+          title: videoDetail.title || this.data.title,
+          cover: videoDetail.cover_image,
+          playCount: videoDetail.views || '0',
+          playUV: videoDetail.play_uv || '0',
+          videoUrl: '', // 暂时为空,等待获取真实播放地址
+          description: videoDetail.description || '暂无简介',
+          tags: videoDetail.tag_ids || [],
+          likeCount: videoDetail.like || '0',
+          shareCount: videoDetail.share || '0',
+          isCollect: videoDetail.is_collect === 1,
+          video_type: videoDetail.video_type, // 保存原始视频类型
+          tag_ids: videoDetail.tag_ids // 保存标签ID列表
+        };
+        
+        this.setData({
+          videoInfo: formattedVideoInfo,
+          title: formattedVideoInfo.title,
+          isLoading: false
+        });
+
+        // 获取真实播放地址
+        this.getVideoPlayUrl(videoDetail);
+        
+        // 加载相关视频
+        this.loadRelatedVideos(videoDetail.tag_ids);
+      } else {
+        this.setData({
+          isLoading: false
+        });
+        
+        wx.showToast({
+          title: '获取视频详情失败',
+          icon: 'none'
+        });
+      }
+    } catch (error) {
+      console.error('获取视频详情失败:', error);
+      
+      this.setData({
+        isLoading: false
+      });
+      
+      wx.showToast({
+        title: '获取视频详情失败',
+        icon: 'none'
+      });
+    } finally {
+      wx.hideLoading();
+    }
+  },
+  
+  // 获取视频播放地址
+  async getVideoPlayUrl(videoDetail) {
+
+    
+    console.log(`videoDetail`,videoDetail)
+    try {
+      // 根据视频类型进行处理
+      switch (videoDetail.video_type) {
+        case 3: // OAS视频
+          // 获取真实播放地址
+          const tagId = videoDetail.tag_ids && videoDetail.tag_ids.length > 0 ? videoDetail.tag_ids[0] : 2072;
+          let parseRes = await getRealVideoUrl(videoDetail.id, tagId);
+          
+          console.log('parseRes',parseRes)
+
+          // 默认视频为免费视频
+          videoDetail.lock = false
+          // 标记暂停插屏是否显示过
+          videoDetail.pauseInsertAd = false
+        
+
+          // 默认视频不显示插屏
+          videoDetail.insertAd = false
+          videoDetail.adType = 'video'
+          videoDetail.likeNum = rnd(101, 209) / 10
+          videoDetail.like = false
+          videoDetail.shareNum = rnd(51, 99) / 10
+          if (videoDetail.video_type !== 4) {
+            videoDetail.playUrl =  parseRes.url_info[0].url
+          }
+          if (videoDetail.is_top === 1) {
+            videoDetail.top = true
+          }
+          this.setData({
+            isLoading:false,
+            videoDetail: videoDetail
+          })
+          
+          
+          break;
+          
+        case 4: // 微信视频
+          // 直接使用video_vid
+          this.setData({
+            'videoInfo.videoUrl': videoDetail.video_vid
+          });
+          break;
+          
+        default:
+          // 默认使用url字段
+          if (videoDetail.url) {
+            this.setData({
+              'videoInfo.videoUrl': videoDetail.url
+            });
+          } else {
+            throw new Error('不支持的视频类型');
+          }
+      }
+    } catch (error) {
+      console.error('获取视频播放地址失败:', error);
+      wx.showToast({
+        title: '无法获取视频播放地址',
+        icon: 'none'
+      });
+    }
+  },
+  
+  // 切换剧集
+  switchEpisode(e) {
+    const index = e.currentTarget.dataset.index;
+    if (index === this.data.currentEpisodeIndex) return;
+    
+    const episode = this.data.episodeList[index];
+    if (!episode) return;
+    
+    this.setData({
+      currentEpisodeIndex: index,
+      currentEpisode: episode,
+      isPlaying: true
+    });
+    
+    // 获取视频实例并播放
+    const videoContext = wx.createVideoContext('myVideo');
+    videoContext.play();
+  },
+  
+  // 视频播放事件
+  onVideoPlay() {
+    this.setData({
+      isPlaying: true,
+      videoStatus: 1
+    });
+    
+    // 可以添加播放埋点
+    console.log('视频开始播放:', this.data.videoInfo.id);
+  },
+  
+  // 视频暂停事件
+  onVideoPause() {
+    this.setData({
+      isPlaying: false,
+      videoStatus: 2
+    });
+  },
+  
+  // 视频播放结束事件
+  onVideoEnd() {
+    this.setData({
+      isPlaying: false,
+      videoStatus: 0
+    });
+    
+    // 可以添加结束埋点
+    console.log('视频播放结束:', this.data.videoInfo.id);
+  },
+  
+  // 视频时间更新事件
+  onVideoUpdate(e) {
+    const { currentTime, duration } = e.detail;
+    this.setData({
+      currentTime,
+      duration
+    });
+  },
+  
+  // 视频元数据加载完成
+  onVideoLoaded(e) {
+    console.log('视频元数据加载完成', e.detail);
+  },
+  
+  // 点击视频控制区
+  onClickVideoControls() {
+    if (this.data.videoStatus === 1) {
+      // 当前正在播放,暂停视频
+      const videoContext = wx.createVideoContext('myVideo');
+      videoContext.pause();
+    } else {
+      // 当前已暂停,开始播放
+      const videoContext = wx.createVideoContext('myVideo');
+      videoContext.play();
+    }
+  },
+  
+  // 点击相关推荐视频
+  onRelatedVideoTap(e) {
+    const videoId = e.currentTarget.dataset.id;
+    const video = this.data.relatedVideos.find(item => item.id === videoId);
+    
+    if (!video) {
+      wx.showToast({
+        title: '视频信息获取失败',
+        icon: 'none'
+      });
+      return;
+    }
+
+    // 显示加载中提示
+    wx.showLoading({
+      title: '加载中...',
+      mask: true
+    });
+
+    // 将标题进行URI编码处理
+    const encodedTitle = encodeURIComponent(video.title || '');
+    
+    // 跳转到视频详情页
+    wx.navigateTo({
+      url: `/pages/video/detail?id=${videoId}&title=${encodedTitle}`,
+      success: () => {
+        wx.hideLoading();
+      },
+      fail: (err) => {
+        console.error('跳转失败:', err);
+        wx.hideLoading();
+        wx.showToast({
+          title: '跳转失败',
+          icon: 'none'
+        });
+      }
+    });
+  },
+  
+  // 返回上一页
+  goBack() {
+    wx.navigateBack();
+  },
+  
+  // 分享功能
+  onShare() {
+    wx.showShareMenu({
+      withShareTicket: true,
+      menus: ['shareAppMessage', 'shareTimeline']
+    });
+  },
+  
+  // 分享给朋友
+  onShareAppMessage() {
+    const videoInfo = this.data.videoInfo;
+    if (!videoInfo) return {};
+    
+    return {
+      title: videoInfo.title,
+      path: `/pages/video/detail?id=${videoInfo.id}`,
+      imageUrl: videoInfo.cover
+    };
+  },
+  
+  // 分享到朋友圈
+  onShareTimeline() {
+    const videoInfo = this.data.videoInfo;
+    if (!videoInfo) return {};
+    
+    return {
+      title: videoInfo.title,
+      query: `id=${videoInfo.id}`,
+      imageUrl: videoInfo.cover
+    };
+  },
+  
+  // 收藏视频
+  toggleCollect() {
+    const isCollect = !this.data.videoInfo.isCollect;
+    
+    this.setData({
+      'videoInfo.isCollect': isCollect
+    });
+    
+    wx.showToast({
+      title: isCollect ? '收藏成功' : '已取消收藏',
+      icon: 'success'
+    });
+  },
+  
+  // 点赞视频
+  likeVideo() {
+    const currentLikes = parseInt(this.data.videoInfo.likeCount) || 0;
+    
+    this.setData({
+      'videoInfo.likeCount': (currentLikes + 1).toString()
+    });
+    
+    wx.showToast({
+      title: '点赞成功',
+      icon: 'success'
+    });
+  },
+  
+  // 滚动事件
+  onScroll(e) {
+    this.setData({
+      scrollHeight: e.detail.scrollTop
+    });
+  },
+  
+  // 加载相关视频
+  async loadRelatedVideos(tagIds) {
+    try {
+      // 使用第一个标签ID获取相关视频
+      const tagId = tagIds && tagIds.length > 0 ? tagIds[0] : 2250;
+      const relatedVideos = await getRelatedVideos({ tag_id: tagId });
+      
+      // 处理相关视频数据
+      const formattedVideos = relatedVideos.map(video => ({
+        id: video.id,
+        title: video.title,
+        cover_image: video.cover_image,
+        playCount: video.views || '0',
+        likeCount: video.like || '0',
+        shareCount: video.share || '0'
+      }));
+      
+      this.setData({
+        relatedVideos: formattedVideos
+      });
+    } catch (error) {
+      console.error('获取相关视频失败:', error);
+    }
+  }
+}) 

+ 4 - 0
pages/video/detail.json

@@ -0,0 +1,4 @@
+{
+  "navigationStyle": "custom",
+  "usingComponents": {}
+} 

+ 91 - 0
pages/video/detail.wxml

@@ -0,0 +1,91 @@
+<!--pages/video/detail.wxml-->
+<view class="container">
+  <view class="nav-container">
+    <view class="nav-back" bindtap="goBack">
+      <image class="icon-back" src="/assets/images/icons/icon-back-black.png"></image>
+    </view>
+    <text class="video-title">{{videoInfo.title || title}}</text>
+  </view>
+
+  <scroll-view scroll-y="{{true}}" class="video-container" bindscroll="onScroll">
+    <view class="video-main" style="height: 500rpx;">
+      <block wx:if="{{videoDetail.video_type !== 4}}">
+        <video autoplay="{{true}}" src="{{videoDetail.playUrl}}" direction="{{0}}" poster="{{videoDetail.cover_image}}"
+          bindplay="onVideoPlay" bindpause="onVideoPause" bindended="onVideoEnd" class="my-video" id="my-video"
+          style="height: 100%" bindtimeupdate="onVideoUpdate" bindseekcomplete="onVideoSeek"
+          bindloadedmetadata="onVideoLoaded" x5-video-player-type="h5" x5-video-player-fullscreen="true" x5-video-orientation="portrait">
+          <view wx:if="{{isShowComplainBtn}}" class="complaint-btn" catchtap="handleComplain">投诉</view>
+          <!-- 激励使用设置的首帧图 -->
+          <image wx:if="{{videoDetail.first_image && videoDetail.lock}}" class="poster" src="{{videoDetail.first_image}}">
+          </image>
+          <view catchtap="handleClickRewardMask" class="reward-mask" wx:if="{{videoDetail.lock}}">
+            <view class="reward-text">观看激励视频解锁更多精彩</view>
+            <view class="play-btn">免费观看</view>
+          </view>
+        </video>
+      </block>
+      
+      <!-- 加载中提示 -->
+      <view wx:if="{{isLoading || !videoDetail.playUrl}}" class="loading-container">
+        <view class="loading-spinner"></view>
+        <view class="loading-text">{{isLoading ? '加载中...' : '获取视频中...'}}</view>
+      </view>
+    </view>
+    
+    <view class="bottom-container" wx:if="{{!isLoading && videoDetail}}">
+
+      <!-- 分享 -->
+      <view class="middle-share-container">
+        <view class="middle-share-btn middle-share-btn-green" data-type="right">
+          <image class="icon-wechat" src="/assets/images/icons/icon-wechat.png"></image>
+          <text>转发好友</text>
+          <button class="cover-button" open-type="share" data-type="video-friend"></button>
+        </view>
+        <view class="middle-share-btn middle-share-btn-orange" data-type="left">
+          <image class="icon-wechat" src="/assets/images/icons/icon-wechat.png"></image>
+          <text>分享群里</text>
+          <button class="cover-button" open-type="share" data-type="video-group"></button>
+        </view>
+      </view>
+      
+      <!-- 标题 -->
+      <view class="title-wrap">
+        <view class="title">{{videoInfo.title}}</view>
+      </view>
+      
+      
+      <!-- <view class="home-btn" bindtap="goBack">返回视频列表</view> -->
+    </view>
+
+    <!-- 相关推荐 -->
+    <view class="recommend-section" wx:if="{{!isLoading && relatedVideos && relatedVideos.length > 0}}">
+      <view class="recommend-title">-更多精彩推荐-</view>
+      <view class="video-list">
+
+        <block wx:for="{{ relatedVideos }}" wx:key="id">
+          <!-- 视频卡片 -->
+          <view class="video-item" bindtap="onRelatedVideoTap" data-id="{{ item.id }}">
+            <view class="video-cover-container">
+              <image
+                class="video-cover"
+                src="{{ item.cover_image }}"
+                mode="aspectFill"
+              />
+              <view class="play-button-container">
+                <view class="play-button">
+                  <image
+                    class="play-icon"
+                    src="/assets/images/icon-video-play.png"
+                  ></image>
+                </view>
+              </view>
+            </view>
+  
+            <view class="video-item-title">{{ item.title }}</view>
+           
+          </view>
+        </block>
+      </view>
+    </view>
+  </scroll-view>
+</view> 

+ 489 - 0
pages/video/detail.wxss

@@ -0,0 +1,489 @@
+/* pages/video/detail.wxss */
+page {
+  height: 100%;
+  background: #fff;
+}
+
+.container {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: 100vh;
+  background-color: #fff;
+}
+
+/* 导航栏 */
+.nav-container {
+  background: #fff;
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  z-index: 99;
+  height: 88px; /* 44px状态栏 + 44px导航 */
+  display: flex;
+  align-items: flex-end;
+  padding-bottom: 10px;
+}
+
+.nav-back {
+  position: absolute;
+  bottom: 10px;
+  left: 16rpx;
+  width: 60rpx;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.icon-back {
+  width: 40rpx;
+  height: 40rpx;
+}
+
+.video-title {
+  position: absolute;
+  top: 110rpx;
+  left: 50%;
+  transform: translateX(-50%);
+  flex: 1;
+  width: 390rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 1;
+  -webkit-box-orient: vertical;
+  font-size: 32rpx;
+  color: #333;
+  text-align: center;
+  margin: 0 auto;
+}
+
+.video-container {
+  position: absolute;
+  left: 0;
+  width: 100%;
+  top: 88px; /* 与导航栏高度一致 */
+  height: calc(100% - 88px);
+}
+
+.video-main {
+  position: relative;
+  height: 500rpx;
+}
+
+.my-video {
+  position: relative;
+  width: 100%;
+  height: 100%;
+}
+
+/* 视频控制区域 */
+.video-control-area {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 9;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.video-play-status {
+  width: 120rpx;
+  height: 120rpx;
+  background-color: rgba(0, 0, 0, 0.3);
+  border-radius: 50%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.icon-play {
+  width: 60rpx;
+  height: 60rpx;
+}
+
+/* 加载中样式 */
+.loading-container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  background-color: #000;
+}
+
+.loading-spinner {
+  width: 80rpx;
+  height: 80rpx;
+  border: 6rpx solid rgba(255, 255, 255, 0.1);
+  border-left-color: #fff;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+.loading-text {
+  margin-top: 20rpx;
+  font-size: 28rpx;
+  color: rgba(255, 255, 255, 0.7);
+}
+
+/* 底部容器 */
+.bottom-container {
+  padding: 0 30rpx;
+}
+
+/* 分享按钮 */
+.middle-share-container {
+  position: relative;
+  box-sizing: border-box;
+  display: flex;
+  align-items: center;
+  justify-content: space-around;
+  padding: 50rpx 0;
+}
+
+.middle-share-btn {
+  position: relative;
+  width: 260rpx;
+  height: 90rpx;
+  border-radius: 50rpx;
+  font-size: 32rpx;
+  font-family: PingFang SC, PingFang SC-Bold;
+  line-height: 42rpx;
+  color: #ffffff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.middle-share-btn-green {
+  background: #14b96c;
+}
+
+.middle-share-btn-orange {
+  background: #ff7300;
+}
+
+.icon-wechat {
+  width: 45rpx;
+  height: 45rpx;
+  margin-right: 10rpx;
+}
+
+.cover-button {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  opacity: 0;
+}
+
+/* 标题样式 */
+.title-wrap {
+  padding: 30rpx 0;
+  color: #111;
+}
+
+.title {
+  width: 100%;
+  font-size: 44rpx;
+  font-family: PingFang SC, PingFang SC-Bold;
+  letter-spacing: 2rpx;
+  font-weight: bold;
+  overflow: hidden;
+}
+
+/* 视频信息 */
+.video-info {
+  margin-bottom: 20rpx;
+}
+
+.video-stats {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+  font-size: 28rpx;
+  color: #666;
+}
+
+/* 视频描述 */
+.video-description {
+  font-size: 28rpx;
+  color: #333;
+  line-height: 1.6;
+  margin-bottom: 20rpx;
+}
+
+/* 互动按钮 */
+.interaction-bar {
+  display: flex;
+  justify-content: space-around;
+  padding: 20rpx 0;
+  border-bottom: 1px solid #f0f0f0;
+  margin-bottom: 20rpx;
+}
+
+.interaction-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  position: relative;
+}
+
+.icon-interact {
+  width: 50rpx;
+  height: 50rpx;
+  margin-bottom: 10rpx;
+}
+
+.interaction-item text {
+  font-size: 24rpx;
+  color: #666;
+}
+
+/* 视频标签 */
+.video-tags {
+  display: flex;
+  flex-wrap: wrap;
+  margin-top: 16rpx;
+}
+
+.tag {
+  font-size: 24rpx;
+  color: #ff4757;
+  background-color: rgba(255, 71, 87, 0.08);
+  padding: 8rpx 20rpx;
+  border-radius: 30rpx;
+  margin-right: 16rpx;
+  margin-bottom: 16rpx;
+}
+
+/* 剧集列表 */
+.episode-section {
+  margin-bottom: 20rpx;
+}
+
+.section-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24rpx;
+}
+
+.title-text {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.episode-count {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.episode-list {
+  white-space: nowrap;
+  height: 80rpx;
+}
+
+.episode-item {
+  display: inline-block;
+  margin-right: 16rpx;
+  padding: 0 30rpx;
+  height: 80rpx;
+  line-height: 80rpx;
+  border-radius: 40rpx;
+  background-color: #f6f6f6;
+  font-size: 28rpx;
+  color: #666;
+}
+
+.episode-item.active {
+  background-color: #ff4757;
+  color: #fff;
+}
+
+/* 返回首页按钮 */
+.home-btn {
+  border: 1px solid #eeeeee;
+  color: #121212;
+  height: 100rpx;
+  line-height: 100rpx;
+  text-align: center;
+  width: 100%;
+  border-radius: 10rpx;
+  font-size: 40rpx;
+  margin-bottom: 30rpx;
+}
+
+/* 更多推荐标题 */
+.more-title {
+  color: #121212;
+  font-size: 32rpx;
+  text-align: center;
+  margin-top: 30rpx;
+}
+
+/* 相关推荐 */
+.recommend-section {
+  padding: 30rpx;
+  background: #fff;
+}
+
+.recommend-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+  text-align: center;
+}
+
+.video-list {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+
+/* 换一换按钮 */
+.change-video {
+  width: 80rpx;
+  height: 80rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  position: fixed;
+  right: 30rpx;
+  bottom: 15%;
+  background: #fff;
+  border-radius: 50%;
+  z-index: 99;
+}
+
+.icon-change {
+  width: 80rpx;
+  height: 80rpx;
+  position: absolute;
+  top: 0;
+  left: 0;
+}
+
+
+/* 视频卡片样式 */
+.video-item {
+  position: relative;
+  margin-bottom: 20rpx;
+  border-radius: 12rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+}
+
+.video-item::before{
+    content: "";
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.4);
+    z-index: 2;
+    position: absolute;
+    top: 0;
+    left: 0;
+  }
+
+.video-cover-container {
+  width: 100%;
+  height: 380rpx;
+}
+
+.video-cover {
+  width: 100%;
+  height: 100%;
+}
+
+.play-button-container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 3;
+}
+
+.play-button {
+  width: 100rpx;
+  height: 100rpx;
+  border-radius: 50%;
+  background-color: rgba(255, 255, 255, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.play-icon {
+  width: 100rpx;
+  height: 100rpx;
+}
+
+.video-item-title {
+  position: absolute;
+  top: 20rpx;
+  left: 20rpx;
+  font-size: 42rpx;
+  font-weight: bold;
+  color: #fff;
+  margin-bottom: 10rpx;
+  display: -webkit-box;
+  width: 95%;
+  z-index: 3;
+}
+
+.video-play-count {
+  position: absolute;
+  bottom: 20rpx;
+  left: 20rpx;
+  font-size: 36rpx;
+  color: #fff;
+  margin-top: 10rpx;
+  z-index: 3;
+}
+
+.share-button {
+  position: absolute;
+  right: 20rpx;
+  bottom: 20rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  display: none;
+  z-index: 3;
+}
+
+.share-icon {
+  width: 50rpx;
+  height: 50rpx;
+  margin-bottom: 6rpx;
+}
+
+.share-button text {
+  font-size: 22rpx;
+  color: #07c160;
+}

+ 288 - 0
pages/video/video.js

@@ -0,0 +1,288 @@
+// pages/video/video.js
+import { getVideoList } from '../../api/api'
+import { rnd,deepCopy } from '../../utils/util'
+
+const app = getApp()
+
+
+Page({
+  data: {
+    videoList: [],
+    loading: false,
+    hasMore: true,
+    page: 1,
+    size: 10,
+    // 状态栏高度
+    statusBarHeight: 20,
+    navHeight: 64, // 导航栏高度 = 状态栏 + 44
+    navStyle: '',
+    listStyle: '',
+    // 添加防抖时间戳
+    lastLoadTime: 0,
+    ads:null
+  },
+
+  onLoad() {
+    // 获取状态栏高度
+    this.getSystemInfo()
+    // 加载视频列表
+    this.loadVideoList()
+
+  },
+
+  onShow() {
+    // 每次显示页面时刷新列表
+    if (this.data.videoList.length === 0) {
+      this.setData({
+        page: 1
+      })
+      this.loadVideoList()
+    }
+  },
+
+  // 获取系统信息
+  getSystemInfo() {
+    const systemInfo = wx.getSystemInfoSync()
+    const statusBarHeight = systemInfo.statusBarHeight
+    const navHeight = statusBarHeight + 44
+    
+    this.setData({
+      statusBarHeight: statusBarHeight,
+      navHeight: navHeight,
+      navStyle: `height:${navHeight}px`,
+      listStyle: `top:${navHeight}px`
+    })
+  },
+
+  // 加载视频列表
+  async loadVideoList() {
+    if (this.data.loading) return
+
+    this.setData({ loading: true })
+
+    try {
+      const params = {
+        page: this.data.page,
+        size: this.data.size,
+        next_id: this.data.page > 1 && this.data.videoList.length > 0 
+          ? this.data.videoList[this.data.videoList.length - 1].id 
+          : ""
+      }
+      
+      let videoList = []
+      try {
+        videoList = await getVideoList(params)
+        
+        if (!Array.isArray(videoList)) {
+          throw new Error('返回数据格式错误')
+        }
+
+        videoList = videoList.map(item => ({
+          ...item,
+          views: rnd(1, 10),
+          favors: rnd(5000, 10000),
+          adType: "video"
+        }))
+
+        videoList = this.formatVideoAdList(deepCopy(videoList))
+
+      } catch (apiError) {
+        console.error('API调用失败:', apiError)
+        throw apiError
+      }
+      
+      // 如果是第一页,直接替换数据
+      if (this.data.page === 1) {
+        this.setData({
+          videoList: videoList,
+          loading: false,
+          hasMore: videoList.length >= this.data.size
+        })
+      } else {
+        // 否则追加数据
+        this.setData({
+          videoList: [...this.data.videoList, ...videoList],
+          loading: false,
+          hasMore: videoList.length >= this.data.size
+        })
+      }
+    } catch (error) {
+      console.error('加载视频列表失败:', error)
+      
+      // 使用模拟数据
+      const mockData = this.getMockVideoList()
+      this.setData({ 
+        loading: false,
+        hasMore: false,
+        videoList: this.data.page === 1 ? mockData : [...this.data.videoList, ...mockData]
+      })
+      
+      wx.showToast({
+        title: '加载失败,使用了模拟数据',
+        icon: 'none',
+        duration: 2000
+      })
+    }
+  },
+/* ------------------ 广告相关 ----------------- */
+  /**
+   * 标记广告位置
+   * @param {} arr
+   * @returns
+   */
+  formatVideoAdList(arr) {
+    // 原生广告间隔,激励广告间隔
+    let templateGap = 0, rewardGap = 0
+    
+    // 第二个固定是原生广告
+    let result = [arr.shift(), {
+      adType: 'templateAd'
+    }]
+    // 辅助计算间隔变量
+    let markIndex = 0
+    let templateAdFlag = true
+    let rewardAdFlag = true
+
+    do {
+      markIndex += 1
+      if (templateGap > 0) {
+        if (templateAdFlag && markIndex - 1 === templateGap) {
+          markIndex = 0
+          result.push({
+            adType: "templateAd"
+          })
+          templateAdFlag = false
+          rewardAdFlag = true
+          continue
+        }
+      } else {
+        rewardAdFlag = true
+      }
+      if (rewardGap > 0) {
+        if (rewardAdFlag && markIndex - 1 === rewardGap) {
+          markIndex = 0
+          templateAdFlag = true
+          rewardAdFlag = false
+          let rewardItem = arr.shift()
+          rewardItem.lock = true
+          result.push(rewardItem)
+          continue
+        }
+      } else {
+        templateAdFlag = true
+      }
+
+      result.push(arr.shift())
+    } while (arr.length > 0);
+
+    return result;
+  },
+  // 加载更多
+  loadMore() {
+    console.log("触发loadMore")
+    console.log("当前状态:", {
+      loading: this.data.loading,
+      hasMore: this.data.hasMore,
+      page: this.data.page,
+      videoListLength: this.data.videoList.length
+    })
+    
+    // 添加防抖,避免频繁触发
+    const now = Date.now()
+    if (now - this.data.lastLoadTime < 500) {
+      console.log("防抖触发,跳过本次加载")
+      return
+    }
+    
+    if (this.data.hasMore && !this.data.loading) {
+      console.log("开始加载更多数据")
+      this.setData({
+        page: this.data.page + 1,
+        lastLoadTime: now
+      })
+      this.loadVideoList()
+    } else {
+      console.log("不满足加载条件:", {
+        hasMore: this.data.hasMore,
+        loading: this.data.loading
+      })
+    }
+  },
+  
+  // 刷新列表
+  refreshList() {
+    wx.showLoading({
+      title: '刷新中...',
+    })
+    
+    this.setData({
+      page: 1,
+      videoList: [],
+      hasMore: true
+    })
+    
+    this.loadVideoList().then(() => {
+      wx.hideLoading()
+      wx.showToast({
+        title: '刷新成功',
+        icon: 'success',
+        duration: 1500
+      })
+    }).catch(() => {
+      wx.hideLoading()
+    })
+  },
+  
+  // 点击视频
+  onVideoTap(e) {
+    const videoId = e.currentTarget.dataset.id;
+    // 显示加载中提示
+    wx.showLoading({
+      title: '加载中...',
+      mask: true
+    });
+    
+    // 获取当前点击的视频项
+    const video = this.data.videoList.find(item => item.id === videoId);
+    
+    // 确保视频对象存在
+    if (!video) {
+      wx.hideLoading();
+      wx.showToast({
+        title: '视频数据错误',
+        icon: 'none'
+      });
+      return;
+    }
+    
+    // 将标题进行URI编码处理(避免特殊字符导致的问题)
+    const encodedTitle = encodeURIComponent(video.title || '');
+    
+    // 跳转到视频详情页
+    wx.navigateTo({
+      url: `/pages/video/detail?id=${videoId}&title=${encodedTitle}`,
+      success: () => {
+        wx.hideLoading();
+      },
+      fail: (err) => {
+        console.error('跳转失败:', err);
+        wx.hideLoading();
+        wx.showToast({
+          title: '跳转失败',
+          icon: 'none'
+        });
+      }
+    });
+  },
+  
+  // 下拉刷新
+  onPullDownRefresh() {
+    this.setData({
+      page: 1,
+      videoList: []
+    })
+    this.loadVideoList().then(() => {
+      wx.stopPullDownRefresh()
+    })
+  }
+}) 

+ 6 - 0
pages/video/video.json

@@ -0,0 +1,6 @@
+{
+  "navigationStyle": "custom",
+  "enablePullDownRefresh": true,
+  "backgroundTextStyle": "dark",
+  "usingComponents": {}
+} 

+ 83 - 0
pages/video/video.wxml

@@ -0,0 +1,83 @@
+<!--pages/video/video.wxml-->
+<view class="container">
+  <!-- 顶部导航栏 -->
+  <view class="nav-container">
+    <text class="hello-text">新推荐</text>
+  </view>
+
+  <!-- 视频列表 -->
+  <scroll-view
+    scroll-y
+    class="list-container"
+    bindscrolltolower="loadMore"
+    enable-back-to-top
+    enhanced="{{ true }}"
+    show-scrollbar="{{ false }}"
+    bounces="{{ true }}"
+    fast-deceleration="{{ true }}"
+  >
+    <view class="list">
+      <block wx:for="{{ videoList }}" wx:key="id">
+        <view wx:if="{{ item.adType === 'video' }}">
+          <!-- 视频卡片 -->
+          <view class="video-item" bindtap="onVideoTap" data-id="{{ item.id }}">
+            <view class="video-cover-container">
+              <image
+                class="video-cover"
+                src="{{ item.cover_image }}"
+                mode="aspectFill"
+              />
+              <view class="play-button-container">
+                <view class="play-button">
+                  <image
+                    class="play-icon"
+                    src="/assets/images/icon-video-play.png"
+                  ></image>
+                </view>
+              </view>
+            </view>
+
+            <view class="video-title">{{ item.title }}</view>
+            <view class="video-play-count">{{ item.views }}w+人播放</view>
+            <view class="share-button">
+              <image
+                class="share-icon"
+                src="/assets/images/icons/refresh.png"
+              ></image>
+              <text>分享</text>
+            </view>
+          </view>
+        </view>
+         <!-- 卡片原生广告 -->
+         <view wx:if="{{hasLoadAdConfig && itm.adType === 'templateAd' }}" class="ad-custom-card-1">
+            <ad-custom unit-id="{{ads.template_card_ads}}" data-field="templateError" data-name="首页页-卡片原生"
+              data-adId="{{ads.template_card_ads}}" capture-bind:tap="appletsAdClick" bindload="appletsAdLoad"
+              binderror="appletsAdError"></ad-custom>
+          </view>
+            
+      </block>
+    </view>
+
+    <!-- 加载状态 -->
+    <view class="loading-status">
+      <!-- loading 提示 -->
+      <view class="loading" wx:if="{{ loading }}">
+        <view class="loading-spinner"></view>
+        <text>加载中...</text>
+      </view>
+
+      <!-- 没有更多 -->
+      <view
+        class="finished"
+        wx:if="{{ !loading && videoList.length > 0 && !hasMore }}"
+      >
+        <text>没有更多视频了</text>
+      </view>
+
+      <!-- 空状态 -->
+      <view class="empty" wx:if="{{ !loading && videoList.length === 0 }}">
+        <text>暂无视频内容</text>
+      </view>
+    </view>
+  </scroll-view>
+</view>

+ 218 - 0
pages/video/video.wxss

@@ -0,0 +1,218 @@
+/* pages/video/video.wxss */
+.container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  background-color: #f8f8f8;
+  position: relative;
+}
+
+/* 头部导航样式 */
+.nav-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  z-index: 1000;
+  height: 180rpx;
+  padding-top: 100rpx;
+  background-color: #f8f8f8;
+  box-sizing: border-box;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+}
+
+.hello-text {
+  font-size: 34rpx;
+  font-weight: bold;
+  color: #333;
+  position: absolute;
+  left: 30rpx;
+}
+
+/* 列表容器 */
+.list-container {
+  flex: 1;
+  height: calc(100vh - 180rpx);
+  background-color: #f8f8f8;
+}
+
+.list {
+  padding: 20rpx;
+  box-sizing: border-box;
+}
+
+/* 视频卡片样式 */
+.video-item {
+  position: relative;
+  margin-bottom: 20rpx;
+  border-radius: 12rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+}
+
+.video-item::before{
+    content: "";
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.4);
+    z-index: 2;
+    position: absolute;
+    top: 0;
+    left: 0;
+  }
+
+.video-cover-container {
+  width: 100%;
+  height: 380rpx;
+}
+
+.video-cover {
+  width: 100%;
+  height: 100%;
+}
+
+.play-button-container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 3;
+}
+
+.play-button {
+  width: 100rpx;
+  height: 100rpx;
+  border-radius: 50%;
+  background-color: rgba(255, 255, 255, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.play-icon {
+  width: 100rpx;
+  height: 100rpx;
+}
+
+.video-title {
+  position: absolute;
+  top: 20rpx;
+  left: 20rpx;
+  font-size: 42rpx;
+  font-weight: bold;
+  color: #fff;
+  margin-bottom: 10rpx;
+  display: -webkit-box;
+  width: 95%;
+  z-index: 3;
+}
+
+.video-play-count {
+  position: absolute;
+  bottom: 20rpx;
+  left: 20rpx;
+  font-size: 36rpx;
+  color: #fff;
+  margin-top: 10rpx;
+  z-index: 3;
+}
+
+.share-button {
+  position: absolute;
+  right: 20rpx;
+  bottom: 20rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  display: none;
+  z-index: 3;
+}
+
+.share-icon {
+  width: 50rpx;
+  height: 50rpx;
+  margin-bottom: 6rpx;
+}
+
+.share-button text {
+  font-size: 22rpx;
+  color: #07c160;
+}
+
+/* 加载状态样式 */
+.loading-status {
+  padding: 30rpx 0;
+  text-align: center;
+  font-size: 28rpx;
+  color: #999;
+}
+
+.loading {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 100rpx;
+}
+
+.loading-spinner {
+  width: 40rpx;
+  height: 40rpx;
+  border: 4rpx solid #f3f3f3;
+  border-top: 4rpx solid #07c160;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+  margin-bottom: 10rpx;
+}
+
+.finished {
+  height: 80rpx;
+  line-height: 80rpx;
+  color: #999;
+}
+
+.empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 100rpx 0;
+}
+
+.empty-icon {
+  width: 200rpx;
+  height: 200rpx;
+  margin-bottom: 20rpx;
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+/* 刷新按钮 */
+.refresh-container {
+  position: fixed;
+  right: 20rpx;
+  bottom: 100rpx;
+  z-index: 1001;
+  width: 120rpx;
+  height: 120rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+@keyframes rotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}

+ 34 - 8
utils/request.js

@@ -1,22 +1,22 @@
 // 预发布地址
+import { apple } from '../config/config';
+
 let httpTrackLog = 'https://hyperion-track-app.mokamrp.com';
 let httpServiceURL = '';
 let version = __wxConfig.envVersion;
+import { https } from '../config/config';
 switch (version) {
   case 'develop':
-    httpServiceURL = 'https://hyperion-novel-qa-app.mokamrp.com';
-    // httpServiceURL = "http://172.16.24.193:10111";
-    // httpServiceURL = 'https://iaa-app.mokamrp.com';
+    httpServiceURL = https.dev_apiHttps;
     break;
   case 'trial':
-    httpServiceURL = 'https://test-iaa-app.mokamrp.com';
-    // httpServiceURL = 'https://iaa-app.mokamrp.com';
+    httpServiceURL = https.test_apiHttps;
     break;
   case 'release': //正式版
-    httpServiceURL = 'https://iaa-app.mokamrp.com';
+    httpServiceURL = https.apiHttps;
     break;
   default: //未知,默认调用正式版
-    httpServiceURL = 'https://iaa-app.mokamrp.com';
+    httpServiceURL = https.apiHttps;
     break;
 }
 
@@ -62,7 +62,7 @@ export const get = (url) => {
       url: url,
       method: 'GET',
       success: (res) => {
-        if (res.statusCode == 200 && res.data.code == 200) {
+        if (res.statusCode == 200) {
           resolve(res.data);
         } else {
           reject(res);
@@ -221,3 +221,29 @@ export const requestAll = (requestUrl, body, method = 'POST', customHeaders = {}
     throw error;
   });
 };
+
+/**
+ * 添加公共参数
+ */
+export function postWithCommonParams(url, body, method = 'POST') {
+  const app = getApp()
+  if (!body) {
+    body = {}
+  }
+  if (app.globalData.uuid) {
+    body.uuid = app.globalData.uuid
+  } else {
+    body.uuid = 'fuck'
+  }
+  body.gh_id = apple.ghid
+  
+  // 判断是否为完整URL
+  const isFullUrl = url.startsWith('http://') || url.startsWith('https://')
+  const fullUrl = isFullUrl ? url : https.apiHttps + url
+  
+  if (method === 'POST') {
+    return post(fullUrl, body)
+  } else {
+    return get(fullUrl)
+  }
+}

+ 43 - 1
utils/util.js

@@ -52,7 +52,49 @@ export const goToBookDetail = (options) => {
   });
 };
 
+// 随机整数方法
+export const rnd = (min = 0, max = 1) => {
+  const random = Math.floor(Math.random() * (max - min + 1) + min)
+  return random
+};
+
+// 随机整数方法
+export const rndone = (self = []) => {
+  return self.length > 0 ? self[rnd(0, self.length - 1)] : ''
+};
+
+/**
+ * 拷贝
+ */
+export function deepCopy(data) {
+  let t = typeof data
+  if (data instanceof Array) {
+    t = 'array'
+  }
+
+  let o
+
+  if (t === 'array') {
+    o = []
+    for (let i = 0; i < data.length; i++) {
+      o.push(deepCopy(data[i]))
+    }
+  } else if (t === 'object') {
+    o = {}
+    for (let i in data) {
+      o[i] = deepCopy(data[i])
+    }
+  } else {
+    return data
+  }
+
+  return o
+}
+
 module.exports = {
   formatTime,
-  goToBookDetail
+  goToBookDetail,
+  rnd,
+  rndone,
+  deepCopy
 }