迷你图规则

迷你图条件格式规则可以直接在单元格中呈现迷你图表。

该示例支持多种迷你图类型,例如饼图、折线图、柱形图、子弹图、HBar、WinLoss、棒棒糖差异图等,并提供规则列表、范围编辑和参数配置面板,便于快速新增、更新和管理迷你图规则。

概述 本示例展示了如何通过条件格式中的 SparklineRule 在单元格内生成迷你图,并结合右侧规则面板完成规则的新增、更新、排序与范围修改。 示例在同一个工作簿中预置了 4 张工作表,分别演示棒棒糖差异图、柱形迷你图、饼图迷你图和子弹图迷你图。每张表都先填充业务数据,再把迷你图规则应用到目标单元格区域中。 实现思路 初始化工作簿并创建 4 个工作表,在页面加载完成后分别写入示例数据和迷你图规则。 每个工作表先设置标题、表头、数据和样式,再通过 conditionalFormats.addSparklineRule 将不同类型的迷你图规则绑定到指定区域。 右侧规则面板负责收集当前选择的迷你图类型和参数,并把这些参数转换成新的条件格式规则。 规则列表会读取当前活动工作表中的全部条件格式规则,支持选中规则、删除规则,以及直接点击范围文本进行编辑。 当切换活动工作表时,规则列表和编辑面板会同步刷新,保证操作始终针对当前工作表生效。 代码解析 示例入口先创建 Workbook,然后依次初始化四个工作表、规则控制面板和面板折叠按钮: 以“棒棒糖差异图”为例,代码先准备计划值、实际值和颜色列,再把两条 LOLLIPOPVARISPARKLINE 规则分别应用到“差异百分比”和“差异绝对值”两列中: 右侧面板新增规则时,会根据当前下拉框和参数输入区域收集配置,再调用 addSparklineRule 把规则加到当前选区: 规则列表支持直接修改规则范围。点击范围文本后,组件会把原始范围公式放入输入框中;用户按 Enter 后再重新解析并写回规则: 参数区域并不是写死的表单,而是根据 sparklineConfigs 配置动态生成。这样同一套界面就可以切换不同迷你图类型,并显示各自对应的参数: 运行效果 工作簿打开后会显示 4 张示例工作表,每张表对应一种不同的迷你图使用场景。 右侧面板可以切换迷你图类型,并动态显示对应的参数配置项。 点击“添加规则”后,当前选区会立即应用新的迷你图条件格式。 在规则列表中选中某条规则后,面板会切换到“更新规则”模式,并回填当前规则配置。 点击规则范围文本可以直接输入新的公式范围;输入合法后会立即更新规则,输入非法时会弹出提示。 规则列表支持上移、下移和删除操作,便于调整多个条件格式规则的顺序。
var spreadNS = GC.Spread.Sheets; window.onload = function () { var spread = new spreadNS.Workbook(document.getElementById('ss'), { sheetCount: 4 }); spread.suspendPaint(); initSpread(spread); initRuleControlPanel(spread); initPanelToggle(spread); spread.resumePaint(); }; function initSpread(spread) { initSheet1_LollipopVariance(spread); initSheet2_Column(spread); initSheet3_Pie(spread); initSheet4_Bullet(spread); spread.setActiveSheetIndex(0); } /* ================================================================ Helpers ================================================================ */ function colToLetter(col) { var s = ''; col++; while (col > 0) { col--; s = String.fromCharCode(65 + (col % 26)) + s; col = Math.floor(col / 26); } return s; } function absRange(row, col, rowCount, colCount) { var r1 = row + 1, r2 = row + rowCount; var c1 = colToLetter(col), c2 = colToLetter(col + colCount - 1); return '$' + c1 + '$' + r1 + ':$' + c2 + '$' + r2; } // Relative-row range: column is absolute ($C), row is relative (4) — shifts per cell function relRowRange(row, col, rowCount, colCount) { var r1 = row + 1, r2 = row + rowCount; var c1 = colToLetter(col), c2 = colToLetter(col + colCount - 1); return '$' + c1 + r1 + ':$' + c2 + r2; } function relRowCell(row, col) { return '$' + colToLetter(col) + (row + 1); } function makeStyle(opts) { var s = new spreadNS.Style(); if (opts.font) s.font = opts.font; if (opts.bg) s.backColor = opts.bg; if (opts.fg) s.foreColor = opts.fg; if (opts.hAlign !== undefined) s.hAlign = opts.hAlign; if (opts.vAlign !== undefined) s.vAlign = opts.vAlign; if (opts.formatter) s.formatter = opts.formatter; if (opts.wordWrap) s.wordWrap = true; return s; } /* ================================================================ Sheet 1 — Lollipop Variance (two sparkline columns: % and absolute) ================================================================ */ function initSheet1_LollipopVariance(spread) { var sheet = spread.getSheet(0); sheet.name('棒棒糖差异图'); sheet.options.sheetTabColor = '#6fa300'; sheet.suspendPaint(); sheet.setColumnCount(8); sheet.setRowCount(12); var cfs = sheet.conditionalFormats; var i, rule; var c = 0; var titleStyle = makeStyle({ font: 'bold 14px Calibri', fg: '#1a1a1a', vAlign: 1 }); var headerStyle = makeStyle({ font: 'bold 11px Calibri', bg: '#6fa300', fg: '#FFFFFF', hAlign: 1, vAlign: 1 }); var labelStyle = makeStyle({ bg: '#e6f2cc', font: '11px Calibri', vAlign: 1 }); var dollarStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1, formatter: '$#,##0' }); var colorStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1 }); var thinBorder = new spreadNS.LineBorder('#a3c760', spreadNS.LineStyle.thin); // A:Dept B:Planned C:Actual D:Variance(%) E:Variance(Abs) F:PosColor G:NegColor var headers = ['部门', '计划值(千美元)', '实际值(千美元)', '差异(%)', '差异(绝对值)', '正向颜色', '负向颜色']; var colWidths = [140, 100, 100, 180, 180, 110, 110]; // Title var r = 1; sheet.addSpan(r, c, 1, headers.length); sheet.setValue(r, c, '预算差异分析'); sheet.setStyle(r, c, titleStyle); sheet.setRowHeight(r, 30); // Headers r = 2; for (i = 0; i < headers.length; i++) { sheet.setValue(r, c + i, headers[i]); sheet.setStyle(r, c + i, headerStyle); sheet.setColumnWidth(c + i, colWidths[i]); } sheet.setRowHeight(r, 30); // Data: [dept, planned, actual, posColor, negColor] var data = [ ['研发', 850, 760, '#6fa300', '#c8a000'], ['市场', 420, 500, '#6fa300', '#c8a000'], ['销售', 380, 330, '#4a7a00', '#c44d2b'], ['客户支持', 260, 305, '#4a7a00', '#c44d2b'], ['基础设施', 520, 475, '#6fa300', '#c8a000'], ['人力与培训', 180, 210, '#6fa300', '#c8a000'], ['法务', 150, 128, '#4a7a00', '#c44d2b'], ['综合行政', 200, 245, '#4a7a00', '#c44d2b'] ]; var dataFirstRow = r + 1; for (i = 0; i < data.length; i++) { var row = dataFirstRow + i; sheet.setValue(row, c, data[i][0]); sheet.setStyle(row, c, labelStyle); sheet.setValue(row, c + 1, data[i][1]); sheet.setStyle(row, c + 1, dollarStyle); sheet.setValue(row, c + 2, data[i][2]); sheet.setStyle(row, c + 2, dollarStyle); // c+3, c+4 are sparkline columns (skip) sheet.setValue(row, c + 5, data[i][3]); sheet.setStyle(row, c + 5, colorStyle); sheet.setValue(row, c + 6, data[i][4]); sheet.setStyle(row, c + 6, colorStyle); sheet.setRowHeight(row, 42); } // Borders sheet.getRange(r, c, data.length + 1, headers.length).setBorder(thinBorder, { all: true }); var plannedRef = absRange(dataFirstRow, c + 1, data.length, 1); var actualRef = absRange(dataFirstRow, c + 2, data.length, 1); // Rule 1: Percentage variance → column D (c+3) rule = cfs.addSparklineRule('LOLLIPOPVARISPARKLINE', { plannedValue: plannedRef, actualValue: actualRef, index: '@', reference: 0, mini: -0.15, maxi: 0.25, legend: true, colorPositive: relRowCell(dataFirstRow, c + 5), colorNegative: relRowCell(dataFirstRow, c + 6), lollipopHeaderColor: '#4a7a00' }, [new spreadNS.Range(dataFirstRow, c + 3, data.length, 1)]); if (rule) rule.showSparklineOnly(true); // Rule 2: Absolute variance → column E (c+4) rule = cfs.addSparklineRule('LOLLIPOPVARISPARKLINE', { plannedValue: plannedRef, actualValue: actualRef, index: '@', absolute: true, reference: 0, mini: -100, maxi: 100, legend: true, colorPositive: relRowCell(dataFirstRow, c + 5), colorNegative: relRowCell(dataFirstRow, c + 6), lollipopHeaderColor: '#4a7a00' }, [new spreadNS.Range(dataFirstRow, c + 4, data.length, 1)]); if (rule) rule.showSparklineOnly(true); sheet.resumePaint(); } /* ================================================================ Sheet 2 — Column ================================================================ */ function initSheet2_Column(spread) { var sheet = spread.getSheet(1); sheet.name('柱形'); sheet.options.sheetTabColor = '#c8a000'; sheet.suspendPaint(); sheet.setColumnCount(7); sheet.setRowCount(10); var cfs = sheet.conditionalFormats; var i, j, rule; var c = 0; var titleStyle = makeStyle({ font: 'bold 14px Calibri', fg: '#1a1a1a', vAlign: 1 }); var headerStyle = makeStyle({ font: 'bold 11px Calibri', bg: '#c8a000', fg: '#FFFFFF', hAlign: 1, vAlign: 1 }); var labelStyle = makeStyle({ bg: '#f5f0d0', font: '11px Calibri', vAlign: 1 }); var dollarStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1, formatter: '$#,##0' }); var thinBorder = new spreadNS.LineBorder('#d4c46a', spreadNS.LineStyle.thin); // A:Region B-E:Q1-Q4 F:Quarterly(sparkline) var headers = ['区域', 'Q1', 'Q2', 'Q3', 'Q4', '季度趋势']; var colWidths = [120, 80, 80, 80, 80, 160]; // Title var r = 1; sheet.addSpan(r, c, 1, headers.length); sheet.setValue(r, c, '各区域季度营收'); sheet.setStyle(r, c, titleStyle); sheet.setRowHeight(r, 30); // Headers r = 2; for (i = 0; i < headers.length; i++) { sheet.setValue(r, c + i, headers[i]); sheet.setStyle(r, c + i, headerStyle); sheet.setColumnWidth(c + i, colWidths[i]); } sheet.setRowHeight(r, 30); // Data: [region, Q1, Q2, Q3, Q4] var data = [ ['北美', 1800, 1620, 1750, 1920], ['欧洲', 1200, 1380, 1150, 1400], ['亚太', 1080, 950, 1140, 1020], ['拉丁美洲', 480, 560, 430, 580], ['中东', 400, 350, 420, 370], ['非洲', 190, 260, 210, 270] ]; var dataFirstRow = r + 1; for (i = 0; i < data.length; i++) { var row = dataFirstRow + i; sheet.setValue(row, c, data[i][0]); sheet.setStyle(row, c, labelStyle); for (j = 1; j <= 4; j++) { sheet.setValue(row, c + j, data[i][j]); sheet.setStyle(row, c + j, dollarStyle); } sheet.setRowHeight(row, 60); } // Borders sheet.getRange(r, c, data.length + 1, headers.length).setBorder(thinBorder, { all: true }); // Column sparkline — single rule spanning all data rows var setting = new GC.Spread.Sheets.Sparklines.SparklineSetting(); setting.options.seriesColor = '#4a7a00'; setting.options.showHigh = true; setting.options.highMarkerColor = '#8b0000'; setting.options.showLow = true; setting.options.lowMarkerColor = '#d45500'; setting.options.showFirst = true; setting.options.firstMarkerColor = '#3d6b00'; setting.options.showLast = true; setting.options.lastMarkerColor = '#b5cc18'; rule = cfs.addSparklineRule('COLUMNSPARKLINE', { data: relRowRange(dataFirstRow, c + 1, 1, 4), dataOrientation: 1, setting: setting }, [new spreadNS.Range(dataFirstRow, c + 5, data.length, 1)]); if (rule) rule.showSparklineOnly(true); sheet.resumePaint(); } /* ================================================================ Sheet 3 — Pie ================================================================ */ function initSheet3_Pie(spread) { var sheet = spread.getSheet(2); sheet.name('饼图'); sheet.options.sheetTabColor = '#4a7a00'; sheet.suspendPaint(); sheet.setColumnCount(9); sheet.setRowCount(11); var cfs = sheet.conditionalFormats; var i, j, rule; var c = 0; var titleStyle = makeStyle({ font: 'bold 14px Calibri', fg: '#1a1a1a', vAlign: 1 }); var headerStyle = makeStyle({ font: 'bold 11px Calibri', bg: '#4a7a00', fg: '#FFFFFF', hAlign: 1, vAlign: 1 }); var labelStyle = makeStyle({ bg: '#dce8c8', font: '11px Calibri', vAlign: 1 }); var pctStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1, formatter: '0%' }); var colorStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1 }); var thinBorder = new spreadNS.LineBorder('#8fb356', spreadNS.LineStyle.thin); // A:Product B-D:HW%,SW%,Svc% E:Distribution(sparkline) F-H:Color1-3 var headers = ['产品', '硬件占比', '软件占比', '服务占比', '分布', '颜色 1', '颜色 2', '颜色 3']; var colWidths = [120, 90, 90, 90, 160, 80, 80, 80]; // Title var r = 1; sheet.addSpan(r, c, 1, headers.length); sheet.setValue(r, c, '各产品营收分布'); sheet.setStyle(r, c, titleStyle); sheet.setRowHeight(r, 30); // Headers r = 2; for (i = 0; i < headers.length; i++) { sheet.setValue(r, c + i, headers[i]); sheet.setStyle(r, c + i, headerStyle); sheet.setColumnWidth(c + i, colWidths[i]); } sheet.setRowHeight(r, 30); // Data: [product, hw%, sw%, svc%, color1, color2, color3] var data = [ ['笔记本', 0.45, 0.35, 0.20, '#4a7a00', '#c8a000', '#6fa300'], ['台式机', 0.30, 0.45, 0.25, '#8bc34a', '#b5cc18', '#A5A5A5'], ['平板', 0.55, 0.25, 0.20, '#2d5200', '#b8d86e', '#3d6b00'], ['手机', 0.40, 0.30, 0.30, '#4a7a00', '#c8a000', '#6fa300'], ['配件', 0.35, 0.40, 0.25, '#d45500', '#c5e17a', '#9acd32'], ['服务器', 0.50, 0.30, 0.20, '#2d5200', '#7d9a30', '#c5d48a'], ['网络设备', 0.25, 0.50, 0.25, '#4a7a00', '#c8a000', '#6fa300'] ]; var dataFirstRow = r + 1; for (i = 0; i < data.length; i++) { var row = dataFirstRow + i; sheet.setValue(row, c, data[i][0]); sheet.setStyle(row, c, labelStyle); for (j = 1; j <= 3; j++) { sheet.setValue(row, c + j, data[i][j]); sheet.setStyle(row, c + j, pctStyle); } // c+4 is sparkline column (skip) // c+5, c+6, c+7 are color columns (plain text) for (j = 0; j < 3; j++) { sheet.setValue(row, c + 5 + j, data[i][4 + j]); sheet.setStyle(row, c + 5 + j, colorStyle); } sheet.setRowHeight(row, 34); } // Borders sheet.getRange(r, c, data.length + 1, headers.length).setBorder(thinBorder, { all: true }); // Pie sparkline — single rule spanning all data rows // colors use relRowCell to reference color columns per row rule = cfs.addSparklineRule('PIESPARKLINE', { value: relRowRange(dataFirstRow, c + 1, 1, 3), colors: [relRowCell(dataFirstRow, c + 5), relRowCell(dataFirstRow, c + 6), relRowCell(dataFirstRow, c + 7)] }, [new spreadNS.Range(dataFirstRow, c + 4, data.length, 1)]); if (rule) rule.showSparklineOnly(true); sheet.resumePaint(); } /* ================================================================ Sheet 4 — Bullet ================================================================ */ function initSheet4_Bullet(spread) { var sheet = spread.getSheet(3); sheet.name('子弹图'); sheet.options.sheetTabColor = '#8bc34a'; sheet.suspendPaint(); sheet.setColumnCount(9); sheet.setRowCount(11); var cfs = sheet.conditionalFormats; var i, j, rule; var c = 0; var titleStyle = makeStyle({ font: 'bold 14px Calibri', fg: '#1a1a1a', vAlign: 1 }); var headerStyle = makeStyle({ font: 'bold 11px Calibri', bg: '#8bc34a', fg: '#FFFFFF', hAlign: 1, vAlign: 1 }); var labelStyle = makeStyle({ bg: '#eaf2d5', font: '11px Calibri', vAlign: 1 }); var dollarStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1, formatter: '$#,##0' }); var colorStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1 }); var thinBorder = new spreadNS.LineBorder('#b8d86e', spreadNS.LineStyle.thin); // A:Rep B-F:Actual,Target,Max,Good,Bad G:Performance(sparkline) H:Color var headers = ['销售代表', '实际值(千美元)', '目标值(千美元)', '最大值(千美元)', '良好值(千美元)', '较差值(千美元)', '绩效', '颜色']; var colWidths = [120, 90, 90, 80, 80, 80, 160, 80]; // Title var r = 1; sheet.addSpan(r, c, 1, headers.length); sheet.setValue(r, c, '销售团队绩效'); sheet.setStyle(r, c, titleStyle); sheet.setRowHeight(r, 30); // Headers r = 2; for (i = 0; i < headers.length; i++) { sheet.setValue(r, c + i, headers[i]); sheet.setStyle(r, c + i, headerStyle); sheet.setColumnWidth(c + i, colWidths[i]); } sheet.setRowHeight(r, 30); // Data: [name, actual, target, max, good, bad, color] var data = [ ['Alice Chen', 82, 90, 120, 90, 60, '#4a7a00'], ['Bob Martinez', 95, 85, 120, 85, 55, '#c8a000'], ['Carol Wang', 68, 80, 120, 80, 50, '#6fa300'], ['David Park', 105, 100, 130, 100, 65, '#b5cc18'], ['Eva Johnson', 73, 75, 110, 75, 45, '#8bc34a'], ['Frank Liu', 88, 95, 125, 95, 60, '#4a7a00'], ['Grace Kim', 110, 105, 135, 105, 70, '#c8a000'] ]; var dataFirstRow = r + 1; for (i = 0; i < data.length; i++) { var row = dataFirstRow + i; sheet.setValue(row, c, data[i][0]); sheet.setStyle(row, c, labelStyle); for (j = 1; j <= 5; j++) { sheet.setValue(row, c + j, data[i][j]); sheet.setStyle(row, c + j, dollarStyle); } // c+6 is sparkline column (skip) // c+7 is color column (plain text) sheet.setValue(row, c + 7, data[i][6]); sheet.setStyle(row, c + 7, colorStyle); sheet.setRowHeight(row, 34); } // Borders sheet.getRange(r, c, data.length + 1, headers.length).setBorder(thinBorder, { all: true }); // Bullet sparkline → Performance column (c+6), colorScheme from Color cell (c+7) rule = cfs.addSparklineRule('BULLETSPARKLINE', { measure: relRowCell(dataFirstRow, c + 1), target: relRowCell(dataFirstRow, c + 2), maxi: relRowCell(dataFirstRow, c + 3), good: relRowCell(dataFirstRow, c + 4), bad: relRowCell(dataFirstRow, c + 5), tickUnit: 20, colorScheme: relRowCell(dataFirstRow, c + 7) }, [new spreadNS.Range(dataFirstRow, c + 6, data.length, 1)]); if (rule) rule.showSparklineOnly(true); sheet.resumePaint(); } /* ================================================================ Control Panel — Redesigned ================================================================ */ function initRuleControlPanel(spread) { var ruleList = new RuleList(spread, document.getElementById('listItemContainer')); var rulePanel = new RulePanel(spread); rulePanel.bind('onRuleAdded', function () { ruleList.update(); }); rulePanel.bind('onCancelUpdate', function () { ruleList.clearSelectedState(); }); ruleList.bind('onRuleSelected', function (evt, args) { if (args.rule) { rulePanel.showUpdateMode(args.activeSheet, args.rule); } else { rulePanel.showAddMode(); } }); ruleList.bind('onRuleRemoved', function () { rulePanel.showAddMode(); }); ruleList.bind('onRuleRangeChanged', function (evt, args) { if (args.rule && rulePanel.ruleForUpdate === args.rule) { rulePanel.setRuleRanges(args.ranges); } }); spread.bind(spreadNS.Events.ActiveSheetChanged, function () { ruleList.update(); rulePanel.showAddMode(); }); document.getElementById('format-rule-move-up').addEventListener('click', function () { ruleList.sortFormatRules(true); }); document.getElementById('format-rule-move-down').addEventListener('click', function () { ruleList.sortFormatRules(false); }); } function initPanelToggle(spread) { var panel = document.querySelector('.options-container'); var toggleBtn = document.getElementById('panelToggleBtn'); var collapseBtn = document.getElementById('panelCollapseBtn'); function collapsePanel() { panel.classList.add('collapsed'); toggleBtn.classList.add('visible'); setTimeout(function () { spread.refresh(); }, 320); } function expandPanel() { panel.classList.remove('collapsed'); toggleBtn.classList.remove('visible'); setTimeout(function () { spread.refresh(); }, 320); } collapseBtn.addEventListener('click', collapsePanel); toggleBtn.addEventListener('click', expandPanel); } function cloneRanges(ranges) { var cloned = []; if (!ranges) return cloned; for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; cloned.push(new spreadNS.Range(range.row, range.col, range.rowCount, range.colCount)); } return cloned; } function rangesToFormula(ranges) { var formulaParts = []; for (var i = 0; i < ranges.length; i++) { formulaParts.push( spreadNS.CalcEngine.rangeToFormula( ranges[i], 0, 0, spreadNS.CalcEngine.RangeReferenceRelative.allAbsolute ) ); } return formulaParts.join(','); } function parseRangesFormula(sheet, formula) { if (!formula) return null; try { var groups = spreadNS.CalcEngine.formulaToRanges(sheet, formula, 0, 0); var ranges = []; if (!groups || !groups.length) return null; for (var i = 0; i < groups.length; i++) { var groupRanges = groups[i] && groups[i].ranges; if (!groupRanges || !groupRanges.length) continue; for (var j = 0; j < groupRanges.length; j++) { ranges.push(groupRanges[j]); } } return ranges.length ? cloneRanges(ranges) : null; } catch (error) { return null; } } /* ================================================================ RulePanel — Add / Update sparkline rules ================================================================ */ function RulePanel(spread) { this.spread = spread; this.eventsHandler = {}; this.ruleForUpdate = null; this.sheetForUpdate = null; this.rangesForUpdate = null; this.$modeLabel = document.getElementById('panelModeLabel'); this.$addBtn = document.getElementById('btnAddRule'); this.$updateBtn = document.getElementById('btnUpdateRule'); this.$cancelBtn = document.getElementById('btnCancelUpdate'); this.$sparklineType = document.getElementById('sparklineType'); this.$showOnly = document.getElementById('showSparklineOnly'); this.init(); } RulePanel.prototype.init = function () { var self = this; // Build initial sparkline options UI buildSparklineOptionsUI(self.$sparklineType.value, self.spread); self.$sparklineType.addEventListener('change', function () { buildSparklineOptionsUI(this.value, self.spread); }); self.$addBtn.addEventListener('click', function () { self.addRule(); }); self.$updateBtn.addEventListener('click', function () { self.updateRule(); }); self.$cancelBtn.addEventListener('click', function () { self.showAddMode(); self.trigger('onCancelUpdate'); }); }; RulePanel.prototype.addRule = function (sheet, ranges) { var self = this; var activeSheet = sheet || self.spread.getActiveSheet(); var targetRanges = ranges || activeSheet.getSelections(); var sparklineType = self.$sparklineType.value; var sparklineOpts = collectSparklineOptions(sparklineType); activeSheet.suspendPaint(); var rule = activeSheet.conditionalFormats.addSparklineRule(sparklineType, sparklineOpts, targetRanges); if (rule) { rule.showSparklineOnly(self.$showOnly.checked); } activeSheet.resumePaint(); self.trigger('onRuleAdded'); }; RulePanel.prototype.updateRule = function () { if (!this.ruleForUpdate || !this.sheetForUpdate) return; var ranges = cloneRanges(this.rangesForUpdate || this.ruleForUpdate.ranges()); this.sheetForUpdate.conditionalFormats.removeRule(this.ruleForUpdate); this.addRule(this.sheetForUpdate, ranges); this.showAddMode(); this.trigger('onCancelUpdate'); }; RulePanel.prototype.showAddMode = function () { this.$modeLabel.textContent = '添加规则'; this.$addBtn.style.display = ''; this.$updateBtn.style.display = 'none'; this.$cancelBtn.style.display = 'none'; this.ruleForUpdate = null; this.sheetForUpdate = null; this.rangesForUpdate = null; // Reset to defaults this.$sparklineType.value = 'PIESPARKLINE'; buildSparklineOptionsUI('PIESPARKLINE', this.spread); this.$showOnly.checked = false; }; RulePanel.prototype.showUpdateMode = function (sheet, rule) { this.ruleForUpdate = rule; this.sheetForUpdate = sheet; this.rangesForUpdate = cloneRanges(rule.ranges ? rule.ranges() : []); this.$modeLabel.textContent = '更新规则'; this.$addBtn.style.display = 'none'; this.$updateBtn.style.display = ''; this.$cancelBtn.style.display = ''; // Sync UI with existing rule var ruleType = rule.ruleType(); if (ruleType === spreadNS.ConditionalFormatting.RuleType.sparklineRule) { var sparklineType = rule.sparklineType ? rule.sparklineType() : ''; var sparklineOptions = rule.sparklineOptions ? rule.sparklineOptions() : {}; this.$sparklineType.value = sparklineType; this.$showOnly.checked = rule.showSparklineOnly ? rule.showSparklineOnly() : false; buildSparklineOptionsUI(sparklineType, this.spread); populateSparklineOptions(sparklineType, sparklineOptions); } }; RulePanel.prototype.setRuleRanges = function (ranges) { this.rangesForUpdate = cloneRanges(ranges); }; RulePanel.prototype.bind = function (event, callback) { if (!this.eventsHandler[event]) this.eventsHandler[event] = []; this.eventsHandler[event].push(callback); }; RulePanel.prototype.trigger = function (event, args) { var handlers = this.eventsHandler[event]; if (!handlers) return; for (var i = 0; i < handlers.length; i++) { handlers[i].call(this, event, args); } }; /* ================================================================ RuleList — Display & manage applied rules ================================================================ */ function RuleList(spread, host) { this.spread = spread; this.host = host; this.conditionalRules = {}; this.eventsHandler = {}; this.update(); } RuleList.Events = { onRuleSelected: 'onRuleSelected', onRuleRemoved: 'onRuleRemoved', onRuleRangeChanged: 'onRuleRangeChanged' }; RuleList.prototype.update = function () { this.clear(); this.buildList(); }; RuleList.prototype.clear = function () { this.host.innerHTML = ''; }; RuleList.prototype.clearSelectedState = function () { var items = this.host.querySelectorAll('.ruleListItem'); for (var i = 0; i < items.length; i++) { items[i].classList.remove('selected'); } }; RuleList.prototype.selectRule = function (item, ruleRef) { this.clearSelectedState(); item.classList.add('selected'); this.trigger('onRuleSelected', { activeSheet: this.spread.getActiveSheet(), rule: ruleRef }); }; RuleList.prototype.startRangeEdit = function (item, ruleRef, rangeHost) { if (rangeHost.classList.contains('editing')) return; var self = this; var sheet = self.spread.getActiveSheet(); var originalFormula = rangesToFormula(ruleRef.ranges()); rangeHost.classList.add('editing'); rangeHost.innerHTML = ''; var input = document.createElement('input'); input.type = 'text'; input.className = 'ruleRangeInput'; input.value = originalFormula; input.title = '按 Enter 应用'; rangeHost.appendChild(input); var completed = false; function finish(shouldApply) { if (completed) return; completed = true; rangeHost.classList.remove('editing'); var nextFormula = originalFormula; if (shouldApply) { var updatedRanges = parseRangesFormula(sheet, input.value.trim()); if (updatedRanges) { sheet.suspendPaint(); ruleRef.ranges(updatedRanges); sheet.resumePaint(); self.spread.refresh(); nextFormula = rangesToFormula(ruleRef.ranges()); self.trigger('onRuleRangeChanged', { activeSheet: sheet, rule: ruleRef, ranges: cloneRanges(ruleRef.ranges()) }); } else { window.alert('规则范围无效,请输入合法的范围公式。'); } } rangeHost.textContent = nextFormula; rangeHost.title = nextFormula; } input.addEventListener('click', function (e) { e.stopPropagation(); }); input.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); finish(true); } else if (e.key === 'Escape') { e.preventDefault(); finish(false); } }); input.addEventListener('blur', function () { finish(true); }); setTimeout(function () { input.focus(); input.select(); }, 0); }; RuleList.prototype.buildList = function () { var self = this; var sheet = self.spread.getActiveSheet(); self.conditionalRules = sheet.conditionalFormats; var rules = self.conditionalRules.getRules(); for (var i = rules.length - 1; i >= 0; i--) { var rule = rules[i]; var $item = document.createElement('div'); $item.classList.add('ruleListItem'); // Description with badge var $desc = document.createElement('div'); $desc.classList.add('ruleDescription'); var description = '规则'; var badgeClass = 'badge-default'; if (rule.ruleType() === spreadNS.ConditionalFormatting.RuleType.sparklineRule) { var sparklineType = rule.sparklineType ? rule.sparklineType() : ''; var friendly = sparklineType.replace('SPARKLINE', '').replace('LOLLIPOPVARI', 'Lollipop').replace('VARI', 'Variance'); if (friendly && friendly === friendly.toUpperCase()) { friendly = friendly.charAt(0) + friendly.slice(1).toLowerCase(); } description = friendly || '迷你图'; var typeKey = friendly.toLowerCase(); if (typeKey === 'bullet') badgeClass = 'badge-bullet'; else if (typeKey === 'pie') badgeClass = 'badge-pie'; else if (typeKey === 'hbar') badgeClass = 'badge-hbar'; else if (typeKey === 'line') badgeClass = 'badge-line'; else if (typeKey === 'column') badgeClass = 'badge-column'; else if (typeKey === 'winloss') badgeClass = 'badge-winloss'; else if (typeKey === 'lollipop') badgeClass = 'badge-lollipop'; } var badge = document.createElement('span'); badge.className = 'sparkline-badge ' + badgeClass; badge.textContent = description; $desc.appendChild(badge); // Range formula var $range = document.createElement('div'); $range.classList.add('ruleFormulas'); var formula = rangesToFormula(rule.ranges()); $range.textContent = formula; $range.title = formula; // Stop If True var $stop = document.createElement('div'); $stop.classList.add('ruleStopIfTrue'); var $cb = document.createElement('input'); $cb.type = 'checkbox'; $cb.checked = rule.stopIfTrue(); if (rule.ruleType() === spreadNS.ConditionalFormatting.RuleType.sparklineRule) { $cb.disabled = true; } $stop.appendChild($cb); // Remove button var $remove = document.createElement('div'); $remove.classList.add('ruleRemove'); var $removeBtn = document.createElement('button'); $removeBtn.className = 'removeRuleBtn'; $removeBtn.textContent = '\u00d7'; $removeBtn.title = '删除规则'; $remove.appendChild($removeBtn); $item.appendChild($desc); $item.appendChild($range); $item.appendChild($stop); $item.appendChild($remove); self.host.appendChild($item); // Event: select row (function (item, ruleRef) { item.addEventListener('click', function (e) { if (e.target.classList.contains('removeRuleBtn')) return; self.selectRule(item, ruleRef); }); })($item, rule); (function (item, ruleRef, rangeHost) { rangeHost.addEventListener('click', function (e) { e.stopPropagation(); self.selectRule(item, ruleRef); self.startRangeEdit(item, ruleRef, rangeHost); }); })($item, rule, $range); // Event: remove (function (ruleRef, removeBtn) { removeBtn.addEventListener('click', function () { var sheet = self.spread.getActiveSheet(); sheet.conditionalFormats.removeRule(ruleRef); self.trigger('onRuleRemoved'); self.update(); }); })(rule, $removeBtn); // Event: stop if true (function (ruleRef, checkbox) { checkbox.addEventListener('change', function () { ruleRef.stopIfTrue(checkbox.checked); }); })(rule, $cb); } }; RuleList.prototype.sortFormatRules = function (isUp) { var self = this; var items = self.host.querySelectorAll('.ruleListItem'); var selectedIdx = -1; for (var i = 0; i < items.length; i++) { if (items[i].classList.contains('selected')) { selectedIdx = i; break; } } if (selectedIdx === -1) return; if ((isUp && selectedIdx === 0) || (!isUp && selectedIdx === items.length - 1)) return; var tempFormats = $.extend(true, {}, self.conditionalRules); self.conditionalRules.clearRule(); var total = tempFormats.count(); var indexInFormats = total - 1 - selectedIdx; for (i = 0; i < total; i++) { if (isUp) { if (i === indexInFormats) self.conditionalRules.addRule(tempFormats.getRule(indexInFormats + 1)); else if (i === indexInFormats + 1) self.conditionalRules.addRule(tempFormats.getRule(indexInFormats)); else self.conditionalRules.addRule(tempFormats.getRule(i)); } else { if (i === indexInFormats) self.conditionalRules.addRule(tempFormats.getRule(indexInFormats - 1)); else if (i === indexInFormats - 1) self.conditionalRules.addRule(tempFormats.getRule(indexInFormats)); else self.conditionalRules.addRule(tempFormats.getRule(i)); } } self.update(); var newItems = self.host.querySelectorAll('.ruleListItem'); var newIdx = isUp ? selectedIdx - 1 : selectedIdx + 1; if (newItems[newIdx]) newItems[newIdx].classList.add('selected'); }; RuleList.prototype.bind = function (event, callback) { if (!this.eventsHandler[event]) this.eventsHandler[event] = []; this.eventsHandler[event].push(callback); }; RuleList.prototype.trigger = function (event, args) { var handlers = this.eventsHandler[event]; if (!handlers) return; for (var i = 0; i < handlers.length; i++) { handlers[i].call(this, event, args); } }; /* ================================================================ Sparkline Configuration & UI ================================================================ */ var sparklineConfigs = { 'PIESPARKLINE': [ { name: 'value', label: '范围/百分比', type: 'range', defaultValue: '@' }, { name: 'colors', label: '颜色(逗号分隔)', type: 'text', defaultValue: '' } ], 'LINESPARKLINE': [ { name: 'data', label: '数据', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'dataOrientation', label: '数据方向', type: 'select', defaultValue: '0', options: ['0', '1'], optionLabels: ['垂直', '水平'] }, { name: 'dateAxisData', label: '日期轴数据', type: 'range', defaultValue: '' }, { name: 'dateAxisOrientation', label: '日期轴方向', type: 'select', defaultValue: '0', options: ['0', '1'], optionLabels: ['垂直', '水平'] }, { name: 'setting.showMarkers', label: '显示标记', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showFirst', label: '显示首个点', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showLast', label: '显示最后一个点', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showHigh', label: '显示最高点', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showLow', label: '显示最低点', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showNegative', label: '显示负值', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.displayXAxis', label: '显示 X 轴', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.seriesColor', label: '序列颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.negativeColor', label: '负值颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.firstMarkerColor', label: '首个点颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.lastMarkerColor', label: '最后一个点颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.highMarkerColor', label: '最高点颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.lowMarkerColor', label: '最低点颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.markersColor', label: '标记颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.axisColor', label: '坐标轴颜色', type: 'text', defaultValue: '', group: 'setting' } ], 'COLUMNSPARKLINE': [ { name: 'data', label: '数据', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'dataOrientation', label: '数据方向', type: 'select', defaultValue: '0', options: ['0', '1'], optionLabels: ['垂直', '水平'] }, { name: 'dateAxisData', label: '日期轴数据', type: 'range', defaultValue: '' }, { name: 'dateAxisOrientation', label: '日期轴方向', type: 'select', defaultValue: '0', options: ['0', '1'], optionLabels: ['垂直', '水平'] }, { name: 'setting.showFirst', label: '显示首个点', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showLast', label: '显示最后一个点', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showHigh', label: '显示最高点', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showLow', label: '显示最低点', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showNegative', label: '显示负值', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.displayXAxis', label: '显示 X 轴', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.seriesColor', label: '序列颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.negativeColor', label: '负值颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.firstMarkerColor', label: '首个点颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.lastMarkerColor', label: '最后一个点颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.highMarkerColor', label: '最高点颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.lowMarkerColor', label: '最低点颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.axisColor', label: '坐标轴颜色', type: 'text', defaultValue: '', group: 'setting' } ], 'WINLOSSSPARKLINE': [ { name: 'data', label: '数据', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'dataOrientation', label: '数据方向', type: 'select', defaultValue: '0', options: ['0', '1'], optionLabels: ['垂直', '水平'] }, { name: 'dateAxisData', label: '日期轴数据', type: 'range', defaultValue: '' }, { name: 'dateAxisOrientation', label: '日期轴方向', type: 'select', defaultValue: '0', options: ['0', '1'], optionLabels: ['垂直', '水平'] }, { name: 'setting.showNegative', label: '显示负值', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.displayXAxis', label: '显示 X 轴', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.seriesColor', label: '序列颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.negativeColor', label: '负值颜色', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.axisColor', label: '坐标轴颜色', type: 'text', defaultValue: '', group: 'setting' } ], 'AREASPARKLINE': [ { name: 'points', label: '数据点', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'mini', label: 'Mini', type: 'text', defaultValue: '' }, { name: 'maxi', label: 'Maxi', type: 'text', defaultValue: '' }, { name: 'line1', label: 'Line1', type: 'text', defaultValue: '' }, { name: 'line2', label: 'Line2', type: 'text', defaultValue: '' }, { name: 'colorPositive', label: '正向颜色', type: 'text', defaultValue: '' }, { name: 'colorNegative', label: '负向颜色', type: 'text', defaultValue: '' } ], 'SCATTERSPARKLINE': [ { name: 'points1', label: '数据点 1', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'points2', label: '数据点 2', type: 'range', defaultValue: '' }, { name: 'minX', label: 'MinX', type: 'text', defaultValue: '' }, { name: 'maxX', label: 'MaxX', type: 'text', defaultValue: '' }, { name: 'minY', label: 'MinY', type: 'text', defaultValue: '' }, { name: 'maxY', label: 'MaxY', type: 'text', defaultValue: '' }, { name: 'hLine', label: 'HLine', type: 'text', defaultValue: '' }, { name: 'vLine', label: 'VLine', type: 'text', defaultValue: '' }, { name: 'tags', label: '标签', type: 'text', defaultValue: '' }, { name: 'drawSymbol', label: '绘制符号', type: 'checkbox', defaultValue: false }, { name: 'drawLines', label: '绘制连线', type: 'checkbox', defaultValue: false }, { name: 'color1', label: '颜色 1', type: 'text', defaultValue: '' }, { name: 'color2', label: '颜色 2', type: 'text', defaultValue: '' }, { name: 'dash', label: '虚线', type: 'checkbox', defaultValue: false } ], 'BULLETSPARKLINE': [ { name: 'measure', label: '测量值', type: 'range', defaultValue: '@' }, { name: 'target', label: '目标值', type: 'range', defaultValue: '' }, { name: 'maxi', label: 'Maxi', type: 'range', defaultValue: '' }, { name: 'good', label: '良好值', type: 'range', defaultValue: '' }, { name: 'bad', label: '较差值', type: 'range', defaultValue: '' }, { name: 'forecast', label: '预测值', type: 'range', defaultValue: '' }, { name: 'tickUnit', label: '刻度间隔', type: 'range', defaultValue: '' }, { name: 'colorScheme', label: '配色方案', type: 'text', defaultValue: '' }, { name: 'vertical', label: '垂直', type: 'checkbox', defaultValue: false }, { name: 'measureColor', label: '测量值颜色', type: 'text', defaultValue: '' }, { name: 'targetColor', label: '目标值颜色', type: 'text', defaultValue: '' }, { name: 'maxiColor', label: '最大值颜色', type: 'text', defaultValue: '' }, { name: 'goodColor', label: '良好值颜色', type: 'text', defaultValue: '' }, { name: 'badColor', label: '较差值颜色', type: 'text', defaultValue: '' }, { name: 'forecastColor', label: '预测值颜色', type: 'text', defaultValue: '' }, { name: 'allowMeasureOverMaxi', label: '允许超出最大值', type: 'checkbox', defaultValue: false }, { name: 'barSize', label: '条形大小', type: 'text', defaultValue: '' } ], 'SPREADSPARKLINE': [ { name: 'points', label: '数据点', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'showAverage', label: '显示平均值', type: 'checkbox', defaultValue: false }, { name: 'scaleStart', label: '缩放起点', type: 'text', defaultValue: '' }, { name: 'scaleEnd', label: '缩放终点', type: 'text', defaultValue: '' }, { name: 'style', label: '样式(1-6)', type: 'text', defaultValue: '1' }, { name: 'colorScheme', label: '配色方案', type: 'text', defaultValue: '' }, { name: 'vertical', label: '垂直', type: 'checkbox', defaultValue: false } ], 'STACKEDSPARKLINE': [ { name: 'points', label: '数据点', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'colorRange', label: '颜色范围', type: 'range', defaultValue: '' }, { name: 'labelRange', label: '标签范围', type: 'range', defaultValue: '' }, { name: 'maximum', label: '最大值', type: 'text', defaultValue: '' }, { name: 'targetRed', label: '红色目标', type: 'text', defaultValue: '' }, { name: 'targetGreen', label: '绿色目标', type: 'text', defaultValue: '' }, { name: 'targetBlue', label: '蓝色目标', type: 'text', defaultValue: '' }, { name: 'targetYellow', label: '黄色目标', type: 'text', defaultValue: '' }, { name: 'color', label: '颜色', type: 'text', defaultValue: '' }, { name: 'highlightPosition', label: '高亮位置', type: 'text', defaultValue: '' }, { name: 'vertical', label: '垂直', type: 'checkbox', defaultValue: false }, { name: 'textOrientation', label: '文本方向', type: 'text', defaultValue: '0' }, { name: 'textSize', label: '文本大小', type: 'text', defaultValue: '10' } ], 'HBARSPARKLINE': [ { name: 'value', label: '值', type: 'range', defaultValue: '@' }, { name: 'colorScheme', label: '配色方案', type: 'text', defaultValue: 'gray' }, { name: 'axisVisible', label: '显示坐标轴', type: 'checkbox', defaultValue: true }, { name: 'barHeight', label: '条形高度', type: 'text', defaultValue: '0.8' } ], 'VBARSPARKLINE': [ { name: 'value', label: '值', type: 'range', defaultValue: '@' }, { name: 'colorScheme', label: '配色方案', type: 'text', defaultValue: 'gray' }, { name: 'axisVisible', label: '显示坐标轴', type: 'checkbox', defaultValue: true }, { name: 'barWidth', label: '条形宽度', type: 'text', defaultValue: '0.8' } ], 'VARISPARKLINE': [ { name: 'variance', label: '差异值', type: 'range', defaultValue: '@' }, { name: 'reference', label: '参考值', type: 'range', defaultValue: '' }, { name: 'mini', label: 'Mini', type: 'range', defaultValue: '' }, { name: 'maxi', label: 'Maxi', type: 'range', defaultValue: '' }, { name: 'mark', label: 'Mark', type: 'range', defaultValue: '' }, { name: 'tickUnit', label: '刻度间隔', type: 'range', defaultValue: '' }, { name: 'legend', label: '图例', type: 'checkbox', defaultValue: false }, { name: 'colorPositive', label: '正向颜色', type: 'text', defaultValue: '' }, { name: 'colorNegative', label: '负向颜色', type: 'text', defaultValue: '' }, { name: 'vertical', label: '垂直', type: 'checkbox', defaultValue: false } ], 'LOLLIPOPVARISPARKLINE': [ { name: 'plannedValue', label: '计划值', type: 'range', defaultValue: '' }, { name: 'actualValue', label: '实际值', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'index', label: '索引', type: 'range', defaultValue: '@' }, { name: 'absolute', label: '绝对值', type: 'checkbox', defaultValue: false }, { name: 'reference', label: '参考值', type: 'range', defaultValue: '' }, { name: 'mini', label: 'Mini', type: 'range', defaultValue: '' }, { name: 'maxi', label: 'Maxi', type: 'range', defaultValue: '' }, { name: 'tickUnit', label: '刻度间隔', type: 'range', defaultValue: '' }, { name: 'legend', label: '图例', type: 'checkbox', defaultValue: false }, { name: 'colorPositive', label: '正向颜色', type: 'text', defaultValue: '' }, { name: 'colorNegative', label: '负向颜色', type: 'text', defaultValue: '' }, { name: 'lollipopHeaderColor', label: '标题颜色', type: 'text', defaultValue: '' }, { name: 'vertical', label: '垂直', type: 'checkbox', defaultValue: false } ], 'BOXPLOTSPARKLINE': [ { name: 'points', label: '数据点', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'boxPlotClass', label: '箱线图类型', type: 'select', defaultValue: '5ns', options: ['5ns', '7ns', 'tukey', 'bowley', 'sigma3'] }, { name: 'showAverage', label: '显示平均值', type: 'checkbox', defaultValue: false }, { name: 'scaleStart', label: '缩放起点', type: 'text', defaultValue: '' }, { name: 'scaleEnd', label: '缩放终点', type: 'text', defaultValue: '' }, { name: 'acceptableStart', label: '可接受起点', type: 'text', defaultValue: '' }, { name: 'acceptableEnd', label: '可接受终点', type: 'text', defaultValue: '' }, { name: 'colorScheme', label: '配色方案', type: 'text', defaultValue: '' }, { name: 'style', label: '样式(0 或 1)', type: 'text', defaultValue: '0' }, { name: 'vertical', label: '垂直', type: 'checkbox', defaultValue: false } ], 'CASCADESPARKLINE': [ { name: 'pointsRange', label: '数据点范围', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'pointIndex', label: '数据点索引', type: 'range', defaultValue: '@' }, { name: 'labelsRange', label: '标签范围', type: 'range', defaultValue: '' }, { name: 'minimum', label: '最小值', type: 'text', defaultValue: '' }, { name: 'maximum', label: '最大值', type: 'text', defaultValue: '' }, { name: 'colorPositive', label: '正向颜色', type: 'text', defaultValue: '' }, { name: 'colorNegative', label: '负向颜色', type: 'text', defaultValue: '' }, { name: 'vertical', label: '垂直', type: 'checkbox', defaultValue: false }, { name: 'itemTypeRange', label: '项目类型范围', type: 'range', defaultValue: '' }, { name: 'colorTotal', label: '总计颜色', type: 'text', defaultValue: '' } ], 'PARETOSPARKLINE': [ { name: 'points', label: '数据点', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'pointIndex', label: '数据点索引', type: 'range', defaultValue: '@' }, { name: 'colorRange', label: '颜色范围', type: 'range', defaultValue: '' }, { name: 'target', label: '目标值', type: 'range', defaultValue: '' }, { name: 'target2', label: '目标值 2', type: 'range', defaultValue: '' }, { name: 'highlightPosition', label: '高亮位置', type: 'range', defaultValue: '' }, { name: 'label', label: '标签(0:无,1:数值,2:百分比)', type: 'text', defaultValue: '0' }, { name: 'vertical', label: '垂直', type: 'checkbox', defaultValue: false }, { name: 'targetColor', label: '目标值颜色', type: 'text', defaultValue: '' }, { name: 'target2Color', label: '目标值 2 颜色', type: 'text', defaultValue: '' }, { name: 'labelColor', label: '标签颜色', type: 'text', defaultValue: '' }, { name: 'barSize', label: '条形大小', type: 'text', defaultValue: '' } ], 'MONTHSPARKLINE': [ { name: 'year', label: '年份', type: 'text', defaultValue: new Date().getFullYear().toString() }, { name: 'month', label: '月份', type: 'text', defaultValue: '1' }, { name: 'dataRange', label: '数据范围', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'emptyColor', label: '空白颜色', type: 'text', defaultValue: '' }, { name: 'startColor', label: '起始颜色', type: 'text', defaultValue: '' }, { name: 'middleColor', label: '中间颜色', type: 'text', defaultValue: '' }, { name: 'endColor', label: '结束颜色', type: 'text', defaultValue: '' }, { name: 'colorRange', label: '颜色范围', type: 'range', defaultValue: '' } ], 'YEARSPARKLINE': [ { name: 'year', label: '年份', type: 'text', defaultValue: new Date().getFullYear().toString() }, { name: 'dataRange', label: '数据范围', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'emptyColor', label: '空白颜色', type: 'text', defaultValue: '' }, { name: 'startColor', label: '起始颜色', type: 'text', defaultValue: '' }, { name: 'middleColor', label: '中间颜色', type: 'text', defaultValue: '' }, { name: 'endColor', label: '结束颜色', type: 'text', defaultValue: '' }, { name: 'colorRange', label: '颜色范围', type: 'range', defaultValue: '' } ], 'HISTOGRAMSPARKLINE': [ { name: 'dataRange', label: '数据范围', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'continuous', label: '连续', type: 'checkbox', defaultValue: false }, { name: 'paintLabel', label: '绘制标签', type: 'checkbox', defaultValue: false }, { name: 'scale', label: '缩放', type: 'text', defaultValue: '' }, { name: 'barWidth', label: '条形宽度', type: 'text', defaultValue: '' }, { name: 'barColor', label: '条形颜色', type: 'text', defaultValue: '' }, { name: 'labelFontStyle', label: '标签字体样式', type: 'text', defaultValue: '' }, { name: 'labelColor', label: '标签颜色', type: 'text', defaultValue: '' }, { name: 'edgeColor', label: '边缘颜色', type: 'text', defaultValue: '' } ], 'GAUGEKPISPARKLINE': [ { name: 'targetValue', label: '目标值', type: 'range', defaultValue: '' }, { name: 'currentValue', label: '当前值', type: 'range', defaultValue: '@' }, { name: 'minValue', label: '最小值', type: 'range', defaultValue: '0' }, { name: 'maxValue', label: '最大值', type: 'range', defaultValue: '100' }, { name: 'showLabel', label: '显示标签', type: 'checkbox', defaultValue: false }, { name: 'targetValueLabel', label: '目标标签', type: 'text', defaultValue: '' }, { name: 'currentValueLabel', label: '当前值标签', type: 'text', defaultValue: '' }, { name: 'minValueLabel', label: '最小值标签', type: 'text', defaultValue: '' }, { name: 'maxValueLabel', label: '最大值标签', type: 'text', defaultValue: '' }, { name: 'minAngle', label: '最小角度', type: 'text', defaultValue: '-90' }, { name: 'maxAngle', label: '最大角度', type: 'text', defaultValue: '90' }, { name: 'radiusRatio', label: '半径比例', type: 'text', defaultValue: '' }, { name: 'gaugeType', label: '仪表类型(0、1、2)', type: 'text', defaultValue: '' } ], 'IMAGE': [ { name: 'source', label: '来源(URL)', type: 'range', defaultValue: '@' }, { name: 'alt_text', label: '替代文本', type: 'text', defaultValue: '' }, { name: 'sizing', label: '缩放方式(0-3)', type: 'text', defaultValue: '0' }, { name: 'height', label: '高度', type: 'text', defaultValue: '' }, { name: 'width', label: '宽度', type: 'text', defaultValue: '' }, { name: 'clipX', label: '裁剪 X', type: 'text', defaultValue: '' }, { name: 'clipY', label: '裁剪 Y', type: 'text', defaultValue: '' }, { name: 'clipHeight', label: '裁剪高度', type: 'text', defaultValue: '' }, { name: 'clipWidth', label: '裁剪宽度', type: 'text', defaultValue: '' }, { name: 'vAlign', label: '垂直对齐(0-2)', type: 'text', defaultValue: '1' }, { name: 'hAlign', label: '水平对齐(0-2)', type: 'text', defaultValue: '1' } ], 'RANGEBLOCKSPARKLINE': [ { name: 'template_range', label: '模板范围', type: 'range', defaultValue: '' }, { name: 'data_expression', label: '数据表达式(JSON)', type: 'text', defaultValue: '' } ] }; var sparklineFormulaTextBoxes = {}; function buildSparklineOptionsUI(sparklineType, spread) { var container = document.getElementById('sparklineOptions'); for (var key in sparklineFormulaTextBoxes) { if (sparklineFormulaTextBoxes.hasOwnProperty(key) && sparklineFormulaTextBoxes[key].destroy) { sparklineFormulaTextBoxes[key].destroy(); } } sparklineFormulaTextBoxes = {}; container.innerHTML = ''; var config = sparklineConfigs[sparklineType]; if (!config) return; var table = document.createElement('table'); table.style.width = '100%'; var lastGroup = null; for (var i = 0; i < config.length; i++) { var param = config[i]; if (param.group && param.group !== lastGroup) { var trH = document.createElement('tr'); trH.className = 'group-header'; var tdH = document.createElement('td'); tdH.colSpan = 2; tdH.innerText = '迷你图设置'; trH.appendChild(tdH); table.appendChild(trH); lastGroup = param.group; } var tr = document.createElement('tr'); var tdLabel = document.createElement('td'); tdLabel.className = 'opt-label'; tdLabel.innerText = param.label; var tdInput = document.createElement('td'); if (param.type === 'checkbox') { var cb = document.createElement('input'); cb.type = 'checkbox'; cb.id = 'sp_' + param.name; cb.checked = !!param.defaultValue; tdInput.appendChild(cb); } else if (param.type === 'select') { var sel = document.createElement('select'); sel.id = 'sp_' + param.name; sel.className = 'ruleOptionsInput'; for (var j = 0; j < param.options.length; j++) { var opt = document.createElement('option'); opt.value = param.options[j]; opt.text = param.optionLabels ? param.optionLabels[j] : param.options[j]; if (param.options[j] === param.defaultValue) opt.selected = true; sel.appendChild(opt); } tdInput.appendChild(sel); } else if (param.type === 'range') { var div = document.createElement('div'); div.id = 'sp_' + param.name; div.className = 'ruleOptionsFormulaTextBox'; tdInput.appendChild(div); } else { var input = document.createElement('input'); input.type = 'text'; input.id = 'sp_' + param.name; input.className = 'ruleOptionsInput'; input.value = param.defaultValue || ''; tdInput.appendChild(input); } tr.appendChild(tdLabel); tr.appendChild(tdInput); table.appendChild(tr); } container.appendChild(table); for (var k = 0; k < config.length; k++) { var p = config[k]; if (p.type === 'range') { var el = document.getElementById('sp_' + p.name); if (el && spread) { var ftb = new spreadNS.FormulaTextBox.FormulaTextBox(el, { absoluteReference: true, needSheetName: false, rangeSelectMode: true }); ftb.workbook(spread); if (p.defaultValue) ftb.text(p.defaultValue); sparklineFormulaTextBoxes[p.name] = ftb; } } } } var sparklineStringFields = [ 'data', 'points', 'points1', 'points2', 'pointsRange', 'dataRange', 'plannedValue', 'actualValue', 'colorRange', 'labelRange', 'labelsRange', 'itemTypeRange', 'template_range', 'data_expression', 'tags', 'colors', 'colorScheme', 'boxPlotClass', 'source', 'alt_text', 'labelFontStyle', 'dateAxisData', 'value', 'targetValueLabel', 'currentValueLabel', 'minValueLabel', 'maxValueLabel' ]; function collectSparklineOptions(sparklineType) { var config = sparklineConfigs[sparklineType]; if (!config) return {}; var options = {}; var settingOptions = {}; var hasSetting = false; for (var i = 0; i < config.length; i++) { var param = config[i]; var val; if (param.type === 'checkbox') { var el = document.getElementById('sp_' + param.name); if (!el) continue; val = el.checked; } else if (param.type === 'range') { var ftb = sparklineFormulaTextBoxes[param.name]; if (!ftb) continue; val = ftb.text(); } else { var el2 = document.getElementById('sp_' + param.name); if (!el2) continue; val = el2.value; } if (param.group === 'setting') { var settingKey = param.name.replace('setting.', ''); if (param.type === 'checkbox') { if (val) { settingOptions[settingKey] = val; hasSetting = true; } } else if (val !== '' && val !== null && val !== undefined) { settingOptions[settingKey] = val; hasSetting = true; } continue; } if (param.type !== 'checkbox' && (val === '' || val === null || val === undefined)) continue; if (param.type === 'checkbox' && !val) continue; if (param.name === 'colors' && val) { var colorArr = val.split(',').map(function (c) { return c.trim(); }).filter(function (c) { return c; }); if (colorArr.length > 0) options[param.name] = colorArr; continue; } if (param.type === 'text' && sparklineStringFields.indexOf(param.name) === -1 && val !== '@' && val !== '$CF_RANGE$') { if (typeof val === 'string' && val.charAt(0) !== '#' && !isColorName(val)) { var numVal = parseFloat(val); if (!isNaN(numVal) && isFinite(numVal)) val = numVal; } } if (param.type === 'select' && val !== '') { var selNum = parseFloat(val); if (!isNaN(selNum) && isFinite(selNum)) val = selNum; } options[param.name] = val; } if (hasSetting) { var setting = new GC.Spread.Sheets.Sparklines.SparklineSetting(); for (var key in settingOptions) { if (settingOptions.hasOwnProperty(key)) setting.options[key] = settingOptions[key]; } options['setting'] = setting; } return options; } function isColorName(val) { var colors = ['red', 'green', 'blue', 'yellow', 'orange', 'purple', 'pink', 'black', 'white', 'gray', 'grey', 'cyan', 'magenta', 'brown', 'lime', 'navy', 'teal', 'maroon', 'olive', 'aqua', 'silver', 'fuchsia']; return colors.indexOf(val.toLowerCase()) >= 0; } function populateSparklineOptions(sparklineType, sparklineOptions) { var config = sparklineConfigs[sparklineType]; if (!config || !sparklineOptions) return; for (var i = 0; i < config.length; i++) { var param = config[i]; var val; if (param.group === 'setting') { var settingObj = sparklineOptions['setting']; if (settingObj) { var settingKey = param.name.replace('setting.', ''); var opts = settingObj.options || settingObj; val = opts[settingKey]; } else { val = undefined; } } else if (param.name === 'colors' && Array.isArray(sparklineOptions[param.name])) { val = sparklineOptions[param.name].join(', '); } else { val = sparklineOptions[param.name]; } if (val === undefined || val === null) continue; if (param.type === 'checkbox') { var el = document.getElementById('sp_' + param.name); if (el) el.checked = !!val; } else if (param.type === 'range') { var ftb = sparklineFormulaTextBoxes[param.name]; if (ftb) ftb.text(val + ''); } else { var el2 = document.getElementById('sp_' + param.name); if (el2) el2.value = val; } } }
<!doctype html> <html style="height:100%;font-size:14px;"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="spreadjs culture" content="zh-cn" /> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <script src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets/dist/gc.spread.sheets.all.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets-resources-zh/dist/gc.spread.sheets.resources.zh.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/spread/source/js/license.js" type="text/javascript"></script> <script src="$DEMOROOT$/spread/source/js/jquery-1.8.2.js" type="text/javascript"></script> <script src="app.js" type="text/javascript"></script> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <div class="sample-tutorial"> <div id="ss" class="sample-spreadsheets"></div> <div id="panelToggleBtn" class="panel-toggle-btn" title="切换面板"> <span class="panel-toggle-icon"><span></span></span> </div> <div class="options-container"> <div class="panel-header"> <span class="panel-title">迷你图规则</span> <span id="panelCollapseBtn" class="panel-collapse-btn" title="收起">&times;</span> </div> <div class="panel-section"> <div class="panel-section-title"> <span>规则列表</span> <div class="rule-order-btns"> <button id="format-rule-move-up" class="btn-icon" title="上移"> <span class="icon-up"></span> </button> <button id="format-rule-move-down" class="btn-icon" title="下移"> <span class="icon-down"></span> </button> </div> </div> <div id="listContainer" class="rule-list-container"> <div id="listControlBar" class="rule-list-header"> <div class="rl-col rl-col-type">类型</div> <div class="rl-col rl-col-range">范围</div> <div class="rl-col rl-col-stop">满足即停止</div> <div class="rl-col rl-col-action"></div> </div> <div id="listItemContainer" class="rule-list-body"> </div> </div> </div> <div class="panel-section panel-section-editor"> <div class="panel-section-title"> <span>规则选项设置</span> <div class="panel-editor-mode"> <span id="panelModeLabel">添加规则</span> <button id="btnCancelUpdate" class="btn-link" style="display:none;">取消</button> </div> </div> <div class="panel-section-hint">选择上方规则可编辑其选项,点击规则范围可直接修改范围。</div> <div class="panel-card"> <div class="form-group form-group-inline"> <label class="form-label">迷你图类型</label> <select id="sparklineType" class="form-select"> <option value="PIESPARKLINE">饼图</option> <option value="LINESPARKLINE">折线</option> <option value="COLUMNSPARKLINE">柱形</option> <option value="WINLOSSSPARKLINE">WinLoss</option> <option value="AREASPARKLINE">面积图</option> <option value="SCATTERSPARKLINE">散点图</option> <option value="BULLETSPARKLINE">子弹图</option> <option value="SPREADSPARKLINE">Spread</option> <option value="STACKEDSPARKLINE">Stacked</option> <option value="HBARSPARKLINE">HBar</option> <option value="VBARSPARKLINE">VBar</option> <option value="VARISPARKLINE">差异图</option> <option value="LOLLIPOPVARISPARKLINE">棒棒糖差异图</option> <option value="BOXPLOTSPARKLINE">BoxPlot</option> <option value="CASCADESPARKLINE">Cascade</option> <option value="PARETOSPARKLINE">Pareto</option> <option value="MONTHSPARKLINE">月份</option> <option value="YEARSPARKLINE">年份</option> <option value="HISTOGRAMSPARKLINE">直方图</option> <option value="GAUGEKPISPARKLINE">GaugeKPI</option> <option value="IMAGE">图片</option> <option value="RANGEBLOCKSPARKLINE">RangeBlock</option> </select> </div> <div id="sparklineOptions" class="sparkline-options-area"></div> <label class="form-checkbox"> <input type="checkbox" id="showSparklineOnly"/> <span>仅显示迷你图</span> </label> <div class="panel-actions"> <button id="btnAddRule" class="btn btn-primary">添加规则</button> <button id="btnUpdateRule" class="btn btn-primary" style="display:none;">更新规则</button> </div> </div> </div> </div> </div> </body> </html>
body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } .sample-tutorial { position: relative; height: 100%; overflow: hidden; } .sample-spreadsheets { width: 100%; height: 100%; overflow: hidden; } .options-container { position: absolute; top: 0; right: 0; width: 480px; overflow-y: auto; overflow-x: hidden; height: 100%; box-sizing: border-box; background: #fff; z-index: 1000; box-shadow: -2px 0 12px rgba(0,0,0,0.10); transition: transform 0.3s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 13px; color: #333; } .options-container.collapsed { transform: translateX(100%); } .panel-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid #e8e8e8; background: #fff; position: sticky; top: 0; z-index: 2; } .panel-title { font-weight: 600; font-size: 15px; color: #1a1a1a; } .panel-collapse-btn { cursor: pointer; font-size: 20px; color: #999; width: 28px; height: 28px; line-height: 28px; text-align: center; border-radius: 6px; user-select: none; transition: all 0.15s; } .panel-collapse-btn:hover { background: #f0f0f0; color: #333; } .panel-toggle-btn { position: absolute; top: 8px; right: 8px; z-index: 999; width: 36px; height: 36px; background: #4a7a00; border-radius: 8px; cursor: pointer; display: none; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(74,122,0,0.3); transition: background 0.2s; } .panel-toggle-btn:hover { background: #3d6b00; } .panel-toggle-btn.visible { display: flex; } .panel-toggle-icon { width: 18px; height: 14px; display: flex; flex-direction: column; justify-content: space-between; } .panel-toggle-icon::before, .panel-toggle-icon::after, .panel-toggle-icon span { content: ''; display: block; height: 2px; background: #fff; border-radius: 1px; } .panel-section { padding: 12px 16px; border-bottom: 1px solid #f0f0f0; } .panel-section:last-child { border-bottom: none; } .panel-section-title { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; font-weight: 600; font-size: 13px; color: #555; text-transform: uppercase; letter-spacing: 0.3px; } .panel-editor-mode { display: flex; align-items: center; gap: 10px; } .panel-section-hint { margin-bottom: 10px; font-size: 12px; line-height: 1.4; color: #7a7a7a; } .panel-card { background: #fafafa; border: 1px solid #e8e8e8; border-radius: 8px; padding: 14px; } .form-group { margin-bottom: 12px; } .form-group-inline { display: flex; align-items: center; } .form-group-inline .form-label { display: inline-block; width: 140px; min-width: 140px; margin-bottom: 0; padding: 0 4px; box-sizing: border-box; } .form-group-inline .form-select { width: 180px; margin-left: 10px; } .form-label { display: block; font-size: 12px; font-weight: 500; color: #666; margin-bottom: 4px; } .form-select { width: 100%; height: 32px; padding: 0 8px; border: 1px solid #d0d0d0; border-radius: 6px; background: #fff; font-size: 13px; color: #333; outline: none; box-sizing: border-box; transition: border-color 0.15s; cursor: pointer; } .form-select:focus { border-color: #4a7a00; box-shadow: 0 0 0 2px rgba(74,122,0,0.15); } .form-input { width: 100%; height: 30px; padding: 0 8px; border: 1px solid #d0d0d0; border-radius: 6px; background: #fff; font-size: 13px; color: #333; outline: none; box-sizing: border-box; transition: border-color 0.15s; } .form-input:focus { border-color: #4a7a00; box-shadow: 0 0 0 2px rgba(74,122,0,0.15); } .form-checkbox { display: flex; align-items: center; gap: 6px; cursor: pointer; margin: 8px 0; font-size: 13px; color: #444; } .form-checkbox input[type="checkbox"] { width: 16px; height: 16px; accent-color: #4a7a00; cursor: pointer; } .sparkline-options-area { max-height: 220px; overflow-y: auto; margin: 8px 0; } .sparkline-options-area table { width: 100%; border-collapse: collapse; } .sparkline-options-area td { padding: 3px 4px; vertical-align: middle; font-size: 12px; } .sparkline-options-area .opt-label { width: 140px; color: #666; white-space: nowrap; } .sparkline-options-area .group-header td { font-weight: 600; color: #4a7a00; padding-top: 8px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.3px; } .ruleOptionsInput { border: 1px solid #d0d0d0; height: 28px; line-height: 28px; padding: 0 6px; border-radius: 4px; font-size: 12px; box-sizing: border-box; outline: none; width: 180px; transition: border-color 0.15s; } .ruleOptionsInput:focus { border-color: #4a7a00; } .ruleOptionsFormulaTextBox { display: inline-block; border: 1px solid #d0d0d0; width: 180px; height: 28px; vertical-align: middle; border-radius: 4px; box-sizing: border-box; overflow: hidden; } .ruleOptionsFormulaTextBox > table { width: 100% !important; table-layout: fixed; } .ruleOptionsFormulaTextBox div[gcuielement="gcAttachedFormulaTextBox"] { width: 100% !important; } .ruleOptionsFormulaTextBox td:first-child > div { width: calc(100% - 2px) !important; } .panel-actions { margin-top: 12px; display: flex; gap: 8px; } .btn { height: 34px; padding: 0 20px; border: none; border-radius: 6px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.15s; } .btn-primary { background: #4a7a00; color: #fff; } .btn-primary:hover { background: #3d6b00; } .btn-link { background: none; border: none; color: #4a7a00; cursor: pointer; font-size: 12px; padding: 0; font-weight: 500; } .btn-link:hover { text-decoration: underline; } .btn-icon { width: 28px; height: 28px; padding: 0; border: 1px solid #d0d0d0; border-radius: 6px; background: #fff; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: all 0.15s; } .btn-icon:hover { background: #f0f0f0; border-color: #bbb; } .rule-order-btns { display: flex; gap: 4px; } .icon-up, .icon-down { display: block; width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; } .icon-up { border-bottom: 6px solid #666; } .icon-down { border-top: 6px solid #666; } .rule-list-container { border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; background: #fff; } .rule-list-header { display: flex; background: #f5f5f5; border-bottom: 1px solid #e0e0e0; font-size: 11px; font-weight: 600; color: #777; text-transform: uppercase; letter-spacing: 0.3px; } .rule-list-body { max-height: 240px; overflow-y: auto; } .rl-col { padding: 8px 10px; box-sizing: border-box; } .rl-col-type { width: 40%; } .rl-col-range { width: 28%; } .rl-col-stop { width: 18%; text-align: center; } .rl-col-action { width: 14%; text-align: center; } .ruleListItem { display: flex; align-items: center; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background 0.1s; } .ruleListItem:last-child { border-bottom: none; } .ruleListItem:hover { background: #f8fbf0; } .ruleListItem.selected { background: #eef5e0; } .ruleDescription { width: 40%; padding: 6px 10px; font-size: 12px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; box-sizing: border-box; } .ruleFormulas { width: 28%; padding: 6px 6px; font-size: 11px; color: #4a7a00; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; box-sizing: border-box; cursor: text; border-radius: 4px; transition: background 0.15s, color 0.15s; } .ruleFormulas:hover { background: #f1f7e6; } .ruleFormulas.editing { padding: 4px 6px; background: #eef5e0; } .ruleStopIfTrue { width: 18%; text-align: center; padding: 6px 4px; box-sizing: border-box; } .ruleStopIfTrue input[type="checkbox"] { accent-color: #4a7a00; } .ruleRemove { width: 14%; text-align: center; padding: 6px 4px; box-sizing: border-box; } .removeRuleBtn { width: 24px; height: 24px; border: none; background: none; color: #ccc; font-size: 16px; cursor: pointer; border-radius: 4px; line-height: 24px; transition: all 0.15s; } .removeRuleBtn:hover { background: #fee; color: #e55; } .ruleRangeInput { width: 100%; height: 28px; padding: 0 8px; border: 1px solid #8fb44a; border-radius: 4px; background: #fff; color: #333; font-size: 11px; box-sizing: border-box; outline: none; } .ruleRangeInput:focus { box-shadow: 0 0 0 2px rgba(74,122,0,0.15); } .sparkline-badge { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; margin-right: 6px; vertical-align: middle; color: #fff; } .badge-bullet { background: #4a7a00; } .badge-pie { background: #c8a000; } .badge-hbar { background: #6fa300; } .badge-line { background: #8bc34a; } .badge-column { background: #b5cc18; color: #333; } .badge-winloss { background: #7030A0; } .badge-lollipop { background: #8b0000; } .badge-default { background: #999; }