图片:

依赖:
streamlit
pandas
matplotlib
代码blog_app.py:
python
import streamlit as st
import sqlite3
import pandas as pd
from datetime import datetime
import os
# 确保中文正常显示
# 数据库初始化函数
def init_db():
conn = sqlite3.connect('blog.db')
c = conn.cursor()
# 创建博客文章表
c.execute('''
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
category TEXT,
tags TEXT
)
''')
conn.commit()
conn.close()
# 添加新博客文章
def add_post(title, content, category='未分类', tags=''):
conn = sqlite3.connect('blog.db')
c = conn.cursor()
c.execute('''
INSERT INTO posts (title, content, category, tags)
VALUES (?, ?, ?, ?)
''', (title, content, category, tags))
conn.commit()
conn.close()
# 获取所有博客文章
def get_all_posts():
conn = sqlite3.connect('blog.db')
# 使用pandas读取数据,确保中文正常显示
posts = pd.read_sql_query('SELECT * FROM posts ORDER BY created_at DESC', conn)
conn.close()
return posts
# 获取单篇博客文章
def get_post(post_id):
conn = sqlite3.connect('blog.db')
c = conn.cursor()
c.execute('SELECT * FROM posts WHERE id = ?', (post_id,))
post = c.fetchone()
conn.close()
return post
# 更新博客文章
def update_post(post_id, title, content, category, tags):
conn = sqlite3.connect('blog.db')
c = conn.cursor()
c.execute('''
UPDATE posts SET title = ?, content = ?, category = ?, tags = ?
WHERE id = ?
''', (title, content, category, tags, post_id))
conn.commit()
conn.close()
# 删除博客文章
def delete_post(post_id):
conn = sqlite3.connect('blog.db')
c = conn.cursor()
c.execute('DELETE FROM posts WHERE id = ?', (post_id,))
conn.commit()
conn.close()
# 获取分类统计
def get_category_stats():
conn = sqlite3.connect('blog.db')
stats = pd.read_sql_query(
'SELECT category, COUNT(*) as count FROM posts GROUP BY category',
conn
)
conn.close()
return stats
# 主应用函数
def main():
# 初始化数据库
init_db()
st.title('我的博客')
# 创建侧边栏导航
menu = ['首页', '写文章', '管理文章', '统计']
choice = st.sidebar.selectbox('选择功能', menu)
if choice == '首页':
st.subheader('博客文章')
# 显示分类筛选
categories = pd.read_sql_query('SELECT DISTINCT category FROM posts', sqlite3.connect('blog.db'))['category'].tolist()
categories.insert(0, '全部')
selected_category = st.selectbox('按分类筛选', categories)
# 获取并显示文章列表
if selected_category == '全部':
posts = get_all_posts()
else:
conn = sqlite3.connect('blog.db')
posts = pd.read_sql_query(
'SELECT * FROM posts WHERE category = ? ORDER BY created_at DESC',
conn,
params=(selected_category,)
)
conn.close()
if not posts.empty:
for _, post in posts.iterrows():
# 使用expander折叠显示文章内容
with st.expander(f"{post['title']} ({post['created_at']})"):
st.write(f"**分类:** {post['category']}")
if post['tags']:
st.write(f"**标签:** {post['tags']}")
st.write(post['content'])
else:
st.info('暂无博客文章')
elif choice == '写文章':
st.subheader('写新文章')
with st.form(key='post_form'):
title = st.text_input('标题')
category = st.text_input('分类', '未分类')
tags = st.text_input('标签(用逗号分隔)')
content = st.text_area('内容', height=300)
submit_button = st.form_submit_button(label='发布文章')
if submit_button:
if title and content:
add_post(title, content, category, tags)
st.success('文章发布成功!')
else:
st.error('请填写标题和内容')
elif choice == '管理文章':
st.subheader('管理文章')
# 显示所有文章进行管理
posts = get_all_posts()
if not posts.empty:
# 创建一个下拉框让用户选择要编辑/删除的文章
post_options = {f"{post['title']} ({post['id']})": post['id'] for _, post in posts.iterrows()}
selected_post_title = st.selectbox('选择文章', list(post_options.keys()))
if selected_post_title:
post_id = post_options[selected_post_title]
post = get_post(post_id)
if post:
# 显示文章信息
st.write(f"**ID:** {post[0]}")
st.write(f"**创建时间:** {post[3]}")
# 编辑表单
with st.form(key='edit_form'):
new_title = st.text_input('标题', post[1])
new_category = st.text_input('分类', post[4])
new_tags = st.text_input('标签', post[5])
new_content = st.text_area('内容', post[2], height=300)
col1, col2 = st.columns(2)
with col1:
update_button = st.form_submit_button(label='更新文章')
with col2:
delete_button = st.form_submit_button(label='删除文章', type='primary')
# 处理表单提交
if update_button:
if new_title and new_content:
update_post(post_id, new_title, new_content, new_category, new_tags)
st.success('文章更新成功!')
st.experimental_rerun()
else:
st.error('请填写标题和内容')
if delete_button:
delete_post(post_id)
st.success('文章删除成功!')
st.experimental_rerun()
else:
st.info('暂无文章可管理')
elif choice == '统计':
st.subheader('博客统计')
# 显示文章总数
posts = get_all_posts()
st.write(f"**文章总数:** {len(posts)}")
# 显示分类统计图表
try:
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
stats = get_category_stats()
if not stats.empty:
st.write('**分类统计:**')
fig, ax = plt.subplots()
stats.plot.pie(y='count', labels=stats['category'], autopct='%1.1f%%', ax=ax)
ax.set_ylabel('')
st.pyplot(fig)
else:
st.info('暂无统计数据')
except Exception as e:
st.error(f'生成统计图表时出错: {e}')
if __name__ == '__main__':
main()
运行:
streamlit run blog_app.py

window隐藏 进程
run.bat ---- ANSI 码
powershell -WindowStyle Hidden -Command "streamlit run blog_app.py "
结束这个进程:

-
打开一个新的 PowerShell 或 命令提示符窗口。
-
tasklist | findstr "streamlit"
-
结束进程 :
找到进程后,你会看到进程的 PID 。使用
taskkill
命令结束它。taskkill /PID 1234 /F
/F
参数表示强制结束。
增强版
-添加图片、附件、搜索功能
python
import streamlit as st
import sqlite3
import pandas as pd
from datetime import datetime
import os
# 确保中文正常显示
# 数据库初始化函数
def init_db():
conn = sqlite3.connect('blog.db')
c = conn.cursor()
# 创建博客文章表
c.execute('''
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
category TEXT,
tags TEXT
)
''')
# 创建文件存储表
c.execute('''
CREATE TABLE IF NOT EXISTS post_files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
post_id INTEGER NOT NULL,
file_name TEXT NOT NULL,
file_path TEXT NOT NULL,
file_type TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES posts (id) ON DELETE CASCADE
)
''')
conn.commit()
conn.close()
# 添加新博客文章
def add_post(title, content, category='未分类', tags=''):
conn = sqlite3.connect('blog.db')
c = conn.cursor()
c.execute('''
INSERT INTO posts (title, content, category, tags)
VALUES (?, ?, ?, ?)
''', (title, content, category, tags))
conn.commit()
conn.close()
# 获取所有博客文章
def get_all_posts():
conn = sqlite3.connect('blog.db')
# 使用pandas读取数据,确保中文正常显示
posts = pd.read_sql_query('SELECT * FROM posts ORDER BY created_at DESC', conn)
conn.close()
return posts
# 获取单篇博客文章
def get_post(post_id):
conn = sqlite3.connect('blog.db')
c = conn.cursor()
c.execute('SELECT * FROM posts WHERE id = ?', (post_id,))
post = c.fetchone()
conn.close()
return post
# 更新博客文章
def update_post(post_id, title, content, category, tags):
conn = sqlite3.connect('blog.db')
c = conn.cursor()
c.execute('''
UPDATE posts SET title = ?, content = ?, category = ?, tags = ?
WHERE id = ?
''', (title, content, category, tags, post_id))
conn.commit()
conn.close()
# 删除博客文章
def delete_post(post_id):
conn = sqlite3.connect('blog.db')
c = conn.cursor()
c.execute('DELETE FROM posts WHERE id = ?', (post_id,))
conn.commit()
conn.close()
# 获取分类统计
def get_category_stats():
conn = sqlite3.connect('blog.db')
stats = pd.read_sql_query(
'SELECT category, COUNT(*) as count FROM posts GROUP BY category',
conn
)
conn.close()
return stats
# 搜索博客文章
def search_posts(search_term):
conn = sqlite3.connect('blog.db')
# 使用LIKE进行模糊搜索,搜索标题、内容、分类和标签
query = '''
SELECT * FROM posts
WHERE title LIKE ? OR content LIKE ? OR category LIKE ? OR tags LIKE ?
ORDER BY created_at DESC
'''
search_pattern = f'%{search_term}%'
posts = pd.read_sql_query(query, conn, params=(search_pattern, search_pattern, search_pattern, search_pattern))
conn.close()
return posts
# 保存上传的文件
def save_uploaded_file(uploaded_file, post_id):
# 创建uploads目录
upload_dir = 'uploads'
if not os.path.exists(upload_dir):
os.makedirs(upload_dir)
# 生成唯一文件名
file_extension = os.path.splitext(uploaded_file.name)[1]
file_name = f"{post_id}_{datetime.now().strftime('%Y%m%d%H%M%S')}{file_extension}"
file_path = os.path.join(upload_dir, file_name)
# 保存文件
with open(file_path, "wb") as f:
f.write(uploaded_file.getbuffer())
# 判断文件类型
if file_extension.lower() in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']:
file_type = 'image'
else:
file_type = 'attachment'
# 保存文件信息到数据库
conn = sqlite3.connect('blog.db')
c = conn.cursor()
c.execute('''
INSERT INTO post_files (post_id, file_name, file_path, file_type)
VALUES (?, ?, ?, ?)
''', (post_id, uploaded_file.name, file_path, file_type))
conn.commit()
conn.close()
return file_path
# 获取文章的所有文件
def get_post_files(post_id):
conn = sqlite3.connect('blog.db')
files = pd.read_sql_query('SELECT * FROM post_files WHERE post_id = ? ORDER BY created_at', conn, params=(post_id,))
conn.close()
return files
# 删除文章的文件
def delete_post_files(post_id):
# 获取所有文件
files = get_post_files(post_id)
# 删除物理文件
for _, file_info in files.iterrows():
if os.path.exists(file_info['file_path']):
os.remove(file_info['file_path'])
# 删除数据库记录
conn = sqlite3.connect('blog.db')
c = conn.cursor()
c.execute('DELETE FROM post_files WHERE post_id = ?', (post_id,))
conn.commit()
conn.close()
# 主应用函数
def main():
# 初始化数据库
init_db()
st.title('我的博客')
# 创建侧边栏导航
menu = ['首页', '写文章', '管理文章', '统计']
choice = st.sidebar.selectbox('选择功能', menu)
if choice == '首页':
st.subheader('博客文章')
# 搜索功能
search_term = st.text_input('🔍 搜索文章(支持标题、内容、分类、标签)', '')
# 显示分类筛选
categories = pd.read_sql_query('SELECT DISTINCT category FROM posts', sqlite3.connect('blog.db'))['category'].tolist()
categories.insert(0, '全部')
selected_category = st.selectbox('按分类筛选', categories)
# 获取并显示文章列表
if search_term:
# 如果用户输入了搜索词,优先使用搜索功能
posts = search_posts(search_term)
if not posts.empty:
st.success(f'找到 {len(posts)} 篇相关文章')
elif selected_category == '全部':
posts = get_all_posts()
else:
conn = sqlite3.connect('blog.db')
posts = pd.read_sql_query(
'SELECT * FROM posts WHERE category = ? ORDER BY created_at DESC',
conn,
params=(selected_category,)
)
conn.close()
if not posts.empty:
for _, post in posts.iterrows():
# 使用expander折叠显示文章内容
with st.expander(f"{post['title']} ({post['created_at']})"):
st.write(f"**分类:** {post['category']}")
if post['tags']:
st.write(f"**标签:** {post['tags']}")
# 显示文章内容
st.write(post['content'])
# 显示文章的文件
files = get_post_files(post['id'])
if not files.empty:
st.subheader('相关文件')
for _, file_info in files.iterrows():
if file_info['file_type'] == 'image':
st.image(file_info['file_path'], caption=file_info['file_name'], width=400)
else:
st.write(f"📎 附件: {file_info['file_name']}")
# 显示文件下载链接
if os.path.exists(file_info['file_path']):
with open(file_info['file_path'], "rb") as file:
st.download_button(
label=f"下载 {file_info['file_name']}",
data=file,
file_name=file_info['file_name'],
mime="application/octet-stream"
)
else:
st.info('暂无博客文章')
elif choice == '写文章':
st.subheader('写新文章')
with st.form(key='post_form'):
title = st.text_input('标题')
category = st.text_input('分类', '未分类')
tags = st.text_input('标签(用逗号分隔)')
content = st.text_area('内容', height=300)
# 文件上传功能
st.subheader('上传文件')
uploaded_images = st.file_uploader(
"上传图片",
type=['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'],
accept_multiple_files=True,
help="支持JPG、PNG、GIF、BMP、WebP格式"
)
uploaded_attachments = st.file_uploader(
"上传附件",
accept_multiple_files=True,
help="支持任意文件格式"
)
submit_button = st.form_submit_button(label='发布文章')
if submit_button:
if title and content:
# 添加文章
add_post(title, content, category, tags)
# 获取最新文章的ID
conn = sqlite3.connect('blog.db')
c = conn.cursor()
c.execute('SELECT id FROM posts ORDER BY id DESC LIMIT 1')
post_id = c.fetchone()[0]
conn.close()
# 保存上传的文件
if uploaded_images:
for uploaded_file in uploaded_images:
save_uploaded_file(uploaded_file, post_id)
if uploaded_attachments:
for uploaded_file in uploaded_attachments:
save_uploaded_file(uploaded_file, post_id)
st.success('文章发布成功!')
else:
st.error('请填写标题和内容')
elif choice == '管理文章':
st.subheader('管理文章')
# 显示所有文章进行管理
posts = get_all_posts()
if not posts.empty:
# 创建一个下拉框让用户选择要编辑/删除的文章
post_options = {f"{post['title']} ({post['id']})": post['id'] for _, post in posts.iterrows()}
selected_post_title = st.selectbox('选择文章', list(post_options.keys()))
if selected_post_title:
post_id = post_options[selected_post_title]
post = get_post(post_id)
if post:
# 显示文章信息
st.write(f"**ID:** {post[0]}")
st.write(f"**创建时间:** {post[3]}")
# 编辑表单
with st.form(key='edit_form'):
new_title = st.text_input('标题', post[1])
new_category = st.text_input('分类', post[4])
new_tags = st.text_input('标签', post[5])
new_content = st.text_area('内容', post[2], height=300)
col1, col2 = st.columns(2)
with col1:
update_button = st.form_submit_button(label='更新文章')
with col2:
delete_button = st.form_submit_button(label='删除文章', type='primary')
# 显示当前文件
st.subheader('当前文件')
files = get_post_files(post_id)
if not files.empty:
for _, file_info in files.iterrows():
col1, col2 = st.columns([3, 1])
with col1:
if file_info['file_type'] == 'image':
st.image(file_info['file_path'], caption=file_info['file_name'], width=300)
else:
st.write(f"📎 {file_info['file_name']}")
with col2:
if st.button('删除', key=f"delete_{file_info['id']}"):
if os.path.exists(file_info['file_path']):
os.remove(file_info['file_path'])
conn = sqlite3.connect('blog.db')
c = conn.cursor()
c.execute('DELETE FROM post_files WHERE id = ?', (file_info['id'],))
conn.commit()
conn.close()
st.success('文件删除成功!')
st.experimental_rerun()
else:
st.info('暂无文件')
# 文件上传功能
st.subheader('上传新文件')
uploaded_images = st.file_uploader(
"上传图片",
type=['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'],
accept_multiple_files=True,
key=f"images_{post_id}",
help="支持JPG、PNG、GIF、BMP、WebP格式"
)
uploaded_attachments = st.file_uploader(
"上传附件",
accept_multiple_files=True,
key=f"attachments_{post_id}",
help="支持任意文件格式"
)
# 处理表单提交
if update_button:
if new_title and new_content:
update_post(post_id, new_title, new_content, new_category, new_tags)
# 保存上传的文件
if uploaded_images:
for uploaded_file in uploaded_images:
save_uploaded_file(uploaded_file, post_id)
if uploaded_attachments:
for uploaded_file in uploaded_attachments:
save_uploaded_file(uploaded_file, post_id)
st.success('文章更新成功!')
st.experimental_rerun()
else:
st.error('请填写标题和内容')
if delete_button:
# 删除文章前先删除相关文件
delete_post_files(post_id)
delete_post(post_id)
st.success('文章删除成功!')
st.experimental_rerun()
else:
st.info('暂无文章可管理')
elif choice == '统计':
st.subheader('博客统计')
# 显示文章总数
posts = get_all_posts()
st.write(f"**文章总数:** {len(posts)}")
# 显示分类统计图表
try:
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
stats = get_category_stats()
if not stats.empty:
st.write('**分类统计:**')
fig, ax = plt.subplots()
stats.plot.pie(y='count', labels=stats['category'], autopct='%1.1f%%', ax=ax)
ax.set_ylabel('')
st.pyplot(fig)
else:
st.info('暂无统计数据')
except Exception as e:
st.error(f'生成统计图表时出错: {e}')
if __name__ == '__main__':
main()