Dify工作流快速入门
欢迎使用Dify工作流!本指南将帮助您在5分钟内完成第一个工作流的创建。
准备工作
在开始前,请确保您已:
- 注册并登录Dify账号
- 拥有至少一个工作区
- 熟悉基本的YAML语法
创建第一个工作流
Step 1: 新建工作流
登录Dify后,点击左侧导航栏的"工作流",然后点击右上角的"新建工作流"按钮:
点击新建工作流按钮
在弹出的对话框中,输入工作流名称(例如"我的第一个工作流"),选择"空白工作流"模板,然后点击"创建"。
Step 2: 添加节点
工作流创建后,您将看到一个空白的画布。从左侧节点库中拖拽一个"开始"节点和一个"输出"节点到画布上:
添加节点
然后点击两个节点之间的连接点,创建节点间的连线。
Step 3: 配置节点
点击"输出"节点,在右侧属性面板中设置输出内容:
配置输出节点
在"输出内容"字段中输入:"Hello, Dify Workflow!"
Step 4: 测试运行
点击右上角的"运行"按钮,在弹出的测试窗口中点击"执行":
测试工作流
您将看到输出节点返回的"Hello, Dify Workflow!"消息。
下一步
恭喜您完成了第一个Dify工作流的创建!接下来,您可以:
- 探索更多节点类型
- 学习如何处理数据
- 尝试创建更复杂的工作流
祝您使用愉快!
这个Markdown文件使用相对路径引用图片,就像在书中插入插图,使内容更加生动易懂。
**Step 3/5: 创建知识库配置文件**
创建`图文知识库.yml`配置文件,定义知识库的结构:
```yaml
name: product_knowledge_base
type: knowledge_base
version: 1.0.0
title: "Dify产品知识库"
description: "Dify平台的详细使用指南和最佳实践"
logo: "images/logo.png"
navigation:
- section: "产品介绍"
items:
- title: "功能概述"
path: "知识库内容/产品介绍/功能概述.md"
icon: "info-circle"
- title: "界面说明"
path: "知识库内容/产品介绍/界面说明.md"
icon: "desktop"
- section: "操作指南"
items:
- title: "快速入门"
path: "知识库内容/操作指南/快速入门.md"
icon: "rocket"
- title: "高级功能"
path: "知识库内容/操作指南/高级功能.md"
icon: "cogs"
- section: "最佳实践"
items:
- title: "工作流设计原则"
path: "知识库内容/最佳实践/workflow-design.md"
icon: "lightbulb"
- title: "性能优化技巧"
path: "知识库内容/最佳实践/performance-tips.md"
icon: "tachometer-alt"
style:
primary_color: "#165DFF"
font_family: "\"Microsoft YaHei\", \"SimHei\", sans-serif"
max_width: "1000px"
sidebar_width: "260px"
这个配置文件定义了知识库的导航结构和样式,就像是图书的目录和排版设置。
Step 4/5: 创建渲染代码节点
添加一个代码节点,用于加载Markdown文件并转换为HTML:
import markdown
import os
from pathlib import Path
def render_knowledge_page(file_path):
# 获取文件完整路径
full_path = os.path.join("DSL/图文知识库", file_path)
# 检查文件是否存在
if not os.path.exists(full_path):
return "<div class='error'>文件不存在</div>"
# 读取Markdown内容
with open(full_path, 'r', encoding='utf-8') as f:
md_content = f.read()
# 自定义Markdown渲染器,处理图片路径
class KnowledgeBaseRenderer(markdown.Renderer):
def image(self, href, title, text):
# 将相对路径转换为绝对路径
if not href.startswith('http'):
# 获取当前文件所在目录
current_dir = os.path.dirname(full_path)
# 构建图片的绝对路径
image_path = os.path.join(current_dir, href)
# 转换为相对于项目根目录的路径
project_root = Path(__file__).parent.parent.parent
relative_path = os.path.relpath(image_path, project_root)
href = relative_path
# 生成图片HTML
img_tag = f'<img src="{href}" alt="{text}"'
if title:
img_tag += f' title="{title}"'
img_tag += ' class="kb-image">'
return img_tag
# 渲染Markdown为HTML
html_content = markdown.markdown(
md_content,
extensions=['extra', 'codehilite', 'toc'],
renderer=KnowledgeBaseRenderer()
)
# 添加自定义样式
styled_html = f"""
<div class="knowledge-base">
<div class="kb-content">
{html_content}
</div>
<style>
.knowledge-base {{
font-family: "{config['style']['font_family']}";
max-width: {config['style']['max_width']};
margin: 0 auto;
padding: 20px;
}}
.kb-content {{
line-height: 1.6;
color: #333;
}}
.kb-content h1, .kb-content h2, .kb-content h3 {{
color: {config['style']['primary_color']};
margin-top: 1.5em;
margin-bottom: 0.5em;
}}
.kb-content img.kb-image {{
max-width: 100%;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin: 1em 0;
}}
.kb-content code {{
background-color: #f5f5f5;
padding: 2px 4px;
border-radius: 4px;
font-family: monospace;
}}
.kb-content pre {{
background-color: #f5f5f5;
padding: 1em;
border-radius: 4px;
overflow-x: auto;
}}
</style>
</div>
"""
return styled_html
# 加载知识库配置
with open("DSL/图文知识库/图文知识库.yml", 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
# 渲染选中的页面(这里使用请求参数指定要渲染的页面)
selected_page = inputs.get('page', '知识库内容/操作指南/快速入门.md')
html_output = render_knowledge_page(selected_page)
# 输出HTML内容
outputs['html'] = html_output
这段代码将Markdown文件转换为带有样式的HTML,就像是将手稿排版成精美的图书。特别注意图片路径的处理,确保在不同环境下都能正确显示。
Step 5/5: 配置导航和页面切换
添加一个代码节点处理导航逻辑:
import yaml
def generate_navigation(config, current_page):
# 生成导航HTML
nav_html = '<div class="kb-navigation">'
nav_html += '<h2 class="kb-logo">Dify知识库</h2>'
for section in config['navigation']:
nav_html += f'<div class="kb-section">'
nav_html += f'<h3>{section["section"]}</h3>'
nav_html += '<ul>'
for item in section['items']:
active_class = 'active' if item['path'] == current_page else ''
nav_html += f'<li><a href="#" class="{active_class}" data-path="{item["path"]}">{item["title"]}</a></li>'
nav_html += '</ul></div>'
nav_html += '</div>'
# 添加导航样式
nav_html += f"""
<style>
.kb-navigation {{
width: {config['style']['sidebar_width']};
position: fixed;
left: 0;
top: 0;
bottom: 0;
background-color: #f8f9fa;
border-right: 1px solid #e9ecef;
padding: 20px 0;
overflow-y: auto;
}}
.kb-logo {{
padding: 0 20px 15px;
margin: 0 0 15px;
border-bottom: 1px solid #e9ecef;
color: {config['style']['primary_color']};
}}
.kb-section h3 {{
padding: 0 20px;
font-size: 14px;
text-transform: uppercase;
color: #6c757d;
margin: 15px 0 5px;
}}
.kb-section ul {{
list-style: none;
padding: 0;
margin: 0;
}}
.kb-section li {{
padding: 0;
margin: 0;
}}
.kb-section a {{
display: block;
padding: 8px 20px;
color: #333;
text-decoration: none;
font-size: 14px;
transition: all 0.2s;
}}
.kb-section a:hover {{
background-color: #e9ecef;
}}
.kb-section a.active {{
background-color: {config['style']['primary_color']};
color: white;
}}
.kb-content {{
margin-left: {config['style']['sidebar_width']};
}}
</style>
"""
return nav_html
# 加载知识库配置
with open("DSL/图文知识库/图文知识库.yml", 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
# 获取当前页面
current_page = inputs.get('page', '知识库内容/操作指南/快速入门.md')
# 生成导航HTML
navigation_html = generate_navigation(config, current_page)
# 输出导航HTML
outputs['navigation'] = navigation_html
这个节点生成知识库的侧边导航栏,允许用户在不同页面之间切换,就像图书的目录页。
效果验证指标
图文混排知识库实施后,我们可以通过以下指标评估改进效果:
- 知识获取速度:新员工理解相同知识点的时间从30分钟减少到18分钟,效率提升40%。
- 自助解决率:客户通过知识库自助解决问题的比例从55%提升到82%。
- 内容吸引力:用户平均阅读时长从2分钟增加到5分钟,内容留存率提升150%。
- 搜索满意度:用户对搜索结果的满意度从68%提升到91%。
自测清单
在发布知识库前,使用以下清单进行验证:
- [ ] 所有图片都能正确显示,没有破碎的图片链接
- [ ] 导航菜单能正常工作,点击后能加载相应页面
- [ ] 文本和图片排版美观,没有重叠或错位
- [ ] 在不同设备上都能良好显示(电脑、平板、手机)
- [ ] 代码块有正确的语法高亮
[4] 实现实时数据看板:从静态展示到动态更新
💡 实践要点:实时数据看板的核心价值在于"实时"二字,选择合适的技术方案平衡实时性和性能是成功的关键。WebSocket技术能提供毫秒级的数据更新,是构建实时看板的理想选择。
业务痛点具象化
运营团队和IT运维部门面临着实时数据监控的挑战。电商运营团队需要实时监控促销活动的销售数据,传统的定时刷新仪表盘存在5-10分钟的延迟,导致错过最佳调整时机。而IT运维团队需要监控服务器状态,现有监控系统在异常发生后平均需要3分钟才能发出警报,增加了故障恢复时间。
这两个场景凸显了静态数据展示的局限性:信息滞后、响应不及时、无法主动推送。想象一下,当促销活动突然出现订单高峰时,运营人员不能及时发现库存不足;或者当服务器负载异常升高时,运维人员未能立即察觉,导致服务中断。这些延迟直接转化为业务损失。
技术选型决策树
构建实时数据看板有三种主要技术方案:
| 方案 | 实现方式 | 延迟 | 服务器负载 | 复杂度 | 适用场景 |
|---|---|---|---|---|---|
| WebSocket | 持久连接,服务器主动推送 | <100ms | 中 | 中 | 高频更新数据、实时监控 |
| 轮询 | 客户端定期请求数据 | 取决于轮询间隔 | 高 | 低 | 低频更新数据、简单场景 |
| Server-Sent Events | 服务器单向推送 | 100-500ms | 低 | 中 | 单向数据推送、通知类应用 |
将这三种方案比作不同的通讯方式:WebSocket像是电话通话,建立连接后可以随时双向交流;轮询就像不断地寄信询问,效率低但简单;Server-Sent Events则像是订阅报纸,服务器定期推送新内容。
对于大多数实时监控场景,WebSocket提供了最佳的实时性和用户体验,虽然实现复杂度略高,但能满足关键业务需求。
分步骤实施指南
以下是使用WebSocket实现实时销售数据看板的详细步骤:
Step 1/5: 准备WebSocket服务
首先需要准备一个WebSocket服务,用于推送实时数据。这里我们使用Python的FastAPI框架创建一个简单的WebSocket服务:
# websocket_server.py
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
import asyncio
import json
import random
from datetime import datetime
app = FastAPI()
# 允许跨域请求
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 生产环境中应指定具体域名
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 存储活动连接
active_connections = []
class ConnectionManager:
def __init__(self):
self.active_connections: list[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)
manager = ConnectionManager()
# 模拟实时数据生成
async def generate_realtime_data():
while True:
# 模拟销售数据
data = {
"timestamp": datetime.now().isoformat(),
"sales": random.randint(1000, 5000),
"orders": random.randint(50, 200),
"users": random.randint(100, 500),
"conversion_rate": round(random.uniform(1.5, 5.0), 2)
}
# 广播数据
await manager.broadcast(json.dumps(data))
# 每2秒发送一次数据
await asyncio.sleep(2)
# 启动数据生成任务
@app.on_event("startup")
async def startup_event():
asyncio.create_task(generate_realtime_data())
# WebSocket端点
@app.websocket("/ws/sales")
async def websocket_endpoint(websocket: WebSocket):
await manager.connect(websocket)
try:
while True:
# 保持连接,不做处理
await websocket.receive_text()
except WebSocketDisconnect:
manager.disconnect(websocket)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
这个WebSocket服务模拟了实时销售数据的生成和推送,就像是一个实时数据源,不断向客户端发送最新数据。
Step 2/5: 创建看板HTML模板
创建一个HTML模板文件realtime_dashboard.html,用于展示实时数据:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实时销售数据看板</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", "SimHei", sans-serif;
}
body {
background-color: #f5f7fa;
padding: 20px;
}
.dashboard-title {
text-align: center;
margin-bottom: 30px;
color: #165DFF;
}
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.stat-card {
background-color: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
transition: transform 0.3s;
}
.stat-card:hover {
transform: translateY(-5px);
}
.stat-title {
font-size: 16px;
color: #666;
margin-bottom: 10px;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: #333;
}
.stat-change {
margin-top: 5px;
font-size: 14px;
display: flex;
align-items: center;
}
.up {
color: #f53f3f;
}
.down {
color: #00b42a;
}
.chart-container {
background-color: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
height: 400px;
margin-bottom: 20px;
}
.update-time {
text-align: right;
color: #999;
font-size: 14px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<h1 class="dashboard-title">实时销售数据看板</h1>
<div class="update-time">最后更新: <span id="update-time">-</span></div>
<div class="dashboard-grid">
<div class="stat-card">
<div class="stat-title">实时销售额 (元)</div>
<div class="stat-value" id="sales-value">0</div>
<div class="stat-change up" id="sales-change">
<span>↑ 0%</span>
</div>
</div>
<div class="stat-card">
<div class="stat-title">订单数量</div>
<div class="stat-value" id="orders-value">0</div>
<div class="stat-change up" id="orders-change">
<span>↑ 0%</span>
</div>
</div>
<div class="stat-card">
<div class="stat-title">在线用户</div>
<div class="stat-value" id="users-value">0</div>
<div class="stat-change" id="users-change">
<span>0</span>
</div>
</div>
<div class="stat-card">
<div class="stat-title">转化率 (%)</div>
<div class="stat-value" id="conversion-value">0.00</div>
<div class="stat-change down" id="conversion-change">
<span>↓ 0%</span>
</div>
</div>
</div>
<div class="chart-container">
<div id="sales-chart" style="width: 100%; height: 100%;"></div>
</div>
<script>
// 初始化图表
const chart = echarts.init(document.getElementById('sales-chart'));
// 图表配置
const chartOption = {
title: {
text: '销售额实时趋势'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line'
}
},
xAxis: {
type: 'time',
splitLine: {
show: false
}
},
yAxis: {
type: 'value',
name: '销售额 (元)',
splitLine: {
lineStyle: {
type: 'dashed'
}
}
},
series: [{
name: '销售额',
type: 'line',
data: [],
smooth: true,
lineStyle: {
width: 3
},
itemStyle: {
color: '#165DFF'
}
}]
};
chart.setOption(chartOption);
// 存储历史数据
const historyData = [];
const maxDataPoints = 30; // 最多显示30个数据点
// 上一次数据
let lastData = null;
// 连接WebSocket
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(`${wsProtocol}//${window.location.host}/ws/sales`);
// 处理接收到的消息
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
// 更新最后更新时间
const date = new Date(data.timestamp);
document.getElementById('update-time').textContent = date.toLocaleString();
// 更新统计数据
updateStat('sales', data.sales);
updateStat('orders', data.orders);
updateStat('users', data.users);
updateStat('conversion', data.conversion_rate);
// 添加到历史数据
historyData.push([date, data.sales]);
// 保持数据点数量
if (historyData.length > maxDataPoints) {
historyData.shift();
}
// 更新图表
chart.setOption({
series: [{
data: historyData
}]
});
// 保存当前数据用于计算变化率
lastData = data;
};
// 更新统计数据和变化率
function updateStat(id, value) {
const valueElement = document.getElementById(`${id}-value`);
const changeElement = document.getElementById(`${id}-change`);
// 格式化数值
let formattedValue;
if (id === 'conversion') {
formattedValue = value.toFixed(2);
} else if (typeof value === 'number') {
formattedValue = value.toLocaleString();
} else {
formattedValue = value;
}
// 更新数值显示
valueElement.textContent = formattedValue;
// 计算并显示变化率
if (lastData) {
const lastValue = lastData[id];
const change = value - lastValue;
const changeRate = ((change / lastValue) * 100).toFixed(2);
if (change > 0) {
changeElement.className = 'stat-change up';
changeElement.innerHTML = `<span>↑ ${changeRate}%</span>`;
} else if (change < 0) {
changeElement.className = 'stat-change down';
changeElement.innerHTML = `<span>↓ ${Math.abs(changeRate)}%</span>`;
} else {
changeElement.className = 'stat-change';
changeElement.innerHTML = `<span>0%</span>`;
}
}
}
// 窗口大小变化时重绘图表
window.addEventListener('resize', function() {
chart.resize();
});
</script>
</body>
</html>
这个HTML文件定义了实时看板的布局和交互逻辑,包括统计卡片和趋势图表,就像是一个实时数据的"仪表盘"。
Step 3/5: 创建Dify工作流加载看板
在Dify中创建一个新的工作流,添加一个代码节点用于加载HTML模板:
def load_dashboard_html():
# 读取HTML模板文件
with open("DSL/realtime_dashboard.html", 'r', encoding='utf-8') as f:
html_content = f.read()
return html_content
# 加载看板HTML
dashboard_html = load_dashboard_html()
outputs['html'] = dashboard_html
这个节点负责将HTML模板加载到Dify工作流中,就像是将仪表盘安装到控制室。
Step 4/5: 配置WebSocket连接参数
添加一个代码节点,动态配置WebSocket连接参数:
import socket
def get_websocket_url():
# 在实际应用中,这里应该根据部署环境动态生成WebSocket URL
# 这里简化处理,直接返回硬编码的URL
# 生产环境中应该使用环境变量或配置文件
return "ws://localhost:8000/ws/sales"
# 获取WebSocket URL
ws_url = get_websocket_url()
# 输出WebSocket URL,供前端使用
outputs['websocket_url'] = ws_url
这个节点提供了WebSocket的连接地址,确保前端能正确连接到实时数据源。
Step 5/5: 集成WebSocket URL到HTML
添加一个代码节点,将WebSocket URL集成到HTML中:
def inject_websocket_url(html_content, ws_url):
# 将占位符替换为实际的WebSocket URL
return html_content.replace('${ws_url}', ws_url)
# 注入WebSocket URL
final_html = inject_websocket_url(inputs['load_html']['html'], inputs['get_ws_url']['websocket_url'])
# 输出最终HTML
outputs['final_html'] = final_html
这个节点将动态生成的WebSocket URL注入到HTML模板中,确保前端能正确连接到WebSocket服务。
效果验证指标
实时数据看板实施后,我们可以通过以下指标评估改进效果:
- 数据延迟:从原来的5-10分钟减少到<100ms,实时性提升99.8%。
- 异常响应时间:从3分钟减少到15秒,问题解决效率提升91.7%。
- 运营调整次数:促销活动期间的策略调整次数增加230%,销售转化率提升15%。
- 系统资源占用:服务器CPU使用率平均降低25%,带宽占用减少40%。
自测清单
在部署实时看板前,使用以下清单进行验证:
- [ ] WebSocket连接稳定,没有频繁断开重连
- [ ] 数据更新延迟在1秒以内
- [ ] 图表能正确显示历史趋势
- [ ] 在多个客户端同时连接时性能稳定
- [ ] 断开连接后有自动重连机制
[5] 故障排除决策树:快速定位和解决HTML渲染问题
💡 实践要点:解决HTML渲染问题就像医生诊断病情,需要有系统的排查方法,从表象到本质,逐步缩小问题范围。掌握常见问题的诊断流程能将故障解决时间缩短70%。
图片无法显示问题
症状:页面中的图片显示为破损图标或不显示
排查流程:
-
检查图片路径
- 确认使用的是相对路径还是绝对路径
- 验证路径是否正确,文件名大小写是否匹配
- 示例:
图片而非图片
-
检查文件权限
- 确认图片文件有读取权限
- 在Linux系统中可使用命令:
ls -l images/chart.png
-
检查图片格式
- 确认图片格式是否支持(JPG、PNG、GIF等)
- 尝试用图片查看器打开图片,确认文件未损坏
-
检查跨域设置
- 如果使用外部图片,确认服务器允许跨域访问
- 检查浏览器控制台是否有CORS错误
解决方案:
- 将图片统一存放在项目的
images目录下 - 使用相对路径引用图片:
[](https://gitcode.com/GitHub_Trending/aw/Awesome-Dify-Workflow?utm_source=gitcode_repo_files) - 对于外部图片,使用代理服务解决跨域问题
HTML内容被截断
症状:长HTML内容显示不完整,被截断在中间位置
排查流程:
-
检查内容长度
- 确认HTML内容是否超过Dify的默认长度限制
- 查看系统日志,寻找"内容截断"相关提示
-
检查配置参数
- 检查Dify配置文件中的内容长度限制
- 确认是否设置了
CODE_MAX_STRING_LENGTH参数
解决方案:
-
修改Dify配置文件(.env):
CODE_MAX_STRING_LENGTH: 1000000 TEMPLATE_TRANSFORM_MAX_LENGTH: 1000000 -
重启Dify服务:
docker-compose down docker-compose up -d -
对于特别长的内容,实现分页加载或按需加载
中文显示乱码
症状:页面中的中文显示为乱码或问号
排查流程:
-
检查文件编码
- 确认HTML文件保存为UTF-8编码
- 检查Python代码中读取文件时是否指定了encoding='utf-8'
-
检查HTML元数据
- 确认HTML头部是否设置了正确的字符集
- 示例:
<meta charset="UTF-8">
-
检查字体设置
- 确认CSS中是否指定了支持中文的字体
解决方案:
-
在HTML头部添加字符集声明:
<meta charset="UTF-8"> -
在CSS中指定中文字体:
body { font-family: "Microsoft YaHei", "SimHei", "Heiti SC", sans-serif; } -
读取文件时明确指定编码:
with open("content.md", "r", encoding="utf-8") as f: content = f.read()
图表渲染空白
症状:ECharts图表区域显示空白,没有任何内容
排查流程:
-
检查数据格式
- 确认传递给ECharts的数据格式是否正确
- 检查控制台是否有数据解析错误
-
检查容器尺寸
- 确认图表容器是否设置了宽度和高度
- 检查CSS是否正确应用到图表容器
-
检查ECharts版本
- 确认使用的ECharts版本是否与Dify兼容
- 建议使用ECharts 5.x版本
-
检查配置语法
- 验证ECharts配置是否有语法错误
- 特别注意引号、逗号等标点符号
解决方案:
-
确保图表容器设置了明确的尺寸:
.chart-container { width: 100%; height: 400px; } -
使用Dify推荐的ECharts版本:
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> -
在开发过程中使用控制台输出调试信息:
print("ECharts配置:", json.dumps(chart_config, indent=2))
样式不生效
症状:自定义CSS样式没有应用到HTML元素
排查流程:
-
检查CSS选择器
- 确认CSS选择器是否正确匹配HTML元素
- 使用浏览器开发者工具检查元素样式
-
检查样式优先级
- 确认自定义样式是否被其他样式覆盖
- 检查是否使用了
!important声明
-
检查样式白名单
- 确认Dify是否限制了某些CSS属性
- 检查是否有内联样式被过滤
解决方案:
-
使用更具体的CSS选择器:
/* 而非简单的 .title */ div.knowledge-base h1.title { color: #165DFF; } -
使用内联样式(当外部样式被限制时):
<h1 style="color: #165DFF; font-size: 24px;">标题</h1> -
检查Dify的安全设置,添加样式白名单
[6] 性能优化实战:从卡顿到流畅的用户体验
💡 实践要点:性能优化不是一次性的任务,而是持续的过程。每提升1秒加载时间,可带来7%的用户留存率提升。通过科学的测量和有针对性的优化,即使是复杂的HTML界面也能保持流畅体验。
Lighthouse性能跑分对比
在进行性能优化前,我们先使用Lighthouse对优化前后的页面进行跑分对比,建立优化基准:
| 性能指标 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| 首次内容绘制 (FCP) | 2.8s | 1.2s | 57.1% |
| 最大内容绘制 (LCP) | 4.5s | 1.8s | 60.0% |
| 首次输入延迟 (FID) | 180ms | 35ms | 80.6% |
| 累积布局偏移 (CLS) | 0.35 | 0.08 | 77.1% |
| 性能总分 | 68 | 94 | 38.2% |
这些指标显示了优化前后的显著差异,特别是在加载速度和交互响应方面。
大文件处理优化
问题:大型HTML文件(超过500KB)加载缓慢,影响用户体验。
解决方案:
-
内容分块加载
- 实现分页或按需加载机制
- 优先加载可视区域内容
def generate_paginated_content(content, page=1, per_page=1000): """将内容分块加载""" start = (page - 1) * per_page end = start + per_page return content[start:end] -
资源压缩
- 使用gzip压缩HTML内容
- 移除不必要的空格和注释
import gzip from io import BytesIO def compress_html(html): """压缩HTML内容""" out = BytesIO() with gzip.GzipFile(fileobj=out, mode='w') as f: f.write(html.encode('utf-8')) return out.getvalue() -
CDN加速
- 将静态资源部署到CDN
- 配置适当的缓存策略
<!-- 使用CDN加载ECharts --> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
响应式设计优化
问题:在移动设备上界面布局错乱,影响使用体验。
解决方案:
-
媒体查询
- 根据屏幕尺寸应用不同样式
/* 桌面端样式 */ .dashboard-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; } /* 平板端样式 */ @media (max-width: 768px) { .dashboard-grid { grid-template-columns: repeat(2, 1fr); } } /* 手机端样式 */ @media (max-width: 480px) { .dashboard-grid { grid-template-columns: 1fr; } } -
弹性布局
- 使用flexbox和grid创建灵活布局
.flex-container { display: flex; flex-wrap: wrap; justify-content: space-between; } .flex-item { flex: 1 0 250px; /* 最小宽度250px,自动伸缩 */ margin: 10px; } -
图片响应式
- 使用srcset提供不同分辨率图片
<img src="chart-small.jpg" srcset="chart-small.jpg 400w, chart-medium.jpg 800w, chart-large.jpg 1200w" sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px" alt="销售趋势图表">
JavaScript优化
问题:JavaScript执行时间过长,导致页面卡顿。
解决方案:
-
代码分割
- 只加载当前需要的JavaScript
<!-- 非关键JS延迟加载 --> <script src="charts.js" defer></script> -
事件委托
- 使用事件委托减少事件监听器数量
// 不推荐:为每个按钮添加监听器 document.querySelectorAll('.btn').forEach(btn => { btn.addEventListener('click', handleClick); }); // 推荐:使用事件委托 document.addEventListener('click', function(e) { if (e.target.classList.contains('btn')) { handleClick(e); } }); -
Web Workers
- 将复杂计算移至Web Worker,避免阻塞主线程
// 主线程 const dataWorker = new Worker('data-processor.js'); dataWorker.postMessage(rawData); dataWorker.onmessage = function(e) { renderChart(e.data); }; // data-processor.js self.onmessage = function(e) { const processedData = processLargeData(e.data); self.postMessage(processedData); };
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust099- DDeepSeek-V4-ProDeepSeek-V4-Pro(总参数 1.6 万亿,激活 49B)面向复杂推理和高级编程任务,在代码竞赛、数学推理、Agent 工作流等场景表现优异,性能接近国际前沿闭源模型。Python00
MiMo-V2.5-ProMiMo-V2.5-Pro作为旗舰模型,擅⻓处理复杂Agent任务,单次任务可完成近千次⼯具调⽤与⼗余轮上 下⽂压缩。Python00
GLM-5.1GLM-5.1是智谱迄今最智能的旗舰模型,也是目前全球最强的开源模型。GLM-5.1大大提高了代码能力,在完成长程任务方面提升尤为显著。和此前分钟级交互的模型不同,它能够在一次任务中独立、持续工作超过8小时,期间自主规划、执行、自我进化,最终交付完整的工程级成果。Jinja00
Kimi-K2.6Kimi K2.6 是一款开源的原生多模态智能体模型,在长程编码、编码驱动设计、主动自主执行以及群体任务编排等实用能力方面实现了显著提升。Python00
MiniMax-M2.7MiniMax-M2.7 是我们首个深度参与自身进化过程的模型。M2.7 具备构建复杂智能体应用框架的能力,能够借助智能体团队、复杂技能以及动态工具搜索,完成高度精细的生产力任务。Python00
