feat:1.标签多余及省略 2.调整政策页面 3.布局优化
This commit is contained in:
parent
1888d64850
commit
5147a55915
@ -11,7 +11,7 @@
|
||||
"build": "cross-env NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build",
|
||||
"build:site:dev": "vue-cli-service build --mode development",
|
||||
"build:site:test": "vue-cli-service build --mode test",
|
||||
"test:unit": "node --test tests/unit/gxnlpt-scroll-helpers.test.js",
|
||||
"test:unit": "node --test tests/unit/gxnlpt-scroll-helpers.test.js tests/unit/gxnlpt-tag-overflow.test.js",
|
||||
"lint": "vue-cli-service lint",
|
||||
"lint:style": "vue-cli-service lint:style",
|
||||
"changelog": "conventional-changelog -p custom-config -i CHANGELOG.md -s -r 0",
|
||||
|
||||
48
txw-mhzc-web/src/pages/index/utils/gxnlpt-tag-overflow.js
Normal file
48
txw-mhzc-web/src/pages/index/utils/gxnlpt-tag-overflow.js
Normal file
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 根据标签宽度与容器宽度计算可见标签数量(纯函数,便于单测)
|
||||
* @param {number[]} tagWidths 各标签测量宽度(px)
|
||||
* @param {number} maxWidth 容器可用宽度(px)
|
||||
* @param {{ gap?: number, ellipsisWidth?: number }} [opts]
|
||||
* @returns {{ visible: number, overflow: boolean }}
|
||||
*/
|
||||
export function calcTagOverflowLayout(tagWidths, maxWidth, opts = {}) {
|
||||
const gap = opts.gap ?? 4;
|
||||
const ellipsisWidth = opts.ellipsisWidth ?? 14;
|
||||
const n = tagWidths.length;
|
||||
|
||||
if (!n || maxWidth <= 0) {
|
||||
return { visible: 0, overflow: false };
|
||||
}
|
||||
|
||||
const rowWidth = (count, withEllipsis) => {
|
||||
if (count <= 0) return 0;
|
||||
let total = 0;
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
total += tagWidths[i];
|
||||
if (i > 0) total += gap;
|
||||
}
|
||||
if (withEllipsis) total += gap + ellipsisWidth;
|
||||
return total;
|
||||
};
|
||||
|
||||
if (rowWidth(n, false) <= maxWidth) {
|
||||
return { visible: n, overflow: false };
|
||||
}
|
||||
|
||||
for (let v = n - 1; v >= 1; v -= 1) {
|
||||
if (rowWidth(v, true) <= maxWidth) {
|
||||
return { visible: v, overflow: true };
|
||||
}
|
||||
}
|
||||
|
||||
return { visible: 1, overflow: n > 1 };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} hiddenTags
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatHiddenTagsTooltip(hiddenTags) {
|
||||
if (!hiddenTags || !hiddenTags.length) return '';
|
||||
return hiddenTags.join('、');
|
||||
}
|
||||
@ -64,9 +64,8 @@
|
||||
<!-- 卡片底部 -->
|
||||
<div class="card-footer">
|
||||
<div class="card-price-info">
|
||||
<span class="price-value" v-if="card.gjjg">¥{{ card.gjjg }}</span>
|
||||
<span class="price-value" v-else>面议</span>
|
||||
<!-- <span class="price-unit" v-if="card.gjdwDm">/{{ card.gjdwDm }}</span> -->
|
||||
<span class="price-value">面议</span>
|
||||
<!-- 价格暂统一展示面议;恢复金额展示时用 card.gjjg -->
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<span @click="handleContact(card)">立即咨询</span>
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<!-- Banner 区 -->
|
||||
<div class="banner-section">
|
||||
<div class="banner-content">
|
||||
<h1 class="banner-title">可信碳服务中心</h1>
|
||||
<h1 class="banner-title">服务中心</h1>
|
||||
<!-- <img src="@/pages/index/assets/home-top-title.png"> -->
|
||||
<p class="banner-subtitle">链接全球碳资产,赋能绿色价值链。一站式解决碳服务撮合、绿色交易与金融对接需求。</p>
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<div v-if="tags.length" ref="root" class="gxnlpt-card-tags">
|
||||
<t-tooltip
|
||||
v-if="hasOverflow"
|
||||
:content="overflowTooltip"
|
||||
theme="light"
|
||||
placement="top"
|
||||
:show-arrow="true"
|
||||
>
|
||||
<div ref="track" class="gxnlpt-tag-track gxnlpt-tag-track--interactive">
|
||||
<span
|
||||
v-for="(tag, ti) in visibleTags"
|
||||
:key="`visible-${ti}-${tag}`"
|
||||
class="gxnlpt-tag"
|
||||
:title="tag"
|
||||
>{{ tag }}</span>
|
||||
<span class="gxnlpt-tag-ellipsis" aria-label="更多标签">…</span>
|
||||
</div>
|
||||
</t-tooltip>
|
||||
<div v-else ref="track" class="gxnlpt-tag-track">
|
||||
<span
|
||||
v-for="(tag, ti) in visibleTags"
|
||||
:key="`visible-${ti}-${tag}`"
|
||||
class="gxnlpt-tag"
|
||||
:title="tag"
|
||||
>{{ tag }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 离屏测量:与可见标签同款式,用于计算溢出 -->
|
||||
<div class="gxnlpt-tag-measure" aria-hidden="true">
|
||||
<span
|
||||
v-for="(tag, ti) in tags"
|
||||
:key="`measure-${ti}-${tag}`"
|
||||
ref="measureTag"
|
||||
class="gxnlpt-tag"
|
||||
>{{ tag }}</span>
|
||||
<span ref="measureEllipsis" class="gxnlpt-tag-ellipsis">…</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
calcTagOverflowLayout,
|
||||
formatHiddenTagsTooltip,
|
||||
} from '@/pages/index/utils/gxnlpt-tag-overflow.js';
|
||||
|
||||
const TAG_GAP = 4;
|
||||
|
||||
export default {
|
||||
name: 'GxnlptCardTags',
|
||||
props: {
|
||||
tags: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visibleCount: 0,
|
||||
hasOverflow: false,
|
||||
resizeObserver: null,
|
||||
measureRaf: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
normalizedTags() {
|
||||
return (this.tags || []).filter(Boolean);
|
||||
},
|
||||
visibleTags() {
|
||||
if (!this.normalizedTags.length) return [];
|
||||
const count = this.visibleCount > 0
|
||||
? Math.min(this.visibleCount, this.normalizedTags.length)
|
||||
: this.normalizedTags.length;
|
||||
return this.normalizedTags.slice(0, count);
|
||||
},
|
||||
hiddenTags() {
|
||||
if (!this.hasOverflow) return [];
|
||||
return this.normalizedTags.slice(this.visibleTags.length);
|
||||
},
|
||||
overflowTooltip() {
|
||||
return formatHiddenTagsTooltip(this.hiddenTags);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
tags: {
|
||||
handler() {
|
||||
this.scheduleMeasure();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
hasOverflow() {
|
||||
this.$nextTick(() => this.scheduleMeasure());
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.visibleCount = this.normalizedTags.length;
|
||||
this.scheduleMeasure();
|
||||
this.bindResizeObserver();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.unbindResizeObserver();
|
||||
if (this.measureRaf) {
|
||||
cancelAnimationFrame(this.measureRaf);
|
||||
this.measureRaf = null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getTrackEl() {
|
||||
return this.$refs.track || this.$el.querySelector('.gxnlpt-tag-track');
|
||||
},
|
||||
scheduleMeasure() {
|
||||
if (this.measureRaf) cancelAnimationFrame(this.measureRaf);
|
||||
this.measureRaf = requestAnimationFrame(() => {
|
||||
this.measureRaf = null;
|
||||
this.$nextTick(() => this.measure());
|
||||
});
|
||||
},
|
||||
measure() {
|
||||
const tags = this.normalizedTags;
|
||||
if (!tags.length) {
|
||||
this.visibleCount = 0;
|
||||
this.hasOverflow = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const track = this.getTrackEl();
|
||||
const measureTags = this.$refs.measureTag;
|
||||
const measureTagEls = Array.isArray(measureTags)
|
||||
? measureTags
|
||||
: measureTags
|
||||
? [measureTags]
|
||||
: [];
|
||||
|
||||
if (!track || measureTagEls.length !== tags.length) {
|
||||
this.visibleCount = tags.length;
|
||||
this.hasOverflow = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const maxWidth = track.clientWidth;
|
||||
const tagWidths = measureTagEls.map((el) => el.offsetWidth);
|
||||
const ellipsisEl = this.$refs.measureEllipsis;
|
||||
const ellipsisWidth = ellipsisEl ? ellipsisEl.offsetWidth : 14;
|
||||
|
||||
const { visible, overflow } = calcTagOverflowLayout(tagWidths, maxWidth, {
|
||||
gap: TAG_GAP,
|
||||
ellipsisWidth,
|
||||
});
|
||||
|
||||
this.visibleCount = visible;
|
||||
this.hasOverflow = overflow;
|
||||
},
|
||||
bindResizeObserver() {
|
||||
if (typeof ResizeObserver === 'undefined') {
|
||||
window.addEventListener('resize', this.scheduleMeasure);
|
||||
return;
|
||||
}
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
this.scheduleMeasure();
|
||||
});
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.root) this.resizeObserver.observe(this.$refs.root);
|
||||
});
|
||||
},
|
||||
unbindResizeObserver() {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
window.removeEventListener('resize', this.scheduleMeasure);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '../../../styles/home-figma-variables.less';
|
||||
|
||||
.gxnlpt-card-tags {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.gxnlpt-tag-track {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.gxnlpt-tag-track--interactive {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.gxnlpt-tag {
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
max-width: 120px;
|
||||
padding: 2px 6px;
|
||||
overflow: hidden;
|
||||
font-family: @home-font-family;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: @home-color-primary-green;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
background: #eefae2;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.gxnlpt-tag-ellipsis {
|
||||
flex-shrink: 0;
|
||||
padding: 0 2px;
|
||||
font-family: @home-font-family;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
color: @home-color-primary-green;
|
||||
}
|
||||
|
||||
.gxnlpt-tag-measure {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 4px;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@ -64,47 +64,46 @@
|
||||
</p>
|
||||
</header>
|
||||
<form class="gxnlpt-submit-form" @submit.prevent="handleSubmitForm">
|
||||
<div class="gxnlpt-submit-columns">
|
||||
<div class="gxnlpt-submit-col">
|
||||
<h3 class="gxnlpt-submit-col-title">内容提交</h3>
|
||||
<label class="gxnlpt-field">
|
||||
<span class="gxnlpt-field-label"><span class="gxnlpt-field-required">*</span>名称</span>
|
||||
<input
|
||||
v-model.trim="submitForm.bt1"
|
||||
type="text"
|
||||
class="gxnlpt-input"
|
||||
:class="{ 'is-error': submitErrors.bt1 }"
|
||||
placeholder="请输入名称"
|
||||
@blur="touchSubmitField('bt1')"
|
||||
/>
|
||||
<span v-if="submitErrors.bt1" class="gxnlpt-field-error">{{ submitErrors.bt1 }}</span>
|
||||
</label>
|
||||
<label class="gxnlpt-field">
|
||||
<span class="gxnlpt-field-label"><span class="gxnlpt-field-required">*</span>链接</span>
|
||||
<input
|
||||
v-model.trim="submitForm.lj"
|
||||
type="text"
|
||||
class="gxnlpt-input"
|
||||
:class="{ 'is-error': submitErrors.lj }"
|
||||
placeholder="请输入链接"
|
||||
@blur="touchSubmitField('lj')"
|
||||
/>
|
||||
<span v-if="submitErrors.lj" class="gxnlpt-field-error">{{ submitErrors.lj }}</span>
|
||||
</label>
|
||||
<label class="gxnlpt-field">
|
||||
<span class="gxnlpt-field-label"><span class="gxnlpt-field-required">*</span>简介</span>
|
||||
<textarea
|
||||
v-model.trim="submitForm.jj"
|
||||
class="gxnlpt-textarea"
|
||||
:class="{ 'is-error': submitErrors.jj }"
|
||||
maxlength="40"
|
||||
rows="3"
|
||||
placeholder="请输入简介"
|
||||
@blur="touchSubmitField('jj')"
|
||||
></textarea>
|
||||
<span class="gxnlpt-field-counter">{{ submitForm.jj.length }}/40</span>
|
||||
<span v-if="submitErrors.jj" class="gxnlpt-field-error">{{ submitErrors.jj }}</span>
|
||||
</label>
|
||||
<div class="gxnlpt-submit-body">
|
||||
<label class="gxnlpt-field">
|
||||
<span class="gxnlpt-field-label"><span class="gxnlpt-field-required">*</span>名称</span>
|
||||
<input
|
||||
v-model.trim="submitForm.bt1"
|
||||
type="text"
|
||||
class="gxnlpt-input"
|
||||
:class="{ 'is-error': submitErrors.bt1 }"
|
||||
placeholder="请输入名称"
|
||||
@blur="touchSubmitField('bt1')"
|
||||
/>
|
||||
<span v-if="submitErrors.bt1" class="gxnlpt-field-error">{{ submitErrors.bt1 }}</span>
|
||||
</label>
|
||||
<label class="gxnlpt-field">
|
||||
<span class="gxnlpt-field-label"><span class="gxnlpt-field-required">*</span>链接</span>
|
||||
<input
|
||||
v-model.trim="submitForm.lj"
|
||||
type="text"
|
||||
class="gxnlpt-input"
|
||||
:class="{ 'is-error': submitErrors.lj }"
|
||||
placeholder="请输入链接(http:// 或 https://)"
|
||||
@blur="touchSubmitField('lj')"
|
||||
/>
|
||||
<span v-if="submitErrors.lj" class="gxnlpt-field-error">{{ submitErrors.lj }}</span>
|
||||
</label>
|
||||
<label class="gxnlpt-field">
|
||||
<span class="gxnlpt-field-label"><span class="gxnlpt-field-required">*</span>简介</span>
|
||||
<textarea
|
||||
v-model.trim="submitForm.jj"
|
||||
class="gxnlpt-textarea"
|
||||
:class="{ 'is-error': submitErrors.jj }"
|
||||
maxlength="40"
|
||||
rows="3"
|
||||
placeholder="请输入简介"
|
||||
@blur="touchSubmitField('jj')"
|
||||
></textarea>
|
||||
<span class="gxnlpt-field-counter">{{ submitForm.jj.length }}/40</span>
|
||||
<span v-if="submitErrors.jj" class="gxnlpt-field-error">{{ submitErrors.jj }}</span>
|
||||
</label>
|
||||
<div class="gxnlpt-submit-row-2">
|
||||
<label class="gxnlpt-field">
|
||||
<span class="gxnlpt-field-label"><span class="gxnlpt-field-required">*</span>分类</span>
|
||||
<select
|
||||
@ -132,50 +131,6 @@
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="gxnlpt-submit-col">
|
||||
<h3 class="gxnlpt-submit-col-title">用户信息</h3>
|
||||
<label class="gxnlpt-field">
|
||||
<span class="gxnlpt-field-label"><span class="gxnlpt-field-required">*</span>昵称</span>
|
||||
<input
|
||||
v-model.trim="submitForm.nc"
|
||||
type="text"
|
||||
class="gxnlpt-input"
|
||||
:class="{ 'is-error': submitErrors.nc }"
|
||||
placeholder="请输入昵称"
|
||||
@blur="touchSubmitField('nc')"
|
||||
/>
|
||||
<span v-if="submitErrors.nc" class="gxnlpt-field-error">{{ submitErrors.nc }}</span>
|
||||
</label>
|
||||
<label class="gxnlpt-field">
|
||||
<span class="gxnlpt-field-label"><span class="gxnlpt-field-required">*</span>联系方式</span>
|
||||
<input
|
||||
v-model.trim="submitForm.lxfs"
|
||||
type="text"
|
||||
class="gxnlpt-input"
|
||||
:class="{ 'is-error': submitErrors.lxfs }"
|
||||
placeholder="请输入联系方式"
|
||||
@blur="touchSubmitField('lxfs')"
|
||||
/>
|
||||
<span v-if="submitErrors.lxfs" class="gxnlpt-field-error">{{ submitErrors.lxfs }}</span>
|
||||
</label>
|
||||
<label class="gxnlpt-field gxnlpt-field--captcha">
|
||||
<span class="gxnlpt-field-label"><span class="gxnlpt-field-required">*</span>验证码</span>
|
||||
<div class="gxnlpt-captcha-row">
|
||||
<input
|
||||
v-model.trim="submitForm.yzm"
|
||||
type="text"
|
||||
class="gxnlpt-input"
|
||||
:class="{ 'is-error': submitErrors.yzm }"
|
||||
placeholder="请输入验证码"
|
||||
@blur="touchSubmitField('yzm')"
|
||||
/>
|
||||
<button type="button" class="gxnlpt-captcha-btn" @click="sendCaptcha">
|
||||
获取验证码
|
||||
</button>
|
||||
</div>
|
||||
<span v-if="submitErrors.yzm" class="gxnlpt-field-error">{{ submitErrors.yzm }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="gxnlpt-submit-btn">提交审核</button>
|
||||
</form>
|
||||
@ -210,13 +165,7 @@
|
||||
</div>
|
||||
<p class="gxnlpt-card-org" :title="card.qymc">{{ card.qymc }}</p>
|
||||
</div>
|
||||
<div v-if="card.tagList && card.tagList.length" class="gxnlpt-card-tags">
|
||||
<span
|
||||
v-for="(tag, ti) in card.tagList"
|
||||
:key="`${card.gxUuid || card._demoId}-fav-tag-${ti}`"
|
||||
class="gxnlpt-tag"
|
||||
>{{ tag }}</span>
|
||||
</div>
|
||||
<GxnlptCardTags :tags="card.tagList" />
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
@ -260,13 +209,7 @@
|
||||
</div>
|
||||
<p class="gxnlpt-card-org" :title="card.qymc">{{ card.qymc }}</p>
|
||||
</div>
|
||||
<div v-if="card.tagList && card.tagList.length" class="gxnlpt-card-tags">
|
||||
<span
|
||||
v-for="(tag, ti) in card.tagList"
|
||||
:key="`${card.gxUuid || card._demoId}-tag-${ti}`"
|
||||
class="gxnlpt-tag"
|
||||
>{{ tag }}</span>
|
||||
</div>
|
||||
<GxnlptCardTags :tags="card.tagList" />
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
@ -290,6 +233,7 @@
|
||||
|
||||
<script>
|
||||
import Footer from '@/pages/index/components/footer/index.vue';
|
||||
import GxnlptCardTags from '@/pages/index/views/gxnlpt/components/GxnlptCardTags.vue';
|
||||
import api from '@/pages/index/api/fwsc/index.js';
|
||||
import {
|
||||
isPortalLoggedIn,
|
||||
@ -305,6 +249,77 @@ const PREVIEW_SIZE = 6;
|
||||
/** 临时开启:优先展示分类假数据,接口就绪后改为 false */
|
||||
const FORCE_DEMO_PREVIEW = true;
|
||||
|
||||
/**
|
||||
* 演示:标签溢出省略 UI 测试(需与 FORCE_DEMO_PREVIEW 同时为 true)
|
||||
* 关闭后恢复每卡 2~3 个短标签的正常展示
|
||||
*/
|
||||
const DEMO_TAG_OVERFLOW_PREVIEW = true;
|
||||
|
||||
/** 按分类注入的碳领域长标签(仅溢出 UI 测试用) */
|
||||
const DEMO_TAG_OVERFLOW_BY_CATEGORY = {
|
||||
'content-1': [
|
||||
'温室气体盘查',
|
||||
'组织碳排放',
|
||||
'产品碳足迹',
|
||||
'范围三排放',
|
||||
'ISO14064-1',
|
||||
'GHG Protocol',
|
||||
'排放因子',
|
||||
'碳数据核算',
|
||||
'年度碳盘查',
|
||||
'供应链碳足迹',
|
||||
'碳中和路径',
|
||||
],
|
||||
'content-2': [
|
||||
'第三方碳核查',
|
||||
'CCER项目审定',
|
||||
'碳中和认证',
|
||||
'自愿减排核证',
|
||||
'绿色项目认定',
|
||||
'产品碳足迹认证',
|
||||
'零碳工厂评价',
|
||||
'林业碳汇核证',
|
||||
'碳标识认证',
|
||||
],
|
||||
'content-3': [
|
||||
'全国碳配额',
|
||||
'CCER自愿减排',
|
||||
'碳市场交易',
|
||||
'配额回购',
|
||||
'履约清缴',
|
||||
'试点碳市场',
|
||||
'碳资产托管',
|
||||
'大宗协议转让',
|
||||
'碳普惠交易',
|
||||
'跨境碳信用',
|
||||
],
|
||||
};
|
||||
|
||||
function mergeDemoOverflowTags(bqjh, categoryId) {
|
||||
let base = [];
|
||||
if (bqjh) {
|
||||
try {
|
||||
const parsed = JSON.parse(bqjh);
|
||||
base = Array.isArray(parsed) ? parsed : [];
|
||||
} catch (e) {
|
||||
base = String(bqjh)
|
||||
.split(/[,,]/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
const extra = DEMO_TAG_OVERFLOW_BY_CATEGORY[categoryId] || [];
|
||||
return JSON.stringify([...new Set([...base, ...extra])]);
|
||||
}
|
||||
|
||||
/** content-1 前 3 张、content-3 前 2 张注入长标签,其余保持短标签便于对比 */
|
||||
function shouldInjectOverflowDemoTags(categoryId, index) {
|
||||
if (!DEMO_TAG_OVERFLOW_PREVIEW || !FORCE_DEMO_PREVIEW) return false;
|
||||
if (categoryId === 'content-1' && index < 3) return true;
|
||||
if (categoryId === 'content-3' && index < 2) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 各分类碳相关演示数据(bqjh 模拟后期 JSON 传输) */
|
||||
const DEMO_BY_CATEGORY = {
|
||||
'content-1': [
|
||||
@ -401,14 +416,11 @@ const SUBMIT_FORM_EMPTY = () => ({
|
||||
jj: '',
|
||||
fl: '',
|
||||
bq: '',
|
||||
nc: '',
|
||||
lxfs: '',
|
||||
yzm: '',
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'GxnlptIndex',
|
||||
components: { Footer },
|
||||
components: { Footer, GxnlptCardTags },
|
||||
data() {
|
||||
return {
|
||||
starOutline,
|
||||
@ -531,7 +543,8 @@ export default {
|
||||
if (Array.isArray(item.fwlxbqList) && item.fwlxbqList.length) {
|
||||
append(item.fwlxbqList);
|
||||
}
|
||||
return tags.slice(0, 12);
|
||||
const maxTags = DEMO_TAG_OVERFLOW_PREVIEW && FORCE_DEMO_PREVIEW ? 20 : 12;
|
||||
return tags.slice(0, maxTags);
|
||||
},
|
||||
attachTags(card) {
|
||||
const tagList = this.buildTagList(card);
|
||||
@ -575,13 +588,16 @@ export default {
|
||||
this.useDemoData = true;
|
||||
this.categoryList.forEach((cat) => {
|
||||
const demos = DEMO_BY_CATEGORY[cat.id] || [];
|
||||
cat.cardList = demos.map((c, i) =>
|
||||
this.attachTags({
|
||||
...c,
|
||||
cat.cardList = demos.map((c, i) => {
|
||||
const demo = shouldInjectOverflowDemoTags(cat.id, i)
|
||||
? { ...c, bqjh: mergeDemoOverflowTags(c.bqjh, cat.id) }
|
||||
: c;
|
||||
return this.attachTags({
|
||||
...demo,
|
||||
_demoId: `${cat.id}-${i}`,
|
||||
gxUuid: '',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
cat.total = cat.cardList.length;
|
||||
cat.expanded = false;
|
||||
this.refreshDisplayList(cat);
|
||||
@ -721,9 +737,6 @@ export default {
|
||||
bt1: '请输入名称',
|
||||
lj: '请输入链接',
|
||||
jj: '请输入简介',
|
||||
nc: '请输入昵称',
|
||||
lxfs: '请输入联系方式',
|
||||
yzm: '请输入验证码',
|
||||
};
|
||||
if (field === 'fl' && !form.fl) {
|
||||
errors.fl = '请选择分类';
|
||||
@ -741,7 +754,7 @@ export default {
|
||||
return !errors[field];
|
||||
},
|
||||
validateSubmitForm() {
|
||||
const fields = ['bt1', 'lj', 'jj', 'fl', 'nc', 'lxfs', 'yzm'];
|
||||
const fields = ['bt1', 'lj', 'jj', 'fl'];
|
||||
let valid = true;
|
||||
fields.forEach((field) => {
|
||||
this.$set(this.submitTouched, field, true);
|
||||
@ -751,14 +764,6 @@ export default {
|
||||
});
|
||||
return valid;
|
||||
},
|
||||
sendCaptcha() {
|
||||
if (!this.submitForm.lxfs) {
|
||||
this.$message.warning('请先填写联系方式');
|
||||
this.touchSubmitField('lxfs');
|
||||
return;
|
||||
}
|
||||
this.$message.success('验证码已发送');
|
||||
},
|
||||
async handleSubmitForm() {
|
||||
if (!this.ensureLogin('提交收录')) {
|
||||
return;
|
||||
@ -774,12 +779,12 @@ export default {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 提交人信息由登录 token 在后端解析,前端不再传昵称/联系方式
|
||||
await api.gxfb({
|
||||
ywlxDm: '01',
|
||||
bt1: this.submitForm.bt1,
|
||||
fwnr: this.submitForm.jj,
|
||||
bqjh: this.submitForm.bq,
|
||||
qymc: this.submitForm.nc,
|
||||
});
|
||||
this.$message.success('提交成功,请等待审核');
|
||||
this.resetSubmitForm();
|
||||
@ -1245,40 +1250,6 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
/* Figma Tag:bg #eefae2 / 字 #00b96b / 13px / py2 px8 / 圆角2 / 间距4 */
|
||||
.gxnlpt-card-tags {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
align-items: flex-start;
|
||||
align-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.gxnlpt-tag {
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
padding: 2px 8px;
|
||||
overflow: hidden;
|
||||
font-family: @home-font-family;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: @home-color-primary-green;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-break: break-word;
|
||||
background: #eefae2;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.gxnlpt-more-wrap {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
@ -1393,18 +1364,16 @@ export default {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.gxnlpt-submit-columns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 32px 40px;
|
||||
.gxnlpt-submit-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gxnlpt-submit-col-title {
|
||||
margin: 0 0 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: @home-color-primary-dark;
|
||||
.gxnlpt-submit-row-2 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0 24px;
|
||||
}
|
||||
|
||||
.gxnlpt-field {
|
||||
@ -1461,31 +1430,6 @@ export default {
|
||||
color: @gxnlpt-form-error;
|
||||
}
|
||||
|
||||
.gxnlpt-captcha-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.gxnlpt-captcha-row .gxnlpt-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.gxnlpt-captcha-btn {
|
||||
flex-shrink: 0;
|
||||
min-height: 40px;
|
||||
padding: 0 16px;
|
||||
font-family: @home-font-family;
|
||||
font-size: 14px;
|
||||
color: @home-color-primary-green;
|
||||
cursor: pointer;
|
||||
border: 1px solid @home-color-primary-green;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.gxnlpt-submit-btn {
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
@ -1561,7 +1505,7 @@ export default {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.gxnlpt-submit-columns {
|
||||
.gxnlpt-submit-row-2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
@ -47,7 +47,10 @@
|
||||
<div class="">
|
||||
<div class="content-wrapper2">
|
||||
<div class="text-section">
|
||||
<div class="text-section-title">法案解读</div>
|
||||
<div class="text-section-head-row">
|
||||
<div class="text-section-title">法案解读</div>
|
||||
<div class="text-section-title-dcfa">申请服务</div>
|
||||
</div>
|
||||
<div class="text-section-content">
|
||||
<div class="content-item1">
|
||||
<ul class="info-list">
|
||||
@ -64,7 +67,6 @@
|
||||
</div>
|
||||
<div class="content-item2">
|
||||
<div class="service-section-dcfa">
|
||||
<div class="text-section-title-dcfa">申请服务</div>
|
||||
<div class="dcfa-cards-stack">
|
||||
<div class="apply-service-card card1">
|
||||
<div class="card-item1">
|
||||
@ -114,7 +116,10 @@
|
||||
|
||||
<div class="content-wrapper2">
|
||||
<div class="text-section qych-policy-frame">
|
||||
<div class="text-section-title">政策解读</div>
|
||||
<div class="text-section-head-row">
|
||||
<div class="text-section-title">政策解读</div>
|
||||
<div class="text-section-title-dcfa">CBAM试算</div>
|
||||
</div>
|
||||
<div class="text-section-content">
|
||||
<div class="content-item1">
|
||||
<div class="info-list info-list--stacked">
|
||||
@ -143,7 +148,6 @@
|
||||
</div>
|
||||
<div class="content-item2">
|
||||
<div class="service-section-dcfa">
|
||||
<div class="text-section-title-dcfa">CBAM试算</div>
|
||||
<div class="dcfa-cards-stack">
|
||||
<div class="apply-service-card card1">
|
||||
<div class="card-item1 card-item--cbam-primary">
|
||||
@ -194,7 +198,10 @@
|
||||
|
||||
<div class="content-wrapper2">
|
||||
<div class="text-section">
|
||||
<div class="text-section-title">低碳政策</div>
|
||||
<div class="text-section-head-row">
|
||||
<div class="text-section-title">低碳政策</div>
|
||||
<div class="text-section-title-dcfa">申请服务</div>
|
||||
</div>
|
||||
<div class="text-section-content">
|
||||
<div class="content-item1">
|
||||
<ul class="info-list">
|
||||
@ -211,7 +218,6 @@
|
||||
</div>
|
||||
<div class="content-item2">
|
||||
<div class="service-section-dcfa">
|
||||
<div class="text-section-title-dcfa">申请服务</div>
|
||||
<div class="dcfa-cards-stack">
|
||||
<div class="apply-service-card card1">
|
||||
<div class="card-item1 card-item--with-action">
|
||||
@ -755,6 +761,18 @@ body {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.text-section-head-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) @qych-policy-sidebar-width;
|
||||
column-gap: @qych-policy-content-gap;
|
||||
align-items: start;
|
||||
padding: 0 @qych-policy-content-px;
|
||||
box-sizing: border-box;
|
||||
border-top-left-radius: @qych-policy-card-radius;
|
||||
border-top-right-radius: @qych-policy-card-radius;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.text-section-title {
|
||||
height: auto;
|
||||
padding: @qych-policy-section-title-py @qych-policy-section-title-px @qych-policy-section-title-pb;
|
||||
@ -764,8 +782,15 @@ body {
|
||||
line-height: normal;
|
||||
letter-spacing: 0;
|
||||
color: @qych-policy-label-color;
|
||||
border-top-left-radius: @qych-policy-card-radius;
|
||||
border-top-right-radius: @qych-policy-card-radius;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.text-section-head-row .text-section-title-dcfa {
|
||||
align-self: start;
|
||||
padding: @qych-policy-section-title-py 0 @qych-policy-section-title-pb;
|
||||
margin: 0;
|
||||
line-height: normal;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.text-section-content {
|
||||
@ -804,7 +829,7 @@ body {
|
||||
display: flex;
|
||||
flex: 0 0 @qych-policy-sidebar-width;
|
||||
flex-direction: column;
|
||||
align-self: stretch;
|
||||
align-self: flex-start;
|
||||
width: @qych-policy-sidebar-width;
|
||||
min-width: @qych-policy-sidebar-width;
|
||||
height: auto;
|
||||
@ -812,12 +837,13 @@ body {
|
||||
|
||||
.service-section-dcfa {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex: 0 0 auto;
|
||||
flex-direction: column;
|
||||
gap: @qych-policy-content-gap;
|
||||
gap: 0;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
align-self: stretch;
|
||||
height: auto;
|
||||
align-self: flex-start;
|
||||
width: 100%;
|
||||
padding-left: @qych-policy-panel-padding;
|
||||
background: @home-color-white;
|
||||
border-left: 1px solid @qych-policy-panel-bg;
|
||||
@ -837,10 +863,11 @@ body {
|
||||
|
||||
.dcfa-cards-stack {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex: 0 0 auto;
|
||||
flex-direction: column;
|
||||
gap: @qych-policy-card-gap;
|
||||
gap: 12px;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
@ -851,7 +878,7 @@ body {
|
||||
|
||||
.apply-service-card {
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
flex: 0 0 auto;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
@ -879,14 +906,14 @@ body {
|
||||
.card-item2 {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
flex: 0 0 auto;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
min-height: @qych-policy-card-min-height;
|
||||
min-height: 132px;
|
||||
height: auto;
|
||||
padding: @qych-policy-card-padding-y @qych-policy-card-padding-x @qych-policy-card-padding-bottom;
|
||||
padding: 20px @qych-policy-card-padding-x 16px;
|
||||
border-radius: @qych-policy-card-radius-inner;
|
||||
overflow: hidden;
|
||||
cursor: default;
|
||||
@ -894,7 +921,7 @@ body {
|
||||
}
|
||||
|
||||
.card-item--with-action {
|
||||
padding-top: 24px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
@ -985,16 +1012,28 @@ body {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#section0 .text-section {
|
||||
gap: @qych-policy-card-inner-gap-battery;
|
||||
#section0 .text-section,
|
||||
#section1 .text-section.qych-policy-frame,
|
||||
#section2 .text-section {
|
||||
gap: @qych-policy-card-inner-gap;
|
||||
}
|
||||
|
||||
#section0 .text-section-title {
|
||||
#section0 .text-section-head-row .text-section-title,
|
||||
#section1 .text-section-head-row .text-section-title,
|
||||
#section2 .text-section-head-row .text-section-title {
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
#section0 .text-section-head-row {
|
||||
background: @qych-policy-header-gradient-battery;
|
||||
}
|
||||
|
||||
#section0 .text-section-head-row .text-section-title-dcfa {
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
#section0 .apply-service-card .card-item1 {
|
||||
background-image: @qych-policy-card-gradient-battery-1;
|
||||
}
|
||||
@ -1008,7 +1047,7 @@ body {
|
||||
.banner2 .text-section.qych-policy-frame {
|
||||
min-height: @qych-policy-card-height;
|
||||
height: @qych-policy-card-height;
|
||||
gap: @qych-policy-card-inner-gap-battery;
|
||||
gap: @qych-policy-card-inner-gap;
|
||||
}
|
||||
|
||||
#section1 .text-section.qych-policy-frame .text-section-content,
|
||||
@ -1018,21 +1057,21 @@ body {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#section1 .text-section.qych-policy-frame .text-section-title,
|
||||
.banner2 .text-section.qych-policy-frame .text-section-title {
|
||||
padding-top: @qych-policy-section-title-py;
|
||||
padding-bottom: @qych-policy-section-title-py;
|
||||
}
|
||||
|
||||
#section1 .text-section.qych-policy-frame .apply-service-card h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#section1 .text-section.qych-policy-frame .text-section-title,
|
||||
.banner2 .text-section.qych-policy-frame .text-section-title {
|
||||
#section1 .text-section-head-row,
|
||||
.banner2 .text-section-head-row {
|
||||
background: @qych-policy-header-gradient-cbam;
|
||||
}
|
||||
|
||||
#section1 .text-section-head-row .text-section-title-dcfa,
|
||||
.banner2 .text-section-head-row .text-section-title-dcfa {
|
||||
padding-top: @qych-policy-section-title-py;
|
||||
padding-bottom: @qych-policy-section-title-py;
|
||||
}
|
||||
|
||||
#section1 .apply-service-card .card-item1,
|
||||
.banner2 .apply-service-card .card-item1 {
|
||||
background-image: @qych-policy-card-gradient-cbam-1;
|
||||
@ -1057,11 +1096,17 @@ body {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#section2 .text-section-title,
|
||||
.banner3 .text-section-title {
|
||||
#section2 .text-section-head-row,
|
||||
.banner3 .text-section-head-row {
|
||||
background: @qych-policy-header-gradient-shipping;
|
||||
}
|
||||
|
||||
#section2 .text-section-head-row .text-section-title-dcfa,
|
||||
.banner3 .text-section-head-row .text-section-title-dcfa {
|
||||
padding-top: @qych-policy-section-title-py;
|
||||
padding-bottom: @qych-policy-section-title-py;
|
||||
}
|
||||
|
||||
#section2 .apply-service-card .card-item1,
|
||||
.banner3 .apply-service-card .card-item1 {
|
||||
background-image: @qych-policy-card-gradient-shipping-1;
|
||||
@ -1074,7 +1119,7 @@ body {
|
||||
|
||||
#section2 .apply-service-card .card-item1,
|
||||
#section2 .apply-service-card .card-item2 {
|
||||
padding-top: 24px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.banner1.qych-snap-section {
|
||||
@ -1348,6 +1393,44 @@ body {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
#anchor-container .banner.qych-snap-section .text-section-head-row {
|
||||
grid-template-columns: minmax(0, 1fr) 260px;
|
||||
}
|
||||
|
||||
#anchor-container .banner.qych-snap-section .content-item2 {
|
||||
flex: 0 0 260px;
|
||||
width: 260px;
|
||||
min-width: 260px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
#anchor-container .banner.qych-snap-section .text-section-head-row {
|
||||
grid-template-columns: 1fr;
|
||||
row-gap: 4px;
|
||||
}
|
||||
|
||||
#anchor-container .banner.qych-snap-section .text-section-content {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
#anchor-container .banner.qych-snap-section .content-item2 {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#anchor-container .banner.qych-snap-section .service-section-dcfa {
|
||||
padding-left: 0;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
#anchor-container .banner.qych-snap-section .card-item1,
|
||||
#anchor-container .banner.qych-snap-section .card-item2 {
|
||||
min-height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
|
||||
38
txw-mhzc-web/tests/unit/gxnlpt-tag-overflow.test.js
Normal file
38
txw-mhzc-web/tests/unit/gxnlpt-tag-overflow.test.js
Normal file
@ -0,0 +1,38 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const {
|
||||
calcTagOverflowLayout,
|
||||
formatHiddenTagsTooltip,
|
||||
} = require('../../src/pages/index/utils/gxnlpt-tag-overflow.js');
|
||||
|
||||
test('calcTagOverflowLayout shows all tags when width is enough', () => {
|
||||
const widths = [40, 50, 36];
|
||||
const result = calcTagOverflowLayout(widths, 200, { gap: 4, ellipsisWidth: 12 });
|
||||
assert.equal(result.visible, 3);
|
||||
assert.equal(result.overflow, false);
|
||||
});
|
||||
|
||||
test('calcTagOverflowLayout truncates with ellipsis when width is tight', () => {
|
||||
const widths = [48, 52, 44, 40];
|
||||
const result = calcTagOverflowLayout(widths, 120, { gap: 4, ellipsisWidth: 12 });
|
||||
assert.ok(result.overflow);
|
||||
assert.ok(result.visible < 4);
|
||||
assert.ok(result.visible >= 1);
|
||||
});
|
||||
|
||||
test('calcTagOverflowLayout keeps one tag with overflow flag when only one fits with ellipsis', () => {
|
||||
const widths = [80, 60, 60];
|
||||
const result = calcTagOverflowLayout(widths, 70, { gap: 4, ellipsisWidth: 12 });
|
||||
assert.equal(result.visible, 1);
|
||||
assert.equal(result.overflow, true);
|
||||
});
|
||||
|
||||
test('calcTagOverflowLayout handles empty input', () => {
|
||||
assert.deepEqual(calcTagOverflowLayout([], 100), { visible: 0, overflow: false });
|
||||
});
|
||||
|
||||
test('formatHiddenTagsTooltip joins hidden tags', () => {
|
||||
assert.equal(formatHiddenTagsTooltip(['交易', '全国']), '交易、全国');
|
||||
assert.equal(formatHiddenTagsTooltip([]), '');
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user