提交 ab0482ce authored 作者: yanpeng's avatar yanpeng

Merge branch 'master' into yp-dev

---
alwaysApply: true
---
# Overview
Insert overview text here. The agent will only see this should they choose to apply the rule.
```markdown
---
alwaysApply: true
---
# 前端开发(Cursor 专用执行要点)
## 1. HTML / 模板
- 属性命名:统一用小写-中线分割。动态 class 也用这种格式,使用单引号:`:class="{ 'xxx-xxx': isXxx }"`
- v-for 必须有唯一 key,`v-for + :key`(如`:key="item.id"`),禁止仅凭索引。示例:`<li v-for="item in list" :key="item.id"></li>`
- 禁止 v-if 与 v-for 同时在同一节点;如需筛选请用计算属性/方法过滤后再 v-for
- 无内容组件或标签必须自闭合,如 `<MyComp />` `<img />` `<input />`
- HTML 属性用双引号,动态属性外已双引号则用单引号
- 模板表达式保持极简,只写取值或很简单的显示;任何有数据处理(如数组/字符串操作、三元、函数执行等)必须抽到方法或计算属性完成
## 2. CSS
- 样式模块优先通用组件,确需定制严格按设计来写
- 全部样式变量统一来源于全局(如 `:root { --color-main-primary: #055fC2; ... }`)
- `<style scoped>` 默认加 scoped
- 修改子组件样式用 `:deep()`(Vue3 推荐写法),如:`.xxx :deep(.el-xxx) { ... }`
- 严禁页面大量内联 style,样式尽量写到 class 或 :style 绑定表达式
## 3. 文件系统
- 文件夹采用小驼峰,无数字/无关字符
- 每个业务模块 API 独立放模块对应文件夹
- 静态资源分全局 assets 与模块 assets:全局图片 assets/images,icon 建议 SVG,模块区分子文件夹
## 4. 组件
- 组件名大驼峰(PascalCase)。组件类型目录小驼峰,具体组件目录(如 AreaTag)大驼峰
- 单文件组件名 index.vue,多组件文件按具体功能如 LeftBtn.vue
- 优先用通用组件,业务组件只能放业务模块,不得放全局
## 5. JS/TS
- 命名统一规范
- 变量:小驼峰 userName、isVisible
- 常量:大写+下划线,如 MAX_COUNT
- 枚举:枚举名大驼峰,枚举值全大写+下划线 enum Status { SUCCESS = 'SUCCESS' }
- 普通函数:小驼峰+动词前缀 getUser/formatTime
- 事件函数:小驼峰+handle/on handleSubmit/onClose
- 布尔型函数:is/has/should + 大驼峰,如 isValid()
- 异步强制 async/await,catch 错误。禁止 Promise.then 链式嵌套(如遇回调 hell 必须拆分/抽象重写)
- 一律用 const/let 替换 var,优先 const
- 注释:
- 单行注释:后加空格,如 `// 注释内容`
- 方法/复杂模块加 /** JSDoc 注释 */
## 6. 其它
- 所有复杂渲染逻辑逻辑一律抽到计算属性或方法,模板结构保持简洁
......@@ -7,6 +7,10 @@ yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
*.rar
*.zip
*.7z
# Dependencies
node_modules
.pnpm
......@@ -14,6 +18,7 @@ node_modules
# Build outputs
dist
dist.rar
dist-ssr
*.local
......
......@@ -26,6 +26,7 @@
"lodash": "^4.17.21",
"markdown-it": "^14.1.0",
"pdfjs-dist": "^5.4.449",
"pinia": "^3.0.4",
"vue": "^3.4.0",
"vue-router": "^4.2.5"
},
......@@ -1790,6 +1791,30 @@
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/@vue/devtools-kit": {
"version": "7.7.9",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz",
"integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==",
"license": "MIT",
"dependencies": {
"@vue/devtools-shared": "^7.7.9",
"birpc": "^2.3.0",
"hookable": "^5.5.3",
"mitt": "^3.0.1",
"perfect-debounce": "^1.0.0",
"speakingurl": "^14.0.1",
"superjson": "^2.2.2"
}
},
"node_modules/@vue/devtools-shared": {
"version": "7.7.9",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz",
"integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==",
"license": "MIT",
"dependencies": {
"rfdc": "^1.4.1"
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.21.tgz",
......@@ -2220,6 +2245,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/birpc": {
"version": "2.9.0",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/birpc/-/birpc-2.9.0.tgz",
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz",
......@@ -2457,6 +2491,21 @@
"dev": true,
"license": "MIT"
},
"node_modules/copy-anything": {
"version": "4.0.5",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/copy-anything/-/copy-anything-4.0.5.tgz",
"integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==",
"license": "MIT",
"dependencies": {
"is-what": "^5.2.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/copy-descriptor": {
"version": "0.1.1",
"resolved": "https://registry.npmmirror.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
......@@ -4049,6 +4098,12 @@
"node": ">=12.0.0"
}
},
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/hookable/-/hookable-5.5.3.tgz",
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"license": "MIT"
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
......@@ -4233,6 +4288,18 @@
"node": ">=0.10.0"
}
},
"node_modules/is-what": {
"version": "5.5.0",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/is-what/-/is-what-5.5.0.tgz",
"integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/is-windows": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/is-windows/-/is-windows-1.0.2.tgz",
......@@ -5112,6 +5179,12 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/mixin-deep": {
"version": "1.3.2",
"resolved": "https://registry.npmmirror.com/mixin-deep/-/mixin-deep-1.3.2.tgz",
......@@ -5414,6 +5487,12 @@
"@napi-rs/canvas": "^0.1.81"
}
},
"node_modules/perfect-debounce": {
"version": "1.0.0",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
......@@ -5442,6 +5521,36 @@
"node": ">=6"
}
},
"node_modules/pinia": {
"version": "3.0.4",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/pinia/-/pinia-3.0.4.tgz",
"integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^7.7.7"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"typescript": ">=4.5.0",
"vue": "^3.5.11"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/@vue/devtools-api": {
"version": "7.7.9",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/devtools-api/-/devtools-api-7.7.9.tgz",
"integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==",
"license": "MIT",
"dependencies": {
"@vue/devtools-kit": "^7.7.9"
}
},
"node_modules/pkg-types": {
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz",
......@@ -5689,6 +5798,12 @@
"node": ">=0.10.0"
}
},
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"license": "MIT"
},
"node_modules/robust-predicates": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz",
......@@ -6045,6 +6160,15 @@
"deprecated": "See https://github.com/lydell/source-map-url#deprecated",
"license": "MIT"
},
"node_modules/speakingurl": {
"version": "14.0.1",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/speakingurl/-/speakingurl-14.0.1.tgz",
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/split-string/-/split-string-3.1.0.tgz",
......@@ -6154,6 +6278,18 @@
"integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
"license": "MIT"
},
"node_modules/superjson": {
"version": "2.2.6",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/superjson/-/superjson-2.2.6.tgz",
"integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==",
"license": "MIT",
"dependencies": {
"copy-anything": "^4"
},
"engines": {
"node": ">=16"
}
},
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz",
......
......@@ -35,6 +35,7 @@
"lodash": "^4.17.21",
"markdown-it": "^14.1.0",
"pdfjs-dist": "^5.4.449",
"pinia": "^3.0.4",
"vue": "^3.4.0",
"vue-router": "^4.2.5"
},
......
差异被折叠。
......@@ -77,9 +77,34 @@ export function getUSGovernmentSanctionHistory(params) {
/**
* @header token
*/
export function getDepartmentList() {
export function getDepartmentList(params) {
return request({
method: 'GET',
url: `/api/organization/summaryDepartList`
url: `/api/organization/summaryDepartList`,
params
})
}
// 全政府-获取制裁手段数据
/**
* @params {orgId}
*/
export function getSanTypeList(params) {
return request({
method: 'GET',
url: `/api/rivalryIndex/sanTypeList`,
params
})
}
// 全政府-获取未来三个月内的打压遏制时间线
/**
* @params {field}
*/
export function getThreeMonthSanctionProcess(params) {
return request({
method: 'GET',
url: `/api/rivalryIndex/nextThreeMonthSanctionProcess`,
params
})
}
\ No newline at end of file
import request from "@/api/request.js";
// 全联盟-获取联盟列表
/**
* @header token
* @param {Object} params
* @param {String} params.date - 日期
*/
export function getAllUnionList(params) {
return request({
method: 'GET',
url: `/api/union/union/unionList/${params.date}`
})
}
// 全联盟-获取排华数量
/**
* @header token
*/
export function getUnionCount() {
return request({
method: 'GET',
url: `/api/union/union/unionCount`
})
}
// 全联盟-获取排华联盟动态
/**
* @header token
*/
export function getDynamic() {
return request({
method: 'GET',
url: `/api/union/union/dynamic`
})
}
// 全联盟-获取排华联盟预警
/**
* @param {Object} params
* @param {Integer} params.id - 上一接口查询的EccnCategoryID
* @header token
*/
export function getPrediction() {
return request({
method: 'GET',
url: `/api/union/union/prediction`
})
}
// 全联盟-获取排华联盟领域分布
/**
* @param {Object} params
* @param {Integer} params.id - 上一接口查询的EccnCategoryID
* @header token
*/
export function getIndustry() {
return request({
method: 'GET',
url: `/api/union/union/industry`
})
}
// 全联盟-获取排华联盟国家紧密度
/**
* @param {Object} params
* @param {Integer} params.id - 上一接口查询的EccnCategoryID
* @header token
*/
export function getCountryRelation() {
return request({
method: 'GET',
url: `/api/union/union/countryRelation`
})
import request from "@/api/request.js";
// 全联盟-获取联盟列表
/**
* @header token
* @param {Object} params
* @param {String} params.date - 日期
*/
export function getAllUnionList(params) {
return request({
method: 'GET',
url: `/api/union/union/unionList/${params.date}`
})
}
// 全联盟-获取排华数量
/**
* @header token
*/
export function getUnionCount() {
return request({
method: 'GET',
url: `/api/union/union/unionCount`
})
}
// 全联盟-获取排华联盟动态
/**
* @header token
*/
export function getDynamic() {
return request({
method: 'GET',
url: `/api/union/union/dynamic`
})
}
// 全联盟-获取排华联盟预警
/**
* @param {Object} params
* @param {Integer} params.id - 上一接口查询的EccnCategoryID
* @header token
*/
export function getPrediction() {
return request({
method: 'GET',
url: `/api/union/union/prediction`
})
}
// 全联盟-获取排华联盟领域分布
/**
* @param {Object} params
* @param {Integer} params.id - 上一接口查询的EccnCategoryID
* @header token
*/
export function getIndustry() {
return request({
method: 'GET',
url: `/api/union/union/industry`
})
}
// 全联盟-获取排华联盟国家紧密度
/**
* @param {Object} params
* @param {Integer} params.id - 上一接口查询的EccnCategoryID
* @header token
*/
export function getCountryRelation() {
return request({
method: 'GET',
url: `/api/union/union/countryRelation`
})
}
\ No newline at end of file
......@@ -14,20 +14,20 @@ export function getBillIndustry(params) {
// 涉华法案统计
export function getBillCount(params) {
return request({
method: 'GET',
url: `/api/BillOverview/billCount`,
params
})
return request({
method: 'GET',
url: `/api/BillOverview/billCount`,
params
})
}
// 获取关键条款
export function getBillOverviewKeyTK() {
return request({
method: 'GET',
url: `/api/BillOverview/keyTk`,
return request({
method: 'GET',
url: `/api/BillOverview/keyTk`,
})
})
}
// 获取热门法案列表
......@@ -104,22 +104,32 @@ export function getBillPostOrg(params) {
})
}
// 获取关键议员提案
// 获取涉华法案进展分布
/**
* @param {year}
* @param {year}
*/
export function getMemberProposal(params) {
export function getBillProcess(params) {
return request({
method: 'GET',
url: `/api/BillOverview/memberProposal/${params.year}`,
url: `/bill/BillOverview/billsProcess/${params.year}`,
})
}
// 获取资源库
// 获取资源库法案
export function getBills(params, signal) {
return request({
method: 'GET',
url: `/api/BillOverview/bills`,
url: `/bill/BillOverview/bills`,
params,
signal
})
}
// 获取资源库国会议员
export function getBillsPerson(params, signal) {
return request({
method: 'GET',
url: `/bill/BillOverview/billsPerson`,
params,
signal
})
......
......@@ -64,6 +64,13 @@ export function getDecreeSummary(params) {
/**
* @param {id}
*/
// export function getDecreeReport(params) {
// return request({
// method: 'GET',
// url: `/api/administrativeOrderInfo/contentUrl/${params.id}`,
// })
// }
export function getDecreeReport(params) {
return request({
method: 'GET',
......
import request from "@/api/request.js";
// 规则限制-首页统计接口
export function getStatCount() {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/statCount`
})
}
// 规则限制-查询最新动态接口
export function getLatestUpdates() {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getLatestUpdates`
})
}
// 规则限制-风险信号
export function getRiskSignal(params) {
return request({
method: 'GET',
url: `/api/commonFeature/riskSignal/${params}`
})
}
// 规则限制-查询新闻资讯
export function getNews(params) {
return request({
method: 'GET',
url: `/api/commonFeature/news/${params}`
})
}
// 规则限制-查询社交媒体
export function getRemarks(params) {
return request({
method: 'GET',
url: `/api/commonFeature/remarks/${params}`
})
}
// 规则限制-限制领域分布情况
/**
* @param {Object} params
* @param {string} params.year
* @header token
*/
export function getAreaDistribution(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getAreaDistribution`,
params
})
}
// 规则限制-受限实体数量变化趋势
/**
* @param {Object} params
* @param {Integer} params.year
* @param {String} params.type
* @header token
*/
export function getEntityChangeTrend(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getEntityChangeTrend`,
params
})
}
// 规则限制-规则限制政令列表查询接口
/**
* @param {Object} params
* @param {Integer} params.currentPage
* @param {Integer} params.pageSize
* @param {List<String>} params.years
* @param {List<String>} params.areas
* @param {String} params.keywords
* @param {String} params.sortField
* @param {String} params.sortOrder
* @header token
*/
export function getRuleLimitList(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getRuleLimitList`,
params
})
}
// 规则限制-排华科技联盟列表接口
/**
* @param {Object} params
* @param {Integer} params.currentPage
* @param {Integer} params.pageSize
* @param {List<String>} params.years
* @param {List<String>} params.countries
* @param {List<String>} params.areas
* @param {String} params.keywords
* @param {String} params.sortField
* @param {String} params.sortOrder
* @header token
*/
export function getACTAList(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getACTAList`,
params
})
}
export function getAcTAAllcountry() {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getACTAAllCountry/`
})
}
// 规则限制-规则限制基本详情
export function getSanctionOverview(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getSanctionOverview/${params}`
})
}
// 规则限制-背景分析
export function getBackGround(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getBackGround/${params}`
})
}
// 规则限制-限制条款
export function getLimitClause(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getLimitClause/${params}`
})
}
// 规则限制-相关举措
export function getRelevantMeasures(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getRelevantMeasures/${params}`
})
}
// // 实体清单-制裁概况-获取发布机构机构动态
// /**
// * @param {Object} data
// * @param {string} data.orgId
// * @header token
// */
// export function getPublishOrgInfo(data) {
// return request({
// method: 'POST',
// url: `/api/organization/relate/news`,
// data,
// })
import request from "@/api/request.js";
// 规则限制-首页统计接口
export function getStatCount() {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/statCount`
})
}
// 规则限制-查询最新动态接口
export function getLatestUpdates() {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getLatestUpdates`
})
}
// 规则限制-风险信号
export function getRiskSignal(params) {
return request({
method: 'GET',
url: `/api/commonFeature/riskSignal/${params}`
})
}
// 规则限制-查询新闻资讯
export function getNews(params) {
return request({
method: 'GET',
url: `/api/commonFeature/news/${params}`
})
}
// 规则限制-查询社交媒体
export function getRemarks(params) {
return request({
method: 'GET',
url: `/api/commonFeature/remarks/${params}`
})
}
// 规则限制-限制领域分布情况
/**
* @param {Object} params
* @param {string} params.year
* @header token
*/
export function getAreaDistribution(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getAreaDistribution`,
params
})
}
// 规则限制-受限实体数量变化趋势
/**
* @param {Object} params
* @param {Integer} params.year
* @param {String} params.type
* @header token
*/
export function getEntityChangeTrend(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getEntityChangeTrend`,
params
})
}
// 规则限制-规则限制政令列表查询接口
/**
* @param {Object} params
* @param {Integer} params.currentPage
* @param {Integer} params.pageSize
* @param {List<String>} params.years
* @param {List<String>} params.areas
* @param {String} params.keywords
* @param {String} params.sortField
* @param {String} params.sortOrder
* @header token
*/
export function getRuleLimitList(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getRuleLimitList`,
params
})
}
// 规则限制-排华科技联盟列表接口
/**
* @param {Object} params
* @param {Integer} params.currentPage
* @param {Integer} params.pageSize
* @param {List<String>} params.years
* @param {List<String>} params.countries
* @param {List<String>} params.areas
* @param {String} params.keywords
* @param {String} params.sortField
* @param {String} params.sortOrder
* @header token
*/
export function getACTAList(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getACTAList`,
params
})
}
export function getAcTAAllcountry() {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getACTAAllCountry/`
})
}
// 规则限制-规则限制基本详情
export function getSanctionOverview(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getSanctionOverview/${params}`
})
}
// 规则限制-背景分析
export function getBackGround(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getBackGround/${params}`
})
}
// 规则限制-限制条款
export function getLimitClause(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getLimitClause/${params}`
})
}
// 规则限制-相关举措
export function getRelevantMeasures(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getRelevantMeasures/${params}`
})
}
// // 实体清单-制裁概况-获取发布机构机构动态
// /**
// * @param {Object} data
// * @param {string} data.orgId
// * @header token
// */
// export function getPublishOrgInfo(data) {
// return request({
// method: 'POST',
// url: `/api/organization/relate/news`,
// data,
// })
// }
\ No newline at end of file
......@@ -20,7 +20,10 @@ export function getAllDomainCount(params) {
/**
* @header token
* @param {Object} params
* @param {String} params.byYOrM - 按月统计或按年统计
* @param {String} params.startDate - 开始日期
* @param {String} params.endDate - 结束日期
* @param {String} params.org - 机构
* @param {String} params.sanMeasures - 制裁手段
*/
export function getDomainContainmentTrend(params) {
return request({
......
......@@ -8,6 +8,6 @@
}
/* 全局一次性应用(可选) */
body {
/* body {
font-family: "YouSheBiaoTiHei", sans-serif;
}
} */
<template>
<div
class="card-title-custom"
:style="{ '--left-offset': leftOffset }"
>
{{ props.title }}
</div>
</template>
<script setup>
const props = defineProps({
title: {
type: String,
required: true
},
leftOffset: {
type: String,
default: '-22px'
}
})
</script>
<style scoped>
.card-title-custom {
font-size: 16px;
font-weight: 600;
color: #333;
position: relative;
}
.card-title-custom::before {
content: '';
position: absolute;
left: var(--left-offset, -22px);
top: 50%;
transform: translateY(-50%);
width: 8px;
height: 16px;
background-color: #1459BB;
}
</style>
\ No newline at end of file
<template>
<div class="hello-world">
<el-card>
<template #header>
<div class="card-header">
<el-icon><Star /></el-icon>
<span>Hello World 组件</span>
</div>
</template>
<div class="content">
<h3>{{ title }}</h3>
<p>{{ description }}</p>
<el-divider />
<div class="counter-section">
<h4>计数器示例</h4>
<div class="counter">
<el-button type="primary" :icon="Minus" @click="decrement" :disabled="count <= 0"></el-button>
<el-input-number v-model="count" :min="0" :max="100" style="margin: 0 10px;"></el-input-number>
<el-button type="primary" :icon="Plus" @click="increment" :disabled="count >= 100"></el-button>
</div>
<el-progress :percentage="count" :color="progressColor" style="margin-top: 20px;"></el-progress>
</div>
<el-divider />
<div class="theme-section">
<h4>主题切换</h4>
<el-radio-group v-model="theme" @change="handleThemeChange">
<el-radio-button label="light">浅色主题</el-radio-button>
<el-radio-button label="dark">深色主题</el-radio-button>
</el-radio-group>
</div>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { Star, Plus, Minus } from '@element-plus/icons-vue'
// Props
const props = defineProps({
title: {
type: String,
default: '欢迎使用 Vue 3 + Element Plus'
},
description: {
type: String,
default: '这是一个可复用的组件示例,展示了 Vue 3 Composition API 的强大功能。'
}
})
// 响应式数据
const count = ref(0)
const theme = ref('light')
// 计算属性
const progressColor = computed(() => {
if (count.value < 30) return '#f56c6c'
if (count.value < 70) return '#e6a23c'
return '#67c23a'
})
// 方法
const increment = () => {
if (count.value < 100) {
count.value++
if (count.value === 100) {
ElMessage.success('恭喜!达到最大值!')
}
}
}
const decrement = () => {
if (count.value > 0) {
count.value--
if (count.value === 0) {
ElMessage.info('已归零')
}
}
}
const handleThemeChange = (value) => {
ElMessage.success(`已切换到${value === 'light' ? '浅色' : '深色'}主题`)
}
// 暴露给父组件的方法
defineExpose({
reset: () => {
count.value = 0
theme.value = 'light'
ElMessage.success('组件已重置')
}
})
</script>
<style scoped>
.hello-world {
margin: 20px 0;
}
.card-header {
display: flex;
align-items: center;
gap: 8px;
font-weight: bold;
}
.content h3 {
color: #409eff;
margin-bottom: 12px;
}
.content p {
color: #606266;
line-height: 1.6;
}
.counter-section,
.theme-section {
text-align: center;
}
.counter-section h4,
.theme-section h4 {
color: #303133;
margin-bottom: 16px;
}
.counter {
display: flex;
align-items: center;
justify-content: center;
}
</style>
\ No newline at end of file
<template>
<div class="news-box">
<div class="box3-header">
<div class="box3-header-left">
<div class="box3-header-icon">
<img src="@/assets/images/box3-header-icon.png" alt="" />
</div>
<div class="box3-header-title">{{ "新闻资讯" }}</div>
<div class="more" @click="handleToMoreNews">{{ "更多 +" }}</div>
</div>
</div>
<div class="box3-main">
<div class="box3-item" v-for="(news, index) in list" :key="index" @click="handleClickToNewsDetail(news)">
<div class="left">
<img
:src="getProxyUrl(news.newsImage) || defaultImg"
alt=""
referrerpolicy="no-referrer"
@error="e => (e.target.src = errImg||News1)"
/>
</div>
<div class="right">
<div class="right-top">
<div class="title">{{ news.newsTitle||news.title }}</div>
<div class="time">
{{ news.newsDate ? news.newsDate.slice(5) : "" }} {{ news.newsOrg ? "-" + news.newsOrg : "" }}
</div>
</div>
<div class="right-footer">{{ news.newsContent||news.description }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import router from "@/router/index";
import News1 from "@/assets/images/news1.png"; // 错误图片
import defaultNew from "@/assets/images/default-icon-news.png"; // 默认图片
let { list,errImg,defaultImg } = defineProps({
list: {
type: Array,
default: () => []
},
defaultImg: {
type: String,
default: defaultNew
},
errImg: {
type: String,
default: ''
}
});
// 处理图片代理
const getProxyUrl = url => {
if (!url) return "";
const urlStr = String(url);
// 排除非 http 开头(相对路径)、已经是代理链接、或者是本地链接
if (
!urlStr.startsWith("http") ||
urlStr.includes("images.weserv.nl") ||
urlStr.includes("localhost") ||
urlStr.includes("127.0.0.1")
) {
return url;
}
// 移除协议头 http:// 或 https://
const cleanUrl = urlStr.replace(/^https?:\/\//i, "");
return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`;
};
const handleClickToNewsDetail = news => {
const route = router.resolve({
path: "/newsAnalysis",
query: {
newsId: news.newsId
}
});
window.open(route.href, "_blank");
};
// 查看更多新闻资讯
const handleToMoreNews = () => {
const route = router.resolve("/newsBrief");
window.open(route.href, "_blank");
};
</script>
<style lang="scss" scoped>
.news-box {
width: 792px;
height: 450px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(25, 69, 130, 0.2);
background: rgba(255, 255, 255, 1);
.box3-header {
height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin: 0 auto;
display: flex;
justify-content: space-between;
position: relative;
.box3-header-left {
display: flex;
.box3-header-icon {
margin-left: 21px;
margin-top: 16px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.box3-header-title {
margin-top: 11px;
margin-left: 20px;
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
}
.more {
width: 49px;
height: 24px;
position: absolute;
top: 14px;
right: 27px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
}
}
.box3-main {
height: 402px;
overflow-y: auto;
overflow-x: hidden;
padding: 6px 0;
.box3-item {
display: flex;
height: 77px;
width: 749px;
margin-left: 21px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
cursor: pointer;
&:hover {
background: var(--color-bg-hover);
}
.left {
width: 72px;
height: 48px;
margin-top: 15px;
img {
width: 100%;
height: 100%;
border-radius: 4px;
}
}
.right {
width: 657px;
margin-left: 20px;
.right-top {
width: 657px;
display: flex;
justify-content: space-between;
&:hover {
text-decoration: underline;
color: var(--color-main-active);
.title {
color: var(--color-main-active);
}
}
.title {
margin-top: 13px;
width: 500px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.time {
width: 157px;
text-align: right;
height: 22px;
margin-top: 19px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.right-footer {
width: 657px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
</style>
<template>
<div class="policy-list">
<div
v-for="(item, index) in policyList"
:key="index"
class="policy-item"
>
<div class="item-left">
<div class="report-cover">
<img :src="$withFallbackImage(item.imageUrl, index)" alt="Report Cover" />
</div>
</div>
<div class="item-right">
<h3 class="item-title">
{{ item.content }}
</h3>
<div class="item-meta">
<span class="meta-date">{{ formatDate(item.times) }}</span>
<span class="meta-divider">·</span>
<span class="meta-source">
{{ item.name }}
<el-icon class="link-icon"><TopRight /></el-icon>
</span>
</div>
<div class="item-tags" v-if="item.tags && item.tags.length">
<span v-for="(tag, tIndex) in item.tags" :key="tIndex" class="tag-pill">
{{ tag }}
</span>
</div>
<div class="item-actions" v-if="item.statusRaw">
<div
v-for="(statusItem, sIndex) in parseStatus(item.statusRaw)"
:key="sIndex"
class="status-link"
>
<span class="status-type">{{ statusItem.type }}</span>
<span class="status-year">{{ statusItem.year }}</span>
<span class="status-name">{{ statusItem.name }}</span>
<el-icon class="arrow-icon"><Right /></el-icon>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { TopRight, Right } from '@element-plus/icons-vue'
interface PolicyItem {
content: string;
statusRaw: string; // 原始的长字符串
name: string;
times: string;
tags?: string[];
coverUrl?: string;
}
const props = defineProps<{
policyList: PolicyItem[]
}>()
// 格式化日期:2025-06-26 -> 2025年6月26日
const formatDate = (dateStr: string) => {
if (!dateStr) return '';
const date = new Date(dateStr);
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}日`;
}
// 解析状态字符串
// 输入: "法案 2024 《芯片科学法案》; 政令 2025 《推动美国...》"
// 输出: 数组对象
const parseStatus = (raw: string) => {
if (!raw) return [];
// 按分号分割多个条目
const items = raw.split(/[;;]/).map(s => s.trim()).filter(s => s);
return items.map(itemStr => {
// 简单正则匹配: "类型 年份 《名称》"
// 注意:这里假设数据格式比较规范,实际需根据后端数据调整
// 尝试移除书名号进行提取
const cleanStr = itemStr.replace(/[《》]/g, '');
const parts = cleanStr.split(' ');
return {
type: parts[0] || '政策',
year: parts[1] || '',
name: parts.slice(2).join(' ') || cleanStr // 剩余部分作为名称
}
});
}
</script>
<style scoped>
.policy-list {
display: flex;
flex-direction: column;
}
.policy-item {
display: flex;
padding: 20px 0;
border-bottom: 1px solid #ebeef5;
gap: 16px;
transition: background-color 0.2s;
}
.policy-item:last-child {
border-bottom: none;
}
/* 左侧封面 */
.item-left {
flex-shrink: 0;
}
.report-cover {
width: 60px;
height: 80px;
background-color: #f2f3f5;
border: 1px solid #e4e7ed;
border-radius: 2px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.report-cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 右侧内容 */
.item-right {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
/* 1. 标题 */
.item-title {
margin: 0 0 6px 0;
font-size: 16px;
font-weight: 700;
color: #1a1a1a;
line-height: 1.4;
cursor: pointer;
}
.item-title:hover {
color: #409EFF;
}
/* 2. 元数据 */
.item-meta {
display: flex;
align-items: center;
font-size: 13px;
color: #606266;
margin-bottom: 8px;
}
.meta-divider {
margin: 0 8px;
font-weight: bold;
}
.meta-source {
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
}
.meta-source:hover {
color: #409EFF;
}
.link-icon {
font-size: 12px;
}
/* 3. 标签 */
.item-tags {
display: flex;
gap: 8px;
margin-bottom: 10px;
}
.tag-pill {
background-color: #f2f3f5;
color: #5e6d82;
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
}
/* 4. 底部状态链接 */
.item-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.status-link {
display: inline-flex;
align-items: center;
background-color: #ecf5ff; /* 浅蓝色背景 */
color: #409EFF; /* 蓝色文字 */
padding: 4px 12px;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.status-link:hover {
background-color: #d9ecff;
}
.status-type {
font-weight: bold;
margin-right: 4px;
}
.status-year {
margin-right: 4px;
}
.status-name {
margin-right: 4px;
}
.arrow-icon {
margin-left: 4px;
font-size: 12px;
}
</style>
\ No newline at end of file
<template>
<div class="policy-tracker-container">
<PolicyOverview />
<!-- 顶部搜索栏 -->
<div class="top-search-bar" v-if="props.showSearch">
<div class="search-left">
<el-input
v-model="searchQuery"
placeholder="搜索政策建议"
class="search-input"
size="default"
>
<template #suffix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</div>
<div class="search-right">
<el-select v-model="dateRange" class="select-item" size="default">
<el-option label="近一年发布" value="last_year" />
<el-option label="近三年发布" value="last_three_years" />
</el-select>
<el-select v-model="sortOrder" class="select-item" size="default">
<el-option label="发布时间" value="time" />
<el-option label="热度" value="popularity" />
</el-select>
</div>
</div>
<el-row :gutter="24">
<el-col :span="6">
<aside class="filter-sidebar">
<div class="filter-group">
<CardTitle title="实施状态" style="margin-bottom: 10px"/>
<div class="checkbox-group">
<el-checkbox-group v-model="activeStatus">
<el-checkbox
v-for="status in statusFilters"
:key="status.id"
:label="status.id"
class="filter-checkbox"
>
<span
class="status-dot"
:style="`background-color: ${status.color}; border-color: ${status.borderColor || status.color || '#dcdfe6'}`"
></span>
{{ status.label }} ({{ status.count }})
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="filter-group">
<CardTitle title="科技领域" style="margin-bottom: 10px"/>
<div class="checkbox-group">
<el-checkbox-group v-model="activeTechField">
<el-checkbox
v-for="field in techFieldFilters"
:key="field.id"
:label="field.id"
class="filter-checkbox tech-checkbox"
>
{{ field.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</aside>
</el-col>
<el-col :span="18">
<div class="main-content">
<PolicyList :policyList="policies" />
</div>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { Search } from '@element-plus/icons-vue';
import PolicyList from './PolicyList.vue';
import CardTitle from './CardTitle.vue';
import { getOverviewPolicy } from '@/api'
import PolicyOverview from '@/views/thinkTank/components/PolicyOverview.vue'
import { mockPolicyList } from '@/views/thinkTank/mockData';
const props = defineProps({
showSearch: {
type: Boolean,
default: true
}
})
// --- Sidebar Filters State & Data ---
const activeStatus = ref(['implemented']); // 改为数组支持多选
const activeTechField = ref(['ai']); // 改为数组支持多选
const statusFilters = ref([
{ id: 'implemented', label: '已实施', count: 18, color: '#e66657' },
{ id: 'partial', label: '部分实施', count: 12, color: '#e6a23c' },
{ id: 'unimplemented', label: '未实施', count: 12, color: '#909399' },
{ id: 'unknown', label: '未知状态', count: 4, color: 'transparent', borderColor: '#909399' },
]);
const techFieldFilters = ref([
{ id: 'ai', label: '人工智能' },
{ id: 'semiconductor', label: '半导体/芯片' },
{ id: 'energy', label: '能源与气候' },
{ id: 'international', label: '国际关系' },
{ id: 'economy', label: '经济决策' },
{ id: 'national_security', label: '国防与安全' },
]);
// --- Main Content State ---
const searchQuery = ref('');
const dateRange = ref('last_year');
const sortOrder = ref('time');
const policies = ref([]);
const getPolicies = async () => {
const { data } = await getOverviewPolicy({
researchTypeIds: activeTechField.value,
statusList: activeStatus.value,
})
// policies.value = data
policies.value = mockPolicyList
}
onMounted(() => {
getPolicies()
})
</script>
<style scoped>
.policy-tracker-container {
padding-top: 0;
}
/* 顶部搜索栏样式 */
.top-search-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.search-left {
flex: 1;
max-width: 400px;
}
.search-input {
width: 100%;
}
.search-input :deep(.el-input__wrapper) {
border-radius: 6px;
box-shadow: 0 0 0 1px #dcdfe6;
transition: box-shadow 0.3s ease;
}
.search-input :deep(.el-input__wrapper:hover) {
box-shadow: 0 0 0 1px #409EFF;
}
.search-input :deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px #409EFF;
}
.search-right {
display: flex;
align-items: center;
gap: 12px;
}
.select-item {
width: 140px;
}
.select-item :deep(.el-input__wrapper) {
border-radius: 6px;
box-shadow: 0 0 0 1px #dcdfe6;
transition: box-shadow 0.3s ease;
}
.select-item :deep(.el-input__wrapper:hover) {
box-shadow: 0 0 0 1px #409EFF;
}
.main-content {
background-color: #fff;
padding: 8px;
}
/* --- Sidebar Styles --- */
.filter-sidebar {
background-color: #fff;
padding: 20px;
border-radius: 4px;
}
.filter-group {
margin-bottom: 25px;
}
.filter-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin: 0 0 15px 0;
display: flex;
align-items: center;
}
.implementation-title {
padding-left: 12px;
position: relative;
}
.implementation-title::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background-color: #409EFF;
border-radius: 2px;
}
.icon-square {
display: inline-block;
width: 4px;
height: 16px;
background-color: #409EFF;
margin-right: 8px;
border-radius: 2px;
}
.filter-sidebar ul {
list-style: none;
padding: 0;
margin: 0;
}
/* 复选框组样式 */
.checkbox-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.filter-checkbox {
margin: 0 !important;
padding: 8px 12px;
border-radius: 4px;
transition: all 0.3s ease;
width: 100%;
display: flex;
align-items: center;
}
.filter-checkbox:hover {
background-color: #f0f2f5;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 0;
border: 1px solid transparent;
display: inline-block;
flex-shrink: 0;
}
/* 响应式设计 */
@media (max-width: 768px) {
.top-search-bar {
flex-direction: column;
gap: 16px;
padding: 16px;
}
.search-left {
max-width: none;
width: 100%;
}
.search-right {
width: 100%;
justify-content: center;
gap: 8px;
}
.select-item {
width: 120px;
}
}
@media (max-width: 480px) {
.search-right {
flex-direction: column;
gap: 8px;
}
.select-item {
width: 100%;
max-width: 200px;
}
}
</style>
\ No newline at end of file
差异被折叠。
差异被折叠。
<template>
<div class="search-container" v-show="!isShow">
<div class="search-type-tabs" v-if="enableBillTypeSwitch">
<div class="search-type-tab" :class="{ active: billSearchType === 'federal' }"
@click="handleChangeBillSearchType('federal')">
联邦议会
</div>
<div class="search-type-tab" :class="{ active: billSearchType === 'state' }"
@click="handleChangeBillSearchType('state')">
州议会
</div>
</div>
<div class="search-main" :class="{ 'search-main-with-tabs': enableBillTypeSwitch }">
<input v-model="store.searchBillText" :placeholder="placeholder" @keyup.enter="handleSearch"
class="search-input" />
<div class="search-btn" @click="handleSearch">
<img src="@/assets/icons/search-icon.png" alt />
搜索
</div>
</div>
<div class="search-center" v-if="countInfo.length">
<div class="search-item" v-for="info in countInfo">
<div class="search-item-num">{{ info.count }}</div>
<div class="search-item-name">{{ info.name }}</div>
</div>
</div>
<div class="search-bottom">
<div class="btn" @click="handleToPosi('position1')">
<div class="btn-text">最新动态</div>
<div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt />
</div>
</div>
<div class="btn" @click="handleToPosi('position2')">
<div class="btn-text">咨询要闻</div>
<div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt />
</div>
</div>
<div class="btn" @click="handleToPosi('position3')">
<div class="btn-text">数据总览</div>
<div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt />
</div>
</div>
<div class="btn" @click="handleToPosi('position4')">
<div class="btn-text">资源库</div>
<div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt />
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, nextTick, watchEffect } from "vue";
import { useContainerScroll } from "@/hooks/useScrollShow";
import { useRouter } from "vue-router";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
const store = useWrittingAsstaintStore();
const router = useRouter();
const {
countInfo,
containerRef,
placeholder,
areaName,
enableBillTypeSwitch,
defaultBillSearchType
} = defineProps({
countInfo: {
type: Array,
default: () => []
},
containerRef: {
type: Object,
default: {}
},
placeholder: {
type: String,
default: ""
},
areaName: {
type: String,
default: "法案"
},
// 法案页专用:是否展示“联邦议会/州议会”搜索类型切换
// 其他页面默认 false,不受影响
enableBillTypeSwitch: {
type: Boolean,
default: false
},
// 法案页专用:默认搜索类型
// 可选值:'federal'(联邦议会)| 'state'(州议会)
defaultBillSearchType: {
type: String,
default: "federal"
}
});
// 法案搜索类型状态(仅在 enableBillTypeSwitch=true 时生效)
// 维护说明:
// - federal: 联邦议会
// - state: 州议会
const billSearchType = ref(defaultBillSearchType === "state" ? "state" : "federal");
const handleChangeBillSearchType = type => {
billSearchType.value = type;
};
const handleSearch = () => {
window.sessionStorage.setItem("curTabName", `搜索-${store.searchBillText}`);
if (!areaName) return;
const query = {
searchText: store.searchBillText,
areaName: areaName
};
// 法案页附带搜索类型参数,便于搜索结果页后续按类型处理
if (enableBillTypeSwitch) {
query.billSearchType = billSearchType.value;
}
const curRoute = router.resolve({
path: "/searchResults",
query
});
window.open(curRoute.href, "_blank");
};
let homeMainRef = ref(containerRef);
const { isShow } = useContainerScroll(homeMainRef);
watchEffect(() => {
if (isShow.value) {
homeMainRef.value.classList.add("scroll-main");
homeMainRef.value.classList.add("scrollHomeMain");
} else {
homeMainRef.value.classList.remove("scroll-main");
homeMainRef.value.classList.remove("scrollHomeMain");
}
store.changeIsShowSearchBar(isShow.value);
});
store.setSearchData({ placeholder, areaName, containerRef: homeMainRef });
// 锚点跳转
const handleToPosi = id => {
const element = document.getElementById(id);
if (element && homeMainRef.value) {
// 如果当前还未显示吸顶搜索栏,先强制切换状态以稳定布局
if (!isShow.value) {
isShow.value = true;
}
// 使用 nextTick 确保 DOM 状态更新(高度变化生效)后再计算
nextTick(() => {
const containerRect = homeMainRef.value.getBoundingClientRect();
const elementRect = element.getBoundingClientRect();
// 使用 getBoundingClientRect 计算元素相对于容器顶部的绝对距离,不受嵌套布局影响
const top = elementRect.top - containerRect.top + homeMainRef.value.scrollTop;
homeMainRef.value.scrollTo({
top: top,
behavior: "smooth"
});
});
}
};
</script>
<style lang="scss" scoped>
.search-container {
width: 960px;
height: 168px;
margin: 0 auto 68px auto;
.search-type-tabs {
display: flex;
align-items: flex-end;
height: 41px;
gap: 2px;
.search-type-tab {
width: 176px;
height: 41px;
line-height: 48px;
text-align: center;
border-radius: 10px 10px 0 0;
border: 1px solid rgb(255, 255, 255);
border-bottom: none;
background: rgba(255, 255, 255, 0.65);
color: rgb(95, 101, 108);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 700;
line-height: 41px;
cursor: pointer;
padding: 0 16px;
box-sizing: border-box;
}
.search-type-tab.active {
background: rgba(231, 243, 255, 1);
color: rgb(5, 95, 194);
border-color: rgb(255, 255, 255);
}
}
.search-main-with-tabs {
border-top-left-radius: 0 !important;
}
.search-center {
width: 688px;
height: 48px;
margin: 0 auto;
margin-top: 36px;
display: flex;
justify-content: space-between;
.search-item {
width: 130px;
height: 57px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
.search-item-num {
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 36px;
font-weight: 700;
line-height: 22px;
letter-spacing: 0px;
text-align: center;
color: rgba(5, 95, 194, 1);
}
.search-item-name {
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: center;
color: rgba(95, 101, 108, 1);
}
}
}
.search-main {
display: flex;
padding-right: 3px;
align-items: center;
justify-content: space-between;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
width: 960px;
height: 48px;
background-color: rgba(255, 255, 255, 0.65);
border-radius: 10px;
border: 1px solid #fff;
&:hover {
border: 1px solid var(--color-main-active);
}
.search-input {
border: none;
outline: none;
width: 838px;
height: 48px;
background-color: transparent;
font-size: 14px;
padding: 12px 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 22px;
color: rgba(59, 65, 75, 1);
&::placeholder {
color: #a8abb2;
}
}
.search-btn {
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 120px;
height: 46px;
margin-right: -3px;
border-radius: 8px;
background-color: rgb(5, 95, 194);
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 22px;
color: #fff;
img {
width: 18px;
height: 18px;
margin-right: 8px;
}
}
}
.search-bottom {
width: 688px;
height: 48px;
margin: 0 auto;
margin-top: 36px;
display: flex;
justify-content: space-between;
// gap: 16px;
.btn {
display: flex;
align-items: center;
gap: 9px;
width: 160px;
height: 48px;
border: 1px solid #aed6ff;
box-sizing: border-box;
border-radius: 24px;
background: #e7f3ff;
cursor: pointer;
position: relative;
&:hover {
background: #cae3fc;
}
.btn-text {
width: 80px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 400;
line-height: 48px;
margin-left: 36px;
text-align: center;
}
.btn-icon {
position: absolute;
top: 16px;
right: 19px;
width: 6px;
height: 12px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
</style>
<template>
<button class="action-button" :type="type">
{{ name }}
</button>
</template>
<script setup>
defineProps({
type: {
type: String,
default: 'normal'
},
name: {
type: String,
default: ''
}
})
</script>
<style scoped>
.action-button {
height: 28px;
padding: 0 8px;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
cursor: pointer;
line-height: 26px;
font-family: Source Han Sans CN;
font-size: 16px;
font-weight: 400;
transition: all 0.3s;
}
.action-button[type="normal"] {
background-color: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
}
.action-button[type="active"] {
background-color: rgba(231, 243, 255, 1);
color: var(--color-main-active);
border: 1px solid var(--color-main-active);
}
/* 悬停效果
.action-button[type="normal"]:hover {
background-color: #d9d9d9;
}
.action-button[type="active"]:hover {
background-color: #40a9ff;
} */
</style>
\ No newline at end of file
<template>
<div class="tag-wrapper" :class="classObject">
{{ tagName }}
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
const props = defineProps({
tagName: {
type: String,
default: '标签名称'
}
})
const classObject = computed(() => ({
'tag1': props.tagName === '人工智能',
'tag2': props.tagName === '生物科技',
'tag3': props.tagName === '新一代通信网络',
'tag4': props.tagName === '量子科技',
'tag5': props.tagName === '新能源',
'tag6': props.tagName === '集成电路',
'tag7': props.tagName === '海洋',
'tag8': props.tagName === '先进制造',
'tag9': props.tagName === '新材料',
'tag10': props.tagName === '航空航天',
'tag11': props.tagName === '太空',
'tag12': props.tagName === '深海',
'tag13': props.tagName === '极地',
'tag14': props.tagName === '核',
'tag15': props.tagName === '其他',
}))
</script>
<style lang="scss" scoped>
.tag-wrapper {
height: 24px;
padding: 0 8px;
line-height: 24px;
text-align: center;
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 14px;
font-weight: 400;
letter-spacing: 0px;
box-sizing: border-box;
border-radius: 4px;
border: 1px solid rgba(183, 235, 143, 1);
background: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
}
.tag1 {
border: 1px solid rgba(255, 163, 158, 1);
background: rgba(255, 241, 240, 1);
color: rgba(245, 34, 45, 1);
}
.tag2 {
border: 1px solid rgba(135, 232, 222, 1);
background: rgba(230, 255, 251, 1);
color: rgba(19, 168, 168, 1);
}
.tag3 {
border: 1px solid rgba(174, 214, 255, 1);
background: rgba(246, 250, 255, 1);
color: rgba(5, 95, 194, 1);
}
.tag4 {
border: 1px solid rgba(211, 173, 247, 1);
background: rgba(249, 240, 255, 1);
color: rgba(114, 46, 209, 1);
}
.tag5 {
border: 1px solid rgba(183, 235, 143, 1);
background: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
}
.tag6 {
border: 1px solid rgba(145, 202, 255, 1);
background: rgba(230, 244, 255, 1);
color: rgba(22, 119, 255, 1);
}
.tag7 {
border: 1px solid rgba(156, 207, 245, 1);
background: rgba(241, 247, 250, 1);
color: rgba(15, 120, 199, 1);
}
.tag8 {
border: 1px solid rgba(255, 229, 143, 1);
background: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1);
}
.tag9 {
border: 1px solid rgba(255, 213, 145, 1);
background: rgba(255, 247, 230, 1);
color: rgba(250, 140, 22, 1);
}
.tag10 {
border: 1px solid rgba(173, 198, 255, 1);
background: rgba(240, 245, 255, 1);
color: rgba(47, 84, 235, 1);
}
.tag11 {
border: 1px solid rgba(173, 198, 255, 1);
background: rgba(240, 245, 255, 1);
color: rgba(47, 84, 235, 1);
}
.tag12 {
border: 1px solid rgba(116, 146, 203, 1);
background: rgba(230, 244, 255, 1);
color: rgba(73, 104, 161, 1);
}
.tag13 {
border: 1px solid rgba(214, 228, 255, 1);
background: rgba(240, 245, 255, 1);
color: rgba(133, 165, 255, 1);
}
.tag14 {
border: 1px solid rgba(255, 187, 150, 1);
background: rgba(255, 242, 232, 1);
color: rgba(250, 84, 28, 1);
}
.tag15 {
border: 1px solid rgba(183, 235, 143, 1);
background: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
}
</style>
\ No newline at end of file
<template>
<button class="main-button" :type="type">
<div class="icon">
<slot name="button-icon"></slot>
</div>
<div class="button-text">{{ name }}</div>
</button>
</template>
<script setup>
defineProps({
type: {
type: String,
default: 'normal'
},
name: {
type: String,
default: ''
}
})
</script>
<style scoped>
.main-button {
height: 36px;
padding: 0 16px;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
cursor: pointer;
line-height: 34px;
font-family: var(--font-family-base);
font-size: var(--font-size-base);
font-weight: 400;
transition: all 0.3s;
}
.main-button[type="normal"] {
background-color: rgba(255, 255, 255, 1);
color: var(--color-main-primary);
}
.main-button[type="active"] {
background-color: var(--color-main-active);
color: #fff;
border: 1px solid var(--color-main-active);
}
/* 悬停效果
.main-button[type="normal"]:hover {
background-color: #d9d9d9;
}
.main-button[type="active"]:hover {
background-color: #40a9ff;
} */
</style>
\ No newline at end of file
<template>
<div class="source-tab-list-wrapper" :style="{ width: width }">
<div class="tab-item" :class="{ tabItemActive: activeSouceTabId === item.id }" v-for="item, index in sourceTabList"
:key="index" @click="handleClcikTab(item)">
{{ item.name }}
</div>
</div>
</template>
<script setup>
const props = defineProps({
width: {
type: String,
default: '1000px'
},
sourceTabList: {
type: Array,
default: [
]
},
activeSouceTabId: {
type: [String, Number],
default: ''
}
})
const emit = defineEmits('clickTab')
const handleClcikTab = (tab) => {
emit('clickTab', tab)
}
</script>
<style lang="scss" scoped>
.source-tab-list-wrapper {
height: 42px;
display: flex;
justify-content: flex-start;
gap: 12px;
.tab-item {
height: 42px;
line-height: 42px;
padding: 0 16px;
font-size: 20px;
font-weight: 400;
color: rgba(59, 65, 75, 1);
font-family: Source Han Sans CN;
text-align: center;
cursor: pointer;
&:hover {
color: var(--color-main-active);
}
}
.tabItemActive {
background: var(--color-main-active);
color: #fff;
font-weight: 700;
border-radius: 21px;
&:hover {
color: #fff;
}
}
}
</style>
\ No newline at end of file
<template>
<div class="analysis-box-wrapper" :style="{ width: width ? width : '100%', height: height ? height : '100%' }">
<div class="wrapper-header">
<div class="header-icon"></div>
<div class="header-title">{{ title }}</div>
<div class="header-btn" v-if="!showAllBtn">
<slot name="header-btn"></slot>
</div>
<div class="header-btn1" v-else>
<slot name="header-btn"></slot>
</div>
<div class="header-right">
<div class="header-right-btn" @click="handleSave" v-if="showAllBtn">
<img src="@/assets/icons/box-header-icon1.png" alt="">
</div>
<div class="header-right-btn" @click="handleDownload">
<img src="@/assets/icons/box-header-icon2.png" alt="">
</div>
<div class="header-right-btn" @click="handleCollect">
<img src="@/assets/icons/box-header-icon3.png" alt="">
</div>
</div>
</div>
<div class="wrapper-main">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { ref, computed } from 'vue'
const props = defineProps({
title: {
type: String,
default: ''
},
width: {
type: String,
default: ''
},
height: {
type: String,
default: ''
},
showAllBtn: {
type: Boolean,
default: true
}
})
const handleSave = () => {
ElMessage.success('保存当前内容')
// emit('save')
}
const handleDownload = () => {
ElMessage.success('下载当前内容')
// emit('download')
}
const handleCollect = () => {
ElMessage.success('收藏当前内容')
// emit('collect')
}
const emit = defineEmits(['save','download','collect'])
</script>
<style lang="scss" scoped>
.analysis-box-wrapper {
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.wrapper-header {
height: 45px;
display: flex;
box-sizing: border-box;
position: relative;
.header-icon {
margin-top: 18px;
width: 8px;
height: 20px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
.header-title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
// color: var(--color-main-active);
// font-family: Source Han Sans CN;
// font-size: 20px;
// font-weight: 700;
// line-height: 26px;
// letter-spacing: 0px;
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
.header-btn {
position: absolute;
top: 14px;
right: 84px;
// display: flex;
// justify-content: flex-end;
// gap: 8px;
}
.header-btn1 {
position: absolute;
top: 14px;
right: 104px;
}
.header-right {
position: absolute;
top: 14px;
right: 14px;
height: 28px;
display: flex;
justify-content: flex-end;
gap: 4px;
.header-right-btn {
width: 28px;
height: 28px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
}
}
.wrapper-main {
height: calc(100% - 45px);
overflow: hidden;
// overflow-y: auto;
padding: 5px auto;
}
}
</style>
<template>
<div class="overview-main-box-wrapper"
:style="{ width: width ? width : '1064px', height: height ? height : '450px' }">
<div class="overview-main-box-header">
<div class="header-left">
<div class="header-icon">
<slot name="header-icon"></slot>
</div>
<div class="header-title">{{ title }}</div>
</div>
<div class="header-right" @click="handleClickToDetail()">
{{ "查看详情 >" }}
</div>
</div>
<div class="wrapper-main">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const emit = defineEmits(['toDetail'])
const props = defineProps({
title: {
type: String,
default: ''
},
width: {
type: String,
default: ''
},
height: {
type: String,
default: ''
}
})
const handleClickToDetail = () => {
emit('toDetail')
}
</script>
<style lang="scss" scoped>
.overview-main-box-wrapper {
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.overview-main-box-header {
height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
justify-content: space-between;
box-sizing: border-box;
.header-left {
display: flex;
.header-icon {
width: 18px;
height: 18px;
margin-top: 13.7px;
margin-left: 18.7px;
}
.header-title {
margin-left: 21px;
height: 48px;
padding: 0 16px;
background: var(--color-main-active);
color: #fff;
font-family: Source Han Sans CN;
font-size: 20px;
font-weight: 700;
line-height: 48px;
text-align: center;
}
}
.header-right {
margin-right: 27px;
margin-top: 12px;
height: 24px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
}
}
.wrapper-main {
height: calc(100% - 48px);
overflow: hidden;
// position: relative;
}
}
</style>
<template>
<div class="overview-normal-box-wrapper"
:style="{ width: width ? width : '1064px', height: height ? height : '460px' }">
<div class="overview-normal-box-header">
<div class="header-left">
<div class="header-icon">
<slot name="header-icon"></slot>
</div>
<div class="header-title">{{ title }}</div>
</div>
<div class="header-right">
<slot name="header-right"></slot>
</div>
</div>
<div class="wrapper-main">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
title: {
type: String,
default: ''
},
width: {
type: String,
default: ''
},
height: {
type: String,
default: ''
}
})
</script>
<style lang="scss" scoped>
.overview-normal-box-wrapper {
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.overview-normal-box-header {
height: 48px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
justify-content: space-between;
box-sizing: border-box;
.header-left {
display: flex;
.header-icon {
width: 24px;
height: 24px;
margin-top: 12px;
margin-left: 20px;
}
.header-title {
margin-left: 16px;
height: 48px;
color: var(--color-main-active);
font-family: Source Han Sans CN;
font-size: 20px;
font-weight: 700;
line-height: 48px;
text-align: center;
}
}
.header-right {
height: 48px;
margin-right: 28px;
}
}
.wrapper-main {
height: calc(100% - 48px);
overflow: hidden;
}
}
</style>
<template>
<div class="box4">
<div class="box4-header">
<div class="header-icon">
<img src="./image1.png" alt="" />
</div>
<div class="header-title">{{ "社交媒体" }}</div>
<div class="more" @click="handleToMoreNews">{{ "更多 +" }}</div>
</div>
<div class="box4-main">
<div class="message-bubble" v-for="(item, index) in messageList" :key="index" @click="handleClickPerson(item)">
<div class="avatar-container">
<img :src="item[props.imageUrl] || avatarUser" :alt="item[props.name]" class="avatar" />
<div class="avatar-containerOne" v-if="isRepublicanParty"><img src="./image2.png" alt=""
class="avatar-imageOne" /></div>
<div class="avatar-containerTwo" v-if="isUnitedStatesSenate"><img src="./image3.png" alt=""
class="avatar-imageTwo" /></div>
</div>
<div class="bubble-container">
<div class="bubble">
<div class="bubble-header">
<span class="name">{{ item[props.name] }}</span>
<span class="meta">{{ item[props.time] }} · {{ item[props.source] }}</span>
</div>
<div class="bubble-content">
{{ item[props.content] }}
</div>
<div class="triangle"></div>
</div>
</div>
</div>
<!-- <MessageBubble v-for="(item, index) in messageList" @click="handleClickPsserson(item)"
@info-click="handleMediaClick(item)" :key="index" :avatar="item.img ? item.img : DefaultIcon1" :name="item.name"
:time="item.time" :source="item.source" :content="item.content" /> -->
<!-- <div class="box4-main-item" v-for="(item, index) in messageList" :key="index">
<div class="left" @click="handleClickPerson(item)">
<img :src="item.img ? item.img : DefaultIcon1" alt="" />
</div>
<div class="right">
<div class="right-top">
<div class="name">{{ item.name }}</div>
<div class="time">{{ item.time }}</div>
</div>
<div class="content">{{ item.content }}</div>
</div>
</div> -->
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import avatarUser from "@/assets/images/avatar_user.png";
const emit = defineEmits(["click", "info-click"]);
const props = defineProps({
isRepublicanParty: {
type: Boolean,
default: false
},
isUnitedStatesSenate: {
type: Boolean,
default: false
},
messageList: {
type: Array,
default: () => []
},
imageUrl: {
type: String,
default: "imageUrl"
},
name: {
type: String,
default: "name"
},
time: {
type: String,
default: "time"
},
source: {
type: String,
default: "source"
},
content: {
type: String,
default:
"content"
}
});
const formattedTime = computed((index) => {
const date = new Date(index);
if (isNaN(date.getTime())) {
return index; // 如果不是有效日期,返回原值
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate());
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
// return `${year}年${month}月${day}日 ${hours}:${minutes}:${seconds}`;
return `${month}${day}${hours}:${minutes}`;
});
const handleClickPerson = (item) => {
emit("person-click", item);
};
const handleInfoClick = (item) => {
emit("info-click", item);
};
const handleToMoreNews = (item) => {
emit("more-click", item);
};
</script>
<style lang="scss" scoped>
.box4 {
width: 792px;
height: 450px;
border-radius: 10px;
box-shadow: 0 0 20px 0 rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
border: 1px solid rgb(234, 236, 238);
.box4-header {
width: 792px;
height: 48px;
border-bottom: 1px solid rgb(234, 236, 238);
display: flex;
box-sizing: border-box;
position: relative;
.header-icon {
margin-left: 18px;
margin-top: 14px;
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
}
}
.header-title {
width: 80px;
margin-top: 11px;
margin-left: 18px;
height: 26px;
color: rgb(5, 95, 194);
font-family: "Source Han Sans CN";
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
}
.more {
width: 45px;
height: 24px;
position: absolute;
top: 12px;
right: 27px;
color: rgb(5, 95, 194);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 400;
line-height: 24px;
cursor: pointer;
text-align: right;
}
}
.box4-main {
height: 402px;
overflow-y: auto;
box-sizing: border-box;
padding-bottom: 8px;
padding-left: 21px;
padding-top: 23px;
.message-bubble {
display: flex;
max-width: 740px;
margin-bottom: 15px;
.avatar-container {
flex-shrink: 0;
flex-grow: 0;
flex: 0;
width: 42px;
height: 42px;
margin-right: 14.5px;
cursor: pointer;
position: relative;
.avatar-containerOne,
.avatar-containerTwo {
display: inline-block;
position: absolute;
}
.avatar-containerOne {
left: 2px;
top: 29px;
.avatar-imageOne {
width: 20px;
height: 20px;
}
}
.avatar-containerTwo {
right: 2px;
top: 29px;
.avatar-imageTwo {
width: 20px;
height: 20px;
}
}
.avatar {
width: 42px;
height: 42px;
border-radius: 50%;
object-fit: cover;
}
}
.bubble-container {
flex: 1;
position: relative;
.bubble {
background-color: rgba(246, 250, 255, 1);
border-radius: 12px;
padding: 12px 12px;
position: relative;
border: 1px solid rgba(231, 243, 255, 1);
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
.bubble-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
.name {
color: rgb(59, 65, 75);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
.meta {
color: rgb(95, 101, 108);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: right;
}
}
.bubble-content {
color: rgba(59, 65, 75, 1);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.triangle {
position: absolute;
left: -9px;
/* 向左偏移1px,给描边留出空间 */
top: 15px;
width: 0;
height: 0;
/* 外层:描边颜色的三角形(比内层大1px) */
border-top: 9px solid transparent;
border-bottom: 9px solid transparent;
border-right: 9px solid rgb(231, 243, 255);
}
/* 内层:原有颜色的三角形,覆盖在外层上面,模拟描边效果 */
.triangle::after {
content: '';
position: absolute;
top: -8px;
/* 向上偏移1px,对齐中心 */
left: 1px;
/* 向右偏移1px,露出外层的描边 */
width: 0;
height: 0;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-right: 8px solid rgba(246, 250, 255, 1);
}
}
}
}
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.message-bubble {
max-width: 100%;
}
.bubble-header {
flex-direction: column;
align-items: flex-start;
}
.meta {
margin-top: 4px;
}
}
</style>
<template>
<div class="module-header-wrapper">
<div class="nav-content">
<div class="nav-left" :class="{ 'flex-start': isShowSearchBar }">
<div class="icon">
<img v-show="!isShowSearchBar" src="@/assets/icons/overview/logo.png" alt="" />
<img v-show="isShowSearchBar" src="@/assets/icons/overview/logo-white.png" alt="" />
</div>
<SearchBar v-show="isShowSearchBar" />
<div class="title-box" v-show="!isShowSearchBar">
<!-- <div class="title-box" v-if="false"> -->
<div
class="title"
v-for="(item, index) in homeTitleList"
:key="index"
@mouseenter="handleShowMenu(index, true)"
@mouseleave="handleShowMenu(index, false)"
@click="handleClickTitle(item)"
>
<div class="text" :class="{ textActive: homeActiveTitleIndex === index }">
{{ item.name }}
</div>
<div class="bottom-line" v-if="homeActiveTitleIndex === index"></div>
</div>
</div>
</div>
<div class="nav-right">
<div class="info-box" @click="handleClickToolBox">
<div class="mail">
<img src="@/assets/icons/overview/mail.png" alt="" />
</div>
<div class="user">
<img src="@/assets/icons/overview/user.png" alt="" />
</div>
<div class="name">{{ "管理员" }}</div>
</div>
</div>
<div class="menu-box" v-if="isShowMenu" @mouseenter="handleHoverMenu(true)" @mouseleave="handleHoverMenu(false)">
<div class="menu-content">
<div class="menu-item" v-for="(item, index) in menuList" :key="index" @click="handleToModule(item)">
<div class="icon">
<img :src="item.icon" alt="" />
</div>
<div class="title">{{ item.title }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, watchEffect } from "vue";
import { useRouter } from "vue-router";
import { useRoute } from "vue-router";
import { getPersonType } from "@/api/common/index";
import SearchBar from "@/components/layout/SearchBar.vue";
import Menu1 from "@/assets/icons/overview/menu1.png";
import Menu2 from "@/assets/icons/overview/menu2.png";
import Menu3 from "@/assets/icons/overview/menu3.png";
import Menu4 from "@/assets/icons/overview/menu4.png";
import Menu5 from "@/assets/icons/overview/menu5.png";
import Menu6 from "@/assets/icons/overview/menu6.png";
import Menu7 from "@/assets/icons/overview/menu7.png";
import Menu8 from "@/assets/icons/overview/menu8.png";
import Menu9 from "@/assets/icons/overview/menu9.png";
import Menu10 from "@/assets/icons/overview/menu10.png";
import Menu11 from "@/assets/icons/overview/menu11.png";
import Menu12 from "@/assets/icons/overview/menu12.png";
import { ElMessage } from "element-plus";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
const store = useWrittingAsstaintStore();
let isShowSearchBar = computed(() => {
return store.isShowSearchBar;
});
const router = useRouter();
const personTypeList = ref([]);
// 获取人物类别
const handleGetPersonType = async () => {
try {
const res = await getPersonType();
console.log("res", res);
if (res.code === 200) {
personTypeList.value = res.data;
} else {
personTypeList.value = [];
}
window.sessionStorage.setItem("personTypeList", JSON.stringify(personTypeList.value));
} catch (error) {}
};
// 概览页标题列表
const homeTitleList = ref([
{
name: "中美科技博弈",
path: "/ZMOverView",
disabled: false
},
{
name: "主要国家科技动向感知",
path: "",
disabled: true
},
{
name: "主要国家竞争科技安全",
path: "",
disabled: true
}
]);
const homeActiveTitleIndex = ref(0);
const isShowMenu = ref(false);
const handleShowMenu = (index, isShow) => {
if (index === 0) {
isShowMenu.value = isShow;
}
};
const handleHoverMenu = isShow => {
isShowMenu.value = isShow;
};
const menuList = ref([
{
title: "中美科技博弈概览",
icon: Menu1,
path: "/ZMOverView"
},
{
title: "科技法案",
icon: Menu2,
path: "/billHome"
},
{
title: "科技政令",
icon: Menu3,
path: "/decree"
},
{
title: "美国科技智库",
icon: Menu4,
path: "/thinkTank"
},
{
title: "出口管制",
icon: Menu5,
path: "/exportControl"
},
{
title: "科研合作限制",
icon: Menu6,
path: "/cooperationRestrictions"
},
{
title: "投融资限制",
icon: Menu7,
path: "/finance"
},
{
title: "市场准入限制",
icon: Menu8,
path: "/marketAccessRestrictions"
},
{
title: "规则限制",
icon: Menu9,
path: "/ruleRestrictions"
},
{
title: "美国科技人物观点",
icon: Menu10,
path: "/technologyFigures"
},
{
title: "美国主要创新主体动向",
icon: Menu11,
path: "/innovationSubject"
},
{
title: "美国科研资助体系",
icon: Menu12,
path: "/scientificFunding"
}
]);
const handleToModule = item => {
const curRoute = router.resolve({
path: item.path
});
window.open(curRoute.href, "_blank");
};
const searchText = ref("");
const handleSearch = () => {
const curRoute = router.resolve({
path: "/searchResults",
query: {
searchText: searchText.value
}
});
window.open(curRoute.href, "_blank");
};
const handleClickTitle = item => {
if (item.name === "主要国家科技动向感知" || item.name === "主要国家竞争科技安全") {
ElMessage.warning("当前功能正在开发中,敬请期待!");
}
};
const handleClickToolBox = () => {
ElMessage.warning("当前功能正在开发中,敬请期待!");
};
onMounted(() => {
handleGetPersonType();
});
</script>
<style lang="scss" scoped>
.module-header-wrapper {
width: 100%;
// height: 64px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: linear-gradient(180deg, rgba(246, 250, 255, 0.8) 0%, rgba(255, 255, 255, 0.8) 100%);
padding: 12px 0;
.nav-content {
width: 1600px;
margin: 0 auto;
display: flex;
justify-content: space-between;
position: relative;
align-items: flex-start;
.nav-left {
display: flex;
align-items: center;
&.flex-start {
align-items: flex-start;
.icon {
flex-shrink: 0;
width: 48px;
height: 48px;
border-radius: 50%;
background: rgb(5, 95, 194);
display: flex;
align-items: center;
justify-content: center;
img {
width: 29px;
height: 30px;
}
}
}
.icon {
width: 29px;
height: 30px;
margin-right: 17px;
img {
width: 100%;
height: 100%;
}
}
.title-box {
display: flex;
gap: 30px;
.title {
cursor: pointer;
position: relative;
&:hover {
.text {
color: var(--color-main-active);
}
}
.text {
color: rgba(59, 65, 75, 1);
font-family: YouSheBiaoTiHei;
font-style: Regular;
font-size: 30px;
font-weight: 400;
letter-spacing: 0px;
}
.textActive {
color: var(--color-main-active);
}
.bottom-line {
position: absolute;
bottom: -15px;
width: 90%;
height: 20px;
margin-top: 9px;
&::after {
display: block;
content: "";
width: 50%;
height: 4px;
background: var(--color-main-active);
position: absolute;
bottom: 2px;
left: 0;
right: 0;
margin: 0 auto;
}
}
}
}
}
.nav-right {
display: flex;
justify-content: flex-end;
gap: 21px;
.info-box {
display: flex;
justify-content: flex-end;
align-items: center;
.mail {
width: 32px;
height: 32px;
margin-right: 14px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.user {
width: 32px;
height: 32px;
margin-right: 11px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.name {
width: 48px;
height: 30px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
}
}
}
.menu-box {
position: absolute;
z-index: 999999999;
width: 713px;
height: 413px;
top: 52px;
left: 0;
box-sizing: border-box;
border: 1px solid rgba(255, 255, 255, 1);
border-radius: 10px;
backdrop-filter: blur(30px);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 0.8);
.menu-content {
width: 562px;
height: 348px;
margin-top: 8px;
margin-left: 72px;
display: flex;
flex-wrap: wrap;
.menu-item {
margin-top: 36px;
width: 280px;
height: 24px;
display: flex;
cursor: pointer;
&:hover {
.title {
color: var(--color-main-active);
font-size: 20px;
}
}
.icon {
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
}
}
.title {
margin-left: 16px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 18px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
}
}
}
}
</style>
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论