build / build (push) Successful in 46s
① 选中态不再「挤」:星期数 <span> 加 padding-bottom:3px(数字位置不动,把今日方块与 圆点整体让下去)+ 圆点 bottom -8→-11px(今日格 -5.5→-8.5px,七格仍同线) ⇒ 星期数↔方块 4.1→6.6px、方块↔圆点 1.5→4.5px,七日条 63→66px ⚠️ 用 padding-bottom 而非 margin-bottom:后者会与 b 的 margin-top 折叠 (max(正)+min(负)),今日格 −1px 与其余格 2px 走不同结果 ⇒ 今日数字偏低 1.75px ② 时间列移到信息行最右端:列表 DOM 由 .line[标题+时间] 改为 content(独占一行) + .meta[.meta-txt + .time];.meta 用 flex space-between、 .time flex:0 0 auto;无时间不留位。标题可用宽度 232→314px(带时间那条) ③ 标题字号 15→14px(信息行 12.5px 不变) ④ 结构化骨架同步:星期条占位改 24px 外框(21 行盒 + 3 下留白)+ 内层 ::before 画 10px 条; ⚠️ 内层定位用父级 padding-top,不用子元素 margin-top(会塌出父级多 5.5px) harness 真组件复测:四态 367.58/367.59/367.58/367.58(差 0.01px);今日墨迹中心 87 vs 其余 87.25(0.25px);七格圆点中心全 107.5;.time 父级=meta、content 14px/block。
744 lines
30 KiB
Vue
744 lines
30 KiB
Vue
<template>
|
||
<div class="wg-weekplan" ref="refRoot">
|
||
<!-- ④ 「查看概览」:Teleport 进卡片 header 右侧(index.vue 埋的 #wg-hdr-<id> 锚点,
|
||
与课程表班级切换器同一机制);a 标签直跳子应用,加载中/空态都在位,不随周数据闪灭 -->
|
||
<Teleport v-if="overviewUrl" :to="'#wg-hdr-' + widgetId">
|
||
<a class="ov-link" :href="overviewUrl" target="_blank" rel="noopener">查看概览</a>
|
||
</Teleport>
|
||
|
||
<!-- 加载态:结构化骨架。各段与真实内容同构同高(标题 40 + 七日条 63 + 列表 171 + 备注区 66.6),
|
||
换周不再「先塌再弹」;方案 B 下加载/空周/有备注周三态恒等 ≈364.6px -->
|
||
<div v-if="loading" class="wk-skeleton" aria-hidden="true">
|
||
<div class="sk-title">
|
||
<i class="bar" style="width: 46%"></i>
|
||
<span class="sk-pager"><i></i><i></i></span>
|
||
</div>
|
||
<div class="sk-days">
|
||
<div class="sd on"><span class="w"></span><b><i></i></b></div>
|
||
<div class="sd"><span class="w"></span><b><i></i></b></div>
|
||
<div class="sd"><span class="w"></span><b><i></i></b></div>
|
||
<div class="sd"><span class="w"></span><b><i></i></b></div>
|
||
<div class="sd"><span class="w"></span><b><i></i></b></div>
|
||
<div class="sd"><span class="w"></span><b><i></i></b></div>
|
||
<div class="sd"><span class="w"></span><b><i></i></b></div>
|
||
</div>
|
||
<div class="sk-list">
|
||
<div class="sr"><div class="l"><span class="c"></span><span class="t"></span></div><div class="m"></div></div>
|
||
<div class="sr"><div class="l"><span class="c"></span><span class="t"></span></div><div class="m"></div></div>
|
||
<div class="sr"><div class="l"><span class="c"></span><span class="t"></span></div><div class="m"></div></div>
|
||
</div>
|
||
<div class="sk-remark-slot"><i class="b1"></i><i class="b2"></i></div>
|
||
</div>
|
||
|
||
<!-- 门控用 hasWeek(有 title 的周对象)而非 hasData:空周(hasData=false)
|
||
也必须保留标题行 + 翻页箭头,否则用户翻进空周后没有出口 -->
|
||
<template v-else-if="hasWeek">
|
||
<div class="wk-title">
|
||
<!-- 显示用简称(日期区间 + 第N周);接口全称(如「学校工作安排」)留在 title 里 hover 可见 -->
|
||
<span :title="titleFull">{{ titleStr }}</span>
|
||
<!-- 翻页:边界由响应 minWeek/maxWeek 驱动(缺省按 [0,0] 禁用两侧);越界 oapi 返回 code:2,前端先拦 -->
|
||
<span class="pager">
|
||
<i :class="{ disabled: !canPrev }" @click="onPrev">‹</i>
|
||
<i :class="{ disabled: !canNext }" @click="onNext">›</i>
|
||
</span>
|
||
</div>
|
||
|
||
<div v-if="days.length > 0" class="wk-days">
|
||
<div
|
||
v-for="(day, index) in days"
|
||
:key="day.date"
|
||
:class="['d', { 'is-on': index === currentIndex, has: hasWork(index), rest: day.dayType === 1, holiday: day.dayType === 2 }]"
|
||
@click="onDayClick(index)"
|
||
>
|
||
<span>{{ day.weekday }}</span>
|
||
<b>{{ dayText(day) }}</b>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 列表区固定高度(= 3 条 × --wk-row):超出滚动;空态(休/节假日/无安排)同高,卡片高度不跳 -->
|
||
<div class="wk-list-area">
|
||
<ul v-if="currentList.length > 0" class="wk-list">
|
||
<li v-for="(item, index) in currentList" :key="index">
|
||
<!-- 标题独占一行(右侧不再被时间占宽,长标题多显示约 8 字) -->
|
||
<span class="content" :title="item.contents">{{ item.contents }}</span>
|
||
<!-- 信息行恒渲染:地点 / 负责人 / 出席对象全空时落「-」,不留白(条目保持等高);
|
||
时间移到本行最右端;无时间不留位 -->
|
||
<div class="meta">
|
||
<span class="meta-txt">{{ metaOf(item) || "-" }}</span>
|
||
<span v-if="item.times" class="time" :class="{ 'is-cjk': isCjk(item.times) }">{{ item.times }}</span>
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
<div v-else class="no-data">{{ currentDayLabel }}</div>
|
||
</div>
|
||
|
||
<!-- 备注区恒占位(min-height = 2 行折叠高 58.59px):无备注只留白、不出灰盒,
|
||
与骨架/空周/有备注周同高 ⇒ 任意切换零高度变化(方案 B)。
|
||
默认 2 行截断,溢出才给「展开」(折叠态按钮贴第二行末尾) -->
|
||
<div class="wk-remark-slot">
|
||
<div v-if="remarkText" class="wk-remark">
|
||
<div ref="refRemark" class="rk-txt" :class="{ expanded: remarkExpanded }">{{ remarkText }}</div>
|
||
<span v-if="remarkOverflow" class="rk-more" @click="toggleRemark">{{ remarkExpanded ? "收起" : "展开" }}</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 空态:模板内联 SVG,不依赖外部图片 / CSS 背景 -->
|
||
<div v-else class="wg-empty">
|
||
<svg width="112" height="112" viewBox="0 0 210 242" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||
<rect opacity="0.01" x="0.5" y="0.5" width="209" height="209" stroke="#DCDEE0" stroke-dasharray="5" />
|
||
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M21 69.5625H47.25V171.938H24.9375H21H2.625V95.8125H21V69.5625Z" fill="#F2F3F5" />
|
||
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M105 36.5217C104.368 36.671 103.709 36.75 103.031 36.75C98.3196 36.75 94.5 32.9304 94.5 28.2188C94.5 23.721 97.9807 20.0361 102.395 19.7108C102.382 19.486 102.375 19.2594 102.375 19.0312C102.375 12.8698 107.37 7.875 113.531 7.875C118.555 7.875 122.803 11.1953 124.201 15.7611C124.362 15.7537 124.524 15.75 124.688 15.75C130.486 15.75 135.188 20.451 135.188 26.25C135.188 32.049 130.486 36.75 124.688 36.75C124.243 36.75 123.805 36.7224 123.375 36.6688V36.75H105V36.5217Z" fill="#F2F3F5" />
|
||
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M20.8304 43.8601C20.5247 43.9314 20.2058 43.969 19.878 43.969C17.5982 43.969 15.75 42.1461 15.75 39.8973C15.75 37.7506 17.4342 35.9919 19.5704 35.8367C19.5638 35.7293 19.5605 35.6211 19.5605 35.5121C19.5605 32.5714 21.9774 30.1875 24.9587 30.1875C27.3894 30.1875 29.4449 31.7722 30.1212 33.9512C30.1994 33.9477 30.2779 33.946 30.3569 33.946C33.1629 33.946 35.4376 36.1896 35.4376 38.9573C35.4376 41.725 33.1629 43.9687 30.3569 43.9687C30.1417 43.9687 29.9297 43.9555 29.7215 43.9299V43.9688H20.8304V43.8601Z" fill="#F2F3F5" />
|
||
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M190.616 19.6875V38.0625H202.125V139.609H161.438V38.0625V19.6875H190.616Z" fill="#F2F3F5" />
|
||
<rect y="182.438" width="210" height="27.5625" fill="#F7F8FA" />
|
||
<rect opacity="0.6" x="107.625" y="82.6875" width="47.25" height="69.5625" rx="2" fill="#DCDCDC" />
|
||
<rect x="57.75" y="65.625" width="84" height="86.625" rx="2" fill="#F2F3F5" />
|
||
<rect x="65.625" y="73.5" width="68.25" height="72.1875" rx="1" fill="#fff" />
|
||
<rect x="77.4375" y="87.9375" width="44.625" height="7.875" rx="1" fill="#EAEDF1" />
|
||
<rect x="77.4375" y="106.312" width="44.625" height="7.875" fill="#EAEDF1" />
|
||
<rect x="77.4375" y="124.688" width="44.625" height="7.875" rx="1" fill="#EAEDF1" />
|
||
<rect x="47.25" y="145.688" width="115.5" height="36.75" rx="1" fill="#EAEDF1" />
|
||
<rect x="85.3125" y="160.125" width="39.375" height="7.875" rx="1" fill="#F7F8FA" />
|
||
</svg>
|
||
<span class="txt">暂无数据</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { computed, nextTick, onMounted, onUnmounted, ref } from "vue";
|
||
import UserSettingApi from "@/api/UserSettingApi";
|
||
|
||
defineProps<{ widgetId: number; overviewUrl?: string }>();
|
||
const emit = defineEmits(["loaded"]);
|
||
|
||
const refRoot = ref<HTMLElement | null>(null);
|
||
const refRemark = ref<HTMLElement | null>(null);
|
||
const loading = ref(true);
|
||
/**
|
||
* 接口 data:单个周对象(title/year/term/week/monday/days[]/remark/hasData/minWeek/maxWeek),非数组。
|
||
* 命名警示:形参 week(偏移量)曾与 ref week(响应体)同名遮蔽,严格模式下 `week.value=` 抛
|
||
* TypeError 且 catch 内再抛 ⇒ loading 永驻(v1 骨架卡死根因)⇒ ref 恒用 weekData,禁止再命名 week。
|
||
*/
|
||
const weekData = ref<any>({});
|
||
/** week 偏移:0=本周、-1=上一周、+1=下周(与数据字段 data.week 绝对周次同名不同义) */
|
||
const weekOffset = ref(0);
|
||
/** 合法偏移范围:每次响应刷新;老版本服务缺省时按 [0,0] 处理(宁可不翻,也不打越界 code:2) */
|
||
const minWeek = ref(0);
|
||
const maxWeek = ref(0);
|
||
const currentIndex = ref(0);
|
||
/** 备注展开态;remarkOverflow 只在折叠态测量(溢出才显示按钮) */
|
||
const remarkExpanded = ref(false);
|
||
const remarkOverflow = ref(false);
|
||
let observer: ResizeObserver | null = null;
|
||
|
||
const days = computed<any[]>(() => (Array.isArray(weekData.value.days) ? weekData.value.days : []));
|
||
/** 标题简称:日期区间 + 第N周(接口 title 往往是「××学校工作安排」这类长名,放到 hover) */
|
||
const titleStr = computed<string>(() => {
|
||
const list = days.value;
|
||
const week = Number(weekData.value.week);
|
||
const weekPart = Number.isFinite(week) && week > 0 ? `第${week}周` : "";
|
||
if (list.length >= 2) {
|
||
const range = `${monthDay(list[0])}~${monthDay(list[list.length - 1])}`;
|
||
return weekPart ? `${range} ${weekPart}` : range;
|
||
}
|
||
return weekPart || weekData.value.title || "";
|
||
});
|
||
/** hover 全称:接口 title,与简称相同则不挂(避免重复提示) */
|
||
const titleFull = computed<string>(() => {
|
||
const full = weekData.value.title || "";
|
||
return full === titleStr.value ? "" : full;
|
||
});
|
||
/**
|
||
* 有周对象 = titleStr 非空(含 hasData=false 的空周:后端仍回 title/days,只是 items 全空)。
|
||
* 翻页箭头必须随之保留,避免「翻进空周 → 箭头消失 → 无法返回」的死角;
|
||
* 真正无周对象(oapi 降级返回 {})才走大图标空态。
|
||
*/
|
||
const hasWeek = computed(() => titleStr.value !== "");
|
||
// trim:备注尾部换行/空格会多出一个空行,让「展开」在只有 2 行内容时也误判溢出
|
||
const remarkText = computed<string>(() => String(weekData.value.remark || "").trim());
|
||
const canPrev = computed(() => weekOffset.value > minWeek.value);
|
||
const canNext = computed(() => weekOffset.value < maxWeek.value);
|
||
|
||
const currentList = computed<any[]>(() => {
|
||
const day = days.value[currentIndex.value];
|
||
return day && Array.isArray(day.items) ? day.items : [];
|
||
});
|
||
|
||
/** 当天无安排时的占位文案:优先接口 dayLabel(节日名/无/休),否则兜「暂无工作安排」 */
|
||
const currentDayLabel = computed<string>(() => {
|
||
const day = days.value[currentIndex.value];
|
||
return (day && day.dayLabel) || "暂无工作安排";
|
||
});
|
||
|
||
onMounted(async () => {
|
||
if (window.ResizeObserver && refRoot.value) {
|
||
observer = new ResizeObserver(reportHeight);
|
||
observer.observe(refRoot.value);
|
||
}
|
||
await loadWeek(0);
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
if (observer) observer.disconnect();
|
||
});
|
||
|
||
async function loadWeek(week: number) {
|
||
weekOffset.value = week;
|
||
loading.value = true;
|
||
try {
|
||
const data: any = await UserSettingApi.getWidgetData("weekplan", "week=" + week);
|
||
weekData.value = data && typeof data === "object" ? data : {};
|
||
// 合法偏移范围:老版本服务不返回时按 0 处理(两侧禁用)
|
||
minWeek.value = Number.isFinite(Number(weekData.value.minWeek)) ? Number(weekData.value.minWeek) : 0;
|
||
maxWeek.value = Number.isFinite(Number(weekData.value.maxWeek)) ? Number(weekData.value.maxWeek) : 0;
|
||
if (week === 0) locateToday();
|
||
else currentIndex.value = 0; // 非本周默认落周一,不沿用「今天」下标
|
||
} catch (e) {
|
||
console.warn("[widget] weekplan 取数失败", e);
|
||
weekData.value = {};
|
||
} finally {
|
||
// finally 收尾:即使 catch 内再抛,loading 也必须落 false(v1 骨架卡死的直接机制)
|
||
loading.value = false;
|
||
// 换周/换数据后重置备注折叠态并重测溢出
|
||
remarkExpanded.value = false;
|
||
remarkOverflow.value = false;
|
||
nextTick(measureRemark);
|
||
reportHeight();
|
||
}
|
||
}
|
||
|
||
/** 今日定位:优先按 date 匹配今日 ISO(防跨周边界偏差),匹配不到退星期推算 */
|
||
function locateToday() {
|
||
const list = days.value;
|
||
if (list.length === 0) {
|
||
currentIndex.value = 0;
|
||
return;
|
||
}
|
||
const now = new Date();
|
||
const iso = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
||
const hit = list.findIndex(d => d && d.date === iso);
|
||
currentIndex.value = hit >= 0 ? hit : (now.getDay() + 6) % 7;
|
||
if (currentIndex.value >= list.length) currentIndex.value = 0;
|
||
}
|
||
|
||
function pad(n: number) {
|
||
return n < 10 ? "0" + n : "" + n;
|
||
}
|
||
|
||
function onPrev() {
|
||
if (!canPrev.value) return;
|
||
loadWeek(weekOffset.value - 1);
|
||
}
|
||
|
||
function onNext() {
|
||
if (!canNext.value) return;
|
||
loadWeek(weekOffset.value + 1);
|
||
}
|
||
|
||
function onDayClick(index: number) {
|
||
currentIndex.value = index;
|
||
}
|
||
|
||
function hasWork(index: number) {
|
||
const day = days.value[index];
|
||
return !!day && Array.isArray(day.items) && day.items.length > 0;
|
||
}
|
||
|
||
/** 日期条只显示「日」,不带月份:优先 date(yyyy-MM-dd),退化到 dateText 尾部数字 */
|
||
function dayText(day: any) {
|
||
if (!day) return "";
|
||
const date = typeof day.date === "string" ? day.date : "";
|
||
if (date.length >= 10) return date.slice(8, 10);
|
||
const text = String(day.dateText || "");
|
||
const hit = text.match(/(\d{1,2})\s*$/);
|
||
return hit ? hit[1].padStart(2, "0") : text;
|
||
}
|
||
|
||
/** M月D日(标题区间用;月/日去前导零) */
|
||
function monthDay(day: any) {
|
||
const date = day && typeof day.date === "string" ? day.date : "";
|
||
if (date.length >= 10) {
|
||
return `${Number(date.slice(5, 7))}月${Number(date.slice(8, 10))}日`;
|
||
}
|
||
return String((day && day.dateText) || "");
|
||
}
|
||
|
||
/** 次要信息行:地点 / 负责人 / 参与人员;三者全空时落「-」,不留白(条目保持等高) */
|
||
function metaOf(item: any) {
|
||
if (!item) return "";
|
||
const parts: string[] = [];
|
||
if (item.place) parts.push(item.place);
|
||
if (item.principal) parts.push(item.principal);
|
||
if (item.participant) parts.push(item.participant);
|
||
return parts.join(" · ");
|
||
}
|
||
|
||
/** 右侧时间文案是否含汉字(如「全天」):同字号下汉字字面高 ≈1.29 × 数字,降一档字号(.is-cjk) */
|
||
function isCjk(value: any) {
|
||
return /[\u4e00-\u9fa5]/.test(String(value || ""));
|
||
}
|
||
|
||
function toggleRemark() {
|
||
remarkExpanded.value = !remarkExpanded.value;
|
||
if (!remarkExpanded.value) nextTick(measureRemark);
|
||
reportHeight();
|
||
}
|
||
|
||
/** 备注溢出判定:折叠态下内容高度超过可视高度才算溢出(展开态保持按钮可见,不重测) */
|
||
function measureRemark() {
|
||
const el = refRemark.value;
|
||
if (!el || remarkExpanded.value) return;
|
||
remarkOverflow.value = el.scrollHeight > el.clientHeight + 2;
|
||
}
|
||
|
||
function reportHeight() {
|
||
if (refRoot.value) emit("loaded", refRoot.value.offsetHeight);
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
/* 主色(2026-10-16 指定):今日方块 / 七日条圆点 / 「查看概览」/「展开」/ 今日星期 */
|
||
$wk-primary: #5979ea;
|
||
$wk-primary-dark: #4864d8;
|
||
|
||
.wg-weekplan {
|
||
/* 一条安排的高度:内容行(15px × 1.5 = 22.5) + 信息行(12.5px × 1.5 = 18.75)
|
||
+ 上下 padding 12 + 分隔线 1 ≈ 56.25px ⇒ 取 57px 留余量。
|
||
有/无「地点·负责人·参与人」的条目都占这个高度,标题行始终对齐。 */
|
||
--wk-row: 57px;
|
||
/* 结构化骨架的灰条颜色 */
|
||
--wk-skel: rgba(190, 190, 190, 0.35);
|
||
padding: 6px 0 10px;
|
||
}
|
||
.wk-title {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
margin: 0 15px;
|
||
padding: 6px 0 10px;
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
color: #344449;
|
||
> span {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.pager {
|
||
display: flex;
|
||
gap: 2px;
|
||
i {
|
||
font-style: normal;
|
||
font-size: 16px;
|
||
font-weight: 400;
|
||
color: #6e7c87;
|
||
/* 定尺寸 + flex 双向居中:行盒(24px)里字形贴基线,直接给背景会把字形顶偏 */
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 24px;
|
||
height: 24px;
|
||
line-height: 1;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: background-color 0.16s, color 0.16s;
|
||
user-select: none;
|
||
&.disabled {
|
||
color: #d6dbe1;
|
||
cursor: default;
|
||
}
|
||
&:not(.disabled):hover {
|
||
background: #eef1f5;
|
||
color: #344449;
|
||
}
|
||
&:not(.disabled):active {
|
||
background: #e2e7ee;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.wk-days {
|
||
display: flex;
|
||
/* 下边距 6→8:删除「日常详情」行后补回原视觉间距(原来 6 与 tips 的 8 塌缩取 max=8) */
|
||
margin: 0 15px 8px;
|
||
gap: 4px;
|
||
.d {
|
||
flex: 1 1 0;
|
||
text-align: center;
|
||
font-size: 14px;
|
||
color: #6e7c87;
|
||
border-radius: 4px;
|
||
// 底部留出圆点位置:整周无安排时也占位,日期条高度恒定
|
||
padding: 4px 0 11px;
|
||
cursor: pointer;
|
||
// 星期数:块级行盒 + 下留 3px —— 把今日方块与圆点整体让下去,数字本身位置不动
|
||
// (方块 28px 上下居中于数字,不加留白时上沿贴「星期数」4.1px、下沿贴圆点 1.5px)
|
||
// ⚠️ 必须用 padding-bottom 而非 margin-bottom:后者会与 b 的 margin-top 折叠
|
||
//(max(正) + min(负)),今日格的 −1px 与其余格的 2px 会走出不同结果 ⇒ 今日数字偏低 1.75px
|
||
> span {
|
||
display: block;
|
||
line-height: 21px;
|
||
padding-bottom: 3px;
|
||
}
|
||
b {
|
||
display: block;
|
||
font-size: 15px;
|
||
color: #344449;
|
||
margin-top: 2px;
|
||
font-weight: 600;
|
||
// 圆点改锚数字块(不再锚格子):格子被今日格 28px 方块拉高时圆点不再跟着沉
|
||
position: relative;
|
||
}
|
||
// 圆点绝对定位在数字块下方 6px(bottom:-11px ⇒ 点中心 = b 底边 + 9,与留白一起把圆点让到方块下方)
|
||
b::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: 50%;
|
||
bottom: -11px;
|
||
transform: translateX(-50%);
|
||
width: 4px;
|
||
height: 4px;
|
||
border-radius: 50%;
|
||
background: $wk-primary;
|
||
visibility: hidden;
|
||
}
|
||
&.has b::after {
|
||
visibility: visible;
|
||
}
|
||
// 休息日(周六 / 周日)与法定节假日:日期数字同档弱化灰(节假日不再标红)
|
||
&.rest b,
|
||
&.holiday b {
|
||
color: #98a2b0;
|
||
}
|
||
// 今日:蓝底白字方块(置最后,压过 rest / holiday 的日期字色)
|
||
// 28px 行盒会把数字中心相对 22.5px 行盒下移 5.5px(canvas 字体度量实测墨迹中心
|
||
// 11.25 → 14),margin-top 取 −1px(精确 −0.75 取整)补回,数字与其余 6 天同基线
|
||
&.is-on {
|
||
color: $wk-primary;
|
||
b {
|
||
display: block;
|
||
width: 28px;
|
||
height: 28px;
|
||
margin: -1px auto 0;
|
||
line-height: 28px;
|
||
background: $wk-primary;
|
||
color: #fff;
|
||
border-radius: 4px;
|
||
}
|
||
// 方块比数字行盒高 5.5px,圆点随之低 2.5px ⇒ 上收 2.5px,七格圆点同线
|
||
b::after {
|
||
bottom: -8.5px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
/* ④ 「查看概览」:渲染在卡片 header 右侧(Teleport 到 index.vue 的 #wg-hdr-<id> 锚点),
|
||
样式复刻课程表班级切换器 .cls-trigger 一族;a 标签直跳、无浮层 */
|
||
.ov-link {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
font-family: inherit;
|
||
font-size: 13px;
|
||
font-weight: 400;
|
||
color: #344449;
|
||
text-decoration: none;
|
||
padding: 3px 8px;
|
||
border-radius: 6px;
|
||
line-height: 1.5;
|
||
transition: background-color 0.15s, color 0.15s;
|
||
&:hover {
|
||
background: #eef1f5;
|
||
color: $wk-primary;
|
||
}
|
||
}
|
||
/* 列表区:固定高度 = 3 条(3 × --wk-row,即 171px);多条滚动、空态占位同高,
|
||
避免切到「休/节假日」时卡片高度跳变 */
|
||
.wk-list-area {
|
||
height: calc(var(--wk-row) * 3);
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
scrollbar-width: thin;
|
||
&::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
&::-webkit-scrollbar-thumb {
|
||
background: #d3d8de;
|
||
border-radius: 3px;
|
||
}
|
||
&::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
}
|
||
.wk-list {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
li {
|
||
margin: 0 15px;
|
||
// 条目等高:有/无「地点·负责人·参与人」都占一行高度(无信息落「-」占位,不留白),
|
||
// 保证标题行纵向对齐;overflow 兜住固定高度可能带来的亚像素裁切
|
||
height: var(--wk-row);
|
||
overflow: hidden;
|
||
padding: 6px 0;
|
||
border-bottom: 1px solid #f2f4f7;
|
||
color: #344449;
|
||
&:last-child {
|
||
border-bottom: 0;
|
||
}
|
||
}
|
||
// 标题独占一行(时间已移到信息行):14px,右侧不再被时间占宽
|
||
.content {
|
||
display: block;
|
||
font-size: 14px;
|
||
color: #344449;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
// 信息行:地点 / 负责人 / 参与人 在左,时间贴最右端;无时间时信息文本照常占满
|
||
.meta {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
margin-top: 2px;
|
||
font-size: 12.5px;
|
||
color: #98a2b0;
|
||
}
|
||
.meta-txt {
|
||
flex: 1 1 auto;
|
||
// ⚠️ 必须显式 min-width:0:flex 项默认 min-width:auto,遇 nowrap 长文本
|
||
// 会以 max-content 为下限而不收缩 ⇒ text-overflow 永不触发
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.time {
|
||
flex: 0 0 auto;
|
||
color: #6e7c87;
|
||
// 汉字型时间(全天/放假)降一档:同字号下汉字字面高 ≈1.29 × 数字(15px 实测 18px vs 14px)
|
||
&.is-cjk {
|
||
font-size: 13px;
|
||
}
|
||
}
|
||
}
|
||
/* ---------- 加载态:结构化骨架(与内容逐段同构同高,换周不塌不跳) ----------
|
||
标题 40 + 七日条 63 + 列表 171 + 备注区 66.59 + 根部 padding 16 ≈ 364.6px,
|
||
与「空周内容」「有备注周内容」严格相等(方案 B)。注意根部勿再加 padding */
|
||
.wk-skeleton {
|
||
.sk-title {
|
||
margin: 0 15px;
|
||
padding: 6px 0 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
.bar {
|
||
height: 16px;
|
||
width: 46%;
|
||
border-radius: 4px;
|
||
background: var(--wk-skel);
|
||
}
|
||
// font-size 16 撑出与真实 pager 相同的 24px 行盒(标题段总高 40 的关键)
|
||
.sk-pager {
|
||
font-size: 16px;
|
||
i {
|
||
display: inline-block;
|
||
width: 10px;
|
||
height: 16px;
|
||
margin-left: 12px;
|
||
border-radius: 2px;
|
||
background: var(--wk-skel);
|
||
}
|
||
}
|
||
}
|
||
.sk-days {
|
||
display: flex;
|
||
margin: 0 15px 8px;
|
||
gap: 4px;
|
||
.sd {
|
||
flex: 1 1 0;
|
||
text-align: center;
|
||
font-size: 14px;
|
||
padding: 4px 0 11px;
|
||
// 星期条:外框 24px 与真实星期数同高(21px 行盒 + 3px 下留白),条本身 10px 居上
|
||
// ⚠️ 内层条的定位必须用父级 padding-top,不能用子元素 margin-top:
|
||
// 父级无 padding/border 时子元素上边距会「塌」到父级外面,凭空多出 5.5px 高度
|
||
.w {
|
||
display: block;
|
||
width: 14px;
|
||
height: 24px;
|
||
margin: 0 auto;
|
||
padding-top: 5.5px;
|
||
&::before {
|
||
content: "";
|
||
display: block;
|
||
height: 10px;
|
||
border-radius: 3px;
|
||
background: var(--wk-skel);
|
||
}
|
||
}
|
||
b {
|
||
display: block;
|
||
font-size: 15px;
|
||
margin-top: 2px;
|
||
i {
|
||
display: inline-block;
|
||
width: 20px;
|
||
height: 16px;
|
||
border-radius: 3px;
|
||
background: var(--wk-skel);
|
||
}
|
||
}
|
||
// 今日格:与真实 .d.is-on b 同几何(28px 方块 + margin-top −1)
|
||
&.on b {
|
||
width: 28px;
|
||
height: 28px;
|
||
margin: -1px auto 0;
|
||
i {
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 4px;
|
||
background: $wk-primary;
|
||
opacity: 0.55;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.sk-list {
|
||
height: calc(var(--wk-row) * 3);
|
||
.sr {
|
||
height: var(--wk-row);
|
||
margin: 0 15px;
|
||
padding: 6px 0;
|
||
border-bottom: 1px solid #f2f4f7;
|
||
&:last-child {
|
||
border-bottom: 0;
|
||
}
|
||
.l {
|
||
height: 21px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
.c {
|
||
height: 14px;
|
||
width: 58%;
|
||
border-radius: 4px;
|
||
background: var(--wk-skel);
|
||
}
|
||
.t {
|
||
height: 14px;
|
||
width: 64px;
|
||
border-radius: 4px;
|
||
background: var(--wk-skel);
|
||
}
|
||
}
|
||
.m {
|
||
margin-top: 2px;
|
||
height: 10px;
|
||
width: 32%;
|
||
border-radius: 4px;
|
||
background: var(--wk-skel);
|
||
}
|
||
}
|
||
}
|
||
// 备注区占位:与 .wk-remark-slot 同高(min-height 58.59,无 padding,盒尺寸与全局 border-box 无关)
|
||
.sk-remark-slot {
|
||
margin: 8px 15px 0;
|
||
min-height: 58.59px;
|
||
border-radius: 6px;
|
||
background: #f7f8fa;
|
||
border-top: 1px solid #eef1f5;
|
||
i {
|
||
display: block;
|
||
height: 12px;
|
||
border-radius: 4px;
|
||
background: var(--wk-skel);
|
||
&.b1 {
|
||
width: 78%;
|
||
margin: 11px 10px 0;
|
||
}
|
||
&.b2 {
|
||
width: 56%;
|
||
margin: 9px 10px 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
/* 备注区恒占位(方案 B):min-height = 2 行折叠高 58.59px。
|
||
无备注时只留白、不出灰盒;有备注时灰盒至少填满该区(1 行备注也不缩)。
|
||
⇒ 加载 / 空周 / 有备注周三态同高,任意切换零高度变化 */
|
||
.wk-remark-slot {
|
||
margin: 8px 15px 0;
|
||
min-height: 58.59px;
|
||
}
|
||
/* 备注:与「安排数据」区分——上方一条分割线 + 浅底块,跟白色列表拉开层次 */
|
||
.wk-remark {
|
||
position: relative;
|
||
padding: 8px 10px;
|
||
background: #f7f8fa;
|
||
border-top: 1px solid #eef1f5;
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
color: #5b6672;
|
||
min-height: 58.59px;
|
||
.rk-txt {
|
||
line-height: 1.6;
|
||
// 备注可能是含换行的多行文本:pre-line 保留换行;折叠态截断 2 行
|
||
white-space: pre-line;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
// 右侧恒留 34px 给按钮:折叠态第二行以省略号收尾,展开态最后一行绕开按钮
|
||
padding-right: 34px;
|
||
&.expanded {
|
||
display: block;
|
||
overflow: visible;
|
||
}
|
||
}
|
||
// 「展开 / 收起」恒贴正文最后一行的右端(折叠=第二行、展开=最后一行),不另起一行
|
||
.rk-more {
|
||
position: absolute;
|
||
right: 10px;
|
||
bottom: 8px;
|
||
// 与备注块同底色,压住可能蹭到的正文尾部
|
||
background: #f7f8fa;
|
||
font-size: 13px;
|
||
color: $wk-primary;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
&:hover {
|
||
color: $wk-primary-dark;
|
||
}
|
||
}
|
||
}
|
||
.no-data {
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #b9c2cc;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* ---------- 空态(模板内联 SVG,不依赖外部图片 / CSS 背景) ---------- */
|
||
.wg-empty {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
min-height: 132px;
|
||
padding: 16px 0 20px;
|
||
|
||
.txt {
|
||
font-size: 12px;
|
||
color: #98a2b0;
|
||
}
|
||
}
|
||
</style>
|