[CMS-VISUAL] 站点管理改版: 表格→站点工作台(主站宽卡+院系子站卡片网格)+ 页头承载站点身份 + v3.1 视觉修订(182 测试绿)
build / build (push) Successful in 2m20s
build / build (push) Successful in 2m20s
- SiteManage.vue: 8 列表格改为站点工作台,按 schId 分组「主站宽卡 + 院系子站网格」, 保留卡片⇄表格视图切换与搜索过滤;scope=all 全局视图支持多校分组,搜索时主站卡始终保留(层级锚点) - 新增 views/cms/components/SiteCard.vue、SiteMainCard.vue - 删除 components/SiteContextCard.vue: 站点身份改由页头「学校名.站点名」承担(SchoolLayout), 侧栏不再铺第二份站点列表,切站入口从 3 处收敛到 2 处(面包屑 + 站点工作台) - v3.1 视觉修订: 色带语义统一为「当前站」(全页同时只有一张卡带选中态,不再一页两处蓝); 删除面包屑 band-sub 橙色接缝条(身份已收回页头);返回按钮 ios-undo 18×18 → ios-arrow-back 26×26 可点方块; 删除 SiteManage .zone-tip 设计说明文字(误当 UI 渲染) - 测试: siteLayer.spec.ts 站点卡 4 用例重写为页头反向/正向断言,siteManage.spec.ts 补卡片视图断言;182 用例绿 - 方案: docs/22-站点管理修订方案.md(含 §9 v3.1 视觉修订与像素对照表)
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { assetUrl } from "@/api/module/cms";
|
||||
import { portalUrlOf } from "@/utils/sitePath";
|
||||
import { SiteVo, ThemeVo } from "@/types/cms";
|
||||
|
||||
/**
|
||||
* 学校主站宽卡(docs/22):站点工作台的层级锚点。
|
||||
* 主站唯一、子站都挂在它下面 —— 横向宽卡独占一行(左侧 4px 主色带),
|
||||
* 与子站网格在版式上分区;层级不靠「所属主站」列的文字表达(那是旧表格的做法)。
|
||||
*/
|
||||
const props = defineProps<{
|
||||
site: SiteVo;
|
||||
isCurrent: boolean;
|
||||
theme?: ThemeVo;
|
||||
/** 本校院系子站总数(由父级按全量行统计,搜索态下不随过滤变化) */
|
||||
subCount: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "enter", site: SiteVo): void;
|
||||
(e: "setting", site: SiteVo): void;
|
||||
(e: "portal", site: SiteVo): void;
|
||||
}>();
|
||||
|
||||
const disabled = computed(() => props.site.status !== 1);
|
||||
const enterDisabled = computed(() => disabled.value || props.isCurrent);
|
||||
|
||||
/** 统计:子站数本地可算(必显);栏目/文章同 SiteCard —— 后端补齐字段后自动出现 */
|
||||
const stats = computed(() => {
|
||||
const s = props.site as any;
|
||||
const list: { num: string | number; lbl: string }[] = [];
|
||||
if (s.categoryCount != null) list.push({ num: s.categoryCount, lbl: "栏目" });
|
||||
if (s.articleCount != null) list.push({ num: s.articleCount, lbl: "文章" });
|
||||
list.push({ num: props.subCount, lbl: "子站" });
|
||||
return list;
|
||||
});
|
||||
|
||||
const themeThumb = computed(() => (props.theme?.screenshotUrl ? assetUrl(props.theme.screenshotUrl) : ""));
|
||||
const themeTitle = computed(() => props.theme?.title || props.site.theme || "default");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main-card" :class="{ 'is-cur': isCurrent, disabled }">
|
||||
<div class="mc-body">
|
||||
<div class="mc-left">
|
||||
<span class="sc-ico" :class="disabled ? 'off' : ''">
|
||||
<Icon type="ios-school" />
|
||||
</span>
|
||||
<div class="sc-id">
|
||||
<div class="sc-name">
|
||||
<h4 :title="site.name">{{ site.name }}</h4>
|
||||
<span class="sc-badge main">主站</span>
|
||||
<span v-if="isCurrent" class="sc-badge cur">当前</span>
|
||||
<span v-else-if="disabled" class="sc-badge off">停用</span>
|
||||
</div>
|
||||
<p class="sc-code">{{ site.code }} · {{ portalUrlOf(site) }}</p>
|
||||
<div class="sc-theme">
|
||||
<span class="thumb" :style="themeThumb ? { backgroundImage: `url(${themeThumb})` } : null"></span>
|
||||
<span class="tname" :title="themeTitle">
|
||||
{{ themeTitle }}
|
||||
<span v-if="site.theme" class="tspec">{{ site.theme }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mc-stats">
|
||||
<div class="st" v-for="st in stats" :key="st.lbl">
|
||||
<div class="num">{{ st.num }}</div>
|
||||
<div class="lbl">{{ st.lbl }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mc-actions">
|
||||
<Button type="primary" :disabled="enterDisabled" @click="emit('enter', site)">进入管理</Button>
|
||||
<Button @click="emit('setting', site)">站点设置</Button>
|
||||
<Button @click="emit('portal', site)">门户预览</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 主站宽卡:与子站卡共用同一套「当前站」语义 —— 4px 左色带 + 蓝描边 + 淡蓝光环,
|
||||
全页同时只有一张卡带选中样式(原色带常驻表达「根节点」,会被误读成选中态)。
|
||||
层级改由版式表达:宽卡独占一行 + 「主站」徽章 + 「N 子站」统计。
|
||||
色带走 ::before 绝对定位而非 border-left —— 用边框会在切换态时把内容挤偏 3px。 */
|
||||
.main-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
border: 1px solid #e8eaec;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 26px;
|
||||
&.is-cur {
|
||||
border-color: #9db4f7;
|
||||
box-shadow: 0 0 0 2px rgba(87, 122, 251, 0.12);
|
||||
}
|
||||
&.is-cur::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 4px;
|
||||
background: #577afb;
|
||||
}
|
||||
&.disabled {
|
||||
opacity: 0.62;
|
||||
background: #fafbfc;
|
||||
}
|
||||
}
|
||||
.mc-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
padding: 18px 20px;
|
||||
}
|
||||
.mc-left {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.sc-ico {
|
||||
flex-shrink: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 7px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 19px;
|
||||
color: #fff;
|
||||
background: #577afb;
|
||||
&.off {
|
||||
background: #c0c4cc;
|
||||
}
|
||||
}
|
||||
.sc-id {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.sc-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
color: #17233d;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
.sc-badge {
|
||||
flex-shrink: 0;
|
||||
font-size: 10px;
|
||||
line-height: 16px;
|
||||
padding: 0 5px;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
&.main {
|
||||
background: #577afb;
|
||||
}
|
||||
&.cur {
|
||||
background: #2ba471;
|
||||
}
|
||||
&.off {
|
||||
background: #c0c4cc;
|
||||
}
|
||||
}
|
||||
.sc-code {
|
||||
margin: 3px 0 0;
|
||||
font-size: 12px;
|
||||
color: #808695;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
}
|
||||
.sc-theme {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 6px 0 0;
|
||||
font-size: 12px;
|
||||
color: #656667;
|
||||
.thumb {
|
||||
width: 52px;
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e8eaec;
|
||||
flex-shrink: 0;
|
||||
background: #f0f2f5 center/cover no-repeat;
|
||||
}
|
||||
.tname {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tspec {
|
||||
color: #a0a6b0;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
.mc-stats {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
.st {
|
||||
padding: 0 18px;
|
||||
border-left: 1px solid #eef0f3;
|
||||
text-align: center;
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
.num {
|
||||
font-size: 19px;
|
||||
font-weight: bold;
|
||||
color: #17233d;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.lbl {
|
||||
font-size: 11px;
|
||||
color: #a0a6b0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
.mc-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user