sync: local modifications to unified_dashboard
- Updated core.py, init.py, mysql.ddl.sql - Added __init__.py - Added API files: dashboard_kpi, report_list - Added UI files: base.ui, dashboard.ui, reports.ui
This commit is contained in:
parent
1d4a6b2893
commit
aec650dcef
@ -0,0 +1,49 @@
|
|||||||
|
-- Table from dashboard_config.json
|
||||||
|
CREATE TABLE IF NOT EXISTS `dashboard_config` (
|
||||||
|
`id` VARCHAR(32) NOT NULL COMMENT '主键UUID',
|
||||||
|
`dashboard_name` VARCHAR(100) COMMENT '仪表板显示名称',
|
||||||
|
`dashboard_type` VARCHAR(50) COMMENT 'sales/finance/customer/executive',
|
||||||
|
`config_json` VARCHAR(2000) COMMENT '仪表板布局和组件配置',
|
||||||
|
`is_default` VARCHAR(1) COMMENT 'Y/N',
|
||||||
|
`org_id` VARCHAR(32) COMMENT '多租户组织隔离',
|
||||||
|
`created_by` VARCHAR(32) COMMENT '创建用户ID',
|
||||||
|
`created_at` TIMESTAMP COMMENT '创建时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='仪表板配置';
|
||||||
|
|
||||||
|
CREATE INDEX `idx_dashboard_org` ON `dashboard_config` (`org_id`);
|
||||||
|
CREATE INDEX `idx_dashboard_type` ON `dashboard_config` (`dashboard_type`);
|
||||||
|
CREATE UNIQUE INDEX `uk_dashboard_name_org` ON `dashboard_config` (`dashboard_name`, `org_id`);
|
||||||
|
|
||||||
|
-- Table from report_template.json
|
||||||
|
CREATE TABLE IF NOT EXISTS `report_template` (
|
||||||
|
`id` VARCHAR(32) NOT NULL COMMENT '主键UUID',
|
||||||
|
`template_name` VARCHAR(100) COMMENT '报表模板名称',
|
||||||
|
`report_type` VARCHAR(50) COMMENT 'sales/finance/customer/contract',
|
||||||
|
`sql_query` VARCHAR(2000) COMMENT '报表数据查询SQL',
|
||||||
|
`columns_config` VARCHAR(1000) NOT NULL COMMENT 'JSON格式的列配置',
|
||||||
|
`filters_config` VARCHAR(1000) NOT NULL COMMENT 'JSON格式的过滤器配置',
|
||||||
|
`chart_config` VARCHAR(1000) NOT NULL COMMENT 'JSON格式的图表配置',
|
||||||
|
`org_id` VARCHAR(32) COMMENT '多租户组织隔离',
|
||||||
|
`created_by` VARCHAR(32) COMMENT '创建用户ID',
|
||||||
|
`created_at` TIMESTAMP COMMENT '创建时间',
|
||||||
|
`is_active` VARCHAR(1) COMMENT 'Y/N',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='报表模板';
|
||||||
|
|
||||||
|
CREATE INDEX `idx_template_org` ON `report_template` (`org_id`);
|
||||||
|
CREATE INDEX `idx_template_type` ON `report_template` (`report_type`);
|
||||||
|
|
||||||
|
-- Table from user_dashboard.json
|
||||||
|
CREATE TABLE IF NOT EXISTS `user_dashboard` (
|
||||||
|
`id` VARCHAR(32) NOT NULL COMMENT '主键UUID',
|
||||||
|
`user_id` VARCHAR(32) COMMENT '关联用户',
|
||||||
|
`dashboard_config_id` VARCHAR(32) COMMENT '关联的仪表板配置',
|
||||||
|
`layout_json` VARCHAR(2000) NOT NULL COMMENT '用户自定义布局',
|
||||||
|
`is_favorite` VARCHAR(1) COMMENT 'Y/N',
|
||||||
|
`org_id` VARCHAR(32) COMMENT '多租户组织隔离',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户仪表板';
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX `idx_user_dashboard_user` ON `user_dashboard` (`user_id`, `dashboard_config_id`);
|
||||||
|
CREATE INDEX `idx_user_dashboard_org` ON `user_dashboard` (`org_id`);
|
||||||
1
unified_dashboard/__init__.py
Normal file
1
unified_dashboard/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Unified Dashboard Module
|
||||||
@ -176,11 +176,12 @@ class DashboardCore:
|
|||||||
return approvals
|
return approvals
|
||||||
|
|
||||||
elif list_type == 'overdue_tasks':
|
elif list_type == 'overdue_tasks':
|
||||||
tasks = await self.db.select('approval_task',
|
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
fields=['title', 'step_name', 'due_at'],
|
tasks = await self.db.sqlExe(
|
||||||
where={'org_id': org_id, 'status': 'pending', 'due_at < NOW()'},
|
"SELECT title, step_name, due_at FROM approval_task WHERE org_id = ${org_id}$ AND status = ${status}$ AND due_at < ${now}$ ORDER BY due_at ASC",
|
||||||
order_by='due_at ASC',
|
{'org_id': org_id, 'status': 'pending', 'now': now},
|
||||||
limit=10)
|
limit=10
|
||||||
|
)
|
||||||
return tasks
|
return tasks
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
from ahserver.serverenv import ServerEnv
|
from ahserver.serverenv import ServerEnv
|
||||||
from appPublic.worker import awaitify
|
from appPublic.worker import awaitify
|
||||||
from .core import DashboardCore, ReportCore
|
from .core import DashboardCore, ReportCore
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
|
||||||
|
|
||||||
def load_unified_dashboard():
|
def load_unified_dashboard():
|
||||||
"""加载统一仪表板和报表模块"""
|
"""加载统一仪表板和报表模块"""
|
||||||
@ -8,14 +10,14 @@ def load_unified_dashboard():
|
|||||||
|
|
||||||
# 创建核心实例的工厂函数
|
# 创建核心实例的工厂函数
|
||||||
async def create_dashboard_core(org_id):
|
async def create_dashboard_core(org_id):
|
||||||
from sqlor.dbp import getDBP
|
dbname = get_module_dbname('unified_dashboard')
|
||||||
db = await getDBP(org_id)
|
sor = await DBPools().sqlorContext(dbname)
|
||||||
return DashboardCore(db)
|
return DashboardCore(sor)
|
||||||
|
|
||||||
async def create_report_core(org_id):
|
async def create_report_core(org_id):
|
||||||
from sqlor.dbp import getDBP
|
dbname = get_module_dbname('unified_dashboard')
|
||||||
db = await getDBP(org_id)
|
sor = await DBPools().sqlorContext(dbname)
|
||||||
return ReportCore(db)
|
return ReportCore(sor)
|
||||||
|
|
||||||
env.create_dashboard_core = create_dashboard_core
|
env.create_dashboard_core = create_dashboard_core
|
||||||
env.create_report_core = create_report_core
|
env.create_report_core = create_report_core
|
||||||
84
wwwroot/api/dashboard_kpi.dspy
Normal file
84
wwwroot/api/dashboard_kpi.dspy
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Dashboard KPI and chart data API"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
result = {'success': False, 'rows': [], 'total': 0}
|
||||||
|
|
||||||
|
try:
|
||||||
|
kpi_type = params_kw.get('type', '').strip()
|
||||||
|
|
||||||
|
if kpi_type == 'sales_funnel':
|
||||||
|
dbname = get_module_dbname('opportunity_management')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
stages = await sor.sqlExe("SELECT id, stage_name, stage_order FROM sales_stages ORDER BY stage_order ASC")
|
||||||
|
funnel_data = []
|
||||||
|
for stage in stages:
|
||||||
|
rows = await sor.sqlExe("SELECT COUNT(*) as cnt, COALESCE(SUM(estimated_amount),0) as amt FROM opportunities WHERE current_stage = ${stage_id}$", {'stage_id': stage['id']})
|
||||||
|
funnel_data.append({
|
||||||
|
'stage': stage['stage_name'],
|
||||||
|
'count': rows[0]['cnt'] if rows else 0,
|
||||||
|
'amount': float(rows[0]['amt'] or 0) if rows else 0
|
||||||
|
})
|
||||||
|
result['rows'] = funnel_data
|
||||||
|
result['total'] = len(funnel_data)
|
||||||
|
result['success'] = True
|
||||||
|
|
||||||
|
elif kpi_type == 'recent_opportunities':
|
||||||
|
dbname = get_module_dbname('opportunity_management')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
rows = await sor.sqlExe("SELECT customer_name, estimated_amount, current_stage FROM opportunities ORDER BY created_at DESC LIMIT 10")
|
||||||
|
result['rows'] = [dict(r) for r in (rows or [])]
|
||||||
|
result['total'] = len(result['rows'])
|
||||||
|
result['success'] = True
|
||||||
|
|
||||||
|
elif kpi_type == 'kpi_summary':
|
||||||
|
# Aggregate KPIs from all modules
|
||||||
|
kpis = {}
|
||||||
|
|
||||||
|
# Customer count
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('customer_management')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
rows = await sor.sqlExe("SELECT COUNT(*) as cnt FROM customers")
|
||||||
|
kpis['total_customers'] = rows[0]['cnt'] if rows else 0
|
||||||
|
except:
|
||||||
|
kpis['total_customers'] = 0
|
||||||
|
|
||||||
|
# Active opportunities
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('opportunity_management')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
rows = await sor.sqlExe("SELECT COUNT(*) as cnt FROM opportunities WHERE status = 'active'")
|
||||||
|
kpis['active_opportunities'] = rows[0]['cnt'] if rows else 0
|
||||||
|
except:
|
||||||
|
kpis['active_opportunities'] = 0
|
||||||
|
|
||||||
|
# Contract count
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('contract_management')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
rows = await sor.sqlExe("SELECT COUNT(*) as cnt FROM contract")
|
||||||
|
kpis['total_contracts'] = rows[0]['cnt'] if rows else 0
|
||||||
|
except:
|
||||||
|
kpis['total_contracts'] = 0
|
||||||
|
|
||||||
|
# Total receivables
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('financial_management')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
rows = await sor.sqlExe("SELECT COALESCE(SUM(receivable_amount),0) as total FROM receivables")
|
||||||
|
kpis['total_receivables'] = float(rows[0]['total'] or 0) if rows else 0
|
||||||
|
except:
|
||||||
|
kpis['total_receivables'] = 0
|
||||||
|
|
||||||
|
result['success'] = True
|
||||||
|
result['data'] = kpis
|
||||||
|
|
||||||
|
else:
|
||||||
|
result['error'] = f'Unknown KPI type: {kpi_type}'
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
result['error'] = str(e)
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
35
wwwroot/api/report_list.dspy
Normal file
35
wwwroot/api/report_list.dspy
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Report list API"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
result = {'success': False, 'rows': [], 'total': 0}
|
||||||
|
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('unified_dashboard')
|
||||||
|
ns = {
|
||||||
|
'page': int(params_kw.get('page', 1)),
|
||||||
|
'rows': int(params_kw.get('rows', 20)),
|
||||||
|
'sort': 'created_at desc'
|
||||||
|
}
|
||||||
|
sql = "SELECT id, template_name, category, description, is_active, created_at FROM report_template WHERE 1=1"
|
||||||
|
|
||||||
|
keyword = params_kw.get('keyword', '').strip()
|
||||||
|
if keyword:
|
||||||
|
sql += " AND template_name LIKE ${keyword}$"
|
||||||
|
ns['keyword'] = f'%{keyword}%'
|
||||||
|
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
data = await sor.sqlExe(sql, ns)
|
||||||
|
if isinstance(data, dict):
|
||||||
|
result['total'] = data.get('total', 0)
|
||||||
|
result['rows'] = [dict(r) for r in data.get('rows', [])]
|
||||||
|
else:
|
||||||
|
result['rows'] = [dict(r) for r in (data or [])]
|
||||||
|
result['total'] = len(result['rows'])
|
||||||
|
result['success'] = True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
result['error'] = str(e)
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
44
wwwroot/base.ui
Normal file
44
wwwroot/base.ui
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"widgettype": "Page",
|
||||||
|
"options": {
|
||||||
|
"title": "统一仪表板",
|
||||||
|
"style": {"height": "100vh", "padding": "0"}
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {"style": {"height": "100%"}},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {"style": {"width": "220px", "backgroundColor": "#1A1E2F", "padding": "8px"}},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": "数据看板", "style": {"color": "#fff", "fontSize": "16px", "fontWeight": "bold", "padding": "12px 8px"}}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Menu",
|
||||||
|
"options": {
|
||||||
|
"width": "100%",
|
||||||
|
"bgcolor": "#1A1E2F",
|
||||||
|
"items": [
|
||||||
|
{"label": "高管视图", "icon": "fa fa-dashboard", "url": "{{entire_url('dashboard.ui')}}?type=executive", "target": "app.dashboard-content"},
|
||||||
|
{"label": "销售视图", "icon": "fa fa-line-chart", "url": "{{entire_url('dashboard.ui')}}?type=sales", "target": "app.dashboard-content"},
|
||||||
|
{"label": "财务视图", "icon": "fa fa-money", "url": "{{entire_url('dashboard.ui')}}?type=finance", "target": "app.dashboard-content"},
|
||||||
|
{"label": "报表中心", "icon": "fa fa-file-text", "url": "{{entire_url('reports.ui')}}", "target": "app.dashboard-content"}
|
||||||
|
],
|
||||||
|
"menuitem_css": "menuitem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"id": "dashboard-content",
|
||||||
|
"options": {"style": {"flex": 1, "overflow": "auto", "backgroundColor": "#f5f5f5"}}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
83
wwwroot/dashboard.ui
Normal file
83
wwwroot/dashboard.ui
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"widgettype": "Page",
|
||||||
|
"options": {
|
||||||
|
"title": "仪表板",
|
||||||
|
"style": {"height": "100%", "padding": "0"}
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {"style": {"padding": "16px", "flex": 1, "overflow": "auto"}},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {"style": {"marginBottom": "16px", "gap": "12px", "flexWrap": "wrap"}},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {"style": {"flex": 1, "minWidth": "200px", "backgroundColor": "#fff", "borderRadius": "8px", "padding": "16px", "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"}},
|
||||||
|
"subwidgets": [
|
||||||
|
{"widgettype": "Text", "options": {"text": "客户总数", "style": {"fontSize": "14px", "color": "#666"}}},
|
||||||
|
{"widgettype": "Text", "id": "kpi_customers", "options": {"text": "--", "style": {"fontSize": "28px", "fontWeight": "bold", "color": "#1E40AF"}}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {"style": {"flex": 1, "minWidth": "200px", "backgroundColor": "#fff", "borderRadius": "8px", "padding": "16px", "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"}},
|
||||||
|
"subwidgets": [
|
||||||
|
{"widgettype": "Text", "options": {"text": "活跃商机", "style": {"fontSize": "14px", "color": "#666"}}},
|
||||||
|
{"widgettype": "Text", "id": "kpi_opportunities", "options": {"text": "--", "style": {"fontSize": "28px", "fontWeight": "bold", "color": "#059669"}}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {"style": {"flex": 1, "minWidth": "200px", "backgroundColor": "#fff", "borderRadius": "8px", "padding": "16px", "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"}},
|
||||||
|
"subwidgets": [
|
||||||
|
{"widgettype": "Text", "options": {"text": "合同总数", "style": {"fontSize": "14px", "color": "#666"}}},
|
||||||
|
{"widgettype": "Text", "id": "kpi_contracts", "options": {"text": "--", "style": {"fontSize": "28px", "fontWeight": "bold", "color": "#D97706"}}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {"style": {"flex": 1, "minWidth": "200px", "backgroundColor": "#fff", "borderRadius": "8px", "padding": "16px", "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"}},
|
||||||
|
"subwidgets": [
|
||||||
|
{"widgettype": "Text", "options": {"text": "应收总额", "style": {"fontSize": "14px", "color": "#666"}}},
|
||||||
|
{"widgettype": "Text", "id": "kpi_receivables", "options": {"text": "--", "style": {"fontSize": "28px", "fontWeight": "bold", "color": "#DC2626"}}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {"style": {"gap": "16px", "flexWrap": "wrap"}},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {"style": {"flex": 2, "minWidth": "400px", "backgroundColor": "#fff", "borderRadius": "8px", "padding": "16px", "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"}},
|
||||||
|
"subwidgets": [
|
||||||
|
{"widgettype": "Text", "options": {"text": "销售漏斗", "style": {"fontSize": "16px", "fontWeight": "bold", "marginBottom": "12px"}}},
|
||||||
|
{"widgettype": "DataGrid", "id": "funnel_grid", "options": {"url": "{{entire_url('api/dashboard_kpi.dspy')}}?type=sales_funnel", "columns": [
|
||||||
|
{"field": "stage", "header": "阶段", "width": 120},
|
||||||
|
{"field": "count", "header": "数量", "width": 80},
|
||||||
|
{"field": "amount", "header": "金额", "width": 120}
|
||||||
|
]}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {"style": {"flex": 1, "minWidth": "300px", "backgroundColor": "#fff", "borderRadius": "8px", "padding": "16px", "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"}},
|
||||||
|
"subwidgets": [
|
||||||
|
{"widgettype": "Text", "options": {"text": "最新商机", "style": {"fontSize": "16px", "fontWeight": "bold", "marginBottom": "12px"}}},
|
||||||
|
{"widgettype": "DataGrid", "id": "recent_opp_grid", "options": {"url": "{{entire_url('api/dashboard_kpi.dspy')}}?type=recent_opportunities", "columns": [
|
||||||
|
{"field": "customer_name", "header": "客户", "width": 120},
|
||||||
|
{"field": "estimated_amount", "header": "金额", "width": 100},
|
||||||
|
{"field": "current_stage", "header": "阶段", "width": 80}
|
||||||
|
]}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
41
wwwroot/reports.ui
Normal file
41
wwwroot/reports.ui
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"widgettype": "Page",
|
||||||
|
"options": {
|
||||||
|
"title": "报表中心",
|
||||||
|
"style": {"height": "100%", "padding": "0"}
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {"style": {"padding": "16px", "flex": 1, "overflow": "auto"}},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {"style": {"marginBottom": "16px", "gap": "8px"}},
|
||||||
|
"subwidgets": [
|
||||||
|
{"widgettype": "TextField", "id": "report_search", "options": {"label": "搜索报表", "placeholder": "报表名称", "style": {"flex": 1}}},
|
||||||
|
{"widgettype": "Button", "id": "btn_search", "options": {"text": "搜索", "variant": "primary"}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "DataGrid",
|
||||||
|
"id": "report_grid",
|
||||||
|
"options": {
|
||||||
|
"url": "{{entire_url('api/report_list.dspy')}}",
|
||||||
|
"style": {"flex": 1},
|
||||||
|
"columns": [
|
||||||
|
{"field": "template_name", "header": "报表名称", "width": 200},
|
||||||
|
{"field": "category", "header": "分类", "width": 120},
|
||||||
|
{"field": "description", "header": "说明", "width": 300},
|
||||||
|
{"field": "is_active", "header": "状态", "width": 80},
|
||||||
|
{"field": "created_at", "header": "创建时间", "width": 160}
|
||||||
|
],
|
||||||
|
"toolbar": [
|
||||||
|
{"type": "button", "text": "查看", "icon": "view", "action": "navigate('main/unified_dashboard/report_view.ui?id={% raw %}{{selectedRow.id}}{% endraw %}')"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user