- 标题改合成简称「10月12日~10月18日 第N周」,接口全称留 hover(title 属性) - 日期条只留「日」(10.12→12),今日改蓝底白字方块(原浅蓝底) - 「日常详情」12px/400/#6e7c87 → 14px/700/#1f2329,同排右侧新增「查看概览」外跳(新窗口) - 外跳地址由后端下发(props.overviewUrl);缺配置则不渲染入口 - 备注 line-clamp:2 + 溢出才出「展开/收起」,支持含换行的多行文本 - index.vue 透传 overview-url;WidgetEditModal 配置页加「外跳入口」行,超管保存/校管覆盖/清除覆盖三链路带上
This commit is contained in:
@@ -13,7 +13,8 @@
|
||||
也必须保留标题行 + 翻页箭头,否则用户翻进空周后没有出口 -->
|
||||
<template v-else-if="hasWeek">
|
||||
<div class="wk-title">
|
||||
<span :title="titleStr">{{ titleStr }}</span>
|
||||
<!-- 显示用简称(日期区间 + 第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>
|
||||
@@ -33,7 +34,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wk-tips">日常详情</div>
|
||||
<div class="wk-tips-row">
|
||||
<span class="wk-tips">日常详情</span>
|
||||
<!-- 外跳地址由后端按「子应用 code + 路由」解析下发(应用注册表 pc_url),前端不写死域名 -->
|
||||
<a
|
||||
v-if="overviewUrl"
|
||||
class="wk-overview"
|
||||
:href="overviewUrl"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>查看概览 ›</a>
|
||||
</div>
|
||||
<ul v-if="currentList.length > 0" class="wk-list">
|
||||
<li v-for="(item, index) in currentList" :key="index">
|
||||
<div class="line">
|
||||
@@ -45,7 +56,11 @@
|
||||
</ul>
|
||||
<div v-else class="no-data">{{ currentDayLabel }}</div>
|
||||
|
||||
<div v-if="remarkText" class="wk-remark" :title="remarkText">{{ remarkText }}</div>
|
||||
<!-- 备注:默认 2 行截断,超出才给「展开/收起」;空备注整块不渲染 -->
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<!-- 空态:模板内联 SVG,不依赖外部图片 / CSS 背景 -->
|
||||
@@ -72,13 +87,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||
import UserSettingApi from "@/api/UserSettingApi";
|
||||
|
||||
defineProps<{ widgetId: number }>();
|
||||
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),非数组。
|
||||
@@ -92,10 +108,28 @@ const weekOffset = ref(0);
|
||||
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 : []));
|
||||
const titleStr = computed<string>(() => weekData.value.title || "");
|
||||
/** 标题简称:日期区间 + 第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 全空)。
|
||||
* 翻页箭头必须随之保留,避免「翻进空周 → 箭头消失 → 无法返回」的死角;
|
||||
@@ -146,6 +180,9 @@ async function loadWeek(week: number) {
|
||||
} finally {
|
||||
// finally 收尾:即使 catch 内再抛,loading 也必须落 false(v1 骨架卡死的直接机制)
|
||||
loading.value = false;
|
||||
// 换周/换数据后重置备注折叠态并重测溢出
|
||||
remarkExpanded.value = false;
|
||||
nextTick(measureRemark);
|
||||
reportHeight();
|
||||
}
|
||||
}
|
||||
@@ -187,10 +224,23 @@ function hasWork(index: number) {
|
||||
return !!day && Array.isArray(day.items) && day.items.length > 0;
|
||||
}
|
||||
|
||||
/** 日期条显示:优先接口 dateText(MM.dd),兜底 date 切片 */
|
||||
/** 日期条只显示「日」,不带月份:优先 date(yyyy-MM-dd),退化到 dateText 尾部数字 */
|
||||
function dayText(day: any) {
|
||||
if (!day) return "";
|
||||
return day.dateText || (day.date || "").slice(8, 10);
|
||||
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) || "");
|
||||
}
|
||||
|
||||
/** 次要信息行:地点 / 负责人 / 参与人员,全部为空则不显示 */
|
||||
@@ -203,6 +253,19 @@ function metaOf(item: any) {
|
||||
return parts.join(" · ");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -259,10 +322,6 @@ function reportHeight() {
|
||||
margin-top: 2px;
|
||||
font-weight: 600;
|
||||
}
|
||||
&.is-on {
|
||||
background: #eef4ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
&.has b::after {
|
||||
content: "";
|
||||
display: block;
|
||||
@@ -280,12 +339,41 @@ function reportHeight() {
|
||||
&.holiday b {
|
||||
color: #e64545;
|
||||
}
|
||||
// 今日:蓝底白字方块(置最后,压过 rest / holiday 的日期字色)
|
||||
&.is-on {
|
||||
color: #1d4ed8;
|
||||
b {
|
||||
width: 26px;
|
||||
margin: 2px auto 0;
|
||||
padding: 1px 0;
|
||||
background: #2d8cf0;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.wk-tips {
|
||||
.wk-tips-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin: 8px 15px 0;
|
||||
}
|
||||
.wk-tips {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #1f2329;
|
||||
}
|
||||
.wk-overview {
|
||||
flex: 0 0 auto;
|
||||
font-size: 12px;
|
||||
color: #6e7c87;
|
||||
color: #2d8cf0;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #1d6fd0;
|
||||
}
|
||||
}
|
||||
.wk-list {
|
||||
list-style: none;
|
||||
@@ -329,9 +417,30 @@ function reportHeight() {
|
||||
margin: 8px 15px 0;
|
||||
font-size: 12px;
|
||||
color: #6e7c87;
|
||||
.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;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
&.expanded {
|
||||
display: block;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
.rk-more {
|
||||
display: inline-block;
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: #2d8cf0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
&:hover {
|
||||
color: #1d6fd0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.no-data {
|
||||
min-height: 90px;
|
||||
|
||||
@@ -76,6 +76,10 @@ export interface WidgetCatalog {
|
||||
originalName?: string | null;
|
||||
/** 校管视角:本校是否已有覆盖行 */
|
||||
covered?: boolean;
|
||||
/** 外跳入口:目标子应用 code(应用注册表 app_code),NULL/空 = 不显示入口 */
|
||||
linkAppCode?: string | null;
|
||||
/** 外跳入口:子应用内路由(如 /weekplan);绝对地址由后端按注册表 pc_url 解析 */
|
||||
linkPath?: string | null;
|
||||
sort?: number;
|
||||
enabled?: number;
|
||||
updateTime?: string | null;
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
v-if="item.compType === 1 && widgetRegistry[item.code]"
|
||||
:is="widgetRegistry[item.code]"
|
||||
:widget-id="item.id"
|
||||
:overview-url="item.overviewUrl"
|
||||
@loaded="onWidgetLoaded(item.id, $event)"
|
||||
/>
|
||||
<template v-else>
|
||||
|
||||
@@ -68,6 +68,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<span class="lb">外跳入口</span>
|
||||
<div class="ctl">
|
||||
<div class="link-cfg">
|
||||
<input
|
||||
v-model="form.linkAppCode"
|
||||
class="inp"
|
||||
maxlength="32"
|
||||
placeholder="子应用 code,如 work-week-plan"
|
||||
/>
|
||||
<input
|
||||
v-model="form.linkPath"
|
||||
class="inp"
|
||||
maxlength="128"
|
||||
placeholder="子应用内路由,如 /weekplan"
|
||||
/>
|
||||
</div>
|
||||
<div class="field-msg hint">{{ linkMsg }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isSuperView" class="form-row">
|
||||
<span class="lb">可见范围</span>
|
||||
<div class="ctl">
|
||||
@@ -333,6 +354,8 @@ const form = reactive({
|
||||
roleAliasMap: {} as Record<string, string>,
|
||||
sort: 0,
|
||||
enabled: 1,
|
||||
linkAppCode: "",
|
||||
linkPath: "",
|
||||
});
|
||||
|
||||
const extensions = [html(), EditorView.lineWrapping];
|
||||
@@ -361,8 +384,12 @@ const dataMsg = computed(() => {
|
||||
if (!props.isSuperView) return "取数键由超管在后端白名单里维护,不可改";
|
||||
return form.compType === 1 ? "内置组件的数据由前端组件自身取,不在此配置" : "只能从后端白名单里选;留空=静态片段";
|
||||
});
|
||||
const scopeHint = computed(() => {
|
||||
if (form.scopeType === 0) return "所有学校都下发。";
|
||||
/** 外跳入口说明:库里只存「子应用 code + 路由」,绝对地址由后端按应用注册表解析 */
|
||||
const linkMsg = computed(() => {
|
||||
const scope = props.isSuperView ? "" : "留空=沿用超管配置;";
|
||||
return `${scope}只填「子应用 code + 路由」,绝对地址由后端按应用注册表 pc_url 解析后下发(不落库域名);两列需成对填写,都留空=不显示入口。`;
|
||||
});
|
||||
const scopeHint = computed(() => { if (form.scopeType === 0) return "所有学校都下发。";
|
||||
return form.scopeType === 1
|
||||
? "白名单:只有选中的学校下发(未选中的学校看不到)。"
|
||||
: "黑名单:除选中的学校外都下发(选中的学校看不到)。";
|
||||
@@ -448,6 +475,8 @@ function fillForm() {
|
||||
form.roleAliasMap = parseRoleAlias(src?.roleAlias);
|
||||
form.sort = src?.sort ?? 0;
|
||||
form.enabled = src?.enabled === 0 ? 0 : 1;
|
||||
form.linkAppCode = src?.linkAppCode ?? "";
|
||||
form.linkPath = src?.linkPath ?? "";
|
||||
if (!props.isSuperView && src?.id) loadSchoolOverride(src.id);
|
||||
}
|
||||
|
||||
@@ -460,6 +489,9 @@ async function loadSchoolOverride(widgetId: number) {
|
||||
form.roleList = parseRoles(setting.roleTypes as string);
|
||||
form.sort = setting.sortNo ?? props.item?.sort ?? 0;
|
||||
form.alias = setting.alias ?? "";
|
||||
// 外跳同样认「NULL=沿用全局」:本校未覆盖则清空输入框,由全局值兜底
|
||||
form.linkAppCode = setting.linkAppCode ?? "";
|
||||
form.linkPath = setting.linkPath ?? "";
|
||||
} catch (e) {
|
||||
console.warn("[widget] 本校覆盖读取失败", e);
|
||||
}
|
||||
@@ -558,6 +590,8 @@ async function save() {
|
||||
roleAlias: Object.keys(aliasMap).length ? JSON.stringify(aliasMap) : null,
|
||||
sort: Number(form.sort) || 0,
|
||||
enabled: form.enabled,
|
||||
linkAppCode: form.linkAppCode.trim() || null,
|
||||
linkPath: form.linkPath.trim() || null,
|
||||
});
|
||||
Message.success("保存成功");
|
||||
emit("saved");
|
||||
@@ -594,6 +628,8 @@ async function clearOverride() {
|
||||
roleTypes: null,
|
||||
sortNo: null,
|
||||
alias: null,
|
||||
linkAppCode: null,
|
||||
linkPath: null,
|
||||
});
|
||||
Message.success("已清除本校覆盖,恢复沿用超管配置");
|
||||
emit("saved");
|
||||
@@ -611,6 +647,8 @@ function buildSchoolSetting() {
|
||||
roleTypes: form.roleList.length ? form.roleList.join(",") : null,
|
||||
sortNo: Number(form.sort) || 0,
|
||||
alias: form.alias.trim() || null,
|
||||
linkAppCode: form.linkAppCode.trim() || null,
|
||||
linkPath: form.linkPath.trim() || null,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
@@ -836,6 +874,16 @@ $wm-brand-line: color.mix(#ffffff, $wm-brand, 78%);
|
||||
max-width: 150px;
|
||||
}
|
||||
}
|
||||
/* 外跳入口:子应用 code + 路由 并排(两列需成对,故放同一行) */
|
||||
.link-cfg {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
max-width: 340px;
|
||||
.inp {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
.hint {
|
||||
font-size: 12px;
|
||||
color: var(--wm-t4);
|
||||
|
||||
Reference in New Issue
Block a user