- 直吃 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
This commit is contained in:
@@ -8,35 +8,66 @@
|
||||
:paragraph="{ rows: 4 }"
|
||||
animated
|
||||
/>
|
||||
<template v-else-if="days.length > 0">
|
||||
|
||||
<!-- 门控用 hasWeek(有 title 的周对象)而非 hasData:空周(hasData=false)
|
||||
也必须保留标题行 + 翻页箭头,否则用户翻进空周后没有出口 -->
|
||||
<template v-else-if="hasWeek">
|
||||
<div class="wk-title">
|
||||
<span>{{ titleStr }}</span>
|
||||
<span :title="titleStr">{{ titleStr }}</span>
|
||||
<!-- 翻页:边界由响应 minWeek/maxWeek 驱动(缺省按 [0,0] 禁用两侧);越界 oapi 返回 code:2,前端先拦 -->
|
||||
<span class="pager">
|
||||
<i :class="{ disabled: iweek <= -1 }" @click="onPrev">‹</i>
|
||||
<i :class="{ disabled: iweek >= 1 }" @click="onNext">›</i>
|
||||
<i :class="{ disabled: !canPrev }" @click="onPrev">‹</i>
|
||||
<i :class="{ disabled: !canNext }" @click="onNext">›</i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="wk-days">
|
||||
|
||||
<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) }]"
|
||||
:class="['d', { 'is-on': index === currentIndex, has: hasWork(index), rest: day.dayType === 1, holiday: day.dayType === 2 }]"
|
||||
@click="onDayClick(index)"
|
||||
>
|
||||
<span>{{ WeekArr[index] }}</span>
|
||||
<b>{{ dayDate(day.date) }}</b>
|
||||
<span>{{ day.weekday }}</span>
|
||||
<b>{{ dayText(day) }}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wk-tips">日常详情</div>
|
||||
<ul v-if="currentList.length > 0" class="wk-list">
|
||||
<li v-for="(item, index) in currentList" :key="index">
|
||||
<span class="content" :title="item.contents">{{ item.contents }}</span>
|
||||
<span class="time">{{ item.times }}</span>
|
||||
<div class="line">
|
||||
<span class="content" :title="item.contents">{{ item.contents }}</span>
|
||||
<span class="time">{{ item.times }}</span>
|
||||
</div>
|
||||
<div v-if="metaOf(item)" class="meta">{{ metaOf(item) }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else class="no-data">暂无工作安排</div>
|
||||
<div v-else class="no-data">{{ currentDayLabel }}</div>
|
||||
|
||||
<div v-if="remarkText" class="wk-remark" :title="remarkText">{{ remarkText }}</div>
|
||||
</template>
|
||||
<div v-else class="no-data">暂无数据</div>
|
||||
|
||||
<!-- 空态:模板内联 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>
|
||||
|
||||
@@ -47,26 +78,43 @@ import UserSettingApi from "@/api/UserSettingApi";
|
||||
defineProps<{ widgetId: number }>();
|
||||
const emit = defineEmits(["loaded"]);
|
||||
|
||||
const WeekArr = ["一", "二", "三", "四", "五", "六", "日"];
|
||||
|
||||
const refRoot = ref<HTMLElement | null>(null);
|
||||
const loading = ref(true);
|
||||
const days = ref<any[]>([]);
|
||||
const otherData = ref<any>({});
|
||||
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<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);
|
||||
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<any[]>(() => (Array.isArray(weekData.value.days) ? weekData.value.days : []));
|
||||
const titleStr = computed<string>(() => weekData.value.title || "");
|
||||
/**
|
||||
* 有周对象 = titleStr 非空(含 hasData=false 的空周:后端仍回 title/days,只是 items 全空)。
|
||||
* 翻页箭头必须随之保留,避免「翻进空周 → 箭头消失 → 无法返回」的死角;
|
||||
* 真正无周对象(oapi 降级返回 {})才走大图标空态。
|
||||
*/
|
||||
const hasWeek = computed(() => titleStr.value !== "");
|
||||
const remarkText = computed<string>(() => weekData.value.remark || "");
|
||||
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 : [];
|
||||
});
|
||||
|
||||
const currentList = computed(() => {
|
||||
/** 当天无安排时的占位文案:优先接口 dayLabel(节日名/无/休),否则兜「暂无工作安排」 */
|
||||
const currentDayLabel = computed<string>(() => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user