介绍
企业门户网站管理系统客户端
# 企业门户网站管理系统客户端
项目预览:企业门户官网 (opens new window)
项目地址:yexiyue/Portal-management-system-web: 门户网站管理系统C端 (github.com) (opens new window)
# 1.进度条展示
安装依赖
pnpm add nprogress
配合路由守卫进行进度条展示
import { createRouter, createWebHashHistory } from "vue-router";
import HomeVue from "@/views/home/Home.vue";
import NewsVue from "@/views/news/News.vue";
import ProductVue from "@/views/product/Product.vue";
import NotFoundVue from '@/views/notFound/NotFound.vue'
import "nprogress/nprogress.css";
import "nprogress/nprogress.js";
import nProgress from "nprogress";
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
component: HomeVue,
},
{
path: "/news",
component: NewsVue,
},
{
path: "/product",
component: ProductVue,
},
{
path:'/:any*',
component:NotFoundVue
}
],
});
router.beforeEach((to,from,next)=>{
nProgress.start()
next()
})
router.afterEach((to,from)=>{
nProgress.done()
})
export default router;
# 2.NavBar
<template>
<div class="navBar">
<el-menu
:default-active="route.fullPath"
class="el-menu-demo"
mode="horizontal"
router
>
<el-menu-item index="/">首页</el-menu-item>
<el-menu-item index="/news">新闻中心</el-menu-item>
<el-menu-item index="/product">产品与服务</el-menu-item>
<el-menu-item index="" @click="handleLoginClick" >登录</el-menu-item>
</el-menu>
</div>
<div class="title">
企业门户官网
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute } from 'vue-router';
const route=useRoute()
const handleLoginClick=()=>{
window.location.replace('http://localhost:5174')
}
</script>
<style scoped>
.navBar{
position: sticky;
top: 0px;
z-index: 999;
}
.title{
position: fixed;
top: 0;
right: 20px;
z-index: 1000;
line-height: 60px;
font-size: 14px;
}
</style>
# 3.搜索处理
<template>
<div class="container">
<div class="header-bg">
<div class="search">
<el-popover placement="bottom" :visible="visible" title="搜索结果" width="50%">
<template #reference>
<el-input :prefix-icon="Search" type="search" @input="visible = true" @blur="visible = false"
v-model="searchStr" placeholder="请输入新闻标题" class="input-with-select">
</el-input>
</template>
<div v-if="searchList.length !== 0">
<div class="search-item" v-for="item in searchList" :key="item.id">
{{ item.title }}
</div>
</div>
<div v-else>
<el-empty description="亲~暂时没有搜索到该标题" :image-size="50" />
</div>
</el-popover>
</div>
</div>
<div class="topNews">
<el-row :gutter="20">
<el-col style="cursor: pointer;" :span="6" v-for="item in topNewsList" :key="item.id">
<el-card shadow="hover" :body-style="{ padding: '0px', }">
<img :src="item.cover"
class="image" />
<div style="padding: 14px">
<p class="news-text">{{ item.title }}</p>
<div class="bottom">
<time class="time">{{ formatTime(item.updateTime) }}</time>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { Search } from '@element-plus/icons-vue'
import { getNews, type News } from '@/api'
import {formatTime} from '@/hooks/formatTime'
const searchStr = ref('')
const visible = ref(false)
const data = ref<News[]>([])
onMounted(async () => {
data.value = (await getNews()).data
})
const searchList = computed(() => {
return searchStr.value ? data.value.filter(item => item.title.includes(searchStr.value)) : []
})
const topNewsList = computed(() => {
return data.value.slice(0, 4)
})
</script>
<style scoped>
.container {
}
.search {
width: 50%;
position: absolute;
top: 70%;
left: 50%;
transform: translate(-50%, 0);
}
.header-bg {
width: 100%;
height: 400px;
background-image: url('../../assets/2030898.jpg');
background-size: cover;
position: relative;
}
.search-item {
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.search-item:hover {
color: #1296db;
cursor: pointer;
background-color: #ddd;
}
.topNews {
margin: 20px;
padding-bottom: 20px;
}
.time {
font-size: 12px;
color: #999;
}
.bottom {
margin-top: 13px;
line-height: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.button {
padding: 0;
min-height: auto;
}
.image {
width: 100%;
display: block;
object-fit: cover;
}
.news-text{
display: -webkit-box;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
</style>
# 4.新闻标签分类
<template>
<div class="container">
<div class="header-bg">
<div class="search">
<el-popover placement="bottom" :visible="visible" title="搜索结果" width="50%">
<template #reference>
<el-input :prefix-icon="Search" type="search" @input="visible = true" @blur="visible = false"
v-model="searchStr" placeholder="请输入新闻标题" class="input-with-select">
</el-input>
</template>
<div v-if="searchList.length !== 0">
<div @click="toNewsDetail(item.id)" class="search-item" v-for="item in searchList" :key="item.id">
{{ item.title }}
</div>
</div>
<div v-else>
<el-empty description="亲~暂时没有搜索到该标题" :image-size="50" />
</div>
</el-popover>
</div>
</div>
<!-- 顶部最新新闻 -->
<div class="topNews">
<el-row :gutter="20" style="margin-bottom: 20px;">
<el-col style="cursor: pointer;" :span="6" v-for="item in topNewsList" :key="item.id">
<el-card @click="toNewsDetail(item.id)" shadow="hover" :body-style="{ padding: '0px', }">
<img :src="item.cover" class="image" />
<div style="padding: 14px">
<p class="news-text">{{ item.title }}</p>
<div class="bottom">
<time class="time">{{ formatTime(item.updateTime) }}</time>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 标签页分类 -->
<el-tabs v-model="activeName">
<el-tab-pane v-for="item in tabList" :label="item.label" :name="item.name">
<el-row justify="space-between">
<el-col :span="14">
<el-card @click="toNewsDetail(item2.id)" class="tabList-card" v-for="item2 in tabListData[item.name]" shadow="hover"
:body-style="{ padding: '0px', }">
<div class="tabList-card-item">
<img :src="item2.cover" />
<div style="padding: 14px;">
<p class="news-text">{{ item2.title }}</p>
<div class="bottom">
<time class="time">{{ formatTime(item2.updateTime) }}</time>
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<!-- 时间线 -->
<el-timeline style="margin: 20px;">
<el-timeline-item @click="toNewsDetail(item3.id)" v-for="item3 in tabListData[item.name]" :key="item3.id"
:timestamp="formatTime(item3.updateTime)">
{{ item3.title }}
</el-timeline-item>
</el-timeline>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
<el-backtop :right="100" :bottom="100" />
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { Search } from '@element-plus/icons-vue'
import { getNews, type News } from '@/api'
import { formatTime } from '@/hooks/formatTime'
import { useRouter } from 'vue-router';
const searchStr = ref('')
const visible = ref(false)
const data = ref<News[]>([])
const activeName = ref(0)
const router=useRouter()
const toNewsDetail=(id:number)=>{
router.push(`/news/${id}`)
}
onMounted(async () => {
data.value = (await getNews()).data
})
const searchList = computed(() => {
return searchStr.value ? data.value.filter(item => item.title.includes(searchStr.value)) : []
})
const topNewsList = computed(() => {
return data.value.slice(0, 4)
})
const tabListData = computed(() => {
return data.value.reduce((pre, cur) => {
if (cur.category === 1) {
pre[0].push(cur)
} else if (cur.category === 2) {
pre[1].push(cur)
} else {
pre[2].push(cur)
}
return pre
}, <News[][]>[[], [], []])
})
const tabList = [
{
label: '最新动态',
name: 0
},
{
label: '典型案例',
name: 1
},
{
label: '通知公告',
name: 2
}
]
</script>
<style scoped>
.container {}
.search {
width: 50%;
position: absolute;
top: 70%;
left: 50%;
transform: translate(-50%, 0);
}
.header-bg {
width: 100%;
height: 400px;
background-image: url('../../assets/2030898.jpg');
background-size: cover;
position: relative;
}
.search-item {
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.search-item:hover {
color: #1296db;
cursor: pointer;
background-color: #ddd;
}
.topNews {
margin: 20px;
padding-bottom: 20px;
}
.time {
font-size: 12px;
color: #999;
}
.bottom {
margin-top: 13px;
line-height: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.image {
width: 100%;
display: block;
object-fit: cover;
}
.news-text {
display: -webkit-box;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.tabList-card {
margin: 20px;
}
.tabList-card-item {
display: flex;
align-items: center;
}
.tabList-card-item img {
width: 35%;
object-fit: cover;
}
</style>
# 5.当组件没有销毁时监听id请求数据
<script setup lang="ts">
import { getNewsById, type News ,getNewsLast} from '@/api';
import { onMounted, ref, watchEffect,onBeforeUnmount } from 'vue';
import { useRoute } from 'vue-router';
import { formatTime } from '@/hooks/formatTime';
import { StarFilled } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router';
const router=useRouter()
const newsDetail = ref<News>()
const route = useRoute()
const lastNews=ref<News[]>([])
onMounted(async () => {
newsDetail.value = (await getNewsById(+route.params.id)).data
lastNews.value=(await getNewsLast(6)).data
})
const stopHandle=watchEffect(async()=>{
//所以加层判断
if(+route.params.id){
newsDetail.value = (await getNewsById(+route.params.id)).data
}
})
//没啥用,因为当组件销毁时watchEffect先执行
onBeforeUnmount(()=>{
stopHandle()
})
const toNewsDetail=(id:number)=>{
router.push(`/news/${id}`)
}
</script>
# 6.使用自己做的埋点sdk
<template>
<nav-bar></nav-bar>
<router-view></router-view>
</template>
<script setup lang="ts">
import NavBar from './components/NavBar.vue';
import Tracker from 'yexiyue_tracker'
//进行数据上报
new Tracker({
requestUrl:'/adminapi/sdk',
jsError:true,
performanceTracker:true,
historyTracker:true,
domTracker:true
})
</script>