提交 6b4028a9 authored 作者: 朱政's avatar 朱政

Merge branch 'pre' into zz-dev

流水线 #310 已通过 于阶段
in 1 分 28 秒
...@@ -17,4 +17,11 @@ export function getThinkTankList() { ...@@ -17,4 +17,11 @@ export function getThinkTankList() {
method: 'GET', method: 'GET',
url: `/temporarySearch/search-info/all-organization-names`, url: `/temporarySearch/search-info/all-organization-names`,
}) })
}
export function getStatusList() {
return request({
method: 'GET',
url: `/temporarySearch/search-info/dBillStage`,
})
} }
\ No newline at end of file
// Bill layout & visual tokens
// 统一用于 bill 板块布局改造,后续可并入全局设计变量体系
// spacing
$bill-space-8: 8px;
$bill-space-12: 12px;
$bill-space-16: 16px;
$bill-space-24: 24px;
$bill-space-30: 30px;
// layout
$bill-column-gap: 16px;
$bill-left-column-width: 1064px;
$bill-right-column-width: 520px;
// typography
$bill-title-font-size: 16px;
$bill-title-font-weight: 600;
$bill-body-font-size: 14px;
$bill-body-font-weight: 400;
// surface
$bill-panel-radius: 8px;
$bill-panel-border-color: #e8ecf3;
$bill-panel-shadow: 0 2px 8px rgba(15, 35, 95, 0.06);
$bill-panel-bg: #ffffff;
// text & color
$bill-text-primary: #1f2a44;
$bill-text-secondary: #5b6b88;
$bill-text-muted: #8b97ad;
$bill-color-accent: #2f66ff;
$bill-color-success: #19be6b;
$bill-color-warning: #ff9f43;
$bill-color-danger: #f56c6c;
# Bill 板块布局统一改造计划
> 目标:统一 `@src/views/bill` 下页面布局与视觉风格,解决“底部 30px 留白不稳定”问题,并沉淀可复用布局组件,便于后续新会话直接接续执行。
## 1. 背景与问题
当前 `bill` 板块存在以下共性问题:
1. 页面布局实现不统一(外层滚动、内层滚动、固定高度混用)。
2. 多数页面为“左右两列 + 多卡片”结构,但容器命名、间距、宽高策略不一致。
3. `30px` 底部留白在不同页面生效机制不同,容易出现:
- 某页不生效
- 增加占位后挤压布局
- Header(Tab/操作按钮)受影响
4. 样式硬编码较多(固定宽高、局部 margin/padding),复用性低。
## 2. 总体改造策略
采用“**分层统一 + 渐进迁移**”策略,避免一次性大改风险:
- **第一层:板块承载层统一**
- 统一滚动、内容宽度、底部安全留白机制。
- **第二层:页面布局骨架统一**
- 抽象“页面壳 + 双列布局 + 卡片容器”组件。
- **第三层:业务页迁移**
- 先迁移典型页,再批量替换。
## 3. 分阶段计划
### 阶段 A:基线确认与止血(进行中)
- 修复当前试错引入的结构问题(例如模板闭合错误、布局挤压)。
- 保证关键页面可用:
- `background`
- `processAnalysis`
- `introdoction`
### 阶段 B:新增通用布局组件(进行中)
#### 完成备注(2026-04-02)
- [x] 第一步已完成并通过验收:完成布局组件目录与基础文件脚手架
- `src/views/bill/components/layout/BillPageShell.vue`
- `src/views/bill/components/layout/BillTwoColumn.vue`
- `src/views/bill/components/layout/BillPanel.vue`
- `src/views/bill/components/layout/index.js`
- [x] 第二步已完成并通过验收:实现 `BillPageShell` 滚动容器与底部 `30px` 安全留白机制
- [x] 第三步已完成并通过验收:实现 `BillTwoColumn` 统一双列布局(1064/520 + gap 16 + 响应式堆叠)
- [x] 第四步已完成并通过验收:实现 `BillPanel` 统一卡片结构与基础样式(header/body/footer + 可配置边框/圆角/阴影)
- [x] 第五步已完成并通过验收:`bill-tokens.scss` 接入 `BillPageShell/BillTwoColumn/BillPanel` 并替换关键样式硬编码
- [x] 第六步已完成并通过验收:试点页 `background/index.vue` 已迁移到统一布局骨架(`BillPageShell + BillTwoColumn`
- [x] 第六步修正项(验收反馈已落实):
- 修正 1:移除 `.right-panel``margin-left/margin-right`,避免右列偏移与原样式不一致
- 修正 2:`BillPageShell` 不再使用内部滚动容器;页面滚动回到外层 `layout-main`,确保 Header 与内容一起滚动
- [x] 第七步已完成并通过验收:`introdoction/index.vue` 迁移到统一布局骨架(`BillPageShell + BillTwoColumn`
- [x] 第七步修正项(验收反馈已落实):
- 修正 1:移除 `.introduction-wrap-right``margin-left/margin-right`,消除右列偏移
- 修正 2:在 `.introduction-wrap-content` 增加 `margin-bottom: 30px`,确保外层滚动时可见底部留白
- [x] 第八步已完成并通过验收:`template/index.vue` 迁移到统一布局骨架(`BillPageShell + BillTwoColumn`
- [x] 第八步注意点(已沉淀):
- 删除 `page-bottom-gap` 占位节点,避免与统一留白策略重复
- 在双列容器 `.content-row` 增加 `margin-bottom: 30px`,适配“外层 `layout-main` 滚动”模式
- 迁移后需检查未使用变量并清理,避免 lint 噪音影响后续页面迁移
- [x] 第九步已完成并通过验收:`deepDig/processOverview/index.vue` 迁移到统一页面壳(`BillPageShell`
- [x] 第九步注意点(已沉淀):
- 保留页面内部横向滚动容器(`.main`),仅替换页面根壳,避免时间轴布局回归风险
- 删除 `page-bottom-gap`,改用页面容器底部留白(`margin-bottom: 30px`)适配外层滚动
- [x] 第十步已完成并通过验收:`deepDig/processAnalysis/index.vue` 迁移到统一页面壳(`BillPageShell`
- [x] 第十步注意点(已沉淀):
- 保持原双列业务结构与内部滚动策略不变,仅替换页面根壳
- 页面底部留白统一为容器 `margin-bottom: 30px`,与外层 `layout-main` 滚动机制一致
新增组件建议放在:`src/views/bill/components/layout/`
#### 计划新增组件索引
1. `src/views/bill/components/layout/BillPageShell.vue`
- 作用:统一页面根容器、内容区、底部留白(30px)机制。
2. `src/views/bill/components/layout/BillTwoColumn.vue`
- 作用:统一左右列布局(默认 1064 / 520)、列间距、响应式策略。
3. `src/views/bill/components/layout/BillPanel.vue`
- 作用:统一卡片容器(header/body/footer)结构、间距、基础样式。
4. `src/views/bill/components/layout/index.js`
- 作用:导出布局组件,便于页面统一引入。
> 可选(后续):
> - `BillPanelGroup.vue`(同列多卡片统一间距)
> - `BillPageSection.vue`(统一页面内 section 标题与辅助动作区)
### 阶段 C:样式 Token 统一(计划)
新增:`src/styles/bill-tokens.scss`(或接入现有变量体系)
- 间距:`8/12/16/24/30`
- 列间距:`16`
- 标题字号/字重
- 卡片边框、阴影、圆角
- 统一颜色 token(主文字、次文字、强调色)
### 阶段 D:页面迁移(计划)
按“先典型、后批量”执行:
1. 试点页(先迁移)
- `background/index.vue`(典型双列 + 卡片 + 多交互)
2. 法案概况组
- `introdoction/index.vue`
- `template/index.vue`
- `background/index.vue`
3. 深度挖掘组
- `deepDig/processOverview/index.vue`
- `deepDig/processAnalysis/index.vue`
- `deepDig/poliContribution/index.vue`
4. 影响分析组
- `influence/industry/index.vue`
- `influence/scientificResearch/index.vue`
- `influence/index.vue`(承载层)
### 阶段 E:收口与回归(计划)
- 清理临时样式补丁(历史试错 `gap/margin-bottom` 等)。
- 回归检查:
1. 所有页面底部留白可见
2. 无横向滚动条
3. Header 不丢失
4. 双列不挤压
## 4. 现有页面索引(改造范围)
### 4.1 板块承载与导航层
- `src/views/bill/billLayout/index.vue`
- `src/views/bill/billLayout/components/BillHeader.vue`
- `src/views/bill/index.vue`
- `src/views/bill/deepDig/index.vue`
- `src/views/bill/influence/index.vue`
### 4.2 法案概况(Bill)
- `src/views/bill/introdoction/index.vue`
- `src/views/bill/background/index.vue`
- `src/views/bill/template/index.vue`
- `src/views/bill/billOriginalText/index.vue`
### 4.3 深度挖掘(DeepDig)
- `src/views/bill/deepDig/processOverview/index.vue`
- `src/views/bill/deepDig/processAnalysis/index.vue`
- `src/views/bill/deepDig/poliContribution/index.vue`
### 4.4 影响分析(Influence)
- `src/views/bill/influence/industry/index.vue`
- `src/views/bill/influence/scientificResearch/index.vue`
- `src/views/bill/influence/ProgressForecast/index.vue`
### 4.5 其他相关页面
- `src/views/bill/relevantCircumstance/index.vue`
- `src/views/bill/versionCompare/index.vue`
- `src/views/bill/billHome/index.vue`
## 5. 迁移原则(执行时必须遵守)
1. 不改变业务逻辑(API、状态、路由行为)
2. 先替换布局骨架,再替换样式细节
3. 每次只迁移少量页面,保证可回滚
4. 对“固定高度 + 内部滚动”页面优先保守改造
5. 若页面是横向 flex 主体,禁止直接插入会改变横向布局的占位节点
## 6. 验收标准
1. `@bill` 目标页面滚动到底统一可见 `30px` 留白
2. 页面核心布局(左右列、卡片)不变形
3. `BillHeader` 的 Tab 与功能按钮稳定显示
4. 不新增横向滚动条
5. 关键路由刷新后视觉一致
## 7. 新会话接续建议
新会话可直接按以下顺序继续:
1. 先读取本文件:
- `src/views/bill/BILL_LAYOUT_UNIFICATION_PLAN.md`
2. 先完成组件脚手架:
- `BillPageShell.vue`
- `BillTwoColumn.vue`
- `BillPanel.vue`
3. 首个迁移页:
- `src/views/bill/background/index.vue`
4. 迁移完成后逐页扩展到 `introdoction/template/processOverview/processAnalysis`
---
最后更新:2026-04-02
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
</div> </div>
<div class="right-box-bottom" v-if="showActions"> <div class="right-box-bottom" v-if="showActions">
<div class="btn2" @click="emit('open-analysis', 'forsee')"> <div class="btn2" v-if="showForsee" @click="emit('open-analysis', 'forsee')">
<div class="icon"> <div class="icon">
<img :src="btnIconForsee" alt="" /> <img :src="btnIconForsee" alt="" />
</div> </div>
...@@ -108,6 +108,10 @@ const props = defineProps({ ...@@ -108,6 +108,10 @@ const props = defineProps({
showActions: { showActions: {
type: Boolean, type: Boolean,
default: true default: true
},
showForsee: {
type: Boolean,
default: true
} }
}); });
...@@ -154,9 +158,7 @@ const emit = defineEmits(["tab-click", "open-analysis"]); ...@@ -154,9 +158,7 @@ const emit = defineEmits(["tab-click", "open-analysis"]);
} }
.header-main { .header-main {
position: sticky; position: relative;
top: 0;
z-index: 1000;
width: 100%; width: 100%;
background-color: #fff; background-color: #fff;
box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.05); box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.05);
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
<div class="layout-main"> <div class="layout-main">
<BillHeader :billInfo="billInfoGlobal" :defaultLogo="USALogo" :tabs="mainHeaderBtnList" <BillHeader :billInfo="billInfoGlobal" :defaultLogo="USALogo" :tabs="mainHeaderBtnList"
:activeTitle="activeTitle" :showTabs="showHeaderTabs" :showActions="showHeaderActions" :activeTitle="activeTitle" :showTabs="showHeaderTabs" :showActions="showHeaderActions"
@tab-click="handleClickMainHeaderBtn" @open-analysis="handleAnalysisClick" /> :showForsee="showForseeAction" @tab-click="handleClickMainHeaderBtn"
@open-analysis="handleAnalysisClick" />
<div class="layout-main-center"> <div class="layout-main-center">
<router-view /> <router-view />
...@@ -14,10 +15,10 @@ ...@@ -14,10 +15,10 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch } from "vue"; import { ref, onMounted, watch, computed } from "vue";
import router from "@/router"; import router from "@/router";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { getBillInfoGlobal } from "@/api/bill"; import { getBillInfoGlobal, getBillInfo } from "@/api/bill";
import BillHeader from "./components/BillHeader.vue"; import BillHeader from "./components/BillHeader.vue";
const route = useRoute(); const route = useRoute();
...@@ -77,6 +78,25 @@ const activeTitle = ref("法案概况"); ...@@ -77,6 +78,25 @@ const activeTitle = ref("法案概况");
const showHeaderTabs = ref(true); const showHeaderTabs = ref(true);
const showHeaderActions = ref(true); const showHeaderActions = ref(true);
const billBasicInfo = ref({});
const getBillBasicInfoFn = async () => {
const billId = route.query.billId;
if (!billId) return;
try {
const res = await getBillInfo({ id: billId });
billBasicInfo.value = res?.data || {};
} catch (error) {
console.error(error);
billBasicInfo.value = {};
}
};
const showForseeAction = computed(() => {
const stageList = Array.isArray(billBasicInfo.value?.stageList) ? billBasicInfo.value.stageList : [];
const latestStage = stageList.length ? String(stageList[stageList.length - 1] || "").trim() : "";
return !latestStage.includes("完成立法");
});
const getActiveTitleByRoutePath = path => { const getActiveTitleByRoutePath = path => {
if (path.startsWith("/billLayout/deepDig")) return "深度挖掘"; if (path.startsWith("/billLayout/deepDig")) return "深度挖掘";
if (path.startsWith("/billLayout/influence")) return "影响分析"; if (path.startsWith("/billLayout/influence")) return "影响分析";
...@@ -132,6 +152,7 @@ const handleAnalysisClick = analysisType => { ...@@ -132,6 +152,7 @@ const handleAnalysisClick = analysisType => {
onMounted(() => { onMounted(() => {
getBillInfoGlobalFn(); getBillInfoGlobalFn();
getBillBasicInfoFn();
// 以当前路由为准,避免 sessionStorage 造成高亮错乱 // 以当前路由为准,避免 sessionStorage 造成高亮错乱
syncHeaderStateFromRoute(); syncHeaderStateFromRoute();
// 兜底:如果未来出现未知路由且有缓存,再用缓存 // 兜底:如果未来出现未知路由且有缓存,再用缓存
...@@ -143,6 +164,14 @@ watch( ...@@ -143,6 +164,14 @@ watch(
() => route.path, () => route.path,
() => { () => {
syncHeaderStateFromRoute(); syncHeaderStateFromRoute();
}
);
watch(
() => route.query.billId,
() => {
getBillInfoGlobalFn();
getBillBasicInfoFn();
}, },
{ immediate: true } { immediate: true }
); );
...@@ -160,12 +189,12 @@ watch( ...@@ -160,12 +189,12 @@ watch(
width: 100%; width: 100%;
height: 100vh; height: 100vh;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden;
.layout-main-center { .layout-main-center {
// height: calc(100% - 137px);
width: 1600px; width: 1600px;
max-width: 100%;
margin: 0 auto; margin: 0 auto;
padding-bottom: 20px;
} }
} }
} }
......
<template>
<div class="bill-page-shell" :class="shellClass" :style="shellStyle">
<div class="bill-page-shell__content" :class="contentClass">
<slot />
</div>
</div>
</template>
<script>
const BILL_DEFAULT_BOTTOM_SAFE_SPACE = 30
export default {
name: 'BillPageShell',
props: {
bottomSafeSpace: {
type: Number,
default: BILL_DEFAULT_BOTTOM_SAFE_SPACE
},
shellClass: {
type: [String, Array, Object],
default: ''
},
contentClass: {
type: [String, Array, Object],
default: ''
}
},
computed: {
shellStyle() {
return {
paddingBottom: `${this.bottomSafeSpace}px`
}
}
}
}
</script>
<style scoped lang="scss">
@import '@/styles/bill-tokens.scss';
.bill-page-shell {
width: 100%;
min-height: 0;
box-sizing: border-box;
}
.bill-page-shell__content {
width: 100%;
color: $bill-text-primary;
}
</style>
<template>
<section class="bill-panel" :class="panelClass" :style="panelStyle">
<header
v-if="$slots.header"
class="bill-panel__header"
:class="headerClass"
:style="sectionStyle"
>
<slot name="header" />
</header>
<div class="bill-panel__body" :class="bodyClass" :style="sectionStyle">
<slot />
</div>
<footer
v-if="$slots.footer"
class="bill-panel__footer"
:class="footerClass"
:style="sectionStyle"
>
<slot name="footer" />
</footer>
</section>
</template>
<script>
const BILL_DEFAULT_PANEL_PADDING = 16
const BILL_DEFAULT_PANEL_RADIUS = 8
export default {
name: 'BillPanel',
props: {
panelClass: {
type: [String, Array, Object],
default: ''
},
headerClass: {
type: [String, Array, Object],
default: ''
},
bodyClass: {
type: [String, Array, Object],
default: ''
},
footerClass: {
type: [String, Array, Object],
default: ''
},
padding: {
type: Number,
default: BILL_DEFAULT_PANEL_PADDING
},
borderRadius: {
type: Number,
default: BILL_DEFAULT_PANEL_RADIUS
},
bordered: {
type: Boolean,
default: true
},
shadow: {
type: Boolean,
default: false
},
background: {
type: String,
default: '#fff'
}
},
computed: {
panelStyle() {
return {
background: this.background,
borderRadius: `${this.borderRadius}px`,
border: this.bordered ? '1px solid #e8ecf3' : 'none',
boxShadow: this.shadow ? '0 2px 8px rgba(15, 35, 95, 0.06)' : 'none'
}
},
sectionStyle() {
return {
padding: `${this.padding}px`
}
}
}
}
</script>
<style scoped lang="scss">
@import '@/styles/bill-tokens.scss';
.bill-panel {
width: 100%;
box-sizing: border-box;
color: $bill-text-primary;
}
.bill-panel__header,
.bill-panel__body,
.bill-panel__footer {
width: 100%;
box-sizing: border-box;
}
.bill-panel__header {
padding-bottom: $bill-space-12;
}
.bill-panel__footer {
padding-top: $bill-space-12;
}
.bill-panel__header:empty,
.bill-panel__footer:empty {
display: none;
}
</style>
<template>
<div class="bill-two-column" :class="wrapperClass" :style="wrapperStyle">
<div class="bill-two-column__left" :style="leftStyle">
<slot name="left" />
</div>
<div class="bill-two-column__right" :style="rightStyle">
<slot name="right" />
</div>
</div>
</template>
<script>
const BILL_DEFAULT_LEFT_WIDTH = 1064
const BILL_DEFAULT_RIGHT_WIDTH = 520
const BILL_DEFAULT_COLUMN_GAP = 16
const BILL_DEFAULT_MIN_STACK_WIDTH = 1660
export default {
name: 'BillTwoColumn',
props: {
leftWidth: {
type: Number,
default: BILL_DEFAULT_LEFT_WIDTH
},
rightWidth: {
type: Number,
default: BILL_DEFAULT_RIGHT_WIDTH
},
gap: {
type: Number,
default: BILL_DEFAULT_COLUMN_GAP
},
minStackWidth: {
type: Number,
default: BILL_DEFAULT_MIN_STACK_WIDTH
},
stackOnNarrow: {
type: Boolean,
default: true
},
wrapperClass: {
type: [String, Array, Object],
default: ''
}
},
computed: {
shouldStack() {
if (!this.stackOnNarrow || typeof window === 'undefined') {
return false
}
return window.innerWidth < this.minStackWidth
},
wrapperStyle() {
if (this.shouldStack) {
return {
display: 'flex',
flexDirection: 'column',
gap: `${this.gap}px`
}
}
return {
display: 'grid',
gridTemplateColumns: `${this.leftWidth}px ${this.rightWidth}px`,
columnGap: `${this.gap}px`,
alignItems: 'start'
}
},
leftStyle() {
if (this.shouldStack) {
return {
width: '100%'
}
}
return {
minWidth: '0'
}
},
rightStyle() {
if (this.shouldStack) {
return {
width: '100%'
}
}
return {
minWidth: '0'
}
}
}
}
</script>
<style scoped lang="scss">
@import '@/styles/bill-tokens.scss';
.bill-two-column {
width: 100%;
min-width: 0;
color: $bill-text-primary;
}
.bill-two-column__left,
.bill-two-column__right {
min-width: 0;
}
</style>
import BillPageShell from './BillPageShell.vue'
import BillTwoColumn from './BillTwoColumn.vue'
import BillPanel from './BillPanel.vue'
export {
BillPageShell,
BillTwoColumn,
BillPanel
}
<template> <template>
<div class="wrap"> <BillPageShell class="wrap">
<div class="left"> <div class="left">
<div class="box1"> <div class="box1">
<!-- <div class="box-header"> <!-- <div class="box-header">
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
</div> </div>
</div> </div>
</div> --> </div> -->
<AnalysisBox title="典型阶段耗时"> <AnalysisBox title="典型阶段耗时分析">
<div class="analysis-ai-wrapper analysis-ai-wrapper--box1"> <div class="analysis-ai-wrapper analysis-ai-wrapper--box1">
<div class="box1-main" :class="{ 'box1-main--full': !timeFooterText }"> <div class="box1-main" :class="{ 'box1-main--full': !timeFooterText }">
<div class="box1-main-center" id="chart1"></div> <div class="box1-main-center" id="chart1"></div>
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
</div> </div>
</div> </div>
<div v-if="!aiPaneVisible.box1" class="analysis-ai-tip-row"> <div v-if="!aiPaneVisible.box1" class="analysis-ai-tip-row">
<TipTab class="analysis-ai-tip" /> <TipTab class="analysis-ai-tip" :text="'与历史同类法案的典型阶段耗时对比分析,数据来源:美国国会官网'"/>
<AiButton class="analysis-ai-tip-action" @mouseenter="handleShowAiPane('box1')" /> <AiButton class="analysis-ai-tip-action" @mouseenter="handleShowAiPane('box1')" />
</div> </div>
<div v-if="aiPaneVisible.box1" class="analysis-ai-pane" @mouseleave="handleHideAiPane('box1')"> <div v-if="aiPaneVisible.box1" class="analysis-ai-pane" @mouseleave="handleHideAiPane('box1')">
...@@ -105,7 +105,7 @@ ...@@ -105,7 +105,7 @@
</div> </div>
</div> </div>
<div v-if="!aiPaneVisible.box2" class="analysis-ai-tip-row"> <div v-if="!aiPaneVisible.box2" class="analysis-ai-tip-row">
<TipTab class="analysis-ai-tip" /> <TipTab class="analysis-ai-tip" :text="'与历史同类法案的修正案次数对比分析,数据来源:美国国会官网'"/>
<AiButton class="analysis-ai-tip-action" @mouseenter="handleShowAiPane('box2')" /> <AiButton class="analysis-ai-tip-action" @mouseenter="handleShowAiPane('box2')" />
</div> </div>
<div v-if="aiPaneVisible.box2" class="analysis-ai-pane" @mouseleave="handleHideAiPane('box2')"> <div v-if="aiPaneVisible.box2" class="analysis-ai-pane" @mouseleave="handleHideAiPane('box2')">
...@@ -716,7 +716,7 @@ ...@@ -716,7 +716,7 @@
</AnalysisBox> </AnalysisBox>
</div> </div>
</div> </div>
</div> </BillPageShell>
</template> </template>
<script setup> <script setup>
...@@ -728,6 +728,7 @@ import { getChartAnalysis } from "@/api/aiAnalysis/index"; ...@@ -728,6 +728,7 @@ import { getChartAnalysis } from "@/api/aiAnalysis/index";
import TipTab from "@/components/base/TipTab/index.vue"; import TipTab from "@/components/base/TipTab/index.vue";
import AiButton from "@/components/base/Ai/AiButton/index.vue"; import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue"; import AiPane from "@/components/base/Ai/AiPane/index.vue";
import { BillPageShell } from "../../components/layout";
import icon1 from "./assets/images/icon1.png"; import icon1 from "./assets/images/icon1.png";
import icon2 from "./assets/images/icon2.png"; import icon2 from "./assets/images/icon2.png";
...@@ -1160,7 +1161,7 @@ onMounted(async () => { ...@@ -1160,7 +1161,7 @@ onMounted(async () => {
min: "最小次数", min: "最小次数",
current: "该法案修正案数量" current: "该法案修正案数量"
}; };
let chart2 = getBoxPlotChcart(chartData2.value, "", countLabels); let chart2 = getBoxPlotChcart(chartData2.value, "", countLabels);
setChart(chart2, "chart2"); setChart(chart2, "chart2");
}); });
</script> </script>
...@@ -1168,6 +1169,8 @@ onMounted(async () => { ...@@ -1168,6 +1169,8 @@ onMounted(async () => {
<style lang="scss" scoped> <style lang="scss" scoped>
.wrap { .wrap {
display: flex; display: flex;
margin-bottom: 30px;
.box-header { .box-header {
height: 56px; height: 56px;
......
<template> <template>
<div class="process-overview-wrap"> <BillPageShell class="process-overview-wrap">
<AnalysisBox title="流程概要" :showAllBtn="false"> <AnalysisBox title="流程概要" :showAllBtn="false">
<div class="main"> <div class="main">
<div class="left" :style="{ width: boardWidth + 'px' }"> <div class="left" :style="{ width: boardWidth + 'px' }">
...@@ -194,7 +194,7 @@ ...@@ -194,7 +194,7 @@
:position="dialogPos" :position="dialogPos"
@close="handleClickDetail(false)" @close="handleClickDetail(false)"
/> />
</div> </BillPageShell>
</template> </template>
<script setup> <script setup>
...@@ -202,6 +202,7 @@ import { ref, onMounted, computed, nextTick } from "vue"; ...@@ -202,6 +202,7 @@ import { ref, onMounted, computed, nextTick } from "vue";
import { getBillDyqkSummary } from "@/api/bill"; import { getBillDyqkSummary } from "@/api/bill";
import CommonPrompt from "../../commonPrompt/index.vue"; import CommonPrompt from "../../commonPrompt/index.vue";
import ProcessOverviewDetailDialog from "../../ProcessOverviewDetailDialog.vue"; import ProcessOverviewDetailDialog from "../../ProcessOverviewDetailDialog.vue";
import { BillPageShell } from "../../components/layout";
const actionList = ref([]); const actionList = ref([]);
const isShowDetailDialog = ref(false); const isShowDetailDialog = ref(false);
...@@ -499,6 +500,7 @@ const updateRightTop = () => { ...@@ -499,6 +500,7 @@ const updateRightTop = () => {
width: 1600px; width: 1600px;
height: 848px; height: 848px;
margin-top: 16px; margin-top: 16px;
margin-bottom: 30px;
position: relative; position: relative;
.main { .main {
...@@ -799,20 +801,13 @@ const updateRightTop = () => { ...@@ -799,20 +801,13 @@ const updateRightTop = () => {
.text { .text {
width: 240px; width: 240px;
max-width: 100%;
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;
display: -webkit-box; white-space: nowrap;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
height: 48px;
}
:deep(.text-ellipsis) {
white-space: normal !important;
} }
} }
} }
......
<template> <template>
<div class="introduction-wrap"> <BillPageShell class="introduction-wrap">
<WarningPane v-if="riskSignal" class="risk-signal-pane-top" :warnningLevel="riskSignal.riskLevel" <WarningPane v-if="riskSignal" class="risk-signal-pane-top" :warnningLevel="riskSignal.riskLevel"
:warnningContent="riskSignal.riskContent" /> :warnningContent="riskSignal.riskContent" />
<div class="introduction-wrap-content"> <BillTwoColumn class="introduction-wrap-content" :stack-on-narrow="false">
<div class="introduction-wrap-left"> <template #left>
<div class="introduction-wrap-left">
<div class="introduction-wrap-left-box1"> <div class="introduction-wrap-left-box1">
<AnalysisBox title="基本信息" :showAllBtn="false"> <AnalysisBox title="基本信息" :showAllBtn="false">
...@@ -69,10 +70,12 @@ ...@@ -69,10 +70,12 @@
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
</div> </div>
<div class="introduction-wrap-right"> </template>
<AnalysisBox title="提出人" :showAllBtn="false"> <template #right>
<div class="introduction-wrap-right-main"> <div class="introduction-wrap-right">
<AnalysisBox title="提出人" :showAllBtn="false">
<div class="introduction-wrap-right-main">
<div class="right-main-box1"> <div class="right-main-box1">
<div class="name-box"> <div class="name-box">
<div class="person-box"> <div class="person-box">
...@@ -139,10 +142,11 @@ ...@@ -139,10 +142,11 @@
</div> </div>
</div> </div>
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
</div> </template>
</div> </BillTwoColumn>
</BillPageShell>
</template> </template>
<script setup> <script setup>
...@@ -151,6 +155,7 @@ import { useRoute, useRouter } from "vue-router"; ...@@ -151,6 +155,7 @@ import { useRoute, useRouter } from "vue-router";
import WordCloudMap from "./WordCloudMap.vue"; import WordCloudMap from "./WordCloudMap.vue";
import STimeline from "./STimeline.vue"; import STimeline from "./STimeline.vue";
import WarningPane from "@/components/base/WarningPane/index.vue"; import WarningPane from "@/components/base/WarningPane/index.vue";
import { BillPageShell, BillTwoColumn } from "../components/layout";
import { getBillInfo, getBillPerson, getBillEvent, getBillDyqk } from "@/api/bill"; import { getBillInfo, getBillPerson, getBillEvent, getBillDyqk } from "@/api/bill";
import { getPersonSummaryInfo } from "@/api/common/index"; import { getPersonSummaryInfo } from "@/api/common/index";
import defaultAvatar from "../assets/images/default-icon1.png"; import defaultAvatar from "../assets/images/default-icon1.png";
...@@ -338,6 +343,14 @@ onMounted(() => { ...@@ -338,6 +343,14 @@ onMounted(() => {
height: auto; height: auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-sizing: border-box;
.page-bottom-gap {
height: 30px;
width: 100%;
flex-shrink: 0;
pointer-events: none;
}
.progress-header-btns { .progress-header-btns {
display: flex; display: flex;
...@@ -432,6 +445,7 @@ onMounted(() => { ...@@ -432,6 +445,7 @@ onMounted(() => {
.introduction-wrap-content { .introduction-wrap-content {
display: flex; display: flex;
margin-bottom: 30px;
} }
.introduction-wrap-left { .introduction-wrap-left {
...@@ -497,7 +511,6 @@ onMounted(() => { ...@@ -497,7 +511,6 @@ onMounted(() => {
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
width: 700px; width: 700px;
min-height: 40px;
gap: 8px; gap: 8px;
.right1-item { .right1-item {
...@@ -769,8 +782,6 @@ onMounted(() => { ...@@ -769,8 +782,6 @@ onMounted(() => {
.introduction-wrap-right { .introduction-wrap-right {
margin-top: 16px; margin-top: 16px;
margin-left: 16px;
margin-right: 18px;
width: 520px; width: 520px;
height: 845px; height: 845px;
......
...@@ -165,7 +165,7 @@ import { useRoute } from "vue-router"; ...@@ -165,7 +165,7 @@ import { useRoute } from "vue-router";
import router from '@/router' import router from '@/router'
import { getPostOrgList, getPostMemberList } from '@/api/bill/billHome' import { getPostOrgList, getPostMemberList } from '@/api/bill/billHome'
import { search } from '@/api/comprehensiveSearch' import { search, getStatusList } from '@/api/comprehensiveSearch'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import getDateRange from '@/utils/getDateRange' import getDateRange from '@/utils/getDateRange'
...@@ -345,10 +345,13 @@ const activeTagList = computed(() => { ...@@ -345,10 +345,13 @@ const activeTagList = computed(() => {
) )
} }
if (selectedStatus.value && selectedStatus.value !== '全部阶段') { if (selectedStatus.value && selectedStatus.value !== '全部阶段') {
const statusName = statusList.value.filter(item => {
return item.id === selectedStatus.value
})[0].name
arr.push( arr.push(
{ {
tag: '所处阶段', tag: '所处阶段',
name: selectedStatus.value name: statusName
} }
) )
} }
...@@ -711,6 +714,31 @@ const statusList = ref([ ...@@ -711,6 +714,31 @@ const statusList = ref([
}, },
]) ])
const handleGetStatusList = async () => {
try {
const res = await getStatusList()
console.log('获取立法阶段列表', res);
if (res.code === 200) {
const arr = res.data.map(item => {
return {
name: item.name,
id: item.number
}
})
statusList.value = [
{
name: '全部阶段',
id: '全部阶段'
},
...arr
]
}
} catch (error) {
}
}
const selectedStatus = ref('全部阶段') const selectedStatus = ref('全部阶段')
const statusPlaceHolder = ref('请选择立法阶段') const statusPlaceHolder = ref('请选择立法阶段')
const handleSelectStauts = value => { const handleSelectStauts = value => {
...@@ -737,6 +765,7 @@ const handleClear = () => { ...@@ -737,6 +765,7 @@ const handleClear = () => {
// 确定 // 确定
const handleConfirm = () => { const handleConfirm = () => {
currentPage.value = 1
fetchTableData() fetchTableData()
} }
...@@ -785,6 +814,9 @@ const fetchTableData = async () => { ...@@ -785,6 +814,9 @@ const fetchTableData = async () => {
// isSelectedAll.value = false // isSelectedAll.value = false
// selectedMap.value.clear() // selectedMap.value.clear()
// 调用接口获取数据... // 调用接口获取数据...
loading.value = true
const params = { const params = {
page: currentPage.value, page: currentPage.value,
size: pageSize.value, size: pageSize.value,
...@@ -843,12 +875,10 @@ const fetchTableData = async () => { ...@@ -843,12 +875,10 @@ const fetchTableData = async () => {
activeChart.value = curDemensionItem.chartTypeList[0] activeChart.value = curDemensionItem.chartTypeList[0]
curChartData.value = curDemensionItem.data curChartData.value = curDemensionItem.data
}) })
loading.value = false
} catch (error) { } catch (error) {
loading.value = false
} }
// tableData.value = res.data // tableData.value = res.data
// total.value = res.total // total.value = res.total
...@@ -867,6 +897,12 @@ const allData = ref([]) ...@@ -867,6 +897,12 @@ const allData = ref([])
// 获取筛选条件下全部表格数据 // 获取筛选条件下全部表格数据
const fetchAllData = async () => { const fetchAllData = async () => {
let statusParam = null
if (selectedStatus.value !== '全部阶段') {
statusParam = statusList.value.filter(item => {
return item.name === selectedStatus.value
})[0].id
}
const params = { const params = {
page: 1, page: 1,
size: 9999, size: 9999,
...@@ -879,7 +915,7 @@ const fetchAllData = async () => { ...@@ -879,7 +915,7 @@ const fetchAllData = 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.value ? 'Y' : 'N', isInvolveCn: isInvolveCn.value ? 'Y' : 'N',
sort: isSort.value ? 0 : 1 // 0 先按分数降序 后按时间降序 1 先按分数降序,再按时间升序 sort: isSort.value ? 0 : 1 // 0 先按分数降序 后按时间降序 1 先按分数降序,再按时间升序
} }
...@@ -1038,8 +1074,10 @@ const initParam = () => { ...@@ -1038,8 +1074,10 @@ const initParam = () => {
customTime.value = JSON.parse(route.query.selectedDate) customTime.value = JSON.parse(route.query.selectedDate)
} }
isInvolveCn.value = route.query.isInvolveCn ? true : false isInvolveCn.value = route.query.isInvolveCn ? true : false
if (route.query.selectedStatus) { if (route.query.selectedStatus && route.query.selectedStatus !== '全部阶段') {
selectedStatus.value = route.query.selectedStatus selectedStatus.value = statusList.value.filter(item => {
return item.name === route.query.selectedStatus
})[0].id
} else { } else {
selectedStatus.value = '全部阶段' selectedStatus.value = '全部阶段'
} }
...@@ -1054,7 +1092,7 @@ const initParam = () => { ...@@ -1054,7 +1092,7 @@ const initParam = () => {
} else { } else {
const savedQuery = JSON.parse(sessionStorage.getItem('routeQuery') || '{}'); const savedQuery = JSON.parse(sessionStorage.getItem('routeQuery') || '{}');
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)
} }
...@@ -1123,9 +1161,11 @@ const handleExport = () => { ...@@ -1123,9 +1161,11 @@ const handleExport = () => {
onMounted(async () => { onMounted(async () => {
handleGetOrgList() handleGetOrgList()
handleGetMemberList() handleGetMemberList()
initParam() await handleGetStatusList()
// 初始化 // 初始化
await fetchTableData() initParam()
fetchTableData()
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
......
...@@ -124,13 +124,13 @@ ...@@ -124,13 +124,13 @@
</div> </div>
</div> </div>
</div> </div>
<div class="data-main-box-main-content" v-loading="loading" element-loading-text="全部数据加载中,请稍候..."> <div class="data-main-box-main-content" v-loading="loading" element-loading-text="数据加载中,请稍候...">
<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="720"> <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.originalTitle
}}</span> }}</span>
</template> </template>
</el-table-column> </el-table-column>
...@@ -139,7 +139,7 @@ ...@@ -139,7 +139,7 @@
</el-table-column> </el-table-column>
<el-table-column label="发布机构"> <el-table-column label="发布机构">
<template #default="scope"> <template #default="scope">
<span class="person-item text-compact" @click="handlePerClick(scope.row)">{{ scope.row.organizationName <span class="person-item text-compact" @click="handleOrgClick(scope.row)">{{ scope.row.organizationName
}}</span> }}</span>
</template> </template>
</el-table-column> </el-table-column>
...@@ -675,6 +675,7 @@ const handleClear = () => { ...@@ -675,6 +675,7 @@ const handleClear = () => {
// 确定 // 确定
const handleConfirm = () => { const handleConfirm = () => {
currentPage.value = 1
fetchTableData() fetchTableData()
} }
...@@ -718,6 +719,7 @@ const selectedCount = computed(() => selectedMap.value.size) ...@@ -718,6 +719,7 @@ const selectedCount = computed(() => selectedMap.value.size)
const fetchTableData = async () => { const fetchTableData = async () => {
// isSelectedAll.value = false // isSelectedAll.value = false
// selectedMap.value.clear() // selectedMap.value.clear()
loading.value = true
// 调用接口获取数据... // 调用接口获取数据...
const params = { const params = {
page: currentPage.value, page: currentPage.value,
...@@ -772,8 +774,9 @@ const fetchTableData = async () => { ...@@ -772,8 +774,9 @@ const fetchTableData = async () => {
activeChart.value = curDemensionItem.chartTypeList[0] activeChart.value = curDemensionItem.chartTypeList[0]
curChartData.value = curDemensionItem.data curChartData.value = curDemensionItem.data
}) })
loading.value = false
} catch (error) { } catch (error) {
loading.value = false
} }
// tableData.value = res.data // tableData.value = res.data
// total.value = res.total // total.value = res.total
...@@ -1003,17 +1006,18 @@ const handleClickToDetail = (curDecree) => { ...@@ -1003,17 +1006,18 @@ const handleClickToDetail = (curDecree) => {
window.open(route.href, "_blank"); window.open(route.href, "_blank");
}; };
// 跳转人物详情 // 跳转机构详情
const handlePerClick = item => { const handleOrgClick = item => {
window.sessionStorage.setItem("curTabName", item.sponsorPersonName); console.log('item', item);
const route = router.resolve({
path: "/characterPage", window.sessionStorage.setItem("curTabName", item.organizationName);
query: { const route = router.resolve({
type: 2, path: "/institution",
personId: item.personId query: {
} id: item.organizationId
}); }
window.open(route.href, "_blank"); });
window.open(route.href, "_blank");
}; };
// 导出 // 导出
...@@ -1216,6 +1220,8 @@ onMounted(async () => { ...@@ -1216,6 +1220,8 @@ onMounted(async () => {
.header-left-item2 { .header-left-item2 {
color: var(--color-primary-100); color: var(--color-primary-100);
display: flex;
gap: 8px;
} }
......
...@@ -42,7 +42,12 @@ ...@@ -42,7 +42,12 @@
<img :src="item.imgUrl || DefaultIcon2" alt="" /> <img :src="item.imgUrl || DefaultIcon2" alt="" />
</div> </div>
<div class="item-right one-line-ellipsis" @click="handleToInstitution(item)">{{ item.orgName }}</div> <div class="item-right one-line-ellipsis" @click="handleToInstitution(item)">{{ item.orgName }}</div>
<div class="item-total" @click="handleToDataLibrary(item)">{{ item.totalOrderNum }}</div> <el-popover content="跳转至数据资源库" placement="top">
<template #reference>
<div class="item-total" @click="handleToDataLibrary(item)">{{ item.totalOrderNum }}</div>
</template>
</el-popover>
<el-icon color="var(--color-primary-100)"> <el-icon color="var(--color-primary-100)">
<ArrowRightBold /> <ArrowRightBold />
</el-icon> </el-icon>
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
</div> </div>
<div class="layout-main-center"> <div class="layout-main-center">
<BaseDecreeOriginal :report-data="reportData" @download="handleDownload" /> <newOriginal ref="refNewOriginal" @download="handleDownload"></newOriginal>
</div> </div>
</div> </div>
</div> </div>
...@@ -38,7 +38,7 @@ import { useRoute } from "vue-router"; ...@@ -38,7 +38,7 @@ import { useRoute } from "vue-router";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { getDecreeSummary } from "@/api/decree/introduction"; import { getDecreeSummary } from "@/api/decree/introduction";
import { getDecreeReport } from "@/api/decree/introduction"; import { getDecreeReport } from "@/api/decree/introduction";
import BaseDecreeOriginal from "@/components/base/DecreeOriginal/index.vue"; import newOriginal from "@/components/base/DecreeOriginal/newOriginal.vue";
const route = useRoute(); const route = useRoute();
let pdfUrl = ""; let pdfUrl = "";
...@@ -98,26 +98,16 @@ const handleGetSummary = async () => { ...@@ -98,26 +98,16 @@ const handleGetSummary = async () => {
}; };
// 获取报告原文 - 修改为获取分段数组 // 获取报告原文 - 修改为获取分段数组
const reportData = ref([]); const refNewOriginal = ref(null);
const handleGetReport = async () => { const handleGetReport = async () => {
try { try {
const res = await getDecreeReport({id: route.query.id}); const res = await getDecreeReport({id: route.query.id});
console.log("报告原文", res); console.log("报告原文", res);
if (res.code === 200 && res.data) { if (res.code === 200) {
pdfUrl = res.data.pdfUrl; pdfUrl = res.data.pdfUrl;
const originData = []; refNewOriginal.value.setOriginalData({...res.data});
let num = Math.max(res.data.content.length, res.data.contentEn.length)
for (let i = 0; i < num; i++) {
let obj = {
content: res.data.content[i] || "",
contentEn: res.data.contentEn[i] || "",
num: i + 1,
}
originData.push(obj);
}
reportData.value = JSON.parse(JSON.stringify(originData));
} }
} catch (error) { } } catch (error) {}
}; };
onMounted(() => { onMounted(() => {
......
...@@ -16,14 +16,12 @@ ...@@ -16,14 +16,12 @@
<div class="timeline-content-card"> <div class="timeline-content-card">
<div class="item-head"> <div class="item-head">
<div :class="`item-tag tag-${item.sortcode}`">{{ item.sortcode }}</div> <div :class="`item-tag tag-${item.sortcode}`">{{ item.sortcode }}</div>
<div class="item-name">{{ item.searchname }}</div> <div class="item-name one-line-ellipsis">{{ item.searchname }}</div>
<div class="item-state"> <div class="item-state">
<span class="dot"></span> {{ item.casestatus }} <span class="dot"></span> {{ item.casestatus }}
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">{{ item.content }}</div>
{{ item.content }}
</div>
<div class="card-footer"> <div class="card-footer">
<div class="footer-left-tags"> <div class="footer-left-tags">
<AreaTag v-for="(name, num) in item.searchArea" :key="num" :tagName="name"></AreaTag> <AreaTag v-for="(name, num) in item.searchArea" :key="num" :tagName="name"></AreaTag>
...@@ -129,7 +127,7 @@ const onNavigateToDetail = item => { ...@@ -129,7 +127,7 @@ const onNavigateToDetail = item => {
.timeline-content-card { .timeline-content-card {
width: 20px; width: 20px;
flex: auto; flex: auto;
padding: 2px 16px 0; padding: 4px 16px 0;
margin-bottom: 30px; margin-bottom: 30px;
&:hover .item-head .item-name { &:hover .item-head .item-name {
......
...@@ -32,39 +32,26 @@ ...@@ -32,39 +32,26 @@
<div class="wrapper-main"> <div class="wrapper-main">
<div class="left"> <div class="left">
<!-- 科技领域 --> <!-- 科技领域 -->
<div class="left-box"> <div class="check-box">
<div class="left-header"> <div class="check-head">
<div class="icon"></div> <div class="head-name">{{ "科技领域" }}</div>
<div class="title">{{ "科技领域" }}</div>
</div>
<div class="left-main">
<el-checkbox-group class="checkbox-group" v-model="checkedAreaList" @change="handleCheckedAreasChange">
<el-checkbox class="filter-checkbox" label="全部领域"> 全部领域 </el-checkbox>
<el-checkbox v-for="area in surveyAreaList" :key="area.id" :label="area.id" class="filter-checkbox">
{{ area.name }}
</el-checkbox>
</el-checkbox-group>
</div> </div>
<el-checkbox-group class="check-list" v-model="checkedAreaList" @change="handleCheckedAreasChange">
<el-checkbox class="check-item" v-for="item in surveyAreaList" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</div> </div>
<!-- 发布时间 --> <!-- 发布时间 -->
<div class="left-box"> <div class="check-box">
<div class="left-header"> <div class="check-head">
<div class="icon"></div> <div class="head-name">{{ "发布时间" }}</div>
<div class="title">{{ "发布时间" }}</div>
</div> </div>
<div class="left-main"> <div class="left-main">
<el-checkbox-group class="checkbox-group" v-model="checkedSurveyYears" @change="handleCheckedYearsChange"> <el-checkbox-group class="check-list" v-model="checkedYearList" @change="handleCheckedYearsChange">
<el-checkbox class="filter-checkbox" label="全部时间"> 全部时间 </el-checkbox> <el-checkbox class="check-item" v-for="year in surveyYearList" :key="year.id" :label="year.id">
<el-checkbox v-for="year in displayedYearList" :key="year.id" :label="year.id" class="filter-checkbox">
{{ year.name }} {{ year.name }}
</el-checkbox> </el-checkbox>
<div v-if="surveyYearList.length > 6" class="expand-btn" @click="isYearExpanded = !isYearExpanded">
{{ isYearExpanded ? "收起" : "更早" }}
<el-icon>
<ArrowUp v-if="isYearExpanded" />
<ArrowDown v-else />
</el-icon>
</div>
</el-checkbox-group> </el-checkbox-group>
</div> </div>
</div> </div>
...@@ -98,8 +85,8 @@ ...@@ -98,8 +85,8 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch, computed } from "vue"; import { ref, onMounted, watch } from "vue";
import { Search, ArrowDown, ArrowUp } from "@element-plus/icons-vue"; import { Search } from "@element-plus/icons-vue";
import { getSearchAllArea, getSearchAllYear, getSurveyList } from "@/api/marketAccessRestrictions"; import { getSearchAllArea, getSearchAllYear, getSurveyList } from "@/api/marketAccessRestrictions";
import SurveyHistory from "@/views/marketAccessRestrictions/com/SurveyHistory.vue" import SurveyHistory from "@/views/marketAccessRestrictions/com/SurveyHistory.vue"
...@@ -116,42 +103,49 @@ const handleSwithSort = () => { ...@@ -116,42 +103,49 @@ const handleSwithSort = () => {
// 科技领域过滤 // 科技领域过滤
const surveyAreaList = ref([]); const surveyAreaList = ref([]);
const checkedAreaList = ref([]); const checkedAreaList = ref(['']);
const checkAllAreas = ref(true); const handleGetSearchAllArea = async () => {
const isIndeterminateAreas = ref(false); try {
const res = await getSearchAllArea({ sortCode: "232" });
const handleCheckAllAreasChange = val => { if (res.code === 200) {
checkedAreaList.value = val ? surveyAreaList.value.map(a => a.id) : []; surveyAreaList.value = res.data.map(item => ({ name: item.AREANAME, id: item.AREACODE }));
isIndeterminateAreas.value = false; }
} catch (error) {}
surveyAreaList.value.unshift({ name: "全部领域", id: "" });
}; };
const handleCheckedAreasChange = event => {
const handleCheckedAreasChange = value => { if (event.length && event[event.length-1] !== "") {
const checkedCount = value.length; checkedAreaList.value = event.filter(item => item !== "");
checkAllAreas.value = checkedCount === surveyAreaList.value.length; } else {
isIndeterminateAreas.value = checkedCount > 0 && checkedCount < surveyAreaList.value.length; checkedAreaList.value = [""];
}
currentPage.value = 1;
handleFetchSurveyList();
}; };
// 发布时间过滤 // 发布时间过滤
const surveyYearList = ref([]); const surveyYearList = ref([]);
const checkedSurveyYears = ref([]); const checkedYearList = ref(['']);
const checkAllYears = ref(true); const handleGetSearchAllYear = async () => {
const isIndeterminateYears = ref(false); try {
const isYearExpanded = ref(false); const res = await getSearchAllYear({ sortCode: "232" });
if (res.code === 200) {
const displayedYearList = computed(() => { let allYear = res.data.sort((a, b) => (b-a));
if (isYearExpanded.value) return surveyYearList.value; let beforeYear = allYear.slice(6).join(',');
return surveyYearList.value.slice(0, 6); surveyYearList.value = allYear.slice(0, 6).map(item => ({ name: item + "年", id: item }));
}); if (beforeYear) surveyYearList.value.push({ name: "更早", id: beforeYear });
}
const handleCheckAllYearsChange = val => { } catch (error) {}
checkedSurveyYears.value = val ? surveyYearList.value.map(y => y.id) : []; surveyYearList.value.unshift({ name: "全部时间", id: "" });
isIndeterminateYears.value = false;
}; };
const handleCheckedYearsChange = event => {
const handleCheckedYearsChange = value => { if (event.length && event[event.length-1] !== "") {
const checkedCount = value.length; checkedYearList.value = event.filter(item => item !== "");
checkAllYears.value = checkedCount === surveyYearList.value.length; } else {
isIndeterminateYears.value = checkedCount > 0 && checkedCount < surveyYearList.value.length; checkedYearList.value = [""];
}
currentPage.value = 1;
handleFetchSurveyList();
}; };
// 数据列表 // 数据列表
...@@ -169,8 +163,8 @@ const handleFetchSurveyList = async () => { ...@@ -169,8 +163,8 @@ const handleFetchSurveyList = async () => {
currentPage: currentPage.value - 1, currentPage: currentPage.value - 1,
pageSize: pageSize.value, pageSize: pageSize.value,
sortCode: "232", sortCode: "232",
publishYear: checkAllYears.value ? "" : checkedSurveyYears.value.toString(), publishYear: checkedYearList.value.join(',') || null,
Area: checkAllAreas.value ? "" : checkedAreaList.value.toString(), Area: checkedAreaList.value.join(',') || null,
caseStatus: filterStage.value, caseStatus: filterStage.value,
keywords: searchText.value, keywords: searchText.value,
sortField: "date", sortField: "date",
...@@ -199,39 +193,12 @@ const handleSearch = () => { ...@@ -199,39 +193,12 @@ const handleSearch = () => {
}; };
// 监听过滤条件 // 监听过滤条件
watch([checkedSurveyYears, checkedAreaList, isSort, filterStage, filterParty, filterReason], () => { watch([isSort, filterStage, filterParty, filterReason], () => {
if (isInitializing.value) return; if (isInitializing.value) return;
currentPage.value = 1; currentPage.value = 1;
handleFetchSurveyList(); handleFetchSurveyList();
}); });
const handleGetSearchAllArea = async () => {
try {
const res = await getSearchAllArea({ sortCode: "232" });
if (res.code === 200 && res.data) {
surveyAreaList.value = res.data.map(item => ({
name: item.AREANAME,
id: item.AREACODE
}));
handleCheckAllAreasChange(true);
}
} catch (error) {}
};
const handleGetSearchAllYear = async () => {
try {
const res = await getSearchAllYear({ sortCode: "232" });
if (res.code === 200 && res.data) {
const sortedYears = res.data.sort((a, b) => b - a);
surveyYearList.value = sortedYears.map(item => ({
name: item + "年",
id: item
}));
handleCheckAllYearsChange(true);
}
} catch (error) {}
};
onMounted(async () => { onMounted(async () => {
await Promise.all([handleGetSearchAllArea(), handleGetSearchAllYear()]); await Promise.all([handleGetSearchAllArea(), handleGetSearchAllYear()]);
isInitializing.value = false; isInitializing.value = false;
...@@ -306,58 +273,50 @@ onMounted(async () => { ...@@ -306,58 +273,50 @@ onMounted(async () => {
.left { .left {
width: 360px; width: 360px;
min-height: 560px; min-height: 300px;
height: fit-content; height: fit-content;
padding-bottom: 20px; padding-bottom: 20px;
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2); box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: #fff; background: #fff;
.left-box { .check-box {
margin-top: 17px; margin-top: 18px;
.check-head {
.left-header { position: relative;
display: flex; margin-bottom: 12px;
align-items: center; &::before {
content: "";
.icon { position: absolute;
top: 0px;
left: 0px;
width: 8px; width: 8px;
height: 16px; height: 100%;
background: var(--color-main-active); background: var(--color-primary-100);
border-radius: 0 2px 2px 0; border-radius: 0 2px 2px 0;
} }
.head-name {
.title { margin-left: 25px;
margin-left: 17px; color: var(--color-primary-100);
color: var(--color-main-active);
font-size: 16px; font-size: 16px;
font-weight: 700; line-height: 16px;
font-family: Source Han Sans CN;
font-weight: bold;
} }
} }
} .check-list {
padding: 0 10px 0 25px;
.checkbox-group { display: grid;
padding: 10px 0 0 25px; grid-template-columns: repeat(2, 1fr);
.filter-checkbox { grid-gap: 0 12px;
width: 130px; .check-item {
margin-bottom: 8px; width: 100%;
height: 32px; height: 32px;
:deep(.el-checkbox__label) { :deep(.el-checkbox__label) {
font-size: 16px; font-family: Source Han Sans CN;
color: #5f656c; font-size: 16px;
} color: var(--text-primary-65-color);
} }
.expand-btn {
color: var(--color-main-active);
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
margin-top: 4px;
.el-icon {
margin-left: 4px;
} }
} }
} }
......
...@@ -21,39 +21,26 @@ ...@@ -21,39 +21,26 @@
<div class="wrapper-main"> <div class="wrapper-main">
<div class="left"> <div class="left">
<!-- 科技领域 --> <!-- 科技领域 -->
<div class="left-box"> <div class="check-box">
<div class="left-header"> <div class="check-head">
<div class="icon"></div> <div class="head-name">{{ "科技领域" }}</div>
<div class="title">{{ "科技领域" }}</div>
</div>
<div class="left-main">
<el-checkbox-group class="checkbox-group" v-model="checkedAreaList" @change="handleCheckedAreasChange">
<el-checkbox class="filter-checkbox" label="全部领域"> 全部领域 </el-checkbox>
<el-checkbox v-for="area in surveyAreaList" :key="area.id" :label="area.id" class="filter-checkbox">
{{ area.name }}
</el-checkbox>
</el-checkbox-group>
</div> </div>
<el-checkbox-group class="check-list" v-model="checkedAreaList" @change="handleCheckedAreasChange">
<el-checkbox class="check-item" v-for="item in surveyAreaList" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</div> </div>
<!-- 发布时间 --> <!-- 发布时间 -->
<div class="left-box"> <div class="check-box">
<div class="left-header"> <div class="check-head">
<div class="icon"></div> <div class="head-name">{{ "发布时间" }}</div>
<div class="title">{{ "发布时间" }}</div>
</div> </div>
<div class="left-main"> <div class="left-main">
<el-checkbox-group class="checkbox-group" v-model="checkedSurveyYears" @change="handleCheckedYearsChange"> <el-checkbox-group class="check-list" v-model="checkedYearList" @change="handleCheckedYearsChange">
<el-checkbox class="filter-checkbox" label="全部时间"> 全部时间 </el-checkbox> <el-checkbox class="check-item" v-for="year in surveyYearList" :key="year.id" :label="year.id">
<el-checkbox v-for="year in displayedYearList" :key="year.id" :label="year.id" class="filter-checkbox">
{{ year.name }} {{ year.name }}
</el-checkbox> </el-checkbox>
<div v-if="surveyYearList.length > 6" class="expand-btn" @click="isYearExpanded = !isYearExpanded">
{{ isYearExpanded ? "收起" : "更早" }}
<el-icon>
<ArrowUp v-if="isYearExpanded" />
<ArrowDown v-else />
</el-icon>
</div>
</el-checkbox-group> </el-checkbox-group>
</div> </div>
</div> </div>
...@@ -78,8 +65,8 @@ ...@@ -78,8 +65,8 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch, computed } from "vue"; import { ref, onMounted, watch } from "vue";
import { Search, ArrowDown, ArrowUp } from "@element-plus/icons-vue"; import { Search } from "@element-plus/icons-vue";
import { getSearchAllArea, getSearchAllYear, getSurveyList } from "@/api/marketAccessRestrictions"; import { getSearchAllArea, getSearchAllYear, getSurveyList } from "@/api/marketAccessRestrictions";
import SurveyHistory from "@/views/marketAccessRestrictions/com/SurveyHistory.vue" import SurveyHistory from "@/views/marketAccessRestrictions/com/SurveyHistory.vue"
...@@ -88,46 +75,51 @@ const handleSwithSort = () => { ...@@ -88,46 +75,51 @@ const handleSwithSort = () => {
isSort.value = !isSort.value; isSort.value = !isSort.value;
}; };
const surveyYearList = ref([]); // 科技领域过滤
const checkedSurveyYears = ref([]); const surveyAreaList = ref([]);
const isYearExpanded = ref(false); const checkedAreaList = ref(['']);
const handleGetSearchAllArea = async () => {
const displayedYearList = computed(() => { try {
if (isYearExpanded.value) { const res = await getSearchAllArea({ sortCode: "301" });
return surveyYearList.value; if (res.code === 200) {
} surveyAreaList.value = res.data.map(item => ({ name: item.AREANAME, id: item.AREACODE }));
return surveyYearList.value.slice(0, 6); }
}); } catch (error) {}
surveyAreaList.value.unshift({ name: "全部领域", id: "" });
const checkAllYears = ref(false);
const isIndeterminateYears = ref(false);
const handleCheckAllYearsChange = (val) => {
checkedSurveyYears.value = val ? surveyYearList.value.map(y => y.id) : [];
isIndeterminateYears.value = false;
}; };
const handleCheckedAreasChange = event => {
const handleCheckedYearsChange = (value) => { if (event.length && event[event.length-1] !== "") {
const checkedCount = value.length; checkedAreaList.value = event.filter(item => item !== "");
checkAllYears.value = checkedCount === surveyYearList.value.length; } else {
isIndeterminateYears.value = checkedCount > 0 && checkedCount < surveyYearList.value.length; checkedAreaList.value = [""];
}
currentPage.value = 1;
handleFetchSurveyList();
}; };
const surveyAreaList = ref([]); // 发布时间过滤
const checkedAreaList = ref([]); const surveyYearList = ref([]);
const checkedYearList = ref(['']);
const checkAllAreas = ref(false); const handleGetSearchAllYear = async () => {
const isIndeterminateAreas = ref(false); try {
const res = await getSearchAllYear({ sortCode: "301" });
const handleCheckAllAreasChange = (val) => { if (res.code === 200) {
checkedAreaList.value = val ? surveyAreaList.value.map(a => a.id) : []; let allYear = res.data.sort((a, b) => (b-a));
isIndeterminateAreas.value = false; let beforeYear = allYear.slice(6).join(',');
surveyYearList.value = allYear.slice(0, 6).map(item => ({ name: item + "年", id: item }));
if (beforeYear) surveyYearList.value.push({ name: "更早", id: beforeYear });
}
} catch (error) {}
surveyYearList.value.unshift({ name: "全部时间", id: "" });
}; };
const handleCheckedYearsChange = event => {
const handleCheckedAreasChange = (value) => { if (event.length && event[event.length-1] !== "") {
const checkedCount = value.length; checkedYearList.value = event.filter(item => item !== "");
checkAllAreas.value = checkedCount === surveyAreaList.value.length; } else {
isIndeterminateAreas.value = checkedCount > 0 && checkedCount < surveyAreaList.value.length; checkedYearList.value = [""];
}
currentPage.value = 1;
handleFetchSurveyList();
}; };
const totalDiscussNum = ref(0); const totalDiscussNum = ref(0);
...@@ -145,8 +137,8 @@ const handleFetchSurveyList = async () => { ...@@ -145,8 +137,8 @@ const handleFetchSurveyList = async () => {
currentPage: currentPage.value - 1, currentPage: currentPage.value - 1,
pageSize: pageSize.value, pageSize: pageSize.value,
sortCode: "301", sortCode: "301",
publishYear: checkAllYears.value ? "" : checkedSurveyYears.value.toString(), publishYear: checkedYearList.value.join(',') || null,
Area: checkAllAreas.value ? "" : checkedAreaList.value.toString(), Area: checkedAreaList.value.join(',') || null,
// keywords: searchText.value, // keywords: searchText.value,
sortField: "date", sortField: "date",
sortOrder: isSort.value ? "asc" : "desc" sortOrder: isSort.value ? "asc" : "desc"
...@@ -173,60 +165,11 @@ const handleSearch = () => { ...@@ -173,60 +165,11 @@ const handleSearch = () => {
handleFetchSurveyList(); handleFetchSurveyList();
}; };
watch( watch([isSort], () => {
[checkedSurveyYears, checkedAreaList, isSort], if (isInitializing.value) return;
() => { currentPage.value = 1;
if (isInitializing.value) return; handleFetchSurveyList();
currentPage.value = 1; });
handleFetchSurveyList();
},
{
deep: true
}
);
const handleGetSearchAllArea = async () => {
try {
const res = await getSearchAllArea({
sortCode: '301'
});
if(res.code === 200 && res.data) {
surveyAreaList.value = res.data.map(item => {
return {
name: item.AREANAME,
id: item.AREACODE
};
});
// 默认选中全部
checkAllAreas.value = true;
handleCheckAllAreasChange(true);
}
} catch (error) {
}
}
const handleGetSearchAllYear = async () => {
try {
const res = await getSearchAllYear({
sortCode: '301'
});
if(res.code === 200 && res.data) {
// 排序并格式化
const sortedYears = res.data.sort((a, b) => b - a);
surveyYearList.value = sortedYears.map(item => {
return {
name: item + '年',
id: item
};
});
// 默认选中全部
checkAllYears.value = true;
handleCheckAllYearsChange(true);
}
} catch (error) {
}
}
onMounted(async () => { onMounted(async () => {
await Promise.all([handleGetSearchAllArea(), handleGetSearchAllYear()]); await Promise.all([handleGetSearchAllArea(), handleGetSearchAllYear()]);
...@@ -309,58 +252,50 @@ onMounted(async () => { ...@@ -309,58 +252,50 @@ onMounted(async () => {
.left { .left {
width: 360px; width: 360px;
min-height: 560px; min-height: 300px;
height: fit-content; height: fit-content;
padding-bottom: 20px; padding-bottom: 20px;
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2); box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: #fff; background: #fff;
.left-box { .check-box {
margin-top: 17px; margin-top: 18px;
.check-head {
.left-header { position: relative;
display: flex; margin-bottom: 12px;
align-items: center; &::before {
content: "";
.icon { position: absolute;
top: 0px;
left: 0px;
width: 8px; width: 8px;
height: 16px; height: 100%;
background: var(--color-main-active); background: var(--color-primary-100);
border-radius: 0 2px 2px 0; border-radius: 0 2px 2px 0;
} }
.head-name {
.title { margin-left: 25px;
margin-left: 17px; color: var(--color-primary-100);
color: var(--color-main-active);
font-size: 16px; font-size: 16px;
font-weight: 700; line-height: 16px;
font-family: Source Han Sans CN;
font-weight: bold;
} }
} }
} .check-list {
padding: 0 10px 0 25px;
.checkbox-group { display: grid;
padding: 10px 0 0 25px; grid-template-columns: repeat(2, 1fr);
.filter-checkbox { grid-gap: 0 12px;
width: 130px; .check-item {
margin-bottom: 8px; width: 100%;
height: 32px; height: 32px;
:deep(.el-checkbox__label) { :deep(.el-checkbox__label) {
font-size: 16px; font-family: Source Han Sans CN;
color: #5f656c; font-size: 16px;
} color: var(--text-primary-65-color);
} }
.expand-btn {
color: var(--color-main-active);
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
margin-top: 4px;
.el-icon {
margin-left: 4px;
} }
} }
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论