This commit is contained in:
李鹏宇
2026-06-10 17:22:12 +08:00
parent 6cc892cc66
commit b0fde094bd
2 changed files with 81 additions and 58 deletions
@@ -55,7 +55,7 @@
onSelected="() => this.onNavBarDropdownItemSelection(menu)"
>
<span class="sh_submenu_icon_wrap" t-attf-style="margin-left: {{ menu.level * 15 }}px;position: relative;display:inline-block;vertical-align:middle;">
<img t-attf-src="/web/image?model=ir.ui.menu&amp;field=web_icon_data&amp;id={{root_app_id}}" class="sh_submenu_app_icon" style="width:16px;height:16px;border-radius:3px;vertical-align:middle;filter: brightness(0) saturate(100%) invert(1);"/>
<img t-attf-src="/web/image?model=ir.ui.menu&amp;field=web_icon_data&amp;id={{root_app_id}}" class="sh_submenu_app_icon" style="width:16px;height:16px;border-radius:3px;vertical-align:middle;filter: brightness(0) saturate(100%) invert(1);margin-right:8px;"/>
<t t-if="menu_badge_count != 0">
<span class="menu-badge">
<t t-esc="menu_badge_count"/>
@@ -73,7 +73,7 @@
<div class="dropdown-header o_menu_header_lvl_1" >
<a class="sh_dropdown_toggle dropdown-item dropdown-toggle" t-on-click.stop="OnClickDropdown">
<span class="sh_submenu_icon_wrap" t-attf-style="margin-left: {{ menu.level * 15 }}px;position: relative;display:inline-block;vertical-align:middle;">
<img t-attf-src="/web/image?model=ir.ui.menu&amp;field=web_icon_data&amp;id={{root_app_id}}" class="sh_submenu_app_icon" style="width:16px;height:16px;border-radius:3px;vertical-align:middle;filter: brightness(0) saturate(100%) invert(1);"/>
<img t-attf-src="/web/image?model=ir.ui.menu&amp;field=web_icon_data&amp;id={{root_app_id}}" class="sh_submenu_app_icon" style="width:16px;height:16px;border-radius:3px;vertical-align:middle;filter: brightness(0) saturate(100%) invert(1);margin-right:8px;"/>
<t t-if="menu_badge_count != 0">
<span class="menu-badge">
<t t-esc="menu_badge_count"/>
@@ -103,7 +103,7 @@
onSelected="() => this.onNavBarDropdownItemSelection(children)"
>
<span class="sh_submenu_icon_wrap" t-attf-style="margin-left: {{ children.level * 15 }}px;position: relative;display:inline-block;vertical-align:middle;">
<img t-attf-src="/web/image?model=ir.ui.menu&amp;field=web_icon_data&amp;id={{root_app_id}}" class="sh_submenu_app_icon" style="width:16px;height:16px;border-radius:3px;vertical-align:middle;filter: brightness(0) saturate(100%) invert(1);"/>
<img t-attf-src="/web/image?model=ir.ui.menu&amp;field=web_icon_data&amp;id={{root_app_id}}" class="sh_submenu_app_icon" style="width:16px;height:16px;border-radius:3px;vertical-align:middle;filter: brightness(0) saturate(100%) invert(1);margin-right:8px;"/>
<t t-if="children_menu_badge_count != 0">
<span class="menu-badge">
<t t-esc="children_menu_badge_count"/>
@@ -43,6 +43,8 @@ body{background:var(--bg);color:var(--fg)}
.chart-ctrls label{font-size:0.7rem;color:var(--fg4);white-space:nowrap}
.chart-ctrls select,.chart-ctrls input{padding:4px 8px;background:var(--bg2);border:1px solid var(--bd);border-radius:6px;color:var(--fg);font-size:0.75rem;outline:none;font-family:inherit;cursor:pointer}
.chart-ctrls select option{background:var(--bg2);color:var(--fg)}
.chart-type-btn{padding:4px 8px;font-size:0.85rem;min-width:32px;justify-content:center}
.chart-type-btn.act{background:rgba(127,119,221,0.2);border-color:var(--hl);color:var(--hl2)}
.report-desc{padding:4px 20px 6px;font-size:0.78rem;color:var(--fg3);display:flex;gap:12px;flex-wrap:wrap;line-height:1.6}
.report-desc{padding:4px 20px 8px;font-size:0.72rem;color:var(--fg4);display:flex;gap:8px;flex-wrap:wrap}
@@ -209,7 +211,23 @@ tr.ch.h{display:none}
<button class="btn" onclick="togSide()" id="btnCfg">⚙️ 列配置</button>
<span class="cnt" id="recCnt"></span>
</div>
<div class="tb-r">
<div class="tb-r" id="tbRight" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
<div id="chartCtrls" style="display:none;gap:6px;align-items:center;flex-wrap:wrap">
<select id="qcDimSel" onchange="updateQuickChart()" title="分组字段" style="padding:4px 8px;background:var(--bg2);border:1px solid var(--bd);border-radius:6px;color:var(--fg);font-size:0.75rem;cursor:pointer;font-family:inherit;outline:none">
<option value="">分组字段</option>
</select>
<select id="qcValSel" onchange="updateQuickChart()" title="数值字段" style="padding:4px 8px;background:var(--bg2);border:1px solid var(--bd);border-radius:6px;color:var(--fg);font-size:0.75rem;cursor:pointer;font-family:inherit;outline:none">
<option value="">数值字段</option>
<option value="__count__">计数</option>
</select>
<div class="chart-type-btns" style="display:flex;gap:3px">
<button class="btn chart-type-btn" onclick="setChartType('bar')" id="btnBar" title="柱状图">📊</button>
<button class="btn chart-type-btn" onclick="setChartType('pie')" id="btnPie" title="饼图">🥧</button>
<button class="btn chart-type-btn" onclick="setChartType('doughnut')" id="btnDoughnut" title="环形图">🍩</button>
<button class="btn chart-type-btn" onclick="setChartType('line')" id="btnLine" title="折线图">📈</button>
<button class="btn chart-type-btn" onclick="setChartType('polarArea')" id="btnPolar" title="玫瑰图">🌹</button>
</div>
</div>
<button class="btn-excel" onclick="expXls()">⬇ 导出 Excel</button>
</div>
</div>
@@ -278,6 +296,7 @@ let grpFields=REPORT_CONFIG.groupBy.map(g=>({...g,active:true}));
let grpExp={};
let fzCount=0;
let dark=true;
let qcState={dim:'',val:'',type:'bar'};
function init(){
$('rptTitle').textContent = REPORT_CONFIG.title || '报表';
@@ -286,6 +305,7 @@ function init(){
initGrpExp();
buildColCheck();
buildGrpChips();
initChartCtrls();
buildChartsUI();
r();
}
@@ -297,7 +317,7 @@ function r(){
const dd=activeGrps.length>0?grpData(fd,activeGrps):fd;
renderT(dd,fd);
renderSum(fd);
renderCharts(fd);
renderCharts();
}
function $(id){return document.getElementById(id)}
function rf(){r()}
@@ -311,41 +331,55 @@ function chartColors(){return dark?CHART_COLORS_DARK:CHART_COLORS_LIGHT}
function chartGridColor(){return dark?'rgba(255,255,255,0.04)':'rgba(0,0,0,0.05)'}
function chartTextColor(){return dark?'rgba(232,230,227,0.5)':'rgba(42,40,38,0.5)'}
function initChartCtrls(){
const charts=REPORT_CONFIG.charts||[];
const ctrls=$('chartCtrls');
if(charts.length){if(ctrls)ctrls.style.display='none';return}
const numCols=COLS.filter(c=>{
const vals=data.map(r=>parseFloat(r[c.k]));
return vals.some(v=>!isNaN(v));
});
if(!numCols.length){if(ctrls)ctrls.style.display='none';return}
if(!ctrls)return;
ctrls.style.display='flex';
const dimSel=$('qcDimSel');
const valSel=$('qcValSel');
if(dimSel){
dimSel.innerHTML='<option value="">分组字段</option>'+COLS.map(c=>`<option value="${c.k}">${c.l}</option>`).join('');
dimSel.value=qcState.dim;
}
if(valSel){
valSel.innerHTML='<option value="">数值字段</option><option value="__count__">计数</option>'+numCols.map(c=>`<option value="${c.k}">${c.l}</option>`).join('');
valSel.value=qcState.val;
}
updateChartTypeBtns();
}
function updateChartTypeBtns(){
['bar','pie','doughnut','line','polarArea'].forEach(t=>{
const btn=$(`btn${t==='bar'?'Bar':t==='pie'?'Pie':t==='doughnut'?'Doughnut':t==='line'?'Line':'Polar'}`);
if(btn)btn.classList.toggle('act',qcState.type===t);
});
}
function setChartType(type){
qcState.type=type;
updateChartTypeBtns();
renderCharts();
}
function updateQuickChart(){
qcState.dim=$('qcDimSel')?$('qcDimSel').value:'';
qcState.val=$('qcValSel')?$('qcValSel').value:'';
renderCharts();
}
function buildChartsUI(){
const charts=REPORT_CONFIG.charts||[];
const cols=COLS;
if(!charts.length){
// No pre-configured charts, show default quick-select
const numCols=cols.filter(c=>{
const vals=data.map(r=>parseFloat(r[c.k]));
return vals.some(v=>!isNaN(v));
});
if(!numCols.length){$('chartsArea').innerHTML='';return}
const txtCols=cols;
const html=`
<div class="chart-box" style="grid-column:1/-1;display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:12px 16px">
<span style="font-size:0.72rem;color:var(--fg4);white-space:nowrap">📊 快速图表:</span>
<select id="qcDimSel" onchange="qcRender()" style="padding:4px 8px;background:var(--bg2);border:1px solid var(--bd);border-radius:6px;color:var(--fg);font-size:0.75rem;cursor:pointer;font-family:inherit">
<option value="">分组字段...</option>
${txtCols.map(c=>`<option value="${c.k}">${c.l}</option>`).join('')}
</select>
<select id="qcValSel" onchange="qcRender()" style="padding:4px 8px;background:var(--bg2);border:1px solid var(--bd);border-radius:6px;color:var(--fg);font-size:0.75rem;cursor:pointer;font-family:inherit">
<option value="">数值字段...</option>
<option value="__count__">计数</option>
${numCols.map(c=>`<option value="${c.k}">${c.l}</option>`).join('')}
</select>
<select id="qcTypeSel" onchange="qcRender()" style="padding:4px 8px;background:var(--bg2);border:1px solid var(--bd);border-radius:6px;color:var(--fg);font-size:0.75rem;cursor:pointer;font-family:inherit">
<option value="bar">柱状图</option>
<option value="pie">饼图</option>
<option value="doughnut">环形图</option>
<option value="line">折线图</option>
<option value="polarArea">玫瑰图</option>
</select>
</div>
`;
$('chartsArea').innerHTML=html;
// Quick mode: just prepare an empty container; controls are in tb-r
$('chartsArea').innerHTML='<div id="qcChartWrap" style="grid-column:1/-1;display:none"></div>';
}else{
// Pre-configured charts from template
let html='';
charts.forEach((ch,i)=>{
html+=`<div class="chart-box"><div class="chart-title">${ch.title||'图表 '+ (i+1)}</div><canvas id="chart${i}" height="200"></canvas></div>`;
@@ -354,9 +388,10 @@ function buildChartsUI(){
}
}
function renderCharts(fd){
function renderCharts(){
chartInstances.forEach(c=>c.destroy());
chartInstances=[];
const fd=flt();
const charts=REPORT_CONFIG.charts||[];
if(charts.length){
charts.forEach((ch,i)=>{
@@ -374,7 +409,6 @@ function renderCharts(fd){
groups['_cnt_'+k]+=1;
}else groups[k]+=1;
});
// Calculate avg
if(vf&&ch.calc==='avg'){
Object.keys(groups).forEach(k=>{
if(k.startsWith('_cnt_'))return;
@@ -409,11 +443,9 @@ function renderCharts(fd){
};
chartInstances.push(new Chart(el,cfg));
});
}else if($('qcDimSel')&&$('qcDimSel').value&&$('qcValSel')&&$('qcValSel').value){
// Quick chart
const dim=$('qcDimSel').value;
const val=$('qcValSel').value;
const tp=$('qcTypeSel')?$('qcTypeSel').value:'bar';
}else{
// Quick chart mode
const dim=qcState.dim, val=qcState.val, tp=qcState.type;
if(!dim||!val)return;
const groups={};
fd.forEach(r=>{
@@ -423,19 +455,11 @@ function renderCharts(fd){
});
const labels=Object.keys(groups).sort();
const values=labels.map(l=>groups[l]);
// Ensure chart canvas exists
let canvas=$('qcCanvas');
if(!canvas){
const wrap=$('chartsArea').querySelector('.chart-box');
if(wrap){
const div=document.createElement('div');
div.style.height='260px';
div.style.marginTop='8px';
div.innerHTML='<canvas id="qcCanvas"></canvas>';
wrap.appendChild(div);
canvas=$('qcCanvas');
}
}
const wrap=$('qcChartWrap');
if(!wrap)return;
wrap.style.display='block';
wrap.innerHTML='<div class="chart-box"><canvas id="qcCanvas" height="260"></canvas></div>';
const canvas=$('qcCanvas');
if(!canvas)return;
const isPie=tp==='pie'||tp==='doughnut'||tp==='polarArea';
const cfg={
@@ -463,7 +487,6 @@ function renderCharts(fd){
chartInstances.push(new Chart(canvas,cfg));
}
}
function qcRender(){r()}
function buildFilters(){
const fltArea=$('fltArea');
@@ -935,7 +958,7 @@ function saveDataConfig(){
COLS.length=0;COLS.push(...REPORT_CONFIG.columns);
D.length=0;D.push(...REPORT_CONFIG.data);
data=D.map(r=>({...r}));
colVis=COLS.map((_,i)=>i<REPORT_CONFIG.defaultVisible);
colVis=COLS.map(()=>true);
grpFields=REPORT_CONFIG.groupBy.map(g=>({...g,active:true}));
grpExp={};
sCol=6;sAsc=false;