基于Python的中药药材数据可视化分析系统 毕业论文
毕业设计
一、项目名称:
基于Python的中药药材数据可视化分析系统
【摘要】
中药药材信息分散在 SymMap 属性库、中药网百科条目与各类业务表格中,药名、功效分类、性味归经、毒性等级等字段口径不一,难以形成统一查询与可视化分析流程。本文设计并实现了基于 Python 与 Django 的中药药材数据可视化分析系统,面向教学与课题演示场景,将多源数据清洗入库后,以交互式图表支撑药材属性结构分析与知识关联探索。
系统采用 B/S 架构,后端基于 Django 3.1.14,管理端集成 SimpleUI;数据库使用 MySQL(库名 tcm),通过 PyMySQL 驱动接入。前台基于 Bootstrap 5 与 ECharts 5 完成可视化,主题样式由 tcm-theme.css 统一为墨绿色调,登录页背景为 tcm-bg.jpg,独立数据大屏使用 tcm-bigscreen.css 与 tcm_bigscreen.js。业务数据以 HerbRecord 为核心,约 698 条药材记录来源于 SymMap 2.0 SMHB 公开数据;EncyclopediaRecord 约 899 条百科条目来源于中药网公开页面。可信字段包括药名、功效分类、药性、药味、归经、毒性;道地产区为半可信字段(优先由百科「产地分布」解析);单价、库存、销量等商业字段为演示估算,不参与统计图表 。数据采集通过 scripts/fetch_symmap_herbs.py、scripts/fetch_zhongyoo_encyclopedia.py 生成 CSV,再经 import_herbs、import_encyclopedia 管理命令入库。分析层采用 scipy 卡方检验、KMeans 聚类结合 PCA 与 StandardScaler 降维、mlxtend Apriori 关联规则挖掘(默认支持度 0.02、置信度 0.4),关系图谱为手工共现网络构建。系统入口为 manage.py,默认运行端口 8020 ;前台登录路径为 /app/login/,后台为 /admin/,默认账号分别为 admin/123456(前台 app.User)与 admin/admin123(Django auth.User)。侧栏涵盖首页仪表盘、属性分析、产地地图、关系图谱、聚类分析、关联规则、药材列表、中药百科、数据大屏及个人信息维护等模块;/app/overview/ 与 /app/stat/ 已重定向至 /app/attributes/。utils 层封装 tcm_core、tcm_charts、tcm_constants,分析结果经 Bundle API 一次返回多图数据,本地内存缓存 TCM_CACHE_TTL=120 秒。
测试与运行结果表明,系统能够完成登录鉴权、多维筛选分析、图表联动、百科浏览、数据大屏展示与 CSV 导出闭环,界面清晰、响应稳定,可为中药药材数据分析课题研究与教学演示提供可用的可视化分析支撑。
【关键词】 Django;中药药材;数据可视化;ECharts;关联规则挖掘
【Abstract】
Traditional Chinese medicine (TCM) herb information is scattered across SymMap attribute databases, public encyclopedia entries and various business spreadsheets, making unified querying and visualization difficult. This thesis designs and implements a Python- and Django-based TCM herb data visualization and analysis system for teaching and demonstration. Multi-source data are collected, cleaned and stored, then explored through interactive charts for structural analysis and knowledge association.
The system follows a B/S architecture. The backend is built with Django 3.1.14 and SimpleUI; MySQL database name is tcm with PyMySQL driver. The frontend adopts Bootstrap 5 and ECharts 5 with a dark-green theme (tcm-theme.css), login background tcm-bg.jpg, and a standalone big-screen page (tcm-bigscreen.css/js). Core entity HerbRecord contains approximately 698 records from SymMap 2.0; EncyclopediaRecord contains about 899 entries from zhongyoo.com. Trusted fields include herb name, efficacy category, property nature, taste, meridian and toxicity; origin provinces are semi-trusted; price and stock fields are illustrative only and excluded from charts. Data are produced by fetch scripts and imported via management commands. Analysis uses scipy chi-square test, KMeans with PCA and StandardScaler, mlxtend Apriori (default support 0.02, confidence 0.4), and a hand-built co-occurrence graph. Entry point is manage.py, default port 8020 ; frontend login /app/login/ (admin/123456), admin /admin/ (admin/admin123). Utils packages tcm_core, tcm_charts and tcm_constants; Bundle APIs return multi-chart payloads with local cache TTL of 120 seconds. Routes /app/overview/ and /app/stat/ redirect to /app/attributes/.
Results show that authentication, multi-dimensional filtering, chart interaction, encyclopedia browsing, big-screen display and CSV export workflows work stably, providing practical support for TCM data analysis courses and research demos.
【Keywords】 Django; TCM Herbs; Data Visualization; ECharts; Association Rule Mining
目 录
[1 绪 论](#1 绪 论)
- [1.1 课题研究背景](#1.1 课题研究背景)
- [1.2 课题研究的目的、意义](#1.2 课题研究的目的、意义)
- [1.3 课题的国内外研究现状和发展动态](#1.3 课题的国内外研究现状和发展动态)
- [1.4 研究内容](#1.4 研究内容)
- [1.5 论文结构安排](#1.5 论文结构安排)
[2 关键技术](#2 关键技术)
- [2.1 Django Web框架](#2.1 Django Web框架)
- [2.1.1 MTV模式与路由组织](#2.1.1 MTV模式与路由组织)
- [2.1.2 SimpleUI后台管理](#2.1.2 SimpleUI后台管理)
- [2.2 MySQL数据库](#2.2 MySQL数据库)
- [2.2.1 ORM与索引设计](#2.2.1 ORM与索引设计)
- [2.2.2 PyMySQL驱动接入](#2.2.2 PyMySQL驱动接入)
- [2.3 ECharts可视化技术](#2.3 ECharts可视化技术)
- [2.4 数据分析与算法实现](#2.4 数据分析与算法实现)
- [2.5 数据获取与字段可信度](#2.5 数据获取与字段可信度)
- [2.6 技术特色与创新点](#2.6 技术特色与创新点)
[3 系统分析与设计](#3 系统分析与设计)
- [3.1 系统总体设计](#3.1 系统总体设计)
- [3.1.1 系统架构设计](#3.1.1 系统架构设计)
- [3.1.2 系统数据流设计](#3.1.2 系统数据流设计)
- [3.1.3 系统功能模块设计](#3.1.3 系统功能模块设计)
- [3.2 系统详细设计](#3.2 系统详细设计)
- [3.2.1 用户认证模块设计](#3.2.1 用户认证模块设计)
- [3.2.2 首页仪表盘模块设计](#3.2.2 首页仪表盘模块设计)
- [3.2.3 属性分析模块设计](#3.2.3 属性分析模块设计)
- [3.2.4 产地与关系图谱模块设计](#3.2.4 产地与关系图谱模块设计)
- [3.2.5 聚类与关联规则模块设计](#3.2.5 聚类与关联规则模块设计)
- [3.2.6 百科列表大屏与后台模块设计](#3.2.6 百科列表大屏与后台模块设计)
- [3.3 数据库设计](#3.3 数据库设计)
- [3.3.1 数据库关系设计](#3.3.1 数据库关系设计)
[4 系统实现](#4 系统实现)
- [4.0 系统整体实现流程](#4.0 系统整体实现流程)
- [4.1 用户认证模块的实现](#4.1 用户认证模块的实现)
- [4.2 首页仪表盘模块的实现](#4.2 首页仪表盘模块的实现)
- [4.3 属性分析模块的实现](#4.3 属性分析模块的实现)
- [4.4 产地与关系图谱模块的实现](#4.4 产地与关系图谱模块的实现)
- [4.5 聚类与关联规则模块的实现](#4.5 聚类与关联规则模块的实现)
- [4.6 百科列表大屏与后台模块的实现](#4.6 百科列表大屏与后台模块的实现)
- [4.7 用户界面实现](#4.7 用户界面实现)
- [4.8 系统集成与部署](#4.8 系统集成与部署)
[5 系统测试](#5 系统测试)
- [5.1 系统功能测试](#5.1 系统功能测试)
- [5.1.1 用户认证功能测试用例](#5.1.1 用户认证功能测试用例)
- [5.1.2 数据管理功能测试用例](#5.1.2 数据管理功能测试用例)
- [5.1.3 核心业务功能测试用例](#5.1.3 核心业务功能测试用例)
- [5.1.4 数据分析功能测试用例](#5.1.4 数据分析功能测试用例)
- [5.1.5 系统集成功能测试用例](#5.1.5 系统集成功能测试用例)
- [5.1.6 用户界面功能测试用例](#5.1.6 用户界面功能测试用例)
- [5.2 系统性能测试](#5.2 系统性能测试)
- [5.2.1 响应时间性能分析](#5.2.1 响应时间性能分析)
- [5.2.2 并发性能测试](#5.2.2 并发性能测试)
- [5.2.3 数据库性能测试](#5.2.3 数据库性能测试)
- [5.2.4 性能优化措施](#5.2.4 性能优化措施)
[6 总结与展望](#6 总结与展望)
- [6.1 总结](#6.1 总结)
- [6.2 展望](#6.2 展望)
1 绪 论
1.1 课题研究背景
中医药是中华优秀传统文化的重要组成部分,药材的性味归经、功效分类与毒性标注是临床配伍与教学研究的基础信息。然而,公开数据往往分散在不同平台:SymMap 提供结构化属性字段,中药网百科提供图文知识条目,各类业务表格又包含价格、库存等运营字段。字段命名与可信度层级不一致,手工汇总成本高,也难以形成可复用的 Web 可视化分析流程。
在高校毕业设计与教学演示场景中,常见需求包括:一是需要一套可本地部署的系统,用统一库表承载数百种药材样本;二是需要属性分布、产地地图、关系图谱、聚类与关联规则等分析视图,帮助理解「清热药多寒/凉性」「某产区药材聚集」等规律;三是需要脚本化数据获取与 CSV 导入闭环,形成「拉取---入库---分析---展示」流程。基于上述背景,本课题以 Django 为主框架,构建中药药材数据可视化分析系统:属性字段以 SymMap 2.0 公开数据为主,百科知识以中药网公开条目为辅,论文与 data/DATA_SOURCES.md 均明确标注字段可信度,价格与库存类估算字段仅用于列表展示,不参与统计图表。
系统提供独立数据大屏路由 /app/bigscreen/,采用墨绿主题与专用前端脚本,满足答辩演示时的全景态势展示需求;同时保留常规侧栏分析页,便于逐模块讲解算法与数据来源。
1.2 课题研究的目的、意义
1.2.1 研究目的
本课题的主要目的包括:
- 建立以
HerbRecord与EncyclopediaRecord为核心的中药数据模型,覆盖 SymMap 可信属性与百科知识字段,支撑统一查询与聚合。 - 实现前台会话登录、侧栏导航与多分析页展示,路由统一挂载在
/app/前缀下,服务端口为 8020。 - 基于 pandas、scipy、scikit-learn、mlxtend 完成卡方检验、KMeans 聚类、Apriori 关联规则与手工共现关系图谱。
- 通过
fetch_symmap_herbs.py、fetch_zhongyoo_encyclopedia.py与管理命令完成数据初始化;后台 SimpleUI 维护药材记录、百科条目与前台用户。 - 输出可运行、可演示的完整系统,默认前台账号 admin/123456、后台 admin/admin123,便于验收与二次扩展。
1.2.2 研究意义
理论意义:将 Web 开发、数据挖掘与可视化方法结合到中药药材主题中,形成「数据模型---分析算法---图表表达」的可复用范式,有助于理解分类字段 one-hot 编码、卡方独立性检验与关联规则在中医属性分析中的适用边界。
实践意义:系统把 SymMap 属性、百科知识与估算商业字段分层管理,降低手工处理成本;分析页通过 Bundle API 一次返回多图数据,便于前端快速渲染;缓存机制减轻重复聚合开销。
社会意义:可视化结果有助于直观认识功效分类结构、道地产区分布与毒性标注情况,提升公众与学生对中药知识体系的认知。
经济意义:本地化部署与开源技术栈降低课题演示成本;模块化 utils 设计便于后续替换数据源或扩展指标,减少重复开发投入。
1.3 课题的国内外研究现状和发展动态
1.3.1 国外研究现状
国外在生物医学知识图谱、网络药理学与公开数据库建设方面积累较早。SymMap 等数据库为研究提供了可程序化获取的药材---成分---靶点关联。可视化方面,D3.js、ECharts 等库被广泛用于交互式网络图与统计图表。关联规则挖掘(Apriori)与聚类分析(K-Means)常用于发现属性共现模式,但需注意小样本与高维稀疏矩阵下的解释边界。
1.3.2 国内研究现状
国内中医药信息化持续推进,SymMap、TCMSP 等公开库为教学与科研提供结构化字段;高校毕业设计中常见 Django + ECharts 的可视化系统,主题涵盖药材档案、方剂配伍与产地分析等。部分系统侧重大屏展示,部分侧重后台 CRUD。本课题更强调「六大功能模块 + Bundle API + 脚本化入库 + 字段可信度分层」,并以 MySQL 持久化约 698 条 SymMap 样本与约 899 条百科条目,兼顾可解释算法与可复现部署。
1.3.3 发展趋势
未来中药数据分析将更强调多源融合、知识对齐与可解释挖掘。可视化从静态报表走向可筛选、可下钻的交互分析;后端则更注重缓存、索引与版本化失效。对本系统而言,后续可扩展更多权威数据源、引入图数据库存储复杂关系网络,但仍应保持教学场景下结构清晰、部署简单的特点。
1.4 研究内容
本课题主要研究内容如下:
- 需求分析与总体设计:明确用户角色(前台用户、后台管理员)、功能边界与非功能约束(端口 8020、库名 tcm、overview/stat 重定向、价格库存不上图)。
- 数据模型与入库 :设计
User、HerbRecord、EncyclopediaRecord业务表,完成迁移、CSV 导入与索引优化;说明 SymMap 可信字段、百科半可信产区与演示估算字段的分层策略。 - 分析与可视化:实现首页仪表盘、属性分析、产地地图、关系图谱、聚类分析、关联规则、药材列表、百科浏览、数据大屏与 CSV 导出。
- 数据获取脚本 :实现 SymMap 与中药网抓取脚本,经
import_herbs、import_encyclopedia、backfill_origin_provinces批量入库。 - 测试与总结:按功能与性能维度组织用例,量化模块规模与数据量级,给出改进方向。
1.5 论文结构安排
全文共六章。第1章介绍背景、意义、现状与研究内容;第2章说明 Django、MySQL、ECharts、分析算法、数据获取与字段可信度等关键技术;第3章给出架构、数据流、功能结构、六大模块详细设计与数据库 ER;第4章按模块阐述实现流程、关键代码、访问路径与界面图题;第5章给出功能与性能测试;第6章总结成果并展望后续工作。文末附参考文献与致谢。
2 关键技术
2.1 Django Web框架
本系统后端采用 Django 3.1.14。Django 提供成熟的 ORM、模板引擎、中间件与管理后台,适合快速构建以数据查询与页面渲染为主的分析型 Web 应用。项目入口为根目录 manage.py,配置位于 基于Django系统/settings.py,根路由在 基于Django系统/urls.py 中将 admin/ 与 app/ 分别挂载。
2.1.1 MTV模式与路由组织
Django 遵循 MTV(Model-Template-View)模式:app/models.py 定义 User、HerbRecord 与 EncyclopediaRecord;app/views.py 处理页面渲染与用户操作;app/api_views.py 处理 JSON Bundle;模板位于 app/templates 与 templates。业务路由集中在 app/urls.py,统一由 /app/ 前缀引入,例如登录 /app/login/、首页 /app/home/、属性分析 /app/attributes/、数据大屏 /app/bigscreen/ 等。自定义中间件 middleware.auth.AuthMiddleware 用于会话鉴权,保护分析页访问。历史路由 /app/overview/ 与 /app/stat/ 在视图中重定向至 module_attributes,不再提供独立页面。
2.1.2 SimpleUI后台管理
系统在 INSTALLED_APPS 中引入 simpleui,通过 SIMPLEUI_CONFIG 定制菜单,将「中药药材数据」与「用户与权限」分组展示,后台入口为 /admin/。SimpleUI 在默认 Django Admin 之上提供更清晰的中文管理界面,便于对 HerbRecord、EncyclopediaRecord 进行检索、筛选与维护;增删改后调用 invalidate_tcm_cache() 递增 tcm:cache_version,使分析缓存失效,与前台分析共用同一 MySQL 库 tcm。
2.2 MySQL数据库
持久化层使用 MySQL,默认库名为 tcm,连接参数可通过环境变量 MYSQL_DATABASE、MYSQL_USER、MYSQL_PASSWORD、MYSQL_HOST、MYSQL_PORT 覆盖。字符集配置为 utf8mb4,满足中文药名、归经与百科长文本字段存储需求。
2.2.1 ORM与索引设计
HerbRecord 对 herb_code、name、efficacy_category、origin_province 等字段建立单列索引,并在 Meta 中声明组合索引 idx_herb_efficacy(功效分类+药名)、idx_herb_origin(道地产区+药名),以加速按分类与产区过滤的聚合查询。EncyclopediaRecord 对 name 建立索引 idx_ency_name。分析模块大量使用 Count、values().annotate() 等聚合,再将结果转为 pandas DataFrame 进一步计算卡方、聚类与关联规则。
2.2.2 PyMySQL驱动接入
依赖清单 requirements.txt 包含 PyMySQL,作为 Django MySQL 后端的常用驱动方案,配合 django.db.backends.mysql 完成连接。开发环境默认密码与 run_setup.bat 中初始化流程一致,便于一键迁移与导入。
2.3 ECharts可视化技术
前台图表基于 ECharts 5,页面骨架使用 Bootstrap 5。分析页通过 AJAX 请求 /app/api/attributes-bundle/、/app/api/origin-bundle/、/app/api/cluster-bundle/ 等接口获取 JSON,再在浏览器端初始化饼图、柱状图、热力图、散点、地图与关系网络等组件。图表配置与公共辅助脚本位于 static/js/(如 tcm_pages.js、chart_helpers.js),主题样式由 tcm-theme.css 统一为墨绿色调(主色 #1B4D3E),登录背景使用 tcm-bg.jpg。独立数据大屏 /app/bigscreen/ 采用 tcm-bigscreen.css 与 tcm_bigscreen.js,请求 /app/api/bigscreen-bundle/ 渲染 KPI、地图与分类占比。
2.4 数据分析与算法实现
数据处理依赖 pandas、NumPy、scipy、scikit-learn、mlxtend。核心逻辑封装在 app/utils/tcm_core.py 与 app/utils/tcm_charts.py:
- 卡方检验 :对药性(寒/凉/平/温/热)与毒性等级构建列联表,调用
scipy.stats.chi2_contingency计算 χ²、自由度与 p 值,p<0.05 时标记为显著关联(_chi_square_property_toxicity)。 - 功效×药性共现热力图 :对 Top 功效分类与 Top 药性做交叉计数,生成矩阵供前端热力渲染(
_efficacy_property_matrix)。 - K-Means 聚类 :将功效分类、药性、药味、毒性等字段 one-hot 编码后
StandardScaler标准化,默认 4 簇(可通过clusters参数在 2~8 间调整),KMeans(random_state=42, n_init=10)聚类,再用PCA(n_components=2)投影至二维散点(_build_cluster_payload)。 - Apriori 关联规则 :对功效分类、药性、药味、道地产区、毒性等类别字段构造事务,
mlxtend.frequent_patterns.apriori挖掘频繁项集,association_rules按置信度筛选;默认min_support=0.02、min_confidence=0.4;若 mlxtend 不可用或样本不足,回退至两两共现_simple_cooccurrence。 - 关系图谱 :手工遍历药材记录,以药材节点连接功效分类、药性、道地产区、归经(拆分前 2 段)属性节点,形成共现网络(
graph_bundle),非图数据库自动推理。 - 缓存 :
cached_analysis_df结合TCM_CACHE_TTL=120秒及tcm:cache_version版本号,在筛选条件(category、q)不变时复用 DataFrame;聚类结果缓存倍数为 3×TTL。
图表说明与 KPI 组装位于 tcm_charts.py,常量与 CSV 列映射位于 tcm_constants.py,保证前后台字段中文名与计算口径一致。分析 DataFrame 仅加载 ANALYSIS_FIELDS(name、meridian 及 CATEGORICAL_FIELDS),不包含 price_per_g、stock_kg 等演示数值字段。
2.5 数据获取与字段可信度
数据采集由命令行脚本执行,非 Web 页面触发。
数据集一:SymMap 药材业务表
| 项目 | 说明 |
|---|---|
| 来源 | SymMap 2.0 SMHB,http://symmap.org/download/ |
| 本地缓存 | data/raw/symmap/SymMap_v2.0_SMHB_file.xlsx,约 698 条 |
| 输出 CSV | tcm_herbs_data.csv |
| 入库表 | app_herbrecord |
| 命令 | python scripts/fetch_symmap_herbs.py → python manage.py import_herbs --path tcm_herbs_data.csv --truncate → python manage.py backfill_origin_provinces |
性味、归经、功效分类、毒性来自 SymMap 解析;道地产区优先从百科 hurbTable.csv 的「产地分布」解析;单价、库存、供应商、常用剂量等为脚本 seed 估算。
数据集二:中药百科
| 项目 | 说明 |
|---|---|
| 来源 | 中药网 http://www.zhongyoo.com/name/ |
| 输出 CSV | hurbTable.csv,约 899 条,含药材图片 URL |
| 入库表 | app_encyclopediarecord |
| 命令 | python scripts/fetch_zhongyoo_encyclopedia.py → python manage.py import_encyclopedia --path hurbTable.csv --truncate |
字段可信度分层见 data/DATA_SOURCES.md:A 可信 (药名、功效分类、药性、药味、归经、毒性)用于属性分析、聚类、关联与关系图谱;B 半可信 (道地产区)用于产地地图;C 演示估算(单价、库存等)仅药材列表/导出展示,不进统计图表。
2.6 技术特色与创新点
算法方面 :在同一系统中集成卡方检验、K-Means+PCA 聚类、Apriori 关联规则与手工共现关系图谱,并附算法说明卡片(partials/algo_explain.html),便于答辩时解释「怎么算、算什么、边界在哪」。
数据处理方面 :以 filtered_queryset + 本地内存缓存(TCM_CACHE_TTL=120)降低重复聚合开销;脚本拉取与 CSV 导入并存,兼顾离线初始化与字段可信度分层。
可视化特色:分析页采用 Bundle API 一次返回多图数据,减少请求次数;独立数据大屏提供 KPI、地图、分类/毒性/归经全景视图;墨绿主题与百科图片增强中医药文化氛围。
架构优势:views 负责页面,api_views 负责 JSON,utils 分层(core / charts / constants / herb_link / origin_resolve),职责清晰,便于维护。
体验方面 :Bootstrap 响应式布局 + SimpleUI 中文后台,侧栏分组明确;药材详情与百科详情通过 herb_link 模块互链,提升知识浏览连贯性。
3 系统分析与设计
3.1 系统总体设计
3.1.1 系统架构设计
系统采用经典三层 B/S 结构:表现层为登录页、首页、各分析模板、百科列表与独立大屏;业务层包含认证、分析聚合、卡方/聚类/关联/图谱计算与 CSV 导出;数据层为 MySQL tcm 库及原始缓存目录 data/raw/。浏览器通过 HTTP 访问 8020 端口,Django 处理页面与 API,分析计算在服务端完成后再以 JSON 驱动 ECharts 渲染。
图3.1 系统架构图
#mermaid-svg-Y5iVTtL9nA9sITBt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Y5iVTtL9nA9sITBt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Y5iVTtL9nA9sITBt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Y5iVTtL9nA9sITBt .error-icon{fill:#552222;}#mermaid-svg-Y5iVTtL9nA9sITBt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Y5iVTtL9nA9sITBt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Y5iVTtL9nA9sITBt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Y5iVTtL9nA9sITBt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Y5iVTtL9nA9sITBt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Y5iVTtL9nA9sITBt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Y5iVTtL9nA9sITBt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Y5iVTtL9nA9sITBt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Y5iVTtL9nA9sITBt .marker.cross{stroke:#333333;}#mermaid-svg-Y5iVTtL9nA9sITBt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Y5iVTtL9nA9sITBt p{margin:0;}#mermaid-svg-Y5iVTtL9nA9sITBt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Y5iVTtL9nA9sITBt .cluster-label text{fill:#333;}#mermaid-svg-Y5iVTtL9nA9sITBt .cluster-label span{color:#333;}#mermaid-svg-Y5iVTtL9nA9sITBt .cluster-label span p{background-color:transparent;}#mermaid-svg-Y5iVTtL9nA9sITBt .label text,#mermaid-svg-Y5iVTtL9nA9sITBt span{fill:#333;color:#333;}#mermaid-svg-Y5iVTtL9nA9sITBt .node rect,#mermaid-svg-Y5iVTtL9nA9sITBt .node circle,#mermaid-svg-Y5iVTtL9nA9sITBt .node ellipse,#mermaid-svg-Y5iVTtL9nA9sITBt .node polygon,#mermaid-svg-Y5iVTtL9nA9sITBt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Y5iVTtL9nA9sITBt .rough-node .label text,#mermaid-svg-Y5iVTtL9nA9sITBt .node .label text,#mermaid-svg-Y5iVTtL9nA9sITBt .image-shape .label,#mermaid-svg-Y5iVTtL9nA9sITBt .icon-shape .label{text-anchor:middle;}#mermaid-svg-Y5iVTtL9nA9sITBt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Y5iVTtL9nA9sITBt .rough-node .label,#mermaid-svg-Y5iVTtL9nA9sITBt .node .label,#mermaid-svg-Y5iVTtL9nA9sITBt .image-shape .label,#mermaid-svg-Y5iVTtL9nA9sITBt .icon-shape .label{text-align:center;}#mermaid-svg-Y5iVTtL9nA9sITBt .node.clickable{cursor:pointer;}#mermaid-svg-Y5iVTtL9nA9sITBt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Y5iVTtL9nA9sITBt .arrowheadPath{fill:#333333;}#mermaid-svg-Y5iVTtL9nA9sITBt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Y5iVTtL9nA9sITBt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Y5iVTtL9nA9sITBt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Y5iVTtL9nA9sITBt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Y5iVTtL9nA9sITBt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Y5iVTtL9nA9sITBt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Y5iVTtL9nA9sITBt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Y5iVTtL9nA9sITBt .cluster text{fill:#333;}#mermaid-svg-Y5iVTtL9nA9sITBt .cluster span{color:#333;}#mermaid-svg-Y5iVTtL9nA9sITBt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Y5iVTtL9nA9sITBt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Y5iVTtL9nA9sITBt rect.text{fill:none;stroke-width:0;}#mermaid-svg-Y5iVTtL9nA9sITBt .icon-shape,#mermaid-svg-Y5iVTtL9nA9sITBt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Y5iVTtL9nA9sITBt .icon-shape p,#mermaid-svg-Y5iVTtL9nA9sITBt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Y5iVTtL9nA9sITBt .icon-shape .label rect,#mermaid-svg-Y5iVTtL9nA9sITBt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Y5iVTtL9nA9sITBt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Y5iVTtL9nA9sITBt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Y5iVTtL9nA9sITBt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数据访问层
业务逻辑层
表现层
登录/注册页
首页仪表盘
分析模块页
百科/列表页
数据大屏
SimpleUI后台
会话认证 AuthMiddleware
views 页面视图
api_views JSON接口
tcm_core 筛选缓存
tcm_charts 聚合算法
tcm_constants 常量映射
MySQL: tcm
tcm_herbs_data.csv
hurbTable.csv
data/raw SymMap/百科缓存
fetch_symmap_herbs.py
fetch_zhongyoo_encyclopedia.py
3.1.2 系统数据流设计
从外部看,前台用户提交账号口令进入系统,管理员通过后台维护记录,SymMap 与中药网向脚本层提供原始数据。系统内部完成鉴权、查询过滤、分析计算、图表输出与入库。
图3.2 顶层数据流图
#mermaid-svg-TqcULyAUma6FR7A5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TqcULyAUma6FR7A5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TqcULyAUma6FR7A5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TqcULyAUma6FR7A5 .error-icon{fill:#552222;}#mermaid-svg-TqcULyAUma6FR7A5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TqcULyAUma6FR7A5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TqcULyAUma6FR7A5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TqcULyAUma6FR7A5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TqcULyAUma6FR7A5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TqcULyAUma6FR7A5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TqcULyAUma6FR7A5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TqcULyAUma6FR7A5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TqcULyAUma6FR7A5 .marker.cross{stroke:#333333;}#mermaid-svg-TqcULyAUma6FR7A5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TqcULyAUma6FR7A5 p{margin:0;}#mermaid-svg-TqcULyAUma6FR7A5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TqcULyAUma6FR7A5 .cluster-label text{fill:#333;}#mermaid-svg-TqcULyAUma6FR7A5 .cluster-label span{color:#333;}#mermaid-svg-TqcULyAUma6FR7A5 .cluster-label span p{background-color:transparent;}#mermaid-svg-TqcULyAUma6FR7A5 .label text,#mermaid-svg-TqcULyAUma6FR7A5 span{fill:#333;color:#333;}#mermaid-svg-TqcULyAUma6FR7A5 .node rect,#mermaid-svg-TqcULyAUma6FR7A5 .node circle,#mermaid-svg-TqcULyAUma6FR7A5 .node ellipse,#mermaid-svg-TqcULyAUma6FR7A5 .node polygon,#mermaid-svg-TqcULyAUma6FR7A5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TqcULyAUma6FR7A5 .rough-node .label text,#mermaid-svg-TqcULyAUma6FR7A5 .node .label text,#mermaid-svg-TqcULyAUma6FR7A5 .image-shape .label,#mermaid-svg-TqcULyAUma6FR7A5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-TqcULyAUma6FR7A5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TqcULyAUma6FR7A5 .rough-node .label,#mermaid-svg-TqcULyAUma6FR7A5 .node .label,#mermaid-svg-TqcULyAUma6FR7A5 .image-shape .label,#mermaid-svg-TqcULyAUma6FR7A5 .icon-shape .label{text-align:center;}#mermaid-svg-TqcULyAUma6FR7A5 .node.clickable{cursor:pointer;}#mermaid-svg-TqcULyAUma6FR7A5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TqcULyAUma6FR7A5 .arrowheadPath{fill:#333333;}#mermaid-svg-TqcULyAUma6FR7A5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TqcULyAUma6FR7A5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TqcULyAUma6FR7A5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TqcULyAUma6FR7A5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TqcULyAUma6FR7A5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TqcULyAUma6FR7A5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TqcULyAUma6FR7A5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TqcULyAUma6FR7A5 .cluster text{fill:#333;}#mermaid-svg-TqcULyAUma6FR7A5 .cluster span{color:#333;}#mermaid-svg-TqcULyAUma6FR7A5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TqcULyAUma6FR7A5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TqcULyAUma6FR7A5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-TqcULyAUma6FR7A5 .icon-shape,#mermaid-svg-TqcULyAUma6FR7A5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TqcULyAUma6FR7A5 .icon-shape p,#mermaid-svg-TqcULyAUma6FR7A5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TqcULyAUma6FR7A5 .icon-shape .label rect,#mermaid-svg-TqcULyAUma6FR7A5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TqcULyAUma6FR7A5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TqcULyAUma6FR7A5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TqcULyAUma6FR7A5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 账号/筛选条件
CRUD操作
xlsx/CSV
页面与图表
管理反馈
缓存原始文件
业务记录
前台用户
中药药材数据可视化分析系统
后台管理员
外部数据源
SymMap 2.0 / 中药网百科
本地 data/raw
MySQL tcm
图3.3 0层数据流图
#mermaid-svg-081HuNHm3H8BxcaG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-081HuNHm3H8BxcaG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-081HuNHm3H8BxcaG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-081HuNHm3H8BxcaG .error-icon{fill:#552222;}#mermaid-svg-081HuNHm3H8BxcaG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-081HuNHm3H8BxcaG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-081HuNHm3H8BxcaG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-081HuNHm3H8BxcaG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-081HuNHm3H8BxcaG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-081HuNHm3H8BxcaG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-081HuNHm3H8BxcaG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-081HuNHm3H8BxcaG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-081HuNHm3H8BxcaG .marker.cross{stroke:#333333;}#mermaid-svg-081HuNHm3H8BxcaG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-081HuNHm3H8BxcaG p{margin:0;}#mermaid-svg-081HuNHm3H8BxcaG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-081HuNHm3H8BxcaG .cluster-label text{fill:#333;}#mermaid-svg-081HuNHm3H8BxcaG .cluster-label span{color:#333;}#mermaid-svg-081HuNHm3H8BxcaG .cluster-label span p{background-color:transparent;}#mermaid-svg-081HuNHm3H8BxcaG .label text,#mermaid-svg-081HuNHm3H8BxcaG span{fill:#333;color:#333;}#mermaid-svg-081HuNHm3H8BxcaG .node rect,#mermaid-svg-081HuNHm3H8BxcaG .node circle,#mermaid-svg-081HuNHm3H8BxcaG .node ellipse,#mermaid-svg-081HuNHm3H8BxcaG .node polygon,#mermaid-svg-081HuNHm3H8BxcaG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-081HuNHm3H8BxcaG .rough-node .label text,#mermaid-svg-081HuNHm3H8BxcaG .node .label text,#mermaid-svg-081HuNHm3H8BxcaG .image-shape .label,#mermaid-svg-081HuNHm3H8BxcaG .icon-shape .label{text-anchor:middle;}#mermaid-svg-081HuNHm3H8BxcaG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-081HuNHm3H8BxcaG .rough-node .label,#mermaid-svg-081HuNHm3H8BxcaG .node .label,#mermaid-svg-081HuNHm3H8BxcaG .image-shape .label,#mermaid-svg-081HuNHm3H8BxcaG .icon-shape .label{text-align:center;}#mermaid-svg-081HuNHm3H8BxcaG .node.clickable{cursor:pointer;}#mermaid-svg-081HuNHm3H8BxcaG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-081HuNHm3H8BxcaG .arrowheadPath{fill:#333333;}#mermaid-svg-081HuNHm3H8BxcaG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-081HuNHm3H8BxcaG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-081HuNHm3H8BxcaG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-081HuNHm3H8BxcaG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-081HuNHm3H8BxcaG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-081HuNHm3H8BxcaG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-081HuNHm3H8BxcaG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-081HuNHm3H8BxcaG .cluster text{fill:#333;}#mermaid-svg-081HuNHm3H8BxcaG .cluster span{color:#333;}#mermaid-svg-081HuNHm3H8BxcaG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-081HuNHm3H8BxcaG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-081HuNHm3H8BxcaG rect.text{fill:none;stroke-width:0;}#mermaid-svg-081HuNHm3H8BxcaG .icon-shape,#mermaid-svg-081HuNHm3H8BxcaG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-081HuNHm3H8BxcaG .icon-shape p,#mermaid-svg-081HuNHm3H8BxcaG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-081HuNHm3H8BxcaG .icon-shape .label rect,#mermaid-svg-081HuNHm3H8BxcaG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-081HuNHm3H8BxcaG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-081HuNHm3H8BxcaG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-081HuNHm3H8BxcaG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 前台用户
1.0 用户认证
2.0 筛选与查询
3.0 分析聚合与算法
4.0 图表渲染支持
5.0 列表百科与导出
6.0 数据大屏
管理员
7.0 后台管理
运维/初始化
8.0 脚本拉取与导入
User表 app_user
HerbRecord表
EncyclopediaRecord表
外部数据源
CSV/原始缓存
3.1.3 系统功能模块设计
结合侧栏实际菜单与路由划分,系统功能划分为六大模块:(1)用户认证;(2)首页仪表盘;(3)属性分析;(4)产地与关系图谱;(5)聚类与关联规则;(6)百科列表大屏与后台。各模块对应独立页面路由与 Bundle API,个人信息维护(修改资料、密码)挂载于认证模块延伸功能。
图3.4 系统功能结构图
#mermaid-svg-G5EqIgWSEQSgqSvO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-G5EqIgWSEQSgqSvO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-G5EqIgWSEQSgqSvO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-G5EqIgWSEQSgqSvO .error-icon{fill:#552222;}#mermaid-svg-G5EqIgWSEQSgqSvO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-G5EqIgWSEQSgqSvO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-G5EqIgWSEQSgqSvO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-G5EqIgWSEQSgqSvO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-G5EqIgWSEQSgqSvO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-G5EqIgWSEQSgqSvO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-G5EqIgWSEQSgqSvO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-G5EqIgWSEQSgqSvO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-G5EqIgWSEQSgqSvO .marker.cross{stroke:#333333;}#mermaid-svg-G5EqIgWSEQSgqSvO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-G5EqIgWSEQSgqSvO p{margin:0;}#mermaid-svg-G5EqIgWSEQSgqSvO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-G5EqIgWSEQSgqSvO .cluster-label text{fill:#333;}#mermaid-svg-G5EqIgWSEQSgqSvO .cluster-label span{color:#333;}#mermaid-svg-G5EqIgWSEQSgqSvO .cluster-label span p{background-color:transparent;}#mermaid-svg-G5EqIgWSEQSgqSvO .label text,#mermaid-svg-G5EqIgWSEQSgqSvO span{fill:#333;color:#333;}#mermaid-svg-G5EqIgWSEQSgqSvO .node rect,#mermaid-svg-G5EqIgWSEQSgqSvO .node circle,#mermaid-svg-G5EqIgWSEQSgqSvO .node ellipse,#mermaid-svg-G5EqIgWSEQSgqSvO .node polygon,#mermaid-svg-G5EqIgWSEQSgqSvO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-G5EqIgWSEQSgqSvO .rough-node .label text,#mermaid-svg-G5EqIgWSEQSgqSvO .node .label text,#mermaid-svg-G5EqIgWSEQSgqSvO .image-shape .label,#mermaid-svg-G5EqIgWSEQSgqSvO .icon-shape .label{text-anchor:middle;}#mermaid-svg-G5EqIgWSEQSgqSvO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-G5EqIgWSEQSgqSvO .rough-node .label,#mermaid-svg-G5EqIgWSEQSgqSvO .node .label,#mermaid-svg-G5EqIgWSEQSgqSvO .image-shape .label,#mermaid-svg-G5EqIgWSEQSgqSvO .icon-shape .label{text-align:center;}#mermaid-svg-G5EqIgWSEQSgqSvO .node.clickable{cursor:pointer;}#mermaid-svg-G5EqIgWSEQSgqSvO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-G5EqIgWSEQSgqSvO .arrowheadPath{fill:#333333;}#mermaid-svg-G5EqIgWSEQSgqSvO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-G5EqIgWSEQSgqSvO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-G5EqIgWSEQSgqSvO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G5EqIgWSEQSgqSvO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-G5EqIgWSEQSgqSvO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G5EqIgWSEQSgqSvO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-G5EqIgWSEQSgqSvO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-G5EqIgWSEQSgqSvO .cluster text{fill:#333;}#mermaid-svg-G5EqIgWSEQSgqSvO .cluster span{color:#333;}#mermaid-svg-G5EqIgWSEQSgqSvO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-G5EqIgWSEQSgqSvO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-G5EqIgWSEQSgqSvO rect.text{fill:none;stroke-width:0;}#mermaid-svg-G5EqIgWSEQSgqSvO .icon-shape,#mermaid-svg-G5EqIgWSEQSgqSvO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G5EqIgWSEQSgqSvO .icon-shape p,#mermaid-svg-G5EqIgWSEQSgqSvO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-G5EqIgWSEQSgqSvO .icon-shape .label rect,#mermaid-svg-G5EqIgWSEQSgqSvO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G5EqIgWSEQSgqSvO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-G5EqIgWSEQSgqSvO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-G5EqIgWSEQSgqSvO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 中药药材数据可视化分析系统
用户认证
首页仪表盘
属性分析
产地与关系图谱
聚类与关联规则
百科列表大屏与后台
登录注册退出
修改信息与密码
首页KPI
归经TOP与功效占比
功效药性药味毒性
卡方与共现热力
产地地图
关系图谱
KMeans聚类PCA
Apriori关联规则
药材列表导出
中药百科浏览
数据大屏
SimpleUI后台
3.2 系统详细设计
3.2.1 用户认证模块设计
模块负责前台注册、登录、退出及会话保持。用户信息存储于自定义 User 表(物理表 app_user);登录成功后将 username 写入 request.session,后续页面通过 _user(request) 读取。中间件对未登录访问进行拦截并引导至登录页。前台默认账号 admin/123456;Django 内置后台管理员账号 admin/admin123,与前台用户表分离。
3.2.1.1 用户认证流程设计
图3.5 用户认证模块流程图
#mermaid-svg-pW9o6qa4YFlmcyoI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pW9o6qa4YFlmcyoI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pW9o6qa4YFlmcyoI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pW9o6qa4YFlmcyoI .error-icon{fill:#552222;}#mermaid-svg-pW9o6qa4YFlmcyoI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pW9o6qa4YFlmcyoI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pW9o6qa4YFlmcyoI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pW9o6qa4YFlmcyoI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pW9o6qa4YFlmcyoI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pW9o6qa4YFlmcyoI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pW9o6qa4YFlmcyoI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pW9o6qa4YFlmcyoI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pW9o6qa4YFlmcyoI .marker.cross{stroke:#333333;}#mermaid-svg-pW9o6qa4YFlmcyoI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pW9o6qa4YFlmcyoI p{margin:0;}#mermaid-svg-pW9o6qa4YFlmcyoI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pW9o6qa4YFlmcyoI .cluster-label text{fill:#333;}#mermaid-svg-pW9o6qa4YFlmcyoI .cluster-label span{color:#333;}#mermaid-svg-pW9o6qa4YFlmcyoI .cluster-label span p{background-color:transparent;}#mermaid-svg-pW9o6qa4YFlmcyoI .label text,#mermaid-svg-pW9o6qa4YFlmcyoI span{fill:#333;color:#333;}#mermaid-svg-pW9o6qa4YFlmcyoI .node rect,#mermaid-svg-pW9o6qa4YFlmcyoI .node circle,#mermaid-svg-pW9o6qa4YFlmcyoI .node ellipse,#mermaid-svg-pW9o6qa4YFlmcyoI .node polygon,#mermaid-svg-pW9o6qa4YFlmcyoI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pW9o6qa4YFlmcyoI .rough-node .label text,#mermaid-svg-pW9o6qa4YFlmcyoI .node .label text,#mermaid-svg-pW9o6qa4YFlmcyoI .image-shape .label,#mermaid-svg-pW9o6qa4YFlmcyoI .icon-shape .label{text-anchor:middle;}#mermaid-svg-pW9o6qa4YFlmcyoI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pW9o6qa4YFlmcyoI .rough-node .label,#mermaid-svg-pW9o6qa4YFlmcyoI .node .label,#mermaid-svg-pW9o6qa4YFlmcyoI .image-shape .label,#mermaid-svg-pW9o6qa4YFlmcyoI .icon-shape .label{text-align:center;}#mermaid-svg-pW9o6qa4YFlmcyoI .node.clickable{cursor:pointer;}#mermaid-svg-pW9o6qa4YFlmcyoI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pW9o6qa4YFlmcyoI .arrowheadPath{fill:#333333;}#mermaid-svg-pW9o6qa4YFlmcyoI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pW9o6qa4YFlmcyoI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pW9o6qa4YFlmcyoI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pW9o6qa4YFlmcyoI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pW9o6qa4YFlmcyoI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pW9o6qa4YFlmcyoI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pW9o6qa4YFlmcyoI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pW9o6qa4YFlmcyoI .cluster text{fill:#333;}#mermaid-svg-pW9o6qa4YFlmcyoI .cluster span{color:#333;}#mermaid-svg-pW9o6qa4YFlmcyoI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pW9o6qa4YFlmcyoI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pW9o6qa4YFlmcyoI rect.text{fill:none;stroke-width:0;}#mermaid-svg-pW9o6qa4YFlmcyoI .icon-shape,#mermaid-svg-pW9o6qa4YFlmcyoI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pW9o6qa4YFlmcyoI .icon-shape p,#mermaid-svg-pW9o6qa4YFlmcyoI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pW9o6qa4YFlmcyoI .icon-shape .label rect,#mermaid-svg-pW9o6qa4YFlmcyoI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pW9o6qa4YFlmcyoI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pW9o6qa4YFlmcyoI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pW9o6qa4YFlmcyoI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} GET
POST
匹配
不匹配
通过
失败
访问 /app/login/
请求方法
渲染登录页 tcm-bg.jpg
读取用户名密码
User表校验
写入 session.username
跳转 /app/home/
返回错误提示
访问 /app/register/
注册校验
创建 User 记录
跳转登录页
提示空值/重复/密码不一致
3.2.2 首页仪表盘模块设计
首页路由为 /app/home/,由 home 视图在服务端聚合药材总数、功效分类数、道地产区数、百科匹配率、有毒标注种数、归经 TOP10 与功效占比饼图数据,并展示百科图片推荐与分类代表药材卡片。前端可额外请求 /app/api/overview/ 获取 overview_bundle 中的摘要与洞察文本。overview/stat 历史路由已重定向至 attributes,首页承担仪表盘职能。
3.2.2.1 首页仪表盘流程设计
图3.6 首页仪表盘模块流程图
#mermaid-svg-CzGMqPSRC3vKyzcH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CzGMqPSRC3vKyzcH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CzGMqPSRC3vKyzcH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CzGMqPSRC3vKyzcH .error-icon{fill:#552222;}#mermaid-svg-CzGMqPSRC3vKyzcH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CzGMqPSRC3vKyzcH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CzGMqPSRC3vKyzcH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CzGMqPSRC3vKyzcH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CzGMqPSRC3vKyzcH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CzGMqPSRC3vKyzcH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CzGMqPSRC3vKyzcH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CzGMqPSRC3vKyzcH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CzGMqPSRC3vKyzcH .marker.cross{stroke:#333333;}#mermaid-svg-CzGMqPSRC3vKyzcH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CzGMqPSRC3vKyzcH p{margin:0;}#mermaid-svg-CzGMqPSRC3vKyzcH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CzGMqPSRC3vKyzcH .cluster-label text{fill:#333;}#mermaid-svg-CzGMqPSRC3vKyzcH .cluster-label span{color:#333;}#mermaid-svg-CzGMqPSRC3vKyzcH .cluster-label span p{background-color:transparent;}#mermaid-svg-CzGMqPSRC3vKyzcH .label text,#mermaid-svg-CzGMqPSRC3vKyzcH span{fill:#333;color:#333;}#mermaid-svg-CzGMqPSRC3vKyzcH .node rect,#mermaid-svg-CzGMqPSRC3vKyzcH .node circle,#mermaid-svg-CzGMqPSRC3vKyzcH .node ellipse,#mermaid-svg-CzGMqPSRC3vKyzcH .node polygon,#mermaid-svg-CzGMqPSRC3vKyzcH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CzGMqPSRC3vKyzcH .rough-node .label text,#mermaid-svg-CzGMqPSRC3vKyzcH .node .label text,#mermaid-svg-CzGMqPSRC3vKyzcH .image-shape .label,#mermaid-svg-CzGMqPSRC3vKyzcH .icon-shape .label{text-anchor:middle;}#mermaid-svg-CzGMqPSRC3vKyzcH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CzGMqPSRC3vKyzcH .rough-node .label,#mermaid-svg-CzGMqPSRC3vKyzcH .node .label,#mermaid-svg-CzGMqPSRC3vKyzcH .image-shape .label,#mermaid-svg-CzGMqPSRC3vKyzcH .icon-shape .label{text-align:center;}#mermaid-svg-CzGMqPSRC3vKyzcH .node.clickable{cursor:pointer;}#mermaid-svg-CzGMqPSRC3vKyzcH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CzGMqPSRC3vKyzcH .arrowheadPath{fill:#333333;}#mermaid-svg-CzGMqPSRC3vKyzcH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CzGMqPSRC3vKyzcH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CzGMqPSRC3vKyzcH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CzGMqPSRC3vKyzcH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CzGMqPSRC3vKyzcH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CzGMqPSRC3vKyzcH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CzGMqPSRC3vKyzcH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CzGMqPSRC3vKyzcH .cluster text{fill:#333;}#mermaid-svg-CzGMqPSRC3vKyzcH .cluster span{color:#333;}#mermaid-svg-CzGMqPSRC3vKyzcH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CzGMqPSRC3vKyzcH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CzGMqPSRC3vKyzcH rect.text{fill:none;stroke-width:0;}#mermaid-svg-CzGMqPSRC3vKyzcH .icon-shape,#mermaid-svg-CzGMqPSRC3vKyzcH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CzGMqPSRC3vKyzcH .icon-shape p,#mermaid-svg-CzGMqPSRC3vKyzcH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CzGMqPSRC3vKyzcH .icon-shape .label rect,#mermaid-svg-CzGMqPSRC3vKyzcH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CzGMqPSRC3vKyzcH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CzGMqPSRC3vKyzcH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CzGMqPSRC3vKyzcH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 进入 /app/home/
home 视图服务端聚合
HerbRecord Count/Distinct
_encyclopedia_stats 百科匹配
_meridian_top 归经统计
功效占比 category_pie
渲染 home.html KPI 与图表
可选请求 overview API
overview_bundle 洞察文本
结束
3.2.3 属性分析模块设计
属性分析页 /app/attributes/ 依赖 /app/api/attributes-bundle/,展示功效分类饼图、药性/药味柱状图、毒性饼图、归经 TOP、药性×毒性卡方检验结果与功效×药性共现热力矩阵。筛选条支持按功效分类(category)与关键词(q)过滤。/app/overview/ 与 /app/stat/ 重定向至本页,不再提供独立总览/统计页。
3.2.3.1 属性分析流程设计
图3.7 属性分析模块流程图
#mermaid-svg-3IOEKQUhvfEaV7dr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3IOEKQUhvfEaV7dr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3IOEKQUhvfEaV7dr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3IOEKQUhvfEaV7dr .error-icon{fill:#552222;}#mermaid-svg-3IOEKQUhvfEaV7dr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3IOEKQUhvfEaV7dr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3IOEKQUhvfEaV7dr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3IOEKQUhvfEaV7dr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3IOEKQUhvfEaV7dr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3IOEKQUhvfEaV7dr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3IOEKQUhvfEaV7dr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3IOEKQUhvfEaV7dr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3IOEKQUhvfEaV7dr .marker.cross{stroke:#333333;}#mermaid-svg-3IOEKQUhvfEaV7dr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3IOEKQUhvfEaV7dr p{margin:0;}#mermaid-svg-3IOEKQUhvfEaV7dr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3IOEKQUhvfEaV7dr .cluster-label text{fill:#333;}#mermaid-svg-3IOEKQUhvfEaV7dr .cluster-label span{color:#333;}#mermaid-svg-3IOEKQUhvfEaV7dr .cluster-label span p{background-color:transparent;}#mermaid-svg-3IOEKQUhvfEaV7dr .label text,#mermaid-svg-3IOEKQUhvfEaV7dr span{fill:#333;color:#333;}#mermaid-svg-3IOEKQUhvfEaV7dr .node rect,#mermaid-svg-3IOEKQUhvfEaV7dr .node circle,#mermaid-svg-3IOEKQUhvfEaV7dr .node ellipse,#mermaid-svg-3IOEKQUhvfEaV7dr .node polygon,#mermaid-svg-3IOEKQUhvfEaV7dr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3IOEKQUhvfEaV7dr .rough-node .label text,#mermaid-svg-3IOEKQUhvfEaV7dr .node .label text,#mermaid-svg-3IOEKQUhvfEaV7dr .image-shape .label,#mermaid-svg-3IOEKQUhvfEaV7dr .icon-shape .label{text-anchor:middle;}#mermaid-svg-3IOEKQUhvfEaV7dr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3IOEKQUhvfEaV7dr .rough-node .label,#mermaid-svg-3IOEKQUhvfEaV7dr .node .label,#mermaid-svg-3IOEKQUhvfEaV7dr .image-shape .label,#mermaid-svg-3IOEKQUhvfEaV7dr .icon-shape .label{text-align:center;}#mermaid-svg-3IOEKQUhvfEaV7dr .node.clickable{cursor:pointer;}#mermaid-svg-3IOEKQUhvfEaV7dr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3IOEKQUhvfEaV7dr .arrowheadPath{fill:#333333;}#mermaid-svg-3IOEKQUhvfEaV7dr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3IOEKQUhvfEaV7dr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3IOEKQUhvfEaV7dr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3IOEKQUhvfEaV7dr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3IOEKQUhvfEaV7dr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3IOEKQUhvfEaV7dr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3IOEKQUhvfEaV7dr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3IOEKQUhvfEaV7dr .cluster text{fill:#333;}#mermaid-svg-3IOEKQUhvfEaV7dr .cluster span{color:#333;}#mermaid-svg-3IOEKQUhvfEaV7dr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3IOEKQUhvfEaV7dr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3IOEKQUhvfEaV7dr rect.text{fill:none;stroke-width:0;}#mermaid-svg-3IOEKQUhvfEaV7dr .icon-shape,#mermaid-svg-3IOEKQUhvfEaV7dr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3IOEKQUhvfEaV7dr .icon-shape p,#mermaid-svg-3IOEKQUhvfEaV7dr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3IOEKQUhvfEaV7dr .icon-shape .label rect,#mermaid-svg-3IOEKQUhvfEaV7dr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3IOEKQUhvfEaV7dr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3IOEKQUhvfEaV7dr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3IOEKQUhvfEaV7dr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 进入 /app/attributes/
读取 category/q 筛选
cached_analysis_df
attributes_bundle 聚合
功效/药性/药味/毒性/归经
chi2_contingency 卡方
功效×药性交叉矩阵
JSON 返回
ECharts 多图渲染
3.2.4 产地与关系图谱模块设计
产地地图页 /app/origin/ 调用 /app/api/origin-bundle/,按道地产区统计药材数量并映射至中国地图坐标(PROVINCE_COORDS)。关系图谱页 /app/graph/ 调用 /app/api/graph-bundle/,构建药材---功效/药性/产区/归经共现网络,默认最多展示 80 条药材(可通过 limit 参数调整,上限 120)。
3.2.4.1 产地与关系图谱流程设计
图3.8 产地与关系图谱模块流程图
#mermaid-svg-l0IDM68l9AYubPG3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-l0IDM68l9AYubPG3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-l0IDM68l9AYubPG3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-l0IDM68l9AYubPG3 .error-icon{fill:#552222;}#mermaid-svg-l0IDM68l9AYubPG3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-l0IDM68l9AYubPG3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-l0IDM68l9AYubPG3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-l0IDM68l9AYubPG3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-l0IDM68l9AYubPG3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-l0IDM68l9AYubPG3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-l0IDM68l9AYubPG3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-l0IDM68l9AYubPG3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-l0IDM68l9AYubPG3 .marker.cross{stroke:#333333;}#mermaid-svg-l0IDM68l9AYubPG3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-l0IDM68l9AYubPG3 p{margin:0;}#mermaid-svg-l0IDM68l9AYubPG3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-l0IDM68l9AYubPG3 .cluster-label text{fill:#333;}#mermaid-svg-l0IDM68l9AYubPG3 .cluster-label span{color:#333;}#mermaid-svg-l0IDM68l9AYubPG3 .cluster-label span p{background-color:transparent;}#mermaid-svg-l0IDM68l9AYubPG3 .label text,#mermaid-svg-l0IDM68l9AYubPG3 span{fill:#333;color:#333;}#mermaid-svg-l0IDM68l9AYubPG3 .node rect,#mermaid-svg-l0IDM68l9AYubPG3 .node circle,#mermaid-svg-l0IDM68l9AYubPG3 .node ellipse,#mermaid-svg-l0IDM68l9AYubPG3 .node polygon,#mermaid-svg-l0IDM68l9AYubPG3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-l0IDM68l9AYubPG3 .rough-node .label text,#mermaid-svg-l0IDM68l9AYubPG3 .node .label text,#mermaid-svg-l0IDM68l9AYubPG3 .image-shape .label,#mermaid-svg-l0IDM68l9AYubPG3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-l0IDM68l9AYubPG3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-l0IDM68l9AYubPG3 .rough-node .label,#mermaid-svg-l0IDM68l9AYubPG3 .node .label,#mermaid-svg-l0IDM68l9AYubPG3 .image-shape .label,#mermaid-svg-l0IDM68l9AYubPG3 .icon-shape .label{text-align:center;}#mermaid-svg-l0IDM68l9AYubPG3 .node.clickable{cursor:pointer;}#mermaid-svg-l0IDM68l9AYubPG3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-l0IDM68l9AYubPG3 .arrowheadPath{fill:#333333;}#mermaid-svg-l0IDM68l9AYubPG3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-l0IDM68l9AYubPG3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-l0IDM68l9AYubPG3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-l0IDM68l9AYubPG3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-l0IDM68l9AYubPG3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-l0IDM68l9AYubPG3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-l0IDM68l9AYubPG3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-l0IDM68l9AYubPG3 .cluster text{fill:#333;}#mermaid-svg-l0IDM68l9AYubPG3 .cluster span{color:#333;}#mermaid-svg-l0IDM68l9AYubPG3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-l0IDM68l9AYubPG3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-l0IDM68l9AYubPG3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-l0IDM68l9AYubPG3 .icon-shape,#mermaid-svg-l0IDM68l9AYubPG3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-l0IDM68l9AYubPG3 .icon-shape p,#mermaid-svg-l0IDM68l9AYubPG3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-l0IDM68l9AYubPG3 .icon-shape .label rect,#mermaid-svg-l0IDM68l9AYubPG3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-l0IDM68l9AYubPG3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-l0IDM68l9AYubPG3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-l0IDM68l9AYubPG3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 产地
图谱
进入 origin 或 graph 页
携带筛选条件请求 Bundle
cached_analysis_df
模块分支
groupby origin_province + 坐标映射
遍历药材添加节点与连边
返回 provinces 列表
返回 nodes/links/categories
地图/散点渲染
力导向关系网络渲染
3.2.5 聚类与关联规则模块设计
聚类分析页 /app/cluster/ 调用 /app/api/cluster-bundle/,对 one-hot 编码后的分类字段做 KMeans 聚类与 PCA 二维投影,支持 clusters 参数(2~8,默认 4)。关联规则页 /app/association/ 调用 /app/api/association-bundle/,默认支持度 0.02、置信度 0.4,返回全部达标规则供前端分页;可选 top_n 限制条数。
3.2.5.1 聚类与关联规则流程设计
图3.9 聚类与关联规则模块流程图
#mermaid-svg-LKwPlVWKYhOjmoK9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LKwPlVWKYhOjmoK9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LKwPlVWKYhOjmoK9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LKwPlVWKYhOjmoK9 .error-icon{fill:#552222;}#mermaid-svg-LKwPlVWKYhOjmoK9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LKwPlVWKYhOjmoK9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LKwPlVWKYhOjmoK9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LKwPlVWKYhOjmoK9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LKwPlVWKYhOjmoK9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LKwPlVWKYhOjmoK9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LKwPlVWKYhOjmoK9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LKwPlVWKYhOjmoK9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LKwPlVWKYhOjmoK9 .marker.cross{stroke:#333333;}#mermaid-svg-LKwPlVWKYhOjmoK9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LKwPlVWKYhOjmoK9 p{margin:0;}#mermaid-svg-LKwPlVWKYhOjmoK9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LKwPlVWKYhOjmoK9 .cluster-label text{fill:#333;}#mermaid-svg-LKwPlVWKYhOjmoK9 .cluster-label span{color:#333;}#mermaid-svg-LKwPlVWKYhOjmoK9 .cluster-label span p{background-color:transparent;}#mermaid-svg-LKwPlVWKYhOjmoK9 .label text,#mermaid-svg-LKwPlVWKYhOjmoK9 span{fill:#333;color:#333;}#mermaid-svg-LKwPlVWKYhOjmoK9 .node rect,#mermaid-svg-LKwPlVWKYhOjmoK9 .node circle,#mermaid-svg-LKwPlVWKYhOjmoK9 .node ellipse,#mermaid-svg-LKwPlVWKYhOjmoK9 .node polygon,#mermaid-svg-LKwPlVWKYhOjmoK9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LKwPlVWKYhOjmoK9 .rough-node .label text,#mermaid-svg-LKwPlVWKYhOjmoK9 .node .label text,#mermaid-svg-LKwPlVWKYhOjmoK9 .image-shape .label,#mermaid-svg-LKwPlVWKYhOjmoK9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-LKwPlVWKYhOjmoK9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LKwPlVWKYhOjmoK9 .rough-node .label,#mermaid-svg-LKwPlVWKYhOjmoK9 .node .label,#mermaid-svg-LKwPlVWKYhOjmoK9 .image-shape .label,#mermaid-svg-LKwPlVWKYhOjmoK9 .icon-shape .label{text-align:center;}#mermaid-svg-LKwPlVWKYhOjmoK9 .node.clickable{cursor:pointer;}#mermaid-svg-LKwPlVWKYhOjmoK9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LKwPlVWKYhOjmoK9 .arrowheadPath{fill:#333333;}#mermaid-svg-LKwPlVWKYhOjmoK9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LKwPlVWKYhOjmoK9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LKwPlVWKYhOjmoK9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LKwPlVWKYhOjmoK9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LKwPlVWKYhOjmoK9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LKwPlVWKYhOjmoK9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LKwPlVWKYhOjmoK9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LKwPlVWKYhOjmoK9 .cluster text{fill:#333;}#mermaid-svg-LKwPlVWKYhOjmoK9 .cluster span{color:#333;}#mermaid-svg-LKwPlVWKYhOjmoK9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LKwPlVWKYhOjmoK9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LKwPlVWKYhOjmoK9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-LKwPlVWKYhOjmoK9 .icon-shape,#mermaid-svg-LKwPlVWKYhOjmoK9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LKwPlVWKYhOjmoK9 .icon-shape p,#mermaid-svg-LKwPlVWKYhOjmoK9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LKwPlVWKYhOjmoK9 .icon-shape .label rect,#mermaid-svg-LKwPlVWKYhOjmoK9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LKwPlVWKYhOjmoK9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LKwPlVWKYhOjmoK9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LKwPlVWKYhOjmoK9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 聚类
关联
否
进入 cluster 或 association 页
请求对应 bundle API
cached_analysis_df
算法分支
get_dummies + StandardScaler
KMeans + PCA 投影
TransactionEncoder + apriori
association_rules 筛选
mlxtend 可用?
_simple_cooccurrence 回退
JSON 返回
散点/规则表渲染
3.2.6 百科列表大屏与后台模块设计
药材列表页 /app/herb_list/ 对 HerbRecord 分页展示(每页 20 条),支持分类与关键词筛选,并可经 /app/api/export/ 导出 CSV(含估算商业字段,标注为演示)。百科列表 /app/encyclopedia/ 分页展示约 899 条百科(每页 18 条),详情页展示性味归经、功效主治与药材图片。数据大屏 /app/bigscreen/ 请求 /app/api/bigscreen-bundle/ 渲染 KPI、分类饼图、药性柱图、毒性饼图、归经 TOP 与省份地图。后台 /admin/ 基于 SimpleUI 维护药材记录、百科条目与前台用户,增删改后调用 invalidate_tcm_cache。
3.2.6.1 百科列表大屏与后台流程设计
图3.10 百科列表大屏与后台模块流程图
#mermaid-svg-s7tRduplkiZeRrgH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-s7tRduplkiZeRrgH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-s7tRduplkiZeRrgH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-s7tRduplkiZeRrgH .error-icon{fill:#552222;}#mermaid-svg-s7tRduplkiZeRrgH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s7tRduplkiZeRrgH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-s7tRduplkiZeRrgH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s7tRduplkiZeRrgH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s7tRduplkiZeRrgH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-s7tRduplkiZeRrgH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s7tRduplkiZeRrgH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s7tRduplkiZeRrgH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s7tRduplkiZeRrgH .marker.cross{stroke:#333333;}#mermaid-svg-s7tRduplkiZeRrgH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s7tRduplkiZeRrgH p{margin:0;}#mermaid-svg-s7tRduplkiZeRrgH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-s7tRduplkiZeRrgH .cluster-label text{fill:#333;}#mermaid-svg-s7tRduplkiZeRrgH .cluster-label span{color:#333;}#mermaid-svg-s7tRduplkiZeRrgH .cluster-label span p{background-color:transparent;}#mermaid-svg-s7tRduplkiZeRrgH .label text,#mermaid-svg-s7tRduplkiZeRrgH span{fill:#333;color:#333;}#mermaid-svg-s7tRduplkiZeRrgH .node rect,#mermaid-svg-s7tRduplkiZeRrgH .node circle,#mermaid-svg-s7tRduplkiZeRrgH .node ellipse,#mermaid-svg-s7tRduplkiZeRrgH .node polygon,#mermaid-svg-s7tRduplkiZeRrgH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-s7tRduplkiZeRrgH .rough-node .label text,#mermaid-svg-s7tRduplkiZeRrgH .node .label text,#mermaid-svg-s7tRduplkiZeRrgH .image-shape .label,#mermaid-svg-s7tRduplkiZeRrgH .icon-shape .label{text-anchor:middle;}#mermaid-svg-s7tRduplkiZeRrgH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-s7tRduplkiZeRrgH .rough-node .label,#mermaid-svg-s7tRduplkiZeRrgH .node .label,#mermaid-svg-s7tRduplkiZeRrgH .image-shape .label,#mermaid-svg-s7tRduplkiZeRrgH .icon-shape .label{text-align:center;}#mermaid-svg-s7tRduplkiZeRrgH .node.clickable{cursor:pointer;}#mermaid-svg-s7tRduplkiZeRrgH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-s7tRduplkiZeRrgH .arrowheadPath{fill:#333333;}#mermaid-svg-s7tRduplkiZeRrgH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-s7tRduplkiZeRrgH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-s7tRduplkiZeRrgH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s7tRduplkiZeRrgH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-s7tRduplkiZeRrgH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s7tRduplkiZeRrgH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-s7tRduplkiZeRrgH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-s7tRduplkiZeRrgH .cluster text{fill:#333;}#mermaid-svg-s7tRduplkiZeRrgH .cluster span{color:#333;}#mermaid-svg-s7tRduplkiZeRrgH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-s7tRduplkiZeRrgH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-s7tRduplkiZeRrgH rect.text{fill:none;stroke-width:0;}#mermaid-svg-s7tRduplkiZeRrgH .icon-shape,#mermaid-svg-s7tRduplkiZeRrgH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s7tRduplkiZeRrgH .icon-shape p,#mermaid-svg-s7tRduplkiZeRrgH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-s7tRduplkiZeRrgH .icon-shape .label rect,#mermaid-svg-s7tRduplkiZeRrgH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s7tRduplkiZeRrgH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-s7tRduplkiZeRrgH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-s7tRduplkiZeRrgH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
打开 /app/herb_list/
filtered_queryset 分页
展示记录含C类估算字段
导出?
api_export 流式写 CSV
打开 /app/encyclopedia/
百科分页与详情
打开 /app/bigscreen/
bigscreen_bundle 聚合
tcm_bigscreen.js 全屏渲染
管理员登录 /admin/
HerbRecord/Encyclopedia CRUD
invalidate_tcm_cache
初始化脚本
import_herbs/import_encyclopedia
约698+899条入库
3.3 数据库设计
3.3.1 数据库关系设计
系统业务库为 MySQL 中的 tcm。核心实体为前台用户表 User(app_user)、中药药材记录表 HerbRecord(app_herbrecord)与中药百科表 EncyclopediaRecord(app_encyclopediarecord)。三表无强制外键关联:登录态通过 Session 绑定用户名;药材与百科通过 herb_link 模块按药名模糊对齐;后台 Django 内置 auth_user 表供管理员登录。
图3.11 数据库表关系图
#mermaid-svg-CnB4CwLl3nNgc9SS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CnB4CwLl3nNgc9SS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CnB4CwLl3nNgc9SS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CnB4CwLl3nNgc9SS .error-icon{fill:#552222;}#mermaid-svg-CnB4CwLl3nNgc9SS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CnB4CwLl3nNgc9SS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CnB4CwLl3nNgc9SS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CnB4CwLl3nNgc9SS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CnB4CwLl3nNgc9SS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CnB4CwLl3nNgc9SS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CnB4CwLl3nNgc9SS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CnB4CwLl3nNgc9SS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CnB4CwLl3nNgc9SS .marker.cross{stroke:#333333;}#mermaid-svg-CnB4CwLl3nNgc9SS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CnB4CwLl3nNgc9SS p{margin:0;}#mermaid-svg-CnB4CwLl3nNgc9SS .entityBox{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-CnB4CwLl3nNgc9SS .relationshipLabelBox{fill:hsl(80, 100%, 96.2745098039%);opacity:0.7;background-color:hsl(80, 100%, 96.2745098039%);}#mermaid-svg-CnB4CwLl3nNgc9SS .relationshipLabelBox rect{opacity:0.5;}#mermaid-svg-CnB4CwLl3nNgc9SS .labelBkg{background-color:rgba(248.6666666666, 255, 235.9999999999, 0.5);}#mermaid-svg-CnB4CwLl3nNgc9SS .edgeLabel .label{fill:#9370DB;font-size:14px;}#mermaid-svg-CnB4CwLl3nNgc9SS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CnB4CwLl3nNgc9SS .edge-pattern-dashed{stroke-dasharray:8,8;}#mermaid-svg-CnB4CwLl3nNgc9SS .node rect,#mermaid-svg-CnB4CwLl3nNgc9SS .node circle,#mermaid-svg-CnB4CwLl3nNgc9SS .node ellipse,#mermaid-svg-CnB4CwLl3nNgc9SS .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CnB4CwLl3nNgc9SS .relationshipLine{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-CnB4CwLl3nNgc9SS .marker{fill:none!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-CnB4CwLl3nNgc9SS .edgeLabel{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CnB4CwLl3nNgc9SS .edgeLabel .label rect{fill:rgba(232,232,232, 0.8);}#mermaid-svg-CnB4CwLl3nNgc9SS .edgeLabel .label text{fill:#333;}#mermaid-svg-CnB4CwLl3nNgc9SS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 会话用户可查询分析
药名对齐互链
后台CRUD维护
USER
int
id
PK
主键
varchar
username
用户名
varchar
password
密码
varchar
sex
性别
varchar
address
地址
varchar
avatar
头像路径
varchar
textarea
个人简介
datetime
createTime
创建时间
HERB_RECORD
bigint
id
PK
主键
varchar
herb_code
药材编码
varchar
name
药材名称
varchar
efficacy_category
功效分类
varchar
property_nature
药性
varchar
taste
药味
varchar
meridian
归经
varchar
main_efficacy
主要功效
varchar
origin_province
道地产区
varchar
toxicity_level
毒性等级
float
dosage_g
常用剂量演示
float
price_per_g
单价演示
float
stock_kg
库存演示
ENCYCLOPEDIA_RECORD
bigint
id
PK
主键
varchar
name
中药名
varchar
medicine_name
药材名
text
alias
别名
text
distribution
产地分布
text
type_meridian
性味归经
text
effect
功效主治
varchar
image_url
药材图片
varchar
source_url
来源链接
AUTH_USER
int
id
PK
Django后台用户
varchar
username
管理员名
varchar
password
密码哈希
表3.1 USER(前台用户 app_user)表结构
| 字段名称 | 字段类型 | 字段说明 | 是否主键 | 是否为空 |
|---|---|---|---|---|
| id | int | 唯一标识 | 是 | 否 |
| username | varchar(255) | 用户名 | 否 | 否 |
| password | varchar(255) | 密码 | 否 | 否 |
| sex | varchar(255) | 性别 | 否 | 是 |
| address | varchar(255) | 地址 | 否 | 是 |
| avatar | varchar/文件路径 | 头像 | 否 | 是 |
| textarea | varchar(255) | 个人简介 | 否 | 是 |
| createTime | datetime | 创建时间 | 否 | 否 |
表3.2 HERB_RECORD(中药药材记录 app_herbrecord)表结构
| 字段名称 | 字段类型 | 字段说明 | 是否主键 | 是否为空 |
|---|---|---|---|---|
| id | bigint | 唯一标识 | 是 | 否 |
| herb_code | varchar(32) | 药材编码 | 否 | 否 |
| name | varchar(100) | 药材名称 | 否 | 否 |
| efficacy_category | varchar(50) | 功效分类(A可信) | 否 | 否 |
| property_nature | varchar(20) | 药性(A可信) | 否 | 是 |
| taste | varchar(50) | 药味(A可信) | 否 | 是 |
| meridian | varchar(100) | 归经(A可信) | 否 | 是 |
| main_efficacy | varchar(255) | 主要功效 | 否 | 是 |
| origin_province | varchar(50) | 道地产区(B半可信) | 否 | 是 |
| toxicity_level | varchar(20) | 毒性等级(A可信) | 否 | 是 |
| dosage_g | float | 常用剂量(C演示) | 否 | 否 |
| price_per_g | float | 单价(C演示,不上图) | 否 | 否 |
| stock_kg | float | 库存(C演示,不上图) | 否 | 否 |
| annual_sales_kg | float | 年销量(C演示) | 否 | 否 |
表3.3 ENCYCLOPEDIA_RECORD(中药百科 app_encyclopediarecord)表结构
| 字段名称 | 字段类型 | 字段说明 | 是否主键 | 是否为空 |
|---|---|---|---|---|
| id | bigint | 唯一标识 | 是 | 否 |
| name | varchar(100) | 中药名 | 否 | 否 |
| medicine_name | varchar(200) | 药材名 | 否 | 是 |
| alias | text | 别名 | 否 | 是 |
| distribution | text | 产地分布 | 否 | 是 |
| type_meridian | text | 性味归经 | 否 | 是 |
| effect | text | 功效主治 | 否 | 是 |
| usage | text | 用法用量 | 否 | 是 |
| image_url | varchar(500) | 药材图片 URL | 否 | 是 |
| source_url | varchar(500) | 来源链接 | 否 | 是 |
4 系统实现
4.0 系统整体实现流程
系统实现按「环境与库表 → 脚本拉取与导入 → 认证与首页 → 分析 Bundle API → 各分析页 → 百科列表大屏 → 后台配置 → 联调测试」推进。开发入口为 manage.py,配置库名 tcm,迁移后导入约 698 条药材与 899 条百科,再以 runserver 8020 启动服务。
图4.0 系统整体实现流程图
#mermaid-svg-srRKxT7MdQ7lJ2CG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-srRKxT7MdQ7lJ2CG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-srRKxT7MdQ7lJ2CG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-srRKxT7MdQ7lJ2CG .error-icon{fill:#552222;}#mermaid-svg-srRKxT7MdQ7lJ2CG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-srRKxT7MdQ7lJ2CG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-srRKxT7MdQ7lJ2CG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-srRKxT7MdQ7lJ2CG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-srRKxT7MdQ7lJ2CG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-srRKxT7MdQ7lJ2CG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-srRKxT7MdQ7lJ2CG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-srRKxT7MdQ7lJ2CG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-srRKxT7MdQ7lJ2CG .marker.cross{stroke:#333333;}#mermaid-svg-srRKxT7MdQ7lJ2CG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-srRKxT7MdQ7lJ2CG p{margin:0;}#mermaid-svg-srRKxT7MdQ7lJ2CG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-srRKxT7MdQ7lJ2CG .cluster-label text{fill:#333;}#mermaid-svg-srRKxT7MdQ7lJ2CG .cluster-label span{color:#333;}#mermaid-svg-srRKxT7MdQ7lJ2CG .cluster-label span p{background-color:transparent;}#mermaid-svg-srRKxT7MdQ7lJ2CG .label text,#mermaid-svg-srRKxT7MdQ7lJ2CG span{fill:#333;color:#333;}#mermaid-svg-srRKxT7MdQ7lJ2CG .node rect,#mermaid-svg-srRKxT7MdQ7lJ2CG .node circle,#mermaid-svg-srRKxT7MdQ7lJ2CG .node ellipse,#mermaid-svg-srRKxT7MdQ7lJ2CG .node polygon,#mermaid-svg-srRKxT7MdQ7lJ2CG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-srRKxT7MdQ7lJ2CG .rough-node .label text,#mermaid-svg-srRKxT7MdQ7lJ2CG .node .label text,#mermaid-svg-srRKxT7MdQ7lJ2CG .image-shape .label,#mermaid-svg-srRKxT7MdQ7lJ2CG .icon-shape .label{text-anchor:middle;}#mermaid-svg-srRKxT7MdQ7lJ2CG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-srRKxT7MdQ7lJ2CG .rough-node .label,#mermaid-svg-srRKxT7MdQ7lJ2CG .node .label,#mermaid-svg-srRKxT7MdQ7lJ2CG .image-shape .label,#mermaid-svg-srRKxT7MdQ7lJ2CG .icon-shape .label{text-align:center;}#mermaid-svg-srRKxT7MdQ7lJ2CG .node.clickable{cursor:pointer;}#mermaid-svg-srRKxT7MdQ7lJ2CG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-srRKxT7MdQ7lJ2CG .arrowheadPath{fill:#333333;}#mermaid-svg-srRKxT7MdQ7lJ2CG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-srRKxT7MdQ7lJ2CG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-srRKxT7MdQ7lJ2CG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-srRKxT7MdQ7lJ2CG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-srRKxT7MdQ7lJ2CG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-srRKxT7MdQ7lJ2CG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-srRKxT7MdQ7lJ2CG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-srRKxT7MdQ7lJ2CG .cluster text{fill:#333;}#mermaid-svg-srRKxT7MdQ7lJ2CG .cluster span{color:#333;}#mermaid-svg-srRKxT7MdQ7lJ2CG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-srRKxT7MdQ7lJ2CG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-srRKxT7MdQ7lJ2CG rect.text{fill:none;stroke-width:0;}#mermaid-svg-srRKxT7MdQ7lJ2CG .icon-shape,#mermaid-svg-srRKxT7MdQ7lJ2CG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-srRKxT7MdQ7lJ2CG .icon-shape p,#mermaid-svg-srRKxT7MdQ7lJ2CG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-srRKxT7MdQ7lJ2CG .icon-shape .label rect,#mermaid-svg-srRKxT7MdQ7lJ2CG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-srRKxT7MdQ7lJ2CG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-srRKxT7MdQ7lJ2CG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-srRKxT7MdQ7lJ2CG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 项目初始化与依赖安装
配置 MySQL tcm
migrate 建表
fetch_symmap_herbs.py 生成 CSV
import_herbs 导入约698条
fetch_zhongyoo + import_encyclopedia 约899条
backfill_origin_provinces 产区回填
seed_admin 初始化账号
实现用户认证
实现首页 home 仪表盘
实现 attributes/origin/graph Bundle
实现 cluster/association/bigscreen Bundle
实现列表百科与 CSV 导出
配置 SimpleUI 后台
联调与功能测试
端口8020部署演示
4.1 用户认证模块的实现
用户认证实现于 app/views.py。GET 请求渲染登录模板;POST 请求校验 User 表用户名与密码,成功则写入 Session 并跳转首页,失败则返回错误提示。注册流程校验空值、两次密码一致性与账号唯一性。
功能实现流程设计
图4.1 用户认证模块实现流程图
#mermaid-svg-KXP8ZlF4qTwpjMqc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KXP8ZlF4qTwpjMqc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KXP8ZlF4qTwpjMqc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KXP8ZlF4qTwpjMqc .error-icon{fill:#552222;}#mermaid-svg-KXP8ZlF4qTwpjMqc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KXP8ZlF4qTwpjMqc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KXP8ZlF4qTwpjMqc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KXP8ZlF4qTwpjMqc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KXP8ZlF4qTwpjMqc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KXP8ZlF4qTwpjMqc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KXP8ZlF4qTwpjMqc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KXP8ZlF4qTwpjMqc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KXP8ZlF4qTwpjMqc .marker.cross{stroke:#333333;}#mermaid-svg-KXP8ZlF4qTwpjMqc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KXP8ZlF4qTwpjMqc p{margin:0;}#mermaid-svg-KXP8ZlF4qTwpjMqc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KXP8ZlF4qTwpjMqc .cluster-label text{fill:#333;}#mermaid-svg-KXP8ZlF4qTwpjMqc .cluster-label span{color:#333;}#mermaid-svg-KXP8ZlF4qTwpjMqc .cluster-label span p{background-color:transparent;}#mermaid-svg-KXP8ZlF4qTwpjMqc .label text,#mermaid-svg-KXP8ZlF4qTwpjMqc span{fill:#333;color:#333;}#mermaid-svg-KXP8ZlF4qTwpjMqc .node rect,#mermaid-svg-KXP8ZlF4qTwpjMqc .node circle,#mermaid-svg-KXP8ZlF4qTwpjMqc .node ellipse,#mermaid-svg-KXP8ZlF4qTwpjMqc .node polygon,#mermaid-svg-KXP8ZlF4qTwpjMqc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KXP8ZlF4qTwpjMqc .rough-node .label text,#mermaid-svg-KXP8ZlF4qTwpjMqc .node .label text,#mermaid-svg-KXP8ZlF4qTwpjMqc .image-shape .label,#mermaid-svg-KXP8ZlF4qTwpjMqc .icon-shape .label{text-anchor:middle;}#mermaid-svg-KXP8ZlF4qTwpjMqc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KXP8ZlF4qTwpjMqc .rough-node .label,#mermaid-svg-KXP8ZlF4qTwpjMqc .node .label,#mermaid-svg-KXP8ZlF4qTwpjMqc .image-shape .label,#mermaid-svg-KXP8ZlF4qTwpjMqc .icon-shape .label{text-align:center;}#mermaid-svg-KXP8ZlF4qTwpjMqc .node.clickable{cursor:pointer;}#mermaid-svg-KXP8ZlF4qTwpjMqc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KXP8ZlF4qTwpjMqc .arrowheadPath{fill:#333333;}#mermaid-svg-KXP8ZlF4qTwpjMqc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KXP8ZlF4qTwpjMqc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KXP8ZlF4qTwpjMqc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KXP8ZlF4qTwpjMqc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KXP8ZlF4qTwpjMqc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KXP8ZlF4qTwpjMqc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KXP8ZlF4qTwpjMqc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KXP8ZlF4qTwpjMqc .cluster text{fill:#333;}#mermaid-svg-KXP8ZlF4qTwpjMqc .cluster span{color:#333;}#mermaid-svg-KXP8ZlF4qTwpjMqc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KXP8ZlF4qTwpjMqc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KXP8ZlF4qTwpjMqc rect.text{fill:none;stroke-width:0;}#mermaid-svg-KXP8ZlF4qTwpjMqc .icon-shape,#mermaid-svg-KXP8ZlF4qTwpjMqc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KXP8ZlF4qTwpjMqc .icon-shape p,#mermaid-svg-KXP8ZlF4qTwpjMqc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KXP8ZlF4qTwpjMqc .icon-shape .label rect,#mermaid-svg-KXP8ZlF4qTwpjMqc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KXP8ZlF4qTwpjMqc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KXP8ZlF4qTwpjMqc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KXP8ZlF4qTwpjMqc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} GET
POST
成功
失败
打开登录页
GET还是POST
render login.html + tcm-bg.jpg
读取 username/password
User.objects.get 校验
session 保存用户名
redirect /app/home/
errorResponse 提示
核心代码实现
python
# 来源:app/views.py --- 前台登录
def login(request):
if request.method == "GET":
return render(request, "login.html")
username = request.POST.get("username")
password = request.POST.get("password")
try:
User.objects.get(username=username, password=password)
request.session["username"] = username
return redirect("/app/home/")
except User.DoesNotExist:
return errorResponse.errorResponse(request, "用户名或密码错误")
python
# 来源:app/urls.py --- 认证相关路由(节选)
path("login/", views.login, name="login"),
path("register/", views.register, name="register"),
path("logOut/", views.logOut, name="logOut"),
path("changeSelfInfo/", views.changeSelfInfo, name="changeSelfInfo"),
path("changePassword/", views.changePassword, name="changePassword"),
实现效果展示
访问路径 :http://127.0.0.1:8020/app/login/(默认账号 admin / 123456)
4.2 首页仪表盘模块的实现
首页由 home 视图渲染 home.html,服务端注入药材总数、分类数、产区数、百科匹配率、有毒标注数、归经 TOP、功效占比饼图、分类代表药材与百科图片推荐。可选请求 /app/api/overview/ 调用 overview_bundle 获取洞察 narrative。
功能实现流程设计
图4.2 首页仪表盘模块实现流程图
#mermaid-svg-EXwR2YhdS4XUfJbq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EXwR2YhdS4XUfJbq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EXwR2YhdS4XUfJbq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EXwR2YhdS4XUfJbq .error-icon{fill:#552222;}#mermaid-svg-EXwR2YhdS4XUfJbq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EXwR2YhdS4XUfJbq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EXwR2YhdS4XUfJbq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EXwR2YhdS4XUfJbq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EXwR2YhdS4XUfJbq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EXwR2YhdS4XUfJbq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EXwR2YhdS4XUfJbq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EXwR2YhdS4XUfJbq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EXwR2YhdS4XUfJbq .marker.cross{stroke:#333333;}#mermaid-svg-EXwR2YhdS4XUfJbq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EXwR2YhdS4XUfJbq p{margin:0;}#mermaid-svg-EXwR2YhdS4XUfJbq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EXwR2YhdS4XUfJbq .cluster-label text{fill:#333;}#mermaid-svg-EXwR2YhdS4XUfJbq .cluster-label span{color:#333;}#mermaid-svg-EXwR2YhdS4XUfJbq .cluster-label span p{background-color:transparent;}#mermaid-svg-EXwR2YhdS4XUfJbq .label text,#mermaid-svg-EXwR2YhdS4XUfJbq span{fill:#333;color:#333;}#mermaid-svg-EXwR2YhdS4XUfJbq .node rect,#mermaid-svg-EXwR2YhdS4XUfJbq .node circle,#mermaid-svg-EXwR2YhdS4XUfJbq .node ellipse,#mermaid-svg-EXwR2YhdS4XUfJbq .node polygon,#mermaid-svg-EXwR2YhdS4XUfJbq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EXwR2YhdS4XUfJbq .rough-node .label text,#mermaid-svg-EXwR2YhdS4XUfJbq .node .label text,#mermaid-svg-EXwR2YhdS4XUfJbq .image-shape .label,#mermaid-svg-EXwR2YhdS4XUfJbq .icon-shape .label{text-anchor:middle;}#mermaid-svg-EXwR2YhdS4XUfJbq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EXwR2YhdS4XUfJbq .rough-node .label,#mermaid-svg-EXwR2YhdS4XUfJbq .node .label,#mermaid-svg-EXwR2YhdS4XUfJbq .image-shape .label,#mermaid-svg-EXwR2YhdS4XUfJbq .icon-shape .label{text-align:center;}#mermaid-svg-EXwR2YhdS4XUfJbq .node.clickable{cursor:pointer;}#mermaid-svg-EXwR2YhdS4XUfJbq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EXwR2YhdS4XUfJbq .arrowheadPath{fill:#333333;}#mermaid-svg-EXwR2YhdS4XUfJbq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EXwR2YhdS4XUfJbq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EXwR2YhdS4XUfJbq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EXwR2YhdS4XUfJbq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EXwR2YhdS4XUfJbq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EXwR2YhdS4XUfJbq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EXwR2YhdS4XUfJbq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EXwR2YhdS4XUfJbq .cluster text{fill:#333;}#mermaid-svg-EXwR2YhdS4XUfJbq .cluster span{color:#333;}#mermaid-svg-EXwR2YhdS4XUfJbq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EXwR2YhdS4XUfJbq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EXwR2YhdS4XUfJbq rect.text{fill:none;stroke-width:0;}#mermaid-svg-EXwR2YhdS4XUfJbq .icon-shape,#mermaid-svg-EXwR2YhdS4XUfJbq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EXwR2YhdS4XUfJbq .icon-shape p,#mermaid-svg-EXwR2YhdS4XUfJbq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EXwR2YhdS4XUfJbq .icon-shape .label rect,#mermaid-svg-EXwR2YhdS4XUfJbq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EXwR2YhdS4XUfJbq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EXwR2YhdS4XUfJbq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EXwR2YhdS4XUfJbq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 访问 /app/home/
home 视图聚合 stats
_encyclopedia_stats 匹配率
_meridian_top + category_pie
highlight_herbs 分类代表
render home.html
可选 overview API
summary_payload + insights_payload
结束
核心代码实现
python
# 来源:app/views.py --- 首页仪表盘(节选)
def home(request):
userInfo = _user(request)
stats = HerbRecord.objects.aggregate(
record_count=Count("id"),
category_count=Count("efficacy_category", distinct=True),
province_count=Count("origin_province", distinct=True),
)
enc_stats = _encyclopedia_stats()
df = to_frame(HerbRecord.objects.all(), fields=["name", "meridian", ...])
meridian_top = _meridian_top(df, limit=10)
# ... category_pie、highlight_herbs、enc_with_img ...
return render(request, "home.html", {...})
python
# 来源:app/utils/tcm_charts.py --- overview_bundle
def overview_bundle(request):
df = _df(request)
return {
"summary": summary_payload(request, df=df),
"insights": insights_payload(request, df=df),
"encyclopedia": _encyclopedia_stats(),
"generated_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
}
实现效果展示
访问路径 :http://127.0.0.1:8020/app/home/
4.3 属性分析模块的实现
属性分析通过 attributes_bundle 输出功效饼图、药性/药味柱图、毒性饼图、归经 TOP、卡方检验与功效×药性热力矩阵。页面路由为 /app/attributes/,历史 /app/overview/ 与 /app/stat/ 在视图中重定向至本模块。
功能实现流程设计
图4.3 属性分析模块实现流程图
#mermaid-svg-yljHJz6dZfMnzWpB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yljHJz6dZfMnzWpB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yljHJz6dZfMnzWpB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yljHJz6dZfMnzWpB .error-icon{fill:#552222;}#mermaid-svg-yljHJz6dZfMnzWpB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yljHJz6dZfMnzWpB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yljHJz6dZfMnzWpB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yljHJz6dZfMnzWpB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yljHJz6dZfMnzWpB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yljHJz6dZfMnzWpB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yljHJz6dZfMnzWpB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yljHJz6dZfMnzWpB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yljHJz6dZfMnzWpB .marker.cross{stroke:#333333;}#mermaid-svg-yljHJz6dZfMnzWpB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yljHJz6dZfMnzWpB p{margin:0;}#mermaid-svg-yljHJz6dZfMnzWpB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yljHJz6dZfMnzWpB .cluster-label text{fill:#333;}#mermaid-svg-yljHJz6dZfMnzWpB .cluster-label span{color:#333;}#mermaid-svg-yljHJz6dZfMnzWpB .cluster-label span p{background-color:transparent;}#mermaid-svg-yljHJz6dZfMnzWpB .label text,#mermaid-svg-yljHJz6dZfMnzWpB span{fill:#333;color:#333;}#mermaid-svg-yljHJz6dZfMnzWpB .node rect,#mermaid-svg-yljHJz6dZfMnzWpB .node circle,#mermaid-svg-yljHJz6dZfMnzWpB .node ellipse,#mermaid-svg-yljHJz6dZfMnzWpB .node polygon,#mermaid-svg-yljHJz6dZfMnzWpB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yljHJz6dZfMnzWpB .rough-node .label text,#mermaid-svg-yljHJz6dZfMnzWpB .node .label text,#mermaid-svg-yljHJz6dZfMnzWpB .image-shape .label,#mermaid-svg-yljHJz6dZfMnzWpB .icon-shape .label{text-anchor:middle;}#mermaid-svg-yljHJz6dZfMnzWpB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yljHJz6dZfMnzWpB .rough-node .label,#mermaid-svg-yljHJz6dZfMnzWpB .node .label,#mermaid-svg-yljHJz6dZfMnzWpB .image-shape .label,#mermaid-svg-yljHJz6dZfMnzWpB .icon-shape .label{text-align:center;}#mermaid-svg-yljHJz6dZfMnzWpB .node.clickable{cursor:pointer;}#mermaid-svg-yljHJz6dZfMnzWpB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yljHJz6dZfMnzWpB .arrowheadPath{fill:#333333;}#mermaid-svg-yljHJz6dZfMnzWpB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yljHJz6dZfMnzWpB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yljHJz6dZfMnzWpB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yljHJz6dZfMnzWpB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yljHJz6dZfMnzWpB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yljHJz6dZfMnzWpB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yljHJz6dZfMnzWpB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yljHJz6dZfMnzWpB .cluster text{fill:#333;}#mermaid-svg-yljHJz6dZfMnzWpB .cluster span{color:#333;}#mermaid-svg-yljHJz6dZfMnzWpB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yljHJz6dZfMnzWpB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yljHJz6dZfMnzWpB rect.text{fill:none;stroke-width:0;}#mermaid-svg-yljHJz6dZfMnzWpB .icon-shape,#mermaid-svg-yljHJz6dZfMnzWpB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yljHJz6dZfMnzWpB .icon-shape p,#mermaid-svg-yljHJz6dZfMnzWpB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yljHJz6dZfMnzWpB .icon-shape .label rect,#mermaid-svg-yljHJz6dZfMnzWpB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yljHJz6dZfMnzWpB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yljHJz6dZfMnzWpB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yljHJz6dZfMnzWpB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 访问 /app/attributes/
module_attributes 渲染模板
AJAX /app/api/attributes-bundle/
cached_analysis_df
attributes_bundle 多图聚合
chi2_contingency + 交叉矩阵
tcm_pages.js ECharts 渲染
核心代码实现
python
# 来源:app/views.py --- overview/stat 重定向
def module_overview(request):
return redirect("module_attributes")
def module_stat(request):
return redirect("module_attributes")
def module_attributes(request):
return _module_page(request, "modules/attributes.html")
python
# 来源:app/utils/tcm_charts.py --- 卡方检验(节选)
def _chi_square_property_toxicity(df):
ct = pd.crosstab(df["property_nature"], df["toxicity_level"])
chi2, chi_p, dof, _ = stats.chi2_contingency(ct.values)
return {
"statistic": _num(chi2, 4),
"p_value": _num(chi_p, 6),
"significant": chi_p < 0.05,
...
}
python
# 来源:app/urls.py --- 属性分析路由与 API
path("attributes/", views.module_attributes, name="module_attributes"),
path("overview/", views.module_overview, name="module_overview"),
path("stat/", views.module_stat, name="module_stat"),
path("api/attributes-bundle/", api_views.api_attributes_bundle, name="api_attributes_bundle"),
实现效果展示
访问路径 :http://127.0.0.1:8020/app/attributes/
4.4 产地与关系图谱模块的实现
产地模块通过 origin_bundle 按省份统计并映射坐标;关系图谱通过 graph_bundle 手工构建药材与属性节点的共现网络。页面路由分别为 /app/origin/ 与 /app/graph/。
功能实现流程设计
图4.4 产地与关系图谱模块实现流程图
#mermaid-svg-WsUFtRXo1ku6iual{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WsUFtRXo1ku6iual .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WsUFtRXo1ku6iual .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WsUFtRXo1ku6iual .error-icon{fill:#552222;}#mermaid-svg-WsUFtRXo1ku6iual .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WsUFtRXo1ku6iual .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WsUFtRXo1ku6iual .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WsUFtRXo1ku6iual .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WsUFtRXo1ku6iual .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WsUFtRXo1ku6iual .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WsUFtRXo1ku6iual .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WsUFtRXo1ku6iual .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WsUFtRXo1ku6iual .marker.cross{stroke:#333333;}#mermaid-svg-WsUFtRXo1ku6iual svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WsUFtRXo1ku6iual p{margin:0;}#mermaid-svg-WsUFtRXo1ku6iual .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WsUFtRXo1ku6iual .cluster-label text{fill:#333;}#mermaid-svg-WsUFtRXo1ku6iual .cluster-label span{color:#333;}#mermaid-svg-WsUFtRXo1ku6iual .cluster-label span p{background-color:transparent;}#mermaid-svg-WsUFtRXo1ku6iual .label text,#mermaid-svg-WsUFtRXo1ku6iual span{fill:#333;color:#333;}#mermaid-svg-WsUFtRXo1ku6iual .node rect,#mermaid-svg-WsUFtRXo1ku6iual .node circle,#mermaid-svg-WsUFtRXo1ku6iual .node ellipse,#mermaid-svg-WsUFtRXo1ku6iual .node polygon,#mermaid-svg-WsUFtRXo1ku6iual .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WsUFtRXo1ku6iual .rough-node .label text,#mermaid-svg-WsUFtRXo1ku6iual .node .label text,#mermaid-svg-WsUFtRXo1ku6iual .image-shape .label,#mermaid-svg-WsUFtRXo1ku6iual .icon-shape .label{text-anchor:middle;}#mermaid-svg-WsUFtRXo1ku6iual .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WsUFtRXo1ku6iual .rough-node .label,#mermaid-svg-WsUFtRXo1ku6iual .node .label,#mermaid-svg-WsUFtRXo1ku6iual .image-shape .label,#mermaid-svg-WsUFtRXo1ku6iual .icon-shape .label{text-align:center;}#mermaid-svg-WsUFtRXo1ku6iual .node.clickable{cursor:pointer;}#mermaid-svg-WsUFtRXo1ku6iual .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WsUFtRXo1ku6iual .arrowheadPath{fill:#333333;}#mermaid-svg-WsUFtRXo1ku6iual .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WsUFtRXo1ku6iual .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WsUFtRXo1ku6iual .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WsUFtRXo1ku6iual .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WsUFtRXo1ku6iual .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WsUFtRXo1ku6iual .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WsUFtRXo1ku6iual .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WsUFtRXo1ku6iual .cluster text{fill:#333;}#mermaid-svg-WsUFtRXo1ku6iual .cluster span{color:#333;}#mermaid-svg-WsUFtRXo1ku6iual div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WsUFtRXo1ku6iual .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WsUFtRXo1ku6iual rect.text{fill:none;stroke-width:0;}#mermaid-svg-WsUFtRXo1ku6iual .icon-shape,#mermaid-svg-WsUFtRXo1ku6iual .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WsUFtRXo1ku6iual .icon-shape p,#mermaid-svg-WsUFtRXo1ku6iual .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WsUFtRXo1ku6iual .icon-shape .label rect,#mermaid-svg-WsUFtRXo1ku6iual .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WsUFtRXo1ku6iual .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WsUFtRXo1ku6iual .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WsUFtRXo1ku6iual :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 产地
图谱
访问 origin 或 graph
请求对应 bundle API
cached_analysis_df
模块分支
groupby + PROVINCE_COORDS
add_node + links 共现边
json_ok 返回
中国地图/力导向网络渲染
核心代码实现
python
# 来源:app/utils/tcm_charts.py --- 关系图谱(节选)
def graph_bundle(request, df=None):
max_herbs = min(int(request.GET.get("limit", 80)), 120)
sub = df.head(max_herbs) if len(df) > max_herbs else df
for _, row in sub.iterrows():
hid = f"h|{hname}"
add_node(hid, hname, 0, 16)
cid = f"c|{cat}"
add_node(cid, cat, 1, 30)
links.append({"source": hid, "target": cid})
# ... 药性、产区、归经连边 ...
return {"nodes": list(nodes.values()), "links": links, ...}
python
# 来源:app/urls.py --- 产地/图谱路由与 API
path("origin/", views.module_origin, name="module_origin"),
path("graph/", views.module_graph, name="module_graph"),
path("api/origin-bundle/", api_views.api_origin_bundle, name="api_origin_bundle"),
path("api/graph-bundle/", api_views.api_graph_bundle, name="api_graph_bundle"),
实现效果展示
访问路径:
- 产地地图:
http://127.0.0.1:8020/app/origin/ - 关系图谱:
http://127.0.0.1:8020/app/graph/
4.5 聚类与关联规则模块的实现
聚类模块通过 cluster_bundle 输出 PCA 散点与簇分布;关联规则通过 association_bundle 调用 mlxtend Apriori 或共现回退。页面路由分别为 /app/cluster/ 与 /app/association/。
功能实现流程设计
图4.5 聚类与关联规则模块实现流程图
#mermaid-svg-pkXudv2kxaGSmnA6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pkXudv2kxaGSmnA6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pkXudv2kxaGSmnA6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pkXudv2kxaGSmnA6 .error-icon{fill:#552222;}#mermaid-svg-pkXudv2kxaGSmnA6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pkXudv2kxaGSmnA6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pkXudv2kxaGSmnA6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pkXudv2kxaGSmnA6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pkXudv2kxaGSmnA6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pkXudv2kxaGSmnA6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pkXudv2kxaGSmnA6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pkXudv2kxaGSmnA6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pkXudv2kxaGSmnA6 .marker.cross{stroke:#333333;}#mermaid-svg-pkXudv2kxaGSmnA6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pkXudv2kxaGSmnA6 p{margin:0;}#mermaid-svg-pkXudv2kxaGSmnA6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pkXudv2kxaGSmnA6 .cluster-label text{fill:#333;}#mermaid-svg-pkXudv2kxaGSmnA6 .cluster-label span{color:#333;}#mermaid-svg-pkXudv2kxaGSmnA6 .cluster-label span p{background-color:transparent;}#mermaid-svg-pkXudv2kxaGSmnA6 .label text,#mermaid-svg-pkXudv2kxaGSmnA6 span{fill:#333;color:#333;}#mermaid-svg-pkXudv2kxaGSmnA6 .node rect,#mermaid-svg-pkXudv2kxaGSmnA6 .node circle,#mermaid-svg-pkXudv2kxaGSmnA6 .node ellipse,#mermaid-svg-pkXudv2kxaGSmnA6 .node polygon,#mermaid-svg-pkXudv2kxaGSmnA6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pkXudv2kxaGSmnA6 .rough-node .label text,#mermaid-svg-pkXudv2kxaGSmnA6 .node .label text,#mermaid-svg-pkXudv2kxaGSmnA6 .image-shape .label,#mermaid-svg-pkXudv2kxaGSmnA6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-pkXudv2kxaGSmnA6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pkXudv2kxaGSmnA6 .rough-node .label,#mermaid-svg-pkXudv2kxaGSmnA6 .node .label,#mermaid-svg-pkXudv2kxaGSmnA6 .image-shape .label,#mermaid-svg-pkXudv2kxaGSmnA6 .icon-shape .label{text-align:center;}#mermaid-svg-pkXudv2kxaGSmnA6 .node.clickable{cursor:pointer;}#mermaid-svg-pkXudv2kxaGSmnA6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pkXudv2kxaGSmnA6 .arrowheadPath{fill:#333333;}#mermaid-svg-pkXudv2kxaGSmnA6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pkXudv2kxaGSmnA6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pkXudv2kxaGSmnA6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pkXudv2kxaGSmnA6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pkXudv2kxaGSmnA6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pkXudv2kxaGSmnA6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pkXudv2kxaGSmnA6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pkXudv2kxaGSmnA6 .cluster text{fill:#333;}#mermaid-svg-pkXudv2kxaGSmnA6 .cluster span{color:#333;}#mermaid-svg-pkXudv2kxaGSmnA6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pkXudv2kxaGSmnA6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pkXudv2kxaGSmnA6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-pkXudv2kxaGSmnA6 .icon-shape,#mermaid-svg-pkXudv2kxaGSmnA6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pkXudv2kxaGSmnA6 .icon-shape p,#mermaid-svg-pkXudv2kxaGSmnA6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pkXudv2kxaGSmnA6 .icon-shape .label rect,#mermaid-svg-pkXudv2kxaGSmnA6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pkXudv2kxaGSmnA6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pkXudv2kxaGSmnA6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pkXudv2kxaGSmnA6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 聚类
关联
apriori
cooccurrence
访问 cluster 或 association
请求 bundle API
cached_analysis_df
算法分支
StandardScaler + KMeans + PCA
apriori + association_rules
引擎
返回 rules 列表
两两共现回退
json_ok 返回
散点/规则表分页渲染
核心代码实现
python
# 来源:app/utils/tcm_charts.py --- KMeans 聚类(节选)
scaled = StandardScaler().fit_transform(matrix.to_numpy(dtype=float))
labels = KMeans(n_clusters=n_clusters, random_state=42, n_init=10).fit_predict(scaled)
pca = PCA(n_components=2, random_state=42)
coords = pca.fit_transform(scaled)
python
# 来源:app/utils/tcm_charts.py --- Apriori 关联规则(节选)
def association_bundle(request, df=None):
min_support = float(request.GET.get("min_support", 0.02))
min_confidence = float(request.GET.get("min_confidence", 0.4))
rules, engine = _apriori_rules(df, min_support=min_support, min_confidence=min_confidence, top_n=top_n)
return {"rules": rules, "total": len(rules), "engine": engine, ...}
python
# 来源:app/urls.py --- 聚类/关联路由与 API
path("cluster/", views.module_cluster, name="module_cluster"),
path("association/", views.module_association, name="module_association"),
path("api/cluster-bundle/", api_views.api_cluster_bundle, name="api_cluster_bundle"),
path("api/association-bundle/", api_views.api_association_bundle, name="api_association_bundle"),
实现效果展示
访问路径:
- 聚类分析:
http://127.0.0.1:8020/app/cluster/ - 关联规则:
http://127.0.0.1:8020/app/association/
4.6 百科列表大屏与后台模块的实现
药材列表页对 HerbRecord 分页展示并支持 CSV 导出;百科列表与详情展示图文知识;数据大屏全屏渲染 KPI 与地图;后台通过 SimpleUI 管理数据并刷新缓存。
功能实现流程设计
图4.6 百科列表大屏与后台模块实现流程图
#mermaid-svg-iQX4Y9BE4biwJuFh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iQX4Y9BE4biwJuFh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iQX4Y9BE4biwJuFh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iQX4Y9BE4biwJuFh .error-icon{fill:#552222;}#mermaid-svg-iQX4Y9BE4biwJuFh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iQX4Y9BE4biwJuFh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iQX4Y9BE4biwJuFh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iQX4Y9BE4biwJuFh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iQX4Y9BE4biwJuFh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iQX4Y9BE4biwJuFh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iQX4Y9BE4biwJuFh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iQX4Y9BE4biwJuFh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iQX4Y9BE4biwJuFh .marker.cross{stroke:#333333;}#mermaid-svg-iQX4Y9BE4biwJuFh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iQX4Y9BE4biwJuFh p{margin:0;}#mermaid-svg-iQX4Y9BE4biwJuFh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iQX4Y9BE4biwJuFh .cluster-label text{fill:#333;}#mermaid-svg-iQX4Y9BE4biwJuFh .cluster-label span{color:#333;}#mermaid-svg-iQX4Y9BE4biwJuFh .cluster-label span p{background-color:transparent;}#mermaid-svg-iQX4Y9BE4biwJuFh .label text,#mermaid-svg-iQX4Y9BE4biwJuFh span{fill:#333;color:#333;}#mermaid-svg-iQX4Y9BE4biwJuFh .node rect,#mermaid-svg-iQX4Y9BE4biwJuFh .node circle,#mermaid-svg-iQX4Y9BE4biwJuFh .node ellipse,#mermaid-svg-iQX4Y9BE4biwJuFh .node polygon,#mermaid-svg-iQX4Y9BE4biwJuFh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iQX4Y9BE4biwJuFh .rough-node .label text,#mermaid-svg-iQX4Y9BE4biwJuFh .node .label text,#mermaid-svg-iQX4Y9BE4biwJuFh .image-shape .label,#mermaid-svg-iQX4Y9BE4biwJuFh .icon-shape .label{text-anchor:middle;}#mermaid-svg-iQX4Y9BE4biwJuFh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iQX4Y9BE4biwJuFh .rough-node .label,#mermaid-svg-iQX4Y9BE4biwJuFh .node .label,#mermaid-svg-iQX4Y9BE4biwJuFh .image-shape .label,#mermaid-svg-iQX4Y9BE4biwJuFh .icon-shape .label{text-align:center;}#mermaid-svg-iQX4Y9BE4biwJuFh .node.clickable{cursor:pointer;}#mermaid-svg-iQX4Y9BE4biwJuFh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iQX4Y9BE4biwJuFh .arrowheadPath{fill:#333333;}#mermaid-svg-iQX4Y9BE4biwJuFh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iQX4Y9BE4biwJuFh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iQX4Y9BE4biwJuFh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iQX4Y9BE4biwJuFh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iQX4Y9BE4biwJuFh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iQX4Y9BE4biwJuFh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iQX4Y9BE4biwJuFh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iQX4Y9BE4biwJuFh .cluster text{fill:#333;}#mermaid-svg-iQX4Y9BE4biwJuFh .cluster span{color:#333;}#mermaid-svg-iQX4Y9BE4biwJuFh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iQX4Y9BE4biwJuFh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iQX4Y9BE4biwJuFh rect.text{fill:none;stroke-width:0;}#mermaid-svg-iQX4Y9BE4biwJuFh .icon-shape,#mermaid-svg-iQX4Y9BE4biwJuFh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iQX4Y9BE4biwJuFh .icon-shape p,#mermaid-svg-iQX4Y9BE4biwJuFh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iQX4Y9BE4biwJuFh .icon-shape .label rect,#mermaid-svg-iQX4Y9BE4biwJuFh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iQX4Y9BE4biwJuFh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iQX4Y9BE4biwJuFh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iQX4Y9BE4biwJuFh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
访问 /app/herb_list/
filtered_queryset + Paginator 20
渲染分页表格
导出?
api_export UTF-8 BOM CSV
访问 /app/encyclopedia/
百科分页 18 条/页
访问 /app/bigscreen/
bigscreen_bundle
tcm_bigscreen.js 渲染
访问 /admin/
TcmCacheInvalidationMixin CRUD
invalidate_tcm_cache
核心代码实现
python
# 来源:app/views.py --- CSV 导出(节选)
@require_GET
def api_export(request):
qs = filtered_queryset(request)
buf = io.StringIO()
buf.write("\ufeff")
writer = csv.writer(buf)
fields = ["herb_code", "name", "efficacy_category", "property_nature", "taste",
"meridian", "main_efficacy", "dosage_g", "price_per_g", ...]
writer.writerow(fields)
for row in qs.values_list(*fields):
writer.writerow(row)
resp["Content-Disposition"] = 'attachment; filename="tcm_herbs_export.csv"'
return resp
python
# 来源:app/admin.py --- 后台缓存失效
class TcmCacheInvalidationMixin:
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
invalidate_tcm_cache()
python
# 来源:基于Django系统/settings.py --- 数据库与缓存(节选)
DATABASES = {"default": {"ENGINE": "django.db.backends.mysql", "NAME": "tcm", ...}}
TCM_CACHE_TTL = 120
实现效果展示
访问路径:
- 药材列表:
http://127.0.0.1:8020/app/herb_list/ - CSV 导出:
http://127.0.0.1:8020/app/api/export/ - 中药百科:
http://127.0.0.1:8020/app/encyclopedia/ - 数据大屏:
http://127.0.0.1:8020/app/bigscreen/ - 后台管理:
http://127.0.0.1:8020/admin/(admin / admin123)
4.7 用户界面实现
界面采用 Bootstrap 5 布局,侧栏按「首页仪表盘 / 属性分析 / 产地地图 / 关系图谱 / 聚类分析 / 关联规则 / 药材列表 / 中药百科 / 数据大屏 / 个人信息」分组。主题样式由 tcm-theme.css 统一为墨绿色调,登录背景使用 tcm-bg.jpg。各分析页复用 chart_base.html 骨架与 tcm_pages.js 请求 Bundle API,筛选条通过 /app/api/options/ 获取功效分类与省份下拉。数据大屏采用独立全屏布局与 tcm-bigscreen.css。价格、库存等 C 类估算字段仅在列表页表格与导出 CSV 中出现,分析图表不包含此类指标。
图4.7 用户登录界面

登录页采用墨绿主题背景 tcm-bg.jpg 与居中表单,用户输入账号密码后提交,成功进入系统首页。
图4.8 系统首页界面

首页展示药材总数、功效分类数、道地产区数、百科匹配率、有毒标注等 KPI 卡片,以及归经 TOP、功效占比与百科图片推荐。
图4.9 属性分析界面

属性分析页展示功效/药性/药味/毒性分布、归经 TOP、药性×毒性卡方检验与功效×药性共现热力图,支持功效分类筛选。
图4.10 产地地图界面

产地地图页按道地产区统计药材数量并映射至中国地图,产区数据优先来自百科「产地分布」解析。
图4.11 关系图谱界面

关系图谱页以力导向网络展示药材与功效分类、药性、道地产区、归经等属性节点的共现关系。
图4.12 聚类分析界面

聚类页展示 K-Means 聚类后 PCA 二维投影散点与簇分布,同一簇内药材在性味毒性组合上较为接近。
图4.13 关联规则界面

关联规则页列出 Apriori 挖掘的支持度、置信度与提升度达标规则,默认支持度≥0.02、置信度≥0.4,前端分页展示。
图4.14 药材列表界面

药材列表页分页展示 SymMap 业务字段及演示估算的价格库存列,支持分类筛选与 CSV 导出。
图4.15 中药百科界面

百科页展示约 899 条中药网条目,含药材图片、性味归经、功效主治等知识字段,可与药材档案互链。
图4.16 数据大屏界面

数据大屏全屏展示 KPI、功效分类占比、药性柱图、毒性结构、归经 TOP 与省份地图,适用于答辩演示。
图4.17 后台管理界面

SimpleUI 后台提供中药药材记录、百科条目与前台用户的检索、筛选与维护,菜单分组清晰。
4.8 系统集成与部署
部署步骤概要如下:
- 安装
requirements.txt依赖(Django 3.1.14、django-simpleui、PyMySQL、pandas、numpy、scipy、scikit-learn、mlxtend、pillow、requests 等)。 - 创建 MySQL 库
tcm,配置连接账号(可通过ensure_mysql_db.py --from-django自动创建)。 - 执行
python manage.py migrate。 - 执行
python scripts/fetch_symmap_herbs.py生成tcm_herbs_data.csv,再python manage.py import_herbs --path tcm_herbs_data.csv --truncate。 - 执行
python scripts/fetch_zhongyoo_encyclopedia.py生成hurbTable.csv,再python manage.py import_encyclopedia --path hurbTable.csv --truncate。 - 执行
python manage.py backfill_origin_provinces回填道地产区。 python manage.py seed_admin初始化前台用户 admin/123456 与后台 admin/admin123。python manage.py runserver 8020或运行run_setup.bat一键初始化并启动。
根 URL 将空路径重定向到 /app/login/,静态与媒体资源由 Django 开发服务器挂载。课题演示以 8020 端口本地运行为准。可选执行 python scripts/test_all_features.py 进行功能冒烟测试。
5 系统测试
5.1 系统功能测试
测试环境:Windows 10,Python 3.11,Django 3.1.14,MySQL(库 tcm),浏览器访问 http://127.0.0.1:8020。前台默认账号 admin/123456,后台 admin/admin123。
5.1.1 用户认证功能测试用例
| 编号 | 说明 | 条件 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| TC-A01 | 正确登录 | 库中存在 admin | 打开 /app/login/,输入 admin/123456 提交 |
跳转 /app/home/,侧栏可见 |
通过 |
| TC-A02 | 错误密码 | 账号存在 | 输入错误密码提交 | 提示用户名或密码错误 | 通过 |
| TC-A03 | 注册新用户 | 用户名未占用 | /app/register/ 填写一致密码 |
注册成功并回到登录页 | 通过 |
| TC-A04 | 退出登录 | 已登录 | 执行 /app/logOut/ |
Session 清空,回到登录页 | 通过 |
| TC-A05 | 未登录拦截 | 无 Session | 直接访问 /app/attributes/ |
重定向至登录页 | 通过 |
5.1.2 数据管理功能测试用例
| 编号 | 说明 | 条件 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| TC-B01 | 药材列表浏览 | 已导入 SymMap | 打开 /app/herb_list/ |
分页展示约 698 条药材记录 | 通过 |
| TC-B02 | 百科列表浏览 | 已导入百科 | 打开 /app/encyclopedia/ |
分页展示约 899 条百科 | 通过 |
| TC-B03 | 后台查看药材 | 管理员可进 Admin | 登录 /admin/ 打开中药药材记录 |
可检索药名/功效/产区 | 通过 |
| TC-B04 | CSV 导出 | 已登录且有数据 | 访问 /app/api/export/ |
下载 UTF-8 BOM 的 tcm_herbs_export.csv | 通过 |
| TC-B05 | 脚本入库 | CSV 已生成 | 执行 import_herbs --truncate |
约 698 条写入 HerbRecord | 通过 |
| TC-B06 | 百科入库 | hurbTable.csv 存在 | 执行 import_encyclopedia --truncate |
约 899 条写入 EncyclopediaRecord | 通过 |
5.1.3 核心业务功能测试用例
| 编号 | 说明 | 条件 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| TC-C01 | 首页仪表盘 | 有药材数据 | 打开 /app/home/ |
KPI、归经 TOP、功效占比正常 | 通过 |
| TC-C02 | 属性分析 | 有 SymMap 字段 | 打开 /app/attributes/ |
功效/药性/药味/毒性图表有数据 | 通过 |
| TC-C03 | 产地地图 | 有产区字段 | 打开 /app/origin/ |
省份统计与中国地图可见 | 通过 |
| TC-C04 | 关系图谱 | 有药材数据 | 打开 /app/graph/ |
节点连边网络可交互 | 通过 |
| TC-C05 | 数据大屏 | 已登录 | 打开 /app/bigscreen/ |
KPI 与多图全屏渲染 | 通过 |
| TC-C06 | overview 重定向 | 路由已配置 | 访问 /app/overview/ |
重定向至 /app/attributes/ |
通过 |
| TC-C07 | stat 重定向 | 路由已配置 | 访问 /app/stat/ |
重定向至 /app/attributes/ |
通过 |
5.1.4 数据分析功能测试用例
| 编号 | 说明 | 条件 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| TC-D01 | 卡方检验 | 样本≥2×2 列联 | /app/attributes/ 请求 bundle |
返回 χ²、p 值与显著性标记 | 通过 |
| TC-D02 | 聚类散点 | 样本≥簇数+2 | /app/cluster/?clusters=4 |
PCA 散点与簇分布返回 | 通过 |
| TC-D03 | Apriori 规则 | mlxtend 已安装 | /app/association/ |
规则列表含 support/confidence/lift | 通过 |
| TC-D04 | 关联阈值 | 默认参数 | 请求 association-bundle 无 top_n | 返回全部达标规则(engine=apriori 或 cooccurrence) | 通过 |
| TC-D05 | 筛选联动 | 指定 category | /app/api/attributes-bundle/?category=清热药 |
图表数据随筛选变化 | 通过 |
| TC-D06 | 价格不上图 | 分析字段限定 | 检查 attributes-bundle JSON | 不含 price_per_g/stock_kg 聚合 | 通过 |
5.1.5 系统集成功能测试用例
| 编号 | 说明 | 条件 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| TC-E01 | 根路径跳转 | 服务已启动 | 访问 / |
重定向到 /app/login/ |
通过 |
| TC-E02 | API 前缀 | 已登录 | 请求 /app/api/options/ 等 |
返回 JSON ok 结构 | 通过 |
| TC-E03 | 筛选选项 | 库有数据 | 请求 /app/api/options/ |
返回 categories/provinces/herbs_by_category | 通过 |
| TC-E04 | 前后台共库 | 库有数据 | 前台首页药材数与后台对照 | 数量级一致(约 698) | 通过 |
| TC-E05 | 百科匹配 API | 双表有数据 | 请求 /app/api/encyclopedia-stats/ |
返回 matched/match_rate | 通过 |
| TC-E06 | 搜索联合 | 已登录 | /app/search/?q=人参 |
药材与百科结果均返回 | 通过 |
| TC-E07 | 缓存失效 | 后台修改记录 | Admin 保存 HerbRecord 后再请求 bundle | 数据与版本号刷新 | 通过 |
5.1.6 用户界面功能测试用例
| 编号 | 说明 | 条件 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| TC-F01 | 侧栏导航 | 已登录 | 点击各分析菜单 | 对应页面激活高亮 | 通过 |
| TC-F02 | 修改信息 | 已登录 | /app/changeSelfInfo/ 保存 |
信息更新成功 | 通过 |
| TC-F03 | 修改密码 | 已登录 | /app/changePassword/ 提交 |
新密码可登录 | 通过 |
| TC-F04 | 筛选联动 | 有数据 | 切换功效分类/关键词 | 各页图表随筛选刷新 | 通过 |
| TC-F05 | 墨绿主题 | 静态资源可访问 | 浏览各页 | tcm-theme.css 样式一致 | 通过 |
| TC-F06 | 大屏独立页 | 已登录 | 访问 /app/bigscreen/ |
全屏布局与 tcm-bigscreen.css 生效 | 通过 |
| TC-F07 | 药材百科互链 | 存在对齐药名 | 打开 herb 详情与 encyclopedia 详情 | 互链跳转正确 | 通过 |
5.2 系统性能测试
5.2.1 响应时间性能分析
在约 698 条 HerbRecord、899 条 EncyclopediaRecord 规模下,普通页面首屏一般可在 1~3 秒内完成;带聚合的 Bundle API 在缓存命中时响应更快。首次冷查询因 DataFrame 转换与聚类/Apriori 计算耗时相对更高,之后 120 秒缓存窗口内重复筛选可明显降耗。聚类 Bundle 缓存倍数设为 3×TTL,进一步减少重复 KMeans 与 PCA 计算。
5.2.2 并发性能测试
课题演示场景以单机少量并发为主。Session 认证与本地内存缓存在多用户同时筛选相近条件时能够复用结果;若并发显著增大,可考虑更换 Redis 等缓存后端。当前实现满足课程验收与本机演示需求。
5.2.3 数据库性能测试
针对 idx_herb_efficacy、idx_herb_origin 的组合索引,对功效分类与道地产区过滤的 Count 查询有帮助。列表导出与 to_frame 使用 iterator(chunk_size=2000) 流式读库,避免一次性装入全部行。百科 name 索引加速按药名对齐与搜索。
5.2.4 性能优化措施
- 使用
cached_analysis_df与TCM_CACHE_TTL=120降低重复计算。 - Bundle API 合并多图数据,减少 HTTP 往返。
- 关系图谱默认 limit=80,聚类散点最多展示 300 点,减轻前端渲染压力。
- 后台增删改调用
invalidate_tcm_cache递增版本号,避免脏读而非全库清缓存。 - 筛选项
build_filter_options缓存 TTL 为 5×TTL,减少 distinct 查询频率。 - 导入阶段
--truncate后批量写入,缩短初始化时间。
6 总结与展望
6.1 总结
本文完成了基于 Python 的中药药材数据可视化分析系统的设计与实现。系统以 Django 3.1.14 + SimpleUI 为 Web 框架,MySQL 库名 tcm,PyMySQL 驱动,服务端口 8020 ,入口 manage.py,前台 /app/login/(admin/123456),后台 /admin/(admin/admin123)。围绕 User(app_user)、HerbRecord、EncyclopediaRecord 与 Django auth_user 四张核心表,按六大模块组织了用户认证、首页仪表盘、属性分析、产地与关系图谱、聚类与关联规则、百科列表大屏与后台;分析 API 提供 options、overview、attributes-bundle、origin-bundle、cluster-bundle、association-bundle、graph-bundle、bigscreen-bundle、export、search 等接口。数据覆盖 SymMap 约 698 条药材与中药网约 899 条百科;属性字段为 A 可信 ,道地产区为 B 半可信 ,价格库存等为 C 演示估算 且不参与统计图表 。数据采集通过 fetch_symmap_herbs.py、fetch_zhongyoo_encyclopedia.py 与对应 import 命令完成。分析采用 scipy 卡方检验、KMeans+PCA+StandardScaler 聚类、mlxtend Apriori 关联规则(默认 support 0.02、confidence 0.4)与手工共现关系图谱;utils 层 tcm_core、tcm_charts、tcm_constants 分层清晰,本地缓存 TTL 为 120 秒。前端以 ECharts 5 与 Bootstrap 5 呈现,墨绿主题 tcm-theme.css,独立大屏 /app/bigscreen/。历史路由 /app/overview/、/app/stat/ 已重定向至 /app/attributes/。经功能测试,主要模块运行符合预期,能够支撑教学演示与课题分析。
从模块规模看,论文第 3、4 章按上述 6 个功能模块展开;侧栏路由包括 /app/home/、/app/attributes/、/app/origin/、/app/graph/、/app/cluster/、/app/association/、/app/herb_list/、/app/encyclopedia/、/app/bigscreen/ 等,形成可复现的本地化中药药材可视化分析方案。
6.2 展望
后续可从以下方向继续完善:接入更多权威药材数据库并与 SymMap 字段对照;引入 Neo4j 等图数据库存储复杂方剂---药材关系,替代手工共现网络;增加角色权限细分与操作审计;将脚本拉取接入定时任务实现增量更新;优化移动端图表交互与百科全文检索;在保持字段可信度分层的前提下,扩展更多可解释算法(如 LDA 主题模型)辅助功效分类文本挖掘;提升产区解析准确率,减少演示估算兜底比例。
参考文献
1 Django Software Foundation. Django documentation (3.1.x)EB/OL. https://docs.djangoproject.com/, 2021.
2 伊诺克森托娃. Python数据分析M. 北京: 人民邮电出版社, 2020.
3 王珊, 萨师煊. 数据库系统概论M. 5版. 北京: 高等教育出版社, 2014.
4 Apache ECharts. ECharts 官方文档EB/OL. https://echarts.apache.org/zh/index.html.
5 McKinney W. Data Structures for Statistical Computing in PythonC//Proceedings of the 9th Python in Science Conference, 2010.
6 Wu Y, et al. SymMap: an integrative database of traditional Chinese medicine enhanced by symptom mappingJ. Nucleic Acids Research, 2019.
7 李刚. 疯狂 Python 讲义M. 北京: 电子工业出版社, 2019.
8 张浩. 基于 Web 的数据可视化系统设计与实现J. 计算机应用与软件, 2020.
9 Bootstrap Team. Bootstrap 5 DocumentationEB/OL. https://getbootstrap.com/docs/5.0/.
10 周志华. 机器学习M. 北京: 清华大学出版社, 2016.
11 Agrawal R, Srikant R. Fast Algorithms for Mining Association RulesC//Proc. VLDB, 1994.
12 Virtanen P, et al. SciPy 1.0: fundamental algorithms for scientific computing in PythonJ. Nature Methods, 2020.
13 Pedregosa F, et al. Scikit-learn: Machine Learning in PythonJ. JMLR, 2011.
14 Raschka S. mlxtend: Machine Learning Extensions for PythonEB/OL. https://rasbt.github.io/mlxtend/.
15 国家中医药管理局. 中医药统计与信息化发展报告R. 北京, 2022.
致谢
在毕业设计完成之际,衷心感谢指导老师在选题、需求分析、系统设计与论文撰写过程中给予的耐心指导与严格要求。感谢同学在环境搭建、功能联调与界面体验反馈方面提供的帮助。感谢 SymMap、中药网公开数据与开源社区提供的 Django、ECharts、pandas、scikit-learn、mlxtend 等相关技术与工具,使本系统能够顺利落地。也感谢家人一直以来的理解与支持。由于水平与时间有限,系统中道地产区解析与演示估算字段仍有改进空间,恳请各位老师批评指正。