ai报表
This commit is contained in:
@@ -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&field=web_icon_data&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&field=web_icon_data&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&field=web_icon_data&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&field=web_icon_data&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&field=web_icon_data&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&field=web_icon_data&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;
|
||||
|
||||
Reference in New Issue
Block a user