提交 94274ead authored 作者: hsx's avatar hsx

feat:新闻详情页

上级 7505facb
......@@ -10,6 +10,7 @@ lerna-debug.log*
*.rar
*.zip
*.7z
*.rest
# Dependencies
node_modules
......
import request from "@/api/request.js";
const INTELLECTUAL_API = "/intelligent-api";
export class IntelligentResultWrapper<T> {
result: T;
status: string;
}
export class TextEntity {
text_span: string;
type: string;
}
// 智能化:提取文本实体
export function extractTextEntity(text: string): Promise<IntelligentResultWrapper<TextEntity[]>> {
return request({
url: `${INTELLECTUAL_API}/extract-entity`,
method: "POST",
data: {
text
}
});
}
......@@ -35,7 +35,7 @@ export function getHotNews() {
export function getHotNewsByArea(params) {
return request({
method: 'GET',
url: `/api/news/hotNews`,
url: `/api/news/hotAreaNews/${params.moduleId}`,
params
})
}
......
......@@ -8,7 +8,6 @@
</template>
<script setup lang="ts">
import { size } from 'lodash';
import { ref, computed, watch, onMounted } from 'vue';
// 组件属性
......@@ -83,7 +82,7 @@ const processedSvgContent = computed(() => {
});
}
console.log(processed)
// console.log(processed)
return processed;
});
......
<template>
<div class="box3-item" @click="handleToNewsAnalysis(news)">
<div class="left">
<img :src="news[props.img] ? news[props.img] : DefaultIconNews" alt="" />
</div>
<div class="right">
<div class="right-top">
<div class="title"><span class="text-inner">{{ news[props.title] }}</span></div>
<div class="time">{{ news[props.from] }}</div>
</div>
<div class="right-footer">{{ news[props.content] }}</div>
</div>
</div>
<el-space alignment="flex-start" :size="10">
<img :width="64" :height="52" :src="news ?? DefaultIconNews" alt="" />
<el-space direction="vertical" alignment="flex-start" :size="0">
<common-text :line-limit="1" class="text-regular text-hover" color="var(--text-primary-80-color)">
{{ title }}
</common-text>
<common-text :line-limit="1" class="text-tip-1" color="var(--text-primary-65-color)">
{{ from }}
</common-text>
</el-space>
</el-space>
</template>
<script setup>
import DefaultIconNews from "@/assets/icons/default-icon-news.png";
const props = defineProps({
// 新闻列表数据
news: {
type: Object,
default: () => { }
},
import { ElSpace } from "element-plus";
import CommonText from "../texts/CommonText.vue";
const props = defineProps({
img: {
type: String,
default: 'img'
default: ''
},
title: {
type: String,
default: "title"
default: ""
},
from: {
type: String,
default: "from"
default: ""
},
content: {
type: String,
default: "content"
default: ""
},
});
......@@ -49,72 +44,4 @@ const handleToNewsAnalysis = (item, index) => {
</script>
<style lang="scss" scoped>
@use '@/styles/common.scss';
.box3-item {
display: flex;
align-items: center;
height: 78px;
margin: 0px 21px;
cursor: pointer;
&:hover {
.right-top .title {
color: rgb(5, 95, 194) !important;
font-weight: 700;
}
.right-top .text-inner {
border-bottom-color: rgb(5, 95, 194) !important;
}
}
}
.left {
width: 97px;
// flex-shrink: 0;
height: 72px;
img {
width: 100%;
height: 100%;
border-radius: 4px;
}
}
.right {
flex: 1;
min-width: 0;
margin-left: 20px;
.right-top {
display: flex;
justify-content: space-between;
.title {
// width: 500px;
@extend .text-title-3-bold;
color: var(--text-primary-80-color);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.text-inner {
border-bottom: 1px solid transparent;
}
}
.time {
text-align: right;
@extend .text-tip-2;
color: var(--text-primary-65-color);
}
}
.right-footer {
@extend .text-compact;
color: var(--text-primary-65-color);
@include common.text-ellipsis(2);
}
}
</style>
\ No newline at end of file
<template>
<div class="common-text">
<div class="common-text-box-hudf">
<slot></slot>
</div>
</template>
......@@ -18,8 +18,8 @@ const props = defineProps({
<style lang="scss" scoped>
@use '@/styles/common.scss';
.common-text {
color: v-bind(color);
.common-text-box-hudf {
color: v-bind(color) !important;
@if('v-bind(lineLimit) !==null') {
@include common.text-ellipsis(v-bind(lineLimit));
......
<template>
<p class="p-regular-rereg">
<span class="text-regular" v-for="(segment, index) in processedText" :key="index">
<a v-if="segment.isEntity" :href="`https://cn.bing.com/search?q=${segment.entity?.text_span}`"
class="entity-link" target="_blank" rel="noopener noreferrer">
{{ segment.entity?.text_span }}
<img :src="SearchIcon" :width="10" :height="10" alt="search" />
</a>
<span v-else>
{{ segment.text }}
</span>
</span>
</p>
</template>
<script lang="ts" setup>
import { TextEntity } from '@/api/intelligent';
import { ref, watch, onMounted } from 'vue';
import SearchIcon from './images/search.png'
export interface ProcessedTextSegment {
text: string
isEntity: boolean
entity?: TextEntity
}
const props = defineProps({
text: {
type: String,
default: ''
}
, entities: {
type: Array<TextEntity>,
default: () => []
}
})
// 处理后的文本段
const processedText = ref<ProcessedTextSegment[]>([])
// 处理文本,识别并替换实体
const processText = () => {
if (!props.text || !props.entities) {
console.log('props.text', props.entities.length)
processedText.value = [{ text: '', isEntity: false }]
return
}
const result = []
let currentPosition = 0
// 按实体文本长度排序,优先匹配长文本
const sortedEntities = [...props.entities].sort((a, b) =>
b.text_span.length - a.text_span.length
)
while (currentPosition < props.text.length) {
let matched = false
for (const entity of sortedEntities) {
const entityText = entity.text_span
const endPosition = currentPosition + entityText.length
if (props.text.substring(currentPosition, endPosition) === entityText) {
// 如果当前位置是实体,添加到结果
result.push({
isEntity: true,
entity: { ...entity }
})
currentPosition = endPosition
matched = true
break
}
}
if (!matched) {
// 如果不是实体,收集普通文本
let nextEntityStart = props.text.length
for (const entity of sortedEntities) {
const pos = props.text.indexOf(entity.text_span, currentPosition)
if (pos !== -1 && pos < nextEntityStart) {
nextEntityStart = pos
}
}
if (nextEntityStart > currentPosition) {
const plainText = props.text.substring(currentPosition, nextEntityStart)
result.push({
text: plainText,
isEntity: false
})
currentPosition = nextEntityStart
} else {
// 没有更多实体,添加剩余文本
const remainingText = props.text.substring(currentPosition)
if (remainingText) {
result.push({
text: remainingText,
isEntity: false
})
}
currentPosition = props.text.length
}
}
}
processedText.value = result
}
// 监听文本和实体变化
watch(() => props.text, processText)
watch(() => props.entities, processText, { deep: true })
// 初始化处理
onMounted(processText)
</script>
<style lang="scss" scoped>
@use '@/styles/common.scss';
.entity-link {
color: var(--color-primary-100);
}
.p-regular-rereg {
text-indent: 2em;
}
</style>
\ No newline at end of file
......@@ -71,6 +71,13 @@
color: var(--text-primary-80);
}
.text-hover {
&:hover {
color: rgb(5, 95, 194) !important;
font-weight: 700;
}
}
//0级标题
.text-title-0 {
@extend .text-base;
......
......@@ -8,7 +8,7 @@
<div class="text-title-1-show">文字样式</div>
<TextStyle />
<div class="text-title-1-show">通用样式/组件</div>
<div style="position: relative; min-height: 300px;">
<div style="position: relative; min-height: 700px;">
<el-tabs tabPosition="left" class="tabs-nav-no-wrap left-float-nav-tabs">
<el-tab-pane label="通用" lazy>
<common-page />
......
......@@ -56,8 +56,11 @@ onMounted(async () => {
async function changeArea(id) {
const { data } = await getHotNewsByArea({
moduleId: moduleId.value,
industryId: id,
industryId: id ? id : null,
});
data?.forEach(item => {
item.newsImage = item.coverUrl ?? ""
})
NewsData.value = data ?? [];
}
......
......@@ -11,9 +11,9 @@
{{ newsDetail.title }}
</common-text>
<common-text class="text-tip-1-bold" color="var(--text-primary-65-color)">
{{ `${newsDetail.publishedTime} · ${newsDetail.source}` }}
{{ `${newsDetail.publishedTime} · ${newsDetail.source} ` }}
<el-space>
<area-tag :area-name="newsDetail.areaName" />
<area-tag v-for="item in newsDetail.domainList" :key="item" :tag-name="item.industryName" />
</el-space>
</common-text>
</el-space>
......@@ -28,11 +28,12 @@
</div>
<div class="flex-display common-page content-box-news-detail">
<el-space direction="vertical" class="background-as-card flex-fill" fill alignment="flex-start">
<div style="margin-top: 10px; margin-right: 24px;" class="flex-display">
<div style="margin-top: 10px; margin-right: 24px;gap:10px" class="flex-display">
<color-prefix-title height="20px">
<div class="text-title-2-bold">新闻内容</div>
</color-prefix-title>
<div class="flex-fill"></div>
<el-switch v-model="isHightLightEntity" active-text="高亮实体" @change="handleHighlightEntity" />
<el-button v-if="hasTranslation" :type="isOpenTranslation ? 'primary' : ''" plain
@click="handleTranslation">
<color-svg :svg-url="TranslationSvg" color="var(--color-primary-100)" :size="18"
......@@ -42,11 +43,12 @@
</div>
<div class="common-padding">
<div class="flex-display" style="align-items: center;">
<common-text class="text-title-3-bold" color="var(--text-primary-80-color)">中文</common-text>
<common-text class="text-title-3-bold" color="var(--text-primary-80-color)">{{ isOpenTranslation
? '中文' : '原文' }}</common-text>
<div class="flex-fill" style="margin: 0 10px;">
<el-divider></el-divider>
</div>
<el-button @click="() => showMore = !showMore">
<el-button v-if="zhEnTexts.length > 6" @click="() => showMore = !showMore">
{{ showMore ? '收起' : '展开' }}
<el-icon>
<arrow-down v-if="showMore" />
......@@ -57,7 +59,9 @@
<el-row :gutter="32">
<el-col :span="znEnColSpan"
v-for="(item, index) in showMore ? zhEnTexts : zhEnTexts.slice(0, 6)" :key="index">
<p class="p-news-content"> {{ item }}</p>
<!-- <p class="p-news-content"> {{ item }}</p> -->
<intelligent-entity-text :text="item"
:entities="isHightLightEntity ? textEntities : []"></intelligent-entity-text>
</el-col>
</el-row>
</div>
......@@ -68,8 +72,10 @@
<div class="text-title-2-bold">相关新闻</div>
</color-prefix-title>
</el-space>
<el-space style="margin-top: 10px;">
<news-item v-for="item in relationNews" :key="item.newsId" :news-item="item" />
<el-space direction="vertical" fill class="common-padding">
<news-item v-for="item in relationNews" :key="item.newsId" :news="item" :img="item.newsImage"
:title="item.newsTitle" :from="`${item.newsDate} · ${item.newsOrg}`"
@click="gotoNewsDetail(item.newsId)" />
</el-space>
</el-space>
</div>
......@@ -81,7 +87,7 @@ import '@/styles/container.scss';
import '@/styles/common.scss';
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import { ElSpace, ElImage, ElButton, ElIcon, ElScrollbar, ElRow, ElCol, ElDivider } from "element-plus";
import { ElSpace, ElImage, ElButton, ElIcon, ElScrollbar, ElRow, ElCol, ElDivider, ElSwitch } from "element-plus";
import CommonText from "@/components/base/texts/CommonText.vue";
import AreaTag from "@/components/base/AreaTag/index.vue";
import ColorPrefixTitle from '@/components/base/texts/ColorPrefixTitle.vue';
......@@ -90,14 +96,22 @@ import NewsItem from "@/components/base/newsList/NewsItem.vue";
import ColorSvg from "@/components/base/images/ColorSvg.vue";
import TranslationSvg from './assets/images/翻译 1.svg';
import NewsLogo from './assets/images/组合 293.svg';
import { extractTextEntity } from "@/api/intelligent";
import { useGotoNewsDetail } from "@/router/modules/news";
import IntelligentEntityText from "@/components/base/texts/IntelligentEntityText.vue";
const newsDetail = ref({});
const relationNews = ref([]);
const zhEnTexts = ref([]);
const znEnColSpan = ref(12);
const hasTranslation = ref(false);
const isOpenTranslation = ref(true);
const isHightLightEntity = ref(true);
const textEntities = ref([]);
const showMore = ref(false);
const route = useRoute();
const gotoNewsDetail = useGotoNewsDetail();
onMounted(async () => {
const params = {
newsId: route.params.id
......@@ -107,9 +121,23 @@ onMounted(async () => {
const { data: relationNewsData } = await getRelationNews(params);
relationNews.value = relationNewsData ?? [];
console.log(relationNews.value)
updateText();
await handleHighlightEntity();
});
async function handleHighlightEntity() {
if (textEntities.value.length > 0) return
const { result: entityDataZh } = await extractTextEntity(newsDetail.value.contentZh);
textEntities.value = [...entityDataZh ?? []]
if (newsDetail.value.contentZh !== newsDetail.value.content) {
const { result: entityData } = await extractTextEntity(newsDetail.value.content);
textEntities.value = [...textEntities.value, ...entityData ?? []]
}
}
function handleTranslation() {
isOpenTranslation.value = !isOpenTranslation.value;
updateText();
......
......@@ -15,31 +15,15 @@
<script setup>
import { ref, onMounted } from "vue";
import '@/styles/container.scss';
import { useRoute } from "vue-router";
import Headlines from "./Headlines.vue";
import Subject from "./Subject.vue";
import { ElInput, ElSpace, ElImage, ElDivider, ElCol, ElRow } from "element-plus";
import AreaTag from '@/components/base/AreaTag/index.vue'
import CommonText from "@/components/base/texts/CommonText.vue";
import NewsMain from "./NewsMain.vue";
import SearchBox from "@/components/base/SearchBox/index.vue";
//顶部数据搜索
const searchInput = ref("");
//当前页面显示
const showPage = ref("home");
const changePage = page => {
console.log(page);
showPage.value = page;
};
</script>
<style lang="scss" scoped>
......
......@@ -104,6 +104,11 @@ export default defineConfig({
proxyReq.setHeader('Cache-Control', 'no-cache');
});
}
},
'/intelligent-api': {
target: 'http://8.140.26.4:10029/',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/intelligent-api/, '')
}
}
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论