ai报表
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
<field name="field_labels">{"name":"订单号","partner_id":"客户","date_order":"下单日期","amount_total":"金额","state":"状态","user_id":"销售员"}</field>
|
||||
<field name="default_order">date_order desc</field>
|
||||
<field name="default_visible">5</field>
|
||||
<field name="report_config"><![CDATA[{"columns":[{"k":"name","l":"订单号","w":130},{"k":"partner_id","l":"客户","w":120},{"k":"date_order","l":"下单日期","w":110},{"k":"amount_total","l":"金额","w":110},{"k":"state","l":"状态","w":80}],"cards":[{"label":"总金额","field":"amount_total","calc":"sum","prefix":"¥"},{"label":"订单数","field":"id","calc":"count"}],"groupBy":[{"key":"state","label":"状态"}],"defaultVisible":5,"colorRules":[{"field":"state","rules":[{"op":"eq","value":"sale","color":"#5DCAA5"},{"op":"eq","value":"draft","color":"#EFC87E"},{"op":"eq","value":"done","color":"#85B7EB"},{"op":"eq","value":"cancel","color":"#ED93B1"}]}],"filters":[{"field":"state","type":"select","label":"状态","options":["draft","sale","done","cancel"]}]}]]></field>
|
||||
<field name="report_config"><![CDATA[{"columns":[{"k":"name","l":"订单号","w":130},{"k":"partner_id","l":"客户","w":120},{"k":"date_order","l":"下单日期","w":110},{"k":"amount_total","l":"金额","w":110},{"k":"state","l":"状态","w":80}],"cards":[{"label":"总金额","field":"amount_total","calc":"sum","prefix":"¥"},{"label":"订单数","field":"id","calc":"count"}],"groupBy":[{"key":"state","label":"状态"}],"defaultVisible":5,"colorRules":[{"field":"state","rules":[{"op":"eq","value":"sale","color":"#5DCAA5"},{"op":"eq","value":"draft","color":"#EFC87E"},{"op":"eq","value":"done","color":"#85B7EB"},{"op":"eq","value":"cancel","color":"#ED93B1"}]}],"filters":[{"field":"state","type":"select","label":"状态","options":["draft","sale","done","cancel"]}],"charts":[{"type":"pie","labelField":"state","calc":"count","title":"订单状态分布"},{"type":"bar","labelField":"state","valueField":"amount_total","calc":"sum","title":"各状态金额汇总"}]}]]></field>
|
||||
<field name="param_definitions"><![CDATA[[{"name":"date_from","label":"开始日期","type":"date","domain_field":"date_order","op":">=","description":"查询起始日期"},{"name":"date_to","label":"结束日期","type":"date","domain_field":"date_order","op":"<=","description":"查询截止日期"}]]]></field>
|
||||
<field name="description">当用户查询销售订单列表或销售业绩时使用此模板。支持按日期范围筛选。</field>
|
||||
<field name="sequence">10</field>
|
||||
@@ -26,7 +26,7 @@
|
||||
<field name="field_labels">{"name":"线索名称","partner_name":"客户","expected_revenue":"预期收入","probability":"赢单率","stage_id":"阶段","create_date":"创建时间","user_id":"负责人"}</field>
|
||||
<field name="default_order">create_date desc</field>
|
||||
<field name="default_visible">6</field>
|
||||
<field name="report_config"><![CDATA[{"columns":[{"k":"name","l":"线索名称","w":140},{"k":"partner_name","l":"客户","w":120},{"k":"expected_revenue","l":"预期收入","w":110},{"k":"probability","l":"赢单率","w":80},{"k":"stage_id","l":"阶段","w":100},{"k":"create_date","l":"创建时间","w":120}],"cards":[{"label":"预期总收入","field":"expected_revenue","calc":"sum","prefix":"¥"},{"label":"线索数","field":"id","calc":"count"}],"groupBy":[{"key":"stage_id","label":"阶段"}],"defaultVisible":6,"colorRules":[],"filters":[{"field":"probability","type":"input","label":"最低赢单率"}]}]]></field>
|
||||
<field name="report_config"><![CDATA[{"columns":[{"k":"name","l":"线索名称","w":140},{"k":"partner_name","l":"客户","w":120},{"k":"expected_revenue","l":"预期收入","w":110},{"k":"probability","l":"赢单率","w":80},{"k":"stage_id","l":"阶段","w":100},{"k":"create_date","l":"创建时间","w":120}],"cards":[{"label":"预期总收入","field":"expected_revenue","calc":"sum","prefix":"¥"},{"label":"线索数","field":"id","calc":"count"}],"groupBy":[{"key":"stage_id","label":"阶段"}],"defaultVisible":6,"colorRules":[],"filters":[{"field":"probability","type":"input","label":"最低赢单率"}],"charts":[{"type":"bar","labelField":"stage_id","valueField":"expected_revenue","calc":"sum","title":"各阶段预期收入"},{"type":"doughnut","labelField":"stage_id","calc":"count","title":"线索阶段分布"}]}]]></field>
|
||||
<field name="description">当用户查询销售线索列表或销售管道时使用此模板。</field>
|
||||
<field name="sequence">20</field>
|
||||
<field name="active" eval="True"/>
|
||||
|
||||
@@ -69,6 +69,8 @@ class AiReportTemplate(models.Model):
|
||||
help='列定义中已配置的字段,用于限制分组/筛选器的可选范围')
|
||||
param_def_ids = fields.One2many('ai.report.template.param.def', 'template_id',
|
||||
string='AI 参数定义', copy=True)
|
||||
chart_ids = fields.One2many('ai.report.template.chart', 'template_id',
|
||||
string='图表配置', copy=True)
|
||||
|
||||
default_visible = fields.Integer('默认可见列数', default=10)
|
||||
|
||||
@@ -88,7 +90,7 @@ class AiReportTemplate(models.Model):
|
||||
|
||||
# ===== Computed: report_config =====
|
||||
@api.depends('column_ids', 'card_ids', 'group_by_ids',
|
||||
'filter_ids', 'default_visible')
|
||||
'filter_ids', 'default_visible', 'chart_ids')
|
||||
def _compute_report_config(self):
|
||||
for t in self:
|
||||
cfg = {}
|
||||
@@ -162,6 +164,23 @@ class AiReportTemplate(models.Model):
|
||||
cfg['filters'] = flt
|
||||
if t.default_visible:
|
||||
cfg['defaultVisible'] = t.default_visible
|
||||
charts = []
|
||||
for ch in t.chart_ids:
|
||||
lf_name = ch.label_field_name or (ch.label_field_id.name if ch.label_field_id else '')
|
||||
vf_name = ch.value_field_name or (ch.value_field_id.name if ch.value_field_id else '')
|
||||
if not lf_name:
|
||||
continue
|
||||
cd = {
|
||||
'type': ch.chart_type,
|
||||
'labelField': lf_name,
|
||||
'calc': ch.calc_method,
|
||||
'title': ch.title or '',
|
||||
}
|
||||
if vf_name:
|
||||
cd['valueField'] = vf_name
|
||||
charts.append(cd)
|
||||
if charts:
|
||||
cfg['charts'] = charts
|
||||
t.report_config = json.dumps(cfg, ensure_ascii=False) if cfg else '{}'
|
||||
|
||||
def _inverse_report_config(self):
|
||||
|
||||
@@ -200,6 +200,51 @@ class AiReportTemplateParamDef(models.Model):
|
||||
self.label = self.field_id.field_description
|
||||
|
||||
|
||||
class AiReportTemplateChart(models.Model):
|
||||
"""报表模板 - 图表配置"""
|
||||
_name = 'ai.report.template.chart'
|
||||
_description = '图表配置'
|
||||
_order = 'sequence, id'
|
||||
|
||||
template_id = fields.Many2one('ai.report.template', string='模板',
|
||||
required=True, ondelete='cascade')
|
||||
model_id = fields.Many2one('ir.model', related='template_id.model_id', store=True)
|
||||
chart_type = fields.Selection([
|
||||
('bar', '柱状图'),
|
||||
('line', '折线图'),
|
||||
('pie', '饼图'),
|
||||
('doughnut', '环形图'),
|
||||
('polarArea', '玫瑰图'),
|
||||
], string='图表类型', default='bar', required=True)
|
||||
label_field_id = fields.Many2one('ir.model.fields', string='分组字段',
|
||||
domain="[('model_id', '=', model_id)]", required=True,
|
||||
help='作为图表分类轴的字段')
|
||||
label_field_name = fields.Char('分组字段名',
|
||||
compute='_compute_label_field_name', store=True)
|
||||
value_field_id = fields.Many2one('ir.model.fields', string='数值字段',
|
||||
domain="[('model_id', '=', model_id), ('ttype', 'in', ('integer', 'float', 'monetary'))]",
|
||||
help='留空则使用计数')
|
||||
value_field_name = fields.Char('数值字段名',
|
||||
compute='_compute_value_field_name', store=True)
|
||||
calc_method = fields.Selection([
|
||||
('count', '计数'),
|
||||
('sum', '求和'),
|
||||
('avg', '平均值'),
|
||||
], string='计算方式', default='count')
|
||||
title = fields.Char('图表标题')
|
||||
sequence = fields.Integer('排序', default=10)
|
||||
|
||||
@api.depends('label_field_id')
|
||||
def _compute_label_field_name(self):
|
||||
for r in self:
|
||||
r.label_field_name = r.label_field_id.name if r.label_field_id else ''
|
||||
|
||||
@api.depends('value_field_id')
|
||||
def _compute_value_field_name(self):
|
||||
for r in self:
|
||||
r.value_field_name = r.value_field_id.name if r.value_field_id else ''
|
||||
|
||||
|
||||
class AiReportTemplateDomainLine(models.Model):
|
||||
_name = 'ai.report.template.domain.line'
|
||||
_description = '报表模板-查询条件行'
|
||||
|
||||
@@ -29,3 +29,5 @@ access_ai_report_template_param_def_user,参数定义-用户只读,model_ai_repo
|
||||
access_ai_report_template_param_def_admin,参数定义-管理员,model_ai_report_template_param_def,yuthon_ai_agent.group_ai_agent_admin,1,1,1,1
|
||||
access_ai_report_template_domain_line_user,查询条件行-用户只读,model_ai_report_template_domain_line,yuthon_ai_agent.group_ai_agent_user,1,0,0,0
|
||||
access_ai_report_template_domain_line_admin,查询条件行-管理员,model_ai_report_template_domain_line,yuthon_ai_agent.group_ai_agent_admin,1,1,1,1
|
||||
access_ai_report_template_chart_user,图表配置-用户只读,model_ai_report_template_chart,yuthon_ai_agent.group_ai_agent_user,1,0,0,0
|
||||
access_ai_report_template_chart_admin,图表配置-管理员,model_ai_report_template_chart,yuthon_ai_agent.group_ai_agent_admin,1,1,1,1
|
||||
|
||||
|
@@ -5,6 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
||||
<title>{{REPORT_TITLE}} · 动态报表</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:"Inter","PingFang SC","Microsoft YaHei",sans-serif;font-size:14px;min-height:100vh;transition:background .3s,color .3s}
|
||||
@@ -32,6 +33,16 @@ body{background:var(--bg);color:var(--fg)}
|
||||
.card .l{font-size:0.75rem;color:var(--fg4);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px}
|
||||
.card .v{font-size:1.4rem;font-weight:700;background:var(--gr2);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin:2px 0 4px}
|
||||
.card .c{font-size:0.72rem;color:var(--fg4)}
|
||||
|
||||
/* ===== Charts ===== */
|
||||
.charts-wrap{display:grid;grid-template-columns:repeat(auto-fit,minmax(340px,1fr));gap:12px;padding:0 20px 8px}
|
||||
.chart-box{background:var(--bg2);border:1px solid var(--bd);border-radius:12px;padding:16px;position:relative}
|
||||
.chart-box .chart-title{font-size:0.75rem;color:var(--fg4);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:8px}
|
||||
.chart-box canvas{max-height:260px}
|
||||
.chart-ctrls{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:0 20px 4px}
|
||||
.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)}
|
||||
.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}
|
||||
|
||||
@@ -177,6 +188,7 @@ tr.ch.h{display:none}
|
||||
<div class="summary" id="sumArea"></div>
|
||||
<div class="report-desc" id="descArea">📊 加载中...</div>
|
||||
|
||||
<div class="charts-wrap" id="chartsArea"></div>
|
||||
|
||||
<div class="flt" id="fltArea">
|
||||
<div class="flt-g wide"><label>🔍</label><input id="fG" placeholder="全局搜索..." oninput="rf()"></div>
|
||||
@@ -260,7 +272,7 @@ const DATA_URL = '{{DATA_URL}}';
|
||||
const COLS=REPORT_CONFIG.columns;
|
||||
const D=REPORT_CONFIG.data;
|
||||
let data=D.map(r=>({...r}));
|
||||
let colVis=COLS.map((_,i)=>i<REPORT_CONFIG.defaultVisible);
|
||||
let colVis=COLS.map(()=>true);
|
||||
let sCol=6,sAsc=false;
|
||||
let grpFields=REPORT_CONFIG.groupBy.map(g=>({...g,active:true}));
|
||||
let grpExp={};
|
||||
@@ -274,6 +286,7 @@ function init(){
|
||||
initGrpExp();
|
||||
buildColCheck();
|
||||
buildGrpChips();
|
||||
buildChartsUI();
|
||||
r();
|
||||
}
|
||||
init();
|
||||
@@ -284,10 +297,174 @@ function r(){
|
||||
const dd=activeGrps.length>0?grpData(fd,activeGrps):fd;
|
||||
renderT(dd,fd);
|
||||
renderSum(fd);
|
||||
renderCharts(fd);
|
||||
}
|
||||
function $(id){return document.getElementById(id)}
|
||||
function rf(){r()}
|
||||
|
||||
// ===== Charts =====
|
||||
let chartInstances=[];
|
||||
const CHART_COLORS=['#7F77DD','#5DCAA5','#F39C12','#E74C3C','#3498DB','#9B59B6','#1ABC9C','#E67E22','#2ECC71','#E91E63'];
|
||||
const CHART_COLORS_DARK=CHART_COLORS;
|
||||
const CHART_COLORS_LIGHT=['#6B63C9','#1D9E75','#D68910','#C0392B','#2980B9','#8E44AD','#17A589','#D35400','#27AE60','#C2185B'];
|
||||
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 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;
|
||||
}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>`;
|
||||
});
|
||||
$('chartsArea').innerHTML=html;
|
||||
}
|
||||
}
|
||||
|
||||
function renderCharts(fd){
|
||||
chartInstances.forEach(c=>c.destroy());
|
||||
chartInstances=[];
|
||||
const charts=REPORT_CONFIG.charts||[];
|
||||
if(charts.length){
|
||||
charts.forEach((ch,i)=>{
|
||||
const el=document.getElementById('chart'+i);
|
||||
if(!el)return;
|
||||
const lf=ch.labelField, vf=ch.valueField;
|
||||
const groups={};
|
||||
fd.forEach(r=>{
|
||||
const k=String(r[lf]||'(空)');
|
||||
if(!groups[k])groups[k]=0;
|
||||
if(vf&&ch.calc==='sum')groups[k]+=Number(r[vf])||0;
|
||||
else if(vf&&ch.calc==='avg'){
|
||||
if(!groups['_cnt_'+k]){groups['_cnt_'+k]=0;groups[k]=0}
|
||||
groups[k]+=Number(r[vf])||0;
|
||||
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;
|
||||
groups[k]=groups['_cnt_'+k]?groups[k]/groups['_cnt_'+k]:0;
|
||||
});
|
||||
Object.keys(groups).forEach(k=>{if(k.startsWith('_cnt_'))delete groups[k]});
|
||||
}
|
||||
const labels=Object.keys(groups).sort();
|
||||
const values=labels.map(l=>groups[l]);
|
||||
const isPie=ch.type==='pie'||ch.type==='doughnut'||ch.type==='polarArea';
|
||||
const cfg={
|
||||
type:ch.type,
|
||||
data:{
|
||||
labels,
|
||||
datasets:[{
|
||||
label:ch.title||(ch.calc==='count'?'计数':'数值'),
|
||||
data:values,
|
||||
backgroundColor:labels.map((_,j)=>chartColors()[j%chartColors().length]),
|
||||
borderColor:isPie?'transparent':chartColors(),
|
||||
borderWidth:isPie?0:1,
|
||||
borderRadius:isPie?0:4,
|
||||
}]
|
||||
},
|
||||
options:{
|
||||
responsive:true,
|
||||
maintainAspectRatio:false,
|
||||
plugins:{
|
||||
legend:isPie?{position:'right',labels:{color:chartTextColor(),padding:12,font:{size:11}}}:{display:false}
|
||||
},
|
||||
scales:isPie?{}:{x:{grid:{color:chartGridColor()},ticks:{color:chartTextColor(),font:{size:10}}},y:{grid:{color:chartGridColor()},ticks:{color:chartTextColor(),font:{size:10}}}}
|
||||
}
|
||||
};
|
||||
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';
|
||||
if(!dim||!val)return;
|
||||
const groups={};
|
||||
fd.forEach(r=>{
|
||||
const k=String(r[dim]||'(空)');
|
||||
if(!groups[k])groups[k]=0;
|
||||
groups[k]+=val==='__count__'?1:(Number(r[val])||0);
|
||||
});
|
||||
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');
|
||||
}
|
||||
}
|
||||
if(!canvas)return;
|
||||
const isPie=tp==='pie'||tp==='doughnut'||tp==='polarArea';
|
||||
const cfg={
|
||||
type:tp,
|
||||
data:{
|
||||
labels,
|
||||
datasets:[{
|
||||
label:val==='__count__'?'计数':(COLS.find(c=>c.k===val)?.l||val),
|
||||
data:values,
|
||||
backgroundColor:labels.map((_,j)=>chartColors()[j%chartColors().length]),
|
||||
borderColor:isPie?'transparent':chartColors(),
|
||||
borderWidth:isPie?0:1,
|
||||
borderRadius:isPie?0:4,
|
||||
}]
|
||||
},
|
||||
options:{
|
||||
responsive:true,
|
||||
maintainAspectRatio:false,
|
||||
plugins:{
|
||||
legend:isPie?{position:'right',labels:{color:chartTextColor(),padding:12,font:{size:11}}}:{display:false}
|
||||
},
|
||||
scales:isPie?{}:{x:{grid:{color:chartGridColor()},ticks:{color:chartTextColor(),font:{size:10}}},y:{grid:{color:chartGridColor()},ticks:{color:chartTextColor(),font:{size:10}}}}
|
||||
}
|
||||
};
|
||||
chartInstances.push(new Chart(canvas,cfg));
|
||||
}
|
||||
}
|
||||
function qcRender(){r()}
|
||||
|
||||
function buildFilters(){
|
||||
const fltArea=$('fltArea');
|
||||
if(!fltArea)return;
|
||||
|
||||
@@ -223,6 +223,24 @@
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="图表" name="charts">
|
||||
<field name="chart_ids" nolabel="1">
|
||||
<tree string="图表配置" editable="bottom">
|
||||
<field name="model_id" column_invisible="1"/>
|
||||
<field name="title" placeholder="图表标题"/>
|
||||
<field name="chart_type"/>
|
||||
<field name="label_field_id"
|
||||
placeholder="分组字段..."
|
||||
options="{'no_create': True}"/>
|
||||
<field name="value_field_id"
|
||||
placeholder="数值字段(可空)..."
|
||||
options="{'no_create': True}"/>
|
||||
<field name="calc_method"/>
|
||||
<field name="sequence" widget="handle"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="AI 参数" name="params" invisible="1">
|
||||
<field name="param_def_ids" nolabel="1">
|
||||
<tree string="参数定义" editable="bottom">
|
||||
|
||||
Reference in New Issue
Block a user