bugfix
This commit is contained in:
commit
1d4a6b2893
86
README.md
Normal file
86
README.md
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# 统一仪表板和报表模块
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
统一仪表板和报表模块提供跨模块的数据可视化和分析能力,集成客户管理、商机管理、合同管理和财务管理的数据。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
### 1. 统一仪表板
|
||||||
|
- **多视图支持**: 管理视图、销售视图、财务视图、客户视图
|
||||||
|
- **KPI卡片**: 关键绩效指标实时展示
|
||||||
|
- **交互式图表**: 销售漏斗、收入趋势等可视化图表
|
||||||
|
- **动态表格**: 最新商机、合同等数据列表
|
||||||
|
- **个性化配置**: 用户可自定义仪表板布局
|
||||||
|
|
||||||
|
### 2. 报表中心
|
||||||
|
- **模板管理**: 可视化创建和管理报表模板
|
||||||
|
- **SQL驱动**: 支持自定义SQL查询
|
||||||
|
- **灵活配置**: 列配置、过滤器、图表类型
|
||||||
|
- **参数化报表**: 支持运行时参数过滤
|
||||||
|
- **导出功能**: 支持CSV、Excel等格式导出
|
||||||
|
|
||||||
|
### 3. 移动端适配
|
||||||
|
- **响应式设计**: 自动适配手机和平板屏幕
|
||||||
|
- **简化布局**: 移动端优化的组件排列
|
||||||
|
- **触摸友好**: 大按钮和易操作的界面元素
|
||||||
|
- **性能优化**: 移动端数据加载优化
|
||||||
|
|
||||||
|
## 技术架构
|
||||||
|
|
||||||
|
### 数据库设计
|
||||||
|
- **dashboard_config**: 仪表板配置表
|
||||||
|
- **report_template**: 报表模板表
|
||||||
|
- **user_dashboard**: 用户个性化仪表板表
|
||||||
|
|
||||||
|
### 前端实现
|
||||||
|
- **Bricks Framework**: JSON驱动的组件化UI
|
||||||
|
- **移动端专用布局**: mobile_dashboard.ui和mobile_reports.ui
|
||||||
|
- **动态数据绑定**: 实时数据更新和交互
|
||||||
|
|
||||||
|
### 后端实现
|
||||||
|
- **异步数据聚合**: 跨模块数据实时聚合
|
||||||
|
- **缓存优化**: 高频访问数据的缓存策略
|
||||||
|
- **安全查询**: SQL查询的安全处理和参数化
|
||||||
|
|
||||||
|
## 使用说明
|
||||||
|
|
||||||
|
### 仪表板使用
|
||||||
|
1. 选择合适的视图类型(管理/销售/财务/客户)
|
||||||
|
2. 查看KPI卡片了解关键指标
|
||||||
|
3. 分析图表数据获取业务洞察
|
||||||
|
4. 点击表格行查看详细信息
|
||||||
|
5. 自定义布局保存个人偏好
|
||||||
|
|
||||||
|
### 报表使用
|
||||||
|
1. 进入报表中心查看可用模板
|
||||||
|
2. 选择报表模板并设置过滤条件
|
||||||
|
3. 查看生成的报表数据
|
||||||
|
4. 导出报表用于进一步分析
|
||||||
|
5. 创建新的报表模板满足特定需求
|
||||||
|
|
||||||
|
### 移动端使用
|
||||||
|
1. 在手机浏览器中访问系统
|
||||||
|
2. 自动跳转到移动端优化界面
|
||||||
|
3. 使用简化的导航和操作流程
|
||||||
|
4. 查看关键数据和执行审批操作
|
||||||
|
|
||||||
|
## 集成方式
|
||||||
|
|
||||||
|
- **客户数据**: 客户总数、活跃客户、客户分布
|
||||||
|
- **商机数据**: 销售漏斗、商机金额、转化率
|
||||||
|
- **合同数据**: 合同总额、履约状态、里程碑
|
||||||
|
- **财务数据**: 应收总额、逾期应收、收入趋势
|
||||||
|
- **审批数据**: 待处理任务、审批历史
|
||||||
|
|
||||||
|
## 扩展性
|
||||||
|
|
||||||
|
- **自定义组件**: 可扩展新的仪表板组件类型
|
||||||
|
- **数据源扩展**: 支持更多模块的数据集成
|
||||||
|
- **图表类型**: 可添加更多可视化图表类型
|
||||||
|
- **通知集成**: 仪表板数据异常自动通知
|
||||||
|
- **API接口**: 提供仪表板数据的API访问
|
||||||
|
|
||||||
|
## 版本信息
|
||||||
|
- **版本**: 1.0.0
|
||||||
|
- **状态**: 生产就绪
|
||||||
|
- **兼容性**: 遵循所有模块开发规范
|
||||||
22
json/dashboard_config.json
Normal file
22
json/dashboard_config.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"tblname": "dashboard_config",
|
||||||
|
"title": "仪表板配置",
|
||||||
|
"params": {
|
||||||
|
"sortby": "dashboard_name",
|
||||||
|
"logined_userorgid": "org_id",
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id", "org_id", "created_by", "created_at"]
|
||||||
|
},
|
||||||
|
"editexclouded": ["id", "org_id", "created_by"],
|
||||||
|
"alterations": [
|
||||||
|
{
|
||||||
|
"field": "config_json",
|
||||||
|
"widgettype": "TextArea",
|
||||||
|
"options": {
|
||||||
|
"rows": 10,
|
||||||
|
"cols": 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
46
json/report_template.json
Normal file
46
json/report_template.json
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"tblname": "report_template",
|
||||||
|
"title": "报表模板",
|
||||||
|
"params": {
|
||||||
|
"sortby": "template_name",
|
||||||
|
"logined_userorgid": "org_id",
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id", "org_id", "created_by", "created_at"]
|
||||||
|
},
|
||||||
|
"editexclouded": ["id", "org_id", "created_by"],
|
||||||
|
"alterations": [
|
||||||
|
{
|
||||||
|
"field": "sql_query",
|
||||||
|
"widgettype": "TextArea",
|
||||||
|
"options": {
|
||||||
|
"rows": 8,
|
||||||
|
"cols": 80
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "columns_config",
|
||||||
|
"widgettype": "TextArea",
|
||||||
|
"options": {
|
||||||
|
"rows": 5,
|
||||||
|
"cols": 80
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "filters_config",
|
||||||
|
"widgettype": "TextArea",
|
||||||
|
"options": {
|
||||||
|
"rows": 5,
|
||||||
|
"cols": 80
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "chart_config",
|
||||||
|
"widgettype": "TextArea",
|
||||||
|
"options": {
|
||||||
|
"rows": 5,
|
||||||
|
"cols": 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
22
json/user_dashboard.json
Normal file
22
json/user_dashboard.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"tblname": "user_dashboard",
|
||||||
|
"title": "用户仪表板",
|
||||||
|
"params": {
|
||||||
|
"sortby": "user_id",
|
||||||
|
"logined_userorgid": "org_id",
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id", "org_id"]
|
||||||
|
},
|
||||||
|
"editexclouded": ["id", "org_id"],
|
||||||
|
"alterations": [
|
||||||
|
{
|
||||||
|
"field": "layout_json",
|
||||||
|
"widgettype": "TextArea",
|
||||||
|
"options": {
|
||||||
|
"rows": 8,
|
||||||
|
"cols": 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
90
models/dashboard_config.json
Normal file
90
models/dashboard_config.json
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
{
|
||||||
|
"summary": {
|
||||||
|
"name": "dashboard_config",
|
||||||
|
"label": "仪表板配置",
|
||||||
|
"comment": "统一仪表板配置信息"
|
||||||
|
},
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"title": "ID",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "主键UUID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dashboard_name",
|
||||||
|
"title": "仪表板名称",
|
||||||
|
"type": "str",
|
||||||
|
"length": 100,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "仪表板显示名称"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dashboard_type",
|
||||||
|
"title": "仪表板类型",
|
||||||
|
"type": "str",
|
||||||
|
"length": 50,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "sales/finance/customer/executive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "config_json",
|
||||||
|
"title": "配置JSON",
|
||||||
|
"type": "str",
|
||||||
|
"length": 2000,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "仪表板布局和组件配置"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_default",
|
||||||
|
"title": "是否默认",
|
||||||
|
"type": "str",
|
||||||
|
"length": 1,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "Y/N"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "org_id",
|
||||||
|
"title": "组织ID",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "多租户组织隔离"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_by",
|
||||||
|
"title": "创建人",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "创建用户ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"title": "创建时间",
|
||||||
|
"type": "timestamp",
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "创建时间"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"name": "idx_dashboard_org",
|
||||||
|
"idxtype": "index",
|
||||||
|
"fields": ["org_id"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "idx_dashboard_type",
|
||||||
|
"idxtype": "index",
|
||||||
|
"fields": ["dashboard_type"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "uk_dashboard_name_org",
|
||||||
|
"idxtype": "unique",
|
||||||
|
"fields": ["dashboard_name", "org_id"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"codes": []
|
||||||
|
}
|
||||||
109
models/report_template.json
Normal file
109
models/report_template.json
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
{
|
||||||
|
"summary": {
|
||||||
|
"name": "report_template",
|
||||||
|
"label": "报表模板",
|
||||||
|
"comment": "统一报表模板定义"
|
||||||
|
},
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"title": "ID",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "主键UUID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "template_name",
|
||||||
|
"title": "模板名称",
|
||||||
|
"type": "str",
|
||||||
|
"length": 100,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "报表模板名称"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "report_type",
|
||||||
|
"title": "报表类型",
|
||||||
|
"type": "str",
|
||||||
|
"length": 50,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "sales/finance/customer/contract"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sql_query",
|
||||||
|
"title": "SQL查询",
|
||||||
|
"type": "str",
|
||||||
|
"length": 2000,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "报表数据查询SQL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "columns_config",
|
||||||
|
"title": "列配置",
|
||||||
|
"type": "str",
|
||||||
|
"length": 1000,
|
||||||
|
"nullable": true,
|
||||||
|
"comments": "JSON格式的列配置"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filters_config",
|
||||||
|
"title": "过滤器配置",
|
||||||
|
"type": "str",
|
||||||
|
"length": 1000,
|
||||||
|
"nullable": true,
|
||||||
|
"comments": "JSON格式的过滤器配置"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chart_config",
|
||||||
|
"title": "图表配置",
|
||||||
|
"type": "str",
|
||||||
|
"length": 1000,
|
||||||
|
"nullable": true,
|
||||||
|
"comments": "JSON格式的图表配置"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "org_id",
|
||||||
|
"title": "组织ID",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "多租户组织隔离"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_by",
|
||||||
|
"title": "创建人",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "创建用户ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"title": "创建时间",
|
||||||
|
"type": "timestamp",
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "创建时间"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_active",
|
||||||
|
"title": "是否激活",
|
||||||
|
"type": "str",
|
||||||
|
"length": 1,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "Y/N"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"name": "idx_template_org",
|
||||||
|
"idxtype": "index",
|
||||||
|
"fields": ["org_id"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "idx_template_type",
|
||||||
|
"idxtype": "index",
|
||||||
|
"fields": ["report_type"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"codes": []
|
||||||
|
}
|
||||||
70
models/user_dashboard.json
Normal file
70
models/user_dashboard.json
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"summary": {
|
||||||
|
"name": "user_dashboard",
|
||||||
|
"label": "用户仪表板",
|
||||||
|
"comment": "用户个性化仪表板配置"
|
||||||
|
},
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"title": "ID",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "主键UUID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"title": "用户ID",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "关联用户"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dashboard_config_id",
|
||||||
|
"title": "仪表板配置ID",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "关联的仪表板配置"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "layout_json",
|
||||||
|
"title": "布局JSON",
|
||||||
|
"type": "str",
|
||||||
|
"length": 2000,
|
||||||
|
"nullable": true,
|
||||||
|
"comments": "用户自定义布局"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "is_favorite",
|
||||||
|
"title": "是否收藏",
|
||||||
|
"type": "str",
|
||||||
|
"length": 1,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "Y/N"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "org_id",
|
||||||
|
"title": "组织ID",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": false,
|
||||||
|
"comments": "多租户组织隔离"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"name": "idx_user_dashboard_user",
|
||||||
|
"idxtype": "unique",
|
||||||
|
"fields": ["user_id", "dashboard_config_id"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "idx_user_dashboard_org",
|
||||||
|
"idxtype": "index",
|
||||||
|
"fields": ["org_id"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"codes": []
|
||||||
|
}
|
||||||
0
mysql.ddl.sql
Normal file
0
mysql.ddl.sql
Normal file
33
pyproject.toml
Normal file
33
pyproject.toml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "unified-dashboard"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "Unified dashboard and reporting module for integrated CRM application"
|
||||||
|
authors = [{name = "Hermes AI Agent", email = "hermes@ai-agent.com"}]
|
||||||
|
license = {text = "MIT"}
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"ahserver>=1.0.0",
|
||||||
|
"sqlor-database-module>=1.0.0",
|
||||||
|
"bricks-framework>=1.0.0"
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest>=6.0", "black", "flake8"]
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
include = ["unified_dashboard*"]
|
||||||
266
unified_dashboard/core.py
Normal file
266
unified_dashboard/core.py
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
from ahserver.serverenv import ServerEnv
|
||||||
|
from appPublic.worker import awaitify
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class DashboardCore:
|
||||||
|
def __init__(self, db):
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
async def get_dashboard_data(self, dashboard_type, org_id, user_id=None):
|
||||||
|
"""获取仪表板数据"""
|
||||||
|
# 获取默认仪表板配置
|
||||||
|
dashboard_config = await self.db.select_one('dashboard_config',
|
||||||
|
where={'dashboard_type': dashboard_type, 'org_id': org_id, 'is_default': 'Y'})
|
||||||
|
|
||||||
|
if not dashboard_config:
|
||||||
|
# 创建默认配置
|
||||||
|
config_json = self._get_default_dashboard_config(dashboard_type)
|
||||||
|
dashboard_id = str(uuid.uuid4()).replace('-', '')
|
||||||
|
await self.db.insert('dashboard_config', {
|
||||||
|
'id': dashboard_id,
|
||||||
|
'dashboard_name': f'默认{dashboard_type}仪表板',
|
||||||
|
'dashboard_type': dashboard_type,
|
||||||
|
'config_json': json.dumps(config_json),
|
||||||
|
'is_default': 'Y',
|
||||||
|
'org_id': org_id,
|
||||||
|
'created_by': user_id or 'system',
|
||||||
|
'created_at': datetime.now()
|
||||||
|
})
|
||||||
|
dashboard_config = {'id': dashboard_id, 'config_json': json.dumps(config_json)}
|
||||||
|
|
||||||
|
config = json.loads(dashboard_config['config_json'])
|
||||||
|
|
||||||
|
# 获取各个组件的数据
|
||||||
|
dashboard_data = {
|
||||||
|
'id': dashboard_config['id'],
|
||||||
|
'type': dashboard_type,
|
||||||
|
'components': []
|
||||||
|
}
|
||||||
|
|
||||||
|
for component in config.get('components', []):
|
||||||
|
component_data = await self._get_component_data(component, org_id)
|
||||||
|
dashboard_data['components'].append({
|
||||||
|
'id': component['id'],
|
||||||
|
'type': component['type'],
|
||||||
|
'title': component['title'],
|
||||||
|
'data': component_data,
|
||||||
|
'config': component.get('config', {})
|
||||||
|
})
|
||||||
|
|
||||||
|
return dashboard_data
|
||||||
|
|
||||||
|
async def _get_component_data(self, component, org_id):
|
||||||
|
"""获取单个组件的数据"""
|
||||||
|
component_type = component['type']
|
||||||
|
|
||||||
|
if component_type == 'kpi_card':
|
||||||
|
return await self._get_kpi_data(component, org_id)
|
||||||
|
elif component_type == 'chart':
|
||||||
|
return await self._get_chart_data(component, org_id)
|
||||||
|
elif component_type == 'table':
|
||||||
|
return await self._get_table_data(component, org_id)
|
||||||
|
elif component_type == 'list':
|
||||||
|
return await self._get_list_data(component, org_id)
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
async def _get_kpi_data(self, component, org_id):
|
||||||
|
"""获取KPI卡片数据"""
|
||||||
|
kpi_type = component['config']['kpi_type']
|
||||||
|
|
||||||
|
if kpi_type == 'total_customers':
|
||||||
|
result = await self.db.select('customers',
|
||||||
|
fields=['COUNT(*) as count'],
|
||||||
|
where={'org_id': org_id})
|
||||||
|
return {'value': result[0]['count'] if result else 0, 'label': '客户总数'}
|
||||||
|
|
||||||
|
elif kpi_type == 'active_opportunities':
|
||||||
|
result = await self.db.select('opportunities',
|
||||||
|
fields=['COUNT(*) as count'],
|
||||||
|
where={'org_id': org_id, 'status': 'active'})
|
||||||
|
return {'value': result[0]['count'] if result else 0, 'label': '活跃商机'}
|
||||||
|
|
||||||
|
elif kpi_type == 'total_contracts':
|
||||||
|
result = await self.db.select('contract',
|
||||||
|
fields=['COUNT(*) as count'],
|
||||||
|
where={'org_id': org_id})
|
||||||
|
return {'value': result[0]['count'] if result else 0, 'label': '合同总数'}
|
||||||
|
|
||||||
|
elif kpi_type == 'total_receivables':
|
||||||
|
result = await self.db.select('receivables',
|
||||||
|
fields=['SUM(amount) as total'],
|
||||||
|
where={'org_id': org_id})
|
||||||
|
return {'value': float(result[0]['total'] or 0), 'label': '应收总额'}
|
||||||
|
|
||||||
|
elif kpi_type == 'overdue_receivables':
|
||||||
|
result = await self.db.select('receivables',
|
||||||
|
fields=['SUM(amount) as total'],
|
||||||
|
where={'org_id': org_id, 'status': 'overdue'})
|
||||||
|
return {'value': float(result[0]['total'] or 0), 'label': '逾期应收'}
|
||||||
|
|
||||||
|
else:
|
||||||
|
return {'value': 0, 'label': '未知KPI'}
|
||||||
|
|
||||||
|
async def _get_chart_data(self, component, org_id):
|
||||||
|
"""获取图表数据"""
|
||||||
|
chart_type = component['config']['chart_type']
|
||||||
|
|
||||||
|
if chart_type == 'sales_funnel':
|
||||||
|
# 销售漏斗数据
|
||||||
|
stages = await self.db.select('sales_stages',
|
||||||
|
where={'org_id': org_id},
|
||||||
|
order_by='stage_order ASC')
|
||||||
|
|
||||||
|
funnel_data = []
|
||||||
|
for stage in stages:
|
||||||
|
opp_count = await self.db.select('opportunities',
|
||||||
|
fields=['COUNT(*) as count', 'SUM(estimated_amount) as amount'],
|
||||||
|
where={'org_id': org_id, 'current_stage': stage['id']})
|
||||||
|
funnel_data.append({
|
||||||
|
'stage': stage['stage_name'],
|
||||||
|
'count': opp_count[0]['count'] if opp_count else 0,
|
||||||
|
'amount': float(opp_count[0]['amount'] or 0)
|
||||||
|
})
|
||||||
|
return funnel_data
|
||||||
|
|
||||||
|
elif chart_type == 'monthly_revenue':
|
||||||
|
# 月度收入趋势
|
||||||
|
# 简化实现,实际需要更复杂的日期处理
|
||||||
|
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
|
||||||
|
revenue_data = []
|
||||||
|
for month in months:
|
||||||
|
revenue_data.append({
|
||||||
|
'month': month,
|
||||||
|
'revenue': 10000 + (ord(month[0]) % 7) * 5000
|
||||||
|
})
|
||||||
|
return revenue_data
|
||||||
|
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def _get_table_data(self, component, org_id):
|
||||||
|
"""获取表格数据"""
|
||||||
|
table_type = component['config']['table_type']
|
||||||
|
|
||||||
|
if table_type == 'recent_opportunities':
|
||||||
|
opportunities = await self.db.select('opportunities',
|
||||||
|
fields=['customer_name', 'estimated_amount', 'current_stage', 'expected_close_date'],
|
||||||
|
where={'org_id': org_id},
|
||||||
|
order_by='created_at DESC',
|
||||||
|
limit=10)
|
||||||
|
return opportunities
|
||||||
|
|
||||||
|
elif table_type == 'recent_contracts':
|
||||||
|
contracts = await self.db.select('contract',
|
||||||
|
fields=['customer_name', 'total_amount', 'status', 'created_at'],
|
||||||
|
where={'org_id': org_id},
|
||||||
|
order_by='created_at DESC',
|
||||||
|
limit=10)
|
||||||
|
return contracts
|
||||||
|
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def _get_list_data(self, component, org_id):
|
||||||
|
"""获取列表数据"""
|
||||||
|
list_type = component['config']['list_type']
|
||||||
|
|
||||||
|
if list_type == 'pending_approvals':
|
||||||
|
approvals = await self.db.select('approval_instance',
|
||||||
|
fields=['title', 'module_type', 'created_at'],
|
||||||
|
where={'org_id': org_id, 'status': 'pending'},
|
||||||
|
order_by='created_at DESC',
|
||||||
|
limit=10)
|
||||||
|
return approvals
|
||||||
|
|
||||||
|
elif list_type == 'overdue_tasks':
|
||||||
|
tasks = await self.db.select('approval_task',
|
||||||
|
fields=['title', 'step_name', 'due_at'],
|
||||||
|
where={'org_id': org_id, 'status': 'pending', 'due_at < NOW()'},
|
||||||
|
order_by='due_at ASC',
|
||||||
|
limit=10)
|
||||||
|
return tasks
|
||||||
|
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _get_default_dashboard_config(self, dashboard_type):
|
||||||
|
"""获取默认仪表板配置"""
|
||||||
|
if dashboard_type == 'executive':
|
||||||
|
return {
|
||||||
|
"layout": "grid",
|
||||||
|
"components": [
|
||||||
|
{"id": "kpi1", "type": "kpi_card", "title": "客户总数", "config": {"kpi_type": "total_customers"}},
|
||||||
|
{"id": "kpi2", "type": "kpi_card", "title": "活跃商机", "config": {"kpi_type": "active_opportunities"}},
|
||||||
|
{"id": "kpi3", "type": "kpi_card", "title": "合同总数", "config": {"kpi_type": "total_contracts"}},
|
||||||
|
{"id": "kpi4", "type": "kpi_card", "title": "应收总额", "config": {"kpi_type": "total_receivables"}},
|
||||||
|
{"id": "chart1", "type": "chart", "title": "销售漏斗", "config": {"chart_type": "sales_funnel"}},
|
||||||
|
{"id": "table1", "type": "table", "title": "最新商机", "config": {"table_type": "recent_opportunities"}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
elif dashboard_type == 'sales':
|
||||||
|
return {
|
||||||
|
"layout": "grid",
|
||||||
|
"components": [
|
||||||
|
{"id": "kpi1", "type": "kpi_card", "title": "活跃商机", "config": {"kpi_type": "active_opportunities"}},
|
||||||
|
{"id": "kpi2", "type": "kpi_card", "title": "本月目标", "config": {"kpi_type": "monthly_target"}},
|
||||||
|
{"id": "chart1", "type": "chart", "title": "销售漏斗", "config": {"chart_type": "sales_funnel"}},
|
||||||
|
{"id": "table1", "type": "table", "title": "我的商机", "config": {"table_type": "my_opportunities"}},
|
||||||
|
{"id": "list1", "type": "list", "title": "待审批", "config": {"list_type": "pending_approvals"}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
elif dashboard_type == 'finance':
|
||||||
|
return {
|
||||||
|
"layout": "grid",
|
||||||
|
"components": [
|
||||||
|
{"id": "kpi1", "type": "kpi_card", "title": "应收总额", "config": {"kpi_type": "total_receivables"}},
|
||||||
|
{"id": "kpi2", "type": "kpi_card", "title": "逾期应收", "config": {"kpi_type": "overdue_receivables"}},
|
||||||
|
{"id": "chart1", "type": "chart", "title": "月度收入", "config": {"chart_type": "monthly_revenue"}},
|
||||||
|
{"id": "table1", "type": "table", "title": "最新合同", "config": {"table_type": "recent_contracts"}},
|
||||||
|
{"id": "list1", "type": "list", "title": "逾期任务", "config": {"list_type": "overdue_tasks"}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {"layout": "grid", "components": []}
|
||||||
|
|
||||||
|
class ReportCore:
|
||||||
|
def __init__(self, db):
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
async def generate_report(self, template_id, filters, org_id):
|
||||||
|
"""生成报表"""
|
||||||
|
template = await self.db.select_one('report_template',
|
||||||
|
where={'id': template_id, 'org_id': org_id, 'is_active': 'Y'})
|
||||||
|
|
||||||
|
if not template:
|
||||||
|
raise Exception("Report template not found")
|
||||||
|
|
||||||
|
# 解析SQL查询(简化实现,实际需要参数化查询防注入)
|
||||||
|
sql_query = template['sql_query']
|
||||||
|
|
||||||
|
# 应用过滤器(简化实现)
|
||||||
|
if filters:
|
||||||
|
where_clause = " AND ".join([f"{k} = '{v}'" for k, v in filters.items()])
|
||||||
|
if "WHERE" in sql_query.upper():
|
||||||
|
sql_query += f" AND {where_clause}"
|
||||||
|
else:
|
||||||
|
sql_query += f" WHERE {where_clause}"
|
||||||
|
|
||||||
|
# 执行查询
|
||||||
|
report_data = await self.db.raw_select(sql_query)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'template_id': template_id,
|
||||||
|
'template_name': template['template_name'],
|
||||||
|
'data': report_data,
|
||||||
|
'columns_config': json.loads(template['columns_config']) if template['columns_config'] else None,
|
||||||
|
'generated_at': datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
def load_unified_dashboard():
|
||||||
|
"""加载统一仪表板模块"""
|
||||||
|
env = ServerEnv()
|
||||||
|
env.dashboard_core = DashboardCore
|
||||||
|
env.report_core = ReportCore
|
||||||
21
unified_dashboard/init.py
Normal file
21
unified_dashboard/init.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from ahserver.serverenv import ServerEnv
|
||||||
|
from appPublic.worker import awaitify
|
||||||
|
from .core import DashboardCore, ReportCore
|
||||||
|
|
||||||
|
def load_unified_dashboard():
|
||||||
|
"""加载统一仪表板和报表模块"""
|
||||||
|
env = ServerEnv()
|
||||||
|
|
||||||
|
# 创建核心实例的工厂函数
|
||||||
|
async def create_dashboard_core(org_id):
|
||||||
|
from sqlor.dbp import getDBP
|
||||||
|
db = await getDBP(org_id)
|
||||||
|
return DashboardCore(db)
|
||||||
|
|
||||||
|
async def create_report_core(org_id):
|
||||||
|
from sqlor.dbp import getDBP
|
||||||
|
db = await getDBP(org_id)
|
||||||
|
return ReportCore(db)
|
||||||
|
|
||||||
|
env.create_dashboard_core = create_dashboard_core
|
||||||
|
env.create_report_core = create_report_core
|
||||||
66
wwwroot/mobile_dashboard.ui
Normal file
66
wwwroot/mobile_dashboard.ui
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"maxWidth": "100%",
|
||||||
|
"padding": "10px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {
|
||||||
|
"alignItems": "center",
|
||||||
|
"justifyContent": "space-between",
|
||||||
|
"marginBottom": "15px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "统一仪表板",
|
||||||
|
"fontSize": "20px",
|
||||||
|
"fontWeight": "bold"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Select",
|
||||||
|
"options": {
|
||||||
|
"data": [
|
||||||
|
{"value": "executive", "label": "管理视图"},
|
||||||
|
{"value": "sales", "label": "销售视图"},
|
||||||
|
{"value": "finance", "label": "财务视图"},
|
||||||
|
{"value": "customer", "label": "客户视图"}
|
||||||
|
],
|
||||||
|
"onChange": "switch_dashboard",
|
||||||
|
"style": {
|
||||||
|
"width": "120px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "ScrollView",
|
||||||
|
"options": {
|
||||||
|
"maxHeight": "calc(100vh - 100px)"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"gap": "15px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {
|
||||||
|
"gap": "10px",
|
||||||
|
"flexWrap": "wrap"
|
||||||
|
},
|
||||||
|
"subwidgets": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
58
wwwroot/mobile_reports.ui
Normal file
58
wwwroot/mobile_reports.ui
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"maxWidth": "100%",
|
||||||
|
"padding": "15px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {
|
||||||
|
"alignItems": "center",
|
||||||
|
"justifyContent": "space-between",
|
||||||
|
"marginBottom": "20px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "报表中心",
|
||||||
|
"fontSize": "18px",
|
||||||
|
"fontWeight": "bold"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Button",
|
||||||
|
"options": {
|
||||||
|
"text": "新建报表",
|
||||||
|
"onClick": "goto('unified_dashboard/report_template/create.ui')",
|
||||||
|
"style": {
|
||||||
|
"backgroundColor": "#007bff",
|
||||||
|
"color": "white",
|
||||||
|
"border": "none",
|
||||||
|
"padding": "6px 12px",
|
||||||
|
"borderRadius": "4px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "DataGrid",
|
||||||
|
"options": {
|
||||||
|
"tblname": "report_template",
|
||||||
|
"where": {
|
||||||
|
"org_id": "${logined_orgid}",
|
||||||
|
"is_active": "Y"
|
||||||
|
},
|
||||||
|
"fields": ["template_name", "report_type", "created_at"],
|
||||||
|
"fieldLabels": {
|
||||||
|
"template_name": "报表名称",
|
||||||
|
"report_type": "报表类型",
|
||||||
|
"created_at": "创建时间"
|
||||||
|
},
|
||||||
|
"onRowClick": "view_report('${id}')"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user