IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Ghost博客实现豆瓣观影清单

    1900发表于 2022-10-15 23:13:46
    love 0

    早期实现

    豆瓣观影清单、书架的功能之前也做过,当时用过好几个方案

    1. 当时找的一个教程,不过不记得出处了,不过我写了一篇日志记录这个事情,里面记录了实现过程,原理其实很现在这个类似
    2. 另外一个是当时一个叫mufeng还是布克牧为的大佬搞了一个douban的数据站,可以缓存你豆瓣的观影数据,不过好像也停止服务了

    这两个方案都或多或少的出现问题了,我后来懒也就一直没有修过,所以之前导航上一直没有书架和豆瓣的链接。

    其他方案

    但是在空着的这段时间我有时候也找过解决方案,有一次在木木木木木 大佬的博客里发现了一个hugo的实现方案,功能非常齐全,可以实现清单的分类筛选、时间筛选、评分筛选、排序等功能。

    当时一下就心动了。

    alt

    大佬博客也有一篇日志提到了这个功能是谁写的,并且附上了链接。

    来自于 @怡红公子 的自制轮子:doumark-action ,豆瓣书影音同步 GitHub Action。

    我当时尝试着弄了一下,虽然成功通过doumark-action 缓存了我的豆瓣数据,但是后续的页面渲染把我卡住了,hugo的模板渲染用到了一些hugo特有的函数,我当时也看不太懂,所以就暂时搁置了。

    后来有些时候想起了会忽然研究一下怎么处理,但是一直没落实代码实现上面。


    不过今天终于下定决心要把这个功能给实现出来。

    仔细研究了hugo的渲染代码,我发现其实前端的渲染完全可以用我目前渲染Memos的方式实现,只需要把数据处理成和hugo用到的格式就行了。

    idtitlesubtitleposterpubdateurlratinggenresstarcommenttagsstar_time
    3469612151杰伊·比姆2021 / 印度 / 剧情 犯罪 / 塔·塞·纳纳维尔 / 苏利耶·西瓦库马 Lijo Mol Josehttps://img9.doubanio.com/view/photo/m_ratio_poster/public/p2734251414.jpg2021-11-02(印度)https://movie.douban.com/subject/35652715/8.7剧情,犯罪5这,如此显然的诬陷,竟然盛行!2022-08-23 10:12:03

    后来我又研究了一下doumark-action,发现可以把数据缓存成JSON,而且因为GitHub又raw格式的链接,可以提供外链。所以我就灵机一动:我可不可前端请求JSON数据,然后把JSON转换成hugo渲染用到的那种格式?

    经过测试后发现确实可以实现数据转换,所以就有了今天这个方案。

    var temp = { movies : [] }
    $.getJSON('https://jsd.cdn.zzko.cn/gh/rebron1900/doumark-action@master/data/douban/movie.json',function(r){
        
        $(r).each(function(){
            temp.movies.push({id : this.id, 
                              title : this.subject.title, 
                              subtitle : this.subject.card_subtitle, 
                              poster: this.subject.pic.large,
                              pubdate: this.subject.pubdate[0],
                              url: this.subject.url,
                              rating: this.subject.rating.value,
                              genres: this.subject.genres.join(','),
                              star: this.subject.rating.star,
                              comment: this.comment,
                              tags: this.tags.join(','),
                              star_time: this.create_time
                             })
        })
        
    })

    实现开始

    缓存豆瓣数据

    这里我就不写了,引用@怡红公子 大佬的教程,唯一的区别是我更改了 format 为 json 。

    使用其实很简单,在你的博客仓库中新建 .github/workflows/douban.yml 文件,以观影为例添加如下内容。它实现了每小时自动抓取你的豆瓣观影记录并更新到文件中,如果发现文件有更新则触发 commit 提交。

    name: douban
    on: 
      schedule:
      - cron: "30 * * * *"
    
    jobs:
      douban:
        name: Douban mark data sync
        runs-on: ubuntu-latest
        steps:
        - name: Checkout
          uses: actions/checkout@v2
    
        - name: movie
          uses: lizheming/doumark-action@master
          with:
            id: lizheming
            type: movie
            format: json
            dir: ./douban
    
        - name: Commit
          uses: EndBug/add-and-commit@v8
          with:
            message: 'chore: update douban data'
            add: './douban'
    

    该 workflow 总共分为三步,

    • 第一步初始化 Git 仓库;
    • 第二步调用 doumark-action 同步豆瓣账号 lizheming 的 movie 类型数据到 ./douban 文件夹下,并保存为 json 格式文件;
    • 最后一步则是当 ./douban 文件夹下有更新则调用插件提交修改。

    转化数据

    完整代码在下面

    1. 在你的主题里添加一个新页面 page-movies.hbs 并添加页面常规代码
    2. 这里我添加了一个主题自定义判断,如果设置了json数据文件的地址才做数据转换
    3. 定义一个临时变量 temp 用于存放最终转换后的数据
    4. 通过JQuery getJSON 方法请求json文件。
    5. 通过 each 方法遍历数据,并从JSON数据里拿去到想要的数据,按hugo那边的格式组成 JSON 对象,并存在变量 temp.movies 里。
    6. 因为取到的分类数据是 剧情,动画 这种带 , 号分隔符的,而之后筛选需要用到单独标签名称,所以需要做下处理,我这里重新遍历了 movies 对象,如果有多个标签的则用 split 做分割,并且通过 indexOf 避免重复,如果有了就不再重复添加。
    7. 另外这里也顺带处理了一下 year 字段,原因和分类一样,之后筛选要用到,所以要去重复,并且做了截取处理。
    8. 通过javascript-template来渲染数据
    {{#if @custom.douban_movie}}
    		<script>
    		$(document).ready(function () {
    			var temp = { movies : [] }
    			$.getJSON('{{ @custom.douban_movie }}',function(r){
    				$(r).each(function(){
    					temp.movies.push({id : this.id, 
    									title : this.subject.title, 
    									subtitle : this.subject.card_subtitle, 
    									poster: this.subject.pic.large,
    									pubdate: this.subject.pubdate[0],
    									url: this.subject.url,
    									rating: this.subject.rating.value,
    									genres: this.subject.genres.join(','),
    									star: this.subject.rating.star_count,
    									comment: this.comment,
    									tags: this.tags.join(','),
    									star_time: this.create_time
    									})
    				})
    				$('.movies').html(tmpl('tmpl-movies', temp))
    				var gtemp = [];
    				var gyear = [];
    				$(temp.movies).each(function(){ 
    					t = this.genres.split(',');
    					if(t.length > 0){
    						$(t).each(function(i,d){
    							if(gtemp.indexOf(d) == -1){ gtemp.push(d) }
    						});
    					}else{
    					gtemp.push(this.genres) }
    
    					var tyear = this.subtitle.substring(0,4);
    					if(gyear.indexOf(tyear) == -1 ){
    						gyear.push(tyear)
    					}
    				});
    				$('.genres').html(tmpl('tmpl-genres', gtemp))
    				$('.fyear').html(tmpl('tmpl-fyear', gyear.sort(function(a,b){ return b-a }	)))
    			})
    			
    		});
    		</script>
    	{{/if}}
    page-movies.hbs script部分代码

    添加样式和模板代码

    在 page-movies.hbs 中添加样式代码,这里的代码我都是直接复制hugo实现那边的,可以完美使用,不需要做任何改动。

    <!-- 样式代码 -->
    <style>
        .gFnzgG,.gFnzgG *{box-sizing:border-box}
        .fIuTG{display:flex;flex-wrap:wrap;margin:0 -2%;background:0 0;line-height:100%;justify-content: center;}
        .dfdORB{width:150px;margin:0 2% 30px;padding:0;font-size:15px}
        .dfdORB a{text-decoration:none}
        .kMthTr{margin-top:12px;line-height:1.3;max-height:2.6rem;overflow:hidden}
        .eysHZq{display:flex;-webkit-box-align:center;align-items:center;margin-top:5px;min-height:16px;line-height:1;-webkit-box-align: center;}
        .HPRth{position:relative;min-height:87px;overflow:hidden;color:transparent;width: 150px;height: 230px;}
        .HPRth:hover{box-shadow:rgb(48 55 66 / 30%) 0 1rem 2.1rem}
        .jcTaHb{display:flex;-webkit-box-align:center;align-items:center}
        .lhtmRw{margin-right:1px;width:12px;height:12px;color:#fccd59}
        .gaztka{margin-right:1px;width:12px;height:12px;color:#eee}
        .iibjPt{margin-left:5px;color:#555;font-size:14px}
        .jvCTkj{margin-bottom:10px}
        .kEoOHR{display:inline-block;margin-right:15px;text-decoration:none;color:#157efb}
        .dvtjjf{display:inline-block;color:#555;text-decoration:none;padding:0 5px}
        .dvtjjf.active{background:rgba(85,85,85,.1)}
        .hide{display:none}
        .sort-by{text-align:right;margin-top:-15px}
        .sort-by-item{margin-left:10px;padding:0 5px;line-height:20px;text-decoration: none;color: var(--color-content-secondary);}
        .sort-by-item.active{background:rgba(85,85,85,.1)}
        .sort-by-item svg{margin-right:5px}
        .sc-hKFxyN img{max-width:100%!important;height:100%!important;display:block!important;vertical-align:middle!important;margin: 0;padding: 0;}
        .lazyload-wrapper {height: 100%;}
        @media(min-width:1024px){
            .lg\:col-span-6{grid-column:span 6/span 6!important}
            .lg\:col-start-2{grid-column-start:2!important}
        }
        @media (max-width:550px){
            .jcTaHb,.sc-bdnxRM{display:none}
        }
    </style>
    <!-- 筛选功能用到的js -->
    <script type="text/javascript">
        function search(e) {
            document.querySelectorAll('.dfdORB').forEach(item => item.classList.add('hide'));
            document.querySelector(`.dvtjjf.active[data-search="${e.target.dataset.search}"]`)?.classList.remove('active');
            if(e.target.dataset.value) {
                e.target.classList.add('active');
            }
            const searchItems = document.querySelectorAll('.dvtjjf.active');
            const attributes = Array.from(searchItems, searchItem => {
                const property = `data-${searchItem.dataset.search}`;
                const logic = searchItem.dataset.method === 'contain' ? '*' : '^';
                const value = searchItem.dataset.method === 'contain' ? `${searchItem.dataset.value}` : searchItem.dataset.value;
                return `[${property}${logic}='${value}']`;
            });
            const selector = `.dfdORB${attributes.join('')}`;
            document.querySelectorAll(selector).forEach(item => item.classList.remove('hide'));
        }
        window.addEventListener('click', function(e) {
            if(e.target.classList.contains('sc-gtsrHT')) {
                e.preventDefault();
                search(e);
            }
        });
        function sort(e) {
            const sortBy = e.target.dataset.order;
            const style = document.createElement('style');
            style.classList.add('sort-order-style');
            document.querySelector('style.sort-order-style')?.remove();
            document.querySelector('.sort-by-item.active')?.classList.remove('active');
            e.target.classList.add('active');
            if(sortBy === 'rating') {
                const movies = Array.from(document.querySelectorAll('.dfdORB'));
                movies.sort((movieA, movieB) => {
                    const ratingA = parseFloat(movieA.dataset.rating) || 0;
                    const ratingB = parseFloat(movieB.dataset.rating) || 0;
                    if(ratingA === ratingB) {
                        return 0;
                    }
                    return ratingA > ratingB ? -1 : 1;
                });
                const stylesheet = movies.map((movie, idx) => `.dfdORB[data-rating="${movie.dataset.rating}"] { order: ${idx}; }`).join('\r\n');
                style.innerHTML = stylesheet;
                document.body.appendChild(style);
            }
        }
        window.addEventListener('click', function(e) {
            if(e.target.classList.contains('sort-by-item')) {
                e.preventDefault();
                sort(e);
            }
        });
    </script>
    样式代码

    渲染筛选器和电影信息

    这里我是按着hugo的代码逻辑,用tmpl做了实现,都是输出数据而已,就不细说了。其中 year 部分做了一下空值判断。

    	<script type="text/x-tmpl" id="tmpl-movies">
    		{% for (var i=0; i<o.movies.length; i++) { %}
    		<div 
    			class="sc-gKAaRy dfdORB hint--top hint--medium" 
    			data-year="{%= typeof(o.movies[i].pubdate) == "undefined" ? "":o.movies[i].pubdate.substring(0,4) %}" 
    			data-star="{%= o.movies[i].star %}"
    			data-rating="{%= o.movies[i].rating %}"
    			data-genres="{%= o.movies[i].genres %}"  
    			aria-label="{%= o.movies[i].comment %}" 
    		>
    			<a rel="link" href="{%= o.movies[i].url %}" target="_blank">
    			<div class="sc-hKFxyN HPRth">
    				<div class="lazyload-wrapper ">
    				<img class="avatar" src="{%= o.movies[i].poster %}" referrer-policy="no-referrer" loading="lazy" alt="{%= o.movies[i].title %}" width="150" height="220">
    				</div>
    			</div>
    			<div class="sc-iCoGMd kMthTr">{%= o.movies[i].title %}</div>
    			<div class="sc-fujyAs eysHZq">
    				<span class="sc-jSFjdj jcTaHb">
    
    					{% for (var b=0; b<5; b++) { %}
    					<svg viewBox="0 0 24 24" width="24" height="24" class="sc-dlnjwi {% if (b < o.movies[i].star) { %} lhtmRw {% }else{ %} gaztka {% } %} ">
    						<path fill="none" d="M0 0h24v24H0z"></path>
    						<path fill="currentColor" d="M12 18.26l-7.053 3.948 1.575-7.928L.587 8.792l8.027-.952L12 .5l3.386 7.34 8.027.952-5.935 5.488 1.575 7.928z"></path>
    					</svg>
    					{% } %}
    				</span>
    				<span class="sc-pNWdM iibjPt">{%= o.movies[i].rating %}</span>
    			</div>
    			</a>
    		</div>
    		{% } %}
    
    	</script>
    	<script type="text/x-tmpl" id="tmpl-genres">
    		<a href="javascript:void 0;" class="sc-gtsrHT kEoOHR" data-search="genres" data-method="contain" data-value="">全部</a>
    		{% for (var i=0; i<o.length; i++) { %}
    		<a href="javascript:void 0;" class="sc-gtsrHT dvtjjf" data-search="genres" data-method="contain" data-value="{%= o[i] %}">{%= o[i] %}</a>
    		{% } %}
    	</script>
    	<script type="text/x-tmpl" id="tmpl-fyear">
    		<a href="javascript:void 0;" class="sc-gtsrHT kEoOHR" data-search="year" data-method="equal" data-value="">全部</a>
    		{% for (var i=0; i<o.length; i++) { %}
    		<a href="javascript:void 0;" class="sc-gtsrHT dvtjjf" data-search="year" data-method="equal" data-value="{%= o[i] %}">{%= o[i] %}</a>
    		{% } %}
    	</script>
    

    完整代码请查看Github,或者查看 page-movies.hbs

    https://github.com/rebron1900/attila/blob/master/page-movies.hbs



沪ICP备19023445号-2号
友情链接