txw/txw-mhzc-web/src/pages/index/views/fwsc/sjsc.vue

1155 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="sjsc-container fwsc-page portal-page-shell portal-market-figma-page">
<div class="portal-figma-scale-viewport">
<div class="portal-figma-scale-stage" ref="figmaStage">
<!-- 二级菜单 -->
<div class="secondary-nav">
<div class="secondary-nav-content">
<div class="nav-tabs">
<button v-for="tab in navTabs" :key="tab.path" :class="['nav-tab', { active: isActiveTab(tab.path) }]"
@click="goToTab(tab)">
{{ tab.label }}
</button>
</div>
<div class="nav-right">
<span class="list-count">共 {{ page.total }} 条数据</span>
<button class="publish-btn" @click="handlePublish">免费发布数据</button>
</div>
</div>
</div>
<main class="sjsc-main">
<div class="content-area">
<!-- 左侧卡片列表 -->
<div class="card-list">
<!-- 加载状态 -->
<div v-if="loading" class="loading-state">
<div class="loading-spinner"></div>
<p>加载中...</p>
</div>
<div class="service-grid" v-else>
<div v-for="card in cardList" :key="card.gxUuid" :data-gx-uuid="card.gxUuid" class="service-card">
<!-- 卡片头部 -->
<div class="card-header">
<div class="card-title-box">
<div class="card-title-text">
<div class="card-title-row">
<div class="card-title-main">{{ card.bt1 }}</div>
<!-- 收藏按钮 -->
<div class="card-collect" @click="handleCollect(card)">
<img v-if="card.scbz === 'Y'" src="../../assets/fwsc/ysc.svg" />
<img v-else src="../../assets/fwsc/wsc.svg" />
</div>
</div>
<div class="card-title-sub">
<div class="card-company">
<img src="../../assets/fwsc/city.svg" />
<span class="company-name">{{ card.qymc }}</span>
</div>
<div class="location" v-if="card.fwfw">
<img src="../../assets/fwsc/map.svg" />
<span class="company-name">{{ card.fwfw }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 卡片内容 -->
<div class="card-content">
<p class="card-desc">{{ card.fwnr }}</p>
<div class="card-tags">
<span v-for="(tag, i) in card.fwlxbqList" :key="i" class="tag">{{ tag }}</span>
</div>
</div>
<!-- 卡片底部 -->
<div class="card-footer">
<div class="card-price-info">
<span class="price-value">免费</span>
</div>
<div class="card-actions">
<span @click="handleContact(card)">立即咨询</span>
</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="cardList.length === 0 && !loading" class="empty-state">
<p>暂无数据信息</p>
</div>
</div>
<!-- 右侧筛选栏 -->
<aside class="filter-sidebar">
<div class="filter-toggle" @click="filterCollapsed = !filterCollapsed">
<span class="toggle-text">筛选</span>
<span class="toggle-icon">{{ filterCollapsed ? '▼' : '▲' }}</span>
</div>
<div :class="['filter-sidebar-content', { collapsed: filterCollapsed }]">
<!-- 内容搜索 -->
<div class="filter-section">
<div class="filter-title">内容搜索</div>
<t-input v-model="filter.nr" placeholder="请输入关键词" @enter="onSearch">
<template #suffix-icon>
<SearchIcon />
</template>
</t-input>
</div>
<!-- 数据类型 -->
<div class="filter-section">
<div class="filter-title">数据类型</div>
<div class="filter-options">
<t-checkbox-group v-model="filter.fwlxjh" :options="sjlxOptions" @change="onSearch" />
</div>
</div>
<!-- 服务地区 -->
<div class="filter-section">
<div class="filter-title">服务地区</div>
<t-select v-model="filter.dq" :options="fwfwOptions" placeholder="请选择" clearable style="width: 100%" @change="onSearch" />
</div>
<!-- 所属行业 -->
<div class="filter-section">
<div class="filter-title">所属行业</div>
<div class="filter-options">
<t-checkbox-group v-model="filter.sshy" :options="sshyDmOptions" @change="onSearch" />
</div>
</div>
</div>
</aside>
</div>
<!-- 分页 -->
<div class="pagination-box" v-if="page.total > 0">
<div class="pagination-total">共 {{ page.total }} 条数据</div>
<t-pagination v-model="page.pageNo" :total="page.total" :page-size.sync="page.pageSize" :totalContent='false'
:page-size-options="[10, 20, 30, 50]" @change="onPageChange" align="center" />
</div>
</main>
</div>
</div>
<!-- 发布数据抽屉 -->
<SjscPublish :visible.sync="publishVisible" @success="onPublishSuccess" />
<!-- 联系数据提供方弹窗 -->
<t-dialog :closeOnOverlayClick="false" header="联系数据提供方" :visible.sync="contactVisible" @confirm="onContactConfirm"
:onClose="onContactClose" 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>
</t-dialog>
<!-- 提示弹窗 -->
<t-dialog :closeOnOverlayClick="false" header="提示" body="请先进行企业入驻" :visible.sync="rzVisible" @confirm="onRzConfirm"
:onClose="onRzClose" :cancelBtn="null" class="global-dialog" />
<!-- 发布成功弹窗 -->
<t-dialog :closeOnOverlayClick="false" body="发布申请成功,请等待审核,是否继续发布?" :visible.sync="publishSuccessVisible"
@confirm="onPublishSuccessConfirm" @cancel="onPublishSuccessCancel" :onClose="onPublishSuccessClose"
cancelBtn="返回" confirmBtn="继续发布" class="global-dialog" />
</div>
</template>
<script>
import { LocationIcon, SearchIcon } from 'tdesign-icons-vue';
import NewNav from '@/pages/index/components/new-nav/index.vue';
import BreadcrumbNav from '@/pages/index/components/breadcrumb/index.vue';
import SjscPublish from './components/SjscPublish.vue';
import gxzxApi from '@/pages/index/api/gxzx/index.js';
import { hasLogin } from '@/pages/index/api/login';
import { scrollPortalContentToTop } from '@/pages/index/utils/portal-scroll-mode';
import portalFigmaScaleMixin from '@/pages/index/utils/portal-figma-scale-mixin';
import comingSoonMixin from '@/pages/index/utils/coming-soon-mixin';
export default {
name: 'SjscPage',
mixins: [portalFigmaScaleMixin, comingSoonMixin],
components: {
NewNav,
BreadcrumbNav,
SjscPublish,
LocationIcon,
SearchIcon,
},
data() {
return {
navTabs: [
{ label: '碳服务市场', path: '/tfwsc' },
{ label: '碳需求市场', path: '/txqsc' },
{ label: '碳数据市场', path: '/tsjsc' },
{ label: '碳金融市场', path: '/tjrsc', disable: true },
],
// 筛选条件
filter: {
fwlxjh: [],
sshy: [],
dq: '',
nr: '',
},
// 数据类型(前端硬编码,与 SjscPublish 保持一致)
sjlxOptions: [
{ value: 'public', label: '公共数据' },
{ value: 'factor', label: '因子库' },
{ value: 'social', label: '社会性数据' },
],
// 代码表选项
sshyDmOptions: [],
fwfwOptions: [],
// 卡片列表
cardList: [],
// 分页
page: {
pageNo: 1,
pageSize: 10,
total: 0,
},
// 加载状态
loading: false,
// 发布抽屉
publishVisible: false,
// 联系弹窗
contactVisible: false,
contactData: {},
// 入驻提示弹窗
rzVisible: false,
// 发布成功弹窗
publishSuccessVisible: false,
// 移动端筛选折叠状态
filterCollapsed: true,
};
},
mounted() {
this.loadDmList();
this.searchList();
// 检查是否需要直接打开发布面板
if (this.$route.query.publish === '1') {
this.handlePublish();
}
},
methods: {
// 加载代码表
loadDmList() {
this.sshyoptionsSearch();
this.fwfwoptionsSearch();
},
async sshyoptionsSearch() {
try {
const res = await gxzxApi.dms2mc('sshy', {});
this.sshyDmOptions = res.data || [];
} catch (error) {
this.sshyDmOptions = [];
}
},
async fwfwoptionsSearch() {
try {
const res = await gxzxApi.dms2mc('xzqh', {});
this.fwfwOptions = res.data || [];
} catch (error) {
this.fwfwOptions = [];
}
},
// 搜索列表
async searchList() {
this.loading = true;
this.cardList = [];
const targetId = this.$route.query.id || '';
try {
const prame = {
pageNo: this.page.pageNo,
pageSize: this.page.pageSize,
sjlxDm: this.filter.fwlxjh.length ? this.filter.fwlxjh.join(',') : '',
dq: this.filter.dq || '',
sshy: this.filter.sshy.length ? this.filter.sshy.join(',') : '',
nr: this.filter.nr || '',
};
// URL携带id按id精确查询单条
if (targetId) {
prame.uuid = targetId;
}
const { data } = await gxzxApi.sjscList(prame);
const records = (data && data.records) || [];
records.map((item) => {
this.cardList.push({
id: item.uuid,
bt1: item.sjmc,
qymc: item.qymc || '',
fwnr: item.sjms,
fwfw: item.fwfw || '',
fwlxbqList: item.sjlxMc ? [item.sjlxMc] : [],
scbz: item.scbz || 'N',
lxr: item.lxr || '',
lxdh: item.lxdh || '',
email: item.email || '',
gxUuid: item.uuid,
});
});
this.page.total = Number((data && data.total) || 0);
// 数据渲染后定位到对应项
if (targetId) {
this.scrollToItem(targetId);
}
} catch (error) {
this.cardList = [];
this.page.total = 0;
console.error('获取数据列表失败', error);
} finally {
this.loading = false;
}
},
// 重置筛选
onReset() {
this.filter = {
fwlxjh: [],
sshy: [],
dq: '',
nr: '',
};
this.page.pageNo = 1;
this.searchList();
},
// 搜索
onSearch() {
this.page.pageNo = 1;
this.searchList();
},
onPageChange(pageInfo) {
this.page.pageNo = pageInfo.current;
this.page.pageSize = pageInfo.pageSize;
scrollPortalContentToTop();
this.searchList();
},
// 滚动到指定项并高亮
scrollToItem(gxUuid) {
this.$nextTick(() => {
const cardElement = document.querySelector(`[data-gx-uuid="${gxUuid}"]`);
if (cardElement) {
cardElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
cardElement.classList.add('highlight-card');
setTimeout(() => {
cardElement.classList.remove('highlight-card');
}, 3000);
}
});
},
// 处理发布
handlePublish() {
const yhxx = window.sessionStorage.getItem('yhxx');
if (yhxx) {
const yhxxData = JSON.parse(yhxx);
if (yhxxData.gxdtRzbz === 'Y') {
this.publishVisible = true;
} else {
this.rzVisible = true;
}
} else {
this.rzVisible = true;
}
},
// 收藏/取消收藏
async handleCollect(card) {
try {
const type = card.scbz === 'Y' ? 'remove' : 'add';
await gxzxApi.gxsc({ gxUuid: card.gxUuid, type });
card.scbz = card.scbz === 'Y' ? 'N' : 'Y';
} catch (error) {
console.error('收藏失败', error);
}
},
// 联系数据提供方
handleContact(card) {
this.contactData = {
lxr: card.lxr || '',
lxdh: card.lxdh || '',
email: card.email || '',
};
this.contactVisible = true;
},
// 发布成功回调
onPublishSuccess() {
this.publishSuccessVisible = true;
},
// 导航相关
isActiveTab(path) {
return this.$route.path === path;
},
goToTab({ path, disable }) {
if (disable) {
this.showComingSoon();
return;
}
this.$router.push(path);
},
// 弹窗回调
onContactConfirm() {
this.contactVisible = false;
},
onContactClose() {
this.contactVisible = false;
},
onRzConfirm() {
this.rzVisible = false;
},
onRzClose() {
this.rzVisible = false;
},
onPublishSuccessConfirm() {
this.publishSuccessVisible = false;
this.publishVisible = true;
},
onPublishSuccessCancel() {
this.publishSuccessVisible = false;
this.$router.push('/tsjsc');
},
onPublishSuccessClose() {
this.publishSuccessVisible = false;
},
},
};
</script>
<style lang="less" scoped>
@import '../../styles/home-figma-variables.less';
.sjsc-container {
background: #F6F7FA;
.portal-figma-scale-page();
}
.sjsc-main {
width: 100%;
max-width: 1400px;
padding: 20px;
margin: 0 auto;
}
// 二级菜单
.secondary-nav {
border-bottom: none;
}
.secondary-nav-content {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1400px;
padding: 20px 20px 0;
margin: 0 auto;
}
.nav-tabs {
display: flex;
gap: 40px;
width: 596px;
height: 42px;
}
.nav-right {
display: flex;
align-items: center;
gap: 20px;
}
.nav-tab {
position: relative;
min-width: max-content;
height: 42px;
padding: 8px 16px;
font-size: 18px;
font-weight: 500;
color: #003B1A;
cursor: pointer;
background: transparent;
border-radius: 32px;
transition: all 0.3s;
&:hover {
color: #009a29;
}
&.active {
background: #8CFFCE;
box-shadow: inset 0 0 0 1px #00B96B;
}
}
.publish-btn {
width: 220px;
height: 42px;
line-height: 26px;
padding: 8px 16px;
font-size: 18px;
font-weight: 400;
color: #fff;
cursor: pointer;
background: linear-gradient(135deg, #009a29 0%, #48C666 100%);
border: none;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 154, 41, 0.25);
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 154, 41, 0.35);
}
&:active {
transform: translateY(0);
}
}
.content-area {
display: flex;
gap: 20px;
align-items: stretch;
}
// 左侧筛选栏
.filter-sidebar {
position: sticky;
top: 104px;
width: 220px;
flex-shrink: 0;
}
// 移动端筛选栏折叠
.filter-toggle {
display: none;
}
@media (max-width: 768px) {
.filter-toggle {
display: flex;
padding: 12px;
margin-bottom: 8px;
cursor: pointer;
background: #fff;
border-radius: 8px;
justify-content: space-between;
align-items: center;
.toggle-text {
font-size: 14px;
font-weight: 600;
color: #333;
}
.toggle-icon {
font-size: 18px;
color: #666;
transition: transform 0.3s;
}
&.collapsed .toggle-icon {
transform: rotate(-90deg);
}
}
.filter-sidebar-content {
max-height: 1000px;
padding: 20px;
overflow: hidden;
transition: max-height 0.3s ease, padding 0.3s ease;
}
.filter-sidebar-content.collapsed {
max-height: 0;
padding-top: 0;
padding-bottom: 0;
}
}
.filter-sidebar-content {
padding: 20px;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}
.filter-section {
// padding-bottom: 20px;
margin-bottom: 20px;
border-bottom: 1px dashed #e0e0e0;
&:last-child {
padding: 10px 0;
margin-bottom: 0;
border-bottom: none;
}
}
.filter-title {
display: flex;
margin-bottom: 12px;
font-size: 14px;
font-weight: 600;
color: #333;
align-items: center;
gap: 6px;
&::before {
width: 4px;
height: 14px;
background: linear-gradient(180deg, #009a29, #48C666);
border-radius: 2px;
content: '';
}
}
.filter-options {
/deep/.t-checkbox__label {
font-size: 14px;
color: #666;
}
&.enterprise-options {
max-height: 200px;
overflow-y: auto;
}
}
// 右侧卡片列表
.card-list {
flex: 1;
min-width: 0;
}
.list-count {
font-size: 14px;
font-weight: 400;
color: #6B8575;
}
// 服务卡片网格
.service-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
.service-card {
position: relative;
display: flex;
flex-direction: column;
overflow: hidden;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
&.highlight-card {
animation: highlight-pulse 3s ease-out;
}
}
.card-header {
position: relative;
padding: 16px;
height: 70px;
&::after {
position: absolute;
right: 16px;
bottom: -6px;
left: 16px;
height: 1px;
background: #E8F0EC;
content: '';
}
}
.card-title-box {
display: flex;
justify-content: space-between;
}
.card-title-text {
flex: 1;
min-width: 0;
}
.card-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.card-title-main {
flex: 1;
overflow: hidden;
font-size: 16px;
font-weight: 500;
line-height: 24px;
color: #003B1A;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-title-sub {
display: flex;
gap: 12px;
justify-content: space-between;
}
.card-company {
display: flex;
align-items: center;
gap: 8px;
}
.card-collect {
display: flex;
width: 20px;
height: 20px;
cursor: pointer;
align-items: center;
justify-content: center;
img {
width: 18px;
height: 18px;
}
}
.location {
display: flex;
align-items: center;
gap: 8px;
}
.company-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
color: #6B8575;
font-weight: 400;
}
.card-content {
padding: 16px;
}
.card-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tag {
padding: 2px 8px;
font-size: 13px;
font-weight: 400;
color: #00B96B;
background: #EEFAE2;
border-radius: 4px;
}
.card-desc {
display: block;
height: 70px;
overflow: hidden;
font-size: 14px;
line-height: 1.6;
color: #666;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
}
.card-footer {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
height: 64px;
&::before {
position: absolute;
top: 0;
right: 16px;
left: 16px;
height: 1px;
background: #E8F0EC;
content: '';
}
}
.card-price-info {
display: flex;
align-items: baseline;
}
.price-value {
font-size: 20px;
font-weight: 600;
color: #2e7d32;
}
.card-actions {
display: flex;
height: 32px;
padding: 6px 12px;
border-radius: 4px;
border: 1px solid #00b96b;
color: #00b96b;
font-size: 14px;
font-weight: 400;
&:hover {
background: rgba(0, 154, 41, 0.1);
cursor: pointer;
}
}
// 空状态
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
min-height: 300px;
padding: 40px;
background: #fff;
border-radius: 12px;
animation: fadeIn 0.5s ease;
.empty-icon {
margin-bottom: 16px;
font-size: 48px;
color: #d0d0d0;
}
p {
font-size: 14px;
color: #999;
}
}
// 加载状态
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400px;
padding: 40px;
background: #fff;
border-radius: 12px;
.loading-spinner {
width: 48px;
height: 48px;
margin-bottom: 16px;
border: 4px solid #e8f5e9;
border-top-color: #009a29;
border-radius: 50%;
animation: spin 1s linear infinite;
}
p {
margin: 0;
font-size: 14px;
color: #999;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes highlight-pulse {
0% {
box-shadow: 0 0 0 0 rgba(0, 154, 41, 0.4);
}
50% {
box-shadow: 0 0 20px 10px rgba(0, 154, 41, 0.2);
}
100% {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}
}
// 分页
.pagination-total {
font-size: 16px;
color: #666;
}
.pagination-box {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
padding-top: 32px;
gap: 16px;
width: 100%;
box-sizing: border-box;
::v-deep .t-pagination {
display: flex !important;
flex-wrap: wrap;
align-items: center;
justify-content: center;
width: auto;
max-width: 100%;
gap: 12px 16px;
.t-input.t-is-readonly {
width: 110px;
}
.t-pagination__select {
margin-right: 0;
}
}
}
// 弹窗样式
.dialog-line {
display: flex;
margin-bottom: 16px;
}
.dialog-line-title {
flex-shrink: 0;
width: 80px;
color: #333;
}
.dialog-line-text {
color: #666;
}
@media (max-width: 1200px) {
.service-card {
width: calc((100% - 20px) / 2);
}
}
@media (max-width: 768px) {
.sjsc-container {
overflow-x: hidden;
}
.sjsc-main {
padding: 12px;
box-sizing: border-box;
}
.secondary-nav-content {
flex-direction: column;
align-items: stretch;
padding: 12px 16px;
gap: 12px;
box-sizing: border-box;
}
.nav-tabs {
display: flex;
flex-wrap: nowrap;
align-items: center;
width: 100%;
max-width: 100%;
height: auto;
min-height: 42px;
padding-bottom: 4px;
overflow-x: auto;
overflow-y: hidden;
gap: 8px;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
}
.nav-tab {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
height: auto;
min-height: 40px;
padding: 10px 12px;
font-size: 13px;
line-height: 1.2;
white-space: nowrap;
box-sizing: border-box;
}
.nav-right {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 8px;
width: 100%;
}
.list-count {
width: 100%;
font-size: 14px;
line-height: 22px;
text-align: center;
white-space: nowrap;
}
.publish-btn {
width: 100%;
height: auto;
min-height: 44px;
padding: 10px 16px;
font-size: 15px;
line-height: 1.3;
box-sizing: border-box;
}
.content-area {
flex-direction: column;
gap: 12px;
}
.filter-sidebar {
position: relative;
top: 0;
order: -1;
width: 100%;
}
.card-list {
order: 0;
width: 100%;
}
.service-grid {
grid-template-columns: 1fr;
gap: 12px;
}
.service-card {
width: 100%;
max-width: 100%;
}
.card-header {
height: auto;
min-height: 0;
padding: 12px 12px 14px;
}
.card-title-sub {
flex-direction: column;
align-items: flex-start;
gap: 6px;
}
.card-content {
padding: 12px;
}
.card-desc {
height: auto;
min-height: 0;
max-height: none;
}
.card-footer {
flex-direction: row;
align-items: center;
justify-content: space-between;
height: auto;
min-height: 0;
padding: 12px;
gap: 12px;
}
.card-price-info {
flex: 1;
min-width: 0;
}
.card-actions {
flex-shrink: 0;
width: auto;
min-width: 96px;
height: 40px;
padding: 8px 14px;
box-sizing: border-box;
}
}
@media (max-width: 480px) {
.sjsc-main {
padding: 8px;
}
.secondary-nav-content {
padding: 8px 12px;
}
.nav-tab {
padding: 8px 10px;
font-size: 12px;
}
.publish-btn {
padding: 8px;
font-size: 13px;
}
.filter-sidebar-content {
padding: 12px;
}
.filter-title {
font-size: 13px;
}
.card-title-main {
font-size: 14px;
}
.card-desc {
font-size: 13px;
}
.price-value {
font-size: 18px;
}
}
</style>