feat: 新增海外资讯页面及相关接口,优化用户体验

This commit is contained in:
傅光孟 2026-04-11 10:39:10 +08:00
parent 38d425cfab
commit c7ebc4d5a3
8 changed files with 912 additions and 17 deletions

33
src/api/cankao.ts Normal file
View File

@ -0,0 +1,33 @@
import request from '/@/utils/requestCankao';
// 登录
export const login = (data: any) => {
return request({
url: '/common/tenantLogin',
method: 'post',
data,
});
};
// 编辑精选接口
export const editTopNews = (data: any) => {
return request({
url: '/news/list/published',
method: 'post',
data,
});
};
/**
*
* @param data
* @id * id number
* @returns
*/
export const fetchArticleDetail = (data: any) => {
return request({
url: `/news/detail/${data.id}`,
method: 'get',
data,
});
};

View File

@ -87,7 +87,68 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
// icon: 'iconfont icon-shouye', // icon: 'iconfont icon-shouye',
}, },
}, },
{
path: '/realtimeInfo',
name: 'realtimeInfo',
component: () => import('/@/views/pages/cankao/index.vue'),
meta: {
title: '海外资讯',
isLink: '',
isHide: true,
isKeepAlive: true,
isAffix: false,
isIframe: false,
roles: [
'admin',
'common',
'subCommon',
'subCenter',
'Director',
'CSRC',
'QJJG',
'DFJG',
'JYS',
'QJJGC',
'DFJGC',
'JYSC',
'operate',
'Bank',
'SubBank',
],
// icon: 'iconfont icon-shouye',
},
},
{
path: '/indexPC',
name: 'indexPC',
component: () => import('/@/views/pages/cankao/indexPC.vue'),
meta: {
title: '海外资讯',
isLink: '',
isHide: true,
isKeepAlive: false,
isAffix: false,
isIframe: false,
roles: [
'admin',
'common',
'subCommon',
'subCenter',
'Director',
'CSRC',
'QJJG',
'DFJG',
'JYS',
'QJJGC',
'DFJGC',
'JYSC',
'operate',
'Bank',
'SubBank',
],
// icon: 'iconfont icon-shouye',
},
},
{ {
path: '/caixun', path: '/caixun',
name: 'caixun', name: 'caixun',

View File

@ -0,0 +1,90 @@
import axios, { AxiosInstance } from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Session } from '/@/utils/storage';
import qs from 'qs';
import Cookies from 'js-cookie';
// 配置新建一个 axios 实例
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_CANKAO,
timeout: 50000,
headers: { 'Content-Type': 'application/json' },
paramsSerializer: {
serialize(params) {
return qs.stringify(params, { allowDots: true });
},
},
});
// 添加请求拦截器
service.interceptors.request.use(
(config) => {
config.headers!['auth-token'] = window.token;
// config.headers!['auth-token'] = `${Cookies.get('token')}`;
// config.headers!['auth-token'] = `${Session.get('token')}`;
if (window.phone) {
config.headers!['phone'] = window.phone;
} else {
config.headers!['phone'] = window.username;
}
// 在发送请求之前做些什么 token
// if (Session.get('token')) {
// config.headers!['auth-token'] = window.token;
// // config.headers!['auth-token'] = `${Cookies.get('token')}`;
// // config.headers!['auth-token'] = `${Session.get('token')}`;
// config.headers!['phone'] = window.phone;
// }
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
service.interceptors.response.use(
(response) => {
// 对响应数据做点什么
const res = response.data;
if (res.code && res.code !== 200) {
// `token` 过期或者账号已在别处登录
// if (res.code === 401 || res.code === 402 || res.code === 4001) {
// Session.clear(); // 清除浏览器全部临时缓存
// window.location.href = '/'; // 去登录页
// ElMessageBox.alert('登录已过期,请重新登录', '提示', {})
// .then(() => {})
// .catch(() => {});
// } else {
// ElMessage.error(res.msg);
// }
return Promise.reject(service.interceptors.response);
} else {
return res;
}
},
(error) => {
console.log('🚀 ~ error:', error);
// 对响应错误做点什么
if (error.message.indexOf('timeout') != -1) {
ElMessage.error('网络超时');
} else if (error.message == 'Network Error') {
ElMessage.error('网络连接错误');
} else if (error.message.indexOf('token') != -1) {
Session.clear(); // 清除浏览器全部临时缓存
window.location.href = '/'; // 去登录页
} else {
if (error.response.data) {
ElMessage.error(error.response.statusText);
} else {
Session.clear(); // 清除浏览器全部临时缓存
window.location.href = '/'; // 去登录页
ElMessage.error('接口路径找不到');
}
}
return Promise.reject(error);
}
);
// 导出 axios 实例
export default service;

View File

@ -0,0 +1,405 @@
<template>
<div class="pc_all">
<div class="content">
<div class="mb24">
<el-breadcrumb separator="/" class="font16">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>海外资讯</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="top_title">
<text class="pageTitle">编辑<text class="strong">精选</text></text>
<div class="r_input">
<input
v-model="form.keyword"
placeholder="请输入搜索内容"
class="input"
@keyup.enter="getNewsList"
@clear="getNewsList"
@blur="getNewsList"
/>
<div class="input_button" @click="getNewsList">搜索</div>
</div>
</div>
<div class="line"></div>
<div class="r_list">
<div class="list_item" v-for="(item, index) in newsList" :key="index">
<div class="list_item_content">
<text class="item_title" @click="goDetail(item)" v-html="item.title"></text>
<text class="item_summary" v-html="item.summary"></text>
<div class="item_bottom">
<div>
<text class="time">中国证券报</text>
<text class="time" style="margin-left: 15px">{{ formatTime(item.time) }}</text>
</div>
</div>
</div>
</div>
</div>
<div class="pagination" style="width: 100%; display: flex; justify-content: flex-end" v-if="newsList && newsList.length > 0">
<el-pagination
popper-class="popper-style"
background
v-model:current-page="currentPage"
:page-size="form.size"
layout="total, sizes, prev, pager, next, jumper"
:total="form.total"
@current-change="currentChange"
@size-change="sizeChange"
>
</el-pagination>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive } from 'vue';
import { editTopNews } from '/@/api/cankao';
import { useRoute, useRouter } from 'vue-router';
const pageSizes = ref([10, 20, 30, 40]);
const form = reactive({
keyword: '',
page: 1,
size: 10,
total: 10,
});
const currentPage = ref(form.page);
const route = useRoute();
const router = useRouter();
const pageType = ref(4);
const newsList = ref([]);
function formatTime(timestamp) {
const date = new Date(Number(timestamp).toString().length === 10 ? timestamp * 1000 : timestamp);
return (
[date.getFullYear(), (date.getMonth() + 1).toString().padStart(2, '0'), date.getDate().toString().padStart(2, '0')].join('-') +
' ' +
[date.getHours().toString().padStart(2, '0'), date.getMinutes().toString().padStart(2, '0'), date.getSeconds().toString().padStart(2, '0')].join(
':'
)
);
}
async function getNewsList() {
//
let { code, data } = await editTopNews({
...form,
});
if (code == 200) {
newsList.value = data.list;
form.total = data.total;
data.list.forEach((item) => {
item._title_ = item.title;
item.summary = item.summary.replace(form.keyword, "<span style='color: #007aff'>" + form.keyword + '</span>');
item.title = item.title.replace(form.keyword, "<span style='color: #007aff'>" + form.keyword + '</span>');
});
}
}
function goDetail(item) {
let id = null;
if (pageType.value != 4) {
id = item.news_id;
} else {
id = item.id;
}
router.push({
path: '/indexPC',
query: {
id: id,
type: pageType.value,
},
});
}
function currentChange(page) {
form.page = page;
getNewsList();
}
function sizeChange(size) {
form.page = 1;
currentPage.value = 1;
form.size = size;
getNewsList();
}
onMounted(() => {
getNewsList();
});
</script>
<style scoped lang="scss">
.pc_all {
background: #f5f7fd;
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
// align-items: center;
}
.content {
background-color: white;
// width: 55vw;
width: 100vw;
min-height: 100vh;
padding: 24px 32px;
}
.top_title {
display: flex;
align-items: center;
.pageTitle {
font-family: AlibabaPuHuiTiM;
font-size: 26px;
color: #1a1a1a;
font-weight: bold;
.strong {
color: #007aff;
}
}
.title_icon {
width: 90px;
height: 25px;
margin-left: 10px;
}
}
.line {
width: 100%;
height: 1px;
background-color: #f0f0f0;
margin-top: 25px;
}
.r_list {
display: flex;
flex-direction: column;
}
.list_item {
display: flex;
min-height: 100px;
padding: 15px 0;
border-bottom: 1px solid #f6f6f6;
margin-top: 15px;
.r_list_item_num {
width: 50px;
display: flex;
text-align: center;
}
.list_item_num {
margin-top: 5px;
width: 20px;
height: 20px;
display: flex;
text-align: center;
justify-content: center;
align-items: center;
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 18px;
color: #ffffff;
padding-bottom: 2.5px;
}
.num1 {
background: linear-gradient(168deg, #ffb505 0%, #fdcf1b 100%);
border-radius: 3px;
}
.num2 {
background: linear-gradient(169deg, #a9c3e3 0%, #92b2e0 100%);
border-radius: 3px;
}
.num3 {
background: linear-gradient(169deg, #f2996e 0%, #f77741 100%);
border-radius: 3px;
}
.nol_num {
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 20px;
color: #93a2b3;
line-height: 28px;
text-align: left;
font-style: normal;
}
.list_item_content {
display: flex;
flex-direction: column;
flex: 1;
}
.item_title:hover {
color: #007aff;
}
.item_title {
font-family: PingFangSC, PingFang SC;
font-weight: bold;
font-size: 20px;
color: #1a1a1a;
cursor: pointer;
}
.item_summary {
font-family: 'Microsoft YaHei', 'PingFangSC', 'PingFang SC';
font-weight: normal;
font-size: 16px;
color: #333333;
margin-top: 10px;
/* 必须:限制内容不溢出容器 */
overflow: hidden;
/* 必须:超出部分显示省略号 */
text-overflow: ellipsis;
/* 必须将元素设置为webkit弹性盒模型用于控制行数 */
display: -webkit-box;
/* 关键限制显示的行数这里设为2行 */
-webkit-line-clamp: 2;
/* 必须:设置弹性盒的排列方向为垂直(让文本换行) */
-webkit-box-orient: vertical;
/* 可选调整行高和容器高度确保刚好容纳2行文本 */
line-height: 1.5;
/* 行高 */
max-height: 3em;
/* 2行总高度 = 行高 × 21.5×2=3 */
}
}
.item_bottom {
display: flex;
justify-content: space-between;
margin-top: 15px;
margin-bottom: 5px;
.time {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 14px;
color: #919191;
line-height: 20px;
text-align: left;
font-style: normal;
}
.score {
font-family: PingFangSC, PingFang SC;
font-weight: 600;
font-size: 18px;
color: #ffa800;
line-height: 25px;
text-align: right;
font-style: normal;
}
}
.search_btn {
border: none;
display: flex;
width: 56px;
height: 44px;
padding: 8px 15px;
justify-content: center;
align-items: center;
gap: 10px;
flex-shrink: 0;
border-radius: 8px;
background: #007aff;
margin-right: 5px;
}
.input {
border: none;
flex: 1;
margin-left: 10px;
}
.input_button {
display: flex;
width: 140px;
height: 100%;
// padding: 8px 15px;
justify-content: center;
align-items: center;
gap: 10px;
flex-shrink: 0;
border-radius: 0 10px 10px 0;
background: #007aff;
color: white;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 18px;
color: #ffffff;
line-height: 25px;
text-align: left;
font-style: normal;
cursor: pointer;
}
.r_input {
background-color: white;
border-radius: 10px;
border: 1px solid #e3e3e3;
flex: 1;
height: 52px;
display: flex;
align-items: center;
margin-left: 30px;
margin-top: 3px;
:deep(.input::placeholder) {
color: #ccc;
font-family: 'PingFang SC';
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
:deep(.el-input__wrapper) {
box-shadow: 0 0 0 #fff;
}
}
.pagination {
margin-top: 24px;
width: 100%;
display: flex;
text-align: center;
justify-content: center;
align-items: center;
// :deep(.el-pager li){
// border: 1px solid #ccc;
// background: transparent;
// }
}
:deep(.el-select-dropdown__item.is-selected) {
color: #007aff;
}
</style>

View File

@ -0,0 +1,301 @@
<template>
<div class="pc_all">
<!-- <PageTop></PageTop> -->
<div class="content">
<!-- <div class="top" @click="goBack">
<el-icon><ArrowLeft /></el-icon>
<text>返回列表</text>
</div>
<div class="line"></div> -->
<div class="mb30">
<el-breadcrumb separator="/" class="font16">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item :to="{ path: '/realtimeInfo' }">海外资讯</el-breadcrumb-item>
<el-breadcrumb-item>{{ infoData?.title }}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="title">
{{ infoData?.title }}
</div>
<div class="sub_title">
<text class="srouse"> {{ infoData?.tag }}</text>
<text class="time"> {{ infoData?.publishTime }}</text>
</div>
<div class="abstract" v-if="infoData?.summary">
{{ infoData?.summary }}
</div>
<!-- 两个标签 start -->
<div class="r_tag_two">
<div style="display: flex" v-if="infoData?.industryLabels && infoData?.industryLabels.length > 0">
<text class="tag_title">行业分类</text>
<div class="r_tags">
<div class="tag" style="background-color: #fff9ec; color: #ffb100" v-for="(item, index) in infoData?.industryLabels" :key="index">
{{ item }}
</div>
</div>
</div>
<div style="display: flex" v-if="infoData?.conceptLabels && infoData?.conceptLabels.length > 0">
<text class="tag_title">概念标签</text>
<div class="r_tags">
<div class="tag" style="background-color: #f5f8fe; color: #007aff" v-for="(item, index) in infoData?.conceptLabels" :key="index">
{{ item }}
</div>
</div>
</div>
</div>
<!-- 两个标签 end -->
<div class="text" v-html="infoData.content" style="white-space: pre-wrap"></div>
<!-- <div class="text">
<text >{{ infoData.content }}</text>
</div> -->
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { fetchArticleDetail } from '/@/api/cankao';
import { Session } from '/@/utils/storage';
import { useRoute, useRouter } from 'vue-router';
import { ArrowLeft } from '@element-plus/icons-vue';
const router = useRouter();
const route = useRoute();
function goBack() {
// uni.navigateBack();
// router.back();
router.push({
path: '/realtimeInfo',
});
// uni.navigateTo({
// url: '/pages/realtimeInfo/pc/index?type=' + type.value,
// });
}
const type = ref(0);
const infoData = ref({});
onMounted(async () => {
console.log('🚀 ~ router:', route.query.type);
if (route.query.type) {
type.value = route.query.type;
}
// uni.showLoading();
const res = await fetchArticleDetail({
id: route.query?.id,
});
console.log('🚀 ~ res:', res);
// uni.hideLoading();
if (res.code === 200) {
infoData.value = res.data;
window.document.title = res.data.title;
}
});
</script>
<style lang="scss" scoped>
.pc_all {
background: #f5f7fd;
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
}
.content {
background-color: white;
width: 100%;
// min-width: 1200px;
min-height: 100vh;
padding: 24px 32px;
background-color: #fff;
box-sizing: border-box;
}
.page_top {
width: 100vw;
height: 60px;
background-color: white;
margin-bottom: 15px;
box-shadow: 0 2.5px 1px rgba($color: #6d6d6d, $alpha: 0.1);
display: flex;
justify-content: center;
align-items: center;
.logo {
width: 95px;
height: 25px;
margin-right: 25px;
}
.r_menu {
width: 55vw;
min-width: 1250px;
height: 100%;
display: flex;
align-items: center;
}
.r_menu_item {
display: flex;
flex-direction: column;
height: 100%;
position: relative;
justify-content: center;
align-items: center;
}
.menu_item {
font-family: PingFangSC, PingFang SC;
font-weight: bold;
font-size: 16px;
color: #1a1a1a;
}
.line {
width: 64px;
height: 4px;
background: #007aff;
position: absolute;
bottom: 0;
}
}
.top {
width: 100%;
height: 60px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
// padding-left: 15px;
.icon_back {
width: 17px;
height: 19px;
}
text {
font-family: PingFangSC, PingFang SC;
font-weight: normal;
font-size: 19px;
color: #919191;
text-align: left;
font-style: normal;
margin-left: 5px;
cursor: pointer;
}
}
.line {
width: 100%;
height: 4px;
background: #f0f0f0;
margin-bottom: 20px;
}
.title {
font-family: PingFangSC, PingFang SC;
font-weight: bold;
font-size: 24px;
color: #1a1a1a;
margin-top: 12px;
}
.sub_title {
display: flex;
justify-content: space-between;
margin-top: 5px;
.srouse {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 16px;
color: #919191;
}
.time {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 16px;
color: #919191;
}
}
.abstract {
font-family: 'Microsoft YaHei', 'PingFangSC', 'PingFang SC';
font-weight: 400;
font-size: 16px;
color: #666666;
line-height: 26px;
text-align: left;
font-style: normal;
background: #f5f8fe;
border: 1px solid #e3ecfd;
padding: 15px;
border-radius: 10px;
margin-top: 25px;
}
.r_tag_two {
display: flex;
margin-top: 20px;
justify-content: space-between;
}
.r_tags {
display: flex;
gap: 10px;
.tag {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 13px;
color: #ffb100;
line-height: 18px;
text-align: left;
font-style: normal;
padding: 2.5px 10px;
border-radius: 5px;
white-space: nowrap;
}
}
.tag_title {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 14px;
color: #999999;
line-height: 20px;
text-align: left;
font-style: normal;
white-space: nowrap;
}
.text {
font-family: 'Microsoft YaHei', 'PingFangSC', 'PingFang SC';
font-weight: 400;
font-size: 16px;
color: #333;
line-height: 28px;
text-align: left;
font-style: normal;
margin-top: 35px;
}
</style>

View File

@ -506,11 +506,13 @@ function jumpHaiwaiUrl() {
} }
} }
getCankaoUrl({}).then((res) => { // getCankaoUrl({}).then((res) => {
if (res.code == 200) { // if (res.code == 200) {
window.open(res.data); // window.open(res.data);
} // }
}); // });
router.push('/realtimeInfo')
} }
const newsList = ref([]); const newsList = ref([]);

View File

@ -2,11 +2,11 @@
<div class="container"> <div class="container">
<div class=""> <div class="">
<div class=""> <div class="">
<el-breadcrumb separator="/" class="font16"> <el-breadcrumb separator="/" class="font16">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item> <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>智能检校</el-breadcrumb-item> <el-breadcrumb-item>智能检校</el-breadcrumb-item>
</el-breadcrumb> </el-breadcrumb>
</div> </div>
<!-- 时间滑动 start --> <!-- 时间滑动 start -->
<Slide v-if="historyData.length > 0" :data="historyData" @doDetail="goDetail" /> <Slide v-if="historyData.length > 0" :data="historyData" @doDetail="goDetail" />
<!-- 时间滑动 end --> <!-- 时间滑动 end -->

View File

@ -167,6 +167,8 @@ import icon_4 from '/@/assets/yuqingNew/icon_4.png';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { Session } from '/@/utils/storage'; import { Session } from '/@/utils/storage';
import RankList from './component/RankList.vue'; // import RankList from './component/RankList.vue'; //
import JSON5 from 'json5';
import router from '/@/router';
// 1024px // 1024px
const SCALE_THRESHOLD = 1480; const SCALE_THRESHOLD = 1480;
@ -390,7 +392,6 @@ async function getWangkuangJinduiFn() {
jingduiList.value = data; jingduiList.value = data;
} }
} }
import JSON5 from 'json5';
// - // -
const cloudWordList = ref([]); const cloudWordList = ref([]);
async function getWangkuangCloudWordFn() { async function getWangkuangCloudWordFn() {
@ -470,11 +471,13 @@ function jumpHaiwaiUrl() {
} }
} }
getCankaoUrl({}).then((res) => { // getCankaoUrl({}).then((res) => {
if (res.code == 200) { // if (res.code == 200) {
window.open(res.data); // window.open(res.data);
} // }
}); // });
router.push('/realtimeInfo')
} }
const hadRole = ref(true); const hadRole = ref(true);