From 8305129c73738457a0bd09e7d40761b7f091377a Mon Sep 17 00:00:00 2001 From: chenxing Date: Thu, 15 Oct 2026 16:30:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E4=B8=80=E5=91=A8=E5=AE=89=E6=8E=92?= =?UTF-8?q?=E5=B0=8F=E7=BB=84=E4=BB=B6):=20=E5=AF=B9=E6=8E=A5=20workplan?= =?UTF-8?q?=20oapi=20=E5=8D=95=E5=91=A8=E5=AF=B9=E8=B1=A1=20+=20=E5=91=A8?= =?UTF-8?q?=E5=81=8F=E7=A7=BB=E7=BF=BB=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 直吃 oapi data 单周对象(title/year/term/week/monday/days[7]/remark/hasData/minWeek/maxWeek) - 箭头翻页按 minWeek/maxWeek 门控,非本周默认落周一,本周按今日定位 - 空周门控 hasWeek(title 非空)替代 hasData:空周保留标题行+箭头+七日条,不再翻进去出不来 - 修复形参 week 遮蔽 ref 致 loading 永驻骨架(ref 改名 weekData,loading/reportHeight 移入 finally) - 大图标空态改模板内联 SVG --- src/components/widgets/WeekplanWidget.vue | 213 +++++++++++++++++----- 1 file changed, 170 insertions(+), 43 deletions(-) diff --git a/src/components/widgets/WeekplanWidget.vue b/src/components/widgets/WeekplanWidget.vue index d32d9f1..6736781 100644 --- a/src/components/widgets/WeekplanWidget.vue +++ b/src/components/widgets/WeekplanWidget.vue @@ -8,35 +8,66 @@ :paragraph="{ rows: 4 }" animated /> - @@ -47,26 +78,43 @@ import UserSettingApi from "@/api/UserSettingApi"; defineProps<{ widgetId: number }>(); const emit = defineEmits(["loaded"]); -const WeekArr = ["一", "二", "三", "四", "五", "六", "日"]; - const refRoot = ref(null); const loading = ref(true); -const days = ref([]); -const otherData = ref({}); -const iweek = ref(0); +/** + * 接口 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({}); +/** 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); let observer: ResizeObserver | null = null; -const titleStr = computed(() => { - if (days.value.length === 0) return ""; - const start = formatMonthDay(days.value[0].date); - const end = formatMonthDay(days.value[days.value.length - 1].date); - return `${start}~${end} 第${otherData.value.week || 0}周`; +const days = computed(() => (Array.isArray(weekData.value.days) ? weekData.value.days : [])); +const titleStr = computed(() => weekData.value.title || ""); +/** + * 有周对象 = titleStr 非空(含 hasData=false 的空周:后端仍回 title/days,只是 items 全空)。 + * 翻页箭头必须随之保留,避免「翻进空周 → 箭头消失 → 无法返回」的死角; + * 真正无周对象(oapi 降级返回 {})才走大图标空态。 + */ +const hasWeek = computed(() => titleStr.value !== ""); +const remarkText = computed(() => weekData.value.remark || ""); +const canPrev = computed(() => weekOffset.value > minWeek.value); +const canNext = computed(() => weekOffset.value < maxWeek.value); + +const currentList = computed(() => { + const day = days.value[currentIndex.value]; + return day && Array.isArray(day.items) ? day.items : []; }); -const currentList = computed(() => { +/** 当天无安排时的占位文案:优先接口 dayLabel(节日名/无/休),否则兜「暂无工作安排」 */ +const currentDayLabel = computed(() => { const day = days.value[currentIndex.value]; - return day && day.list ? day.list : []; + return (day && day.dayLabel) || "暂无工作安排"; }); onMounted(async () => { @@ -74,7 +122,6 @@ onMounted(async () => { observer = new ResizeObserver(reportHeight); observer.observe(refRoot.value); } - currentIndex.value = (new Date().getDay() + 6) % 7; await loadWeek(0); }); @@ -83,28 +130,52 @@ onUnmounted(() => { }); async function loadWeek(week: number) { - iweek.value = week; + weekOffset.value = week; loading.value = true; try { - const data: any = await UserSettingApi.getWidgetData("weekplan", "iweek=" + week); - days.value = (data && data.Data) || []; - otherData.value = (data && data.OtherData) || {}; - if (currentIndex.value >= days.value.length) currentIndex.value = 0; + 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; + reportHeight(); } - loading.value = false; - 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 (iweek.value <= -1) return; - loadWeek(iweek.value - 1); + if (!canPrev.value) return; + loadWeek(weekOffset.value - 1); } function onNext() { - if (iweek.value >= 1) return; - loadWeek(iweek.value + 1); + if (!canNext.value) return; + loadWeek(weekOffset.value + 1); } function onDayClick(index: number) { @@ -113,15 +184,23 @@ function onDayClick(index: number) { function hasWork(index: number) { const day = days.value[index]; - return !!day && !!day.list && day.list.length > 0; + return !!day && Array.isArray(day.items) && day.items.length > 0; } -function dayDate(date: string) { - return (date || "").slice(8, 10); +/** 日期条显示:优先接口 dateText(MM.dd),兜底 date 切片 */ +function dayText(day: any) { + if (!day) return ""; + return day.dateText || (day.date || "").slice(8, 10); } -function formatMonthDay(date: string) { - return (date || "").slice(5, 7) + "月" + (date || "").slice(8, 10) + "日"; +/** 次要信息行:地点 / 负责人 / 参与人员,全部为空则不显示 */ +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(" · "); } function reportHeight() { @@ -137,11 +216,17 @@ function reportHeight() { display: flex; align-items: center; justify-content: space-between; + gap: 8px; margin: 0 15px; padding: 6px 0 10px; font-size: 14px; font-weight: 700; color: #344449; + > span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } .pager i { font-style: normal; font-size: 16px; @@ -187,6 +272,14 @@ function reportHeight() { background: #2d8cf0; margin: 2px auto 0; } + // 休息日(周六 / 周日)弱化日期数字 + &.rest b { + color: #98a2b0; + } + // 法定节假日醒目标记 + &.holiday b { + color: #e64545; + } } } .wk-tips { @@ -199,8 +292,6 @@ function reportHeight() { margin: 0; padding: 0; li { - display: flex; - align-items: center; margin: 0 15px; padding: 5px 0; border-bottom: 1px solid #f2f4f7; @@ -209,6 +300,10 @@ function reportHeight() { &:last-child { border-bottom: 0; } + .line { + display: flex; + align-items: center; + } } .content { flex: 1 1 auto; @@ -221,6 +316,22 @@ function reportHeight() { color: #6e7c87; margin-left: 10px; } + .meta { + margin-top: 2px; + font-size: 11.5px; + color: #98a2b0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} +.wk-remark { + margin: 8px 15px 0; + font-size: 12px; + color: #6e7c87; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .no-data { min-height: 90px; @@ -230,4 +341,20 @@ function reportHeight() { color: #b9c2cc; font-size: 13px; } + +/* ---------- 空态(模板内联 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; + } +}