Files
YschcmsFront/src/views/cms/components/SiteCard.vue
T
chenxing cff4325947
build / build (push) Successful in 2m20s
[CMS-VISUAL] 站点管理改版: 表格→站点工作台(主站宽卡+院系子站卡片网格)+ 页头承载站点身份 + v3.1 视觉修订(182 测试绿)
- 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 视觉修订与像素对照表)
2026-09-28 18:32:13 +08:00

262 lines
7.3 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.
<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):站点工作台网格单元。
* 信息按「身份 → 规模 → 皮肤 → 地址 → 动作」纵向分层,不再与表格争列宽。
* 当前站 = 蓝描边 + 「当前」绿徽章;停用 = 降透明 + 灰图标 + 「进入管理」禁用。
*/
const props = defineProps<{
site: SiteVo;
isCurrent: boolean;
theme?: ThemeVo;
}>();
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);
/**
* 规模统计:SiteVo 暂未提供聚合字段(docs/22 Q1),故当前渲染为空、该行自动隐藏;
* 后端在 /api/site/list 补齐 categoryCount / articleCount / updateTime 后自动出现,模板不动。
*/
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: "文章" });
if (s.updateTime) list.push({ num: String(s.updateTime).slice(5, 10), lbl: "更新" });
return list;
});
/** 皮肤缩略图(走 assetUrl 补资源前缀,与表格视图同源) */
const themeThumb = computed(() => (props.theme?.screenshotUrl ? assetUrl(props.theme.screenshotUrl) : ""));
const themeTitle = computed(() => props.theme?.title || props.site.theme || "default");
</script>
<template>
<div class="site-card" :class="{ 'is-cur': isCurrent, disabled }">
<div class="sc-head">
<span class="sc-ico" :class="disabled ? 'off' : 'sub'">
<Icon type="ios-cog" />
</span>
<div class="sc-id">
<div class="sc-name">
<h4 :title="site.name">{{ site.name }}</h4>
<span class="sc-badge sub">院系子站</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.path || site.code }}</p>
</div>
</div>
<div v-if="stats.length" class="sc-meta">
<span v-for="st in stats" :key="st.lbl">{{ st.lbl }} <b>{{ st.num }}</b></span>
</div>
<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>
<p class="sc-domain">
<Icon type="ios-globe-outline" />
<code :title="portalUrlOf(site)">{{ portalUrlOf(site) }}</code>
</p>
<div class="sc-actions">
<Button type="primary" size="small" :disabled="enterDisabled" @click="emit('enter', site)">
进入管理
</Button>
<Button size="small" @click="emit('setting', site)">站点设置</Button>
<Button size="small" @click="emit('portal', site)">门户</Button>
</div>
</div>
</template>
<style scoped lang="scss">
/* 与超管 SchoolWorkbench 的卡片语法同构:白底 8px 圆角 + 1px 边 + hover 抬升 2px */
.site-card {
position: relative;
overflow: hidden;
background: #fff;
border: 1px solid #e8eaec;
border-radius: 8px;
padding: 16px;
transition: box-shadow 0.2s, transform 0.2s, border-color 0.2s;
&:hover {
box-shadow: 0 4px 16px rgba(31, 42, 58, 0.12);
transform: translateY(-2px);
border-color: #c9d6fb;
}
/* 当前操作站点:4px 左色带 + 蓝描边 + 淡蓝光环。
与 SiteMainCard 同一套语义 —— 全页同时只有一张卡带「选中」,不再出现第二处蓝色标识。 */
&.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;
&:hover {
transform: none;
box-shadow: 0 1px 2px rgba(31, 42, 58, 0.06);
}
}
}
.sc-head {
display: flex;
align-items: flex-start;
gap: 10px;
}
/* 身份色:主站 #577afb / 子站 #ff9900(与 SiteSwitcher 定稿身份色一致) */
.sc-ico {
flex-shrink: 0;
width: 34px;
height: 34px;
border-radius: 7px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #fff;
background: #577afb;
&.sub {
background: #ff9900;
}
&.off {
background: #c0c4cc;
}
}
.sc-id {
min-width: 0;
flex: 1;
}
.sc-name {
display: flex;
align-items: center;
gap: 6px;
h4 {
margin: 0;
font-size: 15px;
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;
&.sub {
background: #ff9900;
}
&.cur {
background: #2ba471;
}
&.off {
background: #c0c4cc;
}
}
.sc-code {
margin: 3px 0 0;
font-size: 12px;
color: #808695;
font-family: Consolas, Monaco, monospace;
}
.sc-meta {
display: flex;
gap: 14px;
margin: 11px 0 0;
font-size: 12px;
color: #515a6e;
b {
color: #17233d;
}
}
.sc-theme {
display: flex;
align-items: center;
gap: 8px;
margin: 10px 0 0;
padding-top: 10px;
border-top: 1px dashed #eceff3;
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;
}
}
.sc-domain {
margin: 8px 0 0;
font-size: 12px;
color: #808695;
display: flex;
align-items: center;
gap: 5px;
code {
font-family: Consolas, Monaco, monospace;
color: #656667;
background: #f5f7fa;
border-radius: 3px;
padding: 1px 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.sc-actions {
display: flex;
gap: 8px;
margin: 14px 0 0;
/* 三个动作等宽铺满(iview Button 是子组件根节点,scoped 属性会下发到根 DOM) */
.ivu-btn {
flex: 1;
padding-left: 0;
padding-right: 0;
}
}
</style>