提交 a4b931c9 authored 作者: 张烨's avatar 张烨

Merge branch 'master' into zy-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. 其它
- 所有复杂渲染逻辑逻辑一律抽到计算属性或方法,模板结构保持简洁
......@@ -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
})
......
<template>
<div class="box3-item" @click="handleToNewsAnalysis(news)">
<div class="left">
<img :src="news[props.img] ? news[props.img] : DefaultIconNews" alt="" />
</div>
<div class="right">
<div class="right-top">
<div class="title"><span class="text-inner">{{ news[props.title] }}</span></div>
<div class="time">{{ news[props.from] }}</div>
</div>
<div class="right-footer">{{ news[props.content] }}</div>
</div>
</div>
</template>
<script setup>
import DefaultIconNews from "@/assets/icons/default-icon-news.png";
const props = defineProps({
// 新闻列表数据
news: {
type: Object,
default: () => { }
},
img: {
type: String,
default: 'img'
},
title: {
type: String,
default: "title"
},
from: {
type: String,
default: "from"
},
content: {
type: String,
default: "content"
},
});
const emit = defineEmits(['item-click', 'more-click']);
const handleToNewsAnalysis = (item, index) => {
emit('item-click', item, index)
};
</script>
<style lang="scss" scoped>
.box3-item {
display: flex;
align-items: center;
height: 78px;
margin: 0px 21px;
cursor: pointer;
&:hover {
.right-top .title {
color: rgb(5, 95, 194) !important;
font-weight: 700;
}
.right-top .text-inner {
border-bottom-color: rgb(5, 95, 194) !important;
}
}
}
.left {
width: 97px;
// flex-shrink: 0;
height: 72px;
img {
width: 100%;
height: 100%;
border-radius: 4px;
}
}
.right {
flex: 1;
min-width: 0;
margin-left: 20px;
.right-top {
display: flex;
justify-content: space-between;
.title {
// width: 500px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 700;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.text-inner {
border-bottom: 1px solid transparent;
}
}
.time {
text-align: right;
height: 22px;
color: rgba(95, 101, 108, 1);
font-family: 'Source Han Sans CN';
font-size: 14px;
font-weight: 400;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.right-footer {
height: 48px;
/* 调整为2行的高度:24px × 2 = 48px */
color: rgba(59, 65, 75, 1);
font-family: 'Source Han Sans CN';
font-size: 16px;
font-weight: 400;
line-height: 24px;
overflow: hidden;
display: -webkit-box;
/* 关键 */
-webkit-line-clamp: 2;
/* 显示2行 */
-webkit-box-orient: vertical;
/* 垂直方向排列 */
text-overflow: ellipsis;
/* 第二行省略号 */
white-space: normal;
/* 改为 normal */
word-break: break-word;
/* 允许单词换行 */
}
}
</style>
\ No newline at end of file
<template>
<el-space @click="onClick(person)">
<ElAvatar :size="48" :src="person[img]" alignment="center" />
<el-space :size="0" direction="vertical" alignment="flex-start">
<div class="text-header">{{ person[name] }}</div>
<div class="person-position">{{ person[position] }}</div>
</el-space>
</el-space>
</template>
<script setup>
import '@/styles/common.scss'
import { ElSpace } from 'element-plus';
const props = defineProps({
// 新闻列表数据
person: {
type: Object,
default: () => { }
},
img: {
type: String,
default: 'avatarUrl'
},
name: {
type: String,
default: "name"
},
position: {
type: String,
default: "position"
},
introduction: {
type: String,
default: "introduction"
},
});
const emit = defineEmits(['item-click', 'more-click']);
const onClick = (item) => {
emit('item-click', item)
console.log(item)
};
</script>
<style lang="scss" scoped>
.person-position {
font-size: 16px;
line-height: 24px;
color: var(--text-primary-65-color);
}
</style>
\ No newline at end of file
<script setup>
import { ElSpace, ElButton } from 'element-plus';
import "@/styles/main.css"
import { copyToClipboardThenTip } from '@/utils/systemUtils'
const colors = [
{ title: "黑色90% / 主标题文字颜色", name: "--text-primary-90-color" },
{ title: "黑色80% / 小标题文字颜色", name: "--text-primary-80-color" },
{ title: "黑色65% / 正文颜色", name: "--text-primary-65-color" },
{ title: "黑色50%", name: "--text-primary-50-color" },
{ title: "黑色10%", name: "--bg-black-10" },
{ title: "黑色5% / 分割线颜色", name: "--bg-black-5" },
{ title: "黑色2% / 灰色背景色", name: "--bg-black-2" },
{ title: "白色主色", name: "--bg-white-100" },
{ title: "主色", name: "--color-primary-100" },
]
function copyColorVar(item) {
const color = `var(${item.name})`
copyToClipboardThenTip(color)
}
</script>
<template>
<el-space direction="vertical" alignment="flex-start">
<div class="text-title-2">颜色</div>
<el-space>
<el-button v-for="(item, index) in colors" :key="index" :color="`var(${item.name})`"
v-on:click="copyColorVar(item)">
{{ item.title }}
</el-button>
</el-space>
</el-space>
</template>
<style scoped></style>
\ No newline at end of file
<script setup lang="ts">
import "@/styles/container.scss"
import TextStyle from './textStyle.vue';
import ConstStyle from './constStyle.vue';
import { ElScrollbar, ElSpace } from "element-plus";
</script>
<template>
<el-scrollbar>
<div class="common-page">
<el-space direction="vertical" alignment="flex-start">
<div class="text-title-0-show">开发样式</div>
<div class="text-title-1-show">样式变量</div>
<const-style></const-style>
<div class="text-title-1-show">文字样式</div>
<text-style></text-style>
</el-space>
</div>
</el-scrollbar>
</template>
\ No newline at end of file
<template>
<table style="width: 100%; border-collapse: collapse; border: 1px solid #ebeef5;">
<!-- 表头 -->
<thead>
<tr class="text-title-2">
<th> 名称</th>
<th> 类型名称</th>
<th> 操作</th>
</tr>
</thead>
<!-- 表格内容 -->
<tbody>
<template v-for="(row, index) in tableData" :key="index">
<!-- 隔行变色效果 -->
<tr :style="{
height: '60px',
backgroundColor: index % 2 === 1 ? '#fafafa' : 'transparent'
}">
<!-- 名称列 -->
<td style="padding: 12px; border: 1px solid #ebeef5;">
<div :class="row.className">
{{ row.name }}
</div>
</td>
<!-- 类型名称列(带复制功能) -->
<td style="padding: 12px; border: 1px solid #ebeef5;">
<div @click="copyToClipboardThenTip(row.className)" style="cursor: pointer;">
<span>{{ row.className }}</span>
</div>
</td>
<!-- 操作列 -->
<td style="padding: 12px; border: 1px solid #ebeef5; text-align: center;">
<el-button type="primary" link @click="copyToClipboardThenTip(row.className)">
<el-icon>
<DocumentCopy />
</el-icon>
复制
</el-button>
</td>
</tr>
</template>
</tbody>
</table>
</template>
<script setup>
import { ref } from 'vue'
import { DocumentCopy } from '@element-plus/icons-vue'
import "@/styles/common.scss"
import { copyToClipboardThenTip } from '@/utils/systemUtils'
// 表格数据
const tableData = ref([
// { name: '0级标题', className: 'text-title-0' },
{ name: '0级标题-加粗', className: 'text-title-0-bold' },
{ name: '0级标题-综艺', className: 'text-title-0-show' },
// { name: '1级标题', className: 'text-title-1' },
{ name: '1级标题-加粗', className: 'text-title-1-bold' },
{ name: '1级标题-综艺', className: 'text-title-1-show' },
{ name: '2级标题', className: 'text-title-2' },
{ name: '2级标题-加粗', className: 'text-title-2-bold' },
{ name: '2级标题-综艺', className: 'text-title-2-show' },
{ name: '3级标题', className: 'text-title-3' },
{ name: '3级标题-加粗', className: 'text-title-3-bold' },
{ name: '3级标题-综艺', className: 'text-title-3-show' },
{ name: '正文', className: 'text-regular' },
{ name: '正文-加粗', className: 'text-bold' },
{ name: '正文-紧凑', className: 'text-compact' },
{ name: '正文-紧凑-加粗', className: 'text-compact-bold' },
{ name: '1级提示文字', className: 'text-tip-1' },
{ name: '1级提示文字-加粗', className: 'text-tip-1-bold' },
{ name: '2级提示文字', className: 'text-tip-2' },
{ name: '2级提示文字-加粗', className: 'text-tip-2-bold' },
{ name: '3级提示文字', className: 'text-tip-3' },
])
</script>
<style scoped></style>
\ No newline at end of file
//企业主页
const companyPages = () => import('@/views/companyPages/index.vue')
const companyPagesRoutes = [
// 智库系统的主要路由
{
......@@ -9,7 +11,7 @@ const companyPagesRoutes = [
component: companyPages,
meta: {
title: "企业主页",
dynamicTitle: true
dynamicTitle: true
}
},
......
//样式主页
const StylePages = () => import("@/components/devStyle/components/index.vue");
const stylePagesRoutes = [
// 智库系统的主要路由
{
path: "/devStylePages",
name: "devStylePages",
component: StylePages,
meta: {
title: "开发样式",
dynamicTitle: true
}
},
]
export default stylePagesRoutes
\ No newline at end of file
......@@ -10,12 +10,141 @@
.flex-display{
display: flex;
}
/***文本样式***/
.text-base{
color: var(--color-primary-90);
}
//0级标题
.text-title-0{
@extend .text-base;
color: var(--color-primary-90);
font-size: 32px;
}
.text-title-0-bold{
@extend .text-title-0;
font-weight: Bold;
}
.text-title-0-show{
@extend .text-title-0;
font-size: 48px;
font-family: "YouSheBiaoTiHei";
}
//1级标题
.text-title-1{
@extend .text-base;
color: var(--color-primary-90);
font-size: 24px;
}
.text-title-1-bold{
@extend .text-title-1;
font-weight: Bold;
}
.text-title-1-show{
@extend .text-title-1;
font-size: 30px;
font-family: "YouSheBiaoTiHei";
}
//2级标题
.text-title-2{
@extend .text-base;
color: var(--color-primary-90);
font-size: 20px;
line-height:26px;
}
.text-title-2-bold{
@extend .text-title-2;
font-weight: Bold;
}
.text-title-2-show{
@extend .text-title-2;
font-size: 24px;
font-family: "YouSheBiaoTiHei";
line-height: normal !important; // 取消继承的行高
}
//3级标题
.text-title-3{
@extend .text-base;
color: var(--color-primary-90);
font-size: 18px;
line-height:24px;
}
.text-title-3-bold{
@extend .text-title-3;
font-weight: Bold;
}
.text-title-3-show{
@extend .text-title-3;
font-size: 20px;
font-family: "YouSheBiaoTiHei";
line-height: normal !important; // 取消继承的行高
}
//正文
.text-regular{
@extend .text-base;
font-size: 16px;
line-height: 24px;
color: var(--text-primary-80-color);
line-height:30px;
}
.text-header {
//正文-加粗
.text-bold{
@extend .text-base;
font-weight: Bold;
}
//正文-紧凑
.text-compact{
@extend .text-base;
font-size: 16px;
line-height:24px;
}
.text-compact-bold{
@extend .text-base;
font-size: 16px;
line-height:24px;
font-weight: Bold;
}
//1级提示文字
.text-tip-1{
@extend .text-base;
color: var(--color-primary-90);
font-size: 16px;
line-height:24px;
}
.text-tip-1-bold{
@extend .text-tip-1;
font-weight: Bold;
}
//2级提示文字
.text-tip-2{
@extend .text-base;
color: var(--color-primary-90);
font-size: 14px;
line-height:22px;
}
.text-tip-2-bold{
@extend .text-tip-2;
font-weight: Bold;
}
//3级提示文字
.text-tip-3{
@extend .text-base;
color: var(--color-primary-90);
font-size: 12px;
}
......@@ -2,8 +2,10 @@
/***没有nav下划线***/
.common-descriptions .el-descriptions__label{
@extend .text-header;
@extend .text-tip-1-bold;
}
.common-descriptions .el-descriptions__content{
@extend .text-base
@extend .text-tip-1;
color: var(--text-primary-80-color);
}
\ No newline at end of file
import { ElMessage } from "element-plus";
interface BaseReturn {
status: boolean;
message: string;
}
export async function copyToClipboard(txet: string): Promise<BaseReturn> {
try {
// 使用现代 Clipboard API
await navigator.clipboard.writeText(txet);
console.log("已复制:", txet);
return { status: true, message: "已复制:" + txet };
} catch (err) {
console.error("复制失败:", err);
// 降级方案:使用document.execCommand
const textArea = document.createElement("textarea");
textArea.value = txet;
document.body.appendChild(textArea);
textArea.select();
try {
const successful = document.execCommand("copy");
if (successful) {
return { status: true, message: "已复制:" + txet };
}
} catch (err) {}
document.body.removeChild(textArea);
return { status: false, message: "复制失败,请手动复制" };
}
}
// 复制类型名称到剪贴板
export async function copyToClipboardThenTip(text: string): Promise<void> {
const { status, message } = await copyToClipboard(text);
if (status) {
ElMessage.success(message);
} else {
ElMessage.error(message);
}
}
<template>
<div class="overview-card">
<div class="overview-card-header">
<div class="overview-card-header-left">
<div class="overview-card-header-icon">
<img :src="icon" alt="" />
</div>
<div class="overview-card-header-title">{{ title }}</div>
</div>
<div v-if="$slots.right" class="overview-card-header-right">
<slot name="right" />
</div>
</div>
<div class="overview-card-main">
<slot />
</div>
</div>
</template>
<script setup>
defineProps({
title: {
type: String,
default: ""
},
icon: {
type: String,
default: ""
}
});
</script>
<style lang="scss" scoped>
.overview-card {
height: 450px;
box-sizing: border-box;
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);
overflow: hidden;
.overview-card-header {
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 27px 0 22px;
.overview-card-header-left {
display: flex;
align-items: center;
.overview-card-header-icon {
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.overview-card-header-title {
margin-left: 19px;
height: 26px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
}
.overview-card-header-right {
display: flex;
align-items: center;
justify-content: flex-end;
height: 100%;
}
}
.overview-card-main {
height: calc(100% - 53px);
}
}
</style>
......@@ -89,8 +89,13 @@ export default defineConfig({
changeOrigin: true,
rewrite: (path) => path.replace(/^\/temporarySearch/, '')
},
'^/bill(?:/|$)': {
target: 'http://172.20.10.3:28080/',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/bill/, '')
},
'/pdfSse': {
target: 'http://8.140.26.4:10020/',
target: 'http://8.140.26.4:10020/',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/pdfSse/, ''),
configure: (proxy) => {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论