diff --git a/src/assets/image/yunhai-logo.svg b/src/assets/image/yunhai-logo.svg
new file mode 100644
index 0000000..7d1d358
--- /dev/null
+++ b/src/assets/image/yunhai-logo.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/assets/styles/appicon.mask.css b/src/assets/styles/appicon.mask.css
new file mode 100644
index 0000000..a8db9f3
--- /dev/null
+++ b/src/assets/styles/appicon.mask.css
@@ -0,0 +1,33 @@
+/* appicon 图标(CSS mask + currentColor,data-uri 自包含),共 2 个。
+ 用法 ;命名空间隔离:类名恒为 .appicon*,无 .icon-*。 */
+
+.appicon {
+ font-family: inherit;
+ font-style: normal;
+ font-size: 16px;
+ line-height: 1;
+ display: inline-block;
+ vertical-align: middle;
+}
+.appicon::before {
+ content: "" !important;
+ display: block;
+ width: 1em;
+ height: 1em;
+ background-color: currentColor;
+ -webkit-mask-position: center;
+ mask-position: center;
+ -webkit-mask-repeat: no-repeat;
+ mask-repeat: no-repeat;
+ -webkit-mask-size: contain;
+ mask-size: contain;
+}
+
+.appicon-tongzhi::before {
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M853.33 725.33H938.67V810.67H85.33V725.33H170.67V426.67C170.67 238.15 323.49 85.33 512 85.33C700.51 85.33 853.33 238.15 853.33 426.67V725.33ZM768 725.33V426.67C768 285.28 653.38 170.67 512 170.67C370.62 170.67 256 285.28 256 426.67V725.33H768ZM384 896H640V981.33H384V896Z' %3E%3C/path%3E%3C/svg%3E");
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M853.33 725.33H938.67V810.67H85.33V725.33H170.67V426.67C170.67 238.15 323.49 85.33 512 85.33C700.51 85.33 853.33 238.15 853.33 426.67V725.33ZM768 725.33V426.67C768 285.28 653.38 170.67 512 170.67C370.62 170.67 256 285.28 256 426.67V725.33H768ZM384 896H640V981.33H384V896Z' %3E%3C/path%3E%3C/svg%3E");
+}
+.appicon-wenjuan::before {
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M725.33 85.33V170.67H853.61C877.02 170.67 896 189.65 896 213.05V896.28C896 919.69 877.02 938.67 853.61 938.67H170.39C146.98 938.67 128 919.68 128 896.28V213.05C128 189.64 146.98 170.67 170.39 170.67H298.67V85.33H725.33ZM298.67 256H213.33V853.33H810.67V256H725.33V341.33H298.67V256ZM384 682.67V768H298.67V682.67H384ZM384 554.67V640H298.67V554.67H384ZM384 426.67V512H298.67V426.67H384ZM640 170.67H384V256H640V170.67Z' %3E%3C/path%3E%3C/svg%3E");
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M725.33 85.33V170.67H853.61C877.02 170.67 896 189.65 896 213.05V896.28C896 919.69 877.02 938.67 853.61 938.67H170.39C146.98 938.67 128 919.68 128 896.28V213.05C128 189.64 146.98 170.67 170.39 170.67H298.67V85.33H725.33ZM298.67 256H213.33V853.33H810.67V256H725.33V341.33H298.67V256ZM384 682.67V768H298.67V682.67H384ZM384 554.67V640H298.67V554.67H384ZM384 426.67V512H298.67V426.67H384ZM640 170.67H384V256H640V170.67Z' %3E%3C/path%3E%3C/svg%3E");
+}
diff --git a/src/assets/styles/componets/_app_container.scss b/src/assets/styles/componets/_app_container.scss
index 55151b8..e172111 100644
--- a/src/assets/styles/componets/_app_container.scss
+++ b/src/assets/styles/componets/_app_container.scss
@@ -56,3 +56,53 @@
// border-radius: 2px;
// background: rgba(255, 255, 255, 0.25);
}
+
+/* ===================== AppIcon 矢量图标三档 scene(方案 §4 渲染契约) =====================
+ * 图标资源只管形状,容器管底色与图标色;底色为端侧派生 token(不落库)。
+ * 形状类(.appicon-<名> 的 mask data-uri)来自全局 appicon.mask.css;
+ * AppIcon.vue 组件按 scene/tint 组合类名与内联覆盖(tint=application_info.icon_color 人工配置色)。
+ * 尺寸对齐上方 img 位图(60px / 18% 圆角),保证矢量与位图在网格里视觉一致。
+ */
+.appicon {
+ display: inline-block;
+ line-height: 1;
+ vertical-align: middle;
+ box-sizing: border-box;
+}
+
+.appicon-tile {
+ // PC 应用卡:方块底 + 白图标(对齐位图 60px/18%)
+ --appicon-bg: var(--brand, #366ef4); // 端侧主题 token(通道 CSS 不可达时兜底蓝)
+ width: 60px;
+ height: 60px;
+ border-radius: 18%;
+ background: var(--appicon-bg);
+ color: #fff;
+ font-size: 44px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.appicon-circle {
+ // App 首页快捷:正圆底 + 白图标
+ --appicon-bg: var(--brand, #366ef4);
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ background: var(--appicon-bg);
+ color: #fff;
+ font-size: 36px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.appicon-plain {
+ // 无底线性:图标色继承(分组色 / tint)
+ width: auto;
+ height: auto;
+ background: transparent;
+ color: var(--appicon-ink, currentColor);
+ font-size: 26px;
+}
diff --git a/src/assets/styles/main.scss b/src/assets/styles/main.scss
index b9d845a..234433e 100644
--- a/src/assets/styles/main.scss
+++ b/src/assets/styles/main.scss
@@ -6,6 +6,12 @@
@use "utils/utils";
@use "utils/bsch_title";
+// AppIcon 资产库(CSS mask 版,data-uri 自包含):
+// 拷贝自 D:\SoftProject\yunhai\YschAppIcons\appicon.mask.css(资产库 build_mask_css.py 产物)。
+// 同步方式:资产库重跑构建后整文件覆盖本处,避免 P1 静态应用部署顺序依赖与跨域问题。
+// 注意:必须 @use 在 componets/* 之前——scene 档(.appicon-tile 等)需覆盖本文件的 .appicon 基类字号。
+@use "appicon.mask.css";
+
@use "componets/status";
@use "componets/small_table";
@use "componets/my_table";
diff --git a/src/components/AppIcon.vue b/src/components/AppIcon.vue
new file mode 100644
index 0000000..c99c88c
--- /dev/null
+++ b/src/components/AppIcon.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
diff --git a/src/components/SearchPanel.vue b/src/components/SearchPanel.vue
index 91895c4..a60abc5 100644
--- a/src/components/SearchPanel.vue
+++ b/src/components/SearchPanel.vue
@@ -13,7 +13,9 @@
-
![]()
+
+
{{ item.name }}
@@ -26,10 +28,22 @@
import { useDefaultStore } from "@/store";
import { ref, inject, computed, onMounted, watch } from "vue";
import vuescroll from "vuescroll";
+import AppIcon from "@/components/AppIcon.vue";
+import { appiconKeyOf } from "@/constants/appIconRules";
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
const { $utils } = useCurrentInstance();
+/**
+ * 矢量渲染判定(方案 §5.4 回退链,与 AppList.vue / index.vue 同口径):
+ * icon_key 命中 → 矢量;有位图 URL → 位图(存量零回归);URL 空且关键词命中 → 矢量;否则默认图。
+ */
+function vectorIconOf(item: any): string {
+ if (item?.iconKey) return item.iconKey;
+ if (item?.icon) return "";
+ return appiconKeyOf(item?.name || "");
+}
+
const refInput = ref();
const searchName = ref();
diff --git a/src/constants/appIconRules.ts b/src/constants/appIconRules.ts
new file mode 100644
index 0000000..67286bb
--- /dev/null
+++ b/src/constants/appIconRules.ts
@@ -0,0 +1,43 @@
+/**
+ * 应用名称关键词 → AppIcon 资产库图标键映射(D4:两端各持一份,关键词列与 MobilePortal 完全一致)。
+ *
+ * 定位(方案 §5.4 / E4):只做兜底、不做主路径——仅在「icon_key 未配置 且 无 icon URL」时生效
+ * (替代默认占位图);有 URL 的存量应用恒定位图渲染,零回归。
+ *
+ * 值列语义:appicon 图标键(如 tongzhi);空串 = 该关键词暂无对应图标(dormant,资产库补图后填值激活)。
+ * 一致性断言:YschAppIcons/verify_map.mjs 深比较两端关键词列(含顺序);值列两端允许不同
+ * (MobilePortal 用 wd-icon 名,本表用 appicon 键)。
+ */
+export const APP_ICON_RULES: [string, string][] = [
+ ['请假', ''],
+ ['课程', ''],
+ ['课表', ''],
+ ['通知', ''],
+ ['公告', ''],
+ ['报修', ''],
+ ['文印', ''],
+ ['打印', ''],
+ ['公文', ''],
+ ['作业', ''],
+ ['班级', ''],
+ ['家校', ''],
+ ['会议', ''],
+ ['场馆', ''],
+ ['申购', ''],
+ ['申领', ''],
+ ['物品', ''],
+ ['通讯录', ''],
+ ['邮箱', ''],
+ ['工资', ''],
+ ['财务', ''],
+ ['成绩', ''],
+]
+
+/** 名称命中关键词时返回对应 appicon 图标键;无命中/值为空返回 ''(不渲染矢量,走原回退)。 */
+export function appiconKeyOf(name: string): string {
+ if (!name) return ''
+ for (const [kw, key] of APP_ICON_RULES) {
+ if (name.includes(kw)) return key
+ }
+ return ''
+}
diff --git a/src/views/AppList.vue b/src/views/AppList.vue
index fa35116..331d0de 100644
--- a/src/views/AppList.vue
+++ b/src/views/AppList.vue
@@ -30,7 +30,9 @@
]"
>
-
+
+
{{ citem.name }}
@@ -60,10 +62,10 @@
]"
>
-
+
+
{{ item.name }}
@@ -79,6 +81,7 @@