1.项目案例-头条新闻

topnews.png

1.1.数据接口

聚合数据:https://www.juhe.cn/

申请 “新闻头条” 数据接口(不可直接跨域访问)

注意:按照聚合数据网站的规定,进行注册以及实名认证,然后申请 “新闻头条” 数据接口。

1.2.工程目录结构

p10_01.png

1.3.package.json文件

  1. {
  2. "name": "topnews",
  3. "version": "0.1.0",
  4. "private": true,
  5. "scripts": {
  6. "serve": "vue-cli-service serve",
  7. "build": "vue-cli-service build"
  8. },
  9. "dependencies": {
  10. "axios": "^0.26.1",
  11. "core-js": "^3.8.3",
  12. "vue": "^3.2.13",
  13. "vue-router": "^4.0.3"
  14. },
  15. "devDependencies": {
  16. "@vue/cli-plugin-babel": "~5.0.0",
  17. "@vue/cli-plugin-router": "~5.0.0",
  18. "@vue/cli-service": "~5.0.0"
  19. },
  20. "browserslist": [
  21. "> 1%",
  22. "last 2 versions",
  23. "not dead",
  24. "not ie 11"
  25. ]
  26. }

1.4.vue.config.js而文件

  1. const { defineConfig } = require('@vue/cli-service')
  2. module.exports = defineConfig({
  3. transpileDependencies: true,
  4. devServer: {
  5. port: 8080,
  6. proxy: {
  7. '/juheNews': {
  8. target: 'http://v.juhe.cn/toutiao/index', //需要跨域的url
  9. ws: true, //代理webSocket
  10. changeOrigin: true, //允许跨域
  11. pathRewrite: {
  12. '^/juheNews': '' //重写路径
  13. }
  14. }
  15. }
  16. }
  17. })

1.5.main.js文件

main.js文件没有改变

  1. import { createApp } from 'vue'
  2. import App from './App.vue'
  3. import router from './router'
  4. createApp(App).use(router).mount('#app')

1.6.路由配置文件

  1. import { createRouter, createWebHashHistory } from 'vue-router'
  2. import TopNews from '../views/TopNews.vue'
  3. import TypeNews from '../views/TypeNews.vue'
  4. const routes = [{
  5. path: '/',
  6. name: 'Home',
  7. component: TopNews
  8. }, {
  9. path: '/topNews',
  10. name: 'TopNews',
  11. component: TopNews
  12. }, {
  13. path: '/typeNews',
  14. name: 'TypeNews',
  15. component: TypeNews
  16. }]
  17. const router = createRouter({
  18. history: createWebHashHistory(),
  19. routes
  20. })
  21. export default router

1.7.Vue文件

1.7.1.App.vue文件

  1. <template>
  2. <div id="app">
  3. <header>新闻头条</header>
  4. <nav>
  5. <ul>
  6. <li
  7. :class="{ navinit: isActive == 'topNews' }"
  8. @click="changeNav('topNews')"
  9. >
  10. 头条新闻
  11. </li>
  12. <li
  13. :class="{ navinit: isActive == 'typeNews' }"
  14. @click="changeNav('typeNews')"
  15. >
  16. 分类新闻
  17. </li>
  18. </ul>
  19. </nav>
  20. <router-view />
  21. </div>
  22. </template>
  23. <script>
  24. import { reactive, toRefs } from "vue";
  25. import { useRouter } from "vue-router";
  26. export default {
  27. setup() {
  28. //初始化路由组件
  29. const router = useRouter();
  30. const state = reactive({
  31. isActive: "topNews",
  32. });
  33. const path = location.href.substring(location.href.lastIndexOf("/") + 1);
  34. state.isActive = path == "" ? "topNews" : path;
  35. function changeNav(param) {
  36. state.isActive = param;
  37. if (param == "topNews") {
  38. router.push("/topNews");
  39. } else if (param == "typeNews") {
  40. router.push("/typeNews");
  41. }
  42. }
  43. return {
  44. ...toRefs(state),
  45. changeNav,
  46. };
  47. },
  48. };
  49. </script>
  50. <style>
  51. /******************** css reset ********************/
  52. html,
  53. body,
  54. div,
  55. header,
  56. nav,
  57. h1,
  58. h2,
  59. h3,
  60. h4,
  61. h5,
  62. h6,
  63. ul,
  64. li {
  65. margin: 0;
  66. padding: 0;
  67. font-family: "微软雅黑";
  68. }
  69. ul {
  70. list-style: none;
  71. }
  72. a {
  73. text-decoration: none;
  74. }
  75. header {
  76. width: 100%;
  77. height: 48px;
  78. background-color: #e03d3e;
  79. display: flex;
  80. justify-content: center;
  81. align-items: center;
  82. font-size: 20px;
  83. color: #fff;
  84. /*设置字间距*/
  85. letter-spacing: 4px;
  86. }
  87. nav {
  88. width: 100%;
  89. height: 56px;
  90. display: flex;
  91. justify-content: center;
  92. align-items: center;
  93. }
  94. nav ul {
  95. width: 160px;
  96. height: 26px;
  97. display: flex;
  98. justify-content: space-between;
  99. }
  100. nav ul li {
  101. width: 70px;
  102. height: 26px;
  103. display: flex;
  104. justify-content: center;
  105. align-items: center;
  106. }
  107. .navinit {
  108. color: #e03d3e;
  109. border-bottom: solid 2px #e03d3e;
  110. }
  111. </style>

1.7.2.NewsList.vue共通组件

<template>
  <div>
    <ul>
      <li
        v-for="item in newsArr"
        @click="toNews(item.url)"
        :key="item.uniquekey"
      >
        <div class="img-box">
          <img :src="item.thumbnail_pic_s" />
        </div>
        <div class="text-box">
          <h3>{{ item.title }}</h3>
          <p>{{ item.author_name }} {{ item.date }}</p>
        </div>
      </li>
    </ul>
  </div>
</template>

<script>
import { reactive, toRefs, computed } from "vue";

export default {
  props: {
    data: Array,
  },
  setup(props) {
    const state = reactive({
      //将父组件传过来的数据中的title进行在加工处理
      newsArr: computed(() => {
        let arr = props.data;
        for (let i = 0; i < arr.length; i++) {
          if (arr[i].title.length > 24) {
            arr[i].title = arr[i].title.substr(0, 24) + "...";
          }
        }
        return arr;
      }),
    });

    function toNews(url) {
      location.href = url;
    }

    return {
      ...toRefs(state),
      toNews,
    };
  },
};
</script>

<style scoped>
ul {
  width: 100%;
}
ul li {
  box-sizing: border-box;
  padding: 6px;
  width: 100%;
  height: 93px;

  display: flex;
  border-bottom: dashed 1px #aaa;

  user-select: none;
  cursor: pointer;
}
ul li .img-box {
  flex: 0 0 100px;
  height: 80px;
}
ul li .img-box img {
  width: 100px;
  height: 80px;
}
ul li .text-box {
  flex: 1;
  box-sizing: border-box;
  padding-left: 10px;
}
ul li .text-box h3 {
  font-size: 16px;
  font-weight: 300;
}
ul li .text-box p {
  font-size: 14px;
  text-align: right;
}
</style>

1.7.3.TopNews.vue组件

<template>
  <div>
    <img src="../assets/logo.png" />
    <NewsList :data="newsList"></NewsList>
  </div>
</template>

<script>
import { reactive, toRefs } from "vue";
import NewsList from "../components/NewsList";
import axios from "axios";

export default {
  setup() {
    const state = reactive({
      newsList: [],
    });

    const init = () => {
      axios
        .get("/juheNews/toutiao/index", {
          params: {
            type: "top",
            key: "655962c80fdaccf03709b567da3bc795",
          },
        })
        .then((response) => {
          state.newsList = response.data.result.data;
        })
        .catch((error) => {
          console.log(error);
        });
    };

    init();

    return {
      ...toRefs(state),
    };
  },
  components: {
    NewsList,
  },
};
</script>

<style scoped>
img {
  width: 100%;
  height: 100px;
  display: block;
}
</style>

1.7.4.TypeNews组件

<template>
  <div>
    <div class="news-img">
      <a :href="news.url">
        <img :src="news.thumbnail_pic_s" />
      </a>
    </div>
    <div class="type-news">
      <ul>
        <li
          v-for="item in typeList"
          :key="item.id"
          :class="{ typeinit: isAlive == item.id }"
          @click="change(item.id)"
        >
          {{ item.name }}
        </li>
      </ul>
    </div>
    <NewsList :data="newsList"></NewsList>
  </div>
</template>

<script>
import { reactive, toRefs } from "vue";
import NewsList from "../components/NewsList";
import axios from "axios";

export default {
  setup() {
    const state = reactive({
      typeList: [
        { id: "guonei", name: "国内" },
        { id: "guoji", name: "国际" },
        { id: "yule", name: "娱乐" },
        { id: "tiyu", name: "体育" },
        { id: "junshi", name: "军事" },
        { id: "keji", name: "科技" },
        { id: "caijing", name: "财经" },
        { id: "youxi", name: "游戏" },
        { id: "qiche", name: "汽车" },
        { id: "jiankang", name: "健康" },
      ],
      isAlive: "guonei",
      newsList: [],
      news: {},
    });

    getNews("guonei");

    function getNews(type) {
      axios
        .get("/juheNews/toutiao/index", {
          params: {
            type: type,
            key: "655962c80fdaccf03709b567da3bc795",
          },
        })
        .then((response) => {
          state.newsList = response.data.result.data;
          state.news =
            state.newsList[Math.floor(Math.random() * state.newsList.length)];
        })
        .catch((error) => {
          console.log(error);
        });
    }

    function change(id) {
      state.isAlive = id;
      getNews(id);
    }

    return {
      ...toRefs(state),
      change,
    };
  },
  components: {
    NewsList,
  },
};
</script>

<style scoped>
.news-img img {
  width: 100%;
  height: 200px;
  display: block;
}
.type-news {
  width: 100%;
  margin-top: 8px;
}
.type-news ul {
  width: 100%;

  display: flex;
  justify-content: center;
  flex-wrap: wrap;
}
.type-news ul li {
  box-sizing: border-box;
  width: 48px;
  height: 22px;
  border: solid 1px #e03d3e;
  border-radius: 11px;
  margin: 5px 10px;

  font-size: 14px;
  color: #e03d3e;

  display: flex;
  justify-content: center;
  align-items: center;
}

.typeinit {
  background-color: #e03d3e;
  color: #fff !important; /*!important:将优先级提升最高*/
}
</style>

1.8.Vue文件(setup语法糖方式)

下面使用setup语法糖的形式,重写这四个组件。

1.8.1.App.vue文件

<template>
  <div id="app">
    <header>新闻头条</header>
    <nav>
      <ul>
        <li
          :class="{ navinit: isActive == 'topNews' }"
          @click="changeNav('topNews')"
        >
          头条新闻
        </li>
        <li
          :class="{ navinit: isActive == 'typeNews' }"
          @click="changeNav('typeNews')"
        >
          分类新闻
        </li>
      </ul>
    </nav>
    <router-view />
  </div>
</template>

<script setup>
import { ref } from "vue";
import { useRouter } from "vue-router";

const router = useRouter();

const isActive = ref("topNews");

const path = location.href.substring(location.href.lastIndexOf("/") + 1);
isActive.value = path == "" ? "topNews" : path;

const changeNav = (param) => {
  isActive.value = param;
  if (param == "topNews") {
    router.push("/topNews");
  } else if (param == "typeNews") {
    router.push("/typeNews");
  }
};
</script>

<style>
/******************** css reset ********************/
html,
body,
div,
header,
nav,
h1,
h2,
h3,
h4,
h5,
h6,
ul,
li {
  margin: 0;
  padding: 0;
  font-family: "微软雅黑";
}
ul {
  list-style: none;
}
a {
  text-decoration: none;
}

header {
  width: 100%;
  height: 48px;
  background-color: #e03d3e;

  display: flex;
  justify-content: center;
  align-items: center;

  font-size: 20px;
  color: #fff;
  /*设置字间距*/
  letter-spacing: 4px;
}
nav {
  width: 100%;
  height: 56px;
  display: flex;
  justify-content: center;
  align-items: center;
}
nav ul {
  width: 160px;
  height: 26px;

  display: flex;
  justify-content: space-between;
}
nav ul li {
  width: 70px;
  height: 26px;

  display: flex;
  justify-content: center;
  align-items: center;
}
.navinit {
  color: #e03d3e;
  border-bottom: solid 2px #e03d3e;
}
</style>

1.8.2.NewsList.vue共通组件

<template>
  <div>
    <ul>
      <li
        v-for="item in newsArr"
        @click="toNews(item.url)"
        :key="item.uniquekey"
      >
        <div class="img-box">
          <img :src="item.thumbnail_pic_s" />
        </div>
        <div class="text-box">
          <h3>{{ item.title }}</h3>
          <p>{{ item.author_name }} {{ item.date }}</p>
        </div>
      </li>
    </ul>
  </div>
</template>

<script setup>
import { reactive, computed } from "vue";

const myProps = defineProps({
  data: Array,
});

const newsArr = reactive(
  computed(() => {
    let arr = myProps.data;
    for (let i = 0; i < arr.length; i++) {
      if (arr[i].title.length > 24) {
        arr[i].title = arr[i].title.substr(0, 24) + "...";
      }
    }
    return arr;
  })
);

const toNews = (url) => {
  location.href = url;
}
</script>

<style scoped>
ul {
  width: 100%;
}
ul li {
  box-sizing: border-box;
  padding: 6px;
  width: 100%;
  height: 93px;

  display: flex;
  border-bottom: dashed 1px #aaa;

  user-select: none;
  cursor: pointer;
}
ul li .img-box {
  flex: 0 0 100px;
  height: 80px;
}
ul li .img-box img {
  width: 100px;
  height: 80px;
}
ul li .text-box {
  flex: 1;
  box-sizing: border-box;
  padding-left: 10px;
}
ul li .text-box h3 {
  font-size: 16px;
  font-weight: 300;
}
ul li .text-box p {
  font-size: 14px;
  text-align: right;
}
</style>

1.8.3.TopNews.vue组件

<template>
  <div>
    <img src="../assets/logo.png" />
    <NewsList :data="newsList"></NewsList>
  </div>
</template>

<script setup>
import { ref } from "vue";
import NewsList from "../components/NewsList";
import axios from "axios";

const newsList = ref([]);

axios
  .get("/juheNews?type=top&key=655962c80fdaccf03709b567da3bc795")
  .then((response) => {
    newsList.value = response.data.result.data;
  })
  .catch((error) => {
    console.log(error);
  });
</script>

<style scoped>
img {
  width: 100%;
  height: 100px;
  display: block;
}
</style>

1.8.4.TypeNews组件

<template>
  <div>
    <div class="news-img">
      <a :href="news.url">
        <img :src="news.thumbnail_pic_s" />
      </a>
    </div>
    <div class="type-news">
      <ul>
        <li
          v-for="item in typeList"
          :key="item.id"
          :class="{ typeinit: isAlive == item.id }"
          @click="change(item.id)"
        >
          {{ item.name }}
        </li>
      </ul>
    </div>
    <NewsList :data="newsList"></NewsList>
  </div>
</template>

<script setup>
import { reactive, ref } from "vue";
import NewsList from "../components/NewsList";
import axios from "axios";

const typeList = [
  { id: "guonei", name: "国内" },
  { id: "guoji", name: "国际" },
  { id: "yule", name: "娱乐" },
  { id: "tiyu", name: "体育" },
  { id: "junshi", name: "军事" },
  { id: "keji", name: "科技" },
  { id: "caijing", name: "财经" },
  { id: "youxi", name: "游戏" },
  { id: "qiche", name: "汽车" },
  { id: "jiankang", name: "健康" },
];

const isAlive = ref("guonei");

const newsList = ref([]);

const news = ref({});

const getNews = (type) => {
  axios
    .get("/juheNews", {
      params: {
        type: type,
        key: "655962c80fdaccf03709b567da3bc795",
      },
    })
    .then((response) => {
      newsList.value = response.data.result.data;
      news.value = newsList.value[Math.floor(Math.random() * newsList.value.length)];
    })
    .catch((error) => {
      console.log(error);
    });
}

getNews("guonei");

const change = (id) => {
  isAlive.value = id;
  getNews(id);
}
</script>

<style scoped>
.news-img img {
  width: 100%;
  height: 200px;
  display: block;
}
.type-news {
  width: 100%;
  margin-top: 8px;
}
.type-news ul {
  width: 100%;

  display: flex;
  justify-content: center;
  flex-wrap: wrap;
}
.type-news ul li {
  box-sizing: border-box;
  width: 48px;
  height: 22px;
  border: solid 1px #e03d3e;
  border-radius: 11px;
  margin: 5px 10px;

  font-size: 14px;
  color: #e03d3e;

  display: flex;
  justify-content: center;
  align-items: center;
}

.typeinit {
  background-color: #e03d3e;
  color: #fff !important; /*!important:将优先级提升最高*/
}
</style>