diff --git a/.gitignore b/.gitignore index 4275bcc..0eeb863 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,13 @@ card-harness.html card-harness.vite.config.ts src/card-harness-main.ts +# 小组件内置化取证 harness(同一类,本地临时) +dist_widget_harness +widget-harness.html +widget-harness.vite.config.ts +src/widget-harness-main.ts +.tmp-build + yarn.lock .npmrc # Editor directories and files diff --git a/src/api/UserSettingApi.ts b/src/api/UserSettingApi.ts index 164b2e8..89f0109 100644 --- a/src/api/UserSettingApi.ts +++ b/src/api/UserSettingApi.ts @@ -92,6 +92,28 @@ class UserSettings extends Abstract { const url = path + "/sortUserDesktopApp"; return this.postRequest({ url, data: { ids: ids.join(",") } }); } + + /** 小组件统一取数:params 为 urlencoded 查询串(对齐旧 otherParams 口径) */ + getWidgetData(code: string, params: string) { + const url = path + "/getWidgetData"; + return this.getRequest({ url, params: { code: code, params: params } }); + } + + /** 仅 comp_type=2:按需取自定义片段内容 */ + getWidgetContent(code: string) { + const url = path + "/getWidgetContent"; + return this.getRequest({ url, params: { code: code } }); + } + + getWidgetCatalogList() { + const url = path + "/GetWidgetCatalogList"; + return this.getRequest({ url }); + } + + saveWidgetCatalog(catalog: Record) { + const url = path + "/SaveWidgetCatalog"; + return this.postRequest({ url, data: catalog }); + } } export default new UserSettings(); diff --git a/src/assets/icons/widget-add.svg b/src/assets/icons/widget-add.svg index ce041b3..c3ce148 100644 --- a/src/assets/icons/widget-add.svg +++ b/src/assets/icons/widget-add.svg @@ -1,4 +1,4 @@ - - + + diff --git a/src/assets/icons/widget-empty.svg b/src/assets/icons/widget-empty.svg index 233e39b..21b7d08 100644 --- a/src/assets/icons/widget-empty.svg +++ b/src/assets/icons/widget-empty.svg @@ -1,9 +1,4 @@ - - - - - - - - + + + diff --git a/src/assets/icons/widget-sub.svg b/src/assets/icons/widget-sub.svg index bf15a52..fc135f7 100644 --- a/src/assets/icons/widget-sub.svg +++ b/src/assets/icons/widget-sub.svg @@ -1,3 +1,3 @@ - + diff --git a/src/assets/styles/componets/_widget.scss b/src/assets/styles/componets/_widget.scss index ac283de..8323c88 100644 --- a/src/assets/styles/componets/_widget.scss +++ b/src/assets/styles/componets/_widget.scss @@ -1,12 +1,26 @@ .widget-wrap { + // 玻璃层令牌(方案 §6.2):深底 + 模糊 + 描边 + 投影,前景走 token,不再随壁纸漂移 + --wg-glass-bg: rgba(15, 23, 42, 0.42); + --wg-glass-border: rgba(255, 255, 255, 0.18); + --wg-glass-shadow: 0 10px 36px rgba(0, 0, 0, 0.3); + --wg-fg: rgba(255, 255, 255, 0.94); + --wg-fg-dim: rgba(255, 255, 255, 0.66); + --wg-fg-shadow: 0 1px 2px rgba(0, 0, 0, 0.35); + width: 380px; min-height: 200px; z-index: 100; border-radius: 5px; - background: rgba(0, 0, 0, 0.35); + background: var(--wg-glass-bg); + backdrop-filter: blur(18px) saturate(140%); + -webkit-backdrop-filter: blur(18px) saturate(140%); + border: 1px solid var(--wg-glass-border); + box-shadow: var(--wg-glass-shadow); .widget-header { display: flex; height: 48px; + color: var(--wg-fg-dim); + text-shadow: var(--wg-fg-shadow); .widget-header-drag { flex: 2 auto; cursor: move; @@ -18,6 +32,10 @@ cursor: pointer; width: 24px; flex-shrink: 1; + color: var(--wg-fg-dim); + } + &:hover svg { + color: var(--wg-fg); } } .operate-btn:nth-child(2) { @@ -36,6 +54,14 @@ &:first-child { margin-top: 0; } + .no-data { + min-height: 90px; + display: flex; + align-items: center; + justify-content: center; + color: #b9c2cc; + font-size: 13px; + } } .widget-app-header { @@ -59,11 +85,12 @@ } } + // 拖拽态加深 + 加强模糊(旧版翻白 → 浅灰图标压浅白,是这轮修掉的反向翻车) &__drag { - .widget-header { - background: rgba(255, 255, 255, 0.5); - } - background: rgba(255, 255, 255, 0.6); + background: rgba(15, 23, 42, 0.56); + backdrop-filter: blur(22px) saturate(150%); + -webkit-backdrop-filter: blur(22px) saturate(150%); + box-shadow: 0 14px 44px rgba(0, 0, 0, 0.38); } } @@ -73,7 +100,12 @@ align-items: center; justify-content: center; flex-direction: column; - color: #828c96; + color: var(--wg-fg); + text-shadow: var(--wg-fg-shadow); + + svg { + color: var(--wg-fg-dim); + } h4 { font-weight: 500; diff --git a/src/components/widgets/EvaluateWidget.vue b/src/components/widgets/EvaluateWidget.vue new file mode 100644 index 0000000..6bfee3b --- /dev/null +++ b/src/components/widgets/EvaluateWidget.vue @@ -0,0 +1,121 @@ + + + + + + diff --git a/src/components/widgets/SyllabusWidget.vue b/src/components/widgets/SyllabusWidget.vue new file mode 100644 index 0000000..16c3749 --- /dev/null +++ b/src/components/widgets/SyllabusWidget.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/src/components/widgets/WeekplanWidget.vue b/src/components/widgets/WeekplanWidget.vue new file mode 100644 index 0000000..d32d9f1 --- /dev/null +++ b/src/components/widgets/WeekplanWidget.vue @@ -0,0 +1,233 @@ + + + + + diff --git a/src/components/widgets/index.ts b/src/components/widgets/index.ts new file mode 100644 index 0000000..e6d1533 --- /dev/null +++ b/src/components/widgets/index.ts @@ -0,0 +1,8 @@ +import { defineAsyncComponent } from "vue"; + +/** 组件键 → 内置组件。新增小组件只改这里 + widget_catalog 插一行。 */ +export const widgetRegistry: Record = { + evaluate: defineAsyncComponent(() => import("./EvaluateWidget.vue")), + syllabus: defineAsyncComponent(() => import("./SyllabusWidget.vue")), + weekplan: defineAsyncComponent(() => import("./WeekplanWidget.vue")), +}; diff --git a/src/store/index.ts b/src/store/index.ts index 1906272..f3e36d6 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -127,7 +127,8 @@ export const useDefaultStore = defineStore("defaultStore", { getWidgetAppIds() { const { widgetAppIds: appIds, widgetAppList: appList } = this.curUserSettingInfo; let checkIds: number[] = []; - if (appIds == null) checkIds = appList.map(x => x.id); + // getWidgetAppList 的比较器按 appIds 降序渲染(新添加置顶),故未配置态需预反转,才与 widget_catalog.sort 正序一致 + if (appIds == null) checkIds = appList.map(x => x.id).reverse(); else if (appIds.length == 0) checkIds = []; else checkIds = appIds.split(",").map(x => parseInt(x)); return checkIds; diff --git a/src/utils/widgetFragment.ts b/src/utils/widgetFragment.ts new file mode 100644 index 0000000..5456aa1 --- /dev/null +++ b/src/utils/widgetFragment.ts @@ -0,0 +1,60 @@ +import conf from "@/assets/config/host.config"; + +/** + * 自定义 HTML 片段包装(方案 §5.2):门户拼两段注入,顺序不能合并成一段—— + * ① 取数桥必须在 head 且早于片段自身脚本(否则片段里 yhWidget 为 undefined、内容永远空白); + * ② 高度上报必须在片段内容之后。管理员只贴正文,不需要自己写取数和上报。 + */ + +/** 平台统一信封 {code,msg,data}:片段用原生 fetch,需自行解出 data 业务体 */ +const BRIDGE = + "window.yhWidget={getData:function(params){" + + "return fetch(YH_WIDGET_API+'?code='+YH_WIDGET_CODE+'¶ms='+encodeURIComponent(params||'')," + + "{credentials:'include',headers:{yschapirequest:'1'}})" + + ".then(function(r){return r.json()})" + + ".then(function(j){return j&&j.data!==undefined?j.data:j});}};"; + +/** + * ② 高度上报:先立即报一次(门户据此关掉骨架、把 iframe 从 display:none 放出来), + * 之后 ResizeObserver 触发节流补报。节流必须用 setTimeout 而不能用 rAF —— + * iframe 隐藏期间子文档不出动画帧,rAF 回调永不回来,会把节流标志永久占住、后续上报全被吞 + * (实测帧高卡在 min-height,内容撑出内层滚动条)。 + */ +const REPORTER = + "(function(){var timer=0;" + + "function send(){try{parent.postMessage(JSON.stringify({id:YH_WIDGET_ID," + + "height:document.documentElement.scrollHeight}),'*')}catch(e){}}" + + "function up(){if(timer)return;timer=setTimeout(function(){timer=0;send()},80)}" + + "if(window.ResizeObserver){try{new ResizeObserver(up).observe(document.body)}catch(e){}}" + + "window.addEventListener('load',up);send();setTimeout(up,300);setTimeout(up,1200);})();"; + +const RESET = + "html,body{margin:0;padding:0;background:transparent;color:#344449;font-size:13px;line-height:1.5;" + + 'font-family:"Helvetica Neue",Helvetica,"PingFang SC","Microsoft YaHei",Arial,sans-serif;}'; + +/** 闭合标签必须拆开拼:否则提前闭合门户自身的 + +