提交 87f457d0 authored 作者: hsx's avatar hsx

合并分支 'hsx' 到 'master'

feat:新闻的高亮实体使用站内搜索 查看合并请求 !199
<template> <template>
<p class="p-regular-rereg"> <p class="p-regular-rereg">
<span class="text-regular" v-for="(segment, index) in processedText" :key="index"> <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}`" <span v-if="segment.isEntity" @click="$emit('onEntityClick', segment.entity)" class="entity-link">
class="entity-link" target="_blank" rel="noopener noreferrer">
{{ segment.entity?.text_span }} {{ segment.entity?.text_span }}
<img :src="SearchIcon" :width="10" :height="10" alt="search" /> <img :src="SearchIcon" :width="10" :height="10" alt="search" />
</a> </span>
<span v-else> <span v-else>
{{ segment.text }} {{ segment.text }}
</span> </span>
...@@ -13,110 +12,112 @@ ...@@ -13,110 +12,112 @@
</p> </p>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { TextEntity } from '@/api/intelligent'; import { TextEntity } from "@/api/intelligent";
import { ref, watch, onMounted } from 'vue'; import { ref, watch, onMounted } from "vue";
import SearchIcon from './images/search.png' import SearchIcon from "./images/search.png";
export interface ProcessedTextSegment { export interface ProcessedTextSegment {
text: string text: string;
isEntity: boolean isEntity: boolean;
entity?: TextEntity entity?: TextEntity;
} }
const props = defineProps({ const props = defineProps({
text: { text: {
type: String, type: String,
default: '' default: ""
}, },
entities: { entities: {
type: Array<TextEntity>, type: Array<TextEntity>,
default: () => [] default: () => []
} }
}) });
const emit = defineEmits(["onEntityClick"]);
// 处理后的文本段 // 处理后的文本段
const processedText = ref<ProcessedTextSegment[]>([]) const processedText = ref<ProcessedTextSegment[]>([]);
// 处理文本,识别并替换实体 // 处理文本,识别并替换实体
const processText = () => { const processText = () => {
console.log('props.entities.length', props.entities.length) console.log("props.entities.length", props.entities.length);
if (!props.text || !props.entities) { if (!props.text || !props.entities) {
// console.log('props.text', props.entities.length) // console.log('props.text', props.entities.length)
processedText.value = [{ text: '', isEntity: false }] processedText.value = [{ text: "", isEntity: false }];
return return;
} }
const result = [] const result = [];
let currentPosition = 0 let currentPosition = 0;
// 按实体文本长度排序,优先匹配长文本 // 按实体文本长度排序,优先匹配长文本
const sortedEntities = [...props.entities].sort((a, b) => const sortedEntities = [...props.entities].sort((a, b) => b.text_span.length - a.text_span.length);
b.text_span.length - a.text_span.length
)
while (currentPosition < props.text.length) { while (currentPosition < props.text.length) {
let matched = false let matched = false;
for (const entity of sortedEntities) { for (const entity of sortedEntities) {
const entityText = entity.text_span const entityText = entity.text_span;
const endPosition = currentPosition + entityText.length const endPosition = currentPosition + entityText.length;
if (props.text.substring(currentPosition, endPosition) === entityText) { if (props.text.substring(currentPosition, endPosition) === entityText) {
// 如果当前位置是实体,添加到结果 // 如果当前位置是实体,添加到结果
result.push({ result.push({
isEntity: true, isEntity: true,
entity: { ...entity } entity: { ...entity }
}) });
currentPosition = endPosition currentPosition = endPosition;
matched = true matched = true;
break break;
} }
} }
if (!matched) { if (!matched) {
// 如果不是实体,收集普通文本 // 如果不是实体,收集普通文本
let nextEntityStart = props.text.length let nextEntityStart = props.text.length;
for (const entity of sortedEntities) { for (const entity of sortedEntities) {
const pos = props.text.indexOf(entity.text_span, currentPosition) const pos = props.text.indexOf(entity.text_span, currentPosition);
if (pos !== -1 && pos < nextEntityStart) { if (pos !== -1 && pos < nextEntityStart) {
nextEntityStart = pos nextEntityStart = pos;
} }
} }
if (nextEntityStart > currentPosition) { if (nextEntityStart > currentPosition) {
const plainText = props.text.substring(currentPosition, nextEntityStart) const plainText = props.text.substring(currentPosition, nextEntityStart);
result.push({ result.push({
text: plainText, text: plainText,
isEntity: false isEntity: false
}) });
currentPosition = nextEntityStart currentPosition = nextEntityStart;
} else { } else {
// 没有更多实体,添加剩余文本 // 没有更多实体,添加剩余文本
const remainingText = props.text.substring(currentPosition) const remainingText = props.text.substring(currentPosition);
if (remainingText) { if (remainingText) {
result.push({ result.push({
text: remainingText, text: remainingText,
isEntity: false isEntity: false
}) });
} }
currentPosition = props.text.length currentPosition = props.text.length;
} }
} }
} }
processedText.value = result processedText.value = result;
} };
// 监听文本和实体变化 // 监听文本和实体变化
watch(() => props.text, processText) watch(() => props.text, processText);
watch(() => props.entities, processText, { deep: true }) watch(() => props.entities, processText, { deep: true });
// 初始化处理 // 初始化处理
onMounted(processText) onMounted(processText);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@use '@/styles/common.scss'; @use "@/styles/common.scss";
.entity-link { .entity-link {
color: var(--color-primary-100); color: var(--color-primary-100);
&:hover {
cursor: pointer;
}
} }
.p-regular-rereg { .p-regular-rereg {
......
<template> <template>
<div class="full-width"> <div class="full-width">
<div class="flex-display" style="align-items: center;"> <div class="flex-display" style="align-items: center">
<common-text class="text-title-3-bold" color="var(--text-primary-80-color)">{{ isOpenTranslation <common-text class="text-title-3-bold" color="var(--text-primary-80-color)">{{
? '中文' : '原文' }}</common-text> isOpenTranslation ? "中文" : "原文"
<div class="flex-fill" style="margin: 0 10px;"> }}</common-text>
<div class="flex-fill" style="margin: 0 10px">
<el-divider></el-divider> <el-divider></el-divider>
</div> </div>
<el-button v-if="showMoreVisible" @click="() => { showMore = !showMore; updateText() }"> <el-button
{{ showMore ? '收起' : '展开' }} v-if="showMoreVisible"
@click="
() => {
showMore = !showMore;
updateText();
}
"
>
{{ showMore ? "收起" : "展开" }}
<el-icon> <el-icon>
<arrow-up v-if="showMore" /> <arrow-up v-if="showMore" />
<arrow-down v-else /> <arrow-down v-else />
...@@ -17,21 +26,24 @@ ...@@ -17,21 +26,24 @@
<el-row :gutter="32"> <el-row :gutter="32">
<el-col :span="textColSpan" v-for="(item, index) in allTexts" :key="index"> <el-col :span="textColSpan" v-for="(item, index) in allTexts" :key="index">
<!-- <p class="p-news-content"> {{ item }}</p> --> <!-- <p class="p-news-content"> {{ item }}</p> -->
<intelligent-entity-text :text="item" <intelligent-entity-text
:entities="isHighlightEntity ? textEntities : []"></intelligent-entity-text> :text="item"
@on-entity-click="e => $emit('onEntityClick', e)"
:entities="isHighlightEntity ? textEntities : []"
></intelligent-entity-text>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import '@/styles/container.scss'; import "@/styles/container.scss";
import '@/styles/common.scss'; import "@/styles/common.scss";
import { ref, watch, onMounted } from 'vue'; import { ref, watch, onMounted } from "vue";
import { TextEntity } from '@/api/intelligent'; import { TextEntity } from "@/api/intelligent";
import IntelligentEntityText from '@/components/base/texts/IntelligentEntityText.vue'; import IntelligentEntityText from "@/components/base/texts/IntelligentEntityText.vue";
import { ElIcon, ElButton, ElDivider, ElRow, ElCol } from 'element-plus'; import { ElIcon, ElButton, ElDivider, ElRow, ElCol } from "element-plus";
import CommonText from './CommonText.vue'; import CommonText from "./CommonText.vue";
const allTexts = ref([]); const allTexts = ref([]);
const textColSpan = ref(12); const textColSpan = ref(12);
...@@ -64,30 +76,30 @@ const props = defineProps({ ...@@ -64,30 +76,30 @@ const props = defineProps({
type: Array<TextEntity>, type: Array<TextEntity>,
default: () => [] default: () => []
} }
}) });
const emit = defineEmits(["onEntityClick"]);
function updateText() { function updateText() {
const tempTexts = [] const tempTexts = [];
const tempRaws = props.textsRaw ?? [] const tempRaws = props.textsRaw ?? [];
const tempTranslates = props.textsTranslate ?? [] const tempTranslates = props.textsTranslate ?? [];
hasTranslation.value = tempTranslates.length > 0 hasTranslation.value = tempTranslates.length > 0;
if (hasTranslation.value && props.isOpenTranslation) { if (hasTranslation.value && props.isOpenTranslation) {
// 遍历原始文本和翻译文本,将它们交替添加到 tempTexts 中,并保持原始文本和翻译文本的的数量一致 // 遍历原始文本和翻译文本,将它们交替添加到 tempTexts 中,并保持原始文本和翻译文本的的数量一致
const maxCount = Math.max(tempRaws.length, tempTranslates.length) const maxCount = Math.max(tempRaws.length, tempTranslates.length);
for (let i = 0; i < maxCount; i++) { for (let i = 0; i < maxCount; i++) {
if (i < tempTranslates.length) { if (i < tempTranslates.length) {
tempTexts.push(tempTranslates[i]); tempTexts.push(tempTranslates[i]);
} else { } else {
tempTexts.push(''); tempTexts.push("");
} }
if (i < tempRaws.length) { if (i < tempRaws.length) {
tempTexts.push(tempRaws[i]); tempTexts.push(tempRaws[i]);
} else { } else {
tempTexts.push(''); tempTexts.push("");
} }
} }
console.log(tempTexts.length) console.log(tempTexts.length);
textColSpan.value = 12; textColSpan.value = 12;
showMoreVisible.value = tempTexts.length > 6; showMoreVisible.value = tempTexts.length > 6;
allTexts.value = showMore.value ? tempTexts : tempTexts.slice(0, 6); allTexts.value = showMore.value ? tempTexts : tempTexts.slice(0, 6);
...@@ -98,12 +110,14 @@ function updateText() { ...@@ -98,12 +110,14 @@ function updateText() {
} }
} }
watch(() => [props.textsRaw, props.textsTranslate, props.isOpenTranslation], () => { watch(
() => [props.textsRaw, props.textsTranslate, props.isOpenTranslation],
() => {
updateText(); updateText();
}) }
);
onMounted(() => { onMounted(() => {
updateText(); updateText();
}) });
</script> </script>
// 综合搜索 // 综合搜索
const ComprehensiveSearch = () => import('@/views/comprehensiveSearch/index.vue') const ComprehensiveSearch = () => import("@/views/comprehensiveSearch/index.vue");
const SearchResults = () => import('@/views/comprehensiveSearch/searchResults/index.vue') const SearchResults = () => import("@/views/comprehensiveSearch/searchResults/index.vue");
const Chat = () => import('@/views/comprehensiveSearch/chat/index.vue') const Chat = () => import("@/views/comprehensiveSearch/chat/index.vue");
const comprehensiveSearchRoutes = [ const comprehensiveSearchRoutes = [
// 综合搜索 // 综合搜索
...@@ -29,19 +29,19 @@ const comprehensiveSearchRoutes = [ ...@@ -29,19 +29,19 @@ const comprehensiveSearchRoutes = [
meta: { meta: {
title: "智能问答" title: "智能问答"
} }
}, }
];
]
import { useGotoPage } from "../common.js"; import { useGotoPage } from "../common.js";
export function useGotoComprehensiveSearch() { export function useGotoComprehensiveSearch() {
const gotoPage = useGotoPage(); const gotoPage = useGotoPage();
return (isNewTabs = true) => gotoPage("/comprehensiveSearch/", {}, isNewTabs) return (isNewTabs = true) => gotoPage("/comprehensiveSearch/", {}, isNewTabs);
} }
export function useGotoSearchResults() { export function useGotoSearchResults() {
const gotoPage = useGotoPage(); const gotoPage = useGotoPage();
return (isNewTabs = true) => gotoPage("/searchResults/", {searchText, areaName}, isNewTabs)
return (searchText, areaName, isNewTabs = true) => gotoPage("/searchResults/", { searchText, areaName }, isNewTabs);
} }
export default comprehensiveSearchRoutes export default comprehensiveSearchRoutes;
\ No newline at end of file
<template> <template>
<el-scrollbar> <el-scrollbar>
<div class="flex-display common-page top-box-news-deatail" style="align-items: center;"> <div class="flex-display common-page top-box-news-deatail" style="align-items: center">
<!-- <color-svg :svg-url="NewsLogo" :size="72" style="margin-right:10px"></color-svg> --> <!-- <color-svg :svg-url="NewsLogo" :size="72" style="margin-right:10px"></color-svg> -->
<img :src="NewsLogo" style="margin-right:24px"> <img :src="NewsLogo" style="margin-right: 24px" />
<el-space direction="vertical" class="flex-fill" alignment="flex-start"> <el-space direction="vertical" class="flex-fill" alignment="flex-start">
<common-text class="text-title-1-bold" color="var(--text-primary-80-color)"> <common-text class="text-title-1-bold" color="var(--text-primary-80-color)">
{{ newsDetail.titleZh }} {{ newsDetail.titleZh }}
...@@ -28,38 +28,63 @@ ...@@ -28,38 +28,63 @@
</div> </div>
<div class="flex-display common-page content-box-news-detail"> <div class="flex-display common-page content-box-news-detail">
<el-space direction="vertical" class="background-as-card flex-fill" fill alignment="flex-start"> <el-space direction="vertical" class="background-as-card flex-fill" fill alignment="flex-start">
<div style="margin-top: 10px; margin-right: 24px;gap:10px" class="flex-display"> <div style="margin-top: 10px; margin-right: 24px; gap: 10px" class="flex-display">
<color-prefix-title height="20px"> <color-prefix-title height="20px">
<div class="text-title-2-bold">新闻内容</div> <div class="text-title-2-bold">新闻内容</div>
</color-prefix-title> </color-prefix-title>
<div class="flex-fill"></div> <div class="flex-fill"></div>
<el-switch v-model="isHightLightEntity" active-text="高亮实体" @change="handleHighlightEntity" /> <el-switch v-model="isHightLightEntity" active-text="高亮实体" @change="handleHighlightEntity" />
<el-button v-if="textZns.length > 0" :type="isOpenTranslation ? 'primary' : ''" plain <el-button
@click="handleTranslation"> v-if="textZns.length > 0"
<color-svg :svg-url="TranslationSvg" color="var(--color-primary-100)" :size="18" :type="isOpenTranslation ? 'primary' : ''"
style="margin-right:10px"></color-svg> plain
@click="handleTranslation"
>
<color-svg
:svg-url="TranslationSvg"
color="var(--color-primary-100)"
:size="18"
style="margin-right: 10px"
></color-svg>
译文 译文
</el-button> </el-button>
</div> </div>
<text-translate-pane class="common-padding-h" :texts-raw="textEns" :texts-translate="textZns" <text-translate-pane
:text-entities="textEntities" :is-open-translation="isOpenTranslation" class="common-padding-h"
:is-highlight-entity="isHightLightEntity"> :texts-raw="textEns"
:texts-translate="textZns"
:text-entities="textEntities"
:is-open-translation="isOpenTranslation"
:is-highlight-entity="isHightLightEntity"
@on-entity-click="e => gotoSearchResults(e.text_span, '')"
>
</text-translate-pane> </text-translate-pane>
<div> <div>
<img v-if="newsDetail.coverUrl" class="common-padding" :src="newsDetail.coverUrl" :width="320" <img
:height="240" /> v-if="newsDetail.coverUrl"
class="common-padding"
:src="newsDetail.coverUrl"
:width="320"
:height="240"
/>
</div> </div>
</el-space> </el-space>
<el-space direction="vertical" class="background-as-card relation-news-box" fill> <el-space direction="vertical" class="background-as-card relation-news-box" fill>
<el-space style="margin-top: 10px;"> <el-space style="margin-top: 10px">
<color-prefix-title height="20px"> <color-prefix-title height="20px">
<div class="text-title-2-bold">相关新闻</div> <div class="text-title-2-bold">相关新闻</div>
</color-prefix-title> </color-prefix-title>
</el-space> </el-space>
<el-space v-if="relationNews?.length > 0" direction="vertical" fill class="common-padding"> <el-space v-if="relationNews?.length > 0" direction="vertical" fill class="common-padding">
<news-item-mini v-for="item in relationNews" :key="item.newsId" :news="item" :img="item.newsImage" <news-item-mini
:title="item.newsTitle" :from="`${item.newsDate} · ${item.newsOrg}`" v-for="item in relationNews"
@click="gotoNewsDetail(item.newsId)" /> :key="item.newsId"
:news="item"
:img="item.newsImage"
:title="item.newsTitle"
:from="`${item.newsDate} · ${item.newsOrg}`"
@click="gotoNewsDetail(item.newsId)"
/>
</el-space> </el-space>
<el-empty v-else style=""></el-empty> <el-empty v-else style=""></el-empty>
</el-space> </el-space>
...@@ -68,21 +93,22 @@ ...@@ -68,21 +93,22 @@
</template> </template>
<script setup> <script setup>
import { getNewsDetail } from "@/api/news/newsBrief"; import { getNewsDetail } from "@/api/news/newsBrief";
import '@/styles/container.scss'; import "@/styles/container.scss";
import '@/styles/common.scss'; import "@/styles/common.scss";
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { ElSpace, ElButton, ElScrollbar, ElSwitch, ElEmpty, ElImage } from "element-plus"; import { ElSpace, ElButton, ElScrollbar, ElSwitch, ElEmpty, ElImage } from "element-plus";
import CommonText from "@/components/base/texts/CommonText.vue"; import CommonText from "@/components/base/texts/CommonText.vue";
import AreaTag from "@/components/base/AreaTag/index.vue"; import AreaTag from "@/components/base/AreaTag/index.vue";
import ColorPrefixTitle from '@/components/base/texts/ColorPrefixTitle.vue'; import ColorPrefixTitle from "@/components/base/texts/ColorPrefixTitle.vue";
import { getRelationNews } from "@/api/news/newsDetail"; import { getRelationNews } from "@/api/news/newsDetail";
import NewsItemMini from "@/components/base/newsList/NewsItemMini.vue"; import NewsItemMini from "@/components/base/newsList/NewsItemMini.vue";
import ColorSvg from "@/components/base/images/ColorSvg.vue"; import ColorSvg from "@/components/base/images/ColorSvg.vue";
import TranslationSvg from './assets/images/翻译 1.svg'; import TranslationSvg from "./assets/images/翻译 1.svg";
import NewsLogo from './assets/images/组合 293.svg'; import NewsLogo from "./assets/images/组合 293.svg";
import { extractTextEntity } from "@/api/intelligent"; import { extractTextEntity } from "@/api/intelligent";
import { useGotoNewsDetail } from "@/router/modules/news"; import { useGotoNewsDetail } from "@/router/modules/news";
import { useGotoSearchResults } from "@/router/modules/comprehensiveSearch";
import TextTranslatePane from "@/components/base/texts/TextTranslatePane.vue"; import TextTranslatePane from "@/components/base/texts/TextTranslatePane.vue";
const newsDetail = ref({}); const newsDetail = ref({});
...@@ -94,36 +120,34 @@ const textZns = ref([]); ...@@ -94,36 +120,34 @@ const textZns = ref([]);
const textEns = ref([]); const textEns = ref([]);
const route = useRoute(); const route = useRoute();
const gotoNewsDetail = useGotoNewsDetail(); const gotoNewsDetail = useGotoNewsDetail();
const gotoSearchResults = useGotoSearchResults();
onMounted(async () => { onMounted(async () => {
const params = { const params = {
newsId: route.params.id newsId: route.params.id
} };
const { data: newsDetailData } = await getNewsDetail(params); const { data: newsDetailData } = await getNewsDetail(params);
newsDetail.value = newsDetailData ?? {}; newsDetail.value = newsDetailData ?? {};
textZns.value = newsDetail.value?.contentZh?.split('\n') ?? []; textZns.value = newsDetail.value?.contentZh?.split("\n") ?? [];
textEns.value = newsDetail.value?.content?.split('\n') ?? []; textEns.value = newsDetail.value?.content?.split("\n") ?? [];
const { data: relationNewsData } = await getRelationNews(params); const { data: relationNewsData } = await getRelationNews(params);
relationNews.value = relationNewsData ?? []; relationNews.value = relationNewsData ?? [];
await handleHighlightEntity(); await handleHighlightEntity();
}); });
async function handleHighlightEntity() { async function handleHighlightEntity() {
if (textEntities.value.length > 0 if (textEntities.value.length > 0 || (!newsDetail.value?.contentZh && !newsDetail.value?.content)) return;
|| (!newsDetail.value?.contentZh && !newsDetail.value?.content)) return const { result: entityDataZh } = await extractTextEntity(newsDetail.value.contentZh ?? "");
const { result: entityDataZh } = await extractTextEntity(newsDetail.value.contentZh ?? ''); textEntities.value = [...(entityDataZh ?? [])];
textEntities.value = [...entityDataZh ?? []]
if (newsDetail.value.contentZh !== newsDetail.value.content) { if (newsDetail.value.contentZh !== newsDetail.value.content) {
const { result: entityData } = await extractTextEntity(newsDetail.value.content ?? ''); const { result: entityData } = await extractTextEntity(newsDetail.value.content ?? "");
textEntities.value = [...textEntities.value, ...entityData ?? []] textEntities.value = [...textEntities.value, ...(entityData ?? [])];
} }
console.log(isHightLightEntity.value) console.log(isHightLightEntity.value);
} }
function handleTranslation() { function handleTranslation() {
isOpenTranslation.value = !isOpenTranslation.value; isOpenTranslation.value = !isOpenTranslation.value;
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import url("./style.css"); @import url("./style.css");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论