Parcourir la source

add video-coloring-page detail

guoziyun il y a 11 mois
Parent
commit
86df8b88e1
4 fichiers modifiés avec 391 ajouts et 11 suppressions
  1. 1 0
      app.js
  2. 93 0
      routes/v2/video-coloring-page.js
  3. 6 11
      views/v2/album.ejs
  4. 291 0
      views/v2/video-coloring-page.ejs

+ 1 - 0
app.js

@@ -128,6 +128,7 @@ app.use('/dragon-coloring-pages', require('./routes/v2/coloring-page-collection'
 app.use('/unicorn-coloring-pages', require('./routes/v2/coloring-page-collection'))
 app.use('/food-coloring-pages', require('./routes/v2/coloring-page-collection'))
 app.use('/video-coloring-pages', require('./routes/v2/video-coloring-page'))
+app.use('/video-coloring-page', require('./routes/v2/video-coloring-page'))
 
 app.use('/coloring-page-albums', require('./routes/v2/album'))
 app.use('/coloring-page-album', require('./routes/v2/album'))

+ 93 - 0
routes/v2/video-coloring-page.js

@@ -2,6 +2,7 @@ var express = require('express');
 var router = express.Router();
 const models = require('../../models');
 const common = require('./common');
+const utils = require('../../libs/utils');
 const redis = require('../../libs/redis');
 const config = require('../../config/app');
 
@@ -9,6 +10,9 @@ const CACHE_PREFIX = "art_v2";
 // const CACHE_EXPIRES = 60; // 60s刷新一次
 const CACHE_EXPIRES = 600;
 
+const artSelect = 'name title desc seoTitle seoDescription width height date publishTime tags lastMod mystery hasSpecial useSpecialThumb publishVersion totalStartCount totalDoneCount completionRate';
+
+// 获得所有video coloring pages
 router.get('/', (req, res, next) => {
   (async function () {
     let uri = req.originalUrl.substring(1);
@@ -98,5 +102,94 @@ router.get('/', (req, res, next) => {
 });
 
 
+// video coloring page详情页路由
+router.get('/:id', function (req, res, next) {
+  (async function () {
+    let id = req.params.id;
+    utils.validators.validateId(id);
+
+    let cacheKey = `${CACHE_PREFIX}_video_${id}`;
+    let htmlData = await redis.getAsync(cacheKey);
+    htmlData = null;
+    if (!htmlData) {
+      // 专辑
+      let doc = await models.ArtVideoStory
+        .findById(id)
+        .populate({ path: 'contents', model: 'Art', select: artSelect, populate: { path: 'user', select: 'username name' } })
+        .lean()
+        .exec();
+
+      if (!doc) throw createError(404, 'Page Not Found!');
+
+      let host = config.cdnHost ?? config.resHost;
+
+      doc.poster = `${host}/thumbs/coloring-page/vs-poster/320/${doc._id}.webp`;
+      doc.size = doc.contents.length;
+      doc.timeCreate = common.dateFormat(doc.timeCreate);
+      if (doc.seoTitle) {
+        try {
+          let titleJson = JSON.parse(doc.seoTitle);
+          doc.seoTitle = titleJson && titleJson['en'] ? titleJson['en'] : doc.name;
+        } catch (e) {
+          console.error(e.message);
+        }
+      } else {
+        doc.seoTitle = doc.name;
+      }
+      if (doc.seoDescription) {
+        try {
+          let descJson = JSON.parse(doc.seoDescription);
+          doc.seoDescription = descJson && descJson['en'] ? descJson['en'] : doc.seoTitle;
+        } catch (e) {
+          console.error(e.message);
+        }
+      } else {
+        doc.seoDescription = doc.seoTitle;
+      }
+
+      common.organizeData(doc.contents);
+
+      const comments = await models.Comment.find({ approved: true, page: id }).sort({ createdAt: -1 });
+
+      // deeplink 相关
+      let applink = `https://art.pcoloring.com${req.originalUrl}`;
+      let downlink = `https://pcoloring.com/anc/`;
+
+      let data = {
+        title: doc.seoTitle,
+        description: doc.seoDescription,
+        data: doc,
+        uri: req.originalUrl,
+        pageId: id,
+        comments,
+        applink,
+        downlink,
+      };
+      // 渲染EJS模板到内存中
+      res.render('v2/video-coloring-page', data, async (err, html) => {
+        if (err) {
+          // 如果渲染出错,调用next()传递错误
+          return next(err);
+        }
+
+        // 渲染成功,存redis, 发送数据到客户端
+        htmlData = html;
+        try {
+          await redis.set(cacheKey, htmlData, 'EX', CACHE_EXPIRES);
+        } catch (e) {
+          console.error(e);
+        }
+
+        res.send(htmlData);
+      });
+    } else {
+      // 缓存命中, 直接发送缓存数据
+      res.set({ 'X-From-Cache': 'true' });
+      res.send(htmlData);
+    }
+
+  })().catch(next);
+
+});
 
 module.exports = router;

+ 6 - 11
views/v2/album.ejs

@@ -230,18 +230,13 @@
         <a href="/">Home</a> &gt; <a href="/coloring-page-albums">Coloring Pages Albums</a> &gt; <%= data.title%>
       </div>
       <section>
-        <h1>
-          <%= data.title%>
-        </h1>
-        <h4 style="margin-bottom: 40px;">By Art Number Coloring / <%=data.timeCreate%>
-        </h4>
+        <h1><%= data.title%></h1>
+        <h4 style="margin-bottom: 40px;">By Art Number Coloring / <%=data.timeCreate%></h4>
         <div class="poster-image">
           <img src="<%= data.cover %>" alt="<%= data.title%>">
         </div>
 
-        <p>
-          <%= data.slogon %>
-        </p>
+        <p><%= data.slogon %></p>
       </section>
 
       <section>
@@ -294,13 +289,13 @@
 
       <%- include('tips-get-use-of-our-coloring-pages') %>
 
-        <%- include('comment') %>
+      <%- include('comment') %>
     </main>
 
     <%- include('footer') %>
 
-      <script src="/scripts/script.js"></script>
-      <script src="/scripts/progress2.js"></script>
+    <script src="/scripts/script.js"></script>
+    <script src="/scripts/progress2.js"></script>
 
 </body>
 

+ 291 - 0
views/v2/video-coloring-page.ejs

@@ -0,0 +1,291 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <link rel="icon" href="/assets/icon/favicon.ico" type="image/x-icon">
+  <title><%= title %></title>
+
+  <meta name="description" content="<%= description %>">
+  <meta property="og:title" content="<%= title %>">
+  <meta property="og:description" content="<%= description %>">
+  <meta property="og:image" content="<%= data.poster %>">
+  <meta property="og:type" content="website">
+
+  <!-- MARK: Universal Link / Android App Link 的核心配置 -->
+  <!-- 这些 meta 标签的值应该是完整的 HTTPS 链接,Facebook 会识别并尝试拉起 App -->
+  <meta property="og:url" content="<%= applink %>" />
+  <!-- **Universal Link 路径** -->
+  <meta property="al:ios:url" content="<%= applink %>" />
+  <!-- **Universal Link 路径** -->
+  <meta property="al:ios:app_store_id" content="1575480118" /> <!-- **iOS App Store ID** -->
+  <meta property="al:ios:app_name" content="Art Number Coloring Book" /> <!-- **iOS 应用名称** -->
+
+  <meta property="al:android:package" content="com.pcoloring.art.puzzle.color.by.number" /> <!-- **Android 包名** -->
+  <meta property="al:android:url" content="<%= applink %>>" />
+  <!-- ** Universal Link 路径** -->
+  <meta property="al:android:app_name" content="Art Number Coloring Book" /> <!-- **Android 应用名称** -->
+
+  <meta name="apple-itunes-app" content="app-id=1575480118">
+  
+
+  <link rel="stylesheet" href="/stylesheets/v2/styles.css">
+  <style>
+    h1 {
+      text-align: start;
+    }
+
+    p {
+      font-size: 1.1rem;
+    }
+
+    .breadcrumb {
+      margin: 20px 0;
+      font-size: 1.0rem;
+      color: var(--light-text);
+    }
+
+    .breadcrumb a {
+      color: var(--primary-color);
+      text-decoration: none;
+    }
+
+    .breadcrumb a:hover {
+      text-decoration: underline;
+    }
+
+    /* 视频播放器样式 */
+    .video-player {
+      max-width: 500px;
+      position: relative;
+      background-color: #000;
+      border-radius: 8px;
+      overflow: hidden;
+      cursor: pointer;
+      aspect-ratio: 1/1; /* 修改为1:1比例 */
+      margin-bottom: 50px;
+    }
+
+    .video-player video {
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+      display: block;
+    }
+
+    .play-button {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 80px;
+      height: 80px;
+      background-color: rgba(255, 107, 107, 0.8);
+      border-radius: 50%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      transition: all 0.3s ease;
+    }
+
+    .play-button:hover {
+      background-color: rgba(255, 107, 107, 1);
+      transform: translate(-50%, -50%) scale(1.1);
+    }
+
+    .play-button::after {
+      content: "";
+      width: 0;
+      height: 0;
+      border-top: 15px solid transparent;
+      border-bottom: 15px solid transparent;
+      border-left: 25px solid white;
+      margin-left: 5px;
+    }
+
+    .gallery-link {
+      text-align: center;
+      margin-top: 30px;
+      font-size: 1.2rem;
+    }
+
+    .gallery-link a {
+      color: var(--primary-color);
+      text-decoration: none;
+      font-weight: 600;
+      display: inline-flex;
+      align-items: center;
+    }
+
+    .gallery-link a:hover {
+      text-decoration: underline;
+    }
+
+    .gallery-link svg {
+      margin-left: 8px;
+      width: 16px;
+      height: 16px;
+    }
+
+
+    @media (max-width: 768px) {
+      .gallery-link {
+        font-size: 1rem;
+      }
+    }
+  </style>
+
+  <script type="application/ld+json">
+{
+  "@context": "https://schema.org",
+  "@type": "CollectionPage",
+  "name": "<%= title %>",
+  "description": "<%= description %>",
+  "url": "https://art.pcoloring.com/<%= uri %>",
+  "mainEntity": {
+    "@type": "ItemList",
+    "itemListElement": [
+    <% data.contents.forEach((item, index)=> { %>
+      {
+        "@type": "ListItem",
+        "position": <%= index+1 %>,
+        "item": {
+          "@type": "CreativeWork",
+          "name": "<%= item.title %>",
+          "url": "https://art.pcoloring.com<%= item.uri %>",
+          "image": "https://d2mb6s2cy1zg97.cloudfront.net/thumbs/coloring-page/page/480/<%= item._id %>.webp",
+          "author": {
+            "@type": "Person",
+            "name": "<%= item.user.username %>"
+          }
+        }
+      }<% if(index < data.contents.length - 1){ %>, <%}%>
+    <% }); %>
+    ]
+  }
+}
+</script>
+
+  <script type='text/javascript'
+    src='https://platform-api.sharethis.com/js/sharethis.js#property=685036ce6c1ae8001abaded7&product=sop'
+    async='async'></script>
+</head>
+<!-- Google tag (gtag.js) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=G-JBGGVGLHTP"></script>
+<script>
+  window.dataLayer = window.dataLayer || [];
+  function gtag() { dataLayer.push(arguments); }
+  gtag('js', new Date());
+
+  gtag('config', 'G-JBGGVGLHTP');
+</script>
+
+<body>
+  <%- include('header') %>
+
+    <main class="container">
+
+      <div class="breadcrumb">
+        <a href="/">Home</a> &gt; <a href="/video-coloring-pages">Video Coloring Pages</a> &gt; <%= title%>
+      </div>
+      <section>
+        <h1><%= title%></h1>
+        <h4 style="margin-bottom: 40px;">By Art Number Coloring / <%=data.timeCreate%></h4>
+
+        <div class="video-player" id="storyVideo">
+          <video poster="<%= data.poster %>" controls>
+            <source src="<%= data.url %>" type="application/x-mpegURL">
+            Your browser does not support the video tag.
+          </video>
+          <div class="play-button"></div>
+        </div>
+
+        <div class="coloring-grid">
+          <% data.contents.forEach(item=> { %>
+            <div class="coloring-card">
+              <div data-content-id="<%= item._id %>" class="coloring-image">
+                <a href="<%= item.uri %>"><img src="<%= item.thumb %>" alt="<%= item.title %>"></a>
+              </div>
+              <div class="coloring-content">
+                <div class="coloring-title">
+                  <%= item.title %>
+                </div>
+                <div class="coloring-author">by <a href="/coloring-page-gallery?author=<%= item.user.username %>">
+                    <%= item.user.username %>
+                  </a></div>
+                <div class="coloring-meta">
+                  <div class="date">
+                    <%= item.publishTime %>
+                  </div>
+                  <div class="views">
+                    <%= item.totalStartCount ? item.totalStartCount : 0 %>
+                  </div>
+                </div>
+                <div class="coloring-tags">
+                  <% item.tags.forEach(tag=> { %>
+                    <a href="/coloring-page-gallery?category=<%= tag %>"><span class="tag" data-tag="<%= tag %>">
+                        <%= tag %>
+                      </span></a>
+                    <% }); %>
+                </div>
+              </div>
+            </div>
+            <% }); %>
+        </div>
+
+      </section>
+
+      <%- include('comment') %>
+
+    </main>
+
+    <%- include('footer') %>
+
+    <script src="/scripts/script.js"></script>
+    <script src="/scripts/progress2.js"></script>
+
+    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
+    <script>
+      const storyVideo = document.getElementById('storyVideo');
+      const video = storyVideo.querySelector('video');
+      const playButton = storyVideo.querySelector('.play-button');
+      // 视频播放控制
+      if (Hls.isSupported()) {
+        var hls = new Hls();
+        var url = '<%= data.url %>';
+        hls.loadSource(url);
+        hls.attachMedia(video);
+      } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
+        // Safari 不支持 hls.js 但原生支持 HLS
+        video.src = '<%= data.url %>';
+      } else {
+        // 浏览器不支持 HLS
+        console.error('Your browser does not support HLS.');
+      }
+      
+      playButton.addEventListener('click', () => {
+          video.play();
+          playButton.style.display = 'none';
+      });
+
+      // 视频播放时隐藏play button
+      video.addEventListener('play', () => {
+        playButton.style.display = 'none';
+      });
+
+      // 视频暂停时显示播放按钮
+      video.addEventListener('pause', () => {
+        playButton.style.display = 'flex';
+      });
+      
+      // 视频结束时显示播放按钮
+      video.addEventListener('ended', () => {
+        playButton.style.display = 'flex';
+      });
+      
+  </script>
+
+</body>
+
+</html>