From 4da97d600a6064d780572282fe299610f0625104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=9D=B0?= Date: Mon, 22 Sep 2025 17:06:43 +0100 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=95=8C=E9=9D=A2=E5=B8=83?= =?UTF-8?q?=E5=B1=80=EF=BC=8C=E5=8F=82=E8=80=83CRM=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=EF=BC=8C=E8=B0=83=E6=95=B4=E5=AD=97=E4=BD=93=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=88=86=E9=A1=B5=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/web/blueprints/alerts.py | 31 +- src/web/blueprints/conversations.py | 15 +- src/web/blueprints/workorders.py | 19 +- src/web/static/css/README.md | 195 ++++++++ src/web/static/css/design-system.css | 554 +++++++++++++++++++++++ src/web/static/css/style.css | 38 +- src/web/static/css/typography-guide.html | 212 +++++++++ src/web/static/js/dashboard.js | 16 +- src/web/templates/dashboard.html | 21 +- 9 files changed, 1038 insertions(+), 63 deletions(-) create mode 100644 src/web/static/css/README.md create mode 100644 src/web/static/css/design-system.css create mode 100644 src/web/static/css/typography-guide.html diff --git a/src/web/blueprints/alerts.py b/src/web/blueprints/alerts.py index 41083d8..991d4ba 100644 --- a/src/web/blueprints/alerts.py +++ b/src/web/blueprints/alerts.py @@ -30,14 +30,14 @@ def get_alerts(): # 构建查询 query = session.query(Alert) - # 应用过滤器 - if level_filter: - query = query.filter(Alert.level == level_filter) - if status_filter: - if status_filter == 'active': - query = query.filter(Alert.status == 'active') - elif status_filter == 'resolved': - query = query.filter(Alert.status == 'resolved') + # 应用过滤器 + if level_filter: + query = query.filter(Alert.level == level_filter) + if status_filter: + if status_filter == 'active': + query = query.filter(Alert.is_active == True) + elif status_filter == 'resolved': + query = query.filter(Alert.is_active == False) # 按创建时间倒序排列 query = query.order_by(Alert.created_at.desc()) @@ -54,11 +54,12 @@ def get_alerts(): alerts_data.append({ 'id': alert.id, 'rule_name': alert.rule_name, - 'message': alert.message, + 'alert_type': alert.alert_type, 'level': alert.level, - 'status': alert.status, - 'source': alert.source, - 'metadata': alert.metadata, + 'severity': alert.severity, + 'message': alert.message, + 'data': alert.data, + 'is_active': alert.is_active, 'created_at': alert.created_at.isoformat() if alert.created_at else None, 'resolved_at': alert.resolved_at.isoformat() if alert.resolved_at else None }) @@ -82,7 +83,7 @@ def create_alert(): """创建预警""" try: data = request.get_json() - alert = get_assistant().create_alert( + alert = service_manager.get_assistant().create_alert( alert_type=data.get('alert_type', 'manual'), title=data.get('title', '手动预警'), description=data.get('description', ''), @@ -96,7 +97,7 @@ def create_alert(): def get_alert_statistics(): """获取预警统计""" try: - stats = get_assistant().get_alert_statistics() + stats = service_manager.get_assistant().get_alert_statistics() return jsonify(stats) except Exception as e: return jsonify({"error": str(e)}), 500 @@ -105,7 +106,7 @@ def get_alert_statistics(): def resolve_alert(alert_id): """解决预警""" try: - success = get_assistant().resolve_alert(alert_id) + success = service_manager.get_assistant().resolve_alert(alert_id) if success: return jsonify({"success": True, "message": "预警已解决"}) else: diff --git a/src/web/blueprints/conversations.py b/src/web/blueprints/conversations.py index 5393a8b..c8603be 100644 --- a/src/web/blueprints/conversations.py +++ b/src/web/blueprints/conversations.py @@ -38,7 +38,20 @@ def get_conversations(): for conv in result.get('conversations', []): if 'user_id' in conv and conv['user_id'] is None: conv.pop('user_id', None) - return jsonify(result) + + # 扁平化分页信息,与前端期望格式一致 + if result.get('success'): + pagination = result.get('pagination', {}) + return jsonify({ + 'conversations': result.get('conversations', []), + 'page': pagination.get('current_page', page), + 'per_page': pagination.get('per_page', per_page), + 'total': pagination.get('total', 0), + 'total_pages': pagination.get('total_pages', 1), + 'stats': result.get('stats', {}) + }) + else: + return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 diff --git a/src/web/blueprints/workorders.py b/src/web/blueprints/workorders.py index 8f9395c..2e6dc36 100644 --- a/src/web/blueprints/workorders.py +++ b/src/web/blueprints/workorders.py @@ -45,12 +45,7 @@ from src.core.query_optimizer import query_optimizer workorders_bp = Blueprint('workorders', __name__, url_prefix='/api/workorders') -def get_assistant(): - """获取TSP助手实例(懒加载)""" - global _assistant - if '_assistant' not in globals(): - _assistant = TSPAssistant() - return _assistant +# 移除get_assistant函数,使用service_manager def _ensure_workorder_template_file() -> str: """返回已有的模板xlsx路径;不做动态生成,避免运行时依赖问题""" @@ -142,11 +137,13 @@ def get_workorders(): 'category': workorder.category, 'priority': workorder.priority, 'status': workorder.status, - 'user_id': workorder.user_id, - 'assigned_to': workorder.assigned_to, + 'assignee': workorder.assignee, + 'source': workorder.source, + 'module': workorder.module, + 'created_by': workorder.created_by, 'created_at': workorder.created_at.isoformat() if workorder.created_at else None, 'updated_at': workorder.updated_at.isoformat() if workorder.updated_at else None, - 'resolved_at': workorder.resolved_at.isoformat() if workorder.resolved_at else None + 'date_of_close': workorder.date_of_close.isoformat() if workorder.date_of_close else None }) # 计算分页信息 @@ -168,7 +165,7 @@ def create_workorder(): """创建工单""" try: data = request.get_json() - result = get_assistant().create_work_order( + result = service_manager.get_assistant().create_work_order( title=data['title'], description=data['description'], category=data['category'], @@ -304,7 +301,7 @@ def generate_workorder_ai_suggestion(workorder_id): # 调用知识库搜索与LLM生成 # 使用问题描述(title)而不是处理过程(description)作为主要查询依据 query = f"{w.title}" - kb_results = get_assistant().search_knowledge(query, top_k=3) + kb_results = service_manager.get_assistant().search_knowledge(query, top_k=3) kb_list = kb_results.get('results', []) if isinstance(kb_results, dict) else [] # 组装提示词 context = "\n".join([f"Q: {k.get('question','')}\nA: {k.get('answer','')}" for k in kb_list]) diff --git a/src/web/static/css/README.md b/src/web/static/css/README.md new file mode 100644 index 0000000..1b89a17 --- /dev/null +++ b/src/web/static/css/README.md @@ -0,0 +1,195 @@ +# TSP智能助手 - 设计系统 + +## 概述 + +本设计系统参考了成熟CRM系统(Salesforce、HubSpot、Zendesk)的设计标准,提供统一的字体、颜色、间距和组件规范。 + +## 文件结构 + +``` +src/web/static/css/ +├── design-system.css # 核心设计系统 +├── style.css # 主样式文件 +├── typography-guide.html # 字体和布局指南 +└── README.md # 本文档 +``` + +## 字体系统 + +### 字体族 +- **主字体**:`-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, 'Noto Sans', sans-serif` +- **等宽字体**:`'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace` + +### 字体大小 +基于16px基准的rem系统: + +| 类名 | 大小 | 用途 | +|------|------|------| +| `.text-xs` | 12px | 辅助信息 | +| `.text-sm` | 14px | 小文本 | +| `.text-base` | 16px | 正文 | +| `.text-lg` | 18px | 大文本 | +| `.text-xl` | 20px | 小标题 | +| `.text-2xl` | 24px | 中标题 | +| `.text-3xl` | 30px | 大标题 | +| `.text-4xl` | 36px | 主标题 | + +### 字重 +| 类名 | 字重 | 用途 | +|------|------|------| +| `.font-light` | 300 | 辅助信息 | +| `.font-normal` | 400 | 正文 | +| `.font-medium` | 500 | 强调 | +| `.font-semibold` | 600 | 小标题 | +| `.font-bold` | 700 | 标题 | + +## 颜色系统 + +### 主色调 +- **主色**:`#2563eb` (蓝色) +- **成功**:`#059669` (绿色) +- **警告**:`#d97706` (橙色) +- **错误**:`#dc2626` (红色) +- **信息**:`#0891b2` (青色) + +### 文本颜色 +- **主要文本**:`#0f172a` (深灰) +- **次要文本**:`#475569` (中灰) +- **第三级文本**:`#64748b` (浅灰) +- **禁用文本**:`#94a3b8` (极浅灰) + +## 间距系统 + +基于8px网格的间距系统: + +| 类名 | 大小 | 用途 | +|------|------|------| +| `.spacing-1` | 4px | 最小间距 | +| `.spacing-2` | 8px | 小间距 | +| `.spacing-3` | 12px | 中小间距 | +| `.spacing-4` | 16px | 中等间距 | +| `.spacing-6` | 24px | 大间距 | +| `.spacing-8` | 32px | 很大间距 | + +## 组件系统 + +### 按钮 +```html + + + + + + + + + +``` + +### 卡片 +```html +
+
+

卡片标题

+
+
+

卡片内容

+
+ +
+``` + +### 表格 +```html + + + + + + + + + + + + + + + +
列标题数据类型状态
数据文本正常
+``` + +### 分页 +```html + +``` + +## 使用规范 + +### 标题层级 +- **页面标题**:H1 (36px, bold) +- **区块标题**:H2 (30px, semibold) +- **卡片标题**:H3 (24px, semibold) +- **表单标题**:H4 (20px, medium) + +### 文本规范 +- **正文**:16px, normal +- **辅助信息**:14px, normal +- **标签**:12px, medium +- **按钮文字**:14px, medium + +### 间距规范 +- **卡片内边距**:24px +- **表单元素间距**:16px +- **按钮间距**:8px +- **文本行间距**:1.5 + +## 响应式设计 + +### 断点 +- **移动端**:< 768px +- **平板端**:768px - 1024px +- **桌面端**:> 1024px + +### 移动端适配 +- 按钮宽度100% +- 表格字体缩小 +- 卡片内边距减少 +- 分页按钮适配 + +## 浏览器支持 + +- Chrome 90+ +- Firefox 88+ +- Safari 14+ +- Edge 90+ + +## 更新日志 + +### v1.0.0 (2025-09-22) +- 初始版本 +- 基于成熟CRM系统设计标准 +- 统一的字体、颜色、间距系统 +- 完整的组件库 +- 响应式设计支持 + +## 贡献指南 + +1. 遵循现有的设计规范 +2. 使用CSS变量进行主题定制 +3. 保持组件的可复用性 +4. 确保响应式兼容性 +5. 更新文档和示例 + +## 联系方式 + +如有问题或建议,请联系开发团队。 diff --git a/src/web/static/css/design-system.css b/src/web/static/css/design-system.css new file mode 100644 index 0000000..280dd5d --- /dev/null +++ b/src/web/static/css/design-system.css @@ -0,0 +1,554 @@ +/* TSP智能助手 - 设计系统 */ +/* 参考成熟CRM系统(Salesforce、HubSpot、Zendesk)的设计标准 */ + +/* ===== 字体系统 ===== */ +:root { + /* 字体族 */ + --font-family-primary: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + --font-family-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; + + /* 字体大小 - 基于16px基准的rem系统 */ + --font-size-xs: 0.75rem; /* 12px - 辅助信息 */ + --font-size-sm: 0.875rem; /* 14px - 小文本 */ + --font-size-base: 1rem; /* 16px - 正文 */ + --font-size-lg: 1.125rem; /* 18px - 大文本 */ + --font-size-xl: 1.25rem; /* 20px - 小标题 */ + --font-size-2xl: 1.5rem; /* 24px - 中标题 */ + --font-size-3xl: 1.875rem; /* 30px - 大标题 */ + --font-size-4xl: 2.25rem; /* 36px - 主标题 */ + + /* 行高 */ + --line-height-tight: 1.25; + --line-height-normal: 1.5; + --line-height-relaxed: 1.75; + + /* 字重 */ + --font-weight-light: 300; + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + + /* 间距系统 - 基于8px网格 */ + --spacing-1: 0.25rem; /* 4px */ + --spacing-2: 0.5rem; /* 8px */ + --spacing-3: 0.75rem; /* 12px */ + --spacing-4: 1rem; /* 16px */ + --spacing-5: 1.25rem; /* 20px */ + --spacing-6: 1.5rem; /* 24px */ + --spacing-8: 2rem; /* 32px */ + --spacing-10: 2.5rem; /* 40px */ + --spacing-12: 3rem; /* 48px */ + --spacing-16: 4rem; /* 64px */ + + /* 颜色系统 */ + --color-primary: #2563eb; + --color-primary-dark: #1d4ed8; + --color-primary-light: #3b82f6; + --color-secondary: #64748b; + --color-success: #059669; + --color-warning: #d97706; + --color-error: #dc2626; + --color-info: #0891b2; + + /* 中性色 */ + --color-gray-50: #f8fafc; + --color-gray-100: #f1f5f9; + --color-gray-200: #e2e8f0; + --color-gray-300: #cbd5e1; + --color-gray-400: #94a3b8; + --color-gray-500: #64748b; + --color-gray-600: #475569; + --color-gray-700: #334155; + --color-gray-800: #1e293b; + --color-gray-900: #0f172a; + + /* 文本颜色 */ + --text-primary: var(--color-gray-900); + --text-secondary: var(--color-gray-600); + --text-tertiary: var(--color-gray-500); + --text-disabled: var(--color-gray-400); + --text-inverse: #ffffff; + + /* 背景颜色 */ + --bg-primary: #ffffff; + --bg-secondary: var(--color-gray-50); + --bg-tertiary: var(--color-gray-100); + --bg-elevated: #ffffff; + + /* 边框 */ + --border-color: var(--color-gray-200); + --border-color-hover: var(--color-gray-300); + --border-radius-sm: 0.25rem; + --border-radius: 0.375rem; + --border-radius-lg: 0.5rem; + --border-radius-xl: 0.75rem; + + /* 阴影 */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); +} + +/* ===== 基础字体设置 ===== */ +* { + box-sizing: border-box; +} + +body { + font-family: var(--font-family-primary); + font-size: var(--font-size-base); + line-height: var(--line-height-normal); + color: var(--text-primary); + background-color: var(--bg-secondary); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* ===== 标题系统 ===== */ +h1, .h1 { + font-size: var(--font-size-4xl); + font-weight: var(--font-weight-bold); + line-height: var(--line-height-tight); + margin: 0 0 var(--spacing-6) 0; + color: var(--text-primary); +} + +h2, .h2 { + font-size: var(--font-size-3xl); + font-weight: var(--font-weight-semibold); + line-height: var(--line-height-tight); + margin: 0 0 var(--spacing-5) 0; + color: var(--text-primary); +} + +h3, .h3 { + font-size: var(--font-size-2xl); + font-weight: var(--font-weight-semibold); + line-height: var(--line-height-normal); + margin: 0 0 var(--spacing-4) 0; + color: var(--text-primary); +} + +h4, .h4 { + font-size: var(--font-size-xl); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-normal); + margin: 0 0 var(--spacing-3) 0; + color: var(--text-primary); +} + +h5, .h5 { + font-size: var(--font-size-lg); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-normal); + margin: 0 0 var(--spacing-2) 0; + color: var(--text-primary); +} + +h6, .h6 { + font-size: var(--font-size-base); + font-weight: var(--font-weight-semibold); + line-height: var(--line-height-normal); + margin: 0 0 var(--spacing-2) 0; + color: var(--text-primary); +} + +/* ===== 文本样式 ===== */ +.text-xs { font-size: var(--font-size-xs); } +.text-sm { font-size: var(--font-size-sm); } +.text-base { font-size: var(--font-size-base); } +.text-lg { font-size: var(--font-size-lg); } +.text-xl { font-size: var(--font-size-xl); } +.text-2xl { font-size: var(--font-size-2xl); } +.text-3xl { font-size: var(--font-size-3xl); } +.text-4xl { font-size: var(--font-size-4xl); } + +.font-light { font-weight: var(--font-weight-light); } +.font-normal { font-weight: var(--font-weight-normal); } +.font-medium { font-weight: var(--font-weight-medium); } +.font-semibold { font-weight: var(--font-weight-semibold); } +.font-bold { font-weight: var(--font-weight-bold); } + +.text-primary { color: var(--text-primary); } +.text-secondary { color: var(--text-secondary); } +.text-tertiary { color: var(--text-tertiary); } +.text-disabled { color: var(--text-disabled); } +.text-inverse { color: var(--text-inverse); } + +/* ===== 布局系统 ===== */ +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--spacing-4); +} + +.container-fluid { + width: 100%; + padding: 0 var(--spacing-4); +} + +/* 网格系统 */ +.row { + display: flex; + flex-wrap: wrap; + margin: 0 calc(var(--spacing-2) * -1); +} + +.col { + flex: 1; + padding: 0 var(--spacing-2); +} + +.col-1 { flex: 0 0 8.333333%; } +.col-2 { flex: 0 0 16.666667%; } +.col-3 { flex: 0 0 25%; } +.col-4 { flex: 0 0 33.333333%; } +.col-6 { flex: 0 0 50%; } +.col-8 { flex: 0 0 66.666667%; } +.col-9 { flex: 0 0 75%; } +.col-12 { flex: 0 0 100%; } + +/* ===== 卡片系统 ===== */ +.card { + background: var(--bg-elevated); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-sm); + overflow: hidden; + transition: box-shadow 0.2s ease; +} + +.card:hover { + box-shadow: var(--shadow-md); +} + +.card-header { + padding: var(--spacing-4) var(--spacing-6); + border-bottom: 1px solid var(--border-color); + background: var(--bg-secondary); +} + +.card-title { + font-size: var(--font-size-lg); + font-weight: var(--font-weight-semibold); + margin: 0; + color: var(--text-primary); +} + +.card-body { + padding: var(--spacing-6); +} + +.card-footer { + padding: var(--spacing-4) var(--spacing-6); + border-top: 1px solid var(--border-color); + background: var(--bg-secondary); +} + +/* ===== 按钮系统 ===== */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: var(--spacing-2) var(--spacing-4); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-tight); + text-decoration: none; + border: 1px solid transparent; + border-radius: var(--border-radius); + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; + user-select: none; +} + +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.btn-sm { + padding: var(--spacing-1) var(--spacing-3); + font-size: var(--font-size-xs); +} + +.btn-lg { + padding: var(--spacing-3) var(--spacing-6); + font-size: var(--font-size-base); +} + +.btn-primary { + background-color: var(--color-primary); + border-color: var(--color-primary); + color: var(--text-inverse); +} + +.btn-primary:hover:not(:disabled) { + background-color: var(--color-primary-dark); + border-color: var(--color-primary-dark); +} + +.btn-secondary { + background-color: var(--color-secondary); + border-color: var(--color-secondary); + color: var(--text-inverse); +} + +.btn-outline-primary { + background-color: transparent; + border-color: var(--color-primary); + color: var(--color-primary); +} + +.btn-outline-primary:hover:not(:disabled) { + background-color: var(--color-primary); + color: var(--text-inverse); +} + +/* ===== 表单系统 ===== */ +.form-group { + margin-bottom: var(--spacing-4); +} + +.form-label { + display: block; + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--text-primary); + margin-bottom: var(--spacing-1); +} + +.form-control { + display: block; + width: 100%; + padding: var(--spacing-2) var(--spacing-3); + font-size: var(--font-size-sm); + line-height: var(--line-height-normal); + color: var(--text-primary); + background-color: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.form-control:focus { + outline: none; + border-color: var(--color-primary); + box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1); +} + +.form-select { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e"); + background-position: right var(--spacing-2) center; + background-repeat: no-repeat; + background-size: 1.5em 1.5em; + padding-right: var(--spacing-8); +} + +/* ===== 表格系统 ===== */ +.table { + width: 100%; + border-collapse: collapse; + font-size: var(--font-size-sm); +} + +.table th, +.table td { + padding: var(--spacing-3) var(--spacing-4); + text-align: left; + border-bottom: 1px solid var(--border-color); +} + +.table th { + font-weight: var(--font-weight-semibold); + color: var(--text-primary); + background-color: var(--bg-secondary); +} + +.table td { + color: var(--text-secondary); +} + +.table tbody tr:hover { + background-color: var(--bg-secondary); +} + +/* ===== 徽章系统 ===== */ +.badge { + display: inline-flex; + align-items: center; + padding: var(--spacing-1) var(--spacing-2); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-medium); + line-height: 1; + border-radius: var(--border-radius-sm); + text-transform: uppercase; + letter-spacing: 0.025em; +} + +.badge-primary { + background-color: var(--color-primary); + color: var(--text-inverse); +} + +.badge-success { + background-color: var(--color-success); + color: var(--text-inverse); +} + +.badge-warning { + background-color: var(--color-warning); + color: var(--text-inverse); +} + +.badge-error { + background-color: var(--color-error); + color: var(--text-inverse); +} + +.badge-secondary { + background-color: var(--color-secondary); + color: var(--text-inverse); +} + +/* ===== 分页系统 ===== */ +.pagination { + display: flex; + align-items: center; + gap: var(--spacing-1); + margin: 0; + padding: 0; + list-style: none; +} + +.pagination .page-item { + display: flex; +} + +.pagination .page-link { + display: flex; + align-items: center; + justify-content: center; + min-width: 2.5rem; + height: 2.5rem; + padding: var(--spacing-2); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--text-secondary); + text-decoration: none; + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + transition: all 0.2s ease; +} + +.pagination .page-link:hover { + background-color: var(--bg-secondary); + border-color: var(--border-color-hover); + color: var(--text-primary); +} + +.pagination .page-item.active .page-link { + background-color: var(--color-primary); + border-color: var(--color-primary); + color: var(--text-inverse); +} + +.pagination .page-item.disabled .page-link { + opacity: 0.5; + cursor: not-allowed; +} + +/* ===== 工具类 ===== */ +.d-flex { display: flex; } +.d-block { display: block; } +.d-inline { display: inline; } +.d-inline-block { display: inline-block; } +.d-none { display: none; } + +.justify-content-start { justify-content: flex-start; } +.justify-content-center { justify-content: center; } +.justify-content-end { justify-content: flex-end; } +.justify-content-between { justify-content: space-between; } + +.align-items-start { align-items: flex-start; } +.align-items-center { align-items: center; } +.align-items-end { align-items: flex-end; } + +.text-left { text-align: left; } +.text-center { text-align: center; } +.text-right { text-align: right; } + +.mb-0 { margin-bottom: 0; } +.mb-1 { margin-bottom: var(--spacing-1); } +.mb-2 { margin-bottom: var(--spacing-2); } +.mb-3 { margin-bottom: var(--spacing-3); } +.mb-4 { margin-bottom: var(--spacing-4); } +.mb-6 { margin-bottom: var(--spacing-6); } + +.mt-0 { margin-top: 0; } +.mt-1 { margin-top: var(--spacing-1); } +.mt-2 { margin-top: var(--spacing-2); } +.mt-3 { margin-top: var(--spacing-3); } +.mt-4 { margin-top: var(--spacing-4); } +.mt-6 { margin-top: var(--spacing-6); } + +.me-1 { margin-right: var(--spacing-1); } +.me-2 { margin-right: var(--spacing-2); } +.me-3 { margin-right: var(--spacing-3); } +.ms-1 { margin-left: var(--spacing-1); } +.ms-2 { margin-left: var(--spacing-2); } +.ms-3 { margin-left: var(--spacing-3); } + +.p-0 { padding: 0; } +.p-1 { padding: var(--spacing-1); } +.p-2 { padding: var(--spacing-2); } +.p-3 { padding: var(--spacing-3); } +.p-4 { padding: var(--spacing-4); } +.p-6 { padding: var(--spacing-6); } + +/* ===== 响应式设计 ===== */ +@media (max-width: 768px) { + .container { + padding: 0 var(--spacing-2); + } + + .card-body { + padding: var(--spacing-4); + } + + .btn { + width: 100%; + margin-bottom: var(--spacing-2); + } + + .table { + font-size: var(--font-size-xs); + } + + .table th, + .table td { + padding: var(--spacing-2); + } +} + +/* ===== 打印样式 ===== */ +@media print { + .btn, + .pagination, + .card-footer { + display: none; + } + + .card { + border: 1px solid #000; + box-shadow: none; + } + + .table th, + .table td { + border: 1px solid #000; + } +} diff --git a/src/web/static/css/style.css b/src/web/static/css/style.css index 65f3306..061407c 100644 --- a/src/web/static/css/style.css +++ b/src/web/static/css/style.css @@ -1,13 +1,20 @@ -/* TSP助手预警管理系统样式 */ +/* TSP智能助手 - 主样式文件 */ +/* 引入设计系统 */ +@import url('design-system.css'); +/* 覆盖和扩展设计系统 */ body { - background-color: #f8f9fa; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: var(--bg-secondary); + font-family: var(--font-family-primary); + font-size: var(--font-size-base); + line-height: var(--line-height-normal); + color: var(--text-primary); } .navbar-brand { - font-weight: bold; - font-size: 1.5rem; + font-size: var(--font-size-2xl); + font-weight: var(--font-weight-bold); + color: var(--text-primary); } /* 健康状态圆圈 */ @@ -22,10 +29,10 @@ body { display: flex; align-items: center; justify-content: center; - margin: 0 auto 10px; - font-size: 1.5rem; - font-weight: bold; - color: white; + margin: 0 auto var(--spacing-2); + font-size: var(--font-size-2xl); + font-weight: var(--font-weight-bold); + color: var(--text-inverse); position: relative; } @@ -113,15 +120,18 @@ body { /* 规则表格 */ .table th { - background-color: #f8f9fa; + background-color: var(--bg-secondary); border-top: none; - font-weight: 600; + font-size: var(--font-size-sm); + font-weight: var(--font-weight-semibold); + color: var(--text-primary); } .rule-status { - font-size: 0.8rem; - padding: 0.25rem 0.5rem; - border-radius: 0.25rem; + font-size: var(--font-size-xs); + font-weight: var(--font-weight-medium); + padding: var(--spacing-1) var(--spacing-2); + border-radius: var(--border-radius-sm); } .rule-status.enabled { diff --git a/src/web/static/css/typography-guide.html b/src/web/static/css/typography-guide.html new file mode 100644 index 0000000..ed108ee --- /dev/null +++ b/src/web/static/css/typography-guide.html @@ -0,0 +1,212 @@ + + + + + + TSP智能助手 - 字体和布局指南 + + + + + + +
+
+
+
+
+

TSP智能助手 - 字体和布局指南

+

参考成熟CRM系统的设计标准

+
+
+ + +
+

字体系统

+
+
+

标题层级

+

主标题 (H1) - 36px

+

大标题 (H2) - 30px

+

中标题 (H3) - 24px

+

小标题 (H4) - 20px

+
副标题 (H5) - 18px
+
小副标题 (H6) - 16px
+
+
+

文本大小

+

超大文本 (36px)

+

大文本 (30px)

+

中文本 (24px)

+

小文本 (20px)

+

大正文 (18px)

+

正文 (16px)

+

小正文 (14px)

+

辅助文本 (12px)

+
+
+
+ + +
+

字重系统

+
+
+

轻字重 (300) - 用于辅助信息

+

正常字重 (400) - 用于正文

+

中等字重 (500) - 用于强调

+

半粗字重 (600) - 用于小标题

+

粗字重 (700) - 用于标题

+
+
+

使用场景

+
    +
  • 标题:使用 font-bold (700)
  • +
  • 小标题:使用 font-semibold (600)
  • +
  • 正文:使用 font-normal (400)
  • +
  • 强调:使用 font-medium (500)
  • +
  • 辅助信息:使用 font-light (300)
  • +
+
+
+
+ + +
+

文本颜色

+
+
+

主要文本 - 用于重要内容

+

次要文本 - 用于一般内容

+

第三级文本 - 用于辅助信息

+

禁用文本 - 用于不可用状态

+
+
+

状态颜色

+

成功状态

+

警告状态

+

错误状态

+

信息状态

+
+
+
+ + +
+

间距系统

+
+
+

垂直间距

+
mb-1 (4px)
+
mb-2 (8px)
+
mb-3 (12px)
+
mb-4 (16px)
+
mb-6 (24px)
+
+
+

水平间距

+
+
me-1
+
me-2
+
me-3
+
+
+
+
+ + +
+

按钮系统

+
+
+

按钮大小

+ + + +
+
+

按钮样式

+ + + +
+
+
+ + +
+

表格系统

+ + + + + + + + + + + + + + + + + + + + + + + +
列标题数据类型状态操作
数据行1文本正常
数据行2数字警告
+
+ + +
+

分页系统

+ +
+ + +
+

使用规范

+
+
+

标题规范

+
    +
  • 页面标题使用 H1 (36px, bold)
  • +
  • 区块标题使用 H2 (30px, semibold)
  • +
  • 卡片标题使用 H3 (24px, semibold)
  • +
  • 表单标题使用 H4 (20px, medium)
  • +
+
+
+

文本规范

+
    +
  • 正文使用 16px, normal
  • +
  • 辅助信息使用 14px, normal
  • +
  • 标签使用 12px, medium
  • +
  • 按钮文字使用 14px, medium
  • +
+
+
+
+ +
+
+
+
+
+ + diff --git a/src/web/static/js/dashboard.js b/src/web/static/js/dashboard.js index 5f48995..01e9445 100644 --- a/src/web/static/js/dashboard.js +++ b/src/web/static/js/dashboard.js @@ -355,8 +355,18 @@ class TSPDashboard { const paginationContainer = document.getElementById(containerId); if (!paginationContainer) return; + // 调试信息 + console.log(`分页数据 (${containerId}):`, data); + const { page, total_pages, total, per_page } = data; + // 检查必要字段 + if (page === undefined || total_pages === undefined || total === undefined || per_page === undefined) { + console.error(`分页数据不完整 (${containerId}):`, { page, total_pages, total, per_page }); + paginationContainer.innerHTML = '
分页数据加载中...
'; + return; + } + if (total_pages <= 1) { paginationContainer.innerHTML = ''; return; @@ -365,10 +375,10 @@ class TSPDashboard { let paginationHtml = `
- 共 ${total} ${itemName},第 ${page} / ${total_pages} 页 + 共 ${total} ${itemName},第 ${page} / ${total_pages} 页
- - `; // 每页显示条数选择器 diff --git a/src/web/templates/dashboard.html b/src/web/templates/dashboard.html index 406e620..7221cb8 100644 --- a/src/web/templates/dashboard.html +++ b/src/web/templates/dashboard.html @@ -7,26 +7,9 @@ + +