Преглед изворни кода

feat(ui): 全局顶部/左侧 + 弹窗 + SsSubTab 全面升级 icon-
base(v3)与交互样式

- 全局顶部(GlobalHeader):右侧工具栏图标改用 icon-base
v3,新命名同步(含菜单模式切换粗体图标)
- 全局左侧(GlobalMenu):active 样式补齐(上下分割线/右边
框“接通内容区”效果),图标分层尺寸保持一致
- 弹窗(dhtmlx):顶部按钮统一走 dialog-toolbar-icon /
close 走 dialog-toolbar-close-icon;图标名调整为 ptab/
pval/save/autoTxt/fix,close 仍为 icon-close;修复
show/hide 被 CSS !important 顶掉的问题
- SsSubTab:菜单与 footer 按钮使用 icon-base(保存/关闭
图标+文字,收起仅图标),样式与全局左侧对齐,修复 icon-
close 旧 svg 背景冲突
- 相关页面/组件同步更新(home / objInfo / objEdit /
objChg / chgChkTab / urgeHomep_tab 等)

apple пре 2 дана
родитељ
комит
466b6caac0

+ 273 - 109
js/vue/ss-components.js

@@ -5740,9 +5740,9 @@ import { EVEN_VAR } from "./EventBus.js";
    * SsSubTab 左侧菜单+iframe内容组件
    * v3.0 改造:去掉顶部图片,改为图标+悬浮模式,iframe懒加载 by xu 20251216
    */
-  const SsSubTab = {
-    name: "SsSubTab",
-    props: {
+	  const SsSubTab = {
+	    name: "SsSubTab",
+	    props: {
       menuList: {
         type: Array,
         required: true,
@@ -5765,33 +5765,195 @@ import { EVEN_VAR } from "./EventBus.js";
         default: 'collapse',
       },
     },
-    emits: ["menu-change", "footer-click"],
-    setup(props, { emit }) {
-      // v3.0 新增:默认图标映射,使用icon-biz图标 by xu 20251216
-      const defaultIcons = [
-        'icon-obj-ry',       // 人员
-        'icon-obj-dw',       // 单位
-        'icon-obj-gw',       // 岗位
-        'icon-biz-rc',       // 人才
-        'icon-biz-xc',       // 巡查
-        'icon-biz-cl',       // 材料
-        'icon-biz-men',      // 门
-        'icon-obj-xy'        // 协议
-      ];
-      const getMenuIcon = (item, index) => {
-        if (item.icon) return item.icon;
-        // 返回完整的 class:menu-icon (字体族) + 具体图标类
-        return 'menu-icon ' + defaultIcons[index % defaultIcons.length];
-      };
-
-      // v3.0 新增:菜单模式管理 by xu 20251216
-      const menuMode = ref(props.initialMode);
-      const isHovering = ref(false);
+	    emits: ["menu-change", "footer-click"],
+	    setup(props, { emit }) {
+	      // v3.0 新增:默认图标映射,使用icon-biz图标 by xu 20251216
+	      const defaultIcons = [
+	        'icon-obj-ry',       // 人员
+	        'icon-obj-dw',       // 单位
+	        'icon-obj-gw',       // 岗位
+	        'icon-biz-rc',       // 人才
+	        'icon-biz-xc',       // 巡查
+	        'icon-biz-cl',       // 材料
+	        'icon-biz-men',      // 门
+	        'icon-obj-xy'        // 协议
+	      ];
+	      //功能: SsSubTab 支持后端下发 iconName + pobj/cobj 两级菜单 by xu 20251222
+	      const isTrue = (v) => v === true || v === "true" || v === 1 || v === "1"; //功能 by xu 20251222
+	      const resolveIconClass = (iconNameOrClass, fallbackIndex) => { //功能 by xu 20251222
+	        const fallback = `menu-icon ${defaultIcons[fallbackIndex % defaultIcons.length]}`;
+	        if (!iconNameOrClass) {
+	          return fallback;
+	        }
+	        // 已经是完整 class(可能包含 menu-icon / menu-base-icon / 多个 class)
+	        if (typeof iconNameOrClass === "string" && iconNameOrClass.indexOf(" ") > -1) {
+	          return iconNameOrClass;
+	        }
+	        const iconName = iconNameOrClass;
+	        if (iconName === "menu-icon" || iconName === "menu-base-icon") {
+	          return fallback;
+	        }
+	        // 业务图标库:icon-biz / icon-obj -> menu-icon
+	        if (
+	            typeof iconName === "string" &&
+	            (iconName.indexOf("icon-obj-") === 0 || iconName.indexOf("icon-biz-") === 0)
+	        ) {
+	          return `menu-icon ${iconName}`;
+	        }
+	        // 默认认为是 icon-base 图标 -> menu-base-icon
+	        return `menu-base-icon ${iconName}`;
+	      };
+	      const getMenuIcon = (item, index) => { //功能 by xu 20251222
+	        if (!item) {
+	          return resolveIconClass(null, index);
+	        }
+	        //功能: 变动图标后端暂不正确,前端先写死为 icon-chg by xu 20251223
+	        if (item.title === "变动" || item.name === "sys_bd") {
+	          return resolveIconClass("icon-chg", index);
+	        }
+	        // 兼容旧字段 icon(优先使用)
+	        if (item.icon) return resolveIconClass(item.icon, index);
+	        // v3.0 使用后端下发 iconName
+	        if (item.iconName) return resolveIconClass(item.iconName, index);
+	        return resolveIconClass(null, index);
+	      };
+
+	      //功能: SsSubTab 底部按钮支持 icon+文字(icon-base)by xu 20251224
+	      const getFooterIcon = (button) => { //功能 by xu 20251224
+	        if (!button) return "menu-base-icon icon-save";
+	        const iconNameOrClass = button.iconClass || button.iconName || button.icon;
+	        if (!iconNameOrClass) return "menu-base-icon icon-save";
+	        if (typeof iconNameOrClass === "string" && iconNameOrClass.indexOf(" ") > -1) {
+	          return iconNameOrClass;
+	        }
+	        return `menu-base-icon ${iconNameOrClass}`;
+	      };
+
+	      //功能: pobj/cobj 扁平结构转换为 children 树结构,兼容原 children 结构 by xu 20251222
+	      const normalizeMenuList = (rawList) => {
+	        if (!Array.isArray(rawList) || rawList.length === 0) {
+	          return [];
+	        }
+	        const hasTree = rawList.some((it) => Array.isArray(it?.children) && it.children.length > 0);
+	        if (hasTree) {
+	          return rawList.map((it) => ({
+	            ...it,
+	            __level: 1,
+	            children: Array.isArray(it.children)
+	                ? it.children.map((c) => ({ ...c, __level: 2 }))
+	                : it.children,
+	          }));
+	        }
+	        const hasMarker = rawList.some((it) => it && ("pobj" in it || "cobj" in it));
+	        if (!hasMarker) {
+	          return rawList.map((it) => ({ ...it, __level: 1 }));
+	        }
+	        const result = [];
+	        let currentGroup = null;
+	        for (const item of rawList) {
+	          //功能: “变动”始终按一级处理(即使后端误传 pobj/cobj)by xu 20251223
+	          const isChgItem = item && (item.title === "变动" || item.name === "sys_bd");
+	          if (isChgItem) {
+	            result.push({ ...item, __level: 1 });
+	            continue;
+	          }
+	          const isParent = isTrue(item?.pobj);
+	          const isChild = isTrue(item?.cobj);
+	          if (isParent) {
+	            currentGroup = {
+	              ...item,
+	              __level: 1,
+	              children: [],
+	            };
+	            result.push(currentGroup);
+	            continue;
+	          }
+	          if (isChild && currentGroup) {
+	            currentGroup.children.push({ ...item, __level: 2 });
+	            continue;
+	          }
+	          //功能: 变动等无 pobj/cobj 的选项按一级展示(不挂到 children,也不打断当前分组)by xu 20251223
+	          result.push({ ...item, __level: 1 });
+	        }
+	        return result;
+	      };
+	      const menuListComputed = computed(() => normalizeMenuList(props.menuList)); //功能 by xu 20251222
+	      //功能: 分组展开状态(默认展开),避免 computed 生成对象导致 open 状态丢失 by xu 20251222
+	      const groupOpenState = reactive({}); // { [key]: boolean }
+	      const getGroupKey = (item) => (item?.name || item?.title || ""); //功能 by xu 20251222
+	      const isGroupOpen = (item) => { //功能 by xu 20251222
+	        const key = getGroupKey(item);
+	        if (!key) return true;
+	        return groupOpenState[key] !== false;
+	      };
+	      const toggleGroupOpen = (item) => { //功能 by xu 20251222
+	        const key = getGroupKey(item);
+	        if (!key) return;
+	        groupOpenState[key] = !isGroupOpen(item);
+	      };
+	      const getLevelClass = (item, fallbackLevel) => { //功能 by xu 20251222
+	        //功能: “变动”始终按一级样式处理 by xu 20251223
+	        if (item && (item.title === "变动" || item.name === "sys_bd")) {
+	          return "level-1";
+	        }
+	        const level = item?.__level || fallbackLevel || 1;
+	        return level === 2 ? "level-2" : "level-1";
+	      };
+
+	      // v3.0 新增:菜单模式管理 by xu 20251216
+	      const menuMode = ref(props.initialMode);
+	      const isHovering = ref(false);
 
       const toggleMenuMode = () => {
         menuMode.value = menuMode.value === 'collapse' ? 'fixed' : 'collapse';
       };
 
+      //功能: 提供给旧UI弹窗顶部按钮调用的 API(替代点击 .menu-mode-toggle DOM)by xu 20251224
+      const registerSsSubTabApi = () => { //功能 by xu 20251224
+        try {
+          window.SS = window.SS || {};
+          window.SS.dom = window.SS.dom || {};
+          //功能: 兼容小写 ss 命名空间(部分页面只引用 window.ss)by xu 20251224
+          window.ss = window.ss || window.SS;
+          window.ss.dom = window.ss.dom || window.SS.dom;
+          const api = {
+            toggleMenuMode,
+            getMenuMode: () => menuMode.value,
+          };
+          window.SS.dom.ssSubTabApi = api;
+          window.ss.dom.ssSubTabApi = api;
+          //功能: 多层弹窗(如 objPlay -> objInfo) 场景,将 API 注册到 topWindow 供按钮跨层调用 by xu 20251224
+          try {
+            const wdDialogId = window.wd && wd.display && wd.display.getwdDialogId && wd.display.getwdDialogId();
+            if (wdDialogId && window.top) {
+              window.top.__ssSubTabApiMap = window.top.__ssSubTabApiMap || {};
+              window.top.__ssSubTabApiMap[wdDialogId] = api;
+            }
+          } catch (e) { }
+          try { console.log("[SsSubTabApi] registered", window.location && window.location.pathname); } catch (e) { }
+        } catch (e) { }
+      };
+      const unregisterSsSubTabApi = () => { //功能 by xu 20251224
+        try {
+          //功能: 从 topWindow 解绑(避免弹窗关闭后残留)by xu 20251224
+          try {
+            const wdDialogId = window.wd && wd.display && wd.display.getwdDialogId && wd.display.getwdDialogId();
+            if (wdDialogId && window.top && window.top.__ssSubTabApiMap && window.top.__ssSubTabApiMap[wdDialogId]) {
+              delete window.top.__ssSubTabApiMap[wdDialogId];
+            }
+          } catch (e) { }
+          if (window.SS?.dom?.ssSubTabApi?.toggleMenuMode === toggleMenuMode) {
+            delete window.SS.dom.ssSubTabApi;
+          }
+          if (window.ss?.dom?.ssSubTabApi?.toggleMenuMode === toggleMenuMode) {
+            delete window.ss.dom.ssSubTabApi;
+          }
+        } catch (e) { }
+      };
+      //功能: 立即注册,避免 enable 早于 onMounted 导致“api not ready”by xu 20251224
+      registerSsSubTabApi(); //功能 by xu 20251224
+      onBeforeUnmount(unregisterSsSubTabApi); //功能 by xu 20251224
+
       const onMouseEnter = () => {
         if (menuMode.value === 'collapse') {
           isHovering.value = true;
@@ -5808,17 +5970,17 @@ import { EVEN_VAR } from "./EventBus.js";
       // v3.0 新增:iframe 懒加载,点击才加载 by xu 20251216
       const loadedMenus = ref(new Set());
 
-      const isMenuLoaded = (menuName) => {
-        return loadedMenus.value.has(menuName);
-      };
-
-      // 根据标题找到对应的菜单项
-      const findMenuByTitle = (title) => {
-        for (const item of props.menuList) {
-          if (item.children?.length > 0) {
-            const child = item.children.find((c) => c.title === title);
-            if (child) return child;
-          } else if (item.title === title) {
+	      const isMenuLoaded = (menuName) => {
+	        return loadedMenus.value.has(menuName);
+	      };
+
+	      // 根据标题找到对应的菜单项
+	      const findMenuByTitle = (title) => {
+	        for (const item of menuListComputed.value) { //功能 by xu 20251222
+	          if (item.children?.length > 0) {
+	            const child = item.children.find((c) => c.title === title);
+	            if (child) return child;
+	          } else if (item.title === title) {
             return item;
           }
         }
@@ -5831,11 +5993,10 @@ import { EVEN_VAR } from "./EventBus.js";
           const menu = findMenuByTitle(props.activeMenu);
           if (menu) return menu;
         }
-        const firstItem = props.menuList[0];
+        const firstItem = menuListComputed.value[0]; //功能 by xu 20251222
         if (!firstItem) return null;
-        return firstItem.children?.length > 0
-            ? firstItem.children[0]
-            : firstItem;
+        //功能: 默认选中第一个一级菜单(不默认跳到第一个二级)by xu 20251224
+        return firstItem;
       });
 
       const currentMenu = ref(defaultActiveMenu.value);
@@ -5875,22 +6036,27 @@ import { EVEN_VAR } from "./EventBus.js";
         emit("footer-click", { button, index });
       };
 
-      return {
-        currentMenu,
-        selectItem,
-        handleFooterClick,
-        menuMode,
-        isHovering,
-        isExpanded,
-        toggleMenuMode,
-        onMouseEnter,
-        onMouseLeave,
-        isMenuLoaded,
-        getMenuIcon,
-      };
-    },
-    template: `
-          <div class="project-edit-container">
+	      return {
+	        menuListComputed, //功能 by xu 20251222
+	        currentMenu,
+	        selectItem,
+	        handleFooterClick,
+	        getFooterIcon, //功能: SsSubTab 底部按钮支持 icon+文字(icon-base)by xu 20251224
+	        menuMode,
+	        isHovering,
+	        isExpanded,
+	        toggleMenuMode,
+	        onMouseEnter,
+	        onMouseLeave,
+	        isMenuLoaded,
+	        getMenuIcon,
+	        isGroupOpen, //功能 by xu 20251222
+	        toggleGroupOpen, //功能 by xu 20251222
+	        getLevelClass, //功能 by xu 20251222
+	      };
+	    },
+	    template: `
+	          <div class="project-edit-container">
               <div class="left-side"
                    v-if="leftDisplay"
                    :data-mode="menuMode"
@@ -5899,64 +6065,62 @@ import { EVEN_VAR } from "./EventBus.js";
                    @mouseleave="onMouseLeave">
 
                   <!-- 菜单内容 -->
-                  <div class="menu-content">
-                      <div class="scroll-view">
-                          <template v-for="(menuItem, i) in menuList" :key="i">
-                              <!-- 分组菜单 -->
-                              <div v-if="menuItem.children?.length > 0" class="group">
-                                  <div class="menu-item" @click="menuItem.open = !menuItem.open">
-                                      <ss-icon :class="getMenuIcon(menuItem, i)" />
-                                      <span class="menu-label">{{ menuItem.title }}</span>
-                                      <span class="arrow">
-                                          <ss-icon :class="menuItem.open ? 'menu-icon icon-jiantou-shang' : 'menu-icon icon-jiantou-xia'" />
-                                      </span>
-                                      <div class="menu-tooltip">{{ menuItem.title }}</div>
-                                  </div>
-                                  <div v-show="menuItem.open" class="group-detail">
-                                      <div v-for="(item, j) in menuItem.children"
-                                          :key="j"
-                                          class="menu-item"
-                                          :class="{ active: item.name === currentMenu?.name }"
-                                          @click="selectItem(item)">
-                                          <ss-icon :class="getMenuIcon(item, j)" />
-                                          <span class="menu-label">{{ item.title }}</span>
-                                          <span class="menu-item-point" v-if="item.cgxList || item.objectList"></span>
-                                      </div>
-                                  </div>
-                              </div>
+	                  <div class="menu-content">
+	                      <div class="scroll-view">
+		                          <template v-for="(menuItem, i) in menuListComputed" :key="i">
+		                              <!-- 分组菜单 -->
+		                              <div v-if="menuItem.children?.length > 0" class="group">
+		                                  <!-- 功能: 一级(pobj)可点击进入,箭头仅控制展开/收起;二级点击不影响一级选中状态 by xu 20251223 -->
+		                                  <div class="menu-item"
+		                                       :class="[getLevelClass(menuItem, 1), { active: menuItem.name === currentMenu?.name }]"
+		                                       @click="selectItem(menuItem)">
+		                                      <ss-icon :class="getMenuIcon(menuItem, i)" />
+		                                      <span class="menu-label">{{ menuItem.title }}</span>
+		                                      <!-- 功能: 一级菜单有子项时显示 dot(参考全局左侧菜单)by xu 20251224 -->
+		                                      <div class="has-children-dot"></div>
+		                                      <div class="menu-tooltip">{{ menuItem.title }}</div>
+		                                  </div>
+		                                  <!-- 功能: 二级菜单始终展示,不做收缩展开 by xu 20251223 -->
+		                                  <div class="group-detail">
+		                                      <div v-for="(item, j) in menuItem.children"
+		                                          :key="j"
+		                                          class="menu-item"
+		                                          :class="[getLevelClass(item, 2), { active: item.name === currentMenu?.name }]"
+		                                          @click.stop="selectItem(item)">
+		                                          <ss-icon :class="getMenuIcon(item, j)" />
+		                                          <span class="menu-label">{{ item.title }}</span>
+		                                      </div>
+	                                  </div>
+	                              </div>
                               <!-- 普通菜单项 -->
-                              <div v-else
-                                  class="menu-item"
-                                  :class="{ active: menuItem.name === currentMenu?.name }"
-                                  @click="selectItem(menuItem)">
-                                  <ss-icon :class="getMenuIcon(menuItem, i)" />
-                                  <span class="menu-label">{{ menuItem.title }}</span>
-                                  <span class="menu-item-point" v-if="menuItem.cgxList || menuItem.objectList"></span>
-                                  <div class="menu-tooltip">{{ menuItem.title }}</div>
-                              </div>
+		                              <div v-else
+		                                  class="menu-item"
+		                                  :class="[getLevelClass(menuItem, 1), { active: menuItem.name === currentMenu?.name }]"
+		                                  @click="selectItem(menuItem)">
+		                                  <ss-icon :class="getMenuIcon(menuItem, i)" />
+		                                  <span class="menu-label">{{ menuItem.title }}</span>
+	                                  <div class="menu-tooltip">{{ menuItem.title }}</div>
+	                              </div>
                           </template>
                       </div>
                   </div>
 
-                  <!-- v3.0 模式切换按钮,只显示图标 by xu 20251216 -->
-                  <div class="menu-mode-toggle" @click="toggleMenuMode">
-                      <ss-icon class="menu-base-icon icon-qiehuan" />
-                  </div>
-
-                  <!-- 底部按钮 -->
-                  <div v-if="footerButtons.length > 0"
-                      class="sub-tab-menu-footer"
-                      @click="footerButtons[0].onclick">
-                      <div>{{ footerButtons[0].text }}</div>
-                      <ss-icon v-if="footerButtons.length > 1" name="arrow-up" size="24px" />
-                      <div v-if="footerButtons.length > 1" class="sub-tab-menu-popup">
-                          <div v-for="(button, index) in footerButtons.slice(1)"
-                              :key="index"
-                              @click.stop="button.onclick">
-                              {{ button.text }}
-                          </div>
-                      </div>
-                  </div>
+	                  <!-- 底部按钮 -->
+		                  <div v-if="footerButtons.length > 0"
+		                      class="sub-tab-menu-footer"
+		                      :class="{ 'has-text': !!footerButtons[0].text }"
+		                      @click="footerButtons[0].onclick">
+	                      <ss-icon :class="getFooterIcon(footerButtons[0])" />
+	                      <div class="footer-label" v-if="footerButtons[0].text">{{ footerButtons[0].text }}</div>
+	                      <ss-icon v-if="footerButtons.length > 1" class="footer-arrow" name="arrow-up" size="24px" />
+	                      <div v-if="footerButtons.length > 1" class="sub-tab-menu-popup">
+	                          <div v-for="(button, index) in footerButtons.slice(1)"
+	                              :key="index"
+	                              @click.stop="button.onclick">
+	                              {{ button.text }}
+	                          </div>
+	                      </div>
+	                  </div>
 
               </div>
 

+ 30 - 2
js/vue/ss-index-components.js

@@ -74,6 +74,31 @@ export const GlobalHeader = {
             { label: '首页', component: '/index.html' },
         ]);
         const iconItems = Vue.ref(props.iconItems);
+        //功能: 顶部工具栏图标跟随菜单模式(collapse/fixed/expand)by xu 20251222
+        const globalMenuMode = Vue.ref(eventBus.getState('globalMenuModeChange') || 'collapse'); 
+        //功能: icon-base 图标名调整:全局顶部菜单模式使用粗体 icon-autoTxt-bold / icon-fixTxt-bold / icon-fix-bold by xu 20251224
+        const menuMode2IconClass = { 
+            collapse: 'icon-autoTxt-bold',
+            fixed: 'icon-fix-bold',
+            expand: 'icon-fixTxt-bold',
+        };
+        const globalMenuModeSubscriber = eventBus.subscribe('globalMenuModeChange', (mode) => { 
+            globalMenuMode.value = mode || 'collapse';
+        });
+        Vue.onUnmounted(() => { 
+            globalMenuModeSubscriber?.unSubscribe?.();
+        });
+        const resolveHeaderIconClass = (icon) => { 
+            if (!icon) {
+                return '';
+            }
+            if (icon.name === 'qiehuan') {
+                const baseClass = icon.class || '';
+                const iconClass = menuMode2IconClass[globalMenuMode.value] || menuMode2IconClass.collapse;
+                return `${baseClass} ${iconClass}`.trim();
+            }
+            return icon.class || '';
+        };
         const onClickMenuItemsNew = (item) => {
             console.log(item.component, item.js)
             if (item.component && item.js) {
@@ -105,7 +130,6 @@ export const GlobalHeader = {
             eventBus.publish(EVEN_VAR.showGlobalSearchDialog);
         };
         const SsIcon = Vue.resolveComponent('ss-icon');
-        const SsHeaderIcon = Vue.resolveComponent('ss-header-icon');
         const SsGolbalMenuIcon = Vue.resolveComponent('ss-golbal-menu-icon');
         // 引入搜索框
         const SsSearch = Vue.resolveComponent('ss-search');
@@ -158,7 +182,8 @@ export const GlobalHeader = {
                 ]),
                 iconItems.value.map(icon =>
                     Vue.h('div', { class: 'icon-item', onClick: icon.action, style: { display: icon.condition ? (icon.condition() ? 'block' : 'none') : 'block' } }, [
-                        Vue.h(SsHeaderIcon, { class: icon.class })
+                        //功能: 顶部工具栏图标改为 ss-icon + icon-base class by xu 20251222
+                        Vue.h(SsIcon, { class: resolveHeaderIconClass(icon) })
                     ])
                 )
             )
@@ -279,6 +304,8 @@ export const GlobalMenu = {
 
         // 当前菜单模式(默认收起)
         const currentMenuMode = Vue.ref(menuModeDict.collapse);
+        //功能: 同步当前菜单模式到顶部工具栏(qiehuan 图标)by xu 20251222
+        eventBus.publish('globalMenuModeChange', currentMenuMode.value.key); 
 
         // 切换菜单模式(循环:收起 → 固定 → 展开 → 收起)by xu 20251219
         const toggleMenuMode = () => {
@@ -288,6 +315,7 @@ export const GlobalMenu = {
             const nextMode = menuModeDict[modeOrder[nextIndex]];
 
             currentMenuMode.value = nextMode;
+            eventBus.publish('globalMenuModeChange', nextMode.key); 
 
             // 更新 CSS 变量
             updateLayoutWidth(nextMode.width);

+ 20 - 5
page/env/chgChkTab.jsp

@@ -333,7 +333,10 @@ window.SS.dom.tabConfig = window.SS.dom.tabConfig || [];
 			width:"${item.width}",
 			height:"${item.height}",
 			minHeight:"${item.minHeight}",
-			maxHeight:"${item.maxHeight}"
+			maxHeight:"${item.maxHeight}",
+			iconName:"${item.iconName}",
+			pobj:"${item.pobj}",
+			cobj:"${item.cobj}"
 		};
 
 		window.SS.dom.tabConfig.push(item);
@@ -493,8 +496,20 @@ SS.ready(function () {
 		}
 	})
 	
-	window.SS.dom.currentApp = app;
-	
+		window.SS.dom.currentApp = app;
+		
+		//功能: 启用 SsSubTab 弹窗顶部菜单模式切换按钮(旧UI dhtmlx 按钮)by xu 20251224
+		(function tryEnableSsSubTabMenuModeButton() {
+			try {
+				if (wd && wd.display && wd.display.enableSsSubTabMenuModeButton && wd.display.enableSsSubTabMenuModeButton()) {
+					return;
+				}
+			} catch (e) {
+				console.log(e);
+			}
+			setTimeout(tryEnableSsSubTabMenuModeButton, 200);
+		})();
+		
 
-})
-</script>
+	})
+	</script>

+ 22 - 5
page/env/objChg.jsp

@@ -137,7 +137,10 @@ console.log(window.SS.dom.tabConfig);
 			width:"${item.width}",
 			height:"${item.height}",
 			minHeight:"${item.minHeight}",
-			maxHeight:"${item.maxHeight}"
+			maxHeight:"${item.maxHeight}",
+			iconName:"${item.iconName}",
+			pobj:"${item.pobj}",
+			cobj:"${item.cobj}"
 		 };
 
 		 window.SS.dom.tabConfig.push(item);
@@ -228,6 +231,8 @@ tokenCleanser("<ss:serv name='ss.clearPageToken'/>", {tokenList:"<%= pageContext
 		footerButtons: [
 			{
 				text: "保存并提交",
+				//功能: SsSubTab 底部按钮改为 icon-base 图标+文字(收起仅显示图标)by xu 20251224
+				iconName: "icon-save",
 				onclick: function(){
 					// 左侧区域隐藏
 					const app = window.SS.dom.currentApp;
@@ -305,8 +310,20 @@ tokenCleanser("<ss:serv name='ss.clearPageToken'/>", {tokenList:"<%= pageContext
 			}
 		})
 		
-		window.SS.dom.currentApp = app;
-		
+			window.SS.dom.currentApp = app;
+			
+			//功能: 启用 SsSubTab 弹窗顶部菜单模式切换按钮(旧UI dhtmlx 按钮)by xu 20251224
+			(function tryEnableSsSubTabMenuModeButton() {
+				try {
+					if (wd && wd.display && wd.display.enableSsSubTabMenuModeButton && wd.display.enableSsSubTabMenuModeButton()) {
+						return;
+					}
+				} catch (e) {
+					console.log(e);
+				}
+				setTimeout(tryEnableSsSubTabMenuModeButton, 200);
+			})();
+			
 
-	})
-</script>
+		})
+	</script>

+ 22 - 5
page/env/objEdit.jsp

@@ -137,7 +137,10 @@ try{wd.display.setCloseWindowParam('${wdclosewindowparam}');
 			width:"${item.width}",
 			height:"${item.height}",
 			minHeight:"${item.minHeight}",
-			maxHeight:"${item.maxHeight}"
+			maxHeight:"${item.maxHeight}",
+			iconName:"${item.iconName}",
+			pobj:"${item.pobj}",
+			cobj:"${item.cobj}"
 		};
 
 		window.SS.dom.tabConfig.push(item);
@@ -185,6 +188,8 @@ const data = {
 	footerButtons: [
 		{
 			text: "保存并提交",
+			//功能: SsSubTab 底部按钮改为 icon-base 图标+文字(收起仅显示图标)by xu 20251224
+			iconName: "icon-save",
 			onclick: function(){
 					// 左侧区域隐藏
 					const app = window.SS.dom.currentApp;
@@ -259,8 +264,20 @@ SS.ready(function () {
 		}
 	})
 	
-	window.SS.dom.currentApp = app;
-	
+		window.SS.dom.currentApp = app;
+		
+		//功能: 启用 SsSubTab 弹窗顶部菜单模式切换按钮(旧UI dhtmlx 按钮)by xu 20251224
+		(function tryEnableSsSubTabMenuModeButton() {
+			try {
+				if (wd && wd.display && wd.display.enableSsSubTabMenuModeButton && wd.display.enableSsSubTabMenuModeButton()) {
+					return;
+				}
+			} catch (e) {
+				console.log(e);
+			}
+			setTimeout(tryEnableSsSubTabMenuModeButton, 200);
+		})();
+		
 
-})
-</script>
+	})
+	</script>

+ 32 - 10
page/env/objInfo.jsp

@@ -129,7 +129,10 @@ try{wd.display.setCloseWindowParam('${wdclosewindowparam}');
 	width:"${item.width}",
 	height:"${item.height}",
 	minHeight:"${item.minHeight}",
-	maxHeight:"${item.maxHeight}"
+	maxHeight:"${item.maxHeight}",
+	iconName:"${item.iconName}",
+	pobj:"${item.pobj}",
+	cobj:"${item.cobj}"
 	};
 
 	window.SS.dom.tabConfig.push(item);
@@ -152,13 +155,20 @@ const data = {
 	})),
 	leftDisplay: true,
 	footerButtons: [
-		
+		{
+			//功能: SsSubTab 查看页底部按钮使用关闭图标(仅图标)by xu 20251224
+			iconName: "icon-close",
+			text: "",
+			onclick: function(){
+				wd.display.closeDialog();
+			}
+		}
 	]
 }
-SS.ready(function () {
-	const app = window.SS.dom.initializeFormApp({
-		el: "#app",
-		data(){
+	SS.ready(function () {
+		const app = window.SS.dom.initializeFormApp({
+			el: "#app",
+			data(){
 			return data;
 		},
 		methods:{
@@ -199,8 +209,20 @@ SS.ready(function () {
 		}
 	})
 	
-	window.SS.dom.currentApp = app;
-	
+		window.SS.dom.currentApp = app;
 
-})
-</script>
+		//功能: 启用 SsSubTab 弹窗顶部菜单模式切换按钮(旧UI dhtmlx 按钮)by xu 20251223
+		(function tryEnableSsSubTabMenuModeButton() {
+			try {
+				if (wd && wd.display && wd.display.enableSsSubTabMenuModeButton && wd.display.enableSsSubTabMenuModeButton()) {
+					return;
+				}
+			} catch (e) {
+				console.log(e);
+			}
+			setTimeout(tryEnableSsSubTabMenuModeButton, 200);
+		})();
+		
+
+	})
+	</script>

+ 11 - 3
page/env/objPlay.ss.jsp

@@ -13,24 +13,32 @@
 	<equal.ss val="${ydsq_sh_ck}" val2="1">
 		<style>
 			.authorize{display:none;}
+			
+			
+	
 		</style>
 		<script>setTimeout(function(){$(".authorize").remove();},500);</script>
 	</equal.ss>
+	<style>
+		#iframe{
+			height: 100% !important;
+		}
+	</style>
 </head>
 <body>
 <%-- 改为 <data@ss name="info"/>。Lin
 <tab@ss name="info"/> --%>
 <data.ss name="info"/>
 
-<iframe width="100%" height="100%" param='${info.param}' frameborder="0"
+<iframe width="100%" height="100%" param='${info.param}' frameborder="0" id="iframe" 
 		src="<varServ.ss name='${info.service}' dest='${info.dest}' parm='${info.param}'/>">
 </iframe>
 
-<div class='bottom-div'><!-- 底部区域 -->
+<!-- <div class='bottom-div'>
 	<div class="bottom-down-div border-top">
 		<input type="button" value="关闭" onclick="wd.display.closeDialog();"  class="bottom-button">
 	</div>
-<div>
+<div> -->
 
 <script>
 	function authorize(){

+ 10 - 11
page/home.jsp

@@ -350,17 +350,16 @@
 					url:'/initDesktop?'
 				}]
               },
-			  iconItems: [
-				{ name: 'qiehuan', size: '22px', class: 'header-menu-toggle', action: () => eventBus.publish('toggleGlobalMenuMode') }, // v3.0 切换左侧菜单模式 by xu 20251219
-			 	{ name: 'question', size: '22px', class: 'header-help', action: () => wd.display.showComponent({show:["wdDialog"],url:"<ss:serv name='querySYSHelp' parm='{"wdConfirmationCaptchaService":"0","ishelp":"true","dialogid":"1"}' dest='cmsPlay'/>",title:"使用指南",width:900,height:800,minHeight:1,maxHeight:800}) },
-				{ name: 'check', size: '22px',class: 'header-save', condition: () => currentMode.value === sysMode.edit.key, action: () => toggoleSysMode()},
-				{ name: 'setting-fill', size: '22px', class: 'header-setting',condition: () => currentMode.value !== sysMode.edit.key, action: () => toggoleSysMode() },
-				{ name: 'list-fill', size: '22px', class: 'header-menu', action: () => console.log('list-fill clicked') },
-				
-				{ name: 'topic-fill', size: '22px', class: 'header-skin', action: () => wd.display.showComponent({show:["wdDialog"],url:"<ss:serv name='gxhpf_cx' parm='{"wdConfirmationCaptchaService":"0"}' dest='ty_hf'/>",title:"换肤",width:799,height:757}) },
-				{ name: 'lock-fill', size: '22px', class: 'header-lock', action: () => this.lockScreenFun() },
-				{ name: 'quit', class: 'big', size: '36px', class: 'header-logout', action: () => wd.display.exit() },
-			  ],
+				  iconItems: [
+					{ name: 'qiehuan', class: 'header-toolbar-icon', action: () => eventBus.publish('toggleGlobalMenuMode') }, //功能: 切换左侧菜单模式(图标跟随菜单模式切换)by xu 20251222
+				 	{ name: 'question', class: 'header-toolbar-icon icon-help-bold', action: () => wd.display.showComponent({show:["wdDialog"],url:"<ss:serv name='querySYSHelp' parm='{"wdConfirmationCaptchaService":"0","ishelp":"true","dialogid":"1"}' dest='cmsPlay'/>",title:"使用指南",width:900,height:800,minHeight:1,maxHeight:800}) }, //功能: 顶部帮助图标 by xu 20251222
+					{ name: 'check', class: 'header-toolbar-icon icon-save', condition: () => currentMode.value === sysMode.edit.key, action: () => toggoleSysMode()}, //功能 by xu 20251222
+					{ name: 'setting-fill', class: 'header-toolbar-icon icon-set-bold', condition: () => currentMode.value !== sysMode.edit.key, action: () => toggoleSysMode() }, //功能 by xu 20251222
+					// { name: 'list-fill', class: 'header-toolbar-icon', action: () => console.log('list-fill clicked') }, //功能: 顶部菜单按钮暂时隐藏 by xu 20251222
+					{ name: 'topic-fill', class: 'header-toolbar-icon icon-skin', action: () => wd.display.showComponent({show:["wdDialog"],url:"<ss:serv name='gxhpf_cx' parm='{"wdConfirmationCaptchaService":"0"}' dest='ty_hf'/>",title:"换肤",width:799,height:757}) }, //功能 by xu 20251222
+					{ name: 'lock-fill', class: 'header-toolbar-icon icon-lock-bold', action: () => this.lockScreenFun() }, //功能 by xu 20251222
+					{ name: 'quit', class: 'header-toolbar-logout-icon icon-exit', action: () => wd.display.exit() }, //功能 by xu 20251222
+				  ],
             };
           },
           methods: {

+ 16 - 4
page/homep/urgeHomep_tab.jsp

@@ -171,8 +171,20 @@ SS.ready(function () {
 		}
 	})
 	
-	window.SS.dom.currentApp = app;
-	
+		window.SS.dom.currentApp = app;
+		
+		//功能: 启用 SsSubTab 弹窗顶部菜单模式切换按钮(旧UI dhtmlx 按钮)by xu 20251224
+		(function tryEnableSsSubTabMenuModeButton() {
+			try {
+				if (wd && wd.display && wd.display.enableSsSubTabMenuModeButton && wd.display.enableSsSubTabMenuModeButton()) {
+					return;
+				}
+			} catch (e) {
+				console.log(e);
+			}
+			setTimeout(tryEnableSsSubTabMenuModeButton, 200);
+		})();
+		
 
-})
-</script>
+	})
+	</script>

+ 266 - 40
skin/easy/css/base.css

@@ -75,6 +75,117 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+/* 功能: icon-base 的 icon-close 与旧UI .icon-close(svg背景图)冲突,使用时强制清理旧样式 by xu 20251224 */
+.menu-base-icon.icon-close.icon-container {
+  background-image: none !important;
+  background: none !important;
+  position: relative !important;
+  top: auto !important;
+  right: auto !important;
+  width: 24px !important;
+  height: 24px !important;
+}
+
+/* 功能: 旧UI弹窗(dhtmlx)顶部按钮图标组(icon-base)by xu 20251222 */
+.dialog-toolbar-icon {
+  width: 36px !important;
+  height: 36px !important;
+  /* 功能: 不使用 display:...!important,避免 w.button(...).hide() 失效导致按钮总是显示 by xu 20251225 */
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-family: "icon-base" !important;
+  font-size: 20px;
+  line-height: 1;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  color: #c3c6ca;
+  cursor: pointer;
+  border-radius: 4px;
+  background: transparent;
+  /* 功能: 兼容按钮原有 background 图标,强制清空 by xu 20251223 */
+  background-image: none !important;
+}
+
+/* 功能: 旧UI弹窗(dhtmlx)关闭按钮图标组(icon-base,48px容器+24px字形)by xu 20251223 */
+.dialog-toolbar-close-icon {
+  width: 48px !important;
+  height: 48px !important;
+  /* 功能: 不使用 display:...!important,避免与 show/hide 冲突 by xu 20251225 */
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-family: "icon-base" !important;
+  font-size: 24px;
+  line-height: 1;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  color: #c3c6ca;
+  cursor: pointer;
+  border-radius: 4px;
+  /* 功能: 避免 main.css/.icon-close 的绝对定位干扰,同时支持分割线定位 by xu 20251224 */
+  position: relative !important;
+  top: auto !important;
+  right: auto !important;
+  left: auto !important;
+  bottom: auto !important;
+  /* 功能: 兼容按钮原有 background 图标,强制清空 by xu 20251223 */
+  background-image: none !important;
+}
+
+/* 功能: 弹窗关闭按钮左侧分割线(避免与 .icon-close:before 图标字形冲突)by xu 20251224 */
+.dialog-toolbar-close-icon::after {
+  content: "";
+  position: absolute;
+  left: -1px;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 1px;
+  height: 20px;
+  background: #c3c6ca;
+  pointer-events: none;
+}
+
+/* 功能: 旧UI弹窗(dhtmlx)顶部按钮 hover/active 样式 by xu 20251222 */
+.dialog-toolbar-icon:hover {
+  background: #383d50;
+  color: #fff;
+}
+.dialog-toolbar-icon:active {
+  background: #000;
+  color: #fff;
+}
+
+/* 功能: 旧UI弹窗(dhtmlx)关闭按钮 hover/active 样式 by xu 20251223 */
+.dialog-toolbar-close-icon:hover {
+  background: #383d50;
+  color: #fff;
+}
+.dialog-toolbar-close-icon:active {
+  background: #000;
+  color: #fff;
+}
+
+/* 功能: 旧UI弹窗右上角按钮图标尺寸统一(close 24,其它 20)by xu 20251223 */
+.dhtmlx_skin_dhx_skyblue div.dhtmlx_wins_btns .largeIcon-close,
+.dhtmlx_skin_dhx_skyblue div.dhtmlx_wins_btns .largeIcon-close:hover,
+.dhtmlx_skin_dhx_skyblue div.dhtmlx_wins_btns .largeIcon-close:active {
+  background-size: 24px !important;
+}
+
+/* 功能: icon-base 图标名调整:弹窗顶部按钮 icon-ptab/icon-pval by xu 20251224 */
+.dhtmlx_skin_dhx_skyblue div.dhtmlx_wins_btns .icon-ptab,
+.dhtmlx_skin_dhx_skyblue div.dhtmlx_wins_btns .icon-pval,
+.dhtmlx_skin_dhx_skyblue div.dhtmlx_wins_btns .icon-lock,
+.dhtmlx_skin_dhx_skyblue div.dhtmlx_wins_btns .icon-help,
+.dhtmlx_skin_dhx_skyblue div.dhtmlx_wins_btns .icon-download,
+.dhtmlx_skin_dhx_skyblue div.dhtmlx_wins_btns .icon-favorite,
+.dhtmlx_skin_dhx_skyblue div.dhtmlx_wins_btns .icon-fullscreen {
+  background-size: 20px !important;
+}
+
 /* 通用icon */
 .common-icon{
   font-size: 22px;
@@ -295,6 +406,56 @@
   --icon-item-border-color: #edf1f5;
 }
 
+/* 功能: 顶部工具栏图标组(icon-base)- 替代 .header-icon 的 v1.0 ::after 方案 by xu 20251222 */
+.header-toolbar-icon {
+  width: 36px;
+  height: 36px;
+  font-size: 22px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-family: "icon-base" !important;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  border-radius: 4px;
+  border: 1px solid var(--icon-item-border-color, transparent);
+  color: inherit;
+}
+
+/* 功能: 顶部工具栏图标 hover(icon-base)by xu 20251222 */
+.header-toolbar-icon:hover {
+  --icon-item-border-color: #edf1f5;
+}
+
+/* 功能: 顶部退出图标组(icon-base)- 48px 特殊处理 by xu 20251222 */
+.header-toolbar-logout-icon {
+  width: 48px;
+  height: 48px;
+  font-size: 33px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-family: "icon-base" !important;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  border-radius: 4px;
+  border: 1px solid var(--icon-item-border-color, transparent);
+  color: inherit;
+}
+
+/* 功能: 顶部退出图标 hover(icon-base)by xu 20251222 */
+.header-toolbar-logout-icon:hover {
+  --icon-item-border-color: #edf1f5;
+  background-color: transparent;
+}
+
+/* 功能: 顶部退出图标按下态(icon-base)- 替代 hover 置黑 by xu 20251222 */
+.icon-item:active .header-toolbar-logout-icon {
+  background-color: #000;
+}
+
 .header-help::after {
   content: "\e6a7";
   /* 帮助图标 */
@@ -1373,6 +1534,19 @@ input::placeholder ,textarea::placeholder{
 .left-side-container .menu-item.active .menu-item-content{
   background: #fff;
   border-right-color: #fff; /* 边框与背景同色,视觉上消失 */
+  /* 功能: 全局左侧菜单 active 增加上下分割线(#dadee2)by xu 20251223 */
+  box-shadow: inset 0 1px 0 #dadee2, inset 0 -1px 0 #dadee2;
+}
+
+/* 功能: 如果全局左侧菜单 active 是第一个菜单项,不显示上分割线 by xu 20251224 */
+.left-side-container .fixed-top .menu-item.active .menu-item-content,
+.left-side-container .scrollable-content > .menu-item.active:first-of-type .menu-item-content {
+  box-shadow: inset 0 -1px 0 #dadee2;
+}
+
+/* 功能: 避免 active 同时叠加 level 分隔线 ::after,导致双线 by xu 20251223 */
+.layout-container .menu-item.active::after {
+  display: none;
 }
 /* 全局左部边框结束 */
 
@@ -2357,6 +2531,10 @@ input::placeholder ,textarea::placeholder{
   table-layout: fixed;
 }
 
+.form-container .content-box .content-div{
+  height: auto !important;
+    overflow: hidden !important;
+}
 
 .form-container th {
   /* width: 200px; */
@@ -4813,7 +4991,7 @@ input::placeholder ,textarea::placeholder{
 /* v3.0 左侧菜单始终absolute定位,不占文本流 by xu 20251219 */
 .project-edit-container .left-side {
   width: 60px;
-  background-color: #fafbfe;
+  background-color: #edf1f5;
   transition: width 0.2s ease;
   display: flex;
   flex-direction: column;
@@ -4885,7 +5063,8 @@ input::placeholder ,textarea::placeholder{
   justify-content: center;
   gap: 5px;
   padding: 0 8px;
-  border-right: 2px solid #dadee2;
+  /* 功能: 右侧分割线由 .left-side::after 统一提供,避免 item 自带边框导致视觉不一致 by xu 20251223 */
+  border-right: none;
 }
 
 /* 展开时左对齐 */
@@ -4901,7 +5080,37 @@ input::placeholder ,textarea::placeholder{
 /* active效果,隐藏右边框 by xu 20251219 改为白色 */
 .project-edit-container .menu-item.active {
   background: #fff;
-  border-right-color: #fff;
+  /* 功能: active 上下增加分割线(#dadee2)by xu 20251223 */
+  box-shadow: inset 0 1px 0 #dadee2, inset 0 -1px 0 #dadee2;
+}
+
+/* 功能: 如果 active 是第一个菜单项,不显示上分割线 by xu 20251224 */
+.project-edit-container .scroll-view > .menu-item.active:first-of-type,
+.project-edit-container .scroll-view > .group:first-of-type > .menu-item.active {
+  box-shadow: inset 0 -1px 0 #dadee2;
+}
+
+/* 功能: SsSubTab 一级菜单 dot(有子项时显示,参考全局左侧菜单)by xu 20251224 */
+.project-edit-container .has-children-dot {
+  position: absolute;
+  top: 8px;
+  left: 45px;
+  width: 6px;
+  height: 6px;
+  background: #999999;
+  border-radius: 50%;
+  z-index: 10;
+}
+
+/* 功能: SsSubTab 菜单图标大小分层(一级24px,二级20px)by xu 20251222 */
+.project-edit-container .menu-item.level-1 .menu-icon,
+.project-edit-container .menu-item.level-1 .menu-base-icon {
+  font-size: 24px !important;
+}
+
+.project-edit-container .menu-item.level-2 .menu-icon,
+.project-edit-container .menu-item.level-2 .menu-base-icon {
+  font-size: 20px !important;
 }
 
 .project-edit-container .menu-item .arrow {
@@ -4982,12 +5191,9 @@ input::placeholder ,textarea::placeholder{
   display: none;
 }
 
+/* 功能: SsSubTab 不显示菜单 dot(历史样式保留但强制隐藏)by xu 20251223 */
 .menu-item-point{
-  width: 6px;
-  height: 6px;
-  display: block;
-  border-radius: 50%;
-  background: #666;
+  display: none !important;
 }
 
 /* 删除旧的底部分割线样式 */
@@ -5001,62 +5207,82 @@ input::placeholder ,textarea::placeholder{
 }
 
 .project-edit-container .group-detail {
-  background: rgba(0, 0, 0, 0.03);
-}
-
-/* v3.0 子菜单样式 by xu 20251216 */
-.project-edit-container .group-detail .menu-item {
-  padding-left: 20px;
+  /* 功能: 二级菜单背景色与一级一致,不单独铺底色 by xu 20251223 */
+  background: transparent;
 }
 
+/* 功能: SsSubTab 二级菜单不缩进,保持与一级对齐(仅图标大小区分)by xu 20251223 */
+.project-edit-container .group-detail .menu-item,
 .project-edit-container .left-side.is-expanded .group-detail .menu-item {
-  padding-left: 50px;
+  padding-left: 0;
 }
 
 /* v3.0 收起时隐藏子菜单 by xu 20251216 */
 .project-edit-container .left-side:not(.is-expanded) .group-detail {
-  display: none;
-}
-
-/* v3.0 新增:模式切换按钮,参考GlobalMenu by xu 20251216 */
-.project-edit-container .menu-mode-toggle {
-  min-height: 40px;
-  width: 100%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 12px 8px;
-  cursor: pointer;
-  border-top: 1px solid #d8dae3;
-  border-right: 2px solid #dadee2;
-  flex-shrink: 0;
+  /* 功能: 二级菜单始终展示(不依赖展开态)by xu 20251223 */
+  display: block;
 }
 
-.project-edit-container .menu-mode-toggle:hover {
-  background: rgba(0, 0, 0, 0.05);
-}
-
-.project-edit-container .menu-mode-toggle .icon-container {
-  padding: 0 !important;
-}
+/* 功能: SsSubTab 菜单模式切换移至旧UI弹窗顶部按钮(不再渲染 .menu-mode-toggle)by xu 20251224 */
 
 .sub-tab-menu-footer {
   height: 60px;
   flex-shrink: 0;
-  background: #afb8d0;
+  background: #edf1f5;
   display: flex;
   flex-direction: row;
-  justify-content: center;
+  /* 功能: SsSubTab 底部按钮(保存/关闭)使用图标+文字;用 padding 对齐菜单icon,避免展开时发生位移 by xu 20251224 */
+  justify-content: flex-start;
   align-items: center;
   gap: 10px;
+  padding: 0 8px;
   font-size: 20px;
-  color: #fff;
+  /* 功能: SsSubTab 底部按钮文字默认色(适配浅色背景)by xu 20251224 */
+  color: #565d6d;
   cursor: pointer;
   position: relative;
+  border-right: 1px solid #dadee2;
+}
+
+/* 功能: SsSubTab 底部按钮顶部增加分割线(top0 w-95% h-1px bg-#e2e4ec 居中)by xu 20251224 */
+.project-edit-container .sub-tab-menu-footer::before {
+  content: "";
+  position: absolute;
+  top: -1px;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 95%;
+  height: 1px;
+  background: #e2e4ec;
+}
+
+/* 功能: 底部按钮 icon-base 图标默认色(关闭/保存)#c3c6ca by xu 20251224 */
+.project-edit-container .sub-tab-menu-footer .menu-base-icon {
+  color: #c3c6ca !important;
+}
+
+/* 功能: 收起状态仅展示图标(隐藏文字)by xu 20251224 */
+
+/* 功能: 底部按钮文字样式 + 收起时隐藏 by xu 20251224 */
+.project-edit-container .sub-tab-menu-footer .footer-label {
+  font-size: 16px;
+  line-height: 1;
+  white-space: nowrap;
+}
+
+.project-edit-container .left-side:not(.is-expanded) .sub-tab-menu-footer .footer-label {
+  display: none;
 }
 
 .sub-tab-menu-footer:hover {
   background: #393d51;
+  /* 功能: hover 时文字变白 by xu 20251224 */
+  color: #fff;
+}
+
+/* 功能: hover 时 icon 也变白 by xu 20251224 */
+.project-edit-container .sub-tab-menu-footer:hover .menu-base-icon {
+  color: #fff !important;
 }
 
 .sub-tab-menu-popup {

+ 19 - 10
skin/easy/css/icon-base/iconfont.css

@@ -5,6 +5,7 @@
        url('../../fonts/icon-base/iconfont.ttf') format('truetype');
 }
 
+
 .icon-base {
   font-family: "icon-base" !important;
   font-size: 16px;
@@ -13,35 +14,43 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
-.icon-zidongcaidan-xi:before {
+.icon-ptab:before {
+  content: "\e655";
+}
+
+.icon-pval:before {
+  content: "\e654";
+}
+
+.icon-autoTxt:before {
   content: "\e649";
 }
 
-.icon-zidongcaidan-cu:before {
+.icon-autoTxt-bold:before {
   content: "\e648";
 }
 
-.icon-zhankaicaidan-xi:before {
+.icon-fixTxt:before {
   content: "\e647";
 }
 
-.icon-zhankaicaidan-cu:before {
+.icon-fixTxt-bold:before {
   content: "\e646";
 }
 
-.icon-gudingcaidan-xi:before {
+.icon-fix:before {
   content: "\e644";
 }
 
-.icon-gudingcaidan-mian:before {
+.icon-fix-bold:before {
   content: "\e645";
 }
 
-.icon-shengyin:before {
+.icon-spk:before {
   content: "\e642";
 }
 
-.icon-chilun:before {
+.icon-set:before {
   content: "\e641";
 }
 
@@ -53,7 +62,7 @@
   content: "\e630";
 }
 
-.icon-spk:before {
+.icon-spk-bold:before {
   content: "\e62f";
 }
 
@@ -73,7 +82,7 @@
   content: "\e62b";
 }
 
-.icon-quxiao:before {
+.icon-cl:before {
   content: "\e62a";
 }
 

BIN
skin/easy/fonts/icon-base/iconfont.ttf


BIN
skin/easy/fonts/icon-base/iconfont.woff


BIN
skin/easy/fonts/icon-base/iconfont.woff2


+ 2 - 1
skin/easy/main.css

@@ -7,6 +7,7 @@
 html,
 body {
     height: 100%;
+    overflow: hidden;
 }
 
 body,
@@ -4739,7 +4740,7 @@ table.list {
     position: relative;
     overflow: auto;
     box-sizing: border-box;
-    padding: 12px 8px 17px 8px;
+    padding: 0;
     color: #666;
     font-size: 14px;
 }

+ 230 - 25
ss/js/display.js

@@ -404,27 +404,33 @@
 
 	};
 
-	//按钮 [id,pos,title,label,className]
-	wd.display.wdButtons = [
-	["wdHelp", 3, "帮助", "帮助", "icon-help"],
-	["CMS_download", 3, "下载", "下载", "icon-download"],
-	["wdShare", 3, "收藏", "收藏", "icon-favorite"],
-	["wdShared", 3, "已收藏", "已收藏", "icon-favoriteOn"],
-	["editWdHelp", 3, "编辑帮助", "编辑帮助", "icon-setHelp"],
-	["editWdHelp_save", 3, "编辑帮助保存", "编辑帮助保存", "invertIcon-save"],
-	["wdTab", 3, "编辑选项卡", "编辑选项卡", "icon-set"],
-	["wdTab_save", 3, "保存选项卡", "保存选项卡", "invertIcon-save"],
-	["wdRecord", 3, "个人选值", "个人选值", "icon-setValue"],
-	["wdRecord_save", 3, "个人选值保存", "个人选值保存", "invertIcon-save"],
-	["CMS_fullscreen", 3, "全屏", "全屏", "dialog-restoreButton"],
-	["CMS_nrProperty", 3, "内容属性", "内容属性", "icon-property"],
-	["change_on", 3, "变动查看-开", "变动查看-开", "dialog-changeOnButton"],
-	["change_off", 3, "变动查看-关", "变动查看-关", "dialog-changeButton"],
-	["lockScreen", 3, "锁定", "锁定", "icon-lock"],
-	["wdPrint", 3, "打印", "打印", "icon-print"],
-	["wdBatchPrint", 3, "批量打印", "批量打印", "icon-print"],
-	["wdHelpCopyhexcode", 3, "复制帮助代码", "复制帮助代码", "icon-key"]
-	];
+			//按钮 [id,pos,title,label,className]
+			wd.display.wdButtons = [
+			//功能: 旧UI弹窗右上角按钮图标升级为 icon-base(先改 help/lock/close)by xu 20251223
+			["wdHelp", 3, "帮助", "帮助", "dialog-toolbar-icon icon-help"],
+			["CMS_download", 3, "下载", "下载", "icon-download"],
+			["wdShare", 3, "收藏", "收藏", "icon-favorite"],
+			["wdShared", 3, "已收藏", "已收藏", "icon-favoriteOn"],
+			["editWdHelp", 3, "编辑帮助", "编辑帮助", "icon-setHelp"],
+			["editWdHelp_save", 3, "编辑帮助保存", "编辑帮助保存", "invertIcon-save"],
+			//功能: icon-base 图标名调整:编辑选项卡 icon-ptab(弹窗顶部用细体)by xu 20251224
+			["wdTab", 3, "编辑选项卡", "编辑选项卡", "dialog-toolbar-icon icon-ptab"],
+			//功能: icon-base 图标名调整:保存选项卡使用 icon-save(不再用 invertIcon-save)by xu 20251225
+			["wdTab_save", 3, "保存选项卡", "保存选项卡", "invertIcon-save dialog-toolbar-icon icon-save"],
+			//功能: icon-base 图标名调整:个人选值 icon-pval(弹窗顶部用细体)by xu 20251224
+			["wdRecord", 3, "个人选值", "个人选值", "dialog-toolbar-icon icon-pval"],
+			//功能: icon-base 图标名调整:个人选值保存使用 icon-save(不再用 invertIcon-save)by xu 20251225
+			["wdRecord_save", 3, "个人选值保存", "个人选值保存", "invertIcon-save dialog-toolbar-icon icon-save"],
+			["CMS_fullscreen", 3, "全屏", "全屏", "dialog-restoreButton"],
+			["CMS_nrProperty", 3, "内容属性", "内容属性", "icon-property"],
+			["change_on", 3, "变动查看-开", "变动查看-开", "dialog-changeOnButton"],
+			["change_off", 3, "变动查看-关", "变动查看-关", "dialog-changeButton"],
+			["lockScreen", 3, "锁定", "锁定", "dialog-toolbar-icon icon-lock"],
+			["wdPrint", 3, "打印", "打印", "icon-print"],
+			["wdBatchPrint", 3, "批量打印", "批量打印", "icon-print"],
+			["wdHelpCopyhexcode", 3, "复制帮助代码", "复制帮助代码", "icon-key"]
+			//功能: SsSubTab 菜单模式切换按钮改为按需创建,避免所有弹窗都出现 by xu 20251224
+			];
 
 	wd.display.initStyleWdButtons = function () {
 
@@ -433,11 +439,12 @@
 	wd.topWindow.$("div.dhtmlx_button_" + wd.display.wdButtons[i][0] + "_default").addClass(wd.display.wdButtons[i][4]);
 	}
 
-	// 特殊处理close,因为如果close和dhtmlx的close冲突,会隐藏原生,新增一个按钮
-	var className = "dhtmlx_button_close_default";
-	wd.topWindow.$("div.dhtmlx_button_close_default").removeClass(className).addClass("largeIcon-close");
+			// 特殊处理close,因为如果close和dhtmlx的close冲突,会隐藏原生,新增一个按钮
+			var className = "dhtmlx_button_close_default";
+			//功能: 弹窗关闭按钮使用 icon-base(避免依赖 svg/png)by xu 20251223
+			wd.topWindow.$("div.dhtmlx_button_close_default").removeClass(className).addClass("dialog-toolbar-close-icon icon-close");
 
-	}
+			}
 
 	/**
 	* 添加隐藏按钮
@@ -584,6 +591,204 @@
 
 	}
 
+		//功能: SsSubTab 菜单模式切换按钮启用(由页面主动调用)by xu 20251223
+		wd.display.enableSsSubTabMenuModeButton = function () {
+				try {
+					var wdDialogId = wd.display.getwdDialogId();
+					if (!wdDialogId) return false;
+					if (!wd.topWindow || !wd.topWindow.dhxWins) return false;
+					var w = wd.topWindow.dhxWins.window(wdDialogId);
+					if (!w) return false;
+					//功能: 打印调试信息(定位点击无反应问题)by xu 20251224
+					try { console.log("[SsSubTabMenuMode] enable start, wdDialogId=", wdDialogId); } catch (e) { }
+					
+					//功能: 获取当前弹窗的 DOM 容器(用于精准定位按钮,避免多弹窗 querySelector 选错)by xu 20251224
+					var iframeEl = null;
+					//功能: 优先通过 wdDialogId 精确定位 iframe(避免误拿到 SsSubTab 内部 iframe)by xu 20251224
+					try {
+						var ifs = wd.topWindow.document.getElementsByTagName("IFRAME");
+						for (var ii = 0; ii < ifs.length; ii++) {
+							var iframeObj = ifs[ii];
+							var tempId = iframeObj.getAttribute("wdDialogId");
+							if (!tempId && iframeObj.dataset) {
+								tempId = iframeObj.dataset["wdDialogId"];
+							}
+							if (tempId && (tempId + "") === (wdDialogId + "")) {
+								iframeEl = iframeObj;
+								break;
+							}
+						}
+					} catch (e) { }
+					if (!iframeEl) {
+						try { iframeEl = w.getFrame && w.getFrame(); } catch (e) { }
+					}
+					if (!iframeEl) {
+						try { console.log("[SsSubTabMenuMode] fail: no iframeEl"); } catch (e) { }
+						return false;
+					}
+					var winEl = null;
+					try { winEl = iframeEl.closest && iframeEl.closest("div.dhtmlx_window_active,div.dhtmlx_window_inactive"); } catch (e) { }
+					if (!winEl) {
+						try { console.log("[SsSubTabMenuMode] fail: no winEl"); } catch (e) { }
+						return false;
+					}
+
+			//功能: 精准获取当前弹窗的按钮 DOM(dhtmlx 会切换 _default/_over_default 等)by xu 20251224
+			var btnEl = null;
+			try { btnEl = winEl.querySelector("div[class*='dhtmlx_button_ssSubTabMenuMode_']"); } catch (e) { }
+			if (!btnEl) {
+				//功能: 仅在使用 SsSubTab 的页面按需创建按钮,避免所有弹窗默认带出 by xu 20251224
+				//功能: icon-base 图标名调整:弹窗顶部菜单模式按钮使用细体 icon-autoTxt / icon-fix by xu 20251224
+				try { w.addUserButton("ssSubTabMenuMode", 3, "菜单模式", "菜单模式", "dialog-toolbar-icon icon-autoTxt"); } catch (e) { }
+				try { wd.display.initStyleWdButtons && wd.display.initStyleWdButtons(); } catch (e) { }
+				try { btnEl = winEl.querySelector("div[class*='dhtmlx_button_ssSubTabMenuMode_']"); } catch (e) { }
+				if (!btnEl) {
+					// 页面可能还没创建完成,交给调用方重试 by xu 20251224
+					try { console.log("[SsSubTabMenuMode] fail: button dom not found"); } catch (e) { }
+					return false;
+				}
+				//功能: 打印调试信息(按钮按需创建成功)by xu 20251224
+				try { console.log("[SsSubTabMenuMode] button created"); } catch (e) { }
+			}
+			var btn = null;
+			try { btn = w.button && w.button("ssSubTabMenuMode"); } catch (e) { }
+
+					//功能: 弹窗右上角切换按钮放到最左边(flex 顺序)by xu 20251223
+					try {
+						var btnContainer = btnEl.parentNode;
+						if (btnContainer && btnContainer.firstElementChild && btnContainer.firstElementChild !== btnEl) {
+							btnContainer.insertBefore(btnEl, btnContainer.firstElementChild);
+						}
+					} catch (e) { }
+
+					//功能: 旧UI弹窗中使用顶部按钮控制 SsSubTab,隐藏组件内置切换按钮 by xu 20251223
+					try { document.documentElement && document.documentElement.classList && document.documentElement.classList.add("ss-sub-tab-dialog"); } catch (e) { }
+
+				// 确保组类存在 by xu 20251223
+				try { btnEl.classList && btnEl.classList.add("dialog-toolbar-icon"); } catch (e) { }
+
+			//功能: 获取 iframeWindow(用于 click 时动态拿 API,避免 enable 时机问题)by xu 20251224
+			var iframeWindow = null;
+			try { iframeWindow = iframeEl && iframeEl.contentWindow; } catch (e) { }
+			if (!iframeWindow) {
+				try { console.log("[SsSubTabMenuMode] fail: no iframeWindow"); } catch (e) { }
+				return false;
+			}
+			
+			//功能: 递归在多层 iframe 中查找 SsSubTab API(objPlay -> objInfo -> ss-sub-tab)by xu 20251224
+			var findSsSubTabCtx = function (win, depth) { //功能 by xu 20251224
+				if (!win) return null;
+				if (depth > 6) return null;
+				try {
+					var api =
+						(win.SS && win.SS.dom && win.SS.dom.ssSubTabApi) ||
+						(win.ss && win.ss.dom && win.ss.dom.ssSubTabApi);
+					var left = win.document && win.document.querySelector && win.document.querySelector(".project-edit-container .left-side");
+					if (api && api.toggleMenuMode && left) {
+						return { win: win, api: api, leftSide: left };
+					}
+				} catch (e) { }
+				try {
+					var ifs = win.document && win.document.getElementsByTagName ? win.document.getElementsByTagName("IFRAME") : [];
+					if (!ifs || !ifs.length) return null;
+					// 限制扫描数量,避免极端页面卡顿 by xu 20251224
+					var maxScan = Math.min(ifs.length, 30);
+					for (var i = 0; i < maxScan; i++) {
+						var cw = null;
+						try { cw = ifs[i].contentWindow; } catch (e) { cw = null; }
+						if (!cw) continue;
+						var r = findSsSubTabCtx(cw, depth + 1);
+						if (r) return r;
+					}
+				} catch (e) { }
+				return null;
+			};
+
+				var updateIcon = function () { //功能 by xu 20251224
+					// dhtmlx hover/active 会切换 class(_default/_over_default 等),这里每次取当前节点 by xu 20251224
+					var currentBtnEl = null;
+					try { currentBtnEl = winEl.querySelector("div[class*='dhtmlx_button_ssSubTabMenuMode_']"); } catch (e) { }
+					if (!currentBtnEl || !currentBtnEl.classList) return;
+					//功能: 优先从 topWindow 的 apiMap 取(解决 objPlay->objInfo 这种多层iframe拿不到 SS 的问题)by xu 20251224
+					var ctx = null;
+					try {
+						var apiFromTop = wd.topWindow && wd.topWindow.__ssSubTabApiMap && wd.topWindow.__ssSubTabApiMap[wdDialogId];
+						if (apiFromTop && apiFromTop.toggleMenuMode) {
+							ctx = { api: apiFromTop, leftSide: null };
+						}
+					} catch (e) { }
+					if (!ctx) {
+						ctx = findSsSubTabCtx(iframeWindow, 0);
+					}
+					var mode = "collapse";
+					try {
+						mode = (ctx && ctx.api && ctx.api.getMenuMode && ctx.api.getMenuMode()) || (ctx && ctx.leftSide && ctx.leftSide.getAttribute("data-mode")) || "collapse";
+					} catch (e) { }
+					currentBtnEl.classList.add("dialog-toolbar-icon");
+					//功能: icon-base 图标名调整:弹窗顶部菜单模式按钮 icon-autoTxt / icon-fix by xu 20251224
+					currentBtnEl.classList.remove("icon-autoTxt", "icon-fix");
+					if (mode === "fixed") {
+						currentBtnEl.classList.add("icon-fix");
+						currentBtnEl.setAttribute("title", "固定菜单");
+					} else {
+						currentBtnEl.classList.add("icon-autoTxt");
+						currentBtnEl.setAttribute("title", "悬浮菜单");
+					}
+				};
+
+				//功能: 用事件委托绑定点击(避免 dhtmlx 切换 class/替换节点导致点击失效)by xu 20251224
+				try {
+					var btnsEl = winEl.querySelector("div.dhtmlx_wins_btns");
+					if (btnsEl && btnsEl.getAttribute("data-ss-sub-tab-menu-bound") !== "1") {
+						btnsEl.setAttribute("data-ss-sub-tab-menu-bound", "1");
+						btnsEl.addEventListener("click", function (e) {
+							try {
+								var t = e && e.target;
+								var hit = t && t.closest ? t.closest("div[class*='dhtmlx_button_ssSubTabMenuMode_']") : null;
+								if (!hit) return;
+								//功能: 优先从 topWindow 的 apiMap 取(解决 objPlay->objInfo)by xu 20251224
+								var ctx = null;
+								var ssSubTabApi = null;
+								try {
+									ssSubTabApi = wd.topWindow && wd.topWindow.__ssSubTabApiMap && wd.topWindow.__ssSubTabApiMap[wdDialogId];
+								} catch (ee) { ssSubTabApi = null; }
+								if (ssSubTabApi && ssSubTabApi.toggleMenuMode) {
+									ctx = { api: ssSubTabApi };
+								} else {
+									ctx = findSsSubTabCtx(iframeWindow, 0);
+									ssSubTabApi = ctx && ctx.api;
+								}
+								if (!ssSubTabApi || !ssSubTabApi.toggleMenuMode) {
+									try {
+										console.log(
+											"[SsSubTabMenuMode] click but api not ready",
+											"href=", iframeWindow.location && iframeWindow.location.href,
+											"foundCtx=", !!ctx
+										);
+									} catch (ee) { }
+									return;
+								}
+								//功能: 打印调试信息(点击事件触发)by xu 20251224
+								try { console.log("[SsSubTabMenuMode] click, before=", ssSubTabApi.getMenuMode && ssSubTabApi.getMenuMode()); } catch (ee) { }
+								try { ssSubTabApi.toggleMenuMode(); } catch (ee) { console.log(ee); }
+								try { console.log("[SsSubTabMenuMode] click, after=", ssSubTabApi.getMenuMode && ssSubTabApi.getMenuMode()); } catch (ee) { }
+								setTimeout(updateIcon, 0);
+							} catch (ee) {
+								console.log(ee);
+							}
+						}, false);
+					}
+				} catch (e) { }
+
+				updateIcon();
+				try { btn && btn.show && btn.show(); } catch (e) { }
+				return true;
+			} catch (e) {
+				console.log(e);
+				return false;
+			}
+	};
+
 	/**
 	* 将url中的动态参数替换成指定的值
 	* 如果param为空,则参数的值在html元素中寻找,如果param的值不为空,则在param中获取