图
代码:
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, send_file, send_from_directory
import os
import json
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.pagesizes import A4
import webbrowser
import threading
import time
app = Flask(__name__)
app.secret_key = 'your-secret-key-here'
# 配置
DATA_FILE = 'software_urls.json'
DEFAULT_URL = 'https://gitee.com/'
VERSION = "应用商店"
class SoftwareManager:
def __init__(self):
self.data_file = DATA_FILE
self.webHref = {}
self.default_url = DEFAULT_URL
self.version = VERSION
self.load_data()
self.create_default_files()
def create_default_files(self):
"""创建默认文件"""
# 创建img目录
os.makedirs('img', exist_ok=True)
# 创建默认测试图片
if not os.path.exists('img/test.png'):
self.create_default_test_image()
# 创建示例软件目录和文件
self.create_sample_software_files()
def create_default_test_image(self):
"""创建默认测试图片"""
try:
width, height = 228, 155
img = Image.new('RGB', (width, height), color=(255, 255, 255))
d = ImageDraw.Draw(img)
# 绘制背景
d.rectangle([0, 0, width, height], fill=(39, 47, 60))
# 绘制渐变色条
d.rectangle([20, 88, 200, 122], fill=(0, 214, 225))
d.rectangle([20, 88, 195, 122], fill=(1, 117, 255))
# 添加文字
try:
font_path = 'C:/Windows/Fonts/simhei.ttf'
font_size = 24
font = ImageFont.truetype(font_path, font_size)
except:
font = ImageFont.load_default()
text_position = (30, 92)
d.text(text_position, "测试图片", fill=(255, 255, 255), font=font)
img.save('img/test.png')
print("创建默认测试图片成功")
except Exception as e:
print(f"创建默认测试图片失败:{str(e)}")
def create_sample_software_files(self):
"""创建示例软件文件"""
# 创建示例目录
sample_dirs = ['办公软件', '开发工具', '系统工具']
for dir_name in sample_dirs:
os.makedirs(dir_name, exist_ok=True)
# 为每个目录创建示例文件
if dir_name == '办公软件':
sample_files = ['Word-文档处理.exe', 'Excel-表格处理.exe', 'PowerPoint-演示文稿.exe']
elif dir_name == '开发工具':
sample_files = ['VS Code-代码编辑器.exe', 'PyCharm-Python IDE.exe', 'Git-版本控制.exe']
else:
sample_files = ['CCleaner-系统清理.exe', 'WinRAR-压缩软件.exe', '7-Zip-解压软件.exe']
for file_name in sample_files:
file_path = os.path.join(dir_name, file_name)
if not os.path.exists(file_path):
# 创建空文件作为示例
with open(file_path, 'w', encoding='utf-8') as f:
f.write(f"# {file_name}\n这是一个示例软件文件。")
def load_data(self):
"""加载数据"""
try:
if os.path.exists(self.data_file):
with open(self.data_file, 'r', encoding='utf-8') as f:
data = json.load(f)
if isinstance(data, dict):
self.webHref = data
else:
self.webHref = {}
else:
# 创建默认数据
self.webHref = {
"Word": {"url": "https://www.microsoft.com/zh-cn/microsoft-365/word"},
"Excel": {"url": "https://www.microsoft.com/zh-cn/microsoft-365/excel"},
"PowerPoint": {"url": "https://www.microsoft.com/zh-cn/microsoft-365/powerpoint"},
"VS Code": {"url": "https://code.visualstudio.com/"},
"PyCharm": {"url": "https://www.jetbrains.com/pycharm/"},
"Git": {"url": "https://git-scm.com/"},
"CCleaner": {"url": "https://www.ccleaner.com/"},
"WinRAR": {"url": "https://www.win-rar.com/"},
"7-Zip": {"url": "https://www.7-zip.org/"}
}
self.save_data()
except Exception as e:
print(f"加载数据时出错:{str(e)}")
self.webHref = {}
def save_data(self):
"""保存数据"""
try:
with open(self.data_file, 'w', encoding='utf-8') as f:
json.dump(self.webHref, f, ensure_ascii=False, indent=4, sort_keys=True)
return True
except Exception as e:
print(f"保存数据时出错:{str(e)}")
return False
def add_software(self, name, url):
"""添加软件"""
if name and url:
self.webHref[name] = {'url': url}
return self.save_data()
return False
def update_software(self, name, url):
"""更新软件"""
if name and url:
self.webHref[name] = {'url': url}
return self.save_data()
return False
def delete_software(self, name):
"""删除软件"""
if name in self.webHref:
del self.webHref[name]
return self.save_data()
return False
def get_string_before_dash(self, s):
"""获取字符串中破折号之前的内容"""
dash_index = s.find('-')
return s[:dash_index] if dash_index != -1 else s
def get_software_list(self):
"""获取软件列表,按目录分类"""
software_categories = {}
for root, _, files in os.walk('.'):
dirname = os.path.basename(root)
if not dirname or dirname == '.' or dirname == 'img' or dirname == 'templates':
continue
# 为每个目录创建一个分类
if dirname not in software_categories:
software_categories[dirname] = []
for file in files:
base_name = self.get_string_before_dash(file)
url_info = self.webHref.get(base_name, {})
if isinstance(url_info, dict):
official_url = url_info.get('url', self.default_url)
else:
official_url = self.default_url
image_path = f'img/{base_name}.png'
has_image = os.path.exists(image_path)
software_info = {
'name': base_name,
'file': file,
'directory': dirname,
'url': official_url,
'has_image': has_image,
'image_path': image_path if has_image else 'img/test.png'
}
software_categories[dirname].append(software_info)
return software_categories
def generate_html(self):
"""生成HTML文件"""
try:
with open("index.html", "w", encoding="utf-8") as f:
# HTML头部
f.write('<!DOCTYPE html>\n<html lang="zh-CHS">\n<head>\n')
f.write('<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n')
f.write('<title>应用商店</title>\n')
f.write('<link rel="icon" href="img/favicon.ico" type="image/x-icon">\n')
f.write('<link rel="shortcut icon" href="img/favicon.ico" type="image/x-icon">\n')
f.write('<link rel="stylesheet" href="index.css">\n</head>\n<body>\n')
# 导航菜单
f.write('<div>\n<div class="nav">\n<h3>目录</h3>\n<ul>\n')
for root, _, _ in os.walk('.'):
dirname = os.path.basename(root)
if not dirname or dirname == '.' or dirname == 'img' or dirname == 'templates':
continue
f.write(f'<li><a href="#{dirname}">{dirname}</a></li>\n')
f.write('</ul></div>\n')
# 内容区域
f.write('<div class="course wrapper">\n<br>\n')
# 生成软件列表
i = 0
for root, _, files in os.walk('.'):
dirname = os.path.basename(root)
if not dirname or dirname == '.' or dirname == 'img' or dirname == 'templates':
continue
i += 1
f.write(f'<div class="hd"><h3 id="{dirname}">{dirname}</h3></div>\n')
f.write(f'<div class="bd bdx{i}"><ul>\n')
for file in files:
base_name = self.get_string_before_dash(file)
url_info = self.webHref.get(base_name, {})
if isinstance(url_info, dict):
official_url = url_info.get('url', self.default_url)
else:
official_url = self.default_url
image_path = f'img/{base_name}.png'
image_src = image_path if os.path.exists(image_path) else 'img/test.png'
f.write(f'''<li>
<div class="pic">
<img src="{image_src}" onerror="replaceImage(this);">
</div>
<div class="text">
<h3>{file}</h3>
<p>
<a href="{dirname}/{file}"><span>下载</span></a>
<a class="{base_name}" href="{official_url}" target="_blank"><strong>外网</strong></a>
<i>{dirname}</i>
</p>
</div>
</li>\n''')
f.write('</ul></div>\n')
# HTML尾部
f.write('</div>\n</div>\n')
f.write('<script>function replaceImage(img) {img.onerror = null;img.src = "img/test.png";}</script>\n')
f.write('<script src="index.js"></script>\n')
f.write('</body>\n</html>\n')
return True
except Exception as e:
print(f"生成HTML时出错:{str(e)}")
return False
def generate_pdf(self):
"""生成PDF文件"""
try:
pdf_filename = "software.pdf"
c = canvas.Canvas(pdf_filename, pagesize=A4)
# 设置中文字体
try:
pdfmetrics.registerFont(TTFont('SimSun', 'SimSun.ttf'))
c.setFont('SimSun', 12)
except:
c.setFont('Helvetica', 12)
y_pos = 790
x_pos = 20
c.drawString(250, 800, f"{self.version} PDF版")
for root, _, files in os.walk('.'):
dirname = os.path.basename(root)
if not dirname or dirname == '.' or dirname == 'img' or dirname == 'templates':
continue
if y_pos <= 60:
c.showPage()
try:
c.setFont('SimSun', 12)
except:
c.setFont('Helvetica', 12)
y_pos = 790
c.drawString(x_pos, y_pos, '-' * 40)
c.drawString(x_pos, y_pos-7, dirname)
c.drawString(x_pos, y_pos-14, '-' * 40)
y_pos -= 30
for file in files:
if y_pos <= 50:
c.showPage()
try:
c.setFont('SimSun', 12)
except:
c.setFont('Helvetica', 12)
y_pos = 790
base_name = self.get_string_before_dash(file)
url_info = self.webHref.get(base_name, {})
if isinstance(url_info, dict):
url = url_info.get('url', self.default_url)
else:
url = self.default_url
file_info = f"{file[:-4]} --> {url}"
c.drawString(x_pos, y_pos, file_info)
y_pos -= 15
c.save()
return True
except Exception as e:
print(f"生成PDF时出错:{str(e)}")
return False
def generate_js(self):
"""生成JS文件"""
try:
with open("index.js", "w", encoding="utf-8") as f:
f.write('// 1. 初始数据\n')
f.write('const sliderData = [\n')
for root, _, files in os.walk('.'):
dirname = os.path.basename(root)
if not dirname or dirname == '.' or dirname == 'img' or dirname == 'templates':
continue
for file in files:
base_name = self.get_string_before_dash(file)
url_info = self.webHref.get(base_name, {})
if isinstance(url_info, dict):
url = url_info.get('url', self.default_url)
else:
url = self.default_url
f.write(f' {{ name:"{base_name}", url:"{url}" }},\n')
f.write(']\n\n')
f.write('// 1. 获取元素\n')
f.write('for(t = 0; t < sliderData.length; t++) {\n')
f.write(' let cname = sliderData[t].name\n')
f.write(' let a = document.querySelector(`.${cname}`)\n')
f.write(' if(a) a.href = sliderData[t].url\n')
f.write('}\n')
return True
except Exception as e:
print(f"生成JS时出错:{str(e)}")
return False
def generate_md(self):
"""生成Markdown文件"""
try:
with open("software.md", "w", encoding="utf-8") as f:
# 写入标题
f.write(f"# 软件管理 {self.version}\n\n")
f.write("> 本文档由软件管理器自动生成\n\n")
# 写入目录
f.write("## 目录\n\n")
for root, _, _ in os.walk('.'):
dirname = os.path.basename(root)
if not dirname or dirname == '.' or dirname == 'img' or dirname == 'templates':
continue
f.write(f"- [{dirname}](#{dirname})\n")
f.write("\n")
# 写入软件列表
for root, _, files in os.walk('.'):
dirname = os.path.basename(root)
if not dirname or dirname == '.' or dirname == 'img' or dirname == 'templates':
continue
f.write(f"## {dirname}\n\n")
f.write("| 软件名 | 图标 | 外网链接 |\n")
f.write("|--------|------|----------|\n")
for file in files:
base_name = self.get_string_before_dash(file)
url_info = self.webHref.get(base_name, {})
if isinstance(url_info, dict):
official_url = url_info.get('url', self.default_url)
else:
official_url = self.default_url
image_path = f'img/{base_name}.png'
if os.path.exists(image_path):
image_markdown = f""
else:
image_markdown = "无图标"
f.write(f"| {file} | {image_markdown} | [外网]({official_url}) |\n")
f.write("\n")
return True
except Exception as e:
print(f"生成Markdown时出错:{str(e)}")
return False
def generate_icon(self, name, text):
"""生成软件图标"""
try:
if not name or not text:
return False
# 创建一个新的白色背景图片
width, height = 228, 155
img = Image.new('RGB', (width, height), color=(255, 255, 255))
# 创建绘图对象
d = ImageDraw.Draw(img)
# 绘制背景
d.rectangle([0, 0, width, height], fill=(39, 47, 60))
# 绘制渐变色条
d.rectangle([20, 88, 200, 122], fill=(0, 214, 225))
d.rectangle([20, 88, 195, 122], fill=(1, 117, 255))
# 添加文字
try:
font_path = 'C:/Windows/Fonts/simhei.ttf'
font_size = 24
font = ImageFont.truetype(font_path, font_size)
except:
# 如果找不到中文字体,使用默认字体
font = ImageFont.load_default()
text_position = (30, 92)
d.text(text_position, text, fill=(255, 255, 255), font=font)
# 确保保存路径存在
save_path = 'img'
os.makedirs(save_path, exist_ok=True)
# 保存图片
full_path = os.path.join(save_path, f'{name}.png')
img.save(full_path)
return True
except Exception as e:
print(f"生成图标时出错:{str(e)}")
return False
# 创建全局软件管理器实例
software_manager = SoftwareManager()
@app.route('/')
def index():
"""主页"""
software_list = software_manager.get_software_list()
return render_template('index.html', software_list=software_list, webHref=software_manager.webHref)
@app.route('/img/<path:filename>')
def serve_image(filename):
"""提供图片文件"""
return send_from_directory('img', filename)
@app.route('/<path:filename>')
def serve_file(filename):
"""提供其他文件"""
if os.path.exists(filename):
return send_file(filename)
else:
return "文件不存在", 404
@app.route('/preview_file/<path:filename>')
def preview_file(filename):
"""预览文件内容"""
if os.path.exists(filename):
try:
# 获取文件扩展名
file_ext = os.path.splitext(filename)[1].lower()
# 如果是文本文件,直接显示内容
if file_ext in ['.txt', '.md', '.py', '.js', '.html', '.css', '.json', '.xml', '.log']:
with open(filename, 'r', encoding='utf-8') as f:
content = f.read()
return render_template('file_preview.html', filename=filename, content=content, file_type='text')
# 如果是图片文件,显示图片
elif file_ext in ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico']:
return send_file(filename)
# 如果是其他文件,显示文件信息
else:
file_size = os.path.getsize(filename)
file_mtime = os.path.getmtime(filename)
import datetime
mtime_str = datetime.datetime.fromtimestamp(file_mtime).strftime('%Y-%m-%d %H:%M:%S')
return render_template('file_preview.html',
filename=filename,
file_size=file_size,
file_mtime=mtime_str,
file_type='binary')
except Exception as e:
return f"读取文件时出错:{str(e)}", 500
else:
return "文件不存在", 404
@app.route('/api/software', methods=['GET'])
def get_software():
"""获取软件列表API"""
return jsonify(software_manager.webHref)
@app.route('/api/software', methods=['POST'])
def add_software():
"""添加软件API"""
data = request.get_json()
name = data.get('name', '').strip()
url = data.get('url', '').strip()
if not name or not url:
return jsonify({'success': False, 'message': '软件名和URL都不能为空!'})
if software_manager.add_software(name, url):
return jsonify({'success': True, 'message': '添加成功!'})
else:
return jsonify({'success': False, 'message': '添加失败!'})
@app.route('/api/software/<name>', methods=['PUT'])
def update_software(name):
"""更新软件API"""
data = request.get_json()
url = data.get('url', '').strip()
if not url:
return jsonify({'success': False, 'message': 'URL不能为空!'})
if software_manager.update_software(name, url):
return jsonify({'success': True, 'message': '更新成功!'})
else:
return jsonify({'success': False, 'message': '更新失败!'})
@app.route('/api/software/<name>', methods=['DELETE'])
def delete_software(name):
"""删除软件API"""
if software_manager.delete_software(name):
return jsonify({'success': True, 'message': '删除成功!'})
else:
return jsonify({'success': False, 'message': '删除失败!'})
@app.route('/generate/html')
def generate_html():
"""生成HTML文件"""
if software_manager.generate_html():
flash('HTML文件生成成功!', 'success')
else:
flash('HTML文件生成失败!', 'error')
return redirect(url_for('index'))
@app.route('/generate/pdf')
def generate_pdf():
"""生成PDF文件"""
if software_manager.generate_pdf():
flash('PDF文件生成成功!', 'success')
else:
flash('PDF文件生成失败!', 'error')
return redirect(url_for('index'))
@app.route('/generate/js')
def generate_js():
"""生成JS文件"""
if software_manager.generate_js():
flash('JS文件生成成功!', 'success')
else:
flash('JS文件生成失败!', 'error')
return redirect(url_for('index'))
@app.route('/generate/md')
def generate_md():
"""生成MD文件"""
if software_manager.generate_md():
flash('MD文件生成成功!', 'success')
else:
flash('MD文件生成失败!', 'error')
return redirect(url_for('index'))
@app.route('/generate/icon', methods=['POST'])
def generate_icon():
"""生成图标"""
name = request.form.get('name', '').strip()
text = request.form.get('text', '').strip()
if not name or not text:
flash('名称和内容都不能为空!', 'error')
return redirect(url_for('index'))
if software_manager.generate_icon(name, text):
# 如果软件不存在,添加到列表中
if name not in software_manager.webHref:
software_manager.add_software(name, software_manager.default_url)
flash('图标生成成功!', 'success')
else:
flash('图标生成失败!', 'error')
return redirect(url_for('index'))
@app.route('/preview/<name>')
def preview_software(name):
"""预览软件"""
url_info = software_manager.webHref.get(name, {})
if isinstance(url_info, dict):
url = url_info.get('url', software_manager.default_url)
else:
url = software_manager.default_url
if not url.startswith(('http://', 'https://')):
url = 'http://' + url
# 在新线程中打开浏览器
def open_browser():
time.sleep(0.5)
webbrowser.open(url)
threading.Thread(target=open_browser).start()
flash(f'正在打开 {url}', 'info')
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
依赖包:requirements.txt (pip install -r requirements.txt)
Flask
Pillow
reportlab
简要说明:
这是一个使用Flask框架开发的软件管理Web应用,主要功能是管理本地软件列表并提供多种格式的导出功能。
### 核心功能模块
1. 1.
SoftwareManager类 :
- 负责软件数据的加载、保存和管理
- 创建默认文件和示例软件目录结构
- 生成HTML、PDF、JS、Markdown等多种格式的软件列表
- 生成软件图标
2. 2.
Web界面与路由 :
- 主页显示分类软件列表
- 文件预览和下载功能
- RESTful API接口用于软件的增删改查
- 生成各类格式文件的接口
- 软件官方网站预览功能
### 技术栈
- Flask:Web框架
- PIL (Pillow):图像处理
- ReportLab:PDF生成
- JSON:数据存储
- HTML/CSS/JS:前端展示
### 数据结构
- 使用JSON文件存储软件名称和官方URL映射关系
- 软件按目录分类展示
- 每个软件包含名称、文件路径、图标、官方URL等信息
### 运行方式
程序通过 app.run(debug=True, host='0.0.0.0', port=5000) 启动,默认监听在5000端口,可通过浏览器访问。### 主要API端点
- GET /api/software :获取软件列表
- POST /api/software :添加软件
- PUT /api/software/<name> :更新软件
- DELETE /api/software/<name> :删除软件
- /generate/<format> :生成不同格式文件
这个应用适合用于本地软件库管理,支持多种导出格式,方便分享和查阅。