Răsfoiți Sursa

add video-story

guoziyun 1 an în urmă
părinte
comite
df6e421451

+ 9 - 0
config/translate.js

@@ -38,6 +38,14 @@ let homePage = {
   ja: 'ホームページ',
 }
 
+let videoPage = {
+  zh: '涂色视频故事',
+  en: 'Coloring Video Story',
+  es: 'Historia de vídeo para colorear',
+  pt: 'História de vídeo para colorir',
+  ja: '着色動画物語',
+}
+
 let categoryPage = {
   zh: '精选填色页主题',
   en: 'Main Coloring Page Topic',
@@ -506,6 +514,7 @@ let translate = {
   homePage,
   introTitle,
   introText,
+  videoPage,
   categoryPage,
   tagPage,
   tagPageHeading,

+ 1 - 1
dist/stylesheets/header.css

@@ -40,7 +40,7 @@
 .dropdown {
   position: relative;
   display: inline-block;
-  z-index: 1000;
+  z-index: 200;
 }
 
 .dropdown-content {

+ 70 - 16
dist/stylesheets/styles.css

@@ -48,6 +48,15 @@ body {
   box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 卡片阴影 */
 }
 
+.image-card img {
+  width: 100%;
+  height: auto; 
+  object-fit: contain;
+  aspect-ratio: 1;
+  display: block;
+  cursor: pointer;
+}
+
 .card-title {
   white-space: nowrap; /* 禁止文本换行 */
   overflow-x: auto;    /* 允许水平滚动 */
@@ -174,36 +183,60 @@ body {
 
 }
 
-.play-button {
+.video-play-button {
   position: absolute;
-  top: 5px;
-  right: 5px;
+  top: 10px;
+  right: 10px;
 }
 
 
 .popup {
   display: none; /* 默认隐藏 */
   position: fixed;
-  z-index: 1000;
+  z-index: 300;
   left: 0;
   top: 0;
   width: 100%;
   height: 100%;
   overflow: auto;
-  background-color: rgba(0, 0, 0, 0.8);
+  background-color: rgba(0, 0, 0, 0.3);
 }
 
-.popup-content {
-  background-color: #fefefe;
-  margin: 15% auto;
-  padding: 20px;
-  border: 1px solid #888;
-  width: 80%;
-  max-width: 800px;
-  text-align: center;
+.popup-content-wrapper {
+  box-sizing: border-box;
+  height: 90vh;
+  width: auto;
+  aspect-ratio: 18/16;
+  margin: 5vh auto;
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
   position: relative;
 }
 
+.popup-content-left {
+  background-color: #000;
+  height: 90vh;
+  width: auto;
+  aspect-ratio: 9/16;
+  border-top-left-radius: 8px;
+  border-bottom-left-radius: 8px;
+}
+
+.popup-content-right {
+  box-sizing: border-box;
+  padding: 20px;
+  background-color: white;
+  width: auto;
+  height: 90vh;
+  max-width: 480px;
+  max-height: 90vh;
+  aspect-ratio: 9/16;
+  overflow: auto;
+  border-top-right-radius: 8px;     /* 卡片圆角 */
+  border-bottom-right-radius: 8px;
+}
+
 .close {
   color: #aaa;
   position: absolute;
@@ -212,16 +245,37 @@ body {
   font-size: 28px;
   font-weight: bold;
   cursor: pointer;
+  z-index: 1000;
 }
 
 .close:hover,
 .close:focus {
-  color: black;
+  color: white;
   text-decoration: none;
 }
 
 #video-player {
+  box-sizing: border-box;
+  height: 100%;
   width: 100%;
-  max-width: 640px;
-  height: auto;
+}
+
+/* 响应式设计, 如果是手机屏幕 */
+@media (max-width: 768px) {
+  .popup-content-wrapper {
+    flex-direction: column;
+    width: 100%;
+    height: auto;
+    margin: 0 auto;
+    aspect-ratio: null;
+  }
+  .popup-content-left {
+    width: 100%;
+    height: 80vh;
+  }
+  .popup-content-right {
+    width: 100%;
+    max-width: 100%;
+    height: auto;
+  }
 }

+ 27 - 2
routes/index.js

@@ -139,6 +139,7 @@ router.get(/^\/(en|zh|es|pt|ja)$/, function (req, res, next) {  // 限制严格
       // 视频故事
       let videos = await models.ArtVideoStory
         .find({ enabled: true, seoTitle: { $exists: true } })
+        .populate({ path: 'contents', model: 'Art', select: 'name title width height date publishTime tags lastMod publishVersion' })
         .sort({ order: 'asc' })
         .limit(6)
         .lean()
@@ -167,6 +168,17 @@ router.get(/^\/(en|zh|es|pt|ja)$/, function (req, res, next) {  // 限制严格
         } else {
           doc.seoDescription = doc.seoTitle;
         }
+
+        organizeData(doc.contents, lang, imageType);
+
+        let jsonStr = JSON.stringify(doc, (key, value) => {
+          if (key == 'seoTitle' || key == 'seoDescription') {
+            value = value.replace(/"/g, '\\\"');
+          }
+          return value;
+        });
+        console.log(jsonStr);
+        doc.jsonStr = jsonStr;
       }
 
       // 设计师
@@ -759,9 +771,9 @@ router.get('/:lang/videos', function (req, res, next) {
     let cacheKey = `${CACHE_PREFIX}_${imageType}_${req.originalUrl}`;
     let htmlData = await redis.getAsync(cacheKey);
     if (!htmlData) {
-      // 视频故事
-      let vidoes = await models.ArtVideoStory
+      let videos = await models.ArtVideoStory
         .find({ enabled: true, seoTitle: { $exists: true } })
+        .populate({ path: 'contents', model: 'Art', select: 'name title width height date publishTime tags lastMod publishVersion' })
         .sort({ order: 'asc' })
         .lean()
         .exec();
@@ -790,6 +802,19 @@ router.get('/:lang/videos', function (req, res, next) {
         } else {
           doc.seoDescription = doc.seoTitle;
         }
+
+        organizeData(doc.contents, lang, imageType);
+
+        delete doc.seoDescription;
+
+        let jsonStr = JSON.stringify(doc, (key, value) => {
+          if (key == 'seoTitle' || key == 'seoDescription') {
+            value = value.replace(/"/g, '\\\"');
+          }
+          return value;
+        });
+        console.log(jsonStr);
+        doc.jsonStr = jsonStr;
       }
 
       let data = {

+ 8 - 0
service/cron-jobs/open-art.js

@@ -30,6 +30,14 @@ async function run() {
     console.log('progress: ' + Math.floor((100 * done / total)) + '% used time: ' + hour + ':' + minute + ':' + second);
 
   }
+
+  // video story 的内容全部开放
+  docs = await models.Art.find({ open: false, tags: 'video-story' });
+  for (let doc of docs) {
+    console.log(`open video-story art: ${doc._id}`);
+    doc.open = true;
+    await doc.save();
+  }
 }
 
 module.exports = { run }

+ 6 - 0
service/cron-jobs/sitemap.js

@@ -30,6 +30,12 @@ async function generateSitemap() {
     '    <priority>1.0</priority>',
     '  </url>',
     '  <url>',
+    '    <loc>https://art.pcoloring.com/en/videos</loc>',
+    `    <lastmod>${date}</lastmod>`,
+    '    <changefreq>weekly</changefreq>',
+    '    <priority>0.8</priority>',
+    '  </url>',
+    '  <url>',
     '    <loc>https://art.pcoloring.com/en/category</loc>',
     `    <lastmod>${date}</lastmod>`,
     '    <changefreq>daily</changefreq>',

+ 1 - 3
views/common-meta.ejs

@@ -10,7 +10,5 @@
 <link rel="alternate" href="https://art.pcoloring.com<%=uri.replace('/'+lang, '/ja') %>" hrefLang="ja" />
 <link rel="alternate" href="https://art.pcoloring.com<%=uri.replace('/'+lang, '/en') %>" hrefLang="x-default" />
 
-<title>
-  <%= title %>
-</title>
+<title><%= title %></title>
 <meta name="description" content="<%= description %>">

+ 3 - 0
views/header.ejs

@@ -14,6 +14,9 @@
         <a href="/<%= lang %>" class="<%= uri == `/${lang}` ? 'selected' : '' %>">
           <%= translate.homePage[lang] %>
         </a>
+        <a href="/<%= lang %>/videos" class="<%= uri.includes(`/${lang}/videos`) ? 'selected' : '' %>">
+          <%= translate.videoPage[lang] %>
+        </a>
         <a href="/<%= lang %>/category" class="<%= uri.includes(`/${lang}/category`) ? 'selected' : '' %>">
           <%= translate.categoryPage[lang] %>
         </a>

+ 1 - 1
views/index.ejs

@@ -49,7 +49,7 @@
 
 <body>
   <%- include('header') %>
-    <!-- <%- include('intro-section') %> -->
+    <%- include('intro-section') %>
     <%- include('video-story-section') %>
     <%- include('latest-section') %>
     <%- include('album-section') %>

+ 40 - 8
views/video-story-section.ejs

@@ -16,14 +16,11 @@
           <div style="padding: 2px; font-size: 14px; color: grey; text-align: center; white-space: nowrap;">
             <%= translate.videoStory[lang] %>
           </div>
-          <a href="javascript:;" onclick="onPlay('<%= video.url %>')">
+          <a href="javascript:;" onclick="onPlay(`<%= video.url %>`, `<%= video.jsonStr %>`)">
             <img src="<%= video.poster %>" class="album-icon-img" alt="<%= video.seoTitle %>">
-            <img src="/assets/svg/play-button.svg" , class="play-button" width="20px" height="20px"
+            <img src="/assets/svg/play-button.svg" , class="video-play-button" width="20px" height="20px"
               alt="Coloring Page Video Play Button">
           </a>
-          <!-- <video id="video" controls>
-            <source src="<%= video.url %>" type="application/x-mpegURL">
-          </video> -->
         </div>
         <% }); %>
     </div>
@@ -33,14 +30,25 @@
 
 <!-- 弹出层 -->
 <div id="video-popup" class="popup">
-  <div class="popup-content">
+  <div class="popup-content-wrapper">
     <span class="close" onclick="closeVideoPopup()">&times;</span>
-    <video id="video-player" width="400" height="500" controls></video>
+    <div class="popup-content-left">
+      <video id="video-player" controls></video>
+    </div>
+    <div id="video-content" class="popup-content-right">
+    </div>
   </div>
 </div>
 
 <script>
-  function onPlay(url) {
+
+  function onPlay(url, videoJson) {
+    console.log(url);
+    console.log(videoJson);
+
+    var video = JSON.parse(`${videoJson}`);
+    generateVideoContent(video);
+
     var videoPopup = document.getElementById('video-popup');
     var videoPlayer = document.getElementById('video-player');
 
@@ -82,4 +90,28 @@
     }
   }
 
+  function generateVideoContent(video) {
+    var div = document.getElementById('video-content');
+
+    div.innerHTML = `
+      <h1 style="font-size: 16pt; font-weight: bold;">${video.seoTitle}</h1>
+    `;
+
+    var contentHTML = '<div>';
+
+    video.contents.forEach(item => {
+      contentHTML += `
+        <div class="image-card" style="margin-bottom: 10px;">
+          <a href=${item.uri}><img src=${item.thumb} alt='${item.title}'></a>
+          <div class="card-title">${item.title}</div>
+        </div>
+      `
+    });
+
+    contentHTML += '</div>';
+
+    div.innerHTML += contentHTML;
+
+  }
+
 </script>

+ 146 - 0
views/videos.ejs

@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<html lang="<%= lang %>">
+
+<head>
+  <%- include('common-meta') %>
+    <link rel="stylesheet" href="/stylesheets/styles.css">
+    <link rel="stylesheet" href="/stylesheets/header.css">
+</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>
+
+<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
+
+<body>
+  <%- include('header') %>
+
+    <h1 style="display: flex; justify-content: center; padding: 10px; color: purple">
+      <%= title %>
+    </h1>
+    <h2 style="display: flex; justify-content: center; padding: 0px 10px 10px 10px; color: #333">
+      <%= description %>
+    </h2>
+
+    <div class="content">
+      <div class="album-icon-grid">
+        <% data.forEach(video=> { %>
+          <div class="album-grid-card">
+            <div style="padding: 2px; font-size: 14px; color: grey; text-align: center; white-space: nowrap;">
+              <%= translate.videoStory[lang] %>
+            </div>
+            <a href="javascript:;" onclick="onPlay(`<%= video.url %>`, `<%= video.jsonStr %>`)">
+              <img src="<%= video.poster %>" class="album-icon-img" alt="<%= video.seoTitle %>">
+              <img src="/assets/svg/play-button.svg" , class="video-play-button" width="20px" height="20px"
+                alt="Coloring Page Video Play Button">
+            </a>
+          </div>
+          <% }); %>
+      </div>
+    </div>
+
+
+    <!-- 弹出层 -->
+    <div id="video-popup" class="popup">
+      <div class="popup-content-wrapper">
+        <span class="close" onclick="closeVideoPopup()">&times;</span>
+        <div class="popup-content-left">
+          <video id="video-player" controls></video>
+        </div>
+        <div id="video-content" class="popup-content-right">
+        </div>
+      </div>
+    </div>
+
+
+    <script>
+
+      function onPlay(url, videoJson) {
+        // alert(url);
+        // return;
+        console.log(url);
+        console.log(videoJson);
+
+
+
+        var video = JSON.parse(`${videoJson}`);
+        generateVideoContent(video);
+
+        var videoPopup = document.getElementById('video-popup');
+        var videoPlayer = document.getElementById('video-player');
+
+        videoPopup.style.display = 'block';
+
+        if (Hls.isSupported()) {
+          var hls = new Hls();
+          hls.loadSource(url);
+          hls.attachMedia(videoPlayer);
+          hls.on(Hls.Events.MANIFEST_PARSED, function () {
+            videoPlayer.play();
+          });
+        } else if (videoPlayer.canPlayType('application/vnd.apple.mpegurl')) {
+          videoPlayer.src = url;
+          videoPlayer.addEventListener('canplay', function () {
+            videoPlayer.play();
+          });
+        }
+
+        window.closeVideoPopup = function () {
+          videoPopup.style.display = 'none';
+          videoPlayer.pause(); // 停止视频播放
+          videoPlayer.src = ''; // 重置视频源,避免浏览器缓存问题
+        };
+
+        // 点击模态对话框外部时关闭对话框(可选,但推荐添加)
+        videoPopup.addEventListener('click', function (event) {
+          if (event.target === videoPopup) {
+            closeVideoPopup();
+          }
+        });
+
+        // 防止点击关闭按钮时事件冒泡到弹出层导致关闭(因为我们已经为关闭按钮单独绑定了事件)
+        var closeButton = document.querySelector('.close');
+        if (closeButton) {
+          closeButton.addEventListener('click', function (event) {
+            event.stopPropagation();
+          });
+        }
+      }
+
+      function generateVideoContent(video) {
+        var div = document.getElementById('video-content');
+
+        div.innerHTML = `
+          <h1 style="font-size: 16pt; font-weight: bold;">${video.seoTitle}</h1>
+        `;
+
+        var contentHTML = '<div>';
+
+        video.contents.forEach(item => {
+          contentHTML += `
+            <div class="image-card" style="margin-bottom: 10px;">
+              <a href=${item.uri}><img src=${item.thumb} alt='${item.title}'></a>
+              <div class="card-title">${item.title}</div>
+            </div>
+          `
+        });
+
+        contentHTML += '</div>';
+
+        div.innerHTML += contentHTML;
+
+      }
+
+    </script>
+
+
+</body>
+
+</html>