[CMS-VISUAL] F5: 页脚结构示意图(六处位置标注+实时回显)+ favicon 1:1/二维码 4:3 占位框与比例预览(180 测试绿)

This commit is contained in:
chenxing
2026-09-28 14:59:05 +08:00
parent 49678a6568
commit 6334b24d34
5 changed files with 337 additions and 4 deletions
+171
View File
@@ -0,0 +1,171 @@
<script setup lang="ts">
import { assetUrl } from "@/api/module/cms";
/**
* F5(docs/20)页脚结构示意图:页脚 Tab 内的固定示意图,标注各字段在门户页脚中的视觉位置——
* 品牌区(站名+联系方式+多校区地址行)/ 二维码区(微信+微博,4:3)/ 社媒图标行 / 版权备案行。
* 已填字段实时回显内容(填完即知页脚长什么样);空字段以灰字占位标注。
*/
const props = defineProps<{
contact?: string;
footerAddresses?: string;
qrWechat?: string;
qrWeibo?: string;
socials?: { label: string; value?: string }[];
copyright?: string;
beian?: string;
}>();
const addressLines = (props.footerAddresses || "")
.split(/\r?\n/)
.map(l => l.trim())
.filter(Boolean)
.slice(0, 3);
</script>
<template>
<div class="footer-preview">
<div class="fp-caption">页脚结构示意(下图与字段一一对应,已填内容实时回显)</div>
<div class="fp-frame" role="img" aria-label="页脚结构示意图">
<div class="fp-brand">
<div class="fp-logo">
<span class="fp-logo-mark">LOGO</span>
<span class="fp-site">XX学校</span>
</div>
<p class="fp-line" :class="{ filled: contact }">
{{ contact || "① 联系方式(页脚品牌区)" }}
</p>
<p v-for="(l, i) in addressLines" :key="i" class="fp-line small filled">{{ l }}</p>
<p v-if="!addressLines.length" class="fp-line small">② 多校区地址(每行一个校区)</p>
</div>
<div class="fp-qr">
<div class="fp-qr-box">
<img v-if="qrWechat" :src="assetUrl(qrWechat)" alt="微信二维码预览" />
<span v-else>微信二维码<br />③ 4:3</span>
</div>
<div class="fp-qr-box">
<img v-if="qrWeibo" :src="assetUrl(qrWeibo)" alt="微博二维码预览" />
<span v-else>微博二维码<br />③ 4:3</span>
</div>
</div>
<div class="fp-social">
<span class="fp-social-label">④ 社媒图标行:</span>
<span v-for="s in socials" :key="s.label" class="fp-social-item" :class="{ on: s.value }">
{{ s.label }}
</span>
</div>
<div class="fp-bottom">
<span :class="{ filled: copyright }">{{ copyright || "⑤ 版权文字" }}</span>
<span :class="{ filled: beian }" class="fp-beian">{{ beian || "⑥ 备案号" }}</span>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.footer-preview {
margin-top: 6px;
.fp-caption {
color: #8a93a6;
font-size: 12px;
margin-bottom: 8px;
}
.fp-frame {
border: 1px solid #e9edf5;
border-radius: 8px;
background: linear-gradient(180deg, #1f3a5f 0%, #16294a 100%);
color: rgba(255, 255, 255, 0.5);
padding: 18px 22px 12px;
display: grid;
grid-template-columns: 1fr auto;
gap: 10px 18px;
font-size: 12px;
line-height: 1.7;
.fp-brand {
.fp-logo {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
.fp-logo-mark {
border: 1px dashed rgba(255, 255, 255, 0.35);
border-radius: 4px;
padding: 2px 8px;
font-size: 11px;
}
.fp-site {
color: rgba(255, 255, 255, 0.75);
font-weight: 600;
}
}
.fp-line {
margin: 2px 0;
&.small {
font-size: 11px;
opacity: 0.9;
}
&.filled {
color: #fff;
}
}
}
.fp-qr {
display: flex;
gap: 12px;
align-items: flex-start;
.fp-qr-box {
width: 84px;
height: 63px;
border: 1px dashed rgba(255, 255, 255, 0.4);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-size: 10px;
position: relative;
overflow: hidden;
img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
}
}
.fp-social {
grid-column: 1 / -1;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
.fp-social-label {
color: rgba(255, 255, 255, 0.45);
}
.fp-social-item {
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
padding: 1px 10px;
font-size: 11px;
opacity: 0.55;
&.on {
opacity: 1;
border-color: rgba(255, 255, 255, 0.85);
color: #fff;
}
}
}
.fp-bottom {
grid-column: 1 / -1;
border-top: 1px solid rgba(255, 255, 255, 0.15);
padding-top: 8px;
display: flex;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
font-size: 11px;
.filled {
color: rgba(255, 255, 255, 0.85);
}
}
}
}
</style>
+36 -1
View File
@@ -15,6 +15,9 @@ const props = defineProps<{
/** 附件场景(空=不带 scene;svc 场景已迁办事大厅,CMS 内为 article/banner 等) */
scene?: string;
placeholder?: string;
/** F5(docs/20):预览/占位框比例("1:1" favicon / "4:3" 二维码;缺省 8:5 通用横图),
* 无值时按比例显示虚线占位框,帮助确认最终在门户中的尺寸感 */
aspect?: "1:1" | "4:3";
}>();
const emit = defineEmits<{ (e: "update:modelValue", v: string): void }>();
@@ -76,12 +79,16 @@ function clearValue() {
>
<Button size="small" icon="ios-cloud-upload-outline" :loading="uploading">上传图片</Button>
</Upload>
<span v-else class="iaf-thumb">
<span v-else class="iaf-thumb" :class="aspect ? 'iaf-thumb-' + aspect.replace(':', 'x') : ''">
<img :src="previewSrc" alt="" />
<span class="iaf-thumb-del" title="清除" @click="clearValue">
<Icon type="ios-close" />
</span>
</span>
<!-- F5:无值时按字段比例显示虚线占位框(尺寸感示意) -->
<span v-if="!modelValue && aspect" class="iaf-holder" :class="'iaf-holder-' + aspect!.replace(':', 'x')">
{{ aspect }} 位
</span>
<Button size="small" type="text" class="iaf-toggle" @click="manualOpen = !manualOpen">
{{ manualOpen ? "收起" : "手动粘贴路径" }}
</Button>
@@ -123,6 +130,16 @@ function clearValue() {
border: 1px solid #dcdee2;
background: #f8f8f9;
}
&.iaf-thumb-1x1 img {
width: 48px;
height: 48px;
object-fit: contain;
}
&.iaf-thumb-4x3 img {
width: 88px;
height: 66px;
object-fit: contain;
}
.iaf-thumb-del {
position: absolute;
top: -7px;
@@ -142,6 +159,24 @@ function clearValue() {
display: block;
}
}
.iaf-holder {
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px dashed #cfd8e6;
border-radius: 4px;
color: #b4bfce;
font-size: 11px;
background: #fafbfd;
&.iaf-holder-1x1 {
width: 48px;
height: 48px;
}
&.iaf-holder-4x3 {
width: 88px;
height: 66px;
}
}
.iaf-manual {
max-width: 420px;
margin-top: 6px;