- components/widgets:widgetRegistry(defineAsyncComponent 按需加载)+ EvaluateWidget/SyllabusWidget/WeekplanWidget,props widgetId、emit loaded、内部自行取数、ResizeObserver 自报真实高度;呈现只保留语义不逐像素复刻旧 html - views/index.vue:iframe 单分支改三分支(内置组件 / compType=2 走 :srcdoc + sandbox="allow-scripts allow-same-origin allow-forms"(不给 allow-top-navigation)/ 兜底「该小工具暂不可用」不白屏);:src="item.url" 与候选池 url 依赖删除;postMessage 身份下发只服务片段分支,高度统一收口 find.height - utils/widgetFragment.ts:wrapFragment 两段注入——取数桥在 head(早于片段自身脚本,否则 yhWidget undefined)、高度上报在 body 之后;</script> 拆拼防提前闭合门户脚本;节流用 setTimeout 尾沿 + 300/1200ms 兜底(iframe 隐藏期子文档不出动画帧,rAF 会把上报永久吞掉、帧高卡 min-height 撑出内层滚动);桥 URL 走 serviceHost 且自行解平台信封 data - views/Settings.vue:新增 admin pane「widget」(ADMIN_PANES/ALL_PANES/「系统」组导航项/v-else-if 链分支),普通用户导航隐藏且 URL 直达落 no-perm 黄条 - views/widget/WidgetManage.vue:双卡管理页(内置/自定义分组列表 + 名称/键/类型/data_code/排序/启用/sch_id 表单 + CodeMirror + 上传 .html 走 FileReader 填编辑器,不落地文件不产生 URL;内置条目 content 只读) - _widget.scss:--wg-* 六令牌 + 深玻璃 blur(18px) saturate(140%) + 描边 + 投影;拖拽态加深 blur(22px) 不再翻白;空态与 +/− 前景改走 token(配套 widget-add/sub/empty.svg 由硬编码色改 currentColor,widget-empty 原为内嵌 PNG 无法着色故换线性版);.widget-item 浅底深字保持不动 - UserSettingApi.ts:getWidgetData / getWidgetContent / GetWidgetCatalogList / SaveWidgetCatalog - 另含 src/store/index.ts 的 getWidgetAppIds 未配置态 .reverse()(非本会话所写,按用户确认一并提交:与 getWidgetAppList 降序比较器对齐,使未配置态按 widget_catalog.sort 正序渲染)
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<div class="wg-weekplan" ref="refRoot">
|
||||
<Skeleton
|
||||
v-if="loading"
|
||||
style="padding: 15px"
|
||||
:loading="true"
|
||||
:title="false"
|
||||
:paragraph="{ rows: 4 }"
|
||||
animated
|
||||
/>
|
||||
<template v-else-if="days.length > 0">
|
||||
<div class="wk-title">
|
||||
<span>{{ titleStr }}</span>
|
||||
<span class="pager">
|
||||
<i :class="{ disabled: iweek <= -1 }" @click="onPrev">‹</i>
|
||||
<i :class="{ disabled: iweek >= 1 }" @click="onNext">›</i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="wk-days">
|
||||
<div
|
||||
v-for="(day, index) in days"
|
||||
:key="day.date"
|
||||
:class="['d', { 'is-on': index === currentIndex, has: hasWork(index) }]"
|
||||
@click="onDayClick(index)"
|
||||
>
|
||||
<span>{{ WeekArr[index] }}</span>
|
||||
<b>{{ dayDate(day.date) }}</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>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else class="no-data">暂无工作安排</div>
|
||||
</template>
|
||||
<div v-else class="no-data">暂无数据</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
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);
|
||||
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 currentList = computed(() => {
|
||||
const day = days.value[currentIndex.value];
|
||||
return day && day.list ? day.list : [];
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
if (window.ResizeObserver && refRoot.value) {
|
||||
observer = new ResizeObserver(reportHeight);
|
||||
observer.observe(refRoot.value);
|
||||
}
|
||||
currentIndex.value = (new Date().getDay() + 6) % 7;
|
||||
await loadWeek(0);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (observer) observer.disconnect();
|
||||
});
|
||||
|
||||
async function loadWeek(week: number) {
|
||||
iweek.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;
|
||||
} catch (e) {
|
||||
console.warn("[widget] weekplan 取数失败", e);
|
||||
}
|
||||
loading.value = false;
|
||||
reportHeight();
|
||||
}
|
||||
|
||||
function onPrev() {
|
||||
if (iweek.value <= -1) return;
|
||||
loadWeek(iweek.value - 1);
|
||||
}
|
||||
|
||||
function onNext() {
|
||||
if (iweek.value >= 1) return;
|
||||
loadWeek(iweek.value + 1);
|
||||
}
|
||||
|
||||
function onDayClick(index: number) {
|
||||
currentIndex.value = index;
|
||||
}
|
||||
|
||||
function hasWork(index: number) {
|
||||
const day = days.value[index];
|
||||
return !!day && !!day.list && day.list.length > 0;
|
||||
}
|
||||
|
||||
function dayDate(date: string) {
|
||||
return (date || "").slice(8, 10);
|
||||
}
|
||||
|
||||
function formatMonthDay(date: string) {
|
||||
return (date || "").slice(5, 7) + "月" + (date || "").slice(8, 10) + "日";
|
||||
}
|
||||
|
||||
function reportHeight() {
|
||||
if (refRoot.value) emit("loaded", refRoot.value.offsetHeight);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wg-weekplan {
|
||||
padding: 6px 0 10px;
|
||||
}
|
||||
.wk-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 0 15px;
|
||||
padding: 6px 0 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #344449;
|
||||
.pager i {
|
||||
font-style: normal;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #6e7c87;
|
||||
padding: 0 6px;
|
||||
cursor: pointer;
|
||||
&.disabled {
|
||||
color: #d6dbe1;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
.wk-days {
|
||||
display: flex;
|
||||
margin: 0 15px 6px;
|
||||
gap: 4px;
|
||||
.d {
|
||||
flex: 1 1 0;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #6e7c87;
|
||||
border-radius: 4px;
|
||||
padding: 4px 0;
|
||||
cursor: pointer;
|
||||
b {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
color: #344449;
|
||||
margin-top: 2px;
|
||||
font-weight: 600;
|
||||
}
|
||||
&.is-on {
|
||||
background: #eef4ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
&.has b::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background: #2d8cf0;
|
||||
margin: 2px auto 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.wk-tips {
|
||||
margin: 8px 15px 0;
|
||||
font-size: 12px;
|
||||
color: #6e7c87;
|
||||
}
|
||||
.wk-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 15px;
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid #f2f4f7;
|
||||
font-size: 12.5px;
|
||||
color: #344449;
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.time {
|
||||
flex: 0 0 auto;
|
||||
color: #6e7c87;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.no-data {
|
||||
min-height: 90px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #b9c2cc;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user