NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name 改善 JVS 开发体验
// @namespace https://github.com/11ze
// @match *://*/*
// @noframes
// @icon data:image/png;base64,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
// @grant GM_addStyle
// @license MIT
// @author 11ze
// @version 0.8.11
// @description 2026-08-30 修复表单设计组件名悬停消失——站点 hover 规则(.formitem:not(.clicked):hover 藏 .type-name)被 !important 覆盖,常显组件名悬停不丢
// ==/UserScript==
(function () {
'use strict';
// ==================== 常量配置区 ====================
/**
* @typedef {'逻辑设计' | '列表设计' | '表单设计' | '流程设计'} DesignType
* @typedef {'打开' | '保存'} LogAction
*/
const CONFIG = {
TIMER_INTERVAL: 200,
LOG_SAVE_DAYS: 365,
LOG_BAR: { top: 14, right: 310, popupGap: 6 },
ENV_LIST: [
{ ip: 'dev.', env: '开发站' },
{ ip: 'test.', env: '测试站' },
{ ip: 'gdae.', env: '正式站' },
],
};
const COLORS = {
component: {
data: '#FFD6E7', // 数据相关
logic: '#D6E4FF', // 逻辑相关
loop: '#D9F7D9', // 循环相关
warning: '#FEF0C7', // 警告
variable: '#EFDBFF', // 变量
},
design: {
逻辑设计: 'blue',
列表设计: 'orange',
表单设计: 'green',
流程设计: 'purple',
},
};
/**
* 组件类型上色配置:数组顺序即匹配优先级
*/
const COMPONENT_TYPE_COLORS = [
{
types: [
'数据模型',
'跳过数据权限',
'删除数据',
'新增数据',
'查询单条',
'查询所有',
'更新模型',
'统计条数',
],
color: COLORS.component.data,
},
{
types: ['逻辑引擎', '逻辑应用'],
color: COLORS.component.logic,
},
{
types: ['循环容器', '对数组对象进行遍历'],
color: COLORS.component.loop,
},
{
types: ['中止程序', '提示消息'],
color: COLORS.component.warning,
},
{
types: [
'对象变量',
'数组变量',
'对象数组变量',
'固定变量',
'对象结构',
'公式值',
'等变量',
'结构示例',
],
color: COLORS.component.variable,
},
];
const STORAGE_KEYS = {
LOGS: '__11ze_JVS_LOG_LOGS_',
APP_MODE_MAP: '__11ze_JVS_APP_MODE_MAP__',
APP_NAME_MAP: '__11ze_JVS_APP_NAME_MAP__',
HIGHLIGHT_APPS: '__11ze_HIGHLIGHT_APPS__',
STARRED_FILTER: '__11ze_JVS_STARRED_FILTER__',
APPCENTER_SIDEBAR: '__11ze_JVS_APPCENTER_SIDEBAR_COLLAPSED__',
};
// ==================== 状态管理 ====================
/**
* 全局状态对象(替代所有 window.xxx 状态变量)
*/
const STATE = {
tabDesignClicked: false, // 替代 window.secondTabDesignClicked11ze
skipCopyComponentButton: false, // 替代 window.currentPageNotAddCopyComponentNameButton
componentLibraryExpanded: false, // 替代 window.autoExpandComponentLibraryCategory11ze
};
/** 模式色板单一事实源:text 投影日志表格行色,buttonPlain 投影按钮整钮换色 CSS */
const MODE_COLORS = {
开发模式: { text: 'black' },
测试模式: {
text: 'green',
buttonPlain: { color: '#67C23A', borderColor: '#B3E19D', hoverBackgroundColor: '#F0F9EB', hoverBorderColor: '#67C23A' },
},
正式模式: {
text: 'red',
buttonPlain: { color: '#F56C6C', borderColor: '#FAB6B6', hoverBackgroundColor: '#FEF0F0', hoverBorderColor: '#F56C6C' },
},
};
/**
* 应用名称的 DOM 选择器列表
* 用于在页面中查找应用名称元素
*/
const APP_NAME_SELECTORS = [
// 应用左上角
'#app > div > div > div.jvs-layout > div.jvs-left > div > div.el-menu-scrollbar.el-scrollbar > div.el-scrollbar__wrap > div > div.app-item-info.app-item-info-hide > div > span',
// 应用左上角
'#app > div > div > div.jvs-layout > div.jvs-left > div > div.el-menu-scrollbar.el-scrollbar > div.el-scrollbar__wrap > div > div.app-item-info > div > span',
// 逻辑设计、列表设计、表单设计
'#app > div > div > div.design-header-box > div.header-left',
// 新版 JVS 列表设计、表单设计
'div.design-header-box > div.header-left',
];
// ==================== 环境检测 ====================
/**
* 检查是否包含 jvs-ui / edf-ui 的 link 标签
* @param {boolean} isLogFunction - 是否为日志功能调用
* @returns {boolean}
*/
function isJVS(isLogFunction) {
const links = document.getElementsByTagName('link');
for (const link of links) {
const matchList = ['jvs-ui', 'edf-ui'];
if (link.href && matchList.some((match) => link.href.includes(match))) {
let message = '%c「改善 JVS 开发体验」已检测到 JVS 环境';
if (isLogFunction) {
message += '(日志功能)';
}
console.log(message, 'color: #0099ff;');
return true;
}
}
return false;
}
if (!isJVS(false)) {
return;
}
// ==================== 本地存储工具 ====================
const jvsStorage = {
get: (key, defaultValue = null) => {
try {
const value = localStorage.getItem(key);
return value ? JSON.parse(value) : defaultValue;
} catch (error) {
console.error(`读取本地存储失败 [${key}]:`, error);
jvsStorage.remove(key);
return defaultValue;
}
},
set: (key, value) => {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(`写入本地存储失败 [${key}]:`, error);
}
},
remove: (key) => {
try {
localStorage.removeItem(key);
} catch (error) {
console.error(`删除本地存储失败 [${key}]:`, error);
}
},
};
// 清理废弃的存储键(切换应用功能已移除)
jvsStorage.remove('__11ze_JVS_APP_ID_LIST_URL_MAP__');
// ==================== 工具函数区 ====================
/**
* 工具函数集合
*/
const Utils = {
/**
* 批量设置元素样式
* @param {HTMLElement | null} el - 目标元素
* @param {Partial<CSSStyleDeclaration>} styles - 样式对象
*/
setStyles(el, styles) {
if (!el || !styles) return;
Object.assign(el.style, styles);
},
/**
* 检查元素是否已标记
* @param {HTMLElement} element - 目标元素
* @param {string} attrName - 属性名
* @param {string | null} [value=null] - 期望的属性值
* @returns {boolean}
*/
isMarked(element, attrName, value = null) {
if (!element) return false;
if (value === null) return element.hasAttribute(attrName);
return element.getAttribute(attrName) === value;
},
/**
* 标记元素
* @param {HTMLElement} element - 目标元素
* @param {string} attrName - 属性名
* @param {string} [value='true'] - 属性值
*/
mark(element, attrName, value = 'true') {
element?.setAttribute(attrName, value);
},
/**
* 复制文本到剪贴板
* @param {string} text - 要复制的文本
* @param {HTMLButtonElement | null} [button] - 触发按钮
* @param {string} [successMessage='已复制'] - 成功提示
* @returns {Promise<void>}
*/
copyToClipboard(text, button, successMessage = '已复制') {
const doCopy = () => {
if (navigator.clipboard?.writeText) {
return navigator.clipboard.writeText(text);
}
// 回退方法
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
return Promise.resolve();
};
return doCopy()
.then(() => {
if (button) {
const originalText = button.textContent;
button.textContent = successMessage;
button.disabled = true;
setTimeout(() => {
button.textContent = originalText;
button.disabled = false;
}, 500);
}
})
.catch((err) => {
console.error('复制失败:', err);
});
},
};
/**
* @typedef {Object} ButtonOptions
* @property {string} [text=''] - 按钮文本
* @property {string} [className=''] - 额外的类名
* @property {string} [id=''] - 元素 ID
* @property {Record<string, string>} [dataset={}] - data 属性
* @property {(event: MouseEvent) => void} [onClick] - 点击回调
* @property {'primary' | 'default' | 'success' | 'warning' | 'danger'} [type='primary'] - 按钮类型
* @property {'mini' | 'small' | 'medium'} [size='mini'] - 按钮尺寸
* @property {boolean} [isHtml=false] - text 是否为 HTML 字符串
*/
/**
* 使容器可通过把手拖拽移动
* @param {HTMLElement} handle - 拖拽把手元素
* @param {HTMLElement} container - 被拖拽的容器元素
* @param {{ minTop: number, maxRight: number, onMove?: (newTop: number, newRight: number) => void }} options - 拖拽配置(边界由调用方给定)
*/
function makeDraggable(handle, container, options = {}) {
const { minTop, maxRight, onMove } = options;
handle.addEventListener('mousedown', (e) => {
e.preventDefault();
e.stopPropagation();
const startX = e.clientX;
const startY = e.clientY;
const startTop = parseInt(container.style.top, 10) || minTop;
const startRight = parseInt(container.style.right, 10) || maxRight;
const containerHeight = container.offsetHeight;
const originalTransition = container.style.transition;
handle.style.cursor = 'move';
container.style.transition = 'none';
// 拖拽时禁用 iframe 防止 mouseup 事件丢失
const iframes = document.querySelectorAll('iframe');
iframes.forEach((iframe) => { iframe.style.pointerEvents = 'none'; });
let moved = false;
function onMouseMove(e) {
e.preventDefault();
moved = true;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const newRight = Math.min(Math.max(startRight - dx, 0), maxRight);
const newTop = Math.max(startTop + dy, minTop);
container.style.top = newTop + 'px';
container.style.right = newRight + 'px';
onMove?.(newTop, newRight, containerHeight);
}
function onMouseUp() {
document.removeEventListener('mousemove', onMouseMove, true);
document.removeEventListener('mouseup', onMouseUp, true);
iframes.forEach((iframe) => { iframe.style.pointerEvents = ''; });
handle.style.cursor = '';
container.style.transition = originalTransition;
if (moved) {
const preventClick = (e) => {
e.stopPropagation();
e.stopImmediatePropagation();
document.removeEventListener('click', preventClick, true);
};
document.addEventListener('click', preventClick, true);
}
}
document.addEventListener('mousemove', onMouseMove, true);
document.addEventListener('mouseup', onMouseUp, true);
});
}
/**
* 创建按钮元素
* @param {ButtonOptions} options - 按钮配置
* @returns {HTMLButtonElement}
*/
function createButton(options = {}) {
const {
text = '',
className = '',
id = '',
dataset = {},
onClick = null,
type = 'primary',
size = 'mini',
isHtml = false,
} = options;
const button = document.createElement('button');
if (isHtml) {
button.innerHTML = text;
} else {
button.textContent = text;
}
button.className =
`modern-button el-button el-button--${type} el-button--${size} button-11ze ${className}`.trim();
if (id) button.id = id;
Object.entries(dataset).forEach(([key, value]) => button.setAttribute(key, value));
if (onClick) button.addEventListener('click', onClick);
return button;
}
/**
* 幂等注入:在 host 内按 find 定位已注入元素,身份键相同则跳过,
* 键异则移除重建;省略 keyAttr 为恒等键(存在即命中)
* @param {Object} spec
* @param {Element | Document | null} spec.host - 宿主(find 在其内查找)
* @param {string} spec.find - 已注入元素的 selector(复用现有类名/id)
* @param {string} [spec.keyAttr] - 身份属性名(挂在已注入元素上)
* @param {string} [spec.key] - 期望身份值
* @param {() => (Element | null)} spec.mount - 造并挂载新元素;返回 null 拒绝注入
* (键异时旧元素已移除,保持「移除后可不建」语义)
* @returns {Element | null} 当前元素(host 缺失或 mount 拒绝时为 null)
*/
function ensureInjected({ host, find, keyAttr, key, mount }) {
if (!host) {
return null;
}
const existing = host.querySelector(find);
if (existing) {
if (!keyAttr || existing.getAttribute(keyAttr) === key) {
return existing;
}
existing.remove();
}
return mount() || null;
}
// ==================== 主逻辑 ====================
/**
* 创建轮询调度器,统一处理 operations 混合数组
* 普通函数每 tick 执行;{ name, probe, apply } 对象由 probe 返回的键控制:
* probe 返回 null/undefined 表示本 tick 不适用;键不变时跳过 apply。
* probe 也可返回 { key, payload } 对象——键对比走 key,payload 直达
* apply(payload)(probe 纯读,apply 消费 probe 采到的数据,不二次读取)
* @param {Array<Function | {name: string, probe: Function, apply: Function}>} operations
* @param {(name: string, error: unknown) => void} onError - 单个操作出错时的回调
* @returns {() => void} tick 函数
*/
function createOperationRunner(operations, onError) {
const lastKeys = new Map();
return function tick() {
for (const operation of operations) {
try {
if (typeof operation === 'function') {
operation();
continue;
}
const result = operation.probe();
if (result === null || result === undefined) continue;
const key = result.key ?? result;
if (lastKeys.get(operation) === key) continue;
lastKeys.set(operation, key);
operation.apply(result.payload);
} catch (error) {
onError(operation.name, error);
}
}
};
}
// 测试钩子:浏览器中 __JVS_TEST__ 不存在,此分支永不执行
if (window.__JVS_TEST__) {
window.__JVS_TEST__.hooks = {
createOperationRunner: createOperationRunner,
paintComponents: paintComponents,
cutOverdueLogs: cutOverdueLogs,
uniqueLogs: uniqueLogs,
enrichLogsWithAppName: enrichLogsWithAppName,
saveAppIdName: saveAppIdName,
getAppIdName: getAppIdName,
saveLog: saveLog,
getLogs: getLogs,
latestLogWhere: latestLogWhere,
getUrlFromLogs: getUrlFromLogs,
getUrlFromLogsAndUrl: getUrlFromLogsAndUrl,
findDesignNameById: findDesignNameById,
getTabType: getTabType,
getModeColor: getModeColor,
collectAppMode: collectAppMode,
getModeFromHistory: getModeFromHistory,
currentMode: currentMode,
buildModeColorCss: buildModeColorCss,
MODE_COLORS: MODE_COLORS,
highlightApps: highlightApps,
filterStarredApps: filterStarredApps,
toggleAppCenterSidebar: toggleAppCenterSidebar,
syncAppCenterUrl: syncAppCenterUrl,
getLogButtonName: getLogButtonName,
ensureInjected: ensureInjected,
addButtonToCopyDesignName: addButtonToCopyDesignName,
addButtonToCopyComponentName: addButtonToCopyComponentName,
addButtonToClearAllFields: addButtonToClearAllFields,
addButtonToOpenNewLogicDesign: addButtonToOpenNewLogicDesign,
addButtonToOpenNewFormOrListDesign: addButtonToOpenNewFormOrListDesign,
showNodeExecTime: showNodeExecTime,
_createCopyNameButton: _createCopyNameButton,
_createLogicNameDisplay: _createLogicNameDisplay,
_createOpenLogicButton: _createOpenLogicButton,
setCanvasScroll: setCanvasScroll,
get canvasScrollOperation() {
return canvasScrollOperation;
},
get updateLogButtonOperation() {
return updateLogButtonOperation;
},
getStyles: () => JVS_STYLES + buildModeColorCss(),
};
}
/**
* 构建待保存的日志条目,appName 过长或与设计名相同时回退为存储中的应用名
* @returns {LogEntry | null} 数据不完整时返回 null
*/
function buildLogForSave() {
const newLog = createLogEntry();
if (!newLog || !newLog.tabType) {
return null;
}
if (newLog.appName.length > 100 || newLog.appName === newLog.designName) {
newLog.appName = getAppIdName(newLog.jvsAppId);
}
return newLog;
}
/**
* 保存当前页面日志(designName 或 appName 变化时落库,由调度器的键对比控制)
* payload 携带 probe 构建好的日志条目,apply 直存不再二次构建
*/
const saveCurrentLogOperation = {
name: 'saveCurrentLog',
probe() {
const newLog = buildLogForSave();
// \u0000 不会出现在页面名称里,用作组合键分隔符
return newLog
? { key: newLog.designName + '\u0000' + newLog.appName, payload: newLog }
: null;
},
apply(newLog) {
if (newLog) {
saveLog(newLog, '打开');
}
},
};
/**
* 更新日志按钮(容器缺失或按钮模式与当前不符时重建,由调度器的键对比控制)
* payload 携带 probe 读到的模式,apply 不再重取
*/
const updateLogButtonOperation = {
name: 'updateLogButton',
probe() {
const mode = currentMode();
const existContainer = document.getElementById('ze-jvs-log-container');
if (!existContainer) {
return { key: 'missing', payload: mode };
}
const buttonMode = existContainer.querySelector('#ze-jvs-log-button')?.dataset.mode || '';
return buttonMode === mode
? { key: 'stable:' + mode, payload: mode }
: { key: 'stale:' + buttonMode + '->' + mode, payload: mode };
},
apply: updateLogButton,
};
/**
* 滚轮平移挂载(画布容器就绪且未挂时挂载,由调度器的键对比控制)
* 键:mount(需挂)/ restore(同一画布重建且销毁前有非零平移,挂载并带回平移)/
* mounted(已挂)——主/循环画布切换、连线等操作会整体重建容器与画布实例,
* 闩锁随 DOM 销毁,键回 mount/restore 触发重挂;切换画布身份变了,不重放旧画布平移
*/
const canvasScrollOperation = {
name: 'canvasScroll',
probe() {
const stage = getButterflyCanvas();
if (!stage) {
return null;
}
const identity = getCanvasIdentity();
if (stage.canvas !== canvasScrollState.seen) {
if (canvasScrollState.identity !== identity) {
// 切换画布:旧画布的平移记录作废
canvasScrollState.offset = null;
}
const pending = canvasScrollRestoreOf(stage.canvas) ? 'restore' : 'mount';
// 待挂键带递增代数:两次重建之间没有 tick 时(快速连续切换),
// 相同的裸 'mount' 会让调度器误判状态没变而永远跳过挂载(监听丢失死锁)
return pending + '@' + (++canvasScrollState.rebuildCount);
}
canvasScrollState.identity = identity;
canvasScrollState.offset = stage.canvas.getOffset();
return stage.container.getAttribute('data-11ze-canvas-scroll') ? 'mounted' : 'mount';
},
apply: setCanvasScroll,
};
const operations = [
// 设计器模块
changeTitle,
enterAppCenter,
syncAppCenterUrl,
enterTabDesign,
adjustInterfaceAndComponentStyle,
addButtonToOpenNewLogicDesign,
firstAddButtonToOpenNewLogicDesignForNestedLogic,
addButtonToCopyDesignName,
addButtonToCopyComponentName,
addButtonToClearAllFields,
addButtonToOpenNewFormOrListDesign,
highlightApps,
filterStarredApps,
toggleAppCenterSidebar,
expandFormDesignAllComponentSettings,
autoExpandComponentLibraryCategory,
applicationSetClick,
showNodeExecTime,
canvasScrollOperation,
// 日志模块
updateLogButtonOperation,
saveCurrentLogOperation,
bindSaveButton,
];
function reportOperationError(name, error) {
console.error('「改善 JVS 开发体验」' + name + ' 运行错误:');
console.error(error);
}
const jvsTimer = setInterval(
createOperationRunner(operations, reportOperationError),
CONFIG.TIMER_INTERVAL,
);
window.addEventListener('beforeunload', () => {
clearInterval(jvsTimer);
});
/**
* @typedef {Object} DesignConfig
* @property {string} shortname - 设计类型简称
* @property {string} color - 设计类型颜色
*/
/**
* @type {Record<DesignType, DesignConfig>}
*/
const DESIGN_CONFIG = {
逻辑设计: { shortname: '逻辑', color: COLORS.design.逻辑设计 },
列表设计: { shortname: '列表', color: COLORS.design.列表设计 },
表单设计: { shortname: '表单', color: COLORS.design.表单设计 },
流程设计: { shortname: '流程', color: COLORS.design.流程设计 },
};
// ==================== 日志模块 ====================
/**
* 创建日志条目
* @returns {LogEntry | null} 日志对象,如果数据不完整则返回 null
*/
function createLogEntry() {
const url = getUrl();
const id = getQueryParamMapping(url)['id'];
const jvsAppId = getJvsAppId();
const tabType = getTabType();
const designName = getNewTabTitle();
const appName = collectAppName();
if (!designName || !appName || !id || !jvsAppId || !tabType) {
return null;
}
return { tabType, url, time: Date.now(), designName, appName, id, jvsAppId };
}
/**
* 采集应用名称(用于日志记录):DOM 抓取,命中干净的单行名时
* 顺手落库 saveAppIdName(写一次语义,应用改名不回写)
* @returns {string} 应用名称
*/
function collectAppName() {
for (const selector of APP_NAME_SELECTORS) {
const elements = document.querySelectorAll(selector);
for (const el of elements) {
const text = el.textContent;
if (text) {
const textArray = text
.trim()
.split('\n')
.map((s) => s.trim())
.filter(Boolean);
if (textArray.length === 1) {
const appName = textArray[0].split(' ')[0].trim();
saveAppIdName(getJvsAppId(), appName);
return appName;
}
if (document.querySelector('.list-item')) {
return textArray[textArray.length - 3] || textArray[0];
}
return textArray[0];
}
}
}
return '';
}
/**
* 剪切过期日志
*/
function cutOverdueLogs(logs, currentTime) {
for (let i = logs.length - 1; i >= 0; i--) {
const log = logs[i];
if (!log.time) {
logs.splice(i, 1);
continue;
}
if (Math.abs(currentTime - log.time) > CONFIG.LOG_SAVE_DAYS * 24 * 60 * 60 * 1000) {
logs.splice(i, 1);
}
}
return uniqueLogs(logs);
}
/**
* 获取日志列表(纯读:剪切过期与重复条目,不改动 appName)
*/
function getLogs() {
const logs = jvsStorage.get(STORAGE_KEYS.LOGS, []);
if (!logs) return [];
return cutOverdueLogs(logs, Date.now());
}
/**
* 用应用目录补全日志的 appName(仅用于展示,不回写存储)
*/
function enrichLogsWithAppName(logs) {
logs.forEach((log) => {
const appName = getAppIdName(log.jvsAppId);
if (appName) log.appName = appName;
});
return logs;
}
/**
* 保存日志
*/
function saveLog(logObj, type) {
if (!logObj) return;
logObj.type = type;
const logList = getLogs();
logList.push(logObj);
jvsStorage.set(STORAGE_KEYS.LOGS, logList);
}
/**
* 日志去重
*/
function uniqueLogs(logs) {
const seen = new Set();
const result = [];
for (let i = logs.length - 1; i >= 0; i--) {
const log = logs[i];
const key = getQueryParamMapping(log.url)['id'] + log.type;
if (!seen.has(key)) {
seen.add(key);
result.push(log);
}
}
return result.reverse();
}
/**
* 格式化时间
*/
function formatTime(time) {
const date = new Date(time);
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${month}-${day} ${hours}:${minutes}:${seconds}`;
}
/**
* 显示日志弹窗
*/
function showLogPopup() {
const popupId = '11ze-jvs-log-popup';
const oldPopup = document.getElementById(popupId);
if (oldPopup) {
oldPopup.remove();
return;
}
const logs = enrichLogsWithAppName(getLogs());
const appModeMap = getAppModelMap();
const hasMode = Object.keys(appModeMap).length > 0;
const listContent = [];
for (let i = logs.length - 1; i >= 0; i--) {
const oneLog = logs[i];
if (oneLog.type === '保存') {
continue;
}
const datetime = formatTime(oneLog.time);
const currentType = DESIGN_CONFIG[oneLog.tabType] ?? { color: 'red', shortname: '未知' };
let appName = oneLog.appName;
if (appName.length > 16) appName = appName.substring(0, 16) + '…';
const mode = appModeMap[oneLog.jvsAppId] ?? '';
const modeColor = getModeColor(mode);
listContent.push(`
<tr class="log-11ze-table-tr">
<td> ${datetime} </td>
${hasMode ? `<td style="color: ${modeColor}"> ${mode.replace('模式', '')} </td>` : ''}
<td> ${appName} </td>
<td style="color: ${currentType.color}"> ${currentType.shortname} </td>
<td class="log-design-name-11ze" data-url="${oneLog.url}" style="color: #1f6fff;"> ${oneLog.designName} </td>
</tr>
`);
}
const popup = document.createElement('div');
popup.className = 'popup-11ze';
popup.id = popupId;
// 弹窗位置以按钮右下角为准
const btnContainer = document.getElementById('ze-jvs-log-container');
if (btnContainer) {
const containerTop = parseInt(btnContainer.style.top, 10) || CONFIG.LOG_BAR.top;
popup.style.top = (containerTop + btnContainer.offsetHeight + CONFIG.LOG_BAR.popupGap) + 'px';
popup.style.right = btnContainer.style.right || CONFIG.LOG_BAR.right + 'px';
}
const logTable = document.createElement('table');
logTable.className = 'table-11ze';
logTable.style.marginTop = '10px';
logTable.innerHTML = `
<thead>
<tr style="background-color: #eef5fe" class="log-11ze-table-tr">
<th> 时间 </th>
${hasMode ? `<th> 模式 </th>` : ''}
<th> 应用 </th>
<th style="text-align: center"> 类型 </th>
<th> 名称 </th>
</tr>
</thead>
<tbody>${listContent.join('')}</tbody>
`;
popup.appendChild(logTable);
document.body.appendChild(popup);
// 事件委托
logTable.addEventListener('click', (e) => {
const nameTd = e.target.closest('.log-design-name-11ze');
if (nameTd) window.open(nameTd.dataset.url, '_blank');
});
logTable.addEventListener('auxclick', (e) => {
if (e.button !== 1) return;
const nameTd = e.target.closest('.log-design-name-11ze');
if (nameTd) window.open(nameTd.dataset.url, '_blank');
});
document.addEventListener('click', function closePopupOnOutsideClick(event) {
if (popup && !popup.contains(event.target) && !event.target.closest('#ze-jvs-log-container')) {
popup.remove();
document.removeEventListener('click', closePopupOnOutsideClick);
}
});
}
/**
* 日志按钮文案:模式前缀与「日志」同字号同色(整钮颜色由
* JVS_STYLES 按 data-mode 换色),间距替代全角「|」
* @param {string} mode 当前模式名,空串表示未识别到模式
*/
function getLogButtonName(mode) {
return mode
? `<span style="margin-right: 6px">${mode}</span>日志`
: '日志';
}
// 拖拽把手十字箭头,stroke 走 currentColor 跟随按钮字色
const MOVE_SVG =
'<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 9 2 12 5 15"/><polyline points="9 5 12 2 15 5"/><polyline points="15 19 12 22 9 19"/><polyline points="19 9 22 12 19 15"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="12" y1="2" x2="12" y2="22"/></svg>';
function updateLogButton(mode) {
const existContainer = document.getElementById('ze-jvs-log-container');
if (existContainer) {
const existButton = existContainer.querySelector('#ze-jvs-log-button');
const buttonMode = existButton?.dataset.mode || '';
if (buttonMode === mode) return;
existContainer.remove();
}
const buttonName = getLogButtonName(mode);
const button = createButton({
text: buttonName,
id: 'ze-jvs-log-button',
isHtml: true,
dataset: { 'data-mode': mode || '' },
});
Utils.setStyles(button, {
fontSize: '14px',
});
button.addEventListener('click', (event) => {
event.stopPropagation();
showLogPopup();
});
const handle = document.createElement('div');
handle.className = 'drag-handle-11ze';
handle.innerHTML = MOVE_SVG;
const container = document.createElement('div');
container.id = 'ze-jvs-log-container';
Utils.setStyles(container, {
position: 'fixed',
top: CONFIG.LOG_BAR.top + 'px',
right: CONFIG.LOG_BAR.right + 'px',
zIndex: '9998',
display: 'flex',
alignItems: 'stretch',
transition: 'transform 0.2s ease',
});
container.appendChild(button);
container.appendChild(handle);
makeDraggable(handle, container, {
minTop: CONFIG.LOG_BAR.top,
maxRight: CONFIG.LOG_BAR.right,
onMove(newTop, newRight, containerHeight) {
const popup = document.getElementById('11ze-jvs-log-popup');
if (popup) {
popup.style.top = (newTop + containerHeight + CONFIG.LOG_BAR.popupGap) + 'px';
popup.style.right = newRight + 'px';
}
},
});
document.body.appendChild(container);
}
/**
* 绑定保存按钮点击事件
*/
function bindSaveButton() {
const attrName = 'log-save-bound-11ze';
const saveButton = document.querySelector(
'#app > div > div > div.design-header-box > div.header-right > button',
);
if (!saveButton || Utils.isMarked(saveButton, attrName)) return;
Utils.mark(saveButton, attrName);
saveButton.addEventListener('click', () => {
const newLog = createLogEntry();
if (newLog) saveLog(newLog, '保存');
});
}
function getQueryParamMapping(url) {
if (typeof url !== 'string' || !url) {
return {};
}
// 先根据 # 分割,再平铺成基于 ? 分割的一维数组
const urlParts = url.split('#');
const urlQuery = urlParts.map((part) => part.split('?')[1]).join('&');
if (!urlQuery) {
return {};
}
const params = urlQuery.split('&');
const mapping = {};
for (let i = 0; i < params.length; i++) {
const param = params[i];
const id = param.split('=')[0];
const value = param.split('=')[1];
mapping[id] = value;
}
return mapping;
}
function getUrl() {
return location.href;
}
function getJvsAppId() {
const urlParams = getQueryParamMapping(getUrl());
return urlParams['jvsAppId'];
}
/**
* 获取当前设计器类型
*
* 旧版 JVS 的非设计页面上 #tab-design 页签会残留(文本「逻辑设计」),
* 必须同时确认设计器头部存在、且文本是已知设计类型,否则视为非设计页面
* @returns {DesignType | ''}
*/
function getTabType() {
if (!document.querySelector('.design-header-box')) {
return '';
}
const typeDom = document.querySelector('#tab-design > span');
if (!typeDom) {
return '';
}
const tabType = typeDom.textContent.trim();
return DESIGN_CONFIG[tabType] ? tabType : '';
}
function getAppModelMap() {
return jvsStorage.get(STORAGE_KEYS.APP_MODE_MAP, {});
}
/**
* 采集当前模式:读 DOM 模式项,值变才写入目录(显式的写入口)
* @returns {string} 模式名,无 .system-list 时空串
*/
function collectAppMode() {
const systemList = document.querySelector('.system-list');
if (!systemList) {
return '';
}
const systemListItems = systemList.querySelectorAll('li');
for (const systemListItem of systemListItems) {
if (systemListItem.innerText.includes('模式')) {
const mode = systemListItem.innerText.trim();
const jvsAppId = getJvsAppId();
if (jvsAppId) {
const appModeMap = getAppModelMap();
if (appModeMap[jvsAppId] !== mode) {
appModeMap[jvsAppId] = mode;
jvsStorage.set(STORAGE_KEYS.APP_MODE_MAP, appModeMap);
}
}
return mode;
}
}
return '';
}
function getModeFromHistory() {
const urlParams = getQueryParamMapping(getUrl());
if (!urlParams) {
return '';
}
const jvsAppId = urlParams['jvsAppId'];
if (!jvsAppId) {
return '';
}
const appModeMap = getAppModelMap();
return appModeMap[jvsAppId];
}
/** 当前模式唯一入口:历史目录优先,未命中走 DOM 采集 */
function currentMode() {
return getModeFromHistory() || collectAppMode();
}
function getModeColor(mode) {
return MODE_COLORS[mode]?.text ?? 'black';
}
/** 按钮整钮随 data-mode 换色的规则,色值全部取自 MODE_COLORS.buttonPlain */
function buildModeColorCss() {
return Object.entries(MODE_COLORS)
.filter(([, palette]) => palette.buttonPlain)
.map(([mode, { buttonPlain }]) => `
.button-11ze[data-mode="${mode}"] {
color: ${buttonPlain.color} !important;
border-color: ${buttonPlain.borderColor} !important;
}
.button-11ze[data-mode="${mode}"]:hover {
background-color: ${buttonPlain.hoverBackgroundColor} !important;
border-color: ${buttonPlain.hoverBorderColor} !important;
}
`)
.join('');
}
/**
* 获取应用名称或设计名称,用于设置浏览器标签页 title
* @returns {string}
*/
function getNewTabTitle() {
// 逻辑设计的设计名称 selector,取值函数覆盖不到,单独查询
const title = document.querySelector(
'#app > div > div > div.design-header-box > div.header-left > span:nth-child(3)',
);
if (title) {
return title.textContent.trim();
}
for (let i = 1; i < APP_NAME_SELECTORS.length; i++) {
const allTextElements = document.querySelectorAll(APP_NAME_SELECTORS[i]);
for (let j = 0; j < allTextElements.length; j++) {
const text = allTextElements[j].innerHTML;
if (!text.includes('<')) {
return text.trim();
}
if (i === 3) {
let splitText = text.split('</svg>')[1];
if (!splitText) {
continue;
}
splitText = splitText.split('center;">')[1];
if (!splitText) {
continue;
}
splitText = splitText.split('<')[0];
return splitText.trim();
}
const splitText = text.split('</svg>')[1];
if (!splitText) {
continue;
}
if (splitText.includes('<')) {
return splitText.split('<')[0].trim();
}
return splitText.trim();
}
}
if (location.href.includes('doc.html')) {
return '接口文档';
}
return '';
}
/**
* 修改浏览器标签页标题
*/
function changeTitle() {
function getEnvironment() {
const url = location.href;
for (let i = 0; i < CONFIG.ENV_LIST.length; i++) {
if (url.includes(CONFIG.ENV_LIST[i].ip)) {
return CONFIG.ENV_LIST[i].env;
}
}
return '';
}
const newTabTitle = getNewTabTitle();
const tabType = getTabType();
function changeFavicon(iconURL) {
const links = document.querySelectorAll("link[rel*='icon']");
links.forEach(function (link) {
link.href = iconURL;
});
}
if (tabType) {
document.title = newTabTitle;
changeFavicon(ICONS[tabType]);
} else {
let prefix = collectAppMode();
if (!prefix) {
prefix = getEnvironment();
}
document.title = prefix + '|' + (newTabTitle ? newTabTitle : '未打开应用');
}
}
/** 平台首页路由:enterAppCenter 靠它判定首页,syncAppCenterUrl 靠它回写地址栏 */
const HOME_ROUTE = '#/wel/index';
/**
* 首次进入平台首页时自动进入应用中心
*/
function enterAppCenter() {
const selector =
'#app > div > div > div.jvs-tags > div > div > div.top-nav > ul > li:nth-child(2) > span';
const element = document.querySelector(selector);
const url = location.href;
if (element) {
if (
element.innerText === '应用中心' &&
url.includes(HOME_ROUTE) &&
!element.hasAttribute('app-center-clicked-11ze')
) {
element.click();
element.setAttribute('app-center-clicked-11ze', 'true');
}
}
}
/**
* 新版应用中心不写 hash,地址栏停留在进站前路由;
* 显示应用中心时统一替换成首页路由,刷新后 enterAppCenter 自动点击才能衔接上。
* replaceState 不触发 hashchange,页面不动;旧版容器不同,天然不处理
*/
function syncAppCenterUrl() {
if (!location.hash.includes(HOME_ROUTE) && document.querySelector('.app-page')) {
history.replaceState(null, '', HOME_ROUTE);
}
}
/**
* 在设计页面自动点击 tab,如【表单设计】
*/
function enterTabDesign() {
const selector = '#tab-design';
const element = document.querySelector(selector);
if (!element) {
return;
}
// 双闩锁是序列两击计数器,缺一不可:属性闩锁 = 第一击(元素级,SPA 换页随 DOM 销毁),
// STATE.tabDesignClicked = 第二击(会话级)——「会话内首个设计页点两次、后续设计页点一次」是有意行为
if (element.getAttribute('second-tab-design-clicked-11ze')) {
if (STATE.tabDesignClicked) {
return;
}
STATE.tabDesignClicked = true;
element.click();
return;
}
element.click();
element.setAttribute('second-tab-design-clicked-11ze', 'true');
}
/**
* 调整界面、组件样式
*/
function adjustInterfaceAndComponentStyle() {
// 旧版 JVS,逻辑设计,所有在用可拖拽组件,改颜色
paintComponents('div.jvs-rule-node.ef-node-container.jtk-droppable', 'innerText');
// 新版 JVS,逻辑设计,所有在用可拖拽组件,改颜色
paintComponents('.jvs-rule-node.ef-node-container', 'textContent');
// 设置侧边栏可选组件的颜色
paintComponents('.getItem', 'innerText');
// 新版 JVS,表单设计,组件的名称全部显示出来
const formComponents = document.querySelectorAll('.formitem2');
for (const component of formComponents) {
const parent = component.parentElement;
if (!parent.getAttribute('draggable')) {
continue;
}
component.classList.add('active-formitem2');
}
}
/**
* 给选择器命中的组件按类型上色(背景 + 边框)
* @param {string} selector - 组件选择器
* @param {'innerText' | 'textContent'} textProperty - 参与匹配的文本属性
*/
function paintComponents(selector, textProperty) {
const components = document.querySelectorAll(selector);
for (const component of components) {
const text = component[textProperty].trim();
for (const typeToColor of COMPONENT_TYPE_COLORS) {
if (typeToColor.types.some((t) => text.includes(t))) {
component.style.backgroundColor = typeToColor.color;
component.style.borderColor = typeToColor.color;
component.classList.add('ze-typed');
component.style.setProperty('--ze-type-color', typeToColor.color);
break;
}
}
}
}
/**
* 倒序取首条命中 matcher 的日志(同 id 可能因 type 不同多条并存,倒序即最新优先)
* @param {(log: object) => boolean} matcher
* @returns {object | null}
*/
function latestLogWhere(matcher) {
const logs = getLogs();
for (let i = logs.length - 1; i >= 0; i--) {
if (matcher(logs[i])) {
return logs[i];
}
}
return null;
}
/**
* 从日志或 url 生成跳转链接
* @param {string} id - 设计 id
* @param {boolean} isFromUrl - 是否是从 url 中获取
* @returns {string | null}
*/
function getUrlFromLogs(id, isFromUrl) {
if (!id) {
return null;
}
const log = latestLogWhere((item) => item.id === id);
if (log) {
return log.url;
}
if (!isFromUrl) {
return null;
}
// http://xxx?id=xxx&xxx
const url = location.href;
return url.replace(/id=([^&]*)/, `id=${id}`);
}
/**
* 从日志和 url 生成跳转链接
* @param {string} logicName - 设计名称
* @param {string} jvsAppId - 应用 id
* @returns {string | null}
*/
function getUrlFromLogsAndUrl(logicName, jvsAppId) {
if (!logicName) {
return null;
}
const log = latestLogWhere(
(item) => item.jvsAppId === jvsAppId && item.designName === logicName
);
return log ? log.url : null;
}
/**
* 按设计 id 反查设计名(查看逻辑按钮的展示与复制名用)
* @param {string} id - 设计 id
* @returns {string | null}
*/
function findDesignNameById(id) {
const log = latestLogWhere((item) => item.id === id);
return log ? log.designName : null;
}
/**
* 逻辑设计,检查到【逻辑调用】组件时,自动添加一个按钮用于查看对应的逻辑设计
*/
function addButtonToOpenNewLogicDesign() {
const labels = document.querySelectorAll('.el-form-item__label');
for (const label of labels) {
if (!label.innerText.includes('逻辑引擎远程调用')) {
continue;
}
const logicKey = label.nextElementSibling.querySelector('.el-input__inner').title;
const newUrl = getUrlFromLogs(logicKey, true);
if (!newUrl) {
_removeLookLogicButton(label);
_createCopyNameButton(label, null);
continue;
}
// 按日志反查逻辑名(展示与复制名用),键用逻辑 id
const logicName = findDesignNameById(logicKey) ?? '';
_createOpenLogicButton(label, logicKey, logicName, () => window.open(newUrl, '_blank'));
}
}
// 获取选中的逻辑名称
function _getSelectedLogicName() {
const logicNameElements = document.querySelectorAll(
'.el-scrollbar__view.el-select-dropdown__list > .el-select-dropdown__item.selected > span',
);
let logicName = null;
for (const nameElement of logicNameElements) {
const text = nameElement.innerText.trim();
if (['string', '公式'].includes(text)) {
continue;
}
if (!logicName || text.length > logicName.length) {
logicName = text;
}
}
return logicName;
}
/**
* 创建查看按钮(工厂函数)
* @param {Element} target - 宿主 label
* @param {string} key - 按钮身份键(target-key,可为逻辑 id 或逻辑名)
* @param {string} name - 展示与复制名跟随的逻辑名
* @param {Function} onClick
*/
function _createOpenLogicButton(target, key, name, onClick) {
_createLogicNameDisplay(target, name);
const button = ensureInjected({
host: target,
find: '.ze-look-logic-button',
keyAttr: 'target-key',
key,
mount() {
const newButton = createButton({
text: '查看',
className: 'ze-look-logic-button',
dataset: { 'target-key': key },
onClick,
});
newButton.style.marginLeft = '10px';
target.appendChild(newButton);
return newButton;
},
});
_createCopyNameButton(target, name);
return button;
}
// 移除查看逻辑按钮
function _removeLookLogicButton(target) {
const buttonClass = 'ze-look-logic-button';
const existedButton = target.querySelector('.' + buttonClass);
if (existedButton) {
existedButton.remove();
}
_createLogicNameDisplay(target, null);
}
// 检查 logicName 是否变化,如变化则移除旧按钮并返回是否需要继续处理
function _updateButtonsIfNeeded(label) {
const existedButton = label.querySelector('.ze-look-logic-button');
const existedLogicName = existedButton?.getAttribute('target-key') || null;
const logicName = _getSelectedLogicName();
// 如果 logicName 没变化,跳过处理
if (logicName === existedLogicName) {
return null;
}
// 没有逻辑名称或发生变化时,移除旧按钮
_removeLookLogicButton(label);
_createCopyNameButton(label, null);
return logicName;
}
// 创建逻辑名称展示(只读,工厂函数)
function _createLogicNameDisplay(target, logicName) {
const displayClass = 'ze-logic-name-display';
return ensureInjected({
host: target,
find: '.' + displayClass,
keyAttr: 'logic-name-11ze',
key: logicName,
mount() {
if (!logicName) return null;
const span = document.createElement('span');
span.className = displayClass;
span.textContent = logicName;
span.setAttribute('logic-name-11ze', logicName);
target.appendChild(span);
return span;
},
});
}
// 创建复制按钮(工厂函数)
function _createCopyNameButton(target, logicName) {
const buttonClass = 'ze-copy-logic-name-button';
return ensureInjected({
host: target,
find: '.' + buttonClass,
keyAttr: 'target-logic-name',
key: logicName,
mount() {
if (!logicName) return null;
const copyButton = createButton({
text: '复制',
className: buttonClass,
dataset: { 'target-logic-name': logicName },
type: 'default',
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
Utils.copyToClipboard(logicName, copyButton, '已复制');
},
});
copyButton.style.marginLeft = '10px';
target.appendChild(copyButton);
return copyButton;
},
});
}
/**
* 新版逻辑嵌套组件,检查到【逻辑嵌套】组件时,自动添加一个按钮用于查看对应的逻辑设计
* 从已打开过的逻辑设计中获取跳转链接
*/
function firstAddButtonToOpenNewLogicDesignForNestedLogic() {
const labels = document.querySelectorAll('.el-form-item__label');
for (const label of labels) {
if (!label.innerText.includes('选择逻辑引擎')) {
continue;
}
const logicName = _updateButtonsIfNeeded(label);
if (!logicName) {
continue;
}
const jvsAppId = getJvsAppId();
const newUrl = getUrlFromLogsAndUrl(logicName, jvsAppId);
if (!newUrl) {
secondAddButtonToOpenNewLogicDesignForNestedLogic();
return;
}
_createOpenLogicButton(label, logicName, logicName, () => window.open(newUrl, '_blank'));
return;
}
}
/**
* 新版逻辑嵌套组件,检查到【逻辑嵌套】组件时,自动添加一个按钮用于查看对应的逻辑设计
* 从左上角的 icon 中获取跳转页面
* 谨慎使用,点到引用按钮会覆盖当前逻辑设计,自动保存,不可逆
*/
function secondAddButtonToOpenNewLogicDesignForNestedLogic() {
const labels = document.querySelectorAll('.el-form-item__label');
const otherRuleListIcon = document.querySelector('.rule-list-icon');
if (!otherRuleListIcon) {
return;
}
if (!otherRuleListIcon.getAttribute('check-logic-design-rule-list-icon-11ze')) {
// 点击后才有逻辑列表
otherRuleListIcon.click();
otherRuleListIcon.click();
}
otherRuleListIcon.setAttribute('check-logic-design-rule-list-icon-11ze', 'true');
const otherRuleList = document.querySelectorAll('.other-rule-list > .list-box > .list-item');
for (const label of labels) {
if (!label.innerText.includes('选择逻辑引擎')) {
continue;
}
const logicName = _updateButtonsIfNeeded(label);
if (!logicName) {
continue;
}
for (const otherRule of otherRuleList) {
const otherRuleText = otherRule?.textContent?.trim();
if (!otherRuleText) {
continue;
}
if (otherRuleText === logicName) {
_createOpenLogicButton(label, logicName, logicName, () => {
// 第一个按钮是「设计」,第二个「引用」
// 点击引用会引用设计覆盖当前逻辑设计,自动保存,不可逆
const desginButtons = otherRule
.querySelector('.list-item-tool')
.querySelectorAll('div.el-tooltip');
if (desginButtons.length === 2) {
desginButtons[0].click();
}
});
return;
}
}
}
}
// 新版 JVS,在逻辑设计名称旁边添加复制按钮
function addButtonToCopyDesignName() {
// 逻辑设计
let designName = document.querySelector(
'#app > div > div > div.design-header-box > div.header-left > span',
);
if (!designName) {
// 表单设计
designName = document.querySelector(
'#app > div > div > div:nth-child(1) > div.design-header-box > div.header-left > span',
);
}
if (designName) {
const designNameText = designName.innerText.trim();
ensureInjected({
host: document,
find: '#copy-design-name-button-11ze',
keyAttr: 'design-name-11ze',
key: designNameText,
mount() {
// 名称旁无 use 图标(编辑态图标)时不注入;键异时旧按钮已在上文移除
if (!designName.querySelector('use')) {
return null;
}
const copyButton = createButton({
text: '复制',
id: 'copy-design-name-button-11ze',
dataset: { 'design-name-11ze': designNameText },
onClick: () => Utils.copyToClipboard(designNameText, copyButton, '已复制'),
});
copyButton.style.marginLeft = '10px';
designName.parentNode.insertBefore(copyButton, designName.nextSibling);
return copyButton;
},
});
}
}
/**
* 新版 JVS,添加按钮复制组件名称
*/
function addButtonToCopyComponentName() {
const buttonClass = 'ze-copy-component-name-button';
const componentName = document.querySelector('#node_detailpannel > h4 > div > span');
if (!componentName) {
return;
}
// 不在旧版加按钮,如果父级有子元素 el-icon-document-copy,则 return
if (componentName.parentNode.querySelector('.el-icon-document-copy')) {
STATE.skipCopyComponentButton = true;
return;
}
// 单向闩锁有意不复位:旧版站点图标消失后也不再注入(会话内判定一次即定)
if (STATE.skipCopyComponentButton) {
return;
}
const componentNameText = componentName.innerText.trim();
ensureInjected({
host: document,
find: '#copy-component-name-button-11ze',
keyAttr: 'component-name-11ze',
key: componentNameText,
mount() {
const copyButton = createButton({
text: '复制',
id: 'copy-component-name-button-11ze',
className: buttonClass,
dataset: { 'component-name-11ze': componentNameText },
onClick: () => Utils.copyToClipboard(componentNameText, copyButton, '已复制'),
});
copyButton.style.marginLeft = '10px';
componentName.parentNode.insertBefore(copyButton, componentName.nextSibling);
return copyButton;
},
});
}
/**
* 逻辑设计,添加按钮一键清空所有字段
*/
function addButtonToClearAllFields() {
const boxes = document.querySelectorAll('.data-model-box');
for (let i = 0; i < boxes.length; i++) {
const box = boxes[i];
ensureInjected({
host: box,
find: '#clear-all-fields-button-11ze' + i,
mount() {
const button = createButton({
text: '清空',
id: 'clear-all-fields-button-11ze' + i,
onClick: () => {
const ps = box.querySelectorAll('p');
for (let i = ps.length - 1; i >= 0; i--) {
const el = ps[i];
if (el.querySelector('.delete-icon-button')) {
el.querySelector('.delete-icon-button > span').click();
}
if (el.querySelector('.el-icon-delete')) {
el.querySelector('.el-icon-delete').click();
}
}
},
});
box.insertBefore(button, box.firstChild);
return button;
},
});
}
}
/**
* 在列表表单列表的 id 旁边添加查看按钮
*/
function addButtonToOpenNewFormOrListDesign() {
// 列表设计页面也有元素,得排除
const tabType = getTabType();
if (tabType === '列表设计') {
return;
}
const selector =
'div.table-body-box > div > div.el-table__body-wrapper.is-scrolling-none > table > tbody > tr > td:nth-child(2) > div > span > span > div';
const elements = document.querySelectorAll(selector);
for (const element of elements) {
const designId = element.innerText.trim();
if (!designId) {
continue;
}
element.style.whiteSpace = 'normal';
// 行级标记是性能闩锁:未标记的行才扫日志,避免每 tick 全表重查
if (Utils.isMarked(element, 'form-added-button-11ze')) {
continue;
}
const targetUrl = getUrlFromLogs(designId, false);
if (!targetUrl) {
continue;
}
const targetElement =
element.parentElement.parentElement.parentElement.parentElement.parentElement.querySelector(
'td:nth-child(5) > div > div',
);
const mounted = ensureInjected({
host: targetElement,
find: '#open-new-form-or-list-design-button-11ze',
mount() {
const copyButton = createButton({
text: '查看',
id: 'open-new-form-or-list-design-button-11ze',
onClick: () => window.open(targetUrl, '_blank'),
});
targetElement.appendChild(copyButton);
return copyButton;
},
});
if (mounted) {
Utils.mark(element, 'form-added-button-11ze');
}
}
}
/**
* 自动展开表单设计所有组件设置
*/
function expandFormDesignAllComponentSettings() {
// 表单设计
const tabType = getTabType();
if (tabType !== '表单设计') {
return;
}
// 文字标题元素跟展开内容元素同级
const buttons = document.querySelectorAll('.el-collapse-item > .el-collapse-item__wrap');
for (const button of buttons) {
if (Utils.isMarked(button, 'bottom-body-checked-11ze')) {
continue;
}
const text = button.parentElement.innerText.trim();
const targetNames = ['设置', '扩展', '功能', '校验'];
for (const targetName of targetNames) {
if (text.includes(targetName)) {
button.style.display = 'block';
break;
}
}
Utils.mark(button, 'bottom-body-checked-11ze');
}
}
/**
* 高亮应用中心的应用
*/
function highlightApps() {
const starClass = 'ze-star-btn';
const markedClass = 'ze-marked';
const STAR_SVG =
'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>';
function getContentSelector() {
return 'div > div > div > p';
}
// 每次现读存储:点击闭包不持有列表快照,多标签页同开不会互相覆盖
function getMarkedApps() {
return jvsStorage.get(STORAGE_KEYS.HIGHLIGHT_APPS, []);
}
function handle(nodes) {
const markedApps = getMarkedApps();
// 如果在标记列表中,就点亮星标
nodes.forEach((n) => {
const star = n.querySelector(`.${starClass}`);
if (!star) {
return;
}
star.classList.toggle(markedClass, markedApps.includes(getNodeText(n)));
});
}
function getNodeText(node) {
return node.querySelector(getContentSelector()).innerText.trim();
}
function handleClickNode(node) {
const text = getNodeText(node);
const markedApps = getMarkedApps();
if (markedApps.includes(text)) {
markedApps.splice(markedApps.indexOf(text), 1);
} else {
markedApps.push(text);
}
jvsStorage.set(STORAGE_KEYS.HIGHLIGHT_APPS, markedApps);
}
function main() {
const containerSelector = '.application';
// 找到相关数据项,不存在就结束
const nodes = [...document.querySelectorAll(containerSelector)];
if (!nodes.length) return;
// 注入星标按钮:平时隐藏,悬停卡片浮现,点击切换标记
nodes.forEach((n) => {
ensureInjected({
host: n,
find: '.' + starClass,
mount() {
const star = document.createElement('button');
star.className = starClass;
star.title = '星标应用';
star.innerHTML = STAR_SVG;
star.addEventListener('click', (event) => {
event.stopPropagation();
handleClickNode(n);
// 立即刷新当前卡片,不等自动轮询
handle([n]);
});
n.appendChild(star);
return star;
},
});
});
// 渲染
handle(nodes);
}
main();
}
/**
* 应用中心「只看星标」过滤开关
* JS 只产出 body class 这一个事实,卡片/空组隐藏与空态提示全由 CSS :has() 驱动;
* 状态现读存储(多标签页同开自动一致),离开应用中心时清理 body class。
* 新版应用中心容器是 .app-page,旧版是 .jvs-layout-tempOpen 下的 .template-content-box,
* 按钮优先注入旧版筛选行(.filter-bar)搜索框右侧,无筛选行才钉容器顶部
*/
function filterStarredApps() {
const btnClass = 'ze-star-filter-btn';
const bodyClass = 'ze-star-filter-on';
function isFilterOn() {
return jvsStorage.get(STORAGE_KEYS.STARRED_FILTER, false);
}
function syncFilterState(btn = document.querySelector('.' + btnClass)) {
const on = isFilterOn();
document.body.classList.toggle(bodyClass, on);
if (btn) {
btn.setAttribute('aria-pressed', on);
}
}
const appPage =
document.querySelector('.app-page') ??
document.querySelector('.jvs-layout-tempOpen > .template-content-box');
if (!appPage) {
// 开关按钮是页面容器子节点,随 SPA 路由切换销毁;body class 要手动清
document.body.classList.remove(bodyClass);
return;
}
// 旧版筛选行(全部分类 + 搜索框)是静态标记、挂载即渲染;卡片列表由异步数据 v-for、
// 晚于筛选行出现。按钮与提示分别判重:提示只在第一张卡出现后插进其父级(卡片流开头),
// 卡片没出来就等下一个 tick(host 缺失守卫)——往容器 prepend 会把提示顶到筛选行上方
const filterBar = appPage.querySelector('.filter-bar');
const firstCard = appPage.querySelector('.application');
const tipHost = filterBar ? firstCard?.parentElement : appPage;
ensureInjected({
host: tipHost,
find: '.ze-star-empty-tip',
mount() {
const tip = document.createElement('div');
tip.className = 'ze-star-empty-tip';
tip.innerText = '暂无星标应用(悬停应用卡片点亮 ★)';
tipHost.prepend(tip);
return tip;
},
});
const btn = ensureInjected({
host: filterBar ?? appPage,
find: '.' + btnClass,
mount() {
const created = document.createElement('button');
created.className = btnClass;
created.type = 'button';
created.innerText = '★ 只看星标';
created.addEventListener('click', () => {
jvsStorage.set(STORAGE_KEYS.STARRED_FILTER, !isFilterOn());
syncFilterState();
});
filterBar ? filterBar.append(created) : appPage.prepend(created);
return created;
},
});
syncFilterState(btn);
}
/**
* 应用中心侧边栏展开/收起开关
* 与 filterStarredApps 同一范式:JS 只产出 body class,侧边栏隐藏与卡片列
* 拉满全由 CSS 驱动;状态现读存储(刷新保持、多标签页同开自动一致)。
* 按钮挂在 .sidebar-col 父级容器内,SPA 路由切换随容器销毁;
* 侧边栏收起后 rect 归零不可读,按钮 left 直接按状态取值
*/
function toggleAppCenterSidebar() {
const btnClass = 'ze-side-toggle-btn';
const bodyClass = 'ze-appcenter-side-collapsed';
function isCollapsed() {
return jvsStorage.get(STORAGE_KEYS.APPCENTER_SIDEBAR, false);
}
function sync() {
const collapsed = isCollapsed();
document.body.classList.toggle(bodyClass, collapsed);
btn.innerText = collapsed ? '»' : '«';
btn.setAttribute('aria-pressed', collapsed);
btn.style.left = (collapsed ? 6 : sidebar.getBoundingClientRect().right - 16) + 'px';
}
const sidebar = document.querySelector('.sidebar-col');
if (!sidebar) {
// 开关按钮随宿主容器销毁;body class 要手动清
document.body.classList.remove(bodyClass);
return;
}
const btn = ensureInjected({
host: sidebar.parentElement,
find: '.' + btnClass,
mount() {
const created = document.createElement('button');
created.className = btnClass;
created.type = 'button';
created.title = '展开/收起侧边栏';
created.addEventListener('click', () => {
jvsStorage.set(STORAGE_KEYS.APPCENTER_SIDEBAR, !isCollapsed());
sync();
});
sidebar.parentElement.appendChild(created);
return created;
},
});
sync();
}
/**
* 逻辑设计,自动展开组件库里指定的分类
*/
function autoExpandComponentLibraryCategory() {
if (STATE.componentLibraryExpanded) {
return;
}
const ruleCategories = document.querySelectorAll('.left-tool-list-box > .rule-assembly-list');
if (ruleCategories.length === 0) {
return;
}
for (const ruleCategory of ruleCategories) {
if (ruleCategory.classList.contains('open')) {
continue;
}
if (Utils.isMarked(ruleCategory, 'auto-expand-component-library-category-11ze')) {
continue;
}
const textDom = ruleCategory.querySelector('div > div > .label');
if (!textDom) {
continue;
}
const text = textDom.innerText.trim();
if (['模型插件', '服务插件', '自定义代码插件'].includes(text)) {
const left = ruleCategory.querySelector('.t-left');
if (left) {
left.dispatchEvent(new MouseEvent('click', { bubbles: true }));
}
}
Utils.mark(ruleCategory, 'auto-expand-component-library-category-11ze');
}
function simulateMouseClick(element) {
const mouseClickEvents = ['mousedown', 'click', 'mouseup'];
mouseClickEvents.forEach((mouseEventType) => {
const mouseEvent = new MouseEvent(mouseEventType, {
bubbles: true, // 允许事件冒泡
cancelable: true, // 允许事件被取消
clientX: element.clientWidth, // 设置点击位置
clientY: element.clientHeight, // 设置点击位置
});
element.dispatchEvent(mouseEvent);
});
}
// 鼠标左键点击一次,收起组件库
const container = document.querySelector('.butterfly-wrapper');
if (container) {
simulateMouseClick(container);
}
STATE.componentLibraryExpanded = true;
}
function getAppNameMap() {
return jvsStorage.get(STORAGE_KEYS.APP_NAME_MAP, {});
}
function getAppIdName(jvsAppId) {
const appNameMap = getAppNameMap();
return appNameMap[jvsAppId] ?? '';
}
function saveAppIdName(jvsAppId, appName) {
if (appName === '复制') {
return;
}
if (!jvsAppId || !appName) {
return;
}
const appNameMap = getAppNameMap();
if (appNameMap[jvsAppId]) {
return;
}
appNameMap[jvsAppId] = appName;
appNameMap[appName] = jvsAppId;
jvsStorage.set(STORAGE_KEYS.APP_NAME_MAP, appNameMap);
}
function applicationSetClick() {
const applicationElements = document.querySelectorAll('div.application');
if (applicationElements.length === 0) {
return;
}
// 使用命名函数以便后续可以移除事件监听器
function handleApplicationClick(event) {
const clickedElement = event.currentTarget;
const idElement = clickedElement.querySelector('label.el-checkbox span.el-checkbox__label');
const nameElement = clickedElement.querySelector('p');
let applicationId = null;
let applicationName = null;
if (idElement) {
applicationId = idElement.textContent.trim();
} else {
console.warn('11ze 未找到 ID 元素:', clickedElement);
}
if (nameElement) {
applicationName = nameElement.textContent.trim();
} else {
console.warn('11ze 未找到名称元素:', clickedElement);
}
if (applicationId !== null && applicationName !== null) {
saveAppIdName(applicationId, applicationName);
} else {
console.warn('11ze 应用中心点击应用未能完整获取点击的应用信息:', clickedElement);
}
}
applicationElements.forEach(function (appElement) {
if (appElement.classList.contains('set-click-11ze')) {
return;
}
appElement.addEventListener('click', handleApplicationClick);
appElement.classList.add('set-click-11ze');
});
}
/**
* 逻辑设计,在组件右边显示执行时间
*/
function showNodeExecTime() {
const nodes = document.querySelectorAll(
'.el-icon-circle-check.el-node-state-success.el-popover__reference',
);
// 在 aria-describedby 属性有 id
for (const node of nodes) {
const id = node.getAttribute('aria-describedby');
if (!id) {
continue;
}
const timeDomId = id + '-time-11ze';
const resultPopover = document.getElementById(id);
if (!resultPopover) {
document.getElementById(timeDomId)?.remove();
continue;
}
const resultText = resultPopover.querySelector('div > h4 > span > span');
if (!resultText) {
document.getElementById(timeDomId)?.remove();
continue;
}
const resultTextContent = resultText.textContent.trim();
if (!resultTextContent) {
document.getElementById(timeDomId)?.remove();
continue;
}
// 键=结果文本:耗时变化时移除旧标注重建
ensureInjected({
host: node.parentNode,
find: '#' + timeDomId,
keyAttr: 'exec-time-11ze',
key: resultTextContent,
mount() {
const span = document.createElement('span');
span.id = timeDomId;
span.setAttribute('exec-time-11ze', resultTextContent);
span.textContent = resultTextContent;
span.style.position = 'absolute';
span.style.right = '-40px';
span.style.color = 'red';
node.parentNode.appendChild(span);
return span;
},
});
}
}
/** 画布滚轮平移的探测状态:上次见到的画布实例 / 画布身份 / 销毁前最后已知平移(连线等操作会让应用整体重建画布,平移归零)/ 重建代数 */
const canvasScrollState = {
seen: null,
identity: null,
offset: null,
rebuildCount: 0,
};
/** 读当前画布身份:主/循环画布切换器的高亮项(主画布/循环容器),取不到给空串 */
function getCanvasIdentity() {
return document.querySelector('.canvas-tool-item.active')?.textContent?.trim() || '';
}
/** 取画布(container 就绪且 canvas 带 move/getOffset 能力),否则 null */
function getButterflyCanvas() {
const container = document.querySelector('.butterfly-vue-container');
const canvas = container?.parentElement?.__vue__?.canvas;
if (!canvas || typeof canvas.move !== 'function' || typeof canvas.getOffset !== 'function') {
return null;
}
return { container, canvas };
}
/** 实例已更换且记录了非零平移时返回待恢复的平移,否则 null */
function canvasScrollRestoreOf(canvas) {
if (canvas === canvasScrollState.seen) {
return null;
}
const [offsetX, offsetY] = canvasScrollState.offset || [0, 0];
return offsetX === 0 && offsetY === 0 ? null : canvasScrollState.offset;
}
/**
* 给逻辑设计的画布挂滚轮平移(wheel → Butterfly canvas.move)
* 全走画布 API:坐标系服务同步(不与左键拖拽冲突),小地图视口框由站点原生联动;
* 主/循环画布切换时容器整体重建,闩锁随 DOM 销毁,由轮询调度自动重挂;
* 同一画布内重建(连线等)把销毁前的平移带回来,切换画布时平移记录直接作废
*/
function setCanvasScroll() {
const { container, canvas } = getButterflyCanvas();
const restore = canvasScrollRestoreOf(canvas);
if (restore) {
canvas.move(restore);
}
canvasScrollState.seen = canvas;
if (container.getAttribute('data-11ze-canvas-scroll')) {
return;
}
container.addEventListener(
'wheel',
(e) => {
e.preventDefault();
const offset = canvas.getOffset();
const deltaX = e.deltaX || (e.shiftKey ? e.deltaY : 0);
const deltaY = deltaX ? 0 : e.deltaY;
canvas.move([offset[0] - deltaX, offset[1] - deltaY]);
},
{ passive: false }
);
container.setAttribute('data-11ze-canvas-scroll', 'true');
}
/**
* favicon 图标映射(Base64 编码的 PNG 图片)
* 键为设计类型简称:列、表、逻、流
*/
const ICONS = {
列表设计:
'data:image/png;base64,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',
表单设计:
'data:image/png;base64,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',
逻辑设计:
'data:image/png;base64,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',
流程设计:
'data:image/png;base64,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',
};
/**
* CSS 样式字符串
* 包含所有需要注入页面的样式规则
*/
const JVS_STYLES = `
/* 修改模型信息的弹窗宽度 */
body > div > div.el-dialog[aria-label="修改模型"],
body > div > div.el-dialog[aria-label="数据集详情"],
body > div > div.el-dialog[aria-label="数据模型配置"] {
width: 75% !important;
}
/* 列表设计的按钮设置弹窗宽度 */
body > div > div.el-dialog[aria-label="导出"],
body > div > div.el-dialog[aria-label="下载模板"] {
width: 75% !important;
}
/* 逻辑设计,调整画布侧边栏宽度 */
.canvas-tool {
width: 260px !important;
}
/* 逻辑设计,画布列表的图标 */
.canvas-tool-item > svg {
display: none;
}
/* 调整画布右边的按钮位置 */
.itempannel-box {
left: 280px !important;
}
/* 新版 JVS,逻辑设计,把浮动操作栏的宽度减小到 45% */
.tool-bar {
width: 45% !important;
}
/* 新版 JVS,逻辑设计,组件详情底部的按钮,设置高度,原本 32px,改成 72px */
#node_detailpannel > div.block-container > div > form > div.el-row > div.form-item-btn.el-col.el-col-24 > div > div {
.el-button,
span {
height: 72px !important;
width: 90px !important;
}
}
/* 逻辑设计,展开组件库的组件名称(换行) */
.left-tool-list {
li.getItem > span {
white-space: normal !important;
}
}
/* 表单设计,展开按钮设置 */
.item-body {
display: block !important;
}
/* 表单设计,悬停时组件名不消失:站点 hover 规则 .formitem:not(.clicked):hover
会把 .type-name 藏掉(只留复制/删除图标),常显功能须盖回 */
.active-formitem2 .handle-btn .type-name {
display: block !important;
}
/* 逻辑设计,展开变量组件设置里的按钮 */
.bottom-body {
display: block !important;
}
/* 展开 JVS 应用名称和侧边栏功能名称 */
/* 首页 */
#app > div > div > div.jvs-layout > div.jvs-main > div.el-scrollbar > div.el-scrollbar__wrap > div > div > div.top-outer-container > div.el-row-bottom-container.el-row > div > div > div > div > p,
/* 旧应用中心 */
#template > div.template-manage-content > div.template-manage-box > div.my-template-list > div > div > div.content > div.content-header > h5,
/* 新首页 > 常用应用 */
#app > div > div > div.jvs-layout > div.jvs-main > div.el-scrollbar > div.el-scrollbar__wrap > div > div > div.top-outer-container > div > div:nth-child(2) > div > div:nth-child(4) > div > div.card-body.el-col.el-col-24 > div > div > p,
/* 新首页 > 应用中心 */
#app > div > div > div.jvs-layout.jvs-layout-tempOpen > div.template-content-box > div.container.el-row > div:nth-child(2) > div > div > div > div:nth-child(1) > p,
/* 应用左上角 */
#app > div > div > div.jvs-layout > div.jvs-left > div > div.el-menu-scrollbar.el-scrollbar > div.el-scrollbar__wrap > div > div.app-item-info.app-item-info-hide > div > span,
#app > div > div > div.jvs-layout > div.jvs-left > div > div.el-menu-scrollbar.el-scrollbar > div.el-scrollbar__wrap > div > div.app-item-info > div > span,
/* 逻辑设计、列表设计、表单设计 */
#app > div > div > div.design-header-box > div.header-left {
white-space: normal !important;
}
/* 放大公式 icon,原本 16px */
.add-formula-svg {
width: 22px !important;
height: 22px !important;
}
/* 移除新版逻辑设计右下角遮挡按钮的透明条 */
.cont-box-right > .tool-bar {
display: none !important;
}
/* 逻辑设计,展开组件名称(.ef-node-text 折行后的善后规则见下方旧版节点块) */
.ef-node-text,
.canvas-tool-item {
white-space: normal !important;
width: 100% !important;
color: #363b4c !important;
}
/* 旧版 JVS,逻辑设计,展开组件名称折行后在原文字框内显示两行(行高 20px、框内居中),节点框与连线锚点不动 */
.jvs-rule-node.ef-node-container.jtk-droppable {
.ef-node-text {
line-height: 20px !important;
min-height: 48px !important;
display: flex !important;
flex-direction: column !important;
justify-content: center !important;
}
}
/* 新版 JVS,逻辑设计,框与 .top 维持原生固定高(连线坐标走原生逻辑),只放开文本层溢出——
折行名称完整画到框外,不占布局、不推锚点;:not 隔离旧版节点。
节点本体严禁加定位:原生 static,inline 里的 top/left 是应用写的画布坐标冗余,一激活就飞 */
.jvs-rule-node.ef-node-container:not(.jtk-droppable) .ef-node-text {
overflow: visible !important;
}
/* 新版 JVS,逻辑设计,隐藏节点左侧图标(类名带 flow-node-drag 但实测非拖拽把手,
拖拽走整个节点),文字左移占满 */
.jvs-rule-node.ef-node-container:not(.jtk-droppable) .ef-node-left-ico {
display: none !important;
}
/* 新版 JVS,逻辑设计,右侧执行结果图标槽空置时收起(有结果时应用给槽内 reference
图标挂 el-node-state-success/el-node-state-error 字体图标类,空置时没有——
图标是字体不是 svg,:empty 也不可用);文字层 width:100% 自动占满,
有执行结果图标时恢复原生占位不压字 */
.jvs-rule-node.ef-node-container:not(.jtk-droppable) .ef-node-right-ico:not(:has([class*="el-node-state"])) {
display: none !important;
}
/* 新版 JVS,逻辑设计,所有节点统一精修:可见的中性灰实线描边(原生是 2px 白色占位边框)、
8px 圆角、轻投影;常驻实线顺带压掉应用原生 hover 的虚线边框 */
.jvs-rule-node.ef-node-container:not(.jtk-droppable) {
border: 1px solid #c0c4cc !important;
border-radius: 8px !important;
box-shadow: 0 1px 3px rgba(31, 45, 61, 0.12) !important;
}
/* 新版 JVS,逻辑设计,命中类型的节点(paint 端加 ze-typed 类与颜色变量)换同色深一阶描边 */
.jvs-rule-node.ef-node-container.ze-typed:not(.jtk-droppable) {
border-color: color-mix(in srgb, var(--ze-type-color), #303133 30%) !important;
}
/* 新版 JVS,逻辑设计,只把静止灰线的虚线改实线(粗细、颜色都保持原生)、端点显形为
白底圆点(原生透明不可见);:not 排除的是应用状态类词表快照(success 绿虚线是日志
回放动画,error/async/abnormal/active 同为状态线)——应用加新状态类须回来补,
否则新状态线会被压平成实线;不写 stroke/stroke-width——线色线宽都由应用控制;
Butterfly 特有类名,旧版 jsPlumber 天然不冲突 */
.butterfly-svg path.butterflies-link:not(.active, .success, .error, .abnormal, .async) {
stroke-dasharray: none !important;
}
.butterfly-wrapper .butterflie-circle-endpoint {
border-radius: 50% !important;
background: #fff !important;
border: 1px solid #909399 !important;
box-sizing: border-box !important;
}
/* 新版 JVS,逻辑设计,入边箭头加大加深后微缩(原生 8宽×8高浅灰实心小三角
不起眼);left 补偿值 = 左右 border 的一半,保持水平居中;尖角钉在节点
顶边(尖角 = 盒顶 + 三角高,top 取负的三角高;原生扎进节点 4px 的位置
被用户否决);:not(.jtk-droppable) 隔离旧版(旧版节点同带这些类,见上方节点规则) */
.jvs-rule-node.ef-node-container:not(.jtk-droppable) .top-endpoint {
top: -10px !important;
left: calc(50% - 6px) !important;
border-width: 10px 6px 4px !important;
border-top-color: #909399 !important;
}
/* 逻辑设计,调整页面设置和已使用逻辑的宽度 */
.content-box:has(.page-setting),
.content-box:has(.used-logic),
.content-box:has(.permission-page) {
width: 80% !important;
margin-left: 10% !important;
}
.content-box > .page-setting,
.content-box > .used-logic,
.content-box > .permission-box,
.content-box > .permission-page {
width: 99% !important;
}
/* 逻辑设计,设计名称输入框 */
#app > div > div > div.design-header-box > div.header-left > div.el-input.el-input--mini > input.el-input__inner {
width: 300px !important;
}
/* 逻辑设计,设计名称编辑 icon */
#app > div > div > div.design-header-box > div.header-left > span > svg {
width: 22px !important;
height: 22px !important;
}
/* 逻辑设计,设置栏「结构定义」按钮与「测试」拉开间距(应用 inline 写死 10px,挨得太近,
须 !important 才能盖过 inline);「测试」按钮外包两层 span 且在自己盒内 margin-left 10px、
溢出 span 盒子右侧,会吃掉本规则的间距——实测需 22px 才比左缝(10px)宽 */
.el-form-item.form-btn-bar .el-form-item__content > button.el-button:last-of-type {
margin-left: 22px !important;
}
/* 自己加的组件 */
/* 日志弹窗 */
.popup-11ze {
border: 1px solid rgba(0, 0, 0, 0.1) !important;
border-radius: 10px !important;
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.1),
0 8px 24px rgba(0, 0, 0, 0.15) !important;
background-color: #fff !important;
position: fixed;
top: 50px;
right: 310px; /* 默认值,由 JS 动态覆盖 */
z-index: 9999;
padding: 0 10px 10px 10px;
max-height: 800px;
overflow-y: auto;
}
.table-11ze {
font-size: 14px !important;
min-width: 600px !important;
border-bottom: 1px solid #dddddd !important;
text-align: left !important;
}
/* 日志弹窗的表格 */
.log-11ze-table-tr > td,
.log-11ze-table-tr > th {
padding: 4px 4px !important;
border-bottom: 1px solid #e5e7eb !important;
border-right: 1px solid #e5e7eb !important;
}
/* 表头样式 */
.log-11ze-table-tr > th {
background-color: #f8fafc !important;
border-bottom: 2px solid #e2e8f0 !important;
position: sticky !important;
z-index: 1 !important;
}
/* 最后一行不显示分割线 */
.log-11ze-table-tr:last-child > td {
border-bottom: none !important;
}
/* 最后一列不显示分割线 */
.log-11ze-table-tr > td:last-child,
.log-11ze-table-tr > th:last-child {
border-right: none !important;
}
.log-11ze-table-tr > td:hover {
background-color: #E8F4FF; /* 悬停时的背景颜色 */
}
/* 名称列鼠标变为小手 */
.log-design-name-11ze {
cursor: pointer;
}
.log-11ze-select {
display: inline-block;
background-color: white !important;
border-color: #D6E4FF !important;
color: black !important;
border: 1px solid #D6E4FF !important;
font-size: 14px !important;
border-radius: 6px !important;
}
.log-11ze-select:hover {
background-color: #E8F4FF !important;
cursor: pointer;
}
/* 日志栏共享样式:仿应用内 Element plain 蓝 */
.button-11ze,
.drag-handle-11ze {
background-color: white !important;
color: #409EFF !important;
border: 1px solid #B3D8FF !important;
border-radius: 8px !important;
padding: 6px 10px !important;
margin: 2px 4px !important;
font-size: 13px !important;
transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
border-color 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
}
.button-11ze:hover,
.drag-handle-11ze:hover {
background-color: #ECF5FF !important;
border-color: #409EFF !important;
}
/* 日志按钮整钮随模式换色:规则由 buildModeColorCss 从 MODE_COLORS 生成(文件末尾拼接注入) */
.button-11ze:hover {
cursor: pointer;
}
/* 日志按钮紧贴拖拽把手,去除中间间距 */
#ze-jvs-log-button {
margin-right: 0 !important;
}
.drag-handle-11ze {
margin: 2px 0 !important;
display: flex;
align-items: center;
justify-content: center;
cursor: move;
user-select: none;
}
.ze-logic-name-display {
display: block;
width: 100%;
margin: 4px 0 2px 0;
padding: 2px 0;
font-size: 13px;
color: #606266;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 逻辑设计左上角的逻辑列表弹窗宽度 */
.other-rule-list {
width: 500px !important;
}
/* 应用中心, 移除每个分类末尾的透明方块(影响点击) */
#app > div > div > div.jvs-layout.jvs-layout-tempOpen > div.template-content-box > div > div > div > div > div > div > img {
display: none !important;
}
/* 应用中心星标:平时隐藏,悬停卡片浮现,已标记实心金星常驻;
边框与定位由 :has() 驱动,只有注入了星标的卡片命中 */
.application:has(.ze-star-btn) {
position: relative !important;
border: 1.5px solid transparent !important;
}
/* 特异性高于透明占位规则,只覆盖颜色,宽度样式继承上一条 */
.application:has(.ze-star-btn.ze-marked) {
border-color: #FAAD14 !important;
}
.ze-star-btn {
position: absolute;
top: 2px;
right: 2px;
width: 28px;
height: 28px;
padding: 0;
border: none;
background: transparent;
line-height: 0;
cursor: pointer;
opacity: 0;
color: #909399;
transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1), color 0.15s cubic-bezier(0.4, 0, 0.2, 1), transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
/* 覆盖 SVG 的 width/height 属性,按钮加大的同时星形同步加大 */
.ze-star-btn svg {
width: 18px;
height: 18px;
}
.application:hover .ze-star-btn {
opacity: 1;
}
/* 悬停星标本体:变金放大给出可点反馈;:active 同特异性靠后,按下回缩胜出 */
.ze-star-btn:hover {
color: #FAAD14;
transform: scale(1.2);
}
.ze-star-btn:active {
transform: scale(0.85);
}
.ze-star-btn.ze-marked {
opacity: 1;
color: #FAAD14;
}
/* fill 覆盖 svg 属性 fill="none" 实心化;fill 与 stroke 都走 currentColor 跟随 color */
.ze-star-btn.ze-marked svg {
fill: currentColor;
}
/* 只看星标过滤:开关状态挂在 body 上,隐藏非星标卡片与无星标部门组,
同样由 :has() 驱动,JS 只产出 body class 这一个事实 */
body.ze-star-filter-on :is(.application, .wrapper-content):not(:has(.ze-star-btn.ze-marked)) {
display: none !important;
}
/* 开了过滤但一个星标都没有:提示行可见,其余规则把卡片全藏了。
新版应用中心容器是 .app-page,旧版是 .template-content-box */
body.ze-star-filter-on :is(.app-page, .template-content-box):not(:has(.ze-star-btn.ze-marked)) .ze-star-empty-tip {
display: block;
}
/* 只看星标开关 pill:钉在应用列表上方,未激活灰描边、激活金色 */
.ze-star-filter-btn {
margin: 0 0 12px 12px;
padding: 4px 12px;
border: 1px solid #C0C4CC;
border-radius: 12px;
background: transparent;
color: #909399;
font-size: 12px;
line-height: 20px;
cursor: pointer;
transition: color 0.2s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
body.ze-star-filter-on .ze-star-filter-btn {
border-color: #FAAD14;
color: #FAAD14;
}
/* 旧版筛选行内(搜索框右侧):对齐与间距交给 flex 的 align-items/gap,清掉钉顶 margin */
.filter-bar .ze-star-filter-btn {
margin: 0;
}
/* 空态提示行:默认隐藏,仅空态规则放行 */
.ze-star-empty-tip {
display: none;
margin: 24px 0;
color: #909399;
font-size: 14px;
text-align: center;
}
/* 应用中心侧边栏收起:分类列与其右侧空白间隔列一起隐藏,卡片区拉满整行
(el-col 为 float 布局、宽度全靠 class,覆盖需 !important) */
body.ze-appcenter-side-collapsed :is(.sidebar-col, .sidebar-col + .el-col) {
display: none !important;
}
body.ze-appcenter-side-collapsed .sidebar-col + .el-col + .el-col {
width: 100% !important;
}
/* .app-page 自带 margin-left: -128px 的跨列偏移,侧边栏没了会推出屏幕外,一并归位 */
body.ze-appcenter-side-collapsed .app-page {
margin-left: 0 !important;
width: 100% !important;
}
/* 侧边栏收起开关:仿应用内样式,白色圆角方块骑在侧边栏右缘,left 由 JS 校准 */
.ze-side-toggle-btn {
position: fixed;
bottom: 48px;
z-index: 999;
width: 32px;
height: 36px;
padding: 0;
border: none;
border-radius: 10px;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
color: #606266;
font-size: 16px;
line-height: 36px;
cursor: pointer;
user-select: none;
}
`;
GM_addStyle(JVS_STYLES + buildModeColorCss());
})();