vue3+electron开发一个文章获取工具(附原码)

一、前言

平时在搜索文章时总会有很多的干扰信息影响我们问题的查找,于是,将我比较常用的CSDN、稀土掘金、简书等网站汇总到这个工具中。每次进入程序时默认获取对应网址前几个文章,仅显示标题和详情信息,如果有自己想看的文章,点击标题即可直接跳转到对应的文章详情,也可以根据自己的需求输入关键字查找对应的文章。这样的好处是过滤掉其他无关信息,以下实现代码是根据网址的调用接口开发的,可能会因为网址的变动而导致调用失败。

二、设计思路

  1. 在要查询的网站,按F12打开控制台,查找对应接口的名称及调用参数
  2. nodejs+express编写后端服务,根据接口参数要求写接口函数
  3. vue3+electron搭建前端页面,对接自己搭建的nodejs服务
  4. nodejs调用接口处理响应数据,然后将处理后的数据返回给前端页面展示

三、运行效果

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

四、代码实现

1. 渲染进程

// api.js
import axios from "axios"
const URL = process.env.NODE_ENV === 'production'?"http://127.0.0.1:3050": 'api'
function juejinList(param) {
    return new Promise((reslove, reject) => {
        axios.post(URL + "/juejinlist", param).then((res) => {
            reslove(res)
        })
    })
}
function juejinSearch(param) {
    return new Promise((reslove, reject) => {
        axios.get(params(URL + "/juejinsearch", param)).then((res) => {
            reslove(res)
        })
    })
}
function csdnList() {
    return new Promise((reslove, reject) => {
        axios.get(URL + "/csdnlist").then((res) => {
            reslove(res)
        })
    })
}
function csdnSearch(param) {
    return new Promise((reslove, reject) => {
        axios.get(params(URL + "/csdnsearch", param)).then((res) => {
            reslove(res)
        })
    })
}
function jianshuList(param) {
    return new Promise((reslove, reject) => {
        axios.get(params(URL + "/jianshulist", param)).then((res) => {
            reslove(res)
        })
    })
}
function jianshuSearch(param) {
    return new Promise((reslove, reject) => {
        axios.get(params(URL + "/jianshusearch", param)).then((res) => {
            reslove(res)
        })
    })
}
function params(url, params) {
    if (params) { // 有参数时拼接
        return params ? url + '?' + Object.keys(params)
            .filter(key => params[key] || params[key] === 0)
            .map(key => `${key}=${params[key]}`)
            .toString().replace(/,/g, '&') :
            url
    } else { // 没参数时获取
        let obj = {};
        url.match(/(\w+)=(\w+)/g).forEach(item => {
            Object.assign(obj, {
                [item.split('=')[0]]: item.split('=')[1]
            })
        })
        return obj
    }
}

export default {
    juejinList,
    juejinSearch,
    csdnList,
    csdnSearch,
    jianshuList,
    jianshuSearch
}
<!-- index.vue -->
<div class="container">
  <div class="silder"> <!-- 侧边栏 -->
    <div v-for="(item,index) in data.list" :key="index">
      <el-link
        type="primary"
        :underline="false"
        style="font-size: 14px; margin-top: 10px"
        :style="item.state ? 'font-weight: bold;' : ''"
        @click="handleChose(index)"
      >{{ item.name }}</el-link>
    </div>
  </div>
  <div class="content"> <!-- 滚动栏 -->
    <div class="head">
      <el-input
        style="width: 150px;margin-right:10px;"
        v-model="data.keyword"
        placeholder="请输入关键字"
        size="small"
        :disabled="data.disabled"
        clearable
        @clear="handleChose(data.index)"
      ></el-input>
      <el-button size="small" :disabled="data.disabled" @click="search">搜索</el-button>
    </div>
    <el-scrollbar class="main_content" :height="`${data.height}px`" @scroll="scroll">
      <div id="content" v-loading.fullscreen.lock="fullscreenLoading">
        <div v-for="(item,index) in data.contentList" :key="index">
          <el-collapse v-model="activeNames">
            <el-collapse-item :name="index">
              <template #title>
                <el-link
                  class="link"
                  :underline="false"
                  @click="handleUrl(item.url)"
                  v-html="item.title"
                ></el-link>
              </template>
              <div style="margin-right: 10px" v-html="item.describe"></div>
              <div v-if="item.time">{{ item.time }}</div>
              <div v-if="item.tag" class="label">
                <div v-for="(val, key) in item.tag" :key="key">
                  <el-tag style="margin:5px 10px 0 0;">{{ val }}</el-tag>
                </div>
              </div>
            </el-collapse-item>
          </el-collapse>
        </div>
      </div>
    </el-scrollbar>
  </div>
</div>
// index.js
import api from "../api";
import { ipcRenderer } from "electron";
import { onMounted, reactive, ref } from "vue";
const data = reactive({
  list: [
    {name: "稀土掘金",state: false},
    {name: "CSDN",state: false},
    {name: "简书",state: false}
  ],
  contentList: [], // 主内容列表
  curPlatform: "", // 当前平台
  index: 0, // 平台索引
  height: 500,
  keyword: "",
  disabled: false
});
var param = {
  juejin: {
    id_type: 2,
    sort_type: 300,
    cate_id: "6809637767543259144",
    cursor: "0",
    limit: 20,
    search: {
      query: "",
      cursor: 0
    }
  },
  csdn: {
    search: {
      q: "",
      t: "all",
      p: 1
    }
  },
  jianshu: {
    page: 1,
    type_id: 31,
    count: 10,
    search: {
      q: "",
      page: 1,
      type: "note",
      order_by: "default"
    }
  }
};
const fullscreenLoading = ref(false)
onMounted(() => {
  data.list[data.index].state = true;
  data.curPlatform = data.list[data.index].name;
  data.height = window.innerHeight - 90;
  window.onresize = function() {
    data.height = window.innerHeight - 90;
  };
  getjuejinList();
});
// 平台切换
function handleChose(index) {
  data.list.forEach(item => {
    item.state = false;
  });
  data.list[index].state = true;
  data.curPlatform = data.list[index].name;
  data.index = index;
  switch (data.curPlatform) {
    case "稀土掘金":
      data.disabled = false
      param.juejin.cursor = "0";
      data.contentList = [];
      getjuejinList();
      break;
    case "CSDN":
      data.disabled = false
      data.contentList = [];
      getcsdnList();
      break;
    case "简书":
      data.disabled = false
      param.jianshu.page = 1;
      data.contentList = [];
      getjianshuList();
      break;
  }
}
// 滚动加载
function scroll(e) {
  let num = document.querySelector("#content").clientHeight - document.querySelector(".main_content").clientHeight;
  if (Math.floor(e.scrollTop - num) == 44) {
    switch (data.curPlatform) {
      case "稀土掘金":
        if (data.keyword) {
          param.juejin.search.cursor += 20;
          juejinSearch();
        } else {
          param.juejin.cursor = "0";
          getjuejinList();
        }
        break;
      case "CSDN":
        if (data.keyword) {
          param.csdn.search.p += 1;
          csdnSearch();
        } else {
          getcsdnList();
        }
        break;
      case "简书":
        if (data.keyword) {
          param.jianshu.search.page += 1;
          jianSearch();
        } else {
          getjianshuList();
        }
        break;
    }
  }
}
function handleUrl(url) {
  ipcRenderer.send("openUrl", url);
}
function getjuejinList() {
  fullscreenLoading.value = true
  api.juejinList(param.juejin).then(res => {
    data.contentList = [...data.contentList, ...res.data.list];
    param.juejin.cursor = res.data.param;
    fullscreenLoading.value = false
  });
}
function juejinSearch() {
  fullscreenLoading.value = true
  api.juejinSearch(param.juejin.search).then(res => {
    data.contentList = [...data.contentList, ...res.data.list];
    fullscreenLoading.value = false
  });
}
function getcsdnList() {
  fullscreenLoading.value = true
  api.csdnList().then(res => {
    data.contentList = [...data.contentList, ...res.data.list];
    fullscreenLoading.value = false
  });
}
function csdnSearch() {
  fullscreenLoading.value = true
  api.csdnSearch(param.csdn.search).then(res => {
    data.contentList = [...data.contentList, ...res.data.list];
    fullscreenLoading.value = false
  });
}
function getjianshuList() {
  fullscreenLoading.value = true
  api.jianshuList(param.jianshu).then(res => {
    data.contentList = [...data.contentList, ...res.data.list];
    fullscreenLoading.value = false
  });
}
function jianSearch() {
  fullscreenLoading.value = true
  api.jianshuSearch(param.jianshu.search).then(res => {
    data.contentList = [...data.contentList, ...res.data.list];
    fullscreenLoading.value = false
  });
}
function search() {
  switch (data.curPlatform) {
    case "稀土掘金":
      param.juejin.search.cursor = 0;
      param.juejin.search.query = data.keyword;
      data.contentList = [];
      juejinSearch();
      break;
    case "CSDN":
      param.csdn.search.p = 1;
      param.csdn.search.q = data.keyword;
      data.contentList = [];
      csdnSearch();
      break;
    case "简书":
      param.jianshu.search.page = 1;
      param.jianshu.search.q = data.keyword;
      data.contentList = [];
      jianSearch();
      break;
  }
}
/* index.css */
.container {
  display: flex;
}
.silder {
  border-right: 1px solid #dddddd;
  width: 10%;
}
.content {
  width: 90%;
}
.head {
  margin: 0 10px;
  padding: 10px 0;
  display: flex;
  align-items: center;
}
.main_content {
  margin-left: 10px;
  padding-right: 10px;
}
.link {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.label {
  display: flex;
}

2. 主进程

// service.js
import express from 'express'
import axios from 'axios'
import dayjs from 'dayjs'

function params(url, params) {
    if (params) { // 有参数时拼接
        return params ? url + '?' + Object.keys(params)
            .filter(key => params[key] || params[key] === 0)
            .map(key => `${key}=${params[key]}`)
            .toString().replace(/,/g, '&') :
            url
    } else { // 没参数时获取
        let obj = {};
        url.match(/(\w+)=(\w+)/g).forEach(item => {
            Object.assign(obj, {
                [item.split('=')[0]]: item.split('=')[1]
            })
        })
        return obj
    }
}

export default function initService() {
    const app = express()
    const port = 3050

    // 获取掘金最新列表
    app.post("/juejinlist", (req, res) => {
        let body = ''
        req.on('data', (thunk) => {
            body += thunk
        })
        req.on('end', () => {
            axios.post(params("https://api.juejin.cn/recommend_api/v1/article/recommend_cate_feed", {
                aid: 2608,
                uuid: "7236633682860246562",
                spider: 0
            }), JSON.parse(body)).then((result) => {
                console.log("yes")
                let list = result.data.data.map((item) => {
                    return {
                        id: item.article_id, // 文章id
                        url: "https://juejin.cn/post/" + item.article_id, // 文章链接
                        title: item.article_info.title, // 文章标题
                        describe: item.article_info.brief_content, // 文章描述
                        time: dayjs.unix(item.article_info.mtime).format('YYYY-MM-DD HH:mm:ss'), // 发布时间
                        tag: item.tags.map(value => value.tag_name) // 文章标签
                    }
                })
                res.send({
                    list,
                    param: result.data.cursor
                })
            }).catch((err) => {
                // console.log(err)
            })
        })
    })

    // 关键字查询
    app.get("/juejinsearch", (req, res) => {
        axios.get(params("https://api.juejin.cn/search_api/v1/search", {
            aid: 2608,
            uuid: "7236633682860246562",
            spider: 0,
            id_type: 0,
            limit: 20,
            search_type: 0,
            sort_type: 0,
            version: 1,
            ...params(req.url)
        })).then((result) => {
            console.log("yes")
            let list = result.data.data.map((item) => {
                return {
                    id: item.result_model.article_id, // 文章id
                    url: "https://juejin.cn/post/" + item.result_model.article_id, // 文章链接
                    title: item.result_model.article_info.title, // 文章标题
                    describe: item.result_model.article_info.brief_content, // 文章描述
                    time: dayjs.unix(item.result_model.article_info.mtime).format('YYYY-MM-DD HH:mm:ss'), // 发布时间
                    tag: item.result_model.tags.map(value => value.tag_name) // 文章标签
                }
            })
            res.send({
                list,
                param: ""
            })
        }).catch((err) => {
            // console.log(err)
        })
    })

    // 获取csdn最新列表
    app.get("/csdnlist", (req, res) => {
        axios.get(params("https://cms-api.csdn.net/v1/web_home/select_content", {
            componentIds: "www-recomend-community",
            cate1: "web"
        })).then((result) => {
            console.log("yes")
            let list = result.data.data["www-recomend-community"].info.map((item) => {
                return {
                    id: item.extend.product_id, // 文章id
                    url: item.extend.url, // 文章链接
                    title: item.extend.title, // 文章标题
                    describe: item.extend.desc, // 文章描述
                    time: item.extend.created_at, // 发布时间
                    tag: item.extend.csdnTag // 文章标签
                }
            })
            res.send({
                list,
                param: ""
            })
        }).catch((err) => {
            // console.log(err)
        })
    })

    // 关键字查询
    app.get("/csdnsearch", (req, res) => {
        axios.get(params("https://so.csdn.net/api/v3/search", {
            ...params(req.url)
        })).then((result) => {
            console.log("yes")
            let list = result.data.result_vos.map((item) => {
                return {
                    id: item.id, // 文章id
                    url: item.url, // 文章链接
                    title: item.title, // 文章标题
                    describe: item.description, // 文章描述
                    time: item.created_at, // 发布时间
                    tag: item.search_tag // 文章标签
                }
            })
            res.send({
                list,
                param: ""
            })
        }).catch((err) => {
            // console.log(err)
        })
    })

    // 获取简书最新列表
    app.get("/jianshulist", (req, res) => {
        axios.get(params("https://www.jianshu.com/programmers", params(req.url))).then((result) => {
            console.log("yes")
            let list = result.data.map((item) => {
                return {
                    id: item.id, // 文章id
                    url: "https://www.jianshu.com/p/" + item.slug, // 文章链接
                    title: item.title, // 文章标题
                    describe: item.desc, // 文章描述
                    time: "", // 发布时间
                    tag: "" // 文章标签
                }
            })
            res.send({
                list,
                param: ""
            })
        }).catch((err) => {
            // console.log(err)
        })
    })

    // 关键字查询
    app.get("/jianshusearch", (req, res) => {
        axios.post(params("https://www.jianshu.com/search/do", params(req.url)),{},{
            headers: {
                "Accept": "*/*"
            }
        }).then((result) => {
            console.log("yes")
            let list = result.data.entries.map((item) => {
                return {
                    id: item.id, // 文章id
                    url: "https://www.jianshu.com/p/" + item.slug, // 文章链接
                    title: item.title, // 文章标题
                    describe: item.content, // 文章描述
                    time: dayjs(item.first_shared_at).format('YYYY-MM-DD HH:mm:ss'), // 发布时间
                    tag: "" // 文章标签
                }
            })
            res.send({
                list,
                param: ""
            })
        }).catch((err) => {
            // console.log(err)
        })
    })

    app.listen(port, () => {
        console.log(`http://127.0.0.1:${port}`)
    })
}

在本地配置代理路径

// vue.config.js
devServer: {
  proxy: {
    '/api': {
      target: "http://127.0.0.1:3050",
      changeOrigin: true,
      pathRewrite: {
        '^/api': ''
      }
    }
  }
}
// index.js
import {
  ipcMain,
  shell
} from 'electron'
import initService from './service'
ipcMain.on("openUrl", (e, url) => {
    shell.openExternal(url)
})
initService()

五、结尾

目前项目只实现了文章搜索和关键字查询的功能,大家可以根据自己的想法扩充查询的范围,也可以自定义开发些的筛选功能以满足自己的需求。

首先,VueElectron是两个不同的技术,Vue是一种用于构建用户界面的JavaScript框架,而Electron是一种用于构建跨平台桌面应用程序的框架,它使用Web技术(HTML,CSS和JavaScript)来构建应用程序。 如果你是0基础入门,首先需要学习VueElectron的基础知识。以下是一些学习资源: Vue: - Vue官方文档:https://v3.vuejs.org/guide/introduction.html - Vue Mastery:https://www.vuemastery.com/ - Codecademy:https://www.codecademy.com/learn/learn-vue Electron: - Electron官方文档:https://www.electronjs.org/docs - Udemy:https://www.udemy.com/course/electron-from-scratch/ - Pluralsight:https://www.pluralsight.com/courses/electron-fundamentals 一旦你学习了VueElectron的基础知识,你可以开始构建你的第一个Vue3+Electron桌面应用程序。以下是一些步骤: 1. 安装Vue CLI和Electron:使用npm安装Vue CLI和Electron。 ``` npm install -g @vue/cli npm install -g electron ``` 2. 创建Vue项目:使用Vue CLI创建一个新的Vue项目。 ``` vue create my-electron-app ``` 3. 添加Electron支持:在Vue项目中安装electron-builder。 ``` cd my-electron-app vue add electron-builder ``` 4. 编写代码:你现在可以开始编写VueElectron代码了。你可以在Vue组件中使用Electron API来访问系统资源。你可以在Electron主进程中编写Node.js代码以访问底层系统资源。 以上是一个简单的介绍,希望对你有所帮助。祝你好运!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值