feat: 服务中心前端界面

This commit is contained in:
liulujian 2026-04-03 20:28:59 +08:00
parent 76f2b947f8
commit 4f2a8c4aac
18 changed files with 1774 additions and 487 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
prd/

View File

@ -0,0 +1,310 @@
# 碳信网服务中心页面开发实现计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将6个PRD原型页面还原为Vue组件并抽取新版首页导航为公共组件。
**Architecture:**
- 新导航组件 `new-nav/index.vue``home2/index.vue` 抽取,复用原有 `nav/index.vue` 的登录状态和用户信息逻辑
- 6个页面组件放在 `views/fwsc/` 目录下
- 路由使用父子路由结构,`/fwsc` 为引导页下设4个子页面路由
**Tech Stack:** Vue2 + TDesign + less
---
## 文件变更总览
| 操作 | 文件路径 |
|------|---------|
| 创建 | `src/pages/index/components/new-nav/index.vue` |
| 创建 | `src/pages/index/views/fwsc/index.vue` |
| 创建 | `src/pages/index/views/fwsc/fwsc.vue` |
| 创建 | `src/pages/index/views/fwsc/xqsc.vue` |
| 创建 | `src/pages/index/views/fwsc/jrsc.vue` |
| 创建 | `src/pages/index/views/fwsc/sjsc.vue` |
| 创建 | `src/pages/index/views/fwsc/sjlbc.vue` |
| 修改 | `src/pages/index/router/routes.js` |
---
## Task 1: 创建 NewNav 导航组件
**文件:**
- 创建: `src/pages/index/components/new-nav/index.vue`
- [ ] **Step 1: 创建 new-nav 目录和组件文件**
`home2/index.vue``.nav-box` 抽取导航栏代码,新建 `src/pages/index/components/new-nav/index.vue`
关键功能点:
- Logo + 菜单栏(首页、碳证中心、服务中心[下拉]、共性能力、企业出海、行业专题)
- 服务中心下拉:碳服务市场、碳需求市场、碳金融市场、碳数据市场
- 右侧:工作台、登录/注册、用户头像下拉菜单
- 登录状态判断复用原有 nav 的 `sessionStorage.getItem('sfdl')` 逻辑
- 用户信息获取复用 `sessionStorage.getItem('yhxx')` 逻辑
- 工作台跳转 `/view/mhzc/yhzx`
- 登录跳转 `/view/mhzc/login`
- 用户下拉:用户中心、退出登录
下拉菜单使用 `position: absolute` + `z-index` 实现hover 显示。
---
## Task 2: 创建服务中心引导页
**文件:**
- 创建: `src/pages/index/views/fwsc/index.vue`
- [ ] **Step 1: 创建 views/fwsc 目录和 index.vue**
根据 `prd/介绍.html` 实现引导页:
- 顶部NewNav 导航栏
- Banner区大标题"可信碳服务中心",副标题"链接全球碳资产,赋能绿色价值链..."
- 四大服务入口卡片2x2 Grid
- 碳服务市场 → `/fwsc/fwsc`
- 碳需求市场 → `/fwsc/xqsc`
- 碳金融市场 → `/fwsc/jrsc`
- 碳数据市场 → `/fwsc/sjsc`
- 交易流程指引(注册认证 → 发布信息 → 在线撮合)
- 底部Footer
卡片样式白色背景、圆角、hover放大效果`transform: scale(1.02)`
配色:绿色主题 #009a29
---
## Task 3: 创建碳服务市场页面
**文件:**
- 创建: `src/pages/index/views/fwsc/fwsc.vue`
- [ ] **Step 1: 根据 prd/供需大厅.html 创建碳服务市场页面**
布局:
- NewNav + 当前位置"首页 / 服务中心"
- 左侧筛选栏220px宽sticky
- 右侧服务卡片列表2列Grid
左侧筛选栏:
- 服务类型全部、碳核查服务、碳足迹核算、碳减排技术服务、碳资产管理服务、ESG报告编制、碳交易咨询
- 服务企业(全部、欧冶云商、上海企源科技、上海零数科技、上海链坤数字科技)
- 关键词搜索输入框 + 搜索按钮
右侧卡片字段(根据原型):
- 标题:"本司提供碳相关服务寻找有需求的企业客户"
- 企业:"上海链坤数字科技有限公司 · 辽宁省"
- 标签ESG报告编制、碳减排技术服务
- 价格:"¥ 10,000.00 /次"(橙色强调色 #D25F00
- 描述:"我们为参与碳市场的企业提供全方位的交易策略与风险管理咨询..."
- 按钮:联系服务商(主按钮)、收藏(次按钮)
卡片底部有分隔线。使用 `t-button` 组件。
---
## Task 4: 创建碳需求市场页面
**文件:**
- 创建: `src/pages/index/views/fwsc/xqsc.vue`
- [ ] **Step 1: 根据 prd/需求.html 创建碳需求市场页面**
布局与碳服务市场基本一致,筛选栏相同。
右侧卡片字段:
- 标题:"碳核查服务需求" 或 "【碳服务需求!急急急!】"
- 企业:"上海链坤数字科技有限公司"
- 标签ESG报告编制、碳减排技术服务
- 预算:"¥ 50-100 万元"(橙色 #D25F00
- 描述
- 有效期至2026-04-24
- 按钮:联系服务
注意:部分卡片标题带有"急"字标记,用不同颜色或样式突出。
---
## Task 5: 创建碳金融市场页面
**文件:**
- 创建: `src/pages/index/views/fwsc/jrsc.vue`
- [ ] **Step 1: 根据 prd/金融.html 创建碳金融市场页面**
左侧筛选栏:
- 服务类型(全部、绿色信贷、绿色保险)
- 服务企业(全部、中国银行、工商银行、农业银行、建设银行、招商银行、中信银行、北京银行、兴业银行、邮政储蓄银行)
右侧金融产品卡片2列Grid
- 机构名称 + Logo
- 产品名称(如"云南昭通信贷业务"
- 额度:"¥ 50,000.00"
- 期限:"1-3年"
- 利率:"3.6%~4.2%"(绿色 #00B42A
- 产品标签(如"绿色项目贷款"
- 按钮:立即申请(主)、查看详情(次)
---
## Task 6: 创建碳数据市场页面
**文件:**
- 创建: `src/pages/index/views/fwsc/sjsc.vue`
- [ ] **Step 1: 根据 prd/数据.html 创建碳数据市场页面**
左侧筛选栏:
- 数据类型(全部、公共数据、因子库、社会性数据)
右侧数据库卡片2列Grid
- 数据库名称:"宝山绿色低碳数据创新实验室"、"HiQLCD数据库"、"天工数据库"、"ecoinvent数据库"
- 描述
- 标签:公共数据/社会性数据/商业数据、免费/付费
- 按钮:查看数据库
---
## Task 7: 创建数据列表页面
**文件:**
- 创建: `src/pages/index/views/fwsc/sjlbc.vue`
- [ ] **Step 1: 根据 prd/数据列表.html 创建数据列表页面**
左侧筛选栏:
- 价格区间(最低 - 最高)
- 有效期(日期选择器)
右侧数据列表(表格形式):
- 列:数据类型、名称、发布时间/更新时间、数据条数、浏览量、下载量
- 数据示例:
- 全球多源高分辨温室气体及大气污染物排放清单 / 社会性数据 / 45,000条 / 475次浏览 / 18次下载
- 国家温室气体排放因子数据库 / 公共数据 / ...
- 碳排放数据库 / ...
列表顶部有排序图标(三角形)和浏览量/下载量指示。
---
## Task 8: 配置路由
**文件:**
- 修改: `src/pages/index/router/routes.js`
- [ ] **Step 1: 添加6个路由**
在 routes.js 顶部添加 lazy-load 导入函数:
```javascript
function fwscIndex() {
return import(/* webpackChunkName: "fwsc" */ '@/pages/index/views/fwsc/index.vue');
}
function fwscFwsc() {
return import(/* webpackChunkName: "fwsc" */ '@/pages/index/views/fwsc/fwsc.vue');
}
function fwscXqsc() {
return import(/* webpackChunkName: "fwsc" */ '@/pages/index/views/fwsc/xqsc.vue');
}
function fwscJrsc() {
return import(/* webpackChunkName: "fwsc" */ '@/pages/index/views/fwsc/jrsc.vue');
}
function fwscSjsc() {
return import(/* webpackChunkName: "fwsc" */ '@/pages/index/views/fwsc/sjsc.vue');
}
function fwscSjlbc() {
return import(/* webpackChunkName: "fwsc" */ '@/pages/index/views/fwsc/sjlbc.vue');
}
```
在 routes 数组末尾添加:
```javascript
{
name: 'fwsc',
path: '/fwsc',
component: fwscIndex,
meta: {
title: '服务中心',
isShowSideBar: false,
hasHome: true,
breadCrumbs: [{ title: '首页', to: '/home' }, { title: '服务中心', to: '/fwsc' }],
disableBack: true,
},
},
{
name: 'fwsc-fwsc',
path: '/fwsc/fwsc',
component: fwscFwsc,
meta: {
title: '碳服务市场',
isShowSideBar: false,
hasHome: true,
breadCrumbs: [{ title: '首页', to: '/home' }, { title: '服务中心', to: '/fwsc' }, { title: '碳服务市场', to: '/fwsc/fwsc' }],
disableBack: true,
},
},
{
name: 'fwsc-xqsc',
path: '/fwsc/xqsc',
component: fwscXqsc,
meta: {
title: '碳需求市场',
isShowSideBar: false,
hasHome: true,
breadCrumbs: [{ title: '首页', to: '/home' }, { title: '服务中心', to: '/fwsc' }, { title: '碳需求市场', to: '/fwsc/xqsc' }],
disableBack: true,
},
},
{
name: 'fwsc-jrsc',
path: '/fwsc/jrsc',
component: fwscJrsc,
meta: {
title: '碳金融市场',
isShowSideBar: false,
hasHome: true,
breadCrumbs: [{ title: '首页', to: '/home' }, { title: '服务中心', to: '/fwsc' }, { title: '碳金融市场', to: '/fwsc/jrsc' }],
disableBack: true,
},
},
{
name: 'fwsc-sjsc',
path: '/fwsc/sjsc',
component: fwscSjsc,
meta: {
title: '碳数据市场',
isShowSideBar: false,
hasHome: true,
breadCrumbs: [{ title: '首页', to: '/home' }, { title: '服务中心', to: '/fwsc' }, { title: '碳数据市场', to: '/fwsc/sjsc' }],
disableBack: true,
},
},
{
name: 'fwsc-sjlbc',
path: '/fwsc/sjlbc',
component: fwscSjlbc,
meta: {
title: '数据列表',
isShowSideBar: false,
hasHome: true,
breadCrumbs: [{ title: '首页', to: '/home' }, { title: '服务中心', to: '/fwsc' }, { title: '碳数据市场', to: '/fwsc/sjsc' }, { title: '数据列表', to: '/fwsc/sjlbc' }],
disableBack: true,
},
}
```
---
## 依赖关系
```
Task 1 (NewNav) ← 无依赖,可最先完成
Task 2 (fwsc引导页) ← 依赖 Task 1
Task 3 (fwsc) ← 依赖 Task 1
Task 4 (xqsc) ← 依赖 Task 1
Task 5 (jrsc) ← 依赖 Task 1
Task 6 (sjsc) ← 依赖 Task 1
Task 7 (sjlbc) ← 依赖 Task 1
Task 8 (路由) ← 依赖 Task 2-7
```
**建议执行顺序:** Task 1 → Task 2-7 并行 → Task 8

View File

@ -0,0 +1,243 @@
# 碳信网服务中心页面开发设计
## 1. 概述
### 1.1 项目背景
`prd/` 目录下的6个Axure原型HTML页面还原为Vue组件集成到 `txw-mhzc-web` 项目中。同时抽取 `home2/index.vue` 中的顶部导航栏为独立公共组件。
### 1.2 页面映射关系
| 页面 | 文件 | 路由 | 说明 |
|------|------|------|------|
| 服务中心 | `views/fwsc/index.vue` | `/fwsc` | 引导页含4个子入口 |
| 碳服务市场 | `views/fwsc/fwsc.vue` | `/fwsc/fwsc` | 对应供需大厅.html |
| 碳需求市场 | `views/fwsc/xqsc.vue` | `/fwsc/xqsc` | 对应需求.html |
| 碳金融市场 | `views/fwsc/jrsc.vue` | `/fwsc/jrsc` | 对应金融.html |
| 碳数据市场 | `views/fwsc/sjsc.vue` | `/fwsc/sjsc` | 对应数据.html |
| 数据列表 | `views/fwsc/sjlbc.vue` | `/fwsc/sjlbc` | 对应数据列表.html |
---
## 2. 新导航组件设计
### 2.1 组件路径
`src/pages/index/components/new-nav/index.vue`
### 2.2 抽取内容
`home2/index.vue``.nav-box` 抽取为独立组件。
### 2.3 功能逻辑(复用原有 nav 的逻辑)
**状态判断:**
- 登录状态:`sessionStorage.getItem('sfdl')`
- 用户信息:`sessionStorage.getItem('yhxx')`
**交互功能:**
- 工作台跳转 → `/view/mhzc/yhzx`
- 登录/注册按钮 → `/view/mhzc/login`
- 用户头像 hover 显示下拉菜单(用户中心、退出登录)
- 消息中心入口
### 2.4 "服务中心"菜单 — 二级下拉结构
```
首页 / 碳证中心 / 服务中心(二级菜单) / 共性能力 / 企业出海 / 行业专题
```
服务中心下拉:
- 碳服务市场 → `/fwsc/fwsc`
- 碳需求市场 → `/fwsc/xqsc`
- 碳金融市场 → `/fwsc/jrsc`
- 碳数据市场 → `/fwsc/sjsc`
---
## 3. 页面设计
### 3.1 服务中心(引导页)`views/fwsc/index.vue`
**布局:**
- 顶部NewNav 导航栏
- Banner区大标题 + 副标题
- 四大服务入口卡片(碳服务市场、碳需求市场、碳金融市场、碳数据市场)
- 底部Footer
**交互:**
- 点击卡片跳转对应二级页面
- 卡片hover有放大效果
### 3.2 碳服务市场 `views/fwsc/fwsc.vue`
**对应原型:** `prd/供需大厅.html`
**布局:**
- 左侧筛选栏(服务类型、服务企业)
- 右侧服务卡片列表2列Grid
- 顶部NewNav + 当前位置导航
- 底部Footer
**卡片内容:**
- 服务标题
- 发布企业 + 地区
- 服务类型标签
- 价格
- 描述
- 操作按钮(联系服务商、收藏)
**筛选功能:**
- 服务类型全部、碳核查服务、碳足迹核算、碳减排技术服务、碳资产管理服务、ESG报告编制、碳交易咨询
- 服务企业:全部、欧冶云商、上海企源科技、上海零数科技、上海链坤数字科技
- 关键词搜索
### 3.3 碳需求市场 `views/fwsc/xqsc.vue`
**对应原型:** `prd/需求.html`
**布局:** 与碳服务市场一致
**卡片内容:**
- 需求标题
- 发布企业
- 需求类型标签
- 预算范围
- 描述
- 操作按钮(联系服务)
- 有效期
### 3.4 碳金融市场 `views/fwsc/jrsc.vue`
**对应原型:** `prd/金融.html`
**布局:**
- 左侧筛选栏(服务类型、服务机构)
- 右侧金融产品卡片2列Grid
**卡片内容:**
- 机构名称Logo
- 产品名称
- 额度、期限、利率
- 操作按钮(立即申请、查看详情)
**筛选功能:**
- 服务类型:全部、绿色信贷、绿色保险
- 服务企业:全部、中国银行、工商银行、农业银行、建设银行、招商银行、中信银行、北京银行、兴业银行、邮政储蓄银行
### 3.5 碳数据市场 `views/fwsc/sjsc.vue`
**对应原型:** `prd/数据.html`
**布局:**
- 左侧筛选栏(数据类型)
- 右侧数据库卡片2列Grid
**卡片内容:**
- 数据库名称
- 描述
- 数据类型标签(公共数据、社会性数据、商业数据)
- 价格标签(免费/付费)
- 操作按钮(查看数据库)
**数据类型筛选:**
- 全部、公共数据、因子库、社会性数据
### 3.6 数据列表 `views/fwsc/sjlbc.vue`
**对应原型:** `prd/数据列表.html`
**布局:**
- 左侧筛选栏
- 右侧数据列表(表格形式)
**列表字段:**
- 数据名称
- 数据类型(公共数据、社会性数据)
- 发布时间/更新时间
- 数据条数
- 浏览量
- 下载量
**筛选项:**
- 价格区间
- 有效期
---
## 4. 路由设计
```javascript
// routes.js 新增
{
name: 'fwsc',
path: '/fwsc',
component: () => import('@/pages/index/views/fwsc/index.vue'),
meta: { title: '服务中心', isShowSideBar: false, hasHome: true }
},
{
name: 'fwsc-fwsc',
path: '/fwsc/fwsc',
component: () => import('@/pages/index/views/fwsc/fwsc.vue'),
meta: { title: '碳服务市场', isShowSideBar: false, hasHome: true }
},
{
name: 'fwsc-xqsc',
path: '/fwsc/xqsc',
component: () => import('@/pages/index/views/fwsc/xqsc.vue'),
meta: { title: '碳需求市场', isShowSideBar: false, hasHome: true }
},
{
name: 'fwsc-jrsc',
path: '/fwsc/jrsc',
component: () => import('@/pages/index/views/fwsc/jrsc.vue'),
meta: { title: '碳金融市场', isShowSideBar: false, hasHome: true }
},
{
name: 'fwsc-sjsc',
path: '/fwsc/sjsc',
component: () => import('@/pages/index/views/fwsc/sjsc.vue'),
meta: { title: '碳数据市场', isShowSideBar: false, hasHome: true }
},
{
name: 'fwsc-sjlbc',
path: '/fwsc/sjlbc',
component: () => import('@/pages/index/views/fwsc/sjlbc.vue'),
meta: { title: '数据列表', isShowSideBar: false, hasHome: true }
}
```
---
## 5. 目录结构
```
src/pages/index/
├── components/
│ └── new-nav/
│ └── index.vue # 新导航组件
└── views/
└── fwsc/
├── index.vue # 服务中心(引导页)
├── fwsc.vue # 碳服务市场
├── xqsc.vue # 碳需求市场
├── jrsc.vue # 碳金融市场
├── sjsc.vue # 碳数据市场
└── sjlbc.vue # 数据列表
```
---
## 6. 技术实现要点
### 6.1 公共组件
- NewNav 组件:复用原有 Nav 的登录状态判断、用户信息获取逻辑
- Footer 组件:直接使用现有 `@/pages/index/components/footer/index.vue`
### 6.2 样式规范
- 使用 TDesign 组件库(`t-button`、`t-input`、`t-select` 等)
- 布局使用 `t-row`、`t-col` 栅格系统
- 卡片hover效果使用 `transform: scale(1.02)` 过渡动画
- 颜色使用项目现有配色(绿色主题 #009a29
### 6.3 筛选栏
- 服务类型、服务企业等筛选项,使用 `t-select` 组件
- 左侧筛选栏宽度固定 220px使用 `position: sticky` 固定
### 6.4 卡片列表
- 使用 CSS Grid 两列布局:`grid-template-columns: repeat(2, 1fr)`
- 卡片间距24px

View File

@ -0,0 +1,35 @@
#!/bin/bash
# 复制 git 变更文件到 change 目录,保留原文件相对路径
set -e
TARGET_DIR="change"
# 创建目标目录
mkdir -p "$TARGET_DIR"
# 获取所有变更文件(包含未跟踪文件)
files=$(git status --porcelain | awk '{print $2}')
if [ -z "$files" ]; then
echo "没有变更文件"
exit 0
fi
count=0
for f in $files; do
# 跳过目录(如 docs/
if [ -d "$f" ]; then
mkdir -p "$TARGET_DIR/$f"
cp -r "$f" "$TARGET_DIR/$f"
echo "$f/"
elif [ -f "$f" ]; then
mkdir -p "$TARGET_DIR/$(dirname "$f")"
cp "$f" "$TARGET_DIR/$f"
echo "$f"
((count++))
fi
done
echo ""
echo "已完成,复制了 $count 个文件到 $TARGET_DIR 目录"

View File

@ -0,0 +1,85 @@
<template>
<div class="breadcrumb-box">
<span class="breadcrumb-text">当前位置</span>
<span class="breadcrumb-link" @click="goHome">首页</span>
<span class="breadcrumb-separator">/</span>
<span class="breadcrumb-link" @click="goService">服务中心</span>
<span v-if="secondPage" class="breadcrumb-separator">/</span>
<span v-if="secondPage" :class="secondLink ? 'breadcrumb-link' : 'breadcrumb-current'" @click="secondLink && goSecond()">
{{ secondPage }}
</span>
<span class="breadcrumb-separator">/</span>
<span class="breadcrumb-current">{{ currentPage }}</span>
</div>
</template>
<script>
export default {
name: 'BreadcrumbNav',
props: {
currentPage: {
type: String,
default: ''
},
secondPage: {
type: String,
default: ''
},
secondLink: {
type: String,
default: ''
}
},
methods: {
goHome() {
this.$router.push('/view/mhzc/home');
},
goService() {
this.$router.push('/fwsc');
},
goSecond() {
if (this.secondLink) {
this.$router.push(this.secondLink);
}
}
}
};
</script>
<style lang="less" scoped>
.breadcrumb-box {
display: flex;
align-items: center;
height: 48px;
padding: 0 20px;
background: #fff;
border-bottom: 1px solid #eee;
}
.breadcrumb-text {
font-size: 14px;
color: #666;
}
.breadcrumb-link {
font-size: 14px;
color: #009a29;
cursor: pointer;
transition: color 0.3s;
&:hover {
color: #007a1f;
}
}
.breadcrumb-separator {
margin: 0 8px;
font-size: 14px;
color: #999;
}
.breadcrumb-current {
font-size: 14px;
color: #333;
}
</style>

View File

@ -12,7 +12,7 @@ export default {
props: {
// logopx40
size: {
type: Number,
type: [Number, String],
default: 35,
},
},

View File

@ -1,70 +1,72 @@
<template>
<div class="nav-box">
<!-- Logo 区域 -->
<div class="logo-box">
<img src="@/pages/index/assets/logo-name.png" @click="goHome" style="cursor: pointer;">
</div>
<!-- 菜单栏 -->
<div class="menu-box">
<!-- 首页 -->
<div class="menu-title meun-title-active" @click="goPage('/view/mhzc/home')">
<img src="@/pages/index/assets/nav-home.png" width="24px" height="24px">
<span> &nbsp;&nbsp;首页</span>
<div>
<div class="nav-box-placeholder"></div>
<div class="nav-box">
<!-- Logo 区域 -->
<div class="logo-box">
<img src="@/pages/index/assets/logo-name.png" @click="goHome" style="cursor: pointer;">
</div>
<!-- 碳证中心 -->
<div class="menu-title" @click="goPage('/view/mhzc/yhzx')">碳证中心</div>
<!-- 服务中心 - 带下拉 -->
<div class="menu-title service-center" @mouseenter="showServiceDropdown = true" @mouseleave="showServiceDropdown = false">
<span>服务中心</span>
<div class="dropdown-arrow"></div>
<!-- 下拉菜单 -->
<div class="dropdown-menu service-dropdown" v-show="showServiceDropdown">
<div class="dropdown-item" @click.stop="goPage('/fwsc/fwsc')">碳服务市场</div>
<div class="dropdown-item" @click.stop="goPage('/fwsc/xqsc')">碳需求市场</div>
<div class="dropdown-item" @click.stop="goPage('/fwsc/jrsc')">碳金融市场</div>
<div class="dropdown-item" @click.stop="goPage('/fwsc/sjsc')">碳数据市场</div>
<!-- 菜单栏 -->
<div class="menu-box">
<!-- 首页 -->
<div class="menu-title meun-title-active" @click="goPage('/view/mhzc/home')">
<img src="@/pages/index/assets/nav-home.png" width="24px" height="24px">
<span> &nbsp;&nbsp;首页</span>
</div>
</div>
<!-- 共性能力 -->
<div class="menu-title" @click="goPage('/view/gxzx/gxnlpt')">共性能力</div>
<!-- 企业出海 -->
<div class="menu-title" @click="goPage('/view/mhzc/qych')">企业出海</div>
<!-- 行业专题 -->
<div class="menu-title" @click="goPage('/view/mhzc/hyzt')">行业专题</div>
</div>
<!-- 右侧操作区 -->
<div class="option-box">
<!-- 工作台 -->
<div class="gzt" @click="goPage('/view/mhzc/yhzx')">
<img src="@/pages/index/assets/home-gzt-icon.png" />
<span> 工作台</span>
</div>
<!-- 已登录状态 -->
<template v-if="isdlwc">
<!-- 用户头像下拉 -->
<div class="user-avatar-box" @mouseenter="showUserDropdown = true" @mouseleave="showUserDropdown = false">
<img class="user-avatar" :src="userAvatar" alt="用户头像">
<div class="dropdown-menu user-dropdown" v-show="showUserDropdown">
<div class="dropdown-item" @click.stop="goUserCenter">用户中心</div>
<div class="dropdown-item" @click.stop="handleLogout">退出登录</div>
<!-- 碳证中心 -->
<div class="menu-title" @click="goPage('/view/mhzc/yhzx')">碳证中心</div>
<!-- 服务中心 - 带下拉 -->
<div class="menu-title service-center" @mouseenter="showServiceDropdown = true" @mouseleave="showServiceDropdown = false">
<span @click.stop="goPage('/fwsc')">服务中心</span>
<!-- 下拉菜单 -->
<div class="dropdown-menu service-dropdown" v-show="showServiceDropdown">
<div class="dropdown-item" @click.stop="goPage('/fwsc/fwsc')">碳服务市场</div>
<div class="dropdown-item" @click.stop="goPage('/fwsc/xqsc')">碳需求市场</div>
<div class="dropdown-item" @click.stop="goPage('/fwsc/jrsc')">碳金融市场</div>
<div class="dropdown-item" @click.stop="goPage('/fwsc/sjsc')">碳数据市场</div>
</div>
</div>
</template>
<!-- 未登录状态 -->
<template v-else>
<div style="margin-left:20px; cursor: pointer;">激活</div>
<div>|</div>
<div style="cursor: pointer;" @click="goLogin">登录</div>
</template>
<!-- 共性能力 -->
<div class="menu-title" @click="goPage('/view/gxzx/gxnlpt')">共性能力</div>
<!-- 企业出海 -->
<div class="menu-title" @click="goPage('/view/mhzc/qych')">企业出海</div>
<!-- 行业专题 -->
<div class="menu-title" @click="goPage('/view/mhzc/hyzt')">行业专题</div>
</div>
<!-- 右侧操作区 -->
<div class="option-box">
<!-- 工作台 -->
<div class="gzt" @click="goPage('/view/mhzc/yhzx')">
<img src="@/pages/index/assets/home-gzt-icon.png" />
<span> 工作台</span>
</div>
<!-- 已登录状态 -->
<template v-if="isdlwc">
<!-- 用户头像下拉 -->
<div class="user-avatar-box" @mouseenter="showUserDropdown = true" @mouseleave="showUserDropdown = false">
<img class="user-avatar" :src="userAvatar" alt="用户头像">
<div class="dropdown-menu user-dropdown" v-show="showUserDropdown">
<div class="dropdown-item" @click.stop="goUserCenter">用户中心</div>
<div class="dropdown-item" @click.stop="handleLogout">退出登录</div>
</div>
</div>
</template>
<!-- 未登录状态 -->
<template v-else>
<div style="margin-left:20px; cursor: pointer;">激活</div>
<div>|</div>
<div style="cursor: pointer;" @click="goLogin">登录</div>
</template>
</div>
</div>
</div>
</template>
@ -131,29 +133,36 @@ export default {
</script>
<style scoped lang="less">
.nav-box-placeholder {
width: 100%;
height: 64px;
}
.nav-box {
position: fixed;
top: 0;
left: 0;
z-index: 9999;
display: flex;
width: 100%;
height: 64px;
min-width: 1920px;
max-width: 100%;
padding: 0 20px;
margin: 0 auto;
overflow: visible;
color: #fff;
background: #009a29;
justify-content: space-around;
box-sizing: border-box;
justify-content: space-between;
align-items: center;
}
.logo-box {
width: 25%;
text-align: center;
flex: 0 0 auto;
}
.option-box {
display: flex;
width: 25%;
flex: 0 0 auto;
font-family: PingFang SC;
font-size: 16px;
font-style: regular;
@ -170,19 +179,20 @@ export default {
display: flex;
padding: 10px;
text-align: center;
cursor: pointer;
background: rgba(255, 255, 255, 0.3);
border-radius: 6px;
justify-content: space-around;
align-items: center;
gap: 10px;
cursor: pointer;
}
.menu-box {
display: flex;
width: 50%;
overflow: hidden;
font-size: 16px;
text-align: center;
flex: 1;
justify-content: center;
gap: 10px;
}
@ -190,11 +200,14 @@ export default {
.menu-title {
position: relative;
display: flex;
width: 96px;
height: 64px;
min-width: 80px;
padding: 0 8px;
overflow: visible;
line-height: 24px;
text-align: center;
cursor: pointer;
flex: 0 0 auto;
justify-content: center;
align-items: center;
}
@ -207,8 +220,8 @@ export default {
//
.service-center {
.dropdown-arrow {
font-size: 10px;
margin-left: 4px;
font-size: 10px;
}
}
@ -217,29 +230,29 @@ export default {
position: absolute;
top: 64px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
min-width: 140px;
overflow: hidden;
background: #fff;
border-radius: 4px;
transform: translateX(-50%);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
overflow: hidden;
}
.dropdown-item {
width: 100%;
height: 40px;
font-size: 14px;
line-height: 40px;
color: #333;
font-size: 14px;
text-align: center;
cursor: pointer;
transition: background 0.3s;
}
.dropdown-item:hover {
background: #e8f5e9;
color: #009a29;
background: #e8f5e9;
}
//
@ -257,8 +270,52 @@ export default {
.user-dropdown {
right: 0;
left: auto;
transform: none;
min-width: 130px;
transform: none;
}
}
//
@media screen and (max-width: 1200px) {
.nav-box {
padding: 0 10px;
}
.menu-box {
gap: 5px;
}
.menu-title {
min-width: 70px;
padding: 0 5px;
font-size: 14px;
}
}
@media screen and (max-width: 992px) {
.menu-title {
min-width: 60px;
font-size: 13px;
}
.option-box .gzt {
padding: 8px;
font-size: 14px;
}
}
@media screen and (max-width: 768px) {
.nav-box {
justify-content: space-between;
padding: 0 10px;
}
.menu-box {
display: none;
}
.option-box .gzt span {
display: none;
}
}
</style>

View File

@ -18,6 +18,9 @@ const router = new VueRouter({
mode: 'history',
base: `${window.STATIC_ENV_CONFIG.ROUTER_PREFIX}/`,
routes: [...routes],
scrollBehavior(_to, _from, _savedPosition) {
return { x: 0, y: 0 };
},
});
router.beforeEach((to, from, next) => {

View File

@ -167,7 +167,7 @@ export default [
component: hyzt,
},
{
name: 'fwsc',
name: 'fwsc-fwsc',
path: '/fwsc/fwsc',
component: fwsc,
meta: {

View File

@ -0,0 +1,289 @@
<template>
<div class="fwsc-container">
<NewNav />
<!-- 面包屑导航 -->
<BreadcrumbNav currentPage="碳服务市场" />
<main class="fwsc-main">
<div class="内容区域">
<!-- 左侧筛选栏 -->
<aside class="筛选栏">
<div class="筛选栏内容">
<!-- 服务类型 -->
<div class="筛选项">
<div class="筛选标签">服务类型</div>
<t-select v-model="筛选.服务类型" :options="服务类型选项" placeholder="请选择服务类型" clearable />
</div>
<!-- 服务企业 -->
<div class="筛选项">
<div class="筛选标签">服务企业</div>
<t-select v-model="筛选.服务企业" :options="服务企业选项" placeholder="请选择服务企业" clearable />
</div>
<!-- 关键词搜索 -->
<div class="筛选项">
<div class="筛选标签">关键词</div>
<t-input v-model="筛选.关键词" placeholder="请输入关键词" />
</div>
<t-button theme="primary" class="搜索按钮" @click="搜索">搜索</t-button>
</div>
</aside>
<!-- 右侧服务卡片列表 -->
<div class="卡片列表">
<div
v-for="(card, index) in 卡片数据"
:key="index"
class="服务卡片"
>
<!-- 卡片内容 -->
<div class="卡片头部">
<h3 class="卡片标题">{{ card.标题 }}</h3>
<p class="卡片企业">{{ card.企业 }}</p>
</div>
<div class="卡片标签">
<span v-for="(tag, tagIndex) in card.标签" :key="tagIndex" class="标签">
{{ tag }}
</span>
</div>
<div class="卡片价格" :style="{ color: '#D25F00' }">
{{ card.价格 }}
</div>
<p class="卡片描述">{{ card.描述 }}</p>
<div class="卡片操作">
<t-button theme="primary" class="操作按钮">联系服务商</t-button>
<t-button variant="outline" class="操作按钮">收藏</t-button>
</div>
<!-- 分隔线 -->
<div v-if="index < 卡片数据.length - 1" class="卡片分隔线"></div>
</div>
</div>
</div>
</main>
<Footer />
</div>
</template>
<script>
import NewNav from '@/pages/index/components/new-nav/index.vue';
import Footer from '@/pages/index/components/footer/index.vue';
import BreadcrumbNav from '@/pages/index/components/breadcrumb/index.vue';
export default {
name: 'FwscPage',
components: {
NewNav,
Footer,
BreadcrumbNav,
},
data() {
return {
筛选: {
服务类型: '',
服务企业: '',
关键词: '',
},
服务类型选项: [
{ label: '全部', value: '' },
{ label: '碳核查服务', value: '碳核查服务' },
{ label: '碳足迹核算', value: '碳足迹核算' },
{ label: '碳减排技术服务', value: '碳减排技术服务' },
{ label: '碳资产管理服务', value: '碳资产管理服务' },
{ label: 'ESG报告编制', value: 'ESG报告编制' },
{ label: '碳交易咨询', value: '碳交易咨询' },
],
服务企业选项: [
{ label: '全部', value: '' },
{ label: '欧冶云商', value: '欧冶云商' },
{ label: '上海企源科技', value: '上海企源科技' },
{ label: '上海零数科技', value: '上海零数科技' },
{ label: '上海链坤数字科技', value: '上海链坤数字科技' },
],
卡片数据: [
{
标题: '本司提供碳相关服务寻找有需求的企业客户',
企业: '上海链坤数字科技有限公司 · 辽宁省',
标签: ['ESG报告编制', '碳减排技术服务'],
价格: '¥ 10,000.00 /次',
描述: '我们为参与碳市场的企业提供全方位的交易策略与风险管理咨询。服务内容包括解读碳市场政策规则、分析碳价走势、制定交易策略助…',
},
{
标题: '为碳超标企业提供中和方案',
企业: '上海零数科技有限公司 · 全国',
标签: ['碳核查服务', '碳足迹核算'],
价格: '¥ 5,000,000.00 /年',
描述: '本服务依据国际国内标准如ISO 14064-1对企业的温室气体排放报告进行独立的第三方审定与核查。我们通过文件评审、现场访…',
},
{
标题: '',
企业: '欧冶云商 · 北京市',
标签: ['ESG报告编制'],
价格: '¥ 1,500.00 /年',
描述: '我们协助企业系统梳理其在环境、社会和治理ESG领域的表现并按照GRI、SASB、TCFD等主流框架编制专业的ESG报告。服…',
},
{
标题: '碳足迹核算、碳减排技术供给',
企业: '上海企源科技股份有限公司 · 全国',
标签: ['碳减排技术服务', '碳足迹核算'],
价格: '面议',
描述: '本服务依据国际国内标准如ISO 14064-1对企业的温室气体排放报告进行独立的第三方审定与核查。我们通过文件评审、现场访…',
},
],
};
},
methods: {
搜索() {
console.log('搜索条件:', this.筛选);
},
},
};
</script>
<style scoped>
.fwsc-container {
min-height: 100vh;
background: #f5f5f5;
}
.fwsc-main {
width: 100%;
max-width: 1400px;
padding: 20px;
margin: 0 auto;
}
.内容区域 {
display: flex;
gap: 20px;
}
/* 左侧筛选栏 */
.筛选栏 {
position: sticky;
top: 104px;
width: 220px;
height: fit-content;
flex-shrink: 0;
}
.筛选栏内容 {
padding: 20px;
background: #fff;
border-radius: 8px;
}
.筛选项 {
margin-bottom: 20px;
}
.筛选标签 {
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: #333;
}
.搜索按钮 {
width: 100%;
margin-top: 10px;
}
/* 右侧卡片列表 */
.卡片列表 {
flex: 1;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.服务卡片 {
position: relative;
padding: 20px;
background: #fff;
border-radius: 8px;
transition: transform 0.2s;
}
.服务卡片:hover {
transform: scale(1.02);
}
.卡片头部 {
margin-bottom: 12px;
}
.卡片标题 {
margin: 0 0 8px;
font-size: 16px;
font-weight: 600;
line-height: 1.4;
color: #333;
}
.卡片企业 {
margin: 0;
font-size: 13px;
color: #666;
}
.卡片标签 {
display: flex;
gap: 8px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.标签 {
display: inline-block;
padding: 4px 12px;
font-size: 12px;
color: #0052d9;
background: #f0f5ff;
border-radius: 4px;
}
.卡片价格 {
margin-bottom: 12px;
font-size: 20px;
font-weight: 700;
}
.卡片描述 {
display: box;
margin-bottom: 16px;
overflow: hidden;
font-size: 13px;
line-height: 1.6;
color: #666;
text-overflow: ellipsis;
box-orient: vertical;
-webkit-line-clamp: 2;
}
.卡片操作 {
display: flex;
gap: 12px;
}
.操作按钮 {
flex: 1;
}
.卡片分隔线 {
position: absolute;
right: 20px;
bottom: 0;
left: 20px;
height: 1px;
background: #eee;
}
</style>

View File

@ -108,7 +108,7 @@ export default {
}
.main-content {
padding-top: 64px;
// padding-top: 64px;
}
//
@ -210,10 +210,10 @@ export default {
}
.service-card-icon {
display: flex;
width: 64px;
height: 64px;
margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: center;

View File

@ -3,17 +3,11 @@
<!-- 顶部导航 -->
<new-nav />
<!-- 面包屑导航 -->
<BreadcrumbNav currentPage="碳金融市场" />
<!-- 页面主体 -->
<div class="jrsc-main">
<!-- 当前位置 -->
<div class="current-position">
<span>首页</span>
<span class="separator">/</span>
<span>服务中心</span>
<span class="separator">/</span>
<span class="current">碳金融市场</span>
</div>
<!-- 内容区域 -->
<div class="content-wrapper">
<!-- 左侧筛选栏 -->
@ -133,12 +127,14 @@
<script>
import NewNav from '@/pages/index/components/new-nav/index.vue';
import FooterComponent from '@/pages/index/components/footer/index.vue';
import BreadcrumbNav from '@/pages/index/components/breadcrumb/index.vue';
export default {
name: 'JrscPage',
components: {
NewNav,
FooterComponent,
BreadcrumbNav,
},
data() {
return {
@ -275,26 +271,10 @@ export default {
.jrsc-main {
max-width: 1400px;
padding-top: 84px;
padding: 20px;
margin: 0 auto;
}
//
.current-position {
padding: 20px 0;
font-size: 14px;
color: #666;
.separator {
margin: 0 8px;
color: #ccc;
}
.current {
color: #009a29;
}
}
//
.content-wrapper {
display: flex;

View File

@ -3,132 +3,125 @@
<!-- 顶部导航 -->
<NewNav />
<div class="main-content">
<!-- 当前位置 -->
<div class="current-position">
<span>当前位置</span>
<span class="position-item" @click="goHome">首页</span>
<span class="separator">/</span>
<span class="position-item" @click="goService">服务中心</span>
<span class="separator">/</span>
<span class="position-item" @click="goDataMarket">碳数据市场</span>
<span class="separator">/</span>
<span class="position-current">数据列表</span>
</div>
<!-- 面包屑导航 -->
<BreadcrumbNav
:currentPage="currentPageName"
secondPage="碳数据市场"
secondLink="/fwsc/sjsc"
/>
<!-- 主内容区 -->
<div class="content-wrapper">
<!-- 左侧筛选栏 -->
<div class="filter-sidebar">
<div class="filter-box">
<!-- 价格区间 -->
<div class="filter-section">
<div class="filter-title">价格区间</div>
<div class="price-range">
<input type="text" v-model="filterParams.priceMin" placeholder="最低" class="price-input" />
<span class="price-separator">-</span>
<input type="text" v-model="filterParams.priceMax" placeholder="最高" class="price-input" />
</div>
</div>
<!-- 有效期 -->
<div class="filter-section">
<div class="filter-title">有效期</div>
<div class="date-picker-box">
<input type="date" v-model="filterParams.validDate" class="date-input" />
</div>
</div>
<!-- 筛选按钮 -->
<div class="filter-buttons">
<button class="btn-reset" @click="resetFilter">重置</button>
<button class="btn-confirm" @click="confirmFilter">确定</button>
</div>
</div>
</div>
<!-- 右侧数据列表 -->
<div class="data-list">
<!-- 表头 -->
<div class="table-header">
<div class="th th-type" @click="sortBy('type')">
数据类型
<span class="sort-icon">
<span class="sort-up" :class="{ active: sortField === 'type' && sortOrder === 'asc' }"></span>
<span class="sort-down" :class="{ active: sortField === 'type' && sortOrder === 'desc' }"></span>
</span>
</div>
<div class="th th-name">名称</div>
<div class="th th-time" @click="sortBy('time')">
发布时间/更新时间
<span class="sort-icon">
<span class="sort-up" :class="{ active: sortField === 'time' && sortOrder === 'asc' }"></span>
<span class="sort-down" :class="{ active: sortField === 'time' && sortOrder === 'desc' }"></span>
</span>
</div>
<div class="th th-count" @click="sortBy('count')">
数据条数
<span class="sort-icon">
<span class="sort-up" :class="{ active: sortField === 'count' && sortOrder === 'asc' }"></span>
<span class="sort-down" :class="{ active: sortField === 'count' && sortOrder === 'desc' }"></span>
</span>
</div>
<div class="th th-views" @click="sortBy('views')">
浏览量
<span class="sort-icon">
<span class="sort-up" :class="{ active: sortField === 'views' && sortOrder === 'asc' }"></span>
<span class="sort-down" :class="{ active: sortField === 'views' && sortOrder === 'desc' }"></span>
</span>
</div>
<div class="th th-downloads" @click="sortBy('downloads')">
下载量
<span class="sort-icon">
<span class="sort-up" :class="{ active: sortField === 'downloads' && sortOrder === 'asc' }"></span>
<span class="sort-down" :class="{ active: sortField === 'downloads' && sortOrder === 'desc' }"></span>
</span>
<!-- 主内容区 -->
<div class="content-wrapper">
<!-- 左侧筛选栏 -->
<div class="filter-sidebar">
<div class="filter-box">
<!-- 价格区间 -->
<div class="filter-section">
<div class="filter-title">价格区间</div>
<div class="price-range">
<input type="text" v-model="filterParams.priceMin" placeholder="最低" class="price-input price-min" />
<span class="price-separator">-</span>
<input type="text" v-model="filterParams.priceMax" placeholder="最高" class="price-input price-max" />
</div>
</div>
<!-- 表格数据 -->
<div class="table-body">
<div
v-for="(item, index) in sortedDataList"
:key="index"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td td-type">
<span class="type-tag" :class="item.type === '公共数据' ? 'tag-green' : 'tag-blue'">
{{ item.type }}
</span>
</div>
<div class="td td-name" :title="item.name">{{ item.name }}</div>
<div class="td td-time">
<div class="time-info">
<span>发布时间{{ item.publishTime }}</span>
<span v-if="item.updateTime">更新时间{{ item.updateTime }}</span>
</div>
</div>
<div class="td td-count">{{ item.dataCount }}</div>
<div class="td td-views">{{ item.views }}次浏览</div>
<div class="td td-downloads">{{ item.downloads }}次下载</div>
<!-- 有效期 -->
<div class="filter-section">
<div class="filter-title">有效期</div>
<div class="date-picker-box">
<input type="date" v-model="filterParams.validDate" class="date-input" />
</div>
</div>
<!-- 筛选按钮 -->
<div class="filter-buttons">
<button class="btn-reset" @click="resetFilter">重置</button>
<button class="btn-confirm" @click="confirmFilter">确定</button>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<span class="page-btn">上一页</span>
<span class="page-btn active">1</span>
<span class="page-btn">2</span>
<span class="page-btn">3</span>
<span class="page-btn">4</span>
<span class="page-btn">5</span>
<span class="page-btn">下一页</span>
<!-- 右侧数据列表 -->
<div class="data-list">
<!-- 表头 -->
<div class="table-header">
<div class="th th-type" @click="sortBy('type')">
数据类型
<span class="sort-icon">
<span class="sort-up" :class="{ active: sortField === 'type' && sortOrder === 'asc' }"></span>
<span class="sort-down" :class="{ active: sortField === 'type' && sortOrder === 'desc' }"></span>
</span>
</div>
<div class="th th-name">名称</div>
<div class="th th-time" @click="sortBy('time')">
发布时间/更新时间
<span class="sort-icon">
<span class="sort-up" :class="{ active: sortField === 'time' && sortOrder === 'asc' }"></span>
<span class="sort-down" :class="{ active: sortField === 'time' && sortOrder === 'desc' }"></span>
</span>
</div>
<div class="th th-count" @click="sortBy('count')">
数据条数
<span class="sort-icon">
<span class="sort-up" :class="{ active: sortField === 'count' && sortOrder === 'asc' }"></span>
<span class="sort-down" :class="{ active: sortField === 'count' && sortOrder === 'desc' }"></span>
</span>
</div>
<div class="th th-views" @click="sortBy('views')">
浏览量
<span class="sort-icon">
<span class="sort-up" :class="{ active: sortField === 'views' && sortOrder === 'asc' }"></span>
<span class="sort-down" :class="{ active: sortField === 'views' && sortOrder === 'desc' }"></span>
</span>
</div>
<div class="th th-downloads" @click="sortBy('downloads')">
下载量
<span class="sort-icon">
<span class="sort-up" :class="{ active: sortField === 'downloads' && sortOrder === 'asc' }"></span>
<span class="sort-down" :class="{ active: sortField === 'downloads' && sortOrder === 'desc' }"></span>
</span>
</div>
</div>
<!-- 表格数据 -->
<div class="table-body">
<div
v-for="(item, index) in sortedDataList"
:key="index"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td td-type">
<span class="type-tag" :class="item.type === '公共数据' ? 'tag-green' : 'tag-blue'">
{{ item.type }}
</span>
</div>
<div class="td td-name" :title="item.name">{{ item.name }}</div>
<div class="td td-time">
<div class="time-info">
<span>发布时间{{ item.publishTime }}</span>
<span v-if="item.updateTime">更新时间{{ item.updateTime }}</span>
</div>
</div>
<div class="td td-count">{{ item.dataCount }}</div>
<div class="td td-views">{{ item.views }}次浏览</div>
<div class="td td-downloads">{{ item.downloads }}次下载</div>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<span class="page-btn">上一页</span>
<span class="page-btn active">1</span>
<span class="page-btn">2</span>
<span class="page-btn">3</span>
<span class="page-btn">4</span>
<span class="page-btn">5</span>
<span class="page-btn">下一页</span>
</div>
<!-- 底部 -->
<Footer />
</div>
@ -137,15 +130,19 @@
<script>
import NewNav from '@/pages/index/components/new-nav/index.vue';
import Footer from '@/pages/index/components/footer/index.vue';
import BreadcrumbNav from '@/pages/index/components/breadcrumb/index.vue';
export default {
name: 'SjlbcPage',
components: {
NewNav,
Footer,
BreadcrumbNav,
},
data() {
return {
currentPageName: '数据列表',
marketId: '',
filterParams: {
priceMin: '',
priceMax: '',
@ -229,6 +226,9 @@ export default {
],
};
},
created() {
this.marketId = this.$route.query.id || '';
},
computed: {
sortedDataList() {
if (!this.sortField) {
@ -279,15 +279,6 @@ export default {
confirmFilter() {
console.log('筛选条件:', this.filterParams);
},
goHome() {
window.location.href = '/view/mhzc/home';
},
goService() {
window.location.href = '/view/mhzc/fwsc/fwsc';
},
goDataMarket() {
window.location.href = '/view/mhzc/fwsc/sjsc';
},
},
};
</script>
@ -298,66 +289,36 @@ export default {
background: #f5f5f5;
}
.main-content {
width: 1920px;
padding-top: 64px;
margin: 0 auto;
}
//
.current-position {
width: 100%;
height: 48px;
padding: 0 60px;
line-height: 48px;
background: #fff;
box-sizing: border-box;
.position-item {
cursor: pointer;
&:hover {
color: #009a29;
}
}
.separator {
margin: 0 8px;
color: #ccc;
}
.position-current {
color: #009a29;
}
}
//
.content-wrapper {
display: flex;
padding: 20px 60px;
max-width: 1400px;
padding: 20px;
margin: 0 auto;
gap: 20px;
}
//
.filter-sidebar {
width: 220px;
width: 200px;
flex-shrink: 0;
}
.filter-box {
position: sticky;
top: 84px;
padding: 20px;
top: 104px;
padding: 16px;
background: #fff;
border-radius: 8px;
}
.filter-section {
margin-bottom: 24px;
margin-bottom: 20px;
}
.filter-title {
margin-bottom: 12px;
font-size: 16px;
font-size: 14px;
font-weight: 600;
color: #333;
}
@ -365,13 +326,12 @@ export default {
.price-range {
display: flex;
align-items: center;
gap: 8px;
gap: 6px;
}
.price-input {
flex: 1;
height: 36px;
padding: 0 12px;
height: 32px;
padding: 0 6px;
border: 1px solid #ddd;
border-radius: 4px;
outline: none;
@ -382,15 +342,21 @@ export default {
}
}
.price-min,
.price-max {
width: 80px;
}
.price-separator {
flex-shrink: 0;
color: #999;
}
.date-picker-box {
.date-input {
width: 100%;
height: 36px;
padding: 0 12px;
height: 32px;
padding: 0 8px;
border: 1px solid #ddd;
border-radius: 4px;
outline: none;

View File

@ -1,19 +1,12 @@
<template>
<div class="sjsc-page">
<NewNav />
<div class="main-content">
<!-- 当前位置导航 -->
<div class="breadcrumb-box">
<span class="breadcrumb-text">当前位置</span>
<span class="breadcrumb-link" @click="goHome">首页</span>
<span class="breadcrumb-separator">/</span>
<span class="breadcrumb-link" @click="goService">服务中心</span>
<span class="breadcrumb-separator">/</span>
<span class="breadcrumb-current">碳数据市场</span>
</div>
<!-- 页面主体 -->
<div class="page-body">
<!-- 面包屑导航 -->
<BreadcrumbNav currentPage="碳数据市场" />
<!-- 页面主体 -->
<div class="page-body">
<!-- 左侧筛选栏 -->
<div class="sidebar">
<div class="sidebar-title">数据类型</div>
@ -56,7 +49,7 @@
<span class="price-tag" :class="card.price === '免费' ? 'free' : 'paid'">
{{ card.price }}
</span>
<t-button theme="primary" size="small" variant="outline">
<t-button theme="primary" size="small" variant="outline" @click="goToDataList(card.id)">
查看数据库
</t-button>
</div>
@ -64,7 +57,6 @@
</div>
</div>
</div>
</div>
<Footer />
</div>
@ -73,12 +65,14 @@
<script>
import NewNav from '@/pages/index/components/new-nav/index.vue';
import Footer from '@/pages/index/components/footer/index.vue';
import BreadcrumbNav from '@/pages/index/components/breadcrumb/index.vue';
export default {
name: 'SjscPage',
components: {
NewNav,
Footer
Footer,
BreadcrumbNav,
},
data() {
return {
@ -150,15 +144,12 @@ export default {
}
},
methods: {
goHome() {
this.$router.push('/view/mhzc/home');
},
goService() {
this.$router.push('/fwsc/fwsc');
},
handleTypeChange(value) {
this.selectedType = value;
},
goToDataList(id) {
this.$router.push({ path: '/fwsc/sjlbc', query: { id } });
},
getTagClass(tag) {
const classMap = {
'公共数据': 'tag-green',
@ -178,58 +169,19 @@ export default {
background: #f5f5f5;
}
.main-content {
padding-top: 64px;
}
//
.breadcrumb-box {
display: flex;
align-items: center;
height: 48px;
padding: 0 200px;
background: #fff;
border-bottom: 1px solid #eee;
}
.breadcrumb-text {
font-size: 14px;
color: #666;
}
.breadcrumb-link {
font-size: 14px;
color: #009a29;
cursor: pointer;
transition: color 0.3s;
&:hover {
color: #007a1f;
}
}
.breadcrumb-separator {
margin: 0 8px;
font-size: 14px;
color: #999;
}
.breadcrumb-current {
font-size: 14px;
color: #333;
}
//
.page-body {
display: flex;
padding: 24px 200px;
gap: 24px;
max-width: 1400px;
padding: 20px;
margin: 0 auto;
gap: 20px;
}
//
.sidebar {
position: sticky;
top: 88px;
top: 104px;
width: 220px;
height: fit-content;
min-width: 220px;

View File

@ -0,0 +1,462 @@
<!--
* @Descripttion: 碳需求市场页面
* @Version: 1.0
* @Author: cby
* @Date: 2026-04-03
-->
<template>
<div>
<NewNav />
<div class="xqsc-page">
<!-- 面包屑导航 -->
<BreadcrumbNav currentPage="碳需求市场" />
<!-- 主内容区 -->
<div class="content-wrapper">
<!-- 左侧筛选栏 -->
<div class="filter-sidebar">
<!-- 搜索框 -->
<div class="filter-search">
<t-input v-model="searchKeyword" placeholder="请输入关键词" @enter="handleSearch"></t-input>
<t-button theme="primary" @click="handleSearch">搜索</t-button>
</div>
<!-- 服务类型 -->
<div class="filter-section">
<div class="filter-title">服务类型</div>
<div class="filter-options">
<div
v-for="item in serviceTypeList"
:key="item.value"
:class="['filter-option', { active: selectedServiceType === item.value }]"
@click="handleServiceTypeSelect(item.value)"
>
{{ item.label }}
</div>
</div>
</div>
<!-- 服务企业 -->
<div class="filter-section">
<div class="filter-title">服务企业</div>
<div class="filter-options">
<div
v-for="item in companyList"
:key="item.value"
:class="['filter-option', { active: selectedCompany === item.value }]"
@click="handleCompanySelect(item.value)"
>
{{ item.label }}
</div>
</div>
</div>
</div>
<!-- 右侧需求卡片列表 -->
<div class="demand-list">
<div class="list-header">
<span class="list-title">碳需求市场</span>
<span class="list-count"> {{ demandList.length }} 条需求</span>
</div>
<div class="demand-grid">
<div
v-for="(item, index) in demandList"
:key="index"
class="demand-card"
>
<!-- 卡片头部 - 标题 -->
<div :class="['demand-title', { 'urgent': item.urgent }]">
{{ item.title }}
</div>
<!-- 发布企业 -->
<div class="demand-company">
{{ item.company }}
</div>
<!-- 标签 -->
<div class="demand-tags">
<span v-for="(tag, tagIndex) in item.tags" :key="tagIndex" class="demand-tag">
{{ tag }}
</span>
</div>
<!-- 预算 -->
<div class="demand-budget">
<span class="budget-label">预算</span>
<span class="budget-value">{{ item.budget }}</span>
</div>
<!-- 描述 -->
<div class="demand-desc">
{{ item.desc }}
</div>
<!-- 有效期 -->
<div class="demand-validity" v-if="item.validity">
<svg class="calendar-icon" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5V3.5C13.5 3.22386 13.2761 3 13 3H12.5V1.5C12.5 1.22386 12.2761 1 12 1C11.7239 1 11.5 1.22386 11.5 1.5V3H4.5V1.5C4.5 1.22386 4.27614 1 4 1C3.72386 1 3.5 1.22386 3.5 1.5V3H3C2.72386 3 2.5 3.22386 2.5 3.5V13.5C2.5 13.7761 2.72386 14 3 14H13C13.2761 14 13.5 13.7761 13.5 13.5V13.5" stroke="#666" stroke-width="1.2"/>
<path d="M5.5 6H10.5M5.5 8.5H10.5M5.5 11H8.5" stroke="#666" stroke-width="1.2" stroke-linecap="round"/>
</svg>
<span>有效期至{{ item.validity }}</span>
</div>
<!-- 操作按钮 -->
<div class="demand-action">
<t-button theme="primary" @click="handleContact(item)">联系服务</t-button>
</div>
</div>
</div>
</div>
</div>
<!-- 联系服务弹窗 -->
<t-dialog
:closeOnOverlayClick="false"
header="联系服务"
:visible.sync="contactDialogVisible"
@confirm="handleConfirm"
@cancel="handleCancel"
:onClose="handleClose"
class="global-dialog"
attach="body"
>
<div class="dialog-line">
<div class="dialog-line-title">联系人</div>
<div class="dialog-line-text">{{ contactData.lxr }}</div>
</div>
<div class="dialog-line">
<div class="dialog-line-title">联系电话</div>
<div class="dialog-line-text">{{ contactData.lxdh }}</div>
</div>
<div class="dialog-line">
<div class="dialog-line-title">电子邮箱</div>
<div class="dialog-line-text">{{ contactData.email }}</div>
</div>
<div class="dialog-line">
<div class="dialog-line-title" style="width:95px">留言内容</div>
<t-textarea v-model="messageContent" :autosize="{ minRows: 3 }"></t-textarea>
</div>
</t-dialog>
<Footer />
</div>
</div>
</template>
<script>
import NewNav from '@/pages/index/components/new-nav/index.vue';
import Footer from '@/pages/index/components/footer/index.vue';
import BreadcrumbNav from '@/pages/index/components/breadcrumb/index.vue';
export default {
name: 'Xqsc',
components: {
NewNav,
Footer,
BreadcrumbNav,
},
data() {
return {
searchKeyword: '',
selectedServiceType: '',
selectedCompany: '',
contactDialogVisible: false,
contactData: {
lxr: '张经理',
lxdh: '021-12345678',
email: 'service@example.com'
},
messageContent: '',
serviceTypeList: [
{ label: '全部', value: '' },
{ label: '碳核查服务', value: 'carbon_check' },
{ label: '碳足迹核算', value: 'carbon_footprint' },
{ label: '碳减排技术服务', value: 'carbon_reduction' },
{ label: '碳资产管理服务', value: 'carbon_asset' },
{ label: 'ESG报告编制', value: 'esg_report' },
{ label: '碳交易咨询', value: 'carbon_trade' }
],
companyList: [
{ label: '全部', value: '' },
{ label: '欧冶云商', value: 'ouye' },
{ label: '上海企源科技股份有限公司', value: 'qiyuan' },
{ label: '上海零数科技有限公司', value: 'lingshu' },
{ label: '上海链坤数字科技有限公司', value: 'linkun' }
],
demandList: [
{
title: '碳核查服务需求',
company: '上海链坤数字科技有限公司',
tags: ['ESG报告编制', '碳减排技术服务'],
budget: '¥ 50-100 万元',
desc: '我们为参与碳市场的企业提供全方位的交易策略与风险管理咨询。服务内容包括解读碳市场政策规则、分析碳价走势、制定交易策略...',
validity: '2026-04-24',
urgent: false
},
{
title: '【碳服务需求!急急急!】',
company: '上海零数科技有限公司',
tags: ['碳核查服务', '碳足迹核算'],
budget: '¥ 50-100 万元',
desc: '本服务依据国际国内标准如ISO 14064-1对企业的温室气体排放报告进行独立的第三方审定与核查。我们通过文件评审、现场访...',
validity: '2026-04-24',
urgent: true
},
{
title: '减碳中和需求',
company: '上海链坤数字科技有限公司',
tags: ['ESG报告编制', '碳减排技术服务'],
budget: '¥ 50-100 万元',
desc: '我们为参与碳市场的企业提供全方位的交易策略与风险管理咨询。服务内容包括解读碳市场政策规则、分析碳价走势、制定交易策略...',
validity: '',
urgent: false
},
{
title: '碳足迹核算需求',
company: '欧冶云商',
tags: ['碳核查服务', '碳足迹核算'],
budget: '¥ 50-100 万元',
desc: '本服务依据国际国内标准如ISO 14064-1对企业的温室气体排放报告进行独立的第三方审定与核查。我们通过文件评审、现场访...',
validity: '',
urgent: false
}
]
};
},
methods: {
handleSearch() {
console.log('搜索关键词:', this.searchKeyword);
},
handleServiceTypeSelect(value) {
this.selectedServiceType = value;
},
handleCompanySelect(value) {
this.selectedCompany = value;
},
handleContact(item) {
this.contactDialogVisible = true;
this.messageContent = '';
},
handleConfirm() {
console.log('提交留言:', this.messageContent);
this.$message.success('留言发布成功!');
this.contactDialogVisible = false;
},
handleCancel() {
this.contactDialogVisible = false;
},
handleClose() {
this.contactDialogVisible = false;
}
}
};
</script>
<style lang="less" scoped>
.xqsc-page {
min-height: 100vh;
background: #efefef;
}
//
.content-wrapper {
display: flex;
max-width: 1400px;
padding: 20px;
margin: 0 auto;
gap: 20px;
}
//
.filter-sidebar {
position: sticky;
top: 104px;
width: 220px;
flex-shrink: 0;
height: fit-content;
padding: 20px;
background: #fff;
border-radius: 8px;
}
.filter-search {
display: flex;
gap: 8px;
margin-bottom: 24px;
}
.filter-section {
margin-bottom: 20px;
}
.filter-title {
margin-bottom: 12px;
font-size: 16px;
font-weight: 600;
color: #333;
}
.filter-options {
display: flex;
flex-direction: column;
gap: 8px;
}
.filter-option {
padding: 8px 12px;
font-size: 14px;
color: #666;
cursor: pointer;
background: #f5f5f5;
border-radius: 4px;
transition: all 0.3s;
&:hover {
color: #009a29;
background: #e8f5e9;
}
&.active {
color: #fff;
background: #009a29;
}
}
//
.demand-list {
flex: 1;
min-width: 0;
}
.list-header {
display: flex;
padding: 0 8px;
margin-bottom: 16px;
justify-content: space-between;
align-items: center;
}
.list-title {
font-size: 20px;
font-weight: 600;
color: #333;
}
.list-count {
font-size: 14px;
color: #999;
}
//
.demand-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.demand-card {
display: flex;
flex-direction: column;
padding: 20px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.demand-title {
margin-bottom: 8px;
font-size: 18px;
font-weight: 600;
line-height: 1.4;
color: #333;
&.urgent {
color: #d25f00;
}
}
.demand-company {
margin-bottom: 12px;
font-size: 14px;
color: #666;
}
.demand-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
.demand-tag {
padding: 4px 8px;
font-size: 12px;
color: #2e7d32;
background: #e4f4e6;
border-radius: 2px;
}
.demand-budget {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.budget-label {
margin-right: 8px;
font-size: 14px;
color: #666;
}
.budget-value {
font-size: 20px;
font-weight: 700;
color: #D25F00;
}
.demand-desc {
margin-bottom: 16px;
font-size: 14px;
line-height: 1.6;
color: #666;
flex-grow: 1;
}
.demand-validity {
display: flex;
margin-bottom: 16px;
font-size: 14px;
color: #999;
align-items: center;
gap: 6px;
}
.calendar-icon {
width: 16px;
height: 16px;
}
.demand-action {
.t-button {
width: 100%;
}
}
//
.dialog-line {
display: flex;
margin-bottom: 16px;
}
.dialog-line-title {
margin-right: 8px;
color: #333;
}
.dialog-line-text {
color: #666;
}
</style>

View File

@ -1,34 +1,11 @@
<template>
<div class="page-box">
<!-- 顶部菜单栏 -->
<div class="nav-box">
<div class="logo-box">
<img src="@/pages/index/assets/logo-name.png">
</div>
<div class="menu-box">
<div class="menu-title meun-title-active">
<img src="@/pages/index/assets/nav-home.png" width="24px" height="24px">
<span> &nbsp;&nbsp;首页</span>
</div>
<div class="menu-title">碳证中心</div>
<div class="menu-title">服务中心</div>
<div class="menu-title">共性能力</div>
<div class="menu-title">企业出海</div>
<div class="menu-title">行业专题</div>
</div>
<div class="option-box">
<div class="gzt">
<img src="@/pages/index/assets/home-gzt-icon.png" />
<span> 工作台</span>
</div>
<div style="margin-left:20px; cursor: pointer;">激活</div>
<div>|</div>
<div style="cursor: pointer;">登录</div>
</div>
</div>
<NewNav />
<!-- 主页面 -->
<div class="container">
<div>asdf</div>
<!-- 顶部风力发电机图片 -->
<div class="top-box">
<div class="top-title">
@ -245,9 +222,11 @@
<script>
import Footer from '@/pages/index/components/footer/index.vue';
import NewNav from '@/pages/index/components/new-nav/index.vue';
export default {
components: {
Footer,
NewNav,
},
}
</script>
@ -257,81 +236,6 @@ export default {
background: #f5f5f7;
}
.nav-box {
position: fixed;
top: 0;
z-index: 9999;
display: flex;
width: 100%;
height: 64px;
min-width: 1920px;
margin: 0 auto;
color: #fff;
background: #009a29;
justify-content: space-around;
align-items: center;
}
.logo-box {
width: 25%;
text-align: center;
}
.option-box {
display: flex;
width: 25%;
font-family: PingFang SC;
font-size: 16px;
font-style: regular;
font-weight: 400;
letter-spacing: 0%;
text-align: center;
justify-content: center;
align-items: center;
gap: 10px;
leading-trim: none;
}
.option-box .gzt {
display: flex;
padding: 10px;
text-align: center;
background: rgba(255, 255, 255, 0.3);
border-radius: 6px;
justify-content: space-around;
align-items: center;
gap: 10px;
}
.menu-box {
display: flex;
width: 50%;
font-size: 16px;
text-align: center;
justify-content: center;
gap: 10px;
}
.menu-title {
display: flex;
width: 96px;
height: 64px;
line-height: 24px;
text-align: center;
cursor: pointer;
justify-content: center;
align-items: center;
}
.meun-title-active {
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.15) 100%);
border-bottom: 2px solid #fff;
}
.top-box {
position: relative;

View File

@ -95,54 +95,54 @@ const configureWebpackPlugin = [];
/**
* 本地开发启用hard-source-webpack-plugin
*/
if (LOCAL_DEV) {
console.log('====>> 启动本地开发缓存');
configureWebpackPlugin.push(
new HardSourceWebpackPlugin({
// 缓存目录,支持相对目录或绝对目录
// 如果有设置VUE_APP_HARD_SOURCE_FOLDER目录则使用设置值
// 一般在coding流水线编译使用把缓存设置到`/data/npm/`中,不会丢失
cacheDirectory: `${
(process.env.VUE_APP_HARD_SOURCE_FOLDER && path.join(process.env.VUE_APP_HARD_SOURCE_FOLDER, '/')) ||
path.join(process.cwd(), 'node_modules', '/')
}.cache/hard-source/[confighash]`,
// 基于package.json中的name来生成不同的hash串用于cacheDirectory的目录
// @param webpackConfig
configHash(webpackConfig) {
return require('node-object-hash')({ sort: false }).hash({ packageName, ...webpackConfig });
},
// 根据目录中的lock文件来决定是否重设缓存如果设置false则需要手动删除
environmentHash: {
root: process.cwd(),
directories: [],
files: ['package-lock.json', 'yarn.lock'],
},
info: {
// 'none' or 'test'.
mode: 'none',
// 'debug', 'log', 'info', 'warn', or 'error'.
level: 'debug',
},
// 自动清除过期、过大缓存
cachePrune: {
// 缓存时间默认7天
maxAge: 7 * 24 * 60 * 60 * 1000,
// 超过该处设置的阈值默认500MB则自动删除缓存
sizeThreshold: 500 * 1024 * 1024,
},
}),
new HardSourceWebpackPlugin.ExcludeModulePlugin([
{
// HardSource works with mini-css-extract-plugin but due to how
// mini-css emits assets, assets are not emitted on repeated builds with
// mini-css and hard-source together. Ignoring the mini-css loader
// modules, but not the other css loader modules, excludes the modules
// that mini-css needs rebuilt to output assets every time.
test: /mini-css-extract-plugin[\\/]dist[\\/]loader/,
},
]),
);
}
// if (LOCAL_DEV) {
// console.log('====>> 启动本地开发缓存');
// configureWebpackPlugin.push(
// new HardSourceWebpackPlugin({
// // 缓存目录,支持相对目录或绝对目录
// // 如果有设置VUE_APP_HARD_SOURCE_FOLDER目录则使用设置值
// // 一般在coding流水线编译使用把缓存设置到`/data/npm/`中,不会丢失
// cacheDirectory: `${
// (process.env.VUE_APP_HARD_SOURCE_FOLDER && path.join(process.env.VUE_APP_HARD_SOURCE_FOLDER, '/')) ||
// path.join(process.cwd(), 'node_modules', '/')
// }.cache/hard-source/[confighash]`,
// // 基于package.json中的name来生成不同的hash串用于cacheDirectory的目录
// // @param webpackConfig
// configHash(webpackConfig) {
// return require('node-object-hash')({ sort: false }).hash({ packageName, ...webpackConfig });
// },
// // 根据目录中的lock文件来决定是否重设缓存如果设置false则需要手动删除
// environmentHash: {
// root: process.cwd(),
// directories: [],
// files: ['package-lock.json', 'yarn.lock'],
// },
// info: {
// // 'none' or 'test'.
// mode: 'none',
// // 'debug', 'log', 'info', 'warn', or 'error'.
// level: 'debug',
// },
// // 自动清除过期、过大缓存
// cachePrune: {
// // 缓存时间默认7天
// maxAge: 7 * 24 * 60 * 60 * 1000,
// // 超过该处设置的阈值默认500MB则自动删除缓存
// sizeThreshold: 500 * 1024 * 1024,
// },
// }),
// new HardSourceWebpackPlugin.ExcludeModulePlugin([
// {
// // HardSource works with mini-css-extract-plugin but due to how
// // mini-css emits assets, assets are not emitted on repeated builds with
// // mini-css and hard-source together. Ignoring the mini-css loader
// // modules, but not the other css loader modules, excludes the modules
// // that mini-css needs rebuilt to output assets every time.
// test: /mini-css-extract-plugin[\\/]dist[\\/]loader/,
// },
// ]),
// );
// }
if (isExtract) {
configureWebpackPlugin.push(
@ -286,7 +286,7 @@ module.exports = {
target: 'http://carbon.liantu.tech',
changeOrigin: true,
},
'/mhzc': {
'^/mhzc': {
target: 'http://carbon.liantu.tech',
changeOrigin: true,
},