提交 7a10f6fc authored 作者: 朱政's avatar 朱政

Merge branch 'pre' into zz-dev

流水线 #226 已通过 于阶段
in 1 分 24 秒
差异被折叠。
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
<div class="icon"> <div class="icon">
<img :src="item.icon" alt="" /> <img :src="item.icon" alt="" />
</div> </div>
<div class="title">{{ item.title }}</div> <div class="title" :class="{ 'active-title': item.active }">{{ item.title }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -157,57 +157,68 @@ const menuList = ref([ ...@@ -157,57 +157,68 @@ const menuList = ref([
{ {
title: "科技法案", title: "科技法案",
icon: Menu2, icon: Menu2,
path: "/billHome" path: "/billHome",
active: false
}, },
{ {
title: "科技政令", title: "科技政令",
icon: Menu3, icon: Menu3,
path: "/decree" path: "/decree",
active: false
}, },
{ {
title: "美国科技智库", title: "美国科技智库",
icon: Menu4, icon: Menu4,
path: "/thinkTank" path: "/thinkTank",
active: false
}, },
{ {
title: "出口管制", title: "出口管制",
icon: Menu5, icon: Menu5,
path: "/exportControl" path: "/exportControl",
active: false
}, },
{ {
title: "科研合作限制", title: "科研合作限制",
icon: Menu6, icon: Menu6,
path: "/cooperationRestrictions" path: "/cooperationRestrictions",
active: false
}, },
{ {
title: "投融资限制", title: "投融资限制",
icon: Menu7, icon: Menu7,
path: "/finance" path: "/finance",
active: false
}, },
{ {
title: "市场准入限制", title: "市场准入限制",
icon: Menu8, icon: Menu8,
path: "/marketAccessRestrictions" path: "/marketAccessRestrictions",
active: false
}, },
{ {
title: "规则限制", title: "规则限制",
icon: Menu9, icon: Menu9,
path: "/ruleRestrictions" path: "/ruleRestrictions",
active: false
}, },
{ {
title: "美国科技人物观点", title: "美国科技人物观点",
icon: Menu10, icon: Menu10,
path: "/technologyFigures" path: "/technologyFigures",
active: false
}, },
{ {
title: "美国主要创新主体动向", title: "美国主要创新主体动向",
icon: Menu11, icon: Menu11,
path: "/innovationSubject" path: "/innovationSubject",
active: false
}, },
{ {
title: "美国科研资助体系", title: "美国科研资助体系",
icon: Menu12, icon: Menu12,
path: "/scientificFunding" path: "/scientificFunding",
active: false
} }
]); ]);
...@@ -235,6 +246,7 @@ const handleToModule = (item, index) => { ...@@ -235,6 +246,7 @@ const handleToModule = (item, index) => {
window.sessionStorage.setItem('homeActiveTitleIndex', index) window.sessionStorage.setItem('homeActiveTitleIndex', index)
if (index === 1) { if (index === 1) {
homeActiveTitleIndex.value = index homeActiveTitleIndex.value = index
item.active = true
router.push({ router.push({
path: item.path path: item.path
}) })
...@@ -497,6 +509,11 @@ onUnmounted(() => { ...@@ -497,6 +509,11 @@ onUnmounted(() => {
letter-spacing: 0px; letter-spacing: 0px;
text-align: left; text-align: left;
} }
.active-title {
color: var(--color-main-active) !important;
font-size: 20px !important;
}
} }
} }
} }
...@@ -528,6 +545,8 @@ onUnmounted(() => { ...@@ -528,6 +545,8 @@ onUnmounted(() => {
display: flex; display: flex;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
.title { .title {
color: var(--color-main-active); color: var(--color-main-active);
...@@ -557,6 +576,11 @@ onUnmounted(() => { ...@@ -557,6 +576,11 @@ onUnmounted(() => {
letter-spacing: 0px; letter-spacing: 0px;
text-align: left; text-align: left;
} }
.active-title {
color: var(--color-main-active) !important;
font-size: 20px !important;
}
} }
} }
} }
......
<template>
<div class="intelligenceLeftTabBar">
<div class="navBox" :class="{navBoxShow:isNavMenuShow}">
<div class="navList" v-for="(item,index) in navList " :key="index" :class="{on:navPath==item.path}" @click="onNavListClick(item.path)">
<div class="icon" :style="{background:`url(${item.img})no-repeat`,backgroundSize:'24px 24px',backgroundPosition:'17px 17px'}"></div>
<span class="text-tip-1" style="white-space: nowrap; ">{{ item.name }}</span>
</div>
</div>
<img class="show" src="@/assets/icons/muenShow.png" :style="isNavMenuShow?'transform: scaleX(1)':''" alt="" @click="()=>{isNavMenuShow=!isNavMenuShow}">
</div>
</template>
<script setup>
import muen1 from '@/assets/icons/tool-item-icon1.png'
import muen2 from '@/assets/icons/tool-item-icon2.png'
import muen3 from '@/assets/icons/tool-item-icon3.png'
import muen4 from '@/assets/icons/tool-item-icon4.png'
import { onMounted, onUnmounted, ref, nextTick } from "vue";
import { useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
const isNavMenuShow=ref(false)
const navList=ref([
{
img:muen1,
path:'/writtingAsstaint',
name:'智能写报'
},
{
img:muen2,
path:'/writtingAsstaint1',
name:'智能翻译'
},
{
img:muen3,
path:'/writtingAsstaint2',
name:'智能查询'
},
{
img:muen4,
path:'/writtingAsstaint3',
name:'智能对话'
},
])
const navPath=ref()
const route=useRoute()
if(route.path){
navPath.value=route.path
}
const onNavListClick=(path)=>{
if(path=='/writtingAsstaint'){
navPath.value=path
}else{
ElMessage.error('正在开发中')
}
}
</script>
<style lang="scss" scoped>
.intelligenceLeftTabBar{
padding: 5px 0;
border-right: 1px solid rgb(234, 236, 238);
position: relative;
.navBox{
height: 100%;
width: 65px;
transition: all 0.3s;
padding: 0 3px;
.navList{
display: flex;
align-items: center;
cursor: pointer;
border-radius: 8px;
overflow: hidden;
height: 60px;
.icon{
width: 60px;
height: 60px;
flex-shrink: 0;
border-radius: 10px;
margin-right: 15px;
}
}
.on{
background-color: var(--color-primary-10);
color: var(--color-primary-100);
font-weight: Bold;
}
}
.navBoxShow{
width: 200px;
transition: all 0.3s;
}
.show{
position: absolute;
width: 24px;
height: 24px;
right: 21px;
bottom: 21px;
cursor: pointer;
transform: scaleX(-1)
}
}
</style>
\ No newline at end of file
...@@ -295,12 +295,15 @@ export function useMarkdownStream() { ...@@ -295,12 +295,15 @@ export function useMarkdownStream() {
// 预处理内容 // 预处理内容
// const processedContent = preprocessMarkdown(rawContent.value) // const processedContent = preprocessMarkdown(rawContent.value)
let content = rawContent.value || '' let content = rawContent.value || ''
// 将 ==n== 转换为按钮样式的 HTML // 将 ==n== 转换为按钮样式的 HTML
// 使用正向预读和反向预读确保只匹配被 == 包裹的数字 // 使用正向预读和反向预读确保只匹配被 == 包裹的数字
content = content.replace(/==(\d+)==/g, (match, p1) => { // content = content.replace(/==(\d+)、==/g, (match, p1) => {
return `<button class="clause-ref-btn" data-clause="${p1}">${p1}</button>` // return `<button class="clause-ref-btn" data-clause="${p1}">${p1}</button>`
}) // })
console.log(content,11223)
content = content.replace(/==\s*(\d+)、.*?==/g, (match, p1) => {
return `<button class="clause-ref-btn" data-clause="${match.replace(/==/g, '') }">${p1}</button>`;
});
return md.render(content) return md.render(content)
}) })
......
const getQuarterRange = (quatarNum) => {
const quarters = {
1: ['2025-01-01', '2025-03-31'],
2: ['2025-04-01', '2025-06-30'],
3: ['2025-07-01', '2025-09-30'],
4: ['2025-10-01', '2025-12-31']
};
return quarters[quatarNum];
}
export default getQuarterRange
\ No newline at end of file
// 绘制echarts图表 // 绘制echarts图表
import getMonthRange from './getMonthRange' import getMonthRange from './getMonthRange'
import getQuarterRange from './getQuarterRange';
import * as echarts from 'echarts' import * as echarts from 'echarts'
import 'echarts-wordcloud'; import 'echarts-wordcloud';
import router from '@/router/index' import router from '@/router/index'
...@@ -77,6 +78,26 @@ const setChart = (option, chartId, allowClick, selectParam) => { ...@@ -77,6 +78,26 @@ const setChart = (option, chartId, allowClick, selectParam) => {
} }
} }
break
case '政令':
if (params.componentType === 'series' && params.seriesType === 'pie') {
selectParam.domains = params.name
const route = router.resolve({
path: "/dataLibrary/dataDecree",
query: selectParam
});
window.open(route.href, "_blank");
} else if (params.componentType === 'series' && params.seriesType === 'bar') {
const quatarNum = Number(params.name[params.name.length - 1])
selectParam.selectedDate = JSON.stringify(getQuarterRange(quatarNum))
const route = router.resolve({
path: "/dataLibrary/dataDecree",
query: selectParam
});
window.open(route.href, "_blank");
}
} }
......
<svg viewBox="0 0 12 13" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.000000" height="13.000000" fill="none" customFrame="#000000">
<path id="矢量 1651" d="M8.05031 2.15239e-07C8.33835 2.69049e-07 8.61901 0.0965686 8.85205 0.275865C9.08508 0.455161 9.25853 0.70798 9.34755 0.998087C9.43656 1.28819 9.43656 1.6007 9.34755 1.8908C9.25853 2.18091 9.08508 2.43373 8.85205 2.61303C8.61901 2.79232 8.33835 2.88889 8.05031 2.88889L3.9583 2.88889C3.69558 2.88873 3.43848 2.80821 3.21793 2.65703C2.99737 2.50585 2.82274 2.29043 2.71502 2.03667C2.35984 2.08328 2.0252 2.23849 1.752 2.48332C1.4788 2.72814 1.27889 3.05197 1.17673 3.41519C1.07456 3.77842 1.07456 4.1653 1.17673 4.52853C1.27889 4.89176 1.4788 5.21558 1.752 5.46041C2.0252 5.70523 2.35984 5.86044 2.71502 5.90706C2.82284 5.65343 2.99751 5.43816 3.21806 5.28711C3.43861 5.13606 3.69565 5.05566 3.9583 5.05556L8.05031 5.05556C8.31755 5.05563 8.57888 5.13883 8.80185 5.29483C9.02483 5.45084 9.19961 5.67276 9.3045 5.93306C9.88944 5.98317 10.4467 6.21802 10.9042 6.60722C11.3618 6.99642 11.6985 7.52206 11.8708 8.11613C12.0431 8.7102 12.0431 9.34536 11.8708 9.93943C11.6985 10.5335 11.3618 11.0591 10.9042 11.4483C10.4467 11.8375 9.88944 12.0724 9.3045 12.1225C9.19961 12.3828 9.02483 12.6047 8.80185 12.7607C8.57888 12.9167 8.31755 12.9999 8.05031 13L3.9583 13C3.71881 13.0001 3.48352 12.9334 3.27608 12.8067C3.06865 12.6799 2.89638 12.4976 2.77661 12.2779C2.65684 12.0583 2.59378 11.8092 2.59378 11.5556C2.59378 11.3019 2.65684 11.0528 2.77661 10.8332C2.89638 10.6136 3.06865 10.4312 3.27608 10.3044C3.48352 10.1777 3.71881 10.111 3.9583 10.1111L8.05031 10.1111C8.60409 10.1111 9.07944 10.4607 9.29291 10.9626C9.64884 10.9171 9.98445 10.7624 10.2585 10.5177C10.5326 10.273 10.7333 9.94881 10.8358 9.58501C10.9384 9.22121 10.9384 8.83362 10.8358 8.46982C10.7333 8.10603 10.5326 7.78186 10.2585 7.53714C9.98445 7.29241 9.64884 7.13778 9.29291 7.09222C9.18531 7.34593 9.0108 7.56132 8.79036 7.71251C8.56992 7.86369 8.31294 7.94423 8.05031 7.94444L3.9583 7.94444C3.69087 7.94445 3.42933 7.86121 3.20622 7.70506C2.9831 7.54892 2.80826 7.32676 2.70342 7.06622C2.11732 7.01784 1.55849 6.78394 1.09952 6.39491C0.640554 6.00587 0.302655 5.47968 0.129709 4.88468C-0.0432364 4.28967 -0.0432363 3.65334 0.129709 3.05833C0.302654 2.46332 0.640554 1.93713 1.09952 1.54809C1.55849 1.15906 2.11732 0.925163 2.70342 0.876778C2.80847 0.616508 2.9834 0.394654 3.2065 0.238777C3.4296 0.0829001 3.69102 -0.000130273 3.9583 2.15239e-07L8.05031 2.15239e-07Z" fill="rgb(95,101,108)" fill-rule="nonzero" />
</svg>
<svg viewBox="0 0 12 13.3145" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.000000" height="13.314453" fill="none" customFrame="#000000">
<path id="矢量 52" d="M2.47123 9.11862L8.566 3.02386C8.82631 2.76355 8.82631 2.3415 8.566 2.08119C8.30569 1.82088 7.88364 1.82088 7.62333 2.08119L1.52857 8.17595C1.40356 8.30096 1.33333 8.4705 1.33333 8.64729C1.33333 9.01542 1.63176 9.31385 1.9999 9.31385C2.17668 9.31385 2.34623 9.24363 2.47123 9.11862ZM2.82867 10.6472L1 10.6472C0.447715 10.6472 0 10.1995 0 9.64719L0 8.23273C0 7.96752 0.105357 7.71316 0.292894 7.52563L7.62333 0.195193C7.74833 0.0702133 7.91787 0 8.09467 0C8.27147 0 8.441 0.0702133 8.566 0.195193L10.452 2.08119C10.577 2.20621 10.6472 2.37575 10.6472 2.55253C10.6472 2.7293 10.577 2.89884 10.452 3.02386L2.82867 10.6472L2.82867 10.6472ZM0.666666 11.9805L11.3333 11.9805C11.7015 11.9805 12 12.279 12 12.6472C12 13.0154 11.7015 13.3139 11.3333 13.3139L0.666667 13.3139C0.298477 13.3139 0 13.0154 0 12.6472C0 12.279 0.298477 11.9805 0.666666 11.9805Z" fill="rgb(255,255,255)" fill-rule="evenodd" />
</svg>
...@@ -38,13 +38,9 @@ ...@@ -38,13 +38,9 @@
</div> </div>
</template> </template>
<template v-else> <template v-else>
<div <div class="left-box-bottom-item"
class="left-box-bottom-item" :class="{ leftBoxBottomItemActive: activeTitle === item.name }" v-for="item in tabs"
:class="{ leftBoxBottomItemActive: activeTitle === item.name }" :key="item.path" @click="emit('tab-click', item)">
v-for="item in tabs"
:key="item.path"
@click="emit('tab-click', item)"
>
<div class="icon"> <div class="icon">
<img v-if="activeTitle === item.name" :src="item.activeIcon" alt="" /> <img v-if="activeTitle === item.name" :src="item.activeIcon" alt="" />
<img v-else :src="item.icon" alt="" /> <img v-else :src="item.icon" alt="" />
...@@ -88,7 +84,13 @@ ...@@ -88,7 +84,13 @@
</div> </div>
</template> </template>
<template v-else> <template v-else>
<div class="btn3" @click="emit('open-analysis')"> <div class="btn2" @click="emit('open-analysis', 'forsee')">
<div class="icon">
<img :src="btnIconForsee" alt="" />
</div>
<div class="text">{{ "进展预测" }}</div>
</div>
<div class="btn3" @click="emit('open-analysis', 'analysis')">
<div class="icon"> <div class="icon">
<img :src="btnIconAnalysis" alt="" /> <img :src="btnIconAnalysis" alt="" />
</div> </div>
...@@ -103,7 +105,8 @@ ...@@ -103,7 +105,8 @@
<script setup> <script setup>
import { computed } from "vue"; import { computed } from "vue";
import btnIconAnalysis from "@/views/thinkTank/ReportDetail/images/btn-icon3.png"; import btnIconAnalysis from "@/views/bill/billLayout/assets/icons/writting-icon.svg";
import btnIconForsee from "@/views/bill/billLayout/assets/icons/forsee-icon.svg";
const props = defineProps({ const props = defineProps({
billInfo: { billInfo: {
...@@ -347,6 +350,29 @@ const emit = defineEmits(["tab-click", "open-analysis"]); ...@@ -347,6 +350,29 @@ const emit = defineEmits(["tab-click", "open-analysis"]);
justify-content: flex-end; justify-content: flex-end;
gap: 8px; gap: 8px;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.btn2 {
cursor: pointer;
width: 120px;
height: 36px;
border-radius: 6px;
background: var(--bg-white-100);
border: 1px solid var(--bg-black-10);
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.btn3 { .btn3 {
cursor: pointer; cursor: pointer;
width: 120px; width: 120px;
...@@ -358,16 +384,6 @@ const emit = defineEmits(["tab-click", "open-analysis"]); ...@@ -358,16 +384,6 @@ const emit = defineEmits(["tab-click", "open-analysis"]);
align-items: center; align-items: center;
gap: 8px; gap: 8px;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text { .text {
height: 24px; height: 24px;
color: rgba(255, 255, 255, 1); color: rgba(255, 255, 255, 1);
...@@ -387,4 +403,3 @@ const emit = defineEmits(["tab-click", "open-analysis"]); ...@@ -387,4 +403,3 @@ const emit = defineEmits(["tab-click", "open-analysis"]);
} }
} }
</style> </style>
...@@ -2,16 +2,9 @@ ...@@ -2,16 +2,9 @@
<div class="layout-container"> <div class="layout-container">
<!-- 导航菜单 --> <!-- 导航菜单 -->
<div class="layout-main"> <div class="layout-main">
<BillHeader <BillHeader :billInfo="billInfoGlobal" :defaultLogo="USALogo" :tabs="mainHeaderBtnList"
:billInfo="billInfoGlobal" :activeTitle="activeTitle" :showTabs="showHeaderTabs" :showActions="showHeaderActions"
:defaultLogo="USALogo" @tab-click="handleClickMainHeaderBtn" @open-analysis="handleAnalysisClick" />
:tabs="mainHeaderBtnList"
:activeTitle="activeTitle"
:showTabs="showHeaderTabs"
:showActions="showHeaderActions"
@tab-click="handleClickMainHeaderBtn"
@open-analysis="handleAnalysisClick"
/>
<div class="layout-main-center"> <div class="layout-main-center">
<router-view /> <router-view />
...@@ -115,12 +108,24 @@ const handleClickMainHeaderBtn = item => { ...@@ -115,12 +108,24 @@ const handleClickMainHeaderBtn = item => {
}); });
}; };
const handleAnalysisClick = () => { const handleAnalysisClick = analysisType => {
const billId = route.query.billId;
if (!billId) return;
// 进展预测 -> 法案简介页(法案进展)
if (analysisType === "forsee") {
router.push({
path: `/billLayout/ProgressForecast/${billId}`,
});
return;
}
// 分析报告 -> 写作助手
router.push({ router.push({
path: "/writtingAsstaint", path: "/writtingAsstaint",
query: { query: {
topic: "法案", topic: "法案",
fileId: route.query.billId fileId: String(billId)
} }
}); });
}; };
...@@ -149,11 +154,13 @@ watch( ...@@ -149,11 +154,13 @@ watch(
// height: 1016px; // height: 1016px;
background: rgba(249, 250, 252, 1); background: rgba(249, 250, 252, 1);
position: relative; position: relative;
// margin: 0 auto; // margin: 0 auto;
.layout-main { .layout-main {
width: 100%; width: 100%;
height: calc(100vh - 72px); height: 100vh;
overflow-y: auto; overflow-y: auto;
.layout-main-center { .layout-main-center {
// height: calc(100% - 137px); // height: calc(100% - 137px);
width: 1600px; width: 1600px;
......
...@@ -151,7 +151,7 @@ ...@@ -151,7 +151,7 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, watch, onMounted, nextTick } from 'vue' import { ref, computed, watch, onMounted, nextTick, onBeforeUnmount } from 'vue'
import ChartContainer from '../../components/ChartContainer/index.vue' import ChartContainer from '../../components/ChartContainer/index.vue'
import ChartHeader from '../../components/ChartHeader/index.vue' import ChartHeader from '../../components/ChartHeader/index.vue'
import ActiveTag from '../../components/ActiveTag/index.vue' import ActiveTag from '../../components/ActiveTag/index.vue'
...@@ -171,6 +171,10 @@ import getDateRange from '@/utils/getDateRange' ...@@ -171,6 +171,10 @@ import getDateRange from '@/utils/getDateRange'
const route = useRoute(); const route = useRoute();
const timer1 = ref(null)
const timer2 = ref(null)
const timer3 = ref(null)
// 图表/数据 // 图表/数据
const isShowChart = ref(false) const isShowChart = ref(false)
// 点击切换数据/图表 // 点击切换数据/图表
...@@ -180,7 +184,7 @@ const handleSwitchChartData = () => { ...@@ -180,7 +184,7 @@ const handleSwitchChartData = () => {
const curDemensionItem = staticsDemensionList.value.filter(item => { const curDemensionItem = staticsDemensionList.value.filter(item => {
return item.name === curDemension.value return item.name === curDemension.value
})[0] })[0]
setTimeout(() => { timer1.value = setTimeout(() => {
activeChart.value = curDemensionItem.chartTypeList[0] activeChart.value = curDemensionItem.chartTypeList[0]
curChartData.value = curDemensionItem.data curChartData.value = curDemensionItem.data
}) })
...@@ -267,7 +271,7 @@ const handleClickDemensionItem = (val) => { ...@@ -267,7 +271,7 @@ const handleClickDemensionItem = (val) => {
}) })
val.active = true val.active = true
curDemension.value = val.name curDemension.value = val.name
setTimeout(() => { timer2.value = setTimeout(() => {
activeChart.value = val.chartTypeList[0] activeChart.value = val.chartTypeList[0]
curChartData.value = val.data curChartData.value = val.data
}) })
...@@ -702,8 +706,8 @@ const statusList = ref([ ...@@ -702,8 +706,8 @@ const statusList = ref([
id: '参议院通过' id: '参议院通过'
}, },
{ {
name: '双院通过', name: '分歧已解决',
id: '双院通过' id: '分歧已解决'
}, },
]) ])
...@@ -778,8 +782,8 @@ const selectedCount = computed(() => selectedMap.value.size) ...@@ -778,8 +782,8 @@ const selectedCount = computed(() => selectedMap.value.size)
// 获取表格数据(示例) // 获取表格数据(示例)
const fetchTableData = async () => { const fetchTableData = async () => {
isSelectedAll.value = false // isSelectedAll.value = false
selectedMap.value.clear() // selectedMap.value.clear()
// 调用接口获取数据... // 调用接口获取数据...
const params = { const params = {
page: currentPage.value, page: currentPage.value,
...@@ -793,8 +797,8 @@ const fetchTableData = async () => { ...@@ -793,8 +797,8 @@ const fetchTableData = async () => {
originChamber: selectedCongress.value === '全部议院' ? null : selectedCongress.value, originChamber: selectedCongress.value === '全部议院' ? null : selectedCongress.value,
originDepart: selectedOrg.value === '全部委员会' ? null : selectedOrg.value, originDepart: selectedOrg.value === '全部委员会' ? null : selectedOrg.value,
sponsorPersonName: selectedmember.value === '全部议员' ? null : selectedmember.value, sponsorPersonName: selectedmember.value === '全部议员' ? null : selectedmember.value,
status: selectedStatus.value === '通过' ? 1 : 0, status: selectedStatus.value === '全部阶段' ? null : selectedStatus.value,
isInvolveCn: isInvolveCn ? 'Y' : 'N', isInvolveCn: isInvolveCn.value ? 'Y' : 'N',
sort: isSort.value ? 0 : 1 // 0 先按分数降序 后按时间降序 1 先按分数降序,再按时间升序 sort: isSort.value ? 0 : 1 // 0 先按分数降序 后按时间降序 1 先按分数降序,再按时间升序
} }
try { try {
...@@ -836,7 +840,7 @@ const fetchTableData = async () => { ...@@ -836,7 +840,7 @@ const fetchTableData = async () => {
return item.name === curDemension.value return item.name === curDemension.value
})[0] })[0]
setTimeout(() => { timer3.value = setTimeout(() => {
activeChart.value = curDemensionItem.chartTypeList[0] activeChart.value = curDemensionItem.chartTypeList[0]
curChartData.value = curDemensionItem.data curChartData.value = curDemensionItem.data
}) })
...@@ -877,7 +881,7 @@ const fetchAllData = async () => { ...@@ -877,7 +881,7 @@ const fetchAllData = async () => {
originDepart: selectedOrg.value === '全部委员会' ? null : selectedOrg.value, originDepart: selectedOrg.value === '全部委员会' ? null : selectedOrg.value,
sponsorPersonName: selectedmember.value === '全部议员' ? null : selectedmember.value, sponsorPersonName: selectedmember.value === '全部议员' ? null : selectedmember.value,
status: selectedStatus.value === '通过' ? 1 : 0, status: selectedStatus.value === '通过' ? 1 : 0,
isInvolveCn: isInvolveCn ? 'Y' : 'N', isInvolveCn: isInvolveCn.value ? 'Y' : 'N',
sort: isSort.value ? 0 : 1 // 0 先按分数降序 后按时间降序 1 先按分数降序,再按时间升序 sort: isSort.value ? 0 : 1 // 0 先按分数降序 后按时间降序 1 先按分数降序,再按时间升序
} }
try { try {
...@@ -1116,14 +1120,21 @@ const handleExport = () => { ...@@ -1116,14 +1120,21 @@ const handleExport = () => {
onMounted(async () => { onMounted(async () => {
handleGetOrgList() handleGetOrgList()
handleGetMemberList() handleGetMemberList()
initParam() initParam()
// 初始化 // 初始化
await fetchTableData() await fetchTableData()
})
onBeforeUnmount(() => {
if (timer1.value) {
clearTimeout(timer1.value)
}
if (timer2.value) {
clearTimeout(timer2.value)
}
if (timer3.value) {
clearTimeout(timer3.value)
}
}) })
</script> </script>
...@@ -1239,10 +1250,11 @@ onMounted(async () => { ...@@ -1239,10 +1250,11 @@ onMounted(async () => {
.data-main-box { .data-main-box {
width: 1568px; width: 1568px;
height: 810px; min-height: 810px;
border-radius: 10px; border-radius: 10px;
background: var(--bg-white-100); background: var(--bg-white-100);
margin: 0 auto; margin: 0 auto;
margin-bottom: 20px;
overflow: hidden; overflow: hidden;
.data-main-box-header { .data-main-box-header {
...@@ -1274,8 +1286,7 @@ onMounted(async () => { ...@@ -1274,8 +1286,7 @@ onMounted(async () => {
.data-main-box-main { .data-main-box-main {
width: 1520px; width: 1520px;
// height: 633px; min-height: 680px;
height: 680px;
border-radius: 10px; border-radius: 10px;
border: 1px solid var(--bg-black-5); border: 1px solid var(--bg-black-5);
margin: 0 auto; margin: 0 auto;
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
<slot name="chart-box"></slot> <slot name="chart-box"></slot>
</div> </div>
<div class="tip-box"> <div class="tip-box">
<TipTab /> <TipTab text="数据来源:美国国会官网" />
</div> </div>
</div> </div>
</div> </div>
...@@ -176,8 +176,7 @@ const chartItemList = computed(() => { ...@@ -176,8 +176,7 @@ const chartItemList = computed(() => {
.tip-box { .tip-box {
height: 54px; height: 54px;
box-sizing: border-box; box-sizing: border-box;
padding-top: 10px; padding: 15px 600px;
// background: orange;
} }
} }
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div class="select-wrapper" :class="{ 'select-wrapper-custom': selectValue === '自定义' }"> <div class="select-wrapper" :class="{ 'select-wrapper-custom': selectValue === '自定义' }">
<div class="select-left text-tip-1">{{ selectTitle + ':' }}</div> <div class="select-left text-tip-1">{{ selectTitle + ':' }}</div>
<div class="select-right" :class="{ 'select-right-custom': selectValue === '自定义' }"> <div class="select-right" :class="{ 'select-right-custom': selectValue === '自定义' }">
<el-select v-model="selectValue" :placeholder="placeholderName" style="width: 240px"> <el-select v-model="selectValue" :placeholder="placeholderName" filterable style="width: 240px">
<!-- <el-option label="全部领域" value="全部领域" /> --> <!-- <el-option label="全部领域" value="全部领域" /> -->
<el-option v-for="item in selectList" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in selectList" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
......
...@@ -128,7 +128,7 @@ ...@@ -128,7 +128,7 @@
<el-table ref="tableRef" :data="tableData" row-key="id" @selection-change="handleSelectionChange" <el-table ref="tableRef" :data="tableData" row-key="id" @selection-change="handleSelectionChange"
@select="handleSelect" @select-all="handleSelectAll" style="width: 100%" :row-style="{ height: '52px' }"> @select="handleSelect" @select-all="handleSelectAll" style="width: 100%" :row-style="{ height: '52px' }">
<el-table-column type="selection" width="40" /> <el-table-column type="selection" width="40" />
<el-table-column label="法案名称" width="455"> <el-table-column label="政令名称" width="720">
<template #default="scope"> <template #default="scope">
<span class="title-item text-compact-bold" @click="handleClickToDetail(scope.row)">{{ scope.row.title <span class="title-item text-compact-bold" @click="handleClickToDetail(scope.row)">{{ scope.row.title
}}</span> }}</span>
...@@ -137,15 +137,23 @@ ...@@ -137,15 +137,23 @@
<el-table-column label="发布时间" width="120" class-name="date-column"> <el-table-column label="发布时间" width="120" class-name="date-column">
<template #default="scope">{{ scope.row.date }}</template> <template #default="scope">{{ scope.row.date }}</template>
</el-table-column> </el-table-column>
<el-table-column label="提案人" width="480"> <el-table-column label="发布机构" width="180">
<template #default="scope"> <template #default="scope">
<span class="person-item text-compact" @click="handlePerClick(scope.row)">{{ scope.row.sponsorPersonName <span class="person-item text-compact" @click="handlePerClick(scope.row)">{{ scope.row.organizationName
}}</span> }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column property="affiliation" label="所属党派" width="120" /> <el-table-column label="涉及领域" width="350" class-name="date-column">
<el-table-column property="originDepart" label="提出委员会" width="180" /> <template #default="scope">
<el-table-column property="status" label="所处阶段" width="120" /> <div class="tag-box">
<AreaTag v-for="tag, index in scope.row.domains" :key="index" :tagName="tag" />
</div>
</template>
</el-table-column>
<el-table-column label="政令类型" width="100">
<template #default="scope">{{ scope.row.typeStr }}</template>
</el-table-column>
</el-table> </el-table>
</div> </div>
</div> </div>
...@@ -175,6 +183,8 @@ import { search } from '@/api/comprehensiveSearch' ...@@ -175,6 +183,8 @@ import { search } from '@/api/comprehensiveSearch'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import getDateRange from '@/utils/getDateRange' import getDateRange from '@/utils/getDateRange'
import { getDepartmentList } from "@/api/decree/home";
const route = useRoute(); const route = useRoute();
// 图表/数据 // 图表/数据
...@@ -363,7 +373,7 @@ const activeTagList = computed(() => { ...@@ -363,7 +373,7 @@ const activeTagList = computed(() => {
} }
if (isInvolveTechnology.value) { if (isInvolveTechnology.value) {
const involveStr = '政令相关' const involveStr = '科技相关'
arr.push( arr.push(
{ {
tag: '科技相关', tag: '科技相关',
...@@ -593,18 +603,6 @@ const insList = ref([ ...@@ -593,18 +603,6 @@ const insList = ref([
name: '全部机构', name: '全部机构',
id: '全部机构' id: '全部机构'
}, },
{
name: '机构1',
id: '机构1'
},
{
name: '机构2',
id: '机构2'
},
{
name: '其他',
id: '其他'
},
]) ])
const selectedIns = ref('全部机构') const selectedIns = ref('全部机构')
const insPlaceHolder = ref('请选择发布机构') const insPlaceHolder = ref('请选择发布机构')
...@@ -612,6 +610,30 @@ const handleSelectIns = value => { ...@@ -612,6 +610,30 @@ const handleSelectIns = value => {
selectedIns.value = value selectedIns.value = value
} }
const handleGetDeparmentList = async () => {
try {
// let { keyWord, pageNum, pageSize, day } = organizationInfo
const params = {
day: 365,
}
const res = await getDepartmentList(params);
console.log("机构列表", res);
if (res.code === 200) {
let arr = res.data.orgList.map(item => {
return {
name: item.orgName,
id: item.orgName
}
})
insList.value = [...insList.value, ...arr]
}
} catch (error) {
}
}
// 政令类型列表 // 政令类型列表
const decreeTypeList = ref([ const decreeTypeList = ref([
{ {
...@@ -642,6 +664,7 @@ const isInvolveTechnology = ref(false) ...@@ -642,6 +664,7 @@ const isInvolveTechnology = ref(false)
const handleClear = () => { const handleClear = () => {
selectedArea.value = '全部领域' selectedArea.value = '全部领域'
selectedDate.value = '' selectedDate.value = ''
customTime.value = []
selectedIns.value = '全部机构' selectedIns.value = '全部机构'
selectedDecreeType.value = '全部类型' selectedDecreeType.value = '全部类型'
isInvolveCn.value = false isInvolveCn.value = false
...@@ -701,12 +724,12 @@ const fetchTableData = async () => { ...@@ -701,12 +724,12 @@ const fetchTableData = async () => {
keyword: '', keyword: '',
type: 2, // type 1= 法案 2= 政令 3 =智库 4=智库报告 5=实体清单【制裁记录】 6= 人物 7= 机构 8=新闻 9= 社媒 type: 2, // type 1= 法案 2= 政令 3 =智库 4=智库报告 5=实体清单【制裁记录】 6= 人物 7= 机构 8=新闻 9= 社媒
domains: selectedArea.value === '全部领域' ? null : [selectedArea.value], domains: selectedArea.value === '全部领域' ? null : [selectedArea.value],
proposedDateStart: customTime.value[0], proposedDateStart: customTime.value[0]?customTime.value[0]:null,
proposedDateEnd: customTime.value[1], proposedDateEnd: customTime.value[1]?customTime.value[1]:null,
affiliation: selectedIns.value === '全部机构' ? null : selectedIns.value, organizationName: selectedIns.value === '全部机构' ? null : selectedIns.value,
originChamber: selectedDecreeType.value === '全部类型' ? null : selectedDecreeType.value, decreeType: selectedDecreeType.value === '全部类型' ? null : selectedDecreeType.value,
sleStatus: isInvolveCn ? 'Y' : 'N', isInvolveCn: isInvolveCn.value ? 'Y' : 'N',
aaaa: isInvolveTechnology? 'Y' : 'N', isTechRelated: isInvolveTechnology.value ? 'Y' : 'N',
sort: isSort.value ? 0 : 1 // 0 先按分数降序 后按时间降序 1 先按分数降序,再按时间升序 sort: isSort.value ? 0 : 1 // 0 先按分数降序 后按时间降序 1 先按分数降序,再按时间升序
} }
try { try {
...@@ -731,7 +754,7 @@ const fetchTableData = async () => { ...@@ -731,7 +754,7 @@ const fetchTableData = async () => {
name: key, name: key,
value: Number(value) value: Number(value)
})) }))
staticsDemensionList.value[2].data = Object.entries(res.data.aggregationsOriginChamber).map(([key, value]) => ({ staticsDemensionList.value[2].data = Object.entries(res.data.aggregationsorganizationName).map(([key, value]) => ({
name: key, name: key,
value: Number(value) value: Number(value)
})) }))
...@@ -773,11 +796,11 @@ const fetchAllData = async () => { ...@@ -773,11 +796,11 @@ const fetchAllData = async () => {
domains: selectedArea.value === '全部领域' ? null : [selectedArea.value], domains: selectedArea.value === '全部领域' ? null : [selectedArea.value],
proposedDateStart: customTime.value[0], proposedDateStart: customTime.value[0],
proposedDateEnd: customTime.value[1], proposedDateEnd: customTime.value[1],
affiliation: selectedIns.value === '全部机构' ? null : selectedIns.value, organizationName: selectedIns.value === '全部机构' ? null : selectedIns.value,
originChamber: selectedDecreeType.value === '全部类型' ? null : selectedDecreeType.value, decreeType: selectedDecreeType.value === '全部类型' ? null : selectedDecreeType.value,
sleStatus: isInvolveCn ? 'Y' : 'N', isInvolveCn: isInvolveCn.value ? 'Y' : 'N',
aaaa: isInvolveTechnology? 'Y' : 'N', isTechRelated: isInvolveTechnology.value ? 'Y' : 'N',
sort: isSort.value ? 0 : 1 // 0 先按分数降序 后按时间降序 1 先按分数降序,再按时间升序 sort: isSort.value ? 0 : 1
} }
try { try {
const res = await search(params) const res = await search(params)
...@@ -933,6 +956,9 @@ const initParam = () => { ...@@ -933,6 +956,9 @@ const initParam = () => {
selectedDate.value = '自定义' selectedDate.value = '自定义'
customTime.value = JSON.parse(route.query.selectedDate) customTime.value = JSON.parse(route.query.selectedDate)
} }
selectedIns.value = route.query.orgnizationName? route.query.orgnizationName : '全部机构'
isInvolveCn.value = route.query.isInvolveCn ? true : false isInvolveCn.value = route.query.isInvolveCn ? true : false
isInvolveTechnology.value = route.query.isInvolveTechnology ? true : false isInvolveTechnology.value = route.query.isInvolveTechnology ? true : false
...@@ -946,7 +972,7 @@ const initParam = () => { ...@@ -946,7 +972,7 @@ const initParam = () => {
} else { } else {
const savedQuery = JSON.parse(sessionStorage.getItem('decreeRouteQuery') || '{}'); const savedQuery = JSON.parse(sessionStorage.getItem('decreeRouteQuery') || '{}');
selectedArea.value = savedQuery.domains ? savedQuery.domains : '全部领域' selectedArea.value = savedQuery.domains ? savedQuery.domains : '全部领域'
if (Array.isArray(JSON.parse(savedQuery.selectedDate)) && JSON.parse(savedQuery.selectedDate).length) { if (savedQuery.selectedDate && Array.isArray(JSON.parse(savedQuery.selectedDate)) && JSON.parse(savedQuery.selectedDate).length) {
selectedDate.value = '自定义' selectedDate.value = '自定义'
customTime.value = JSON.parse(savedQuery.selectedDate) customTime.value = JSON.parse(savedQuery.selectedDate)
} }
...@@ -959,16 +985,16 @@ const initParam = () => { ...@@ -959,16 +985,16 @@ const initParam = () => {
} }
// 跳转法案详情 // 跳转政令详情
const handleClickToDetail = (curBill) => { const handleClickToDetail = (curDecree) => {
console.log('curBill', curBill); console.log('curDecree', curDecree);
window.sessionStorage.setItem("billId", curBill.id); window.sessionStorage.setItem("billId", curDecree.id);
window.sessionStorage.setItem("curTabName", curBill.title); window.sessionStorage.setItem("curTabName", curDecree.title);
const route = router.resolve({ const route = router.resolve({
path: "/billLayout", path: "/decreeLayout",
query: { query: {
billId: curBill.id billId: curDecree.id
} }
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
...@@ -989,13 +1015,16 @@ const handlePerClick = item => { ...@@ -989,13 +1015,16 @@ const handlePerClick = item => {
// 导出 // 导出
const handleExport = () => { const handleExport = () => {
if(!selectedCount.value) {
ElMessage.warning('请选择至少一项数据!')
return
}
console.log(selectedMap.value); console.log(selectedMap.value);
const arr = Array.from(selectedMap.value); const arr = Array.from(selectedMap.value);
const jsonStr = JSON.stringify(arr, null, 2); const jsonStr = JSON.stringify(arr, null, 2);
const blob = new Blob([jsonStr], { type: 'application/json' }); const blob = new Blob([jsonStr], { type: 'application/json' });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const link = document.createElement('a'); const link = document.createElement('a');
link.href = url; link.href = url;
link.download = 'export.json'; link.download = 'export.json';
...@@ -1005,7 +1034,7 @@ const handleExport = () => { ...@@ -1005,7 +1034,7 @@ const handleExport = () => {
}; };
onMounted(async () => { onMounted(async () => {
handleGetDeparmentList()
initParam() initParam()
// 初始化 // 初始化
...@@ -1127,10 +1156,11 @@ onMounted(async () => { ...@@ -1127,10 +1156,11 @@ onMounted(async () => {
.data-main-box { .data-main-box {
width: 1568px; width: 1568px;
height: 810px; min-height: 810px;
border-radius: 10px; border-radius: 10px;
background: var(--bg-white-100); background: var(--bg-white-100);
margin: 0 auto; margin: 0 auto;
margin-bottom: 20px;
overflow: hidden; overflow: hidden;
.data-main-box-header { .data-main-box-header {
...@@ -1163,7 +1193,7 @@ onMounted(async () => { ...@@ -1163,7 +1193,7 @@ onMounted(async () => {
.data-main-box-main { .data-main-box-main {
width: 1520px; width: 1520px;
// height: 633px; // height: 633px;
height: 680px; min-height: 680px;
border-radius: 10px; border-radius: 10px;
border: 1px solid var(--bg-black-5); border: 1px solid var(--bg-black-5);
margin: 0 auto; margin: 0 auto;
...@@ -1257,6 +1287,14 @@ onMounted(async () => { ...@@ -1257,6 +1287,14 @@ onMounted(async () => {
.date-column { .date-column {
background-color: #ecf5ff; background-color: #ecf5ff;
.tag-box {
display: flex;
flex-wrap: wrap;
gap: 8px;
width: 340px;
}
} }
.date-column .cell { .date-column .cell {
...@@ -1284,6 +1322,8 @@ onMounted(async () => { ...@@ -1284,6 +1322,8 @@ onMounted(async () => {
} }
} }
:deep(.el-table__header-wrapper) { :deep(.el-table__header-wrapper) {
// background-color: #f5f7fa; // background-color: #f5f7fa;
height: 48px; height: 48px;
......
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
</template> </template>
<script setup> <script setup>
import { computed, onMounted, ref } from 'vue' import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import Icon1 from './assets/icons/sider-icon1.svg' import Icon1 from './assets/icons/sider-icon1.svg'
import Icon2 from './assets/icons/sider-icon2.svg' import Icon2 from './assets/icons/sider-icon2.svg'
import Icon3 from './assets/icons/sider-icon3.svg' import Icon3 from './assets/icons/sider-icon3.svg'
...@@ -398,6 +398,8 @@ const handleClickTab = (tab) => { ...@@ -398,6 +398,8 @@ const handleClickTab = (tab) => {
}) })
} }
const timer = ref(null)
// 关闭当前标签页 // 关闭当前标签页
const handleCloseCurTab = (tab, index) => { const handleCloseCurTab = (tab, index) => {
...@@ -416,7 +418,7 @@ const handleCloseCurTab = (tab, index) => { ...@@ -416,7 +418,7 @@ const handleCloseCurTab = (tab, index) => {
}) })
if (index === openedTabList.value.length - 1) { if (index === openedTabList.value.length - 1) {
tagsViewStore.delView(tab) tagsViewStore.delView(tab)
setTimeout(() => { timer.value = setTimeout(() => {
tagsViewStore.visitedViews[tagsViewStore.visitedViews.length - 1].active = true tagsViewStore.visitedViews[tagsViewStore.visitedViews.length - 1].active = true
activeTab = tagsViewStore.visitedViews[tagsViewStore.visitedViews.length - 1] activeTab = tagsViewStore.visitedViews[tagsViewStore.visitedViews.length - 1]
router.push({ router.push({
...@@ -593,6 +595,11 @@ onMounted(() => { ...@@ -593,6 +595,11 @@ onMounted(() => {
}) })
onBeforeUnmount(() => {
if(timer.value) {
clearTimeout(timer.value)
}
})
</script> </script>
......
...@@ -814,7 +814,25 @@ const handleBox5 = async () => { ...@@ -814,7 +814,25 @@ const handleBox5 = async () => {
let chart1 = getBarChart(chart1Data.value.dataX, chart1Data.value.dataY); let chart1 = getBarChart(chart1Data.value.dataX, chart1Data.value.dataY);
chart1.yAxis.name = "数量"; chart1.yAxis.name = "数量";
chart1.yAxis.nameTextStyle = { align: 'right' } chart1.yAxis.nameTextStyle = { align: 'right' }
setChart(chart1, "chart1"); let org = '全部机构'
if(box5Params.proposeName) {
org = keyOrganizationList.value.filter(item => {
return item.orgId === box5Params.proposeName
})[0].orgName
}
let domain = '全部领域'
if(box5Params.domainId) {
domain = areaList.value.filter(item => {
return item.id === box5Params.domainId
})[0].name
}
const selectParam = {
moduleType: '政令',
orgnizationName: org,
domains: domain,
selectDate: box5Params.year
}
setChart(chart1, "chart1", true, selectParam);
}; };
// 政令科技领域 // 政令科技领域
...@@ -861,8 +879,19 @@ const handleGetDecreeArea = async () => { ...@@ -861,8 +879,19 @@ const handleGetDecreeArea = async () => {
}; };
const handleBox6 = async () => { const handleBox6 = async () => {
await handleGetDecreeArea(); await handleGetDecreeArea();
let org = '全部机构'
if(box6Params.proposeName) {
org = keyOrganizationList.value.filter(item => {
return item.orgId === box6Params.proposeName
})[0].orgName
}
const selectParam = {
moduleType: '政令',
orgnizationName: org,
selectedDate: JSON.stringify([box6Params.year+'-01-01', box6Params.year+'-12-31'])
}
let chart2 = getPieChart(chart2Data.value); let chart2 = getPieChart(chart2Data.value);
setChart(chart2, "chart2"); setChart(chart2, "chart2", true, selectParam);
}; };
const handleBox6YearChange = () => { const handleBox6YearChange = () => {
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<router-view /> <router-view />
</div> </div>
</div> </div>
<div class="right-btn" @click="handleClickToolBox"> <div class="right-btn" v-if="isShowToolBox" @click="handleClickToolBox">
<div class="item"> <div class="item">
<div class="icon"> <div class="icon">
<img src="@/assets/icons/overview/domain.png" alt="" /> <img src="@/assets/icons/overview/domain.png" alt="" />
...@@ -22,20 +22,7 @@ ...@@ -22,20 +22,7 @@
</div> </div>
</div> </div>
<div class="tool-box"> <div class="tool-box" v-if="isShowToolBox">
<!-- <div class="tool-item">
<img src="@/assets/icons/tool-item-icon1.png" alt="" />
</div>
<div class="tool-item">
<img src="@/assets/icons/tool-item-icon2.png" alt="" />
</div>
<div class="tool-item">
<img src="@/assets/icons/tool-item-icon3.png" alt="" />
</div>
<div class="tool-item">
<img src="@/assets/icons/tool-item-icon4.png" alt="" />
</div> -->
<el-tooltip content="智能写报" placement="left" :offset="10"> <el-tooltip content="智能写报" placement="left" :offset="10">
<div class="tool-item" @click="handleOpenPage('znxb')"> <div class="tool-item" @click="handleOpenPage('znxb')">
<img src="@/assets/icons/tool-item-icon1.png" alt="" /> <img src="@/assets/icons/tool-item-icon1.png" alt="" />
...@@ -96,11 +83,13 @@ import { ElMessage } from "element-plus"; ...@@ -96,11 +83,13 @@ import { ElMessage } from "element-plus";
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const isShowHeader = computed(() => { const isShowToolBox = computed(() => {
const isShow = route.meta.isShowHeader const isDataLibrary = route.fullPath.includes("dataLibrary");
return isShow? true : false const isWrittingAsstaint = route.path === "/writtingAsstaint";
return !isDataLibrary && !isWrittingAsstaint;
}) })
const isShowHeader = computed(() => !!route.meta.isShowHeader);
const isShowAiBox = ref(false); const isShowAiBox = ref(false);
...@@ -816,4 +805,7 @@ body { ...@@ -816,4 +805,7 @@ body {
cursor: not-allowed; cursor: not-allowed;
pointer-events: none; pointer-events: none;
} }
</style> </style>
<template>
<div class="headerBox">
<div class="tabBox" v-if="store.bottomProgressNum>0">
<div class="fileName">
<img src="@/assets/icons/pdf-icon.png" alt=" ">
<span class="text-tip-1-bold">{{ store.uploadFileList[0]?.name||'文件错误' }}</span>
</div>
<div class="tab">
<div class="tabList text-tip-1-bold" v-for="(item,index) in store.tabList" :key="index" :class="{'on':store.headerTabType==item.type}"
:style="!item.active?'color:#bfbfbf;cursor: no-drop;':''"
@click="onTabListClick(item.type,item.active)">{{ item.name }}</div>
</div>
<div class="switch" v-if="store.headerTabType=='translate'">
<el-switch v-model="store.isShowOriginal"/>
<div class="iconBOx">
<img src="@/assets/icons/translate-icon.png" alt="">
<span class="text-tip-1">显示原文</span>
</div>
<el-button @click="store.handleIsSsearchFor"><img style="width: 16px;" src="@/assets/icons/aiBox/search.png" alt=""> 查找</el-button>
</div>
<div v-else style="margin-right: 22px;">
<el-button @click="onExport">导出</el-button>
</div>
</div>
<div class="logo" v-else>
<img src="@/assets/icons/tool-item-icon1.png" alt="">
<span class="text-title-3-bold">智能写库</span>
</div>
</div>
</template>
<script setup>
import { onMounted, onUnmounted, ref, nextTick ,computed,watch} from "vue";
import { useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
const emit = defineEmits(["onExport"]);
// 子组件直接获取Pinia Store(核心优化)
const store = useWrittingAsstaintStore();
const onTabListClick= (type,active)=>{
if(!active) return
store.handleHeaderTab(type)
}
const onExport=()=>{
if(store.headerTabType=='mind'){
emit('onExport')
}else if(store.headerTabType=='message'){
store.exportContent
}
}
</script>
<style lang="scss" scoped>
.headerBox{
background-color: #fff;
border-bottom: 1px solid rgb(234, 236, 238);
.logo{
height: 60px;
display: flex;
align-items: center;
img{
width: 30px;
height: 30px;
margin-left: 28px;
margin-right: 20px;
}
}
.tabBox{
height: 60px;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.fileName{
display: flex;
align-items: center;
img{
width: 24px;
height: 24px;
margin-left: 28px;
margin-right: 16px;
}
}
.tab{
width: 232px;
display: flex;
justify-content: space-between;
position: absolute;
left: 50%;
transform: translate(-50%);
.tabList{
cursor: pointer;
height: 59px;
line-height: 66px;
}
.on{
border-bottom: 4px solid var(--color-primary-100);
color:var(--color-primary-100) ;
transition: all 0.1s;
}
}
.btns{
margin-right: 23px;
}
}
.switch{
display: flex;
align-items: center;
margin-right: 23px;
.iconBOx{
display: flex;
align-items: center;
margin-right: 12px;
img{
width: 16px;
height: 16px;
margin:0 5px;
}
}
}
}
</style>
\ No newline at end of file
<template> <template>
<div class="left-box-wrapper"> <div class="left-box-wrapper">
<div class="back" @click="store.resetGenerateState" v-if="store.isGenerating">&lt; 返回</div> <!-- <div class="back" @click="store.resetGenerateState" v-if="store.isGenerating">&lt; 返回</div> -->
<div class="left-box" :class="{ 'has-back-btn': store.isGenerating }" <div class="left-box" :class="{ 'has-back-btn': store.isGenerating }"
v-if="!store.isShowClauseTranslation && !store.isShowSteps"> v-if="!store.isShowClauseTranslation && !store.isShowSteps">
<div class="left-box-input"> <div class="left-box-input">
...@@ -11,15 +11,15 @@ ...@@ -11,15 +11,15 @@
<div class="header">报文主题</div> <div class="header">报文主题</div>
<div class="title-box"> <div class="title-box">
<div class="title">主题名称</div> <div class="title">主题名称</div>
<el-input :disabled="store.isDisableTemplate" style="width: 476px; height: 32px" <el-input :disabled="store.isDisableTemplate" style="width: 476px; height: 32px ;background: #f7f8f9;"
class="title-input" placeholder="输入主题名称,如:大而美法案" v-model="store.writtingTitle" /> class="title-input" placeholder="输入主题名称,如:大而美法案" v-model="store.writtingTitle" />
</div> </div>
<div class="description-box"> <!-- <div class="description-box">
<div class="title">主题描述</div> <div class="title">主题描述</div>
<el-input :disabled="store.isDisableTemplate" class="description-input" type="textarea" <el-input :disabled="store.isDisableTemplate" class="description-input" type="textarea"
style="width: 476px" :rows="8" placeholder="输入报文主题描述,如:从科技领域方面分析大而美法案通过后对中国可能产生的影响" style="width: 476px" :rows="8" placeholder="输入报文主题描述,如:从科技领域方面分析大而美法案通过后对中国可能产生的影响"
v-model="store.descText" /> v-model="store.descText" />
</div> </div> -->
</div> </div>
<!-- 报文模板 --> <!-- 报文模板 -->
...@@ -88,24 +88,24 @@ ...@@ -88,24 +88,24 @@
</div> </div>
<!-- 提交区域 --> <!-- 提交区域 -->
<div class="submit-area"> <!-- <div class="submit-area">
<div class="tips"> <div class="tips">
<div class="tips-icon"> <div class="tips-icon">
<img src="../assets/images/tips-icon.png" alt="" /> <img src="../assets/images/tips-icon.png" alt="" />
</div> </div>
<div class="tips-text">内容由AI生成,无法确保真实准确,仅供参考</div> <div class="tips-text">内容由AI生成,无法确保真实准确,仅供参考</div>
</div> </div> -->
<!-- 生成按钮 --> <!-- 生成按钮 -->
<div class="submit-btn" @click="triggerGenerate" v-if="!store.isGenerating"> <!-- <div class="submit-btn" @click="triggerGenerate" v-if="!store.isGenerating">
<div class="submit-icon"> <div class="submit-icon">
<img src="../assets/images/ai.png" alt="" /> <img src="../assets/images/ai.png" alt="" />
</div> </div>
<div class="submit-text">生成报文</div> <div class="submit-text">生成报文</div>
</div> </div> -->
<!-- 生成中状态 --> <!-- 生成中状态 -->
<div class="process-footer-box" v-else> <!-- <div class="process-footer-box" v-else>
<div class="footer-left"> <div class="footer-left">
{{ store.isGenerating ? "报文生成中..." : "报文已生成" }} {{ store.isGenerating ? "报文生成中..." : "报文已生成" }}
</div> </div>
...@@ -114,11 +114,11 @@ ...@@ -114,11 +114,11 @@
<div class="text">停止</div> <div class="text">停止</div>
</div> </div>
</div> </div>
</div> </div> end -->
</div> </div>
<!-- 步骤侧边栏(拆分出来) --> <!-- 步骤侧边栏(拆分出来) -->
<div class="left-box process" :class="{ 'has-back-btn': store.isGenerating }" v-if="store.isShowSteps"> <!-- <div class="left-box process" :class="{ 'has-back-btn': store.isGenerating }" v-if="store.isShowSteps">
<div class="left-box-input"> <div class="left-box-input">
<div class="process-box"> <div class="process-box">
<div class="process-main-box"> <div class="process-main-box">
...@@ -137,17 +137,17 @@ ...@@ -137,17 +137,17 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div> -->
<div class="submit-area"> <!-- <div class="submit-area">
<div class="tips"> <div class="tips">
<div class="tips-icon"> <div class="tips-icon">
<img src="../assets/images/tips-icon.png" alt="" /> <img src="../assets/images/tips-icon.png" alt="" />
</div> </div>
<div class="tips-text">内容由AI生成,无法确保真实准确,仅供参考</div> <div class="tips-text">内容由AI生成,无法确保真实准确,仅供参考</div>
</div> </div> -->
<!-- 生成中状态 --> <!-- 生成中状态 -->
<div class="process-footer-box"> <!-- <div class="process-footer-box">
<div class="footer-left"> <div class="footer-left">
{{ store.isGenerating ? "报文生成中..." : "报文已生成" }} {{ store.isGenerating ? "报文生成中..." : "报文已生成" }}
</div> </div>
...@@ -155,25 +155,23 @@ ...@@ -155,25 +155,23 @@
<div class="icon"></div> <div class="icon"></div>
<div class="text">停止</div> <div class="text">停止</div>
</div> </div>
</div> </div> -->
</div> <!-- </div>
</div> </div> -->
<!-- 条款翻译侧边栏 srot -->
<!-- 条款翻译侧边栏 --> <div class="left-box translation-box" :class="{ 'has-back-btn': store.isGenerating }" v-if="store.isShowSteps&&store.headerTabType=='message'">
<div class="left-box translation-box" :class="{ 'has-back-btn': store.isGenerating }"
v-if="store.isShowClauseTranslation">
<div class="translation-main-box"> <div class="translation-main-box">
<div class="translation-actions" v-if="!store.isGenerating"> <!-- <div class="translation-actions" v-if="!store.isGenerating">
<div class="back-input-btn" @click="store.backToInputAndClear">返回输入栏</div> <div class="back-input-btn" @click="store.backToInputAndClear">返回输入栏</div>
</div> </div> -->
<!-- 政令标题卡片 --> <!-- 政令标题卡片 -->
<div class="metadata-card" v-if="store.pdfMetadata"> <!-- <div class="metadata-card" v-if="store.pdfMetadata">
<div class="card-header"> <div class="card-header">
<div class="chinese-name">{{ store.pdfMetadata.name }}</div> <div class="chinese-name">{{ store.pdfMetadata.name }}</div>
<div class="type-tag">{{ store.pdfMetadata.signing_date }}</div> <div class="type-tag">{{ store.pdfMetadata.signing_date }}</div>
</div> </div>
<div class="english-name">{{ store.pdfMetadata.order_title }}</div> <div class="english-name">{{ store.pdfMetadata.order_title }}</div>
</div> </div> -->
<div class="translation-header-new"> <div class="translation-header-new">
<div class="header-left">共{{ store.clauseTranslationMessages.length }}章节</div> <div class="header-left">共{{ store.clauseTranslationMessages.length }}章节</div>
<div class="header-right"> <div class="header-right">
...@@ -200,7 +198,7 @@ ...@@ -200,7 +198,7 @@
</div> </div>
</div> </div>
<!-- 步骤侧边栏显隐按钮 --> <!-- 步骤侧边栏显隐按钮 -->
<div class="toggle-steps-btn" @click="store.isShowSteps = !store.isShowSteps"> <div class="toggle-steps-btn" @click=" isShowSteps ">
<div class="arrow" :class="{ 'is-active': store.isShowSteps }"></div> <div class="arrow" :class="{ 'is-active': store.isShowSteps }"></div>
</div> </div>
</div> </div>
...@@ -225,6 +223,10 @@ const emit = defineEmits(["generate"]); ...@@ -225,6 +223,10 @@ const emit = defineEmits(["generate"]);
const triggerGenerate = () => { const triggerGenerate = () => {
emit("generate"); emit("generate");
}; };
const isShowSteps=()=>{
store.isShowSteps = !store.isShowSteps
store.highlightClauseId=''
}
// 数字转中文序号 // 数字转中文序号
const getChineseNumber = (num) => { const getChineseNumber = (num) => {
...@@ -245,13 +247,25 @@ const { renderedProcess, updateProcess, clearContent } = useStream(); ...@@ -245,13 +247,25 @@ const { renderedProcess, updateProcess, clearContent } = useStream();
watch( watch(
() => store.highlightClauseId, () => store.highlightClauseId,
async (newId) => { async (newId) => {
if (!newId || !translationContentRef.value) return; console.log('789')
await nextTick(); await nextTick();
if (!newId || !translationContentRef.value) return;
const container = translationContentRef.value; const container = translationContentRef.value;
const item = container.querySelector(`.translation-item[data-clause-number="${newId}"]`); const result = newId.replace(/^\d+、/, '');
const item = container.querySelector(`.translation-item[data-clause-number="${newId.match(/^(\d+)/)[1]}"]`);
const itemHtml=item.querySelector(`.translated-text`);
if (!item) return; if (!item) return;
// 你已经拿到的 外层大标签
const element =itemHtml
// 你要找的文字
const targetText = result
const location=findTextInElement(element, targetText);
const containerRect = container.getBoundingClientRect(); const containerRect = container.getBoundingClientRect();
const itemRect = item.getBoundingClientRect(); const itemRect = item.getBoundingClientRect();
const delta = itemRect.top - containerRect.top; const delta = itemRect.top - containerRect.top;
...@@ -264,6 +278,74 @@ watch( ...@@ -264,6 +278,74 @@ watch(
}); });
} }
); );
// 👇 核心:在 element 内部找文字位置
// =========================================
const findTextInElement=(element, targetText )=> {
// 遍历标签内的所有内容
const nodes = element.childNodes;
let rect =''
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
// 只找纯文字
if (node.nodeType === 3) {
const originalText = node.textContent;
const cleanText=(str)=> {
return str
.replace(/(/g, '(')
.replace(/)/g, ')')
.replace(/,/g, ',')
.replace(/。/g, '.')
.replace(/:/g, ':')
.replace(/;/g, ';');
}
// 清理后的文字(无标点)
const nodeClean = cleanText(originalText);
const targetClean = cleanText(targetText);
// 用干净文字对比
if (nodeClean.includes(targetClean)) {
// 找到真实位置(用原始文本定位,不影响)
const index = originalText.indexOf(
originalText.includes(targetText) ? targetText : originalText
);
const range = document.createRange();
range.setStart(node, index);
range.setEnd(node, index + targetText.length);
// 拿到位置
rect = range.getBoundingClientRect();
const marks = element.querySelectorAll('mark');
marks.forEach(mark => {
// 把 mark 里的文字放回原位,删除标签
const parent = mark.parentNode;
while (mark.firstChild) {
parent.insertBefore(mark.firstChild, mark);
}
parent.removeChild(mark);
// 合并相邻文本节点(恢复页面原貌)
parent.normalize();
});
const mark = document.createElement("mark");
mark.style.backgroundColor = '#ffff00;';
mark.style.color = "#000"; // 文字颜色
range.surroundContents(mark);
break;
}
}
}
return rect
}
// 监听 store.processLog 变化,更新步骤内容并滚动 // 监听 store.processLog 变化,更新步骤内容并滚动
watch( watch(
...@@ -308,7 +390,7 @@ defineExpose({ ...@@ -308,7 +390,7 @@ defineExpose({
width: 521px; width: 521px;
height: 100%; height: 100%;
padding-top: 22px; padding-top: 22px;
padding-bottom: 29px; padding-bottom: 10px;
box-sizing: border-box; box-sizing: border-box;
border-right: 1px solid rgba(234, 236, 238, 1); border-right: 1px solid rgba(234, 236, 238, 1);
border-top: 1px solid rgba(234, 236, 238, 1); border-top: 1px solid rgba(234, 236, 238, 1);
......
...@@ -41,7 +41,9 @@ const handleGlobalClick = (e) => { ...@@ -41,7 +41,9 @@ const handleGlobalClick = (e) => {
const clauseId = btn.getAttribute('data-clause'); const clauseId = btn.getAttribute('data-clause');
if (clauseId) { if (clauseId) {
store.highlightClauseId = clauseId; store.highlightClauseId = clauseId;
store.isShowSteps = true;
// 翻译栏一直显示,所以这里只需要确保它在视图内 // 翻译栏一直显示,所以这里只需要确保它在视图内
console.log(store.highlightClauseId )
} }
} }
}; };
...@@ -88,6 +90,7 @@ watch( ...@@ -88,6 +90,7 @@ watch(
}, },
{ immediate: true } { immediate: true }
); );
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -103,7 +106,7 @@ watch( ...@@ -103,7 +106,7 @@ watch(
} }
.content-box { .content-box {
width: 1069px; width: 100%;
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
padding: 20px 80px; padding: 20px 80px;
...@@ -111,8 +114,8 @@ watch( ...@@ -111,8 +114,8 @@ watch(
font-size: 16px; font-size: 16px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1); // border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px; // border-radius: 10px;
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
margin: 17px auto 0 auto; margin: 17px auto 0 auto;
...@@ -138,9 +141,10 @@ watch( ...@@ -138,9 +141,10 @@ watch(
cursor: pointer; cursor: pointer;
vertical-align: middle; vertical-align: middle;
transition: background-color 0.2s; transition: background-color 0.2s;
border-radius: 50%;
background: #818181;
&:hover { &:hover {
background-color: #044da5; background-color: #818181;
} }
&:active { &:active {
......
<template>
<div style="width: 100%; display: flex;justify-content: center;align-items: stretch;">
<!-- 右侧子组件:绑定ref -->
<writtingMainBox v-show="!!store.reportContent" :report-content="store.reportContent" />
<div v-if="!store.reportContent" class="main-placeholder">
<img src="../assets/images/container-image.png" alt="无数据占位图" />
<div class="placeholder-text">
<div>智能体写报任务执行中...</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch, onMounted, onUnmounted } from "vue";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
import writtingMainBox from "./WrittingMainBox.vue";
const store = useWrittingAsstaintStore();
const mainBoxRef = ref(null); // 右侧子组件ref
</script>
<style lang="scss" scoped>
.main-placeholder {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
background: #f7f8f9;
img {
max-width: 100%;
max-height: 80%;
object-fit: contain;
display: block;
}
.placeholder-text {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
</style>
\ No newline at end of file
<template>
<div class="mind-map-container">
<div ref="containerRef" class="mind-map"></div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import G6 from '@antv/g6'
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
// 子组件直接获取Pinia Store(核心优化)
const store = useWrittingAsstaintStore();
// pushfenzhi
let mindMapData = {
id: 'root',
label: '名称',
children: [
]
}
let uniqueId = 1;
function convertMindMap(rawData) {
uniqueId = 1; // 每次重置
const tree = buildTreeRecursive(rawData.node, rawData.links);
return tree[0]; // 返回单个根节点
}
const raw = store.resultWriteData.政令深度分析.条款分析.科技条款内容[0].领域举措.data;
const treeData = convertMindMap(raw);
// 全局唯一自增计数器(最稳、最短、永不重复)
// 递归构建树 + 自动生成全局唯一数字ID
function buildTreeRecursive(nodes, links, parentId = null) {
const tree = [];
// 找到当前父节点的所有子节点
const childrenNodes = nodes.filter(node => {
if (parentId === null) {
return node.depth === 0;
}
return links.some(
link => link.source === parentId && link.target === node.id
);
});
// 遍历子节点,递归生成
for (const node of childrenNodes) {
const currentNode = {
id: `${uniqueId++}_`, // 🔥 纯数字自增,绝对唯一!
label: node.name,
children: buildTreeRecursive(nodes, links, node.id)
};
tree.push(currentNode);
}
return tree;
}
treeData.id= 'root',
console.log(treeData)
mindMapData=treeData
const containerRef = ref(null)
let graph = null
// 文字换行(不溢出)
function splitTextToLines(text, maxWidth, fontSize) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
ctx.font = `${fontSize}px sans-serif`
const lines = []
let currentLine = ''
let width = 0
for (const char of text) {
const w = ctx.measureText(char).width
if (width + w > maxWidth) {
lines.push(currentLine)
currentLine = char
width = w
} else {
currentLine += char
width += w
}
}
if (currentLine) lines.push(currentLine)
const lineHeight = fontSize + 8
const totalHeight = lineHeight * lines.length + 24
return { lines, lineHeight, totalHeight }
}
onMounted(() => {
if (graph) graph.destroy()
const el = containerRef.value
if (!el) return
// 注册节点
G6.registerNode('custom-node', {
draw(cfg, group) {
const isRoot = cfg.id === 'root'
const isDept = cfg.id.startsWith('dept')
const MAX_WIDTH = isRoot ? 280 : isDept ? 240 : 400
const fontSize = isRoot ? 16 : isDept ? 15 : 14
const fontWeight = isRoot || isDept ? 'bold' : 'normal'
const color = 'rgba(5, 95, 194, 1)'
const padding = 16
const { lines, lineHeight, totalHeight } = splitTextToLines(cfg.label, MAX_WIDTH - padding * 2, fontSize)
const nodeW = MAX_WIDTH
const nodeH = totalHeight
// 节点背景
group.addShape('rect', {
attrs: {
x: -nodeW / 2,
y: -nodeH / 2,
width: nodeW,
height: nodeH,
fill: '#f8fcff',
stroke: color,
lineWidth: 1,
radius: 6
}
})
// 文字居中
lines.forEach((line, i) => {
group.addShape('text', {
attrs: {
text: line,
x: 0,
y: (i - (lines.length - 1) / 2) * lineHeight,
textAlign: 'center',
textBaseline: 'middle',
fontSize, fontWeight, fill: color
}
})
})
return group
},
getAnchorPoints: () => [[0, 0.5], [1, 0.5]]
}, 'single-node')
graph = new G6.TreeGraph({
container: el,
width: el.offsetWidth,
height: el.offsetHeight,
modes: {
default: ['drag-canvas', 'zoom-canvas', 'collapse-expand']
},
defaultNode: { type: 'custom-node' },
defaultEdge: {
type: 'cubic-horizontal',
style: { stroke: 'rgba(5, 95, 194, 0.4)', lineWidth: 1 }
},
layout: {
type: 'compactBox',
direction: 'LR',
getWidth: () => 400,
getHGap: () => 80,
getVGap: () => 50,
}
})
graph.data(mindMapData)
graph.render()
// 自适应显示
setTimeout(() => {
graph.fitCenter()
graph.zoomTo(0.65)
}, 200)
})
onUnmounted(() => graph?.destroy())
function exportGraph() {
if (!graph) return
graph.downloadFullImage('思维导图.png')
}
defineExpose({
exportGraph
});
</script>
<style scoped>
.mind-map-container {
width: 100%;
height: 100vh;
background: #f5f7fa;
}
.mind-map {
width: 92%;
height: 100%;
margin: 0 auto;
}
</style>
\ No newline at end of file
<template>
<div class="translation-content" ref="translationContentRef">
<!-- :class="{ active: store.highlightClauseId === item.payload?.clause_number }"
:data-clause-number="item.payload?.clause_number" -->
<!-- 查找 -->
<div class="searchFor" v-if="store.isSsearchFor">
<el-input v-model="keyword" style="width: 260px;" placeholder="查找原文内容" />
<div class="searchTextNum">
<span v-if="total==0">0</span>
<span v-else> {{ current + 1 }}</span>/{{ total }}
</div>
<div class="prev" @click="prev"><el-icon><ArrowUp /></el-icon></div>
<div class="next" @click="next"><el-icon><ArrowDown /></el-icon></div>
<div class="close" @click="closeClick"><el-icon><CloseBold /></el-icon></div>
</div>
<div class="content-box" ref="contentBox">
<div class="translation-item" v-for="(item, index) in renderList" :key="index">
<div class="item-body">
<div class="original-text" v-if="store.isShowOriginal">
<template v-for="(t, i) in item.fragments" :key="i">
<span :class="{ high: t.hit, current: t.hit && currentGlobalIndex === t.globalIndex}">
{{ t.text }}
</span>
</template>
</div>
<div class="translated-text">
<span class="clause-title">第{{ getChineseNumber(item.payload?.clause_number) }}节</span>{{ item.payload?.clause_content_zh }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, onUnmounted, ref, nextTick ,watch,computed} from "vue";
import { useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
import { useStream } from "@/hooks/useStream";
// 子组件直接获取Pinia Store(核心优化)
const store = useWrittingAsstaintStore();
const translationContentRef = ref(null);
// 数字转中文序号
const getChineseNumber = (num) => {
const zh = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
const n = parseInt(num);
if (n <= 10) return zh[n];
if (n < 20) return '十' + zh[n % 10];
if (n < 100) {
return zh[Math.floor(n / 10)] + '十' + (n % 10 === 0 ? '' : zh[n % 10]);
}
return num;
};
const closeClick=()=>{
store.handleIsSsearchFor()
current.value=0
total.value=0
keyword.value=''
}
const keyword = ref('')
const contentBox = ref(null)
const current = ref(0)
const total = ref(0)
// 渲染列表(不修改原数据,自动更新)
const renderList = computed(() => {
const key = keyword.value.trim()
return (store.clauseTranslationMessages || []).map(item => {
const section = item.payload?.clause_section || ''
const content = item.payload?.clause_content || ''
const fullText = section + ' ' + content
if (!key) {
return {
...item,
fragments: [{ text: fullText, hit: false, globalIndex: -1 }]
}
}
const parts = fullText.split(new RegExp(`(${key})`, 'g'))
const fragments = []
parts.forEach(t => {
fragments.push({
text: t,
hit: t === key,
globalIndex: -1
})
})
return { ...item, fragments }
})
})
// 全局匹配列表(只计算一次,修复蓝色全部选中BUG)
const globalMatchList = computed(() => {
const arr = []
renderList.value.forEach(item => {
item.fragments.forEach(f => {
if (f.hit) arr.push(f)
})
})
return arr
})
// 当前高亮的全局索引
const currentGlobalIndex = computed(() => {
if (!globalMatchList.value.length) return -1
return globalMatchList.value[current.value]?.globalIndex ?? -2
})
// 总数
watch(globalMatchList, (val) => {
total.value = val.length
current.value = 0
}, { immediate: true })
// 给每个命中项分配唯一 index
watch([renderList, globalMatchList], () => {
let idx = 0
const map = new Map()
globalMatchList.value.forEach(item => {
map.set(item, idx++)
})
renderList.value.forEach(item => {
item.fragments.forEach(f => {
if (f.hit) f.globalIndex = map.get(f)
})
})
})
function doSearch() {
current.value = 0
scrollTo(0)
}
function prev() {
if (!total.value) return
current.value = (current.value - 1 + total.value) % total.value
console.log(current.value )
scrollTo(current.value)
}
function next() {
if (!total.value) return
current.value = (current.value + 1) % total.value
scrollTo(current.value)
}
function scrollTo(idx) {
setTimeout(() => {
const all = contentBox.value?.querySelectorAll('.high')
if (all?.[idx]) {
contentBox.value.scrollTo({
top: all[idx].offsetTop - 100,
behavior: 'smooth'
})
}
}, 0)
}
</script>
<style lang="scss" scoped>
.content-box{
overflow-y: auto;
margin-top: 30px;
height: calc(100vh - 250px);
.translation-item{
margin-bottom: 24px;
.item-body{
display: flex;
justify-content: space-between;
width: 85%;
margin: 0 auto;
.original-text{
min-width: 48%;
line-height: 30px;
margin-right: 62px;
flex: 2;
}
.translated-text{
width: 100%;
line-height: 30px;
}
}
}
}
.searchFor{
display: flex;
align-items: center;
width: 430px;
height: 60px;
padding: 12px 0;
border-radius: 10px;
background-color: #fff;
border: 1px solid rgb(234, 236, 238);
position: fixed;
top: 120px;
right: 20px;
:deep(.el-input__wrapper){
background-color: #fff;
}
.searchTextNum{
width: 70px;
height: 100%;
border-right: 1px solid rgb(234, 236, 238);
line-height: 40px;
margin-right: 16px;
text-align: center;
}
.prev{
margin-right: 12px;
}
.next{
margin-right: 12px;
}
// position: absolute;
}
.high {
background: #ffeb3b;
}
.current {
background: #409eff !important;
color: #fff !important;
}
</style>
\ No newline at end of file
<template> <template>
<div class="writting-wrapper"> <div class="writting-wrapper">
<!-- 头部区域 --> <!-- 头部区域 -->
<div class="writting-header"> <!-- <div class="writting-header">
<div class="tab-box"> <div class="tab-box">
<div class="tab" :class="{ tabActive: item.active }" v-for="(item, index) in store.tabList" <div class="tab" :class="{ tabActive: item.active }" v-for="(item, index) in store.tabList"
:key="index"> :key="index">
...@@ -10,12 +10,6 @@ ...@@ -10,12 +10,6 @@
</div> </div>
<div class="edit-box"></div> <div class="edit-box"></div>
<div class="btn-box"> <div class="btn-box">
<!-- <div class="btn" @click="store.exportContent">
<div class="icon">
<img src="./assets/images/export-icon.png" alt="" />
</div>
<div class="text">导出</div>
</div> -->
<div class="btn" @click="store.toggleEditMode"> <div class="btn" @click="store.toggleEditMode">
<div class="icon"> <div class="icon">
<img v-if="store.isEditMode" src="./assets/images/preview-icon.png" alt="" /> <img v-if="store.isEditMode" src="./assets/images/preview-icon.png" alt="" />
...@@ -30,22 +24,38 @@ ...@@ -30,22 +24,38 @@
<div class="text text1">保存</div> <div class="text text1">保存</div>
</div> </div>
</div> </div>
</div> </div> -->
<IntelligenceLeftTabBar></IntelligenceLeftTabBar>
<!-- 主体区域:子组件 --> <!-- 主体区域:子组件 -->
<div style="width: 100%;">
<WrittingHeader @onExport="onExport"></WrittingHeader>
<div class="writting-main"> <div class="writting-main">
<!-- 左侧子组件:绑定ref --> <!-- 左侧子组件:绑定ref -->
<writtingleftBox ref="leftBoxRef" @generate="handleGenerate" /> <!-- <writtingleftBox ref="leftBoxRef" @generate="handleGenerate" /> -->
<!-- 右侧子组件:绑定ref --> <WrittingLeftBox ref="leftBoxRef" />
<writtingMainBox v-show="!!store.reportContent" ref="mainBoxRef" :report-content="store.reportContent" /> <!-- 翻译 -->
<WrittingTranslate v-if="store.isShowClauseTranslation&&store.headerTabType=='translate'"></WrittingTranslate>
<!-- 思维导图 " -->
<WrittingMind v-else-if="store.isShowClauseTranslation&&store.headerTabType=='mind' " ref="mindRef"></WrittingMind>
<!-- 写报 -->
<WrittingMessage v-else-if="store.isShowClauseTranslation&&store.headerTabType=='message'"></WrittingMessage>
<!-- 无数据时显示占位图 --> <!-- 无数据时显示占位图 -->
<div v-show="!store.reportContent" class="main-placeholder"> <div v-else class="main-placeholder">
<img src="./assets/images/container-image.png" alt="无数据占位图" /> <img src="./assets/images/container-image.png" alt="无数据占位图" />
<div class="placeholder-text"> <div class="placeholder-text">
<div v-if="store.isGenerating">智能体写报任务执行中...</div> <div v-if="store.isGenerating">智能体写报任务执行中...</div>
<div v-else>上传文件后点击“生成报文”开始写报...</div> <div v-else>上传文件后点击“生成报文”开始写报...</div>
</div> </div>
</div> </div>
<!-- 右侧子组件:绑定ref -->
<!-- <writtingMainBox v-show="!!store.reportContent" ref="mainBoxRef" :report-content="store.reportContent" /> -->
</div>
<WrittingBottom @generate="handleGenerate" @write="handleWrite"></WrittingBottom>
</div> </div>
</div> </div>
</template> </template>
...@@ -55,14 +65,27 @@ import { onMounted, onUnmounted, ref, nextTick } from "vue"; ...@@ -55,14 +65,27 @@ import { onMounted, onUnmounted, ref, nextTick } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore"; import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
import writtingleftBox from "./components/WrittingLeftBox.vue"; import WrittingLeftBox from "./components/WrittingLeftBox.vue";
import writtingMainBox from "./components/WrittingMainBox.vue";
import WrittingHeader from "./components/WrittingHeader.vue"; //头
import WrittingBottom from "./components/WrittingBottom.vue"; //底部
import WrittingTranslate from "./components/WrittingTranslate.vue"; //翻译
import WrittingMind from "./components/WrittingMind.vue"; //思维导图
import WrittingMessage from "./components/WrittingMessage.vue"; //写报
// 获取路由实例(组件内读取) // 获取路由实例(组件内读取)
const route = useRoute(); const route = useRoute();
// 获取Pinia Store实例 // 获取Pinia Store实例
const leftBoxRef = ref(null); // 左侧子组件ref const leftBoxRef = ref(null); // 左侧子组件ref
const mainBoxRef = ref(null); // 右侧子组件ref const mindRef=ref(null) //思维导图ref
const onExport = () => {
mindRef.value.exportGraph()
}
const store = useWrittingAsstaintStore(); const store = useWrittingAsstaintStore();
// 2. 核心:触发生成流程 // 2. 核心:触发生成流程
...@@ -83,6 +106,19 @@ const handleGenerate = async () => { ...@@ -83,6 +106,19 @@ const handleGenerate = async () => {
console.error("生成报文失败:", error); console.error("生成报文失败:", error);
} }
}; };
const handleWrite=async ()=>{
try {
// // 等待DOM更新(确保子组件DOM已挂载)
store.tabList[2].active=true //写报生成之后放开写报按钮
store.headerTabType='message'
await nextTick();
await store.generateWrite()
} catch (error) {
ElMessage.error(error.message);
console.error("生成写报失败:", error);
}
}
// 生命周期 // 生命周期
onMounted(async () => { onMounted(async () => {
...@@ -100,7 +136,7 @@ onUnmounted(() => { ...@@ -100,7 +136,7 @@ onUnmounted(() => {
.writting-wrapper { .writting-wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex;
.writting-header { .writting-header {
height: 60px; height: 60px;
box-sizing: border-box; box-sizing: border-box;
...@@ -191,7 +227,7 @@ onUnmounted(() => { ...@@ -191,7 +227,7 @@ onUnmounted(() => {
.writting-main { .writting-main {
display: flex; display: flex;
height: calc(100% - 60px); height: calc(100% - 126px);
position: relative; position: relative;
.main-placeholder { .main-placeholder {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论