feat:1.标签多余及省略 2.调整政策页面 3.布局优化

This commit is contained in:
liulong 2026-05-25 21:23:43 +08:00
parent 1888d64850
commit 5147a55915
8 changed files with 587 additions and 232 deletions

View File

@ -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",

View 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('、');
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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
* 关闭后恢复每卡 23 个短标签的正常展示
*/
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 Tagbg #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;
}

View File

@ -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) {

View 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([]), '');
});