feat: 添加登录验证和实时数据更新功能

- 在HeaderView组件中添加登录验证逻辑,未登录时触发onShow事件
- 实现TodayNewsView组件的实时数据更新,每5秒刷新一次新闻计数
- 重构TopNewsView组件为轮播展示模式,添加点击跳转和登录验证
- 在realtimeInfo页面中统一处理各组件的登录弹窗事件
- 删除无用的RadarView组件文件
- 重构topNews页面,实现完整的新闻列表展示和日历筛选功能
- 修复分享存储中的appId空值问题
- 将sessionStorage改为localStorage以增强数据持久性
This commit is contained in:
傅光孟 2026-02-04 13:10:15 +08:00
parent dba108f0e1
commit 4be150d344
8 changed files with 435 additions and 203 deletions

View File

@ -60,7 +60,13 @@ const userInfos = computed(() => {
return userStore.getUserInfos();
});
const emit = defineEmits(["onShow"]);
const goto = (path: string, auth: string) => {
if (!userStore.isLogin) {
emit("onShow");
return;
}
if (userInfos.value.auth.includes(auth)) {
uni.navigateTo({
url: path,

View File

@ -4,13 +4,33 @@
</view>
</template>
<script setup lang='ts'>
import { ref } from 'vue';
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from "vue";
import InfoSummary from "@/components/InfoSummary.vue"; //
import { getNews_cnt_d } from "@/api/newsInfo";
const newsNum = ref(40);
const newsNum = ref(0);
//
async function getNews_cnt_dFn() {
let temp = await getNews_cnt_d({});
newsNum.value = temp[0].value;
}
const timer = setInterval(() => {
getNews_cnt_dFn();
}, 5000);
onMounted(() => {
getNews_cnt_dFn();
});
onBeforeUnmount(() => {
clearInterval(timer);
});
</script>
<style scoped lang='scss'>
<style scoped lang="scss">
.info-section {
margin: 0 20rpx 20rpx;
}

View File

@ -1,6 +1,6 @@
<template>
<view class="top-section">
<view class="top-section-news">
<view class="top-section-news" @click="navigateTo">
<view class="top-section-img">
<image
class="image"
@ -8,18 +8,84 @@
src="@/assets/images/page/icon_smart@2x.png"
></image>
</view>
<view class="news-title">
<view class="num">1</view>
<view class="text"
>小米将于6月26日发布新款YU7车型上市小米将于6月26日发布新款YU7车型上市</view
<swiper
class="swiper-box"
circular
:indicator-dots="false"
:autoplay="true"
:vertical="true"
:disable-touch="true"
:interval="3000"
:duration="500"
>
<swiper-item
class="swiper-item"
v-for="(item, index) in newsListAll"
:key="item.news_id"
>
<view class="news-item">
<view class="num">{{ index + 1 }}</view>
<view class="title">{{ item.title }}</view>
</view>
</swiper-item>
</swiper>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { getTopNews } from "@/api/newsInfo";
import dayjs from "dayjs";
import { onMounted, reactive, ref } from "vue";
import { useUserStore } from "@/stores/user";
const emit = defineEmits(["onShow"]);
const userStore = useUserStore();
//
const loading = ref(false);
const newsListAll = ref([]); //
const limit_num = ref(5);
//
const formatDateStart = "YYYY-MM-DD 00:00:00";
const formatDateEnd = "YYYY-MM-DD 23:59:59";
//
const chooseDate = reactive({
startDate: dayjs().format(formatDateStart),
endDate: dayjs().format(formatDateEnd),
});
async function getNewsList() {
const params = {
start_date: chooseDate.startDate,
end_date: chooseDate.endDate,
limit_num: limit_num.value,
};
loading.value = true;
try {
const result = await getTopNews(params);
newsListAll.value = result;
} catch (e) {
console.log(e);
} finally {
loading.value = false;
}
}
//
const navigateTo = () => {
if (!userStore.isLogin) {
emit("onShow");
return;
}
uni.navigateTo({
url: "/pages/topNews/index",
});
};
onMounted(() => {
getNewsList();
});
</script>
<style scoped lang="scss">
@ -29,12 +95,15 @@ import { ref } from "vue";
background: linear-gradient(90deg, #fce8ea 0%, #ffffff 27.03%, #ffffff 100%);
box-shadow: inset 0px 1rpx 0px 0px #ffffff;
border-radius: 20rpx;
box-sizing: border-box;
.top-section-news {
display: flex;
justify-content: flex-start;
align-items: center;
height: 100%;
padding: 12rpx 20rpx;
box-sizing: border-box;
.top-section-img {
width: 83rpx;
@ -47,8 +116,18 @@ import { ref } from "vue";
height: 100%;
}
}
}
.news-title {
.swiper-box {
width: 100%;
height: 100%;
.swiper-item {
display: flex;
align-items: center;
}
.news-item {
display: flex;
align-items: center;
padding-left: 22rpx;
@ -66,7 +145,7 @@ import { ref } from "vue";
font-size: 30rpx;
color: #ffffff;
}
.text {
.title {
width: 488rpx;
font-family: "PingFangSC, PingFang SC";
font-weight: 500;

View File

@ -2,23 +2,23 @@
<!-- 容器 -->
<view class="container">
<!-- 头部 start -->
<HeaderView />
<HeaderView @on-show="handleShowLogin" />
<!-- 头部 end -->
<!-- 海外独家 start -->
<BannerNewsView />
<BannerNewsView @on-show="handleShowLogin" />
<!-- 海外独家 end -->
<!-- 资讯榜 start -->
<TopNewsView />
<TopNewsView @on-show="handleShowLogin" />
<!-- 资讯榜 end -->
<!-- 热门行业 start -->
<HotNewsView />
<HotNewsView @on-show="handleShowLogin" />
<!-- 热门行业 end -->
<!-- 风口概念 start -->
<ConceptNewsView />
<ConceptNewsView @on-show="handleShowLogin" />
<!-- 风口概念 end -->
<!-- 今日资讯 start -->
@ -50,7 +50,6 @@ import ConceptNewsView from "./components/ConceptNewsView/index.vue";
import TodayNewsView from "./components/TodayNewsView/index.vue";
import FooterView from "./components/FooterView/index.vue";
import LoginDialog from "./components/LoginDialog/index.vue";
import { Session } from "@/utils/storage";
import { useUserStore } from "@/stores/user";
const userStore = useUserStore();
@ -58,6 +57,11 @@ const userStore = useUserStore();
//
const LoginShow = ref(false);
//
const handleShowLogin = () => {
LoginShow.value = true;
};
//
const handleLoginCancel = () => {
LoginShow.value = false;
@ -71,20 +75,9 @@ const handleLoginError = () => {
console.log("登录失败");
};
//
const handleToDetail = (item) => {
if (userStore.isLogin) {
uni.navigateTo({
url: `/pages/detail/indexNewsInfo?id=${item.news_id}`,
});
} else {
LoginShow.value = true;
}
};
onMounted(async () => {
if (!userStore.isLogin) {
LoginShow.value = true;
handleShowLogin();
}
const { aplus_queue } = window;

View File

@ -1,116 +0,0 @@
<template>
<view class="radar-container">
<view class="title">24h AI追踪海外资讯</view>
<div ref="chatRef" class="chat-box"></div>
</view>
</template>
<script setup lang="ts">
import { nextTick, onMounted, ref, watch } from "vue";
import * as echarts from "echarts";
const chatRef = ref(null);
let myChart = null;
//
const initChart = () => {
myChart = echarts.init(chatRef.value);
let option = {
gird: {
show: true,
top: 50,
left: 50,
right: 50,
},
color: ["#4681FF"],
radar: {
// shape: 'circle',
indicator: [
{ name: "资本市场相关", max: 6500 },
{ name: "资讯质量", max: 6000 },
{ name: "媒体影响力", max: 30000 },
{ name: "行业导向", max: 38000 },
{ name: "历史溯洄", max: 52000 },
{ name: "话题性", max: 30000 },
{ name: "风险性", max: 6500 },
{ name: "地缘影响", max: 6500 },
{ name: "产业政策", max: 6500 },
{ name: "潜在信号", max: 6500 },
],
axisName: {
color: '#666666',
fontSize: 12,
fontWeight: 400,
},
axisLine: {
show: true,
lineStyle: {
color: 'rgba(234, 234, 236, 1)'
}
}
},
series: [
{
name: "1111",
type: "radar",
areaStyle: { color: "rgba(62, 122, 252, 0.20)" },
data: [
{
value: [4200, 3000, 20000, 35000, 50000, 18000, 4200, 3000,4200, 3000],
name: "AI追踪海外资讯",
},
],
},
],
};
//使
myChart.setOption(option);
};
watch(() => chatRef.value, () => {
initChart()
})
// onMounted(async () => {
// await nextTick()
// await initChart();
// })
</script>
<style scoped lang="scss">
.radar-container {
height: 600rpx;
margin: 38rpx 20rpx 20rpx;
padding-top: 39rpx;
background-image: linear-gradient( 180deg, rgba(194,214,253,0.96) 0%, #FFFFFF 50%);
box-shadow: inset 0px 1px 0px 0px #ffffff;
border-radius: 20px;
backdrop-filter: blur(10px);
box-sizing: border-box;
.title {
width: 329rpx;
padding: 14rpx 30rpx;
margin: 0 auto;
background: #ffffff;
border-radius: 32rpx;
border: 1px solid #e7e7e7;
font-family: "PingFangSC, PingFang SC";
font-weight: 500;
font-size: 26rpx;
color: #666666;
line-height: 38rpx;
text-align: center;
box-sizing: border-box;
}
.chat-box{
width: 100%;
height: calc(100% - 120rpx);
margin-top: 20rpx;
}
}
</style>

View File

@ -10,76 +10,263 @@
<!-- 导航栏 end -->
<!-- 24小时榜 start -->
<RadarView />
<view class="radar-container">
<view class="title">24h AI追踪海外资讯</view>
<div ref="chatRef" class="chat-box"></div>
<view
style="
display: flex;
align-items: center;
justify-content: flex-end;
margin-right: 15rpx;
font-size: 30rpx;
gap: 10rpx;
margin-top: 20rpx;
"
@click="showCalendar"
>
{{ chooseDate.startDate.split(" ")[0] }}
<u-icon name="calendar" size="26" style="margin-right: 10rpx"></u-icon>
</view>
</view>
<!-- 24小时榜 end -->
<view class="page-main blur">
<view class="news-list-top3">
<view class="news-item">
<view class="news-no"> 1 </view>
<view v-if="!loading" :class="['page-main ', { blur: !userStore.isLogin }]">
<view class="news-list-top">
<view class="news-item" v-for="(item, index) in newsListTop" :key="item.news_id">
<view class="news-no"> {{ index + 1 }} </view>
<view class="news-info">
<view class="news-title">
<view class="title">三星生物风险投资基金将投资拓济医药</view>
<view class="score">98.52</view>
<view class="title">{{ item.title }}</view>
<view class="score">{{ item.news_score }}</view>
</view>
<view class="news-cont">
三星集团宣布其生物风险基金将投资中国生物技术公司phronline
Biopharma以扩大其抗体药物偶联物A ...
{{ item.summary }}
</view>
<view class="source">
<view class="t-1">中国证券报</view>
<view class="t-2">2025-06-23 11:51</view>
</view>
</view>
</view>
<view class="news-item">
<view class="news-no"> 1 </view>
<view class="news-info">
<view class="news-title">
<view class="title">三星生物风险投资基金将投资拓济医药</view>
<view class="score">98.5</view>
</view>
<view class="news-cont">
三星集团宣布其生物风险基金将投资中国生物技术公司phronline
Biopharma以扩大其抗体药物偶联物A ...
</view>
<view class="source">
<view class="t-1">中国证券报</view>
<view class="t-2">2025-06-23 11:51</view>
<view class="t-1">{{ item.source }}</view>
<view class="t-2">{{
dayjs(item.publish_time).format("YYYY-MM-DD hh:mm:ss")
}}</view>
</view>
</view>
</view>
</view>
<view class="news-list">
<view class="news-item">
<view class="news-no"> 1 </view>
<view class="news-item" v-for="(item, index) in newsList" :key="item.news_id">
<view class="news-no"> {{ index + 1 + topNum }} </view>
<view class="news-title">
三星生物风险投资基金将投资拓济医药三星生物风险投资基金将投资拓济医药
{{ item.title }}
</view>
<view class="news-score">98.5</view>
<view class="news-score">{{ item.news_score }}</view>
</view>
<view class="news-item">
<view class="news-no"> 1 </view>
<view class="news-title">
三星生物风险投资基金将投资拓济医药三星生物风险投资基金将投资拓济医药
</view>
<view class="news-score">98.5</view>
</view>
<view class="news-item">
<view class="news-no"> 1 </view>
<view class="news-title">
三星生物风险投资基金将投资拓济医药三星生物风险投资基金将投资拓济医药
</view>
<view class="news-score">98.5</view>
<view class="more-btn" v-if="!isExpAll" @click="handleShowAll">
<view class="text">查看更多</view>
</view>
</view>
<view class="more-loading" v-if="moreLoading">
<u-loading-icon
mode="circle"
text="AI正在实时计算剩余资讯"
textSize="26rpx"
textColor="#999999"
color="rgba(23, 119, 255, 1)"
size="28rpx"
></u-loading-icon>
</view>
</view>
<u-loading-icon
v-else
mode="circle"
text="加载中"
textSize="26rpx"
textColor="#999999"
color="rgba(23, 119, 255, 1)"
size="28rpx"
></u-loading-icon>
<!-- 日历 -->
<u-calendar
:show="calendarShow"
:min-date="calendar.minDate"
:max-date="calendar.maxDate"
:monthNum="calendar.monthNum"
:default-date="chooseDate.startDate"
@confirm="calendarConfirm"
@close="hideCalendar"
>
</u-calendar>
</view>
</template>
<script setup lang="ts">
import { ref } from "vue";
import RadarView from "./components/RadarView.vue";
import { computed, onMounted, reactive, ref, watch } from "vue";
import { getTopNews } from "@/api/newsInfo";
import * as echarts from "echarts";
import dayjs from "dayjs";
import { useUserStore } from "@/stores/user";
const userStore = useUserStore();
const chatRef = ref(null);
let myChart = null;
//
const initChart = () => {
myChart = echarts.init(chatRef.value);
let option = {
gird: {
show: true,
top: 50,
left: 50,
right: 50,
},
color: ["#4681FF"],
radar: {
// shape: 'circle',
indicator: [
{ name: "资本市场相关", max: 6500 },
{ name: "资讯质量", max: 6000 },
{ name: "媒体影响力", max: 30000 },
{ name: "行业导向", max: 38000 },
{ name: "历史溯洄", max: 52000 },
{ name: "话题性", max: 30000 },
{ name: "风险性", max: 6500 },
{ name: "地缘影响", max: 6500 },
{ name: "产业政策", max: 6500 },
{ name: "潜在信号", max: 6500 },
],
axisName: {
color: "#666666",
fontSize: 12,
fontWeight: 400,
},
axisLine: {
show: true,
lineStyle: {
color: "rgba(234, 234, 236, 1)",
},
},
},
series: [
{
name: "1111",
type: "radar",
areaStyle: { color: "rgba(62, 122, 252, 0.20)" },
data: [
{
value: [4200, 3000, 20000, 35000, 50000, 18000, 4200, 3000, 4200, 3000],
name: "AI追踪海外资讯",
},
],
},
],
};
//使
myChart.setOption(option);
};
watch(
() => chatRef.value,
() => {
initChart();
},
);
//
const loading = ref(false);
const isExpAll = ref(false);
const topNum = 3; //
const lastNewsIndex = ref(10); // 10
const newsListAll = ref([]); //
const limit_num = ref(1000);
const moreLoading = ref(false);
//
const newsListTop = computed(() => {
const _data = newsListAll.value.slice(0, topNum);
return _data;
});
//
const newsList = computed(() => {
const _data = newsListAll.value.slice(topNum, lastNewsIndex.value);
return _data;
});
//
const handleShowAll = async () => {
isExpAll.value = true;
moreLoading.value = true;
//
await new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, 500);
}).then(() => {
moreLoading.value = false;
});
//
lastNewsIndex.value = newsListAll.value.length;
};
//
const formatDateStart = "YYYY-MM-DD 00:00:00";
const formatDateEnd = "YYYY-MM-DD 23:59:59";
//
const calendar = reactive({
minDate: "2025-01-01",
maxDate: dayjs().format(formatDateEnd),
monthNum: Math.ceil(dayjs().diff("2025-01-01", "month", true)),
});
//
const chooseDate = reactive({
startDate: dayjs().format(formatDateStart),
endDate: dayjs().format(formatDateEnd),
});
//
const calendarShow = ref(false);
function showCalendar() {
calendarShow.value = true;
}
//
function hideCalendar() {
calendarShow.value = false;
}
function calendarConfirm(dateList: string[]) {
if (dateList && dateList.length > 0) {
chooseDate.startDate = dayjs(dateList[0]).format(formatDateStart);
chooseDate.endDate = dayjs(dateList[dateList.length - 1]).format(formatDateEnd);
}
getNewsList();
calendarShow.value = false;
isExpAll.value = false;
lastNewsIndex.value = 10
}
async function getNewsList() {
const params = {
start_date: chooseDate.startDate,
end_date: chooseDate.endDate,
limit_num: limit_num.value,
};
loading.value = true;
try {
const result = await getTopNews(params);
newsListAll.value = result;
} catch (e) {
console.log(e);
} finally {
loading.value = false;
}
}
//
const handleBack = () => {
@ -87,6 +274,11 @@ const handleBack = () => {
delta: 1,
});
};
onMounted(() => {
initChart();
getNewsList();
});
</script>
<style scoped lang="scss">
@ -140,11 +332,44 @@ const handleBack = () => {
}
}
.radar-container {
// min-height: 600rpx;
margin: 38rpx 20rpx 20rpx;
padding: 39rpx 0 30rpx;
background-image: linear-gradient(180deg, rgba(194, 214, 253, 0.96) 0%, #ffffff 50%);
box-shadow: inset 0px 1px 0px 0px #ffffff;
border-radius: 20px;
backdrop-filter: blur(10px);
box-sizing: border-box;
.title {
width: 329rpx;
padding: 14rpx 30rpx;
margin: 0 auto;
background: #ffffff;
border-radius: 32rpx;
border: 1px solid #e7e7e7;
font-family: "PingFangSC, PingFang SC";
font-weight: 500;
font-size: 26rpx;
color: #666666;
line-height: 38rpx;
text-align: center;
box-sizing: border-box;
}
.chat-box {
width: 100%;
height: 400rpx;
margin-top: 30rpx;
}
}
.page-main {
&.blur {
filter: blur(5px);
}
.news-list-top3 {
.news-list-top {
margin: 0 20rpx;
.news-item {
display: flex;
@ -254,6 +479,7 @@ const handleBack = () => {
}
.news-list {
padding-bottom: 20rpx;
margin: 0 20rpx;
background: #ffffff;
box-shadow: inset 0px 2rpx 0px 0px #ffffff;
@ -301,5 +527,28 @@ const handleBack = () => {
}
}
}
.more-btn {
margin-top: 22rpx;
display: flex;
justify-content: center;
align-items: center;
.text {
padding-right: 31rpx;
background: url("@/assets/images/page/grey_down@2x.png") no-repeat right center;
background-size: 21rpx 11rpx;
font-family: "PingFangSC, PingFang SC";
font-weight: 400;
font-size: 28rpx;
color: #666666;
line-height: 40rpx;
text-align: center;
}
}
.more-loading {
padding: 30rpx 0;
}
}
</style>

View File

@ -56,6 +56,7 @@ export const useShareStore = defineStore("share", {
}).then((res: any) => {
const data: IWxConfig = res.data.data;
// console.log("🚀 ~ getWeChatSdkData ~ data:", data);
if(!data?.appId) return;
wx.config({
debug: false,
appId: data.appId,

View File

@ -43,24 +43,24 @@ export const Session = {
// 设置临时缓存
set<T>(key: string, val: T) {
if (key === 'token' || key === 'userPhone') return Cookies.set(key, val);
window.sessionStorage.setItem(Local.setKey(key), JSON.stringify(val));
window.localStorage.setItem(Local.setKey(key), JSON.stringify(val));
},
// 获取临时缓存
get(key: string) {
// console.log("🚀 ~ get ~ key:", key)
if (key === 'token' || key === 'userPhone') return Cookies.get(key);
let json = <string>window.sessionStorage.getItem(Local.setKey(key));
let json = <string>window.localStorage.getItem(Local.setKey(key));
return JSON.parse(json);
},
// 移除临时缓存
remove(key: string) {
if (key === 'token' || key === 'userPhone') return Cookies.remove(key);
window.sessionStorage.removeItem(Local.setKey(key));
window.localStorage.removeItem(Local.setKey(key));
},
// 移除全部临时缓存
clear() {
Cookies.remove('token');
Cookies.remove('userPhone');
window.sessionStorage.clear();
window.localStorage.clear();
},
};