在探索、学习,最重要的是,玩 Vue.js 的过程中,我一直在构建不同类型的应用程序,以此来练习并提高我的使用技巧。几周前,我读到关于 关闭 Digg 的 RSS 阅读器,虽然存在很棒的替代方案,但我认为用 Vue 构建自己的阅读器会很有趣。在这篇文章中,我将解释我是如何将其组合在一起的,以及它有什么问题。我知道在开始构建时需要做出一些妥协,所以计划是在后续文章中使用更漂亮的版本来跟进此版本。
文章系列
- 设置和第一次迭代 (本文)
- 改进和最终版本
让我们首先看看这个应用程序并解释各个组件。
查看应用程序
打开应用程序时,会显示一些基本说明和添加新 RSS 提要的提示。

单击按钮将打开一个模态窗口,允许您输入提要

添加按钮后,将显示该提要的博客条目

注意颜色。我将其设置成每个提要都有一个独特的颜色,以便更容易区分不同网站的内容。例如,以下是添加更多提要后的外观。

左侧的面板允许您通过单击提要进行筛选。不幸的是,您还无法删除提要,因此,如果您需要删除某些内容,则需要打开 DevTools 并编辑缓存的值。
让我们回顾一下技术栈!
组件
首先是 Vue 库本身。我*没有*为此应用程序使用 webpack——只是一个简单的脚本包含,没有构建过程。
该 UI 是全部 Vuetify,一个非常棒的材质设计框架,易于使用。我还在学习它,所以可以肯定地说我的设计可以更好,尽管我现在对它的外观非常满意。
持久性通过localStorage
完成。我存储从 RSS 提要中检索到的提要元数据。这通常包括网站名称、主要 URL 和描述等信息。我没有存储提要项目,这意味着每次加载站点时,我都会重新获取项目。下一个版本将使用 IndexedDB 在本地保留项目。
那么,我如何加载提要信息呢?我可以直接向 URL 发出网络请求,但大多数 RSS 提要都没有使用 CORS,这意味着浏览器将被阻止加载它。为了解决这个问题,我使用 Webtask编写了一个快速无服务器函数。它既处理创建对 CORS 友好的端点,也处理将提要的 XML 解析成友好的 JSON。
现在我已经介绍了应用程序的各个部分,让我们开始查看代码吧!
布局
让我们从布局开始。正如我所说,我正在使用 Vuetify 作为 UI。我一开始使用的是 黑暗示例布局。这就是创建用于菜单的标题、页脚和左侧列的方式。

我使用了 卡片组件 用于单个提要项目。我对这里的布局不太满意。例如,我还没有呈现发布日期,因为我很难找到一种好的呈现方式。我决定简单地跳过它,等到下一个版本,我们将在**本系列的第 2 部分**中看到它。
与其一次性将所有源代码都放到您面前,不如让我们看看各个部分。首先,这是在任何提要添加之前显示的介绍/帮助文本
<div v-if="showIntro">
<p>
Welcome to the RSS Reader, a simple way to manage RSS feeds and read content. To begin using the RSS Reader, add your first feed by clicking the button below.
</p>
<p>
<v-btn color="primary" large @click="addFeed">
<v-icon>add</v-icon>
Add Feed
</v-btn>
</p>
</div>
当您拥有提要时,项目将显示为卡片列表
<v-container fluid grid-list-lg>
<v-layout row wrap>
<v-flex xs12 v-for="item in items">
<v-card :color="item.feedColor">
<v-card-title primary-title>
<div class="headline">{{item.title}}</div>
</v-card-title>
<v-card-text>
{{item.content | maxText }}
</v-card-text>
<v-card-actions>
<v-btn flat target="_new" :href="item.link">Read on {{item.feedTitle}}</v-btn>
</v-card-actions>
</v-card>
</v-flex>
</v-layout>
</v-container>
请注意,用于阅读提要项目的按钮使用target
在新标签页中打开它。
为了显示提要,我使用了一个列表组件
<v-list dense>
<v-list-tile @click="allFeeds">
<v-list-tile-action>
<v-icon>dashboard</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>All Feeds</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile @click="filterFeed(feed)" v-for="feed in feeds" :value="feed == selectedFeed">
<v-list-tile-action>
<v-icon :color="feed.color">bookmark</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>{{ feed.title }} </v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile @click="addFeed">
<v-list-tile-action>
<v-icon>add</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>Add Feed</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
最后,这是模态布局
<v-dialog v-model="addFeedDialog" max-width="500px">
<v-card>
<v-card-title>Add Feed</v-card-title>
<v-card-text>
Add the RSS URL for a feed below, or the URL for the site and I'll try to
auto-discover the RSS feed.
<v-text-field v-model="addURL" label="URL" :error="urlError"
:rules="urlRules"></v-text-field>
</v-card-text>
<v-card-actions>
<v-btn color="primary" @click.stop="addFeedAction">Add</v-btn>
<v-btn color="primary" flat @click.stop="addFeedDialog=false">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
逻辑
现在到了有趣的部分——JavaScript!和之前一样,我不会一次性把整个文件都放到您面前。相反,让我们一点一点地解决它。
在启动时,我加载可能已定义的任何现有提要,然后根据需要显示介绍文本
created() {
this.restoreFeeds();
if (this.feeds.length === 0) this.showIntro = true;
},
restoreFeeds
方法处理检查 LocalStorage 中是否存在现有提要。
restoreFeeds() {
let feeds = localStorage.getItem('feeds');
if (feeds) {
this.feeds = JSON.parse(feeds);
this.feeds.forEach((feed,idx) => {
feed.color = colors[idx % (colors.length-1)];
this.loadFeed(feed);
});
}
},
请注意,此方法处理分配颜色(这是一个简单的数组),然后加载提要数据。
说到这一点,我如何处理加载 RSS 信息?目前有两种情况会发生这种情况。第一种是当您最初添加提要时,第二种是当您重新加载应用程序并且提要已定义时。在这两种情况下,我都调用一个 URL——使用 Webtask 定义的无服务器任务。此任务将返回所有内容——有关提要的元数据和项目本身。我仅在第一次调用时关心元数据,理论上,我可以通过在服务器端删除元数据并将其剔除来使代码更快一些,但这似乎不值得付出努力。
该无服务器函数非常简单
'use strict';
const Parser = require('rss-parser');
const parser = new Parser();
module.exports = function(context, cb) {
let url = '';
if(context.body && context.body.url) url = context.body.url;
if(context.query && context.query.url) url = context.query.url;
if(url === '') cb(new Error('URL parameter not passed.'));
console.log('gonna parse '+url);
parser.parseURL(url)
.then(feed => {
console.log(feed);
cb(null, {feed:feed});
})
.catch(e => {
cb(e);
});
}
我在这里所做的只是包装 npm 包 rss-parser,它为我处理所有转换工作。您在开头看到的if
语句处理查找url
参数。当调用我的 webtask 时,我可以传递查询字符串变量或将其作为 HTTP 主体的一部分发送。无论哪种方式,我都只是使用rss-parser
模块并返回结果。
此函数的端点为
https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.sandbox.auth0-extend.com/getRss
欢迎您自己尝试。您可以在处理添加提要的方法中看到这一点
addFeedAction() {
this.urlError = false;
this.urlRules = [];
//first, see if new
if(this.feeds.findIndex((feed) => {
return (feed.rsslink === this.addURL);
}) >= 0) {
this.urlError = true;
this.urlRules = ["URL already exists."];
return;
} else {
fetch(rssAPI+encodeURIComponent(this.addURL))
.then(res => res.json())
.then(res => {
// ok for now, assume no error, cuz awesome
this.addURL = '';
//assign a color first
res.feed.color = colors[this.feeds.length % (colors.length-1)];
// ok, add the items (but we append the url as a fk so we can filter later)
res.feed.items.forEach(item => {
item.feedPk = this.addURL;
item.feedColor = res.feed.color;
this.allItems.push(item);
});
// delete items
delete res.feed.items;
// add the original rss link
res.feed.rsslink = this.addURL;
this.feeds.push(res.feed);
this.addFeedDialog = false;
//always hide intro
this.showIntro = false;
//persist the feed, but not the items
this.storeFeeds();
});
}
},
此方法首先检查提要是否已存在,如果不存在,则会访问无服务器端点以获取详细信息。当存储项目时,我有一些数据重复。我不想将项目存储在提要对象“下”,而是使用全局 Vue 数据值allItems
。因此,我将提要标识符和颜色复制到每个项目中。这样做的目的是为了便于以后进行项目显示和筛选。这对我来说感觉“不对”,但同样,这是我的第一个草稿。我正在为项目使用计算属性,您可以在此处看到逻辑
items:function() {
if(this.allItems.length === 0) return [];
// filter
let items = [];
if(this.selectedFeed) {
console.log('filtered');
items = this.allItems.filter(item => {
return item.feedPk == this.selectedFeed.rsslink;
});
} else {
items = this.allItems;
}
items = items.sort((a, b) => {
return new Date(b.isoDate) - new Date(a.isoDate);
});
return items;
}
现在看来,我可以从每个提要中收集
我的项目,而不是存储一个全局数组,尽管如果需要,我以后可以解决这个问题。我喜欢 Vue 为我提供了解决此类问题的选择。
下一步是什么?
当我开始撰写这篇文章时,我明确地认为*这* *是*第一个草稿。我已经在这里和那里指出了我喜欢和不喜欢的东西,但对于下一个版本,我到底计划做什么?
- 我想将所有数据访问移动到 Vuex 中。Vuex 被描述为 Vue 的“状态管理模式 + 库”。如果您对此不太理解,请不要担心。一开始我也不知道这意味着什么。对我来说,Vuex 提供了一种以封装方式处理更复杂数据的方法。随着您开始构建更多需要共享数据的组件,这一点变得更加重要。
- 说到组件,我应该考虑将“项目”制作成一个合适的 Vue 组件。这是一个简单的胜利。
- 我想开始将提要项目存储在 IndexedDB 中,以便您在打开应用程序时立即获得内容。这将使应用程序的性能大大提高,并提供基本的离线支持。当然,如果您离线,则无法阅读完整的条目,但至少可以提供*某些*内容。
- ……以及您提出的任何建议!查看代码,随时提出建议并指出错误!
敬请期待第二篇文章!
喜欢这篇文章。我目前也在重新设计我的 RSS 阅读器,它使用 Vuex 和 Vuejs 制作。 https://github.com/mrgodhani/rss-reader/tree/newversion